diff options
author | Rob Herring <r.herring@freescale.com> | 2009-10-19 14:43:19 -0500 |
---|---|---|
committer | Rob Herring <r.herring@freescale.com> | 2009-10-26 16:57:04 -0500 |
commit | 460b55880e47a98943f5072bc03ffcfcc8a40a14 (patch) | |
tree | 5690552665f0b7843e6552e4d5fe7b63cbc78f51 | |
parent | 40abba66d676c6c7aff57a5fbd1345974c90b2fe (diff) |
ENGR00117389 Port 5.0.0 release to 2.6.31
This is i.MX BSP 5.0.0 release ported to 2.6.31
Signed-off-by: Rob Herring <r.herring@freescale.com>
Signed-off-by: Alan Tull <r80115@freescale.com>
Signed-off-by: Xinyu Chen <xinyu.chen@freescale.com>
860 files changed, 347710 insertions, 1112 deletions
diff --git a/Documentation/imx_nfc.txt b/Documentation/imx_nfc.txt new file mode 100644 index 000000000000..7fec999c2884 --- /dev/null +++ b/Documentation/imx_nfc.txt @@ -0,0 +1,369 @@ +i.MX NAND Flash Controller Driver Documentation +=============================================== + + +Definitions of Terms +==================== + +To avoid confusion, let's carefully define some of the terms we'll be using: + + "NAND Flash Chip" or "Chip" + A NAND Flash storage array and controller (including a chip select + signal, ready/busy signal, data register, etc.). + + "NAND Flash Package" or "Package" + A hardware package containing one or more NAND Flash chips that share + data lines and most control signals, but with separate chip select + and ready/busy signals. Package "boundaries" are unimportant to MTD. + + "NAND Flash Medium" or "Medium" + A collection of one or more NAND Flash chips that the system views as + logically contiguous. + + "Memory Technology Device" or "MTD" + An abstraction of underlying memory hardware, represented by a single + struct mtd_info. + + "NAND Flash MTD" + An abstraction of a NAND Flash medium that is represented by a single + struct nand_chip (the name of this structure is misleading because it + has evolved to represent an entire medium, not just a single chip). + All the physical chips in a NAND Flash MTD medium have answered the + "Read ID" command with the same manufacturer code and device code and + are presumed to have identical characteristics. + + "NAND Flash MTD Hardware-independent Layer" or "HIL" + The code that implements an MTD interface and drives all NAND Flash + MTDs. + + "NAND Flash MTD Hardware Abstraction Layer" or "HAL" + Code that implements the internal NAND Flash hardware model and + matches it to specific hardware. + + "NAND Flash MTD Reference Implementation" + A reference implementation of a HAL "stack." + + "Out-of-Band" or "OOB" + An adjective describing information that is not part of the "data" in + a NAND Flash page. NAND Flash pages are generally described in terms + of the amount of data they can hold (e.g., "2K pages" or "4K pages"). + The physical page size is actually larger than this and the + additional bytes are called "out-of-band." + + + +The Structure of the MTD NAND Flash System +========================================== + +The following figure illustrates how control flows down from the system's +MTD interface into the NAND Flash MTD system and down to the hardware- +specific implementation (this driver): + + + + / +---------------------------------------+ + | | MTD | + | +---------------------------------------+ + MTD | | + | | +----------+ + | | | | + | v v | + \ ======================================== | + - struct mtd_info | + / ======================================== | + | | | + | v | + | +---------------------------------------+ | + | | NAND Flash MTD | | + NAND Flash | | Interface Functions | | + Hardware- | +---------------------------------------+ | + Independent | | | | + Layer | | v | + (HIL) | +-------------+ | +-------------+ +-------------+ + | | Init | | | Support | | Reference | + | | Functions | | | Functions | |BBT Functions| + | +-------------+ | +-------------+ +-------------+ + | | | | ^ + | v v v | + \ ======================================== | + - struct nand_chip | + / ======================================== | + | | ^ | ^ | | + NAND Flash | | | | | | | + Hardware | v | v | | | + Abstraction | +--------------+ +---------------+ | | + Layer | | Hardware- | | Reference | +-----------+ + (HAL) | | Specific | | | + | |Implementation| |Implementations| + \ +--------------+ +---------------+ + + + +The function pointers and attributes in struct mtd_info embody an abstract +model of memory technology devices. + +The struct nand_chip is an aggregation of two categories of function pointers +and attributes: + + - Function pointers and attributes used by the HIL. These members embody + an abstract model of NAND Flash media, or a hardware abstraction + layer (HAL). + + - Function pointers and attributes used by the reference implementations. + +The single most confusing thing about the MTD NAND Flash system is that +struct nand_chip contains all the main HAL members mixed up with all the +members used only by the reference implementation, without any clear +distinction. Recognizing the distinction is critical for understanding the +relationship between the HIL and HAL, and can greatly simplify driver +implementation. + +The fundamental operations represented by the function pointers in +struct nand_chip fall into the following categories (from conceptually +"low-level" to "high-level"): + + - Signal Control + - Chip Control + - Low-level I/O + - ECC Control + - ECC-aware I/O + - Error Recovery + - High-level I/O + - Bad Block Management + +The HIL uses only the following "Replaceable" function pointers in +struct nand_chip: + + - Signal Control + - None + - Chip Control + - dev_ready + - select_chip + - cmdfunc + - waitfunc + - Low-level I/O + - read_byte + - ECC Control + - None + - ECC-aware I/O + - ecc.read_page + - ecc.read_page_raw + - Error Recovery + - None + - High-level I/O + - write_page + - ecc.read_oob + - ecc.write_oob + - Bad Block Management + - block_bad + - block_markbad + - scan_bbt + +Note that the HIL calls erase_cmd, but this member is marked "Internal." + +The HIL uses only the following commands with cmdfunc: + + * NAND_CMD_STATUS + - nand_check_wp() - Checks if the current chip is + write-protected. + * NAND_CMD_READID + - nand_get_flash_type() - Gets information about the first chip. + - nand_scan_ident() - Scans for additional chips. + * NAND_CMD_RESET + - nand_do_write_oob() - Clears a bug observed on the + Toshiba TC5832DC and DiskOnChip 2000. + * NAND_CMD_READ0 + - nand_do_read_ops() - Used to begin a full page read (both with + and without ECC). + * NAND_CMD_ERASE1 + - single_erase_cmd() - Starts a block erase operation. + - multi_erase_cmd() - Starts a block erase operation. + * NAND_CMD_ERASE2 + - single_erase_cmd() - Finishes a block erase operation. + - multi_erase_cmd() - Finishes a block erase operation. + + +Since this is all the HIL uses, this is all a driver needs to implement. + + + +The Structure of the imx_nfc Driver +=================================== + +This driver supports many different versions of underlying controller, and +also supports higher-level abstractions like interleaving. To facilitate this +versatility, the code is layered as shown in the following diagram: + + + +--------------------------------------+ + | MTD | + +--------------------------------------+ + | NAND Flash MTD | + ======================================== <-- struct nand_chip + / | MTD Interface Layer (mil_*) | + | | +----------------------------------+ + | | | Medium Abstraction Layer (mal_*) | + imx_nfc | | | | + driver | | | +------------------------+ + | | | | NFC Utils (nfc_util_*) | + | ======================================== <-- struct nfc_hal + \ | NFC HAL (nfc_x_y_*) | + ======================================== <-- Hardware Interface + | NFC Hardware | + +--------------------------------------+ + + +MTD Interface Layer +------------------- +This layer includes functions that the NAND Flash MTD system calls directly. +In a manner of speaking, this layer "parses" the function calls made by MTD +and translates them into fundamental operations understood by the Medium +Abstraction Layer. Some simple operations don't need any abstraction, so code +in this layer can sometimes use the NFC HAL directly. + + +Medium Abstraction Layer +------------------------ +This layer implements the abstract model of the NAND Flash medium and hides +details that shouldn't concern higher layers (e.g., interleaving). + + +NFC Utilities +------------- +These functions make it easier to use the NFC HAL. Even though this layer +is shown above the NFC HAL in the diagram, it's actually possible for the +NFC HAL to call some of these functions. + + +NFC HAL +------- +This layer implements the abstract model of an i.MX NAND Flash controller. + + +Other Collections of Functions +------------------------------ + +- System Interface + - imx_nfc_* + +- sysfs Interface + - get_module_* + - set_module_* + - show_device_* + - store_device_* + + + + +i.MX NAND Flash Controller Versions +=================================== + +The i.MX NAND Flash controller (NFC) has evolved over time. Both its memory +layout and behaviors have changed. In this driver, we use major and minor +version numbers to label these stages in the NFC's evolution. These version +numbers are very useful, but they are entirely a figment of this driver's +imagination -- you will never find them in Freescale hardware reference +manuals. + +When the platform code instantiates an i.MX NFC device, it provides a struct +imx_nfc_platform_data that contains platform-specific information. This +includes the major and minor version numbers for the NFC. This driver uses +the version numbers to select an appopriate NFC HAL structure. + + + +i.MX NFC Memory Map +=================== + +While many things have changed during the evolution of the NFC, much has +remained the same. All i.MX NFCs have two or three essential memory-mapped +regions: a set of buffers, and one or two sets of registers (one on the AXI +bus and perhaps a second on the IP bus). + +The buffer area contains the captive memory to which the NFC writes data +received from the NAND Flash medium. This area is subdivided into several +contiguous "main" buffers that hold 512-byte chunks of data, and several +"spare" buffers that hold varying-size chunks of out-of-band bytes. The +number of main buffers is always the same as the number of spare buffers, but +the exact count and the size of the spare buffers varies across NFC versions. + +The register areas contain the NFC's control interface. Some versions have +only one set of registers, and some have two. + +The platform-specific resources passed to this driver include the starting +and ending physical addresses of the buffer and register areas. This driver +maps those physical addresses to virtual addresses, and then uses version- +specific offsets and bit masks to operate the NFC. + + + +Matching the NAND Flash MTD Page Model with the i.MX NFC +======================================================== + +The NAND Flash MTD HAL model views a page as containing two essentially +independent groups of bytes: "data" bytes and "out-of-band" bytes. If the +underlying physical format has data and out-of-band bytes distributed across +the page, they must be reassembled before being delivered to the caller +(e.g., see the function nand_read_page_syndrome(), which is part of the +reference implementation). + +The i.MX NFC hardware imposes both a physical page layout and a layout in its +memory buffer that differ from the HAL model. The following figure shows how +all these layouts relate to each other: + + + i.MX NFC i.MX NFC + Physical Memory NAND Flash + Page Buffers MTD Model + + +--------+ +--------+ +--------+ + |OOB[N-1]| |OOB[N-1]| |OOB[N-1]| + +--------+ +--------+ +--------+ + | | <gap> |OOB[ 1 ]| + | Data | +--------+ +--------+ + | [N-1] | |OOB[ 1 ]| |OOB[ 0 ]| + | | +--------+ +--------+ + +--------+ <gap> + ... +--------+ +--------+ + +--------+ |OOB[ 0 ]| | | + |OOB[ 1 ]| +--------+ | Data | + +--------+ | | | [N-1] | + | | | Data | | | + | Data | | [N-1] | +--------+ + | [ 1 ] | | | | | + | | +--------+ | Data | + +--------+ ... | [ 1 ] | + |OOB[ 0 ]| +--------+ | | + +--------+ | | +--------+ + | | | Data | | | + | Data | | [ 1 ] | | Data | + | [ 0 ] | | | | [ 0 ] | + | | +--------+ | | + +--------+ | | +--------+ + | Data | + | [ 0 ] | + | | + +--------+ + + +The NFC memory is *almost* what we want, but not quite. The problems are: + + 1) There are gaps between the out-of-band fragments. + + 2) The NFC memory responds only to 16-byte or 32-byte reads and writes. + +To resolve these problems, we've encapsulated the NFC memory behind these +functions: + + nfc_util_copy_from_the_nfc() - Copies data to main memory from the the NFC. + nfc_util_copy_to_the_nfc() - Copies data from main memory to the NFC. + +These functions don't take pointers to locations within the NFC memory - they +take a "column address." These functions know how to skip over the gaps and +handle the NFC memory such that it looks like all the data and out-of-band +bytes are completely contiguous. They also handle copying arbitrary bytes +from/to a memory that only responds to 16- or 32-byte reads/writes. If you're +accessing the NFC memory without these functions, you're *probably* doing +something wrong. + + diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 00eb350ef811..954e50bf91c5 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -294,7 +294,8 @@ config ARCH_MXC select ARCH_MTD_XIP select GENERIC_GPIO select ARCH_REQUIRE_GPIOLIB - select HAVE_CLK +# select HAVE_CLK + select ZONE_DMA help Support for Freescale MXC/iMX-based family of processors @@ -308,6 +309,7 @@ config ARCH_STMP3XXX select GENERIC_CLOCKEVENTS select GENERIC_GPIO select USB_ARCH_HAS_EHCI + select ZONE_DMA help Support for systems based on the Freescale 3xxx CPUs. @@ -1077,7 +1079,7 @@ config LEDS ARCH_OMAP || ARCH_P720T || ARCH_PXA_IDP || \ ARCH_SA1100 || ARCH_SHARK || ARCH_VERSATILE || \ ARCH_AT91 || ARCH_DAVINCI || \ - ARCH_KS8695 || MACH_RD88F5182 || ARCH_REALVIEW + ARCH_KS8695 || MACH_RD88F5182 || ARCH_REALVIEW || ARCH_MXC help If you say Y here, the LEDs on your machine will be used to provide useful information about your current system status. @@ -1254,7 +1256,7 @@ endmenu menu "CPU Power Management" -if (ARCH_SA1100 || ARCH_INTEGRATOR || ARCH_OMAP || ARCH_PXA || ARCH_S3C64XX) +if (ARCH_SA1100 || ARCH_INTEGRATOR || ARCH_OMAP || ARCH_PXA || ARCH_S3C64XX || ARCH_MXC || ARCH_STMP3XXX) source "drivers/cpufreq/Kconfig" @@ -1289,6 +1291,12 @@ config CPU_FREQ_S3C64XX bool "CPUfreq support for Samsung S3C64XX CPUs" depends on CPU_FREQ && CPU_S3C6410 +config CPU_FREQ_IMX + tristate "CPUfreq driver for i.MX CPUs" + depends on ARCH_MXC && CPU_FREQ && REGULATOR + help + This enables the CPUfreq driver for i.MX CPUs. + endif source "drivers/cpuidle/Kconfig" @@ -1436,6 +1444,8 @@ source "drivers/char/Kconfig" source "drivers/i2c/Kconfig" +source "drivers/i2c-slave/Kconfig" + source "drivers/spi/Kconfig" source "drivers/gpio/Kconfig" @@ -1490,6 +1500,10 @@ source "drivers/uio/Kconfig" source "drivers/staging/Kconfig" +if ARCH_MXC +source "drivers/mxc/Kconfig" +endif + endmenu source "fs/Kconfig" diff --git a/arch/arm/Makefile b/arch/arm/Makefile index c877d6df23d1..d22dff606aac 100644 --- a/arch/arm/Makefile +++ b/arch/arm/Makefile @@ -50,7 +50,7 @@ comma = , # Note that GCC does not numerically define an architecture version # macro, but instead defines a whole series of macros which makes # testing for a specific architecture or later rather impossible. -arch-$(CONFIG_CPU_32v7) :=-D__LINUX_ARM_ARCH__=7 $(call cc-option,-march=armv7-a,-march=armv5t -Wa$(comma)-march=armv7-a) +arch-$(CONFIG_CPU_32v7) :=-D__LINUX_ARM_ARCH__=7 $(call cc-option,-march=armv7a,-march=armv5t -Wa$(comma)-march=armv7a) arch-$(CONFIG_CPU_32v6) :=-D__LINUX_ARM_ARCH__=6 $(call cc-option,-march=armv6,-march=armv5t -Wa$(comma)-march=armv6) # Only override the compiler option if ARMv6. The ARMv6K extensions are # always available in ARMv7 @@ -135,7 +135,11 @@ machine-$(CONFIG_ARCH_MSM) := msm machine-$(CONFIG_ARCH_MV78XX0) := mv78xx0 machine-$(CONFIG_ARCH_MX1) := mx1 machine-$(CONFIG_ARCH_MX2) := mx2 +machine-$(CONFIG_ARCH_MX25) := mx25 machine-$(CONFIG_ARCH_MX3) := mx3 +machine-$(CONFIG_ARCH_MX35) := mx35 +machine-$(CONFIG_ARCH_MX37) := mx37 +machine-$(CONFIG_ARCH_MX51) := mx51 machine-$(CONFIG_ARCH_NETX) := netx machine-$(CONFIG_ARCH_NS9XXX) := ns9xxx machine-$(CONFIG_ARCH_OMAP1) := omap1 diff --git a/arch/arm/configs/imx233_defconfig b/arch/arm/configs/imx233_defconfig new file mode 100644 index 000000000000..af4cd4e0a771 --- /dev/null +++ b/arch/arm/configs/imx233_defconfig @@ -0,0 +1,1682 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.28 +# Fri Jun 12 19:40:44 2009 +# +CONFIG_ARM=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +CONFIG_GENERIC_GPIO=y +CONFIG_GENERIC_TIME=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_MMU=y +# CONFIG_NO_IOPORT is not set +CONFIG_GENERIC_HARDIRQS=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +# CONFIG_ARCH_HAS_ILOG2_U32 is not set +# CONFIG_ARCH_HAS_ILOG2_U64 is not set +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_LOCK_KERNEL=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="" +CONFIG_LOCALVERSION_AUTO=y +# CONFIG_SWAP is not set +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +# CONFIG_POSIX_MQUEUE is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set +# CONFIG_AUDIT is not set +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=14 +# CONFIG_CGROUPS is not set +# CONFIG_GROUP_SCHED is not set +CONFIG_SYSFS_DEPRECATED=y +CONFIG_SYSFS_DEPRECATED_V2=y +# CONFIG_RELAY is not set +# CONFIG_NAMESPACES is not set +# CONFIG_BLK_DEV_INITRD is not set +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_SYSCTL=y +CONFIG_EMBEDDED=y +CONFIG_UID16=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_KALLSYMS=y +# CONFIG_KALLSYMS_EXTRA_PASS is not set +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +CONFIG_COMPAT_BRK=y +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_ANON_INODES=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_AIO=y +CONFIG_VM_EVENT_COUNTERS=y +CONFIG_SLUB_DEBUG=y +# CONFIG_SLAB is not set +CONFIG_SLUB=y +# CONFIG_SLOB is not set +# CONFIG_PROFILING is not set +# CONFIG_MARKERS is not set +CONFIG_HAVE_OPROFILE=y +# CONFIG_KPROBES is not set +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +# CONFIG_TINY_SHMEM is not set +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_KMOD=y +CONFIG_BLOCK=y +# CONFIG_LBD is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_LSF is not set +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_INTEGRITY is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_AS=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_AS is not set +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" +CONFIG_CLASSIC_RCU=y +CONFIG_FREEZER=y + +# +# System Type +# +# CONFIG_ARCH_AAEC2000 is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_VERSATILE is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_CLPS7500 is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_H720X is not set +# CONFIG_ARCH_IMX is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP23XX is not set +# CONFIG_ARCH_IXP2000 is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_KIRKWOOD is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_NS9XXX is not set +# CONFIG_ARCH_LOKI is not set +# CONFIG_ARCH_MV78XX0 is not set +# CONFIG_ARCH_MXC is not set +# CONFIG_ARCH_ORION5X is not set +# CONFIG_ARCH_PNX4008 is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_SA1100 is not set +# CONFIG_ARCH_S3C2410 is not set +# CONFIG_ARCH_SHARK is not set +# CONFIG_ARCH_LH7A40X is not set +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_OMAP is not set +# CONFIG_ARCH_MSM is not set +CONFIG_ARCH_STMP3XXX=y + +# +# Boot options +# + +# +# Power management +# + +# +# SigmaTel STMP3xxx implementations +# +# CONFIG_ARCH_STMP37XX is not set +CONFIG_ARCH_STMP378X=y +# CONFIG_MACH_STMP37XX is not set +CONFIG_MACH_STMP378X=y +CONFIG_FB_STMP37XX_LMS350=y +CONFIG_FB_STMP37XX_LMS430=y +CONFIG_FB_STMP378X_TVENC=y +CONFIG_STMP3XXX_UNIQUE_ID=y +CONFIG_STMP3XXX_UNIQUE_ID_OTP=y +CONFIG_STMP378X_RAM_FREQ_SCALING=y +# CONFIG_STMP378X_RAM_MDDR is not set +CONFIG_STMP378X_RAM_DDR=y + +# +# Processor Type +# +CONFIG_CPU_32=y +CONFIG_CPU_ARM926T=y +CONFIG_CPU_32v5=y +CONFIG_CPU_ABRT_EV5TJ=y +CONFIG_CPU_PABRT_NOIFAR=y +CONFIG_CPU_CACHE_VIVT=y +CONFIG_CPU_COPY_V4WB=y +CONFIG_CPU_TLB_V4WBI=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y + +# +# Processor Features +# +CONFIG_ARM_THUMB=y +# CONFIG_CPU_ICACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_WRITETHROUGH is not set +# CONFIG_CPU_CACHE_ROUND_ROBIN is not set +# CONFIG_OUTER_CACHE is not set + +# +# Bus support +# +CONFIG_ISA=y +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +# CONFIG_PCCARD is not set + +# +# Kernel Features +# +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +CONFIG_VMSPLIT_3G=y +# CONFIG_VMSPLIT_2G is not set +# CONFIG_VMSPLIT_1G is not set +CONFIG_PAGE_OFFSET=0xC0000000 +CONFIG_PREEMPT=y +CONFIG_HZ=100 +CONFIG_AEABI=y +# CONFIG_OABI_COMPAT is not set +CONFIG_ARCH_FLATMEM_HAS_HOLES=y +# CONFIG_ARCH_SPARSEMEM_DEFAULT is not set +# CONFIG_ARCH_SELECT_MEMORY_MODEL is not set +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_SPLIT_PTLOCK_CPUS=4096 +# CONFIG_RESOURCES_64BIT is not set +# CONFIG_PHYS_ADDR_T_64BIT is not set +CONFIG_ZONE_DMA_FLAG=0 +CONFIG_VIRT_TO_BUS=y +CONFIG_UNEVICTABLE_LRU=y +CONFIG_ALIGNMENT_TRAP=y + +# +# Boot options +# +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="console=ttyAM0,115200 root=/dev/mmcblk0p2 rootwait lcd_panel=lms350" +# CONFIG_XIP_KERNEL is not set +# CONFIG_KEXEC is not set + +# +# CPU Power Management +# +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_TABLE=y +# CONFIG_CPU_FREQ_DEBUG is not set +CONFIG_CPU_FREQ_STAT=y +# CONFIG_CPU_FREQ_STAT_DETAILS is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set +CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +# CONFIG_CPU_IDLE is not set + +# +# Floating point emulation +# + +# +# At least one emulation must be selected +# +# CONFIG_VFP is not set + +# +# Userspace binary formats +# +CONFIG_BINFMT_ELF=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_HAVE_AOUT=y +# CONFIG_BINFMT_AOUT is not set +# CONFIG_BINFMT_MISC is not set + +# +# Power management options +# +CONFIG_PM=y +# CONFIG_PM_DEBUG is not set +CONFIG_PM_SLEEP=y +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +# CONFIG_APM_EMULATION is not set +CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +CONFIG_PACKET_MMAP=y +CONFIG_UNIX=y +CONFIG_XFRM=y +# CONFIG_XFRM_USER is not set +# CONFIG_XFRM_SUB_POLICY is not set +# CONFIG_XFRM_MIGRATE is not set +# CONFIG_XFRM_STATISTICS is not set +# CONFIG_NET_KEY is not set +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_ASK_IP_FIB_HASH=y +# CONFIG_IP_FIB_TRIE is not set +CONFIG_IP_FIB_HASH=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +# CONFIG_IP_PNP_RARP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_IP_MROUTE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +# CONFIG_INET_TUNNEL is not set +CONFIG_INET_XFRM_MODE_TRANSPORT=y +CONFIG_INET_XFRM_MODE_TUNNEL=y +CONFIG_INET_XFRM_MODE_BEET=y +# CONFIG_INET_LRO is not set +CONFIG_INET_DIAG=y +CONFIG_INET_TCP_DIAG=y +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +# CONFIG_IPV6 is not set +# CONFIG_NETLABEL is not set +# CONFIG_NETWORK_SECMARK is not set +# CONFIG_NETFILTER is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_NET_DSA is not set +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +CONFIG_NET_SCHED=y + +# +# Queueing/Scheduling +# +# CONFIG_NET_SCH_CBQ is not set +# CONFIG_NET_SCH_HTB is not set +# CONFIG_NET_SCH_HFSC is not set +# CONFIG_NET_SCH_PRIO is not set +# CONFIG_NET_SCH_MULTIQ is not set +# CONFIG_NET_SCH_RED is not set +# CONFIG_NET_SCH_SFQ is not set +# CONFIG_NET_SCH_TEQL is not set +# CONFIG_NET_SCH_TBF is not set +# CONFIG_NET_SCH_GRED is not set +# CONFIG_NET_SCH_DSMARK is not set +# CONFIG_NET_SCH_NETEM is not set + +# +# Classification +# +# CONFIG_NET_CLS_BASIC is not set +# CONFIG_NET_CLS_TCINDEX is not set +# CONFIG_NET_CLS_ROUTE4 is not set +# CONFIG_NET_CLS_FW is not set +# CONFIG_NET_CLS_U32 is not set +# CONFIG_NET_CLS_RSVP is not set +# CONFIG_NET_CLS_RSVP6 is not set +# CONFIG_NET_CLS_FLOW is not set +# CONFIG_NET_EMATCH is not set +# CONFIG_NET_CLS_ACT is not set +CONFIG_NET_SCH_FIFO=y + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_HAMRADIO is not set +# CONFIG_CAN is not set +# CONFIG_IRDA is not set +# CONFIG_BT is not set +# CONFIG_AF_RXRPC is not set +# CONFIG_PHONET is not set +CONFIG_FIB_RULES=y +CONFIG_WIRELESS=y +# CONFIG_CFG80211 is not set +CONFIG_WIRELESS_OLD_REGULATORY=y +CONFIG_WIRELESS_EXT=y +CONFIG_WIRELESS_EXT_SYSFS=y +# CONFIG_MAC80211 is not set +CONFIG_IEEE80211=y +# CONFIG_IEEE80211_DEBUG is not set +# CONFIG_IEEE80211_CRYPT_WEP is not set +# CONFIG_IEEE80211_CRYPT_CCMP is not set +# CONFIG_IEEE80211_CRYPT_TKIP is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +CONFIG_FIRMWARE_IN_KERNEL=y +CONFIG_EXTRA_FIRMWARE="" +# CONFIG_SYS_HYPERVISOR is not set +CONFIG_CONNECTOR=y +CONFIG_PROC_EVENTS=y +CONFIG_MTD=y +# CONFIG_MTD_DEBUG is not set +# CONFIG_MTD_CONCAT is not set +CONFIG_MTD_PARTITIONS=y +# CONFIG_MTD_REDBOOT_PARTS is not set +CONFIG_MTD_CMDLINE_PARTS=y +# CONFIG_MTD_AFS_PARTS is not set +# CONFIG_MTD_AR7_PARTS is not set + +# +# User Modules And Translation Layers +# +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLKDEVS=y +CONFIG_MTD_BLOCK=y +# CONFIG_FTL is not set +# CONFIG_NFTL is not set +# CONFIG_INFTL is not set +# CONFIG_RFD_FTL is not set +# CONFIG_SSFDC is not set +# CONFIG_MTD_OOPS is not set + +# +# RAM/ROM/Flash chip drivers +# +# CONFIG_MTD_CFI is not set +# CONFIG_MTD_JEDECPROBE is not set +CONFIG_MTD_MAP_BANK_WIDTH_1=y +CONFIG_MTD_MAP_BANK_WIDTH_2=y +CONFIG_MTD_MAP_BANK_WIDTH_4=y +# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set +CONFIG_MTD_CFI_I1=y +CONFIG_MTD_CFI_I2=y +# CONFIG_MTD_CFI_I4 is not set +# CONFIG_MTD_CFI_I8 is not set +# CONFIG_MTD_RAM is not set +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_ABSENT is not set + +# +# Mapping drivers for chip access +# +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_PLATRAM is not set + +# +# Self-contained MTD device drivers +# +# CONFIG_MTD_DATAFLASH is not set +# CONFIG_MTD_M25P80 is not set +# CONFIG_MTD_SLRAM is not set +# CONFIG_MTD_PHRAM is not set +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_BLOCK2MTD is not set + +# +# Disk-On-Chip Device Drivers +# +# CONFIG_MTD_DOC2000 is not set +# CONFIG_MTD_DOC2001 is not set +# CONFIG_MTD_DOC2001PLUS is not set +CONFIG_MTD_NAND=y +# CONFIG_MTD_NAND_VERIFY_WRITE is not set +# CONFIG_MTD_NAND_ECC_SMC is not set +# CONFIG_MTD_NAND_MUSEUM_IDS is not set +# CONFIG_MTD_NAND_GPIO is not set +CONFIG_MTD_NAND_IDS=y +# CONFIG_MTD_NAND_DISKONCHIP is not set +# CONFIG_MTD_NAND_NANDSIM is not set +CONFIG_MTD_NAND_GPMI_LBA=m +CONFIG_MTD_NAND_GPMI=m +CONFIG_MTD_NAND_GPMI_SYSFS_ENTRIES=y +CONFIG_MTD_NAND_GPMI_BCH=y +CONFIG_MTD_NAND_GPMI_TA1=y +CONFIG_MTD_NAND_GPMI_TA3=y +# CONFIG_MTD_NAND_PLATFORM is not set +# CONFIG_MTD_ALAUDA is not set +# CONFIG_MTD_ONENAND is not set + +# +# UBI - Unsorted block images +# +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_WL_THRESHOLD=128 +CONFIG_MTD_UBI_BEB_RESERVE=2 +CONFIG_MTD_UBI_GLUEBI=y + +# +# UBI debugging options +# +# CONFIG_MTD_UBI_DEBUG is not set +# CONFIG_PARPORT is not set +# CONFIG_PNP is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_CRYPTOLOOP=y +# CONFIG_BLK_DEV_NBD is not set +# CONFIG_BLK_DEV_UB is not set +# CONFIG_BLK_DEV_RAM is not set +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +# CONFIG_MISC_DEVICES is not set +CONFIG_HAVE_IDE=y +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_TGT is not set +# CONFIG_SCSI_NETLINK is not set +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +# CONFIG_BLK_DEV_SR is not set +# CONFIG_CHR_DEV_SG is not set +# CONFIG_CHR_DEV_SCH is not set + +# +# Some SCSI devices (e.g. CD jukebox) support multiple LUNs +# +# CONFIG_SCSI_MULTI_LUN is not set +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +CONFIG_SCSI_WAIT_SCAN=m + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +# CONFIG_SCSI_LOWLEVEL is not set +# CONFIG_SCSI_DH is not set +# CONFIG_ATA is not set +# CONFIG_MD is not set +CONFIG_NETDEVICES=y +# CONFIG_DUMMY is not set +# CONFIG_BONDING is not set +# CONFIG_MACVLAN is not set +# CONFIG_EQUALIZER is not set +# CONFIG_TUN is not set +# CONFIG_VETH is not set +# CONFIG_ARCNET is not set +# CONFIG_PHYLIB is not set +CONFIG_NET_ETHERNET=y +# CONFIG_MII is not set +# CONFIG_AX88796 is not set +# CONFIG_NET_VENDOR_3COM is not set +# CONFIG_NET_VENDOR_SMC is not set +# CONFIG_SMC91X is not set +# CONFIG_DM9000 is not set +CONFIG_ENC28J60=y +# CONFIG_ENC28J60_WRITEVERIFY is not set +# CONFIG_SMC911X is not set +# CONFIG_SMSC911X is not set +# CONFIG_NET_VENDOR_RACAL is not set +# CONFIG_AT1700 is not set +# CONFIG_DEPCA is not set +# CONFIG_HP100 is not set +# CONFIG_NET_ISA is not set +# CONFIG_IBM_NEW_EMAC_ZMII is not set +# CONFIG_IBM_NEW_EMAC_RGMII is not set +# CONFIG_IBM_NEW_EMAC_TAH is not set +# CONFIG_IBM_NEW_EMAC_EMAC4 is not set +# CONFIG_IBM_NEW_EMAC_NO_FLOW_CTRL is not set +# CONFIG_IBM_NEW_EMAC_MAL_CLR_ICINTSTAT is not set +# CONFIG_IBM_NEW_EMAC_MAL_COMMON_ERR is not set +# CONFIG_NET_PCI is not set +# CONFIG_B44 is not set +# CONFIG_CS89x0 is not set +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +# CONFIG_TR is not set + +# +# Wireless LAN +# +# CONFIG_WLAN_PRE80211 is not set +# CONFIG_WLAN_80211 is not set +# CONFIG_IWLWIFI_LEDS is not set + +# +# USB Network Adapters +# +# CONFIG_USB_CATC is not set +# CONFIG_USB_KAWETH is not set +# CONFIG_USB_PEGASUS is not set +# CONFIG_USB_RTL8150 is not set +# CONFIG_USB_USBNET is not set +# CONFIG_WAN is not set +# CONFIG_PPP is not set +# CONFIG_SLIP is not set +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_ISDN is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +CONFIG_INPUT_POLLDEV=y + +# +# Userland interfaces +# +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_MOUSEDEV_PSAUX=y +CONFIG_INPUT_MOUSEDEV_SCREEN_X=320 +CONFIG_INPUT_MOUSEDEV_SCREEN_Y=240 +# CONFIG_INPUT_JOYDEV is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +# CONFIG_KEYBOARD_GPIO is not set +CONFIG_KEYBOARD_STMP3XXX=y +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TABLET is not set +CONFIG_INPUT_TOUCHSCREEN=y +# CONFIG_TOUCHSCREEN_ADS7846 is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_INEXIO is not set +# CONFIG_TOUCHSCREEN_MK712 is not set +CONFIG_TOUCHSCREEN_STMP3XXX=y +# CONFIG_TOUCHSCREEN_HTCPEN is not set +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +# CONFIG_TOUCHSCREEN_TOUCHIT213 is not set +CONFIG_INPUT_MISC=y +CONFIG_INPUT_STMP3XXX_ROTDEC=y +# CONFIG_INPUT_ATI_REMOTE is not set +# CONFIG_INPUT_ATI_REMOTE2 is not set +# CONFIG_INPUT_KEYSPAN_REMOTE is not set +# CONFIG_INPUT_POWERMATE is not set +# CONFIG_INPUT_YEALINK is not set +# CONFIG_INPUT_CM109 is not set +# CONFIG_INPUT_UINPUT is not set + +# +# Hardware I/O ports +# +CONFIG_SERIO=y +CONFIG_SERIO_SERPORT=y +# CONFIG_SERIO_LIBPS2 is not set +# CONFIG_SERIO_RAW is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +# CONFIG_VT_HW_CONSOLE_BINDING is not set +CONFIG_DEVKMEM=y +CONFIG_MXS_VIIM=y +# CONFIG_SERIAL_NONSTANDARD is not set + +# +# Serial drivers +# +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_SERIAL_STMP_DBG=y +CONFIG_SERIAL_STMP_DBG_CONSOLE=y +CONFIG_SERIAL_STMP_APP=y +CONFIG_UNIX98_PTYS=y +# CONFIG_LEGACY_PTYS is not set +# CONFIG_IPMI_HANDLER is not set +CONFIG_HW_RANDOM=y +# CONFIG_NVRAM is not set +# CONFIG_DTLK is not set +# CONFIG_R3964 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_DEVPORT=y +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_CHARDEV=m +CONFIG_I2C_HELPER_AUTO=y +CONFIG_I2C_ALGOBIT=m + +# +# I2C Hardware Bus support +# + +# +# I2C system bus drivers (mostly embedded / system-on-chip) +# +# CONFIG_I2C_GPIO is not set +CONFIG_I2C_STMP378X=m +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_SIMTEC is not set + +# +# External I2C/SMBus adapter drivers +# +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_TINY_USB is not set + +# +# Other I2C/SMBus bus drivers +# +# CONFIG_I2C_ELEKTOR is not set +# CONFIG_I2C_PCA_ISA is not set +# CONFIG_I2C_PCA_PLATFORM is not set +# CONFIG_I2C_STUB is not set + +# +# Miscellaneous I2C Chip support +# +# CONFIG_DS1682 is not set +# CONFIG_AT24 is not set +# CONFIG_SENSORS_EEPROM is not set +# CONFIG_SENSORS_PCF8574 is not set +# CONFIG_PCF8575 is not set +# CONFIG_SENSORS_PCA9539 is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_SENSORS_MAX6875 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CHIP is not set +# CONFIG_I2C_SLAVE is not set +CONFIG_SPI=y +CONFIG_SPI_MASTER=y + +# +# SPI Master Controller Drivers +# +# CONFIG_SPI_BITBANG is not set +CONFIG_SPI_STMP3XXX=y + +# +# SPI Protocol Masters +# +# CONFIG_SPI_AT25 is not set +# CONFIG_SPI_SPIDEV is not set +# CONFIG_SPI_TLE62X0 is not set +# CONFIG_W1 is not set +CONFIG_POWER_SUPPLY=y +# CONFIG_POWER_SUPPLY_DEBUG is not set +# CONFIG_PDA_POWER is not set +# CONFIG_BATTERY_DS2760 is not set +# CONFIG_BATTERY_BQ27x00 is not set +CONFIG_BATTERY_STMP3XXX=y +# CONFIG_HWMON is not set +# CONFIG_THERMAL is not set +# CONFIG_THERMAL_HWMON is not set +CONFIG_WATCHDOG=y +# CONFIG_WATCHDOG_NOWAYOUT is not set + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +CONFIG_STMP3XXX_WATCHDOG=y + +# +# ISA-based Watchdog Cards +# +# CONFIG_PCWATCHDOG is not set +# CONFIG_MIXCOMWD is not set +# CONFIG_WDT is not set + +# +# USB-based Watchdog Cards +# +# CONFIG_USBPCWATCHDOG is not set +CONFIG_SSB_POSSIBLE=y + +# +# Sonics Silicon Backplane +# +# CONFIG_SSB is not set + +# +# Multifunction device drivers +# +# CONFIG_MFD_CORE is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_HTC_PASIC3 is not set +# CONFIG_MFD_TMIO is not set +# CONFIG_PMIC_DA903X is not set +# CONFIG_MFD_WM8400 is not set +# CONFIG_MFD_WM8350_I2C is not set + +# +# Multimedia devices +# + +# +# Multimedia core support +# +CONFIG_VIDEO_DEV=y +CONFIG_VIDEO_V4L2_COMMON=y +# CONFIG_VIDEO_ALLOW_V4L1 is not set +CONFIG_VIDEO_V4L1_COMPAT=y +# CONFIG_DVB_CORE is not set +CONFIG_VIDEO_MEDIA=y + +# +# Multimedia drivers +# +# CONFIG_MEDIA_ATTACH is not set +CONFIG_MEDIA_TUNER=y +# CONFIG_MEDIA_TUNER_CUSTOMIZE is not set +CONFIG_MEDIA_TUNER_SIMPLE=y +CONFIG_MEDIA_TUNER_TDA8290=y +CONFIG_MEDIA_TUNER_TDA9887=y +CONFIG_MEDIA_TUNER_TEA5761=y +CONFIG_MEDIA_TUNER_TEA5767=y +CONFIG_MEDIA_TUNER_MT20XX=y +CONFIG_MEDIA_TUNER_XC2028=y +CONFIG_MEDIA_TUNER_XC5000=y +CONFIG_VIDEO_V4L2=y +CONFIG_VIDEOBUF_GEN=y +CONFIG_VIDEOBUF_DMA_CONTIG=y +CONFIG_VIDEO_CAPTURE_DRIVERS=y +# CONFIG_VIDEO_ADV_DEBUG is not set +# CONFIG_VIDEO_FIXED_MINOR_RANGES is not set +# CONFIG_VIDEO_HELPER_CHIPS_AUTO is not set + +# +# Encoders/decoders and other helper chips +# + +# +# Audio decoders +# +# CONFIG_VIDEO_TVAUDIO is not set +# CONFIG_VIDEO_TDA7432 is not set +# CONFIG_VIDEO_TDA9840 is not set +# CONFIG_VIDEO_TDA9875 is not set +# CONFIG_VIDEO_TEA6415C is not set +# CONFIG_VIDEO_TEA6420 is not set +# CONFIG_VIDEO_MSP3400 is not set +# CONFIG_VIDEO_CS5345 is not set +# CONFIG_VIDEO_CS53L32A is not set +# CONFIG_VIDEO_M52790 is not set +# CONFIG_VIDEO_TLV320AIC23B is not set +# CONFIG_VIDEO_WM8775 is not set +# CONFIG_VIDEO_WM8739 is not set +# CONFIG_VIDEO_VP27SMPX is not set + +# +# Video decoders +# +# CONFIG_VIDEO_OV7670 is not set +# CONFIG_VIDEO_TCM825X is not set +# CONFIG_VIDEO_SAA711X is not set +# CONFIG_VIDEO_SAA717X is not set +# CONFIG_VIDEO_TVP5150 is not set + +# +# Video and audio decoders +# +# CONFIG_VIDEO_CX25840 is not set + +# +# MPEG video encoders +# +# CONFIG_VIDEO_CX2341X is not set + +# +# Video encoders +# +# CONFIG_VIDEO_SAA7127 is not set + +# +# Video improvement chips +# +# CONFIG_VIDEO_UPD64031A is not set +# CONFIG_VIDEO_UPD64083 is not set +# CONFIG_VIDEO_VIVI is not set +CONFIG_VIDEO_PXP=y +# CONFIG_VIDEO_SAA5246A is not set +# CONFIG_VIDEO_SAA5249 is not set +# CONFIG_SOC_CAMERA is not set +CONFIG_V4L_USB_DRIVERS=y +# CONFIG_USB_VIDEO_CLASS is not set +CONFIG_USB_GSPCA=m +# CONFIG_USB_M5602 is not set +# CONFIG_USB_GSPCA_CONEX is not set +# CONFIG_USB_GSPCA_ETOMS is not set +# CONFIG_USB_GSPCA_FINEPIX is not set +# CONFIG_USB_GSPCA_MARS is not set +# CONFIG_USB_GSPCA_OV519 is not set +# CONFIG_USB_GSPCA_PAC207 is not set +# CONFIG_USB_GSPCA_PAC7311 is not set +# CONFIG_USB_GSPCA_SONIXB is not set +# CONFIG_USB_GSPCA_SONIXJ is not set +# CONFIG_USB_GSPCA_SPCA500 is not set +# CONFIG_USB_GSPCA_SPCA501 is not set +# CONFIG_USB_GSPCA_SPCA505 is not set +# CONFIG_USB_GSPCA_SPCA506 is not set +# CONFIG_USB_GSPCA_SPCA508 is not set +# CONFIG_USB_GSPCA_SPCA561 is not set +# CONFIG_USB_GSPCA_STK014 is not set +# CONFIG_USB_GSPCA_SUNPLUS is not set +# CONFIG_USB_GSPCA_T613 is not set +# CONFIG_USB_GSPCA_TV8532 is not set +# CONFIG_USB_GSPCA_VC032X is not set +# CONFIG_USB_GSPCA_ZC3XX is not set +# CONFIG_VIDEO_PVRUSB2 is not set +# CONFIG_VIDEO_EM28XX is not set +# CONFIG_VIDEO_USBVISION is not set +# CONFIG_USB_ET61X251 is not set +# CONFIG_USB_SN9C102 is not set +# CONFIG_USB_ZC0301 is not set +# CONFIG_USB_ZR364XX is not set +# CONFIG_USB_STKWEBCAM is not set +# CONFIG_USB_S2255 is not set +CONFIG_RADIO_ADAPTERS=y +# CONFIG_RADIO_CADET is not set +# CONFIG_RADIO_RTRACK is not set +# CONFIG_RADIO_RTRACK2 is not set +# CONFIG_RADIO_AZTECH is not set +# CONFIG_RADIO_GEMTEK is not set +# CONFIG_RADIO_SF16FMI is not set +# CONFIG_RADIO_SF16FMR2 is not set +# CONFIG_RADIO_TERRATEC is not set +# CONFIG_RADIO_TRUST is not set +# CONFIG_RADIO_TYPHOON is not set +# CONFIG_RADIO_ZOLTRIX is not set +# CONFIG_USB_DSBR is not set +# CONFIG_USB_SI470X is not set +# CONFIG_USB_MR800 is not set +CONFIG_RADIO_STFM1000=m +CONFIG_RADIO_STFM1000_ALSA=m +# CONFIG_DAB is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +# CONFIG_VIDEO_OUTPUT_CONTROL is not set +CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +# CONFIG_FB_DDC is not set +# CONFIG_FB_BOOT_VESA_SUPPORT is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_FOREIGN_ENDIAN is not set +# CONFIG_FB_SYS_FOPS is not set +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_MODE_HELPERS is not set +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# +# CONFIG_FB_UVESA is not set +# CONFIG_FB_S1D13XXX is not set +CONFIG_FB_STMP37XX=y +# CONFIG_FB_VIRTUAL is not set +# CONFIG_FB_METRONOME is not set +# CONFIG_FB_MB862XX is not set +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_LCD_CLASS_DEVICE=y +# CONFIG_LCD_LTV350QV is not set +# CONFIG_LCD_ILI9320 is not set +# CONFIG_LCD_TDO24M is not set +# CONFIG_LCD_VGG2432A4 is not set +# CONFIG_LCD_PLATFORM is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_CORGI is not set +CONFIG_BACKLIGHT_STMP37XX=y + +# +# Display device support +# +# CONFIG_DISPLAY_SUPPORT is not set + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +# CONFIG_MDA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE=y +# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +# CONFIG_FONTS is not set +CONFIG_FONT_8x8=y +CONFIG_FONT_8x16=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_LOGO_LINUX_CLUT224=y +CONFIG_SOUND=y +# CONFIG_SOUND_OSS_CORE is not set +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +# CONFIG_SND_SEQUENCER is not set +# CONFIG_SND_MIXER_OSS is not set +# CONFIG_SND_PCM_OSS is not set +# CONFIG_SND_DYNAMIC_MINORS is not set +# CONFIG_SND_SUPPORT_OLD_API is not set +CONFIG_SND_VERBOSE_PROCFS=y +# CONFIG_SND_VERBOSE_PRINTK is not set +# CONFIG_SND_DEBUG is not set +# CONFIG_SND_DRIVERS is not set +# CONFIG_SND_ARM is not set +# CONFIG_SND_SPI is not set +CONFIG_SND_USB=y +# CONFIG_SND_USB_AUDIO is not set +# CONFIG_SND_USB_CAIAQ is not set +CONFIG_SND_SOC=y +CONFIG_SND_STMP3XXX_SOC=y +CONFIG_SND_STMP3XXX_SOC_DAI=y +CONFIG_SND_STMP3XXX_SOC_SPDIF_DAI=y +CONFIG_SND_STMP3XXX_SOC_STMP3780_DEVB=y +CONFIG_SND_STMP3XXX_SOC_STMP3780_DEVB_SPDIF=y +# CONFIG_SND_SOC_ALL_CODECS is not set +CONFIG_SND_SOC_STMP378X_CODEC=y +CONFIG_SND_SOC_STMP3XXX_SPDIF=y +# CONFIG_SOUND_PRIME is not set +# CONFIG_HID_SUPPORT is not set +CONFIG_USB_SUPPORT=y +CONFIG_USB_ARCH_HAS_HCD=y +# CONFIG_USB_ARCH_HAS_OHCI is not set +CONFIG_USB_ARCH_HAS_EHCI=y +CONFIG_USB=y +# CONFIG_USB_DEBUG is not set +# CONFIG_USB_ANNOUNCE_NEW_DEVICES is not set + +# +# Miscellaneous USB options +# +# CONFIG_USB_DEVICEFS is not set +CONFIG_USB_DEVICE_CLASS=y +# CONFIG_USB_DYNAMIC_MINORS is not set +# CONFIG_USB_SUSPEND is not set +# CONFIG_USB_OTG is not set +# CONFIG_USB_OTG_WHITELIST is not set +# CONFIG_USB_OTG_BLACKLIST_HUB is not set +CONFIG_USB_MON=y +# CONFIG_USB_WUSB is not set +# CONFIG_USB_WUSB_CBAF is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_C67X00_HCD is not set +CONFIG_USB_EHCI_HCD=m +CONFIG_USB_EHCI_ARC=y +CONFIG_USB_EHCI_ARC_OTG=y +# CONFIG_USB_STATIC_IRAM is not set +# CONFIG_USB_EHCI_FSL_MC13783 is not set +# CONFIG_USB_EHCI_FSL_1301 is not set +# CONFIG_USB_EHCI_FSL_1504 is not set +CONFIG_USB_EHCI_FSL_UTMI=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +# CONFIG_USB_ISP116X_HCD is not set +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +# CONFIG_USB_HWA_HCD is not set +# CONFIG_USB_GADGET_MUSB_HDRC is not set + +# +# USB Device Class drivers +# +# CONFIG_USB_ACM is not set +# CONFIG_USB_PRINTER is not set +# CONFIG_USB_WDM is not set +# CONFIG_USB_TMC is not set + +# +# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; +# + +# +# see USB_STORAGE Help for more information +# +CONFIG_USB_STORAGE=y +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +# CONFIG_USB_STORAGE_FREECOM is not set +# CONFIG_USB_STORAGE_ISD200 is not set +# CONFIG_USB_STORAGE_DPCM is not set +# CONFIG_USB_STORAGE_USBAT is not set +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_ONETOUCH is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set +# CONFIG_USB_LIBUSUAL is not set + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set + +# +# USB port drivers +# +# CONFIG_USB_SERIAL is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_SEVSEG is not set +# CONFIG_USB_RIO500 is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_BERRY_CHARGE is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_PHIDGET is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_USB_SISUSBVGA is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_ISIGHTFW is not set +# CONFIG_USB_VST is not set +CONFIG_USB_GADGET=m +# CONFIG_USB_GADGET_DEBUG_FILES is not set +CONFIG_USB_GADGET_VBUS_DRAW=2 +CONFIG_USB_GADGET_SELECTED=y +# CONFIG_USB_GADGET_AT91 is not set +# CONFIG_USB_GADGET_ATMEL_USBA is not set +# CONFIG_USB_GADGET_FSL_USB2 is not set +# CONFIG_USB_GADGET_LH7A40X is not set +# CONFIG_USB_GADGET_OMAP is not set +# CONFIG_USB_GADGET_PXA25X is not set +# CONFIG_USB_GADGET_PXA27X is not set +# CONFIG_USB_GADGET_S3C2410 is not set +# CONFIG_USB_GADGET_M66592 is not set +# CONFIG_USB_GADGET_AMD5536UDC is not set +# CONFIG_USB_GADGET_FSL_QE is not set +# CONFIG_USB_GADGET_NET2280 is not set +# CONFIG_USB_GADGET_GOKU is not set +CONFIG_USB_GADGET_ARC=y +CONFIG_USB_ARC=m +# CONFIG_USB_GADGET_DUMMY_HCD is not set +CONFIG_USB_GADGET_DUALSPEED=y +CONFIG_USB_GADGET_ARC_OTG=y +# CONFIG_USB_GADGET_FSL_MC13783 is not set +# CONFIG_USB_GADGET_FSL_1301 is not set +# CONFIG_USB_GADGET_FSL_1504 is not set +CONFIG_USB_GADGET_FSL_UTMI=y +# CONFIG_USB_ZERO is not set +CONFIG_USB_ETH=m +CONFIG_USB_ETH_RNDIS=y +# CONFIG_USB_GADGETFS is not set +CONFIG_USB_FILE_STORAGE=m +# CONFIG_USB_FILE_STORAGE_TEST is not set +CONFIG_USB_G_SERIAL=m +# CONFIG_USB_MIDI_GADGET is not set +# CONFIG_USB_G_PRINTER is not set +# CONFIG_USB_CDC_COMPOSITE is not set +CONFIG_MMC=y +# CONFIG_MMC_DEBUG is not set +CONFIG_MMC_UNSAFE_RESUME=y + +# +# MMC/SD/SDIO Card Drivers +# +CONFIG_MMC_BLOCK=y +CONFIG_MMC_BLOCK_BOUNCE=y +# CONFIG_SDIO_UART is not set +# CONFIG_MMC_TEST is not set + +# +# MMC/SD/SDIO Host Controller Drivers +# +# CONFIG_MMC_SDHCI is not set +# CONFIG_MMC_SPI is not set +# CONFIG_MMC_IMX_ESDHCI_PIO_MODE is not set +CONFIG_MMC_STMP3XXX=y +# CONFIG_MEMSTICK is not set +# CONFIG_ACCESSIBILITY is not set +CONFIG_NEW_LEDS=y +# CONFIG_LEDS_CLASS is not set + +# +# LED drivers +# + +# +# LED Triggers +# +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +# CONFIG_LEDS_TRIGGER_BACKLIGHT is not set +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +CONFIG_RTC_INTF_DEV_UIE_EMUL=y +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set +# CONFIG_RTC_DRV_S35390A is not set +# CONFIG_RTC_DRV_FM3130 is not set +# CONFIG_RTC_DRV_RX8581 is not set + +# +# SPI RTC drivers +# +# CONFIG_RTC_DRV_M41T94 is not set +# CONFIG_RTC_DRV_DS1305 is not set +# CONFIG_RTC_DRV_DS1390 is not set +# CONFIG_RTC_DRV_MAX6902 is not set +# CONFIG_RTC_DRV_R9701 is not set +# CONFIG_RTC_DRV_RS5C348 is not set +# CONFIG_RTC_DRV_DS3234 is not set + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_BQ4802 is not set +# CONFIG_RTC_DRV_V3020 is not set + +# +# on-CPU RTC drivers +# +CONFIG_RTC_DRV_STMP3XXX=y +# CONFIG_DMADEVICES is not set +CONFIG_REGULATOR=y +# CONFIG_REGULATOR_DEBUG is not set +# CONFIG_REGULATOR_FIXED_VOLTAGE is not set +# CONFIG_REGULATOR_VIRTUAL_CONSUMER is not set +# CONFIG_REGULATOR_BQ24022 is not set +CONFIG_REGULATOR_STMP3XXX=y +# CONFIG_UIO is not set + +# +# File systems +# +CONFIG_EXT2_FS=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT2_FS_POSIX_ACL=y +CONFIG_EXT2_FS_SECURITY=y +# CONFIG_EXT2_FS_XIP is not set +CONFIG_EXT3_FS=y +CONFIG_EXT3_FS_XATTR=y +CONFIG_EXT3_FS_POSIX_ACL=y +CONFIG_EXT3_FS_SECURITY=y +# CONFIG_EXT4_FS is not set +CONFIG_JBD=y +CONFIG_FS_MBCACHE=y +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +CONFIG_FS_POSIX_ACL=y +CONFIG_FILE_LOCKING=y +# CONFIG_XFS_FS is not set +# CONFIG_OCFS2_FS is not set +CONFIG_DNOTIFY=y +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_QUOTA is not set +# CONFIG_AUTOFS_FS is not set +# CONFIG_AUTOFS4_FS is not set +# CONFIG_FUSE_FS is not set + +# +# CD-ROM/DVD Filesystems +# +# CONFIG_ISO9660_FS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +# CONFIG_TMPFS_POSIX_ACL is not set +# CONFIG_HUGETLB_PAGE is not set +# CONFIG_CONFIGFS_FS is not set + +# +# Miscellaneous filesystems +# +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_ECRYPT_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_DEBUG=0 +CONFIG_JFFS2_FS_WRITEBUFFER=y +# CONFIG_JFFS2_FS_WBUF_VERIFY is not set +# CONFIG_JFFS2_SUMMARY is not set +# CONFIG_JFFS2_FS_XATTR is not set +# CONFIG_JFFS2_COMPRESSION_OPTIONS is not set +CONFIG_JFFS2_ZLIB=y +# CONFIG_JFFS2_LZO is not set +CONFIG_JFFS2_RTIME=y +# CONFIG_JFFS2_RUBIN is not set +CONFIG_UBIFS_FS=y +# CONFIG_UBIFS_FS_XATTR is not set +CONFIG_UBIFS_FS_ADVANCED_COMPR=y +CONFIG_UBIFS_FS_LZO=y +CONFIG_UBIFS_FS_ZLIB=y +# CONFIG_UBIFS_FS_DEBUG is not set +# CONFIG_CRAMFS is not set +# CONFIG_VXFS_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +# CONFIG_NFS_V3_ACL is not set +# CONFIG_NFS_V4 is not set +CONFIG_ROOT_NFS=y +# CONFIG_NFSD is not set +CONFIG_LOCKD=y +CONFIG_LOCKD_V4=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=y +# CONFIG_SUNRPC_REGISTER_V4 is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +# CONFIG_RPCSEC_GSS_SPKM3 is not set +# CONFIG_SMB_FS is not set +# CONFIG_CIFS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +CONFIG_NLS_UTF8=y +# CONFIG_DLM is not set + +# +# Kernel hacking +# +# CONFIG_PRINTK_TIME is not set +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +CONFIG_FRAME_WARN=1024 +# CONFIG_MAGIC_SYSRQ is not set +# CONFIG_UNUSED_SYMBOLS is not set +# CONFIG_DEBUG_FS is not set +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_KERNEL is not set +# CONFIG_SLUB_DEBUG_ON is not set +# CONFIG_SLUB_STATS is not set +# CONFIG_DEBUG_BUGVERBOSE is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +CONFIG_FRAME_POINTER=y +# CONFIG_RCU_CPU_STALL_DETECTOR is not set +# CONFIG_LATENCYTOP is not set +CONFIG_SYSCTL_SYSCALL_CHECK=y +CONFIG_HAVE_FUNCTION_TRACER=y + +# +# Tracers +# +# CONFIG_DYNAMIC_PRINTK_DEBUG is not set +# CONFIG_SAMPLES is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_DEBUG_USER is not set + +# +# Security options +# +CONFIG_KEYS=y +CONFIG_KEYS_DEBUG_PROC_KEYS=y +CONFIG_SECURITY=y +# CONFIG_SECURITYFS is not set +# CONFIG_SECURITY_NETWORK is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +# CONFIG_SECURITY_ROOTPLUG is not set +CONFIG_SECURITY_DEFAULT_MMAP_MIN_ADDR=0 +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +# CONFIG_CRYPTO_FIPS is not set +CONFIG_CRYPTO_ALGAPI=y +CONFIG_CRYPTO_ALGAPI2=y +CONFIG_CRYPTO_AEAD2=y +CONFIG_CRYPTO_BLKCIPHER=y +CONFIG_CRYPTO_BLKCIPHER2=y +CONFIG_CRYPTO_HASH=y +CONFIG_CRYPTO_HASH2=y +CONFIG_CRYPTO_RNG2=y +CONFIG_CRYPTO_MANAGER=y +CONFIG_CRYPTO_MANAGER2=y +# CONFIG_CRYPTO_GF128MUL is not set +# CONFIG_CRYPTO_NULL is not set +# CONFIG_CRYPTO_CRYPTD is not set +# CONFIG_CRYPTO_AUTHENC is not set +CONFIG_CRYPTO_TEST=m +CONFIG_CRYPTO_CRYPTODEV=y + +# +# Authenticated Encryption with Associated Data +# +# CONFIG_CRYPTO_CCM is not set +# CONFIG_CRYPTO_GCM is not set +# CONFIG_CRYPTO_SEQIV is not set + +# +# Block modes +# +CONFIG_CRYPTO_CBC=y +# CONFIG_CRYPTO_CTR is not set +# CONFIG_CRYPTO_CTS is not set +CONFIG_CRYPTO_ECB=y +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_XTS is not set + +# +# Hash modes +# +CONFIG_CRYPTO_HMAC=y +# CONFIG_CRYPTO_XCBC is not set + +# +# Digest +# +# CONFIG_CRYPTO_CRC32C is not set +# CONFIG_CRYPTO_MD4 is not set +CONFIG_CRYPTO_MD5=y +# CONFIG_CRYPTO_MICHAEL_MIC is not set +# CONFIG_CRYPTO_RMD128 is not set +# CONFIG_CRYPTO_RMD160 is not set +# CONFIG_CRYPTO_RMD256 is not set +# CONFIG_CRYPTO_RMD320 is not set +CONFIG_CRYPTO_SHA1=m +# CONFIG_CRYPTO_SHA256 is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_WP512 is not set + +# +# Ciphers +# +CONFIG_CRYPTO_AES=y +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_ARC4 is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +CONFIG_CRYPTO_DES=y +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_SALSA20 is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SERPENT is not set +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TWOFISH is not set + +# +# Compression +# +CONFIG_CRYPTO_DEFLATE=y +CONFIG_CRYPTO_LZO=y + +# +# Random Number Generation +# +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_HW=y +CONFIG_CRYPTO_DEV_STMP3XXX_DCP=y + +# +# Library routines +# +CONFIG_BITREVERSE=y +CONFIG_CRC_CCITT=m +CONFIG_CRC16=y +# CONFIG_CRC_T10DIF is not set +# CONFIG_CRC_ITU_T is not set +CONFIG_CRC32=y +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_LZO_COMPRESS=y +CONFIG_LZO_DECOMPRESS=y +CONFIG_PLIST=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_DMA=y diff --git a/arch/arm/configs/imx233_updater_defconfig b/arch/arm/configs/imx233_updater_defconfig new file mode 100644 index 000000000000..ab64c7f17d3b --- /dev/null +++ b/arch/arm/configs/imx233_updater_defconfig @@ -0,0 +1,1028 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.28 +# Mon Aug 24 17:11:35 2009 +# +CONFIG_ARM=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +CONFIG_GENERIC_GPIO=y +CONFIG_GENERIC_TIME=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_MMU=y +# CONFIG_NO_IOPORT is not set +CONFIG_GENERIC_HARDIRQS=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +# CONFIG_ARCH_HAS_ILOG2_U32 is not set +# CONFIG_ARCH_HAS_ILOG2_U64 is not set +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_ZONE_DMA=y +CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="-updater" +CONFIG_LOCALVERSION_AUTO=y +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +CONFIG_BSD_PROCESS_ACCT=y +# CONFIG_BSD_PROCESS_ACCT_V3 is not set +# CONFIG_IKCONFIG is not set +CONFIG_LOG_BUF_SHIFT=17 +# CONFIG_CGROUPS is not set +# CONFIG_GROUP_SCHED is not set +CONFIG_SYSFS_DEPRECATED=y +CONFIG_SYSFS_DEPRECATED_V2=y +CONFIG_RELAY=y +# CONFIG_NAMESPACES is not set +CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="" +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_SYSCTL=y +CONFIG_EMBEDDED=y +CONFIG_UID16=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_KALLSYMS=y +# CONFIG_KALLSYMS_EXTRA_PASS is not set +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +CONFIG_COMPAT_BRK=y +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_ANON_INODES=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_AIO=y +# CONFIG_VM_EVENT_COUNTERS is not set +CONFIG_SLAB=y +# CONFIG_SLUB is not set +# CONFIG_SLOB is not set +# CONFIG_PROFILING is not set +# CONFIG_MARKERS is not set +CONFIG_HAVE_OPROFILE=y +# CONFIG_KPROBES is not set +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +# CONFIG_TINY_SHMEM is not set +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +# CONFIG_MODULE_FORCE_LOAD is not set +# CONFIG_MODULE_UNLOAD is not set +# CONFIG_MODVERSIONS is not set +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_KMOD=y +CONFIG_BLOCK=y +# CONFIG_LBD is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_LSF is not set +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_INTEGRITY is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +# CONFIG_IOSCHED_AS is not set +# CONFIG_IOSCHED_DEADLINE is not set +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_AS is not set +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" +CONFIG_CLASSIC_RCU=y +# CONFIG_FREEZER is not set + +# +# System Type +# +# CONFIG_ARCH_AAEC2000 is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_VERSATILE is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_CLPS7500 is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_H720X is not set +# CONFIG_ARCH_IMX is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP23XX is not set +# CONFIG_ARCH_IXP2000 is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_KIRKWOOD is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_NS9XXX is not set +# CONFIG_ARCH_LOKI is not set +# CONFIG_ARCH_MV78XX0 is not set +# CONFIG_ARCH_MXC is not set +# CONFIG_ARCH_ORION5X is not set +# CONFIG_ARCH_PNX4008 is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_SA1100 is not set +# CONFIG_ARCH_S3C2410 is not set +# CONFIG_ARCH_SHARK is not set +# CONFIG_ARCH_LH7A40X is not set +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_OMAP is not set +# CONFIG_ARCH_MSM is not set +CONFIG_ARCH_STMP3XXX=y + +# +# Boot options +# + +# +# Power management +# +CONFIG_DMA_ZONE_SIZE=12 + +# +# SigmaTel STMP3xxx implementations +# +# CONFIG_ARCH_STMP37XX is not set +CONFIG_ARCH_STMP378X=y +# CONFIG_MACH_STMP37XX is not set +CONFIG_MACH_STMP378X=y +# CONFIG_FB_STMP37XX_LMS350 is not set +CONFIG_FB_STMP37XX_LMS430=y +# CONFIG_FB_STMP378X_TVENC is not set +# CONFIG_STMP3XXX_UNIQUE_ID is not set +# CONFIG_STMP378X_RAM_FREQ_SCALING is not set + +# +# Processor Type +# +CONFIG_CPU_32=y +CONFIG_CPU_ARM926T=y +CONFIG_CPU_32v5=y +CONFIG_CPU_ABRT_EV5TJ=y +CONFIG_CPU_PABRT_NOIFAR=y +CONFIG_CPU_CACHE_VIVT=y +CONFIG_CPU_COPY_V4WB=y +CONFIG_CPU_TLB_V4WBI=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y + +# +# Processor Features +# +# CONFIG_ARM_THUMB is not set +# CONFIG_CPU_ICACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_WRITETHROUGH is not set +# CONFIG_CPU_CACHE_ROUND_ROBIN is not set +# CONFIG_OUTER_CACHE is not set + +# +# Bus support +# +CONFIG_ISA=y +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +# CONFIG_PCCARD is not set + +# +# Kernel Features +# +# CONFIG_NO_HZ is not set +# CONFIG_HIGH_RES_TIMERS is not set +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +CONFIG_VMSPLIT_3G=y +# CONFIG_VMSPLIT_2G is not set +# CONFIG_VMSPLIT_1G is not set +CONFIG_PAGE_OFFSET=0xC0000000 +# CONFIG_PREEMPT is not set +CONFIG_HZ=100 +CONFIG_AEABI=y +# CONFIG_OABI_COMPAT is not set +CONFIG_ARCH_FLATMEM_HAS_HOLES=y +# CONFIG_ARCH_SPARSEMEM_DEFAULT is not set +# CONFIG_ARCH_SELECT_MEMORY_MODEL is not set +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_SPLIT_PTLOCK_CPUS=4096 +# CONFIG_RESOURCES_64BIT is not set +# CONFIG_PHYS_ADDR_T_64BIT is not set +CONFIG_ZONE_DMA_FLAG=1 +CONFIG_BOUNCE=y +CONFIG_VIRT_TO_BUS=y +CONFIG_UNEVICTABLE_LRU=y +CONFIG_ALIGNMENT_TRAP=y + +# +# Boot options +# +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="console=ttyAM0,115200 rdinit=/linuxrc" +# CONFIG_XIP_KERNEL is not set +# CONFIG_KEXEC is not set + +# +# CPU Power Management +# +# CONFIG_CPU_FREQ is not set +# CONFIG_CPU_IDLE is not set + +# +# Floating point emulation +# + +# +# At least one emulation must be selected +# +# CONFIG_VFP is not set + +# +# Userspace binary formats +# +CONFIG_BINFMT_ELF=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_HAVE_AOUT=y +# CONFIG_BINFMT_AOUT is not set +# CONFIG_BINFMT_MISC is not set + +# +# Power management options +# +CONFIG_PM=y +# CONFIG_PM_DEBUG is not set +# CONFIG_SUSPEND is not set +# CONFIG_APM_EMULATION is not set +CONFIG_ARCH_SUSPEND_POSSIBLE=y +# CONFIG_NET is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +# CONFIG_STANDALONE is not set +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +CONFIG_FIRMWARE_IN_KERNEL=y +CONFIG_EXTRA_FIRMWARE="" +# CONFIG_SYS_HYPERVISOR is not set +CONFIG_MTD=y +# CONFIG_MTD_DEBUG is not set +CONFIG_MTD_CONCAT=y +CONFIG_MTD_PARTITIONS=y +# CONFIG_MTD_REDBOOT_PARTS is not set +# CONFIG_MTD_CMDLINE_PARTS is not set +# CONFIG_MTD_AFS_PARTS is not set +# CONFIG_MTD_AR7_PARTS is not set + +# +# User Modules And Translation Layers +# +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLKDEVS=y +# CONFIG_MTD_BLOCK is not set +# CONFIG_MTD_BLOCK_RO is not set +# CONFIG_FTL is not set +# CONFIG_NFTL is not set +# CONFIG_INFTL is not set +# CONFIG_RFD_FTL is not set +# CONFIG_SSFDC is not set +# CONFIG_MTD_OOPS is not set + +# +# RAM/ROM/Flash chip drivers +# +# CONFIG_MTD_CFI is not set +# CONFIG_MTD_JEDECPROBE is not set +CONFIG_MTD_MAP_BANK_WIDTH_1=y +CONFIG_MTD_MAP_BANK_WIDTH_2=y +CONFIG_MTD_MAP_BANK_WIDTH_4=y +# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set +CONFIG_MTD_CFI_I1=y +CONFIG_MTD_CFI_I2=y +# CONFIG_MTD_CFI_I4 is not set +# CONFIG_MTD_CFI_I8 is not set +# CONFIG_MTD_RAM is not set +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_ABSENT is not set + +# +# Mapping drivers for chip access +# +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_PLATRAM is not set + +# +# Self-contained MTD device drivers +# +# CONFIG_MTD_DATAFLASH is not set +# CONFIG_MTD_M25P80 is not set +# CONFIG_MTD_SLRAM is not set +# CONFIG_MTD_PHRAM is not set +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_BLOCK2MTD is not set + +# +# Disk-On-Chip Device Drivers +# +# CONFIG_MTD_DOC2000 is not set +# CONFIG_MTD_DOC2001 is not set +# CONFIG_MTD_DOC2001PLUS is not set +CONFIG_MTD_NAND=y +# CONFIG_MTD_NAND_VERIFY_WRITE is not set +# CONFIG_MTD_NAND_ECC_SMC is not set +# CONFIG_MTD_NAND_MUSEUM_IDS is not set +# CONFIG_MTD_NAND_GPIO is not set +CONFIG_MTD_NAND_IDS=y +# CONFIG_MTD_NAND_DISKONCHIP is not set +# CONFIG_MTD_NAND_NANDSIM is not set +# CONFIG_MTD_NAND_IMX_NFC is not set +# CONFIG_MTD_NAND_GPMI_LBA is not set +CONFIG_MTD_NAND_GPMI=y +CONFIG_MTD_NAND_GPMI_SYSFS_ENTRIES=y +CONFIG_MTD_NAND_GPMI_BCH=y +CONFIG_MTD_NAND_GPMI_TA1=y +CONFIG_MTD_NAND_GPMI_TA3=y +# CONFIG_MTD_NAND_PLATFORM is not set +# CONFIG_MTD_ALAUDA is not set +# CONFIG_MTD_ONENAND is not set + +# +# UBI - Unsorted block images +# +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_WL_THRESHOLD=4096 +CONFIG_MTD_UBI_BEB_RESERVE=1 +# CONFIG_MTD_UBI_GLUEBI is not set + +# +# UBI debugging options +# +# CONFIG_MTD_UBI_DEBUG is not set +# CONFIG_PARPORT is not set +# CONFIG_PNP is not set +# CONFIG_BLK_DEV is not set +# CONFIG_MISC_DEVICES is not set +CONFIG_HAVE_IDE=y +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +# CONFIG_SCSI is not set +# CONFIG_SCSI_DMA is not set +# CONFIG_SCSI_NETLINK is not set +# CONFIG_ATA is not set +# CONFIG_MD is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +# CONFIG_INPUT_POLLDEV is not set + +# +# Userland interfaces +# +# CONFIG_INPUT_MOUSEDEV is not set +# CONFIG_INPUT_JOYDEV is not set +# CONFIG_INPUT_EVDEV is not set +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +# CONFIG_INPUT_KEYBOARD is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TABLET is not set +# CONFIG_INPUT_TOUCHSCREEN is not set +# CONFIG_INPUT_MISC is not set + +# +# Hardware I/O ports +# +# CONFIG_SERIO is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +# CONFIG_VT_HW_CONSOLE_BINDING is not set +# CONFIG_DEVKMEM is not set +# CONFIG_SERIAL_NONSTANDARD is not set +# CONFIG_MXS_VIIM is not set + +# +# Serial drivers +# +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_SERIAL_STMP_DBG=y +CONFIG_SERIAL_STMP_DBG_CONSOLE=y +# CONFIG_SERIAL_STMP_APP is not set +# CONFIG_UNIX98_PTYS is not set +# CONFIG_LEGACY_PTYS is not set +# CONFIG_IPMI_HANDLER is not set +# CONFIG_HW_RANDOM is not set +# CONFIG_NVRAM is not set +# CONFIG_DTLK is not set +# CONFIG_R3964 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_DEVPORT=y +# CONFIG_I2C is not set +# CONFIG_I2C_SLAVE is not set +CONFIG_SPI=y +CONFIG_SPI_MASTER=y + +# +# SPI Master Controller Drivers +# +# CONFIG_SPI_BITBANG is not set +CONFIG_SPI_STMP3XXX=y + +# +# SPI Protocol Masters +# +# CONFIG_SPI_AT25 is not set +# CONFIG_SPI_SPIDEV is not set +# CONFIG_SPI_TLE62X0 is not set +# CONFIG_W1 is not set +# CONFIG_POWER_SUPPLY is not set +# CONFIG_HWMON is not set +# CONFIG_THERMAL is not set +# CONFIG_THERMAL_HWMON is not set +# CONFIG_WATCHDOG is not set +CONFIG_SSB_POSSIBLE=y + +# +# Sonics Silicon Backplane +# +# CONFIG_SSB is not set + +# +# Multifunction device drivers +# +# CONFIG_MFD_CORE is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_HTC_PASIC3 is not set +# CONFIG_MFD_TMIO is not set + +# +# Multimedia devices +# + +# +# Multimedia core support +# +# CONFIG_VIDEO_DEV is not set +# CONFIG_VIDEO_MEDIA is not set + +# +# Multimedia drivers +# +# CONFIG_DAB is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +# CONFIG_VIDEO_OUTPUT_CONTROL is not set +CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +# CONFIG_FB_DDC is not set +# CONFIG_FB_BOOT_VESA_SUPPORT is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_FOREIGN_ENDIAN is not set +# CONFIG_FB_SYS_FOPS is not set +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_MODE_HELPERS is not set +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# +# CONFIG_FB_S1D13XXX is not set +CONFIG_FB_STMP37XX=y +# CONFIG_FB_VIRTUAL is not set +# CONFIG_FB_METRONOME is not set +# CONFIG_FB_MB862XX is not set +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_LCD_CLASS_DEVICE=y +# CONFIG_LCD_LTV350QV is not set +# CONFIG_LCD_ILI9320 is not set +# CONFIG_LCD_TDO24M is not set +# CONFIG_LCD_VGG2432A4 is not set +CONFIG_LCD_PLATFORM=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_CORGI is not set +CONFIG_BACKLIGHT_STMP37XX=y + +# +# Display device support +# +# CONFIG_DISPLAY_SUPPORT is not set + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +# CONFIG_MDA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +CONFIG_FONTS=y +CONFIG_FONT_8x8=y +# CONFIG_FONT_8x16 is not set +# CONFIG_FONT_6x11 is not set +# CONFIG_FONT_7x14 is not set +# CONFIG_FONT_PEARL_8x8 is not set +# CONFIG_FONT_ACORN_8x8 is not set +# CONFIG_FONT_MINI_4x6 is not set +# CONFIG_FONT_SUN8x16 is not set +# CONFIG_FONT_SUN12x22 is not set +# CONFIG_FONT_10x18 is not set +# CONFIG_LOGO is not set +# CONFIG_SOUND is not set +# CONFIG_HID_SUPPORT is not set +CONFIG_USB_SUPPORT=y +CONFIG_USB_ARCH_HAS_HCD=y +# CONFIG_USB_ARCH_HAS_OHCI is not set +CONFIG_USB_ARCH_HAS_EHCI=y +CONFIG_USB=y +# CONFIG_USB_DEBUG is not set +# CONFIG_USB_ANNOUNCE_NEW_DEVICES is not set + +# +# Miscellaneous USB options +# +# CONFIG_USB_DEVICEFS is not set +# CONFIG_USB_DEVICE_CLASS is not set +# CONFIG_USB_DYNAMIC_MINORS is not set +# CONFIG_USB_SUSPEND is not set +# CONFIG_USB_OTG is not set +# CONFIG_USB_OTG_WHITELIST is not set +# CONFIG_USB_OTG_BLACKLIST_HUB is not set +# CONFIG_USB_MON is not set +# CONFIG_USB_WUSB is not set +# CONFIG_USB_WUSB_CBAF is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_C67X00_HCD is not set +# CONFIG_USB_EHCI_HCD is not set +# CONFIG_USB_ISP116X_HCD is not set +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +# CONFIG_USB_HWA_HCD is not set +# CONFIG_USB_GADGET_MUSB_HDRC is not set + +# +# USB Device Class drivers +# +# CONFIG_USB_ACM is not set +# CONFIG_USB_PRINTER is not set +# CONFIG_USB_WDM is not set +# CONFIG_USB_TMC is not set + +# +# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; +# + +# +# see USB_STORAGE Help for more information +# +# CONFIG_USB_LIBUSUAL is not set + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set + +# +# USB port drivers +# +# CONFIG_USB_SERIAL is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_SEVSEG is not set +# CONFIG_USB_RIO500 is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_BERRY_CHARGE is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_PHIDGET is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_ISIGHTFW is not set +# CONFIG_USB_VST is not set +CONFIG_USB_GADGET=y +# CONFIG_USB_GADGET_DEBUG_FILES is not set +CONFIG_USB_GADGET_VBUS_DRAW=2 +CONFIG_USB_GADGET_SELECTED=y +# CONFIG_USB_GADGET_AT91 is not set +# CONFIG_USB_GADGET_ATMEL_USBA is not set +# CONFIG_USB_GADGET_FSL_USB2 is not set +# CONFIG_USB_GADGET_LH7A40X is not set +# CONFIG_USB_GADGET_OMAP is not set +# CONFIG_USB_GADGET_PXA25X is not set +# CONFIG_USB_GADGET_PXA27X is not set +# CONFIG_USB_GADGET_S3C2410 is not set +# CONFIG_USB_GADGET_M66592 is not set +# CONFIG_USB_GADGET_AMD5536UDC is not set +# CONFIG_USB_GADGET_FSL_QE is not set +# CONFIG_USB_GADGET_NET2280 is not set +# CONFIG_USB_GADGET_GOKU is not set +CONFIG_USB_GADGET_ARC=y +CONFIG_USB_ARC=y +# CONFIG_USB_GADGET_DUMMY_HCD is not set +# CONFIG_USB_GADGET_DUALSPEED is not set +CONFIG_USB_GADGET_ARC_OTG=y +CONFIG_USB_GADGET_FSL_MC13783=y +# CONFIG_USB_GADGET_FSL_1301 is not set +# CONFIG_USB_GADGET_FSL_1504 is not set +# CONFIG_USB_GADGET_FSL_UTMI is not set +# CONFIG_USB_ZERO is not set +# CONFIG_USB_ETH is not set +# CONFIG_USB_GADGETFS is not set +CONFIG_USB_FILE_STORAGE=y +CONFIG_STMP_UTP=y +# CONFIG_USB_FILE_STORAGE_TEST is not set +# CONFIG_USB_G_SERIAL is not set +# CONFIG_USB_MIDI_GADGET is not set +# CONFIG_USB_G_PRINTER is not set +# CONFIG_USB_CDC_COMPOSITE is not set +CONFIG_MMC=y +# CONFIG_MMC_DEBUG is not set +# CONFIG_MMC_UNSAFE_RESUME is not set + +# +# MMC/SD/SDIO Card Drivers +# +CONFIG_MMC_BLOCK=y +CONFIG_MMC_BLOCK_BOUNCE=y +# CONFIG_SDIO_UART is not set +# CONFIG_MMC_TEST is not set + +# +# MMC/SD/SDIO Host Controller Drivers +# +# CONFIG_MMC_SDHCI is not set +# CONFIG_MMC_SPI is not set +# CONFIG_MMC_IMX_ESDHCI_PIO_MODE is not set +CONFIG_MMC_STMP3XXX=y +# CONFIG_MEMSTICK is not set +# CONFIG_ACCESSIBILITY is not set +# CONFIG_NEW_LEDS is not set +CONFIG_RTC_LIB=y +# CONFIG_RTC_CLASS is not set +# CONFIG_DMADEVICES is not set +CONFIG_REGULATOR=y +# CONFIG_REGULATOR_DEBUG is not set +# CONFIG_REGULATOR_FIXED_VOLTAGE is not set +# CONFIG_REGULATOR_VIRTUAL_CONSUMER is not set +# CONFIG_REGULATOR_BQ24022 is not set +CONFIG_REGULATOR_STMP3XXX=y +# CONFIG_UIO is not set + +# +# File systems +# +# CONFIG_EXT2_FS is not set +# CONFIG_EXT3_FS is not set +# CONFIG_EXT4_FS is not set +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_FS_POSIX_ACL is not set +CONFIG_FILE_LOCKING=y +# CONFIG_XFS_FS is not set +# CONFIG_DNOTIFY is not set +# CONFIG_INOTIFY is not set +# CONFIG_QUOTA is not set +# CONFIG_AUTOFS_FS is not set +# CONFIG_AUTOFS4_FS is not set +# CONFIG_FUSE_FS is not set + +# +# CD-ROM/DVD Filesystems +# +# CONFIG_ISO9660_FS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +# CONFIG_MSDOS_FS is not set +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +# CONFIG_PROC_SYSCTL is not set +# CONFIG_PROC_PAGE_MONITOR is not set +CONFIG_SYSFS=y +# CONFIG_TMPFS is not set +# CONFIG_HUGETLB_PAGE is not set +# CONFIG_CONFIGFS_FS is not set + +# +# Miscellaneous filesystems +# +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_DEBUG=0 +CONFIG_JFFS2_FS_WRITEBUFFER=y +# CONFIG_JFFS2_FS_WBUF_VERIFY is not set +CONFIG_JFFS2_SUMMARY=y +# CONFIG_JFFS2_FS_XATTR is not set +# CONFIG_JFFS2_COMPRESSION_OPTIONS is not set +CONFIG_JFFS2_ZLIB=y +# CONFIG_JFFS2_LZO is not set +CONFIG_JFFS2_RTIME=y +# CONFIG_JFFS2_RUBIN is not set +CONFIG_UBIFS_FS=y +CONFIG_UBIFS_FS_XATTR=y +CONFIG_UBIFS_FS_ADVANCED_COMPR=y +CONFIG_UBIFS_FS_LZO=y +# CONFIG_UBIFS_FS_ZLIB is not set +# CONFIG_UBIFS_FS_DEBUG is not set +# CONFIG_CRAMFS is not set +# CONFIG_VXFS_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +# CONFIG_NLS_CODEPAGE_437 is not set +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +# CONFIG_NLS_ASCII is not set +# CONFIG_NLS_ISO8859_1 is not set +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +# CONFIG_NLS_UTF8 is not set + +# +# Kernel hacking +# +# CONFIG_PRINTK_TIME is not set +# CONFIG_ENABLE_WARN_DEPRECATED is not set +# CONFIG_ENABLE_MUST_CHECK is not set +CONFIG_FRAME_WARN=1024 +# CONFIG_MAGIC_SYSRQ is not set +# CONFIG_UNUSED_SYMBOLS is not set +# CONFIG_DEBUG_FS is not set +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_KERNEL is not set +# CONFIG_DEBUG_BUGVERBOSE is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +CONFIG_FRAME_POINTER=y +# CONFIG_RCU_CPU_STALL_DETECTOR is not set +# CONFIG_LATENCYTOP is not set +CONFIG_SYSCTL_SYSCALL_CHECK=y +CONFIG_HAVE_FUNCTION_TRACER=y + +# +# Tracers +# +# CONFIG_DYNAMIC_PRINTK_DEBUG is not set +# CONFIG_SAMPLES is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_DEBUG_USER is not set + +# +# Security options +# +# CONFIG_KEYS is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITYFS is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +# CONFIG_CRYPTO_FIPS is not set +CONFIG_CRYPTO_ALGAPI=y +CONFIG_CRYPTO_ALGAPI2=y +# CONFIG_CRYPTO_MANAGER is not set +# CONFIG_CRYPTO_MANAGER2 is not set +# CONFIG_CRYPTO_GF128MUL is not set +# CONFIG_CRYPTO_NULL is not set +# CONFIG_CRYPTO_CRYPTD is not set +# CONFIG_CRYPTO_AUTHENC is not set +# CONFIG_CRYPTO_TEST is not set +# CONFIG_CRYPTO_CRYPTODEV is not set + +# +# Authenticated Encryption with Associated Data +# +# CONFIG_CRYPTO_CCM is not set +# CONFIG_CRYPTO_GCM is not set +# CONFIG_CRYPTO_SEQIV is not set + +# +# Block modes +# +# CONFIG_CRYPTO_CBC is not set +# CONFIG_CRYPTO_CTR is not set +# CONFIG_CRYPTO_CTS is not set +# CONFIG_CRYPTO_ECB is not set +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_XTS is not set + +# +# Hash modes +# +# CONFIG_CRYPTO_HMAC is not set +# CONFIG_CRYPTO_XCBC is not set + +# +# Digest +# +# CONFIG_CRYPTO_CRC32C is not set +# CONFIG_CRYPTO_MD4 is not set +# CONFIG_CRYPTO_MD5 is not set +# CONFIG_CRYPTO_MICHAEL_MIC is not set +# CONFIG_CRYPTO_RMD128 is not set +# CONFIG_CRYPTO_RMD160 is not set +# CONFIG_CRYPTO_RMD256 is not set +# CONFIG_CRYPTO_RMD320 is not set +# CONFIG_CRYPTO_SHA1 is not set +# CONFIG_CRYPTO_SHA256 is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_WP512 is not set + +# +# Ciphers +# +# CONFIG_CRYPTO_AES is not set +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_ARC4 is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +# CONFIG_CRYPTO_DES is not set +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_SALSA20 is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SERPENT is not set +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TWOFISH is not set + +# +# Compression +# +CONFIG_CRYPTO_DEFLATE=y +CONFIG_CRYPTO_LZO=y + +# +# Random Number Generation +# +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_HW=y +# CONFIG_CRYPTO_DEV_STMP3XXX_DCP is not set + +# +# Library routines +# +CONFIG_BITREVERSE=y +# CONFIG_CRC_CCITT is not set +CONFIG_CRC16=y +# CONFIG_CRC_T10DIF is not set +# CONFIG_CRC_ITU_T is not set +CONFIG_CRC32=y +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_LZO_COMPRESS=y +CONFIG_LZO_DECOMPRESS=y +CONFIG_PLIST=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_DMA=y diff --git a/arch/arm/configs/imx25_3stack_defconfig b/arch/arm/configs/imx25_3stack_defconfig new file mode 100644 index 000000000000..c76198147662 --- /dev/null +++ b/arch/arm/configs/imx25_3stack_defconfig @@ -0,0 +1,1725 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.28 +# Fri Aug 21 12:15:05 2009 +# +CONFIG_ARM=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +# CONFIG_GENERIC_GPIO is not set +CONFIG_GENERIC_TIME=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_MMU=y +# CONFIG_NO_IOPORT is not set +CONFIG_GENERIC_HARDIRQS=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +# CONFIG_ARCH_HAS_ILOG2_U32 is not set +# CONFIG_ARCH_HAS_ILOG2_U64 is not set +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_ZONE_DMA=y +CONFIG_ARCH_MTD_XIP=y +CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_LOCK_KERNEL=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="" +CONFIG_LOCALVERSION_AUTO=y +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +# CONFIG_POSIX_MQUEUE is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set +# CONFIG_AUDIT is not set +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=14 +# CONFIG_CGROUPS is not set +# CONFIG_GROUP_SCHED is not set +CONFIG_SYSFS_DEPRECATED=y +CONFIG_SYSFS_DEPRECATED_V2=y +# CONFIG_RELAY is not set +# CONFIG_NAMESPACES is not set +# CONFIG_BLK_DEV_INITRD is not set +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_SYSCTL=y +CONFIG_EMBEDDED=y +CONFIG_UID16=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_KALLSYMS=y +# CONFIG_KALLSYMS_EXTRA_PASS is not set +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +CONFIG_COMPAT_BRK=y +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_ANON_INODES=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_AIO=y +CONFIG_VM_EVENT_COUNTERS=y +CONFIG_SLAB=y +# CONFIG_SLUB is not set +# CONFIG_SLOB is not set +CONFIG_PROFILING=y +# CONFIG_MARKERS is not set +CONFIG_OPROFILE=y +CONFIG_HAVE_OPROFILE=y +# CONFIG_KPROBES is not set +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +# CONFIG_TINY_SHMEM is not set +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_KMOD=y +CONFIG_BLOCK=y +# CONFIG_LBD is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_LSF is not set +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_INTEGRITY is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_AS=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_AS is not set +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" +CONFIG_CLASSIC_RCU=y +CONFIG_FREEZER=y + +# +# System Type +# +# CONFIG_ARCH_AAEC2000 is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_VERSATILE is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_CLPS7500 is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_H720X is not set +# CONFIG_ARCH_IMX is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP23XX is not set +# CONFIG_ARCH_IXP2000 is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_KIRKWOOD is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_NS9XXX is not set +# CONFIG_ARCH_LOKI is not set +# CONFIG_ARCH_MV78XX0 is not set +CONFIG_ARCH_MXC=y +# CONFIG_ARCH_ORION5X is not set +# CONFIG_ARCH_PNX4008 is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_SA1100 is not set +# CONFIG_ARCH_S3C2410 is not set +# CONFIG_ARCH_SHARK is not set +# CONFIG_ARCH_LH7A40X is not set +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_OMAP is not set +# CONFIG_ARCH_MSM is not set +# CONFIG_ARCH_STMP3XXX is not set + +# +# Boot options +# + +# +# Power management +# + +# +# Freescale MXC Implementations +# +# CONFIG_ARCH_MX37 is not set +# CONFIG_ARCH_MX35 is not set +# CONFIG_ARCH_MX51 is not set +# CONFIG_ARCH_MX3 is not set +# CONFIG_ARCH_MX27 is not set +CONFIG_ARCH_MX25=y +CONFIG_I2C_MXC_SELECT1=y +# CONFIG_I2C_MXC_SELECT2 is not set + +# +# MX25 Options +# +CONFIG_MX25_OPTIONS=y +CONFIG_MACH_MX25_3DS=y +# CONFIG_MX25_DOZE_DURING_IDLE is not set +CONFIG_MXC_SDMA_API=y + +# +# SDMA options +# +CONFIG_SDMA_IRAM=y +CONFIG_SDMA_IRAM_SIZE=0x1000 +CONFIG_ARCH_MXC_HAS_NFC_V2=y +CONFIG_ARCH_MXC_HAS_NFC_V2_1=y + +# +# Device options +# +# CONFIG_I2C_MXC_SELECT3 is not set +# CONFIG_FLEXCAN_MXC_SELECT1 is not set +CONFIG_DMA_ZONE_SIZE=24 +CONFIG_UTMI_MXC=y +CONFIG_UTMI_MXC_OTG=m +# CONFIG_MXC_PWM is not set + +# +# Processor Type +# +CONFIG_CPU_32=y +CONFIG_CPU_ARM926T=y +# CONFIG_CPU_V6 is not set +# CONFIG_CPU_V7 is not set +CONFIG_CPU_32v5=y +CONFIG_CPU_ABRT_EV5TJ=y +CONFIG_CPU_PABRT_NOIFAR=y +CONFIG_CPU_CACHE_VIVT=y +CONFIG_CPU_COPY_V4WB=y +CONFIG_CPU_TLB_V4WBI=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y + +# +# Processor Features +# +CONFIG_ARM_THUMB=y +# CONFIG_CPU_ICACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_WRITETHROUGH is not set +# CONFIG_CPU_CACHE_ROUND_ROBIN is not set +# CONFIG_OUTER_CACHE is not set + +# +# Bus support +# +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +# CONFIG_PCCARD is not set + +# +# Kernel Features +# +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +CONFIG_VMSPLIT_3G=y +# CONFIG_VMSPLIT_2G is not set +# CONFIG_VMSPLIT_1G is not set +CONFIG_PAGE_OFFSET=0xC0000000 +CONFIG_PREEMPT=y +CONFIG_HZ=100 +CONFIG_AEABI=y +# CONFIG_OABI_COMPAT is not set +CONFIG_ARCH_FLATMEM_HAS_HOLES=y +# CONFIG_ARCH_SPARSEMEM_DEFAULT is not set +# CONFIG_ARCH_SELECT_MEMORY_MODEL is not set +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_SPLIT_PTLOCK_CPUS=4096 +# CONFIG_RESOURCES_64BIT is not set +# CONFIG_PHYS_ADDR_T_64BIT is not set +CONFIG_ZONE_DMA_FLAG=1 +CONFIG_BOUNCE=y +CONFIG_VIRT_TO_BUS=y +CONFIG_UNEVICTABLE_LRU=y +# CONFIG_LEDS is not set +CONFIG_ALIGNMENT_TRAP=y + +# +# Boot options +# +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="noinitrd console=ttymxc0,115200 root=/dev/mtdblock2 rw rootfstype=jffs2 ip=off" +# CONFIG_XIP_KERNEL is not set +# CONFIG_KEXEC is not set + +# +# CPU Power Management +# +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_TABLE=y +# CONFIG_CPU_FREQ_DEBUG is not set +CONFIG_CPU_FREQ_STAT=y +# CONFIG_CPU_FREQ_STAT_DETAILS is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set +CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +# CONFIG_CPU_FREQ_GOV_ONDEMAND is not set +# CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set +CONFIG_CPU_FREQ_IMX=y +# CONFIG_CPU_IDLE is not set + +# +# Floating point emulation +# + +# +# At least one emulation must be selected +# +# CONFIG_VFP is not set + +# +# Userspace binary formats +# +CONFIG_BINFMT_ELF=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_HAVE_AOUT=y +# CONFIG_BINFMT_AOUT is not set +# CONFIG_BINFMT_MISC is not set + +# +# Power management options +# +CONFIG_PM=y +# CONFIG_PM_DEBUG is not set +CONFIG_PM_SLEEP=y +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +# CONFIG_APM_EMULATION is not set +CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +CONFIG_PACKET_MMAP=y +CONFIG_UNIX=y +CONFIG_XFRM=y +# CONFIG_XFRM_USER is not set +# CONFIG_XFRM_SUB_POLICY is not set +# CONFIG_XFRM_MIGRATE is not set +# CONFIG_XFRM_STATISTICS is not set +# CONFIG_NET_KEY is not set +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_FIB_HASH=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +# CONFIG_IP_PNP_RARP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_IP_MROUTE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +# CONFIG_INET_TUNNEL is not set +CONFIG_INET_XFRM_MODE_TRANSPORT=y +CONFIG_INET_XFRM_MODE_TUNNEL=y +CONFIG_INET_XFRM_MODE_BEET=y +# CONFIG_INET_LRO is not set +CONFIG_INET_DIAG=y +CONFIG_INET_TCP_DIAG=y +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +# CONFIG_IPV6 is not set +# CONFIG_NETWORK_SECMARK is not set +# CONFIG_NETFILTER is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_NET_DSA is not set +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_NET_SCHED is not set + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_HAMRADIO is not set +CONFIG_CAN=y +CONFIG_CAN_RAW=y +# CONFIG_CAN_BCM is not set + +# +# CAN Device Drivers +# +# CONFIG_CAN_VCAN is not set +# CONFIG_CAN_DEBUG_DEVICES is not set +CONFIG_CAN_FLEXCAN=y +CONFIG_IRDA=m + +# +# IrDA protocols +# +CONFIG_IRLAN=m +CONFIG_IRNET=m +CONFIG_IRCOMM=m +# CONFIG_IRDA_ULTRA is not set + +# +# IrDA options +# +CONFIG_IRDA_CACHE_LAST_LSAP=y +CONFIG_IRDA_FAST_RR=y +# CONFIG_IRDA_DEBUG is not set + +# +# Infrared-port device drivers +# + +# +# SIR device drivers +# +CONFIG_IRTTY_SIR=m + +# +# Dongle support +# +# CONFIG_DONGLE is not set +# CONFIG_KINGSUN_DONGLE is not set +# CONFIG_KSDAZZLE_DONGLE is not set +# CONFIG_KS959_DONGLE is not set + +# +# FIR device drivers +# +# CONFIG_USB_IRDA is not set +# CONFIG_SIGMATEL_FIR is not set +# CONFIG_MCS_FIR is not set +# CONFIG_MXC_FIR is not set +# CONFIG_BT is not set +# CONFIG_AF_RXRPC is not set +# CONFIG_PHONET is not set +CONFIG_WIRELESS=y +# CONFIG_CFG80211 is not set +CONFIG_WIRELESS_OLD_REGULATORY=y +CONFIG_WIRELESS_EXT=y +CONFIG_WIRELESS_EXT_SYSFS=y +# CONFIG_MAC80211 is not set +CONFIG_IEEE80211=y +# CONFIG_IEEE80211_DEBUG is not set +# CONFIG_IEEE80211_CRYPT_WEP is not set +# CONFIG_IEEE80211_CRYPT_CCMP is not set +# CONFIG_IEEE80211_CRYPT_TKIP is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +CONFIG_FIRMWARE_IN_KERNEL=y +CONFIG_EXTRA_FIRMWARE="" +# CONFIG_SYS_HYPERVISOR is not set +CONFIG_CONNECTOR=y +CONFIG_PROC_EVENTS=y +CONFIG_MTD=y +# CONFIG_MTD_DEBUG is not set +# CONFIG_MTD_CONCAT is not set +CONFIG_MTD_PARTITIONS=y +CONFIG_MTD_REDBOOT_PARTS=y +CONFIG_MTD_REDBOOT_DIRECTORY_BLOCK=-1 +# CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED is not set +# CONFIG_MTD_REDBOOT_PARTS_READONLY is not set +CONFIG_MTD_CMDLINE_PARTS=y +# CONFIG_MTD_AFS_PARTS is not set +# CONFIG_MTD_AR7_PARTS is not set + +# +# User Modules And Translation Layers +# +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLKDEVS=y +CONFIG_MTD_BLOCK=y +# CONFIG_FTL is not set +# CONFIG_NFTL is not set +# CONFIG_INFTL is not set +# CONFIG_RFD_FTL is not set +# CONFIG_SSFDC is not set +# CONFIG_MTD_OOPS is not set + +# +# RAM/ROM/Flash chip drivers +# +CONFIG_MTD_CFI=y +# CONFIG_MTD_JEDECPROBE is not set +CONFIG_MTD_GEN_PROBE=y +CONFIG_MTD_CFI_ADV_OPTIONS=y +CONFIG_MTD_CFI_NOSWAP=y +# CONFIG_MTD_CFI_BE_BYTE_SWAP is not set +# CONFIG_MTD_CFI_LE_BYTE_SWAP is not set +CONFIG_MTD_CFI_GEOMETRY=y +# CONFIG_MTD_MAP_BANK_WIDTH_1 is not set +CONFIG_MTD_MAP_BANK_WIDTH_2=y +# CONFIG_MTD_MAP_BANK_WIDTH_4 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set +CONFIG_MTD_CFI_I1=y +# CONFIG_MTD_CFI_I2 is not set +# CONFIG_MTD_CFI_I4 is not set +# CONFIG_MTD_CFI_I8 is not set +# CONFIG_MTD_OTP is not set +# CONFIG_MTD_CFI_INTELEXT is not set +CONFIG_MTD_CFI_AMDSTD=y +# CONFIG_MTD_CFI_STAA is not set +CONFIG_MTD_CFI_UTIL=y +CONFIG_MTD_RAM=y +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_ABSENT is not set +# CONFIG_MTD_XIP is not set + +# +# Mapping drivers for chip access +# +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_PHYSMAP is not set +# CONFIG_MTD_ARM_INTEGRATOR is not set +# CONFIG_MTD_PLATRAM is not set +# CONFIG_MTD_MXC is not set + +# +# Self-contained MTD device drivers +# +# CONFIG_MTD_DATAFLASH is not set +# CONFIG_MTD_M25P80 is not set +# CONFIG_MTD_SLRAM is not set +# CONFIG_MTD_PHRAM is not set +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_BLOCK2MTD is not set + +# +# Disk-On-Chip Device Drivers +# +# CONFIG_MTD_DOC2000 is not set +# CONFIG_MTD_DOC2001 is not set +# CONFIG_MTD_DOC2001PLUS is not set +CONFIG_MTD_NAND=y +# CONFIG_MTD_NAND_VERIFY_WRITE is not set +# CONFIG_MTD_NAND_ECC_SMC is not set +# CONFIG_MTD_NAND_MUSEUM_IDS is not set +CONFIG_MTD_NAND_IDS=y +# CONFIG_MTD_NAND_DISKONCHIP is not set +# CONFIG_MTD_NAND_NANDSIM is not set +# CONFIG_MTD_NAND_IMX_NFC is not set +CONFIG_MTD_NAND_MXC_V2=y +# CONFIG_MTD_NAND_MXC_SWECC is not set +# CONFIG_MTD_NAND_MXC_FORCE_CE is not set +# CONFIG_MXC_NAND_LOW_LEVEL_ERASE is not set +# CONFIG_MTD_NAND_PLATFORM is not set +# CONFIG_MTD_ALAUDA is not set +# CONFIG_MTD_ONENAND is not set + +# +# UBI - Unsorted block images +# +# CONFIG_MTD_UBI is not set +# CONFIG_PARPORT is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=y +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +# CONFIG_BLK_DEV_NBD is not set +# CONFIG_BLK_DEV_UB is not set +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=16 +CONFIG_BLK_DEV_RAM_SIZE=16384 +# CONFIG_BLK_DEV_XIP is not set +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +CONFIG_MISC_DEVICES=y +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_ICS932S401 is not set +# CONFIG_ENCLOSURE_SERVICES is not set +# CONFIG_C2PORT is not set +CONFIG_HAVE_IDE=y +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_TGT is not set +# CONFIG_SCSI_NETLINK is not set +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +# CONFIG_BLK_DEV_SR is not set +# CONFIG_CHR_DEV_SG is not set +# CONFIG_CHR_DEV_SCH is not set + +# +# Some SCSI devices (e.g. CD jukebox) support multiple LUNs +# +CONFIG_SCSI_MULTI_LUN=y +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +CONFIG_SCSI_WAIT_SCAN=m + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +CONFIG_SCSI_LOWLEVEL=y +# CONFIG_ISCSI_TCP is not set +# CONFIG_SCSI_DEBUG is not set +# CONFIG_SCSI_DH is not set +# CONFIG_ATA is not set +# CONFIG_MD is not set +CONFIG_NETDEVICES=y +# CONFIG_DUMMY is not set +# CONFIG_BONDING is not set +# CONFIG_MACVLAN is not set +# CONFIG_EQUALIZER is not set +# CONFIG_TUN is not set +# CONFIG_VETH is not set +# CONFIG_PHYLIB is not set +CONFIG_NET_ETHERNET=y +CONFIG_MII=y +# CONFIG_AX88796 is not set +# CONFIG_SMC91X is not set +# CONFIG_DM9000 is not set +# CONFIG_ENC28J60 is not set +# CONFIG_SMC911X is not set +CONFIG_SMSC911X=y +# CONFIG_IBM_NEW_EMAC_ZMII is not set +# CONFIG_IBM_NEW_EMAC_RGMII is not set +# CONFIG_IBM_NEW_EMAC_TAH is not set +# CONFIG_IBM_NEW_EMAC_EMAC4 is not set +# CONFIG_IBM_NEW_EMAC_NO_FLOW_CTRL is not set +# CONFIG_IBM_NEW_EMAC_MAL_CLR_ICINTSTAT is not set +# CONFIG_IBM_NEW_EMAC_MAL_COMMON_ERR is not set +# CONFIG_B44 is not set +# CONFIG_CS89x0 is not set +CONFIG_FEC=m +# CONFIG_FEC2 is not set +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set + +# +# Wireless LAN +# +# CONFIG_WLAN_PRE80211 is not set +# CONFIG_WLAN_80211 is not set +# CONFIG_IWLWIFI_LEDS is not set + +# +# USB Network Adapters +# +# CONFIG_USB_CATC is not set +# CONFIG_USB_KAWETH is not set +# CONFIG_USB_PEGASUS is not set +CONFIG_USB_RTL8150=m +CONFIG_USB_USBNET=m +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_CDCETHER=m +# CONFIG_USB_NET_DM9601 is not set +# CONFIG_USB_NET_SMSC95XX is not set +# CONFIG_USB_NET_GL620A is not set +# CONFIG_USB_NET_NET1080 is not set +# CONFIG_USB_NET_PLUSB is not set +# CONFIG_USB_NET_MCS7830 is not set +# CONFIG_USB_NET_RNDIS_HOST is not set +# CONFIG_USB_NET_CDC_SUBSET is not set +# CONFIG_USB_NET_ZAURUS is not set +# CONFIG_WAN is not set +CONFIG_PPP=m +# CONFIG_PPP_MULTILINK is not set +# CONFIG_PPP_FILTER is not set +CONFIG_PPP_ASYNC=m +CONFIG_PPP_SYNC_TTY=m +CONFIG_PPP_DEFLATE=m +# CONFIG_PPP_BSDCOMP is not set +# CONFIG_PPP_MPPE is not set +# CONFIG_PPPOE is not set +# CONFIG_PPPOL2TP is not set +# CONFIG_SLIP is not set +CONFIG_SLHC=m +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_ISDN is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +# CONFIG_INPUT_POLLDEV is not set + +# +# Userland interfaces +# +# CONFIG_INPUT_MOUSEDEV is not set +# CONFIG_INPUT_JOYDEV is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +CONFIG_KEYBOARD_MXC=y +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TABLET is not set +CONFIG_INPUT_TOUCHSCREEN=y +# CONFIG_TOUCHSCREEN_ADS7846 is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_INEXIO is not set +# CONFIG_TOUCHSCREEN_MK712 is not set +CONFIG_TOUCHSCREEN_IMX_ADC=y +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +# CONFIG_TOUCHSCREEN_TOUCHIT213 is not set +# CONFIG_INPUT_MISC is not set + +# +# Hardware I/O ports +# +# CONFIG_SERIO is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +# CONFIG_VT_HW_CONSOLE_BINDING is not set +CONFIG_DEVKMEM=y +# CONFIG_SERIAL_NONSTANDARD is not set +CONFIG_MXC_IIM=y +CONFIG_IMX_SIM=m + +# +# Serial drivers +# +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +CONFIG_SERIAL_MXC=y +CONFIG_SERIAL_MXC_CONSOLE=y +# CONFIG_SERIAL_IMX is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_UNIX98_PTYS=y +CONFIG_LEGACY_PTYS=y +CONFIG_LEGACY_PTY_COUNT=256 +# CONFIG_IPMI_HANDLER is not set +CONFIG_HW_RANDOM=y +# CONFIG_NVRAM is not set +# CONFIG_R3964 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +# CONFIG_I2C_CHARDEV is not set +CONFIG_I2C_HELPER_AUTO=y + +# +# I2C Hardware Bus support +# + +# +# I2C system bus drivers (mostly embedded / system-on-chip) +# +CONFIG_I2C_MXC=y +# CONFIG_I2C_MXC_HS is not set +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_SIMTEC is not set + +# +# External I2C/SMBus adapter drivers +# +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_TINY_USB is not set + +# +# Other I2C/SMBus bus drivers +# +# CONFIG_I2C_PCA_PLATFORM is not set +# CONFIG_I2C_STUB is not set + +# +# Miscellaneous I2C Chip support +# +# CONFIG_DS1682 is not set +# CONFIG_AT24 is not set +# CONFIG_SENSORS_EEPROM is not set +# CONFIG_SENSORS_PCF8574 is not set +# CONFIG_PCF8575 is not set +# CONFIG_SENSORS_PCA9539 is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_SENSORS_MAX6875 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CHIP is not set +# CONFIG_I2C_SLAVE is not set +CONFIG_SPI=y +CONFIG_SPI_MASTER=y + +# +# SPI Master Controller Drivers +# +CONFIG_SPI_BITBANG=y +CONFIG_SPI_MXC=y +# CONFIG_SPI_MXC_TEST_LOOPBACK is not set +CONFIG_SPI_MXC_SELECT1=y +# CONFIG_SPI_MXC_SELECT2 is not set +# CONFIG_SPI_MXC_SELECT3 is not set + +# +# SPI Protocol Masters +# +# CONFIG_SPI_AT25 is not set +# CONFIG_SPI_SPIDEV is not set +# CONFIG_SPI_TLE62X0 is not set +# CONFIG_W1 is not set +# CONFIG_POWER_SUPPLY is not set +# CONFIG_HWMON is not set +# CONFIG_THERMAL is not set +# CONFIG_THERMAL_HWMON is not set +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_NOWAYOUT=y + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +# CONFIG_MXC_WATCHDOG is not set + +# +# USB-based Watchdog Cards +# +# CONFIG_USBPCWATCHDOG is not set +CONFIG_SSB_POSSIBLE=y + +# +# Sonics Silicon Backplane +# +# CONFIG_SSB is not set + +# +# Multifunction device drivers +# +# CONFIG_MFD_CORE is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_HTC_PASIC3 is not set +# CONFIG_MFD_TMIO is not set +# CONFIG_PMIC_DA903X is not set +# CONFIG_MFD_WM8400 is not set +# CONFIG_MFD_WM8350_I2C is not set + +# +# Multimedia devices +# + +# +# Multimedia core support +# +CONFIG_VIDEO_DEV=y +CONFIG_VIDEO_V4L2_COMMON=y +CONFIG_VIDEO_ALLOW_V4L1=y +CONFIG_VIDEO_V4L1_COMPAT=y +# CONFIG_DVB_CORE is not set +CONFIG_VIDEO_MEDIA=y + +# +# Multimedia drivers +# +# CONFIG_MEDIA_ATTACH is not set +CONFIG_MEDIA_TUNER=y +# CONFIG_MEDIA_TUNER_CUSTOMIZE is not set +CONFIG_MEDIA_TUNER_SIMPLE=y +CONFIG_MEDIA_TUNER_TDA8290=y +CONFIG_MEDIA_TUNER_TDA9887=y +CONFIG_MEDIA_TUNER_TEA5761=y +CONFIG_MEDIA_TUNER_TEA5767=y +CONFIG_MEDIA_TUNER_MT20XX=y +CONFIG_MEDIA_TUNER_XC2028=y +CONFIG_MEDIA_TUNER_XC5000=y +CONFIG_VIDEO_V4L2=y +CONFIG_VIDEO_V4L1=y +CONFIG_VIDEO_CAPTURE_DRIVERS=y +# CONFIG_VIDEO_ADV_DEBUG is not set +# CONFIG_VIDEO_FIXED_MINOR_RANGES is not set +CONFIG_VIDEO_HELPER_CHIPS_AUTO=y +# CONFIG_VIDEO_VIVI is not set +CONFIG_VIDEO_MXC_CAMERA=m + +# +# MXC Camera/V4L2 PRP Features support +# +CONFIG_VIDEO_MXC_CSI_CAMERA=m +# CONFIG_MXC_CAMERA_MC521DA is not set +# CONFIG_MXC_EMMA_CAMERA_MICRON111 is not set +# CONFIG_MXC_CAMERA_OV2640_EMMA is not set +# CONFIG_MXC_CAMERA_MICRON111 is not set +CONFIG_MXC_CAMERA_OV2640=m +# CONFIG_MXC_CAMERA_OV3640 is not set +# CONFIG_MXC_TVIN_ADV7180 is not set +# CONFIG_VIDEO_MXC_OUTPUT is not set +# CONFIG_VIDEO_MXC_OPL is not set +# CONFIG_VIDEO_CPIA is not set +# CONFIG_VIDEO_CPIA2 is not set +# CONFIG_VIDEO_SAA5246A is not set +# CONFIG_VIDEO_SAA5249 is not set +# CONFIG_SOC_CAMERA is not set +# CONFIG_V4L_USB_DRIVERS is not set +CONFIG_RADIO_ADAPTERS=y +# CONFIG_USB_DSBR is not set +# CONFIG_USB_SI470X is not set +# CONFIG_USB_MR800 is not set +CONFIG_DAB=y +# CONFIG_USB_DABUSB is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +# CONFIG_VIDEO_OUTPUT_CONTROL is not set +CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +# CONFIG_FB_DDC is not set +# CONFIG_FB_BOOT_VESA_SUPPORT is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_FOREIGN_ENDIAN is not set +# CONFIG_FB_SYS_FOPS is not set +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_BACKLIGHT is not set +CONFIG_FB_MODE_HELPERS=y +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# +CONFIG_FB_MXC=y +CONFIG_FB_MXC_SYNC_PANEL=y +# CONFIG_FB_MXC_EPSON_VGA_SYNC_PANEL is not set +# CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL is not set +# CONFIG_FB_MXC_CH7026 is not set +# CONFIG_FB_MXC_TVOUT is not set +# CONFIG_FB_MXC_TVOUT_CH7024 is not set +# CONFIG_FB_MXC_ASYNC_PANEL is not set +# CONFIG_FB_UVESA is not set +# CONFIG_FB_S1D13XXX is not set +# CONFIG_FB_VIRTUAL is not set +# CONFIG_FB_METRONOME is not set +# CONFIG_FB_MB862XX is not set +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_LCD_CLASS_DEVICE is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_CORGI is not set +CONFIG_BACKLIGHT_MXC=y +CONFIG_BACKLIGHT_MXC_LCDC=y + +# +# Display device support +# +# CONFIG_DISPLAY_SUPPORT is not set + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE=y +# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +# CONFIG_FONTS is not set +CONFIG_FONT_8x8=y +CONFIG_FONT_8x16=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_LOGO_LINUX_CLUT224=y +CONFIG_SOUND=y +CONFIG_SOUND_OSS_CORE=y +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +# CONFIG_SND_SEQUENCER is not set +CONFIG_SND_OSSEMUL=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +CONFIG_SND_PCM_OSS_PLUGINS=y +# CONFIG_SND_DYNAMIC_MINORS is not set +CONFIG_SND_SUPPORT_OLD_API=y +CONFIG_SND_VERBOSE_PROCFS=y +# CONFIG_SND_VERBOSE_PRINTK is not set +# CONFIG_SND_DEBUG is not set +CONFIG_SND_DRIVERS=y +# CONFIG_SND_DUMMY is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_MPU401 is not set +CONFIG_SND_ARM=y +# CONFIG_SND_MXC_SPDIF is not set +CONFIG_SND_SPI=y +CONFIG_SND_USB=y +# CONFIG_SND_USB_AUDIO is not set +# CONFIG_SND_USB_CAIAQ is not set +CONFIG_SND_SOC=y +CONFIG_SND_MXC_SOC=y +CONFIG_SND_MXC_SOC_SSI=y +CONFIG_SND_MXC_SOC_ESAI=m +CONFIG_SND_MXC_SOC_IRAM=y +CONFIG_SND_SOC_IMX_3STACK_SGTL5000=y +# CONFIG_SND_SOC_IMX_3STACK_AK4647 is not set +CONFIG_SND_SOC_IMX_3STACK_WM8580=m +CONFIG_SND_SOC_IMX_3STACK_AK5702=m +# CONFIG_SND_SOC_IMX_3STACK_BLUETOOTH is not set +# CONFIG_SND_SOC_ALL_CODECS is not set +CONFIG_SND_SOC_AK5702=m +CONFIG_SND_SOC_WM8580=m +CONFIG_SND_SOC_SGTL5000=y +# CONFIG_SOUND_PRIME is not set +CONFIG_HID_SUPPORT=y +CONFIG_HID=y +# CONFIG_HID_DEBUG is not set +# CONFIG_HIDRAW is not set + +# +# USB Input Devices +# +CONFIG_USB_HID=y +# CONFIG_HID_PID is not set +# CONFIG_USB_HIDDEV is not set + +# +# Special HID drivers +# +CONFIG_HID_COMPAT=y +CONFIG_HID_A4TECH=y +CONFIG_HID_APPLE=y +CONFIG_HID_BELKIN=y +CONFIG_HID_BRIGHT=y +CONFIG_HID_CHERRY=y +CONFIG_HID_CHICONY=y +CONFIG_HID_CYPRESS=y +CONFIG_HID_DELL=y +CONFIG_HID_EZKEY=y +CONFIG_HID_GYRATION=y +CONFIG_HID_LOGITECH=y +# CONFIG_LOGITECH_FF is not set +# CONFIG_LOGIRUMBLEPAD2_FF is not set +CONFIG_HID_MICROSOFT=y +CONFIG_HID_MONTEREY=y +CONFIG_HID_PANTHERLORD=y +# CONFIG_PANTHERLORD_FF is not set +CONFIG_HID_PETALYNX=y +CONFIG_HID_SAMSUNG=y +CONFIG_HID_SONY=y +CONFIG_HID_SUNPLUS=y +# CONFIG_THRUSTMASTER_FF is not set +# CONFIG_ZEROPLUS_FF is not set +CONFIG_USB_SUPPORT=y +CONFIG_USB_ARCH_HAS_HCD=y +# CONFIG_USB_ARCH_HAS_OHCI is not set +CONFIG_USB_ARCH_HAS_EHCI=y +CONFIG_USB=y +# CONFIG_USB_DEBUG is not set +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y + +# +# Miscellaneous USB options +# +CONFIG_USB_DEVICEFS=y +# CONFIG_USB_DEVICE_CLASS is not set +# CONFIG_USB_DYNAMIC_MINORS is not set +CONFIG_USB_SUSPEND=y +CONFIG_USB_OTG=y +# CONFIG_USB_OTG_WHITELIST is not set +# CONFIG_USB_OTG_BLACKLIST_HUB is not set +# CONFIG_USB_MON is not set +# CONFIG_USB_WUSB is not set +# CONFIG_USB_WUSB_CBAF is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_C67X00_HCD is not set +CONFIG_USB_EHCI_HCD=m +CONFIG_USB_EHCI_ARC=y +CONFIG_USB_EHCI_ARC_H2=y +CONFIG_USB_EHCI_ARC_OTG=y +# CONFIG_USB_STATIC_IRAM is not set +# CONFIG_USB_EHCI_FSL_MC13783 is not set +# CONFIG_USB_EHCI_FSL_1301 is not set +# CONFIG_USB_EHCI_FSL_1504 is not set +CONFIG_USB_EHCI_FSL_UTMI=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +# CONFIG_USB_ISP116X_HCD is not set +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +# CONFIG_USB_HWA_HCD is not set +# CONFIG_USB_GADGET_MUSB_HDRC is not set + +# +# USB Device Class drivers +# +# CONFIG_USB_ACM is not set +# CONFIG_USB_PRINTER is not set +# CONFIG_USB_WDM is not set +# CONFIG_USB_TMC is not set + +# +# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; +# + +# +# see USB_STORAGE Help for more information +# +CONFIG_USB_STORAGE=y +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +# CONFIG_USB_STORAGE_FREECOM is not set +# CONFIG_USB_STORAGE_ISD200 is not set +# CONFIG_USB_STORAGE_DPCM is not set +# CONFIG_USB_STORAGE_USBAT is not set +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_ONETOUCH is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set +# CONFIG_USB_LIBUSUAL is not set + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set + +# +# USB port drivers +# +# CONFIG_USB_SERIAL is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_SEVSEG is not set +# CONFIG_USB_RIO500 is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_BERRY_CHARGE is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_PHIDGET is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_USB_SISUSBVGA is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_TEST is not set +# CONFIG_USB_ISIGHTFW is not set +# CONFIG_USB_VST is not set +CONFIG_USB_GADGET=m +# CONFIG_USB_GADGET_DEBUG_FILES is not set +CONFIG_USB_GADGET_VBUS_DRAW=2 +CONFIG_USB_GADGET_SELECTED=y +# CONFIG_USB_GADGET_AT91 is not set +# CONFIG_USB_GADGET_ATMEL_USBA is not set +# CONFIG_USB_GADGET_FSL_USB2 is not set +# CONFIG_USB_GADGET_LH7A40X is not set +# CONFIG_USB_GADGET_OMAP is not set +# CONFIG_USB_GADGET_PXA25X is not set +# CONFIG_USB_GADGET_PXA27X is not set +# CONFIG_USB_GADGET_S3C2410 is not set +# CONFIG_USB_GADGET_M66592 is not set +# CONFIG_USB_GADGET_AMD5536UDC is not set +# CONFIG_USB_GADGET_FSL_QE is not set +# CONFIG_USB_GADGET_NET2280 is not set +# CONFIG_USB_GADGET_GOKU is not set +CONFIG_USB_GADGET_ARC=y +CONFIG_USB_STATIC_IRAM_PPH=y +CONFIG_USB_ARC=m +# CONFIG_USB_GADGET_DUMMY_HCD is not set +CONFIG_USB_GADGET_DUALSPEED=y +CONFIG_USB_GADGET_ARC_OTG=y +# CONFIG_USB_GADGET_FSL_MC13783 is not set +# CONFIG_USB_GADGET_FSL_1301 is not set +# CONFIG_USB_GADGET_FSL_1504 is not set +CONFIG_USB_GADGET_FSL_UTMI=y +# CONFIG_USB_ZERO is not set +CONFIG_USB_ETH=m +CONFIG_USB_ETH_RNDIS=y +# CONFIG_USB_GADGETFS is not set +CONFIG_USB_FILE_STORAGE=m +# CONFIG_USB_FILE_STORAGE_TEST is not set +CONFIG_USB_G_SERIAL=m +# CONFIG_USB_MIDI_GADGET is not set +# CONFIG_USB_G_PRINTER is not set +# CONFIG_USB_CDC_COMPOSITE is not set +CONFIG_MMC=y +# CONFIG_MMC_DEBUG is not set +CONFIG_MMC_UNSAFE_RESUME=y + +# +# MMC/SD/SDIO Card Drivers +# +CONFIG_MMC_BLOCK=y +CONFIG_MMC_BLOCK_BOUNCE=y +# CONFIG_SDIO_UART is not set +# CONFIG_MMC_TEST is not set + +# +# MMC/SD/SDIO Host Controller Drivers +# +# CONFIG_MMC_SDHCI is not set +# CONFIG_MMC_SPI is not set +# CONFIG_MMC_MXC is not set +CONFIG_MMC_IMX_ESDHCI=y +# CONFIG_MMC_IMX_ESDHCI_SELECT2 is not set +# CONFIG_MMC_IMX_ESDHCI_PIO_MODE is not set +# CONFIG_MEMSTICK is not set +# CONFIG_ACCESSIBILITY is not set +# CONFIG_NEW_LEDS is not set +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set +# CONFIG_RTC_DRV_S35390A is not set +# CONFIG_RTC_DRV_FM3130 is not set +# CONFIG_RTC_DRV_RX8581 is not set + +# +# SPI RTC drivers +# +# CONFIG_RTC_DRV_M41T94 is not set +# CONFIG_RTC_DRV_DS1305 is not set +# CONFIG_RTC_DRV_DS1390 is not set +# CONFIG_RTC_DRV_MAX6902 is not set +# CONFIG_RTC_DRV_R9701 is not set +# CONFIG_RTC_DRV_RS5C348 is not set +# CONFIG_RTC_DRV_DS3234 is not set + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_BQ4802 is not set +# CONFIG_RTC_DRV_V3020 is not set + +# +# on-CPU RTC drivers +# +# CONFIG_RTC_MXC is not set +# CONFIG_RTC_DRV_MXC_V2 is not set +CONFIG_RTC_DRV_IMXDI=y +# CONFIG_DMADEVICES is not set +CONFIG_REGULATOR=y +# CONFIG_REGULATOR_DEBUG is not set +# CONFIG_REGULATOR_FIXED_VOLTAGE is not set +# CONFIG_REGULATOR_VIRTUAL_CONSUMER is not set +# CONFIG_REGULATOR_BQ24022 is not set +CONFIG_REGULATOR_MC34704=y +# CONFIG_UIO is not set + +# +# MXC support drivers +# + +# +# MXC SSI support +# +# CONFIG_MXC_SSI is not set + +# +# MXC Digital Audio Multiplexer support +# +# CONFIG_MXC_DAM is not set + +# +# MXC PMIC support +# +CONFIG_MXC_PMIC=y +# CONFIG_MXC_PMIC_MC13783 is not set +# CONFIG_MXC_PMIC_MC13892 is not set +CONFIG_MXC_PMIC_MC34704=y +# CONFIG_MXC_PMIC_MC9SDZ60 is not set +CONFIG_MXC_PMIC_CHARDEV=y + +# +# MXC PMIC Client Drivers +# +# CONFIG_MXC_PMIC_MC9S08DZ60 is not set + +# +# MXC Security Drivers +# +# CONFIG_MXC_SECURITY_SCC is not set +# CONFIG_MXC_SECURITY_RNG is not set +# CONFIG_MXC_DRYICE is not set + +# +# MXC MPEG4 Encoder Kernel module support +# +# CONFIG_MXC_HMP4E is not set + +# +# MXC HARDWARE EVENT +# +# CONFIG_MXC_HWEVENT is not set + +# +# MXC VPU(Video Processing Unit) support +# + +# +# MXC Asynchronous Sample Rate Converter support +# + +# +# MXC Bluetooth support +# + +# +# Broadcom GPS ioctrl support +# + +# +# MXC Media Local Bus Driver +# + +# +# i.MX ADC support +# +CONFIG_IMX_ADC=y + +# +# File systems +# +CONFIG_EXT2_FS=y +# CONFIG_EXT2_FS_XATTR is not set +# CONFIG_EXT2_FS_XIP is not set +# CONFIG_EXT3_FS is not set +# CONFIG_EXT4_FS is not set +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_FS_POSIX_ACL is not set +CONFIG_FILE_LOCKING=y +# CONFIG_XFS_FS is not set +# CONFIG_OCFS2_FS is not set +CONFIG_DNOTIFY=y +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_QUOTA is not set +# CONFIG_AUTOFS_FS is not set +CONFIG_AUTOFS4_FS=m +# CONFIG_FUSE_FS is not set + +# +# CD-ROM/DVD Filesystems +# +# CONFIG_ISO9660_FS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +# CONFIG_TMPFS_POSIX_ACL is not set +# CONFIG_HUGETLB_PAGE is not set +# CONFIG_CONFIGFS_FS is not set + +# +# Miscellaneous filesystems +# +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_DEBUG=0 +CONFIG_JFFS2_FS_WRITEBUFFER=y +# CONFIG_JFFS2_FS_WBUF_VERIFY is not set +# CONFIG_JFFS2_SUMMARY is not set +# CONFIG_JFFS2_FS_XATTR is not set +# CONFIG_JFFS2_COMPRESSION_OPTIONS is not set +CONFIG_JFFS2_ZLIB=y +# CONFIG_JFFS2_LZO is not set +CONFIG_JFFS2_RTIME=y +# CONFIG_JFFS2_RUBIN is not set +CONFIG_CRAMFS=y +# CONFIG_VXFS_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +# CONFIG_NFS_V3_ACL is not set +# CONFIG_NFS_V4 is not set +CONFIG_ROOT_NFS=y +# CONFIG_NFSD is not set +CONFIG_LOCKD=y +CONFIG_LOCKD_V4=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=y +# CONFIG_SUNRPC_REGISTER_V4 is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +# CONFIG_RPCSEC_GSS_SPKM3 is not set +# CONFIG_SMB_FS is not set +# CONFIG_CIFS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +CONFIG_NLS_ASCII=m +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +CONFIG_NLS_UTF8=m +# CONFIG_DLM is not set + +# +# Kernel hacking +# +# CONFIG_PRINTK_TIME is not set +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +CONFIG_FRAME_WARN=1024 +# CONFIG_MAGIC_SYSRQ is not set +# CONFIG_UNUSED_SYMBOLS is not set +# CONFIG_DEBUG_FS is not set +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_KERNEL is not set +# CONFIG_DEBUG_BUGVERBOSE is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +CONFIG_FRAME_POINTER=y +# CONFIG_RCU_CPU_STALL_DETECTOR is not set +# CONFIG_LATENCYTOP is not set +CONFIG_SYSCTL_SYSCALL_CHECK=y +CONFIG_HAVE_FUNCTION_TRACER=y + +# +# Tracers +# +# CONFIG_DYNAMIC_PRINTK_DEBUG is not set +# CONFIG_SAMPLES is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_DEBUG_USER is not set + +# +# Security options +# +# CONFIG_KEYS is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITYFS is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +# CONFIG_CRYPTO_FIPS is not set +# CONFIG_CRYPTO_MANAGER is not set +# CONFIG_CRYPTO_MANAGER2 is not set +# CONFIG_CRYPTO_GF128MUL is not set +# CONFIG_CRYPTO_NULL is not set +# CONFIG_CRYPTO_CRYPTD is not set +# CONFIG_CRYPTO_AUTHENC is not set +# CONFIG_CRYPTO_TEST is not set +# CONFIG_CRYPTO_CRYPTODEV is not set + +# +# Authenticated Encryption with Associated Data +# +# CONFIG_CRYPTO_CCM is not set +# CONFIG_CRYPTO_GCM is not set +# CONFIG_CRYPTO_SEQIV is not set + +# +# Block modes +# +# CONFIG_CRYPTO_CBC is not set +# CONFIG_CRYPTO_CTR is not set +# CONFIG_CRYPTO_CTS is not set +# CONFIG_CRYPTO_ECB is not set +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_XTS is not set + +# +# Hash modes +# +# CONFIG_CRYPTO_HMAC is not set +# CONFIG_CRYPTO_XCBC is not set + +# +# Digest +# +# CONFIG_CRYPTO_CRC32C is not set +# CONFIG_CRYPTO_MD4 is not set +# CONFIG_CRYPTO_MD5 is not set +# CONFIG_CRYPTO_MICHAEL_MIC is not set +# CONFIG_CRYPTO_RMD128 is not set +# CONFIG_CRYPTO_RMD160 is not set +# CONFIG_CRYPTO_RMD256 is not set +# CONFIG_CRYPTO_RMD320 is not set +# CONFIG_CRYPTO_SHA1 is not set +# CONFIG_CRYPTO_SHA256 is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_WP512 is not set + +# +# Ciphers +# +# CONFIG_CRYPTO_AES is not set +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_ARC4 is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +# CONFIG_CRYPTO_DES is not set +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_SALSA20 is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SERPENT is not set +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TWOFISH is not set + +# +# Compression +# +# CONFIG_CRYPTO_DEFLATE is not set +# CONFIG_CRYPTO_LZO is not set + +# +# Random Number Generation +# +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_HW=y + +# +# Library routines +# +CONFIG_BITREVERSE=y +CONFIG_CRC_CCITT=m +# CONFIG_CRC16 is not set +# CONFIG_CRC_T10DIF is not set +# CONFIG_CRC_ITU_T is not set +CONFIG_CRC32=y +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_PLIST=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_DMA=y diff --git a/arch/arm/configs/imx27ads_defconfig b/arch/arm/configs/imx27ads_defconfig new file mode 100644 index 000000000000..9f5569365ebd --- /dev/null +++ b/arch/arm/configs/imx27ads_defconfig @@ -0,0 +1,1756 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.28 +# Wed Apr 22 10:24:31 2009 +# +CONFIG_ARM=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +# CONFIG_GENERIC_GPIO is not set +CONFIG_GENERIC_TIME=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_MMU=y +# CONFIG_NO_IOPORT is not set +CONFIG_GENERIC_HARDIRQS=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +# CONFIG_ARCH_HAS_ILOG2_U32 is not set +# CONFIG_ARCH_HAS_ILOG2_U64 is not set +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_ZONE_DMA=y +CONFIG_ARCH_MTD_XIP=y +CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_LOCK_KERNEL=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="" +CONFIG_LOCALVERSION_AUTO=y +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +# CONFIG_POSIX_MQUEUE is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set +# CONFIG_AUDIT is not set +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=14 +# CONFIG_CGROUPS is not set +# CONFIG_GROUP_SCHED is not set +CONFIG_SYSFS_DEPRECATED=y +CONFIG_SYSFS_DEPRECATED_V2=y +# CONFIG_RELAY is not set +# CONFIG_NAMESPACES is not set +# CONFIG_BLK_DEV_INITRD is not set +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_SYSCTL=y +CONFIG_EMBEDDED=y +CONFIG_UID16=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_KALLSYMS=y +# CONFIG_KALLSYMS_EXTRA_PASS is not set +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +CONFIG_COMPAT_BRK=y +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_ANON_INODES=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_AIO=y +CONFIG_VM_EVENT_COUNTERS=y +CONFIG_SLUB_DEBUG=y +# CONFIG_SLAB is not set +CONFIG_SLUB=y +# CONFIG_SLOB is not set +CONFIG_PROFILING=y +# CONFIG_MARKERS is not set +CONFIG_OPROFILE=y +CONFIG_HAVE_OPROFILE=y +# CONFIG_KPROBES is not set +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +# CONFIG_TINY_SHMEM is not set +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_KMOD=y +CONFIG_BLOCK=y +# CONFIG_LBD is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_LSF is not set +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_INTEGRITY is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_AS=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_AS is not set +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" +CONFIG_CLASSIC_RCU=y +CONFIG_FREEZER=y + +# +# System Type +# +# CONFIG_ARCH_AAEC2000 is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_VERSATILE is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_CLPS7500 is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_H720X is not set +# CONFIG_ARCH_IMX is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP23XX is not set +# CONFIG_ARCH_IXP2000 is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_KIRKWOOD is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_NS9XXX is not set +# CONFIG_ARCH_LOKI is not set +# CONFIG_ARCH_MV78XX0 is not set +CONFIG_ARCH_MXC=y +# CONFIG_ARCH_ORION5X is not set +# CONFIG_ARCH_PNX4008 is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_SA1100 is not set +# CONFIG_ARCH_S3C2410 is not set +# CONFIG_ARCH_SHARK is not set +# CONFIG_ARCH_LH7A40X is not set +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_OMAP is not set +# CONFIG_ARCH_MSM is not set + +# +# Boot options +# + +# +# Power management +# + +# +# Freescale MXC Implementations +# +# CONFIG_ARCH_MX37 is not set +# CONFIG_ARCH_MX35 is not set +# CONFIG_ARCH_MX51 is not set +# CONFIG_ARCH_MX3 is not set +CONFIG_ARCH_MX27=y +# CONFIG_ARCH_MX25 is not set + +# +# MX27 Options +# +CONFIG_MX27_OPTIONS=y +CONFIG_MACH_MX27ADS=y +CONFIG_ARCH_MXC_HAS_NFC_V1=y + +# +# Device options +# +CONFIG_I2C_MXC_SELECT1=y +# CONFIG_I2C_MXC_SELECT2 is not set +CONFIG_MXC_EMMA=y +CONFIG_DMA_ZONE_SIZE=24 +CONFIG_ISP1301_MXC=y +CONFIG_MXC_USB_SU6=y +# CONFIG_MXC_USB_SB3 is not set +# CONFIG_MXC_USB_DU6 is not set +# CONFIG_MXC_USB_DB4 is not set + +# +# Processor Type +# +CONFIG_CPU_32=y +CONFIG_CPU_ARM926T=y +# CONFIG_CPU_V6 is not set +# CONFIG_CPU_V7 is not set +CONFIG_CPU_32v5=y +CONFIG_CPU_ABRT_EV5TJ=y +CONFIG_CPU_PABRT_NOIFAR=y +CONFIG_CPU_CACHE_VIVT=y +CONFIG_CPU_COPY_V4WB=y +CONFIG_CPU_TLB_V4WBI=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y + +# +# Processor Features +# +CONFIG_ARM_THUMB=y +# CONFIG_CPU_ICACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_WRITETHROUGH is not set +# CONFIG_CPU_CACHE_ROUND_ROBIN is not set +# CONFIG_OUTER_CACHE is not set + +# +# Bus support +# +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +# CONFIG_PCCARD is not set + +# +# Kernel Features +# +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +CONFIG_VMSPLIT_3G=y +# CONFIG_VMSPLIT_2G is not set +# CONFIG_VMSPLIT_1G is not set +CONFIG_PAGE_OFFSET=0xC0000000 +CONFIG_PREEMPT=y +CONFIG_HZ=100 +CONFIG_AEABI=y +# CONFIG_OABI_COMPAT is not set +CONFIG_ARCH_FLATMEM_HAS_HOLES=y +# CONFIG_ARCH_SPARSEMEM_DEFAULT is not set +# CONFIG_ARCH_SELECT_MEMORY_MODEL is not set +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_SPLIT_PTLOCK_CPUS=4096 +# CONFIG_RESOURCES_64BIT is not set +# CONFIG_PHYS_ADDR_T_64BIT is not set +CONFIG_ZONE_DMA_FLAG=1 +CONFIG_BOUNCE=y +CONFIG_VIRT_TO_BUS=y +CONFIG_UNEVICTABLE_LRU=y +# CONFIG_LEDS is not set +CONFIG_ALIGNMENT_TRAP=y + +# +# Boot options +# +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="noinitrd console=ttymxc0 root=/dev/mtdblock2 rw ip=off" +# CONFIG_XIP_KERNEL is not set +# CONFIG_KEXEC is not set + +# +# CPU Power Management +# +# CONFIG_CPU_FREQ is not set +# CONFIG_CPU_IDLE is not set + +# +# Floating point emulation +# + +# +# At least one emulation must be selected +# +# CONFIG_VFP is not set + +# +# Userspace binary formats +# +CONFIG_BINFMT_ELF=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_HAVE_AOUT=y +# CONFIG_BINFMT_AOUT is not set +# CONFIG_BINFMT_MISC is not set + +# +# Power management options +# +CONFIG_PM=y +# CONFIG_PM_DEBUG is not set +CONFIG_PM_SLEEP=y +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +# CONFIG_APM_EMULATION is not set +CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +CONFIG_PACKET_MMAP=y +CONFIG_UNIX=y +CONFIG_XFRM=y +# CONFIG_XFRM_USER is not set +# CONFIG_XFRM_SUB_POLICY is not set +# CONFIG_XFRM_MIGRATE is not set +# CONFIG_XFRM_STATISTICS is not set +# CONFIG_NET_KEY is not set +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_FIB_HASH=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +# CONFIG_IP_PNP_RARP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_IP_MROUTE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +# CONFIG_INET_TUNNEL is not set +CONFIG_INET_XFRM_MODE_TRANSPORT=y +CONFIG_INET_XFRM_MODE_TUNNEL=y +CONFIG_INET_XFRM_MODE_BEET=y +# CONFIG_INET_LRO is not set +CONFIG_INET_DIAG=y +CONFIG_INET_TCP_DIAG=y +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +# CONFIG_IPV6 is not set +# CONFIG_NETWORK_SECMARK is not set +# CONFIG_NETFILTER is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_NET_DSA is not set +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_NET_SCHED is not set + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_HAMRADIO is not set +# CONFIG_CAN is not set +CONFIG_IRDA=m + +# +# IrDA protocols +# +CONFIG_IRLAN=m +CONFIG_IRCOMM=m +# CONFIG_IRDA_ULTRA is not set + +# +# IrDA options +# +CONFIG_IRDA_CACHE_LAST_LSAP=y +CONFIG_IRDA_FAST_RR=y +# CONFIG_IRDA_DEBUG is not set + +# +# Infrared-port device drivers +# + +# +# SIR device drivers +# +CONFIG_IRTTY_SIR=m + +# +# Dongle support +# +# CONFIG_DONGLE is not set +# CONFIG_KINGSUN_DONGLE is not set +# CONFIG_KSDAZZLE_DONGLE is not set +# CONFIG_KS959_DONGLE is not set + +# +# FIR device drivers +# +# CONFIG_USB_IRDA is not set +# CONFIG_SIGMATEL_FIR is not set +# CONFIG_MCS_FIR is not set +# CONFIG_MXC_FIR is not set +# CONFIG_BT is not set +# CONFIG_AF_RXRPC is not set +# CONFIG_PHONET is not set +CONFIG_WIRELESS=y +# CONFIG_CFG80211 is not set +CONFIG_WIRELESS_OLD_REGULATORY=y +CONFIG_WIRELESS_EXT=y +CONFIG_WIRELESS_EXT_SYSFS=y +# CONFIG_MAC80211 is not set +CONFIG_IEEE80211=y +# CONFIG_IEEE80211_DEBUG is not set +# CONFIG_IEEE80211_CRYPT_WEP is not set +# CONFIG_IEEE80211_CRYPT_CCMP is not set +# CONFIG_IEEE80211_CRYPT_TKIP is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +CONFIG_FIRMWARE_IN_KERNEL=y +CONFIG_EXTRA_FIRMWARE="" +# CONFIG_SYS_HYPERVISOR is not set +CONFIG_CONNECTOR=y +CONFIG_PROC_EVENTS=y +CONFIG_MTD=y +# CONFIG_MTD_DEBUG is not set +# CONFIG_MTD_CONCAT is not set +CONFIG_MTD_PARTITIONS=y +CONFIG_MTD_REDBOOT_PARTS=y +CONFIG_MTD_REDBOOT_DIRECTORY_BLOCK=-1 +# CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED is not set +# CONFIG_MTD_REDBOOT_PARTS_READONLY is not set +CONFIG_MTD_CMDLINE_PARTS=y +# CONFIG_MTD_AFS_PARTS is not set +# CONFIG_MTD_AR7_PARTS is not set + +# +# User Modules And Translation Layers +# +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLKDEVS=y +CONFIG_MTD_BLOCK=y +# CONFIG_FTL is not set +# CONFIG_NFTL is not set +# CONFIG_INFTL is not set +# CONFIG_RFD_FTL is not set +# CONFIG_SSFDC is not set +# CONFIG_MTD_OOPS is not set + +# +# RAM/ROM/Flash chip drivers +# +CONFIG_MTD_CFI=y +# CONFIG_MTD_JEDECPROBE is not set +CONFIG_MTD_GEN_PROBE=y +CONFIG_MTD_CFI_ADV_OPTIONS=y +CONFIG_MTD_CFI_NOSWAP=y +# CONFIG_MTD_CFI_BE_BYTE_SWAP is not set +# CONFIG_MTD_CFI_LE_BYTE_SWAP is not set +CONFIG_MTD_CFI_GEOMETRY=y +# CONFIG_MTD_MAP_BANK_WIDTH_1 is not set +CONFIG_MTD_MAP_BANK_WIDTH_2=y +# CONFIG_MTD_MAP_BANK_WIDTH_4 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set +CONFIG_MTD_CFI_I1=y +# CONFIG_MTD_CFI_I2 is not set +# CONFIG_MTD_CFI_I4 is not set +# CONFIG_MTD_CFI_I8 is not set +# CONFIG_MTD_OTP is not set +# CONFIG_MTD_CFI_INTELEXT is not set +CONFIG_MTD_CFI_AMDSTD=y +# CONFIG_MTD_CFI_STAA is not set +CONFIG_MTD_CFI_UTIL=y +CONFIG_MTD_RAM=y +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_ABSENT is not set +# CONFIG_MTD_XIP is not set + +# +# Mapping drivers for chip access +# +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_PHYSMAP is not set +# CONFIG_MTD_ARM_INTEGRATOR is not set +# CONFIG_MTD_PLATRAM is not set +CONFIG_MTD_MXC=y + +# +# Self-contained MTD device drivers +# +# CONFIG_MTD_DATAFLASH is not set +# CONFIG_MTD_M25P80 is not set +# CONFIG_MTD_SLRAM is not set +# CONFIG_MTD_PHRAM is not set +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_BLOCK2MTD is not set + +# +# Disk-On-Chip Device Drivers +# +# CONFIG_MTD_DOC2000 is not set +# CONFIG_MTD_DOC2001 is not set +# CONFIG_MTD_DOC2001PLUS is not set +CONFIG_MTD_NAND=y +# CONFIG_MTD_NAND_VERIFY_WRITE is not set +# CONFIG_MTD_NAND_ECC_SMC is not set +# CONFIG_MTD_NAND_MUSEUM_IDS is not set +CONFIG_MTD_NAND_IDS=y +# CONFIG_MTD_NAND_DISKONCHIP is not set +# CONFIG_MTD_NAND_NANDSIM is not set +CONFIG_MTD_NAND_MXC=y +# CONFIG_MTD_NAND_MXC_SWECC is not set +# CONFIG_MTD_NAND_MXC_FORCE_CE is not set +# CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 is not set +# CONFIG_MXC_NAND_LOW_LEVEL_ERASE is not set +# CONFIG_MTD_NAND_PLATFORM is not set +# CONFIG_MTD_ALAUDA is not set +# CONFIG_MTD_ONENAND is not set + +# +# UBI - Unsorted block images +# +# CONFIG_MTD_UBI is not set +# CONFIG_PARPORT is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=y +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +# CONFIG_BLK_DEV_NBD is not set +# CONFIG_BLK_DEV_UB is not set +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=16 +CONFIG_BLK_DEV_RAM_SIZE=16384 +# CONFIG_BLK_DEV_XIP is not set +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +CONFIG_MISC_DEVICES=y +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_ICS932S401 is not set +# CONFIG_ENCLOSURE_SERVICES is not set +# CONFIG_C2PORT is not set +CONFIG_HAVE_IDE=y +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_TGT is not set +# CONFIG_SCSI_NETLINK is not set +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +# CONFIG_BLK_DEV_SR is not set +# CONFIG_CHR_DEV_SG is not set +# CONFIG_CHR_DEV_SCH is not set + +# +# Some SCSI devices (e.g. CD jukebox) support multiple LUNs +# +CONFIG_SCSI_MULTI_LUN=y +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +CONFIG_SCSI_WAIT_SCAN=m + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +CONFIG_SCSI_LOWLEVEL=y +# CONFIG_ISCSI_TCP is not set +# CONFIG_SCSI_DEBUG is not set +# CONFIG_SCSI_DH is not set +CONFIG_ATA=m +# CONFIG_ATA_NONSTANDARD is not set +# CONFIG_SATA_PMP is not set +CONFIG_ATA_SFF=y +# CONFIG_SATA_MV is not set +# CONFIG_PATA_PLATFORM is not set +CONFIG_PATA_FSL=m +# CONFIG_MD is not set +CONFIG_NETDEVICES=y +# CONFIG_DUMMY is not set +# CONFIG_BONDING is not set +# CONFIG_MACVLAN is not set +# CONFIG_EQUALIZER is not set +# CONFIG_TUN is not set +# CONFIG_VETH is not set +# CONFIG_PHYLIB is not set +CONFIG_NET_ETHERNET=y +CONFIG_MII=y +# CONFIG_AX88796 is not set +# CONFIG_SMC91X is not set +# CONFIG_DM9000 is not set +# CONFIG_ENC28J60 is not set +# CONFIG_SMC911X is not set +# CONFIG_SMSC911X is not set +# CONFIG_IBM_NEW_EMAC_ZMII is not set +# CONFIG_IBM_NEW_EMAC_RGMII is not set +# CONFIG_IBM_NEW_EMAC_TAH is not set +# CONFIG_IBM_NEW_EMAC_EMAC4 is not set +# CONFIG_IBM_NEW_EMAC_NO_FLOW_CTRL is not set +# CONFIG_IBM_NEW_EMAC_MAL_CLR_ICINTSTAT is not set +# CONFIG_IBM_NEW_EMAC_MAL_COMMON_ERR is not set +# CONFIG_B44 is not set +CONFIG_CS89x0=y +CONFIG_CS89x0_NONISA_IRQ=y +# CONFIG_FEC is not set +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set + +# +# Wireless LAN +# +# CONFIG_WLAN_PRE80211 is not set +# CONFIG_WLAN_80211 is not set +# CONFIG_IWLWIFI_LEDS is not set + +# +# USB Network Adapters +# +# CONFIG_USB_CATC is not set +# CONFIG_USB_KAWETH is not set +# CONFIG_USB_PEGASUS is not set +# CONFIG_USB_RTL8150 is not set +# CONFIG_USB_USBNET is not set +# CONFIG_WAN is not set +# CONFIG_PPP is not set +# CONFIG_SLIP is not set +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_ISDN is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +# CONFIG_INPUT_POLLDEV is not set + +# +# Userland interfaces +# +# CONFIG_INPUT_MOUSEDEV is not set +# CONFIG_INPUT_JOYDEV is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +CONFIG_KEYBOARD_MXC=y +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TABLET is not set +CONFIG_INPUT_TOUCHSCREEN=y +# CONFIG_TOUCHSCREEN_ADS7846 is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_INEXIO is not set +# CONFIG_TOUCHSCREEN_MK712 is not set +CONFIG_TOUCHSCREEN_MXC=y +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +# CONFIG_TOUCHSCREEN_TOUCHIT213 is not set +# CONFIG_INPUT_MISC is not set + +# +# Hardware I/O ports +# +# CONFIG_SERIO is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +# CONFIG_VT_HW_CONSOLE_BINDING is not set +CONFIG_DEVKMEM=y +# CONFIG_SERIAL_NONSTANDARD is not set +# CONFIG_MXC_IIM is not set + +# +# Serial drivers +# +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_NR_UARTS=1 +CONFIG_SERIAL_8250_RUNTIME_UARTS=1 +CONFIG_SERIAL_8250_EXTENDED=y +# CONFIG_SERIAL_8250_MANY_PORTS is not set +# CONFIG_SERIAL_8250_SHARE_IRQ is not set +# CONFIG_SERIAL_8250_DETECT_IRQ is not set +# CONFIG_SERIAL_8250_RSA is not set + +# +# Non-8250 serial port support +# +CONFIG_SERIAL_MXC=y +CONFIG_SERIAL_MXC_CONSOLE=y +# CONFIG_SERIAL_IMX is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_UNIX98_PTYS=y +CONFIG_LEGACY_PTYS=y +CONFIG_LEGACY_PTY_COUNT=256 +# CONFIG_IPMI_HANDLER is not set +CONFIG_HW_RANDOM=y +# CONFIG_NVRAM is not set +# CONFIG_R3964 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_HELPER_AUTO=y + +# +# I2C Hardware Bus support +# + +# +# I2C system bus drivers (mostly embedded / system-on-chip) +# +CONFIG_I2C_MXC=y +# CONFIG_I2C_MXC_HS is not set +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_SIMTEC is not set + +# +# External I2C/SMBus adapter drivers +# +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_TINY_USB is not set + +# +# Other I2C/SMBus bus drivers +# +# CONFIG_I2C_PCA_PLATFORM is not set +# CONFIG_I2C_STUB is not set + +# +# Miscellaneous I2C Chip support +# +# CONFIG_DS1682 is not set +# CONFIG_AT24 is not set +# CONFIG_SENSORS_EEPROM is not set +# CONFIG_SENSORS_PCF8574 is not set +# CONFIG_PCF8575 is not set +# CONFIG_SENSORS_PCA9539 is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_SENSORS_MAX6875 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CHIP is not set +# CONFIG_I2C_SLAVE is not set +CONFIG_SPI=y +CONFIG_SPI_MASTER=y + +# +# SPI Master Controller Drivers +# +CONFIG_SPI_BITBANG=y +CONFIG_SPI_MXC=y +# CONFIG_SPI_MXC_TEST_LOOPBACK is not set +CONFIG_SPI_MXC_SELECT1=y +CONFIG_SPI_MXC_SELECT2=y +# CONFIG_SPI_MXC_SELECT3 is not set + +# +# SPI Protocol Masters +# +# CONFIG_SPI_AT25 is not set +# CONFIG_SPI_SPIDEV is not set +# CONFIG_SPI_TLE62X0 is not set +CONFIG_W1=m +CONFIG_W1_CON=y + +# +# 1-wire Bus Masters +# +# CONFIG_W1_MASTER_DS2490 is not set +# CONFIG_W1_MASTER_DS2482 is not set +CONFIG_W1_MASTER_MXC=m + +# +# 1-wire Slaves +# +# CONFIG_W1_SLAVE_THERM is not set +# CONFIG_W1_SLAVE_SMEM is not set +# CONFIG_W1_SLAVE_DS2751 is not set +CONFIG_W1_SLAVE_DS2433=m +# CONFIG_W1_SLAVE_DS2438 is not set +# CONFIG_W1_SLAVE_DS2433_CRC is not set +# CONFIG_W1_SLAVE_DS2760 is not set +# CONFIG_W1_SLAVE_BQ27000 is not set +# CONFIG_POWER_SUPPLY is not set +# CONFIG_HWMON is not set +# CONFIG_THERMAL is not set +# CONFIG_THERMAL_HWMON is not set +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_NOWAYOUT=y + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +CONFIG_MXC_WATCHDOG=y + +# +# USB-based Watchdog Cards +# +# CONFIG_USBPCWATCHDOG is not set +CONFIG_SSB_POSSIBLE=y + +# +# Sonics Silicon Backplane +# +# CONFIG_SSB is not set + +# +# Multifunction device drivers +# +# CONFIG_MFD_CORE is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_HTC_PASIC3 is not set +# CONFIG_MFD_TMIO is not set +# CONFIG_PMIC_DA903X is not set +# CONFIG_MFD_WM8400 is not set +# CONFIG_MFD_WM8350_I2C is not set + +# +# Multimedia devices +# + +# +# Multimedia core support +# +CONFIG_VIDEO_DEV=y +CONFIG_VIDEO_V4L2_COMMON=y +CONFIG_VIDEO_ALLOW_V4L1=y +CONFIG_VIDEO_V4L1_COMPAT=y +# CONFIG_DVB_CORE is not set +CONFIG_VIDEO_MEDIA=y + +# +# Multimedia drivers +# +# CONFIG_MEDIA_ATTACH is not set +CONFIG_MEDIA_TUNER=y +# CONFIG_MEDIA_TUNER_CUSTOMIZE is not set +CONFIG_MEDIA_TUNER_SIMPLE=y +CONFIG_MEDIA_TUNER_TDA8290=y +CONFIG_MEDIA_TUNER_TDA9887=y +CONFIG_MEDIA_TUNER_TEA5761=y +CONFIG_MEDIA_TUNER_TEA5767=y +CONFIG_MEDIA_TUNER_MT20XX=y +CONFIG_MEDIA_TUNER_XC2028=y +CONFIG_MEDIA_TUNER_XC5000=y +CONFIG_VIDEO_V4L2=y +CONFIG_VIDEO_V4L1=y +CONFIG_VIDEO_CAPTURE_DRIVERS=y +# CONFIG_VIDEO_ADV_DEBUG is not set +# CONFIG_VIDEO_FIXED_MINOR_RANGES is not set +CONFIG_VIDEO_HELPER_CHIPS_AUTO=y +# CONFIG_VIDEO_VIVI is not set +CONFIG_VIDEO_MXC_CAMERA=y + +# +# MXC Camera/V4L2 PRP Features support +# +CONFIG_VIDEO_MXC_EMMA_CAMERA=y +# CONFIG_VIDEO_MXC_CSI_DMA is not set +# CONFIG_MXC_CAMERA_MC521DA is not set +# CONFIG_MXC_EMMA_CAMERA_MICRON111 is not set +CONFIG_MXC_CAMERA_OV2640_EMMA=y +# CONFIG_MXC_CAMERA_MICRON111 is not set +# CONFIG_MXC_CAMERA_OV2640 is not set +# CONFIG_MXC_CAMERA_OV3640 is not set +# CONFIG_MXC_TVIN_ADV7180 is not set +CONFIG_VIDEO_MXC_OUTPUT=y +CONFIG_VIDEO_MXC_EMMA_OUTPUT=y +CONFIG_VIDEO_MXC_OUTPUT_FBSYNC=y +CONFIG_VIDEO_MXC_OPL=y +# CONFIG_VIDEO_CPIA is not set +# CONFIG_VIDEO_CPIA2 is not set +# CONFIG_VIDEO_SAA5246A is not set +# CONFIG_VIDEO_SAA5249 is not set +# CONFIG_SOC_CAMERA is not set +CONFIG_V4L_USB_DRIVERS=y +# CONFIG_USB_VIDEO_CLASS is not set +CONFIG_USB_GSPCA=m +# CONFIG_USB_M5602 is not set +# CONFIG_USB_GSPCA_CONEX is not set +# CONFIG_USB_GSPCA_ETOMS is not set +# CONFIG_USB_GSPCA_FINEPIX is not set +# CONFIG_USB_GSPCA_MARS is not set +# CONFIG_USB_GSPCA_OV519 is not set +# CONFIG_USB_GSPCA_PAC207 is not set +# CONFIG_USB_GSPCA_PAC7311 is not set +# CONFIG_USB_GSPCA_SONIXB is not set +# CONFIG_USB_GSPCA_SONIXJ is not set +# CONFIG_USB_GSPCA_SPCA500 is not set +# CONFIG_USB_GSPCA_SPCA501 is not set +# CONFIG_USB_GSPCA_SPCA505 is not set +# CONFIG_USB_GSPCA_SPCA506 is not set +# CONFIG_USB_GSPCA_SPCA508 is not set +# CONFIG_USB_GSPCA_SPCA561 is not set +# CONFIG_USB_GSPCA_STK014 is not set +# CONFIG_USB_GSPCA_SUNPLUS is not set +# CONFIG_USB_GSPCA_T613 is not set +# CONFIG_USB_GSPCA_TV8532 is not set +# CONFIG_USB_GSPCA_VC032X is not set +# CONFIG_USB_GSPCA_ZC3XX is not set +# CONFIG_VIDEO_PVRUSB2 is not set +# CONFIG_VIDEO_EM28XX is not set +# CONFIG_VIDEO_USBVISION is not set +# CONFIG_USB_VICAM is not set +# CONFIG_USB_IBMCAM is not set +# CONFIG_USB_KONICAWC is not set +# CONFIG_USB_QUICKCAM_MESSENGER is not set +# CONFIG_USB_ET61X251 is not set +# CONFIG_VIDEO_OVCAMCHIP is not set +# CONFIG_USB_OV511 is not set +# CONFIG_USB_SE401 is not set +# CONFIG_USB_SN9C102 is not set +# CONFIG_USB_STV680 is not set +# CONFIG_USB_ZC0301 is not set +# CONFIG_USB_PWC is not set +# CONFIG_USB_ZR364XX is not set +# CONFIG_USB_STKWEBCAM is not set +# CONFIG_USB_S2255 is not set +CONFIG_RADIO_ADAPTERS=y +# CONFIG_USB_DSBR is not set +# CONFIG_USB_SI470X is not set +# CONFIG_USB_MR800 is not set +CONFIG_DAB=y +# CONFIG_USB_DABUSB is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +# CONFIG_VIDEO_OUTPUT_CONTROL is not set +CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +# CONFIG_FB_DDC is not set +# CONFIG_FB_BOOT_VESA_SUPPORT is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_FOREIGN_ENDIAN is not set +# CONFIG_FB_SYS_FOPS is not set +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_MODE_HELPERS is not set +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# +CONFIG_FB_MXC=y +CONFIG_FB_MXC_SYNC_PANEL=y +# CONFIG_FB_MXC_EPSON_VGA_SYNC_PANEL is not set +# CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL is not set +# CONFIG_FB_MXC_CH7026 is not set +CONFIG_FB_MXC_TVOUT=y +# CONFIG_FB_MXC_TVOUT_CH7024 is not set +# CONFIG_FB_MXC_ASYNC_PANEL is not set +# CONFIG_FB_UVESA is not set +# CONFIG_FB_S1D13XXX is not set +# CONFIG_FB_VIRTUAL is not set +# CONFIG_FB_METRONOME is not set +# CONFIG_FB_MB862XX is not set +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_LCD_CLASS_DEVICE is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_CORGI is not set +CONFIG_BACKLIGHT_MXC=y +CONFIG_BACKLIGHT_MXC_LCDC=y +CONFIG_BACKLIGHT_MXC_PMIC=y + +# +# Display device support +# +# CONFIG_DISPLAY_SUPPORT is not set + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE=y +# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +# CONFIG_FONTS is not set +CONFIG_FONT_8x8=y +CONFIG_FONT_8x16=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_LOGO_LINUX_CLUT224=y +CONFIG_SOUND=y +CONFIG_SOUND_OSS_CORE=y +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +# CONFIG_SND_SEQUENCER is not set +CONFIG_SND_OSSEMUL=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +CONFIG_SND_PCM_OSS_PLUGINS=y +# CONFIG_SND_DYNAMIC_MINORS is not set +CONFIG_SND_SUPPORT_OLD_API=y +CONFIG_SND_VERBOSE_PROCFS=y +# CONFIG_SND_VERBOSE_PRINTK is not set +# CONFIG_SND_DEBUG is not set +CONFIG_SND_DRIVERS=y +# CONFIG_SND_DUMMY is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_MPU401 is not set +CONFIG_SND_ARM=y +# CONFIG_SND_MXC_SPDIF is not set +CONFIG_SND_MXC_PMIC=y +# CONFIG_HEADSET_DETECT_ENABLE is not set +CONFIG_SND_SPI=y +CONFIG_SND_USB=y +# CONFIG_SND_USB_AUDIO is not set +# CONFIG_SND_USB_CAIAQ is not set +# CONFIG_SND_SOC is not set +# CONFIG_SOUND_PRIME is not set +CONFIG_HID_SUPPORT=y +CONFIG_HID=y +# CONFIG_HID_DEBUG is not set +# CONFIG_HIDRAW is not set + +# +# USB Input Devices +# +CONFIG_USB_HID=m +# CONFIG_HID_PID is not set +# CONFIG_USB_HIDDEV is not set + +# +# USB HID Boot Protocol drivers +# +# CONFIG_USB_KBD is not set +# CONFIG_USB_MOUSE is not set + +# +# Special HID drivers +# +CONFIG_HID_COMPAT=y +CONFIG_HID_A4TECH=m +CONFIG_HID_APPLE=m +CONFIG_HID_BELKIN=m +CONFIG_HID_BRIGHT=m +CONFIG_HID_CHERRY=m +CONFIG_HID_CHICONY=m +CONFIG_HID_CYPRESS=m +CONFIG_HID_DELL=m +CONFIG_HID_EZKEY=m +CONFIG_HID_GYRATION=m +CONFIG_HID_LOGITECH=m +# CONFIG_LOGITECH_FF is not set +# CONFIG_LOGIRUMBLEPAD2_FF is not set +CONFIG_HID_MICROSOFT=m +CONFIG_HID_MONTEREY=m +CONFIG_HID_PANTHERLORD=m +# CONFIG_PANTHERLORD_FF is not set +CONFIG_HID_PETALYNX=m +CONFIG_HID_SAMSUNG=m +CONFIG_HID_SONY=m +CONFIG_HID_SUNPLUS=m +# CONFIG_THRUSTMASTER_FF is not set +# CONFIG_ZEROPLUS_FF is not set +CONFIG_USB_SUPPORT=y +CONFIG_USB_ARCH_HAS_HCD=y +# CONFIG_USB_ARCH_HAS_OHCI is not set +CONFIG_USB_ARCH_HAS_EHCI=y +CONFIG_USB=y +# CONFIG_USB_DEBUG is not set +# CONFIG_USB_ANNOUNCE_NEW_DEVICES is not set + +# +# Miscellaneous USB options +# +CONFIG_USB_DEVICEFS=y +CONFIG_USB_DEVICE_CLASS=y +# CONFIG_USB_DYNAMIC_MINORS is not set +CONFIG_USB_SUSPEND=y +# CONFIG_USB_OTG is not set +# CONFIG_USB_OTG_WHITELIST is not set +# CONFIG_USB_OTG_BLACKLIST_HUB is not set +CONFIG_USB_MON=y +# CONFIG_USB_WUSB is not set +# CONFIG_USB_WUSB_CBAF is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_C67X00_HCD is not set +CONFIG_USB_EHCI_HCD=m +CONFIG_USB_EHCI_ARC=y +CONFIG_USB_EHCI_ARC_H1=y +# CONFIG_USB_EHCI_ARC_H2 is not set +CONFIG_USB_EHCI_ARC_OTG=y +# CONFIG_USB_EHCI_ARC_OTG_WAKE_UP is not set +# CONFIG_USB_STATIC_IRAM is not set +# CONFIG_USB_EHCI_FSL_MC13783 is not set +CONFIG_USB_EHCI_FSL_1301=y +# CONFIG_USB_EHCI_FSL_1504 is not set +# CONFIG_USB_EHCI_FSL_UTMI is not set +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +# CONFIG_USB_ISP116X_HCD is not set +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +# CONFIG_USB_HWA_HCD is not set +# CONFIG_USB_GADGET_MUSB_HDRC is not set + +# +# USB Device Class drivers +# +# CONFIG_USB_ACM is not set +# CONFIG_USB_PRINTER is not set +# CONFIG_USB_WDM is not set +# CONFIG_USB_TMC is not set + +# +# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; +# + +# +# see USB_STORAGE Help for more information +# +CONFIG_USB_STORAGE=y +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +# CONFIG_USB_STORAGE_FREECOM is not set +# CONFIG_USB_STORAGE_ISD200 is not set +# CONFIG_USB_STORAGE_DPCM is not set +# CONFIG_USB_STORAGE_USBAT is not set +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_ONETOUCH is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set +# CONFIG_USB_LIBUSUAL is not set + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set + +# +# USB port drivers +# +# CONFIG_USB_SERIAL is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_SEVSEG is not set +# CONFIG_USB_RIO500 is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_BERRY_CHARGE is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_PHIDGET is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_USB_SISUSBVGA is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_TEST is not set +# CONFIG_USB_ISIGHTFW is not set +# CONFIG_USB_VST is not set +CONFIG_USB_GADGET=m +# CONFIG_USB_GADGET_DEBUG_FILES is not set +CONFIG_USB_GADGET_VBUS_DRAW=2 +CONFIG_USB_GADGET_SELECTED=y +# CONFIG_USB_GADGET_AT91 is not set +# CONFIG_USB_GADGET_ATMEL_USBA is not set +# CONFIG_USB_GADGET_FSL_USB2 is not set +# CONFIG_USB_GADGET_LH7A40X is not set +# CONFIG_USB_GADGET_OMAP is not set +# CONFIG_USB_GADGET_PXA25X is not set +# CONFIG_USB_GADGET_PXA27X is not set +# CONFIG_USB_GADGET_S3C2410 is not set +# CONFIG_USB_GADGET_M66592 is not set +# CONFIG_USB_GADGET_AMD5536UDC is not set +# CONFIG_USB_GADGET_FSL_QE is not set +# CONFIG_USB_GADGET_NET2280 is not set +# CONFIG_USB_GADGET_GOKU is not set +CONFIG_USB_GADGET_ARC=y +CONFIG_USB_ARC=m +# CONFIG_USB_GADGET_DUMMY_HCD is not set +# CONFIG_USB_GADGET_DUALSPEED is not set +CONFIG_USB_GADGET_ARC_OTG=y +# CONFIG_USB_GADGET_WAKE_UP is not set +# CONFIG_USB_GADGET_FSL_MC13783 is not set +CONFIG_USB_GADGET_FSL_1301=y +# CONFIG_USB_GADGET_FSL_1504 is not set +# CONFIG_USB_GADGET_FSL_UTMI is not set +# CONFIG_USB_ZERO is not set +CONFIG_USB_ETH=m +CONFIG_USB_ETH_RNDIS=y +CONFIG_USB_GADGETFS=m +CONFIG_USB_FILE_STORAGE=m +# CONFIG_USB_FILE_STORAGE_TEST is not set +CONFIG_USB_G_SERIAL=m +# CONFIG_USB_MIDI_GADGET is not set +# CONFIG_USB_G_PRINTER is not set +# CONFIG_USB_CDC_COMPOSITE is not set +CONFIG_MMC=y +# CONFIG_MMC_DEBUG is not set +CONFIG_MMC_UNSAFE_RESUME=y + +# +# MMC/SD/SDIO Card Drivers +# +CONFIG_MMC_BLOCK=y +CONFIG_MMC_BLOCK_BOUNCE=y +# CONFIG_SDIO_UART is not set +# CONFIG_MMC_TEST is not set + +# +# MMC/SD/SDIO Host Controller Drivers +# +# CONFIG_MMC_SDHCI is not set +# CONFIG_MMC_SPI is not set +CONFIG_MMC_MXC=y +# CONFIG_MMC_IMX_ESDHCI is not set +# CONFIG_MMC_IMX_ESDHCI_PIO_MODE is not set +# CONFIG_MEMSTICK is not set +# CONFIG_ACCESSIBILITY is not set +# CONFIG_NEW_LEDS is not set +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set +# CONFIG_RTC_DRV_S35390A is not set +# CONFIG_RTC_DRV_FM3130 is not set +# CONFIG_RTC_DRV_RX8581 is not set + +# +# SPI RTC drivers +# +# CONFIG_RTC_DRV_M41T94 is not set +# CONFIG_RTC_DRV_DS1305 is not set +# CONFIG_RTC_DRV_DS1390 is not set +# CONFIG_RTC_DRV_MAX6902 is not set +# CONFIG_RTC_DRV_R9701 is not set +# CONFIG_RTC_DRV_RS5C348 is not set +# CONFIG_RTC_DRV_DS3234 is not set + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_BQ4802 is not set +# CONFIG_RTC_DRV_V3020 is not set + +# +# on-CPU RTC drivers +# +CONFIG_RTC_MXC=y +# CONFIG_RTC_DRV_MXC_V2 is not set +# CONFIG_DMADEVICES is not set +# CONFIG_REGULATOR is not set +# CONFIG_UIO is not set + +# +# MXC support drivers +# + +# +# MXC SSI support +# +CONFIG_MXC_SSI=y + +# +# MXC Digital Audio Multiplexer support +# +CONFIG_MXC_DAM=y + +# +# MXC PMIC support +# +CONFIG_MXC_PMIC=y +CONFIG_MXC_PMIC_MC13783=y +# CONFIG_MXC_PMIC_MC13892 is not set +# CONFIG_MXC_PMIC_SPI is not set +# CONFIG_MXC_PMIC_MC34704 is not set +# CONFIG_MXC_PMIC_MC9SDZ60 is not set +CONFIG_MXC_PMIC_CHARDEV=y + +# +# MXC PMIC Client Drivers +# +CONFIG_MXC_MC13783_ADC=y +CONFIG_MXC_MC13783_AUDIO=y +CONFIG_MXC_MC13783_RTC=y +CONFIG_MXC_MC13783_LIGHT=y +CONFIG_MXC_MC13783_BATTERY=y +CONFIG_MXC_MC13783_CONNECTIVITY=y +CONFIG_MXC_MC13783_POWER=y +# CONFIG_MXC_PMIC_MC9S08DZ60 is not set + +# +# MXC Security Drivers +# +CONFIG_MXC_SECURITY_SCC=y +# CONFIG_SCC_DEBUG is not set + +# +# SAHARA2 Security Hardware Support +# +CONFIG_MXC_SAHARA=y +CONFIG_MXC_SAHARA_USER_MODE=y +# CONFIG_MXC_SAHARA_POLL_MODE is not set + +# +# MXC MPEG4 Encoder Kernel module support +# + +# +# MXC HARDWARE EVENT +# +CONFIG_MXC_HWEVENT=y + +# +# MXC VPU(Video Processing Unit) support +# +CONFIG_MXC_VPU=y +# CONFIG_MXC_VPU_DEBUG is not set + +# +# MXC Asynchronous Sample Rate Converter support +# + +# +# MXC Bluetooth support +# + +# +# Broadcom GPS ioctrl support +# + +# +# MXC Media Local Bus Driver +# + +# +# i.MX ADC support +# +# CONFIG_IMX_ADC is not set + +# +# File systems +# +CONFIG_EXT2_FS=y +# CONFIG_EXT2_FS_XATTR is not set +# CONFIG_EXT2_FS_XIP is not set +# CONFIG_EXT3_FS is not set +# CONFIG_EXT4_FS is not set +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_FS_POSIX_ACL is not set +CONFIG_FILE_LOCKING=y +# CONFIG_XFS_FS is not set +# CONFIG_OCFS2_FS is not set +CONFIG_DNOTIFY=y +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_QUOTA is not set +# CONFIG_AUTOFS_FS is not set +CONFIG_AUTOFS4_FS=m +# CONFIG_FUSE_FS is not set + +# +# CD-ROM/DVD Filesystems +# +# CONFIG_ISO9660_FS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +# CONFIG_TMPFS_POSIX_ACL is not set +# CONFIG_HUGETLB_PAGE is not set +# CONFIG_CONFIGFS_FS is not set + +# +# Miscellaneous filesystems +# +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_DEBUG=0 +CONFIG_JFFS2_FS_WRITEBUFFER=y +# CONFIG_JFFS2_FS_WBUF_VERIFY is not set +# CONFIG_JFFS2_SUMMARY is not set +# CONFIG_JFFS2_FS_XATTR is not set +# CONFIG_JFFS2_COMPRESSION_OPTIONS is not set +CONFIG_JFFS2_ZLIB=y +# CONFIG_JFFS2_LZO is not set +CONFIG_JFFS2_RTIME=y +# CONFIG_JFFS2_RUBIN is not set +CONFIG_CRAMFS=y +# CONFIG_VXFS_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +# CONFIG_NFS_V3_ACL is not set +# CONFIG_NFS_V4 is not set +CONFIG_ROOT_NFS=y +# CONFIG_NFSD is not set +CONFIG_LOCKD=y +CONFIG_LOCKD_V4=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=y +# CONFIG_SUNRPC_REGISTER_V4 is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +# CONFIG_RPCSEC_GSS_SPKM3 is not set +# CONFIG_SMB_FS is not set +# CONFIG_CIFS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +CONFIG_NLS_ASCII=m +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +CONFIG_NLS_UTF8=m +# CONFIG_DLM is not set + +# +# Kernel hacking +# +# CONFIG_PRINTK_TIME is not set +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +CONFIG_FRAME_WARN=1024 +# CONFIG_MAGIC_SYSRQ is not set +# CONFIG_UNUSED_SYMBOLS is not set +# CONFIG_DEBUG_FS is not set +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_KERNEL is not set +# CONFIG_SLUB_DEBUG_ON is not set +# CONFIG_SLUB_STATS is not set +# CONFIG_DEBUG_BUGVERBOSE is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +CONFIG_FRAME_POINTER=y +# CONFIG_RCU_CPU_STALL_DETECTOR is not set +# CONFIG_LATENCYTOP is not set +CONFIG_SYSCTL_SYSCALL_CHECK=y +CONFIG_HAVE_FUNCTION_TRACER=y + +# +# Tracers +# +# CONFIG_DYNAMIC_PRINTK_DEBUG is not set +# CONFIG_SAMPLES is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_DEBUG_USER is not set + +# +# Security options +# +# CONFIG_KEYS is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITYFS is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +# CONFIG_CRYPTO_FIPS is not set +# CONFIG_CRYPTO_MANAGER is not set +# CONFIG_CRYPTO_MANAGER2 is not set +# CONFIG_CRYPTO_GF128MUL is not set +# CONFIG_CRYPTO_NULL is not set +# CONFIG_CRYPTO_CRYPTD is not set +# CONFIG_CRYPTO_AUTHENC is not set +# CONFIG_CRYPTO_TEST is not set + +# +# Authenticated Encryption with Associated Data +# +# CONFIG_CRYPTO_CCM is not set +# CONFIG_CRYPTO_GCM is not set +# CONFIG_CRYPTO_SEQIV is not set + +# +# Block modes +# +# CONFIG_CRYPTO_CBC is not set +# CONFIG_CRYPTO_CTR is not set +# CONFIG_CRYPTO_CTS is not set +# CONFIG_CRYPTO_ECB is not set +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_XTS is not set + +# +# Hash modes +# +# CONFIG_CRYPTO_HMAC is not set +# CONFIG_CRYPTO_XCBC is not set + +# +# Digest +# +# CONFIG_CRYPTO_CRC32C is not set +# CONFIG_CRYPTO_MD4 is not set +# CONFIG_CRYPTO_MD5 is not set +# CONFIG_CRYPTO_MICHAEL_MIC is not set +# CONFIG_CRYPTO_RMD128 is not set +# CONFIG_CRYPTO_RMD160 is not set +# CONFIG_CRYPTO_RMD256 is not set +# CONFIG_CRYPTO_RMD320 is not set +# CONFIG_CRYPTO_SHA1 is not set +# CONFIG_CRYPTO_SHA256 is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_WP512 is not set + +# +# Ciphers +# +# CONFIG_CRYPTO_AES is not set +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_ARC4 is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +# CONFIG_CRYPTO_DES is not set +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_SALSA20 is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SERPENT is not set +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TWOFISH is not set + +# +# Compression +# +# CONFIG_CRYPTO_DEFLATE is not set +# CONFIG_CRYPTO_LZO is not set + +# +# Random Number Generation +# +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_HW=y + +# +# Library routines +# +CONFIG_BITREVERSE=y +CONFIG_CRC_CCITT=m +# CONFIG_CRC16 is not set +# CONFIG_CRC_T10DIF is not set +# CONFIG_CRC_ITU_T is not set +CONFIG_CRC32=y +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_PLIST=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_DMA=y diff --git a/arch/arm/configs/imx31_3stack_defconfig b/arch/arm/configs/imx31_3stack_defconfig new file mode 100644 index 000000000000..d54abf1ea30b --- /dev/null +++ b/arch/arm/configs/imx31_3stack_defconfig @@ -0,0 +1,1835 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.28 +# Wed May 27 16:47:24 2009 +# +CONFIG_ARM=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +# CONFIG_GENERIC_GPIO is not set +CONFIG_GENERIC_TIME=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_MMU=y +# CONFIG_NO_IOPORT is not set +CONFIG_GENERIC_HARDIRQS=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +# CONFIG_ARCH_HAS_ILOG2_U32 is not set +# CONFIG_ARCH_HAS_ILOG2_U64 is not set +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_ZONE_DMA=y +CONFIG_ARCH_MTD_XIP=y +CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y +CONFIG_OPROFILE_ARMV6=y +CONFIG_OPROFILE_ARM11_CORE=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_LOCK_KERNEL=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="" +CONFIG_LOCALVERSION_AUTO=y +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +# CONFIG_POSIX_MQUEUE is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set +# CONFIG_AUDIT is not set +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=14 +# CONFIG_CGROUPS is not set +# CONFIG_GROUP_SCHED is not set +CONFIG_SYSFS_DEPRECATED=y +CONFIG_SYSFS_DEPRECATED_V2=y +# CONFIG_RELAY is not set +# CONFIG_NAMESPACES is not set +# CONFIG_BLK_DEV_INITRD is not set +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_SYSCTL=y +CONFIG_EMBEDDED=y +CONFIG_UID16=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_KALLSYMS=y +# CONFIG_KALLSYMS_EXTRA_PASS is not set +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +CONFIG_COMPAT_BRK=y +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_ANON_INODES=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_AIO=y +CONFIG_VM_EVENT_COUNTERS=y +CONFIG_SLUB_DEBUG=y +# CONFIG_SLAB is not set +CONFIG_SLUB=y +# CONFIG_SLOB is not set +CONFIG_PROFILING=y +# CONFIG_MARKERS is not set +CONFIG_OPROFILE=y +CONFIG_HAVE_OPROFILE=y +# CONFIG_KPROBES is not set +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +# CONFIG_TINY_SHMEM is not set +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_KMOD=y +CONFIG_BLOCK=y +# CONFIG_LBD is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_LSF is not set +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_INTEGRITY is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_AS=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_AS is not set +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" +CONFIG_CLASSIC_RCU=y +CONFIG_FREEZER=y + +# +# System Type +# +# CONFIG_ARCH_AAEC2000 is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_VERSATILE is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_CLPS7500 is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_H720X is not set +# CONFIG_ARCH_IMX is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP23XX is not set +# CONFIG_ARCH_IXP2000 is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_KIRKWOOD is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_NS9XXX is not set +# CONFIG_ARCH_LOKI is not set +# CONFIG_ARCH_MV78XX0 is not set +CONFIG_ARCH_MXC=y +# CONFIG_ARCH_ORION5X is not set +# CONFIG_ARCH_PNX4008 is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_SA1100 is not set +# CONFIG_ARCH_S3C2410 is not set +# CONFIG_ARCH_SHARK is not set +# CONFIG_ARCH_LH7A40X is not set +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_OMAP is not set +# CONFIG_ARCH_MSM is not set +# CONFIG_ARCH_STMP3XXX is not set + +# +# Boot options +# + +# +# Power management +# + +# +# Freescale MXC Implementations +# +# CONFIG_ARCH_MX37 is not set +# CONFIG_ARCH_MX35 is not set +# CONFIG_ARCH_MX51 is not set +CONFIG_ARCH_MX3=y +# CONFIG_ARCH_MX27 is not set +# CONFIG_ARCH_MX25 is not set +CONFIG_ARCH_MXC_HAS_NFC_V1=y +CONFIG_I2C_MXC_SELECT1=y +# CONFIG_I2C_MXC_SELECT2 is not set +CONFIG_MXC_SDMA_API=y +CONFIG_ARCH_MXC_HAS_NFC_V2=y +# CONFIG_I2C_MXC_SELECT3 is not set + +# +# MX3 Options +# +CONFIG_MX3_OPTIONS=y +# CONFIG_MACH_MX31ADS is not set +# CONFIG_MACH_PCM037 is not set +# CONFIG_MACH_MX31LITE is not set +CONFIG_MACH_MX31_3DS=y +# CONFIG_MX3_DOZE_DURING_IDLE is not set + +# +# SDMA options +# +# CONFIG_SDMA_IRAM is not set + +# +# Device options +# +CONFIG_ARCH_HAS_RNGA=y +CONFIG_ARCH_HAS_EVTMON=y +CONFIG_DMA_ZONE_SIZE=24 +CONFIG_ISP1504_MXC=y + +# +# Processor Type +# +CONFIG_CPU_32=y +# CONFIG_CPU_ARM926T is not set +CONFIG_CPU_V6=y +# CONFIG_CPU_32v6K is not set +# CONFIG_CPU_V7 is not set +CONFIG_CPU_32v6=y +CONFIG_CPU_ABRT_EV6=y +CONFIG_CPU_PABRT_NOIFAR=y +CONFIG_CPU_CACHE_V6=y +CONFIG_CPU_CACHE_VIPT=y +CONFIG_CPU_COPY_V6=y +CONFIG_CPU_TLB_V6=y +CONFIG_CPU_HAS_ASID=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y + +# +# Processor Features +# +CONFIG_ARM_THUMB=y +# CONFIG_CPU_ICACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_BPREDICT_DISABLE is not set +CONFIG_OUTER_CACHE=y +CONFIG_CACHE_L2X0=y +CONFIG_ARM_ERRATA_364296=y +CONFIG_ARM_ERRATA_411920=y + +# +# Bus support +# +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +# CONFIG_PCCARD is not set + +# +# Kernel Features +# +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +CONFIG_VMSPLIT_3G=y +# CONFIG_VMSPLIT_2G is not set +# CONFIG_VMSPLIT_1G is not set +CONFIG_PAGE_OFFSET=0xC0000000 +CONFIG_PREEMPT=y +CONFIG_HZ=100 +CONFIG_AEABI=y +# CONFIG_OABI_COMPAT is not set +CONFIG_ARCH_FLATMEM_HAS_HOLES=y +# CONFIG_ARCH_SPARSEMEM_DEFAULT is not set +# CONFIG_ARCH_SELECT_MEMORY_MODEL is not set +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_SPLIT_PTLOCK_CPUS=4 +# CONFIG_RESOURCES_64BIT is not set +# CONFIG_PHYS_ADDR_T_64BIT is not set +CONFIG_ZONE_DMA_FLAG=1 +CONFIG_BOUNCE=y +CONFIG_VIRT_TO_BUS=y +CONFIG_UNEVICTABLE_LRU=y +# CONFIG_LEDS is not set +CONFIG_ALIGNMENT_TRAP=y + +# +# Boot options +# +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="noinitrd console=ttymxc0,115200 root=/dev/mtdblock2 rw rootfstype=jffs2 ip=off" +# CONFIG_XIP_KERNEL is not set +# CONFIG_KEXEC is not set + +# +# CPU Power Management +# +# CONFIG_CPU_FREQ is not set +# CONFIG_CPU_IDLE is not set + +# +# Floating point emulation +# + +# +# At least one emulation must be selected +# +CONFIG_VFP=y + +# +# Userspace binary formats +# +CONFIG_BINFMT_ELF=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_HAVE_AOUT=y +# CONFIG_BINFMT_AOUT is not set +# CONFIG_BINFMT_MISC is not set + +# +# Power management options +# +CONFIG_PM=y +# CONFIG_PM_DEBUG is not set +CONFIG_PM_SLEEP=y +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +# CONFIG_APM_EMULATION is not set +CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +CONFIG_PACKET_MMAP=y +CONFIG_UNIX=y +CONFIG_XFRM=y +# CONFIG_XFRM_USER is not set +# CONFIG_XFRM_SUB_POLICY is not set +# CONFIG_XFRM_MIGRATE is not set +# CONFIG_XFRM_STATISTICS is not set +# CONFIG_NET_KEY is not set +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_FIB_HASH=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +# CONFIG_IP_PNP_RARP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_IP_MROUTE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +# CONFIG_INET_TUNNEL is not set +CONFIG_INET_XFRM_MODE_TRANSPORT=y +CONFIG_INET_XFRM_MODE_TUNNEL=y +CONFIG_INET_XFRM_MODE_BEET=y +# CONFIG_INET_LRO is not set +CONFIG_INET_DIAG=y +CONFIG_INET_TCP_DIAG=y +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +# CONFIG_IPV6 is not set +# CONFIG_NETWORK_SECMARK is not set +# CONFIG_NETFILTER is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_NET_DSA is not set +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_NET_SCHED is not set + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_HAMRADIO is not set +# CONFIG_CAN is not set +# CONFIG_IRDA is not set +# CONFIG_BT is not set +# CONFIG_AF_RXRPC is not set +# CONFIG_PHONET is not set +CONFIG_WIRELESS=y +# CONFIG_CFG80211 is not set +CONFIG_WIRELESS_OLD_REGULATORY=y +CONFIG_WIRELESS_EXT=y +CONFIG_WIRELESS_EXT_SYSFS=y +# CONFIG_MAC80211 is not set +CONFIG_IEEE80211=y +# CONFIG_IEEE80211_DEBUG is not set +# CONFIG_IEEE80211_CRYPT_WEP is not set +# CONFIG_IEEE80211_CRYPT_CCMP is not set +# CONFIG_IEEE80211_CRYPT_TKIP is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +CONFIG_FIRMWARE_IN_KERNEL=y +CONFIG_EXTRA_FIRMWARE="" +# CONFIG_SYS_HYPERVISOR is not set +CONFIG_CONNECTOR=y +CONFIG_PROC_EVENTS=y +CONFIG_MTD=y +# CONFIG_MTD_DEBUG is not set +# CONFIG_MTD_CONCAT is not set +CONFIG_MTD_PARTITIONS=y +# CONFIG_MTD_REDBOOT_PARTS is not set +CONFIG_MTD_CMDLINE_PARTS=y +# CONFIG_MTD_AFS_PARTS is not set +# CONFIG_MTD_AR7_PARTS is not set + +# +# User Modules And Translation Layers +# +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLKDEVS=y +CONFIG_MTD_BLOCK=y +# CONFIG_FTL is not set +# CONFIG_NFTL is not set +# CONFIG_INFTL is not set +# CONFIG_RFD_FTL is not set +# CONFIG_SSFDC is not set +# CONFIG_MTD_OOPS is not set + +# +# RAM/ROM/Flash chip drivers +# +# CONFIG_MTD_CFI is not set +# CONFIG_MTD_JEDECPROBE is not set +CONFIG_MTD_MAP_BANK_WIDTH_1=y +CONFIG_MTD_MAP_BANK_WIDTH_2=y +CONFIG_MTD_MAP_BANK_WIDTH_4=y +# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set +CONFIG_MTD_CFI_I1=y +CONFIG_MTD_CFI_I2=y +# CONFIG_MTD_CFI_I4 is not set +# CONFIG_MTD_CFI_I8 is not set +# CONFIG_MTD_RAM is not set +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_ABSENT is not set + +# +# Mapping drivers for chip access +# +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_PLATRAM is not set +# CONFIG_MTD_MXC is not set + +# +# Self-contained MTD device drivers +# +# CONFIG_MTD_DATAFLASH is not set +# CONFIG_MTD_M25P80 is not set +# CONFIG_MTD_SLRAM is not set +# CONFIG_MTD_PHRAM is not set +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_BLOCK2MTD is not set + +# +# Disk-On-Chip Device Drivers +# +# CONFIG_MTD_DOC2000 is not set +# CONFIG_MTD_DOC2001 is not set +# CONFIG_MTD_DOC2001PLUS is not set +CONFIG_MTD_NAND=y +# CONFIG_MTD_NAND_VERIFY_WRITE is not set +# CONFIG_MTD_NAND_ECC_SMC is not set +# CONFIG_MTD_NAND_MUSEUM_IDS is not set +CONFIG_MTD_NAND_IDS=y +# CONFIG_MTD_NAND_DISKONCHIP is not set +# CONFIG_MTD_NAND_NANDSIM is not set +CONFIG_MTD_NAND_MXC=y +CONFIG_MTD_NAND_MXC_V2=y +# CONFIG_MTD_NAND_MXC_SWECC is not set +# CONFIG_MTD_NAND_MXC_FORCE_CE is not set +# CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 is not set +# CONFIG_MXC_NAND_LOW_LEVEL_ERASE is not set +# CONFIG_MTD_NAND_PLATFORM is not set +# CONFIG_MTD_ALAUDA is not set +# CONFIG_MTD_ONENAND is not set + +# +# UBI - Unsorted block images +# +# CONFIG_MTD_UBI is not set +# CONFIG_PARPORT is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=y +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +# CONFIG_BLK_DEV_NBD is not set +# CONFIG_BLK_DEV_UB is not set +# CONFIG_BLK_DEV_RAM is not set +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +CONFIG_MISC_DEVICES=y +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_ICS932S401 is not set +# CONFIG_ENCLOSURE_SERVICES is not set +# CONFIG_C2PORT is not set +CONFIG_HAVE_IDE=y +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_TGT is not set +# CONFIG_SCSI_NETLINK is not set +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +CONFIG_BLK_DEV_SR=y +# CONFIG_BLK_DEV_SR_VENDOR is not set +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=y + +# +# Some SCSI devices (e.g. CD jukebox) support multiple LUNs +# +CONFIG_SCSI_MULTI_LUN=y +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +CONFIG_SCSI_WAIT_SCAN=m + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +CONFIG_SCSI_LOWLEVEL=y +# CONFIG_ISCSI_TCP is not set +# CONFIG_SCSI_DEBUG is not set +# CONFIG_SCSI_DH is not set +CONFIG_ATA=m +# CONFIG_ATA_NONSTANDARD is not set +# CONFIG_SATA_PMP is not set +CONFIG_ATA_SFF=y +# CONFIG_SATA_MV is not set +# CONFIG_PATA_PLATFORM is not set +CONFIG_PATA_FSL=m +# CONFIG_MD is not set +CONFIG_NETDEVICES=y +# CONFIG_DUMMY is not set +# CONFIG_BONDING is not set +# CONFIG_MACVLAN is not set +# CONFIG_EQUALIZER is not set +# CONFIG_TUN is not set +# CONFIG_VETH is not set +# CONFIG_PHYLIB is not set +CONFIG_NET_ETHERNET=y +CONFIG_MII=y +# CONFIG_AX88796 is not set +# CONFIG_SMC91X is not set +# CONFIG_DM9000 is not set +# CONFIG_ENC28J60 is not set +# CONFIG_SMC911X is not set +CONFIG_SMSC911X=y +# CONFIG_IBM_NEW_EMAC_ZMII is not set +# CONFIG_IBM_NEW_EMAC_RGMII is not set +# CONFIG_IBM_NEW_EMAC_TAH is not set +# CONFIG_IBM_NEW_EMAC_EMAC4 is not set +# CONFIG_IBM_NEW_EMAC_NO_FLOW_CTRL is not set +# CONFIG_IBM_NEW_EMAC_MAL_CLR_ICINTSTAT is not set +# CONFIG_IBM_NEW_EMAC_MAL_COMMON_ERR is not set +# CONFIG_B44 is not set +# CONFIG_CS89x0 is not set +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set + +# +# Wireless LAN +# +# CONFIG_WLAN_PRE80211 is not set +# CONFIG_WLAN_80211 is not set +# CONFIG_IWLWIFI_LEDS is not set + +# +# USB Network Adapters +# +# CONFIG_USB_CATC is not set +# CONFIG_USB_KAWETH is not set +# CONFIG_USB_PEGASUS is not set +# CONFIG_USB_RTL8150 is not set +CONFIG_USB_USBNET=m +# CONFIG_USB_NET_AX8817X is not set +CONFIG_USB_NET_CDCETHER=m +# CONFIG_USB_NET_DM9601 is not set +# CONFIG_USB_NET_SMSC95XX is not set +# CONFIG_USB_NET_GL620A is not set +# CONFIG_USB_NET_NET1080 is not set +# CONFIG_USB_NET_PLUSB is not set +# CONFIG_USB_NET_MCS7830 is not set +# CONFIG_USB_NET_RNDIS_HOST is not set +# CONFIG_USB_NET_CDC_SUBSET is not set +# CONFIG_USB_NET_ZAURUS is not set +# CONFIG_WAN is not set +# CONFIG_PPP is not set +# CONFIG_SLIP is not set +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_ISDN is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +CONFIG_INPUT_POLLDEV=y + +# +# Userland interfaces +# +# CONFIG_INPUT_MOUSEDEV is not set +# CONFIG_INPUT_JOYDEV is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +CONFIG_KEYBOARD_MXC=y +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TABLET is not set +CONFIG_INPUT_TOUCHSCREEN=y +# CONFIG_TOUCHSCREEN_ADS7846 is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_INEXIO is not set +# CONFIG_TOUCHSCREEN_MK712 is not set +CONFIG_TOUCHSCREEN_MXC=y +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +# CONFIG_TOUCHSCREEN_TOUCHIT213 is not set +# CONFIG_INPUT_MISC is not set + +# +# Hardware I/O ports +# +# CONFIG_SERIO is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +# CONFIG_VT_HW_CONSOLE_BINDING is not set +CONFIG_DEVKMEM=y +# CONFIG_SERIAL_NONSTANDARD is not set +CONFIG_FM_SI4702=m +CONFIG_MXC_IIM=y + +# +# Serial drivers +# +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +CONFIG_SERIAL_MXC=y +CONFIG_SERIAL_MXC_CONSOLE=y +# CONFIG_SERIAL_IMX is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_UNIX98_PTYS=y +CONFIG_LEGACY_PTYS=y +CONFIG_LEGACY_PTY_COUNT=256 +# CONFIG_IPMI_HANDLER is not set +CONFIG_HW_RANDOM=y +# CONFIG_NVRAM is not set +# CONFIG_R3964 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_HELPER_AUTO=y + +# +# I2C Hardware Bus support +# + +# +# I2C system bus drivers (mostly embedded / system-on-chip) +# +CONFIG_I2C_MXC=y +# CONFIG_I2C_MXC_HS is not set +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_SIMTEC is not set + +# +# External I2C/SMBus adapter drivers +# +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_TINY_USB is not set + +# +# Other I2C/SMBus bus drivers +# +# CONFIG_I2C_PCA_PLATFORM is not set +# CONFIG_I2C_STUB is not set + +# +# Miscellaneous I2C Chip support +# +# CONFIG_DS1682 is not set +# CONFIG_AT24 is not set +# CONFIG_SENSORS_EEPROM is not set +# CONFIG_SENSORS_PCF8574 is not set +# CONFIG_PCF8575 is not set +# CONFIG_SENSORS_PCA9539 is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_SENSORS_MAX6875 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CHIP is not set +# CONFIG_I2C_SLAVE is not set +CONFIG_SPI=y +CONFIG_SPI_MASTER=y + +# +# SPI Master Controller Drivers +# +CONFIG_SPI_BITBANG=y +CONFIG_SPI_MXC=y +# CONFIG_SPI_MXC_TEST_LOOPBACK is not set +CONFIG_SPI_MXC_SELECT1=y +CONFIG_SPI_MXC_SELECT2=y +# CONFIG_SPI_MXC_SELECT3 is not set + +# +# SPI Protocol Masters +# +# CONFIG_SPI_AT25 is not set +# CONFIG_SPI_SPIDEV is not set +# CONFIG_SPI_TLE62X0 is not set +# CONFIG_W1 is not set +# CONFIG_POWER_SUPPLY is not set +CONFIG_HWMON=y +# CONFIG_HWMON_VID is not set +# CONFIG_SENSORS_AD7414 is not set +# CONFIG_SENSORS_AD7418 is not set +# CONFIG_SENSORS_ADCXX is not set +# CONFIG_SENSORS_ADM1021 is not set +# CONFIG_SENSORS_ADM1025 is not set +# CONFIG_SENSORS_ADM1026 is not set +# CONFIG_SENSORS_ADM1029 is not set +# CONFIG_SENSORS_ADM1031 is not set +# CONFIG_SENSORS_ADM9240 is not set +# CONFIG_SENSORS_ADT7462 is not set +# CONFIG_SENSORS_ADT7470 is not set +# CONFIG_SENSORS_ADT7473 is not set +# CONFIG_SENSORS_ATXP1 is not set +# CONFIG_SENSORS_DS1621 is not set +# CONFIG_SENSORS_F71805F is not set +# CONFIG_SENSORS_F71882FG is not set +# CONFIG_SENSORS_F75375S is not set +# CONFIG_SENSORS_GL518SM is not set +# CONFIG_SENSORS_GL520SM is not set +# CONFIG_SENSORS_IT87 is not set +# CONFIG_SENSORS_LM63 is not set +# CONFIG_SENSORS_LM70 is not set +# CONFIG_SENSORS_LM75 is not set +# CONFIG_SENSORS_LM77 is not set +# CONFIG_SENSORS_LM78 is not set +# CONFIG_SENSORS_LM80 is not set +# CONFIG_SENSORS_LM83 is not set +# CONFIG_SENSORS_LM85 is not set +# CONFIG_SENSORS_LM87 is not set +# CONFIG_SENSORS_LM90 is not set +# CONFIG_SENSORS_LM92 is not set +# CONFIG_SENSORS_LM93 is not set +# CONFIG_SENSORS_MAX1111 is not set +# CONFIG_SENSORS_MAX1619 is not set +# CONFIG_SENSORS_MAX6650 is not set +# CONFIG_SENSORS_PC87360 is not set +# CONFIG_SENSORS_PC87427 is not set +# CONFIG_SENSORS_DME1737 is not set +# CONFIG_SENSORS_SMSC47M1 is not set +# CONFIG_SENSORS_SMSC47M192 is not set +# CONFIG_SENSORS_SMSC47B397 is not set +# CONFIG_SENSORS_ADS7828 is not set +# CONFIG_SENSORS_THMC50 is not set +# CONFIG_SENSORS_VT1211 is not set +# CONFIG_SENSORS_W83781D is not set +# CONFIG_SENSORS_W83791D is not set +# CONFIG_SENSORS_W83792D is not set +# CONFIG_SENSORS_W83793 is not set +# CONFIG_SENSORS_W83L785TS is not set +# CONFIG_SENSORS_W83L786NG is not set +# CONFIG_SENSORS_W83627HF is not set +# CONFIG_SENSORS_W83627EHF is not set +# CONFIG_HWMON_DEBUG_CHIP is not set +CONFIG_MXC_MMA7450=m +# CONFIG_THERMAL is not set +# CONFIG_THERMAL_HWMON is not set +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_NOWAYOUT=y + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +CONFIG_MXC_WATCHDOG=y + +# +# USB-based Watchdog Cards +# +# CONFIG_USBPCWATCHDOG is not set +CONFIG_SSB_POSSIBLE=y + +# +# Sonics Silicon Backplane +# +# CONFIG_SSB is not set + +# +# Multifunction device drivers +# +# CONFIG_MFD_CORE is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_HTC_PASIC3 is not set +# CONFIG_MFD_TMIO is not set +# CONFIG_PMIC_DA903X is not set +# CONFIG_MFD_WM8400 is not set +# CONFIG_MFD_WM8350_I2C is not set + +# +# Multimedia devices +# + +# +# Multimedia core support +# +CONFIG_VIDEO_DEV=y +CONFIG_VIDEO_V4L2_COMMON=y +CONFIG_VIDEO_ALLOW_V4L1=y +CONFIG_VIDEO_V4L1_COMPAT=y +# CONFIG_DVB_CORE is not set +CONFIG_VIDEO_MEDIA=y + +# +# Multimedia drivers +# +# CONFIG_MEDIA_ATTACH is not set +CONFIG_MEDIA_TUNER=y +# CONFIG_MEDIA_TUNER_CUSTOMIZE is not set +CONFIG_MEDIA_TUNER_SIMPLE=y +CONFIG_MEDIA_TUNER_TDA8290=y +CONFIG_MEDIA_TUNER_TDA9887=y +CONFIG_MEDIA_TUNER_TEA5761=y +CONFIG_MEDIA_TUNER_TEA5767=y +CONFIG_MEDIA_TUNER_MT20XX=y +CONFIG_MEDIA_TUNER_XC2028=y +CONFIG_MEDIA_TUNER_XC5000=y +CONFIG_VIDEO_V4L2=y +CONFIG_VIDEO_V4L1=y +CONFIG_VIDEO_CAPTURE_DRIVERS=y +# CONFIG_VIDEO_ADV_DEBUG is not set +# CONFIG_VIDEO_FIXED_MINOR_RANGES is not set +# CONFIG_VIDEO_HELPER_CHIPS_AUTO is not set + +# +# Encoders/decoders and other helper chips +# + +# +# Audio decoders +# +# CONFIG_VIDEO_TVAUDIO is not set +# CONFIG_VIDEO_TDA7432 is not set +# CONFIG_VIDEO_TDA9840 is not set +# CONFIG_VIDEO_TDA9875 is not set +# CONFIG_VIDEO_TEA6415C is not set +# CONFIG_VIDEO_TEA6420 is not set +# CONFIG_VIDEO_MSP3400 is not set +# CONFIG_VIDEO_CS5345 is not set +# CONFIG_VIDEO_CS53L32A is not set +# CONFIG_VIDEO_M52790 is not set +# CONFIG_VIDEO_TLV320AIC23B is not set +# CONFIG_VIDEO_WM8775 is not set +# CONFIG_VIDEO_WM8739 is not set +# CONFIG_VIDEO_VP27SMPX is not set + +# +# Video decoders +# +# CONFIG_VIDEO_BT819 is not set +# CONFIG_VIDEO_BT856 is not set +# CONFIG_VIDEO_BT866 is not set +# CONFIG_VIDEO_KS0127 is not set +# CONFIG_VIDEO_OV7670 is not set +# CONFIG_VIDEO_TCM825X is not set +# CONFIG_VIDEO_SAA7110 is not set +# CONFIG_VIDEO_SAA7111 is not set +# CONFIG_VIDEO_SAA7114 is not set +# CONFIG_VIDEO_SAA711X is not set +# CONFIG_VIDEO_SAA717X is not set +# CONFIG_VIDEO_SAA7191 is not set +# CONFIG_VIDEO_TVP5150 is not set +# CONFIG_VIDEO_VPX3220 is not set + +# +# Video and audio decoders +# +# CONFIG_VIDEO_CX25840 is not set + +# +# MPEG video encoders +# +# CONFIG_VIDEO_CX2341X is not set + +# +# Video encoders +# +# CONFIG_VIDEO_SAA7127 is not set +# CONFIG_VIDEO_SAA7185 is not set +# CONFIG_VIDEO_ADV7170 is not set +# CONFIG_VIDEO_ADV7175 is not set + +# +# Video improvement chips +# +# CONFIG_VIDEO_UPD64031A is not set +# CONFIG_VIDEO_UPD64083 is not set +# CONFIG_VIDEO_VIVI is not set +CONFIG_VIDEO_MXC_CAMERA=m + +# +# MXC Camera/V4L2 PRP Features support +# +CONFIG_VIDEO_MXC_IPU_CAMERA=y +# CONFIG_MXC_CAMERA_MC521DA is not set +# CONFIG_MXC_EMMA_CAMERA_MICRON111 is not set +# CONFIG_MXC_CAMERA_OV2640_EMMA is not set +# CONFIG_MXC_CAMERA_MICRON111 is not set +CONFIG_MXC_CAMERA_OV2640=m +# CONFIG_MXC_CAMERA_OV3640 is not set +# CONFIG_MXC_TVIN_ADV7180 is not set +CONFIG_MXC_IPU_PRP_VF_SDC=m +CONFIG_MXC_IPU_PRP_ENC=m +CONFIG_VIDEO_MXC_OUTPUT=y +CONFIG_VIDEO_MXC_IPU_OUTPUT=y +# CONFIG_VIDEO_MXC_IPUV1_WVGA_OUTPUT is not set +# CONFIG_VIDEO_MXC_OPL is not set +# CONFIG_VIDEO_CPIA is not set +# CONFIG_VIDEO_CPIA2 is not set +# CONFIG_VIDEO_SAA5246A is not set +# CONFIG_VIDEO_SAA5249 is not set +# CONFIG_SOC_CAMERA is not set +CONFIG_V4L_USB_DRIVERS=y +# CONFIG_USB_VIDEO_CLASS is not set +CONFIG_USB_GSPCA=m +# CONFIG_USB_M5602 is not set +# CONFIG_USB_GSPCA_CONEX is not set +# CONFIG_USB_GSPCA_ETOMS is not set +# CONFIG_USB_GSPCA_FINEPIX is not set +# CONFIG_USB_GSPCA_MARS is not set +# CONFIG_USB_GSPCA_OV519 is not set +# CONFIG_USB_GSPCA_PAC207 is not set +# CONFIG_USB_GSPCA_PAC7311 is not set +# CONFIG_USB_GSPCA_SONIXB is not set +# CONFIG_USB_GSPCA_SONIXJ is not set +# CONFIG_USB_GSPCA_SPCA500 is not set +# CONFIG_USB_GSPCA_SPCA501 is not set +# CONFIG_USB_GSPCA_SPCA505 is not set +# CONFIG_USB_GSPCA_SPCA506 is not set +# CONFIG_USB_GSPCA_SPCA508 is not set +# CONFIG_USB_GSPCA_SPCA561 is not set +# CONFIG_USB_GSPCA_STK014 is not set +# CONFIG_USB_GSPCA_SUNPLUS is not set +# CONFIG_USB_GSPCA_T613 is not set +# CONFIG_USB_GSPCA_TV8532 is not set +# CONFIG_USB_GSPCA_VC032X is not set +# CONFIG_USB_GSPCA_ZC3XX is not set +# CONFIG_VIDEO_PVRUSB2 is not set +# CONFIG_VIDEO_EM28XX is not set +# CONFIG_VIDEO_USBVISION is not set +# CONFIG_USB_VICAM is not set +# CONFIG_USB_IBMCAM is not set +# CONFIG_USB_KONICAWC is not set +# CONFIG_USB_QUICKCAM_MESSENGER is not set +# CONFIG_USB_ET61X251 is not set +# CONFIG_VIDEO_OVCAMCHIP is not set +# CONFIG_USB_OV511 is not set +# CONFIG_USB_SE401 is not set +# CONFIG_USB_SN9C102 is not set +# CONFIG_USB_STV680 is not set +# CONFIG_USB_ZC0301 is not set +# CONFIG_USB_PWC is not set +# CONFIG_USB_ZR364XX is not set +# CONFIG_USB_STKWEBCAM is not set +# CONFIG_USB_S2255 is not set +# CONFIG_RADIO_ADAPTERS is not set +# CONFIG_DAB is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +# CONFIG_VIDEO_OUTPUT_CONTROL is not set +CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +# CONFIG_FB_DDC is not set +# CONFIG_FB_BOOT_VESA_SUPPORT is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_FOREIGN_ENDIAN is not set +# CONFIG_FB_SYS_FOPS is not set +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_MODE_HELPERS is not set +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# +CONFIG_FB_MXC=y +CONFIG_FB_MXC_SYNC_PANEL=y +CONFIG_FB_MXC_EPSON_VGA_SYNC_PANEL=y +# CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL is not set +# CONFIG_FB_MXC_CH7026 is not set +# CONFIG_FB_MXC_TVOUT is not set +CONFIG_FB_MXC_TVOUT_CH7024=y +# CONFIG_FB_MXC_ASYNC_PANEL is not set +# CONFIG_FB_UVESA is not set +# CONFIG_FB_S1D13XXX is not set +# CONFIG_FB_VIRTUAL is not set +# CONFIG_FB_METRONOME is not set +# CONFIG_FB_MB862XX is not set +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_LCD_CLASS_DEVICE is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_CORGI is not set +CONFIG_BACKLIGHT_MXC=y +CONFIG_BACKLIGHT_MXC_IPU=y +CONFIG_BACKLIGHT_MXC_PMIC=y + +# +# Display device support +# +# CONFIG_DISPLAY_SUPPORT is not set + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE=y +# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +CONFIG_FONTS=y +# CONFIG_FONT_8x8 is not set +CONFIG_FONT_8x16=y +# CONFIG_FONT_6x11 is not set +# CONFIG_FONT_7x14 is not set +# CONFIG_FONT_PEARL_8x8 is not set +# CONFIG_FONT_ACORN_8x8 is not set +# CONFIG_FONT_MINI_4x6 is not set +# CONFIG_FONT_SUN8x16 is not set +# CONFIG_FONT_SUN12x22 is not set +# CONFIG_FONT_10x18 is not set +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_LOGO_LINUX_CLUT224=y +CONFIG_SOUND=y +CONFIG_SOUND_OSS_CORE=y +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +# CONFIG_SND_SEQUENCER is not set +CONFIG_SND_OSSEMUL=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +CONFIG_SND_PCM_OSS_PLUGINS=y +# CONFIG_SND_DYNAMIC_MINORS is not set +CONFIG_SND_SUPPORT_OLD_API=y +CONFIG_SND_VERBOSE_PROCFS=y +# CONFIG_SND_VERBOSE_PRINTK is not set +# CONFIG_SND_DEBUG is not set +CONFIG_SND_DRIVERS=y +# CONFIG_SND_DUMMY is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_MPU401 is not set +CONFIG_SND_ARM=y +# CONFIG_SND_MXC_SPDIF is not set +CONFIG_SND_MXC_PMIC=y +# CONFIG_SND_MXC_PLAYBACK_MIXING is not set +# CONFIG_HEADSET_DETECT_ENABLE is not set +CONFIG_SND_SPI=y +CONFIG_SND_USB=y +# CONFIG_SND_USB_AUDIO is not set +# CONFIG_SND_USB_CAIAQ is not set +# CONFIG_SND_SOC is not set +# CONFIG_SOUND_PRIME is not set +CONFIG_HID_SUPPORT=y +CONFIG_HID=y +# CONFIG_HID_DEBUG is not set +# CONFIG_HIDRAW is not set + +# +# USB Input Devices +# +CONFIG_USB_HID=m +# CONFIG_HID_PID is not set +# CONFIG_USB_HIDDEV is not set + +# +# USB HID Boot Protocol drivers +# +# CONFIG_USB_KBD is not set +# CONFIG_USB_MOUSE is not set + +# +# Special HID drivers +# +CONFIG_HID_COMPAT=y +CONFIG_HID_A4TECH=m +CONFIG_HID_APPLE=m +CONFIG_HID_BELKIN=m +CONFIG_HID_BRIGHT=m +CONFIG_HID_CHERRY=m +CONFIG_HID_CHICONY=m +CONFIG_HID_CYPRESS=m +CONFIG_HID_DELL=m +CONFIG_HID_EZKEY=m +CONFIG_HID_GYRATION=m +CONFIG_HID_LOGITECH=m +# CONFIG_LOGITECH_FF is not set +# CONFIG_LOGIRUMBLEPAD2_FF is not set +CONFIG_HID_MICROSOFT=m +CONFIG_HID_MONTEREY=m +CONFIG_HID_PANTHERLORD=m +# CONFIG_PANTHERLORD_FF is not set +CONFIG_HID_PETALYNX=m +CONFIG_HID_SAMSUNG=m +CONFIG_HID_SONY=m +CONFIG_HID_SUNPLUS=m +# CONFIG_THRUSTMASTER_FF is not set +# CONFIG_ZEROPLUS_FF is not set +CONFIG_USB_SUPPORT=y +CONFIG_USB_ARCH_HAS_HCD=y +# CONFIG_USB_ARCH_HAS_OHCI is not set +CONFIG_USB_ARCH_HAS_EHCI=y +CONFIG_USB=y +# CONFIG_USB_DEBUG is not set +# CONFIG_USB_ANNOUNCE_NEW_DEVICES is not set + +# +# Miscellaneous USB options +# +CONFIG_USB_DEVICEFS=y +CONFIG_USB_DEVICE_CLASS=y +# CONFIG_USB_DYNAMIC_MINORS is not set +CONFIG_USB_SUSPEND=y +# CONFIG_USB_OTG is not set +# CONFIG_USB_OTG_WHITELIST is not set +# CONFIG_USB_OTG_BLACKLIST_HUB is not set +CONFIG_USB_MON=y +# CONFIG_USB_WUSB is not set +# CONFIG_USB_WUSB_CBAF is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_C67X00_HCD is not set +CONFIG_USB_EHCI_HCD=m +CONFIG_USB_EHCI_ARC=y +# CONFIG_USB_EHCI_ARC_H1 is not set +CONFIG_USB_EHCI_ARC_H2=y +# CONFIG_USB_EHCI_ARC_H2_WAKE_UP is not set +CONFIG_USB_EHCI_ARC_OTG=y +# CONFIG_USB_EHCI_ARC_OTG_WAKE_UP is not set +# CONFIG_USB_STATIC_IRAM is not set +# CONFIG_USB_EHCI_FSL_MC13783 is not set +# CONFIG_USB_EHCI_FSL_1301 is not set +CONFIG_USB_EHCI_FSL_1504=y +# CONFIG_USB_EHCI_FSL_UTMI is not set +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +# CONFIG_USB_ISP116X_HCD is not set +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +# CONFIG_USB_HWA_HCD is not set +# CONFIG_USB_GADGET_MUSB_HDRC is not set + +# +# USB Device Class drivers +# +# CONFIG_USB_ACM is not set +# CONFIG_USB_PRINTER is not set +# CONFIG_USB_WDM is not set +# CONFIG_USB_TMC is not set + +# +# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; +# + +# +# see USB_STORAGE Help for more information +# +CONFIG_USB_STORAGE=y +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +# CONFIG_USB_STORAGE_FREECOM is not set +# CONFIG_USB_STORAGE_ISD200 is not set +# CONFIG_USB_STORAGE_DPCM is not set +# CONFIG_USB_STORAGE_USBAT is not set +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_ONETOUCH is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set +# CONFIG_USB_LIBUSUAL is not set + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set + +# +# USB port drivers +# +# CONFIG_USB_SERIAL is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_SEVSEG is not set +# CONFIG_USB_RIO500 is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_BERRY_CHARGE is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_PHIDGET is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_USB_SISUSBVGA is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_TEST is not set +# CONFIG_USB_ISIGHTFW is not set +# CONFIG_USB_VST is not set +CONFIG_USB_GADGET=m +# CONFIG_USB_GADGET_DEBUG_FILES is not set +CONFIG_USB_GADGET_VBUS_DRAW=2 +CONFIG_USB_GADGET_SELECTED=y +# CONFIG_USB_GADGET_AT91 is not set +# CONFIG_USB_GADGET_ATMEL_USBA is not set +# CONFIG_USB_GADGET_FSL_USB2 is not set +# CONFIG_USB_GADGET_LH7A40X is not set +# CONFIG_USB_GADGET_OMAP is not set +# CONFIG_USB_GADGET_PXA25X is not set +# CONFIG_USB_GADGET_PXA27X is not set +# CONFIG_USB_GADGET_S3C2410 is not set +# CONFIG_USB_GADGET_M66592 is not set +# CONFIG_USB_GADGET_AMD5536UDC is not set +# CONFIG_USB_GADGET_FSL_QE is not set +# CONFIG_USB_GADGET_NET2280 is not set +# CONFIG_USB_GADGET_GOKU is not set +CONFIG_USB_GADGET_ARC=y +# CONFIG_USB_STATIC_IRAM_PPH is not set +CONFIG_USB_ARC=m +# CONFIG_USB_GADGET_DUMMY_HCD is not set +CONFIG_USB_GADGET_DUALSPEED=y +CONFIG_USB_GADGET_ARC_OTG=y +# CONFIG_USB_GADGET_WAKE_UP is not set +# CONFIG_USB_GADGET_FSL_MC13783 is not set +# CONFIG_USB_GADGET_FSL_1301 is not set +CONFIG_USB_GADGET_FSL_1504=y +# CONFIG_USB_GADGET_FSL_UTMI is not set +# CONFIG_USB_ZERO is not set +CONFIG_USB_ETH=m +CONFIG_USB_ETH_RNDIS=y +CONFIG_USB_GADGETFS=m +CONFIG_USB_FILE_STORAGE=m +# CONFIG_USB_FILE_STORAGE_TEST is not set +CONFIG_USB_G_SERIAL=m +# CONFIG_USB_MIDI_GADGET is not set +# CONFIG_USB_G_PRINTER is not set +# CONFIG_USB_CDC_COMPOSITE is not set +CONFIG_MMC=y +# CONFIG_MMC_DEBUG is not set +CONFIG_MMC_UNSAFE_RESUME=y + +# +# MMC/SD/SDIO Card Drivers +# +CONFIG_MMC_BLOCK=y +CONFIG_MMC_BLOCK_BOUNCE=y +# CONFIG_SDIO_UART is not set +# CONFIG_MMC_TEST is not set +CONFIG_SDIO_UNIFI_FS=m + +# +# MMC/SD/SDIO Host Controller Drivers +# +# CONFIG_MMC_SDHCI is not set +# CONFIG_MMC_SPI is not set +CONFIG_MMC_MXC=m +# CONFIG_MMC_IMX_ESDHCI is not set +# CONFIG_MMC_IMX_ESDHCI_PIO_MODE is not set +# CONFIG_MEMSTICK is not set +# CONFIG_ACCESSIBILITY is not set +# CONFIG_NEW_LEDS is not set +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set +# CONFIG_RTC_DRV_S35390A is not set +# CONFIG_RTC_DRV_FM3130 is not set +# CONFIG_RTC_DRV_RX8581 is not set + +# +# SPI RTC drivers +# +# CONFIG_RTC_DRV_M41T94 is not set +# CONFIG_RTC_DRV_DS1305 is not set +# CONFIG_RTC_DRV_DS1390 is not set +# CONFIG_RTC_DRV_MAX6902 is not set +# CONFIG_RTC_DRV_R9701 is not set +# CONFIG_RTC_DRV_RS5C348 is not set +# CONFIG_RTC_DRV_DS3234 is not set + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_BQ4802 is not set +# CONFIG_RTC_DRV_V3020 is not set + +# +# on-CPU RTC drivers +# +CONFIG_RTC_MXC=y +# CONFIG_RTC_DRV_MXC_V2 is not set +# CONFIG_RTC_DRV_IMXDI is not set +# CONFIG_DMADEVICES is not set +CONFIG_REGULATOR=y +# CONFIG_REGULATOR_DEBUG is not set +# CONFIG_REGULATOR_FIXED_VOLTAGE is not set +# CONFIG_REGULATOR_VIRTUAL_CONSUMER is not set +# CONFIG_REGULATOR_BQ24022 is not set +CONFIG_REGULATOR_MC13783=y +# CONFIG_UIO is not set + +# +# MXC support drivers +# +CONFIG_MXC_IPU=y +CONFIG_MXC_IPU_V1=y +CONFIG_MXC_IPU_PF=y + +# +# MXC SSI support +# +CONFIG_MXC_SSI=y + +# +# MXC Digital Audio Multiplexer support +# +CONFIG_MXC_DAM=y + +# +# MXC PMIC support +# +CONFIG_MXC_PMIC=y +CONFIG_MXC_PMIC_MC13783=y +# CONFIG_MXC_PMIC_MC13892 is not set +# CONFIG_MXC_PMIC_SPI is not set +# CONFIG_MXC_PMIC_MC34704 is not set +# CONFIG_MXC_PMIC_MC9SDZ60 is not set +CONFIG_MXC_PMIC_CHARDEV=y + +# +# MXC PMIC Client Drivers +# +CONFIG_MXC_MC13783_ADC=y +CONFIG_MXC_MC13783_AUDIO=y +CONFIG_MXC_MC13783_RTC=y +CONFIG_MXC_MC13783_LIGHT=y +CONFIG_MXC_MC13783_BATTERY=y +CONFIG_MXC_MC13783_CONNECTIVITY=y +CONFIG_MXC_MC13783_POWER=y +# CONFIG_MXC_PMIC_MC9S08DZ60 is not set + +# +# MXC Security Drivers +# +CONFIG_MXC_SECURITY_SCC=y +# CONFIG_SCC_DEBUG is not set +CONFIG_MXC_SECURITY_RNG=y +# CONFIG_MXC_RNG_TEST_DRIVER is not set +# CONFIG_MXC_RNG_DEBUG is not set + +# +# MXC MPEG4 Encoder Kernel module support +# +CONFIG_MXC_HMP4E=y +# CONFIG_MXC_HMP4E_DEBUG is not set + +# +# MXC HARDWARE EVENT +# +CONFIG_MXC_HWEVENT=y + +# +# MXC VPU(Video Processing Unit) support +# +# CONFIG_MXC_VPU is not set + +# +# MXC Asynchronous Sample Rate Converter support +# + +# +# MXC Bluetooth support +# +CONFIG_MXC_BLUETOOTH=m + +# +# Broadcom GPS ioctrl support +# +CONFIG_GPS_IOCTRL=m + +# +# MXC Media Local Bus Driver +# + +# +# i.MX ADC support +# +# CONFIG_IMX_ADC is not set + +# +# File systems +# +CONFIG_EXT2_FS=y +# CONFIG_EXT2_FS_XATTR is not set +# CONFIG_EXT2_FS_XIP is not set +# CONFIG_EXT3_FS is not set +# CONFIG_EXT4_FS is not set +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_FS_POSIX_ACL is not set +CONFIG_FILE_LOCKING=y +# CONFIG_XFS_FS is not set +# CONFIG_OCFS2_FS is not set +CONFIG_DNOTIFY=y +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_QUOTA is not set +# CONFIG_AUTOFS_FS is not set +CONFIG_AUTOFS4_FS=m +# CONFIG_FUSE_FS is not set + +# +# CD-ROM/DVD Filesystems +# +# CONFIG_ISO9660_FS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +# CONFIG_TMPFS_POSIX_ACL is not set +# CONFIG_HUGETLB_PAGE is not set +# CONFIG_CONFIGFS_FS is not set + +# +# Miscellaneous filesystems +# +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_DEBUG=0 +CONFIG_JFFS2_FS_WRITEBUFFER=y +# CONFIG_JFFS2_FS_WBUF_VERIFY is not set +# CONFIG_JFFS2_SUMMARY is not set +# CONFIG_JFFS2_FS_XATTR is not set +# CONFIG_JFFS2_COMPRESSION_OPTIONS is not set +CONFIG_JFFS2_ZLIB=y +# CONFIG_JFFS2_LZO is not set +CONFIG_JFFS2_RTIME=y +# CONFIG_JFFS2_RUBIN is not set +CONFIG_CRAMFS=y +# CONFIG_VXFS_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +# CONFIG_NFS_V3_ACL is not set +# CONFIG_NFS_V4 is not set +CONFIG_ROOT_NFS=y +# CONFIG_NFSD is not set +CONFIG_LOCKD=y +CONFIG_LOCKD_V4=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=y +# CONFIG_SUNRPC_REGISTER_V4 is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +# CONFIG_RPCSEC_GSS_SPKM3 is not set +# CONFIG_SMB_FS is not set +# CONFIG_CIFS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +CONFIG_NLS_ASCII=m +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +CONFIG_NLS_UTF8=m +# CONFIG_DLM is not set + +# +# Kernel hacking +# +# CONFIG_PRINTK_TIME is not set +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +CONFIG_FRAME_WARN=1024 +# CONFIG_MAGIC_SYSRQ is not set +# CONFIG_UNUSED_SYMBOLS is not set +# CONFIG_DEBUG_FS is not set +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_KERNEL is not set +# CONFIG_SLUB_DEBUG_ON is not set +# CONFIG_SLUB_STATS is not set +# CONFIG_DEBUG_BUGVERBOSE is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +CONFIG_FRAME_POINTER=y +# CONFIG_RCU_CPU_STALL_DETECTOR is not set +# CONFIG_LATENCYTOP is not set +CONFIG_SYSCTL_SYSCALL_CHECK=y +CONFIG_HAVE_FUNCTION_TRACER=y + +# +# Tracers +# +# CONFIG_DYNAMIC_PRINTK_DEBUG is not set +# CONFIG_SAMPLES is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_DEBUG_USER is not set + +# +# Security options +# +# CONFIG_KEYS is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITYFS is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +# CONFIG_CRYPTO_FIPS is not set +# CONFIG_CRYPTO_MANAGER is not set +# CONFIG_CRYPTO_MANAGER2 is not set +# CONFIG_CRYPTO_GF128MUL is not set +# CONFIG_CRYPTO_NULL is not set +# CONFIG_CRYPTO_CRYPTD is not set +# CONFIG_CRYPTO_AUTHENC is not set +# CONFIG_CRYPTO_TEST is not set +# CONFIG_CRYPTO_CRYPTODEV is not set + +# +# Authenticated Encryption with Associated Data +# +# CONFIG_CRYPTO_CCM is not set +# CONFIG_CRYPTO_GCM is not set +# CONFIG_CRYPTO_SEQIV is not set + +# +# Block modes +# +# CONFIG_CRYPTO_CBC is not set +# CONFIG_CRYPTO_CTR is not set +# CONFIG_CRYPTO_CTS is not set +# CONFIG_CRYPTO_ECB is not set +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_XTS is not set + +# +# Hash modes +# +# CONFIG_CRYPTO_HMAC is not set +# CONFIG_CRYPTO_XCBC is not set + +# +# Digest +# +# CONFIG_CRYPTO_CRC32C is not set +# CONFIG_CRYPTO_MD4 is not set +# CONFIG_CRYPTO_MD5 is not set +# CONFIG_CRYPTO_MICHAEL_MIC is not set +# CONFIG_CRYPTO_RMD128 is not set +# CONFIG_CRYPTO_RMD160 is not set +# CONFIG_CRYPTO_RMD256 is not set +# CONFIG_CRYPTO_RMD320 is not set +# CONFIG_CRYPTO_SHA1 is not set +# CONFIG_CRYPTO_SHA256 is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_WP512 is not set + +# +# Ciphers +# +# CONFIG_CRYPTO_AES is not set +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_ARC4 is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +# CONFIG_CRYPTO_DES is not set +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_SALSA20 is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SERPENT is not set +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TWOFISH is not set + +# +# Compression +# +# CONFIG_CRYPTO_DEFLATE is not set +# CONFIG_CRYPTO_LZO is not set + +# +# Random Number Generation +# +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_HW=y + +# +# Library routines +# +CONFIG_BITREVERSE=y +CONFIG_CRC_CCITT=m +# CONFIG_CRC16 is not set +# CONFIG_CRC_T10DIF is not set +# CONFIG_CRC_ITU_T is not set +CONFIG_CRC32=y +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_PLIST=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_DMA=y diff --git a/arch/arm/configs/imx31ads_defconfig b/arch/arm/configs/imx31ads_defconfig new file mode 100644 index 000000000000..86ca17ea1648 --- /dev/null +++ b/arch/arm/configs/imx31ads_defconfig @@ -0,0 +1,1707 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.26 +# Tue Sep 16 10:19:21 2008 +# +CONFIG_ARM=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +# CONFIG_GENERIC_GPIO is not set +CONFIG_GENERIC_TIME=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_MMU=y +# CONFIG_NO_IOPORT is not set +CONFIG_GENERIC_HARDIRQS=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +# CONFIG_ARCH_HAS_ILOG2_U32 is not set +# CONFIG_ARCH_HAS_ILOG2_U64 is not set +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_ARCH_SUPPORTS_AOUT=y +CONFIG_ZONE_DMA=y +CONFIG_ARCH_MTD_XIP=y +CONFIG_OPROFILE_ARMV6=y +CONFIG_OPROFILE_ARM11_CORE=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_LOCK_KERNEL=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="" +CONFIG_LOCALVERSION_AUTO=y +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +# CONFIG_POSIX_MQUEUE is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set +# CONFIG_AUDIT is not set +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=14 +# CONFIG_CGROUPS is not set +# CONFIG_GROUP_SCHED is not set +CONFIG_SYSFS_DEPRECATED=y +CONFIG_SYSFS_DEPRECATED_V2=y +# CONFIG_RELAY is not set +# CONFIG_NAMESPACES is not set +# CONFIG_BLK_DEV_INITRD is not set +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_SYSCTL=y +CONFIG_EMBEDDED=y +CONFIG_UID16=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_SYSCTL_SYSCALL_CHECK=y +CONFIG_KALLSYMS=y +# CONFIG_KALLSYMS_EXTRA_PASS is not set +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +CONFIG_COMPAT_BRK=y +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_ANON_INODES=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_VM_EVENT_COUNTERS=y +CONFIG_SLUB_DEBUG=y +# CONFIG_SLAB is not set +CONFIG_SLUB=y +# CONFIG_SLOB is not set +CONFIG_PROFILING=y +# CONFIG_MARKERS is not set +CONFIG_OPROFILE=y +CONFIG_HAVE_OPROFILE=y +# CONFIG_KPROBES is not set +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +# CONFIG_HAVE_DMA_ATTRS is not set +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +# CONFIG_TINY_SHMEM is not set +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_KMOD=y +CONFIG_BLOCK=y +# CONFIG_LBD is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_LSF is not set +# CONFIG_BLK_DEV_BSG is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_AS=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_AS is not set +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" +CONFIG_CLASSIC_RCU=y + +# +# System Type +# +# CONFIG_ARCH_AAEC2000 is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_VERSATILE is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_CLPS7500 is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_CO285 is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_H720X is not set +# CONFIG_ARCH_IMX is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP23XX is not set +# CONFIG_ARCH_IXP2000 is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_NS9XXX is not set +CONFIG_ARCH_MXC=y +# CONFIG_ARCH_ORION5X is not set +# CONFIG_ARCH_PNX4008 is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_SA1100 is not set +# CONFIG_ARCH_S3C2410 is not set +# CONFIG_ARCH_SHARK is not set +# CONFIG_ARCH_LH7A40X is not set +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_OMAP is not set +# CONFIG_ARCH_MSM7X00A is not set + +# +# Boot options +# + +# +# Power management +# + +# +# Freescale MXC Implementations +# +# CONFIG_ARCH_MX37 is not set +# CONFIG_ARCH_MX35 is not set +# CONFIG_ARCH_MX51 is not set +CONFIG_ARCH_MX3=y +# CONFIG_ARCH_MX27 is not set +# CONFIG_ARCH_MX21 is not set +CONFIG_ARCH_MXC_HAS_NFC_V1=y +CONFIG_I2C_MXC_SELECT1=y +# CONFIG_I2C_MXC_SELECT2 is not set + +# +# MX3 Options +# +CONFIG_MX3_OPTIONS=y +CONFIG_MACH_MX31ADS=y +# CONFIG_MACH_MX31_3DS is not set +# CONFIG_MX3_DOZE_DURING_IDLE is not set +CONFIG_MXC_SDMA_API=y + +# +# SDMA options +# +# CONFIG_SDMA_IRAM is not set +CONFIG_ARCH_MXC_HAS_NFC_V2=y + +# +# Device options +# +# CONFIG_I2C_MXC_SELECT3 is not set +CONFIG_ARCH_HAS_EVTMON=y +CONFIG_DMA_ZONE_SIZE=24 +CONFIG_ISP1504_MXC=y + +# +# Processor Type +# +CONFIG_CPU_32=y +# CONFIG_CPU_ARM926T is not set +CONFIG_CPU_V6=y +# CONFIG_CPU_32v6K is not set +# CONFIG_CPU_V7 is not set +CONFIG_CPU_32v6=y +CONFIG_CPU_ABRT_EV6=y +CONFIG_CPU_PABRT_NOIFAR=y +CONFIG_CPU_CACHE_V6=y +CONFIG_CPU_CACHE_VIPT=y +CONFIG_CPU_COPY_V6=y +CONFIG_CPU_TLB_V6=y +CONFIG_CPU_HAS_ASID=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y + +# +# Processor Features +# +CONFIG_ARM_THUMB=y +# CONFIG_CPU_ICACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_BPREDICT_DISABLE is not set +CONFIG_OUTER_CACHE=y +CONFIG_CACHE_L2X0=y + +# +# Bus support +# +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +CONFIG_PCCARD=m +# CONFIG_PCMCIA_DEBUG is not set +CONFIG_PCMCIA=m +CONFIG_PCMCIA_LOAD_CIS=y +# CONFIG_PCMCIA_IOCTL is not set + +# +# PC-card bridges +# +CONFIG_PCMCIA_MX31ADS=m + +# +# Kernel Features +# +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +CONFIG_PREEMPT=y +CONFIG_HZ=100 +CONFIG_AEABI=y +# CONFIG_OABI_COMPAT is not set +# CONFIG_ARCH_DISCONTIGMEM_ENABLE is not set +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +# CONFIG_SPARSEMEM_STATIC is not set +# CONFIG_SPARSEMEM_VMEMMAP_ENABLE is not set +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_SPLIT_PTLOCK_CPUS=4 +# CONFIG_RESOURCES_64BIT is not set +CONFIG_ZONE_DMA_FLAG=1 +CONFIG_BOUNCE=y +CONFIG_VIRT_TO_BUS=y +# CONFIG_LEDS is not set +CONFIG_ALIGNMENT_TRAP=y + +# +# Boot options +# +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="noinitrd console=ttymxc0 root=/dev/mtdblock2 rw ip=off" +# CONFIG_XIP_KERNEL is not set +# CONFIG_KEXEC is not set + +# +# CPU Frequency scaling +# +# CONFIG_CPU_FREQ is not set + +# +# Floating point emulation +# + +# +# At least one emulation must be selected +# +CONFIG_VFP=y + +# +# Userspace binary formats +# +CONFIG_BINFMT_ELF=y +# CONFIG_BINFMT_AOUT is not set +# CONFIG_BINFMT_MISC is not set + +# +# Power management options +# +CONFIG_PM=y +# CONFIG_PM_DEBUG is not set +CONFIG_PM_SLEEP=y +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +# CONFIG_APM_EMULATION is not set +CONFIG_ARCH_SUSPEND_POSSIBLE=y + +# +# Networking +# +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +CONFIG_PACKET_MMAP=y +CONFIG_UNIX=y +CONFIG_XFRM=y +# CONFIG_XFRM_USER is not set +# CONFIG_XFRM_SUB_POLICY is not set +# CONFIG_XFRM_MIGRATE is not set +# CONFIG_XFRM_STATISTICS is not set +# CONFIG_NET_KEY is not set +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_FIB_HASH=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +# CONFIG_IP_PNP_RARP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_IP_MROUTE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +# CONFIG_INET_TUNNEL is not set +CONFIG_INET_XFRM_MODE_TRANSPORT=y +CONFIG_INET_XFRM_MODE_TUNNEL=y +CONFIG_INET_XFRM_MODE_BEET=y +# CONFIG_INET_LRO is not set +CONFIG_INET_DIAG=y +CONFIG_INET_TCP_DIAG=y +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +# CONFIG_IPV6 is not set +# CONFIG_NETWORK_SECMARK is not set +# CONFIG_NETFILTER is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_NET_SCHED is not set + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_HAMRADIO is not set +# CONFIG_CAN is not set +CONFIG_IRDA=m + +# +# IrDA protocols +# +CONFIG_IRLAN=m +CONFIG_IRCOMM=m +# CONFIG_IRDA_ULTRA is not set + +# +# IrDA options +# +CONFIG_IRDA_CACHE_LAST_LSAP=y +CONFIG_IRDA_FAST_RR=y +# CONFIG_IRDA_DEBUG is not set + +# +# Infrared-port device drivers +# + +# +# SIR device drivers +# +CONFIG_IRTTY_SIR=m + +# +# Dongle support +# +# CONFIG_DONGLE is not set +# CONFIG_KINGSUN_DONGLE is not set +# CONFIG_KSDAZZLE_DONGLE is not set +# CONFIG_KS959_DONGLE is not set + +# +# FIR device drivers +# +# CONFIG_USB_IRDA is not set +# CONFIG_SIGMATEL_FIR is not set +# CONFIG_MCS_FIR is not set +CONFIG_MXC_FIR=m +# CONFIG_BT is not set +# CONFIG_AF_RXRPC is not set + +# +# Wireless +# +# CONFIG_CFG80211 is not set +CONFIG_WIRELESS_EXT=y +# CONFIG_MAC80211 is not set +CONFIG_IEEE80211=y +# CONFIG_IEEE80211_DEBUG is not set +# CONFIG_IEEE80211_CRYPT_WEP is not set +# CONFIG_IEEE80211_CRYPT_CCMP is not set +# CONFIG_IEEE80211_CRYPT_TKIP is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +# CONFIG_SYS_HYPERVISOR is not set +CONFIG_CONNECTOR=y +CONFIG_PROC_EVENTS=y +CONFIG_MTD=y +# CONFIG_MTD_DEBUG is not set +# CONFIG_MTD_CONCAT is not set +CONFIG_MTD_PARTITIONS=y +CONFIG_MTD_REDBOOT_PARTS=y +CONFIG_MTD_REDBOOT_DIRECTORY_BLOCK=-1 +# CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED is not set +# CONFIG_MTD_REDBOOT_PARTS_READONLY is not set +CONFIG_MTD_CMDLINE_PARTS=y +# CONFIG_MTD_AFS_PARTS is not set +# CONFIG_MTD_AR7_PARTS is not set + +# +# User Modules And Translation Layers +# +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLKDEVS=y +CONFIG_MTD_BLOCK=y +# CONFIG_FTL is not set +# CONFIG_NFTL is not set +# CONFIG_INFTL is not set +# CONFIG_RFD_FTL is not set +# CONFIG_SSFDC is not set +# CONFIG_MTD_OOPS is not set + +# +# RAM/ROM/Flash chip drivers +# +CONFIG_MTD_CFI=y +# CONFIG_MTD_JEDECPROBE is not set +CONFIG_MTD_GEN_PROBE=y +CONFIG_MTD_CFI_ADV_OPTIONS=y +CONFIG_MTD_CFI_NOSWAP=y +# CONFIG_MTD_CFI_BE_BYTE_SWAP is not set +# CONFIG_MTD_CFI_LE_BYTE_SWAP is not set +CONFIG_MTD_CFI_GEOMETRY=y +# CONFIG_MTD_MAP_BANK_WIDTH_1 is not set +CONFIG_MTD_MAP_BANK_WIDTH_2=y +# CONFIG_MTD_MAP_BANK_WIDTH_4 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set +CONFIG_MTD_CFI_I1=y +# CONFIG_MTD_CFI_I2 is not set +# CONFIG_MTD_CFI_I4 is not set +# CONFIG_MTD_CFI_I8 is not set +# CONFIG_MTD_OTP is not set +# CONFIG_MTD_CFI_INTELEXT is not set +CONFIG_MTD_CFI_AMDSTD=y +# CONFIG_MTD_CFI_STAA is not set +CONFIG_MTD_CFI_UTIL=y +CONFIG_MTD_RAM=y +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_ABSENT is not set +# CONFIG_MTD_XIP is not set + +# +# Mapping drivers for chip access +# +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_PHYSMAP is not set +# CONFIG_MTD_ARM_INTEGRATOR is not set +# CONFIG_MTD_PLATRAM is not set +CONFIG_MTD_MXC=y + +# +# Self-contained MTD device drivers +# +# CONFIG_MTD_DATAFLASH is not set +# CONFIG_MTD_M25P80 is not set +# CONFIG_MTD_SLRAM is not set +# CONFIG_MTD_PHRAM is not set +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_BLOCK2MTD is not set + +# +# Disk-On-Chip Device Drivers +# +# CONFIG_MTD_DOC2000 is not set +# CONFIG_MTD_DOC2001 is not set +# CONFIG_MTD_DOC2001PLUS is not set +CONFIG_MTD_NAND=y +# CONFIG_MTD_NAND_VERIFY_WRITE is not set +# CONFIG_MTD_NAND_ECC_SMC is not set +# CONFIG_MTD_NAND_MUSEUM_IDS is not set +CONFIG_MTD_NAND_IDS=y +# CONFIG_MTD_NAND_DISKONCHIP is not set +# CONFIG_MTD_NAND_NANDSIM is not set +CONFIG_MTD_NAND_MXC=y +CONFIG_MTD_NAND_MXC_V2=y +# CONFIG_MTD_NAND_MXC_SWECC is not set +# CONFIG_MTD_NAND_MXC_FORCE_CE is not set +# CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 is not set +# CONFIG_MXC_NAND_LOW_LEVEL_ERASE is not set +# CONFIG_MTD_NAND_PLATFORM is not set +# CONFIG_MTD_ALAUDA is not set +# CONFIG_MTD_ONENAND is not set + +# +# UBI - Unsorted block images +# +# CONFIG_MTD_UBI is not set + +# +# Voltage and Current regulators +# +CONFIG_REGULATOR_API=y +CONFIG_REGULATOR=y +# CONFIG_REGULATOR_DEBUG is not set +CONFIG_REGULATOR_MC13783=y +# CONFIG_REGULATOR_WM8350 is not set +# CONFIG_PARPORT is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=y +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +# CONFIG_BLK_DEV_NBD is not set +# CONFIG_BLK_DEV_UB is not set +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=16 +CONFIG_BLK_DEV_RAM_SIZE=16384 +# CONFIG_BLK_DEV_XIP is not set +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +CONFIG_MISC_DEVICES=y +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_ENCLOSURE_SERVICES is not set +CONFIG_HAVE_IDE=y +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_TGT is not set +# CONFIG_SCSI_NETLINK is not set +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +# CONFIG_BLK_DEV_SR is not set +# CONFIG_CHR_DEV_SG is not set +# CONFIG_CHR_DEV_SCH is not set + +# +# Some SCSI devices (e.g. CD jukebox) support multiple LUNs +# +CONFIG_SCSI_MULTI_LUN=y +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +CONFIG_SCSI_WAIT_SCAN=m + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +CONFIG_SCSI_LOWLEVEL=y +# CONFIG_ISCSI_TCP is not set +# CONFIG_SCSI_DEBUG is not set +# CONFIG_SCSI_LOWLEVEL_PCMCIA is not set +CONFIG_ATA=m +# CONFIG_ATA_NONSTANDARD is not set +# CONFIG_SATA_PMP is not set +CONFIG_ATA_SFF=y +# CONFIG_SATA_MV is not set +# CONFIG_PATA_PCMCIA is not set +# CONFIG_PATA_PLATFORM is not set +CONFIG_PATA_FSL=m +# CONFIG_MD is not set +CONFIG_NETDEVICES=y +# CONFIG_NETDEVICES_MULTIQUEUE is not set +# CONFIG_DUMMY is not set +# CONFIG_BONDING is not set +# CONFIG_MACVLAN is not set +# CONFIG_EQUALIZER is not set +# CONFIG_TUN is not set +# CONFIG_VETH is not set +# CONFIG_PHYLIB is not set +CONFIG_NET_ETHERNET=y +CONFIG_MII=y +# CONFIG_AX88796 is not set +# CONFIG_SMC91X is not set +# CONFIG_DM9000 is not set +# CONFIG_ENC28J60 is not set +# CONFIG_SMC911X is not set +# CONFIG_SMSC911X is not set +# CONFIG_IBM_NEW_EMAC_ZMII is not set +# CONFIG_IBM_NEW_EMAC_RGMII is not set +# CONFIG_IBM_NEW_EMAC_TAH is not set +# CONFIG_IBM_NEW_EMAC_EMAC4 is not set +# CONFIG_B44 is not set +CONFIG_CS89x0=y +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set + +# +# Wireless LAN +# +# CONFIG_WLAN_PRE80211 is not set +# CONFIG_WLAN_80211 is not set +# CONFIG_IWLWIFI_LEDS is not set + +# +# USB Network Adapters +# +# CONFIG_USB_CATC is not set +# CONFIG_USB_KAWETH is not set +# CONFIG_USB_PEGASUS is not set +# CONFIG_USB_RTL8150 is not set +# CONFIG_USB_USBNET is not set +CONFIG_NET_PCMCIA=y +# CONFIG_PCMCIA_3C589 is not set +# CONFIG_PCMCIA_3C574 is not set +# CONFIG_PCMCIA_FMVJ18X is not set +CONFIG_PCMCIA_PCNET=m +# CONFIG_PCMCIA_NMCLAN is not set +# CONFIG_PCMCIA_SMC91C92 is not set +# CONFIG_PCMCIA_XIRC2PS is not set +# CONFIG_PCMCIA_AXNET is not set +# CONFIG_WAN is not set +# CONFIG_PPP is not set +# CONFIG_SLIP is not set +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_ISDN is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +# CONFIG_INPUT_POLLDEV is not set + +# +# Userland interfaces +# +# CONFIG_INPUT_MOUSEDEV is not set +# CONFIG_INPUT_JOYDEV is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +CONFIG_KEYBOARD_MXC=y +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TABLET is not set +CONFIG_INPUT_TOUCHSCREEN=y +# CONFIG_TOUCHSCREEN_ADS7846 is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_MK712 is not set +CONFIG_TOUCHSCREEN_MXC=y +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_UCB1400 is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +# CONFIG_INPUT_MISC is not set + +# +# Hardware I/O ports +# +# CONFIG_SERIO is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +CONFIG_VT=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +# CONFIG_VT_HW_CONSOLE_BINDING is not set +CONFIG_DEVKMEM=y +# CONFIG_SERIAL_NONSTANDARD is not set + +# +# Serial drivers +# +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +# CONFIG_SERIAL_8250_CS is not set +CONFIG_SERIAL_8250_NR_UARTS=2 +CONFIG_SERIAL_8250_RUNTIME_UARTS=2 +CONFIG_SERIAL_8250_EXTENDED=y +# CONFIG_SERIAL_8250_MANY_PORTS is not set +# CONFIG_SERIAL_8250_SHARE_IRQ is not set +# CONFIG_SERIAL_8250_DETECT_IRQ is not set +# CONFIG_SERIAL_8250_RSA is not set + +# +# Non-8250 serial port support +# +CONFIG_SERIAL_MXC=y +CONFIG_SERIAL_MXC_CONSOLE=y +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_UNIX98_PTYS=y +CONFIG_LEGACY_PTYS=y +CONFIG_LEGACY_PTY_COUNT=256 +# CONFIG_IPMI_HANDLER is not set +CONFIG_HW_RANDOM=y +# CONFIG_NVRAM is not set +# CONFIG_R3964 is not set + +# +# PCMCIA character devices +# +# CONFIG_SYNCLINK_CS is not set +# CONFIG_CARDMAN_4000 is not set +# CONFIG_CARDMAN_4040 is not set +# CONFIG_IPWIRELESS is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_CHARDEV=y + +# +# I2C Hardware Bus support +# +CONFIG_I2C_MXC=y +# CONFIG_I2C_MXC_HS is not set +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_SIMTEC is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_STUB is not set +# CONFIG_I2C_TINY_USB is not set +# CONFIG_I2C_PCA_PLATFORM is not set + +# +# Miscellaneous I2C Chip support +# +# CONFIG_DS1682 is not set +# CONFIG_SENSORS_EEPROM is not set +# CONFIG_SENSORS_PCF8574 is not set +# CONFIG_PCF8575 is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_SENSORS_MAX6875 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CHIP is not set +# CONFIG_I2C_SLAVE is not set +CONFIG_SPI=y +CONFIG_SPI_MASTER=y + +# +# SPI Master Controller Drivers +# +CONFIG_SPI_BITBANG=y +CONFIG_SPI_MXC=y +# CONFIG_SPI_MXC_TEST_LOOPBACK is not set +# CONFIG_SPI_MXC_SELECT1 is not set +CONFIG_SPI_MXC_SELECT2=y +# CONFIG_SPI_MXC_SELECT3 is not set + +# +# SPI Protocol Masters +# +# CONFIG_SPI_AT25 is not set +# CONFIG_SPI_SPIDEV is not set +# CONFIG_SPI_TLE62X0 is not set +CONFIG_W1=y +CONFIG_W1_CON=y + +# +# 1-wire Bus Masters +# +# CONFIG_W1_MASTER_DS2490 is not set +# CONFIG_W1_MASTER_DS2482 is not set +CONFIG_W1_MASTER_MXC=y +# CONFIG_W1_MASTER_DS1WM is not set + +# +# 1-wire Slaves +# +# CONFIG_W1_SLAVE_THERM is not set +# CONFIG_W1_SLAVE_SMEM is not set +# CONFIG_W1_SLAVE_DS2751 is not set +CONFIG_W1_SLAVE_DS2433=y +# CONFIG_W1_SLAVE_DS2433_CRC is not set +# CONFIG_W1_SLAVE_DS2760 is not set +# CONFIG_POWER_SUPPLY is not set +# CONFIG_HWMON is not set +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_NOWAYOUT=y + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +CONFIG_MXC_WATCHDOG=y + +# +# USB-based Watchdog Cards +# +# CONFIG_USBPCWATCHDOG is not set + +# +# Sonics Silicon Backplane +# +CONFIG_SSB_POSSIBLE=y +# CONFIG_SSB is not set + +# +# Multifunction device drivers +# +# CONFIG_MFD_SM501 is not set +# CONFIG_MFD_ASIC3 is not set +# CONFIG_HTC_PASIC3 is not set + +# +# Multimedia devices +# + +# +# Multimedia core support +# +CONFIG_VIDEO_DEV=y +CONFIG_VIDEO_V4L2_COMMON=y +CONFIG_VIDEO_ALLOW_V4L1=y +CONFIG_VIDEO_V4L1_COMPAT=y +# CONFIG_DVB_CORE is not set +CONFIG_VIDEO_MEDIA=y + +# +# Multimedia drivers +# +# CONFIG_MEDIA_ATTACH is not set +CONFIG_MEDIA_TUNER=y +# CONFIG_MEDIA_TUNER_CUSTOMIZE is not set +CONFIG_MEDIA_TUNER_SIMPLE=y +CONFIG_MEDIA_TUNER_TDA8290=y +CONFIG_MEDIA_TUNER_TDA9887=y +CONFIG_MEDIA_TUNER_TEA5761=y +CONFIG_MEDIA_TUNER_TEA5767=y +CONFIG_MEDIA_TUNER_MT20XX=y +CONFIG_MEDIA_TUNER_XC2028=y +CONFIG_MEDIA_TUNER_XC5000=y +CONFIG_VIDEO_V4L2=y +CONFIG_VIDEO_V4L1=y +CONFIG_VIDEO_CAPTURE_DRIVERS=y +# CONFIG_VIDEO_ADV_DEBUG is not set +CONFIG_VIDEO_HELPER_CHIPS_AUTO=y +# CONFIG_VIDEO_VIVI is not set +CONFIG_VIDEO_MXC_CAMERA=y + +# +# MXC Camera/V4L2 PRP Features support +# +CONFIG_VIDEO_MXC_IPU_CAMERA=y +# CONFIG_MXC_CAMERA_MC521DA is not set +CONFIG_MXC_CAMERA_MICRON111=y +# CONFIG_MXC_CAMERA_OV2640 is not set +# CONFIG_MXC_TVIN_ADV7180 is not set +CONFIG_MXC_IPU_PRP_VF_SDC=y +CONFIG_MXC_IPU_PRP_ENC=y +CONFIG_VIDEO_MXC_OUTPUT=y +CONFIG_VIDEO_MXC_IPU_OUTPUT=y +# CONFIG_VIDEO_MXC_IPUV1_WVGA_OUTPUT is not set +# CONFIG_VIDEO_MXC_OPL is not set +# CONFIG_VIDEO_CPIA is not set +# CONFIG_VIDEO_CPIA2 is not set +# CONFIG_VIDEO_SAA5246A is not set +# CONFIG_VIDEO_SAA5249 is not set +# CONFIG_TUNER_3036 is not set +CONFIG_V4L_USB_DRIVERS=y +# CONFIG_USB_VIDEO_CLASS is not set +# CONFIG_VIDEO_PVRUSB2 is not set +# CONFIG_VIDEO_EM28XX is not set +# CONFIG_VIDEO_USBVISION is not set +# CONFIG_USB_VICAM is not set +# CONFIG_USB_IBMCAM is not set +# CONFIG_USB_KONICAWC is not set +# CONFIG_USB_QUICKCAM_MESSENGER is not set +# CONFIG_USB_ET61X251 is not set +# CONFIG_VIDEO_OVCAMCHIP is not set +# CONFIG_USB_W9968CF is not set +# CONFIG_USB_OV511 is not set +# CONFIG_USB_SE401 is not set +# CONFIG_USB_SN9C102 is not set +# CONFIG_USB_STV680 is not set +# CONFIG_USB_ZC0301 is not set +# CONFIG_USB_PWC is not set +# CONFIG_USB_ZR364XX is not set +# CONFIG_USB_STKWEBCAM is not set +# CONFIG_SOC_CAMERA is not set +CONFIG_RADIO_ADAPTERS=y +# CONFIG_USB_DSBR is not set +# CONFIG_USB_SI470X is not set +CONFIG_DAB=y +# CONFIG_USB_DABUSB is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +# CONFIG_VIDEO_OUTPUT_CONTROL is not set +CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +# CONFIG_FB_DDC is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_FOREIGN_ENDIAN is not set +# CONFIG_FB_SYS_FOPS is not set +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_MODE_HELPERS is not set +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# +CONFIG_FB_MXC=y +CONFIG_FB_MXC_SYNC_PANEL=y +# CONFIG_FB_MXC_EPSON_VGA_SYNC_PANEL is not set +# CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL is not set +CONFIG_FB_MXC_TVOUT=y +# CONFIG_FB_MXC_TVOUT_CH7024 is not set +# CONFIG_FB_MXC_ASYNC_PANEL is not set +# CONFIG_FB_UVESA is not set +# CONFIG_FB_S1D13XXX is not set +# CONFIG_FB_VIRTUAL is not set +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_LCD_CLASS_DEVICE is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_CORGI is not set +CONFIG_BACKLIGHT_MXC=y +CONFIG_BACKLIGHT_MXC_IPU=y +CONFIG_BACKLIGHT_MXC_PMIC=y + +# +# Display device support +# +# CONFIG_DISPLAY_SUPPORT is not set + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE=y +# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +# CONFIG_FONTS is not set +CONFIG_FONT_8x8=y +CONFIG_FONT_8x16=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_LOGO_LINUX_CLUT224=y + +# +# Sound +# +CONFIG_SOUND=y + +# +# Advanced Linux Sound Architecture +# +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +# CONFIG_SND_SEQUENCER is not set +CONFIG_SND_OSSEMUL=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +CONFIG_SND_PCM_OSS_PLUGINS=y +# CONFIG_SND_DYNAMIC_MINORS is not set +CONFIG_SND_SUPPORT_OLD_API=y +CONFIG_SND_VERBOSE_PROCFS=y +# CONFIG_SND_VERBOSE_PRINTK is not set +# CONFIG_SND_DEBUG is not set + +# +# Generic devices +# +# CONFIG_SND_DUMMY is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_MPU401 is not set + +# +# ALSA ARM devices +# +# CONFIG_SND_MXC_SPDIF is not set +CONFIG_SND_MXC_PMIC=y +# CONFIG_SND_MXC_PLAYBACK_MIXING is not set +# CONFIG_HEADSET_DETECT_ENABLE is not set + +# +# SPI devices +# + +# +# USB devices +# +# CONFIG_SND_USB_AUDIO is not set +# CONFIG_SND_USB_CAIAQ is not set + +# +# PCMCIA devices +# +# CONFIG_SND_VXPOCKET is not set +# CONFIG_SND_PDAUDIOCF is not set + +# +# System on Chip audio support +# +# CONFIG_SND_SOC is not set +# CONFIG_SND_MXC_SOC is not set +# CONFIG_SND_MXC_SOC_IRAM is not set +# CONFIG_SND_SOC_IMX_3STACK_WM8350 is not set +# CONFIG_SND_SOC_IMX_3STACK_AK4647 is not set +# CONFIG_SND_SOC_IMX_3STACK_WM8580 is not set +# CONFIG_SND_SOC_IMX_3STACK_WM8903 is not set + +# +# ALSA SoC audio for Freescale SOCs +# + +# +# SoC Audio for the Texas Instruments OMAP +# + +# +# Open Sound System +# +# CONFIG_SOUND_PRIME is not set +CONFIG_HID_SUPPORT=y +CONFIG_HID=y +# CONFIG_HID_DEBUG is not set +# CONFIG_HIDRAW is not set + +# +# USB Input Devices +# +CONFIG_USB_HID=m +# CONFIG_USB_HIDINPUT_POWERBOOK is not set +# CONFIG_HID_FF is not set +# CONFIG_USB_HIDDEV is not set + +# +# USB HID Boot Protocol drivers +# +# CONFIG_USB_KBD is not set +# CONFIG_USB_MOUSE is not set +CONFIG_USB_SUPPORT=y +CONFIG_USB_ARCH_HAS_HCD=y +# CONFIG_USB_ARCH_HAS_OHCI is not set +CONFIG_USB_ARCH_HAS_EHCI=y +CONFIG_USB=y +# CONFIG_USB_DEBUG is not set +# CONFIG_USB_ANNOUNCE_NEW_DEVICES is not set + +# +# Miscellaneous USB options +# +CONFIG_USB_DEVICEFS=y +CONFIG_USB_DEVICE_CLASS=y +# CONFIG_USB_DYNAMIC_MINORS is not set +CONFIG_USB_SUSPEND=y +# CONFIG_USB_OTG is not set +# CONFIG_USB_OTG_WHITELIST is not set +# CONFIG_USB_OTG_BLACKLIST_HUB is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_C67X00_HCD is not set +CONFIG_USB_EHCI_HCD=m +CONFIG_USB_EHCI_ARC=y +CONFIG_USB_EHCI_ARC_H1=y +CONFIG_USB_EHCI_ARC_H2=y +CONFIG_USB_EHCI_ARC_OTG=y +# CONFIG_USB_STATIC_IRAM is not set +# CONFIG_USB_EHCI_FSL_MC13783 is not set +# CONFIG_USB_EHCI_FSL_1301 is not set +CONFIG_USB_EHCI_FSL_1504=y +# CONFIG_USB_EHCI_FSL_UTMI is not set +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +# CONFIG_USB_ISP116X_HCD is not set +# CONFIG_USB_ISP1760_HCD is not set +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set + +# +# USB Device Class drivers +# +# CONFIG_USB_ACM is not set +# CONFIG_USB_PRINTER is not set +# CONFIG_USB_WDM is not set + +# +# NOTE: USB_STORAGE enables SCSI, and 'SCSI disk support' +# + +# +# may also be needed; see USB_STORAGE Help for more information +# +CONFIG_USB_STORAGE=y +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +# CONFIG_USB_STORAGE_FREECOM is not set +# CONFIG_USB_STORAGE_ISD200 is not set +# CONFIG_USB_STORAGE_DPCM is not set +# CONFIG_USB_STORAGE_USBAT is not set +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_ONETOUCH is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set +# CONFIG_USB_LIBUSUAL is not set + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set +CONFIG_USB_MON=y + +# +# Belcarra USBLAN Networking for USB +# +# CONFIG_USB_USBLAN is not set + +# +# USB port drivers +# +# CONFIG_USB_SERIAL is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_AUERSWALD is not set +# CONFIG_USB_RIO500 is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_BERRY_CHARGE is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_PHIDGET is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_USB_SISUSBVGA is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_TEST is not set +# CONFIG_USB_ISIGHTFW is not set +CONFIG_USB_GADGET=m +# CONFIG_USB_GADGET_DEBUG_FILES is not set +CONFIG_USB_GADGET_SELECTED=y +# CONFIG_USB_GADGET_AMD5536UDC is not set +# CONFIG_USB_GADGET_ATMEL_USBA is not set +# CONFIG_USB_GADGET_FSL_USB2 is not set +# CONFIG_USB_GADGET_NET2280 is not set +# CONFIG_USB_GADGET_PXA2XX is not set +# CONFIG_USB_GADGET_M66592 is not set +# CONFIG_USB_GADGET_PXA27X is not set +# CONFIG_USB_GADGET_GOKU is not set +# CONFIG_USB_GADGET_LH7A40X is not set +# CONFIG_USB_GADGET_OMAP is not set +CONFIG_USB_GADGET_ARC=y +CONFIG_USB_ARC=m +# CONFIG_USB_GADGET_S3C2410 is not set +# CONFIG_USB_GADGET_AT91 is not set +# CONFIG_USB_GADGET_DUMMY_HCD is not set +CONFIG_USB_GADGET_DUALSPEED=y +CONFIG_USB_GADGET_ARC_OTG=y +# CONFIG_USB_GADGET_FSL_MC13783 is not set +# CONFIG_USB_GADGET_FSL_1301 is not set +CONFIG_USB_GADGET_FSL_1504=y +# CONFIG_USB_GADGET_FSL_UTMI is not set +# CONFIG_USB_ZERO is not set +CONFIG_USB_ETH=m +CONFIG_USB_ETH_RNDIS=y +CONFIG_USB_GADGETFS=m +CONFIG_USB_FILE_STORAGE=m +# CONFIG_USB_FILE_STORAGE_TEST is not set +CONFIG_USB_G_SERIAL=m +# CONFIG_USB_MIDI_GADGET is not set +# CONFIG_USB_G_PRINTER is not set +CONFIG_MMC=y +# CONFIG_MMC_DEBUG is not set +CONFIG_MMC_UNSAFE_RESUME=y + +# +# MMC/SD Card Drivers +# +CONFIG_MMC_BLOCK=y +CONFIG_MMC_BLOCK_BOUNCE=y +# CONFIG_SDIO_UART is not set +# CONFIG_MMC_TEST is not set + +# +# MMC/SD Host Controller Drivers +# +# CONFIG_MMC_SPI is not set +CONFIG_MMC_MXC=y +# CONFIG_MMC_IMX_ESDHCI is not set +# CONFIG_MMC_IMX_ESDHCI_PIO_MODE is not set +# CONFIG_NEW_LEDS is not set +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set +# CONFIG_RTC_DRV_S35390A is not set +# CONFIG_RTC_DRV_FM3130 is not set + +# +# SPI RTC drivers +# +# CONFIG_RTC_DRV_MAX6902 is not set +# CONFIG_RTC_DRV_R9701 is not set +# CONFIG_RTC_DRV_RS5C348 is not set + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_V3020 is not set + +# +# on-CPU RTC drivers +# +CONFIG_RTC_MXC=y +# CONFIG_RTC_DRV_MXC_V2 is not set +# CONFIG_UIO is not set + +# +# MXC support drivers +# +CONFIG_MXC_IPU=y +CONFIG_MXC_IPU_V1=y +CONFIG_MXC_IPU_PF=y + +# +# MXC SSI support +# +CONFIG_MXC_SSI=y + +# +# MXC Digital Audio Multiplexer support +# +CONFIG_MXC_DAM=y + +# +# MXC PMIC support +# +CONFIG_MXC_PMIC=y +CONFIG_MXC_PMIC_MC13783=y +# CONFIG_MXC_PMIC_MC13892 is not set +# CONFIG_MXC_PMIC_MC9SDZ60 is not set +CONFIG_MXC_PMIC_CHARDEV=y + +# +# MXC PMIC Client Drivers +# +CONFIG_MXC_MC13783_ADC=y +CONFIG_MXC_MC13783_AUDIO=y +CONFIG_MXC_MC13783_RTC=y +CONFIG_MXC_MC13783_LIGHT=y +CONFIG_MXC_MC13783_BATTERY=y +CONFIG_MXC_MC13783_CONNECTIVITY=y +CONFIG_MXC_MC13783_POWER=y + +# +# Advanced Power Management devices +# + +# +# MXC Security Drivers +# +CONFIG_MXC_SECURITY_SCC=y +# CONFIG_SCC_DEBUG is not set +CONFIG_MXC_SECURITY_RNG=y +# CONFIG_MXC_RNG_TEST_DRIVER is not set +# CONFIG_MXC_RNG_DEBUG is not set +CONFIG_MXC_SECURITY_CORE=y + +# +# MXC MPEG4 Encoder Kernel module support +# +CONFIG_MXC_HMP4E=y +# CONFIG_MXC_HMP4E_DEBUG is not set + +# +# MXC HARDWARE EVENT +# +CONFIG_MXC_HWEVENT=y + +# +# MXC VPU(Video Processing Unit) support +# +CONFIG_MXC_VPU=y +# CONFIG_MXC_VPU_DEBUG is not set + +# +# MXC Asynchronous Sample Rate Converter support +# + +# +# MXC Bluetooth support +# + +# +# Broadcom GPS ioctrl support +# + +# +# MXC Media Local Bus Driver +# + +# +# File systems +# +CONFIG_EXT2_FS=y +# CONFIG_EXT2_FS_XATTR is not set +# CONFIG_EXT2_FS_XIP is not set +# CONFIG_EXT3_FS is not set +# CONFIG_EXT4DEV_FS is not set +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_FS_POSIX_ACL is not set +# CONFIG_XFS_FS is not set +# CONFIG_OCFS2_FS is not set +CONFIG_DNOTIFY=y +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_QUOTA is not set +# CONFIG_AUTOFS_FS is not set +CONFIG_AUTOFS4_FS=m +# CONFIG_FUSE_FS is not set + +# +# CD-ROM/DVD Filesystems +# +# CONFIG_ISO9660_FS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +# CONFIG_TMPFS_POSIX_ACL is not set +# CONFIG_HUGETLB_PAGE is not set +# CONFIG_CONFIGFS_FS is not set + +# +# Miscellaneous filesystems +# +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_DEBUG=0 +CONFIG_JFFS2_FS_WRITEBUFFER=y +# CONFIG_JFFS2_FS_WBUF_VERIFY is not set +# CONFIG_JFFS2_SUMMARY is not set +# CONFIG_JFFS2_FS_XATTR is not set +# CONFIG_JFFS2_COMPRESSION_OPTIONS is not set +CONFIG_JFFS2_ZLIB=y +# CONFIG_JFFS2_LZO is not set +CONFIG_JFFS2_RTIME=y +# CONFIG_JFFS2_RUBIN is not set +CONFIG_CRAMFS=y +# CONFIG_VXFS_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +# CONFIG_NFS_V3_ACL is not set +# CONFIG_NFS_V4 is not set +# CONFIG_NFSD is not set +CONFIG_ROOT_NFS=y +CONFIG_LOCKD=y +CONFIG_LOCKD_V4=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=y +# CONFIG_SUNRPC_BIND34 is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +# CONFIG_RPCSEC_GSS_SPKM3 is not set +# CONFIG_SMB_FS is not set +# CONFIG_CIFS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +CONFIG_NLS_ASCII=m +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +CONFIG_NLS_UTF8=m +# CONFIG_DLM is not set + +# +# Kernel hacking +# +# CONFIG_PRINTK_TIME is not set +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +CONFIG_FRAME_WARN=1024 +# CONFIG_MAGIC_SYSRQ is not set +# CONFIG_UNUSED_SYMBOLS is not set +# CONFIG_DEBUG_FS is not set +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_KERNEL is not set +# CONFIG_SLUB_DEBUG_ON is not set +# CONFIG_SLUB_STATS is not set +# CONFIG_DEBUG_BUGVERBOSE is not set +CONFIG_FRAME_POINTER=y +# CONFIG_SAMPLES is not set +# CONFIG_DEBUG_USER is not set + +# +# Security options +# +# CONFIG_KEYS is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +# CONFIG_CRYPTO_MANAGER is not set +# CONFIG_CRYPTO_GF128MUL is not set +# CONFIG_CRYPTO_NULL is not set +# CONFIG_CRYPTO_CRYPTD is not set +# CONFIG_CRYPTO_AUTHENC is not set +# CONFIG_CRYPTO_TEST is not set + +# +# Authenticated Encryption with Associated Data +# +# CONFIG_CRYPTO_CCM is not set +# CONFIG_CRYPTO_GCM is not set +# CONFIG_CRYPTO_SEQIV is not set + +# +# Block modes +# +# CONFIG_CRYPTO_CBC is not set +# CONFIG_CRYPTO_CTR is not set +# CONFIG_CRYPTO_CTS is not set +# CONFIG_CRYPTO_ECB is not set +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_XTS is not set + +# +# Hash modes +# +# CONFIG_CRYPTO_HMAC is not set +# CONFIG_CRYPTO_XCBC is not set + +# +# Digest +# +# CONFIG_CRYPTO_CRC32C is not set +# CONFIG_CRYPTO_MD4 is not set +# CONFIG_CRYPTO_MD5 is not set +# CONFIG_CRYPTO_MICHAEL_MIC is not set +# CONFIG_CRYPTO_SHA1 is not set +# CONFIG_CRYPTO_SHA256 is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_WP512 is not set + +# +# Ciphers +# +# CONFIG_CRYPTO_AES is not set +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_ARC4 is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +# CONFIG_CRYPTO_DES is not set +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_SALSA20 is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SERPENT is not set +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TWOFISH is not set + +# +# Compression +# +# CONFIG_CRYPTO_DEFLATE is not set +# CONFIG_CRYPTO_LZO is not set +CONFIG_CRYPTO_HW=y + +# +# Library routines +# +CONFIG_BITREVERSE=y +# CONFIG_GENERIC_FIND_FIRST_BIT is not set +# CONFIG_GENERIC_FIND_NEXT_BIT is not set +CONFIG_CRC_CCITT=m +# CONFIG_CRC16 is not set +# CONFIG_CRC_ITU_T is not set +CONFIG_CRC32=y +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_PLIST=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_DMA=y diff --git a/arch/arm/configs/imx35_3stack_defconfig b/arch/arm/configs/imx35_3stack_defconfig new file mode 100644 index 000000000000..08fcc91ad5cf --- /dev/null +++ b/arch/arm/configs/imx35_3stack_defconfig @@ -0,0 +1,1813 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.28 +# Fri Aug 21 13:59:41 2009 +# +CONFIG_ARM=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +# CONFIG_GENERIC_GPIO is not set +CONFIG_GENERIC_TIME=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_MMU=y +# CONFIG_NO_IOPORT is not set +CONFIG_GENERIC_HARDIRQS=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +# CONFIG_ARCH_HAS_ILOG2_U32 is not set +# CONFIG_ARCH_HAS_ILOG2_U64 is not set +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_ZONE_DMA=y +CONFIG_ARCH_MTD_XIP=y +CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y +CONFIG_OPROFILE_ARMV6=y +CONFIG_OPROFILE_ARM11_CORE=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_LOCK_KERNEL=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="" +CONFIG_LOCALVERSION_AUTO=y +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +# CONFIG_POSIX_MQUEUE is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set +# CONFIG_AUDIT is not set +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=14 +# CONFIG_CGROUPS is not set +# CONFIG_GROUP_SCHED is not set +CONFIG_SYSFS_DEPRECATED=y +CONFIG_SYSFS_DEPRECATED_V2=y +# CONFIG_RELAY is not set +# CONFIG_NAMESPACES is not set +# CONFIG_BLK_DEV_INITRD is not set +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_SYSCTL=y +CONFIG_EMBEDDED=y +CONFIG_UID16=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_KALLSYMS=y +# CONFIG_KALLSYMS_EXTRA_PASS is not set +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +CONFIG_COMPAT_BRK=y +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_ANON_INODES=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_AIO=y +CONFIG_VM_EVENT_COUNTERS=y +CONFIG_SLUB_DEBUG=y +# CONFIG_SLAB is not set +CONFIG_SLUB=y +# CONFIG_SLOB is not set +CONFIG_PROFILING=y +# CONFIG_MARKERS is not set +CONFIG_OPROFILE=y +CONFIG_HAVE_OPROFILE=y +# CONFIG_KPROBES is not set +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +# CONFIG_TINY_SHMEM is not set +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_KMOD=y +CONFIG_BLOCK=y +# CONFIG_LBD is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_LSF is not set +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_INTEGRITY is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_AS=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_AS is not set +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" +CONFIG_CLASSIC_RCU=y +CONFIG_FREEZER=y + +# +# System Type +# +# CONFIG_ARCH_AAEC2000 is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_VERSATILE is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_CLPS7500 is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_H720X is not set +# CONFIG_ARCH_IMX is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP23XX is not set +# CONFIG_ARCH_IXP2000 is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_KIRKWOOD is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_NS9XXX is not set +# CONFIG_ARCH_LOKI is not set +# CONFIG_ARCH_MV78XX0 is not set +CONFIG_ARCH_MXC=y +# CONFIG_ARCH_ORION5X is not set +# CONFIG_ARCH_PNX4008 is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_SA1100 is not set +# CONFIG_ARCH_S3C2410 is not set +# CONFIG_ARCH_SHARK is not set +# CONFIG_ARCH_LH7A40X is not set +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_OMAP is not set +# CONFIG_ARCH_MSM is not set +# CONFIG_ARCH_STMP3XXX is not set + +# +# Boot options +# + +# +# Power management +# + +# +# Freescale MXC Implementations +# +# CONFIG_ARCH_MX37 is not set +CONFIG_ARCH_MX35=y +# CONFIG_ARCH_MX51 is not set +# CONFIG_ARCH_MX3 is not set +# CONFIG_ARCH_MX27 is not set +# CONFIG_ARCH_MX25 is not set +CONFIG_I2C_MXC_SELECT1=y +# CONFIG_I2C_MXC_SELECT2 is not set +CONFIG_MXC_SDMA_API=y +CONFIG_SDMA_IRAM=y +CONFIG_SDMA_IRAM_SIZE=0x1000 +CONFIG_ARCH_MXC_HAS_NFC_V2=y +CONFIG_ARCH_MXC_HAS_NFC_V2_1=y +# CONFIG_I2C_MXC_SELECT3 is not set + +# +# MX35 Options +# +CONFIG_FORCE_MAX_ZONEORDER=13 +CONFIG_MX35_OPTIONS=y +CONFIG_MACH_MX35_3DS=y +# CONFIG_MACH_MX35EVB is not set +# CONFIG_MX35_DOZE_DURING_IDLE is not set + +# +# SDMA options +# + +# +# Device options +# +CONFIG_MXC_PSEUDO_IRQS=y +CONFIG_ARCH_HAS_RNGC=y +CONFIG_ARCH_HAS_EVTMON=y +CONFIG_DMA_ZONE_SIZE=24 +CONFIG_UTMI_MXC=y +# CONFIG_MXC_PWM is not set + +# +# Processor Type +# +CONFIG_CPU_32=y +# CONFIG_CPU_ARM926T is not set +CONFIG_CPU_V6=y +# CONFIG_CPU_32v6K is not set +# CONFIG_CPU_V7 is not set +CONFIG_CPU_32v6=y +CONFIG_CPU_ABRT_EV6=y +CONFIG_CPU_PABRT_NOIFAR=y +CONFIG_CPU_CACHE_V6=y +CONFIG_CPU_CACHE_VIPT=y +CONFIG_CPU_COPY_V6=y +CONFIG_CPU_TLB_V6=y +CONFIG_CPU_HAS_ASID=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y + +# +# Processor Features +# +CONFIG_ARM_THUMB=y +# CONFIG_CPU_ICACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_BPREDICT_DISABLE is not set +CONFIG_OUTER_CACHE=y +CONFIG_CACHE_L2X0=y +CONFIG_ARM_ERRATA_364296=y +CONFIG_ARM_ERRATA_411920=y + +# +# Bus support +# +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +# CONFIG_PCCARD is not set + +# +# Kernel Features +# +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +CONFIG_VMSPLIT_3G=y +# CONFIG_VMSPLIT_2G is not set +# CONFIG_VMSPLIT_1G is not set +CONFIG_PAGE_OFFSET=0xC0000000 +CONFIG_PREEMPT=y +CONFIG_HZ=100 +CONFIG_AEABI=y +# CONFIG_OABI_COMPAT is not set +CONFIG_ARCH_FLATMEM_HAS_HOLES=y +# CONFIG_ARCH_SPARSEMEM_DEFAULT is not set +# CONFIG_ARCH_SELECT_MEMORY_MODEL is not set +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_SPLIT_PTLOCK_CPUS=4 +# CONFIG_RESOURCES_64BIT is not set +# CONFIG_PHYS_ADDR_T_64BIT is not set +CONFIG_ZONE_DMA_FLAG=1 +CONFIG_BOUNCE=y +CONFIG_VIRT_TO_BUS=y +CONFIG_UNEVICTABLE_LRU=y +# CONFIG_LEDS is not set +CONFIG_ALIGNMENT_TRAP=y + +# +# Boot options +# +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="noinitrd console=ttymxc0,115200 root=/dev/mtdblock8 rw rootfstype=jffs2 ip=off" +# CONFIG_XIP_KERNEL is not set +# CONFIG_KEXEC is not set + +# +# CPU Power Management +# +# CONFIG_CPU_FREQ is not set +# CONFIG_CPU_IDLE is not set + +# +# Floating point emulation +# + +# +# At least one emulation must be selected +# +CONFIG_VFP=y + +# +# Userspace binary formats +# +CONFIG_BINFMT_ELF=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_HAVE_AOUT=y +# CONFIG_BINFMT_AOUT is not set +# CONFIG_BINFMT_MISC is not set + +# +# Power management options +# +CONFIG_PM=y +# CONFIG_PM_DEBUG is not set +CONFIG_PM_SLEEP=y +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +# CONFIG_APM_EMULATION is not set +CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +CONFIG_PACKET_MMAP=y +CONFIG_UNIX=y +CONFIG_XFRM=y +# CONFIG_XFRM_USER is not set +# CONFIG_XFRM_SUB_POLICY is not set +# CONFIG_XFRM_MIGRATE is not set +# CONFIG_XFRM_STATISTICS is not set +# CONFIG_NET_KEY is not set +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_FIB_HASH=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +# CONFIG_IP_PNP_RARP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_IP_MROUTE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +# CONFIG_INET_TUNNEL is not set +CONFIG_INET_XFRM_MODE_TRANSPORT=y +CONFIG_INET_XFRM_MODE_TUNNEL=y +CONFIG_INET_XFRM_MODE_BEET=y +# CONFIG_INET_LRO is not set +CONFIG_INET_DIAG=y +CONFIG_INET_TCP_DIAG=y +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +# CONFIG_IPV6 is not set +# CONFIG_NETWORK_SECMARK is not set +# CONFIG_NETFILTER is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_NET_DSA is not set +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_NET_SCHED is not set + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_HAMRADIO is not set +CONFIG_CAN=y +CONFIG_CAN_RAW=y +CONFIG_CAN_BCM=y + +# +# CAN Device Drivers +# +CONFIG_CAN_VCAN=y +# CONFIG_CAN_DEBUG_DEVICES is not set +CONFIG_CAN_FLEXCAN=m +# CONFIG_IRDA is not set +# CONFIG_BT is not set +# CONFIG_AF_RXRPC is not set +# CONFIG_PHONET is not set +CONFIG_WIRELESS=y +# CONFIG_CFG80211 is not set +CONFIG_WIRELESS_OLD_REGULATORY=y +CONFIG_WIRELESS_EXT=y +CONFIG_WIRELESS_EXT_SYSFS=y +# CONFIG_MAC80211 is not set +CONFIG_IEEE80211=y +# CONFIG_IEEE80211_DEBUG is not set +# CONFIG_IEEE80211_CRYPT_WEP is not set +# CONFIG_IEEE80211_CRYPT_CCMP is not set +# CONFIG_IEEE80211_CRYPT_TKIP is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +CONFIG_FIRMWARE_IN_KERNEL=y +CONFIG_EXTRA_FIRMWARE="" +# CONFIG_SYS_HYPERVISOR is not set +CONFIG_CONNECTOR=y +CONFIG_PROC_EVENTS=y +CONFIG_MTD=y +# CONFIG_MTD_DEBUG is not set +# CONFIG_MTD_CONCAT is not set +CONFIG_MTD_PARTITIONS=y +# CONFIG_MTD_REDBOOT_PARTS is not set +CONFIG_MTD_CMDLINE_PARTS=y +# CONFIG_MTD_AFS_PARTS is not set +# CONFIG_MTD_AR7_PARTS is not set + +# +# User Modules And Translation Layers +# +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLKDEVS=y +CONFIG_MTD_BLOCK=y +# CONFIG_FTL is not set +# CONFIG_NFTL is not set +# CONFIG_INFTL is not set +# CONFIG_RFD_FTL is not set +# CONFIG_SSFDC is not set +# CONFIG_MTD_OOPS is not set + +# +# RAM/ROM/Flash chip drivers +# +CONFIG_MTD_CFI=y +# CONFIG_MTD_JEDECPROBE is not set +CONFIG_MTD_GEN_PROBE=y +# CONFIG_MTD_CFI_ADV_OPTIONS is not set +CONFIG_MTD_MAP_BANK_WIDTH_1=y +CONFIG_MTD_MAP_BANK_WIDTH_2=y +CONFIG_MTD_MAP_BANK_WIDTH_4=y +# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set +CONFIG_MTD_CFI_I1=y +CONFIG_MTD_CFI_I2=y +# CONFIG_MTD_CFI_I4 is not set +# CONFIG_MTD_CFI_I8 is not set +# CONFIG_MTD_CFI_INTELEXT is not set +CONFIG_MTD_CFI_AMDSTD=y +# CONFIG_MTD_CFI_STAA is not set +CONFIG_MTD_CFI_UTIL=y +# CONFIG_MTD_RAM is not set +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_ABSENT is not set +# CONFIG_MTD_XIP is not set + +# +# Mapping drivers for chip access +# +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_PHYSMAP is not set +# CONFIG_MTD_ARM_INTEGRATOR is not set +# CONFIG_MTD_PLATRAM is not set +CONFIG_MTD_MXC=y + +# +# Self-contained MTD device drivers +# +# CONFIG_MTD_DATAFLASH is not set +# CONFIG_MTD_M25P80 is not set +# CONFIG_MTD_SLRAM is not set +# CONFIG_MTD_PHRAM is not set +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_BLOCK2MTD is not set + +# +# Disk-On-Chip Device Drivers +# +# CONFIG_MTD_DOC2000 is not set +# CONFIG_MTD_DOC2001 is not set +# CONFIG_MTD_DOC2001PLUS is not set +CONFIG_MTD_NAND=y +# CONFIG_MTD_NAND_VERIFY_WRITE is not set +# CONFIG_MTD_NAND_ECC_SMC is not set +# CONFIG_MTD_NAND_MUSEUM_IDS is not set +CONFIG_MTD_NAND_IDS=y +# CONFIG_MTD_NAND_DISKONCHIP is not set +# CONFIG_MTD_NAND_NANDSIM is not set +# CONFIG_MTD_NAND_IMX_NFC is not set +CONFIG_MTD_NAND_MXC_V2=y +# CONFIG_MTD_NAND_MXC_SWECC is not set +# CONFIG_MTD_NAND_MXC_FORCE_CE is not set +# CONFIG_MXC_NAND_LOW_LEVEL_ERASE is not set +# CONFIG_MTD_NAND_PLATFORM is not set +# CONFIG_MTD_ALAUDA is not set +# CONFIG_MTD_ONENAND is not set + +# +# UBI - Unsorted block images +# +# CONFIG_MTD_UBI is not set +# CONFIG_PARPORT is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=y +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +# CONFIG_BLK_DEV_NBD is not set +# CONFIG_BLK_DEV_UB is not set +# CONFIG_BLK_DEV_RAM is not set +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +CONFIG_MISC_DEVICES=y +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_ICS932S401 is not set +# CONFIG_ENCLOSURE_SERVICES is not set +# CONFIG_C2PORT is not set +CONFIG_HAVE_IDE=y +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_TGT is not set +# CONFIG_SCSI_NETLINK is not set +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +CONFIG_BLK_DEV_SR=y +# CONFIG_BLK_DEV_SR_VENDOR is not set +# CONFIG_CHR_DEV_SG is not set +# CONFIG_CHR_DEV_SCH is not set + +# +# Some SCSI devices (e.g. CD jukebox) support multiple LUNs +# +CONFIG_SCSI_MULTI_LUN=y +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +CONFIG_SCSI_WAIT_SCAN=m + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +CONFIG_SCSI_LOWLEVEL=y +# CONFIG_ISCSI_TCP is not set +# CONFIG_SCSI_DEBUG is not set +# CONFIG_SCSI_DH is not set +CONFIG_ATA=m +# CONFIG_ATA_NONSTANDARD is not set +# CONFIG_SATA_PMP is not set +CONFIG_ATA_SFF=y +# CONFIG_SATA_MV is not set +# CONFIG_PATA_PLATFORM is not set +CONFIG_PATA_FSL=m +# CONFIG_MD is not set +CONFIG_NETDEVICES=y +# CONFIG_DUMMY is not set +# CONFIG_BONDING is not set +# CONFIG_MACVLAN is not set +# CONFIG_EQUALIZER is not set +# CONFIG_TUN is not set +# CONFIG_VETH is not set +# CONFIG_PHYLIB is not set +CONFIG_NET_ETHERNET=y +CONFIG_MII=y +# CONFIG_AX88796 is not set +# CONFIG_SMC91X is not set +# CONFIG_DM9000 is not set +# CONFIG_ENC28J60 is not set +# CONFIG_SMC911X is not set +CONFIG_SMSC911X=y +# CONFIG_IBM_NEW_EMAC_ZMII is not set +# CONFIG_IBM_NEW_EMAC_RGMII is not set +# CONFIG_IBM_NEW_EMAC_TAH is not set +# CONFIG_IBM_NEW_EMAC_EMAC4 is not set +# CONFIG_IBM_NEW_EMAC_NO_FLOW_CTRL is not set +# CONFIG_IBM_NEW_EMAC_MAL_CLR_ICINTSTAT is not set +# CONFIG_IBM_NEW_EMAC_MAL_COMMON_ERR is not set +# CONFIG_B44 is not set +# CONFIG_CS89x0 is not set +# CONFIG_FEC is not set +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set + +# +# Wireless LAN +# +# CONFIG_WLAN_PRE80211 is not set +# CONFIG_WLAN_80211 is not set +# CONFIG_IWLWIFI_LEDS is not set + +# +# USB Network Adapters +# +# CONFIG_USB_CATC is not set +# CONFIG_USB_KAWETH is not set +# CONFIG_USB_PEGASUS is not set +# CONFIG_USB_RTL8150 is not set +CONFIG_USB_USBNET=m +# CONFIG_USB_NET_AX8817X is not set +CONFIG_USB_NET_CDCETHER=m +# CONFIG_USB_NET_DM9601 is not set +# CONFIG_USB_NET_SMSC95XX is not set +# CONFIG_USB_NET_GL620A is not set +# CONFIG_USB_NET_NET1080 is not set +# CONFIG_USB_NET_PLUSB is not set +# CONFIG_USB_NET_MCS7830 is not set +# CONFIG_USB_NET_RNDIS_HOST is not set +# CONFIG_USB_NET_CDC_SUBSET is not set +# CONFIG_USB_NET_ZAURUS is not set +# CONFIG_WAN is not set +# CONFIG_PPP is not set +# CONFIG_SLIP is not set +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_ISDN is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +CONFIG_INPUT_POLLDEV=y + +# +# Userland interfaces +# +# CONFIG_INPUT_MOUSEDEV is not set +# CONFIG_INPUT_JOYDEV is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +# CONFIG_KEYBOARD_MXC is not set +CONFIG_KEYBOARD_MC9S08DZ60=y +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TABLET is not set +CONFIG_INPUT_TOUCHSCREEN=y +# CONFIG_TOUCHSCREEN_ADS7846 is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_INEXIO is not set +# CONFIG_TOUCHSCREEN_MK712 is not set +CONFIG_TOUCHSCREEN_MXC=y +CONFIG_TOUCHSCREEN_TSC2007=y +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +# CONFIG_TOUCHSCREEN_TOUCHIT213 is not set +# CONFIG_INPUT_MISC is not set + +# +# Hardware I/O ports +# +# CONFIG_SERIO is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +# CONFIG_VT_HW_CONSOLE_BINDING is not set +CONFIG_DEVKMEM=y +# CONFIG_SERIAL_NONSTANDARD is not set +CONFIG_FM_SI4702=m +CONFIG_MXC_IIM=y + +# +# Serial drivers +# +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +CONFIG_SERIAL_MXC=y +CONFIG_SERIAL_MXC_CONSOLE=y +# CONFIG_SERIAL_IMX is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_UNIX98_PTYS=y +CONFIG_LEGACY_PTYS=y +CONFIG_LEGACY_PTY_COUNT=256 +# CONFIG_IPMI_HANDLER is not set +CONFIG_HW_RANDOM=y +# CONFIG_NVRAM is not set +# CONFIG_R3964 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_HELPER_AUTO=y + +# +# I2C Hardware Bus support +# + +# +# I2C system bus drivers (mostly embedded / system-on-chip) +# +CONFIG_I2C_MXC=y +# CONFIG_I2C_MXC_HS is not set +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_SIMTEC is not set + +# +# External I2C/SMBus adapter drivers +# +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_TINY_USB is not set + +# +# Other I2C/SMBus bus drivers +# +# CONFIG_I2C_PCA_PLATFORM is not set +# CONFIG_I2C_STUB is not set + +# +# Miscellaneous I2C Chip support +# +# CONFIG_DS1682 is not set +# CONFIG_AT24 is not set +# CONFIG_SENSORS_EEPROM is not set +# CONFIG_SENSORS_PCF8574 is not set +# CONFIG_PCF8575 is not set +# CONFIG_SENSORS_PCA9539 is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_SENSORS_MAX6875 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CHIP is not set +# CONFIG_I2C_SLAVE is not set +CONFIG_SPI=y +CONFIG_SPI_MASTER=y + +# +# SPI Master Controller Drivers +# +CONFIG_SPI_BITBANG=y +CONFIG_SPI_MXC=y +# CONFIG_SPI_MXC_TEST_LOOPBACK is not set +CONFIG_SPI_MXC_SELECT1=y +# CONFIG_SPI_MXC_SELECT2 is not set + +# +# SPI Protocol Masters +# +# CONFIG_SPI_AT25 is not set +# CONFIG_SPI_SPIDEV is not set +# CONFIG_SPI_TLE62X0 is not set +# CONFIG_W1 is not set +# CONFIG_POWER_SUPPLY is not set +# CONFIG_HWMON is not set +# CONFIG_THERMAL is not set +# CONFIG_THERMAL_HWMON is not set +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_NOWAYOUT=y + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +CONFIG_MXC_WATCHDOG=y + +# +# USB-based Watchdog Cards +# +# CONFIG_USBPCWATCHDOG is not set +CONFIG_SSB_POSSIBLE=y + +# +# Sonics Silicon Backplane +# +# CONFIG_SSB is not set + +# +# Multifunction device drivers +# +# CONFIG_MFD_CORE is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_HTC_PASIC3 is not set +# CONFIG_MFD_TMIO is not set +# CONFIG_PMIC_DA903X is not set +# CONFIG_MFD_WM8400 is not set +# CONFIG_MFD_WM8350_I2C is not set + +# +# Multimedia devices +# + +# +# Multimedia core support +# +CONFIG_VIDEO_DEV=y +CONFIG_VIDEO_V4L2_COMMON=y +CONFIG_VIDEO_ALLOW_V4L1=y +CONFIG_VIDEO_V4L1_COMPAT=y +# CONFIG_DVB_CORE is not set +CONFIG_VIDEO_MEDIA=y + +# +# Multimedia drivers +# +# CONFIG_MEDIA_ATTACH is not set +CONFIG_MEDIA_TUNER=y +# CONFIG_MEDIA_TUNER_CUSTOMIZE is not set +CONFIG_MEDIA_TUNER_SIMPLE=y +CONFIG_MEDIA_TUNER_TDA8290=y +CONFIG_MEDIA_TUNER_TDA9887=y +CONFIG_MEDIA_TUNER_TEA5761=y +CONFIG_MEDIA_TUNER_TEA5767=y +CONFIG_MEDIA_TUNER_MT20XX=y +CONFIG_MEDIA_TUNER_XC2028=y +CONFIG_MEDIA_TUNER_XC5000=y +CONFIG_VIDEO_V4L2=y +CONFIG_VIDEO_V4L1=y +CONFIG_VIDEO_CAPTURE_DRIVERS=y +# CONFIG_VIDEO_ADV_DEBUG is not set +# CONFIG_VIDEO_FIXED_MINOR_RANGES is not set +# CONFIG_VIDEO_HELPER_CHIPS_AUTO is not set + +# +# Encoders/decoders and other helper chips +# + +# +# Audio decoders +# +# CONFIG_VIDEO_TVAUDIO is not set +# CONFIG_VIDEO_TDA7432 is not set +# CONFIG_VIDEO_TDA9840 is not set +# CONFIG_VIDEO_TDA9875 is not set +# CONFIG_VIDEO_TEA6415C is not set +# CONFIG_VIDEO_TEA6420 is not set +# CONFIG_VIDEO_MSP3400 is not set +# CONFIG_VIDEO_CS5345 is not set +# CONFIG_VIDEO_CS53L32A is not set +# CONFIG_VIDEO_M52790 is not set +# CONFIG_VIDEO_TLV320AIC23B is not set +# CONFIG_VIDEO_WM8775 is not set +# CONFIG_VIDEO_WM8739 is not set +# CONFIG_VIDEO_VP27SMPX is not set + +# +# Video decoders +# +# CONFIG_VIDEO_BT819 is not set +# CONFIG_VIDEO_BT856 is not set +# CONFIG_VIDEO_BT866 is not set +# CONFIG_VIDEO_KS0127 is not set +# CONFIG_VIDEO_OV7670 is not set +# CONFIG_VIDEO_TCM825X is not set +# CONFIG_VIDEO_SAA7110 is not set +# CONFIG_VIDEO_SAA7111 is not set +# CONFIG_VIDEO_SAA7114 is not set +# CONFIG_VIDEO_SAA711X is not set +# CONFIG_VIDEO_SAA717X is not set +# CONFIG_VIDEO_SAA7191 is not set +# CONFIG_VIDEO_TVP5150 is not set +# CONFIG_VIDEO_VPX3220 is not set + +# +# Video and audio decoders +# +# CONFIG_VIDEO_CX25840 is not set + +# +# MPEG video encoders +# +# CONFIG_VIDEO_CX2341X is not set + +# +# Video encoders +# +# CONFIG_VIDEO_SAA7127 is not set +# CONFIG_VIDEO_SAA7185 is not set +# CONFIG_VIDEO_ADV7170 is not set +# CONFIG_VIDEO_ADV7175 is not set + +# +# Video improvement chips +# +# CONFIG_VIDEO_UPD64031A is not set +# CONFIG_VIDEO_UPD64083 is not set +# CONFIG_VIDEO_VIVI is not set +CONFIG_VIDEO_MXC_CAMERA=m + +# +# MXC Camera/V4L2 PRP Features support +# +CONFIG_VIDEO_MXC_IPU_CAMERA=y +# CONFIG_VIDEO_MXC_CSI_CAMERA is not set +# CONFIG_MXC_CAMERA_MC521DA is not set +# CONFIG_MXC_EMMA_CAMERA_MICRON111 is not set +# CONFIG_MXC_CAMERA_OV2640_EMMA is not set +# CONFIG_MXC_CAMERA_MICRON111 is not set +CONFIG_MXC_CAMERA_OV2640=m +# CONFIG_MXC_CAMERA_OV3640 is not set +CONFIG_MXC_TVIN_ADV7180=m +CONFIG_MXC_IPU_PRP_VF_SDC=m +CONFIG_MXC_IPU_PRP_ENC=m +CONFIG_VIDEO_MXC_OUTPUT=y +CONFIG_VIDEO_MXC_IPU_OUTPUT=y +# CONFIG_VIDEO_MXC_IPUV1_WVGA_OUTPUT is not set +# CONFIG_VIDEO_MXC_OPL is not set +# CONFIG_VIDEO_CPIA is not set +# CONFIG_VIDEO_CPIA2 is not set +# CONFIG_VIDEO_SAA5246A is not set +# CONFIG_VIDEO_SAA5249 is not set +# CONFIG_SOC_CAMERA is not set +CONFIG_V4L_USB_DRIVERS=y +# CONFIG_USB_VIDEO_CLASS is not set +CONFIG_USB_GSPCA=m +# CONFIG_USB_M5602 is not set +# CONFIG_USB_GSPCA_CONEX is not set +# CONFIG_USB_GSPCA_ETOMS is not set +# CONFIG_USB_GSPCA_FINEPIX is not set +# CONFIG_USB_GSPCA_MARS is not set +# CONFIG_USB_GSPCA_OV519 is not set +# CONFIG_USB_GSPCA_PAC207 is not set +# CONFIG_USB_GSPCA_PAC7311 is not set +# CONFIG_USB_GSPCA_SONIXB is not set +# CONFIG_USB_GSPCA_SONIXJ is not set +# CONFIG_USB_GSPCA_SPCA500 is not set +# CONFIG_USB_GSPCA_SPCA501 is not set +# CONFIG_USB_GSPCA_SPCA505 is not set +# CONFIG_USB_GSPCA_SPCA506 is not set +# CONFIG_USB_GSPCA_SPCA508 is not set +# CONFIG_USB_GSPCA_SPCA561 is not set +# CONFIG_USB_GSPCA_STK014 is not set +# CONFIG_USB_GSPCA_SUNPLUS is not set +# CONFIG_USB_GSPCA_T613 is not set +# CONFIG_USB_GSPCA_TV8532 is not set +# CONFIG_USB_GSPCA_VC032X is not set +# CONFIG_USB_GSPCA_ZC3XX is not set +# CONFIG_VIDEO_PVRUSB2 is not set +# CONFIG_VIDEO_EM28XX is not set +# CONFIG_VIDEO_USBVISION is not set +# CONFIG_USB_VICAM is not set +# CONFIG_USB_IBMCAM is not set +# CONFIG_USB_KONICAWC is not set +# CONFIG_USB_QUICKCAM_MESSENGER is not set +# CONFIG_USB_ET61X251 is not set +# CONFIG_VIDEO_OVCAMCHIP is not set +# CONFIG_USB_OV511 is not set +# CONFIG_USB_SE401 is not set +# CONFIG_USB_SN9C102 is not set +# CONFIG_USB_STV680 is not set +# CONFIG_USB_ZC0301 is not set +# CONFIG_USB_PWC is not set +# CONFIG_USB_ZR364XX is not set +# CONFIG_USB_STKWEBCAM is not set +# CONFIG_USB_S2255 is not set +# CONFIG_RADIO_ADAPTERS is not set +# CONFIG_DAB is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +# CONFIG_VIDEO_OUTPUT_CONTROL is not set +CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +# CONFIG_FB_DDC is not set +# CONFIG_FB_BOOT_VESA_SUPPORT is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_FOREIGN_ENDIAN is not set +# CONFIG_FB_SYS_FOPS is not set +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_BACKLIGHT is not set +CONFIG_FB_MODE_HELPERS=y +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# +CONFIG_FB_MXC=y +CONFIG_FB_MXC_SYNC_PANEL=y +# CONFIG_FB_MXC_EPSON_VGA_SYNC_PANEL is not set +CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL=y +# CONFIG_FB_MXC_CH7026 is not set +# CONFIG_FB_MXC_TVOUT is not set +# CONFIG_FB_MXC_TVOUT_CH7024 is not set +# CONFIG_FB_MXC_ASYNC_PANEL is not set +# CONFIG_FB_UVESA is not set +# CONFIG_FB_S1D13XXX is not set +# CONFIG_FB_VIRTUAL is not set +# CONFIG_FB_METRONOME is not set +# CONFIG_FB_MB862XX is not set +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_LCD_CLASS_DEVICE is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_CORGI is not set +CONFIG_BACKLIGHT_MXC=y +CONFIG_BACKLIGHT_MXC_IPU=y +CONFIG_BACKLIGHT_MXC_MC13892=y + +# +# Display device support +# +# CONFIG_DISPLAY_SUPPORT is not set + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE=y +# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +CONFIG_FONTS=y +# CONFIG_FONT_8x8 is not set +CONFIG_FONT_8x16=y +# CONFIG_FONT_6x11 is not set +# CONFIG_FONT_7x14 is not set +# CONFIG_FONT_PEARL_8x8 is not set +# CONFIG_FONT_ACORN_8x8 is not set +# CONFIG_FONT_MINI_4x6 is not set +# CONFIG_FONT_SUN8x16 is not set +# CONFIG_FONT_SUN12x22 is not set +# CONFIG_FONT_10x18 is not set +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_LOGO_LINUX_CLUT224=y +CONFIG_SOUND=y +CONFIG_SOUND_OSS_CORE=y +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +# CONFIG_SND_SEQUENCER is not set +CONFIG_SND_OSSEMUL=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +CONFIG_SND_PCM_OSS_PLUGINS=y +# CONFIG_SND_DYNAMIC_MINORS is not set +CONFIG_SND_SUPPORT_OLD_API=y +CONFIG_SND_VERBOSE_PROCFS=y +# CONFIG_SND_VERBOSE_PRINTK is not set +# CONFIG_SND_DEBUG is not set +CONFIG_SND_DRIVERS=y +# CONFIG_SND_DUMMY is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_MPU401 is not set +CONFIG_SND_ARM=y +CONFIG_SND_MXC_SPDIF=m +CONFIG_SND_SPI=y +CONFIG_SND_USB=y +# CONFIG_SND_USB_AUDIO is not set +# CONFIG_SND_USB_CAIAQ is not set +CONFIG_SND_SOC=y +CONFIG_SND_MXC_SOC=y +CONFIG_SND_MXC_SOC_SSI=y +CONFIG_SND_MXC_SOC_ESAI=y +CONFIG_SND_MXC_SOC_IRAM=y +CONFIG_SND_SOC_IMX_3STACK_SGTL5000=y +CONFIG_SND_SOC_IMX_3STACK_AK4647=y +CONFIG_SND_SOC_IMX_3STACK_WM8580=y +# CONFIG_SND_SOC_IMX_3STACK_AK5702 is not set +CONFIG_SND_SOC_IMX_3STACK_BLUETOOTH=y +# CONFIG_SND_SOC_ALL_CODECS is not set +CONFIG_SND_SOC_WM8580=y +CONFIG_SND_SOC_SGTL5000=y +CONFIG_SND_SOC_AK4647=y +CONFIG_SND_SOC_BLUETOOTH=y +# CONFIG_SOUND_PRIME is not set +CONFIG_HID_SUPPORT=y +CONFIG_HID=y +# CONFIG_HID_DEBUG is not set +# CONFIG_HIDRAW is not set + +# +# USB Input Devices +# +CONFIG_USB_HID=m +# CONFIG_HID_PID is not set +# CONFIG_USB_HIDDEV is not set + +# +# USB HID Boot Protocol drivers +# +# CONFIG_USB_KBD is not set +# CONFIG_USB_MOUSE is not set + +# +# Special HID drivers +# +CONFIG_HID_COMPAT=y +CONFIG_HID_A4TECH=m +CONFIG_HID_APPLE=m +CONFIG_HID_BELKIN=m +CONFIG_HID_BRIGHT=m +CONFIG_HID_CHERRY=m +CONFIG_HID_CHICONY=m +CONFIG_HID_CYPRESS=m +CONFIG_HID_DELL=m +CONFIG_HID_EZKEY=m +CONFIG_HID_GYRATION=m +CONFIG_HID_LOGITECH=m +# CONFIG_LOGITECH_FF is not set +# CONFIG_LOGIRUMBLEPAD2_FF is not set +CONFIG_HID_MICROSOFT=m +CONFIG_HID_MONTEREY=m +CONFIG_HID_PANTHERLORD=m +# CONFIG_PANTHERLORD_FF is not set +CONFIG_HID_PETALYNX=m +CONFIG_HID_SAMSUNG=m +CONFIG_HID_SONY=m +CONFIG_HID_SUNPLUS=m +# CONFIG_THRUSTMASTER_FF is not set +# CONFIG_ZEROPLUS_FF is not set +CONFIG_USB_SUPPORT=y +CONFIG_USB_ARCH_HAS_HCD=y +# CONFIG_USB_ARCH_HAS_OHCI is not set +CONFIG_USB_ARCH_HAS_EHCI=y +CONFIG_USB=y +# CONFIG_USB_DEBUG is not set +# CONFIG_USB_ANNOUNCE_NEW_DEVICES is not set + +# +# Miscellaneous USB options +# +CONFIG_USB_DEVICEFS=y +CONFIG_USB_DEVICE_CLASS=y +# CONFIG_USB_DYNAMIC_MINORS is not set +CONFIG_USB_SUSPEND=y +# CONFIG_USB_OTG is not set +# CONFIG_USB_OTG_WHITELIST is not set +# CONFIG_USB_OTG_BLACKLIST_HUB is not set +CONFIG_USB_MON=y +# CONFIG_USB_WUSB is not set +# CONFIG_USB_WUSB_CBAF is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_C67X00_HCD is not set +CONFIG_USB_EHCI_HCD=m +CONFIG_USB_EHCI_ARC=y +CONFIG_USB_EHCI_ARC_H2=y +CONFIG_USB_EHCI_ARC_OTG=y +# CONFIG_USB_STATIC_IRAM is not set +# CONFIG_USB_EHCI_FSL_MC13783 is not set +# CONFIG_USB_EHCI_FSL_1301 is not set +# CONFIG_USB_EHCI_FSL_1504 is not set +CONFIG_USB_EHCI_FSL_UTMI=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +# CONFIG_USB_ISP116X_HCD is not set +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +# CONFIG_USB_HWA_HCD is not set +# CONFIG_USB_GADGET_MUSB_HDRC is not set + +# +# USB Device Class drivers +# +# CONFIG_USB_ACM is not set +# CONFIG_USB_PRINTER is not set +# CONFIG_USB_WDM is not set +# CONFIG_USB_TMC is not set + +# +# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; +# + +# +# see USB_STORAGE Help for more information +# +CONFIG_USB_STORAGE=y +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +# CONFIG_USB_STORAGE_FREECOM is not set +# CONFIG_USB_STORAGE_ISD200 is not set +# CONFIG_USB_STORAGE_DPCM is not set +# CONFIG_USB_STORAGE_USBAT is not set +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_ONETOUCH is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set +# CONFIG_USB_LIBUSUAL is not set + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set + +# +# USB port drivers +# +# CONFIG_USB_SERIAL is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_SEVSEG is not set +# CONFIG_USB_RIO500 is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_BERRY_CHARGE is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_PHIDGET is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_USB_SISUSBVGA is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_TEST is not set +# CONFIG_USB_ISIGHTFW is not set +# CONFIG_USB_VST is not set +CONFIG_USB_GADGET=m +# CONFIG_USB_GADGET_DEBUG_FILES is not set +CONFIG_USB_GADGET_VBUS_DRAW=2 +CONFIG_USB_GADGET_SELECTED=y +# CONFIG_USB_GADGET_AT91 is not set +# CONFIG_USB_GADGET_ATMEL_USBA is not set +# CONFIG_USB_GADGET_FSL_USB2 is not set +# CONFIG_USB_GADGET_LH7A40X is not set +# CONFIG_USB_GADGET_OMAP is not set +# CONFIG_USB_GADGET_PXA25X is not set +# CONFIG_USB_GADGET_PXA27X is not set +# CONFIG_USB_GADGET_S3C2410 is not set +# CONFIG_USB_GADGET_M66592 is not set +# CONFIG_USB_GADGET_AMD5536UDC is not set +# CONFIG_USB_GADGET_FSL_QE is not set +# CONFIG_USB_GADGET_NET2280 is not set +# CONFIG_USB_GADGET_GOKU is not set +CONFIG_USB_GADGET_ARC=y +CONFIG_USB_ARC=m +# CONFIG_USB_GADGET_DUMMY_HCD is not set +CONFIG_USB_GADGET_DUALSPEED=y +CONFIG_USB_GADGET_ARC_OTG=y +# CONFIG_USB_GADGET_FSL_MC13783 is not set +# CONFIG_USB_GADGET_FSL_1301 is not set +# CONFIG_USB_GADGET_FSL_1504 is not set +CONFIG_USB_GADGET_FSL_UTMI=y +# CONFIG_USB_ZERO is not set +CONFIG_USB_ETH=m +CONFIG_USB_ETH_RNDIS=y +CONFIG_USB_GADGETFS=m +CONFIG_USB_FILE_STORAGE=m +# CONFIG_USB_FILE_STORAGE_TEST is not set +CONFIG_USB_G_SERIAL=m +# CONFIG_USB_MIDI_GADGET is not set +# CONFIG_USB_G_PRINTER is not set +# CONFIG_USB_CDC_COMPOSITE is not set +CONFIG_MMC=y +# CONFIG_MMC_DEBUG is not set +CONFIG_MMC_UNSAFE_RESUME=y + +# +# MMC/SD/SDIO Card Drivers +# +CONFIG_MMC_BLOCK=y +CONFIG_MMC_BLOCK_BOUNCE=y +# CONFIG_SDIO_UART is not set +# CONFIG_MMC_TEST is not set +CONFIG_SDIO_UNIFI_FS=m + +# +# MMC/SD/SDIO Host Controller Drivers +# +# CONFIG_MMC_SDHCI is not set +# CONFIG_MMC_SPI is not set +# CONFIG_MMC_MXC is not set +CONFIG_MMC_IMX_ESDHCI=m +# CONFIG_MMC_IMX_ESDHCI_PIO_MODE is not set +# CONFIG_MEMSTICK is not set +# CONFIG_ACCESSIBILITY is not set +# CONFIG_NEW_LEDS is not set +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set +# CONFIG_RTC_DRV_S35390A is not set +# CONFIG_RTC_DRV_FM3130 is not set +# CONFIG_RTC_DRV_RX8581 is not set + +# +# SPI RTC drivers +# +# CONFIG_RTC_DRV_M41T94 is not set +# CONFIG_RTC_DRV_DS1305 is not set +# CONFIG_RTC_DRV_DS1390 is not set +# CONFIG_RTC_DRV_MAX6902 is not set +# CONFIG_RTC_DRV_R9701 is not set +# CONFIG_RTC_DRV_RS5C348 is not set +# CONFIG_RTC_DRV_DS3234 is not set + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_BQ4802 is not set +# CONFIG_RTC_DRV_V3020 is not set + +# +# on-CPU RTC drivers +# +CONFIG_RTC_MXC=y +# CONFIG_RTC_DRV_MXC_V2 is not set +# CONFIG_RTC_DRV_IMXDI is not set +CONFIG_RTC_MC13892=y +# CONFIG_DMADEVICES is not set +CONFIG_REGULATOR=y +# CONFIG_REGULATOR_DEBUG is not set +# CONFIG_REGULATOR_FIXED_VOLTAGE is not set +# CONFIG_REGULATOR_VIRTUAL_CONSUMER is not set +# CONFIG_REGULATOR_BQ24022 is not set +CONFIG_REGULATOR_MC13892=y +CONFIG_REGULATOR_MC9S08DZ60=y +# CONFIG_UIO is not set + +# +# MXC support drivers +# +CONFIG_MXC_IPU=y +CONFIG_MXC_IPU_V1=y +CONFIG_MXC_IPU_PF=y + +# +# MXC SSI support +# +# CONFIG_MXC_SSI is not set + +# +# MXC Digital Audio Multiplexer support +# +# CONFIG_MXC_DAM is not set + +# +# MXC PMIC support +# +CONFIG_MXC_PMIC=y +# CONFIG_MXC_PMIC_MC13783 is not set +CONFIG_MXC_PMIC_MC13892=y +CONFIG_MXC_PMIC_I2C=y +# CONFIG_MXC_PMIC_SPI is not set +# CONFIG_MXC_PMIC_MC34704 is not set +CONFIG_MXC_PMIC_MC9SDZ60=y +# CONFIG_MXC_PMIC_CHARDEV is not set + +# +# MXC PMIC Client Drivers +# +CONFIG_MXC_MC13892_ADC=y +CONFIG_MXC_MC13892_RTC=y +CONFIG_MXC_MC13892_LIGHT=y +# CONFIG_MXC_MC13892_BATTERY is not set +# CONFIG_MXC_MC13892_CONNECTIVITY is not set +CONFIG_MXC_MC13892_POWER=y +CONFIG_MXC_PMIC_MC9S08DZ60=y +# CONFIG_MXC_MC9SDZ60_RTC is not set + +# +# MXC Security Drivers +# +CONFIG_MXC_SECURITY_SCC=y +# CONFIG_SCC_DEBUG is not set +CONFIG_MXC_SECURITY_RNG=y +# CONFIG_MXC_RNG_TEST_DRIVER is not set +# CONFIG_MXC_RNG_DEBUG is not set + +# +# MXC MPEG4 Encoder Kernel module support +# +# CONFIG_MXC_HMP4E is not set + +# +# MXC HARDWARE EVENT +# +# CONFIG_MXC_HWEVENT is not set + +# +# MXC VPU(Video Processing Unit) support +# + +# +# MXC Asynchronous Sample Rate Converter support +# +CONFIG_MXC_ASRC=y + +# +# MXC Bluetooth support +# +CONFIG_MXC_BLUETOOTH=m + +# +# Broadcom GPS ioctrl support +# +CONFIG_GPS_IOCTRL=m + +# +# MXC Media Local Bus Driver +# +CONFIG_MXC_MLB=m + +# +# i.MX ADC support +# +# CONFIG_IMX_ADC is not set + +# +# File systems +# +CONFIG_EXT2_FS=y +# CONFIG_EXT2_FS_XATTR is not set +# CONFIG_EXT2_FS_XIP is not set +# CONFIG_EXT3_FS is not set +# CONFIG_EXT4_FS is not set +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_FS_POSIX_ACL is not set +CONFIG_FILE_LOCKING=y +# CONFIG_XFS_FS is not set +# CONFIG_OCFS2_FS is not set +CONFIG_DNOTIFY=y +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_QUOTA is not set +# CONFIG_AUTOFS_FS is not set +CONFIG_AUTOFS4_FS=m +# CONFIG_FUSE_FS is not set + +# +# CD-ROM/DVD Filesystems +# +CONFIG_ISO9660_FS=y +CONFIG_JOLIET=y +CONFIG_ZISOFS=y +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +# CONFIG_TMPFS_POSIX_ACL is not set +# CONFIG_HUGETLB_PAGE is not set +# CONFIG_CONFIGFS_FS is not set + +# +# Miscellaneous filesystems +# +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_DEBUG=0 +CONFIG_JFFS2_FS_WRITEBUFFER=y +# CONFIG_JFFS2_FS_WBUF_VERIFY is not set +# CONFIG_JFFS2_SUMMARY is not set +# CONFIG_JFFS2_FS_XATTR is not set +# CONFIG_JFFS2_COMPRESSION_OPTIONS is not set +CONFIG_JFFS2_ZLIB=y +# CONFIG_JFFS2_LZO is not set +CONFIG_JFFS2_RTIME=y +# CONFIG_JFFS2_RUBIN is not set +CONFIG_CRAMFS=y +# CONFIG_VXFS_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +# CONFIG_NFS_V3_ACL is not set +# CONFIG_NFS_V4 is not set +CONFIG_ROOT_NFS=y +# CONFIG_NFSD is not set +CONFIG_LOCKD=y +CONFIG_LOCKD_V4=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=y +# CONFIG_SUNRPC_REGISTER_V4 is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +# CONFIG_RPCSEC_GSS_SPKM3 is not set +# CONFIG_SMB_FS is not set +# CONFIG_CIFS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +CONFIG_NLS_ASCII=m +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +CONFIG_NLS_UTF8=m +# CONFIG_DLM is not set + +# +# Kernel hacking +# +# CONFIG_PRINTK_TIME is not set +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +CONFIG_FRAME_WARN=1024 +# CONFIG_MAGIC_SYSRQ is not set +# CONFIG_UNUSED_SYMBOLS is not set +# CONFIG_DEBUG_FS is not set +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_KERNEL is not set +# CONFIG_SLUB_DEBUG_ON is not set +# CONFIG_SLUB_STATS is not set +# CONFIG_DEBUG_BUGVERBOSE is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +CONFIG_FRAME_POINTER=y +# CONFIG_RCU_CPU_STALL_DETECTOR is not set +# CONFIG_LATENCYTOP is not set +CONFIG_SYSCTL_SYSCALL_CHECK=y +CONFIG_HAVE_FUNCTION_TRACER=y + +# +# Tracers +# +# CONFIG_DYNAMIC_PRINTK_DEBUG is not set +# CONFIG_SAMPLES is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_DEBUG_USER is not set + +# +# Security options +# +# CONFIG_KEYS is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITYFS is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +# CONFIG_CRYPTO_FIPS is not set +# CONFIG_CRYPTO_MANAGER is not set +# CONFIG_CRYPTO_MANAGER2 is not set +# CONFIG_CRYPTO_GF128MUL is not set +# CONFIG_CRYPTO_NULL is not set +# CONFIG_CRYPTO_CRYPTD is not set +# CONFIG_CRYPTO_AUTHENC is not set +# CONFIG_CRYPTO_TEST is not set +# CONFIG_CRYPTO_CRYPTODEV is not set + +# +# Authenticated Encryption with Associated Data +# +# CONFIG_CRYPTO_CCM is not set +# CONFIG_CRYPTO_GCM is not set +# CONFIG_CRYPTO_SEQIV is not set + +# +# Block modes +# +# CONFIG_CRYPTO_CBC is not set +# CONFIG_CRYPTO_CTR is not set +# CONFIG_CRYPTO_CTS is not set +# CONFIG_CRYPTO_ECB is not set +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_XTS is not set + +# +# Hash modes +# +# CONFIG_CRYPTO_HMAC is not set +# CONFIG_CRYPTO_XCBC is not set + +# +# Digest +# +# CONFIG_CRYPTO_CRC32C is not set +# CONFIG_CRYPTO_MD4 is not set +# CONFIG_CRYPTO_MD5 is not set +# CONFIG_CRYPTO_MICHAEL_MIC is not set +# CONFIG_CRYPTO_RMD128 is not set +# CONFIG_CRYPTO_RMD160 is not set +# CONFIG_CRYPTO_RMD256 is not set +# CONFIG_CRYPTO_RMD320 is not set +# CONFIG_CRYPTO_SHA1 is not set +# CONFIG_CRYPTO_SHA256 is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_WP512 is not set + +# +# Ciphers +# +# CONFIG_CRYPTO_AES is not set +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_ARC4 is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +# CONFIG_CRYPTO_DES is not set +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_SALSA20 is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SERPENT is not set +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TWOFISH is not set + +# +# Compression +# +# CONFIG_CRYPTO_DEFLATE is not set +# CONFIG_CRYPTO_LZO is not set + +# +# Random Number Generation +# +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_HW=y + +# +# Library routines +# +CONFIG_BITREVERSE=y +CONFIG_CRC_CCITT=m +# CONFIG_CRC16 is not set +# CONFIG_CRC_T10DIF is not set +# CONFIG_CRC_ITU_T is not set +CONFIG_CRC32=y +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_PLIST=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_DMA=y diff --git a/arch/arm/configs/imx35evb_defconfig b/arch/arm/configs/imx35evb_defconfig new file mode 100644 index 000000000000..1e2ab3b76f24 --- /dev/null +++ b/arch/arm/configs/imx35evb_defconfig @@ -0,0 +1,976 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.24 +# +CONFIG_ARM=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +# CONFIG_GENERIC_GPIO is not set +CONFIG_GENERIC_TIME=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_MMU=y +# CONFIG_NO_IOPORT is not set +CONFIG_GENERIC_HARDIRQS=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +# CONFIG_ARCH_HAS_ILOG2_U32 is not set +# CONFIG_ARCH_HAS_ILOG2_U64 is not set +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_ZONE_DMA=y +CONFIG_ARCH_MTD_XIP=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_LOCK_KERNEL=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +# CONFIG_POSIX_MQUEUE is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set +# CONFIG_USER_NS is not set +# CONFIG_PID_NS is not set +# CONFIG_AUDIT is not set +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=14 +# CONFIG_CGROUPS is not set +CONFIG_FAIR_GROUP_SCHED=y +CONFIG_FAIR_USER_SCHED=y +# CONFIG_FAIR_CGROUP_SCHED is not set +CONFIG_SYSFS_DEPRECATED=y +# CONFIG_RELAY is not set +# CONFIG_BLK_DEV_INITRD is not set +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_SYSCTL=y +CONFIG_EMBEDDED=y +CONFIG_UID16=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_KALLSYMS=y +# CONFIG_KALLSYMS_EXTRA_PASS is not set +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_ANON_INODES=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_VM_EVENT_COUNTERS=y +CONFIG_SLAB=y +# CONFIG_SLUB is not set +# CONFIG_SLOB is not set +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +# CONFIG_TINY_SHMEM is not set +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_KMOD=y +CONFIG_BLOCK=y +# CONFIG_LBD is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_LSF is not set +# CONFIG_BLK_DEV_BSG is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_AS=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_AS is not set +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" + +# +# System Type +# +# CONFIG_ARCH_AAEC2000 is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_VERSATILE is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_CLPS7500 is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_CO285 is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_H720X is not set +# CONFIG_ARCH_IMX is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP23XX is not set +# CONFIG_ARCH_IXP2000 is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_NS9XXX is not set +CONFIG_ARCH_MXC=y +# CONFIG_ARCH_PNX4008 is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_SA1100 is not set +# CONFIG_ARCH_S3C2410 is not set +# CONFIG_ARCH_SHARK is not set +# CONFIG_ARCH_LH7A40X is not set +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_OMAP is not set + +# +# Boot options +# + +# +# Power management +# + +# +# Freescale MXC Implementations +# +# CONFIG_ARCH_MXC91321 is not set +# CONFIG_ARCH_MX37 is not set +CONFIG_ARCH_MX35=y +# CONFIG_ARCH_MX3 is not set +# CONFIG_ARCH_MX27 is not set +# CONFIG_ARCH_MX21 is not set +CONFIG_MXC_SDMA_API=y + +# +# MX35 Options +# +# CONFIG_MACH_MX35_3DS is not set +CONFIG_MACH_MX35EVB=y +# CONFIG_MX35_DOZE_DURING_IDLE is not set + +# +# Device options +# +CONFIG_ARCH_HAS_EVTMON=y +CONFIG_DMA_ZONE_SIZE=24 + +# +# Processor Type +# +CONFIG_CPU_32=y +# CONFIG_CPU_ARM926T is not set +CONFIG_CPU_V6=y +# CONFIG_CPU_32v6K is not set +CONFIG_CPU_32v6=y +CONFIG_CPU_ABRT_EV6=y +CONFIG_CPU_CACHE_V6=y +CONFIG_CPU_CACHE_VIPT=y +CONFIG_CPU_COPY_V6=y +CONFIG_CPU_TLB_V6=y +CONFIG_CPU_HAS_ASID=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y + +# +# Processor Features +# +CONFIG_ARM_THUMB=y +# CONFIG_CPU_ICACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_BPREDICT_DISABLE is not set +CONFIG_OUTER_CACHE=y +CONFIG_CACHE_L2X0=y + +# +# Bus support +# +CONFIG_ISA=y +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +CONFIG_PCCARD=m +# CONFIG_PCMCIA_DEBUG is not set +CONFIG_PCMCIA=m +CONFIG_PCMCIA_LOAD_CIS=y +# CONFIG_PCMCIA_IOCTL is not set + +# +# PC-card bridges +# +# CONFIG_I82365 is not set +# CONFIG_TCIC is not set +CONFIG_PCMCIA_PROBE=y + +# +# Kernel Features +# +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +CONFIG_PREEMPT=y +CONFIG_HZ=100 +CONFIG_AEABI=y +# CONFIG_OABI_COMPAT is not set +# CONFIG_ARCH_DISCONTIGMEM_ENABLE is not set +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +# CONFIG_SPARSEMEM_STATIC is not set +# CONFIG_SPARSEMEM_VMEMMAP_ENABLE is not set +CONFIG_SPLIT_PTLOCK_CPUS=4 +# CONFIG_RESOURCES_64BIT is not set +CONFIG_ZONE_DMA_FLAG=1 +CONFIG_BOUNCE=y +CONFIG_VIRT_TO_BUS=y +# CONFIG_LEDS is not set +CONFIG_ALIGNMENT_TRAP=y + +# +# Boot options +# +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="noinitrd console=ttymxc0,115200 root=/dev/mtdblock2 rw rootfstype=jffs2 ip=off" +# CONFIG_XIP_KERNEL is not set +# CONFIG_KEXEC is not set + +# +# Floating point emulation +# + +# +# At least one emulation must be selected +# +CONFIG_VFP=y + +# +# Userspace binary formats +# +CONFIG_BINFMT_ELF=y +# CONFIG_BINFMT_AOUT is not set +# CONFIG_BINFMT_MISC is not set + +# +# Power management options +# +# CONFIG_PM is not set +CONFIG_SUSPEND_UP_POSSIBLE=y + +# +# Networking +# +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +CONFIG_PACKET_MMAP=y +CONFIG_UNIX=y +CONFIG_XFRM=y +# CONFIG_XFRM_USER is not set +# CONFIG_XFRM_SUB_POLICY is not set +# CONFIG_XFRM_MIGRATE is not set +# CONFIG_NET_KEY is not set +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_FIB_HASH=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +# CONFIG_IP_PNP_RARP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_IP_MROUTE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +# CONFIG_INET_TUNNEL is not set +CONFIG_INET_XFRM_MODE_TRANSPORT=y +CONFIG_INET_XFRM_MODE_TUNNEL=y +CONFIG_INET_XFRM_MODE_BEET=y +# CONFIG_INET_LRO is not set +CONFIG_INET_DIAG=y +CONFIG_INET_TCP_DIAG=y +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +# CONFIG_IPV6 is not set +# CONFIG_INET6_XFRM_TUNNEL is not set +# CONFIG_INET6_TUNNEL is not set +# CONFIG_NETWORK_SECMARK is not set +# CONFIG_NETFILTER is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_NET_SCHED is not set + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_HAMRADIO is not set +# CONFIG_IRDA is not set +# CONFIG_BT is not set +# CONFIG_AF_RXRPC is not set + +# +# Wireless +# +# CONFIG_CFG80211 is not set +CONFIG_WIRELESS_EXT=y +# CONFIG_MAC80211 is not set +CONFIG_IEEE80211=y +# CONFIG_IEEE80211_DEBUG is not set +# CONFIG_IEEE80211_CRYPT_WEP is not set +# CONFIG_IEEE80211_CRYPT_CCMP is not set +# CONFIG_IEEE80211_CRYPT_TKIP is not set +# CONFIG_IEEE80211_SOFTMAC is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=m +# CONFIG_SYS_HYPERVISOR is not set +CONFIG_CONNECTOR=y +CONFIG_PROC_EVENTS=y +# CONFIG_MTD is not set + +# +# Voltage and Current regulators +# +# CONFIG_REGULATOR is not set +# CONFIG_PARPORT is not set +# CONFIG_PNP is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=y +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +# CONFIG_BLK_DEV_NBD is not set +# CONFIG_BLK_DEV_UB is not set +# CONFIG_BLK_DEV_RAM is not set +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +CONFIG_MISC_DEVICES=y +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_TGT is not set +# CONFIG_SCSI_NETLINK is not set +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +# CONFIG_BLK_DEV_SR is not set +# CONFIG_CHR_DEV_SG is not set +# CONFIG_CHR_DEV_SCH is not set + +# +# Some SCSI devices (e.g. CD jukebox) support multiple LUNs +# +CONFIG_SCSI_MULTI_LUN=y +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +CONFIG_SCSI_WAIT_SCAN=m + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +CONFIG_SCSI_LOWLEVEL=y +# CONFIG_ISCSI_TCP is not set +# CONFIG_SCSI_AHA152X is not set +# CONFIG_SCSI_AIC7XXX_OLD is not set +# CONFIG_SCSI_ADVANSYS is not set +# CONFIG_SCSI_IN2000 is not set +# CONFIG_SCSI_DTC3280 is not set +# CONFIG_SCSI_FUTURE_DOMAIN is not set +# CONFIG_SCSI_GENERIC_NCR5380 is not set +# CONFIG_SCSI_GENERIC_NCR5380_MMIO is not set +# CONFIG_SCSI_NCR53C406A is not set +# CONFIG_SCSI_PAS16 is not set +# CONFIG_SCSI_PSI240I is not set +# CONFIG_SCSI_QLOGIC_FAS is not set +# CONFIG_SCSI_SYM53C416 is not set +# CONFIG_SCSI_T128 is not set +# CONFIG_SCSI_DEBUG is not set +# CONFIG_SCSI_LOWLEVEL_PCMCIA is not set +# CONFIG_ATA is not set +# CONFIG_MD is not set +CONFIG_NETDEVICES=y +# CONFIG_NETDEVICES_MULTIQUEUE is not set +# CONFIG_DUMMY is not set +# CONFIG_BONDING is not set +# CONFIG_MACVLAN is not set +# CONFIG_EQUALIZER is not set +# CONFIG_TUN is not set +# CONFIG_VETH is not set +# CONFIG_ARCNET is not set +# CONFIG_PHYLIB is not set +CONFIG_NET_ETHERNET=y +CONFIG_MII=y +# CONFIG_AX88796 is not set +# CONFIG_NET_VENDOR_3COM is not set +# CONFIG_NET_VENDOR_SMC is not set +# CONFIG_SMC91X is not set +# CONFIG_DM9000 is not set +# CONFIG_SMC911X is not set +# CONFIG_SMSC911X is not set +# CONFIG_NET_VENDOR_RACAL is not set +# CONFIG_AT1700 is not set +# CONFIG_DEPCA is not set +# CONFIG_HP100 is not set +# CONFIG_NET_ISA is not set +# CONFIG_IBM_NEW_EMAC_ZMII is not set +# CONFIG_IBM_NEW_EMAC_RGMII is not set +# CONFIG_IBM_NEW_EMAC_TAH is not set +# CONFIG_IBM_NEW_EMAC_EMAC4 is not set +CONFIG_NET_PCI=y +# CONFIG_AC3200 is not set +# CONFIG_APRICOT is not set +# CONFIG_B44 is not set +# CONFIG_CS89x0 is not set +# CONFIG_FEC is not set +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +# CONFIG_TR is not set + +# +# Wireless LAN +# +# CONFIG_WLAN_PRE80211 is not set +# CONFIG_WLAN_80211 is not set +CONFIG_NET_PCMCIA=y +# CONFIG_PCMCIA_3C589 is not set +# CONFIG_PCMCIA_3C574 is not set +# CONFIG_PCMCIA_FMVJ18X is not set +CONFIG_PCMCIA_PCNET=m +# CONFIG_PCMCIA_NMCLAN is not set +# CONFIG_PCMCIA_SMC91C92 is not set +# CONFIG_PCMCIA_XIRC2PS is not set +# CONFIG_PCMCIA_AXNET is not set +# CONFIG_WAN is not set +CONFIG_PPP=m +# CONFIG_PPP_MULTILINK is not set +# CONFIG_PPP_FILTER is not set +CONFIG_PPP_ASYNC=m +CONFIG_PPP_SYNC_TTY=m +CONFIG_PPP_DEFLATE=m +# CONFIG_PPP_BSDCOMP is not set +# CONFIG_PPP_MPPE is not set +# CONFIG_PPPOE is not set +# CONFIG_PPPOL2TP is not set +# CONFIG_SLIP is not set +CONFIG_SLHC=m +# CONFIG_SHAPER is not set +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_ISDN is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +# CONFIG_INPUT_POLLDEV is not set + +# +# Userland interfaces +# +# CONFIG_INPUT_MOUSEDEV is not set +# CONFIG_INPUT_JOYDEV is not set +# CONFIG_INPUT_EVDEV is not set +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +# CONFIG_INPUT_KEYBOARD is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TABLET is not set +# CONFIG_INPUT_TOUCHSCREEN is not set +# CONFIG_INPUT_MISC is not set + +# +# Hardware I/O ports +# +# CONFIG_SERIO is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +# CONFIG_VT is not set +# CONFIG_SERIAL_NONSTANDARD is not set +# CONFIG_MXC_MU is not set +# CONFIG_MXC_SUPER_GEM is not set + +# +# Serial drivers +# +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +CONFIG_SERIAL_MXC=y +CONFIG_SERIAL_MXC_CONSOLE=y +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_UNIX98_PTYS=y +CONFIG_LEGACY_PTYS=y +CONFIG_LEGACY_PTY_COUNT=256 +# CONFIG_IPMI_HANDLER is not set +CONFIG_HW_RANDOM=y +# CONFIG_NVRAM is not set +# CONFIG_DTLK is not set +# CONFIG_R3964 is not set + +# +# PCMCIA character devices +# +# CONFIG_SYNCLINK_CS is not set +# CONFIG_CARDMAN_4000 is not set +# CONFIG_CARDMAN_4040 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_DEVPORT=y +# CONFIG_I2C is not set + +# +# SPI support +# +CONFIG_SPI=y +CONFIG_SPI_MASTER=y + +# +# SPI Master Controller Drivers +# +CONFIG_SPI_BITBANG=y +CONFIG_SPI_MXC=y +# CONFIG_SPI_MXC_TEST_LOOPBACK is not set +CONFIG_SPI_MXC_SELECT1=y +# CONFIG_SPI_MXC_SELECT2 is not set +# CONFIG_W1 is not set +# CONFIG_POWER_SUPPLY is not set +# CONFIG_HWMON is not set +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_NOWAYOUT=y + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +CONFIG_MXC_WATCHDOG=y + +# +# ISA-based Watchdog Cards +# +# CONFIG_PCWATCHDOG is not set +# CONFIG_MIXCOMWD is not set +# CONFIG_WDT is not set + +# +# Sonics Silicon Backplane +# +CONFIG_SSB_POSSIBLE=y +# CONFIG_SSB is not set + +# +# Multifunction device drivers +# +# CONFIG_MFD_SM501 is not set + +# +# Multimedia devices +# +# CONFIG_VIDEO_DEV is not set +# CONFIG_DVB_CORE is not set +# CONFIG_DAB is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +# CONFIG_VIDEO_OUTPUT_CONTROL is not set +# CONFIG_FB is not set +# CONFIG_BACKLIGHT_LCD_SUPPORT is not set + +# +# Display device support +# +# CONFIG_DISPLAY_SUPPORT is not set + +# +# Sound +# +# CONFIG_SOUND is not set +CONFIG_HID_SUPPORT=y +# CONFIG_HID is not set +CONFIG_USB_SUPPORT=y +CONFIG_USB_ARCH_HAS_HCD=y +# CONFIG_USB_ARCH_HAS_OHCI is not set +# CONFIG_USB_ARCH_HAS_EHCI is not set +# CONFIG_USB is not set + +# +# NOTE: USB_STORAGE enables SCSI, and 'SCSI disk support' +# + +# +# USB Gadget Support +# +# CONFIG_USB_GADGET is not set + +# +# On-The-Go and USB Peripheral Support +# +# CONFIG_OTG is not set + +# +# +# + +# +# +# +# CONFIG_MMC is not set +# CONFIG_NEW_LEDS is not set +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# SPI RTC drivers +# + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_V3020 is not set + +# +# on-CPU RTC drivers +# +CONFIG_RTC_MXC=y +# CONFIG_RTC_DRV_MXC_V2 is not set + +# +# MXC support drivers +# +# CONFIG_MXC_IPU is not set + +# +# MXC SSI support +# +# CONFIG_MXC_SSI is not set + +# +# MXC Digital Audio Multiplexer support +# +# CONFIG_MXC_DAM is not set + +# +# MXC PMIC support +# +# CONFIG_MXC_PMIC is not set + +# +# Advanced Power Management devices +# + +# +# MXC Security Drivers +# +# CONFIG_MXC_SECURITY_SCC is not set +# CONFIG_MXC_SECURITY_RNG is not set +# CONFIG_MXC_SECURITY_RTIC is not set + +# +# MXC MPEG4 Encoder Kernel module support +# +# CONFIG_MXC_HMP4E is not set + +# +# MXC HARDWARE EVENT +# +CONFIG_MXC_HWEVENT=y + +# +# MXC VPU(Video Processing Unit) support +# + +# +# File systems +# +CONFIG_EXT2_FS=y +# CONFIG_EXT2_FS_XATTR is not set +# CONFIG_EXT2_FS_XIP is not set +# CONFIG_EXT3_FS is not set +# CONFIG_EXT4DEV_FS is not set +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_FS_POSIX_ACL is not set +# CONFIG_XFS_FS is not set +# CONFIG_GFS2_FS is not set +# CONFIG_OCFS2_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_ROMFS_FS is not set +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_QUOTA is not set +CONFIG_DNOTIFY=y +# CONFIG_AUTOFS_FS is not set +CONFIG_AUTOFS4_FS=m +# CONFIG_FUSE_FS is not set + +# +# CD-ROM/DVD Filesystems +# +# CONFIG_ISO9660_FS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +# CONFIG_TMPFS_POSIX_ACL is not set +# CONFIG_HUGETLB_PAGE is not set +# CONFIG_CONFIGFS_FS is not set + +# +# Miscellaneous filesystems +# +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_DEBUG=0 +CONFIG_JFFS2_FS_WRITEBUFFER=y +# CONFIG_JFFS2_FS_WBUF_VERIFY is not set +# CONFIG_JFFS2_SUMMARY is not set +# CONFIG_JFFS2_FS_XATTR is not set +# CONFIG_JFFS2_COMPRESSION_OPTIONS is not set +CONFIG_JFFS2_ZLIB=y +# CONFIG_JFFS2_LZO is not set +CONFIG_JFFS2_RTIME=y +# CONFIG_JFFS2_RUBIN is not set +CONFIG_CRAMFS=y +# CONFIG_VXFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +# CONFIG_NFS_V3_ACL is not set +# CONFIG_NFS_V4 is not set +# CONFIG_NFS_DIRECTIO is not set +# CONFIG_NFSD is not set +CONFIG_ROOT_NFS=y +CONFIG_LOCKD=y +CONFIG_LOCKD_V4=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=y +# CONFIG_SUNRPC_BIND34 is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +# CONFIG_RPCSEC_GSS_SPKM3 is not set +# CONFIG_SMB_FS is not set +# CONFIG_CIFS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +CONFIG_NLS_ASCII=m +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +CONFIG_NLS_UTF8=m +# CONFIG_DLM is not set +CONFIG_INSTRUMENTATION=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=y +CONFIG_OPROFILE_ARMV6=y +CONFIG_OPROFILE_ARM11_CORE=y +CONFIG_OPROFILE_ARM11_EVTMON=y +# CONFIG_MARKERS is not set + +# +# Kernel hacking +# +# CONFIG_PRINTK_TIME is not set +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +# CONFIG_MAGIC_SYSRQ is not set +# CONFIG_UNUSED_SYMBOLS is not set +# CONFIG_DEBUG_FS is not set +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_KERNEL is not set +# CONFIG_DEBUG_BUGVERBOSE is not set +CONFIG_FRAME_POINTER=y +# CONFIG_SAMPLES is not set +# CONFIG_DEBUG_USER is not set + +# +# Security options +# +# CONFIG_KEYS is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +# CONFIG_CRYPTO is not set + +# +# Library routines +# +CONFIG_BITREVERSE=y +CONFIG_CRC_CCITT=m +# CONFIG_CRC16 is not set +# CONFIG_CRC_ITU_T is not set +CONFIG_CRC32=y +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_PLIST=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_DMA=y diff --git a/arch/arm/configs/imx37_3stack_defconfig b/arch/arm/configs/imx37_3stack_defconfig new file mode 100644 index 000000000000..2d01ad697a57 --- /dev/null +++ b/arch/arm/configs/imx37_3stack_defconfig @@ -0,0 +1,1828 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.28 +# Wed May 27 17:22:50 2009 +# +CONFIG_ARM=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +# CONFIG_GENERIC_GPIO is not set +CONFIG_GENERIC_TIME=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_MMU=y +# CONFIG_NO_IOPORT is not set +CONFIG_GENERIC_HARDIRQS=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +# CONFIG_ARCH_HAS_ILOG2_U32 is not set +# CONFIG_ARCH_HAS_ILOG2_U64 is not set +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_ZONE_DMA=y +CONFIG_ARCH_MTD_XIP=y +CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_LOCK_KERNEL=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="" +CONFIG_LOCALVERSION_AUTO=y +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +# CONFIG_POSIX_MQUEUE is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set +# CONFIG_AUDIT is not set +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=14 +# CONFIG_CGROUPS is not set +# CONFIG_GROUP_SCHED is not set +CONFIG_SYSFS_DEPRECATED=y +CONFIG_SYSFS_DEPRECATED_V2=y +# CONFIG_RELAY is not set +# CONFIG_NAMESPACES is not set +# CONFIG_BLK_DEV_INITRD is not set +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_SYSCTL=y +CONFIG_EMBEDDED=y +CONFIG_UID16=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_KALLSYMS=y +# CONFIG_KALLSYMS_EXTRA_PASS is not set +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +CONFIG_COMPAT_BRK=y +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_ANON_INODES=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_AIO=y +CONFIG_VM_EVENT_COUNTERS=y +CONFIG_SLUB_DEBUG=y +# CONFIG_SLAB is not set +CONFIG_SLUB=y +# CONFIG_SLOB is not set +# CONFIG_PROFILING is not set +# CONFIG_MARKERS is not set +CONFIG_HAVE_OPROFILE=y +# CONFIG_KPROBES is not set +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +# CONFIG_TINY_SHMEM is not set +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_KMOD=y +CONFIG_BLOCK=y +# CONFIG_LBD is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_LSF is not set +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_INTEGRITY is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_AS=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_AS is not set +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" +CONFIG_CLASSIC_RCU=y +CONFIG_FREEZER=y + +# +# System Type +# +# CONFIG_ARCH_AAEC2000 is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_VERSATILE is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_CLPS7500 is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_H720X is not set +# CONFIG_ARCH_IMX is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP23XX is not set +# CONFIG_ARCH_IXP2000 is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_KIRKWOOD is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_NS9XXX is not set +# CONFIG_ARCH_LOKI is not set +# CONFIG_ARCH_MV78XX0 is not set +CONFIG_ARCH_MXC=y +# CONFIG_ARCH_ORION5X is not set +# CONFIG_ARCH_PNX4008 is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_SA1100 is not set +# CONFIG_ARCH_S3C2410 is not set +# CONFIG_ARCH_SHARK is not set +# CONFIG_ARCH_LH7A40X is not set +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_OMAP is not set +# CONFIG_ARCH_MSM is not set +# CONFIG_ARCH_STMP3XXX is not set + +# +# Boot options +# + +# +# Power management +# + +# +# Freescale MXC Implementations +# +CONFIG_ARCH_MX37=y +# CONFIG_ARCH_MX35 is not set +# CONFIG_ARCH_MX51 is not set +# CONFIG_ARCH_MX3 is not set +# CONFIG_ARCH_MX27 is not set +# CONFIG_ARCH_MX25 is not set +CONFIG_I2C_MXC_SELECT1=y +CONFIG_I2C_MXC_SELECT2=y +CONFIG_MXC_SDMA_API=y +# CONFIG_I2C_MXC_SELECT3 is not set +CONFIG_SDMA_IRAM=y +CONFIG_SDMA_IRAM_SIZE=0x1000 + +# +# MX37 Options +# +CONFIG_MX37_OPTIONS=y +CONFIG_MACH_MX37_3DS=y + +# +# SDMA options +# +CONFIG_ARCH_MXC_HAS_NFC_V3=y +CONFIG_ARCH_MXC_HAS_NFC_V3_1=y + +# +# Device options +# +CONFIG_MXC_TZIC=y +CONFIG_ARCH_HAS_RNGC=y +CONFIG_ARCH_HAS_EVTMON=y +CONFIG_DMA_ZONE_SIZE=32 +CONFIG_UTMI_MXC=y + +# +# Processor Type +# +CONFIG_CPU_32=y +# CONFIG_CPU_ARM926T is not set +CONFIG_CPU_V6=y +CONFIG_CPU_32v6K=y +# CONFIG_CPU_V7 is not set +CONFIG_CPU_32v6=y +CONFIG_CPU_ABRT_EV6=y +CONFIG_CPU_PABRT_NOIFAR=y +CONFIG_CPU_CACHE_V6=y +CONFIG_CPU_CACHE_VIPT=y +CONFIG_CPU_COPY_V6=y +CONFIG_CPU_TLB_V6=y +CONFIG_CPU_HAS_ASID=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y + +# +# Processor Features +# +CONFIG_ARM_THUMB=y +# CONFIG_CPU_ICACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_BPREDICT_DISABLE is not set +CONFIG_OUTER_CACHE=y +CONFIG_CACHE_L2X0=y +# CONFIG_ARM_ERRATA_364296 is not set +CONFIG_ARM_ERRATA_411920=y + +# +# Bus support +# +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +# CONFIG_PCCARD is not set + +# +# Kernel Features +# +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +CONFIG_VMSPLIT_3G=y +# CONFIG_VMSPLIT_2G is not set +# CONFIG_VMSPLIT_1G is not set +CONFIG_PAGE_OFFSET=0xC0000000 +CONFIG_PREEMPT=y +CONFIG_HZ=100 +CONFIG_AEABI=y +# CONFIG_OABI_COMPAT is not set +CONFIG_ARCH_FLATMEM_HAS_HOLES=y +# CONFIG_ARCH_SPARSEMEM_DEFAULT is not set +# CONFIG_ARCH_SELECT_MEMORY_MODEL is not set +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_SPLIT_PTLOCK_CPUS=4 +# CONFIG_RESOURCES_64BIT is not set +# CONFIG_PHYS_ADDR_T_64BIT is not set +CONFIG_ZONE_DMA_FLAG=1 +CONFIG_BOUNCE=y +CONFIG_VIRT_TO_BUS=y +CONFIG_UNEVICTABLE_LRU=y +# CONFIG_LEDS is not set +CONFIG_ALIGNMENT_TRAP=y + +# +# Boot options +# +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="noinitrd console=ttymxc0,115200 root=/dev/mtdblock2 rw rootfstype=jffs2 ip=off" +# CONFIG_XIP_KERNEL is not set +# CONFIG_KEXEC is not set + +# +# CPU Power Management +# +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_TABLE=y +# CONFIG_CPU_FREQ_DEBUG is not set +CONFIG_CPU_FREQ_STAT=y +# CONFIG_CPU_FREQ_STAT_DETAILS is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set +CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +# CONFIG_CPU_FREQ_GOV_ONDEMAND is not set +# CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set +CONFIG_CPU_FREQ_IMX=y +# CONFIG_CPU_IDLE is not set + +# +# Floating point emulation +# + +# +# At least one emulation must be selected +# +CONFIG_VFP=y + +# +# Userspace binary formats +# +CONFIG_BINFMT_ELF=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_HAVE_AOUT=y +# CONFIG_BINFMT_AOUT is not set +# CONFIG_BINFMT_MISC is not set + +# +# Power management options +# +CONFIG_PM=y +# CONFIG_PM_DEBUG is not set +CONFIG_PM_SLEEP=y +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +# CONFIG_APM_EMULATION is not set +CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +CONFIG_PACKET_MMAP=y +CONFIG_UNIX=y +CONFIG_XFRM=y +# CONFIG_XFRM_USER is not set +# CONFIG_XFRM_SUB_POLICY is not set +# CONFIG_XFRM_MIGRATE is not set +# CONFIG_XFRM_STATISTICS is not set +# CONFIG_NET_KEY is not set +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_FIB_HASH=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +# CONFIG_IP_PNP_RARP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_IP_MROUTE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +# CONFIG_INET_TUNNEL is not set +CONFIG_INET_XFRM_MODE_TRANSPORT=y +CONFIG_INET_XFRM_MODE_TUNNEL=y +CONFIG_INET_XFRM_MODE_BEET=y +# CONFIG_INET_LRO is not set +CONFIG_INET_DIAG=y +CONFIG_INET_TCP_DIAG=y +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +# CONFIG_IPV6 is not set +# CONFIG_NETWORK_SECMARK is not set +# CONFIG_NETFILTER is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_NET_DSA is not set +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_NET_SCHED is not set + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_HAMRADIO is not set +# CONFIG_CAN is not set +# CONFIG_IRDA is not set +# CONFIG_BT is not set +# CONFIG_AF_RXRPC is not set +# CONFIG_PHONET is not set +CONFIG_WIRELESS=y +# CONFIG_CFG80211 is not set +CONFIG_WIRELESS_OLD_REGULATORY=y +CONFIG_WIRELESS_EXT=y +CONFIG_WIRELESS_EXT_SYSFS=y +# CONFIG_MAC80211 is not set +CONFIG_IEEE80211=y +# CONFIG_IEEE80211_DEBUG is not set +# CONFIG_IEEE80211_CRYPT_WEP is not set +# CONFIG_IEEE80211_CRYPT_CCMP is not set +# CONFIG_IEEE80211_CRYPT_TKIP is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +CONFIG_FIRMWARE_IN_KERNEL=y +CONFIG_EXTRA_FIRMWARE="" +# CONFIG_SYS_HYPERVISOR is not set +CONFIG_CONNECTOR=y +CONFIG_PROC_EVENTS=y +CONFIG_MTD=y +# CONFIG_MTD_DEBUG is not set +# CONFIG_MTD_CONCAT is not set +CONFIG_MTD_PARTITIONS=y +# CONFIG_MTD_REDBOOT_PARTS is not set +CONFIG_MTD_CMDLINE_PARTS=y +# CONFIG_MTD_AFS_PARTS is not set +# CONFIG_MTD_AR7_PARTS is not set + +# +# User Modules And Translation Layers +# +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLKDEVS=y +CONFIG_MTD_BLOCK=y +# CONFIG_FTL is not set +# CONFIG_NFTL is not set +# CONFIG_INFTL is not set +# CONFIG_RFD_FTL is not set +# CONFIG_SSFDC is not set +# CONFIG_MTD_OOPS is not set + +# +# RAM/ROM/Flash chip drivers +# +# CONFIG_MTD_CFI is not set +# CONFIG_MTD_JEDECPROBE is not set +CONFIG_MTD_MAP_BANK_WIDTH_1=y +CONFIG_MTD_MAP_BANK_WIDTH_2=y +CONFIG_MTD_MAP_BANK_WIDTH_4=y +# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set +CONFIG_MTD_CFI_I1=y +CONFIG_MTD_CFI_I2=y +# CONFIG_MTD_CFI_I4 is not set +# CONFIG_MTD_CFI_I8 is not set +# CONFIG_MTD_RAM is not set +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_ABSENT is not set + +# +# Mapping drivers for chip access +# +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_PLATRAM is not set +# CONFIG_MTD_MXC is not set + +# +# Self-contained MTD device drivers +# +# CONFIG_MTD_DATAFLASH is not set +# CONFIG_MTD_M25P80 is not set +# CONFIG_MTD_SLRAM is not set +# CONFIG_MTD_PHRAM is not set +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_BLOCK2MTD is not set + +# +# Disk-On-Chip Device Drivers +# +# CONFIG_MTD_DOC2000 is not set +# CONFIG_MTD_DOC2001 is not set +# CONFIG_MTD_DOC2001PLUS is not set +CONFIG_MTD_NAND=y +# CONFIG_MTD_NAND_VERIFY_WRITE is not set +# CONFIG_MTD_NAND_ECC_SMC is not set +# CONFIG_MTD_NAND_MUSEUM_IDS is not set +CONFIG_MTD_NAND_IDS=y +# CONFIG_MTD_NAND_DISKONCHIP is not set +# CONFIG_MTD_NAND_NANDSIM is not set +CONFIG_MTD_NAND_MXC_V3=y +# CONFIG_MTD_NAND_MXC_SWECC is not set +# CONFIG_MTD_NAND_MXC_FORCE_CE is not set +# CONFIG_MXC_NAND_LOW_LEVEL_ERASE is not set +# CONFIG_MTD_NAND_PLATFORM is not set +# CONFIG_MTD_ALAUDA is not set +# CONFIG_MTD_ONENAND is not set + +# +# UBI - Unsorted block images +# +# CONFIG_MTD_UBI is not set +# CONFIG_PARPORT is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=y +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +# CONFIG_BLK_DEV_NBD is not set +# CONFIG_BLK_DEV_UB is not set +# CONFIG_BLK_DEV_RAM is not set +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +CONFIG_MISC_DEVICES=y +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_ICS932S401 is not set +# CONFIG_ENCLOSURE_SERVICES is not set +# CONFIG_C2PORT is not set +CONFIG_HAVE_IDE=y +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_TGT is not set +# CONFIG_SCSI_NETLINK is not set +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +CONFIG_BLK_DEV_SR=y +# CONFIG_BLK_DEV_SR_VENDOR is not set +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=y + +# +# Some SCSI devices (e.g. CD jukebox) support multiple LUNs +# +CONFIG_SCSI_MULTI_LUN=y +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +CONFIG_SCSI_WAIT_SCAN=m + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +CONFIG_SCSI_LOWLEVEL=y +# CONFIG_ISCSI_TCP is not set +# CONFIG_SCSI_DEBUG is not set +# CONFIG_SCSI_DH is not set +CONFIG_ATA=m +# CONFIG_ATA_NONSTANDARD is not set +# CONFIG_SATA_PMP is not set +CONFIG_ATA_SFF=y +# CONFIG_SATA_MV is not set +# CONFIG_PATA_PLATFORM is not set +CONFIG_PATA_FSL=m +# CONFIG_MD is not set +CONFIG_NETDEVICES=y +# CONFIG_DUMMY is not set +# CONFIG_BONDING is not set +# CONFIG_MACVLAN is not set +# CONFIG_EQUALIZER is not set +# CONFIG_TUN is not set +# CONFIG_VETH is not set +# CONFIG_PHYLIB is not set +CONFIG_NET_ETHERNET=y +CONFIG_MII=y +# CONFIG_AX88796 is not set +# CONFIG_SMC91X is not set +# CONFIG_DM9000 is not set +# CONFIG_ENC28J60 is not set +# CONFIG_SMC911X is not set +CONFIG_SMSC911X=y +# CONFIG_IBM_NEW_EMAC_ZMII is not set +# CONFIG_IBM_NEW_EMAC_RGMII is not set +# CONFIG_IBM_NEW_EMAC_TAH is not set +# CONFIG_IBM_NEW_EMAC_EMAC4 is not set +# CONFIG_IBM_NEW_EMAC_NO_FLOW_CTRL is not set +# CONFIG_IBM_NEW_EMAC_MAL_CLR_ICINTSTAT is not set +# CONFIG_IBM_NEW_EMAC_MAL_COMMON_ERR is not set +# CONFIG_B44 is not set +# CONFIG_CS89x0 is not set +# CONFIG_FEC is not set +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set + +# +# Wireless LAN +# +# CONFIG_WLAN_PRE80211 is not set +# CONFIG_WLAN_80211 is not set +# CONFIG_IWLWIFI_LEDS is not set + +# +# USB Network Adapters +# +# CONFIG_USB_CATC is not set +# CONFIG_USB_KAWETH is not set +# CONFIG_USB_PEGASUS is not set +# CONFIG_USB_RTL8150 is not set +CONFIG_USB_USBNET=m +# CONFIG_USB_NET_AX8817X is not set +CONFIG_USB_NET_CDCETHER=m +# CONFIG_USB_NET_DM9601 is not set +# CONFIG_USB_NET_SMSC95XX is not set +# CONFIG_USB_NET_GL620A is not set +# CONFIG_USB_NET_NET1080 is not set +# CONFIG_USB_NET_PLUSB is not set +# CONFIG_USB_NET_MCS7830 is not set +# CONFIG_USB_NET_RNDIS_HOST is not set +# CONFIG_USB_NET_CDC_SUBSET is not set +# CONFIG_USB_NET_ZAURUS is not set +# CONFIG_WAN is not set +# CONFIG_PPP is not set +# CONFIG_SLIP is not set +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_ISDN is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +CONFIG_INPUT_POLLDEV=y + +# +# Userland interfaces +# +# CONFIG_INPUT_MOUSEDEV is not set +# CONFIG_INPUT_JOYDEV is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +# CONFIG_KEYBOARD_MXC is not set +CONFIG_KEYBOARD_MPR084=y +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TABLET is not set +CONFIG_INPUT_TOUCHSCREEN=y +# CONFIG_TOUCHSCREEN_ADS7846 is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_INEXIO is not set +# CONFIG_TOUCHSCREEN_MK712 is not set +CONFIG_TOUCHSCREEN_TSC2007=y +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +# CONFIG_TOUCHSCREEN_TOUCHIT213 is not set +# CONFIG_INPUT_MISC is not set + +# +# Hardware I/O ports +# +# CONFIG_SERIO is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +# CONFIG_VT_HW_CONSOLE_BINDING is not set +CONFIG_DEVKMEM=y +# CONFIG_SERIAL_NONSTANDARD is not set +# CONFIG_FM_SI4702 is not set +CONFIG_MXC_IIM=y + +# +# Serial drivers +# +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +CONFIG_SERIAL_MXC=y +CONFIG_SERIAL_MXC_CONSOLE=y +# CONFIG_SERIAL_IMX is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_UNIX98_PTYS=y +CONFIG_LEGACY_PTYS=y +CONFIG_LEGACY_PTY_COUNT=256 +# CONFIG_IPMI_HANDLER is not set +CONFIG_HW_RANDOM=y +# CONFIG_HW_RANDOM_FSL_RNGC is not set +# CONFIG_NVRAM is not set +# CONFIG_R3964 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_HELPER_AUTO=y + +# +# I2C Hardware Bus support +# + +# +# I2C system bus drivers (mostly embedded / system-on-chip) +# +CONFIG_I2C_MXC=y +# CONFIG_I2C_MXC_HS is not set +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_SIMTEC is not set + +# +# External I2C/SMBus adapter drivers +# +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_TINY_USB is not set + +# +# Other I2C/SMBus bus drivers +# +# CONFIG_I2C_PCA_PLATFORM is not set +# CONFIG_I2C_STUB is not set + +# +# Miscellaneous I2C Chip support +# +# CONFIG_DS1682 is not set +# CONFIG_AT24 is not set +# CONFIG_SENSORS_EEPROM is not set +# CONFIG_SENSORS_PCF8574 is not set +# CONFIG_PCF8575 is not set +# CONFIG_SENSORS_PCA9539 is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_SENSORS_MAX6875 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CHIP is not set +# CONFIG_I2C_SLAVE is not set +CONFIG_SPI=y +CONFIG_SPI_MASTER=y + +# +# SPI Master Controller Drivers +# +CONFIG_SPI_BITBANG=y +CONFIG_SPI_MXC=y +# CONFIG_SPI_MXC_TEST_LOOPBACK is not set +# CONFIG_SPI_MXC_SELECT1 is not set +CONFIG_SPI_MXC_SELECT2=y +# CONFIG_SPI_MXC_SELECT3 is not set + +# +# SPI Protocol Masters +# +# CONFIG_SPI_AT25 is not set +# CONFIG_SPI_SPIDEV is not set +# CONFIG_SPI_TLE62X0 is not set +# CONFIG_W1 is not set +# CONFIG_POWER_SUPPLY is not set +CONFIG_HWMON=y +# CONFIG_HWMON_VID is not set +# CONFIG_SENSORS_AD7414 is not set +# CONFIG_SENSORS_AD7418 is not set +# CONFIG_SENSORS_ADCXX is not set +# CONFIG_SENSORS_ADM1021 is not set +# CONFIG_SENSORS_ADM1025 is not set +# CONFIG_SENSORS_ADM1026 is not set +# CONFIG_SENSORS_ADM1029 is not set +# CONFIG_SENSORS_ADM1031 is not set +# CONFIG_SENSORS_ADM9240 is not set +# CONFIG_SENSORS_ADT7462 is not set +# CONFIG_SENSORS_ADT7470 is not set +# CONFIG_SENSORS_ADT7473 is not set +# CONFIG_SENSORS_ATXP1 is not set +# CONFIG_SENSORS_DS1621 is not set +# CONFIG_SENSORS_F71805F is not set +# CONFIG_SENSORS_F71882FG is not set +# CONFIG_SENSORS_F75375S is not set +# CONFIG_SENSORS_GL518SM is not set +# CONFIG_SENSORS_GL520SM is not set +# CONFIG_SENSORS_IT87 is not set +# CONFIG_SENSORS_LM63 is not set +# CONFIG_SENSORS_LM70 is not set +# CONFIG_SENSORS_LM75 is not set +# CONFIG_SENSORS_LM77 is not set +# CONFIG_SENSORS_LM78 is not set +# CONFIG_SENSORS_LM80 is not set +# CONFIG_SENSORS_LM83 is not set +# CONFIG_SENSORS_LM85 is not set +# CONFIG_SENSORS_LM87 is not set +# CONFIG_SENSORS_LM90 is not set +# CONFIG_SENSORS_LM92 is not set +# CONFIG_SENSORS_LM93 is not set +# CONFIG_SENSORS_MAX1111 is not set +# CONFIG_SENSORS_MAX1619 is not set +# CONFIG_SENSORS_MAX6650 is not set +# CONFIG_SENSORS_PC87360 is not set +# CONFIG_SENSORS_PC87427 is not set +# CONFIG_SENSORS_DME1737 is not set +# CONFIG_SENSORS_SMSC47M1 is not set +# CONFIG_SENSORS_SMSC47M192 is not set +# CONFIG_SENSORS_SMSC47B397 is not set +# CONFIG_SENSORS_ADS7828 is not set +# CONFIG_SENSORS_THMC50 is not set +# CONFIG_SENSORS_VT1211 is not set +# CONFIG_SENSORS_W83781D is not set +# CONFIG_SENSORS_W83791D is not set +# CONFIG_SENSORS_W83792D is not set +# CONFIG_SENSORS_W83793 is not set +# CONFIG_SENSORS_W83L785TS is not set +# CONFIG_SENSORS_W83L786NG is not set +# CONFIG_SENSORS_W83627HF is not set +# CONFIG_SENSORS_W83627EHF is not set +# CONFIG_HWMON_DEBUG_CHIP is not set +CONFIG_SENSORS_ISL29003=y +# CONFIG_THERMAL is not set +# CONFIG_THERMAL_HWMON is not set +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_NOWAYOUT=y + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +CONFIG_MXC_WATCHDOG=y + +# +# USB-based Watchdog Cards +# +# CONFIG_USBPCWATCHDOG is not set +CONFIG_SSB_POSSIBLE=y + +# +# Sonics Silicon Backplane +# +# CONFIG_SSB is not set + +# +# Multifunction device drivers +# +# CONFIG_MFD_CORE is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_HTC_PASIC3 is not set +# CONFIG_MFD_TMIO is not set +# CONFIG_PMIC_DA903X is not set +# CONFIG_MFD_WM8400 is not set +CONFIG_MFD_WM8350=y +CONFIG_MFD_WM8350_CONFIG_MODE_0=y +CONFIG_MFD_WM8350_I2C=y + +# +# Multimedia devices +# + +# +# Multimedia core support +# +CONFIG_VIDEO_DEV=y +CONFIG_VIDEO_V4L2_COMMON=y +CONFIG_VIDEO_ALLOW_V4L1=y +CONFIG_VIDEO_V4L1_COMPAT=y +# CONFIG_DVB_CORE is not set +CONFIG_VIDEO_MEDIA=y + +# +# Multimedia drivers +# +# CONFIG_MEDIA_ATTACH is not set +CONFIG_MEDIA_TUNER=y +# CONFIG_MEDIA_TUNER_CUSTOMIZE is not set +CONFIG_MEDIA_TUNER_SIMPLE=y +CONFIG_MEDIA_TUNER_TDA8290=y +CONFIG_MEDIA_TUNER_TDA9887=y +CONFIG_MEDIA_TUNER_TEA5761=y +CONFIG_MEDIA_TUNER_TEA5767=y +CONFIG_MEDIA_TUNER_MT20XX=y +CONFIG_MEDIA_TUNER_XC2028=y +CONFIG_MEDIA_TUNER_XC5000=y +CONFIG_VIDEO_V4L2=y +CONFIG_VIDEO_V4L1=y +CONFIG_VIDEO_CAPTURE_DRIVERS=y +# CONFIG_VIDEO_ADV_DEBUG is not set +# CONFIG_VIDEO_FIXED_MINOR_RANGES is not set +# CONFIG_VIDEO_HELPER_CHIPS_AUTO is not set + +# +# Encoders/decoders and other helper chips +# + +# +# Audio decoders +# +# CONFIG_VIDEO_TVAUDIO is not set +# CONFIG_VIDEO_TDA7432 is not set +# CONFIG_VIDEO_TDA9840 is not set +# CONFIG_VIDEO_TDA9875 is not set +# CONFIG_VIDEO_TEA6415C is not set +# CONFIG_VIDEO_TEA6420 is not set +# CONFIG_VIDEO_MSP3400 is not set +# CONFIG_VIDEO_CS5345 is not set +# CONFIG_VIDEO_CS53L32A is not set +# CONFIG_VIDEO_M52790 is not set +# CONFIG_VIDEO_TLV320AIC23B is not set +# CONFIG_VIDEO_WM8775 is not set +# CONFIG_VIDEO_WM8739 is not set +# CONFIG_VIDEO_VP27SMPX is not set + +# +# Video decoders +# +# CONFIG_VIDEO_BT819 is not set +# CONFIG_VIDEO_BT856 is not set +# CONFIG_VIDEO_BT866 is not set +# CONFIG_VIDEO_KS0127 is not set +# CONFIG_VIDEO_OV7670 is not set +# CONFIG_VIDEO_TCM825X is not set +# CONFIG_VIDEO_SAA7110 is not set +# CONFIG_VIDEO_SAA7111 is not set +# CONFIG_VIDEO_SAA7114 is not set +# CONFIG_VIDEO_SAA711X is not set +# CONFIG_VIDEO_SAA717X is not set +# CONFIG_VIDEO_SAA7191 is not set +# CONFIG_VIDEO_TVP5150 is not set +# CONFIG_VIDEO_VPX3220 is not set + +# +# Video and audio decoders +# +# CONFIG_VIDEO_CX25840 is not set + +# +# MPEG video encoders +# +# CONFIG_VIDEO_CX2341X is not set + +# +# Video encoders +# +# CONFIG_VIDEO_SAA7127 is not set +# CONFIG_VIDEO_SAA7185 is not set +# CONFIG_VIDEO_ADV7170 is not set +# CONFIG_VIDEO_ADV7175 is not set + +# +# Video improvement chips +# +# CONFIG_VIDEO_UPD64031A is not set +# CONFIG_VIDEO_UPD64083 is not set +# CONFIG_VIDEO_VIVI is not set +# CONFIG_VIDEO_MXC_CAMERA is not set +CONFIG_VIDEO_MXC_OUTPUT=y +CONFIG_VIDEO_MXC_IPU_OUTPUT=y +# CONFIG_VIDEO_MXC_IPUV1_WVGA_OUTPUT is not set +# CONFIG_VIDEO_MXC_OPL is not set +# CONFIG_VIDEO_CPIA is not set +# CONFIG_VIDEO_CPIA2 is not set +# CONFIG_VIDEO_SAA5246A is not set +# CONFIG_VIDEO_SAA5249 is not set +# CONFIG_SOC_CAMERA is not set +CONFIG_V4L_USB_DRIVERS=y +# CONFIG_USB_VIDEO_CLASS is not set +CONFIG_USB_GSPCA=m +# CONFIG_USB_M5602 is not set +# CONFIG_USB_GSPCA_CONEX is not set +# CONFIG_USB_GSPCA_ETOMS is not set +# CONFIG_USB_GSPCA_FINEPIX is not set +# CONFIG_USB_GSPCA_MARS is not set +# CONFIG_USB_GSPCA_OV519 is not set +# CONFIG_USB_GSPCA_PAC207 is not set +# CONFIG_USB_GSPCA_PAC7311 is not set +# CONFIG_USB_GSPCA_SONIXB is not set +# CONFIG_USB_GSPCA_SONIXJ is not set +# CONFIG_USB_GSPCA_SPCA500 is not set +# CONFIG_USB_GSPCA_SPCA501 is not set +# CONFIG_USB_GSPCA_SPCA505 is not set +# CONFIG_USB_GSPCA_SPCA506 is not set +# CONFIG_USB_GSPCA_SPCA508 is not set +# CONFIG_USB_GSPCA_SPCA561 is not set +# CONFIG_USB_GSPCA_STK014 is not set +# CONFIG_USB_GSPCA_SUNPLUS is not set +# CONFIG_USB_GSPCA_T613 is not set +# CONFIG_USB_GSPCA_TV8532 is not set +# CONFIG_USB_GSPCA_VC032X is not set +# CONFIG_USB_GSPCA_ZC3XX is not set +# CONFIG_VIDEO_PVRUSB2 is not set +# CONFIG_VIDEO_EM28XX is not set +# CONFIG_VIDEO_USBVISION is not set +# CONFIG_USB_VICAM is not set +# CONFIG_USB_IBMCAM is not set +# CONFIG_USB_KONICAWC is not set +# CONFIG_USB_QUICKCAM_MESSENGER is not set +# CONFIG_USB_ET61X251 is not set +# CONFIG_VIDEO_OVCAMCHIP is not set +# CONFIG_USB_OV511 is not set +# CONFIG_USB_SE401 is not set +# CONFIG_USB_SN9C102 is not set +# CONFIG_USB_STV680 is not set +# CONFIG_USB_ZC0301 is not set +# CONFIG_USB_PWC is not set +# CONFIG_USB_ZR364XX is not set +# CONFIG_USB_STKWEBCAM is not set +# CONFIG_USB_S2255 is not set +# CONFIG_RADIO_ADAPTERS is not set +# CONFIG_DAB is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +# CONFIG_VIDEO_OUTPUT_CONTROL is not set +CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +# CONFIG_FB_DDC is not set +# CONFIG_FB_BOOT_VESA_SUPPORT is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_FOREIGN_ENDIAN is not set +# CONFIG_FB_SYS_FOPS is not set +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_MODE_HELPERS is not set +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# +CONFIG_FB_MXC=y +CONFIG_FB_MXC_SYNC_PANEL=y +CONFIG_FB_MXC_EPSON_VGA_SYNC_PANEL=y +CONFIG_FB_MXC_TVOUT_TVE=y +# CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL is not set +# CONFIG_FB_MXC_CH7026 is not set +# CONFIG_FB_MXC_TVOUT is not set +# CONFIG_FB_MXC_TVOUT_CH7024 is not set +# CONFIG_FB_MXC_ASYNC_PANEL is not set +# CONFIG_FB_UVESA is not set +# CONFIG_FB_S1D13XXX is not set +# CONFIG_FB_VIRTUAL is not set +# CONFIG_FB_METRONOME is not set +# CONFIG_FB_MB862XX is not set +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_LCD_CLASS_DEVICE is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_CORGI is not set +CONFIG_BACKLIGHT_MXC=y +CONFIG_BACKLIGHT_WM8350=y + +# +# Display device support +# +# CONFIG_DISPLAY_SUPPORT is not set + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE=y +# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +CONFIG_FONTS=y +# CONFIG_FONT_8x8 is not set +CONFIG_FONT_8x16=y +# CONFIG_FONT_6x11 is not set +# CONFIG_FONT_7x14 is not set +# CONFIG_FONT_PEARL_8x8 is not set +# CONFIG_FONT_ACORN_8x8 is not set +# CONFIG_FONT_MINI_4x6 is not set +# CONFIG_FONT_SUN8x16 is not set +# CONFIG_FONT_SUN12x22 is not set +# CONFIG_FONT_10x18 is not set +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_LOGO_LINUX_CLUT224=y +CONFIG_SOUND=y +CONFIG_SOUND_OSS_CORE=y +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +# CONFIG_SND_SEQUENCER is not set +CONFIG_SND_OSSEMUL=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +CONFIG_SND_PCM_OSS_PLUGINS=y +# CONFIG_SND_DYNAMIC_MINORS is not set +CONFIG_SND_SUPPORT_OLD_API=y +CONFIG_SND_VERBOSE_PROCFS=y +# CONFIG_SND_VERBOSE_PRINTK is not set +# CONFIG_SND_DEBUG is not set +CONFIG_SND_DRIVERS=y +# CONFIG_SND_DUMMY is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_MPU401 is not set +CONFIG_SND_ARM=y +CONFIG_SND_MXC_SPDIF=m +CONFIG_SND_SPI=y +CONFIG_SND_USB=y +# CONFIG_SND_USB_AUDIO is not set +# CONFIG_SND_USB_CAIAQ is not set +CONFIG_SND_SOC=y +CONFIG_SND_MXC_SOC=y +CONFIG_SND_MXC_SOC_SSI=y +# CONFIG_SND_MXC_SOC_IRAM is not set +CONFIG_SND_SOC_IMX_3STACK_WM8350=y +# CONFIG_SND_SOC_IMX_3STACK_SGTL5000 is not set +# CONFIG_SND_SOC_IMX_3STACK_AK4647 is not set +# CONFIG_SND_SOC_IMX_3STACK_WM8580 is not set +# CONFIG_SND_SOC_IMX_3STACK_BLUETOOTH is not set +# CONFIG_SND_SOC_ALL_CODECS is not set +CONFIG_SND_SOC_WM8350=y +# CONFIG_SOUND_PRIME is not set +CONFIG_HID_SUPPORT=y +CONFIG_HID=y +# CONFIG_HID_DEBUG is not set +# CONFIG_HIDRAW is not set + +# +# USB Input Devices +# +CONFIG_USB_HID=m +# CONFIG_HID_PID is not set +# CONFIG_USB_HIDDEV is not set + +# +# USB HID Boot Protocol drivers +# +# CONFIG_USB_KBD is not set +# CONFIG_USB_MOUSE is not set + +# +# Special HID drivers +# +CONFIG_HID_COMPAT=y +CONFIG_HID_A4TECH=m +CONFIG_HID_APPLE=m +CONFIG_HID_BELKIN=m +CONFIG_HID_BRIGHT=m +CONFIG_HID_CHERRY=m +CONFIG_HID_CHICONY=m +CONFIG_HID_CYPRESS=m +CONFIG_HID_DELL=m +CONFIG_HID_EZKEY=m +CONFIG_HID_GYRATION=m +CONFIG_HID_LOGITECH=m +# CONFIG_LOGITECH_FF is not set +# CONFIG_LOGIRUMBLEPAD2_FF is not set +CONFIG_HID_MICROSOFT=m +CONFIG_HID_MONTEREY=m +CONFIG_HID_PANTHERLORD=m +# CONFIG_PANTHERLORD_FF is not set +CONFIG_HID_PETALYNX=m +CONFIG_HID_SAMSUNG=m +CONFIG_HID_SONY=m +CONFIG_HID_SUNPLUS=m +# CONFIG_THRUSTMASTER_FF is not set +# CONFIG_ZEROPLUS_FF is not set +CONFIG_USB_SUPPORT=y +CONFIG_USB_ARCH_HAS_HCD=y +# CONFIG_USB_ARCH_HAS_OHCI is not set +CONFIG_USB_ARCH_HAS_EHCI=y +CONFIG_USB=y +# CONFIG_USB_DEBUG is not set +# CONFIG_USB_ANNOUNCE_NEW_DEVICES is not set + +# +# Miscellaneous USB options +# +CONFIG_USB_DEVICEFS=y +# CONFIG_USB_DEVICE_CLASS is not set +# CONFIG_USB_DYNAMIC_MINORS is not set +# CONFIG_USB_SUSPEND is not set +# CONFIG_USB_OTG is not set +# CONFIG_USB_OTG_WHITELIST is not set +# CONFIG_USB_OTG_BLACKLIST_HUB is not set +# CONFIG_USB_MON is not set +# CONFIG_USB_WUSB is not set +# CONFIG_USB_WUSB_CBAF is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_C67X00_HCD is not set +CONFIG_USB_EHCI_HCD=m +CONFIG_USB_EHCI_ARC=y +CONFIG_USB_EHCI_ARC_OTG=y +# CONFIG_USB_EHCI_ARC_OTG_WAKE_UP is not set +# CONFIG_USB_STATIC_IRAM is not set +# CONFIG_USB_EHCI_FSL_MC13783 is not set +# CONFIG_USB_EHCI_FSL_1301 is not set +# CONFIG_USB_EHCI_FSL_1504 is not set +CONFIG_USB_EHCI_FSL_UTMI=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +# CONFIG_USB_ISP116X_HCD is not set +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +# CONFIG_USB_HWA_HCD is not set +# CONFIG_USB_GADGET_MUSB_HDRC is not set + +# +# USB Device Class drivers +# +# CONFIG_USB_ACM is not set +# CONFIG_USB_PRINTER is not set +# CONFIG_USB_WDM is not set +# CONFIG_USB_TMC is not set + +# +# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; +# + +# +# see USB_STORAGE Help for more information +# +CONFIG_USB_STORAGE=y +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +# CONFIG_USB_STORAGE_FREECOM is not set +# CONFIG_USB_STORAGE_ISD200 is not set +# CONFIG_USB_STORAGE_DPCM is not set +# CONFIG_USB_STORAGE_USBAT is not set +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_ONETOUCH is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set +# CONFIG_USB_LIBUSUAL is not set + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set + +# +# USB port drivers +# +# CONFIG_USB_SERIAL is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_SEVSEG is not set +# CONFIG_USB_RIO500 is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_BERRY_CHARGE is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_PHIDGET is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_USB_SISUSBVGA is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_TEST is not set +# CONFIG_USB_ISIGHTFW is not set +# CONFIG_USB_VST is not set +CONFIG_USB_GADGET=m +# CONFIG_USB_GADGET_DEBUG_FILES is not set +CONFIG_USB_GADGET_VBUS_DRAW=2 +CONFIG_USB_GADGET_SELECTED=y +# CONFIG_USB_GADGET_AT91 is not set +# CONFIG_USB_GADGET_ATMEL_USBA is not set +# CONFIG_USB_GADGET_FSL_USB2 is not set +# CONFIG_USB_GADGET_LH7A40X is not set +# CONFIG_USB_GADGET_OMAP is not set +# CONFIG_USB_GADGET_PXA25X is not set +# CONFIG_USB_GADGET_PXA27X is not set +# CONFIG_USB_GADGET_S3C2410 is not set +# CONFIG_USB_GADGET_M66592 is not set +# CONFIG_USB_GADGET_AMD5536UDC is not set +# CONFIG_USB_GADGET_FSL_QE is not set +# CONFIG_USB_GADGET_NET2280 is not set +# CONFIG_USB_GADGET_GOKU is not set +CONFIG_USB_GADGET_ARC=y +# CONFIG_USB_STATIC_IRAM_PPH is not set +CONFIG_USB_ARC=m +# CONFIG_USB_GADGET_DUMMY_HCD is not set +CONFIG_USB_GADGET_DUALSPEED=y +CONFIG_USB_GADGET_ARC_OTG=y +# CONFIG_USB_GADGET_WAKE_UP is not set +# CONFIG_USB_GADGET_FSL_MC13783 is not set +# CONFIG_USB_GADGET_FSL_1301 is not set +# CONFIG_USB_GADGET_FSL_1504 is not set +CONFIG_USB_GADGET_FSL_UTMI=y +# CONFIG_USB_ZERO is not set +# CONFIG_USB_ETH is not set +# CONFIG_USB_GADGETFS is not set +CONFIG_USB_FILE_STORAGE=m +# CONFIG_USB_FILE_STORAGE_TEST is not set +# CONFIG_USB_G_SERIAL is not set +# CONFIG_USB_MIDI_GADGET is not set +# CONFIG_USB_G_PRINTER is not set +# CONFIG_USB_CDC_COMPOSITE is not set +CONFIG_MMC=y +# CONFIG_MMC_DEBUG is not set +CONFIG_MMC_UNSAFE_RESUME=y + +# +# MMC/SD/SDIO Card Drivers +# +CONFIG_MMC_BLOCK=y +CONFIG_MMC_BLOCK_BOUNCE=y +# CONFIG_SDIO_UART is not set +# CONFIG_MMC_TEST is not set +CONFIG_SDIO_UNIFI_FS=m + +# +# MMC/SD/SDIO Host Controller Drivers +# +# CONFIG_MMC_SDHCI is not set +# CONFIG_MMC_SPI is not set +# CONFIG_MMC_MXC is not set +CONFIG_MMC_IMX_ESDHCI=m +# CONFIG_MMC_IMX_ESDHCI_PIO_MODE is not set +# CONFIG_MEMSTICK is not set +# CONFIG_ACCESSIBILITY is not set +# CONFIG_NEW_LEDS is not set +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +CONFIG_RTC_INTF_DEV_UIE_EMUL=y +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set +# CONFIG_RTC_DRV_S35390A is not set +# CONFIG_RTC_DRV_FM3130 is not set +# CONFIG_RTC_DRV_RX8581 is not set + +# +# SPI RTC drivers +# +# CONFIG_RTC_DRV_M41T94 is not set +# CONFIG_RTC_DRV_DS1305 is not set +# CONFIG_RTC_DRV_DS1390 is not set +# CONFIG_RTC_DRV_MAX6902 is not set +# CONFIG_RTC_DRV_R9701 is not set +# CONFIG_RTC_DRV_RS5C348 is not set +# CONFIG_RTC_DRV_DS3234 is not set + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_BQ4802 is not set +# CONFIG_RTC_DRV_V3020 is not set +# CONFIG_RTC_DRV_WM8350 is not set + +# +# on-CPU RTC drivers +# +# CONFIG_RTC_MXC is not set +CONFIG_RTC_DRV_MXC_V2=y +# CONFIG_RTC_DRV_IMXDI is not set +# CONFIG_DMADEVICES is not set +CONFIG_REGULATOR=y +# CONFIG_REGULATOR_DEBUG is not set +# CONFIG_REGULATOR_FIXED_VOLTAGE is not set +# CONFIG_REGULATOR_VIRTUAL_CONSUMER is not set +# CONFIG_REGULATOR_BQ24022 is not set +CONFIG_REGULATOR_WM8350=y +# CONFIG_UIO is not set + +# +# MXC support drivers +# +CONFIG_MXC_IPU=y +CONFIG_MXC_IPU_V3=y +CONFIG_MXC_IPU_V3D=y + +# +# MXC SSI support +# +# CONFIG_MXC_SSI is not set + +# +# MXC Digital Audio Multiplexer support +# +# CONFIG_MXC_DAM is not set + +# +# MXC PMIC support +# +# CONFIG_MXC_PMIC_MC13783 is not set +# CONFIG_MXC_PMIC_MC13892 is not set +# CONFIG_MXC_PMIC_MC34704 is not set +# CONFIG_MXC_PMIC_MC9SDZ60 is not set +# CONFIG_MXC_PMIC_MC9S08DZ60 is not set + +# +# MXC Security Drivers +# +# CONFIG_MXC_SECURITY_SCC is not set +# CONFIG_MXC_SECURITY_SCC2 is not set +# CONFIG_MXC_SECURITY_RNG is not set + +# +# SAHARA2 Security Hardware Support +# +# CONFIG_MXC_SAHARA is not set + +# +# MXC MPEG4 Encoder Kernel module support +# +# CONFIG_MXC_HMP4E is not set + +# +# MXC HARDWARE EVENT +# +# CONFIG_MXC_HWEVENT is not set + +# +# MXC VPU(Video Processing Unit) support +# +CONFIG_MXC_VPU=y +CONFIG_MXC_VPU_IRAM=y +# CONFIG_MXC_VPU_DEBUG is not set + +# +# MXC Asynchronous Sample Rate Converter support +# + +# +# MXC Bluetooth support +# +CONFIG_MXC_BLUETOOTH=m + +# +# Broadcom GPS ioctrl support +# +CONFIG_GPS_IOCTRL=m + +# +# MXC Media Local Bus Driver +# + +# +# i.MX ADC support +# +# CONFIG_IMX_ADC is not set + +# +# File systems +# +CONFIG_EXT2_FS=y +# CONFIG_EXT2_FS_XATTR is not set +# CONFIG_EXT2_FS_XIP is not set +# CONFIG_EXT3_FS is not set +# CONFIG_EXT4_FS is not set +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_FS_POSIX_ACL is not set +CONFIG_FILE_LOCKING=y +# CONFIG_XFS_FS is not set +# CONFIG_OCFS2_FS is not set +CONFIG_DNOTIFY=y +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_QUOTA is not set +# CONFIG_AUTOFS_FS is not set +CONFIG_AUTOFS4_FS=m +# CONFIG_FUSE_FS is not set + +# +# CD-ROM/DVD Filesystems +# +# CONFIG_ISO9660_FS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +# CONFIG_TMPFS_POSIX_ACL is not set +# CONFIG_HUGETLB_PAGE is not set +# CONFIG_CONFIGFS_FS is not set + +# +# Miscellaneous filesystems +# +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_DEBUG=0 +CONFIG_JFFS2_FS_WRITEBUFFER=y +# CONFIG_JFFS2_FS_WBUF_VERIFY is not set +# CONFIG_JFFS2_SUMMARY is not set +# CONFIG_JFFS2_FS_XATTR is not set +# CONFIG_JFFS2_COMPRESSION_OPTIONS is not set +CONFIG_JFFS2_ZLIB=y +# CONFIG_JFFS2_LZO is not set +CONFIG_JFFS2_RTIME=y +# CONFIG_JFFS2_RUBIN is not set +CONFIG_CRAMFS=y +# CONFIG_VXFS_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +# CONFIG_NFS_V3_ACL is not set +# CONFIG_NFS_V4 is not set +CONFIG_ROOT_NFS=y +# CONFIG_NFSD is not set +CONFIG_LOCKD=y +CONFIG_LOCKD_V4=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=y +# CONFIG_SUNRPC_REGISTER_V4 is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +# CONFIG_RPCSEC_GSS_SPKM3 is not set +# CONFIG_SMB_FS is not set +# CONFIG_CIFS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +CONFIG_NLS_ASCII=m +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +CONFIG_NLS_UTF8=m +# CONFIG_DLM is not set + +# +# Kernel hacking +# +# CONFIG_PRINTK_TIME is not set +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +CONFIG_FRAME_WARN=1024 +# CONFIG_MAGIC_SYSRQ is not set +# CONFIG_UNUSED_SYMBOLS is not set +# CONFIG_DEBUG_FS is not set +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_KERNEL is not set +# CONFIG_SLUB_DEBUG_ON is not set +# CONFIG_SLUB_STATS is not set +# CONFIG_DEBUG_BUGVERBOSE is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +CONFIG_FRAME_POINTER=y +# CONFIG_RCU_CPU_STALL_DETECTOR is not set +# CONFIG_LATENCYTOP is not set +CONFIG_SYSCTL_SYSCALL_CHECK=y +CONFIG_HAVE_FUNCTION_TRACER=y + +# +# Tracers +# +# CONFIG_DYNAMIC_PRINTK_DEBUG is not set +# CONFIG_SAMPLES is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_DEBUG_USER is not set + +# +# Security options +# +# CONFIG_KEYS is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITYFS is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +# CONFIG_CRYPTO_FIPS is not set +# CONFIG_CRYPTO_MANAGER is not set +# CONFIG_CRYPTO_MANAGER2 is not set +# CONFIG_CRYPTO_GF128MUL is not set +# CONFIG_CRYPTO_NULL is not set +# CONFIG_CRYPTO_CRYPTD is not set +# CONFIG_CRYPTO_AUTHENC is not set +# CONFIG_CRYPTO_TEST is not set +# CONFIG_CRYPTO_CRYPTODEV is not set + +# +# Authenticated Encryption with Associated Data +# +# CONFIG_CRYPTO_CCM is not set +# CONFIG_CRYPTO_GCM is not set +# CONFIG_CRYPTO_SEQIV is not set + +# +# Block modes +# +# CONFIG_CRYPTO_CBC is not set +# CONFIG_CRYPTO_CTR is not set +# CONFIG_CRYPTO_CTS is not set +# CONFIG_CRYPTO_ECB is not set +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_XTS is not set + +# +# Hash modes +# +# CONFIG_CRYPTO_HMAC is not set +# CONFIG_CRYPTO_XCBC is not set + +# +# Digest +# +# CONFIG_CRYPTO_CRC32C is not set +# CONFIG_CRYPTO_MD4 is not set +# CONFIG_CRYPTO_MD5 is not set +# CONFIG_CRYPTO_MICHAEL_MIC is not set +# CONFIG_CRYPTO_RMD128 is not set +# CONFIG_CRYPTO_RMD160 is not set +# CONFIG_CRYPTO_RMD256 is not set +# CONFIG_CRYPTO_RMD320 is not set +# CONFIG_CRYPTO_SHA1 is not set +# CONFIG_CRYPTO_SHA256 is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_WP512 is not set + +# +# Ciphers +# +# CONFIG_CRYPTO_AES is not set +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_ARC4 is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +# CONFIG_CRYPTO_DES is not set +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_SALSA20 is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SERPENT is not set +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TWOFISH is not set + +# +# Compression +# +# CONFIG_CRYPTO_DEFLATE is not set +# CONFIG_CRYPTO_LZO is not set + +# +# Random Number Generation +# +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_HW=y + +# +# Library routines +# +CONFIG_BITREVERSE=y +CONFIG_CRC_CCITT=m +# CONFIG_CRC16 is not set +# CONFIG_CRC_T10DIF is not set +# CONFIG_CRC_ITU_T is not set +CONFIG_CRC32=y +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_PLIST=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_DMA=y diff --git a/arch/arm/configs/imx51_defconfig b/arch/arm/configs/imx51_defconfig new file mode 100644 index 000000000000..afdd73553ed6 --- /dev/null +++ b/arch/arm/configs/imx51_defconfig @@ -0,0 +1,1954 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.28 +# Thu Sep 17 16:39:00 2009 +# +CONFIG_ARM=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +CONFIG_GENERIC_GPIO=y +CONFIG_GENERIC_TIME=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_MMU=y +# CONFIG_NO_IOPORT is not set +CONFIG_GENERIC_HARDIRQS=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +# CONFIG_ARCH_HAS_ILOG2_U32 is not set +# CONFIG_ARCH_HAS_ILOG2_U64 is not set +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_ZONE_DMA=y +CONFIG_ARCH_MTD_XIP=y +CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_LOCK_KERNEL=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="" +CONFIG_LOCALVERSION_AUTO=y +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +# CONFIG_POSIX_MQUEUE is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set +# CONFIG_AUDIT is not set +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=14 +# CONFIG_CGROUPS is not set +# CONFIG_GROUP_SCHED is not set +CONFIG_SYSFS_DEPRECATED=y +CONFIG_SYSFS_DEPRECATED_V2=y +# CONFIG_RELAY is not set +# CONFIG_NAMESPACES is not set +# CONFIG_BLK_DEV_INITRD is not set +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_SYSCTL=y +CONFIG_EMBEDDED=y +CONFIG_UID16=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_KALLSYMS=y +# CONFIG_KALLSYMS_EXTRA_PASS is not set +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +CONFIG_COMPAT_BRK=y +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_ANON_INODES=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_AIO=y +CONFIG_VM_EVENT_COUNTERS=y +CONFIG_SLUB_DEBUG=y +# CONFIG_SLAB is not set +CONFIG_SLUB=y +# CONFIG_SLOB is not set +# CONFIG_PROFILING is not set +# CONFIG_MARKERS is not set +CONFIG_HAVE_OPROFILE=y +# CONFIG_KPROBES is not set +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +# CONFIG_TINY_SHMEM is not set +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_KMOD=y +CONFIG_BLOCK=y +# CONFIG_LBD is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_LSF is not set +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_INTEGRITY is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_AS=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_AS is not set +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" +CONFIG_CLASSIC_RCU=y +CONFIG_FREEZER=y + +# +# System Type +# +# CONFIG_ARCH_AAEC2000 is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_VERSATILE is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_CLPS7500 is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_H720X is not set +# CONFIG_ARCH_IMX is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP23XX is not set +# CONFIG_ARCH_IXP2000 is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_KIRKWOOD is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_NS9XXX is not set +# CONFIG_ARCH_LOKI is not set +# CONFIG_ARCH_MV78XX0 is not set +CONFIG_ARCH_MXC=y +# CONFIG_ARCH_ORION5X is not set +# CONFIG_ARCH_PNX4008 is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_SA1100 is not set +# CONFIG_ARCH_S3C2410 is not set +# CONFIG_ARCH_SHARK is not set +# CONFIG_ARCH_LH7A40X is not set +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_OMAP is not set +# CONFIG_ARCH_MSM is not set +# CONFIG_ARCH_STMP3XXX is not set + +# +# Boot options +# + +# +# Power management +# + +# +# Freescale MXC Implementations +# +# CONFIG_ARCH_MX37 is not set +# CONFIG_ARCH_MX35 is not set +CONFIG_ARCH_MX51=y +# CONFIG_ARCH_MX3 is not set +# CONFIG_ARCH_MX27 is not set +# CONFIG_ARCH_MX25 is not set +CONFIG_I2C_MXC_SELECT1=y +CONFIG_I2C_MXC_SELECT2=y +CONFIG_MXC_SDMA_API=y +CONFIG_SDMA_IRAM=y +CONFIG_SDMA_IRAM_SIZE=0x1000 +# CONFIG_I2C_MXC_SELECT3 is not set +CONFIG_FORCE_MAX_ZONEORDER=13 +CONFIG_ARCH_MXC_HAS_NFC_V3=y + +# +# MX51 Options +# +CONFIG_MX51_OPTIONS=y +CONFIG_MACH_MX51_3DS=y +CONFIG_MACH_MX51_BABBAGE=y +CONFIG_ARCH_MXC_HAS_NFC_V3_2=y + +# +# SDMA options +# + +# +# Device options +# +CONFIG_MXC_TZIC=y +CONFIG_DMA_ZONE_SIZE=64 +CONFIG_UTMI_MXC=y +# CONFIG_MXC_PWM is not set + +# +# Processor Type +# +CONFIG_CPU_32=y +# CONFIG_CPU_ARM926T is not set +# CONFIG_CPU_V6 is not set +CONFIG_CPU_32v6K=y +CONFIG_CPU_V7=y +CONFIG_CPU_32v7=y +CONFIG_CPU_ABRT_EV7=y +CONFIG_CPU_PABRT_IFAR=y +CONFIG_CPU_CACHE_V7=y +CONFIG_CPU_CACHE_VIPT=y +CONFIG_CPU_COPY_V6=y +CONFIG_CPU_TLB_V7=y +CONFIG_CPU_HAS_ASID=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y + +# +# Processor Features +# +CONFIG_ARM_THUMB=y +# CONFIG_ARM_THUMBEE is not set +# CONFIG_CPU_ICACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_BPREDICT_DISABLE is not set +CONFIG_HAS_TLS_REG=y +# CONFIG_OUTER_CACHE is not set + +# +# Bus support +# +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +# CONFIG_PCCARD is not set + +# +# Kernel Features +# +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +CONFIG_VMSPLIT_3G=y +# CONFIG_VMSPLIT_2G is not set +# CONFIG_VMSPLIT_1G is not set +CONFIG_PAGE_OFFSET=0xC0000000 +CONFIG_PREEMPT=y +CONFIG_HZ=100 +CONFIG_AEABI=y +# CONFIG_OABI_COMPAT is not set +CONFIG_ARCH_FLATMEM_HAS_HOLES=y +# CONFIG_ARCH_SPARSEMEM_DEFAULT is not set +# CONFIG_ARCH_SELECT_MEMORY_MODEL is not set +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_SPLIT_PTLOCK_CPUS=4 +# CONFIG_RESOURCES_64BIT is not set +# CONFIG_PHYS_ADDR_T_64BIT is not set +CONFIG_ZONE_DMA_FLAG=1 +CONFIG_BOUNCE=y +CONFIG_VIRT_TO_BUS=y +CONFIG_UNEVICTABLE_LRU=y +# CONFIG_LEDS is not set +CONFIG_ALIGNMENT_TRAP=y + +# +# Boot options +# +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="noinitrd console=ttymxc0,115200 root=/dev/mtdblock2 rw rootfstype=jffs2 ip=off" +# CONFIG_XIP_KERNEL is not set +# CONFIG_KEXEC is not set + +# +# CPU Power Management +# +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_TABLE=y +# CONFIG_CPU_FREQ_DEBUG is not set +CONFIG_CPU_FREQ_STAT=y +# CONFIG_CPU_FREQ_STAT_DETAILS is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set +CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +# CONFIG_CPU_FREQ_GOV_ONDEMAND is not set +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_FREQ_IMX=y +# CONFIG_CPU_IDLE is not set + +# +# Floating point emulation +# + +# +# At least one emulation must be selected +# +CONFIG_VFP=y +CONFIG_VFPv3=y +CONFIG_NEON=y + +# +# Userspace binary formats +# +CONFIG_BINFMT_ELF=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_HAVE_AOUT=y +# CONFIG_BINFMT_AOUT is not set +# CONFIG_BINFMT_MISC is not set + +# +# Power management options +# +CONFIG_PM=y +# CONFIG_PM_DEBUG is not set +CONFIG_PM_SLEEP=y +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +CONFIG_APM_EMULATION=y +CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +CONFIG_PACKET_MMAP=y +CONFIG_UNIX=y +CONFIG_XFRM=y +# CONFIG_XFRM_USER is not set +# CONFIG_XFRM_SUB_POLICY is not set +# CONFIG_XFRM_MIGRATE is not set +# CONFIG_XFRM_STATISTICS is not set +# CONFIG_NET_KEY is not set +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_FIB_HASH=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +# CONFIG_IP_PNP_RARP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_IP_MROUTE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +# CONFIG_INET_TUNNEL is not set +CONFIG_INET_XFRM_MODE_TRANSPORT=y +CONFIG_INET_XFRM_MODE_TUNNEL=y +CONFIG_INET_XFRM_MODE_BEET=y +# CONFIG_INET_LRO is not set +CONFIG_INET_DIAG=y +CONFIG_INET_TCP_DIAG=y +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +# CONFIG_IPV6 is not set +# CONFIG_NETWORK_SECMARK is not set +# CONFIG_NETFILTER is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_NET_DSA is not set +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_NET_SCHED is not set + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_HAMRADIO is not set +# CONFIG_CAN is not set +# CONFIG_IRDA is not set +CONFIG_BT=y +CONFIG_BT_L2CAP=y +CONFIG_BT_SCO=y +CONFIG_BT_RFCOMM=y +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=y +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y + +# +# Bluetooth device drivers +# +CONFIG_BT_HCIBTUSB=y +# CONFIG_BT_HCIBTSDIO is not set +# CONFIG_BT_HCIUART is not set +# CONFIG_BT_HCIBCM203X is not set +# CONFIG_BT_HCIBPA10X is not set +# CONFIG_BT_HCIBFUSB is not set +CONFIG_BT_HCIVHCI=y +# CONFIG_AF_RXRPC is not set +# CONFIG_PHONET is not set +CONFIG_WIRELESS=y +# CONFIG_CFG80211 is not set +CONFIG_WIRELESS_OLD_REGULATORY=y +CONFIG_WIRELESS_EXT=y +CONFIG_WIRELESS_EXT_SYSFS=y +# CONFIG_MAC80211 is not set +CONFIG_IEEE80211=y +# CONFIG_IEEE80211_DEBUG is not set +# CONFIG_IEEE80211_CRYPT_WEP is not set +# CONFIG_IEEE80211_CRYPT_CCMP is not set +# CONFIG_IEEE80211_CRYPT_TKIP is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +CONFIG_FIRMWARE_IN_KERNEL=y +CONFIG_EXTRA_FIRMWARE="" +# CONFIG_SYS_HYPERVISOR is not set +CONFIG_CONNECTOR=y +CONFIG_PROC_EVENTS=y +CONFIG_MTD=y +# CONFIG_MTD_DEBUG is not set +# CONFIG_MTD_CONCAT is not set +CONFIG_MTD_PARTITIONS=y +# CONFIG_MTD_REDBOOT_PARTS is not set +CONFIG_MTD_CMDLINE_PARTS=y +# CONFIG_MTD_AFS_PARTS is not set +# CONFIG_MTD_AR7_PARTS is not set + +# +# User Modules And Translation Layers +# +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLKDEVS=y +CONFIG_MTD_BLOCK=y +# CONFIG_FTL is not set +# CONFIG_NFTL is not set +# CONFIG_INFTL is not set +# CONFIG_RFD_FTL is not set +# CONFIG_SSFDC is not set +# CONFIG_MTD_OOPS is not set + +# +# RAM/ROM/Flash chip drivers +# +# CONFIG_MTD_CFI is not set +# CONFIG_MTD_JEDECPROBE is not set +CONFIG_MTD_MAP_BANK_WIDTH_1=y +CONFIG_MTD_MAP_BANK_WIDTH_2=y +CONFIG_MTD_MAP_BANK_WIDTH_4=y +# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set +CONFIG_MTD_CFI_I1=y +CONFIG_MTD_CFI_I2=y +# CONFIG_MTD_CFI_I4 is not set +# CONFIG_MTD_CFI_I8 is not set +# CONFIG_MTD_RAM is not set +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_ABSENT is not set + +# +# Mapping drivers for chip access +# +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_PLATRAM is not set +# CONFIG_MTD_MXC is not set + +# +# Self-contained MTD device drivers +# +# CONFIG_MTD_DATAFLASH is not set +# CONFIG_MTD_M25P80 is not set +# CONFIG_MTD_SLRAM is not set +# CONFIG_MTD_PHRAM is not set +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_BLOCK2MTD is not set + +# +# Disk-On-Chip Device Drivers +# +# CONFIG_MTD_DOC2000 is not set +# CONFIG_MTD_DOC2001 is not set +# CONFIG_MTD_DOC2001PLUS is not set +CONFIG_MTD_NAND=y +# CONFIG_MTD_NAND_VERIFY_WRITE is not set +# CONFIG_MTD_NAND_ECC_SMC is not set +# CONFIG_MTD_NAND_MUSEUM_IDS is not set +# CONFIG_MTD_NAND_GPIO is not set +CONFIG_MTD_NAND_IDS=y +# CONFIG_MTD_NAND_DISKONCHIP is not set +# CONFIG_MTD_NAND_NANDSIM is not set +# CONFIG_MTD_NAND_IMX_NFC is not set +CONFIG_MTD_NAND_MXC_V3=y +# CONFIG_MTD_NAND_MXC_SWECC is not set +# CONFIG_MTD_NAND_MXC_FORCE_CE is not set +# CONFIG_MXC_NAND_LOW_LEVEL_ERASE is not set +# CONFIG_MTD_NAND_PLATFORM is not set +# CONFIG_MTD_ALAUDA is not set +# CONFIG_MTD_ONENAND is not set + +# +# UBI - Unsorted block images +# +# CONFIG_MTD_UBI is not set +# CONFIG_PARPORT is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=y +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +# CONFIG_BLK_DEV_NBD is not set +# CONFIG_BLK_DEV_UB is not set +# CONFIG_BLK_DEV_RAM is not set +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +CONFIG_MISC_DEVICES=y +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_ICS932S401 is not set +# CONFIG_ENCLOSURE_SERVICES is not set +# CONFIG_C2PORT is not set +CONFIG_HAVE_IDE=y +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_TGT is not set +# CONFIG_SCSI_NETLINK is not set +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +# CONFIG_BLK_DEV_SR is not set +# CONFIG_CHR_DEV_SG is not set +# CONFIG_CHR_DEV_SCH is not set + +# +# Some SCSI devices (e.g. CD jukebox) support multiple LUNs +# +CONFIG_SCSI_MULTI_LUN=y +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +CONFIG_SCSI_WAIT_SCAN=m + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +CONFIG_SCSI_LOWLEVEL=y +# CONFIG_ISCSI_TCP is not set +# CONFIG_SCSI_DEBUG is not set +# CONFIG_SCSI_DH is not set +CONFIG_ATA=m +# CONFIG_ATA_NONSTANDARD is not set +# CONFIG_SATA_PMP is not set +CONFIG_ATA_SFF=y +# CONFIG_SATA_MV is not set +# CONFIG_PATA_PLATFORM is not set +CONFIG_PATA_FSL=m +# CONFIG_MD is not set +CONFIG_NETDEVICES=y +# CONFIG_DUMMY is not set +# CONFIG_BONDING is not set +# CONFIG_MACVLAN is not set +# CONFIG_EQUALIZER is not set +# CONFIG_TUN is not set +# CONFIG_VETH is not set +# CONFIG_PHYLIB is not set +CONFIG_NET_ETHERNET=y +CONFIG_MII=y +# CONFIG_AX88796 is not set +# CONFIG_SMC91X is not set +# CONFIG_DM9000 is not set +# CONFIG_ENC28J60 is not set +# CONFIG_SMC911X is not set +CONFIG_SMSC911X=y +# CONFIG_IBM_NEW_EMAC_ZMII is not set +# CONFIG_IBM_NEW_EMAC_RGMII is not set +# CONFIG_IBM_NEW_EMAC_TAH is not set +# CONFIG_IBM_NEW_EMAC_EMAC4 is not set +# CONFIG_IBM_NEW_EMAC_NO_FLOW_CTRL is not set +# CONFIG_IBM_NEW_EMAC_MAL_CLR_ICINTSTAT is not set +# CONFIG_IBM_NEW_EMAC_MAL_COMMON_ERR is not set +# CONFIG_B44 is not set +# CONFIG_CS89x0 is not set +CONFIG_FEC=y +# CONFIG_FEC2 is not set +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set + +# +# Wireless LAN +# +# CONFIG_WLAN_PRE80211 is not set +# CONFIG_WLAN_80211 is not set +# CONFIG_IWLWIFI_LEDS is not set + +# +# USB Network Adapters +# +# CONFIG_USB_CATC is not set +# CONFIG_USB_KAWETH is not set +# CONFIG_USB_PEGASUS is not set +# CONFIG_USB_RTL8150 is not set +# CONFIG_USB_USBNET is not set +# CONFIG_WAN is not set +# CONFIG_PPP is not set +# CONFIG_SLIP is not set +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_ISDN is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +CONFIG_INPUT_POLLDEV=y + +# +# Userland interfaces +# +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_MOUSEDEV_PSAUX=y +CONFIG_INPUT_MOUSEDEV_SCREEN_X=1024 +CONFIG_INPUT_MOUSEDEV_SCREEN_Y=768 +# CONFIG_INPUT_JOYDEV is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_EVBUG is not set +# CONFIG_INPUT_APMPOWER is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +CONFIG_KEYBOARD_MXC=y +# CONFIG_KEYBOARD_GPIO is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TABLET is not set +CONFIG_INPUT_TOUCHSCREEN=y +# CONFIG_TOUCHSCREEN_ADS7846 is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_INEXIO is not set +# CONFIG_TOUCHSCREEN_MK712 is not set +CONFIG_TOUCHSCREEN_MXC=y +# CONFIG_TOUCHSCREEN_TSC2007 is not set +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +# CONFIG_TOUCHSCREEN_TOUCHIT213 is not set +CONFIG_INPUT_MISC=y +# CONFIG_INPUT_ATI_REMOTE is not set +# CONFIG_INPUT_ATI_REMOTE2 is not set +# CONFIG_INPUT_KEYSPAN_REMOTE is not set +# CONFIG_INPUT_POWERMATE is not set +# CONFIG_INPUT_YEALINK is not set +# CONFIG_INPUT_CM109 is not set +CONFIG_INPUT_UINPUT=y + +# +# Hardware I/O ports +# +# CONFIG_SERIO is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +# CONFIG_VT_HW_CONSOLE_BINDING is not set +CONFIG_DEVKMEM=y +# CONFIG_SERIAL_NONSTANDARD is not set +CONFIG_FM_SI4702=m +CONFIG_MXC_IIM=y +CONFIG_IMX_SIM=m + +# +# Serial drivers +# +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +CONFIG_SERIAL_MXC=y +CONFIG_SERIAL_MXC_CONSOLE=y +# CONFIG_SERIAL_IMX is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_UNIX98_PTYS=y +CONFIG_LEGACY_PTYS=y +CONFIG_LEGACY_PTY_COUNT=256 +# CONFIG_IPMI_HANDLER is not set +CONFIG_HW_RANDOM=y +# CONFIG_NVRAM is not set +# CONFIG_R3964 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_HELPER_AUTO=y + +# +# I2C Hardware Bus support +# + +# +# I2C system bus drivers (mostly embedded / system-on-chip) +# +# CONFIG_I2C_GPIO is not set +CONFIG_I2C_MXC=y +CONFIG_I2C_MXC_HS=y +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_SIMTEC is not set + +# +# External I2C/SMBus adapter drivers +# +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_TINY_USB is not set + +# +# Other I2C/SMBus bus drivers +# +# CONFIG_I2C_PCA_PLATFORM is not set +# CONFIG_I2C_STUB is not set + +# +# Miscellaneous I2C Chip support +# +# CONFIG_DS1682 is not set +# CONFIG_AT24 is not set +# CONFIG_SENSORS_EEPROM is not set +# CONFIG_SENSORS_PCF8574 is not set +# CONFIG_PCF8575 is not set +# CONFIG_SENSORS_PCA9539 is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_TPS65010 is not set +# CONFIG_SENSORS_MAX6875 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CHIP is not set +# CONFIG_I2C_SLAVE is not set +CONFIG_SPI=y +CONFIG_SPI_MASTER=y + +# +# SPI Master Controller Drivers +# +CONFIG_SPI_BITBANG=y +CONFIG_SPI_MXC=y +# CONFIG_SPI_MXC_TEST_LOOPBACK is not set +CONFIG_SPI_MXC_SELECT1=y +# CONFIG_SPI_MXC_SELECT2 is not set +# CONFIG_SPI_MXC_SELECT3 is not set + +# +# SPI Protocol Masters +# +# CONFIG_SPI_AT25 is not set +# CONFIG_SPI_SPIDEV is not set +# CONFIG_SPI_TLE62X0 is not set +CONFIG_ARCH_REQUIRE_GPIOLIB=y +CONFIG_GPIOLIB=y +# CONFIG_GPIO_SYSFS is not set + +# +# Memory mapped GPIO expanders: +# + +# +# I2C GPIO expanders: +# +# CONFIG_GPIO_MAX732X is not set +# CONFIG_GPIO_PCA953X is not set +# CONFIG_GPIO_PCF857X is not set + +# +# PCI GPIO expanders: +# + +# +# SPI GPIO expanders: +# +# CONFIG_GPIO_MAX7301 is not set +# CONFIG_GPIO_MCP23S08 is not set +CONFIG_W1=m +CONFIG_W1_CON=y + +# +# 1-wire Bus Masters +# +# CONFIG_W1_MASTER_DS2490 is not set +# CONFIG_W1_MASTER_DS2482 is not set +CONFIG_W1_MASTER_MXC=m +# CONFIG_W1_MASTER_GPIO is not set + +# +# 1-wire Slaves +# +# CONFIG_W1_SLAVE_THERM is not set +# CONFIG_W1_SLAVE_SMEM is not set +# CONFIG_W1_SLAVE_DS2751 is not set +# CONFIG_W1_SLAVE_DS2433 is not set +CONFIG_W1_SLAVE_DS2438=m +# CONFIG_W1_SLAVE_DS2760 is not set +# CONFIG_W1_SLAVE_BQ27000 is not set +CONFIG_POWER_SUPPLY=y +# CONFIG_POWER_SUPPLY_DEBUG is not set +# CONFIG_PDA_POWER is not set +CONFIG_APM_POWER=y +# CONFIG_BATTERY_DS2760 is not set +# CONFIG_BATTERY_BQ27x00 is not set +CONFIG_HWMON=y +# CONFIG_HWMON_VID is not set +# CONFIG_SENSORS_AD7414 is not set +# CONFIG_SENSORS_AD7418 is not set +# CONFIG_SENSORS_ADCXX is not set +# CONFIG_SENSORS_ADM1021 is not set +# CONFIG_SENSORS_ADM1025 is not set +# CONFIG_SENSORS_ADM1026 is not set +# CONFIG_SENSORS_ADM1029 is not set +# CONFIG_SENSORS_ADM1031 is not set +# CONFIG_SENSORS_ADM9240 is not set +# CONFIG_SENSORS_ADT7462 is not set +# CONFIG_SENSORS_ADT7470 is not set +# CONFIG_SENSORS_ADT7473 is not set +# CONFIG_SENSORS_ATXP1 is not set +# CONFIG_SENSORS_DS1621 is not set +# CONFIG_SENSORS_F71805F is not set +# CONFIG_SENSORS_F71882FG is not set +# CONFIG_SENSORS_F75375S is not set +# CONFIG_SENSORS_GL518SM is not set +# CONFIG_SENSORS_GL520SM is not set +# CONFIG_SENSORS_IT87 is not set +# CONFIG_SENSORS_LM63 is not set +# CONFIG_SENSORS_LM70 is not set +# CONFIG_SENSORS_LM75 is not set +# CONFIG_SENSORS_LM77 is not set +# CONFIG_SENSORS_LM78 is not set +# CONFIG_SENSORS_LM80 is not set +# CONFIG_SENSORS_LM83 is not set +# CONFIG_SENSORS_LM85 is not set +# CONFIG_SENSORS_LM87 is not set +# CONFIG_SENSORS_LM90 is not set +# CONFIG_SENSORS_LM92 is not set +# CONFIG_SENSORS_LM93 is not set +# CONFIG_SENSORS_MAX1111 is not set +# CONFIG_SENSORS_MAX1619 is not set +# CONFIG_SENSORS_MAX6650 is not set +# CONFIG_SENSORS_PC87360 is not set +# CONFIG_SENSORS_PC87427 is not set +# CONFIG_SENSORS_DME1737 is not set +# CONFIG_SENSORS_SMSC47M1 is not set +# CONFIG_SENSORS_SMSC47M192 is not set +# CONFIG_SENSORS_SMSC47B397 is not set +# CONFIG_SENSORS_ADS7828 is not set +# CONFIG_SENSORS_THMC50 is not set +# CONFIG_SENSORS_VT1211 is not set +# CONFIG_SENSORS_W83781D is not set +# CONFIG_SENSORS_W83791D is not set +# CONFIG_SENSORS_W83792D is not set +# CONFIG_SENSORS_W83793 is not set +# CONFIG_SENSORS_W83L785TS is not set +# CONFIG_SENSORS_W83L786NG is not set +# CONFIG_SENSORS_W83627HF is not set +# CONFIG_SENSORS_W83627EHF is not set +# CONFIG_HWMON_DEBUG_CHIP is not set +CONFIG_SENSORS_ISL29003=y +# CONFIG_THERMAL is not set +# CONFIG_THERMAL_HWMON is not set +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_NOWAYOUT=y + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +CONFIG_MXC_WATCHDOG=y + +# +# USB-based Watchdog Cards +# +# CONFIG_USBPCWATCHDOG is not set +CONFIG_SSB_POSSIBLE=y + +# +# Sonics Silicon Backplane +# +# CONFIG_SSB is not set + +# +# Multifunction device drivers +# +# CONFIG_MFD_CORE is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_MFD_ASIC3 is not set +# CONFIG_HTC_EGPIO is not set +# CONFIG_HTC_PASIC3 is not set +# CONFIG_MFD_TMIO is not set +# CONFIG_MFD_TC6393XB is not set +# CONFIG_PMIC_DA903X is not set +# CONFIG_MFD_WM8400 is not set +# CONFIG_MFD_WM8350_I2C is not set + +# +# Multimedia devices +# + +# +# Multimedia core support +# +CONFIG_VIDEO_DEV=y +CONFIG_VIDEO_V4L2_COMMON=y +CONFIG_VIDEO_ALLOW_V4L1=y +CONFIG_VIDEO_V4L1_COMPAT=y +# CONFIG_DVB_CORE is not set +CONFIG_VIDEO_MEDIA=y + +# +# Multimedia drivers +# +# CONFIG_MEDIA_ATTACH is not set +CONFIG_MEDIA_TUNER=y +CONFIG_MEDIA_TUNER_CUSTOMIZE=y +# CONFIG_MEDIA_TUNER_SIMPLE is not set +# CONFIG_MEDIA_TUNER_TDA8290 is not set +# CONFIG_MEDIA_TUNER_TDA827X is not set +# CONFIG_MEDIA_TUNER_TDA18271 is not set +# CONFIG_MEDIA_TUNER_TDA9887 is not set +# CONFIG_MEDIA_TUNER_TEA5761 is not set +# CONFIG_MEDIA_TUNER_TEA5767 is not set +# CONFIG_MEDIA_TUNER_MT20XX is not set +# CONFIG_MEDIA_TUNER_MT2060 is not set +# CONFIG_MEDIA_TUNER_MT2266 is not set +# CONFIG_MEDIA_TUNER_MT2131 is not set +# CONFIG_MEDIA_TUNER_QT1010 is not set +# CONFIG_MEDIA_TUNER_XC2028 is not set +# CONFIG_MEDIA_TUNER_XC5000 is not set +# CONFIG_MEDIA_TUNER_MXL5005S is not set +# CONFIG_MEDIA_TUNER_MXL5007T is not set +CONFIG_VIDEO_V4L2=y +CONFIG_VIDEO_V4L1=y +CONFIG_VIDEO_CAPTURE_DRIVERS=y +# CONFIG_VIDEO_ADV_DEBUG is not set +# CONFIG_VIDEO_FIXED_MINOR_RANGES is not set +# CONFIG_VIDEO_HELPER_CHIPS_AUTO is not set + +# +# Encoders/decoders and other helper chips +# + +# +# Audio decoders +# +# CONFIG_VIDEO_TVAUDIO is not set +# CONFIG_VIDEO_TDA7432 is not set +# CONFIG_VIDEO_TDA9840 is not set +# CONFIG_VIDEO_TDA9875 is not set +# CONFIG_VIDEO_TEA6415C is not set +# CONFIG_VIDEO_TEA6420 is not set +# CONFIG_VIDEO_MSP3400 is not set +# CONFIG_VIDEO_CS5345 is not set +# CONFIG_VIDEO_CS53L32A is not set +# CONFIG_VIDEO_M52790 is not set +# CONFIG_VIDEO_TLV320AIC23B is not set +# CONFIG_VIDEO_WM8775 is not set +# CONFIG_VIDEO_WM8739 is not set +# CONFIG_VIDEO_VP27SMPX is not set + +# +# Video decoders +# +# CONFIG_VIDEO_BT819 is not set +# CONFIG_VIDEO_BT856 is not set +# CONFIG_VIDEO_BT866 is not set +# CONFIG_VIDEO_KS0127 is not set +# CONFIG_VIDEO_OV7670 is not set +# CONFIG_VIDEO_TCM825X is not set +# CONFIG_VIDEO_SAA7110 is not set +# CONFIG_VIDEO_SAA7111 is not set +# CONFIG_VIDEO_SAA7114 is not set +# CONFIG_VIDEO_SAA711X is not set +# CONFIG_VIDEO_SAA717X is not set +# CONFIG_VIDEO_SAA7191 is not set +# CONFIG_VIDEO_TVP5150 is not set +# CONFIG_VIDEO_VPX3220 is not set + +# +# Video and audio decoders +# +# CONFIG_VIDEO_CX25840 is not set + +# +# MPEG video encoders +# +# CONFIG_VIDEO_CX2341X is not set + +# +# Video encoders +# +# CONFIG_VIDEO_SAA7127 is not set +# CONFIG_VIDEO_SAA7185 is not set +# CONFIG_VIDEO_ADV7170 is not set +# CONFIG_VIDEO_ADV7175 is not set + +# +# Video improvement chips +# +# CONFIG_VIDEO_UPD64031A is not set +# CONFIG_VIDEO_UPD64083 is not set +# CONFIG_VIDEO_VIVI is not set +CONFIG_VIDEO_MXC_CAMERA=m + +# +# MXC Camera/V4L2 PRP Features support +# +CONFIG_VIDEO_MXC_IPU_CAMERA=y +# CONFIG_VIDEO_MXC_CSI_CAMERA is not set +# CONFIG_MXC_CAMERA_MC521DA is not set +# CONFIG_MXC_EMMA_CAMERA_MICRON111 is not set +# CONFIG_MXC_CAMERA_OV2640_EMMA is not set +# CONFIG_MXC_CAMERA_MICRON111 is not set +# CONFIG_MXC_CAMERA_OV2640 is not set +CONFIG_MXC_CAMERA_OV3640=m +# CONFIG_MXC_TVIN_ADV7180 is not set +CONFIG_MXC_IPU_PRP_VF_SDC=m +CONFIG_MXC_IPU_PRP_ENC=m +CONFIG_MXC_IPU_CSI_ENC=m +CONFIG_VIDEO_MXC_OUTPUT=y +CONFIG_VIDEO_MXC_IPU_OUTPUT=y +# CONFIG_VIDEO_MXC_IPUV1_WVGA_OUTPUT is not set +# CONFIG_VIDEO_MXC_OPL is not set +# CONFIG_VIDEO_CPIA is not set +# CONFIG_VIDEO_CPIA2 is not set +# CONFIG_VIDEO_SAA5246A is not set +# CONFIG_VIDEO_SAA5249 is not set +# CONFIG_SOC_CAMERA is not set +CONFIG_V4L_USB_DRIVERS=y +# CONFIG_USB_VIDEO_CLASS is not set +CONFIG_USB_GSPCA=m +# CONFIG_USB_M5602 is not set +# CONFIG_USB_GSPCA_CONEX is not set +# CONFIG_USB_GSPCA_ETOMS is not set +# CONFIG_USB_GSPCA_FINEPIX is not set +# CONFIG_USB_GSPCA_MARS is not set +# CONFIG_USB_GSPCA_OV519 is not set +# CONFIG_USB_GSPCA_PAC207 is not set +# CONFIG_USB_GSPCA_PAC7311 is not set +# CONFIG_USB_GSPCA_SONIXB is not set +# CONFIG_USB_GSPCA_SONIXJ is not set +# CONFIG_USB_GSPCA_SPCA500 is not set +# CONFIG_USB_GSPCA_SPCA501 is not set +# CONFIG_USB_GSPCA_SPCA505 is not set +# CONFIG_USB_GSPCA_SPCA506 is not set +# CONFIG_USB_GSPCA_SPCA508 is not set +# CONFIG_USB_GSPCA_SPCA561 is not set +# CONFIG_USB_GSPCA_STK014 is not set +# CONFIG_USB_GSPCA_SUNPLUS is not set +# CONFIG_USB_GSPCA_T613 is not set +# CONFIG_USB_GSPCA_TV8532 is not set +# CONFIG_USB_GSPCA_VC032X is not set +# CONFIG_USB_GSPCA_ZC3XX is not set +# CONFIG_VIDEO_PVRUSB2 is not set +# CONFIG_VIDEO_EM28XX is not set +# CONFIG_VIDEO_USBVISION is not set +# CONFIG_USB_VICAM is not set +# CONFIG_USB_IBMCAM is not set +# CONFIG_USB_KONICAWC is not set +# CONFIG_USB_QUICKCAM_MESSENGER is not set +# CONFIG_USB_ET61X251 is not set +# CONFIG_VIDEO_OVCAMCHIP is not set +# CONFIG_USB_OV511 is not set +# CONFIG_USB_SE401 is not set +# CONFIG_USB_SN9C102 is not set +# CONFIG_USB_STV680 is not set +# CONFIG_USB_ZC0301 is not set +# CONFIG_USB_PWC is not set +# CONFIG_USB_ZR364XX is not set +# CONFIG_USB_STKWEBCAM is not set +# CONFIG_USB_S2255 is not set +# CONFIG_RADIO_ADAPTERS is not set +# CONFIG_DAB is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +# CONFIG_VIDEO_OUTPUT_CONTROL is not set +CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +# CONFIG_FB_DDC is not set +# CONFIG_FB_BOOT_VESA_SUPPORT is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_FOREIGN_ENDIAN is not set +# CONFIG_FB_SYS_FOPS is not set +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_BACKLIGHT is not set +CONFIG_FB_MODE_HELPERS=y +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# +CONFIG_FB_MXC=y +CONFIG_FB_MXC_SYNC_PANEL=y +CONFIG_FB_MXC_EPSON_VGA_SYNC_PANEL=y +CONFIG_FB_MXC_TVOUT_TVE=y +# CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL is not set +CONFIG_FB_MXC_CH7026=y +# CONFIG_FB_MXC_TVOUT is not set +# CONFIG_FB_MXC_TVOUT_CH7024 is not set +# CONFIG_FB_MXC_ASYNC_PANEL is not set +# CONFIG_FB_UVESA is not set +# CONFIG_FB_S1D13XXX is not set +# CONFIG_FB_VIRTUAL is not set +# CONFIG_FB_METRONOME is not set +# CONFIG_FB_MB862XX is not set +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_LCD_CLASS_DEVICE is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_CORGI is not set +CONFIG_BACKLIGHT_MXC=y +CONFIG_BACKLIGHT_MXC_MC13892=y + +# +# Display device support +# +# CONFIG_DISPLAY_SUPPORT is not set + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE=y +# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +CONFIG_FONTS=y +# CONFIG_FONT_8x8 is not set +CONFIG_FONT_8x16=y +# CONFIG_FONT_6x11 is not set +# CONFIG_FONT_7x14 is not set +# CONFIG_FONT_PEARL_8x8 is not set +# CONFIG_FONT_ACORN_8x8 is not set +# CONFIG_FONT_MINI_4x6 is not set +# CONFIG_FONT_SUN8x16 is not set +# CONFIG_FONT_SUN12x22 is not set +# CONFIG_FONT_10x18 is not set +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_LOGO_LINUX_CLUT224=y +CONFIG_SOUND=y +CONFIG_SOUND_OSS_CORE=y +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +# CONFIG_SND_SEQUENCER is not set +CONFIG_SND_OSSEMUL=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +CONFIG_SND_PCM_OSS_PLUGINS=y +# CONFIG_SND_DYNAMIC_MINORS is not set +CONFIG_SND_SUPPORT_OLD_API=y +CONFIG_SND_VERBOSE_PROCFS=y +# CONFIG_SND_VERBOSE_PRINTK is not set +# CONFIG_SND_DEBUG is not set +CONFIG_SND_DRIVERS=y +# CONFIG_SND_DUMMY is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_MPU401 is not set +CONFIG_SND_ARM=y +CONFIG_SND_MXC_SPDIF=m +CONFIG_SND_SPI=y +CONFIG_SND_USB=y +# CONFIG_SND_USB_AUDIO is not set +# CONFIG_SND_USB_CAIAQ is not set +CONFIG_SND_SOC=y +CONFIG_SND_MXC_SOC=y +CONFIG_SND_MXC_SOC_SSI=y +CONFIG_SND_MXC_SOC_IRAM=y +CONFIG_SND_SOC_IMX_3STACK_SGTL5000=y +# CONFIG_SND_SOC_IMX_3STACK_AK4647 is not set +# CONFIG_SND_SOC_IMX_3STACK_WM8580 is not set +# CONFIG_SND_SOC_IMX_3STACK_AK5702 is not set +# CONFIG_SND_SOC_IMX_3STACK_BLUETOOTH is not set +# CONFIG_SND_SOC_ALL_CODECS is not set +CONFIG_SND_SOC_SGTL5000=y +# CONFIG_SOUND_PRIME is not set +CONFIG_HID_SUPPORT=y +CONFIG_HID=y +# CONFIG_HID_DEBUG is not set +# CONFIG_HIDRAW is not set + +# +# USB Input Devices +# +CONFIG_USB_HID=y +# CONFIG_HID_PID is not set +# CONFIG_USB_HIDDEV is not set + +# +# Special HID drivers +# +CONFIG_HID_COMPAT=y +CONFIG_HID_A4TECH=m +CONFIG_HID_APPLE=m +CONFIG_HID_BELKIN=m +CONFIG_HID_BRIGHT=m +CONFIG_HID_CHERRY=m +CONFIG_HID_CHICONY=m +CONFIG_HID_CYPRESS=m +CONFIG_HID_DELL=m +CONFIG_HID_EZKEY=m +CONFIG_HID_GYRATION=m +CONFIG_HID_LOGITECH=m +# CONFIG_LOGITECH_FF is not set +# CONFIG_LOGIRUMBLEPAD2_FF is not set +CONFIG_HID_MICROSOFT=m +CONFIG_HID_MONTEREY=m +CONFIG_HID_PANTHERLORD=m +# CONFIG_PANTHERLORD_FF is not set +CONFIG_HID_PETALYNX=m +CONFIG_HID_SAMSUNG=m +CONFIG_HID_SONY=m +CONFIG_HID_SUNPLUS=m +# CONFIG_THRUSTMASTER_FF is not set +# CONFIG_ZEROPLUS_FF is not set +CONFIG_USB_SUPPORT=y +CONFIG_USB_ARCH_HAS_HCD=y +# CONFIG_USB_ARCH_HAS_OHCI is not set +CONFIG_USB_ARCH_HAS_EHCI=y +CONFIG_USB=y +# CONFIG_USB_DEBUG is not set +# CONFIG_USB_ANNOUNCE_NEW_DEVICES is not set + +# +# Miscellaneous USB options +# +# CONFIG_USB_DEVICEFS is not set +CONFIG_USB_DEVICE_CLASS=y +# CONFIG_USB_DYNAMIC_MINORS is not set +CONFIG_USB_SUSPEND=y +# CONFIG_USB_OTG is not set +# CONFIG_USB_OTG_WHITELIST is not set +# CONFIG_USB_OTG_BLACKLIST_HUB is not set +# CONFIG_USB_MON is not set +# CONFIG_USB_WUSB is not set +# CONFIG_USB_WUSB_CBAF is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_C67X00_HCD is not set +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_ARC=y +CONFIG_USB_EHCI_ARC_H1=y +# CONFIG_USB_EHCI_ARC_H2 is not set +# CONFIG_USB_EHCI_ARC_OTG is not set +# CONFIG_USB_STATIC_IRAM is not set +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +# CONFIG_USB_ISP116X_HCD is not set +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +# CONFIG_USB_HWA_HCD is not set +# CONFIG_USB_GADGET_MUSB_HDRC is not set + +# +# USB Device Class drivers +# +# CONFIG_USB_ACM is not set +# CONFIG_USB_PRINTER is not set +# CONFIG_USB_WDM is not set +# CONFIG_USB_TMC is not set + +# +# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; +# + +# +# see USB_STORAGE Help for more information +# +CONFIG_USB_STORAGE=y +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +# CONFIG_USB_STORAGE_FREECOM is not set +# CONFIG_USB_STORAGE_ISD200 is not set +# CONFIG_USB_STORAGE_DPCM is not set +# CONFIG_USB_STORAGE_USBAT is not set +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_ONETOUCH is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set +# CONFIG_USB_LIBUSUAL is not set + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set + +# +# USB port drivers +# +# CONFIG_USB_SERIAL is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_SEVSEG is not set +# CONFIG_USB_RIO500 is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_BERRY_CHARGE is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_PHIDGET is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_USB_SISUSBVGA is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_ISIGHTFW is not set +# CONFIG_USB_VST is not set +CONFIG_USB_GADGET=y +# CONFIG_USB_GADGET_DEBUG_FILES is not set +CONFIG_USB_GADGET_VBUS_DRAW=2 +CONFIG_USB_GADGET_SELECTED=y +# CONFIG_USB_GADGET_AT91 is not set +# CONFIG_USB_GADGET_ATMEL_USBA is not set +# CONFIG_USB_GADGET_FSL_USB2 is not set +# CONFIG_USB_GADGET_LH7A40X is not set +# CONFIG_USB_GADGET_OMAP is not set +# CONFIG_USB_GADGET_PXA25X is not set +# CONFIG_USB_GADGET_PXA27X is not set +# CONFIG_USB_GADGET_S3C2410 is not set +# CONFIG_USB_GADGET_M66592 is not set +# CONFIG_USB_GADGET_AMD5536UDC is not set +# CONFIG_USB_GADGET_FSL_QE is not set +# CONFIG_USB_GADGET_NET2280 is not set +# CONFIG_USB_GADGET_GOKU is not set +CONFIG_USB_GADGET_ARC=y +CONFIG_USB_ARC=y +# CONFIG_USB_GADGET_DUMMY_HCD is not set +CONFIG_USB_GADGET_DUALSPEED=y +CONFIG_USB_GADGET_ARC_OTG=y +# CONFIG_USB_GADGET_FSL_MC13783 is not set +# CONFIG_USB_GADGET_FSL_1301 is not set +# CONFIG_USB_GADGET_FSL_1504 is not set +CONFIG_USB_GADGET_FSL_UTMI=y +# CONFIG_USB_ZERO is not set +CONFIG_USB_ETH=m +CONFIG_USB_ETH_RNDIS=y +CONFIG_USB_GADGETFS=m +CONFIG_USB_FILE_STORAGE=m +# CONFIG_USB_FILE_STORAGE_TEST is not set +CONFIG_USB_G_SERIAL=m +# CONFIG_USB_MIDI_GADGET is not set +# CONFIG_USB_G_PRINTER is not set +# CONFIG_USB_CDC_COMPOSITE is not set +CONFIG_MMC=y +# CONFIG_MMC_DEBUG is not set +CONFIG_MMC_UNSAFE_RESUME=y + +# +# MMC/SD/SDIO Card Drivers +# +CONFIG_MMC_BLOCK=y +CONFIG_MMC_BLOCK_BOUNCE=y +# CONFIG_SDIO_UART is not set +# CONFIG_MMC_TEST is not set +CONFIG_SDIO_UNIFI_FS=m + +# +# MMC/SD/SDIO Host Controller Drivers +# +# CONFIG_MMC_SDHCI is not set +# CONFIG_MMC_SPI is not set +# CONFIG_MMC_MXC is not set +CONFIG_MMC_IMX_ESDHCI=y +# CONFIG_MMC_IMX_ESDHCI_PIO_MODE is not set +# CONFIG_MEMSTICK is not set +# CONFIG_ACCESSIBILITY is not set +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y + +# +# LED drivers +# +CONFIG_LEDS_MC13892=y +# CONFIG_LEDS_PCA9532 is not set +# CONFIG_LEDS_GPIO is not set +# CONFIG_LEDS_PCA955X is not set + +# +# LED Triggers +# +# CONFIG_LEDS_TRIGGERS is not set +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +CONFIG_RTC_INTF_DEV_UIE_EMUL=y +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set +# CONFIG_RTC_DRV_S35390A is not set +# CONFIG_RTC_DRV_FM3130 is not set +# CONFIG_RTC_DRV_RX8581 is not set + +# +# SPI RTC drivers +# +# CONFIG_RTC_DRV_M41T94 is not set +# CONFIG_RTC_DRV_DS1305 is not set +# CONFIG_RTC_DRV_DS1390 is not set +# CONFIG_RTC_DRV_MAX6902 is not set +# CONFIG_RTC_DRV_R9701 is not set +# CONFIG_RTC_DRV_RS5C348 is not set +# CONFIG_RTC_DRV_DS3234 is not set + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_BQ4802 is not set +# CONFIG_RTC_DRV_V3020 is not set + +# +# on-CPU RTC drivers +# +# CONFIG_RTC_MXC is not set +# CONFIG_RTC_DRV_MXC_V2 is not set +# CONFIG_RTC_DRV_IMXDI is not set +CONFIG_RTC_MC13892=y +# CONFIG_DMADEVICES is not set +CONFIG_REGULATOR=y +# CONFIG_REGULATOR_DEBUG is not set +# CONFIG_REGULATOR_FIXED_VOLTAGE is not set +# CONFIG_REGULATOR_VIRTUAL_CONSUMER is not set +# CONFIG_REGULATOR_BQ24022 is not set +CONFIG_REGULATOR_MC13892=y +CONFIG_UIO=y +# CONFIG_UIO_PDRV is not set +CONFIG_UIO_PDRV_GENIRQ=m +# CONFIG_UIO_SMX is not set +# CONFIG_UIO_SERCOS3 is not set + +# +# MXC support drivers +# +CONFIG_MXC_IPU=y +CONFIG_MXC_IPU_V3=y + +# +# MXC SSI support +# +# CONFIG_MXC_SSI is not set + +# +# MXC Digital Audio Multiplexer support +# +# CONFIG_MXC_DAM is not set + +# +# MXC PMIC support +# +CONFIG_MXC_PMIC=y +# CONFIG_MXC_PMIC_MC13783 is not set +CONFIG_MXC_PMIC_MC13892=y +CONFIG_MXC_PMIC_I2C=y +CONFIG_MXC_PMIC_SPI=y +# CONFIG_MXC_PMIC_MC34704 is not set +# CONFIG_MXC_PMIC_MC9SDZ60 is not set +# CONFIG_MXC_PMIC_CHARDEV is not set + +# +# MXC PMIC Client Drivers +# +CONFIG_MXC_MC13892_ADC=y +CONFIG_MXC_MC13892_RTC=y +CONFIG_MXC_MC13892_LIGHT=y +CONFIG_MXC_MC13892_BATTERY=y +CONFIG_MXC_MC13892_CONNECTIVITY=y +CONFIG_MXC_MC13892_POWER=y +# CONFIG_MXC_PMIC_MC9S08DZ60 is not set + +# +# MXC Security Drivers +# +# CONFIG_MXC_SECURITY_SCC is not set +CONFIG_MXC_SECURITY_SCC2=y +CONFIG_SCC_DEBUG=y +# CONFIG_MXC_SECURITY_RNG is not set + +# +# SAHARA2 Security Hardware Support +# +# CONFIG_MXC_SAHARA is not set + +# +# MXC MPEG4 Encoder Kernel module support +# +# CONFIG_MXC_HMP4E is not set + +# +# MXC HARDWARE EVENT +# +# CONFIG_MXC_HWEVENT is not set + +# +# MXC VPU(Video Processing Unit) support +# +CONFIG_MXC_VPU=y +CONFIG_MXC_VPU_IRAM=y +# CONFIG_MXC_VPU_DEBUG is not set + +# +# MXC Asynchronous Sample Rate Converter support +# + +# +# MXC Bluetooth support +# +CONFIG_MXC_BLUETOOTH=m + +# +# Broadcom GPS ioctrl support +# +CONFIG_GPS_IOCTRL=m + +# +# MXC Media Local Bus Driver +# + +# +# i.MX ADC support +# +# CONFIG_IMX_ADC is not set + +# +# File systems +# +CONFIG_EXT2_FS=y +# CONFIG_EXT2_FS_XATTR is not set +# CONFIG_EXT2_FS_XIP is not set +CONFIG_EXT3_FS=y +CONFIG_EXT3_FS_XATTR=y +# CONFIG_EXT3_FS_POSIX_ACL is not set +# CONFIG_EXT3_FS_SECURITY is not set +# CONFIG_EXT4_FS is not set +CONFIG_JBD=y +CONFIG_FS_MBCACHE=y +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_FS_POSIX_ACL is not set +CONFIG_FILE_LOCKING=y +# CONFIG_XFS_FS is not set +# CONFIG_OCFS2_FS is not set +CONFIG_DNOTIFY=y +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_QUOTA is not set +# CONFIG_AUTOFS_FS is not set +CONFIG_AUTOFS4_FS=m +# CONFIG_FUSE_FS is not set + +# +# CD-ROM/DVD Filesystems +# +# CONFIG_ISO9660_FS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +# CONFIG_TMPFS_POSIX_ACL is not set +# CONFIG_HUGETLB_PAGE is not set +# CONFIG_CONFIGFS_FS is not set + +# +# Miscellaneous filesystems +# +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_DEBUG=0 +CONFIG_JFFS2_FS_WRITEBUFFER=y +# CONFIG_JFFS2_FS_WBUF_VERIFY is not set +# CONFIG_JFFS2_SUMMARY is not set +# CONFIG_JFFS2_FS_XATTR is not set +# CONFIG_JFFS2_COMPRESSION_OPTIONS is not set +CONFIG_JFFS2_ZLIB=y +# CONFIG_JFFS2_LZO is not set +CONFIG_JFFS2_RTIME=y +# CONFIG_JFFS2_RUBIN is not set +CONFIG_CRAMFS=y +# CONFIG_VXFS_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +# CONFIG_NFS_V3_ACL is not set +# CONFIG_NFS_V4 is not set +CONFIG_ROOT_NFS=y +# CONFIG_NFSD is not set +CONFIG_LOCKD=y +CONFIG_LOCKD_V4=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=y +# CONFIG_SUNRPC_REGISTER_V4 is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +# CONFIG_RPCSEC_GSS_SPKM3 is not set +# CONFIG_SMB_FS is not set +# CONFIG_CIFS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +CONFIG_NLS_ASCII=m +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +CONFIG_NLS_UTF8=m +# CONFIG_DLM is not set + +# +# Kernel hacking +# +# CONFIG_PRINTK_TIME is not set +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +CONFIG_FRAME_WARN=1024 +# CONFIG_MAGIC_SYSRQ is not set +# CONFIG_UNUSED_SYMBOLS is not set +# CONFIG_DEBUG_FS is not set +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_KERNEL is not set +# CONFIG_SLUB_DEBUG_ON is not set +# CONFIG_SLUB_STATS is not set +# CONFIG_DEBUG_BUGVERBOSE is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +CONFIG_FRAME_POINTER=y +# CONFIG_RCU_CPU_STALL_DETECTOR is not set +# CONFIG_LATENCYTOP is not set +CONFIG_SYSCTL_SYSCALL_CHECK=y +CONFIG_HAVE_FUNCTION_TRACER=y + +# +# Tracers +# +# CONFIG_DYNAMIC_PRINTK_DEBUG is not set +# CONFIG_SAMPLES is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_DEBUG_USER is not set + +# +# Security options +# +# CONFIG_KEYS is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITYFS is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +# CONFIG_CRYPTO_FIPS is not set +# CONFIG_CRYPTO_MANAGER is not set +# CONFIG_CRYPTO_MANAGER2 is not set +# CONFIG_CRYPTO_GF128MUL is not set +# CONFIG_CRYPTO_NULL is not set +# CONFIG_CRYPTO_CRYPTD is not set +# CONFIG_CRYPTO_AUTHENC is not set +# CONFIG_CRYPTO_TEST is not set +# CONFIG_CRYPTO_CRYPTODEV is not set + +# +# Authenticated Encryption with Associated Data +# +# CONFIG_CRYPTO_CCM is not set +# CONFIG_CRYPTO_GCM is not set +# CONFIG_CRYPTO_SEQIV is not set + +# +# Block modes +# +# CONFIG_CRYPTO_CBC is not set +# CONFIG_CRYPTO_CTR is not set +# CONFIG_CRYPTO_CTS is not set +# CONFIG_CRYPTO_ECB is not set +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_XTS is not set + +# +# Hash modes +# +# CONFIG_CRYPTO_HMAC is not set +# CONFIG_CRYPTO_XCBC is not set + +# +# Digest +# +# CONFIG_CRYPTO_CRC32C is not set +# CONFIG_CRYPTO_MD4 is not set +# CONFIG_CRYPTO_MD5 is not set +# CONFIG_CRYPTO_MICHAEL_MIC is not set +# CONFIG_CRYPTO_RMD128 is not set +# CONFIG_CRYPTO_RMD160 is not set +# CONFIG_CRYPTO_RMD256 is not set +# CONFIG_CRYPTO_RMD320 is not set +# CONFIG_CRYPTO_SHA1 is not set +# CONFIG_CRYPTO_SHA256 is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_WP512 is not set + +# +# Ciphers +# +# CONFIG_CRYPTO_AES is not set +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_ARC4 is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +# CONFIG_CRYPTO_DES is not set +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_SALSA20 is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SERPENT is not set +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TWOFISH is not set + +# +# Compression +# +# CONFIG_CRYPTO_DEFLATE is not set +# CONFIG_CRYPTO_LZO is not set + +# +# Random Number Generation +# +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_HW=y + +# +# Library routines +# +CONFIG_BITREVERSE=y +CONFIG_CRC_CCITT=m +# CONFIG_CRC16 is not set +# CONFIG_CRC_T10DIF is not set +# CONFIG_CRC_ITU_T is not set +CONFIG_CRC32=y +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_PLIST=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_DMA=y diff --git a/arch/arm/include/asm/mach/keypad.h b/arch/arm/include/asm/mach/keypad.h new file mode 100644 index 000000000000..cfee65ab044a --- /dev/null +++ b/arch/arm/include/asm/mach/keypad.h @@ -0,0 +1,28 @@ +/* + * include/asm-arm/mach/keypad.h + * + * Generic Keypad struct + * + * Author: Armin Kuster <Akuster@mvista.com> + * + * 2005 (c) MontaVista Software, Inc. This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + */ + +#ifndef __ASM_MACH_KEYPAD_H_ +#define __ASM_MACH_KEYPAD_H_ + +#include <linux/input.h> + +struct keypad_data { + u16 rowmax; + u16 colmax; + u32 irq; + u16 delay; + u16 learning; + u16 *matrix; +}; + +#endif /* __ARM_MACH_KEYPAD_H_ */ diff --git a/arch/arm/mach-mx25/Kconfig b/arch/arm/mach-mx25/Kconfig new file mode 100644 index 000000000000..3f404eeb326f --- /dev/null +++ b/arch/arm/mach-mx25/Kconfig @@ -0,0 +1,96 @@ +menu "MX25 Options" + depends on ARCH_MX25 + +config MX25_OPTIONS + bool + default y + select CPU_ARM926T + select USB_ARCH_HAS_EHCI + +config MACH_MX25_3DS + bool "Support MX25 3STACK platforms" + default y + help + Include support for MX25 3STACK platform. This includes specific + configurations for the board and its peripherals. + +config MX25_DOZE_DURING_IDLE + bool "Enter Doze mode during idle" + help + Turning on this option will put the CPU into Doze mode during idle. + The default is to enter Wait mode during idle. Doze mode during + idle will save additional power over Wait mode. + +config MXC_SDMA_API + bool "Use SDMA API" + default y + help + This selects the Freescale MXC SDMA API. + If unsure, say N. + +menu "SDMA options" + depends on MXC_SDMA_API + +config SDMA_IRAM + bool "Use Internal RAM for SDMA transfer" + default n + help + Support Internal RAM as SDMA buffer or control structures + +config SDMA_IRAM_SIZE + hex "Reserved bytes of IRAM for SDMA (0x800-0x1000)" + range 0x800 0x1000 + depends on SDMA_IRAM + default "0x1000" + help + Set the size of IRAM for SDMA. It must be a multiple of 512bytes. +endmenu + +config ARCH_MXC_HAS_NFC_V2 + bool "MXC NFC Hardware Version 2" + depends on ARCH_MX25 + default y + help + This selects the Freescale MXC Nand Flash Controller Hardware Version 2 + If unsure, say N. + +config ARCH_MXC_HAS_NFC_V2_1 + bool "MXC NFC Hardware Version 2.1" + depends on ARCH_MXC_HAS_NFC_V2 + default y + help + This selects the Freescale MXC Nand Flash Controller Hardware Version 2.1 + If unsure, say N. + +menu "Device options" + +config I2C_MXC_SELECT1 + bool "Enable I2C1 module" + default y + depends on I2C_MXC + help + Enable MX25 I2C1 module. + +config I2C_MXC_SELECT2 + bool "Enable I2C2 module" + default n + depends on I2C_MXC + help + Enable MX25 I2C2 module. + +config I2C_MXC_SELECT3 + bool "Enable I2C3 module" + default n + depends on I2C_MXC + help + Enable MX25 I2C3 module. + +config FLEXCAN_MXC_SELECT1 + bool "Enable FlexCAN1 module" + depends on CAN_FLEXCAN + help + Enable MX25 FlexCAN1 module. + +endmenu + +endmenu diff --git a/arch/arm/mach-mx25/Makefile b/arch/arm/mach-mx25/Makefile new file mode 100644 index 000000000000..efca65083819 --- /dev/null +++ b/arch/arm/mach-mx25/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for the linux kernel. +# + +# Object file lists. + +obj-y := system.o iomux.o cpu.o mm.o clock.o bus_freq.o devices.o serial.o +obj-$(CONFIG_MXC_SDMA_API) += dma.o +obj-$(CONFIG_SPI_MXC) += mx25_3stack_cpld.o +obj-$(CONFIG_MACH_MX25_3DS) += mx25_3stack.o mx25_3stack_gpio.o mx25_3stack_pmic_mc34704.o + +obj-$(CONFIG_USB_EHCI_ARC_H2) += usb_h2.o + +obj-$(CONFIG_PM) += pm.o + +ifneq ($(strip $(CONFIG_USB_GADGET_ARC) $(CONFIG_USB_EHCI_ARC_OTG)),) + obj-y += usb_dr.o +endif diff --git a/arch/arm/mach-mx25/Makefile.boot b/arch/arm/mach-mx25/Makefile.boot new file mode 100644 index 000000000000..e1dd366f836b --- /dev/null +++ b/arch/arm/mach-mx25/Makefile.boot @@ -0,0 +1,3 @@ + zreladdr-y := 0x80008000 +params_phys-y := 0x80000100 +initrd_phys-y := 0x80800000 diff --git a/arch/arm/mach-mx25/board-mx25_3stack.h b/arch/arm/mach-mx25/board-mx25_3stack.h new file mode 100644 index 000000000000..73fc7b1f576a --- /dev/null +++ b/arch/arm/mach-mx25/board-mx25_3stack.h @@ -0,0 +1,173 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ASM_ARCH_MXC_BOARD_MX25_3STACK_H__ +#define __ASM_ARCH_MXC_BOARD_MX25_3STACK_H__ + +#ifdef CONFIG_MACH_MX25_3DS + +/*! + * @defgroup BRDCFG_MX25 Board Configuration Options + * @ingroup MSL_MX25 + */ + +/*! + * @file mach-mx25/board-mx25_3stack.h + * + * @brief This file contains all the board level configuration options. + * + * It currently hold the options defined for MX25 3STACK Platform. + * + * @ingroup BRDCFG_MX25 + */ + +/* + * Include Files + */ +#include <mach/mxc_uart.h> + +/*! + * @name MXC UART board-level configurations + */ +/*! @{ */ +/* UART 1 configuration */ +/*! + * This define specifies if the UART port is configured to be in DTE or + * DCE mode. There exists a define like this for each UART port. Valid + * values that can be used are \b MODE_DTE or \b MODE_DCE. + */ +#define UART1_MODE MODE_DCE +/*! + * This define specifies if the UART is to be used for IRDA. There exists a + * define like this for each UART port. Valid values that can be used are + * \b IRDA or \b NO_IRDA. + */ +#define UART1_IR NO_IRDA +/*! + * This define is used to enable or disable a particular UART port. If + * disabled, the UART will not be registered in the file system and the user + * will not be able to access it. There exists a define like this for each UART + * port. Specify a value of 1 to enable the UART and 0 to disable it. + */ +#define UART1_ENABLED 1 +/*! @} */ +/* UART 2 configuration */ +#define UART2_MODE MODE_DCE +#define UART2_IR NO_IRDA +#define UART2_ENABLED 1 + +/* UART 3 configuration */ +#define UART3_MODE MODE_DTE +#define UART3_IR NO_IRDA +#define UART3_ENABLED 0 + +/* UART 4 configuration */ +#define UART4_MODE MODE_DTE +#define UART4_IR NO_IRDA +#define UART4_ENABLED 0 + +/* UART 5 configuration */ +#define UART5_MODE MODE_DTE +#define UART5_IR NO_IRDA +#define UART5_ENABLED 0 + +#define MXC_LL_UART_PADDR UART1_BASE_ADDR +#define MXC_LL_UART_VADDR AIPS1_IO_ADDRESS(UART1_BASE_ADDR) + +/*! + * @name debug board parameters + */ +/*! @{ */ +/*! + * Base address of debug board + */ +#define DEBUG_BASE_ADDRESS 0x78000000 /* Use a dummy base address */ + +/* External ethernet LAN9217 base address */ +#define LAN9217_BASE_ADDR DEBUG_BASE_ADDRESS + +/* External UART */ +#define UARTA_BASE_ADDR (DEBUG_BASE_ADDRESS + 0x08000) +#define UARTB_BASE_ADDR (DEBUG_BASE_ADDRESS + 0x10000) + +#define BOARD_IO_ADDR 0x20000 + +/* LED switchs */ +#define LED_SWITCH_REG (BOARD_IO_ADDR + 0x00) +/* buttons */ +#define SWITCH_BUTTON_REG (BOARD_IO_ADDR + 0x08) +/* status, interrupt */ +#define INTR_STATUS_REG (BOARD_IO_ADDR + 0x10) +#define INTR_RESET_REG (BOARD_IO_ADDR + 0x20) +/*CPLD configuration*/ +#define CONFIG1_REG (BOARD_IO_ADDR + 0x28) +#define CONFIG2_REG (BOARD_IO_ADDR + 0x30) +/*interrupt mask */ +#define INTR_MASK_REG (BOARD_IO_ADDR + 0x38) + +/* magic word for debug CPLD */ +#define MAGIC_NUMBER1_REG (BOARD_IO_ADDR + 0x40) +#define MAGIC_NUMBER2_REG (BOARD_IO_ADDR + 0x48) +/* CPLD code version */ +#define CPLD_CODE_VER_REG (BOARD_IO_ADDR + 0x50) +/* magic word for debug CPLD */ +#define MAGIC3_NUMBER3_REG (BOARD_IO_ADDR + 0x58) +/* module reset register*/ +#define CONTROL_REG (BOARD_IO_ADDR + 0x60) +/* CPU ID and Personality ID*/ +#define IDENT_REG (BOARD_IO_ADDR + 0x68) + +/* For interrupts like xuart, enet etc */ +#define EXPIO_PARENT_INT MX25_PIN_GPIO1_1 + +#define EXPIO_INT_ENET_INT (MXC_BOARD_IRQ_START + 0) +#define EXPIO_INT_XUARTA_INT (MXC_BOARD_IRQ_START + 1) +#define EXPIO_INT_XUARTB_INT (MXC_BOARD_IRQ_START + 2) + +/*! This is System IRQ used by LAN9217 for interrupt generation taken + * from platform.h + */ +#define LAN9217_IRQ EXPIO_INT_ENET_INT + +/*! This is base virtual address of debug board*/ +extern unsigned int mx25_3stack_board_io; + +#define MXC_BD_LED1 (1 << 0) +#define MXC_BD_LED2 (1 << 1) +#define MXC_BD_LED3 (1 << 2) +#define MXC_BD_LED4 (1 << 3) +#define MXC_BD_LED5 (1 << 4) +#define MXC_BD_LED6 (1 << 5) +#define MXC_BD_LED7 (1 << 6) +#define MXC_BD_LED8 (1 << 7) +#define MXC_BD_LED_ON(led) +#define MXC_BD_LED_OFF(led) + +#define MXC_DEFAULT_INTENSITY 127 +#define MXC_INTENSITY_OFF 0 + +/*! @} */ + +extern void mx25_3stack_gpio_init(void) __init; +extern int headphone_det_status(void); +extern void sgtl5000_enable_amp(void); +extern unsigned int sdhc_get_card_det_status(struct device *dev); +extern int sdhc_write_protect(struct device *dev); +extern void gpio_can_active(int id); +extern void gpio_can_inactive(int id); +extern struct flexcan_platform_data flexcan_data[]; +extern void mx2fb_set_brightness(uint8_t); +extern int __init mx25_3stack_init_mc34704(void); + +#endif /* CONFIG_MACH_MX25_3DS */ +#endif /* __ASM_ARCH_MXC_BOARD_MX25_3STACK_H__ */ diff --git a/arch/arm/mach-mx25/bus_freq.c b/arch/arm/mach-mx25/bus_freq.c new file mode 100644 index 000000000000..61bed5552a03 --- /dev/null +++ b/arch/arm/mach-mx25/bus_freq.c @@ -0,0 +1,102 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file bus_freq.c + * + * @brief A common API for the Freescale Semiconductor i.MXC CPUfreq module + * and DVFS CORE module. + * + * The APIs are for setting bus frequency to low or high. + * + * @ingroup PM + */ + +#include <linux/proc_fs.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <mach/clock.h> +#include <mach/hardware.h> + +int low_bus_freq_mode; +int high_bus_freq_mode; +char *gp_reg_id = "REG3_CORE"; + +int set_low_bus_freq(void) +{ + return 0; +} + +int set_high_bus_freq(int high_bus_freq) +{ + return 0; +} + +int low_freq_bus_used(void) +{ + return 0; +} + +/*! + * This is the probe routine for the bus frequency driver. + * + * @param pdev The platform device structure + * + * @return The function returns 0 on success + * + */ +static int __devinit busfreq_probe(struct platform_device *pdev) +{ + low_bus_freq_mode = 0; + high_bus_freq_mode = 0; + + return 0; +} + +static struct platform_driver busfreq_driver = { + .driver = { + .name = "busfreq", + }, + .probe = busfreq_probe, +}; + +/*! + * Initialise the busfreq_driver. + * + * @return The function always returns 0. + */ + +static int __init busfreq_init(void) +{ + if (platform_driver_register(&busfreq_driver) != 0) { + printk(KERN_ERR "busfreq_driver register failed\n"); + return -ENODEV; + } + + printk(KERN_INFO "Bus freq driver module loaded\n"); + return 0; +} + +static void __exit busfreq_cleanup(void) +{ + /* Unregister the device structure */ + platform_driver_unregister(&busfreq_driver); +} + +module_init(busfreq_init); +module_exit(busfreq_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("BusFreq driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-mx25/clock.c b/arch/arm/mach-mx25/clock.c new file mode 100644 index 000000000000..7d6ce2b09434 --- /dev/null +++ b/arch/arm/mach-mx25/clock.c @@ -0,0 +1,1739 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* based on mach-mx27/clock.c */ + +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <mach/clock.h> +#include <mach/common.h> +#include "crm_regs.h" + +#define OSC24M_CLK_FREQ 24000000 /* 24M reference clk */ +#define OSC32K_CLK_FREQ 32768 /* 32.768k oscillator in */ + +#if defined CONFIG_CPU_FREQ_IMX +#define AHB_CLK_DEFAULT 133000000 +#define ARM_SRC_DEFAULT 532000000 +#endif + +static struct clk mpll_clk; +static struct clk upll_clk; +static struct clk ahb_clk; +static struct clk upll_24610k_clk; +int cpu_wp_nr; + +static int _clk_enable(struct clk *clk) +{ + unsigned long reg; + + reg = __raw_readl(clk->enable_reg); + reg |= 1 << clk->enable_shift; + __raw_writel(reg, clk->enable_reg); + + return 0; +} + +static void _clk_disable(struct clk *clk) +{ + unsigned long reg; + + reg = __raw_readl(clk->enable_reg); + reg &= ~(1 << clk->enable_shift); + __raw_writel(reg, clk->enable_reg); +} + +static int _clk_upll_enable(struct clk *clk) +{ + unsigned long reg; + + reg = __raw_readl(MXC_CCM_CCTL); + reg &= ~MXC_CCM_CCTL_UPLL_DISABLE; + __raw_writel(reg, MXC_CCM_CCTL); + + while ((__raw_readl(MXC_CCM_UPCTL) & MXC_CCM_UPCTL_LF) == 0) ; + + return 0; +} + +static void _clk_upll_disable(struct clk *clk) +{ + unsigned long reg; + + reg = __raw_readl(MXC_CCM_CCTL); + reg |= MXC_CCM_CCTL_UPLL_DISABLE; + __raw_writel(reg, MXC_CCM_CCTL); +} + +static int _perclk_enable(struct clk *clk) +{ + unsigned long reg; + + reg = __raw_readl(MXC_CCM_CGCR0); + reg |= 1 << clk->id; + __raw_writel(reg, MXC_CCM_CGCR0); + + return 0; +} + +static void _perclk_disable(struct clk *clk) +{ + unsigned long reg; + + reg = __raw_readl(MXC_CCM_CGCR0); + reg &= ~(1 << clk->id); + __raw_writel(reg, MXC_CCM_CGCR0); +} + +static void _clk_pll_recalc(struct clk *clk) +{ + unsigned long mfi = 0, mfn = 0, mfd = 0, pdf = 0; + unsigned long ref_clk; + unsigned long reg; + unsigned long long temp; + + ref_clk = clk->parent->rate; + + if (clk == &mpll_clk) { + reg = __raw_readl(MXC_CCM_MPCTL); + pdf = (reg & MXC_CCM_MPCTL_PD_MASK) >> MXC_CCM_MPCTL_PD_OFFSET; + mfd = + (reg & MXC_CCM_MPCTL_MFD_MASK) >> MXC_CCM_MPCTL_MFD_OFFSET; + mfi = + (reg & MXC_CCM_MPCTL_MFI_MASK) >> MXC_CCM_MPCTL_MFI_OFFSET; + mfn = + (reg & MXC_CCM_MPCTL_MFN_MASK) >> MXC_CCM_MPCTL_MFN_OFFSET; + } else if (clk == &upll_clk) { + reg = __raw_readl(MXC_CCM_UPCTL); + pdf = (reg & MXC_CCM_UPCTL_PD_MASK) >> MXC_CCM_UPCTL_PD_OFFSET; + mfd = + (reg & MXC_CCM_UPCTL_MFD_MASK) >> MXC_CCM_UPCTL_MFD_OFFSET; + mfi = + (reg & MXC_CCM_UPCTL_MFI_MASK) >> MXC_CCM_UPCTL_MFI_OFFSET; + mfn = + (reg & MXC_CCM_UPCTL_MFN_MASK) >> MXC_CCM_UPCTL_MFN_OFFSET; + } else { + BUG(); /* oops */ + } + + mfi = (mfi <= 5) ? 5 : mfi; + temp = 2LL * ref_clk * mfn; + do_div(temp, mfd + 1); + temp = 2LL * ref_clk * mfi + temp; + do_div(temp, pdf + 1); + + clk->rate = temp; +} + +static unsigned long _clk_cpu_round_rate(struct clk *clk, unsigned long rate) +{ + int div = clk->parent->rate / rate; + + if (clk->parent->rate % rate) + div++; + + if (div > 4) + div = 4; + + return clk->parent->rate / div; +} + +static int _clk_cpu_set_rate(struct clk *clk, unsigned long rate) +{ + unsigned long div = 0x0, reg = 0x0; + unsigned long cctl = __raw_readl(MXC_CCM_CCTL); + +#if defined CONFIG_CPU_FREQ_IMX + struct cpu_wp *cpu_wp; + unsigned long ahb_clk_div = 0; + unsigned long arm_src = 0; + int i; + + cpu_wp = get_cpu_wp(&cpu_wp_nr); + for (i = 0; i < cpu_wp_nr; i++) { + if (cpu_wp[i].cpu_rate == rate) { + div = cpu_wp[i].cpu_podf; + ahb_clk_div = cpu_wp[i].cpu_rate / AHB_CLK_DEFAULT - 1; + arm_src = + (cpu_wp[i].pll_rate == ARM_SRC_DEFAULT) ? 0 : 1; + break; + } + } + if (i == cpu_wp_nr) + return -EINVAL; + reg = (cctl & ~MXC_CCM_CCTL_ARM_MASK) | + (div << MXC_CCM_CCTL_ARM_OFFSET); + reg = (reg & ~MXC_CCM_CCTL_AHB_MASK) | + (ahb_clk_div << MXC_CCM_CCTL_AHB_OFFSET); + reg = (reg & ~MXC_CCM_CCTL_ARM_SRC) | + (arm_src << MXC_CCM_CCTL_ARM_SRC_OFFSET); + __raw_writel(reg, MXC_CCM_CCTL); + clk->rate = rate; +#else + div = clk->parent->rate / rate; + + if (div > 4 || div < 1 || ((clk->parent->rate / div) != rate)) + return -EINVAL; + div--; + + reg = + (cctl & ~MXC_CCM_CCTL_ARM_MASK) | (div << MXC_CCM_CCTL_ARM_OFFSET); + __raw_writel(reg, MXC_CCM_CCTL); + clk->rate = rate; +#endif + + return 0; +} + +static void _clk_cpu_recalc(struct clk *clk) +{ + unsigned long div; + unsigned long cctl = __raw_readl(MXC_CCM_CCTL); + + div = (cctl & MXC_CCM_CCTL_ARM_MASK) >> MXC_CCM_CCTL_ARM_OFFSET; + + clk->rate = clk->parent->rate / (div + 1); + + if (cctl & MXC_CCM_CCTL_ARM_SRC) { + clk->rate *= 3; + clk->rate /= 4; + } +} + +static void _clk_ahb_recalc(struct clk *clk) +{ + unsigned long div; + unsigned long cctl = __raw_readl(MXC_CCM_CCTL); + + div = (cctl & MXC_CCM_CCTL_AHB_MASK) >> MXC_CCM_CCTL_AHB_OFFSET; + + clk->rate = clk->parent->rate / (div + 1); +} + +static void *pcdr_a[4] = { + MXC_CCM_PCDR0, MXC_CCM_PCDR1, MXC_CCM_PCDR2, MXC_CCM_PCDR3 +}; +static void _clk_perclkx_recalc(struct clk *clk) +{ + unsigned long perclk_pdf; + unsigned long pcdr; + + if (clk->id < 0 || clk->id > 15) + return; + + pcdr = __raw_readl(pcdr_a[clk->id >> 2]); + + perclk_pdf = + (pcdr >> ((clk->id & 3) << 3)) & MXC_CCM_PCDR1_PERDIV1_MASK; + + clk->rate = clk->parent->rate / (perclk_pdf + 1); +} + +static unsigned long _clk_perclkx_round_rate(struct clk *clk, + unsigned long rate) +{ + unsigned long div; + + div = clk->parent->rate / rate; + if (clk->parent->rate % rate) + div++; + + if (div > 64) + div = 64; + + return clk->parent->rate / div; +} + +static int _clk_perclkx_set_rate(struct clk *clk, unsigned long rate) +{ + unsigned long reg; + unsigned long div; + + if (clk->id < 0 || clk->id > 15) + return -EINVAL; + + div = clk->parent->rate / rate; + if (div > 64 || div < 1 || ((clk->parent->rate / div) != rate)) + return -EINVAL; + div--; + + reg = + __raw_readl(pcdr_a[clk->id >> 2]) & ~(MXC_CCM_PCDR1_PERDIV1_MASK << + ((clk->id & 3) << 3)); + reg |= div << ((clk->id & 3) << 3); + __raw_writel(reg, pcdr_a[clk->id >> 2]); + + clk->rate = rate; + + return 0; +} + +static int _clk_perclkx_set_parent(struct clk *clk, struct clk *parent) +{ + unsigned long mcr; + + if (parent != &upll_clk && parent != &ahb_clk) + return -EINVAL; + + clk->parent = parent; + mcr = __raw_readl(MXC_CCM_MCR); + if (parent == &upll_clk) + mcr |= (1 << clk->id); + else + mcr &= ~(1 << clk->id); + + __raw_writel(mcr, MXC_CCM_MCR); + + return 0; +} + +static int _clk_perclkx_set_parent3(struct clk *clk, struct clk *parent) +{ + unsigned long mcr = __raw_readl(MXC_CCM_MCR); + int bit; + + if (parent != &upll_clk && parent != &ahb_clk && + parent != &upll_24610k_clk) + return -EINVAL; + + switch (clk->id) { + case 2: + bit = MXC_CCM_MCR_ESAI_CLK_MUX_OFFSET; + break; + case 13: + bit = MXC_CCM_MCR_SSI1_CLK_MUX_OFFSET; + break; + case 14: + bit = MXC_CCM_MCR_SSI2_CLK_MUX_OFFSET; + break; + default: + return -EINVAL; + } + + if (parent == &upll_24610k_clk) { + mcr |= 1 << bit; + __raw_writel(mcr, MXC_CCM_MCR); + clk->parent = parent; + } else { + mcr &= ~(1 << bit); + __raw_writel(mcr, MXC_CCM_MCR); + return _clk_perclkx_set_parent(clk, parent); + } + + return 0; +} + +static void _clk_ipg_recalc(struct clk *clk) +{ + clk->rate = clk->parent->rate / 2; /* Always AHB / 2 */ +} + +static unsigned long _clk_parent_round_rate(struct clk *clk, unsigned long rate) +{ + return clk->parent->round_rate(clk->parent, rate); +} + +static int _clk_parent_set_rate(struct clk *clk, unsigned long rate) +{ + int ret; + + ret = clk->parent->set_rate(clk->parent, rate); + if (ret == 0) + clk->rate = rate; + return ret; +} + +/* Top-level clocks */ + +static struct clk osc24m_clk = { + .name = "osc24m", + .rate = OSC24M_CLK_FREQ, + .flags = RATE_PROPAGATES, +}; + +static struct clk osc32k_clk = { + .name = "osc32k", + .rate = OSC32K_CLK_FREQ, + .flags = RATE_PROPAGATES, +}; + +static struct clk mpll_clk = { + .name = "mpll", + .parent = &osc24m_clk, + .recalc = _clk_pll_recalc, + .flags = RATE_PROPAGATES, +}; + +static struct clk upll_clk = { + .name = "upll", + .parent = &osc24m_clk, + .recalc = _clk_pll_recalc, + .enable = _clk_upll_enable, + .disable = _clk_upll_disable, + .flags = RATE_PROPAGATES, +}; + +static void _clk_24610k_recalc(struct clk *clk) +{ + long long temp = clk->parent->rate * 2461LL; + + do_div(temp, 24000); + + clk->rate = temp; /* Always (UPLL * 24.61 / 240) */ +} + +static struct clk upll_24610k_clk = { + .name = "upll_24610k", + .parent = &upll_clk, + .recalc = _clk_24610k_recalc, + .flags = RATE_PROPAGATES, +}; + +/* Mid-level clocks */ + +static struct clk cpu_clk = { /* ARM clock */ + .name = "cpu_clk", + .parent = &mpll_clk, + .set_rate = _clk_cpu_set_rate, + .recalc = _clk_cpu_recalc, + .round_rate = _clk_cpu_round_rate, + .flags = RATE_PROPAGATES, +}; + +static struct clk ahb_clk = { /* a.k.a. HCLK */ + .name = "ahb_clk", + .parent = &cpu_clk, + .recalc = _clk_ahb_recalc, + .flags = RATE_PROPAGATES, +}; + +static struct clk ipg_clk = { + .name = "ipg_clk", + .parent = &ahb_clk, + .recalc = _clk_ipg_recalc, + .flags = RATE_PROPAGATES, +}; + +/* Bottom-level clocks */ + +struct clk usb_ahb_clk = { + .name = "usb_ahb_clk", + .id = 0, + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR0, + .enable_shift = MXC_CCM_CGCR0_HCLK_USBOTG_OFFSET, + .disable = _clk_disable, +}; + +struct clk rtic_clk = { + .name = "rtic_clk", + .id = 0, + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR0, + .enable_shift = MXC_CCM_CGCR0_HCLK_RTIC_OFFSET, + .disable = _clk_disable, +}; + +struct clk emi_clk = { + .name = "emi_clk", + .id = 0, + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR0, + .enable_shift = MXC_CCM_CGCR0_HCLK_EMI_OFFSET, + .disable = _clk_disable, +}; + +struct clk brom_clk = { + .name = "brom_clk", + .id = 0, + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR0, + .enable_shift = MXC_CCM_CGCR0_HCLK_BROM_OFFSET, + .disable = _clk_disable, +}; + +static struct clk per_clk[] = { + { + .name = "per_csi_clk", + .id = 0, + .parent = &upll_clk, /* can be AHB or UPLL */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_epit_clk", + .id = 1, + .parent = &ahb_clk, /* can be AHB or UPLL */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_esai_clk", + .id = 2, + .parent = &ahb_clk, /* can be AHB or UPLL or 24.61MHz */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent3, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_esdhc1_clk", + .id = 3, + .parent = &ahb_clk, /* can be AHB or UPLL */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_esdhc2_clk", + .id = 4, + .parent = &ahb_clk, /* can be AHB or UPLL */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_gpt_clk", + .id = 5, + .parent = &ahb_clk, /* Must be AHB */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_i2c_clk", + .id = 6, + .parent = &ahb_clk, /* can be AHB or UPLL */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_lcdc_clk", + .id = 7, + .parent = &upll_clk, /* Must be UPLL */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_nfc_clk", + .id = 8, + .parent = &ahb_clk, /* can be AHB or UPLL */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_owire_clk", + .id = 9, + .parent = &ahb_clk, /* can be AHB or UPLL */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_pwm_clk", + .id = 10, + .parent = &ahb_clk, /* can be AHB or UPLL */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_sim1_clk", + .id = 11, + .parent = &ahb_clk, /* can be AHB or UPLL */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_sim2_clk", + .id = 12, + .parent = &ahb_clk, /* can be AHB or UPLL */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_ssi1_clk", + .id = 13, + .parent = &ahb_clk, /* can be AHB or UPLL or 24.61MHz */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent3, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_ssi2_clk", + .id = 14, + .parent = &ahb_clk, /* can be AHB or UPLL or 24.61MHz */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent3, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, + { + .name = "per_uart_clk", + .id = 15, + .parent = &ahb_clk, /* can be AHB or UPLL */ + .round_rate = _clk_perclkx_round_rate, + .set_rate = _clk_perclkx_set_rate, + .set_parent = _clk_perclkx_set_parent, + .recalc = _clk_perclkx_recalc, + .enable = _perclk_enable, + .disable = _perclk_disable, + .flags = RATE_PROPAGATES,}, +}; + +struct clk nfc_clk = { + .name = "nfc_clk", + .id = 0, + .parent = &per_clk[8], +}; + +struct clk audmux_clk = { + .name = "audmux_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_AUDMUX_OFFSET, + .disable = _clk_disable, +}; + +struct clk ata_clk[] = { + { + .name = "ata_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_ATA_OFFSET, + .disable = _clk_disable, + .secondary = &ata_clk[1],}, + { + .name = "ata_ahb_clk", + .id = 0, + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR0, + .enable_shift = MXC_CCM_CGCR0_HCLK_ATA_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk can_clk[] = { + { + .name = "can_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_CAN1_OFFSET, + .disable = _clk_disable,}, + { + .name = "can_clk", + .id = 1, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_CAN2_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk csi_clk[] = { + { + .name = "csi_clk", + .id = 0, + .parent = &per_clk[0], + .secondary = &csi_clk[1],}, + { + .name = "csi_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_CSI_OFFSET, + .disable = _clk_disable, + .secondary = &csi_clk[2],}, + { + .name = "csi_ahb_clk", + .id = 0, + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR0, + .enable_shift = MXC_CCM_CGCR0_HCLK_CSI_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk cspi_clk[] = { + { + .name = "cspi_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_CSPI1_OFFSET, + .disable = _clk_disable,}, + { + .name = "cspi_clk", + .id = 1, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_CSPI2_OFFSET, + .disable = _clk_disable,}, + { + .name = "cspi_clk", + .id = 2, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_CSPI3_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk dryice_clk = { + .name = "dryice_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_DRYICE_OFFSET, + .disable = _clk_disable, +}; + +struct clk ect_clk = { + .name = "ect_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_ECT_OFFSET, + .disable = _clk_disable, +}; + +struct clk epit1_clk[] = { + { + .name = "epit_clk", + .id = 0, + .parent = &per_clk[1], + .secondary = &epit1_clk[1],}, + { + .name = "epit_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_EPIT1_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk epit2_clk[] = { + { + .name = "epit_clk", + .id = 1, + .parent = &per_clk[1], + .secondary = &epit2_clk[1],}, + { + .name = "epit_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_EPIT2_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk esai_clk[] = { + { + .name = "esai_clk", + .id = 0, + .parent = &per_clk[2], + .secondary = &esai_clk[1],}, + { + .name = "esai_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_ESAI_OFFSET, + .disable = _clk_disable, + .secondary = &esai_clk[2],}, + { + .name = "esai_ahb_clk", + .id = 0, + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR0, + .enable_shift = MXC_CCM_CGCR0_HCLK_ESAI_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk esdhc1_clk[] = { + { + .name = "esdhc_clk", + .id = 0, + .parent = &per_clk[3], + .secondary = &esdhc1_clk[1],}, + { + .name = "esdhc_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_ESDHC1_OFFSET, + .disable = _clk_disable, + .secondary = &esdhc1_clk[2],}, + { + .name = "esdhc_ahb_clk", + .id = 0, + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR0, + .enable_shift = MXC_CCM_CGCR0_HCLK_ESDHC1_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk esdhc2_clk[] = { + { + .name = "esdhc_clk", + .id = 1, + .parent = &per_clk[4], + .secondary = &esdhc2_clk[1],}, + { + .name = "esdhc_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_ESDHC2_OFFSET, + .disable = _clk_disable, + .secondary = &esdhc2_clk[2],}, + { + .name = "esdhc_ahb_clk", + .id = 1, + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR0, + .enable_shift = MXC_CCM_CGCR0_HCLK_ESDHC2_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk fec_clk[] = { + { + .name = "fec_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_FEC_OFFSET, + .disable = _clk_disable, + .secondary = &fec_clk[1],}, + { + .name = "fec_ahb_clk", + .id = 0, + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR0, + .enable_shift = MXC_CCM_CGCR0_HCLK_FEC_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk gpio_clk[] = { + { + .name = "gpio_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_GPIO1_OFFSET, + .disable = _clk_disable,}, + { + .name = "gpio_clk", + .id = 1, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_GPIO2_OFFSET, + .disable = _clk_disable,}, + { + .name = "gpio_clk", + .id = 2, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_GPIO3_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk gpt1_clk[] = { + { + .name = "gpt_clk", + .id = 0, + .parent = &per_clk[5], + .secondary = &gpt1_clk[1],}, + { + .name = "gpt_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_GPT1_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk gpt2_clk[] = { + { + .name = "gpt_clk", + .id = 1, + .parent = &per_clk[5], + .secondary = &gpt1_clk[1],}, + { + .name = "gpt_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_GPT2_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk gpt3_clk[] = { + { + .name = "gpt_clk", + .id = 2, + .parent = &per_clk[5], + .secondary = &gpt1_clk[1],}, + { + .name = "gpt_ipg_clk", + .id = 2, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_GPT3_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk gpt4_clk[] = { + { + .name = "gpt_clk", + .id = 3, + .parent = &per_clk[5], + .secondary = &gpt1_clk[1],}, + { + .name = "gpt_ipg_clk", + .id = 3, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_GPT4_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk i2c_clk[] = { + { + .name = "i2c_clk", + .id = 0, + .parent = &per_clk[6],}, + { + .name = "i2c_clk", + .id = 1, + .parent = &per_clk[6],}, + { + .name = "i2c_clk", + .id = 2, + .parent = &per_clk[6],}, +}; + +struct clk iim_clk = { + .name = "iim_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_IIM_OFFSET, + .disable = _clk_disable, +}; + +struct clk iomuxc_clk = { + .name = "iomuxc_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_IOMUXC_OFFSET, + .disable = _clk_disable, +}; + +struct clk kpp_clk = { + .name = "kpp_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_KPP_OFFSET, + .disable = _clk_disable, +}; + +struct clk lcdc_clk[] = { + { + .name = "lcdc_clk", + .id = 0, + .parent = &per_clk[7], + .secondary = &lcdc_clk[1], + .round_rate = _clk_parent_round_rate, + .set_rate = _clk_parent_set_rate,}, + { + .name = "lcdc_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_LCDC_OFFSET, + .disable = _clk_disable, + .secondary = &lcdc_clk[2],}, + { + .name = "lcdc_ahb_clk", + .id = 0, + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR0, + .enable_shift = MXC_CCM_CGCR0_HCLK_LCDC_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk owire_clk[] = { + { + .name = "owire_clk", + .id = 0, + .parent = &per_clk[9], + .secondary = &owire_clk[1],}, + { + .name = "owire_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_OWIRE_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk pwm1_clk[] = { + { + .name = "pwm_clk", + .id = 0, + .parent = &per_clk[10], + .secondary = &pwm1_clk[1],}, + { + .name = "pwm_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR1, + .enable_shift = MXC_CCM_CGCR1_PWM1_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk pwm2_clk[] = { + { + .name = "pwm_clk", + .id = 1, + .parent = &per_clk[10], + .secondary = &pwm2_clk[1],}, + { + .name = "pwm_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_PWM2_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk pwm3_clk[] = { + { + .name = "pwm_clk", + .id = 2, + .parent = &per_clk[10], + .secondary = &pwm3_clk[1],}, + { + .name = "pwm_ipg_clk", + .id = 2, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_PWM3_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk pwm4_clk[] = { + { + .name = "pwm_clk", + .id = 3, + .parent = &per_clk[10], + .secondary = &pwm4_clk[1],}, + { + .name = "pwm_ipg_clk", + .id = 3, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_PWM3_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk rng_clk = { + .name = "rng_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_RNGB_OFFSET, + .disable = _clk_disable, +}; + +struct clk scc_clk = { + .name = "scc_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_SCC_OFFSET, + .disable = _clk_disable, +}; + +struct clk sdma_clk[] = { + { + .name = "sdma_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_SDMA_OFFSET, + .disable = _clk_disable, + .secondary = &sdma_clk[1],}, + { + .name = "sdma_ahb_clk", + .id = 0, + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR0, + .enable_shift = MXC_CCM_CGCR0_HCLK_SDMA_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk sim1_clk[] = { + { + .name = "sim1_clk", + .id = 0, + .parent = &per_clk[11], + .secondary = &sim1_clk[1],}, + { + .name = "sim_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_SIM1_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk sim2_clk[] = { + { + .name = "sim2_clk", + .id = 1, + .parent = &per_clk[12], + .secondary = &sim2_clk[1],}, + { + .name = "sim_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_SIM2_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk slcdc_clk[] = { + { + .name = "slcdc_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_SLCDC_OFFSET, + .disable = _clk_disable, + .secondary = &slcdc_clk[1],}, + { + .name = "slcdc_ahb_clk", + .id = 0, + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR0, + .enable_shift = MXC_CCM_CGCR0_HCLK_SLCDC_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk spba_clk = { + .name = "spba_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_SPBA_OFFSET, + .disable = _clk_disable, +}; + +struct clk ssi1_clk[] = { + { + .name = "ssi_clk", + .id = 0, + .parent = &per_clk[13], + .secondary = &ssi1_clk[1],}, + { + .name = "ssi_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_SSI1_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk ssi2_clk[] = { + { + .name = "ssi_clk", + .id = 1, + .parent = &per_clk[14], + .secondary = &ssi2_clk[1],}, + { + .name = "ssi_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_SSI2_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk tchscrn_clk = { + .name = "tchscrn_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_TCHSCRN_OFFSET, + .disable = _clk_disable, +}; + +struct clk uart1_clk[] = { + { + .name = "uart_clk", + .id = 0, + .parent = &per_clk[15], + .secondary = &uart1_clk[1],}, + { + .name = "uart_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_UART1_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk uart2_clk[] = { + { + .name = "uart_clk", + .id = 1, + .parent = &per_clk[15], + .secondary = &uart2_clk[1],}, + { + .name = "uart_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_UART2_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk uart3_clk[] = { + { + .name = "uart_clk", + .id = 2, + .parent = &per_clk[15], + .secondary = &uart3_clk[1],}, + { + .name = "uart_ipg_clk", + .id = 2, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_UART3_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk uart4_clk[] = { + { + .name = "uart_clk", + .id = 3, + .parent = &per_clk[15], + .secondary = &uart4_clk[1],}, + { + .name = "uart_ipg_clk", + .id = 3, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_UART4_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk uart5_clk[] = { + { + .name = "uart_clk", + .id = 4, + .parent = &per_clk[15], + .secondary = &uart5_clk[1],}, + { + .name = "uart_ipg_clk", + .id = 4, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_UART5_OFFSET, + .disable = _clk_disable,}, +}; + +struct clk wdog_clk = { + .name = "wdog_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGCR2, + .enable_shift = MXC_CCM_CGCR2_WDOG_OFFSET, + .disable = _clk_disable, +}; + +static unsigned long _clk_usb_round_rate(struct clk *clk, unsigned long rate) +{ + unsigned long div; + + div = clk->parent->rate / rate; + if (clk->parent->rate % rate) + div++; + + if (div > 64) + return -EINVAL; + + return clk->parent->rate / div; +} + +static int _clk_usb_set_rate(struct clk *clk, unsigned long rate) +{ + unsigned long reg; + unsigned long div; + + div = clk->parent->rate / rate; + + if (clk->parent->rate / div != rate) + return -EINVAL; + if (div > 64) + return -EINVAL; + + reg = __raw_readl(MXC_CCM_CCTL) & ~MXC_CCM_CCTL_USB_DIV_MASK; + reg |= (div - 1) << MXC_CCM_CCTL_USB_DIV_OFFSET; + __raw_writel(reg, MXC_CCM_CCTL); + + return 0; +} + +static void _clk_usb_recalc(struct clk *clk) +{ + unsigned long div = + __raw_readl(MXC_CCM_CCTL) & MXC_CCM_CCTL_USB_DIV_MASK; + + div >>= MXC_CCM_CCTL_USB_DIV_OFFSET; + + clk->rate = clk->parent->rate / (div + 1); +} + +static int _clk_usb_set_parent(struct clk *clk, struct clk *parent) +{ + unsigned long mcr; + + if (parent != &upll_clk && parent != &ahb_clk) + return -EINVAL; + + clk->parent = parent; + mcr = __raw_readl(MXC_CCM_MCR); + if (parent == &ahb_clk) + mcr |= (1 << MXC_CCM_MCR_USB_CLK_MUX_OFFSET); + else + mcr &= ~(1 << MXC_CCM_MCR_USB_CLK_MUX_OFFSET); + + __raw_writel(mcr, MXC_CCM_MCR); + + return 0; +} + +static struct clk usb_clk = { + .name = "usb_clk", + .parent = &upll_clk, + .recalc = _clk_usb_recalc, + .set_rate = _clk_usb_set_rate, + .round_rate = _clk_usb_round_rate, + .set_parent = _clk_usb_set_parent, +}; + +/* CLKO */ + +static unsigned long _clk_clko_round_rate(struct clk *clk, unsigned long rate) +{ + unsigned long div; + + div = clk->parent->rate / rate; + if (clk->parent->rate % rate) + div++; + + if (div > 64) + return -EINVAL; + + return clk->parent->rate / div; +} + +static int _clk_clko_set_rate(struct clk *clk, unsigned long rate) +{ + unsigned long reg; + unsigned long div; + + div = clk->parent->rate / rate; + + if ((clk->parent->rate / div) != rate) + return -EINVAL; + if (div > 64) + return -EINVAL; + + reg = __raw_readl(MXC_CCM_MCR) & ~MXC_CCM_MCR_CLKO_DIV_MASK; + reg |= (div - 1) << MXC_CCM_MCR_CLKO_DIV_OFFSET; + __raw_writel(reg, MXC_CCM_MCR); + + return 0; +} + +static void _clk_clko_recalc(struct clk *clk) +{ + unsigned long div = + __raw_readl(MXC_CCM_MCR) & MXC_CCM_MCR_CLKO_DIV_MASK; + + div >>= MXC_CCM_MCR_CLKO_DIV_OFFSET; + + clk->rate = clk->parent->rate / (div + 1); +} + +static struct clk *clko_sources[] = { + &osc32k_clk, /* 0x0 */ + &osc24m_clk, /* 0x1 */ + &cpu_clk, /* 0x2 */ + &ahb_clk, /* 0x3 */ + &ipg_clk, /* 0x4 */ + NULL, /* 0x5 */ + NULL, /* 0x6 */ + NULL, /* 0x7 */ + NULL, /* 0x8 */ + NULL, /* 0x9 */ + &per_clk[0], /* 0xA */ + &per_clk[2], /* 0xB */ + &per_clk[13], /* 0xC */ + &per_clk[14], /* 0xD */ + &usb_clk, /* 0xE */ + NULL, /* 0xF */ +}; + +#define NR_CLKO_SOURCES (sizeof(clko_sources) / sizeof(struct clk *)) + +static int _clk_clko_set_parent(struct clk *clk, struct clk *parent) +{ + unsigned long reg = + __raw_readl(MXC_CCM_MCR) & ~MXC_CCM_MCR_CLKO_SEL_MASK; + struct clk **src; + int i; + + for (i = 0, src = clko_sources; i < NR_CLKO_SOURCES; i++, src++) + if (*src == parent) + break; + + if (i == NR_CLKO_SOURCES) + return -EINVAL; + + clk->parent = parent; + + reg |= i << MXC_CCM_MCR_CLKO_SEL_OFFSET; + + __raw_writel(reg, MXC_CCM_MCR); + + return 0; +} + +static struct clk clko_clk = { + .name = "clko_clk", + .recalc = _clk_clko_recalc, + .set_rate = _clk_clko_set_rate, + .round_rate = _clk_clko_round_rate, + .set_parent = _clk_clko_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_MCR, + .enable_shift = MXC_CCM_MCR_CLKO_EN_OFFSET, + .disable = _clk_disable, +}; + +static struct clk *mxc_clks[] = { + &osc24m_clk, + &osc32k_clk, + &mpll_clk, + &upll_clk, + &cpu_clk, + &ahb_clk, + &ipg_clk, + &usb_ahb_clk, + &per_clk[0], + &per_clk[1], + &per_clk[2], + &per_clk[3], + &per_clk[4], + &per_clk[5], + &per_clk[6], + &per_clk[7], + &per_clk[8], + &per_clk[9], + &per_clk[10], + &per_clk[11], + &per_clk[12], + &per_clk[13], + &per_clk[14], + &per_clk[15], + &nfc_clk, + &audmux_clk, + &ata_clk[0], + &ata_clk[1], + &can_clk[0], + &can_clk[1], + &csi_clk[0], + &csi_clk[1], + &csi_clk[2], + &cspi_clk[0], + &cspi_clk[1], + &cspi_clk[2], + &dryice_clk, + &ect_clk, + &epit1_clk[0], + &epit1_clk[1], + &epit2_clk[0], + &epit2_clk[1], + &esai_clk[0], + &esai_clk[1], + &esai_clk[2], + &esdhc1_clk[0], + &esdhc1_clk[1], + &esdhc1_clk[2], + &esdhc2_clk[0], + &esdhc2_clk[1], + &esdhc2_clk[2], + &fec_clk[0], + &fec_clk[1], + &gpio_clk[0], + &gpio_clk[1], + &gpio_clk[2], + &gpt1_clk[0], + &gpt1_clk[1], + &gpt2_clk[0], + &gpt2_clk[1], + &gpt3_clk[0], + &gpt3_clk[1], + &gpt4_clk[0], + &gpt4_clk[1], + &i2c_clk[0], + &i2c_clk[1], + &i2c_clk[2], + &iim_clk, + &iomuxc_clk, + &kpp_clk, + &lcdc_clk[0], + &lcdc_clk[1], + &lcdc_clk[2], + &owire_clk[0], + &owire_clk[1], + &pwm1_clk[0], + &pwm1_clk[1], + &pwm2_clk[0], + &pwm2_clk[1], + &pwm3_clk[0], + &pwm3_clk[1], + &pwm4_clk[0], + &pwm4_clk[1], + &rng_clk, + &scc_clk, + &sdma_clk[0], + &sdma_clk[1], + &sim1_clk[0], + &sim1_clk[1], + &sim2_clk[0], + &sim2_clk[1], + &slcdc_clk[0], + &slcdc_clk[1], + &spba_clk, + &ssi1_clk[0], + &ssi1_clk[1], + &ssi2_clk[0], + &ssi2_clk[1], + &tchscrn_clk, + &uart1_clk[0], + &uart1_clk[1], + &uart2_clk[0], + &uart2_clk[1], + &uart3_clk[0], + &uart3_clk[1], + &uart4_clk[0], + &uart4_clk[1], + &uart5_clk[0], + &uart5_clk[1], + &wdog_clk, + &usb_clk, + &clko_clk, +}; + +/*! + * Function to get timer clock rate early in boot process before clock tree is + * initialized. + * + * @return Clock rate for timer + */ +unsigned long __init clk_early_get_timer_rate(void) +{ + upll_clk.recalc(&upll_clk); + per_clk[5].recalc(&per_clk[5]); + per_clk[5].enable(&per_clk[5]); + + return per_clk[5].rate; +} + +extern void propagate_rate(struct clk *tclk); + +int __init mx25_clocks_init(unsigned long fref) +{ + int i; + struct clk **clkp; + + for (clkp = mxc_clks; clkp < mxc_clks + ARRAY_SIZE(mxc_clks); clkp++) + clk_register(*clkp); + + /* Turn off all possible clocks */ + __raw_writel((1 << MXC_CCM_CGCR0_HCLK_EMI_OFFSET), MXC_CCM_CGCR0); + + __raw_writel((1 << MXC_CCM_CGCR1_GPT1_OFFSET) | + (1 << MXC_CCM_CGCR1_IIM_OFFSET), MXC_CCM_CGCR1); + __raw_writel(1 << MXC_CCM_CGCR2_SCC_OFFSET, MXC_CCM_CGCR2); + + /* Init all perclk sources to ahb clock*/ + for (i = 0; i < (sizeof(per_clk) / sizeof(struct clk)); i++) + per_clk[i].set_parent(&per_clk[i], &ahb_clk); + + /* This will propagate to all children and init all the clock rates */ + propagate_rate(&osc24m_clk); + propagate_rate(&osc32k_clk); + + /* GPT clock must be derived from AHB clock */ + clk_set_rate(&per_clk[5], ahb_clk.rate / 10); + + /* LCDC clock must be derived from UPLL clock */ + clk_set_parent(&per_clk[7], &upll_clk); + clk_set_rate(&per_clk[7], upll_clk.rate); + + /* the NFC clock must be derived from AHB clock */ + clk_set_parent(&per_clk[8], &ahb_clk); + clk_set_rate(&per_clk[8], ahb_clk.rate / 6); + + /* sim clock */ + clk_set_rate(&per_clk[11], ahb_clk.rate / 2); + + /* the csi clock must be derived from UPLL clock */ + clk_set_parent(&per_clk[0], &upll_clk); + clk_set_rate(&per_clk[0], upll_clk.rate / 5); + + pr_info("Clock input source is %ld\n", osc24m_clk.rate); + + clk_enable(&emi_clk); + clk_enable(&gpio_clk[0]); + clk_enable(&gpio_clk[1]); + clk_enable(&gpio_clk[2]); + clk_enable(&iim_clk); + clk_enable(&gpt1_clk[0]); + clk_enable(&iomuxc_clk); + clk_enable(&scc_clk); + + mxc_timer_init(&gpt1_clk[0], IO_ADDRESS(GPT1_BASE_ADDR), MXC_INT_GPT1); + return 0; +} diff --git a/arch/arm/mach-mx25/cpu.c b/arch/arm/mach-mx25/cpu.c new file mode 100644 index 000000000000..d44ad5530921 --- /dev/null +++ b/arch/arm/mach-mx25/cpu.c @@ -0,0 +1,60 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mach-mx25/cpu.c + * + * @brief This file contains the CPU initialization code. + * + * @ingroup MSL_MX25 + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <mach/hardware.h> +#include <asm/io.h> + +/*! + * CPU initialization. It is called by fixup_mxc_board() + */ +void __init mxc_cpu_init(void) +{ + if (!system_rev) + mxc_set_system_rev(0x25, CHIP_REV_1_0); +} + +static int __init post_cpu_init(void) +{ + void __iomem *base; + unsigned int reg; + + base = IO_ADDRESS(AIPS1_BASE_ADDR); + __raw_writel(0x0, base + 0x40); + __raw_writel(0x0, base + 0x44); + __raw_writel(0x0, base + 0x48); + __raw_writel(0x0, base + 0x4C); + reg = __raw_readl(base + 0x50) & 0x00FFFFFF; + __raw_writel(reg, base + 0x50); + + base = IO_ADDRESS(AIPS2_BASE_ADDR); + __raw_writel(0x0, base + 0x40); + __raw_writel(0x0, base + 0x44); + __raw_writel(0x0, base + 0x48); + __raw_writel(0x0, base + 0x4C); + reg = __raw_readl(base + 0x50) & 0x00FFFFFF; + __raw_writel(reg, base + 0x50); + + return 0; +} +postcore_initcall(post_cpu_init); diff --git a/arch/arm/mach-mx25/crm_regs.h b/arch/arm/mach-mx25/crm_regs.h new file mode 100644 index 000000000000..a675232ab706 --- /dev/null +++ b/arch/arm/mach-mx25/crm_regs.h @@ -0,0 +1,215 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ARCH_ARM_MACH_MX25_CRM_REGS_H__ +#define __ARCH_ARM_MACH_MX25_CRM_REGS_H__ + +#include <mach/hardware.h> + +#define MXC_CCM_BASE ((char *)IO_ADDRESS(CCM_BASE_ADDR)) + +/* Register offsets */ +#define MXC_CCM_MPCTL (MXC_CCM_BASE + 0x00) +#define MXC_CCM_UPCTL (MXC_CCM_BASE + 0x04) +#define MXC_CCM_CCTL (MXC_CCM_BASE + 0x08) +#define MXC_CCM_CGCR0 (MXC_CCM_BASE + 0x0C) +#define MXC_CCM_CGCR1 (MXC_CCM_BASE + 0x10) +#define MXC_CCM_CGCR2 (MXC_CCM_BASE + 0x14) +#define MXC_CCM_PCDR0 (MXC_CCM_BASE + 0x18) +#define MXC_CCM_PCDR1 (MXC_CCM_BASE + 0x1C) +#define MXC_CCM_PCDR2 (MXC_CCM_BASE + 0x20) +#define MXC_CCM_PCDR3 (MXC_CCM_BASE + 0x24) +#define MXC_CCM_RCSR (MXC_CCM_BASE + 0x28) +#define MXC_CCM_CRDR (MXC_CCM_BASE + 0x2C) +#define MXC_CCM_DCVR0 (MXC_CCM_BASE + 0x30) +#define MXC_CCM_DCVR1 (MXC_CCM_BASE + 0x34) +#define MXC_CCM_DCVR2 (MXC_CCM_BASE + 0x38) +#define MXC_CCM_DCVR3 (MXC_CCM_BASE + 0x3C) +#define MXC_CCM_LTR0 (MXC_CCM_BASE + 0x40) +#define MXC_CCM_LTR1 (MXC_CCM_BASE + 0x44) +#define MXC_CCM_LTR2 (MXC_CCM_BASE + 0x48) +#define MXC_CCM_LTR3 (MXC_CCM_BASE + 0x4C) +#define MXC_CCM_LTBR0 (MXC_CCM_BASE + 0x50) +#define MXC_CCM_LTBR1 (MXC_CCM_BASE + 0x54) +#define MXC_CCM_PMCR0 (MXC_CCM_BASE + 0x58) +#define MXC_CCM_PMCR1 (MXC_CCM_BASE + 0x5C) +#define MXC_CCM_PMCR2 (MXC_CCM_BASE + 0x60) +#define MXC_CCM_MCR (MXC_CCM_BASE + 0x64) +#define MXC_CCM_LPIMR0 (MXC_CCM_BASE + 0x68) +#define MXC_CCM_LPIMR1 (MXC_CCM_BASE + 0x6C) + +#define MXC_CCM_MPCTL_BRMO (1 << 31) +#define MXC_CCM_MPCTL_PD_OFFSET 26 +#define MXC_CCM_MPCTL_PD_MASK (0xf << 26) +#define MXC_CCM_MPCTL_MFD_OFFSET 16 +#define MXC_CCM_MPCTL_MFD_MASK (0x3ff << 16) +#define MXC_CCM_MPCTL_MFI_OFFSET 10 +#define MXC_CCM_MPCTL_MFI_MASK (0xf << 10) +#define MXC_CCM_MPCTL_MFN_OFFSET 0 +#define MXC_CCM_MPCTL_MFN_MASK 0x3ff +#define MXC_CCM_MPCTL_LF (1 << 15) + +#define MXC_CCM_UPCTL_BRMO (1 << 31) +#define MXC_CCM_UPCTL_PD_OFFSET 26 +#define MXC_CCM_UPCTL_PD_MASK (0xf << 26) +#define MXC_CCM_UPCTL_MFD_OFFSET 16 +#define MXC_CCM_UPCTL_MFD_MASK (0x3ff << 16) +#define MXC_CCM_UPCTL_MFI_OFFSET 10 +#define MXC_CCM_UPCTL_MFI_MASK (0xf << 10) +#define MXC_CCM_UPCTL_MFN_OFFSET 0 +#define MXC_CCM_UPCTL_MFN_MASK 0x3ff +#define MXC_CCM_UPCTL_LF (1 << 15) + +#define MXC_CCM_CCTL_ARM_OFFSET 30 +#define MXC_CCM_CCTL_ARM_MASK (0x3 << 30) +#define MXC_CCM_CCTL_AHB_OFFSET 28 +#define MXC_CCM_CCTL_AHB_MASK (0x3 << 28) +#define MXC_CCM_CCTL_MPLL_RST (1 << 27) +#define MXC_CCM_CCTL_UPLL_RST (1 << 26) +#define MXC_CCM_CCTL_LP_CTL_OFFSET 24 +#define MXC_CCM_CCTL_LP_CTL_MASK (0x3 << 24) +#define MXC_CCM_CCTL_LP_MODE_RUN (0x0 << 24) +#define MXC_CCM_CCTL_LP_MODE_WAIT (0x1 << 24) +#define MXC_CCM_CCTL_LP_MODE_DOZE (0x2 << 24) +#define MXC_CCM_CCTL_LP_MODE_STOP (0x3 << 24) +#define MXC_CCM_CCTL_UPLL_DISABLE (1 << 23) +#define MXC_CCM_CCTL_MPLL_BYPASS (1 << 22) +#define MXC_CCM_CCTL_USB_DIV_OFFSET 16 +#define MXC_CCM_CCTL_USB_DIV_MASK (0x3 << 16) +#define MXC_CCM_CCTL_CG_CTRL (1 << 15) +#define MXC_CCM_CCTL_ARM_SRC (1 << 14) +#define MXC_CCM_CCTL_ARM_SRC_OFFSET 14 + +#define MXC_CCM_CGCR0_HCLK_ATA_OFFSET 16 +#define MXC_CCM_CGCR0_HCLK_BROM_OFFSET 17 +#define MXC_CCM_CGCR0_HCLK_CSI_OFFSET 18 +#define MXC_CCM_CGCR0_HCLK_EMI_OFFSET 19 +#define MXC_CCM_CGCR0_HCLK_ESAI_OFFSET 20 +#define MXC_CCM_CGCR0_HCLK_ESDHC1_OFFSET 21 +#define MXC_CCM_CGCR0_HCLK_ESDHC2_OFFSET 22 +#define MXC_CCM_CGCR0_HCLK_FEC_OFFSET 23 +#define MXC_CCM_CGCR0_HCLK_LCDC_OFFSET 24 +#define MXC_CCM_CGCR0_HCLK_RTIC_OFFSET 25 +#define MXC_CCM_CGCR0_HCLK_SDMA_OFFSET 26 +#define MXC_CCM_CGCR0_HCLK_SLCDC_OFFSET 27 +#define MXC_CCM_CGCR0_HCLK_USBOTG_OFFSET 28 + +#define MXC_CCM_CGCR0_PER_CSI_OFFSET 0 +#define MXC_CCM_CGCR0_PER_EPIT_OFFSET 1 +#define MXC_CCM_CGCR0_PER_ESAI_OFFSET 2 +#define MXC_CCM_CGCR0_PER_ESDHC1_OFFSET 3 +#define MXC_CCM_CGCR0_PER_ESDHC2_OFFSET 4 +#define MXC_CCM_CGCR0_PER_GPT_OFFSET 5 +#define MXC_CCM_CGCR0_PER_I2C_OFFSET 6 +#define MXC_CCM_CGCR0_PER_LCDC_OFFSET 7 +#define MXC_CCM_CGCR0_PER_NFC_OFFSET 8 +#define MXC_CCM_CGCR0_PER_OWIRE_OFFSET 9 +#define MXC_CCM_CGCR0_PER_PWM_OFFSET 10 +#define MXC_CCM_CGCR0_PER_SIM1_OFFSET 11 +#define MXC_CCM_CGCR0_PER_SIM2_OFFSET 12 +#define MXC_CCM_CGCR0_PER_SSI1_OFFSET 13 +#define MXC_CCM_CGCR0_PER_SSI2_OFFSET 14 +#define MXC_CCM_CGCR0_PER_UART_OFFSET 15 + +#define MXC_CCM_CGCR1_AUDMUX_OFFSET 0 +#define MXC_CCM_CGCR1_ATA_OFFSET 1 +#define MXC_CCM_CGCR1_CAN1_OFFSET 2 +#define MXC_CCM_CGCR1_CAN2_OFFSET 3 +#define MXC_CCM_CGCR1_CSI_OFFSET 4 +#define MXC_CCM_CGCR1_CSPI1_OFFSET 5 +#define MXC_CCM_CGCR1_CSPI2_OFFSET 6 +#define MXC_CCM_CGCR1_CSPI3_OFFSET 7 +#define MXC_CCM_CGCR1_DRYICE_OFFSET 8 +#define MXC_CCM_CGCR1_ECT_OFFSET 9 +#define MXC_CCM_CGCR1_EPIT1_OFFSET 10 +#define MXC_CCM_CGCR1_EPIT2_OFFSET 11 +#define MXC_CCM_CGCR1_ESAI_OFFSET 12 +#define MXC_CCM_CGCR1_ESDHC1_OFFSET 13 +#define MXC_CCM_CGCR1_ESDHC2_OFFSET 14 +#define MXC_CCM_CGCR1_FEC_OFFSET 15 +#define MXC_CCM_CGCR1_GPIO1_OFFSET 16 +#define MXC_CCM_CGCR1_GPIO2_OFFSET 17 +#define MXC_CCM_CGCR1_GPIO3_OFFSET 18 +#define MXC_CCM_CGCR1_GPT1_OFFSET 19 +#define MXC_CCM_CGCR1_GPT2_OFFSET 20 +#define MXC_CCM_CGCR1_GPT3_OFFSET 21 +#define MXC_CCM_CGCR1_GPT4_OFFSET 22 +#define MXC_CCM_CGCR1_I2C1_OFFSET 23 +#define MXC_CCM_CGCR1_I2C2_OFFSET 24 +#define MXC_CCM_CGCR1_I2C3_OFFSET 25 +#define MXC_CCM_CGCR1_IIM_OFFSET 26 +#define MXC_CCM_CGCR1_IOMUXC_OFFSET 27 +#define MXC_CCM_CGCR1_KPP_OFFSET 28 +#define MXC_CCM_CGCR1_LCDC_OFFSET 29 +#define MXC_CCM_CGCR1_OWIRE_OFFSET 30 +#define MXC_CCM_CGCR1_PWM1_OFFSET 31 + +#define MXC_CCM_CGCR2_PWM2_OFFSET (32-32) +#define MXC_CCM_CGCR2_PWM3_OFFSET (33-32) +#define MXC_CCM_CGCR2_PWM4_OFFSET (34-32) +#define MXC_CCM_CGCR2_RNGB_OFFSET (35-32) +#define MXC_CCM_CGCR2_RTIC_OFFSET (36-32) +#define MXC_CCM_CGCR2_SCC_OFFSET (37-32) +#define MXC_CCM_CGCR2_SDMA_OFFSET (38-32) +#define MXC_CCM_CGCR2_SIM1_OFFSET (39-32) +#define MXC_CCM_CGCR2_SIM2_OFFSET (40-32) +#define MXC_CCM_CGCR2_SLCDC_OFFSET (41-32) +#define MXC_CCM_CGCR2_SPBA_OFFSET (42-32) +#define MXC_CCM_CGCR2_SSI1_OFFSET (43-32) +#define MXC_CCM_CGCR2_SSI2_OFFSET (44-32) +#define MXC_CCM_CGCR2_TCHSCRN_OFFSET (45-32) +#define MXC_CCM_CGCR2_UART1_OFFSET (46-32) +#define MXC_CCM_CGCR2_UART2_OFFSET (47-32) +#define MXC_CCM_CGCR2_UART3_OFFSET (48-32) +#define MXC_CCM_CGCR2_UART4_OFFSET (49-32) +#define MXC_CCM_CGCR2_UART5_OFFSET (50-32) +#define MXC_CCM_CGCR2_WDOG_OFFSET (51-32) + +#define MXC_CCM_CGCR0_STOP_MODE_MASK \ + ((1 << MXC_CCM_CGCR0_HCLK_SLCDC_OFFSET) | \ + (1 << MXC_CCM_CGCR0_HCLK_RTIC_OFFSET) | \ + (1 << MXC_CCM_CGCR0_HCLK_EMI_OFFSET) | \ + (1 << MXC_CCM_CGCR0_HCLK_BROM_OFFSET)) + +#define MXC_CCM_CGCR1_STOP_MODE_MASK ((1 << MXC_CCM_CGCR1_IIM_OFFSET) | \ + (1 << MXC_CCM_CGCR1_CAN2_OFFSET) | \ + (1 << MXC_CCM_CGCR1_CAN1_OFFSET)) + +#define MXC_CCM_CGCR2_STOP_MODE_MASK ((1 << MXC_CCM_CGCR2_SPBA_OFFSET) | \ + (1 << MXC_CCM_CGCR2_SDMA_OFFSET) | \ + (1 << MXC_CCM_CGCR2_RTIC_OFFSET)) + +#define MXC_CCM_PCDR1_PERDIV1_MASK 0x3f + +#define MXC_CCM_RCSR_NF16B (1 << 14) + +#define MXC_CCM_PMCR2_VSTBY (1 << 17) +#define MXC_CCM_PMCR2_OSC24M_DOWN (1 << 16) + +#define MXC_CCM_MCR_USB_XTAL_MUX_OFFSET 31 +#define MXC_CCM_MCR_CLKO_EN_OFFSET 30 +#define MXC_CCM_MCR_CLKO_DIV_OFFSET 24 +#define MXC_CCM_MCR_CLKO_DIV_MASK (0x3F << 24) +#define MXC_CCM_MCR_CLKO_SEL_OFFSET 20 +#define MXC_CCM_MCR_CLKO_SEL_MASK (0xF << 20) +#define MXC_CCM_MCR_ESAI_CLK_MUX_OFFSET 19 +#define MXC_CCM_MCR_SSI2_CLK_MUX_OFFSET 18 +#define MXC_CCM_MCR_SSI1_CLK_MUX_OFFSET 17 +#define MXC_CCM_MCR_USB_CLK_MUX_OFFSET 16 + +#define MXC_CCM_MCR_PER_CLK_MUX_MASK (0xFFFF << 0) + +#define MXC_CCM_LPIMR0_MASK 0xFFFFFFFF +#define MXC_CCM_LPIMR1_MASK 0xFFFFFFFF + +#endif /* __ARCH_ARM_MACH_MX25_CRM_REGS_H__ */ diff --git a/arch/arm/mach-mx25/devices.c b/arch/arm/mach-mx25/devices.c new file mode 100644 index 000000000000..c8d69aa7108e --- /dev/null +++ b/arch/arm/mach-mx25/devices.c @@ -0,0 +1,615 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/spi/spi.h> + +#include <mach/hardware.h> +#include <mach/mmc.h> +#include <mach/spba.h> +#include <mach/sdma.h> + +#include "iomux.h" +#include "sdma_script_code.h" +#include "board-mx25_3stack.h" + +void mxc_sdma_get_script_info(sdma_script_start_addrs * sdma_script_addr) +{ + sdma_script_addr->mxc_sdma_ap_2_ap_addr = ap_2_ap_ADDR; + sdma_script_addr->mxc_sdma_ap_2_bp_addr = -1; + sdma_script_addr->mxc_sdma_bp_2_ap_addr = -1; + sdma_script_addr->mxc_sdma_loopback_on_dsp_side_addr = -1; + sdma_script_addr->mxc_sdma_mcu_interrupt_only_addr = -1; + + sdma_script_addr->mxc_sdma_firi_2_per_addr = -1; + sdma_script_addr->mxc_sdma_firi_2_mcu_addr = -1; + sdma_script_addr->mxc_sdma_per_2_firi_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_firi_addr = -1; + + sdma_script_addr->mxc_sdma_uart_2_per_addr = uart_2_per_ADDR; + sdma_script_addr->mxc_sdma_uart_2_mcu_addr = uart_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_per_2_app_addr = per_2_app_ADDR; + sdma_script_addr->mxc_sdma_mcu_2_app_addr = mcu_2_app_ADDR; + + sdma_script_addr->mxc_sdma_per_2_per_addr = -1; + + sdma_script_addr->mxc_sdma_uartsh_2_per_addr = uartsh_2_per_ADDR; + sdma_script_addr->mxc_sdma_uartsh_2_mcu_addr = uartsh_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_per_2_shp_addr = per_2_shp_ADDR; + sdma_script_addr->mxc_sdma_mcu_2_shp_addr = mcu_2_shp_ADDR; + + sdma_script_addr->mxc_sdma_ata_2_mcu_addr = ata_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_mcu_2_ata_addr = mcu_2_ata_ADDR; + + sdma_script_addr->mxc_sdma_app_2_per_addr = app_2_per_ADDR; + sdma_script_addr->mxc_sdma_app_2_mcu_addr = app_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_shp_2_per_addr = shp_2_per_ADDR; + sdma_script_addr->mxc_sdma_shp_2_mcu_addr = shp_2_mcu_ADDR; + + sdma_script_addr->mxc_sdma_mshc_2_mcu_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_mshc_addr = -1; + + sdma_script_addr->mxc_sdma_spdif_2_mcu_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_spdif_addr = -1; + + sdma_script_addr->mxc_sdma_asrc_2_mcu_addr = -1; + + sdma_script_addr->mxc_sdma_dptc_dvfs_addr = -1; + sdma_script_addr->mxc_sdma_ext_mem_2_ipu_addr = ext_mem__ipu_ram_ADDR; + sdma_script_addr->mxc_sdma_descrambler_addr = -1; + + sdma_script_addr->mxc_sdma_start_addr = (unsigned short *)sdma_code; + sdma_script_addr->mxc_sdma_ram_code_size = RAM_CODE_SIZE; + sdma_script_addr->mxc_sdma_ram_code_start_addr = RAM_CODE_START_ADDR; +} + +static void mxc_nop_release(struct device *dev) +{ + /* Nothing */ +} + +#if defined(CONFIG_RTC_DRV_IMXDI) || defined(CONFIG_RTC_DRV_IMXDI_MODULE) +static struct resource rtc_resources[] = { + { + .start = SRTC_BASE_ADDR, + .end = SRTC_BASE_ADDR + 0x40, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_INT_DRYICE_NORM, + .flags = IORESOURCE_IRQ, + }, +}; +static struct platform_device imxdi_rtc_device = { + .name = "imxdi_rtc", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(rtc_resources), + .resource = rtc_resources, +}; +static void mxc_init_rtc(void) +{ + (void)platform_device_register(&imxdi_rtc_device); +} +#else +static inline void mxc_init_rtc(void) +{ +} +#endif + +#if defined(CONFIG_MXC_WATCHDOG) || defined(CONFIG_MXC_WATCHDOG_MODULE) +static struct resource wdt_resources[] = { + { + .start = WDOG1_BASE_ADDR, + .end = WDOG1_BASE_ADDR + 0x30, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device mxc_wdt_device = { + .name = "mxc_wdt", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(wdt_resources), + .resource = wdt_resources, +}; + +static void mxc_init_wdt(void) +{ + (void)platform_device_register(&mxc_wdt_device); +} +#else +static inline void mxc_init_wdt(void) +{ +} +#endif + +/* SPI controller and device data */ +#if defined(CONFIG_SPI_MXC) || defined(CONFIG_SPI_MXC_MODULE) + +#ifdef CONFIG_SPI_MXC_SELECT1 +/*! + * Resource definition for the CSPI1 + */ +static struct resource mxcspi1_resources[] = { + [0] = { + .start = CSPI1_BASE_ADDR, + .end = CSPI1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CSPI1, + .end = MXC_INT_CSPI1, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC CSPI1 */ +static struct mxc_spi_master mxcspi1_data = { + .maxchipselect = 4, + .spi_version = 7, +}; + +/*! Device Definition for MXC CSPI1 */ +static struct platform_device mxcspi1_device = { + .name = "mxc_spi", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxcspi1_data, + }, + .num_resources = ARRAY_SIZE(mxcspi1_resources), + .resource = mxcspi1_resources, +}; + +#endif /* CONFIG_SPI_MXC_SELECT1 */ + +#ifdef CONFIG_SPI_MXC_SELECT2 +/*! + * Resource definition for the CSPI2 + */ +static struct resource mxcspi2_resources[] = { + [0] = { + .start = CSPI2_BASE_ADDR, + .end = CSPI2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CSPI2, + .end = MXC_INT_CSPI2, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC CSPI2 */ +static struct mxc_spi_master mxcspi2_data = { + .maxchipselect = 4, + .spi_version = 7, +}; + +/*! Device Definition for MXC CSPI2 */ +static struct platform_device mxcspi2_device = { + .name = "mxc_spi", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxcspi2_data, + }, + .num_resources = ARRAY_SIZE(mxcspi2_resources), + .resource = mxcspi2_resources, +}; +#endif /* CONFIG_SPI_MXC_SELECT2 */ + +#ifdef CONFIG_SPI_MXC_SELECT3 +/*! + * Resource definition for the CSPI3 + */ +static struct resource mxcspi3_resources[] = { + [0] = { + .start = CSPI3_BASE_ADDR, + .end = CSPI3_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CSPI3, + .end = MXC_INT_CSPI3, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC CSPI3 */ +static struct mxc_spi_master mxcspi3_data = { + .maxchipselect = 4, + .spi_version = 7, +}; + +/*! Device Definition for MXC CSPI3 */ +static struct platform_device mxcspi3_device = { + .name = "mxc_spi", + .id = 2, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxcspi3_data, + }, + .num_resources = ARRAY_SIZE(mxcspi3_resources), + .resource = mxcspi3_resources, +}; +#endif /* CONFIG_SPI_MXC_SELECT3 */ + +static inline void mxc_init_spi(void) +{ + spba_take_ownership(SPBA_CSPI2, SPBA_MASTER_A); + spba_take_ownership(SPBA_CSPI3, SPBA_MASTER_A); + +#ifdef CONFIG_SPI_MXC_SELECT1 + if (platform_device_register(&mxcspi1_device) < 0) + printk(KERN_ERR "Error: Registering the SPI Controller_1\n"); +#endif /* CONFIG_SPI_MXC_SELECT1 */ +#ifdef CONFIG_SPI_MXC_SELECT2 + if (platform_device_register(&mxcspi2_device) < 0) + printk(KERN_ERR "Error: Registering the SPI Controller_2\n"); +#endif /* CONFIG_SPI_MXC_SELECT2 */ +#ifdef CONFIG_SPI_MXC_SELECT3 + if (platform_device_register(&mxcspi3_device) < 0) + printk(KERN_ERR "Error: Registering the SPI Controller_3\n"); +#endif /* CONFIG_SPI_MXC_SELECT3 */ +} +#else +static inline void mxc_init_spi(void) +{ +} +#endif + +/* I2C controller and device data */ +#if defined(CONFIG_I2C_MXC) || defined(CONFIG_I2C_MXC_MODULE) + +#ifdef CONFIG_I2C_MXC_SELECT1 +/*! + * Resource definition for the I2C1 + */ +static struct resource mxci2c1_resources[] = { + [0] = { + .start = I2C_BASE_ADDR, + .end = I2C_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C, + .end = MXC_INT_I2C, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c1_data = { + .i2c_clk = 100000, +}; +#endif + +#ifdef CONFIG_I2C_MXC_SELECT2 +/*! + * Resource definition for the I2C2 + */ +static struct resource mxci2c2_resources[] = { + [0] = { + .start = I2C2_BASE_ADDR, + .end = I2C2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C2, + .end = MXC_INT_I2C2, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c2_data = { + .i2c_clk = 100000, +}; +#endif + +#ifdef CONFIG_I2C_MXC_SELECT3 +/*! + * Resource definition for the I2C3 + */ +static struct resource mxci2c3_resources[] = { + [0] = { + .start = I2C3_BASE_ADDR, + .end = I2C3_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C3, + .end = MXC_INT_I2C3, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c3_data = { + .i2c_clk = 100000, +}; +#endif + +/*! Device Definition for MXC I2C1 */ +static struct platform_device mxci2c_devices[] = { +#ifdef CONFIG_I2C_MXC_SELECT1 + { + .name = "mxc_i2c", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c1_data, + }, + .num_resources = ARRAY_SIZE(mxci2c1_resources), + .resource = mxci2c1_resources,}, +#endif +#ifdef CONFIG_I2C_MXC_SELECT2 + { + .name = "mxc_i2c", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c2_data, + }, + .num_resources = ARRAY_SIZE(mxci2c2_resources), + .resource = mxci2c2_resources,}, +#endif +#ifdef CONFIG_I2C_MXC_SELECT3 + { + .name = "mxc_i2c", + .id = 2, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c3_data, + }, + .num_resources = ARRAY_SIZE(mxci2c3_resources), + .resource = mxci2c3_resources,}, +#endif +}; + +static inline void mxc_init_i2c(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mxci2c_devices); i++) { + if (platform_device_register(&mxci2c_devices[i]) < 0) + dev_err(&mxci2c_devices[i].dev, + "Unable to register I2C device\n"); + } +} +#else +static inline void mxc_init_i2c(void) +{ +} +#endif + +struct mxc_gpio_port mxc_gpio_ports[] = { + [0] = { + .chip.label = "gpio-0", + .base = IO_ADDRESS(GPIO1_BASE_ADDR), + .irq = MXC_INT_GPIO1, + .irq_high = 0, + .virtual_irq_start = MXC_GPIO_IRQ_START + }, + [1] = { + .chip.label = "gpio-1", + .base = IO_ADDRESS(GPIO2_BASE_ADDR), + .irq = MXC_INT_GPIO2, + .irq_high = 0, + .virtual_irq_start = MXC_GPIO_IRQ_START + 32 + }, + [2] = { + .chip.label = "gpio-2", + .base = IO_ADDRESS(GPIO3_BASE_ADDR), + .irq = MXC_INT_GPIO3, + .irq_high = 0, + .virtual_irq_start = MXC_GPIO_IRQ_START + 32 * 2 + }, + [3] = { + .chip.label = "gpio-3", + .base = IO_ADDRESS(GPIO4_BASE_ADDR), + .irq = MXC_INT_GPIO4, + .irq_high = 0, + .virtual_irq_start = MXC_GPIO_IRQ_START + 32 * 3 + } +}; + +int __init mxc_register_gpios(void) +{ + return mxc_gpio_init(mxc_gpio_ports, ARRAY_SIZE(mxc_gpio_ports)); +} + +static inline void mxc_init_ssi(void) +{ + /* SPBA configuration for SSI - SDMA and MCU are set */ + spba_take_ownership(SPBA_SSI1, SPBA_MASTER_A | SPBA_MASTER_C); + spba_take_ownership(SPBA_SSI2, SPBA_MASTER_A | SPBA_MASTER_C); +} + +static struct platform_device mxc_dma_device = { + .name = "mxc_dma", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, +}; + +static inline void mxc_init_dma(void) +{ + (void)platform_device_register(&mxc_dma_device); +} + +/* imx adc driver */ +#if defined(CONFIG_IMX_ADC) || defined(CONFIG_IMX_ADC_MODULE) + +static struct resource imx_adc_resources[] = { + [0] = { + .start = MXC_INT_TSC, + .end = MXC_INT_TSC, + .flags = IORESOURCE_IRQ, + }, + [1] = { + .start = TSC_BASE_ADDR, + .end = TSC_BASE_ADDR + PAGE_SIZE, + .flags = IORESOURCE_MEM, + } +}; + +static struct platform_device imx_adc_device = { + .name = "imx_adc", + .id = 0, + .num_resources = ARRAY_SIZE(imx_adc_resources), + .resource = imx_adc_resources, + .dev = { + .release = NULL, + }, +}; +static void imx_init_adc(void) +{ + (void)platform_device_register(&imx_adc_device); +} +#else +static void imx_init_adc(void) +{ +} +#endif + +#if defined(CONFIG_CAN_FLEXCAN) || defined(CONFIG_CAN_FLEXCAN_MODULE) + +static struct resource flexcan1_resources[] = { + { + .start = CAN1_BASE_ADDR, + .end = CAN1_BASE_ADDR + 0x97F, + .flags = IORESOURCE_MEM,}, + { + .start = MXC_INT_CAN1, + .end = MXC_INT_CAN1, + .flags = IORESOURCE_IRQ,} +}; +static struct resource flexcan2_resources[] = { + { + .start = CAN3_BASE_ADDR, + .end = CAN3_BASE_ADDR + 0x97F, + .flags = IORESOURCE_MEM,}, + { + .start = MXC_INT_CAN2, + .end = MXC_INT_CAN2, + .flags = IORESOURCE_IRQ,} +}; + +static struct platform_device flexcan_devices[] = { + { + .name = "FlexCAN", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &flexcan_data[0], + }, + .num_resources = ARRAY_SIZE(flexcan1_resources), + .resource = flexcan1_resources,}, + { + .name = "FlexCAN", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &flexcan_data[1], + }, + .num_resources = ARRAY_SIZE(flexcan2_resources), + .resource = flexcan2_resources,}, +}; + +static inline void mxc_init_flexcan(void) +{ +#ifdef CONFIG_FLEXCAN_MXC_SELECT1 + /* MX25 3stack doesn't use CAN1 */ + platform_device_register(&flexcan_devices[0]); +#endif + platform_device_register(&flexcan_devices[1]); +} +#else +static inline void mxc_init_flexcan(void) +{ +} +#endif + +static struct platform_device mxc_alsa_surround_device = { + .name = "imx-3stack-wm8580", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, +}; + +static void mxc_init_surround_audio(void) +{ + platform_device_register(&mxc_alsa_surround_device); +} + +#if defined(CONFIG_MXC_IIM) || defined(CONFIG_MXC_IIM_MODULE) +static struct resource mxc_iim_resources[] = { + { + .start = IIM_BASE_ADDR, + .end = IIM_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device mxc_iim_device = { + .name = "mxc_iim", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(mxc_iim_resources), + .resource = mxc_iim_resources +}; + +static inline void mxc_init_iim(void) +{ + if (platform_device_register(&mxc_iim_device) < 0) + dev_err(&mxc_iim_device.dev, + "Unable to register mxc iim device\n"); +} +#else +static inline void mxc_init_iim(void) +{ +} +#endif + +static int __init mxc_init_devices(void) +{ + mxc_init_wdt(); + mxc_init_spi(); + mxc_init_i2c(); + mxc_init_dma(); + mxc_init_ssi(); + mxc_init_surround_audio(); + mxc_init_rtc(); + imx_init_adc(); + mxc_init_flexcan(); + mxc_init_iim(); + + return 0; +} + +arch_initcall(mxc_init_devices); diff --git a/arch/arm/mach-mx25/dma.c b/arch/arm/mach-mx25/dma.c new file mode 100644 index 000000000000..04d0c7aa1a0b --- /dev/null +++ b/arch/arm/mach-mx25/dma.c @@ -0,0 +1,663 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/init.h> +#include <linux/device.h> +#include <asm/dma.h> +#include <mach/hardware.h> + +#include "serial.h" + +#ifdef CONFIG_SND_MXC_SOC_IRAM +#define soc_trans_type int_2_per +#else +#define soc_trans_type emi_2_per +#endif + +#define MXC_SSI_TX0_REG 0x0 +#define MXC_SSI_TX1_REG 0x4 +#define MXC_SSI_RX0_REG 0x8 +#define MXC_SSI_RX1_REG 0xC +#define MXC_SSI_TXFIFO_WML 0x4 +#define MXC_SSI_RXFIFO_WML 0x6 + +#define MXC_ESAI_TX_REG 0x00 +#define MXC_ESAI_RX_REG 0x04 +#define MXC_ESAI_FIFO_WML 0x40 + +struct mxc_sdma_info_entry_s { + mxc_dma_device_t device; + mxc_sdma_channel_params_t *chnl_info; +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart1_rx_params = { + .chnl_params = { + .watermark_level = UART1_UFCR_RXTL, + .per_address = UART1_BASE_ADDR, + .peripheral_type = UART, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART1_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART1_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart1_tx_params = { + .chnl_params = { + .watermark_level = UART1_UFCR_TXTL, + .per_address = UART1_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART1_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART1_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart2_rx_params = { + .chnl_params = { + .watermark_level = UART2_UFCR_RXTL, + .per_address = UART2_BASE_ADDR, + .peripheral_type = UART, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART2_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART2_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart2_tx_params = { + .chnl_params = { + .watermark_level = UART2_UFCR_TXTL, + .per_address = UART2_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART2_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART2_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart3_rx_params = { + .chnl_params = { + .watermark_level = UART3_UFCR_RXTL, + .per_address = UART3_BASE_ADDR, + .peripheral_type = UART_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART3_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART3_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart3_tx_params = { + .chnl_params = { + .watermark_level = UART3_UFCR_TXTL, + .per_address = UART3_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART3_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART3_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart4_rx_params = { + .chnl_params = { + .watermark_level = UART4_UFCR_RXTL, + .per_address = UART4_BASE_ADDR, + .peripheral_type = UART_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART4_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART4_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart4_tx_params = { + .chnl_params = { + .watermark_level = UART4_UFCR_TXTL, + .per_address = UART4_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART4_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART4_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart5_rx_params = { + .chnl_params = { + .watermark_level = UART5_UFCR_RXTL, + .per_address = UART5_BASE_ADDR, + .peripheral_type = UART_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART5_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART5_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart5_tx_params = { + .chnl_params = { + .watermark_level = UART5_UFCR_TXTL, + .per_address = UART5_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART5_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART5_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX0, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_SSI1_TX0, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX0, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_SSI1_TX0, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX0, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_SSI1_TX0, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX0, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX0, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX0, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX0, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX0, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX0, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_memory_params = { + .chnl_params = { + .peripheral_type = MEMORY, + .transfer_type = emi_2_emi, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MEMORY, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_esai_16bit_rx_params = { + .chnl_params = { + .watermark_level = MXC_ESAI_FIFO_WML, + .per_address = ESAI_BASE_ADDR + MXC_ESAI_RX_REG, + .peripheral_type = ESAI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_ESAI_RX, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ESAI_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_esai_16bit_tx_params = { + .chnl_params = { + .watermark_level = MXC_ESAI_FIFO_WML, + .per_address = ESAI_BASE_ADDR + MXC_ESAI_TX_REG, + .peripheral_type = ESAI, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_ESAI_TX, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ESAI_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_esai_24bit_rx_params = { + .chnl_params = { + .watermark_level = MXC_ESAI_FIFO_WML, + .per_address = ESAI_BASE_ADDR + MXC_ESAI_RX_REG, + .peripheral_type = ESAI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_ESAI_RX, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ESAI_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_esai_24bit_tx_params = { + .chnl_params = { + .watermark_level = MXC_ESAI_FIFO_WML, + .per_address = ESAI_BASE_ADDR + MXC_ESAI_TX_REG, + .peripheral_type = ESAI, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_ESAI_TX, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ESAI_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static struct mxc_sdma_info_entry_s mxc_sdma_active_dma_info[] = { + {MXC_DMA_UART1_RX, &mxc_sdma_uart1_rx_params}, + {MXC_DMA_UART1_TX, &mxc_sdma_uart1_tx_params}, + {MXC_DMA_UART2_RX, &mxc_sdma_uart2_rx_params}, + {MXC_DMA_UART2_TX, &mxc_sdma_uart2_tx_params}, + {MXC_DMA_UART3_RX, &mxc_sdma_uart3_rx_params}, + {MXC_DMA_UART3_TX, &mxc_sdma_uart3_tx_params}, + {MXC_DMA_UART4_RX, &mxc_sdma_uart4_rx_params}, + {MXC_DMA_UART4_TX, &mxc_sdma_uart4_tx_params}, + {MXC_DMA_UART5_RX, &mxc_sdma_uart5_rx_params}, + {MXC_DMA_UART5_TX, &mxc_sdma_uart5_tx_params}, + {MXC_DMA_SSI1_8BIT_RX0, &mxc_sdma_ssi1_8bit_rx0_params}, + {MXC_DMA_SSI1_8BIT_TX0, &mxc_sdma_ssi1_8bit_tx0_params}, + {MXC_DMA_SSI1_16BIT_RX0, &mxc_sdma_ssi1_16bit_rx0_params}, + {MXC_DMA_SSI1_16BIT_TX0, &mxc_sdma_ssi1_16bit_tx0_params}, + {MXC_DMA_SSI1_24BIT_RX0, &mxc_sdma_ssi1_24bit_rx0_params}, + {MXC_DMA_SSI1_24BIT_TX0, &mxc_sdma_ssi1_24bit_tx0_params}, + {MXC_DMA_SSI1_8BIT_RX1, &mxc_sdma_ssi1_8bit_rx1_params}, + {MXC_DMA_SSI1_8BIT_TX1, &mxc_sdma_ssi1_8bit_tx1_params}, + {MXC_DMA_SSI1_16BIT_RX1, &mxc_sdma_ssi1_16bit_rx1_params}, + {MXC_DMA_SSI1_16BIT_TX1, &mxc_sdma_ssi1_16bit_tx1_params}, + {MXC_DMA_SSI1_24BIT_RX1, &mxc_sdma_ssi1_24bit_rx1_params}, + {MXC_DMA_SSI1_24BIT_TX1, &mxc_sdma_ssi1_24bit_tx1_params}, + {MXC_DMA_SSI2_8BIT_RX0, &mxc_sdma_ssi2_8bit_rx0_params}, + {MXC_DMA_SSI2_8BIT_TX0, &mxc_sdma_ssi2_8bit_tx0_params}, + {MXC_DMA_SSI2_16BIT_RX0, &mxc_sdma_ssi2_16bit_rx0_params}, + {MXC_DMA_SSI2_16BIT_TX0, &mxc_sdma_ssi2_16bit_tx0_params}, + {MXC_DMA_SSI2_24BIT_RX0, &mxc_sdma_ssi2_24bit_rx0_params}, + {MXC_DMA_SSI2_24BIT_TX0, &mxc_sdma_ssi2_24bit_tx0_params}, + {MXC_DMA_SSI2_8BIT_RX1, &mxc_sdma_ssi2_8bit_rx1_params}, + {MXC_DMA_SSI2_8BIT_TX1, &mxc_sdma_ssi2_8bit_tx1_params}, + {MXC_DMA_SSI2_16BIT_RX1, &mxc_sdma_ssi2_16bit_rx1_params}, + {MXC_DMA_SSI2_16BIT_TX1, &mxc_sdma_ssi2_16bit_tx1_params}, + {MXC_DMA_SSI2_24BIT_RX1, &mxc_sdma_ssi2_24bit_rx1_params}, + {MXC_DMA_SSI2_24BIT_TX1, &mxc_sdma_ssi2_24bit_tx1_params}, + {MXC_DMA_ESAI_16BIT_RX, &mxc_sdma_esai_16bit_rx_params}, + {MXC_DMA_ESAI_16BIT_TX, &mxc_sdma_esai_16bit_tx_params}, + {MXC_DMA_ESAI_24BIT_RX, &mxc_sdma_esai_24bit_rx_params}, + {MXC_DMA_ESAI_24BIT_TX, &mxc_sdma_esai_24bit_tx_params}, + {MXC_DMA_MEMORY, &mxc_sdma_memory_params}, +}; + +static int mxc_sdma_info_entrys = + sizeof(mxc_sdma_active_dma_info) / sizeof(mxc_sdma_active_dma_info[0]); +/*! + * This functions Returns the SDMA paramaters associated for a module + * + * @param channel_id the ID of the module requesting DMA + * @return returns the sdma parameters structure for the device + */ +mxc_sdma_channel_params_t *mxc_sdma_get_channel_params(mxc_dma_device_t + channel_id) +{ + struct mxc_sdma_info_entry_s *p = mxc_sdma_active_dma_info; + int i; + + for (i = 0; i < mxc_sdma_info_entrys; i++, p++) { + if (p->device == channel_id) + return p->chnl_info; + } + return NULL; +} +EXPORT_SYMBOL(mxc_sdma_get_channel_params); + +/*! + * This functions marks the SDMA channels that are statically allocated + * + * @param chnl the channel array used to store channel information + */ +void mxc_get_static_channels(mxc_dma_channel_t *chnl) +{ +#ifdef CONFIG_SDMA_IRAM + int i; + for (i = MXC_DMA_CHANNEL_IRAM; i < MAX_DMA_CHANNELS; i++) + chnl[i].dynamic = 0; +#endif +} +EXPORT_SYMBOL(mxc_get_static_channels); diff --git a/arch/arm/mach-mx25/iomux.c b/arch/arm/mach-mx25/iomux.c new file mode 100644 index 000000000000..5518bbf04914 --- /dev/null +++ b/arch/arm/mach-mx25/iomux.c @@ -0,0 +1,199 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup GPIO_MX25 Board GPIO and Muxing Setup + * @ingroup MSL_MX25 + */ +/*! + * @file mach-mx25/iomux.c + * + * @brief I/O Muxing control functions + * + * @ingroup GPIO_MX25 + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/gpio.h> +#include <mach/hardware.h> +#include <mach/irqs.h> +#include "iomux.h" + +/*! + * IOMUX register (base) addresses + */ +#define IOMUXGPR (IO_ADDRESS(IOMUXC_BASE_ADDR)) +#define IOMUXSW_MUX_CTL (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x008) +#define IOMUXSW_MUX_END (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x228) +#define IOMUXSW_PAD_CTL (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x22C) +#define IOMUXSW_PAD_END (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x414) +#define IOMUXSW_INPUT_CTL (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x460) +#define IOMUXSW_INPUT_END (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x580) + +#define MUX_PIN_NUM_MAX \ + (((IOMUXSW_MUX_END - IOMUXSW_MUX_CTL) >> 2) + 1) +#define MUX_INPUT_NUM_MUX \ + (((IOMUXSW_INPUT_END - IOMUXSW_INPUT_CTL) >> 2) + 1) + +#define PIN_TO_IOMUX_INDEX(pin) (PIN_TO_IOMUX_MUX(pin) >> 2) + +static DEFINE_SPINLOCK(gpio_mux_lock); +static u8 iomux_pin_res_table[MUX_PIN_NUM_MAX]; +#define MUX_USED 0x80 + +/*! + * This function is used to configure a pin through the IOMUX module. + * FIXED ME: for backward compatible. Will be static function! + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param cfg an output function as defined in \b #iomux_pin_cfg_t + * + * @return 0 if successful; Non-zero otherwise + */ +static int iomux_config_mux(iomux_pin_name_t pin, iomux_pin_cfg_t cfg) +{ + u32 ret = 0; + u32 pin_index = PIN_TO_IOMUX_INDEX(pin); + void *mux_reg = IOMUXGPR + PIN_TO_IOMUX_MUX(pin); + u8 *rp; + + BUG_ON(pin_index > MUX_PIN_NUM_MAX); + BUG_ON((mux_reg > IOMUXSW_MUX_END) || (mux_reg < IOMUXSW_MUX_CTL)); + spin_lock(&gpio_mux_lock); + __raw_writel(cfg, mux_reg); + /* + * Log a warning if a pin changes ownership + */ + rp = iomux_pin_res_table + pin_index; + if (*rp && *rp != (cfg | MUX_USED)) { + /*Console: how to do */ + printk(KERN_ERR "iomux_config_mux: Warning: iomux pin" + " config changed, index=%d register=%p, " + " prev=0x%x new=0x%x\n", pin_index, mux_reg, + *rp, cfg); + ret = -EINVAL; + } + *rp = cfg | MUX_USED; + spin_unlock(&gpio_mux_lock); + + return ret; +} + +/*! + * Request ownership for an IO pin. This function has to be the first one + * being called before that pin is used. The caller has to check the + * return value to make sure it returns 0. + * + * @param pin a name defined by \b iomux_pin_name_t + * @param cfg an input function as defined in \b #iomux_pin_cfg_t + * + * @return 0 if successful; Non-zero otherwise + */ +int mxc_request_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t cfg) +{ + int ret = iomux_config_mux(pin, cfg); + if (IOMUX_TO_GPIO(pin) < MXC_GPIO_IRQS) { + if (((cfg & (~MUX_CONFIG_SION)) == MUX_CONFIG_GPIO) || + (((cfg & (~MUX_CONFIG_SION)) == MUX_CONFIG_FUNC) && + ((pin == MX25_PIN_GPIO_A) || (pin == MX25_PIN_GPIO_B) || + (pin == MX25_PIN_GPIO_C) || (pin == MX25_PIN_GPIO_D) || + (pin == MX25_PIN_GPIO_E) || (pin == MX25_PIN_GPIO_F)))) + ret |= gpio_request(IOMUX_TO_GPIO(pin), NULL); + } + return ret; +} +EXPORT_SYMBOL(mxc_request_iomux); + +/*! + * Release ownership for an IO pin + * + * @param pin a name defined by \b iomux_pin_name_t + * @param cfg an input function as defined in \b #iomux_pin_cfg_t + */ +void mxc_free_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t cfg) +{ + u32 pin_index = PIN_TO_IOMUX_INDEX(pin); + u8 *rp = iomux_pin_res_table + pin_index; + + BUG_ON((pin_index > MUX_PIN_NUM_MAX)); + + *rp = 0; + if (IOMUX_TO_GPIO(pin) < MXC_GPIO_IRQS) { + if (((cfg & (~MUX_CONFIG_SION)) == MUX_CONFIG_GPIO) || + (((cfg & (~MUX_CONFIG_SION)) == MUX_CONFIG_FUNC) && + ((pin == MX25_PIN_GPIO_A) || (pin == MX25_PIN_GPIO_B) || + (pin == MX25_PIN_GPIO_C) || (pin == MX25_PIN_GPIO_D) || + (pin == MX25_PIN_GPIO_E) || (pin == MX25_PIN_GPIO_F)))) + gpio_free(IOMUX_TO_GPIO(pin)); + } +} +EXPORT_SYMBOL(mxc_free_iomux); + +/*! + * This function configures the pad value for a IOMUX pin. + * + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param config the ORed value of elements defined in \b #iomux_pad_config_t + */ +void mxc_iomux_set_pad(iomux_pin_name_t pin, u32 config) +{ + void *pad_reg = IOMUXGPR + PIN_TO_IOMUX_PAD(pin); + + BUG_ON((pad_reg > IOMUXSW_PAD_END) || (pad_reg < IOMUXSW_PAD_CTL)); + + __raw_writel(config, pad_reg); +} +EXPORT_SYMBOL(mxc_iomux_set_pad); + +/*! + * This function enables/disables the general purpose function for a particular + * signal. + * + * @param gp one signal as defined in \b #iomux_gp_func_t + * @param en \b #true to enable; \b #false to disable + */ +void mxc_iomux_set_gpr(iomux_gp_func_t gp, bool en) +{ + u32 l; + + spin_lock(&gpio_mux_lock); + l = __raw_readl(IOMUXGPR); + + if (en) + l |= gp; + else + l &= ~gp; + + __raw_writel(l, IOMUXGPR); + spin_unlock(&gpio_mux_lock); +} +EXPORT_SYMBOL(mxc_iomux_set_gpr); + +/*! + * This function configures input path. + * + * @param input index of input select register as defined in \b + * #iomux_input_select_t + * @param config the binary value of elements defined in \b + * #iomux_input_config_t + */ +void mxc_iomux_set_input(iomux_input_select_t input, u32 config) +{ + void *reg = IOMUXSW_INPUT_CTL + (input << 2); + + BUG_ON(input >= MUX_INPUT_NUM_MUX); + + __raw_writel(config, reg); +} +EXPORT_SYMBOL(mxc_iomux_set_input); diff --git a/arch/arm/mach-mx25/iomux.h b/arch/arm/mach-mx25/iomux.h new file mode 100644 index 000000000000..4addb1f94557 --- /dev/null +++ b/arch/arm/mach-mx25/iomux.h @@ -0,0 +1,233 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MACH_MX25_IOMUX_H__ +#define __MACH_MX25_IOMUX_H__ + +#include <linux/types.h> +#include <mach/gpio.h> +#include "mx25_pins.h" + +/*! + * @file mach-mx25/iomux.h + * + * @brief I/O Muxing control definitions and functions + * + * @ingroup GPIO_MX25 + */ + +typedef unsigned int iomux_pin_name_t; + +/*! + * IOMUX functions + * SW_MUX_CTL + */ +typedef enum iomux_pin_config { + MUX_CONFIG_FUNC = 0, /*!< used as function */ + MUX_CONFIG_ALT1, /*!< used as alternate function 1 */ + MUX_CONFIG_ALT2, /*!< used as alternate function 2 */ + MUX_CONFIG_ALT3, /*!< used as alternate function 3 */ + MUX_CONFIG_ALT4, /*!< used as alternate function 4 */ + MUX_CONFIG_ALT5, /*!< used as alternate function 5 */ + MUX_CONFIG_ALT6, /*!< used as alternate function 6 */ + MUX_CONFIG_ALT7, /*!< used as alternate function 7 */ + MUX_CONFIG_SION = 0x1 << 4, /*!< used as LOOPBACK:MUX SION bit */ + MUX_CONFIG_GPIO = MUX_CONFIG_ALT5, /*!< used as GPIO */ +} iomux_pin_cfg_t; + +/*! + * IOMUX pad functions + * SW_PAD_CTL + */ +typedef enum iomux_pad_config { + PAD_CTL_DRV_3_3V = 0x0 << 13, + PAD_CTL_DRV_1_8V = 0x1 << 13, + PAD_CTL_HYS_CMOS = 0x0 << 8, + PAD_CTL_HYS_SCHMITZ = 0x1 << 8, + PAD_CTL_PKE_NONE = 0x0 << 7, + PAD_CTL_PKE_ENABLE = 0x1 << 7, + PAD_CTL_PUE_KEEPER = 0x0 << 6, + PAD_CTL_PUE_PULL = 0x1 << 6, + PAD_CTL_PUE_PUD = 0x1 << 6, + PAD_CTL_100K_PD = 0x0 << 4, + PAD_CTL_47K_PU = 0x1 << 4, + PAD_CTL_100K_PU = 0x2 << 4, + PAD_CTL_22K_PU = 0x3 << 4, + PAD_CTL_ODE_CMOS = 0x0 << 3, + PAD_CTL_ODE_OpenDrain = 0x1 << 3, + PAD_CTL_DRV_NORMAL = 0x0 << 1, + PAD_CTL_DRV_HIGH = 0x1 << 1, + PAD_CTL_DRV_MAX = 0x2 << 1, + PAD_CTL_SRE_SLOW = 0x0 << 0, + PAD_CTL_SRE_FAST = 0x1 << 0 +} iomux_pad_config_t; + +/*! + * IOMUX general purpose functions + * IOMUXC_GPR1 + */ +typedef enum iomux_gp_func { + MUX_SDCTL_CSD0_SEL = 0x1 << 0, + MUX_SDCTL_CSD1_SEL = 0x1 << 1, +} iomux_gp_func_t; + +/*! + * IOMUX SELECT_INPUT register index + * Base register is IOMUXSW_INPUT_CTL in iomux.c + */ +typedef enum iomux_input_select { + MUX_IN_AUDMUX_P4_INPUT_DA_AMX = 0, + MUX_IN_AUDMUX_P4_INPUT_DB_AMX, + MUX_IN_AUDMUX_P4_INPUT_RXCLK_AMX, + MUX_IN_AUDMUX_P4_INPUT_RXFS_AMX, + MUX_IN_AUDMUX_P4_INPUT_TXCLK_AMX, + MUX_IN_AUDMUX_P4_INPUT_TXFS_AMX, + MUX_IN_AUDMUX_P7_INPUT_DA_AMX, + MUX_IN_AUDMUX_P7_INPUT_TXFS_AMX, + MUX_IN_CAN1_IPP_IND_CANRX, + MUX_IN_CAN2_IPP_IND_CANRX, + MUX_IN_CSI_IPP_CSI_D_0, + MUX_IN_CSI_IPP_CSI_D_1, + MUX_IN_CSPI1_IPP_IND_SS3_B, + MUX_IN_CSPI2_IPP_CSPI_CLK_IN, + MUX_IN_CSPI2_IPP_IND_DATAREADY_B, + MUX_IN_CSPI2_IPP_IND_MISO, + MUX_IN_CSPI2_IPP_IND_MOSI, + MUX_IN_CSPI2_IPP_IND_SS0_B, + MUX_IN_CSPI2_IPP_IND_SS1_B, + MUX_IN_CSPI3_IPP_CSPI_CLK_IN, + MUX_IN_CSPI3_IPP_IND_DATAREADY_B, + MUX_IN_CSPI3_IPP_IND_MISO, + MUX_IN_CSPI3_IPP_IND_MOSI, + MUX_IN_CSPI3_IPP_IND_SS0_B, + MUX_IN_CSPI3_IPP_IND_SS1_B, + MUX_IN_CSPI3_IPP_IND_SS2_B, + MUX_IN_CSPI3_IPP_IND_SS3_B, + MUX_IN_ESDHC1_IPP_DAT4_IN, + MUX_IN_ESDHC1_IPP_DAT5_IN, + MUX_IN_ESDHC1_IPP_DAT6_IN, + MUX_IN_ESDHC1_IPP_DAT7_IN, + MUX_IN_ESDHC2_IPP_CARD_CLK_IN, + MUX_IN_ESDHC2_IPP_CMD_IN, + MUX_IN_ESDHC2_IPP_DAT0_IN, + MUX_IN_ESDHC2_IPP_DAT1_IN, + MUX_IN_ESDHC2_IPP_DAT2_IN, + MUX_IN_ESDHC2_IPP_DAT3_IN, + MUX_IN_ESDHC2_IPP_DAT4_IN, + MUX_IN_ESDHC2_IPP_DAT5_IN, + MUX_IN_ESDHC2_IPP_DAT6_IN, + MUX_IN_ESDHC2_IPP_DAT7_IN, + MUX_IN_FEC_FEC_COL, + MUX_IN_FEC_FEC_CRS, + MUX_IN_FEC_FEC_RDATA_2, + MUX_IN_FEC_FEC_RDATA_3, + MUX_IN_FEC_FEC_RX_CLK, + MUX_IN_FEC_FEC_RX_ER, + MUX_IN_I2C2_IPP_SCL_IN, + MUX_IN_I2C2_IPP_SDA_IN, + MUX_IN_I2C3_IPP_SCL_IN, + MUX_IN_I2C3_IPP_SDA_IN, + MUX_IN_KPP_IPP_IND_COL_4, + MUX_IN_KPP_IPP_IND_COL_5, + MUX_IN_KPP_IPP_IND_COL_6, + MUX_IN_KPP_IPP_IND_COL_7, + MUX_IN_KPP_IPP_IND_ROW_4, + MUX_IN_KPP_IPP_IND_ROW_5, + MUX_IN_KPP_IPP_IND_ROW_6, + MUX_IN_KPP_IPP_IND_ROW_7, + MUX_IN_SIM1_PIN_SIM_RCVD1_IN, + MUX_IN_SIM1_PIN_SIM_SIMPD1, + MUX_IN_SIM1_SIM_RCVD1_IO, + MUX_IN_SIM2_PIN_SIM_RCVD1_IN, + MUX_IN_SIM2_PIN_SIM_SIMPD1, + MUX_IN_SIM2_SIM_RCVD1_IO, + MUX_IN_UART3_IPP_UART_RTS_B, + MUX_IN_UART3_IPP_UART_RXD_MUX, + MUX_IN_UART4_IPP_UART_RTS_B, + MUX_IN_UART4_IPP_UART_RXD_MUX, + MUX_IN_UART5_IPP_UART_RTS_B, + MUX_IN_UART5_IPP_UART_RXD_MUX, + MUX_IN_USB_TOP_IPP_IND_OTG_USB_OC, + MUX_IN_USB_TOP_IPP_IND_UH2_USB_OC, +} iomux_input_select_t; + +/*! + * IOMUX input functions + * SW_SELECT_INPUT bits 2-0 + */ +typedef enum iomux_input_config { + INPUT_CTL_PATH0 = 0x0, + INPUT_CTL_PATH1, + INPUT_CTL_PATH2, + INPUT_CTL_PATH3, + INPUT_CTL_PATH4, + INPUT_CTL_PATH5, + INPUT_CTL_PATH6, + INPUT_CTL_PATH7, +} iomux_input_cfg_t; + +struct mxc_iomux_pin_cfg { + iomux_pin_name_t pin; + u8 mux_mode; + u16 pad_cfg; + u8 in_select; + u8 in_mode; +}; + +/*! + * Request ownership for an IO pin. This function has to be the first one + * being called before that pin is used. The caller has to check the + * return value to make sure it returns 0. + * + * @param pin a name defined by \b iomux_pin_name_t + * @param cfg an input function as defined in \b #iomux_pin_cfg_t + * + * @return 0 if successful; Non-zero otherwise + */ +int mxc_request_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t cfg); + +/*! + * Release ownership for an IO pin + * + * @param pin a name defined by \b iomux_pin_name_t + * @param cfg an input function as defined in \b #iomux_pin_cfg_t + */ +void mxc_free_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t cfg); + +/*! + * This function enables/disables the general purpose function for a particular + * signal. + * + * @param gp one signal as defined in \b #iomux_gp_func_t + * @param en \b #true to enable; \b #false to disable + */ +void mxc_iomux_set_gpr(iomux_gp_func_t gp, bool en); + +/*! + * This function configures the pad value for a IOMUX pin. + * + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param config the ORed value of elements defined in \b + * #iomux_pad_config_t + */ +void mxc_iomux_set_pad(iomux_pin_name_t pin, u32 config); + +/*! + * This function configures input path. + * + * @param input index of input select register as defined in \b + * #iomux_input_select_t + * @param config the binary value of elements defined in \b + * #iomux_input_cfg_t + */ +void mxc_iomux_set_input(iomux_input_select_t input, u32 config); +#endif diff --git a/arch/arm/mach-mx25/mm.c b/arch/arm/mach-mx25/mm.c new file mode 100644 index 000000000000..bfd83833be72 --- /dev/null +++ b/arch/arm/mach-mx25/mm.c @@ -0,0 +1,82 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/mm.h> +#include <linux/init.h> +#include <mach/hardware.h> +#include <asm/pgtable.h> +#include <asm/mach/map.h> + +/*! + * @file mach-mx25/mm.c + * + * @brief This file creates static mapping between physical to virtual memory. + * + * @ingroup Memory_MX25 + */ + +/*! + * This structure defines the MX25 memory map. + */ +static struct map_desc mx25_io_desc[] __initdata = { + { + .virtual = IRAM_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(IRAM_BASE_ADDR), + .length = IRAM_SIZE, + .type = MT_DEVICE}, + { + .virtual = X_MEMC_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(X_MEMC_BASE_ADDR), + .length = X_MEMC_SIZE, + .type = MT_DEVICE}, + { + .virtual = NFC_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(NFC_BASE_ADDR), + .length = NFC_SIZE, + .type = MT_DEVICE}, + { + .virtual = ROMP_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(ROMP_BASE_ADDR), + .length = ROMP_SIZE, + .type = MT_DEVICE}, + { + .virtual = ASIC_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(ASIC_BASE_ADDR), + .length = ASIC_SIZE, + .type = MT_DEVICE}, + { + .virtual = AIPS1_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(AIPS1_BASE_ADDR), + .length = AIPS1_SIZE, + .type = MT_DEVICE}, + { + .virtual = SPBA0_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(SPBA0_BASE_ADDR), + .length = SPBA0_SIZE, + .type = MT_DEVICE}, + { + .virtual = AIPS2_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(AIPS2_BASE_ADDR), + .length = AIPS2_SIZE, + .type = MT_DEVICE}, +}; + +/*! + * This function initializes the memory map. It is called during the + * system startup to create static physical to virtual memory map for + * the IO modules. + */ +void __init mx25_map_io(void) +{ + iotable_init(mx25_io_desc, ARRAY_SIZE(mx25_io_desc)); +} diff --git a/arch/arm/mach-mx25/mx25_3stack.c b/arch/arm/mach-mx25/mx25_3stack.c new file mode 100644 index 000000000000..fd0d30121f57 --- /dev/null +++ b/arch/arm/mach-mx25/mx25_3stack.c @@ -0,0 +1,741 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/nodemask.h> +#include <linux/clk.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/smsc911x.h> +#if defined(CONFIG_MTD) || defined(CONFIG_MTD_MODULE) +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> + +#include <asm/mach/flash.h> +#endif + +#include <mach/common.h> +#include <mach/hardware.h> +#include <asm/irq.h> +#include <asm/mach/keypad.h> +#include <asm/setup.h> +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <asm/mach/irq.h> +#include <asm/mach/time.h> +#include <mach/memory.h> +#include <mach/gpio.h> +#include <mach/mmc.h> + +#include "board-mx25_3stack.h" +#include "crm_regs.h" +#include "iomux.h" + +/*! + * @file mach-mx25/mx25_3stack.c + * + * @brief This file contains the board specific initialization routines. + * + * @ingroup MSL_MX25 + */ + +unsigned int mx25_3stack_board_io; + +/* working point(wp): 0 - 399MHz; 1 - 266MHz; 2 - 133MHz; */ +/* 24MHz input clock table */ +static struct cpu_wp cpu_wp_mx25[] = { + { + .pll_rate = 399000000, + .cpu_rate = 399000000, + .cpu_podf = 0x0, + .cpu_voltage = 1450000}, + { + .pll_rate = 532000000, + .cpu_rate = 266000000, + .cpu_podf = 0x1, + .cpu_voltage = 1340000}, + { + .pll_rate = 532000000, + .cpu_rate = 133000000, + .cpu_podf = 0x3, + .cpu_voltage = 1340000}, +}; +struct cpu_wp *get_cpu_wp(int *wp) +{ + *wp = 3; + return cpu_wp_mx25; +} + +static void mxc_nop_release(struct device *dev) +{ + /* Nothing */ +} + +#if defined(CONFIG_KEYBOARD_MXC) || defined(CONFIG_KEYBOARD_MXC_MODULE) +static u16 keymapping[16] = { + KEY_UP, KEY_DOWN, KEY_VOLUMEDOWN, KEY_HOME, + KEY_RIGHT, KEY_LEFT, KEY_ENTER, KEY_VOLUMEUP, + KEY_F6, KEY_F8, KEY_F9, KEY_F10, + KEY_F1, KEY_F2, KEY_F3, KEY_POWER, +}; + +static struct resource mxc_kpp_resources[] = { + [0] = { + .start = MXC_INT_KPP, + .end = MXC_INT_KPP, + .flags = IORESOURCE_IRQ, + } +}; + +static struct keypad_data keypad_plat_data = { + .rowmax = 4, + .colmax = 4, + .irq = MXC_INT_KPP, + .learning = 0, + .delay = 2, + .matrix = keymapping, +}; + +/* mxc keypad driver */ +static struct platform_device mxc_keypad_device = { + .name = "mxc_keypad", + .id = 0, + .num_resources = ARRAY_SIZE(mxc_kpp_resources), + .resource = mxc_kpp_resources, + .dev = { + .release = mxc_nop_release, + .platform_data = &keypad_plat_data, + }, +}; + +static void mxc_init_keypad(void) +{ + (void)platform_device_register(&mxc_keypad_device); +} +#else +static inline void mxc_init_keypad(void) +{ +} +#endif + +/* MTD NAND flash */ + +#if defined(CONFIG_MTD_NAND_MXC_V2) || defined(CONFIG_MTD_NAND_MXC_V2_MODULE) + +static struct mtd_partition mxc_nand_partitions[] = { + { + .name = "nand.bootloader", + .offset = 0, + .size = 1024 * 1024}, + { + .name = "nand.kernel", + .offset = MTDPART_OFS_APPEND, + .size = 5 * 1024 * 1024}, + { + .name = "nand.rootfs", + .offset = MTDPART_OFS_APPEND, + .size = 256 * 1024 * 1024}, + { + .name = "nand.configure", + .offset = MTDPART_OFS_APPEND, + .size = 8 * 1024 * 1024}, + { + .name = "nand.userfs", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL}, +}; + +static struct flash_platform_data mxc_nand_data = { + .parts = mxc_nand_partitions, + .nr_parts = ARRAY_SIZE(mxc_nand_partitions), + .width = 1, +}; + +static struct platform_device mxc_nand_mtd_device = { + .name = "mxc_nandv2_flash", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_nand_data, + }, +}; + +static void mxc_init_nand_mtd(void) +{ + if (__raw_readl(MXC_CCM_RCSR) & MXC_CCM_RCSR_NF16B) + mxc_nand_data.width = 2; + + platform_device_register(&mxc_nand_mtd_device); +} +#else +static inline void mxc_init_nand_mtd(void) +{ +} +#endif + +#if defined(CONFIG_FB_MXC_SYNC_PANEL) || \ + defined(CONFIG_FB_MXC_SYNC_PANEL_MODULE) +static const char fb_default_mode[] = "CPT-VGA"; + +/* mxc lcd driver */ +static struct platform_device mxc_fb_device = { + .name = "mxc_sdc_fb", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &fb_default_mode, + .coherent_dma_mask = 0xFFFFFFFF, + }, +}; + +/* + * Power on/off CPT VGA panel. + */ +void board_power_lcd(int on) +{ + if (on) + mx2fb_set_brightness(MXC_DEFAULT_INTENSITY); + else + mx2fb_set_brightness(MXC_INTENSITY_OFF); +} +EXPORT_SYMBOL_GPL(board_power_lcd); + +static void mxc_init_fb(void) +{ + (void)platform_device_register(&mxc_fb_device); +} +#else +static inline void mxc_init_fb(void) +{ +} +#endif + +#if defined(CONFIG_BACKLIGHT_MXC) +static struct platform_device mxcbl_devices[] = { +#if defined(CONFIG_BACKLIGHT_MXC_LCDC) || \ + defined(CONFIG_BACKLIGHT_MXC_LCDC_MODULE) + { + .name = "mxc_lcdc_bl", + .id = 0, + }, +#endif +}; + +static inline void mxc_init_bl(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mxcbl_devices); i++) + platform_device_register(&mxcbl_devices[i]); +} +#else +static inline void mxc_init_bl(void) +{ +} +#endif + +/*! + * Power Key interrupt handler. + */ +static irqreturn_t power_key_int(int irq, void *dev_id) +{ + pr_info("on-off key pressed\n"); + return 0; +} + +/*! + * Power Key initialization. + */ +static int __init mxc_init_power_key(void) +{ + /*Set power key as wakeup resource */ + int irq, ret; + + mxc_request_iomux(MX25_PIN_A25, MUX_CONFIG_ALT5); + mxc_iomux_set_pad(MX25_PIN_A25, PAD_CTL_DRV_NORMAL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_A25), NULL); + gpio_direction_input(IOMUX_TO_GPIO(MX25_PIN_A25)); + + irq = IOMUX_TO_IRQ(MX25_PIN_A25); + set_irq_type(irq, IRQF_TRIGGER_RISING); + ret = request_irq(irq, power_key_int, 0, "power_key", 0); + if (ret) + pr_info("register on-off key interrupt failed\n"); + else + enable_irq_wake(irq); + + return ret; +} + +late_initcall(mxc_init_power_key); + +static struct spi_board_info mxc_spi_board_info[] __initdata = { + { + .modalias = "cpld_spi", + .max_speed_hz = 18000000, + .bus_num = 1, + .chip_select = 0, + .mode = SPI_MODE_2, + }, + { + .modalias = "wm8580_spi", + .max_speed_hz = 8000000, /* max spi SCK clock speed in HZ */ + .bus_num = 1, + .chip_select = 1, + }, +}; + +static struct mxc_camera_platform_data camera_data = { + .core_regulator = NULL, + .io_regulator = NULL, + .analog_regulator = NULL, + .gpo_regulator = NULL, + .mclk = 24000000, +}; + +static struct i2c_board_info mxc_i2c_board_info[] __initdata = { + { + .type = "sgtl5000-i2c", + .addr = 0x0a, + }, + { + .type = "ak5702-i2c", + .addr = 0x13, + }, + { + .type = "ov2640", + .addr = 0x30, + .platform_data = (void *)&camera_data, + }, +}; + +#if defined(CONFIG_SND_SOC_IMX_3STACK_SGTL5000) \ + || defined(CONFIG_SND_SOC_IMX_3STACK_SGTL5000_MODULE) +static struct mxc_audio_platform_data sgtl5000_data = { + .ssi_num = 2, + .src_port = 1, + .ext_port = 4, + .hp_irq = IOMUX_TO_IRQ(MX25_PIN_A10), + .hp_status = headphone_det_status, + .vddio = 1800000, + .vdda = 3300000, + .vddd = 0, + .sysclk = 8300000, +}; + +static struct platform_device mxc_sgtl5000_device = { + .name = "imx-3stack-sgtl5000", + .dev = { + .release = mxc_nop_release, + .platform_data = &sgtl5000_data, + }, +}; + +static void mxc_init_sgtl5000(void) +{ + struct clk *cko1, *parent; + unsigned long rate; + + /* cko1 clock */ + mxc_request_iomux(MX25_PIN_CLKO, MUX_CONFIG_FUNC); + + cko1 = clk_get(NULL, "clko_clk"); + if (IS_ERR(cko1)) + return; + parent = clk_get(NULL, "ipg_clk"); + if (IS_ERR(parent)) + return; + clk_set_parent(cko1, parent); + rate = clk_round_rate(cko1, 13000000); + if (rate < 8000000 || rate > 27000000) { + pr_err("Error: SGTL5000 mclk freq %ld out of range!\n", rate); + clk_put(parent); + clk_put(cko1); + return; + } + clk_set_rate(cko1, rate); + clk_enable(cko1); + sgtl5000_data.sysclk = rate; + sgtl5000_enable_amp(); + platform_device_register(&mxc_sgtl5000_device); +} +#else +static inline void mxc_init_sgtl5000(void) +{ +} +#endif + +#if defined(CONFIG_SND_SOC_IMX_3STACK_AK5702) \ + || defined(CONFIG_SND_SOC_IMX_3STACK_AK5702_MODULE) +static struct platform_device mxc_ak5702_device = { + .name = "imx-3stack-ak5702", + .dev = { + .release = mxc_nop_release, + }, +}; + +static void mxc_init_ak5702(void) +{ + platform_device_register(&mxc_ak5702_device); +} +#else +static inline void mxc_init_ak5702(void) +{ +} +#endif + +#if defined(CONFIG_SMSC911X) || defined(CONFIG_SMSC911X_MODULE) +static struct resource smsc911x_resources[] = { + { + .start = LAN9217_BASE_ADDR, + .end = LAN9217_BASE_ADDR + 255, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_BOARD_IRQ_START, + .flags = IORESOURCE_IRQ, + } +}; + +struct smsc911x_platform_config smsc911x_config = { + .irq_polarity = SMSC911X_IRQ_POLARITY_ACTIVE_LOW, + .flags = 0x8000 | SMSC911X_USE_16BIT | SMSC911X_FORCE_INTERNAL_PHY, +}; + +static struct platform_device smsc_lan9217_device = { + .name = "smsc911x", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &smsc911x_config, + }, + .num_resources = ARRAY_SIZE(smsc911x_resources), + .resource = smsc911x_resources, +}; + +static int __init mxc_init_enet(void) +{ + (void)platform_device_register(&smsc_lan9217_device); + return 0; +} +#else +static int __init mxc_init_enet(void) +{ + return 0; +} +#endif + +late_initcall(mxc_init_enet); + +#if defined(CONFIG_FEC) || defined(CONFIG_FEC_MODULE) +static struct resource mxc_fec_resources[] = { + { + .start = FEC_BASE_ADDR, + .end = FEC_BASE_ADDR + 0xfff, + .flags = IORESOURCE_MEM + }, { + .start = MXC_INT_FEC, + .end = MXC_INT_FEC, + .flags = IORESOURCE_IRQ + }, +}; + +struct platform_device mxc_fec_device = { + .name = "fec", + .id = 0, + .num_resources = ARRAY_SIZE(mxc_fec_resources), + .resource = mxc_fec_resources, +}; + +static __init int mxc_init_fec(void) +{ + return platform_device_register(&mxc_fec_device); +} +#else +static inline int mxc_init_fec(void) +{ + return 0; +} +#endif + +#if defined(CONFIG_IMX_SIM) || defined(CONFIG_IMX_SIM_MODULE) +/* Used to configure the SIM bus */ +static struct mxc_sim_platform_data sim1_data = { + .clk_rate = 5000000, + .clock_sim = "sim1_clk", + .power_sim = NULL, + .init = NULL, + .exit = NULL, + .detect = 1, +}; + +/*! + * Resource definition for the SIM + */ +static struct resource mxc_sim1_resources[] = { + [0] = { + .start = SIM1_BASE_ADDR, + .end = SIM1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_SIM1, + .end = MXC_INT_SIM1, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Device Definition for IMX SIM */ +static struct platform_device mxc_sim1_device = { + .name = "mxc_sim", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &sim1_data, + }, + .num_resources = ARRAY_SIZE(mxc_sim1_resources), + .resource = mxc_sim1_resources, +}; + +static inline void mxc_init_sim(void) +{ + (void)platform_device_register(&mxc_sim1_device); +} +#else +static inline void mxc_init_sim(void) +{ +} +#endif + +#if defined(CONFIG_MMC_IMX_ESDHCI) || defined(CONFIG_MMC_IMX_ESDHCI_MODULE) +static struct mxc_mmc_platform_data mmc1_data = { + .ocr_mask = MMC_VDD_29_30 | MMC_VDD_32_33, + .caps = MMC_CAP_4_BIT_DATA, + .min_clk = 400000, + .max_clk = 52000000, + .card_inserted_state = 1, + .status = sdhc_get_card_det_status, + .wp_status = sdhc_write_protect, + .clock_mmc = "esdhc_clk", +}; + +/*! + * Resource definition for the SDHC1 + */ +static struct resource mxcsdhc1_resources[] = { + [0] = { + .start = MMC_SDHC1_BASE_ADDR, + .end = MMC_SDHC1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_SDHC1, + .end = MXC_INT_SDHC1, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = IOMUX_TO_IRQ(MX25_PIN_A15), + .end = IOMUX_TO_IRQ(MX25_PIN_A15), + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Device Definition for MXC SDHC1 */ +static struct platform_device mxcsdhc1_device = { + .name = "mxsdhci", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mmc1_data, + }, + .num_resources = ARRAY_SIZE(mxcsdhc1_resources), + .resource = mxcsdhc1_resources, +}; + +#ifdef CONFIG_MMC_IMX_ESDHCI_SELECT2 +static struct mxc_mmc_platform_data mmc2_data = { + .ocr_mask = MMC_VDD_29_30 | MMC_VDD_32_33, + .caps = MMC_CAP_4_BIT_DATA, + .min_clk = 400000, + .max_clk = 52000000, + .card_fixed = 1, + .card_inserted_state = 1, + .status = sdhc_get_card_det_status, + .clock_mmc = "esdhc2_clk", +}; + +/*! + * Resource definition for the SDHC2 + */ +static struct resource mxcsdhc2_resources[] = { + [0] = { + .start = MMC_SDHC2_BASE_ADDR, + .end = MMC_SDHC2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_SDHC2, + .end = MXC_INT_SDHC2, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Device Definition for MXC SDHC2 */ +static struct platform_device mxcsdhc2_device = { + .name = "mxsdhci", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mmc2_data, + }, + .num_resources = ARRAY_SIZE(mxcsdhc2_resources), + .resource = mxcsdhc2_resources, +}; +#endif + +static inline void mxc_init_mmc(void) +{ + (void)platform_device_register(&mxcsdhc1_device); +#ifdef CONFIG_MMC_IMX_ESDHCI_SELECT2 + (void)platform_device_register(&mxcsdhc2_device); +#endif +} +#else +static inline void mxc_init_mmc(void) +{ +} +#endif + +static void __init mx25_3stack_timer_init(void) +{ + mx25_clocks_init(24000000); +} + +static struct sys_timer mxc_timer = { + .init = mx25_3stack_timer_init, +}; + +#if defined(CONFIG_CAN_FLEXCAN) || defined(CONFIG_CAN_FLEXCAN_MODULE) +static void flexcan_xcvr_enable(int id, int en) +{ + static int pwdn; + + if (id != 1) /* MX25 3-stack uses only CAN2 */ + return; + + if (en) { + if (!pwdn++) + gpio_set_value(IOMUX_TO_GPIO(MX25_PIN_D14), 0); + } else { + if (!--pwdn) + gpio_set_value(IOMUX_TO_GPIO(MX25_PIN_D14), 1); + } +} + +struct flexcan_platform_data flexcan_data[] = { + { + .core_reg = NULL, + .io_reg = NULL, + .xcvr_enable = flexcan_xcvr_enable, + .active = gpio_can_active, + .inactive = gpio_can_inactive,}, + { + .core_reg = NULL, + .io_reg = NULL, + .xcvr_enable = flexcan_xcvr_enable, + .active = gpio_can_active, + .inactive = gpio_can_inactive,}, +}; +#endif + +/*! + * Board specific fixup function. It is called by \b setup_arch() in + * setup.c file very early on during kernel starts. It allows the user to + * statically fill in the proper values for the passed-in parameters. None of + * the parameters is used currently. + * + * @param desc pointer to \b struct \b machine_desc + * @param tags pointer to \b struct \b tag + * @param cmdline pointer to the command line + * @param mi pointer to \b struct \b meminfo + */ +static void __init fixup_mxc_board(struct machine_desc *desc, struct tag *tags, + char **cmdline, struct meminfo *mi) +{ + mxc_cpu_init(); + +#ifdef CONFIG_DISCONTIGMEM + do { + int nid; + mi->nr_banks = MXC_NUMNODES; + for (nid = 0; nid < mi->nr_banks; nid++) + SET_NODE(mi, nid); + } while (0); +#endif +} + +/*! + * Board specific initialization. + */ +static void __init mxc_board_init(void) +{ + pr_info("AIPS1 VA base: 0x%p\n", IO_ADDRESS(AIPS1_BASE_ADDR)); + mxc_cpu_common_init(); + mxc_register_gpios(); + mx25_3stack_gpio_init(); + early_console_setup(saved_command_line); + mxc_init_keypad(); +#ifdef CONFIG_I2C + i2c_register_board_info(0, mxc_i2c_board_info, + ARRAY_SIZE(mxc_i2c_board_info)); +#endif + spi_register_board_info(mxc_spi_board_info, + ARRAY_SIZE(mxc_spi_board_info)); + mx25_3stack_init_mc34704(); + mxc_init_fb(); + mxc_init_bl(); + mxc_init_nand_mtd(); + mxc_init_sgtl5000(); + mxc_init_ak5702(); + mxc_init_mmc(); + mxc_init_sim(); + mxc_init_fec(); +} + +/* + * The following uses standard kernel macros define in arch.h in order to + * initialize __mach_desc_MX25_3DS data structure. + */ +/* *INDENT-OFF* */ +MACHINE_START(MX25_3DS, "Freescale MX25 3-Stack Board") + /* Maintainer: Freescale Semiconductor, Inc. */ + .phys_io = AIPS1_BASE_ADDR, + .io_pg_offst = ((AIPS1_BASE_ADDR_VIRT) >> 18) & 0xfffc, + .boot_params = PHYS_OFFSET + 0x100, + .fixup = fixup_mxc_board, + .map_io = mx25_map_io, + .init_irq = mxc_init_irq, + .init_machine = mxc_board_init, + .timer = &mxc_timer, +MACHINE_END diff --git a/arch/arm/mach-mx25/mx25_3stack_cpld.c b/arch/arm/mach-mx25/mx25_3stack_cpld.c new file mode 100644 index 000000000000..1f8a05f7c8c8 --- /dev/null +++ b/arch/arm/mach-mx25/mx25_3stack_cpld.c @@ -0,0 +1,246 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <mach/hardware.h> +#include <asm/mach/irq.h> +#include <mach/gpio.h> +#include "board-mx25_3stack.h" +#include "iomux.h" + +/*! + * @file mach-mx25/mx25_3stack_cpld.c + * + * @brief This file contains the board specific initialization routines. + * + * @ingroup MSL_MX25 + */ + +extern int mxc_spi_poll_transfer(struct spi_device *spi, + struct spi_transfer *t); +static int __init mxc_expio_init(void); + +struct spi_device *cpld_spi; + +/*! + * This function is used to tranfer data to CPLD regs over CSPI + */ +static inline int mx25_3ds_cpld_rw(u8 *buf, size_t len) +{ + struct spi_transfer t = { + .tx_buf = (const void *)buf, + .rx_buf = buf, + .len = len, + .cs_change = 0, + .delay_usecs = 0, + }; + + if (!cpld_spi) + return -1; + + mxc_spi_poll_transfer(cpld_spi, &t); + return 0; +} + +/*! + * This function is called to read a CPLD register over CSPI. + * + * @param offset number of the cpld register to be read + * + * @return Returns 0 on success -1 on failure. + */ +unsigned int spi_cpld_read(unsigned int offset) +{ + unsigned int frame[2]; + unsigned int reg_num = offset >> 1; + unsigned int data = 0; + + frame[0] = (1 << 13) | ((reg_num & 0x0001FFFF) >> 5) | 0x00001000; + frame[1] = (((reg_num & 0x0000001F) << 27) | 0x0200001f); + mx25_3ds_cpld_rw((u8 *) frame, 2); + data = (frame[1] >> 6) & 0xFFFF; + + reg_num = (offset + 2) >> 1; + frame[0] = (1 << 13) | ((reg_num & 0x0001FFFF) >> 5) | 0x00001000; + frame[1] = (((reg_num & 0x0000001F) << 27) | 0x0200001f); + mx25_3ds_cpld_rw((u8 *) frame, 2); + + data |= (((frame[1] >> 6) & 0xFFFF) << 16); + return data; +} +EXPORT_SYMBOL(spi_cpld_read); + +/*! + * This function is called to write to a CPLD register over CSPI. + * + * @param offset number of the cpld register to be written + * @param reg_val value to be written + * + * @return Returns 0 on success -1 on failure. + */ +unsigned int spi_cpld_write(unsigned int offset, unsigned int reg_val) +{ + unsigned int frame[2] = { 0, 0 }; + unsigned int reg_num = offset >> 1; + unsigned int data = reg_val; + + frame[0] = ((reg_num & 0x0001FFFF) >> 5) | 0x00001000; + frame[1] = (((reg_num & 0x0000001F) << 27) | + ((data & 0x0000FFFF) << 6) | 0x03C00027); + mx25_3ds_cpld_rw((u8 *) frame, 2); + + reg_num = (offset + 2) >> 1; + data = reg_val >> 16; + frame[0] = 0; + frame[1] = 0; + frame[0] = ((reg_num & 0x0001FFFF) >> 5) | 0x00001000; + frame[1] = (((reg_num & 0x0000001F) << 27) | + ((data & 0x0000FFFF) << 6) | 0x03C00027); + + mx25_3ds_cpld_rw((u8 *) frame, 2); + + return 0; +} +EXPORT_SYMBOL(spi_cpld_write); + +static int __init mx25_3ds_cpld_probe(struct spi_device *spi) +{ + unsigned int i = 0; + + spi->bits_per_word = 46; + cpld_spi = spi; + + spi_setup(spi); + i = spi_cpld_read(CPLD_CODE_VER_REG); + pr_info("3-Stack Debug board detected, rev = 0x%04X\n", i); + spi_cpld_write(LED_SWITCH_REG, 0xFF); + + /* disable the interrupt and clear the status */ + spi_cpld_write(INTR_MASK_REG, 0); + spi_cpld_write(INTR_RESET_REG, 0xFFFF); + spi_cpld_write(INTR_RESET_REG, 0); + spi_cpld_write(INTR_MASK_REG, 0x1E); + + mxc_expio_init(); + return 0; +} + +/*! + * This structure contains pointers to the CPLD callback functions. + */ +static struct spi_driver mx25_3ds_cpld_driver = { + .driver = { + .name = "cpld_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = mx25_3ds_cpld_probe, +}; + +static int __init mx25_3ds_cpld_init(void) +{ + pr_info("Registering the CPLD Driver\n"); + return spi_register_driver(&mx25_3ds_cpld_driver); +} +device_initcall(mx25_3ds_cpld_init); + +static int __initdata is_dbg_removed = { 0 }; +static int __init remove_dbg_setup(char *__unused) +{ + is_dbg_removed = 1; + return 0; +} +__setup("remove_dbg", remove_dbg_setup); + +static void mxc_expio_irq_handler(u32 irq, struct irq_desc *desc) +{ + u32 expio_irq; + struct irq_desc *d; + + desc->chip->mask(irq); /* irq = gpio irq number */ + + expio_irq = MXC_BOARD_IRQ_START; + + d = irq_desc + expio_irq; + if (unlikely(!(d->handle_irq))) { + printk(KERN_ERR "\nEXPIO irq: %d unhandled\n", expio_irq); + BUG(); /* oops */ + } + d->handle_irq(expio_irq, d); + + desc->chip->ack(irq); + desc->chip->unmask(irq); +} + +/* + * Disable an expio pin's interrupt by setting the bit in the imr. + * @param irq an expio virtual irq number + */ +static void expio_mask_irq(u32 irq) +{ +} + +/* + * Acknowledge an expanded io pin's interrupt by clearing the bit in the isr. + * @param irq an expanded io virtual irq number + */ +static void expio_ack_irq(u32 irq) +{ + /* clear the interrupt status */ + spi_cpld_write(INTR_RESET_REG, 1); + spi_cpld_write(INTR_RESET_REG, 0); +} + +/* + * Enable a expio pin's interrupt by clearing the bit in the imr. + * @param irq a expio virtual irq number + */ +static void expio_unmask_irq(u32 irq) +{ +} + +static struct irq_chip expio_irq_chip = { + .ack = expio_ack_irq, + .mask = expio_mask_irq, + .unmask = expio_unmask_irq, +}; + +static int __init mxc_expio_init(void) +{ + int i; + + if (is_dbg_removed) + return 0; + + /* + * Configure INT line as GPIO input + */ + mxc_request_iomux(MX25_PIN_PWM, MUX_CONFIG_GPIO); + mxc_iomux_set_pad(MX25_PIN_PWM, PAD_CTL_PUE_PUD); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_PWM), NULL); + gpio_direction_input(IOMUX_TO_GPIO(MX25_PIN_PWM)); + + for (i = MXC_BOARD_IRQ_START; i < (MXC_BOARD_IRQ_START + MXC_BOARD_IRQS); + i++) { + set_irq_chip(i, &expio_irq_chip); + set_irq_handler(i, handle_level_irq); + set_irq_flags(i, IRQF_VALID); + } + set_irq_type(IOMUX_TO_IRQ(MX25_PIN_PWM), IRQF_TRIGGER_LOW); + set_irq_chained_handler(IOMUX_TO_IRQ(MX25_PIN_PWM), + mxc_expio_irq_handler); + + return 0; +} + diff --git a/arch/arm/mach-mx25/mx25_3stack_gpio.c b/arch/arm/mach-mx25/mx25_3stack_gpio.c new file mode 100644 index 000000000000..5f7dd4f63b06 --- /dev/null +++ b/arch/arm/mach-mx25/mx25_3stack_gpio.c @@ -0,0 +1,1367 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <mach/hardware.h> +#include <mach/gpio.h> +#include "board-mx25_3stack.h" +#include "iomux.h" + +/*! + * @file mach-mx25/mx25_3stack_gpio.c + * + * @brief This file contains all the GPIO setup functions for the board. + * + * @ingroup GPIO_MX25 + */ +static struct mxc_iomux_pin_cfg __initdata mxc_iomux_pins[] = { +}; + +static struct mxc_iomux_pin_cfg __initdata sim_iomux_pins[] = { + /* SIM1 */ + /* SIM1 CLK */ + { + MX25_PIN_CSI_D2, MUX_CONFIG_ALT4, + PAD_CTL_SRE_FAST | + PAD_CTL_DRV_HIGH | PAD_CTL_DRV_3_3V | + PAD_CTL_HYS_CMOS | PAD_CTL_47K_PU | + PAD_CTL_PUE_PULL | PAD_CTL_ODE_CMOS | PAD_CTL_PKE_ENABLE, + }, + /* SIM1 RST */ + { + MX25_PIN_CSI_D3, MUX_CONFIG_ALT4, + PAD_CTL_DRV_HIGH | PAD_CTL_DRV_3_3V | + PAD_CTL_HYS_CMOS | PAD_CTL_47K_PU | + PAD_CTL_PUE_PULL | PAD_CTL_ODE_CMOS | PAD_CTL_PKE_ENABLE, + }, + /* SIM1 VEN */ + { + MX25_PIN_CSI_D4, MUX_CONFIG_ALT4, + PAD_CTL_DRV_HIGH | PAD_CTL_DRV_3_3V | + PAD_CTL_HYS_CMOS | PAD_CTL_100K_PU | + PAD_CTL_PUE_PULL | PAD_CTL_ODE_CMOS | PAD_CTL_PKE_ENABLE, + }, + /* SIM1 TX */ + { + MX25_PIN_CSI_D5, MUX_CONFIG_ALT4, + PAD_CTL_SRE_FAST | + PAD_CTL_DRV_HIGH | PAD_CTL_DRV_3_3V | + PAD_CTL_HYS_CMOS | PAD_CTL_22K_PU | + PAD_CTL_PUE_PULL | PAD_CTL_ODE_OpenDrain | PAD_CTL_PKE_ENABLE, + }, + /* SIM1 PD */ + { + MX25_PIN_CSI_D6, MUX_CONFIG_ALT4, + PAD_CTL_DRV_HIGH | PAD_CTL_DRV_3_3V | + PAD_CTL_HYS_CMOS | PAD_CTL_22K_PU | + PAD_CTL_PUE_PULL | PAD_CTL_ODE_CMOS | PAD_CTL_PKE_ENABLE, + }, + /* SIM2 */ + /* SIM2 CLK */ + { + MX25_PIN_CSI_D8, MUX_CONFIG_ALT4, + PAD_CTL_DRV_NORMAL | PAD_CTL_DRV_3_3V | + PAD_CTL_HYS_SCHMITZ | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_ODE_CMOS | PAD_CTL_PKE_ENABLE, + }, + /* SIM2 RST */ + { + MX25_PIN_CSI_D9, MUX_CONFIG_ALT4, + PAD_CTL_DRV_NORMAL | PAD_CTL_DRV_3_3V | + PAD_CTL_HYS_SCHMITZ | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_ODE_CMOS | PAD_CTL_PKE_ENABLE, + }, + /* SIM2 VEN */ + { + MX25_PIN_CSI_MCLK, MUX_CONFIG_ALT4, + PAD_CTL_DRV_NORMAL | PAD_CTL_DRV_3_3V | + PAD_CTL_HYS_SCHMITZ | PAD_CTL_100K_PU | + PAD_CTL_PUE_PULL | PAD_CTL_ODE_CMOS | PAD_CTL_PKE_NONE, + }, + /* SIM2 TX */ + { + MX25_PIN_CSI_VSYNC, MUX_CONFIG_ALT4, + PAD_CTL_DRV_NORMAL | PAD_CTL_DRV_3_3V | + PAD_CTL_HYS_SCHMITZ | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_ODE_CMOS | PAD_CTL_PKE_ENABLE, + }, + /* SIM2 PD */ + { + MX25_PIN_CSI_HSYNC, MUX_CONFIG_ALT4, + PAD_CTL_DRV_NORMAL | PAD_CTL_DRV_3_3V | + PAD_CTL_HYS_SCHMITZ | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_ODE_CMOS | PAD_CTL_PKE_ENABLE, + }, +}; + +static int __initdata enable_sim = { 0 }; +static int __init sim_setup(char *__unused) +{ + enable_sim = 1; + return 1; +} + +__setup("sim", sim_setup); + +/*! + * This system-wide GPIO function initializes the pins during system startup. + * All the statically linked device drivers should put the proper GPIO + * initialization code inside this function. It is called by + * \b fixup_mx25_3stack() during system startup. This function is board + * specific. + */ +void __init mx25_3stack_gpio_init(void) +{ + int i, num = 0; + struct mxc_iomux_pin_cfg *pin_ptr; + + for (i = 0; i < ARRAY_SIZE(mxc_iomux_pins); i++) { + mxc_request_iomux(mxc_iomux_pins[i].pin, + mxc_iomux_pins[i].mux_mode); + if (mxc_iomux_pins[i].pad_cfg) + mxc_iomux_set_pad(mxc_iomux_pins[i].pin, + mxc_iomux_pins[i].pad_cfg); + if (mxc_iomux_pins[i].in_select) + mxc_iomux_set_input(mxc_iomux_pins[i].in_select, + mxc_iomux_pins[i].in_mode); + } + + if (enable_sim) { + pin_ptr = sim_iomux_pins; + num = ARRAY_SIZE(sim_iomux_pins); + } + + for (i = 0; i < num; i++) { + mxc_request_iomux(pin_ptr[i].pin, pin_ptr[i].mux_mode); + if (pin_ptr[i].pad_cfg) + mxc_iomux_set_pad(pin_ptr[i].pin, pin_ptr[i].pad_cfg); + if (pin_ptr[i].in_select) + mxc_iomux_set_input(pin_ptr[i].in_select, + pin_ptr[i].in_mode); + } +} + +/*! + * Activate a UART port + * + * @param port a UART port + * @param no_irda indicates if the port is used for SIR + */ +void gpio_uart_active(int port, int no_irda) +{ + /* + * Configure the IOMUX control registers for the UART signals + */ + switch (port) { + case 0: + /* UART 1 IOMUX Configs */ + mxc_request_iomux(MX25_PIN_UART1_RXD, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_UART1_TXD, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_UART1_RTS, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_UART1_CTS, MUX_CONFIG_FUNC); + mxc_iomux_set_pad(MX25_PIN_UART1_RXD, + PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PU); + mxc_iomux_set_pad(MX25_PIN_UART1_TXD, + PAD_CTL_PUE_PUD | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX25_PIN_UART1_RTS, + PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PU); + mxc_iomux_set_pad(MX25_PIN_UART1_CTS, + PAD_CTL_PUE_PUD | PAD_CTL_100K_PD); + + break; + case 1: + /* UART 2 IOMUX Configs */ + mxc_request_iomux(MX25_PIN_UART2_RXD, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_UART2_TXD, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_UART2_RTS, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_UART2_CTS, MUX_CONFIG_FUNC); + mxc_iomux_set_pad(MX25_PIN_UART2_RXD, + PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PU); + mxc_iomux_set_pad(MX25_PIN_UART2_TXD, PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX25_PIN_UART2_RTS, + PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PU); + mxc_iomux_set_pad(MX25_PIN_UART2_CTS, PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PD); + break; + case 2: + /* UART 3 IOMUX Configs */ + mxc_request_iomux(MX25_PIN_KPP_ROW0, MUX_CONFIG_ALT1); /*RXD*/ + mxc_request_iomux(MX25_PIN_KPP_ROW1, MUX_CONFIG_ALT1); /*TXD*/ + mxc_request_iomux(MX25_PIN_KPP_ROW2, MUX_CONFIG_ALT1); /*RTS*/ + mxc_request_iomux(MX25_PIN_KPP_ROW3, MUX_CONFIG_ALT1); /*CTS*/ + + mxc_iomux_set_input(MUX_IN_UART3_IPP_UART_RTS_B, + INPUT_CTL_PATH1); + mxc_iomux_set_input(MUX_IN_UART3_IPP_UART_RXD_MUX, + INPUT_CTL_PATH1); + break; + case 3: + /* UART 4 IOMUX Configs */ + mxc_request_iomux(MX25_PIN_LD8, MUX_CONFIG_ALT2); /*RXD*/ + mxc_request_iomux(MX25_PIN_LD9, MUX_CONFIG_ALT2); /*TXD*/ + mxc_request_iomux(MX25_PIN_LD10, MUX_CONFIG_ALT2); /*RTS*/ + mxc_request_iomux(MX25_PIN_LD11, MUX_CONFIG_ALT2); /*CTS*/ + + mxc_iomux_set_input(MUX_IN_UART4_IPP_UART_RTS_B, + INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_UART4_IPP_UART_RXD_MUX, + INPUT_CTL_PATH0); + case 4: + /* UART 5 IOMUX Configs */ + mxc_request_iomux(MX25_PIN_CSI_D2, MUX_CONFIG_ALT1); /*RXD*/ + mxc_request_iomux(MX25_PIN_CSI_D3, MUX_CONFIG_ALT1); /*TXD*/ + mxc_request_iomux(MX25_PIN_CSI_D4, MUX_CONFIG_ALT1); /*RTS*/ + mxc_request_iomux(MX25_PIN_CSI_D5, MUX_CONFIG_ALT1); /*CTS*/ + + mxc_iomux_set_input(MUX_IN_UART5_IPP_UART_RTS_B, + INPUT_CTL_PATH1); + mxc_iomux_set_input(MUX_IN_UART5_IPP_UART_RXD_MUX, + INPUT_CTL_PATH1); + default: + break; + } +} +EXPORT_SYMBOL(gpio_uart_active); + +/*! + * Inactivate a UART port + * + * @param port a UART port + * @param no_irda indicates if the port is used for SIR + */ +void gpio_uart_inactive(int port, int no_irda) +{ + switch (port) { + case 0: + gpio_request(IOMUX_TO_GPIO(MX25_PIN_UART1_RXD), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_UART1_TXD), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_UART1_RTS), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_UART1_CTS), NULL); + + mxc_free_iomux(MX25_PIN_UART1_RXD, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_UART1_TXD, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_UART1_RTS, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_UART1_CTS, MUX_CONFIG_GPIO); + break; + case 1: + gpio_request(IOMUX_TO_GPIO(MX25_PIN_UART2_RXD), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_UART2_TXD), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_UART2_RTS), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_UART2_CTS), NULL); + + mxc_free_iomux(MX25_PIN_UART2_RXD, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_UART2_TXD, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_UART2_RTS, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_UART2_CTS, MUX_CONFIG_GPIO); + break; + case 2: + gpio_request(IOMUX_TO_GPIO(MX25_PIN_KPP_ROW0), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_KPP_ROW1), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_KPP_ROW2), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_KPP_ROW3), NULL); + + mxc_free_iomux(MX25_PIN_KPP_ROW0, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_KPP_ROW1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_KPP_ROW2, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_KPP_ROW3, MUX_CONFIG_GPIO); + break; + case 3: + mxc_free_iomux(MX25_PIN_LD8, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_LD9, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_LD10, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_LD11, MUX_CONFIG_FUNC); + break; + case 4: + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D2), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D3), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D4), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D5), NULL); + + mxc_free_iomux(MX25_PIN_CSI_D2, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D3, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D4, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D5, MUX_CONFIG_GPIO); + break; + default: + break; + } +} +EXPORT_SYMBOL(gpio_uart_inactive); + +/*! + * Configure the IOMUX GPR register to receive shared SDMA UART events + * + * @param port a UART port + */ +void config_uartdma_event(int port) +{ +} +EXPORT_SYMBOL(config_uartdma_event); + +/*! + * Activate Keypad + */ +void gpio_keypad_active(void) +{ + mxc_request_iomux(MX25_PIN_KPP_ROW0, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_KPP_ROW1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_KPP_ROW2, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_KPP_ROW3, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_KPP_COL0, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_KPP_COL1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_KPP_COL2, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_KPP_COL3, MUX_CONFIG_FUNC); + +#define KPP_PAD_CTL_ROW (PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PUD | \ + PAD_CTL_100K_PU) +#define KPP_PAD_CTL_COL (PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PUD | \ + PAD_CTL_100K_PU | PAD_CTL_ODE_OpenDrain) + + mxc_iomux_set_pad(MX25_PIN_KPP_ROW0, KPP_PAD_CTL_ROW); + mxc_iomux_set_pad(MX25_PIN_KPP_ROW1, KPP_PAD_CTL_ROW); + mxc_iomux_set_pad(MX25_PIN_KPP_ROW2, KPP_PAD_CTL_ROW); + mxc_iomux_set_pad(MX25_PIN_KPP_ROW3, KPP_PAD_CTL_ROW); + mxc_iomux_set_pad(MX25_PIN_KPP_COL0, KPP_PAD_CTL_COL); + mxc_iomux_set_pad(MX25_PIN_KPP_COL1, KPP_PAD_CTL_COL); + mxc_iomux_set_pad(MX25_PIN_KPP_COL2, KPP_PAD_CTL_COL); + mxc_iomux_set_pad(MX25_PIN_KPP_COL3, KPP_PAD_CTL_COL); + +#undef KPP_PAD_CTL_ROW +#undef KPP_PAD_CTL_COL +} +EXPORT_SYMBOL(gpio_keypad_active); + +/*! + * Inactivate Keypad + */ +void gpio_keypad_inactive(void) +{ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_KPP_ROW0), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_KPP_ROW1), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_KPP_ROW2), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_KPP_ROW3), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_KPP_COL0), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_KPP_COL1), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_KPP_COL2), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_KPP_COL3), NULL); + + mxc_free_iomux(MX25_PIN_KPP_ROW0, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_KPP_ROW1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_KPP_ROW2, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_KPP_ROW3, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_KPP_COL0, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_KPP_COL1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_KPP_COL2, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_KPP_COL3, MUX_CONFIG_GPIO); +} +EXPORT_SYMBOL(gpio_keypad_inactive); + +/*! + * Activate FEC + */ +void gpio_fec_active(void) +{ + mxc_request_iomux(MX25_PIN_FEC_TX_CLK, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_FEC_RX_DV, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_FEC_RDATA0, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_FEC_TDATA0, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_FEC_TX_EN, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_FEC_MDC, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_FEC_MDIO, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_FEC_RDATA1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_FEC_TDATA1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_POWER_FAIL, MUX_CONFIG_FUNC); /* PHY INT */ + +#define FEC_PAD_CTL1 (PAD_CTL_HYS_SCHMITZ | PAD_CTL_PUE_PUD | \ + PAD_CTL_PKE_ENABLE) +#define FEC_PAD_CTL2 (PAD_CTL_PUE_PUD) + + mxc_iomux_set_pad(MX25_PIN_FEC_TX_CLK, FEC_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_FEC_RX_DV, FEC_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_FEC_RDATA0, FEC_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_FEC_TDATA0, FEC_PAD_CTL2); + mxc_iomux_set_pad(MX25_PIN_FEC_TX_EN, FEC_PAD_CTL2); + mxc_iomux_set_pad(MX25_PIN_FEC_MDC, FEC_PAD_CTL2); + mxc_iomux_set_pad(MX25_PIN_FEC_MDIO, FEC_PAD_CTL1 | PAD_CTL_22K_PU); + mxc_iomux_set_pad(MX25_PIN_FEC_RDATA1, FEC_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_FEC_TDATA1, FEC_PAD_CTL2); + mxc_iomux_set_pad(MX25_PIN_POWER_FAIL, FEC_PAD_CTL1); + + /* + * Set up the FEC_RESET_B and FEC_ENABLE GPIO pins. + * Assert FEC_RESET_B, then power up the PHY by asserting + * FEC_ENABLE, at the same time lifting FEC_RESET_B. + * + * FEC_RESET_B: gpio2[3] is ALT 5 mode of pin D12 + * FEC_ENABLE_B: gpio4[8] is ALT 5 mode of pin A17 + */ + mxc_request_iomux(MX25_PIN_A17, MUX_CONFIG_ALT5); /* FEC_EN */ + mxc_request_iomux(MX25_PIN_D12, MUX_CONFIG_ALT5); /* FEC_RESET_B */ + + mxc_iomux_set_pad(MX25_PIN_A17, PAD_CTL_ODE_OpenDrain); + mxc_iomux_set_pad(MX25_PIN_D12, 0); + + gpio_request(IOMUX_TO_GPIO(MX25_PIN_A17), "a17"); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_D12), "d12"); + + gpio_direction_output(IOMUX_TO_GPIO(MX25_PIN_A17), 0); /* FEC_EN */ + gpio_direction_output(IOMUX_TO_GPIO(MX25_PIN_D12), 0); /* FEC_RESET_B */ + + /* drop PHY power */ + gpio_set_value(IOMUX_TO_GPIO(MX25_PIN_A17), 0); /* FEC_EN */ + + /* assert reset */ + gpio_set_value(IOMUX_TO_GPIO(MX25_PIN_D12), 0); /* FEC_RESET_B */ + udelay(2); /* spec says 1us min */ + + /* turn on PHY power and lift reset */ + gpio_set_value(IOMUX_TO_GPIO(MX25_PIN_A17), 1); /* FEC_EN */ + gpio_set_value(IOMUX_TO_GPIO(MX25_PIN_D12), 1); /* FEC_RESET_B */ + +#undef FEC_PAD_CTL_COMMON +#undef FEC_PAD_CTL1 +#undef FEC_PAD_CTL2 +} +EXPORT_SYMBOL(gpio_fec_active); + +/*! + * Inactivate FEC + */ +void gpio_fec_inactive(void) +{ + /* + * Turn off the PHY. + */ + gpio_set_value(IOMUX_TO_GPIO(MX25_PIN_A17), 0); + + gpio_request(IOMUX_TO_GPIO(MX25_PIN_FEC_TX_CLK), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_FEC_RX_DV), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_FEC_RDATA0), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_FEC_TDATA0), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_FEC_TX_EN), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_FEC_MDC), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_FEC_MDIO), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_FEC_RDATA1), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_FEC_TDATA1), NULL); + + mxc_free_iomux(MX25_PIN_FEC_TX_CLK, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_FEC_RX_DV, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_FEC_RDATA0, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_FEC_TDATA0, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_FEC_TX_EN, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_FEC_MDC, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_FEC_MDIO, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_FEC_RDATA1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_FEC_TDATA1, MUX_CONFIG_GPIO); + mxc_request_iomux(MX25_PIN_POWER_FAIL, MUX_CONFIG_FUNC); /* PHY INT */ + + mxc_free_iomux(MX25_PIN_A17, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_D12, MUX_CONFIG_GPIO); /* FEC_RESET_B */ + + /* We keep pin A17, so FEC_ENABLE doesn't float */ +} +EXPORT_SYMBOL(gpio_fec_inactive); + +/*! + * Activate an I2C device + * + * @param i2c_num an I2C device + */ +void gpio_i2c_active(int i2c_num) +{ +#define I2C_PAD_CTL (PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | \ + PAD_CTL_PUE_PUD | PAD_CTL_100K_PU | PAD_CTL_ODE_OpenDrain) + + switch (i2c_num) { + case 0: + /*I2C1*/ + mxc_request_iomux(MX25_PIN_I2C1_CLK, MUX_CONFIG_SION); + mxc_request_iomux(MX25_PIN_I2C1_DAT, MUX_CONFIG_SION); + mxc_iomux_set_pad(MX25_PIN_I2C1_CLK, I2C_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_I2C1_DAT, I2C_PAD_CTL); + break; + case 1: + /*I2C2*/ + mxc_request_iomux(MX25_PIN_GPIO_C, MUX_CONFIG_ALT2); /*SCL*/ + mxc_request_iomux(MX25_PIN_GPIO_D, MUX_CONFIG_ALT2); /*SDA*/ + mxc_iomux_set_pad(MX25_PIN_GPIO_C, I2C_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_GPIO_D, I2C_PAD_CTL); + mxc_iomux_set_input(MUX_IN_I2C2_IPP_SCL_IN, INPUT_CTL_PATH1); + mxc_iomux_set_input(MUX_IN_I2C2_IPP_SDA_IN, INPUT_CTL_PATH1); + +#if 0 + /* Or use FEC pins if it is not used */ + mxc_request_iomux(MX25_PIN_FEC_RDATA1, MUX_CONFIG_ALT1); /*SCL*/ + mxc_request_iomux(MX25_PIN_FEC_RX_DV, MUX_CONFIG_ALT1); /*SDA*/ + mxc_iomux_set_pad(MX25_PIN_FEC_RDATA1, I2C_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_FEC_RX_DV, I2C_PAD_CTL); + mxc_iomux_set_input(MUX_IN_I2C2_IPP_SCL_IN, INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_I2C2_IPP_SDA_IN, INPUT_CTL_PATH0); +#endif + + break; + case 2: + /*I2C3*/ + mxc_request_iomux(MX25_PIN_HSYNC, MUX_CONFIG_ALT2); /*SCL*/ + mxc_request_iomux(MX25_PIN_VSYNC, MUX_CONFIG_ALT2); /*SDA*/ + mxc_iomux_set_pad(MX25_PIN_HSYNC, I2C_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_VSYNC, I2C_PAD_CTL); + mxc_iomux_set_input(MUX_IN_I2C3_IPP_SCL_IN, INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_I2C3_IPP_SDA_IN, INPUT_CTL_PATH0); + break; + default: + break; + } +#undef I2C_PAD_CTL +} +EXPORT_SYMBOL(gpio_i2c_active); + +/*! + * Inactivate an I2C device + * + * @param i2c_num an I2C device + */ +void gpio_i2c_inactive(int i2c_num) +{ + switch (i2c_num) { + case 0: + /*I2C1*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_I2C1_CLK), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_I2C1_DAT), NULL); + mxc_free_iomux(MX25_PIN_I2C1_CLK, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_I2C1_DAT, MUX_CONFIG_GPIO); + break; + case 1: + /*I2C2*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_GPIO_C), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_GPIO_D), NULL); + mxc_free_iomux(MX25_PIN_GPIO_C, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_GPIO_D, MUX_CONFIG_GPIO); + +#if 0 + /* Or use FEC pins if not in use */ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_FEC_RDATA1, NULL); /*SCL*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_FEC_RX_DV, NULL); /*SDA*/ + mxc_free_iomux(MX25_PIN_FEC_RDATA1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_FEC_RX_DV, MUX_CONFIG_GPIO); +#endif + + break; + case 2: + /*I2C3*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_HSYNC), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_VSYNC), NULL); + mxc_free_iomux(MX25_PIN_HSYNC, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_VSYNC, MUX_CONFIG_GPIO); + break; + default: + break; + } +} +EXPORT_SYMBOL(gpio_i2c_inactive); + +/*! + * Activate a CSPI device + * + * @param cspi_mod a CSPI device + */ +void gpio_spi_active(int cspi_mod) +{ +#define SPI_PAD_CTL1 (PAD_CTL_HYS_SCHMITZ|PAD_CTL_PKE_ENABLE| \ + PAD_CTL_100K_PU) +#define SPI_PAD_CTL2 (PAD_CTL_HYS_SCHMITZ|PAD_CTL_PKE_ENABLE| \ + PAD_CTL_PUE_PUD|PAD_CTL_100K_PU) + + switch (cspi_mod) { + case 0: + /* SPI1 */ + mxc_request_iomux(MX25_PIN_CSPI1_MOSI, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSPI1_MISO, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSPI1_SS0, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSPI1_SS1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSPI1_SCLK, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSPI1_RDY, MUX_CONFIG_FUNC); +#ifndef CONFIG_CAN_FLEXCAN /* MX25 3-stack uses this pin for CAN2 */ + mxc_request_iomux(MX25_PIN_GPIO_C, MUX_CONFIG_ALT5); /*SS2*/ +#endif + mxc_request_iomux(MX25_PIN_VSTBY_ACK, MUX_CONFIG_ALT2); /*SS3*/ + + /* Or if VSTBY_ACK is being used */ + /*mxc_request_iomux(MX25_PIN_NF_CE0, MUX_CONFIG_ALT1);*/ /*SS3*/ + + mxc_iomux_set_pad(MX25_PIN_CSPI1_MOSI, SPI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_CSPI1_MISO, SPI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_CSPI1_SS0, SPI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_CSPI1_SS1, SPI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_CSPI1_SCLK, SPI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_CSPI1_RDY, SPI_PAD_CTL1); +#ifndef CONFIG_CAN_FLEXCAN /* MX25 3-stack uses this pin for CAN2 */ + mxc_iomux_set_pad(MX25_PIN_GPIO_C, SPI_PAD_CTL2); +#endif + mxc_iomux_set_pad(MX25_PIN_VSTBY_ACK, SPI_PAD_CTL1); + + mxc_iomux_set_input(MUX_IN_CSPI1_IPP_IND_SS3_B, + INPUT_CTL_PATH1); + break; + case 1: + /* SPI2 */ + mxc_request_iomux(MX25_PIN_LD12, MUX_CONFIG_ALT2); /*MOSI*/ + mxc_request_iomux(MX25_PIN_LD13, MUX_CONFIG_ALT2); /*MISO*/ + mxc_request_iomux(MX25_PIN_LD14, MUX_CONFIG_ALT2); /*SCLK*/ + mxc_request_iomux(MX25_PIN_LD15, MUX_CONFIG_ALT2); /*RDY*/ + mxc_request_iomux(MX25_PIN_OE_ACD, MUX_CONFIG_ALT2); /*SS0*/ + mxc_request_iomux(MX25_PIN_CONTRAST, MUX_CONFIG_ALT2); /*SS1*/ + mxc_request_iomux(MX25_PIN_GPIO_C, MUX_CONFIG_ALT7); /*SS2*/ + mxc_request_iomux(MX25_PIN_UART2_RTS, MUX_CONFIG_ALT6); /*SS3*/ + + mxc_iomux_set_pad(MX25_PIN_LD12, SPI_PAD_CTL2); + mxc_iomux_set_pad(MX25_PIN_LD13, SPI_PAD_CTL2); + mxc_iomux_set_pad(MX25_PIN_LD14, SPI_PAD_CTL2); + mxc_iomux_set_pad(MX25_PIN_LD15, SPI_PAD_CTL2); + mxc_iomux_set_pad(MX25_PIN_OE_ACD, SPI_PAD_CTL2); + mxc_iomux_set_pad(MX25_PIN_CONTRAST, SPI_PAD_CTL2); + mxc_iomux_set_pad(MX25_PIN_GPIO_C, SPI_PAD_CTL2); + mxc_iomux_set_pad(MX25_PIN_UART2_RTS, SPI_PAD_CTL2); + + mxc_iomux_set_input(MUX_IN_CSPI2_IPP_CSPI_CLK_IN, + INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_CSPI2_IPP_IND_DATAREADY_B, + INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_CSPI2_IPP_IND_MISO, INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_CSPI2_IPP_IND_MOSI, INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_CSPI2_IPP_IND_SS0_B, + INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_CSPI2_IPP_IND_SS1_B, + INPUT_CTL_PATH0); + break; + case 2: + /* SPI3 */ + mxc_request_iomux(MX25_PIN_EB0, MUX_CONFIG_ALT6); /*SS0*/ + mxc_request_iomux(MX25_PIN_EB1, MUX_CONFIG_ALT6); /*SS1*/ + mxc_request_iomux(MX25_PIN_CS4, MUX_CONFIG_ALT6); /*MOSI*/ + mxc_request_iomux(MX25_PIN_CS5, MUX_CONFIG_ALT6); /*MISO*/ + mxc_request_iomux(MX25_PIN_ECB, MUX_CONFIG_ALT6); /*SCLK*/ + mxc_request_iomux(MX25_PIN_LBA, MUX_CONFIG_ALT6); /*RDY*/ + mxc_request_iomux(MX25_PIN_GPIO_D, MUX_CONFIG_ALT7); /*SS2*/ + mxc_request_iomux(MX25_PIN_CSI_D9, MUX_CONFIG_ALT7); /*SS3*/ + + mxc_iomux_set_pad(MX25_PIN_EB0, SPI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_EB1, SPI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_CS4, SPI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_CS5, SPI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_ECB, SPI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_LBA, SPI_PAD_CTL1); + + mxc_iomux_set_input(MUX_IN_CSPI3_IPP_CSPI_CLK_IN, + INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_CSPI3_IPP_IND_DATAREADY_B, + INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_CSPI3_IPP_IND_MISO, INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_CSPI3_IPP_IND_MOSI, INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_CSPI3_IPP_IND_SS0_B, + INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_CSPI3_IPP_IND_SS1_B, + INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_CSPI3_IPP_IND_SS2_B, + INPUT_CTL_PATH1); + mxc_iomux_set_input(MUX_IN_CSPI3_IPP_IND_SS3_B, + INPUT_CTL_PATH0); + break; + default: + break; + } +#undef SPI_PAD_CTL1 +#undef SPI_PAD_CTL2 +} +EXPORT_SYMBOL(gpio_spi_active); + +/*! + * Inactivate a CSPI device + * + * @param cspi_mod a CSPI device + */ +void gpio_spi_inactive(int cspi_mod) +{ + switch (cspi_mod) { + case 0: + /* SPI1 */ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSPI1_MOSI), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSPI1_MISO), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSPI1_SS0), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSPI1_SS1), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSPI1_SCLK), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSPI1_RDY), NULL); +#ifndef CONFIG_CAN_FLEXCAN /* MX25 3-stack uses this pin for CAN2 */ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_GPIO_C), NULL); /*SS2*/ +#endif + gpio_request(IOMUX_TO_GPIO(MX25_PIN_VSTBY_ACK), NULL); /*SS3*/ + + mxc_free_iomux(MX25_PIN_CSPI1_MOSI, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSPI1_MISO, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSPI1_SS0, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSPI1_SS1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSPI1_SCLK, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSPI1_RDY, MUX_CONFIG_GPIO); +#ifndef CONFIG_CAN_FLEXCAN /* MX25 3-stack uses this pin for CAN2 */ + mxc_free_iomux(MX25_PIN_GPIO_C, MUX_CONFIG_GPIO); +#endif + mxc_free_iomux(MX25_PIN_VSTBY_ACK, MUX_CONFIG_GPIO); + break; + case 1: + /* SPI2 */ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD12), NULL); /*MOSI*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD13), NULL); /*MISO*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD14), NULL); /*SCLK*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD15), NULL); /*RDY*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_OE_ACD), NULL); /*SS0*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CONTRAST), NULL); /*SS1*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_GPIO_C), NULL); /*SS2*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_UART2_RTS), NULL); /*SS3*/ + + mxc_free_iomux(MX25_PIN_LD12, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD13, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD14, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD15, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_OE_ACD, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CONTRAST, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_GPIO_C, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_UART2_RTS, MUX_CONFIG_GPIO); + break; + case 2: + /* SPI3 */ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_EB0), NULL); /*SS0*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_EB1), NULL); /*SS1*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CS4), NULL); /*MOSI*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CS5), NULL); /*MISO*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_ECB), NULL); /*SCLK*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LBA), NULL); /*RDY*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_GPIO_D), NULL); /*SS2*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D9), NULL); /*SS3*/ + + mxc_free_iomux(MX25_PIN_EB0, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_EB1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CS4, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CS5, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_ECB, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LBA, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_GPIO_D, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D9, MUX_CONFIG_GPIO); + break; + default: + break; + } +} +EXPORT_SYMBOL(gpio_spi_inactive); + +/*! + * Activate LCD + */ +void gpio_lcdc_active(void) +{ + mxc_request_iomux(MX25_PIN_LD0, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD2, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD3, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD4, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD5, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD6, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD7, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD8, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD9, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD10, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD11, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD12, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD13, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD14, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LD15, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_GPIO_E, MUX_CONFIG_ALT2); /*D16*/ + mxc_request_iomux(MX25_PIN_GPIO_F, MUX_CONFIG_ALT2); /*D17*/ + mxc_request_iomux(MX25_PIN_HSYNC, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_VSYNC, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_LSCLK, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_OE_ACD, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CONTRAST, MUX_CONFIG_FUNC); + +#define LCD_PAD_CTL (PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PUD | PAD_CTL_100K_PU) + mxc_iomux_set_pad(MX25_PIN_LD0, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD1, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD2, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD3, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD4, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD5, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD6, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD7, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD8, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD9, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD10, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD11, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD12, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD13, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD14, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD15, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_GPIO_E, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_GPIO_F, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_HSYNC, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_VSYNC, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LSCLK, LCD_PAD_CTL | PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX25_PIN_OE_ACD, LCD_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CONTRAST, LCD_PAD_CTL); +} +EXPORT_SYMBOL(gpio_lcdc_active); + +/*! + * Inactivate LCD + */ +void gpio_lcdc_inactive(void) +{ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD0), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD1), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD2), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD3), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD4), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD5), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD6), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD7), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_GPIO_E), NULL); /*D16*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_GPIO_F), NULL); /*D17*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_HSYNC), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_VSYNC), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LSCLK), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_OE_ACD), NULL); + + mxc_free_iomux(MX25_PIN_LD0, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD2, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD3, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD4, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD5, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD6, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD7, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD8, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_LD9, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_LD10, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_LD11, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_LD12, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_LD13, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_LD14, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_LD15, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_GPIO_E, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_GPIO_F, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_HSYNC, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_VSYNC, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LSCLK, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_OE_ACD, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CONTRAST, MUX_CONFIG_FUNC); +} +EXPORT_SYMBOL(gpio_lcdc_inactive); + +/*! + * Activate SDHC + * + * @param module SDHC module number + */ +void gpio_sdhc_active(int module) +{ +#define SDHC_PAD_CTL (PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PUD | \ + PAD_CTL_47K_PU | PAD_CTL_SRE_FAST) + + switch (module) { + case 0: + /* SDHC1 */ + mxc_request_iomux(MX25_PIN_SD1_CMD, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_request_iomux(MX25_PIN_SD1_CLK, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_request_iomux(MX25_PIN_SD1_DATA0, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_SD1_DATA1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_SD1_DATA2, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_SD1_DATA3, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_A14, MUX_CONFIG_ALT5); /*SD1_WP*/ + mxc_request_iomux(MX25_PIN_A15, MUX_CONFIG_ALT5); /*SD1_DET*/ + + mxc_iomux_set_pad(MX25_PIN_SD1_CMD, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_SD1_CLK, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_SD1_DATA0, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_SD1_DATA1, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_SD1_DATA2, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_SD1_DATA3, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_A14, PAD_CTL_DRV_NORMAL); + mxc_iomux_set_pad(MX25_PIN_A15, PAD_CTL_DRV_NORMAL); + + /* Set write protect and card detect gpio as inputs */ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_A14), "a14"); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_A15), "a15"); + gpio_direction_input(IOMUX_TO_GPIO(MX25_PIN_A14)); /*SD1_WP*/ + gpio_direction_input(IOMUX_TO_GPIO(MX25_PIN_A15)); /*SD1_DET*/ + + break; + case 1: + /* SDHC2 */ + mxc_request_iomux(MX25_PIN_LD8, + MUX_CONFIG_ALT6 | MUX_CONFIG_SION); /*CMD*/ + mxc_request_iomux(MX25_PIN_LD9, + MUX_CONFIG_ALT6 | MUX_CONFIG_SION); /*CLK*/ + mxc_request_iomux(MX25_PIN_LD10, MUX_CONFIG_ALT6); /*DAT0*/ + mxc_request_iomux(MX25_PIN_LD11, MUX_CONFIG_ALT6); /*DAT1*/ + mxc_request_iomux(MX25_PIN_LD12, MUX_CONFIG_ALT6); /*DAT2*/ + mxc_request_iomux(MX25_PIN_LD13, MUX_CONFIG_ALT6); /*DAT3*/ + + mxc_iomux_set_pad(MX25_PIN_LD8, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD9, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD10, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD11, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD12, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_LD13, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_D2, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_D3, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_D4, SDHC_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_D5, SDHC_PAD_CTL); + break; + default: + break; + } +} +EXPORT_SYMBOL(gpio_sdhc_active); + +/*! + * Inactivate SDHC + * + * @param module SDHC module number + */ +void gpio_sdhc_inactive(int module) +{ + switch (module) { + case 0: + /* SDHC1 */ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_SD1_CMD), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_SD1_CLK), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_SD1_DATA0), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_SD1_DATA1), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_SD1_DATA2), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_SD1_DATA3), NULL); + + mxc_free_iomux(MX25_PIN_SD1_CMD, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_SD1_CLK, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_SD1_DATA0, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_SD1_DATA1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_SD1_DATA2, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_SD1_DATA3, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_A14, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_A15, MUX_CONFIG_GPIO); + break; + case 1: + /* SDHC2 */ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD8), NULL); /*CMD*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD9), NULL); /*CLK*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD10), NULL); /*DAT0*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD11), NULL); /*DAT1*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD12), NULL); /*DAT2*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_LD13), NULL); /*DAT3*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D2), NULL); /*DAT4*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D3), NULL); /*DAT5*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D4), NULL); /*DAT6*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D5), NULL); /*DAT7*/ + + mxc_free_iomux(MX25_PIN_LD8, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD9, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD10, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD11, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD12, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_LD13, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D2, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D3, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D4, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D5, MUX_CONFIG_GPIO); + break; + default: + break; + } +} +EXPORT_SYMBOL(gpio_sdhc_inactive); + +/* + * Probe for the card. If present the GPIO data would be set. + */ +unsigned int sdhc_get_card_det_status(struct device *dev) +{ + unsigned int ret = 0; + + ret = gpio_get_value(IOMUX_TO_GPIO(MX25_PIN_A15)); + return ret; +} +EXPORT_SYMBOL(sdhc_get_card_det_status); + +/*! + * Get pin value to detect write protection + */ +int sdhc_write_protect(struct device *dev) +{ + unsigned int rc = 0; + + rc = gpio_get_value(IOMUX_TO_GPIO(MX25_PIN_A14)); + return rc; +} +EXPORT_SYMBOL(sdhc_write_protect); + +/* + * USB Host2 + * + * This configuration uses the on-chip FS/LS serial transceiver. + * USBPHY2_{DP,DM} pins are not muxed. + * We just need to grab USBH2_PWR, USBH2_OC and the Bluetooth/USB + * mux control signal. + */ +int gpio_usbh2_active(void) +{ + if (mxc_request_iomux(MX25_PIN_D9, MUX_CONFIG_ALT6) || /* PWR */ + mxc_request_iomux(MX25_PIN_D8, MUX_CONFIG_ALT6) || /* OC */ + mxc_request_iomux(MX25_PIN_A21, MUX_CONFIG_ALT5)) { /* BT_USB_CS */ + return -EINVAL; + } + + /* + * This pin controls the mux that switches between + * the J18 connector and the on-board bluetooth module. + * dir: 0 = out + * pin: 0 = J18, 1 = BT + */ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_A21), "a21"); + gpio_direction_output(IOMUX_TO_GPIO(MX25_PIN_A21), 0); + gpio_set_value(IOMUX_TO_GPIO(MX25_PIN_A21), 0); + + return 0; +} +EXPORT_SYMBOL(gpio_usbh2_active); + +void gpio_usbh2_inactive(void) +{ + mxc_free_iomux(MX25_PIN_D9, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_D8, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_A21, MUX_CONFIG_GPIO); +} + +/* + * USB OTG UTMI + * + * This configuration uses the on-chip UTMI transceiver. + * USBPHY1_{VBUS,DP,DM,UID,RREF} pins are not muxed. + * We just need to grab the USBOTG_PWR and USBOTG_OC pins. + */ +int gpio_usbotg_utmi_active(void) +{ + if (mxc_request_iomux(MX25_PIN_GPIO_A, MUX_CONFIG_ALT2) || /* PWR */ + mxc_request_iomux(MX25_PIN_GPIO_B, MUX_CONFIG_ALT2)) { /* OC */ + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL(gpio_usbotg_utmi_active); + +void gpio_usbotg_utmi_inactive(void) +{ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_GPIO_A), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_GPIO_B), NULL); + + mxc_free_iomux(MX25_PIN_GPIO_A, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_GPIO_B, MUX_CONFIG_GPIO); +} +EXPORT_SYMBOL(gpio_usbotg_utmi_inactive); + +/*! + * Activate camera sensor + */ +void gpio_sensor_active(void) +{ + mxc_request_iomux(MX25_PIN_CSI_D2, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSI_D3, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSI_D4, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSI_D5, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSI_D6, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSI_D7, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSI_D8, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSI_D9, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSI_HSYNC, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSI_MCLK, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSI_PIXCLK, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_CSI_VSYNC, MUX_CONFIG_FUNC); + mxc_request_iomux(MX25_PIN_A19, MUX_CONFIG_ALT5); /*CSI_PWDN*/ + mxc_request_iomux(MX25_PIN_A20, MUX_CONFIG_ALT5); /*CMOS_RST*/ + + gpio_request(IOMUX_TO_GPIO(MX25_PIN_A19), "a19"); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_A20), "a20"); + gpio_direction_output(IOMUX_TO_GPIO(MX25_PIN_A19), 0); /*CSI_PWDN*/ + gpio_set_value(IOMUX_TO_GPIO(MX25_PIN_A19), 0); + gpio_direction_output(IOMUX_TO_GPIO(MX25_PIN_A20), 0); /*CMOS_RST*/ + gpio_set_value(IOMUX_TO_GPIO(MX25_PIN_A20), 0); + mdelay(20); + gpio_set_value(IOMUX_TO_GPIO(MX25_PIN_A20), 1); + +#define CSI_PAD_CTL1 (PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PU) +#define CSI_PAD_CTL2 (PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | \ + PAD_CTL_100K_PU) + + mxc_iomux_set_pad(MX25_PIN_CSI_D2, CSI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_CSI_D3, CSI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_CSI_D4, CSI_PAD_CTL2); + mxc_iomux_set_pad(MX25_PIN_CSI_D5, CSI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_CSI_D6, CSI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_CSI_D7, CSI_PAD_CTL2); + mxc_iomux_set_pad(MX25_PIN_CSI_D8, CSI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_CSI_D9, CSI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_CSI_HSYNC, CSI_PAD_CTL1); + mxc_iomux_set_pad(MX25_PIN_CSI_MCLK, PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PU | PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX25_PIN_CSI_PIXCLK, CSI_PAD_CTL2); + mxc_iomux_set_pad(MX25_PIN_CSI_VSYNC, CSI_PAD_CTL1); +} +EXPORT_SYMBOL(gpio_sensor_active); + +/*! + * Inactivate camera sensor + */ +void gpio_sensor_inactive(void) +{ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D2), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D3), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D4), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D5), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D6), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D7), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D8), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_D9), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_HSYNC), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_MCLK), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_PIXCLK), NULL); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_CSI_VSYNC), NULL); + + mxc_free_iomux(MX25_PIN_A19, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_A20, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D2, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D3, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D4, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D5, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D6, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D7, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D8, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_D9, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_HSYNC, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_MCLK, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_PIXCLK, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_CSI_VSYNC, MUX_CONFIG_GPIO); +} +EXPORT_SYMBOL(gpio_sensor_inactive); + +/*! + * Activate ESAI ports to enable surround sound I/O + */ +void gpio_activate_esai_ports(void) +{ + mxc_request_iomux(MX25_PIN_CSI_D2, MUX_CONFIG_ALT3); /*SCKR*/ + mxc_request_iomux(MX25_PIN_CSI_D3, MUX_CONFIG_ALT3); /*FSR*/ + mxc_request_iomux(MX25_PIN_CSI_D4, MUX_CONFIG_ALT3); /*HCKR*/ + mxc_request_iomux(MX25_PIN_CSI_D5, MUX_CONFIG_ALT3); /*SCKT*/ + mxc_request_iomux(MX25_PIN_CSI_D6, MUX_CONFIG_ALT3); /*FST*/ + mxc_request_iomux(MX25_PIN_CSI_D7, MUX_CONFIG_ALT3); /*HCKT*/ + mxc_request_iomux(MX25_PIN_CSI_D8, MUX_CONFIG_ALT3); /*TX5_RX0*/ + mxc_request_iomux(MX25_PIN_CSI_D9, MUX_CONFIG_ALT3); /*TX4_RX1*/ + mxc_request_iomux(MX25_PIN_CSI_MCLK, MUX_CONFIG_ALT3); /*TX3_RX2*/ + mxc_request_iomux(MX25_PIN_CSI_VSYNC, MUX_CONFIG_ALT3); /*TX2_RX3*/ + mxc_request_iomux(MX25_PIN_CSI_HSYNC, MUX_CONFIG_ALT3); /*TX1*/ + mxc_request_iomux(MX25_PIN_CSI_PIXCLK, MUX_CONFIG_ALT3); /*TX0*/ + +#define ESAI_PAD_CTL (PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | \ + PAD_CTL_100K_PU | PAD_CTL_PUE_PUD) + mxc_iomux_set_pad(MX25_PIN_CSI_D2, ESAI_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_D3, ESAI_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_D4, ESAI_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_D5, ESAI_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_D6, ESAI_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_D7, ESAI_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_D8, ESAI_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_D9, ESAI_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_MCLK, ESAI_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_VSYNC, ESAI_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_HSYNC, ESAI_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_CSI_PIXCLK, ESAI_PAD_CTL); + +#undef ESAI_PAD_CTL +} +EXPORT_SYMBOL(gpio_activate_esai_ports); + +/*! + * Inactivate ESAI ports to disable surround sound I/O + */ +void gpio_deactivate_esai_ports(void) +{ + mxc_free_iomux(MX25_PIN_CSI_D2, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_CSI_D3, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_CSI_D4, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_CSI_D5, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_CSI_D6, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_CSI_D7, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_CSI_D8, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_CSI_D9, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_CSI_MCLK, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_CSI_VSYNC, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_CSI_HSYNC, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_CSI_PIXCLK, MUX_CONFIG_FUNC); +} +EXPORT_SYMBOL(gpio_deactivate_esai_ports); + + +/*! + * Activate CAN + */ +void gpio_can_active(int id) +{ +#define CAN_PAD_CTL (PAD_CTL_DRV_3_3V | PAD_CTL_PKE_NONE | PAD_CTL_ODE_CMOS | \ + PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW) +#define CAN_PAD_IN_CTL (PAD_CTL_HYS_CMOS | PAD_CTL_PKE_NONE) + + switch (id) { + case 0: + /* CAN1 */ + mxc_request_iomux(MX25_PIN_GPIO_A, MUX_CONFIG_ALT6); /*TXCAN*/ + mxc_request_iomux(MX25_PIN_GPIO_B, MUX_CONFIG_ALT6); /*RXCAN*/ + + mxc_iomux_set_pad(MX25_PIN_GPIO_A, CAN_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_GPIO_B, CAN_PAD_IN_CTL); + + mxc_iomux_set_input(MUX_IN_CAN1_IPP_IND_CANRX, INPUT_CTL_PATH1); + break; + case 1: + /* CAN2 */ + mxc_request_iomux(MX25_PIN_GPIO_C, MUX_CONFIG_ALT6); /*TXCAN*/ + mxc_request_iomux(MX25_PIN_GPIO_D, MUX_CONFIG_ALT6); /*RXCAN*/ + mxc_request_iomux(MX25_PIN_D14, MUX_CONFIG_ALT5); /*PWDN*/ + + mxc_iomux_set_pad(MX25_PIN_GPIO_C, CAN_PAD_CTL); + mxc_iomux_set_pad(MX25_PIN_GPIO_D, CAN_PAD_IN_CTL); + mxc_iomux_set_pad(MX25_PIN_D14, CAN_PAD_CTL); + + mxc_iomux_set_input(MUX_IN_CAN2_IPP_IND_CANRX, INPUT_CTL_PATH1); + + /* Configure CAN_PWDN as output */ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_D14), "d14"); + gpio_direction_output(IOMUX_TO_GPIO(MX25_PIN_D14), 0); + + /* Enable input by setting PWDN/TLE6250.INH low (gpio4 bit6) */ + gpio_set_value(IOMUX_TO_GPIO(MX25_PIN_D14), 0); + break; + default: + break; + } +} +EXPORT_SYMBOL(gpio_can_active); + +/*! + * Inactivate CAN + */ +void gpio_can_inactive(int id) +{ + switch (id) { + case 0: + /* CAN1 */ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_GPIO_A), NULL); /*TXCAN*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_GPIO_B), NULL); /*RXCAN*/ + + mxc_free_iomux(MX25_PIN_GPIO_A, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_GPIO_B, MUX_CONFIG_FUNC); + + break; + case 1: + /* CAN2 */ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_GPIO_C), NULL); /*TXCAN*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_GPIO_D), NULL); /*RXCAN*/ + + mxc_free_iomux(MX25_PIN_GPIO_C, MUX_CONFIG_FUNC); + mxc_free_iomux(MX25_PIN_GPIO_D, MUX_CONFIG_FUNC); + + /* Disable input by setting PWDN/TLE6250.INH high */ + gpio_set_value(IOMUX_TO_GPIO(MX25_PIN_D14), 1); + mxc_free_iomux(MX25_PIN_D14, MUX_CONFIG_ALT5); + break; + default: + break; + } +} +EXPORT_SYMBOL(gpio_can_inactive); + +/*! + * This function activates DAM port 4 to enable + * audio I/O. + */ +void gpio_activate_audio_ports(void) +{ + mxc_request_iomux(MX25_PIN_EB0, MUX_CONFIG_ALT4); /*SSI4_STXD*/ + mxc_request_iomux(MX25_PIN_EB1, MUX_CONFIG_ALT4); /*SSI4_SRXD*/ + mxc_request_iomux(MX25_PIN_RW, MUX_CONFIG_ALT4); /*SSI4_STXFS*/ + mxc_request_iomux(MX25_PIN_OE, MUX_CONFIG_ALT4); /*SSI4_SCK*/ + mxc_request_iomux(MX25_PIN_A10, MUX_CONFIG_ALT5); /*HP_DEC*/ + mxc_request_iomux(MX25_PIN_D13, MUX_CONFIG_ALT5); /*AMP_SHUTDOWN*/ + + mxc_iomux_set_pad(MX25_PIN_EB0, PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX25_PIN_EB1, PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX25_PIN_RW, PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX25_PIN_OE, PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX25_PIN_D13, PAD_CTL_DRV_3_3V); + + gpio_request(IOMUX_TO_GPIO(MX25_PIN_A10), "a10"); + gpio_direction_input(IOMUX_TO_GPIO(MX25_PIN_A10)); + gpio_request(IOMUX_TO_GPIO(MX25_PIN_D13), "d13"); + gpio_direction_output(IOMUX_TO_GPIO(MX25_PIN_D13), 0); +} +EXPORT_SYMBOL(gpio_activate_audio_ports); + +/*! + * This function inactivates DAM port 4 for + * audio I/O + */ +void gpio_inactive_audio_ports(void) +{ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_EB0), NULL); /*SSI4_STXD*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_EB1), NULL); /*SSI4_SRXD*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_RW), NULL); /*SSI4_STXFS*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_OE), NULL); /*SSI4_SCK*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_A10), NULL); /*HP_DEC*/ + gpio_request(IOMUX_TO_GPIO(MX25_PIN_D13), NULL); /*AMP_SHUTDOWN*/ + + mxc_free_iomux(MX25_PIN_EB0, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_EB1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_RW, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_OE, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_A10, MUX_CONFIG_GPIO); + mxc_free_iomux(MX25_PIN_D13, MUX_CONFIG_GPIO); +} +EXPORT_SYMBOL(gpio_inactive_audio_ports); + +int headphone_det_status(void) +{ + return gpio_get_value(IOMUX_TO_GPIO(MX25_PIN_A10)); +} +EXPORT_SYMBOL(headphone_det_status); + +void sgtl5000_enable_amp(void) +{ + gpio_set_value(IOMUX_TO_GPIO(MX25_PIN_D13), 1); +} +EXPORT_SYMBOL(sgtl5000_enable_amp); diff --git a/arch/arm/mach-mx25/mx25_3stack_pmic_mc34704.c b/arch/arm/mach-mx25/mx25_3stack_pmic_mc34704.c new file mode 100644 index 000000000000..95fbf66fe564 --- /dev/null +++ b/arch/arm/mach-mx25/mx25_3stack_pmic_mc34704.c @@ -0,0 +1,127 @@ +/* + * mx25-3stack-pmic-mc34704.c -- i.MX25 3STACK Driver for MC34704 PMIC + */ + /* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + + /* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/pmic_external.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/mc34704/core.h> +#include "iomux.h" + +/* + * Convenience conversion. + * Here atm, maybe there is somewhere better for this. + */ +#define mV_to_uV(mV) (mV * 1000) +#define uV_to_mV(uV) (uV / 1000) +#define V_to_uV(V) (mV_to_uV(V * 1000)) +#define uV_to_V(uV) (uV_to_mV(uV) / 1000) + +struct mc34704; + +static struct regulator_init_data rbklt_init = { + .constraints = { + .name = "REG1_BKLT", + .min_uV = + mV_to_uV(REG1_V_MV * (1000 + REG1_DVS_MIN_PCT * 10) / + 1000), + .max_uV = + mV_to_uV(REG1_V_MV * (1000 + REG1_DVS_MAX_PCT * 10) / + 1000), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data rcpu_init = { + .constraints = { + .name = "REG2_CPU", + .min_uV = + mV_to_uV(REG2_V_MV * (1000 + REG2_DVS_MIN_PCT * 10) / + 1000), + .max_uV = + mV_to_uV(REG2_V_MV * (1000 + REG2_DVS_MAX_PCT * 10) / + 1000), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data rcore_init = { + .constraints = { + .name = "REG3_CORE", + .min_uV = + mV_to_uV(REG3_V_MV * (1000 + REG3_DVS_MIN_PCT * 10) / + 1000), + .max_uV = + mV_to_uV(REG3_V_MV * (1000 + REG3_DVS_MAX_PCT * 10) / + 1000), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data rddr_init = { + .constraints = { + .name = "REG4_DDR", + .min_uV = + mV_to_uV(REG4_V_MV * (1000 + REG4_DVS_MIN_PCT * 10) / + 1000), + .max_uV = + mV_to_uV(REG4_V_MV * (1000 + REG4_DVS_MAX_PCT * 10) / + 1000), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data rpers_init = { + .constraints = { + .name = "REG5_PERS", + .min_uV = + mV_to_uV(REG5_V_MV * (1000 + REG5_DVS_MIN_PCT * 10) / + 1000), + .max_uV = + mV_to_uV(REG5_V_MV * (1000 + REG5_DVS_MAX_PCT * 10) / + 1000), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static int mc34704_regulator_init(struct mc34704 *mc34704) +{ + mc34704_register_regulator(mc34704, MC34704_BKLT, &rbklt_init); + mc34704_register_regulator(mc34704, MC34704_CPU, &rcpu_init); + mc34704_register_regulator(mc34704, MC34704_CORE, &rcore_init); + mc34704_register_regulator(mc34704, MC34704_DDR, &rddr_init); + mc34704_register_regulator(mc34704, MC34704_PERS, &rpers_init); + + return 0; +} + +static struct mc34704_platform_data mc34704_plat = { + .init = mc34704_regulator_init, +}; + +static struct i2c_board_info __initdata mc34704_i2c_device = { + .type = "mc34704", + .addr = 0x54, + .platform_data = &mc34704_plat, +}; + +int __init mx25_3stack_init_mc34704(void) +{ + return i2c_register_board_info(0, &mc34704_i2c_device, 1); +} diff --git a/arch/arm/mach-mx25/mx25_pins.h b/arch/arm/mach-mx25/mx25_pins.h new file mode 100644 index 000000000000..693eeb5918dd --- /dev/null +++ b/arch/arm/mach-mx25/mx25_pins.h @@ -0,0 +1,250 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_ARCH_MXC_MX25_PINS_H__ +#define __ASM_ARCH_MXC_MX25_PINS_H__ + +/*! + * @file arch-mxc/mx25_pins.h + * + * @brief MX25 I/O Pin List + * + * @ingroup GPIO_MX25 + */ + +#ifndef __ASSEMBLY__ + +/*! + * @name IOMUX/PAD Bit field definitions + */ + +/*! @{ */ + +/*! + * In order to identify pins more effectively, each mux-controlled pin's + * enumerated value is constructed in the following way: + * + * ------------------------------------------------------------------- + * 31-29 | 28 - 24 |23 - 21| 20 - 10| 9 - 0 + * ------------------------------------------------------------------- + * IO_P | IO_I | RSVD | PAD_I | MUX_I + * ------------------------------------------------------------------- + * + * Bit 0 to 7 contains MUX_I used to identify the register + * offset (base is IOMUX_module_base ) defined in the Section + * "sw_pad_ctl & sw_mux_ctl details" of the IC Spec. Similar field + * definitions are used for the pad control register. For example, + * MX25_PIN_A14 is defined in the enumeration: + * ( 0x10 << MUX_I) | ( 0x230 << PAD_I) + * So the absolute address is: IOMUX_module_base + 0x10. + * The pad control register offset is: 0x230. + */ + +/*! + * Starting bit position within each entry of \b iomux_pins to represent the + * MUX control register offset + */ +#define MUX_I 0 +/*! + * Starting bit position within each entry of \b iomux_pins to represent the + * PAD control register offset + */ +#define PAD_I 10 + +/*! + * Starting bit position within each entry of \b iomux_pins to represent the + * reserved filed + */ +#define RSVD_I 21 + +#define NON_GPIO_I 0x7 +#define PIN_TO_MUX_MASK ((1<<(PAD_I - MUX_I)) - 1) +#define PIN_TO_PAD_MASK ((1<<(RSVD_I - PAD_I)) - 1) +#define NON_MUX_I PIN_TO_MUX_MASK + +#define _MXC_BUILD_PIN(gp, gi, mi, pi) \ + (((gp) << MUX_IO_P) | ((gi) << MUX_IO_I) | \ + ((mi) << MUX_I) | ((pi) << PAD_I)) + +#define _MXC_BUILD_GPIO_PIN(gp, gi, mi, pi) \ + _MXC_BUILD_PIN(gp, gi, mi, pi) + +#define _MXC_BUILD_NON_GPIO_PIN(mi, pi) \ + _MXC_BUILD_PIN(NON_GPIO_I, 0, mi, pi) + +#define PIN_TO_IOMUX_MUX(pin) ((pin >> MUX_I) & PIN_TO_MUX_MASK) +#define PIN_TO_IOMUX_PAD(pin) ((pin >> PAD_I) & PIN_TO_PAD_MASK) + +/*! @} End IOMUX/PAD Bit field definitions */ + +enum iomux_pins { + MX25_PIN_A10 = _MXC_BUILD_GPIO_PIN(3, 0, 0x8, 0x0), + MX25_PIN_A13 = _MXC_BUILD_GPIO_PIN(3, 1, 0x0c, 0x22C), + MX25_PIN_A14 = _MXC_BUILD_GPIO_PIN(1, 0, 0x10, 0x230), + MX25_PIN_A15 = _MXC_BUILD_GPIO_PIN(1, 1, 0x14, 0x234), + MX25_PIN_A16 = _MXC_BUILD_GPIO_PIN(1, 2, 0x18, 0x0), + MX25_PIN_A17 = _MXC_BUILD_GPIO_PIN(1, 3, 0x1c, 0x238), + MX25_PIN_A18 = _MXC_BUILD_GPIO_PIN(1, 4, 0x20, 0x23c), + MX25_PIN_A19 = _MXC_BUILD_GPIO_PIN(1, 5, 0x24, 0x240), + MX25_PIN_A20 = _MXC_BUILD_GPIO_PIN(1, 6, 0x28, 0x244), + MX25_PIN_A21 = _MXC_BUILD_GPIO_PIN(1, 7, 0x2c, 0x248), + MX25_PIN_A22 = _MXC_BUILD_GPIO_PIN(1, 8, 0x30, 0x0), + MX25_PIN_A23 = _MXC_BUILD_GPIO_PIN(1, 9, 0x34, 0x24c), + MX25_PIN_A24 = _MXC_BUILD_GPIO_PIN(1, 10, 0x38, 0x250), + MX25_PIN_A25 = _MXC_BUILD_GPIO_PIN(1, 11, 0x3c, 0x254), + MX25_PIN_EB0 = _MXC_BUILD_GPIO_PIN(1, 12, 0x40, 0x258), + MX25_PIN_EB1 = _MXC_BUILD_GPIO_PIN(1, 13, 0x44, 0x25c), + MX25_PIN_OE = _MXC_BUILD_GPIO_PIN(1, 14, 0x48, 0x260), + MX25_PIN_CS0 = _MXC_BUILD_GPIO_PIN(3, 2, 0x4c, 0x0), + MX25_PIN_CS1 = _MXC_BUILD_GPIO_PIN(3, 3, 0x50, 0x0), + MX25_PIN_CS4 = _MXC_BUILD_GPIO_PIN(2, 20, 0x54, 0x264), + MX25_PIN_CS5 = _MXC_BUILD_GPIO_PIN(2, 21, 0x58, 0x268), + MX25_PIN_NF_CE0 = _MXC_BUILD_GPIO_PIN(2, 22, 0x5c, 0x26c), + MX25_PIN_ECB = _MXC_BUILD_GPIO_PIN(2, 23, 0x60, 0x270), + MX25_PIN_LBA = _MXC_BUILD_GPIO_PIN(2, 24, 0x64, 0x274), + MX25_PIN_BCLK = _MXC_BUILD_GPIO_PIN(3, 4, 0x68, 0x0), + MX25_PIN_RW = _MXC_BUILD_GPIO_PIN(2, 25, 0x6c, 0x278), + MX25_PIN_NFWE_B = _MXC_BUILD_GPIO_PIN(2, 26, 0x70, 0x0), + MX25_PIN_NFRE_B = _MXC_BUILD_GPIO_PIN(2, 27, 0x74, 0x0), + MX25_PIN_NFALE = _MXC_BUILD_GPIO_PIN(2, 28, 0x78, 0x0), + MX25_PIN_NFCLE = _MXC_BUILD_GPIO_PIN(2, 29, 0x7c, 0x0), + MX25_PIN_NFWP_B = _MXC_BUILD_GPIO_PIN(2, 30, 0x80, 0x0), + MX25_PIN_NFRB = _MXC_BUILD_GPIO_PIN(2, 31, 0x84, 0x27c), + MX25_PIN_D15 = _MXC_BUILD_GPIO_PIN(3, 5, 0x88, 0x280), + MX25_PIN_D14 = _MXC_BUILD_GPIO_PIN(3, 6, 0x8c, 0x284), + MX25_PIN_D13 = _MXC_BUILD_GPIO_PIN(3, 7, 0x90, 0x288), + MX25_PIN_D12 = _MXC_BUILD_GPIO_PIN(3, 8, 0x94, 0x28c), + MX25_PIN_D11 = _MXC_BUILD_GPIO_PIN(3, 9, 0x98, 0x290), + MX25_PIN_D10 = _MXC_BUILD_GPIO_PIN(3, 10, 0x9c, 0x294), + MX25_PIN_D9 = _MXC_BUILD_GPIO_PIN(3, 11, 0xa0, 0x298), + MX25_PIN_D8 = _MXC_BUILD_GPIO_PIN(3, 12, 0xa4, 0x29c), + MX25_PIN_D7 = _MXC_BUILD_GPIO_PIN(3, 13, 0xa8, 0x2a0), + MX25_PIN_D6 = _MXC_BUILD_GPIO_PIN(3, 14, 0xac, 0x2a4), + MX25_PIN_D5 = _MXC_BUILD_GPIO_PIN(3, 15, 0xb0, 0x2a8), + MX25_PIN_D4 = _MXC_BUILD_GPIO_PIN(3, 16, 0xb4, 0x2ac), + MX25_PIN_D3 = _MXC_BUILD_GPIO_PIN(3, 17, 0xb8, 0x2b0), + MX25_PIN_D2 = _MXC_BUILD_GPIO_PIN(3, 18, 0xbc, 0x2b4), + MX25_PIN_D1 = _MXC_BUILD_GPIO_PIN(3, 19, 0xc0, 0x2b8), + MX25_PIN_D0 = _MXC_BUILD_GPIO_PIN(3, 20, 0xc4, 0x2bc), + MX25_PIN_LD0 = _MXC_BUILD_GPIO_PIN(1, 15, 0xc8, 0x2c0), + MX25_PIN_LD1 = _MXC_BUILD_GPIO_PIN(1, 16, 0xcc, 0x2c4), + MX25_PIN_LD2 = _MXC_BUILD_GPIO_PIN(1, 17, 0xd0, 0x2c8), + MX25_PIN_LD3 = _MXC_BUILD_GPIO_PIN(1, 18, 0xd4, 0x2cc), + MX25_PIN_LD4 = _MXC_BUILD_GPIO_PIN(1, 19, 0xd8, 0x2d0), + MX25_PIN_LD5 = _MXC_BUILD_GPIO_PIN(0, 19, 0xdc, 0x2d4), + MX25_PIN_LD6 = _MXC_BUILD_GPIO_PIN(0, 20, 0xe0, 0x2d8), + MX25_PIN_LD7 = _MXC_BUILD_GPIO_PIN(0, 21, 0xe4, 0x2dc), + MX25_PIN_LD8 = _MXC_BUILD_NON_GPIO_PIN(0xe8, 0x2e0), + MX25_PIN_LD9 = _MXC_BUILD_NON_GPIO_PIN(0xec, 0x2e4), + MX25_PIN_LD10 = _MXC_BUILD_NON_GPIO_PIN(0xf0, 0x2e8), + MX25_PIN_LD11 = _MXC_BUILD_NON_GPIO_PIN(0xf4, 0x2ec), + MX25_PIN_LD12 = _MXC_BUILD_NON_GPIO_PIN(0xf8, 0x2f0), + MX25_PIN_LD13 = _MXC_BUILD_NON_GPIO_PIN(0xfc, 0x2f4), + MX25_PIN_LD14 = _MXC_BUILD_NON_GPIO_PIN(0x100, 0x2f8), + MX25_PIN_LD15 = _MXC_BUILD_NON_GPIO_PIN(0x104, 0x2fc), + MX25_PIN_HSYNC = _MXC_BUILD_GPIO_PIN(0, 22, 0x108, 0x300), + MX25_PIN_VSYNC = _MXC_BUILD_GPIO_PIN(0, 23, 0x10c, 0x304), + MX25_PIN_LSCLK = _MXC_BUILD_GPIO_PIN(0, 24, 0x110, 0x308), + MX25_PIN_OE_ACD = _MXC_BUILD_GPIO_PIN(0, 25, 0x114, 0x30c), + MX25_PIN_CONTRAST = _MXC_BUILD_NON_GPIO_PIN(0x118, 0x310), + MX25_PIN_PWM = _MXC_BUILD_GPIO_PIN(0, 26, 0x11c, 0x314), + MX25_PIN_CSI_D2 = _MXC_BUILD_GPIO_PIN(0, 27, 0x120, 0x318), + MX25_PIN_CSI_D3 = _MXC_BUILD_GPIO_PIN(0, 28, 0x124, 0x31c), + MX25_PIN_CSI_D4 = _MXC_BUILD_GPIO_PIN(0, 29, 0x128, 0x320), + MX25_PIN_CSI_D5 = _MXC_BUILD_GPIO_PIN(0, 30, 0x12c, 0x324), + MX25_PIN_CSI_D6 = _MXC_BUILD_GPIO_PIN(0, 31, 0x130, 0x328), + MX25_PIN_CSI_D7 = _MXC_BUILD_GPIO_PIN(0, 6, 0x134, 0x32c), + MX25_PIN_CSI_D8 = _MXC_BUILD_GPIO_PIN(0, 7, 0x138, 0x330), + MX25_PIN_CSI_D9 = _MXC_BUILD_GPIO_PIN(3, 21, 0x13c, 0x334), + MX25_PIN_CSI_MCLK = _MXC_BUILD_GPIO_PIN(0, 8, 0x140, 0x338), + MX25_PIN_CSI_VSYNC = _MXC_BUILD_GPIO_PIN(0, 9, 0x144, 0x33c), + MX25_PIN_CSI_HSYNC = _MXC_BUILD_GPIO_PIN(0, 10, 0x148, 0x340), + MX25_PIN_CSI_PIXCLK = _MXC_BUILD_GPIO_PIN(0, 11, 0x14c, 0x344), + MX25_PIN_I2C1_CLK = _MXC_BUILD_GPIO_PIN(0, 12, 0x150, 0x348), + MX25_PIN_I2C1_DAT = _MXC_BUILD_GPIO_PIN(0, 13, 0x154, 0x34c), + MX25_PIN_CSPI1_MOSI = _MXC_BUILD_GPIO_PIN(0, 14, 0x158, 0x350), + MX25_PIN_CSPI1_MISO = _MXC_BUILD_GPIO_PIN(0, 15, 0x15c, 0x354), + MX25_PIN_CSPI1_SS0 = _MXC_BUILD_GPIO_PIN(0, 16, 0x160, 0x358), + MX25_PIN_CSPI1_SS1 = _MXC_BUILD_GPIO_PIN(0, 17, 0x164, 0x35c), + MX25_PIN_CSPI1_SCLK = _MXC_BUILD_GPIO_PIN(0, 18, 0x168, 0x360), + MX25_PIN_CSPI1_RDY = _MXC_BUILD_GPIO_PIN(1, 22, 0x16c, 0x364), + MX25_PIN_UART1_RXD = _MXC_BUILD_GPIO_PIN(3, 22, 0x170, 0x368), + MX25_PIN_UART1_TXD = _MXC_BUILD_GPIO_PIN(3, 23, 0x174, 0x36c), + MX25_PIN_UART1_RTS = _MXC_BUILD_GPIO_PIN(3, 24, 0x178, 0x370), + MX25_PIN_UART1_CTS = _MXC_BUILD_GPIO_PIN(3, 25, 0x17c, 0x374), + MX25_PIN_UART2_RXD = _MXC_BUILD_GPIO_PIN(3, 26, 0x180, 0x378), + MX25_PIN_UART2_TXD = _MXC_BUILD_GPIO_PIN(3, 27, 0x184, 0x37c), + MX25_PIN_UART2_RTS = _MXC_BUILD_GPIO_PIN(3, 28, 0x188, 0x380), + MX25_PIN_UART2_CTS = _MXC_BUILD_GPIO_PIN(3, 29, 0x18c, 0x384), + MX25_PIN_SD1_CMD = _MXC_BUILD_GPIO_PIN(1, 23, 0x190, 0x388), + MX25_PIN_SD1_CLK = _MXC_BUILD_GPIO_PIN(1, 24, 0x194, 0x38c), + MX25_PIN_SD1_DATA0 = _MXC_BUILD_GPIO_PIN(1, 25, 0x198, 0x390), + MX25_PIN_SD1_DATA1 = _MXC_BUILD_GPIO_PIN(1, 26, 0x19c, 0x394), + MX25_PIN_SD1_DATA2 = _MXC_BUILD_GPIO_PIN(1, 27, 0x1a0, 0x398), + MX25_PIN_SD1_DATA3 = _MXC_BUILD_GPIO_PIN(1, 28, 0x1a4, 0x39c), + MX25_PIN_KPP_ROW0 = _MXC_BUILD_GPIO_PIN(1, 29, 0x1a8, 0x3a0), + MX25_PIN_KPP_ROW1 = _MXC_BUILD_GPIO_PIN(1, 30, 0x1ac, 0x3a4), + MX25_PIN_KPP_ROW2 = _MXC_BUILD_GPIO_PIN(1, 31, 0x1b0, 0x3a8), + MX25_PIN_KPP_ROW3 = _MXC_BUILD_GPIO_PIN(2, 0, 0x1b4, 0x3ac), + MX25_PIN_KPP_COL0 = _MXC_BUILD_GPIO_PIN(2, 1, 0x1b8, 0x3b0), + MX25_PIN_KPP_COL1 = _MXC_BUILD_GPIO_PIN(2, 2, 0x1bc, 0x3b4), + MX25_PIN_KPP_COL2 = _MXC_BUILD_GPIO_PIN(2, 3, 0x1c0, 0x3b8), + MX25_PIN_KPP_COL3 = _MXC_BUILD_GPIO_PIN(2, 4, 0x1c4, 0x3bc), + MX25_PIN_FEC_MDC = _MXC_BUILD_GPIO_PIN(2, 5, 0x1c8, 0x3c0), + MX25_PIN_FEC_MDIO = _MXC_BUILD_GPIO_PIN(2, 6, 0x1cc, 0x3c4), + MX25_PIN_FEC_TDATA0 = _MXC_BUILD_GPIO_PIN(2, 7, 0x1d0, 0x3c8), + MX25_PIN_FEC_TDATA1 = _MXC_BUILD_GPIO_PIN(2, 8, 0x1d4, 0x3cc), + MX25_PIN_FEC_TX_EN = _MXC_BUILD_GPIO_PIN(2, 9, 0x1d8, 0x3d0), + MX25_PIN_FEC_RDATA0 = _MXC_BUILD_GPIO_PIN(2, 10, 0x1dc, 0x3d4), + MX25_PIN_FEC_RDATA1 = _MXC_BUILD_GPIO_PIN(2, 11, 0x1e0, 0x3d8), + MX25_PIN_FEC_RX_DV = _MXC_BUILD_GPIO_PIN(2, 12, 0x1e4, 0x3dc), + MX25_PIN_FEC_TX_CLK = _MXC_BUILD_GPIO_PIN(2, 13, 0x1e8, 0x3e0), + MX25_PIN_RTCK = _MXC_BUILD_GPIO_PIN(2, 14, 0x1ec, 0x3e4), + MX25_PIN_DE_B = _MXC_BUILD_GPIO_PIN(1, 20, 0x1f0, 0x3ec), + MX25_PIN_TDO = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x3e8), + MX25_PIN_GPIO_A = _MXC_BUILD_GPIO_PIN(0, 0, 0x1f4, 0x3f0), + MX25_PIN_GPIO_B = _MXC_BUILD_GPIO_PIN(0, 1, 0x1f8, 0x3f4), + MX25_PIN_GPIO_C = _MXC_BUILD_GPIO_PIN(0, 2, 0x1fc, 0x3f8), + MX25_PIN_GPIO_D = _MXC_BUILD_GPIO_PIN(0, 3, 0x200, 0x3fc), + MX25_PIN_GPIO_E = _MXC_BUILD_GPIO_PIN(0, 4, 0x204, 0x400), + MX25_PIN_GPIO_F = _MXC_BUILD_GPIO_PIN(0, 5, 0x208, 0x404), + MX25_PIN_EXT_ARMCLK = _MXC_BUILD_GPIO_PIN(2, 15, 0x20c, 0x0), + MX25_PIN_UPLL_BYPCLK = _MXC_BUILD_GPIO_PIN(2, 16, 0x210, 0x0), + MX25_PIN_VSTBY_REQ = _MXC_BUILD_GPIO_PIN(2, 17, 0x214, 0x408), + MX25_PIN_VSTBY_ACK = _MXC_BUILD_GPIO_PIN(2, 18, 0x218, 0x40c), + MX25_PIN_POWER_FAIL = _MXC_BUILD_GPIO_PIN(2, 19, 0x21c, 0x410), + MX25_PIN_CLKO = _MXC_BUILD_GPIO_PIN(1, 21, 0x220, 0x414), + MX25_PIN_BOOT_MODE0 = _MXC_BUILD_GPIO_PIN(3, 30, 0x224, 0x0), + MX25_PIN_BOOT_MODE1 = _MXC_BUILD_GPIO_PIN(3, 31, 0x228, 0x0), + + MX25_PIN_CTL_GRP_DVS_MISC = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x418), + MX25_PIN_CTL_GRP_DSE_FEC = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x41c), + MX25_PIN_CTL_GRP_DVS_JTAG = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x420), + MX25_PIN_CTL_GRP_DSE_NFC = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x424), + MX25_PIN_CTL_GRP_DSE_CSI = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x428), + MX25_PIN_CTL_GRP_DSE_WEIM = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x42c), + MX25_PIN_CTL_GRP_DSE_DDR = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x430), + MX25_PIN_CTL_GRP_DVS_CRM = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x434), + MX25_PIN_CTL_GRP_DSE_KPP = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x438), + MX25_PIN_CTL_GRP_DSE_SDHC1 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x43c), + MX25_PIN_CTL_GRP_DSE_LCD = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x440), + MX25_PIN_CTL_GRP_DSE_UART = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x444), + MX25_PIN_CTL_GRP_DVS_NFC = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x448), + MX25_PIN_CTL_GRP_DVS_CSI = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x44c), + MX25_PIN_CTL_GRP_DSE_CSPI1 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x450), + MX25_PIN_CTL_GRP_DDRTYPE = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x454), + MX25_PIN_CTL_GRP_DVS_SDHC1 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x458), + MX25_PIN_CTL_GRP_DVS_LCD = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x45c) +}; + +#endif +#endif diff --git a/arch/arm/mach-mx25/pm.c b/arch/arm/mach-mx25/pm.c new file mode 100644 index 000000000000..3f6f96496dae --- /dev/null +++ b/arch/arm/mach-mx25/pm.c @@ -0,0 +1,103 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/suspend.h> +#include <linux/io.h> +#include <mach/hardware.h> +#include "crm_regs.h" + +/*! + * @defgroup MSL_MX25 i.MX25 Machine Specific Layer (MSL) + */ + +/*! + * @file mach-mx25/pm.c + * @brief This file contains suspend operations + * + * @ingroup MSL_MX25 + */ +static unsigned int cgcr0, cgcr1, cgcr2; + +static int mx25_suspend_enter(suspend_state_t state) +{ + unsigned int reg; + + switch (state) { + case PM_SUSPEND_MEM: + mxc_cpu_lp_set(STOP_POWER_OFF); + break; + case PM_SUSPEND_STANDBY: + mxc_cpu_lp_set(WAIT_UNCLOCKED_POWER_OFF); + break; + default: + return -EINVAL; + } + /* Executing CP15 (Wait-for-Interrupt) Instruction */ + cpu_do_idle(); + + reg = (__raw_readl(MXC_CCM_CGCR0) & ~MXC_CCM_CGCR0_STOP_MODE_MASK) | + cgcr0; + __raw_writel(reg, MXC_CCM_CGCR0); + + reg = (__raw_readl(MXC_CCM_CGCR1) & ~MXC_CCM_CGCR1_STOP_MODE_MASK) | + cgcr1; + __raw_writel(reg, MXC_CCM_CGCR1); + + reg = (__raw_readl(MXC_CCM_CGCR2) & ~MXC_CCM_CGCR2_STOP_MODE_MASK) | + cgcr2; + __raw_writel(reg, MXC_CCM_CGCR2); + + return 0; +} + +/* + * Called after processes are frozen, but before we shut down devices. + */ +static int mx25_suspend_prepare(void) +{ + cgcr0 = __raw_readl(MXC_CCM_CGCR0) & MXC_CCM_CGCR0_STOP_MODE_MASK; + cgcr1 = __raw_readl(MXC_CCM_CGCR1) & MXC_CCM_CGCR1_STOP_MODE_MASK; + cgcr2 = __raw_readl(MXC_CCM_CGCR2) & MXC_CCM_CGCR2_STOP_MODE_MASK; + + return 0; +} + +/* + * Called after devices are re-setup, but before processes are thawed. + */ +static void mx25_suspend_finish(void) +{ +} + +static int mx25_pm_valid(suspend_state_t state) +{ + return state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX; +} + +struct platform_suspend_ops mx25_suspend_ops = { + .valid = mx25_pm_valid, + .prepare = mx25_suspend_prepare, + .enter = mx25_suspend_enter, + .finish = mx25_suspend_finish, +}; + +static int __init mx25_pm_init(void) +{ + pr_info("Static Power Management for Freescale i.MX25\n"); + suspend_set_ops(&mx25_suspend_ops); + + return 0; +} + +late_initcall(mx25_pm_init); diff --git a/arch/arm/mach-mx25/sdma_script_code.h b/arch/arm/mach-mx25/sdma_script_code.h new file mode 100644 index 000000000000..86789dd2f353 --- /dev/null +++ b/arch/arm/mach-mx25/sdma_script_code.h @@ -0,0 +1,159 @@ + +/* + * Copyright 2008 Freescale Semiconductor, Inc. All 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 + */ + +/*! + * @file sdma_script_code.h + * @brief This file contains functions of SDMA scripts code initialization + * + * The file was generated automatically. Based on sdma scripts library. + * + * @ingroup SDMA + */ +/************************************************************************ + + SDMA RELEASE LABEL: "SS15_SENNA" + +************************************************************************/ + +#ifndef SDMA_SCRIPT_CODE_H +#define SDMA_SCRIPT_CODE_H + +/*! + * SDMA ROM scripts start addresses and sizes + */ +#define start_ADDR 0 +#define start_SIZE 22 + +#define core_ADDR 80 +#define core_SIZE 233 + +#define common_ADDR 313 +#define common_SIZE 416 + +#define ap_2_ap_ADDR 729 +#define ap_2_ap_SIZE 41 + +#define app_2_mcu_ADDR 770 +#define app_2_mcu_SIZE 64 + +#define mcu_2_app_ADDR 834 +#define mcu_2_app_SIZE 70 + +#define uart_2_mcu_ADDR 904 +#define uart_2_mcu_SIZE 75 + +#define shp_2_mcu_ADDR 979 +#define shp_2_mcu_SIZE 69 + +#define mcu_2_shp_ADDR 1048 +#define mcu_2_shp_SIZE 72 + +#define uartsh_2_mcu_ADDR 1120 +#define uartsh_2_mcu_SIZE 69 + +#define app_2_per_ADDR 1189 +#define app_2_per_SIZE 66 + +#define per_2_app_ADDR 1255 +#define per_2_app_SIZE 74 + +#define per_2_shp_ADDR 1329 +#define per_2_shp_SIZE 78 + +#define shp_2_per_ADDR 1407 +#define shp_2_per_SIZE 72 + +#define mcu_2_ata_ADDR 1479 +#define mcu_2_ata_SIZE 81 + +#define ata_2_mcu_ADDR 1560 +#define ata_2_mcu_SIZE 96 + +#define loop_DMAs_routines_ADDR 1656 +#define loop_DMAs_routines_SIZE 227 + +#define test_ADDR 1883 +#define test_SIZE 63 + +#define signature_ADDR 1022 +#define signature_SIZE 1 + +/*! + * SDMA RAM scripts start addresses and sizes + */ +#define ext_mem__ipu_ram_ADDR 6144 +#define ext_mem__ipu_ram_SIZE 123 + +#define uart_2_per_ADDR 6267 +#define uart_2_per_SIZE 73 + +#define uartsh_2_per_ADDR 6340 +#define uartsh_2_per_SIZE 67 + +/*! + * SDMA RAM image start address and size + */ +#define RAM_CODE_START_ADDR 6144 +#define RAM_CODE_SIZE 263 + +/*! + * Buffer that holds the SDMA RAM image + */ +__attribute__ ((__aligned__(4))) +#ifndef CONFIG_XIP_KERNEL +const +#endif +static const short sdma_code[] = { + 0x0e70, 0x0611, 0x5616, 0xc18a, 0x7d2a, 0x5ade, 0x008e, 0xc19c, + 0x7c26, 0x5be0, 0x5ef0, 0x5ce8, 0x0688, 0x08ff, 0x0011, 0x28ff, + 0x00bc, 0x53f6, 0x05df, 0x7d0b, 0x6dc5, 0x03df, 0x7d03, 0x6bd5, + 0xd84f, 0x982b, 0x6b05, 0xc6d8, 0x7e27, 0x7f29, 0x982b, 0x6d01, + 0x03df, 0x7d05, 0x6bd5, 0xc702, 0x7e18, 0x7f1a, 0x982b, 0x6b05, + 0xc678, 0x7e07, 0x7f06, 0x52de, 0x53e6, 0xc1a8, 0x7dd7, 0x0200, + 0x9803, 0x0007, 0x6004, 0x680c, 0x53f6, 0x028e, 0x00a3, 0xc2ad, + 0x048b, 0x0498, 0x0454, 0x068a, 0x982b, 0x0207, 0x680c, 0x6ddf, + 0x0107, 0x68ff, 0x60d0, 0x9834, 0x0207, 0x68ff, 0x6d28, 0x0107, + 0x6004, 0x680c, 0x9834, 0x0007, 0x68ff, 0x60d0, 0x9834, 0x0288, + 0x03a5, 0x3b03, 0x3d03, 0x4d00, 0x7d0a, 0x0804, 0x00a5, 0x00da, + 0x7d1a, 0x02a0, 0x7b01, 0x65d8, 0x7eee, 0x65ff, 0x7eec, 0x0804, + 0x02d0, 0x7d11, 0x4b00, 0x7c0f, 0x008a, 0x3003, 0x6dcf, 0x6bdf, + 0x0015, 0x0015, 0x7b02, 0x65d8, 0x0000, 0x7edd, 0x63ff, 0x7edb, + 0x3a03, 0x6dcd, 0x6bdd, 0x008a, 0x7b02, 0x65d8, 0x0000, 0x7ed3, + 0x65ff, 0x7ed1, 0x0006, 0xc23a, 0x57db, 0x52f3, 0x6ad5, 0x56fb, + 0x028e, 0x1a94, 0x6ac3, 0x62c8, 0x0269, 0x7d1e, 0x1e94, 0x6ee3, + 0x62d0, 0x5aeb, 0x62c8, 0x0248, 0x6ed3, 0x6ac8, 0x2694, 0x52eb, + 0x6ad5, 0x6ee3, 0x62c8, 0x026e, 0x7d27, 0x6ac8, 0x7f23, 0x2501, + 0x4d00, 0x7d26, 0x028e, 0x1a98, 0x6ac3, 0x62c8, 0x6ec3, 0x0260, + 0x7df1, 0x62d0, 0xc2d1, 0x98c0, 0x6ee3, 0x008f, 0x2001, 0x00d5, + 0x7d01, 0x008d, 0x05a0, 0x62c8, 0x026e, 0x7d0e, 0x6ac8, 0x7f0a, + 0x2001, 0x7cf9, 0x6add, 0x7f06, 0x0000, 0x4d00, 0x7d09, 0xc251, + 0x57db, 0x987f, 0x0007, 0x6aff, 0x62d0, 0xc2d1, 0x0458, 0x0454, + 0x6add, 0x7ff8, 0xc261, 0x987c, 0xc230, 0xc23a, 0x57db, 0x52f3, + 0x6ad5, 0x56fb, 0x028e, 0x1a94, 0x5202, 0x0269, 0x7d17, 0x1e94, + 0x5206, 0x0248, 0x5a06, 0x2694, 0x5206, 0x026e, 0x7d26, 0x6ac8, + 0x7f22, 0x2501, 0x4d00, 0x7d27, 0x028e, 0x1a98, 0x5202, 0x0260, + 0x7df3, 0x6add, 0x7f18, 0x62d0, 0xc2d1, 0x9903, 0x008f, 0x2001, + 0x00d5, 0x7d01, 0x008d, 0x05a0, 0x5206, 0x026e, 0x7d0e, 0x6ac8, + 0x7f0a, 0x2001, 0x7cf9, 0x6add, 0x7f06, 0x0000, 0x4d00, 0x7d0b, + 0xc251, 0x57db, 0x98c9, 0x0007, 0x6aff, 0x6add, 0x7ffc, 0x62d0, + 0xc2d1, 0x0458, 0x0454, 0x6add, 0x7ff6, 0xc261, 0x98c6 +}; +#endif diff --git a/arch/arm/mach-mx25/serial.c b/arch/arm/mach-mx25/serial.c new file mode 100644 index 000000000000..d71dd1082f2f --- /dev/null +++ b/arch/arm/mach-mx25/serial.c @@ -0,0 +1,263 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @file mach-mx25/serial.c + * + * @brief This file contains the UART initiliazation. + * + * @ingroup MSL_MX25 + */ +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/serial.h> +#include <mach/hardware.h> +#include <mach/mxc_uart.h> +#include <mach/spba.h> +#include "serial.h" +#include "board-mx25_3stack.h" + +#if defined(CONFIG_SERIAL_MXC) || defined(CONFIG_SERIAL_MXC_MODULE) + +/*! + * This is an array where each element holds information about a UART port, + * like base address of the UART, interrupt numbers etc. This structure is + * passed to the serial_core.c file. Based on which UART is used, the core file + * passes back the appropriate port structure as an argument to the control + * functions. + */ +static uart_mxc_port mxc_ports[] = { + [0] = { + .port = { + .membase = (void *)IO_ADDRESS(UART1_BASE_ADDR), + .mapbase = UART1_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART1_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 0, + }, + .ints_muxed = UART1_MUX_INTS, + .irqs = {UART1_INT2, UART1_INT3}, + .mode = UART1_MODE, + .ir_mode = UART1_IR, + .enabled = UART1_ENABLED, + .hardware_flow = UART1_HW_FLOW, + .cts_threshold = UART1_UCR4_CTSTL, + .dma_enabled = UART1_DMA_ENABLE, + .dma_rxbuf_size = UART1_DMA_RXBUFSIZE, + .rx_threshold = UART1_UFCR_RXTL, + .tx_threshold = UART1_UFCR_TXTL, + .shared = UART1_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART1_TX, + .dma_rx_id = MXC_DMA_UART1_RX, + .rxd_mux = MXC_UART_RXDMUX, + }, + [1] = { + .port = { + .membase = (void *)IO_ADDRESS(UART2_BASE_ADDR), + .mapbase = UART2_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART2_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 1, + }, + .ints_muxed = UART2_MUX_INTS, + .irqs = {UART2_INT2, UART2_INT3}, + .mode = UART2_MODE, + .ir_mode = UART2_IR, + .enabled = UART2_ENABLED, + .hardware_flow = UART2_HW_FLOW, + .cts_threshold = UART2_UCR4_CTSTL, + .dma_enabled = UART2_DMA_ENABLE, + .dma_rxbuf_size = UART2_DMA_RXBUFSIZE, + .rx_threshold = UART2_UFCR_RXTL, + .tx_threshold = UART2_UFCR_TXTL, + .shared = UART2_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART2_TX, + .dma_rx_id = MXC_DMA_UART2_RX, + .rxd_mux = MXC_UART_IR_RXDMUX, + }, +#if UART3_ENABLED == 1 + [2] = { + .port = { + .membase = (void *)IO_ADDRESS(UART3_BASE_ADDR), + .mapbase = UART3_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART3_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 2, + }, + .ints_muxed = UART3_MUX_INTS, + .irqs = {UART3_INT2, UART3_INT3}, + .mode = UART3_MODE, + .ir_mode = UART3_IR, + .enabled = UART3_ENABLED, + .hardware_flow = UART3_HW_FLOW, + .cts_threshold = UART3_UCR4_CTSTL, + .dma_enabled = UART3_DMA_ENABLE, + .dma_rxbuf_size = UART3_DMA_RXBUFSIZE, + .rx_threshold = UART3_UFCR_RXTL, + .tx_threshold = UART3_UFCR_TXTL, + .shared = UART3_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART3_TX, + .dma_rx_id = MXC_DMA_UART3_RX, + .rxd_mux = MXC_UART_RXDMUX, + }, +#endif +#if UART4_ENABLED == 1 + [3] = { + .port = { + .membase = (void *)IO_ADDRESS(UART4_BASE_ADDR), + .mapbase = UART4_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART4_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 3, + }, + .ints_muxed = UART4_MUX_INTS, + .irqs = {UART4_INT2, UART4_INT3}, + .mode = UART4_MODE, + .ir_mode = UART4_IR, + .enabled = UART4_ENABLED, + .hardware_flow = UART4_HW_FLOW, + .cts_threshold = UART4_UCR4_CTSTL, + .dma_enabled = UART4_DMA_ENABLE, + .dma_rxbuf_size = UART4_DMA_RXBUFSIZE, + .rx_threshold = UART4_UFCR_RXTL, + .tx_threshold = UART4_UFCR_TXTL, + .shared = UART4_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART4_TX, + .dma_rx_id = MXC_DMA_UART4_RX, + .rxd_mux = MXC_UART_RXDMUX, + }, +#endif +#if UART5_ENABLED == 1 + [4] = { + .port = { + .membase = (void *)IO_ADDRESS(UART5_BASE_ADDR), + .mapbase = UART5_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART5_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 4, + }, + .ints_muxed = UART5_MUX_INTS, + .irqs = {UART5_INT2, UART5_INT3}, + .mode = UART5_MODE, + .ir_mode = UART5_IR, + .enabled = UART5_ENABLED, + .hardware_flow = UART5_HW_FLOW, + .cts_threshold = UART5_UCR4_CTSTL, + .dma_enabled = UART5_DMA_ENABLE, + .dma_rxbuf_size = UART5_DMA_RXBUFSIZE, + .rx_threshold = UART5_UFCR_RXTL, + .tx_threshold = UART5_UFCR_TXTL, + .shared = UART5_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART5_TX, + .dma_rx_id = MXC_DMA_UART5_RX, + .rxd_mux = MXC_UART_RXDMUX, + }, +#endif +}; + +static struct platform_device mxc_uart_device1 = { + .name = "mxcintuart", + .id = 0, + .dev = { + .platform_data = &mxc_ports[0], + }, +}; + +static struct platform_device mxc_uart_device2 = { + .name = "mxcintuart", + .id = 1, + .dev = { + .platform_data = &mxc_ports[1], + }, +}; + +#if UART3_ENABLED == 1 +static struct platform_device mxc_uart_device3 = { + .name = "mxcintuart", + .id = 2, + .dev = { + .platform_data = &mxc_ports[2], + }, +}; +#endif +#if UART4_ENABLED == 1 +static struct platform_device mxc_uart_device4 = { + .name = "mxcintuart", + .id = 3, + .dev = { + .platform_data = &mxc_ports[3], + }, +}; +#endif +#if UART5_ENABLED == 1 +static struct platform_device mxc_uart_device5 = { + .name = "mxcintuart", + .id = 4, + .dev = { + .platform_data = &mxc_ports[4], + }, +}; +#endif + +static int __init mxc_init_uart(void) +{ + /* Register all the MXC UART platform device structures */ + platform_device_register(&mxc_uart_device1); + platform_device_register(&mxc_uart_device2); + + /* Grab ownership of shared UARTs 3 and 4, only when enabled */ +#if UART3_ENABLED == 1 +#if UART3_DMA_ENABLE == 1 + spba_take_ownership(UART3_SHARED_PERI, (SPBA_MASTER_A | SPBA_MASTER_C)); +#else + spba_take_ownership(UART3_SHARED_PERI, SPBA_MASTER_A); +#endif /* UART3_DMA_ENABLE */ + platform_device_register(&mxc_uart_device3); +#endif /* UART3_ENABLED */ +#if UART4_ENABLED == 1 +#if UART4_DMA_ENABLE == 1 + spba_take_ownership(UART4_SHARED_PERI, (SPBA_MASTER_A | SPBA_MASTER_C)); +#else + spba_take_ownership(UART4_SHARED_PERI, SPBA_MASTER_A); +#endif /* UARTr_DMA_ENABLE */ + platform_device_register(&mxc_uart_device4); +#endif /* UART4_ENABLED */ +#if UART5_ENABLED == 1 +#if UART5_DMA_ENABLE == 1 + spba_take_ownership(UART5_SHARED_PERI, (SPBA_MASTER_A | SPBA_MASTER_C)); +#else + spba_take_ownership(UART5_SHARED_PERI, SPBA_MASTER_A); +#endif /* UART5_DMA_ENABLE */ + platform_device_register(&mxc_uart_device5); +#endif /* UART5_ENABLED */ + + return 0; +} + +#else +static int __init mxc_init_uart(void) +{ + return 0; +} +#endif + +arch_initcall(mxc_init_uart); diff --git a/arch/arm/mach-mx25/serial.h b/arch/arm/mach-mx25/serial.h new file mode 100644 index 000000000000..4f53b09db1be --- /dev/null +++ b/arch/arm/mach-mx25/serial.h @@ -0,0 +1,158 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ARCH_ARM_MACH_MX25_SERIAL_H__ +#define __ARCH_ARM_MACH_MX25_SERIAL_H__ + +/*! + * @file mach-mx25/serial.h + * + * @ingroup MSL_MX25 + */ +#include <mach/mxc_uart.h> + +/* UART 1 configuration */ +/*! + * This option allows to choose either an interrupt-driven software controlled + * hardware flow control (set this option to 0) or hardware-driven hardware + * flow control (set this option to 1). + */ +#define UART1_HW_FLOW 1 +/*! + * This specifies the threshold at which the CTS pin is deasserted by the + * RXFIFO. Set this value in Decimal to anything from 0 to 32 for + * hardware-driven hardware flow control. Read the HW spec while specifying + * this value. When using interrupt-driven software controlled hardware + * flow control set this option to -1. + */ +#define UART1_UCR4_CTSTL 16 +/*! + * This is option to enable (set this option to 1) or disable DMA data transfer + */ +#define UART1_DMA_ENABLE 0 +/*! + * Specify the size of the DMA receive buffer. The minimum buffer size is 512 + * bytes. The buffer size should be a multiple of 256. + */ +#define UART1_DMA_RXBUFSIZE 1024 +/*! + * Specify the MXC UART's Receive Trigger Level. This controls the threshold at + * which a maskable interrupt is generated by the RxFIFO. Set this value in + * Decimal to anything from 0 to 32. Read the HW spec while specifying this + * value. + */ +#define UART1_UFCR_RXTL 16 +/*! + * Specify the MXC UART's Transmit Trigger Level. This controls the threshold at + * which a maskable interrupt is generated by the TxFIFO. Set this value in + * Decimal to anything from 0 to 32. Read the HW spec while specifying this + * value. + */ +#define UART1_UFCR_TXTL 16 +/* UART 2 configuration */ +#define UART2_HW_FLOW 0 +#define UART2_UCR4_CTSTL (-1) +#define UART2_DMA_ENABLE 0 +#define UART2_DMA_RXBUFSIZE 512 +#define UART2_UFCR_RXTL 16 +#define UART2_UFCR_TXTL 16 +/* UART 3 configuration */ +#define UART3_HW_FLOW 1 +#define UART3_UCR4_CTSTL 16 +#define UART3_DMA_ENABLE 1 +#define UART3_DMA_RXBUFSIZE 1024 +#define UART3_UFCR_RXTL 16 +#define UART3_UFCR_TXTL 16 +/* UART 4 configuration */ +#define UART4_HW_FLOW 1 +#define UART4_UCR4_CTSTL 16 +#define UART4_DMA_ENABLE 1 +#define UART4_DMA_RXBUFSIZE 1024 +#define UART4_UFCR_RXTL 16 +#define UART4_UFCR_TXTL 16 +/* UART 5 configuration */ +#define UART5_HW_FLOW 1 +#define UART5_UCR4_CTSTL 16 +#define UART5_DMA_ENABLE 1 +#define UART5_DMA_RXBUFSIZE 1024 +#define UART5_UFCR_RXTL 16 +#define UART5_UFCR_TXTL 16 + +/* + * UART Chip level Configuration that a user may not have to edit. These + * configuration vary depending on how the UART module is integrated with + * the ARM core + */ +/* + * Is the MUXED interrupt output sent to the ARM core + */ +#define INTS_NOTMUXED 0 +#define INTS_MUXED 1 +/* UART 1 configuration */ +/*! + * This define specifies whether the muxed ANDed interrupt line or the + * individual interrupts from the UART port is integrated with the ARM core. + * There exists a define like this for each UART port. Valid values that can + * be used are \b INTS_NOTMUXED or \b INTS_MUXED. + */ +#define UART1_MUX_INTS INTS_MUXED +/*! + * This define specifies the transmitter interrupt number or the interrupt + * number of the ANDed interrupt in case the interrupts are muxed. There exists + * a define like this for each UART port. + */ +#define UART1_INT1 MXC_INT_UART1 +/*! + * This define specifies the receiver interrupt number. If the interrupts of + * the UART are muxed, then we specify here a dummy value -1. There exists a + * define like this for each UART port. + */ +#define UART1_INT2 (-1) +/*! + * This specifies the master interrupt number. If the interrupts of the UART + * are muxed, then we specify here a dummy value of -1. There exists a define + * like this for each UART port. + */ +#define UART1_INT3 (-1) +/*! + * This specifies if the UART is a shared peripheral. It holds the shared + * peripheral number if it is shared or -1 if it is not shared. There exists + * a define like this for each UART port. + */ +#define UART1_SHARED_PERI (-1) +/* UART 2 configuration */ +#define UART2_MUX_INTS INTS_MUXED +#define UART2_INT1 MXC_INT_UART2 +#define UART2_INT2 (-1) +#define UART2_INT3 (-1) +#define UART2_SHARED_PERI (-1) +/* UART 3 configuration */ +#define UART3_MUX_INTS INTS_MUXED +#define UART3_INT1 MXC_INT_UART3 +#define UART3_INT2 (-1) +#define UART3_INT3 (-1) +#define UART3_SHARED_PERI SPBA_UART3 +/* UART 4 configuration */ +#define UART4_MUX_INTS INTS_MUXED +#define UART4_INT1 MXC_INT_UART4 +#define UART4_INT2 (-1) +#define UART4_INT3 (-1) +#define UART4_SHARED_PERI SPBA_UART4 +/* UART 5 configuration */ +#define UART5_MUX_INTS INTS_MUXED +#define UART5_INT1 MXC_INT_UART5 +#define UART5_INT2 (-1) +#define UART5_INT3 (-1) +#define UART5_SHARED_PERI SPBA_UART5 + +#endif /* __ARCH_ARM_MACH_MX25_SERIAL_H__ */ diff --git a/arch/arm/mach-mx25/system.c b/arch/arm/mach-mx25/system.c new file mode 100644 index 000000000000..d2b6fb1e8721 --- /dev/null +++ b/arch/arm/mach-mx25/system.c @@ -0,0 +1,151 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <mach/hardware.h> +#include <asm/proc-fns.h> +#include <asm/system.h> +#include <mach/clock.h> +#include "crm_regs.h" + +/*! + * @defgroup MSL_MX25 i.MX25 Machine Specific Layer (MSL) + */ + +/*! + * @file mach-mx25/system.c + * @brief This file contains idle and reset functions. + * + * @ingroup MSL_MX25 + */ + +/*! + * MX25 low-power mode + */ +enum mx25_low_pwr_mode { + MX25_RUN_MODE, + MX25_WAIT_MODE, + MX25_DOZE_MODE, + MX25_STOP_MODE +}; + +extern int mxc_jtag_enabled; + +/*! + * This function is used to set cpu low power mode before WFI instruction + * + * @param mode indicates different kinds of power modes + */ +void mxc_cpu_lp_set(enum mxc_cpu_pwr_mode mode) +{ + unsigned int lpm; + unsigned long reg; + unsigned int pmcr2, lpimr; + unsigned int cgcr0, cgcr1, cgcr2; + struct irq_desc *desc; + int i; + + /*read CCTL value */ + reg = __raw_readl(MXC_CCM_CCTL); + + switch (mode) { + case WAIT_UNCLOCKED_POWER_OFF: + lpm = MX25_DOZE_MODE; + break; + + case STOP_POWER_ON: + case STOP_POWER_OFF: + lpm = MX25_STOP_MODE; + /* The clock of LCDC/SLCDC, SDMA, RTIC, RNGC, MAX, CAN + and EMI needs to be gated on when entering Stop mode. + */ + cgcr0 = __raw_readl(MXC_CCM_CGCR0); + cgcr1 = __raw_readl(MXC_CCM_CGCR1); + cgcr2 = __raw_readl(MXC_CCM_CGCR2); + __raw_writel(cgcr0 | MXC_CCM_CGCR0_STOP_MODE_MASK, + MXC_CCM_CGCR0); + __raw_writel(cgcr1 | MXC_CCM_CGCR1_STOP_MODE_MASK, + MXC_CCM_CGCR1); + __raw_writel(cgcr2 | MXC_CCM_CGCR2_STOP_MODE_MASK, + MXC_CCM_CGCR2); + /* The interrupts which are not wake-up sources need + be mask when entering Stop mode. + */ + lpimr = MXC_CCM_LPIMR0_MASK; + for (i = 0; i < 32; i++) { + desc = irq_desc + i; + if ((desc->status & IRQ_WAKEUP) != 0) + lpimr &= ~(1 << i); + } + __raw_writel(lpimr, MXC_CCM_LPIMR0); + lpimr = MXC_CCM_LPIMR1_MASK; + for (i = 32; i < 64; i++) { + desc = irq_desc + i; + if ((desc->status & IRQ_WAKEUP) != 0) + lpimr &= ~(1 << (i - 32)); + } + __raw_writel(lpimr, MXC_CCM_LPIMR1); + + if (mode == STOP_POWER_OFF) { + pmcr2 = __raw_readl(MXC_CCM_PMCR2); + pmcr2 |= (MXC_CCM_PMCR2_OSC24M_DOWN | + MXC_CCM_PMCR2_VSTBY); + __raw_writel(pmcr2, MXC_CCM_PMCR2); + } + break; + + case WAIT_CLOCKED: + case WAIT_UNCLOCKED: + default: + /* Wait is the default mode used when idle. */ + lpm = MX25_WAIT_MODE; + break; + } + + /* program LP CTL bit */ + reg = ((reg & (~MXC_CCM_CCTL_LP_CTL_MASK)) | + lpm << MXC_CCM_CCTL_LP_CTL_OFFSET); + + __raw_writel(reg, MXC_CCM_CCTL); +} + +/*! + * This function puts the CPU into idle mode. It is called by default_idle() + * in process.c file. + */ +void arch_idle(void) +{ + /* + * This should do all the clock switching + * and wait for interrupt tricks. + */ + if (!mxc_jtag_enabled) { + /* set as Wait mode */ + mxc_cpu_lp_set(WAIT_UNCLOCKED); + cpu_do_idle(); + } +} + +/* + * This function resets the system. It is called by machine_restart(). + * + * @param mode indicates different kinds of resets + */ +void arch_reset(char mode) +{ + /* Assert SRS signal */ + mxc_wd_reset(); +} diff --git a/arch/arm/mach-mx25/usb.h b/arch/arm/mach-mx25/usb.h new file mode 100644 index 000000000000..0ab29b5147d6 --- /dev/null +++ b/arch/arm/mach-mx25/usb.h @@ -0,0 +1,103 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +extern int usbotg_init(struct platform_device *pdev); +extern void usbotg_uninit(struct fsl_usb2_platform_data *pdata); +extern int gpio_usbotg_utmi_active(void); +extern void gpio_usbotg_utmi_inactive(void); +extern struct platform_device *host_pdev_register(struct resource *res, + int n_res, + struct fsl_usb2_platform_data + *config); + +extern int fsl_usb_host_init(struct platform_device *pdev); +extern void fsl_usb_host_uninit(struct fsl_usb2_platform_data *pdata); +extern int gpio_usbh2_active(void); +extern void gpio_usbh2_inactive(void); + +/* + * Determine which platform_data struct to use for the DR controller, + * based on which transceiver is configured. + * PDATA is a pointer to it. + */ +static struct fsl_usb2_platform_data __maybe_unused dr_utmi_config; +#define PDATA (&dr_utmi_config) + +/* + * Used to set pdata->operating_mode before registering the platform_device. + * If OTG is configured, the controller operates in OTG mode, + * otherwise it's either host or device. + */ +#ifdef CONFIG_USB_OTG +#define DR_UDC_MODE FSL_USB2_DR_OTG +#define DR_HOST_MODE FSL_USB2_DR_OTG +#else +#define DR_UDC_MODE FSL_USB2_DR_DEVICE +#define DR_HOST_MODE FSL_USB2_DR_HOST +#endif + +#ifdef CONFIG_USB_EHCI_ARC_OTG +static inline void dr_register_host(struct resource *r, int rs) +{ + PDATA->operating_mode = DR_HOST_MODE; + host_pdev_register(r, rs, PDATA); +} +#else +static inline void dr_register_host(struct resource *r, int rs) +{ +} +#endif + +#ifdef CONFIG_USB_GADGET_ARC +static struct platform_device dr_udc_device; + +static inline void dr_register_udc(void) +{ + PDATA->operating_mode = DR_UDC_MODE; + dr_udc_device.dev.platform_data = PDATA; + + if (platform_device_register(&dr_udc_device)) + printk(KERN_ERR "usb: can't register DR gadget\n"); + else + printk(KERN_INFO "usb: DR gadget (%s) registered\n", + PDATA->transceiver); +} +#else +static inline void dr_register_udc(void) +{ +} +#endif + +#ifdef CONFIG_USB_OTG +static struct platform_device dr_otg_device; + +/* + * set the proper operating_mode and + * platform_data pointer, then register the + * device. + */ +static inline void dr_register_otg(void) +{ + PDATA->operating_mode = FSL_USB2_DR_OTG; + dr_otg_device.dev.platform_data = PDATA; + + if (platform_device_register(&dr_otg_device)) + printk(KERN_ERR "usb: can't register otg device\n"); + else + printk(KERN_INFO "usb: DR OTG registered\n"); +} +#else +static inline void dr_register_otg(void) +{ +} +#endif diff --git a/arch/arm/mach-mx25/usb_dr.c b/arch/arm/mach-mx25/usb_dr.c new file mode 100644 index 000000000000..e9c1e7d78c49 --- /dev/null +++ b/arch/arm/mach-mx25/usb_dr.c @@ -0,0 +1,102 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <mach/hardware.h> +#include <mach/arc_otg.h> +#include "usb.h" + +/* + * platform data structs + * - Which one to use is determined by CONFIG options in usb.h + * - operating_mode plugged at run time + */ +static struct fsl_usb2_platform_data __maybe_unused dr_utmi_config = { + .name = "DR", + .platform_init = usbotg_init, + .platform_uninit = usbotg_uninit, + .phy_mode = FSL_USB2_PHY_UTMI_WIDE, + .power_budget = 500, /* via RT9706 */ + .gpio_usb_active = gpio_usbotg_utmi_active, + .gpio_usb_inactive = gpio_usbotg_utmi_inactive, + .transceiver = "utmi", +}; + +/* + * resources + */ +static struct resource otg_resources[] = { + [0] = { + .start = (u32)(USB_OTGREGS_BASE), + .end = (u32)(USB_OTGREGS_BASE + 0x1ff), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_USB_OTG, + .flags = IORESOURCE_IRQ, + }, +}; + +static u64 dr_udc_dmamask = ~(u32) 0; +static void dr_udc_release(struct device *dev) +{ +} + +static u64 dr_otg_dmamask = ~(u32) 0; +static void dr_otg_release(struct device *dev) +{ +} + +/* + * platform device structs + * dev.platform_data field plugged at run time + */ +static struct platform_device __maybe_unused dr_udc_device = { + .name = "fsl-usb2-udc", + .id = -1, + .dev = { + .release = dr_udc_release, + .dma_mask = &dr_udc_dmamask, + .coherent_dma_mask = 0xffffffff, + }, + .resource = otg_resources, + .num_resources = ARRAY_SIZE(otg_resources), +}; + +static struct platform_device __maybe_unused dr_otg_device = { + .name = "fsl-usb2-otg", + .id = -1, + .dev = { + .release = dr_otg_release, + .dma_mask = &dr_otg_dmamask, + .coherent_dma_mask = 0xffffffff, + }, + .resource = otg_resources, + .num_resources = ARRAY_SIZE(otg_resources), +}; + +static int __init usb_dr_init(void) +{ + pr_debug("%s: \n", __func__); + + dr_register_otg(); + dr_register_host(otg_resources, ARRAY_SIZE(otg_resources)); + dr_register_udc(); + + return 0; +} + +module_init(usb_dr_init); diff --git a/arch/arm/mach-mx25/usb_h2.c b/arch/arm/mach-mx25/usb_h2.c new file mode 100644 index 000000000000..3ed336923b92 --- /dev/null +++ b/arch/arm/mach-mx25/usb_h2.c @@ -0,0 +1,88 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/usb/fsl_xcvr.h> +#include <linux/regulator/consumer.h> +#include <mach/hardware.h> +#include <mach/arc_otg.h> +#include "usb.h" + +static struct fsl_usb2_platform_data usbh2_config = { + .name = "Host 2", + .platform_init = fsl_usb_host_init, + .platform_uninit = fsl_usb_host_uninit, + .operating_mode = FSL_USB2_MPH_HOST, + .phy_mode = FSL_USB2_PHY_SERIAL, + .power_budget = 500, /* via RT9702 */ + .gpio_usb_active = gpio_usbh2_active, + .gpio_usb_inactive = gpio_usbh2_inactive, + .transceiver = "serial", /* on-chip */ +}; + +static struct resource usbh2_resources[] = { + [0] = { + .start = (u32) (USB_H2REGS_BASE), + .end = (u32) (USB_H2REGS_BASE + 0x1ff), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_USB_HTG, + .flags = IORESOURCE_IRQ, + }, +}; + +void usbh2_get_xcvr_power(struct device *dev) +{ + struct regulator *usbh2_regux; + + usbh2_regux = regulator_get(dev, "GPO1"); + regulator_enable(usbh2_regux); + ((struct fsl_usb2_platform_data *)dev->platform_data)-> + xcvr_pwr->regu1 = usbh2_regux; + + usbh2_regux = regulator_get(dev, "GPO3"); + regulator_enable(usbh2_regux); + ((struct fsl_usb2_platform_data *)dev->platform_data)-> + xcvr_pwr->regu2 = usbh2_regux; +} +EXPORT_SYMBOL(usbh2_get_xcvr_power); + +void usbh2_put_xcvr_power(struct device *dev) +{ + struct regulator *usbh2_regux; + + usbh2_regux = ((struct fsl_usb2_platform_data *)dev-> + platform_data)->xcvr_pwr->regu2; + regulator_disable(usbh2_regux); + regulator_put(usbh2_regux); + + usbh2_regux = ((struct fsl_usb2_platform_data *)dev-> + platform_data)->xcvr_pwr->regu1; + regulator_disable(usbh2_regux); + regulator_put(usbh2_regux); +} +EXPORT_SYMBOL(usbh2_put_xcvr_power); + +static int __init usbh2_init(void) +{ + pr_debug("%s: \n", __func__); + + host_pdev_register(usbh2_resources, ARRAY_SIZE(usbh2_resources), + &usbh2_config); + return 0; +} +module_init(usbh2_init); diff --git a/arch/arm/mach-mx3/Kconfig b/arch/arm/mach-mx3/Kconfig index 851f2458bf65..5aaae399cc73 100644 --- a/arch/arm/mach-mx3/Kconfig +++ b/arch/arm/mach-mx3/Kconfig @@ -4,9 +4,17 @@ config ARCH_MX31 select ARCH_HAS_RNGA bool -config ARCH_MX35 +config MX3_OPTIONS bool - select ARCH_MXC_IOMUX_V3 + default y + select CPU_V6 + select ARM_ERRATA_364296 + select ARM_ERRATA_411920 + select CACHE_L2X0 + select OUTER_CACHE + select USB_ARCH_HAS_EHCI + select ARCH_HAS_EVTMON + select ARCH_HAS_RNGA comment "MX3 platforms:" @@ -81,7 +89,7 @@ config MACH_QONG config MACH_PCM043 bool "Support Phytec pcm043 (i.MX35) platforms" - select ARCH_MX35 +# select ARCH_MX35 help Include support for Phytec pcm043 platform. This includes specific configurations for the board and its peripherals. @@ -92,12 +100,72 @@ config MACH_ARMADILLO5X0 help Include support for Atmark Armadillo-500 platform. This includes specific configurations for the board and its peripherals. + Include support for Phytec pcm043 platform. This includes + specific configurations for the board and its peripherals. + +config MXC_SDMA_API + bool "Use SDMA API" + default y + help + This selects the Freescale MXC SDMA API. + If unsure, say N. + +menu "SDMA options" + depends on MXC_SDMA_API + +config SDMA_IRAM + bool "Use Internal RAM for SDMA transfer" + default n + help + Support Internal RAM as SDMA buffer or control structures + +config SDMA_IRAM_SIZE + hex "Reserved bytes of IRAM for SDMA (0x800-0x2000)" + range 0x800 0x2000 + depends on SDMA_IRAM + default "0x1000" + help + Set the size of IRAM for SDMA. It must be multiple of 512bytes. +endmenu + +config ARCH_MXC_HAS_NFC_V1 + bool "MXC NFC Hardware Version 1" + depends on !(MACH_MX31ADS && XIP_KERNEL) + default y + help + This selects the Freescale MXC Nand Flash Controller Hardware Version 1 + If unsure, say N. -config MACH_MX35_3DS - bool "Support MX35PDK platform" - select ARCH_MX35 +config ARCH_MXC_HAS_NFC_V2 + bool "MXC NFC Hardware Version 2" + depends on !(MACH_MX31ADS && XIP_KERNEL) + default y + help + This selects the Freescale MXC Nand Flash Controller Hardware Version 2 + If unsure, say N. + +menu "Device options" + +config I2C_MXC_SELECT1 + bool "Enable I2C1 module" + default y + depends on I2C_MXC + help + Enable MX31 I2C1 module. + +config I2C_MXC_SELECT2 + bool "Enable I2C2 module" default n + depends on I2C_MXC help - Include support for MX35PDK platform. This includes specific - configurations for the board and its peripherals. + Enable MX31 I2C2 module. + +config I2C_MXC_SELECT3 + bool "Enable I2C3 module" + default n + depends on I2C_MXC + help + Enable MX31 I2C3 module. + +endmenu endif diff --git a/arch/arm/mach-mx3/Makefile b/arch/arm/mach-mx3/Makefile index 6b9775471be6..ddbbf2672309 100644 --- a/arch/arm/mach-mx3/Makefile +++ b/arch/arm/mach-mx3/Makefile @@ -4,18 +4,25 @@ # Object file lists. -obj-y := mm.o devices.o -obj-$(CONFIG_ARCH_MX31) += clock.o iomux.o -obj-$(CONFIG_ARCH_MX35) += clock-imx35.o -obj-$(CONFIG_MACH_MX31ADS) += mx31ads.o +obj-y := system.o iomux.o cpu.o mm.o clock.o dptc.o devices.o serial.o dma.o mxc_pm.o dvfs_v2.o +obj-$(CONFIG_MACH_MX31ADS) += mx31ads.o mx31ads_gpio.o obj-$(CONFIG_MACH_MX31LILLY) += mx31lilly.o mx31lilly-db.o obj-$(CONFIG_MACH_MX31LITE) += mx31lite.o obj-$(CONFIG_MACH_PCM037) += pcm037.o obj-$(CONFIG_MACH_PCM037_EET) += pcm037_eet.o -obj-$(CONFIG_MACH_MX31_3DS) += mx31pdk.o +obj-$(CONFIG_MACH_MX31_3DS) += mx3_3stack.o mx3_3stack_gpio.o mx3_3stack_pmic_mc13783.o obj-$(CONFIG_MACH_MX31MOBOARD) += mx31moboard.o mx31moboard-devboard.o \ mx31moboard-marxbot.o obj-$(CONFIG_MACH_QONG) += qong.o obj-$(CONFIG_MACH_PCM043) += pcm043.o obj-$(CONFIG_MACH_ARMADILLO5X0) += armadillo5x0.o -obj-$(CONFIG_MACH_MX35_3DS) += mx35pdk.o + +# power management +obj-$(CONFIG_PM) += pm.o + +obj-$(CONFIG_USB_EHCI_ARC_H1) += usb_h1.o +obj-$(CONFIG_USB_EHCI_ARC_H2) += usb_h2.o + +ifneq ($(strip $(CONFIG_USB_GADGET_ARC) $(CONFIG_USB_EHCI_ARC_OTG)),) + obj-y += usb_dr.o +endif diff --git a/arch/arm/mach-mx3/board-mx31ads.h b/arch/arm/mach-mx3/board-mx31ads.h new file mode 100644 index 000000000000..658e3e736c0f --- /dev/null +++ b/arch/arm/mach-mx3/board-mx31ads.h @@ -0,0 +1,329 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ASM_ARCH_MXC_BOARD_MX31ADS_H__ +#define __ASM_ARCH_MXC_BOARD_MX31ADS_H__ + +#ifdef CONFIG_MACH_MX31ADS +/*! + * @defgroup BRDCFG_MX31 Board Configuration Options + * @ingroup MSL_MX31 + */ + +/*! + * @file mach-mx3/board-mx31ads.h + * + * @brief This file contains all the board level configuration options. + * + * It currently hold the options defined for MX31 ADS Platform. + * + * @ingroup BRDCFG_MX31 + */ + +/* + * Include Files + */ +#include <mach/mxc_uart.h> + +/*! + * @name MXC UART EVB board level configurations + */ +/*! @{ */ +/*! + * Specifies if the Irda transmit path is inverting + */ +#define MXC_IRDA_TX_INV 0 +/*! + * Specifies if the Irda receive path is inverting + */ +#define MXC_IRDA_RX_INV 0 + +/* UART 1 configuration */ +/*! + * This define specifies if the UART port is configured to be in DTE or + * DCE mode. There exists a define like this for each UART port. Valid + * values that can be used are \b MODE_DTE or \b MODE_DCE. + */ +#define UART1_MODE MODE_DCE +/*! + * This define specifies if the UART is to be used for IRDA. There exists a + * define like this for each UART port. Valid values that can be used are + * \b IRDA or \b NO_IRDA. + */ +#define UART1_IR NO_IRDA +/*! + * This define is used to enable or disable a particular UART port. If + * disabled, the UART will not be registered in the file system and the user + * will not be able to access it. There exists a define like this for each UART + * port. Specify a value of 1 to enable the UART and 0 to disable it. + */ +#define UART1_ENABLED 1 +/*! @} */ +/* UART 2 configuration */ +#define UART2_MODE MODE_DCE +#define UART2_IR IRDA +#ifdef CONFIG_MXC_FIR_MODULE +#define UART2_ENABLED 0 +#else +#define UART2_ENABLED 1 +#endif +/* UART 3 configuration */ +#define UART3_MODE MODE_DTE +#define UART3_IR NO_IRDA +#define UART3_ENABLED 1 +/* UART 4 configuration */ +#define UART4_MODE MODE_DTE +#define UART4_IR NO_IRDA +#define UART4_ENABLED 0 /* Disable UART 4 as its pins are shared with ATA */ +/* UART 5 configuration */ +#define UART5_MODE MODE_DTE +#define UART5_IR NO_IRDA +#define UART5_ENABLED 1 + +#define MXC_LL_EXTUART_PADDR (CS4_BASE_ADDR + 0x10000) +#define MXC_LL_EXTUART_VADDR CS4_IO_ADDRESS(MXC_LL_EXTUART_PADDR) +#undef MXC_LL_EXTUART_16BIT_BUS + +#define MXC_LL_UART_PADDR UART1_BASE_ADDR +#define MXC_LL_UART_VADDR AIPS1_IO_ADDRESS(UART1_BASE_ADDR) + +/*! + * @name PBC Controller parameters + */ +/*! @{ */ +/*! + * Base address of PBC controller + */ +#define PBC_BASE_ADDRESS IO_ADDRESS(CS4_BASE_ADDR) +/* Offsets for the PBC Controller register */ +/*! + * PBC Board status register offset + */ +#define PBC_BSTAT 0x000002 +/*! + * PBC Board control register 1 set address. + */ +#define PBC_BCTRL1_SET 0x000004 +/*! + * PBC Board control register 1 clear address. + */ +#define PBC_BCTRL1_CLEAR 0x000006 +/*! + * PBC Board control register 2 set address. + */ +#define PBC_BCTRL2_SET 0x000008 +/*! + * PBC Board control register 2 clear address. + */ +#define PBC_BCTRL2_CLEAR 0x00000A +/*! + * PBC Board control register 3 set address. + */ +#define PBC_BCTRL3_SET 0x00000C +/*! + * PBC Board control register 3 clear address. + */ +#define PBC_BCTRL3_CLEAR 0x00000E +/*! + * PBC Board control register 4 set address. + */ +#define PBC_BCTRL4_SET 0x000010 +/*! + * PBC Board control register 4 clear address. + */ +#define PBC_BCTRL4_CLEAR 0x000012 +/*! + * PBC Board status register 1. + */ +#define PBC_BSTAT1 0x000014 +/*! + * PBC Board interrupt status register. + */ +#define PBC_INTSTATUS 0x000016 +/*! + * PBC Board interrupt current status register. + */ +#define PBC_INTCURR_STATUS 0x000018 +/*! + * PBC Interrupt mask register set address. + */ +#define PBC_INTMASK_SET 0x00001A +/*! + * PBC Interrupt mask register clear address. + */ +#define PBC_INTMASK_CLEAR 0x00001C + +/*! + * External UART A. + */ +#define PBC_SC16C652_UARTA 0x010000 +/*! + * External UART B. + */ +#define PBC_SC16C652_UARTB 0x010010 +/*! + * Ethernet Controller IO base address. + */ +#define PBC_CS8900A_IOBASE 0x020000 +/*! + * Ethernet Controller Memory base address. + */ +#define PBC_CS8900A_MEMBASE 0x021000 +/*! + * Ethernet Controller DMA base address. + */ +#define PBC_CS8900A_DMABASE 0x022000 +/*! + * External chip select 0. + */ +#define PBC_XCS0 0x040000 +/*! + * LCD Display enable. + */ +#define PBC_LCD_EN_B 0x060000 +/*! + * Code test debug enable. + */ +#define PBC_CODE_B 0x070000 +/*! + * PSRAM memory select. + */ +#define PBC_PSRAM_B 0x5000000 + +/* PBC Board Status Register 1 bit definitions */ +#define PBC_BSTAT1_NF_DET 0x0001 /* NAND flash card. 0 = connected */ +#define PBC_BSTAT1_KP_ON 0x0002 /* KPP board. 0 = connected */ +#define PBC_BSTAT1_LS 0x0004 /* KPP:LightSense signal */ +#define PBC_BSTAT1_ATA_IOCS16 0x0008 /* ATA_IOCS16 signal */ +#define PBC_BSTAT1_ATA_CBLID 0x0010 /* ATA_CBLID signal */ +#define PBC_BSTAT1_ATA_DASP 0x0020 /* ATA_DASP signal */ +#define PBC_BSTAT1_PWR_RDY 0x0040 /* MC13783 power. 1 = ready */ +#define PBC_BSTAT1_SD1_WP 0x0080 /* 0 = SD1 card is write protected */ +#define PBC_BSTAT1_SD2_WP 0x0100 /* 0 = SD2 card is write protected */ +#define PBC_BSTAT1_FS1 0x0200 /* KPP:FlipSense1 signal */ +#define PBC_BSTAT1_FS2 0x0400 /* KPP:FlipSense2 signal */ +#define PBC_BSTAT1_PTT 0x0800 /* KPP:PTT signal */ +#define PBC_BSTAT1_MC13783_IN 0x1000 /* MC13783 board. 0 = connected. */ + +/* PBC Board Control Register 1 bit definitions */ +#define PBC_BCTRL1_ERST 0x0001 /* Ethernet Reset */ +#define PBC_BCTRL1_URST 0x0002 /* Reset External UART controller */ +#define PBC_BCTRL1_UENA 0x0004 /* Enable UART A transceiver */ +#define PBC_BCTRL1_UENB 0x0008 /* Enable UART B transceiver */ +#define PBC_BCTRL1_UENCE 0x0010 /* Enable UART CE transceiver */ +#define PBC_BCTRL1_IREN 0x0020 /* Enable the IRDA transmitter */ +#define PBC_BCTRL1_LED0 0x0040 /* Used to control LED 0 (green) */ +#define PBC_BCTRL1_LED1 0x0080 /* Used to control LED 1 (yellow) */ +#define PBC_BCTRL1_SENSOR1_ON 0x0600 /* Enable Sensor 1 */ +#define PBC_BCTRL1_SENSOR2_ON 0x3000 /* Enable Sensor 2 */ +#define PBC_BCTRL1_BEND 0x4000 /* Big Endian Select */ +#define PBC_BCTRL1_LCDON 0x8000 /* Enable the LCD */ + +/* PBC Board Control Register 2 bit definitions */ +#define PBC_BCTRL2_USELA 0x0001 /* UART A Select, 0 = UART1, 1 = UART5 */ +#define PBC_BCTRL2_USELB 0x0002 /* UART B Select, 0 = UART3, 1 = UART5 */ +#define PBC_BCTRL2_USELC 0x0004 /* UART C Select, 0 = UART2, 1 = UART1 */ +#define PBC_BCTRL2_UMODENA 0x0008 /* UART A Modem Signals Enable, 0 = enabled */ +#define PBC_BCTRL2_UMODENC 0x0008 /* UART C Modem Signals Enable, 0 = enabled */ +#define PBC_BCTRL2_CSI_EN 0x0020 /* Enable the CSI interface, 0 = enabled */ +#define PBC_BCTRL2_ATA_EN 0x0040 /* Enable the ATA interface, 0 = enabled */ +#define PBC_BCTRL2_ATA_SEL 0x0080 /* ATA Select, 0 = group A, 1 = group B */ +#define PBC_BCTRL2_IRDA_MOD 0x0100 /* IRDA Mode (see CPLD spec) */ +#define PBC_BCTRL2_LDC_RST0 0x0200 /* LCD 0 Reset, 1 = reset signal asserted */ +#define PBC_BCTRL2_LDC_RST1 0x0400 /* LCD 1 Reset, 1 = reset signal asserted */ +#define PBC_BCTRL2_LDC_RST2 0x0800 /* LCD 2 Reset, 1 = reset signal asserted */ +#define PBC_BCTRL2_LDCIO_EN 0x1000 /* LCD GPIO Enable, 0 = enabled */ +#define PBC_BCTRL2_CT_CS 0x2000 /* Code Test Chip Select, = Code Test selected */ +#define PBC_BCTRL2_VPP_EN 0x4000 /* PCMCIA VPP Enable, 1 = power on */ +#define PBC_BCTRL2_VCC_EN 0x8000 /* PCMCIA VCC Enable, 1 = power on */ + +/* PBC Board Control Register 3 bit definitions */ +#define PBC_BCTRL3_OTG_FS_SEL 0x0001 /* USB OTG Full Speed Select, 0 = PMIC, 1 = CPU */ +#define PBC_BCTRL3_OTG_FS_EN 0x0002 /* USB OTG Full Speed Enable, 0 = enabled */ +#define PBC_BCTRL3_FSH_SEL 0x0004 /* USB Full Speed Host Select, 0 = Group A, 1 = Group B */ +#define PBC_BCTRL3_FSH_EN 0x0008 /* USB Full Speed Host Enable, 0 = enabled */ +#define PBC_BCTRL3_HSH_SEL 0x0010 /* USB High Speed Host Select, 0 = Group A, 1 = Group B */ +#define PBC_BCTRL3_HSH_EN 0x0020 /* USB High Speed Host Enable, 0 = enabled */ +#define PBC_BCTRL3_FSH_MOD 0x0040 /* USB Full Speed Host Mode, 0 = Differential, 1 = Single ended */ +#define PBC_BCTRL3_OTG_HS_EN 0x0080 /* USB OTG High Speed Enable, 0 = enabled */ +#define PBC_BCTRL3_OTG_VBUS_EN 0x0100 /* USB OTG VBUS Regulator Enable, 0 = enabled */ +#define PBC_BCTRL3_FSH_VBUS_EN 0x0200 /* USB Full Speed Host VBUS Regulator Enable, 0 = enabled */ +#define PBC_BCTRL3_CARD1_SEL 0x0400 /* Card1 Select, 0 = SD1, 1 = MS1 */ +#define PBC_BCTRL3_CARD2_SEL 0x0800 /* Card2 Select, 0 = PCMCIA & SD2, 1 = MS2 */ +#define PBC_BCTRL3_SYNTH_RST 0x1000 /* Audio Synthesizer Reset, 0 = reset asserted */ +#define PBC_BCTRL3_VSIM_EN 0x2000 /* VSIM Regulator Enable, 1 = enabled */ +#define PBC_BCTRL3_VESIM_EN 0x4000 /* VESIM Regulator Enable, 1 = enabled */ +#define PBC_BCTRL3_SPI3_RESET 0x8000 /* CSPI3 Connector Reset, 0 = reset asserted */ + +/* PBC Board Control Register 4 bit definitions */ +#define PBC_BCTRL4_CSI_MSB_EN 0x0001 /* CSI MSB Enable, 0 = CSI_Data[3:0] enabled */ +#define PBC_BCTRL4_REGEN_SEL 0x0002 /* Regulator Enable Select, 0 = enabled */ +#define PBC_BCTRL4_USER_OFF 0x0004 /* User Off Indication, 1 = user off confirmation */ +#define PBC_BCTRL4_VIB_EN 0x0008 /* Vibrator Enable, 1 = enabled */ +#define PBC_BCTRL4_PCMCIA_EN 0x0010 /* PCMCIA Enable, 0 = buffer enabled */ + +#define CKIH_27MHZ_BIT_SET (1 << 4) + +#define PBC_INT_CS8900A 4 +/*! @} */ + +#define PBC_INTSTATUS_REG (PBC_INTSTATUS + PBC_BASE_ADDRESS) +#define PBC_INTCURR_STATUS_REG (PBC_INTCURR_STATUS + PBC_BASE_ADDRESS) +#define PBC_INTMASK_SET_REG (PBC_INTMASK_SET + PBC_BASE_ADDRESS) +#define PBC_INTMASK_CLEAR_REG (PBC_INTMASK_CLEAR + PBC_BASE_ADDRESS) +#define EXPIO_PARENT_INT IOMUX_TO_IRQ(MX31_PIN_GPIO1_4) + +#define MXC_EXP_IO_BASE (MXC_BOARD_IRQ_START) +#define EXPIO_INT_LOW_BAT (MXC_EXP_IO_BASE + 0) +#define EXPIO_INT_PB_IRQ (MXC_EXP_IO_BASE + 1) +#define EXPIO_INT_OTG_FS_OVR (MXC_EXP_IO_BASE + 2) +#define EXPIO_INT_FSH_OVR (MXC_EXP_IO_BASE + 3) +#define EXPIO_INT_RES4 (MXC_EXP_IO_BASE + 4) +#define EXPIO_INT_RES5 (MXC_EXP_IO_BASE + 5) +#define EXPIO_INT_RES6 (MXC_EXP_IO_BASE + 6) +#define EXPIO_INT_RES7 (MXC_EXP_IO_BASE + 7) +#define EXPIO_INT_ENET_INT (MXC_EXP_IO_BASE + 8) +#define EXPIO_INT_OTG_FS_INT (MXC_EXP_IO_BASE + 9) +#define EXPIO_INT_XUART_INTA (MXC_EXP_IO_BASE + 10) +#define EXPIO_INT_XUART_INTB (MXC_EXP_IO_BASE + 11) +#define EXPIO_INT_SYNTH_IRQ (MXC_EXP_IO_BASE + 12) +#define EXPIO_INT_CE_INT1 (MXC_EXP_IO_BASE + 13) +#define EXPIO_INT_CE_INT2 (MXC_EXP_IO_BASE + 14) +#define EXPIO_INT_RES15 (MXC_EXP_IO_BASE + 15) + +#define MXC_MAX_EXP_IO_LINES 16 + +/*! + * @name Defines Base address and IRQ used for CS8900A Ethernet Controller on MXC Boards + */ +/*! @{*/ +/*! This is System IRQ used by CS8900A for interrupt generation taken from platform.h */ +#define CS8900AIRQ EXPIO_INT_ENET_INT +/*! This is I/O Base address used to access registers of CS8900A on MXC ADS */ +#define CS8900A_BASE_ADDRESS (PBC_BASE_ADDRESS + PBC_CS8900A_IOBASE + 0x300) +/*! @} */ + +#define MXC_PMIC_INT_LINE IOMUX_TO_IRQ(MX31_PIN_GPIO1_3) + +#define AHB_FREQ 133000000 +#define IPG_FREQ 66500000 + +#define MXC_BD_LED1 (1 << 6) +#define MXC_BD_LED2 (1 << 7) +#define MXC_BD_LED_ON(led) \ + __raw_writew(led, PBC_BASE_ADDRESS + PBC_BCTRL1_SET) +#define MXC_BD_LED_OFF(led) \ + __raw_writew(led, PBC_BASE_ADDRESS + PBC_BCTRL1_CLEAR) + +#endif /* CONFIG_MACH_MX31ADS */ +#endif /* __ASM_ARCH_MXC_BOARD_MX31ADS_H__ */ diff --git a/arch/arm/mach-mx3/board-mx3_3stack.h b/arch/arm/mach-mx3/board-mx3_3stack.h new file mode 100644 index 000000000000..fd58060c9a18 --- /dev/null +++ b/arch/arm/mach-mx3/board-mx3_3stack.h @@ -0,0 +1,153 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ASM_ARCH_MXC_BOARD_MX31PDK_H__ +#define __ASM_ARCH_MXC_BOARD_MX31PDK_H__ + +#ifdef CONFIG_MACH_MX31_3DS +/*! + * @defgroup BRDCFG_MX31 Board Configuration Options + * @ingroup MSL_MX31 + */ + +/*! + * @file mach-mx3/board-mx3_3stack.h + * + * @brief This file contains all the board level configuration options. + * + * It currently hold the options defined for MX31 3STACK Platform. + * + * @ingroup BRDCFG_MX31 + */ + +/* + * Include Files + */ +#include <mach/mxc_uart.h> + +/*! + * @name MXC UART EVB board level configurations + */ +/*! @{ */ +/*! + * Specifies if the Irda transmit path is inverting + */ +#define MXC_IRDA_TX_INV 0 +/*! + * Specifies if the Irda receive path is inverting + */ +#define MXC_IRDA_RX_INV 0 + +/* UART 1 configuration */ +/*! + * This define specifies if the UART port is configured to be in DTE or + * DCE mode. There exists a define like this for each UART port. Valid + * values that can be used are \b MODE_DTE or \b MODE_DCE. + */ +#define UART1_MODE MODE_DCE +/*! + * This define specifies if the UART is to be used for IRDA. There exists a + * define like this for each UART port. Valid values that can be used are + * \b IRDA or \b NO_IRDA. + */ +#define UART1_IR NO_IRDA +/*! + * This define is used to enable or disable a particular UART port. If + * disabled, the UART will not be registered in the file system and the user + * will not be able to access it. There exists a define like this for each UART + * port. Specify a value of 1 to enable the UART and 0 to disable it. + */ +#define UART1_ENABLED 1 +/*! @} */ +/* UART 2 configuration */ +#define UART2_MODE MODE_DCE +#define UART2_IR NO_IRDA +#define UART2_ENABLED 1 +/* UART 3 configuration */ +#define UART3_MODE MODE_DTE +#define UART3_IR NO_IRDA +#define UART3_ENABLED 1 +/* UART 4 configuration */ +#define UART4_MODE MODE_DTE +#define UART4_IR NO_IRDA +#define UART4_ENABLED 0 /* Disable UART 4 as its pins are shared with ATA */ +/* UART 5 configuration */ +#define UART5_MODE MODE_DTE +#define UART5_IR NO_IRDA +#define UART5_ENABLED 0 + +#define MXC_LL_UART_PADDR UART1_BASE_ADDR +#define MXC_LL_UART_VADDR AIPS1_IO_ADDRESS(UART1_BASE_ADDR) + +#define DEBUG_BASE_ADDRESS CS5_BASE_ADDR +/* LAN9217 ethernet base address */ +#define LAN9217_BASE_ADDR DEBUG_BASE_ADDRESS +/* External UART */ +#define UARTA_BASE_ADDR (DEBUG_BASE_ADDRESS + 0x8000) +#define UARTB_BASE_ADDR (DEBUG_BASE_ADDRESS + 0x10000) + +#define BOARD_IO_ADDR (DEBUG_BASE_ADDRESS + 0x20000) +/* LED switchs */ +#define LED_SWITCH_REG 0x00 +/* buttons */ +#define SWITCH_BUTTONS_REG 0x08 +/* status, interrupt */ +#define INTR_STATUS_REG 0x10 +#define INTR_MASK_REG 0x38 +#define INTR_RESET_REG 0x20 +/* magic word for debug CPLD */ +#define MAGIC_NUMBER1_REG 0x40 +#define MAGIC_NUMBER2_REG 0x48 +/* CPLD code version */ +#define CPLD_CODE_VER_REG 0x50 +/* magic word for debug CPLD */ +#define MAGIC_NUMBER3_REG 0x58 +/* module reset register*/ +#define MODULE_RESET_REG 0x60 +/* CPU ID and Personality ID */ +#define MCU_BOARD_ID_REG 0x68 + +/* interrupts like external uart , external ethernet etc*/ +#define EXPIO_PARENT_INT IOMUX_TO_IRQ(MX31_PIN_GPIO1_1) + +#define MXC_EXP_IO_BASE (MXC_BOARD_IRQ_START) +#define EXPIO_INT_ENET (MXC_BOARD_IRQ_START + 0) +#define EXPIO_INT_XUART_A (MXC_BOARD_IRQ_START + 1) +#define EXPIO_INT_XUART_B (MXC_BOARD_IRQ_START + 2) +#define EXPIO_INT_BUTTON_A (MXC_BOARD_IRQ_START + 3) +#define EXPIO_INT_BUTTON_B (MXC_BOARD_IRQ_START + 4) + +/*! This is System IRQ used by LAN9217 */ +#define LAN9217_IRQ EXPIO_INT_ENET + +/*! LED definition*/ +#define MXC_BD_LED1 (1) +#define MXC_BD_LED2 (1 << 1) +#define MXC_BD_LED3 (1 << 2) +#define MXC_BD_LED4 (1 << 3) +#define MXC_BD_LED5 (1 << 4) +#define MXC_BD_LED6 (1 << 5) +#define MXC_BD_LED7 (1 << 6) +#define MXC_BD_LED8 (1 << 7) + +#define MXC_BD_LED_ON(led) +#define MXC_BD_LED_OFF(led) + +extern unsigned int sdhc_get_card_det_status(struct device *dev); +extern int sdhc_init_card_det(int id); +extern int sdhc_write_protect(struct device *dev); + +extern int __init mx3_3stack_init_mc13783(void); + +#endif /* CONFIG_MACH_MX31_3DS */ +#endif /* __ASM_ARCH_MXC_BOARD_MX31PDK_H__ */ diff --git a/arch/arm/mach-mx3/clock.c b/arch/arm/mach-mx3/clock.c index 8b14239724c9..bf19312acb1a 100644 --- a/arch/arm/mach-mx3/clock.c +++ b/arch/arm/mach-mx3/clock.c @@ -1,5 +1,5 @@ /* - * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright (C) 2008 by Sascha Hauer <kernel@pengutronix.de> * * This program is free software; you can redistribute it and/or @@ -17,23 +17,27 @@ * MA 02110-1301, USA. */ +#include <linux/kernel.h> #include <linux/module.h> #include <linux/spinlock.h> #include <linux/delay.h> #include <linux/clk.h> #include <linux/err.h> #include <linux/io.h> - -#include <asm/clkdev.h> -#include <asm/div64.h> - #include <mach/clock.h> #include <mach/hardware.h> +#include <mach/mxc_dptc.h> +#include <asm/div64.h> #include <mach/common.h> +#include <mach/mxc_dptc.h> #include "crm_regs.h" #define PRE_DIV_MIN_FREQ 10000000 /* Minimum Frequency after Predivider */ +#define PROPAGATE_RATE_DIS 2 + +static int cpu_clk_set_wp(int wp); +struct timer_list dptcen_timer; static void __calc_pre_post_dividers(u32 div, u32 *pre, u32 *post) { @@ -68,17 +72,17 @@ static void __calc_pre_post_dividers(u32 div, u32 *pre, u32 *post) } static struct clk mcu_pll_clk; +static struct clk mcu_main_clk; +static struct clk usb_pll_clk; static struct clk serial_pll_clk; static struct clk ipg_clk; static struct clk ckih_clk; +static struct clk ahb_clk; -static int cgr_enable(struct clk *clk) +static int _clk_enable(struct clk *clk) { u32 reg; - if (!clk->enable_reg) - return 0; - reg = __raw_readl(clk->enable_reg); reg |= 3 << clk->enable_shift; __raw_writel(reg, clk->enable_reg); @@ -86,69 +90,150 @@ static int cgr_enable(struct clk *clk) return 0; } -static void cgr_disable(struct clk *clk) +static void _clk_disable(struct clk *clk) { u32 reg; - if (!clk->enable_reg) - return; - reg = __raw_readl(clk->enable_reg); reg &= ~(3 << clk->enable_shift); - - /* special case for EMI clock */ - if (clk->enable_reg == MXC_CCM_CGR2 && clk->enable_shift == 8) - reg |= (1 << clk->enable_shift); - __raw_writel(reg, clk->enable_reg); } -static unsigned long pll_ref_get_rate(void) +static void _clk_emi_disable(struct clk *clk) { - unsigned long ccmr; - unsigned int prcs; + u32 reg; - ccmr = __raw_readl(MXC_CCM_CCMR); - prcs = (ccmr & MXC_CCM_CCMR_PRCS_MASK) >> MXC_CCM_CCMR_PRCS_OFFSET; - if (prcs == 0x1) - return CKIL_CLK_FREQ * 1024; - else - return clk_get_rate(&ckih_clk); + reg = __raw_readl(clk->enable_reg); + reg &= ~(3 << clk->enable_shift); + reg |= (1 << clk->enable_shift); + __raw_writel(reg, clk->enable_reg); } -static unsigned long usb_pll_get_rate(struct clk *clk) +static int _clk_pll_set_rate(struct clk *clk, unsigned long rate) { - unsigned long reg; + u32 reg; + signed long pd = 1; /* Pre-divider */ + signed long mfi; /* Multiplication Factor (Integer part) */ + signed long mfn; /* Multiplication Factor (Integer part) */ + signed long mfd; /* Multiplication Factor (Denominator Part) */ + signed long tmp; + u32 ref_freq = clk_get_rate(clk->parent); + + while (((ref_freq / pd) * 10) > rate) + pd++; + + if ((ref_freq / pd) < PRE_DIV_MIN_FREQ) + return -EINVAL; + + /* the ref_freq/2 in the following is to round up */ + mfi = (((rate / 2) * pd) + (ref_freq / 2)) / ref_freq; + if (mfi < 5 || mfi > 15) + return -EINVAL; - reg = __raw_readl(MXC_CCM_UPCTL); + /* pick a mfd value that will work + * then solve for mfn */ + mfd = ref_freq / 50000; - return mxc_decode_pll(reg, pll_ref_get_rate()); + /* + * pll_freq * pd * mfd + * mfn = -------------------- - (mfi * mfd) + * 2 * ref_freq + */ + /* the tmp/2 is for rounding */ + tmp = ref_freq / 10000; + mfn = + ((((((rate / 2) + (tmp / 2)) / tmp) * pd) * mfd) / 10000) - + (mfi * mfd); + + mfn = mfn & 0x3ff; + pd--; + mfd--; + + /* Change the Pll value */ + reg = (mfi << MXC_CCM_PCTL_MFI_OFFSET) | + (mfn << MXC_CCM_PCTL_MFN_OFFSET) | + (mfd << MXC_CCM_PCTL_MFD_OFFSET) | (pd << MXC_CCM_PCTL_PD_OFFSET); + + if (clk == &mcu_pll_clk) + __raw_writel(reg, MXC_CCM_MPCTL); + else if (clk == &usb_pll_clk) + __raw_writel(reg, MXC_CCM_UPCTL); + else if (clk == &serial_pll_clk) + __raw_writel(reg, MXC_CCM_SRPCTL); + + clk->rate = rate; + return 0; } -static unsigned long serial_pll_get_rate(struct clk *clk) +static int _clk_cpu_set_rate(struct clk *clk, unsigned long rate) { - unsigned long reg; + if ((rate < ahb_clk.rate) || (rate % ahb_clk.rate != 0)) { + printk(KERN_ERR "Wrong rate %lu in _clk_cpu_set_rate\n", rate); + return -EINVAL; + } - reg = __raw_readl(MXC_CCM_SRPCTL); + cpu_clk_set_wp(rate / ahb_clk.rate - 1); - return mxc_decode_pll(reg, pll_ref_get_rate()); + return PROPAGATE_RATE_DIS; } -static unsigned long mcu_pll_get_rate(struct clk *clk) +static void _clk_pll_recalc(struct clk *clk) { + long mfi, mfn, mfd, pdf, ref_clk, mfn_abs; unsigned long reg, ccmr; + s64 temp; + unsigned int prcs; ccmr = __raw_readl(MXC_CCM_CCMR); + prcs = (ccmr & MXC_CCM_CCMR_PRCS_MASK) >> MXC_CCM_CCMR_PRCS_OFFSET; + if (prcs == 0x1) + ref_clk = CKIL_CLK_FREQ * 1024; + else + ref_clk = clk_get_rate(&ckih_clk); + + if (clk == &mcu_pll_clk) { + if ((ccmr & MXC_CCM_CCMR_MPE) == 0) { + clk->rate = ref_clk; + return; + } + if ((ccmr & MXC_CCM_CCMR_MDS) != 0) { + clk->rate = ref_clk; + return; + } + reg = __raw_readl(MXC_CCM_MPCTL); + } else if (clk == &usb_pll_clk) + reg = __raw_readl(MXC_CCM_UPCTL); + else if (clk == &serial_pll_clk) + reg = __raw_readl(MXC_CCM_SRPCTL); + else { + BUG(); + return; + } + + pdf = (reg & MXC_CCM_PCTL_PD_MASK) >> MXC_CCM_PCTL_PD_OFFSET; + mfd = (reg & MXC_CCM_PCTL_MFD_MASK) >> MXC_CCM_PCTL_MFD_OFFSET; + mfi = (reg & MXC_CCM_PCTL_MFI_MASK) >> MXC_CCM_PCTL_MFI_OFFSET; + mfi = (mfi <= 5) ? 5 : mfi; + mfn = mfn_abs = reg & MXC_CCM_PCTL_MFN_MASK; - if (!(ccmr & MXC_CCM_CCMR_MPE) || (ccmr & MXC_CCM_CCMR_MDS)) - return clk_get_rate(&ckih_clk); + if (mfn >= 0x200) { + mfn |= 0xFFFFFE00; + mfn_abs = -mfn; + } + + ref_clk *= 2; + ref_clk /= pdf + 1; - reg = __raw_readl(MXC_CCM_MPCTL); + temp = (u64) ref_clk * mfn_abs; + do_div(temp, mfd + 1); + if (mfn < 0) + temp = -temp; + temp = (ref_clk * mfi) + temp; - return mxc_decode_pll(reg, pll_ref_get_rate()); + clk->rate = temp; } -static int usb_pll_enable(struct clk *clk) +static int _clk_usb_pll_enable(struct clk *clk) { u32 reg; @@ -162,7 +247,7 @@ static int usb_pll_enable(struct clk *clk) return 0; } -static void usb_pll_disable(struct clk *clk) +static void _clk_usb_pll_disable(struct clk *clk) { u32 reg; @@ -171,7 +256,7 @@ static void usb_pll_disable(struct clk *clk) __raw_writel(reg, MXC_CCM_CCMR); } -static int serial_pll_enable(struct clk *clk) +static int _clk_serial_pll_enable(struct clk *clk) { u32 reg; @@ -185,7 +270,7 @@ static int serial_pll_enable(struct clk *clk) return 0; } -static void serial_pll_disable(struct clk *clk) +static void _clk_serial_pll_disable(struct clk *clk) { u32 reg; @@ -198,53 +283,103 @@ static void serial_pll_disable(struct clk *clk) #define PDR1(mask, off) ((__raw_readl(MXC_CCM_PDR1) & mask) >> off) #define PDR2(mask, off) ((__raw_readl(MXC_CCM_PDR2) & mask) >> off) -static unsigned long mcu_main_get_rate(struct clk *clk) +static void _clk_mcu_main_recalc(struct clk *clk) { u32 pmcr0 = __raw_readl(MXC_CCM_PMCR0); - if ((pmcr0 & MXC_CCM_PMCR0_DFSUP1) == MXC_CCM_PMCR0_DFSUP1_SPLL) - return clk_get_rate(&serial_pll_clk); - else - return clk_get_rate(&mcu_pll_clk); + if ((pmcr0 & MXC_CCM_PMCR0_DFSUP1) == MXC_CCM_PMCR0_DFSUP1_SPLL) { + serial_pll_clk.recalc(&serial_pll_clk); + clk->rate = serial_pll_clk.rate; + } else { + mcu_pll_clk.recalc(&mcu_pll_clk); + clk->rate = mcu_pll_clk.rate; + } +} + +static void _clk_cpu_recalc(struct clk *clk) +{ + unsigned long mcu_pdf; + + mcu_pdf = PDR0(MXC_CCM_PDR0_MCU_PODF_MASK, + MXC_CCM_PDR0_MCU_PODF_OFFSET); + clk->rate = clk->parent->rate / (mcu_pdf + 1); } -static unsigned long ahb_get_rate(struct clk *clk) +static void _clk_hclk_recalc(struct clk *clk) { unsigned long max_pdf; max_pdf = PDR0(MXC_CCM_PDR0_MAX_PODF_MASK, MXC_CCM_PDR0_MAX_PODF_OFFSET); - return clk_get_rate(clk->parent) / (max_pdf + 1); + clk->rate = clk->parent->rate / (max_pdf + 1); } -static unsigned long ipg_get_rate(struct clk *clk) +static void _clk_ipg_recalc(struct clk *clk) { unsigned long ipg_pdf; ipg_pdf = PDR0(MXC_CCM_PDR0_IPG_PODF_MASK, MXC_CCM_PDR0_IPG_PODF_OFFSET); - return clk_get_rate(clk->parent) / (ipg_pdf + 1); + clk->rate = clk->parent->rate / (ipg_pdf + 1); } -static unsigned long nfc_get_rate(struct clk *clk) +static void _clk_nfc_recalc(struct clk *clk) { unsigned long nfc_pdf; nfc_pdf = PDR0(MXC_CCM_PDR0_NFC_PODF_MASK, MXC_CCM_PDR0_NFC_PODF_OFFSET); - return clk_get_rate(clk->parent) / (nfc_pdf + 1); + clk->rate = clk->parent->rate / (nfc_pdf + 1); + +} + +static int _clk_nfc_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + u32 div; + + div = clk->parent->rate / rate; + + if ((clk->parent->rate / div) != rate) + return -EINVAL; + + if (div > 8) + return -EINVAL; + + reg = __raw_readl(MXC_CCM_PDR0) & ~MXC_CCM_PDR0_NFC_PODF_MASK; + reg |= (div - 1) << MXC_CCM_PDR0_NFC_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_PDR0); + + clk->rate = rate; + + return 0; + } -static unsigned long hsp_get_rate(struct clk *clk) +static unsigned long _clk_nfc_round_rate(struct clk *clk, unsigned long rate) +{ + u32 div = clk->parent->rate / rate; + + if (clk->parent->rate % rate) + div++; + + if (div > 8) + return -EINVAL; + + return clk->parent->rate / div; + +} + +static void _clk_hsp_recalc(struct clk *clk) { unsigned long hsp_pdf; hsp_pdf = PDR0(MXC_CCM_PDR0_HSP_PODF_MASK, MXC_CCM_PDR0_HSP_PODF_OFFSET); - return clk_get_rate(clk->parent) / (hsp_pdf + 1); + clk->rate = clk->parent->rate / (hsp_pdf + 1); } -static unsigned long usb_get_rate(struct clk *clk) +static void _clk_usb_recalc(struct clk *clk) { unsigned long usb_pdf, usb_prepdf; @@ -252,10 +387,10 @@ static unsigned long usb_get_rate(struct clk *clk) MXC_CCM_PDR1_USB_PODF_OFFSET); usb_prepdf = PDR1(MXC_CCM_PDR1_USB_PRDF_MASK, MXC_CCM_PDR1_USB_PRDF_OFFSET); - return clk_get_rate(clk->parent) / (usb_prepdf + 1) / (usb_pdf + 1); + clk->rate = clk->parent->rate / (usb_prepdf + 1) / (usb_pdf + 1); } -static unsigned long csi_get_rate(struct clk *clk) +static void _clk_csi_recalc(struct clk *clk) { u32 reg, pre, post; @@ -266,10 +401,10 @@ static unsigned long csi_get_rate(struct clk *clk) post = (reg & MXC_CCM_PDR0_CSI_PODF_MASK) >> MXC_CCM_PDR0_CSI_PODF_OFFSET; post++; - return clk_get_rate(clk->parent) / (pre * post); + clk->rate = clk->parent->rate / (pre * post); } -static unsigned long csi_round_rate(struct clk *clk, unsigned long rate) +static unsigned long _clk_csi_round_rate(struct clk *clk, unsigned long rate) { u32 pre, post, parent = clk_get_rate(clk->parent); u32 div = parent / rate; @@ -282,7 +417,7 @@ static unsigned long csi_round_rate(struct clk *clk, unsigned long rate) return parent / (pre * post); } -static int csi_set_rate(struct clk *clk, unsigned long rate) +static int _clk_csi_set_rate(struct clk *clk, unsigned long rate) { u32 reg, div, pre, post, parent = clk_get_rate(clk->parent); @@ -300,10 +435,20 @@ static int csi_set_rate(struct clk *clk, unsigned long rate) reg |= (pre - 1) << MXC_CCM_PDR0_CSI_PRDF_OFFSET; __raw_writel(reg, MXC_CCM_PDR0); + clk->rate = rate; return 0; } -static unsigned long ssi1_get_rate(struct clk *clk) +static void _clk_per_recalc(struct clk *clk) +{ + unsigned long per_pdf; + + per_pdf = PDR0(MXC_CCM_PDR0_PER_PODF_MASK, + MXC_CCM_PDR0_PER_PODF_OFFSET); + clk->rate = clk->parent->rate / (per_pdf + 1); +} + +static void _clk_ssi1_recalc(struct clk *clk) { unsigned long ssi1_pdf, ssi1_prepdf; @@ -311,10 +456,10 @@ static unsigned long ssi1_get_rate(struct clk *clk) MXC_CCM_PDR1_SSI1_PODF_OFFSET); ssi1_prepdf = PDR1(MXC_CCM_PDR1_SSI1_PRE_PODF_MASK, MXC_CCM_PDR1_SSI1_PRE_PODF_OFFSET); - return clk_get_rate(clk->parent) / (ssi1_prepdf + 1) / (ssi1_pdf + 1); + clk->rate = clk->parent->rate / (ssi1_prepdf + 1) / (ssi1_pdf + 1); } -static unsigned long ssi2_get_rate(struct clk *clk) +static void _clk_ssi2_recalc(struct clk *clk) { unsigned long ssi2_pdf, ssi2_prepdf; @@ -322,10 +467,10 @@ static unsigned long ssi2_get_rate(struct clk *clk) MXC_CCM_PDR1_SSI2_PODF_OFFSET); ssi2_prepdf = PDR1(MXC_CCM_PDR1_SSI2_PRE_PODF_MASK, MXC_CCM_PDR1_SSI2_PRE_PODF_OFFSET); - return clk_get_rate(clk->parent) / (ssi2_prepdf + 1) / (ssi2_pdf + 1); + clk->rate = clk->parent->rate / (ssi2_prepdf + 1) / (ssi2_pdf + 1); } -static unsigned long firi_get_rate(struct clk *clk) +static void _clk_firi_recalc(struct clk *clk) { unsigned long firi_pdf, firi_prepdf; @@ -333,10 +478,10 @@ static unsigned long firi_get_rate(struct clk *clk) MXC_CCM_PDR1_FIRI_PODF_OFFSET); firi_prepdf = PDR1(MXC_CCM_PDR1_FIRI_PRE_PODF_MASK, MXC_CCM_PDR1_FIRI_PRE_PODF_OFFSET); - return clk_get_rate(clk->parent) / (firi_prepdf + 1) / (firi_pdf + 1); + clk->rate = clk->parent->rate / (firi_prepdf + 1) / (firi_pdf + 1); } -static unsigned long firi_round_rate(struct clk *clk, unsigned long rate) +static unsigned long _clk_firi_round_rate(struct clk *clk, unsigned long rate) { u32 pre, post; u32 parent = clk_get_rate(clk->parent); @@ -351,7 +496,7 @@ static unsigned long firi_round_rate(struct clk *clk, unsigned long rate) } -static int firi_set_rate(struct clk *clk, unsigned long rate) +static int _clk_firi_set_rate(struct clk *clk, unsigned long rate) { u32 reg, div, pre, post, parent = clk_get_rate(clk->parent); @@ -369,232 +514,724 @@ static int firi_set_rate(struct clk *clk, unsigned long rate) reg |= (post - 1) << MXC_CCM_PDR1_FIRI_PODF_OFFSET; __raw_writel(reg, MXC_CCM_PDR1); + clk->rate = rate; return 0; } -static unsigned long mbx_get_rate(struct clk *clk) +static void _clk_mbx_recalc(struct clk *clk) { - return clk_get_rate(clk->parent) / 2; + clk->rate = clk->parent->rate / 2; } -static unsigned long mstick1_get_rate(struct clk *clk) +static void _clk_mstick1_recalc(struct clk *clk) { unsigned long msti_pdf; msti_pdf = PDR2(MXC_CCM_PDR2_MST1_PDF_MASK, MXC_CCM_PDR2_MST1_PDF_OFFSET); - return clk_get_rate(clk->parent) / (msti_pdf + 1); + clk->rate = clk->parent->rate / (msti_pdf + 1); } -static unsigned long mstick2_get_rate(struct clk *clk) +static void _clk_mstick2_recalc(struct clk *clk) { unsigned long msti_pdf; msti_pdf = PDR2(MXC_CCM_PDR2_MST2_PDF_MASK, MXC_CCM_PDR2_MST2_PDF_OFFSET); - return clk_get_rate(clk->parent) / (msti_pdf + 1); -} - -static unsigned long ckih_rate; - -static unsigned long clk_ckih_get_rate(struct clk *clk) -{ - return ckih_rate; + clk->rate = clk->parent->rate / (msti_pdf + 1); } static struct clk ckih_clk = { - .get_rate = clk_ckih_get_rate, + .name = "ckih", + .rate = 0, /* determined later (26 or 27 MHz) */ + .flags = RATE_PROPAGATES, +}; + +static struct clk ckil_clk = { + .name = "ckil", + .rate = CKIL_CLK_FREQ, + .flags = RATE_PROPAGATES, }; static struct clk mcu_pll_clk = { + .name = "mcu_pll", .parent = &ckih_clk, - .get_rate = mcu_pll_get_rate, + .set_rate = _clk_pll_set_rate, + .recalc = _clk_pll_recalc, + .flags = RATE_PROPAGATES, }; static struct clk mcu_main_clk = { + .name = "mcu_main_clk", .parent = &mcu_pll_clk, - .get_rate = mcu_main_get_rate, + .recalc = _clk_mcu_main_recalc, }; static struct clk serial_pll_clk = { + .name = "serial_pll", .parent = &ckih_clk, - .get_rate = serial_pll_get_rate, - .enable = serial_pll_enable, - .disable = serial_pll_disable, + .set_rate = _clk_pll_set_rate, + .recalc = _clk_pll_recalc, + .enable = _clk_serial_pll_enable, + .disable = _clk_serial_pll_disable, + .flags = RATE_PROPAGATES, }; static struct clk usb_pll_clk = { + .name = "usb_pll", .parent = &ckih_clk, - .get_rate = usb_pll_get_rate, - .enable = usb_pll_enable, - .disable = usb_pll_disable, + .set_rate = _clk_pll_set_rate, + .recalc = _clk_pll_recalc, + .enable = _clk_usb_pll_enable, + .disable = _clk_usb_pll_disable, + .flags = RATE_PROPAGATES, +}; + +static struct clk cpu_clk = { + .name = "cpu_clk", + .parent = &mcu_main_clk, + .recalc = _clk_cpu_recalc, + .set_rate = _clk_cpu_set_rate, }; static struct clk ahb_clk = { + .name = "ahb_clk", .parent = &mcu_main_clk, - .get_rate = ahb_get_rate, -}; - -#define DEFINE_CLOCK(name, i, er, es, gr, s, p) \ - static struct clk name = { \ - .id = i, \ - .enable_reg = er, \ - .enable_shift = es, \ - .get_rate = gr, \ - .enable = cgr_enable, \ - .disable = cgr_disable, \ - .secondary = s, \ - .parent = p, \ - } + .recalc = _clk_hclk_recalc, + .flags = RATE_PROPAGATES, +}; -#define DEFINE_CLOCK1(name, i, er, es, getsetround, s, p) \ - static struct clk name = { \ - .id = i, \ - .enable_reg = er, \ - .enable_shift = es, \ - .get_rate = getsetround##_get_rate, \ - .set_rate = getsetround##_set_rate, \ - .round_rate = getsetround##_round_rate, \ - .enable = cgr_enable, \ - .disable = cgr_disable, \ - .secondary = s, \ - .parent = p, \ - } +static struct clk per_clk = { + .name = "per_clk", + .parent = &usb_pll_clk, + .recalc = _clk_per_recalc, + .flags = RATE_PROPAGATES, +}; + +static struct clk perclk_clk = { + .name = "perclk_clk", + .parent = &ipg_clk, + .flags = RATE_PROPAGATES, +}; + +static struct clk cspi_clk[] = { + { + .name = "cspi_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_CSPI1_OFFSET, + .disable = _clk_disable,}, + { + .name = "cspi_clk", + .id = 1, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_CSPI2_OFFSET, + .disable = _clk_disable,}, + { + .name = "cspi_clk", + .id = 2, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_CSPI3_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk ipg_clk = { + .name = "ipg_clk", + .parent = &ahb_clk, + .recalc = _clk_ipg_recalc, + .flags = RATE_PROPAGATES, +}; + +static struct clk emi_clk = { + .name = "emi_clk", + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_EMI_OFFSET, + .disable = _clk_emi_disable, +}; + +static struct clk gpt_clk = { + .name = "gpt_clk", + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_GPT_OFFSET, + .disable = _clk_disable, +}; + +static struct clk pwm_clk = { + .name = "pwm", + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_PWM_OFFSET, + .disable = _clk_disable, +}; + +static struct clk epit_clk[] = { + { + .name = "epit_clk", + .id = 0, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_EPIT1_OFFSET, + .disable = _clk_disable,}, + { + .name = "epit_clk", + .id = 1, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_EPIT2_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk nfc_clk = { + .name = "nfc_clk", + .parent = &ahb_clk, + .recalc = _clk_nfc_recalc, + .set_rate = _clk_nfc_set_rate, + .round_rate = _clk_nfc_round_rate, +}; + +static struct clk scc_clk = { + .name = "scc_clk", + .parent = &ipg_clk, +}; + +static struct clk ipu_clk = { + .name = "ipu_clk", + .parent = &mcu_main_clk, + .recalc = _clk_hsp_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_IPU_OFFSET, + .disable = _clk_disable, +}; + +static struct clk kpp_clk = { + .name = "kpp_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_KPP_OFFSET, + .disable = _clk_disable, +}; + +static struct clk wdog_clk = { + .name = "wdog_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_WDOG_OFFSET, + .disable = _clk_disable, +}; +static struct clk rtc_clk = { + .name = "rtc_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_RTC_OFFSET, + .disable = _clk_disable, +}; + +static struct clk usb_clk[] = { + { + .name = "usb_clk", + .parent = &usb_pll_clk, + .recalc = _clk_usb_recalc,}, + { + .name = "usb_ahb_clk", + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_USBOTG_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk csi_clk = { + .name = "csi_clk", + .parent = &serial_pll_clk, + .recalc = _clk_csi_recalc, + .round_rate = _clk_csi_round_rate, + .set_rate = _clk_csi_set_rate, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_CSI_OFFSET, + .disable = _clk_disable, +}; + +static struct clk uart_clk[] = { + { + .name = "uart_clk", + .id = 0, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_UART1_OFFSET, + .disable = _clk_disable,}, + { + .name = "uart_clk", + .id = 1, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_UART2_OFFSET, + .disable = _clk_disable,}, + { + .name = "uart_clk", + .id = 2, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_UART3_OFFSET, + .disable = _clk_disable,}, + { + .name = "uart_clk", + .id = 3, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_UART4_OFFSET, + .disable = _clk_disable,}, + { + .name = "uart_clk", + .id = 4, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_UART5_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk i2c_clk[] = { + { + .name = "i2c_clk", + .id = 0, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_I2C1_OFFSET, + .disable = _clk_disable,}, + { + .name = "i2c_clk", + .id = 1, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_I2C2_OFFSET, + .disable = _clk_disable,}, + { + .name = "i2c_clk", + .id = 2, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_I2C3_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk owire_clk = { + .name = "owire_clk", + .parent = &perclk_clk, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_OWIRE_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, +}; + +static struct clk sdhc_clk[] = { + { + .name = "sdhc_clk", + .id = 0, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_SD_MMC1_OFFSET, + .disable = _clk_disable,}, + { + .name = "sdhc_clk", + .id = 1, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_SD_MMC2_OFFSET, + .disable = _clk_disable,}, +}; -DEFINE_CLOCK(perclk_clk, 0, NULL, 0, NULL, NULL, &ipg_clk); - -DEFINE_CLOCK(sdhc1_clk, 0, MXC_CCM_CGR0, 0, NULL, NULL, &perclk_clk); -DEFINE_CLOCK(sdhc2_clk, 1, MXC_CCM_CGR0, 2, NULL, NULL, &perclk_clk); -DEFINE_CLOCK(gpt_clk, 0, MXC_CCM_CGR0, 4, NULL, NULL, &perclk_clk); -DEFINE_CLOCK(epit1_clk, 0, MXC_CCM_CGR0, 6, NULL, NULL, &perclk_clk); -DEFINE_CLOCK(epit2_clk, 1, MXC_CCM_CGR0, 8, NULL, NULL, &perclk_clk); -DEFINE_CLOCK(iim_clk, 0, MXC_CCM_CGR0, 10, NULL, NULL, &ipg_clk); -DEFINE_CLOCK(ata_clk, 0, MXC_CCM_CGR0, 12, NULL, NULL, &ipg_clk); -DEFINE_CLOCK(sdma_clk1, 0, MXC_CCM_CGR0, 14, NULL, &sdma_clk1, &ahb_clk); -DEFINE_CLOCK(cspi3_clk, 2, MXC_CCM_CGR0, 16, NULL, NULL, &ipg_clk); -DEFINE_CLOCK(rng_clk, 0, MXC_CCM_CGR0, 18, NULL, NULL, &ipg_clk); -DEFINE_CLOCK(uart1_clk, 0, MXC_CCM_CGR0, 20, NULL, NULL, &perclk_clk); -DEFINE_CLOCK(uart2_clk, 1, MXC_CCM_CGR0, 22, NULL, NULL, &perclk_clk); -DEFINE_CLOCK(ssi1_clk, 0, MXC_CCM_CGR0, 24, ssi1_get_rate, NULL, &serial_pll_clk); -DEFINE_CLOCK(i2c1_clk, 0, MXC_CCM_CGR0, 26, NULL, NULL, &perclk_clk); -DEFINE_CLOCK(i2c2_clk, 1, MXC_CCM_CGR0, 28, NULL, NULL, &perclk_clk); -DEFINE_CLOCK(i2c3_clk, 2, MXC_CCM_CGR0, 30, NULL, NULL, &perclk_clk); - -DEFINE_CLOCK(mpeg4_clk, 0, MXC_CCM_CGR1, 0, NULL, NULL, &ahb_clk); -DEFINE_CLOCK(mstick1_clk, 0, MXC_CCM_CGR1, 2, mstick1_get_rate, NULL, &usb_pll_clk); -DEFINE_CLOCK(mstick2_clk, 1, MXC_CCM_CGR1, 4, mstick2_get_rate, NULL, &usb_pll_clk); -DEFINE_CLOCK1(csi_clk, 0, MXC_CCM_CGR1, 6, csi, NULL, &serial_pll_clk); -DEFINE_CLOCK(rtc_clk, 0, MXC_CCM_CGR1, 8, NULL, NULL, &ipg_clk); -DEFINE_CLOCK(wdog_clk, 0, MXC_CCM_CGR1, 10, NULL, NULL, &ipg_clk); -DEFINE_CLOCK(pwm_clk, 0, MXC_CCM_CGR1, 12, NULL, NULL, &perclk_clk); -DEFINE_CLOCK(usb_clk2, 0, MXC_CCM_CGR1, 18, usb_get_rate, NULL, &ahb_clk); -DEFINE_CLOCK(kpp_clk, 0, MXC_CCM_CGR1, 20, NULL, NULL, &ipg_clk); -DEFINE_CLOCK(ipu_clk, 0, MXC_CCM_CGR1, 22, hsp_get_rate, NULL, &mcu_main_clk); -DEFINE_CLOCK(uart3_clk, 2, MXC_CCM_CGR1, 24, NULL, NULL, &perclk_clk); -DEFINE_CLOCK(uart4_clk, 3, MXC_CCM_CGR1, 26, NULL, NULL, &perclk_clk); -DEFINE_CLOCK(uart5_clk, 4, MXC_CCM_CGR1, 28, NULL, NULL, &perclk_clk); -DEFINE_CLOCK(owire_clk, 0, MXC_CCM_CGR1, 30, NULL, NULL, &perclk_clk); - -DEFINE_CLOCK(ssi2_clk, 1, MXC_CCM_CGR2, 0, ssi2_get_rate, NULL, &serial_pll_clk); -DEFINE_CLOCK(cspi1_clk, 0, MXC_CCM_CGR2, 2, NULL, NULL, &ipg_clk); -DEFINE_CLOCK(cspi2_clk, 1, MXC_CCM_CGR2, 4, NULL, NULL, &ipg_clk); -DEFINE_CLOCK(mbx_clk, 0, MXC_CCM_CGR2, 6, mbx_get_rate, NULL, &ahb_clk); -DEFINE_CLOCK(emi_clk, 0, MXC_CCM_CGR2, 8, NULL, NULL, &ahb_clk); -DEFINE_CLOCK(rtic_clk, 0, MXC_CCM_CGR2, 10, NULL, NULL, &ahb_clk); -DEFINE_CLOCK1(firi_clk, 0, MXC_CCM_CGR2, 12, firi, NULL, &usb_pll_clk); - -DEFINE_CLOCK(sdma_clk2, 0, NULL, 0, NULL, NULL, &ipg_clk); -DEFINE_CLOCK(usb_clk1, 0, NULL, 0, usb_get_rate, NULL, &usb_pll_clk); -DEFINE_CLOCK(nfc_clk, 0, NULL, 0, nfc_get_rate, NULL, &ahb_clk); -DEFINE_CLOCK(scc_clk, 0, NULL, 0, NULL, NULL, &ipg_clk); -DEFINE_CLOCK(ipg_clk, 0, NULL, 0, ipg_get_rate, NULL, &ahb_clk); - -#define _REGISTER_CLOCK(d, n, c) \ - { \ - .dev_id = d, \ - .con_id = n, \ - .clk = &c, \ - }, - -static struct clk_lookup lookups[] = { - _REGISTER_CLOCK(NULL, "emi", emi_clk) - _REGISTER_CLOCK(NULL, "cspi", cspi1_clk) - _REGISTER_CLOCK(NULL, "cspi", cspi2_clk) - _REGISTER_CLOCK(NULL, "cspi", cspi3_clk) - _REGISTER_CLOCK(NULL, "gpt", gpt_clk) - _REGISTER_CLOCK(NULL, "pwm", pwm_clk) - _REGISTER_CLOCK(NULL, "wdog", wdog_clk) - _REGISTER_CLOCK(NULL, "rtc", rtc_clk) - _REGISTER_CLOCK(NULL, "epit", epit1_clk) - _REGISTER_CLOCK(NULL, "epit", epit2_clk) - _REGISTER_CLOCK("mxc_nand.0", NULL, nfc_clk) - _REGISTER_CLOCK("ipu-core", NULL, ipu_clk) - _REGISTER_CLOCK("mx3_sdc_fb", NULL, ipu_clk) - _REGISTER_CLOCK(NULL, "kpp", kpp_clk) - _REGISTER_CLOCK("fsl-usb2-udc", "usb", usb_clk1) - _REGISTER_CLOCK("fsl-usb2-udc", "usb_ahb", usb_clk2) - _REGISTER_CLOCK("mx3-camera.0", NULL, csi_clk) - _REGISTER_CLOCK("imx-uart.0", NULL, uart1_clk) - _REGISTER_CLOCK("imx-uart.1", NULL, uart2_clk) - _REGISTER_CLOCK("imx-uart.2", NULL, uart3_clk) - _REGISTER_CLOCK("imx-uart.3", NULL, uart4_clk) - _REGISTER_CLOCK("imx-uart.4", NULL, uart5_clk) - _REGISTER_CLOCK("imx-i2c.0", NULL, i2c1_clk) - _REGISTER_CLOCK("imx-i2c.1", NULL, i2c2_clk) - _REGISTER_CLOCK("imx-i2c.2", NULL, i2c3_clk) - _REGISTER_CLOCK("mxc_w1.0", NULL, owire_clk) - _REGISTER_CLOCK("mxc-mmc.0", NULL, sdhc1_clk) - _REGISTER_CLOCK("mxc-mmc.1", NULL, sdhc2_clk) - _REGISTER_CLOCK(NULL, "ssi", ssi1_clk) - _REGISTER_CLOCK(NULL, "ssi", ssi2_clk) - _REGISTER_CLOCK(NULL, "firi", firi_clk) - _REGISTER_CLOCK(NULL, "ata", ata_clk) - _REGISTER_CLOCK(NULL, "rtic", rtic_clk) - _REGISTER_CLOCK(NULL, "rng", rng_clk) - _REGISTER_CLOCK(NULL, "sdma_ahb", sdma_clk1) - _REGISTER_CLOCK(NULL, "sdma_ipg", sdma_clk2) - _REGISTER_CLOCK(NULL, "mstick", mstick1_clk) - _REGISTER_CLOCK(NULL, "mstick", mstick2_clk) - _REGISTER_CLOCK(NULL, "scc", scc_clk) - _REGISTER_CLOCK(NULL, "iim", iim_clk) - _REGISTER_CLOCK(NULL, "mpeg4", mpeg4_clk) - _REGISTER_CLOCK(NULL, "mbx", mbx_clk) +static struct clk ssi_clk[] = { + { + .name = "ssi_clk", + .parent = &serial_pll_clk, + .recalc = _clk_ssi1_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_SSI1_OFFSET, + .disable = _clk_disable,}, + { + .name = "ssi_clk", + .id = 1, + .parent = &serial_pll_clk, + .recalc = _clk_ssi2_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_SSI2_OFFSET, + .disable = _clk_disable,}, }; +static struct clk firi_clk = { + .name = "firi_clk", + .parent = &usb_pll_clk, + .round_rate = _clk_firi_round_rate, + .set_rate = _clk_firi_set_rate, + .recalc = _clk_firi_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_FIRI_OFFSET, + .disable = _clk_disable, +}; + +static struct clk ata_clk = { + .name = "ata_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_ATA_OFFSET, + .disable = _clk_disable, +}; + +static struct clk mbx_clk = { + .name = "mbx_clk", + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_GACC_OFFSET, + .recalc = _clk_mbx_recalc, +}; + +static struct clk vpu_clk = { + .name = "vpu_clk", + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_GACC_OFFSET, + .recalc = _clk_mbx_recalc, +}; + +static struct clk rtic_clk = { + .name = "rtic_clk", + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_RTIC_OFFSET, + .disable = _clk_disable, +}; + +static struct clk rng_clk = { + .name = "rng_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_RNG_OFFSET, + .disable = _clk_disable, +}; + +static struct clk sdma_clk[] = { + { + .name = "sdma_ahb_clk", + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_SDMA_OFFSET, + .disable = _clk_disable,}, + { + .name = "sdma_ipg_clk", + .parent = &ipg_clk,} +}; + +static struct clk mpeg4_clk = { + .name = "mpeg4_clk", + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_HANTRO_OFFSET, + .disable = _clk_disable, +}; + +static struct clk vl2cc_clk = { + .name = "vl2cc_clk", + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_HANTRO_OFFSET, + .disable = _clk_disable, +}; + +static struct clk mstick_clk[] = { + { + .name = "mstick_clk", + .id = 0, + .parent = &usb_pll_clk, + .recalc = _clk_mstick1_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_MEMSTICK1_OFFSET, + .disable = _clk_disable,}, + { + .name = "mstick_clk", + .id = 1, + .parent = &usb_pll_clk, + .recalc = _clk_mstick2_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_MEMSTICK2_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk iim_clk = { + .name = "iim_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_IIM_OFFSET, + .disable = _clk_disable, +}; + +static unsigned long _clk_cko1_round_rate(struct clk *clk, unsigned long rate) +{ + u32 div, parent = clk_get_rate(clk->parent); + + div = parent / rate; + if (parent % rate) + div++; + + if (div > 8) + div = 16; + else if (div > 4) + div = 8; + else if (div > 2) + div = 4; + + return parent / div; +} + +static int _clk_cko1_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div, parent = clk_get_rate(clk->parent); + + div = parent / rate; + + if (div == 16) + div = 4; + else if (div == 8) + div = 3; + else if (div == 4) + div = 2; + else if (div == 2) + div = 1; + else if (div == 1) + div = 0; + else + return -EINVAL; + + reg = __raw_readl(MXC_CCM_COSR) & ~MXC_CCM_COSR_CLKOUTDIV_MASK; + reg |= div << MXC_CCM_COSR_CLKOUTDIV_OFFSET; + __raw_writel(reg, MXC_CCM_COSR); + + return 0; +} + +static void _clk_cko1_recalc(struct clk *clk) +{ + u32 div; + + div = __raw_readl(MXC_CCM_COSR) & MXC_CCM_COSR_CLKOUTDIV_MASK >> + MXC_CCM_COSR_CLKOUTDIV_OFFSET; + + clk->rate = clk->parent->rate / (1 << div); +} + +static int _clk_cko1_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_COSR) & ~MXC_CCM_COSR_CLKOSEL_MASK; + + if (parent == &mcu_main_clk) + reg |= 0 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &ipg_clk) + reg |= 1 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &usb_pll_clk) + reg |= 2 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == mcu_main_clk.parent) + reg |= 3 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &ahb_clk) + reg |= 5 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &cpu_clk) + reg |= 6 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &serial_pll_clk) + reg |= 7 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &ckih_clk) + reg |= 8 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &emi_clk) + reg |= 9 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &ipu_clk) + reg |= 0xA << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &nfc_clk) + reg |= 0xB << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &uart_clk[0]) + reg |= 0xC << MXC_CCM_COSR_CLKOSEL_OFFSET; + else + return -EINVAL; + + __raw_writel(reg, MXC_CCM_COSR); + + return 0; +} + +static int _clk_cko1_enable(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_COSR) | MXC_CCM_COSR_CLKOEN; + __raw_writel(reg, MXC_CCM_COSR); + + return 0; +} + +static void _clk_cko1_disable(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_COSR) & ~MXC_CCM_COSR_CLKOEN; + __raw_writel(reg, MXC_CCM_COSR); +} + +static struct clk cko1_clk = { + .name = "cko1_clk", + .recalc = _clk_cko1_recalc, + .set_rate = _clk_cko1_set_rate, + .round_rate = _clk_cko1_round_rate, + .set_parent = _clk_cko1_set_parent, + .enable = _clk_cko1_enable, + .disable = _clk_cko1_disable, +}; + +static struct clk *mxc_clks[] = { + &ckih_clk, + &ckil_clk, + &mcu_pll_clk, + &usb_pll_clk, + &serial_pll_clk, + &mcu_main_clk, + &cpu_clk, + &ahb_clk, + &per_clk, + &perclk_clk, + &cko1_clk, + &emi_clk, + &cspi_clk[0], + &cspi_clk[1], + &cspi_clk[2], + &ipg_clk, + &gpt_clk, + &pwm_clk, + &wdog_clk, + &rtc_clk, + &epit_clk[0], + &epit_clk[1], + &nfc_clk, + &ipu_clk, + &kpp_clk, + &usb_clk[0], + &usb_clk[1], + &csi_clk, + &uart_clk[0], + &uart_clk[1], + &uart_clk[2], + &uart_clk[3], + &uart_clk[4], + &i2c_clk[0], + &i2c_clk[1], + &i2c_clk[2], + &owire_clk, + &sdhc_clk[0], + &sdhc_clk[1], + &ssi_clk[0], + &ssi_clk[1], + &firi_clk, + &ata_clk, + &rtic_clk, + &rng_clk, + &sdma_clk[0], + &sdma_clk[1], + &mstick_clk[0], + &mstick_clk[1], + &scc_clk, + &iim_clk, +}; + +static int cpu_curr_wp; +static struct cpu_wp *cpu_wp_tbl; + +static int cpu_wp_nr; + +extern void propagate_rate(struct clk *tclk); + int __init mx31_clocks_init(unsigned long fref) { u32 reg; - int i; + struct clk **clkp; - ckih_rate = fref; + ckil_clk.rate = 32768; + ckih_clk.rate = fref; - for (i = 0; i < ARRAY_SIZE(lookups); i++) - clkdev_add(&lookups[i]); + for (clkp = mxc_clks; clkp < mxc_clks + ARRAY_SIZE(mxc_clks); clkp++) + clk_register(*clkp); - /* change the csi_clk parent if necessary */ - reg = __raw_readl(MXC_CCM_CCMR); - if (!(reg & MXC_CCM_CCMR_CSCS)) - if (clk_set_parent(&csi_clk, &usb_pll_clk)) - pr_err("%s: error changing csi_clk parent\n", __func__); + if (cpu_is_mx31()) { + clk_register(&mpeg4_clk); + clk_register(&mbx_clk); + } else { + clk_register(&vpu_clk); + clk_register(&vl2cc_clk); + } + /* CCMR stby control */ + reg = __raw_readl(MXC_CCM_CCMR); + reg |= MXC_CCM_CCMR_VSTBY | MXC_CCM_CCMR_WAMO; + __raw_writel(reg, MXC_CCM_CCMR); /* Turn off all possible clocks */ - __raw_writel((3 << 4), MXC_CCM_CGR0); + __raw_writel(MXC_CCM_CGR0_GPT_MASK, MXC_CCM_CGR0); __raw_writel(0, MXC_CCM_CGR1); - __raw_writel((3 << 8) | (3 << 14) | (3 << 16)| - 1 << 27 | 1 << 28, /* Bit 27 and 28 are not defined for - MX32, but still required to be set */ - MXC_CCM_CGR2); - /* - * Before turning off usb_pll make sure ipg_per_clk is generated - * by ipg_clk and not usb_pll. - */ - __raw_writel(__raw_readl(MXC_CCM_CCMR) | (1 << 24), MXC_CCM_CCMR); + reg = MXC_CCM_CGR2_EMI_MASK | /*For MX32 */ + MXC_CCM_CGR2_IPMUX1_MASK | /*For MX32 */ + MXC_CCM_CGR2_IPMUX2_MASK | /*For MX32 */ + MXC_CCM_CGR2_MXCCLKENSEL_MASK | /*For MX32 */ + MXC_CCM_CGR2_CHIKCAMPEN_MASK | /*For MX32 */ + MXC_CCM_CGR2_OVRVPUBUSY_MASK | /*For MX32 */ + 0x3 << 27 | /*Bit 27 and 28 are not defined for MX32, + but still requires to be set */ + MXC_CCM_CGR2_APMSYSCLKSEL_MASK | MXC_CCM_CGR2_AOMENA_MASK; + __raw_writel(reg, MXC_CCM_CGR2); + + clk_disable(&cko1_clk); + clk_disable(&usb_pll_clk); - usb_pll_disable(&usb_pll_clk); + pr_info("Clock input source is %ld\n", ckih_clk.rate); - pr_info("Clock input source is %ld\n", clk_get_rate(&ckih_clk)); + /* This will propagate to all children and init all the clock rates */ + propagate_rate(&ckih_clk); clk_enable(&gpt_clk); clk_enable(&emi_clk); @@ -602,15 +1239,173 @@ int __init mx31_clocks_init(unsigned long fref) clk_enable(&serial_pll_clk); - if (mx31_revision() >= CHIP_REV_2_0) { - reg = __raw_readl(MXC_CCM_PMCR1); - /* No PLL restart on DVFS switch; enable auto EMI handshake */ - reg |= MXC_CCM_PMCR1_PLLRDIS | MXC_CCM_PMCR1_EMIRQ_EN; - __raw_writel(reg, MXC_CCM_PMCR1); + cpu_curr_wp = cpu_clk.rate / ahb_clk.rate - 1; + cpu_wp_tbl = get_cpu_wp(&cpu_wp_nr); + + /* Init serial PLL according */ + clk_set_rate(&serial_pll_clk, (cpu_wp_tbl[2].pll_rate)); + + if (cpu_is_mx31_rev(CHIP_REV_2_0) < 0) { + /* replace 399MHz wp with 266MHz one */ + memcpy(&cpu_wp_tbl[2], &cpu_wp_tbl[1], sizeof(cpu_wp_tbl[0])); } - mxc_timer_init(&ipg_clk); + mxc_timer_init(&gpt_clk, IO_ADDRESS(GPT1_BASE_ADDR), MXC_INT_GPT); return 0; } +#define MXC_PMCR0_DVFS_MASK (MXC_CCM_PMCR0_DVSUP_MASK | \ + MXC_CCM_PMCR0_UDSC_MASK | \ + MXC_CCM_PMCR0_VSCNT_MASK | \ + MXC_CCM_PMCR0_DPVCR) + +#define MXC_PDR0_MAX_MCU_MASK (MXC_CCM_PDR0_MAX_PODF_MASK | \ + MXC_CCM_PDR0_MCU_PODF_MASK | \ + MXC_CCM_PDR0_HSP_PODF_MASK | \ + MXC_CCM_PDR0_IPG_PODF_MASK | \ + MXC_CCM_PDR0_NFC_PODF_MASK) + +static DEFINE_SPINLOCK(mxc_dfs_lock); + +static void dptcen_after_timeout(unsigned long ptr) +{ + unsigned long flags = 0; + + spin_lock_irqsave(&mxc_dfs_lock, flags); + + /* + * If DPTC is still active and core is running in Turbo mode + */ + if (dptcen_timer.data == cpu_wp_nr - 1) { + dptc_resume(DPTC_GP_ID); + } + spin_unlock_irqrestore(&mxc_dfs_lock, flags); +} + +/*! + * Setup cpu clock based on working point. + * @param wp cpu freq working point (0 is the slowest) + * @return 0 on success or error code on failure. + */ +static int cpu_clk_set_wp(int wp) +{ + struct cpu_wp *p; + u32 dvsup; + u32 pmcr0, pmcr1; + u32 pdr0; + u32 cgr2 = 0x80000000; + u32 vscnt = MXC_CCM_PMCR0_VSCNT_2; + u32 udsc = MXC_CCM_PMCR0_UDSC_DOWN; + void __iomem *ipu_base = IO_ADDRESS(IPU_CTRL_BASE_ADDR); + u32 ipu_conf; + + if (wp >= cpu_wp_nr || wp < 0) { + printk(KERN_ERR "Wrong wp: %d for cpu_clk_set_wp\n", wp); + return -EINVAL; + } + if (wp == cpu_curr_wp) { + return 0; + } + + pmcr0 = __raw_readl(MXC_CCM_PMCR0); + pmcr1 = __raw_readl(MXC_CCM_PMCR1); + pdr0 = __raw_readl(MXC_CCM_PDR0); + + if (!(pmcr0 & MXC_CCM_PMCR0_UPDTEN)) { + return -EBUSY; + } + + if (wp > cpu_curr_wp) { + /* going faster */ + if (wp == (cpu_wp_nr - 1)) { + /* Only update vscnt going into Turbo */ + vscnt = MXC_CCM_PMCR0_VSCNT_8; + } + udsc = MXC_CCM_PMCR0_UDSC_UP; + } + + p = &cpu_wp_tbl[wp]; + + dvsup = (cpu_wp_nr - 1 - wp) << MXC_CCM_PMCR0_DVSUP_OFFSET; + + if ((mcu_main_clk.rate == 399000000) && (p->cpu_rate == 532000000)) { + cgr2 = __raw_readl(MXC_CCM_CGR2); + cgr2 &= 0x7fffffff; + vscnt = 0; + pmcr0 = (pmcr0 & ~MXC_PMCR0_DVFS_MASK) | dvsup | vscnt; + pr_debug("manul dvfs, dvsup = %x\n", dvsup); + __raw_writel(cgr2, MXC_CCM_CGR2); + __raw_writel(pmcr0, MXC_CCM_PMCR0); + udelay(100); + } + + if (mcu_main_clk.rate == p->pll_rate) { + /* No pll switching and relocking needed */ + pmcr0 |= MXC_CCM_PMCR0_DFSUP0_PDR; + } else { + /* pll switching and relocking needed */ + pmcr0 ^= MXC_CCM_PMCR0_DFSUP1; /* flip MSB bit */ + pmcr0 &= ~(MXC_CCM_PMCR0_DFSUP0); + } + + pmcr0 = (pmcr0 & ~MXC_PMCR0_DVFS_MASK) | dvsup | vscnt | udsc; + /* also enable DVFS hardware */ + pmcr0 |= MXC_CCM_PMCR0_DVFEN; + + __raw_writel(pmcr0, MXC_CCM_PMCR0); + + /* IPU and DI submodule must be on for PDR0 update to take effect */ + if (!clk_get_usecount(&ipu_clk)) + ipu_clk.enable(&ipu_clk); + ipu_conf = __raw_readl(ipu_base); + if (!(ipu_conf & 0x40)) + __raw_writel(ipu_conf | 0x40, ipu_base); + + __raw_writel((pdr0 & ~MXC_PDR0_MAX_MCU_MASK) | p->pdr0_reg, + MXC_CCM_PDR0); + + if ((pmcr0 & MXC_CCM_PMCR0_DFSUP0) == MXC_CCM_PMCR0_DFSUP0_PLL) { + /* prevent pll restart */ + pmcr1 |= 0x80; + __raw_writel(pmcr1, MXC_CCM_PMCR1); + /* PLL and post divider update */ + if ((pmcr0 & MXC_CCM_PMCR0_DFSUP1) == MXC_CCM_PMCR0_DFSUP1_SPLL) { + __raw_writel(p->pll_reg, MXC_CCM_SRPCTL); + serial_pll_clk.rate = p->pll_rate; + mcu_main_clk.parent = &serial_pll_clk; + } else { + __raw_writel(p->pll_reg, MXC_CCM_MPCTL); + mcu_pll_clk.rate = p->pll_rate; + mcu_main_clk.parent = &mcu_pll_clk; + } + } + + if ((cgr2 & 0x80000000) == 0x0) { + pr_debug("start auto dvfs\n"); + cgr2 |= 0x80000000; + __raw_writel(cgr2, MXC_CCM_CGR2); + } + + mcu_main_clk.rate = p->pll_rate; + cpu_clk.rate = p->cpu_rate; + + cpu_curr_wp = wp; + + /* Restore IPU_CONF setting */ + __raw_writel(ipu_conf, ipu_base); + if (!clk_get_usecount(&ipu_clk)) + ipu_clk.disable(&ipu_clk); + + if (wp == cpu_wp_nr - 1) { + init_timer(&dptcen_timer); + dptcen_timer.expires = jiffies + 2; + dptcen_timer.function = dptcen_after_timeout; + dptcen_timer.data = wp; + add_timer(&dptcen_timer); + } else { + dptc_suspend(DPTC_GP_ID); + } + + return 0; +} diff --git a/arch/arm/mach-mx3/cpu.c b/arch/arm/mach-mx3/cpu.c new file mode 100644 index 000000000000..c89dfab910c2 --- /dev/null +++ b/arch/arm/mach-mx3/cpu.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2001 Deep Blue Solutions Ltd. + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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. + * + */ + +/*! + * @file mach-mx3/cpu.c + * + * @brief This file contains the CPU initialization code. + * + * @ingroup MSL_MX31 + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <mach/hardware.h> +#include <linux/io.h> +#include <asm/hardware/cache-l2x0.h> + +/*! + * CPU initialization. It is called by fixup_mxc_board() + */ +void __init mxc_cpu_init(void) +{ + /* Setup Peripheral Port Remap register for AVIC */ + asm("ldr r0, =0xC0000015 \n\ + mcr p15, 0, r0, c15, c2, 4"); + if (!system_rev) { + mxc_set_system_rev(0x31, CHIP_REV_2_0); + } +} + +/*! + * Post CPU init code + * + * @return 0 always + */ +static int __init post_cpu_init(void) +{ + volatile unsigned long aips_reg; + + /* + * S/W workaround: Clear the off platform peripheral modules + * Supervisor Protect bit for SDMA to access them. + */ + __raw_writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x40)); + __raw_writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x44)); + __raw_writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x48)); + __raw_writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x4C)); + aips_reg = __raw_readl(IO_ADDRESS(AIPS1_BASE_ADDR + 0x50)); + aips_reg &= 0x00FFFFFF; + __raw_writel(aips_reg, IO_ADDRESS(AIPS1_BASE_ADDR + 0x50)); + + __raw_writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x40)); + __raw_writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x44)); + __raw_writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x48)); + __raw_writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x4C)); + aips_reg = __raw_readl(IO_ADDRESS(AIPS2_BASE_ADDR + 0x50)); + aips_reg &= 0x00FFFFFF; + __raw_writel(aips_reg, IO_ADDRESS(AIPS2_BASE_ADDR + 0x50)); + + return 0; +} + +postcore_initcall(post_cpu_init); diff --git a/arch/arm/mach-mx3/crm_regs.h b/arch/arm/mach-mx3/crm_regs.h index adfa3627ad84..04d4d7c12086 100644 --- a/arch/arm/mach-mx3/crm_regs.h +++ b/arch/arm/mach-mx3/crm_regs.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright (C) 2008 by Sascha Hauer <kernel@pengutronix.de> * * This program is free software; you can redistribute it and/or @@ -24,7 +24,7 @@ #define CKIH_CLK_FREQ_27MHZ 27000000 #define CKIL_CLK_FREQ 32768 -#define MXC_CCM_BASE IO_ADDRESS(CCM_BASE_ADDR) +#define MXC_CCM_BASE ((char *)IO_ADDRESS(CCM_BASE_ADDR)) /* Register addresses */ #define MXC_CCM_CCMR (MXC_CCM_BASE + 0x00) @@ -55,6 +55,7 @@ #define MXC_CCM_PDR2 (MXC_CCM_BASE + 0x64) /* Register bit definitions */ +#define MXC_CCM_CCMR_VSTBY (1 << 28) #define MXC_CCM_CCMR_WBEN (1 << 27) #define MXC_CCM_CCMR_CSCS (1 << 25) #define MXC_CCM_CCMR_PERCS (1 << 24) @@ -66,6 +67,7 @@ #define MXC_CCM_CCMR_LPM_MASK (0x3 << 14) #define MXC_CCM_CCMR_FIRS_OFFSET 11 #define MXC_CCM_CCMR_FIRS_MASK (0x3 << 11) +#define MXC_CCM_CCMR_WAMO (1 << 10) #define MXC_CCM_CCMR_UPE (1 << 9) #define MXC_CCM_CCMR_SPE (1 << 8) #define MXC_CCM_CCMR_MDS (1 << 7) @@ -91,6 +93,47 @@ #define MXC_CCM_PDR0_MCU_PODF_OFFSET 0 #define MXC_CCM_PDR0_MCU_PODF_MASK 0x7 +#define MXC_CCM_PDR0_HSP_DIV_1 (0x0 << 11) +#define MXC_CCM_PDR0_HSP_DIV_2 (0x1 << 11) +#define MXC_CCM_PDR0_HSP_DIV_3 (0x2 << 11) +#define MXC_CCM_PDR0_HSP_DIV_4 (0x3 << 11) +#define MXC_CCM_PDR0_HSP_DIV_5 (0x4 << 11) +#define MXC_CCM_PDR0_HSP_DIV_6 (0x5 << 11) +#define MXC_CCM_PDR0_HSP_DIV_7 (0x6 << 11) +#define MXC_CCM_PDR0_HSP_DIV_8 (0x7 << 11) + +#define MXC_CCM_PDR0_IPG_DIV_1 (0x0 << 6) +#define MXC_CCM_PDR0_IPG_DIV_2 (0x1 << 6) +#define MXC_CCM_PDR0_IPG_DIV_3 (0x2 << 6) +#define MXC_CCM_PDR0_IPG_DIV_4 (0x3 << 6) + +#define MXC_CCM_PDR0_MAX_DIV_1 (0x0 << 3) +#define MXC_CCM_PDR0_MAX_DIV_2 (0x1 << 3) +#define MXC_CCM_PDR0_MAX_DIV_3 (0x2 << 3) +#define MXC_CCM_PDR0_MAX_DIV_4 (0x3 << 3) +#define MXC_CCM_PDR0_MAX_DIV_5 (0x4 << 3) +#define MXC_CCM_PDR0_MAX_DIV_6 (0x5 << 3) +#define MXC_CCM_PDR0_MAX_DIV_7 (0x6 << 3) +#define MXC_CCM_PDR0_MAX_DIV_8 (0x7 << 3) + +#define MXC_CCM_PDR0_NFC_DIV_1 (0x0 << 8) +#define MXC_CCM_PDR0_NFC_DIV_2 (0x1 << 8) +#define MXC_CCM_PDR0_NFC_DIV_3 (0x2 << 8) +#define MXC_CCM_PDR0_NFC_DIV_4 (0x3 << 8) +#define MXC_CCM_PDR0_NFC_DIV_5 (0x4 << 8) +#define MXC_CCM_PDR0_NFC_DIV_6 (0x5 << 8) +#define MXC_CCM_PDR0_NFC_DIV_7 (0x6 << 8) +#define MXC_CCM_PDR0_NFC_DIV_8 (0x7 << 8) + +#define MXC_CCM_PDR0_MCU_DIV_1 0x0 +#define MXC_CCM_PDR0_MCU_DIV_2 0x1 +#define MXC_CCM_PDR0_MCU_DIV_3 0x2 +#define MXC_CCM_PDR0_MCU_DIV_4 0x3 +#define MXC_CCM_PDR0_MCU_DIV_5 0x4 +#define MXC_CCM_PDR0_MCU_DIV_6 0x5 +#define MXC_CCM_PDR0_MCU_DIV_7 0x6 +#define MXC_CCM_PDR0_MCU_DIV_8 0x7 + #define MXC_CCM_PDR1_USB_PRDF_OFFSET 30 #define MXC_CCM_PDR1_USB_PRDF_MASK (0x3 << 30) #define MXC_CCM_PDR1_USB_PODF_OFFSET 27 @@ -109,7 +152,120 @@ #define MXC_CCM_PDR1_SSI1_PODF_MASK 0x3F /* Bit definitions for RCSR */ -#define MXC_CCM_RCSR_NF16B 0x80000000 +#define MXC_CCM_RCSR_NF16B (0x1 << 31) +#define MXC_CCM_RCSR_NFMS (0x1 << 30) + +/* Bit definitions for both MCU, USB and SR PLL control registers */ +#define MXC_CCM_PCTL_BRM 0x80000000 +#define MXC_CCM_PCTL_PD_OFFSET 26 +#define MXC_CCM_PCTL_PD_MASK (0xF << 26) +#define MXC_CCM_PCTL_MFD_OFFSET 16 +#define MXC_CCM_PCTL_MFD_MASK (0x3FF << 16) +#define MXC_CCM_PCTL_MFI_OFFSET 10 +#define MXC_CCM_PCTL_MFI_MASK (0xF << 10) +#define MXC_CCM_PCTL_MFN_OFFSET 0 +#define MXC_CCM_PCTL_MFN_MASK 0x3FF + +#define MXC_CCM_CGR0_SD_MMC1_OFFSET 0 +#define MXC_CCM_CGR0_SD_MMC1_MASK (0x3 << 0) +#define MXC_CCM_CGR0_SD_MMC2_OFFSET 2 +#define MXC_CCM_CGR0_SD_MMC2_MASK (0x3 << 2) +#define MXC_CCM_CGR0_GPT_OFFSET 4 +#define MXC_CCM_CGR0_GPT_MASK (0x3 << 4) +#define MXC_CCM_CGR0_EPIT1_OFFSET 6 +#define MXC_CCM_CGR0_EPIT1_MASK (0x3 << 6) +#define MXC_CCM_CGR0_EPIT2_OFFSET 8 +#define MXC_CCM_CGR0_EPIT2_MASK (0x3 << 8) +#define MXC_CCM_CGR0_IIM_OFFSET 10 +#define MXC_CCM_CGR0_IIM_MASK (0x3 << 10) +#define MXC_CCM_CGR0_ATA_OFFSET 12 +#define MXC_CCM_CGR0_ATA_MASK (0x3 << 12) +#define MXC_CCM_CGR0_SDMA_OFFSET 14 +#define MXC_CCM_CGR0_SDMA_MASK (0x3 << 14) +#define MXC_CCM_CGR0_CSPI3_OFFSET 16 +#define MXC_CCM_CGR0_CSPI3_MASK (0x3 << 16) +#define MXC_CCM_CGR0_RNG_OFFSET 18 +#define MXC_CCM_CGR0_RNG_MASK (0x3 << 18) +#define MXC_CCM_CGR0_UART1_OFFSET 20 +#define MXC_CCM_CGR0_UART1_MASK (0x3 << 20) +#define MXC_CCM_CGR0_UART2_OFFSET 22 +#define MXC_CCM_CGR0_UART2_MASK (0x3 << 22) +#define MXC_CCM_CGR0_SSI1_OFFSET 24 +#define MXC_CCM_CGR0_SSI1_MASK (0x3 << 24) +#define MXC_CCM_CGR0_I2C1_OFFSET 26 +#define MXC_CCM_CGR0_I2C1_MASK (0x3 << 26) +#define MXC_CCM_CGR0_I2C2_OFFSET 28 +#define MXC_CCM_CGR0_I2C2_MASK (0x3 << 28) +#define MXC_CCM_CGR0_I2C3_OFFSET 30 +#define MXC_CCM_CGR0_I2C3_MASK (0x3 << 30) + +#define MXC_CCM_CGR1_HANTRO_OFFSET 0 +#define MXC_CCM_CGR1_HANTRO_MASK (0x3 << 0) +#define MXC_CCM_CGR1_MEMSTICK1_OFFSET 2 +#define MXC_CCM_CGR1_MEMSTICK1_MASK (0x3 << 2) +#define MXC_CCM_CGR1_MEMSTICK2_OFFSET 4 +#define MXC_CCM_CGR1_MEMSTICK2_MASK (0x3 << 4) +#define MXC_CCM_CGR1_CSI_OFFSET 6 +#define MXC_CCM_CGR1_CSI_MASK (0x3 << 6) +#define MXC_CCM_CGR1_RTC_OFFSET 8 +#define MXC_CCM_CGR1_RTC_MASK (0x3 << 8) +#define MXC_CCM_CGR1_WDOG_OFFSET 10 +#define MXC_CCM_CGR1_WDOG_MASK (0x3 << 10) +#define MXC_CCM_CGR1_PWM_OFFSET 12 +#define MXC_CCM_CGR1_PWM_MASK (0x3 << 12) +#define MXC_CCM_CGR1_SIM_OFFSET 14 +#define MXC_CCM_CGR1_SIM_MASK (0x3 << 14) +#define MXC_CCM_CGR1_ECT_OFFSET 16 +#define MXC_CCM_CGR1_ECT_MASK (0x3 << 16) +#define MXC_CCM_CGR1_USBOTG_OFFSET 18 +#define MXC_CCM_CGR1_USBOTG_MASK (0x3 << 18) +#define MXC_CCM_CGR1_KPP_OFFSET 20 +#define MXC_CCM_CGR1_KPP_MASK (0x3 << 20) +#define MXC_CCM_CGR1_IPU_OFFSET 22 +#define MXC_CCM_CGR1_IPU_MASK (0x3 << 22) +#define MXC_CCM_CGR1_UART3_OFFSET 24 +#define MXC_CCM_CGR1_UART3_MASK (0x3 << 24) +#define MXC_CCM_CGR1_UART4_OFFSET 26 +#define MXC_CCM_CGR1_UART4_MASK (0x3 << 26) +#define MXC_CCM_CGR1_UART5_OFFSET 28 +#define MXC_CCM_CGR1_UART5_MASK (0x3 << 28) +#define MXC_CCM_CGR1_OWIRE_OFFSET 30 +#define MXC_CCM_CGR1_OWIRE_MASK (0x3 << 30) + +#define MXC_CCM_CGR2_SSI2_OFFSET 0 +#define MXC_CCM_CGR2_SSI2_MASK (0x3 << 0) +#define MXC_CCM_CGR2_CSPI1_OFFSET 2 +#define MXC_CCM_CGR2_CSPI1_MASK (0x3 << 2) +#define MXC_CCM_CGR2_CSPI2_OFFSET 4 +#define MXC_CCM_CGR2_CSPI2_MASK (0x3 << 4) +#define MXC_CCM_CGR2_GACC_OFFSET 6 +#define MXC_CCM_CGR2_GACC_MASK (0x3 << 6) +#define MXC_CCM_CGR2_EMI_OFFSET 8 +#define MXC_CCM_CGR2_EMI_MASK (0x3 << 8) +#define MXC_CCM_CGR2_RTIC_OFFSET 10 +#define MXC_CCM_CGR2_RTIC_MASK (0x3 << 10) +#define MXC_CCM_CGR2_FIRI_OFFSET 12 +#define MXC_CCM_CGR2_FIRI_MASK (0x3 << 12) +#define MXC_CCM_CGR2_IPMUX1_OFFSET 14 +#define MXC_CCM_CGR2_IPMUX1_MASK (0x3 << 14) +#define MXC_CCM_CGR2_IPMUX2_OFFSET 16 +#define MXC_CCM_CGR2_IPMUX2_MASK (0x3 << 16) + +/* These new CGR2 bits are added in MX32 */ +#define MXC_CCM_CGR2_APMSYSCLKSEL_OFFSET 18 +#define MXC_CCM_CGR2_APMSYSCLKSEL_MASK (0x3 << 18) +#define MXC_CCM_CGR2_APMSSICLKSEL_OFFSET 20 +#define MXC_CCM_CGR2_APMSSICLKSEL_MASK (0x3 << 20) +#define MXC_CCM_CGR2_APMPERCLKSEL_OFFSET 22 +#define MXC_CCM_CGR2_APMPERCLKSEL_MASK (0x3 << 22) +#define MXC_CCM_CGR2_MXCCLKENSEL_OFFSET 24 +#define MXC_CCM_CGR2_MXCCLKENSEL_MASK (0x1 << 24) +#define MXC_CCM_CGR2_CHIKCAMPEN_OFFSET 25 +#define MXC_CCM_CGR2_CHIKCAMPEN_MASK (0x1 << 25) +#define MXC_CCM_CGR2_OVRVPUBUSY_OFFSET 26 +#define MXC_CCM_CGR2_OVRVPUBUSY_MASK (0x1 << 26) +#define MXC_CCM_CGR2_APMENA_OFFSET 30 +#define MXC_CCM_CGR2_AOMENA_MASK (0x1 << 30) /* * LTR0 register offsets diff --git a/arch/arm/mach-mx3/devices.c b/arch/arm/mach-mx3/devices.c index 9e87e08fb121..f4a3cdd502bd 100644 --- a/arch/arm/mach-mx3/devices.c +++ b/arch/arm/mach-mx3/devices.c @@ -1,434 +1,901 @@ /* - * Copyright 2006-2007 Freescale Semiconductor, Inc. All Rights Reserved. - * Copyright 2008 Sascha Hauer, kernel@pengutronix.de + * Author: MontaVista Software, Inc. + * <source@mvista.com> * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * Based on the OMAP devices.c * - * You 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. + * 2005 (c) MontaVista Software, Inc. This file is licensed under the + * terms of the GNU General Public License version 2. This program is + * licensed "as is" without any warranty of any kind, whether express + * or implied. + * + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. */ - -#include <linux/dma-mapping.h> #include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> #include <linux/platform_device.h> -#include <linux/serial.h> -#include <linux/gpio.h> +#include <linux/clk.h> +#include <linux/pmic_external.h> + +#include <linux/spi/spi.h> + +#include <asm/mach-types.h> #include <mach/hardware.h> -#include <mach/irqs.h> -#include <mach/common.h> -#include <mach/imx-uart.h> -#include <mach/mx3_camera.h> +#include <mach/pmic_power.h> +#include <mach/spba.h> +#include <mach/sdma.h> +#include <mach/mxc_dptc.h> +#include <mach/gpio.h> + +#include "iomux.h" +#include "crm_regs.h" +#include "sdma_script_code.h" +#include "sdma_script_code_pass2.h" + +extern struct dptc_wp dptc_wp_allfreq_26ckih[DPTC_WP_SUPPORTED]; +extern struct dptc_wp dptc_wp_allfreq_26ckih_TO_2_0[DPTC_WP_SUPPORTED]; +extern struct dptc_wp dptc_wp_allfreq_27ckih_TO_2_0[DPTC_WP_SUPPORTED]; +/* + * Clock structures + */ +static struct clk *ckih_clk; + +void mxc_sdma_get_script_info(sdma_script_start_addrs * sdma_script_addr) +{ + if (cpu_is_mx31_rev(CHIP_REV_1_0) == 1) { + sdma_script_addr->mxc_sdma_app_2_mcu_addr = app_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_ap_2_ap_addr = ap_2_ap_ADDR; + sdma_script_addr->mxc_sdma_ap_2_bp_addr = -1; + sdma_script_addr->mxc_sdma_bp_2_ap_addr = -1; + sdma_script_addr->mxc_sdma_loopback_on_dsp_side_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_app_addr = mcu_2_app_ADDR; + sdma_script_addr->mxc_sdma_mcu_2_shp_addr = mcu_2_shp_ADDR; + sdma_script_addr->mxc_sdma_mcu_interrupt_only_addr = -1; + sdma_script_addr->mxc_sdma_shp_2_mcu_addr = shp_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_start_addr = + (unsigned short *)sdma_code; + sdma_script_addr->mxc_sdma_uartsh_2_mcu_addr = + uartsh_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_uart_2_mcu_addr = uart_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_ram_code_size = RAM_CODE_SIZE; + sdma_script_addr->mxc_sdma_ram_code_start_addr = + RAM_CODE_START_ADDR; + sdma_script_addr->mxc_sdma_dptc_dvfs_addr = dptc_dvfs_ADDR; + sdma_script_addr->mxc_sdma_firi_2_mcu_addr = firi_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_firi_2_per_addr = -1; + sdma_script_addr->mxc_sdma_mshc_2_mcu_addr = mshc_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_per_2_app_addr = -1; + sdma_script_addr->mxc_sdma_per_2_firi_addr = -1; + sdma_script_addr->mxc_sdma_per_2_shp_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_ata_addr = mcu_2_ata_ADDR; + sdma_script_addr->mxc_sdma_mcu_2_firi_addr = mcu_2_firi_ADDR; + sdma_script_addr->mxc_sdma_mcu_2_mshc_addr = mcu_2_mshc_ADDR; + sdma_script_addr->mxc_sdma_ata_2_mcu_addr = ata_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_uartsh_2_per_addr = -1; + sdma_script_addr->mxc_sdma_shp_2_per_addr = -1; + sdma_script_addr->mxc_sdma_uart_2_per_addr = -1; + sdma_script_addr->mxc_sdma_app_2_per_addr = -1; + } else { + sdma_script_addr->mxc_sdma_app_2_mcu_addr = app_2_mcu_ADDR_2; + sdma_script_addr->mxc_sdma_ap_2_ap_addr = ap_2_ap_ADDR_2; + sdma_script_addr->mxc_sdma_ap_2_ap_fixed_addr = + ap_2_ap_fixed_addr_ADDR_2; + sdma_script_addr->mxc_sdma_ap_2_bp_addr = ap_2_bp_ADDR_2; + sdma_script_addr->mxc_sdma_ap_2_ap_fixed_addr = + ap_2_ap_fixed_addr_ADDR_2; + sdma_script_addr->mxc_sdma_bp_2_ap_addr = bp_2_ap_ADDR_2; + sdma_script_addr->mxc_sdma_loopback_on_dsp_side_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_app_addr = mcu_2_app_ADDR_2; + sdma_script_addr->mxc_sdma_mcu_2_shp_addr = mcu_2_shp_ADDR_2; + sdma_script_addr->mxc_sdma_mcu_interrupt_only_addr = -1; + sdma_script_addr->mxc_sdma_shp_2_mcu_addr = shp_2_mcu_ADDR_2; + sdma_script_addr->mxc_sdma_start_addr = + (unsigned short *)sdma_code_2; + sdma_script_addr->mxc_sdma_uartsh_2_mcu_addr = + uartsh_2_mcu_ADDR_2; + sdma_script_addr->mxc_sdma_uart_2_mcu_addr = uart_2_mcu_ADDR_2; + sdma_script_addr->mxc_sdma_ram_code_size = RAM_CODE_SIZE_2; + sdma_script_addr->mxc_sdma_ram_code_start_addr = + RAM_CODE_START_ADDR_2; + sdma_script_addr->mxc_sdma_dptc_dvfs_addr = -1; + sdma_script_addr->mxc_sdma_firi_2_mcu_addr = firi_2_mcu_ADDR_2; + sdma_script_addr->mxc_sdma_firi_2_per_addr = -1; + sdma_script_addr->mxc_sdma_mshc_2_mcu_addr = mshc_2_mcu_ADDR_2; + sdma_script_addr->mxc_sdma_per_2_app_addr = per_2_app_ADDR_2; + sdma_script_addr->mxc_sdma_per_2_firi_addr = -1; + sdma_script_addr->mxc_sdma_per_2_shp_addr = per_2_shp_ADDR_2; + sdma_script_addr->mxc_sdma_mcu_2_ata_addr = mcu_2_ata_ADDR_2; + sdma_script_addr->mxc_sdma_mcu_2_firi_addr = mcu_2_firi_ADDR_2; + sdma_script_addr->mxc_sdma_mcu_2_mshc_addr = mcu_2_mshc_ADDR_2; + sdma_script_addr->mxc_sdma_ata_2_mcu_addr = ata_2_mcu_ADDR_2; + sdma_script_addr->mxc_sdma_uartsh_2_per_addr = -1; + sdma_script_addr->mxc_sdma_shp_2_per_addr = shp_2_per_ADDR_2; + sdma_script_addr->mxc_sdma_uart_2_per_addr = -1; + sdma_script_addr->mxc_sdma_app_2_per_addr = app_2_per_ADDR_2; + } +} + +static void mxc_nop_release(struct device *dev) +{ + /* Nothing */ +} + +#if defined(CONFIG_W1_MASTER_MXC) || defined(CONFIG_W1_MASTER_MXC_MODULE) +static struct mxc_w1_config mxc_w1_data = { + .search_rom_accelerator = 0, +}; -#include "devices.h" +static struct platform_device mxc_w1_devices = { + .name = "mxc_w1", + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_w1_data, + }, + .id = 0 +}; + +static void mxc_init_owire(void) +{ + (void)platform_device_register(&mxc_w1_devices); +} +#else +static inline void mxc_init_owire(void) +{ +} +#endif -static struct resource uart0[] = { +#if defined(CONFIG_RTC_MXC) || defined(CONFIG_RTC_MXC_MODULE) +static struct resource rtc_resources[] = { { - .start = UART1_BASE_ADDR, - .end = UART1_BASE_ADDR + 0x0B5, - .flags = IORESOURCE_MEM, - }, { - .start = MXC_INT_UART1, - .end = MXC_INT_UART1, - .flags = IORESOURCE_IRQ, - }, -}; - -struct platform_device mxc_uart_device0 = { - .name = "imx-uart", + .start = RTC_BASE_ADDR, + .end = RTC_BASE_ADDR + 0x30, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_INT_RTC, + .flags = IORESOURCE_IRQ, + }, +}; +static struct platform_device mxc_rtc_device = { + .name = "mxc_rtc", .id = 0, - .resource = uart0, - .num_resources = ARRAY_SIZE(uart0), + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(rtc_resources), + .resource = rtc_resources, }; +static void mxc_init_rtc(void) +{ + (void)platform_device_register(&mxc_rtc_device); +} +#else +static inline void mxc_init_rtc(void) +{ +} +#endif -static struct resource uart1[] = { +#if defined(CONFIG_MXC_WATCHDOG) || defined(CONFIG_MXC_WATCHDOG_MODULE) + +static struct resource wdt_resources[] = { { - .start = UART2_BASE_ADDR, - .end = UART2_BASE_ADDR + 0x0B5, - .flags = IORESOURCE_MEM, - }, { - .start = MXC_INT_UART2, - .end = MXC_INT_UART2, - .flags = IORESOURCE_IRQ, - }, -}; - -struct platform_device mxc_uart_device1 = { - .name = "imx-uart", - .id = 1, - .resource = uart1, - .num_resources = ARRAY_SIZE(uart1), + .start = WDOG1_BASE_ADDR, + .end = WDOG1_BASE_ADDR + 0x30, + .flags = IORESOURCE_MEM, + }, }; -static struct resource uart2[] = { - { - .start = UART3_BASE_ADDR, - .end = UART3_BASE_ADDR + 0x0B5, - .flags = IORESOURCE_MEM, - }, { - .start = MXC_INT_UART3, - .end = MXC_INT_UART3, - .flags = IORESOURCE_IRQ, - }, -}; - -struct platform_device mxc_uart_device2 = { - .name = "imx-uart", - .id = 2, - .resource = uart2, - .num_resources = ARRAY_SIZE(uart2), +static struct platform_device mxc_wdt_device = { + .name = "mxc_wdt", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(wdt_resources), + .resource = wdt_resources, }; -#ifdef CONFIG_ARCH_MX31 -static struct resource uart3[] = { +static void mxc_init_wdt(void) +{ + (void)platform_device_register(&mxc_wdt_device); +} +#else +static inline void mxc_init_wdt(void) +{ +} +#endif + +#if defined(CONFIG_MXC_IPU) || defined(CONFIG_MXC_IPU_MODULE) +static struct mxc_ipu_config mxc_ipu_data = { + .rev = 1, +}; + +static struct resource ipu_resources[] = { + { + .start = IPU_CTRL_BASE_ADDR, + .end = IPU_CTRL_BASE_ADDR + SZ_4K, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_INT_IPU_SYN, + .flags = IORESOURCE_IRQ, + }, { - .start = UART4_BASE_ADDR, - .end = UART4_BASE_ADDR + 0x0B5, - .flags = IORESOURCE_MEM, - }, { - .start = MXC_INT_UART4, - .end = MXC_INT_UART4, - .flags = IORESOURCE_IRQ, - }, + .start = MXC_INT_IPU_ERR, + .flags = IORESOURCE_IRQ, + }, }; -struct platform_device mxc_uart_device3 = { - .name = "imx-uart", - .id = 3, - .resource = uart3, - .num_resources = ARRAY_SIZE(uart3), +static struct platform_device mxc_ipu_device = { + .name = "mxc_ipu", + .id = -1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_ipu_data, + }, + .num_resources = ARRAY_SIZE(ipu_resources), + .resource = ipu_resources, }; -static struct resource uart4[] = { - { - .start = UART5_BASE_ADDR, - .end = UART5_BASE_ADDR + 0x0B5, - .flags = IORESOURCE_MEM, - }, { - .start = MXC_INT_UART5, - .end = MXC_INT_UART5, - .flags = IORESOURCE_IRQ, - }, -}; - -struct platform_device mxc_uart_device4 = { - .name = "imx-uart", - .id = 4, - .resource = uart4, - .num_resources = ARRAY_SIZE(uart4), -}; -#endif /* CONFIG_ARCH_MX31 */ - -/* GPIO port description */ -static struct mxc_gpio_port imx_gpio_ports[] = { - [0] = { - .chip.label = "gpio-0", - .base = IO_ADDRESS(GPIO1_BASE_ADDR), - .irq = MXC_INT_GPIO1, - .virtual_irq_start = MXC_GPIO_IRQ_START, - }, - [1] = { - .chip.label = "gpio-1", - .base = IO_ADDRESS(GPIO2_BASE_ADDR), - .irq = MXC_INT_GPIO2, - .virtual_irq_start = MXC_GPIO_IRQ_START + 32, - }, - [2] = { - .chip.label = "gpio-2", - .base = IO_ADDRESS(GPIO3_BASE_ADDR), - .irq = MXC_INT_GPIO3, - .virtual_irq_start = MXC_GPIO_IRQ_START + 64, - } +static void mxc_init_ipu(void) +{ + platform_device_register(&mxc_ipu_device); +} +#else +static inline void mxc_init_ipu(void) +{ +} +#endif + +#if defined(CONFIG_SND_MXC_PMIC) || defined(CONFIG_SND_MXC_PMIC_MODULE) +static struct mxc_audio_platform_data mxc_audio_data; + +static struct platform_device mxc_alsa_device = { + .name = "mxc_alsa", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_audio_data, + }, + }; -int __init mxc_register_gpios(void) +static void mxc_init_audio(void) { - return mxc_gpio_init(imx_gpio_ports, ARRAY_SIZE(imx_gpio_ports)); + struct clk *pll_clk; + pll_clk = clk_get(NULL, "usb_pll"); + mxc_audio_data.ssi_clk[0] = clk_get(NULL, "ssi_clk.0"); + clk_set_parent(mxc_audio_data.ssi_clk[0], pll_clk); + clk_put(mxc_audio_data.ssi_clk[0]); + if (machine_is_mx31_3ds()) { + mxc_audio_data.ssi_num = 1; + } else { + mxc_audio_data.ssi_num = 2; + mxc_audio_data.ssi_clk[1] = clk_get(NULL, "ssi_clk.1"); + clk_set_parent(mxc_audio_data.ssi_clk[1], pll_clk); + clk_put(mxc_audio_data.ssi_clk[1]); + } + clk_put(pll_clk); + mxc_audio_data.src_port = 0; + platform_device_register(&mxc_alsa_device); } +#else -static struct resource mxc_w1_master_resources[] = { - { - .start = OWIRE_BASE_ADDR, - .end = OWIRE_BASE_ADDR + SZ_4K - 1, - .flags = IORESOURCE_MEM, - }, +static void mxc_init_audio(void) +{ +} + +#endif + +#if defined(CONFIG_MXC_SSI) || defined(CONFIG_MXC_SSI_MODULE) +/*! + * Resource definition for the SSI + */ +static struct resource mxcssi2_resources[] = { + [0] = { + .start = SSI2_BASE_ADDR, + .end = SSI2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, }; -struct platform_device mxc_w1_master_device = { - .name = "mxc_w1", +static struct resource mxcssi1_resources[] = { + [0] = { + .start = SSI1_BASE_ADDR, + .end = SSI1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +/*! Device Definition for MXC SSI */ +static struct platform_device mxc_ssi1_device = { + .name = "mxc_ssi", .id = 0, - .num_resources = ARRAY_SIZE(mxc_w1_master_resources), - .resource = mxc_w1_master_resources, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_audio_data, + }, + .num_resources = ARRAY_SIZE(mxcssi1_resources), + .resource = mxcssi1_resources, }; -static struct resource mxc_nand_resources[] = { - { - .start = 0, /* runtime dependent */ - .end = 0, - .flags = IORESOURCE_MEM - }, { - .start = MXC_INT_NANDFC, - .end = MXC_INT_NANDFC, - .flags = IORESOURCE_IRQ - }, -}; - -struct platform_device mxc_nand_device = { - .name = "mxc_nand", +static struct platform_device mxc_ssi2_device = { + .name = "mxc_ssi", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_audio_data, + }, + .num_resources = ARRAY_SIZE(mxcssi2_resources), + .resource = mxcssi2_resources, +}; + +static void mxc_init_ssi(void) +{ + platform_device_register(&mxc_ssi1_device); + platform_device_register(&mxc_ssi2_device); +} +#else + +static void mxc_init_ssi(void) +{ +} +#endif + +/*! + * This is platform device structure for adding SCC + */ +#if defined(CONFIG_MXC_SECURITY_SCC) || defined(CONFIG_MXC_SECURITY_SCC_MODULE) +static struct platform_device mxc_scc_device = { + .name = "mxc_scc", .id = 0, - .num_resources = ARRAY_SIZE(mxc_nand_resources), - .resource = mxc_nand_resources, }; -static struct resource mxc_i2c0_resources[] = { - { - .start = I2C_BASE_ADDR, - .end = I2C_BASE_ADDR + SZ_4K - 1, - .flags = IORESOURCE_MEM, - }, - { - .start = MXC_INT_I2C, - .end = MXC_INT_I2C, - .flags = IORESOURCE_IRQ, - }, +static void mxc_init_scc(void) +{ + platform_device_register(&mxc_scc_device); +} +#else +static inline void mxc_init_scc(void) +{ +} +#endif + +/* SPI controller and device data */ +#if defined(CONFIG_SPI_MXC) || defined(CONFIG_SPI_MXC_MODULE) + +#ifdef CONFIG_SPI_MXC_SELECT1 +/*! + * Resource definition for the CSPI1 + */ +static struct resource mxcspi1_resources[] = { + [0] = { + .start = CSPI1_BASE_ADDR, + .end = CSPI1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CSPI1, + .end = MXC_INT_CSPI1, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC CSPI1 */ +static struct mxc_spi_master mxcspi1_data = { + .maxchipselect = 4, + .spi_version = 4, }; -struct platform_device mxc_i2c_device0 = { - .name = "imx-i2c", +/*! Device Definition for MXC CSPI1 */ +static struct platform_device mxcspi1_device = { + .name = "mxc_spi", .id = 0, - .num_resources = ARRAY_SIZE(mxc_i2c0_resources), - .resource = mxc_i2c0_resources, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxcspi1_data, + }, + .num_resources = ARRAY_SIZE(mxcspi1_resources), + .resource = mxcspi1_resources, }; -static struct resource mxc_i2c1_resources[] = { - { - .start = I2C2_BASE_ADDR, - .end = I2C2_BASE_ADDR + SZ_4K - 1, - .flags = IORESOURCE_MEM, - }, - { - .start = MXC_INT_I2C2, - .end = MXC_INT_I2C2, - .flags = IORESOURCE_IRQ, - }, +#endif /* CONFIG_SPI_MXC_SELECT1 */ + +#ifdef CONFIG_SPI_MXC_SELECT2 +/*! + * Resource definition for the CSPI2 + */ +static struct resource mxcspi2_resources[] = { + [0] = { + .start = CSPI2_BASE_ADDR, + .end = CSPI2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CSPI2, + .end = MXC_INT_CSPI2, + .flags = IORESOURCE_IRQ, + }, }; -struct platform_device mxc_i2c_device1 = { - .name = "imx-i2c", +/*! Platform Data for MXC CSPI2 */ +static struct mxc_spi_master mxcspi2_data = { + .maxchipselect = 4, + .spi_version = 4, +}; + +/*! Device Definition for MXC CSPI2 */ +static struct platform_device mxcspi2_device = { + .name = "mxc_spi", .id = 1, - .num_resources = ARRAY_SIZE(mxc_i2c1_resources), - .resource = mxc_i2c1_resources, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxcspi2_data, + }, + .num_resources = ARRAY_SIZE(mxcspi2_resources), + .resource = mxcspi2_resources, +}; +#endif /* CONFIG_SPI_MXC_SELECT2 */ + +#ifdef CONFIG_SPI_MXC_SELECT3 +/*! + * Resource definition for the CSPI3 + */ +static struct resource mxcspi3_resources[] = { + [0] = { + .start = CSPI3_BASE_ADDR, + .end = CSPI3_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CSPI3, + .end = MXC_INT_CSPI3, + .flags = IORESOURCE_IRQ, + }, }; -static struct resource mxc_i2c2_resources[] = { - { - .start = I2C3_BASE_ADDR, - .end = I2C3_BASE_ADDR + SZ_4K - 1, - .flags = IORESOURCE_MEM, - }, - { - .start = MXC_INT_I2C3, - .end = MXC_INT_I2C3, - .flags = IORESOURCE_IRQ, - }, +/*! Platform Data for MXC CSPI3 */ +static struct mxc_spi_master mxcspi3_data = { + .maxchipselect = 4, + .spi_version = 4, }; -struct platform_device mxc_i2c_device2 = { - .name = "imx-i2c", +/*! Device Definition for MXC CSPI3 */ +static struct platform_device mxcspi3_device = { + .name = "mxc_spi", .id = 2, - .num_resources = ARRAY_SIZE(mxc_i2c2_resources), - .resource = mxc_i2c2_resources, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxcspi3_data, + }, + .num_resources = ARRAY_SIZE(mxcspi3_resources), + .resource = mxcspi3_resources, }; +#endif /* CONFIG_SPI_MXC_SELECT3 */ -#ifdef CONFIG_ARCH_MX31 -static struct resource mxcsdhc0_resources[] = { - { - .start = MMC_SDHC1_BASE_ADDR, - .end = MMC_SDHC1_BASE_ADDR + SZ_16K - 1, - .flags = IORESOURCE_MEM, - }, { - .start = MXC_INT_MMC_SDHC1, - .end = MXC_INT_MMC_SDHC1, - .flags = IORESOURCE_IRQ, - }, +static inline void mxc_init_spi(void) +{ + /* SPBA configuration for CSPI2 - MCU is set */ + spba_take_ownership(SPBA_CSPI2, SPBA_MASTER_A); +#ifdef CONFIG_SPI_MXC_SELECT1 + if (platform_device_register(&mxcspi1_device) < 0) + printk("Error: Registering the SPI Controller_1\n"); +#endif /* CONFIG_SPI_MXC_SELECT1 */ +#ifdef CONFIG_SPI_MXC_SELECT2 + if (platform_device_register(&mxcspi2_device) < 0) + printk("Error: Registering the SPI Controller_2\n"); +#endif /* CONFIG_SPI_MXC_SELECT2 */ +#ifdef CONFIG_SPI_MXC_SELECT3 + if (platform_device_register(&mxcspi3_device) < 0) + printk("Error: Registering the SPI Controller_3\n"); +#endif /* CONFIG_SPI_MXC_SELECT3 */ +} +#else +static inline void mxc_init_spi(void) +{ +} +#endif + +/* I2C controller and device data */ +#if defined(CONFIG_I2C_MXC) || defined(CONFIG_I2C_MXC_MODULE) + +#ifdef CONFIG_I2C_MXC_SELECT1 +/*! + * Resource definition for the I2C1 + */ +static struct resource mxci2c1_resources[] = { + [0] = { + .start = I2C_BASE_ADDR, + .end = I2C_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C, + .end = MXC_INT_I2C, + .flags = IORESOURCE_IRQ, + }, }; -static struct resource mxcsdhc1_resources[] = { - { - .start = MMC_SDHC2_BASE_ADDR, - .end = MMC_SDHC2_BASE_ADDR + SZ_16K - 1, - .flags = IORESOURCE_MEM, - }, { - .start = MXC_INT_MMC_SDHC2, - .end = MXC_INT_MMC_SDHC2, - .flags = IORESOURCE_IRQ, - }, -}; - -struct platform_device mxcsdhc_device0 = { - .name = "mxc-mmc", - .id = 0, - .num_resources = ARRAY_SIZE(mxcsdhc0_resources), - .resource = mxcsdhc0_resources, +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c1_data = { + .i2c_clk = 100000, }; +#endif -struct platform_device mxcsdhc_device1 = { - .name = "mxc-mmc", - .id = 1, - .num_resources = ARRAY_SIZE(mxcsdhc1_resources), - .resource = mxcsdhc1_resources, +#ifdef CONFIG_I2C_MXC_SELECT2 +/*! + * Resource definition for the I2C2 + */ +static struct resource mxci2c2_resources[] = { + [0] = { + .start = I2C2_BASE_ADDR, + .end = I2C2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C2, + .end = MXC_INT_I2C2, + .flags = IORESOURCE_IRQ, + }, }; -static struct resource rnga_resources[] = { - { - .start = RNGA_BASE_ADDR, - .end = RNGA_BASE_ADDR + 0x28, - .flags = IORESOURCE_MEM, - }, +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c2_data = { + .i2c_clk = 100000, }; +#endif -struct platform_device mxc_rnga_device = { - .name = "mxc_rnga", - .id = -1, - .num_resources = 1, - .resource = rnga_resources, +#ifdef CONFIG_I2C_MXC_SELECT3 +/*! + * Resource definition for the I2C3 + */ +static struct resource mxci2c3_resources[] = { + [0] = { + .start = I2C3_BASE_ADDR, + .end = I2C3_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C3, + .end = MXC_INT_I2C3, + .flags = IORESOURCE_IRQ, + }, }; -#endif /* CONFIG_ARCH_MX31 */ -/* i.MX31 Image Processing Unit */ +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c3_data = { + .i2c_clk = 100000, +}; +#endif -/* The resource order is important! */ -static struct resource mx3_ipu_rsrc[] = { +/*! Device Definition for MXC I2C1 */ +static struct platform_device mxci2c_devices[] = { +#ifdef CONFIG_I2C_MXC_SELECT1 { - .start = IPU_CTRL_BASE_ADDR, - .end = IPU_CTRL_BASE_ADDR + 0x5F, - .flags = IORESOURCE_MEM, - }, { - .start = IPU_CTRL_BASE_ADDR + 0x88, - .end = IPU_CTRL_BASE_ADDR + 0xB3, - .flags = IORESOURCE_MEM, - }, { - .start = MXC_INT_IPU_SYN, - .end = MXC_INT_IPU_SYN, - .flags = IORESOURCE_IRQ, - }, { - .start = MXC_INT_IPU_ERR, - .end = MXC_INT_IPU_ERR, - .flags = IORESOURCE_IRQ, - }, -}; - -struct platform_device mx3_ipu = { - .name = "ipu-core", - .id = -1, - .num_resources = ARRAY_SIZE(mx3_ipu_rsrc), - .resource = mx3_ipu_rsrc, + .name = "mxc_i2c", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c1_data, + }, + .num_resources = ARRAY_SIZE(mxci2c1_resources), + .resource = mxci2c1_resources,}, +#endif +#ifdef CONFIG_I2C_MXC_SELECT2 + { + .name = "mxc_i2c", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c2_data, + }, + .num_resources = ARRAY_SIZE(mxci2c2_resources), + .resource = mxci2c2_resources,}, +#endif +#ifdef CONFIG_I2C_MXC_SELECT3 + { + .name = "mxc_i2c", + .id = 2, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c3_data, + }, + .num_resources = ARRAY_SIZE(mxci2c3_resources), + .resource = mxci2c3_resources,}, +#endif }; -static struct resource fb_resources[] = { - { - .start = IPU_CTRL_BASE_ADDR + 0xB4, - .end = IPU_CTRL_BASE_ADDR + 0x1BF, - .flags = IORESOURCE_MEM, - }, +static inline void mxc_init_i2c(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mxci2c_devices); i++) { + if (platform_device_register(&mxci2c_devices[i]) < 0) + dev_err(&mxci2c_devices[i].dev, + "Unable to register I2C device\n"); + } +} +#else +static inline void mxc_init_i2c(void) +{ +} +#endif + +struct mxc_gpio_port mxc_gpio_ports[] = { + [0] = { + .chip.label = "gpio-0", + .base = IO_ADDRESS(GPIO1_BASE_ADDR), + .irq = MXC_INT_GPIO1, + .irq_high = 0, + .virtual_irq_start = MXC_GPIO_IRQ_START, + }, + [1] = { + .chip.label = "gpio-1", + .base = IO_ADDRESS(GPIO2_BASE_ADDR), + .irq = MXC_INT_GPIO2, + .irq_high = 0, + .virtual_irq_start = MXC_GPIO_IRQ_START + 32, + }, + [2] = { + .chip.label = "gpio-2", + .base = IO_ADDRESS(GPIO3_BASE_ADDR), + .irq = MXC_INT_GPIO3, + .irq_high = 0, + .virtual_irq_start = MXC_GPIO_IRQ_START + 64, + } }; -struct platform_device mx3_fb = { - .name = "mx3_sdc_fb", - .id = -1, - .num_resources = ARRAY_SIZE(fb_resources), - .resource = fb_resources, - .dev = { - .coherent_dma_mask = DMA_BIT_MASK(32), - }, +int __init mxc_register_gpios(void) +{ + return mxc_gpio_init(mxc_gpio_ports, ARRAY_SIZE(mxc_gpio_ports)); +} + +#if defined(CONFIG_PCMCIA_MX31ADS) || defined(CONFIG_PCMCIA_MX31ADS_MODULE) + +static struct platform_device mx31ads_device = { + .name = "Mx31ads_pcmcia_socket", + .id = 0, + .dev.release = mxc_nop_release, +}; +static inline void mxc_init_pcmcia(void) +{ + platform_device_register(&mx31ads_device); +} +#else +static inline void mxc_init_pcmcia(void) +{ +} +#endif + +#if defined(CONFIG_MXC_HMP4E) || defined(CONFIG_MXC_HMP4E_MODULE) +static struct platform_device hmp4e_device = { + .name = "mxc_hmp4e", + .id = 0, + .dev = { + .release = mxc_nop_release, + } }; -static struct resource camera_resources[] = { +static inline void mxc_init_hmp4e(void) +{ + void __iomem *iim_reg = IO_ADDRESS(IIM_BASE_ADDR); + if (cpu_is_mx32()) + return; + + /* override fuse for Hantro HW clock */ + if (__raw_readl(iim_reg + 0x808) == 0x4) { + if (!(__raw_readl(iim_reg + 0x800) & (1 << 5))) { + writel(__raw_readl(iim_reg + 0x808) & 0xfffffffb, + iim_reg + 0x808); + } + } + + platform_device_register(&hmp4e_device); +} +#else +static inline void mxc_init_hmp4e(void) +{ +} +#endif + +static struct platform_device mxc_dma_device = { + .name = "mxc_dma", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, +}; + +static inline void mxc_init_dma(void) +{ + (void)platform_device_register(&mxc_dma_device); +} + +/*! + * Resource definition for the DPTC LP + */ +static struct resource dptc_resources[] = { + [0] = { + .start = CCM_BASE_ADDR, + .end = CCM_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CCM, + .end = MXC_INT_CCM, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for DPTC */ +static struct mxc_dptc_data dptc_data = { + .reg_id = "SW1A", + .clk_id = "cpu_clk", + .dptccr_reg_addr = (unsigned int)MXC_CCM_PMCR0, + .dcvr0_reg_addr = (int)MXC_CCM_DCVR0, + .gpc_cntr_reg_addr = (int)MXC_CCM_PMCR0, + .dptccr = 0xFFFFFFFF, + .dptc_wp_supported = DPTC_WP_SUPPORTED, + .dptc_wp_allfreq = dptc_wp_allfreq_26ckih, + .clk_max_val = 532000000, + .gpc_adu = 0x0, + .vai_mask = MXC_CCM_PMCR0_PTVAI_MASK, + .vai_offset = MXC_CCM_PMCR0_PTVAI_OFFSET, + .dptc_enable_bit = MXC_CCM_PMCR0_DPTEN, + .irq_mask = MXC_CCM_PMCR0_PTVAIM, + .dptc_nvcr_bit = 0x0, + .gpc_irq_bit = 0x00000000, + .init_config = + MXC_CCM_PMCR0_PTVIS | MXC_CCM_PMCR0_DRCE3 | MXC_CCM_PMCR0_DRCE1, + .enable_config = + MXC_CCM_PMCR0_DPTEN | MXC_CCM_PMCR0_DPVCR | MXC_CCM_PMCR0_DPVV, + .dcr_mask = MXC_CCM_PMCR0_DCR, +}; + +/*! Device Definition for MXC DPTC */ +static struct platform_device mxc_dptc_device = { + .name = "mxc_dptc", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &dptc_data, + }, + .num_resources = ARRAY_SIZE(dptc_resources), + .resource = dptc_resources, +}; + +static inline void mxc_init_dptc(void) +{ + if (clk_get_rate(ckih_clk) == 27000000) { + + if (mxc_cpu_is_rev(CHIP_REV_2_0) < 0) + dptc_data.dptc_wp_allfreq = NULL; + else + dptc_data.dptc_wp_allfreq = + dptc_wp_allfreq_27ckih_TO_2_0; + + } else if (clk_get_rate(ckih_clk) == 26000000 + && mxc_cpu_is_rev(CHIP_REV_2_0) == 1) { + dptc_data.dptc_wp_allfreq = dptc_wp_allfreq_26ckih_TO_2_0; + } + + (void)platform_device_register(&mxc_dptc_device); +} + +#ifdef CONFIG_MXC_VPU +static struct resource vpu_resources[] = { { - .start = IPU_CTRL_BASE_ADDR + 0x60, - .end = IPU_CTRL_BASE_ADDR + 0x87, - .flags = IORESOURCE_MEM, - }, + .start = VL2CC_BASE_ADDR, + .end = VL2CC_BASE_ADDR + SZ_8K - 1, + .flags = IORESOURCE_MEM, + }, }; -struct platform_device mx3_camera = { - .name = "mx3-camera", - .id = 0, - .num_resources = ARRAY_SIZE(camera_resources), - .resource = camera_resources, - .dev = { - .coherent_dma_mask = DMA_BIT_MASK(32), - }, +/*! Platform Data for MXC VPU */ +static struct platform_device mxcvpu_device = { + .name = "mxc_vpu", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(vpu_resources), + .resource = vpu_resources, }; -static struct resource otg_resources[] = { +static inline void mxc_init_vpu(void) +{ + if (cpu_is_mx32()) { + if (platform_device_register(&mxcvpu_device) < 0) + printk(KERN_ERR "Error: Registering the VPU.\n"); + } +} +#else +static inline void mxc_init_vpu(void) +{ +} +#endif + +#if defined(CONFIG_HW_RANDOM_FSL_RNGA) || \ +defined(CONFIG_HW_RANDOM_FSL_RNGA_MODULE) +static struct resource rnga_resources[] = { { - .start = OTG_BASE_ADDR, - .end = OTG_BASE_ADDR + 0x1ff, - .flags = IORESOURCE_MEM, - }, { - .start = MXC_INT_USB3, - .end = MXC_INT_USB3, - .flags = IORESOURCE_IRQ, - }, -}; - -static u64 otg_dmamask = DMA_BIT_MASK(32); - -/* OTG gadget device */ -struct platform_device mxc_otg_udc_device = { - .name = "fsl-usb2-udc", - .id = -1, - .dev = { - .dma_mask = &otg_dmamask, - .coherent_dma_mask = DMA_BIT_MASK(32), - }, - .resource = otg_resources, - .num_resources = ARRAY_SIZE(otg_resources), -}; - -#ifdef CONFIG_ARCH_MX35 -static struct resource mxc_fec_resources[] = { + .start = RNGA_BASE_ADDR, + .end = RNGA_BASE_ADDR + 0x28, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device fsl_rnga_device = { + .name = "fsl_rnga", + .id = -1, + .num_resources = 1, + .resource = rnga_resources, +}; + +static inline void mxc_init_rnga(void) +{ + platform_device_register(&fsl_rnga_device); +} +#else +static inline void mxc_init_rnga(void) +{ +} +#endif + +#if defined(CONFIG_MXC_IIM) || defined(CONFIG_MXC_IIM_MODULE) +static struct resource mxc_iim_resources[] = { { - .start = MXC_FEC_BASE_ADDR, - .end = MXC_FEC_BASE_ADDR + 0xfff, - .flags = IORESOURCE_MEM - }, { - .start = MXC_INT_FEC, - .end = MXC_INT_FEC, - .flags = IORESOURCE_IRQ - }, -}; - -struct platform_device mxc_fec_device = { - .name = "fec", + .start = IIM_BASE_ADDR, + .end = IIM_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device mxc_iim_device = { + .name = "mxc_iim", .id = 0, - .num_resources = ARRAY_SIZE(mxc_fec_resources), - .resource = mxc_fec_resources, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(mxc_iim_resources), + .resource = mxc_iim_resources }; -#endif -static int mx3_devices_init(void) +static inline void mxc_init_iim(void) { - if (cpu_is_mx31()) { - mxc_nand_resources[0].start = MX31_NFC_BASE_ADDR; - mxc_nand_resources[0].end = MX31_NFC_BASE_ADDR + 0xfff; - mxc_register_device(&mxc_rnga_device, NULL); - } - if (cpu_is_mx35()) { - mxc_nand_resources[0].start = MX35_NFC_BASE_ADDR; - mxc_nand_resources[0].end = MX35_NFC_BASE_ADDR + 0xfff; - } + if (platform_device_register(&mxc_iim_device) < 0) + dev_err(&mxc_iim_device.dev, + "Unable to register mxc iim device\n"); +} +#else +static inline void mxc_init_iim(void) +{ +} +#endif +int __init mxc_init_devices(void) +{ + mxc_init_wdt(); + mxc_init_ipu(); + mxc_init_spi(); + mxc_init_i2c(); + mxc_init_rtc(); + mxc_init_owire(); + mxc_init_pcmcia(); + mxc_init_scc(); + mxc_init_ssi(); + mxc_init_hmp4e(); + mxc_init_dma(); + mxc_init_audio(); + ckih_clk = clk_get(NULL, "ckih"); + mxc_init_dptc(); + mxc_init_vpu(); + mxc_init_rnga(); + mxc_init_iim(); + + /* SPBA configuration for SSI2 - SDMA and MCU are set */ + spba_take_ownership(SPBA_SSI2, SPBA_MASTER_C | SPBA_MASTER_A); return 0; } - -subsys_initcall(mx3_devices_init); diff --git a/arch/arm/mach-mx3/dma.c b/arch/arm/mach-mx3/dma.c new file mode 100644 index 000000000000..4d63d706cba4 --- /dev/null +++ b/arch/arm/mach-mx3/dma.c @@ -0,0 +1,745 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/init.h> +#include <linux/device.h> +#include <asm/dma.h> +#include <mach/hardware.h> + +#include "serial.h" + +#define MXC_MMC_BUFFER_ACCESS 0x38 +#define MXC_SSI_TX0_REG 0x0 +#define MXC_SSI_TX1_REG 0x4 +#define MXC_SSI_RX0_REG 0x8 +#define MXC_SSI_RX1_REG 0xC +#define MXC_FIRI_TXFIFO 0x14 +#define MXC_SDHC_MMC_WML 16 +#define MXC_SDHC_SD_WML 64 +#define MXC_SSI_TXFIFO_WML 0x4 +#define MXC_SSI_RXFIFO_WML 0x6 +#define MXC_FIRI_WML 16 + +#ifdef CONFIG_SDMA_IRAM +#define trans_type int_2_per +#else +#define trans_type emi_2_per +#endif + +typedef struct mxc_sdma_info_entry_s { + mxc_dma_device_t device; + mxc_sdma_channel_params_t *chnl_info; +} mxc_sdma_info_entry_t; + +static mxc_sdma_channel_params_t mxc_sdma_uart1_rx_params = { + .chnl_params = { + .watermark_level = UART1_UFCR_RXTL, + .per_address = UART1_BASE_ADDR, + .peripheral_type = UART, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART1_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART1_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart1_tx_params = { + .chnl_params = { + .watermark_level = UART1_UFCR_TXTL, + .per_address = UART1_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART1_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART1_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart2_rx_params = { + .chnl_params = { + .watermark_level = UART2_UFCR_RXTL, + .per_address = UART2_BASE_ADDR, + .peripheral_type = UART, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART2_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART2_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart2_tx_params = { + .chnl_params = { + .watermark_level = UART2_UFCR_TXTL, + .per_address = UART2_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART2_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART2_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart3_rx_params = { + .chnl_params = { + .watermark_level = UART3_UFCR_RXTL, + .per_address = UART3_BASE_ADDR, + .peripheral_type = UART_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART3_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART3_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart3_tx_params = { + .chnl_params = { + .watermark_level = UART3_UFCR_TXTL, + .per_address = UART3_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART3_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART3_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart4_rx_params = { + .chnl_params = { + .watermark_level = UART4_UFCR_RXTL, + .per_address = UART4_BASE_ADDR, + .peripheral_type = UART, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART4_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART4_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart4_tx_params = { + .chnl_params = { + .watermark_level = UART4_UFCR_TXTL, + .per_address = UART4_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART4_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART4_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart5_rx_params = { + .chnl_params = { + .watermark_level = UART5_UFCR_RXTL, + .per_address = UART5_BASE_ADDR, + .peripheral_type = UART, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART5_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART5_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart5_tx_params = { + .chnl_params = { + .watermark_level = UART5_UFCR_TXTL, + .per_address = UART5_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART5_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART5_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_mmc1_width1_params = { + .chnl_params = { + .watermark_level = MXC_SDHC_MMC_WML, + .per_address = + MMC_SDHC1_BASE_ADDR + MXC_MMC_BUFFER_ACCESS, + .peripheral_type = MMC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SDHC1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MMC1, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_mmc1_width4_params = { + .chnl_params = { + .watermark_level = MXC_SDHC_SD_WML, + .per_address = + MMC_SDHC1_BASE_ADDR + MXC_MMC_BUFFER_ACCESS, + .peripheral_type = MMC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SDHC1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MMC1, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_mmc2_width1_params = { + .chnl_params = { + .watermark_level = MXC_SDHC_MMC_WML, + .per_address = + MMC_SDHC2_BASE_ADDR + MXC_MMC_BUFFER_ACCESS, + .peripheral_type = MMC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SDHC2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MMC2, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_mmc2_width4_params = { + .chnl_params = { + .watermark_level = MXC_SDHC_SD_WML, + .per_address = + MMC_SDHC2_BASE_ADDR + MXC_MMC_BUFFER_ACCESS, + .peripheral_type = MMC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SDHC2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MMC2, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = trans_type, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = trans_type, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = trans_type, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = trans_type, + .event_id = DMA_REQ_SSI2_TX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = trans_type, + .event_id = DMA_REQ_SSI2_TX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_fir_rx_params = { + .chnl_params = { + .watermark_level = MXC_FIRI_WML, + .per_address = FIRI_BASE_ADDR, + .peripheral_type = FIRI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_FIRI_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_FIR_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_fir_tx_params = { + .chnl_params = { + .watermark_level = MXC_FIRI_WML, + .per_address = FIRI_BASE_ADDR + MXC_FIRI_TXFIFO, + .peripheral_type = FIRI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_FIRI_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_FIR_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_memory_params = { + .chnl_params = { + .peripheral_type = MEMORY, + .transfer_type = emi_2_emi, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MEMORY, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_fifo_memory_params = { + .chnl_params = { + .peripheral_type = FIFO_MEMORY, + .per_address = MXC_FIFO_MEM_DEST_FIXED, + .transfer_type = emi_2_emi, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + .event_id = 0, + }, + .channel_num = MXC_DMA_CHANNEL_FIFO_MEMORY, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ata_rx_params = { + .chnl_params = { + .watermark_level = MXC_IDE_DMA_WATERMARK, + .per_address = ATA_DMA_BASE_ADDR, + .peripheral_type = ATA, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_ATA_TX_END, + .event_id2 = DMA_REQ_ATA_RX, + .bd_number = MXC_IDE_DMA_BD_NR, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ATA_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ata_tx_params = { + .chnl_params = { + .watermark_level = MXC_IDE_DMA_WATERMARK, + .per_address = ATA_DMA_BASE_ADDR + 0x18, + .peripheral_type = ATA, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_ATA_TX_END, + .event_id2 = DMA_REQ_ATA_TX, + .bd_number = MXC_IDE_DMA_BD_NR, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ATA_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; +static mxc_sdma_info_entry_t mxc_sdma_active_dma_info[] = { + {MXC_DMA_UART1_RX, &mxc_sdma_uart1_rx_params}, + {MXC_DMA_UART1_TX, &mxc_sdma_uart1_tx_params}, + {MXC_DMA_UART2_RX, &mxc_sdma_uart2_rx_params}, + {MXC_DMA_UART2_TX, &mxc_sdma_uart2_tx_params}, + {MXC_DMA_UART3_RX, &mxc_sdma_uart3_rx_params}, + {MXC_DMA_UART3_TX, &mxc_sdma_uart3_tx_params}, + {MXC_DMA_UART4_RX, &mxc_sdma_uart4_rx_params}, + {MXC_DMA_UART4_TX, &mxc_sdma_uart4_tx_params}, + {MXC_DMA_UART5_RX, &mxc_sdma_uart5_rx_params}, + {MXC_DMA_UART5_TX, &mxc_sdma_uart5_tx_params}, + {MXC_DMA_MMC1_WIDTH_1, &mxc_sdma_mmc1_width1_params}, + {MXC_DMA_MMC1_WIDTH_4, &mxc_sdma_mmc1_width4_params}, + {MXC_DMA_MMC2_WIDTH_1, &mxc_sdma_mmc2_width1_params}, + {MXC_DMA_MMC2_WIDTH_4, &mxc_sdma_mmc2_width4_params}, + {MXC_DMA_SSI1_8BIT_RX0, &mxc_sdma_ssi1_8bit_rx0_params}, + {MXC_DMA_SSI1_8BIT_TX0, &mxc_sdma_ssi1_8bit_tx0_params}, + {MXC_DMA_SSI1_16BIT_RX0, &mxc_sdma_ssi1_16bit_rx0_params}, + {MXC_DMA_SSI1_16BIT_TX0, &mxc_sdma_ssi1_16bit_tx0_params}, + {MXC_DMA_SSI1_24BIT_RX0, &mxc_sdma_ssi1_24bit_rx0_params}, + {MXC_DMA_SSI1_24BIT_TX0, &mxc_sdma_ssi1_24bit_tx0_params}, + {MXC_DMA_SSI1_8BIT_RX1, &mxc_sdma_ssi1_8bit_rx1_params}, + {MXC_DMA_SSI1_8BIT_TX1, &mxc_sdma_ssi1_8bit_tx1_params}, + {MXC_DMA_SSI1_16BIT_RX1, &mxc_sdma_ssi1_16bit_rx1_params}, + {MXC_DMA_SSI1_16BIT_TX1, &mxc_sdma_ssi1_16bit_tx1_params}, + {MXC_DMA_SSI1_24BIT_RX1, &mxc_sdma_ssi1_24bit_rx1_params}, + {MXC_DMA_SSI1_24BIT_TX1, &mxc_sdma_ssi1_24bit_tx1_params}, + {MXC_DMA_SSI2_8BIT_RX0, &mxc_sdma_ssi2_8bit_rx0_params}, + {MXC_DMA_SSI2_8BIT_TX0, &mxc_sdma_ssi2_8bit_tx0_params}, + {MXC_DMA_SSI2_16BIT_RX0, &mxc_sdma_ssi2_16bit_rx0_params}, + {MXC_DMA_SSI2_16BIT_TX0, &mxc_sdma_ssi2_16bit_tx0_params}, + {MXC_DMA_SSI2_24BIT_RX0, &mxc_sdma_ssi2_24bit_rx0_params}, + {MXC_DMA_SSI2_24BIT_TX0, &mxc_sdma_ssi2_24bit_tx0_params}, + {MXC_DMA_SSI2_8BIT_RX1, &mxc_sdma_ssi2_8bit_rx1_params}, + {MXC_DMA_SSI2_8BIT_TX1, &mxc_sdma_ssi2_8bit_tx1_params}, + {MXC_DMA_SSI2_16BIT_RX1, &mxc_sdma_ssi2_16bit_rx1_params}, + {MXC_DMA_SSI2_16BIT_TX1, &mxc_sdma_ssi2_16bit_tx1_params}, + {MXC_DMA_SSI2_24BIT_RX1, &mxc_sdma_ssi2_24bit_rx1_params}, + {MXC_DMA_SSI2_24BIT_TX1, &mxc_sdma_ssi2_24bit_tx1_params}, + {MXC_DMA_FIR_RX, &mxc_sdma_fir_rx_params}, + {MXC_DMA_FIR_TX, &mxc_sdma_fir_tx_params}, + {MXC_DMA_MEMORY, &mxc_sdma_memory_params}, + {MXC_DMA_FIFO_MEMORY, &mxc_sdma_fifo_memory_params}, + {MXC_DMA_ATA_RX, &mxc_sdma_ata_rx_params}, + {MXC_DMA_ATA_TX, &mxc_sdma_ata_tx_params}, +}; + +static int mxc_sdma_info_entrys = + sizeof(mxc_sdma_active_dma_info) / sizeof(mxc_sdma_active_dma_info[0]); + +/*! + * This functions Returns the SDMA paramaters associated for a module + * + * @param channel_id the ID of the module requesting DMA + * @return returns the sdma parameters structure for the device + */ +mxc_sdma_channel_params_t *mxc_sdma_get_channel_params(mxc_dma_device_t + channel_id) +{ + mxc_sdma_info_entry_t *p = mxc_sdma_active_dma_info; + int i; + + for (i = 0; i < mxc_sdma_info_entrys; i++, p++) { + if (p->device == channel_id) { + return p->chnl_info; + } + } + return NULL; +} + +/*! + * This functions marks the SDMA channels that are statically allocated + * + * @param chnl the channel array used to store channel information + */ +void mxc_get_static_channels(mxc_dma_channel_t * chnl) +{ +#ifdef CONFIG_SDMA_IRAM + int i; + for (i = MXC_DMA_CHANNEL_IRAM; i < MAX_DMA_CHANNELS; i++) + chnl[i].dynamic = 0; +#endif /*CONFIG_SDMA_IRAM */ +} + +EXPORT_SYMBOL(mxc_sdma_get_channel_params); +EXPORT_SYMBOL(mxc_get_static_channels); diff --git a/arch/arm/mach-mx3/dptc.c b/arch/arm/mach-mx3/dptc.c new file mode 100644 index 000000000000..e4fb0bb74df1 --- /dev/null +++ b/arch/arm/mach-mx3/dptc.c @@ -0,0 +1,103 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dptc.c + * + * @brief DPTC table for the Freescale Semiconductor MXC DPTC module. + * + * @ingroup PM + */ + +#include <mach/hardware.h> +#include <mach/mxc_dptc.h> + +struct dptc_wp dptc_wp_allfreq_26ckih[DPTC_WP_SUPPORTED] = { + /* 532MHz */ + /* dcvr0 dcvr1 dcvr2 dcvr3 voltage */ + /* wp0 */ + {0xffc00000, 0x95c00000, 0xffc00000, 0xe5800000, 1625}, + {0xffc00000, 0x95e3e8e4, 0xffc00000, 0xe5b6fda0, 1600}, + {0xffc00000, 0x95e3e8e4, 0xffc00000, 0xe5b6fda0, 1575}, + {0xffc00000, 0x95e3e8e8, 0xffc00000, 0xe5f70da4, 1550}, + {0xffc00000, 0x9623f8e8, 0xffc00000, 0xe6371da8, 1525}, + /* wp5 */ + {0xffc00000, 0x966408f0, 0xffc00000, 0xe6b73db0, 1500}, + {0xffc00000, 0x96e428f4, 0xffc00000, 0xe7776dbc, 1475}, + {0xffc00000, 0x976448fc, 0xffc00000, 0xe8379dc8, 1450}, + {0xffc00000, 0x97e46904, 0xffc00000, 0xe977ddd8, 1425}, + {0xffc00000, 0x98a48910, 0xffc00000, 0xeab81de8, 1400}, + /* wp10 */ + {0xffc00000, 0x9964b918, 0xffc00000, 0xebf86df8, 1375}, + {0xffc00000, 0xffe4e924, 0xffc00000, 0xfff8ae08, 1350}, + {0xffc00000, 0xffe5192c, 0xffc00000, 0xfff8fe1c, 1350}, + {0xffc00000, 0xffe54938, 0xffc00000, 0xfff95e2c, 1350}, + {0xffc00000, 0xffe57944, 0xffc00000, 0xfff9ae44, 1350}, + /* wp15 */ + {0xffc00000, 0xffe5b954, 0xffc00000, 0xfffa0e58, 1350}, + {0xffc00000, 0xffe5e960, 0xffc00000, 0xfffa6e70, 1350}, +}; + +struct dptc_wp dptc_wp_allfreq_26ckih_TO_2_0[DPTC_WP_SUPPORTED] = { + /* Mx31 TO 2.0 Offset table */ + /* 532MHz */ + /* dcvr0 dcvr1 dcvr2 dcvr3 voltage */ + /* wp0 */ + {0xffc00000, 0x9E265978, 0xffc00000, 0xE4371D9C, 1625}, + {0xffc00000, 0x9E665978, 0xffc00000, 0xE4772D9C, 1600}, + {0xffc00000, 0x9EA65978, 0xffc00000, 0xE4772DA0, 1575}, + {0xffc00000, 0x9EE66978, 0xffc00000, 0xE4B73DA0, 1550}, + {0xffc00000, 0x9F26697C, 0xffc00000, 0xE4F73DA0, 1525}, + /* wp5 */ + {0xffc00000, 0x9F66797C, 0xffc00000, 0xE5774DA4, 1500}, + {0xffc00000, 0x9FE6797C, 0xffc00000, 0xE5F75DA4, 1475}, + {0xffc00000, 0xA026897C, 0xffc00000, 0xE6776DA4, 1450}, + {0xffc00000, 0xA0A6897C, 0xffc00000, 0xE6F77DA8, 1425}, + {0xffc00000, 0xA0E69980, 0xffc00000, 0xE7B78DAC, 1400}, + /* wp10 */ + {0xffc00000, 0xA1669980, 0xffc00000, 0xE8379DAC, 1375}, + {0xffc00000, 0xA1A6A980, 0xffc00000, 0xE8F7ADB0, 1350}, + {0xffc00000, 0xA226B984, 0xffc00000, 0xE9F7CDB0, 1325}, + {0xffc00000, 0xA2A6C984, 0xffc00000, 0xEAB7DDB4, 1300}, + {0xffc00000, 0xA326C988, 0xffc00000, 0xEBB7FDB8, 1275}, + /* wp15 */ + {0xffc00000, 0xA3A6D988, 0xffc00000, 0xECB80DBC, 1250}, + {0xffc00000, 0xA426E988, 0xffc00000, 0xEDB82DC0, 1225}, +}; + +struct dptc_wp dptc_wp_allfreq_27ckih_TO_2_0[DPTC_WP_SUPPORTED] = { + /* Mx31 TO 2.0 Offset table */ + /* 532MHz */ + /* dcvr0 dcvr1 dcvr2 dcvr3 voltage */ + /* wp0 */ + {0xffc00000, 0x9864E920, 0xffc00000, 0xDBB50D1C, 1625}, + {0xffc00000, 0x98A4E920, 0xffc00000, 0xDBF51D1C, 1600}, + {0xffc00000, 0x98E4E920, 0xffc00000, 0xDBF51D20, 1575}, + {0xffc00000, 0x9924F920, 0xffc00000, 0xDC352D20, 1550}, + {0xffc00000, 0x9924F924, 0xffc00000, 0xDC752D20, 1525}, + /* wp5 */ + {0xffc00000, 0x99650924, 0xffc00000, 0xDCF53D24, 1500}, + {0xffc00000, 0x99E50924, 0xffc00000, 0xDD754D24, 1475}, + {0xffc00000, 0x9A251924, 0xffc00000, 0xDDF55D24, 1450}, + {0xffc00000, 0x9AA51924, 0xffc00000, 0xDE756D28, 1425}, + {0xffc00000, 0x9AE52928, 0xffc00000, 0xDF357D2C, 1400}, + /* wp10 */ + {0xffc00000, 0x9B652928, 0xffc00000, 0xDFB58D2C, 1375}, + {0xffc00000, 0x9BA53928, 0xffc00000, 0xE0759D30, 1350}, + {0xffc00000, 0x9C254928, 0xffc00000, 0xE135BD30, 1325}, + {0xffc00000, 0x9CA55928, 0xffc00000, 0xE1F5CD34, 1300}, + {0xffc00000, 0x9D25592C, 0xffc00000, 0xE2F5ED38, 1275}, + /* wp15 */ + {0xffc00000, 0x9DA5692C, 0xffc00000, 0xE3F5FD38, 1250}, + {0xffc00000, 0x9E25792C, 0xffc00000, 0xE4F61D3C, 1225}, +}; diff --git a/arch/arm/mach-mx3/dvfs_v2.c b/arch/arm/mach-mx3/dvfs_v2.c new file mode 100644 index 000000000000..9fa9c7aa5206 --- /dev/null +++ b/arch/arm/mach-mx3/dvfs_v2.c @@ -0,0 +1,535 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dvfs_v2.c + * + * @brief A simplied driver for the Freescale Semiconductor MXC DVFS module. + * + * Upon initialization, the DVFS driver initializes the DVFS hardware + * sets up driver nodes attaches to the DVFS interrupt and initializes internal + * data structures. When the DVFS interrupt occurs the driver checks the cause + * of the interrupt (lower frequency, increase frequency or emergency) and changes + * the CPU voltage according to translation table that is loaded into the driver. + * + * @ingroup PM + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/device.h> +#include <linux/sysdev.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/pmic_external.h> +#include <mach/pmic_power.h> + +#include "iomux.h" +#include "crm_regs.h" + +static int dvfs_is_active; + +/* Used for tracking the number of interrupts */ +static u32 dvfs_nr_up[4]; +static u32 dvfs_nr_dn[4]; + +/* + * Clock structures + */ +static struct clk *cpu_clk; +static struct clk *ahb_clk; + +enum { + FSVAI_FREQ_NOCHANGE = 0x0, + FSVAI_FREQ_INCREASE, + FSVAI_FREQ_DECREASE, + FSVAI_FREQ_EMERG, +}; + +/* + * Frequency increase threshold. Increase frequency change request + * will be sent if DVFS counter value will be more than this value. + */ +#define DVFS_UPTHR (30 << MXC_CCM_LTR0_UPTHR_OFFSET) + +/* + * Frequency decrease threshold. Decrease frequency change request + * will be sent if DVFS counter value will be less than this value. + */ +#define DVFS_DNTHR (18 << MXC_CCM_LTR0_DNTHR_OFFSET) + +/* + * With the ARM clocked at 532, this setting yields a DIV_3_CLK of 2.03 kHz. + */ +#define DVFS_DIV3CK (3 << MXC_CCM_LTR0_DIV3CK_OFFSET) + +/* + * DNCNT defines the amount of times the down threshold should be exceeded + * before DVFS will trigger frequency decrease request. + */ +#define DVFS_DNCNT (0x33 << MXC_CCM_LTR1_DNCNT_OFFSET) + +/* + * UPCNT defines the amount of times the up threshold should be exceeded + * before DVFS will trigger frequency increase request. + */ +#define DVFS_UPCNT (0x33 << MXC_CCM_LTR1_UPCNT_OFFSET) + +/* + * Panic threshold. Panic frequency change request + * will be sent if DVFS counter value will be more than this value. + */ +#define DVFS_PNCTHR (63 << MXC_CCM_LTR1_PNCTHR_OFFSET) + +/* + * Load tracking buffer source: 1 for ld_add; 0 for pre_ld_add + */ +#define DVFS_LTBRSR (1 << MXC_CCM_LTR1_LTBRSR_OFFSET) + +/* EMAC defines how many samples are included in EMA calculation */ +#define DVFS_EMAC (0x20 << MXC_CCM_LTR2_EMAC_OFFSET) + +const static u8 ltr_gp_weight[] = { + 0, /* 0 */ + 0, + 0, + 0, + 0, + 0, /* 5 */ + 0, + 0, + 0, + 0, + 0, /* 10 */ + 0, + 7, + 7, + 7, + 7, /* 15 */ +}; + +DEFINE_SPINLOCK(mxc_dvfs_lock); + +/*! + * This function sets the weight of general purpose signals + * @param gp_id number of general purpose bit + * @param weight the weight of the general purpose bit + */ +static void set_gp_weight(int gp_id, u8 weight) +{ + u32 reg; + + if (gp_id < 9) { + reg = __raw_readl(MXC_CCM_LTR3); + reg = (reg & ~(MXC_CCM_LTR3_WSW_MASK(gp_id))) | + (weight << MXC_CCM_LTR3_WSW_OFFSET(gp_id)); + __raw_writel(reg, MXC_CCM_LTR3); + } else if (gp_id < 16) { + reg = __raw_readl(MXC_CCM_LTR2); + reg = (reg & ~(MXC_CCM_LTR2_WSW_MASK(gp_id))) | + (weight << MXC_CCM_LTR2_WSW_OFFSET(gp_id)); + __raw_writel(reg, MXC_CCM_LTR2); + } +} + +static int start_dvfs(void) +{ + u32 reg; + unsigned long flags; + + if (dvfs_is_active) { + return 0; + } + + spin_lock_irqsave(&mxc_dvfs_lock, flags); + + reg = __raw_readl(MXC_CCM_PMCR0); + + /* enable dvfs and interrupt */ + reg = (reg & ~MXC_CCM_PMCR0_FSVAIM) | MXC_CCM_PMCR0_DVFEN; + + __raw_writel(reg, MXC_CCM_PMCR0); + + dvfs_is_active = 1; + + spin_unlock_irqrestore(&mxc_dvfs_lock, flags); + + pr_info("DVFS is started\n"); + + return 0; +} + +#define MXC_CCM_LTR0_CONFIG_MASK (MXC_CCM_LTR0_UPTHR_MASK | \ + MXC_CCM_LTR0_DNTHR_MASK | \ + MXC_CCM_LTR0_DIV3CK_MASK) +#define MXC_CCM_LTR0_CONFIG_VAL (DVFS_UPTHR | DVFS_DNTHR | DVFS_DIV3CK) + +#define MXC_CCM_LTR1_CONFIG_MASK (MXC_CCM_LTR1_UPCNT_MASK | \ + MXC_CCM_LTR1_DNCNT_MASK | \ + MXC_CCM_LTR1_PNCTHR_MASK | \ + MXC_CCM_LTR1_LTBRSR_MASK) +#define MXC_CCM_LTR1_CONFIG_VAL (DVFS_UPCNT | DVFS_DNCNT | \ + DVFS_PNCTHR | DVFS_LTBRSR) + +/*! + * This function is called for module initialization. + * It sets up the DVFS hardware. + * It sets default values for DVFS thresholds and counters. The default + * values was chosen from a set of different reasonable values. They was tested + * and the default values in the driver gave the best results. + * More work should be done to find optimal values. + * + * @return 0 if successful; non-zero otherwise. + * + */ +static int init_dvfs_controller(void) +{ + u32 i, reg; + + /* Configure 2 MC13783 DVFS pins */ + mxc_request_iomux(MX31_PIN_DVFS0, OUTPUTCONFIG_FUNC, INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_DVFS1, OUTPUTCONFIG_FUNC, INPUTCONFIG_NONE); + + /* Configure MC13783 voltage ready input pin */ + mxc_request_iomux(MX31_PIN_GPIO1_5, OUTPUTCONFIG_GPIO, + INPUTCONFIG_FUNC); + + /* setup LTR0 */ + reg = __raw_readl(MXC_CCM_LTR0); + reg = (reg & ~(MXC_CCM_LTR0_CONFIG_MASK)) | MXC_CCM_LTR0_CONFIG_VAL; + __raw_writel(reg, MXC_CCM_LTR0); + + /* set up LTR1 */ + reg = __raw_readl(MXC_CCM_LTR1); + reg = (reg & ~(MXC_CCM_LTR1_CONFIG_MASK)) | MXC_CCM_LTR1_CONFIG_VAL; + __raw_writel(reg, MXC_CCM_LTR1); + + /* setup LTR2 */ + reg = __raw_readl(MXC_CCM_LTR2); + reg = (reg & ~(MXC_CCM_LTR2_EMAC_MASK)) | DVFS_EMAC; + __raw_writel(reg, MXC_CCM_LTR2); + + /* Set general purpose weights to 0 */ + for (i = 0; i < 16; i++) { + set_gp_weight(i, ltr_gp_weight[i]); + } + + /* ARM interrupt, mask load buf full interrupt */ + reg = __raw_readl(MXC_CCM_PMCR0); + reg |= MXC_CCM_PMCR0_DVFIS | MXC_CCM_PMCR0_LBMI; + __raw_writel(reg, MXC_CCM_PMCR0); + + /* configuring EMI Handshake and PLL relock disable */ + reg = __raw_readl(MXC_CCM_PMCR1); + reg |= MXC_CCM_PMCR1_PLLRDIS; + reg |= MXC_CCM_PMCR1_EMIRQ_EN; + __raw_writel(reg, MXC_CCM_PMCR1); + + return 0; +} + +static irqreturn_t dvfs_irq(int irq, void *dev_id) +{ + u32 pmcr0 = __raw_readl(MXC_CCM_PMCR0); + u32 fsvai = (pmcr0 & MXC_CCM_PMCR0_FSVAI_MASK) >> + MXC_CCM_PMCR0_FSVAI_OFFSET; + u32 dvsup = (pmcr0 & MXC_CCM_PMCR0_DVSUP_MASK) >> + MXC_CCM_PMCR0_DVSUP_OFFSET; + u32 curr_ahb, curr_cpu, rate; + + /* Should not be here if FSVAIM is set */ + BUG_ON(pmcr0 & MXC_CCM_PMCR0_FSVAIM); + + if (fsvai == FSVAI_FREQ_NOCHANGE) { + /* Do nothing. Freq change is not required */ + printk(KERN_WARNING "fsvai should not be 0\n"); + return IRQ_HANDLED; + } + + if (!(pmcr0 & MXC_CCM_PMCR0_UPDTEN)) { + /* Do nothing. DVFS didn't finish previous flow update */ + return IRQ_HANDLED; + } + + if (((dvsup == DVSUP_LOW) && (fsvai == FSVAI_FREQ_DECREASE)) || + ((dvsup == DVSUP_TURBO) && ((fsvai == FSVAI_FREQ_INCREASE) || + (fsvai == FSVAI_FREQ_EMERG)))) { + /* Interrupt should be disabled in these cases according to + * the spec since DVFS is already at lowest (highest) state */ + printk(KERN_WARNING "Something is wrong?\n"); + return IRQ_HANDLED; + } + + curr_ahb = clk_get_rate(ahb_clk); + if (fsvai == FSVAI_FREQ_DECREASE) { + curr_cpu = clk_get_rate(cpu_clk); + rate = ((curr_cpu / curr_ahb) - 1) * curr_ahb; + if ((cpu_is_mx31_rev(CHIP_REV_2_0) < 0) && + ((curr_cpu / curr_ahb) == 4)) { + rate = ((curr_cpu / curr_ahb) - 2) * curr_ahb; + } + dvfs_nr_dn[dvsup]++; + } else { + rate = 4 * curr_ahb; + dvfs_nr_up[dvsup]++; + } + + clk_set_rate(cpu_clk, rate); + return IRQ_HANDLED; +} + +/*! + * This function disables the DVFS module. + */ +static void stop_dvfs(void) +{ + u32 pmcr0, dvsup; + unsigned long flags; + u32 curr_ahb = clk_get_rate(ahb_clk); + + if (dvfs_is_active) { + spin_lock_irqsave(&mxc_dvfs_lock, flags); + + pmcr0 = __raw_readl(MXC_CCM_PMCR0); + dvsup = (pmcr0 & MXC_CCM_PMCR0_DVSUP_MASK) >> + MXC_CCM_PMCR0_DVSUP_OFFSET; + if (dvsup != DVSUP_TURBO) { + /* Use sw delay to insure volt/freq change */ + clk_set_rate(cpu_clk, (4 * curr_ahb)); + udelay(200); + } + + pmcr0 = __raw_readl(MXC_CCM_PMCR0); + /* disable dvfs and its interrupt */ + pmcr0 = (pmcr0 & ~MXC_CCM_PMCR0_DVFEN) | MXC_CCM_PMCR0_FSVAIM; + __raw_writel(pmcr0, MXC_CCM_PMCR0); + + dvfs_is_active = 0; + + spin_unlock_irqrestore(&mxc_dvfs_lock, flags); + } + + pr_info("DVFS is stopped\n"); +} + +void pmic_voltage_init(void) +{ + t_regulator_voltage volt; + + /* Enable 4 mc13783 output voltages */ + pmic_write_reg(REG_ARBITRATION_SWITCHERS, (1 << 5), (1 << 5)); + + /* Set mc13783 DVS speed 25mV each 4us */ + pmic_write_reg(REG_SWITCHERS_4, (0 << 6), (3 << 6)); + + if (cpu_is_mx31()) + volt.sw1a = SW1A_1_625V; + else + volt.sw1a = SW1A_1_425V; + + pmic_power_regulator_set_voltage(SW_SW1A, volt); + + volt.sw1a = SW1A_1_25V; + pmic_power_switcher_set_dvs(SW_SW1A, volt); + + if (cpu_is_mx32()) { + volt.sw1a = SW1A_0_975V; + pmic_power_switcher_set_stby(SW_SW1A, volt); + } + + volt.sw1b = SW1A_1_25V; + pmic_power_switcher_set_dvs(SW_SW1B, volt); + + volt.sw1b = SW1A_1_25V; + pmic_power_switcher_set_stby(SW_SW1B, volt); +} + +static ssize_t dvfs_enable_store(struct sys_device *dev, struct sysdev_attribute *attr, + const char *buf, size_t size) +{ + if (strstr(buf, "1") != NULL) { + if (start_dvfs() != 0) { + printk(KERN_ERR "Failed to start DVFS\n"); + } + } else if (strstr(buf, "0") != NULL) { + stop_dvfs(); + } + + return size; +} + +static ssize_t dvfs_status_show(struct sys_device *dev, struct sysdev_attribute *attr, + char *buf) +{ + int size = 0; + + if (dvfs_is_active) { + size = sprintf(buf, "DVFS is enabled\n"); + } else { + size = sprintf(buf, "DVFS is disabled\n"); + } + size += + sprintf((buf + size), "UP:\t%d\t%d\t%d\t%d\n", dvfs_nr_up[0], + dvfs_nr_up[1], dvfs_nr_up[2], dvfs_nr_up[3]); + size += + sprintf((buf + size), "DOWN:\t%d\t%d\t%d\t%d\n\n", dvfs_nr_dn[0], + dvfs_nr_dn[1], dvfs_nr_dn[2], dvfs_nr_dn[3]); + + return size; +} + +static ssize_t dvfs_status_store(struct sys_device *dev, struct sysdev_attribute *attr, + const char *buf, size_t size) +{ + if (strstr(buf, "reset") != NULL) { + int i; + for (i = 0; i < 4; i++) { + dvfs_nr_up[i] = 0; + dvfs_nr_dn[i] = 0; + } + } + + return size; +} + +static ssize_t dvfs_debug_show(struct sys_device *dev, struct sysdev_attribute *attr, + char *buf) +{ + int size = 0; + u32 curr_ahb, curr_cpu; + + curr_ahb = clk_get_rate(ahb_clk); + curr_cpu = clk_get_rate(cpu_clk); + + pr_debug("ahb %d, cpu %d\n", curr_ahb, curr_cpu); + + return size; +} + +static ssize_t dvfs_debug_store(struct sys_device *dev, struct sysdev_attribute *attr, + const char *buf, size_t size) +{ + u32 curr_ahb, curr_cpu, rate = 0; + + curr_ahb = clk_get_rate(ahb_clk); + curr_cpu = clk_get_rate(cpu_clk); + + if (strstr(buf, "inc") != NULL) { + rate = 4 * curr_ahb; + pr_debug("inc to %d\n", rate); + } + + if (strstr(buf, "dec") != NULL) { + rate = ((curr_cpu / curr_ahb) - 1) * curr_ahb; + if ((cpu_is_mx31_rev(CHIP_REV_2_0) < 0) && + ((curr_cpu / curr_ahb) == 4)) + rate = ((curr_cpu / curr_ahb) - 2) * curr_ahb; + + pr_debug("dec to %d\n", rate); + } + + clk_set_rate(cpu_clk, rate); + + return size; +} + +static SYSDEV_ATTR(enable, 0200, NULL, dvfs_enable_store); +static SYSDEV_ATTR(status, 0644, dvfs_status_show, dvfs_status_store); +static SYSDEV_ATTR(debug, 0644, dvfs_debug_show, dvfs_debug_store); + +static struct sysdev_class dvfs_sysclass = { + .name = "dvfs", +}; + +static struct sys_device dvfs_device = { + .id = 0, + .cls = &dvfs_sysclass, +}; + +static int dvfs_sysdev_ctrl_init(void) +{ + int err; + + err = sysdev_class_register(&dvfs_sysclass); + if (!err) + err = sysdev_register(&dvfs_device); + if (!err) { + err = sysdev_create_file(&dvfs_device, &attr_enable); + err = sysdev_create_file(&dvfs_device, &attr_status); + err = sysdev_create_file(&dvfs_device, &attr_debug); + } + + return err; +} + +static void dvfs_sysdev_ctrl_exit(void) +{ + sysdev_remove_file(&dvfs_device, &attr_enable); + sysdev_remove_file(&dvfs_device, &attr_status); + sysdev_unregister(&dvfs_device); + sysdev_class_unregister(&dvfs_sysclass); +} + +static int __init dvfs_init(void) +{ + int err = 0; + pmic_voltage_init(); + + cpu_clk = clk_get(NULL, "cpu_clk"); + ahb_clk = clk_get(NULL, "ahb_clk"); + err = init_dvfs_controller(); + if (err) { + printk(KERN_ERR "DVFS: Unable to initialize DVFS"); + return err; + } + + /* request the DVFS interrupt */ + err = request_irq(MXC_INT_CCM_DVFS, dvfs_irq, IRQF_DISABLED, "dvfs", NULL); + if (err) { + printk(KERN_ERR "DVFS: Unable to attach to DVFS interrupt"); + } + + err = dvfs_sysdev_ctrl_init(); + if (err) { + printk(KERN_ERR + "DVFS: Unable to register sysdev entry for dvfs"); + return err; + } + + return err; +} + +static void __exit dvfs_cleanup(void) +{ + stop_dvfs(); + + /* release the DVFS interrupt */ + free_irq(MXC_INT_CCM_DVFS, NULL); + + dvfs_sysdev_ctrl_exit(); + + clk_put(cpu_clk); + clk_put(ahb_clk); +} + +module_init(dvfs_init); +module_exit(dvfs_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("DVFS driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-mx3/iomux.c b/arch/arm/mach-mx3/iomux.c index c66ccbcdc11b..9f6841ec2fdf 100644 --- a/arch/arm/mach-mx3/iomux.c +++ b/arch/arm/mach-mx3/iomux.c @@ -1,181 +1,260 @@ /* - * Copyright 2004-2006 Freescale Semiconductor, Inc. All Rights Reserved. - * Copyright (C) 2008 by Sascha Hauer <kernel@pengutronix.de> - * Copyright (C) 2009 by Valentin Longchamp <valentin.longchamp@epfl.ch> + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup GPIO_MX31 Board GPIO and Muxing Setup + * @ingroup MSL_MX31 + */ +/*! + * @file mach-mx3/iomux.c * - * 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. + * @brief I/O Muxing control functions * - * You 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. + * @ingroup GPIO_MX31 */ +#include <linux/io.h> #include <linux/module.h> #include <linux/spinlock.h> -#include <linux/io.h> -#include <linux/kernel.h> #include <mach/hardware.h> #include <mach/gpio.h> -#include <mach/iomux-mx3.h> +#include "iomux.h" -/* - * IOMUX register (base) addresses +/*! + * 4 control fields per MUX register */ -#define IOMUX_BASE IO_ADDRESS(IOMUXC_BASE_ADDR) -#define IOMUXINT_OBS1 (IOMUX_BASE + 0x000) -#define IOMUXINT_OBS2 (IOMUX_BASE + 0x004) -#define IOMUXGPR (IOMUX_BASE + 0x008) -#define IOMUXSW_MUX_CTL (IOMUX_BASE + 0x00C) -#define IOMUXSW_PAD_CTL (IOMUX_BASE + 0x154) +#define MUX_CTL_FIELDS 4 -static DEFINE_SPINLOCK(gpio_mux_lock); +/*! + * 3 control fields per PAD register + */ +#define PAD_CTL_FIELDS 3 -#define IOMUX_REG_MASK (IOMUX_PADNUM_MASK & ~0x3) +/*! + * Maximum number of MUX pins + * Number of pins = (highest iomux reg - lowest iomux reg + 1) * (4 pins/reg) + */ +#define MUX_PIN_NUM_MAX \ + (((u32 *)IOMUXSW_MUX_END - (u32 *)IOMUXSW_MUX_CTL + 1) * MUX_CTL_FIELDS) -unsigned long mxc_pin_alloc_map[NB_PORTS * 32 / BITS_PER_LONG]; -/* - * set the mode for a IOMUX pin. +/*! + * Number of pad controls = + * (highest pad ctl reg - lowest pad ctl reg + 1) * (3 pins/reg) */ -int mxc_iomux_mode(unsigned int pin_mode) -{ - u32 field, l, mode, ret = 0; - void __iomem *reg; +#define PAD_CTL_NUM_MAX \ + (((u32 *)IOMUXSW_PAD_END - (u32 *)IOMUXSW_PAD_CTL + 1) * PAD_CTL_FIELDS) - reg = IOMUXSW_MUX_CTL + (pin_mode & IOMUX_REG_MASK); - field = pin_mode & 0x3; - mode = (pin_mode & IOMUX_MODE_MASK) >> IOMUX_MODE_SHIFT; +#define PIN_TO_IOMUX_INDEX(pin) ((pin >> MUX_I) & ((1 << (MUX_F - MUX_I)) - 1)) +#define PIN_TO_IOMUX_FIELD(pin) ((pin >> MUX_F) & ((1 << (PAD_I - MUX_F)) - 1)) - spin_lock(&gpio_mux_lock); +/*! + * 8 bits for each MUX control field + */ +#define MUX_CTL_BIT_LEN 8 - l = __raw_readl(reg); - l &= ~(0xff << (field * 8)); - l |= mode << (field * 8); - __raw_writel(l, reg); +/*! + * 10 bits for each PAD control field + */ +#define MUX_PAD_BIT_LEN 10 - spin_unlock(&gpio_mux_lock); +/*! + * IOMUX register (base) addresses + */ +#define IOMUXGPR (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x008) /*!< General purpose */ +#define IOMUXSW_MUX_CTL (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x00C) /*!< MUX control */ +#define IOMUXSW_MUX_END (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x150) /*!< last MUX control register */ +#define IOMUXSW_PAD_CTL (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x154) /*!< Pad control */ +#define IOMUXSW_PAD_END (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x308) /*!< last Pad control register */ +#define IOMUXINT_OBS1 (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x000) /*!< Observe interrupts 1 */ +#define IOMUXINT_OBS2 (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x004) /*!< Observe interrupts 2 */ - return ret; -} -EXPORT_SYMBOL(mxc_iomux_mode); +/* len - mask bit length; fld - mask bit field. Example, to have the mask: + * 0xFF000000, use GET_FIELD_MASK(8, 3). Translate in plain language: + * "set the 3rd (0-based) 8-bit-long field to all 1's */ +#define GET_FIELD_MASK(len, fld) (((1 << len) - 1) << (len * fld)) +static DEFINE_SPINLOCK(gpio_mux_lock); +static u8 iomux_pin_res_table[MUX_PIN_NUM_MAX]; -/* - * This function configures the pad value for a IOMUX pin. +/*! + * This function is used to configure a pin through the IOMUX module. + * FIXED ME: for backward compatible. Will be static function! + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param out an output function as defined in \b #iomux_pin_ocfg_t + * @param in an input function as defined in \b #iomux_pin_icfg_t + * + * @return 0 if successful; Non-zero otherwise */ -void mxc_iomux_set_pad(enum iomux_pins pin, u32 config) +int iomux_config_mux(iomux_pin_name_t pin, iomux_pin_ocfg_t out, + iomux_pin_icfg_t in) { - u32 field, l; void __iomem *reg; + u32 l, ret = 0; + u32 mux_index = PIN_TO_IOMUX_INDEX(pin); + u32 mux_field = PIN_TO_IOMUX_FIELD(pin); + u32 mux_mask = GET_FIELD_MASK(MUX_CTL_BIT_LEN, mux_field); + u8 *rp; - pin &= IOMUX_PADNUM_MASK; - reg = IOMUXSW_PAD_CTL + (pin + 2) / 3 * 4; - field = (pin + 2) % 3; - - pr_debug("%s: reg offset = 0x%x, field = %d\n", - __func__, (pin + 2) / 3, field); + BUG_ON((mux_index > (MUX_PIN_NUM_MAX / MUX_CTL_FIELDS - 1)) || + (mux_field >= MUX_CTL_FIELDS)); + reg = IOMUXSW_MUX_CTL + (mux_index * 4); spin_lock(&gpio_mux_lock); - l = __raw_readl(reg); - l &= ~(0x1ff << (field * 10)); - l |= config << (field * 10); + l = (l & (~mux_mask)) | + (((out << 4) | in) << (mux_field * MUX_CTL_BIT_LEN)); __raw_writel(l, reg); - + /* + * Log a warning if a pin changes ownership + */ + rp = iomux_pin_res_table + mux_index * MUX_CTL_FIELDS + mux_field; + if (out & *rp && *rp != ((out << 4) | in)) { + /* + * Don't call printk if we're tweaking the console uart or + * we'll deadlock. + */ + if (pin != MX31_PIN_CTS1 && + pin != MX31_PIN_RTS1 && + pin != MX31_PIN_DCD_DCE1 && + pin != MX31_PIN_DSR_DTE1 && + pin != MX31_PIN_DTR_DTE1 && + pin != MX31_PIN_RI_DCE1 && + pin != MX31_PIN_DSR_DCE1 && + pin != MX31_PIN_DTR_DCE1 && + pin != MX31_PIN_RXD1 && pin != MX31_PIN_TXD1) { + printk(KERN_ERR "iomux_config_mux: Warning: iomux pin" + " config changed, index=%d field=%d, " + " prev=0x%x new=0x%x\n", mux_index, mux_field, + *rp, (out << 4) | in); + } + ret = -EINVAL; + } + *rp = (out << 4) | in; spin_unlock(&gpio_mux_lock); + + return ret; } -EXPORT_SYMBOL(mxc_iomux_set_pad); -/* - * allocs a single pin: - * - reserves the pin so that it is not claimed by another driver - * - setups the iomux according to the configuration +/*! + * Request ownership for an IO pin. This function has to be the first one + * being called before that pin is used. The caller has to check the + * return value to make sure it returns 0. + * + * @param pin a name defined by \b iomux_pin_name_t + * @param out an output function as defined in \b #iomux_pin_ocfg_t + * @param in an input function as defined in \b #iomux_pin_icfg_t + * + * @return 0 if successful; Non-zero otherwise */ -int mxc_iomux_alloc_pin(const unsigned int pin, const char *label) +int mxc_request_iomux(iomux_pin_name_t pin, iomux_pin_ocfg_t out, + iomux_pin_icfg_t in) { - unsigned pad = pin & IOMUX_PADNUM_MASK; - - if (pad >= (PIN_MAX + 1)) { - printk(KERN_ERR "mxc_iomux: Attempt to request nonexistant pin %u for \"%s\"\n", - pad, label ? label : "?"); - return -EINVAL; - } - - if (test_and_set_bit(pad, mxc_pin_alloc_map)) { - printk(KERN_ERR "mxc_iomux: pin %u already used. Allocation for \"%s\" failed\n", - pad, label ? label : "?"); - return -EBUSY; + int ret = iomux_config_mux(pin, out, in); + if (out == OUTPUTCONFIG_GPIO && in == INPUTCONFIG_GPIO) { + ret |= gpio_request(IOMUX_TO_GPIO(pin), NULL); } - mxc_iomux_mode(pin); - - return 0; + return ret; } -EXPORT_SYMBOL(mxc_iomux_alloc_pin); -int mxc_iomux_setup_multiple_pins(unsigned int *pin_list, unsigned count, - const char *label) +/*! + * Release ownership for an IO pin + * + * @param pin a name defined by \b iomux_pin_name_t + * @param out an output function as defined in \b #iomux_pin_ocfg_t + * @param in an input function as defined in \b #iomux_pin_icfg_t + */ +void mxc_free_iomux(iomux_pin_name_t pin, iomux_pin_ocfg_t out, + iomux_pin_icfg_t in) { - unsigned int *p = pin_list; - int i; - int ret = -EINVAL; - - for (i = 0; i < count; i++) { - ret = mxc_iomux_alloc_pin(*p, label); - if (ret) - goto setup_error; - p++; - } - return 0; + u32 mux_index = PIN_TO_IOMUX_INDEX(pin); + u32 mux_field = PIN_TO_IOMUX_FIELD(pin); + u8 *rp = iomux_pin_res_table + mux_index * MUX_CTL_FIELDS + mux_field; -setup_error: - mxc_iomux_release_multiple_pins(pin_list, i); - return ret; + BUG_ON((mux_index > (MUX_PIN_NUM_MAX / MUX_CTL_FIELDS - 1)) || + (mux_field >= MUX_CTL_FIELDS)); + + *rp = 0; + if (out == OUTPUTCONFIG_GPIO && in == INPUTCONFIG_GPIO) { + gpio_free(IOMUX_TO_GPIO(pin)); + } } -EXPORT_SYMBOL(mxc_iomux_setup_multiple_pins); -void mxc_iomux_release_pin(const unsigned int pin) +/*! + * This function configures the pad value for a IOMUX pin. + * + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param config the ORed value of elements defined in \b #iomux_pad_config_t + */ +void mxc_iomux_set_pad(iomux_pin_name_t pin, u32 config) { - unsigned pad = pin & IOMUX_PADNUM_MASK; + void __iomem *reg; + u32 l; + u32 pad_index = (pin >> PAD_I) & ((1 << (PAD_F - PAD_I)) - 1); + u32 pad_field = (pin >> PAD_F) & ((1 << (MUX_IO_I - PAD_F)) - 1); + u32 pad_mask = GET_FIELD_MASK(MUX_PAD_BIT_LEN, pad_field); - if (pad < (PIN_MAX + 1)) - clear_bit(pad, mxc_pin_alloc_map); + BUG_ON((pad_index > (PAD_CTL_NUM_MAX / PAD_CTL_FIELDS - 1)) || + (pad_field >= PAD_CTL_FIELDS)); + + reg = IOMUXSW_PAD_CTL + (pad_index * 4); + spin_lock(&gpio_mux_lock); + l = __raw_readl(reg); + l = (l & (~pad_mask)) | (config << (pad_field * MUX_PAD_BIT_LEN)); + __raw_writel(l, reg); + spin_unlock(&gpio_mux_lock); } -EXPORT_SYMBOL(mxc_iomux_release_pin); -void mxc_iomux_release_multiple_pins(unsigned int *pin_list, int count) +/* + * FIXED ME: for backward compatible. to be removed! + */ +void iomux_config_pad(iomux_pin_name_t pin, u32 config) { - unsigned int *p = pin_list; - int i; - - for (i = 0; i < count; i++) { - mxc_iomux_release_pin(*p); - p++; - } + mxc_iomux_set_pad(pin, config); } -EXPORT_SYMBOL(mxc_iomux_release_multiple_pins); -/* +/*! * This function enables/disables the general purpose function for a particular * signal. + * + * @param gp one signal as defined in \b #iomux_gp_func_t + * @param en \b #true to enable; \b #false to disable */ -void mxc_iomux_set_gpr(enum iomux_gp_func gp, bool en) +void mxc_iomux_set_gpr(iomux_gp_func_t gp, bool en) { u32 l; spin_lock(&gpio_mux_lock); l = __raw_readl(IOMUXGPR); - if (en) + if (en) { l |= gp; - else + } else { l &= ~gp; - + } __raw_writel(l, IOMUXGPR); spin_unlock(&gpio_mux_lock); } + +/*! + * FIXED ME: for backward compatible. to be removed! + */ +void iomux_config_gpr(iomux_gp_func_t gp, bool en) +{ + mxc_iomux_set_gpr(gp, en); +} + +EXPORT_SYMBOL(mxc_request_iomux); +EXPORT_SYMBOL(mxc_free_iomux); +EXPORT_SYMBOL(mxc_iomux_set_pad); EXPORT_SYMBOL(mxc_iomux_set_gpr); +EXPORT_SYMBOL(iomux_config_pad); +EXPORT_SYMBOL(iomux_config_gpr); +EXPORT_SYMBOL(iomux_config_mux); diff --git a/arch/arm/mach-mx3/iomux.h b/arch/arm/mach-mx3/iomux.h new file mode 100644 index 000000000000..194e091ec656 --- /dev/null +++ b/arch/arm/mach-mx3/iomux.h @@ -0,0 +1,186 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MACH_MX31_IOMUX_H__ +#define __MACH_MX31_IOMUX_H__ + +#include <linux/types.h> +#include <mach/gpio.h> +#include "mx31_pins.h" + +typedef unsigned int iomux_pin_name_t; +/*! + * @file mach-mx3/iomux.h + * + * @brief I/O Muxing control definitions and functions + * + * @ingroup GPIO_MX31 + */ + +/*! + * various IOMUX output functions + */ +typedef enum iomux_output_config { + OUTPUTCONFIG_GPIO = 0, /*!< used as GPIO */ + OUTPUTCONFIG_FUNC, /*!< used as function */ + OUTPUTCONFIG_ALT1, /*!< used as alternate function 1 */ + OUTPUTCONFIG_ALT2, /*!< used as alternate function 2 */ + OUTPUTCONFIG_ALT3, /*!< used as alternate function 3 */ + OUTPUTCONFIG_ALT4, /*!< used as alternate function 4 */ + OUTPUTCONFIG_ALT5, /*!< used as alternate function 5 */ + OUTPUTCONFIG_ALT6 /*!< used as alternate function 6 */ +} iomux_pin_ocfg_t; + +/*! + * various IOMUX input functions + */ +typedef enum iomux_input_config { + INPUTCONFIG_NONE = 0, /*!< not configured for input */ + INPUTCONFIG_GPIO = 1 << 0, /*!< used as GPIO */ + INPUTCONFIG_FUNC = 1 << 1, /*!< used as function */ + INPUTCONFIG_ALT1 = 1 << 2, /*!< used as alternate function 1 */ + INPUTCONFIG_ALT2 = 1 << 3 /*!< used as alternate function 2 */ +} iomux_pin_icfg_t; + +/*! + * various IOMUX pad functions + */ +typedef enum iomux_pad_config { + PAD_CTL_NOLOOPBACK = 0x0 << 9, + PAD_CTL_LOOPBACK = 0x1 << 9, + PAD_CTL_PKE_NONE = 0x0 << 8, + PAD_CTL_PKE_ENABLE = 0x1 << 8, + PAD_CTL_PUE_KEEPER = 0x0 << 7, + PAD_CTL_PUE_PUD = 0x1 << 7, + PAD_CTL_100K_PD = 0x0 << 5, + PAD_CTL_100K_PU = 0x1 << 5, + PAD_CTL_47K_PU = 0x2 << 5, + PAD_CTL_22K_PU = 0x3 << 5, + PAD_CTL_HYS_CMOS = 0x0 << 4, + PAD_CTL_HYS_SCHMITZ = 0x1 << 4, + PAD_CTL_ODE_CMOS = 0x0 << 3, + PAD_CTL_ODE_OpenDrain = 0x1 << 3, + PAD_CTL_DRV_NORMAL = 0x0 << 1, + PAD_CTL_DRV_HIGH = 0x1 << 1, + PAD_CTL_DRV_MAX = 0x2 << 1, + PAD_CTL_SRE_SLOW = 0x0 << 0, + PAD_CTL_SRE_FAST = 0x1 << 0 +} iomux_pad_config_t; + +/*! + * various IOMUX general purpose functions + */ +typedef enum iomux_gp_func { + MUX_PGP_FIRI = 0x1 << 0, + MUX_DDR_MODE = 0x1 << 1, + MUX_PGP_CSPI_BB = 0x1 << 2, + MUX_PGP_ATA_1 = 0x1 << 3, + MUX_PGP_ATA_2 = 0x1 << 4, + MUX_PGP_ATA_3 = 0x1 << 5, + MUX_PGP_ATA_4 = 0x1 << 6, + MUX_PGP_ATA_5 = 0x1 << 7, + MUX_PGP_ATA_6 = 0x1 << 8, + MUX_PGP_ATA_7 = 0x1 << 9, + MUX_PGP_ATA_8 = 0x1 << 10, + MUX_PGP_UH2 = 0x1 << 11, + MUX_SDCTL_CSD0_SEL = 0x1 << 12, + MUX_SDCTL_CSD1_SEL = 0x1 << 13, + MUX_CSPI1_UART3 = 0x1 << 14, + MUX_EXTDMAREQ2_MBX_SEL = 0x1 << 15, + MUX_TAMPER_DETECT_EN = 0x1 << 16, + MUX_PGP_USB_4WIRE = 0x1 << 17, + MUX_PGB_USB_COMMON = 0x1 << 18, + MUX_SDHC_MEMSTICK1 = 0x1 << 19, + MUX_SDHC_MEMSTICK2 = 0x1 << 20, + MUX_PGP_SPLL_BYP = 0x1 << 21, + MUX_PGP_UPLL_BYP = 0x1 << 22, + MUX_PGP_MSHC1_CLK_SEL = 0x1 << 23, + MUX_PGP_MSHC2_CLK_SEL = 0x1 << 24, + MUX_CSPI3_UART5_SEL = 0x1 << 25, + MUX_PGP_ATA_9 = 0x1 << 26, + MUX_PGP_USB_SUSPEND = 0x1 << 27, + MUX_PGP_USB_OTG_LOOPBACK = 0x1 << 28, + MUX_PGP_USB_HS1_LOOPBACK = 0x1 << 29, + MUX_PGP_USB_HS2_LOOPBACK = 0x1 << 30, + MUX_CLKO_DDR_MODE = 0x1 << 31, +} iomux_gp_func_t; + +/*! + * This function is used to configure a pin through the IOMUX module. + * + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param out an output function as defined in \b #iomux_pin_ocfg_t + * @param in an input function as defined in \b #iomux_pin_icfg_t + * @return 0 if successful; Non-zero otherwise + */ +int iomux_config_mux(iomux_pin_name_t pin, iomux_pin_ocfg_t out, + iomux_pin_icfg_t in); + +/*! + * This function configures the pad value for a IOMUX pin. + * + * @param pin a pin number as defined in \b #iomux_pins + * @param config ORed value of elements defined in \b #iomux_pad_config_t + */ +void iomux_config_pad(iomux_pin_name_t pin, __u32 config); + +/*! + * This function enables/disables the general purpose function for a particular + * signal. + * + * @param gp one signal as defined in \b #iomux_gp_func_t + * @param en \b #true to enable; \b #false to disable + */ +void iomux_config_gpr(iomux_gp_func_t gp, bool en); + +/*! + * Request ownership for an IO pin. This function has to be the first one + * being called before that pin is used. The caller has to check the + * return value to make sure it returns 0. + * + * @param pin a name defined by \b iomux_pin_name_t + * @param out an output function as defined in \b #iomux_pin_ocfg_t + * @param in an input function as defined in \b #iomux_pin_icfg_t + * + * @return 0 if successful; Non-zero otherwise + */ +int mxc_request_iomux(iomux_pin_name_t pin, iomux_pin_ocfg_t out, + iomux_pin_icfg_t in); + +/*! + * Release ownership for an IO pin + * + * @param pin a name defined by \b iomux_pin_name_t + * @param out an output function as defined in \b #iomux_pin_ocfg_t + * @param in an input function as defined in \b #iomux_pin_icfg_t + */ +void mxc_free_iomux(iomux_pin_name_t pin, iomux_pin_ocfg_t out, + iomux_pin_icfg_t in); + +/*! + * This function enables/disables the general purpose function for a particular + * signal. + * + * @param gp one signal as defined in \b #iomux_gp_func_t + * @param en \b #true to enable; \b #false to disable + */ +void mxc_iomux_set_gpr(iomux_gp_func_t gp, bool en); + +/*! + * This function configures the pad value for a IOMUX pin. + * + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param config the ORed value of elements defined in \b #iomux_pad_config_t + */ +void mxc_iomux_set_pad(iomux_pin_name_t pin, u32 config); + +#endif diff --git a/arch/arm/mach-mx3/mm.c b/arch/arm/mach-mx3/mm.c index 1f5fdd456cb9..de000f01a193 100644 --- a/arch/arm/mach-mx3/mm.c +++ b/arch/arm/mach-mx3/mm.c @@ -30,7 +30,6 @@ #include <mach/common.h> #include <mach/hardware.h> - /*! * @file mm.c * @@ -43,7 +42,7 @@ * This table defines static virtual address mappings for I/O regions. * These are the mappings common across all MX3 boards. */ -static struct map_desc mxc_io_desc[] __initdata = { +static struct map_desc mx31_io_desc[] __initdata = { { .virtual = X_MEMC_BASE_ADDR_VIRT, .pfn = __phys_to_pfn(X_MEMC_BASE_ADDR), @@ -64,6 +63,16 @@ static struct map_desc mxc_io_desc[] __initdata = { .pfn = __phys_to_pfn(AIPS2_BASE_ADDR), .length = AIPS2_SIZE, .type = MT_DEVICE_NONSHARED + }, { + .virtual = SPBA0_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(SPBA0_BASE_ADDR), + .length = SPBA0_SIZE, + .type = MT_DEVICE_NONSHARED + }, { + .virtual = MX31_IRAM_BASE_ADDR_VIRT & 0xFFF00000, + .pfn = __phys_to_pfn(MX31_IRAM_BASE_ADDR & 0xFFF00000), + .length = SZ_1M, + .type = MT_DEVICE_NONSHARED }, }; @@ -76,14 +85,7 @@ void __init mx31_map_io(void) { mxc_set_cpu_type(MXC_CPU_MX31); - iotable_init(mxc_io_desc, ARRAY_SIZE(mxc_io_desc)); -} - -void __init mx35_map_io(void) -{ - mxc_set_cpu_type(MXC_CPU_MX35); - - iotable_init(mxc_io_desc, ARRAY_SIZE(mxc_io_desc)); + iotable_init(mx31_io_desc, ARRAY_SIZE(mx31_io_desc)); } #ifdef CONFIG_CACHE_L2X0 diff --git a/arch/arm/mach-mx3/mx31_pins.h b/arch/arm/mach-mx3/mx31_pins.h new file mode 100644 index 000000000000..2e2274922069 --- /dev/null +++ b/arch/arm/mach-mx3/mx31_pins.h @@ -0,0 +1,429 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_ARCH_MXC_MX31_PINS_H__ +#define __ASM_ARCH_MXC_MX31_PINS_H__ + +/*! + * @file arch-mxc/mx31_pins.h + * + * @brief MX31 I/O Pin List + * + * @ingroup GPIO_MX31 + */ + +#ifndef __ASSEMBLY__ + +/*! + * @name IOMUX/PAD Bit field definitions + */ + +/*! @{ */ + +/*! + * In order to identify pins more effectively, each mux-controlled pin's + * enumerated value is constructed in the following way: + * + * ------------------------------------------------------------------- + * 31-29 | 28 - 24 |23 - 21| 20 | 19 - 18 | 17 - 10| 9 - 8 | 7 - 0 + * ------------------------------------------------------------------- + * IO_P | IO_I | RSVD | PAD_F | PAD_I | MUX_F | MUX_I + * ------------------------------------------------------------------- + * + * Bit 0 to 7 contains MUX_I used to identify the register + * offset (0-based. base is IOMUX_module_base + 0xC) defined in the Section + * "sw_pad_ctl & sw_mux_ctl details" of the IC Spec. Bit 8 to 9 is MUX_F which + * contains the offset value defined WITHIN the same register (each IOMUX + * control register contains four 8-bit fields for four different pins). The + * similar field definitions are used for the pad control register. + * For example, the MX31_PIN_A0 is defined in the enumeration: + * ( 73 << MUX_I) | (0 << MUX_F)|( 98 << PAD_I) | (0 << PAD_F) + * It means the mux control register is at register offset 73. So the absolute + * address is: 0xC+73*4=0x130 0 << MUX_F means the control bits are at the + * least significant bits within the register. The pad control register offset + * is: 0x154+98*4=0x2DC and also occupy the least significant bits within the + * register. + */ + +/*! + * Starting bit position within each entry of \b iomux_pins to represent the + * MUX control register index (0-based) + */ +#define MUX_I 0 + +/*! + * Starting bit position within each entry of \b iomux_pins to represent the + * field within IOMUX control register for control bits + * (legal values are 0, 1, 2, 3) + */ +#define MUX_F 8 + +/*! + * Starting bit position within each entry of \b iomux_pins to represent the + * PAD control register index (0-based) + */ +#define PAD_I 10 + +/*! + * Starting bit position within each entry of \b iomux_pins to represent the + * field within PAD control register for control bits + * (legal values are 0, 1, 2) + */ +#define PAD_F 18 + +#define _MXC_BUILD_PIN(gp,gi,mi,mf,pi,pf) \ + ((gp) << MUX_IO_P) | ((gi) << MUX_IO_I) | ((mi) << MUX_I) | \ + ((mf) << MUX_F) | ((pi) << PAD_I) | ((pf) << PAD_F) + +#define _MXC_BUILD_GPIO_PIN(gp,gi,mi,mf,pi,pf) \ + _MXC_BUILD_PIN(gp,gi,mi,mf,pi,pf) +#define _MXC_BUILD_NON_GPIO_PIN(mi,mf,pi,pf) \ + _MXC_BUILD_PIN(7,0,mi,mf,pi,pf) + +/*! + * This enumeration is constructed based on the Section + * "sw_pad_ctl & sw_mux_ctl details" of the MX31 IC Spec. Each enumerated + * value is constructed based on the rules described above. + */ +enum iomux_pins { + MX31_PIN_CSPI3_MISO = _MXC_BUILD_NON_GPIO_PIN(0, 3, 1, 2), + MX31_PIN_CSPI3_SCLK = _MXC_BUILD_NON_GPIO_PIN(0, 2, 1, 1), + MX31_PIN_CSPI3_SPI_RDY = _MXC_BUILD_NON_GPIO_PIN(0, 1, 1, 0), + MX31_PIN_TTM_PAD = _MXC_BUILD_NON_GPIO_PIN(0, 0, 0, 2), + MX31_PIN_ATA_RESET_B = _MXC_BUILD_GPIO_PIN(2, 31, 1, 3, 3, 0), + MX31_PIN_CE_CONTROL = _MXC_BUILD_NON_GPIO_PIN(1, 2, 2, 2), + MX31_PIN_CLKSS = _MXC_BUILD_NON_GPIO_PIN(1, 1, 2, 1), + MX31_PIN_CSPI3_MOSI = _MXC_BUILD_NON_GPIO_PIN(1, 0, 2, 0), + MX31_PIN_ATA_CS1 = _MXC_BUILD_GPIO_PIN(2, 27, 2, 3, 4, 1), + MX31_PIN_ATA_DIOR = _MXC_BUILD_GPIO_PIN(2, 28, 2, 2, 4, 0), + MX31_PIN_ATA_DIOW = _MXC_BUILD_GPIO_PIN(2, 29, 2, 1, 3, 2), + MX31_PIN_ATA_DMACK = _MXC_BUILD_GPIO_PIN(2, 30, 2, 0, 3, 1), + MX31_PIN_SD1_DATA1 = _MXC_BUILD_GPIO_PIN(1, 29, 3, 3, 5, 2), + MX31_PIN_SD1_DATA2 = _MXC_BUILD_GPIO_PIN(1, 30, 3, 2, 5, 1), + MX31_PIN_SD1_DATA3 = _MXC_BUILD_GPIO_PIN(1, 31, 3, 1, 5, 0), + MX31_PIN_ATA_CS0 = _MXC_BUILD_GPIO_PIN(2, 26, 3, 0, 4, 2), + MX31_PIN_D3_SPL = _MXC_BUILD_NON_GPIO_PIN(4, 3, 7, 0), + MX31_PIN_SD1_CMD = _MXC_BUILD_GPIO_PIN(1, 26, 4, 2, 6, 2), + MX31_PIN_SD1_CLK = _MXC_BUILD_GPIO_PIN(1, 27, 4, 1, 6, 1), + MX31_PIN_SD1_DATA0 = _MXC_BUILD_GPIO_PIN(1, 28, 4, 0, 6, 0), + MX31_PIN_VSYNC3 = _MXC_BUILD_NON_GPIO_PIN(5, 3, 8, 1), + MX31_PIN_CONTRAST = _MXC_BUILD_NON_GPIO_PIN(5, 2, 8, 0), + MX31_PIN_D3_REV = _MXC_BUILD_NON_GPIO_PIN(5, 1, 7, 2), + MX31_PIN_D3_CLS = _MXC_BUILD_NON_GPIO_PIN(5, 0, 7, 1), + MX31_PIN_SER_RS = _MXC_BUILD_GPIO_PIN(2, 25, 6, 3, 9, 2), + MX31_PIN_PAR_RS = _MXC_BUILD_NON_GPIO_PIN(6, 2, 9, 1), + MX31_PIN_WRITE = _MXC_BUILD_NON_GPIO_PIN(6, 1, 9, 0), + MX31_PIN_READ = _MXC_BUILD_NON_GPIO_PIN(6, 0, 8, 2), + MX31_PIN_SD_D_IO = _MXC_BUILD_GPIO_PIN(2, 21, 7, 3, 11, 0), + MX31_PIN_SD_D_CLK = _MXC_BUILD_GPIO_PIN(2, 22, 7, 2, 10, 2), + MX31_PIN_LCS0 = _MXC_BUILD_GPIO_PIN(2, 23, 7, 1, 10, 1), + MX31_PIN_LCS1 = _MXC_BUILD_GPIO_PIN(2, 24, 7, 0, 10, 0), + MX31_PIN_HSYNC = _MXC_BUILD_NON_GPIO_PIN(8, 3, 12, 1), + MX31_PIN_FPSHIFT = _MXC_BUILD_NON_GPIO_PIN(8, 2, 12, 0), + MX31_PIN_DRDY0 = _MXC_BUILD_NON_GPIO_PIN(8, 1, 11, 2), + MX31_PIN_SD_D_I = _MXC_BUILD_GPIO_PIN(2, 20, 8, 0, 11, 1), + MX31_PIN_LD15 = _MXC_BUILD_NON_GPIO_PIN(9, 3, 13, 2), + MX31_PIN_LD16 = _MXC_BUILD_NON_GPIO_PIN(9, 2, 13, 1), + MX31_PIN_LD17 = _MXC_BUILD_NON_GPIO_PIN(9, 1, 13, 0), + MX31_PIN_VSYNC0 = _MXC_BUILD_NON_GPIO_PIN(9, 0, 12, 2), + MX31_PIN_LD11 = _MXC_BUILD_NON_GPIO_PIN(10, 3, 15, 0), + MX31_PIN_LD12 = _MXC_BUILD_NON_GPIO_PIN(10, 2, 14, 2), + MX31_PIN_LD13 = _MXC_BUILD_NON_GPIO_PIN(10, 1, 14, 1), + MX31_PIN_LD14 = _MXC_BUILD_NON_GPIO_PIN(10, 0, 14, 0), + MX31_PIN_LD7 = _MXC_BUILD_NON_GPIO_PIN(11, 3, 16, 1), + MX31_PIN_LD8 = _MXC_BUILD_NON_GPIO_PIN(11, 2, 16, 0), + MX31_PIN_LD9 = _MXC_BUILD_NON_GPIO_PIN(11, 1, 15, 2), + MX31_PIN_LD10 = _MXC_BUILD_NON_GPIO_PIN(11, 0, 15, 1), + MX31_PIN_LD3 = _MXC_BUILD_NON_GPIO_PIN(12, 3, 17, 2), + MX31_PIN_LD4 = _MXC_BUILD_NON_GPIO_PIN(12, 2, 17, 1), + MX31_PIN_LD5 = _MXC_BUILD_NON_GPIO_PIN(12, 1, 17, 0), + MX31_PIN_LD6 = _MXC_BUILD_NON_GPIO_PIN(12, 0, 16, 2), + MX31_PIN_USBH2_DATA1 = _MXC_BUILD_NON_GPIO_PIN(13, 3, 19, 0), + MX31_PIN_LD0 = _MXC_BUILD_NON_GPIO_PIN(13, 2, 18, 2), + MX31_PIN_LD1 = _MXC_BUILD_NON_GPIO_PIN(13, 1, 18, 1), + MX31_PIN_LD2 = _MXC_BUILD_NON_GPIO_PIN(13, 0, 18, 0), + MX31_PIN_USBH2_DIR = _MXC_BUILD_NON_GPIO_PIN(14, 3, 20, 1), + MX31_PIN_USBH2_STP = _MXC_BUILD_NON_GPIO_PIN(14, 2, 20, 0), + MX31_PIN_USBH2_NXT = _MXC_BUILD_NON_GPIO_PIN(14, 1, 19, 2), + MX31_PIN_USBH2_DATA0 = _MXC_BUILD_NON_GPIO_PIN(14, 0, 19, 1), + MX31_PIN_USBOTG_DATA5 = _MXC_BUILD_NON_GPIO_PIN(15, 3, 21, 2), + MX31_PIN_USBOTG_DATA6 = _MXC_BUILD_NON_GPIO_PIN(15, 2, 21, 1), + MX31_PIN_USBOTG_DATA7 = _MXC_BUILD_NON_GPIO_PIN(15, 1, 21, 0), + MX31_PIN_USBH2_CLK = _MXC_BUILD_NON_GPIO_PIN(15, 0, 20, 2), + MX31_PIN_USBOTG_DATA1 = _MXC_BUILD_NON_GPIO_PIN(16, 3, 23, 0), + MX31_PIN_USBOTG_DATA2 = _MXC_BUILD_NON_GPIO_PIN(16, 2, 22, 2), + MX31_PIN_USBOTG_DATA3 = _MXC_BUILD_NON_GPIO_PIN(16, 1, 22, 1), + MX31_PIN_USBOTG_DATA4 = _MXC_BUILD_NON_GPIO_PIN(16, 0, 22, 0), + MX31_PIN_USBOTG_DIR = _MXC_BUILD_NON_GPIO_PIN(17, 3, 24, 1), + MX31_PIN_USBOTG_STP = _MXC_BUILD_NON_GPIO_PIN(17, 2, 24, 0), + MX31_PIN_USBOTG_NXT = _MXC_BUILD_NON_GPIO_PIN(17, 1, 23, 2), + MX31_PIN_USBOTG_DATA0 = _MXC_BUILD_NON_GPIO_PIN(17, 0, 23, 1), + MX31_PIN_USB_PWR = _MXC_BUILD_GPIO_PIN(0, 29, 18, 3, 25, 2), + MX31_PIN_USB_OC = _MXC_BUILD_GPIO_PIN(0, 30, 18, 2, 25, 1), + MX31_PIN_USB_BYP = _MXC_BUILD_GPIO_PIN(0, 31, 18, 1, 25, 0), + MX31_PIN_USBOTG_CLK = _MXC_BUILD_NON_GPIO_PIN(18, 0, 24, 2), + MX31_PIN_TDO = _MXC_BUILD_NON_GPIO_PIN(19, 3, 27, 0), + MX31_PIN_TRSTB = _MXC_BUILD_NON_GPIO_PIN(19, 2, 26, 2), + MX31_PIN_DE_B = _MXC_BUILD_NON_GPIO_PIN(19, 1, 26, 1), + MX31_PIN_SJC_MOD = _MXC_BUILD_NON_GPIO_PIN(19, 0, 26, 0), + MX31_PIN_RTCK = _MXC_BUILD_NON_GPIO_PIN(20, 3, 28, 1), + MX31_PIN_TCK = _MXC_BUILD_NON_GPIO_PIN(20, 2, 28, 0), + MX31_PIN_TMS = _MXC_BUILD_NON_GPIO_PIN(20, 1, 27, 2), + MX31_PIN_TDI = _MXC_BUILD_NON_GPIO_PIN(20, 0, 27, 1), + MX31_PIN_KEY_COL4 = _MXC_BUILD_GPIO_PIN(1, 22, 21, 3, 29, 2), + MX31_PIN_KEY_COL5 = _MXC_BUILD_GPIO_PIN(1, 23, 21, 2, 29, 1), + MX31_PIN_KEY_COL6 = _MXC_BUILD_GPIO_PIN(1, 24, 21, 1, 29, 0), + MX31_PIN_KEY_COL7 = _MXC_BUILD_GPIO_PIN(1, 25, 21, 0, 28, 2), + MX31_PIN_KEY_COL0 = _MXC_BUILD_NON_GPIO_PIN(22, 3, 31, 0), + MX31_PIN_KEY_COL1 = _MXC_BUILD_NON_GPIO_PIN(22, 2, 30, 2), + MX31_PIN_KEY_COL2 = _MXC_BUILD_NON_GPIO_PIN(22, 1, 30, 1), + MX31_PIN_KEY_COL3 = _MXC_BUILD_NON_GPIO_PIN(22, 0, 30, 0), + MX31_PIN_KEY_ROW4 = _MXC_BUILD_GPIO_PIN(1, 18, 23, 3, 32, 1), + MX31_PIN_KEY_ROW5 = _MXC_BUILD_GPIO_PIN(1, 19, 23, 2, 32, 0), + MX31_PIN_KEY_ROW6 = _MXC_BUILD_GPIO_PIN(1, 20, 23, 1, 31, 2), + MX31_PIN_KEY_ROW7 = _MXC_BUILD_GPIO_PIN(1, 21, 23, 0, 31, 1), + MX31_PIN_KEY_ROW0 = _MXC_BUILD_NON_GPIO_PIN(24, 3, 33, 2), + MX31_PIN_KEY_ROW1 = _MXC_BUILD_NON_GPIO_PIN(24, 2, 33, 1), + MX31_PIN_KEY_ROW2 = _MXC_BUILD_NON_GPIO_PIN(24, 1, 33, 0), + MX31_PIN_KEY_ROW3 = _MXC_BUILD_NON_GPIO_PIN(24, 0, 32, 2), + MX31_PIN_TXD2 = _MXC_BUILD_GPIO_PIN(0, 28, 25, 3, 35, 0), + MX31_PIN_RTS2 = _MXC_BUILD_NON_GPIO_PIN(25, 2, 34, 2), + MX31_PIN_CTS2 = _MXC_BUILD_NON_GPIO_PIN(25, 1, 34, 1), + MX31_PIN_BATT_LINE = _MXC_BUILD_GPIO_PIN(1, 17, 25, 0, 34, 0), + MX31_PIN_RI_DTE1 = _MXC_BUILD_GPIO_PIN(1, 14, 26, 3, 36, 1), + MX31_PIN_DCD_DTE1 = _MXC_BUILD_GPIO_PIN(1, 15, 26, 2, 36, 0), + MX31_PIN_DTR_DCE2 = _MXC_BUILD_GPIO_PIN(1, 16, 26, 1, 35, 2), + MX31_PIN_RXD2 = _MXC_BUILD_GPIO_PIN(0, 27, 26, 0, 35, 1), + MX31_PIN_RI_DCE1 = _MXC_BUILD_GPIO_PIN(1, 10, 27, 3, 37, 2), + MX31_PIN_DCD_DCE1 = _MXC_BUILD_GPIO_PIN(1, 11, 27, 2, 37, 1), + MX31_PIN_DTR_DTE1 = _MXC_BUILD_GPIO_PIN(1, 12, 27, 1, 37, 0), + MX31_PIN_DSR_DTE1 = _MXC_BUILD_GPIO_PIN(1, 13, 27, 0, 36, 2), + MX31_PIN_RTS1 = _MXC_BUILD_GPIO_PIN(1, 6, 28, 3, 39, 0), + MX31_PIN_CTS1 = _MXC_BUILD_GPIO_PIN(1, 7, 28, 2, 38, 2), + MX31_PIN_DTR_DCE1 = _MXC_BUILD_GPIO_PIN(1, 8, 28, 1, 38, 1), + MX31_PIN_DSR_DCE1 = _MXC_BUILD_GPIO_PIN(1, 9, 28, 0, 38, 0), + MX31_PIN_CSPI2_SCLK = _MXC_BUILD_NON_GPIO_PIN(29, 3, 40, 1), + MX31_PIN_CSPI2_SPI_RDY = _MXC_BUILD_NON_GPIO_PIN(29, 2, 40, 0), + MX31_PIN_RXD1 = _MXC_BUILD_GPIO_PIN(1, 4, 29, 1, 39, 2), + MX31_PIN_TXD1 = _MXC_BUILD_GPIO_PIN(1, 5, 29, 0, 39, 1), + MX31_PIN_CSPI2_MISO = _MXC_BUILD_NON_GPIO_PIN(30, 3, 41, 2), + MX31_PIN_CSPI2_SS0 = _MXC_BUILD_NON_GPIO_PIN(30, 2, 41, 1), + MX31_PIN_CSPI2_SS1 = _MXC_BUILD_NON_GPIO_PIN(30, 1, 41, 0), + MX31_PIN_CSPI2_SS2 = _MXC_BUILD_NON_GPIO_PIN(30, 0, 40, 2), + MX31_PIN_CSPI1_SS2 = _MXC_BUILD_NON_GPIO_PIN(31, 3, 43, 0), + MX31_PIN_CSPI1_SCLK = _MXC_BUILD_NON_GPIO_PIN(31, 2, 42, 2), + MX31_PIN_CSPI1_SPI_RDY = _MXC_BUILD_NON_GPIO_PIN(31, 1, 42, 1), + MX31_PIN_CSPI2_MOSI = _MXC_BUILD_NON_GPIO_PIN(31, 0, 42, 0), + MX31_PIN_CSPI1_MOSI = _MXC_BUILD_NON_GPIO_PIN(32, 3, 44, 1), + MX31_PIN_CSPI1_MISO = _MXC_BUILD_NON_GPIO_PIN(32, 2, 44, 0), + MX31_PIN_CSPI1_SS0 = _MXC_BUILD_NON_GPIO_PIN(32, 1, 43, 2), + MX31_PIN_CSPI1_SS1 = _MXC_BUILD_NON_GPIO_PIN(32, 0, 43, 1), + MX31_PIN_STXD6 = _MXC_BUILD_GPIO_PIN(0, 23, 33, 3, 45, 2), + MX31_PIN_SRXD6 = _MXC_BUILD_GPIO_PIN(0, 24, 33, 2, 45, 1), + MX31_PIN_SCK6 = _MXC_BUILD_GPIO_PIN(0, 25, 33, 1, 45, 0), + MX31_PIN_SFS6 = _MXC_BUILD_GPIO_PIN(0, 26, 33, 0, 44, 2), + MX31_PIN_STXD5 = _MXC_BUILD_GPIO_PIN(0, 21, 34, 3, 47, 0), + MX31_PIN_SRXD5 = _MXC_BUILD_GPIO_PIN(0, 22, 34, 2, 46, 2), + MX31_PIN_SCK5 = _MXC_BUILD_NON_GPIO_PIN(34, 1, 46, 1), + MX31_PIN_SFS5 = _MXC_BUILD_NON_GPIO_PIN(34, 0, 46, 0), + MX31_PIN_STXD4 = _MXC_BUILD_GPIO_PIN(0, 19, 35, 3, 48, 1), + MX31_PIN_SRXD4 = _MXC_BUILD_GPIO_PIN(0, 20, 35, 2, 48, 0), + MX31_PIN_SCK4 = _MXC_BUILD_NON_GPIO_PIN(35, 1, 47, 2), + MX31_PIN_SFS4 = _MXC_BUILD_NON_GPIO_PIN(35, 0, 47, 1), + MX31_PIN_STXD3 = _MXC_BUILD_GPIO_PIN(0, 17, 36, 3, 49, 2), + MX31_PIN_SRXD3 = _MXC_BUILD_GPIO_PIN(0, 18, 36, 2, 49, 1), + MX31_PIN_SCK3 = _MXC_BUILD_NON_GPIO_PIN(36, 1, 49, 0), + MX31_PIN_SFS3 = _MXC_BUILD_NON_GPIO_PIN(36, 0, 48, 2), + MX31_PIN_CSI_HSYNC = _MXC_BUILD_GPIO_PIN(2, 18, 37, 3, 51, 0), + MX31_PIN_CSI_PIXCLK = _MXC_BUILD_GPIO_PIN(2, 19, 37, 2, 50, 2), + MX31_PIN_I2C_CLK = _MXC_BUILD_NON_GPIO_PIN(37, 1, 50, 1), + MX31_PIN_I2C_DAT = _MXC_BUILD_NON_GPIO_PIN(37, 0, 50, 0), + MX31_PIN_CSI_D14 = _MXC_BUILD_GPIO_PIN(2, 14, 38, 3, 52, 1), + MX31_PIN_CSI_D15 = _MXC_BUILD_GPIO_PIN(2, 15, 38, 2, 52, 0), + MX31_PIN_CSI_MCLK = _MXC_BUILD_GPIO_PIN(2, 16, 38, 1, 51, 2), + MX31_PIN_CSI_VSYNC = _MXC_BUILD_GPIO_PIN(2, 17, 38, 0, 51, 1), + MX31_PIN_CSI_D10 = _MXC_BUILD_GPIO_PIN(2, 10, 39, 3, 53, 2), + MX31_PIN_CSI_D11 = _MXC_BUILD_GPIO_PIN(2, 11, 39, 2, 53, 1), + MX31_PIN_CSI_D12 = _MXC_BUILD_GPIO_PIN(2, 12, 39, 1, 53, 0), + MX31_PIN_CSI_D13 = _MXC_BUILD_GPIO_PIN(2, 13, 39, 0, 52, 2), + MX31_PIN_CSI_D6 = _MXC_BUILD_GPIO_PIN(2, 6, 40, 3, 55, 0), + MX31_PIN_CSI_D7 = _MXC_BUILD_GPIO_PIN(2, 7, 40, 2, 54, 2), + MX31_PIN_CSI_D8 = _MXC_BUILD_GPIO_PIN(2, 8, 40, 1, 54, 1), + MX31_PIN_CSI_D9 = _MXC_BUILD_GPIO_PIN(2, 9, 40, 0, 54, 0), + MX31_PIN_M_REQUEST = _MXC_BUILD_NON_GPIO_PIN(41, 3, 56, 1), + MX31_PIN_M_GRANT = _MXC_BUILD_NON_GPIO_PIN(41, 2, 56, 0), + MX31_PIN_CSI_D4 = _MXC_BUILD_GPIO_PIN(2, 4, 41, 1, 55, 2), + MX31_PIN_CSI_D5 = _MXC_BUILD_GPIO_PIN(2, 5, 41, 0, 55, 1), + MX31_PIN_PC_RST = _MXC_BUILD_NON_GPIO_PIN(42, 3, 57, 2), + MX31_PIN_IOIS16 = _MXC_BUILD_NON_GPIO_PIN(42, 2, 57, 1), + MX31_PIN_PC_RW_B = _MXC_BUILD_NON_GPIO_PIN(42, 1, 57, 0), + MX31_PIN_PC_POE = _MXC_BUILD_NON_GPIO_PIN(42, 0, 56, 2), + MX31_PIN_PC_VS1 = _MXC_BUILD_NON_GPIO_PIN(43, 3, 59, 0), + MX31_PIN_PC_VS2 = _MXC_BUILD_NON_GPIO_PIN(43, 2, 58, 2), + MX31_PIN_PC_BVD1 = _MXC_BUILD_NON_GPIO_PIN(43, 1, 58, 1), + MX31_PIN_PC_BVD2 = _MXC_BUILD_NON_GPIO_PIN(43, 0, 58, 0), + MX31_PIN_PC_CD2_B = _MXC_BUILD_NON_GPIO_PIN(44, 3, 60, 1), + MX31_PIN_PC_WAIT_B = _MXC_BUILD_NON_GPIO_PIN(44, 2, 60, 0), + MX31_PIN_PC_READY = _MXC_BUILD_NON_GPIO_PIN(44, 1, 59, 2), + MX31_PIN_PC_PWRON = _MXC_BUILD_NON_GPIO_PIN(44, 0, 59, 1), + MX31_PIN_D2 = _MXC_BUILD_NON_GPIO_PIN(45, 3, 61, 2), + MX31_PIN_D1 = _MXC_BUILD_NON_GPIO_PIN(45, 2, 61, 1), + MX31_PIN_D0 = _MXC_BUILD_NON_GPIO_PIN(45, 1, 61, 0), + MX31_PIN_PC_CD1_B = _MXC_BUILD_NON_GPIO_PIN(45, 0, 60, 2), + MX31_PIN_D6 = _MXC_BUILD_NON_GPIO_PIN(46, 3, 63, 0), + MX31_PIN_D5 = _MXC_BUILD_NON_GPIO_PIN(46, 2, 62, 2), + MX31_PIN_D4 = _MXC_BUILD_NON_GPIO_PIN(46, 1, 62, 1), + MX31_PIN_D3 = _MXC_BUILD_NON_GPIO_PIN(46, 0, 62, 0), + MX31_PIN_D10 = _MXC_BUILD_NON_GPIO_PIN(47, 3, 64, 1), + MX31_PIN_D9 = _MXC_BUILD_NON_GPIO_PIN(47, 2, 64, 0), + MX31_PIN_D8 = _MXC_BUILD_NON_GPIO_PIN(47, 1, 63, 2), + MX31_PIN_D7 = _MXC_BUILD_NON_GPIO_PIN(47, 0, 63, 1), + MX31_PIN_D14 = _MXC_BUILD_NON_GPIO_PIN(48, 3, 65, 2), + MX31_PIN_D13 = _MXC_BUILD_NON_GPIO_PIN(48, 2, 65, 1), + MX31_PIN_D12 = _MXC_BUILD_NON_GPIO_PIN(48, 1, 65, 0), + MX31_PIN_D11 = _MXC_BUILD_NON_GPIO_PIN(48, 0, 64, 2), + MX31_PIN_NFWP_B = _MXC_BUILD_GPIO_PIN(0, 14, 49, 3, 67, 0), + MX31_PIN_NFCE_B = _MXC_BUILD_GPIO_PIN(0, 15, 49, 2, 66, 2), + MX31_PIN_NFRB = _MXC_BUILD_GPIO_PIN(0, 16, 49, 1, 66, 1), + MX31_PIN_D15 = _MXC_BUILD_NON_GPIO_PIN(49, 0, 66, 0), + MX31_PIN_NFWE_B = _MXC_BUILD_GPIO_PIN(0, 10, 50, 3, 68, 1), + MX31_PIN_NFRE_B = _MXC_BUILD_GPIO_PIN(0, 11, 50, 2, 68, 0), + MX31_PIN_NFALE = _MXC_BUILD_GPIO_PIN(0, 12, 50, 1, 67, 2), + MX31_PIN_NFCLE = _MXC_BUILD_GPIO_PIN(0, 13, 50, 0, 67, 1), + MX31_PIN_SDQS0 = _MXC_BUILD_NON_GPIO_PIN(51, 3, 69, 2), + MX31_PIN_SDQS1 = _MXC_BUILD_NON_GPIO_PIN(51, 2, 69, 1), + MX31_PIN_SDQS2 = _MXC_BUILD_NON_GPIO_PIN(51, 1, 69, 0), + MX31_PIN_SDQS3 = _MXC_BUILD_NON_GPIO_PIN(51, 0, 68, 2), + MX31_PIN_SDCKE0 = _MXC_BUILD_NON_GPIO_PIN(52, 3, 71, 0), + MX31_PIN_SDCKE1 = _MXC_BUILD_NON_GPIO_PIN(52, 2, 70, 2), + MX31_PIN_SDCLK = _MXC_BUILD_NON_GPIO_PIN(52, 1, 70, 1), + MX31_PIN_SDCLK_B = _MXC_BUILD_NON_GPIO_PIN(52, 0, 70, 0), + MX31_PIN_RW = _MXC_BUILD_NON_GPIO_PIN(53, 3, 72, 1), + MX31_PIN_RAS = _MXC_BUILD_NON_GPIO_PIN(53, 2, 72, 0), + MX31_PIN_CAS = _MXC_BUILD_NON_GPIO_PIN(53, 1, 71, 2), + MX31_PIN_SDWE = _MXC_BUILD_NON_GPIO_PIN(53, 0, 71, 1), + MX31_PIN_CS5 = _MXC_BUILD_NON_GPIO_PIN(54, 3, 73, 2), + MX31_PIN_ECB = _MXC_BUILD_NON_GPIO_PIN(54, 2, 73, 1), + MX31_PIN_LBA = _MXC_BUILD_NON_GPIO_PIN(54, 1, 73, 0), + MX31_PIN_BCLK = _MXC_BUILD_NON_GPIO_PIN(54, 0, 72, 2), + MX31_PIN_CS1 = _MXC_BUILD_NON_GPIO_PIN(55, 3, 75, 0), + MX31_PIN_CS2 = _MXC_BUILD_NON_GPIO_PIN(55, 2, 74, 2), + MX31_PIN_CS3 = _MXC_BUILD_NON_GPIO_PIN(55, 1, 74, 1), + MX31_PIN_CS4 = _MXC_BUILD_NON_GPIO_PIN(55, 0, 74, 0), + MX31_PIN_EB0 = _MXC_BUILD_NON_GPIO_PIN(56, 3, 76, 1), + MX31_PIN_EB1 = _MXC_BUILD_NON_GPIO_PIN(56, 2, 76, 0), + MX31_PIN_OE = _MXC_BUILD_NON_GPIO_PIN(56, 1, 75, 2), + MX31_PIN_CS0 = _MXC_BUILD_NON_GPIO_PIN(56, 0, 75, 1), + MX31_PIN_DQM0 = _MXC_BUILD_NON_GPIO_PIN(57, 3, 77, 2), + MX31_PIN_DQM1 = _MXC_BUILD_NON_GPIO_PIN(57, 2, 77, 1), + MX31_PIN_DQM2 = _MXC_BUILD_NON_GPIO_PIN(57, 1, 77, 0), + MX31_PIN_DQM3 = _MXC_BUILD_NON_GPIO_PIN(57, 0, 76, 2), + MX31_PIN_SD28 = _MXC_BUILD_NON_GPIO_PIN(58, 3, 79, 0), + MX31_PIN_SD29 = _MXC_BUILD_NON_GPIO_PIN(58, 2, 78, 2), + MX31_PIN_SD30 = _MXC_BUILD_NON_GPIO_PIN(58, 1, 78, 1), + MX31_PIN_SD31 = _MXC_BUILD_NON_GPIO_PIN(58, 0, 78, 0), + MX31_PIN_SD24 = _MXC_BUILD_NON_GPIO_PIN(59, 3, 80, 1), + MX31_PIN_SD25 = _MXC_BUILD_NON_GPIO_PIN(59, 2, 80, 0), + MX31_PIN_SD26 = _MXC_BUILD_NON_GPIO_PIN(59, 1, 79, 2), + MX31_PIN_SD27 = _MXC_BUILD_NON_GPIO_PIN(59, 0, 79, 1), + MX31_PIN_SD20 = _MXC_BUILD_NON_GPIO_PIN(60, 3, 81, 2), + MX31_PIN_SD21 = _MXC_BUILD_NON_GPIO_PIN(60, 2, 81, 1), + MX31_PIN_SD22 = _MXC_BUILD_NON_GPIO_PIN(60, 1, 81, 0), + MX31_PIN_SD23 = _MXC_BUILD_NON_GPIO_PIN(60, 0, 80, 2), + MX31_PIN_SD16 = _MXC_BUILD_NON_GPIO_PIN(61, 3, 83, 0), + MX31_PIN_SD17 = _MXC_BUILD_NON_GPIO_PIN(61, 2, 82, 2), + MX31_PIN_SD18 = _MXC_BUILD_NON_GPIO_PIN(61, 1, 82, 1), + MX31_PIN_SD19 = _MXC_BUILD_NON_GPIO_PIN(61, 0, 82, 0), + MX31_PIN_SD12 = _MXC_BUILD_NON_GPIO_PIN(62, 3, 84, 1), + MX31_PIN_SD13 = _MXC_BUILD_NON_GPIO_PIN(62, 2, 84, 0), + MX31_PIN_SD14 = _MXC_BUILD_NON_GPIO_PIN(62, 1, 83, 2), + MX31_PIN_SD15 = _MXC_BUILD_NON_GPIO_PIN(62, 0, 83, 1), + MX31_PIN_SD8 = _MXC_BUILD_NON_GPIO_PIN(63, 3, 85, 2), + MX31_PIN_SD9 = _MXC_BUILD_NON_GPIO_PIN(63, 2, 85, 1), + MX31_PIN_SD10 = _MXC_BUILD_NON_GPIO_PIN(63, 1, 85, 0), + MX31_PIN_SD11 = _MXC_BUILD_NON_GPIO_PIN(63, 0, 84, 2), + MX31_PIN_SD4 = _MXC_BUILD_NON_GPIO_PIN(64, 3, 87, 0), + MX31_PIN_SD5 = _MXC_BUILD_NON_GPIO_PIN(64, 2, 86, 2), + MX31_PIN_SD6 = _MXC_BUILD_NON_GPIO_PIN(64, 1, 86, 1), + MX31_PIN_SD7 = _MXC_BUILD_NON_GPIO_PIN(64, 0, 86, 0), + MX31_PIN_SD0 = _MXC_BUILD_NON_GPIO_PIN(65, 3, 88, 1), + MX31_PIN_SD1 = _MXC_BUILD_NON_GPIO_PIN(65, 2, 88, 0), + MX31_PIN_SD2 = _MXC_BUILD_NON_GPIO_PIN(65, 1, 87, 2), + MX31_PIN_SD3 = _MXC_BUILD_NON_GPIO_PIN(65, 0, 87, 1), + MX31_PIN_A24 = _MXC_BUILD_NON_GPIO_PIN(66, 3, 89, 2), + MX31_PIN_A25 = _MXC_BUILD_NON_GPIO_PIN(66, 2, 89, 1), + MX31_PIN_SDBA1 = _MXC_BUILD_NON_GPIO_PIN(66, 1, 89, 0), + MX31_PIN_SDBA0 = _MXC_BUILD_NON_GPIO_PIN(66, 0, 88, 2), + MX31_PIN_A20 = _MXC_BUILD_NON_GPIO_PIN(67, 3, 91, 0), + MX31_PIN_A21 = _MXC_BUILD_NON_GPIO_PIN(67, 2, 90, 2), + MX31_PIN_A22 = _MXC_BUILD_NON_GPIO_PIN(67, 1, 90, 1), + MX31_PIN_A23 = _MXC_BUILD_NON_GPIO_PIN(67, 0, 90, 0), + MX31_PIN_A16 = _MXC_BUILD_NON_GPIO_PIN(68, 3, 92, 1), + MX31_PIN_A17 = _MXC_BUILD_NON_GPIO_PIN(68, 2, 92, 0), + MX31_PIN_A18 = _MXC_BUILD_NON_GPIO_PIN(68, 1, 91, 2), + MX31_PIN_A19 = _MXC_BUILD_NON_GPIO_PIN(68, 0, 91, 1), + MX31_PIN_A12 = _MXC_BUILD_NON_GPIO_PIN(69, 3, 93, 2), + MX31_PIN_A13 = _MXC_BUILD_NON_GPIO_PIN(69, 2, 93, 1), + MX31_PIN_A14 = _MXC_BUILD_NON_GPIO_PIN(69, 1, 93, 0), + MX31_PIN_A15 = _MXC_BUILD_NON_GPIO_PIN(69, 0, 92, 2), + MX31_PIN_A9 = _MXC_BUILD_NON_GPIO_PIN(70, 3, 95, 0), + MX31_PIN_A10 = _MXC_BUILD_NON_GPIO_PIN(70, 2, 94, 2), + MX31_PIN_MA10 = _MXC_BUILD_NON_GPIO_PIN(70, 1, 94, 1), + MX31_PIN_A11 = _MXC_BUILD_NON_GPIO_PIN(70, 0, 94, 0), + MX31_PIN_A5 = _MXC_BUILD_NON_GPIO_PIN(71, 3, 96, 1), + MX31_PIN_A6 = _MXC_BUILD_NON_GPIO_PIN(71, 2, 96, 0), + MX31_PIN_A7 = _MXC_BUILD_NON_GPIO_PIN(71, 1, 95, 2), + MX31_PIN_A8 = _MXC_BUILD_NON_GPIO_PIN(71, 0, 95, 1), + MX31_PIN_A1 = _MXC_BUILD_NON_GPIO_PIN(72, 3, 97, 2), + MX31_PIN_A2 = _MXC_BUILD_NON_GPIO_PIN(72, 2, 97, 1), + MX31_PIN_A3 = _MXC_BUILD_NON_GPIO_PIN(72, 1, 97, 0), + MX31_PIN_A4 = _MXC_BUILD_NON_GPIO_PIN(72, 0, 96, 2), + MX31_PIN_DVFS1 = _MXC_BUILD_NON_GPIO_PIN(73, 3, 99, 0), + MX31_PIN_VPG0 = _MXC_BUILD_NON_GPIO_PIN(73, 2, 98, 2), + MX31_PIN_VPG1 = _MXC_BUILD_NON_GPIO_PIN(73, 1, 98, 1), + MX31_PIN_A0 = _MXC_BUILD_NON_GPIO_PIN(73, 0, 98, 0), + MX31_PIN_CKIL = _MXC_BUILD_NON_GPIO_PIN(74, 3, 100, 1), + MX31_PIN_POWER_FAIL = _MXC_BUILD_NON_GPIO_PIN(74, 2, 100, 0), + MX31_PIN_VSTBY = _MXC_BUILD_NON_GPIO_PIN(74, 1, 99, 2), + MX31_PIN_DVFS0 = _MXC_BUILD_NON_GPIO_PIN(74, 0, 99, 1), + MX31_PIN_BOOT_MODE1 = _MXC_BUILD_NON_GPIO_PIN(75, 3, 101, 2), + MX31_PIN_BOOT_MODE2 = _MXC_BUILD_NON_GPIO_PIN(75, 2, 101, 1), + MX31_PIN_BOOT_MODE3 = _MXC_BUILD_NON_GPIO_PIN(75, 1, 101, 0), + MX31_PIN_BOOT_MODE4 = _MXC_BUILD_NON_GPIO_PIN(75, 0, 100, 2), + MX31_PIN_RESET_IN_B = _MXC_BUILD_NON_GPIO_PIN(76, 3, 103, 0), + MX31_PIN_POR_B = _MXC_BUILD_NON_GPIO_PIN(76, 2, 102, 2), + MX31_PIN_CLKO = _MXC_BUILD_NON_GPIO_PIN(76, 1, 102, 1), + MX31_PIN_BOOT_MODE0 = _MXC_BUILD_NON_GPIO_PIN(76, 0, 102, 0), + MX31_PIN_STX0 = _MXC_BUILD_GPIO_PIN(1, 1, 77, 3, 104, 1), + MX31_PIN_SRX0 = _MXC_BUILD_GPIO_PIN(1, 2, 77, 2, 104, 0), + MX31_PIN_SIMPD0 = _MXC_BUILD_GPIO_PIN(1, 3, 77, 1, 103, 2), + MX31_PIN_CKIH = _MXC_BUILD_NON_GPIO_PIN(77, 0, 103, 1), + MX31_PIN_GPIO3_1 = _MXC_BUILD_GPIO_PIN(2, 1, 78, 3, 105, 2), + MX31_PIN_SCLK0 = _MXC_BUILD_GPIO_PIN(2, 2, 78, 2, 105, 1), + MX31_PIN_SRST0 = _MXC_BUILD_GPIO_PIN(2, 3, 78, 1, 105, 0), + MX31_PIN_SVEN0 = _MXC_BUILD_GPIO_PIN(1, 0, 78, 0, 104, 2), + MX31_PIN_GPIO1_4 = _MXC_BUILD_GPIO_PIN(0, 4, 79, 3, 107, 0), + MX31_PIN_GPIO1_5 = _MXC_BUILD_GPIO_PIN(0, 5, 79, 2, 106, 2), + MX31_PIN_GPIO1_6 = _MXC_BUILD_GPIO_PIN(0, 6, 79, 1, 106, 1), + MX31_PIN_GPIO3_0 = _MXC_BUILD_GPIO_PIN(2, 0, 79, 0, 106, 0), + MX31_PIN_GPIO1_0 = _MXC_BUILD_GPIO_PIN(0, 0, 80, 3, 108, 1), + MX31_PIN_GPIO1_1 = _MXC_BUILD_GPIO_PIN(0, 1, 80, 2, 108, 0), + MX31_PIN_GPIO1_2 = _MXC_BUILD_GPIO_PIN(0, 2, 80, 1, 107, 2), + MX31_PIN_GPIO1_3 = _MXC_BUILD_GPIO_PIN(0, 3, 80, 0, 107, 1), + MX31_PIN_CAPTURE = _MXC_BUILD_GPIO_PIN(0, 7, 81, 3, 109, 2), + MX31_PIN_COMPARE = _MXC_BUILD_GPIO_PIN(0, 8, 81, 2, 109, 1), + MX31_PIN_WATCHDOG_RST = _MXC_BUILD_NON_GPIO_PIN(81, 1, 109, 0), + MX31_PIN_PWMO = _MXC_BUILD_GPIO_PIN(0, 9, 81, 0, 108, 2), +}; + +#endif +#endif diff --git a/arch/arm/mach-mx3/mx31ads.c b/arch/arm/mach-mx3/mx31ads.c index 30e2767a78ae..c9526bc5e22c 100644 --- a/arch/arm/mach-mx3/mx31ads.c +++ b/arch/arm/mach-mx3/mx31ads.c @@ -1,7 +1,7 @@ /* * Copyright (C) 2000 Deep Blue Solutions Ltd * Copyright (C) 2002 Shane Nay (shane@minirl.com) - * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2005-2009 Freescale Semiconductor, Inc. All 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 @@ -23,19 +23,30 @@ #include <linux/clk.h> #include <linux/serial_8250.h> #include <linux/gpio.h> +#include <linux/input.h> +#include <linux/nodemask.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/spi/spi.h> #include <linux/i2c.h> #include <linux/irq.h> +#include <linux/ata.h> +#if defined(CONFIG_MTD) || defined(CONFIG_MTD_MODULE) +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> + +#include <asm/mach/flash.h> +#endif #include <mach/hardware.h> #include <asm/mach-types.h> #include <asm/mach/arch.h> +#include <asm/mach/keypad.h> #include <asm/mach/time.h> #include <asm/memory.h> #include <asm/mach/map.h> #include <mach/common.h> -#include <mach/board-mx31ads.h> -#include <mach/imx-uart.h> -#include <mach/iomux-mx3.h> #ifdef CONFIG_MACH_MX31ADS_WM1133_EV1 #include <linux/mfd/wm8350/audio.h> @@ -43,8 +54,13 @@ #include <linux/mfd/wm8350/pmic.h> #endif -#include "devices.h" +#include <mach/memory.h> +#include <mach/mmc.h> +#include <mach/spba.h> +#include "board-mx31ads.h" +#include "crm_regs.h" +#include "iomux.h" /*! * @file mx31ads.c * @@ -53,6 +69,95 @@ * @ingroup System */ +extern void mx31ads_gpio_init(void) __init; + +static void mxc_nop_release(struct device *dev) +{ + /* Nothing */ +} + +#if defined(CONFIG_CS89x0) || defined(CONFIG_CS89x0_MODULE) +/*! Null terminated portlist used to probe for the CS8900A device on ISA Bus + * Add 3 to reset the page window before probing (fixes eth probe when deployed + * using nand_boot) + */ +unsigned int netcard_portlist[] = { (u32)CS8900A_BASE_ADDRESS + 3, 0 }; + +EXPORT_SYMBOL(netcard_portlist); +/*! + * The CS8900A has 4 IRQ pins, which is software selectable, CS8900A interrupt + * pin 0 is used for interrupt generation. + */ +unsigned int cs8900_irq_map[] = { CS8900AIRQ, 0, 0, 0 }; + +EXPORT_SYMBOL(cs8900_irq_map); +#endif + +#if defined(CONFIG_KEYBOARD_MXC) || defined(CONFIG_KEYBOARD_MXC_MODULE) + +/* Keypad keycodes for the EVB 8x8 + * keypad. POWER and PTT keys don't generate + * any interrupts via this driver so they are + * not support. Change any keys as u like! + */ +static u16 keymapping[64] = { + KEY_SELECT, KEY_LEFT, KEY_DOWN, KEY_RIGHT, + KEY_UP, KEY_F12, KEY_END, KEY_BACK, + KEY_F1, KEY_SENDFILE, KEY_HOME, KEY_F6, + KEY_VOLUMEUP, KEY_F8, KEY_F9, KEY_F10, + KEY_3, KEY_2, KEY_1, KEY_4, + KEY_VOLUMEDOWN, KEY_7, KEY_5, KEY_6, + KEY_9, KEY_LEFTSHIFT, KEY_8, KEY_0, + KEY_KPASTERISK, KEY_RECORD, KEY_Q, KEY_W, + KEY_A, KEY_S, KEY_D, KEY_E, + KEY_F, KEY_R, KEY_T, KEY_Y, + KEY_TAB, KEY_F7, KEY_CAPSLOCK, KEY_Z, + KEY_X, KEY_C, KEY_V, KEY_G, + KEY_B, KEY_H, KEY_N, KEY_M, + KEY_J, KEY_K, KEY_U, KEY_I, + KEY_SPACE, KEY_F2, KEY_DOT, KEY_ENTER, + KEY_L, KEY_BACKSPACE, KEY_P, KEY_O, +}; + +static struct resource mxc_kpp_resources[] = { + [0] = { + .start = MXC_INT_KPP, + .end = MXC_INT_KPP, + .flags = IORESOURCE_IRQ, + } +}; + +static struct keypad_data evb_8_by_8_keypad = { + .rowmax = 8, + .colmax = 8, + .irq = MXC_INT_KPP, + .learning = 0, + .delay = 2, + .matrix = keymapping, +}; + +/* mxc keypad driver */ +static struct platform_device mxc_keypad_device = { + .name = "mxc_keypad", + .id = 0, + .num_resources = ARRAY_SIZE(mxc_kpp_resources), + .resource = mxc_kpp_resources, + .dev = { + .release = mxc_nop_release, + .platform_data = &evb_8_by_8_keypad, + }, +}; + +static void mxc_init_keypad(void) +{ + (void)platform_device_register(&mxc_keypad_device); +} +#else +static inline void mxc_init_keypad(void) +{ +} +#endif + #if defined(CONFIG_SERIAL_8250) || defined(CONFIG_SERIAL_8250_MODULE) /*! * The serial port definition structure. @@ -96,29 +201,470 @@ static inline int mxc_init_extuart(void) return 0; } #endif +/* MTD NOR flash */ + +#if defined(CONFIG_MTD_MXC) || defined(CONFIG_MTD_MXC_MODULE) + +static struct mtd_partition mxc_nor_partitions[] = { + { + .name = "Bootloader", + .size = 512 * 1024, + .offset = 0x00000000, + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "nor.Kernel", + .size = 2 * 1024 * 1024, + .offset = MTDPART_OFS_APPEND, + .mask_flags = 0}, + { + .name = "nor.userfs", + .size = 14 * 1024 * 1024, + .offset = MTDPART_OFS_APPEND, + .mask_flags = 0}, + { + .name = "nor.rootfs", + .size = 12 * 1024 * 1024, + .offset = MTDPART_OFS_APPEND, + .mask_flags = MTD_WRITEABLE}, + { + .name = "FIS directory", + .size = 12 * 1024, + .offset = 0x01FE0000, + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "Redboot config", + .size = MTDPART_SIZ_FULL, + .offset = 0x01FFF000, + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, +}; + +static struct flash_platform_data mxc_flash_data = { + .map_name = "cfi_probe", + .width = 2, + .parts = mxc_nor_partitions, + .nr_parts = ARRAY_SIZE(mxc_nor_partitions), +}; + +static struct resource mxc_flash_resource = { + .start = 0xa0000000, + .end = 0xa0000000 + 0x02000000 - 1, + .flags = IORESOURCE_MEM, + +}; + +static struct platform_device mxc_nor_mtd_device = { + .name = "mxc_nor_flash", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_flash_data, + }, + .num_resources = 1, + .resource = &mxc_flash_resource, +}; + +static void mxc_init_nor_mtd(void) +{ + (void)platform_device_register(&mxc_nor_mtd_device); +} +#else +static void mxc_init_nor_mtd(void) +{ +} +#endif + +/* NAND Flash Partitions */ +#ifdef CONFIG_MTD_PARTITIONS + +static struct mtd_partition nand_flash_partitions[4] = { + { + .name = "nand.bootloader", + .offset = 0, + .size = 1024 * 1024}, + { + .name = "nand.kernel", + .offset = MTDPART_OFS_APPEND, + .size = 5 * 1024 * 1024}, + { + .name = "nand.rootfs", + .offset = MTDPART_OFS_APPEND, + .size = 22 * 1024 * 1024}, + { + .name = "nand.userfs", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL}, +}; + +#endif + +/* MTD NAND flash */ + +#if defined(CONFIG_MTD_NAND_MXC) \ + || defined(CONFIG_MTD_NAND_MXC_MODULE) \ + || defined(CONFIG_MTD_NAND_MXC_V2) \ + || defined(CONFIG_MTD_NAND_MXC_V2_MODULE) + +static struct flash_platform_data mxc_nand_data = { + #ifdef CONFIG_MTD_PARTITIONS + .parts = nand_flash_partitions, + .nr_parts = ARRAY_SIZE(nand_flash_partitions), + #endif + .width = 1, +}; + +static struct platform_device mxc_nand_mtd_device = { + .name = "mxc_nand_flash", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_nand_data, + }, +}; + +static struct platform_device mxc_nandv2_mtd_device = { + .name = "mxc_nandv2_flash", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_nand_data, + }, +}; + +static void mxc_init_nand_mtd(void) +{ + if (__raw_readl(MXC_CCM_RCSR) & MXC_CCM_RCSR_NF16B) { + mxc_nand_data.width = 2; + } + if (cpu_is_mx31()) { + (void)platform_device_register(&mxc_nand_mtd_device); + } + if (cpu_is_mx32()) { + (void)platform_device_register(&mxc_nandv2_mtd_device); + } +} +#else +static inline void mxc_init_nand_mtd(void) +{ +} +#endif + +/* i.MX MTD NAND Flash Controller */ + +#if defined(CONFIG_MTD_NAND_IMX_NFC) || defined(CONFIG_MTD_NAND_IMX_NFC_MODULE) + +/* Resources for this device. */ + +static struct resource imx_nfc_resources[] = { + { + .flags = IORESOURCE_MEM, + .start = NFC_BASE_ADDR + 0x000, + .end = NFC_BASE_ADDR + 0x840 - 1, + .name = IMX_NFC_BUFFERS_ADDR_RES_NAME, + }, + { + .flags = IORESOURCE_MEM, + .start = NFC_BASE_ADDR + 0xE00, + .end = NFC_BASE_ADDR + 0xE20 - 1, + .name = IMX_NFC_PRIMARY_REGS_ADDR_RES_NAME, + }, + { + .flags = IORESOURCE_IRQ, + .start = MXC_INT_NANDFC, + .end = MXC_INT_NANDFC, + .name = IMX_NFC_INTERRUPT_RES_NAME, + }, +}; + +/** + * imx_nfc_set_page_size() - Tells the hardware the page size. + * + * @data_size_in_bytes: The page size in bytes (e.g., 512, 2048, etc.). This + * size refers specifically to the the data bytes in the + * page, *not* including out-of-band bytes. The return + * value is zero if the operation succeeded. Do not + * interpret a non-zero value as an error code - it only + * indicates failure. The driver will decide what error + * code to return to its caller. + */ +static int imx_nfc_set_page_size(unsigned int data_size_in_bytes) +{ + + unsigned long x = __raw_readl(MXC_CCM_RCSR); + + switch (data_size_in_bytes) { + + case 512: + x &= ~MXC_CCM_RCSR_NFMS; + break; + + case 2048: + x |= MXC_CCM_RCSR_NFMS; + break; + + default: + return !0; + break; + + } + + __raw_writel(x, MXC_CCM_RCSR); + + return 0; + +} + +/* + * Platform-specific information about this device. Some of the details depend + * on the SoC. See imx_init_nfc() below for code that fills in the rest. + */ + +static struct imx_nfc_platform_data imx_nfc_platform_data = { + .force_ce = false, + .target_cycle_in_ns = 50, + .clock_name = "nfc_clk", + .set_page_size = imx_nfc_set_page_size, + .interleave = false, + #ifdef CONFIG_MTD_PARTITIONS + .partitions = nand_flash_partitions, + .partition_count = ARRAY_SIZE(nand_flash_partitions), + #endif +}; + +/* The structure that represents the NFC device. */ + +static struct platform_device imx_nfc_device = { + .name = IMX_NFC_DRIVER_NAME, + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &imx_nfc_platform_data, + }, + .resource = imx_nfc_resources, + .num_resources = ARRAY_SIZE(imx_nfc_resources), +}; + +/** + * imx_init_nfc() - Sets up the NFC for this platform. + * + * This function sets up data structures representing the NFC device on this + * platform and registers the device with the platform management system. + */ + +static void imx_nfc_init(void) +{ + + /* + * A field in the Reset Control and Source Register register tells us + * the bus width. + */ + + if (__raw_readl(MXC_CCM_RCSR) & MXC_CCM_RCSR_NF16B) + imx_nfc_platform_data.bus_width_in_bits = 16; + else + imx_nfc_platform_data.bus_width_in_bits = 8; + + /* + * Discover the type of SoC we're running on and, based on that, fill in + * some details about the NFC. + */ + + if (cpu_is_mx31()) { + imx_nfc_platform_data.major_version = 1; + imx_nfc_platform_data.minor_version = 0; + } else if (cpu_is_mx32()) { + imx_nfc_platform_data.major_version = 2; + imx_nfc_platform_data.minor_version = 0; + } else { + pr_err("imx_nfc: Can't identify the SoC\n"); + BUG(); + } + + /* Register the NFC device. */ + + (void)platform_device_register(&imx_nfc_device); + +} + +#else + +static inline void imx_nfc_init(void) +{ +} + +#endif /* i.MX MTD NAND Flash Controller */ + +static struct spi_board_info mxc_spi_board_info[] __initdata = { + { + .modalias = "pmic_spi", + .irq = IOMUX_TO_IRQ(MX31_PIN_GPIO1_3), + .max_speed_hz = 4000000, + .bus_num = 2, + .chip_select = 0, + }, +}; + +#if defined(CONFIG_FB_MXC_SYNC_PANEL) || defined(CONFIG_FB_MXC_SYNC_PANEL_MODULE) +static const char fb_default_mode[] = "Sharp-QVGA"; + +/* mxc lcd driver */ +static struct platform_device mxc_fb_device = { + .name = "mxc_sdc_fb", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &fb_default_mode, + .coherent_dma_mask = 0xFFFFFFFF, + }, +}; + +static void mxc_init_fb(void) +{ + (void)platform_device_register(&mxc_fb_device); +} +#else +static inline void mxc_init_fb(void) +{ +} +#endif -#if defined(CONFIG_SERIAL_IMX) || defined(CONFIG_SERIAL_IMX_MODULE) -static struct imxuart_platform_data uart_pdata = { - .flags = IMXUART_HAVE_RTSCTS, +#if defined(CONFIG_BACKLIGHT_MXC) +static struct platform_device mxcbl_devices[] = { +#if defined(CONFIG_BACKLIGHT_MXC_PMIC) || defined(CONFIG_BACKLIGHT_MXC_PMIC_MODULE) + { + .name = "mxc_pmic_bl", + .id = 0, + .dev = { + .platform_data = (void *)-1, /* DISP # for this backlight */ + }, + }, + { + .name = "mxc_pmic_bl", + .id = 1, + .dev = { + .platform_data = (void *)0, /* DISP # for this backlight */ + }, + }, +#endif +#if defined(CONFIG_BACKLIGHT_MXC_IPU) || defined(CONFIG_BACKLIGHT_MXC_IPU_MODULE) + { + .name = "mxc_ipu_bl", + .id = 0, + .dev = { + .platform_data = (void *)3, /* DISP # for this backlight */ + }, + }, +#endif +}; +static inline void mxc_init_bl(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(mxcbl_devices); i++) { + platform_device_register(&mxcbl_devices[i]); + } +} +#else +static inline void mxc_init_bl(void) +{ +} +#endif + +/*! + * Data structures and data for mt9v111 camera. + */ +static struct mxc_camera_platform_data camera_mt9v111_data = { + .mclk = 27000000, +}; + +/*! + * Data structures and data for ov2640 camera. + */ +static struct mxc_camera_platform_data camera_ov2640_data = { + .core_regulator = NULL, + .io_regulator = NULL, + .analog_regulator = NULL, + .gpo_regulator = NULL, + .mclk = 24000000, }; -static unsigned int uart_pins[] = { - MX31_PIN_CTS1__CTS1, - MX31_PIN_RTS1__RTS1, - MX31_PIN_TXD1__TXD1, - MX31_PIN_RXD1__RXD1 +/*! + * Info to register i2c devices. + */ +static struct i2c_board_info mxc_i2c_info[] __initdata = { + { + .type = "mt9v111", + .addr = 0x48, + .platform_data = (void *)&camera_mt9v111_data, + }, + { + .type = "ov2640", + .addr = 0x30, + .platform_data = (void *)&camera_ov2640_data, + }, }; -static inline void mxc_init_imx_uart(void) +#if defined(CONFIG_MXC_FIR) || defined(CONFIG_MXC_FIR_MODULE) +/*! + * Resource definition for the FIR + */ +static struct resource mxcir_resources[] = { + [0] = { + .start = UART2_BASE_ADDR, + .end = UART2_BASE_ADDR + SZ_16K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_UART2, + .end = MXC_INT_UART2, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = FIRI_BASE_ADDR, + .end = FIRI_BASE_ADDR + SZ_16K - 1, + .flags = IORESOURCE_MEM, + }, + [3] = { + .start = MXC_INT_FIRI, + .end = MXC_INT_FIRI, + .flags = IORESOURCE_IRQ, + }, + [4] = { + .start = MXC_INT_UART2, + .end = MXC_INT_UART2, + .flags = IORESOURCE_IRQ, + } +}; + +static struct mxc_ir_platform_data ir_data = { + .uart_ir_mux = 1, + .ir_rx_invert = MXC_IRDA_RX_INV, + .ir_tx_invert = MXC_IRDA_TX_INV, +}; + +/*! Device Definition for MXC FIR */ +static struct platform_device mxcir_device = { + .name = "mxcir", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &ir_data, + }, + .num_resources = ARRAY_SIZE(mxcir_resources), + .resource = mxcir_resources, +}; + +static inline void mxc_init_ir(void) { - mxc_iomux_setup_multiple_pins(uart_pins, ARRAY_SIZE(uart_pins), "uart-0"); - mxc_register_device(&mxc_uart_device0, &uart_pdata); + ir_data.uart_clk = clk_get(NULL, "uart_clk.1");; + (void)platform_device_register(&mxcir_device); } -#else /* !SERIAL_IMX */ -static inline void mxc_init_imx_uart(void) +#else +static inline void mxc_init_ir(void) { } -#endif /* !SERIAL_IMX */ +#endif static void mx31ads_expio_irq_handler(u32 irq, struct irq_desc *desc) { @@ -187,7 +733,10 @@ static void __init mx31ads_init_expio(void) /* * Configure INT line as GPIO input */ - mxc_iomux_alloc_pin(IOMUX_MODE(MX31_PIN_GPIO1_4, IOMUX_CONFIG_GPIO), "expio"); + mxc_request_iomux(MX31_PIN_GPIO1_4, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_GPIO1_4), NULL); + gpio_direction_input(IOMUX_TO_GPIO(MX31_PIN_GPIO1_4)); /* disable the interrupt and clear the status */ __raw_writew(0xFFFF, PBC_INTMASK_CLEAR_REG); @@ -462,6 +1011,121 @@ static struct wm8350_platform_data __initdata mx31_wm8350_pdata = { }; #endif +/* MMC device data */ + +#if defined(CONFIG_MMC_MXC) || defined(CONFIG_MMC_MXC_MODULE) +extern unsigned int sdhc_get_card_det_status(struct device *dev); +extern int sdhc_init_card_det(int id); + +static struct mxc_mmc_platform_data mmc1_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30, + .min_clk = 150000, + .max_clk = 25000000, + .card_inserted_state = 1, + .status = sdhc_get_card_det_status, + .power_mmc = "VMMC1", +}; +static struct mxc_mmc_platform_data mmc2_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30, + .min_clk = 150000, + .max_clk = 25000000, + .card_inserted_state = 1, + .status = sdhc_get_card_det_status, + .power_mmc = "VMMC2", +}; +/*! + * Resource definition for the SDHC1 + */ +static struct resource mxcsdhc1_resources[] = { + [0] = { + .start = MMC_SDHC1_BASE_ADDR, + .end = MMC_SDHC1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_MMC_SDHC1, + .end = MXC_INT_MMC_SDHC1, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! + * Resource definition for the SDHC2 + */ +static struct resource mxcsdhc2_resources[] = { + [0] = { + .start = MMC_SDHC2_BASE_ADDR, + .end = MMC_SDHC2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_MMC_SDHC2, + .end = MXC_INT_MMC_SDHC2, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Device Definition for MXC SDHC1 */ +static struct platform_device mxcsdhc1_device = { + .name = "mxcmci", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mmc1_data, + }, + .num_resources = ARRAY_SIZE(mxcsdhc1_resources), + .resource = mxcsdhc1_resources, +}; + +/*! Device Definition for MXC SDHC2 */ +static struct platform_device mxcsdhc2_device = { + .name = "mxcmci", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mmc2_data, + }, + .num_resources = ARRAY_SIZE(mxcsdhc2_resources), + .resource = mxcsdhc2_resources, +}; + +static inline void mxc_init_mmc(void) +{ + int cd_irq; + + cd_irq = sdhc_init_card_det(0); + if (cd_irq) { + mxcsdhc1_device.resource[2].start = cd_irq; + mxcsdhc1_device.resource[2].end = cd_irq; + } + + cd_irq = sdhc_init_card_det(1); + if (cd_irq) { + mxcsdhc2_device.resource[2].start = cd_irq; + mxcsdhc2_device.resource[2].end = cd_irq; + } + + spba_take_ownership(SPBA_SDHC1, SPBA_MASTER_A | SPBA_MASTER_C); + (void)platform_device_register(&mxcsdhc1_device); + spba_take_ownership(SPBA_SDHC2, SPBA_MASTER_A | SPBA_MASTER_C); + (void)platform_device_register(&mxcsdhc2_device); +} +#else +static inline void mxc_init_mmc(void) +{ +} +#endif + #if defined(CONFIG_I2C_IMX) || defined(CONFIG_I2C_IMX_MODULE) static struct i2c_board_info __initdata mx31ads_i2c1_devices[] = { #ifdef CONFIG_MACH_MX31ADS_WM1133_EV1 @@ -489,6 +1153,147 @@ static void mxc_init_i2c(void) } #endif +#if (defined(CONFIG_MXC_PMIC_MC13783) || \ + defined(CONFIG_MXC_PMIC_MC13783_MODULE)) \ + && (defined(CONFIG_SND_MXC_PMIC) || defined(CONFIG_SND_MXC_PMIC_MODULE)) +extern void gpio_activate_audio_ports(void); + +static void __init mxc_init_pmic_audio(void) +{ + struct clk *ckih_clk; + struct clk *cko_clk; + + /* Enable 26 mhz clock on CKO1 for PMIC audio */ + ckih_clk = clk_get(NULL, "ckih"); + cko_clk = clk_get(NULL, "cko1_clk"); + if (IS_ERR(ckih_clk) || IS_ERR(cko_clk)) { + printk(KERN_ERR "Unable to set CKO1 output to CKIH\n"); + } else { + clk_set_parent(cko_clk, ckih_clk); + clk_set_rate(cko_clk, clk_get_rate(ckih_clk)); + clk_enable(cko_clk); + } + clk_put(ckih_clk); + clk_put(cko_clk); + + gpio_activate_audio_ports(); +} +#else +static void __inline mxc_init_pmic_audio(void) +{ +} +#endif + +/* IDE device data */ +#if defined(CONFIG_BLK_DEV_IDE_MXC) || defined(CONFIG_BLK_DEV_IDE_MXC_MODULE) + +/*! Platform Data for MXC IDE */ +static struct mxc_ide_platform_data mxc_ide_data = { + .power_drive = NULL, + .power_io = NULL, +}; + +static struct platform_device mxc_ide_device = { + .name = "mxc_ide", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_ide_data, + }, +}; + +static inline void mxc_init_ide(void) +{ + if (platform_device_register(&mxc_ide_device) < 0) + printk(KERN_ERR "Error: Registering the ide.\n"); +} +#else +static inline void mxc_init_ide(void) +{ +} +#endif + +#if defined(CONFIG_PATA_FSL) || defined(CONFIG_PATA_FSL_MODULE) +extern void gpio_ata_active(void); +extern void gpio_ata_inactive(void); + +static int ata_init(struct platform_device *pdev) +{ + /* Configure the pins */ + gpio_ata_active(); + + return 0; +} + +static void ata_exit(void) +{ + /* Free the pins */ + gpio_ata_inactive(); +} + +static struct fsl_ata_platform_data ata_data = { + .udma_mask = ATA_UDMA3, /* board can handle up to UDMA3 */ + .mwdma_mask = ATA_MWDMA2, + .pio_mask = ATA_PIO4, + .fifo_alarm = MXC_IDE_DMA_WATERMARK / 2, + .max_sg = MXC_IDE_DMA_BD_NR, + .init = ata_init, + .exit = ata_exit, + .core_reg = NULL, /*"LDO2", */ + .io_reg = NULL, /*"LDO3", */ +}; + +static struct resource pata_fsl_resources[] = { + [0] = { /* I/O */ + .start = ATA_BASE_ADDR + 0x00, + .end = ATA_BASE_ADDR + 0xD8, + .flags = IORESOURCE_MEM, + }, + [2] = { /* IRQ */ + .start = MXC_INT_ATA, + .end = MXC_INT_ATA, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device pata_fsl_device = { + .name = "pata_fsl", + .id = -1, + .num_resources = ARRAY_SIZE(pata_fsl_resources), + .resource = pata_fsl_resources, + .dev = { + .platform_data = &ata_data, + .coherent_dma_mask = ~0, + }, +}; + +static void __init mxc_init_pata(void) +{ + (void)platform_device_register(&pata_fsl_device); +} +#else /* CONFIG_PATA_FSL */ +static void __init mxc_init_pata(void) +{ +} +#endif /* CONFIG_PATA_FSL */ + +/*! + * Board specific fixup function. It is called by \b setup_arch() in + * setup.c file very early on during kernel starts. It allows the user to + * statically fill in the proper values for the passed-in parameters. None of + * the parameters is used currently. + * + * @param desc pointer to \b struct \b machine_desc + * @param tags pointer to \b struct \b tag + * @param cmdline pointer to the command line + * @param mi pointer to \b struct \b meminfo + */ +static void __init fixup_mxc_board(struct machine_desc *desc, struct tag *tags, + char **cmdline, struct meminfo *mi) +{ + mxc_cpu_init(); +} + /*! * This structure defines static mappings for the i.MX31ADS board. */ @@ -526,20 +1331,126 @@ static void __init mx31ads_init_irq(void) */ static void __init mxc_board_init(void) { + mxc_cpu_common_init(); + early_console_setup(saved_command_line); + mxc_init_devices(); + mxc_init_pmic_audio(); + mxc_register_gpios(); + mx31ads_gpio_init(); + mxc_init_keypad(); mxc_init_extuart(); - mxc_init_imx_uart(); mxc_init_i2c(); + mxc_init_nor_mtd(); + mxc_init_nand_mtd(); + imx_nfc_init(); + + i2c_register_board_info(0, mxc_i2c_info, ARRAY_SIZE(mxc_i2c_info)); + spi_register_board_info(mxc_spi_board_info, + ARRAY_SIZE(mxc_spi_board_info)); + + mxc_init_fb(); + mxc_init_bl(); + mxc_init_ir(); + mxc_init_mmc(); + mxc_init_ide(); + mxc_init_pata(); } static void __init mx31ads_timer_init(void) { - mx31_clocks_init(26000000); + unsigned long ckih = 26000000; + + if ((__raw_readw(PBC_BASE_ADDRESS + PBC_BSTAT) & + CKIH_27MHZ_BIT_SET) != 0) { + ckih = 27000000; + } + + mx31_clocks_init(ckih); } static struct sys_timer mx31ads_timer = { .init = mx31ads_timer_init, }; + +#define PLL_PCTL_REG(pd, mfd, mfi, mfn) \ + ((((pd) - 1) << 26) + (((mfd) - 1) << 16) + ((mfi) << 10) + mfn) + +/* For 26MHz input clock */ +#define PLL_532MHZ PLL_PCTL_REG(1, 13, 10, 3) +#define PLL_399MHZ PLL_PCTL_REG(1, 52, 7, 35) +#define PLL_133MHZ PLL_PCTL_REG(2, 26, 5, 3) + +/* For 27MHz input clock */ +#define PLL_532_8MHZ PLL_PCTL_REG(1, 15, 9, 13) +#define PLL_399_6MHZ PLL_PCTL_REG(1, 18, 7, 7) +#define PLL_133_2MHZ PLL_PCTL_REG(3, 5, 7, 2) + +#define PDR0_REG(mcu, max, hsp, ipg, nfc) \ + (MXC_CCM_PDR0_MCU_DIV_##mcu | MXC_CCM_PDR0_MAX_DIV_##max | \ + MXC_CCM_PDR0_HSP_DIV_##hsp | MXC_CCM_PDR0_IPG_DIV_##ipg | \ + MXC_CCM_PDR0_NFC_DIV_##nfc) + +/* working point(wp): 0 - 133MHz; 1 - 266MHz; 2 - 399MHz; 3 - 532MHz */ +/* 26MHz input clock table */ +static struct cpu_wp cpu_wp_26[] = { + { + .pll_reg = PLL_532MHZ, + .pll_rate = 532000000, + .cpu_rate = 133000000, + .pdr0_reg = PDR0_REG(4, 4, 4, 2, 6),}, + { + .pll_reg = PLL_532MHZ, + .pll_rate = 532000000, + .cpu_rate = 266000000, + .pdr0_reg = PDR0_REG(2, 4, 4, 2, 6),}, + { + .pll_reg = PLL_399MHZ, + .pll_rate = 399000000, + .cpu_rate = 399000000, + .pdr0_reg = PDR0_REG(1, 3, 3, 2, 6),}, + { + .pll_reg = PLL_532MHZ, + .pll_rate = 532000000, + .cpu_rate = 532000000, + .pdr0_reg = PDR0_REG(1, 4, 4, 2, 6),}, +}; + +/* 27MHz input clock table */ +static struct cpu_wp cpu_wp_27[] = { + { + .pll_reg = PLL_532_8MHZ, + .pll_rate = 532800000, + .cpu_rate = 133200000, + .pdr0_reg = PDR0_REG(4, 4, 4, 2, 6),}, + { + .pll_reg = PLL_532_8MHZ, + .pll_rate = 532800000, + .cpu_rate = 266400000, + .pdr0_reg = PDR0_REG(2, 4, 4, 2, 6),}, + { + .pll_reg = PLL_399_6MHZ, + .pll_rate = 399600000, + .cpu_rate = 399600000, + .pdr0_reg = PDR0_REG(1, 3, 3, 2, 6),}, + { + .pll_reg = PLL_532_8MHZ, + .pll_rate = 532800000, + .cpu_rate = 532800000, + .pdr0_reg = PDR0_REG(1, 4, 4, 2, 6),}, +}; + +struct cpu_wp *get_cpu_wp(int *wp) +{ + *wp = 4; + if ((__raw_readw(PBC_BASE_ADDRESS + PBC_BSTAT) & + CKIH_27MHZ_BIT_SET) != 0) { + return cpu_wp_27; + } else { + return cpu_wp_26; + } +} + /* * The following uses standard kernel macros defined in arch.h in order to * initialize __mach_desc_MX31ADS data structure. @@ -549,6 +1460,7 @@ MACHINE_START(MX31ADS, "Freescale MX31ADS") .phys_io = AIPS1_BASE_ADDR, .io_pg_offst = ((AIPS1_BASE_ADDR_VIRT) >> 18) & 0xfffc, .boot_params = PHYS_OFFSET + 0x100, + .fixup = fixup_mxc_board, .map_io = mx31ads_map_io, .init_irq = mx31ads_init_irq, .init_machine = mxc_board_init, diff --git a/arch/arm/mach-mx3/mx31ads_gpio.c b/arch/arm/mach-mx3/mx31ads_gpio.c new file mode 100644 index 000000000000..4d63db812804 --- /dev/null +++ b/arch/arm/mach-mx3/mx31ads_gpio.c @@ -0,0 +1,1561 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <mach/hardware.h> +#include <mach/gpio.h> +#include "board-mx31ads.h" +#include "iomux.h" + +/*! + * @file mach-mx3/mx31ads_gpio.c + * + * @brief This file contains all the GPIO setup functions for the board. + * + * @ingroup GPIO_MX31 + */ + +void gpio_activate_audio_ports(void); + +/*! + * This system-wise GPIO function initializes the pins during system startup. + * All the statically linked device drivers should put the proper GPIO initialization + * code inside this function. It is called by \b fixup_mx31ads() during + * system startup. This function is board specific. + */ +void mx31ads_gpio_init(void) +{ + /* config CS4 */ + mxc_request_iomux(MX31_PIN_CS4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + /*Connect DAM ports 4 & 5 to enable audio I/O */ + gpio_activate_audio_ports(); +} + +/*! + * Setup GPIO for a UART port to be active + * + * @param port a UART port + * @param no_irda indicates if the port is used for SIR + */ +void gpio_uart_active(int port, int no_irda) +{ + unsigned int pbc_bctrl1_clr = 0, pbc_bctrl2_set = 0, pbc_bctrl2_clr = 0; + /* + * Configure the IOMUX control registers for the UART signals + */ + switch (port) { + /* UART 1 IOMUX Configs */ + case 0: + mxc_request_iomux(MX31_PIN_RXD1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_TXD1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_RTS1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CTS1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_DTR_DCE1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_DSR_DCE1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_RI_DCE1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_DCD_DCE1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + + /* Enable the transceiver */ + pbc_bctrl1_clr |= PBC_BCTRL1_UENCE; + pbc_bctrl2_set |= PBC_BCTRL2_USELC; + break; + /* UART 2 IOMUX Configs */ + case 1: + mxc_request_iomux(MX31_PIN_TXD2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_RXD2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + + if (no_irda == 1) { + mxc_request_iomux(MX31_PIN_RTS2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CTS2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_DTR_DCE2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + pbc_bctrl1_clr |= PBC_BCTRL1_UENCE; + pbc_bctrl2_clr |= PBC_BCTRL2_USELC; + } else { + pbc_bctrl1_clr |= PBC_BCTRL1_IREN; + pbc_bctrl2_clr |= PBC_BCTRL2_IRDA_MOD; + } + break; + /* UART 3 IOMUX Configs */ + case 2: + mxc_request_iomux(MX31_PIN_CSPI3_MOSI, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_CSPI3_MISO, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_CSPI3_SCLK, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_CSPI3_SPI_RDY, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + + pbc_bctrl1_clr |= PBC_BCTRL1_UENB; + pbc_bctrl2_clr |= PBC_BCTRL2_USELB; + break; + /* UART 4 IOMUX Configs */ + case 3: + mxc_request_iomux(MX31_PIN_ATA_CS0, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_ATA_CS1, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_ATA_DIOR, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_ATA_DIOW, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + + pbc_bctrl1_clr |= PBC_BCTRL1_UENB; + pbc_bctrl2_set |= PBC_BCTRL2_USELB; + break; + /* UART 5 IOMUX Configs */ + case 4: + mxc_request_iomux(MX31_PIN_PC_VS2, OUTPUTCONFIG_ALT2, + INPUTCONFIG_ALT2); + mxc_request_iomux(MX31_PIN_PC_RST, OUTPUTCONFIG_ALT2, + INPUTCONFIG_ALT2); + mxc_request_iomux(MX31_PIN_PC_BVD1, OUTPUTCONFIG_ALT2, + INPUTCONFIG_ALT2); + mxc_request_iomux(MX31_PIN_PC_BVD2, OUTPUTCONFIG_ALT2, + INPUTCONFIG_ALT2); + + pbc_bctrl1_clr |= PBC_BCTRL1_UENA; + pbc_bctrl2_set |= PBC_BCTRL2_USELA; + break; + default: + break; + } + + __raw_writew(pbc_bctrl1_clr, PBC_BASE_ADDRESS + PBC_BCTRL1_CLEAR); + __raw_writew(pbc_bctrl2_set, PBC_BASE_ADDRESS + PBC_BCTRL2_SET); + __raw_writew(pbc_bctrl2_clr, PBC_BASE_ADDRESS + PBC_BCTRL2_CLEAR); + /* + * TODO: Configure the Pad registers for the UART pins + */ +} + +/*! + * Setup GPIO for a UART port to be inactive + * + * @param port a UART port + * @param no_irda indicates if the port is used for SIR + */ +void gpio_uart_inactive(int port, int no_irda) +{ + unsigned int pbc_bctrl1_set = 0; + + switch (port) { + case 0: + gpio_request(IOMUX_TO_GPIO(MX31_PIN_RXD1), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_TXD1), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_RTS1), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_CTS1), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_DTR_DCE1), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_DSR_DCE1), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_RI_DCE1), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_DCD_DCE1), NULL); + + mxc_free_iomux(MX31_PIN_RXD1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_TXD1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_RTS1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_CTS1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_DTR_DCE1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_DSR_DCE1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_RI_DCE1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_DCD_DCE1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + + pbc_bctrl1_set |= PBC_BCTRL1_UENCE; + break; + case 1: + gpio_request(IOMUX_TO_GPIO(MX31_PIN_TXD2), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_RXD2), NULL); + + mxc_free_iomux(MX31_PIN_TXD2, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_RXD2, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + + if (no_irda == 1) { + gpio_request(IOMUX_TO_GPIO(MX31_PIN_DTR_DCE2), NULL); + mxc_free_iomux(MX31_PIN_DTR_DCE2, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + + pbc_bctrl1_set |= PBC_BCTRL1_UENCE; + } else { + pbc_bctrl1_set |= PBC_BCTRL1_IREN; + } + break; + case 2: + pbc_bctrl1_set |= PBC_BCTRL1_UENB; + break; + case 3: + gpio_request(IOMUX_TO_GPIO(MX31_PIN_ATA_CS0), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_ATA_CS1), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_ATA_DIOR), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_ATA_DIOW), NULL); + + mxc_free_iomux(MX31_PIN_ATA_CS0, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_ATA_CS1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_ATA_DIOR, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_ATA_DIOW, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + + pbc_bctrl1_set |= PBC_BCTRL1_UENB; + break; + case 4: + pbc_bctrl1_set |= PBC_BCTRL1_UENA; + break; + default: + break; + } + __raw_writew(pbc_bctrl1_set, PBC_BASE_ADDRESS + PBC_BCTRL1_SET); +} + +/*! + * Configure the IOMUX GPR register to receive shared SDMA UART events + * + * @param port a UART port + */ +void config_uartdma_event(int port) +{ + switch (port) { + case 1: + /* Configure to receive UART 2 SDMA events */ + mxc_iomux_set_gpr(MUX_PGP_FIRI, false); + break; + case 2: + /* Configure to receive UART 3 SDMA events */ + mxc_iomux_set_gpr(MUX_CSPI1_UART3, true); + break; + case 4: + /* Configure to receive UART 5 SDMA events */ + mxc_iomux_set_gpr(MUX_CSPI3_UART5_SEL, true); + break; + default: + break; + } +} + +EXPORT_SYMBOL(gpio_uart_active); +EXPORT_SYMBOL(gpio_uart_inactive); +EXPORT_SYMBOL(config_uartdma_event); + +/*! + * Setup GPIO for Keypad to be active + * + */ +void gpio_keypad_active(void) +{ + /* + * Configure the IOMUX control register for keypad signals. + */ + mxc_request_iomux(MX31_PIN_KEY_COL0, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_COL1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_COL2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_COL3, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_COL4, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_COL5, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_COL6, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_COL7, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_ROW0, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_ROW1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_ROW2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_ROW3, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_ROW4, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_ROW5, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_ROW6, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_ROW7, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); +} + +EXPORT_SYMBOL(gpio_keypad_active); + +/*! + * Setup GPIO for Keypad to be inactive + * + */ +void gpio_keypad_inactive(void) +{ + mxc_request_iomux(MX31_PIN_KEY_COL4, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_request_iomux(MX31_PIN_KEY_COL5, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_request_iomux(MX31_PIN_KEY_COL6, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_request_iomux(MX31_PIN_KEY_COL7, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + + mxc_request_iomux(MX31_PIN_KEY_ROW4, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_request_iomux(MX31_PIN_KEY_ROW5, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_request_iomux(MX31_PIN_KEY_ROW6, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_request_iomux(MX31_PIN_KEY_ROW7, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); +} + +EXPORT_SYMBOL(gpio_keypad_inactive); + +void gpio_power_key_active(void) +{ +} +EXPORT_SYMBOL(gpio_power_key_active); + +/*! + * Setup GPIO for a CSPI device to be active + * + * @param cspi_mod an CSPI device + */ +void gpio_spi_active(int cspi_mod) +{ + switch (cspi_mod) { + case 0: + /* SPI1 */ + mxc_request_iomux(MX31_PIN_CSPI1_MISO, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI1_MOSI, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI1_SCLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI1_SPI_RDY, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI1_SS0, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI1_SS1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI1_SS2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + break; + case 1: + /* SPI2 */ + mxc_request_iomux(MX31_PIN_CSPI2_MISO, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_MOSI, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SCLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SPI_RDY, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SS0, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SS1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SS2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + break; + case 2: + /* SPI3 */ + /* + mxc_request_iomux(MX31_PIN_CSPI2_MISO, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_MOSI, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SCLK, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SPI_RDY, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SS0, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SS1, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SS2, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + */ + break; + default: + break; + } +} + +/*! + * Setup 1-Wire to be active + */ +void gpio_owire_active(void) +{ + /* + * Configure the IOMUX control register for 1-wire signals. + */ + iomux_config_mux(MX31_PIN_BATT_LINE, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + iomux_config_pad(MX31_PIN_BATT_LINE, PAD_CTL_LOOPBACK); +} + +/*! + * Setup 1-Wire to be active + */ +void gpio_owire_inactive(void) +{ + /* + * Configure the IOMUX control register for 1-wire signals. + */ + iomux_config_mux(MX31_PIN_BATT_LINE, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); +} + +EXPORT_SYMBOL(gpio_owire_active); +EXPORT_SYMBOL(gpio_owire_inactive); + +/*! + * Setup GPIO for a CSPI device to be inactive + * + * @param cspi_mod a CSPI device + */ +void gpio_spi_inactive(int cspi_mod) +{ + /* Do nothing as CSPI pins doesn't have/support GPIO mode */ +} + +/*! + * Setup GPIO for an I2C device to be active + * + * @param i2c_num an I2C device + */ +void gpio_i2c_active(int i2c_num) +{ + switch (i2c_num) { + case 0: + mxc_request_iomux(MX31_PIN_I2C_CLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_I2C_DAT, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + break; + case 1: + mxc_request_iomux(MX31_PIN_CSPI2_MOSI, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_CSPI2_MISO, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + break; + case 2: + mxc_request_iomux(MX31_PIN_CSPI2_SS2, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_CSPI2_SCLK, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + break; + default: + break; + } + +} + +/*! + * Setup GPIO for an I2C device to be inactive + * + * @param i2c_num an I2C device + */ +void gpio_i2c_inactive(int i2c_num) +{ + switch (i2c_num) { + case 0: + break; + case 1: + break; + case 2: + mxc_request_iomux(MX31_PIN_CSPI2_SS2, OUTPUTCONFIG_GPIO, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_CSPI2_SCLK, OUTPUTCONFIG_GPIO, + INPUTCONFIG_ALT1); + break; + default: + break; + } +} + +/*! + * This function configures the IOMux block for PMIC standard operations. + * + */ +void gpio_pmic_active(void) +{ + mxc_request_iomux(MX31_PIN_GPIO1_3, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_GPIO1_3), NULL); + gpio_direction_input(IOMUX_TO_GPIO(MX31_PIN_GPIO1_3)); +} + +EXPORT_SYMBOL(gpio_pmic_active); + +/*! + * This function activates DAM ports 4 & 5 to enable + * audio I/O. Thsi function is called from mx31ads_gpio_init + * function, which is board-specific. + */ +void gpio_activate_audio_ports(void) +{ + /* config Audio ports (4 & 5) */ + mxc_request_iomux(MX31_PIN_SCK4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SRXD4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_STXD4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SFS4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SCK5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SRXD5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_STXD5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SFS5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); +} + +/*! + * Setup GPIO for SDHC to be active + * + * @param module SDHC module number + */ +void gpio_sdhc_active(int module) +{ + switch (module) { + case 0: + mxc_request_iomux(MX31_PIN_SD1_CLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SD1_CMD, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SD1_DATA0, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SD1_DATA1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SD1_DATA2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SD1_DATA3, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + + mxc_iomux_set_pad(MX31_PIN_SD1_CLK, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_SD1_CMD, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA0, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA1, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA2, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA3, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + break; + case 1: + mxc_request_iomux(MX31_PIN_PC_CD2_B, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_PC_CD1_B, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_PC_WAIT_B, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_PC_READY, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_PC_VS1, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_PC_PWRON, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + break; + default: + break; + } +} + +EXPORT_SYMBOL(gpio_sdhc_active); + +/*! + * Setup GPIO for SDHC1 to be inactive + * + * @param module SDHC module number + */ +void gpio_sdhc_inactive(int module) +{ + switch (module) { + case 0: + mxc_request_iomux(MX31_PIN_SD1_CLK, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_SD1_CMD, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_SD1_DATA0, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_SD1_DATA1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_SD1_DATA2, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_SD1_DATA3, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + + mxc_iomux_set_pad(MX31_PIN_SD1_CLK, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX31_PIN_SD1_CMD, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA0, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA1, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA2, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA3, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + break; + case 1: + /* TODO:what are the pins for SDHC2? */ + mxc_request_iomux(MX31_PIN_PC_CD2_B, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_PC_CD1_B, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_PC_WAIT_B, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_PC_READY, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_PC_VS1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_PC_PWRON, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + break; + default: + break; + } +} + +EXPORT_SYMBOL(gpio_sdhc_inactive); + +/* + * Probe for the card. If present the GPIO data would be set. + */ +int sdhc_get_card_det_status(struct device *dev) +{ + if (to_platform_device(dev)->id == 0) { + return gpio_get_value(IOMUX_TO_GPIO(MX31_PIN_GPIO1_1)); + } else { + return gpio_get_value(IOMUX_TO_GPIO(MX31_PIN_GPIO1_2)); + } +} + +EXPORT_SYMBOL(sdhc_get_card_det_status); + +/* + * Return the card detect pin. + */ +int sdhc_init_card_det(int id) +{ + if (id == 0) { + iomux_config_mux(MX31_PIN_GPIO1_1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + return IOMUX_TO_IRQ(MX31_PIN_GPIO1_1); + } else { + iomux_config_mux(MX31_PIN_GPIO1_2, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + return IOMUX_TO_IRQ(MX31_PIN_GPIO1_2); + + } +} + +EXPORT_SYMBOL(sdhc_init_card_det); + +/*! + * Setup GPIO for LCD to be active + * + */ +void gpio_lcd_active(void) +{ + u16 temp; + + mxc_request_iomux(MX31_PIN_LD0, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD1, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD2, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD3, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD7, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD8, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD9, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD10, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD11, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD12, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD13, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD14, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD15, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD16, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // LD16 + mxc_request_iomux(MX31_PIN_LD17, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // LD17 + mxc_request_iomux(MX31_PIN_VSYNC3, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // VSYNC + mxc_request_iomux(MX31_PIN_HSYNC, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // HSYNC + mxc_request_iomux(MX31_PIN_FPSHIFT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // CLK + mxc_request_iomux(MX31_PIN_DRDY0, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // DRDY + mxc_request_iomux(MX31_PIN_D3_REV, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // REV + mxc_request_iomux(MX31_PIN_CONTRAST, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // CONTR + mxc_request_iomux(MX31_PIN_D3_SPL, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // SPL + mxc_request_iomux(MX31_PIN_D3_CLS, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // CLS + + temp = PBC_BCTRL1_LCDON; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL1_SET); +} + +/*! + * Setup GPIO for LCD to be inactive + * + */ +void gpio_lcd_inactive(void) +{ + u16 pbc_bctrl1_set = 0; + + pbc_bctrl1_set = (u16) PBC_BCTRL1_LCDON; + __raw_writew(pbc_bctrl1_set, PBC_BASE_ADDRESS + PBC_BCTRL1_SET + 2); +} + +/*! + * Setup pins for SLCD to be active + * + */ +void slcd_gpio_config(void) +{ + u16 temp; + + /* Reset smart lcd */ + temp = PBC_BCTRL2_LDC_RST0; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL2_CLEAR); + msleep(2); + /* Bring out of reset */ + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL2_SET); + msleep(2); + + mxc_request_iomux(MX31_PIN_LD0, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD1, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD2, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD3, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD7, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD8, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD9, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD10, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD11, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD12, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD13, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD14, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD15, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD16, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD17, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + mxc_request_iomux(MX31_PIN_READ, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); /* read */ + mxc_request_iomux(MX31_PIN_WRITE, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); /* write */ + mxc_request_iomux(MX31_PIN_PAR_RS, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); /* RS */ + mxc_request_iomux(MX31_PIN_LCS0, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); /* chip select */ + + /* Enable smart lcd interface */ + temp = PBC_BCTRL2_LDCIO_EN; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL2_CLEAR); +} + +/*! + * Switch to the specified sensor - MX31 ADS has two + * + */ +void gpio_sensor_select(int sensor) +{ + u16 temp; + + switch (sensor) { + case 0: +#ifdef CONFIG_MXC_CAMERA_MC521DA + temp = 0x100; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL1_SET); +#else + temp = PBC_BCTRL1_SENSOR2_ON; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL1_CLEAR); + temp = PBC_BCTRL1_SENSOR1_ON; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL1_SET); +#endif + break; + case 1: + temp = PBC_BCTRL1_SENSOR1_ON; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL1_CLEAR); + temp = PBC_BCTRL1_SENSOR2_ON; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL1_SET); + break; + default: + break; + } +} + +/*! + * Setup GPIO for sensor to be active + * + */ +void gpio_sensor_active(void) +{ + gpio_sensor_select(0); + + /* + * Configure the iomuxen for the CSI. + */ + + mxc_request_iomux(MX31_PIN_CSI_D4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D7, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D8, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D9, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D10, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D11, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D12, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D13, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D14, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D15, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_HSYNC, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_MCLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_PIXCLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_VSYNC, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + +#ifdef CONFIG_MXC_IPU_CAMERA_16BIT + /* + * The other 4 data bits are multiplexed on MX31. + */ + mxc_request_iomux(MX31_PIN_ATA_CS0, OUTPUTCONFIG_ALT2, + INPUTCONFIG_ALT2); + mxc_request_iomux(MX31_PIN_ATA_CS1, OUTPUTCONFIG_ALT2, + INPUTCONFIG_ALT2); + mxc_request_iomux(MX31_PIN_ATA_DIOR, OUTPUTCONFIG_ALT2, + INPUTCONFIG_ALT2); + mxc_request_iomux(MX31_PIN_ATA_DIOW, OUTPUTCONFIG_ALT2, + INPUTCONFIG_ALT2); +#endif + + /* + * Now enable the CSI buffers + */ + + __raw_writew(PBC_BCTRL2_CSI_EN, PBC_BASE_ADDRESS + PBC_BCTRL2_CLEAR); + +#ifdef CONFIG_MXC_IPU_CAMERA_16BIT + /* + * Enable the other buffer for the additional 4 data bits. + */ + __raw_writew(PBC_BCTRL4_CSI_MSB_EN, + PBC_BASE_ADDRESS + PBC_BCTRL4_CLEAR); +#endif +} + +EXPORT_SYMBOL(gpio_sensor_active); + +void gpio_sensor_reset(bool flag) +{ + u16 temp; + + if (flag) { + temp = 0x200; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL1_CLEAR); + } else { + temp = 0x200; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL1_SET); + } +} + +EXPORT_SYMBOL(gpio_sensor_reset); + +/*! + * Setup GPIO for sensor to be inactive + * + */ +void gpio_sensor_inactive(void) +{ + mxc_free_iomux(MX31_PIN_CSI_D4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D7, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D8, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D9, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D10, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D11, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D12, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D13, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D14, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D15, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_HSYNC, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_MCLK, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_PIXCLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_VSYNC, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); +} + +EXPORT_SYMBOL(gpio_sensor_inactive); + +/*! + * Setup GPIO for ATA interface + * + */ +void gpio_ata_active(void) +{ + /* + * Configure the GPR for ATA group B signals + */ + __raw_writew(PBC_BCTRL2_ATA_SEL, PBC_BASE_ADDRESS + PBC_BCTRL2_SET); + __raw_writew(PBC_BCTRL2_ATA_EN, PBC_BASE_ADDRESS + PBC_BCTRL2_CLEAR); + + mxc_iomux_set_gpr(MUX_PGP_ATA_7 | MUX_PGP_ATA_6 | MUX_PGP_ATA_2 | + MUX_PGP_ATA_1, true); + + /* + * Configure the IOMUX for ATA group B signals + */ + + mxc_request_iomux(MX31_PIN_CSPI1_MOSI, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D0 + mxc_request_iomux(MX31_PIN_CSPI1_MISO, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D1 + mxc_request_iomux(MX31_PIN_CSPI1_SS0, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D2 + mxc_request_iomux(MX31_PIN_CSPI1_SS1, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D3 + mxc_request_iomux(MX31_PIN_CSPI1_SS2, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D4 + mxc_request_iomux(MX31_PIN_CSPI1_SCLK, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D5 + mxc_request_iomux(MX31_PIN_CSPI1_SPI_RDY, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D6 + mxc_request_iomux(MX31_PIN_STXD3, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D7 + mxc_request_iomux(MX31_PIN_SRXD3, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D8 + mxc_request_iomux(MX31_PIN_SCK3, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D9 + mxc_request_iomux(MX31_PIN_SFS3, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D10 + mxc_request_iomux(MX31_PIN_STXD6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D11 + mxc_request_iomux(MX31_PIN_SRXD6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D12 + mxc_request_iomux(MX31_PIN_SCK6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D13 + mxc_request_iomux(MX31_PIN_CAPTURE, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D14 + mxc_request_iomux(MX31_PIN_COMPARE, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D15 + + mxc_request_iomux(MX31_PIN_USBH2_STP, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_DMARQ_B + mxc_request_iomux(MX31_PIN_USBH2_CLK, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_INTRQ_B + mxc_request_iomux(MX31_PIN_USBH2_NXT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_DA0 + mxc_request_iomux(MX31_PIN_USBH2_DATA0, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_DA1 + mxc_request_iomux(MX31_PIN_USBH2_DATA1, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_DA2 + mxc_request_iomux(MX31_PIN_USBH2_DIR, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_BUFFER_DIR + + /* These ATA pins are common to Group A and Group B */ + + mxc_request_iomux(MX31_PIN_ATA_CS0, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_ATA_CS1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_ATA_DIOR, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_ATA_DIOW, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_ATA_DMACK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_ATA_RESET_B, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_PWMO, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + /* Need fast slew rate for UDMA mode */ + + mxc_iomux_set_pad(MX31_PIN_CSPI1_MISO, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 0 + mxc_iomux_set_pad(MX31_PIN_CSPI1_MOSI, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 1 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS0, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 2 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS1, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 3 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS2, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 4 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SCLK, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 5 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SPI_RDY, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 6 + mxc_iomux_set_pad(MX31_PIN_STXD3, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 7 + mxc_iomux_set_pad(MX31_PIN_SRXD3, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 8 + mxc_iomux_set_pad(MX31_PIN_SCK3, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 9 + mxc_iomux_set_pad(MX31_PIN_SFS3, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 10 + mxc_iomux_set_pad(MX31_PIN_STXD6, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 11 + mxc_iomux_set_pad(MX31_PIN_SRXD6, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 12 + mxc_iomux_set_pad(MX31_PIN_SCK6, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 13 + mxc_iomux_set_pad(MX31_PIN_CAPTURE, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 14 + mxc_iomux_set_pad(MX31_PIN_COMPARE, PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE); // data 12 + + /* + * Turn off default pullups on high asserted control signals. + * These are pulled down externally, so it will just waste + * power and create voltage divider action to pull them up + * on chip. + */ + mxc_iomux_set_pad(MX31_PIN_USBH2_STP, PAD_CTL_PKE_NONE); // ATA_DMARQ + mxc_iomux_set_pad(MX31_PIN_USBH2_CLK, PAD_CTL_PKE_NONE); // ATA_INTRQ +} + +EXPORT_SYMBOL(gpio_ata_active); + +/*! + * Restore ATA interface pins to reset values + * + */ +void gpio_ata_inactive(void) +{ + __raw_writew(PBC_BCTRL2_ATA_EN, PBC_BASE_ADDRESS + PBC_BCTRL2_SET); + /* + * Turn off ATA group B signals + */ + mxc_request_iomux(MX31_PIN_STXD3, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D7 + mxc_request_iomux(MX31_PIN_SRXD3, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D8 + mxc_request_iomux(MX31_PIN_STXD6, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D11 + mxc_request_iomux(MX31_PIN_SRXD6, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D12 + mxc_request_iomux(MX31_PIN_SCK6, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D13 + mxc_request_iomux(MX31_PIN_CAPTURE, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D14 + mxc_request_iomux(MX31_PIN_COMPARE, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D15 + + /* These ATA pins are common to Group A and Group B */ + + mxc_request_iomux(MX31_PIN_ATA_CS0, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_ATA_CS1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_ATA_DIOR, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_ATA_DIOW, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_ATA_DMACK, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_ATA_RESET_B, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + + /* Needed fast slew rate for UDMA mode */ + + mxc_iomux_set_pad(MX31_PIN_CSPI1_MISO, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 0 + mxc_iomux_set_pad(MX31_PIN_CSPI1_MOSI, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 1 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS0, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 2 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS1, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 3 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS2, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 4 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SCLK, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 5 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SPI_RDY, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 6 + mxc_iomux_set_pad(MX31_PIN_STXD3, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 7 + mxc_iomux_set_pad(MX31_PIN_SRXD3, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 8 + mxc_iomux_set_pad(MX31_PIN_SCK3, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 9 + mxc_iomux_set_pad(MX31_PIN_SFS3, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 10 + mxc_iomux_set_pad(MX31_PIN_STXD3, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 11 + mxc_iomux_set_pad(MX31_PIN_SRXD6, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 12 + mxc_iomux_set_pad(MX31_PIN_SCK6, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 13 + mxc_iomux_set_pad(MX31_PIN_CAPTURE, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 14 + mxc_iomux_set_pad(MX31_PIN_COMPARE, PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE); // data 12 +} + +EXPORT_SYMBOL(gpio_ata_inactive); + +/*! + * Setup EDIO/IOMUX for external UART. + * + * @param port UART port + * @param irq Interrupt line to allocate + * @param handler Function to be called when the IRQ occurs + * @param irq_flags Interrupt type flags + * @param devname An ascii name for the claiming device + * @param dev_id A cookie passed back to the handler function + * @return Returns 0 if the interrupt was successfully requested, + * otherwise returns an error code. + */ +int extuart_intr_setup(unsigned int port, unsigned int irq, + irqreturn_t(*handler) (int, void *), + unsigned long irq_flags, const char *devname, + void *dev_id) +{ + return 0; +} + +/*! + * Get the EDIO interrupt, clear if set. + * + * @param port UART port + */ +void extuart_intr_clear(unsigned int port) +{ +} + +/*! + * Do IOMUX configs required to put the + * pin back in low power mode. + * + * @param port UART port + * @param irq Interrupt line to free + * @param dev_id Device identity to free + * @return Returns 0 if the interrupt was successfully freed, + * otherwise returns an error code. + */ +int extuart_intr_cleanup(unsigned int port, unsigned int irq, void *dev_id) +{ + return 0; +} + +/* *INDENT-OFF* */ +/* + * USB Host 1 + * pins conflict with SPI1, ATA, UART3 + */ +int gpio_usbh1_active(void) +{ + if (mxc_request_iomux(MX31_PIN_CSPI1_MOSI, /* USBH1_RXDM */ + OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1) || + mxc_request_iomux(MX31_PIN_CSPI1_MISO, /* USBH1_RXDP */ + OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1) || + mxc_request_iomux(MX31_PIN_CSPI1_SS0, /* USBH1_TXDM */ + OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1) || + mxc_request_iomux(MX31_PIN_CSPI1_SS1, /* USBH1_TXDP */ + OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1) || + mxc_request_iomux(MX31_PIN_CSPI1_SS2, /* USBH1_RCV */ + OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1) || + mxc_request_iomux(MX31_PIN_CSPI1_SCLK, /* USBH1_OEB (_TXOE) */ + OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1) || + mxc_request_iomux(MX31_PIN_CSPI1_SPI_RDY, /* USBH1_FS */ + OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1)) { + return -EINVAL; + } + + mxc_iomux_set_pad(MX31_PIN_CSPI1_MOSI, /* USBH1_RXDM */ + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + + mxc_iomux_set_pad(MX31_PIN_CSPI1_MISO, /* USBH1_RXDP */ + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS0, /* USBH1_TXDM */ + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS1, /* USBH1_TXDP */ + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS2, /* USBH1_RCV */ + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + + mxc_iomux_set_pad(MX31_PIN_CSPI1_SCLK, /* USBH1_OEB (_TXOE) */ + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + + mxc_iomux_set_pad(MX31_PIN_CSPI1_SPI_RDY, /* USBH1_FS */ + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + + mxc_iomux_set_gpr(MUX_PGP_USB_SUSPEND, true); + + __raw_writew(PBC_BCTRL3_FSH_EN, PBC_BASE_ADDRESS + PBC_BCTRL3_CLEAR); /* enable FSH */ + __raw_writew(PBC_BCTRL3_FSH_SEL, PBC_BASE_ADDRESS + PBC_BCTRL3_SET); /* Group B */ + __raw_writew(PBC_BCTRL3_FSH_MOD, PBC_BASE_ADDRESS + PBC_BCTRL3_CLEAR); /* single ended */ + __raw_writew(PBC_BCTRL3_FSH_VBUS_EN, PBC_BASE_ADDRESS + PBC_BCTRL3_CLEAR); /* enable FSH VBUS */ + + return 0; +} + +EXPORT_SYMBOL(gpio_usbh1_active); + +void gpio_usbh1_inactive(void) +{ + /* Do nothing as pins don't have/support GPIO mode */ + +} + +EXPORT_SYMBOL(gpio_usbh1_inactive); + +/* + * USB Host 2 + * pins conflict with UART5, PCMCIA + */ +int gpio_usbh2_active(void) +{ + if (mxc_request_iomux(MX31_PIN_USBH2_CLK, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBH2_DIR, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBH2_NXT, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBH2_STP, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBH2_DATA0, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBH2_DATA1, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_PC_VS2, /* USBH2_DATA2 */ + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE) || + mxc_request_iomux(MX31_PIN_PC_BVD1, /* USBH2_DATA3 */ + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE) || + mxc_request_iomux(MX31_PIN_PC_BVD2, /* USBH2_DATA4 */ + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE) || + mxc_request_iomux(MX31_PIN_PC_RST, /* USBH2_DATA5 */ + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE) || + mxc_request_iomux(MX31_PIN_IOIS16, /* USBH2_DATA6 */ + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE) || + mxc_request_iomux(MX31_PIN_PC_RW_B, /* USBH2_DATA7 */ + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE) || + mxc_request_iomux(MX31_PIN_NFWE_B, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE) || + mxc_request_iomux(MX31_PIN_NFRE_B, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE) || + mxc_request_iomux(MX31_PIN_NFALE, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE) || + mxc_request_iomux(MX31_PIN_NFCLE, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE) || + mxc_request_iomux(MX31_PIN_NFWP_B, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE) || + mxc_request_iomux(MX31_PIN_NFCE_B, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE)) { + return -EINVAL; + } + +#define H2_PAD_CFG (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST | PAD_CTL_HYS_CMOS | PAD_CTL_ODE_CMOS | PAD_CTL_100K_PU) + mxc_iomux_set_pad(MX31_PIN_USBH2_CLK, H2_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_USBH2_DIR, H2_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_USBH2_NXT, H2_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_USBH2_STP, H2_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_USBH2_DATA0, H2_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_USBH2_DATA1, H2_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_SRXD6, H2_PAD_CFG); /* USBH2_DATA2 */ + mxc_iomux_set_pad(MX31_PIN_STXD6, H2_PAD_CFG); /* USBH2_DATA3 */ + mxc_iomux_set_pad(MX31_PIN_SFS3, H2_PAD_CFG); /* USBH2_DATA4 */ + mxc_iomux_set_pad(MX31_PIN_SCK3, H2_PAD_CFG); /* USBH2_DATA5 */ + mxc_iomux_set_pad(MX31_PIN_SRXD3, H2_PAD_CFG); /* USBH2_DATA6 */ + mxc_iomux_set_pad(MX31_PIN_STXD3, H2_PAD_CFG); /* USBH2_DATA7 */ +#undef H2_PAD_CFG + + mxc_iomux_set_gpr(MUX_PGP_UH2, true); + + __raw_writew(PBC_BCTRL3_HSH_SEL, PBC_BASE_ADDRESS + PBC_BCTRL3_SET); /* enable HSH select */ + __raw_writew(PBC_BCTRL3_HSH_EN, PBC_BASE_ADDRESS + PBC_BCTRL3_CLEAR); /* enable HSH */ + + return 0; +} + +EXPORT_SYMBOL(gpio_usbh2_active); + +void gpio_usbh2_inactive(void) +{ + iomux_config_gpr(MUX_PGP_UH2, false); + + iomux_config_pad(MX31_PIN_USBH2_CLK, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + iomux_config_pad(MX31_PIN_USBH2_DIR, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + iomux_config_pad(MX31_PIN_USBH2_NXT, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + iomux_config_pad(MX31_PIN_USBH2_STP, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + iomux_config_pad(MX31_PIN_USBH2_DATA0, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + iomux_config_pad(MX31_PIN_USBH2_DATA1, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + iomux_config_pad(MX31_PIN_SRXD6, /* USBH2_DATA2 */ + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + iomux_config_pad(MX31_PIN_STXD6, /* USBH2_DATA3 */ + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + iomux_config_pad(MX31_PIN_SFS3, /* USBH2_DATA4 */ + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + iomux_config_pad(MX31_PIN_SCK3, /* USBH2_DATA5 */ + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + iomux_config_pad(MX31_PIN_SRXD3, /* USBH2_DATA6 */ + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + iomux_config_pad(MX31_PIN_STXD3, /* USBH2_DATA7 */ + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + + mxc_free_iomux(MX31_PIN_NFWE_B, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_NFRE_B, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_NFALE, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_NFCLE, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_NFWP_B, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_NFCE_B, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + + __raw_writew(PBC_BCTRL3_HSH_SEL, PBC_BASE_ADDRESS + PBC_BCTRL3_CLEAR); + __raw_writew(PBC_BCTRL3_HSH_EN, PBC_BASE_ADDRESS + PBC_BCTRL3_SET); +} + +EXPORT_SYMBOL(gpio_usbh2_inactive); + +/* + * USB OTG HS port + */ +int gpio_usbotg_hs_active(void) +{ + if (mxc_request_iomux(MX31_PIN_USBOTG_DATA0, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA1, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA2, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA3, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA4, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA5, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA6, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA7, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_CLK, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DIR, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_NXT, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_STP, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC)) { + return -EINVAL; + } + + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA0, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA1, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA2, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA3, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA4, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA5, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA6, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA7, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_CLK, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DIR, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_NXT, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_STP, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + + /* enable OTG/HS */ + __raw_writew(PBC_BCTRL3_OTG_HS_EN, PBC_BASE_ADDRESS + PBC_BCTRL3_CLEAR); + /* disable OTG/FS */ + __raw_writew(PBC_BCTRL3_OTG_FS_EN, PBC_BASE_ADDRESS + PBC_BCTRL3_SET); + return 0; +} + +EXPORT_SYMBOL(gpio_usbotg_hs_active); + +void gpio_usbotg_hs_inactive(void) +{ + /* Do nothing as pins doesn't have/support GPIO mode */ + +} + +EXPORT_SYMBOL(gpio_usbotg_hs_inactive); + +/* + * USB OTG FS port + */ +int gpio_usbotg_fs_active(void) +{ + if (mxc_request_iomux(MX31_PIN_USBOTG_DATA0, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA1, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA2, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA3, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA4, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA5, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA6, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA7, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_CLK, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DIR, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_NXT, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_STP, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USB_PWR, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC)) { + return -EINVAL; + } + + /* disable OTG/HS */ + __raw_writew(PBC_BCTRL3_OTG_HS_EN, PBC_BASE_ADDRESS + PBC_BCTRL3_SET); + /* enable OTG/FS */ + __raw_writew(PBC_BCTRL3_OTG_FS_EN, PBC_BASE_ADDRESS + PBC_BCTRL3_CLEAR); + +#if defined(CONFIG_MC13783_MXC) + /* Select PMIC transceiver */ + __raw_writew(PBC_BCTRL3_OTG_FS_SEL, PBC_BASE_ADDRESS + PBC_BCTRL3_CLEAR); +#endif + return 0; + +} + +EXPORT_SYMBOL(gpio_usbotg_fs_active); + +void gpio_usbotg_fs_inactive(void) +{ + /* Do nothing as pins doesn't have/support GPIO mode */ + +} + +EXPORT_SYMBOL(gpio_usbotg_fs_inactive); +/* *INDENT-ON* */ + +/*! + * Setup GPIO for PCMCIA interface + * + */ +void gpio_pcmcia_active(void) +{ + u16 temp; + + mxc_request_iomux(MX31_PIN_SDBA0, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SDBA1, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + iomux_config_mux(MX31_PIN_LBA, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_RW, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_EB0, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_EB1, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_OE, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + iomux_config_mux(MX31_PIN_IOIS16, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_PC_BVD1, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_PC_BVD2, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_PC_CD1_B, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_PC_CD2_B, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_PC_POE, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_PC_PWRON, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_PC_READY, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_PC_RST, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_PC_RW_B, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_PC_VS1, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_PC_VS2, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(MX31_PIN_PC_WAIT_B, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + + /* PCMCIA VPP, VCC Enable, 1 = power on */ + temp = PBC_BCTRL2_VPP_EN | PBC_BCTRL2_VCC_EN; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL2_SET); + + /* Set up Card2 Select pin for PCMCIA, 0 = PCMCIA & SD2 */ + temp = PBC_BCTRL3_CARD2_SEL; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL3_CLEAR); + + /* PCMCIA Enable, 0 = enable */ + temp = PBC_BCTRL4_PCMCIA_EN; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL4_CLEAR); + mdelay(1); +} + +EXPORT_SYMBOL(gpio_pcmcia_active); + +/*! + * Setup GPIO for pcmcia to be inactive + */ +void gpio_pcmcia_inactive(void) +{ + u16 temp; + + /* PCMCIA Enable, 0 = enable */ + temp = PBC_BCTRL4_PCMCIA_EN; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL4_SET); + + /* Set up Card2 Select pin for PCMCIA, 0 = PCMCIA & SD2 */ + temp = PBC_BCTRL3_CARD2_SEL; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL3_SET); + + /* PCMCIA VPP, VCC Enable, 1 = power on */ + temp = PBC_BCTRL2_VPP_EN | PBC_BCTRL2_VCC_EN; + __raw_writew(temp, PBC_BASE_ADDRESS + PBC_BCTRL2_CLEAR); +} + +EXPORT_SYMBOL(gpio_pcmcia_inactive); +/*! + * Setup IR to be used by UART and FIRI + */ +void gpio_firi_init(void) +{ + gpio_uart_active(1, 0); +} + +EXPORT_SYMBOL(gpio_firi_init); + +/*! + * Setup IR to be used by UART + */ +void gpio_firi_inactive(void) +{ + unsigned int pbc_bctrl2_set = 0, pbc_bctrl2_clr = 0; + + iomux_config_gpr(MUX_PGP_FIRI, false); + mxc_request_iomux(MX31_PIN_TXD2, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_RXD2, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + pbc_bctrl2_set |= PBC_BCTRL2_IRDA_MOD; + __raw_writew(pbc_bctrl2_set, PBC_BASE_ADDRESS + PBC_BCTRL2_SET); + + pbc_bctrl2_clr |= PBC_BCTRL2_IRDA_MOD; + __raw_writew(pbc_bctrl2_clr, PBC_BASE_ADDRESS + PBC_BCTRL2_CLEAR); +} + +EXPORT_SYMBOL(gpio_firi_inactive); + +/*! + * Setup IR to be used by FIRI + */ +void gpio_firi_active(void *fir_cong_reg_base, unsigned int tpp_mask) +{ + unsigned int pbc_bctrl2_set = 0, pbc_bctrl2_clr = 0; + unsigned int cr; + + iomux_config_gpr(MUX_PGP_FIRI, true); + + cr = readl(fir_cong_reg_base); + cr &= ~tpp_mask; + writel(cr, fir_cong_reg_base); + + pbc_bctrl2_clr |= PBC_BCTRL2_IRDA_MOD; + __raw_writew(pbc_bctrl2_clr, PBC_BASE_ADDRESS + PBC_BCTRL2_CLEAR); + + pbc_bctrl2_set |= PBC_BCTRL2_IRDA_MOD; + __raw_writew(pbc_bctrl2_set, PBC_BASE_ADDRESS + PBC_BCTRL2_SET); + + cr = readl(fir_cong_reg_base); + cr |= tpp_mask; + writel(cr, fir_cong_reg_base); + + __raw_writew(pbc_bctrl2_clr, PBC_BASE_ADDRESS + PBC_BCTRL2_CLEAR); + + cr = readl(fir_cong_reg_base); + cr &= ~tpp_mask; + writel(cr, fir_cong_reg_base); +} + +EXPORT_SYMBOL(gpio_firi_active); diff --git a/arch/arm/mach-mx3/mx3_3stack.c b/arch/arm/mach-mx3/mx3_3stack.c new file mode 100644 index 000000000000..5a29b0550509 --- /dev/null +++ b/arch/arm/mach-mx3/mx3_3stack.c @@ -0,0 +1,1075 @@ +/* + * Copyright (C) 2000 Deep Blue Solutions Ltd + * Copyright (C) 2002 Shane Nay (shane@minirl.com) + * Copyright 2005-2009 Freescale Semiconductor, Inc. All 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 <linux/types.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/i2c.h> +#include <linux/nodemask.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/consumer.h> +#include <linux/ata.h> +#include <linux/smsc911x.h> +#if defined(CONFIG_MTD) || defined(CONFIG_MTD_MODULE) +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> + +#include <asm/mach/flash.h> +#endif + +#include <asm/irq.h> +#include <asm/setup.h> +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <asm/mach/irq.h> +#include <asm/mach/keypad.h> +#include <asm/mach/time.h> +#include <mach/common.h> +#include <mach/hardware.h> +#include <mach/memory.h> +#include <mach/gpio.h> +#include <mach/mmc.h> +#include <mach/spba.h> +#include <mach/pmic_power.h> + +#include "board-mx3_3stack.h" +#include "crm_regs.h" +#include "iomux.h" +/*! + * @file mach-mx3/mx3_3stack.c + * + * @brief This file contains the board specific initialization routines. + * + * @ingroup MSL_MX31 + */ + +extern void mxc_map_io(void); +extern void mxc_init_irq(void); +extern void mxc_cpu_init(void) __init; +extern void mxc_cpu_common_init(void); +extern void __init early_console_setup(char *); +extern int mxc_init_devices(void); + +static void mxc_nop_release(struct device *dev) +{ + /* Nothing */ +} + +#if defined(CONFIG_KEYBOARD_MXC) || defined(CONFIG_KEYBOARD_MXC_MODULE) +static u16 keymapping[12] = { + KEY_UP, KEY_DOWN, 0, 0, + KEY_RIGHT, KEY_LEFT, KEY_ENTER, 0, + KEY_F6, KEY_F8, KEY_F9, KEY_F10, +}; + +static struct resource mxc_kpp_resources[] = { + [0] = { + .start = MXC_INT_KPP, + .end = MXC_INT_KPP, + .flags = IORESOURCE_IRQ, + } +}; + +static struct keypad_data keypad_plat_data = { + .rowmax = 3, + .colmax = 4, + .irq = MXC_INT_KPP, + .learning = 0, + .delay = 2, + .matrix = keymapping, +}; + +/* mxc keypad driver */ +static struct platform_device mxc_keypad_device = { + .name = "mxc_keypad", + .id = 0, + .num_resources = ARRAY_SIZE(mxc_kpp_resources), + .resource = mxc_kpp_resources, + .dev = { + .release = mxc_nop_release, + .platform_data = &keypad_plat_data, + }, +}; + +static void mxc_init_keypad(void) +{ + (void)platform_device_register(&mxc_keypad_device); +} +#else +static inline void mxc_init_keypad(void) +{ +} +#endif + +/* MTD NAND flash */ +#if defined(CONFIG_MTD_NAND_MXC) || defined(CONFIG_MTD_NAND_MXC_MODULE) \ + || defined(CONFIG_MTD_NAND_MXC_V2) || defined(CONFIG_MTD_NAND_MXC_V2_MODULE) + +static struct mtd_partition mxc_nand_partitions[] = { + { + .name = "nand.bootloader", + .offset = 0, + .size = 1024 * 1024}, + { + .name = "nand.kernel", + .offset = MTDPART_OFS_APPEND, + .size = 5 * 1024 * 1024}, + { + .name = "nand.rootfs", + .offset = MTDPART_OFS_APPEND, + .size = 180 * 1024 * 1024}, + { + .name = "nand.configure", + .offset = MTDPART_OFS_APPEND, + .size = 8 * 1024 * 1024}, + { + .name = "nand.userfs", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL}, +}; + +static struct flash_platform_data mxc_nand_data = { + .parts = mxc_nand_partitions, + .nr_parts = ARRAY_SIZE(mxc_nand_partitions), + .width = 1, +}; + +static struct platform_device mxc_nand_mtd_device = { + .name = "mxc_nand_flash", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_nand_data, + }, +}; + +static struct platform_device mxc_nandv2_mtd_device = { + .name = "mxc_nandv2_flash", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_nand_data, + }, +}; + +static void mxc_init_nand_mtd(void) +{ + if (__raw_readl(MXC_CCM_RCSR) & MXC_CCM_RCSR_NF16B) { + mxc_nand_data.width = 2; + } + if (cpu_is_mx31()) { + (void)platform_device_register(&mxc_nand_mtd_device); + } + if (cpu_is_mx32()) { + (void)platform_device_register(&mxc_nandv2_mtd_device); + } +} +#else +static inline void mxc_init_nand_mtd(void) +{ +} +#endif + +static void lcd_reset(void) +{ + /* ensure that LCDIO(1.8V) has been turn on */ + /* active reset line GPIO */ + mxc_request_iomux(MX31_PIN_LCS1, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_LCS1), "lcs1"); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_LCS1), 0); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_LCS1), 0); + /* do reset */ + msleep(10); /* tRES >= 100us */ + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_LCS1), 1); + msleep(60); +#ifdef CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_LCS1), 0); +#endif +} + +static struct mxc_lcd_platform_data lcd_data = { + .io_reg = "VGEN", +#ifdef CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL + .core_reg = "GPO1", +#else + .core_reg = "VMMC1", +#endif + .reset = lcd_reset, +}; + +static struct mxc_camera_platform_data camera_data = { + .core_regulator = "VVIB", + .io_regulator = "VMMC1", + .analog_regulator = "SW2B", + .gpo_regulator = "GPO3", + .mclk = 27000000, +}; + +struct mxc_tvout_platform_data tvout_data = { + .io_reg = "VGEN", + .core_reg = "GPO3", + .analog_reg = "GPO1", + .detect_line = 49, +}; + +void si4702_reset(void) +{ + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_SRST0), 0); + msleep(100); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_SRST0), 1); + msleep(100); +} + +void si4702_clock_ctl(int flag) +{ + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_SIMPD0), flag); +} + +static void si4702_gpio_get(void) +{ + /* reset pin */ + mxc_request_iomux(MX31_PIN_SRST0, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_SRST0), "srst0"); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_SRST0), 0); + + mxc_request_iomux(MX31_PIN_SIMPD0, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_SIMPD0), "simpd0"); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_SIMPD0), 0); +} + +static void si4702_gpio_put(void) +{ + mxc_free_iomux(MX31_PIN_SRST0, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_SIMPD0, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); +} + +static struct mxc_fm_platform_data si4702_data = { + .reg_vio = "GPO3", + .reg_vdd = "VMMC1", + .gpio_get = si4702_gpio_get, + .gpio_put = si4702_gpio_put, + .reset = si4702_reset, + .clock_ctl = si4702_clock_ctl, + .sksnr = 0, + .skcnt = 0, + .band = 0, + .space = 100, + .seekth = 0xa, +}; + +/* setup GPIO for mma7450 */ +static void gpio_mma7450_get(void) +{ + mxc_request_iomux(MX31_PIN_STX0, OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); + mxc_iomux_set_pad(MX31_PIN_STX0, PAD_CTL_PKE_NONE); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_STX0), "stx0"); + gpio_direction_input(IOMUX_TO_GPIO(MX31_PIN_STX0)); + + mxc_request_iomux(MX31_PIN_SRX0, OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); + mxc_iomux_set_pad(MX31_PIN_SRX0, PAD_CTL_PKE_NONE); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_SRX0), "srx0"); + gpio_direction_input(IOMUX_TO_GPIO(MX31_PIN_SRX0)); +} + +static void gpio_mma7450_put(void) +{ + mxc_free_iomux(MX31_PIN_STX0, OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_SRX0, OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); +} + +static struct mxc_mma7450_platform_data mma7450_data = { + .reg_dvdd_io = "GPO3", + .reg_avdd = "VMMC1", + .gpio_pin_get = gpio_mma7450_get, + .gpio_pin_put = gpio_mma7450_put, + .int1 = IOMUX_TO_IRQ(MX31_PIN_STX0), + .int2 = IOMUX_TO_IRQ(MX31_PIN_SRX0), +}; + +static struct i2c_board_info mxc_i2c_board_info[] __initdata = { + { + .type = "ov2640", + .addr = 0x30, + .platform_data = (void *)&camera_data,}, + { + .type = "ch7024", + .addr = 0x76, + .platform_data = (void *)&tvout_data, + .irq = IOMUX_TO_IRQ(MX31_PIN_BATT_LINE),}, + { + .type = "si4702", + .addr = 0x10, + .platform_data = (void *)&si4702_data, + }, + { + .type = "mma7450", + .addr = 0x1d, + .platform_data = (void *)&mma7450_data, + }, +}; + +static struct spi_board_info mxc_spi_board_info[] __initdata = { + { + .modalias = "pmic_spi", + .irq = IOMUX_TO_IRQ(MX31_PIN_GPIO1_3), + .max_speed_hz = 4000000, + .bus_num = 2, + .chip_select = 2, + }, + { + .modalias = "lcd_spi", + .platform_data = (void *)&lcd_data, + .max_speed_hz = 5000000, + .bus_num = 1, + .chip_select = 2, + }, +}; + +/*lan9217 device*/ +#if defined(CONFIG_SMSC911X) || defined(CONFIG_SMSC911X_MODULE) +static struct resource smsc911x_resources[] = { + { + .start = LAN9217_BASE_ADDR, + .end = LAN9217_BASE_ADDR + 0x100, + .flags = IORESOURCE_MEM, + }, + { + .start = LAN9217_IRQ, + .end = LAN9217_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct smsc911x_platform_config smsc911x_config = { + .irq_polarity = SMSC911X_IRQ_POLARITY_ACTIVE_LOW, + .flags = SMSC911X_USE_32BIT | SMSC911X_FORCE_INTERNAL_PHY, +}; + +static struct platform_device smsc_lan9217_device = { + .name = "smsc911x", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &smsc911x_config, + }, + .num_resources = ARRAY_SIZE(smsc911x_resources), + .resource = smsc911x_resources, +}; +static void mxc_init_enet(void) +{ + (void)platform_device_register(&smsc_lan9217_device); +} +#else +static inline void mxc_init_enet(void) +{ +} +#endif + +#if defined(CONFIG_FB_MXC_SYNC_PANEL) || defined(CONFIG_FB_MXC_SYNC_PANEL_MODULE) +static const char fb_default_mode[] = "Epson-VGA"; + +/* mxc lcd driver */ +static struct platform_device mxc_fb_device = { + .name = "mxc_sdc_fb", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &fb_default_mode, + .coherent_dma_mask = 0xFFFFFFFF, + }, +}; + +static struct platform_device mxc_fb_wvga_device = { + .name = "lcd_claa", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &lcd_data, + }, +}; + +static void mxc_init_fb(void) +{ + (void)platform_device_register(&mxc_fb_device); + (void)platform_device_register(&mxc_fb_wvga_device); +} +#else +static inline void mxc_init_fb(void) +{ +} +#endif + +#if defined(CONFIG_BACKLIGHT_MXC) +static struct platform_device mxcbl_devices[] = { +#if defined(CONFIG_BACKLIGHT_MXC_IPU) || defined(CONFIG_BACKLIGHT_MXC_IPU_MODULE) + { + .name = "mxc_ipu_bl", + .id = 0, + .dev = { + .platform_data = (void *)3, /* DISP # for this backlight */ + }, + }, +#endif +}; +static inline void mxc_init_bl(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(mxcbl_devices); i++) { + platform_device_register(&mxcbl_devices[i]); + } +} +#else +static inline void mxc_init_bl(void) +{ +} +#endif + +#if defined(CONFIG_FB_MXC_TVOUT_CH7024) || \ + defined(CONFIG_FB_MXC_TVOUT_CH7024_MODULE) +static int mxc_init_ch7024(void) +{ + /* request gpio for phone jack detect */ + mxc_request_iomux(MX31_PIN_BATT_LINE, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_iomux_set_pad(MX31_PIN_BATT_LINE, PAD_CTL_PKE_NONE); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_BATT_LINE), "batt_line"); + gpio_direction_input(IOMUX_TO_GPIO(MX31_PIN_BATT_LINE)); + + return 0; +} +#else +static inline int mxc_init_ch7024(void) +{ + return 0; +} +#endif + +static u32 brd_io; + +static void mxc_expio_irq_handler(u32 irq, struct irq_desc *desc) +{ + u32 imr_val; + u32 int_valid; + u32 expio_irq; + + desc->chip->mask(irq); /* irq = gpio irq number */ + + imr_val = __raw_readw(brd_io + INTR_MASK_REG); + int_valid = __raw_readw(brd_io + INTR_STATUS_REG) & ~imr_val; + + if (unlikely(!int_valid)) { + goto out; + } + + expio_irq = MXC_BOARD_IRQ_START; + for (; int_valid != 0; int_valid >>= 1, expio_irq++) { + struct irq_desc *d; + if ((int_valid & 1) == 0) + continue; + d = irq_desc + expio_irq; + if (unlikely(!(d->handle_irq))) { + printk(KERN_ERR "\nEXPIO irq: %d unhandled\n", + expio_irq); + BUG(); /* oops */ + } + d->handle_irq(expio_irq, d); + } + + out: + desc->chip->ack(irq); + desc->chip->unmask(irq); +} + +/* + * Disable an expio pin's interrupt by setting the bit in the imr. + * @param irq an expio virtual irq number + */ +static void expio_mask_irq(u32 irq) +{ + u16 reg; + u32 expio = MXC_IRQ_TO_EXPIO(irq); + /* mask the interrupt */ + reg = __raw_readw(brd_io + INTR_MASK_REG); + reg |= (1 << expio); + __raw_writew(reg, brd_io + INTR_MASK_REG); +} + +/* + * Acknowledge an expanded io pin's interrupt by clearing the bit in the isr. + * @param irq an expanded io virtual irq number + */ +static void expio_ack_irq(u32 irq) +{ + u32 expio = MXC_IRQ_TO_EXPIO(irq); + /* clear the interrupt status */ + __raw_writew(1 << expio, brd_io + INTR_RESET_REG); + __raw_writew(0, brd_io + INTR_RESET_REG); + /* mask the interrupt */ + expio_mask_irq(irq); +} + +/* + * Enable a expio pin's interrupt by clearing the bit in the imr. + * @param irq a expio virtual irq number + */ +static void expio_unmask_irq(u32 irq) +{ + u16 reg; + u32 expio = MXC_IRQ_TO_EXPIO(irq); + /* unmask the interrupt */ + reg = __raw_readw(brd_io + INTR_MASK_REG); + reg &= ~(1 << expio); + __raw_writew(reg, brd_io + INTR_MASK_REG); +} + +static struct irq_chip expio_irq_chip = { + .ack = expio_ack_irq, + .mask = expio_mask_irq, + .unmask = expio_unmask_irq, +}; + +static int __init mxc_expio_init(void) +{ + int i; + + brd_io = (u32) ioremap(BOARD_IO_ADDR, SZ_4K); + if (brd_io == 0) + return -ENOMEM; + + if ((__raw_readw(brd_io + MAGIC_NUMBER1_REG) != 0xAAAA) || + (__raw_readw(brd_io + MAGIC_NUMBER2_REG) != 0x5555) || + (__raw_readw(brd_io + MAGIC_NUMBER3_REG) != 0xCAFE)) { + iounmap((void *)brd_io); + brd_io = 0; + return -ENODEV; + } + + pr_info("3-Stack Debug board detected, rev = 0x%04X\n", + readw(brd_io + CPLD_CODE_VER_REG)); + + /* + * Configure INT line as GPIO input + */ + mxc_request_iomux(MX31_PIN_GPIO1_1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_GPIO1_1), "gpio1_1"); + gpio_direction_input(IOMUX_TO_GPIO(MX31_PIN_GPIO1_1)); + + /* disable the interrupt and clear the status */ + __raw_writew(0, brd_io + INTR_MASK_REG); + __raw_writew(0xFFFF, brd_io + INTR_RESET_REG); + __raw_writew(0, brd_io + INTR_RESET_REG); + __raw_writew(0x1F, brd_io + INTR_MASK_REG); + for (i = MXC_BOARD_IRQ_START; i < (MXC_BOARD_IRQ_START + MXC_BOARD_IRQS); i++) { + set_irq_chip(i, &expio_irq_chip); + set_irq_handler(i, handle_level_irq); + set_irq_flags(i, IRQF_VALID); + } + set_irq_type(EXPIO_PARENT_INT, IRQF_TRIGGER_LOW); + set_irq_chained_handler(EXPIO_PARENT_INT, mxc_expio_irq_handler); + + return 0; +} + +#if (defined(CONFIG_MXC_PMIC_MC13783) || \ + defined(CONFIG_MXC_PMIC_MC13783_MODULE)) \ + && (defined(CONFIG_SND_MXC_PMIC) || defined(CONFIG_SND_MXC_PMIC_MODULE)) +static void __init mxc_init_pmic_audio(void) +{ + struct clk *ckih_clk; + struct clk *cko_clk; + + /* Enable 26 mhz clock on CKO1 for PMIC audio */ + ckih_clk = clk_get(NULL, "ckih"); + cko_clk = clk_get(NULL, "cko1_clk"); + if (IS_ERR(ckih_clk) || IS_ERR(cko_clk)) { + printk(KERN_ERR "Unable to set CKO1 output to CKIH\n"); + } else { + clk_set_parent(cko_clk, ckih_clk); + clk_set_rate(cko_clk, clk_get_rate(ckih_clk)); + clk_enable(cko_clk); + } + clk_put(ckih_clk); + clk_put(cko_clk); + + /* config Audio ports (4 & 5) */ + mxc_request_iomux(MX31_PIN_SCK4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SRXD4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_STXD4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SFS4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SCK5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SRXD5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_STXD5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SFS5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); +} +#else +static void __inline mxc_init_pmic_audio(void) +{ +} +#endif + +/* MMC device data */ + +#if defined(CONFIG_MMC_MXC) || defined(CONFIG_MMC_MXC_MODULE) +static struct mxc_mmc_platform_data mmc1_data = { + .ocr_mask = MMC_VDD_32_33, + .min_clk = 150000, + .max_clk = 25000000, + .card_inserted_state = 0, + .status = sdhc_get_card_det_status, + .wp_status = sdhc_write_protect, + .power_mmc = "GPO1", +}; + +/*! + * Resource definition for the SDHC1 + */ +static struct resource mxcsdhc1_resources[] = { + [0] = { + .start = MMC_SDHC1_BASE_ADDR, + .end = MMC_SDHC1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_MMC_SDHC1, + .end = MXC_INT_MMC_SDHC1, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mxc_mmc_platform_data mmc2_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30 | + MMC_VDD_31_32, + .min_clk = 150000, + .max_clk = 25000000, + .card_fixed = 1, + .card_inserted_state = 0, + .status = sdhc_get_card_det_status, + .power_mmc = "VMMC2", +}; + +/*! + * Resource definition for the SDHC1 + */ +static struct resource mxcsdhc2_resources[] = { + [0] = { + .start = MMC_SDHC2_BASE_ADDR, + .end = MMC_SDHC2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_MMC_SDHC2, + .end = MXC_INT_MMC_SDHC2, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Device Definition for MXC SDHC1 */ +static struct platform_device mxcsdhc1_device = { + .name = "mxcmci", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mmc1_data, + }, + .num_resources = ARRAY_SIZE(mxcsdhc1_resources), + .resource = mxcsdhc1_resources, +}; + +/*! Device Definition for MXC SDHC2 */ +static struct platform_device mxcsdhc2_device = { + .name = "mxcmci", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mmc2_data, + }, + .num_resources = ARRAY_SIZE(mxcsdhc2_resources), + .resource = mxcsdhc2_resources, +}; + +static inline void mxc_init_mmc(void) +{ + int cd_irq; + + cd_irq = sdhc_init_card_det(0); + if (cd_irq) { + mxcsdhc1_device.resource[2].start = cd_irq; + mxcsdhc1_device.resource[2].end = cd_irq; + } + + cd_irq = sdhc_init_card_det(1); + if (cd_irq) { + mxcsdhc2_device.resource[2].start = cd_irq; + mxcsdhc2_device.resource[2].end = cd_irq; + } + + spba_take_ownership(SPBA_SDHC1, SPBA_MASTER_A | SPBA_MASTER_C); + (void)platform_device_register(&mxcsdhc1_device); + + spba_take_ownership(SPBA_SDHC2, SPBA_MASTER_A | SPBA_MASTER_C); + (void)platform_device_register(&mxcsdhc2_device); +} +#else +static inline void mxc_init_mmc(void) +{ +} +#endif + +/*! + * Board specific fixup function. It is called by \b setup_arch() in + * setup.c file very early on during kernel starts. It allows the user to + * statically fill in the proper values for the passed-in parameters. None of + * the parameters is used currently. + * + * @param desc pointer to \b struct \b machine_desc + * @param tags pointer to \b struct \b tag + * @param cmdline pointer to the command line + * @param mi pointer to \b struct \b meminfo + */ +static void __init fixup_mxc_board(struct machine_desc *desc, struct tag *tags, + char **cmdline, struct meminfo *mi) +{ + mxc_cpu_init(); + +#ifdef CONFIG_DISCONTIGMEM + do { + int nid; + mi->nr_banks = MXC_NUMNODES; + for (nid = 0; nid < mi->nr_banks; nid++) { + SET_NODE(mi, nid); + } + } while (0); +#endif +} + +/* IDE device data */ +#if defined(CONFIG_BLK_DEV_IDE_MXC) || defined(CONFIG_BLK_DEV_IDE_MXC_MODULE) + +/*! Platform Data for MXC IDE */ +static struct mxc_ide_platform_data mxc_ide_data = { + .power_drive = "GPO2", + .power_io = "GPO3", +}; + +static struct platform_device mxc_ide_device = { + .name = "mxc_ide", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_ide_data, + }, +}; + +static inline void mxc_init_ide(void) +{ + if (platform_device_register(&mxc_ide_device) < 0) + printk(KERN_ERR "Error: Registering the ide.\n"); +} +#else +static inline void mxc_init_ide(void) +{ +} +#endif + +#if defined(CONFIG_PATA_FSL) || defined(CONFIG_PATA_FSL_MODULE) +extern void gpio_ata_active(void); +extern void gpio_ata_inactive(void); + +static int ata_init(struct platform_device *pdev) +{ + /* Configure the pins */ + gpio_ata_active(); + + return 0; +} + +static void ata_exit(void) +{ + /* Free the pins */ + gpio_ata_inactive(); +} + +static struct fsl_ata_platform_data ata_data = { + .udma_mask = ATA_UDMA3, /* board can handle up to UDMA3 */ + .mwdma_mask = ATA_MWDMA2, + .pio_mask = ATA_PIO4, + .fifo_alarm = MXC_IDE_DMA_WATERMARK / 2, + .max_sg = MXC_IDE_DMA_BD_NR, + .init = ata_init, + .exit = ata_exit, + .core_reg = "GPO2", /*"LDO2", */ + .io_reg = "GPO3", /*"LDO3", */ +}; + +static struct resource pata_fsl_resources[] = { + [0] = { /* I/O */ + .start = ATA_BASE_ADDR + 0x00, + .end = ATA_BASE_ADDR + 0xD8, + .flags = IORESOURCE_MEM, + }, + [2] = { /* IRQ */ + .start = MXC_INT_ATA, + .end = MXC_INT_ATA, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device pata_fsl_device = { + .name = "pata_fsl", + .id = -1, + .num_resources = ARRAY_SIZE(pata_fsl_resources), + .resource = pata_fsl_resources, + .dev = { + .platform_data = &ata_data, + .coherent_dma_mask = ~0, + }, +}; + +static void __init mxc_init_pata(void) +{ + (void)platform_device_register(&pata_fsl_device); +} +#else /* CONFIG_PATA_FSL */ +static void __init mxc_init_pata(void) +{ +} +#endif /* CONFIG_PATA_FSL */ + +static void bt_reset(void) +{ + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_DCD_DCE1), 1); +} + +static struct mxc_bt_platform_data mxc_bt_data = { + .bt_vdd = "VMMC2", + .bt_vdd_parent = "GPO1", + .bt_vusb = NULL, + .bt_vusb_parent = "GPO3", + .bt_reset = bt_reset, +}; + +static struct platform_device mxc_bt_device = { + .name = "mxc_bt", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_bt_data, + }, +}; + +static void mxc_init_bluetooth(void) +{ + (void)platform_device_register(&mxc_bt_device); +} + +static void mxc_unifi_hardreset(int pin_level) +{ + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_DCD_DCE1), pin_level & 0x01); +} + +static struct mxc_unifi_platform_data unifi_data = { + .hardreset = mxc_unifi_hardreset, + + /* GPO3 -> enables SW2B 1.8V out - this becomes 1V8 on personality + * board, then 1V8_EXT, then BT_VUSB + */ + .reg_gpo1 = "GPO3", + + /* GPO4 -> WiFi_PWEN, but this signal is not used on current boards */ + .reg_gpo2 = "GPO4", + + .reg_1v5_ana_bb = "VRF1", /* VRF1 -> WL_1V5ANA and WL_1V5BB */ + .reg_vdd_vpa = "VMMC2", /* VMMC2 -> WL_VDD and WL_VPA */ + .reg_1v5_dd = "VRF2", /* VRF2 -> WL_1V5DD */ + + .host_id = 1, +}; + +struct mxc_unifi_platform_data *get_unifi_plat_data(void) +{ + return &unifi_data; +} + +EXPORT_SYMBOL(get_unifi_plat_data); + +#if defined(CONFIG_GPS_IOCTRL) || defined(CONFIG_GPS_IOCTRL_MODULE) +static struct mxc_gps_platform_data gps_data = { + .core_reg = "GPO3", + .analog_reg = "GPO1", +}; + +static struct platform_device mxc_gps_device = { + .name = "gps_ioctrl", + .id = -1, + .dev = { + .platform_data = &gps_data, + }, +}; + +static void __init mxc_init_gps(void) +{ + (void)platform_device_register(&mxc_gps_device); +} +#else +static void __init mxc_init_gps(void) +{ +} +#endif + +static void __init mx3_3stack_timer_init(void) +{ + mx31_clocks_init(26000000); +} + +static struct sys_timer mxc_timer = { + .init = mx3_3stack_timer_init, +}; + +/*! + * Board specific initialization. + */ +static void __init mxc_board_init(void) +{ + /* config CS5 for debug board */ + mxc_request_iomux(MX31_PIN_CS5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + mxc_cpu_common_init(); + mxc_register_gpios(); + early_console_setup(saved_command_line); + mxc_init_devices(); + + /*Pull down MX31_PIN_USB_BYP to reset USB3317 */ + mxc_request_iomux(MX31_PIN_USB_BYP, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_USB_BYP), "usb_byp"); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_USB_BYP), 0); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_USB_BYP), 0); + mxc_free_iomux(MX31_PIN_USB_BYP, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + + /* Reset BT/WiFi chip */ + mxc_request_iomux(MX31_PIN_DCD_DCE1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_DCD_DCE1), "dcd_dce1"); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_DCD_DCE1), 0); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_DCD_DCE1), 0); + + mxc_init_pmic_audio(); + mxc_expio_init(); + mxc_init_keypad(); + mxc_init_enet(); + mxc_init_nand_mtd(); + mxc_init_ch7024(); + mx3_3stack_init_mc13783(); + + i2c_register_board_info(0, mxc_i2c_board_info, + ARRAY_SIZE(mxc_i2c_board_info)); + spi_register_board_info(mxc_spi_board_info, + ARRAY_SIZE(mxc_spi_board_info)); + + mxc_init_fb(); + mxc_init_bl(); + mxc_init_mmc(); + mxc_init_ide(); + mxc_init_pata(); + mxc_init_bluetooth(); + mxc_init_gps(); +} + +#define PLL_PCTL_REG(pd, mfd, mfi, mfn) \ + ((((pd) - 1) << 26) + (((mfd) - 1) << 16) + ((mfi) << 10) + mfn) + +/* For 26MHz input clock */ +#define PLL_532MHZ PLL_PCTL_REG(1, 13, 10, 3) +#define PLL_399MHZ PLL_PCTL_REG(1, 52, 7, 35) +#define PLL_133MHZ PLL_PCTL_REG(2, 26, 5, 3) + +#define PDR0_REG(mcu, max, hsp, ipg, nfc) \ + (MXC_CCM_PDR0_MCU_DIV_##mcu | MXC_CCM_PDR0_MAX_DIV_##max | \ + MXC_CCM_PDR0_HSP_DIV_##hsp | MXC_CCM_PDR0_IPG_DIV_##ipg | \ + MXC_CCM_PDR0_NFC_DIV_##nfc) + +/* working point(wp): 0 - 133MHz; 1 - 266MHz; 2 - 399MHz; 3 - 532MHz */ +/* 26MHz input clock table */ +static struct cpu_wp cpu_wp_26[] = { + { + .pll_reg = PLL_532MHZ, + .pll_rate = 532000000, + .cpu_rate = 133000000, + .pdr0_reg = PDR0_REG(4, 4, 4, 2, 6),}, + { + .pll_reg = PLL_532MHZ, + .pll_rate = 532000000, + .cpu_rate = 266000000, + .pdr0_reg = PDR0_REG(2, 4, 4, 2, 6),}, + { + .pll_reg = PLL_399MHZ, + .pll_rate = 399000000, + .cpu_rate = 399000000, + .pdr0_reg = PDR0_REG(1, 3, 3, 2, 6),}, + { + .pll_reg = PLL_532MHZ, + .pll_rate = 532000000, + .cpu_rate = 532000000, + .pdr0_reg = PDR0_REG(1, 4, 4, 2, 6),}, +}; + +struct cpu_wp *get_cpu_wp(int *wp) +{ + *wp = 4; + return cpu_wp_26; +} + +/* + * The following uses standard kernel macros define in arch.h in order to + * initialize __mach_desc_MX3_3STACK data structure. + */ +/* *INDENT-OFF* */ +MACHINE_START(MX31_3DS, "Freescale MX31/MX32 3-Stack Board") + /* Maintainer: Freescale Semiconductor, Inc. */ + .phys_io = AIPS1_BASE_ADDR, + .io_pg_offst = ((AIPS1_BASE_ADDR_VIRT) >> 18) & 0xfffc, + .boot_params = PHYS_OFFSET + 0x100, + .fixup = fixup_mxc_board, + .map_io = mx31_map_io, + .init_irq = mxc_init_irq, + .init_machine = mxc_board_init, + .timer = &mxc_timer, +MACHINE_END diff --git a/arch/arm/mach-mx3/mx3_3stack_gpio.c b/arch/arm/mach-mx3/mx3_3stack_gpio.c new file mode 100644 index 000000000000..41e0b692bf37 --- /dev/null +++ b/arch/arm/mach-mx3/mx3_3stack_gpio.c @@ -0,0 +1,1311 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/irq.h> +#include <linux/pmic_adc.h> +#include <linux/io.h> +#include <mach/hardware.h> +#include <mach/gpio.h> +#include "board-mx3_3stack.h" +#include "iomux.h" + +/*! + * @file mach-mx3/mx3_3stack_gpio.c + * + * @brief This file contains all the GPIO setup functions for the board. + * + * @ingroup GPIO_MX31 + */ + +/*! + * Setup GPIO for a UART port to be active + * + * @param port a UART port + * @param no_irda indicates if the port is used for SIR + */ +void gpio_uart_active(int port, int no_irda) +{ + /* + * Configure the IOMUX control registers for the UART signals + */ + switch (port) { + /* UART 1 IOMUX Configs */ + case 0: + mxc_request_iomux(MX31_PIN_RXD1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_TXD1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_RTS1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CTS1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + break; + /* UART 2 IOMUX Configs */ + case 1: + mxc_request_iomux(MX31_PIN_TXD2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_RXD2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + + mxc_request_iomux(MX31_PIN_RTS2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CTS2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + break; + /* UART 3 IOMUX Configs */ + case 2: + mxc_request_iomux(MX31_PIN_CSPI3_MOSI, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_CSPI3_MISO, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_CSPI3_SCLK, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_CSPI3_SPI_RDY, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + break; + default: + break; + } + + /* + * TODO: Configure the Pad registers for the UART pins + */ +} + +/*! + * Setup GPIO for a UART port to be inactive + * + * @param port a UART port + * @param no_irda indicates if the port is used for SIR + */ +void gpio_uart_inactive(int port, int no_irda) +{ + switch (port) { + case 0: + gpio_request(IOMUX_TO_GPIO(MX31_PIN_RXD1), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_TXD1), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_RTS1), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_CTS1), NULL); + + mxc_free_iomux(MX31_PIN_RXD1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_TXD1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_RTS1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_CTS1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + break; + case 1: + gpio_request(IOMUX_TO_GPIO(MX31_PIN_TXD2), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_RXD2), NULL); + + mxc_free_iomux(MX31_PIN_TXD2, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_RXD2, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_RTS2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CTS2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + break; + default: + break; + } +} + +/*! + * Configure the IOMUX GPR register to receive shared SDMA UART events + * + * @param port a UART port + */ +void config_uartdma_event(int port) +{ + switch (port) { + case 1: + /* Configure to receive UART 2 SDMA events */ + mxc_iomux_set_gpr(MUX_PGP_FIRI, false); + break; + case 2: + /* Configure to receive UART 3 SDMA events */ + mxc_iomux_set_gpr(MUX_CSPI1_UART3, true); + break; + case 4: + /* Configure to receive UART 5 SDMA events */ + mxc_iomux_set_gpr(MUX_CSPI3_UART5_SEL, true); + break; + default: + break; + } +} + +EXPORT_SYMBOL(gpio_uart_active); +EXPORT_SYMBOL(gpio_uart_inactive); +EXPORT_SYMBOL(config_uartdma_event); + +/*! + * Setup GPIO for Keypad to be active + * + */ +void gpio_keypad_active(void) +{ + /* + * Configure the IOMUX control register for keypad signals. + */ + mxc_request_iomux(MX31_PIN_KEY_COL0, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_COL1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_COL2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_COL3, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_ROW0, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_ROW1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_KEY_ROW2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); +} + +EXPORT_SYMBOL(gpio_keypad_active); + +/*! + * Setup GPIO for Keypad to be inactive + * + */ +void gpio_keypad_inactive(void) +{ + gpio_request(IOMUX_TO_GPIO(MX31_PIN_KEY_COL0), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_KEY_COL1), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_KEY_COL2), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_KEY_COL3), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_KEY_ROW0), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_KEY_ROW1), NULL); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_KEY_ROW2), NULL); + + mxc_free_iomux(MX31_PIN_KEY_COL0, OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_KEY_COL1, OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_KEY_COL2, OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_KEY_COL3, OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_KEY_ROW0, OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_KEY_ROW1, OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_KEY_ROW2, OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); +} + +EXPORT_SYMBOL(gpio_keypad_inactive); + +void gpio_power_key_active(void) +{ + mxc_request_iomux(MX31_PIN_GPIO1_2, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_GPIO1_2), NULL); + gpio_direction_input(IOMUX_TO_GPIO(MX31_PIN_GPIO1_2)); + mxc_iomux_set_pad(MX31_PIN_GPIO1_2, PAD_CTL_PKE_NONE); +} + +EXPORT_SYMBOL(gpio_power_key_active); + +/*! + * Setup GPIO for a CSPI device to be active + * + * @param cspi_mod an CSPI device + */ +void gpio_spi_active(int cspi_mod) +{ + switch (cspi_mod) { + case 0: + /* SPI1 */ + /* setup GPR for CSPI BB */ + iomux_config_gpr(MUX_PGP_CSPI_BB, true); + /* CSPI1 clock and RDY use full UART ALT1 mode */ + mxc_request_iomux(MX31_PIN_DSR_DCE1, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_RI_DCE1, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + break; + case 1: + /* SPI2 */ + mxc_request_iomux(MX31_PIN_CSPI2_MISO, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_MOSI, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SCLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SPI_RDY, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SS0, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SS2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + break; + case 2: + /* SPI3 */ + /* + mxc_request_iomux(MX31_PIN_CSPI2_MISO, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_MOSI, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SCLK, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SPI_RDY, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SS0, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SS1, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSPI2_SS2, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + */ + break; + default: + break; + } +} + +/*! + * Setup 1-Wire to be active + */ +void gpio_owire_active(void) +{ + /* + * Configure the IOMUX control register for 1-wire signals. + */ + iomux_config_mux(MX31_PIN_BATT_LINE, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + iomux_config_pad(MX31_PIN_BATT_LINE, PAD_CTL_LOOPBACK); +} + +/*! + * Setup 1-Wire to be active + */ +void gpio_owire_inactive(void) +{ + /* + * Configure the IOMUX control register for 1-wire signals. + */ + iomux_config_mux(MX31_PIN_BATT_LINE, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); +} + +EXPORT_SYMBOL(gpio_owire_active); +EXPORT_SYMBOL(gpio_owire_inactive); + +/*! + * Setup GPIO for a CSPI device to be inactive + * + * @param cspi_mod a CSPI device + */ +void gpio_spi_inactive(int cspi_mod) +{ + switch (cspi_mod) { + case 0: + /* SPI1 */ + /* setup GPR for CSPI BB */ + iomux_config_gpr(MUX_PGP_CSPI_BB, false); + /* CSPI1 clock and RDY use full UART ALT1 mode */ + mxc_free_iomux(MX31_PIN_DSR_DCE1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_RI_DCE1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + break; + case 1: + /* SPI2 */ + mxc_free_iomux(MX31_PIN_CSPI2_MISO, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSPI2_MOSI, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSPI2_SCLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSPI2_SPI_RDY, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSPI2_SS0, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSPI2_SS2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + break; + case 2: + /* SPI3 */ + break; + default: + break; + } +} + +/*! + * Setup GPIO for an I2C device to be active + * + * @param i2c_num an I2C device + */ +void gpio_i2c_active(int i2c_num) +{ + switch (i2c_num) { + case 0: + mxc_request_iomux(MX31_PIN_I2C_CLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_I2C_DAT, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + break; + case 1: + mxc_request_iomux(MX31_PIN_CSPI2_MOSI, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_CSPI2_MISO, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + break; + case 2: + mxc_request_iomux(MX31_PIN_CSPI2_SS2, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_CSPI2_SCLK, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + break; + default: + break; + } + +} + +/*! + * Setup GPIO for an I2C device to be inactive + * + * @param i2c_num an I2C device + */ +void gpio_i2c_inactive(int i2c_num) +{ + switch (i2c_num) { + case 0: + mxc_free_iomux(MX31_PIN_I2C_CLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_I2C_DAT, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + break; + case 1: + mxc_free_iomux(MX31_PIN_CSPI2_MOSI, OUTPUTCONFIG_FUNC, + INPUTCONFIG_ALT1); + mxc_free_iomux(MX31_PIN_CSPI2_MISO, OUTPUTCONFIG_FUNC, + INPUTCONFIG_ALT1); + break; + case 2: + mxc_request_iomux(MX31_PIN_CSPI2_SS2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_CSPI2_SCLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_ALT1); + break; + default: + break; + } +} + +/*! + * This function configures the IOMux block for PMIC standard operations. + * + */ +void gpio_pmic_active(void) +{ + mxc_request_iomux(MX31_PIN_GPIO1_3, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_GPIO1_3), NULL); + gpio_direction_input(IOMUX_TO_GPIO(MX31_PIN_GPIO1_3)); +} + +EXPORT_SYMBOL(gpio_pmic_active); + +/*! + * Setup GPIO for SDHC to be active + * + * @param module SDHC module number + */ +void gpio_sdhc_active(int module) +{ + switch (module) { + case 0: + mxc_request_iomux(MX31_PIN_SD1_CLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SD1_CMD, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SD1_DATA0, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SD1_DATA1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SD1_DATA2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_SD1_DATA3, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + + mxc_iomux_set_pad(MX31_PIN_SD1_CLK, + (PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST + | PAD_CTL_100K_PU)); + mxc_iomux_set_pad(MX31_PIN_SD1_CMD, + (PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST + | PAD_CTL_100K_PU)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA0, + (PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST + | PAD_CTL_100K_PU)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA1, + (PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST + | PAD_CTL_100K_PU)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA2, + (PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST + | PAD_CTL_100K_PU)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA3, + (PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST + | PAD_CTL_100K_PU)); + + /* + * Active the Buffer Enable Pin only if there is + * a card in slot. + * To fix the card voltage issue caused by + * bi-directional chip TXB0108 on 3Stack + */ + if (gpio_get_value(IOMUX_TO_GPIO(MX31_PIN_GPIO3_1))) + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_GPIO3_0), 0); + else + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_GPIO3_0), 1); + break; + case 1: + mxc_request_iomux(MX31_PIN_PC_CD2_B, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_PC_CD1_B, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_PC_WAIT_B, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_PC_READY, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_PC_VS1, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + mxc_request_iomux(MX31_PIN_PC_PWRON, OUTPUTCONFIG_ALT1, + INPUTCONFIG_ALT1); + break; + default: + break; + } +} + +EXPORT_SYMBOL(gpio_sdhc_active); + +/*! + * Setup GPIO for SDHC1 to be inactive + * + * @param module SDHC module number + */ +void gpio_sdhc_inactive(int module) +{ + switch (module) { + case 0: + mxc_free_iomux(MX31_PIN_SD1_CLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_SD1_CMD, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_SD1_DATA0, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_SD1_DATA1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_SD1_DATA2, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_SD1_DATA3, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + + mxc_iomux_set_pad(MX31_PIN_SD1_CLK, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX31_PIN_SD1_CMD, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA0, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA1, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA2, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX31_PIN_SD1_DATA3, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + + /* Buffer Enable Pin of SD, Active HI */ + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_GPIO3_0), 0); + break; + case 1: + /* TODO:what are the pins for SDHC2? */ + mxc_free_iomux(MX31_PIN_PC_CD2_B, OUTPUTCONFIG_FUNC, + INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_PC_CD1_B, OUTPUTCONFIG_FUNC, + INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_PC_WAIT_B, OUTPUTCONFIG_FUNC, + INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_PC_READY, OUTPUTCONFIG_FUNC, + INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_PC_VS1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_PC_PWRON, OUTPUTCONFIG_FUNC, + INPUTCONFIG_NONE); + break; + default: + break; + } +} + +EXPORT_SYMBOL(gpio_sdhc_inactive); + +/* + * Probe for the card. If present the GPIO data would be set. + */ +unsigned int sdhc_get_card_det_status(struct device *dev) +{ + int ret; + + if (to_platform_device(dev)->id == 0) { + ret = gpio_get_value(IOMUX_TO_GPIO(MX31_PIN_GPIO3_1)); + /* + * Active the Buffer Enable Pin only if there is + * a card in slot. + * To fix the card voltage issue caused by + * bi-directional chip TXB0108 on 3Stack + */ + if (ret) + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_GPIO3_0), 0); + else + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_GPIO3_0), 1); + return ret; + } else + return gpio_get_value(IOMUX_TO_GPIO(MX31_PIN_GPIO1_2)); +} + +EXPORT_SYMBOL(sdhc_get_card_det_status); + +/* + * Return the card detect pin. + */ +int sdhc_init_card_det(int id) +{ + if (id == 0) { + /* Buffer Enable Pin, Active HI */ + mxc_request_iomux(MX31_PIN_GPIO3_0, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_GPIO3_0), "gpio3_0"); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_GPIO3_0), 0); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_GPIO3_0), 0); + + /* CD Pin */ + mxc_request_iomux(MX31_PIN_GPIO3_1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_iomux_set_pad(MX31_PIN_GPIO3_1, PAD_CTL_PKE_NONE); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_GPIO3_1), "gpio3_1"); + gpio_direction_input(IOMUX_TO_GPIO(MX31_PIN_GPIO3_1)); + return IOMUX_TO_IRQ(MX31_PIN_GPIO3_1); + } else { + iomux_config_mux(MX31_PIN_GPIO1_2, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + return IOMUX_TO_IRQ(MX31_PIN_GPIO1_2); + + } +} + +EXPORT_SYMBOL(sdhc_init_card_det); + +/*! + * Get SD1_WP ADIN7 of ATLAS pin value to detect write protection + */ +int sdhc_write_protect(struct device *dev) +{ + unsigned short rc = 0; + + pmic_adc_convert(GEN_PURPOSE_AD7, &rc); + if (rc > 0) + return 1; + else + return 0; +} + +EXPORT_SYMBOL(sdhc_write_protect); + +/*! + * Setup GPIO for LCD to be active + * + */ +void gpio_lcd_active(void) +{ + + mxc_request_iomux(MX31_PIN_LD0, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD1, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD2, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD3, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD7, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD8, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD9, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD10, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD11, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD12, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD13, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD14, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD15, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_LD16, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // LD16 + mxc_request_iomux(MX31_PIN_LD17, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // LD17 + mxc_request_iomux(MX31_PIN_VSYNC3, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // VSYNC + mxc_request_iomux(MX31_PIN_HSYNC, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // HSYNC + mxc_request_iomux(MX31_PIN_FPSHIFT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // CLK + mxc_request_iomux(MX31_PIN_CONTRAST, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // CONTR + +#ifdef CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL + mxc_request_iomux(MX31_PIN_DRDY0, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); /* DRDY */ + mxc_request_iomux(MX31_PIN_D3_REV, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); /* REV */ + mxc_request_iomux(MX31_PIN_D3_SPL, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); /* SPL */ + mxc_request_iomux(MX31_PIN_D3_CLS, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); /* CLS */ +#else + /* ensure that LCDIO(1.8V) has been turn on */ + /* active reset line GPIO */ + mxc_request_iomux(MX31_PIN_LCS1, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_LCS1), "lcs1"); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_LCS1), 0); + /* do reset */ + mdelay(10); /* tRES >= 100us */ + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_LCS1), 1); + + /* enable data */ + mxc_request_iomux(MX31_PIN_SER_RS, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_SER_RS), "ser_rs"); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_SER_RS), 0); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_SER_RS), 1); +#endif +} + +/*! + * Setup GPIO for LCD to be inactive + * + */ +void gpio_lcd_inactive(void) +{ + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_SER_RS), 0); +} + +/*! + * Switch to the specified sensor - MX31 ADS has two + * + */ +void gpio_sensor_select(int sensor) +{ +} + +/*! + * Setup GPIO for sensor to be active + * + */ +void gpio_sensor_active(void) +{ + gpio_sensor_select(0); + + /* + * Configure the iomuxen for the CSI. + */ + + mxc_request_iomux(MX31_PIN_CSI_D5, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_CSI_D5), "csi_d5"); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_CSI_D5), 0); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_CSI_D5), 0); + + mxc_request_iomux(MX31_PIN_CSI_D6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D7, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D8, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D9, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D10, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D11, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D12, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D13, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D14, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_D15, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_HSYNC, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_MCLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_PIXCLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_CSI_VSYNC, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + + if (mxc_request_iomux(MX31_PIN_A23, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE) + == 0) { + printk(KERN_ERR "%s:REGEN set request gpio ok\n", __func__); + } else { + printk(KERN_ERR "%s:REGEN set error, request gpio error\n", + __func__); + return; + } + gpio_request(IOMUX_TO_GPIO(MX31_PIN_SD_D_IO), "sd_d_io"); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_SD_D_IO), 0); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_SD_D_IO), 1); +} + +EXPORT_SYMBOL(gpio_sensor_active); + +void gpio_sensor_reset(bool flag) +{ +} + +EXPORT_SYMBOL(gpio_sensor_reset); + +/*! + * Setup GPIO for sensor to be inactive + * + */ +void gpio_sensor_inactive(void) +{ + mxc_free_iomux(MX31_PIN_CSI_D5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D7, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D8, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D9, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D10, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D11, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D12, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D13, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D14, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_D15, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_HSYNC, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_MCLK, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_PIXCLK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_free_iomux(MX31_PIN_CSI_VSYNC, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); +} + +EXPORT_SYMBOL(gpio_sensor_inactive); + +/*! + * Setup GPIO for ATA interface + * + */ +void gpio_ata_active(void) +{ + /* + * Configure the GPR for ATA group B signals + */ + mxc_iomux_set_gpr(MUX_PGP_ATA_8 | MUX_PGP_ATA_5 | MUX_PGP_ATA_4 | + MUX_PGP_ATA_3 | MUX_PGP_ATA_2, false); + + mxc_iomux_set_gpr(MUX_PGP_ATA_9 | MUX_PGP_ATA_7 | MUX_PGP_ATA_6 | + MUX_PGP_ATA_1, true); + + /* + * Configure the IOMUX for ATA group B signals + */ + + mxc_request_iomux(MX31_PIN_CSPI1_MOSI, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D0 + mxc_request_iomux(MX31_PIN_CSPI1_MISO, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D1 + mxc_request_iomux(MX31_PIN_CSPI1_SS0, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D2 + mxc_request_iomux(MX31_PIN_CSPI1_SS1, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D3 + mxc_request_iomux(MX31_PIN_CSPI1_SS2, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D4 + mxc_request_iomux(MX31_PIN_CSPI1_SCLK, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D5 + mxc_request_iomux(MX31_PIN_CSPI1_SPI_RDY, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D6 + mxc_request_iomux(MX31_PIN_STXD3, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D7 + mxc_request_iomux(MX31_PIN_SRXD3, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D8 + mxc_request_iomux(MX31_PIN_SCK3, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D9 + mxc_request_iomux(MX31_PIN_SFS3, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D10 + mxc_request_iomux(MX31_PIN_STXD6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D11 + mxc_request_iomux(MX31_PIN_SRXD6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D12 + mxc_request_iomux(MX31_PIN_SCK6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D13 + mxc_request_iomux(MX31_PIN_CAPTURE, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D14 + mxc_request_iomux(MX31_PIN_COMPARE, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_D15 + + /* Config the multiplex pin of ATA interface DIR, DA0-2, INTRQ, DMARQ */ + mxc_request_iomux(MX31_PIN_KEY_COL4, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_DMARQ_B + mxc_request_iomux(MX31_PIN_KEY_ROW6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_INTRQ_B + mxc_request_iomux(MX31_PIN_KEY_COL5, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_DA0 + mxc_request_iomux(MX31_PIN_KEY_COL6, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_DA1 + mxc_request_iomux(MX31_PIN_KEY_COL7, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_DA2 + mxc_request_iomux(MX31_PIN_KEY_ROW7, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); // ATA_BUFFER_DIR + + /* HDD_ENABLE_B(H:Disable,L:Enable) */ + mxc_request_iomux(MX31_PIN_CSI_D4, OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); // HDD_ENABLE_B + gpio_request(IOMUX_TO_GPIO(MX31_PIN_CSI_D4), "csi_d4"); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_CSI_D4), 0); + mdelay(10); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_CSI_D4), 0); + mdelay(10); + + /* These ATA pins are common to Group A and Group B */ + + mxc_request_iomux(MX31_PIN_ATA_CS0, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_ATA_CS1, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_ATA_DIOR, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_ATA_DIOW, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_ATA_DMACK, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_ATA_RESET_B, OUTPUTCONFIG_FUNC, + INPUTCONFIG_FUNC); + mxc_request_iomux(MX31_PIN_PWMO, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + /* Need fast slew rate for UDMA mode */ + +#define ATA_DAT_PAD_CFG (PAD_CTL_SRE_FAST | PAD_CTL_PKE_NONE | PAD_CTL_100K_PU) + mxc_iomux_set_pad(MX31_PIN_CSPI1_MISO, ATA_DAT_PAD_CFG); // data 0 + mxc_iomux_set_pad(MX31_PIN_CSPI1_MOSI, ATA_DAT_PAD_CFG); // data 1 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS0, ATA_DAT_PAD_CFG); // data 2 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS1, ATA_DAT_PAD_CFG); // data 3 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS2, ATA_DAT_PAD_CFG); // data 4 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SCLK, ATA_DAT_PAD_CFG); // data 5 + mxc_iomux_set_pad(MX31_PIN_CSPI1_SPI_RDY, ATA_DAT_PAD_CFG); // data 6 + mxc_iomux_set_pad(MX31_PIN_STXD3, ATA_DAT_PAD_CFG); // data 7 + mxc_iomux_set_pad(MX31_PIN_SRXD3, ATA_DAT_PAD_CFG); // data 8 + mxc_iomux_set_pad(MX31_PIN_SCK3, ATA_DAT_PAD_CFG); // data 9 + mxc_iomux_set_pad(MX31_PIN_SFS3, ATA_DAT_PAD_CFG); // data 10 + mxc_iomux_set_pad(MX31_PIN_STXD6, ATA_DAT_PAD_CFG); // data 11 + mxc_iomux_set_pad(MX31_PIN_SRXD6, ATA_DAT_PAD_CFG); // data 12 + mxc_iomux_set_pad(MX31_PIN_SCK6, ATA_DAT_PAD_CFG); // data 13 + mxc_iomux_set_pad(MX31_PIN_CAPTURE, ATA_DAT_PAD_CFG); // data 14 + mxc_iomux_set_pad(MX31_PIN_COMPARE, ATA_DAT_PAD_CFG); // data 15 +#undef ATA_DAT_PAD_CFG + +#define ATA_CTL_PAD_CFG (PAD_CTL_SRE_SLOW | PAD_CTL_PKE_NONE) + mxc_iomux_set_pad(MX31_PIN_KEY_COL4, ATA_CTL_PAD_CFG); // ATA_DMARQ); + mxc_iomux_set_pad(MX31_PIN_KEY_ROW6, ATA_CTL_PAD_CFG); // ATA_INTRQ); + mxc_iomux_set_pad(MX31_PIN_KEY_COL5, ATA_CTL_PAD_CFG); // + mxc_iomux_set_pad(MX31_PIN_KEY_COL6, ATA_CTL_PAD_CFG); // + mxc_iomux_set_pad(MX31_PIN_KEY_COL7, ATA_CTL_PAD_CFG); // + mxc_iomux_set_pad(MX31_PIN_KEY_ROW7, ATA_CTL_PAD_CFG); // + + mxc_iomux_set_pad(MX31_PIN_ATA_CS0, ATA_CTL_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_ATA_CS1, ATA_CTL_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_ATA_DIOR, ATA_CTL_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_ATA_DIOW, ATA_CTL_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_ATA_DMACK, ATA_CTL_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_ATA_RESET_B, ATA_CTL_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_PWMO, ATA_CTL_PAD_CFG); +#undef ATA_CTL_PAD_CFG +} + +EXPORT_SYMBOL(gpio_ata_active); + +/*! + * Restore ATA interface pins to reset values + * + */ +void gpio_ata_inactive(void) +{ + /* + * Turn off ATA group B signals + */ + mxc_request_iomux(MX31_PIN_CSPI1_MOSI, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D0 + mxc_request_iomux(MX31_PIN_CSPI1_MISO, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D1 + mxc_request_iomux(MX31_PIN_CSPI1_SS0, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D2 + mxc_request_iomux(MX31_PIN_CSPI1_SS1, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D3 + mxc_request_iomux(MX31_PIN_CSPI1_SS2, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D4 + mxc_request_iomux(MX31_PIN_CSPI1_SCLK, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D5 + mxc_request_iomux(MX31_PIN_CSPI1_SPI_RDY, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D6 + mxc_request_iomux(MX31_PIN_STXD3, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D7 + mxc_request_iomux(MX31_PIN_SRXD3, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D8 + mxc_request_iomux(MX31_PIN_SCK3, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D9 + mxc_request_iomux(MX31_PIN_SFS3, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D10 + mxc_request_iomux(MX31_PIN_STXD6, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D11 + mxc_request_iomux(MX31_PIN_SRXD6, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D12 + mxc_request_iomux(MX31_PIN_SCK6, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D13 + mxc_request_iomux(MX31_PIN_CAPTURE, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D14 + mxc_request_iomux(MX31_PIN_COMPARE, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_D15 + + /* Config the multiplex pin of ATA interface DIR, DA0-2, INTRQ, DMARQ */ + mxc_request_iomux(MX31_PIN_KEY_COL4, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_DMARQ_B + mxc_request_iomux(MX31_PIN_KEY_ROW6, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_INTRQ_B + mxc_request_iomux(MX31_PIN_KEY_COL5, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_DA0 + mxc_request_iomux(MX31_PIN_KEY_COL6, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_DA1 + mxc_request_iomux(MX31_PIN_KEY_COL7, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_DA2 + mxc_request_iomux(MX31_PIN_KEY_ROW7, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); // ATA_BUFFER_DIR + + /* HDD_BUFF_EN (H:A->B, L:B->A) and HDD_ENABLE_B(H:Disable,L:Enable) */ + mxc_free_iomux(MX31_PIN_CSI_D4, OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); + + /* These ATA pins are common to Group A and Group B */ + + mxc_request_iomux(MX31_PIN_ATA_CS0, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_ATA_CS1, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_ATA_DIOR, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_ATA_DIOW, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_ATA_DMACK, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_ATA_RESET_B, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + mxc_request_iomux(MX31_PIN_PWMO, OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + + /* Needed fast slew rate for UDMA mode */ + +#define ATA_DAT_PAD_CFG (PAD_CTL_SRE_SLOW | PAD_CTL_DRV_NORMAL | PAD_CTL_PKE_NONE) + mxc_iomux_set_pad(MX31_PIN_KEY_COL4, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_KEY_ROW6, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_KEY_COL5, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_KEY_COL6, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_KEY_COL7, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_KEY_ROW7, ATA_DAT_PAD_CFG); + + mxc_iomux_set_pad(MX31_PIN_ATA_CS0, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_ATA_CS1, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_ATA_DIOR, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_ATA_DIOW, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_ATA_DMACK, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_ATA_RESET_B, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_PWMO, ATA_DAT_PAD_CFG); + + mxc_iomux_set_pad(MX31_PIN_CSPI1_MISO, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_CSPI1_MOSI, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS0, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS1, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_CSPI1_SS2, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_CSPI1_SCLK, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_CSPI1_SPI_RDY, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_STXD3, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_SRXD3, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_SCK3, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_SFS3, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_STXD6, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_SRXD6, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_SCK6, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_CAPTURE, ATA_DAT_PAD_CFG); + mxc_iomux_set_pad(MX31_PIN_COMPARE, ATA_DAT_PAD_CFG); +#undef ATA_DAT_PAD_CFG +} + +EXPORT_SYMBOL(gpio_ata_inactive); + +/* *INDENT-OFF* */ +/* + * USB Host 1 + * pins conflict with SPI1, ATA, UART3 + */ +int gpio_usbh1_active(void) +{ + return 0; +} + +EXPORT_SYMBOL(gpio_usbh1_active); + +void gpio_usbh1_inactive(void) +{ + /* Do nothing as pins don't have/support GPIO mode */ + +} + +EXPORT_SYMBOL(gpio_usbh1_inactive); + +/* + * USB Host 2 + * pins conflict with UART5, PCMCIA + */ +int gpio_usbh2_active(void) +{ + if (mxc_request_iomux(MX31_PIN_USBH2_CLK, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBH2_DIR, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBH2_NXT, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBH2_STP, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBH2_DATA0, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBH2_DATA1, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_PC_VS2, /* USBH2_DATA2 */ + OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1) || + mxc_request_iomux(MX31_PIN_PC_BVD1, /* USBH2_DATA3 */ + OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1) || + mxc_request_iomux(MX31_PIN_PC_BVD2, /* USBH2_DATA4 */ + OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1) || + mxc_request_iomux(MX31_PIN_PC_RST, /* USBH2_DATA5 */ + OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1) || + mxc_request_iomux(MX31_PIN_IOIS16, /* USBH2_DATA6 */ + OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1) || + mxc_request_iomux(MX31_PIN_PC_RW_B, /* USBH2_DATA7 */ + OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1)) { + return -EINVAL; + } + mxc_iomux_set_pad(MX31_PIN_USBH2_CLK, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST | + PAD_CTL_PKE_NONE)); + mxc_iomux_set_pad(MX31_PIN_USBH2_DIR, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBH2_NXT, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBH2_STP, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBH2_DATA0, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBH2_DATA1, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_PC_VS2, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_PC_BVD1, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_PC_BVD2, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_PC_RST, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_IOIS16, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_PC_RW_B, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + + mxc_request_iomux(MX31_PIN_USB_BYP, OUTPUTCONFIG_GPIO, + INPUTCONFIG_NONE); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_USB_BYP), "usb_byp"); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_USB_BYP), 0); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_USB_BYP), 0); + mdelay(1); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_USB_BYP), 1); + return 0; +} + +EXPORT_SYMBOL(gpio_usbh2_active); + +void gpio_usbh2_inactive(void) +{ + mxc_iomux_set_pad(MX31_PIN_USBH2_CLK, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBH2_DIR, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBH2_NXT, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBH2_STP, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBH2_DATA0, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBH2_DATA1, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_PC_VS2, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_PC_BVD1, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_PC_BVD2, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_PC_RST, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_IOIS16, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_PC_RW_B, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_FAST)); + + mxc_free_iomux(MX31_PIN_USBH2_CLK, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_USBH2_DIR, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_USBH2_NXT, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_USBH2_STP, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_USBH2_DATA0, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_USBH2_DATA1, + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + + mxc_free_iomux(MX31_PIN_PC_VS2, /* USBH2_DATA2 */ + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_PC_BVD1, /* USBH2_DATA3 */ + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_PC_BVD2, /* USBH2_DATA4 */ + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_PC_RST, /* USBH2_DATA5 */ + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_IOIS16, /* USBH2_DATA6 */ + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + mxc_free_iomux(MX31_PIN_PC_RW_B, /* USBH2_DATA7 */ + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); + + mxc_free_iomux(MX31_PIN_USB_BYP, /* USBH2 PHY reset */ + OUTPUTCONFIG_GPIO, INPUTCONFIG_NONE); +} + +EXPORT_SYMBOL(gpio_usbh2_inactive); + +/* + * USB OTG HS port + */ +int gpio_usbotg_hs_active(void) +{ + if (mxc_request_iomux(MX31_PIN_USBOTG_DATA0, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA1, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA2, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA3, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA4, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA5, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA6, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA7, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_CLK, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DIR, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_NXT, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_STP, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC)) { + return -EINVAL; + } + + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA0, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA1, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA2, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA3, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA4, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA5, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA6, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DATA7, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_CLK, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_DIR, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_NXT, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + mxc_iomux_set_pad(MX31_PIN_USBOTG_STP, + (PAD_CTL_DRV_MAX | PAD_CTL_SRE_FAST)); + + /* reset transceiver */ + mxc_request_iomux(MX31_PIN_USB_PWR, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_USB_PWR), "usb_pwr"); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_USB_PWR), 0); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_USB_PWR), 0); + mdelay(1); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_USB_PWR), 1); + + return 0; +} + +EXPORT_SYMBOL(gpio_usbotg_hs_active); + +void gpio_usbotg_hs_inactive(void) +{ + mxc_free_iomux(MX31_PIN_USB_PWR, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); +} + +EXPORT_SYMBOL(gpio_usbotg_hs_inactive); + +/*! + * USB OTG FS port + */ +int gpio_usbotg_fs_active(void) +{ + if (mxc_request_iomux(MX31_PIN_USBOTG_DATA0, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA1, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA2, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA3, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA4, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA5, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA6, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DATA7, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_CLK, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_DIR, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_NXT, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USBOTG_STP, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC) || + mxc_request_iomux(MX31_PIN_USB_PWR, + OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC)) + return -EINVAL; + return 0; +} + +EXPORT_SYMBOL(gpio_usbotg_fs_active); + +void gpio_usbotg_fs_inactive(void) +{ + /* Do nothing as pins doesn't have/support GPIO mode */ + +} + +EXPORT_SYMBOL(gpio_usbotg_fs_inactive); + +/*! + * GPS GPIO + */ +void gpio_gps_active(void) +{ + /* POWER_EN */ + mxc_request_iomux(MX31_PIN_SCLK0, + OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_SCLK0), "sclk0"); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_SCLK0), 0); + /* Reset Pin */ + mxc_request_iomux(MX31_PIN_DCD_DTE1, + OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); + gpio_request(IOMUX_TO_GPIO(MX31_PIN_DCD_DTE1), "dcd_dte1"); + gpio_direction_output(IOMUX_TO_GPIO(MX31_PIN_DCD_DTE1), 0); + + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_SCLK0), 0); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_DCD_DTE1), 0); + + msleep(5); + gpio_set_value(IOMUX_TO_GPIO(MX31_PIN_DCD_DTE1), 1); + msleep(5); +} + +EXPORT_SYMBOL(gpio_gps_active); + +int gpio_gps_access(int para) +{ + iomux_pin_name_t pin; + pin = (para & 0x1) ? MX31_PIN_SCLK0 : MX31_PIN_DCD_DTE1; + + if (para & 0x4) /* Read GPIO */ + return gpio_get_value(IOMUX_TO_GPIO(pin)); + else if (para & 0x2) /* Write GPIO */ + gpio_set_value(IOMUX_TO_GPIO(pin), 1); + else + gpio_set_value(IOMUX_TO_GPIO(pin), 0); + return 0; +} + +EXPORT_SYMBOL(gpio_gps_access); + +void gpio_gps_inactive(void) +{ + mxc_free_iomux(MX31_PIN_DCD_DTE1, + OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); + mxc_free_iomux(MX31_PIN_SCLK0, + OUTPUTCONFIG_GPIO, INPUTCONFIG_GPIO); +} + +EXPORT_SYMBOL(gpio_gps_inactive); diff --git a/arch/arm/mach-mx3/mx3_3stack_pmic_mc13783.c b/arch/arm/mach-mx3/mx3_3stack_pmic_mc13783.c new file mode 100644 index 000000000000..69c5d3ed8a6f --- /dev/null +++ b/arch/arm/mach-mx3/mx3_3stack_pmic_mc13783.c @@ -0,0 +1,272 @@ +/* + * mx3-3stack-pmic-mc13783.c -- i.MX3 3STACK Driver for Atlas MC13783 PMIC + */ + /* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + + /* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/err.h> +#include <linux/pmic_external.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/mc13783/core.h> +#include <mach/irqs.h> +#include "iomux.h" + +/* + * Convenience conversion. + * Here atm, maybe there is somewhere better for this. + */ +#define mV_to_uV(mV) (mV * 1000) + +struct mc13783; + +static struct regulator_init_data violo_init = { + .constraints = { + .min_uV = mV_to_uV(1200), /* mc13783 allows min of 1200. */ + .max_uV = mV_to_uV(1800), /* mc13783 allows max of 1800. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vdig_init = { + .constraints = { + .min_uV = mV_to_uV(1200), /* mc13783 allows min of 1200. */ + .max_uV = mV_to_uV(1800), /* mc13783 allows max of 1800. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vgen_init = { + .constraints = { + .min_uV = mV_to_uV(1100), /* mc13783 allows min of 1100. */ + .max_uV = mV_to_uV(2775), /* mc13783 allows max of 2775. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vrfdig_init = { + .constraints = { + .min_uV = mV_to_uV(1200), /* mc13783 allows min of 1200. */ + .max_uV = mV_to_uV(1875), /* mc13783 allows max of 1875. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vrfref_init = { + .constraints = { + .min_uV = mV_to_uV(2475), /* mc13783 allows min of 2475. */ + .max_uV = mV_to_uV(2775), /* mc13783 allows max of 2775. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vrfcp_init = { + .constraints = { + .min_uV = mV_to_uV(2700), /* mc13783 allows min of 2700. */ + .max_uV = mV_to_uV(2775), /* mc13783 allows max of 2775. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vsim_init = { + .constraints = { + .min_uV = mV_to_uV(1800), /* mc13783 allows min of 1800. */ + .max_uV = mV_to_uV(2900), /* mc13783 allows max of 2900. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vesim_init = { + .constraints = { + .min_uV = mV_to_uV(1800), /* mc13783 allows min of 1800. */ + .max_uV = mV_to_uV(2900), /* mc13783 allows max of 2900. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vcam_init = { + .constraints = { + .min_uV = mV_to_uV(1500), /* mc13783 allows min of 1500. */ + .max_uV = mV_to_uV(3000), /* mc13783 allows max of 3000. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vvib_init = { + .constraints = { + .min_uV = mV_to_uV(1300), /* mc13783 allows min of 1300. */ + .max_uV = mV_to_uV(3000), /* mc13783 allows max of 3000. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vrf_init = { + .constraints = { + .min_uV = mV_to_uV(1500), /* mc13783 allows min of 1500. */ + .max_uV = mV_to_uV(2775), /* mc13783 allows max of 2775. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vmmc_init = { + .constraints = { + .min_uV = mV_to_uV(1600), /* mc13783 allows min of 1600. */ + .max_uV = mV_to_uV(3000), /* mc13783 allows max of 3000. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data sw3_init = { + .constraints = { + .min_uV = mV_to_uV(5000), /* mc13783 allows min of 5000. */ + .max_uV = mV_to_uV(5500), /* mc13783 allows max of 5500. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data sw1_init = { + .constraints = { + .min_uV = mV_to_uV(1200), /* mc13783 allows min of 900. */ + .max_uV = mV_to_uV(1600), /* mc13783 allows max of 2200. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE + | REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_FAST + | REGULATOR_MODE_NORMAL + | REGULATOR_MODE_IDLE + | REGULATOR_MODE_STANDBY, + .always_on = 1, + .boot_on = 1, + .initial_state = PM_SUSPEND_MEM, + .state_mem = { + .uV = mV_to_uV(1250), + .mode = REGULATOR_MODE_NORMAL, + .enabled = 1, + }, + } +}; + +static struct regulator_init_data sw_init = { + .constraints = { + .min_uV = mV_to_uV(1200), /* mc13783 allows min of 900. */ + .max_uV = mV_to_uV(2200), /* mc13783 allows max of 2200. */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vaudio_init = { + .constraints = { + .boot_on = 1, + } +}; + +static struct regulator_init_data viohi_init = { + .constraints = { + .boot_on = 1, + } +}; + +static struct regulator_init_data gpo1_init = { + .constraints = { + .boot_on = 1, + } +}; + +static struct regulator_init_data gpo4_init = { + .constraints = { + }, +}; + +static struct regulator_init_data gpo_init = { + .constraints = { + }, +}; + +static int mc13783_regulator_init(void *data) +{ + struct mc13783 *mc13783 = data; + unsigned int value; + + /*most regulators are controled by standby signal*/ + /*except violo*/ + pmic_read_reg(REG_REGULATOR_MODE_0, &value, 0xffffff); + value |= 0x492412; + pmic_write_reg(REG_REGULATOR_MODE_0, value, 0xffffff); + pmic_read_reg(REG_REGULATOR_MODE_1, &value, 0xffffff); + value |= 0x492492; + pmic_write_reg(REG_REGULATOR_MODE_1, value, 0xffffff); + /*also sw3 is controled by standby signal*/ + pmic_read_reg(REG_SWITCHERS_5, &value, 0xffffff); + value |= 0x200000; + pmic_write_reg(REG_SWITCHERS_5, value, 0xffffff); + + mc13783_register_regulator(mc13783, MC13783_SW1A, &sw1_init); + mc13783_register_regulator(mc13783, MC13783_SW1B, &sw_init); + mc13783_register_regulator(mc13783, MC13783_SW2A, &sw_init); + mc13783_register_regulator(mc13783, MC13783_SW2B, &sw_init); + mc13783_register_regulator(mc13783, MC13783_SW3, &sw3_init); + mc13783_register_regulator(mc13783, MC13783_VMMC1, &vmmc_init); + mc13783_register_regulator(mc13783, MC13783_VMMC2, &vmmc_init); + mc13783_register_regulator(mc13783, MC13783_VVIB, &vvib_init); + mc13783_register_regulator(mc13783, MC13783_VIOHI, &viohi_init); + mc13783_register_regulator(mc13783, MC13783_VIOLO, &violo_init); + mc13783_register_regulator(mc13783, MC13783_VDIG, &vdig_init); + mc13783_register_regulator(mc13783, MC13783_VRFDIG, &vrfdig_init); + mc13783_register_regulator(mc13783, MC13783_VRFREF, &vrfref_init); + mc13783_register_regulator(mc13783, MC13783_VRFCP, &vrfcp_init); + mc13783_register_regulator(mc13783, MC13783_VRF1, &vrf_init); + mc13783_register_regulator(mc13783, MC13783_VRF2, &vrf_init); + mc13783_register_regulator(mc13783, MC13783_VAUDIO, &vaudio_init); + mc13783_register_regulator(mc13783, MC13783_VCAM, &vcam_init); + mc13783_register_regulator(mc13783, MC13783_VGEN, &vgen_init); + mc13783_register_regulator(mc13783, MC13783_VSIM, &vsim_init); + mc13783_register_regulator(mc13783, MC13783_VESIM, &vesim_init); + mc13783_register_regulator(mc13783, MC13783_GPO1, &gpo1_init); + + gpo_init.supply_regulator_dev = &(mc13783->pmic.pdev[MC13783_GPO1]->dev); + mc13783_register_regulator(mc13783, MC13783_GPO2, &gpo_init); + mc13783_register_regulator(mc13783, MC13783_GPO3, &gpo_init); + mc13783_register_regulator(mc13783, MC13783_GPO4, &gpo4_init); + + return 0; +} + +static struct pmic_platform_data mc13783_plat = { + .init = mc13783_regulator_init, + .power_key_irq = IOMUX_TO_IRQ(MX31_PIN_GPIO1_2), +}; + +static struct spi_board_info __initdata mc13783_spi_device = { + .modalias = "pmic_spi", + .irq = IOMUX_TO_IRQ(MX31_PIN_GPIO1_3), + .max_speed_hz = 4000000, + .bus_num = 2, + .platform_data = &mc13783_plat, + .chip_select = 2, +}; + +int __init mx3_3stack_init_mc13783(void) +{ + return spi_register_board_info(&mc13783_spi_device, 1); +} diff --git a/arch/arm/mach-mx3/mxc_pm.c b/arch/arm/mach-mx3/mxc_pm.c new file mode 100644 index 000000000000..97bfba956922 --- /dev/null +++ b/arch/arm/mach-mx3/mxc_pm.c @@ -0,0 +1,440 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup DPM_MX31 Power Management + * @ingroup MSL_MX31 + */ +/*! + * @file mach-mx3/mxc_pm.c + * + * @brief This file provides all the kernel level and user level API + * definitions for the CRM_MCU and DPLL in mx3. + * + * @ingroup DPM_MX31 + */ + +/* + * Include Files + */ +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <mach/hardware.h> +#include <mach/system.h> +#include <mach/mxc_pm.h> +#include <asm/cacheflush.h> +#include <asm/irq.h> +#include <mach/common.h> +#include <asm/hardware/cache-l2x0.h> + +#include "crm_regs.h" + +/* Local defines */ +#define FREQ_COMP_TOLERANCE 200 /* tolerance percentage times 100 */ +#define MCU_PLL_MAX_FREQ 600000000 /* Maximum frequency MCU PLL clock */ +#define MCU_PLL_MIN_FREQ 160000000 /* Minimum frequency MCU PLL clock */ +#define NFC_MAX_FREQ 20000000 /* Maximum frequency NFC clock */ +#define PRE_DIV_MIN_FREQ 10000000 /* Minimum Frequency after Predivider */ + +static struct clk *mcu_pll_clk; +static struct clk *cpu_clk; +static struct clk *ahb_clk; +static struct clk *ipg_clk; + +/*! + * Spinlock to protect CRM register accesses + */ +static DEFINE_SPINLOCK(mxc_crm_lock); + +/*! + * This function is called to modify the contents of a CCM_MCU register + * + * @param reg_offset the CCM_MCU register that will read + * @param mask the mask to be used to clear the bits that are to be modified + * @param data the data that should be written to the register + */ +void mxc_ccm_modify_reg(void *reg_offset, unsigned int mask, + unsigned int data) +{ + unsigned long flags; + unsigned long reg; + + spin_lock_irqsave(&mxc_crm_lock, flags); + reg = __raw_readl(reg_offset); + reg = (reg & (~mask)) | data; + __raw_writel(reg, reg_offset); + spin_unlock_irqrestore(&mxc_crm_lock, flags); +} + +/*! + * Compare two frequences using allowable tolerance + * + * The MX3 PLL can generate many frequencies. This function + * compares the generated frequency to the requested frequency + * and determines it they are within and acceptable tolerance. + * + * @param freq1 desired frequency + * @param freq2 generated frequency + * + * @return Returns 0 is frequencies are within talerance + * and non-zero is they are not. + */ +static int freq_equal(unsigned long freq1, unsigned long freq2) +{ + if (freq1 > freq2) { + return (freq1 - freq2) <= (freq1 / FREQ_COMP_TOLERANCE); + } + return (freq2 - freq1) <= (freq1 / FREQ_COMP_TOLERANCE); +} + +/*! + * Calculate new MCU clock dividers for the PDR0 regiser. + * + * @param mcu_main_clk PLL output frequency (Hz) + * @param arm_freq desired ARM frequency (Hz) + * @param max_freq desired MAX frequency (Hz) + * @param ip_freq desired IP frequency (Hz) + * @param mask were to return PDR0 mask + * @param value were to return PDR0 value + * + * @return Returns 0 on success or + * Returns non zero if error + * PLL_LESS_ARM_ERR if pll frequency is less than + * desired core frequency + * FREQ_OUT_OF_RANGE if desided frequencies ar not + * possible with the current mcu pll frequency. + */ +static int +cal_pdr0_value(unsigned long mcu_main_clk, + long arm_freq, + long max_freq, + long ip_freq, unsigned long *mask, unsigned long *value) +{ + unsigned long arm_div; /* ARM core clock divider */ + unsigned long max_div; /* MAX clock divider */ + unsigned long ipg_div; /* IPG clock divider */ + unsigned long nfc_div; /* NFC (Nand Flash Controller) clock divider */ + unsigned long hsp_div; /* HSP clock divider */ + + if (arm_freq > mcu_main_clk) { + return -PLL_LESS_ARM_ERR; + } + + arm_div = mcu_main_clk / arm_freq; + if ((arm_div == 0) || !freq_equal(arm_freq, mcu_main_clk / arm_div)) { + return FREQ_OUT_OF_RANGE; + } + max_div = mcu_main_clk / max_freq; + if ((max_div == 0) || !freq_equal(max_freq, mcu_main_clk / max_div)) { + return FREQ_OUT_OF_RANGE; + } + hsp_div = max_div; + + ipg_div = max_freq / ip_freq; + if ((ipg_div == 0) || !freq_equal(ip_freq, max_freq / ipg_div)) { + return FREQ_OUT_OF_RANGE; + } + + nfc_div = ((max_freq - 1000000) / NFC_MAX_FREQ) + 1; + + /* All of the divider values have been calculated. + * Now change the hardware register. */ + + *mask = MXC_CCM_PDR0_HSP_PODF_MASK | + MXC_CCM_PDR0_NFC_PODF_MASK | + MXC_CCM_PDR0_IPG_PODF_MASK | + MXC_CCM_PDR0_MAX_PODF_MASK | MXC_CCM_PDR0_MCU_PODF_MASK; + + *value = ((hsp_div - 1) << MXC_CCM_PDR0_HSP_PODF_OFFSET) | + ((nfc_div - 1) << MXC_CCM_PDR0_NFC_PODF_OFFSET) | + ((ipg_div - 1) << MXC_CCM_PDR0_IPG_PODF_OFFSET) | + ((max_div - 1) << MXC_CCM_PDR0_MAX_PODF_OFFSET) | + ((arm_div - 1) << MXC_CCM_PDR0_MCU_PODF_OFFSET); + + return 0; +} + +/*! + * Integer clock scaling + * + * Change main arm clock frequencies without changing the PLL. + * The integer dividers are changed to produce the desired + * frequencies. The number of valid frequency are limited and + * are determined by the current MCU PLL frequency + * + * @param arm_freq desired ARM frequency (Hz) + * @param max_freq desired MAX frequency (Hz) + * @param ip_freq desired IP frequency (Hz) + * + * @return Returns 0 on success or + * Returns non zero if error + * PLL_LESS_ARM_ERR if pll frequency is less than + * desired core frequency + * FREQ_OUT_OF_RANGE if desided frequencies ar not + * possible with the current mcu pll frequency. + */ +int mxc_pm_intscale(long arm_freq, long max_freq, long ip_freq) +{ + unsigned long mcu_main_clk; /* mcu clock domain main clock */ + unsigned long mask; + unsigned long value; + int ret_value; + + printk(KERN_INFO "arm_freq=%ld, max_freq=%ld, ip_freq=%ld\n", + arm_freq, max_freq, ip_freq); + //print_frequencies(); /* debug */ + + mcu_main_clk = clk_get_rate(mcu_pll_clk); + ret_value = cal_pdr0_value(mcu_main_clk, arm_freq, max_freq, ip_freq, + &mask, &value); + if ((arm_freq != clk_round_rate(cpu_clk, arm_freq)) || + (max_freq != clk_round_rate(ahb_clk, max_freq)) || + (ip_freq != clk_round_rate(ipg_clk, ip_freq))) { + return -EINVAL; + } + + if ((max_freq != clk_get_rate(ahb_clk)) || + (ip_freq != clk_get_rate(ipg_clk))) { + return -EINVAL; + } + + if (arm_freq != clk_get_rate(cpu_clk)) { + ret_value = clk_set_rate(cpu_clk, arm_freq); + } + return ret_value; +} + +/*! + * PLL clock scaling + * + * Change MCU PLL frequency and adjust derived clocks. Integer + * dividers are used generate the derived clocks so changed to produce + * the desired the valid frequencies are limited by the desired ARM + * frequency. + * + * The clock source for the MCU is set to the MCU PLL. + * + * @param arm_freq desired ARM frequency (Hz) + * @param max_freq desired MAX frequency (Hz) + * @param ip_freq desired IP frequency (Hz) + * + * @return Returns 0 on success or + * Returns non zero if error + * PLL_LESS_ARM_ERR if pll frequency is less than + * desired core frequency + * FREQ_OUT_OF_RANGE if desided frequencies ar not + * possible with the current mcu pll frequency. + */ +int mxc_pm_pllscale(long arm_freq, long max_freq, long ip_freq) +{ + signed long pll_freq = 0; /* target pll frequency */ + unsigned long old_pll; + unsigned long mask; + unsigned long value; + int ret_value; + + printk(KERN_INFO "arm_freq=%ld, max_freq=%ld, ip_freq=%ld\n", + arm_freq, max_freq, ip_freq); + //print_frequencies(); + + do { + pll_freq += arm_freq; + if ((pll_freq > MCU_PLL_MAX_FREQ) || (pll_freq / 8 > arm_freq)) { + return FREQ_OUT_OF_RANGE; + } + if (pll_freq < MCU_PLL_MIN_FREQ) { + ret_value = 111; + } else { + ret_value = + cal_pdr0_value(pll_freq, arm_freq, max_freq, + ip_freq, &mask, &value); + } + } while (ret_value != 0); + + old_pll = clk_get_rate(mcu_pll_clk); + if (pll_freq > old_pll) { + /* if pll freq is increasing then change dividers first */ + mxc_ccm_modify_reg(MXC_CCM_PDR0, mask, value); + ret_value = clk_set_rate(mcu_pll_clk, pll_freq); + } else { + /* if pll freq is decreasing then change pll first */ + ret_value = clk_set_rate(mcu_pll_clk, pll_freq); + mxc_ccm_modify_reg(MXC_CCM_PDR0, mask, value); + } + //print_frequencies(); + return ret_value; +} + +/*! + * Implementing steps required to transition to low-power modes + * + * @param mode The desired low-power mode. Possible values are, + * WAIT_MODE, DOZE_MODE, STOP_MODE or DSM_MODE + * + */ +void mxc_pm_lowpower(int mode) +{ + unsigned int lpm; + int enable_flag; + unsigned long reg; + + local_irq_disable(); + enable_flag = 0; + + switch (mode) { + case STOP_MODE: + /* State Retention mode */ + lpm = 2; + /* Disable timer interrupt */ + disable_irq(MXC_INT_GPT); + enable_flag = 1; + + /* Enable Well Bias and set VSTBY + * VSTBY pin will be asserted during SR mode. This asks the + * PM IC to set the core voltage to the standby voltage + * Must clear the MXC_CCM_CCMR_SBYCS bit as well */ + mxc_ccm_modify_reg(MXC_CCM_CCMR, + MXC_CCM_CCMR_WBEN | MXC_CCM_CCMR_VSTBY | + MXC_CCM_CCMR_SBYCS, + MXC_CCM_CCMR_WBEN | MXC_CCM_CCMR_VSTBY | + MXC_CCM_CCMR_SBYCS); + + mxc_ccm_modify_reg(MXC_CCM_CCMR, + MXC_CCM_CCMR_LPM_MASK, + lpm << MXC_CCM_CCMR_LPM_OFFSET); + cpu_do_idle(); + break; + + case DSM_MODE: + /* Deep Sleep Mode */ + lpm = 3; + /* Disable timer interrupt */ + disable_irq(MXC_INT_GPT); + enable_flag = 1; + /* Enabled Well Bias + * SBYCS = 0, MCU clock source is disabled*/ + mxc_ccm_modify_reg(MXC_CCM_CCMR, + MXC_CCM_CCMR_WBEN | MXC_CCM_CCMR_VSTBY | + MXC_CCM_CCMR_SBYCS | MXC_CCM_CCMR_LPM_MASK, + MXC_CCM_CCMR_WBEN | MXC_CCM_CCMR_VSTBY | + MXC_CCM_CCMR_SBYCS | + (lpm << MXC_CCM_CCMR_LPM_OFFSET)); + + /* wake up by keypad */ + reg = __raw_readl(MXC_CCM_WIMR); + reg &= ~(1 << 18); + __raw_writel(reg, MXC_CCM_WIMR); + + flush_cache_all(); + l2x0_disable(); + + mxc_pm_arch_entry(IO_ADDRESS(MX31_NFC_BASE_ADDR), 2048); + printk(KERN_INFO "Resume from DSM\n"); + + l2x0_enable(); + mxc_init_irq(); + + break; + default: + case WAIT_MODE: + /* Wait is the default mode used when idle. */ + reg = __raw_readl(MXC_CCM_CCMR); + reg &= ~MXC_CCM_CCMR_LPM_MASK; + __raw_writel(reg, MXC_CCM_CCMR); + break; + } + + if (enable_flag) { + /* Enable timer interrupt */ + enable_irq(MXC_INT_GPT); + } + local_irq_enable(); +} + +#ifdef CONFIG_MXC_DVFS +/*! + * Changes MCU frequencies using dvfs. + * + * @param armfreq desired ARM frequency in Hz + * @param ahbfreq desired AHB frequency in Hz + * @param ipfreq desired IP frequency in Hz + * + * @return Returns 0 on success, non-zero on error + */ +int mxc_pm_dvfs(unsigned long armfreq, long ahbfreq, long ipfreq) +{ + int ret_value; + int i; + + if (ahbfreq != 133000000) { + return FREQ_OUT_OF_RANGE; + } + if (ipfreq != 66500000) { + return FREQ_OUT_OF_RANGE; + } + ret_value = FREQ_OUT_OF_RANGE; + for (i = 0; i < dvfs_states_tbl->num_of_states; i++) { + if (dvfs_states_tbl->freqs[i] == armfreq) { + ret_value = dvfs_set_state(i); + break; + } + } + + return ret_value; +} +#endif /* CONFIG_MXC_DVFS */ + +/*! + * This function is used to load the module. + * + * @return Returns an Integer on success + */ +static int __init mxc_pm_init_module(void) +{ + printk(KERN_INFO "Low-Level PM Driver module loaded\n"); + + mcu_pll_clk = clk_get(NULL, "mcu_pll"); + cpu_clk = clk_get(NULL, "cpu_clk"); + ahb_clk = clk_get(NULL, "ahb_clk"); + ipg_clk = clk_get(NULL, "ipg_clk"); + return 0; +} + +/*! + * This function is used to unload the module + */ +static void __exit mxc_pm_cleanup_module(void) +{ + clk_put(mcu_pll_clk); + clk_put(cpu_clk); + clk_put(ahb_clk); + clk_put(ipg_clk); + printk(KERN_INFO "Low-Level PM Driver module Unloaded\n"); +} + +module_init(mxc_pm_init_module); +module_exit(mxc_pm_cleanup_module); + +EXPORT_SYMBOL(mxc_pm_intscale); +EXPORT_SYMBOL(mxc_pm_pllscale); +EXPORT_SYMBOL(mxc_pm_lowpower); +#ifdef CONFIG_MXC_DVFS +EXPORT_SYMBOL(mxc_pm_dvfs); +#endif + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MX3 Low-level Power Management Driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-mx3/pm.c b/arch/arm/mach-mx3/pm.c new file mode 100644 index 000000000000..68b692283070 --- /dev/null +++ b/arch/arm/mach-mx3/pm.c @@ -0,0 +1,103 @@ +/* + * linux/arch/arm/mach-mx3/pm.c + * + * MX3 Power Management Routines + * + * Original code for the SA11x0: + * Copyright (c) 2001 Cliff Brake <cbrake@accelent.com> + * + * Modified for the PXA250 by Nicolas Pitre: + * Copyright (c) 2002 Monta Vista Software, Inc. + * + * Modified for the OMAP1510 by David Singleton: + * Copyright (c) 2002 Monta Vista Software, Inc. + * + * Cleanup 2004 for OMAP1510/1610 by Dirk Behme <dirk.behme@de.bosch.com> + * + * Modified for the MX31 + * Copyright 2005-2009 Freescale Semiconductor, Inc. All 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 SOFTWARE IS PROVIDED ``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. + * + * You 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. + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/suspend.h> +#include <linux/regulator/machine.h> +#include <mach/mxc_pm.h> + +/* + * TODO: whatta save? + */ + +static int mx31_suspend_enter(suspend_state_t state) +{ + printk(KERN_INFO "Hi, from mx31_pm_enter\n"); + switch (state) { + case PM_SUSPEND_MEM: + mxc_pm_lowpower(DSM_MODE); + break; + case PM_SUSPEND_STANDBY: + mxc_pm_lowpower(STOP_MODE); + break; + default: + return -1; + } + return 0; +} + +/* + * Called after processes are frozen, but before we shut down devices. + */ +static int mx31_suspend_prepare(void) +{ + return 0; +} + +/* + * Called after devices are re-setup, but before processes are thawed. + */ +static void mx31_suspend_finish(void) +{ + return; +} + +static int mx31_pm_valid(suspend_state_t state) +{ + return (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX); +} + +struct platform_suspend_ops mx31_suspend_ops = { + .valid = mx31_pm_valid, + .prepare = mx31_suspend_prepare, + .enter = mx31_suspend_enter, + .finish = mx31_suspend_finish, +}; + +static int __init mx31_pm_init(void) +{ + printk(KERN_INFO "Power Management for Freescale MX31\n"); + suspend_set_ops(&mx31_suspend_ops); + + return 0; +} + +late_initcall(mx31_pm_init); diff --git a/arch/arm/mach-mx3/sdma_script_code.h b/arch/arm/mach-mx3/sdma_script_code.h new file mode 100644 index 000000000000..627b896f91a1 --- /dev/null +++ b/arch/arm/mach-mx3/sdma_script_code.h @@ -0,0 +1,581 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __SDMA_SCRIPT_CODE_H__ +#define __SDMA_SCRIPT_CODE_H__ + +/*! +* Following define start address of start script +*/ +#define start_ADDR 0 +/*! +* Following define size of start script +*/ +#define start_SIZE 21 + +/*! +* Following define start address of core script +*/ +#define core_ADDR 80 +/*! +* Following define size of core script +*/ +#define core_SIZE 152 + +/*! +* Following define start address of common script +*/ +#define common_ADDR 232 +/*! +* Following define size of common script +*/ +#define common_SIZE 191 + +/*! +* Following define start address of burst_copy script +*/ +#define burst_copy_ADDR 423 +/*! +* Following define size of burst_copy script +*/ +#define burst_copy_SIZE 87 + +/*! +* Following define start address of dsp_2_burst script +*/ +#define dsp_2_burst_ADDR 510 +/*! +* Following define size of dsp_2_burst script +*/ +#define dsp_2_burst_SIZE 24 + +/*! +* Following define start address of burst_2_dsp script +*/ +#define burst_2_dsp_ADDR 534 +/*! +* Following define size of burst_2_dsp script +*/ +#define burst_2_dsp_SIZE 24 + +/*! +* Following define start address of dsp_copy script +*/ +#define dsp_copy_ADDR 558 +/*! +* Following define size of dsp_copy script +*/ +#define dsp_copy_SIZE 86 + +/*! +* Following define start address of mcu_2_mcu script +*/ +#define mcu_2_mcu_ADDR 644 +/*! +* Following define size of mcu_2_mcu script +*/ +#define mcu_2_mcu_SIZE 79 + +/*! +* Following define start address of mcu_2_per script +*/ +#define mcu_2_per_ADDR 723 +/*! +* Following define size of mcu_2_per script +*/ +#define mcu_2_per_SIZE 88 + +/*! +* Following define start address of test script +*/ +#define test_ADDR 811 +/*! +* Following define size of test script +*/ +#define test_SIZE 63 + +/*! +* Following define start address of mcu_2_dsp script +*/ +#define mcu_2_dsp_ADDR 874 +/*! +* Following define size of mcu_2_dsp script +*/ +#define mcu_2_dsp_SIZE 30 + +/*! +* Following define start address of mcu_2_dsp_2buf script +*/ +#define mcu_2_dsp_2buf_ADDR 904 +/*! +* Following define size of mcu_2_dsp_2buf script +*/ +#define mcu_2_dsp_2buf_SIZE 113 + +/*! +* Following define start address of dsp_2_mcu script +*/ +#define dsp_2_mcu_ADDR 1017 +/*! +* Following define size of dsp_2_mcu script +*/ +#define dsp_2_mcu_SIZE 30 + +/*! +* Following define start address of dsp_2_mcu_2buf script +*/ +#define dsp_2_mcu_2buf_ADDR 1047 +/*! +* Following define size of dsp_2_mcu_2buf script +*/ +#define dsp_2_mcu_2buf_SIZE 113 + +/*! +* Following define start address of dsp_2_dsp script +*/ +#define dsp_2_dsp_ADDR 1160 +/*! +* Following define size of dsp_2_dsp script +*/ +#define dsp_2_dsp_SIZE 64 + +/*! +* Following define start address of per_2_mcu script +*/ +#define per_2_mcu_ADDR 1224 +/*! +* Following define size of per_2_mcu script +*/ +#define per_2_mcu_SIZE 121 + +/*! +* Following define start address of dsp_2_per_2buf script +*/ +#define dsp_2_per_2buf_ADDR 1345 +/*! +* Following define size of dsp_2_per_2buf script +*/ +#define dsp_2_per_2buf_SIZE 164 + +/*! +* Following define start address of per_2_dsp_2buf script +*/ +#define per_2_dsp_2buf_ADDR 1509 +/*! +* Following define size of per_2_dsp_2buf script +*/ +#define per_2_dsp_2buf_SIZE 168 + +/*! +* Following define start address of per_2_per script +*/ +#define per_2_per_ADDR 1677 +/*! +* Following define size of per_2_per script +*/ +#define per_2_per_SIZE 67 + +/*! +* Following define start address of error_dsp script +*/ +#define error_dsp_ADDR 1744 +/*! +* Following define size of error_dsp script +*/ +#define error_dsp_SIZE 34 + +/*! +* Following define start address of ap_2_ap script +*/ +#define ap_2_ap_ADDR 6144 +/*! +* Following define size of ap_2_ap script +*/ +#define ap_2_ap_SIZE 294 + +/*! +* Following define start address of app_2_mcu script +*/ +#define app_2_mcu_ADDR 6438 +/*! +* Following define size of app_2_mcu script +*/ +#define app_2_mcu_SIZE 101 + +/*! +* Following define start address of ata_2_mcu script +*/ +#define ata_2_mcu_ADDR 6539 +/*! +* Following define size of ata_2_mcu script +*/ +#define ata_2_mcu_SIZE 110 + +/*! +* Following define start address of dptc_dvfs script +*/ +#define dptc_dvfs_ADDR 6649 +/*! +* Following define size of dptc_dvfs script +*/ +#define dptc_dvfs_SIZE 274 + +/*! +* Following define start address of error script +*/ +#define error_ADDR 6923 +/*! +* Following define size of error script +*/ +#define error_SIZE 73 + +/*! +* Following define start address of firi_2_mcu script +*/ +#define firi_2_mcu_ADDR 6996 +/*! +* Following define size of firi_2_mcu script +*/ +#define firi_2_mcu_SIZE 114 + +/*! +* Following define start address of mcu_2_app script +*/ +#define mcu_2_app_ADDR 7110 +/*! +* Following define size of mcu_2_app script +*/ +#define mcu_2_app_SIZE 127 + +/*! +* Following define start address of mcu_2_ata script +*/ +#define mcu_2_ata_ADDR 7237 +/*! +* Following define size of mcu_2_ata script +*/ +#define mcu_2_ata_SIZE 87 + +/*! +* Following define start address of mcu_2_firi script +*/ +#define mcu_2_firi_ADDR 7324 +/*! +* Following define size of mcu_2_firi script +*/ +#define mcu_2_firi_SIZE 77 + +/*! +* Following define start address of mcu_2_mshc script +*/ +#define mcu_2_mshc_ADDR 7401 +/*! +* Following define size of mcu_2_mshc script +*/ +#define mcu_2_mshc_SIZE 48 + +/*! +* Following define start address of mcu_2_shp script +*/ +#define mcu_2_shp_ADDR 7449 +/*! +* Following define size of mcu_2_shp script +*/ +#define mcu_2_shp_SIZE 123 + +/*! +* Following define start address of mshc_2_mcu script +*/ +#define mshc_2_mcu_ADDR 7572 +/*! +* Following define size of mshc_2_mcu script +*/ +#define mshc_2_mcu_SIZE 60 + +/*! +* Following define start address of shp_2_mcu script +*/ +#define shp_2_mcu_ADDR 7632 +/*! +* Following define size of shp_2_mcu script +*/ +#define shp_2_mcu_SIZE 101 + +/*! +* Following define start address of uart_2_mcu script +*/ +#define uart_2_mcu_ADDR 7733 +/*! +* Following define size of uart_2_mcu script +*/ +#define uart_2_mcu_SIZE 105 + +/*! +* Following define start address of uartsh_2_mcu script +*/ +#define uartsh_2_mcu_ADDR 7838 +/*! +* Following define size of uartsh_2_mcu script +*/ +#define uartsh_2_mcu_SIZE 98 + +/*! +* Following define the start address of sdma ram +*/ + +#define RAM_CODE_START_ADDR 6144 +/*! +* Following define the size of sdma ram +*/ +#define RAM_CODE_SIZE 1792 + +/*! +* This function returns buffer that holds the image of SDMA RAM. +* This is required to start on a 4-byte aligned boundary on some platforms +* for SDMA to work properly. +* +* @return pointer to buffer that holds the image of SDMA RAM +*/ + +__attribute__ ((__aligned__(4))) +#ifndef CONFIG_XIP_KERNEL +const +#endif +static short sdma_code[] = { + 0xc0ec, 0x7d59, 0x0970, 0x0111, 0x5111, 0x5ad1, 0x5bd9, 0xc0fe, + 0x5ce1, 0x7d02, 0x0200, 0x9806, 0x08ff, 0x0011, 0x28ff, 0x00bc, + 0x05df, 0x7d4b, 0x06df, 0x7d2f, 0x6dc5, 0x6ed5, 0x5ef1, 0x0288, + 0xd81a, 0x9854, 0x0b04, 0x00d3, 0x7d20, 0x06a5, 0x3e03, 0x3d03, + 0x03a5, 0x3b03, 0x008b, 0x058b, 0x7802, 0x63d8, 0x0000, 0x7e72, + 0x63ff, 0x7e70, 0x02a5, 0x008a, 0x4e00, 0x7d01, 0x983d, 0x6dcf, + 0x6edf, 0x0015, 0x0015, 0x7802, 0x63d8, 0x0000, 0x7e63, 0x63ff, + 0x7e61, 0x3a03, 0x008a, 0x6dcd, 0x6edd, 0x7801, 0x63d8, 0x7e5a, + 0x63ff, 0x7e58, 0x0006, 0x6dc5, 0x6e07, 0x5ef1, 0x0288, 0xd8f7, + 0x7e02, 0x7f04, 0x9854, 0x0007, 0x68cc, 0x6b28, 0x54e1, 0x0089, + 0xdb13, 0x0188, 0x5ce1, 0x9854, 0x52d1, 0x53d9, 0x54e1, 0xc10d, + 0x7dad, 0x0200, 0x9800, 0x0200, 0x9800, 0x06df, 0x7d06, 0x6d23, + 0x6ed5, 0x5ef1, 0x0288, 0xd8cd, 0x9854, 0x5ef1, 0x6e07, 0x6d03, + 0x0b04, 0x00d3, 0x7d59, 0x06a5, 0x3e03, 0x3d03, 0x4d00, 0x7d09, + 0x03a5, 0x00a3, 0x0588, 0x008b, 0xd8c9, 0x7ed8, 0x620c, 0x7ed6, + 0x008d, 0x4e00, 0x7c25, 0x0a20, 0x00da, 0x7c22, 0x6503, 0x3d1f, + 0x02a5, 0x00a2, 0x0215, 0x0215, 0x6a18, 0x6a28, 0x7fc7, 0x0a20, + 0x0b08, 0x00da, 0x7c06, 0x6b18, 0x6b28, 0x7fc0, 0x0000, 0x2020, + 0x9889, 0x0688, 0x0015, 0x0015, 0x6818, 0x6828, 0x7fb7, 0x98c2, + 0x0007, 0x6a0c, 0x54e1, 0x0089, 0xdb0f, 0x0188, 0x5ce1, 0x9854, + 0x0b04, 0x00d3, 0x7d21, 0x0389, 0x1b12, 0x048b, 0x0688, 0x0015, + 0x0015, 0x0588, 0x038c, 0x0a08, 0x05da, 0x008d, 0x7c01, 0x008a, + 0x05a0, 0x7803, 0x620b, 0x5a03, 0x1b01, 0x7e98, 0x008b, 0x00a4, + 0x038c, 0x7803, 0x5203, 0x6a0b, 0x1b01, 0x6a28, 0x7f8f, 0x0000, + 0x4d00, 0x7ce8, 0x008e, 0x3803, 0xd8c9, 0x7e88, 0x620c, 0x7e86, + 0x9854, 0x7802, 0x6209, 0x6a29, 0x0006, 0x3e03, 0x4e00, 0x7d11, + 0x0b04, 0x03a6, 0x02db, 0x7d01, 0x038a, 0x02a3, 0x048a, 0x008b, + 0x7802, 0x6329, 0x6bc8, 0x7ebc, 0x63c8, 0x7ebc, 0x008c, 0x4800, + 0x7d15, 0x0488, 0x0015, 0x0015, 0x6edf, 0x7803, 0x632b, 0x6bc8, + 0x0000, 0x7eae, 0x63c8, 0x7eae, 0x008c, 0x3803, 0x6edd, 0x7803, + 0x6329, 0x6bc8, 0x0000, 0x7ea4, 0x63c8, 0x7ea4, 0x0006, 0x3d03, + 0x4d00, 0x7d0e, 0x0b04, 0x03a5, 0x02db, 0x7d01, 0x038a, 0x02a3, + 0x048a, 0x008b, 0x7802, 0x63c8, 0x6b09, 0x7e1e, 0x7f1e, 0x008c, + 0x0488, 0x0015, 0x0015, 0x6dcf, 0x0288, 0x008a, 0x0d08, 0x02dd, + 0x7c01, 0x008d, 0x7802, 0x63c8, 0x6b0b, 0x7e0e, 0x6b28, 0x7f0d, + 0x0000, 0x02dd, 0x7c02, 0x2208, 0x990d, 0x008c, 0x3803, 0x65c0, + 0x6dc5, 0x7802, 0x63c8, 0x6b09, 0x6b28, 0x0006, 0x0870, 0x0011, + 0x5010, 0xc0ec, 0x7d5e, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, 0x56f8, + 0x7d02, 0x0200, 0x992c, 0x6ec3, 0x6d07, 0x5df0, 0x0dff, 0x0511, + 0x1dff, 0x05bc, 0x4d00, 0x7d44, 0x0b70, 0x0311, 0x522b, 0x5313, + 0x02b9, 0x4a00, 0x7c04, 0x6a28, 0x7f3a, 0x0400, 0x993c, 0x008f, + 0x00d5, 0x7d01, 0x008d, 0x05a0, 0x0a03, 0x0212, 0x02bc, 0x0210, + 0x4a00, 0x7d1c, 0x4a02, 0x7d20, 0x4a01, 0x7d23, 0x0b70, 0x0311, + 0x53eb, 0x62c8, 0x7e24, 0x0360, 0x7d02, 0x0210, 0x0212, 0x6a09, + 0x7f1e, 0x0212, 0x6a09, 0x7f1b, 0x0212, 0x6a09, 0x7f18, 0x2003, + 0x4800, 0x7cef, 0x0b70, 0x0311, 0x5313, 0x997d, 0x0015, 0x0015, + 0x7802, 0x62c8, 0x6a0b, 0x997c, 0x0015, 0x7802, 0x62c8, 0x6a0a, + 0x997c, 0x7802, 0x62c8, 0x6a09, 0x7c02, 0x0000, 0x993a, 0xdb13, + 0x6a28, 0x7ffd, 0x008b, 0x52c3, 0x53cb, 0xc10d, 0x7da5, 0x0200, + 0x992c, 0x0200, 0x9929, 0xc19d, 0xc0ec, 0x7d69, 0x0c70, 0x0411, + 0x5414, 0x5ac4, 0x028c, 0x58da, 0x5efa, 0xc0fe, 0x56fa, 0x7d02, + 0x0200, 0x9994, 0x6d07, 0x5bca, 0x5cd2, 0x0bff, 0x0311, 0x1bff, + 0x04bb, 0x0415, 0x53da, 0x4c00, 0x7d47, 0x0a70, 0x0211, 0x552a, + 0x5212, 0x008d, 0x00bb, 0x4800, 0x7c07, 0x05b9, 0x4d00, 0x7c13, + 0x6928, 0x7f2d, 0x0400, 0x99a5, 0x008f, 0x0015, 0x04d8, 0x7d01, + 0x008c, 0x04a0, 0x0015, 0x7802, 0x55c6, 0x6d0b, 0x7e29, 0x6d28, + 0x7f1e, 0x0000, 0x99a3, 0x1e20, 0x5506, 0x2620, 0x008d, 0x0560, + 0x7c08, 0x065f, 0x55c6, 0x063f, 0x7e1b, 0x6d0a, 0x7f10, 0x4c00, + 0x7d1b, 0x04d8, 0x7d02, 0x008c, 0x0020, 0x04a0, 0x0015, 0x7802, + 0x55c6, 0x6d0b, 0x7e0d, 0x6d28, 0x7f02, 0x0000, 0x99ec, 0x0007, + 0x680c, 0x6d0c, 0x6507, 0x6d07, 0x6d2b, 0x6d28, 0x0007, 0x680c, + 0x0007, 0x54d2, 0x0454, 0x99ef, 0x6928, 0x7ff1, 0x54d2, 0x008a, + 0x52c0, 0x53c8, 0xc10d, 0x0288, 0x7d9f, 0x0200, 0x9994, 0x0200, + 0x998c, 0xc0ec, 0x7d72, 0x0800, 0x0970, 0x0111, 0x5111, 0x5ac1, + 0x5bc9, 0x028e, 0xc0fe, 0x068a, 0x7c6a, 0x5dd9, 0x5ce1, 0x0bff, + 0x0311, 0x1bff, 0x03bc, 0x5bd1, 0x1a5c, 0x6ac3, 0x63c8, 0x0363, + 0x7c05, 0x036f, 0x7d27, 0x0374, 0x7c7a, 0x9a71, 0xdb04, 0x3c06, + 0x4c00, 0x7df7, 0x028f, 0x1a04, 0x6a23, 0x620b, 0x6f23, 0x301f, + 0x00aa, 0x0462, 0x7c04, 0x4a00, 0x7d0b, 0x2001, 0x9a30, 0x048a, + 0x620b, 0x2201, 0x1c01, 0x1801, 0x02dc, 0x7d02, 0x301f, 0x00aa, + 0x048f, 0x1c04, 0x6c07, 0x0488, 0x3c1f, 0x6c2b, 0x0045, 0x028e, + 0x1a5c, 0x9a11, 0x058f, 0x1d0c, 0x6d23, 0x650b, 0x007d, 0x7c01, + 0x1d08, 0x007c, 0x7c01, 0x1d04, 0x6d23, 0x650b, 0x0488, 0x3c1f, + 0x0417, 0x0417, 0x0417, 0x0417, 0x059c, 0x6d23, 0x028e, 0x1a34, + 0x6ad7, 0x0488, 0x0804, 0x7802, 0x650b, 0x6dc8, 0x008c, 0x1a28, + 0x6ad7, 0x63c8, 0x034c, 0x6bc8, 0x54d1, 0x4c00, 0x7d06, 0x0065, + 0x7c02, 0x0101, 0x0025, 0x0400, 0x9a0d, 0x52c1, 0x53c9, 0x54e1, + 0x0453, 0xc10d, 0x7d95, 0x0200, 0x9a00, 0x0200, 0x99f9, 0x0200, + 0x9a00, 0x55d9, 0x6d07, 0x54d1, 0x058a, 0x2508, 0x6dc7, 0x0373, + 0x7c03, 0x65c8, 0x6d0b, 0x2408, 0x0372, 0x7c04, 0x65c8, 0x6d0b, + 0x2408, 0x9a86, 0x6cce, 0x65c8, 0x6d0a, 0x2404, 0x6d28, 0x6507, + 0x5dd9, 0x5cd1, 0x6ad7, 0x6ae3, 0x63c8, 0x0334, 0x6bc8, 0x0370, + 0x7ca9, 0x0c60, 0x0411, 0x04bb, 0x4c00, 0x7da4, 0x0410, 0x1c30, + 0x0410, 0x04bb, 0x046d, 0x7d0a, 0x047d, 0x7c03, 0x047c, 0x7c01, + 0x9a3a, 0x003b, 0x003a, 0x0039, 0x0058, 0x9ab5, 0x047d, 0x7d03, + 0x047c, 0x7d01, 0x9a3a, 0x005b, 0xdaf9, 0x1d18, 0x6d23, 0x650b, + 0x0510, 0x003a, 0x0039, 0x0038, 0x00ad, 0xdb04, 0x0c30, 0x0410, + 0x04bb, 0x003c, 0x003d, 0x00ac, 0xdaf9, 0x007b, 0x7c04, 0x003d, + 0x003c, 0x1d0c, 0x9ad6, 0x048f, 0x1c14, 0x6c23, 0x640b, 0x4401, + 0x7d04, 0x005d, 0x005c, 0x1d0c, 0x9ad6, 0x0310, 0x3b30, 0x4b30, + 0x7d01, 0x1b10, 0x0310, 0x003d, 0x003c, 0x00ab, 0x6ad7, 0x63c8, + 0x6d23, 0x650b, 0x0560, 0x7d03, 0x005e, 0xdaed, 0x9a3a, 0x003e, + 0x0c80, 0x0410, 0x0394, 0xdaed, 0x640b, 0x037f, 0x7d02, 0x1a14, + 0x9aea, 0x1a0c, 0x6ad7, 0x6cc8, 0x9a3a, 0x0c7f, 0x0410, 0x03b4, + 0x04b8, 0x03ac, 0x640b, 0x6bc8, 0x028e, 0x1a04, 0x6ad7, 0x6cc8, + 0x0006, 0x058f, 0x1d08, 0x6d23, 0x650b, 0x007d, 0x7c01, 0x1d38, + 0x007c, 0x7c01, 0x1d1c, 0x0006, 0x048b, 0x042c, 0x0454, 0x042b, + 0x6ad7, 0x6cc8, 0x0006, 0x0007, 0x684c, 0x6144, 0x9b1c, 0x0007, + 0x68cc, 0x61d0, 0x9b1c, 0x0007, 0x680c, 0x680c, 0x6107, 0x6907, + 0x692b, 0x6928, 0x0007, 0x680c, 0x0d70, 0x0511, 0x5515, 0x55f5, + 0x01a5, 0x0dff, 0x0512, 0x1dff, 0x0512, 0x04bd, 0x0499, 0x0454, + 0x0006, 0x08ff, 0x0011, 0x28ff, 0x0006, 0x038c, 0x0eff, 0x0611, + 0x2eff, 0x03b6, 0x0006, 0x53d6, 0x0398, 0x5bd6, 0x53ee, 0x0398, + 0x5bee, 0x0006, 0x52de, 0x53e6, 0x54ee, 0x0498, 0x0454, 0x0006, + 0x50f6, 0x52c6, 0x53ce, 0x54d6, 0x0498, 0x0454, 0x0006, 0x6207, + 0x0b70, 0x0311, 0x5013, 0x55f0, 0x02a5, 0x0bff, 0x0312, 0x1bff, + 0x0312, 0x04bb, 0x049a, 0x0006, 0x1e10, 0x0870, 0x0011, 0x5010, + 0xc0ec, 0x7d39, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, + 0x0200, 0x9b5b, 0x6d07, 0x5df0, 0x0dff, 0x0511, 0x1dff, 0x05bc, + 0x4d00, 0x7d17, 0x6ec3, 0x62c8, 0x7e28, 0x0264, 0x7d08, 0x0b70, + 0x0311, 0x522b, 0x02b9, 0x4a00, 0x7c18, 0x0400, 0x9b6a, 0x0212, + 0x3aff, 0x008a, 0x05d8, 0x7d01, 0x008d, 0x0a10, 0x6ed3, 0x6ac8, + 0xdba5, 0x6a28, 0x7f17, 0x0b70, 0x0311, 0x5013, 0xdbbd, 0x52c0, + 0x53c8, 0xc10d, 0x7dd0, 0x0200, 0x9b5b, 0x008f, 0x00d5, 0x7d01, + 0x008d, 0xdba5, 0x9b68, 0x0200, 0x9b58, 0x0007, 0x68cc, 0x6a28, + 0x7f01, 0x9ba3, 0x0007, 0x6a0c, 0x6a0c, 0x6207, 0x6a07, 0x6a2b, + 0x6a28, 0x0007, 0x680c, 0x0454, 0x9b81, 0x05a0, 0x1e08, 0x6ec3, + 0x0388, 0x3b03, 0x0015, 0x0015, 0x7802, 0x62c8, 0x6a0b, 0x7ee5, + 0x6a28, 0x7fe8, 0x0000, 0x6ec1, 0x008b, 0x7802, 0x62c8, 0x6a09, + 0x7edc, 0x6a28, 0x7fdf, 0x2608, 0x0006, 0x55f0, 0x6207, 0x02a5, + 0x0dff, 0x0511, 0x1dff, 0x04b5, 0x049a, 0x0006, 0x0870, 0x0011, + 0x5010, 0xc0ec, 0x7d78, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, 0x56f8, + 0x7d02, 0x0200, 0x9bcc, 0x6d03, 0x6ed3, 0x0dff, 0x0511, 0x1dff, + 0x05bc, 0x5df8, 0x4d00, 0x7d5e, 0x0b70, 0x0311, 0x522b, 0x5313, + 0x02b9, 0x4a00, 0x7c04, 0x62ff, 0x7e3f, 0x0400, 0x9bdc, 0x008f, + 0x00d5, 0x7d01, 0x008d, 0x05a0, 0x5ddb, 0x0d03, 0x0512, 0x05bc, + 0x0510, 0x5dd3, 0x4d00, 0x7d27, 0x4d02, 0x7d20, 0x4d01, 0x7d1a, + 0x0b70, 0x0311, 0x53eb, 0x0360, 0x7d05, 0x6509, 0x7e25, 0x620a, + 0x7e23, 0x9c06, 0x620a, 0x7e20, 0x6509, 0x7e1e, 0x0512, 0x0512, + 0x02ad, 0x6ac8, 0x7f19, 0x2003, 0x4800, 0x7ced, 0x0b70, 0x0311, + 0x5313, 0x9c21, 0x7802, 0x6209, 0x6ac8, 0x9c20, 0x0015, 0x7802, + 0x620a, 0x6ac8, 0x9c20, 0x0015, 0x0015, 0x7802, 0x620b, 0x6ac8, + 0x7c03, 0x0000, 0x55db, 0x9bda, 0x0007, 0x68cc, 0x680c, 0x55d3, + 0x4d00, 0x7d03, 0x4d02, 0x7d02, 0x9c2f, 0x0017, 0x0017, 0x55db, + 0x009d, 0x55fb, 0x05a0, 0x08ff, 0x0011, 0x18ff, 0x0010, 0x04b8, + 0x04ad, 0x0454, 0x62ff, 0x7ee8, 0x008b, 0x52c0, 0x53c8, 0xc10d, + 0x7d8b, 0x0200, 0x9bcc, 0x0200, 0x9bc9, 0xc19d, 0xc0ec, 0x7d52, + 0x0c70, 0x0411, 0x5414, 0x5ac4, 0x028c, 0x58da, 0x5efa, 0xc0fe, + 0x56fa, 0x7d02, 0x0200, 0x9c4e, 0x6d03, 0x5bca, 0x5cd2, 0x0bff, + 0x0311, 0x1bff, 0x04bb, 0x0415, 0x53da, 0x0a70, 0x0211, 0x4c00, + 0x7d28, 0x552a, 0x05bb, 0x4d00, 0x7c02, 0x0400, 0x9c61, 0x4c01, + 0x7d0f, 0x008f, 0x0015, 0x04d8, 0x7d01, 0x008c, 0x0020, 0x04a0, + 0x0015, 0x7802, 0x650b, 0x5d06, 0x0000, 0x7e0c, 0x7f0d, 0x9c5f, + 0x650a, 0x7e08, 0x008d, 0x0011, 0x0010, 0x05a8, 0x065f, 0x5d06, + 0x063f, 0x7f02, 0x0007, 0x680c, 0x0007, 0x5012, 0x54d0, 0x0454, + 0x9c8b, 0x5012, 0x54d0, 0x0473, 0x7c06, 0x552a, 0x05b9, 0x4d00, + 0x7c02, 0x0400, 0x9c8d, 0x52c0, 0x53c8, 0xc10d, 0x0288, 0x7db6, + 0x0200, 0x9c4e, 0x0200, 0x9c46, 0x0870, 0x0011, 0x5010, 0xc0ec, + 0x7d46, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, 0x0200, + 0x9ca2, 0x0b70, 0x0311, 0x6ed3, 0x6d03, 0x0dff, 0x0511, 0x1dff, + 0x05bc, 0x4d00, 0x7d2b, 0x522b, 0x02b9, 0x4a00, 0x7c04, 0x62c8, + 0x7e1f, 0x0400, 0x9cb3, 0x008f, 0x00d5, 0x7d01, 0x008d, 0x05a0, + 0x0060, 0x7c05, 0x6edd, 0x6209, 0x7e16, 0x6ac8, 0x7f11, 0x0015, + 0x0060, 0x7c05, 0x6ede, 0x620a, 0x7e0e, 0x6ac8, 0x7f09, 0x6edf, + 0x0015, 0x7802, 0x620b, 0x6ac8, 0x0000, 0x7e05, 0x7f01, 0x9cb1, + 0x0007, 0x68cc, 0x9cdd, 0x0007, 0x6a0c, 0x0454, 0x62c8, 0x7ef8, + 0x5013, 0x52c0, 0x53c8, 0xc10d, 0x7dbd, 0x0200, 0x9ca2, 0x0200, + 0x9c9f, 0xc19d, 0x0870, 0x0011, 0xc0ec, 0x7d29, 0x5010, 0x5ac0, + 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, 0x0200, 0x9cf0, 0x0870, 0x0011, + 0x6d03, 0x0dff, 0x0511, 0x1dff, 0x05bc, 0x4d00, 0x7d12, 0x5228, + 0x02b9, 0x4a00, 0x7c02, 0x0400, 0x9cff, 0x620b, 0x7e06, 0x5a06, + 0x7f06, 0x0000, 0x2504, 0x7d05, 0x9cff, 0x0007, 0x680c, 0x0007, + 0x0454, 0x5010, 0x52c0, 0xc10d, 0x7ddb, 0x0200, 0x9cf0, 0x0200, + 0x9cec, 0xc19d, 0x0870, 0x0011, 0xc0ec, 0x7d74, 0x5010, 0x5ac0, + 0x5bc8, 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, 0x0200, 0x9d20, 0x6d03, + 0x0d03, 0x0512, 0x05bc, 0x0510, 0x5dd0, 0x0dff, 0x0511, 0x1dff, + 0x05bc, 0x5df8, 0x4d00, 0x7d57, 0x0a70, 0x0211, 0x532a, 0x5212, + 0x03b9, 0x4b00, 0x7c02, 0x0400, 0x9d34, 0x008f, 0x05d8, 0x7d01, + 0x008d, 0x05a0, 0x5dda, 0x55d2, 0x4d00, 0x7d27, 0x4d02, 0x7d20, + 0x4d01, 0x7d1a, 0x0a70, 0x0211, 0x52ea, 0x0260, 0x7d05, 0x6509, + 0x7e25, 0x630a, 0x7e23, 0x9d58, 0x630a, 0x7e20, 0x6509, 0x7e1e, + 0x0512, 0x0512, 0x03ad, 0x5b06, 0x7f19, 0x2003, 0x4800, 0x7ced, + 0x0a70, 0x0211, 0x5212, 0x9d73, 0x7802, 0x6309, 0x5b06, 0x9d72, + 0x0015, 0x7802, 0x630a, 0x5b06, 0x9d72, 0x0015, 0x0015, 0x7802, + 0x630b, 0x5b06, 0x7c03, 0x55da, 0x0000, 0x9d32, 0x0007, 0x680c, + 0x55d2, 0x4d00, 0x7d03, 0x4d02, 0x7d02, 0x9d80, 0x0017, 0x0017, + 0x55da, 0x009d, 0x55fa, 0x05a0, 0x08ff, 0x0011, 0x18ff, 0x0010, + 0x04b8, 0x04ad, 0x0454, 0x008a, 0x52c0, 0x53c8, 0xc10d, 0x7d90, + 0x0200, 0x9d20, 0x0200, 0x9d1c, 0xc19d, 0x0870, 0x0011, 0xc0ec, + 0x7d35, 0x5010, 0x5ac0, 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, 0x0200, + 0x9d9b, 0x0870, 0x0011, 0x6d07, 0x0dff, 0x0511, 0x1dff, 0x05bc, + 0x4d00, 0x7d1c, 0x5228, 0x02b9, 0x4a00, 0x7c04, 0x6928, 0x7f0b, + 0x0400, 0x9daa, 0x5206, 0x7e10, 0x6a0b, 0x6928, 0x7f04, 0x0000, + 0x2504, 0x7d0c, 0x9daa, 0x0007, 0x680c, 0x680c, 0x6207, 0x6a07, + 0x6a2b, 0x6a28, 0x0007, 0x680c, 0x0007, 0x0454, 0x6928, 0x7ff3, + 0x5010, 0x52c0, 0xc10d, 0x7dcf, 0x0200, 0x9d9b, 0x0200, 0x9d97, + 0xc19d, 0x0870, 0x0011, 0xc0ec, 0x7d5e, 0x5010, 0x5ac0, 0x5bc8, + 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, 0x0200, 0x9dd7, 0x6d07, 0x5df0, + 0x0dff, 0x0511, 0x1dff, 0x05bc, 0x4d00, 0x7d44, 0x0a70, 0x0211, + 0x532a, 0x5212, 0x03b9, 0x4b00, 0x7c04, 0x6a28, 0x7f3a, 0x0400, + 0x9de6, 0x008f, 0x05d8, 0x7d01, 0x008d, 0x05a0, 0x0b03, 0x0312, + 0x03bc, 0x0310, 0x4b00, 0x7d1c, 0x4b02, 0x7d20, 0x4b01, 0x7d23, + 0x0a70, 0x0211, 0x52ea, 0x5306, 0x7e24, 0x0260, 0x7d02, 0x0310, + 0x0312, 0x6b09, 0x7f1e, 0x0312, 0x6b09, 0x7f1b, 0x0312, 0x6b09, + 0x7f18, 0x2003, 0x4800, 0x7cef, 0x0a70, 0x0211, 0x5212, 0x9e27, + 0x0015, 0x0015, 0x7802, 0x5306, 0x6b0b, 0x9e26, 0x0015, 0x7802, + 0x5306, 0x6b0a, 0x9e26, 0x7802, 0x5306, 0x6b09, 0x7c02, 0x0000, + 0x9de4, 0xdb13, 0x6928, 0x7ffd, 0x008a, 0x52c0, 0x53c8, 0xc10d, + 0x7da6, 0x0200, 0x9dd7, 0x0200, 0x9dd3, 0x0870, 0x0011, 0x5010, + 0xc0ec, 0x7d5b, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, + 0x0200, 0x9e3b, 0x0b70, 0x0311, 0x6ec3, 0x6d07, 0x5df0, 0x0dff, + 0x0511, 0x1dff, 0x05bc, 0x4d00, 0x7d3d, 0x522b, 0x02b9, 0x4a00, + 0x7c04, 0x6a28, 0x7f33, 0x0400, 0x9e4d, 0x028e, 0x1a94, 0x6ac3, + 0x62c8, 0x0269, 0x7d1b, 0x1e94, 0x6ec3, 0x6ed3, 0x62c8, 0x0248, + 0x6ac8, 0x2694, 0x6ec3, 0x62c8, 0x026e, 0x7d31, 0x6a09, 0x7f1e, + 0x2501, 0x4d00, 0x7d1f, 0x028e, 0x1a98, 0x6ac3, 0x62c8, 0x6ec3, + 0x0260, 0x7df1, 0x6a28, 0x7f12, 0xdb47, 0x9e8c, 0x6ee3, 0x008f, + 0x2001, 0x00d5, 0x7d01, 0x008d, 0x05a0, 0x62c8, 0x026e, 0x7d17, + 0x6a09, 0x7f04, 0x2001, 0x7cf9, 0x0000, 0x9e4b, 0x0289, 0xdb13, + 0x018a, 0x9e9b, 0x6a28, 0x7ffa, 0x0b70, 0x0311, 0x5013, 0x52c0, + 0x53c8, 0xc10d, 0x7da8, 0x0200, 0x9e3b, 0x0200, 0x9e38, 0x6a28, + 0x7fed, 0xdb47, 0x9e9b, 0x0458, 0x0454, 0x9e8c, 0xc19d, 0x0870, + 0x0011, 0xc0ec, 0x7d54, 0x5010, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, + 0x56f8, 0x7d02, 0x0200, 0x9ea5, 0x0b70, 0x0311, 0x6d07, 0x5df0, + 0x0dff, 0x0511, 0x1dff, 0x05bc, 0x4d00, 0x7d36, 0x522b, 0x02b9, + 0x4a00, 0x7c04, 0x6928, 0x7f2c, 0x0400, 0x9eb6, 0x028e, 0x1a94, + 0x5202, 0x0269, 0x7d16, 0x1e94, 0x5206, 0x0248, 0x5a06, 0x2694, + 0x5206, 0x026e, 0x7d2e, 0x6a09, 0x7f1b, 0x2501, 0x4d00, 0x7d1c, + 0x028e, 0x1a98, 0x5202, 0x0260, 0x7df3, 0x6a28, 0x7f11, 0xdb47, + 0x9eee, 0x008f, 0x2001, 0x00d5, 0x7d01, 0x008d, 0x05a0, 0x5206, + 0x026e, 0x7d17, 0x6a09, 0x7f04, 0x2001, 0x7cf9, 0x0000, 0x9eb4, + 0x0289, 0xdb13, 0x018a, 0x9efd, 0x6928, 0x7ffa, 0x0b70, 0x0311, + 0x5013, 0x52c0, 0x53c8, 0xc10d, 0x7db0, 0x0200, 0x9ea5, 0x0200, + 0x9ea1, 0x6a28, 0x7fed, 0xdb47, 0x9efd, 0x0458, 0x0454, 0x9eee, + 0x9eee +}; +#endif diff --git a/arch/arm/mach-mx3/sdma_script_code_pass2.h b/arch/arm/mach-mx3/sdma_script_code_pass2.h new file mode 100644 index 000000000000..85de716c45f9 --- /dev/null +++ b/arch/arm/mach-mx3/sdma_script_code_pass2.h @@ -0,0 +1,434 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All 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 */ + +/*! + * @file sdma_script_code.h + * @brief This file contains functions of SDMA scripts code initialization + * + * The file was generated automatically. Based on sdma scripts library. + * + * @ingroup SDMA + */ +/******************************************************************************* + + SDMA RELEASE LABEL: "SS15_MX31" + +*******************************************************************************/ + +#ifndef __SDMA_SCRIPT_CODE_PASS2_H__ +#define __SDMA_SCRIPT_CODE_PASS2_H__ + +/*! +* SDMA ROM scripts start addresses and sizes +*/ + +#define start_ADDR_2 0 +#define start_SIZE_2 20 + +#define core_ADDR_2 80 +#define core_SIZE_2 152 + +#define common_ADDR_2 232 +#define common_SIZE_2 191 + +#define ap_2_ap_ADDR_2 423 +#define ap_2_ap_SIZE_2 294 + +#define bp_2_bp_ADDR_2 717 +#define bp_2_bp_SIZE_2 112 + +#define ap_2_bp_ADDR_2 829 +#define ap_2_bp_SIZE_2 200 + +#define bp_2_ap_ADDR_2 1029 +#define bp_2_ap_SIZE_2 223 + +#define app_2_mcu_ADDR_2 1252 +#define app_2_mcu_SIZE_2 101 + +#define mcu_2_app_ADDR_2 1353 +#define mcu_2_app_SIZE_2 127 + +#define uart_2_mcu_ADDR_2 1480 +#define uart_2_mcu_SIZE_2 105 + +#define uartsh_2_mcu_ADDR_2 1585 +#define uartsh_2_mcu_SIZE_2 98 + +#define mcu_2_shp_ADDR_2 1683 +#define mcu_2_shp_SIZE_2 123 + +#define shp_2_mcu_ADDR_2 1806 +#define shp_2_mcu_SIZE_2 101 + +#define error_ADDR_2 1907 +#define error_SIZE_2 73 + +#define test_ADDR_2 1980 +#define test_SIZE_2 63 + +#define signature_ADDR_2 1023 +#define signature_SIZE_2 1 + +/*! +* SDMA RAM scripts start addresses and sizes +*/ + +#define ap_2_ap_fixed_addr_ADDR_2 6144 +#define ap_2_ap_fixed_addr_SIZE_2 68 + +#define app_2_mcu_patched_ADDR_2 6212 +#define app_2_mcu_patched_SIZE_2 104 + +#undef app_2_mcu_ADDR_2 +#undef app_2_mcu_SIZE_2 + +/*mapping the app_2_mcu start address to the patched(RAM)script start address*/ +#define app_2_mcu_ADDR_2 app_2_mcu_patched_ADDR_2 +#define app_2_mcu_SIZE_2 app_2_mcu_patched_SIZE_2 + +#define app_2_per_ADDR_2 6316 +#define app_2_per_SIZE_2 105 + +#define ata_2_mcu_ADDR_2 6421 +#define ata_2_mcu_SIZE_2 110 + +#define firi_2_mcu_ADDR_2 6531 +#define firi_2_mcu_SIZE_2 114 + +#define loop_DMAs_fixed_addr_ADDR_2 6645 +#define loop_DMAs_fixed_addr_SIZE_2 90 + +#define mcu_2_app_patched_ADDR_2 6735 +#define mcu_2_app_patched_SIZE_2 129 + +#undef mcu_2_app_ADDR_2 +#undef mcu_2_app_SIZE_2 + +/*mapping the mcu_2_app start address to the patched(RAM)script start address*/ +#define mcu_2_app_ADDR_2 mcu_2_app_patched_ADDR_2 +#define mcu_2_app_SIZE_2 mcu_2_app_patched_SIZE_2 + +#define mcu_2_ata_ADDR_2 6864 +#define mcu_2_ata_SIZE_2 87 + +#define mcu_2_firi_ADDR_2 6951 +#define mcu_2_firi_SIZE_2 77 + +#define mcu_2_mshc_ADDR_2 7028 +#define mcu_2_mshc_SIZE_2 48 + +#define mcu_2_shp_patched_ADDR_2 7076 +#define mcu_2_shp_patched_SIZE_2 125 + +#undef mcu_2_shp_ADDR_2 +#undef mcu_2_shp_SIZE_2 + +/*mapping the mcu_2_shp start address to the patched(RAM)script start address*/ +#define mcu_2_shp_ADDR_2 mcu_2_shp_patched_ADDR_2 +#define mcu_2_shp_SIZE_2 mcu_2_shp_patched_SIZE_2 + +#define mshc_2_mcu_ADDR_2 7201 +#define mshc_2_mcu_SIZE_2 60 + +#define per_2_app_ADDR_2 7261 +#define per_2_app_SIZE_2 131 + +#define per_2_shp_ADDR_2 7392 +#define per_2_shp_SIZE_2 131 + +#define shp_2_mcu_patched_ADDR_2 7523 +#define shp_2_mcu_patched_SIZE_2 104 + +#undef shp_2_mcu_ADDR_2 +#undef shp_2_mcu_SIZE_2 + +/*mapping the shp_2_mcu start address to the patched(RAM)script start address*/ +#define shp_2_mcu_ADDR_2 shp_2_mcu_patched_ADDR_2 +#define shp_2_mcu_SIZE_2 shp_2_mcu_patched_SIZE_2 + +#define shp_2_per_ADDR_2 7627 +#define shp_2_per_SIZE_2 109 + +#define uart_2_mcu_patched_ADDR_2 7736 +#define uart_2_mcu_patched_SIZE_2 106 + +#undef uart_2_mcu_ADDR_2 +#undef uart_2_mcu_SIZE_2 + +/*mapping the uart_2_mcu start address to the patched(RAM)script start address*/ +#define uart_2_mcu_ADDR_2 uart_2_mcu_patched_ADDR_2 +#define uart_2_mcu_SIZE_2 uart_2_mcu_patched_SIZE_2 + +#define uartsh_2_mcu_patched_ADDR_2 7842 +#define uartsh_2_mcu_patched_SIZE_2 99 + +#undef uartsh_2_mcu_ADDR_2 +#undef uartsh_2_mcu_SIZE_2 + +/* + * mapping the uartsh_2_mcu start address to the patched(RAM)script + * start address + */ +#define uartsh_2_mcu_ADDR_2 uartsh_2_mcu_patched_ADDR_2 +#define uartsh_2_mcu_SIZE_2 uartsh_2_mcu_patched_SIZE_2 + +/*! +* SDMA RAM image start address and size +*/ + +#define RAM_CODE_START_ADDR_2 6144 +#define RAM_CODE_SIZE_2 1797 + +/*! +* Buffer that holds the SDMA RAM image +*/ + +__attribute__ ((__aligned__(4))) +#ifndef CONFIG_XIP_KERNEL +const +#endif +static const short sdma_code_2[] = { + 0x0970, 0x0111, 0x5111, 0x5ef9, 0xc0ec, 0x7d23, 0x5ad1, 0x5bd9, + 0xc0fe, 0x7c1f, 0x5ce1, 0x5de9, 0x5ef1, 0x08ff, 0x0011, 0x28ff, + 0x00bc, 0x048e, 0x56f9, 0x0660, 0x7d05, 0x0661, 0x7c2b, 0x6c07, + 0x6d13, 0x9821, 0x0661, 0x7d26, 0x6c17, 0x6d03, 0x028d, 0x058c, + 0x048a, 0xd9f5, 0x7e08, 0x7f07, 0x54e1, 0x52d1, 0x53d9, 0xc10d, + 0x7dde, 0x0200, 0x9804, 0x0660, 0x7d03, 0x6007, 0x52f1, 0x9832, + 0x6003, 0x52e9, 0x00a2, 0x0007, 0x6a0c, 0x6a0c, 0x6207, 0x6a07, + 0x6a2b, 0x6a28, 0x0007, 0x6a0c, 0x54e1, 0xc795, 0x048b, 0x0498, + 0x0454, 0x9825, 0x0800, 0x983c, 0x0870, 0x0011, 0x5010, 0xc0ec, + 0x7d61, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, 0x0200, + 0x984a, 0x6ec3, 0x6d07, 0x5df0, 0x0dff, 0x0511, 0x1dff, 0x05bc, + 0x4d00, 0x7d45, 0x0b70, 0x0311, 0x522b, 0x5313, 0x02b9, 0x4a00, + 0x7c04, 0x6a28, 0x7f3b, 0x0400, 0x985a, 0x008f, 0x00d5, 0x7d01, + 0x008d, 0x05a0, 0x0a03, 0x0212, 0x02bc, 0x0210, 0x4a00, 0x7d1c, + 0x4a02, 0x7d20, 0x4a01, 0x7d23, 0x0b70, 0x0311, 0x53eb, 0x62c8, + 0x7e25, 0x0360, 0x7d02, 0x0210, 0x0212, 0x6a09, 0x7f1f, 0x0212, + 0x6a09, 0x7f1c, 0x0212, 0x6a09, 0x7f19, 0x2003, 0x4800, 0x7cef, + 0x0b70, 0x0311, 0x5313, 0x989b, 0x0015, 0x0015, 0x7802, 0x62c8, + 0x6a0b, 0x989a, 0x0015, 0x7802, 0x62c8, 0x6a0a, 0x989a, 0x7802, + 0x62c8, 0x6a09, 0x7c03, 0x6a28, 0x0000, 0x9858, 0xc77b, 0x6a28, + 0x7ffd, 0x0870, 0x0011, 0x5010, 0x52c0, 0x53c8, 0xc10d, 0x7da2, + 0x0200, 0x984a, 0x0200, 0x9847, 0x0870, 0x0011, 0x5010, 0xc0ec, + 0x7d62, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, 0x0200, + 0x98b2, 0x6ec3, 0x6dd7, 0x5df0, 0x0dff, 0x0511, 0x1dff, 0x05bc, + 0x4d00, 0x7d46, 0x0b70, 0x0311, 0x522b, 0x5313, 0x02b9, 0x4a00, + 0x7c04, 0x62ff, 0x7e3c, 0x0400, 0x98c2, 0x008f, 0x00d5, 0x7d01, + 0x008d, 0x05a0, 0x0a03, 0x0212, 0x02bc, 0x0210, 0x4a00, 0x7d28, + 0x4a02, 0x7d20, 0x4a01, 0x7d19, 0x6ddd, 0x0b70, 0x0311, 0x53eb, + 0x62c8, 0x7e25, 0x0360, 0x7d02, 0x0210, 0x0212, 0x6ac8, 0x7f1f, + 0x0212, 0x6ac8, 0x7f1c, 0x0212, 0x6ac8, 0x7f19, 0x2003, 0x4800, + 0x7cef, 0x0b70, 0x0311, 0x5313, 0x9905, 0x6ddd, 0x7802, 0x62c8, + 0x6ac8, 0x9904, 0x6dde, 0x0015, 0x7802, 0x62c8, 0x6ac8, 0x9904, + 0x0015, 0x0015, 0x7801, 0x62d8, 0x7c02, 0x0000, 0x98c0, 0xc777, + 0x62ff, 0x7efd, 0x0870, 0x0011, 0x5010, 0x52c0, 0x53c8, 0xc10d, + 0x7da1, 0x0200, 0x98b2, 0x0200, 0x98af, 0xc19d, 0xc0ec, 0x7d69, + 0x0c70, 0x0411, 0x5414, 0x5ac4, 0x028c, 0x58da, 0x5efa, 0xc0fe, + 0x56fa, 0x7d02, 0x0200, 0x991e, 0x6d07, 0x5bca, 0x5cd2, 0x0bff, + 0x0311, 0x1bff, 0x04bb, 0x0415, 0x53da, 0x4c00, 0x7d47, 0x0a70, + 0x0211, 0x552a, 0x5212, 0x008d, 0x00bb, 0x4800, 0x7c07, 0x05b9, + 0x4d00, 0x7c13, 0x6928, 0x7f2d, 0x0400, 0x992f, 0x008f, 0x0015, + 0x04d8, 0x7d01, 0x008c, 0x04a0, 0x0015, 0x7802, 0x55c6, 0x6d0b, + 0x7e29, 0x6d28, 0x7f1e, 0x0000, 0x992d, 0x1e20, 0x5506, 0x2620, + 0x008d, 0x0560, 0x7c08, 0x065f, 0x55c6, 0x063f, 0x7e1b, 0x6d0a, + 0x7f10, 0x4c00, 0x7d1b, 0x04d8, 0x7d02, 0x008c, 0x0020, 0x04a0, + 0x0015, 0x7802, 0x55c6, 0x6d0b, 0x7e0d, 0x6d28, 0x7f02, 0x0000, + 0x9976, 0x0007, 0x680c, 0x6d0c, 0x6507, 0x6d07, 0x6d2b, 0x6d28, + 0x0007, 0x680c, 0x0007, 0x54d2, 0x0454, 0x9979, 0x6928, 0x7ff1, + 0x54d2, 0x008a, 0x52c0, 0x53c8, 0xc10d, 0x0288, 0x7d9f, 0x0200, + 0x991e, 0x0200, 0x9916, 0x1e10, 0x0870, 0x0011, 0x5010, 0xc0ec, + 0x7d39, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, 0x0200, + 0x998a, 0x6d07, 0x5df0, 0x0dff, 0x0511, 0x1dff, 0x05bc, 0x4d00, + 0x7d17, 0x6ec3, 0x62c8, 0x7e28, 0x0264, 0x7d08, 0x0b70, 0x0311, + 0x522b, 0x02b9, 0x4a00, 0x7c18, 0x0400, 0x9999, 0x0212, 0x3aff, + 0x008a, 0x05d8, 0x7d01, 0x008d, 0x0a10, 0x6ed3, 0x6ac8, 0xd9d4, + 0x6a28, 0x7f17, 0x0b70, 0x0311, 0x5013, 0xd9ec, 0x52c0, 0x53c8, + 0xc10d, 0x7dd0, 0x0200, 0x998a, 0x008f, 0x00d5, 0x7d01, 0x008d, + 0xd9d4, 0x9997, 0x0200, 0x9987, 0x0007, 0x68cc, 0x6a28, 0x7f01, + 0x99d2, 0x0007, 0x6a0c, 0x6a0c, 0x6207, 0x6a07, 0x6a2b, 0x6a28, + 0x0007, 0x680c, 0x0454, 0x99b0, 0x05a0, 0x1e08, 0x6ec3, 0x0388, + 0x3b03, 0x0015, 0x0015, 0x7802, 0x62c8, 0x6a0b, 0x7ee5, 0x6a28, + 0x7fe8, 0x0000, 0x6ec1, 0x008b, 0x7802, 0x62c8, 0x6a09, 0x7edc, + 0x6a28, 0x7fdf, 0x2608, 0x0006, 0x55f0, 0x6207, 0x02a5, 0x0dff, + 0x0511, 0x1dff, 0x04b5, 0x049a, 0x0006, 0x0388, 0x028d, 0x3a03, + 0x4a00, 0x7c33, 0x028c, 0x3a03, 0x4a00, 0x7d0c, 0x0804, 0x00a2, + 0x00db, 0x7d24, 0x03a0, 0x0498, 0x7802, 0x6209, 0x6a29, 0x7e24, + 0x620c, 0x7e22, 0x0804, 0x03d0, 0x7d19, 0x0820, 0x028c, 0x3a1f, + 0x00a2, 0x03d0, 0x7c02, 0x008b, 0x3003, 0x03a0, 0x0015, 0x0015, + 0x6818, 0x7e12, 0x6828, 0x7f10, 0x0000, 0x0820, 0x03d8, 0x7df5, + 0x0804, 0x03d0, 0x7d03, 0x008b, 0x3003, 0x9a15, 0x008b, 0x7802, + 0x6209, 0x6a29, 0x7e01, 0x620c, 0x0006, 0x0804, 0x03d0, 0x7df6, + 0x048b, 0x3403, 0x03a4, 0x0415, 0x0415, 0x0d0f, 0x0511, 0x1df0, + 0x0808, 0x04d0, 0x7c01, 0x008c, 0x58c1, 0x04a0, 0x7803, 0x620b, + 0x5a05, 0x1d01, 0x7ee9, 0x50c1, 0x05a0, 0x7803, 0x5205, 0x6a0b, + 0x1d01, 0x6a28, 0x7fe1, 0x0000, 0x4c00, 0x7ce7, 0x9a26, 0x0870, + 0x0011, 0x5010, 0xc0ec, 0x7d7a, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, + 0x56f8, 0x7d02, 0x0200, 0x9a55, 0x6d03, 0x6ed3, 0x0dff, 0x0511, + 0x1dff, 0x05bc, 0x5df8, 0x4d00, 0x7d5e, 0x0b70, 0x0311, 0x522b, + 0x5313, 0x02b9, 0x4a00, 0x7c04, 0x62ff, 0x7e3f, 0x0400, 0x9a65, + 0x008f, 0x00d5, 0x7d01, 0x008d, 0x05a0, 0x5ddb, 0x0d03, 0x0512, + 0x05bc, 0x0510, 0x5dd3, 0x4d00, 0x7d27, 0x4d02, 0x7d20, 0x4d01, + 0x7d1a, 0x0b70, 0x0311, 0x53eb, 0x0360, 0x7d05, 0x6509, 0x7e25, + 0x620a, 0x7e23, 0x9a8f, 0x620a, 0x7e20, 0x6509, 0x7e1e, 0x0512, + 0x0512, 0x02ad, 0x6ac8, 0x7f19, 0x2003, 0x4800, 0x7ced, 0x0b70, + 0x0311, 0x5313, 0x9aaa, 0x7802, 0x6209, 0x6ac8, 0x9aa9, 0x0015, + 0x7802, 0x620a, 0x6ac8, 0x9aa9, 0x0015, 0x0015, 0x7802, 0x620b, + 0x6ac8, 0x7c03, 0x0000, 0x55db, 0x9a63, 0x0007, 0x68cc, 0x680c, + 0x55d3, 0x4d00, 0x7d03, 0x4d02, 0x7d02, 0x9ab8, 0x0017, 0x0017, + 0x55db, 0x009d, 0x55fb, 0x05a0, 0x08ff, 0x0011, 0x18ff, 0x0010, + 0x04b8, 0x04ad, 0x0454, 0x62ff, 0x7ee8, 0x0870, 0x0011, 0x5010, + 0x52c0, 0x53c8, 0xc10d, 0x7d89, 0x0200, 0x9a55, 0x0200, 0x9a52, + 0xc19d, 0xc0ec, 0x7d52, 0x0c70, 0x0411, 0x5414, 0x5ac4, 0x028c, + 0x58da, 0x5efa, 0xc0fe, 0x56fa, 0x7d02, 0x0200, 0x9ad9, 0x6d03, + 0x5bca, 0x5cd2, 0x0bff, 0x0311, 0x1bff, 0x04bb, 0x0415, 0x53da, + 0x0a70, 0x0211, 0x4c00, 0x7d28, 0x552a, 0x05bb, 0x4d00, 0x7c02, + 0x0400, 0x9aec, 0x4c01, 0x7d0f, 0x008f, 0x0015, 0x04d8, 0x7d01, + 0x008c, 0x0020, 0x04a0, 0x0015, 0x7802, 0x650b, 0x5d06, 0x0000, + 0x7e0c, 0x7f0d, 0x9aea, 0x650a, 0x7e08, 0x008d, 0x0011, 0x0010, + 0x05a8, 0x065f, 0x5d06, 0x063f, 0x7f02, 0x0007, 0x680c, 0x0007, + 0x5012, 0x54d0, 0x0454, 0x9b16, 0x5012, 0x54d0, 0x0473, 0x7c06, + 0x552a, 0x05b9, 0x4d00, 0x7c02, 0x0400, 0x9b18, 0x52c0, 0x53c8, + 0xc10d, 0x0288, 0x7db6, 0x0200, 0x9ad9, 0x0200, 0x9ad1, 0x0870, + 0x0011, 0x5010, 0xc0ec, 0x7d46, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, + 0x56f8, 0x7d02, 0x0200, 0x9b2d, 0x0b70, 0x0311, 0x6ed3, 0x6d03, + 0x0dff, 0x0511, 0x1dff, 0x05bc, 0x4d00, 0x7d2b, 0x522b, 0x02b9, + 0x4a00, 0x7c04, 0x62c8, 0x7e1f, 0x0400, 0x9b3e, 0x008f, 0x00d5, + 0x7d01, 0x008d, 0x05a0, 0x0060, 0x7c05, 0x6edd, 0x6209, 0x7e16, + 0x6ac8, 0x7f11, 0x0015, 0x0060, 0x7c05, 0x6ede, 0x620a, 0x7e0e, + 0x6ac8, 0x7f09, 0x6edf, 0x0015, 0x7802, 0x620b, 0x6ac8, 0x0000, + 0x7e05, 0x7f01, 0x9b3c, 0x0007, 0x68cc, 0x9b68, 0x0007, 0x6a0c, + 0x0454, 0x62c8, 0x7ef8, 0x5013, 0x52c0, 0x53c8, 0xc10d, 0x7dbd, + 0x0200, 0x9b2d, 0x0200, 0x9b2a, 0xc19d, 0x0870, 0x0011, 0xc0ec, + 0x7d29, 0x5010, 0x5ac0, 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, 0x0200, + 0x9b7b, 0x0870, 0x0011, 0x6d03, 0x0dff, 0x0511, 0x1dff, 0x05bc, + 0x4d00, 0x7d12, 0x5228, 0x02b9, 0x4a00, 0x7c02, 0x0400, 0x9b8a, + 0x620b, 0x7e06, 0x5a06, 0x7f06, 0x0000, 0x2504, 0x7d05, 0x9b8a, + 0x0007, 0x680c, 0x0007, 0x0454, 0x5010, 0x52c0, 0xc10d, 0x7ddb, + 0x0200, 0x9b7b, 0x0200, 0x9b77, 0xc19d, 0x0870, 0x0011, 0xc0ec, + 0x7d76, 0x5010, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, + 0x0200, 0x9bab, 0x6d03, 0x0d03, 0x0512, 0x05bc, 0x0510, 0x5dd0, + 0x0dff, 0x0511, 0x1dff, 0x05bc, 0x5df8, 0x4d00, 0x7d57, 0x0a70, + 0x0211, 0x532a, 0x5212, 0x03b9, 0x4b00, 0x7c02, 0x0400, 0x9bbf, + 0x008f, 0x05d8, 0x7d01, 0x008d, 0x05a0, 0x5dda, 0x55d2, 0x4d00, + 0x7d27, 0x4d02, 0x7d20, 0x4d01, 0x7d1a, 0x0a70, 0x0211, 0x52ea, + 0x0260, 0x7d05, 0x6509, 0x7e25, 0x630a, 0x7e23, 0x9be3, 0x630a, + 0x7e20, 0x6509, 0x7e1e, 0x0512, 0x0512, 0x03ad, 0x5b06, 0x7f19, + 0x2003, 0x4800, 0x7ced, 0x0a70, 0x0211, 0x5212, 0x9bfe, 0x7802, + 0x6309, 0x5b06, 0x9bfd, 0x0015, 0x7802, 0x630a, 0x5b06, 0x9bfd, + 0x0015, 0x0015, 0x7802, 0x630b, 0x5b06, 0x7c03, 0x55da, 0x0000, + 0x9bbd, 0x0007, 0x680c, 0x55d2, 0x4d00, 0x7d03, 0x4d02, 0x7d02, + 0x9c0b, 0x0017, 0x0017, 0x55da, 0x009d, 0x55fa, 0x05a0, 0x08ff, + 0x0011, 0x18ff, 0x0010, 0x04b8, 0x04ad, 0x0454, 0x0870, 0x0011, + 0x5010, 0x52c0, 0x53c8, 0xc10d, 0x7d8e, 0x0200, 0x9bab, 0x0200, + 0x9ba7, 0xc19d, 0x0870, 0x0011, 0xc0ec, 0x7d35, 0x5010, 0x5ac0, + 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, 0x0200, 0x9c28, 0x0870, 0x0011, + 0x6d07, 0x0dff, 0x0511, 0x1dff, 0x05bc, 0x4d00, 0x7d1c, 0x5228, + 0x02b9, 0x4a00, 0x7c04, 0x6928, 0x7f0b, 0x0400, 0x9c37, 0x5206, + 0x7e10, 0x6a0b, 0x6928, 0x7f04, 0x0000, 0x2504, 0x7d0c, 0x9c37, + 0x0007, 0x680c, 0x680c, 0x6207, 0x6a07, 0x6a2b, 0x6a28, 0x0007, + 0x680c, 0x0007, 0x0454, 0x6928, 0x7ff3, 0x5010, 0x52c0, 0xc10d, + 0x7dcf, 0x0200, 0x9c28, 0x0200, 0x9c24, 0x0870, 0x0011, 0x5010, + 0xc0ec, 0x7d7c, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, + 0x0200, 0x9c63, 0x6ed3, 0x6dc5, 0x0dff, 0x0511, 0x1dff, 0x05bc, + 0x5df8, 0x4d00, 0x7d60, 0x0b70, 0x0311, 0x522b, 0x5313, 0x02b9, + 0x4a00, 0x7c02, 0x0400, 0x9c73, 0x008f, 0x00d5, 0x7d01, 0x008d, + 0x05a0, 0x5ddb, 0x0d03, 0x0512, 0x05bc, 0x0510, 0x5dd3, 0x4d00, + 0x7d2c, 0x4d02, 0x7d24, 0x4d01, 0x7d1e, 0x59e3, 0x0b70, 0x0311, + 0x53eb, 0x61c8, 0x7e2b, 0x62c8, 0x7e29, 0x65c8, 0x7e27, 0x0360, + 0x7d03, 0x0112, 0x0112, 0x9c9e, 0x0512, 0x0512, 0x0211, 0x02a9, + 0x02ad, 0x6ac8, 0x7f1b, 0x2003, 0x4800, 0x7ceb, 0x0b70, 0x0311, + 0x5313, 0x51e3, 0x9cbb, 0x7802, 0x62c8, 0x6ac8, 0x9cba, 0x6dce, + 0x0015, 0x7802, 0x62c8, 0x6ac8, 0x9cba, 0x6dcf, 0x0015, 0x0015, + 0x7801, 0x62d8, 0x7c03, 0x0000, 0x55db, 0x9c71, 0x0007, 0x68ff, + 0x55d3, 0x4d00, 0x7d03, 0x4d02, 0x7d02, 0x9cc8, 0x0017, 0x0017, + 0x55db, 0x009d, 0x55fb, 0x05a0, 0x08ff, 0x0011, 0x18ff, 0x0010, + 0x04b8, 0x04ad, 0x0454, 0x62c8, 0x7ee9, 0x0870, 0x0011, 0x5010, + 0x52c0, 0x53c8, 0xc10d, 0x7d87, 0x0200, 0x9c63, 0x0200, 0x9c60, + 0xc19d, 0x0870, 0x0011, 0xc0ec, 0x7d7c, 0x5010, 0x5ac0, 0x5bc8, + 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, 0x0200, 0x9ce7, 0x6dc5, 0x0d03, + 0x0512, 0x05bc, 0x0510, 0x5dd0, 0x0dff, 0x0511, 0x1dff, 0x05bc, + 0x5df8, 0x4d00, 0x7d5d, 0x0a70, 0x0211, 0x532a, 0x5212, 0x03b9, + 0x4b00, 0x7c02, 0x0400, 0x9cfb, 0x008f, 0x05d8, 0x7d01, 0x008d, + 0x05a0, 0x5dda, 0x55d2, 0x4d00, 0x7d2c, 0x4d02, 0x7d24, 0x4d01, + 0x7d1e, 0x59e2, 0x0a70, 0x0211, 0x52ea, 0x61c8, 0x7e2c, 0x63c8, + 0x7e2a, 0x65c8, 0x7e28, 0x0260, 0x7d03, 0x0112, 0x0112, 0x9d22, + 0x0512, 0x0512, 0x0311, 0x03a9, 0x03ad, 0x5b06, 0x7f1c, 0x2003, + 0x4800, 0x7ceb, 0x0a70, 0x0211, 0x5212, 0x51e2, 0x9d40, 0x7802, + 0x63c8, 0x5b06, 0x9d3f, 0x6dce, 0x0015, 0x7802, 0x63c8, 0x5b06, + 0x9d3f, 0x6dcf, 0x0015, 0x0015, 0x7802, 0x63c8, 0x5b06, 0x7c03, + 0x55da, 0x0000, 0x9cf9, 0x0007, 0x68ff, 0x55d2, 0x4d00, 0x7d03, + 0x4d02, 0x7d02, 0x9d4d, 0x0017, 0x0017, 0x55da, 0x009d, 0x55fa, + 0x05a0, 0x08ff, 0x0011, 0x18ff, 0x0010, 0x04b8, 0x04ad, 0x0454, + 0x0870, 0x0011, 0x5010, 0x52c0, 0x53c8, 0xc10d, 0x7d88, 0x0200, + 0x9ce7, 0x0200, 0x9ce3, 0xc19d, 0x0870, 0x0011, 0xc0ec, 0x7d61, + 0x5010, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, 0x0200, + 0x9d6a, 0x6d07, 0x5df0, 0x0dff, 0x0511, 0x1dff, 0x05bc, 0x4d00, + 0x7d45, 0x0a70, 0x0211, 0x532a, 0x5212, 0x03b9, 0x4b00, 0x7c04, + 0x6a28, 0x7f3b, 0x0400, 0x9d79, 0x008f, 0x05d8, 0x7d01, 0x008d, + 0x05a0, 0x0b03, 0x0312, 0x03bc, 0x0310, 0x4b00, 0x7d1c, 0x4b02, + 0x7d20, 0x4b01, 0x7d23, 0x0a70, 0x0211, 0x52ea, 0x5306, 0x7e25, + 0x0260, 0x7d02, 0x0310, 0x0312, 0x6b09, 0x7f1f, 0x0312, 0x6b09, + 0x7f1c, 0x0312, 0x6b09, 0x7f19, 0x2003, 0x4800, 0x7cef, 0x0a70, + 0x0211, 0x5212, 0x9dba, 0x0015, 0x0015, 0x7802, 0x5306, 0x6b0b, + 0x9db9, 0x0015, 0x7802, 0x5306, 0x6b0a, 0x9db9, 0x7802, 0x5306, + 0x6b09, 0x7c03, 0x6b28, 0x0000, 0x9d77, 0xc77b, 0x6928, 0x7ffd, + 0x0870, 0x0011, 0x5010, 0x52c0, 0x53c8, 0xc10d, 0x7da3, 0x0200, + 0x9d6a, 0x0200, 0x9d66, 0xc19d, 0x0870, 0x0011, 0xc0ec, 0x7d60, + 0x5010, 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, 0x0200, + 0x9dd2, 0x6dd7, 0x5df0, 0x0dff, 0x0511, 0x1dff, 0x05bc, 0x4d00, + 0x7d46, 0x0a70, 0x0211, 0x532a, 0x5212, 0x03b9, 0x4b00, 0x7c02, + 0x0400, 0x9de1, 0x008f, 0x05d8, 0x7d01, 0x008d, 0x05a0, 0x0b03, + 0x0312, 0x03bc, 0x0310, 0x4b00, 0x7d28, 0x4b02, 0x7d20, 0x4b01, + 0x7d19, 0x6ddd, 0x0a70, 0x0211, 0x52ea, 0x5306, 0x7e27, 0x0260, + 0x7d02, 0x0310, 0x0312, 0x6bc8, 0x7f21, 0x0312, 0x6bc8, 0x7f1e, + 0x0312, 0x6bc8, 0x7f1b, 0x2003, 0x4800, 0x7cef, 0x0a70, 0x0211, + 0x5212, 0x9e23, 0x6ddd, 0x7802, 0x5306, 0x6bc8, 0x9e22, 0x6dde, + 0x0015, 0x7802, 0x5306, 0x6bc8, 0x9e22, 0x0015, 0x0015, 0x7802, + 0x5306, 0x6bc8, 0x7c03, 0x0000, 0xde32, 0x9ddf, 0xc777, 0x0870, + 0x0011, 0x5010, 0x52c0, 0x53c8, 0xc10d, 0x7da4, 0x0200, 0x9dd2, + 0x0200, 0x9dce, 0x63ff, 0x0368, 0x7d02, 0x0369, 0x7def, 0x0006, + 0x0870, 0x0011, 0x5010, 0xc0ec, 0x7d5c, 0x5ac0, 0x5bc8, 0x5ef8, + 0xc0fe, 0x56f8, 0x7d02, 0x0200, 0x9e3e, 0x0b70, 0x0311, 0x6ec3, + 0x6d07, 0x5df0, 0x0dff, 0x0511, 0x1dff, 0x05bc, 0x4d00, 0x7d3e, + 0x522b, 0x02b9, 0x4a00, 0x7c04, 0x6a28, 0x7f34, 0x0400, 0x9e50, + 0x028e, 0x1a94, 0x6ac3, 0x62c8, 0x0269, 0x7d1b, 0x1e94, 0x6ec3, + 0x6ed3, 0x62c8, 0x0248, 0x6ac8, 0x2694, 0x6ec3, 0x62c8, 0x026e, + 0x7d32, 0x6a09, 0x7f1f, 0x2501, 0x4d00, 0x7d20, 0x028e, 0x1a98, + 0x6ac3, 0x62c8, 0x6ec3, 0x0260, 0x7df1, 0x6a28, 0x7f13, 0xc7af, + 0x9e90, 0x6ee3, 0x008f, 0x2001, 0x00d5, 0x7d01, 0x008d, 0x05a0, + 0x62c8, 0x026e, 0x7d18, 0x6a09, 0x7f05, 0x2001, 0x7cf9, 0x6a28, + 0x0000, 0x9e4e, 0x0289, 0xc77b, 0x018a, 0x9e9f, 0x6a28, 0x7ffa, + 0x0b70, 0x0311, 0x5013, 0x52c0, 0x53c8, 0xc10d, 0x7da7, 0x0200, + 0x9e3e, 0x0200, 0x9e3b, 0x6a28, 0x7fed, 0xc7af, 0x9e9f, 0x0458, + 0x0454, 0x9e90, 0xc19d, 0x0870, 0x0011, 0xc0ec, 0x7d55, 0x5010, + 0x5ac0, 0x5bc8, 0x5ef8, 0xc0fe, 0x56f8, 0x7d02, 0x0200, 0x9ea9, + 0x0b70, 0x0311, 0x6d07, 0x5df0, 0x0dff, 0x0511, 0x1dff, 0x05bc, + 0x4d00, 0x7d37, 0x522b, 0x02b9, 0x4a00, 0x7c04, 0x6928, 0x7f2d, + 0x0400, 0x9eba, 0x028e, 0x1a94, 0x5202, 0x0269, 0x7d16, 0x1e94, + 0x5206, 0x0248, 0x5a06, 0x2694, 0x5206, 0x026e, 0x7d2f, 0x6a09, + 0x7f1c, 0x2501, 0x4d00, 0x7d1d, 0x028e, 0x1a98, 0x5202, 0x0260, + 0x7df3, 0x6a28, 0x7f12, 0xc7af, 0x9ef3, 0x008f, 0x2001, 0x00d5, + 0x7d01, 0x008d, 0x05a0, 0x5206, 0x026e, 0x7d18, 0x6a09, 0x7f05, + 0x2001, 0x7cf9, 0x6a28, 0x0000, 0x9eb8, 0x0289, 0xc77b, 0x018a, + 0x9f02, 0x6928, 0x7ffa, 0x0b70, 0x0311, 0x5013, 0x52c0, 0x53c8, + 0xc10d, 0x7daf, 0x0200, 0x9ea9, 0x0200, 0x9ea5, 0x6a28, 0x7fed, + 0xc7af, 0x9f02, 0x0458, 0x0454, 0x9ef3 +}; +#endif diff --git a/arch/arm/mach-mx3/serial.c b/arch/arm/mach-mx3/serial.c new file mode 100644 index 000000000000..fca1b5b8a38f --- /dev/null +++ b/arch/arm/mach-mx3/serial.c @@ -0,0 +1,267 @@ +/* + * Copyright 2006-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @file mach-mx3/serial.c + * + * @brief This file contains the UART initiliazation. + * + * @ingroup MSL_MX31 + */ +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/serial.h> +#include <mach/hardware.h> +#include <mach/mxc_uart.h> +#include <mach/spba.h> +#include "serial.h" +#include "board-mx31ads.h" +#include "board-mx3_3stack.h" + +#if defined(CONFIG_SERIAL_MXC) || defined(CONFIG_SERIAL_MXC_MODULE) + +/*! + * This is an array where each element holds information about a UART port, + * like base address of the UART, interrupt numbers etc. This structure is + * passed to the serial_core.c file. Based on which UART is used, the core file + * passes back the appropriate port structure as an argument to the control + * functions. + */ +static uart_mxc_port mxc_ports[MXC_UART_NR] = { + [0] = { + .port = { + .membase = (void *)IO_ADDRESS(UART1_BASE_ADDR), + .mapbase = UART1_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART1_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 0, + }, + .ints_muxed = UART1_MUX_INTS, + .irqs = {UART1_INT2, UART1_INT3}, + .mode = UART1_MODE, + .ir_mode = UART1_IR, + .enabled = UART1_ENABLED, + .hardware_flow = UART1_HW_FLOW, + .cts_threshold = UART1_UCR4_CTSTL, + .dma_enabled = UART1_DMA_ENABLE, + .dma_rxbuf_size = UART1_DMA_RXBUFSIZE, + .rx_threshold = UART1_UFCR_RXTL, + .tx_threshold = UART1_UFCR_TXTL, + .shared = UART1_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART1_TX, + .dma_rx_id = MXC_DMA_UART1_RX, + .rxd_mux = MXC_UART_RXDMUX, + .ir_tx_inv = MXC_IRDA_TX_INV, + .ir_rx_inv = MXC_IRDA_RX_INV, + }, + [1] = { + .port = { + .membase = (void *)IO_ADDRESS(UART2_BASE_ADDR), + .mapbase = UART2_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART2_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 1, + }, + .ints_muxed = UART2_MUX_INTS, + .irqs = {UART2_INT2, UART2_INT3}, + .mode = UART2_MODE, + .ir_mode = UART2_IR, + .enabled = UART2_ENABLED, + .hardware_flow = UART2_HW_FLOW, + .cts_threshold = UART2_UCR4_CTSTL, + .dma_enabled = UART2_DMA_ENABLE, + .dma_rxbuf_size = UART2_DMA_RXBUFSIZE, + .rx_threshold = UART2_UFCR_RXTL, + .tx_threshold = UART2_UFCR_TXTL, + .shared = UART2_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART2_TX, + .dma_rx_id = MXC_DMA_UART2_RX, + .rxd_mux = MXC_UART_IR_RXDMUX, + .ir_tx_inv = MXC_IRDA_TX_INV, + .ir_rx_inv = MXC_IRDA_RX_INV, + }, +#if UART3_ENABLED == 1 + [2] = { + .port = { + .membase = (void *)IO_ADDRESS(UART3_BASE_ADDR), + .mapbase = UART3_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART3_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 2, + }, + .ints_muxed = UART3_MUX_INTS, + .irqs = {UART3_INT2, UART3_INT3}, + .mode = UART3_MODE, + .ir_mode = UART3_IR, + .enabled = UART3_ENABLED, + .hardware_flow = UART3_HW_FLOW, + .cts_threshold = UART3_UCR4_CTSTL, + .dma_enabled = UART3_DMA_ENABLE, + .dma_rxbuf_size = UART3_DMA_RXBUFSIZE, + .rx_threshold = UART3_UFCR_RXTL, + .tx_threshold = UART3_UFCR_TXTL, + .shared = UART3_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART3_TX, + .dma_rx_id = MXC_DMA_UART3_RX, + .rxd_mux = MXC_UART_RXDMUX, + .ir_tx_inv = MXC_IRDA_TX_INV, + .ir_rx_inv = MXC_IRDA_RX_INV, + }, +#endif +#if UART4_ENABLED == 1 + [3] = { + .port = { + .membase = (void *)IO_ADDRESS(UART4_BASE_ADDR), + .mapbase = UART4_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART4_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 3, + }, + .ints_muxed = UART4_MUX_INTS, + .irqs = {UART4_INT2, UART4_INT3}, + .mode = UART4_MODE, + .ir_mode = UART4_IR, + .enabled = UART4_ENABLED, + .hardware_flow = UART4_HW_FLOW, + .cts_threshold = UART4_UCR4_CTSTL, + .dma_enabled = UART4_DMA_ENABLE, + .dma_rxbuf_size = UART4_DMA_RXBUFSIZE, + .rx_threshold = UART4_UFCR_RXTL, + .tx_threshold = UART4_UFCR_TXTL, + .shared = UART4_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART4_TX, + .dma_rx_id = MXC_DMA_UART4_RX, + .rxd_mux = MXC_UART_RXDMUX, + .ir_tx_inv = MXC_IRDA_TX_INV, + .ir_rx_inv = MXC_IRDA_RX_INV, + }, +#endif +#if UART5_ENABLED == 1 + [4] = { + .port = { + .membase = (void *)IO_ADDRESS(UART5_BASE_ADDR), + .mapbase = UART5_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART5_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 4, + }, + .ints_muxed = UART5_MUX_INTS, + .irqs = {UART5_INT2, UART5_INT3}, + .mode = UART5_MODE, + .ir_mode = UART5_IR, + .enabled = UART5_ENABLED, + .hardware_flow = UART5_HW_FLOW, + .cts_threshold = UART5_UCR4_CTSTL, + .dma_enabled = UART5_DMA_ENABLE, + .dma_rxbuf_size = UART5_DMA_RXBUFSIZE, + .rx_threshold = UART5_UFCR_RXTL, + .tx_threshold = UART5_UFCR_TXTL, + .shared = UART5_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART5_TX, + .dma_rx_id = MXC_DMA_UART5_RX, + .rxd_mux = MXC_UART_RXDMUX, + .ir_tx_inv = MXC_IRDA_TX_INV, + .ir_rx_inv = MXC_IRDA_RX_INV, + }, +#endif +}; + +static struct platform_device mxc_uart_device1 = { + .name = "mxcintuart", + .id = 0, + .dev = { + .platform_data = &mxc_ports[0], + }, +}; + +static struct platform_device mxc_uart_device2 = { + .name = "mxcintuart", + .id = 1, + .dev = { + .platform_data = &mxc_ports[1], + }, +}; + +#if UART3_ENABLED == 1 +static struct platform_device mxc_uart_device3 = { + .name = "mxcintuart", + .id = 2, + .dev = { + .platform_data = &mxc_ports[2], + }, +}; +#endif + +#if UART4_ENABLED == 1 +static struct platform_device mxc_uart_device4 = { + .name = "mxcintuart", + .id = 3, + .dev = { + .platform_data = &mxc_ports[3], + }, +}; +#endif + +#if UART5_ENABLED == 1 +static struct platform_device mxc_uart_device5 = { + .name = "mxcintuart", + .id = 4, + .dev = { + .platform_data = &mxc_ports[4], + }, +}; +#endif + +static int __init mxc_init_uart(void) +{ + /* Register all the MXC UART platform device structures */ + platform_device_register(&mxc_uart_device1); + platform_device_register(&mxc_uart_device2); + + /* Grab ownership of shared UARTs 3 and 4, only when enabled */ +#if UART3_ENABLED == 1 +#if UART3_DMA_ENABLE == 1 + spba_take_ownership(UART3_SHARED_PERI, (SPBA_MASTER_A | SPBA_MASTER_C)); +#else + spba_take_ownership(UART3_SHARED_PERI, SPBA_MASTER_A); +#endif /* UART3_DMA_ENABLE */ + platform_device_register(&mxc_uart_device3); +#endif /* UART3_ENABLED */ + +#if UART4_ENABLED == 1 + platform_device_register(&mxc_uart_device4); +#endif /* UART4_ENABLED */ + +#if UART5_ENABLED == 1 + platform_device_register(&mxc_uart_device5); +#endif /* UART5_ENABLED */ + return 0; +} + +#else +static int __init mxc_init_uart(void) +{ + return 0; +} +#endif + +arch_initcall(mxc_init_uart); diff --git a/arch/arm/mach-mx3/serial.h b/arch/arm/mach-mx3/serial.h new file mode 100644 index 000000000000..b2b0a05b9dbd --- /dev/null +++ b/arch/arm/mach-mx3/serial.h @@ -0,0 +1,175 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ARCH_ARM_MACH_MX3_SERIAL_H__ +#define __ARCH_ARM_MACH_MX3_SERIAL_H__ + +/*! + * @file mach-mx3/serial.h + * + * @ingroup MSL_MX31 + */ +#include <mach/mxc_uart.h> + +/* + * UART Chip level Configuration that a user may not have to edit. These + * configuration vary depending on how the UART module is integrated with + * the ARM core + */ +#define MXC_UART_NR 5 +/*! + * This option is used to set or clear the RXDMUXSEL bit in control reg 3. + * Certain platforms need this bit to be set in order to receive Irda data. + */ +#define MXC_UART_IR_RXDMUX 0x0004 +/*! + * This option is used to set or clear the RXDMUXSEL bit in control reg 3. + * Certain platforms need this bit to be set in order to receive UART data. + */ +#define MXC_UART_RXDMUX 0x0004 + +/* UART 1 configuration */ +/*! + * This option allows to choose either an interrupt-driven software controlled + * hardware flow control (set this option to 0) or hardware-driven hardware + * flow control (set this option to 1). + */ +/* UART used as wakeup source */ +#define UART1_HW_FLOW 0 +/*! + * This specifies the threshold at which the CTS pin is deasserted by the + * RXFIFO. Set this value in Decimal to anything from 0 to 32 for + * hardware-driven hardware flow control. Read the HW spec while specifying + * this value. When using interrupt-driven software controlled hardware + * flow control set this option to -1. + */ +#define UART1_UCR4_CTSTL 16 +/*! + * This is option to enable (set this option to 1) or disable DMA data transfer + */ +#define UART1_DMA_ENABLE 0 +/*! + * Specify the size of the DMA receive buffer. The minimum buffer size is 512 + * bytes. The buffer size should be a multiple of 256. + */ +#define UART1_DMA_RXBUFSIZE 1024 +/*! + * Specify the MXC UART's Receive Trigger Level. This controls the threshold at + * which a maskable interrupt is generated by the RxFIFO. Set this value in + * Decimal to anything from 0 to 32. Read the HW spec while specifying this + * value. + */ +#define UART1_UFCR_RXTL 16 +/*! + * Specify the MXC UART's Transmit Trigger Level. This controls the threshold at + * which a maskable interrupt is generated by the TxFIFO. Set this value in + * Decimal to anything from 0 to 32. Read the HW spec while specifying this + * value. + */ +#define UART1_UFCR_TXTL 16 +/* UART 2 configuration */ +#define UART2_HW_FLOW 0 +#define UART2_UCR4_CTSTL -1 +#define UART2_DMA_ENABLE 0 +#define UART2_DMA_RXBUFSIZE 512 +#define UART2_UFCR_RXTL 16 +#define UART2_UFCR_TXTL 16 +/* UART 3 configuration */ +#define UART3_HW_FLOW 1 +#define UART3_UCR4_CTSTL 16 +#define UART3_DMA_ENABLE 1 +#define UART3_DMA_RXBUFSIZE 1024 +#define UART3_UFCR_RXTL 16 +#define UART3_UFCR_TXTL 16 +/* UART 4 configuration */ +#define UART4_HW_FLOW 1 +#define UART4_UCR4_CTSTL 16 +#define UART4_DMA_ENABLE 0 +#define UART4_DMA_RXBUFSIZE 512 +#define UART4_UFCR_RXTL 16 +#define UART4_UFCR_TXTL 16 +/* UART 5 configuration */ +#define UART5_HW_FLOW 1 +#define UART5_UCR4_CTSTL 16 +#define UART5_DMA_ENABLE 0 +#define UART5_DMA_RXBUFSIZE 512 +#define UART5_UFCR_RXTL 16 +#define UART5_UFCR_TXTL 16 +/* + * UART Chip level Configuration that a user may not have to edit. These + * configuration vary depending on how the UART module is integrated with + * the ARM core + */ +/* + * Is the MUXED interrupt output sent to the ARM core + */ +#define INTS_NOTMUXED 0 +#define INTS_MUXED 1 +/* UART 1 configuration */ +/*! + * This define specifies whether the muxed ANDed interrupt line or the + * individual interrupts from the UART port is integrated with the ARM core. + * There exists a define like this for each UART port. Valid values that can + * be used are \b INTS_NOTMUXED or \b INTS_MUXED. + */ +#define UART1_MUX_INTS INTS_MUXED +/*! + * This define specifies the transmitter interrupt number or the interrupt + * number of the ANDed interrupt in case the interrupts are muxed. There exists + * a define like this for each UART port. + */ +#define UART1_INT1 MXC_INT_UART1 +/*! + * This define specifies the receiver interrupt number. If the interrupts of + * the UART are muxed, then we specify here a dummy value -1. There exists a + * define like this for each UART port. + */ +#define UART1_INT2 -1 +/*! + * This specifies the master interrupt number. If the interrupts of the UART + * are muxed, then we specify here a dummy value of -1. There exists a define + * like this for each UART port. + */ +#define UART1_INT3 -1 +/*! + * This specifies if the UART is a shared peripheral. It holds the shared + * peripheral number if it is shared or -1 if it is not shared. There exists + * a define like this for each UART port. + */ +#define UART1_SHARED_PERI -1 +/* UART 2 configuration */ +#define UART2_MUX_INTS INTS_MUXED +#define UART2_INT1 MXC_INT_UART2 +#define UART2_INT2 -1 +#define UART2_INT3 -1 +#define UART2_SHARED_PERI -1 +/* UART 3 configuration */ +#define UART3_MUX_INTS INTS_MUXED +#define UART3_INT1 MXC_INT_UART3 +#define UART3_INT2 -1 +#define UART3_INT3 -1 +#define UART3_SHARED_PERI SPBA_UART3 +/* UART 4 configuration */ +#define UART4_MUX_INTS INTS_MUXED +#define UART4_INT1 MXC_INT_UART4 +#define UART4_INT2 -1 +#define UART4_INT3 -1 +#define UART4_SHARED_PERI -1 +/* UART 5 configuration */ +#define UART5_MUX_INTS INTS_MUXED +#define UART5_INT1 MXC_INT_UART5 +#define UART5_INT2 -1 +#define UART5_INT3 -1 +#define UART5_SHARED_PERI -1 + +#endif /* __ARCH_ARM_MACH_MX3_SERIAL_H__ */ diff --git a/arch/arm/mach-mx3/system.c b/arch/arm/mach-mx3/system.c new file mode 100644 index 000000000000..932bf99322d9 --- /dev/null +++ b/arch/arm/mach-mx3/system.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 1999 ARM Limited + * Copyright (C) 2000 Deep Blue Solutions Ltd + * Copyright 2004-2009 Freescale Semiconductor, Inc. All 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 <linux/clk.h> +#include <linux/io.h> +#include <mach/hardware.h> +#include <asm/proc-fns.h> +#include <asm/system.h> +#include <mach/clock.h> +#include "crm_regs.h" + +/*! + * @defgroup MSL_MX31 i.MX31 Machine Specific Layer (MSL) + */ + +/*! + * @file mach-mx3/system.c + * @brief This file contains idle and reset functions. + * + * @ingroup MSL_MX31 + */ + +static int clks_initialized = 0; +static struct clk *sdma_clk, *mbx_clk, *ipu_clk, *mpeg_clk, *vpu_clk, *usb_clk, + *rtic_clk, *nfc_clk, *emi_clk; + +extern int mxc_jtag_enabled; + +/*! + * This function puts the CPU into idle mode. It is called by default_idle() + * in process.c file. + */ +void arch_idle(void) +{ + int emi_gated_off = 0; + + /* + * This should do all the clock switching + * and wait for interrupt tricks. + */ + if (!mxc_jtag_enabled) { + if (clks_initialized == 0) { + clks_initialized = 1; + sdma_clk = clk_get(NULL, "sdma_ahb_clk"); + ipu_clk = clk_get(NULL, "ipu_clk"); + if (cpu_is_mx31()) { + mpeg_clk = clk_get(NULL, "mpeg4_clk"); + mbx_clk = clk_get(NULL, "mbx_clk"); + } else { + vpu_clk = clk_get(NULL, "vpu_clk"); + } + usb_clk = clk_get(NULL, "usb_ahb_clk"); + rtic_clk = clk_get(NULL, "rtic_clk"); + nfc_clk = clk_get(NULL, "nfc_clk"); + emi_clk = clk_get(NULL, "emi_clk"); + } + + if ((clk_get_usecount(sdma_clk) == 0) + && (clk_get_usecount(ipu_clk) <= 1) + && (clk_get_usecount(usb_clk) == 0) + && (clk_get_usecount(rtic_clk) == 0) + && (clk_get_usecount(mpeg_clk) == 0) + && (clk_get_usecount(mbx_clk) == 0) + && (clk_get_usecount(nfc_clk) == 0) + && (clk_get_usecount(vpu_clk) == 0)) { + emi_gated_off = 1; + clk_disable(emi_clk); + } + + cpu_do_idle(); + if (emi_gated_off == 1) { + clk_enable(emi_clk); + } + } +} + +/* + * This function resets the system. It is called by machine_restart(). + * + * @param mode indicates different kinds of resets + */ +void arch_reset(char mode) +{ + /* Assert SRS signal */ + mxc_wd_reset(); +} diff --git a/arch/arm/mach-mx3/usb.h b/arch/arm/mach-mx3/usb.h new file mode 100644 index 000000000000..5a7170367773 --- /dev/null +++ b/arch/arm/mach-mx3/usb.h @@ -0,0 +1,116 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +extern int usbotg_init(struct platform_device *pdev); +extern void usbotg_uninit(struct fsl_usb2_platform_data *pdata); +extern int gpio_usbotg_fs_active(void); +extern void gpio_usbotg_fs_inactive(void); +extern int gpio_usbotg_hs_active(void); +extern void gpio_usbotg_hs_inactive(void); +extern struct platform_device *host_pdev_register(struct resource *res, + int n_res, struct fsl_usb2_platform_data *config); + +extern int fsl_usb_host_init(struct platform_device *pdev); +extern void fsl_usb_host_uninit(struct fsl_usb2_platform_data *pdata); +extern int gpio_usbh1_active(void); +extern void gpio_usbh1_inactive(void); +extern int gpio_usbh2_active(void); +extern void gpio_usbh2_inactive(void); + +/* + * Determine which platform_data struct to use for the DR controller, + * based on which transceiver is configured. + * PDATA is a pointer to it. + */ +#if defined(CONFIG_ISP1504_MXC) +static struct fsl_usb2_platform_data __maybe_unused dr_1504_config; +#define PDATA (&dr_1504_config) +#elif defined(CONFIG_ISP1301_MXC) +static struct fsl_usb2_platform_data __maybe_unused dr_1301_config; +#define PDATA (&dr_1301_config) +#elif defined(CONFIG_MC13783_MXC) +static struct fsl_usb2_platform_data __maybe_unused dr_13783_config; +#define PDATA (&dr_13783_config) +#endif + + +/* + * Used to set pdata->operating_mode before registering the platform_device. + * If OTG is configured, the controller operates in OTG mode, + * otherwise it's either host or device. + */ +#ifdef CONFIG_USB_OTG +#define DR_UDC_MODE FSL_USB2_DR_OTG +#define DR_HOST_MODE FSL_USB2_DR_OTG +#else +#define DR_UDC_MODE FSL_USB2_DR_DEVICE +#define DR_HOST_MODE FSL_USB2_DR_HOST +#endif + + +#ifdef CONFIG_USB_EHCI_ARC_OTG +static inline void dr_register_host(struct resource *r, int rs) +{ + PDATA->operating_mode = DR_HOST_MODE; + host_pdev_register(r, rs, PDATA); +} +#else +static inline void dr_register_host(struct resource *r, int rs) +{ +} +#endif + +#ifdef CONFIG_USB_GADGET_ARC +static struct platform_device dr_udc_device; + +static inline void dr_register_udc(void) +{ + PDATA->operating_mode = DR_UDC_MODE; + dr_udc_device.dev.platform_data = PDATA; + + if (platform_device_register(&dr_udc_device)) + printk(KERN_ERR "usb: can't register DR gadget\n"); + else + printk(KERN_INFO "usb: DR gadget (%s) registered\n", + PDATA->transceiver); +} +#else +static inline void dr_register_udc(void) +{ +} +#endif + +#ifdef CONFIG_USB_OTG +static struct platform_device dr_otg_device; + +/* + * set the proper operating_mode and + * platform_data pointer, then register the + * device. + */ +static inline void dr_register_otg(void) +{ + PDATA->operating_mode = FSL_USB2_DR_OTG; + dr_otg_device.dev.platform_data = PDATA; + + if (platform_device_register(&dr_otg_device)) + printk(KERN_ERR "usb: can't register otg device\n"); + else + printk(KERN_INFO "usb: DR OTG registered\n"); +} +#else +static inline void dr_register_otg(void) +{ +} +#endif diff --git a/arch/arm/mach-mx3/usb_dr.c b/arch/arm/mach-mx3/usb_dr.c new file mode 100644 index 000000000000..7331463173e9 --- /dev/null +++ b/arch/arm/mach-mx3/usb_dr.c @@ -0,0 +1,129 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <mach/hardware.h> +#include <mach/arc_otg.h> +#include "usb.h" + +/* + * platform data structs + * - Which one to use is determined by CONFIG options in usb.h + * - operating_mode plugged at run time + */ +static struct fsl_usb2_platform_data __maybe_unused dr_13783_config = { + .name = "DR", + .platform_init = usbotg_init, + .platform_uninit = usbotg_uninit, + .phy_mode = FSL_USB2_PHY_SERIAL, + .power_budget = 500, /* 500 mA max power */ + .gpio_usb_active = gpio_usbotg_fs_active, + .gpio_usb_inactive = gpio_usbotg_fs_inactive, + .transceiver = "mc13783", +}; + +static struct fsl_usb2_platform_data __maybe_unused dr_1301_config = { + .name = "DR", + .platform_init = usbotg_init, + .platform_uninit = usbotg_uninit, + .phy_mode = FSL_USB2_PHY_SERIAL, + .power_budget = 150, /* 150 mA max power */ + .gpio_usb_active = gpio_usbotg_fs_active, + .gpio_usb_inactive = gpio_usbotg_fs_inactive, + .transceiver = "isp1301", +}; + +static struct fsl_usb2_platform_data __maybe_unused dr_1504_config = { + .name = "DR", + .platform_init = usbotg_init, + .platform_uninit = usbotg_uninit, + .phy_mode = FSL_USB2_PHY_ULPI, + .power_budget = 150, /* 150 mA max power */ + .gpio_usb_active = gpio_usbotg_hs_active, + .gpio_usb_inactive = gpio_usbotg_hs_inactive, + .transceiver = "isp1504", +}; + + +/* + * resources + */ +static struct resource otg_resources[] = { + [0] = { + .start = (u32)(USB_OTGREGS_BASE), + .end = (u32)(USB_OTGREGS_BASE + 0x1ff), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_USB3, + .flags = IORESOURCE_IRQ, + }, +}; + + +static u64 dr_udc_dmamask = ~(u32) 0; +static void dr_udc_release(struct device *dev) +{ +} + +static u64 dr_otg_dmamask = ~(u32) 0; +static void dr_otg_release(struct device *dev) +{ +} + +/* + * platform device structs + * dev.platform_data field plugged at run time + */ +static struct platform_device __maybe_unused dr_udc_device = { + .name = "fsl-usb2-udc", + .id = -1, + .dev = { + .release = dr_udc_release, + .dma_mask = &dr_udc_dmamask, + .coherent_dma_mask = 0xffffffff, + }, + .resource = otg_resources, + .num_resources = ARRAY_SIZE(otg_resources), +}; + +static struct platform_device __maybe_unused dr_otg_device = { + .name = "fsl-usb2-otg", + .id = -1, + .dev = { + .release = dr_otg_release, + .dma_mask = &dr_otg_dmamask, + .coherent_dma_mask = 0xffffffff, + }, + .resource = otg_resources, + .num_resources = ARRAY_SIZE(otg_resources), +}; + +static int __init usb_dr_init(void) +{ + pr_debug("%s: \n", __func__); + + dr_register_otg(); + dr_register_host(otg_resources, ARRAY_SIZE(otg_resources)); + dr_register_udc(); +#ifdef CONFIG_USB_GADGET_WAKE_UP + /* set udc may and should wakeup */ + device_init_wakeup(&(dr_udc_device.dev), 1); +#endif + return 0; +} + +module_init(usb_dr_init); diff --git a/arch/arm/mach-mx3/usb_h1.c b/arch/arm/mach-mx3/usb_h1.c new file mode 100644 index 000000000000..1119ba6edd06 --- /dev/null +++ b/arch/arm/mach-mx3/usb_h1.c @@ -0,0 +1,54 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <mach/arc_otg.h> +#include <mach/hardware.h> +#include "usb.h" + +static struct fsl_usb2_platform_data usbh1_config = { + .name = "Host 1", + .platform_init = fsl_usb_host_init, + .platform_uninit = fsl_usb_host_uninit, + .operating_mode = FSL_USB2_MPH_HOST, + .phy_mode = FSL_USB2_PHY_SERIAL, + .power_budget = 500, /* 500 mA max power */ + .gpio_usb_active = gpio_usbh1_active, + .gpio_usb_inactive = gpio_usbh1_inactive, + .transceiver = "serial", +}; + +static struct resource usbh1_resources[] = { + [0] = { + .start = (u32) (USB_H1REGS_BASE), + .end = (u32) (USB_H1REGS_BASE + 0x1ff), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_USB1, + .flags = IORESOURCE_IRQ, + }, +}; + +static int __init usbh1_init(void) +{ + pr_debug("%s: \n", __func__); + + host_pdev_register(usbh1_resources, ARRAY_SIZE(usbh1_resources), + &usbh1_config); + return 0; +} +module_init(usbh1_init); diff --git a/arch/arm/mach-mx3/usb_h2.c b/arch/arm/mach-mx3/usb_h2.c new file mode 100644 index 000000000000..b95b06866c47 --- /dev/null +++ b/arch/arm/mach-mx3/usb_h2.c @@ -0,0 +1,70 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <asm/mach-types.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/usb/fsl_xcvr.h> +#include <linux/regulator/consumer.h> +#include <mach/hardware.h> +#include <mach/arc_otg.h> +#include "usb.h" + +static struct fsl_usb2_platform_data usbh2_config = { + .name = "Host 2", + .platform_init = fsl_usb_host_init, + .platform_uninit = fsl_usb_host_uninit, + .operating_mode = FSL_USB2_MPH_HOST, + .phy_mode = FSL_USB2_PHY_ULPI, + .power_budget = 500, /* 500 mA max power */ + .gpio_usb_active = gpio_usbh2_active, + .gpio_usb_inactive = gpio_usbh2_inactive, + .transceiver = "isp1504", +}; + +static struct resource usbh2_resources[] = { + [0] = { + .start = (u32) (USB_H2REGS_BASE), + .end = (u32) (USB_H2REGS_BASE + 0x1ff), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_USB2, + .flags = IORESOURCE_IRQ, + }, +}; + +static int __init usbh2_init(void) +{ + pr_debug("%s: \n", __func__); + + if (machine_is_mx31_3ds()) { + struct regulator *usbh2_regux; + usbh2_config.xcvr_pwr = + kmalloc(sizeof(struct fsl_xcvr_power), GFP_KERNEL); + if (!(usbh2_config.xcvr_pwr)) + return -ENOMEM; + + usbh2_regux = regulator_get(NULL, "GPO1"); + usbh2_config.xcvr_pwr->regu1 = usbh2_regux; + usbh2_regux = regulator_get(NULL, "GPO3"); + usbh2_config.xcvr_pwr->regu2 = usbh2_regux; + } + + host_pdev_register(usbh2_resources, ARRAY_SIZE(usbh2_resources), + &usbh2_config); + return 0; +} +module_init(usbh2_init); diff --git a/arch/arm/mach-mx35/Kconfig b/arch/arm/mach-mx35/Kconfig new file mode 100644 index 000000000000..6fdc1dd45919 --- /dev/null +++ b/arch/arm/mach-mx35/Kconfig @@ -0,0 +1,111 @@ +menu "MX35 Options" + depends on ARCH_MX35 + +config FORCE_MAX_ZONEORDER + int "MAX_ORDER" + default "13" + +config MX35_OPTIONS + bool + default y + select CPU_V6 + select ARM_ERRATA_364296 + select ARM_ERRATA_411920 + select CACHE_L2X0 + select OUTER_CACHE + select USB_ARCH_HAS_EHCI + select ARCH_HAS_EVTMON + select ARCH_HAS_RNGC + +config MACH_MX35_3DS + bool "Support MX35 3STACK platforms" + default y + select MXC_PSEUDO_IRQS if MXC_PMIC_MC9SDZ60 + help + Include support for MX35 3STACK platform. This includes specific + configurations for the board and its peripherals. + +config MACH_MX35EVB + bool "Support MX35EVB platforms" + default n + help + Include support for MX35EVB platform. This includes specific + configurations for the board and its peripherals. + +config MX35_DOZE_DURING_IDLE + bool "Enter Doze mode during idle" + help + Turning on this option will put the CPU into Doze mode during idle. + The default is to enter Wait mode during idle. Doze mode during + idle will save additional power over Wait mode. + +config MXC_SDMA_API + bool "Use SDMA API" + default y + help + This selects the Freescale MXC SDMA API. + If unsure, say N. + +menu "SDMA options" + depends on MXC_SDMA_API + +config SDMA_IRAM + bool "Use Internal RAM for SDMA transfer" + default n + help + Support Internal RAM as SDMA buffer or control structures + +config SDMA_IRAM_SIZE + hex "Reserved bytes of IRAM for SDMA (0x800-0x1000)" + range 0x800 0x1000 + depends on SDMA_IRAM + default "0x1000" + help + Set the size of IRAM for SDMA. It must be a multiple of 512bytes. +endmenu + +config ARCH_MXC_HAS_NFC_V2 + bool "MXC NFC Hardware Version 2" + depends on ARCH_MX35 + default y + help + This selects the Freescale MXC Nand Flash Controller Hardware Version 3 + If unsure, say N. + +config ARCH_MXC_HAS_NFC_V2_1 + bool "MXC NFC Hardware Version 2.1" + depends on ARCH_MXC_HAS_NFC_V2 + default y + help + This selects the Freescale MXC Nand Flash Controller Hardware Version 2.1 + If unsure, say N. + +menu "Device options" + +config I2C_MXC_SELECT1 + bool "Enable I2C1 module" + default y + depends on I2C_MXC + help + Enable MX35 I2C1 module. + +config I2C_MXC_SELECT2 + bool "Enable I2C2 module" + default n + depends on I2C_MXC + help + Enable MX35 I2C2 module. + +config I2C_MXC_SELECT3 + bool "Enable I2C3 module" + default n + depends on I2C_MXC + help + Enable MX35 I2C3 module. + +endmenu + +config MXC_PSEUDO_IRQS + bool + +endmenu diff --git a/arch/arm/mach-mx35/Makefile b/arch/arm/mach-mx35/Makefile new file mode 100644 index 000000000000..39b152d9ad21 --- /dev/null +++ b/arch/arm/mach-mx35/Makefile @@ -0,0 +1,19 @@ +# +# Makefile for the linux kernel. +# + +# Object file lists. + +obj-y := system.o iomux.o cpu.o mm.o clock.o devices.o serial.o +obj-$(CONFIG_MXC_SDMA_API) += dma.o +obj-$(CONFIG_MACH_MX35_3DS) += mx35_3stack.o mx35_3stack_gpio.o mx35_3stack_cpld.o dvfs.o mx35_3stack_pmic_mc13892.o mx35_3stack_pmic_mc9s08dz60.o +obj-$(CONFIG_MACH_MX35EVB) += mx35evb.o mx35evb_cpld.o mx35evb_gpio.o + +obj-$(CONFIG_MXC_PSEUDO_IRQS) += mx35_3stack_irq.o +obj-$(CONFIG_PM) += pm.o + +obj-$(CONFIG_USB_EHCI_ARC_H2) += usb_h2.o + +ifneq ($(strip $(CONFIG_USB_GADGET_ARC) $(CONFIG_USB_EHCI_ARC_OTG)),) + obj-y += usb_dr.o +endif diff --git a/arch/arm/mach-mx35/Makefile.boot b/arch/arm/mach-mx35/Makefile.boot new file mode 100644 index 000000000000..198d92d5e463 --- /dev/null +++ b/arch/arm/mach-mx35/Makefile.boot @@ -0,0 +1,9 @@ +ifeq ($(CONFIG_MACH_MX35EVB), y) + zreladdr-y := 0x90008000 +params_phys-y := 0x90000100 +initrd_phys-y := 0x90800000 +else + zreladdr-y := 0x80008000 +params_phys-y := 0x80000100 +initrd_phys-y := 0x80800000 +endif diff --git a/arch/arm/mach-mx35/board-mx35_3stack.h b/arch/arm/mach-mx35/board-mx35_3stack.h new file mode 100644 index 000000000000..231ea5af91a3 --- /dev/null +++ b/arch/arm/mach-mx35/board-mx35_3stack.h @@ -0,0 +1,200 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ASM_ARCH_MXC_BOARD_MX35_3STACK_H__ +#define __ASM_ARCH_MXC_BOARD_MX35_3STACK_H__ + +#ifdef CONFIG_MACH_MX35_3DS + +/*! + * @defgroup BRDCFG_MX35 Board Configuration Options + * @ingroup MSL_MX35 + */ + +/*! + * @file mach-mx35/board-mx35_3stack.h + * + * @brief This file contains all the board level configuration options. + * + * It currently hold the options defined for MX35 3STACK Platform. + * + * @ingroup BRDCFG_MX35 + */ + +/* + * Include Files + */ +#include <mach/mxc_uart.h> + +/*! + * @name MXC UART EVB board level configurations + */ +/*! @{ */ +/*! + * Specifies if the Irda transmit path is inverting + */ +#define MXC_IRDA_TX_INV 0 +/*! + * Specifies if the Irda receive path is inverting + */ +#define MXC_IRDA_RX_INV 0 + +/* UART 1 configuration */ +/*! + * This define specifies if the UART port is configured to be in DTE or + * DCE mode. There exists a define like this for each UART port. Valid + * values that can be used are \b MODE_DTE or \b MODE_DCE. + */ +#define UART1_MODE MODE_DCE +/*! + * This define specifies if the UART is to be used for IRDA. There exists a + * define like this for each UART port. Valid values that can be used are + * \b IRDA or \b NO_IRDA. + */ +#define UART1_IR NO_IRDA +/*! + * This define is used to enable or disable a particular UART port. If + * disabled, the UART will not be registered in the file system and the user + * will not be able to access it. There exists a define like this for each UART + * port. Specify a value of 1 to enable the UART and 0 to disable it. + */ +#define UART1_ENABLED 1 +/*! @} */ +/* UART 2 configuration */ +#define UART2_MODE MODE_DTE +#define UART2_IR NO_IRDA +#define UART2_ENABLED 1 + +/* UART 3 configuration */ +#define UART3_MODE MODE_DTE +#define UART3_IR NO_IRDA +#define UART3_ENABLED 1 + +#define MXC_LL_UART_PADDR UART1_BASE_ADDR +#define MXC_LL_UART_VADDR AIPS1_IO_ADDRESS(UART1_BASE_ADDR) + +#define MXC_PSEUDO_PARENT MXC_INT_FORCE + +enum { + MCU_INT_HEADPHONE = 0, + MCU_INT_GPS, + MCU_INT_SD1_CD, + MCU_INT_SD1_WP, + MCU_INT_SD2_CD, + MCU_INT_SD2_WP, + MCU_INT_POWER_KEY, + MCU_INT_RTC, + MCU_INT_TS_ADC, + MCU_INT_KEYPAD, +}; + +#define MXC_PSEUDO_IRQ_HEADPHONE (MXC_PSEUDO_IO_BASE + MCU_INT_HEADPHONE) +#define MXC_PSEUDO_IRQ_GPS (MXC_PSEUDO_IO_BASE + MCU_INT_GPS) +#define MXC_PSEUDO_IRQ_SD1_CD (MXC_PSEUDO_IO_BASE + MCU_INT_SD1_CD) +#define MXC_PSEUDO_IRQ_SD1_WP (MXC_PSEUDO_IO_BASE + MCU_INT_SD1_WP) +#define MXC_PSEUDO_IRQ_SD2_CD (MXC_PSEUDO_IO_BASE + MCU_INT_SD2_CD) +#define MXC_PSEUDO_IRQ_SD2_WP (MXC_PSEUDO_IO_BASE + MCU_INT_SD2_WP) +#define MXC_PSEUDO_IRQ_POWER_KEY (MXC_PSEUDO_IO_BASE + MCU_INT_POWER_KEY) +#define MXC_PSEUDO_IRQ_KEYPAD (MXC_PSEUDO_IO_BASE + MCU_INT_KEYPAD) +#define MXC_PSEUDO_IRQ_RTC (MXC_PSEUDO_IO_BASE + MCU_INT_RTC) +#define MXC_PSEUDO_IRQ_TS_ADC (MXC_PSEUDO_IO_BASE + MCU_INT_TS_ADC) + +/*! + * @name debug board parameters + */ +/*! @{ */ +/*! + * Base address of debug board + */ +#define DEBUG_BASE_ADDRESS CS5_BASE_ADDR + +/* External ethernet LAN9217 base address */ +#define LAN9217_BASE_ADDR DEBUG_BASE_ADDRESS + +/* External UART */ +#define UARTA_BASE_ADDR (DEBUG_BASE_ADDRESS + 0x08000) +#define UARTB_BASE_ADDR (DEBUG_BASE_ADDRESS + 0x10000) + +#define BOARD_IO_ADDR (DEBUG_BASE_ADDRESS + 0x20000) + +/* LED switchs */ +#define LED_SWITCH_REG 0x00 +/* buttons */ +#define SWITCH_BUTTON_REG 0x08 +/* status, interrupt */ +#define INTR_STATUS_REG 0x10 +#define INTR_RESET_REG 0x20 +/*CPLD configuration*/ +#define CONFIG1_REG 0x28 +#define CONFIG2_REG 0x30 +/*interrupt mask */ +#define INTR_MASK_REG 0x38 + +/* magic word for debug CPLD */ +#define MAGIC_NUMBER1_REG 0x40 +#define MAGIC_NUMBER2_REG 0x48 +/* CPLD code version */ +#define CPLD_CODE_VER_REG 0x50 +/* magic word for debug CPLD */ +#define MAGIC3_NUMBER3_REG 0x58 +/* module reset register*/ +#define CONTROL_REG 0x60 +/* CPU ID and Personality ID*/ +#define IDENT_REG 0x68 + +/* For interrupts like xuart, enet etc */ +#define EXPIO_PARENT_INT MX35_PIN_GPIO1_1 + +#define EXPIO_INT_ENET_INT (MXC_BOARD_IRQ_START + 0) +#define EXPIO_INT_XUARTA_INT (MXC_BOARD_IRQ_START + 1) +#define EXPIO_INT_XUARTB_INT (MXC_BOARD_IRQ_START + 2) +#define EXPIO_INT_BUTTONA_INT (MXC_BOARD_IRQ_START + 3) +#define EXPIO_INT_BUTTONB_INT (MXC_BOARD_IRQ_START + 4) + +/*! This is System IRQ used by LAN9217 for interrupt generation taken + * from platform.h + */ +#define LAN9217_IRQ EXPIO_INT_ENET_INT + +/*! This is base virtual address of debug board*/ +extern unsigned int mx35_3stack_board_io; + +#define MXC_BD_LED1 (1) +#define MXC_BD_LED2 (1 << 1) +#define MXC_BD_LED3 (1 << 2) +#define MXC_BD_LED4 (1 << 3) +#define MXC_BD_LED5 (1 << 4) +#define MXC_BD_LED6 (1 << 5) +#define MXC_BD_LED7 (1 << 6) +#define MXC_BD_LED8 (1 << 7) +#define MXC_BD_LED_ON(led) +#define MXC_BD_LED_OFF(led) + +/*! @} */ + +#define AHB_FREQ 133000000 +#define IPG_FREQ 66500000 + +extern void mx35_3stack_gpio_init(void) __init; +extern void gpio_tsc_active(void); +extern void gpio_tsc_inactive(void); +extern unsigned int sdhc_get_card_det_status(struct device *dev); +extern int sdhc_write_protect(struct device *dev); +extern void gpio_can_active(int id); +extern void gpio_can_inactive(int id); +extern struct flexcan_platform_data flexcan_data[]; +extern int __init mx35_3stack_init_mc13892(void); +extern int __init mx35_3stack_init_mc9s08dz60(void); + +#endif /* CONFIG_MACH_MX35_3DS */ +#endif /* __ASM_ARCH_MXC_BOARD_MX35_3STACK_H__ */ diff --git a/arch/arm/mach-mx35/clock.c b/arch/arm/mach-mx35/clock.c new file mode 100644 index 000000000000..5abc3cceba7e --- /dev/null +++ b/arch/arm/mach-mx35/clock.c @@ -0,0 +1,1932 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <mach/common.h> +#include <mach/hardware.h> +#include <mach/clock.h> +#include <asm/div64.h> + +#include "crm_regs.h" + +#define PRE_DIV_MIN_FREQ 10000000 /* Minimum Frequency after Predivider */ +#define PROPAGATE_RATE_DIS 2 + +struct timer_list dptcen_timer; +static int cpu_curr_wp; +static struct cpu_wp *cpu_wp_tbl; +static int cpu_wp_nr; +static int cpu_wp_offset; + +static struct clk mcu_pll_clk; +static struct clk peri_pll_clk; +static struct clk ipg_clk; +static struct clk ckih_clk; +static struct clk ckie_clk; +static struct clk ahb_clk; +static struct clk cpu_clk; + +#define CLK_CODE(arm, ahb, sel) (((arm) << 16) + ((ahb) << 8) + (sel)) +#define CLK_CODE_ARM(c) (((c) >> 16) & 0xFF) +#define CLK_CODE_AHB(c) (((c) >> 8) & 0xFF) +#define CLK_CODE_PATH(c) ((c) & 0xFF) + +static int __get_arm_div(unsigned long pdr0, int *fi, int *fd); + +static int g_clk_mux_auto[8] = { + CLK_CODE(1, 3, 0), CLK_CODE(1, 2, 1), CLK_CODE(2, 1, 1), -1, + CLK_CODE(1, 6, 0), CLK_CODE(1, 4, 1), CLK_CODE(2, 2, 1), -1, +}; + +static int g_clk_mux_consumer[16] = { + CLK_CODE(1, 4, 0), CLK_CODE(1, 3, 1), CLK_CODE(2, 2, 0), -1, + -1, -1, CLK_CODE(4, 1, 0), CLK_CODE(1, 5, 0), + CLK_CODE(1, 8, 0), CLK_CODE(1, 6, 1), CLK_CODE(2, 4, 0), -1, + -1, -1, CLK_CODE(4, 2, 0), -1, +}; + +static int g_hsp_div_table[3][16] = { + {4, 3, 2, -1, -1, -1, 1, 5, 4, 3, 2, -1, -1, -1, 1, -1}, + {-1, -1, -1, -1, -1, -1, -1, -1, 8, 6, 4, -1, -1, -1, 2, -1}, + {3, -1, -1, -1, -1, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1}, +}; + +static void __calc_dividers(u32 div, u32 *pre, u32 *post, u32 base) +{ + u32 min_pre, temp_pre, old_err, err; + min_pre = (div - 1) / base + 1; + old_err = 8; + for (temp_pre = 8; temp_pre >= min_pre; temp_pre--) { + if (div > (temp_pre * base)) + break; + if (div < (temp_pre * temp_pre)) + continue; + err = div % temp_pre; + if (err == 0) { + *pre = temp_pre; + break; + } + err = temp_pre - err; + if (err < old_err) { + old_err = err; + *pre = temp_pre; + } + } + *post = (div + *pre - 1) / *pre; +} + +static void __calc_pre_post_dividers(u32 div, u32 *pre, u32 *post) +{ + if (div >= 512) { + *pre = 8; + *post = 64; + } else if (div >= 64) { + __calc_dividers(div, pre, post, 64); + } else if (div <= 8) { + *pre = div; + *post = 1; + } else { + *pre = 1; + *post = div; + } +} + +static void __calc_two_dividers(u32 div, u32 *pre, u32 *post) +{ + if (div >= 64) { + *pre = *post = 8; + } else if (div > 8) { + __calc_dividers(div, pre, post, 8); + } else { + *pre = 1; + *post = div; + } +} + +static unsigned long _clk_per_post_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 pre, post; + u32 div = clk->parent->rate / rate; + if (clk->parent->rate % rate) + div++; + + __calc_pre_post_dividers(div, &pre, &post); + + return clk->parent->rate / (pre * post); +} + +static unsigned long _clk_round_rate(struct clk *clk, unsigned long rate) +{ + u32 pre, post; + u32 div = clk->parent->rate / rate; + if (clk->parent->rate % rate) + div++; + + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + __calc_two_dividers(div, &pre, &post); + return clk->parent->rate / (pre * post); + } else + return clk->parent->rate / div; +} + +static int __switch_cpu_wp(struct clk *clk, unsigned long rate) +{ + int i; + u32 reg_value; + if (cpu_wp_tbl[cpu_curr_wp].cpu_rate < rate) { + for (i = cpu_curr_wp + 2; i < cpu_wp_nr; i += 2) { + if (rate == cpu_wp_tbl[i].cpu_rate) + goto found; + } + return -EINVAL; + } else { + for (i = cpu_curr_wp - 2; i >= 0; i -= 2) { + if (rate == cpu_wp_tbl[i].cpu_rate) + goto found; + } + return -EINVAL; + } + found: + reg_value = __raw_readl(MXC_CCM_PDR0); + reg_value = (reg_value & ~(MXC_CCM_PDR0_CON_MUX_DIV_MASK | + MXC_CCM_PDR0_AUTO_MUX_DIV_MASK)) | + cpu_wp_tbl[i].pdr0_reg; + __raw_writel(reg_value, MXC_CCM_PDR0); + + if (cpu_wp_tbl[i].pll_rate != cpu_wp_tbl[cpu_curr_wp].pll_rate) + clk_set_rate(clk->parent, cpu_wp_tbl[i].pll_rate); + cpu_curr_wp = i; + clk->rate = rate; + return 0; +} + +static int __switch_cpu_rate(struct clk *clk, unsigned long rate) +{ + int prev; + unsigned long tmp; + int arm_div, fi, fd, start, end; + u32 reg_value; + + if (cpu_wp_tbl[cpu_curr_wp].cpu_rate < rate) { + start = cpu_curr_wp + 2; + end = cpu_wp_nr; + prev = cpu_curr_wp; + } else { + start = cpu_wp_offset + 2; + end = cpu_curr_wp; + prev = cpu_wp_offset; + } + while (start < end) { + arm_div = __get_arm_div(cpu_wp_tbl[start].pdr0_reg, &fi, &fd); + tmp = (mcu_pll_clk.rate * fi) / (arm_div * fd); + if (tmp == rate) { + prev = start; + break; + } + if (tmp < rate) { + if (prev < start) + prev = start; + } else { + break; + } + start += 2; + } + if (start >= end) + return -EINVAL; + + if (prev == cpu_curr_wp) + return 0; + + reg_value = __raw_readl(MXC_CCM_PDR0); + reg_value = (reg_value & ~(MXC_CCM_PDR0_CON_MUX_DIV_MASK | + MXC_CCM_PDR0_AUTO_MUX_DIV_MASK)) | + cpu_wp_tbl[prev].pdr0_reg; + __raw_writel(reg_value, MXC_CCM_PDR0); + + cpu_curr_wp = prev; + clk->rate = rate; + return 0; +} + +static int __get_arm_div(unsigned long pdr0, int *fi, int *fd) +{ + int *pclk_mux; + if ((pdr0 & MXC_CCM_PDR0_AUTO_CON) + || (cpu_is_mx35_rev(CHIP_REV_2_0) >= 1)) + pclk_mux = + g_clk_mux_consumer + + ((pdr0 & MXC_CCM_PDR0_CON_MUX_DIV_MASK) >> + MXC_CCM_PDR0_CON_MUX_DIV_OFFSET); + else { + pclk_mux = g_clk_mux_auto + + ((pdr0 & MXC_CCM_PDR0_AUTO_MUX_DIV_MASK) >> + MXC_CCM_PDR0_AUTO_MUX_DIV_OFFSET); + } + + if ((*pclk_mux) == -1) { + BUG(); + return -EINVAL; + } + + if (fi && fd) { + if (!CLK_CODE_PATH(*pclk_mux)) { + *fi = *fd = 1; + return CLK_CODE_ARM(*pclk_mux); + } + if ((pdr0 & MXC_CCM_PDR0_AUTO_CON) + || (cpu_is_mx35_rev(CHIP_REV_2_0) >= 1)) { + *fi = 3; + *fd = 4; + } else { + *fi = 2; + *fd = 3; + } + } + return CLK_CODE_ARM(*pclk_mux); +} + +static int __get_ahb_div(unsigned long pdr0) +{ + int *pclk_mux; + if ((pdr0 & MXC_CCM_PDR0_AUTO_CON) + || (cpu_is_mx35_rev(CHIP_REV_2_0) >= 1)) { + pclk_mux = + g_clk_mux_consumer + + ((pdr0 & MXC_CCM_PDR0_CON_MUX_DIV_MASK) >> + MXC_CCM_PDR0_CON_MUX_DIV_OFFSET); + } else { + pclk_mux = g_clk_mux_auto + + ((pdr0 & MXC_CCM_PDR0_AUTO_MUX_DIV_MASK) >> + MXC_CCM_PDR0_AUTO_MUX_DIV_OFFSET); + } + + if ((*pclk_mux) == -1) { + BUG(); + return -EINVAL; + } + return CLK_CODE_AHB(*pclk_mux); +} + +static void sync_cpu_wb(void) +{ + int i; + struct cpu_wp *p; + unsigned long reg = __raw_readl(MXC_CCM_PDR0); + if ((reg & MXC_CCM_PDR0_AUTO_CON) + || (cpu_is_mx35_rev(CHIP_REV_2_0) >= 1)) { + reg &= MXC_CCM_PDR0_CON_MUX_DIV_MASK; + } else { + reg &= MXC_CCM_PDR0_AUTO_MUX_DIV_MASK; + } + for (i = 0; i < cpu_wp_nr; i++) { + p = cpu_wp_tbl + cpu_curr_wp; + if (p->pdr0_reg == (reg & 0xF0E00)) + break; + cpu_curr_wp = (cpu_curr_wp + 1) % cpu_wp_nr; + } + cpu_wp_offset = cpu_curr_wp & 1; +} + +static int _clk_enable(struct clk *clk) +{ + u32 reg; + reg = __raw_readl(clk->enable_reg); + reg |= 3 << clk->enable_shift; + __raw_writel(reg, clk->enable_reg); + + return 0; +} + +static void _clk_disable(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(clk->enable_reg); + reg &= ~(3 << clk->enable_shift); + __raw_writel(reg, clk->enable_reg); +} + +static void _clk_emi_disable(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(clk->enable_reg); + reg &= ~(3 << clk->enable_shift); + reg |= (1 << clk->enable_shift); + __raw_writel(reg, clk->enable_reg); +} + +static int _clk_asrc_enable(struct clk *clk) +{ + u32 reg; + reg = __raw_readl(MXC_CCM_COSR); + __raw_writel(reg | MXC_CCM_COSR_ASRC_AUDIO_EN, MXC_CCM_COSR); + return 0; +} + +static void _clk_asrc_disable(struct clk *clk) +{ + u32 reg; + reg = __raw_readl(MXC_CCM_COSR); + __raw_writel(reg & (~MXC_CCM_COSR_ASRC_AUDIO_EN), MXC_CCM_COSR); +} + +static int _clk_pll_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + signed long pd = 1; /* Pre-divider */ + signed long mfi; /* Multiplication Factor (Integer part) */ + signed long mfn; /* Multiplication Factor (Integer part) */ + signed long mfd; /* Multiplication Factor (Denominator Part) */ + signed long tmp; + u32 ref_freq = clk->parent->rate; + + if ((clk == &mcu_pll_clk) + && (clk->parent->rate == cpu_wp_tbl[cpu_curr_wp].pll_rate)) { + __raw_writel(cpu_wp_tbl[cpu_curr_wp].pll_reg, MXC_CCM_MPCTL); + clk->rate = rate; + return 0; + } + + while (((ref_freq / pd) * 10) > rate) + pd++; + + if ((ref_freq / pd) < PRE_DIV_MIN_FREQ) + return -EINVAL; + + /* the ref_freq/2 in the following is to round up */ + mfi = (((rate / 2) * pd) + (ref_freq / 2)) / ref_freq; + if (mfi < 5 || mfi > 15) + return -EINVAL; + + /* pick a mfd value that will work + * then solve for mfn */ + mfd = ref_freq / 50000; + + /* + * pll_freq * pd * mfd + * mfn = -------------------- - (mfi * mfd) + * 2 * ref_freq + */ + /* the tmp/2 is for rounding */ + tmp = ref_freq / 10000; + mfn = + ((((((rate / 2) + (tmp / 2)) / tmp) * pd) * mfd) / 10000) - + (mfi * mfd); + + mfn = mfn & 0x3ff; + pd--; + mfd--; + + /* Change the Pll value */ + reg = (mfi << MXC_CCM_PCTL_MFI_OFFSET) | + (mfn << MXC_CCM_PCTL_MFN_OFFSET) | + (mfd << MXC_CCM_PCTL_MFD_OFFSET) | (pd << MXC_CCM_PCTL_PD_OFFSET); + + if (clk == &mcu_pll_clk) + __raw_writel(reg, MXC_CCM_MPCTL); + else if (clk == &peri_pll_clk) + __raw_writel(reg, MXC_CCM_PPCTL); + + clk->rate = rate; + return 0; +} + +static int _clk_cpu_set_rate(struct clk *clk, unsigned long rate) +{ + if ((rate < ahb_clk.rate) || (rate % ahb_clk.rate != 0)) { + printk(KERN_ERR "Wrong rate %lu in _clk_cpu_set_rate\n", rate); + return -EINVAL; + } + + if (clk->rate == rate) + return 0; + + if (clk->parent->rate == cpu_wp_tbl[cpu_curr_wp].pll_rate) + return __switch_cpu_wp(clk, rate); + return __switch_cpu_rate(clk, rate); +} + +static void _clk_pll_recalc(struct clk *clk) +{ + long mfi, mfn, mfd, pdf, ref_clk, mfn_abs; + unsigned long reg = 0; + s64 temp; + + ref_clk = ckih_clk.rate; + + if (clk == &mcu_pll_clk) + reg = __raw_readl(MXC_CCM_MPCTL); + else if (clk == &peri_pll_clk) + reg = __raw_readl(MXC_CCM_PPCTL); + else + BUG(); + + pdf = (reg & MXC_CCM_PCTL_PD_MASK) >> MXC_CCM_PCTL_PD_OFFSET; + mfd = (reg & MXC_CCM_PCTL_MFD_MASK) >> MXC_CCM_PCTL_MFD_OFFSET; + mfi = (reg & MXC_CCM_PCTL_MFI_MASK) >> MXC_CCM_PCTL_MFI_OFFSET; + mfi = (mfi <= 5) ? 5 : mfi; + mfn = mfn_abs = reg & MXC_CCM_PCTL_MFN_MASK; + + if (mfn >= 0x200) { + mfn |= 0xFFFFFE00; + mfn_abs = -mfn; + } + + ref_clk *= 2; + ref_clk /= pdf + 1; + + temp = (u64) ref_clk * mfn_abs; + do_div(temp, mfd + 1); + if (mfn < 0) + temp = -temp; + temp = (ref_clk * mfi) + temp; + + clk->rate = temp; +} + +static int _clk_peri_pll_enable(struct clk *clk) +{ + u32 reg; + reg = __raw_readl(MXC_CCM_CCMR); + reg |= MXC_CCM_CCMR_UPE; + __raw_writel(reg, MXC_CCM_CCMR); + + /* No lock bit on MX31, so using max time from spec */ + udelay(80); + + return 0; +} + +static void _clk_peri_pll_disable(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_CCMR); + reg &= ~MXC_CCM_CCMR_UPE; + __raw_writel(reg, MXC_CCM_CCMR); +} + +#define PDR0(mask, off) ((__raw_readl(MXC_CCM_PDR0) & mask) >> off) +#define PDR1(mask, off) ((__raw_readl(MXC_CCM_PDR1) & mask) >> off) +#define PDR2(mask, off) ((__raw_readl(MXC_CCM_PDR2) & mask) >> off) +#define PDR3(mask, off) ((__raw_readl(MXC_CCM_PDR3) & mask) >> off) +#define PDR4(mask, off) ((__raw_readl(MXC_CCM_PDR4) & mask) >> off) + +static void _clk_cpu_recalc(struct clk *clk) +{ + unsigned long pdr0 = __raw_readl(MXC_CCM_PDR0); + int arm_div, fi, fd; + if (clk->parent->rate == cpu_wp_tbl[cpu_curr_wp].pll_rate) { + clk->rate = cpu_wp_tbl[cpu_curr_wp].cpu_rate; + } else { + arm_div = __get_arm_div(pdr0, &fi, &fd); + clk->rate = (clk->parent->rate * fi) / (arm_div * fd); + } +} + +static void _clk_hclk_recalc(struct clk *clk) +{ + unsigned long ahb_div, pdr0 = __raw_readl(MXC_CCM_PDR0); + ahb_div = __get_ahb_div(pdr0); + clk->rate = clk->parent->rate / ahb_div; +} + +static void _clk_ipg_recalc(struct clk *clk) +{ + clk->rate = clk->parent->rate / 2; +} + +static void _clk_nfc_recalc(struct clk *clk) +{ + unsigned long nfc_pdf; + + nfc_pdf = PDR4(MXC_CCM_PDR4_NFC_PODF_MASK, + MXC_CCM_PDR4_NFC_PODF_OFFSET); + clk->rate = clk->parent->rate / (nfc_pdf + 1); +} + +static void _clk_hsp_recalc(struct clk *clk) +{ + int hsp_pdf; + unsigned long reg; + reg = __raw_readl(MXC_CCM_PDR0); + + if ((reg & MXC_CCM_PDR0_AUTO_CON) + || (cpu_is_mx35_rev(CHIP_REV_2_0) >= 1)) { + hsp_pdf = + (reg & MXC_CCM_PDR0_HSP_PODF_MASK) >> + MXC_CCM_PDR0_HSP_PODF_OFFSET; + reg = + (reg & MXC_CCM_PDR0_CON_MUX_DIV_MASK) >> + MXC_CCM_PDR0_CON_MUX_DIV_OFFSET; + if (hsp_pdf < 3) { + hsp_pdf = g_hsp_div_table[hsp_pdf][reg]; + if (hsp_pdf > 0) + clk->rate = clk->parent->rate / hsp_pdf; + } + } else { + clk->rate = clk->parent->rate; + } +} + +static void _clk_mlb_recalc(struct clk *clk) +{ + clk->rate = clk->parent->rate * 2; +} + +static void _clk_usb_recalc(struct clk *clk) +{ + unsigned long usb_podf, usb_prdf; + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + usb_podf = PDR4(MXC_CCM_PDR4_USB_PODF_MASK, + MXC_CCM_PDR4_USB_PODF_OFFSET); + usb_prdf = PDR4(MXC_CCM_PDR4_USB_PRDF_MASK, + MXC_CCM_PDR4_USB_PRDF_OFFSET); + clk->rate = + clk->parent->rate / ((usb_prdf + 1) * (usb_podf + 1)); + } else { + usb_podf = PDR4(MXC_CCM_PDR4_USB_PODF_MASK_V2, + MXC_CCM_PDR4_USB_PODF_OFFSET); + clk->rate = clk->parent->rate / (usb_podf + 1); + } +} + +static int _clk_usb_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + u32 div; + u32 podf, prdf; + + div = clk->parent->rate / rate; + + if ((clk->parent->rate / div) != rate) + return -EINVAL; + + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + __calc_two_dividers(div, &prdf, &podf); + reg = __raw_readl(MXC_CCM_PDR4) & + ~(MXC_CCM_PDR4_USB_PODF_MASK | MXC_CCM_PDR4_USB_PRDF_MASK); + reg |= (podf - 1) << MXC_CCM_PDR4_USB_PODF_OFFSET; + reg |= (prdf - 1) << MXC_CCM_PDR4_USB_PRDF_OFFSET; + } else { + podf = div - 1; + reg = + __raw_readl(MXC_CCM_PDR4) & ~MXC_CCM_PDR4_USB_PODF_MASK_V2; + reg |= (podf - 1) << MXC_CCM_PDR4_USB_PODF_OFFSET; + } + __raw_writel(reg, MXC_CCM_PDR4); + clk->rate = rate; + return 0; +} + +static void _clk_csi_recalc(struct clk *clk) +{ + u32 podf, prdf; + + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + prdf = PDR2(MXC_CCM_PDR2_CSI_PRDF_MASK, + MXC_CCM_PDR2_CSI_PRDF_OFFSET); + podf = + PDR2(MXC_CCM_PDR2_CSI_PODF_MASK, + MXC_CCM_PDR2_CSI_PODF_OFFSET); + clk->rate = clk->parent->rate / ((prdf + 1) * (podf + 1)); + } else { + podf = + PDR2(MXC_CCM_PDR2_CSI_PODF_MASK_V2, + MXC_CCM_PDR2_CSI_PODF_OFFSET); + clk->rate = clk->parent->rate / (podf + 1); + } +} + +static int _clk_csi_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + u32 div; + u32 prdf, podf; + + div = clk->parent->rate / rate; + + if ((clk->parent->rate / div) != rate) + return -EINVAL; + + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + __calc_two_dividers(div, &prdf, &podf); + reg = __raw_readl(MXC_CCM_PDR2) & + ~(MXC_CCM_PDR2_CSI_PRDF_MASK | MXC_CCM_PDR2_CSI_PODF_MASK); + reg |= (podf - 1) << MXC_CCM_PDR2_CSI_PODF_OFFSET; + reg |= (prdf - 1) << MXC_CCM_PDR2_CSI_PRDF_OFFSET; + } else { + reg = + __raw_readl(MXC_CCM_PDR2) & ~MXC_CCM_PDR2_CSI_PODF_MASK_V2; + reg |= (div - 1) << MXC_CCM_PDR2_CSI_PODF_OFFSET; + } + + /* Set CSI clock divider */ + __raw_writel(reg, MXC_CCM_PDR2); + clk->rate = rate; + return 0; +} + +static int _clk_csi_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + if (parent == &cpu_clk) + reg = __raw_readl(MXC_CCM_PDR2) | MXC_CCM_PDR2_CSI_M_U; + else if (parent == &peri_pll_clk) + reg = __raw_readl(MXC_CCM_PDR2) & (~MXC_CCM_PDR2_CSI_M_U); + else + return -EINVAL; + __raw_writel(reg, MXC_CCM_PDR2); + return 0; +} + +static void _clk_per_recalc(struct clk *clk) +{ + u32 podf = 0, prdf = 0; + + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + if (clk->parent == &cpu_clk) { + prdf = PDR4(MXC_CCM_PDR4_PER0_PRDF_MASK, + MXC_CCM_PDR4_PER0_PRDF_OFFSET); + podf = PDR4(MXC_CCM_PDR4_PER0_PODF_MASK, + MXC_CCM_PDR4_PER0_PODF_OFFSET); + } else { + podf = PDR0(MXC_CCM_PDR0_PER_PODF_MASK, + MXC_CCM_PDR0_PER_PODF_OFFSET); + } + clk->rate = clk->parent->rate / ((podf + 1) * (prdf + 1)); + } else { + if (clk->parent == &ahb_clk) + podf = PDR0(MXC_CCM_PDR0_PER_PODF_MASK, + MXC_CCM_PDR0_PER_PODF_OFFSET); + else if (clk->parent == &cpu_clk) { + podf = PDR4(MXC_CCM_PDR4_PER0_PODF_MASK_V2, + MXC_CCM_PDR4_PER0_PODF_OFFSET); + } + clk->rate = clk->parent->rate / (podf + 1); + } +} + +static void _clk_uart_per_recalc(struct clk *clk) +{ + unsigned long podf, prdf; + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + prdf = PDR4(MXC_CCM_PDR4_UART_PRDF_MASK, + MXC_CCM_PDR4_UART_PRDF_OFFSET); + podf = PDR4(MXC_CCM_PDR4_UART_PODF_MASK, + MXC_CCM_PDR4_UART_PODF_OFFSET); + clk->rate = clk->parent->rate / ((prdf + 1) * (podf + 1)); + } else { + podf = + PDR4(MXC_CCM_PDR4_UART_PODF_MASK_V2, + MXC_CCM_PDR4_UART_PODF_OFFSET); + clk->rate = clk->parent->rate / (podf + 1); + } + +} + +static int _clk_uart_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + u32 div; + u32 prdf, podf; + + div = clk->parent->rate / rate; + + if ((clk->parent->rate / div) != rate) + return -EINVAL; + + /* Set UART clock divider */ + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + __calc_two_dividers(div, &prdf, &podf); + reg = __raw_readl(MXC_CCM_PDR4) & + ~(MXC_CCM_PDR4_UART_PRDF_MASK | + MXC_CCM_PDR4_UART_PODF_MASK); + reg |= (podf - 1) << MXC_CCM_PDR4_UART_PODF_OFFSET; + reg |= (prdf - 1) << MXC_CCM_PDR4_UART_PRDF_OFFSET; + } else { + reg = + __raw_readl(MXC_CCM_PDR4) & ~MXC_CCM_PDR4_UART_PODF_MASK_V2; + reg |= (div - 1) << MXC_CCM_PDR4_UART_PODF_OFFSET; + } + __raw_writel(reg, MXC_CCM_PDR4); + clk->rate = rate; + return 0; +} + +static void _clk_ssi_recalc(struct clk *clk) +{ + unsigned long ssi_pdf, ssi_prepdf; + + if (clk->id == 1) { + ssi_pdf = PDR2(MXC_CCM_PDR2_SSI2_PODF_MASK, + MXC_CCM_PDR2_SSI2_PODF_OFFSET); + ssi_prepdf = PDR2(MXC_CCM_PDR2_SSI2_PRDF_MASK, + MXC_CCM_PDR2_SSI2_PRDF_OFFSET); + } else { + ssi_pdf = PDR2(MXC_CCM_PDR2_SSI1_PODF_MASK, + MXC_CCM_PDR2_SSI1_PODF_OFFSET); + ssi_prepdf = PDR2(MXC_CCM_PDR2_SSI1_PRDF_MASK, + MXC_CCM_PDR2_SSI1_PRDF_OFFSET); + } + clk->rate = clk->parent->rate / ((ssi_prepdf + 1) * (ssi_pdf + 1)); +} + +static int _clk_ssi_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + u32 div; + u32 pre, post; + + div = clk->parent->rate / rate; + + if ((clk->parent->rate / div) != rate) + return -EINVAL; + + __calc_pre_post_dividers(div, &pre, &post); + + if (clk->id == 1) { + reg = __raw_readl(MXC_CCM_PDR2) & + ~(MXC_CCM_PDR2_SSI2_PRDF_MASK | + MXC_CCM_PDR2_SSI2_PODF_MASK); + reg |= (post - 1) << MXC_CCM_PDR2_SSI2_PODF_OFFSET; + reg |= (pre - 1) << MXC_CCM_PDR2_SSI2_PRDF_OFFSET; + } else { + reg = __raw_readl(MXC_CCM_PDR2) & + ~(MXC_CCM_PDR2_SSI1_PRDF_MASK | + MXC_CCM_PDR2_SSI1_PODF_MASK); + reg |= (post - 1) << MXC_CCM_PDR2_SSI1_PODF_OFFSET; + reg |= (pre - 1) << MXC_CCM_PDR2_SSI1_PRDF_OFFSET; + } + __raw_writel(reg, MXC_CCM_PDR2); + + clk->rate = rate; + return 0; +} + +static void _clk_mstick1_recalc(struct clk *clk) +{ + unsigned long prdf, podf; + prdf = PDR1(MXC_CCM_PDR1_MSHC_PRDF_MASK, MXC_CCM_PDR1_MSHC_PRDF_OFFSET); + podf = PDR1(MXC_CCM_PDR1_MSHC_PODF_MASK, MXC_CCM_PDR1_MSHC_PODF_OFFSET); + clk->rate = clk->parent->rate / ((prdf + 1) * (podf + 1)); +} + +static int _clk_mstick1_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + u32 div; + u32 pre, post; + + div = clk->parent->rate / rate; + + if ((clk->parent->rate / div) != rate) + return -EINVAL; + + __calc_pre_post_dividers(div, &pre, &post); + + reg = __raw_readl(MXC_CCM_PDR1) & + ~(MXC_CCM_PDR1_MSHC_PRDF_MASK | MXC_CCM_PDR1_MSHC_PODF_MASK); + reg |= (post - 1) << MXC_CCM_PDR1_MSHC_PODF_OFFSET; + reg |= (pre - 1) << MXC_CCM_PDR1_MSHC_PRDF_OFFSET; + __raw_writel(reg, MXC_CCM_PDR1); + + clk->rate = rate; + return 0; +} + +static int _clk_mstick1_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + if (parent == &cpu_clk) + reg = __raw_readl(MXC_CCM_PDR1) | MXC_CCM_PDR1_MSHC_M_U; + else if (parent == &peri_pll_clk) + reg = __raw_readl(MXC_CCM_PDR1) & (~MXC_CCM_PDR1_MSHC_M_U); + else + return -EINVAL; + __raw_writel(reg, MXC_CCM_PDR1); + return 0; +} + +static void _clk_spdif_recalc(struct clk *clk) +{ + unsigned long prdf, podf; + prdf = + PDR3(MXC_CCM_PDR3_SPDIF_PRDF_MASK, MXC_CCM_PDR3_SPDIF_PRDF_OFFSET); + podf = + PDR3(MXC_CCM_PDR3_SPDIF_PODF_MASK, MXC_CCM_PDR3_SPDIF_PODF_OFFSET); + clk->rate = clk->parent->rate / ((prdf + 1) * (podf + 1)); +} + +static int _clk_spdif_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + u32 div; + u32 pre, post; + + div = clk->parent->rate / rate; + + if ((clk->parent->rate / div) != rate) + return -EINVAL; + + __calc_pre_post_dividers(div, &pre, &post); + + reg = __raw_readl(MXC_CCM_PDR3) & + ~(MXC_CCM_PDR3_SPDIF_PRDF_MASK | MXC_CCM_PDR3_SPDIF_PODF_MASK); + reg |= (post - 1) << MXC_CCM_PDR3_SPDIF_PODF_OFFSET; + reg |= (pre - 1) << MXC_CCM_PDR3_SPDIF_PRDF_OFFSET; + __raw_writel(reg, MXC_CCM_PDR3); + + clk->rate = rate; + return 0; +} + +static int _clk_spdif_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + if (parent == &cpu_clk) + reg = __raw_readl(MXC_CCM_PDR3) | MXC_CCM_PDR3_SPDIF_M_U; + else if (parent == &peri_pll_clk) + reg = __raw_readl(MXC_CCM_PDR3) & (~MXC_CCM_PDR3_SPDIF_M_U); + else + return -EINVAL; + __raw_writel(reg, MXC_CCM_PDR3); + return 0; +} + +static void _clk_asrc_recalc(struct clk *clk) +{ + unsigned long div; + div = __raw_readl(MXC_CCM_COSR) & MXC_CCM_COSR_ASRC_AUDIO_PODF_MASK; + div = div >> MXC_CCM_COSR_ASRC_AUDIO_PODF_OFFSET; + clk->rate = clk->parent->rate / (div + 1); +} + +static int _clk_asrc_set_rate(struct clk *clk, unsigned long rate) +{ + int div; + unsigned long reg; + if (clk->parent->rate % rate) + return -EINVAL; + + div = clk->parent->rate / rate; + reg = __raw_readl(MXC_CCM_COSR) & (~MXC_CCM_COSR_ASRC_AUDIO_PODF_MASK); + reg |= (div - 1) << MXC_CCM_COSR_ASRC_AUDIO_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_COSR); + clk->rate = rate; + return 0; +} + +static void _clk_sdhc_recalc(struct clk *clk) +{ + u32 podf = 0, prdf = 0; + + switch (clk->id) { + case 0: + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + prdf = PDR3(MXC_CCM_PDR3_ESDHC1_PRDF_MASK, + MXC_CCM_PDR3_ESDHC1_PRDF_OFFSET); + podf = PDR3(MXC_CCM_PDR3_ESDHC1_PODF_MASK, + MXC_CCM_PDR3_ESDHC1_PODF_OFFSET); + } else + podf = PDR3(MXC_CCM_PDR3_ESDHC1_PODF_MASK_V2, + MXC_CCM_PDR3_ESDHC1_PODF_OFFSET); + break; + case 1: + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + prdf = PDR3(MXC_CCM_PDR3_ESDHC2_PRDF_MASK, + MXC_CCM_PDR3_ESDHC2_PRDF_OFFSET); + podf = PDR3(MXC_CCM_PDR3_ESDHC2_PODF_MASK, + MXC_CCM_PDR3_ESDHC2_PODF_OFFSET); + } else + podf = PDR3(MXC_CCM_PDR3_ESDHC2_PODF_MASK_V2, + MXC_CCM_PDR3_ESDHC2_PODF_OFFSET); + break; + case 2: + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + prdf = PDR3(MXC_CCM_PDR3_ESDHC3_PRDF_MASK, + MXC_CCM_PDR3_ESDHC3_PRDF_OFFSET); + podf = PDR3(MXC_CCM_PDR3_ESDHC3_PODF_MASK, + MXC_CCM_PDR3_ESDHC3_PODF_OFFSET); + } else + podf = PDR3(MXC_CCM_PDR3_ESDHC3_PODF_MASK_V2, + MXC_CCM_PDR3_ESDHC3_PODF_OFFSET); + break; + default: + return; + } + clk->rate = clk->parent->rate / ((podf + 1) * (prdf + 1)); +} + +static int _clk_sdhc_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + u32 div; + u32 prdf, podf; + + div = clk->parent->rate / rate; + + if ((clk->parent->rate / div) != rate) + return -EINVAL; + + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) + __calc_pre_post_dividers(div, &prdf, &podf); + + switch (clk->id) { + case 0: + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + reg = __raw_readl(MXC_CCM_PDR3) & + ~(MXC_CCM_PDR3_ESDHC1_PRDF_MASK | + MXC_CCM_PDR3_ESDHC1_PODF_MASK); + reg |= (podf - 1) << MXC_CCM_PDR3_ESDHC1_PODF_OFFSET; + reg |= (prdf - 1) << MXC_CCM_PDR3_ESDHC1_PRDF_OFFSET; + } else { + reg = __raw_readl(MXC_CCM_PDR3) & + ~MXC_CCM_PDR3_ESDHC1_PODF_MASK_V2; + reg |= (div - 1) << MXC_CCM_PDR3_ESDHC1_PODF_OFFSET; + } + break; + case 1: + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + reg = __raw_readl(MXC_CCM_PDR3) & + ~(MXC_CCM_PDR3_ESDHC2_PRDF_MASK | + MXC_CCM_PDR3_ESDHC2_PODF_MASK); + reg |= (podf - 1) << MXC_CCM_PDR3_ESDHC2_PODF_OFFSET; + reg |= (prdf - 1) << MXC_CCM_PDR3_ESDHC2_PRDF_OFFSET; + } else { + reg = __raw_readl(MXC_CCM_PDR3) & + ~MXC_CCM_PDR3_ESDHC2_PODF_MASK_V2; + reg |= (div - 1) << MXC_CCM_PDR3_ESDHC2_PODF_OFFSET; + } + break; + case 2: + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + reg = __raw_readl(MXC_CCM_PDR3) & + ~(MXC_CCM_PDR3_ESDHC3_PRDF_MASK | + MXC_CCM_PDR3_ESDHC3_PODF_MASK); + reg |= (podf - 1) << MXC_CCM_PDR3_ESDHC3_PODF_OFFSET; + reg |= (prdf - 1) << MXC_CCM_PDR3_ESDHC3_PRDF_OFFSET; + } else { + reg = __raw_readl(MXC_CCM_PDR3) & + ~MXC_CCM_PDR3_ESDHC3_PODF_MASK_V2; + reg |= (div - 1) << MXC_CCM_PDR3_ESDHC3_PODF_OFFSET; + } + break; + default: + return -EINVAL; + } + __raw_writel(reg, MXC_CCM_PDR3); + + clk->rate = rate; + return 0; +} + +static struct clk ckih_clk = { + .name = "ckih", + .rate = CKIH_CLK_FREQ, + .flags = RATE_FIXED, +}; + +static struct clk int_32k_clk = { + .name = "int_32k", + .rate = CKIL_CLK_FREQ, + .flags = RATE_FIXED, +}; + +static struct clk ext_32k_clk = { + .name = "ext_32k", + .rate = CKIL_EXT_FREQ, + .flags = RATE_FIXED, +}; + +static int _clk_ckil_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + if (parent == &int_32k_clk) { + reg = __raw_readl(MXC_CCM_PDR0) & (~MXC_CCM_PDR0_CKIL_SEL); + clk->rate = parent->rate; + } else if (parent == &ext_32k_clk) { + reg = __raw_readl(MXC_CCM_PDR0) | MXC_CCM_PDR0_CKIL_SEL; + clk->rate = parent->rate; + } else + return -EINVAL; + __raw_writel(reg, MXC_CCM_PDR0); + return 0; +} + +static int _clk_ckil_set_rate(struct clk *clk, unsigned long rate) +{ + clk->rate = clk->parent->rate; + return 0; +} + +static struct clk ckil_clk = { + .name = "ckil", + .parent = &ext_32k_clk, + .set_parent = _clk_ckil_set_parent, + .set_rate = _clk_ckil_set_rate, +}; + +static int _clk_ckie_enable(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_PMCR2) & ~MXC_CCM_PMCR2_OSC_AUDIO_DOWN; + __raw_writel(reg, MXC_CCM_PMCR2); + + return 0; +} + +static void _clk_ckie_disable(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_PMCR2) | MXC_CCM_PMCR2_OSC_AUDIO_DOWN; + __raw_writel(reg, MXC_CCM_PMCR2); +} + +static struct clk ckie_clk = { + .name = "ckie", + .rate = CKIE_CLK_FREQ, + .flags = RATE_FIXED, + .enable = _clk_ckie_enable, + .disable = _clk_ckie_disable, +}; + +static struct clk mcu_pll_clk = { + .name = "mcu_pll", + .parent = &ckih_clk, + .set_rate = _clk_pll_set_rate, + .recalc = _clk_pll_recalc, + .flags = RATE_PROPAGATES, +}; + +static struct clk peri_pll_clk = { + .name = "peri_pll", + .parent = &ckih_clk, + .set_rate = _clk_pll_set_rate, + .recalc = _clk_pll_recalc, + .enable = _clk_peri_pll_enable, + .disable = _clk_peri_pll_disable, + .flags = RATE_PROPAGATES, +}; + +static struct clk cpu_clk = { + .name = "cpu_clk", + .parent = &mcu_pll_clk, + .recalc = _clk_cpu_recalc, + .set_rate = _clk_cpu_set_rate, +}; + +static struct clk ahb_clk = { + .name = "ahb_clk", + .parent = &cpu_clk, + .recalc = _clk_hclk_recalc, + .flags = RATE_PROPAGATES, +}; + +static struct clk ipg_clk = { + .name = "ipg_clk", + .parent = &ahb_clk, + .recalc = _clk_ipg_recalc, + .flags = RATE_PROPAGATES, +}; + +static struct clk perclk_clk = { + .name = "perclk_clk", + .parent = &ahb_clk, + .recalc = _clk_per_recalc, + .flags = RATE_PROPAGATES, +}; + +static struct clk uart_per_clk = { + .name = "uart_per_clk", + .parent = &peri_pll_clk, + .recalc = _clk_uart_per_recalc, + .round_rate = _clk_round_rate, + .set_rate = _clk_uart_set_rate, + .flags = RATE_PROPAGATES, +}; + +static struct clk asrc_clk[] = { + { + .name = "asrc_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_ASRC_OFFSET, + .disable = _clk_disable,}, + { + .name = "asrc_audio_clk", + .parent = &ckie_clk, + .recalc = _clk_asrc_recalc, + .round_rate = _clk_round_rate, + .set_rate = _clk_asrc_set_rate, + .enable = _clk_asrc_enable, + .disable = _clk_asrc_disable,}, +}; + +static struct clk ata_clk = { + .name = "ata_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_ATA_OFFSET, + .disable = _clk_disable, +}; + +static struct clk can_clk[] = { + { + .name = "can_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_CAN1_OFFSET, + .disable = _clk_disable,}, + { + .name = "can_clk", + .id = 1, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_CAN2_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk cspi_clk[] = { + { + .name = "cspi_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_CSPI1_OFFSET, + .disable = _clk_disable,}, + { + .name = "cspi_clk", + .id = 1, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_CSPI2_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk ect_clk = { + .name = "ect_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_ECT_OFFSET, + .disable = _clk_disable, +}; + +static struct clk emi_clk = { + .name = "emi_clk", + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_EMI_OFFSET, + .disable = _clk_emi_disable, +}; + +static struct clk epit_clk[] = { + { + .name = "epit_clk", + .id = 0, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_EPIT1_OFFSET, + .disable = _clk_disable,}, + { + .name = "epit_clk", + .id = 1, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_EPIT2_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk esai_clk = { + .name = "esai_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_ESAI_OFFSET, + .disable = _clk_disable, +}; + +static struct clk sdhc_clk[] = { + { + .name = "sdhc_clk", + .id = 0, + .parent = &peri_pll_clk, + .recalc = _clk_sdhc_recalc, + .set_rate = _clk_sdhc_set_rate, + .round_rate = _clk_round_rate, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_ESDHC1_OFFSET, + .disable = _clk_disable,}, + { + .name = "sdhc_clk", + .id = 1, + .parent = &peri_pll_clk, + .recalc = _clk_sdhc_recalc, + .set_rate = _clk_sdhc_set_rate, + .round_rate = _clk_round_rate, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_ESDHC2_OFFSET, + .disable = _clk_disable,}, + { + .name = "sdhc_clk", + .id = 2, + .parent = &peri_pll_clk, + .recalc = _clk_sdhc_recalc, + .set_rate = _clk_sdhc_set_rate, + .round_rate = _clk_round_rate, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR0, + .enable_shift = MXC_CCM_CGR0_ESDHC3_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk fec_clk = { + .name = "fec_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_FEC_OFFSET, + .disable = _clk_disable, +}; + +static struct clk gpt_clk = { + .name = "gpt_clk", + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_GPT_OFFSET, + .disable = _clk_disable, +}; + +static struct clk i2c_clk[] = { + { + .name = "i2c_clk", + .id = 0, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_I2C1_OFFSET, + .disable = _clk_disable,}, + { + .name = "i2c_clk", + .id = 1, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_I2C2_OFFSET, + .disable = _clk_disable,}, + { + .name = "i2c_clk", + .id = 2, + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_I2C3_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk ipu_clk = { + .name = "ipu_clk", + .parent = &cpu_clk, + .recalc = _clk_hsp_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_IPU_OFFSET, + .disable = _clk_disable, +}; + +static struct clk kpp_clk = { + .name = "kpp_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_KPP_OFFSET, + .disable = _clk_disable, +}; + +static struct clk mlb_clk = { + .name = "mlb_clk", + .parent = &ahb_clk, + .recalc = _clk_mlb_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_MLB_OFFSET, + .disable = _clk_disable, +}; + +static struct clk mstick_clk = { + .name = "mstick_clk", + .id = 0, + .parent = &peri_pll_clk, + .recalc = _clk_mstick1_recalc, + .set_rate = _clk_mstick1_set_rate, + .round_rate = _clk_per_post_round_rate, + .set_parent = _clk_mstick1_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_MSHC_OFFSET, + .disable = _clk_disable, +}; + +static struct clk owire_clk = { + .name = "owire_clk", + .parent = &perclk_clk, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_OWIRE_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, +}; + +static struct clk pwm_clk = { + .name = "pwm_clk", + .parent = &perclk_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_PWM_OFFSET, + .disable = _clk_disable, +}; + +static struct clk rng_clk = { + .name = "rng_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR1, + .enable_shift = MXC_CCM_CGR1_RNGC_OFFSET, + .disable = _clk_disable, +}; + +static struct clk rtc_clk = { + .name = "rtc_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_RTC_OFFSET, + .disable = _clk_disable, +}; + +static struct clk rtic_clk = { + .name = "rtic_clk", + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_RTIC_OFFSET, + .disable = _clk_disable, +}; + +static struct clk scc_clk = { + .name = "scc_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_SCC_OFFSET, + .disable = _clk_disable, +}; + +static struct clk sdma_clk[] = { + { + .name = "sdma_ahb_clk", + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_SDMA_OFFSET, + .disable = _clk_disable,}, + { + .name = "sdma_ipg_clk", + .parent = &ipg_clk,} +}; + +static struct clk spba_clk = { + .name = "spba_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_SPBA_OFFSET, + .disable = _clk_disable, +}; + +static struct clk spdif_clk[] = { + { + .name = "spdif_clk", + .parent = &peri_pll_clk, + .recalc = _clk_spdif_recalc, + .set_rate = _clk_spdif_set_rate, + .round_rate = _clk_per_post_round_rate, + .set_parent = _clk_spdif_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_SPDIF_OFFSET, + .disable = _clk_disable,}, + { + .name = "spdif_audio_clk", + .parent = &ckie_clk,}, + { + .name = "spdif_ipg_clk", + .parent = &ipg_clk,}, +}; + +static struct clk ssi_clk[] = { + { + .name = "ssi_clk", + .parent = &peri_pll_clk, + .recalc = _clk_ssi_recalc, + .set_rate = _clk_ssi_set_rate, + .round_rate = _clk_per_post_round_rate, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_SSI1_OFFSET, + .disable = _clk_disable,}, + { + .name = "ssi_clk", + .id = 1, + .parent = &peri_pll_clk, + .recalc = _clk_ssi_recalc, + .set_rate = _clk_ssi_set_rate, + .round_rate = _clk_per_post_round_rate, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_SSI2_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk uart_clk[] = { + { + .name = "uart_clk", + .id = 0, + .parent = &uart_per_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_UART1_OFFSET, + .disable = _clk_disable,}, + { + .name = "uart_clk", + .id = 1, + .parent = &uart_per_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_UART2_OFFSET, + .disable = _clk_disable,}, + { + .name = "uart_clk", + .id = 2, + .parent = &uart_per_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_UART3_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk usb_clk[] = { + { + .name = "usb_clk", + .parent = &peri_pll_clk, + .recalc = _clk_usb_recalc, + .round_rate = _clk_round_rate, + .set_rate = _clk_usb_set_rate,}, + { + .name = "usb_ahb_clk", + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_USBOTG_OFFSET, + .disable = _clk_disable,}, +}; + +static struct clk wdog_clk = { + .name = "wdog_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR2, + .enable_shift = MXC_CCM_CGR2_WDOG_OFFSET, + .disable = _clk_disable, +}; + +static struct clk csi_clk = { + .name = "csi_clk", + .parent = &peri_pll_clk, + .recalc = _clk_csi_recalc, + .round_rate = _clk_round_rate, + .set_rate = _clk_csi_set_rate, + .set_parent = _clk_csi_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR3, + .enable_shift = MXC_CCM_CGR3_CSI_OFFSET, + .disable = _clk_disable, +}; + +static struct clk iim_clk = { + .name = "iim_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR3, + .enable_shift = MXC_CCM_CGR3_IIM_OFFSET, + .disable = _clk_disable, +}; + +static struct clk nfc_clk = { + .name = "nfc_clk", + .parent = &ahb_clk, + .recalc = _clk_nfc_recalc, +}; + +static unsigned long _clk_cko1_round_rate(struct clk *clk, unsigned long rate) +{ + u32 div = 0, div1 = 1; + + div = clk->parent->rate / rate; + if (clk->parent->rate % rate) + div++; + + if (div > 64) { + div = (div + 1) >> 1; + div1++; + } + + if (div > 128) + div = 64; + return clk->parent->rate / (div * div1); +} + +static int _clk_cko1_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + u32 div, div1 = 0; + u32 prdf, podf; + + div = clk->parent->rate / rate; + if ((clk->parent->rate / div) != rate) + return -EINVAL; + if (div > 64) { + div1 = MXC_CCM_COSR_CLKOUTDIV_1; + div >>= 1; + } else { + div1 = 0; + } + + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + __calc_two_dividers(div, &prdf, &podf); + reg = __raw_readl(MXC_CCM_COSR) & + ~(MXC_CCM_COSR_CLKOUT_PREDIV_MASK | + MXC_CCM_COSR_CLKOUT_PRODIV_MASK | + MXC_CCM_COSR_CLKOUTDIV_1); + reg |= ((prdf - 1) << MXC_CCM_COSR_CLKOUT_PREDIV_OFFSET) + | ((podf - 1) << MXC_CCM_COSR_CLKOUT_PRODIV_OFFSET) + | div1; + } else { + reg = __raw_readl(MXC_CCM_COSR) & + ~(MXC_CCM_COSR_CLKOUT_PRODIV_MASK_V2 | + MXC_CCM_COSR_CLKOUTDIV_1); + reg |= ((div - 1) << MXC_CCM_COSR_CLKOUT_PRODIV_OFFSET) | div1; + } + __raw_writel(reg, MXC_CCM_COSR); + + return 0; +} + +static void _clk_cko1_recalc(struct clk *clk) +{ + u32 prdf = 1; + u32 podf, div1; + u32 reg = __raw_readl(MXC_CCM_COSR); + + div1 = 1 << ((reg & MXC_CCM_COSR_CLKOUTDIV_1) != 0); + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + prdf = (reg & MXC_CCM_COSR_CLKOUT_PREDIV_MASK) >> + MXC_CCM_COSR_CLKOUT_PREDIV_OFFSET; + podf = (reg & MXC_CCM_COSR_CLKOUT_PRODIV_MASK) >> + MXC_CCM_COSR_CLKOUT_PRODIV_OFFSET; + } else + podf = (reg & MXC_CCM_COSR_CLKOUT_PRODIV_MASK_V2) >> + MXC_CCM_COSR_CLKOUT_PRODIV_OFFSET; + + clk->rate = clk->parent->rate / (div1 * (podf + 1) * (prdf + 1)); +} + +static int _clk_cko1_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + reg = __raw_readl(MXC_CCM_COSR) & ~MXC_CCM_COSR_CLKOSEL_MASK; + + if (parent == &ckil_clk) { + reg &= ~MXC_CCM_COSR_CKIL_CKIH_MASK; + reg |= 0 << MXC_CCM_COSR_CLKOSEL_OFFSET; + } else if (parent == &ckih_clk) { + reg |= 1 << MXC_CCM_COSR_CLKOSEL_OFFSET; + } else if (parent == &ckie_clk) + reg |= 2 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &peri_pll_clk) + reg |= 6 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &cpu_clk) + reg |= 7 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &ahb_clk) + reg |= 8 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &ipg_clk) + reg |= 9 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &usb_clk[1]) + reg |= 0xB << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &sdhc_clk[1]) + reg |= 0xC << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &ssi_clk[1]) + reg |= 0xD << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &mlb_clk) + reg |= 0xE << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &csi_clk) + reg |= 0x11 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &spdif_clk[0]) + reg |= 0x12 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &uart_clk[0]) + reg |= 0x13 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if (parent == &asrc_clk[1]) + reg |= 0x14 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if ((parent == &nfc_clk) && (cpu_is_mx35_rev(CHIP_REV_2_0) >= 1)) + reg |= 0x17 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else if ((parent == &ipu_clk) && (cpu_is_mx35_rev(CHIP_REV_2_0) >= 1)) + reg |= 0x18 << MXC_CCM_COSR_CLKOSEL_OFFSET; + else + return -EINVAL; + + __raw_writel(reg, MXC_CCM_COSR); + return 0; +} + +static int _clk_cko1_enable(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_COSR) | MXC_CCM_COSR_CLKOEN; + __raw_writel(reg, MXC_CCM_COSR); + + return 0; +} + +static void _clk_cko1_disable(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_COSR) & ~MXC_CCM_COSR_CLKOEN; + __raw_writel(reg, MXC_CCM_COSR); +} + +static struct clk cko1_clk = { + .name = "cko1_clk", + .recalc = _clk_cko1_recalc, + .set_rate = _clk_cko1_set_rate, + .round_rate = _clk_cko1_round_rate, + .set_parent = _clk_cko1_set_parent, + .enable = _clk_cko1_enable, + .disable = _clk_cko1_disable, +}; + +static struct clk gpu2d_clk = { + .name = "gpu2d_clk", + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CGR3, + .enable_shift = MXC_CCM_CGR3_GPU2D_OFFSET, + .disable = _clk_disable, +}; + +static struct clk *mxc_clks[] = { + &int_32k_clk, + &ext_32k_clk, + &ckih_clk, + &ckil_clk, + &ckie_clk, + &mcu_pll_clk, + &peri_pll_clk, + &cpu_clk, + &ahb_clk, + &ipg_clk, + &perclk_clk, + &uart_per_clk, + &asrc_clk[0], + &asrc_clk[1], + &ata_clk, + &can_clk[0], + &can_clk[1], + &cspi_clk[0], + &cspi_clk[1], + &ect_clk, + &emi_clk, + &epit_clk[0], + &epit_clk[1], + &esai_clk, + &sdhc_clk[0], + &sdhc_clk[1], + &sdhc_clk[2], + &fec_clk, + &gpt_clk, + &i2c_clk[0], + &i2c_clk[1], + &i2c_clk[2], + &ipu_clk, + &kpp_clk, + &mlb_clk, + &mstick_clk, + &owire_clk, + &rng_clk, + &pwm_clk, + &rtc_clk, + &rtic_clk, + &scc_clk, + &sdma_clk[0], + &sdma_clk[1], + &spba_clk, + &spdif_clk[0], + &spdif_clk[1], + &spdif_clk[2], + &ssi_clk[0], + &ssi_clk[1], + &uart_clk[0], + &uart_clk[1], + &uart_clk[2], + &usb_clk[0], + &usb_clk[1], + &wdog_clk, + &csi_clk, + &iim_clk, + &nfc_clk, + &cko1_clk, + &gpu2d_clk, +}; + +extern void propagate_rate(struct clk *tclk); + +static void mxc_clockout_scan(void) +{ + u32 reg = __raw_readl(MXC_CCM_COSR) & MXC_CCM_COSR_CLKOSEL_MASK; + reg >>= MXC_CCM_COSR_CLKOSEL_OFFSET; + switch (reg) { + case 0: + cko1_clk.parent = &ckil_clk; + break; + case 1: + cko1_clk.parent = &ckih_clk; + break; + case 2: + cko1_clk.parent = &ckie_clk; + break; + case 6: + cko1_clk.parent = &peri_pll_clk; + break; + case 7: + cko1_clk.parent = &cpu_clk; + break; + case 8: + cko1_clk.parent = &ahb_clk; + break; + case 9: + cko1_clk.parent = &ipg_clk; + break; + case 0xB: + cko1_clk.parent = &usb_clk[1]; + break; + case 0xC: + cko1_clk.parent = &sdhc_clk[1]; + break; + case 0xD: + cko1_clk.parent = &ssi_clk[1]; + break; + case 0xE: + cko1_clk.parent = &mlb_clk; + break; + case 0x11: + cko1_clk.parent = &csi_clk; + break; + case 0x12: + cko1_clk.parent = &spdif_clk[0]; + break; + case 0x13: + cko1_clk.parent = &uart_clk[0]; + break; + case 0x14: + cko1_clk.parent = &asrc_clk[1]; + break; + case 0x17: + cko1_clk.parent = &nfc_clk; + break; + case 0x18: + cko1_clk.parent = &ipu_clk; + break; + } +} + +static void mxc_update_clocks(void) +{ + unsigned long reg; + reg = __raw_readl(MXC_CCM_PDR0); + if ((!(reg & MXC_CCM_PDR0_AUTO_CON)) + && (cpu_is_mx35_rev(CHIP_REV_2_0) < 1)) + ipu_clk.parent = &ahb_clk; + + if (reg & MXC_CCM_PDR0_PER_SEL) + perclk_clk.parent = &cpu_clk; + + reg = __raw_readl(MXC_CCM_PDR1); + if (reg & MXC_CCM_PDR1_MSHC_M_U) + mstick_clk.parent = &cpu_clk; + + reg = __raw_readl(MXC_CCM_PDR2); + if (reg & MXC_CCM_PDR2_CSI_M_U) + csi_clk.parent = &cpu_clk; + if (reg & MXC_CCM_PDR2_SSI_M_U) { + ssi_clk[0].parent = &cpu_clk; + ssi_clk[1].parent = &cpu_clk; + } + + reg = __raw_readl(MXC_CCM_PDR3); + if (reg & MXC_CCM_PDR3_SPDIF_M_U) + spdif_clk[0].parent = &cpu_clk; + + if (reg & MXC_CCM_PDR3_UART_M_U) + uart_per_clk.parent = &cpu_clk; + + if (reg & MXC_CCM_PDR3_ESDHC_M_U) { + sdhc_clk[0].parent = &cpu_clk; + sdhc_clk[1].parent = &cpu_clk; + sdhc_clk[2].parent = &cpu_clk; + } + + reg = __raw_readl(MXC_CCM_PDR4); + if (reg & MXC_CCM_PDR4_USB_M_U) + usb_clk[0].parent = &cpu_clk; + + mxc_clockout_scan(); +} + +int __init mx35_clocks_init(void) +{ + struct clk **clkp; + for (clkp = mxc_clks; clkp < mxc_clks + ARRAY_SIZE(mxc_clks); clkp++) + clk_register(*clkp); + + /* Turn off all possible clocks */ + __raw_writel(MXC_CCM_CGR0_ECT_MASK | MXC_CCM_CGR0_EMI_MASK, + MXC_CCM_CGR0); + __raw_writel(MXC_CCM_CGR1_GPIO1_MASK | MXC_CCM_CGR1_GPIO2_MASK | + MXC_CCM_CGR1_GPIO3_MASK | MXC_CCM_CGR1_GPT_MASK | + MXC_CCM_CGR1_IOMUXC_MASK, MXC_CCM_CGR1); + __raw_writel(MXC_CCM_CGR2_MAX_MASK | MXC_CCM_CGR2_SPBA_MASK | + MXC_CCM_CGR2_AUDMUX_MASK | MXC_CCM_CGR2_MAX_ENABLE, + MXC_CCM_CGR2); + __raw_writel(MXC_CCM_CGR3_IIM_MASK, MXC_CCM_CGR3); + __raw_writel((__raw_readl(MXC_CCM_PMCR2) | + MXC_CCM_PMCR2_OSC24M_DOWN | + MXC_CCM_PMCR2_OSC_AUDIO_DOWN), MXC_CCM_PMCR2); + mxc_update_clocks(); + pr_info("Clock input source is %ld\n", ckih_clk.rate); + + /* Determine which high frequency clock source is coming in */ + cpu_wp_tbl = get_cpu_wp(&cpu_wp_nr); + sync_cpu_wb(); + + /* This will propagate to all children and init all the clock rates */ + propagate_rate(&ckih_clk); + propagate_rate(&ext_32k_clk); + propagate_rate(&ckie_clk); + + clk_enable(&mcu_pll_clk); + clk_enable(&gpt_clk); + clk_enable(&emi_clk); + clk_enable(&iim_clk); + clk_enable(&spba_clk); + + /* Init serial PLL according */ + clk_set_rate(&peri_pll_clk, 300000000); + + clk_enable(&peri_pll_clk); + + mxc_timer_init(&gpt_clk, IO_ADDRESS(GPT1_BASE_ADDR), MXC_INT_GPT); + + return 0; +} diff --git a/arch/arm/mach-mx35/cpu.c b/arch/arm/mach-mx35/cpu.c new file mode 100644 index 000000000000..90848a55e0ad --- /dev/null +++ b/arch/arm/mach-mx35/cpu.c @@ -0,0 +1,82 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mach-mx35/cpu.c + * + * @brief This file contains the CPU initialization code. + * + * @ingroup MSL_MX35 + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <mach/hardware.h> +#include <linux/io.h> +#include <asm/hardware/cache-l2x0.h> + +/*! + * CPU initialization. It is called by fixup_mxc_board() + */ +void __init mxc_cpu_init(void) +{ + + /* Setup Peripheral Port Remap register for AVIC */ + asm("ldr r0, =0xC0000015 \n\ + mcr p15, 0, r0, c15, c2, 4"); + /*TODO:Add code to check chip version */ + + if (!system_rev) + mxc_set_system_rev(0x35, CHIP_REV_1_0); + +} + +/*! + * Post CPU init code + * + * @return 0 always + */ +static int __init post_cpu_init(void) +{ + void *l2_base; + unsigned long aips_reg; + + /* Initialize L2 cache */ + l2_base = ioremap(L2CC_BASE_ADDR, SZ_4K); + if (l2_base) + l2x0_init(l2_base, 0x00030024, 0x00000000); + + /* + * S/W workaround: Clear the off platform peripheral modules + * Supervisor Protect bit for SDMA to access them. + */ + __raw_writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x40)); + __raw_writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x44)); + __raw_writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x48)); + __raw_writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x4C)); + aips_reg = __raw_readl(IO_ADDRESS(AIPS1_BASE_ADDR + 0x50)); + aips_reg &= 0x00FFFFFF; + __raw_writel(aips_reg, IO_ADDRESS(AIPS1_BASE_ADDR + 0x50)); + __raw_writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x40)); + __raw_writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x44)); + __raw_writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x48)); + __raw_writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x4C)); + aips_reg = __raw_readl(IO_ADDRESS(AIPS2_BASE_ADDR + 0x50)); + aips_reg &= 0x00FFFFFF; + __raw_writel(aips_reg, IO_ADDRESS(AIPS2_BASE_ADDR + 0x50)); + + return 0; +} + +postcore_initcall(post_cpu_init); diff --git a/arch/arm/mach-mx35/crm_regs.h b/arch/arm/mach-mx35/crm_regs.h new file mode 100644 index 000000000000..b770b4841e8e --- /dev/null +++ b/arch/arm/mach-mx35/crm_regs.h @@ -0,0 +1,430 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ARCH_ARM_MACH_MX35_CRM_REGS_H__ +#define __ARCH_ARM_MACH_MX35_CRM_REGS_H__ + +#define CKIH_CLK_FREQ 24000000 +#define CKIE_CLK_FREQ 24576000 +#define CKIL_CLK_FREQ 32000 +#define CKIL_EXT_FREQ 32768 + +#define MXC_CCM_BASE ((char *)IO_ADDRESS(CCM_BASE_ADDR)) + +/* Register addresses */ +#define MXC_CCM_CCMR (MXC_CCM_BASE + 0x00) +#define MXC_CCM_PDR0 (MXC_CCM_BASE + 0x04) +#define MXC_CCM_PDR1 (MXC_CCM_BASE + 0x08) +#define MXC_CCM_PDR2 (MXC_CCM_BASE + 0x0C) +#define MXC_CCM_PDR3 (MXC_CCM_BASE + 0x10) +#define MXC_CCM_PDR4 (MXC_CCM_BASE + 0x14) +#define MXC_CCM_RCSR (MXC_CCM_BASE + 0x18) +#define MXC_CCM_MPCTL (MXC_CCM_BASE + 0x1C) +#define MXC_CCM_PPCTL (MXC_CCM_BASE + 0x20) +#define MXC_CCM_ACMR (MXC_CCM_BASE + 0x24) +#define MXC_CCM_COSR (MXC_CCM_BASE + 0x28) +#define MXC_CCM_CGR0 (MXC_CCM_BASE + 0x2C) +#define MXC_CCM_CGR1 (MXC_CCM_BASE + 0x30) +#define MXC_CCM_CGR2 (MXC_CCM_BASE + 0x34) +#define MXC_CCM_CGR3 (MXC_CCM_BASE + 0x38) +#define MXC_CCM_RESV (MXC_CCM_BASE + 0x3C) +#define MXC_CCM_DCVR0 (MXC_CCM_BASE + 0x40) +#define MXC_CCM_DCVR1 (MXC_CCM_BASE + 0x44) +#define MXC_CCM_DCVR2 (MXC_CCM_BASE + 0x48) +#define MXC_CCM_DCVR3 (MXC_CCM_BASE + 0x4C) +#define MXC_CCM_LTR0 (MXC_CCM_BASE + 0x50) +#define MXC_CCM_LTR1 (MXC_CCM_BASE + 0x54) +#define MXC_CCM_LTR2 (MXC_CCM_BASE + 0x58) +#define MXC_CCM_LTR3 (MXC_CCM_BASE + 0x5C) +#define MXC_CCM_LTBR0 (MXC_CCM_BASE + 0x60) +#define MXC_CCM_LTBR1 (MXC_CCM_BASE + 0x64) +#define MXC_CCM_PMCR0 (MXC_CCM_BASE + 0x68) +#define MXC_CCM_PMCR1 (MXC_CCM_BASE + 0x6C) +#define MXC_CCM_PMCR2 (MXC_CCM_BASE + 0x70) + +/* Register bit definitions */ +#define MXC_CCM_CCMR_WFI (1 << 30) +#define MXC_CCM_CCMR_STBY_EXIT_SRC (1 << 29) +#define MXC_CCM_CCMR_VSTBY (1 << 28) +#define MXC_CCM_CCMR_WBEN (1 << 27) +#define MXC_CCM_CCMR_VOL_RDY_CNT_OFFSET 20 +#define MXC_CCM_CCMR_VOL_RDY_CNT_MASK (0xF << 20) +#define MXC_CCM_CCMR_ROMW_OFFSET 18 +#define MXC_CCM_CCMR_ROMW_MASK (0x3 << 18) +#define MXC_CCM_CCMR_RAMW_OFFSET 21 +#define MXC_CCM_CCMR_RAMW_MASK (0x3 << 21) +#define MXC_CCM_CCMR_LPM_OFFSET 14 +#define MXC_CCM_CCMR_LPM_MASK (0x3 << 14) +#define MXC_CCM_CCMR_UPE (1 << 9) +#define MXC_CCM_CCMR_MPE (1 << 3) + +#define MXC_CCM_PDR0_PER_SEL (1 << 26) +#define MXC_CCM_PDR0_IPU_HND_BYP (1 << 23) +#define MXC_CCM_PDR0_HSP_PODF_OFFSET 20 +#define MXC_CCM_PDR0_HSP_PODF_MASK (0x3 << 20) +#define MXC_CCM_PDR0_CON_MUX_DIV_OFFSET 16 +#define MXC_CCM_PDR0_CON_MUX_DIV_MASK (0xF << 16) +#define MXC_CCM_PDR0_CKIL_SEL (1 << 15) +#define MXC_CCM_PDR0_PER_PODF_OFFSET 12 +#define MXC_CCM_PDR0_PER_PODF_MASK (0xF << 12) +#define MXC_CCM_PDR0_AUTO_MUX_DIV_OFFSET 9 +#define MXC_CCM_PDR0_AUTO_MUX_DIV_MASK (0x7 << 9) +#define MXC_CCM_PDR0_AUTO_CON 0x1 + +#define MXC_CCM_PDR1_MSHC_PRDF_OFFSET 28 +#define MXC_CCM_PDR1_MSHC_PRDF_MASK (0x7 << 28) +#define MXC_CCM_PDR1_MSHC_PODF_OFFSET 22 +#define MXC_CCM_PDR1_MSHC_PODF_MASK (0x3F << 22) +#define MXC_CCM_PDR1_MSHC_M_U (1 << 7) + +#define MXC_CCM_PDR2_SSI2_PRDF_OFFSET 27 +#define MXC_CCM_PDR2_SSI2_PRDF_MASK (0x7 << 27) +#define MXC_CCM_PDR2_SSI1_PRDF_OFFSET 24 +#define MXC_CCM_PDR2_SSI1_PRDF_MASK (0x7 << 24) +#define MXC_CCM_PDR2_CSI_PRDF_OFFSET 19 +#define MXC_CCM_PDR2_CSI_PRDF_MASK (0x7 << 19) +#define MXC_CCM_PDR2_CSI_PODF_OFFSET 16 +#define MXC_CCM_PDR2_CSI_PODF_MASK (0x7 << 16) +#define MXC_CCM_PDR2_SSI2_PODF_OFFSET 8 +#define MXC_CCM_PDR2_SSI2_PODF_MASK (0x3F << 8) +#define MXC_CCM_PDR2_CSI_M_U (1 << 7) +#define MXC_CCM_PDR2_SSI_M_U (1 << 6) +#define MXC_CCM_PDR2_SSI1_PODF_OFFSET 0 +#define MXC_CCM_PDR2_SSI1_PODF_MASK (0x3F) + +/* Extra definitions for Chip Version 2*/ +#define MXC_CCM_PDR2_CSI_PODF_MASK_V2 (0x3F << 16) + +#define MXC_CCM_PDR3_SPDIF_PRDF_OFFSET 29 +#define MXC_CCM_PDR3_SPDIF_PRDF_MASK (0x7 << 29) +#define MXC_CCM_PDR3_SPDIF_PODF_OFFSET 23 +#define MXC_CCM_PDR3_SPDIF_PODF_MASK (0x3F << 23) +#define MXC_CCM_PDR3_SPDIF_M_U (1 << 22) +#define MXC_CCM_PDR3_ESDHC3_PRDF_OFFSET 19 +#define MXC_CCM_PDR3_ESDHC3_PRDF_MASK (0x7 << 19) +#define MXC_CCM_PDR3_ESDHC3_PODF_OFFSET 16 +#define MXC_CCM_PDR3_ESDHC3_PODF_MASK (0x7 << 16) +#define MXC_CCM_PDR3_UART_M_U (1 << 15) +#define MXC_CCM_PDR3_ESDHC2_PRDF_OFFSET 11 +#define MXC_CCM_PDR3_ESDHC2_PRDF_MASK (0x7 << 11) +#define MXC_CCM_PDR3_ESDHC2_PODF_OFFSET 8 +#define MXC_CCM_PDR3_ESDHC2_PODF_MASK (0x7 << 8) +#define MXC_CCM_PDR3_ESDHC_M_U (1 << 6) +#define MXC_CCM_PDR3_ESDHC1_PRDF_OFFSET 3 +#define MXC_CCM_PDR3_ESDHC1_PRDF_MASK (0x7 << 3) +#define MXC_CCM_PDR3_ESDHC1_PODF_OFFSET 0 +#define MXC_CCM_PDR3_ESDHC1_PODF_MASK (0x7) + +/* Extra definitions for Chip Version 2 */ +#define MXC_CCM_PDR3_ESDHC3_PODF_MASK_V2 (0x3F << 16) +#define MXC_CCM_PDR3_ESDHC2_PODF_MASK_V2 (0x3F << 8) +#define MXC_CCM_PDR3_ESDHC1_PODF_MASK_V2 0x3F + +#define MXC_CCM_PDR4_NFC_PODF_OFFSET 28 +#define MXC_CCM_PDR4_NFC_PODF_MASK (0xF << 28) +#define MXC_CCM_PDR4_USB_PRDF_OFFSET 25 +#define MXC_CCM_PDR4_USB_PRDF_MASK (0x7 << 25) +#define MXC_CCM_PDR4_USB_PODF_OFFSET 22 +#define MXC_CCM_PDR4_USB_PODF_MASK (0x7 << 22) +#define MXC_CCM_PDR4_PER0_PRDF_OFFSET 19 +#define MXC_CCM_PDR4_PER0_PRDF_MASK (0x7 << 19) +#define MXC_CCM_PDR4_PER0_PODF_OFFSET 16 +#define MXC_CCM_PDR4_PER0_PODF_MASK (0x7 << 16) +#define MXC_CCM_PDR4_UART_PRDF_OFFSET 13 +#define MXC_CCM_PDR4_UART_PRDF_MASK (0x7 << 13) +#define MXC_CCM_PDR4_UART_PODF_OFFSET 10 +#define MXC_CCM_PDR4_UART_PODF_MASK (0x7 << 10) +#define MXC_CCM_PDR4_USB_M_U (1 << 9) + +/* Extra definitions for Chip Version 2 */ +#define MXC_CCM_PDR4_USB_PODF_MASK_V2 (0x3F << 22) +#define MXC_CCM_PDR4_PER0_PODF_MASK_V2 (0x3F << 16) +#define MXC_CCM_PDR4_UART_PODF_MASK_V2 (0x3F << 10) + +/* Bit definitions for RCSR */ +#define MXC_CCM_RCSR_BUS_WIDTH (1 << 29) +#define MXC_CCM_RCSR_BUS_16BIT (1 << 29) +#define MXC_CCM_RCSR_PAGE_SIZE (3 << 27) +#define MXC_CCM_RCSR_PAGE_512 (0 << 27) +#define MXC_CCM_RCSR_PAGE_2K (1 << 27) +#define MXC_CCM_RCSR_PAGE_4K1 (2 << 27) +#define MXC_CCM_RCSR_PAGE_4K2 (3 << 27) +#define MXC_CCM_RCSR_SOFT_RESET (1 << 15) +#define MXC_CCM_RCSR_NF16B (1 << 14) +#define MXC_CCM_RCSR_NFC_4K (1 << 9) +#define MXC_CCM_RCSR_NFC_FMS (1 << 8) + +/* Bit definitions for both MCU, PERIPHERAL PLL control registers */ +#define MXC_CCM_PCTL_BRM 0x80000000 +#define MXC_CCM_PCTL_PD_OFFSET 26 +#define MXC_CCM_PCTL_PD_MASK (0xF << 26) +#define MXC_CCM_PCTL_MFD_OFFSET 16 +#define MXC_CCM_PCTL_MFD_MASK (0x3FF << 16) +#define MXC_CCM_PCTL_MFI_OFFSET 10 +#define MXC_CCM_PCTL_MFI_MASK (0xF << 10) +#define MXC_CCM_PCTL_MFN_OFFSET 0 +#define MXC_CCM_PCTL_MFN_MASK 0x3FF + +/* Bit definitions for Audio clock mux register*/ +#define MXC_CCM_ACMR_ESAI_CLK_SEL_OFFSET 12 +#define MXC_CCM_ACMR_ESAI_CLK_SEL_MASK (0xF << 12) +#define MXC_CCM_ACMR_SPDIF_CLK_SEL_OFFSET 8 +#define MXC_CCM_ACMR_SPDIF_CLK_SEL_MASK (0xF << 8) +#define MXC_CCM_ACMR_SSI1_CLK_SEL_OFFSET 4 +#define MXC_CCM_ACMR_SSI1_CLK_SEL_MASK (0xF << 4) +#define MXC_CCM_ACMR_SSI2_CLK_SEL_OFFSET 0 +#define MXC_CCM_ACMR_SSI2_CLK_SEL_MASK (0xF << 0) + +/* Extra definitions for Version 2 */ +#define MXC_CCM_ACMR_CKILH_PODF0_OFFSET 16 +#define MXC_CCM_ACMR_CKILH_PODF1_OFFSET 19 +#define MXC_CCM_ACMR_CKILH_PODF2_OFFSET 22 +#define MXC_CCM_ACMR_CKILH_PODF3_OFFSET 25 +#define MXC_CCM_ACMR_CKILH_PODF_MASK 0x7 + +/* Bit definitions for Clock gating Register*/ +#define MXC_CCM_CGR0_ASRC_OFFSET 0 +#define MXC_CCM_CGR0_ASRC_MASK (0x3 << 0) +#define MXC_CCM_CGR0_ATA_OFFSET 2 +#define MXC_CCM_CGR0_ATA_MASK (0x3 << 2) +#define MXC_CCM_CGR0_CAN1_OFFSET 6 +#define MXC_CCM_CGR0_CAN1_MASK (0x3 << 6) +#define MXC_CCM_CGR0_CAN2_OFFSET 8 +#define MXC_CCM_CGR0_CAN2_MASK (0x3 << 8) +#define MXC_CCM_CGR0_CSPI1_OFFSET 10 +#define MXC_CCM_CGR0_CSPI1_MASK (0x3 << 10) +#define MXC_CCM_CGR0_CSPI2_OFFSET 12 +#define MXC_CCM_CGR0_CSPI2_MASK (0x3 << 12) +#define MXC_CCM_CGR0_ECT_OFFSET 14 +#define MXC_CCM_CGR0_ECT_MASK (0x3 << 14) +#define MXC_CCM_CGR0_EMI_OFFSET 18 +#define MXC_CCM_CGR0_EMI_MASK (0x3 << 18) +#define MXC_CCM_CGR0_EPIT1_OFFSET 20 +#define MXC_CCM_CGR0_EPIT1_MASK (0x3 << 20) +#define MXC_CCM_CGR0_EPIT2_OFFSET 22 +#define MXC_CCM_CGR0_EPIT2_MASK (0x3 << 22) +#define MXC_CCM_CGR0_ESAI_OFFSET 24 +#define MXC_CCM_CGR0_ESAI_MASK (0x3 << 24) +#define MXC_CCM_CGR0_ESDHC1_OFFSET 26 +#define MXC_CCM_CGR0_ESDHC1_MASK (0x3 << 26) +#define MXC_CCM_CGR0_ESDHC2_OFFSET 28 +#define MXC_CCM_CGR0_ESDHC2_MASK (0x3 << 28) +#define MXC_CCM_CGR0_ESDHC3_OFFSET 30 +#define MXC_CCM_CGR0_ESDHC3_MASK (0x3 << 30) + +#define MXC_CCM_CGR1_FEC_OFFSET 0 +#define MXC_CCM_CGR1_FEC_MASK (0x3 << 0) +#define MXC_CCM_CGR1_GPIO1_OFFSET 2 +#define MXC_CCM_CGR1_GPIO1_MASK (0x3 << 2) +#define MXC_CCM_CGR1_GPIO2_OFFSET 4 +#define MXC_CCM_CGR1_GPIO2_MASK (0x3 << 4) +#define MXC_CCM_CGR1_GPIO3_OFFSET 6 +#define MXC_CCM_CGR1_GPIO3_MASK (0x3 << 6) +#define MXC_CCM_CGR1_GPT_OFFSET 8 +#define MXC_CCM_CGR1_GPT_MASK (0x3 << 8) +#define MXC_CCM_CGR1_I2C1_OFFSET 10 +#define MXC_CCM_CGR1_I2C1_MASK (0x3 << 10) +#define MXC_CCM_CGR1_I2C2_OFFSET 12 +#define MXC_CCM_CGR1_I2C2_MASK (0x3 << 12) +#define MXC_CCM_CGR1_I2C3_OFFSET 14 +#define MXC_CCM_CGR1_I2C3_MASK (0x3 << 14) +#define MXC_CCM_CGR1_IOMUXC_OFFSET 16 +#define MXC_CCM_CGR1_IOMUXC_MASK (0x3 << 16) +#define MXC_CCM_CGR1_IPU_OFFSET 18 +#define MXC_CCM_CGR1_IPU_MASK (0x3 << 18) +#define MXC_CCM_CGR1_KPP_OFFSET 20 +#define MXC_CCM_CGR1_KPP_MASK (0x3 << 20) +#define MXC_CCM_CGR1_MLB_OFFSET 22 +#define MXC_CCM_CGR1_MLB_MASK (0x3 << 22) +#define MXC_CCM_CGR1_MSHC_OFFSET 24 +#define MXC_CCM_CGR1_MSHC_MASK (0x3 << 24) +#define MXC_CCM_CGR1_OWIRE_OFFSET 26 +#define MXC_CCM_CGR1_OWIRE_MASK (0x3 << 26) +#define MXC_CCM_CGR1_PWM_OFFSET 28 +#define MXC_CCM_CGR1_PWM_MASK (0x3 << 28) +#define MXC_CCM_CGR1_RNGC_OFFSET 30 +#define MXC_CCM_CGR1_RNGC_MASK (0x3 << 30) + +#define MXC_CCM_CGR2_RTC_OFFSET 0 +#define MXC_CCM_CGR2_RTC_MASK (0x3 << 0) +#define MXC_CCM_CGR2_RTIC_OFFSET 2 +#define MXC_CCM_CGR2_RTIC_MASK (0x3 << 2) +#define MXC_CCM_CGR2_SCC_OFFSET 4 +#define MXC_CCM_CGR2_SCC_MASK (0x3 << 4) +#define MXC_CCM_CGR2_SDMA_OFFSET 6 +#define MXC_CCM_CGR2_SDMA_MASK (0x3 << 6) +#define MXC_CCM_CGR2_SPBA_OFFSET 8 +#define MXC_CCM_CGR2_SPBA_MASK (0x3 << 8) +#define MXC_CCM_CGR2_SPDIF_OFFSET 10 +#define MXC_CCM_CGR2_SPDIF_MASK (0x3 << 10) +#define MXC_CCM_CGR2_SSI1_OFFSET 12 +#define MXC_CCM_CGR2_SSI1_MASK (0x3 << 12) +#define MXC_CCM_CGR2_SSI2_OFFSET 14 +#define MXC_CCM_CGR2_SSI2_MASK (0x3 << 14) +#define MXC_CCM_CGR2_UART1_OFFSET 16 +#define MXC_CCM_CGR2_UART1_MASK (0x3 << 16) +#define MXC_CCM_CGR2_UART2_OFFSET 18 +#define MXC_CCM_CGR2_UART2_MASK (0x3 << 18) +#define MXC_CCM_CGR2_UART3_OFFSET 20 +#define MXC_CCM_CGR2_UART3_MASK (0x3 << 20) +#define MXC_CCM_CGR2_USBOTG_OFFSET 22 +#define MXC_CCM_CGR2_USBOTG_MASK (0x3 << 22) +#define MXC_CCM_CGR2_WDOG_OFFSET 24 +#define MXC_CCM_CGR2_WDOG_MASK (0x3 << 24) +#define MXC_CCM_CGR2_MAX_OFFSET 26 +#define MXC_CCM_CGR2_MAX_MASK (0x3 << 26) +#define MXC_CCM_CGR2_MAX_ENABLE (0x2 << 26) +#define MXC_CCM_CGR2_AUDMUX_OFFSET 30 +#define MXC_CCM_CGR2_AUDMUX_MASK (0x3 << 30) + +#define MXC_CCM_CGR3_CSI_OFFSET 0 +#define MXC_CCM_CGR3_CSI_MASK (0x3 << 0) +#define MXC_CCM_CGR3_IIM_OFFSET 2 +#define MXC_CCM_CGR3_IIM_MASK (0x3 << 2) +#define MXC_CCM_CGR3_GPU2D_OFFSET 4 +#define MXC_CCM_CGR3_GPU2D_MASK (0x3 << 4) +/* + * LTR0 register offsets + */ +#define MXC_CCM_LTR0_DNTHR_OFFSET 16 +#define MXC_CCM_LTR0_DNTHR_MASK (0x3F << 16) +#define MXC_CCM_LTR0_UPTHR_OFFSET 22 +#define MXC_CCM_LTR0_UPTHR_MASK (0x3F << 22) +#define MXC_CCM_LTR0_DIV3CK_OFFSET 1 +#define MXC_CCM_LTR0_DIV3CK_MASK (0x3 << 1) + +/* + * LTR1 register offsets + */ +#define MXC_CCM_LTR1_PNCTHR_OFFSET 0 +#define MXC_CCM_LTR1_PNCTHR_MASK 0x3F +#define MXC_CCM_LTR1_UPCNT_OFFSET 6 +#define MXC_CCM_LTR1_UPCNT_MASK (0xFF << 6) +#define MXC_CCM_LTR1_DNCNT_OFFSET 14 +#define MXC_CCM_LTR1_DNCNT_MASK (0xFF << 14) +#define MXC_CCM_LTR1_LTBRSR_MASK 0x400000 +#define MXC_CCM_LTR1_LTBRSR_OFFSET 22 +#define MXC_CCM_LTR1_LTBRSR 0x400000 +#define MXC_CCM_LTR1_LTBRSH 0x800000 + +/* + * LTR2 bit definitions. x ranges from 0 for WSW9 to 6 for WSW15 + */ +#define MXC_CCM_LTR2_WSW_OFFSET(x) (11 + (x) * 3) +#define MXC_CCM_LTR2_WSW_MASK(x) (0x7 << MXC_CCM_LTR2_WSW_OFFSET((x))) +#define MXC_CCM_LTR2_EMAC_OFFSET 0 +#define MXC_CCM_LTR2_EMAC_MASK 0x1FF + +/* + * LTR3 bit definitions. x ranges from 0 for WSW0 to 8 for WSW8 + */ +#define MXC_CCM_LTR3_WSW_OFFSET(x) (5 + (x) * 3) +#define MXC_CCM_LTR3_WSW_MASK(x) (0x7 << MXC_CCM_LTR3_WSW_OFFSET((x))) + +#define DVSUP_TURBO 0 +#define DVSUP_HIGH 1 +#define DVSUP_MEDIUM 2 +#define DVSUP_LOW 3 +#define MXC_CCM_PMCR0_DVSUP_TURBO (DVSUP_TURBO << 28) +#define MXC_CCM_PMCR0_DVSUP_HIGH (DVSUP_HIGH << 28) +#define MXC_CCM_PMCR0_DVSUP_MEDIUM (DVSUP_MEDIUM << 28) +#define MXC_CCM_PMCR0_DVSUP_LOW (DVSUP_LOW << 28) +#define MXC_CCM_PMCR0_DVSUP_OFFSET 28 +#define MXC_CCM_PMCR0_DVSUP_MASK (0x3 << 28) +#define MXC_CCM_PMCR0_DVFS_UPDATE_FINISH 0x01000000 +#define MXC_CCM_PMCR0_DVFEV 0x00800000 +#define MXC_CCM_PMCR0_DVFIS 0x00400000 +#define MXC_CCM_PMCR0_LBMI 0x00200000 +#define MXC_CCM_PMCR0_LBFL 0x00100000 +#define MXC_CCM_PMCR0_LBCF_4 (0x0 << 18) +#define MXC_CCM_PMCR0_LBCF_8 (0x1 << 18) +#define MXC_CCM_PMCR0_LBCF_12 (0x2 << 18) +#define MXC_CCM_PMCR0_LBCF_16 (0x3 << 18) +#define MXC_CCM_PMCR0_LBCF_OFFSET 18 +#define MXC_CCM_PMCR0_LBCF_MASK (0x3 << 18) +#define MXC_CCM_PMCR0_PTVIS 0x00020000 +#define MXC_CCM_PMCR0_DVFS_START 0x00010000 +#define MXC_CCM_PMCR0_DVFS_START_MASK 0x1 << 16) +#define MXC_CCM_PMCR0_FSVAIM 0x00008000 +#define MXC_CCM_PMCR0_FSVAI_OFFSET 13 +#define MXC_CCM_PMCR0_FSVAI_MASK (0x3 << 13) +#define MXC_CCM_PMCR0_DPVCR 0x00001000 +#define MXC_CCM_PMCR0_DPVV 0x00000800 +#define MXC_CCM_PMCR0_WFIM 0x00000400 +#define MXC_CCM_PMCR0_DRCE3 0x00000200 +#define MXC_CCM_PMCR0_DRCE2 0x00000100 +#define MXC_CCM_PMCR0_DRCE1 0x00000080 +#define MXC_CCM_PMCR0_DRCE0 0x00000040 +#define MXC_CCM_PMCR0_DCR 0x00000020 +#define MXC_CCM_PMCR0_DVFEN 0x00000010 +#define MXC_CCM_PMCR0_PTVAIM 0x00000008 +#define MXC_CCM_PMCR0_PTVAI_OFFSET 1 +#define MXC_CCM_PMCR0_PTVAI_MASK (0x3 << 1) +#define MXC_CCM_PMCR0_DPTEN 0x00000001 + +#define MXC_CCM_PMCR1_DVGP_OFFSET 0 +#define MXC_CCM_PMCR1_DVGP_MASK (0xF) + +#define MXC_CCM_PMCR1_PLLRDIS (0x1 << 7) +#define MXC_CCM_PMCR1_EMIRQ_EN (0x1 << 8) + +#define MXC_CCM_DCVR_ULV_MASK (0x3FF << 22) +#define MXC_CCM_DCVR_ULV_OFFSET 22 +#define MXC_CCM_DCVR_LLV_MASK (0x3FF << 12) +#define MXC_CCM_DCVR_LLV_OFFSET 12 +#define MXC_CCM_DCVR_ELV_MASK (0x3FF << 2) +#define MXC_CCM_DCVR_ELV_OFFSET 2 + +#define MXC_CCM_PDR2_MST2_PDF_MASK (0x3F << 7) +#define MXC_CCM_PDR2_MST2_PDF_OFFSET 7 +#define MXC_CCM_PDR2_MST1_PDF_MASK 0x3F +#define MXC_CCM_PDR2_MST1_PDF_OFFSET 0 + +#define MXC_CCM_COSR_CLKOSEL_MASK 0x1F +#define MXC_CCM_COSR_CLKOSEL_OFFSET 0 +#define MXC_CCM_COSR_CLKOEN (1 << 5) +#define MXC_CCM_COSR_CLKOUTDIV_1 (1 << 6) +#define MXC_CCM_COSR_CLKOUT_PREDIV_MASK (0x7 << 13) +#define MXC_CCM_COSR_CLKOUT_PREDIV_OFFSET 13 +#define MXC_CCM_COSR_CLKOUT_PRODIV_MASK (0x7 << 10) +#define MXC_CCM_COSR_CLKOUT_PRODIV_OFFSET 10 +#define MXC_CCM_COSR_SSI1_RX_SRC_SEL_MASK (0x3 << 16) +#define MXC_CCM_COSR_SSI1_RX_SRC_SEL_OFFSET 16 +#define MXC_CCM_COSR_SSI1_TX_SRC_SEL_MASK (0x3 << 18) +#define MXC_CCM_COSR_SSI1_TX_SRC_SEL_OFFSET 18 +#define MXC_CCM_COSR_SSI2_RX_SRC_SEL_MASK (0x3 << 20) +#define MXC_CCM_COSR_SSI2_RX_SRC_SEL_OFFSET 20 +#define MXC_CCM_COSR_SSI2_TX_SRC_SEL_MASK (0x3 << 22) +#define MXC_CCM_COSR_SSI2_TX_SRC_SEL_OFFSET 22 +#define MXC_CCM_COSR_ASRC_AUDIO_EN (1 << 24) +#define MXC_CCM_COSR_ASRC_AUDIO_PODF_MASK (0x3F << 26) +#define MXC_CCM_COSR_ASRC_AUDIO_PODF_OFFSET 26 + +/* extra definitions for Version 2 */ +#define MXC_CCM_COSR_CKIL_CKIH_MASK (1 << 7) +#define MXC_CCM_COSR_CKIL_CKIH_OFFSET 7 +#define MXC_CCM_COSR_CLKOUT_PRODIV_MASK_V2 (0x3F << 10) + +/* + * PMCR0 register offsets + */ +#define MXC_CCM_PMCR0_LBFL_OFFSET 20 +#define MXC_CCM_PMCR0_DFSUP0_OFFSET 30 +#define MXC_CCM_PMCR0_DFSUP1_OFFSET 31 + +/* + * PMCR2 register definitions + */ +#define MXC_CCM_PMCR2_OSC24M_DOWN (1 << 16) +#define MXC_CCM_PMCR2_OSC_AUDIO_DOWN (1 << 17) + +#endif /* __ARCH_ARM_MACH_MX3_CRM_REGS_H__ */ diff --git a/arch/arm/mach-mx35/devices.c b/arch/arm/mach-mx35/devices.c new file mode 100644 index 000000000000..769b7ac0541f --- /dev/null +++ b/arch/arm/mach-mx35/devices.c @@ -0,0 +1,804 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/spi/spi.h> + +#include <mach/hardware.h> +#include <mach/mmc.h> +#include <mach/spba.h> +#include <mach/sdma.h> + +#include "iomux.h" +#include "sdma_script_code.h" +#include "sdma_script_code_v2.h" +#include "board-mx35_3stack.h" + +void mxc_sdma_get_script_info(sdma_script_start_addrs * sdma_script_addr) +{ + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) { + sdma_script_addr->mxc_sdma_ap_2_ap_addr = ap_2_ap_ADDR; + sdma_script_addr->mxc_sdma_ap_2_bp_addr = -1; + sdma_script_addr->mxc_sdma_bp_2_ap_addr = -1; + sdma_script_addr->mxc_sdma_loopback_on_dsp_side_addr = -1; + sdma_script_addr->mxc_sdma_mcu_interrupt_only_addr = -1; + + sdma_script_addr->mxc_sdma_firi_2_per_addr = -1; + sdma_script_addr->mxc_sdma_firi_2_mcu_addr = -1; + sdma_script_addr->mxc_sdma_per_2_firi_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_firi_addr = -1; + + sdma_script_addr->mxc_sdma_uart_2_per_addr = uart_2_per_ADDR; + sdma_script_addr->mxc_sdma_uart_2_mcu_addr = uart_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_per_2_app_addr = per_2_app_ADDR; + sdma_script_addr->mxc_sdma_mcu_2_app_addr = mcu_2_app_ADDR; + + sdma_script_addr->mxc_sdma_per_2_per_addr = p_2_p_ADDR; + + sdma_script_addr->mxc_sdma_uartsh_2_per_addr = + uartsh_2_per_ADDR; + sdma_script_addr->mxc_sdma_uartsh_2_mcu_addr = + uartsh_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_per_2_shp_addr = per_2_shp_ADDR; + sdma_script_addr->mxc_sdma_mcu_2_shp_addr = mcu_2_shp_ADDR; + + sdma_script_addr->mxc_sdma_ata_2_mcu_addr = ata_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_mcu_2_ata_addr = mcu_2_ata_ADDR; + + sdma_script_addr->mxc_sdma_app_2_per_addr = app_2_per_ADDR; + sdma_script_addr->mxc_sdma_app_2_mcu_addr = app_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_shp_2_per_addr = shp_2_per_ADDR; + sdma_script_addr->mxc_sdma_shp_2_mcu_addr = shp_2_mcu_ADDR; + + sdma_script_addr->mxc_sdma_mshc_2_mcu_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_mshc_addr = -1; + + sdma_script_addr->mxc_sdma_spdif_2_mcu_addr = spdif_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_mcu_2_spdif_addr = mcu_2_spdif_ADDR; + + sdma_script_addr->mxc_sdma_asrc_2_mcu_addr = asrc__mcu_ADDR; + + sdma_script_addr->mxc_sdma_dptc_dvfs_addr = -1; + sdma_script_addr->mxc_sdma_ext_mem_2_ipu_addr = + ext_mem__ipu_ram_ADDR; + sdma_script_addr->mxc_sdma_descrambler_addr = -1; + + sdma_script_addr->mxc_sdma_start_addr = + (unsigned short *)sdma_code; + sdma_script_addr->mxc_sdma_ram_code_size = RAM_CODE_SIZE; + sdma_script_addr->mxc_sdma_ram_code_start_addr = + RAM_CODE_START_ADDR; + } else { + sdma_script_addr->mxc_sdma_ap_2_ap_addr = ap_2_ap_ADDR_V2; + sdma_script_addr->mxc_sdma_ap_2_bp_addr = -1; + sdma_script_addr->mxc_sdma_bp_2_ap_addr = -1; + sdma_script_addr->mxc_sdma_loopback_on_dsp_side_addr = -1; + sdma_script_addr->mxc_sdma_mcu_interrupt_only_addr = -1; + + sdma_script_addr->mxc_sdma_firi_2_per_addr = -1; + sdma_script_addr->mxc_sdma_firi_2_mcu_addr = -1; + sdma_script_addr->mxc_sdma_per_2_firi_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_firi_addr = -1; + + sdma_script_addr->mxc_sdma_uart_2_per_addr = uart_2_per_ADDR_V2; + sdma_script_addr->mxc_sdma_uart_2_mcu_addr = uart_2_mcu_ADDR_V2; + sdma_script_addr->mxc_sdma_per_2_app_addr = per_2_app_ADDR_V2; + sdma_script_addr->mxc_sdma_mcu_2_app_addr = mcu_2_app_ADDR_V2; + + sdma_script_addr->mxc_sdma_per_2_per_addr = p_2_p_ADDR_V2; + + sdma_script_addr->mxc_sdma_uartsh_2_per_addr = + uartsh_2_per_ADDR_V2; + sdma_script_addr->mxc_sdma_uartsh_2_mcu_addr = + uartsh_2_mcu_ADDR_V2; + sdma_script_addr->mxc_sdma_per_2_shp_addr = per_2_shp_ADDR_V2; + sdma_script_addr->mxc_sdma_mcu_2_shp_addr = mcu_2_shp_ADDR_V2; + + sdma_script_addr->mxc_sdma_ata_2_mcu_addr = ata_2_mcu_ADDR_V2; + sdma_script_addr->mxc_sdma_mcu_2_ata_addr = mcu_2_ata_ADDR_V2; + + sdma_script_addr->mxc_sdma_app_2_per_addr = app_2_per_ADDR_V2; + sdma_script_addr->mxc_sdma_app_2_mcu_addr = app_2_mcu_ADDR_V2; + sdma_script_addr->mxc_sdma_shp_2_per_addr = shp_2_per_ADDR_V2; + sdma_script_addr->mxc_sdma_shp_2_mcu_addr = shp_2_mcu_ADDR_V2; + + sdma_script_addr->mxc_sdma_mshc_2_mcu_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_mshc_addr = -1; + + sdma_script_addr->mxc_sdma_spdif_2_mcu_addr = + spdif_2_mcu_ADDR_V2; + sdma_script_addr->mxc_sdma_mcu_2_spdif_addr = + mcu_2_spdif_ADDR_V2; + + sdma_script_addr->mxc_sdma_asrc_2_mcu_addr = asrc__mcu_ADDR_V2; + + sdma_script_addr->mxc_sdma_dptc_dvfs_addr = -1; + sdma_script_addr->mxc_sdma_ext_mem_2_ipu_addr = + ext_mem__ipu_ram_ADDR_V2; + sdma_script_addr->mxc_sdma_descrambler_addr = -1; + + sdma_script_addr->mxc_sdma_start_addr = + (unsigned short *)sdma_code_v2; + sdma_script_addr->mxc_sdma_ram_code_size = RAM_CODE_SIZE; + sdma_script_addr->mxc_sdma_ram_code_start_addr = + RAM_CODE_START_ADDR_V2; + } +} + +static void mxc_nop_release(struct device *dev) +{ + /* Nothing */ +} + +#if defined(CONFIG_RTC_MXC) || defined(CONFIG_RTC_MXC_MODULE) +static struct resource rtc_resources[] = { + { + .start = RTC_BASE_ADDR, + .end = RTC_BASE_ADDR + 0x30, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_INT_RTC, + .flags = IORESOURCE_IRQ, + }, +}; +static struct platform_device mxc_rtc_device = { + .name = "mxc_rtc", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(rtc_resources), + .resource = rtc_resources, +}; +static void mxc_init_rtc(void) +{ + (void)platform_device_register(&mxc_rtc_device); +} +#else +static inline void mxc_init_rtc(void) +{ +} +#endif + +#if defined(CONFIG_MXC_MC9SDZ60_RTC) || defined(CONFIG_MXC_MC9SDZ60_RTC_MODULE) +static struct resource pmic_rtc_resources[] = { + { + .start = MXC_PSEUDO_IRQ_RTC, + .flags = IORESOURCE_IRQ, + }, +}; +static struct platform_device pmic_rtc_device = { + .name = "pmic_rtc", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(pmic_rtc_resources), + .resource = pmic_rtc_resources, +}; +static void pmic_init_rtc(void) +{ + platform_device_register(&pmic_rtc_device); +} +#else +static void pmic_init_rtc(void) +{ +} +#endif + +#if defined(CONFIG_MXC_WATCHDOG) || defined(CONFIG_MXC_WATCHDOG_MODULE) +static struct resource wdt_resources[] = { + { + .start = WDOG1_BASE_ADDR, + .end = WDOG1_BASE_ADDR + 0x30, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device mxc_wdt_device = { + .name = "mxc_wdt", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(wdt_resources), + .resource = wdt_resources, +}; + +static void mxc_init_wdt(void) +{ + (void)platform_device_register(&mxc_wdt_device); +} +#else +static inline void mxc_init_wdt(void) +{ +} +#endif + +#if defined(CONFIG_MXC_IPU) || defined(CONFIG_MXC_IPU_MODULE) +static struct mxc_ipu_config mxc_ipu_data = { + .rev = 2, +}; + +static struct resource ipu_resources[] = { + { + .start = IPU_CTRL_BASE_ADDR, + .end = IPU_CTRL_BASE_ADDR + SZ_4K, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_INT_IPU_SYN, + .flags = IORESOURCE_IRQ, + }, + { + .start = MXC_INT_IPU_ERR, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_ipu_device = { + .name = "mxc_ipu", + .id = -1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_ipu_data, + }, + .num_resources = ARRAY_SIZE(ipu_resources), + .resource = ipu_resources, +}; + +static void mxc_init_ipu(void) +{ + platform_device_register(&mxc_ipu_device); +} +#else +static inline void mxc_init_ipu(void) +{ +} +#endif + +/* SPI controller and device data */ +#if defined(CONFIG_SPI_MXC) || defined(CONFIG_SPI_MXC_MODULE) + +#ifdef CONFIG_SPI_MXC_SELECT1 +/*! + * Resource definition for the CSPI1 + */ +static struct resource mxcspi1_resources[] = { + [0] = { + .start = CSPI1_BASE_ADDR, + .end = CSPI1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CSPI1, + .end = MXC_INT_CSPI1, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC CSPI1 */ +static struct mxc_spi_master mxcspi1_data = { + .maxchipselect = 4, + .spi_version = 7, +}; + +/*! Device Definition for MXC CSPI1 */ +static struct platform_device mxcspi1_device = { + .name = "mxc_spi", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxcspi1_data, + }, + .num_resources = ARRAY_SIZE(mxcspi1_resources), + .resource = mxcspi1_resources, +}; + +#endif /* CONFIG_SPI_MXC_SELECT1 */ + +#ifdef CONFIG_SPI_MXC_SELECT2 +/*! + * Resource definition for the CSPI2 + */ +static struct resource mxcspi2_resources[] = { + [0] = { + .start = CSPI2_BASE_ADDR, + .end = CSPI2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CSPI2, + .end = MXC_INT_CSPI2, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC CSPI2 */ +static struct mxc_spi_master mxcspi2_data = { + .maxchipselect = 4, + .spi_version = 7, +}; + +/*! Device Definition for MXC CSPI2 */ +static struct platform_device mxcspi2_device = { + .name = "mxc_spi", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxcspi2_data, + }, + .num_resources = ARRAY_SIZE(mxcspi2_resources), + .resource = mxcspi2_resources, +}; +#endif /* CONFIG_SPI_MXC_SELECT2 */ + +static inline void mxc_init_spi(void) +{ + /* SPBA configuration for CSPI2 - MCU is set */ + spba_take_ownership(SPBA_CSPI2, SPBA_MASTER_A); +#ifdef CONFIG_SPI_MXC_SELECT1 + if (platform_device_register(&mxcspi1_device) < 0) + printk(KERN_ERR "Error: Registering the SPI Controller_1\n"); +#endif /* CONFIG_SPI_MXC_SELECT1 */ +#ifdef CONFIG_SPI_MXC_SELECT2 + if (platform_device_register(&mxcspi2_device) < 0) + printk(KERN_ERR "Error: Registering the SPI Controller_2\n"); +#endif /* CONFIG_SPI_MXC_SELECT2 */ +} +#else +static inline void mxc_init_spi(void) +{ +} +#endif + +/* I2C controller and device data */ +#if defined(CONFIG_I2C_MXC) || defined(CONFIG_I2C_MXC_MODULE) + +#ifdef CONFIG_I2C_MXC_SELECT1 +/*! + * Resource definition for the I2C1 + */ +static struct resource mxci2c1_resources[] = { + [0] = { + .start = I2C_BASE_ADDR, + .end = I2C_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C, + .end = MXC_INT_I2C, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c1_data = { + .i2c_clk = 100000, +}; +#endif + +/*! + * Resource definition for the I2C2 + */ +static struct resource mxci2c2_resources[] = { + [0] = { + .start = I2C2_BASE_ADDR, + .end = I2C2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C2, + .end = MXC_INT_I2C2, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c2_data = { + .i2c_clk = 100000, +}; + +#ifdef CONFIG_I2C_MXC_SELECT3 +/*! + * Resource definition for the I2C3 + */ +static struct resource mxci2c3_resources[] = { + [0] = { + .start = I2C3_BASE_ADDR, + .end = I2C3_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C3, + .end = MXC_INT_I2C3, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c3_data = { + .i2c_clk = 100000, +}; +#endif + +/*! Device Definition for MXC I2C1 */ +static struct platform_device mxci2c_devices[] = { +#ifdef CONFIG_I2C_MXC_SELECT1 + { + .name = "mxc_i2c", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c1_data, + }, + .num_resources = ARRAY_SIZE(mxci2c1_resources), + .resource = mxci2c1_resources,}, +#endif + { + .name = "mxc_i2c_slave", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c2_data, + }, + .num_resources = ARRAY_SIZE(mxci2c2_resources), + .resource = mxci2c2_resources,}, +#ifdef CONFIG_I2C_MXC_SELECT3 + { + .name = "mxc_i2c", + .id = 2, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c3_data, + }, + .num_resources = ARRAY_SIZE(mxci2c3_resources), + .resource = mxci2c3_resources,}, +#endif +}; + +static inline void mxc_init_i2c(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mxci2c_devices); i++) { + if (platform_device_register(&mxci2c_devices[i]) < 0) + dev_err(&mxci2c_devices[i].dev, + "Unable to register I2C device\n"); + } +} +#else +static inline void mxc_init_i2c(void) +{ +} +#endif + +struct mxc_gpio_port mxc_gpio_ports[] = { + [0] = { + .chip.label = "gpio-0", + .base = IO_ADDRESS(GPIO1_BASE_ADDR), + .irq = MXC_INT_GPIO1, + .irq_high = 0, + .virtual_irq_start = MXC_GPIO_IRQ_START + }, + [1] = { + .chip.label = "gpio-1", + .base = IO_ADDRESS(GPIO2_BASE_ADDR), + .irq = MXC_INT_GPIO2, + .irq_high = 0, + .virtual_irq_start = MXC_GPIO_IRQ_START + 32 + }, + [2] = { + .chip.label = "gpio-2", + .base = IO_ADDRESS(GPIO3_BASE_ADDR), + .irq = MXC_INT_GPIO3, + .irq_high = 0, + .virtual_irq_start = MXC_GPIO_IRQ_START + 32 * 2 + } +}; + +int __init mxc_register_gpios(void) +{ + return mxc_gpio_init(mxc_gpio_ports, ARRAY_SIZE(mxc_gpio_ports)); +} + +static struct platform_device mxc_dma_device = { + .name = "mxc_dma", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, +}; + +static inline void mxc_init_dma(void) +{ + (void)platform_device_register(&mxc_dma_device); +} + +static struct resource spdif_resources[] = { + { + .start = SPDIF_BASE_ADDR, + .end = SPDIF_BASE_ADDR + 0x50, + .flags = IORESOURCE_MEM, + }, +}; + +static struct mxc_spdif_platform_data mxc_spdif_data = { + .spdif_tx = 1, + .spdif_rx = 1, + .spdif_clk_44100 = 3, /* spdif_ext_clk source for 44.1KHz */ + .spdif_clk_48000 = 0, /* audio osc source */ + .spdif_clkid = 0, + .spdif_clk = NULL, /* spdif bus clk */ +}; + +static struct platform_device mxc_alsa_spdif_device = { + .name = "mxc_alsa_spdif", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_spdif_data, + }, + .num_resources = ARRAY_SIZE(spdif_resources), + .resource = spdif_resources, +}; + +static inline void mxc_init_spdif(void) +{ + mxc_spdif_data.spdif_clk = clk_get(NULL, "spdif_ipg_clk"); + clk_put(mxc_spdif_data.spdif_clk); + mxc_spdif_data.spdif_core_clk = clk_get(NULL, "spdif_clk"); + clk_put(mxc_spdif_data.spdif_core_clk); + mxc_spdif_data.spdif_audio_clk = clk_get(NULL, "spdif_audio_clk"); + clk_put(mxc_spdif_data.spdif_audio_clk); + platform_device_register(&mxc_alsa_spdif_device); +} + +static struct platform_device mxc_alsa_surround_device = { + .name = "imx-3stack-wm8580", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, +}; + +static void mxc_init_surround_audio(void) +{ + platform_device_register(&mxc_alsa_surround_device); +} + +static struct mxc_audio_platform_data mxc_bt_audio_data; + +static struct platform_device mxc_bt_alsa_device = { + .name = "imx-3stack-bt", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_bt_audio_data, + }, + +}; + +static void mxc_init_bt_audio(void) +{ + mxc_bt_audio_data.src_port = 2; + mxc_bt_audio_data.ext_port = 5; + mxc_bt_audio_data.ext_ram = 1; + platform_device_register(&mxc_bt_alsa_device); +} + +static struct resource asrc_resources[] = { + { + .start = ASRC_BASE_ADDR, + .end = ASRC_BASE_ADDR + 0x9C, + .flags = IORESOURCE_MEM, + }, +}; + +static struct mxc_asrc_platform_data mxc_asrc_data; + +static struct platform_device mxc_asrc_device = { + .name = "mxc_asrc", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_asrc_data, + }, + .num_resources = ARRAY_SIZE(asrc_resources), + .resource = asrc_resources, +}; + +static inline void mxc_init_asrc(void) +{ + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 1) + mxc_asrc_data.channel_bits = 3; + else + mxc_asrc_data.channel_bits = 4; + + mxc_asrc_data.asrc_core_clk = clk_get(NULL, "asrc_clk"); + clk_put(mxc_asrc_data.asrc_core_clk); + mxc_asrc_data.asrc_audio_clk = clk_get(NULL, "asrc_audio_clk"); + clk_set_rate(mxc_asrc_data.asrc_audio_clk, 768000); + clk_put(mxc_asrc_data.asrc_audio_clk); + platform_device_register(&mxc_asrc_device); +} + +#if defined(CONFIG_CAN_FLEXCAN) || defined(CONFIG_CAN_FLEXCAN_MODULE) + +static struct resource flexcan1_resources[] = { + { + .start = CAN1_BASE_ADDR, + .end = CAN1_BASE_ADDR + 0x97F, + .flags = IORESOURCE_MEM,}, + { + .start = MXC_INT_CAN1, + .end = MXC_INT_CAN1, + .flags = IORESOURCE_IRQ,} +}; + +static struct resource flexcan2_resources[] = { + { + .start = CAN2_BASE_ADDR, + .end = CAN2_BASE_ADDR + 0x97F, + .flags = IORESOURCE_MEM,}, + { + .start = MXC_INT_CAN2, + .end = MXC_INT_CAN2, + .flags = IORESOURCE_IRQ,} +}; + +static struct platform_device flexcan_devices[] = { + { + .name = "FlexCAN", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &flexcan_data[0], + }, + .num_resources = ARRAY_SIZE(flexcan1_resources), + .resource = flexcan1_resources,}, + { + .name = "FlexCAN", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &flexcan_data[1], + }, + .num_resources = ARRAY_SIZE(flexcan2_resources), + .resource = flexcan2_resources,}, +}; + +static inline void mxc_init_flexcan(void) +{ + platform_device_register(&flexcan_devices[0]); + platform_device_register(&flexcan_devices[1]); +} +#else +static inline void mxc_init_flexcan(void) +{ +} +#endif + +#if defined(CONFIG_HW_RANDOM_FSL_RNGC) || \ +defined(CONFIG_HW_RANDOM_FSL_RNGC_MODULE) +static struct resource rngc_resources[] = { + { + .start = RNGC_BASE_ADDR, + .end = RNGC_BASE_ADDR + 0x34, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_INT_RNG, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device fsl_rngc_device = { + .name = "fsl_rngc", + .id = -1, + .num_resources = ARRAY_SIZE(rngc_resources), + .resource = rngc_resources, +}; + +static inline void mxc_init_rngc(void) +{ + platform_device_register(&fsl_rngc_device); +} +#else +static inline void mxc_init_rngc(void) +{ +} +#endif + +#if defined(CONFIG_MXC_IIM) || defined(CONFIG_MXC_IIM_MODULE) +static struct resource mxc_iim_resources[] = { + { + .start = IIM_BASE_ADDR, + .end = IIM_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device mxc_iim_device = { + .name = "mxc_iim", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(mxc_iim_resources), + .resource = mxc_iim_resources +}; + +static inline void mxc_init_iim(void) +{ + if (platform_device_register(&mxc_iim_device) < 0) + dev_err(&mxc_iim_device.dev, + "Unable to register mxc iim device\n"); +} +#else +static inline void mxc_init_iim(void) +{ +} +#endif + +static struct resource mxc_gpu_resources[] = { + { + .start = MXC_INT_GPU2D, + .end = MXC_INT_GPU2D, + .name = "gpu_2d_irq", + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device gpu_device = { + .name = "mxc_gpu", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(mxc_gpu_resources), + .resource = mxc_gpu_resources, +}; + +static void __init mxc_init_gpu(void) +{ + platform_device_register(&gpu_device); +} + +int __init mxc_init_devices(void) +{ + mxc_init_wdt(); + mxc_init_ipu(); + mxc_init_spi(); + mxc_init_i2c(); + pmic_init_rtc(); + mxc_init_rtc(); + mxc_init_dma(); + mxc_init_bt_audio(); + mxc_init_spdif(); + mxc_init_surround_audio(); + mxc_init_asrc(); + mxc_init_flexcan(); + mxc_init_rngc(); + mxc_init_iim(); + mxc_init_gpu(); + + /* SPBA configuration for SSI2 - SDMA and MCU are set */ + spba_take_ownership(SPBA_SSI2, SPBA_MASTER_C | SPBA_MASTER_A); + return 0; +} diff --git a/arch/arm/mach-mx35/dma.c b/arch/arm/mach-mx35/dma.c new file mode 100644 index 000000000000..ef7f557d1cc8 --- /dev/null +++ b/arch/arm/mach-mx35/dma.c @@ -0,0 +1,1046 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/init.h> +#include <linux/device.h> +#include <asm/dma.h> +#include <mach/hardware.h> + +#include "serial.h" + +#ifdef CONFIG_SND_MXC_SOC_IRAM +#define soc_trans_type int_2_per +#else +#define soc_trans_type emi_2_per +#endif + +#define MXC_SPDIF_TXFIFO_WML 8 +#define MXC_SPDIF_RXFIFO_WML 8 +#define MXC_SPDIF_TX_REG 0x2C +#define MXC_SPDIF_RX_REG 0x14 + +#define MXC_SSI_TX0_REG 0x0 +#define MXC_SSI_TX1_REG 0x4 +#define MXC_SSI_RX0_REG 0x8 +#define MXC_SSI_RX1_REG 0xC +#define MXC_SSI_TXFIFO_WML 0x4 +#define MXC_SSI_RXFIFO_WML 0x6 + +#define MXC_ASRC_FIFO_WML 0x40 +#define MXC_ASRCA_RX_REG 0x60 +#define MXC_ASRCA_TX_REG 0x64 +#define MXC_ASRCB_RX_REG 0x68 +#define MXC_ASRCB_TX_REG 0x6C +#define MXC_ASRCC_RX_REG 0x70 +#define MXC_ASRCC_TX_REG 0x74 + +#define MXC_ESAI_TX_REG 0x00 +#define MXC_ESAI_RX_REG 0x04 +#define MXC_ESAI_FIFO_WML 0x40 + +struct mxc_sdma_info_entry_s { + mxc_dma_device_t device; + void *chnl_info; +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart1_rx_params = { + .chnl_params = { + .watermark_level = UART1_UFCR_RXTL, + .per_address = UART1_BASE_ADDR, + .peripheral_type = UART, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART1_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART1_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart1_tx_params = { + .chnl_params = { + .watermark_level = UART1_UFCR_TXTL, + .per_address = UART1_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART1_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART1_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart2_rx_params = { + .chnl_params = { + .watermark_level = UART2_UFCR_RXTL, + .per_address = UART2_BASE_ADDR, + .peripheral_type = UART, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART2_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART2_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart2_tx_params = { + .chnl_params = { + .watermark_level = UART2_UFCR_TXTL, + .per_address = UART2_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART2_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART2_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart3_rx_params = { + .chnl_params = { + .watermark_level = UART3_UFCR_RXTL, + .per_address = UART3_BASE_ADDR, + .peripheral_type = UART_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART3_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART3_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart3_tx_params = { + .chnl_params = { + .watermark_level = UART3_UFCR_TXTL, + .per_address = UART3_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART3_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART3_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_spdif_16bit_tx_params = { + .chnl_params = { + .watermark_level = MXC_SPDIF_TXFIFO_WML, + .per_address = SPDIF_BASE_ADDR + MXC_SPDIF_TX_REG, + .peripheral_type = SPDIF, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SPDIF_TX, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SPDIF_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_spdif_32bit_tx_params = { + .chnl_params = { + .watermark_level = MXC_SPDIF_TXFIFO_WML, + .per_address = SPDIF_BASE_ADDR + MXC_SPDIF_TX_REG, + .peripheral_type = SPDIF, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SPDIF_TX, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SPDIF_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_spdif_32bit_rx_params = { + .chnl_params = { + .watermark_level = MXC_SPDIF_RXFIFO_WML, + .per_address = SPDIF_BASE_ADDR + MXC_SPDIF_RX_REG, + .peripheral_type = SPDIF, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SPDIF_RX, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SPDIF_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_memory_params = { + .chnl_params = { + .peripheral_type = MEMORY, + .transfer_type = emi_2_emi, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MEMORY, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_SSI1_TX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_SSI1_TX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_SSI1_TX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_asrca_rx_params = { + .chnl_params = { + .watermark_level = MXC_ASRC_FIFO_WML, + .per_address = ASRC_BASE_ADDR + MXC_ASRCA_RX_REG, + .peripheral_type = ASRC, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_ASRC_DMA1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCA_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_asrca_tx_params = { + .chnl_params = { + .watermark_level = MXC_ASRC_FIFO_WML, + .per_address = ASRC_BASE_ADDR + MXC_ASRCA_TX_REG, + .peripheral_type = ASRC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_ASRC_DMA4, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCA_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_asrcb_rx_params = { + .chnl_params = { + .watermark_level = MXC_ASRC_FIFO_WML, + .per_address = ASRC_BASE_ADDR + MXC_ASRCB_RX_REG, + .peripheral_type = ASRC, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_ASRC_DMA2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCB_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_asrcb_tx_params = { + .chnl_params = { + .watermark_level = MXC_ASRC_FIFO_WML, + .per_address = ASRC_BASE_ADDR + MXC_ASRCB_TX_REG, + .peripheral_type = ASRC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_ASRC_DMA5, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCB_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_asrcc_rx_params = { + .chnl_params = { + .watermark_level = MXC_ASRC_FIFO_WML * 3, + .per_address = ASRC_BASE_ADDR + MXC_ASRCC_RX_REG, + .peripheral_type = ASRC, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_ASRC_DMA3, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCC_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_asrcc_tx_params = { + .chnl_params = { + .watermark_level = MXC_ASRC_FIFO_WML * 3, + .per_address = ASRC_BASE_ADDR + MXC_ASRCC_TX_REG, + .peripheral_type = ASRC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_ASRC_DMA6, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCC_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_ext_params_t mxc_sdma_asrca_ssi1_tx0_params = { + .chnl_ext_params = { + .common = { + .watermark_level = + MXC_ASRC_FIFO_WML >> 1, + .per_address = + SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = ASRC, + .transfer_type = per_2_per, + .event_id = DMA_REQ_SSI1_TX1, + .event_id2 = DMA_REQ_ASRC_DMA4, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + .ext = 1, + }, + .p2p_dir = 0, + .info_bits = + SDMA_ASRC_P2P_INFO_CONT | SDMA_ASRC_P2P_INFO_SP, + .watermark_level2 = MXC_SSI_TXFIFO_WML, + .per_address2 = ASRC_BASE_ADDR + MXC_ASRCA_TX_REG, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCA_SSI1_TX0, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_ext_params_t mxc_sdma_asrca_ssi1_tx1_params = { + .chnl_ext_params = { + .common = { + .watermark_level = + MXC_ASRC_FIFO_WML >> 1, + .per_address = + SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = ASRC, + .transfer_type = per_2_per, + .event_id = DMA_REQ_SSI1_TX2, + .event_id2 = DMA_REQ_ASRC_DMA4, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + .ext = 1, + }, + .p2p_dir = 0, + .info_bits = + SDMA_ASRC_P2P_INFO_CONT | SDMA_ASRC_P2P_INFO_SP, + .watermark_level2 = MXC_SSI_TXFIFO_WML, + .per_address2 = ASRC_BASE_ADDR + MXC_ASRCA_TX_REG, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCA_SSI1_TX1, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_ext_params_t mxc_sdma_asrca_ssi2_tx0_params = { + .chnl_ext_params = { + .common = { + .watermark_level = + MXC_ASRC_FIFO_WML >> 1, + .per_address = + SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = ASRC, + .transfer_type = per_2_per, + .event_id = DMA_REQ_SSI2_TX1, + .event_id2 = DMA_REQ_ASRC_DMA4, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + .ext = 1, + }, + .p2p_dir = 0, + .info_bits = + SDMA_ASRC_P2P_INFO_CONT | SDMA_ASRC_P2P_INFO_SP | + SDMA_ASRC_P2P_INFO_DP, + .watermark_level2 = MXC_SSI_TXFIFO_WML, + .per_address2 = ASRC_BASE_ADDR + MXC_ASRCA_TX_REG, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCA_SSI2_TX0, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_ext_params_t mxc_sdma_asrca_ssi2_tx1_params = { + .chnl_ext_params = { + .common = { + .watermark_level = + MXC_ASRC_FIFO_WML >> 1, + .per_address = + SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = ASRC, + .transfer_type = per_2_per, + .event_id = DMA_REQ_SSI2_TX2, + .event_id2 = DMA_REQ_ASRC_DMA4, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + .ext = 1, + }, + .p2p_dir = 0, + .info_bits = + SDMA_ASRC_P2P_INFO_CONT | SDMA_ASRC_P2P_INFO_SP | + SDMA_ASRC_P2P_INFO_DP, + .watermark_level2 = MXC_SSI_TXFIFO_WML, + .per_address2 = ASRC_BASE_ADDR + MXC_ASRCA_TX_REG, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCA_SSI2_TX1, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_ext_params_t mxc_sdma_asrcb_ssi1_tx0_params = { + .chnl_ext_params = { + .common = { + .watermark_level = + MXC_ASRC_FIFO_WML >> 1, + .per_address = + SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = ASRC, + .transfer_type = per_2_per, + .event_id = DMA_REQ_SSI1_TX1, + .event_id2 = DMA_REQ_ASRC_DMA5, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + .ext = 1, + }, + .p2p_dir = 0, + .info_bits = + SDMA_ASRC_P2P_INFO_CONT | SDMA_ASRC_P2P_INFO_SP, + .watermark_level2 = MXC_SSI_TXFIFO_WML, + .per_address2 = ASRC_BASE_ADDR + MXC_ASRCB_TX_REG, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCB_SSI1_TX0, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_ext_params_t mxc_sdma_asrcb_ssi1_tx1_params = { + .chnl_ext_params = { + .common = { + .watermark_level = + MXC_ASRC_FIFO_WML >> 1, + .per_address = + SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = ASRC, + .transfer_type = per_2_per, + .event_id = DMA_REQ_SSI1_TX2, + .event_id2 = DMA_REQ_ASRC_DMA5, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + .ext = 1, + }, + .p2p_dir = 0, + .info_bits = + SDMA_ASRC_P2P_INFO_CONT | SDMA_ASRC_P2P_INFO_SP, + .watermark_level2 = MXC_SSI_TXFIFO_WML, + .per_address2 = ASRC_BASE_ADDR + MXC_ASRCB_TX_REG, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCB_SSI1_TX1, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_ext_params_t mxc_sdma_asrcb_ssi2_tx0_params = { + .chnl_ext_params = { + .common = { + .watermark_level = + MXC_ASRC_FIFO_WML >> 1, + .per_address = + SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = ASRC, + .transfer_type = per_2_per, + .event_id = DMA_REQ_SSI2_TX1, + .event_id2 = DMA_REQ_ASRC_DMA5, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + .ext = 1, + }, + .p2p_dir = 0, + .info_bits = + SDMA_ASRC_P2P_INFO_CONT | SDMA_ASRC_P2P_INFO_SP | + SDMA_ASRC_P2P_INFO_DP, + .watermark_level2 = MXC_SSI_TXFIFO_WML, + .per_address2 = ASRC_BASE_ADDR + MXC_ASRCB_TX_REG, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCB_SSI2_TX0, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_ext_params_t mxc_sdma_asrcb_ssi2_tx1_params = { + .chnl_ext_params = { + .common = { + .watermark_level = + MXC_ASRC_FIFO_WML >> 1, + .per_address = + SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = ASRC, + .transfer_type = per_2_per, + .event_id = DMA_REQ_SSI2_TX2, + .event_id2 = DMA_REQ_ASRC_DMA5, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + .ext = 1, + }, + .p2p_dir = 0, + .info_bits = + SDMA_ASRC_P2P_INFO_CONT | SDMA_ASRC_P2P_INFO_SP | + SDMA_ASRC_P2P_INFO_DP, + .watermark_level2 = MXC_SSI_TXFIFO_WML, + .per_address2 = ASRC_BASE_ADDR + MXC_ASRCB_TX_REG, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCB_SSI2_TX1, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_ext_params_t mxc_sdma_asrca_esai_params = { + .chnl_ext_params = { + .common = { + .watermark_level = + MXC_ASRC_FIFO_WML >> 1, + .per_address = + ESAI_BASE_ADDR + MXC_ESAI_TX_REG, + .peripheral_type = ASRC, + .transfer_type = per_2_per, + .event_id = DMA_REQ_ESAI_TX, + .event_id2 = DMA_REQ_ASRC_DMA4, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + .ext = 1, + }, + .p2p_dir = 0, + .info_bits = + SDMA_ASRC_P2P_INFO_CONT | SDMA_ASRC_P2P_INFO_SP | + SDMA_ASRC_P2P_INFO_DP, + .watermark_level2 = MXC_ESAI_FIFO_WML, + .per_address2 = ASRC_BASE_ADDR + MXC_ASRCA_TX_REG, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCA_ESAI, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_ext_params_t mxc_sdma_asrcb_esai_params = { + .chnl_ext_params = { + .common = { + .watermark_level = + MXC_ASRC_FIFO_WML >> 1, + .per_address = + ESAI_BASE_ADDR + MXC_ESAI_TX_REG, + .peripheral_type = ASRC, + .transfer_type = per_2_per, + .event_id = DMA_REQ_ESAI_TX, + .event_id2 = DMA_REQ_ASRC_DMA5, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + .ext = 1, + }, + .p2p_dir = 0, + .info_bits = + SDMA_ASRC_P2P_INFO_CONT | SDMA_ASRC_P2P_INFO_SP | + SDMA_ASRC_P2P_INFO_DP, + .watermark_level2 = MXC_ESAI_FIFO_WML, + .per_address2 = ASRC_BASE_ADDR + MXC_ASRCB_TX_REG, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCB_ESAI, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_ext_params_t mxc_sdma_asrcc_esai_params = { + .chnl_ext_params = { + .common = { + .watermark_level = + MXC_ASRC_FIFO_WML >> 1, + .per_address = + ESAI_BASE_ADDR + MXC_ESAI_TX_REG, + .peripheral_type = ASRC, + .transfer_type = per_2_per, + .event_id = DMA_REQ_ESAI_TX, + .event_id2 = DMA_REQ_ASRC_DMA6, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + .ext = 1, + }, + .p2p_dir = 0, + .info_bits = + SDMA_ASRC_P2P_INFO_CONT | SDMA_ASRC_P2P_INFO_SP | + SDMA_ASRC_P2P_INFO_DP, + .watermark_level2 = MXC_ASRC_FIFO_WML, + .per_address2 = ASRC_BASE_ADDR + MXC_ASRCC_TX_REG, + }, + .channel_num = MXC_DMA_CHANNEL_ASRCC_ESAI, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_esai_16bit_rx_params = { + .chnl_params = { + .watermark_level = MXC_ESAI_FIFO_WML, + .per_address = ESAI_BASE_ADDR + MXC_ESAI_RX_REG, + .peripheral_type = ESAI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_ESAI_RX, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ESAI_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_esai_16bit_tx_params = { + .chnl_params = { + .watermark_level = MXC_ESAI_FIFO_WML, + .per_address = ESAI_BASE_ADDR + MXC_ESAI_TX_REG, + .peripheral_type = ESAI, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_ESAI_TX, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ESAI_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_esai_24bit_rx_params = { + .chnl_params = { + .watermark_level = MXC_ESAI_FIFO_WML, + .per_address = ESAI_BASE_ADDR + MXC_ESAI_RX_REG, + .peripheral_type = ESAI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_ESAI_RX, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ESAI_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_esai_24bit_tx_params = { + .chnl_params = { + .watermark_level = MXC_ESAI_FIFO_WML, + .per_address = ESAI_BASE_ADDR + MXC_ESAI_TX_REG, + .peripheral_type = ESAI, + .transfer_type = soc_trans_type, + .event_id = DMA_REQ_ESAI_TX, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ESAI_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static struct mxc_sdma_info_entry_s mxc_sdma_active_dma_info[] = { + {MXC_DMA_UART1_RX, &mxc_sdma_uart1_rx_params}, + {MXC_DMA_UART1_TX, &mxc_sdma_uart1_tx_params}, + {MXC_DMA_UART2_RX, &mxc_sdma_uart2_rx_params}, + {MXC_DMA_UART2_TX, &mxc_sdma_uart2_tx_params}, + {MXC_DMA_UART3_RX, &mxc_sdma_uart3_rx_params}, + {MXC_DMA_UART3_TX, &mxc_sdma_uart3_tx_params}, + {MXC_DMA_SPDIF_16BIT_TX, &mxc_sdma_spdif_16bit_tx_params}, + {MXC_DMA_SPDIF_32BIT_TX, &mxc_sdma_spdif_32bit_tx_params}, + {MXC_DMA_SPDIF_32BIT_RX, &mxc_sdma_spdif_32bit_rx_params}, + {MXC_DMA_SSI1_8BIT_RX0, &mxc_sdma_ssi1_8bit_rx0_params}, + {MXC_DMA_SSI1_8BIT_TX0, &mxc_sdma_ssi1_8bit_tx0_params}, + {MXC_DMA_SSI1_16BIT_RX0, &mxc_sdma_ssi1_16bit_rx0_params}, + {MXC_DMA_SSI1_16BIT_TX0, &mxc_sdma_ssi1_16bit_tx0_params}, + {MXC_DMA_SSI1_24BIT_RX0, &mxc_sdma_ssi1_24bit_rx0_params}, + {MXC_DMA_SSI1_24BIT_TX0, &mxc_sdma_ssi1_24bit_tx0_params}, + {MXC_DMA_SSI1_8BIT_RX1, &mxc_sdma_ssi1_8bit_rx1_params}, + {MXC_DMA_SSI1_8BIT_TX1, &mxc_sdma_ssi1_8bit_tx1_params}, + {MXC_DMA_SSI1_16BIT_RX1, &mxc_sdma_ssi1_16bit_rx1_params}, + {MXC_DMA_SSI1_16BIT_TX1, &mxc_sdma_ssi1_16bit_tx1_params}, + {MXC_DMA_SSI1_24BIT_RX1, &mxc_sdma_ssi1_24bit_rx1_params}, + {MXC_DMA_SSI1_24BIT_TX1, &mxc_sdma_ssi1_24bit_tx1_params}, + {MXC_DMA_SSI2_8BIT_RX0, &mxc_sdma_ssi2_8bit_rx0_params}, + {MXC_DMA_SSI2_8BIT_TX0, &mxc_sdma_ssi2_8bit_tx0_params}, + {MXC_DMA_SSI2_16BIT_RX0, &mxc_sdma_ssi2_16bit_rx0_params}, + {MXC_DMA_SSI2_16BIT_TX0, &mxc_sdma_ssi2_16bit_tx0_params}, + {MXC_DMA_SSI2_24BIT_RX0, &mxc_sdma_ssi2_24bit_rx0_params}, + {MXC_DMA_SSI2_24BIT_TX0, &mxc_sdma_ssi2_24bit_tx0_params}, + {MXC_DMA_SSI2_8BIT_RX1, &mxc_sdma_ssi2_8bit_rx1_params}, + {MXC_DMA_SSI2_8BIT_TX1, &mxc_sdma_ssi2_8bit_tx1_params}, + {MXC_DMA_SSI2_16BIT_RX1, &mxc_sdma_ssi2_16bit_rx1_params}, + {MXC_DMA_SSI2_16BIT_TX1, &mxc_sdma_ssi2_16bit_tx1_params}, + {MXC_DMA_SSI2_24BIT_RX1, &mxc_sdma_ssi2_24bit_rx1_params}, + {MXC_DMA_SSI2_24BIT_TX1, &mxc_sdma_ssi2_24bit_tx1_params}, + {MXC_DMA_ASRC_A_RX, &mxc_sdma_asrca_rx_params}, + {MXC_DMA_ASRC_A_TX, &mxc_sdma_asrca_tx_params}, + {MXC_DMA_ASRC_B_RX, &mxc_sdma_asrcb_rx_params}, + {MXC_DMA_ASRC_B_TX, &mxc_sdma_asrcb_tx_params}, + {MXC_DMA_ASRC_C_RX, &mxc_sdma_asrcc_rx_params}, + {MXC_DMA_ASRC_C_TX, &mxc_sdma_asrcc_tx_params}, + {MXC_DMA_ASRCA_SSI1_TX0, &mxc_sdma_asrca_ssi1_tx0_params}, + {MXC_DMA_ASRCA_SSI1_TX1, &mxc_sdma_asrca_ssi1_tx1_params}, + {MXC_DMA_ASRCA_SSI2_TX0, &mxc_sdma_asrca_ssi2_tx0_params}, + {MXC_DMA_ASRCA_SSI2_TX1, &mxc_sdma_asrca_ssi2_tx1_params}, + {MXC_DMA_ASRCB_SSI1_TX0, &mxc_sdma_asrcb_ssi1_tx0_params}, + {MXC_DMA_ASRCB_SSI1_TX1, &mxc_sdma_asrcb_ssi1_tx1_params}, + {MXC_DMA_ASRCB_SSI2_TX0, &mxc_sdma_asrcb_ssi2_tx0_params}, + {MXC_DMA_ASRCB_SSI2_TX1, &mxc_sdma_asrcb_ssi2_tx1_params}, + {MXC_DMA_ASRCA_ESAI, &mxc_sdma_asrca_esai_params}, + {MXC_DMA_ASRCB_ESAI, &mxc_sdma_asrcb_esai_params}, + {MXC_DMA_ASRCC_ESAI, &mxc_sdma_asrcc_esai_params}, + {MXC_DMA_ESAI_16BIT_RX, &mxc_sdma_esai_16bit_rx_params}, + {MXC_DMA_ESAI_16BIT_TX, &mxc_sdma_esai_16bit_tx_params}, + {MXC_DMA_ESAI_24BIT_RX, &mxc_sdma_esai_24bit_rx_params}, + {MXC_DMA_ESAI_24BIT_TX, &mxc_sdma_esai_24bit_tx_params}, + {MXC_DMA_MEMORY, &mxc_sdma_memory_params}, +}; + +static int mxc_sdma_info_entrys = + sizeof(mxc_sdma_active_dma_info) / sizeof(mxc_sdma_active_dma_info[0]); +/*! + * This functions Returns the SDMA paramaters associated for a module + * + * @param channel_id the ID of the module requesting DMA + * @return returns the sdma parameters structure for the device + */ +mxc_sdma_channel_params_t *mxc_sdma_get_channel_params(mxc_dma_device_t + channel_id) +{ + struct mxc_sdma_info_entry_s *p = mxc_sdma_active_dma_info; + int i; + + for (i = 0; i < mxc_sdma_info_entrys; i++, p++) { + if (p->device == channel_id) + return p->chnl_info; + } + return NULL; +} + +EXPORT_SYMBOL(mxc_sdma_get_channel_params); + +/*! + * This functions marks the SDMA channels that are statically allocated + * + * @param chnl the channel array used to store channel information + */ +void mxc_get_static_channels(mxc_dma_channel_t *chnl) +{ + /* No channels statically allocated for MX35 */ +#ifdef CONFIG_SDMA_IRAM + int i; + for (i = MXC_DMA_CHANNEL_IRAM; i < MAX_DMA_CHANNELS; i++) + chnl[i].dynamic = 0; +#endif +} + +EXPORT_SYMBOL(mxc_get_static_channels); diff --git a/arch/arm/mach-mx35/dvfs.c b/arch/arm/mach-mx35/dvfs.c new file mode 100644 index 000000000000..c2e3de4f56c3 --- /dev/null +++ b/arch/arm/mach-mx35/dvfs.c @@ -0,0 +1,606 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * @file dvfs.c + * + * @brief A simplied driver for the Freescale Semiconductor MXC DVFS module. + * + * Upon initialization, the DVFS driver initializes the DVFS hardware + * sets up driver nodes attaches to the DVFS interrupt and initializes internal + * data structures. When the DVFS interrupt occurs the driver checks the cause + * of the interrupt (lower frequency, increase frequency or emergency) and + * changes the CPU voltage according to translation table that is loaded into + * the driver. + * + * @ingroup PM + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/device.h> +#include <linux/sysdev.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> +#include <mach/hardware.h> +#include "crm_regs.h" + +/* + * The frequency of div_3_clk will affect the dvfs sample rate.. + */ +#define DVFS_DIV3CK (3 << MXC_CCM_LTR0_DIV3CK_OFFSET) + +/* + * Panic threshold. Panic frequency change request + * will be sent if DVFS counter value will be more than this value. + */ +#define DVFS_PNCTHR (63 << MXC_CCM_LTR1_PNCTHR_OFFSET) + +/* + * Load tracking buffer source: 1 for ld_add; 0 for pre_ld_add + */ +#define DVFS_LTBRSR (1 << MXC_CCM_LTR1_LTBRSR_OFFSET) + +/* EMAC defines how many samples are included in EMA calculation */ +#define DVFS_EMAC (0x20 << MXC_CCM_LTR2_EMAC_OFFSET) + +/* + * Frequency increase threshold. Increase frequency change request + * will be sent if DVFS counter value will be more than this value. + */ +#define DVFS_UPTHR(val) (val << MXC_CCM_LTR0_UPTHR_OFFSET) + +/* + * Frequency decrease threshold. Decrease frequency change request + * will be sent if DVFS counter value will be less than this value. + */ +#define DVFS_DNTHR(val) (val << MXC_CCM_LTR0_DNTHR_OFFSET) + +/* + * DNCNT defines the amount of times the down threshold should be exceeded + * before DVFS will trigger frequency decrease request. + */ +#define DVFS_DNCNT(val) (val << MXC_CCM_LTR1_DNCNT_OFFSET) + +/* + * UPCNT defines the amount of times the up threshold should be exceeded + * before DVFS will trigger frequency increase request. + */ +#define DVFS_UPCNT(val) (val << MXC_CCM_LTR1_UPCNT_OFFSET) + +#define DVFS_DVSUP(val) (val << MXC_CCM_PMCR0_DVSUP_OFFSET) + +#define MXC_DVFS_MAX_WP_NUM 2 + +enum { + FSVAI_FREQ_NOCHANGE = 0x0, + FSVAI_FREQ_INCREASE, + FSVAI_FREQ_DECREASE, + FSVAI_FREQ_EMERG, +}; + +struct dvfs_wp { + unsigned long cpu_rate; + u32 core_voltage; + u32 dvsup; + u32 dnthr; + u32 upthr; + u32 dncnt; + u32 upcnt; +}; + +/* the default working points for MX35 TO2 DVFS. */ +static struct dvfs_wp dvfs_wp_tbl[MXC_DVFS_MAX_WP_NUM] = { + {399000000, 1200000, DVFS_DVSUP(DVSUP_LOW), DVFS_DNTHR(18), + DVFS_UPTHR(31), DVFS_DNCNT(0x33), + DVFS_UPCNT(0x33)}, +/* TBD: Need to set default voltage according to published data sheet */ + {532000000, 1350000, DVFS_DVSUP(DVSUP_TURBO), DVFS_DNTHR(18), + DVFS_UPTHR(30), DVFS_DNCNT(0x33), + DVFS_UPCNT(0x33)} +}; + +static u8 dvfs_wp_num = MXC_DVFS_MAX_WP_NUM; + + /* Used for tracking the number of interrupts */ +static u32 dvfs_nr_up[MXC_DVFS_MAX_WP_NUM]; +static u32 dvfs_nr_dn[MXC_DVFS_MAX_WP_NUM]; +static unsigned long stored_cpu_rate; /* cpu rate before DVFS starts */ +static u32 stored_pmcr0; +static int dvfs_is_active; /* indicate DVFS is active or not */ + +static struct delayed_work dvfs_work; + +/* + * Clock structures + */ +static struct clk *cpu_clk; +static struct regulator *core_reg; + +const static u8 ltr_gp_weight[] = { + 0, /* 0 */ + 0, + 0, + 0, + 0, + 0, /* 5 */ + 0, + 0, + 0, + 0, + 0, /* 10 */ + 0, + 0, + 0, + 0, + 0, /* 15 */ +}; + +DEFINE_SPINLOCK(mxc_dvfs_lock); + +/*! + * This function sets the weight of general purpose signals + * @param gp_id number of general purpose bit + * @param weight the weight of the general purpose bit + */ +static void set_gp_weight(int gp_id, u8 weight) +{ + u32 reg; + + if (gp_id < 9) { + reg = __raw_readl(MXC_CCM_LTR3); + reg = (reg & ~(MXC_CCM_LTR3_WSW_MASK(gp_id))) | + (weight << MXC_CCM_LTR3_WSW_OFFSET(gp_id)); + __raw_writel(reg, MXC_CCM_LTR3); + } else if (gp_id < 16) { + reg = __raw_readl(MXC_CCM_LTR2); + reg = (reg & ~(MXC_CCM_LTR2_WSW_MASK(gp_id))) | + (weight << MXC_CCM_LTR2_WSW_OFFSET(gp_id)); + __raw_writel(reg, MXC_CCM_LTR2); + } +} + +/*! + * This function sets upper threshold, lower threshold, + * up-counter, down-counter for load tracking. + * @param upthr upper threshold + * @param dnthr lower threshold + * @param upcnt up counter + * @param dncnt down counter + */ +static void set_ltr_thres_counter(u32 upthr, u32 dnthr, u32 upcnt, u32 dncnt) +{ + u32 reg; + reg = __raw_readl(MXC_CCM_LTR0); + reg = + (reg & + ~(MXC_CCM_LTR0_UPTHR_MASK | + MXC_CCM_LTR0_DNTHR_MASK)) | upthr | dnthr; + __raw_writel(reg, MXC_CCM_LTR0); + + reg = __raw_readl(MXC_CCM_LTR1); + reg = + (reg & + ~(MXC_CCM_LTR1_UPCNT_MASK | + MXC_CCM_LTR1_DNCNT_MASK)) | upcnt | dncnt; + __raw_writel(reg, MXC_CCM_LTR1); +} + +/*! + * This function is called for module initialization. + * It sets up the DVFS hardware. + * It sets default values for DVFS thresholds and counters. The default + * values was chosen from a set of different reasonable values. They was tested + * and the default values in the driver gave the best results. + * More work should be done to find optimal values. + * + * @return 0 if successful; non-zero otherwise. + * + */ +static int init_dvfs_controller(void) +{ + u32 i, reg; + + /* setup LTR0 */ + reg = __raw_readl(MXC_CCM_LTR0); + reg = (reg & ~(MXC_CCM_LTR0_DIV3CK_MASK)) | DVFS_DIV3CK; + __raw_writel(reg, MXC_CCM_LTR0); + + /* set up LTR1 */ + reg = __raw_readl(MXC_CCM_LTR1); + reg = (reg & ~(MXC_CCM_LTR1_PNCTHR_MASK | MXC_CCM_LTR1_LTBRSR_MASK)); + reg = reg | DVFS_PNCTHR | DVFS_LTBRSR; + __raw_writel(reg, MXC_CCM_LTR1); + + /* setup LTR2 */ + reg = __raw_readl(MXC_CCM_LTR2); + reg = (reg & ~(MXC_CCM_LTR2_EMAC_MASK)) | DVFS_EMAC; + __raw_writel(reg, MXC_CCM_LTR2); + + /* Set general purpose weights to 0 */ + for (i = 0; i < 16; i++) + set_gp_weight(i, ltr_gp_weight[i]); + + /* ARM interrupt, mask load buf full interrupt */ + reg = __raw_readl(MXC_CCM_PMCR0); + reg |= MXC_CCM_PMCR0_DVFIS | MXC_CCM_PMCR0_LBMI; + __raw_writel(reg, MXC_CCM_PMCR0); + + return 0; +} + +static void dvfs_workqueue_handler(struct work_struct *work) +{ + u32 pmcr0 = stored_pmcr0; + u32 fsvai = (pmcr0 & MXC_CCM_PMCR0_FSVAI_MASK) >> + MXC_CCM_PMCR0_FSVAI_OFFSET; + u32 dvsup = (pmcr0 & MXC_CCM_PMCR0_DVSUP_MASK) >> + MXC_CCM_PMCR0_DVSUP_OFFSET; + u32 curr_cpu; + u8 curr_dvfs; + + if (!dvfs_is_active) + return; + + if (fsvai == FSVAI_FREQ_NOCHANGE) { + /* Do nothing. Freq change is not required */ + printk(KERN_WARNING "fsvai should not be 0\n"); + goto exit; + } + + if (((dvsup == DVSUP_LOW) && (fsvai == FSVAI_FREQ_DECREASE)) || + ((dvsup == DVSUP_TURBO) && ((fsvai == FSVAI_FREQ_INCREASE) || + (fsvai == FSVAI_FREQ_EMERG)))) { + /* Interrupt should be disabled in these cases according to + * the spec since DVFS is already at lowest (highest) state */ + printk(KERN_WARNING "Something is wrong?\n"); + goto exit; + } + + /*Disable DPTC voltage update */ + pmcr0 = pmcr0 & ~MXC_CCM_PMCR0_DPVCR; + __raw_writel(pmcr0, MXC_CCM_PMCR0); + + curr_cpu = clk_get_rate(cpu_clk); + for (curr_dvfs = 0; curr_dvfs < dvfs_wp_num; curr_dvfs++) { + if (dvfs_wp_tbl[curr_dvfs].cpu_rate == curr_cpu) { + if (fsvai == FSVAI_FREQ_DECREASE) { + curr_dvfs--; + dvfs_nr_dn[dvsup]++; + /*reduce frequency and then voltage */ + clk_set_rate(cpu_clk, + dvfs_wp_tbl[curr_dvfs].cpu_rate); + regulator_set_voltage(core_reg, + dvfs_wp_tbl[curr_dvfs]. + core_voltage, + dvfs_wp_tbl[curr_dvfs]. + core_voltage); + pr_info("Decrease frequency to: %ld \n", + dvfs_wp_tbl[curr_dvfs].cpu_rate); + } else { + /*increase freq to the highest one */ + curr_dvfs = dvfs_wp_num - 1; + dvfs_nr_up[dvsup]++; + /*Increase voltage and then frequency */ + regulator_set_voltage(core_reg, + dvfs_wp_tbl[curr_dvfs]. + core_voltage, + dvfs_wp_tbl[curr_dvfs]. + core_voltage); + clk_set_rate(cpu_clk, + dvfs_wp_tbl[curr_dvfs].cpu_rate); + pr_info("Increase frequency to: %ld \n", + dvfs_wp_tbl[curr_dvfs].cpu_rate); + } + pmcr0 = (pmcr0 & ~MXC_CCM_PMCR0_DVSUP_MASK) + | (dvfs_wp_tbl[curr_dvfs].dvsup); + __raw_writel(pmcr0, MXC_CCM_PMCR0); + + set_ltr_thres_counter(dvfs_wp_tbl[curr_dvfs].upthr, + dvfs_wp_tbl[curr_dvfs].dnthr, + dvfs_wp_tbl[curr_dvfs].upcnt, + dvfs_wp_tbl[curr_dvfs].dncnt); + break; + } + } + + exit: + /* unmask interrupt */ + pmcr0 = pmcr0 & ~MXC_CCM_PMCR0_FSVAIM; + __raw_writel(pmcr0, MXC_CCM_PMCR0); + /*DVFS update finish */ + pmcr0 = (pmcr0 | MXC_CCM_PMCR0_DVFS_UPDATE_FINISH); + __raw_writel(pmcr0, MXC_CCM_PMCR0); +} + +static irqreturn_t dvfs_irq(int irq, void *dev_id) +{ + + u32 pmcr0 = __raw_readl(MXC_CCM_PMCR0); + + /* Config dvfs_start bit */ + pmcr0 = pmcr0 | MXC_CCM_PMCR0_DVFS_START; + /*Mask interrupt */ + pmcr0 = pmcr0 | MXC_CCM_PMCR0_FSVAIM; + __raw_writel(pmcr0, MXC_CCM_PMCR0); + + stored_pmcr0 = pmcr0; + schedule_delayed_work(&dvfs_work, 0); + + return IRQ_RETVAL(1); +} + +/*! + * This function enables the DVFS module. + */ +static int start_dvfs(void) +{ + u32 reg = 0; + unsigned long flags; + u8 i; + + if (dvfs_is_active) { + pr_info("DVFS is already started\n"); + return 0; + } + + spin_lock_irqsave(&mxc_dvfs_lock, flags); + + stored_cpu_rate = clk_get_rate(cpu_clk); + for (i = 0; i < dvfs_wp_num; i++) { + if (dvfs_wp_tbl[i].cpu_rate == stored_cpu_rate) { + /*Set LTR0 and LTR1 */ + set_ltr_thres_counter(dvfs_wp_tbl[i].upthr, + dvfs_wp_tbl[i].dnthr, + dvfs_wp_tbl[i].upcnt, + dvfs_wp_tbl[i].dncnt); + + reg = __raw_readl(MXC_CCM_PMCR0); + reg = + (reg & ~MXC_CCM_PMCR0_DVSUP_MASK) | (dvfs_wp_tbl[i]. + dvsup); + /* enable dvfs and interrupt */ + reg = + (reg & ~MXC_CCM_PMCR0_FSVAIM) | MXC_CCM_PMCR0_DVFEN; + + __raw_writel(reg, MXC_CCM_PMCR0); + + dvfs_is_active = 1; + pr_info("DVFS Starts\n"); + break; + } + } + + spin_unlock_irqrestore(&mxc_dvfs_lock, flags); + if (dvfs_is_active) + return 0; + else + return 1; +} + +/*! + * This function disables the DVFS module. + */ +static void stop_dvfs(void) +{ + u32 pmcr0; + unsigned long curr_cpu = clk_get_rate(cpu_clk); + u8 index; + + if (dvfs_is_active) { + + pmcr0 = __raw_readl(MXC_CCM_PMCR0); + /* disable dvfs and its interrupt */ + pmcr0 = (pmcr0 & ~MXC_CCM_PMCR0_DVFEN) | MXC_CCM_PMCR0_FSVAIM; + __raw_writel(pmcr0, MXC_CCM_PMCR0); + + if (stored_cpu_rate < curr_cpu) { + for (index = 0; index < dvfs_wp_num; index++) { + if (dvfs_wp_tbl[index].cpu_rate == + stored_cpu_rate) + break; + } + clk_set_rate(cpu_clk, stored_cpu_rate); + regulator_set_voltage(core_reg, + dvfs_wp_tbl[index].core_voltage, + dvfs_wp_tbl[index].core_voltage); + } else if (stored_cpu_rate > curr_cpu) { + for (index = 0; index < dvfs_wp_num; index++) { + if (dvfs_wp_tbl[index].cpu_rate == + stored_cpu_rate) + break; + } + regulator_set_voltage(core_reg, + dvfs_wp_tbl[index].core_voltage, + dvfs_wp_tbl[index].core_voltage); + clk_set_rate(cpu_clk, stored_cpu_rate); + } + + dvfs_is_active = 0; + } + + pr_info("DVFS is stopped\n"); +} + +static ssize_t dvfs_enable_store(struct sys_device *dev, struct sysdev_attribute *attr, + const char *buf, size_t size) +{ + if (strstr(buf, "1") != NULL) { + if (start_dvfs() != 0) + printk(KERN_ERR "Failed to start DVFS\n"); + } else if (strstr(buf, "0") != NULL) { + stop_dvfs(); + } + + return size; +} + +static ssize_t dvfs_status_show(struct sys_device *dev, struct sysdev_attribute *attr, + char *buf) +{ + int size = 0, i; + + if (dvfs_is_active) + size = sprintf(buf, "DVFS is enabled\n"); + else + size = sprintf(buf, "DVFS is disabled\n"); + + size += sprintf((buf + size), "UP:\t"); + for (i = 0; i < MXC_DVFS_MAX_WP_NUM; i++) + size += sprintf((buf + size), "%d\t", dvfs_nr_up[i]); + size += sprintf((buf + size), "\n"); + + size += sprintf((buf + size), "DOWN:\t"); + for (i = 0; i < MXC_DVFS_MAX_WP_NUM; i++) + size += sprintf((buf + size), "%d\t", dvfs_nr_dn[i]); + size += sprintf((buf + size), "\n"); + + return size; +} + +static ssize_t dvfs_status_store(struct sys_device *dev, struct sysdev_attribute *attr, + const char *buf, size_t size) +{ + if (strstr(buf, "reset") != NULL) { + int i; + for (i = 0; i < MXC_DVFS_MAX_WP_NUM; i++) { + dvfs_nr_up[i] = 0; + dvfs_nr_dn[i] = 0; + } + } + + return size; +} + +static SYSDEV_ATTR(enable, 0200, NULL, dvfs_enable_store); +static SYSDEV_ATTR(status, 0644, dvfs_status_show, dvfs_status_store); + +static struct sysdev_class dvfs_sysclass = { + .name = "dvfs", +}; + +static struct sys_device dvfs_device = { + .id = 0, + .cls = &dvfs_sysclass, +}; + +static int dvfs_sysdev_ctrl_init(void) +{ + int err; + + err = sysdev_class_register(&dvfs_sysclass); + if (!err) + err = sysdev_register(&dvfs_device); + if (!err) { + err = sysdev_create_file(&dvfs_device, &attr_enable); + err = sysdev_create_file(&dvfs_device, &attr_status); + } + + return err; +} + +static void dvfs_sysdev_ctrl_exit(void) +{ + sysdev_remove_file(&dvfs_device, &attr_enable); + sysdev_remove_file(&dvfs_device, &attr_status); + sysdev_unregister(&dvfs_device); + sysdev_class_unregister(&dvfs_sysclass); +} + +static int __init dvfs_init(void) +{ + int err = 0; + u8 index; + unsigned long curr_cpu; + + if (cpu_is_mx35_rev(CHIP_REV_1_0) == 1) { + /* + * Don't support DVFS for auto path in TO1 because + * the voltages under 399M are all 1.2v + */ + if (!(__raw_readl(MXC_CCM_PDR0) & MXC_CCM_PDR0_AUTO_CON)) { + pr_info("MX35 TO1 auto path, no need to use DVFS \n"); + return -1; + } + } + + cpu_clk = clk_get(NULL, "cpu_clk"); + curr_cpu = clk_get_rate(cpu_clk); + + if (board_is_rev(BOARD_REV_2)) + core_reg = regulator_get(NULL, "SW2"); + else + core_reg = regulator_get(NULL, "SW3"); + + dvfs_is_active = 0; + + /*Set voltage */ + for (index = 0; index < dvfs_wp_num; index++) { + if (dvfs_wp_tbl[index].cpu_rate == curr_cpu + && !IS_ERR(core_reg)) { + regulator_set_voltage(core_reg, + dvfs_wp_tbl[index].core_voltage, + dvfs_wp_tbl[index].core_voltage); + break; + } + } + + err = init_dvfs_controller(); + if (err) { + printk(KERN_ERR "DVFS: Unable to initialize DVFS"); + return err; + } + + INIT_DELAYED_WORK(&dvfs_work, dvfs_workqueue_handler); + + /* request the DVFS interrupt */ + err = request_irq(MXC_INT_DVFS, dvfs_irq, IRQF_DISABLED, "dvfs", NULL); + if (err) { + printk(KERN_ERR "DVFS: Unable to attach to DVFS interrupt"); + return err; + } + + err = dvfs_sysdev_ctrl_init(); + if (err) { + printk(KERN_ERR + "DVFS: Unable to register sysdev entry for dvfs"); + return err; + } + + return err; +} + +static void __exit dvfs_cleanup(void) +{ + stop_dvfs(); + + /* release the DVFS interrupt */ + free_irq(MXC_INT_DVFS, NULL); + + dvfs_sysdev_ctrl_exit(); + + clk_put(cpu_clk); + regulator_put(core_reg); +} + +module_init(dvfs_init); +module_exit(dvfs_cleanup); + +MODULE_AUTHOR("Freescale Seminconductor, Inc."); +MODULE_DESCRIPTION("DVFS driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-mx35/iomux.c b/arch/arm/mach-mx35/iomux.c new file mode 100644 index 000000000000..5d5547344896 --- /dev/null +++ b/arch/arm/mach-mx35/iomux.c @@ -0,0 +1,206 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup GPIO_MX35 Board GPIO and Muxing Setup + * @ingroup MSL_MX35 + */ +/*! + * @file mach-mx35/iomux.c + * + * @brief I/O Muxing control functions + * + * @ingroup GPIO_MX35 + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <mach/hardware.h> +#include <mach/gpio.h> +#include <mach/irqs.h> +#include "iomux.h" + +/*! + * IOMUX register (base) addresses + */ +#define IOMUXGPR (IO_ADDRESS(IOMUXC_BASE_ADDR)) +#define IOMUXSW_MUX_CTL (IO_ADDRESS(IOMUXC_BASE_ADDR) + 4) +#define IOMUXSW_MUX_END (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x324) +#define IOMUXSW_PAD_CTL (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x328) +#define IOMUXSW_PAD_END (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x790) +#define IOMUXSW_INPUT_CTL (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x7A8) +#define IOMUXSW_INPUT_END (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x9F4) + +#define MUX_PIN_NUM_MAX \ + (((IOMUXSW_PAD_END - IOMUXSW_PAD_CTL) >> 2) + 1) +#define MUX_INPUT_NUM_MUX \ + (((IOMUXSW_INPUT_END - IOMUXSW_INPUT_CTL) >> 2) + 1) + +#define PIN_TO_IOMUX_INDEX(pin) ((PIN_TO_IOMUX_PAD(pin) - 0x328) >> 2) + +static DEFINE_SPINLOCK(gpio_mux_lock); +static u8 iomux_pin_res_table[MUX_PIN_NUM_MAX]; + +/*! + * This function is used to configure a pin through the IOMUX module. + * FIXED ME: for backward compatible. Will be static function! + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param cfg an output function as defined in \b #iomux_pin_cfg_t + * + * @return 0 if successful; Non-zero otherwise + */ +static int iomux_config_mux(iomux_pin_name_t pin, iomux_pin_cfg_t cfg) +{ + u32 ret = 0; + u32 pin_index = PIN_TO_IOMUX_INDEX(pin); + void *mux_reg = IOMUXGPR + PIN_TO_IOMUX_MUX(pin); + u8 *rp; + + BUG_ON(pin_index > MUX_PIN_NUM_MAX); + BUG_ON((mux_reg > IOMUXSW_MUX_END) || (mux_reg < IOMUXSW_MUX_CTL)); + + spin_lock(&gpio_mux_lock); + __raw_writel(cfg, mux_reg); + /* + * Log a warning if a pin changes ownership + */ + rp = iomux_pin_res_table + pin_index; + if ((cfg & *rp) && (*rp != cfg)) { + /*Console: how to do */ + printk(KERN_ERR "iomux_config_mux: Warning: iomux pin" + " config changed, index=%d register=%p, " + " prev=0x%x new=0x%x\n", pin_index, mux_reg, + *rp, cfg); + ret = -EINVAL; + } + *rp = cfg; + spin_unlock(&gpio_mux_lock); + + return ret; +} + +/*! + * Request ownership for an IO pin. This function has to be the first one + * being called before that pin is used. The caller has to check the + * return value to make sure it returns 0. + * + * @param pin a name defined by \b iomux_pin_name_t + * @param cfg an input function as defined in \b #iomux_pin_cfg_t + * + * @return 0 if successful; Non-zero otherwise + */ +int mxc_request_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t cfg) +{ + u32 gpio = IOMUX_TO_GPIO(pin); + int ret = iomux_config_mux(pin, cfg); + if (gpio < MXC_GPIO_IRQS) { + if (((cfg & (~MUX_CONFIG_SION)) == MUX_CONFIG_GPIO) || + (((cfg & (~MUX_CONFIG_SION)) == MUX_CONFIG_FUNC) && + ((pin == MX35_PIN_GPIO1_0) || (pin == MX35_PIN_GPIO1_1) || + (pin == MX35_PIN_GPIO2_0) || (pin == MX35_PIN_GPIO3_0)))) + ret |= gpio_request(gpio, NULL); + } + return ret; +} + +EXPORT_SYMBOL(mxc_request_iomux); + +/*! + * Release ownership for an IO pin + * + * @param pin a name defined by \b iomux_pin_name_t + * @param cfg an input function as defined in \b #iomux_pin_cfg_t + */ +void mxc_free_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t cfg) +{ + u32 pin_index = PIN_TO_IOMUX_INDEX(pin); + u8 *rp = iomux_pin_res_table + pin_index; + u32 gpio = IOMUX_TO_GPIO(pin); + + BUG_ON((pin_index > MUX_PIN_NUM_MAX)); + + *rp = 0; + if (gpio < MXC_GPIO_IRQS) { + if (((cfg & (~MUX_CONFIG_SION)) == MUX_CONFIG_GPIO) || + (((cfg & (~MUX_CONFIG_SION)) == MUX_CONFIG_FUNC) && + ((pin == MX35_PIN_GPIO1_0) || (pin == MX35_PIN_GPIO1_1) || + (pin == MX35_PIN_GPIO2_0) || (pin == MX35_PIN_GPIO3_0)))) + gpio_free(gpio); + } +} + +EXPORT_SYMBOL(mxc_free_iomux); + +/*! + * This function configures the pad value for a IOMUX pin. + * + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param config the ORed value of elements defined in \b #iomux_pad_config_t + */ +void mxc_iomux_set_pad(iomux_pin_name_t pin, u32 config) +{ + void *pad_reg = IOMUXGPR + PIN_TO_IOMUX_PAD(pin); + + BUG_ON((pad_reg > IOMUXSW_PAD_END) || (pad_reg < IOMUXSW_PAD_CTL)); + + spin_lock(&gpio_mux_lock); + __raw_writel(config, pad_reg); + spin_unlock(&gpio_mux_lock); +} + +EXPORT_SYMBOL(mxc_iomux_set_pad); + +/*! + * This function enables/disables the general purpose function for a particular + * signal. + * + * @param gp one signal as defined in \b #iomux_gp_func_t + * @param en \b #true to enable; \b #false to disable + */ +void mxc_iomux_set_gpr(iomux_gp_func_t gp, bool en) +{ + u32 l; + + spin_lock(&gpio_mux_lock); + l = __raw_readl(IOMUXGPR); + + if (en) + l |= gp; + else + l &= ~gp; + + __raw_writel(l, IOMUXGPR); + spin_unlock(&gpio_mux_lock); +} + +EXPORT_SYMBOL(mxc_iomux_set_gpr); + +/*! + * This function configures input path. + * + * @param input index of input select register as defined in \b + * #iomux_input_select_t + * @param config the binary value of elements defined in \b + * #iomux_input_config_t + */ +void mxc_iomux_set_input(iomux_input_select_t input, u32 config) +{ + void *reg = IOMUXSW_INPUT_CTL + (input << 2); + + BUG_ON(input >= MUX_INPUT_NUM_MUX); + + __raw_writel(config, reg); +} + +EXPORT_SYMBOL(mxc_iomux_set_input); diff --git a/arch/arm/mach-mx35/iomux.h b/arch/arm/mach-mx35/iomux.h new file mode 100644 index 000000000000..dc8de152c5a5 --- /dev/null +++ b/arch/arm/mach-mx35/iomux.h @@ -0,0 +1,295 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MACH_MX35_IOMUX_H__ +#define __MACH_MX35_IOMUX_H__ + +#include <linux/types.h> +#include <mach/gpio.h> +#include "mx35_pins.h" + +/*! + * @file mach-mx35/iomux.h + * + * @brief I/O Muxing control definitions and functions + * + * @ingroup GPIO_MX35 + */ + +typedef unsigned int iomux_pin_name_t; + +/*! + * various IOMUX functions + */ +typedef enum iomux_pin_config { + MUX_CONFIG_FUNC = 0, /*!< used as function */ + MUX_CONFIG_ALT1, /*!< used as alternate function 1 */ + MUX_CONFIG_ALT2, /*!< used as alternate function 2 */ + MUX_CONFIG_ALT3, /*!< used as alternate function 3 */ + MUX_CONFIG_ALT4, /*!< used as alternate function 4 */ + MUX_CONFIG_ALT5, /*!< used as alternate function 5 */ + MUX_CONFIG_ALT6, /*!< used as alternate function 6 */ + MUX_CONFIG_ALT7, /*!< used as alternate function 7 */ + MUX_CONFIG_SION = 0x1 << 4, /*!< used as LOOPBACK:MUX SION bit */ + MUX_CONFIG_GPIO = MUX_CONFIG_ALT5, /*!< used as GPIO */ +} iomux_pin_cfg_t; + +/*! + * various IOMUX pad functions + */ +typedef enum iomux_pad_config { + PAD_CTL_DRV_3_3V = 0x0 << 13, + PAD_CTL_DRV_1_8V = 0x1 << 13, + PAD_CTL_HYS_CMOS = 0x0 << 8, + PAD_CTL_HYS_SCHMITZ = 0x1 << 8, + PAD_CTL_PKE_NONE = 0x0 << 7, + PAD_CTL_PKE_ENABLE = 0x1 << 7, + PAD_CTL_PUE_KEEPER = 0x0 << 6, + PAD_CTL_PUE_PUD = 0x1 << 6, + PAD_CTL_100K_PD = 0x0 << 4, + PAD_CTL_47K_PU = 0x1 << 4, + PAD_CTL_100K_PU = 0x2 << 4, + PAD_CTL_22K_PU = 0x3 << 4, + PAD_CTL_ODE_CMOS = 0x0 << 3, + PAD_CTL_ODE_OpenDrain = 0x1 << 3, + PAD_CTL_DRV_NORMAL = 0x0 << 1, + PAD_CTL_DRV_HIGH = 0x1 << 1, + PAD_CTL_DRV_MAX = 0x2 << 1, + PAD_CTL_SRE_SLOW = 0x0 << 0, + PAD_CTL_SRE_FAST = 0x1 << 0 +} iomux_pad_config_t; + +/*! + * various IOMUX general purpose functions + */ +typedef enum iomux_gp_func { + MUX_SDCTL_CSD0_SEL = 0x1 << 0, + MUX_SDCTL_CSD1_SEL = 0x1 << 1, + MUX_TAMPER_DETECT_EN = 0x1 << 2, +} iomux_gp_func_t; + +/*! + * various IOMUX input select register index + */ +typedef enum iomux_input_select { + MUX_IN_AMX_P5_RXCLK = 0, + MUX_IN_AMX_P5_RXFS, + MUX_IN_AMX_P6_DA, + MUX_IN_AMX_P6_DB, + MUX_IN_AMX_P6_RXCLK, + MUX_IN_AMX_P6_RXFS, + MUX_IN_AMX_P6_TXCLK, + MUX_IN_AMX_P6_TXFS, + MUX_IN_CAN1_CANRX, + MUX_IN_CAN2_CANRX, + MUX_IN_CCM_32K_MUXED, + MUX_IN_CCM_PMIC_RDY, + MUX_IN_CSPI1_SS2_B, + MUX_IN_CSPI1_SS3_B, + MUX_IN_CSPI2_CLK_IN, + MUX_IN_CSPI2_DATAREADY_B, + MUX_IN_CSPI2_MISO, + MUX_IN_CSPI2_MOSI, + MUX_IN_CSPI2_SS0_B, + MUX_IN_CSPI2_SS1_B, + MUX_IN_CSPI2_SS2_B, + MUX_IN_CSPI2_SS3_B, + MUX_IN_EMI_WEIM_DTACK_B, + MUX_IN_ESDHC1_DAT4_IN, + MUX_IN_ESDHC1_DAT5_IN, + MUX_IN_ESDHC1_DAT6_IN, + MUX_IN_ESDHC1_DAT7_IN, + MUX_IN_ESDHC3_CARD_CLK_IN, + MUX_IN_ESDHC3_CMD_IN, + MUX_IN_ESDHC3_DAT0, + MUX_IN_ESDHC3_DAT1, + MUX_IN_ESDHC3_DAT2, + MUX_IN_ESDHC3_DAT3, + MUX_IN_GPIO1_IN_0, + MUX_IN_GPIO1_IN_10, + MUX_IN_GPIO1_IN_11, + MUX_IN_GPIO1_IN_1, + MUX_IN_GPIO1_IN_20, + MUX_IN_GPIO1_IN_21, + MUX_IN_GPIO1_IN_22, + MUX_IN_GPIO1_IN_2, + MUX_IN_GPIO1_IN_3, + MUX_IN_GPIO1_IN_4, + MUX_IN_GPIO1_IN_5, + MUX_IN_GPIO1_IN_6, + MUX_IN_GPIO1_IN_7, + MUX_IN_GPIO1_IN_8, + MUX_IN_GPIO1_IN_9, + MUX_IN_GPIO2_IN_0, + MUX_IN_GPIO2_IN_10, + MUX_IN_GPIO2_IN_11, + MUX_IN_GPIO2_IN_12, + MUX_IN_GPIO2_IN_13, + MUX_IN_GPIO2_IN_14, + MUX_IN_GPIO2_IN_15, + MUX_IN_GPIO2_IN_16, + MUX_IN_GPIO2_IN_17, + MUX_IN_GPIO2_IN_18, + MUX_IN_GPIO2_IN_19, + MUX_IN_GPIO2_IN_1, + MUX_IN_GPIO2_IN_20, + MUX_IN_GPIO2_IN_21, + MUX_IN_GPIO2_IN_22, + MUX_IN_GPIO2_IN_23, + MUX_IN_GPIO2_IN_24, + MUX_IN_GPIO2_IN_25, + MUX_IN_GPIO2_IN_26, + MUX_IN_GPIO2_IN_27, + MUX_IN_GPIO2_IN_28, + MUX_IN_GPIO2_IN_29, + MUX_IN_GPIO2_IN_2, + MUX_IN_GPIO2_IN_30, + MUX_IN_GPIO2_IN_31, + MUX_IN_GPIO2_IN_3, + MUX_IN_GPIO2_IN_4, + MUX_IN_GPIO2_IN_5, + MUX_IN_GPIO2_IN_6, + MUX_IN_GPIO2_IN_7, + MUX_IN_GPIO2_IN_8, + MUX_IN_GPIO2_IN_9, + MUX_IN_GPIO3_IN_0, + MUX_IN_GPIO3_IN_10, + MUX_IN_GPIO3_IN_11, + MUX_IN_GPIO3_IN_12, + MUX_IN_GPIO3_IN_13, + MUX_IN_GPIO3_IN_14, + MUX_IN_GPIO3_IN_15, + MUX_IN_GPIO3_IN_4, + MUX_IN_GPIO3_IN_5, + MUX_IN_GPIO3_IN_6, + MUX_IN_GPIO3_IN_7, + MUX_IN_GPIO3_IN_8, + MUX_IN_GPIO3_IN_9, + MUX_IN_I2C3_SCL_IN, + MUX_IN_I2C3_SDA_IN, + MUX_IN_IPU_DISPB_D0_VSYNC, + MUX_IN_IPU_DISPB_D12_VSYNC, + MUX_IN_IPU_DISPB_SD_D, + MUX_IN_IPU_SENSB_DATA_0, + MUX_IN_IPU_SENSB_DATA_1, + MUX_IN_IPU_SENSB_DATA_2, + MUX_IN_IPU_SENSB_DATA_3, + MUX_IN_IPU_SENSB_DATA_4, + MUX_IN_IPU_SENSB_DATA_5, + MUX_IN_IPU_SENSB_DATA_6, + MUX_IN_IPU_SENSB_DATA_7, + MUX_IN_KPP_COL_0, + MUX_IN_KPP_COL_1, + MUX_IN_KPP_COL_2, + MUX_IN_KPP_COL_3, + MUX_IN_KPP_COL_4, + MUX_IN_KPP_COL_5, + MUX_IN_KPP_COL_6, + MUX_IN_KPP_COL_7, + MUX_IN_KPP_ROW_0, + MUX_IN_KPP_ROW_1, + MUX_IN_KPP_ROW_2, + MUX_IN_KPP_ROW_3, + MUX_IN_KPP_ROW_4, + MUX_IN_KPP_ROW_5, + MUX_IN_KPP_ROW_6, + MUX_IN_KPP_ROW_7, + MUX_IN_OWIRE_BATTERY_LINE, + MUX_IN_SPDIF_HCKT_CLK2, + MUX_IN_SPDIF_SPDIF_IN1, + MUX_IN_UART3_UART_RTS_B, + MUX_IN_UART3_UART_RXD_MUX, + MUX_IN_USB_OTG_DATA_0, + MUX_IN_USB_OTG_DATA_1, + MUX_IN_USB_OTG_DATA_2, + MUX_IN_USB_OTG_DATA_3, + MUX_IN_USB_OTG_DATA_4, + MUX_IN_USB_OTG_DATA_5, + MUX_IN_USB_OTG_DATA_6, + MUX_IN_USB_OTG_DATA_7, + MUX_IN_USB_OTG_DIR, + MUX_IN_USB_OTG_NXT, + MUX_IN_USB_UH2_DATA_0, + MUX_IN_USB_UH2_DATA_1, + MUX_IN_USB_UH2_DATA_2, + MUX_IN_USB_UH2_DATA_3, + MUX_IN_USB_UH2_DATA_4, + MUX_IN_USB_UH2_DATA_5, + MUX_IN_USB_UH2_DATA_6, + MUX_IN_USB_UH2_DATA_7, + MUX_IN_USB_UH2_DIR, + MUX_IN_USB_UH2_NXT, + MUX_IN_USB_UH2_USB_OC, +} iomux_input_select_t; + +/*! + * various IOMUX input functions + */ +typedef enum iomux_input_config { + INPUT_CTL_PATH0 = 0x0, + INPUT_CTL_PATH1, + INPUT_CTL_PATH2, + INPUT_CTL_PATH3, + INPUT_CTL_PATH4, + INPUT_CTL_PATH5, + INPUT_CTL_PATH6, + INPUT_CTL_PATH7, +} iomux_input_cfg_t; + +/*! + * Request ownership for an IO pin. This function has to be the first one + * being called before that pin is used. The caller has to check the + * return value to make sure it returns 0. + * + * @param pin a name defined by \b iomux_pin_name_t + * @param cfg an input function as defined in \b #iomux_pin_cfg_t + * + * @return 0 if successful; Non-zero otherwise + */ +int mxc_request_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t cfg); + +/*! + * Release ownership for an IO pin + * + * @param pin a name defined by \b iomux_pin_name_t + * @param cfg an input function as defined in \b #iomux_pin_cfg_t + */ +void mxc_free_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t cfg); + +/*! + * This function enables/disables the general purpose function for a particular + * signal. + * + * @param gp one signal as defined in \b #iomux_gp_func_t + * @param en \b #true to enable; \b #false to disable + */ +void mxc_iomux_set_gpr(iomux_gp_func_t gp, bool en); + +/*! + * This function configures the pad value for a IOMUX pin. + * + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param config the ORed value of elements defined in \b + * #iomux_pad_config_t + */ +void mxc_iomux_set_pad(iomux_pin_name_t pin, u32 config); + +/*! + * This function configures input path. + * + * @param input index of input select register as defined in \b + * #iomux_input_select_t + * @param config the binary value of elements defined in \b + * #iomux_input_cfg_t + */ +void mxc_iomux_set_input(iomux_input_select_t input, u32 config); +#endif diff --git a/arch/arm/mach-mx35/mm.c b/arch/arm/mach-mx35/mm.c new file mode 100644 index 000000000000..2d86bae4b3bd --- /dev/null +++ b/arch/arm/mach-mx35/mm.c @@ -0,0 +1,77 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/mm.h> +#include <linux/init.h> +#include <mach/hardware.h> +#include <asm/pgtable.h> +#include <asm/mach/map.h> + +/*! + * @file mach-mx35/mm.c + * + * @brief This file creates static mapping between physical to virtual memory. + * + * @ingroup Memory_MX35 + */ + +/*! + * This structure defines the MX35 memory map. + */ +static struct map_desc mx35_io_desc[] __initdata = { + { + .virtual = IRAM_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(MX35_IRAM_BASE_ADDR), + .length = MX35_IRAM_SIZE, + .type = MT_DEVICE_NONSHARED}, + { + .virtual = X_MEMC_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(X_MEMC_BASE_ADDR), + .length = X_MEMC_SIZE, + .type = MT_DEVICE}, + { + .virtual = NFC_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(NFC_BASE_ADDR), + .length = NFC_SIZE, + .type = MT_DEVICE_NONSHARED}, + { + .virtual = AVIC_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(AVIC_BASE_ADDR), + .length = AVIC_SIZE, + .type = MT_DEVICE_NONSHARED}, + { + .virtual = AIPS1_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(AIPS1_BASE_ADDR), + .length = AIPS1_SIZE, + .type = MT_DEVICE_NONSHARED}, + { + .virtual = SPBA0_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(SPBA0_BASE_ADDR), + .length = SPBA0_SIZE, + .type = MT_DEVICE_NONSHARED}, + { + .virtual = AIPS2_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(AIPS2_BASE_ADDR), + .length = AIPS2_SIZE, + .type = MT_DEVICE_NONSHARED}, +}; + +/*! + * This function initializes the memory map. It is called during the + * system startup to create static physical to virtual memory map for + * the IO modules. + */ +void __init mx35_map_io(void) +{ + iotable_init(mx35_io_desc, ARRAY_SIZE(mx35_io_desc)); +} diff --git a/arch/arm/mach-mx35/mx35_3stack.c b/arch/arm/mach-mx35/mx35_3stack.c new file mode 100644 index 000000000000..02fed337d772 --- /dev/null +++ b/arch/arm/mach-mx35/mx35_3stack.c @@ -0,0 +1,1260 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/nodemask.h> +#include <linux/clk.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/ata.h> +#include <linux/pmic_external.h> +#include <linux/mfd/mc9s08dz60/pmic.h> +#include <linux/regulator/consumer.h> +#include <linux/smsc911x.h> +#include <linux/i2c/tsc2007.h> +#if defined(CONFIG_MTD) || defined(CONFIG_MTD_MODULE) +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> +#include <linux/delay.h> + +#include <asm/mach/flash.h> +#endif + +#include <mach/hardware.h> +#include <asm/irq.h> +#include <asm/setup.h> +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <asm/mach/irq.h> +#include <asm/mach/time.h> +#include <mach/common.h> +#include <mach/memory.h> +#include <mach/gpio.h> +#include <mach/mmc.h> + +#include "board-mx35_3stack.h" +#include "crm_regs.h" +#include "iomux.h" + +/*! + * @file mach-mx35/mx35_3stack.c + * + * @brief This file contains the board specific initialization routines. + * + * @ingroup MSL_MX35 + */ + +unsigned int mx35_3stack_board_io; + +static void mxc_nop_release(struct device *dev) +{ + /* Nothing */ +} + +/* MTD NOR flash */ + +#if defined(CONFIG_MTD_MXC) || defined(CONFIG_MTD_MXC_MODULE) + +static struct mtd_partition mxc_nor_partitions[] = { + { + .name = "Bootloader", + .size = 512 * 1024, + .offset = 0x00000000, + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "nor.Kernel", + .size = 4 * 1024 * 1024, + .offset = MTDPART_OFS_APPEND, + .mask_flags = 0}, + { + .name = "nor.userfs", + .size = 30 * 1024 * 1024, + .offset = MTDPART_OFS_APPEND, + .mask_flags = 0}, + { + .name = "nor.rootfs", + .size = 28 * 1024 * 1024, + .offset = MTDPART_OFS_APPEND, + .mask_flags = MTD_WRITEABLE}, + { + .name = "FIS directory", + .size = 12 * 1024, + .offset = 0x01FE0000, + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, + { + .name = "Redboot config", + .size = MTDPART_SIZ_FULL, + .offset = 0x01FFF000, + .mask_flags = MTD_WRITEABLE /* force read-only */ + }, +}; + +static struct flash_platform_data mxc_flash_data = { + .map_name = "cfi_probe", + .width = 2, + .parts = mxc_nor_partitions, + .nr_parts = ARRAY_SIZE(mxc_nor_partitions), +}; + +static struct resource mxc_flash_resource = { + .start = 0xa0000000, + .end = 0xa0000000 + 0x04000000 - 1, + .flags = IORESOURCE_MEM, + +}; + +static struct platform_device mxc_nor_mtd_device = { + .name = "mxc_nor_flash", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_flash_data, + }, + .num_resources = 1, + .resource = &mxc_flash_resource, +}; + +static void mxc_init_nor_mtd(void) +{ + (void)platform_device_register(&mxc_nor_mtd_device); +} +#else +static void mxc_init_nor_mtd(void) +{ +} +#endif + +/* MTD NAND flash */ + +#if defined(CONFIG_MTD_NAND_MXC) || defined(CONFIG_MTD_NAND_MXC_MODULE) \ +|| defined(CONFIG_MTD_NAND_MXC_V2) || defined(CONFIG_MTD_NAND_MXC_V2_MODULE) + +static struct mtd_partition mxc_nand_partitions[] = { + { + .name = "nand.bootloader", + .offset = 0, + .size = 1024 * 1024}, + { + .name = "nand.kernel", + .offset = MTDPART_OFS_APPEND, + .size = 5 * 1024 * 1024}, + { + .name = "nand.rootfs", + .offset = MTDPART_OFS_APPEND, + .size = 256 * 1024 * 1024}, + { + .name = "nand.configure", + .offset = MTDPART_OFS_APPEND, + .size = 8 * 1024 * 1024}, + { + .name = "nand.userfs", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL}, +}; + +static struct flash_platform_data mxc_nand_data = { + .parts = mxc_nand_partitions, + .nr_parts = ARRAY_SIZE(mxc_nand_partitions), + .width = 1, +}; + +static struct platform_device mxc_nand_mtd_device = { + .name = "mxc_nandv2_flash", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_nand_data, + }, +}; + +static void mxc_init_nand_mtd(void) +{ + if (__raw_readl(MXC_CCM_RCSR) & MXC_CCM_RCSR_NF16B) + mxc_nand_data.width = 2; + + platform_device_register(&mxc_nand_mtd_device); +} +#else +static inline void mxc_init_nand_mtd(void) +{ +} +#endif + +static struct mxc_lcd_platform_data lcd_data = { + .io_reg = "LCD" +}; + +static struct platform_device lcd_dev = { + .name = "lcd_claa", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = (void *)&lcd_data, + }, +}; + +static void mxc_init_lcd(void) +{ + platform_device_register(&lcd_dev); +} + +#if defined(CONFIG_FB_MXC_SYNC_PANEL) || defined(CONFIG_FB_MXC_SYNC_PANEL_MODULE) +/* mxc lcd driver */ +static struct platform_device mxc_fb_device = { + .name = "mxc_sdc_fb", + .id = 0, + .dev = { + .release = mxc_nop_release, + .coherent_dma_mask = 0xFFFFFFFF, + }, +}; + +static void mxc_init_fb(void) +{ + (void)platform_device_register(&mxc_fb_device); +} +#else +static inline void mxc_init_fb(void) +{ +} +#endif + +#if defined(CONFIG_BACKLIGHT_MXC) +static struct platform_device mxcbl_devices[] = { +#if defined(CONFIG_BACKLIGHT_MXC_IPU) || defined(CONFIG_BACKLIGHT_MXC_IPU_MODULE) + { + .name = "mxc_ipu_bl", + .id = 0, + .dev = { + .platform_data = (void *)3, /* DISP # for this backlight */ + }, + } +#endif +}; + +static inline void mxc_init_bl(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(mxcbl_devices); i++) { + platform_device_register(&mxcbl_devices[i]); + } +} +#else +static inline void mxc_init_bl(void) +{ +} +#endif + +#if defined(CONFIG_MXC_MLB) || defined(CONFIG_MXC_MLB_MODULE) +static struct resource mlb_resource[] = { + [0] = { + .start = MLB_BASE_ADDR, + .end = MLB_BASE_ADDR + 0x300, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_MLB, + .end = MXC_INT_MLB, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mxc_mlb_platform_data mlb_data = { + .buf_address = IRAM_BASE_ADDR_VIRT + MLB_IRAM_ADDR_OFFSET, + .phy_address = IRAM_BASE_ADDR + MLB_IRAM_ADDR_OFFSET, + .reg_nvcc = "VVIDEO", + .mlb_clk = "mlb_clk", +}; + +static struct platform_device mlb_dev = { + .name = "mxc_mlb", + .id = 0, + .dev = { + .platform_data = &mlb_data, + }, + .num_resources = ARRAY_SIZE(mlb_resource), + .resource = mlb_resource, +}; + +static inline void mxc_init_mlb(void) +{ + platform_device_register(&mlb_dev); +} +#else +static inline void mxc_init_mlb(void) +{ +} +#endif + +static void mxc_unifi_hardreset(int pin_level) +{ + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_1, 1, pin_level & 0x01); +} + +static void mxc_unifi_enable(int en) +{ + if (en) { + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 5, 1); + msleep(10); + } else + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 5, 0); +} + +static struct mxc_unifi_platform_data unifi_data = { + .hardreset = mxc_unifi_hardreset, + .enable = mxc_unifi_enable, + .reg_gpo1 = "GPO2", + .reg_gpo2 = "GPO3", + .reg_1v5_ana_bb = "PWGT1", + .reg_vdd_vpa = "VAUDIO", + .reg_1v5_dd = "SW1", + .host_id = 1, +}; + +struct mxc_unifi_platform_data *get_unifi_plat_data(void) +{ + return &unifi_data; +} + +EXPORT_SYMBOL(get_unifi_plat_data); + +static int tsc2007_get_pendown_state(void) +{ + return !gpio_get_value(IOMUX_TO_GPIO(MX35_PIN_CAPTURE)); +} + +static int tsc2007_init(void) +{ + return 0; +} + +static void tsc2007_exit(void) +{ +} + +struct tsc2007_platform_data tsc2007_data = { + .model = 2007, + .x_plate_ohms = 400, + .get_pendown_state = tsc2007_get_pendown_state, + .init_platform_hw = tsc2007_init, + .exit_platform_hw = tsc2007_exit, +}; + +static struct mxc_camera_platform_data camera_data = { + .core_regulator = "SW1", + .io_regulator = "VAUDIO", + .analog_regulator = NULL, + .gpo_regulator = "PWGT1", + .mclk = 27000000, +}; + +void si4702_reset(void) +{ + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_1, 4, 0); + msleep(100); + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_1, 4, 1); + msleep(100); +} + +void si4702_clock_ctl(int flag) +{ + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 7, flag); +} + +static void si4702_gpio_get(void) +{ +} + +static void si4702_gpio_put(void) +{ +} + +static struct mxc_fm_platform_data si4702_data = { + .reg_vio = "VSD", + .reg_vdd = NULL, + .gpio_get = si4702_gpio_get, + .gpio_put = si4702_gpio_put, + .reset = si4702_reset, + .clock_ctl = si4702_clock_ctl, + .sksnr = 0, + .skcnt = 0, + .band = 0, + .space = 100, + .seekth = 0xa, +}; + +static void adv7180_pwdn(int pwdn) +{ + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 1, pwdn); +} + +static void adv7180_reset(void) +{ + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_1, 6, 0); + msleep(5); + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_1, 6, 1); + msleep(5); +} + +static struct mxc_tvin_platform_data adv7180_data = { + .dvddio_reg = NULL, + .dvdd_reg = "SW3", + .avdd_reg = "PWGT2", + .pvdd_reg = NULL, + .pwdn = adv7180_pwdn, + .reset = adv7180_reset, +}; + +static struct i2c_board_info mxc_i2c_board_info[] __initdata = { + { + .type = "max8660", + .addr = 0x34, + }, + { + .type = "tsc2007", + .addr = 0x48, + .platform_data = &tsc2007_data, + .irq = IOMUX_TO_IRQ(MX35_PIN_CAPTURE), + }, + { + .type = "ov2640", + .addr = 0x30, + .platform_data = (void *)&camera_data, + }, + { + .type = "sgtl5000-i2c", + .addr = 0x0a, + }, + { + .type = "ak4647-i2c", + .addr = 0x12, + }, +#if defined(CONFIG_I2C_SLAVE_CLIENT) + { + .type = "i2c-slave-client", + .addr = 0x55, + }, +#endif + { + .type = "si4702", + .addr = 0x10, + .platform_data = (void *)&si4702_data, + }, + { + .type = "adv7180", + .addr = 0x21, + .platform_data = (void *)&adv7180_data, + }, +}; + +static struct spi_board_info mxc_spi_board_info[] __initdata = { + { + .modalias = "wm8580_spi", + .max_speed_hz = 8000000, /* max spi SCK clock speed in HZ */ + .bus_num = 1, + .chip_select = 1, + }, +}; + +#if defined(CONFIG_SMSC911X) || defined(CONFIG_SMSC911X_MODULE) +static struct resource smsc911x_resources[] = { + { + .start = LAN9217_BASE_ADDR, + .end = LAN9217_BASE_ADDR + 0x100, + .flags = IORESOURCE_MEM, + }, + { + .start = LAN9217_IRQ, + .end = LAN9217_IRQ, + .flags = IORESOURCE_IRQ, + } +}; + +struct smsc911x_platform_config smsc911x_config = { + .irq_polarity = SMSC911X_IRQ_POLARITY_ACTIVE_LOW, + .flags = SMSC911X_USE_32BIT | SMSC911X_FORCE_INTERNAL_PHY, +}; + +static struct platform_device smsc_lan9217_device = { + .name = "smsc911x", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &smsc911x_config, + }, + .num_resources = ARRAY_SIZE(smsc911x_resources), + .resource = smsc911x_resources, +}; + +static void mxc_init_enet(void) +{ + platform_device_register(&smsc_lan9217_device); +} +#else +static inline void mxc_init_enet(void) +{ +} +#endif + +#if defined(CONFIG_FEC) || defined(CONFIG_FEC_MODULE) +static struct resource mxc_fec_resources[] = { + { + .start = FEC_BASE_ADDR, + .end = FEC_BASE_ADDR + 0xfff, + .flags = IORESOURCE_MEM + }, { + .start = MXC_INT_FEC, + .end = MXC_INT_FEC, + .flags = IORESOURCE_IRQ + }, +}; + +struct platform_device mxc_fec_device = { + .name = "fec", + .id = 0, + .num_resources = ARRAY_SIZE(mxc_fec_resources), + .resource = mxc_fec_resources, +}; + +static __init int mxc_init_fec(void) +{ + return platform_device_register(&mxc_fec_device); +} +#else +static inline int mxc_init_fec(void) +{ + return 0; +} +#endif + +#if defined(CONFIG_MMC_IMX_ESDHCI) || defined(CONFIG_MMC_IMX_ESDHCI_MODULE) +static struct mxc_mmc_platform_data mmc1_data = { + .ocr_mask = MMC_VDD_32_33, +#if defined(CONFIG_SDIO_UNIFI_FS) || defined(CONFIG_SDIO_UNIFI_FS_MODULE) + .caps = MMC_CAP_4_BIT_DATA, +#else + .caps = MMC_CAP_8_BIT_DATA, +#endif + .min_clk = 150000, + .max_clk = 52000000, + .card_inserted_state = 0, + .status = sdhc_get_card_det_status, + .wp_status = sdhc_write_protect, + .clock_mmc = "sdhc_clk", +}; + +/*! + * Resource definition for the SDHC1 + */ +static struct resource mxcsdhc1_resources[] = { + [0] = { + .start = MMC_SDHC1_BASE_ADDR, + .end = MMC_SDHC1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MX35_INT_MMC_SDHC1, + .end = MX35_INT_MMC_SDHC1, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = MXC_PSEUDO_IRQ_SD1_CD, + .end = MXC_PSEUDO_IRQ_SD1_CD, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Device Definition for MXC SDHC1 */ +static struct platform_device mxcsdhc1_device = { + .name = "mxsdhci", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mmc1_data, + }, + .num_resources = ARRAY_SIZE(mxcsdhc1_resources), + .resource = mxcsdhc1_resources, +}; + +#if defined(CONFIG_SDIO_UNIFI_FS) || defined(CONFIG_SDIO_UNIFI_FS_MODULE) +static struct mxc_mmc_platform_data mmc2_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30 | + MMC_VDD_31_32, + .caps = MMC_CAP_4_BIT_DATA, + .min_clk = 150000, + .max_clk = 50000000, + .card_inserted_state = 0, + .status = sdhc_get_card_det_status, + .wp_status = sdhc_write_protect, + .clock_mmc = "sdhc_clk", +}; + +static struct resource mxcsdhc2_resources[] = { + [0] = { + .start = MMC_SDHC2_BASE_ADDR, + .end = MMC_SDHC2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_MMC_SDHC2, + .end = MXC_INT_MMC_SDHC2, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxcsdhc2_device = { + .name = "mxsdhci", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mmc2_data, + }, + .num_resources = ARRAY_SIZE(mxcsdhc2_resources), + .resource = mxcsdhc2_resources, +}; +#endif + +static inline void mxc_init_mmc(void) +{ + (void)platform_device_register(&mxcsdhc1_device); +#if defined(CONFIG_SDIO_UNIFI_FS) || defined(CONFIG_SDIO_UNIFI_FS_MODULE) + (void)platform_device_register(&mxcsdhc2_device); +#endif +} +#else +static inline void mxc_init_mmc(void) +{ +} +#endif + +#ifdef CONFIG_MXC_PSEUDO_IRQS +/*! Device Definition for MXC SDHC1 */ +static struct platform_device mxc_pseudo_irq_device = { + .name = "mxc_pseudo_irq", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, +}; + +static inline int mxc_init_pseudo_irq(void) +{ + return platform_device_register(&mxc_pseudo_irq_device); +} + +late_initcall(mxc_init_pseudo_irq); + +/*! + * Power Key interrupt handler. + */ +static irqreturn_t power_key_int(int irq, void *dev_id) +{ + pr_info(KERN_INFO "on-off key pressed\n"); + return 0; +} + +/*! + * Power Key initialization. + */ +static int __init mxc_init_power_key(void) +{ + if (!board_is_rev(BOARD_REV_2)) { + /*Set power key as wakeup resource */ + int irq, ret; + irq = MXC_PSEUDO_IRQ_POWER_KEY; + set_irq_type(irq, IRQF_TRIGGER_RISING); + ret = request_irq(irq, power_key_int, 0, "power_key", 0); + if (ret) + pr_info("register on-off key interrupt failed\n"); + else + enable_irq_wake(irq); + return ret; + } + return 0; +} + +late_initcall(mxc_init_power_key); +#endif + +#if defined(CONFIG_PATA_FSL) || defined(CONFIG_PATA_FSL_MODULE) +extern void gpio_ata_active(void); +extern void gpio_ata_inactive(void); + +static int ata_init(struct platform_device *pdev) +{ + /* Configure the pins */ + gpio_ata_active(); + + return 0; +} + +static void ata_exit(void) +{ + /* Free the pins */ + gpio_ata_inactive(); +} + +static struct fsl_ata_platform_data ata_data = { + .adma_flag = 1, /* 0:smart dma, 1:ADMA */ + .udma_mask = 0x3F, + .mwdma_mask = 0x1F, + .pio_mask = ATA_PIO4, + .fifo_alarm = MXC_IDE_DMA_WATERMARK / 2, + .max_sg = MXC_IDE_DMA_BD_NR, + .init = ata_init, + .exit = ata_exit, + .core_reg = NULL, /*"LDO2", */ + .io_reg = NULL, /*"LDO3", */ +}; + +static struct resource pata_fsl_resources[] = { + [0] = { /* I/O */ + .start = ATA_BASE_ADDR, + .end = ATA_BASE_ADDR + 0x000000C8, + .flags = IORESOURCE_MEM, + }, + [2] = { /* IRQ */ + .start = MXC_INT_ATA, + .end = MXC_INT_ATA, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device pata_fsl_device = { + .name = "pata_fsl", + .id = -1, + .num_resources = ARRAY_SIZE(pata_fsl_resources), + .resource = pata_fsl_resources, + .dev = { + .platform_data = &ata_data, + .coherent_dma_mask = ~0, + }, +}; + +static void __init mxc_init_pata(void) +{ + (void)platform_device_register(&pata_fsl_device); +} +#else /* CONFIG_PATA_FSL */ +static void __init mxc_init_pata(void) +{ +} +#endif /* CONFIG_PATA_FSL */ + +#if defined(CONFIG_GPS_IOCTRL) || defined(CONFIG_GPS_IOCTRL_MODULE) +static struct mxc_gps_platform_data gps_data = { + .core_reg = "SW3", + .analog_reg = "PWGT2", +}; + +static struct platform_device mxc_gps_device = { + .name = "gps_ioctrl", + .id = 0, + .dev = { + .platform_data = &gps_data, + }, +}; + +static void __init mxc_init_gps(void) +{ + (void)platform_device_register(&mxc_gps_device); +} +#else +static void __init mxc_init_gps(void) +{ +} +#endif + +/*! + * Board specific fixup function. It is called by \b setup_arch() in + * setup.c file very early on during kernel starts. It allows the user to + * statically fill in the proper values for the passed-in parameters. None of + * the parameters is used currently. + * + * @param desc pointer to \b struct \b machine_desc + * @param tags pointer to \b struct \b tag + * @param cmdline pointer to the command line + * @param mi pointer to \b struct \b meminfo + */ +static void __init fixup_mxc_board(struct machine_desc *desc, struct tag *tags, + char **cmdline, struct meminfo *mi) +{ + mxc_cpu_init(); + +#ifdef CONFIG_DISCONTIGMEM + do { + int nid; + mi->nr_banks = MXC_NUMNODES; + for (nid = 0; nid < mi->nr_banks; nid++) + SET_NODE(mi, nid); + } while (0); +#endif +} + +static void bt_reset(void) +{ + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_1, 2, 0); + msleep(5); + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_1, 2, 1); +} + +static struct mxc_bt_platform_data mxc_bt_data = { + .bt_vdd = "GPO2", + .bt_vdd_parent = NULL, + .bt_vusb = NULL, + .bt_vusb_parent = NULL, + .bt_reset = bt_reset, +}; + +static struct platform_device mxc_bt_device = { + .name = "mxc_bt", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_bt_data, + }, +}; + +static void mxc_init_bluetooth(void) +{ + (void)platform_device_register(&mxc_bt_device); +} + +#if defined(CONFIG_SND_SOC_IMX_3STACK_SGTL5000) \ + || defined(CONFIG_SND_SOC_IMX_3STACK_SGTL5000_MODULE) + +static int sgtl5000_headphone_det_status(void) +{ + int ret = 0; + if (0 != pmic_gpio_get_designation_bit_val(0, &ret)) + printk(KERN_ERR "Get headphone status error."); + return ret; +} + +static int mxc_sgtl5000_plat_init(void); +static int mxc_sgtl5000_plat_finit(void); +static int mxc_sgtl5000_amp_enable(int enable); + +static struct mxc_audio_platform_data sgtl5000_data = { + .ssi_num = 1, + .src_port = 1, + .ext_port = 4, + .hp_irq = MXC_PSEUDO_IRQ_HEADPHONE, + .hp_status = sgtl5000_headphone_det_status, + .vddio_reg = NULL, + .vdda_reg = "VCAM", + .amp_enable = mxc_sgtl5000_amp_enable, + .vddio = 0, + .vdda = 3000000, + .vddd = 0, + .sysclk = 12000000, + .init = mxc_sgtl5000_plat_init, + .finit = mxc_sgtl5000_plat_finit, +}; + +static struct platform_device mxc_sgtl5000_device = { + .name = "imx-3stack-sgtl5000", + .dev = { + .release = mxc_nop_release, + .platform_data = &sgtl5000_data, + }, +}; + +static int mxc_sgtl5000_plat_init(void) +{ + struct regulator *reg; + reg = regulator_get(&mxc_sgtl5000_device.dev, "SPKR"); + if (IS_ERR(reg)) + return -EINVAL; + sgtl5000_data.priv = reg; + return 0; +} + +static int mxc_sgtl5000_plat_finit(void) +{ + struct regulator *reg; + reg = sgtl5000_data.priv; + if (reg) { + regulator_put(reg); + sgtl5000_data.priv = NULL; + } + return 0; +} + +static int mxc_sgtl5000_amp_enable(int enable) +{ + struct regulator *reg; + reg = sgtl5000_data.priv; + + if (!reg) + return -EINVAL; + if (enable) + regulator_enable(reg); + else + regulator_disable(reg); + return 0; +} + +static void mxc_init_sgtl5000(void) +{ + struct clk *cko1, *parent; + unsigned long rate; + + /* for board v1.1 do nothing */ + if (!board_is_rev(BOARD_REV_2)) + return; + + cko1 = clk_get(NULL, "cko1_clk"); + if (IS_ERR(cko1)) + return; + parent = clk_get(NULL, "ckih"); + if (IS_ERR(parent)) + return; + clk_set_parent(cko1, parent); + rate = clk_round_rate(cko1, 12000000); + if (rate < 8000000 || rate > 27000000) { + printk(KERN_ERR "Error: SGTL5000 mclk freq %d out of range!\n", + (unsigned int)rate); + clk_put(parent); + clk_put(cko1); + return; + } + clk_set_rate(cko1, rate); + clk_enable(cko1); + sgtl5000_data.sysclk = rate; + platform_device_register(&mxc_sgtl5000_device); +} +#else +static void mxc_init_sgtl5000(void) +{ +} +#endif + +#if defined(CONFIG_SND_SOC_IMX_3STACK_AK4647) \ + || defined(CONFIG_SND_SOC_IMX_3STACK_AK4647_MODULE) +static int mxc_ak4647_amp_enable(int enable) +{ + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 0, enable); + return 0; +} + +static int mxc_ak4647_plat_init(void) +{ + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_2, 1, 0); + msleep(1); + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_2, 1, 1); + return 0; +} + +static int ak4647_headphone_det_status(void) +{ + int ret = 0; + if (0 != pmic_gpio_get_designation_bit_val(0, &ret)) + printk(KERN_ERR "Get headphone status error."); + return ret; +} + +static struct mxc_audio_platform_data mxc_ak4647_data = { + .ssi_num = 1, + .src_port = 1, + .ext_port = 4, + .amp_enable = mxc_ak4647_amp_enable, + .init = mxc_ak4647_plat_init, + .hp_status = ak4647_headphone_det_status, + .intr_id_hp = MXC_PSEUDO_IRQ_HEADPHONE, +}; + +static struct platform_device mxc_alsa_device = { + .name = "imx-3stack-ak4647", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_ak4647_data, + }, + +}; + +static void mxc_init_ak4647(void) +{ + platform_device_register(&mxc_alsa_device); +} +#else +static void mxc_init_ak4647(void) +{ +} +#endif + +#if defined(CONFIG_CAN_FLEXCAN) || defined(CONFIG_CAN_FLEXCAN_MODULE) +static void flexcan_xcvr_enable(int id, int en) +{ + static int pwdn; + + if (id < 0 || id > 1) + return; + + if (en) { + if (!(pwdn++)) + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_2, + 1, 0); + } else { + if (!(--pwdn)) + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_2, + 1, 1); + } +} + +struct flexcan_platform_data flexcan_data[] = { + { + .core_reg = "GPO2", + .io_reg = NULL, + .xcvr_enable = flexcan_xcvr_enable, + .active = gpio_can_active, + .inactive = gpio_can_inactive,}, + { + .core_reg = "GPO2", + .io_reg = NULL, + .xcvr_enable = flexcan_xcvr_enable, + .active = gpio_can_active, + .inactive = gpio_can_inactive,}, +}; +#endif + +/*! + * fixup for mx35 3stack board v1.0 (MAX8660) + */ +static void mx35_3stack_fixup_for_board_v1(void) +{ +#if defined(CONFIG_MXC_MLB) || defined(CONFIG_MXC_MLB_MODULE) + mlb_data.reg_nvcc = "LDO6"; +#endif + +#if defined(CONFIG_SDIO_UNIFI_FS) || defined(CONFIG_SDIO_UNIFI_FS_MODULE) + unifi_data.reg_gpo1 = NULL; + unifi_data.reg_gpo2 = NULL; + unifi_data.reg_1v5_ana_bb = "SW4"; + unifi_data.reg_vdd_vpa = "SW1"; + unifi_data.reg_1v5_dd = "SW4"; +#endif + camera_data.analog_regulator = "LDO7"; + camera_data.core_regulator = NULL; + camera_data.io_regulator = NULL; + camera_data.gpo_regulator = NULL; + camera_data.mclk = 20000000; + + adv7180_data.dvddio_reg = NULL; + adv7180_data.dvdd_reg = NULL; + adv7180_data.avdd_reg = NULL; + adv7180_data.pvdd_reg = NULL; + + si4702_data.reg_vio = "SW1"; + si4702_data.reg_vdd = NULL; + +#if defined(CONFIG_GPS_IOCTRL) || defined(CONFIG_GPS_IOCTRL_MODULE) + gps_data.core_reg = "SW1"; + gps_data.analog_reg = "SW2"; +#endif + + mxc_bt_data.bt_vdd = "SW1"; + +#if defined(CONFIG_CAN_FLEXCAN) || defined(CONFIG_CAN_FLEXCAN_MODULE) + flexcan_data[0].core_reg = "SW1"; + flexcan_data[1].core_reg = "SW1"; +#endif +} + +/*! + * Board specific initialization. + */ +static void __init mxc_board_init(void) +{ + mxc_cpu_common_init(); + + early_console_setup(saved_command_line); + mxc_register_gpios(); + mxc_init_devices(); + if (!board_is_rev(BOARD_REV_2)) + mx35_3stack_fixup_for_board_v1(); + mx35_3stack_gpio_init(); + mxc_init_enet(); + mxc_init_nor_mtd(); + mxc_init_nand_mtd(); + + mx35_3stack_init_mc13892(); + mx35_3stack_init_mc9s08dz60(); + mxc_init_lcd(); + mxc_init_fb(); + mxc_init_bl(); + mxc_init_sgtl5000(); + mxc_init_ak4647(); + + i2c_register_board_info(0, mxc_i2c_board_info, + ARRAY_SIZE(mxc_i2c_board_info)); + + spi_register_board_info(mxc_spi_board_info, + ARRAY_SIZE(mxc_spi_board_info)); + mxc_init_mmc(); + mxc_init_pata(); + mxc_init_bluetooth(); + mxc_init_gps(); + mxc_init_mlb(); + mxc_init_fec(); +} + +#define PLL_PCTL_REG(brmo, pd, mfd, mfi, mfn) \ + (((brmo) << 31) + (((pd) - 1) << 26) + (((mfd) - 1) << 16) + \ + ((mfi) << 10) + mfn) + +/* For 24MHz input clock */ +#define PLL_665MHZ PLL_PCTL_REG(1, 1, 48, 13, 41) +#define PLL_532MHZ PLL_PCTL_REG(1, 1, 12, 11, 1) +#define PLL_399MHZ PLL_PCTL_REG(0, 1, 16, 8, 5) + +/* working point(wp): 0,1 - 133MHz; 2,3 - 266MHz; 4,5 - 399MHz;*/ +/* auto input clock table */ +static struct cpu_wp cpu_wp_auto[] = { + { + .pll_reg = PLL_399MHZ, + .pll_rate = 399000000, + .cpu_rate = 133000000, + .pdr0_reg = (0x2 << MXC_CCM_PDR0_AUTO_MUX_DIV_OFFSET),}, + { + .pll_reg = PLL_399MHZ, + .pll_rate = 399000000, + .cpu_rate = 133000000, + .pdr0_reg = (0x6 << MXC_CCM_PDR0_AUTO_MUX_DIV_OFFSET),}, + { + .pll_reg = PLL_399MHZ, + .pll_rate = 399000000, + .cpu_rate = 266000000, + .pdr0_reg = (0x1 << MXC_CCM_PDR0_AUTO_MUX_DIV_OFFSET),}, + { + .pll_reg = PLL_399MHZ, + .pll_rate = 399000000, + .cpu_rate = 266000000, + .pdr0_reg = (0x5 << MXC_CCM_PDR0_AUTO_MUX_DIV_OFFSET),}, + { + .pll_reg = PLL_399MHZ, + .pll_rate = 399000000, + .cpu_rate = 399000000, + .pdr0_reg = (0x0 << MXC_CCM_PDR0_AUTO_MUX_DIV_OFFSET),}, + { + .pll_reg = PLL_399MHZ, + .pll_rate = 399000000, + .cpu_rate = 399000000, + .pdr0_reg = (0x6 << MXC_CCM_PDR0_AUTO_MUX_DIV_OFFSET),}, +}; + +/* consumer input clock table */ +static struct cpu_wp cpu_wp_con[] = { + { + .pll_reg = PLL_532MHZ, + .pll_rate = 532000000, + .cpu_rate = 133000000, + .pdr0_reg = (0x6 << MXC_CCM_PDR0_CON_MUX_DIV_OFFSET),}, + { + .pll_reg = PLL_532MHZ, + .pll_rate = 532000000, + .cpu_rate = 133000000, + .pdr0_reg = (0xE << MXC_CCM_PDR0_CON_MUX_DIV_OFFSET),}, + { + .pll_reg = PLL_532MHZ, + .pll_rate = 532000000, + .cpu_rate = 266000000, + .pdr0_reg = (0x2 << MXC_CCM_PDR0_CON_MUX_DIV_OFFSET),}, + { + .pll_reg = PLL_532MHZ, + .pll_rate = 532000000, + .cpu_rate = 266000000, + .pdr0_reg = (0xA << MXC_CCM_PDR0_CON_MUX_DIV_OFFSET),}, + { + .pll_reg = PLL_532MHZ, + .pll_rate = 532000000, + .cpu_rate = 399000000, + .pdr0_reg = (0x1 << MXC_CCM_PDR0_CON_MUX_DIV_OFFSET),}, + { + .pll_reg = PLL_532MHZ, + .pll_rate = 532000000, + .cpu_rate = 399000000, + .pdr0_reg = (0x9 << MXC_CCM_PDR0_CON_MUX_DIV_OFFSET),}, + { + .pll_reg = PLL_532MHZ, + .pll_rate = 532000000, + .cpu_rate = 532000000, + .pdr0_reg = (0x0 << MXC_CCM_PDR0_CON_MUX_DIV_OFFSET),}, + { + .pll_reg = PLL_532MHZ, + .pll_rate = 532000000, + .cpu_rate = 532000000, + .pdr0_reg = (0x8 << MXC_CCM_PDR0_CON_MUX_DIV_OFFSET),}, + { + .pll_reg = PLL_665MHZ, + .pll_rate = 665000000, + .cpu_rate = 665000000, + .pdr0_reg = (0x7 << MXC_CCM_PDR0_CON_MUX_DIV_OFFSET),}, +}; + +struct cpu_wp *get_cpu_wp(int *wp) +{ + if (cpu_is_mx35_rev(CHIP_REV_2_0) >= 1) { + *wp = 9; + return cpu_wp_con; + } else { + if (__raw_readl(MXC_CCM_PDR0) & MXC_CCM_PDR0_AUTO_CON) { + *wp = 9; + return cpu_wp_con; + } else { + *wp = 6; + return cpu_wp_auto; + } + } +} + +static void __init mx35_3stack_timer_init(void) +{ + mx35_clocks_init(); +} + +static struct sys_timer mxc_timer = { + .init = mx35_3stack_timer_init, +}; + +/* + * The following uses standard kernel macros define in arch.h in order to + * initialize __mach_desc_MX35_3DS data structure. + */ +/* *INDENT-OFF* */ +MACHINE_START(MX35_3DS, "Freescale MX35 3-Stack Board") + /* Maintainer: Freescale Semiconductor, Inc. */ + .phys_io = AIPS1_BASE_ADDR, + .io_pg_offst = ((AIPS1_BASE_ADDR_VIRT) >> 18) & 0xfffc, + .boot_params = PHYS_OFFSET + 0x100, + .fixup = fixup_mxc_board, + .map_io = mx35_map_io, + .init_irq = mxc_init_irq, + .init_machine = mxc_board_init, + .timer = &mxc_timer, +MACHINE_END diff --git a/arch/arm/mach-mx35/mx35_3stack_cpld.c b/arch/arm/mach-mx35/mx35_3stack_cpld.c new file mode 100644 index 000000000000..efa4b2b42019 --- /dev/null +++ b/arch/arm/mach-mx35/mx35_3stack_cpld.c @@ -0,0 +1,161 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/clk.h> + +#include <asm/irq.h> +#include <asm/setup.h> +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <mach/hardware.h> +#include <mach/gpio.h> + +#include "board-mx35_3stack.h" +#include "crm_regs.h" +#include "iomux.h" + +/*! + * @file mach-mx35/mx35_3stack_cpld.c + * + * @brief This file contains the board specific initialization routines. + * + * @ingroup MSL_MX35 + */ +static void mxc_expio_irq_handler(u32 irq, struct irq_desc *desc) +{ + u32 expio_irq; + u32 index, mask; + desc->chip->mask(irq); /* irq = gpio irq number */ + + index = __raw_readw(mx35_3stack_board_io + INTR_STATUS_REG); + mask = __raw_readw(mx35_3stack_board_io + INTR_MASK_REG); + + if (unlikely(!(index & (~mask)))) { + printk(KERN_ERR "\nEXPIO: Spurious interrupt:0x%0x\n\n", index); + pr_info("CPLD IMR(0x38)=0x%x, PENDING(0x28)=0x%x\n", mask, + index); + goto out; + } + index = index & (~mask); + expio_irq = MXC_BOARD_IRQ_START; + for (; index != 0; index >>= 1, expio_irq++) { + struct irq_desc *d; + if ((index & 1) == 0) + continue; + d = irq_desc + expio_irq; + if (unlikely(!(d->handle_irq))) { + printk(KERN_ERR "\nEXPIO irq: %d unhandeled\n", + expio_irq); + BUG(); /* oops */ + } + d->handle_irq(expio_irq, d); + } + + out: + desc->chip->ack(irq); + desc->chip->unmask(irq); +} + +/* + * Disable an expio pin's interrupt by setting the bit in the imr. + * @param irq an expio virtual irq number + */ +static void expio_mask_irq(u32 irq) +{ + u16 reg, expio = MXC_IRQ_TO_EXPIO(irq); + + reg = __raw_readw(mx35_3stack_board_io + INTR_MASK_REG); + /* mask the interrupt */ + __raw_writew(reg | (1 << expio), mx35_3stack_board_io + INTR_MASK_REG); +} + +/* + * Acknowledge an expanded io pin's interrupt by clearing the bit in the isr. + * @param irq an expanded io virtual irq number + */ +static void expio_ack_irq(u32 irq) +{ + u32 expio = MXC_IRQ_TO_EXPIO(irq); + /* clear the interrupt status */ + __raw_writew(1 << expio, mx35_3stack_board_io + INTR_RESET_REG); + __raw_writew(0, mx35_3stack_board_io + INTR_RESET_REG); + /* mask the interrupt */ + expio_mask_irq(irq); +} + +/* + * Enable a expio pin's interrupt by clearing the bit in the imr. + * @param irq a expio virtual irq number + */ +static void expio_unmask_irq(u32 irq) +{ + u16 reg, expio = MXC_IRQ_TO_EXPIO(irq); + + reg = __raw_readw(mx35_3stack_board_io + INTR_MASK_REG); + /* unmask the interrupt */ + __raw_writew(reg & (~(1 << expio)), + mx35_3stack_board_io + INTR_MASK_REG); +} + +static struct irq_chip expio_irq_chip = { + .ack = expio_ack_irq, + .mask = expio_mask_irq, + .unmask = expio_unmask_irq, +}; + +static int __init mxc_expio_init(void) +{ + int i; + + mx35_3stack_board_io = (u32) ioremap(BOARD_IO_ADDR, SZ_4K); + if (mx35_3stack_board_io == 0) + return -ENOMEM; + + if ((__raw_readw(mx35_3stack_board_io + MAGIC_NUMBER1_REG) != 0xAAAA) || + (__raw_readw(mx35_3stack_board_io + MAGIC_NUMBER2_REG) != 0x5555)) + return -ENODEV; + + pr_info("3-Stack Debug board detected, rev = 0x%04X\n", + readw(mx35_3stack_board_io + CPLD_CODE_VER_REG)); + + /* + * Configure INT line as GPIO input + */ + mxc_request_iomux(EXPIO_PARENT_INT, MUX_CONFIG_FUNC); + gpio_request(IOMUX_TO_GPIO(EXPIO_PARENT_INT), NULL); + gpio_direction_input(IOMUX_TO_GPIO(EXPIO_PARENT_INT)); + + /* disable the interrupt and clear the status */ + __raw_writew(0, mx35_3stack_board_io + INTR_MASK_REG); + __raw_writew(0xFFFF, mx35_3stack_board_io + INTR_RESET_REG); + __raw_writew(0, mx35_3stack_board_io + INTR_RESET_REG); + __raw_writew(0x1F, mx35_3stack_board_io + INTR_MASK_REG); + for (i = MXC_BOARD_IRQ_START; i < (MXC_BOARD_IRQ_START + MXC_BOARD_IRQS); + i++) { + set_irq_chip(i, &expio_irq_chip); + set_irq_handler(i, handle_level_irq); + set_irq_flags(i, IRQF_VALID); + } + set_irq_type(IOMUX_TO_IRQ(EXPIO_PARENT_INT), IRQF_TRIGGER_LOW); + set_irq_chained_handler(IOMUX_TO_IRQ(EXPIO_PARENT_INT), + mxc_expio_irq_handler); + return 0; +} + +arch_initcall(mxc_expio_init); diff --git a/arch/arm/mach-mx35/mx35_3stack_gpio.c b/arch/arm/mach-mx35/mx35_3stack_gpio.c new file mode 100644 index 000000000000..43474317add2 --- /dev/null +++ b/arch/arm/mach-mx35/mx35_3stack_gpio.c @@ -0,0 +1,1378 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <mach/hardware.h> +#include <mach/gpio.h> +#include <linux/mfd/mc9s08dz60/pmic.h> +#include "board-mx35_3stack.h" +#include "iomux.h" + +/*! + * @file mach-mx35/mx35_3stack_gpio.c + * + * @brief This file contains all the GPIO setup functions for the board. + * + * @ingroup GPIO_MX35 + */ + +/*! + * This system-wise GPIO function initializes the pins during system startup. + * All the statically linked device drivers should put the proper GPIO + * initialization code inside this function. It is called by \b fixup_mx31ads() + * during system startup. This function is board specific. + */ +void mx35_3stack_gpio_init(void) +{ + /* config CS5 */ + mxc_request_iomux(MX35_PIN_CS5, MUX_CONFIG_FUNC); + + /* configure capture pin for ckil input */ + mxc_request_iomux(MX35_PIN_CAPTURE, MUX_CONFIG_ALT4); + mxc_iomux_set_pad(MX35_PIN_CAPTURE, + PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | + PAD_CTL_100K_PU | PAD_CTL_PUE_PUD); + mxc_iomux_set_input(MUX_IN_CCM_32K_MUXED, INPUT_CTL_PATH0); + +} + +/*! + * Setup GPIO for a UART port to be active + * + * @param port a UART port + * @param no_irda indicates if the port is used for SIR + */ +void gpio_uart_active(int port, int no_irda) +{ + /* + * Configure the IOMUX control registers for the UART signals + */ + switch (port) { + /* UART 1 IOMUX Configs */ + case 0: + mxc_request_iomux(MX35_PIN_RXD1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_TXD1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_RTS1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CTS1, MUX_CONFIG_FUNC); + + mxc_iomux_set_pad(MX35_PIN_RXD1, + PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PU); + mxc_iomux_set_pad(MX35_PIN_TXD1, + PAD_CTL_PUE_PUD | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_RTS1, + PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PU); + mxc_iomux_set_pad(MX35_PIN_CTS1, + PAD_CTL_PUE_PUD | PAD_CTL_100K_PD); + + break; + /* UART 2 IOMUX Configs */ + case 1: + mxc_request_iomux(MX35_PIN_TXD2, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_RXD2, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_RTS2, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CTS2, MUX_CONFIG_FUNC); + mxc_iomux_set_pad(MX35_PIN_RXD2, + PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PU); + mxc_iomux_set_pad(MX35_PIN_TXD2, + PAD_CTL_PUE_PUD | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_RTS2, + PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PU); + mxc_iomux_set_pad(MX35_PIN_CTS2, + PAD_CTL_PUE_PUD | PAD_CTL_100K_PD); + break; + /* UART 3 IOMUX Configs */ + case 2: + mxc_request_iomux(MX35_PIN_FEC_TX_CLK, MUX_CONFIG_ALT2); + mxc_request_iomux(MX35_PIN_FEC_RX_CLK, MUX_CONFIG_ALT2); + mxc_request_iomux(MX35_PIN_FEC_COL, MUX_CONFIG_ALT2); + mxc_request_iomux(MX35_PIN_FEC_RX_DV, MUX_CONFIG_ALT2); + + mxc_iomux_set_pad(MX35_PIN_FEC_TX_CLK, + PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PU); + mxc_iomux_set_pad(MX35_PIN_FEC_RX_CLK, + PAD_CTL_PUE_PUD | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_RX_DV, + PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PU); + mxc_iomux_set_pad(MX35_PIN_FEC_COL, + PAD_CTL_PUE_PUD | PAD_CTL_100K_PD); + + mxc_iomux_set_input(MUX_IN_UART3_UART_RTS_B, INPUT_CTL_PATH2); + mxc_iomux_set_input(MUX_IN_UART3_UART_RXD_MUX, INPUT_CTL_PATH3); + break; + default: + break; + } + +} + +EXPORT_SYMBOL(gpio_uart_active); + +/*! + * Setup GPIO for a UART port to be inactive + * + * @param port a UART port + * @param no_irda indicates if the port is used for SIR + */ +void gpio_uart_inactive(int port, int no_irda) +{ + switch (port) { + case 0: + gpio_request(IOMUX_TO_GPIO(MX35_PIN_RXD1), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_TXD1), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_RTS1), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_CTS1), NULL); + + mxc_free_iomux(MX35_PIN_RXD1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_TXD1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_RTS1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_CTS1, MUX_CONFIG_GPIO); + break; + case 1: + gpio_request(IOMUX_TO_GPIO(MX35_PIN_RXD2), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_TXD2), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_RTS2), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_CTS2), NULL); + + mxc_free_iomux(MX35_PIN_RXD2, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_TXD2, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_RTS2, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_CTS2, MUX_CONFIG_GPIO); + break; + case 2: + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_TX_CLK), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_RX_CLK), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_COL), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_RX_DV), NULL); + + mxc_free_iomux(MX35_PIN_FEC_TX_CLK, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_RX_CLK, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_COL, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_RX_DV, MUX_CONFIG_GPIO); + + mxc_iomux_set_input(MUX_IN_UART3_UART_RTS_B, INPUT_CTL_PATH0); + mxc_iomux_set_input(MUX_IN_UART3_UART_RXD_MUX, INPUT_CTL_PATH0); + break; + default: + break; + } +} + +EXPORT_SYMBOL(gpio_uart_inactive); + +/*! + * Configure the IOMUX GPR register to receive shared SDMA UART events + * + * @param port a UART port + */ +void config_uartdma_event(int port) +{ +} + +EXPORT_SYMBOL(config_uartdma_event); + +void gpio_fec_active(void) +{ + mxc_request_iomux(MX35_PIN_FEC_TX_CLK, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_RX_CLK, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_RX_DV, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_COL, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_RDATA0, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_TDATA0, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_TX_EN, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_MDC, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_MDIO, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_TX_ERR, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_RX_ERR, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_CRS, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_RDATA1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_TDATA1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_RDATA2, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_TDATA2, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_RDATA3, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FEC_TDATA3, MUX_CONFIG_FUNC); + +#define FEC_PAD_CTL_COMMON (PAD_CTL_DRV_3_3V|PAD_CTL_PUE_PUD| \ + PAD_CTL_ODE_CMOS|PAD_CTL_DRV_NORMAL|PAD_CTL_SRE_SLOW) + mxc_iomux_set_pad(MX35_PIN_FEC_TX_CLK, FEC_PAD_CTL_COMMON | + PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | + PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_RX_CLK, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_RX_DV, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_COL, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_RDATA0, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_TDATA0, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_CMOS | + PAD_CTL_PKE_NONE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_TX_EN, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_CMOS | + PAD_CTL_PKE_NONE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_MDC, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_CMOS | + PAD_CTL_PKE_NONE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_MDIO, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_22K_PU); + mxc_iomux_set_pad(MX35_PIN_FEC_TX_ERR, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_CMOS | + PAD_CTL_PKE_NONE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_RX_ERR, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_CRS, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_RDATA1, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_TDATA1, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_CMOS | + PAD_CTL_PKE_NONE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_RDATA2, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_TDATA2, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_CMOS | + PAD_CTL_PKE_NONE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_RDATA3, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PD); + mxc_iomux_set_pad(MX35_PIN_FEC_TDATA3, + FEC_PAD_CTL_COMMON | PAD_CTL_HYS_CMOS | + PAD_CTL_PKE_NONE | PAD_CTL_100K_PD); +#undef FEC_PAD_CTL_COMMON + /* Pull GPIO1_5 to be high for routing signal to FEC */ + if (board_is_rev(BOARD_REV_2)) { + mxc_request_iomux(MX35_PIN_COMPARE, MUX_CONFIG_GPIO); + mxc_iomux_set_pad(MX35_PIN_COMPARE, PAD_CTL_DRV_NORMAL | + PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PU | + PAD_CTL_DRV_3_3V | PAD_CTL_PUE_PUD | + PAD_CTL_SRE_SLOW); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_COMPARE), "compare"); + gpio_direction_output(IOMUX_TO_GPIO(MX35_PIN_COMPARE), 0); + gpio_set_value(IOMUX_TO_GPIO(MX35_PIN_COMPARE), 1); + } + + /* FEC enable */ + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 2, 1); + /* FEC reset */ + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_1, 7, 0); + msleep(10); + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_1, 7, 1); + msleep(100); +} + +EXPORT_SYMBOL(gpio_fec_active); + +void gpio_fec_inactive(void) +{ + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_TX_CLK), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_RX_CLK), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_RX_DV), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_COL), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_RDATA0), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_TDATA0), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_TX_EN), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_MDC), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_MDIO), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_TX_ERR), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_RX_ERR), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_CRS), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_RDATA1), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_TDATA1), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_RDATA2), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_TDATA2), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_RDATA3), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_FEC_TDATA3), NULL); + + mxc_free_iomux(MX35_PIN_FEC_TX_CLK, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_RX_CLK, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_RX_DV, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_COL, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_RDATA0, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_TDATA0, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_TX_EN, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_MDC, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_MDIO, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_TX_ERR, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_RX_ERR, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_CRS, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_RDATA1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_TDATA1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_RDATA2, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_TDATA2, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_RDATA3, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FEC_TDATA3, MUX_CONFIG_GPIO); + + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 2, 0); + + /* Free GPIO1_5 */ + if (board_is_rev(BOARD_REV_2)) { + gpio_free(IOMUX_TO_GPIO(MX35_PIN_COMPARE)); + mxc_free_iomux(MX35_PIN_COMPARE, MUX_CONFIG_GPIO); + } +} + +EXPORT_SYMBOL(gpio_fec_inactive); + +/*! + * Setup GPIO for an I2C device to be active + * + * @param i2c_num an I2C device + */ +void gpio_i2c_active(int i2c_num) +{ + +#define PAD_CONFIG (PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PUD | PAD_CTL_ODE_OpenDrain) + + switch (i2c_num) { + case 0: + mxc_request_iomux(MX35_PIN_I2C1_CLK, MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_I2C1_DAT, MUX_CONFIG_SION); + + mxc_iomux_set_pad(MX35_PIN_I2C1_CLK, PAD_CONFIG); + mxc_iomux_set_pad(MX35_PIN_I2C1_DAT, PAD_CONFIG); + break; + case 1: + mxc_request_iomux(MX35_PIN_I2C2_CLK, MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_I2C2_DAT, MUX_CONFIG_SION); + + mxc_iomux_set_pad(MX35_PIN_I2C2_CLK, PAD_CONFIG); + mxc_iomux_set_pad(MX35_PIN_I2C2_DAT, PAD_CONFIG); + + break; + case 2: + mxc_request_iomux(MX35_PIN_TX3_RX2, MUX_CONFIG_ALT1); + mxc_request_iomux(MX35_PIN_TX2_RX3, MUX_CONFIG_ALT1); + mxc_iomux_set_pad(MX35_PIN_TX3_RX2, PAD_CONFIG); + mxc_iomux_set_pad(MX35_PIN_TX2_RX3, PAD_CONFIG); + break; + default: + break; + } + +#undef PAD_CONFIG + +} + +EXPORT_SYMBOL(gpio_i2c_active); + +/*! + * Setup GPIO for an I2C device to be inactive + * + * @param i2c_num an I2C device + */ +void gpio_i2c_inactive(int i2c_num) +{ + switch (i2c_num) { + case 0: + break; + case 1: + break; + case 2: + mxc_request_iomux(MX35_PIN_TX3_RX2, MUX_CONFIG_GPIO); + mxc_request_iomux(MX35_PIN_TX2_RX3, MUX_CONFIG_GPIO); + break; + default: + break; + } +} + +EXPORT_SYMBOL(gpio_i2c_inactive); + +/*! + * Setup GPIO for a CSPI device to be active + * + * @param cspi_mod an CSPI device + */ +void gpio_spi_active(int cspi_mod) +{ + switch (cspi_mod) { + case 0: + /* SPI1 */ + mxc_request_iomux(MX35_PIN_CSPI1_MOSI, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSPI1_MISO, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSPI1_SS0, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSPI1_SS1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSPI1_SCLK, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSPI1_SPI_RDY, MUX_CONFIG_FUNC); + + mxc_iomux_set_pad(MX35_PIN_CSPI1_MOSI, + PAD_CTL_DRV_3_3V | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PUD | + PAD_CTL_100K_PD | PAD_CTL_DRV_NORMAL); + mxc_iomux_set_pad(MX35_PIN_CSPI1_MISO, + PAD_CTL_DRV_3_3V | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PUD | + PAD_CTL_100K_PD | PAD_CTL_DRV_NORMAL); + mxc_iomux_set_pad(MX35_PIN_CSPI1_SS0, + PAD_CTL_DRV_3_3V | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PUD | + PAD_CTL_100K_PU | PAD_CTL_ODE_CMOS | + PAD_CTL_DRV_NORMAL); + mxc_iomux_set_pad(MX35_PIN_CSPI1_SS1, + PAD_CTL_DRV_3_3V | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PUD | + PAD_CTL_100K_PU | PAD_CTL_ODE_CMOS | + PAD_CTL_DRV_NORMAL); + mxc_iomux_set_pad(MX35_PIN_CSPI1_SCLK, + PAD_CTL_DRV_3_3V | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PUD | + PAD_CTL_100K_PD | PAD_CTL_DRV_NORMAL); + mxc_iomux_set_pad(MX35_PIN_CSPI1_SPI_RDY, + PAD_CTL_DRV_3_3V | PAD_CTL_HYS_SCHMITZ | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PUD | + PAD_CTL_100K_PU | PAD_CTL_DRV_NORMAL); + break; + case 1: + /* SPI2 */ + break; + default: + break; + } +} + +EXPORT_SYMBOL(gpio_spi_active); + +/*! + * Setup GPIO for a CSPI device to be inactive + * + * @param cspi_mod a CSPI device + */ +void gpio_spi_inactive(int cspi_mod) +{ + switch (cspi_mod) { + case 0: + /* SPI1 */ + gpio_request(IOMUX_TO_GPIO(MX35_PIN_CSPI1_MOSI), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_CSPI1_MISO), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_CSPI1_SS0), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_CSPI1_SS1), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_CSPI1_SCLK), NULL); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_CSPI1_SPI_RDY), NULL); + + mxc_free_iomux(MX35_PIN_CSPI1_MOSI, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_CSPI1_MISO, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_CSPI1_SS0, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_CSPI1_SS1, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_CSPI1_SCLK, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_CSPI1_SPI_RDY, MUX_CONFIG_GPIO); + break; + case 1: + /* SPI2 */ + break; + default: + break; + } +} + +EXPORT_SYMBOL(gpio_spi_inactive); + +/*! + * Setup GPIO for LCD to be active + */ +void gpio_lcd_active(void) +{ + mxc_request_iomux(MX35_PIN_LD0, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD2, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD3, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD4, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD5, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD6, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD7, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD8, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD9, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD10, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD11, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD12, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD13, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD14, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD15, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD16, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_LD17, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_D3_VSYNC, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_D3_HSYNC, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_D3_FPSHIFT, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_D3_DRDY, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CONTRAST, MUX_CONFIG_FUNC); +} + +EXPORT_SYMBOL(gpio_lcd_active); + +/*! + * Setup GPIO for LCD to be inactive + */ +void gpio_lcd_inactive(void) +{ +} + +EXPORT_SYMBOL(gpio_lcd_inactive); + +/*! + * Setup pin for touchscreen + */ +void gpio_tsc_active(void) +{ + unsigned int pad_val = PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PU; + mxc_request_iomux(MX35_PIN_CAPTURE, MUX_CONFIG_GPIO); + mxc_iomux_set_pad(MX35_PIN_CAPTURE, pad_val); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_CAPTURE), "capture"); + gpio_direction_input(IOMUX_TO_GPIO(MX35_PIN_CAPTURE)); +} + +/*! + * Release pin for touchscreen + */ +void gpio_tsc_inactive(void) +{ + gpio_free(IOMUX_TO_GPIO(MX35_PIN_CAPTURE)); + mxc_free_iomux(MX35_PIN_CAPTURE, MUX_CONFIG_GPIO); +} + +/*! + * Setup GPIO for SDHC to be active + * + * @param module SDHC module number + */ +void gpio_sdhc_active(int module) +{ + unsigned int pad_val; + + switch (module) { + case 0: + mxc_request_iomux(MX35_PIN_SD1_CLK, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_SD1_CMD, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_SD1_DATA0, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_SD1_DATA1, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_SD1_DATA2, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_SD1_DATA3, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); +#if defined(CONFIG_SDIO_UNIFI_FS) || defined(CONFIG_SDIO_UNIFI_FS_MODULE) +#else + /* MUX4_CTR , 0: SD2 to WIFI, 1:SD2 to SD1 8bit */ + if (board_is_rev(BOARD_REV_2)) + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_2, + 7, 1); + mxc_request_iomux(MX35_PIN_SD2_CMD, + MUX_CONFIG_ALT2 | MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_SD2_CLK, + MUX_CONFIG_ALT2 | MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_SD2_DATA0, + MUX_CONFIG_ALT2 | MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_SD2_DATA1, + MUX_CONFIG_ALT2 | MUX_CONFIG_SION); +#endif + + pad_val = PAD_CTL_PUE_PUD | PAD_CTL_PKE_ENABLE | + PAD_CTL_HYS_SCHMITZ | PAD_CTL_DRV_MAX | + PAD_CTL_47K_PU | PAD_CTL_SRE_FAST; + mxc_iomux_set_pad(MX35_PIN_SD1_CMD, pad_val); + mxc_iomux_set_pad(MX35_PIN_SD1_DATA0, pad_val); + mxc_iomux_set_pad(MX35_PIN_SD1_DATA1, pad_val); + mxc_iomux_set_pad(MX35_PIN_SD1_DATA2, pad_val); + pad_val = PAD_CTL_PUE_PUD | PAD_CTL_PKE_ENABLE | + PAD_CTL_DRV_MAX | PAD_CTL_47K_PU | PAD_CTL_SRE_FAST; + mxc_iomux_set_pad(MX35_PIN_SD1_CLK, pad_val); + pad_val = PAD_CTL_PUE_PUD | PAD_CTL_PKE_ENABLE | + PAD_CTL_HYS_SCHMITZ | PAD_CTL_DRV_MAX | + PAD_CTL_100K_PU | PAD_CTL_SRE_FAST; + mxc_iomux_set_pad(MX35_PIN_SD1_DATA3, pad_val); +#if defined(CONFIG_SDIO_UNIFI_FS) || defined(CONFIG_SDIO_UNIFI_FS_MODULE) +#else + pad_val = PAD_CTL_PUE_PUD | PAD_CTL_PKE_ENABLE | + PAD_CTL_HYS_SCHMITZ | PAD_CTL_DRV_MAX | + PAD_CTL_47K_PU | PAD_CTL_SRE_FAST; + mxc_iomux_set_pad(MX35_PIN_SD2_CMD, pad_val); + mxc_iomux_set_pad(MX35_PIN_SD2_DATA0, pad_val); + mxc_iomux_set_pad(MX35_PIN_SD2_DATA1, pad_val); + pad_val = PAD_CTL_PUE_PUD | PAD_CTL_PKE_ENABLE | + PAD_CTL_DRV_MAX | PAD_CTL_47K_PU | PAD_CTL_SRE_FAST; + mxc_iomux_set_pad(MX35_PIN_SD2_CLK, pad_val); +#endif + break; + case 1: + /* MUX4_CTR , 0: SD2 to WIFI, 1:SD2 to SD1 8bit */ + if (board_is_rev(BOARD_REV_2)) + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_2, + 7, 0); + mxc_request_iomux(MX35_PIN_SD2_CLK, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_SD2_CMD, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_SD2_DATA0, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_SD2_DATA1, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_SD2_DATA2, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_request_iomux(MX35_PIN_SD2_DATA3, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + + pad_val = PAD_CTL_PUE_PUD | PAD_CTL_PKE_ENABLE | + PAD_CTL_HYS_SCHMITZ | PAD_CTL_DRV_MAX | + PAD_CTL_47K_PU | PAD_CTL_SRE_FAST; + + mxc_iomux_set_pad(MX35_PIN_SD2_CLK, pad_val); + mxc_iomux_set_pad(MX35_PIN_SD2_CMD, pad_val); + mxc_iomux_set_pad(MX35_PIN_SD2_DATA0, pad_val); + mxc_iomux_set_pad(MX35_PIN_SD2_DATA1, pad_val); + mxc_iomux_set_pad(MX35_PIN_SD2_DATA2, pad_val); + + pad_val = PAD_CTL_PUE_PUD | PAD_CTL_PKE_ENABLE | + PAD_CTL_HYS_SCHMITZ | PAD_CTL_DRV_MAX | + PAD_CTL_100K_PU | PAD_CTL_SRE_FAST; + + mxc_iomux_set_pad(MX35_PIN_SD2_DATA3, pad_val); + break; + default: + break; + } +} + +EXPORT_SYMBOL(gpio_sdhc_active); + +/*! + * Setup GPIO for SDHC1 to be inactive + * + * @param module SDHC module number + */ +void gpio_sdhc_inactive(int module) +{ + switch (module) { + case 0: + mxc_free_iomux(MX35_PIN_SD1_CLK, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_free_iomux(MX35_PIN_SD1_CMD, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_free_iomux(MX35_PIN_SD1_DATA0, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_free_iomux(MX35_PIN_SD1_DATA1, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_free_iomux(MX35_PIN_SD1_DATA2, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_free_iomux(MX35_PIN_SD1_DATA3, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_free_iomux(MX35_PIN_SD2_CMD, + MUX_CONFIG_ALT2 | MUX_CONFIG_SION); + mxc_free_iomux(MX35_PIN_SD2_CLK, + MUX_CONFIG_ALT2 | MUX_CONFIG_SION); + mxc_free_iomux(MX35_PIN_SD2_DATA0, + MUX_CONFIG_ALT2 | MUX_CONFIG_SION); + mxc_free_iomux(MX35_PIN_SD2_DATA1, + MUX_CONFIG_ALT2 | MUX_CONFIG_SION); + + mxc_iomux_set_pad(MX35_PIN_SD1_CLK, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX35_PIN_SD1_CMD, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX35_PIN_SD1_DATA0, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX35_PIN_SD1_DATA1, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX35_PIN_SD1_DATA2, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX35_PIN_SD1_DATA3, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX35_PIN_SD2_CMD, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX35_PIN_SD2_CLK, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX35_PIN_SD2_DATA0, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX35_PIN_SD2_DATA1, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + break; + case 1: + mxc_free_iomux(MX35_PIN_SD2_CLK, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_free_iomux(MX35_PIN_SD2_CMD, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_free_iomux(MX35_PIN_SD2_DATA0, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_free_iomux(MX35_PIN_SD2_DATA1, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_free_iomux(MX35_PIN_SD2_DATA2, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + mxc_free_iomux(MX35_PIN_SD2_DATA3, + MUX_CONFIG_FUNC | MUX_CONFIG_SION); + + mxc_iomux_set_pad(MX35_PIN_SD2_CLK, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX35_PIN_SD2_CMD, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX35_PIN_SD2_DATA0, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX35_PIN_SD2_DATA1, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX35_PIN_SD2_DATA2, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX35_PIN_SD2_DATA3, + (PAD_CTL_DRV_NORMAL | PAD_CTL_SRE_SLOW)); + break; + default: + break; + } +} + +EXPORT_SYMBOL(gpio_sdhc_inactive); + +/* + * Probe for the card. If present the GPIO data would be set. + */ +unsigned int sdhc_get_card_det_status(struct device *dev) +{ + unsigned int ret; + + if (to_platform_device(dev)->id == 0) { + if (0 != pmic_gpio_get_designation_bit_val(2, &ret)) + printk(KERN_ERR "Get cd status error."); + return ret; + } else { /* config the det pin for SDHC2 */ + return 0; + } +} + +EXPORT_SYMBOL(sdhc_get_card_det_status); + +/*! + * Get pin value to detect write protection + */ +int sdhc_write_protect(struct device *dev) +{ + unsigned int rc = 0; + + if (0 != pmic_gpio_get_designation_bit_val(3, &rc)) + printk(KERN_ERR "Get wp status error."); + return rc; +} + +EXPORT_SYMBOL(sdhc_write_protect); + +/* + * USB Host2 + */ +int gpio_usbh2_active(void) +{ + if (board_is_rev(BOARD_REV_2)) { + /* MUX3_CTR to be low for USB Host2 DP&DM */ + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_2, 6, 0); + /* CAN_PWDN to be high for USB Host2 Power&OC */ + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_2, 1, 1); + } + + mxc_request_iomux(MX35_PIN_I2C2_CLK, MUX_CONFIG_ALT2); + mxc_iomux_set_pad(MX35_PIN_I2C2_CLK, 0x0040); + + mxc_request_iomux(MX35_PIN_I2C2_DAT, MUX_CONFIG_ALT2); + mxc_iomux_set_input(MUX_IN_USB_UH2_USB_OC, INPUT_CTL_PATH0); + mxc_iomux_set_pad(MX35_PIN_I2C2_DAT, 0x01c0); + + return 0; +} + +EXPORT_SYMBOL(gpio_usbh2_active); + +void gpio_usbh2_inactive(void) +{ + gpio_request(IOMUX_TO_GPIO(MX35_PIN_I2C2_DAT), NULL); + mxc_free_iomux(MX35_PIN_I2C2_DAT, MUX_CONFIG_GPIO); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_I2C2_CLK), NULL); + mxc_free_iomux(MX35_PIN_I2C2_CLK, MUX_CONFIG_GPIO); +} + +EXPORT_SYMBOL(gpio_usbh2_inactive); + +/* + * USB OTG UTMI + */ +int gpio_usbotg_utmi_active(void) +{ + mxc_request_iomux(MX35_PIN_USBOTG_PWR, MUX_CONFIG_FUNC); + mxc_iomux_set_pad(MX35_PIN_USBOTG_PWR, 0x0040); + mxc_request_iomux(MX35_PIN_USBOTG_OC, MUX_CONFIG_FUNC); + mxc_iomux_set_pad(MX35_PIN_USBOTG_OC, 0x01c0); + + return 0; +} + +EXPORT_SYMBOL(gpio_usbotg_utmi_active); + +void gpio_usbotg_utmi_inactive(void) +{ + gpio_request(IOMUX_TO_GPIO(MX35_PIN_USBOTG_PWR), NULL); + mxc_free_iomux(MX35_PIN_USBOTG_PWR, MUX_CONFIG_GPIO); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_USBOTG_OC), NULL); + mxc_free_iomux(MX35_PIN_USBOTG_OC, MUX_CONFIG_GPIO); +} + +EXPORT_SYMBOL(gpio_usbotg_utmi_inactive); + +void gpio_sensor_active(void) +{ + /*CSI D6 */ + mxc_request_iomux(MX35_PIN_TX1, MUX_CONFIG_ALT6); + /*CSI D7 */ + mxc_request_iomux(MX35_PIN_TX0, MUX_CONFIG_ALT6); + mxc_request_iomux(MX35_PIN_CSI_D8, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSI_D9, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSI_D10, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSI_D11, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSI_D12, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSI_D13, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSI_D14, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSI_D15, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSI_HSYNC, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSI_MCLK, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSI_PIXCLK, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_CSI_VSYNC, MUX_CONFIG_FUNC); +} + +EXPORT_SYMBOL(gpio_sensor_active); + +void gpio_sensor_inactive(void) +{ + mxc_request_iomux(MX35_PIN_TX1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_TX0, MUX_CONFIG_FUNC); +} + +EXPORT_SYMBOL(gpio_sensor_inactive); + +/*! + * Setup GPIO for spdif tx/rx to be active + */ +void gpio_spdif_active(void) +{ + /* SPDIF OUT */ + mxc_request_iomux(MX35_PIN_STXD5, MUX_CONFIG_ALT1); + mxc_iomux_set_pad(MX35_PIN_STXD5, PAD_CTL_PKE_NONE | PAD_CTL_PUE_PUD); + /* SPDIF IN */ + mxc_request_iomux(MX35_PIN_SRXD5, MUX_CONFIG_ALT1); + mxc_iomux_set_pad(MX35_PIN_SRXD5, PAD_CTL_PKE_ENABLE + | PAD_CTL_100K_PU | PAD_CTL_HYS_SCHMITZ); + /* SPDIF ext clock */ + mxc_request_iomux(MX35_PIN_SCK5, MUX_CONFIG_ALT1); + if (board_is_rev(BOARD_REV_2)) + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_2, 5, 1); + else + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_2, 0, 1); +} + +EXPORT_SYMBOL(gpio_spdif_active); + +/*! + * Setup GPIO for spdif tx/rx to be inactive + */ +void gpio_spdif_inactive(void) +{ + /* SPDIF OUT */ + mxc_free_iomux(MX35_PIN_STXD5, MUX_CONFIG_ALT1); + /* SPDIF IN */ + mxc_free_iomux(MX35_PIN_SRXD5, MUX_CONFIG_ALT1); + /* SPDIF ext clock */ + mxc_free_iomux(MX35_PIN_SCK5, MUX_CONFIG_ALT1); +} + +EXPORT_SYMBOL(gpio_spdif_inactive); + +/*! + * This function activates DAM ports 3 to enable + * audio I/O. + */ +void gpio_activate_audio_ports(void) +{ + unsigned int pad_val; + + mxc_request_iomux(MX35_PIN_STXD4, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_SRXD4, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_SCK4, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_STXFS4, MUX_CONFIG_FUNC); + + pad_val = PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PU | + PAD_CTL_PUE_PUD; + mxc_iomux_set_pad(MX35_PIN_STXD4, pad_val); + mxc_iomux_set_pad(MX35_PIN_SRXD4, pad_val); + mxc_iomux_set_pad(MX35_PIN_SCK4, pad_val); + mxc_iomux_set_pad(MX35_PIN_STXFS4, pad_val); +} + +EXPORT_SYMBOL(gpio_activate_audio_ports); + +/*! + * This function deactivates DAM ports 3 to disable + * audio I/O. + */ +void gpio_inactivate_audio_ports(void) +{ + mxc_free_iomux(MX35_PIN_STXD4, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_SRXD4, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_SCK4, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_STXFS4, MUX_CONFIG_FUNC); +} + +EXPORT_SYMBOL(gpio_inactivate_audio_ports); + +/*! + * This function activates DAM ports 5 to enable + * audio I/O. + */ +void gpio_activate_bt_audio_port(void) +{ + unsigned int pad_val; + + mxc_request_iomux(MX35_PIN_STXD5, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_SRXD5, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_SCK5, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_STXFS5, MUX_CONFIG_FUNC); + + pad_val = PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PU | + PAD_CTL_PUE_PUD; + mxc_iomux_set_pad(MX35_PIN_STXD5, pad_val); + mxc_iomux_set_pad(MX35_PIN_SRXD5, pad_val); + mxc_iomux_set_pad(MX35_PIN_SCK5, pad_val); + mxc_iomux_set_pad(MX35_PIN_STXFS5, pad_val); + if (board_is_rev(BOARD_REV_2)) + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_2, 5, 0); + else + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_2, 0, 0); +} + +EXPORT_SYMBOL(gpio_activate_bt_audio_port); + +/*! + * Setup GPIO for bluetooth audio to be inactive + */ +void gpio_inactivate_bt_audio_port(void) +{ + mxc_free_iomux(MX35_PIN_STXD5, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_SRXD5, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_SCK5, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_STXFS5, MUX_CONFIG_FUNC); +} + +EXPORT_SYMBOL(gpio_inactivate_bt_audio_port); + +/*! + * Setup GPIO for ATA interface + * + */ +void gpio_ata_active(void) +{ + unsigned int ata_ctl_pad_cfg, ata_dat_pad_cfg; + + /* HDD_ENBALE */ + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 3, 0); + /* Power On the HDD */ + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 4, 1); + msleep(300); + + /*IOMUX Settings */ + /*PATA_DIOR */ + mxc_request_iomux(MX35_PIN_ATA_DIOR, MUX_CONFIG_FUNC); + /*PATA_DIOW */ + mxc_request_iomux(MX35_PIN_ATA_DIOW, MUX_CONFIG_FUNC); + /*PATA_DMARQ_B */ + mxc_request_iomux(MX35_PIN_ATA_DMARQ, MUX_CONFIG_FUNC); + /*PATA_DMACK */ + mxc_request_iomux(MX35_PIN_ATA_DMACK, MUX_CONFIG_FUNC); + /*PATA_RESET_B */ + mxc_request_iomux(MX35_PIN_ATA_RESET_B, MUX_CONFIG_FUNC); + /*PATA_IORDY */ + mxc_request_iomux(MX35_PIN_ATA_IORDY, MUX_CONFIG_FUNC); + /*PATA_INTRQ_B */ + mxc_request_iomux(MX35_PIN_ATA_INTRQ, MUX_CONFIG_FUNC); + /*PATA_CS_0 */ + mxc_request_iomux(MX35_PIN_ATA_CS0, MUX_CONFIG_FUNC); + /*PATA_CS_1 */ + mxc_request_iomux(MX35_PIN_ATA_CS1, MUX_CONFIG_FUNC); + /*PATA_DA0 */ + mxc_request_iomux(MX35_PIN_ATA_DA0, MUX_CONFIG_FUNC); + /*PATA_DA1 */ + mxc_request_iomux(MX35_PIN_ATA_DA1, MUX_CONFIG_FUNC); + /*PATA_DA2 */ + mxc_request_iomux(MX35_PIN_ATA_DA2, MUX_CONFIG_FUNC); + /* BUFFER_ENABLE - HDD_ENABLE_B */ + mxc_request_iomux(MX35_PIN_ATA_BUFF_EN, MUX_CONFIG_FUNC); + + /*PATA_D0 */ + mxc_request_iomux(MX35_PIN_ATA_DATA0, MUX_CONFIG_FUNC); + /*PATA_D1 */ + mxc_request_iomux(MX35_PIN_ATA_DATA1, MUX_CONFIG_FUNC); + /*PATA_D2 */ + mxc_request_iomux(MX35_PIN_ATA_DATA2, MUX_CONFIG_FUNC); + /*PATA_D3 */ + mxc_request_iomux(MX35_PIN_ATA_DATA3, MUX_CONFIG_FUNC); + /*PATA_D4 */ + mxc_request_iomux(MX35_PIN_ATA_DATA4, MUX_CONFIG_FUNC); + /*PATA_D5 */ + mxc_request_iomux(MX35_PIN_ATA_DATA5, MUX_CONFIG_FUNC); + /*PATA_D6 */ + mxc_request_iomux(MX35_PIN_ATA_DATA6, MUX_CONFIG_FUNC); + /*PATA_D7 */ + mxc_request_iomux(MX35_PIN_ATA_DATA7, MUX_CONFIG_FUNC); + /*PATA_D8 */ + mxc_request_iomux(MX35_PIN_ATA_DATA8, MUX_CONFIG_FUNC); + /*PATA_D9 */ + mxc_request_iomux(MX35_PIN_ATA_DATA9, MUX_CONFIG_FUNC); + /*PATA_D10 */ + mxc_request_iomux(MX35_PIN_ATA_DATA10, MUX_CONFIG_FUNC); + /*PATA_D11 */ + mxc_request_iomux(MX35_PIN_ATA_DATA11, MUX_CONFIG_FUNC); + /*PATA_D12 */ + mxc_request_iomux(MX35_PIN_ATA_DATA12, MUX_CONFIG_FUNC); + /*PATA_D13 */ + mxc_request_iomux(MX35_PIN_ATA_DATA13, MUX_CONFIG_FUNC); + /*PATA_D14 */ + mxc_request_iomux(MX35_PIN_ATA_DATA14, MUX_CONFIG_FUNC); + /*PATA_D15 */ + mxc_request_iomux(MX35_PIN_ATA_DATA15, MUX_CONFIG_FUNC); + + /* IOMUX Pad Settings */ + ata_ctl_pad_cfg = PAD_CTL_SRE_SLOW | PAD_CTL_DRV_NORMAL | + PAD_CTL_ODE_CMOS | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PD | + PAD_CTL_HYS_CMOS | PAD_CTL_DRV_3_3V; + ata_dat_pad_cfg = PAD_CTL_SRE_FAST | PAD_CTL_DRV_MAX | + PAD_CTL_ODE_CMOS | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PUD | PAD_CTL_100K_PD | + PAD_CTL_HYS_SCHMITZ | PAD_CTL_DRV_3_3V; + + mxc_iomux_set_pad(MX35_PIN_ATA_DMARQ, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DIOR, ata_ctl_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DIOW, ata_ctl_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DMACK, ata_ctl_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_RESET_B, PAD_CTL_SRE_SLOW | + PAD_CTL_DRV_NORMAL | PAD_CTL_ODE_CMOS | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PUD | + PAD_CTL_100K_PU | PAD_CTL_HYS_CMOS | + PAD_CTL_DRV_3_3V); + mxc_iomux_set_pad(MX35_PIN_ATA_IORDY, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_INTRQ, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_CS0, ata_ctl_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_CS1, ata_ctl_pad_cfg); + + mxc_iomux_set_pad(MX35_PIN_ATA_DATA0, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA1, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA2, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA3, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA4, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA5, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA6, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA7, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA8, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA9, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA10, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA11, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA12, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA13, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA14, ata_dat_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DATA15, ata_dat_pad_cfg); + + mxc_iomux_set_pad(MX35_PIN_ATA_DA0, ata_ctl_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DA1, ata_ctl_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_DA2, ata_ctl_pad_cfg); + mxc_iomux_set_pad(MX35_PIN_ATA_BUFF_EN, ata_ctl_pad_cfg); +} + +EXPORT_SYMBOL(gpio_ata_active); + +/*! + * Restore ATA interface pins to reset values + * + */ +void gpio_ata_inactive(void) +{ + /*Turn off the IOMUX for ATA group B signals */ + mxc_free_iomux(MX35_PIN_ATA_DATA0, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA1, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA2, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA3, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA4, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA5, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA6, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA7, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA8, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA9, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA10, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA11, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA12, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA13, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA14, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DATA15, MUX_CONFIG_FUNC); + + /* Config the multiplex pin of ATA interface DIR, DA0-2, INTRQ, DMARQ */ + mxc_free_iomux(MX35_PIN_ATA_DMARQ, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DIOR, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DIOW, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DMACK, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_RESET_B, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_IORDY, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_INTRQ, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_CS0, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_CS1, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DA0, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DA1, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_DA2, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_ATA_BUFF_EN, MUX_CONFIG_FUNC); + + /* Power Off the HDD */ + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 4, 0); + /* HDD_ENBALE */ + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 3, 1); +} + +EXPORT_SYMBOL(gpio_ata_inactive); + +/*! + * This function activates ESAI ports to enable + * surround sound I/O + */ +void gpio_activate_esai_ports(void) +{ + unsigned int pad_val; + /* ESAI TX - WM8580 */ + mxc_request_iomux(MX35_PIN_HCKT, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_SCKT, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FST, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_TX0, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_TX1, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_TX2_RX3, MUX_CONFIG_FUNC); + + /* ESAI RX - AK5702 */ + /*mxc_request_iomux(MX35_PIN_HCKR, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_SCKR, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_FSR, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_TX3_RX2, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_TX4_RX1, MUX_CONFIG_FUNC);*/ + + pad_val = PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PU | + PAD_CTL_PUE_PUD; + /* ESAI TX - WM8580 */ + mxc_iomux_set_pad(MX35_PIN_SCKT, pad_val); + mxc_iomux_set_pad(MX35_PIN_FST, pad_val); + mxc_iomux_set_pad(MX35_PIN_TX0, pad_val); + mxc_iomux_set_pad(MX35_PIN_TX1, pad_val); + mxc_iomux_set_pad(MX35_PIN_TX2_RX3, pad_val); + + /* ESAI RX - AK5702 */ + /*mxc_iomux_set_pad(MX35_PIN_SCKR, pad_val); + mxc_iomux_set_pad(MX35_PIN_FSR, pad_val); + mxc_iomux_set_pad(MX35_PIN_TX3_RX2, pad_val); + mxc_iomux_set_pad(MX35_PIN_TX4_RX1, pad_val);*/ + + pad_val = + PAD_CTL_DRV_HIGH | PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PU | + PAD_CTL_PUE_PUD; + + /* ESAI TX - WM8580 */ + mxc_iomux_set_pad(MX35_PIN_HCKT, pad_val); + /* ESAI RX - AK5702 */ + /*mxc_iomux_set_pad(MX35_PIN_HCKR, pad_val);*/ +} + +EXPORT_SYMBOL(gpio_activate_esai_ports); + +/*! + * This function deactivates ESAI ports to disable + * surround sound I/O + */ +void gpio_deactivate_esai_ports(void) +{ + + mxc_free_iomux(MX35_PIN_HCKT, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_SCKT, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_FST, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_TX0, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_TX1, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_TX2_RX3, MUX_CONFIG_FUNC); + /*mxc_free_iomux(MX35_PIN_HCKR, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_SCKR, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_FSR, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_TX3_RX2, MUX_CONFIG_GPIO); + mxc_free_iomux(MX35_PIN_TX4_RX1, MUX_CONFIG_GPIO);*/ +} + +EXPORT_SYMBOL(gpio_deactivate_esai_ports); + +/*! + * This function enable and reset GPS GPIO + */ +void gpio_gps_active(void) +{ + /* Pull GPIO1_5 to be low for routing signal to UART3/GPS */ + if (board_is_rev(BOARD_REV_2)) { + mxc_request_iomux(MX35_PIN_COMPARE, MUX_CONFIG_GPIO); + mxc_iomux_set_pad(MX35_PIN_COMPARE, PAD_CTL_DRV_NORMAL | + PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PU | + PAD_CTL_DRV_3_3V | PAD_CTL_PUE_PUD | + PAD_CTL_SRE_SLOW); + gpio_direction_output(IOMUX_TO_GPIO(MX35_PIN_COMPARE), 0); + gpio_set_value(IOMUX_TO_GPIO(MX35_PIN_COMPARE), 0); + } + + /* PWR_EN_GPS is set to be 0, will be toggled on in app by ioctl */ + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_2, 0, 0); + + /* GPS 32KHz clock enbale */ + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 7, 1); + + /* GPS reset */ + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_1, 5, 0); + msleep(5); + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_1, 5, 1); + msleep(5); +} + +EXPORT_SYMBOL(gpio_gps_active); + +/*! + * This function get GPS GPIO status. + */ +int gpio_gps_access(int para) +{ + unsigned int gps_val; + + if (para & 0x4) { /* Read GPIO */ + if (para & 0x1) /* Read PWR_EN */ + pmic_gpio_get_bit_val(MCU_GPIO_REG_GPIO_CONTROL_2, 0, + &gps_val); + else /* Read nReset */ + pmic_gpio_get_bit_val(MCU_GPIO_REG_RESET_1, 5, + &gps_val); + return gps_val; + } else { /* Write GPIO */ + gps_val = (para & 0x2) ? 1 : 0; + if (para & 0x1) + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_2, 0, + gps_val); + else + pmic_gpio_set_bit_val(MCU_GPIO_REG_RESET_1, 5, gps_val); + } + return 0; +} + +EXPORT_SYMBOL(gpio_gps_access); + +/*! + * This function disable GPS GPIO + */ +void gpio_gps_inactive(void) +{ + /* GPS disable */ + pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_2, 0, 0); + /* Free GPIO1_5 */ + if (board_is_rev(BOARD_REV_2)) + mxc_free_iomux(MX35_PIN_COMPARE, MUX_CONFIG_GPIO); +} + +EXPORT_SYMBOL(gpio_gps_inactive); + +/*! + * The MLB gpio configuration routine + */ +void gpio_mlb_active(void) +{ + mxc_request_iomux(MX35_PIN_MLB_CLK, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_MLB_SIG, MUX_CONFIG_FUNC); + mxc_request_iomux(MX35_PIN_MLB_DAT, MUX_CONFIG_FUNC); +} + +EXPORT_SYMBOL(gpio_mlb_active); + +void gpio_mlb_inactive(void) +{ + mxc_free_iomux(MX35_PIN_MLB_CLK, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_MLB_SIG, MUX_CONFIG_FUNC); + mxc_free_iomux(MX35_PIN_MLB_DAT, MUX_CONFIG_FUNC); +} + +EXPORT_SYMBOL(gpio_mlb_inactive); + +void gpio_can_active(int id) +{ + int pad; + + switch (id) { + case 0: + pad = PAD_CTL_HYS_SCHMITZ | PAD_CTL_PKE_ENABLE | \ + PAD_CTL_PUE_PUD | PAD_CTL_100K_PU | PAD_CTL_DRV_HIGH; + mxc_request_iomux(MX35_PIN_I2C2_CLK, MUX_CONFIG_ALT1); + mxc_request_iomux(MX35_PIN_I2C2_DAT, MUX_CONFIG_ALT1); + mxc_iomux_set_pad(MX35_PIN_I2C2_CLK, pad); + mxc_iomux_set_pad(MX35_PIN_I2C2_DAT, pad); + mxc_iomux_set_input(MUX_IN_CAN1_CANRX, INPUT_CTL_PATH0); + break; + case 1: + pad = PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PUD | PAD_CTL_100K_PU; + mxc_request_iomux(MX35_PIN_FEC_MDC, MUX_CONFIG_ALT1); + mxc_request_iomux(MX35_PIN_FEC_MDIO, MUX_CONFIG_ALT1); + mxc_iomux_set_pad(MX35_PIN_FEC_MDC, pad); + mxc_iomux_set_pad(MX35_PIN_FEC_MDIO, pad); + mxc_iomux_set_input(MUX_IN_CAN2_CANRX, INPUT_CTL_PATH2); + break; + default: + printk(KERN_ERR "NO such device\n"); + } +} + +void gpio_can_inactive(int id) +{ + switch (id) { + case 0: + mxc_free_iomux(MX35_PIN_I2C2_CLK, MUX_CONFIG_ALT1); + mxc_free_iomux(MX35_PIN_I2C2_DAT, MUX_CONFIG_ALT1); + mxc_iomux_set_input(MUX_IN_CAN1_CANRX, INPUT_CTL_PATH0); + break; + case 1: + mxc_free_iomux(MX35_PIN_FEC_MDC, MUX_CONFIG_ALT1); + mxc_free_iomux(MX35_PIN_FEC_MDIO, MUX_CONFIG_ALT1); + mxc_iomux_set_input(MUX_IN_CAN2_CANRX, INPUT_CTL_PATH0); + break; + default: + printk(KERN_ERR "NO such device\n"); + } +} + +void gpio_pmic_active(void) +{ + unsigned int pad_val = PAD_CTL_SRE_SLOW | PAD_CTL_DRV_NORMAL + | PAD_CTL_HYS_CMOS | PAD_CTL_100K_PU | PAD_CTL_DRV_3_3V; + mxc_request_iomux(MX35_PIN_GPIO2_0, MUX_CONFIG_FUNC); + mxc_iomux_set_pad(MX35_PIN_GPIO2_0, pad_val); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_GPIO2_0), NULL); + gpio_direction_input(IOMUX_TO_GPIO(MX35_PIN_GPIO2_0)); +} + +EXPORT_SYMBOL(gpio_pmic_active); diff --git a/arch/arm/mach-mx35/mx35_3stack_irq.c b/arch/arm/mach-mx35/mx35_3stack_irq.c new file mode 100644 index 000000000000..ad772040c19e --- /dev/null +++ b/arch/arm/mach-mx35/mx35_3stack_irq.c @@ -0,0 +1,372 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/mfd/mc9s08dz60/pmic.h> + +#include <mach/hardware.h> +#include <asm/irq.h> +#include <asm/setup.h> +#include <asm/bitops.h> +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <asm/mach/irq.h> + +#include <mach/gpio.h> + +#include "board-mx35_3stack.h" +#include "iomux.h" + +/*! + * @file mach-mx35/mx35_3stack_irq.c + * + * @brief This file contains the board specific initialization routines. + * + * @ingroup MSL_MX35 + */ +#ifdef CONFIG_MXC_PSEUDO_IRQS + +/* + * The interrupt status and mask variables. + */ +static unsigned long pseudo_irq_pending; +static unsigned long pseudo_irq_enable; +static unsigned long pseudo_irq_wakeup; +static unsigned long pseudo_suspend; +static atomic_t pseudo_irq_state = ATOMIC_INIT(0); + +/* + * The declaration of handler of two work queue. + * The one is the work queue to indentify the events from MCU. + * The another is the work queue to change the events mask. + */ +static void mcu_event_handler(struct work_struct *work); +static void mcu_state_handler(struct work_struct *work); +static void mcu_event_delay(unsigned long data); + +/*! + * The work structure for mcu events. + */ +static DECLARE_WORK(mcu_event_ws, mcu_event_handler); +static DECLARE_WORK(mcu_state_ws, mcu_state_handler); +static DEFINE_TIMER(mcu_delay_timer, mcu_event_delay, HZ, 0); + +static inline void mxc_pseudo_irq_ack(void) +{ + disable_irq(MXC_PSEUDO_PARENT); + atomic_set(&pseudo_irq_state, 0); +} + +static inline void mxc_pseudo_irq_trigger(void) +{ + if (!atomic_xchg(&pseudo_irq_state, 1)) + enable_irq(MXC_PSEUDO_PARENT); +} + +/* + * mask a pseudo interrupt by setting the bit in the mask variable. + * @param irq a pseudo virtual irq number + */ +static void pseudo_mask_irq(u32 irq) +{ + int index = irq - MXC_PSEUDO_IO_BASE; + clear_bit(index, &pseudo_irq_enable); +} + +/* + * disable a pseudo interrupt by triggerring a work queue + * @param irq a pseudo virtual irq number + */ +static void pseudo_disable_irq(u32 irq) +{ + struct irq_desc *desc = irq_desc + irq; + desc->chip->mask(irq); + desc->status |= IRQ_MASKED; + schedule_work(&mcu_state_ws); +} + +/* + * Acknowledge a pseudo interrupt by clearing the bit in the isr variable. + * @param irq a pseudo virtual irq number + */ +static void pseudo_ack_irq(u32 irq) +{ + int index = irq - MXC_PSEUDO_IO_BASE; + /* clear the interrupt status */ + clear_bit(index, &pseudo_irq_pending); +} + +/* + * unmask a pseudo interrupt by clearing the bit in the imr. + * @param irq a pseudo virtual irq number + */ +static void pseudo_unmask_irq(u32 irq) +{ + int index = irq - MXC_PSEUDO_IO_BASE; + + set_bit(index, &pseudo_irq_enable); + + if (test_bit(index, &pseudo_irq_pending)) + mxc_pseudo_irq_trigger(); +} + +/* + * Enable a pseudo interrupt by triggerring a work queue + * @param irq a pseudo virtual irq number + */ +static void pseudo_enable_irq(u32 irq) +{ + struct irq_desc *desc = irq_desc + irq; + desc->chip->unmask(irq); + desc->status &= ~IRQ_MASKED; + schedule_work(&mcu_state_ws); +} + +/* + * set pseudo irq as a wake-up source. + * @param irq a pseudo virtual irq number + * @param enable enable as wake-up if equal to non-ero + * @return This function return 0 on success + */ +static int pseudo_set_wake_irq(u32 irq, u32 enable) +{ + int index = irq - MXC_PSEUDO_IO_BASE; + + if (index >= MXC_MAX_PSEUDO_IO_LINES) + return -ENODEV; + + if (enable) { + if (!pseudo_irq_wakeup) + enable_irq_wake(IOMUX_TO_IRQ(MX35_PIN_GPIO1_0)); + pseudo_irq_wakeup |= (1 << index); + } else { + pseudo_irq_wakeup &= ~(1 << index); + if (!pseudo_irq_wakeup) + disable_irq_wake(IOMUX_TO_IRQ(MX35_PIN_GPIO1_0)); + } + return 0; +} + +static struct irq_chip pseudo_irq_chip = { + .ack = pseudo_ack_irq, + .mask = pseudo_mask_irq, + .disable = pseudo_disable_irq, + .unmask = pseudo_unmask_irq, + .enable = pseudo_enable_irq, + .set_wake = pseudo_set_wake_irq, +}; + +static void mxc_pseudo_irq_handler(u32 irq, struct irq_desc *desc) +{ + u32 pseudo_irq; + u32 index, mask; + + desc->chip->mask(irq); + mxc_pseudo_irq_ack(); + + mask = pseudo_irq_enable; + index = pseudo_irq_pending; + + if (unlikely(!(index & mask))) { + printk(KERN_ERR "\nPseudo IRQ: Spurious interrupt:0x%0x\n\n", + index); + pr_info("IEN=0x%x, PENDING=0x%x\n", mask, index); + return; + } + + index = index & mask; + pseudo_irq = MXC_PSEUDO_IO_BASE; + for (; index != 0; index >>= 1, pseudo_irq++) { + struct irq_desc *d; + if ((index & 1) == 0) + continue; + d = irq_desc + pseudo_irq; + if (unlikely(!(d->handle_irq))) { + printk(KERN_ERR "\nPseudo irq: %d unhandeled\n", + pseudo_irq); + BUG(); /* oops */ + } + d->handle_irq(pseudo_irq, d); + d->chip->ack(pseudo_irq); + } +} + +static void mcu_event_delay(unsigned long data) +{ + schedule_work(&mcu_event_ws); +} + +/*! + * This function is called when mcu interrupt occurs on the processor. + * It is the interrupt handler for the mcu. + * + * @param irq the irq number + * @param dev_id the pointer on the device + * + * @return The function returns IRQ_HANDLED when handled. + */ +static irqreturn_t mcu_irq_handler(int irq, void *dev_id) +{ + disable_irq(IOMUX_TO_IRQ(MX35_PIN_GPIO1_0)); + if (pseudo_suspend) + mod_timer(&mcu_delay_timer, jiffies + HZ); + else + schedule_work(&mcu_event_ws); + + return IRQ_HANDLED; +} + +/*! + * This function is the work handler of mcu interrupt. + * It reads the events status and trigger the pseudo irq. + */ +static void mcu_event_handler(struct work_struct *work) +{ + int i, err; + unsigned int flag1, flag2; + + /* read int flags and ack int */ + for (i = 0; i < 3; i++) { + err = mcu_pmic_read_reg(REG_MCU_INT_FLAG_1, &flag1, 0xFFFFFFFF); + err |= mcu_pmic_read_reg(REG_MCU_INT_FLAG_2, + &flag2, 0xFFFFFFFF); + err |= mcu_pmic_write_reg(REG_MCU_INT_FLAG_1, 0, 0xFFFFFFFF); + err |= mcu_pmic_write_reg(REG_MCU_INT_FLAG_2, 0, 0xFFFFFFFF); + if (err == 0) + break; + } + + if (i >= 3) { + printk(KERN_ERR "Reads MCU event fail\n"); + goto no_new_events; + } + + for (i = 0; flag1 && (i < MCU_INT_RTC); i++, flag1 >>= 1) + if (flag1 & 1) + set_bit(i, &pseudo_irq_pending); + + for (i = MCU_INT_RTC; flag2 && (i <= MCU_INT_KEYPAD); i++, flag2 >>= 1) + if (flag2 & 1) + set_bit(i, &pseudo_irq_pending); + no_new_events: + if (pseudo_irq_pending & pseudo_irq_enable) + mxc_pseudo_irq_trigger(); + enable_irq(IOMUX_TO_IRQ(MX35_PIN_GPIO1_0)); +} + +static void mcu_state_handler(struct work_struct *work) +{ + int err, i; + unsigned int event1, event2; + event1 = pseudo_irq_enable & ((1 << MCU_INT_RTC) - 1); + event2 = pseudo_irq_enable >> MCU_INT_RTC; + + for (i = 0; i < 3; i++) { + err = mcu_pmic_write_reg(REG_MCU_INT_ENABLE_1, event1, 0xFF); + err |= mcu_pmic_write_reg(REG_MCU_INT_ENABLE_2, event2, 0xFF); + if (err == 0) + break; + } + if (i >= 3) + printk(KERN_ERR "Change MCU event mask fail\n"); +} + +static int __init mxc_pseudo_init(void) +{ + int i; + + /* disable the interrupt and clear the status */ + pseudo_irq_pending = 0; + pseudo_irq_enable = 0; + + pr_info("3-Stack Pseudo interrupt rev=0.1v\n"); + + for (i = MXC_PSEUDO_IO_BASE; + i < (MXC_PSEUDO_IO_BASE + MXC_MAX_PSEUDO_IO_LINES); i++) { + set_irq_chip(i, &pseudo_irq_chip); + set_irq_handler(i, handle_simple_irq); + set_irq_flags(i, IRQF_VALID); + } + + set_irq_flags(MXC_PSEUDO_PARENT, IRQF_NOAUTOEN); + set_irq_handler(MXC_PSEUDO_PARENT, mxc_pseudo_irq_handler); + + /* Set and install PMIC IRQ handler */ + mxc_request_iomux(MX35_PIN_GPIO1_0, MUX_CONFIG_FUNC); + mxc_iomux_set_pad(MX35_PIN_GPIO1_0, PAD_CTL_PKE_NONE); + gpio_request(IOMUX_TO_GPIO(MX35_PIN_GPIO1_0), NULL); + gpio_direction_input(IOMUX_TO_GPIO(MX35_PIN_GPIO1_0)); + + set_irq_type(IOMUX_TO_IRQ(MX35_PIN_GPIO1_0), IRQF_TRIGGER_RISING); + if (request_irq(IOMUX_TO_IRQ(MX35_PIN_GPIO1_0), mcu_irq_handler, + 0, "MCU_IRQ", 0)) { + printk(KERN_ERR "mcu request irq failed\n"); + return -1; + } + return 0; +} + +fs_initcall_sync(mxc_pseudo_init); + +static int mxc_pseudo_irq_suspend(struct platform_device *dev, + pm_message_t mesg) +{ + int err, i; + unsigned int event1, event2; + + if (!pseudo_irq_wakeup) + return 0; + + event1 = pseudo_irq_wakeup & ((1 << MCU_INT_RTC) - 1); + event2 = pseudo_irq_wakeup >> MCU_INT_RTC; + + for (i = 0; i < 3; i++) { + err = mcu_pmic_write_reg(REG_MCU_INT_ENABLE_1, event1, 0xFF); + err |= mcu_pmic_write_reg(REG_MCU_INT_ENABLE_2, event2, 0xFF); + if (err == 0) + break; + } + pseudo_suspend = 1; + return err; +} + +static int mxc_pseudo_irq_resume(struct platform_device *dev) +{ + if (!pseudo_irq_wakeup) + return 0; + + schedule_work(&mcu_state_ws); + pseudo_suspend = 0; + return 0; +} + +static struct platform_driver mxc_pseudo_irq_driver = { + .driver = { + .name = "mxc_pseudo_irq", + }, + .suspend = mxc_pseudo_irq_suspend, + .resume = mxc_pseudo_irq_resume, +}; + +static int __init mxc_pseudo_sysinit(void) +{ + return platform_driver_register(&mxc_pseudo_irq_driver); +} + +late_initcall(mxc_pseudo_sysinit); +#endif /* CONFIG_MXC_PSEUDO_IRQS */ diff --git a/arch/arm/mach-mx35/mx35_3stack_pmic_mc13892.c b/arch/arm/mach-mx35/mx35_3stack_pmic_mc13892.c new file mode 100644 index 000000000000..44cccc564c2e --- /dev/null +++ b/arch/arm/mach-mx35/mx35_3stack_pmic_mc13892.c @@ -0,0 +1,347 @@ +/* + * mx35-3stack-pmic-mc13892.c -- i.MX35 3STACK Driver for Atlas MC13892 PMIC + */ + /* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + + /* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/pmic_external.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/mc13892/core.h> +#include <mach/irqs.h> +#include "iomux.h" + +/* + * Convenience conversion. + * Here atm, maybe there is somewhere better for this. + */ +#define mV_to_uV(mV) (mV * 1000) +#define uV_to_mV(uV) (uV / 1000) +#define V_to_uV(V) (mV_to_uV(V * 1000)) +#define uV_to_V(uV) (uV_to_mV(uV) / 1000) + +#define STANDBYSECINV_LSH 11 +#define STANDBYSECINV_WID 1 + +/* CPU */ +static struct regulator_consumer_supply sw1_consumers[] = { + { + .supply = "cpu_vcc", + } +}; + +struct mc13892; + +static struct regulator_init_data sw1_init = { + .constraints = { + .name = "SW1", + .min_uV = mV_to_uV(600), + .max_uV = mV_to_uV(1375), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .valid_modes_mask = 0, + .always_on = 1, + .boot_on = 1, + .initial_state = PM_SUSPEND_MEM, + .state_mem = { + .uV = 700000, + .mode = REGULATOR_MODE_NORMAL, + .enabled = 1, + }, + }, + .num_consumer_supplies = ARRAY_SIZE(sw1_consumers), + .consumer_supplies = sw1_consumers, +}; + +static struct regulator_init_data sw2_init = { + .constraints = { + .name = "SW2", + .min_uV = mV_to_uV(900), + .max_uV = mV_to_uV(1850), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .always_on = 1, + .boot_on = 1, + .initial_state = PM_SUSPEND_MEM, + .state_mem = { + .uV = 1200000, + .mode = REGULATOR_MODE_NORMAL, + .enabled = 1, + }, + .state_standby = { + .uV = 1000000, + .mode = REGULATOR_MODE_NORMAL, + .enabled = 1, + }, + } +}; + +static struct regulator_init_data sw3_init = { + .constraints = { + .name = "SW3", + .min_uV = mV_to_uV(1100), + .max_uV = mV_to_uV(1850), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .always_on = 1, + .boot_on = 1, + } +}; + +static struct regulator_init_data sw4_init = { + .constraints = { + .name = "SW4", + .min_uV = mV_to_uV(1100), + .max_uV = mV_to_uV(1850), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .always_on = 1, + .boot_on = 1, + } +}; + +static struct regulator_init_data viohi_init = { + .constraints = { + .name = "VIOHI", + .boot_on = 1, + } +}; + +static struct regulator_init_data vusb_init = { + .constraints = { + .name = "VUSB", + .boot_on = 1, + } +}; + +static struct regulator_init_data swbst_init = { + .constraints = { + .name = "SWBST", + } +}; + +static struct regulator_init_data vdig_init = { + .constraints = { + .name = "VDIG", + .min_uV = mV_to_uV(1050), + .max_uV = mV_to_uV(1800), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vpll_init = { + .constraints = { + .name = "VPLL", + .min_uV = mV_to_uV(1050), + .max_uV = mV_to_uV(1800), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vusb2_init = { + .constraints = { + .name = "VUSB2", + .min_uV = mV_to_uV(2400), + .max_uV = mV_to_uV(2775), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vvideo_init = { + .constraints = { + .name = "VVIDEO", + .min_uV = mV_to_uV(2500), + .max_uV = mV_to_uV(2775), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vaudio_init = { + .constraints = { + .name = "VAUDIO", + .min_uV = mV_to_uV(2300), + .max_uV = mV_to_uV(3000), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vsd_init = { + .constraints = { + .name = "VSD", + .min_uV = mV_to_uV(1800), + .max_uV = mV_to_uV(3150), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vcam_init = { + .constraints = { + .name = "VCAM", + .min_uV = mV_to_uV(2500), + .max_uV = mV_to_uV(3000), + .valid_ops_mask = + REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_FAST | REGULATOR_MODE_NORMAL, + } +}; + +static struct regulator_init_data vgen1_init = { + .constraints = { + .name = "VGEN1", + .min_uV = mV_to_uV(1200), + .max_uV = mV_to_uV(3150), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vgen2_init = { + .constraints = { + .name = "VGEN2", + .min_uV = mV_to_uV(1200), + .max_uV = mV_to_uV(3150), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vgen3_init = { + .constraints = { + .name = "VGEN3", + .min_uV = mV_to_uV(1800), + .max_uV = mV_to_uV(2900), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data gpo1_init = { + .constraints = { + .name = "GPO1", + } +}; + +static struct regulator_init_data gpo2_init = { + .constraints = { + .name = "GPO2", + } +}; + +static struct regulator_init_data gpo3_init = { + .constraints = { + .name = "GPO3", + } +}; + +static struct regulator_init_data gpo4_init = { + .constraints = { + .name = "GPO4", + } +}; + +static struct regulator_init_data pwg1_init = { + .constraints = { + .name = "PWG1", + } +}; + +static struct regulator_init_data pwg2_init = { + .constraints = { + .name = "PWG2", + } +}; + +/*! + * the event handler for power on event + */ +static void power_on_evt_handler(void) +{ + pr_info("pwr on event1 is received \n"); +} + +/*! + * pmic board initialization code + */ +static int init_mc13892(void) +{ + unsigned int value; + pmic_event_callback_t power_key_event; + + if (!board_is_rev(BOARD_REV_2)) + return -1; + + /* subscribe PWRON1 event. */ + power_key_event.param = NULL; + power_key_event.func = (void *)power_on_evt_handler; + pmic_event_subscribe(EVENT_PWRONI, power_key_event); + + pmic_read_reg(REG_POWER_CTL2, &value, 0xffffff); + /* Bit 11 (STANDBYSECINV): Active Low */ + value |= 0x00800; + /* Bit 12 (WDIRESET): enable */ + value |= 0x01000; + pmic_write_reg(REG_POWER_CTL2, value, 0xffffff); + + /* Battery charger default settings */ + /* current limit = 1200mA, PLIM = 1000mw, disable auto charge */ + value = 0x210068; + pmic_write_reg(REG_CHARGE, value, 0x018078); + + return 0; +} + +static int mc13892_regulator_init(struct mc13892 *mc13892) +{ + mc13892_register_regulator(mc13892, MC13892_SW1, &sw1_init); + mc13892_register_regulator(mc13892, MC13892_SW2, &sw2_init); + mc13892_register_regulator(mc13892, MC13892_SW3, &sw3_init); + mc13892_register_regulator(mc13892, MC13892_SW4, &sw4_init); + mc13892_register_regulator(mc13892, MC13892_SWBST, &swbst_init); + mc13892_register_regulator(mc13892, MC13892_VIOHI, &viohi_init); + mc13892_register_regulator(mc13892, MC13892_VPLL, &vpll_init); + mc13892_register_regulator(mc13892, MC13892_VDIG, &vdig_init); + mc13892_register_regulator(mc13892, MC13892_VSD, &vsd_init); + mc13892_register_regulator(mc13892, MC13892_VUSB2, &vusb2_init); + mc13892_register_regulator(mc13892, MC13892_VVIDEO, &vvideo_init); + mc13892_register_regulator(mc13892, MC13892_VAUDIO, &vaudio_init); + mc13892_register_regulator(mc13892, MC13892_VCAM, &vcam_init); + mc13892_register_regulator(mc13892, MC13892_VGEN1, &vgen1_init); + mc13892_register_regulator(mc13892, MC13892_VGEN2, &vgen2_init); + mc13892_register_regulator(mc13892, MC13892_VGEN3, &vgen3_init); + mc13892_register_regulator(mc13892, MC13892_VUSB, &vusb_init); + mc13892_register_regulator(mc13892, MC13892_GPO1, &gpo1_init); + mc13892_register_regulator(mc13892, MC13892_GPO2, &gpo2_init); + mc13892_register_regulator(mc13892, MC13892_GPO3, &gpo3_init); + mc13892_register_regulator(mc13892, MC13892_GPO4, &gpo4_init); + mc13892_register_regulator(mc13892, MC13892_PWGT1, &pwg1_init); + mc13892_register_regulator(mc13892, MC13892_PWGT2, &pwg2_init); + + init_mc13892(); + return 0; +} + +static struct mc13892_platform_data mc13892_plat = { + .init = mc13892_regulator_init, +}; + +static struct i2c_board_info __initdata mc13892_i2c_device = { + I2C_BOARD_INFO("mc13892", 0x08), + .irq = IOMUX_TO_IRQ(MX35_PIN_GPIO2_0), + .platform_data = &mc13892_plat, +}; + +int __init mx35_3stack_init_mc13892(void) +{ + return i2c_register_board_info(0, &mc13892_i2c_device, 1); +} diff --git a/arch/arm/mach-mx35/mx35_3stack_pmic_mc9s08dz60.c b/arch/arm/mach-mx35/mx35_3stack_pmic_mc9s08dz60.c new file mode 100644 index 000000000000..25d37f98f9d7 --- /dev/null +++ b/arch/arm/mach-mx35/mx35_3stack_pmic_mc9s08dz60.c @@ -0,0 +1,104 @@ +/* + * mx35-3stack-pmic-mc9s08dz60.c -- i.MX35 3STACK Driver for MCU PMIC + */ + /* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + + /* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/pmic_external.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/mc9s08dz60/core.h> +#include "iomux.h" +#include "board-mx35_3stack.h" + +static struct regulator_init_data lcd_init = { + .constraints = { + .name = "LCD", + } +}; + +static struct regulator_init_data wifi_init = { + .constraints = { + .name = "WIFI", + } +}; + +static struct regulator_init_data hdd_init = { + .constraints = { + .name = "HDD", + } +}; + +static struct regulator_init_data gps_init = { + .constraints = { + .name = "GPS", + } +}; + +static struct regulator_init_data spkr_init = { + .constraints = { + .name = "SPKR", + } +}; + +static int mc9s08dz60_regulator_init(struct mc9s08dz60 *mc9s08dz60) +{ + if (!board_is_rev(BOARD_REV_2)) + return 0; + + mc9s08dz60_register_regulator( + mc9s08dz60, MC9S08DZ60_LCD, &lcd_init); + mc9s08dz60_register_regulator(mc9s08dz60, + MC9S08DZ60_WIFI, &wifi_init); + mc9s08dz60_register_regulator( + mc9s08dz60, MC9S08DZ60_HDD, &hdd_init); + mc9s08dz60_register_regulator( + mc9s08dz60, MC9S08DZ60_GPS, &gps_init); + mc9s08dz60_register_regulator(mc9s08dz60, + MC9S08DZ60_SPKR, &spkr_init); + return 0; +} + +static struct mc9s08dz60_platform_data mc9s08dz60_plat = { + .init = mc9s08dz60_regulator_init, +}; + +static struct i2c_board_info __initdata mc9s08dz60_i2c_device = { + I2C_BOARD_INFO("mc9s08dz60", 0x69), + .platform_data = &mc9s08dz60_plat, +}; + +static struct resource mc9s08dz60_keypad_resource = { + .start = MXC_PSEUDO_IRQ_KEYPAD, + .end = MXC_PSEUDO_IRQ_KEYPAD, + .flags = IORESOURCE_IRQ, +}; + +static struct platform_device mc9s08dz60_keypad_dev = { + .name = "mc9s08dz60keypad", + .num_resources = 1, + .resource = &mc9s08dz60_keypad_resource, +}; + +int __init mx35_3stack_init_mc9s08dz60(void) +{ + int retval = 0; + retval = i2c_register_board_info(0, &mc9s08dz60_i2c_device, 1); + if (retval == 0) + platform_device_register(&mc9s08dz60_keypad_dev); + return retval; +} diff --git a/arch/arm/mach-mx35/mx35_pins.h b/arch/arm/mach-mx35/mx35_pins.h new file mode 100644 index 000000000000..082384231370 --- /dev/null +++ b/arch/arm/mach-mx35/mx35_pins.h @@ -0,0 +1,333 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_ARCH_MXC_MX35_PINS_H__ +#define __ASM_ARCH_MXC_MX35_PINS_H__ + +/*! + * @file arch-mxc/mx35_pins.h + * + * @brief MX35 I/O Pin List + * + * @ingroup GPIO_MX35 + */ + +#ifndef __ASSEMBLY__ + +/*! + * @name IOMUX/PAD Bit field definitions + */ + +/*! @{ */ + +/*! + * In order to identify pins more effectively, each mux-controlled pin's + * enumerated value is constructed in the following way: + * + * ------------------------------------------------------------------- + * 31-29 | 28 - 24 |23 - 21| 20 - 10| 9 - 0 + * ------------------------------------------------------------------- + * IO_P | IO_I | RSVD | PAD_I | MUX_I + * ------------------------------------------------------------------- + * + * Bit 0 to 7 contains MUX_I used to identify the register + * offset (base is IOMUX_module_base ) defined in the Section + * "sw_pad_ctl & sw_mux_ctl details" of the IC Spec. The similar field + * definitions are used for the pad control register.the MX35_PIN_A0 is + * defined in the enumeration: ( 0x28 << MUX_I) |( 0x368 << PAD_I) + * So the absolute address is: IOMUX_module_base + 0x28. + * The pad control register offset is: 0x368. + */ + +/*! + * Starting bit position within each entry of \b iomux_pins to represent the + * MUX control register offset + */ +#define MUX_I 0 +/*! + * Starting bit position within each entry of \b iomux_pins to represent the + * PAD control register offset + */ +#define PAD_I 10 + +/*! + * Starting bit position within each entry of \b iomux_pins to represent the + * reserved filed + */ +#define RSVD_I 21 + +#define NON_GPIO_I 0x7 +#define PIN_TO_MUX_MASK ((1<<(PAD_I - MUX_I)) - 1) +#define PIN_TO_PAD_MASK ((1<<(RSVD_I - PAD_I)) - 1) +#define NON_MUX_I PIN_TO_MUX_MASK + +#define _MXC_BUILD_PIN(gp, gi, mi, pi) \ + (((gp) << MUX_IO_P) | ((gi) << MUX_IO_I) | \ + ((mi) << MUX_I) | ((pi) << PAD_I)) + +#define _MXC_BUILD_GPIO_PIN(gp, gi, mi, pi) \ + _MXC_BUILD_PIN(gp, gi, mi, pi) + +#define _MXC_BUILD_NON_GPIO_PIN(mi, pi) \ + _MXC_BUILD_PIN(NON_GPIO_I, 0, mi, pi) + +#define PIN_TO_IOMUX_MUX(pin) ((pin >> MUX_I) & PIN_TO_MUX_MASK) +#define PIN_TO_IOMUX_PAD(pin) ((pin >> PAD_I) & PIN_TO_PAD_MASK) + +/*! @} End IOMUX/PAD Bit field definitions */ + +/*! + * This enumeration is constructed based on the Section + * "sw_pad_ctl & sw_mux_ctl details" of the MX35 IC Spec. Each enumerated + * value is constructed based on the rules described above. + */ +enum iomux_pins { + MX35_PIN_CAPTURE = _MXC_BUILD_GPIO_PIN(0, 4, 0x4, 0x328), + MX35_PIN_COMPARE = _MXC_BUILD_GPIO_PIN(0, 5, 0x8, 0x32C), + MX35_PIN_WATCHDOG_RST = _MXC_BUILD_GPIO_PIN(0, 6, 0xC, 0x330), + MX35_PIN_GPIO1_0 = _MXC_BUILD_GPIO_PIN(0, 0, 0x10, 0x334), + MX35_PIN_GPIO1_1 = _MXC_BUILD_GPIO_PIN(0, 1, 0x14, 0x338), + MX35_PIN_GPIO2_0 = _MXC_BUILD_GPIO_PIN(1, 0, 0x18, 0x33C), + MX35_PIN_GPIO3_0 = _MXC_BUILD_GPIO_PIN(2, 0, 0x1C, 0x340), + MX35_PIN_CLKO = _MXC_BUILD_GPIO_PIN(0, 8, 0x20, 0x34C), + + MX35_PIN_POWER_FAIL = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x360), + MX35_PIN_VSTBY = _MXC_BUILD_GPIO_PIN(0, 7, 0x24, 0x364), + MX35_PIN_A0 = _MXC_BUILD_NON_GPIO_PIN(0x28, 0x368), + MX35_PIN_A1 = _MXC_BUILD_NON_GPIO_PIN(0x2C, 0x36C), + MX35_PIN_A2 = _MXC_BUILD_NON_GPIO_PIN(0x30, 0x370), + MX35_PIN_A3 = _MXC_BUILD_NON_GPIO_PIN(0x34, 0x374), + MX35_PIN_A4 = _MXC_BUILD_NON_GPIO_PIN(0x38, 0x378), + MX35_PIN_A5 = _MXC_BUILD_NON_GPIO_PIN(0x3C, 0x37C), + MX35_PIN_A6 = _MXC_BUILD_NON_GPIO_PIN(0x40, 0x380), + MX35_PIN_A7 = _MXC_BUILD_NON_GPIO_PIN(0x44, 0x384), + MX35_PIN_A8 = _MXC_BUILD_NON_GPIO_PIN(0x48, 0x388), + MX35_PIN_A9 = _MXC_BUILD_NON_GPIO_PIN(0x4C, 0x38C), + MX35_PIN_A10 = _MXC_BUILD_NON_GPIO_PIN(0x50, 0x390), + MX35_PIN_MA10 = _MXC_BUILD_NON_GPIO_PIN(0x54, 0x394), + MX35_PIN_A11 = _MXC_BUILD_NON_GPIO_PIN(0x58, 0x398), + MX35_PIN_A12 = _MXC_BUILD_NON_GPIO_PIN(0x5C, 0x39C), + MX35_PIN_A13 = _MXC_BUILD_NON_GPIO_PIN(0x60, 0x3A0), + MX35_PIN_A14 = _MXC_BUILD_NON_GPIO_PIN(0x64, 0x3A4), + MX35_PIN_A15 = _MXC_BUILD_NON_GPIO_PIN(0x68, 0x3A8), + MX35_PIN_A16 = _MXC_BUILD_NON_GPIO_PIN(0x6C, 0x3AC), + MX35_PIN_A17 = _MXC_BUILD_NON_GPIO_PIN(0x70, 0x3B0), + MX35_PIN_A18 = _MXC_BUILD_NON_GPIO_PIN(0x74, 0x3B4), + MX35_PIN_A19 = _MXC_BUILD_NON_GPIO_PIN(0x78, 0x3B8), + MX35_PIN_A20 = _MXC_BUILD_NON_GPIO_PIN(0x7C, 0x3BC), + MX35_PIN_A21 = _MXC_BUILD_NON_GPIO_PIN(0x80, 0x3C0), + MX35_PIN_A22 = _MXC_BUILD_NON_GPIO_PIN(0x84, 0x3C4), + MX35_PIN_A23 = _MXC_BUILD_NON_GPIO_PIN(0x88, 0x3C8), + MX35_PIN_A24 = _MXC_BUILD_NON_GPIO_PIN(0x8C, 0x3CC), + MX35_PIN_A25 = _MXC_BUILD_NON_GPIO_PIN(0x90, 0x3D0), + + MX35_PIN_EB0 = _MXC_BUILD_NON_GPIO_PIN(0x94, 0x46C), + MX35_PIN_EB1 = _MXC_BUILD_NON_GPIO_PIN(0x98, 0x470), + MX35_PIN_OE = _MXC_BUILD_NON_GPIO_PIN(0x9C, 0x474), + MX35_PIN_CS0 = _MXC_BUILD_NON_GPIO_PIN(0xA0, 0x478), + MX35_PIN_CS1 = _MXC_BUILD_NON_GPIO_PIN(0xA4, 0x47C), + MX35_PIN_CS2 = _MXC_BUILD_NON_GPIO_PIN(0xA8, 0x480), + MX35_PIN_CS3 = _MXC_BUILD_NON_GPIO_PIN(0xAC, 0x484), + MX35_PIN_CS4 = _MXC_BUILD_GPIO_PIN(0, 20, 0xB0, 0x488), + MX35_PIN_CS5 = _MXC_BUILD_GPIO_PIN(0, 21, 0xB4, 0x48C), + MX35_PIN_NFCE_B = _MXC_BUILD_GPIO_PIN(0, 22, 0xB8, 0x490), + + MX35_PIN_LBA = _MXC_BUILD_NON_GPIO_PIN(0xBC, 0x498), + MX35_PIN_BCLK = _MXC_BUILD_NON_GPIO_PIN(0xC0, 0x49C), + MX35_PIN_RW = _MXC_BUILD_NON_GPIO_PIN(0xC4, 0x4A0), + + MX35_PIN_NFWE_B = _MXC_BUILD_GPIO_PIN(1, 18, 0xC8, 0x4CC), + MX35_PIN_NFRE_B = _MXC_BUILD_GPIO_PIN(1, 19, 0xCC, 0x4D0), + MX35_PIN_NFALE = _MXC_BUILD_GPIO_PIN(1, 20, 0xD0, 0x4D4), + MX35_PIN_NFCLE = _MXC_BUILD_GPIO_PIN(1, 21, 0xD4, 0x4D8), + MX35_PIN_NFWP_B = _MXC_BUILD_GPIO_PIN(1, 22, 0xD8, 0x4DC), + MX35_PIN_NFRB = _MXC_BUILD_GPIO_PIN(1, 23, 0xDC, 0x4E0), + + MX35_PIN_D15 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x4E4), + MX35_PIN_D14 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x4E8), + MX35_PIN_D13 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x4EC), + MX35_PIN_D12 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x4F0), + MX35_PIN_D11 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x4F4), + MX35_PIN_D10 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x4F8), + MX35_PIN_D9 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x4FC), + MX35_PIN_D8 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x500), + MX35_PIN_D7 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x504), + MX35_PIN_D6 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x508), + MX35_PIN_D5 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x50C), + MX35_PIN_D4 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x510), + MX35_PIN_D3 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x514), + MX35_PIN_D2 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x518), + MX35_PIN_D1 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x51C), + MX35_PIN_D0 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x520), + + MX35_PIN_CSI_D8 = _MXC_BUILD_GPIO_PIN(0, 20, 0xE0, 0x524), + MX35_PIN_CSI_D9 = _MXC_BUILD_GPIO_PIN(0, 21, 0xE4, 0x528), + MX35_PIN_CSI_D10 = _MXC_BUILD_GPIO_PIN(0, 22, 0xE8, 0x52C), + MX35_PIN_CSI_D11 = _MXC_BUILD_GPIO_PIN(0, 23, 0xEC, 0x530), + MX35_PIN_CSI_D12 = _MXC_BUILD_GPIO_PIN(0, 24, 0xF0, 0x534), + MX35_PIN_CSI_D13 = _MXC_BUILD_GPIO_PIN(0, 25, 0xF4, 0x538), + MX35_PIN_CSI_D14 = _MXC_BUILD_GPIO_PIN(0, 26, 0xF8, 0x53C), + MX35_PIN_CSI_D15 = _MXC_BUILD_GPIO_PIN(0, 27, 0xFC, 0x540), + MX35_PIN_CSI_MCLK = _MXC_BUILD_GPIO_PIN(0, 28, 0x100, 0x544), + MX35_PIN_CSI_VSYNC = _MXC_BUILD_GPIO_PIN(0, 29, 0x104, 0x548), + MX35_PIN_CSI_HSYNC = _MXC_BUILD_GPIO_PIN(0, 30, 0x108, 0x54C), + MX35_PIN_CSI_PIXCLK = _MXC_BUILD_GPIO_PIN(0, 31, 0x10C, 0x550), + + MX35_PIN_I2C1_CLK = _MXC_BUILD_GPIO_PIN(1, 24, 0x110, 0x554), + MX35_PIN_I2C1_DAT = _MXC_BUILD_GPIO_PIN(1, 25, 0x114, 0x558), + MX35_PIN_I2C2_CLK = _MXC_BUILD_GPIO_PIN(1, 26, 0x118, 0x55C), + MX35_PIN_I2C2_DAT = _MXC_BUILD_GPIO_PIN(1, 27, 0x11C, 0x560), + + MX35_PIN_STXD4 = _MXC_BUILD_GPIO_PIN(1, 28, 0x120, 0x564), + MX35_PIN_SRXD4 = _MXC_BUILD_GPIO_PIN(1, 29, 0x124, 0x568), + MX35_PIN_SCK4 = _MXC_BUILD_GPIO_PIN(1, 30, 0x128, 0x56C), + MX35_PIN_STXFS4 = _MXC_BUILD_GPIO_PIN(1, 31, 0x12C, 0x570), + MX35_PIN_STXD5 = _MXC_BUILD_GPIO_PIN(0, 0, 0x130, 0x574), + MX35_PIN_SRXD5 = _MXC_BUILD_GPIO_PIN(0, 1, 0x134, 0x578), + MX35_PIN_SCK5 = _MXC_BUILD_GPIO_PIN(0, 2, 0x138, 0x57C), + MX35_PIN_STXFS5 = _MXC_BUILD_GPIO_PIN(0, 3, 0x13C, 0x580), + + MX35_PIN_SCKR = _MXC_BUILD_GPIO_PIN(0, 4, 0x140, 0x584), + MX35_PIN_FSR = _MXC_BUILD_GPIO_PIN(0, 5, 0x144, 0x588), + MX35_PIN_HCKR = _MXC_BUILD_GPIO_PIN(0, 6, 0x148, 0x58C), + MX35_PIN_SCKT = _MXC_BUILD_GPIO_PIN(0, 7, 0x14C, 0x590), + MX35_PIN_FST = _MXC_BUILD_GPIO_PIN(0, 8, 0x150, 0x594), + MX35_PIN_HCKT = _MXC_BUILD_GPIO_PIN(0, 9, 0x154, 0x598), + MX35_PIN_TX5_RX0 = _MXC_BUILD_GPIO_PIN(0, 10, 0x158, 0x59C), + MX35_PIN_TX4_RX1 = _MXC_BUILD_GPIO_PIN(0, 11, 0x15C, 0x5A0), + MX35_PIN_TX3_RX2 = _MXC_BUILD_GPIO_PIN(0, 12, 0x160, 0x5A4), + MX35_PIN_TX2_RX3 = _MXC_BUILD_GPIO_PIN(0, 13, 0x164, 0x5A8), + MX35_PIN_TX1 = _MXC_BUILD_GPIO_PIN(0, 14, 0x168, 0x5AC), + MX35_PIN_TX0 = _MXC_BUILD_GPIO_PIN(0, 15, 0x16C, 0x5B0), + + MX35_PIN_CSPI1_MOSI = _MXC_BUILD_GPIO_PIN(0, 16, 0x170, 0x5B4), + MX35_PIN_CSPI1_MISO = _MXC_BUILD_GPIO_PIN(0, 17, 0x174, 0x5B8), + MX35_PIN_CSPI1_SS0 = _MXC_BUILD_GPIO_PIN(0, 18, 0x178, 0x5BC), + MX35_PIN_CSPI1_SS1 = _MXC_BUILD_GPIO_PIN(0, 19, 0x17C, 0x5C0), + MX35_PIN_CSPI1_SCLK = _MXC_BUILD_GPIO_PIN(2, 4, 0x180, 0x5C4), + MX35_PIN_CSPI1_SPI_RDY = _MXC_BUILD_GPIO_PIN(2, 5, 0x184, 0x5C8), + + MX35_PIN_RXD1 = _MXC_BUILD_GPIO_PIN(2, 6, 0x188, 0x5CC), + MX35_PIN_TXD1 = _MXC_BUILD_GPIO_PIN(2, 7, 0x18C, 0x5D0), + MX35_PIN_RTS1 = _MXC_BUILD_GPIO_PIN(2, 8, 0x190, 0x5D4), + MX35_PIN_CTS1 = _MXC_BUILD_GPIO_PIN(2, 9, 0x194, 0x5D8), + MX35_PIN_RXD2 = _MXC_BUILD_GPIO_PIN(2, 10, 0x198, 0x5DC), + MX35_PIN_TXD2 = _MXC_BUILD_GPIO_PIN(2, 11, 0x19C, 0x5E0), + MX35_PIN_RTS2 = _MXC_BUILD_GPIO_PIN(2, 12, 0x1A0, 0x5E4), + MX35_PIN_CTS2 = _MXC_BUILD_GPIO_PIN(2, 13, 0x1A4, 0x5E8), + + MX35_PIN_USBOTG_PWR = _MXC_BUILD_GPIO_PIN(2, 14, 0x1A8, 0x60C), + MX35_PIN_USBOTG_OC = _MXC_BUILD_GPIO_PIN(2, 15, 0x1AC, 0x610), + + MX35_PIN_LD0 = _MXC_BUILD_GPIO_PIN(1, 0, 0x1B0, 0x614), + MX35_PIN_LD1 = _MXC_BUILD_GPIO_PIN(1, 1, 0x1B4, 0x618), + MX35_PIN_LD2 = _MXC_BUILD_GPIO_PIN(1, 2, 0x1B8, 0x61C), + MX35_PIN_LD3 = _MXC_BUILD_GPIO_PIN(1, 3, 0x1BC, 0x620), + MX35_PIN_LD4 = _MXC_BUILD_GPIO_PIN(1, 4, 0x1C0, 0x624), + MX35_PIN_LD5 = _MXC_BUILD_GPIO_PIN(1, 5, 0x1C4, 0x628), + MX35_PIN_LD6 = _MXC_BUILD_GPIO_PIN(1, 6, 0x1C8, 0x62C), + MX35_PIN_LD7 = _MXC_BUILD_GPIO_PIN(1, 7, 0x1CC, 0x630), + MX35_PIN_LD8 = _MXC_BUILD_GPIO_PIN(1, 8, 0x1D0, 0x634), + MX35_PIN_LD9 = _MXC_BUILD_GPIO_PIN(1, 9, 0x1D4, 0x638), + MX35_PIN_LD10 = _MXC_BUILD_GPIO_PIN(1, 10, 0x1D8, 0x63C), + MX35_PIN_LD11 = _MXC_BUILD_GPIO_PIN(1, 11, 0x1DC, 0x640), + MX35_PIN_LD12 = _MXC_BUILD_GPIO_PIN(1, 12, 0x1E0, 0x644), + MX35_PIN_LD13 = _MXC_BUILD_GPIO_PIN(1, 13, 0x1E4, 0x648), + MX35_PIN_LD14 = _MXC_BUILD_GPIO_PIN(1, 14, 0x1E8, 0x64C), + MX35_PIN_LD15 = _MXC_BUILD_GPIO_PIN(1, 15, 0x1EC, 0x650), + MX35_PIN_LD16 = _MXC_BUILD_GPIO_PIN(1, 16, 0x1F0, 0x654), + MX35_PIN_LD17 = _MXC_BUILD_GPIO_PIN(1, 17, 0x1F4, 0x658), + MX35_PIN_LD18 = _MXC_BUILD_GPIO_PIN(2, 24, 0x1F8, 0x65C), + MX35_PIN_LD19 = _MXC_BUILD_GPIO_PIN(2, 25, 0x1FC, 0x660), + MX35_PIN_LD20 = _MXC_BUILD_GPIO_PIN(2, 26, 0x200, 0x664), + MX35_PIN_LD21 = _MXC_BUILD_GPIO_PIN(2, 27, 0x204, 0x668), + MX35_PIN_LD22 = _MXC_BUILD_GPIO_PIN(2, 28, 0x208, 0x66C), + MX35_PIN_LD23 = _MXC_BUILD_GPIO_PIN(2, 29, 0x20C, 0x670), + + MX35_PIN_D3_HSYNC = _MXC_BUILD_GPIO_PIN(2, 30, 0x210, 0x674), + MX35_PIN_D3_FPSHIFT = _MXC_BUILD_GPIO_PIN(2, 31, 0x214, 0x678), + MX35_PIN_D3_DRDY = _MXC_BUILD_GPIO_PIN(0, 0, 0x218, 0x67C), + MX35_PIN_CONTRAST = _MXC_BUILD_GPIO_PIN(0, 1, 0x21C, 0x680), + MX35_PIN_D3_VSYNC = _MXC_BUILD_GPIO_PIN(0, 2, 0x220, 0x684), + MX35_PIN_D3_REV = _MXC_BUILD_GPIO_PIN(0, 3, 0x224, 0x688), + MX35_PIN_D3_CLS = _MXC_BUILD_GPIO_PIN(0, 4, 0x228, 0x68C), + MX35_PIN_D3_SPL = _MXC_BUILD_GPIO_PIN(0, 5, 0x22C, 0x690), + + MX35_PIN_SD1_CMD = _MXC_BUILD_GPIO_PIN(0, 6, 0x230, 0x694), + MX35_PIN_SD1_CLK = _MXC_BUILD_GPIO_PIN(0, 7, 0x234, 0x698), + MX35_PIN_SD1_DATA0 = _MXC_BUILD_GPIO_PIN(0, 8, 0x238, 0x69C), + MX35_PIN_SD1_DATA1 = _MXC_BUILD_GPIO_PIN(0, 9, 0x23C, 0x6A0), + MX35_PIN_SD1_DATA2 = _MXC_BUILD_GPIO_PIN(0, 10, 0x240, 0x6A4), + MX35_PIN_SD1_DATA3 = _MXC_BUILD_GPIO_PIN(0, 11, 0x244, 0x6A8), + MX35_PIN_SD2_CMD = _MXC_BUILD_GPIO_PIN(1, 0, 0x248, 0x6AC), + MX35_PIN_SD2_CLK = _MXC_BUILD_GPIO_PIN(1, 1, 0x24C, 0x6B0), + MX35_PIN_SD2_DATA0 = _MXC_BUILD_GPIO_PIN(1, 2, 0x250, 0x6B4), + MX35_PIN_SD2_DATA1 = _MXC_BUILD_GPIO_PIN(1, 3, 0x254, 0x6B8), + MX35_PIN_SD2_DATA2 = _MXC_BUILD_GPIO_PIN(1, 4, 0x258, 0x6BC), + MX35_PIN_SD2_DATA3 = _MXC_BUILD_GPIO_PIN(1, 5, 0x25C, 0x6C0), + + MX35_PIN_ATA_CS0 = _MXC_BUILD_GPIO_PIN(1, 6, 0x260, 0x6C4), + MX35_PIN_ATA_CS1 = _MXC_BUILD_GPIO_PIN(1, 7, 0x264, 0x6C8), + MX35_PIN_ATA_DIOR = _MXC_BUILD_GPIO_PIN(1, 8, 0x268, 0x6CC), + MX35_PIN_ATA_DIOW = _MXC_BUILD_GPIO_PIN(1, 9, 0x26C, 0x6D0), + MX35_PIN_ATA_DMACK = _MXC_BUILD_GPIO_PIN(1, 10, 0x270, 0x6D4), + MX35_PIN_ATA_RESET_B = _MXC_BUILD_GPIO_PIN(1, 11, 0x274, 0x6D8), + MX35_PIN_ATA_IORDY = _MXC_BUILD_GPIO_PIN(1, 12, 0x278, 0x6DC), + MX35_PIN_ATA_DATA0 = _MXC_BUILD_GPIO_PIN(1, 13, 0x27C, 0x6E0), + MX35_PIN_ATA_DATA1 = _MXC_BUILD_GPIO_PIN(1, 14, 0x280, 0x6E4), + MX35_PIN_ATA_DATA2 = _MXC_BUILD_GPIO_PIN(1, 15, 0x284, 0x6E8), + MX35_PIN_ATA_DATA3 = _MXC_BUILD_GPIO_PIN(1, 16, 0x288, 0x6EC), + MX35_PIN_ATA_DATA4 = _MXC_BUILD_GPIO_PIN(1, 17, 0x28C, 0x6F0), + MX35_PIN_ATA_DATA5 = _MXC_BUILD_GPIO_PIN(1, 18, 0x290, 0x6F4), + MX35_PIN_ATA_DATA6 = _MXC_BUILD_GPIO_PIN(1, 19, 0x294, 0x6F8), + MX35_PIN_ATA_DATA7 = _MXC_BUILD_GPIO_PIN(1, 20, 0x298, 0x6FC), + MX35_PIN_ATA_DATA8 = _MXC_BUILD_GPIO_PIN(1, 21, 0x29C, 0x700), + MX35_PIN_ATA_DATA9 = _MXC_BUILD_GPIO_PIN(1, 22, 0x2A0, 0x704), + MX35_PIN_ATA_DATA10 = _MXC_BUILD_GPIO_PIN(1, 23, 0x2A4, 0x708), + MX35_PIN_ATA_DATA11 = _MXC_BUILD_GPIO_PIN(1, 24, 0x2A8, 0x70C), + MX35_PIN_ATA_DATA12 = _MXC_BUILD_GPIO_PIN(1, 25, 0x2AC, 0x710), + MX35_PIN_ATA_DATA13 = _MXC_BUILD_GPIO_PIN(1, 26, 0x2B0, 0x714), + MX35_PIN_ATA_DATA14 = _MXC_BUILD_GPIO_PIN(1, 27, 0x2B4, 0x718), + MX35_PIN_ATA_DATA15 = _MXC_BUILD_GPIO_PIN(1, 28, 0x2B8, 0x71C), + MX35_PIN_ATA_INTRQ = _MXC_BUILD_GPIO_PIN(1, 29, 0x2BC, 0x720), + MX35_PIN_ATA_BUFF_EN = _MXC_BUILD_GPIO_PIN(1, 30, 0x2C0, 0x724), + MX35_PIN_ATA_DMARQ = _MXC_BUILD_GPIO_PIN(1, 31, 0x2C4, 0x728), + MX35_PIN_ATA_DA0 = _MXC_BUILD_GPIO_PIN(2, 0, 0x2C8, 0x72C), + MX35_PIN_ATA_DA1 = _MXC_BUILD_GPIO_PIN(2, 1, 0x2CC, 0x730), + MX35_PIN_ATA_DA2 = _MXC_BUILD_GPIO_PIN(2, 2, 0x2D0, 0x734), + + MX35_PIN_MLB_CLK = _MXC_BUILD_GPIO_PIN(2, 3, 0x2D4, 0x738), + MX35_PIN_MLB_DAT = _MXC_BUILD_GPIO_PIN(2, 4, 0x2D8, 0x73C), + MX35_PIN_MLB_SIG = _MXC_BUILD_GPIO_PIN(2, 5, 0x2DC, 0x740), + + MX35_PIN_FEC_TX_CLK = _MXC_BUILD_GPIO_PIN(2, 6, 0x2E0, 0x744), + MX35_PIN_FEC_RX_CLK = _MXC_BUILD_GPIO_PIN(2, 7, 0x2E4, 0x748), + MX35_PIN_FEC_RX_DV = _MXC_BUILD_GPIO_PIN(2, 8, 0x2E8, 0x74C), + MX35_PIN_FEC_COL = _MXC_BUILD_GPIO_PIN(2, 9, 0x2EC, 0x750), + MX35_PIN_FEC_RDATA0 = _MXC_BUILD_GPIO_PIN(2, 10, 0x2F0, 0x754), + MX35_PIN_FEC_TDATA0 = _MXC_BUILD_GPIO_PIN(2, 11, 0x2F4, 0x758), + MX35_PIN_FEC_TX_EN = _MXC_BUILD_GPIO_PIN(2, 12, 0x2F8, 0x75C), + MX35_PIN_FEC_MDC = _MXC_BUILD_GPIO_PIN(2, 13, 0x2FC, 0x760), + MX35_PIN_FEC_MDIO = _MXC_BUILD_GPIO_PIN(2, 14, 0x300, 0x764), + MX35_PIN_FEC_TX_ERR = _MXC_BUILD_GPIO_PIN(2, 15, 0x304, 0x768), + MX35_PIN_FEC_RX_ERR = _MXC_BUILD_GPIO_PIN(2, 16, 0x308, 0x76C), + MX35_PIN_FEC_CRS = _MXC_BUILD_GPIO_PIN(2, 17, 0x30C, 0x770), + MX35_PIN_FEC_RDATA1 = _MXC_BUILD_GPIO_PIN(2, 18, 0x310, 0x774), + MX35_PIN_FEC_TDATA1 = _MXC_BUILD_GPIO_PIN(2, 19, 0x314, 0x778), + MX35_PIN_FEC_RDATA2 = _MXC_BUILD_GPIO_PIN(2, 20, 0x318, 0x77C), + MX35_PIN_FEC_TDATA2 = _MXC_BUILD_GPIO_PIN(2, 21, 0x31C, 0x780), + MX35_PIN_FEC_RDATA3 = _MXC_BUILD_GPIO_PIN(2, 22, 0x320, 0x784), + MX35_PIN_FEC_TDATA3 = _MXC_BUILD_GPIO_PIN(2, 23, 0x324, 0x788), +}; + +#endif +#endif diff --git a/arch/arm/mach-mx35/pm.c b/arch/arm/mach-mx35/pm.c new file mode 100644 index 000000000000..6a7c8c8a0844 --- /dev/null +++ b/arch/arm/mach-mx35/pm.c @@ -0,0 +1,80 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/suspend.h> +#include <mach/hardware.h> + +/*! + * @defgroup MSL_MX35 i.MX35 Machine Specific Layer (MSL) + */ + +/*! + * @file mach-mx35/pm.c + * @brief This file contains suspend operations + * + * @ingroup MSL_MX35 + */ +static int mx35_suspend_enter(suspend_state_t state) +{ + switch (state) { + case PM_SUSPEND_MEM: + mxc_cpu_lp_set(STOP_POWER_OFF); + break; + case PM_SUSPEND_STANDBY: + mxc_cpu_lp_set(STOP_POWER_ON); + break; + default: + return -EINVAL; + } + /* Executing CP15 (Wait-for-Interrupt) Instruction */ + cpu_do_idle(); + return 0; +} + +/* + * Called after processes are frozen, but before we shut down devices. + */ +static int mx35_suspend_prepare(void) +{ + return 0; +} + +/* + * Called after devices are re-setup, but before processes are thawed. + */ +static void mx35_suspend_finish(void) +{ +} + +static int mx35_pm_valid(suspend_state_t state) +{ + return (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX); +} + +struct platform_suspend_ops mx35_suspend_ops = { + .valid = mx35_pm_valid, + .prepare = mx35_suspend_prepare, + .enter = mx35_suspend_enter, + .finish = mx35_suspend_finish, +}; + +static int __init mx35_pm_init(void) +{ + pr_info("Static Power Management for Freescale i.MX35\n"); + suspend_set_ops(&mx35_suspend_ops); + + return 0; +} + +late_initcall(mx35_pm_init); diff --git a/arch/arm/mach-mx35/sdma_script_code.h b/arch/arm/mach-mx35/sdma_script_code.h new file mode 100644 index 000000000000..e1b6b59bc31e --- /dev/null +++ b/arch/arm/mach-mx35/sdma_script_code.h @@ -0,0 +1,254 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All 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 */ + +/*! + * @file sdma_script_code.h + * @brief This file contains functions of SDMA scripts code initialization + * + * The file was generated automatically. Based on sdma scripts library. + * + * @ingroup SDMA + */ +/******************************************************************************* + + SDMA RELEASE LABEL: "SS15_RINGO" + +*******************************************************************************/ + +#ifndef __SDMA_SCRIPT_CODE_H__ +#define __SDMA_SCRIPT_CODE_H__ + +/*! +* SDMA ROM scripts start addresses and sizes +*/ + +#define start_ADDR 0 +#define start_SIZE 22 + +#define core_ADDR 80 +#define core_SIZE 232 + +#define common_ADDR 312 +#define common_SIZE 330 + +#define ap_2_ap_ADDR 642 +#define ap_2_ap_SIZE 41 + +#define app_2_mcu_ADDR 683 +#define app_2_mcu_SIZE 64 + +#define mcu_2_app_ADDR 747 +#define mcu_2_app_SIZE 70 + +#define uart_2_mcu_ADDR 817 +#define uart_2_mcu_SIZE 75 + +#define shp_2_mcu_ADDR 892 +#define shp_2_mcu_SIZE 69 + +#define mcu_2_shp_ADDR 961 +#define mcu_2_shp_SIZE 72 + +#define per_2_shp_ADDR 1033 +#define per_2_shp_SIZE 78 + +#define shp_2_per_ADDR 1111 +#define shp_2_per_SIZE 72 + +#define uartsh_2_mcu_ADDR 1183 +#define uartsh_2_mcu_SIZE 69 + +#define mcu_2_ata_ADDR 1252 +#define mcu_2_ata_SIZE 81 + +#define ata_2_mcu_ADDR 1333 +#define ata_2_mcu_SIZE 96 + +#define loop_DMAs_routines_ADDR 1429 +#define loop_DMAs_routines_SIZE 227 + +#define test_ADDR 1656 +#define test_SIZE 63 + +#define signature_ADDR 1023 +#define signature_SIZE 1 + +/*! +* SDMA RAM scripts start addresses and sizes +*/ + +#define app_2_per_ADDR 6144 +#define app_2_per_SIZE 66 + +#define asrc__mcu_ADDR 6210 +#define asrc__mcu_SIZE 114 + +#define ext_mem__ipu_ram_ADDR 6324 +#define ext_mem__ipu_ram_SIZE 123 + +#define mcu_2_spdif_ADDR 6447 +#define mcu_2_spdif_SIZE 103 + +#define p_2_p_ADDR 6550 +#define p_2_p_SIZE 254 + +#define per_2_app_ADDR 6804 +#define per_2_app_SIZE 74 + +#define spdif_2_mcu_ADDR 6878 +#define spdif_2_mcu_SIZE 47 + +#define uart_2_per_ADDR 6925 +#define uart_2_per_SIZE 73 + +#define uartsh_2_per_ADDR 6998 +#define uartsh_2_per_SIZE 67 + +/*! +* SDMA RAM image start address and size +*/ + +#define RAM_CODE_START_ADDR 6144 +#define RAM_CODE_SIZE 921 + +/*! +* Buffer that holds the SDMA RAM image +*/ +__attribute__ ((__aligned__(4))) +#ifndef CONFIG_XIP_KERNEL +const +#endif +static const short sdma_code[] = { + 0xc1e3, 0x57db, 0x52fb, 0x6ac3, 0x52f3, 0x6ad7, 0x008f, 0x00d5, + 0x7d01, 0x008d, 0x05a0, 0x0478, 0x7d03, 0x0479, 0x7d1c, 0x7c21, + 0x0479, 0x7c14, 0x6ddd, 0x56ee, 0x62c8, 0x7e28, 0x0660, 0x7d02, + 0x0210, 0x0212, 0x6ac8, 0x7f22, 0x0212, 0x6ac8, 0x7f1f, 0x0212, + 0x6ac8, 0x7f1c, 0x2003, 0x4800, 0x7cef, 0x9836, 0x6ddd, 0x7802, + 0x62c8, 0x6ac8, 0x9835, 0x6dde, 0x0015, 0x7802, 0x62c8, 0x6ac8, + 0x9835, 0x0015, 0x0015, 0x7801, 0x62d8, 0x7c08, 0x6ddf, 0x7f06, + 0x0000, 0x4d00, 0x7d05, 0xc1fa, 0x57db, 0x9806, 0xc273, 0x0454, + 0xc20a, 0x9801, 0xc1d9, 0xc1e3, 0x56f3, 0x57db, 0x047a, 0x7d07, + 0x072f, 0x076e, 0x7d02, 0x6ec7, 0x9855, 0x6ed7, 0x9855, 0x074f, + 0x076e, 0x7d02, 0x6e01, 0x9855, 0x6e05, 0x5ce3, 0x048f, 0x0410, + 0x3c0f, 0x5c93, 0x0eff, 0x06bf, 0x06d5, 0x7d01, 0x068d, 0x05a6, + 0x5deb, 0x55fb, 0x008e, 0x0768, 0x7d02, 0x0769, 0x7c04, 0x06d4, + 0x7d01, 0x008c, 0x04a0, 0x06a0, 0x076f, 0x7d0c, 0x076e, 0x7d05, + 0x7802, 0x62c8, 0x5a05, 0x7c2b, 0x9887, 0x7802, 0x5205, 0x6ac8, + 0x7c26, 0x9887, 0x076e, 0x7d05, 0x7802, 0x620b, 0x5a05, 0x7c21, + 0x9887, 0x7802, 0x5205, 0x6a0b, 0x7c1c, 0x6a28, 0x7f1a, 0x0768, + 0x7d02, 0x0769, 0x7c0a, 0x4c00, 0x7c08, 0x0768, 0x7d03, 0x5a05, + 0x7f11, 0x9894, 0x5205, 0x7e0e, 0x5493, 0x4e00, 0x7ccb, 0x0000, + 0x54e3, 0x55eb, 0x4d00, 0x7d0a, 0xc1fa, 0x57db, 0x9856, 0x68cc, + 0x98a2, 0x680c, 0x009e, 0x0007, 0x54e3, 0xd8a8, 0xc20a, 0x9844, + 0x55eb, 0x009d, 0x058c, 0x0aff, 0x0211, 0x1aff, 0x05ba, 0x05a0, + 0x04b2, 0x04ad, 0x0454, 0x0006, 0x0e70, 0x0611, 0x5616, 0xc13c, + 0x7d2a, 0x5ade, 0x008e, 0xc14e, 0x7c26, 0x5be0, 0x5ef0, 0x5ce8, + 0x0688, 0x08ff, 0x0011, 0x28ff, 0x00bc, 0x53f6, 0x05df, 0x7d0b, + 0x6dc5, 0x03df, 0x7d03, 0x6bd5, 0xd903, 0x98df, 0x6b05, 0xc5f5, + 0x7e27, 0x7f29, 0x98df, 0x6d01, 0x03df, 0x7d05, 0x6bd5, 0xc61f, + 0x7e18, 0x7f1a, 0x98df, 0x6b05, 0xc595, 0x7e07, 0x7f06, 0x52de, + 0x53e6, 0xc159, 0x7dd7, 0x0200, 0x98b7, 0x0007, 0x6004, 0x680c, + 0x53f6, 0x028e, 0x00a3, 0xc256, 0x048b, 0x0498, 0x0454, 0x068a, + 0x98df, 0x0207, 0x680c, 0x6ddf, 0x0107, 0x68ff, 0x60d0, 0x98e8, + 0x0207, 0x68ff, 0x6d28, 0x0107, 0x6004, 0x680c, 0x98e8, 0x0007, + 0x68ff, 0x60d0, 0x98e8, 0x0288, 0x03a5, 0x3b03, 0x3d03, 0x4d00, + 0x7d0a, 0x0804, 0x00a5, 0x00da, 0x7d1a, 0x02a0, 0x7b01, 0x65d8, + 0x7eee, 0x65ff, 0x7eec, 0x0804, 0x02d0, 0x7d11, 0x4b00, 0x7c0f, + 0x008a, 0x3003, 0x6dcf, 0x6bdf, 0x0015, 0x0015, 0x7b02, 0x65d8, + 0x0000, 0x7edd, 0x63ff, 0x7edb, 0x3a03, 0x6dcd, 0x6bdd, 0x008a, + 0x7b02, 0x65d8, 0x0000, 0x7ed3, 0x65ff, 0x7ed1, 0x0006, 0xc1d9, + 0xc1e3, 0x57db, 0x52f3, 0x047a, 0x7d06, 0x0479, 0x7c02, 0x6ac6, + 0x993c, 0x6ac7, 0x993c, 0x6a01, 0x008f, 0x00d5, 0x7d01, 0x008d, + 0x05a0, 0x5deb, 0x56fb, 0x0478, 0x7d4e, 0x0479, 0x7c1f, 0x0015, + 0x0388, 0x047a, 0x7d03, 0x62c8, 0x7e39, 0x9950, 0x620a, 0x7e38, + 0x0808, 0x7801, 0x0217, 0x5a06, 0x7f34, 0x2301, 0x047a, 0x7d03, + 0x62c8, 0x7e2c, 0x995d, 0x620a, 0x7e2b, 0x0808, 0x7801, 0x0217, + 0x5a26, 0x7f27, 0x2301, 0x4b00, 0x7ce4, 0x997c, 0x0015, 0x0015, + 0x0015, 0x047a, 0x7d09, 0x7806, 0x0b00, 0x62c8, 0x5a06, 0x0b01, + 0x62c8, 0x5a26, 0x7c13, 0x997c, 0x7806, 0x0b00, 0x620b, 0x5a06, + 0x0b01, 0x620b, 0x5a26, 0x7c0c, 0x0b70, 0x0311, 0x5313, 0x0000, + 0x55eb, 0x4d00, 0x7d11, 0xc1fa, 0x57db, 0x993c, 0x68cc, 0x9989, + 0x680c, 0x0007, 0x0479, 0x7c02, 0x008b, 0x9990, 0x0017, 0x00a3, + 0x0b70, 0x0311, 0x5313, 0xc213, 0xc20a, 0x9931, 0x0b70, 0x0311, + 0x5313, 0x076c, 0x7c01, 0xc1d9, 0x5efb, 0x068a, 0x076b, 0x7c01, + 0xc1d9, 0x5ef3, 0x59db, 0x58d3, 0x018f, 0x0110, 0x390f, 0x008b, + 0xc13c, 0x7d2b, 0x5ac0, 0x5bc8, 0xc14e, 0x7c27, 0x0388, 0x0689, + 0x5ce3, 0x0dff, 0x0511, 0x1dff, 0x05bc, 0x073e, 0x4d00, 0x7d18, + 0x0870, 0x0011, 0x077e, 0x7d09, 0x077d, 0x7d02, 0x5228, 0x99c1, + 0x52f8, 0x54db, 0x02bc, 0x02cc, 0x7c09, 0x077c, 0x7d02, 0x5228, + 0x99ca, 0x52f8, 0x54d3, 0x02bc, 0x02cc, 0x7d09, 0x0400, 0x99b8, + 0x008b, 0x52c0, 0x53c8, 0xc159, 0x7dd6, 0x0200, 0x99a8, 0x08ff, + 0x00bf, 0x077f, 0x7d15, 0x0488, 0x00d5, 0x7d01, 0x008d, 0x05a0, + 0x5deb, 0x028f, 0x0212, 0x0212, 0x3aff, 0x05da, 0x7c02, 0x073e, + 0x99f3, 0x02a4, 0x02dd, 0x7d02, 0x073e, 0x99f3, 0x075e, 0x99f3, + 0x55eb, 0x0598, 0x5deb, 0x52f3, 0x54fb, 0x076a, 0x7d26, 0x076c, + 0x7d01, 0x9a30, 0x076b, 0x7c57, 0x0769, 0x7d04, 0x0768, 0x7d02, + 0x0e01, 0x9a0a, 0x5893, 0x00d6, 0x7d01, 0x008e, 0x5593, 0x05a0, + 0x5d93, 0x06a0, 0x7802, 0x5502, 0x5d04, 0x7c1d, 0x4e00, 0x7c08, + 0x0769, 0x7d03, 0x5502, 0x7e17, 0x9a17, 0x5d04, 0x7f14, 0x0689, + 0x5093, 0x4800, 0x7d01, 0x9a02, 0x9a7b, 0x0015, 0x7806, 0x5502, + 0x5d04, 0x074f, 0x5502, 0x5d24, 0x072f, 0x7c01, 0x9a7b, 0x0017, + 0x076f, 0x7c01, 0x2001, 0x5593, 0x009d, 0x0007, 0xda82, 0x99d0, + 0x6cd3, 0x0769, 0x7d04, 0x0768, 0x7d02, 0x0e01, 0x9a3f, 0x5893, + 0x00d6, 0x7d01, 0x008e, 0x5593, 0x05a0, 0x5d93, 0x06a0, 0x7802, + 0x5502, 0x6dc8, 0x7c0f, 0x4e00, 0x7c08, 0x0769, 0x7d03, 0x5502, + 0x7e09, 0x9a4c, 0x6dc8, 0x7f06, 0x0689, 0x5093, 0x4800, 0x7d01, + 0x9a37, 0x9a7b, 0x9a75, 0x6ac3, 0x0769, 0x7d04, 0x0768, 0x7d02, + 0x0e01, 0x9a62, 0x5893, 0x00d6, 0x7d01, 0x008e, 0x5593, 0x05a0, + 0x5d93, 0x06a0, 0x7802, 0x65c8, 0x5d04, 0x7c0f, 0x4e00, 0x7c08, + 0x0769, 0x7d03, 0x65c8, 0x7e09, 0x9a6f, 0x5d04, 0x7f06, 0x0689, + 0x5093, 0x4800, 0x7d01, 0x9a5a, 0x9a7b, 0x5593, 0x009d, 0x0007, + 0x6cff, 0xda82, 0x99d0, 0x0000, 0x54e3, 0x55eb, 0x4d00, 0x7c01, + 0x99d0, 0x99b8, 0x54e3, 0x55eb, 0x0aff, 0x0211, 0x1aff, 0x077f, + 0x7c02, 0x05a0, 0x9a8f, 0x009d, 0x058c, 0x05ba, 0x05a0, 0x0210, + 0x04ba, 0x04ad, 0x0454, 0x0006, 0xc1e3, 0x57db, 0x52f3, 0x6ac5, + 0x52fb, 0x6ad3, 0x008f, 0x00d5, 0x7d01, 0x008d, 0x05a0, 0x5deb, + 0x0478, 0x7d03, 0x0479, 0x7d20, 0x7c25, 0x0479, 0x7c19, 0x59e3, + 0x56ee, 0x61c8, 0x7e2e, 0x62c8, 0x7e2c, 0x65c8, 0x7e2a, 0x0660, + 0x7d03, 0x0112, 0x0112, 0x9ab6, 0x0512, 0x0512, 0x0211, 0x02a9, + 0x02ad, 0x6ac8, 0x7f1e, 0x2003, 0x4800, 0x7ceb, 0x51e3, 0x9ad0, + 0x7802, 0x62c8, 0x6ac8, 0x9acf, 0x6dce, 0x0015, 0x7802, 0x62c8, + 0x6ac8, 0x9acf, 0x6dcf, 0x0015, 0x0015, 0x7801, 0x62d8, 0x7c09, + 0x6ddf, 0x7f07, 0x0000, 0x55eb, 0x4d00, 0x7d06, 0xc1fa, 0x57db, + 0x9a9a, 0x0007, 0x68ff, 0xc213, 0xc20a, 0x9a95, 0xc1d9, 0xc1e3, + 0x57db, 0x52f3, 0x047a, 0x7d02, 0x6ad7, 0x9ae7, 0x6a05, 0x008f, + 0x00d5, 0x7d01, 0x008d, 0x05a0, 0x56fb, 0x0015, 0x0015, 0x0015, + 0x047a, 0x7d07, 0x7804, 0x5206, 0x6ac8, 0x5226, 0x6ac8, 0x7c0f, + 0x9b01, 0x7804, 0x5206, 0x6a0b, 0x5226, 0x6a0b, 0x7c0a, 0x6a28, + 0x7f08, 0x0000, 0x4d00, 0x7d07, 0xc1fa, 0x57db, 0x9ae7, 0xc273, + 0x9b0a, 0xc277, 0x0454, 0xc20a, 0x9ae0, 0xc1e3, 0x57db, 0x52f3, + 0x6ad5, 0x56fb, 0x028e, 0x1a94, 0x6ac3, 0x62c8, 0x0269, 0x7d1e, + 0x1e94, 0x6ee3, 0x62d0, 0x5aeb, 0x62c8, 0x0248, 0x6ed3, 0x6ac8, + 0x2694, 0x52eb, 0x6ad5, 0x6ee3, 0x62c8, 0x026e, 0x7d27, 0x6ac8, + 0x7f23, 0x2501, 0x4d00, 0x7d26, 0x028e, 0x1a98, 0x6ac3, 0x62c8, + 0x6ec3, 0x0260, 0x7df1, 0x62d0, 0xc27a, 0x9b52, 0x6ee3, 0x008f, + 0x2001, 0x00d5, 0x7d01, 0x008d, 0x05a0, 0x62c8, 0x026e, 0x7d0e, + 0x6ac8, 0x7f0a, 0x2001, 0x7cf9, 0x6add, 0x7f06, 0x0000, 0x4d00, + 0x7d09, 0xc1fa, 0x57db, 0x9b11, 0x0007, 0x6aff, 0x62d0, 0xc27a, + 0x0458, 0x0454, 0x6add, 0x7ff8, 0xc20a, 0x9b0e, 0xc1d9, 0xc1e3, + 0x57db, 0x52f3, 0x6ad5, 0x56fb, 0x028e, 0x1a94, 0x5202, 0x0269, + 0x7d17, 0x1e94, 0x5206, 0x0248, 0x5a06, 0x2694, 0x5206, 0x026e, + 0x7d26, 0x6ac8, 0x7f22, 0x2501, 0x4d00, 0x7d27, 0x028e, 0x1a98, + 0x5202, 0x0260, 0x7df3, 0x6add, 0x7f18, 0x62d0, 0xc27a, 0x9b95, + 0x008f, 0x2001, 0x00d5, 0x7d01, 0x008d, 0x05a0, 0x5206, 0x026e, + 0x7d0e, 0x6ac8, 0x7f0a, 0x2001, 0x7cf9, 0x6add, 0x7f06, 0x0000, + 0x4d00, 0x7d0b, 0xc1fa, 0x57db, 0x9b5b, 0x0007, 0x6aff, 0x6add, + 0x7ffc, 0x62d0, 0xc27a, 0x0458, 0x0454, 0x6add, 0x7ff6, 0xc20a, + 0x9b58 +}; +#endif diff --git a/arch/arm/mach-mx35/sdma_script_code_v2.h b/arch/arm/mach-mx35/sdma_script_code_v2.h new file mode 100644 index 000000000000..1bd949e6c992 --- /dev/null +++ b/arch/arm/mach-mx35/sdma_script_code_v2.h @@ -0,0 +1,234 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All 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 */ + +/*! + * @file sdma_script_code.h + * @brief This file contains functions of SDMA scripts code initialization + * + * The file was generated automatically. Based on sdma scripts library. + * + * @ingroup SDMA + */ +/******************************************************************************* + + SDMA RELEASE LABEL: "SDMA_RINGO.03.00.00" + +*******************************************************************************/ + +#ifndef SDMA_SCRIPT_CODE_V2_H +#define SDMA_SCRIPT_CODE_V2_H + +/*! +* SDMA ROM scripts start addresses and sizes +*/ + +#define start_ADDR_V2 0 +#define start_SIZE_V2 24 + +#define core_ADDR_V2 80 +#define core_SIZE_V2 233 + +#define common_ADDR_V2 313 +#define common_SIZE_V2 416 + +#define ap_2_ap_ADDR_V2 729 +#define ap_2_ap_SIZE_V2 41 + +#define app_2_mcu_ADDR_V2 770 +#define app_2_mcu_SIZE_V2 64 + +#define mcu_2_app_ADDR_V2 834 +#define mcu_2_app_SIZE_V2 70 + +#define uart_2_mcu_ADDR_V2 904 +#define uart_2_mcu_SIZE_V2 75 + +#define shp_2_mcu_ADDR_V2 979 +#define shp_2_mcu_SIZE_V2 69 + +#define mcu_2_shp_ADDR_V2 1048 +#define mcu_2_shp_SIZE_V2 72 + +#define per_2_shp_ADDR_V2 1120 +#define per_2_shp_SIZE_V2 78 + +#define shp_2_per_ADDR_V2 1198 +#define shp_2_per_SIZE_V2 72 + +#define uartsh_2_mcu_ADDR_V2 1270 +#define uartsh_2_mcu_SIZE_V2 69 + +#define mcu_2_ata_ADDR_V2 1339 +#define mcu_2_ata_SIZE_V2 90 + +#define ata_2_mcu_ADDR_V2 1429 +#define ata_2_mcu_SIZE_V2 102 + +#define app_2_per_ADDR_V2 1531 +#define app_2_per_SIZE_V2 66 + +#define per_2_app_ADDR_V2 1597 +#define per_2_app_SIZE_V2 74 + +#define loop_DMAs_routines_ADDR_V2 1671 +#define loop_DMAs_routines_SIZE_V2 240 + +#define test_ADDR_V2 1911 +#define test_SIZE_V2 63 + +#define signature_ADDR_V2 1022 +#define signature_SIZE_V2 1 + +/*! +* SDMA RAM scripts start addresses and sizes +*/ + +#define asrc__mcu_ADDR_V2 6144 +#define asrc__mcu_SIZE_V2 116 + +#define ext_mem__ipu_ram_ADDR_V2 6260 +#define ext_mem__ipu_ram_SIZE_V2 123 + +#define mcu_2_spdif_ADDR_V2 6383 +#define mcu_2_spdif_SIZE_V2 103 + +#define p_2_p_ADDR_V2 6486 +#define p_2_p_SIZE_V2 260 + +#define spdif_2_mcu_ADDR_V2 6746 +#define spdif_2_mcu_SIZE_V2 47 + +#define uart_2_per_ADDR_V2 6793 +#define uart_2_per_SIZE_V2 73 + +#define uartsh_2_per_ADDR_V2 6866 +#define uartsh_2_per_SIZE_V2 67 + +/*! +* SDMA RAM image start address and size +*/ + +#define RAM_CODE_START_ADDR_V2 6144 +#define RAM_CODE_SIZE_V2 789 + +/*! +* Buffer that holds the SDMA RAM image +*/ + +static const short sdma_code_v2[] = { + 0xc230, 0xc23a, 0x56f3, 0x57db, 0x047a, 0x7d07, 0x072f, 0x076e, + 0x7d02, 0x6ec7, 0x9813, 0x6ed7, 0x9813, 0x074f, 0x076e, 0x7d02, + 0x6e01, 0x9813, 0x6e05, 0x5ce3, 0x048f, 0x0410, 0x3c0f, 0x5c93, + 0x0e03, 0x0611, 0x1eff, 0x06bf, 0x06d5, 0x7d01, 0x068d, 0x05a6, + 0x5deb, 0x55fb, 0x008e, 0x076a, 0x7d02, 0x076b, 0x7c04, 0x06d4, + 0x7d01, 0x008c, 0x04a0, 0x06a0, 0x076f, 0x7d0c, 0x076e, 0x7d05, + 0x7802, 0x62c8, 0x5a05, 0x7c2b, 0x9847, 0x7802, 0x5205, 0x6ac8, + 0x7c26, 0x9847, 0x076e, 0x7d05, 0x7802, 0x620b, 0x5a05, 0x7c21, + 0x9847, 0x7802, 0x5205, 0x6a0b, 0x7c1c, 0x6a28, 0x7f1a, 0x076a, + 0x7d02, 0x076b, 0x7c0a, 0x4c00, 0x7c08, 0x076a, 0x7d03, 0x5a05, + 0x7f11, 0x9854, 0x5205, 0x7e0e, 0x5493, 0x4e00, 0x7ccb, 0x0000, + 0x54e3, 0x55eb, 0x4d00, 0x7d0a, 0xc251, 0x57db, 0x9814, 0x68cc, + 0x9862, 0x680c, 0x009e, 0x0007, 0x54e3, 0xd868, 0xc261, 0x9802, + 0x55eb, 0x009d, 0x058c, 0x0aff, 0x0211, 0x1aff, 0x05ba, 0x05a0, + 0x04b2, 0x04ad, 0x0454, 0x0006, 0x0e70, 0x0611, 0x5616, 0xc18a, + 0x7d2a, 0x5ade, 0x008e, 0xc19c, 0x7c26, 0x5be0, 0x5ef0, 0x5ce8, + 0x0688, 0x08ff, 0x0011, 0x28ff, 0x00bc, 0x53f6, 0x05df, 0x7d0b, + 0x6dc5, 0x03df, 0x7d03, 0x6bd5, 0xd8c3, 0x989f, 0x6b05, 0xc6e7, + 0x7e27, 0x7f29, 0x989f, 0x6d01, 0x03df, 0x7d05, 0x6bd5, 0xc711, + 0x7e18, 0x7f1a, 0x989f, 0x6b05, 0xc687, 0x7e07, 0x7f06, 0x52de, + 0x53e6, 0xc1a8, 0x7dd7, 0x0200, 0x9877, 0x0007, 0x6004, 0x680c, + 0x53f6, 0x028e, 0x00a3, 0xc2ad, 0x048b, 0x0498, 0x0454, 0x068a, + 0x989f, 0x0207, 0x680c, 0x6ddf, 0x0107, 0x68ff, 0x60d0, 0x98a8, + 0x0207, 0x68ff, 0x6d28, 0x0107, 0x6004, 0x680c, 0x98a8, 0x0007, + 0x68ff, 0x60d0, 0x98a8, 0x0288, 0x03a5, 0x3b03, 0x3d03, 0x4d00, + 0x7d0a, 0x0804, 0x00a5, 0x00da, 0x7d1a, 0x02a0, 0x7b01, 0x65d8, + 0x7eee, 0x65ff, 0x7eec, 0x0804, 0x02d0, 0x7d11, 0x4b00, 0x7c0f, + 0x008a, 0x3003, 0x6dcf, 0x6bdf, 0x0015, 0x0015, 0x7b02, 0x65d8, + 0x0000, 0x7edd, 0x63ff, 0x7edb, 0x3a03, 0x6dcd, 0x6bdd, 0x008a, + 0x7b02, 0x65d8, 0x0000, 0x7ed3, 0x65ff, 0x7ed1, 0x0006, 0xc230, + 0xc23a, 0x57db, 0x52f3, 0x047a, 0x7d06, 0x0479, 0x7c02, 0x6ac6, + 0x98fc, 0x6ac7, 0x98fc, 0x6a01, 0x008f, 0x00d5, 0x7d01, 0x008d, + 0x05a0, 0x5deb, 0x56fb, 0x0478, 0x7d4e, 0x0479, 0x7c1f, 0x0015, + 0x0388, 0x047a, 0x7d03, 0x62c8, 0x7e39, 0x9910, 0x620a, 0x7e38, + 0x0808, 0x7801, 0x0217, 0x5a06, 0x7f34, 0x2301, 0x047a, 0x7d03, + 0x62c8, 0x7e2c, 0x991d, 0x620a, 0x7e2b, 0x0808, 0x7801, 0x0217, + 0x5a26, 0x7f27, 0x2301, 0x4b00, 0x7ce4, 0x993c, 0x0015, 0x0015, + 0x0015, 0x047a, 0x7d09, 0x7806, 0x0b00, 0x62c8, 0x5a06, 0x0b01, + 0x62c8, 0x5a26, 0x7c13, 0x993c, 0x7806, 0x0b00, 0x620b, 0x5a06, + 0x0b01, 0x620b, 0x5a26, 0x7c0c, 0x0b70, 0x0311, 0x5313, 0x0000, + 0x55eb, 0x4d00, 0x7d11, 0xc251, 0x57db, 0x98fc, 0x68cc, 0x9949, + 0x680c, 0x0007, 0x0479, 0x7c02, 0x008b, 0x9950, 0x0017, 0x00a3, + 0x0b70, 0x0311, 0x5313, 0xc26a, 0xc261, 0x98f1, 0x0b70, 0x0311, + 0x5313, 0x076c, 0x7c01, 0xc230, 0x5efb, 0x068a, 0x076b, 0x7c01, + 0xc230, 0x5ef3, 0x59db, 0x58d3, 0x018f, 0x0110, 0x390f, 0x008b, + 0xc18a, 0x7d2b, 0x5ac0, 0x5bc8, 0xc19c, 0x7c27, 0x0388, 0x0689, + 0x5ce3, 0x0dff, 0x0511, 0x1dff, 0x05bc, 0x073e, 0x4d00, 0x7d18, + 0x0870, 0x0011, 0x077e, 0x7d09, 0x077d, 0x7d02, 0x5228, 0x9981, + 0x52f8, 0x54db, 0x02bc, 0x02cc, 0x7c09, 0x077c, 0x7d02, 0x5228, + 0x998a, 0x52f8, 0x54d3, 0x02bc, 0x02cc, 0x7d09, 0x0400, 0x9978, + 0x008b, 0x52c0, 0x53c8, 0xc1a8, 0x7dd6, 0x0200, 0x9968, 0x08ff, + 0x00bf, 0x077f, 0x7d1b, 0x0488, 0x00d5, 0x7d01, 0x008d, 0x05a0, + 0x5deb, 0x028f, 0x32ff, 0x0210, 0x32ff, 0x0210, 0x0212, 0x0217, + 0x0217, 0x32ff, 0x0212, 0x05da, 0x7c02, 0x073e, 0x99b9, 0x02a4, + 0x02dd, 0x7d02, 0x073e, 0x99b9, 0x075e, 0x99b9, 0x55eb, 0x0598, + 0x5deb, 0x52f3, 0x54fb, 0x076a, 0x7d26, 0x076c, 0x7d01, 0x99f6, + 0x076b, 0x7c57, 0x0769, 0x7d04, 0x0768, 0x7d02, 0x0e01, 0x99d0, + 0x5893, 0x00d6, 0x7d01, 0x008e, 0x5593, 0x05a0, 0x5d93, 0x06a0, + 0x7802, 0x5502, 0x5d04, 0x7c1d, 0x4e00, 0x7c08, 0x0769, 0x7d03, + 0x5502, 0x7e17, 0x99dd, 0x5d04, 0x7f14, 0x0689, 0x5093, 0x4800, + 0x7d01, 0x99c8, 0x9a41, 0x0015, 0x7806, 0x5502, 0x5d04, 0x074d, + 0x5502, 0x5d24, 0x072d, 0x7c01, 0x9a41, 0x0017, 0x076d, 0x7c01, + 0x2001, 0x5593, 0x009d, 0x0007, 0xda48, 0x9990, 0x6cd3, 0x0769, + 0x7d04, 0x0768, 0x7d02, 0x0e01, 0x9a05, 0x5893, 0x00d6, 0x7d01, + 0x008e, 0x5593, 0x05a0, 0x5d93, 0x06a0, 0x7802, 0x5502, 0x6dc8, + 0x7c0f, 0x4e00, 0x7c08, 0x0769, 0x7d03, 0x5502, 0x7e09, 0x9a12, + 0x6dc8, 0x7f06, 0x0689, 0x5093, 0x4800, 0x7d01, 0x99fd, 0x9a41, + 0x9a3b, 0x6ac3, 0x0769, 0x7d04, 0x0768, 0x7d02, 0x0e01, 0x9a28, + 0x5893, 0x00d6, 0x7d01, 0x008e, 0x5593, 0x05a0, 0x5d93, 0x06a0, + 0x7802, 0x65c8, 0x5d04, 0x7c0f, 0x4e00, 0x7c08, 0x0769, 0x7d03, + 0x65c8, 0x7e09, 0x9a35, 0x5d04, 0x7f06, 0x0689, 0x5093, 0x4800, + 0x7d01, 0x9a20, 0x9a41, 0x5593, 0x009d, 0x0007, 0x6cff, 0xda48, + 0x9990, 0x0000, 0x54e3, 0x55eb, 0x4d00, 0x7c01, 0x9990, 0x9978, + 0x54e3, 0x55eb, 0x0aff, 0x0211, 0x1aff, 0x077f, 0x7c02, 0x05a0, + 0x9a55, 0x009d, 0x058c, 0x05ba, 0x05a0, 0x0210, 0x04ba, 0x04ad, + 0x0454, 0x0006, 0xc230, 0xc23a, 0x57db, 0x52f3, 0x047a, 0x7d02, + 0x6ad7, 0x9a63, 0x6a05, 0x008f, 0x00d5, 0x7d01, 0x008d, 0x05a0, + 0x56fb, 0x0015, 0x0015, 0x0015, 0x047a, 0x7d07, 0x7804, 0x5206, + 0x6ac8, 0x5226, 0x6ac8, 0x7c0f, 0x9a7d, 0x7804, 0x5206, 0x6a0b, + 0x5226, 0x6a0b, 0x7c0a, 0x6a28, 0x7f08, 0x0000, 0x4d00, 0x7d07, + 0xc251, 0x57db, 0x9a63, 0xc2ca, 0x9a86, 0xc2ce, 0x0454, 0xc261, + 0x9a5c, 0xc23a, 0x57db, 0x52f3, 0x6ad5, 0x56fb, 0x028e, 0x1a94, + 0x6ac3, 0x62c8, 0x0269, 0x7d1e, 0x1e94, 0x6ee3, 0x62d0, 0x5aeb, + 0x62c8, 0x0248, 0x6ed3, 0x6ac8, 0x2694, 0x52eb, 0x6ad5, 0x6ee3, + 0x62c8, 0x026e, 0x7d27, 0x6ac8, 0x7f23, 0x2501, 0x4d00, 0x7d26, + 0x028e, 0x1a98, 0x6ac3, 0x62c8, 0x6ec3, 0x0260, 0x7df1, 0x62d0, + 0xc2d1, 0x9ace, 0x6ee3, 0x008f, 0x2001, 0x00d5, 0x7d01, 0x008d, + 0x05a0, 0x62c8, 0x026e, 0x7d0e, 0x6ac8, 0x7f0a, 0x2001, 0x7cf9, + 0x6add, 0x7f06, 0x0000, 0x4d00, 0x7d09, 0xc251, 0x57db, 0x9a8d, + 0x0007, 0x6aff, 0x62d0, 0xc2d1, 0x0458, 0x0454, 0x6add, 0x7ff8, + 0xc261, 0x9a8a, 0xc230, 0xc23a, 0x57db, 0x52f3, 0x6ad5, 0x56fb, + 0x028e, 0x1a94, 0x5202, 0x0269, 0x7d17, 0x1e94, 0x5206, 0x0248, + 0x5a06, 0x2694, 0x5206, 0x026e, 0x7d26, 0x6ac8, 0x7f22, 0x2501, + 0x4d00, 0x7d27, 0x028e, 0x1a98, 0x5202, 0x0260, 0x7df3, 0x6add, + 0x7f18, 0x62d0, 0xc2d1, 0x9b11, 0x008f, 0x2001, 0x00d5, 0x7d01, + 0x008d, 0x05a0, 0x5206, 0x026e, 0x7d0e, 0x6ac8, 0x7f0a, 0x2001, + 0x7cf9, 0x6add, 0x7f06, 0x0000, 0x4d00, 0x7d0b, 0xc251, 0x57db, + 0x9ad7, 0x0007, 0x6aff, 0x6add, 0x7ffc, 0x62d0, 0xc2d1, 0x0458, + 0x0454, 0x6add, 0x7ff6, 0xc261, 0x9ad4 +}; +#endif diff --git a/arch/arm/mach-mx35/serial.c b/arch/arm/mach-mx35/serial.c new file mode 100644 index 000000000000..74068f1c39dd --- /dev/null +++ b/arch/arm/mach-mx35/serial.c @@ -0,0 +1,179 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @file mach-mx35/serial.c + * + * @brief This file contains the UART initiliazation. + * + * @ingroup MSL_MX35 + */ +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/serial.h> +#include <mach/hardware.h> +#include <mach/mxc_uart.h> +#include <mach/spba.h> +#include "serial.h" +#include "board-mx35_3stack.h" + +#if defined(CONFIG_SERIAL_MXC) || defined(CONFIG_SERIAL_MXC_MODULE) + +/*! + * This is an array where each element holds information about a UART port, + * like base address of the UART, interrupt numbers etc. This structure is + * passed to the serial_core.c file. Based on which UART is used, the core file + * passes back the appropriate port structure as an argument to the control + * functions. + */ +static uart_mxc_port mxc_ports[] = { + [0] = { + .port = { + .membase = (void *)IO_ADDRESS(UART1_BASE_ADDR), + .mapbase = UART1_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART1_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 0, + }, + .ints_muxed = UART1_MUX_INTS, + .irqs = {UART1_INT2, UART1_INT3}, + .mode = UART1_MODE, + .ir_mode = UART1_IR, + .enabled = UART1_ENABLED, + .hardware_flow = UART1_HW_FLOW, + .cts_threshold = UART1_UCR4_CTSTL, + .dma_enabled = UART1_DMA_ENABLE, + .dma_rxbuf_size = UART1_DMA_RXBUFSIZE, + .rx_threshold = UART1_UFCR_RXTL, + .tx_threshold = UART1_UFCR_TXTL, + .shared = UART1_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART1_TX, + .dma_rx_id = MXC_DMA_UART1_RX, + .rxd_mux = MXC_UART_RXDMUX, + .ir_tx_inv = MXC_IRDA_TX_INV, + .ir_rx_inv = MXC_IRDA_RX_INV, + }, + [1] = { + .port = { + .membase = (void *)IO_ADDRESS(UART2_BASE_ADDR), + .mapbase = UART2_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART2_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 1, + }, + .ints_muxed = UART2_MUX_INTS, + .irqs = {UART2_INT2, UART2_INT3}, + .mode = UART2_MODE, + .ir_mode = UART2_IR, + .enabled = UART2_ENABLED, + .hardware_flow = UART2_HW_FLOW, + .cts_threshold = UART2_UCR4_CTSTL, + .dma_enabled = UART2_DMA_ENABLE, + .dma_rxbuf_size = UART2_DMA_RXBUFSIZE, + .rx_threshold = UART2_UFCR_RXTL, + .tx_threshold = UART2_UFCR_TXTL, + .shared = UART2_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART2_TX, + .dma_rx_id = MXC_DMA_UART2_RX, + .rxd_mux = MXC_UART_IR_RXDMUX, + .ir_tx_inv = MXC_IRDA_TX_INV, + .ir_rx_inv = MXC_IRDA_RX_INV, + }, +#if UART3_ENABLED == 1 + [2] = { + .port = { + .membase = (void *)IO_ADDRESS(UART3_BASE_ADDR), + .mapbase = UART3_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART3_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 2, + }, + .ints_muxed = UART3_MUX_INTS, + .irqs = {UART3_INT2, UART3_INT3}, + .mode = UART3_MODE, + .ir_mode = UART3_IR, + .enabled = UART3_ENABLED, + .hardware_flow = UART3_HW_FLOW, + .cts_threshold = UART3_UCR4_CTSTL, + .dma_enabled = UART3_DMA_ENABLE, + .dma_rxbuf_size = UART3_DMA_RXBUFSIZE, + .rx_threshold = UART3_UFCR_RXTL, + .tx_threshold = UART3_UFCR_TXTL, + .shared = UART3_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART3_TX, + .dma_rx_id = MXC_DMA_UART3_RX, + .rxd_mux = MXC_UART_RXDMUX, + .ir_tx_inv = MXC_IRDA_TX_INV, + .ir_rx_inv = MXC_IRDA_RX_INV, + }, +#endif +}; + +static struct platform_device mxc_uart_device1 = { + .name = "mxcintuart", + .id = 0, + .dev = { + .platform_data = &mxc_ports[0], + }, +}; + +static struct platform_device mxc_uart_device2 = { + .name = "mxcintuart", + .id = 1, + .dev = { + .platform_data = &mxc_ports[1], + }, +}; + +#if UART3_ENABLED == 1 +static struct platform_device mxc_uart_device3 = { + .name = "mxcintuart", + .id = 2, + .dev = { + .platform_data = &mxc_ports[2], + }, +}; +#endif + +static int __init mxc_init_uart(void) +{ + /* Register all the MXC UART platform device structures */ + platform_device_register(&mxc_uart_device1); + platform_device_register(&mxc_uart_device2); + + /* Grab ownership of shared UARTs 3 and 4, only when enabled */ +#if UART3_ENABLED == 1 +#if UART3_DMA_ENABLE == 1 + spba_take_ownership(UART3_SHARED_PERI, (SPBA_MASTER_A | SPBA_MASTER_C)); +#else + spba_take_ownership(UART3_SHARED_PERI, SPBA_MASTER_A); +#endif /* UART3_DMA_ENABLE */ + platform_device_register(&mxc_uart_device3); +#endif /* UART3_ENABLED */ + + return 0; +} + +#else +static int __init mxc_init_uart(void) +{ + return 0; +} +#endif + +arch_initcall(mxc_init_uart); diff --git a/arch/arm/mach-mx35/serial.h b/arch/arm/mach-mx35/serial.h new file mode 100644 index 000000000000..467a4a437525 --- /dev/null +++ b/arch/arm/mach-mx35/serial.h @@ -0,0 +1,132 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ARCH_ARM_MACH_MX35_SERIAL_H__ +#define __ARCH_ARM_MACH_MX35_SERIAL_H__ + +/*! + * @file mach-mx35/serial.h + * + * @ingroup MSL_MX35 + */ +#include <mach/mxc_uart.h> + +/* UART 1 configuration */ +/*! + * This option allows to choose either an interrupt-driven software controlled + * hardware flow control (set this option to 0) or hardware-driven hardware + * flow control (set this option to 1). + */ +#define UART1_HW_FLOW 1 +/*! + * This specifies the threshold at which the CTS pin is deasserted by the + * RXFIFO. Set this value in Decimal to anything from 0 to 32 for + * hardware-driven hardware flow control. Read the HW spec while specifying + * this value. When using interrupt-driven software controlled hardware + * flow control set this option to -1. + */ +#define UART1_UCR4_CTSTL 16 +/*! + * This is option to enable (set this option to 1) or disable DMA data transfer + */ +#define UART1_DMA_ENABLE 0 +/*! + * Specify the size of the DMA receive buffer. The minimum buffer size is 512 + * bytes. The buffer size should be a multiple of 256. + */ +#define UART1_DMA_RXBUFSIZE 1024 +/*! + * Specify the MXC UART's Receive Trigger Level. This controls the threshold at + * which a maskable interrupt is generated by the RxFIFO. Set this value in + * Decimal to anything from 0 to 32. Read the HW spec while specifying this + * value. + */ +#define UART1_UFCR_RXTL 16 +/*! + * Specify the MXC UART's Transmit Trigger Level. This controls the threshold at + * which a maskable interrupt is generated by the TxFIFO. Set this value in + * Decimal to anything from 0 to 32. Read the HW spec while specifying this + * value. + */ +#define UART1_UFCR_TXTL 16 +/* UART 2 configuration */ +#define UART2_HW_FLOW 1 +#define UART2_UCR4_CTSTL 16 +#define UART2_DMA_ENABLE 0 +#define UART2_DMA_RXBUFSIZE 1024 +#define UART2_UFCR_RXTL 16 +#define UART2_UFCR_TXTL 16 +/* UART 3 configuration */ +#define UART3_HW_FLOW 1 +#define UART3_UCR4_CTSTL 16 +#define UART3_DMA_ENABLE 1 +#define UART3_DMA_RXBUFSIZE 1024 +#define UART3_UFCR_RXTL 16 +#define UART3_UFCR_TXTL 16 + +/* + * UART Chip level Configuration that a user may not have to edit. These + * configuration vary depending on how the UART module is integrated with + * the ARM core + */ +/* + * Is the MUXED interrupt output sent to the ARM core + */ +#define INTS_NOTMUXED 0 +#define INTS_MUXED 1 +/* UART 1 configuration */ +/*! + * This define specifies whether the muxed ANDed interrupt line or the + * individual interrupts from the UART port is integrated with the ARM core. + * There exists a define like this for each UART port. Valid values that can + * be used are \b INTS_NOTMUXED or \b INTS_MUXED. + */ +#define UART1_MUX_INTS INTS_MUXED +/*! + * This define specifies the transmitter interrupt number or the interrupt + * number of the ANDed interrupt in case the interrupts are muxed. There exists + * a define like this for each UART port. + */ +#define UART1_INT1 MXC_INT_UART1 +/*! + * This define specifies the receiver interrupt number. If the interrupts of + * the UART are muxed, then we specify here a dummy value -1. There exists a + * define like this for each UART port. + */ +#define UART1_INT2 -1 +/*! + * This specifies the master interrupt number. If the interrupts of the UART + * are muxed, then we specify here a dummy value of -1. There exists a define + * like this for each UART port. + */ +#define UART1_INT3 -1 +/*! + * This specifies if the UART is a shared peripheral. It holds the shared + * peripheral number if it is shared or -1 if it is not shared. There exists + * a define like this for each UART port. + */ +#define UART1_SHARED_PERI -1 +/* UART 2 configuration */ +#define UART2_MUX_INTS INTS_MUXED +#define UART2_INT1 MXC_INT_UART2 +#define UART2_INT2 -1 +#define UART2_INT3 -1 +#define UART2_SHARED_PERI -1 +/* UART 3 configuration */ +#define UART3_MUX_INTS INTS_MUXED +#define UART3_INT1 MXC_INT_UART3 +#define UART3_INT2 -1 +#define UART3_INT3 -1 +#define UART3_SHARED_PERI SPBA_UART3 + +#endif /* __ARCH_ARM_MACH_MX35_SERIAL_H__ */ diff --git a/arch/arm/mach-mx35/system.c b/arch/arm/mach-mx35/system.c new file mode 100644 index 000000000000..71a4e8c27599 --- /dev/null +++ b/arch/arm/mach-mx35/system.c @@ -0,0 +1,126 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/io.h> +#include <mach/hardware.h> +#include <asm/proc-fns.h> +#include <asm/system.h> +#include <mach/clock.h> +#include <mach/hardware.h> +#include "crm_regs.h" + +/*! + * @defgroup MSL_MX35 i.MX35 Machine Specific Layer (MSL) + */ + +/*! + * @file mach-mx35/system.c + * @brief This file contains idle and reset functions. + * + * @ingroup MSL_MX35 + */ + +/*! +* MX35 low-power mode +*/ +enum mx35_low_pwr_mode { + MX35_RUN_MODE, + MX35_WAIT_MODE, + MX35_DOZE_MODE, + MX35_STOP_MODE +}; + +extern int mxc_jtag_enabled; + +/*! + * This function is used to set cpu low power mode before WFI instruction + * + * @param mode indicates different kinds of power modes + */ +void mxc_cpu_lp_set(enum mxc_cpu_pwr_mode mode) +{ + unsigned int lpm; + unsigned long reg; + + /*read CCMR value */ + reg = __raw_readl(MXC_CCM_CCMR); + + switch (mode) { + case WAIT_UNCLOCKED_POWER_OFF: + lpm = MX35_DOZE_MODE; + break; + + case STOP_POWER_ON: + case STOP_POWER_OFF: + lpm = MX35_STOP_MODE; + /* Enabled Well Bias */ + reg |= MXC_CCM_CCMR_WBEN; + if (!board_is_rev(BOARD_REV_1)) + reg |= MXC_CCM_CCMR_VSTBY; + break; + + case WAIT_CLOCKED: + case WAIT_UNCLOCKED: + default: + /* Wait is the default mode used when idle. */ + lpm = MX35_WAIT_MODE; + break; + } + + /* program LPM bit */ + reg = (reg & (~MXC_CCM_CCMR_LPM_MASK)) | lpm << MXC_CCM_CCMR_LPM_OFFSET; + /* program Interrupt holdoff bit */ + reg = reg | MXC_CCM_CCMR_WFI; + /* TBD: PMIC has put the voltage back to Normal if the voltage ready */ + /* counter finished */ + reg = reg | MXC_CCM_CCMR_STBY_EXIT_SRC; + + __raw_writel(reg, MXC_CCM_CCMR); +} + +EXPORT_SYMBOL(mxc_cpu_lp_set); + +/*! + * This function puts the CPU into idle mode. It is called by default_idle() + * in process.c file. + */ +void arch_idle(void) +{ + /* + * This should do all the clock switching + * and wait for interrupt tricks. + */ + if (!mxc_jtag_enabled) { +#ifdef CONFIG_MX35_DOZE_DURING_IDLE + /*set as Doze mode */ + mxc_cpu_lp_set(WAIT_UNCLOCKED_POWER_OFF); +#else + /* set as Wait mode */ + mxc_cpu_lp_set(WAIT_UNCLOCKED); +#endif + cpu_do_idle(); + } +} + +/* + * This function resets the system. It is called by machine_restart(). + * + * @param mode indicates different kinds of resets + */ +void arch_reset(char mode) +{ + /* Assert SRS signal */ + mxc_wd_reset(); +} diff --git a/arch/arm/mach-mx35/usb.h b/arch/arm/mach-mx35/usb.h new file mode 100644 index 000000000000..52b6f803bb50 --- /dev/null +++ b/arch/arm/mach-mx35/usb.h @@ -0,0 +1,104 @@ +/* + * Copyright 2005-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +extern int usbotg_init(struct platform_device *pdev); +extern void usbotg_uninit(struct fsl_usb2_platform_data *pdata); +extern struct platform_device *host_pdev_register(struct resource *res, + int n_res, struct fsl_usb2_platform_data *config); + +extern int fsl_usb_host_init(struct platform_device *pdev); +extern void fsl_usb_host_uninit(struct fsl_usb2_platform_data *pdata); +extern int gpio_usbh2_active(void); +extern void gpio_usbh2_inactive(void); +extern int gpio_usbotg_utmi_active(void); +extern void gpio_usbotg_utmi_inactive(void); + +/* + * Determine which platform_data struct to use for the DR controller, + * based on which transceiver is configured. + * PDATA is a pointer to it. + */ +static struct fsl_usb2_platform_data __maybe_unused dr_utmi_config; +#define PDATA (&dr_utmi_config) + + +/* + * Used to set pdata->operating_mode before registering the platform_device. + * If OTG is configured, the controller operates in OTG mode, + * otherwise it's either host or device. + */ +#ifdef CONFIG_USB_OTG +#define DR_UDC_MODE FSL_USB2_DR_OTG +#define DR_HOST_MODE FSL_USB2_DR_OTG +#else +#define DR_UDC_MODE FSL_USB2_DR_DEVICE +#define DR_HOST_MODE FSL_USB2_DR_HOST +#endif + + +#ifdef CONFIG_USB_EHCI_ARC_OTG +static inline void dr_register_host(struct resource *r, int rs) +{ + PDATA->operating_mode = DR_HOST_MODE; + host_pdev_register(r, rs, PDATA); +} +#else +static inline void dr_register_host(struct resource *r, int rs) +{ +} +#endif + +#ifdef CONFIG_USB_GADGET_ARC +static struct platform_device dr_udc_device; + +static inline void dr_register_udc(void) +{ + PDATA->operating_mode = DR_UDC_MODE; + dr_udc_device.dev.platform_data = PDATA; + + if (platform_device_register(&dr_udc_device)) + printk(KERN_ERR "usb: can't register DR gadget\n"); + else + printk(KERN_INFO "usb: DR gadget (%s) registered\n", + PDATA->transceiver); +} +#else +static inline void dr_register_udc(void) +{ +} +#endif + +#ifdef CONFIG_USB_OTG +static struct platform_device dr_otg_device; + +/* + * set the proper operating_mode and + * platform_data pointer, then register the + * device. + */ +static inline void dr_register_otg(void) +{ + PDATA->operating_mode = FSL_USB2_DR_OTG; + dr_otg_device.dev.platform_data = PDATA; + + if (platform_device_register(&dr_otg_device)) + printk(KERN_ERR "usb: can't register otg device\n"); + else + printk(KERN_INFO "usb: DR OTG registered\n"); +} +#else +static inline void dr_register_otg(void) +{ +} +#endif diff --git a/arch/arm/mach-mx35/usb_dr.c b/arch/arm/mach-mx35/usb_dr.c new file mode 100644 index 000000000000..18f76b90907a --- /dev/null +++ b/arch/arm/mach-mx35/usb_dr.c @@ -0,0 +1,110 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <mach/hardware.h> +#include <mach/arc_otg.h> +#include "usb.h" + +/* + * platform data structs + * - Which one to use is determined by CONFIG options in usb.h + * - operating_mode plugged at run time + */ +static struct fsl_usb2_platform_data __maybe_unused dr_utmi_config = { + .name = "DR", + .platform_init = usbotg_init, + .platform_uninit = usbotg_uninit, + .phy_mode = FSL_USB2_PHY_UTMI_WIDE, + .power_budget = 500, /* 500 mA max power */ + .gpio_usb_active = gpio_usbotg_utmi_active, + .gpio_usb_inactive = gpio_usbotg_utmi_inactive, + .transceiver = "utmi", +}; + + +/* + * resources + */ +static struct resource otg_resources[] = { + [0] = { + .start = (u32)(USB_OTGREGS_BASE), + .end = (u32)(USB_OTGREGS_BASE + 0x1ff), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_USBOTG, + .flags = IORESOURCE_IRQ, + }, +}; + + +static u64 dr_udc_dmamask = ~(u32) 0; +static void dr_udc_release(struct device *dev) +{ +} + +static u64 dr_otg_dmamask = ~(u32) 0; +static void dr_otg_release(struct device *dev) +{ +} + +/* + * platform device structs + * dev.platform_data field plugged at run time + */ +static struct platform_device __maybe_unused dr_udc_device = { + .name = "fsl-usb2-udc", + .id = -1, + .dev = { + .release = dr_udc_release, + .dma_mask = &dr_udc_dmamask, + .coherent_dma_mask = 0xffffffff, + }, + .resource = otg_resources, + .num_resources = ARRAY_SIZE(otg_resources), +}; + +static struct platform_device __maybe_unused dr_otg_device = { + .name = "fsl-usb2-otg", + .id = -1, + .dev = { + .release = dr_otg_release, + .dma_mask = &dr_otg_dmamask, + .coherent_dma_mask = 0xffffffff, + }, + .resource = otg_resources, + .num_resources = ARRAY_SIZE(otg_resources), +}; + +static int __init usb_dr_init(void) +{ + pr_debug("%s: \n", __func__); + + /* i.MX35 1.0 should work in INCR mode */ + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 0) { + PDATA->change_ahb_burst = 1; + PDATA->ahb_burst_mode = 0; + } + + dr_register_otg(); + dr_register_host(otg_resources, ARRAY_SIZE(otg_resources)); + dr_register_udc(); + + return 0; +} + +module_init(usb_dr_init); diff --git a/arch/arm/mach-mx35/usb_h2.c b/arch/arm/mach-mx35/usb_h2.c new file mode 100644 index 000000000000..3590dc5daf1d --- /dev/null +++ b/arch/arm/mach-mx35/usb_h2.c @@ -0,0 +1,63 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/usb/fsl_xcvr.h> + +#include <mach/hardware.h> +#include <mach/arc_otg.h> +#include "usb.h" + +static struct fsl_usb2_platform_data usbh2_config = { + .name = "Host 2", + .platform_init = fsl_usb_host_init, + .platform_uninit = fsl_usb_host_uninit, + .operating_mode = FSL_USB2_MPH_HOST, + .phy_mode = FSL_USB2_PHY_SERIAL, + .power_budget = 500, /* 500 mA max power */ + .gpio_usb_active = gpio_usbh2_active, + .gpio_usb_inactive = gpio_usbh2_inactive, + .transceiver = "serial", +}; + +static struct resource usbh2_resources[] = { + [0] = { + .start = (u32) (USB_H2REGS_BASE), + .end = (u32) (USB_H2REGS_BASE + 0x1ff), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_USBHS, + .flags = IORESOURCE_IRQ, + }, +}; + + +static int __init usbh2_init(void) +{ + pr_debug("%s: \n", __func__); + + /* i.MX35 1.0 should work in INCR mode */ + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 0) { + usbh2_config.change_ahb_burst = 1; + usbh2_config.ahb_burst_mode = 0; + } + + host_pdev_register(usbh2_resources, ARRAY_SIZE(usbh2_resources), + &usbh2_config); + return 0; +} +module_init(usbh2_init); diff --git a/arch/arm/mach-mx37/Kconfig b/arch/arm/mach-mx37/Kconfig new file mode 100644 index 000000000000..75b5e0c4ae95 --- /dev/null +++ b/arch/arm/mach-mx37/Kconfig @@ -0,0 +1,90 @@ +menu "MX37 Options" + depends on ARCH_MX37 + +config MX37_OPTIONS + bool + default y + select CPU_V6 + select ARM_ERRATA_411920 + select CACHE_L2X0 + select OUTER_CACHE + select USB_ARCH_HAS_EHCI + select ARCH_HAS_EVTMON + select MXC_TZIC + select ARCH_HAS_RNGC + +config MACH_MX37_3DS + bool "Support MX37 3-Stack platforms" + default y + select MFD_WM8350_CONFIG_MODE_0 + help + Include support for MX37 3-Stack platform. This includes specific + configurations for the board and its peripherals. + +config MXC_SDMA_API + bool "Use SDMA API" + default y + help + This selects the Freescale MXC SDMA API. + If unsure, say N. + +menu "SDMA options" + depends on MXC_SDMA_API + +config SDMA_IRAM + bool "Use Internal RAM for SDMA transfer" + default n + help + Support Internal RAM as SDMA buffer or control structures + +config SDMA_IRAM_SIZE + hex "Reserved bytes of IRAM for SDMA (0x800-0x1000)" + range 0x800 0x1000 + depends on SDMA_IRAM + default "0x1000" + help + Set the size of IRAM for SDMA. It must be a multiple of 512bytes. +endmenu + +config ARCH_MXC_HAS_NFC_V3 + bool "MXC NFC Hardware Version 3" + depends on ARCH_MX37 + default y + help + This selects the Freescale MXC Nand Flash Controller Hardware Version 3 + If unsure, say N. + +config ARCH_MXC_HAS_NFC_V3_1 + bool "MXC NFC Hardware Version 3.1" + depends on ARCH_MXC_HAS_NFC_V3 + default y + help + This selects the Freescale MXC Nand Flash Controller Hardware Version 3.1 + If unsure, say N. + +menu "Device options" + +config I2C_MXC_SELECT1 + bool "Enable I2C1 module" + default y + depends on I2C_MXC + help + Enable MX37 I2C1 module. + +config I2C_MXC_SELECT2 + bool "Enable I2C2 module" + default n + depends on I2C_MXC + help + Enable MX37 I2C2 module. + +config I2C_MXC_SELECT3 + bool "Enable I2C3 module" + default n + depends on I2C_MXC + help + Enable MX37 I2C3 module. + +endmenu + +endmenu diff --git a/arch/arm/mach-mx37/Makefile b/arch/arm/mach-mx37/Makefile new file mode 100644 index 000000000000..2b3eeadfc495 --- /dev/null +++ b/arch/arm/mach-mx37/Makefile @@ -0,0 +1,19 @@ +# +# Makefile for the linux kernel. +# + +# Object file lists. + +obj-y := system.o iomux.o cpu.o mm.o clock.o devices.o serial.o dma.o lpmodes.o dptc.o bus_freq.o + + +obj-$(CONFIG_MACH_MX37_3DS) += mx37_3stack.o mx37_3stack_gpio.o +obj-$(CONFIG_SPI_MXC) += mx37_3stack_cpld.o +obj-$(CONFIG_REGULATOR_WM8350) += mx37_3stack_pmic_wm8350.o + +# power management +obj-$(CONFIG_PM) += pm.o + +ifneq ($(strip $(CONFIG_USB_GADGET_ARC) $(CONFIG_USB_EHCI_ARC_OTG)),) + obj-y += usb_dr.o +endif diff --git a/arch/arm/mach-mx37/Makefile.boot b/arch/arm/mach-mx37/Makefile.boot new file mode 100644 index 000000000000..1568ad404d59 --- /dev/null +++ b/arch/arm/mach-mx37/Makefile.boot @@ -0,0 +1,3 @@ + zreladdr-y := 0x40008000 +params_phys-y := 0x40000100 +initrd_phys-y := 0x40800000 diff --git a/arch/arm/mach-mx37/board-mx37_3stack.h b/arch/arm/mach-mx37/board-mx37_3stack.h new file mode 100644 index 000000000000..82eee4d47476 --- /dev/null +++ b/arch/arm/mach-mx37/board-mx37_3stack.h @@ -0,0 +1,117 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ASM_ARCH_MXC_BOARD_MX37_3STACK_H__ +#define __ASM_ARCH_MXC_BOARD_MX37_3STACK_H__ + +/*! + * @defgroup BRDCFG_MX37 Board Configuration Options + * @ingroup MSL_MX37 + */ + +/*! + * @file mach-mx37/board-mx37_3stack.h + * + * @brief This file contains all the board level configuration options. + * + * It currently hold the options defined for MX31 ADS Platform. + * + * @ingroup BRDCFG_MX37 + */ + +/* + * Include Files + */ +#include <mach/mxc_uart.h> +#include <mach/mxc_dptc.h> + +/*! + * @name MXC UART EVB board level configurations + */ +/*! @{ */ +/*! + * Specifies if the Irda transmit path is inverting + */ +#define MXC_IRDA_TX_INV 0 +/*! + * Specifies if the Irda receive path is inverting + */ +#define MXC_IRDA_RX_INV 0 + +/* UART 1 configuration */ +/*! + * This define specifies if the UART port is configured to be in DTE or + * DCE mode. There exists a define like this for each UART port. Valid + * values that can be used are \b MODE_DTE or \b MODE_DCE. + */ +#define UART1_MODE MODE_DCE +/*! + * This define specifies if the UART is to be used for IRDA. There exists a + * define like this for each UART port. Valid values that can be used are + * \b IRDA or \b NO_IRDA. + */ +#define UART1_IR NO_IRDA +/*! + * This define is used to enable or disable a particular UART port. If + * disabled, the UART will not be registered in the file system and the user + * will not be able to access it. There exists a define like this for each UART + * port. Specify a value of 1 to enable the UART and 0 to disable it. + */ +#define UART1_ENABLED 1 +/*! @} */ +/* UART 2 configuration */ +#define UART2_MODE MODE_DCE +#define UART2_IR NO_IRDA +#define UART2_ENABLED 1 +/* UART 3 configuration */ +#define UART3_MODE MODE_DCE +#define UART3_IR NO_IRDA +#define UART3_ENABLED 1 + +#define MXC_LL_UART_PADDR UART1_BASE_ADDR +#define MXC_LL_UART_VADDR AIPS1_IO_ADDRESS(UART1_BASE_ADDR) + +#define DEBUG_BASE_ADDRESS 0x78000000 /* Use a Dummy base address */ +/* LAN9217 ethernet base address */ +#define LAN9217_BASE_ADDR DEBUG_BASE_ADDRESS +/* External UART */ +#define UARTA_BASE_ADDR (DEBUG_BASE_ADDRESS + 0x8000) +#define UARTB_BASE_ADDR (DEBUG_BASE_ADDRESS + 0x10000) + +#define BOARD_IO_ADDR 0x20000 +/* LED switchs */ +#define LED_SWITCH_REG BOARD_IO_ADDR + 0x00 +/* buttons */ +#define SWITCH_BUTTONS_REG BOARD_IO_ADDR + 0x08 +/* status, interrupt */ +#define INTR_STATUS_REG BOARD_IO_ADDR + 0x10 +#define INTR_MASK_REG BOARD_IO_ADDR + 0x38 +#define INTR_RESET_REG BOARD_IO_ADDR + 0x20 +/* magic word for debug CPLD */ +#define MAGIC_NUMBER1_REG BOARD_IO_ADDR + 0x40 +#define MAGIC_NUMBER2_REG BOARD_IO_ADDR + 0x48 +/* CPLD code version */ +#define CPLD_CODE_VER_REG BOARD_IO_ADDR + 0x50 + +extern unsigned int sdhc_get_card_det_status(struct device *dev); +extern int sdhc_write_protect(struct device *dev); +extern int sdhc_init_card_det(int id); +extern struct tve_platform_data tve_data; +extern struct mxc_dptc_data dptc_lp_data; +extern struct mxc_dptc_data dptc_gp_data; +extern struct mxc_dvfs_platform_data dvfs_core_data; +extern char *gp_reg_id; +extern char *lp_reg_id; + +extern int headphone_det_status(void); +#endif /* __ASM_ARCH_MXC_BOARD_MX37_3STACK_H__ */ diff --git a/arch/arm/mach-mx37/bus_freq.c b/arch/arm/mach-mx37/bus_freq.c new file mode 100644 index 000000000000..6af034605451 --- /dev/null +++ b/arch/arm/mach-mx37/bus_freq.c @@ -0,0 +1,460 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file bus_freq.c + * + * @brief A common API for the Freescale Semiconductor i.MXC CPUfreq module + * and DVFS CORE module. + * + * The APIs are for setting bus frequency to low or high. + * + * @ingroup PM + */ + +#include <linux/proc_fs.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <mach/clock.h> +#include <mach/hardware.h> +#include <linux/regulator/consumer.h> +#include <mach/mxc_dvfs.h> + +#include "iomux.h" +#include "crm_regs.h" + +#define GP_LPM_VOLTAGE 850000 +#define LP_LPM_VOLTAGE 1050000 +#define LP_LOWFREQ_VOLTAGE 1050000 +#define LP_NORMAL_VOLTAGE 1200000 + +DEFINE_SPINLOCK(bus_freq_lock); + +struct clk *main_bus_clk; +struct clk *pll2; +struct clk *pll1; +struct clk *axi_a_clk; +struct clk *axi_b_clk; +struct clk *axi_c_clk; +struct clk *emi_core_clk; +struct clk *emi_intr_clk; +struct clk *nfc_clk; +struct clk *ahb_clk; +struct clk *vpu_clk; +struct clk *vpu_core_clk; +struct clk *arm_axi_clk; +struct clk *ddr_clk; +struct clk *ipu_clk; +struct clk *periph_apm_clk; +struct clk *lp_apm; +struct clk *cpu_clk; +struct clk *osc; +struct clk *uart_clk; +struct regulator *lp_regulator; +int low_bus_freq_mode; +int high_bus_freq_mode; +char *gp_reg_id = "SW1"; +char *lp_reg_id = "SW2"; +static struct cpu_wp *cpu_wp_tbl; + +struct dvfs_wp dvfs_core_setpoint[] = {{33, 7, 33, 10, 10, 0x10}, + {22, 0, 33, 10, 10, 0x10},}; + +int set_low_bus_freq(void) +{ + int ret = 0; + unsigned long lp_lpm_clk; + unsigned long flags; + + struct clk *p_clk; + struct clk *amode_parent_clk; + + if (low_bus_freq_mode) + return ret; + + if (clk_get_rate(cpu_clk) != 200000000) + return; + + clk_disable(uart_clk); + + lp_lpm_clk = clk_get_rate(lp_apm); + amode_parent_clk = lp_apm; + p_clk = clk_get_parent(periph_apm_clk); + spin_lock_irqsave(&bus_freq_lock, flags); + /* Make sure osc_clk is the parent of lp_apm. */ + if (clk_get_parent(amode_parent_clk) != osc) + clk_set_parent(amode_parent_clk, osc); + + /* Set the parent of periph_apm_clk to be lp_apm */ + clk_set_parent(periph_apm_clk, amode_parent_clk); + amode_parent_clk = periph_apm_clk; + + p_clk = clk_get_parent(main_bus_clk); + /* Set the parent of main_bus_clk to be periph_apm_clk */ + clk_set_parent(main_bus_clk, amode_parent_clk); + + clk_set_rate(ahb_clk, lp_lpm_clk); + /* Set the emi_internal clock to 24MHz */ + clk_set_rate(emi_intr_clk, lp_lpm_clk); + if (clk_get_parent(emi_core_clk) != ahb_clk) + clk_set_rate(emi_core_clk, lp_lpm_clk); + + if (clk_get_usecount(axi_a_clk) != 0) + clk_set_rate(axi_a_clk, lp_lpm_clk); + + if (clk_get_usecount(axi_b_clk) != 0) + clk_set_rate(axi_b_clk, lp_lpm_clk); + + if (clk_get_usecount(axi_c_clk) != 0) + clk_set_rate(axi_c_clk, lp_lpm_clk); + + amode_parent_clk = emi_core_clk; + + p_clk = clk_get_parent(arm_axi_clk); + if (p_clk != amode_parent_clk) + clk_set_parent(arm_axi_clk, amode_parent_clk); + + p_clk = clk_get_parent(vpu_clk); + if (p_clk != amode_parent_clk) + clk_set_parent(vpu_clk, amode_parent_clk); + + p_clk = clk_get_parent(vpu_core_clk); + if (p_clk != amode_parent_clk) + clk_set_parent(vpu_core_clk, amode_parent_clk); + spin_unlock_irqrestore(&bus_freq_lock, flags); + + /* Set the voltage to 1.05V for the LP domain. */ + ret = regulator_set_voltage(lp_regulator, 1050000, 1050000); + udelay(100); + if (ret < 0) { + printk(KERN_ERR "COULD NOT SET LP VOLTAGE!!!!!!\n"); + return ret; + } + + low_bus_freq_mode = 1; + high_bus_freq_mode = 0; + return ret; +} + +int set_high_bus_freq(int high_bus_freq) +{ + struct clk *p_clk; + struct clk *rmode_parent_clk; + int ret = 0; + unsigned long flags; + + if (!low_bus_freq_mode) + return ret; + + low_bus_freq_mode = 0; + + /* Set the voltage to 1.25V for the LP domain. */ + ret = regulator_set_voltage(lp_regulator, 1250000, 1250000); + udelay(100); + if (ret < 0) { + printk(KERN_ERR "COULD NOT SET LP VOLTAGE!!!!!!\n"); + return ret; + } + + rmode_parent_clk = pll2; + spin_lock_irqsave(&bus_freq_lock, flags); + + /* Set the dividers before setting the parent clock. */ + if (clk_get_usecount(axi_a_clk) != 0) + clk_set_rate(axi_a_clk, 4800000); + if (clk_get_usecount(axi_b_clk) != 0) + clk_set_rate(axi_b_clk, 4000000); + if (clk_get_usecount(axi_c_clk) != 0) + clk_set_rate(axi_c_clk, 6000000); + if (clk_get_parent(emi_core_clk) != ahb_clk) + clk_set_rate(emi_core_clk, 4800000); + + clk_set_rate(ahb_clk, 4800000); + /* Set emi_intr clock back to divide by 2. */ + clk_set_rate(emi_intr_clk, 2400000); + /* Set the parent of main_bus_clk to be pll2 */ + clk_set_parent(main_bus_clk, rmode_parent_clk); + spin_unlock_irqrestore(&bus_freq_lock, flags); + + clk_enable(uart_clk); + high_bus_freq_mode = 1; + return ret; +} + +int low_freq_bus_used(void) +{ + if ((clk_get_usecount(ipu_clk) == 0) + && (clk_get_usecount(vpu_clk) == 0)) + return 1; + else + return 0; +} + +void setup_pll(void) +{ + u32 reg; + u32 hfsm; + struct cpu_wp *p; + + /* Setup the DPLL registers */ + hfsm = __raw_readl(MXC_DPLL1_BASE + MXC_PLL_DP_CTL) & + MXC_PLL_DP_CTL_HFSM; + reg = __raw_readl(MXC_DPLL1_BASE + MXC_PLL_DP_CONFIG); + reg &= ~MXC_PLL_DP_CONFIG_AREN; + __raw_writel(reg, MXC_DPLL1_BASE + MXC_PLL_DP_CONFIG); + + if (hfsm) { + /* Running at lower frequency, need to bump up. */ + p = &cpu_wp_tbl[0]; + /* PDF and MFI */ + reg = p->pdf | p->mfi << MXC_PLL_DP_OP_MFI_OFFSET; + __raw_writel(reg, MXC_DPLL1_BASE + MXC_PLL_DP_OP); + + /* MFD */ + __raw_writel(p->mfd, MXC_DPLL1_BASE + MXC_PLL_DP_MFD); + + /* MFI */ + __raw_writel(p->mfn, MXC_DPLL1_BASE + MXC_PLL_DP_MFN); + } else { + /* Running at high frequency, need to lower it. */ + p = &cpu_wp_tbl[1]; + /* PDF and MFI */ + reg = p->pdf | p->mfi << MXC_PLL_DP_OP_MFI_OFFSET; + __raw_writel(reg, MXC_DPLL1_BASE + MXC_PLL_DP_HFS_OP); + + /* MFD */ + __raw_writel(p->mfd, MXC_DPLL1_BASE + MXC_PLL_DP_HFS_MFD); + + /* MFN */ + __raw_writel(p->mfn, MXC_DPLL1_BASE + MXC_PLL_DP_HFS_MFN); + } + + /* Set PLL2_PODF to be 3 */ + reg = __raw_readl(MXC_CCM_CCSR); + reg |= 2 << MXC_CCM_CCSR_PLL2_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CCSR); + /* Set the parent of STEP_CLK to be PLL2 */ + reg = __raw_readl(MXC_CCM_CCSR); + reg = (reg & ~MXC_CCM_CCSR_STEP_SEL_MASK) | + (2 << MXC_CCM_CCSR_STEP_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CCSR); +} + +/*! + * This is the probe routine for the bus frequency driver. + * + * @param pdev The platform device structure + * + * @return The function returns 0 on success + * + */ +static int __devinit busfreq_probe(struct platform_device *pdev) +{ + int cpu_wp_nr; + + main_bus_clk = clk_get(NULL, "main_bus_clk"); + if (IS_ERR(main_bus_clk)) { + printk(KERN_DEBUG "%s: failed to get main_bus_clk\n", __func__); + return PTR_ERR(main_bus_clk); + } + + pll2 = clk_get(NULL, "pll2"); + if (IS_ERR(pll2)) { + printk(KERN_DEBUG "%s: failed to get pll2\n", __func__); + return PTR_ERR(pll2); + } + + axi_a_clk = clk_get(NULL, "axi_a_clk"); + if (IS_ERR(axi_a_clk)) { + printk(KERN_DEBUG "%s: failed to get axi_a_clk\n", __func__); + return PTR_ERR(axi_a_clk); + } + + axi_b_clk = clk_get(NULL, "axi_b_clk"); + if (IS_ERR(axi_b_clk)) { + printk(KERN_DEBUG "%s: failed to get axi_b_clk\n", __func__); + return PTR_ERR(axi_b_clk); + } + + axi_c_clk = clk_get(NULL, "axi_c_clk"); + if (IS_ERR(axi_c_clk)) { + printk(KERN_DEBUG "%s: failed to get axi_c_clk\n", __func__); + return PTR_ERR(axi_c_clk); + } + + emi_core_clk = clk_get(NULL, "emi_core_clk"); + if (IS_ERR(emi_core_clk)) { + printk(KERN_DEBUG "%s: failed to get emi_core_clk\n", __func__); + return PTR_ERR(emi_core_clk); + } + + emi_intr_clk = clk_get(NULL, "emi_intr_clk"); + if (IS_ERR(emi_intr_clk)) { + printk(KERN_DEBUG "%s: failed to get emi_intr_clk\n", __func__); + return PTR_ERR(emi_intr_clk); + } + + nfc_clk = clk_get(NULL, "nfc_clk"); + if (IS_ERR(nfc_clk)) { + printk(KERN_DEBUG "%s: failed to get nfc_clk\n", __func__); + return PTR_ERR(nfc_clk); + } + + ahb_clk = clk_get(NULL, "ahb_clk"); + if (IS_ERR(ahb_clk)) { + printk(KERN_DEBUG "%s: failed to get ahb_clk\n", __func__); + return PTR_ERR(ahb_clk); + } + + vpu_core_clk = clk_get(NULL, "vpu_core_clk"); + if (IS_ERR(vpu_core_clk)) { + printk(KERN_DEBUG "%s: failed to get vpu_core_clk\n", __func__); + return PTR_ERR(vpu_core_clk); + } + + arm_axi_clk = clk_get(NULL, "arm_axi_clk"); + if (IS_ERR(arm_axi_clk)) { + printk(KERN_DEBUG "%s: failed to get arm_axi_clk\n", __func__); + return PTR_ERR(arm_axi_clk); + } + + ddr_clk = clk_get(NULL, "ddr_clk"); + if (IS_ERR(ddr_clk)) { + printk(KERN_DEBUG "%s: failed to get ddr_clk\n", __func__); + return PTR_ERR(ddr_clk); + } + + ipu_clk = clk_get(NULL, "ipu_clk"); + if (IS_ERR(ipu_clk)) { + printk(KERN_DEBUG "%s: failed to get ipu_clk\n", __func__); + return PTR_ERR(ipu_clk); + } + + vpu_clk = clk_get(NULL, "vpu_clk"); + if (IS_ERR(vpu_clk)) { + printk(KERN_DEBUG "%s: failed to get vpu_clk\n", __func__); + return PTR_ERR(vpu_clk); + } + + periph_apm_clk = clk_get(NULL, "periph_apm_clk"); + if (IS_ERR(periph_apm_clk)) { + printk(KERN_DEBUG "%s: failed to get periph_apm_clk\n", + __func__); + return PTR_ERR(periph_apm_clk); + } + + lp_apm = clk_get(NULL, "lp_apm"); + if (IS_ERR(lp_apm)) { + printk(KERN_DEBUG "%s: failed to get lp_apm\n", __func__); + return PTR_ERR(lp_apm); + } + + cpu_clk = clk_get(NULL, "cpu_clk"); + if (IS_ERR(cpu_clk)) { + printk(KERN_DEBUG "%s: failed to get cpu_clk\n", __func__); + return PTR_ERR(cpu_clk); + } + + osc = clk_get(NULL, "osc"); + if (IS_ERR(osc)) { + printk(KERN_DEBUG "%s: failed to get osc\n", __func__); + return PTR_ERR(osc); + } + + uart_clk = clk_get(NULL, "uart_clk.0"); + if (IS_ERR(uart_clk)) { + printk(KERN_DEBUG "%s: failed to get uart_clk-0\n", __func__); + return PTR_ERR(uart_clk); + } + + pll1 = clk_get(NULL, "pll1_sw_clk"); + if (IS_ERR(pll1)) { + printk(KERN_DEBUG "%s: failed to get pll1_sw_clk\n", __func__); + return PTR_ERR(pll1); + } + + + lp_regulator = regulator_get(NULL, lp_reg_id); + if (IS_ERR(lp_regulator)) { + clk_put(ahb_clk); + printk(KERN_DEBUG "%s: failed to get lp regulator\n", __func__); + return PTR_ERR(lp_regulator); + } + + low_bus_freq_mode = 0; + high_bus_freq_mode = 0; + + cpu_wp_tbl = get_cpu_wp(&cpu_wp_nr); + + return 0; +} + +static struct platform_driver busfreq_driver = { + .driver = { + .name = "busfreq", + }, + .probe = busfreq_probe, +}; + +/*! + * Initialise the busfreq_driver. + * + * @return The function always returns 0. + */ + +static int __init busfreq_init(void) +{ + if (platform_driver_register(&busfreq_driver) != 0) { + printk(KERN_ERR "busfreq_driver register failed\n"); + return -ENODEV; + } + + printk(KERN_INFO "Bus freq driver module loaded\n"); + return 0; +} + +static void __exit busfreq_cleanup(void) +{ + /* Unregister the device structure */ + platform_driver_unregister(&busfreq_driver); + + clk_put(main_bus_clk); + clk_put(pll2); + clk_put(axi_a_clk); + clk_put(axi_b_clk); + clk_put(axi_c_clk); + clk_put(emi_core_clk); + clk_put(emi_intr_clk); + clk_put(nfc_clk); + clk_put(ahb_clk); + clk_put(vpu_core_clk); + clk_put(arm_axi_clk); + clk_put(ddr_clk); + clk_put(ipu_clk); + clk_put(periph_apm_clk); + clk_put(lp_apm); + clk_put(osc); + clk_put(pll1); + clk_put(pll2); + regulator_put(lp_regulator); + +} + +module_init(busfreq_init); +module_exit(busfreq_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("BusFreq driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-mx37/clock.c b/arch/arm/mach-mx37/clock.c new file mode 100644 index 000000000000..3d3938ca3d9b --- /dev/null +++ b/arch/arm/mach-mx37/clock.c @@ -0,0 +1,3201 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <asm/div64.h> +#include <mach/hardware.h> +#include <mach/common.h> +#include <mach/clock.h> +#include <mach/mxc_dptc.h> +#include <mach/spba.h> +#include <mach/mxc_uart.h> + +#include "crm_regs.h" +#include "iomux.h" + +extern int mxc_jtag_enabled; +extern int cpufreq_trig_needed; +extern int dvfs_core_is_active; + +static unsigned long pll_base[] = { + (unsigned long)MXC_DPLL1_BASE, + (unsigned long)MXC_DPLL2_BASE, + (unsigned long)MXC_DPLL3_BASE, +}; + +static struct clk pll1_main_clk; +static struct clk pll1_sw_clk; +static struct clk pll2_sw_clk; +static struct clk pll3_sw_clk; +static struct clk lp_apm_clk; +static struct clk emi_core_clk; +static struct clk emi_fast_clk; +static struct clk emi_slow_clk; +static struct clk emi_intr_clk; +static struct clk ddr_clk; +static struct clk ipu_clk[]; +static struct clk axi_a_clk; +static struct clk axi_b_clk; +static struct clk axi_c_clk; +static struct clk ahb_clk; + +int cpu_wp_nr; +int lp_high_freq; +int lp_med_freq; +static int cpu_curr_wp; +static struct cpu_wp *cpu_wp_tbl; + +extern void propagate_rate(struct clk *tclk); +extern void board_ref_clk_rate(unsigned long *ckil, unsigned long *osc, + unsigned long *ckih); +static int cpu_clk_set_wp(int wp); + +static int _clk_enable(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(clk->enable_reg); + reg |= MXC_CCM_CCGR_CG_MASK << clk->enable_shift; + __raw_writel(reg, clk->enable_reg); + + return 0; +} + +static void _clk_disable(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(clk->enable_reg); + reg &= ~(MXC_CCM_CCGR_CG_MASK << clk->enable_shift); + __raw_writel(reg, clk->enable_reg); +} + +static void _clk_disable_inwait(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(clk->enable_reg); + reg &= ~(MXC_CCM_CCGR_CG_MASK << clk->enable_shift); + reg |= 1 << clk->enable_shift; + __raw_writel(reg, clk->enable_reg); +} + +/* + * For the 4-to-1 muxed input clock + */ +static inline u32 _get_mux(struct clk *parent, struct clk *m0, + struct clk *m1, struct clk *m2, struct clk *m3) +{ + if (parent == m0) { + return 0; + } else if (parent == m1) { + return 1; + } else if (parent == m2) { + return 2; + } else if (parent == m3) { + return 3; + } else { + BUG(); + } + return 0; +} + +static inline unsigned long _get_pll_base(struct clk *pll) +{ + if (pll == &pll1_main_clk) { + return pll_base[0]; + } else if (pll == &pll2_sw_clk) { + return pll_base[1]; + } else if (pll == &pll3_sw_clk) { + return pll_base[2]; + } else { + BUG(); + } + return 0; +} + +static struct clk ckih_clk = { + .name = "ckih", + .flags = RATE_PROPAGATES, +}; + +static struct clk osc_clk = { + .name = "osc", + .flags = RATE_PROPAGATES, +}; + +static struct clk ckil_clk = { + .name = "ckil", + .flags = RATE_PROPAGATES, +}; + +static void _fpm_recalc(struct clk *clk) +{ + clk->rate = ckil_clk.rate * 512; + if ((__raw_readl(MXC_CCM_CCR) & MXC_CCM_CCR_FPM_MULT_MASK) != 0) { + clk->rate *= 2; + } +} + +static int _fpm_enable(struct clk *clk) +{ + u32 reg = __raw_readl(MXC_CCM_CCR); + reg |= MXC_CCM_CCR_FPM_EN; + __raw_writel(reg, MXC_CCM_CCR); + return 0; +} + +static void _fpm_disable(struct clk *clk) +{ + u32 reg = __raw_readl(MXC_CCM_CCR); + reg &= ~MXC_CCM_CCR_FPM_EN; + __raw_writel(reg, MXC_CCM_CCR); +} + +static struct clk fpm_clk = { + .name = "fpm_clk", + .parent = &ckil_clk, + .recalc = _fpm_recalc, + .enable = _fpm_enable, + .disable = _fpm_disable, + .flags = RATE_PROPAGATES, +}; + +static void _fpm_div2_recalc(struct clk *clk) +{ + clk->rate = clk->parent->rate / 2; +} + +static struct clk fpm_div2_clk = { + .name = "fpm_div2_clk", + .parent = &fpm_clk, + .recalc = _fpm_div2_recalc, + .flags = RATE_PROPAGATES, +}; + +static int _clk_pll_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + u32 pllbase; + + long mfi, pdf, mfn, mfd = 999999; + s64 temp64; + unsigned long quad_parent_rate; + unsigned long pll_hfsm, dp_ctl; + + pllbase = _get_pll_base(clk); + + quad_parent_rate = 4*clk->parent->rate; + pdf = mfi = -1; + while (++pdf < 16 && mfi < 5) + mfi = rate * (pdf+1) / quad_parent_rate; + if (mfi > 15) + return -1; + pdf--; + + temp64 = rate*(pdf+1) - quad_parent_rate*mfi; + do_div(temp64, quad_parent_rate/1000000); + mfn = (long)temp64; + + dp_ctl = __raw_readl(pllbase + MXC_PLL_DP_CTL); + /* use dpdck0_2 */ + __raw_writel(dp_ctl | 0x1000L, pllbase + MXC_PLL_DP_CTL); + pll_hfsm = dp_ctl & MXC_PLL_DP_CTL_HFSM; + if (pll_hfsm == 0) { + reg = mfi<<4 | pdf; + __raw_writel(reg, pllbase + MXC_PLL_DP_OP); + __raw_writel(mfd, pllbase + MXC_PLL_DP_MFD); + __raw_writel(mfn, pllbase + MXC_PLL_DP_MFN); + } else { + reg = mfi<<4 | pdf; + __raw_writel(reg, pllbase + MXC_PLL_DP_HFS_OP); + __raw_writel(mfd, pllbase + MXC_PLL_DP_HFS_MFD); + __raw_writel(mfn, pllbase + MXC_PLL_DP_HFS_MFN); + } + + return 0; +} + +static void _clk_pll_recalc(struct clk *clk) +{ + long mfi, mfn, mfd, pdf, ref_clk, mfn_abs; + unsigned long dp_op, dp_mfd, dp_mfn, dp_ctl, pll_hfsm, dbl; + unsigned long pllbase; + s64 temp; + + pllbase = _get_pll_base(clk); + + dp_ctl = __raw_readl(pllbase + MXC_PLL_DP_CTL); + pll_hfsm = dp_ctl & MXC_PLL_DP_CTL_HFSM; + dbl = dp_ctl & MXC_PLL_DP_CTL_DPDCK0_2_EN; + + if (pll_hfsm == 0) { + dp_op = __raw_readl(pllbase + MXC_PLL_DP_OP); + dp_mfd = __raw_readl(pllbase + MXC_PLL_DP_MFD); + dp_mfn = __raw_readl(pllbase + MXC_PLL_DP_MFN); + } else { + dp_op = __raw_readl(pllbase + MXC_PLL_DP_HFS_OP); + dp_mfd = __raw_readl(pllbase + MXC_PLL_DP_HFS_MFD); + dp_mfn = __raw_readl(pllbase + MXC_PLL_DP_HFS_MFN); + } + pdf = dp_op & MXC_PLL_DP_OP_PDF_MASK; + mfi = (dp_op & MXC_PLL_DP_OP_MFI_MASK) >> MXC_PLL_DP_OP_MFI_OFFSET; + mfi = (mfi <= 5) ? 5 : mfi; + mfd = dp_mfd & MXC_PLL_DP_MFD_MASK; + mfn = mfn_abs = dp_mfn & MXC_PLL_DP_MFN_MASK; + /* Sign extend to 32-bits */ + if (mfn >= 0x04000000) { + mfn |= 0xFC000000; + mfn_abs = -mfn; + } + + ref_clk = 2 * clk->parent->rate; + if (dbl != 0) { + ref_clk *= 2; + } + ref_clk /= (pdf + 1); + temp = (u64) ref_clk *mfn_abs; + do_div(temp, mfd + 1); + if (mfn < 0) + temp = -temp; + temp = (ref_clk * mfi) + temp; + + clk->rate = temp; +} + +static int _clk_pll_enable(struct clk *clk) +{ + u32 reg; + u32 pllbase; + + pllbase = _get_pll_base(clk); + reg = __raw_readl(pllbase + MXC_PLL_DP_CTL) | MXC_PLL_DP_CTL_UPEN; + __raw_writel(reg, pllbase + MXC_PLL_DP_CTL); + + /* Wait for lock */ + while (!(__raw_readl(pllbase + MXC_PLL_DP_CTL) & MXC_PLL_DP_CTL_LRF)) ; + + return 0; +} + +static void _clk_pll_disable(struct clk *clk) +{ + u32 reg; + u32 pllbase; + + pllbase = _get_pll_base(clk); + reg = __raw_readl(pllbase + MXC_PLL_DP_CTL) & ~MXC_PLL_DP_CTL_UPEN; + __raw_writel(reg, pllbase + MXC_PLL_DP_CTL); +} + +static struct clk pll1_main_clk = { + .name = "pll1_main_clk", + .parent = &osc_clk, + .recalc = _clk_pll_recalc, + .set_rate = _clk_pll_set_rate, + .enable = _clk_pll_enable, + .disable = _clk_pll_disable, + .flags = RATE_PROPAGATES, +}; + +static int _clk_pll1_sw_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CCSR); + + if (parent == &pll1_main_clk) { + reg &= ~MXC_CCM_CCSR_PLL1_SW_CLK_SEL; + } else { + mux = _get_mux(parent, &lp_apm_clk, NULL, &pll2_sw_clk, + &pll3_sw_clk); + reg = (reg & ~MXC_CCM_CCSR_STEP_SEL_MASK) | + (mux << MXC_CCM_CCSR_STEP_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CCSR); + reg = __raw_readl(MXC_CCM_CCSR); + reg |= MXC_CCM_CCSR_PLL1_SW_CLK_SEL; + } + __raw_writel(reg, MXC_CCM_CCSR); + return 0; +} + +static void _clk_pll1_sw_recalc(struct clk *clk) +{ + u32 reg, div; + div = 1; + reg = __raw_readl(MXC_CCM_CCSR); + + if (clk->parent == &pll2_sw_clk) { + div = ((reg & MXC_CCM_CCSR_PLL2_PODF_MASK) >> + MXC_CCM_CCSR_PLL2_PODF_OFFSET) + 1; + } else if (clk->parent == &pll3_sw_clk) { + div = ((reg & MXC_CCM_CCSR_PLL3_PODF_MASK) >> + MXC_CCM_CCSR_PLL3_PODF_OFFSET) + 1; + } + clk->rate = clk->parent->rate / div; +} + +/* pll1 switch clock */ +static struct clk pll1_sw_clk = { + .name = "pll1_sw_clk", + .parent = &pll1_main_clk, + .set_parent = _clk_pll1_sw_set_parent, + .recalc = _clk_pll1_sw_recalc, + .flags = RATE_PROPAGATES, +}; + +/* same as pll2_main_clk. These two clocks should always be the same */ +static struct clk pll2_sw_clk = { + .name = "pll2", + .parent = &osc_clk, + .recalc = _clk_pll_recalc, + .enable = _clk_pll_enable, + .disable = _clk_pll_disable, + .flags = RATE_PROPAGATES, +}; + +/* same as pll3_main_clk. These two clocks should always be the same */ +static struct clk pll3_sw_clk = { + .name = "pll3", + .parent = &osc_clk, + .recalc = _clk_pll_recalc, + .enable = _clk_pll_enable, + .disable = _clk_pll_disable, + .flags = RATE_PROPAGATES, +}; + +static int _clk_lp_apm_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + if (parent == &osc_clk) { + reg = __raw_readl(MXC_CCM_CCSR) & ~MXC_CCM_CCSR_LP_APM_SEL; + } else if (parent == &fpm_clk) { + reg = __raw_readl(MXC_CCM_CCSR) | MXC_CCM_CCSR_LP_APM_SEL; + } else { + return -EINVAL; + } + __raw_writel(reg, MXC_CCM_CCSR); + + return 0; +} + +static struct clk lp_apm_clk = { + .name = "lp_apm", + .parent = &osc_clk, + .set_parent = _clk_lp_apm_set_parent, + .flags = RATE_PROPAGATES, +}; + +static void _clk_arm_recalc(struct clk *clk) +{ + u32 cacrr, div; + + cacrr = __raw_readl(MXC_CCM_CACRR); + div = (cacrr & MXC_CCM_CACRR_ARM_PODF_MASK) + 1; + clk->rate = clk->parent->rate / div; +} + +static int _clk_cpu_set_rate(struct clk *clk, unsigned long rate) +{ + u32 i; + for (i = 0; i < cpu_wp_nr; i++) { + if (rate == cpu_wp_tbl[i].cpu_rate) + break; + } + if (i > cpu_wp_nr) + return -EINVAL; + cpu_clk_set_wp(i); + + return 0; +} + +static unsigned long _clk_cpu_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 i; + u32 wp; + + for (i = 0; i < cpu_wp_nr; i++) { + if (rate == cpu_wp_tbl[i].cpu_rate) + break; + } + + if (i > cpu_wp_nr) + wp = 0; + + return cpu_wp_tbl[wp].cpu_rate; +} + +static struct clk cpu_clk = { + .name = "cpu_clk", + .parent = &pll1_sw_clk, + .recalc = _clk_arm_recalc, + .set_rate = _clk_cpu_set_rate, + .round_rate = _clk_cpu_round_rate, +}; + +static int _clk_periph_apm_set_parent(struct clk *clk, + struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll3_sw_clk, &lp_apm_clk, NULL); + + reg = __raw_readl(MXC_CCM_CAMR) & ~MXC_CCM_CAMR_PERIPH_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CAMR_PERIPH_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CAMR); + + return 0; +} + +static struct clk periph_apm_clk = { + .name = "periph_apm_clk", + .parent = &pll1_sw_clk, + .set_parent = _clk_periph_apm_set_parent, + .flags = RATE_PROPAGATES, +}; + +static void _clk_main_bus_recalc(struct clk *clk) +{ + clk->rate = clk->parent->rate; +} + +static int _clk_main_bus_set_rate(struct clk *clk, unsigned long rate) +{ + u32 div = 0; + + clk->rate = clk->parent->rate/(div + 1); + return 0; +} +static int _clk_main_bus_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, stat; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.enable(&emi_slow_clk); + if (emi_intr_clk.usecount == 0) + emi_intr_clk.enable(&emi_intr_clk); + + if (ipu_clk[0].usecount == 0) + ipu_clk[0].enable(&ipu_clk[0]); + + if (parent == &pll2_sw_clk) { + reg = __raw_readl(MXC_CCM_CBCDR6) & + ~MXC_CCM_CBCDR6_PERIPH_CLK_SEL; + } else if (parent == &periph_apm_clk) { + reg = __raw_readl(MXC_CCM_CBCDR6) | + MXC_CCM_CBCDR6_PERIPH_CLK_SEL; + } else { + return -EINVAL; + } + __raw_writel(reg, MXC_CCM_CBCDR6); + + /* Set the Load-dividers bit in CCM */ + reg = __raw_readl(MXC_CCM_CCDR); + reg |= MXC_CCM_CCDR_LOAD_DIVIDERS; + __raw_writel(reg, MXC_CCM_CCDR); + + do { + stat = __raw_readl(MXC_CCM_CCDR) & MXC_CCM_CCDR_LOAD_DIVIDERS; + } while (stat); + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.disable(&emi_slow_clk); + if (emi_intr_clk.usecount == 0) + emi_intr_clk.disable(&emi_intr_clk); + + if (ipu_clk[0].usecount == 0) + ipu_clk[0].enable(&ipu_clk[0]); + + return 0; +} + +static struct clk main_bus_clk = { + .name = "main_bus_clk", + .parent = &pll2_sw_clk, + .set_parent = _clk_main_bus_set_parent, + .set_rate = _clk_main_bus_set_rate, + .recalc = _clk_main_bus_recalc, + .flags = RATE_PROPAGATES, +}; + +static int _clk_axi_a_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div, stat; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) + return -EINVAL; + + if (ddr_clk.parent == &axi_a_clk && emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + + reg = __raw_readl(MXC_CCM_CBCDR3); + reg &= ~MXC_CCM_CBCDR3_AXI_A_PODF_MASK; + reg |= (div - 1) << MXC_CCM_CBCDR3_AXI_A_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CBCDR3); + + /* Set the Load-dividers bit in CCM */ + reg = __raw_readl(MXC_CCM_CCDR); + reg |= MXC_CCM_CCDR_LOAD_DIVIDERS; + __raw_writel(reg, MXC_CCM_CCDR); + + do { + stat = __raw_readl(MXC_CCM_CCDR) & MXC_CCM_CCDR_LOAD_DIVIDERS; + } while (stat); + clk->rate = rate; + + if (ddr_clk.parent == &axi_a_clk && emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + + return 0; +} + +static void _clk_axi_a_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR3); + div = ((reg & MXC_CCM_CBCDR3_AXI_A_PODF_MASK) >> + MXC_CCM_CBCDR3_AXI_A_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + +static unsigned long _clk_axi_a_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 8) + div = 8; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + +static struct clk axi_a_clk = { + .name = "axi_a_clk", + .parent = &main_bus_clk, + .recalc = _clk_axi_a_recalc, + .set_rate = _clk_axi_a_set_rate, + .round_rate = _clk_axi_a_round_rate, + .flags = RATE_PROPAGATES, +}; + +static int _clk_axi_b_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div, stat; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) + return -EINVAL; + + if (ddr_clk.parent == &axi_b_clk && emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + + reg = __raw_readl(MXC_CCM_CBCDR4); + reg &= ~MXC_CCM_CBCDR4_AXI_B_PODF_MASK; + reg |= (div - 1) << MXC_CCM_CBCDR4_AXI_B_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CBCDR4); + + /* Set the Load-dividers bit in CCM */ + reg = __raw_readl(MXC_CCM_CCDR); + reg |= MXC_CCM_CCDR_LOAD_DIVIDERS; + __raw_writel(reg, MXC_CCM_CCDR); + + do { + stat = __raw_readl(MXC_CCM_CCDR) & MXC_CCM_CCDR_LOAD_DIVIDERS; + } while (stat); + clk->rate = rate; + + if (ddr_clk.parent == &axi_c_clk && emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + + return 0; +} + +static void _clk_axi_b_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR4); + div = ((reg & MXC_CCM_CBCDR4_AXI_B_PODF_MASK) >> + MXC_CCM_CBCDR4_AXI_B_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + +static unsigned long _clk_axi_b_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 8) + div = 8; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + +static struct clk axi_b_clk = { + .name = "axi_b_clk", + .parent = &main_bus_clk, + .recalc = _clk_axi_b_recalc, + .set_rate = _clk_axi_b_set_rate, + .round_rate = _clk_axi_b_round_rate, + .flags = RATE_PROPAGATES, +}; + +static int _clk_axi_c_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div, stat; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) + return -EINVAL; + + if (ddr_clk.parent == &axi_c_clk && emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + + reg = __raw_readl(MXC_CCM_CBCDR5); + reg &= ~MXC_CCM_CBCDR5_AXI_C_PODF_MASK; + reg |= (div - 1) << MXC_CCM_CBCDR5_AXI_C_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CBCDR5); + + /* Set the Load-dividers bit in CCM */ + reg = __raw_readl(MXC_CCM_CCDR); + reg |= MXC_CCM_CCDR_LOAD_DIVIDERS; + __raw_writel(reg, MXC_CCM_CCDR); + + do { + stat = __raw_readl(MXC_CCM_CCDR) & MXC_CCM_CCDR_LOAD_DIVIDERS; + } while (stat); + clk->rate = rate; + + if (ddr_clk.parent == &axi_c_clk && emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + + return 0; +} + +static void _clk_axi_c_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR5); + div = ((reg & MXC_CCM_CBCDR5_AXI_C_PODF_MASK) >> + MXC_CCM_CBCDR5_AXI_C_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + +static unsigned long _clk_axi_c_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 8) + div = 8; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + +static struct clk axi_c_clk = { + .name = "axi_c_clk", + .parent = &main_bus_clk, + .recalc = _clk_axi_c_recalc, + .set_rate = _clk_axi_c_set_rate, + .round_rate = _clk_axi_c_round_rate, + .flags = RATE_PROPAGATES, +}; + +static void _clk_ahb_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR2); + div = ((reg & MXC_CCM_CBCDR2_AHB_PODF_MASK) >> + MXC_CCM_CBCDR2_AHB_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + +static int _clk_ahb_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) + return -EINVAL; + + reg = __raw_readl(MXC_CCM_CBCDR2); + reg &= ~MXC_CCM_CBCDR2_AHB_PODF_MASK; + reg |= (div - 1) << MXC_CCM_CBCDR2_AHB_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CBCDR2); + + /* Set the Load-dividers bit in CCM */ + reg = __raw_readl(MXC_CCM_CCDR); + reg |= MXC_CCM_CCDR_LOAD_DIVIDERS; + __raw_writel(reg, MXC_CCM_CCDR); + clk->rate = rate; + + return 0; +} + +static unsigned long _clk_ahb_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 8) + div = 8; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + +static struct clk ahb_clk = { + .name = "ahb_clk", + .parent = &main_bus_clk, + .recalc = _clk_ahb_recalc, + .set_rate = _clk_ahb_set_rate, + .round_rate = _clk_ahb_round_rate, + .flags = RATE_PROPAGATES, +}; + +static struct clk ahb_max_clk = { + .name = "max_clk", + .parent = &ahb_clk, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG12_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable_inwait, +}; + +static int _clk_emi_core_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div, stat; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) + return -EINVAL; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.enable(&emi_slow_clk); + if (emi_intr_clk.usecount == 0) + emi_intr_clk.enable(&emi_intr_clk); + + reg = __raw_readl(MXC_CCM_CBCDR6); + reg &= ~MXC_CCM_CBCDR6_EMI_PODF_MASK; + reg |= (div - 1) << MXC_CCM_CBCDR6_EMI_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CBCDR6); + + /* Set the Load-dividers bit in CCM */ + reg = __raw_readl(MXC_CCM_CCDR); + reg |= MXC_CCM_CCDR_LOAD_DIVIDERS; + __raw_writel(reg, MXC_CCM_CCDR); + + do { + stat = __raw_readl(MXC_CCM_CCDR) & MXC_CCM_CCDR_LOAD_DIVIDERS; + } while (stat); + + clk->rate = rate; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.disable(&emi_slow_clk); + if (emi_intr_clk.usecount == 0) + emi_intr_clk.disable(&emi_intr_clk); + + return 0; +} + +static void _clk_emi_core_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR6); + div = ((reg & MXC_CCM_CBCDR6_EMI_PODF_MASK) >> + MXC_CCM_CBCDR6_EMI_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + +static int _clk_emi_core_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + u32 stat; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.enable(&emi_slow_clk); + if (emi_intr_clk.usecount == 0) + emi_intr_clk.enable(&emi_intr_clk); + + if ((ipu_clk[0].parent == &emi_core_clk) && + (ipu_clk[0].usecount == 0)) + ipu_clk[0].enable(&ipu_clk[0]); + + reg = __raw_readl(MXC_CCM_CBCDR6); + if (parent == &ahb_clk) { + reg |= MXC_CCM_CBCDR6_EMI_CLK_SEL; + } else if (parent == &main_bus_clk) { + reg &= ~MXC_CCM_CBCDR6_EMI_CLK_SEL; + } else { + BUG(); + } + __raw_writel(reg, MXC_CCM_CBCDR6); + + do { + stat = __raw_readl(MXC_CCM_CCDR) & MXC_CCM_CCDR_LOAD_DIVIDERS; + } while (stat); + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.disable(&emi_slow_clk); + if (emi_intr_clk.usecount == 0) + emi_intr_clk.disable(&emi_intr_clk); + if ((ipu_clk[0].parent == &emi_core_clk) && + (ipu_clk[0].usecount == 0)) + ipu_clk[0].disable(&ipu_clk[0]); + + return 0; +} + +static unsigned long _clk_emi_core_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 8) + div = 8; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + +static struct clk emi_core_clk = { + .name = "emi_core_clk", + .set_parent = _clk_emi_core_set_parent, + .recalc = _clk_emi_core_recalc, + .set_rate = _clk_emi_core_set_rate, + .round_rate = _clk_emi_core_round_rate, + .flags = RATE_PROPAGATES, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG11_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable_inwait, +}; + +static struct clk ahbmux1_clk = { + .name = "ahbmux1_clk", + .id = 0, + .parent = &ahb_clk, + .secondary = &ahb_max_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG4_OFFSET, + .disable = _clk_disable_inwait, +}; + +static struct clk ahbmux2_clk = { + .name = "ahbmux2_clk", + .id = 0, + .parent = &emi_core_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG7_OFFSET, + .disable = _clk_disable_inwait, +}; + +static struct clk emi_fast_clk = { + .name = "emi_fast_clk", + .parent = &emi_core_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG12_OFFSET, + .disable = _clk_disable_inwait, +}; + +static struct clk emi_slow_clk = { + .name = "emi_slow_clk", + .parent = &emi_core_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG13_OFFSET, + .disable = _clk_disable_inwait, +}; + +static int _clk_emi_intr_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div, stat; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 4)) + return -EINVAL; + + reg = __raw_readl(MXC_CCM_CBCDR7); + reg &= ~MXC_CCM_CBCDR7_IPG_INIT_MEM_PODF_MASK; + reg |= (div - 1) << MXC_CCM_CBCDR7_IPG_INT_MEM_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CBCDR7); + + /* Set the Load-dividers bit in CCM */ + reg = __raw_readl(MXC_CCM_CCDR); + reg |= MXC_CCM_CCDR_LOAD_DIVIDERS; + __raw_writel(reg, MXC_CCM_CCDR); + + do { + stat = __raw_readl(MXC_CCM_CCDR) & MXC_CCM_CCDR_LOAD_DIVIDERS; + } while (stat); + + clk->rate = rate; + + return 0; +} + +static void _clk_emi_intr_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR7); + div = ((reg & MXC_CCM_CBCDR7_IPG_INIT_MEM_PODF_MASK) >> + MXC_CCM_CBCDR7_IPG_INT_MEM_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + +static unsigned long _clk_emi_intr_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 4) + div = 4; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + +static struct clk emi_intr_clk = { + .name = "emi_intr_clk", + .parent = &emi_core_clk, + .secondary = &ahbmux2_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG14_OFFSET, + .disable = _clk_disable_inwait, + .recalc = _clk_emi_intr_recalc, + .set_rate = _clk_emi_intr_set_rate, + .round_rate = _clk_emi_intr_round_rate, +}; + +static void _clk_ipg_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR2); + div = ((reg & MXC_CCM_CBCDR2_IPG_PODF_MASK) >> + MXC_CCM_CBCDR2_IPG_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + +static struct clk ipg_clk = { + .name = "ipg_clk", + .parent = &ahb_clk, + .recalc = _clk_ipg_recalc, + .flags = RATE_PROPAGATES, +}; + +static void _clk_ipg_per_recalc(struct clk *clk) +{ + u32 reg, prediv1, prediv2, podf; + + if (clk->parent == &main_bus_clk || clk->parent == &lp_apm_clk) { + /* the main_bus_clk is the one before the DVFS engine */ + reg = __raw_readl(MXC_CCM_CBCDR2); + prediv1 = ((reg & MXC_CCM_CBCDR2_PERCLK_PRED1_MASK) >> + MXC_CCM_CBCDR2_PERCLK_PRED1_OFFSET) + 1; + prediv2 = ((reg & MXC_CCM_CBCDR2_PERCLK_PRED2_MASK) >> + MXC_CCM_CBCDR2_PERCLK_PRED2_OFFSET) + 1; + podf = ((reg & MXC_CCM_CBCDR2_PERCLK_PODF_MASK) >> + MXC_CCM_CBCDR2_PERCLK_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / (prediv1 * prediv2 * podf); + } else if (clk->parent == &ipg_clk) { + clk->rate = ipg_clk.rate; + } else { + BUG(); + } +} + +static int _clk_ipg_per_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CSCMR1); + mux = _get_mux(parent, &main_bus_clk, &lp_apm_clk, &ipg_clk, NULL); + if (mux == 2) { + reg |= MXC_CCM_CSCMR1_PERCLK_IPG_CLK_SEL; + } else { + reg &= ~MXC_CCM_CSCMR1_PERCLK_IPG_CLK_SEL; + if (mux == 0) { + reg &= ~MXC_CCM_CSCMR1_PERCLK_LP_APM_CLK_SEL; + } else { + reg |= MXC_CCM_CSCMR1_PERCLK_LP_APM_CLK_SEL; + } + } + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk ipg_perclk = { + .name = "ipg_perclk", + .parent = &ipg_clk, + .recalc = _clk_ipg_per_recalc, + .set_parent = _clk_ipg_per_set_parent, + .flags = RATE_PROPAGATES, +}; + +static struct clk aips_tz1_clk = { + .name = "aips_tz1_clk", + .parent = &ahb_clk, + .secondary = &ahb_max_clk, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG13_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable_inwait, +}; + +static struct clk aips_tz2_clk = { + .name = "aips_tz2_clk", + .parent = &ahb_clk, + .secondary = &ahb_max_clk, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG14_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable_inwait, +}; + +static struct clk gpc_dvfs_clk = { + .name = "gpc_dvfs_clk", + .parent = &aips_tz2_clk, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG15_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, +}; + +static struct clk sdma_clk[] = { + { + .name = "sdma_ahb_clk", + .parent = &ahb_clk, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG0_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "sdma_ipg_clk", + .parent = &ipg_clk, +#ifdef CONFIG_SDMA_IRAM + .secondary = &emi_intr_clk, +#endif + }, +}; + +static int _clk_tve_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_CSCMR1); + + if (parent == &pll3_sw_clk) { + reg &= ~(MXC_CCM_CSCMR1_TVE_CLK_SEL); + } else if (parent == &osc_clk) { + reg |= MXC_CCM_CSCMR1_TVE_CLK_SEL; + reg &= MXC_CCM_CSCMR1_TVE_EXT_CLK_SEL; + } else if (parent == &ckih_clk) { + reg |= MXC_CCM_CSCMR1_TVE_CLK_SEL; + reg |= MXC_CCM_CSCMR1_TVE_EXT_CLK_SEL; + } else { + BUG(); + } + + __raw_writel(reg, MXC_CCM_CSCMR1); + return 0; +} + +static void _clk_tve_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if ((reg & MXC_CCM_CSCMR1_TVE_CLK_SEL) == 0) { + reg = __raw_readl(MXC_CCM_CDCDR) & + MXC_CCM_CDCDR_TVE_CLK_PRED_MASK; + div = (reg >> MXC_CCM_CDCDR_TVE_CLK_PRED_OFFSET) + 1; + clk->rate = clk->parent->rate / div; + } else { + clk->rate = clk->parent->rate; + } +} + +static unsigned long _clk_tve_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if (reg & MXC_CCM_CSCMR1_TVE_CLK_SEL) { + return -EINVAL; + } + + div = clk->parent->rate / rate; + if (div > 8) + div = 8; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + +static int _clk_tve_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if (reg & MXC_CCM_CSCMR1_TVE_CLK_SEL) { + return -EINVAL; + } + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) { + return -EINVAL; + } + + div--; + reg = __raw_readl(MXC_CCM_CDCDR) & ~MXC_CCM_CDCDR_TVE_CLK_PRED_MASK; + reg |= div << MXC_CCM_CDCDR_TVE_CLK_PRED_OFFSET; + __raw_writel(reg, MXC_CCM_CDCDR); + clk->rate = rate; + return 0; +} + +static struct clk tve_clk = { + .name = "tve_clk", + .parent = &pll3_sw_clk, + .secondary = &aips_tz1_clk, + .set_parent = _clk_tve_set_parent, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG10_OFFSET, + .recalc = _clk_tve_recalc, + .round_rate = _clk_tve_round_rate, + .set_rate = _clk_tve_set_rate, + .enable = _clk_enable, + .disable = _clk_disable, + .flags = RATE_PROPAGATES, +}; + +static struct clk spba_clk = { + .name = "spba_clk", + .parent = &ipg_clk, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG1_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, +}; + +static void _clk_uart_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CSCDR1); + prediv = ((reg & MXC_CCM_CSCDR1_UART_CLK_PRED_MASK) >> + MXC_CCM_CSCDR1_UART_CLK_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CSCDR1_UART_CLK_PODF_MASK) >> + MXC_CCM_CSCDR1_UART_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} + +static int _clk_uart_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &lp_apm_clk); + reg = __raw_readl(MXC_CCM_CSCMR1) & ~MXC_CCM_CSCMR1_UART_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_UART_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static int _clk_uart_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div, post_div = 1; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 64) || (div == 1)) + return -EINVAL; + + if (div > 8) { + int i = 1; + while ((div / (2 * i)) > 8) + i++; + post_div = i * 2; + div = div / post_div; + } + + reg = __raw_readl(MXC_CCM_CSCDR1); + reg &= ~MXC_CCM_CSCDR1_UART_CLK_PODF_MASK; + reg &= ~MXC_CCM_CSCDR1_UART_CLK_PRED_MASK; + reg |= (div - 1) << MXC_CCM_CSCDR1_UART_CLK_PRED_OFFSET | + (post_div - 1) << MXC_CCM_CSCDR1_UART_CLK_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CSCDR1); + clk->rate = rate; + + return 0; +} + +static unsigned long _clk_uart_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 64) + div = 64; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + +static struct clk uart_main_clk = { + .name = "uart_main_clk", + .parent = &pll2_sw_clk, + .secondary = &emi_fast_clk, + .recalc = _clk_uart_recalc, + .set_parent = _clk_uart_set_parent, + .set_rate = _clk_uart_set_rate, + .round_rate = _clk_uart_round_rate, + .flags = RATE_PROPAGATES, +}; + +static struct clk uart1_clk[] = { + { + .name = "uart_clk", + .id = 0, + .parent = &uart_main_clk, + .secondary = &uart1_clk[1], + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG5_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "uart_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .secondary = &aips_tz2_clk, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG4_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, +}; + +static struct clk uart2_clk[] = { + { + .name = "uart_clk", + .id = 1, + .parent = &uart_main_clk, + .secondary = &uart2_clk[1], + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG7_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "uart_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .secondary = &aips_tz2_clk, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG6_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, +}; + +static struct clk uart3_clk[] = { + { + .name = "uart_clk", + .id = 2, + .parent = &uart_main_clk, + .secondary = &uart3_clk[1], + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG9_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "uart_ipg_clk", + .id = 2, + .parent = &ipg_clk, + .secondary = &spba_clk, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG8_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, +}; + +static struct clk gpt_clk[] = { + { + .name = "gpt_clk", + .id = 0, + .parent = &ipg_perclk, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG8_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + .secondary = &gpt_clk[1], + }, + { + .name = "gpt_ipg_clk", + .parent = &ipg_clk, + .id = 0, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG7_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "gpt_32k_clk", + .id = 0, + .parent = &ckil_clk, + }, +}; + +static struct clk i2c_clk[] = { + { + .name = "i2c_clk", + .id = 0, + .parent = &ipg_perclk, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG14_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "i2c_clk", + .id = 1, + .parent = &ipg_perclk, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG15_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "i2c_clk", + .id = 2, + .parent = &ipg_perclk, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG0_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, +}; + +static void _clk_cspi_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CSCDR2); + prediv = ((reg & MXC_CCM_CSCDR2_CSPI_CLK_PRED_MASK) >> + MXC_CCM_CSCDR2_CSPI_CLK_PRED_OFFSET) + 1; + if (prediv == 1) + BUG(); + podf = ((reg & MXC_CCM_CSCDR2_CSPI_CLK_PODF_MASK) >> + MXC_CCM_CSCDR2_CSPI_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} + +static int _clk_cspi_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &lp_apm_clk); + reg = __raw_readl(MXC_CCM_CSCMR1) & ~MXC_CCM_CSCMR1_CSPI_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_CSPI_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static int _clk_cspi_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div, post_div = 1; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 512) || (div == 1)) + return -EINVAL; + + if (div > 8) { + int i = 1; + while ((div / (2 * i)) > 8) + i++; + post_div = i * 2; + div = div / post_div; + } + + reg = __raw_readl(MXC_CCM_CSCDR2); + reg &= ~MXC_CCM_CSCDR2_CSPI_CLK_PODF_MASK; + reg &= ~MXC_CCM_CSCDR2_CSPI_CLK_PRED_MASK; + reg |= (div - 1) << MXC_CCM_CSCDR2_CSPI_CLK_PRED_OFFSET; + reg |= (post_div - 1) << MXC_CCM_CSCDR2_CSPI_CLK_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CSCDR2); + clk->rate = rate; + + return 0; +} + +static unsigned long _clk_cspi_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 512) + div = 8; + else if (div == 1) + div = 2; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + +static struct clk cspi_main_clk = { + .name = "cspi_main_clk", + .parent = &pll3_sw_clk, + .recalc = _clk_cspi_recalc, + .set_parent = _clk_cspi_set_parent, + .set_rate = _clk_cspi_set_rate, + .round_rate = _clk_cspi_round_rate, + .flags = RATE_PROPAGATES, +}; + +static struct clk cspi1_clk[] = { + { + .name = "cspi_clk", + .id = 0, + .parent = &cspi_main_clk, + .secondary = &cspi1_clk[1], + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG8_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "cspi_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .secondary = &aips_tz2_clk, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG7_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, +}; + +static struct clk cspi2_clk[] = { + { + .name = "cspi_clk", + .id = 1, + .parent = &cspi_main_clk, + .secondary = &cspi2_clk[1], + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG10_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "cspi_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .secondary = &spba_clk, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG9_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, +}; + +static struct clk cspi3_clk[] = { + { + .name = "cspi_clk", + .id = 2, + .parent = &cspi_main_clk, + .secondary = &cspi3_clk[1], + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG12_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "cspi_ipg_clk", + .id = 2, + .parent = &ipg_clk, + .secondary = &aips_tz2_clk, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG11_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, +}; + +static int _clk_ssi_lp_apm_set_parent(struct clk *clk, + struct clk *parent) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_CSCMR1); + + if (parent == &ckih_clk) { + reg &= ~MXC_CCM_CSCMR1_SSI_APM_CLK_SEL; + } else if (parent == &lp_apm_clk) { + reg |= MXC_CCM_CSCMR1_SSI_APM_CLK_SEL; + } else { + BUG(); + } + + __raw_writel(reg, MXC_CCM_CSCMR1); + return 0; +} + +static struct clk ssi_lp_apm_clk = { + .name = "ssi_lp_apm_clk", + .parent = &ckih_clk, + .set_parent = _clk_ssi_lp_apm_set_parent, +}; + +static void _clk_ssi1_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CS1CDR); + prediv = ((reg & MXC_CCM_CS1CDR_SSI1_CLK_PRED_MASK) >> + MXC_CCM_CS1CDR_SSI1_CLK_PRED_OFFSET) + 1; + if (prediv == 1) + BUG(); + podf = ((reg & MXC_CCM_CS1CDR_SSI1_CLK_PODF_MASK) >> + MXC_CCM_CS1CDR_SSI1_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} +static int _clk_ssi1_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, + &pll3_sw_clk, &ssi_lp_apm_clk); + reg = __raw_readl(MXC_CCM_CSCMR1) & ~MXC_CCM_CSCMR1_SSI1_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_SSI1_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk ssi1_clk[] = { + { + .name = "ssi_clk", + .id = 0, + .parent = &pll3_sw_clk, + .set_parent = _clk_ssi1_set_parent, + .secondary = &ssi1_clk[1], + .recalc = _clk_ssi1_recalc, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG8_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "ssi_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .secondary = &ssi1_clk[2], + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG7_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "ssi_dep_clk", + .id = 0, + .parent = &aips_tz2_clk, +#ifdef CONFIG_SND_MXC_SOC_IRAM + .secondary = &emi_intr_clk, +#else + .secondary = &emi_fast_clk, +#endif + }, +}; + +static void _clk_ssi2_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CS2CDR); + prediv = ((reg & MXC_CCM_CS2CDR_SSI2_CLK_PRED_MASK) >> + MXC_CCM_CS2CDR_SSI2_CLK_PRED_OFFSET) + 1; + if (prediv == 1) + BUG(); + podf = ((reg & MXC_CCM_CS2CDR_SSI2_CLK_PODF_MASK) >> + MXC_CCM_CS2CDR_SSI2_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} + +static int _clk_ssi2_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, + &pll3_sw_clk, &ssi_lp_apm_clk); + reg = __raw_readl(MXC_CCM_CSCMR1) & ~MXC_CCM_CSCMR1_SSI2_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_SSI2_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk ssi2_clk[] = { + { + .name = "ssi_clk", + .id = 1, + .parent = &pll3_sw_clk, + .set_parent = _clk_ssi2_set_parent, + .secondary = &ssi2_clk[1], + .recalc = _clk_ssi2_recalc, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG10_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "ssi_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .secondary = &ssi2_clk[2], + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG9_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "ssi_dep_clk", + .id = 1, + .parent = &spba_clk, +#ifdef CONFIG_SND_MXC_SOC_IRAM + .secondary = &emi_intr_clk, +#else + .secondary = &emi_fast_clk, +#endif + }, +}; + +static void _clk_ssi_ext1_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + clk->rate = clk->parent->rate; + reg = __raw_readl(MXC_CCM_CSCMR1); + if ((reg & MXC_CCM_CSCMR1_SSI_EXT1_COM_CLK_SEL) == 0) { + reg = __raw_readl(MXC_CCM_CSECDR1); + prediv = ((reg & MXC_CCM_CSECDR1_SSI_EXT1_CLK_PRED_MASK) >> + MXC_CCM_CSECDR1_SSI_EXT1_CLK_PRED_OFFSET) + 1; + if (prediv == 1) + BUG(); + podf = ((reg & MXC_CCM_CSECDR1_SSI_EXT1_CLK_PODF_MASK) >> + MXC_CCM_CSECDR1_SSI_EXT1_CLK_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / (prediv * podf); + } +} + +static int _clk_ssi_ext1_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if (parent == &ssi1_clk[0]) { + reg |= MXC_CCM_CSCMR1_SSI_EXT1_COM_CLK_SEL; + } else { + reg &= ~MXC_CCM_CSCMR1_SSI_EXT1_COM_CLK_SEL; + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &ssi_lp_apm_clk); + reg = (reg & ~MXC_CCM_CSCMR1_SSI_EXT1_CLK_SEL_MASK) | + (mux << MXC_CCM_CSCMR1_SSI_EXT1_CLK_SEL_OFFSET); + } + + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk ssi_ext1_clk = { + .name = "ssi_ext1_clk", + .parent = &pll3_sw_clk, + .set_parent = _clk_ssi_ext1_set_parent, + .recalc = _clk_ssi_ext1_recalc, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG11_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, +}; + +static void _clk_ssi_ext2_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + clk->rate = clk->parent->rate; + reg = __raw_readl(MXC_CCM_CSCMR1); + if ((reg & MXC_CCM_CSCMR1_SSI_EXT2_COM_CLK_SEL) == 0) { + reg = __raw_readl(MXC_CCM_CSECDR2); + prediv = ((reg & MXC_CCM_CSECDR2_SSI_EXT2_CLK_PRED_MASK) >> + MXC_CCM_CSECDR2_SSI_EXT2_CLK_PRED_OFFSET) + 1; + if (prediv == 1) + BUG(); + podf = ((reg & MXC_CCM_CSECDR2_SSI_EXT2_CLK_PODF_MASK) >> + MXC_CCM_CSECDR2_SSI_EXT2_CLK_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / (prediv * podf); + } +} + +static int _clk_ssi_ext2_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if (parent == &ssi2_clk[0]) { + reg |= MXC_CCM_CSCMR1_SSI_EXT2_COM_CLK_SEL; + } else { + reg &= ~MXC_CCM_CSCMR1_SSI_EXT2_COM_CLK_SEL; + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &ssi_lp_apm_clk); + reg = (reg & ~MXC_CCM_CSCMR1_SSI_EXT2_CLK_SEL_MASK) | + (mux << MXC_CCM_CSCMR1_SSI_EXT2_CLK_SEL_OFFSET); + } + + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk ssi_ext2_clk = { + .name = "ssi_ext2_clk", + .parent = &pll3_sw_clk, + .set_parent = _clk_ssi_ext2_set_parent, + .recalc = _clk_ssi_ext2_recalc, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG12_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, +}; + +static struct clk iim_clk = { + .name = "iim_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG15_OFFSET, + .disable = _clk_disable, +}; + +static struct clk tmax1_clk = { + .name = "tmax1_clk", + .id = 0, + .parent = &ahb_clk, + .secondary = &ahb_max_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG0_OFFSET, + .disable = _clk_disable, +}; + +static struct clk tmax2_clk = { + .name = "tmax2_clk", + .id = 0, + .parent = &ahb_clk, + .secondary = &ahb_max_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG1_OFFSET, + .disable = _clk_disable, +}; + +static void _clk_usboh2_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CSCDR1); + prediv = ((reg & MXC_CCM_CSCDR1_USBOH2_CLK_PRED_MASK) >> + MXC_CCM_CSCDR1_USBOH2_CLK_PRED_OFFSET) + 1; + if (prediv == 1) + BUG(); + podf = ((reg & MXC_CCM_CSCDR1_USBOH2_CLK_PODF_MASK) >> + MXC_CCM_CSCDR1_USBOH2_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} + +static int _clk_usboh2_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &lp_apm_clk); + reg = __raw_readl(MXC_CCM_CSCMR1) & ~MXC_CCM_CSCMR1_USBOH2_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_USBOH2_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +/* + * This is USB core clock. + ** need access DDR/iram, TMAX + */ +static struct clk usb_core_clk[] = { + { + .name = "usb_ahb_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG11_OFFSET, + .disable = _clk_disable, + .secondary = &usb_core_clk[1], + }, + { + .name = "usb_tmax_clk", + .parent = &tmax1_clk, + .secondary = &usb_core_clk[2], + }, + { + .name = "usb_ddr_clk", + .parent = &emi_fast_clk, +#if defined CONFIG_USB_STATIC_IRAM_PPH || defined CONFIG_USB_STATIC_IRAM + .secondary = &usb_core_clk[3], +#endif + }, + /* iram patch, need access internal ram */ + { + .name = "usb_iram_clk", + .parent = &emi_intr_clk, + }, +}; + +/* used for connecting external PHY */ +static struct clk usboh2_clk = { + .name = "usboh2_clk", + .parent = &pll3_sw_clk, + .set_parent = _clk_usboh2_set_parent, + .recalc = _clk_usboh2_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG12_OFFSET, + .disable = _clk_disable, +}; + +static void _clk_usb_phy_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + if (clk->parent == &pll3_sw_clk) { + reg = __raw_readl(MXC_CCM_CDCDR); + prediv = ((reg & MXC_CCM_CDCDR_USB_PHY_PRED_MASK) >> + MXC_CCM_CDCDR_USB_PHY_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CDCDR_USB_PHY_PODF_MASK) >> + MXC_CCM_CDCDR_USB_PHY_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); + } else + clk->rate = clk->parent->rate; +} + +static int _clk_usb_phy_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if (parent == &osc_clk) { + reg &= ~MXC_CCM_CSCMR1_USB_PHY_CLK_SEL; + } else if (parent == &pll3_sw_clk) { + reg |= MXC_CCM_CSCMR1_USB_PHY_CLK_SEL; + } else { + BUG(); + } + + __raw_writel(reg, MXC_CCM_CSCMR1); + return 0; +} + +static struct clk usb_phy_clk = { + .name = "usb_phy_clk", + .parent = &osc_clk, + .set_parent = _clk_usb_phy_set_parent, + .recalc = _clk_usb_phy_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG6_OFFSET, + .disable = _clk_disable, +}; + +static struct clk esdhc_dep_clks = { + .name = "sd_dep_clk", + .parent = &spba_clk, + .secondary = &emi_fast_clk, +}; + + +static void _clk_esdhc1_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CSCDR1); + prediv = ((reg & MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PRED_MASK) >> + MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PODF_MASK) >> + MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} + +static int _clk_esdhc1_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &lp_apm_clk); + reg = __raw_readl(MXC_CCM_CSCMR1) & + ~MXC_CCM_CSCMR1_ESDHC1_MSHC1_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_ESDHC1_MSHC1_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk esdhc1_clk[] = { + { + .name = "esdhc_clk", + .id = 0, + .parent = &pll3_sw_clk, + .set_parent = _clk_esdhc1_set_parent, + .recalc = _clk_esdhc1_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG14_OFFSET, + .disable = _clk_disable, + .secondary = &esdhc1_clk[1], + }, + { + .name = "esdhc_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG13_OFFSET, + .disable = _clk_disable, + .secondary = &esdhc1_clk[2], + }, + { + .name = "esdhc1_sec_clk", + .parent = &tmax2_clk, + .secondary = &esdhc_dep_clks, + }, +}; + +static void _clk_esdhc2_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CSCDR1); + prediv = ((reg & MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PRED_MASK) >> + MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PODF_MASK) >> + MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} + +static int _clk_esdhc2_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &lp_apm_clk); + reg = __raw_readl(MXC_CCM_CSCMR1) & + ~MXC_CCM_CSCMR1_ESDHC2_MSHC2_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_ESDHC2_MSHC2_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk esdhc2_clk[] = { + { + .name = "esdhc_clk", + .id = 1, + .parent = &pll3_sw_clk, + .set_parent = _clk_esdhc2_set_parent, + .recalc = _clk_esdhc2_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG0_OFFSET, + .disable = _clk_disable, + .secondary = &esdhc2_clk[1], + }, + { + .name = "esdhc_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG15_OFFSET, + .disable = _clk_disable, + .secondary = &esdhc2_clk[2], + }, + { + .name = "esdhc2_sec_clk", + .parent = &ahb_max_clk, + .secondary = &esdhc_dep_clks, + }, +}; + +static int _clk_esdhc3_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if (parent == &esdhc1_clk[0]) { + reg &= ~MXC_CCM_CSCMR1_ESDHC3_CLK_SEL; + } else if (parent == &esdhc2_clk[0]) { + reg |= MXC_CCM_CSCMR1_ESDHC3_CLK_SEL; + } else { + BUG(); + } + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk esdhc3_clk[] = { + { + .name = "esdhc_clk", + .id = 2, + .parent = &esdhc1_clk[0], + .set_parent = _clk_esdhc3_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG2_OFFSET, + .disable = _clk_disable, + .secondary = &esdhc3_clk[1], + }, + { + .name = "esdhc_ipg_clk", + .id = 2, + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG1_OFFSET, + .disable = _clk_disable, + .secondary = &esdhc3_clk[2], + }, + { + .name = "esdhc3_sec_clk", + .parent = &ahb_max_clk, + .secondary = &esdhc_dep_clks, + }, + +}; + +static void _clk_nfc_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR7); + div = ((reg & MXC_CCM_CBCDR7_NFC_PODF_MASK) >> + MXC_CCM_CBCDR7_NFC_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + +static int _clk_nfc_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div, stat; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) + return -EINVAL; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.enable(&emi_slow_clk); + if (emi_intr_clk.usecount == 0) + emi_intr_clk.enable(&emi_intr_clk); + + reg = __raw_readl(MXC_CCM_CBCDR7); + reg &= ~MXC_CCM_CBCDR7_NFC_PODF_MASK; + reg |= (div - 1) << MXC_CCM_CBCDR7_NFC_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CBCDR7); + + /* Set the Load-dividers bit in CCM */ + reg = __raw_readl(MXC_CCM_CCDR); + reg |= MXC_CCM_CCDR_LOAD_DIVIDERS; + __raw_writel(reg, MXC_CCM_CCDR); + + do { + stat = __raw_readl(MXC_CCM_CCDR) & MXC_CCM_CCDR_LOAD_DIVIDERS; + } while (stat); + clk->rate = rate; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.disable(&emi_slow_clk); + if (emi_intr_clk.usecount == 0) + emi_intr_clk.disable(&emi_intr_clk); + + return 0; +} + +static unsigned long _clk_nfc_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 8) + div = 8; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + +static struct clk nfc_clk = { + .name = "nfc_clk", + .parent = &emi_core_clk, + .secondary = &emi_slow_clk, + .recalc = _clk_nfc_recalc, + .set_rate = _clk_nfc_set_rate, + .round_rate = _clk_nfc_round_rate, +}; + +static int _clk_spdif_xtal_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if (parent == &osc_clk) { + reg &= ~MXC_CCM_CSCMR1_SPDIF_CLK_SEL; + } else if (parent == &ckih_clk) { + reg |= MXC_CCM_CSCMR1_SPDIF_CLK_SEL; + } else { + BUG(); + } + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk spdif_xtal_clk = { + .name = "spdif_xtal_clk", + .parent = &osc_clk, + .set_parent = _clk_spdif_xtal_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG10_OFFSET, + .disable = _clk_disable, +}; + +static int _clk_spdif0_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CSCMR2); + reg |= MXC_CCM_CSCMR2_SPDIF0_COM; + if (parent != &ssi1_clk[0]) { + reg &= ~MXC_CCM_CSCMR2_SPDIF0_COM; + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &spdif_xtal_clk); + reg = (reg & ~MXC_CCM_CSCMR2_SPDIF0_CLK_SEL_MASK) | + (mux << MXC_CCM_CSCMR2_SPDIF0_CLK_SEL_OFFSET); + } + __raw_writel(reg, MXC_CCM_CSCMR2); + + return 0; +} + +static void _clk_spdif0_recalc(struct clk *clk) +{ + u32 reg, pred, podf; + + if (clk->parent == &ssi1_clk[0]) { + clk->rate = clk->parent->rate; + } else { + reg = __raw_readl(MXC_CCM_CDCDR); + pred = ((reg & MXC_CCM_CDCDR_SPDIF0_CLK_PRED_MASK) >> + MXC_CCM_CDCDR_SPDIF0_CLK_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CDCDR_SPDIF0_CLK_PODF_MASK) >> + MXC_CCM_CDCDR_SPDIF0_CLK_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / (pred * podf); + } +} + +static struct clk spdif0_clk[] = { + { + .name = "spdif_clk", + .id = 0, + .parent = &pll3_sw_clk, + .secondary = &spdif0_clk[1], + .set_parent = _clk_spdif0_set_parent, + .recalc = _clk_spdif0_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG11_OFFSET, + .disable = _clk_disable, + }, + { + .name = "spdif_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .secondary = &spba_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG10_OFFSET, + .disable = _clk_disable, + }, +}; + +static int _clk_spdif1_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CSCMR2); + reg |= MXC_CCM_CSCMR2_SPDIF1_COM; + if (parent != &ssi2_clk[0]) { + reg &= ~MXC_CCM_CSCMR2_SPDIF1_COM; + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &spdif_xtal_clk); + reg = (reg & ~MXC_CCM_CSCMR2_SPDIF1_CLK_SEL_MASK) | + (mux << MXC_CCM_CSCMR2_SPDIF1_CLK_SEL_OFFSET); + } + __raw_writel(reg, MXC_CCM_CSCMR2); + + return 0; +} + +static void _clk_spdif1_recalc(struct clk *clk) +{ + u32 reg, pred, podf; + + if (clk->parent == &ssi2_clk[0]) { + clk->rate = clk->parent->rate; + } else { + reg = __raw_readl(MXC_CCM_CDCDR); + pred = ((reg & MXC_CCM_CDCDR_SPDIF1_CLK_PRED_MASK) >> + MXC_CCM_CDCDR_SPDIF1_CLK_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CDCDR_SPDIF1_CLK_PODF_MASK) >> + MXC_CCM_CDCDR_SPDIF1_CLK_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / (pred * podf); + } +} + +static struct clk spdif1_clk[] = { + { + .name = "spdif_clk", + .id = 1, + .parent = &pll3_sw_clk, + .secondary = &spdif1_clk[1], + .set_parent = _clk_spdif1_set_parent, + .recalc = _clk_spdif1_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG12_OFFSET, + .disable = _clk_disable, + }, + { + .name = "spdif_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .secondary = &spba_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG10_OFFSET, + .disable = _clk_disable, + }, +}; + +static int _clk_ipu_enable(struct clk *clk) +{ + u32 reg; + + _clk_enable(clk); + /* Handshake with IPU when certain clock rates are changed. */ + reg = __raw_readl(MXC_CCM_CCDR); + reg &= ~MXC_CCM_CCDR_IPU_HS_MASK; + __raw_writel(reg, MXC_CCM_CCDR); + + /* Handshake with IPU when LPM is entered as its enabled. */ + reg = __raw_readl(MXC_CCM_CLPCR); + reg &= ~MXC_CCM_CLPCR_BYPASS_IPU_LPM_HS; + __raw_writel(reg, MXC_CCM_CLPCR); + + return 0; +} + +static void _clk_ipu_disable(struct clk *clk) +{ + u32 reg; + _clk_disable(clk); + + /* No handshake with IPU as its not enabled. */ + reg = __raw_readl(MXC_CCM_CCDR); + reg |= MXC_CCM_CCDR_IPU_HS_MASK; + __raw_writel(reg, MXC_CCM_CCDR); + + /* No handshake with IPU when LPM is entered as its not enabled. */ + reg = __raw_readl(MXC_CCM_CLPCR); + reg |= MXC_CCM_CLPCR_BYPASS_IPU_LPM_HS; + __raw_writel(reg, MXC_CCM_CLPCR); +} + +static int _clk_ipu_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + reg = __raw_readl(MXC_CCM_CAMR); + mux = _get_mux(parent, &axi_a_clk, &axi_b_clk, &axi_c_clk, + &emi_core_clk); + reg = (reg & ~MXC_CCM_CAMR_IPU_HSP_CLK_SEL_MASK) | + (mux << MXC_CCM_CAMR_IPU_HSP_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CAMR); + + return 0; +} + +static struct clk ipu_clk[] = { + { + .name = "ipu_clk", + .parent = &axi_a_clk, + .secondary = &ipu_clk[1], + .set_parent = _clk_ipu_set_parent, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG15_OFFSET, + .enable = _clk_ipu_enable, + .disable = _clk_ipu_disable, + .flags = CPU_FREQ_TRIG_UPDATE, + }, + { + .name = "ipu_sec_clk", + .parent = &emi_fast_clk, + .secondary = &ahbmux1_clk, + } +}; + +static int _clk_ipu_di_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + if (parent == &tve_clk) { + mxc_iomux_set_gpr(MUX_IPUv3D_CAMP, false, 0); + } else if (parent == &ckih_clk) { + mxc_iomux_set_gpr(MUX_IPUv3D_CAMP, true, 0); + reg = __raw_readl(MXC_CCM_CSCMR1); + reg |= MXC_CCM_CSCMR1_DI_CLK_SEL; + __raw_writel(reg, MXC_CCM_CSCMR1); + } else if (parent == &osc_clk) { + mxc_iomux_set_gpr(MUX_IPUv3D_CAMP, true, 0); + reg = __raw_readl(MXC_CCM_CSCMR1); + reg &= ~MXC_CCM_CSCMR1_DI_CLK_SEL; + __raw_writel(reg, MXC_CCM_CSCMR1); + } else { + return -EINVAL; + } + + return 0; +} + +static void _clk_ipu_di_recalc(struct clk *clk) +{ + if (clk->parent == &tve_clk) { + clk->rate = clk->parent->rate / 8; + } else { + clk->rate = clk->parent->rate; + } +} + +static struct clk ipu_di_clk = { + .name = "ipu_di_clk", + .parent = &tve_clk, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG14_OFFSET, + .recalc = _clk_ipu_di_recalc, + .set_parent = _clk_ipu_di_set_parent, + .enable = _clk_enable, + .disable = _clk_disable, +}; + +static int _clk_ddr_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + reg = __raw_readl(MXC_CCM_CAMR); + mux = _get_mux(parent, &axi_a_clk, &axi_b_clk, &axi_c_clk, + &emi_core_clk); + reg = (reg & ~MXC_CCM_CAMR_DDR_CLK_SEL_MASK) | + (mux << MXC_CCM_CAMR_DDR_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CAMR); + + return 0; +} + +static struct clk ddr_clk = { + .name = "ddr_clk", + .parent = &axi_c_clk, + .set_parent = _clk_ddr_set_parent, +}; + +static int _clk_arm_axi_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + reg = __raw_readl(MXC_CCM_CAMR); + mux = _get_mux(parent, &axi_a_clk, &axi_b_clk, &axi_c_clk, + &emi_core_clk); + reg = (reg & ~MXC_CCM_CAMR_ARM_AXI_CLK_SEL_MASK) | + (mux << MXC_CCM_CAMR_ARM_AXI_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CAMR); + + return 0; +} + +static struct clk arm_axi_clk = { + .name = "arm_axi_clk", + .parent = &axi_a_clk, + .set_parent = _clk_arm_axi_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG1_OFFSET, + .disable = _clk_disable, +}; + +static int _clk_vpu_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + reg = __raw_readl(MXC_CCM_CAMR); + mux = _get_mux(parent, &axi_a_clk, &axi_b_clk, &axi_c_clk, + &emi_core_clk); + reg = (reg & ~MXC_CCM_CAMR_VPU_AXI_CLK_SEL_MASK) | + (mux << MXC_CCM_CAMR_VPU_AXI_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CAMR); + + return 0; +} + +static int _clk_vpu_core_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + reg = __raw_readl(MXC_CCM_CAMR); + mux = _get_mux(parent, &axi_a_clk, &axi_b_clk, &axi_c_clk, + &emi_core_clk); + reg = (reg & ~MXC_CCM_CAMR_VPU_CLK_SEL_MASK) | + (mux << MXC_CCM_CAMR_VPU_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CAMR); + + return 0; +} + +static struct clk vpu_clk[] = { + { + .name = "vpu_clk", + .parent = &axi_a_clk, + .set_parent = _clk_vpu_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG7_OFFSET, + .disable = _clk_disable, + .secondary = &vpu_clk[1], + .flags = CPU_FREQ_TRIG_UPDATE, + }, + { + .name = "vpu_core_clk", + .parent = &axi_a_clk, + .secondary = &vpu_clk[2], + .set_parent = _clk_vpu_core_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG6_OFFSET, + .disable = _clk_disable, + }, + { + .name = "vpu_emi_clk", + .parent = &emi_fast_clk, +#ifdef CONFIG_MXC_VPU_IRAM + .secondary = &emi_intr_clk, +#endif + } +}; + +static int _clk_lpsr_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + reg = __raw_readl(MXC_CCM_CLPCR); + mux = _get_mux(parent, &ckil_clk, &fpm_clk, &fpm_div2_clk, NULL); + reg = (reg & ~MXC_CCM_CLPCR_LPSR_CLK_SEL_MASK) | + (mux << MXC_CCM_CLPCR_LPSR_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CLPCR); + + return 0; +} + +static struct clk lpsr_clk = { + .name = "lpsr_clk", + .parent = &ckil_clk, + .set_parent = _clk_lpsr_set_parent, +}; + +static void _clk_pgc_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CSCDR1); + div = (reg & MXC_CCM_CSCDR1_PGC_CLK_PODF_MASK) >> + MXC_CCM_CSCDR1_PGC_CLK_PODF_OFFSET; + div = 1 >> div; + clk->rate = clk->parent->rate / div; +} + +static struct clk pgc_clk = { + .name = "pgc_clk", + .parent = &ipg_clk, + .recalc = _clk_pgc_recalc, +}; + +/*usb OTG clock */ +/*Notes: in mx37, usb clock get from UTMI PHY, always 60MHz*/ + +static struct clk usb_clk = { + .name = "usb_clk", + .rate = 60000000, +}; + +static struct clk rtc_clk = { + .name = "rtc_clk", + .parent = &ckil_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG13_OFFSET, + .disable = _clk_disable, +}; + +static struct clk ata_clk = { + .name = "ata_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG14_OFFSET, + .disable = _clk_disable, +}; + +static struct clk rng_clk = { + .name = "rng_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG5_OFFSET, + .disable = _clk_disable, +}; + +static struct clk scc_clk = { + .name = "scc_clk", + .parent = &ipg_clk, + .secondary = &emi_fast_clk, +}; + +static void cko1_recalc(struct clk *clk) +{ + unsigned long rate; + u32 reg; + + reg = __raw_readl(MXC_CCM_CCOSR); + reg &= MXC_CCM_CCOSR_CKOL_DIV_MASK; + reg = reg >> MXC_CCM_CCOSR_CKOL_DIV_OFFSET; + rate = clk->parent->rate; + clk->rate = rate / (reg + 1); +} + +static int cko1_enable(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_CCOSR); + reg |= MXC_CCM_CCOSR_CKOL_EN; + __raw_writel(reg, MXC_CCM_CCOSR); + return 0; +} + +static void cko1_disable(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_CCOSR); + reg &= ~MXC_CCM_CCOSR_CKOL_EN; + __raw_writel(reg, MXC_CCM_CCOSR); +} + +static int cko1_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div; + + div = (clk->parent->rate/rate - 1) & 0x7; + reg = __raw_readl(MXC_CCM_CCOSR); + reg &= ~MXC_CCM_CCOSR_CKOL_DIV_MASK; + reg |= div << MXC_CCM_CCOSR_CKOL_DIV_OFFSET; + __raw_writel(reg, MXC_CCM_CCOSR); + return 0; +} + +static unsigned long cko1_round_rate(struct clk *clk, unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + div = div < 1 ? 1 : div; + div = div > 8 ? 8 : div; + return clk->parent->rate / div; +} + +static int cko1_set_parent(struct clk *clk, struct clk *parent) +{ + u32 sel, reg; + + if (parent == &cpu_clk) + sel = 0; + else if (parent == &pll1_sw_clk) + sel = 1; + else if (parent == &pll2_sw_clk) + sel = 2; + else if (parent == &pll3_sw_clk) + sel = 3; + else if (parent == &emi_core_clk) + sel = 4; + else if (parent == &nfc_clk) + sel = 6; + else if (parent == &vpu_clk[1]) + sel = 7; + else if (parent == &ipu_di_clk) + sel = 8; + else if (parent == &ahb_clk) + sel = 11; + else if (parent == &ipg_clk) + sel = 12; + else if (parent == &ipg_perclk) + sel = 13; + else + return -EINVAL; + + reg = __raw_readl(MXC_CCM_CCOSR); + reg &= ~MXC_CCM_CCOSR_CKOL_SEL_MASK; + reg |= sel << MXC_CCM_CCOSR_CKOL_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CCOSR); + return 0; +} +static struct clk cko1_clk = { + .name = "cko1_clk", + .recalc = cko1_recalc, + .enable = cko1_enable, + .disable = cko1_disable, + .set_rate = cko1_set_rate, + .round_rate = cko1_round_rate, + .set_parent = cko1_set_parent, +}; + +static struct clk *mxc_clks[] = { + &osc_clk, + &ckih_clk, + &ckil_clk, + &fpm_clk, + &fpm_div2_clk, + &pll1_main_clk, + &pll1_sw_clk, + &pll2_sw_clk, + &pll3_sw_clk, + &gpc_dvfs_clk, + &lp_apm_clk, + &cpu_clk, + &periph_apm_clk, + &main_bus_clk, + &axi_a_clk, + &axi_b_clk, + &axi_c_clk, + &ahb_clk, + &ahb_max_clk, + &aips_tz1_clk, + &aips_tz2_clk, + &ipg_clk, + &ipg_perclk, + &sdma_clk[0], + &sdma_clk[1], + &ipu_clk[0], + &ipu_clk[1], + &ipu_di_clk, + &tve_clk, + &uart_main_clk, + &uart1_clk[0], + &uart1_clk[1], + &uart2_clk[0], + &uart2_clk[1], + &uart3_clk[0], + &uart3_clk[1], + &spba_clk, + &i2c_clk[0], + &i2c_clk[1], + &i2c_clk[2], + &gpt_clk[0], + &gpt_clk[1], + &gpt_clk[2], + &cspi_main_clk, + &cspi1_clk[0], + &cspi1_clk[1], + &cspi2_clk[0], + &cspi2_clk[1], + &cspi3_clk[0], + &cspi3_clk[1], + &ssi_lp_apm_clk, + &ssi1_clk[0], + &ssi1_clk[1], + &ssi2_clk[0], + &ssi2_clk[1], + &ssi_ext1_clk, + &ssi_ext2_clk, + &iim_clk, + &tmax1_clk, + &tmax2_clk, + &ahbmux1_clk, + &ahbmux2_clk, + &usb_core_clk[0], + &usb_core_clk[1], + &usb_core_clk[2], + &usb_core_clk[3], + &usboh2_clk, + &usb_phy_clk, + &usb_clk, + &esdhc1_clk[0], + &esdhc1_clk[1], + &esdhc1_clk[2], + &esdhc2_clk[0], + &esdhc2_clk[1], + &esdhc2_clk[2], + &esdhc3_clk[0], + &esdhc3_clk[1], + &esdhc3_clk[2], + &esdhc_dep_clks, + &emi_core_clk, + &emi_fast_clk, + &emi_slow_clk, + &emi_intr_clk, + &nfc_clk, + &spdif_xtal_clk, + &spdif0_clk[0], + &spdif0_clk[1], + &spdif1_clk[0], + &spdif1_clk[1], + &ddr_clk, + &arm_axi_clk, + &vpu_clk[0], + &vpu_clk[1], + &vpu_clk[2], + &lpsr_clk, + &pgc_clk, + &rtc_clk, + &ata_clk, + &rng_clk, + &scc_clk, + &cko1_clk, +}; + +static void clk_tree_init(void) +{ + u32 reg, dp_ctl; + + ipg_perclk.set_parent(&ipg_perclk, &lp_apm_clk); + + /* + *Initialise the IPG PER CLK dividers to 3. IPG_PER_CLK should be at + * 8MHz, its derived from lp_apm. + */ + reg = __raw_readl(MXC_CCM_CBCDR2); + reg &= ~MXC_CCM_CBCDR2_PERCLK_PRED1_MASK; + reg &= ~MXC_CCM_CBCDR2_PERCLK_PRED2_MASK; + reg &= ~MXC_CCM_CBCDR2_PERCLK_PODF_MASK; + reg |= (2 << MXC_CCM_CBCDR2_PERCLK_PRED1_OFFSET); + __raw_writel(reg, MXC_CCM_CBCDR2); + + /* set pll1_main_clk parent */ + pll1_main_clk.parent = &osc_clk; + dp_ctl = __raw_readl(pll_base[0] + MXC_PLL_DP_CTL); + if ((dp_ctl & MXC_PLL_DP_CTL_REF_CLK_SEL_MASK) == 0) + pll1_main_clk.parent = &fpm_clk; + /* set pll2_sw_clk parent */ + pll2_sw_clk.parent = &osc_clk; + dp_ctl = __raw_readl(pll_base[1] + MXC_PLL_DP_CTL); + if ((dp_ctl & MXC_PLL_DP_CTL_REF_CLK_SEL_MASK) == 0) + pll2_sw_clk.parent = &fpm_clk; + /* set pll3_clk parent */ + pll3_sw_clk.parent = &osc_clk; + dp_ctl = __raw_readl(pll_base[2] + MXC_PLL_DP_CTL); + if ((dp_ctl & MXC_PLL_DP_CTL_REF_CLK_SEL_MASK) == 0) + pll3_sw_clk.parent = &fpm_clk; + + /* set emi_core_clk parent */ + emi_core_clk.parent = &main_bus_clk; + reg = __raw_readl(MXC_CCM_CBCDR6); + if ((reg & MXC_CCM_CBCDR6_EMI_CLK_SEL) != 0) { + emi_core_clk.parent = &ahb_clk; + } + + /* set ipg_perclk parent */ + ipg_perclk.parent = &lp_apm_clk; + reg = __raw_readl(MXC_CCM_CSCMR1); + if ((reg & MXC_CCM_CSCMR1_PERCLK_IPG_CLK_SEL) != 0) { + ipg_perclk.parent = &ipg_clk; + } else { + if ((reg & MXC_CCM_CSCMR1_PERCLK_LP_APM_CLK_SEL) == 0) + ipg_perclk.parent = &main_bus_clk; + } + + /* set DDR clock parent */ + reg = __raw_readl(MXC_CCM_CAMR) & MXC_CCM_CAMR_DDR_CLK_SEL_MASK; + reg >>= MXC_CCM_CAMR_DDR_CLK_SEL_OFFSET; + if (reg == 0) { + ddr_clk.parent = &axi_a_clk; + } else if (reg == 1) { + ddr_clk.parent = &axi_b_clk; + } else if (reg == 2) { + ddr_clk.parent = &axi_c_clk; + } else { + ddr_clk.parent = &emi_core_clk; + } +} + +int __init mx37_clocks_init(unsigned long ckil, unsigned long osc, unsigned long ckih1, unsigned long ckih2) +{ + struct clk **clkp; + u32 reg; + int i; + + /* Turn off all possible clocks */ + if (mxc_jtag_enabled) { + __raw_writel((1 << MXC_CCM_CCGR0_CG0_OFFSET) | + (1 << MXC_CCM_CCGR0_CG1_OFFSET) | + (1 << MXC_CCM_CCGR0_CG2_OFFSET) | + (1 << MXC_CCM_CCGR0_CG12_OFFSET) | + (1 << MXC_CCM_CCGR0_CG13_OFFSET) | + (1 << MXC_CCM_CCGR0_CG7_OFFSET) | + (1 << MXC_CCM_CCGR0_CG14_OFFSET), MXC_CCM_CCGR0); + } else { + __raw_writel((1 << MXC_CCM_CCGR0_CG0_OFFSET) | + (1 << MXC_CCM_CCGR0_CG1_OFFSET) | + (1 << MXC_CCM_CCGR0_CG12_OFFSET) | + (1 << MXC_CCM_CCGR0_CG13_OFFSET) | + (1 << MXC_CCM_CCGR0_CG7_OFFSET) | + (1 << MXC_CCM_CCGR0_CG14_OFFSET), MXC_CCM_CCGR0); + } + __raw_writel(0, MXC_CCM_CCGR1); + + /* TMAX clocks. */ + reg = __raw_readl(MXC_CCM_CCGR1); + reg |= 1 << MXC_CCM_CCGR1_CG0_OFFSET; + reg |= 1 << MXC_CCM_CCGR1_CG1_OFFSET; + __raw_writel(reg, MXC_CCM_CCGR1); + __raw_writel(0, MXC_CCM_CCGR2); + __raw_writel(0, MXC_CCM_CCGR3); + __raw_writel(0, MXC_CCM_CCGR4); + /* Initialise the EMI clocks to be OFF when ARM is in WAIT mode. */ + __raw_writel((1 << MXC_CCM_CCGR5_CG4_OFFSET) | + (1 << MXC_CCM_CCGR5_CG12_OFFSET) | + (1 << MXC_CCM_CCGR5_CG13_OFFSET) | + (1 << MXC_CCM_CCGR5_CG14_OFFSET) | + MXC_CCM_CCGR5_CG11_MASK, MXC_CCM_CCGR5); + + ckil_clk.rate = ckil; + ckih_clk.rate = ckih1; + osc_clk.rate = osc; + + clk_tree_init(); + + for (clkp = mxc_clks; clkp < mxc_clks + ARRAY_SIZE(mxc_clks); clkp++) + clk_register(*clkp); + + reg = __raw_readl(MXC_CCM_CCSR); + /*STEP_CLK mxc_timer_init(&gpt_clk[0], IO_ADDRESS(GPT1_BASE_ADDR), MXC_INT_GPT); + - make sure its source is lp_apm */ + reg &= ~MXC_CCM_CCSR_STEP_SEL_MASK; + __raw_writel(reg, MXC_CCM_CCSR); + + /* This will propagate to all children and init all the clock rates */ + propagate_rate(&osc_clk); + propagate_rate(&ckih_clk); + propagate_rate(&ckil_clk); + + _clk_pll_disable(&pll3_sw_clk); + + clk_enable(&cpu_clk); + clk_enable(&main_bus_clk); + + /* Move UART to run from pll2_sw_clk */ + clk_set_parent(&uart_main_clk, &pll2_sw_clk); + + /* Set the UART dividers to divide by 10, so the UART_CLK is 66.5MHz. */ + reg = __raw_readl(MXC_CCM_CSCDR1); + reg &= ~MXC_CCM_CSCDR1_UART_CLK_PODF_MASK; + reg &= ~MXC_CCM_CSCDR1_UART_CLK_PRED_MASK; + reg |= (4 << MXC_CCM_CSCDR1_UART_CLK_PRED_OFFSET) | + (1 << MXC_CCM_CSCDR1_UART_CLK_PODF_OFFSET); + __raw_writel(reg, MXC_CCM_CSCDR1); + + /* move cspi to 24MHz */ + clk_set_parent(&cspi_main_clk, &lp_apm_clk); + clk_set_rate(&cspi_main_clk, 12000000); + + /*move the spdif0 to spdif_xtal_ckl */ + clk_set_parent(&spdif0_clk[0], &spdif_xtal_clk); + /*set the SPDIF dividers to 1 */ + reg = __raw_readl(MXC_CCM_CDCDR); + reg &= ~MXC_CCM_CDCDR_SPDIF0_CLK_PODF_MASK; + reg &= ~MXC_CCM_CDCDR_SPDIF0_CLK_PRED_MASK; + __raw_writel(reg, MXC_CCM_CDCDR); + + /* move the spdif1 to 24MHz */ + clk_set_parent(&spdif1_clk[0], &spdif_xtal_clk); + /* set the spdif1 dividers to 1 */ + reg = __raw_readl(MXC_CCM_CDCDR); + reg &= ~MXC_CCM_CDCDR_SPDIF1_CLK_PODF_MASK; + reg &= ~MXC_CCM_CDCDR_SPDIF1_CLK_PRED_MASK; + __raw_writel(reg, MXC_CCM_CDCDR); + + /* Move SSI clocks to SSI_LP_APM clock */ + clk_set_parent(&ssi_lp_apm_clk, &lp_apm_clk); + + clk_set_parent(&ssi1_clk[0], &ssi_lp_apm_clk); + /* set the SSI dividers to divide by 2 */ + reg = __raw_readl(MXC_CCM_CS1CDR); + reg &= ~MXC_CCM_CS1CDR_SSI1_CLK_PODF_MASK; + reg &= ~MXC_CCM_CS1CDR_SSI1_CLK_PRED_MASK; + reg |= 1 << MXC_CCM_CS1CDR_SSI1_CLK_PRED_OFFSET; + __raw_writel(reg, MXC_CCM_CS1CDR); + + clk_set_parent(&ssi2_clk[0], &ssi_lp_apm_clk); + reg = __raw_readl(MXC_CCM_CS2CDR); + reg &= ~MXC_CCM_CS2CDR_SSI2_CLK_PODF_MASK; + reg &= ~MXC_CCM_CS2CDR_SSI2_CLK_PRED_MASK; + reg |= 1 << MXC_CCM_CS2CDR_SSI2_CLK_PRED_OFFSET; + __raw_writel(reg, MXC_CCM_CS2CDR); + + /* Change the SSI_EXT1_CLK to be sourced from SSI1_CLK_ROOT */ + clk_set_parent(&ssi_ext1_clk, &ssi1_clk[0]); + clk_set_parent(&ssi_ext2_clk, &ssi2_clk[0]); + + propagate_rate(&ssi_lp_apm_clk); + + clk_set_parent(&arm_axi_clk, &emi_core_clk); + + clk_set_parent(&ipu_clk[0], &axi_a_clk); + clk_set_parent(&vpu_clk[0], &axi_a_clk); + clk_set_parent(&vpu_clk[1], &axi_a_clk); + + clk_set_parent(&emi_core_clk, &ahb_clk); + clk_set_rate(&emi_core_clk, clk_round_rate(&emi_core_clk, 130000000)); + propagate_rate(&emi_core_clk); + clk_set_rate(&emi_intr_clk, clk_round_rate(&emi_intr_clk, 66000000)); + /* Change the NFC clock rate to be 1:3 ratio with emi clock. */ + clk_set_rate(&nfc_clk, clk_round_rate(&nfc_clk, + (clk_get_rate(&emi_slow_clk))/3)); + + clk_set_parent(&usb_phy_clk, &osc_clk); + + clk_set_parent(&cko1_clk, &ipg_perclk); + clk_set_rate(&cko1_clk, 8000000); + /* Set the current working point. */ + cpu_wp_tbl = get_cpu_wp(&cpu_wp_nr); + for (i = 0; i < cpu_wp_nr; i++) { + if (clk_get_rate(&cpu_clk) == cpu_wp_tbl[i].cpu_rate) { + cpu_curr_wp = i; + break; + } + } + if (i > cpu_wp_nr) + BUG(); + + propagate_rate(&osc_clk); + propagate_rate(&pll1_sw_clk); + propagate_rate(&pll2_sw_clk); + +#ifdef DVFS_SW_WORKAROUND + clk_set_parent(&periph_apm_clk, &pll3_sw_clk); + + clk_set_parent(&main_bus_clk, &periph_apm_clk); + clk_disable(&pll2_sw_clk); + clk_set_rate(&pll2_sw_clk, 266000000); + pll2_sw_clk.recalc(&pll2_sw_clk); + clk_enable(&pll2_sw_clk); + clk_set_parent(&main_bus_clk, &pll2_sw_clk); + + clk_set_rate(&ahb_clk, clk_round_rate(&ahb_clk, 130000000)); + clk_set_rate(&axi_b_clk, clk_round_rate(&axi_b_clk, 110000000)); + clk_set_rate(&axi_c_clk, clk_round_rate(&axi_c_clk, 166000000)); + clk_set_rate(&axi_a_clk, clk_round_rate(&axi_a_clk, 130000000)); + + clk_set_parent(&emi_core_clk, &ahb_clk); + clk_set_rate(&emi_core_clk, clk_round_rate(&emi_core_clk, 130000000)); + clk_set_rate(&emi_intr_clk, clk_round_rate(&emi_intr_clk, 66000000)); + + clk_set_rate(&axi_c_clk, clk_round_rate(&axi_c_clk, 130000000)); + clk_set_parent(&ddr_clk, &axi_c_clk); + /* Set the UART dividers to divide by 6, so the UART_CLK is 66.5MHz. */ + reg = __raw_readl(MXC_CCM_CSCDR1); + reg &= ~MXC_CCM_CSCDR1_UART_CLK_PODF_MASK; + reg &= ~MXC_CCM_CSCDR1_UART_CLK_PRED_MASK; + reg |= (3 << MXC_CCM_CSCDR1_UART_CLK_PRED_OFFSET) | + (0 << MXC_CCM_CSCDR1_UART_CLK_PODF_OFFSET); + __raw_writel(reg, MXC_CCM_CSCDR1); +#endif + + mxc_timer_init(&gpt_clk[0], IO_ADDRESS(GPT1_BASE_ADDR), MXC_INT_GPT); + + return 0; +} + +/*! + * Setup cpu clock based on working point. + * @param wp cpu freq working point + * @return 0 on success or error code on failure. + */ +static int cpu_clk_set_wp(int wp) +{ + struct cpu_wp *p; + u32 reg; + u32 stat; + + if (wp == cpu_curr_wp) + return 0; + + p = &cpu_wp_tbl[wp]; + + if (!dvfs_core_is_active) { + /* Change the ARM clock to requested frequency */ + /* First move the ARM clock to step clock */ + /* which is running at 24MHz. */ + + /* Change the source of pll1_sw_clk to be the step_clk */ + reg = __raw_readl(MXC_CCM_CCSR); + reg |= MXC_CCM_CCSR_PLL1_SW_CLK_SEL; + __raw_writel(reg, MXC_CCM_CCSR); + + /* Stop the PLL */ + reg = __raw_readl(MXC_DPLL1_BASE + MXC_PLL_DP_CTL); + reg &= ~MXC_PLL_DP_CTL_UPEN; + __raw_writel(reg, MXC_DPLL1_BASE + MXC_PLL_DP_CTL); + + /* PDF and MFI */ + reg = p->pdf | p->mfi << MXC_PLL_DP_OP_MFI_OFFSET; + __raw_writel(reg, MXC_DPLL1_BASE + MXC_PLL_DP_OP); + + /* MFD */ + __raw_writel(p->mfd, MXC_DPLL1_BASE + MXC_PLL_DP_MFD); + + /* MFI */ + __raw_writel(p->mfn, MXC_DPLL1_BASE + MXC_PLL_DP_MFN); + + reg = __raw_readl(MXC_DPLL1_BASE + MXC_PLL_DP_CTL); + reg |= MXC_PLL_DP_CTL_UPEN; + /* Set the UPEN bits */ + __raw_writel(reg, MXC_DPLL1_BASE + MXC_PLL_DP_CTL); + /* Forcefully restart the PLL */ + reg |= MXC_PLL_DP_CTL_RST; + __raw_writel(reg, MXC_DPLL1_BASE + MXC_PLL_DP_CTL); + + /* Wait for the PLL to lock */ + do { + stat = __raw_readl(MXC_DPLL1_BASE + MXC_PLL_DP_CTL) & + MXC_PLL_DP_CTL_LRF; + } while (!stat); + + reg = __raw_readl(MXC_CCM_CCSR); + /* Move the PLL1 back to the pll1_main_clk */ + reg &= ~MXC_CCM_CCSR_PLL1_SW_CLK_SEL; + __raw_writel(reg, MXC_CCM_CCSR); + } + cpu_curr_wp = wp; + + pll1_sw_clk.rate = cpu_wp_tbl[wp].cpu_rate; + pll1_main_clk.rate = pll1_sw_clk.rate; + cpu_clk.rate = pll1_sw_clk.rate; + +#if defined(CONFIG_CPU_FREQ) + cpufreq_trig_needed = 1; +#endif + + if (wp == 0) + dptc_resume(DPTC_GP_ID); + else + dptc_suspend(DPTC_GP_ID); + + return 0; +} diff --git a/arch/arm/mach-mx37/cpu.c b/arch/arm/mach-mx37/cpu.c new file mode 100644 index 000000000000..1ca44ae9ed86 --- /dev/null +++ b/arch/arm/mach-mx37/cpu.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2001 Deep Blue Solutions Ltd. + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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. + * + */ + +/*! + * @file mach-mx37/cpu.c + * + * @brief This file contains the CPU initialization code. + * + * @ingroup MSL_MX37 + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <mach/hardware.h> +#include <linux/io.h> +#include <asm/hardware/cache-l2x0.h> + +/*! + * CPU initialization. It is called by fixup_mxc_board() + */ +void __init mxc_cpu_init(void) +{ + if (!system_rev) { + mxc_set_system_rev(0x37, CHIP_REV_1_0); + } +} + +/*! + * Post CPU init code + * + * @return 0 always + */ +static int __init post_cpu_init(void) +{ + void *l2_base; + volatile unsigned long aips_reg; + + /* Initialize L2 cache */ + l2_base = ioremap(L2CC_BASE_ADDR, SZ_4K); + if (l2_base) { + l2x0_init(l2_base, 0x0003001B, 0x00000000); + } + + __raw_writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x40)); + __raw_writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x44)); + __raw_writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x48)); + __raw_writel(0x0, IO_ADDRESS(AIPS1_BASE_ADDR + 0x4C)); + aips_reg = __raw_readl(IO_ADDRESS(AIPS1_BASE_ADDR + 0x50)); + aips_reg &= 0x00FFFFFF; + __raw_writel(aips_reg, IO_ADDRESS(AIPS1_BASE_ADDR + 0x50)); + + __raw_writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x40)); + __raw_writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x44)); + __raw_writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x48)); + __raw_writel(0x0, IO_ADDRESS(AIPS2_BASE_ADDR + 0x4C)); + aips_reg = __raw_readl(IO_ADDRESS(AIPS2_BASE_ADDR + 0x50)); + aips_reg &= 0x00FFFFFF; + __raw_writel(aips_reg, IO_ADDRESS(AIPS2_BASE_ADDR + 0x50)); + + return 0; +} + +postcore_initcall(post_cpu_init); diff --git a/arch/arm/mach-mx37/crm_regs.h b/arch/arm/mach-mx37/crm_regs.h new file mode 100644 index 000000000000..ccc96f02ff68 --- /dev/null +++ b/arch/arm/mach-mx37/crm_regs.h @@ -0,0 +1,611 @@ +/* + * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ARCH_ARM_MACH_MX37_CRM_REGS_H__ +#define __ARCH_ARM_MACH_MX37_CRM_REGS_H__ + +#define MXC_CCM_BASE ((char *)IO_ADDRESS(CCM_BASE_ADDR)) +#define MXC_DPLL1_BASE IO_ADDRESS(PLL0_BASE_ADDR) +#define MXC_DPLL2_BASE IO_ADDRESS(PLL1_BASE_ADDR) +#define MXC_DPLL3_BASE IO_ADDRESS(PLL2_BASE_ADDR) + +/* PLL Register Offsets */ +#define MXC_PLL_DP_CTL 0x00 +#define MXC_PLL_DP_CONFIG 0x04 +#define MXC_PLL_DP_OP 0x08 +#define MXC_PLL_DP_MFD 0x0C +#define MXC_PLL_DP_MFN 0x10 +#define MXC_PLL_DP_MFNMINUS 0x14 +#define MXC_PLL_DP_MFNPLUS 0x18 +#define MXC_PLL_DP_HFS_OP 0x1C +#define MXC_PLL_DP_HFS_MFD 0x20 +#define MXC_PLL_DP_HFS_MFN 0x24 +#define MXC_PLL_DP_MFN_TOGC 0x28 +#define MXC_PLL_DP_DESTAT 0x2c + +/* PLL Register Bit definitions */ +#define MXC_PLL_DP_CTL_MUL_CTRL 0x2000 +#define MXC_PLL_DP_CTL_DPDCK0_2_EN 0x1000 +#define MXC_PLL_DP_CTL_DPDCK0_2_OFFSET 12 +#define MXC_PLL_DP_CTL_ADE 0x800 +#define MXC_PLL_DP_CTL_REF_CLK_DIV 0x400 +#define MXC_PLL_DP_CTL_REF_CLK_SEL_MASK (3 << 8) +#define MXC_PLL_DP_CTL_REF_CLK_SEL_OFFSET 8 +#define MXC_PLL_DP_CTL_HFSM 0x80 +#define MXC_PLL_DP_CTL_PRE 0x40 +#define MXC_PLL_DP_CTL_UPEN 0x20 +#define MXC_PLL_DP_CTL_RST 0x10 +#define MXC_PLL_DP_CTL_RCP 0x8 +#define MXC_PLL_DP_CTL_PLM 0x4 +#define MXC_PLL_DP_CTL_BRM0 0x2 +#define MXC_PLL_DP_CTL_LRF 0x1 + +#define MXC_PLL_DP_CONFIG_BIST 0x8 +#define MXC_PLL_DP_CONFIG_SJC_CE 0x4 +#define MXC_PLL_DP_CONFIG_AREN 0x2 +#define MXC_PLL_DP_CONFIG_LDREQ 0x1 + +#define MXC_PLL_DP_OP_MFI_OFFSET 4 +#define MXC_PLL_DP_OP_MFI_MASK (0xF << 4) +#define MXC_PLL_DP_OP_PDF_OFFSET 0 +#define MXC_PLL_DP_OP_PDF_MASK 0xF + +#define MXC_PLL_DP_MFD_OFFSET 0 +#define MXC_PLL_DP_MFD_MASK 0x07FFFFFF + +#define MXC_PLL_DP_MFN_OFFSET 0x0 +#define MXC_PLL_DP_MFN_MASK 0x07FFFFFF + +#define MXC_PLL_DP_MFN_TOGC_TOG_DIS (1 << 17) +#define MXC_PLL_DP_MFN_TOGC_TOG_EN (1 << 16) +#define MXC_PLL_DP_MFN_TOGC_CNT_OFFSET 0x0 +#define MXC_PLL_DP_MFN_TOGC_CNT_MASK 0xFFFF + +#define MXC_PLL_DP_DESTAT_TOG_SEL (1 << 31) +#define MXC_PLL_DP_DESTAT_MFN 0x07FFFFFF + +/* Register addresses of CCM*/ +#define MXC_CCM_CCR (MXC_CCM_BASE + 0x00) +#define MXC_CCM_CCDR (MXC_CCM_BASE + 0x04) +#define MXC_CCM_CSR (MXC_CCM_BASE + 0x08) +#define MXC_CCM_CCSR (MXC_CCM_BASE + 0x0C) +#define MXC_CCM_CACRR (MXC_CCM_BASE + 0x10) +#define MXC_CCM_CBCDR2 (MXC_CCM_BASE + 0x18) +#define MXC_CCM_CBCDR3 (MXC_CCM_BASE + 0x1C) +#define MXC_CCM_CBCDR4 (MXC_CCM_BASE + 0x20) +#define MXC_CCM_CBCDR5 (MXC_CCM_BASE + 0x24) +#define MXC_CCM_CBCDR6 (MXC_CCM_BASE + 0x28) +#define MXC_CCM_CBCDR7 (MXC_CCM_BASE + 0x2C) +#define MXC_CCM_CAMR (MXC_CCM_BASE + 0x30) +#define MXC_CCM_CSCMR1 (MXC_CCM_BASE + 0x34) +#define MXC_CCM_CSCMR2 (MXC_CCM_BASE + 0x38) +#define MXC_CCM_CSCDR1 (MXC_CCM_BASE + 0x3C) +#define MXC_CCM_CS1CDR (MXC_CCM_BASE + 0x40) +#define MXC_CCM_CS2CDR (MXC_CCM_BASE + 0x44) +#define MXC_CCM_CSECDR1 (MXC_CCM_BASE + 0x48) +#define MXC_CCM_CSECDR2 (MXC_CCM_BASE + 0x4C) +#define MXC_CCM_CECDR (MXC_CCM_BASE + 0x50) +#define MXC_CCM_CDCDR (MXC_CCM_BASE + 0x54) +#define MXC_CCM_CH1CDR (MXC_CCM_BASE + 0x58) +#define MXC_CCM_CH2CDR (MXC_CCM_BASE + 0x5C) +#define MXC_CCM_CSCDR2 (MXC_CCM_BASE + 0x60) +#define MXC_CCM_CR2 (MXC_CCM_BASE + 0x64) +#define MXC_CCM_CDHIPR (MXC_CCM_BASE + 0x68) +#define MXC_CCM_CDCR (MXC_CCM_BASE + 0x6C) +#define MXC_CCM_CTOR (MXC_CCM_BASE + 0x70) +#define MXC_CCM_CLPCR (MXC_CCM_BASE + 0x74) +#define MXC_CCM_CISR (MXC_CCM_BASE + 0x78) +#define MXC_CCM_CIMR (MXC_CCM_BASE + 0x7C) +#define MXC_CCM_CCOSR (MXC_CCM_BASE + 0x80) +#define MXC_CCM_CGPR (MXC_CCM_BASE + 0x84) +#define MXC_CCM_CCGR0 (MXC_CCM_BASE + 0x88) +#define MXC_CCM_CCGR1 (MXC_CCM_BASE + 0x8C) +#define MXC_CCM_CCGR2 (MXC_CCM_BASE + 0x90) +#define MXC_CCM_CCGR3 (MXC_CCM_BASE + 0x94) +#define MXC_CCM_CCGR4 (MXC_CCM_BASE + 0x98) +#define MXC_CCM_CCGR5 (MXC_CCM_BASE + 0x9C) +#define MXC_CCM_CMEOR (MXC_CCM_BASE + 0xA0) + +/* Define the bits in register CCR */ +#define MXC_CCM_CCR_COSC_EN (1 << 11) +#define MXC_CCM_CCR_FPM_MULT_MASK (1 << 10) +#define MXC_CCM_CCR_CAMP_EN (1 << 9) +#define MXC_CCM_CCR_FPM_EN (1 << 8) +#define MXC_CCM_CCR_OSCNT_OFFSET (0) +#define MXC_CCM_CCR_OSCNT_MASK (0xFF) + +/* Define the bits in register CCDR */ +#define MXC_CCM_CCDR_IPU_HS_MASK (0x1 << 17) +#define MXC_CCM_CCDR_EMI_HS_MASK (0x1 << 16) +#define MXC_CCM_CCDR_LOAD_DIVIDERS (0x1 << 0) + +/* Define the bits in register CSR */ +#define MXC_CCM_CSR_COSR_READY (1 << 4) +#define MXC_CCM_CSR_LVS_VALUE (1 << 3) +#define MXC_CCM_CSR_CAMP_READY (1 << 2) +#define MXC_CCM_CSR_FPM_READY (1 << 1) +#define MXC_CCM_CSR_REF_EN_B (1 << 0) + +/* Define the bits in register CCSR */ +#define MXC_CCM_CCSR_LP_APM_SEL (0x1 << 9) +#define MXC_CCM_CCSR_STEP_SEL_OFFSET (7) +#define MXC_CCM_CCSR_STEP_SEL_MASK (0x3 << 7) +#define MXC_CCM_CCSR_PLL2_PODF_OFFSET (5) +#define MXC_CCM_CCSR_PLL2_PODF_MASK (0x3 << 5) +#define MXC_CCM_CCSR_PLL3_PODF_OFFSET (3) +#define MXC_CCM_CCSR_PLL3_PODF_MASK (0x3 << 3) +#define MXC_CCM_CCSR_PLL1_SW_CLK_SEL (1 << 2) +#define MXC_CCM_CCSR_PLL2_SW_CLK_SEL (1 << 1) +#define MXC_CCM_CCSR_PLL3_SW_CLK_SEL (1 << 0) + +/* Define the bits in register CACRR */ +#define MXC_CCM_CACRR_ARM_PODF_OFFSET (0) +#define MXC_CCM_CACRR_ARM_PODF_MASK (0x7) + +/* Define the bits in register CBCDR2 */ +#define MXC_CCM_CBCDR2_AHB_PODF_OFFSET (10) +#define MXC_CCM_CBCDR2_AHB_PODF_MASK (0x7 << 10) +#define MXC_CCM_CBCDR2_IPG_PODF_OFFSET (8) +#define MXC_CCM_CBCDR2_IPG_PODF_MASK (0x3 << 8) +#define MXC_CCM_CBCDR2_PERCLK_PRED1_OFFSET (6) +#define MXC_CCM_CBCDR2_PERCLK_PRED1_MASK (0x3 << 6) +#define MXC_CCM_CBCDR2_PERCLK_PRED2_OFFSET (3) +#define MXC_CCM_CBCDR2_PERCLK_PRED2_MASK (0x7 << 3) +#define MXC_CCM_CBCDR2_PERCLK_PODF_OFFSET (0) +#define MXC_CCM_CBCDR2_PERCLK_PODF_MASK (0x7) + +/* Define the bits in register CBCDR3 */ +#define MXC_CCM_CBCDR3_AXI_A_PODF_OFFSET (0) +#define MXC_CCM_CBCDR3_AXI_A_PODF_MASK (0x7) + +/* Define the bits in register CBCDR4 */ +#define MXC_CCM_CBCDR4_AXI_B_PODF_OFFSET (0) +#define MXC_CCM_CBCDR4_AXI_B_PODF_MASK (0x7) + +/* Define the bits in register CBCDR5 */ +#define MXC_CCM_CBCDR5_AXI_C_PODF_OFFSET (0) +#define MXC_CCM_CBCDR5_AXI_C_PODF_MASK (0x7) + +/* Define the bits in register CBCDR6 */ +#define MXC_CCM_CBCDR6_EMI_PODF_OFFSET (0) +#define MXC_CCM_CBCDR6_EMI_PODF_MASK (0x7) +#define MXC_CCM_CBCDR6_EMI_CLK_SEL (0x1 << 3) +#define MXC_CCM_CBCDR6_PERIPH_CLK_SEL (0x1 << 4) + +/* Define the bits in register CBCDR7 */ +#define MXC_CCM_CBCDR7_IPG_INT_MEM_PODF_OFFSET (3) +#define MXC_CCM_CBCDR7_IPG_INIT_MEM_PODF_MASK (0x3 << 3) +#define MXC_CCM_CBCDR7_NFC_PODF_OFFSET (0) +#define MXC_CCM_CBCDR7_NFC_PODF_MASK (0x7) + +/* Define the bits in register CAMR */ +#define MXC_CCM_CAMR_PERIPH_CLK_SEL_OFFSET (12) +#define MXC_CCM_CAMR_PERIPH_CLK_SEL_MASK (0x3 << 12) +#define MXC_CCM_CAMR_DDR_CLK_SEL_OFFSET (10) +#define MXC_CCM_CAMR_DDR_CLK_SEL_MASK (0x3 << 10) +#define MXC_CCM_CAMR_ARM_AXI_CLK_SEL_OFFSET (8) +#define MXC_CCM_CAMR_ARM_AXI_CLK_SEL_MASK (0x3 << 8) +#define MXC_CCM_CAMR_VPU_CLK_SEL_OFFSET (6) +#define MXC_CCM_CAMR_VPU_CLK_SEL_MASK (0x3 << 6) +#define MXC_CCM_CAMR_VPU_AXI_CLK_SEL_OFFSET (4) +#define MXC_CCM_CAMR_VPU_AXI_CLK_SEL_MASK (0x3 << 4) +#define MXC_CCM_CAMR_IPU_HSP_CLK_SEL_OFFSET (2) +#define MXC_CCM_CAMR_IPU_HSP_CLK_SEL_MASK (0x3 << 2) + +/* Define the bits in register CSCMR1 */ +#define MXC_CCM_CSCMR1_SSI_EXT2_CLK_SEL_OFFSET (30) +#define MXC_CCM_CSCMR1_SSI_EXT2_CLK_SEL_MASK (0x3 << 30) +#define MXC_CCM_CSCMR1_SSI_EXT1_CLK_SEL_OFFSET (28) +#define MXC_CCM_CSCMR1_SSI_EXT1_CLK_SEL_MASK (0x3 << 28) +#define MXC_CCM_CSCMR1_DI_CLK_SEL (0x1 << 27) +#define MXC_CCM_CSCMR1_USB_PHY_CLK_SEL_OFFSET (26) +#define MXC_CCM_CSCMR1_USB_PHY_CLK_SEL (0x1 << 26) +#define MXC_CCM_CSCMR1_UART_CLK_SEL_OFFSET (24) +#define MXC_CCM_CSCMR1_UART_CLK_SEL_MASK (0x3 << 24) +#define MXC_CCM_CSCMR1_USBOH2_CLK_SEL_OFFSET (22) +#define MXC_CCM_CSCMR1_USBOH2_CLK_SEL_MASK (0x3 << 22) +#define MXC_CCM_CSCMR1_ESDHC1_MSHC1_CLK_SEL_OFFSET (20) +#define MXC_CCM_CSCMR1_ESDHC1_MSHC1_CLK_SEL_MASK (0x3 << 20) +#define MXC_CCM_CSCMR1_ESDHC3_CLK_SEL (0x1 << 19) +#define MXC_CCM_CSCMR1_PERCLK_IPG_CLK_SEL (0x1 << 18) +#define MXC_CCM_CSCMR1_ESDHC2_MSHC2_CLK_SEL_OFFSET (16) +#define MXC_CCM_CSCMR1_ESDHC2_MSHC2_CLK_SEL_MASK (0x3 << 16) +#define MXC_CCM_CSCMR1_SSI1_CLK_SEL_OFFSET (14) +#define MXC_CCM_CSCMR1_SSI1_CLK_SEL_MASK (0x3 << 14) +#define MXC_CCM_CSCMR1_SSI2_CLK_SEL_OFFSET (12) +#define MXC_CCM_CSCMR1_SSI2_CLK_SEL_MASK (0x3 << 12) +#define MXC_CCM_CSCMR1_SSI_APM_CLK_SEL (0x1 << 9) +#define MXC_CCM_CSCMR1_SPDIF_CLK_SEL (0x1 << 8) +#define MXC_CCM_CSCMR1_TVE_CLK_SEL (0x1 << 7) +#define MXC_CCM_CSCMR1_TVE_EXT_CLK_SEL (0x1 << 6) +#define MXC_CCM_CSCMR1_CSPI_CLK_SEL_OFFSET (4) +#define MXC_CCM_CSCMR1_CSPI_CLK_SEL_MASK (0x3 << 4) +#define MXC_CCM_CSCMR1_PERCLK_LP_APM_CLK_SEL (0x1 << 2) +#define MXC_CCM_CSCMR1_SSI_EXT2_COM_CLK_SEL (0x1 << 1) +#define MXC_CCM_CSCMR1_SSI_EXT1_COM_CLK_SEL (0x1) + +/* Define the bits in register CSCMR2 */ +#define MXC_CCM_CSCMR2_SPDIF1_COM (1 << 5) +#define MXC_CCM_CSCMR2_SPDIF0_COM (1 << 4) +#define MXC_CCM_CSCMR2_SPDIF1_CLK_SEL_OFFSET (2) +#define MXC_CCM_CSCMR2_SPDIF1_CLK_SEL_MASK (0x3 << 2) +#define MXC_CCM_CSCMR2_SPDIF0_CLK_SEL_OFFSET (0) +#define MXC_CCM_CSCMR2_SPDIF0_CLK_SEL_MASK (0x3) + +/* Define the bits in register CSCDR1 */ +#define MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PRED_OFFSET (22) +#define MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PRED_MASK (0x7 << 22) +#define MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PODF_OFFSET (19) +#define MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PODF_MASK (0x7 << 19) +#define MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PRED_OFFSET (16) +#define MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PRED_MASK (0x7 << 16) +#define MXC_CCM_CSCDR1_PGC_CLK_PODF_OFFSET (14) +#define MXC_CCM_CSCDR1_PGC_CLK_PODF_MASK (0x3 << 14) +#define MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PODF_OFFSET (11) +#define MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PODF_MASK (0x7 << 11) +#define MXC_CCM_CSCDR1_USBOH2_CLK_PRED_OFFSET (8) +#define MXC_CCM_CSCDR1_USBOH2_CLK_PRED_MASK (0x7 << 8) +#define MXC_CCM_CSCDR1_USBOH2_CLK_PODF_OFFSET (6) +#define MXC_CCM_CSCDR1_USBOH2_CLK_PODF_MASK (0x3 << 6) +#define MXC_CCM_CSCDR1_UART_CLK_PRED_OFFSET (3) +#define MXC_CCM_CSCDR1_UART_CLK_PRED_MASK (0x7 << 3) +#define MXC_CCM_CSCDR1_UART_CLK_PODF_OFFSET (0) +#define MXC_CCM_CSCDR1_UART_CLK_PODF_MASK (0x7) + +/* Define the bits in register CS1CDR and CS2CDR */ +#define MXC_CCM_CS1CDR_SSI1_CLK_PRED_OFFSET (6) +#define MXC_CCM_CS1CDR_SSI1_CLK_PRED_MASK (0x7 << 6) +#define MXC_CCM_CS1CDR_SSI1_CLK_PODF_OFFSET (0) +#define MXC_CCM_CS1CDR_SSI1_CLK_PODF_MASK (0x3F) + +#define MXC_CCM_CS2CDR_SSI2_CLK_PRED_OFFSET (6) +#define MXC_CCM_CS2CDR_SSI2_CLK_PRED_MASK (0x7 << 6) +#define MXC_CCM_CS2CDR_SSI2_CLK_PODF_OFFSET (0) +#define MXC_CCM_CS2CDR_SSI2_CLK_PODF_MASK (0x3F) + +/* Define the bits in register CSECDR1 and CSECDR2 */ +#define MXC_CCM_CSECDR1_SSI_EXT1_CLK_PRED_OFFSET (6) +#define MXC_CCM_CSECDR1_SSI_EXT1_CLK_PRED_MASK (0x7 << 6) +#define MXC_CCM_CSECDR1_SSI_EXT1_CLK_PODF_OFFSET (0) +#define MXC_CCM_CSECDR1_SSI_EXT1_CLK_PODF_MASK (0x3F) + +#define MXC_CCM_CSECDR2_SSI_EXT2_CLK_PRED_OFFSET (6) +#define MXC_CCM_CSECDR2_SSI_EXT2_CLK_PRED_MASK (0x7 << 6) +#define MXC_CCM_CSECDR2_SSI_EXT2_CLK_PODF_OFFSET (0) +#define MXC_CCM_CSECDR2_SSI_EXT2_CLK_PODF_MASK (0x3F) + +/* Define the bits in register CDCDR */ +#define MXC_CCM_CDCDR_TVE_CLK_PRED_OFFSET (28) +#define MXC_CCM_CDCDR_TVE_CLK_PRED_MASK (0x7 << 28) +#define MXC_CCM_CDCDR_SPDIF0_CLK_PRED_OFFSET (25) +#define MXC_CCM_CDCDR_SPDIF0_CLK_PRED_MASK (0x7 << 25) +#define MXC_CCM_CDCDR_SPDIF0_CLK_PODF_OFFSET (19) +#define MXC_CCM_CDCDR_SPDIF0_CLK_PODF_MASK (0x3F << 19) +#define MXC_CCM_CDCDR_SPDIF1_CLK_PRED_OFFSET (16) +#define MXC_CCM_CDCDR_SPDIF1_CLK_PRED_MASK (0x7 << 16) +#define MXC_CCM_CDCDR_SPDIF1_CLK_PODF_OFFSET (9) +#define MXC_CCM_CDCDR_SPDIF1_CLK_PODF_MASK (0x3F << 9) +#define MXC_CCM_CDCDR_USB_PHY_PRED_OFFSET (4) +#define MXC_CCM_CDCDR_USB_PHY_PRED_MASK (0x7 << 4) +#define MXC_CCM_CDCDR_USB_PHY_PODF_OFFSET (1) +#define MXC_CCM_CDCDR_USB_PHY_PODF_MASK (0x7 << 1) + +/* Define the bits in register CSCDR2 */ +#define MXC_CCM_CSCDR2_CSPI_CLK_PRED_OFFSET (25) +#define MXC_CCM_CSCDR2_CSPI_CLK_PRED_MASK (0x7 << 25) +#define MXC_CCM_CSCDR2_CSPI_CLK_PODF_OFFSET (19) +#define MXC_CCM_CSCDR2_CSPI_CLK_PODF_MASK (0x3F << 19) + +/* Define the bits in register CDHIPR */ +#define MXC_CCM_CDHIPR_ARM_PODF_BUSY (1 << 16) +#define MXC_CCM_CDHIPR_NFC_IPG_INT_MEM_PODF_BUSY (1 << 4) +#define MXC_CCM_CDHIPR_EMI_PODF_BUSY (1 << 3) +#define MXC_CCM_CDHIPR_AXI_C_PODF_BUSY (1 << 2) +#define MXC_CCM_CDHIPR_AXI_B_PODF_BUSY (1 << 1) +#define MXC_CCM_CDHIPR_AXI_A_PODF_BUSY (1 << 0) + +/* Define the bits in register CDCR */ +#define MXC_CCM_CDCR_ARM_FREQ_SHIFT_DIVIDER (0x1 << 2) +#define MXC_CCM_CDCR_PERIPH_CLK_DVFS_PODF_OFFSET (0) +#define MXC_CCM_CDCR_PERIPH_CLK_DVFS_PODF_MASK (0x3) + +/* Define the bits in register CLPCR */ +#define MXC_CCM_CLPCR_BYPASS_SCC_LPM_HS (0x1 << 22) +#define MXC_CCM_CLPCR_BYPASS_MAX_LPM_HS (0x1 << 21) +#define MXC_CCM_CLPCR_BYPASS_SDMA_LPM_HS (0x1 << 20) +#define MXC_CCM_CLPCR_BYPASS_EMI_LPM_HS (0x1 << 19) +#define MXC_CCM_CLPCR_BYPASS_IPU_LPM_HS (0x1 << 18) +#define MXC_CCM_CLPCR_BYPASS_RTIC_LPM_HS (0x1 << 17) +#define MXC_CCM_CLPCR_BYPASS_RNGC_LPM_HS (0x1 << 16) +#define MXC_CCM_CLPCR_COSC_PWRDOWN (0x1 << 11) +#define MXC_CCM_CLPCR_STBY_COUNT_OFFSET (9) +#define MXC_CCM_CLPCR_STBY_COUNT_MASK (0x3 << 9) +#define MXC_CCM_CLPCR_VSTBY (0x1 << 8) +#define MXC_CCM_CLPCR_DIS_REF_OSC (0x1 << 7) +#define MXC_CCM_CLPCR_SBYOS (0x1 << 6) +#define MXC_CCM_CLPCR_ARM_CLK_DIS_ON_LPM (0x1 << 5) +#define MXC_CCM_CLPCR_LPSR_CLK_SEL_OFFSET (3) +#define MXC_CCM_CLPCR_LPSR_CLK_SEL_MASK (0x3 << 3) +#define MXC_CCM_CLPCR_LPM_OFFSET (0) +#define MXC_CCM_CLPCR_LPM_MASK (0x3) + +/* Define the bits in register CISR */ +#define MXC_CCM_CISR_ARM_PODF_LOADED (0x1 << 25) +#define MXC_CCM_CISR_NFC_IPG_INT_MEM_PODF_LOADED (0x1 << 21) +#define MXC_CCM_CISR_EMI_PODF_LOADED (0x1 << 20) +#define MXC_CCM_CISR_AXI_C_PODF_LOADED (0x1 << 19) +#define MXC_CCM_CISR_AXI_B_PODF_LOADED (0x1 << 18) +#define MXC_CCM_CISR_AXI_A_PODF_LOADED (0x1 << 17) +#define MXC_CCM_CISR_DIVIDER_LOADED (0x1 << 16) +#define MXC_CCM_CISR_COSC_READY (0x1 << 5) +#define MXC_CCM_CISR_CKIH_READY (0x1 << 4) +#define MXC_CCM_CISR_FPM_READY (0x1 << 3) +#define MXC_CCM_CISR_LRF_PLL3 (0x1 << 2) +#define MXC_CCM_CISR_LRF_PLL2 (0x1 << 1) +#define MXC_CCM_CISR_LRF_PLL1 (0x1) + +/* Define the bits in register CIMR */ +#define MXC_CCM_CIMR_MASK_ARM_PODF_LOADED (0x1 << 25) +#define MXC_CCM_CIMR_MASK_NFC_IPG_INT_MEM_PODF_LOADED (0x1 << 21) +#define MXC_CCM_CIMR_MASK_EMI_PODF_LOADED (0x1 << 20) +#define MXC_CCM_CIMR_MASK_AXI_C_PODF_LOADED (0x1 << 19) +#define MXC_CCM_CIMR_MASK_AXI_B_PODF_LOADED (0x1 << 18) +#define MXC_CCM_CIMR_MASK_AXI_A_PODF_LOADED (0x1 << 17) +#define MXC_CCM_CIMR_MASK_DIVIDER_LOADED (0x1 << 16) +#define MXC_CCM_CIMR_MASK_COSC_READY (0x1 << 5) +#define MXC_CCM_CIMR_MASK_CKIH_READY (0x1 << 4) +#define MXC_CCM_CIMR_MASK_FPM_READY (0x1 << 3) +#define MXC_CCM_CIMR_MASK_LRF_PLL3 (0x1 << 2) +#define MXC_CCM_CIMR_MASK_LRF_PLL2 (0x1 << 1) +#define MXC_CCM_CIMR_MASK_LRF_PLL1 (0x1) + +/* Define the bits in register CCOSR */ +#define MXC_CCM_CCOSR_CKO2_EN_OFFSET (0x1 << 24) +#define MXC_CCM_CCOSR_CKO2_DIV_OFFSET (21) +#define MXC_CCM_CCOSR_CKO2_DIV_MASK (0x7 << 21) +#define MXC_CCM_CCOSR_CKO2_SEL_OFFSET (16) +#define MXC_CCM_CCOSR_CKO2_SEL_MASK (0x1F << 16) +#define MXC_CCM_CCOSR_CKOL_EN (0x1 << 7) +#define MXC_CCM_CCOSR_CKOL_DIV_OFFSET (4) +#define MXC_CCM_CCOSR_CKOL_DIV_MASK (0x7 << 4) +#define MXC_CCM_CCOSR_CKOL_SEL_OFFSET (0) +#define MXC_CCM_CCOSR_CKOL_SEL_MASK (0xF) + +/* Define the bits in registers CGPR */ +#define MXC_CCM_CGPR_EFUSE_PROG_SUPPLY_GATE (0x1 << 4) +#define MXC_CCM_CGPR_FPM_SEL (0x1 << 3) +#define MXC_CCM_CGPR_VL_L2BIST_CLKDIV_OFFSET (0) +#define MXC_CCM_CGPR_VL_L2BIST_CLKDIV_MASK (0x7) + +/* Define the bits in registers CCGRx */ +#define MXC_CCM_CCGR_CG_MASK 0x3 + +#define MXC_CCM_CCGR0_CG15_OFFSET 30 +#define MXC_CCM_CCGR0_CG14_OFFSET 28 +#define MXC_CCM_CCGR0_CG14_MASK (0x3 << 28) +#define MXC_CCM_CCGR0_CG13_OFFSET 26 +#define MXC_CCM_CCGR0_CG13_MASK (0x3 << 26) +#define MXC_CCM_CCGR0_CG12_OFFSET 24 +#define MXC_CCM_CCGR0_CG12_MASK (0x3 << 24) +#define MXC_CCM_CCGR0_CG11_OFFSET 22 +#define MXC_CCM_CCGR0_CG10_OFFSET 20 +#define MXC_CCM_CCGR0_CG9_OFFSET 18 +#define MXC_CCM_CCGR0_CG8_OFFSET 16 +#define MXC_CCM_CCGR0_CG7_OFFSET 14 +#define MXC_CCM_CCGR0_CG6_OFFSET 12 +#define MXC_CCM_CCGR0_CG5_OFFSET 10 +#define MXC_CCM_CCGR0_CG4_OFFSET 8 +#define MXC_CCM_CCGR0_CG3_OFFSET 6 +#define MXC_CCM_CCGR0_CG2_OFFSET 4 +#define MXC_CCM_CCGR0_CG2_MASK (0x3 << 4) +#define MXC_CCM_CCGR0_CG1_OFFSET 2 +#define MXC_CCM_CCGR0_CG1_MASK (0x3 << 2) +#define MXC_CCM_CCGR0_CG0_OFFSET 0 +#define MXC_CCM_CCGR0_CG0_MASK 0x3 + +#define MXC_CCM_CCGR1_CG15_OFFSET 30 +#define MXC_CCM_CCGR1_CG14_OFFSET 28 +#define MXC_CCM_CCGR1_CG13_OFFSET 26 +#define MXC_CCM_CCGR1_CG12_OFFSET 24 +#define MXC_CCM_CCGR1_CG11_OFFSET 22 +#define MXC_CCM_CCGR1_CG10_OFFSET 20 +#define MXC_CCM_CCGR1_CG9_OFFSET 18 +#define MXC_CCM_CCGR1_CG8_OFFSET 16 +#define MXC_CCM_CCGR1_CG7_OFFSET 14 +#define MXC_CCM_CCGR1_CG6_OFFSET 12 +#define MXC_CCM_CCGR1_CG5_OFFSET 10 +#define MXC_CCM_CCGR1_CG4_OFFSET 8 +#define MXC_CCM_CCGR1_CG3_OFFSET 6 +#define MXC_CCM_CCGR1_CG2_OFFSET 4 +#define MXC_CCM_CCGR1_CG1_OFFSET 2 +#define MXC_CCM_CCGR1_CG0_OFFSET 0 + +#define MXC_CCM_CCGR2_CG15_OFFSET 30 +#define MXC_CCM_CCGR2_CG14_OFFSET 28 +#define MXC_CCM_CCGR2_CG13_OFFSET 26 +#define MXC_CCM_CCGR2_CG12_OFFSET 24 +#define MXC_CCM_CCGR2_CG11_OFFSET 22 +#define MXC_CCM_CCGR2_CG10_OFFSET 20 +#define MXC_CCM_CCGR2_CG9_OFFSET 18 +#define MXC_CCM_CCGR2_CG8_OFFSET 16 +#define MXC_CCM_CCGR2_CG7_OFFSET 14 +#define MXC_CCM_CCGR2_CG6_OFFSET 12 +#define MXC_CCM_CCGR2_CG5_OFFSET 10 +#define MXC_CCM_CCGR2_CG4_OFFSET 8 +#define MXC_CCM_CCGR2_CG3_OFFSET 6 +#define MXC_CCM_CCGR2_CG2_OFFSET 4 +#define MXC_CCM_CCGR2_CG1_OFFSET 2 +#define MXC_CCM_CCGR2_CG0_OFFSET 0 + +#define MXC_CCM_CCGR3_CG15_OFFSET 30 +#define MXC_CCM_CCGR3_CG14_OFFSET 28 +#define MXC_CCM_CCGR3_CG13_OFFSET 26 +#define MXC_CCM_CCGR3_CG12_OFFSET 24 +#define MXC_CCM_CCGR3_CG11_OFFSET 22 +#define MXC_CCM_CCGR3_CG10_OFFSET 20 +#define MXC_CCM_CCGR3_CG9_OFFSET 18 +#define MXC_CCM_CCGR3_CG8_OFFSET 16 +#define MXC_CCM_CCGR3_CG7_OFFSET 14 +#define MXC_CCM_CCGR3_CG6_OFFSET 12 +#define MXC_CCM_CCGR3_CG5_OFFSET 10 +#define MXC_CCM_CCGR3_CG4_OFFSET 8 +#define MXC_CCM_CCGR3_CG3_OFFSET 6 +#define MXC_CCM_CCGR3_CG2_OFFSET 4 +#define MXC_CCM_CCGR3_CG1_OFFSET 2 +#define MXC_CCM_CCGR3_CG0_OFFSET 0 + +#define MXC_CCM_CCGR4_CG15_OFFSET 30 +#define MXC_CCM_CCGR4_CG14_OFFSET 28 +#define MXC_CCM_CCGR4_CG13_OFFSET 26 +#define MXC_CCM_CCGR4_CG12_OFFSET 24 +#define MXC_CCM_CCGR4_CG11_OFFSET 22 +#define MXC_CCM_CCGR4_CG10_OFFSET 20 +#define MXC_CCM_CCGR4_CG9_OFFSET 18 +#define MXC_CCM_CCGR4_CG8_OFFSET 16 +#define MXC_CCM_CCGR4_CG7_OFFSET 14 +#define MXC_CCM_CCGR4_CG6_OFFSET 12 +#define MXC_CCM_CCGR4_CG5_OFFSET 10 +#define MXC_CCM_CCGR4_CG4_OFFSET 8 +#define MXC_CCM_CCGR4_CG3_OFFSET 6 +#define MXC_CCM_CCGR4_CG2_OFFSET 4 +#define MXC_CCM_CCGR4_CG1_OFFSET 2 +#define MXC_CCM_CCGR4_CG0_OFFSET 0 + +#define MXC_CCM_CCGR5_CG15_OFFSET 30 +#define MXC_CCM_CCGR5_CG14_OFFSET 28 +#define MXC_CCM_CCGR5_CG14_MASK (0x3 << 28) +#define MXC_CCM_CCGR5_CG13_OFFSET 26 +#define MXC_CCM_CCGR5_CG13_MASK (0x3 << 26) +#define MXC_CCM_CCGR5_CG12_OFFSET 24 +#define MXC_CCM_CCGR5_CG12_MASK (0x3 << 24) +#define MXC_CCM_CCGR5_CG11_OFFSET 22 +#define MXC_CCM_CCGR5_CG11_MASK (0x3 << 22) +#define MXC_CCM_CCGR5_CG10_OFFSET 20 +#define MXC_CCM_CCGR5_CG9_OFFSET 18 +#define MXC_CCM_CCGR5_CG8_OFFSET 16 +#define MXC_CCM_CCGR5_CG7_OFFSET 14 +#define MXC_CCM_CCGR5_CG6_OFFSET 12 +#define MXC_CCM_CCGR5_CG5_OFFSET 10 +#define MXC_CCM_CCGR5_CG4_OFFSET 8 +#define MXC_CCM_CCGR5_CG3_OFFSET 6 +#define MXC_CCM_CCGR5_CG2_OFFSET 4 +#define MXC_CCM_CCGR5_CG1_OFFSET 2 +#define MXC_CCM_CCGR5_CG0_OFFSET 0 + +#define MXC_ARM1176_BASE IO_ADDRESS(ARM1176_BASE_ADDR) +#define MXC_GPC_BASE IO_ADDRESS(GPC_BASE_ADDR) +#define MXC_DPTC_LP_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x80) +#define MXC_DPTC_GP_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x100) +#define MXC_DVFS_CORE_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x180) +#define MXC_DPTC_PER_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x1C0) +#define MXC_PGC_IPU_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x220) +#define MXC_PGC_VPU_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x240) +#define MXC_SRPGC_EMI_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x280) +#define MXC_SRPGC_ARM_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x2A0) +#define MXC_EMPGC0_ARM_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x2C0) +#define MXC_EMPGC1_ARM_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x2D0) + +/* ARM1176 platform */ +#define MXC_ARM1176_PLAT_PVID (MXC_ARM1176_BASE + 0x0) +#define MXC_ARM1176_PLAT_GPC (MXC_ARM1176_BASE + 0x4) +#define MXC_ARM1176_PLAT_PIC (MXC_ARM1176_BASE + 0x8) +#define MXC_ARM1176_PLAT_L2SO (MXC_ARM1176_BASE + 0xC) +#define MXC_ARM1176_PLAT_EMSO (MXC_ARM1176_BASE + 0x10) +#define MXC_ARM1176_PLAT_LPC (MXC_ARM1176_BASE + 0x14) +#define MXC_ARM1176_PLAT_ICGC (MXC_ARM1176_BASE + 0x18) +#define MXC_ARM1176_PLAT_AMC (MXC_ARM1176_BASE + 0x1C) + +/* GPC */ +#define MXC_GPC_CNTR (MXC_GPC_BASE + 0x0) +#define MXC_GPC_PGR (MXC_GPC_BASE + 0x4) +#define MXC_GPC_VCR (MXC_GPC_BASE + 0x8) + +/* DVFS CORE */ +#define MXC_DVFSTHRS (MXC_DVFS_CORE_BASE + 0x00) +#define MXC_DVFSCOUN (MXC_DVFS_CORE_BASE + 0x04) +#define MXC_DVFSSIG1 (MXC_DVFS_CORE_BASE + 0x08) +#define MXC_DVFSSIG0 (MXC_DVFS_CORE_BASE + 0x0C) +#define MXC_DVFSGPC0 (MXC_DVFS_CORE_BASE + 0x10) +#define MXC_DVFSGPC1 (MXC_DVFS_CORE_BASE + 0x14) +#define MXC_DVFSGPBT (MXC_DVFS_CORE_BASE + 0x18) +#define MXC_DVFSEMAC (MXC_DVFS_CORE_BASE + 0x1C) +#define MXC_DVFSCNTR (MXC_DVFS_CORE_BASE + 0x20) +#define MXC_DVFSLTR0_0 (MXC_DVFS_CORE_BASE + 0x24) +#define MXC_DVFSLTR0_1 (MXC_DVFS_CORE_BASE + 0x28) +#define MXC_DVFSLTR1_0 (MXC_DVFS_CORE_BASE + 0x2C) +#define MXC_DVFSLTR1_1 (MXC_DVFS_CORE_BASE + 0x30) +#define MXC_DVFSPT0 (MXC_DVFS_CORE_BASE + 0x34) +#define MXC_DVFSPT1 (MXC_DVFS_CORE_BASE + 0x38) +#define MXC_DVFSPT2 (MXC_DVFS_CORE_BASE + 0x3C) +#define MXC_DVFSPT3 (MXC_DVFS_CORE_BASE + 0x40) + +/* DPTC GP */ +#define MXC_GP_DPTCCR (MXC_DPTC_GP_BASE + 0x00) +#define MXC_GP_DPTCDBG (MXC_DPTC_GP_BASE + 0x04) +#define MXC_GP_DCVR0 (MXC_DPTC_GP_BASE + 0x08) +#define MXC_GP_DCVR1 (MXC_DPTC_GP_BASE + 0x0C) +#define MXC_GP_DCVR2 (MXC_DPTC_GP_BASE + 0x10) +#define MXC_GP_DCVR3 (MXC_DPTC_GP_BASE + 0x14) + +/* DPTC LP */ +#define MXC_LP_DPTCCR (MXC_DPTC_LP_BASE + 0x00) +#define MXC_LP_DPTCDBG (MXC_DPTC_LP_BASE + 0x04) +#define MXC_LP_DCVR0 (MXC_DPTC_LP_BASE + 0x08) +#define MXC_LP_DCVR1 (MXC_DPTC_LP_BASE + 0x0C) +#define MXC_LP_DCVR2 (MXC_DPTC_LP_BASE + 0x10) +#define MXC_LP_DCVR3 (MXC_DPTC_LP_BASE + 0x14) + +#define MXC_DPTCCR_DRCE3 0x00400000 +#define MXC_DPTCCR_DRCE2 0x00200000 +#define MXC_DPTCCR_DRCE1 0x00100000 +#define MXC_DPTCCR_DRCE0 0x00080000 +#define MXC_DPTCCR_DCR_256 0x00060000 +#define MXC_DPTCCR_DCR_128 0x00040000 +#define MXC_DPTCCR_DCR_64 0x00020000 +#define MXC_DPTCCR_DCR_32 0x00000000 +#define MXC_DPTCCR_DSMM 0x00000040 +#define MXC_DPTCCR_DPNVCR 0x00000020 +#define MXC_DPTCCR_DPVV 0x00000010 +#define MXC_DPTCCR_VAIM 0x00000008 +#define MXC_DPTCCR_VAI_OFFSET 1 +#define MXC_DPTCCR_VAI_MASK 0x00000006 +#define MXC_DPTCCR_DEN 0x00000001 + +#define MXC_GPCCNTR_GPCIRQ 0x00100000 +#define MXC_GPCCNTR_DPTC0CR 0x00040000 +#define MXC_GPCCNTR_DPTC1CR 0x00080000 +#define MXC_GPCCNTR_ADU 0x00008000 + +/* SRPG */ +#define MXC_SRPGC_EMI_SRPGCR (MXC_SRPGC_EMI_BASE + 0x0) +#define MXC_SRPGC_ARM_SRPGCR (MXC_SRPGC_ARM_BASE + 0x0) +#define MXC_EMPGC0_ARM_EMPGCR (MXC_EMPGC0_ARM_BASE + 0x0) +#define MXC_EMPGC1_ARM_EMPGCR (MXC_EMPGC1_ARM_BASE + 0x0) + +#define MXC_PGC_IPU_PGCR (MXC_PGC_IPU_BASE + 0x0) +#define MXC_PGC_IPU_PGSR (MXC_PGC_IPU_BASE + 0xC) +#define MXC_PGC_VPU_PGCR (MXC_PGC_VPU_BASE + 0x0) +#define MXC_PGC_VPU_PGSR (MXC_PGC_VPU_BASE + 0xC) + +#define MXC_ARM1176_PLAT_LPC_DSM (1 << 16) +#define MXC_ARM1176_PLAT_LPC_DBG_DSM (1 << 17) + +#define MXC_GPC_PGR_ARMPG_OFFSET 8 +#define MXC_GPC_PGR_ARMPG_MASK (3 << 8) + +#define MXC_PGCR_PCR 1 +#define MXC_SRPGCR_PCR 1 +#define MXC_EMPGCR_PCR 1 + +#define MXC_PGSR_PSR 1 + +#endif /* __ARCH_ARM_MACH_MX37_CRM_REGS_H__ */ diff --git a/arch/arm/mach-mx37/devices.c b/arch/arm/mach-mx37/devices.c new file mode 100644 index 000000000000..300648d8f3dd --- /dev/null +++ b/arch/arm/mach-mx37/devices.c @@ -0,0 +1,1004 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/mxc_scc2_driver.h> +#include <linux/spi/spi.h> + +#include <mach/gpio.h> +#include <mach/hardware.h> +#include <mach/mxc_dptc.h> +#include <mach/mxc_dvfs.h> +#include <mach/sdma.h> +#include <mach/spba.h> + +#include "sdma_script_code.h" +#include "crm_regs.h" + +extern struct dptc_wp dptc_gp_wp_allfreq[DPTC_GP_WP_SUPPORTED]; +extern struct dptc_wp dptc_lp_wp_allfreq[DPTC_LP_WP_SUPPORTED]; + +void mxc_sdma_get_script_info(sdma_script_start_addrs * sdma_script_addr) +{ + sdma_script_addr->mxc_sdma_app_2_mcu_addr = app_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_ap_2_ap_addr = ap_2_ap_ADDR; + sdma_script_addr->mxc_sdma_ap_2_bp_addr = -1; + sdma_script_addr->mxc_sdma_bp_2_ap_addr = -1; + sdma_script_addr->mxc_sdma_loopback_on_dsp_side_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_app_addr = mcu_2_app_ADDR; + sdma_script_addr->mxc_sdma_mcu_2_shp_addr = mcu_2_shp_ADDR; + sdma_script_addr->mxc_sdma_mcu_interrupt_only_addr = -1; + sdma_script_addr->mxc_sdma_shp_2_mcu_addr = shp_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_start_addr = (unsigned short *)sdma_code; + sdma_script_addr->mxc_sdma_uartsh_2_mcu_addr = uartsh_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_uart_2_mcu_addr = uart_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_ram_code_size = RAM_CODE_SIZE; + sdma_script_addr->mxc_sdma_ram_code_start_addr = RAM_CODE_START_ADDR; + sdma_script_addr->mxc_sdma_dptc_dvfs_addr = dptc_dvfs_ADDR; + sdma_script_addr->mxc_sdma_firi_2_mcu_addr = -1; + sdma_script_addr->mxc_sdma_firi_2_per_addr = -1; + sdma_script_addr->mxc_sdma_mshc_2_mcu_addr = -1; + sdma_script_addr->mxc_sdma_per_2_app_addr = -1; + sdma_script_addr->mxc_sdma_per_2_firi_addr = -1; + sdma_script_addr->mxc_sdma_per_2_shp_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_ata_addr = mcu_2_ata_ADDR; + sdma_script_addr->mxc_sdma_mcu_2_firi_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_mshc_addr = -1; + sdma_script_addr->mxc_sdma_ata_2_mcu_addr = ata_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_uartsh_2_per_addr = -1; + sdma_script_addr->mxc_sdma_shp_2_per_addr = -1; + sdma_script_addr->mxc_sdma_uart_2_per_addr = -1; + sdma_script_addr->mxc_sdma_app_2_per_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_spdif_addr = mcu_2_spdif_marley_ADDR; +} + +static void mxc_nop_release(struct device *dev) +{ + /* Nothing */ +} + +#if defined(CONFIG_W1_MASTER_MXC) || defined(CONFIG_W1_MASTER_MXC_MODULE) +static struct mxc_w1_config mxc_w1_data = { + .search_rom_accelerator = 0, +}; + +static struct platform_device mxc_w1_devices = { + .name = "mxc_w1", + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_w1_data, + }, + .id = 0 +}; + +static void mxc_init_owire(void) +{ + (void)platform_device_register(&mxc_w1_devices); +} +#else +static inline void mxc_init_owire(void) +{ +} +#endif + +#if defined(CONFIG_RTC_DRV_MXC_V2) || defined(CONFIG_RTC_DRV_MXC_V2_MODULE) +static struct mxc_srtc_platform_data srtc_data = { + .srtc_sec_mode_addr = 0xC3FAC80C, +}; + +static struct resource rtc_resources[] = { + { + .start = SRTC_BASE_ADDR, + .end = SRTC_BASE_ADDR + 0x40, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_INT_SRTC_NTZ, + .flags = IORESOURCE_IRQ, + }, +}; +static struct platform_device mxc_rtc_device = { + .name = "mxc_rtc", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &srtc_data, + }, + .num_resources = ARRAY_SIZE(rtc_resources), + .resource = rtc_resources, +}; +static void mxc_init_rtc(void) +{ + (void)platform_device_register(&mxc_rtc_device); +} +#else +static inline void mxc_init_rtc(void) +{ +} +#endif + +#if defined(CONFIG_MXC_WATCHDOG) || defined(CONFIG_MXC_WATCHDOG_MODULE) + +static struct resource wdt_resources[] = { + { + .start = WDOG1_BASE_ADDR, + .end = WDOG1_BASE_ADDR + 0x30, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device mxc_wdt_device = { + .name = "mxc_wdt", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(wdt_resources), + .resource = wdt_resources, +}; + +static void mxc_init_wdt(void) +{ + (void)platform_device_register(&mxc_wdt_device); +} +#else +static inline void mxc_init_wdt(void) +{ +} +#endif + +#if defined(CONFIG_MXC_IPU_V3) || defined(CONFIG_MXC_IPU_V3_MODULE) +static struct mxc_ipu_config mxc_ipu_data = { + .rev = 1, +}; + +static struct resource ipu_resources[] = { + { + .start = IPU_CTRL_BASE_ADDR, + .end = IPU_CTRL_BASE_ADDR + SZ_512M, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_INT_IPU_SYN, + .flags = IORESOURCE_IRQ, + }, + { + .start = MXC_INT_IPU_ERR, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_ipu_device = { + .name = "mxc_ipu", + .id = -1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_ipu_data, + }, + .num_resources = ARRAY_SIZE(ipu_resources), + .resource = ipu_resources, +}; + +static void mxc_init_ipu(void) +{ + mxc_ipu_data.di_clk[1] = clk_get(NULL, "ipu_di_clk"); + + platform_device_register(&mxc_ipu_device); +} +#else +static inline void mxc_init_ipu(void) +{ +} +#endif + +/*! + * This is platform device structure for adding SCC + */ +#if defined(CONFIG_MXC_SECURITY_SCC) || defined(CONFIG_MXC_SECURITY_SCC_MODULE) +static struct platform_device mxc_scc_device = { + .name = "mxc_scc", + .id = 0, +}; + +static void mxc_init_scc(void) +{ + platform_device_register(&mxc_scc_device); +} +#else +static inline void mxc_init_scc(void) +{ + uint32_t reg_value; + uint8_t *UMID_base; + uint32_t *MAP_base; + uint8_t i; + uint32_t partition_no; + uint32_t scc_partno; + void *scm_ram_base; + void *scc_base; + + scc_base = ioremap((uint32_t) SCC_BASE_ADDR, 0x140); + if (scc_base == NULL) { + printk(KERN_ERR "FAILED TO MAP IRAM REGS\n"); + return; + } + scm_ram_base = ioremap((uint32_t) IRAM_BASE_ADDR, IRAM_SIZE); + if (scm_ram_base == NULL) { + printk(KERN_ERR "FAILED TO MAP IRAM\n"); + return; + } + + for (partition_no = 0; partition_no < 9; partition_no++) { + reg_value = ((partition_no << SCM_ZCMD_PART_SHIFT) & + SCM_ZCMD_PART_MASK) | ((0x03 << + SCM_ZCMD_CCMD_SHIFT) + & SCM_ZCMD_CCMD_MASK); + __raw_writel(reg_value, scc_base + SCM_ZCMD_REG); + + while ((__raw_readl(scc_base + SCM_STATUS_REG) & + SCM_STATUS_SRS_READY) != SCM_STATUS_SRS_READY) ; + + __raw_writel(0, scc_base + (SCM_SMID0_REG + 8 * partition_no)); + + reg_value = __raw_readl(scc_base + SCM_PART_OWNERS_REG); + + if (((reg_value >> (2 * (partition_no))) & 3) != 3) { + printk(KERN_ERR "FAILED TO ACQUIRE IRAM PARTITION\n"); + iounmap(scm_ram_base); + return; + } + + MAP_base = scm_ram_base + (partition_no * 0x2000); + UMID_base = (uint8_t *) MAP_base + 0x10; + + for (i = 0; i < 16; i++) + UMID_base[i] = 0; + + MAP_base[0] = SCM_PERM_NO_ZEROIZE | SCM_PERM_HD_SUP_DISABLE | + SCM_PERM_HD_READ | SCM_PERM_HD_WRITE | + SCM_PERM_TH_READ | SCM_PERM_TH_WRITE; + + } + + /*Freeing 2 partitions for SCC2 */ + scc_partno = 9 - (SCC_IRAM_SIZE / SZ_8K); + for (partition_no = scc_partno; partition_no < 9; partition_no++) { + reg_value = ((partition_no << SCM_ZCMD_PART_SHIFT) & + SCM_ZCMD_PART_MASK) | ((0x03 << + SCM_ZCMD_CCMD_SHIFT) + & SCM_ZCMD_CCMD_MASK); + __raw_writel(reg_value, scc_base + SCM_ZCMD_REG); + + while ((__raw_readl(scc_base + SCM_STATUS_REG) & + SCM_STATUS_SRS_READY) != SCM_STATUS_SRS_READY) ; + } + iounmap(scm_ram_base); + iounmap(scc_base); + printk(KERN_INFO "IRAM READY\n"); + +} +#endif + +/* SPI controller and device data */ +#if defined(CONFIG_SPI_MXC) || defined(CONFIG_SPI_MXC_MODULE) + +#ifdef CONFIG_SPI_MXC_SELECT1 +/*! + * Resource definition for the CSPI1 + */ +static struct resource mxcspi1_resources[] = { + [0] = { + .start = CSPI1_BASE_ADDR, + .end = CSPI1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CSPI1, + .end = MXC_INT_CSPI1, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC CSPI1 */ +static struct mxc_spi_master mxcspi1_data = { + .maxchipselect = 4, + .spi_version = 23, +}; + +/*! Device Definition for MXC CSPI1 */ +static struct platform_device mxcspi1_device = { + .name = "mxc_spi", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxcspi1_data, + }, + .num_resources = ARRAY_SIZE(mxcspi1_resources), + .resource = mxcspi1_resources, +}; + +#endif /* CONFIG_SPI_MXC_SELECT1 */ + +#ifdef CONFIG_SPI_MXC_SELECT2 +/*! + * Resource definition for the CSPI2 + */ +static struct resource mxcspi2_resources[] = { + [0] = { + .start = CSPI2_BASE_ADDR, + .end = CSPI2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CSPI2, + .end = MXC_INT_CSPI2, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC CSPI2 */ +static struct mxc_spi_master mxcspi2_data = { + .maxchipselect = 4, + .spi_version = 23, +}; + +/*! Device Definition for MXC CSPI2 */ +static struct platform_device mxcspi2_device = { + .name = "mxc_spi", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxcspi2_data, + }, + .num_resources = ARRAY_SIZE(mxcspi2_resources), + .resource = mxcspi2_resources, +}; +#endif /* CONFIG_SPI_MXC_SELECT2 */ + +#ifdef CONFIG_SPI_MXC_SELECT3 +/*! + * Resource definition for the CSPI3 + */ +static struct resource mxcspi3_resources[] = { + [0] = { + .start = CSPI3_BASE_ADDR, + .end = CSPI3_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CSPI3, + .end = MXC_INT_CSPI3, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC CSPI3 */ +static struct mxc_spi_master mxcspi3_data = { + .maxchipselect = 4, + .spi_version = 23, +}; + +/*! Device Definition for MXC CSPI3 */ +static struct platform_device mxcspi3_device = { + .name = "mxc_spi", + .id = 2, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxcspi3_data, + }, + .num_resources = ARRAY_SIZE(mxcspi3_resources), + .resource = mxcspi3_resources, +}; +#endif /* CONFIG_SPI_MXC_SELECT3 */ + +void __init mxc_init_spi(void) +{ + /* SPBA configuration for CSPI2 - MCU is set */ + spba_take_ownership(SPBA_CSPI2, SPBA_MASTER_A); +#ifdef CONFIG_SPI_MXC_SELECT1 + if (platform_device_register(&mxcspi1_device) < 0) + printk("Error: Registering the SPI Controller_1\n"); +#endif /* CONFIG_SPI_MXC_SELECT1 */ +#ifdef CONFIG_SPI_MXC_SELECT2 + if (platform_device_register(&mxcspi2_device) < 0) + printk("Error: Registering the SPI Controller_2\n"); +#endif /* CONFIG_SPI_MXC_SELECT2 */ +#ifdef CONFIG_SPI_MXC_SELECT3 + if (platform_device_register(&mxcspi3_device) < 0) + printk("Error: Registering the SPI Controller_3\n"); +#endif /* CONFIG_SPI_MXC_SELECT3 */ +} +#else +void __init mxc_init_spi(void) +{ +} +#endif + +/* I2C controller and device data */ +#if defined(CONFIG_I2C_MXC) || defined(CONFIG_I2C_MXC_MODULE) + +#ifdef CONFIG_I2C_MXC_SELECT1 +/*! + * Resource definition for the I2C1 + */ +static struct resource mxci2c1_resources[] = { + [0] = { + .start = I2C_BASE_ADDR, + .end = I2C_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C, + .end = MXC_INT_I2C, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c1_data = { + .i2c_clk = 100000, +}; +#endif + +#ifdef CONFIG_I2C_MXC_SELECT2 +/*! + * Resource definition for the I2C2 + */ +static struct resource mxci2c2_resources[] = { + [0] = { + .start = I2C2_BASE_ADDR, + .end = I2C2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C2, + .end = MXC_INT_I2C2, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c2_data = { + .i2c_clk = 100000, +}; +#endif + +#ifdef CONFIG_I2C_MXC_SELECT3 +/*! + * Resource definition for the I2C3 + */ +static struct resource mxci2c3_resources[] = { + [0] = { + .start = I2C3_BASE_ADDR, + .end = I2C3_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C3, + .end = MXC_INT_I2C3, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c3_data = { + .i2c_clk = 100000, +}; +#endif + +/*! Device Definition for MXC I2C1 */ +static struct platform_device mxci2c_devices[] = { +#ifdef CONFIG_I2C_MXC_SELECT1 + { + .name = "mxc_i2c", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c1_data, + }, + .num_resources = ARRAY_SIZE(mxci2c1_resources), + .resource = mxci2c1_resources,}, +#endif +#ifdef CONFIG_I2C_MXC_SELECT2 + { + .name = "mxc_i2c", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c2_data, + }, + .num_resources = ARRAY_SIZE(mxci2c2_resources), + .resource = mxci2c2_resources,}, +#endif +#ifdef CONFIG_I2C_MXC_SELECT3 + { + .name = "mxc_i2c", + .id = 2, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c3_data, + }, + .num_resources = ARRAY_SIZE(mxci2c3_resources), + .resource = mxci2c3_resources,}, +#endif +}; + +static inline void mxc_init_i2c(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mxci2c_devices); i++) { + if (platform_device_register(&mxci2c_devices[i]) < 0) + dev_err(&mxci2c_devices[i].dev, + "Unable to register I2C device\n"); + } +} +#else +static inline void mxc_init_i2c(void) +{ +} +#endif + +struct tve_platform_data tve_data = { + .dac_reg = "VVIDEO", + .dig_reg = "VDIG", +}; + +static struct resource tve_resources[] = { + { + .start = TVE_BASE_ADDR, + .end = TVE_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_INT_TVOUT, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_tve_device = { + .name = "tve", + .dev = { + .release = mxc_nop_release, + .platform_data = &tve_data, + }, + .num_resources = ARRAY_SIZE(tve_resources), + .resource = tve_resources, +}; + +void __init mxc_init_tve(void) +{ + platform_device_register(&mxc_tve_device); +} + +/*! + * Resource definition for the DVFS CORE + */ +static struct resource dvfs_core_resources[] = { + [0] = { + .start = MXC_DVFS_CORE_BASE, + .end = MXC_DVFS_CORE_BASE + 4 * SZ_16 - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_GPC1, + .end = MXC_INT_GPC1, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for DVFS CORE */ +struct mxc_dvfs_platform_data dvfs_core_data = { + .reg_id = "SW1", + .clk1_id = "cpu_clk", + .clk2_id = "gpc_dvfs_clk", + .gpc_cntr_reg_addr = MXC_GPC_CNTR, + .gpc_vcr_reg_addr = MXC_GPC_VCR, + .ccm_cdcr_reg_addr = MXC_CCM_CDCR, + .ccm_cacrr_reg_addr = MXC_CCM_CACRR, + .ccm_cdhipr_reg_addr = MXC_CCM_CDHIPR, + .dvfs_thrs_reg_addr = MXC_DVFSTHRS, + .dvfs_coun_reg_addr = MXC_DVFSCOUN, + .dvfs_emac_reg_addr = MXC_DVFSEMAC, + .dvfs_cntr_reg_addr = MXC_DVFSCNTR, + .prediv_mask = 0x3800, + .prediv_offset = 11, + .prediv_val = 1, + .div3ck_mask = 0x00000006, + .div3ck_offset = 1, + .div3ck_val = 3, + .emac_val = 0x08, + .upthr_val = 30, + .dnthr_val = 10, + .pncthr_val = 33, + .upcnt_val = 5, + .dncnt_val = 5, + .delay_time = 100, + .num_wp = 3, +}; + +/*! Device Definition for MXC DVFS core */ +static struct platform_device mxc_dvfs_core_device = { + .name = "mxc_dvfs_core", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &dvfs_core_data, + }, + .num_resources = ARRAY_SIZE(dvfs_core_resources), + .resource = dvfs_core_resources, +}; + +static inline void mxc_init_dvfs(void) +{ + if (platform_device_register(&mxc_dvfs_core_device) < 0) + dev_err(&mxc_dvfs_core_device.dev, + "Unable to register DVFS core device\n"); +} + +/*! + * Resource definition for the DPTC GP + */ +static struct resource dptc_gp_resources[] = { + [0] = { + .start = MXC_DPTC_GP_BASE, + .end = MXC_DPTC_GP_BASE + 8 * SZ_16 - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_GPC1, + .end = MXC_INT_GPC1, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for DPTC GP */ +struct mxc_dptc_data dptc_gp_data = { + .reg_id = "SW1", + .clk_id = "cpu_clk", + .dptccr_reg_addr = MXC_GP_DPTCCR, + .dcvr0_reg_addr = MXC_GP_DCVR0, + .gpc_cntr_reg_addr = MXC_GPC_CNTR, + .dptccr = MXC_GPCCNTR_DPTC0CR, + .dptc_wp_supported = DPTC_GP_WP_SUPPORTED, + .dptc_wp_allfreq = dptc_gp_wp_allfreq, + .clk_max_val = 532000000, + .gpc_adu = MXC_GPCCNTR_ADU, + .vai_mask = MXC_DPTCCR_VAI_MASK, + .vai_offset = MXC_DPTCCR_VAI_OFFSET, + .dptc_enable_bit = MXC_DPTCCR_DEN, + .irq_mask = MXC_DPTCCR_VAIM, + .dptc_nvcr_bit = MXC_DPTCCR_DPNVCR, + .gpc_irq_bit = MXC_GPCCNTR_GPCIRQ, + .init_config = + MXC_DPTCCR_DRCE0 | MXC_DPTCCR_DRCE1 | MXC_DPTCCR_DRCE2 | + MXC_DPTCCR_DRCE3 | MXC_DPTCCR_DCR_128 | MXC_DPTCCR_DPNVCR | + MXC_DPTCCR_DPVV, + .enable_config = + MXC_DPTCCR_DEN | MXC_DPTCCR_DPNVCR | MXC_DPTCCR_DPVV | + MXC_DPTCCR_DSMM, + .dcr_mask = MXC_DPTCCR_DCR_256, +}; + +/*! + * Resource definition for the DPTC LP + */ +static struct resource dptc_lp_resources[] = { + [0] = { + .start = MXC_DPTC_LP_BASE, + .end = MXC_DPTC_LP_BASE + 8 * SZ_16 - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_GPC1, + .end = MXC_INT_GPC1, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC DPTC LP */ +struct mxc_dptc_data dptc_lp_data = { + .reg_id = "SW2", + .clk_id = "ahb_clk", + .dptccr_reg_addr = MXC_LP_DPTCCR, + .dcvr0_reg_addr = MXC_LP_DCVR0, + .gpc_cntr_reg_addr = MXC_GPC_CNTR, + .dptccr = MXC_GPCCNTR_DPTC1CR, + .dptc_wp_supported = DPTC_LP_WP_SUPPORTED, + .dptc_wp_allfreq = dptc_lp_wp_allfreq, + .clk_max_val = 133000000, + .gpc_adu = 0x0, + .vai_mask = MXC_DPTCCR_VAI_MASK, + .vai_offset = MXC_DPTCCR_VAI_OFFSET, + .dptc_enable_bit = MXC_DPTCCR_DEN, + .irq_mask = MXC_DPTCCR_VAIM, + .dptc_nvcr_bit = MXC_DPTCCR_DPNVCR, + .gpc_irq_bit = MXC_GPCCNTR_GPCIRQ, + .init_config = + MXC_DPTCCR_DRCE0 | MXC_DPTCCR_DRCE1 | MXC_DPTCCR_DRCE2 | + MXC_DPTCCR_DRCE3 | MXC_DPTCCR_DCR_128 | MXC_DPTCCR_DPNVCR | + MXC_DPTCCR_DPVV, + .enable_config = + MXC_DPTCCR_DEN | MXC_DPTCCR_DPNVCR | MXC_DPTCCR_DPVV | + MXC_DPTCCR_DSMM, + .dcr_mask = MXC_DPTCCR_DCR_256, +}; + +/*! Device Definition for MXC DPTC */ +static struct platform_device mxc_dptc_devices[] = { + { + .name = "mxc_dptc", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &dptc_gp_data, + }, + .num_resources = ARRAY_SIZE(dptc_gp_resources), + .resource = dptc_gp_resources, + }, + { + .name = "mxc_dptc", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &dptc_lp_data, + }, + .num_resources = ARRAY_SIZE(dptc_lp_resources), + .resource = dptc_lp_resources, + }, +}; + +static inline void mxc_init_dptc(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mxc_dptc_devices); i++) { + if (platform_device_register(&mxc_dptc_devices[i]) < 0) + dev_err(&mxc_dptc_devices[i].dev, + "Unable to register DPTC device\n"); + } +} + +struct mxc_gpio_port mxc_gpio_ports[] = { + [0] = { + .chip.label = "gpio-0", + .base = IO_ADDRESS(GPIO1_BASE_ADDR), + .irq = MXC_INT_GPIO1_LOW, + .irq_high = MXC_INT_GPIO1_HIGH, + .virtual_irq_start = MXC_GPIO_IRQ_START + }, + [1] = { + .chip.label = "gpio-1", + .base = IO_ADDRESS(GPIO2_BASE_ADDR), + .irq = MXC_INT_GPIO2_LOW, + .irq_high = MXC_INT_GPIO2_HIGH, + .virtual_irq_start = MXC_GPIO_IRQ_START + 32 + }, + [2] = { + .chip.label = "gpio-2", + .base = IO_ADDRESS(GPIO3_BASE_ADDR), + .irq = MXC_INT_GPIO3_LOW, + .irq_high = MXC_INT_GPIO3_HIGH, + .virtual_irq_start = MXC_GPIO_IRQ_START + 32 * 2 + } +}; + +int __init mxc_register_gpios(void) +{ + return mxc_gpio_init(mxc_gpio_ports, ARRAY_SIZE(mxc_gpio_ports)); +} + +#if defined(CONFIG_MXC_VPU) || defined(CONFIG_MXC_VPU_MODULE) +static struct resource vpu_resources[] = { + [0] = { + .start = VPU_IRAM_BASE_ADDR, + .end = VPU_IRAM_BASE_ADDR + VPU_IRAM_SIZE, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IO_ADDRESS(SRC_BASE_ADDR), + .end = IO_ADDRESS(SRC_BASE_ADDR), + .flags = IORESOURCE_MEM, + }, +}; + +/*! Platform Data for MXC VPU */ +static struct platform_device mxcvpu_device = { + .name = "mxc_vpu", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(vpu_resources), + .resource = vpu_resources, +}; + +static inline void mxc_init_vpu(void) +{ + if (platform_device_register(&mxcvpu_device) < 0) + printk(KERN_ERR "Error: Registering the VPU.\n"); +} +#else +static inline void mxc_init_vpu(void) +{ +} +#endif + +static struct platform_device mxc_dma_device = { + .name = "mxc_dma", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, +}; + +static inline void mxc_init_dma(void) +{ + (void)platform_device_register(&mxc_dma_device); +} + +static struct resource spdif_resources[] = { + { + .start = SPDIF_BASE_ADDR, + .end = SPDIF_BASE_ADDR + 0x50, + .flags = IORESOURCE_MEM, + }, +}; + +static struct mxc_spdif_platform_data mxc_spdif_data = { + .spdif_tx = 1, + .spdif_rx = 0, + .spdif_clk_44100 = 0, + .spdif_clk_48000 = 3, + .spdif_clk = NULL, + .spdif_core_clk = NULL, +}; + +static struct platform_device mxc_alsa_spdif_device = { + .name = "mxc_alsa_spdif", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_spdif_data, + }, + .num_resources = ARRAY_SIZE(spdif_resources), + .resource = spdif_resources, +}; + +static inline void mxc_init_spdif(void) +{ + struct clk *ckih_clk; + ckih_clk = clk_get(NULL, "ckih"); + mxc_spdif_data.spdif_core_clk = clk_get(NULL, "spdif_xtal_clk"); + clk_set_parent(mxc_spdif_data.spdif_core_clk, ckih_clk); + clk_put(ckih_clk); + clk_put(mxc_spdif_data.spdif_core_clk); + platform_device_register(&mxc_alsa_spdif_device); +} + +static struct platform_device mx37_lpmode_device = { + .name = "mx37_lpmode", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, +}; + +static inline void mx37_init_lpmode(void) +{ + (void)platform_device_register(&mx37_lpmode_device); +} + +static struct platform_device busfreq_device = { + .name = "busfreq", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, +}; + +static inline void mxc_init_busfreq(void) +{ + (void)platform_device_register(&busfreq_device); +} + +#if defined(CONFIG_HW_RANDOM_FSL_RNGC) || \ +defined(CONFIG_HW_RANDOM_FSL_RNGC_MODULE) +static struct resource rngc_resources[] = { + { + .start = RNGC_BASE_ADDR, + .end = RNGC_BASE_ADDR + 0x34, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_INT_RNG, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device fsl_rngc_device = { + .name = "fsl_rngc", + .id = -1, + .num_resources = ARRAY_SIZE(rngc_resources), + .resource = rngc_resources, +}; + +static inline void mxc_init_rngc(void) +{ + platform_device_register(&fsl_rngc_device); +} +#else +static inline void mxc_init_rngc(void) +{ +} +#endif + +#if defined(CONFIG_MXC_IIM) || defined(CONFIG_MXC_IIM_MODULE) +static struct resource mxc_iim_resources[] = { + { + .start = IIM_BASE_ADDR, + .end = IIM_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device mxc_iim_device = { + .name = "mxc_iim", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(mxc_iim_resources), + .resource = mxc_iim_resources +}; + +static inline void mxc_init_iim(void) +{ + if (platform_device_register(&mxc_iim_device) < 0) + dev_err(&mxc_iim_device.dev, + "Unable to register mxc iim device\n"); +} +#else +static inline void mxc_init_iim(void) +{ +} +#endif + +int __init mxc_init_devices(void) +{ + mxc_init_wdt(); + mxc_init_ipu(); + mxc_init_spi(); + mxc_init_i2c(); + mxc_init_rtc(); + mxc_init_owire(); + mxc_init_scc(); + mxc_init_dma(); + mxc_init_vpu(); + mxc_init_spdif(); + mxc_init_tve(); + mx37_init_lpmode(); + mxc_init_busfreq(); + mxc_init_dvfs(); + mxc_init_dptc(); + mxc_init_rngc(); + mxc_init_iim(); + + /* SPBA configuration for SSI2 - SDMA and MCU are set */ + spba_take_ownership(SPBA_SSI2, SPBA_MASTER_C | SPBA_MASTER_A); + return 0; +} diff --git a/arch/arm/mach-mx37/dma.c b/arch/arm/mach-mx37/dma.c new file mode 100644 index 000000000000..5732c5803888 --- /dev/null +++ b/arch/arm/mach-mx37/dma.c @@ -0,0 +1,666 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/init.h> +#include <linux/device.h> +#include <asm/dma.h> +#include <mach/hardware.h> + +#include "serial.h" + +#define MXC_MMC_BUFFER_ACCESS 0x20 +#define MXC_SDHC_MMC_WML 512 +#define MXC_SDHC_SD_WML 512 +#define MXC_SSI_TX0_REG 0x0 +#define MXC_SSI_TX1_REG 0x4 +#define MXC_SSI_RX0_REG 0x8 +#define MXC_SSI_RX1_REG 0xC +#define MXC_SSI_TXFIFO_WML 0x4 +#define MXC_SSI_RXFIFO_WML 0x6 +#define MXC_SPDIF_TXFIFO_WML 0x0 +#define MXC_SPDIF_TX_REG 0x2C + +typedef struct mxc_sdma_info_entry_s { + mxc_dma_device_t device; + mxc_sdma_channel_params_t *chnl_info; +} mxc_sdma_info_entry_t; + +static mxc_sdma_channel_params_t mxc_sdma_uart1_rx_params = { + .chnl_params = { + .watermark_level = UART1_UFCR_RXTL, + .per_address = UART1_BASE_ADDR, + .peripheral_type = UART, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART1_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART1_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart1_tx_params = { + .chnl_params = { + .watermark_level = UART1_UFCR_TXTL, + .per_address = UART1_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART1_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART1_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart2_rx_params = { + .chnl_params = { + .watermark_level = UART2_UFCR_RXTL, + .per_address = UART2_BASE_ADDR, + .peripheral_type = UART, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART2_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART2_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart2_tx_params = { + .chnl_params = { + .watermark_level = UART2_UFCR_TXTL, + .per_address = UART2_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART2_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART2_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart3_rx_params = { + .chnl_params = { + .watermark_level = UART3_UFCR_RXTL, + .per_address = UART3_BASE_ADDR, + .peripheral_type = UART_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART3_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART3_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart3_tx_params = { + .chnl_params = { + .watermark_level = UART3_UFCR_TXTL, + .per_address = UART3_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART3_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART3_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_mmc1_width1_params = { + .chnl_params = { + .watermark_level = MXC_SDHC_MMC_WML, + .per_address = + MMC_SDHC1_BASE_ADDR + MXC_MMC_BUFFER_ACCESS, + .peripheral_type = MMC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SDHC1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MMC1, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_mmc1_width4_params = { + .chnl_params = { + .watermark_level = MXC_SDHC_SD_WML, + .per_address = + MMC_SDHC1_BASE_ADDR + MXC_MMC_BUFFER_ACCESS, + .peripheral_type = MMC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SDHC1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MMC1, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_mmc2_width1_params = { + .chnl_params = { + .watermark_level = MXC_SDHC_MMC_WML / 32, + .per_address = + MMC_SDHC2_BASE_ADDR + MXC_MMC_BUFFER_ACCESS, + .peripheral_type = MMC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SDHC2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MMC2, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_mmc2_width4_params = { + .chnl_params = { + .watermark_level = MXC_SDHC_SD_WML / 8, + .per_address = + MMC_SDHC2_BASE_ADDR + MXC_MMC_BUFFER_ACCESS, + .peripheral_type = MMC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SDHC2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MMC2, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_memory_params = { + .chnl_params = { + .peripheral_type = MEMORY, + .transfer_type = emi_2_emi, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MEMORY, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ata_rx_params = { + .chnl_params = { + .watermark_level = MXC_IDE_DMA_WATERMARK, + .per_address = ATA_DMA_BASE_ADDR, + .peripheral_type = ATA, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_ATA_TX_END, + .event_id2 = DMA_REQ_ATA_RX, + .bd_number = MXC_IDE_DMA_BD_NR, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ATA_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ata_tx_params = { + .chnl_params = { + .watermark_level = MXC_IDE_DMA_WATERMARK, + .per_address = ATA_DMA_BASE_ADDR + 0x18, + .peripheral_type = ATA, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_ATA_TX_END, + .event_id2 = DMA_REQ_ATA_TX, + .bd_number = MXC_IDE_DMA_BD_NR, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ATA_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_spdif_16bit_tx_params = { + .chnl_params = { + .watermark_level = MXC_SPDIF_TXFIFO_WML, + .per_address = SPDIF_BASE_ADDR + MXC_SPDIF_TX_REG, + .peripheral_type = SPDIF, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SPDIF_TX, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SPDIF_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_spdif_32bit_tx_params = { + .chnl_params = { + .watermark_level = MXC_SPDIF_TXFIFO_WML, + .per_address = SPDIF_BASE_ADDR + MXC_SPDIF_TX_REG, + .peripheral_type = SPDIF, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SPDIF_TX, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SPDIF_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_info_entry_t mxc_sdma_active_dma_info[] = { + {MXC_DMA_UART1_RX, &mxc_sdma_uart1_rx_params}, + {MXC_DMA_UART1_TX, &mxc_sdma_uart1_tx_params}, + {MXC_DMA_UART2_RX, &mxc_sdma_uart2_rx_params}, + {MXC_DMA_UART2_TX, &mxc_sdma_uart2_tx_params}, + {MXC_DMA_UART3_RX, &mxc_sdma_uart3_rx_params}, + {MXC_DMA_UART3_TX, &mxc_sdma_uart3_tx_params}, + {MXC_DMA_MMC1_WIDTH_1, &mxc_sdma_mmc1_width1_params}, + {MXC_DMA_MMC1_WIDTH_4, &mxc_sdma_mmc1_width4_params}, + {MXC_DMA_MMC2_WIDTH_1, &mxc_sdma_mmc2_width1_params}, + {MXC_DMA_MMC2_WIDTH_4, &mxc_sdma_mmc2_width4_params}, + {MXC_DMA_SSI1_8BIT_RX0, &mxc_sdma_ssi1_8bit_rx0_params}, + {MXC_DMA_SSI1_8BIT_TX0, &mxc_sdma_ssi1_8bit_tx0_params}, + {MXC_DMA_SSI1_16BIT_RX0, &mxc_sdma_ssi1_16bit_rx0_params}, + {MXC_DMA_SSI1_16BIT_TX0, &mxc_sdma_ssi1_16bit_tx0_params}, + {MXC_DMA_SSI1_24BIT_RX0, &mxc_sdma_ssi1_24bit_rx0_params}, + {MXC_DMA_SSI1_24BIT_TX0, &mxc_sdma_ssi1_24bit_tx0_params}, + {MXC_DMA_SSI1_8BIT_RX1, &mxc_sdma_ssi1_8bit_rx1_params}, + {MXC_DMA_SSI1_8BIT_TX1, &mxc_sdma_ssi1_8bit_tx1_params}, + {MXC_DMA_SSI1_16BIT_RX1, &mxc_sdma_ssi1_16bit_rx1_params}, + {MXC_DMA_SSI1_16BIT_TX1, &mxc_sdma_ssi1_16bit_tx1_params}, + {MXC_DMA_SSI1_24BIT_RX1, &mxc_sdma_ssi1_24bit_rx1_params}, + {MXC_DMA_SSI1_24BIT_TX1, &mxc_sdma_ssi1_24bit_tx1_params}, + {MXC_DMA_SSI2_8BIT_RX0, &mxc_sdma_ssi2_8bit_rx0_params}, + {MXC_DMA_SSI2_8BIT_TX0, &mxc_sdma_ssi2_8bit_tx0_params}, + {MXC_DMA_SSI2_16BIT_RX0, &mxc_sdma_ssi2_16bit_rx0_params}, + {MXC_DMA_SSI2_16BIT_TX0, &mxc_sdma_ssi2_16bit_tx0_params}, + {MXC_DMA_SSI2_24BIT_RX0, &mxc_sdma_ssi2_24bit_rx0_params}, + {MXC_DMA_SSI2_24BIT_TX0, &mxc_sdma_ssi2_24bit_tx0_params}, + {MXC_DMA_SSI2_8BIT_RX1, &mxc_sdma_ssi2_8bit_rx1_params}, + {MXC_DMA_SSI2_8BIT_TX1, &mxc_sdma_ssi2_8bit_tx1_params}, + {MXC_DMA_SSI2_16BIT_RX1, &mxc_sdma_ssi2_16bit_rx1_params}, + {MXC_DMA_SSI2_16BIT_TX1, &mxc_sdma_ssi2_16bit_tx1_params}, + {MXC_DMA_SSI2_24BIT_RX1, &mxc_sdma_ssi2_24bit_rx1_params}, + {MXC_DMA_SSI2_24BIT_TX1, &mxc_sdma_ssi2_24bit_tx1_params}, + {MXC_DMA_MEMORY, &mxc_sdma_memory_params}, + {MXC_DMA_ATA_RX, &mxc_sdma_ata_rx_params}, + {MXC_DMA_ATA_TX, &mxc_sdma_ata_tx_params}, + {MXC_DMA_SPDIF_16BIT_TX, &mxc_sdma_spdif_16bit_tx_params}, + {MXC_DMA_SPDIF_32BIT_TX, &mxc_sdma_spdif_32bit_tx_params}, +}; + +static int mxc_sdma_info_entrys = + sizeof(mxc_sdma_active_dma_info) / sizeof(mxc_sdma_active_dma_info[0]); + +/*! + * This functions Returns the SDMA paramaters associated for a module + * + * @param channel_id the ID of the module requesting DMA + * @return returns the sdma parameters structure for the device + */ +mxc_sdma_channel_params_t *mxc_sdma_get_channel_params(mxc_dma_device_t + channel_id) +{ + mxc_sdma_info_entry_t *p = mxc_sdma_active_dma_info; + int i; + + for (i = 0; i < mxc_sdma_info_entrys; i++, p++) { + if (p->device == channel_id) { + return p->chnl_info; + } + } + return NULL; +} + +/*! + * This functions marks the SDMA channels that are statically allocated + * + * @param chnl the channel array used to store channel information + */ +void mxc_get_static_channels(mxc_dma_channel_t * chnl) +{ +#ifdef CONFIG_SDMA_IRAM + int i; + for (i = MXC_DMA_CHANNEL_IRAM; i < MAX_DMA_CHANNELS; i++) + chnl[i].dynamic = 0; +#endif +} + +EXPORT_SYMBOL(mxc_sdma_get_channel_params); +EXPORT_SYMBOL(mxc_get_static_channels); diff --git a/arch/arm/mach-mx37/dptc.c b/arch/arm/mach-mx37/dptc.c new file mode 100644 index 000000000000..4585423c0036 --- /dev/null +++ b/arch/arm/mach-mx37/dptc.c @@ -0,0 +1,69 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dptc_gp.c + * + * @brief DPTC table for the Freescale Semiconductor MXC DPTC module. + * + * @ingroup PM + */ + +#include <mach/hardware.h> +#include <mach/mxc_dptc.h> + +struct dptc_wp dptc_gp_wp_allfreq[DPTC_GP_WP_SUPPORTED] = { + /* 532MHz */ + /* dcvr0 dcvr1 dcvr2 + dcvr3 voltage */ + /* wp0 */ + {DCVR(107, 108, 112), DCVR(122, 123, 127), DCVR(133, 134, 139), + DCVR(115, 116, 121), 1000}, + {DCVR(107, 108, 113), DCVR(122, 123, 127), DCVR(133, 134, 139), + DCVR(115, 117, 122), 975}, + {DCVR(107, 109, 113), DCVR(122, 123, 127), DCVR(133, 134, 139), + DCVR(115, 117, 122), 950}, + {DCVR(107, 109, 114), DCVR(122, 123, 127), DCVR(133, 135, 140), + DCVR(115, 117, 122), 925}, + {DCVR(108, 109, 115), DCVR(122, 123, 127), DCVR(133, 136, 142), + DCVR(115, 117, 123), 900}, + {DCVR(108, 110, 115), DCVR(122, 123, 127), DCVR(133, 136, 142), + DCVR(115, 117, 123), 875}, + {DCVR(108, 110, 115), DCVR(122, 124, 128), DCVR(133, 136, 143), + DCVR(115, 118, 124), 850}, +}; + +struct dptc_wp dptc_lp_wp_allfreq[DPTC_LP_WP_SUPPORTED] = { + /* 532MHz */ + /* dcvr0 dcvr1 dcvr2 + dcvr3 regulator voltage */ + /* wp0 */ + {DCVR(141, 143, 149), DCVR(155, 157, 162), DCVR(106, 108, 112), + DCVR(124, 126, 130), 1200}, + {DCVR(141, 143, 149), DCVR(155, 157, 162), DCVR(106, 108, 113), + DCVR(124, 126, 131), 1175}, + {DCVR(141, 144, 150), DCVR(155, 157, 163), DCVR(106, 108, 113), + DCVR(124, 126, 131), 1150}, + {DCVR(141, 144, 151), DCVR(155, 157, 163), DCVR(106, 108, 114), + DCVR(124, 126, 131), 1125}, + {DCVR(142, 144, 152), DCVR(155, 157, 163), DCVR(107, 109, 114), + DCVR(125, 127, 132), 1100}, + {DCVR(142, 145, 153), DCVR(155, 157, 164), DCVR(107, 109, 115), + DCVR(125, 127, 133), 1075}, + {DCVR(142, 145, 153), DCVR(155, 158, 164), DCVR(107, 109, 116), + DCVR(125, 127, 133), 1050}, + {DCVR(142, 145, 154), DCVR(155, 158, 165), DCVR(107, 110, 117), + DCVR(125, 127, 134), 1025}, + {DCVR(142, 146, 156), DCVR(155, 158, 165), DCVR(107, 110, 117), + DCVR(125, 128, 135), 1000}, +}; diff --git a/arch/arm/mach-mx37/iomux.c b/arch/arm/mach-mx37/iomux.c new file mode 100644 index 000000000000..2d8360ce2b91 --- /dev/null +++ b/arch/arm/mach-mx37/iomux.c @@ -0,0 +1,202 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup GPIO_MX37 Board GPIO and Muxing Setup + * @ingroup MSL_MX37 + */ +/*! + * @file mach-mx37/iomux.c + * + * @brief I/O Muxing control functions + * + * @ingroup GPIO_MX37 + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <mach/hardware.h> +#include <mach/gpio.h> +#include <mach/irqs.h> +#include "iomux.h" + +/*! + * IOMUX register (base) addresses + */ +#define IOMUXGPR0 (IO_ADDRESS(IOMUXC_BASE_ADDR)) /*!< General purpose 0 */ +#define IOMUXGPR1 (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x004) /*!< General purpose 1 */ +#define IOMUXSW_MUX_CTL (IO_ADDRESS(IOMUXC_BASE_ADDR) + MUX_I_START) /*!< MUX control */ +#define IOMUXSW_MUX_END (IO_ADDRESS(IOMUXC_BASE_ADDR) + PAD_I_END) /*!< last MUX control register */ +#define IOMUXSW_PAD_CTL (IO_ADDRESS(IOMUXC_BASE_ADDR) + PAD_I_START) /*!< Pad control */ +#define IOMUXSW_PAD_END (IO_ADDRESS(IOMUXC_BASE_ADDR) + PAD_I_END) /*!< last Pad control register */ +#define IOMUXSW_INPUT_CTL (IO_ADDRESS(IOMUXC_BASE_ADDR) + INPUT_CTL_START) /*!< input select register */ +#define IOMUXSW_INPUT_END (IO_ADDRESS(IOMUXC_BASE_ADDR) + INPUT_CTL_END) /*!< last input select register */ + +#define MUX_PIN_NUM_MAX (((IOMUXSW_MUX_END - IOMUXSW_MUX_CTL) >> 2) + 1) +#define MUX_INPUT_NUM_MUX (((IOMUXSW_INPUT_END - IOMUXSW_INPUT_CTL) >> 2) + 1) + +static u8 iomux_pin_res_table[MUX_PIN_NUM_MAX]; +static DEFINE_SPINLOCK(gpio_mux_lock); + +/*! + * This function is used to configure a pin through the IOMUX module. + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param config a configuration as defined in \b #iomux_pin_cfg_t + * + * @return 0 if successful; Non-zero otherwise + */ +static int iomux_config_mux(iomux_pin_name_t pin, iomux_pin_cfg_t config) +{ + u32 ret = 0; + u32 pin_index = PIN_TO_IOMUX_INDEX(pin); + void *mux_reg = IOMUXSW_MUX_CTL + PIN_TO_IOMUX_MUX(pin); + u32 mux_data = 0; + u8 *rp; + + BUG_ON((mux_reg > IOMUXSW_MUX_END) || (mux_reg < IOMUXSW_MUX_CTL)); + spin_lock(&gpio_mux_lock); + + if (config == IOMUX_CONFIG_GPIO) { + mux_data = PIN_TO_ALT_GPIO(pin); + } else { + mux_data = config; + } + + __raw_writel(mux_data, mux_reg); + + /* + * Log a warning if a pin changes ownership + */ + rp = iomux_pin_res_table + pin_index; + if ((mux_data & *rp) && (*rp != mux_data)) { + /* + * Don't call printk if we're tweaking the console uart or + * we'll deadlock. + */ + printk(KERN_ERR "iomux_config_mux: Warning: iomux pin" + " config changed, pin=%p, " + " prev=0x%x new=0x%x\n", mux_reg, *rp, mux_data); + ret = -EINVAL; + } + *rp = mux_data; + spin_unlock(&gpio_mux_lock); + return ret; +} + +/*! + * Request ownership for an IO pin. This function has to be the first one + * being called before that pin is used. The caller has to check the + * return value to make sure it returns 0. + * + * @param pin a name defined by \b iomux_pin_name_t + * @param config a configuration as defined in \b #iomux_pin_cfg_t + * + * @return 0 if successful; Non-zero otherwise + */ +int mxc_request_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t config) +{ + int ret = iomux_config_mux(pin, config); + int gpio = IOMUX_TO_GPIO(pin); + + if (!ret && (gpio < MXC_GPIO_IRQS) && ((config == IOMUX_CONFIG_GPIO) + || (config == PIN_TO_ALT_GPIO(pin)))) { + ret |= gpio_request(gpio, NULL); + } + return ret; +} + +/*! + * Release ownership for an IO pin + * + * @param pin a name defined by \b iomux_pin_name_t + * @param config config as defined in \b #iomux_pin_ocfg_t + */ +void mxc_free_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t config) +{ + u32 pin_index = PIN_TO_IOMUX_INDEX(pin); + u8 *rp = iomux_pin_res_table + pin_index; + int gpio = IOMUX_TO_GPIO(pin); + + BUG_ON(pin_index > MUX_PIN_NUM_MAX); + *rp = 0; + if ((gpio < MXC_GPIO_IRQS) && ((config == IOMUX_CONFIG_GPIO) + || (config == PIN_TO_ALT_GPIO(pin)))) { + gpio_free(gpio); + } +} + +/*! + * This function configures the pad value for a IOMUX pin. + * + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param config the ORed value of elements defined in \b #iomux_pad_config_t + */ +void mxc_iomux_set_pad(iomux_pin_name_t pin, u32 config) +{ + void *pad_reg = IOMUXSW_PAD_CTL + PIN_TO_IOMUX_PAD(pin); + + BUG_ON((pad_reg > IOMUXSW_PAD_END) || (pad_reg < IOMUXSW_PAD_CTL)); + spin_lock(&gpio_mux_lock); + __raw_writel(config, pad_reg); + spin_unlock(&gpio_mux_lock); +} + +unsigned int mxc_iomux_get_pad(iomux_pin_name_t pin) +{ + void *pad_reg = IOMUXSW_PAD_CTL + PIN_TO_IOMUX_PAD(pin); + return __raw_readl(pad_reg); +} + +/*! + * This function enables/disables the general purpose function for a particular + * signal. + * + * @param gp one signal as defined in \b #iomux_gp_func_t + * @param en \b #true to enable; \b #false to disable + * @param index 0 for GPR0 and 1 for GPR1 + */ +void mxc_iomux_set_gpr(iomux_gp_func_t gp, bool en, u8 index) +{ + volatile u32 l; + + spin_lock(&gpio_mux_lock); + l = __raw_readl(IOMUXGPR0 + (index << 2)); + if (en) { + l |= gp; + } else { + l &= ~gp; + } + __raw_writel(l, IOMUXGPR0 + (index << 2)); + spin_unlock(&gpio_mux_lock); +} + +/*! + * This function configures input path. + * + * @param input index of input select register as defined in \b #iomux_input_select_t + * @param config the binary value of elements defined in \b #iomux_input_config_t + * */ +void mxc_iomux_set_input(iomux_input_select_t input, u32 config) +{ + void *reg = IOMUXSW_INPUT_CTL + (input << 2); + + BUG_ON(input >= MUX_INPUT_NUM_MUX); + __raw_writel(config, reg); +} + +EXPORT_SYMBOL(mxc_request_iomux); +EXPORT_SYMBOL(mxc_free_iomux); +EXPORT_SYMBOL(mxc_iomux_set_input); +EXPORT_SYMBOL(mxc_iomux_set_pad); +EXPORT_SYMBOL(mxc_iomux_set_gpr); diff --git a/arch/arm/mach-mx37/iomux.h b/arch/arm/mach-mx37/iomux.h new file mode 100644 index 000000000000..b8f38212da18 --- /dev/null +++ b/arch/arm/mach-mx37/iomux.h @@ -0,0 +1,228 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MACH_MX37_IOMUX_H__ +#define __MACH_MX37_IOMUX_H__ + +#include <linux/types.h> +#include <mach/gpio.h> +#include "mx37_pins.h" + +/*! + * @file mach-mx37/iomux.h + * + * @brief I/O Muxing control definitions and functions + * + * @ingroup GPIO_MX37 + */ + +typedef unsigned int iomux_pin_name_t; + +/*! + * various IOMUX output functions + */ +typedef enum iomux_config { + IOMUX_CONFIG_ALT0, /*!< used as alternate function 0 */ + IOMUX_CONFIG_ALT1, /*!< used as alternate function 1 */ + IOMUX_CONFIG_ALT2, /*!< used as alternate function 2 */ + IOMUX_CONFIG_ALT3, /*!< used as alternate function 3 */ + IOMUX_CONFIG_ALT4, /*!< used as alternate function 4 */ + IOMUX_CONFIG_ALT5, /*!< used as alternate function 5 */ + IOMUX_CONFIG_ALT6, /*!< used as alternate function 6 */ + IOMUX_CONFIG_ALT7, /*!< used as alternate function 7 */ + IOMUX_CONFIG_GPIO, /*!< added to help user use GPIO mode */ + IOMUX_CONFIG_SION = 0x1 << 4, /*!< used as LOOPBACK:MUX SION bit */ +} iomux_pin_cfg_t; + +/*! + * various IOMUX pad functions + */ +typedef enum iomux_pad_config { + PAD_CTL_SRE_SLOW = 0x0 << 0, + PAD_CTL_SRE_FAST = 0x1 << 0, + PAD_CTL_DRV_LOW = 0x0 << 1, + PAD_CTL_DRV_MEDIUM = 0x1 << 1, + PAD_CTL_DRV_HIGH = 0x2 << 1, + PAD_CTL_DRV_MAX = 0x3 << 1, + PAD_CTL_ODE_OPENDRAIN_NONE = 0x0 << 3, + PAD_CTL_ODE_OPENDRAIN_ENABLE = 0x1 << 3, + PAD_CTL_100K_PD = 0x0 << 4, + PAD_CTL_47K_PU = 0x1 << 4, + PAD_CTL_100K_PU = 0x2 << 4, + PAD_CTL_22K_PU = 0x3 << 4, + PAD_CTL_PUE_KEEPER = 0x0 << 6, + PAD_CTL_PUE_PULL = 0x1 << 6, + PAD_CTL_PKE_NONE = 0x0 << 7, + PAD_CTL_PKE_ENABLE = 0x1 << 7, + PAD_CTL_HYS_NONE = 0x0 << 8, + PAD_CTL_HYS_ENABLE = 0x1 << 8, + PAD_CTL_DDR_INPUT_CMOS = 0x0 << 9, + PAD_CTL_DDR_INPUT_DDR = 0x1 << 9, + PAD_CTL_DRV_VOT_LOW = 0x0 << 13, + PAD_CTL_DRV_VOT_HIGH = 0x1 << 13, +} iomux_pad_config_t; + +/*! + * various IOMUX general purpose functions + */ +typedef enum iomux_gp_func { + MUX_IPD_ESDHC_DREQ_B = 0x0 << 0, + MUX_XDRQ = 0x1 << 0, + MUX_EMI_DMA_ACCESS_1 = 0x0 << 4, + MUX_KEY_COL2 = 0x1 << 4, + MUX_TAMPER_DETECT_EN = 0x1 << 8, + MUX_IPUv3D_TVE = 0x0 << 12, + MUX_IPUv3D_CAMP = 0x1 << 12, +} iomux_gp_func_t; + +/*! + * various IOMUX input select register index + */ +typedef enum iomux_input_select { + MUX_IN_CCM_PLL1_BYPASS_CLK = 0, + MUX_IN_CCM_PLL2_BYPASS_CLK, + MUX_IN_CCM_PLL3_BYPASS_CLK, + MUX_IN_CSPI3_CSPI_CLK, + MUX_IN_CSPI3_MISO, + MUX_IN_CSPI3_MOSI, + MUX_IN_EMI_READ_MADDR_DATA_0, + MUX_IN_EMI_READ_MADDR_DATA_10, + MUX_IN_EMI_READ_MADDR_DATA_11, + MUX_IN_EMI_READ_MADDR_DATA_12, + MUX_IN_EMI_READ_MADDR_DATA_13, + MUX_IN_EMI_READ_MADDR_DATA_14, + MUX_IN_EMI_READ_MADDR_DATA_15, + MUX_IN_EMI_READ_MADDR_DATA_1, + MUX_IN_EMI_READ_MADDR_DATA_2, + MUX_IN_EMI_READ_MADDR_DATA_3, + MUX_IN_EMI_READ_MADDR_DATA_4, + MUX_IN_EMI_READ_MADDR_DATA_5, + MUX_IN_EMI_READ_MADDR_DATA_6, + MUX_IN_EMI_READ_MADDR_DATA_7, + MUX_IN_EMI_READ_MADDR_DATA_8, + MUX_IN_EMI_READ_MADDR_DATA_9, + MUX_IN_EMI_NFC_READ_DATA_IN_0, + MUX_IN_EMI_NFC_READ_DATA_IN_10, + MUX_IN_EMI_NFC_READ_DATA_IN_11, + MUX_IN_EMI_NFC_READ_DATA_IN_12, + MUX_IN_EMI_NFC_READ_DATA_IN_13, + MUX_IN_EMI_NFC_READ_DATA_IN_14, + MUX_IN_EMI_NFC_READ_DATA_IN_15, + MUX_IN_EMI_NFC_READ_DATA_IN_1, + MUX_IN_EMI_NFC_READ_DATA_IN_2, + MUX_IN_EMI_NFC_READ_DATA_IN_3, + MUX_IN_EMI_NFC_READ_DATA_IN_4, + MUX_IN_EMI_NFC_READ_DATA_IN_5, + MUX_IN_EMI_NFC_READ_DATA_IN_6, + MUX_IN_EMI_NFC_READ_DATA_IN_7, + MUX_IN_EMI_NFC_READ_DATA_IN_8, + MUX_IN_EMI_NFC_READ_DATA_IN_9, + MUX_IN_FEC_FEC_COL, + MUX_IN_FEC_FEC_CRS, MUX_IN_FEC_FEC_MDI, + MUX_IN_FEC_FEC_RDATA_0, + MUX_IN_FEC_FEC_RX_CLK, + MUX_IN_FEC_FEC_RX_DV, + MUX_IN_FEC_FEC_RX_ER, + MUX_IN_FEC_FEC_TX_CLK, + MUX_IN_I2C1_SCL, + MUX_IN_I2C1_SDA, + MUX_IN_I2C2_SCL, + MUX_IN_I2C2_SDA, + MUX_IN_I2C3_SCL, + MUX_IN_I2C3_SDA, + MUX_IN_IPU_DI_0_IND_DISPB_D0_VSYNC, + MUX_IN__IPU_DI_0_IND_DISPB_SD_D, + MUX_IN_KPP_ROW_0, + MUX_IN_KPP_ROW_1, + MUX_IN_KPP_ROW_2, + MUX_IN_KPP_ROW_3, + MUX_IN_KPP_ROW_4, + MUX_IN_KPP_ROW_5, + MUX_IN_KPP_ROW_6, + MUX_IN_KPP_ROW_7, + MUX_IN_UART1_UART_RTS_B, + MUX_IN_UART1_UART_RXD_MUX, + MUX_IN_UART2_UART_RTS_B, + MUX_IN_UART2_UART_RXD_MUX, + MUX_IN_UART3_UART_RTS_B, + MUX_IN_UART3_UART_RXD_MUX, +} iomux_input_select_t; + +/*! + * various IOMUX input functions + */ +typedef enum iomux_input_config { + INPUT_CTL_PATH0 = 0x0, + INPUT_CTL_PATH1, + INPUT_CTL_PATH2, + INPUT_CTL_PATH3, + INPUT_CTL_PATH4, + INPUT_CTL_PATH5, + INPUT_CTL_PATH6, + INPUT_CTL_PATH7, +} iomux_input_config_t; + +/*! + * Request ownership for an IO pin. This function has to be the first one + * being called before that pin is used. The caller has to check the + * return value to make sure it returns 0. + * + * @param pin a name defined by \b iomux_pin_name_t + * @param config config as defined in \b #iomux_pin_ocfg_t + * + * @return 0 if successful; Non-zero otherwise + */ +int mxc_request_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t config); + +/*! + * Release ownership for an IO pin + * + * @param pin a name defined by \b iomux_pin_name_t + * @param config config as defined in \b #iomux_pin_ocfg_t + */ +void mxc_free_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t config); + +/*! + * This function enables/disables the general purpose function for a particular + * signal. + * + * @param gp one signal as defined in \b #iomux_gp_func_t + * @param en \b #true to enable; \b #false to disable + * @param index 0 for GPR0 and 1 for GPR1 + */ +void mxc_iomux_set_gpr(iomux_gp_func_t gp, bool en, u8 index); + +/*! + * This function configures the pad value for a IOMUX pin. + * + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param config the ORed value of elements defined in \b #iomux_pad_config_t + */ +void mxc_iomux_set_pad(iomux_pin_name_t pin, u32 config); + +/*! + * This function gets the current pad value for a IOMUX pin. + * + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @return current pad value + */ +unsigned int mxc_iomux_get_pad(iomux_pin_name_t pin); + +/*! + * This function configures input path. + * + * @param input index of input select register as defined in \b #iomux_input_select_t + * @param config the binary value of elements defined in \b #iomux_input_config_t + */ +void mxc_iomux_set_input(iomux_input_select_t input, u32 config); + +#endif /* __MACH_MX37_IOMUX_H__ */ diff --git a/arch/arm/mach-mx37/lpmodes.c b/arch/arm/mach-mx37/lpmodes.c new file mode 100644 index 000000000000..7685a5c249a0 --- /dev/null +++ b/arch/arm/mach-mx37/lpmodes.c @@ -0,0 +1,408 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx37_lpmodes.c + * + * @brief Driver for the Freescale Semiconductor MXC low power modes setup. + * + * MX37 is designed to play and video with minimal power consumption. + * This driver enables the platform to enter and exit audio and video low + * power modes. + * + * @ingroup PM + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <mach/clock.h> +#include <mach/hardware.h> +#include <linux/regulator/consumer.h> +#include "crm_regs.h" + +#define ARM_LP_CLK 200000000 +#define GP_LPM_VOLTAGE 850000 +#define LP_LPM_VOLTAGE 1000000 +#define GP_NORMAL_VOLTAGE 1000000 +#define LP_NORMAL_VOLTAGE 1200000 + +static int org_cpu_rate; +int lp_video_mode; +int lp_audio_mode; +static struct device *lpmode_dev; + +void enter_lp_video_mode(void) +{ + int ret = 0; + + struct clk *p_clk; + struct clk *tclk; + struct clk *vmode_parent_clk; + struct regulator *gp_core; + + tclk = clk_get(NULL, "main_bus_clk"); + vmode_parent_clk = clk_get(NULL, "pll2"); + p_clk = clk_get_parent(tclk); + + if (p_clk != vmode_parent_clk) { + clk_set_parent(tclk, vmode_parent_clk); + + clk_set_rate(clk_get(NULL, "axi_a_clk"), 133000000); + clk_set_rate(clk_get(NULL, "axi_b_clk"), 66500000); + clk_set_rate(clk_get(NULL, "axi_c_clk"), 166000000); + clk_set_rate(clk_get(NULL, "emi_core_clk"), 133000000); + clk_set_rate(clk_get(NULL, "nfc_clk"), 26600000); + clk_set_rate(clk_get(NULL, "ahb_clk"), 133000000); + } + + /* move VPU clock to source from the emi_core_clk */ + tclk = clk_get(NULL, "vpu_clk"); + vmode_parent_clk = clk_get(NULL, "emi_core_clk"); + if (clk_get_parent(tclk) != vmode_parent_clk) + clk_set_parent(tclk, vmode_parent_clk); + + tclk = clk_get(NULL, "vpu_core_clk"); + if (clk_get_parent(tclk) != vmode_parent_clk) + clk_set_parent(tclk, vmode_parent_clk); + + tclk = clk_get(NULL, "arm_axi_clk"); + if (clk_get_parent(tclk) != vmode_parent_clk) + clk_set_parent(tclk, vmode_parent_clk); + + tclk = clk_get(NULL, "ddr_clk"); + vmode_parent_clk = clk_get(NULL, "axi_c_clk"); + if (clk_get_parent(tclk) != vmode_parent_clk) + clk_set_parent(tclk, vmode_parent_clk); + + /* disable PLL3 */ + tclk = clk_get(NULL, "pll3"); + if (tclk->usecount == 1) + clk_disable(tclk); + + tclk = clk_get(NULL, "cpu_clk"); + org_cpu_rate = clk_get_rate(tclk); + + ret = clk_set_rate(tclk, ARM_LP_CLK); + if (ret != 0) + printk(KERN_DEBUG "cannot set CPU clock rate\n"); + + /* Set the voltage to 0.8v for the GP domain. */ + + if (!board_is_rev(BOARD_REV_2)) + gp_core = regulator_get(NULL, "DCDC1"); + else + gp_core = regulator_get(NULL, "SW1"); + + ret = regulator_set_voltage(gp_core, GP_LPM_VOLTAGE, GP_LPM_VOLTAGE); + if (ret < 0) + printk(KERN_DEBUG "COULD NOT SET GP VOLTAGE!!!\n"); + + lp_video_mode = 1; +} + +void exit_lp_video_mode(void) +{ + int ret = 0; + static struct clk *tclk; + struct regulator *gp_core; + + /*Set the voltage to 0.8v for the GP domain. */ + if (!board_is_rev(BOARD_REV_2)) + gp_core = regulator_get(NULL, "DCDC1"); + else + gp_core = regulator_get(NULL, "SW1"); + + ret = regulator_set_voltage(gp_core, GP_NORMAL_VOLTAGE, GP_NORMAL_VOLTAGE); + if (ret < 0) + printk(KERN_DEBUG "COULD NOT SET GP VOLTAGE!!!!\n"); + + tclk = clk_get(NULL, "cpu_clk"); + + ret = clk_set_rate(tclk, org_cpu_rate); + if (ret != 0) + printk(KERN_DEBUG "cannot set CPU clock rate\n"); + + lp_video_mode = 0; +} + +void enter_lp_audio_mode(void) +{ + int ret = 0; + + struct clk *p_clk; + struct clk *tclk; + struct clk *amode_parent_clk; + struct regulator *gp_core; + struct regulator *lp_core; + + tclk = clk_get(NULL, "ipu_clk"); + if (clk_get_usecount(tclk) != 0) { + printk(KERN_INFO + "Cannot enter AUDIO LPM mode - display is still active\n"); + return; + } + + tclk = clk_get(NULL, "periph_apm_clk"); + amode_parent_clk = clk_get(NULL, "lp_apm"); + p_clk = clk_get_parent(tclk); + + /* Make sure osc_clk is the parent of lp_apm. */ + clk_set_parent(amode_parent_clk, clk_get(NULL, "osc")); + + /* Set the parent of periph_apm_clk to be lp_apm */ + clk_set_parent(tclk, amode_parent_clk); + amode_parent_clk = tclk; + + tclk = clk_get(NULL, "main_bus_clk"); + p_clk = clk_get_parent(tclk); + /* Set the parent of main_bus_clk to be periph_apm_clk */ + clk_set_parent(tclk, amode_parent_clk); + + clk_set_rate(clk_get(NULL, "axi_a_clk"), 24000000); + clk_set_rate(clk_get(NULL, "axi_b_clk"), 24000000); + clk_set_rate(clk_get(NULL, "axi_c_clk"), 24000000); + clk_set_rate(clk_get(NULL, "emi_core_clk"), 24000000); + clk_set_rate(clk_get(NULL, "nfc_clk"), 4800000); + clk_set_rate(clk_get(NULL, "ahb_clk"), 24000000); + + amode_parent_clk = clk_get(NULL, "emi_core_clk"); + + tclk = clk_get(NULL, "arm_axi_clk"); + p_clk = clk_get_parent(tclk); + if (p_clk != amode_parent_clk) { + clk_set_parent(tclk, amode_parent_clk); + } + + tclk = clk_get(NULL, "vpu_clk"); + p_clk = clk_get_parent(tclk); + if (p_clk != amode_parent_clk) { + clk_set_parent(tclk, amode_parent_clk); + } + + tclk = clk_get(NULL, "vpu_core_clk"); + p_clk = clk_get_parent(tclk); + if (p_clk != amode_parent_clk) { + clk_set_parent(tclk, amode_parent_clk); + } + + /* disable PLL3 */ + tclk = clk_get(NULL, "pll3"); + if (tclk->usecount == 1) + clk_disable(tclk); + + /* disable PLL2 */ + tclk = clk_get(NULL, "pll2"); + if (tclk->usecount == 1) + clk_disable(tclk); + + /* Set the voltage to 1.0v for the LP domain. */ + if (!board_is_rev(BOARD_REV_2)) + lp_core = regulator_get(NULL, "DCDC4"); + else + lp_core = regulator_get(NULL, "SW2"); + + if (lp_core != NULL) { + ret = regulator_set_voltage(lp_core, LP_LPM_VOLTAGE, LP_LPM_VOLTAGE); + if (ret < 0) + printk(KERN_DEBUG "COULD NOT SET GP VOLTAGE!!!!!!\n"); + } + + tclk = clk_get(NULL, "cpu_clk"); + org_cpu_rate = clk_get_rate(tclk); + + ret = clk_set_rate(tclk, ARM_LP_CLK); + if (ret != 0) + printk(KERN_DEBUG "cannot set CPU clock rate\n"); + + /* Set the voltage to 0.8v for the GP domain. */ + if (!board_is_rev(BOARD_REV_2)) + gp_core = regulator_get(NULL, "DCDC1"); + else + gp_core = regulator_get(NULL, "SW1"); + + if (gp_core != NULL) { + ret = regulator_set_voltage(gp_core, GP_LPM_VOLTAGE, GP_LPM_VOLTAGE); + if (ret < 0) + printk(KERN_DEBUG "COULD NOT SET GP VOLTAGE!!!!!\n"); + } + lp_audio_mode = 1; +} + +void exit_lp_audio_mode(void) +{ + struct regulator *gp_core; + struct regulator *lp_core; + struct clk *tclk; + struct clk *p_clk; + struct clk *rmode_parent_clk; + int ret; + + lp_audio_mode = 0; + /* Set the voltage to 1.2v for the LP domain. */ + if (!board_is_rev(BOARD_REV_2)) + lp_core = regulator_get(NULL, "DCDC4"); + else + lp_core = regulator_get(NULL, "SW2"); + + if (lp_core != NULL) { + ret = regulator_set_voltage(lp_core, LP_NORMAL_VOLTAGE, LP_NORMAL_VOLTAGE); + if (ret < 0) + printk(KERN_DEBUG "COULD NOT SET GP VOLTAGE!!!!!!\n"); + } + + /* Set the voltage to 1.0v for the GP domain. */ + if (!board_is_rev(BOARD_REV_2)) + gp_core = regulator_get(NULL, "DCDC1"); + else + gp_core = regulator_get(NULL, "SW1"); + + ret = regulator_set_voltage(gp_core, GP_NORMAL_VOLTAGE, GP_NORMAL_VOLTAGE); + if (ret < 0) + printk(KERN_DEBUG "COULD NOT SET GP VOLTAGE!!!!\n"); + + tclk = clk_get(NULL, "cpu_clk"); + + ret = clk_set_rate(tclk, org_cpu_rate); + if (ret != 0) + printk(KERN_DEBUG "cannot set CPU clock rate\n"); + + rmode_parent_clk = clk_get(NULL, "pll2"); + clk_enable(rmode_parent_clk); + + tclk = clk_get(NULL, "main_bus_clk"); + p_clk = clk_get_parent(tclk); + + /* Set the dividers before setting the parent clock. */ + clk_set_rate(clk_get(NULL, "axi_a_clk"), 4800000); + clk_set_rate(clk_get(NULL, "axi_b_clk"), 4000000); + clk_set_rate(clk_get(NULL, "axi_c_clk"), 6000000); + clk_set_rate(clk_get(NULL, "emi_core_clk"), 4800000); + clk_set_rate(clk_get(NULL, "ahb_clk"), 4800000); + + /* Set the parent of main_bus_clk to be pll2 */ + clk_set_parent(tclk, rmode_parent_clk); + udelay(5); +} + +static ssize_t lp_curr_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (lp_video_mode) + return sprintf(buf, "in lp_video_mode\n"); + else if (lp_audio_mode) + return sprintf(buf, "in lp_audio_mode\n"); + else + return sprintf(buf, "in normal mode\n"); +} + +static ssize_t set_lp_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + printk(KERN_DEBUG "In set_lp_mode() \n"); + + if (strstr(buf, "enable_lp_video") != NULL) { + if (!lp_video_mode) + enter_lp_video_mode(); + } else if (strstr(buf, "disable_lp_video") != NULL) { + if (lp_video_mode) + exit_lp_video_mode(); + } else if (strstr(buf, "enable_lp_audio") != NULL) { + if (!lp_audio_mode) + enter_lp_audio_mode(); + } else if (strstr(buf, "disable_lp_audio") != NULL) { + if (lp_audio_mode) + exit_lp_audio_mode(); + } + return size; +} + +static DEVICE_ATTR(lp_modes, 0644, lp_curr_mode, set_lp_mode); + +/*! + * This is the probe routine for the lp_mode driver. + * + * @param pdev The platform device structure + * + * @return The function returns 0 on success + * + */ +static int __devinit mx37_lpmode_probe(struct platform_device *pdev) +{ + u32 res = 0; + lpmode_dev = &pdev->dev; + + res = sysfs_create_file(&lpmode_dev->kobj, &dev_attr_lp_modes.attr); + if (res) { + printk(KERN_ERR + "lpmode_dev: Unable to register sysdev entry for lpmode_dev"); + return res; + } + + if (res != 0) { + printk(KERN_ERR "lpmode_dev: Unable to start"); + return res; + } + lp_video_mode = 0; + lp_audio_mode = 0; + + return 0; +} + +static struct platform_driver mx37_lpmode_driver = { + .driver = { + .name = "mx37_lpmode", + }, + .probe = mx37_lpmode_probe, +}; + +/*! + * Initialise the mx37_lpmode_driver. + * + * @return The function always returns 0. + */ + +static int __init lpmode_init(void) +{ + if (platform_driver_register(&mx37_lpmode_driver) != 0) { + printk(KERN_ERR "mx37_lpmode_driver register failed\n"); + return -ENODEV; + } + + printk(KERN_INFO "LPMode driver module loaded\n"); + return 0; +} + +static void __exit lpmode_cleanup(void) +{ + sysfs_remove_file(&lpmode_dev->kobj, &dev_attr_lp_modes.attr); + + /* Unregister the device structure */ + platform_driver_unregister(&mx37_lpmode_driver); +} + +module_init(lpmode_init); +module_exit(lpmode_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("LPMode driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-mx37/mm.c b/arch/arm/mach-mx37/mm.c new file mode 100644 index 000000000000..8f9947fc8fc6 --- /dev/null +++ b/arch/arm/mach-mx37/mm.c @@ -0,0 +1,82 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/mm.h> +#include <linux/init.h> +#include <mach/hardware.h> +#include <asm/pgtable.h> +#include <asm/mach/map.h> + +/*! + * @file mach-mx37/mm.c + * + * @brief This file creates static mapping between physical to virtual memory. + * + * @ingroup Memory_MX37 + */ + +/*! + * This structure defines the MX37 memory map. + */ +static struct map_desc mx37_io_desc[] __initdata = { + { + .virtual = IRAM_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(IRAM_BASE_ADDR), + .length = IRAM_SIZE, + .type = MT_DEVICE_NONSHARED}, + { + .virtual = PLATFORM_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(PLATFORM_BASE_ADDR), + .length = PLATFORM_SIZE, + .type = MT_DEVICE}, + { + .virtual = DEBUG_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(DEBUG_BASE_ADDR), + .length = DEBUG_SIZE, + .type = MT_DEVICE_NONSHARED}, + { + .virtual = TZIC_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(TZIC_BASE_ADDR), + .length = TZIC_SIZE, + .type = MT_DEVICE_NONSHARED}, + { + .virtual = AIPS1_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(AIPS1_BASE_ADDR), + .length = AIPS1_SIZE, + .type = MT_DEVICE_NONSHARED}, + { + .virtual = SPBA0_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(SPBA0_BASE_ADDR), + .length = SPBA0_SIZE, + .type = MT_DEVICE_NONSHARED}, + { + .virtual = AIPS2_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(AIPS2_BASE_ADDR), + .length = AIPS2_SIZE, + .type = MT_DEVICE_NONSHARED}, + { + .virtual = NFC_BASE_ADDR_AXI_VIRT, + .pfn = __phys_to_pfn(NFC_BASE_ADDR_AXI), + .length = NFC_AXI_SIZE, + .type = MT_DEVICE}, +}; + +/*! + * This function initializes the memory map. It is called during the + * system startup to create static physical to virtual memory map for + * the IO modules. + */ +void __init mx37_map_io(void) +{ + iotable_init(mx37_io_desc, ARRAY_SIZE(mx37_io_desc)); +} diff --git a/arch/arm/mach-mx37/mx37_3stack.c b/arch/arm/mach-mx37/mx37_3stack.c new file mode 100644 index 000000000000..b9e88e57daab --- /dev/null +++ b/arch/arm/mach-mx37/mx37_3stack.c @@ -0,0 +1,978 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/nodemask.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/ata.h> +#include <linux/regulator/consumer.h> +#include <linux/pmic_external.h> +#include <linux/smsc911x.h> +#include <linux/i2c/tsc2007.h> +#if defined(CONFIG_MTD) || defined(CONFIG_MTD_MODULE) +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> + +#include <asm/mach/flash.h> +#endif + +#include <mach/hardware.h> +#include <mach/spba.h> +#include <asm/irq.h> +#include <asm/setup.h> +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <asm/mach/time.h> +#include <mach/common.h> +#include <mach/memory.h> +#include <mach/gpio.h> +#include <mach/mmc.h> +#include "board-mx37_3stack.h" +#include "iomux.h" +#include "crm_regs.h" + +/*! + * @file mach-mx37/mx37_3stack.c + * + * @brief This file contains the board specific initialization routines. + * + * @ingroup MSL_MX37 + */ +extern void gpio_lcd_active(void); + +/* working point(wp): 0 - 532MHz; 1 - 200MHz; */ +static struct cpu_wp cpu_wp_auto[] = { + { + .pll_rate = 532000000, + .cpu_rate = 532000000, + .pdf = 0, + .mfi = 5, + .mfd = 23, + .mfn = 13, + .cpu_voltage = 1050000,}, + { + .pll_rate = 400000000, + .cpu_rate = 400000000, + .pdf = 1, + .mfi = 8, + .mfd = 2, + .mfn = 1, + .cpu_voltage = 950000,}, + { + .pll_rate = 200000000, + .cpu_rate = 200000000, + .pdf = 3, + .mfi = 8, + .mfd = 2, + .mfn = 1, + .cpu_voltage = 850000,}, + { + .pll_rate = 600000000, + .cpu_rate = 600000000, + .pdf = 0, + .mfi = 6, + .mfd = 3, + .mfn = 1, + .cpu_voltage = 1200000,}, +}; + +struct cpu_wp *get_cpu_wp(int *wp) +{ + *wp = 3; + return cpu_wp_auto; +} + +#if defined(CONFIG_REGULATOR_MC13892) \ + || defined(CONFIG_REGULATOR_MC13892_MODULE) +static int mc13892_reg_int(void) +{ + int i = 0; + unsigned int value; + struct regulator *regulator; + struct cpu_wp *cpu_wp_tbl1; + int cpu_wp_nr1; + char *reg_name[] = { + "SW1", + "SW2", + "SW3", + "SW4", + "SW1_STBY", + "SW2_STBY", + "SW3_STBY", + "SW4_STBY", + "SW1_DVS", + "SW2_DVS", + "SWBST", + "VIOHI", + "VPLL", + "VDIG", + "VSD", + "VUSB2", + "VVIDEO", + "VAUDIO", + "VCAM", + "VGEN1", + "VGEN2", + "VGEN3", + "USB", + "GPO1", + "GPO2", + "GPO3", + "GPO4", + }; + + /* for board v1.1 do nothing */ + if (!board_is_rev(BOARD_REV_2)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(reg_name); i++) { + regulator = regulator_get(NULL, reg_name[i]); + if (regulator != ERR_PTR(-ENOENT)) { + regulator_enable(regulator); + regulator_put(regulator); + } + } + for (i = 0; i < ARRAY_SIZE(reg_name); i++) { + if ((strcmp(reg_name[i], "VIOHI") == 0) || + (strcmp(reg_name[i], "VPLL") == 0) || + (strcmp(reg_name[i], "VDIG") == 0) || + (strcmp(reg_name[i], "VGEN2") == 0)) + continue; + regulator = regulator_get(NULL, reg_name[i]); + if (regulator != ERR_PTR(-ENOENT)) { + regulator_disable(regulator); + regulator_put(regulator); + } + } + + /* Set the current working point. */ + cpu_wp_tbl1 = get_cpu_wp(&cpu_wp_nr1); + for (i = 0; i < cpu_wp_nr1; i++) + cpu_wp_tbl1[i].cpu_voltage += 50000; + + /* Bit 4 DRM: keep VSRTC and CLK32KMCU on for all states */ + pmic_read_reg(REG_POWER_CTL0, &value, 0xffffff); + value |= 0x000010; + pmic_write_reg(REG_POWER_CTL0, value, 0xffffff); + + return 0; +} + +late_initcall(mc13892_reg_int); +#endif + +static void mxc_nop_release(struct device *dev) +{ + /* Nothing */ +} + +/* MTD NAND flash */ +#if defined(CONFIG_MTD_NAND_MXC) || defined(CONFIG_MTD_NAND_MXC_MODULE) \ + || defined(CONFIG_MTD_NAND_MXC_V2) || defined(CONFIG_MTD_NAND_MXC_V2_MODULE) \ + || defined(CONFIG_MTD_NAND_MXC_V3) + +static struct mtd_partition mxc_nand_partitions[] = { + { + .name = "nand.bootloader", + .offset = 0, + .size = 2 * 1024 * 1024}, + { + .name = "nand.kernel", + .offset = MTDPART_OFS_APPEND, + .size = 4 * 1024 * 1024}, + { + .name = "nand.rootfs", + .offset = MTDPART_OFS_APPEND, + .size = 256 * 1024 * 1024}, + { + .name = "nand.userfs1", + .offset = MTDPART_OFS_APPEND, + .size = 256 * 1024 * 1024}, + { + .name = "nand.userfs2", + .offset = MTDPART_OFS_APPEND, + .size = 512 * 1024 * 1024}, + { + .name = "nand.userfs3", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL}, +}; + +static struct flash_platform_data mxc_nand_data = { + .parts = mxc_nand_partitions, + .nr_parts = ARRAY_SIZE(mxc_nand_partitions), + .width = 1, +}; + +static struct platform_device mxc_nandv2_mtd_device = { + .name = "mxc_nandv2_flash", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_nand_data, + }, +}; + +static void mxc_init_nand_mtd(void) +{ +// if (__raw_readl(MXC_CCM_RCSR) & MXC_CCM_RCSR_NF16B) { +// mxc_nand_data.width = 2; +// } + (void)platform_device_register(&mxc_nandv2_mtd_device); +} +#else +static inline void mxc_init_nand_mtd(void) +{ +} +#endif + +static void lcd_reset(void) +{ + static int first; + + /* ensure that LCDIO(1.8V) has been turn on */ + /* active reset line GPIO */ + if (!first) { + mxc_request_iomux(MX37_PIN_GPIO1_5, IOMUX_CONFIG_GPIO); + gpio_request(IOMUX_TO_GPIO(MX37_PIN_GPIO1_5), "gpio1_5"); + first = 1; + } + gpio_set_value(IOMUX_TO_GPIO(MX37_PIN_GPIO1_5), 0); + gpio_direction_output(IOMUX_TO_GPIO(MX37_PIN_GPIO1_5), 0); + /* do reset */ + msleep(10); /* tRES >= 100us */ + gpio_set_value(IOMUX_TO_GPIO(MX37_PIN_GPIO1_5), 1); + msleep(60); +} + +static struct mxc_lcd_platform_data lcd_data = { + .core_reg = "VVIDEO", + .io_reg = "SW4", + .reset = lcd_reset, +}; + +#if defined(CONFIG_KEYBOARD_MPR084) || defined(CONFIG_KEYBOARD_MPR084_MODULE) +/*! + * These functions are used to configure and the GPIO pins for keypad to + * activate and deactivate it. + */ +extern void gpio_keypad_active(void); + +extern void gpio_keypad_inactive(void); + +static u16 keymap[] = { + KEY_DOWN, KEY_LEFT, KEY_ENTER, + KEY_RIGHT, KEY_UP, KEY_LEFTALT, + KEY_TAB, KEY_ESC, +}; + +static struct mxc_keyp_platform_data keypad_data = { + .matrix = keymap, + .active = gpio_keypad_active, + .inactive = gpio_keypad_inactive, + .vdd_reg = "VGEN2", +}; +#else + +static struct mxc_keyp_platform_data keypad_data = {}; + +#endif + +static struct mxc_lightsensor_platform_data ls_data = { + .vdd_reg = "VGEN2", + .rext = 100, +}; + +#if defined(CONFIG_TOUCHSCREEN_TSC2007) || defined(CONFIG_TOUCHSCREEN_TSC2007_MODULE) +static int tsc2007_get_pendown_state(void) +{ + return !gpio_get_value(IOMUX_TO_GPIO(MX37_PIN_AUD5_RXFS)); +} + +static int tsc2007_init(void) +{ + int pad_val; + + mxc_request_iomux(MX37_PIN_AUD5_RXFS, IOMUX_CONFIG_GPIO); + pad_val = PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PU; + mxc_iomux_set_pad(MX37_PIN_AUD5_RXFS, pad_val); + gpio_request(IOMUX_TO_GPIO(MX37_PIN_AUD5_RXFS), "aud5_rxfs"); + gpio_direction_input(IOMUX_TO_GPIO(MX37_PIN_AUD5_RXFS)); + return 0; +} + +static void tsc2007_exit(void) +{ +} + +struct tsc2007_platform_data tsc2007_data = { + .model = 2007, + .x_plate_ohms = 400, + .get_pendown_state = tsc2007_get_pendown_state, + .init_platform_hw = tsc2007_init, + .exit_platform_hw = tsc2007_exit, +}; +#else +struct tsc2007_platform_data tsc2007_data; +#endif + +static struct i2c_board_info mxc_i2c0_board_info[] __initdata = { + { + .type = "tsc2007", + .addr = 0x48, + .irq = IOMUX_TO_IRQ(MX37_PIN_AUD5_RXFS), + .platform_data = &tsc2007_data, + }, + { + .type = "mpr084", + .addr = 0x5D, + .platform_data = &keypad_data, + .irq = IOMUX_TO_IRQ(MX37_PIN_GPIO1_3), + }, + { + .type = "isl29003", + .addr = 0x44, + .platform_data = &ls_data, + }, +}; + +static struct i2c_board_info mxc_i2c1_board_info[] __initdata = { + { + .type = "mc13892", + .addr = 0x08, + .platform_data = (void *)MX37_PIN_OWIRE_LINE, + }, + { + .type = "sgtl5000-i2c", + .addr = 0x0a, + }, +}; + +static struct spi_board_info mxc_spi_board_info[] __initdata = { + { + .modalias = "cpld_spi", + .max_speed_hz = 27000000, + .bus_num = 2, + .chip_select = 0, + }, + { + .modalias = "lcd_spi", + .max_speed_hz = 5000000, + .bus_num = 2, + .platform_data = &lcd_data, + .chip_select = 1,}, +}; + +#if defined(CONFIG_FB_MXC_SYNC_PANEL) || defined(CONFIG_FB_MXC_SYNC_PANEL_MODULE) +static struct platform_device mxc_fb_device[] = { + { + .name = "mxc_sdc_fb", + .id = 0, + .dev = { + .release = mxc_nop_release, + .coherent_dma_mask = 0xFFFFFFFF, + }, + }, + { + .name = "mxc_sdc_fb", + .id = 1, + .dev = { + .release = mxc_nop_release, + .coherent_dma_mask = 0xFFFFFFFF, + }, + }, + { + .name = "mxc_sdc_fb", + .id = 2, + .dev = { + .release = mxc_nop_release, + .coherent_dma_mask = 0xFFFFFFFF, + }, + }, +}; + +static void mxc_init_fb(void) +{ + (void)platform_device_register(&mxc_fb_device[0]); + (void)platform_device_register(&mxc_fb_device[1]); + (void)platform_device_register(&mxc_fb_device[2]); + gpio_lcd_active(); +} +#else +static inline void mxc_init_fb(void) +{ +} +#endif + +static struct platform_device mxcbl_device = { + .name = "mxc_mc13892_bl", +}; + +static inline void mxc_init_bl(void) +{ + platform_device_register(&mxcbl_device); +} + +/*lan9217 device*/ +#if defined(CONFIG_SMSC911X) || defined(CONFIG_SMSC911X_MODULE) +static struct resource smsc911x_resources[] = { + { + .start = LAN9217_BASE_ADDR, + .end = LAN9217_BASE_ADDR + 255, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_BOARD_IRQ_START, + .flags = IORESOURCE_IRQ, + }, +}; + +struct smsc911x_platform_config smsc911x_config = { + .irq_polarity = SMSC911X_IRQ_POLARITY_ACTIVE_LOW, + .flags = 0x8000 | SMSC911X_USE_16BIT | SMSC911X_FORCE_INTERNAL_PHY, +}; + +static struct platform_device smsc_lan9217_device = { + .name = "smsc911x", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &smsc911x_config, + }, + .num_resources = ARRAY_SIZE(smsc911x_resources), + .resource = smsc911x_resources, +}; + +static int __init mxc_init_enet(void) +{ + (void)platform_device_register(&smsc_lan9217_device); + return 0; +} +#else +static int __init mxc_init_enet(void) +{ + return 0; +} +#endif + +late_initcall(mxc_init_enet); + +#if defined(CONFIG_PATA_FSL) || defined(CONFIG_PATA_FSL_MODULE) +extern void gpio_ata_active(void); +extern void gpio_ata_inactive(void); + +static int ata_init(struct platform_device *pdev) +{ + /* Configure the pins */ + gpio_ata_active(); + + return 0; +} + +static void ata_exit(void) +{ + /* Free the pins */ + gpio_ata_inactive(); +} + +static struct fsl_ata_platform_data ata_data = { + .udma_mask = ATA_UDMA3, /* board can handle up to UDMA3 */ + .mwdma_mask = ATA_MWDMA2, + .pio_mask = ATA_PIO4, + .fifo_alarm = MXC_IDE_DMA_WATERMARK / 2, + .max_sg = MXC_IDE_DMA_BD_NR, + .init = ata_init, + .exit = ata_exit, + .core_reg = NULL, /*"LDO2", */ + .io_reg = NULL, /*"LDO3", */ +}; + +static struct resource pata_fsl_resources[] = { + [0] = { /* I/O */ + .start = ATA_BASE_ADDR, + .end = ATA_BASE_ADDR + 0x000000C8, + .flags = IORESOURCE_MEM, + }, + [2] = { /* IRQ */ + .start = MXC_INT_ATA, + .end = MXC_INT_ATA, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device pata_fsl_device = { + .name = "pata_fsl", + .id = -1, + .num_resources = ARRAY_SIZE(pata_fsl_resources), + .resource = pata_fsl_resources, + .dev = { + .platform_data = &ata_data, + .coherent_dma_mask = ~0, + }, +}; + +static void __init mxc_init_pata(void) +{ + (void)platform_device_register(&pata_fsl_device); +} +#else /* CONFIG_PATA_FSL */ +static void __init mxc_init_pata(void) +{ +} +#endif /* CONFIG_PATA_FSL */ + +/*! + * Board specific fixup function. It is called by \b setup_arch() in + * setup.c file very early on during kernel starts. It allows the user to + * statically fill in the proper values for the passed-in parameters. None of + * the parameters is used currently. + * + * @param desc pointer to \b struct \b machine_desc + * @param tags pointer to \b struct \b tag + * @param cmdline pointer to the command line + * @param mi pointer to \b struct \b meminfo + */ +static void __init fixup_mxc_board(struct machine_desc *desc, struct tag *tags, + char **cmdline, struct meminfo *mi) +{ + mxc_cpu_init(); + +#ifdef CONFIG_DISCONTIGMEM + do { + int nid; + mi->nr_banks = MXC_NUMNODES; + for (nid = 0; nid < mi->nr_banks; nid++) { + SET_NODE(mi, nid); + } + } while (0); +#endif +} + +static void mxc_unifi_hardreset(int pin_level) +{ + struct regulator *gpo4; + + if (board_is_rev(BOARD_REV_2)) { + gpo4 = regulator_get(NULL, "GPO4"); + if (!IS_ERR(gpo4)) { + if (pin_level & 0x01) + regulator_enable(gpo4); + else + regulator_disable(gpo4); + } + regulator_put(gpo4); + } else { + mxc_request_iomux(MX37_PIN_AUD5_RXC, IOMUX_CONFIG_GPIO); + gpio_request(IOMUX_TO_GPIO(MX37_PIN_AUD5_RXC), "aud5_rxc"); + gpio_set_value(IOMUX_TO_GPIO(MX37_PIN_AUD5_RXC), + pin_level & 0x01); + gpio_direction_output(IOMUX_TO_GPIO(MX37_PIN_AUD5_RXC), 0); + mxc_free_iomux(MX37_PIN_AUD5_RXC, IOMUX_CONFIG_GPIO); + } +} + +static struct mxc_unifi_platform_data unifi_data = { + .hardreset = mxc_unifi_hardreset, + .enable = NULL, + .reg_1v5_ana_bb = "VGEN1", + .reg_vdd_vpa = "VCAM", + .reg_1v5_dd = "VGEN1", + .host_id = 1, +}; + +struct mxc_unifi_platform_data *get_unifi_plat_data(void) +{ + return &unifi_data; +} + +EXPORT_SYMBOL(get_unifi_plat_data); + +#if defined(CONFIG_MMC_IMX_ESDHCI) || defined(CONFIG_MMC_IMX_ESDHCI_MODULE) +static struct mxc_mmc_platform_data mmc1_data = { + .ocr_mask = MMC_VDD_32_33, + .caps = MMC_CAP_4_BIT_DATA, + .min_clk = 150000, + .max_clk = 52000000, + .card_inserted_state = 0, + .status = sdhc_get_card_det_status, + .wp_status = sdhc_write_protect, + .clock_mmc = "esdhc_clk", +}; + +/*! + * Resource definition for the SDHC1 + */ +static struct resource mxcsdhc1_resources[] = { + [0] = { + .start = MMC_SDHC1_BASE_ADDR, + .end = MMC_SDHC1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_MMC_SDHC1, + .end = MXC_INT_MMC_SDHC1, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Device Definition for MXC SDHC1 */ +static struct platform_device mxcsdhc1_device = { + .name = "mxsdhci", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mmc1_data, + }, + .num_resources = ARRAY_SIZE(mxcsdhc1_resources), + .resource = mxcsdhc1_resources, +}; + +static struct mxc_mmc_platform_data mmc2_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30 | + MMC_VDD_31_32, + .caps = MMC_CAP_4_BIT_DATA, + .min_clk = 150000, + .max_clk = 50000000, + .card_inserted_state = 0, + .status = sdhc_get_card_det_status, + .wp_status = sdhc_write_protect, + .clock_mmc = "esdhc_clk", +}; + +static struct resource mxcsdhc2_resources[] = { + [0] = { + .start = MMC_SDHC2_BASE_ADDR, + .end = MMC_SDHC2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_MMC_SDHC2, + .end = MXC_INT_MMC_SDHC2, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxcsdhc2_device = { + .name = "mxsdhci", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mmc2_data, + }, + .num_resources = ARRAY_SIZE(mxcsdhc2_resources), + .resource = mxcsdhc2_resources, +}; + +static inline void mxc_init_mmc(void) +{ + int cd_irq; + + cd_irq = sdhc_init_card_det(0); + if (cd_irq) { + mxcsdhc1_device.resource[2].start = cd_irq; + mxcsdhc1_device.resource[2].end = cd_irq; + } + + spba_take_ownership(SPBA_SDHC1, SPBA_MASTER_A | SPBA_MASTER_C); + (void)platform_device_register(&mxcsdhc1_device); + cd_irq = sdhc_init_card_det(1); + if (cd_irq) { + mxcsdhc2_device.resource[2].start = cd_irq; + mxcsdhc2_device.resource[2].end = cd_irq; + } + spba_take_ownership(SPBA_SDHC2, SPBA_MASTER_A | SPBA_MASTER_C); + (void)platform_device_register(&mxcsdhc2_device); +} +#else +static inline void mxc_init_mmc(void) +{ +} +#endif + +static void bt_reset(void) +{ + struct regulator *gpo4; + if (board_is_rev(BOARD_REV_2)) { + gpo4 = regulator_get(NULL, "GPO4"); + if (!IS_ERR(gpo4)) + regulator_enable(gpo4); + regulator_put(gpo4); + } else { + mxc_request_iomux(MX37_PIN_AUD5_RXC, IOMUX_CONFIG_GPIO); + gpio_request(IOMUX_TO_GPIO(MX37_PIN_AUD5_RXC), "aud5_rxc"); + gpio_set_value(IOMUX_TO_GPIO(MX37_PIN_AUD5_RXC), 1); + gpio_direction_output(IOMUX_TO_GPIO(MX37_PIN_AUD5_RXC), 0); + } +} + +static struct mxc_bt_platform_data mxc_bt_data = { + .bt_vdd = "VGEN2", + .bt_vdd_parent = NULL, + .bt_vusb = "SW4", + .bt_vusb_parent = NULL, + .bt_reset = bt_reset, +}; + +static struct platform_device mxc_bt_device = { + .name = "mxc_bt", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_bt_data, + }, +}; + +static void mxc_init_bluetooth(void) +{ + (void)platform_device_register(&mxc_bt_device); +} + +#if defined(CONFIG_SND_SOC_IMX_3STACK_SGTL5000) \ + || defined(CONFIG_SND_SOC_IMX_3STACK_SGTL5000_MODULE) +static int mxc_sgtl5000_plat_init(void); +static int mxc_sgtl5000_plat_finit(void); +static int mxc_sgtl5000_amp_enable(int enable); + +static struct mxc_audio_platform_data sgtl5000_data = { + .ssi_num = 1, + .src_port = 2, + .ext_port = 5, + .hp_irq = IOMUX_TO_IRQ(MX37_PIN_AUD5_RXFS), + .hp_status = headphone_det_status, + .vddio_reg = "SW3", + .vdda_reg = "VAUDIO", + .amp_enable = mxc_sgtl5000_amp_enable, + .vddio = 1850000, + .vdda = 2775000, + .vddd = 0, + .init = mxc_sgtl5000_plat_init, + .finit = mxc_sgtl5000_plat_finit, +}; + +static struct platform_device mxc_sgtl5000_device = { + .name = "imx-3stack-sgtl5000", + .dev = { + .release = mxc_nop_release, + .platform_data = &sgtl5000_data, + }, +}; + +static int mxc_sgtl5000_plat_init(void) +{ + struct regulator *reg; + reg = regulator_get(&mxc_sgtl5000_device.dev, "GPO2"); + if (IS_ERR(reg)) + return -EINVAL; + sgtl5000_data.priv = reg; + return 0; +} + +static int mxc_sgtl5000_plat_finit(void) +{ + struct regulator *reg; + reg = sgtl5000_data.priv; + if (reg) { + regulator_put(reg); + sgtl5000_data.priv = NULL; + } + return 0; +} + +static int mxc_sgtl5000_amp_enable(int enable) +{ + struct regulator *reg; + reg = sgtl5000_data.priv; + + if (!reg) + return -EINVAL; + if (enable) + regulator_enable(reg); + else + regulator_disable(reg); + return 0; +} + +static void mxc_init_sgtl5000(void) +{ + int err, pin; + struct clk *cko1, *parent; + unsigned long rate; + + /* for board v1.1 do nothing */ + if (!board_is_rev(BOARD_REV_2)) + return; + + pin = MX37_PIN_AUD5_RXFS; + err = mxc_request_iomux(pin, IOMUX_CONFIG_GPIO); + if (err) { + sgtl5000_data.hp_irq = -1; + printk(KERN_ERR "Error: sgtl5000_init request gpio failed!\n"); + return; + } + mxc_iomux_set_pad(pin, PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PU); + gpio_request(IOMUX_TO_GPIO(pin), "aud5_rxfs"); + gpio_direction_input(IOMUX_TO_GPIO(pin)); + + /* cko1 clock */ + mxc_request_iomux(MX37_PIN_GPIO1_6, IOMUX_CONFIG_ALT2); + + cko1 = clk_get(NULL, "cko1_clk"); + if (IS_ERR(cko1)) + return; + parent = clk_get(NULL, "ipg_perclk"); + if (IS_ERR(parent)) + return; + clk_set_parent(cko1, parent); + rate = clk_round_rate(cko1, 13000000); + if (rate < 8000000 || rate > 27000000) { + printk(KERN_ERR "Error: SGTL5000 mclk freq %d out of range!\n", + rate); + clk_put(parent); + clk_put(cko1); + return; + } + clk_set_rate(cko1, rate); + clk_enable(cko1); + sgtl5000_data.sysclk = rate; + platform_device_register(&mxc_sgtl5000_device); +} +#else +static inline void mxc_init_sgtl5000(void) +{ +} +#endif + +/*! + * fixup for mx37 3stack board v1.1(wm8350) + */ +static void mx37_3stack_fixup_for_board_v1(void) +{ + dptc_gp_data.reg_id = "DCDC1"; + dptc_lp_data.reg_id = "DCDC4"; + gp_reg_id = "DCDC1"; + lp_reg_id = "DCDC4"; + tve_data.dac_reg = "LDO2"; + tve_data.dig_reg = "LDO3"; + lcd_data.core_reg = "LDO1"; + lcd_data.io_reg = "DCDC6"; + dvfs_core_data.reg_id = "DCDC1"; + ls_data.vdd_reg = "DCDC3"; + mxc_bt_data.bt_vdd = "DCDC3"; + mxc_bt_data.bt_vusb = "DCDC6"; + + unifi_data.reg_1v5_ana_bb = NULL; /* VMAIN is used on v1 board */ + unifi_data.reg_vdd_vpa = NULL; + unifi_data.reg_1v5_dd = NULL; +#if defined(CONFIG_KEYBOARD_MPR084) || defined(CONFIG_KEYBOARD_MPR084_MODULE) + keypad_data.vdd_reg = "DCDC3"; +#endif +} + +#if defined(CONFIG_GPS_IOCTRL) || defined(CONFIG_GPS_IOCTRL_MODULE) +static struct mxc_gps_platform_data gps_data = { + .core_reg = "VIOHI", + .analog_reg = "SW3", +}; + +static struct platform_device mxc_gps_device = { + .name = "gps_ioctrl", + .id = 0, + .dev = { + .platform_data = &gps_data, + }, +}; + +static void __init mxc_init_gps(void) +{ + (void)platform_device_register(&mxc_gps_device); +} +#else +static void __init mxc_init_gps(void) +{ +} +#endif + +/*! + * Board specific initialization. + */ +static void __init mxc_board_init(void) +{ + mxc_cpu_common_init(); + mxc_register_gpios(); + early_console_setup(saved_command_line); + mxc_init_devices(); + if (!board_is_rev(BOARD_REV_2)) + mx37_3stack_fixup_for_board_v1(); + i2c_register_board_info(0, mxc_i2c0_board_info, + ARRAY_SIZE(mxc_i2c0_board_info)); + i2c_register_board_info(1, mxc_i2c1_board_info, + ARRAY_SIZE(mxc_i2c1_board_info)); + + spi_register_board_info(mxc_spi_board_info, + ARRAY_SIZE(mxc_spi_board_info)); + mxc_init_nand_mtd(); + mxc_init_mmc(); + mxc_init_pata(); + mxc_init_fb(); + mxc_init_bl(); + mxc_init_bluetooth(); + mxc_init_gps(); + mxc_init_sgtl5000(); +} + +static void __init mx37_3stack_timer_init(void) +{ + mx37_clocks_init(32768, 24000000, 22579200, 0); +} + +static struct sys_timer mxc_timer = { + .init = mx37_3stack_timer_init, +}; + +/* + * The following uses standard kernel macros define in arch.h in order to + * initialize __mach_desc_MX37_3STACK data structure. + */ +/* *INDENT-OFF* */ +MACHINE_START(MX37_3DS, "Freescale MX37 3-Stack Board") + /* Maintainer: Freescale Semiconductor, Inc. */ + .phys_io = AIPS1_BASE_ADDR, + .io_pg_offst = ((AIPS1_BASE_ADDR_VIRT) >> 18) & 0xfffc, + .boot_params = PHYS_OFFSET + 0x100, + .fixup = fixup_mxc_board, + .map_io = mx37_map_io, + .init_irq = mxc_init_irq, + .init_machine = mxc_board_init, + .timer = &mxc_timer, +MACHINE_END diff --git a/arch/arm/mach-mx37/mx37_3stack_cpld.c b/arch/arm/mach-mx37/mx37_3stack_cpld.c new file mode 100644 index 000000000000..ad3ab6fb7a34 --- /dev/null +++ b/arch/arm/mach-mx37/mx37_3stack_cpld.c @@ -0,0 +1,233 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <mach/hardware.h> +#include <asm/mach/irq.h> +#include <mach/gpio.h> +#include "board-mx37_3stack.h" +#include "iomux.h" + +/*! + * @file mach-mx37/mx37_3stack_cpld.c + * + * @brief This file contains the board specific initialization routines. + * + * @ingroup MSL_MX37 + */ + +extern int mxc_spi_poll_transfer(struct spi_device *spi, + struct spi_transfer *t); +static int __init mxc_expio_init(void); + +struct spi_device *cpld_spi; + +/*! + * This function is used to tranfer data to CPLD regs over CSPI + */ +static inline int mx37_3ds_cpld_rw(u8 * buf, size_t len) +{ + struct spi_transfer t = { + .tx_buf = (const void *)buf, + .rx_buf = buf, + .len = len, + .cs_change = 0, + .delay_usecs = 0, + }; + mxc_spi_poll_transfer(cpld_spi, &t); + return 0; +} + +/*! + * This function is called to read a CPLD register over CSPI. + * + * @param offset number of the cpld register to be read + * + * @return Returns 0 on success -1 on failure. + */ +unsigned int spi_cpld_read(unsigned int offset) +{ + unsigned int frame[2]; + unsigned int reg_num = offset >> 1; + unsigned int data = 0; + + frame[0] = (1 << 13) | ((reg_num & 0x0001FFFF) >> 5) | 0x00001000; + frame[1] = (((reg_num & 0x0000001F) << 27) | 0x0200001f); + mx37_3ds_cpld_rw((u8 *) frame, 2); + data = (frame[1] >> 6) & 0xFFFF; + + reg_num = (offset + 2) >> 1; + frame[0] = (1 << 13) | ((reg_num & 0x0001FFFF) >> 5) | 0x00001000; + frame[1] = (((reg_num & 0x0000001F) << 27) | 0x0200001f); + mx37_3ds_cpld_rw((u8 *) frame, 2); + + data |= (((frame[1] >> 6) & 0xFFFF) << 16); + return data; +} +EXPORT_SYMBOL(spi_cpld_read); + +/*! + * This function is called to write to a CPLD register over CSPI. + * + * @param offset number of the cpld register to be written + * @param reg_val value to be written + * + * @return Returns 0 on success -1 on failure. + */ +unsigned int spi_cpld_write(unsigned int offset, unsigned int reg_val) +{ + unsigned int frame[2] = { 0, 0 }; + unsigned int reg_num = offset >> 1; + unsigned int data = reg_val; + + frame[0] = ((reg_num & 0x0001FFFF) >> 5) | 0x00001000; + frame[1] = (((reg_num & 0x0000001F) << 27) | + ((data & 0x0000FFFF) << 6) | 0x03C00027); + mx37_3ds_cpld_rw((u8 *) frame, 2); + + reg_num = (offset + 2) >> 1; + data = reg_val >> 16; + frame[0] = 0; + frame[1] = 0; + frame[0] = ((reg_num & 0x0001FFFF) >> 5) | 0x00001000; + frame[1] = (((reg_num & 0x0000001F) << 27) | + ((data & 0x0000FFFF) << 6) | 0x03C00027); + + mx37_3ds_cpld_rw((u8 *) frame, 2); + + return 0; +} +EXPORT_SYMBOL(spi_cpld_write); + +static int __init mx37_3ds_cpld_probe(struct spi_device *spi) +{ + unsigned int i = 0; + + spi->bits_per_word = 46; + cpld_spi = spi; + + spi_setup(spi); + i = spi_cpld_read(CPLD_CODE_VER_REG); + pr_info("3-Stack Debug board detected, rev = 0x%04X\n", i); + spi_cpld_write(LED_SWITCH_REG, 0xFF); + + /* disable the interrupt and clear the status */ + spi_cpld_write(INTR_MASK_REG, 0); + spi_cpld_write(INTR_RESET_REG, 0xFFFF); + spi_cpld_write(INTR_RESET_REG, 0); + spi_cpld_write(INTR_MASK_REG, 0x1E); + + mxc_expio_init(); + return 0; +} + +/*! + * This structure contains pointers to the CPLD callback functions. + */ +static struct spi_driver mx37_3ds_cpld_driver = { + .driver = { + .name = "cpld_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = mx37_3ds_cpld_probe, +}; + +static int __init mx37_3ds_cpld_init(void) +{ + pr_debug("Registering the CPLD Driver\n"); + return spi_register_driver(&mx37_3ds_cpld_driver); +} + +device_initcall(mx37_3ds_cpld_init); + +static void mxc_expio_irq_handler(u32 irq, struct irq_desc *desc) +{ + u32 expio_irq; + struct irq_desc *d; + + desc->chip->mask(irq); /* irq = gpio irq number */ + + expio_irq = MXC_BOARD_IRQ_START; + + d = irq_desc + expio_irq; + if (unlikely(!(d->handle_irq))) { + printk(KERN_ERR "\nEXPIO irq: %d unhandled\n", expio_irq); + BUG(); /* oops */ + } + d->handle_irq(expio_irq, d); + + desc->chip->ack(irq); + desc->chip->unmask(irq); +} + +/* + * Disable an expio pin's interrupt by setting the bit in the imr. + * @param irq an expio virtual irq number + */ +static void expio_mask_irq(u32 irq) +{ +} + +/* + * Acknowledge an expanded io pin's interrupt by clearing the bit in the isr. + * @param irq an expanded io virtual irq number + */ +static void expio_ack_irq(u32 irq) +{ + /* clear the interrupt status */ + spi_cpld_write(INTR_RESET_REG, 1); + spi_cpld_write(INTR_RESET_REG, 0); +} + +/* + * Enable a expio pin's interrupt by clearing the bit in the imr. + * @param irq a expio virtual irq number + */ +static void expio_unmask_irq(u32 irq) +{ +} + +static struct irq_chip expio_irq_chip = { + .ack = expio_ack_irq, + .mask = expio_mask_irq, + .unmask = expio_unmask_irq, +}; + +static int __init mxc_expio_init(void) +{ + int i; + int pad_val; + + /* + * Configure INT line as GPIO input + */ + mxc_request_iomux(MX37_PIN_GPIO1_2, IOMUX_CONFIG_GPIO); + pad_val = mxc_iomux_get_pad(MX37_PIN_GPIO1_2); + pad_val |= PAD_CTL_PUE_PULL; + mxc_iomux_set_pad(MX37_PIN_GPIO1_2, pad_val); + gpio_direction_input(IOMUX_TO_GPIO(MX37_PIN_GPIO1_2)); + + for (i = MXC_BOARD_IRQ_START; i < (MXC_BOARD_IRQ_START + MXC_BOARD_IRQS); + i++) { + set_irq_chip(i, &expio_irq_chip); + set_irq_handler(i, handle_level_irq); + set_irq_flags(i, IRQF_VALID); + } + set_irq_type(IOMUX_TO_IRQ(MX37_PIN_GPIO1_2), IRQF_TRIGGER_LOW); + set_irq_chained_handler(IOMUX_TO_IRQ(MX37_PIN_GPIO1_2), + mxc_expio_irq_handler); + return 0; +} + diff --git a/arch/arm/mach-mx37/mx37_3stack_gpio.c b/arch/arm/mach-mx37/mx37_3stack_gpio.c new file mode 100644 index 000000000000..8b4e073e1384 --- /dev/null +++ b/arch/arm/mach-mx37/mx37_3stack_gpio.c @@ -0,0 +1,1029 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <mach/hardware.h> +#include <mach/clock.h> +#include <mach/gpio.h> +#include <mach/irqs.h> + +#include "iomux.h" + +/*! + * @file mx37_3stack_gpio.c + * + * @brief This file contains all the GPIO setup functions for the board. + * + * @ingroup GPIO + */ + +void gpio_activate_audio_ports(void); + +/*! + * Setup GPIO for a UART port to be active + * + * @param port a UART port + * @param no_irda indicates if the port is used for SIR + */ +void gpio_uart_active(int port, int no_irda) +{ + /* + * Configure the IOMUX control registers for the UART signals + * and enable the UART transceivers + */ + switch (port) { + /* UART 1 IOMUX Configs */ + case 0: + mxc_request_iomux(MX37_PIN_UART1_RXD, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX37_PIN_UART1_RXD, PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST); + mxc_iomux_set_input(MUX_IN_UART1_UART_RXD_MUX, INPUT_CTL_PATH4); + mxc_request_iomux(MX37_PIN_UART1_TXD, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX37_PIN_UART1_TXD, PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST); + mxc_request_iomux(MX37_PIN_UART1_RTS, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX37_PIN_UART1_RTS, PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH); + mxc_iomux_set_input(MUX_IN_UART1_UART_RTS_B, INPUT_CTL_PATH4); + mxc_request_iomux(MX37_PIN_UART1_CTS, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX37_PIN_UART1_CTS, PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH); + break; + case 1: + mxc_request_iomux(MX37_PIN_UART1_DCD, IOMUX_CONFIG_ALT3); + mxc_iomux_set_pad(MX37_PIN_UART1_DCD, PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST); + mxc_iomux_set_input(MUX_IN_UART2_UART_RXD_MUX, INPUT_CTL_PATH1); + mxc_request_iomux(MX37_PIN_UART1_RI, IOMUX_CONFIG_ALT3); + mxc_iomux_set_pad(MX37_PIN_UART1_RI, PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST); + mxc_request_iomux(MX37_PIN_UART1_DSR, IOMUX_CONFIG_ALT3); + mxc_iomux_set_pad(MX37_PIN_UART1_DSR, PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH); + mxc_iomux_set_input(MUX_IN_UART2_UART_RTS_B, INPUT_CTL_PATH1); + mxc_request_iomux(MX37_PIN_UART1_DTR, IOMUX_CONFIG_ALT3); + mxc_iomux_set_pad(MX37_PIN_UART1_DTR, PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH); + break; + case 2: + mxc_request_iomux(MX37_PIN_AUD3_BB_TXD, IOMUX_CONFIG_ALT3); + mxc_iomux_set_pad(MX37_PIN_AUD3_BB_TXD, PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST); + mxc_iomux_set_input(MUX_IN_UART3_UART_RXD_MUX, INPUT_CTL_PATH0); + mxc_request_iomux(MX37_PIN_AUD3_BB_RXD, IOMUX_CONFIG_ALT3); + mxc_iomux_set_pad(MX37_PIN_AUD3_BB_RXD, PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST); + mxc_request_iomux(MX37_PIN_AUD3_BB_CK, IOMUX_CONFIG_ALT3); + mxc_iomux_set_pad(MX37_PIN_AUD3_BB_CK, PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH); + mxc_iomux_set_input(MUX_IN_UART3_UART_RTS_B, INPUT_CTL_PATH0); + mxc_request_iomux(MX37_PIN_AUD3_BB_FS, IOMUX_CONFIG_ALT3); + mxc_iomux_set_pad(MX37_PIN_AUD3_BB_FS, PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH); + break; + default: + break; + } +} + +/*! + * Setup GPIO for a UART port to be inactive + * + * @param port a UART port + * @param no_irda indicates if the port is used for SIR + */ +void gpio_uart_inactive(int port, int no_irda) +{ + /* + * Configure the IOMUX control registers for the UART signals + * and disable the UART transceivers + */ + switch (port) { + case 0: + mxc_request_iomux(MX37_PIN_UART1_RXD, IOMUX_CONFIG_GPIO); + mxc_request_iomux(MX37_PIN_UART1_TXD, IOMUX_CONFIG_GPIO); + mxc_request_iomux(MX37_PIN_UART1_RTS, IOMUX_CONFIG_GPIO); + mxc_request_iomux(MX37_PIN_UART1_CTS, IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX37_PIN_UART1_RXD, IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX37_PIN_UART1_TXD, IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX37_PIN_UART1_RTS, IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX37_PIN_UART1_CTS, IOMUX_CONFIG_GPIO); + break; + case 1: + mxc_request_iomux(MX37_PIN_UART1_DCD, IOMUX_CONFIG_GPIO); + mxc_request_iomux(MX37_PIN_UART1_RI, IOMUX_CONFIG_GPIO); + mxc_request_iomux(MX37_PIN_UART1_DSR, IOMUX_CONFIG_GPIO); + mxc_request_iomux(MX37_PIN_UART1_DTR, IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX37_PIN_UART1_DCD, IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX37_PIN_UART1_RI, IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX37_PIN_UART1_DSR, IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX37_PIN_UART1_DTR, IOMUX_CONFIG_GPIO); + break; + /* UART 3 IOMUX Configs */ + case 2: + mxc_request_iomux(MX37_PIN_AUD3_BB_TXD, IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX37_PIN_AUD3_BB_TXD, IOMUX_CONFIG_GPIO); + mxc_request_iomux(MX37_PIN_AUD3_BB_RXD, IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX37_PIN_AUD3_BB_RXD, IOMUX_CONFIG_GPIO); + mxc_request_iomux(MX37_PIN_AUD3_BB_CK, IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX37_PIN_AUD3_BB_CK, IOMUX_CONFIG_GPIO); + mxc_request_iomux(MX37_PIN_AUD3_BB_FS, IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX37_PIN_AUD3_BB_FS, IOMUX_CONFIG_GPIO); + break; + default: + break; + } +} + +/*! + * Configure the IOMUX GPR register to receive shared SDMA UART events + * + * @param port a UART port + */ +void config_uartdma_event(int port) +{ + +} + +EXPORT_SYMBOL(gpio_uart_active); +EXPORT_SYMBOL(gpio_uart_inactive); +EXPORT_SYMBOL(config_uartdma_event); + +/*! + * Setup GPIO for a CSPI device to be active + * + * @param cspi_mod an CSPI device + */ +void gpio_spi_active(int cspi_mod) +{ + switch (cspi_mod) { + case 0: + break; + case 1: + /* SPI2 */ + mxc_request_iomux(MX37_PIN_CSPI2_MISO, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX37_PIN_CSPI2_MISO, PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX37_PIN_GRP_H9, PAD_CTL_HYS_ENABLE); + + mxc_request_iomux(MX37_PIN_CSPI2_MOSI, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX37_PIN_CSPI2_MOSI, PAD_CTL_SRE_FAST); + + mxc_request_iomux(MX37_PIN_UART1_CTS, IOMUX_CONFIG_ALT3); + mxc_iomux_set_pad(MX37_PIN_UART1_CTS, PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE); + + mxc_request_iomux(MX37_PIN_CSPI2_SCLK, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX37_PIN_CSPI2_SCLK, PAD_CTL_HYS_ENABLE | + PAD_CTL_SRE_FAST); + + mxc_request_iomux(MX37_PIN_CSPI2_SS1, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX37_PIN_CSPI2_SS1, PAD_CTL_SRE_FAST); + + mxc_request_iomux(MX37_PIN_CSPI2_SS0, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX37_PIN_CSPI2_SS0, PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX37_PIN_GRP_H10, PAD_CTL_HYS_ENABLE); + break; + case 2: + break; + default: + break; + } +} + +/*! + * Setup GPIO for a CSPI device to be inactive + * + * @param cspi_mod a CSPI device + */ +void gpio_spi_inactive(int cspi_mod) +{ +} + +/*! + * Setup 1-Wire to be active + */ +void gpio_owire_active(void) +{ + /*TODO*/} + +/*! + * Setup 1-Wire to be active + */ +void gpio_owire_inactive(void) +{ + /*TODO*/} + +EXPORT_SYMBOL(gpio_owire_active); +EXPORT_SYMBOL(gpio_owire_inactive); + +/*! + * Setup GPIO for an I2C device to be active + * + * @param i2c_num an I2C device + */ +void gpio_i2c_active(int i2c_num) +{ + iomux_pad_config_t regval = 0; + + switch (i2c_num) { + case 0: + /* Touch */ + /* select I2C1_SCK as daisy chain input */ + mxc_request_iomux(MX37_PIN_I2C1_CLK, IOMUX_CONFIG_ALT0); + mxc_iomux_set_input(MUX_IN_I2C1_SCL, INPUT_CTL_PATH1); + /* OpenDrain enabled, 100k PU enabled */ + regval = + PAD_CTL_ODE_OPENDRAIN_ENABLE | PAD_CTL_100K_PU | + PAD_CTL_PKE_ENABLE; + mxc_iomux_set_pad(MX37_PIN_I2C1_CLK, regval); + + /*select I2C1_SDA as daisy chain input */ + mxc_request_iomux(MX37_PIN_I2C1_DAT, IOMUX_CONFIG_ALT0); + mxc_iomux_set_input(MUX_IN_I2C1_SDA, INPUT_CTL_PATH1); + mxc_iomux_set_pad(MX37_PIN_I2C1_DAT, regval); + mxc_iomux_set_pad(MX37_PIN_GRP_H3, PAD_CTL_HYS_ENABLE); + break; + case 1: + /* PMIC */ + /*select I2C2_SCL as daisy chain input */ + mxc_iomux_set_input(MUX_IN_I2C2_SCL, INPUT_CTL_PATH1); + regval = PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PULL | PAD_CTL_100K_PU | + PAD_CTL_ODE_OPENDRAIN_ENABLE | PAD_CTL_DRV_HIGH; + mxc_iomux_set_pad(MX37_PIN_GPIO1_0, regval); + mxc_request_iomux(MX37_PIN_GPIO1_0, + (IOMUX_CONFIG_SION | IOMUX_CONFIG_ALT2)); + + /*select I2C2_SDA as daisy chain input */ + mxc_iomux_set_input(MUX_IN_I2C2_SDA, INPUT_CTL_PATH1); + mxc_iomux_set_pad(MX37_PIN_GPIO1_1, regval); + mxc_request_iomux(MX37_PIN_GPIO1_1, + (IOMUX_CONFIG_SION | IOMUX_CONFIG_ALT2)); + break; + case 2: + break; + default: + break; + } +} + +/*! + * Setup GPIO for an I2C device to be inactive + * + * @param i2c_num an I2C device + */ +void gpio_i2c_inactive(int i2c_num) +{ + /*TODO*/} + +/*! + * This function activates DAM ports 4 & 5 to enable + * audio I/O. + */ +void gpio_activate_audio_ports(void) +{ + unsigned int pad_val; + + /* AUD4_TXD */ + mxc_request_iomux(MX37_PIN_DISP1_DAT20, IOMUX_CONFIG_ALT5); + /* AUD4_RXD */ + mxc_request_iomux(MX37_PIN_DISP1_DAT21, IOMUX_CONFIG_ALT5); + /* AUD4_TXC */ + mxc_request_iomux(MX37_PIN_DISP1_DAT22, IOMUX_CONFIG_ALT5); + /* AUD4_TXFS */ + mxc_request_iomux(MX37_PIN_DISP1_DAT23, IOMUX_CONFIG_ALT5); + + pad_val = PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST; + mxc_iomux_set_pad(MX37_PIN_AUD5_WB_CK, PAD_CTL_100K_PU | pad_val); + mxc_request_iomux(MX37_PIN_AUD5_WB_CK, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX37_PIN_AUD5_WB_RXD, pad_val); + mxc_request_iomux(MX37_PIN_AUD5_WB_RXD, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX37_PIN_AUD5_WB_TXD, pad_val); + mxc_request_iomux(MX37_PIN_AUD5_WB_TXD, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX37_PIN_AUD5_WB_FS, PAD_CTL_100K_PU | pad_val); + mxc_request_iomux(MX37_PIN_AUD5_WB_FS, IOMUX_CONFIG_ALT0); + + /* Enable hysteresis for AUD5_WB_CK, AUD5_WB_RXD, AUD5_WB_TXD, AUD5_WB_FS */ + mxc_iomux_set_pad(MX37_PIN_GRP_H5, PAD_CTL_HYS_ENABLE); +} + +EXPORT_SYMBOL(gpio_activate_audio_ports); + +/*! + * Setup GPIO for SDHC to be active + * + * @param module SDHC module number + */ +void gpio_sdhc_active(int module) +{ + switch (module) { + case 0: + mxc_request_iomux(MX37_PIN_SD1_CLK, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_request_iomux(MX37_PIN_SD1_CMD, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_request_iomux(MX37_PIN_SD1_DATA0, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_request_iomux(MX37_PIN_SD1_DATA1, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_request_iomux(MX37_PIN_SD1_DATA2, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_request_iomux(MX37_PIN_SD1_DATA3, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + + mxc_iomux_set_pad(MX37_PIN_SD1_CMD, + PAD_CTL_DRV_MAX | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_47K_PU | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX37_PIN_SD1_CLK, + PAD_CTL_DRV_MAX | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_PUE_PULL | + PAD_CTL_47K_PU | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX37_PIN_SD1_DATA0, + PAD_CTL_DRV_MAX | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_47K_PU | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX37_PIN_SD1_DATA1, + PAD_CTL_DRV_MAX | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_47K_PU | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX37_PIN_SD1_DATA2, + PAD_CTL_DRV_MAX | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_47K_PU | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX37_PIN_SD1_DATA3, + PAD_CTL_DRV_MAX | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_47K_PU | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + + /* Write Protected Pin */ + mxc_request_iomux(MX37_PIN_CSPI1_SS0, + IOMUX_CONFIG_SION | IOMUX_CONFIG_ALT4); + mxc_iomux_set_pad(MX37_PIN_CSPI1_SS0, + PAD_CTL_DRV_HIGH | PAD_CTL_HYS_NONE | + PAD_CTL_SRE_FAST); + /* + * SW workaround for the eSDHC1 Write Protected feature + * The PSR of CSPI1_SS0 (GPIO3_2) should be read. + */ + gpio_request(IOMUX_TO_GPIO(MX37_PIN_CSPI1_SS0), "cspi1_ss0"); + gpio_direction_output(IOMUX_TO_GPIO(MX37_PIN_CSPI1_SS0), 0); + gpio_set_value(IOMUX_TO_GPIO(MX37_PIN_CSPI1_SS0), 1); + break; + case 1: + mxc_request_iomux(MX37_PIN_SD2_CLK, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_request_iomux(MX37_PIN_SD2_CMD, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_request_iomux(MX37_PIN_SD2_DATA0, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_request_iomux(MX37_PIN_SD2_DATA1, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_request_iomux(MX37_PIN_SD2_DATA2, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_request_iomux(MX37_PIN_SD2_DATA3, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + + mxc_iomux_set_pad(MX37_PIN_SD2_CMD, + PAD_CTL_DRV_MAX | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_PUE_PULL | + PAD_CTL_47K_PU | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX37_PIN_SD2_CLK, + PAD_CTL_DRV_MAX | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_PUE_PULL | + PAD_CTL_47K_PU | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX37_PIN_SD2_DATA0, + PAD_CTL_DRV_MAX | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_PUE_PULL | + PAD_CTL_47K_PU | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX37_PIN_SD2_DATA1, + PAD_CTL_DRV_MAX | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_PUE_PULL | + PAD_CTL_47K_PU | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX37_PIN_SD2_DATA2, + PAD_CTL_DRV_MAX | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_PUE_PULL | + PAD_CTL_47K_PU | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + mxc_iomux_set_pad(MX37_PIN_SD2_DATA3, + PAD_CTL_DRV_MAX | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_PUE_PULL | + PAD_CTL_47K_PU | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + break; + default: + break; + } +} + +EXPORT_SYMBOL(gpio_sdhc_active); + +/*! + * Setup GPIO for SDHC1 to be inactive + * + * @param module SDHC module number + */ +void gpio_sdhc_inactive(int module) +{ + switch (module) { + case 0: + mxc_free_iomux(MX37_PIN_SD1_CLK, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_free_iomux(MX37_PIN_SD1_CMD, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_free_iomux(MX37_PIN_SD1_DATA0, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_free_iomux(MX37_PIN_SD1_DATA1, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_free_iomux(MX37_PIN_SD1_DATA2, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_free_iomux(MX37_PIN_SD1_DATA3, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + + mxc_iomux_set_pad(MX37_PIN_SD1_CLK, + (PAD_CTL_DRV_LOW | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX37_PIN_SD1_CMD, + (PAD_CTL_DRV_LOW | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX37_PIN_SD1_DATA0, + (PAD_CTL_DRV_LOW | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX37_PIN_SD1_DATA1, + (PAD_CTL_DRV_LOW | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX37_PIN_SD1_DATA2, + (PAD_CTL_DRV_LOW | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX37_PIN_SD1_DATA3, + (PAD_CTL_DRV_LOW | PAD_CTL_SRE_SLOW)); + + /* Free Write Protected Pin */ + mxc_free_iomux(MX37_PIN_CSPI1_SS0, + IOMUX_CONFIG_SION | IOMUX_CONFIG_ALT4); + mxc_iomux_set_pad(MX37_PIN_CSPI1_SS0, + (PAD_CTL_DRV_LOW | PAD_CTL_SRE_SLOW)); + break; + case 1: + mxc_free_iomux(MX37_PIN_SD2_CLK, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_free_iomux(MX37_PIN_SD2_CMD, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_free_iomux(MX37_PIN_SD2_DATA0, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_free_iomux(MX37_PIN_SD2_DATA1, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_free_iomux(MX37_PIN_SD2_DATA2, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + mxc_free_iomux(MX37_PIN_SD2_DATA3, + IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION); + + mxc_iomux_set_pad(MX37_PIN_SD2_CLK, + (PAD_CTL_DRV_LOW | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX37_PIN_SD2_CMD, + (PAD_CTL_DRV_LOW | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX37_PIN_SD2_DATA0, + (PAD_CTL_DRV_LOW | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX37_PIN_SD2_DATA1, + (PAD_CTL_DRV_LOW | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX37_PIN_SD2_DATA2, + (PAD_CTL_DRV_LOW | PAD_CTL_SRE_SLOW)); + mxc_iomux_set_pad(MX37_PIN_SD2_DATA3, + (PAD_CTL_DRV_LOW | PAD_CTL_SRE_SLOW)); + break; + default: + break; + } +} + +EXPORT_SYMBOL(gpio_sdhc_inactive); + +/* + * Probe for the card. If present the GPIO data would be set. + */ +int sdhc_get_card_det_status(struct device *dev) +{ + int ret; + u32 gpio = IOMUX_TO_GPIO(MX37_PIN_OWIRE_LINE); + + if (to_platform_device(dev)->id == 0) { + if (board_is_rev(BOARD_REV_2)) + ret = gpio_get_value(IOMUX_TO_GPIO(MX37_PIN_GPIO1_4)); + else + ret = gpio_get_value(gpio); + return ret; + } else { /* config the det pin for SDHC2 */ + return 0; + } +} + +EXPORT_SYMBOL(sdhc_get_card_det_status); + +/* + * Return the card detect pin. + */ +int sdhc_init_card_det(int id) +{ + u32 gpio; + + if (id == 0) { + if (board_is_rev(BOARD_REV_2)) { + gpio = IOMUX_TO_GPIO(MX37_PIN_GPIO1_4); + mxc_request_iomux(MX37_PIN_GPIO1_4, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX37_PIN_GPIO1_4, + PAD_CTL_DRV_HIGH | + PAD_CTL_HYS_NONE | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_NONE | PAD_CTL_SRE_FAST); + gpio_request(gpio, "gpio1_4"); + gpio_direction_input(gpio); + return IOMUX_TO_IRQ(MX37_PIN_GPIO1_4); + } else { + gpio = IOMUX_TO_GPIO(MX37_PIN_OWIRE_LINE); + mxc_request_iomux(MX37_PIN_OWIRE_LINE, + IOMUX_CONFIG_ALT4); + mxc_iomux_set_pad(MX37_PIN_OWIRE_LINE, + PAD_CTL_DRV_HIGH | + PAD_CTL_HYS_NONE | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_NONE | PAD_CTL_SRE_FAST); + gpio_request(gpio, "owire_line"); + gpio_direction_input(gpio); + return IOMUX_TO_IRQ(MX37_PIN_OWIRE_LINE); + } + } else { /* config the det pin for SDHC2 */ + return 0; + + } +} + +EXPORT_SYMBOL(sdhc_init_card_det); + +/*! + * Get CSPI1_SS0 pin value to detect write protection + */ +int sdhc_write_protect(struct device *dev) +{ + unsigned short rc = 0; + + rc = gpio_get_value(IOMUX_TO_GPIO(MX37_PIN_CSPI1_SS0)); + if (rc > 0) + return 1; + else + return 0; +} + +EXPORT_SYMBOL(sdhc_write_protect); + +/*! + * Setup GPIO for LCD to be active + * + */ +void gpio_lcd_active(void) +{ + mxc_request_iomux(MX37_PIN_DI1_PIN2, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_PAD_DI1_PIN3, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_DISP_CLK, IOMUX_CONFIG_ALT0); +} + +/*! + * Setup GPIO for LCD to be inactive + * + */ +void gpio_lcd_inactive(void) +{ + /*TODO*/} + +/*! + * Setup pins for SLCD to be active + * + */ +void slcd_gpio_config(void) +{ + /*TODO*/} + +/*! + * Switch to the specified sensor - MX33 ADS has two + * + */ +void gpio_sensor_select(int sensor) +{ + /*TODO*/} + +/*! + * Setup GPIO for sensor to be active + * + */ +void gpio_sensor_active(void) +{ + /*TODO*/} + +EXPORT_SYMBOL(gpio_sensor_active); + +/*! + * Setup GPIO for sensor to be inactive + * + */ +void gpio_sensor_inactive(void) +{ + /*TODO*/} + +EXPORT_SYMBOL(gpio_sensor_inactive); + +/*! + * Setup GPIO for ATA interface + * + */ +void gpio_ata_active(void) +{ + /*IOMUX Settings */ + /*PATA_DMARQ_B */ + mxc_request_iomux(MX37_PIN_NANDF_RE_B, IOMUX_CONFIG_ALT1); + /*PATA_DIOR */ + mxc_request_iomux(MX37_PIN_NANDF_ALE, IOMUX_CONFIG_ALT1); + /*PATA_DIOW */ + mxc_request_iomux(MX37_PIN_NANDF_CLE, IOMUX_CONFIG_ALT1); + /*PATA_DMACK */ + mxc_request_iomux(MX37_PIN_NANDF_WP_B, IOMUX_CONFIG_ALT1); + /*PATA_RESET_B */ + mxc_request_iomux(MX37_PIN_NANDF_RB, IOMUX_CONFIG_ALT1); + /*PATA_IORDY */ + mxc_request_iomux(MX37_PIN_NANDF_CS0, IOMUX_CONFIG_ALT1); + /*PATA_INTRQ_B */ + mxc_request_iomux(MX37_PIN_NANDF_CS1, IOMUX_CONFIG_ALT1); + /*PATA_CS_0 */ + mxc_request_iomux(MX37_PIN_NANDF_CS2, IOMUX_CONFIG_ALT1); + /*PATA_CS_1 */ + mxc_request_iomux(MX37_PIN_NANDF_CS3, IOMUX_CONFIG_ALT1); + + /*PATA_D0 */ + mxc_request_iomux(MX37_PIN_EIM_D0, IOMUX_CONFIG_ALT1); + /*PATA_D1 */ + mxc_request_iomux(MX37_PIN_EIM_D1, IOMUX_CONFIG_ALT1); + /*PATA_D2 */ + mxc_request_iomux(MX37_PIN_EIM_D2, IOMUX_CONFIG_ALT1); + /*PATA_D3 */ + mxc_request_iomux(MX37_PIN_EIM_D3, IOMUX_CONFIG_ALT1); + /*PATA_D4 */ + mxc_request_iomux(MX37_PIN_EIM_D4, IOMUX_CONFIG_ALT1); + /*PATA_D5 */ + mxc_request_iomux(MX37_PIN_EIM_D5, IOMUX_CONFIG_ALT1); + /*PATA_D6 */ + mxc_request_iomux(MX37_PIN_EIM_D6, IOMUX_CONFIG_ALT1); + /*PATA_D7 */ + mxc_request_iomux(MX37_PIN_EIM_D7, IOMUX_CONFIG_ALT1); + /*PATA_D8 */ + mxc_request_iomux(MX37_PIN_EIM_D8, IOMUX_CONFIG_ALT1); + /*PATA_D9 */ + mxc_request_iomux(MX37_PIN_EIM_D9, IOMUX_CONFIG_ALT1); + /*PATA_D10 */ + mxc_request_iomux(MX37_PIN_EIM_D10, IOMUX_CONFIG_ALT1); + /*PATA_D11 */ + mxc_request_iomux(MX37_PIN_EIM_D11, IOMUX_CONFIG_ALT1); + /*PATA_D12 */ + mxc_request_iomux(MX37_PIN_EIM_D12, IOMUX_CONFIG_ALT1); + /*PATA_D13 */ + mxc_request_iomux(MX37_PIN_EIM_D13, IOMUX_CONFIG_ALT1); + /*PATA_D14 */ + mxc_request_iomux(MX37_PIN_EIM_D14, IOMUX_CONFIG_ALT1); + /*PATA_D15 */ + mxc_request_iomux(MX37_PIN_EIM_D15, IOMUX_CONFIG_ALT1); + /*PATA_DA0 */ + mxc_request_iomux(MX37_PIN_SD2_DATA3, IOMUX_CONFIG_ALT1); + /*PATA_DA1 */ + mxc_request_iomux(MX37_PIN_SD2_DATA2, IOMUX_CONFIG_ALT1); + /*PATA_DA2 */ + mxc_request_iomux(MX37_PIN_SD2_DATA1, IOMUX_CONFIG_ALT1); + + /* BUFFER_ENABLE - HDD_ENABLE_B */ + mxc_request_iomux(MX37_PIN_NANDF_WE_B, IOMUX_CONFIG_ALT1); + + /* IOMUX Pad Settings */ + mxc_iomux_set_pad(MX37_PIN_EIM_D0, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D1, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D2, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D3, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D4, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D5, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D6, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D7, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D8, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D9, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D10, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D11, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D12, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D13, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D14, 0xc0); + mxc_iomux_set_pad(MX37_PIN_EIM_D15, 0xc0); + + mxc_iomux_set_pad(MX37_PIN_NANDF_RE_B, 0x0080); + mxc_iomux_set_pad(MX37_PIN_NANDF_CS1, 0x0020); + mxc_iomux_set_pad(MX37_PIN_SD2_DATA3, 0x00); + mxc_iomux_set_pad(MX37_PIN_SD2_DATA2, 0x00); + mxc_iomux_set_pad(MX37_PIN_SD2_DATA1, 0x00); + mxc_iomux_set_pad(MX37_PIN_NANDF_ALE, 0x00); + mxc_iomux_set_pad(MX37_PIN_NANDF_CS2, 0x00); + mxc_iomux_set_pad(MX37_PIN_NANDF_CS3, 0x00); + mxc_iomux_set_pad(MX37_PIN_NANDF_WE_B, 0x00); + mxc_iomux_set_pad(MX37_PIN_NANDF_CLE, 0x00); + mxc_iomux_set_pad(MX37_PIN_NANDF_WP_B, 0x00); + mxc_iomux_set_pad(MX37_PIN_NANDF_RB, 0x00); + mxc_iomux_set_pad(MX37_PIN_NANDF_CS0, 0x0020); + +} + +EXPORT_SYMBOL(gpio_ata_active); + +/*! + * Restore ATA interface pins to reset values + * + */ +void gpio_ata_inactive(void) +{ + /*Turn off the IOMUX for ATA group B signals */ + mxc_request_iomux(MX37_PIN_EIM_D0, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D1, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D2, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D3, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D4, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D5, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D6, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D7, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D8, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D9, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D10, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D11, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D12, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D13, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D14, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_EIM_D15, IOMUX_CONFIG_ALT0); + + /* Config the multiplex pin of ATA interface DIR, DA0-2, INTRQ, DMARQ */ + mxc_request_iomux(MX37_PIN_NANDF_RE_B, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_NANDF_CS1, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_SD2_DATA3, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_SD2_DATA2, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_SD2_DATA1, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_NANDF_ALE, IOMUX_CONFIG_ALT0); + + /* HDD_BUFF_EN (H:A->B, L:B->A) and HDD_ENABLE_B(H:Disable,L:Enable) */ + mxc_free_iomux(MX37_PIN_NANDF_WE_B, IOMUX_CONFIG_ALT0); + + /* These ATA pins are common to Group A and Group B */ + mxc_request_iomux(MX37_PIN_NANDF_CS2, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_NANDF_CS3, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_NANDF_ALE, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_NANDF_CLE, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_NANDF_WP_B, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_NANDF_RB, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX37_PIN_NANDF_CS0, IOMUX_CONFIG_ALT0); + +} + +EXPORT_SYMBOL(gpio_ata_inactive); + +/*! + * Setup GPIO for Keypad to be active + * + */ +void gpio_keypad_active(void) +{ + int pad_val; + + /* + * Configure the IOMUX control register for keypad signals. + */ + /*KEY_INT */ + mxc_request_iomux(MX37_PIN_GPIO1_3, IOMUX_CONFIG_ALT0); + /*KEY_WAKE */ + mxc_request_iomux(MX37_PIN_DISP1_DAT18, IOMUX_CONFIG_ALT4); + + /* fast slew rate */ + pad_val = (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_22K_PU | \ + PAD_CTL_ODE_OPENDRAIN_ENABLE | PAD_CTL_HYS_NONE | \ + PAD_CTL_DDR_INPUT_CMOS | PAD_CTL_DRV_VOT_LOW); + /*KEY_INT */ + mxc_iomux_set_pad(MX37_PIN_GPIO1_3, pad_val); + + /* fast slew rate */ + pad_val = (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | \ + PAD_CTL_ODE_OPENDRAIN_NONE | PAD_CTL_HYS_NONE | \ + PAD_CTL_DDR_INPUT_CMOS | PAD_CTL_DRV_VOT_LOW); + /*KEY_WAKE */ + mxc_iomux_set_pad(MX37_PIN_DISP1_DAT18, pad_val); + gpio_request(IOMUX_TO_GPIO(MX37_PIN_DISP1_DAT18), "disp1_dat18"); + gpio_direction_output(IOMUX_TO_GPIO(MX37_PIN_DISP1_DAT18), 0); + gpio_request(IOMUX_TO_GPIO(MX37_PIN_GPIO1_3), "gpio1_3"); + gpio_direction_input(IOMUX_TO_GPIO(MX37_PIN_GPIO1_3)); + + /* drive initial value */ + gpio_set_value(IOMUX_TO_GPIO(MX37_PIN_DISP1_DAT18), 1); +} + +EXPORT_SYMBOL(gpio_keypad_active); + +/*! + * Setup GPIO for Keypad to be inactive + * + */ +void gpio_keypad_inactive(void) +{ + /*KEY_INT */ + mxc_request_iomux(MX37_PIN_GPIO1_3, IOMUX_CONFIG_ALT0); + /*KEY_WAKE */ + mxc_request_iomux(MX37_PIN_DISP1_DAT18, IOMUX_CONFIG_ALT0); +} + +EXPORT_SYMBOL(gpio_keypad_inactive); + +/* + * USB OTG HS port + */ +int gpio_usbotg_hs_active(void) +{ + /*TODO*/ return 0; +} + +EXPORT_SYMBOL(gpio_usbotg_hs_active); + +void gpio_usbotg_hs_inactive(void) +{ + /*TODO*/} + +EXPORT_SYMBOL(gpio_usbotg_hs_inactive); + +/*! + * Setup GPIO for PCMCIA interface + * + */ +void gpio_pcmcia_active(void) +{ + /*TODO*/} + +EXPORT_SYMBOL(gpio_pcmcia_active); + +/*! + * Setup GPIO for pcmcia to be inactive + */ +void gpio_pcmcia_inactive(void) +{ + /*TODO*/} + +EXPORT_SYMBOL(gpio_pcmcia_inactive); + +/*! + * Setup GPIO for fec to be active + */ +void gpio_fec_active(void) +{ + /*TODO*/} + +EXPORT_SYMBOL(gpio_fec_active); +/*! + * Setup GPIO for fec to be inactive + */ +void gpio_fec_inactive(void) +{ + /*TODO*/} + +EXPORT_SYMBOL(gpio_fec_inactive); + +void gpio_spdif_active(void) +{ + iomux_pad_config_t regval = 0; + regval = + PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_PKE_ENABLE | + PAD_CTL_100K_PU; + mxc_iomux_set_pad(MX37_PIN_AUD3_BB_RXD, regval); + mxc_request_iomux(MX37_PIN_AUD3_BB_RXD, IOMUX_CONFIG_ALT1); +} + +EXPORT_SYMBOL(gpio_spdif_active); + +void gpio_spdif_inactive(void) +{ + mxc_free_iomux(MX37_PIN_AUD3_BB_RXD, IOMUX_CONFIG_ALT1); +} + +EXPORT_SYMBOL(gpio_spdif_inactive); + +void gpio_pmic_active(void) +{ + mxc_request_iomux(MX37_PIN_OWIRE_LINE, IOMUX_CONFIG_GPIO); + mxc_iomux_set_pad(MX37_PIN_OWIRE_LINE, PAD_CTL_SRE_SLOW | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_DRV_MEDIUM | + PAD_CTL_100K_PU | + PAD_CTL_HYS_ENABLE | + PAD_CTL_DRV_VOT_HIGH | PAD_CTL_DDR_INPUT_CMOS); + gpio_request(IOMUX_TO_GPIO(MX37_PIN_OWIRE_LINE), "owire_line"); + gpio_direction_input(IOMUX_TO_GPIO(MX37_PIN_OWIRE_LINE)); +} + +EXPORT_SYMBOL(gpio_pmic_active); + +void gpio_gps_active(void) +{ + /* PWR_EN */ + mxc_request_iomux(MX37_PIN_EIM_OE, IOMUX_CONFIG_GPIO); + mxc_iomux_set_pad(MX37_PIN_EIM_OE, PAD_CTL_100K_PU | + PAD_CTL_DRV_HIGH | PAD_CTL_HYS_NONE | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + gpio_request(IOMUX_TO_GPIO(MX37_PIN_EIM_OE), "eim_oe"); + gpio_direction_output(IOMUX_TO_GPIO(MX37_PIN_EIM_OE), 0); + + /* RESET */ + mxc_request_iomux(MX37_PIN_EIM_BCLK, IOMUX_CONFIG_GPIO); + mxc_iomux_set_pad(MX37_PIN_EIM_BCLK, PAD_CTL_DRV_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + gpio_request(IOMUX_TO_GPIO(MX37_PIN_EIM_BCLK), "eim_bclk"); + gpio_direction_output(IOMUX_TO_GPIO(MX37_PIN_EIM_BCLK), 0); + + gpio_set_value(IOMUX_TO_GPIO(MX37_PIN_EIM_OE), 0); + gpio_set_value(IOMUX_TO_GPIO(MX37_PIN_EIM_BCLK), 0); + + msleep(5); + gpio_set_value(IOMUX_TO_GPIO(MX37_PIN_EIM_BCLK), 1); + + msleep(5); +} + +EXPORT_SYMBOL(gpio_gps_active); + +int gpio_gps_access(int para) +{ + iomux_pin_name_t pin; + pin = (para & 0x1) ? MX37_PIN_EIM_OE : MX37_PIN_EIM_BCLK; + + if (para & 0x4) + return gpio_get_value(IOMUX_TO_GPIO(pin)); + else if (para & 0x2) + gpio_set_value(IOMUX_TO_GPIO(pin), 1); + else + gpio_set_value(IOMUX_TO_GPIO(pin), 0); + + return 0; +} + +EXPORT_SYMBOL(gpio_gps_access); + +void gpio_gps_inactive(void) +{ + mxc_free_iomux(MX37_PIN_EIM_BCLK, IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX37_PIN_EIM_OE, IOMUX_CONFIG_GPIO); +} + +EXPORT_SYMBOL(gpio_gps_inactive); + +int headphone_det_status(void) +{ + return gpio_get_value(IOMUX_TO_GPIO(MX37_PIN_AUD5_RXFS)); +} + +EXPORT_SYMBOL(headphone_det_status); diff --git a/arch/arm/mach-mx37/mx37_3stack_pmic_wm8350.c b/arch/arm/mach-mx37/mx37_3stack_pmic_wm8350.c new file mode 100644 index 000000000000..62ec76f0761b --- /dev/null +++ b/arch/arm/mach-mx37/mx37_3stack_pmic_wm8350.c @@ -0,0 +1,349 @@ +/* + * mx37-3stack-pmic-wm8350.c -- i.MX37 3STACK Driver for Wolfson WM8350 PMIC + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Copyright 2008-2009 Freescale Semiconductor Inc. + * + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/wm8350/core.h> +#include <linux/mfd/wm8350/pmic.h> +#include <linux/mfd/wm8350/gpio.h> +#include <linux/mfd/wm8350/bl.h> +#include <mach/irqs.h> + +#include "iomux.h" + +/* CPU */ +static struct regulator_consumer_supply dcdc1_consumers[] = { + { + .supply = "cpu_vcc", + } +}; + +static struct regulator_init_data dcdc1_data = { + .constraints = { + .name = "DCDC1", + .min_uV = 850000, + .max_uV = 1200000, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_FAST, + .state_mem = { + .uV = 1050000, + .mode = REGULATOR_MODE_NORMAL, + .enabled = 1, + }, + .initial_state = PM_SUSPEND_MEM, + .always_on = 1, + .boot_on = 1, + }, + .num_consumer_supplies = ARRAY_SIZE(dcdc1_consumers), + .consumer_supplies = dcdc1_consumers, +}; + +/* MX37 LP */ +static struct regulator_init_data dcdc4_data = { + .constraints = { + .name = "DCDC4", + .min_uV = 1000000, + .max_uV = 1250000, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_FAST, + .state_mem = { + .uV = 1250000, + .mode = REGULATOR_MODE_NORMAL, + .enabled = 1, + }, + .initial_state = PM_SUSPEND_MEM, + .always_on = 1, + .boot_on = 1, + }, +}; + +/* DDR RAM */ +static struct regulator_init_data dcdc6_data = { + .constraints = { + .name = "DCDC6", + .min_uV = 1800000, + .max_uV = 1800000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .state_mem = { + .uV = 1800000, + .mode = REGULATOR_MODE_NORMAL, + .enabled = 1, + }, + .state_disk = { + .mode = REGULATOR_MODE_NORMAL, + .enabled = 0, + }, + .always_on = 1, + .boot_on = 1, + .initial_state = PM_SUSPEND_MEM, + }, +}; + +static struct regulator_init_data dcdc3_data = { + .constraints = { + .name = "DCDC3", + .min_uV = 3300000, + .max_uV = 3300000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .apply_uV = 1, + }, +}; + +static struct regulator_init_data ldo1_data = { + .constraints = { + .name = "LDO1", + .min_uV = 2800000, + .max_uV = 2800000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .apply_uV = 1, + .always_on = 1, + .boot_on = 1, + }, +}; + +static struct regulator_init_data ldo2_data = { + .constraints = { + .name = "LDO2", + .min_uV = 2500000, + .max_uV = 2500000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .apply_uV = 1, + .always_on = 1, + .boot_on = 1, + }, +}; + +static struct regulator_init_data ldo3_data = { + .constraints = { + .name = "LDO3", + .min_uV = 1200000, + .max_uV = 1200000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .apply_uV = 1, + .always_on = 1, + .boot_on = 1, + }, +}; + +static struct regulator_init_data ldo4_data = { + .constraints = { + .name = "LDO4", + .min_uV = 1800000, + .max_uV = 1800000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .apply_uV = 1, + .always_on = 1, + .boot_on = 1, + }, +}; + +static struct regulator_init_data isinka_data = { + .constraints = { + .name = "ISINKA", + .min_uA = 0, + .max_uA = 225000, + .valid_ops_mask = REGULATOR_CHANGE_CURRENT, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + }, +}; + +static struct regulator_init_data dcdc5_data = { + .constraints = { + .name = "DCDC5", + .min_uV = 0, + .max_uV = 5000000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + }, +}; + +static struct regulator_init_data dcdc2_data = { + .constraints = { + .name = "DCDC2", + .min_uV = 0, + .max_uV = 5000000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + }, +}; + +static void wm8350_nop_release(struct device *dev) +{ + /* Nothing */ +} + +static int wm8350_check_fb(struct fb_info *info) +{ + return (to_platform_device(info->device)->id == 0); +} + +struct wm8350_bl_platform_data wm8350_bl_data = { + .isink = WM8350_ISINK_A, + .dcdc = WM8350_DCDC_5, + .voltage_ramp = WM8350_DC5_RMP_20V, + .retries = 5, + .max_brightness = 63, + .power = FB_BLANK_UNBLANK, + .brightness = 50, + .check_fb = wm8350_check_fb, +}; + +static struct platform_device mxc_wm8350_devices[] = { + { + .name = "wm8350-bl", + .id = 2, + .dev = { + .release = wm8350_nop_release, + .platform_data = &wm8350_bl_data, + }, + }, +}; + +struct mxc_audio_platform_data imx_3stack_audio_platform_data = { + .ssi_num = 2, + .src_port = 2, + .ext_port = 5, + .regulator1 = "DCDC6", + .regulator2 = "DCDC3", +}; + +static struct platform_device *imx_snd_device; + +static int mx37_wm8350_init(struct wm8350 *wm8350) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(mxc_wm8350_devices); i++) { + if (platform_device_register(&mxc_wm8350_devices[i]) < 0) + dev_err(&mxc_wm8350_devices[i].dev, + "Unable to register WM8350 device\n"); + } + + wm8350->pmic.isink_A_dcdc = WM8350_DCDC_5; + + /*Note: Needs to be moved into a regulator function. */ + /* Configuring -- GPIO 7 pin */ + if (wm8350_gpio_config(wm8350, 7, WM8350_GPIO_DIR_OUT, 0, + WM8350_GPIO_ACTIVE_LOW, WM8350_GPIO_PULL_NONE, + WM8350_GPIO_INVERT_OFF, + WM8350_GPIO_DEBOUNCE_OFF) == 0) + wm8350_set_bits(wm8350, WM8350_GPIO_PIN_STATUS, 1 << 7); + else + printk(KERN_ERR "Error in setting Wolfson GPIO pin 7 \n"); + /* enable gpio4:USB_VBUS_EN */ + ret = + wm8350_gpio_config(wm8350, 4, WM8350_GPIO_DIR_IN, + WM8350_GPIO4_MR_IN, WM8350_GPIO_ACTIVE_HIGH, + WM8350_GPIO_PULL_UP, WM8350_GPIO_INVERT_OFF, + WM8350_GPIO_DEBOUNCE_OFF); + if (ret) + printk(KERN_ERR "Error in setting USB VBUS enable pin\n"); + + wm8350_register_regulator(wm8350, WM8350_DCDC_1, &dcdc1_data); + wm8350_register_regulator(wm8350, WM8350_DCDC_2, &dcdc2_data); + wm8350_register_regulator(wm8350, WM8350_DCDC_3, &dcdc3_data); + wm8350_register_regulator(wm8350, WM8350_DCDC_4, &dcdc4_data); + wm8350_register_regulator(wm8350, WM8350_DCDC_5, &dcdc5_data); + wm8350_register_regulator(wm8350, WM8350_DCDC_6, &dcdc6_data); + wm8350_register_regulator(wm8350, WM8350_LDO_1, &ldo1_data); + wm8350_register_regulator(wm8350, WM8350_LDO_2, &ldo2_data); + wm8350_register_regulator(wm8350, WM8350_LDO_3, &ldo3_data); + wm8350_register_regulator(wm8350, WM8350_LDO_4, &ldo4_data); + wm8350_register_regulator(wm8350, WM8350_ISINK_A, &isinka_data); + + /* register sound */ + pr_info("Registering imx37_snd_device"); + imx_snd_device = platform_device_alloc("wm8350-imx-3stack-audio", -1); + if (!imx_snd_device) { + ret = -ENOMEM; + goto err; + } + imx_3stack_audio_platform_data.priv = wm8350; + + imx_snd_device->dev.platform_data = &imx_3stack_audio_platform_data; + ret = platform_device_add(imx_snd_device); + if (ret) + goto snd_err; + + return 0; + +snd_err: + platform_device_put(imx_snd_device); + +err: + kfree(wm8350->reg_cache); + return ret; +} + +struct wm8350_platform_data __initdata mx37_wm8350_pdata = { + .init = mx37_wm8350_init, +}; + +static struct i2c_board_info __initdata wm8350_i2c_device = { + I2C_BOARD_INFO("wm8350", 0x1a), + .platform_data = &mx37_wm8350_pdata, + .irq = IOMUX_TO_IRQ(MX37_PIN_GPIO1_4), +}; + +static __init int mxc_init_i2c(void) +{ + i2c_register_board_info(1, &wm8350_i2c_device, 1); + return 0; +} + +subsys_initcall(mxc_init_i2c); + +static __init int wm8350_regulator_init(void) +{ + int i = 0; + int ret = 0; + struct regulator *regulator; + char *wm8350_global_regulator[] = { + "DCDC1", + "DCDC3", + "DCDC4", + "DCDC6", + "LDO3", + }; + + /* for board v2.0 later, do nothing here */ + if (board_is_rev(BOARD_REV_2)) + return 0; + while ((i < ARRAY_SIZE(wm8350_global_regulator)) && + !IS_ERR_VALUE( + (unsigned long)(regulator = + regulator_get(NULL, + wm8350_global_regulator + [i])))) { + regulator_enable(regulator); + if (wm8350_global_regulator[i] == "DCDC4") + ret = + regulator_set_voltage(regulator, 1250000, 1250000); + else if (wm8350_global_regulator[i] == "DCDC1") + ret = + regulator_set_voltage(regulator, 1050000, 1050000); + i++; + } + return ret; +} + +late_initcall(wm8350_regulator_init); diff --git a/arch/arm/mach-mx37/mx37_pins.h b/arch/arm/mach-mx37/mx37_pins.h new file mode 100644 index 000000000000..10dcbf3c8d8b --- /dev/null +++ b/arch/arm/mach-mx37/mx37_pins.h @@ -0,0 +1,256 @@ +/* + * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_ARCH_MXC_MX37_PINS_H__ +#define __ASM_ARCH_MXC_MX37_PINS_H__ + +/*! + * @file arch-mxc/mx37_pins.h + * + * @brief MX37 I/O Pin List + * + * @ingroup GPIO_MX37 + */ + +#ifndef __ASSEMBLY__ + +/*! + * @name IOMUX/PAD Bit field definitions + */ + +/*! @{ */ + +/*! + * In order to identify pins more effectively, each mux-controlled pin's + * enumerated value is constructed in the following way: + * + * ------------------------------------------------------------------- + * 31-29 | 28 - 24 | 23 | 22 - 20 | 19 - 10| 9 - 0 + * ------------------------------------------------------------------- + * IO_P | IO_I | RSVD_I | GPIO_I | PAD_I | MUX_I + * ------------------------------------------------------------------- + * + * Bit 0 to 9 contains MUX_I used to identify the register + * offset (0-based. base is IOMUX_module_base) defined in the Section + * "sw_pad_ctl & sw_mux_ctl details" of the IC Spec. The + * similar field definitions are used for the pad control register. + * For example, the MX37_PIN_ETM_D0 is defined in the enumeration: + * ( (0x28 - MUX_I_START) << MUX_I)|( (0x250 - PAD_I_START) << PAD_I) + * It means the mux control register is at register offset 0x28. The pad control + * register offset is: 0x250 and also occupy the least significant bits + * within the register. + */ + +/*! + * Starting bit position within each entry of \b iomux_pins to represent the + * MUX control register offset + */ +#define MUX_I 0 +/*! + * Starting bit position within each entry of \b iomux_pins to represent the + * PAD control register offset + */ +#define PAD_I 10 +/*! + * Starting bit position within each entry of \b iomux_pins to represent which + * mux mode is for GPIO (0-based) + */ +#define GPIO_I 20 +/*! + * Starting bit position which is reserved. + */ +#define RSVD_I 23 + +#define NON_GPIO_PORT 0x7 +#define PIN_TO_MUX_MASK ((1 << (PAD_I - MUX_I)) -1) +#define PIN_TO_PAD_MASK ((1 << (GPIO_I - PAD_I)) - 1) +#define PIN_TO_ALT_GPIO_MASK ((1 << (RSVD_I - GPIO_I)) - 1) + +#define NON_MUX_I PIN_TO_MUX_MASK +#define MUX_I_START 0x0008 +#define MUX_I_END (PAD_I_START - 4) +#define PAD_I_START 0x230 +#define PAD_I_END (INPUT_CTL_START - 4) +#define INPUT_CTL_START 0x508 +#define INPUT_CTL_END 0x614 + +#define _MXC_BUILD_PIN(gp, gi, ga, mi, pi) \ + (((gp) << MUX_IO_P) | ((gi) << MUX_IO_I) | \ + ((mi - MUX_I_START) << MUX_I) | \ + ((pi - PAD_I_START) << PAD_I) | \ + ((ga) << GPIO_I)) + +#define _MXC_BUILD_GPIO_PIN(gp, gi, ga, mi, pi) \ + _MXC_BUILD_PIN(gp, gi, ga, mi, pi) + +#define _MXC_BUILD_NON_GPIO_PIN(mi, pi) \ + _MXC_BUILD_PIN(NON_GPIO_PORT, 0, 0, mi, pi) + +#define PIN_TO_IOMUX_MUX(pin) ((pin >> MUX_I) & PIN_TO_MUX_MASK) +#define PIN_TO_IOMUX_PAD(pin) ((pin >> PAD_I) & PIN_TO_PAD_MASK) +#define PIN_TO_ALT_GPIO(pin) ((pin >> GPIO_I) & PIN_TO_ALT_GPIO_MASK) +#define PIN_TO_IOMUX_INDEX(pin) (PIN_TO_IOMUX_MUX(pin) >> 2) + +/*! @} End IOMUX/PAD Bit field definitions */ + +/*! + * This enumeration is constructed based on the Section + * "sw_pad_ctl & sw_mux_ctl details" of the MX37 IC Spec. Each enumerated + * value is constructed based on the rules described above. + */ +enum iomux_pins { + MX37_PIN_KEY_ROW0 = _MXC_BUILD_NON_GPIO_PIN(0x8, 0x230), + MX37_PIN_KEY_ROW1 = _MXC_BUILD_NON_GPIO_PIN(0xC, 0x234), + MX37_PIN_KEY_ROW2 = _MXC_BUILD_NON_GPIO_PIN(0x10, 0x238), + MX37_PIN_KEY_ROW3 = _MXC_BUILD_NON_GPIO_PIN(0x14, 0x23C), + MX37_PIN_KEY_ROW4 = _MXC_BUILD_NON_GPIO_PIN(0x18, 0x240), + MX37_PIN_KEY_ROW5 = _MXC_BUILD_NON_GPIO_PIN(0x1C, 0x244), + MX37_PIN_KEY_ROW6 = _MXC_BUILD_NON_GPIO_PIN(0x20, 0x248), + MX37_PIN_KEY_ROW7 = _MXC_BUILD_NON_GPIO_PIN(0x24, 0x24C), + MX37_PIN_ETM_D0 = _MXC_BUILD_NON_GPIO_PIN(0x28, 0x250), + MX37_PIN_ETM_D1 = _MXC_BUILD_NON_GPIO_PIN(0x2C, 0x254), + MX37_PIN_ETM_D2 = _MXC_BUILD_NON_GPIO_PIN(0x30, 0x258), + MX37_PIN_ETM_D3 = _MXC_BUILD_NON_GPIO_PIN(0x34, 0x25C), + MX37_PIN_ETM_D4 = _MXC_BUILD_NON_GPIO_PIN(0x38, 0x260), + MX37_PIN_ETM_D5 = _MXC_BUILD_NON_GPIO_PIN(0x3C, 0x264), + MX37_PIN_ETM_D6 = _MXC_BUILD_NON_GPIO_PIN(0x40, 0x268), + MX37_PIN_ETM_D7 = _MXC_BUILD_NON_GPIO_PIN(0x44, 0x26C), + MX37_PIN_EIM_EB0 = _MXC_BUILD_GPIO_PIN(0, 15, 3, 0x48, 0x2A8), + MX37_PIN_EIM_EB1 = _MXC_BUILD_GPIO_PIN(0, 14, 3, 0x4C, 0x2AC), + MX37_PIN_EIM_OE = _MXC_BUILD_GPIO_PIN(0, 13, 3, 0x50, 0x2B0), + MX37_PIN_EIM_CS0 = _MXC_BUILD_GPIO_PIN(1, 0, 1, 0x54, 0x2B4), + MX37_PIN_EIM_CS1 = _MXC_BUILD_GPIO_PIN(1, 1, 1, 0x58, 0x2B8), + MX37_PIN_EIM_ECB = _MXC_BUILD_GPIO_PIN(0, 12, 3, 0x5C, 0x2BC), + MX37_PIN_EIM_LBA = _MXC_BUILD_GPIO_PIN(0, 11, 3, 0x60, 0x2C0), + MX37_PIN_EIM_BCLK = _MXC_BUILD_GPIO_PIN(0, 10, 3, 0x64, 0x2C4), + MX37_PIN_EIM_RW = _MXC_BUILD_GPIO_PIN(0, 9, 3, 0x68, 0x2C8), + MX37_PIN_NANDF_WE_B = _MXC_BUILD_GPIO_PIN(1, 2, 4, 0x6C, 0x2CC), + MX37_PIN_NANDF_RE_B = _MXC_BUILD_GPIO_PIN(1, 3, 4, 0x70, 0x2D0), + MX37_PIN_NANDF_ALE = _MXC_BUILD_GPIO_PIN(1, 4, 4, 0x74, 0x2D4), + MX37_PIN_NANDF_CLE = _MXC_BUILD_GPIO_PIN(1, 5, 4, 0x78, 0x2D8), + MX37_PIN_NANDF_WP_B = _MXC_BUILD_GPIO_PIN(1, 6, 4, 0x7C, 0x2DC), + MX37_PIN_NANDF_RB = _MXC_BUILD_GPIO_PIN(1, 7, 4, 0x80, 0x2E0), + MX37_PIN_NANDF_CS0 = _MXC_BUILD_GPIO_PIN(1, 8, 4, 0x84, 0x2E4), + MX37_PIN_NANDF_CS1 = _MXC_BUILD_GPIO_PIN(1, 9, 4, 0x88, 0x2E8), + MX37_PIN_NANDF_CS2 = _MXC_BUILD_GPIO_PIN(1, 10, 4, 0x8C, 0x2EC), + MX37_PIN_NANDF_CS3 = _MXC_BUILD_GPIO_PIN(1, 11, 4, 0x90, 0x2F0), + MX37_PIN_EIM_D15 = _MXC_BUILD_NON_GPIO_PIN(0x94, 0x2F4), + MX37_PIN_EIM_D14 = _MXC_BUILD_NON_GPIO_PIN(0x98, 0x2F8), + MX37_PIN_EIM_D13 = _MXC_BUILD_NON_GPIO_PIN(0x9C, 0x2FC), + MX37_PIN_EIM_D12 = _MXC_BUILD_NON_GPIO_PIN(0xA0, 0x300), + MX37_PIN_EIM_D11 = _MXC_BUILD_NON_GPIO_PIN(0xA4, 0x304), + MX37_PIN_EIM_D10 = _MXC_BUILD_NON_GPIO_PIN(0xA8, 0x308), + MX37_PIN_EIM_D9 = _MXC_BUILD_NON_GPIO_PIN(0xAC, 0x30C), + MX37_PIN_EIM_D8 = _MXC_BUILD_GPIO_PIN(0, 8, 3, 0xB0, 0x310), + MX37_PIN_EIM_D7 = _MXC_BUILD_GPIO_PIN(2, 27, 3, 0xB4, 0x314), + MX37_PIN_EIM_D6 = _MXC_BUILD_GPIO_PIN(2, 26, 3, 0xB8, 0x318), + MX37_PIN_EIM_D5 = _MXC_BUILD_GPIO_PIN(2, 25, 3, 0xBC, 0x31C), + MX37_PIN_EIM_D4 = _MXC_BUILD_GPIO_PIN(2, 24, 3, 0xC0, 0x320), + MX37_PIN_EIM_D3 = _MXC_BUILD_GPIO_PIN(2, 23, 3, 0xC4, 0x324), + MX37_PIN_EIM_D2 = _MXC_BUILD_GPIO_PIN(2, 22, 3, 0xC8, 0x328), + MX37_PIN_EIM_D1 = _MXC_BUILD_GPIO_PIN(2, 21, 3, 0xCC, 0x32C), + MX37_PIN_EIM_D0 = _MXC_BUILD_GPIO_PIN(2, 20, 3, 0xD0, 0x330), + MX37_PIN_SD1_CMD = _MXC_BUILD_GPIO_PIN(0, 16, 3, 0xD4, 0x334), + MX37_PIN_SD1_CLK = _MXC_BUILD_GPIO_PIN(0, 17, 3, 0xD8, 0x338), + MX37_PIN_SD1_DATA0 = _MXC_BUILD_GPIO_PIN(0, 18, 3, 0xDC, 0x33C), + MX37_PIN_SD1_DATA1 = _MXC_BUILD_GPIO_PIN(0, 19, 3, 0xE0, 0x340), + MX37_PIN_SD1_DATA2 = _MXC_BUILD_GPIO_PIN(0, 20, 3, 0xE4, 0x344), + MX37_PIN_SD1_DATA3 = _MXC_BUILD_GPIO_PIN(0, 21, 3, 0xE8, 0x348), + MX37_PIN_SD2_CMD = _MXC_BUILD_GPIO_PIN(0, 22, 3, 0xEC, 0x34C), + MX37_PIN_SD2_CLK = _MXC_BUILD_GPIO_PIN(0, 23, 3, 0xF0, 0x350), + MX37_PIN_SD2_DATA0 = _MXC_BUILD_GPIO_PIN(0, 24, 3, 0xF4, 0x354), + MX37_PIN_SD2_DATA1 = _MXC_BUILD_GPIO_PIN(0, 25, 3, 0xF8, 0x358), + MX37_PIN_SD2_DATA2 = _MXC_BUILD_GPIO_PIN(0, 26, 3, 0xFC, 0x35C), + MX37_PIN_SD2_DATA3 = _MXC_BUILD_GPIO_PIN(0, 27, 3, 0x100, 0x360), + MX37_PIN_I2C1_CLK = _MXC_BUILD_GPIO_PIN(1, 12, 4, 0x104, 0x364), + MX37_PIN_I2C1_DAT = _MXC_BUILD_GPIO_PIN(1, 13, 4, 0x108, 0x368), + MX37_PIN_AUD3_BB_TXD = _MXC_BUILD_GPIO_PIN(1, 14, 4, 0x10C, 0x36C), + MX37_PIN_AUD3_BB_RXD = _MXC_BUILD_GPIO_PIN(1, 15, 4, 0x110, 0x370), + MX37_PIN_AUD3_BB_CK = _MXC_BUILD_GPIO_PIN(1, 16, 4, 0x114, 0x374), + MX37_PIN_AUD3_BB_FS = _MXC_BUILD_GPIO_PIN(1, 17, 4, 0x118, 0x378), + MX37_PIN_AUD5_RXFS = _MXC_BUILD_GPIO_PIN(1, 18, 4, 0x11C, 0x37C), + MX37_PIN_AUD5_RXC = _MXC_BUILD_GPIO_PIN(1, 19, 4, 0x120, 0x380), + MX37_PIN_AUD5_WB_TXD = _MXC_BUILD_GPIO_PIN(1, 20, 4, 0x124, 0x384), + MX37_PIN_AUD5_WB_RXD = _MXC_BUILD_GPIO_PIN(1, 21, 4, 0x128, 0x388), + MX37_PIN_AUD5_WB_CK = _MXC_BUILD_GPIO_PIN(1, 22, 4, 0x12C, 0x38C), + MX37_PIN_AUD5_WB_FS = _MXC_BUILD_GPIO_PIN(1, 23, 4, 0x130, 0x390), + MX37_PIN_CSPI1_MOSI = _MXC_BUILD_GPIO_PIN(2, 0, 4, 0x134, 0x394), + MX37_PIN_CSPI1_MISO = _MXC_BUILD_GPIO_PIN(2, 1, 4, 0x138, 0x398), + MX37_PIN_CSPI1_SS0 = _MXC_BUILD_GPIO_PIN(2, 2, 4, 0x13C, 0x39C), + MX37_PIN_CSPI1_SS1 = _MXC_BUILD_GPIO_PIN(2, 3, 4, 0x140, 0x3A0), + MX37_PIN_CSPI1_SCLK = _MXC_BUILD_GPIO_PIN(2, 4, 4, 0x144, 0x3A4), + MX37_PIN_CSPI2_MOSI = _MXC_BUILD_GPIO_PIN(2, 5, 4, 0x148, 0x3A8), + MX37_PIN_CSPI2_MISO = _MXC_BUILD_GPIO_PIN(2, 6, 4, 0x14C, 0x3AC), + MX37_PIN_CSPI2_SS0 = _MXC_BUILD_GPIO_PIN(2, 7, 4, 0x150, 0x3B0), + MX37_PIN_CSPI2_SS1 = _MXC_BUILD_GPIO_PIN(2, 8, 4, 0x154, 0x3B4), + MX37_PIN_CSPI2_SCLK = _MXC_BUILD_GPIO_PIN(2, 9, 4, 0x158, 0x3B8), + MX37_PIN_UART1_RXD = _MXC_BUILD_GPIO_PIN(1, 24, 4, 0x15C, 0x3BC), + MX37_PIN_UART1_TXD = _MXC_BUILD_GPIO_PIN(1, 25, 4, 0x160, 0x3C0), + MX37_PIN_UART1_RTS = _MXC_BUILD_GPIO_PIN(1, 26, 4, 0x164, 0x3C4), + MX37_PIN_UART1_CTS = _MXC_BUILD_GPIO_PIN(1, 27, 4, 0x168, 0x3C8), + MX37_PIN_UART1_DTR = _MXC_BUILD_GPIO_PIN(1, 28, 4, 0x16C, 0x3CC), + MX37_PIN_UART1_DSR = _MXC_BUILD_GPIO_PIN(1, 29, 4, 0x170, 0x3D0), + MX37_PIN_UART1_RI = _MXC_BUILD_GPIO_PIN(1, 30, 4, 0x174, 0x3D4), + MX37_PIN_UART1_DCD = _MXC_BUILD_GPIO_PIN(1, 31, 4, 0x178, 0x3D8), + MX37_PIN_OWIRE_LINE = _MXC_BUILD_GPIO_PIN(0, 31, 4, 0x17C, 0x3DC), + MX37_PIN_JTAG_DE_B = _MXC_BUILD_NON_GPIO_PIN(0x180, 0x3E0), + MX37_PIN_DI1_PIN11 = _MXC_BUILD_GPIO_PIN(2, 10, 4, 0x184, 0x3E4), + MX37_PIN_DI1_PIN12 = _MXC_BUILD_GPIO_PIN(2, 11, 4, 0x188, 0x3E8), + MX37_PIN_DI1_PIN13 = _MXC_BUILD_GPIO_PIN(2, 12, 4, 0x18C, 0x3EC), + MX37_PIN_DI1_D0_CS = _MXC_BUILD_GPIO_PIN(2, 13, 4, 0x190, 0x3F0), + MX37_PIN_DI1_PIN15 = _MXC_BUILD_GPIO_PIN(0, 30, 4, 0x194, 0), + MX37_PIN_DISP1_DAT0 = _MXC_BUILD_NON_GPIO_PIN(0x198, 0x3F4), + MX37_PIN_DISP1_DAT1 = _MXC_BUILD_NON_GPIO_PIN(0x19C, 0x3F8), + MX37_PIN_DISP1_DAT2 = _MXC_BUILD_NON_GPIO_PIN(0x1A0, 0x3FC), + MX37_PIN_DISP1_DAT3 = _MXC_BUILD_NON_GPIO_PIN(0x1A4, 0x400), + MX37_PIN_DISP1_DAT4 = _MXC_BUILD_NON_GPIO_PIN(0x1A8, 0x404), + MX37_PIN_DISP1_DAT5 = _MXC_BUILD_NON_GPIO_PIN(0x1AC, 0x408), + MX37_PIN_DISP1_DAT6 = _MXC_BUILD_NON_GPIO_PIN(0x1B0, 0x40C), + MX37_PIN_DISP1_DAT7 = _MXC_BUILD_NON_GPIO_PIN(0x1B4, 0x410), + MX37_PIN_DISP1_DAT8 = _MXC_BUILD_NON_GPIO_PIN(0x1B8, 0x414), + MX37_PIN_DISP1_DAT9 = _MXC_BUILD_NON_GPIO_PIN(0x1BC, 0x418), + MX37_PIN_DISP1_DAT10 = _MXC_BUILD_NON_GPIO_PIN(0x1C0, 0x41C), + MX37_PIN_DISP1_DAT11 = _MXC_BUILD_NON_GPIO_PIN(0x1C4, 0x420), + MX37_PIN_DISP1_DAT12 = _MXC_BUILD_NON_GPIO_PIN(0x1C8, 0x424), + MX37_PIN_DISP1_DAT13 = _MXC_BUILD_NON_GPIO_PIN(0x1CC, 0x428), + MX37_PIN_DISP1_DAT14 = _MXC_BUILD_NON_GPIO_PIN(0x1D0, 0x42C), + MX37_PIN_DISP1_DAT15 = _MXC_BUILD_NON_GPIO_PIN(0x1D4, 0x430), + MX37_PIN_DISP1_DAT16 = _MXC_BUILD_GPIO_PIN(0, 28, 4, 0x1D8, 0x434), + MX37_PIN_DISP1_DAT17 = _MXC_BUILD_GPIO_PIN(0, 29, 4, 0x1DC, 0x438), + MX37_PIN_DISP1_DAT18 = _MXC_BUILD_GPIO_PIN(2, 14, 4, 0x1E0, 0x43C), + MX37_PIN_DISP1_DAT19 = _MXC_BUILD_GPIO_PIN(2, 15, 4, 0x1E4, 0x440), + MX37_PIN_DISP1_DAT20 = _MXC_BUILD_GPIO_PIN(2, 16, 4, 0x1E8, 0x444), + MX37_PIN_DISP1_DAT21 = _MXC_BUILD_GPIO_PIN(2, 17, 4, 0x1EC, 0x448), + MX37_PIN_DISP1_DAT22 = _MXC_BUILD_GPIO_PIN(2, 18, 4, 0x1F0, 0x44C), + MX37_PIN_DISP1_DAT23 = _MXC_BUILD_GPIO_PIN(2, 18, 4, 0x1F4, 0x450), + MX37_PIN_PAD_DI1_PIN3 = _MXC_BUILD_GPIO_PIN(2, 29, 4, 0x1F8, 0), + MX37_PIN_DISP_CLK = _MXC_BUILD_GPIO_PIN(2, 30, 4, 0x1FC, 0), + MX37_PIN_DI1_PIN2 = _MXC_BUILD_GPIO_PIN(2, 31, 4, 0x200, 0), + MX37_PIN_BOOT_MODE1 = _MXC_BUILD_NON_GPIO_PIN(0x204, 0x454), + MX37_PIN_BOOT_MODE0 = _MXC_BUILD_NON_GPIO_PIN(0x208, 0x458), + MX37_PIN_WDOG_RST = _MXC_BUILD_GPIO_PIN(2, 28, 1, 0x20C, 0x464), + MX37_PIN_GPIO1_0 = _MXC_BUILD_GPIO_PIN(0, 0, 0, 0x210, 0x468), + MX37_PIN_GPIO1_1 = _MXC_BUILD_GPIO_PIN(0, 1, 0, 0x214, 0x46C), + MX37_PIN_GPIO1_2 = _MXC_BUILD_GPIO_PIN(0, 2, 1, 0x218, 0x470), + MX37_PIN_GPIO1_3 = _MXC_BUILD_GPIO_PIN(0, 3, 0, 0x21C, 0x474), + MX37_PIN_GPIO1_4 = _MXC_BUILD_GPIO_PIN(0, 4, 0, 0x220, 0x478), + MX37_PIN_GPIO1_5 = _MXC_BUILD_GPIO_PIN(0, 5, 0, 0x224, 0x47C), + MX37_PIN_GPIO1_6 = _MXC_BUILD_GPIO_PIN(0, 6, 0, 0x228, 0x480), + MX37_PIN_GPIO1_7 = _MXC_BUILD_GPIO_PIN(0, 7, 0, 0x22C, 0x484), + MX37_PIN_GRP_H10 = _MXC_BUILD_NON_GPIO_PIN(0x230, 0x490), + MX37_PIN_GRP_H9 = _MXC_BUILD_NON_GPIO_PIN(0x230, 0x494), + MX37_PIN_GRP_H3 = _MXC_BUILD_NON_GPIO_PIN(0x230, 0x4D0), + MX37_PIN_GRP_H5 = _MXC_BUILD_NON_GPIO_PIN(0x230, 0x4EC), +}; + +#endif /* */ +#endif /* */ diff --git a/arch/arm/mach-mx37/pm.c b/arch/arm/mach-mx37/pm.c new file mode 100644 index 000000000000..8ea478a4447c --- /dev/null +++ b/arch/arm/mach-mx37/pm.c @@ -0,0 +1,74 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/suspend.h> +#include <mach/hardware.h> + +static int mx37_suspend_enter(suspend_state_t state) +{ + switch (state) { + case PM_SUSPEND_MEM: + mxc_cpu_lp_set(STOP_POWER_OFF); + break; + case PM_SUSPEND_STANDBY: + mxc_cpu_lp_set(WAIT_UNCLOCKED_POWER_OFF); + break; + default: + return -EINVAL; + } + + if (tzic_enable_wake(0) != 0) + return -EAGAIN; + + cpu_do_idle(); + + return 0; +} + +/* + * Called after processes are frozen, but before we shut down devices. + */ +static int mx37_suspend_prepare(void) +{ + return 0; +} + +/* + * Called after devices are re-setup, but before processes are thawed. + */ +static void mx37_suspend_finish(void) +{ +} + +static int mx37_pm_valid(suspend_state_t state) +{ + return (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX); +} + +struct platform_suspend_ops mx37_suspend_ops = { + .valid = mx37_pm_valid, + .prepare = mx37_suspend_prepare, + .enter = mx37_suspend_enter, + .finish = mx37_suspend_finish, +}; + +static int __init mx37_pm_init(void) +{ + pr_info("Static Power Management for Freescale i.MX37\n"); + suspend_set_ops(&mx37_suspend_ops); + + return 0; +} + +late_initcall(mx37_pm_init); diff --git a/arch/arm/mach-mx37/sdma_script_code.h b/arch/arm/mach-mx37/sdma_script_code.h new file mode 100644 index 000000000000..f3b7d509ffb5 --- /dev/null +++ b/arch/arm/mach-mx37/sdma_script_code.h @@ -0,0 +1,203 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All 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 */ + +/*! + * @file sdma_script_code.h + * @brief This file contains functions of SDMA scripts code initialization + * + * The file was generated automatically. Based on sdma scripts library. + * + * @ingroup SDMA + */ +/******************************************************************************* + + SDMA RELEASE LABEL: "SS15_MARLEY" + +*******************************************************************************/ + +#ifndef __SDMA_SCRIPT_CODE_H__ +#define __SDMA_SCRIPT_CODE_H__ + +/*! +* SDMA ROM scripts start addresses and sizes +*/ + +#define start_ADDR 0 +#define start_SIZE 20 + +#define core_ADDR 80 +#define core_SIZE 232 + +#define common_ADDR 312 +#define common_SIZE 330 + +#define ap_2_ap_ADDR 642 +#define ap_2_ap_SIZE 41 + +#define app_2_mcu_ADDR 683 +#define app_2_mcu_SIZE 64 + +#define mcu_2_app_ADDR 747 +#define mcu_2_app_SIZE 70 + +#define uart_2_mcu_ADDR 817 +#define uart_2_mcu_SIZE 75 + +#define shp_2_mcu_ADDR 892 +#define shp_2_mcu_SIZE 69 + +#define mcu_2_shp_ADDR 961 +#define mcu_2_shp_SIZE 72 + +#define uartsh_2_mcu_ADDR 1033 +#define uartsh_2_mcu_SIZE 69 + +#define mcu_2_ata_ADDR 1102 +#define mcu_2_ata_SIZE 81 + +#define ata_2_mcu_ADDR 1183 +#define ata_2_mcu_SIZE 96 + +#define burstDMA__2__burstDMA_routine_ADDR 1279 +#define burstDMA__2__burstDMA_routine_SIZE 227 + +#define test_ADDR 1506 +#define test_SIZE 63 + +#define signature_ADDR 1023 +#define signature_SIZE 1 + +/*! +* SDMA RAM scripts start addresses and sizes +*/ + +#define dptc_dvfs_ADDR 6144 +#define dptc_dvfs_SIZE 270 + +#define ext_mem__ipu_ram_ADDR 6414 +#define ext_mem__ipu_ram_SIZE 123 + +#define mcu_2_mshc_ADDR 6537 +#define mcu_2_mshc_SIZE 54 + +#define mcu_2_spdif_marley_ADDR 6591 +#define mcu_2_spdif_marley_SIZE 161 + +#define mshc_2_mcu_ADDR 6752 +#define mshc_2_mcu_SIZE 54 + +/*! +* SDMA RAM image start address and size +*/ + +#define RAM_CODE_START_ADDR 6144 +#define RAM_CODE_SIZE 662 + +/*! +* Buffer that holds the SDMA RAM image +*/ +__attribute__ ((__aligned__(4))) +#ifndef CONFIG_XIP_KERNEL +const +#endif +static const short sdma_code[] = { + 0xc13c, 0x7d70, 0x0800, 0x0970, 0x0111, 0x5111, 0x5ac1, 0x5bc9, + 0x028e, 0xc14e, 0x068a, 0x7c66, 0x5dd9, 0x5ce1, 0x0bff, 0x0311, + 0x1bff, 0x03bc, 0x5bd1, 0x1a5c, 0x6ac3, 0x63c8, 0x0363, 0x7c05, + 0x036f, 0x7d27, 0x0374, 0x7c76, 0x9874, 0xd907, 0x3c06, 0x4c00, + 0x7df7, 0x028f, 0x1a04, 0x6a20, 0x620b, 0x6f20, 0x301f, 0x00aa, + 0x0462, 0x7c04, 0x4a00, 0x7d0b, 0x2001, 0x9837, 0x048a, 0x620b, + 0x2201, 0x1c01, 0x1801, 0x02dc, 0x7d02, 0x301f, 0x00aa, 0x048f, + 0x1c04, 0x6c04, 0x0488, 0x3c1f, 0x6c2b, 0x0045, 0x028e, 0x1a5c, + 0x9818, 0x058f, 0x1d0c, 0x6d20, 0x650b, 0x007d, 0x7c01, 0x1d08, + 0x007c, 0x7c01, 0x1d04, 0x6d20, 0x650b, 0x0488, 0x3c1f, 0x0417, + 0x0417, 0x0417, 0x0417, 0x059c, 0x6d20, 0x028e, 0x1a34, 0x6ad7, + 0x0488, 0x0804, 0x7802, 0x650b, 0x6dc8, 0x008c, 0x1a28, 0x6ad7, + 0x63c8, 0x034c, 0x6bc8, 0x54d1, 0x4c00, 0x7d06, 0x0065, 0x7c02, + 0x0101, 0x0025, 0x0400, 0x9814, 0x52c1, 0x53c9, 0x54e1, 0x0453, + 0xc159, 0x7d95, 0x0200, 0x9800, 0x55d9, 0x6d04, 0x54d1, 0x058a, + 0x2508, 0x6dc7, 0x0373, 0x7c03, 0x65c8, 0x6d0b, 0x2408, 0x0372, + 0x7c04, 0x65c8, 0x6d0b, 0x2408, 0x9889, 0x6cce, 0x65c8, 0x6d0a, + 0x2404, 0x6d28, 0x6504, 0x5dd9, 0x5cd1, 0x6ad7, 0x6ae3, 0x63c8, + 0x0334, 0x6bc8, 0x0370, 0x7cad, 0x0c60, 0x0411, 0x04bb, 0x4c00, + 0x7da8, 0x0410, 0x1c30, 0x0410, 0x04bb, 0x046d, 0x7d0a, 0x047d, + 0x7c03, 0x047c, 0x7c01, 0x9841, 0x003b, 0x003a, 0x0039, 0x0058, + 0x98b8, 0x047d, 0x7d03, 0x047c, 0x7d01, 0x9841, 0x005b, 0xd8fc, + 0x1d18, 0x6d20, 0x650b, 0x0510, 0x003a, 0x0039, 0x0038, 0x00ad, + 0xd907, 0x0c30, 0x0410, 0x04bb, 0x003c, 0x003d, 0x00ac, 0xd8fc, + 0x007b, 0x7c04, 0x003d, 0x003c, 0x1d0c, 0x98d9, 0x048f, 0x1c14, + 0x6c20, 0x640b, 0x4401, 0x7d04, 0x005d, 0x005c, 0x1d0c, 0x98d9, + 0x0310, 0x3b30, 0x4b30, 0x7d01, 0x1b10, 0x0310, 0x003d, 0x003c, + 0x00ab, 0x6ad7, 0x63c8, 0x6d20, 0x650b, 0x0560, 0x7d03, 0x005e, + 0xd8f0, 0x9841, 0x003e, 0x0c80, 0x0410, 0x0394, 0xd8f0, 0x640b, + 0x037f, 0x7d02, 0x1a14, 0x98ed, 0x1a0c, 0x6ad7, 0x6cc8, 0x9841, + 0x0c7f, 0x0410, 0x03b4, 0x04b8, 0x03ac, 0x640b, 0x6bc8, 0x028e, + 0x1a04, 0x6ad7, 0x6cc8, 0x0006, 0x058f, 0x1d08, 0x6d20, 0x650b, + 0x007d, 0x7c01, 0x1d38, 0x007c, 0x7c01, 0x1d1c, 0x0006, 0x048b, + 0x042c, 0x0454, 0x042b, 0x6ad7, 0x6cc8, 0x0006, 0x0e70, 0x0611, + 0x5616, 0xc13c, 0x7d2a, 0x5ade, 0x008e, 0xc14e, 0x7c26, 0x5be0, + 0x5ef0, 0x5ce8, 0x0688, 0x08ff, 0x0011, 0x28ff, 0x00bc, 0x53f6, + 0x05df, 0x7d0b, 0x6dc5, 0x03df, 0x7d03, 0x6bd5, 0xd95d, 0x9939, + 0x6b05, 0xc55f, 0x7e27, 0x7f29, 0x9939, 0x6d01, 0x03df, 0x7d05, + 0x6bd5, 0xc589, 0x7e18, 0x7f1a, 0x9939, 0x6b05, 0xc4ff, 0x7e07, + 0x7f06, 0x52de, 0x53e6, 0xc159, 0x7dd7, 0x0200, 0x9911, 0x0007, + 0x6004, 0x680c, 0x53f6, 0x028e, 0x00a3, 0xc256, 0x048b, 0x0498, + 0x0454, 0x068a, 0x9939, 0x0207, 0x680c, 0x6ddf, 0x0107, 0x68ff, + 0x60d0, 0x9942, 0x0207, 0x68ff, 0x6d28, 0x0107, 0x6004, 0x680c, + 0x9942, 0x0007, 0x68ff, 0x60d0, 0x9942, 0x0288, 0x03a5, 0x3b03, + 0x3d03, 0x4d00, 0x7d0a, 0x0804, 0x00a5, 0x00da, 0x7d1a, 0x02a0, + 0x7b01, 0x65d8, 0x7eee, 0x65ff, 0x7eec, 0x0804, 0x02d0, 0x7d11, + 0x4b00, 0x7c0f, 0x008a, 0x3003, 0x6dcf, 0x6bdf, 0x0015, 0x0015, + 0x7b02, 0x65d8, 0x0000, 0x7edd, 0x63ff, 0x7edb, 0x3a03, 0x6dcd, + 0x6bdd, 0x008a, 0x7b02, 0x65d8, 0x0000, 0x7ed3, 0x65ff, 0x7ed1, + 0x0006, 0xc1d9, 0x0b70, 0x0311, 0x5313, 0x58d3, 0x008b, 0x5efb, + 0xc13c, 0x7d2b, 0x5ac0, 0x5bc8, 0xc14e, 0x7c27, 0x6d01, 0x0388, + 0x0dff, 0x0511, 0x1dff, 0x05bc, 0x4d00, 0x7d1a, 0x0e70, 0x0611, + 0x522e, 0x02b9, 0x4a00, 0x7c07, 0x52fe, 0x50d3, 0x02b8, 0x4a00, + 0x7c02, 0x0400, 0x999e, 0x56fb, 0x620b, 0x7e06, 0x5a06, 0x7f06, + 0x0000, 0x2504, 0x7d05, 0x999e, 0x0007, 0x680c, 0x0007, 0x0454, + 0x008b, 0x52c0, 0x53c8, 0xc159, 0x7dd6, 0x0200, 0x9990, 0xc1d9, + 0xc1e3, 0x0800, 0x005f, 0x00ac, 0x58e3, 0x0478, 0x7d5c, 0x0479, + 0x7d01, 0x0515, 0x0515, 0xda38, 0xda57, 0x0479, 0x7d26, 0x54e3, + 0x047f, 0x7d12, 0x50eb, 0x56fb, 0x0015, 0x52db, 0x7806, 0x5402, + 0x5c06, 0x1a01, 0x5402, 0x5c26, 0x1a01, 0x54e3, 0x043f, 0x5ce3, + 0x4d00, 0x7d4e, 0x0479, 0x7d14, 0x047f, 0x7d01, 0xda57, 0x52f3, + 0x6a21, 0x56db, 0x7803, 0x620b, 0x5a06, 0x1e01, 0x7f34, 0x7e33, + 0x6200, 0x5af3, 0x047f, 0x7dde, 0x9a20, 0x54e3, 0x047f, 0x7cda, + 0x54e3, 0x047f, 0x7d01, 0xda57, 0x54eb, 0x0fff, 0x0711, 0x1fff, + 0x56db, 0x52f3, 0x6a21, 0x630b, 0x028b, 0x03bf, 0xda32, 0x5b06, + 0x2401, 0x4c00, 0x7d0b, 0x1e01, 0x038a, 0x03b7, 0x0312, 0x0312, + 0xda32, 0x5b06, 0x1e01, 0x2401, 0x4c00, 0x7ced, 0x0b70, 0x0311, + 0x5313, 0x7f09, 0x7e08, 0x6200, 0x5af3, 0x54e3, 0x047f, 0x7db2, + 0x57db, 0xc1fa, 0x99cd, 0x0007, 0x680c, 0x54e3, 0x0478, 0x7c02, + 0x0800, 0x9a2e, 0x0479, 0x7d01, 0x0517, 0x0517, 0x5deb, 0xc213, + 0xc20a, 0x99c1, 0x0808, 0x7801, 0x0317, 0x0006, 0x020a, 0x0006, + 0x070a, 0xda36, 0x1a05, 0x0215, 0x5adb, 0x0708, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x080c, + 0x00d5, 0x7d01, 0x008d, 0x05a0, 0x4800, 0x7dd2, 0x58eb, 0x0006, + 0xc1d9, 0x0b70, 0x0311, 0x5313, 0x58d3, 0x008b, 0x5efb, 0xc13c, + 0x7d2b, 0x5ac0, 0x5bc8, 0xc14e, 0x7c27, 0x0388, 0x6d05, 0x0dff, + 0x0511, 0x1dff, 0x05bc, 0x4d00, 0x7d1a, 0x0e70, 0x0611, 0x522e, + 0x02b9, 0x4a00, 0x7c07, 0x52fe, 0x50d3, 0x02b8, 0x4a00, 0x7c02, + 0x0400, 0x9a75, 0x56fb, 0x5206, 0x7e08, 0x6a0b, 0x6a28, 0x7f04, + 0x0000, 0x2504, 0x7d04, 0x9a75, 0x680c, 0x0007, 0x0454, 0x008b, + 0x52c0, 0x53c8, 0xc159, 0x7dd6, 0x0200, 0x9a67 +}; +#endif diff --git a/arch/arm/mach-mx37/serial.c b/arch/arm/mach-mx37/serial.c new file mode 100644 index 000000000000..aaa5171eb177 --- /dev/null +++ b/arch/arm/mach-mx37/serial.c @@ -0,0 +1,169 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @file mach-mx37/serial.c + * + * @brief This file contains the UART initiliazation. + * + * @ingroup MSL_MX37 + */ +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/serial.h> +#include <mach/hardware.h> +#include <mach/mxc_uart.h> +#include <mach/spba.h> +#include "serial.h" +#include "board-mx37_3stack.h" + +#if defined(CONFIG_SERIAL_MXC) || defined(CONFIG_SERIAL_MXC_MODULE) + +/*! + * This is an array where each element holds information about a UART port, + * like base address of the UART, interrupt numbers etc. This structure is + * passed to the serial_core.c file. Based on which UART is used, the core file + * passes back the appropriate port structure as an argument to the control + * functions. + */ +static uart_mxc_port mxc_ports[] = { + [0] = { + .port = { + .membase = (void *)IO_ADDRESS(UART1_BASE_ADDR), + .mapbase = UART1_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART1_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 0, + }, + .ints_muxed = UART1_MUX_INTS, + .irqs = {UART1_INT2, UART1_INT3}, + .mode = UART1_MODE, + .ir_mode = UART1_IR, + .enabled = UART1_ENABLED, + .hardware_flow = UART1_HW_FLOW, + .cts_threshold = UART1_UCR4_CTSTL, + .dma_enabled = UART1_DMA_ENABLE, + .dma_rxbuf_size = UART1_DMA_RXBUFSIZE, + .rx_threshold = UART1_UFCR_RXTL, + .tx_threshold = UART1_UFCR_TXTL, + .shared = UART1_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART1_TX, + .dma_rx_id = MXC_DMA_UART1_RX, + .rxd_mux = MXC_UART_RXDMUX, + }, + [1] = { + .port = { + .membase = (void *)IO_ADDRESS(UART2_BASE_ADDR), + .mapbase = UART2_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART2_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 1, + }, + .ints_muxed = UART2_MUX_INTS, + .irqs = {UART2_INT2, UART2_INT3}, + .mode = UART2_MODE, + .ir_mode = UART2_IR, + .enabled = UART2_ENABLED, + .hardware_flow = UART2_HW_FLOW, + .cts_threshold = UART2_UCR4_CTSTL, + .dma_enabled = UART2_DMA_ENABLE, + .dma_rxbuf_size = UART2_DMA_RXBUFSIZE, + .rx_threshold = UART2_UFCR_RXTL, + .tx_threshold = UART2_UFCR_TXTL, + .shared = UART2_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART2_TX, + .dma_rx_id = MXC_DMA_UART2_RX, + .rxd_mux = MXC_UART_RXDMUX, + }, + [2] = { + .port = { + .membase = (void *)IO_ADDRESS(UART3_BASE_ADDR), + .mapbase = UART3_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART3_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 2, + }, + .ints_muxed = UART3_MUX_INTS, + .irqs = {UART3_INT2, UART3_INT3}, + .mode = UART3_MODE, + .ir_mode = UART3_IR, + .enabled = UART3_ENABLED, + .hardware_flow = UART3_HW_FLOW, + .cts_threshold = UART3_UCR4_CTSTL, + .dma_enabled = UART3_DMA_ENABLE, + .dma_rxbuf_size = UART3_DMA_RXBUFSIZE, + .rx_threshold = UART3_UFCR_RXTL, + .tx_threshold = UART3_UFCR_TXTL, + .shared = UART3_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART3_TX, + .dma_rx_id = MXC_DMA_UART3_RX, + .rxd_mux = MXC_UART_RXDMUX, + }, +}; + +static struct platform_device mxc_uart_device1 = { + .name = "mxcintuart", + .id = 0, + .dev = { + .platform_data = &mxc_ports[0], + }, +}; + +static struct platform_device mxc_uart_device2 = { + .name = "mxcintuart", + .id = 1, + .dev = { + .platform_data = &mxc_ports[1], + }, +}; + +static struct platform_device mxc_uart_device3 = { + .name = "mxcintuart", + .id = 2, + .dev = { + .platform_data = &mxc_ports[2], + }, +}; + +static int __init mxc_init_uart(void) +{ + /* Register all the MXC UART platform device structures */ + platform_device_register(&mxc_uart_device1); + platform_device_register(&mxc_uart_device2); + + /* Grab ownership of shared UARTs 3 and 4, only when enabled */ +#if UART3_ENABLED == 1 +#if UART3_DMA_ENABLE == 1 + spba_take_ownership(UART3_SHARED_PERI, (SPBA_MASTER_A | SPBA_MASTER_C)); +#else + spba_take_ownership(UART3_SHARED_PERI, SPBA_MASTER_A); +#endif /* UART3_DMA_ENABLE */ + platform_device_register(&mxc_uart_device3); +#endif /* UART3_ENABLED */ + + return 0; +} + +#else +static int __init mxc_init_uart(void) +{ + return 0; +} +#endif + +arch_initcall(mxc_init_uart); diff --git a/arch/arm/mach-mx37/serial.h b/arch/arm/mach-mx37/serial.h new file mode 100644 index 000000000000..b8b8403e2698 --- /dev/null +++ b/arch/arm/mach-mx37/serial.h @@ -0,0 +1,127 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ARCH_ARM_MACH_MX37_SERIAL_H__ +#define __ARCH_ARM_MACH_MX37_SERIAL_H__ + +#include <mach/mxc_uart.h> + +/* UART 1 configuration */ +/*! + * This option allows to choose either an interrupt-driven software controlled + * hardware flow control (set this option to 0) or hardware-driven hardware + * flow control (set this option to 1). + */ +/* UART used as wakeup source */ +#define UART1_HW_FLOW 0 +/*! + * This specifies the threshold at which the CTS pin is deasserted by the + * RXFIFO. Set this value in Decimal to anything from 0 to 32 for + * hardware-driven hardware flow control. Read the HW spec while specifying + * this value. When using interrupt-driven software controlled hardware + * flow control set this option to -1. + */ +#define UART1_UCR4_CTSTL 16 +/*! + * This is option to enable (set this option to 1) or disable DMA data transfer + */ +#define UART1_DMA_ENABLE 0 +/*! + * Specify the size of the DMA receive buffer. The minimum buffer size is 512 + * bytes. The buffer size should be a multiple of 256. + */ +#define UART1_DMA_RXBUFSIZE 1024 +/*! + * Specify the MXC UART's Receive Trigger Level. This controls the threshold at + * which a maskable interrupt is generated by the RxFIFO. Set this value in + * Decimal to anything from 0 to 32. Read the HW spec while specifying this + * value. + */ +#define UART1_UFCR_RXTL 16 +/*! + * Specify the MXC UART's Transmit Trigger Level. This controls the threshold at + * which a maskable interrupt is generated by the TxFIFO. Set this value in + * Decimal to anything from 0 to 32. Read the HW spec while specifying this + * value. + */ +#define UART1_UFCR_TXTL 16 +/* UART 2 configuration */ +#define UART2_HW_FLOW 0 +#define UART2_UCR4_CTSTL -1 +#define UART2_DMA_ENABLE 0 +#define UART2_DMA_RXBUFSIZE 512 +#define UART2_UFCR_RXTL 16 +#define UART2_UFCR_TXTL 16 +/* UART 3 configuration */ +#define UART3_HW_FLOW 1 +#define UART3_UCR4_CTSTL 16 +#define UART3_DMA_ENABLE 0 +#define UART3_DMA_RXBUFSIZE 1024 +#define UART3_UFCR_RXTL 16 +#define UART3_UFCR_TXTL 16 +/* + * UART Chip level Configuration that a user may not have to edit. These + * configuration vary depending on how the UART module is integrated with + * the ARM core + */ +/* + * Is the MUXED interrupt output sent to the ARM core + */ +#define INTS_NOTMUXED 0 +#define INTS_MUXED 1 +/* UART 1 configuration */ +/*! + * This define specifies whether the muxed ANDed interrupt line or the + * individual interrupts from the UART port is integrated with the ARM core. + * There exists a define like this for each UART port. Valid values that can + * be used are \b INTS_NOTMUXED or \b INTS_MUXED. + */ +#define UART1_MUX_INTS INTS_MUXED +/*! + * This define specifies the transmitter interrupt number or the interrupt + * number of the ANDed interrupt in case the interrupts are muxed. There exists + * a define like this for each UART port. + */ +#define UART1_INT1 MXC_INT_UART1 +/*! + * This define specifies the receiver interrupt number. If the interrupts of + * the UART are muxed, then we specify here a dummy value -1. There exists a + * define like this for each UART port. + */ +#define UART1_INT2 -1 +/*! + * This specifies the master interrupt number. If the interrupts of the UART + * are muxed, then we specify here a dummy value of -1. There exists a define + * like this for each UART port. + */ +#define UART1_INT3 -1 +/*! + * This specifies if the UART is a shared peripheral. It holds the shared + * peripheral number if it is shared or -1 if it is not shared. There exists + * a define like this for each UART port. + */ +#define UART1_SHARED_PERI -1 +/* UART 2 configuration */ +#define UART2_MUX_INTS INTS_MUXED +#define UART2_INT1 MXC_INT_UART2 +#define UART2_INT2 -1 +#define UART2_INT3 -1 +#define UART2_SHARED_PERI -1 +/* UART 3 configuration */ +#define UART3_MUX_INTS INTS_MUXED +#define UART3_INT1 MXC_INT_UART3 +#define UART3_INT2 -1 +#define UART3_INT3 -1 +#define UART3_SHARED_PERI SPBA_UART3 + +#endif /* __ARCH_ARM_MACH_MX37_SERIAL_H__ */ diff --git a/arch/arm/mach-mx37/system.c b/arch/arm/mach-mx37/system.c new file mode 100644 index 000000000000..d40549b29ddc --- /dev/null +++ b/arch/arm/mach-mx37/system.c @@ -0,0 +1,192 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#define DEBUG +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <mach/hardware.h> +#include <asm/proc-fns.h> +#include <asm/system.h> +#include <asm/cacheflush.h> +#include <mach/clock.h> +#include "crm_regs.h" + +/*! + * @defgroup MSL_MX37 i.MX37 Machine Specific Layer (MSL) + */ + +/*! + * @file mach-mx37/system.c + * @brief This file contains idle and reset functions. + * + * @ingroup MSL_MX37 + */ + +extern int mxc_jtag_enabled; +extern int low_bus_freq_mode; + +static struct clk *pll1_main; +static struct clk *pll1_sw_clk; +static struct clk *lp_apm_clk; +static struct clk *gpc_dvfs_clk; + +/* set cpu low power mode before WFI instruction */ +void mxc_cpu_lp_set(enum mxc_cpu_pwr_mode mode) +{ + u32 plat_lpc, gpc_pgr, arm_srpgcr, empgcr0, empgcr1, ccm_clpcr; + /* always allow platform to issue a deep sleep mode request */ + plat_lpc = __raw_readl(MXC_ARM1176_PLAT_LPC) & + ~(MXC_ARM1176_PLAT_LPC_DSM); + + ccm_clpcr = __raw_readl(MXC_CCM_CLPCR) & ~(MXC_CCM_CLPCR_LPM_MASK); + gpc_pgr = __raw_readl(MXC_GPC_PGR) & ~(MXC_GPC_PGR_ARMPG_MASK); + arm_srpgcr = __raw_readl(MXC_SRPGC_ARM_SRPGCR) & ~(MXC_SRPGCR_PCR); + empgcr0 = __raw_readl(MXC_EMPGC0_ARM_EMPGCR) & ~(MXC_EMPGCR_PCR); + empgcr1 = __raw_readl(MXC_EMPGC1_ARM_EMPGCR) & ~(MXC_EMPGCR_PCR); + + switch (mode) { + case WAIT_CLOCKED: + break; + case WAIT_UNCLOCKED: + ccm_clpcr |= (0x1 << MXC_CCM_CLPCR_LPM_OFFSET); + break; + case WAIT_UNCLOCKED_POWER_OFF: + case STOP_POWER_OFF: + plat_lpc |= MXC_ARM1176_PLAT_LPC_DSM; + if (mode == WAIT_UNCLOCKED_POWER_OFF) + ccm_clpcr |= (0x1 << MXC_CCM_CLPCR_LPM_OFFSET); + else { + ccm_clpcr |= (0x2 << MXC_CCM_CLPCR_LPM_OFFSET); + ccm_clpcr |= (0x3 << MXC_CCM_CLPCR_STBY_COUNT_OFFSET); + ccm_clpcr |= MXC_CCM_CLPCR_VSTBY; + ccm_clpcr |= MXC_CCM_CLPCR_SBYOS; + } + + gpc_pgr |= (0x1 << MXC_GPC_PGR_ARMPG_OFFSET); + arm_srpgcr |= MXC_SRPGCR_PCR; + empgcr0 |= MXC_EMPGCR_PCR; + empgcr1 |= MXC_EMPGCR_PCR; + + if (tzic_enable_wake(1) != 0) + return; + break; + case STOP_POWER_ON: + ccm_clpcr |= (0x2 << MXC_CCM_CLPCR_LPM_OFFSET); + break; + default: + printk(KERN_WARNING "UNKNOWN cpu power mode: %d\n", mode); + return; + } + + __raw_writel(plat_lpc, MXC_ARM1176_PLAT_LPC); + __raw_writel(ccm_clpcr, MXC_CCM_CLPCR); + __raw_writel(gpc_pgr, MXC_GPC_PGR); + __raw_writel(arm_srpgcr, MXC_SRPGC_ARM_SRPGCR); + if ((mxc_cpu_is_rev(CHIP_REV_1_0)) != 1) + __raw_writel(empgcr0, MXC_EMPGC0_ARM_EMPGCR); + + __raw_writel(empgcr1, MXC_EMPGC1_ARM_EMPGCR); + + flush_cache_all(); + + if (gpc_dvfs_clk == NULL) + gpc_dvfs_clk = clk_get(NULL, "gpc_dvfs_clk"); + + /* gpc clock is needed for SRPG */ + clk_enable(gpc_dvfs_clk); + if (low_bus_freq_mode) { + if (pll1_sw_clk == NULL) + pll1_sw_clk = clk_get(NULL, "pll1_sw_clk"); + if (lp_apm_clk == NULL) + lp_apm_clk = clk_get(NULL, "lp_apm"); + if (pll1_main == NULL) + pll1_main = clk_get(NULL, "pll1_main_clk"); + + /* Move the ARM to run off the 24MHz clock. Shutdown the PLL1 */ + /* Change the source of pll1_sw_clk to be the step_clk */ + clk_set_parent(pll1_sw_clk, lp_apm_clk); + } +} + +void mxc_pg_enable(struct platform_device *pdev) +{ + if (pdev == NULL) + return; + + if (strcmp(pdev->name, "mxc_ipu") == 0) { + __raw_writel(MXC_PGCR_PCR, MXC_PGC_IPU_PGCR); + __raw_writel(MXC_PGSR_PSR, MXC_PGC_IPU_PGSR); + } else if (strcmp(pdev->name, "mxc_vpu") == 0) { + __raw_writel(MXC_PGCR_PCR, MXC_PGC_VPU_PGCR); + __raw_writel(MXC_PGSR_PSR, MXC_PGC_VPU_PGSR); + } +} + +EXPORT_SYMBOL(mxc_pg_enable); + +void mxc_pg_disable(struct platform_device *pdev) +{ + if (pdev == NULL) + return; + + if (strcmp(pdev->name, "mxc_ipu") == 0) { + __raw_writel(0x0, MXC_PGC_IPU_PGCR); + if (__raw_readl(MXC_PGC_IPU_PGSR) & MXC_PGSR_PSR) + dev_dbg(&pdev->dev, "power gating successful\n"); + __raw_writel(MXC_PGSR_PSR, MXC_PGC_IPU_PGSR); + } else if (strcmp(pdev->name, "mxc_vpu") == 0) { + __raw_writel(0x0, MXC_PGC_VPU_PGCR); + if (__raw_readl(MXC_PGC_VPU_PGSR) & MXC_PGSR_PSR) + dev_dbg(&pdev->dev, "power gating successful\n"); + __raw_writel(MXC_PGSR_PSR, MXC_PGC_VPU_PGSR); + } +} + +EXPORT_SYMBOL(mxc_pg_disable); + +/* To change the idle power mode, need to set arch_idle_mode to a different + * power mode as in enum mxc_cpu_pwr_mode. + * May allow dynamically changing the idle mode. + */ +static int arch_idle_mode = WAIT_UNCLOCKED_POWER_OFF; + +/*! + * This function puts the CPU into idle mode. It is called by default_idle() + * in process.c file. + */ +void arch_idle(void) +{ + if (likely(!mxc_jtag_enabled)) { + mxc_cpu_lp_set(arch_idle_mode); + cpu_do_idle(); + /* gpc clock is needed for SRPG */ + clk_disable(gpc_dvfs_clk); + if (low_bus_freq_mode) { + /* Move ARM back to PLL from step clk. */ + /* Move the PLL1 back to the pll1_main_clk */ + clk_set_parent(pll1_sw_clk, pll1_main); + } + } +} + +/* + * This function resets the system. It is called by machine_restart(). + * + * @param mode indicates different kinds of resets + */ +void arch_reset(char mode) +{ + /* Assert SRS signal */ + mxc_wd_reset(); +} diff --git a/arch/arm/mach-mx37/usb.h b/arch/arm/mach-mx37/usb.h new file mode 100644 index 000000000000..3542866ada0b --- /dev/null +++ b/arch/arm/mach-mx37/usb.h @@ -0,0 +1,112 @@ +/* + * Copyright 2005-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +extern int usbotg_init(struct platform_device *pdev); +extern void usbotg_uninit(struct fsl_usb2_platform_data *pdata); +extern int gpio_usbotg_hs_active(void); +extern void gpio_usbotg_hs_inactive(void); +extern struct platform_device *host_pdev_register(struct resource *res, + int n_res, struct fsl_usb2_platform_data *config); + +extern int gpio_usbotg_utmi_active(void); +extern void gpio_usbotg_utmi_inactive(void); +static int usbotg_init_ext(struct platform_device *pdev); +static void usbotg_uninit_ext(struct fsl_usb2_platform_data *pdata); + +/* + * Determine which platform_data struct to use for the DR controller, + * based on which transceiver is configured. + * PDATA is a pointer to it. + */ +#if defined(CONFIG_ISP1301_MXC) +static struct fsl_usb2_platform_data __maybe_unused dr_1301_config; +#define PDATA (&dr_1301_config) +#elif defined(CONFIG_MC13783_MXC) +static struct fsl_usb2_platform_data __maybe_unused dr_13783_config; +#define PDATA (&dr_13783_config) +#elif defined(CONFIG_UTMI_MXC) +static struct fsl_usb2_platform_data __maybe_unused dr_utmi_config; +#define PDATA (&dr_utmi_config) +#endif + + +/* + * Used to set pdata->operating_mode before registering the platform_device. + * If OTG is configured, the controller operates in OTG mode, + * otherwise it's either host or device. + */ +#ifdef CONFIG_USB_OTG +#define DR_UDC_MODE FSL_USB2_DR_OTG +#define DR_HOST_MODE FSL_USB2_DR_OTG +#else +#define DR_UDC_MODE FSL_USB2_DR_DEVICE +#define DR_HOST_MODE FSL_USB2_DR_HOST +#endif + + +#ifdef CONFIG_USB_EHCI_ARC_OTG +static inline void dr_register_host(struct resource *r, int rs) +{ + PDATA->operating_mode = DR_HOST_MODE; + host_pdev_register(r, rs, PDATA); +} +#else +static inline void dr_register_host(struct resource *r, int rs) +{ +} +#endif + +#ifdef CONFIG_USB_GADGET_ARC +static struct platform_device dr_udc_device; + +static inline void dr_register_udc(void) +{ + PDATA->operating_mode = DR_UDC_MODE; + dr_udc_device.dev.platform_data = PDATA; + + if (platform_device_register(&dr_udc_device)) + printk(KERN_ERR "usb: can't register DR gadget\n"); + else + printk(KERN_INFO "usb: DR gadget (%s) registered\n", + PDATA->transceiver); +} +#else +static inline void dr_register_udc(void) +{ +} +#endif + +#ifdef CONFIG_USB_OTG +static struct platform_device dr_otg_device; + +/* + * set the proper operating_mode and + * platform_data pointer, then register the + * device. + */ +static inline void dr_register_otg(void) +{ + PDATA->operating_mode = FSL_USB2_DR_OTG; + dr_otg_device.dev.platform_data = PDATA; + + if (platform_device_register(&dr_otg_device)) + printk(KERN_ERR "usb: can't register otg device\n"); + else + printk(KERN_INFO "usb: DR OTG registered\n"); +} +#else +static inline void dr_register_otg(void) +{ +} +#endif diff --git a/arch/arm/mach-mx37/usb_dr.c b/arch/arm/mach-mx37/usb_dr.c new file mode 100644 index 000000000000..585553d9a03e --- /dev/null +++ b/arch/arm/mach-mx37/usb_dr.c @@ -0,0 +1,150 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <mach/hardware.h> +#include <mach/arc_otg.h> +#include "usb.h" + +static void usbotg_pm_clock(bool on); +/* + * platform data structs + * - Which one to use is determined by CONFIG options in usb.h + * - operating_mode plugged at run time + */ +static struct fsl_usb2_platform_data __maybe_unused dr_utmi_config = { + .name = "DR", + .platform_init = usbotg_init_ext, + .platform_uninit = usbotg_uninit_ext, + .phy_mode = FSL_USB2_PHY_UTMI_WIDE, + .power_budget = 500, /* 500 mA max power */ + .gpio_usb_active = gpio_usbotg_hs_active, + .gpio_usb_inactive = gpio_usbotg_hs_inactive, + .usb_clock_for_pm = usbotg_pm_clock, + .transceiver = "utmi", +}; + + +/* + * resources + */ +static struct resource otg_resources[] = { + [0] = { + .start = (u32)(OTG_BASE_ADDR), + .end = (u32)(OTG_BASE_ADDR + 0x620), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_USB_OTG, + .flags = IORESOURCE_IRQ, + }, +}; + + +static u64 dr_udc_dmamask = ~(u32) 0; +static void dr_udc_release(struct device *dev) +{ +} + +/* + * platform device structs + * dev.platform_data field plugged at run time + */ +static struct platform_device dr_udc_device = { + .name = "fsl-usb2-udc", + .id = -1, + .dev = { + .release = dr_udc_release, + .dma_mask = &dr_udc_dmamask, + .coherent_dma_mask = 0xffffffff, + }, + .resource = otg_resources, + .num_resources = ARRAY_SIZE(otg_resources), +}; + +static u64 dr_otg_dmamask = ~(u32) 0; +static void dr_otg_release(struct device *dev) +{} + +static struct platform_device __maybe_unused dr_otg_device = { + .name = "fsl-usb2-otg", + .id = -1, + .dev = { + .release = dr_otg_release, + .dma_mask = &dr_otg_dmamask, + .coherent_dma_mask = 0xffffffff, + }, + .resource = otg_resources, + .num_resources = ARRAY_SIZE(otg_resources), +}; + +/* Notes: configure USB clock*/ +static int usbotg_init_ext(struct platform_device *pdev) +{ + struct clk *usb_clk, *usboh2_clk; + int ret; + + usboh2_clk = clk_get(NULL, "usboh2_clk"); + clk_enable(usboh2_clk); + + usb_clk = clk_get(NULL, "usb_phy_clk"); + clk_enable(usb_clk); + clk_put(usb_clk); + + ret = usbotg_init(pdev); + + /* this clock is no use after set portsc PTS bit */ + clk_disable(usboh2_clk); + clk_put(usboh2_clk); + + return ret; +} + +static void usbotg_pm_clock(bool on) +{ + struct clk *usb_clk; + + usb_clk = clk_get(NULL, "usb_phy_clk"); + /* close and open usb phy clock for suspend and resume */ + if (on) { + clk_enable(usb_clk); + } else { + clk_disable(usb_clk); + } + clk_put(usb_clk); +} + +static void usbotg_uninit_ext(struct fsl_usb2_platform_data *pdata) +{ + usbotg_pm_clock(false); + usbotg_uninit(pdata); +} + + + +static int __init usb_dr_init(void) +{ + pr_debug("%s: \n", __func__); + + dr_register_otg(); + dr_register_host(otg_resources, ARRAY_SIZE(otg_resources)); + dr_register_udc(); + + return 0; +} + +module_init(usb_dr_init); diff --git a/arch/arm/mach-mx51/Kconfig b/arch/arm/mach-mx51/Kconfig new file mode 100644 index 000000000000..f6a0598dcf39 --- /dev/null +++ b/arch/arm/mach-mx51/Kconfig @@ -0,0 +1,94 @@ +menu "MX51 Options" + depends on ARCH_MX51 + +config FORCE_MAX_ZONEORDER + int "MAX_ORDER" + default "13" + +config MX51_OPTIONS + bool + default y + select CPU_V7 + select USB_ARCH_HAS_EHCI + select MXC_TZIC + +config MACH_MX51_3DS + bool "Support MX51 3-Stack platforms" + default y + help + Include support for MX51 3-Stack platform. This includes specific + configurations for the board and its peripherals. + +config MACH_MX51_BABBAGE + bool "Support MX51 BABBAGE platforms" + help + Include support for MX51 Babbage platform. This includes specific + configurations for the board and its peripherals. + +config MXC_SDMA_API + bool "Use SDMA API" + default y + help + This selects the Freescale MXC SDMA API. + If unsure, say N. + +config ARCH_MXC_HAS_NFC_V3 + bool "MXC NFC Hardware Version 3" + depends on ARCH_MX51 + default y + help + This selects the Freescale MXC Nand Flash Controller Hardware Version 3 + If unsure, say N. + +config ARCH_MXC_HAS_NFC_V3_2 + bool "MXC NFC Hardware Version 3.2" + depends on ARCH_MXC_HAS_NFC_V3 + default y + help + This selects the Freescale MXC Nand Flash Controller Hardware Version 3.1 + If unsure, say N. + +menu "SDMA options" + depends on MXC_SDMA_API + +config SDMA_IRAM + bool "Use Internal RAM for SDMA transfer" + default n + help + Support Internal RAM as SDMA buffer or control structures + +config SDMA_IRAM_SIZE + hex "Reserved bytes of IRAM for SDMA (0x800-0x1000)" + range 0x800 0x1000 + depends on SDMA_IRAM + default "0x1000" + help + Set the size of IRAM for SDMA. It must be a multiple of 512bytes. +endmenu + +menu "Device options" + +config I2C_MXC_SELECT1 + bool "Enable I2C1 module" + default y + depends on I2C_MXC + help + Enable MX51 I2C1 module. + +config I2C_MXC_SELECT2 + bool "Enable I2C2 module" + default n + depends on I2C_MXC + help + Enable MX51 I2C2 module. + +config I2C_MXC_SELECT3 + bool "Enable I2C3 module" + default n + depends on I2C_MXC + help + Enable MX51 I2C3 module. + +endmenu + +endmenu diff --git a/arch/arm/mach-mx51/Makefile b/arch/arm/mach-mx51/Makefile new file mode 100644 index 000000000000..3ac8416600b7 --- /dev/null +++ b/arch/arm/mach-mx51/Makefile @@ -0,0 +1,21 @@ +# +# Makefile for the linux kernel. +# + +# Object file lists. + + +obj-y := system.o iomux.o cpu.o mm.o clock.o devices.o serial.o dma.o lpmodes.o pm.o bus_freq.o + +obj-y += dummy_gpio.o + +obj-$(CONFIG_CPU_V7) += wfi.o suspend.o +obj-$(CONFIG_MACH_MX51_3DS) += mx51_3stack.o mx51_3stack_gpio.o mx51_3stack_pmic_mc13892.o +obj-$(CONFIG_MACH_MX51_BABBAGE) += mx51_babbage.o mx51_babbage_gpio.o mx51_babbage_pmic_mc13892.o + +obj-$(CONFIG_USB_EHCI_ARC_H1) += usb_h1.o +obj-$(CONFIG_USB_EHCI_ARC_H2) += usb_h2.o + +ifneq ($(strip $(CONFIG_USB_GADGET_ARC) $(CONFIG_USB_EHCI_ARC_OTG)),) + obj-y += usb_dr.o +endif diff --git a/arch/arm/mach-mx51/Makefile.boot b/arch/arm/mach-mx51/Makefile.boot new file mode 100644 index 000000000000..9939a19d99a1 --- /dev/null +++ b/arch/arm/mach-mx51/Makefile.boot @@ -0,0 +1,3 @@ + zreladdr-y := 0x90008000 +params_phys-y := 0x90000100 +initrd_phys-y := 0x90800000 diff --git a/arch/arm/mach-mx51/board-mx51_3stack.h b/arch/arm/mach-mx51/board-mx51_3stack.h new file mode 100644 index 000000000000..bd5aface4a92 --- /dev/null +++ b/arch/arm/mach-mx51/board-mx51_3stack.h @@ -0,0 +1,125 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ASM_ARCH_MXC_BOARD_MX51_3STACK_H__ +#define __ASM_ARCH_MXC_BOARD_MX51_3STACK_H__ + +/*! + * @defgroup BRDCFG_MX51 Board Configuration Options + * @ingroup MSL_MX51 + */ + +/*! + * @file mach-mx51/board-mx51_3stack.h + * + * @brief This file contains all the board level configuration options. + * + * It currently hold the options defined for MX51 3Stack Platform. + * + * @ingroup BRDCFG_MX51 + */ + +/* + * Include Files + */ +#include <mach/mxc_uart.h> + +/*! + * @name MXC UART board level configurations + */ +/*! @{ */ +/*! + * Specifies if the Irda transmit path is inverting + */ +#define MXC_IRDA_TX_INV 0 +/*! + * Specifies if the Irda receive path is inverting + */ +#define MXC_IRDA_RX_INV 0 + +/* UART 1 configuration */ +/*! + * This define specifies if the UART port is configured to be in DTE or + * DCE mode. There exists a define like this for each UART port. Valid + * values that can be used are \b MODE_DTE or \b MODE_DCE. + */ +#define UART1_MODE MODE_DCE +/*! + * This define specifies if the UART is to be used for IRDA. There exists a + * define like this for each UART port. Valid values that can be used are + * \b IRDA or \b NO_IRDA. + */ +#define UART1_IR NO_IRDA +/*! + * This define is used to enable or disable a particular UART port. If + * disabled, the UART will not be registered in the file system and the user + * will not be able to access it. There exists a define like this for each UART + * port. Specify a value of 1 to enable the UART and 0 to disable it. + */ +#define UART1_ENABLED 1 +/*! @} */ +/* UART 2 configuration */ +#define UART2_MODE MODE_DCE +#define UART2_IR NO_IRDA +#define UART2_ENABLED 1 +/* UART 3 configuration */ +#define UART3_MODE MODE_DCE +#define UART3_IR NO_IRDA +#define UART3_ENABLED 1 + +#define MXC_LL_UART_PADDR UART1_BASE_ADDR +#define MXC_LL_UART_VADDR AIPS1_IO_ADDRESS(UART1_BASE_ADDR) + +#define DEBUG_BOARD_BASE_ADDRESS(n) (n) +/* LAN9217 ethernet base address */ +#define LAN9217_BASE_ADDR(n) (DEBUG_BOARD_BASE_ADDRESS(n)) +/* External UART */ +#define UARTA_BASE_ADDR(n) (DEBUG_BOARD_BASE_ADDRESS(n) + 0x8000) +#define UARTB_BASE_ADDR(n) (DEBUG_BOARD_BASE_ADDRESS(n) + 0x10000) + +#define BOARD_IO_ADDR(n) (DEBUG_BOARD_BASE_ADDRESS(n) + 0x20000) +/* LED switchs */ +#define LED_SWITCH_REG 0x00 +/* buttons */ +#define SWITCH_BUTTONS_REG 0x08 +/* status, interrupt */ +#define INTR_STATUS_REG 0x10 +#define INTR_MASK_REG 0x38 +#define INTR_RESET_REG 0x20 +/* magic word for debug CPLD */ +#define MAGIC_NUMBER1_REG 0x40 +#define MAGIC_NUMBER2_REG 0x48 +/* CPLD code version */ +#define CPLD_CODE_VER_REG 0x50 +/* magic word for debug CPLD */ +#define MAGIC_NUMBER3_REG 0x58 +/* module reset register*/ +#define MODULE_RESET_REG 0x60 +/* CPU ID and Personality ID */ +#define MCU_BOARD_ID_REG 0x68 + +/* interrupts like external uart , external ethernet etc*/ +#define EXPIO_PARENT_INT IOMUX_TO_IRQ(MX51_PIN_GPIO1_6) + +#define EXPIO_INT_ENET (MXC_BOARD_IRQ_START + 0) +#define EXPIO_INT_XUART_A (MXC_BOARD_IRQ_START + 1) +#define EXPIO_INT_XUART_B (MXC_BOARD_IRQ_START + 2) +#define EXPIO_INT_BUTTON_A (MXC_BOARD_IRQ_START + 3) +#define EXPIO_INT_BUTTON_B (MXC_BOARD_IRQ_START + 4) + +/*! This is System IRQ used by LAN9217 */ +#define LAN9217_IRQ EXPIO_INT_ENET + +extern int __init mx51_3stack_init_mc13892(void); + +#endif /* __ASM_ARCH_MXC_BOARD_MX51_3STACK_H__ */ diff --git a/arch/arm/mach-mx51/board-mx51_babbage.h b/arch/arm/mach-mx51/board-mx51_babbage.h new file mode 100644 index 000000000000..1e6c7c8e849e --- /dev/null +++ b/arch/arm/mach-mx51/board-mx51_babbage.h @@ -0,0 +1,85 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ASM_ARCH_MXC_BOARD_MX51_BABBAGE_H__ +#define __ASM_ARCH_MXC_BOARD_MX51_BABBAGE_H__ + +/*! + * @defgroup BRDCFG_MX51 Board Configuration Options + * @ingroup MSL_MX51 + */ + +/*! + * @file mach-mx51/board-mx51_babbage.h + * + * @brief This file contains all the board level configuration options. + * + * It currently hold the options defined for MX51 Babbage Platform. + * + * @ingroup BRDCFG_MX51 + */ + +/* + * Include Files + */ +#include <mach/mxc_uart.h> + +/*! + * @name MXC UART board level configurations + */ +/*! @{ */ +/*! + * Specifies if the Irda transmit path is inverting + */ +#define MXC_IRDA_TX_INV 0 +/*! + * Specifies if the Irda receive path is inverting + */ +#define MXC_IRDA_RX_INV 0 + +/* UART 1 configuration */ +/*! + * This define specifies if the UART port is configured to be in DTE or + * DCE mode. There exists a define like this for each UART port. Valid + * values that can be used are \b MODE_DTE or \b MODE_DCE. + */ +#define UART1_MODE MODE_DCE +/*! + * This define specifies if the UART is to be used for IRDA. There exists a + * define like this for each UART port. Valid values that can be used are + * \b IRDA or \b NO_IRDA. + */ +#define UART1_IR NO_IRDA +/*! + * This define is used to enable or disable a particular UART port. If + * disabled, the UART will not be registered in the file system and the user + * will not be able to access it. There exists a define like this for each UART + * port. Specify a value of 1 to enable the UART and 0 to disable it. + */ +#define UART1_ENABLED 1 +/*! @} */ +/* UART 2 configuration */ +#define UART2_MODE MODE_DCE +#define UART2_IR IRDA +#define UART2_ENABLED 1 +/* UART 3 configuration */ +#define UART3_MODE MODE_DTE +#define UART3_IR NO_IRDA +#define UART3_ENABLED 1 + +#define MXC_LL_UART_PADDR UART1_BASE_ADDR +#define MXC_LL_UART_VADDR AIPS1_IO_ADDRESS(UART1_BASE_ADDR) + +extern int __init mx51_babbage_init_mc13892(void); + +#endif /* __ASM_ARCH_MXC_BOARD_MX51_BABBAGE_H__ */ diff --git a/arch/arm/mach-mx51/bus_freq.c b/arch/arm/mach-mx51/bus_freq.c new file mode 100644 index 000000000000..637aa0247c97 --- /dev/null +++ b/arch/arm/mach-mx51/bus_freq.c @@ -0,0 +1,392 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file bus_freq.c + * + * @brief A common API for the Freescale Semiconductor i.MXC CPUfreq module + * and DVFS CORE module. + * + * The APIs are for setting bus frequency to low or high. + * + * @ingroup PM + */ + +#include <linux/proc_fs.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <mach/clock.h> +#include <mach/hardware.h> +#include <mach/mxc_dvfs.h> +#include <linux/regulator/consumer.h> + +#define LP_NORMAL_CLK 133000000 +#define LP_MED_CLK 83125000 +#define LP_APM_CLK 24000000 +#define NAND_LP_APM_CLK 12000000 +#define DDR_LOW_FREQ_CLK 133000000 +#define DDR_NORMAL_CLK 200000000 +#define AXI_A_NORMAL_CLK 166250000 +#define AXI_A_CLK_NORMAL_DIV 4 +#define AXI_B_CLK_NORMAL_DIV 5 +#define AHB_CLK_NORMAL_DIV AXI_B_CLK_NORMAL_DIV +#define EMI_SLOW_CLK_NORMAL_DIV AXI_B_CLK_NORMAL_DIV +#define NFC_CLK_NORMAL_DIV 4 + +struct clk *ddr_clk; +struct clk *pll2; +struct clk *main_bus_clk; +struct clk *axi_a_clk; +struct clk *axi_b_clk; +struct clk *cpu_clk; +struct clk *ddr_hf_clk; +struct clk *nfc_clk; +struct clk *ahb_clk; +struct clk *vpu_clk; +struct clk *vpu_core_clk; +struct clk *emi_slow_clk; +struct clk *ddr_clk; +struct clk *ipu_clk; +struct clk *periph_apm_clk; +struct clk *lp_apm; +struct clk *osc; +struct regulator *lp_regulator; +int low_bus_freq_mode; +int high_bus_freq_mode; +int bus_freq_scaling_is_active; +char *gp_reg_id = "SW1"; +char *lp_reg_id = "SW2"; + +static struct cpu_wp *cpu_wp_tbl; +static struct device *busfreq_dev; +extern int lp_high_freq; +extern int lp_med_freq; +extern int dvfs_core_is_active; +extern struct cpu_wp *(*get_cpu_wp)(int *wp); +extern int cpu_wp_nr; + +struct dvfs_wp dvfs_core_setpoint[] = {{33, 7, 33, 20, 20, 0x10}, + {27, 0, 33, 20, 20, 0x10},}; + +int set_low_bus_freq(void) +{ + struct clk *p_clk; + struct clk *amode_parent_clk; + + if (bus_freq_scaling_is_active) { + /*Change the DDR freq to 133Mhz. */ + clk_set_rate(ddr_hf_clk, + clk_round_rate(ddr_hf_clk, DDR_LOW_FREQ_CLK)); + + p_clk = clk_get_parent(periph_apm_clk); + /* Make sure osc_clk is the parent of lp_apm. */ + clk_set_parent(lp_apm, osc); + /* Set the parent of periph_apm_clk to be lp_apm */ + clk_set_parent(periph_apm_clk, lp_apm); + + amode_parent_clk = periph_apm_clk; + + p_clk = clk_get_parent(main_bus_clk); + /* Set the parent of main_bus_clk to be periph_apm_clk */ + clk_set_parent(main_bus_clk, amode_parent_clk); + + clk_set_rate(axi_a_clk, LP_APM_CLK); + clk_set_rate(axi_b_clk, LP_APM_CLK); + clk_set_rate(ahb_clk, LP_APM_CLK); + clk_set_rate(emi_slow_clk, LP_APM_CLK); + clk_set_rate(nfc_clk, NAND_LP_APM_CLK); + + low_bus_freq_mode = 1; + high_bus_freq_mode = 0; + } + + return 0; +} + +int set_high_bus_freq(int high_bus_freq) +{ + if (bus_freq_scaling_is_active) { + if (clk_get_rate(main_bus_clk) == LP_APM_CLK) { + + clk_enable(pll2); + + /* Set the dividers before setting the parent clock. */ + clk_set_rate(axi_a_clk, + LP_APM_CLK/AXI_A_CLK_NORMAL_DIV); + clk_set_rate(axi_b_clk, + LP_APM_CLK/AXI_B_CLK_NORMAL_DIV); + clk_set_rate(ahb_clk, LP_APM_CLK/AHB_CLK_NORMAL_DIV); + clk_set_rate(emi_slow_clk, + LP_APM_CLK/EMI_SLOW_CLK_NORMAL_DIV); + clk_set_rate(nfc_clk, + clk_get_rate(emi_slow_clk)/NFC_CLK_NORMAL_DIV); + /* Set the parent of main_bus_clk to be pll2 */ + clk_set_parent(main_bus_clk, pll2); + + /*Change the DDR freq to 200MHz*/ + clk_set_rate(ddr_hf_clk, + clk_round_rate(ddr_hf_clk, DDR_NORMAL_CLK)); + + low_bus_freq_mode = 0; + } + /* + * If the CPU freq is 800MHz, set the bus to the high setpoint + * (133MHz) and DDR to 200MHz. + */ + if (clk_get_rate(cpu_clk) != cpu_wp_tbl[cpu_wp_nr - 1].cpu_rate) + high_bus_freq = 1; + + if (((clk_get_rate(ahb_clk) == LP_MED_CLK) && lp_high_freq) || + high_bus_freq) { + /* Set to the high setpoint. */ + high_bus_freq_mode = 1; + clk_set_rate(ahb_clk, + clk_round_rate(ahb_clk, LP_NORMAL_CLK)); + clk_set_rate(ddr_hf_clk, + clk_round_rate(ddr_hf_clk, DDR_NORMAL_CLK)); + + } + if (!lp_high_freq && !high_bus_freq) { + /* Set to the medium setpoint. */ + high_bus_freq_mode = 0; + low_bus_freq_mode = 0; + clk_set_rate(ddr_hf_clk, + clk_round_rate(ddr_hf_clk, DDR_LOW_FREQ_CLK)); + clk_set_rate(ahb_clk, + clk_round_rate(ahb_clk, LP_MED_CLK)); + } + } + + return 0; +} + +int low_freq_bus_used(void) +{ + if ((clk_get_usecount(ipu_clk) == 0) + && (clk_get_usecount(vpu_clk) == 0) + && (lp_high_freq == 0) + && (lp_med_freq == 0)) + return 1; + else + return 0; +} + +void setup_pll(void) +{ +} + +static ssize_t bus_freq_scaling_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (bus_freq_scaling_is_active) + return sprintf(buf, "Bus frequency scaling is enabled\n"); + else + return sprintf(buf, "Bus frequency scaling is disabled\n"); +} + +static ssize_t bus_freq_scaling_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + if (strstr(buf, "1") != NULL) + bus_freq_scaling_is_active = 1; + else if (strstr(buf, "0") != NULL) { + if (bus_freq_scaling_is_active) + set_high_bus_freq(1); + + bus_freq_scaling_is_active = 0; + } + + return size; +} + +static DEVICE_ATTR(enable, 0644, bus_freq_scaling_enable_show, + bus_freq_scaling_enable_store); + +/*! + * This is the probe routine for the bus frequency driver. + * + * @param pdev The platform device structure + * + * @return The function returns 0 on success + * + */ +static int __devinit busfreq_probe(struct platform_device *pdev) +{ + int err = 0; + + busfreq_dev = &pdev->dev; + + main_bus_clk = clk_get(NULL, "main_bus_clk"); + if (IS_ERR(main_bus_clk)) { + printk(KERN_DEBUG "%s: failed to get main_bus_clk\n", + __func__); + return PTR_ERR(main_bus_clk); + } + + pll2 = clk_get(NULL, "pll2"); + if (IS_ERR(pll2)) { + printk(KERN_DEBUG "%s: failed to get pll2\n", __func__); + return PTR_ERR(pll2); + } + + axi_a_clk = clk_get(NULL, "axi_a_clk"); + if (IS_ERR(axi_a_clk)) { + printk(KERN_DEBUG "%s: failed to get axi_a_clk\n", + __func__); + return PTR_ERR(axi_a_clk); + } + + axi_b_clk = clk_get(NULL, "axi_b_clk"); + if (IS_ERR(axi_b_clk)) { + printk(KERN_DEBUG "%s: failed to get axi_b_clk\n", + __func__); + return PTR_ERR(axi_b_clk); + } + + ddr_hf_clk = clk_get(NULL, "ddr_hf_clk"); + if (IS_ERR(ddr_hf_clk)) { + printk(KERN_DEBUG "%s: failed to get ddr_hf_clk\n", + __func__); + return PTR_ERR(ddr_hf_clk); + } + + emi_slow_clk = clk_get(NULL, "emi_slow_clk"); + if (IS_ERR(emi_slow_clk)) { + printk(KERN_DEBUG "%s: failed to get emi_slow_clk\n", + __func__); + return PTR_ERR(emi_slow_clk); + } + + nfc_clk = clk_get(NULL, "nfc_clk"); + if (IS_ERR(nfc_clk)) { + printk(KERN_DEBUG "%s: failed to get nfc_clk\n", + __func__); + return PTR_ERR(nfc_clk); + } + + ahb_clk = clk_get(NULL, "ahb_clk"); + if (IS_ERR(ahb_clk)) { + printk(KERN_DEBUG "%s: failed to get ahb_clk\n", + __func__); + return PTR_ERR(ahb_clk); + } + + vpu_core_clk = clk_get(NULL, "vpu_core_clk"); + if (IS_ERR(vpu_core_clk)) { + printk(KERN_DEBUG "%s: failed to get vpu_core_clk\n", + __func__); + return PTR_ERR(vpu_core_clk); + } + + ddr_clk = clk_get(NULL, "ddr_clk"); + if (IS_ERR(ddr_clk)) { + printk(KERN_DEBUG "%s: failed to get ddr_clk\n", + __func__); + return PTR_ERR(ddr_clk); + } + + cpu_clk = clk_get(NULL, "cpu_clk"); + if (IS_ERR(cpu_clk)) { + printk(KERN_DEBUG "%s: failed to get cpu_clk\n", + __func__); + return PTR_ERR(cpu_clk); + } + + ipu_clk = clk_get(NULL, "ipu_clk"); + if (IS_ERR(ipu_clk)) { + printk(KERN_DEBUG "%s: failed to get ipu_clk\n", + __func__); + return PTR_ERR(ipu_clk); + } + + vpu_clk = clk_get(NULL, "vpu_clk"); + if (IS_ERR(vpu_clk)) { + printk(KERN_DEBUG "%s: failed to get vpu_clk\n", + __func__); + return PTR_ERR(vpu_clk); + } + + periph_apm_clk = clk_get(NULL, "periph_apm_clk"); + if (IS_ERR(periph_apm_clk)) { + printk(KERN_DEBUG "%s: failed to get periph_apm_clk\n", + __func__); + return PTR_ERR(periph_apm_clk); + } + + lp_apm = clk_get(NULL, "lp_apm"); + if (IS_ERR(lp_apm)) { + printk(KERN_DEBUG "%s: failed to get lp_apm\n", + __func__); + return PTR_ERR(lp_apm); + } + + osc = clk_get(NULL, "osc"); + if (IS_ERR(osc)) { + printk(KERN_DEBUG "%s: failed to get osc\n", __func__); + return PTR_ERR(osc); + } + + err = sysfs_create_file(&busfreq_dev->kobj, &dev_attr_enable.attr); + if (err) { + printk(KERN_ERR + "Unable to register sysdev entry for BUSFREQ"); + return err; + } + cpu_wp_tbl = get_cpu_wp(&cpu_wp_nr); + low_bus_freq_mode = 0; + high_bus_freq_mode = 0; + bus_freq_scaling_is_active = 0; + + return 0; +} + +static struct platform_driver busfreq_driver = { + .driver = { + .name = "busfreq", + }, + .probe = busfreq_probe, +}; + +/*! + * Initialise the busfreq_driver. + * + * @return The function always returns 0. + */ + +static int __init busfreq_init(void) +{ + if (platform_driver_register(&busfreq_driver) != 0) { + printk(KERN_ERR "busfreq_driver register failed\n"); + return -ENODEV; + } + + printk(KERN_INFO "Bus freq driver module loaded\n"); + return 0; +} + +static void __exit busfreq_cleanup(void) +{ + sysfs_remove_file(&busfreq_dev->kobj, &dev_attr_enable.attr); + + /* Unregister the device structure */ + platform_driver_unregister(&busfreq_driver); +} + +module_init(busfreq_init); +module_exit(busfreq_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("BusFreq driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-mx51/clock.c b/arch/arm/mach-mx51/clock.c new file mode 100644 index 000000000000..0d9dd80e7616 --- /dev/null +++ b/arch/arm/mach-mx51/clock.c @@ -0,0 +1,3895 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/time.h> +#include <linux/hrtimer.h> +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <asm/io.h> +#include <asm/div64.h> +#include <mach/hardware.h> +#include <mach/common.h> +#include <mach/clock.h> +#include <mach/spba.h> + +#include "crm_regs.h" + +static unsigned long pll_base[] = { + (unsigned long)MXC_DPLL1_BASE, + (unsigned long)MXC_DPLL2_BASE, + (unsigned long)MXC_DPLL3_BASE, +}; + +static struct clk pll1_main_clk; +static struct clk pll1_sw_clk; +static struct clk pll2_sw_clk; +static struct clk pll3_sw_clk; +static struct clk lp_apm_clk; +static struct clk tve_clk; +static struct clk emi_fast_clk; +static struct clk emi_slow_clk; +static struct clk emi_intr_clk; +static struct clk ddr_clk; +static struct clk ipu_clk[]; +static struct clk axi_a_clk; +static struct clk axi_b_clk; +static struct clk ddr_hf_clk; +static struct clk mipi_hsp_clk; +static int cpu_curr_wp; +static struct cpu_wp *cpu_wp_tbl; + +int cpu_wp_nr; +int lp_high_freq; +int lp_med_freq; + +#define SPIN_DELAY 1000000 /* in nanoseconds */ + +extern int mxc_jtag_enabled; +extern int cpufreq_trig_needed; + +static int cpu_clk_set_wp(int wp); +extern void propagate_rate(struct clk *tclk); +struct cpu_wp *(*get_cpu_wp)(int *wp); +void (*set_num_cpu_wp)(int num); + +static void __calc_pre_post_dividers(u32 div, u32 *pre, u32 *post) +{ + u32 min_pre, temp_pre, old_err, err; + + if (div >= 512) { + *pre = 8; + *post = 64; + } else if (div >= 8) { + min_pre = (div - 1) / 64 + 1; + old_err = 8; + for (temp_pre = 8; temp_pre >= min_pre; temp_pre--) { + err = div % temp_pre; + if (err == 0) { + *pre = temp_pre; + break; + } + err = temp_pre - err; + if (err < old_err) { + old_err = err; + *pre = temp_pre; + } + } + *post = (div + *pre - 1) / *pre; + } else if (div < 8) { + *pre = div; + *post = 1; + } +} + +static int _clk_enable(struct clk *clk) +{ + u32 reg; + reg = __raw_readl(clk->enable_reg); + reg |= MXC_CCM_CCGR_CG_MASK << clk->enable_shift; + __raw_writel(reg, clk->enable_reg); + + if (clk->flags & AHB_HIGH_SET_POINT) + lp_high_freq++; + else if (clk->flags & AHB_MED_SET_POINT) + lp_med_freq++; + + + return 0; +} + +static void _clk_disable(struct clk *clk) +{ + u32 reg; + reg = __raw_readl(clk->enable_reg); + reg &= ~(MXC_CCM_CCGR_CG_MASK << clk->enable_shift); + __raw_writel(reg, clk->enable_reg); + + if (clk->flags & AHB_HIGH_SET_POINT) + lp_high_freq--; + else if (clk->flags & AHB_MED_SET_POINT) + lp_med_freq--; + +} + +static void _clk_disable_inwait(struct clk *clk) +{ + u32 reg; + + reg = __raw_readl(clk->enable_reg); + reg &= ~(MXC_CCM_CCGR_CG_MASK << clk->enable_shift); + reg |= 1 << clk->enable_shift; + __raw_writel(reg, clk->enable_reg); +} + +/* + * For the 4-to-1 muxed input clock + */ +static inline u32 _get_mux(struct clk *parent, struct clk *m0, + struct clk *m1, struct clk *m2, struct clk *m3) +{ + if (parent == m0) + return 0; + else if (parent == m1) + return 1; + else if (parent == m2) + return 2; + else if (parent == m3) + return 3; + else + BUG(); + + return 0; +} + +/* + * For the ddr muxed input clock + */ +static inline u32 _get_mux_ddr(struct clk *parent, struct clk *m0, + struct clk *m1, struct clk *m2, struct clk *m3, struct clk *m4) +{ + if (parent == m0) + return 0; + else if (parent == m1) + return 1; + else if (parent == m2) + return 2; + else if (parent == m3) + return 3; + else if (parent == m4) + return 4; + else + BUG(); + + return 0; +} + +static inline unsigned long _get_pll_base(struct clk *pll) +{ + if (pll == &pll1_main_clk) + return pll_base[0]; + else if (pll == &pll2_sw_clk) + return pll_base[1]; + else if (pll == &pll3_sw_clk) + return pll_base[2]; + else + BUG(); + + return 0; +} + +static struct clk ckih_clk = { + .name = "ckih", + .flags = RATE_PROPAGATES, +}; + +static struct clk ckih2_clk = { + .name = "ckih2", + .flags = RATE_PROPAGATES, +}; + +static struct clk osc_clk = { + .name = "osc", + .flags = RATE_PROPAGATES, +}; + +static struct clk ckil_clk = { + .name = "ckil", + .flags = RATE_PROPAGATES, +}; + +static void _fpm_recalc(struct clk *clk) +{ + clk->rate = ckil_clk.rate * 512; + if ((__raw_readl(MXC_CCM_CCR) & MXC_CCM_CCR_FPM_MULT_MASK) != 0) + clk->rate *= 2; + +} + +static int _fpm_enable(struct clk *clk) +{ + u32 reg = __raw_readl(MXC_CCM_CCR); + reg |= MXC_CCM_CCR_FPM_EN; + __raw_writel(reg, MXC_CCM_CCR); + return 0; +} + +static void _fpm_disable(struct clk *clk) +{ + u32 reg = __raw_readl(MXC_CCM_CCR); + reg &= ~MXC_CCM_CCR_FPM_EN; + __raw_writel(reg, MXC_CCM_CCR); +} + +static struct clk fpm_clk = { + .name = "fpm_clk", + .parent = &ckil_clk, + .recalc = _fpm_recalc, + .enable = _fpm_enable, + .disable = _fpm_disable, + .flags = RATE_PROPAGATES, +}; + +static void _fpm_div2_recalc(struct clk *clk) +{ + clk->rate = clk->parent->rate / 2; +} + +static struct clk fpm_div2_clk = { + .name = "fpm_div2_clk", + .parent = &fpm_clk, + .recalc = _fpm_div2_recalc, + .flags = RATE_PROPAGATES, +}; + +static void _clk_pll_recalc(struct clk *clk) +{ + long mfi, mfn, mfd, pdf, ref_clk, mfn_abs; + unsigned long dp_op, dp_mfd, dp_mfn, dp_ctl, pll_hfsm, dbl; + unsigned long pllbase; + s64 temp; + + pllbase = _get_pll_base(clk); + + dp_ctl = __raw_readl(pllbase + MXC_PLL_DP_CTL); + pll_hfsm = dp_ctl & MXC_PLL_DP_CTL_HFSM; + dbl = dp_ctl & MXC_PLL_DP_CTL_DPDCK0_2_EN; + + if (pll_hfsm == 0) { + dp_op = __raw_readl(pllbase + MXC_PLL_DP_OP); + dp_mfd = __raw_readl(pllbase + MXC_PLL_DP_MFD); + dp_mfn = __raw_readl(pllbase + MXC_PLL_DP_MFN); + } else { + dp_op = __raw_readl(pllbase + MXC_PLL_DP_HFS_OP); + dp_mfd = __raw_readl(pllbase + MXC_PLL_DP_HFS_MFD); + dp_mfn = __raw_readl(pllbase + MXC_PLL_DP_HFS_MFN); + } + pdf = dp_op & MXC_PLL_DP_OP_PDF_MASK; + mfi = (dp_op & MXC_PLL_DP_OP_MFI_MASK) >> MXC_PLL_DP_OP_MFI_OFFSET; + mfi = (mfi <= 5) ? 5 : mfi; + mfd = dp_mfd & MXC_PLL_DP_MFD_MASK; + mfn = mfn_abs = dp_mfn & MXC_PLL_DP_MFN_MASK; + /* Sign extend to 32-bits */ + if (mfn >= 0x04000000) { + mfn |= 0xFC000000; + mfn_abs = -mfn; + } + + ref_clk = 2 * clk->parent->rate; + if (dbl != 0) + ref_clk *= 2; + + ref_clk /= (pdf + 1); + temp = (u64) ref_clk * mfn_abs; + do_div(temp, mfd + 1); + if (mfn < 0) + temp = -temp; + temp = (ref_clk * mfi) + temp; + + clk->rate = temp; +} + +static int _clk_pll_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + u32 pllbase; + + long mfi, pdf, mfn, mfd = 999999; + s64 temp64; + unsigned long quad_parent_rate; + unsigned long pll_hfsm, dp_ctl; + + pllbase = _get_pll_base(clk); + + quad_parent_rate = 4*clk->parent->rate; + pdf = mfi = -1; + while (++pdf < 16 && mfi < 5) + mfi = rate * (pdf+1) / quad_parent_rate; + if (mfi > 15) + return -1; + pdf--; + + temp64 = rate*(pdf+1) - quad_parent_rate*mfi; + do_div(temp64, quad_parent_rate/1000000); + mfn = (long)temp64; + + dp_ctl = __raw_readl(pllbase + MXC_PLL_DP_CTL); + /* use dpdck0_2 */ + __raw_writel(dp_ctl | 0x1000L, pllbase + MXC_PLL_DP_CTL); + pll_hfsm = dp_ctl & MXC_PLL_DP_CTL_HFSM; + if (pll_hfsm == 0) { + reg = mfi<<4 | pdf; + __raw_writel(reg, pllbase + MXC_PLL_DP_OP); + __raw_writel(mfd, pllbase + MXC_PLL_DP_MFD); + __raw_writel(mfn, pllbase + MXC_PLL_DP_MFN); + } else { + reg = mfi<<4 | pdf; + __raw_writel(reg, pllbase + MXC_PLL_DP_HFS_OP); + __raw_writel(mfd, pllbase + MXC_PLL_DP_HFS_MFD); + __raw_writel(mfn, pllbase + MXC_PLL_DP_HFS_MFN); + } + + clk->rate = rate; + return 0; +} + +static int _clk_pll_enable(struct clk *clk) +{ + u32 reg; + u32 pllbase; + struct timespec nstimeofday; + struct timespec curtime; + + pllbase = _get_pll_base(clk); + reg = __raw_readl(pllbase + MXC_PLL_DP_CTL) | MXC_PLL_DP_CTL_UPEN; + __raw_writel(reg, pllbase + MXC_PLL_DP_CTL); + + /* Wait for lock */ + getnstimeofday(&nstimeofday); + while (!(__raw_readl(pllbase + MXC_PLL_DP_CTL) & MXC_PLL_DP_CTL_LRF)) { + getnstimeofday(&curtime); + if (curtime.tv_nsec - nstimeofday.tv_nsec > SPIN_DELAY) + panic("pll relock failed\n"); + } + return 0; +} + +static void _clk_pll_disable(struct clk *clk) +{ + u32 reg; + u32 pllbase; + + pllbase = _get_pll_base(clk); + reg = __raw_readl(pllbase + MXC_PLL_DP_CTL) & ~MXC_PLL_DP_CTL_UPEN; + __raw_writel(reg, pllbase + MXC_PLL_DP_CTL); +} + +static struct clk pll1_main_clk = { + .name = "pll1_main_clk", + .parent = &osc_clk, + .recalc = _clk_pll_recalc, + .enable = _clk_pll_enable, + .disable = _clk_pll_disable, + .flags = RATE_PROPAGATES, +}; + +static int _clk_pll1_sw_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CCSR); + + if (parent == &pll1_main_clk) { + reg &= ~MXC_CCM_CCSR_PLL1_SW_CLK_SEL; + __raw_writel(reg, MXC_CCM_CCSR); + /* Set the step_clk parent to be lp_apm, to save power. */ + mux = _get_mux(&lp_apm_clk, &lp_apm_clk, NULL, &pll2_sw_clk, + &pll3_sw_clk); + reg = __raw_readl(MXC_CCM_CCSR); + reg = (reg & ~MXC_CCM_CCSR_STEP_SEL_MASK) | + (mux << MXC_CCM_CCSR_STEP_SEL_OFFSET); + } else { + if (parent == &lp_apm_clk) { + reg |= MXC_CCM_CCSR_PLL1_SW_CLK_SEL; + reg = __raw_readl(MXC_CCM_CCSR); + mux = _get_mux(parent, &lp_apm_clk, NULL, &pll2_sw_clk, + &pll3_sw_clk); + reg = (reg & ~MXC_CCM_CCSR_STEP_SEL_MASK) | + (mux << MXC_CCM_CCSR_STEP_SEL_OFFSET); + } else { + mux = _get_mux(parent, &lp_apm_clk, NULL, &pll2_sw_clk, + &pll3_sw_clk); + reg = (reg & ~MXC_CCM_CCSR_STEP_SEL_MASK) | + (mux << MXC_CCM_CCSR_STEP_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CCSR); + reg = __raw_readl(MXC_CCM_CCSR); + reg |= MXC_CCM_CCSR_PLL1_SW_CLK_SEL; + + } + } + __raw_writel(reg, MXC_CCM_CCSR); + return 0; +} + +static void _clk_pll1_sw_recalc(struct clk *clk) +{ + u32 reg, div; + div = 1; + reg = __raw_readl(MXC_CCM_CCSR); + + if (clk->parent == &pll2_sw_clk) { + div = ((reg & MXC_CCM_CCSR_PLL2_PODF_MASK) >> + MXC_CCM_CCSR_PLL2_PODF_OFFSET) + 1; + } else if (clk->parent == &pll3_sw_clk) { + div = ((reg & MXC_CCM_CCSR_PLL3_PODF_MASK) >> + MXC_CCM_CCSR_PLL3_PODF_OFFSET) + 1; + } + clk->rate = clk->parent->rate / div; +} + +/* pll1 switch clock */ +static struct clk pll1_sw_clk = { + .name = "pll1_sw_clk", + .parent = &pll1_main_clk, + .set_parent = _clk_pll1_sw_set_parent, + .recalc = _clk_pll1_sw_recalc, + .flags = RATE_PROPAGATES, +}; + +static int _clk_pll2_sw_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_CCSR); + + if (parent == &pll2_sw_clk) { + reg &= ~MXC_CCM_CCSR_PLL2_SW_CLK_SEL; + } else { + reg = (reg & ~MXC_CCM_CCSR_PLL2_SW_CLK_SEL); + reg |= MXC_CCM_CCSR_PLL2_SW_CLK_SEL; + } + __raw_writel(reg, MXC_CCM_CCSR); + return 0; +} + +/* same as pll2_main_clk. These two clocks should always be the same */ +static struct clk pll2_sw_clk = { + .name = "pll2", + .parent = &osc_clk, + .recalc = _clk_pll_recalc, + .enable = _clk_pll_enable, + .disable = _clk_pll_disable, + .set_rate = _clk_pll_set_rate, + .set_parent = _clk_pll2_sw_set_parent, + .flags = RATE_PROPAGATES, +}; + +/* same as pll3_main_clk. These two clocks should always be the same */ +static struct clk pll3_sw_clk = { + .name = "pll3", + .parent = &osc_clk, + .set_rate = _clk_pll_set_rate, + .recalc = _clk_pll_recalc, + .enable = _clk_pll_enable, + .disable = _clk_pll_disable, + .flags = RATE_PROPAGATES, +}; + +static int _clk_lp_apm_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + if (parent == &osc_clk) + reg = __raw_readl(MXC_CCM_CCSR) & ~MXC_CCM_CCSR_LP_APM_SEL; + else if (parent == &fpm_clk) + reg = __raw_readl(MXC_CCM_CCSR) | MXC_CCM_CCSR_LP_APM_SEL; + else + return -EINVAL; + + __raw_writel(reg, MXC_CCM_CCSR); + + return 0; +} + +static struct clk lp_apm_clk = { + .name = "lp_apm", + .parent = &osc_clk, + .set_parent = _clk_lp_apm_set_parent, + .flags = RATE_PROPAGATES, +}; + +static void _clk_arm_recalc(struct clk *clk) +{ + u32 cacrr, div; + + cacrr = __raw_readl(MXC_CCM_CACRR); + div = (cacrr & MXC_CCM_CACRR_ARM_PODF_MASK) + 1; + clk->rate = clk->parent->rate / div; +} + +static int _clk_cpu_set_rate(struct clk *clk, unsigned long rate) +{ + u32 i; + for (i = 0; i < cpu_wp_nr; i++) { + if (rate == cpu_wp_tbl[i].cpu_rate) + break; + } + if (i >= cpu_wp_nr) + return -EINVAL; + cpu_clk_set_wp(i); + + return 0; +} + +static unsigned long _clk_cpu_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 i; + u32 wp; + + for (i = 0; i < cpu_wp_nr; i++) { + if (rate == cpu_wp_tbl[i].cpu_rate) + break; + } + + if (i > cpu_wp_nr) + wp = 0; + + return cpu_wp_tbl[wp].cpu_rate; +} + + +static struct clk cpu_clk = { + .name = "cpu_clk", + .parent = &pll1_sw_clk, + .recalc = _clk_arm_recalc, + .set_rate = _clk_cpu_set_rate, + .round_rate = _clk_cpu_round_rate, +}; + +static int _clk_periph_apm_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + struct timespec nstimeofday; + struct timespec curtime; + + mux = _get_mux(parent, &pll1_sw_clk, &pll3_sw_clk, &lp_apm_clk, NULL); + + reg = __raw_readl(MXC_CCM_CBCMR) & ~MXC_CCM_CBCMR_PERIPH_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CBCMR_PERIPH_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CBCMR); + + getnstimeofday(&nstimeofday); + while (__raw_readl(MXC_CCM_CDHIPR) & + MXC_CCM_CDHIPR_PERIPH_CLK_SEL_BUSY) { + getnstimeofday(&curtime); + if (curtime.tv_nsec - nstimeofday.tv_nsec > SPIN_DELAY) + panic("pll _clk_periph_apm_set_parent failed\n"); + } + return 0; +} + +static struct clk periph_apm_clk = { + .name = "periph_apm_clk", + .parent = &pll1_sw_clk, + .set_parent = _clk_periph_apm_set_parent, + .flags = RATE_PROPAGATES, +}; + +/* TODO: Need to sync with GPC to determine if DVFS is in place so that + * the DVFS_PODF divider can be applied in CDCR register. + */ +static void _clk_main_bus_recalc(struct clk *clk) +{ + clk->rate = clk->parent->rate; +} + +static int _clk_main_bus_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.enable(&emi_slow_clk); + + if (ipu_clk[0].usecount == 0) + ipu_clk[0].enable(&ipu_clk[0]); + if (mipi_hsp_clk.usecount == 0) + mipi_hsp_clk.enable(&mipi_hsp_clk); + + if (parent == &pll2_sw_clk) { + reg = __raw_readl(MXC_CCM_CBCDR) & + ~MXC_CCM_CBCDR_PERIPH_CLK_SEL; + } else if (parent == &periph_apm_clk) { + reg = __raw_readl(MXC_CCM_CBCDR) | MXC_CCM_CBCDR_PERIPH_CLK_SEL; + } else { + return -EINVAL; + } + __raw_writel(reg, MXC_CCM_CBCDR); + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.disable(&emi_slow_clk); + + if (ipu_clk[0].usecount == 0) + ipu_clk[0].disable(&ipu_clk[0]); + if (mipi_hsp_clk.usecount == 0) + mipi_hsp_clk.disable(&mipi_hsp_clk); + + return 0; +} + +static struct clk main_bus_clk = { + .name = "main_bus_clk", + .parent = &pll2_sw_clk, + .set_parent = _clk_main_bus_set_parent, + .recalc = _clk_main_bus_recalc, + .flags = RATE_PROPAGATES, +}; + +static void _clk_axi_a_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR); + div = ((reg & MXC_CCM_CBCDR_AXI_A_PODF_MASK) >> + MXC_CCM_CBCDR_AXI_A_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + +static int _clk_axi_a_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div; + struct timespec nstimeofday; + struct timespec curtime; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) + return -EINVAL; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.enable(&emi_slow_clk); + if (ipu_clk[0].usecount == 0) + ipu_clk[0].enable(&ipu_clk[0]); + if (mipi_hsp_clk.usecount == 0) + mipi_hsp_clk.enable(&mipi_hsp_clk); + + reg = __raw_readl(MXC_CCM_CBCDR); + reg &= ~MXC_CCM_CBCDR_AXI_A_PODF_MASK; + reg |= (div - 1) << MXC_CCM_CBCDR_AXI_A_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CBCDR); + + getnstimeofday(&nstimeofday); + while (__raw_readl(MXC_CCM_CDHIPR) & MXC_CCM_CDHIPR_AXI_A_PODF_BUSY) { + getnstimeofday(&curtime); + if (curtime.tv_nsec - nstimeofday.tv_nsec > SPIN_DELAY) + panic("pll _clk_axi_a_set_rate failed\n"); + } + clk->rate = rate; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.disable(&emi_slow_clk); + + if (ipu_clk[0].usecount == 0) + ipu_clk[0].disable(&ipu_clk[0]); + if (mipi_hsp_clk.usecount == 0) + mipi_hsp_clk.disable(&mipi_hsp_clk); + + return 0; +} + +static unsigned long _clk_axi_a_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 8) + div = 8; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + + +static struct clk axi_a_clk = { + .name = "axi_a_clk", + .parent = &main_bus_clk, + .recalc = _clk_axi_a_recalc, + .set_rate = _clk_axi_a_set_rate, + .round_rate = _clk_axi_a_round_rate, + .flags = RATE_PROPAGATES, +}; + +static void _clk_ddr_hf_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR); + div = ((reg & MXC_CCM_CBCDR_DDR_PODF_MASK) >> + MXC_CCM_CBCDR_DDR_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + +static unsigned long _clk_ddr_hf_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 8) + div = 8; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + +static int _clk_ddr_hf_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div; + struct timespec nstimeofday; + struct timespec curtime; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) + return -EINVAL; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.enable(&emi_slow_clk); + + reg = __raw_readl(MXC_CCM_CBCDR); + reg &= ~MXC_CCM_CBCDR_DDR_PODF_MASK; + reg |= (div - 1) << MXC_CCM_CBCDR_DDR_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CBCDR); + + getnstimeofday(&nstimeofday); + while (__raw_readl(MXC_CCM_CDHIPR) & MXC_CCM_CDHIPR_DDR_PODF_BUSY) { + getnstimeofday(&curtime); + if (curtime.tv_nsec - nstimeofday.tv_nsec > SPIN_DELAY) + panic("clk_ddr_hf_set_rate failed\n"); + } + clk->rate = rate; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.disable(&emi_slow_clk); + + return 0; +} + +static struct clk ddr_hf_clk = { + .name = "ddr_hf_clk", + .parent = &pll1_sw_clk, + .recalc = _clk_ddr_hf_recalc, + .round_rate = _clk_ddr_hf_round_rate, + .set_rate = _clk_ddr_hf_set_rate, + .flags = RATE_PROPAGATES, +}; + +static void _clk_axi_b_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR); + div = ((reg & MXC_CCM_CBCDR_AXI_B_PODF_MASK) >> + MXC_CCM_CBCDR_AXI_B_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + +static int _clk_axi_b_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div; + struct timespec nstimeofday; + struct timespec curtime; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) + return -EINVAL; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.enable(&emi_slow_clk); + + if (ipu_clk[0].usecount == 0) + ipu_clk[0].enable(&ipu_clk[0]); + if (mipi_hsp_clk.usecount == 0) + mipi_hsp_clk.enable(&mipi_hsp_clk); + + reg = __raw_readl(MXC_CCM_CBCDR); + reg &= ~MXC_CCM_CBCDR_AXI_B_PODF_MASK; + reg |= (div - 1) << MXC_CCM_CBCDR_AXI_B_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CBCDR); + + getnstimeofday(&nstimeofday); + while (__raw_readl(MXC_CCM_CDHIPR) & MXC_CCM_CDHIPR_AXI_B_PODF_BUSY) { + getnstimeofday(&curtime); + if (curtime.tv_nsec - nstimeofday.tv_nsec > SPIN_DELAY) + panic("_clk_axi_b_set_rate failed\n"); + } + + clk->rate = rate; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.disable(&emi_slow_clk); + + if (ipu_clk[0].usecount == 0) + ipu_clk[0].disable(&ipu_clk[0]); + if (mipi_hsp_clk.usecount == 0) + mipi_hsp_clk.disable(&mipi_hsp_clk); + + return 0; +} + +static unsigned long _clk_axi_b_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 8) + div = 8; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + + +static struct clk axi_b_clk = { + .name = "axi_b_clk", + .parent = &main_bus_clk, + .recalc = _clk_axi_b_recalc, + .set_rate = _clk_axi_b_set_rate, + .round_rate = _clk_axi_b_round_rate, + .flags = RATE_PROPAGATES, +}; + +static void _clk_ahb_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR); + div = ((reg & MXC_CCM_CBCDR_AHB_PODF_MASK) >> + MXC_CCM_CBCDR_AHB_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + + +static int _clk_ahb_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div; + struct timespec nstimeofday; + struct timespec curtime; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) + return -EINVAL; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.enable(&emi_slow_clk); + if (ipu_clk[0].usecount == 0) + ipu_clk[0].enable(&ipu_clk[0]); + if (mipi_hsp_clk.usecount == 0) + mipi_hsp_clk.enable(&mipi_hsp_clk); + + reg = __raw_readl(MXC_CCM_CBCDR); + reg &= ~MXC_CCM_CBCDR_AHB_PODF_MASK; + reg |= (div - 1) << MXC_CCM_CBCDR_AHB_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CBCDR); + + getnstimeofday(&nstimeofday); + while (__raw_readl(MXC_CCM_CDHIPR) & MXC_CCM_CDHIPR_AHB_PODF_BUSY) { + getnstimeofday(&curtime); + if (curtime.tv_nsec - nstimeofday.tv_nsec > SPIN_DELAY) + panic("_clk_ahb_set_rate failed\n"); + } + clk->rate = rate; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.disable(&emi_slow_clk); + + if (ipu_clk[0].usecount == 0) + ipu_clk[0].disable(&ipu_clk[0]); + if (mipi_hsp_clk.usecount == 0) + mipi_hsp_clk.disable(&mipi_hsp_clk); + + return 0; +} + +static unsigned long _clk_ahb_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 8) + div = 8; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + + +static struct clk ahb_clk = { + .name = "ahb_clk", + .parent = &main_bus_clk, + .recalc = _clk_ahb_recalc, + .set_rate = _clk_ahb_set_rate, + .round_rate = _clk_ahb_round_rate, + .flags = RATE_PROPAGATES, +}; + +static int _clk_max_enable(struct clk *clk) +{ + u32 reg; + + _clk_enable(clk); + + /* Handshake with MAX when LPM is entered. */ + reg = __raw_readl(MXC_CCM_CLPCR); + reg &= ~MXC_CCM_CLPCR_BYPASS_MAX_LPM_HS; + __raw_writel(reg, MXC_CCM_CLPCR); + + return 0; +} + + +static void _clk_max_disable(struct clk *clk) +{ + u32 reg; + + _clk_disable_inwait(clk); + + /* No Handshake with MAX when LPM is entered as its disabled. */ + reg = __raw_readl(MXC_CCM_CLPCR); + reg |= MXC_CCM_CLPCR_BYPASS_MAX_LPM_HS; + __raw_writel(reg, MXC_CCM_CLPCR); +} + + +static struct clk ahb_max_clk = { + .name = "max_clk", + .parent = &ahb_clk, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG14_OFFSET, + .enable = _clk_max_enable, + .disable = _clk_max_disable, +}; + +static int _clk_emi_slow_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.enable(&emi_slow_clk); + + if (ipu_clk[0].usecount == 0) + ipu_clk[0].enable(&ipu_clk[0]); + if (mipi_hsp_clk.usecount == 0) + mipi_hsp_clk.enable(&mipi_hsp_clk); + + reg = __raw_readl(MXC_CCM_CBCDR); + if (parent == &ahb_clk) { + reg |= MXC_CCM_CBCDR_EMI_CLK_SEL; + } else if (parent == &main_bus_clk) { + reg &= ~MXC_CCM_CBCDR_EMI_CLK_SEL; + } else { + BUG(); + } + __raw_writel(reg, MXC_CCM_CBCDR); + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.disable(&emi_slow_clk); + + if (ipu_clk[0].usecount == 0) + ipu_clk[0].disable(&ipu_clk[0]); + if (mipi_hsp_clk.usecount == 0) + mipi_hsp_clk.disable(&mipi_hsp_clk); + + return 0; +} + +static void _clk_emi_slow_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR); + div = ((reg & MXC_CCM_CBCDR_EMI_PODF_MASK) >> + MXC_CCM_CBCDR_EMI_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + +static int _clk_emi_slow_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div; + struct timespec nstimeofday; + struct timespec curtime; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) + return -EINVAL; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + + if (ipu_clk[0].usecount == 0) + ipu_clk[0].enable(&ipu_clk[0]); + if (mipi_hsp_clk.usecount == 0) + mipi_hsp_clk.enable(&mipi_hsp_clk); + + reg = __raw_readl(MXC_CCM_CBCDR); + reg &= ~MXC_CCM_CBCDR_EMI_PODF_MASK; + reg |= (div - 1) << MXC_CCM_CBCDR_EMI_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CBCDR); + getnstimeofday(&nstimeofday); + while (__raw_readl(MXC_CCM_CDHIPR) & MXC_CCM_CDHIPR_EMI_PODF_BUSY) { + getnstimeofday(&curtime); + if ((curtime.tv_nsec - nstimeofday.tv_nsec) > SPIN_DELAY) + panic("_clk_emi_slow_set_rate failed\n"); + } + clk->rate = rate; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + + if (ipu_clk[0].usecount == 0) + ipu_clk[0].disable(&ipu_clk[0]); + if (mipi_hsp_clk.usecount == 0) + mipi_hsp_clk.disable(&mipi_hsp_clk); + + return 0; +} + +static unsigned long _clk_emi_slow_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 8) + div = 8; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + + +static struct clk emi_slow_clk = { + .name = "emi_slow_clk", + .parent = &main_bus_clk, + .set_parent = _clk_emi_slow_set_parent, + .recalc = _clk_emi_slow_recalc, + .set_rate = _clk_emi_slow_set_rate, + .round_rate = _clk_emi_slow_round_rate, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG8_OFFSET, + .disable = _clk_disable_inwait, + .flags = RATE_PROPAGATES, +}; + +static struct clk ahbmux1_clk = { + .name = "ahbmux1_clk", + .id = 0, + .parent = &ahb_clk, + .secondary = &ahb_max_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG8_OFFSET, + .disable = _clk_disable_inwait, +}; + +static struct clk ahbmux2_clk = { + .name = "ahbmux2_clk", + .id = 0, + .parent = &ahb_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG9_OFFSET, + .disable = _clk_disable_inwait, +}; + + +static struct clk emi_fast_clk = { + .name = "emi_fast_clk", + .parent = &ddr_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG7_OFFSET, + .disable = _clk_disable_inwait, +}; + +static struct clk emi_intr_clk = { + .name = "emi_intr_clk", + .parent = &ahb_clk, + .secondary = &ahbmux2_clk, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG9_OFFSET, +}; + +static void _clk_ipg_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR); + div = ((reg & MXC_CCM_CBCDR_IPG_PODF_MASK) >> + MXC_CCM_CBCDR_IPG_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + +static struct clk ipg_clk = { + .name = "ipg_clk", + .parent = &ahb_clk, + .recalc = _clk_ipg_recalc, + .flags = RATE_PROPAGATES, +}; + +static void _clk_ipg_per_recalc(struct clk *clk) +{ + u32 reg, prediv1, prediv2, podf; + + if (clk->parent == &main_bus_clk || clk->parent == &lp_apm_clk) { + /* the main_bus_clk is the one before the DVFS engine */ + reg = __raw_readl(MXC_CCM_CBCDR); + prediv1 = ((reg & MXC_CCM_CBCDR_PERCLK_PRED1_MASK) >> + MXC_CCM_CBCDR_PERCLK_PRED1_OFFSET) + 1; + prediv2 = ((reg & MXC_CCM_CBCDR_PERCLK_PRED2_MASK) >> + MXC_CCM_CBCDR_PERCLK_PRED2_OFFSET) + 1; + podf = ((reg & MXC_CCM_CBCDR_PERCLK_PODF_MASK) >> + MXC_CCM_CBCDR_PERCLK_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / (prediv1 * prediv2 * podf); + } else if (clk->parent == &ipg_clk) { + clk->rate = ipg_clk.rate; + } else { + BUG(); + } +} + +static int _clk_ipg_per_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CBCMR); + mux = _get_mux(parent, &main_bus_clk, &lp_apm_clk, &ipg_clk, NULL); + if (mux == 2) { + reg |= MXC_CCM_CBCMR_PERCLK_IPG_CLK_SEL; + } else { + reg &= ~MXC_CCM_CBCMR_PERCLK_IPG_CLK_SEL; + if (mux == 0) + reg &= ~MXC_CCM_CBCMR_PERCLK_LP_APM_CLK_SEL; + else + reg |= MXC_CCM_CBCMR_PERCLK_LP_APM_CLK_SEL; + } + __raw_writel(reg, MXC_CCM_CBCMR); + + return 0; +} + +static struct clk ipg_perclk = { + .name = "ipg_perclk", + .parent = &lp_apm_clk, + .recalc = _clk_ipg_per_recalc, + .set_parent = _clk_ipg_per_set_parent, + .flags = RATE_PROPAGATES, +}; + +static struct clk aips_tz1_clk = { + .name = "aips_tz1_clk", + .parent = &ahb_clk, + .secondary = &ahb_max_clk, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG12_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable_inwait, +}; + +static struct clk aips_tz2_clk = { + .name = "aips_tz2_clk", + .parent = &ahb_clk, + .secondary = &ahb_max_clk, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG13_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable_inwait, +}; + +static struct clk gpc_dvfs_clk = { + .name = "gpc_dvfs_clk", + .parent = &aips_tz2_clk, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG12_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, +}; + +static int _clk_sdma_enable(struct clk *clk) +{ + u32 reg; + + _clk_enable(clk); + + /* Handshake with SDMA when LPM is entered. */ + reg = __raw_readl(MXC_CCM_CLPCR); + reg &= ~MXC_CCM_CLPCR_BYPASS_SDMA_LPM_HS; + __raw_writel(reg, MXC_CCM_CLPCR); + + return 0; +} + +static void _clk_sdma_disable(struct clk *clk) +{ + u32 reg; + + _clk_disable(clk); + /* No handshake with SDMA as its not enabled. */ + reg = __raw_readl(MXC_CCM_CLPCR); + reg |= MXC_CCM_CLPCR_BYPASS_SDMA_LPM_HS; + __raw_writel(reg, MXC_CCM_CLPCR); +} + + +static struct clk sdma_clk[] = { + { + .name = "sdma_ahb_clk", + .parent = &ahb_clk, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG15_OFFSET, + .enable = _clk_sdma_enable, + .disable = _clk_sdma_disable, + }, + { + .name = "sdma_ipg_clk", + .parent = &ipg_clk, +#ifdef CONFIG_SDMA_IRAM + .secondary = &emi_intr_clk, +#endif + }, +}; + +static int _clk_ipu_enable(struct clk *clk) +{ + u32 reg; + + _clk_enable(clk); + /* Handshake with IPU when certain clock rates are changed. */ + reg = __raw_readl(MXC_CCM_CCDR); + reg &= ~MXC_CCM_CCDR_IPU_HS_MASK; + __raw_writel(reg, MXC_CCM_CCDR); + + /* Handshake with IPU when LPM is entered as its enabled. */ + reg = __raw_readl(MXC_CCM_CLPCR); + reg &= ~MXC_CCM_CLPCR_BYPASS_IPU_LPM_HS; + __raw_writel(reg, MXC_CCM_CLPCR); + + + return 0; +} + +static void _clk_ipu_disable(struct clk *clk) +{ + u32 reg; + _clk_disable(clk); + + /* No handshake with IPU whe dividers are changed + * as its not enabled. */ + reg = __raw_readl(MXC_CCM_CCDR); + reg |= MXC_CCM_CCDR_IPU_HS_MASK; + __raw_writel(reg, MXC_CCM_CCDR); + + /* No handshake with IPU when LPM is entered as its not enabled. */ + reg = __raw_readl(MXC_CCM_CLPCR); + reg |= MXC_CCM_CLPCR_BYPASS_IPU_LPM_HS; + __raw_writel(reg, MXC_CCM_CLPCR); + +} + +static int _clk_ipu_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + reg = __raw_readl(MXC_CCM_CBCMR); + mux = _get_mux(parent, &axi_a_clk, &axi_b_clk, &ahb_clk, + &emi_slow_clk); + reg = (reg & ~MXC_CCM_CBCMR_IPU_HSP_CLK_SEL_MASK) | + (mux << MXC_CCM_CBCMR_IPU_HSP_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CBCMR); + + return 0; +} + + +static struct clk ipu_clk[] = { + { + .name = "ipu_clk", + .parent = &ahb_clk, + .secondary = &ipu_clk[1], + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG5_OFFSET, + .enable = _clk_ipu_enable, + .disable = _clk_ipu_disable, + .set_parent = _clk_ipu_set_parent, + .flags = CPU_FREQ_TRIG_UPDATE | AHB_MED_SET_POINT, + }, + { + .name = "ipu_sec_clk", + .parent = &emi_fast_clk, + .secondary = &ahbmux1_clk, + } +}; + +static int _clk_ipu_di_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_CSCMR2); + reg &= ~MXC_CCM_CSCMR2_DI_CLK_SEL_MASK(clk->id); + if (parent == &pll3_sw_clk) + ; + else if (parent == &osc_clk) + reg |= 1 << MXC_CCM_CSCMR2_DI_CLK_SEL_OFFSET(clk->id); + else if (parent == &ckih_clk) + reg |= 2 << MXC_CCM_CSCMR2_DI_CLK_SEL_OFFSET(clk->id); + else if (parent == &tve_clk) + reg |= 3 << MXC_CCM_CSCMR2_DI_CLK_SEL_OFFSET(clk->id); + else /* Assume any other clock is external clock pin */ + reg |= 4 << MXC_CCM_CSCMR2_DI_CLK_SEL_OFFSET(clk->id); + __raw_writel(reg, MXC_CCM_CSCMR2); + + return 0; +} + +static void _clk_ipu_di_recalc(struct clk *clk) +{ + u32 reg, div, mux; + + reg = __raw_readl(MXC_CCM_CSCMR2); + mux = (reg & MXC_CCM_CSCMR2_DI_CLK_SEL_MASK(clk->id)) >> + MXC_CCM_CSCMR2_DI_CLK_SEL_OFFSET(clk->id); + if (mux == 0) { + reg = __raw_readl(MXC_CCM_CDCDR) & + MXC_CCM_CDCDR_DI_CLK_PRED_MASK; + div = (reg >> MXC_CCM_CDCDR_DI_CLK_PRED_OFFSET) + 1; + clk->rate = clk->parent->rate / div; + } else if (mux == 3) { + clk->rate = clk->parent->rate / 8; + } else { + clk->rate = clk->parent->rate; + } +} + +static int _clk_ipu_di_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) + return -EINVAL; + + reg = __raw_readl(MXC_CCM_CDCDR); + reg &= ~MXC_CCM_CDCDR_DI_CLK_PRED_MASK; + reg |= (div - 1) << MXC_CCM_CDCDR_DI_CLK_PRED_OFFSET; + __raw_writel(reg, MXC_CCM_CDCDR); + + clk->rate = rate; + + return 0; +} + +static unsigned long _clk_ipu_di_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + div = clk->parent->rate / rate; + if (div > 8) + div = 8; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + +static struct clk ipu_di_clk[] = { + { + .name = "ipu_di0_clk", + .id = 0, + .parent = &pll3_sw_clk, + .enable_reg = MXC_CCM_CCGR6, + .enable_shift = MXC_CCM_CCGR6_CG5_OFFSET, + .recalc = _clk_ipu_di_recalc, + .set_parent = _clk_ipu_di_set_parent, + .round_rate = _clk_ipu_di_round_rate, + .set_rate = _clk_ipu_di_set_rate, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "ipu_di1_clk", + .id = 1, + .parent = &pll3_sw_clk, + .enable_reg = MXC_CCM_CCGR6, + .enable_shift = MXC_CCM_CCGR6_CG6_OFFSET, + .recalc = _clk_ipu_di_recalc, + .set_parent = _clk_ipu_di_set_parent, + .round_rate = _clk_ipu_di_round_rate, + .set_rate = _clk_ipu_di_set_rate, + .enable = _clk_enable, + .disable = _clk_disable, + }, +}; + +static int _clk_csi0_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CSCMR2); + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, NULL); + reg = (reg & ~MXC_CCM_CSCMR2_CSI_MCLK1_CLK_SEL_MASK) | + (mux << MXC_CCM_CSCMR2_CSI_MCLK1_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CSCMR2); + + return 0; +} + +static void _clk_csi0_recalc(struct clk *clk) +{ + u32 reg, pred, podf; + + reg = __raw_readl(MXC_CCM_CSCDR4); + pred = ((reg & MXC_CCM_CSCDR4_CSI_MCLK1_CLK_PRED_MASK) >> + MXC_CCM_CSCDR4_CSI_MCLK1_CLK_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CSCDR4_CSI_MCLK1_CLK_PODF_MASK) >> + MXC_CCM_CSCDR4_CSI_MCLK1_CLK_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / (pred * podf); +} + +static unsigned long _clk_csi0_round_rate(struct clk *clk, unsigned long rate) +{ + u32 pre, post; + u32 div = clk->parent->rate / rate; + if (clk->parent->rate % rate) + div++; + + __calc_pre_post_dividers(div, &pre, &post); + + return clk->parent->rate / (pre * post); +} + +static int _clk_csi0_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + u32 div; + u32 pre, post; + + div = clk->parent->rate / rate; + + if ((clk->parent->rate / div) != rate) + return -EINVAL; + + __calc_pre_post_dividers(div, &pre, &post); + + /* Set CSI clock divider */ + reg = __raw_readl(MXC_CCM_CSCDR4) & + ~(MXC_CCM_CSCDR4_CSI_MCLK1_CLK_PRED_MASK | + MXC_CCM_CSCDR4_CSI_MCLK1_CLK_PODF_MASK); + reg |= (post - 1) << MXC_CCM_CSCDR4_CSI_MCLK1_CLK_PODF_OFFSET; + reg |= (pre - 1) << MXC_CCM_CSCDR4_CSI_MCLK1_CLK_PRED_OFFSET; + __raw_writel(reg, MXC_CCM_CSCDR4); + + clk->rate = rate; + return 0; +} + +static struct clk csi0_clk = { + .name = "csi_mclk1", + .parent = &pll3_sw_clk, + .set_parent = _clk_csi0_set_parent, + .recalc = _clk_csi0_recalc, + .round_rate = _clk_csi0_round_rate, + .set_rate = _clk_csi0_set_rate, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR6, + .enable_shift = MXC_CCM_CCGR6_CG2_OFFSET, + .disable = _clk_disable, +}; + +static int _clk_csi1_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CSCMR2); + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, NULL); + reg = (reg & ~MXC_CCM_CSCMR2_CSI_MCLK2_CLK_SEL_MASK) | + (mux << MXC_CCM_CSCMR2_CSI_MCLK2_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CSCMR2); + + return 0; +} + +static void _clk_csi1_recalc(struct clk *clk) +{ + u32 reg, pred, podf; + + reg = __raw_readl(MXC_CCM_CSCDR4); + pred = ((reg & MXC_CCM_CSCDR4_CSI_MCLK2_CLK_PRED_MASK) >> + MXC_CCM_CSCDR4_CSI_MCLK2_CLK_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CSCDR4_CSI_MCLK2_CLK_PODF_MASK) >> + MXC_CCM_CSCDR4_CSI_MCLK2_CLK_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / (pred * podf); +} + +static unsigned long _clk_csi1_round_rate(struct clk *clk, unsigned long rate) +{ + u32 pre, post; + u32 div = clk->parent->rate / rate; + if (clk->parent->rate % rate) + div++; + + __calc_pre_post_dividers(div, &pre, &post); + + return clk->parent->rate / (pre * post); +} + +static int _clk_csi1_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + u32 div; + u32 pre, post; + + div = clk->parent->rate / rate; + + if ((clk->parent->rate / div) != rate) + return -EINVAL; + + __calc_pre_post_dividers(div, &pre, &post); + + /* Set CSI clock divider */ + reg = __raw_readl(MXC_CCM_CSCDR4) & + ~(MXC_CCM_CSCDR4_CSI_MCLK2_CLK_PRED_MASK | + MXC_CCM_CSCDR4_CSI_MCLK2_CLK_PODF_MASK); + reg |= (post - 1) << MXC_CCM_CSCDR4_CSI_MCLK2_CLK_PODF_OFFSET; + reg |= (pre - 1) << MXC_CCM_CSCDR4_CSI_MCLK2_CLK_PRED_OFFSET; + __raw_writel(reg, MXC_CCM_CSCDR4); + + clk->rate = rate; + return 0; +} + +static struct clk csi1_clk = { + .name = "csi_mclk2", + .parent = &pll3_sw_clk, + .set_parent = _clk_csi1_set_parent, + .recalc = _clk_csi1_recalc, + .round_rate = _clk_csi1_round_rate, + .set_rate = _clk_csi1_set_rate, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR6, + .enable_shift = MXC_CCM_CCGR6_CG3_OFFSET, + .disable = _clk_disable, +}; + + +static int _clk_hsc_enable(struct clk *clk) +{ + u32 reg; + + _clk_enable(clk); + /* Handshake with IPU when certain clock rates are changed. */ + reg = __raw_readl(MXC_CCM_CCDR); + reg &= ~MXC_CCM_CCDR_HSC_HS_MASK; + __raw_writel(reg, MXC_CCM_CCDR); + + reg = __raw_readl(MXC_CCM_CLPCR); + reg &= ~MXC_CCM_CLPCR_BYPASS_HSC_LPM_HS; + __raw_writel(reg, MXC_CCM_CLPCR); + + return 0; +} + +static void _clk_hsc_disable(struct clk *clk) +{ + u32 reg; + + _clk_disable(clk); + /* No handshake with HSC as its not enabled. */ + reg = __raw_readl(MXC_CCM_CCDR); + reg |= MXC_CCM_CCDR_IPU_HS_MASK; + __raw_writel(reg, MXC_CCM_CCDR); + + reg = __raw_readl(MXC_CCM_CLPCR); + reg |= MXC_CCM_CLPCR_BYPASS_HSC_LPM_HS; + __raw_writel(reg, MXC_CCM_CLPCR); +} + +static struct clk mipi_esc_clk = { + .name = "mipi_esc_clk", + .parent = &pll2_sw_clk, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG5_OFFSET, +}; + +static struct clk mipi_hsc2_clk = { + .name = "mipi_hsc2_clk", + .parent = &pll2_sw_clk, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG4_OFFSET, + .secondary = &mipi_esc_clk, +}; + +static struct clk mipi_hsc1_clk = { + .name = "mipi_hsc1_clk", + .parent = &pll2_sw_clk, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG3_OFFSET, + .secondary = &mipi_hsc2_clk, +}; + +static struct clk mipi_hsp_clk = { + .name = "mipi_hsp_clk", + .parent = &ipu_clk[0], + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG6_OFFSET, + .enable = _clk_hsc_enable, + .disable = _clk_hsc_disable, + .secondary = &mipi_hsc1_clk, +}; + +static int _clk_tve_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_CSCMR1); + + if (parent == &pll3_sw_clk) { + reg &= ~(MXC_CCM_CSCMR1_TVE_CLK_SEL); + } else if (parent == &osc_clk) { + reg |= MXC_CCM_CSCMR1_TVE_CLK_SEL; + reg &= MXC_CCM_CSCMR1_TVE_EXT_CLK_SEL; + } else if (parent == &ckih_clk) { + reg |= MXC_CCM_CSCMR1_TVE_CLK_SEL; + reg |= MXC_CCM_CSCMR1_TVE_EXT_CLK_SEL; + } else { + BUG(); + } + + __raw_writel(reg, MXC_CCM_CSCMR1); + return 0; +} + +static void _clk_tve_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if ((reg & MXC_CCM_CSCMR1_TVE_CLK_SEL) == 0) { + reg = __raw_readl(MXC_CCM_CDCDR) & + MXC_CCM_CDCDR_TVE_CLK_PRED_MASK; + div = (reg >> MXC_CCM_CDCDR_TVE_CLK_PRED_OFFSET) + 1; + clk->rate = clk->parent->rate / div; + } else { + clk->rate = clk->parent->rate; + } +} + +static unsigned long _clk_tve_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if (reg & MXC_CCM_CSCMR1_TVE_CLK_SEL) + return -EINVAL; + + div = clk->parent->rate / rate; + if (div > 8) + div = 8; + else if (div == 0) + div++; + return clk->parent->rate / div; +} + +static int _clk_tve_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if (reg & MXC_CCM_CSCMR1_TVE_CLK_SEL) + return -EINVAL; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) + return -EINVAL; + + div--; + reg = __raw_readl(MXC_CCM_CDCDR) & ~MXC_CCM_CDCDR_TVE_CLK_PRED_MASK; + reg |= div << MXC_CCM_CDCDR_TVE_CLK_PRED_OFFSET; + __raw_writel(reg, MXC_CCM_CDCDR); + clk->rate = rate; + return 0; +} + +static struct clk tve_clk = { + .name = "tve_clk", + .parent = &pll3_sw_clk, + .set_parent = _clk_tve_set_parent, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG15_OFFSET, + .recalc = _clk_tve_recalc, + .round_rate = _clk_tve_round_rate, + .set_rate = _clk_tve_set_rate, + .enable = _clk_enable, + .disable = _clk_disable, + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, +}; + +static struct clk spba_clk = { + .name = "spba_clk", + .parent = &ipg_clk, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG0_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, +}; + +static void _clk_uart_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CSCDR1); + prediv = ((reg & MXC_CCM_CSCDR1_UART_CLK_PRED_MASK) >> + MXC_CCM_CSCDR1_UART_CLK_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CSCDR1_UART_CLK_PODF_MASK) >> + MXC_CCM_CSCDR1_UART_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} + +static int _clk_uart_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &lp_apm_clk); + reg = __raw_readl(MXC_CCM_CSCMR1) & ~MXC_CCM_CSCMR1_UART_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_UART_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk uart_main_clk = { + .name = "uart_main_clk", + .parent = &pll2_sw_clk, + .recalc = _clk_uart_recalc, + .set_parent = _clk_uart_set_parent, + .flags = RATE_PROPAGATES, +}; + +static struct clk uart1_clk[] = { + { + .name = "uart_clk", + .id = 0, + .parent = &uart_main_clk, + .secondary = &uart1_clk[1], + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG4_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, +#ifdef UART1_DMA_ENABLE + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, +#endif + }, + { + .name = "uart_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .secondary = &aips_tz1_clk, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG3_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, +}; + +static struct clk uart2_clk[] = { + { + .name = "uart_clk", + .id = 1, + .parent = &uart_main_clk, + .secondary = &uart2_clk[1], + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG6_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, +#ifdef UART2_DMA_ENABLE + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, +#endif + }, + { + .name = "uart_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .secondary = &aips_tz1_clk, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG5_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, +}; + +static struct clk uart3_clk[] = { + { + .name = "uart_clk", + .id = 2, + .parent = &uart_main_clk, + .secondary = &uart3_clk[1], + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG8_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, +#ifdef UART3_DMA_ENABLE + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, +#endif + }, + { + .name = "uart_ipg_clk", + .id = 2, + .parent = &ipg_clk, + .secondary = &spba_clk, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG7_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, +}; + +static struct clk gpt_clk[] = { + { + .name = "gpt_clk", + .parent = &ipg_perclk, + .id = 0, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG9_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + .secondary = &gpt_clk[1], + }, + { + .name = "gpt_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG10_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "gpt_32k_clk", + .id = 0, + .parent = &ckil_clk, + }, +}; + +static struct clk pwm1_clk[] = { + { + .name = "pwm", + .parent = &ipg_perclk, + .id = 0, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG6_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + .secondary = &pwm1_clk[1], + }, + { + .name = "pwm_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG5_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "pwm_32k_clk", + .id = 0, + .parent = &ckil_clk, + }, +}; + +static struct clk pwm2_clk[] = { + { + .name = "pwm", + .parent = &ipg_perclk, + .id = 1, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG8_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + .secondary = &pwm2_clk[1], + }, + { + .name = "pwm_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG7_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "pwm_32k_clk", + .id = 1, + .parent = &ckil_clk, + }, +}; + +static struct clk i2c_clk[] = { + { + .name = "i2c_clk", + .id = 0, + .parent = &ipg_perclk, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG9_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "i2c_clk", + .id = 1, + .parent = &ipg_perclk, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG10_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, +}; + +static void _clk_hsi2c_serial_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CSCDR3); + prediv = ((reg & MXC_CCM_CSCDR3_HSI2C_CLK_PRED_MASK) >> + MXC_CCM_CSCDR3_HSI2C_CLK_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CSCDR3_HSI2C_CLK_PODF_MASK) >> + MXC_CCM_CSCDR3_HSI2C_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} + +static struct clk hsi2c_serial_clk = { + .name = "hsi2c_serial_clk", + .id = 0, + .parent = &pll3_sw_clk, + .secondary = &spba_clk, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG11_OFFSET, + .recalc = _clk_hsi2c_serial_recalc, + .enable = _clk_enable, + .disable = _clk_disable, +}; + +static struct clk hsi2c_clk = { + .name = "hsi2c_clk", + .id = 0, + .parent = &ipg_clk, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG12_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, +}; + +static void _clk_cspi_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CSCDR2); + prediv = ((reg & MXC_CCM_CSCDR2_CSPI_CLK_PRED_MASK) >> + MXC_CCM_CSCDR2_CSPI_CLK_PRED_OFFSET) + 1; + if (prediv == 1) + BUG(); + podf = ((reg & MXC_CCM_CSCDR2_CSPI_CLK_PODF_MASK) >> + MXC_CCM_CSCDR2_CSPI_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} + +static int _clk_cspi_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &lp_apm_clk); + reg = __raw_readl(MXC_CCM_CSCMR1) & ~MXC_CCM_CSCMR1_CSPI_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_CSPI_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk cspi_main_clk = { + .name = "cspi_main_clk", + .parent = &pll3_sw_clk, + .recalc = _clk_cspi_recalc, + .set_parent = _clk_cspi_set_parent, + .flags = RATE_PROPAGATES, +}; + +static struct clk cspi1_clk[] = { + { + .name = "cspi_clk", + .id = 0, + .parent = &cspi_main_clk, + .secondary = &cspi1_clk[1], + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG10_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "cspi_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .secondary = &spba_clk, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG9_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, +}; + +static struct clk cspi2_clk[] = { + { + .name = "cspi_clk", + .id = 1, + .parent = &cspi_main_clk, + .secondary = &cspi2_clk[1], + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG12_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "cspi_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .secondary = &aips_tz2_clk, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG11_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, +}; + +static struct clk cspi3_clk[] = { + { + .name = "cspi_clk", + .id = 2, + .parent = &cspi_main_clk, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG13_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + .secondary = &cspi3_clk[1], + }, + { + .name = "cspi_ipg_clk", + .id = 2, + .parent = &ipg_clk, + .secondary = &aips_tz2_clk, + }, +}; + +static int _clk_ssi_lp_apm_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &ckih_clk, &lp_apm_clk, &ckih2_clk, NULL); + reg = __raw_readl(MXC_CCM_CSCMR1) & + ~MXC_CCM_CSCMR1_SSI_APM_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_SSI_APM_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk ssi_lp_apm_clk = { + .name = "ssi_lp_apm_clk", + .parent = &ckih_clk, + .set_parent = _clk_ssi_lp_apm_set_parent, +}; + +static void _clk_ssi1_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CS1CDR); + prediv = ((reg & MXC_CCM_CS1CDR_SSI1_CLK_PRED_MASK) >> + MXC_CCM_CS1CDR_SSI1_CLK_PRED_OFFSET) + 1; + if (prediv == 1) + BUG(); + podf = ((reg & MXC_CCM_CS1CDR_SSI1_CLK_PODF_MASK) >> + MXC_CCM_CS1CDR_SSI1_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} +static int _clk_ssi1_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, + &pll3_sw_clk, &ssi_lp_apm_clk); + reg = __raw_readl(MXC_CCM_CSCMR1) & ~MXC_CCM_CSCMR1_SSI1_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_SSI1_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk ssi1_clk[] = { + { + .name = "ssi_clk", + .id = 0, + .parent = &pll3_sw_clk, + .set_parent = _clk_ssi1_set_parent, + .secondary = &ssi1_clk[1], + .recalc = _clk_ssi1_recalc, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG9_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "ssi_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .secondary = &ssi1_clk[2], + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG8_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "ssi_dep_clk", + .id = 0, + .parent = &aips_tz2_clk, +#ifdef CONFIG_SND_MXC_SOC_IRAM + .secondary = &emi_intr_clk, +#else + .secondary = &emi_fast_clk, +#endif + }, +}; + +static void _clk_ssi2_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CS2CDR); + prediv = ((reg & MXC_CCM_CS2CDR_SSI2_CLK_PRED_MASK) >> + MXC_CCM_CS2CDR_SSI2_CLK_PRED_OFFSET) + 1; + if (prediv == 1) + BUG(); + podf = ((reg & MXC_CCM_CS2CDR_SSI2_CLK_PODF_MASK) >> + MXC_CCM_CS2CDR_SSI2_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} + +static int _clk_ssi2_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, + &pll3_sw_clk, &ssi_lp_apm_clk); + reg = __raw_readl(MXC_CCM_CSCMR1) & ~MXC_CCM_CSCMR1_SSI2_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_SSI2_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk ssi2_clk[] = { + { + .name = "ssi_clk", + .id = 1, + .parent = &pll3_sw_clk, + .set_parent = _clk_ssi2_set_parent, + .secondary = &ssi2_clk[1], + .recalc = _clk_ssi2_recalc, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG11_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "ssi_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .secondary = &ssi2_clk[2], + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG10_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "ssi_dep_clk", + .id = 1, + .parent = &spba_clk, +#ifdef CONFIG_SND_MXC_SOC_IRAM + .secondary = &emi_intr_clk, +#else + .secondary = &emi_fast_clk, +#endif + }, +}; + +static void _clk_ssi_ext1_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + clk->rate = clk->parent->rate; + reg = __raw_readl(MXC_CCM_CSCMR1); + if ((reg & MXC_CCM_CSCMR1_SSI_EXT1_COM_CLK_SEL) == 0) { + reg = __raw_readl(MXC_CCM_CS1CDR); + prediv = ((reg & MXC_CCM_CS1CDR_SSI_EXT1_CLK_PRED_MASK) >> + MXC_CCM_CS1CDR_SSI_EXT1_CLK_PRED_OFFSET) + 1; + if (prediv == 1) + BUG(); + podf = ((reg & MXC_CCM_CS1CDR_SSI_EXT1_CLK_PODF_MASK) >> + MXC_CCM_CS1CDR_SSI_EXT1_CLK_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / (prediv * podf); + } +} + +static int _clk_ssi_ext1_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if (parent == &ssi1_clk[0]) { + reg |= MXC_CCM_CSCMR1_SSI_EXT1_COM_CLK_SEL; + } else { + reg &= ~MXC_CCM_CSCMR1_SSI_EXT1_COM_CLK_SEL; + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &ssi_lp_apm_clk); + reg = (reg & ~MXC_CCM_CSCMR1_SSI_EXT1_CLK_SEL_MASK) | + (mux << MXC_CCM_CSCMR1_SSI_EXT1_CLK_SEL_OFFSET); + } + + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk ssi_ext1_clk = { + .name = "ssi_ext1_clk", + .parent = &pll3_sw_clk, + .set_parent = _clk_ssi_ext1_set_parent, + .recalc = _clk_ssi_ext1_recalc, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG14_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, +}; + +static void _clk_ssi_ext2_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + clk->rate = clk->parent->rate; + reg = __raw_readl(MXC_CCM_CSCMR1); + if ((reg & MXC_CCM_CSCMR1_SSI_EXT2_COM_CLK_SEL) == 0) { + reg = __raw_readl(MXC_CCM_CS2CDR); + prediv = ((reg & MXC_CCM_CS2CDR_SSI_EXT2_CLK_PRED_MASK) >> + MXC_CCM_CS2CDR_SSI_EXT2_CLK_PRED_OFFSET) + 1; + if (prediv == 1) + BUG(); + podf = ((reg & MXC_CCM_CS2CDR_SSI_EXT2_CLK_PODF_MASK) >> + MXC_CCM_CS2CDR_SSI_EXT2_CLK_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / (prediv * podf); + } +} + +static int _clk_ssi_ext2_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if (parent == &ssi2_clk[0]) { + reg |= MXC_CCM_CSCMR1_SSI_EXT2_COM_CLK_SEL; + } else { + reg &= ~MXC_CCM_CSCMR1_SSI_EXT2_COM_CLK_SEL; + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &ssi_lp_apm_clk); + reg = (reg & ~MXC_CCM_CSCMR1_SSI_EXT2_CLK_SEL_MASK) | + (mux << MXC_CCM_CSCMR1_SSI_EXT2_CLK_SEL_OFFSET); + } + + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk ssi_ext2_clk = { + .name = "ssi_ext2_clk", + .parent = &pll3_sw_clk, + .set_parent = _clk_ssi_ext2_set_parent, + .recalc = _clk_ssi_ext2_recalc, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG15_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, +}; + +static struct clk iim_clk = { + .name = "iim_clk", + .parent = &ipg_clk, + .secondary = &aips_tz2_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG15_OFFSET, + .disable = _clk_disable, +}; + +static struct clk tmax1_clk = { + .name = "tmax1_clk", + .id = 0, + .parent = &ahb_clk, + .secondary = &ahb_max_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG0_OFFSET, + .disable = _clk_disable, + }; + +static struct clk tmax2_clk = { + .name = "tmax2_clk", + .id = 0, + .parent = &ahb_clk, + .secondary = &ahb_max_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG1_OFFSET, + .disable = _clk_disable, +}; + +static struct clk tmax3_clk = { + .name = "tmax3_clk", + .id = 0, + .parent = &ahb_clk, + .secondary = &ahb_max_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG2_OFFSET, + .disable = _clk_disable, +}; + +static void _clk_usboh3_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CSCDR1); + prediv = ((reg & MXC_CCM_CSCDR1_USBOH3_CLK_PRED_MASK) >> + MXC_CCM_CSCDR1_USBOH3_CLK_PRED_OFFSET) + 1; + if (prediv == 1) + BUG(); + podf = ((reg & MXC_CCM_CSCDR1_USBOH3_CLK_PODF_MASK) >> + MXC_CCM_CSCDR1_USBOH3_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} + +static int _clk_usboh3_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &lp_apm_clk); + reg = __raw_readl(MXC_CCM_CSCMR1) & ~MXC_CCM_CSCMR1_USBOH3_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_USBOH3_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk usboh3_clk[] = { + { + .name = "usboh3_clk", + .parent = &pll3_sw_clk, + .set_parent = _clk_usboh3_set_parent, + .recalc = _clk_usboh3_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG14_OFFSET, + .disable = _clk_disable, + .secondary = &usboh3_clk[1], + .flags = AHB_MED_SET_POINT | CPU_FREQ_TRIG_UPDATE, + }, + { + .name = "usb_sec_clk", + .parent = &tmax2_clk, + .secondary = &emi_fast_clk, + }, +}; +static struct clk usb_ahb_clk = { + .name = "usb_ahb_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG13_OFFSET, + .disable = _clk_disable, +}; + +static void _clk_usb_phy_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + if (clk->parent == &pll3_sw_clk) { + reg = __raw_readl(MXC_CCM_CDCDR); + prediv = ((reg & MXC_CCM_CDCDR_USB_PHY_PRED_MASK) >> + MXC_CCM_CDCDR_USB_PHY_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CDCDR_USB_PHY_PODF_MASK) >> + MXC_CCM_CDCDR_USB_PHY_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); + } else + clk->rate = clk->parent->rate; +} + +static int _clk_usb_phy_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if (parent == &osc_clk) + reg &= ~MXC_CCM_CSCMR1_USB_PHY_CLK_SEL; + else if (parent == &pll3_sw_clk) + reg |= MXC_CCM_CSCMR1_USB_PHY_CLK_SEL; + else + BUG(); + + __raw_writel(reg, MXC_CCM_CSCMR1); + return 0; +} + +static struct clk usb_phy_clk = { + .name = "usb_phy_clk", + .parent = &pll3_sw_clk, + .secondary = &tmax3_clk, + .set_parent = _clk_usb_phy_set_parent, + .recalc = _clk_usb_phy_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG0_OFFSET, + .disable = _clk_disable, +}; + +static struct clk esdhc_dep_clks = { + .name = "sd_dep_clk", + .parent = &spba_clk, + .secondary = &emi_fast_clk, +}; + + +static void _clk_esdhc1_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CSCDR1); + prediv = ((reg & MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PRED_MASK) >> + MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PODF_MASK) >> + MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} + +static int _clk_esdhc1_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &lp_apm_clk); + reg = __raw_readl(MXC_CCM_CSCMR1) & + ~MXC_CCM_CSCMR1_ESDHC1_MSHC1_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_ESDHC1_MSHC1_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk esdhc1_clk[] = { + { + .name = "esdhc_clk", + .id = 0, + .parent = &pll3_sw_clk, + .set_parent = _clk_esdhc1_set_parent, + .recalc = _clk_esdhc1_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG1_OFFSET, + .disable = _clk_disable, + .secondary = &esdhc1_clk[1], + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, + }, + { + .name = "esdhc_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .secondary = &esdhc1_clk[2], + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG0_OFFSET, + .disable = _clk_disable, + }, + { + .name = "esdhc_sec_clk", + .id = 0, + .parent = &tmax3_clk, + .secondary = &esdhc_dep_clks, + }, + +}; + +static void _clk_esdhc2_recalc(struct clk *clk) +{ + u32 reg, prediv, podf; + + reg = __raw_readl(MXC_CCM_CSCDR1); + prediv = ((reg & MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PRED_MASK) >> + MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PODF_MASK) >> + MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PODF_OFFSET) + 1; + + clk->rate = clk->parent->rate / (prediv * podf); +} + +static int _clk_esdhc2_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &lp_apm_clk); + reg = __raw_readl(MXC_CCM_CSCMR1) & + ~MXC_CCM_CSCMR1_ESDHC2_MSHC2_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_ESDHC2_MSHC2_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk esdhc2_clk[] = { + { + .name = "esdhc_clk", + .id = 1, + .parent = &pll3_sw_clk, + .set_parent = _clk_esdhc2_set_parent, + .recalc = _clk_esdhc2_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG3_OFFSET, + .disable = _clk_disable, + .secondary = &esdhc2_clk[1], + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, + }, + { + .name = "esdhc_ipg_clk", + .id = 1, + .parent = &ipg_clk, + .secondary = &esdhc2_clk[2], + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG2_OFFSET, + .disable = _clk_disable, + }, + { + .name = "esdhc_sec_clk", + .id = 0, + .parent = &tmax2_clk, + .secondary = &esdhc_dep_clks, + }, +}; + +static int _clk_esdhc3_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if (parent == &esdhc1_clk[0]) + reg &= ~MXC_CCM_CSCMR1_ESDHC3_CLK_SEL; + else if (parent == &esdhc2_clk[0]) + reg |= MXC_CCM_CSCMR1_ESDHC3_CLK_SEL; + else + BUG(); + + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk esdhc3_clk[] = { + { + .name = "esdhc_clk", + .id = 2, + .parent = &esdhc1_clk[0], + .set_parent = _clk_esdhc3_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG5_OFFSET, + .disable = _clk_disable, + .secondary = &esdhc3_clk[1], + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, + }, + { + .name = "esdhc_ipg_clk", + .id = 2, + .parent = &ipg_clk, + .secondary = &esdhc3_clk[2], + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG4_OFFSET, + .disable = _clk_disable, + }, + { + .name = "esdhc_sec_clk", + .id = 0, + .parent = &ahb_max_clk, + .secondary = &esdhc_dep_clks, + }, +}; + + +static int _clk_esdhc4_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg; + + reg = __raw_readl(MXC_CCM_CSCMR1); + if (parent == &esdhc1_clk[0]) + reg &= ~MXC_CCM_CSCMR1_ESDHC4_CLK_SEL; + else if (parent == &esdhc2_clk[0]) + reg |= MXC_CCM_CSCMR1_ESDHC4_CLK_SEL; + else + BUG(); + + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk esdhc4_clk[] = { + { + .name = "esdhc_clk", + .id = 3, + .parent = &esdhc1_clk[0], + .set_parent = _clk_esdhc4_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG7_OFFSET, + .disable = _clk_disable, + .secondary = &esdhc3_clk[1], + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, + }, + { + .name = "esdhc_ipg_clk", + .id = 3, + .parent = &ipg_clk, + .secondary = &esdhc3_clk[2], + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR3, + .enable_shift = MXC_CCM_CCGR3_CG6_OFFSET, + .disable = _clk_disable, + }, + { + .name = "esdhc_sec_clk", + .id = 0, + .parent = &tmax3_clk, + .secondary = &esdhc_dep_clks, + }, +}; + +static int _clk_sim_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, NULL); + reg = __raw_readl(MXC_CCM_CSCMR2) & ~MXC_CCM_CSCMR2_SIM_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR2_SIM_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR2); + + return 0; +} + +static void _clk_sim_recalc(struct clk *clk) +{ + u32 reg, pred, podf; + + reg = __raw_readl(MXC_CCM_CSCDR2); + pred = ((reg & MXC_CCM_CSCDR2_SIM_CLK_PRED_MASK) >> + MXC_CCM_CSCDR2_SIM_CLK_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CSCDR2_SIM_CLK_PODF_MASK) >> + MXC_CCM_CSCDR2_SIM_CLK_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / (pred * podf); +} + +static unsigned long _clk_sim_round_rate(struct clk *clk, unsigned long rate) +{ + u32 pre, post; + u32 div = clk->parent->rate / rate; + if (clk->parent->rate % rate) + div++; + + __calc_pre_post_dividers(div, &pre, &post); + + return clk->parent->rate / (pre * post); +} + +static int _clk_sim_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg; + u32 div; + u32 pre, post; + + div = clk->parent->rate / rate; + + if ((clk->parent->rate / div) != rate) + return -EINVAL; + + __calc_pre_post_dividers(div, &pre, &post); + + /* Set SIM clock divider */ + reg = __raw_readl(MXC_CCM_CSCDR2) & + ~(MXC_CCM_CSCDR2_SIM_CLK_PRED_MASK | + MXC_CCM_CSCDR2_SIM_CLK_PODF_MASK); + reg |= (post - 1) << MXC_CCM_CSCDR2_SIM_CLK_PODF_OFFSET; + reg |= (pre - 1) << MXC_CCM_CSCDR2_SIM_CLK_PRED_OFFSET; + __raw_writel(reg, MXC_CCM_CSCDR2); + + clk->rate = rate; + return 0; + +} + +static struct clk sim_clk[] = { + { + .name = "sim_clk", + .parent = &pll3_sw_clk, + .set_parent = _clk_sim_set_parent, + .secondary = &sim_clk[1], + .recalc = _clk_sim_recalc, + .round_rate = _clk_sim_round_rate, + .set_rate = _clk_sim_set_rate, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG2_OFFSET, + .disable = _clk_disable, + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, + }, + { + .name = "sim_ipg_clk", + .parent = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG1_OFFSET, + .disable = _clk_disable, + }, +}; + +static void _clk_nfc_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CBCDR); + div = ((reg & MXC_CCM_CBCDR_NFC_PODF_MASK) >> + MXC_CCM_CBCDR_NFC_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / div; +} + +static unsigned long _clk_nfc_round_rate(struct clk *clk, + unsigned long rate) +{ + u32 div; + + /* + * Compute the divider we'd have to use to reach the target rate. + */ + + div = clk->parent->rate / rate; + + /* + * If there's a remainder after the division, then we have to increment + * the divider. There are two reasons for this: + * + * 1) The frequency we round to must be LESS THAN OR EQUAL to the + * target. We aren't allowed to round to a frequency that is higher + * than the target. + * + * 2) This also catches the case where target rate is less than the + * parent rate, which implies a divider of zero. We can't allow a + * divider of zero. + */ + + if (clk->parent->rate % rate) + div++; + + /* + * The divider for this clock is 3 bits wide, so we can't possibly + * divide the parent by more than eight. + */ + + if (div > 8) + return -EINVAL; + + return clk->parent->rate / div; + +} + +static int _clk_nfc_set_rate(struct clk *clk, unsigned long rate) +{ + u32 reg, div; + struct timespec nstimeofday; + struct timespec curtime; + + div = clk->parent->rate / rate; + if (div == 0) + div++; + if (((clk->parent->rate / div) != rate) || (div > 8)) + return -EINVAL; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.enable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.enable(&emi_slow_clk); + + + reg = __raw_readl(MXC_CCM_CBCDR); + reg &= ~MXC_CCM_CBCDR_NFC_PODF_MASK; + reg |= (div - 1) << MXC_CCM_CBCDR_NFC_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CBCDR); + getnstimeofday(&nstimeofday); + while (__raw_readl(MXC_CCM_CDHIPR) & + MXC_CCM_CDHIPR_NFC_IPG_INT_MEM_PODF_BUSY){ + getnstimeofday(&curtime); + if ((curtime.tv_nsec - nstimeofday.tv_nsec) > SPIN_DELAY) + panic("_clk_nfc_set_rate failed\n"); + } + clk->rate = rate; + + if (emi_fast_clk.usecount == 0) + emi_fast_clk.disable(&emi_fast_clk); + if (emi_slow_clk.usecount == 0) + emi_slow_clk.disable(&emi_slow_clk); + + return 0; +} + +static struct clk emi_enfc_clk = { + .name = "nfc_clk", + .parent = &emi_slow_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG10_OFFSET, + .disable = _clk_disable_inwait, + .recalc = _clk_nfc_recalc, + .round_rate = _clk_nfc_round_rate, + .set_rate = _clk_nfc_set_rate, +}; + +static int _clk_spdif_xtal_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + mux = _get_mux(parent, &osc_clk, &ckih_clk, &ckih2_clk, NULL); + reg = __raw_readl(MXC_CCM_CSCMR1) & ~MXC_CCM_CSCMR1_SPDIF_CLK_SEL_MASK; + reg |= mux << MXC_CCM_CSCMR1_SPDIF_CLK_SEL_OFFSET; + __raw_writel(reg, MXC_CCM_CSCMR1); + + return 0; +} + +static struct clk spdif_xtal_clk = { + .name = "spdif_xtal_clk", + .parent = &osc_clk, + .set_parent = _clk_spdif_xtal_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG15_OFFSET, + .disable = _clk_disable, +}; + +static int _clk_spdif0_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CSCMR2); + reg |= MXC_CCM_CSCMR2_SPDIF0_COM; + if (parent != &ssi1_clk[0]) { + reg &= ~MXC_CCM_CSCMR2_SPDIF0_COM; + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &spdif_xtal_clk); + reg = (reg & ~MXC_CCM_CSCMR2_SPDIF0_CLK_SEL_MASK) | + (mux << MXC_CCM_CSCMR2_SPDIF0_CLK_SEL_OFFSET); + } + __raw_writel(reg, MXC_CCM_CSCMR2); + + return 0; +} + +static void _clk_spdif0_recalc(struct clk *clk) +{ + u32 reg, pred, podf; + + if (clk->parent == &ssi1_clk[0]) { + clk->rate = clk->parent->rate; + } else { + reg = __raw_readl(MXC_CCM_CDCDR); + pred = ((reg & MXC_CCM_CDCDR_SPDIF0_CLK_PRED_MASK) >> + MXC_CCM_CDCDR_SPDIF0_CLK_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CDCDR_SPDIF0_CLK_PODF_MASK) >> + MXC_CCM_CDCDR_SPDIF0_CLK_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / (pred * podf); + } +} + +static struct clk spdif0_clk[] = { + { + .name = "spdif_clk", + .id = 0, + .parent = &pll3_sw_clk, + .set_parent = _clk_spdif0_set_parent, + .recalc = _clk_spdif0_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG13_OFFSET, + .disable = _clk_disable, + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, + }, + { + .name = "spdif_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .secondary = &spba_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG15_OFFSET, + .disable = _clk_disable, + }, +}; + +static int _clk_spdif1_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CSCMR2); + reg |= MXC_CCM_CSCMR2_SPDIF1_COM; + if (parent != &ssi2_clk[0]) { + reg &= ~MXC_CCM_CSCMR2_SPDIF1_COM; + mux = _get_mux(parent, &pll1_sw_clk, &pll2_sw_clk, &pll3_sw_clk, + &spdif_xtal_clk); + reg = (reg & ~MXC_CCM_CSCMR2_SPDIF1_CLK_SEL_MASK) | + (mux << MXC_CCM_CSCMR2_SPDIF1_CLK_SEL_OFFSET); + } + __raw_writel(reg, MXC_CCM_CSCMR2); + + return 0; +} + +static void _clk_spdif1_recalc(struct clk *clk) +{ + u32 reg, pred, podf; + + if (clk->parent == &ssi2_clk[0]) { + clk->rate = clk->parent->rate; + } else { + reg = __raw_readl(MXC_CCM_CDCDR); + pred = ((reg & MXC_CCM_CDCDR_SPDIF1_CLK_PRED_MASK) >> + MXC_CCM_CDCDR_SPDIF1_CLK_PRED_OFFSET) + 1; + podf = ((reg & MXC_CCM_CDCDR_SPDIF1_CLK_PODF_MASK) >> + MXC_CCM_CDCDR_SPDIF1_CLK_PODF_OFFSET) + 1; + clk->rate = clk->parent->rate / (pred * podf); + } +} + +static struct clk spdif1_clk[] = { + { + .name = "spdif_clk", + .id = 1, + .parent = &pll3_sw_clk, + .set_parent = _clk_spdif1_set_parent, + .recalc = _clk_spdif1_recalc, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG14_OFFSET, + .disable = _clk_disable, + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, + }, + { + .name = "spdif_ipg_clk", + .id = 0, + .parent = &ipg_clk, + .secondary = &spba_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG15_OFFSET, + .disable = _clk_disable, + }, +}; + +static int _clk_ddr_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, reg2, mux; + struct timespec nstimeofday; + struct timespec curtime; + + reg = __raw_readl(MXC_CCM_CBCMR); + reg2 = __raw_readl(MXC_CCM_CBCDR); + mux = _get_mux_ddr(parent, &axi_a_clk, &axi_b_clk, &emi_slow_clk, &ahb_clk, &ddr_hf_clk); + if (mux < 4) { + reg = (reg & ~MXC_CCM_CBCMR_DDR_CLK_SEL_MASK) | + (mux << MXC_CCM_CBCMR_DDR_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CBCMR); + reg2 = (reg2 & ~MXC_CCM_CBCDR_DDR_HF_SEL); + } else { + reg2 = (reg2 & ~MXC_CCM_CBCDR_DDR_HF_SEL) | + (MXC_CCM_CBCDR_DDR_HF_SEL); + } + __raw_writel(reg2, MXC_CCM_CBCDR); + getnstimeofday(&nstimeofday); + while (__raw_readl(MXC_CCM_CDHIPR) & + MXC_CCM_CDHIPR_DDR_HF_CLK_SEL_BUSY){ + getnstimeofday(&curtime); + if ((curtime.tv_nsec - nstimeofday.tv_nsec) > SPIN_DELAY) + panic("_clk_ddr_set_parent failed\n"); + } + return 0; +} + +static struct clk ddr_clk = { + .name = "ddr_clk", + .parent = &ddr_hf_clk, + .set_parent = _clk_ddr_set_parent, + .flags = RATE_PROPAGATES, +}; + +static int _clk_arm_axi_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + reg = __raw_readl(MXC_CCM_CBCMR); + mux = _get_mux(parent, &axi_a_clk, &axi_b_clk, &emi_slow_clk, &ahb_clk); + reg = (reg & ~MXC_CCM_CBCMR_ARM_AXI_CLK_SEL_MASK) | + (mux << MXC_CCM_CBCMR_ARM_AXI_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CBCMR); + + return 0; +} + +static struct clk arm_axi_clk = { + .name = "arm_axi_clk", + .parent = &axi_a_clk, + .set_parent = _clk_arm_axi_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR0, + .enable_shift = MXC_CCM_CCGR0_CG1_OFFSET, + .disable = _clk_disable, +}; + +static int _clk_vpu_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + reg = __raw_readl(MXC_CCM_CBCMR); + mux = _get_mux(parent, &axi_a_clk, &axi_b_clk, &emi_slow_clk, &ahb_clk); + reg = (reg & ~MXC_CCM_CBCMR_VPU_AXI_CLK_SEL_MASK) | + (mux << MXC_CCM_CBCMR_VPU_AXI_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CBCMR); + + return 0; +} + +static struct clk vpu_clk[] = { + { + .name = "vpu_clk", + .set_parent = _clk_vpu_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG4_OFFSET, + .disable = _clk_disable, + .secondary = &vpu_clk[1], + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, + }, + { + .name = "vpu_core_clk", + .set_parent = _clk_vpu_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG3_OFFSET, + .disable = _clk_disable, + .secondary = &vpu_clk[2], + }, + { + .name = "vpu_emi_clk", + .parent = &emi_fast_clk, +#ifdef CONFIG_MXC_VPU_IRAM + .secondary = &emi_intr_clk, +#endif + } +}; + +static int _clk_lpsr_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + reg = __raw_readl(MXC_CCM_CLPCR); + mux = _get_mux(parent, &ckil_clk, &fpm_clk, &fpm_div2_clk, NULL); + reg = (reg & ~MXC_CCM_CLPCR_LPSR_CLK_SEL_MASK) | + (mux << MXC_CCM_CLPCR_LPSR_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CLPCR); + + return 0; +} + +static struct clk lpsr_clk = { + .name = "lpsr_clk", + .parent = &ckil_clk, + .set_parent = _clk_lpsr_set_parent, +}; + +static void _clk_pgc_recalc(struct clk *clk) +{ + u32 reg, div; + + reg = __raw_readl(MXC_CCM_CSCDR1); + div = (reg & MXC_CCM_CSCDR1_PGC_CLK_PODF_MASK) >> + MXC_CCM_CSCDR1_PGC_CLK_PODF_OFFSET; + div = 1 >> div; + clk->rate = clk->parent->rate / div; +} + +static struct clk pgc_clk = { + .name = "pgc_clk", + .parent = &ipg_clk, + .recalc = _clk_pgc_recalc, +}; + +/*usb OTG clock */ + +static struct clk usb_clk = { + .name = "usb_clk", + .rate = 60000000, +}; + +static struct clk usb_utmi_clk = { + .name = "usb_utmi_clk", + .enable = _clk_enable, + .enable_reg = MXC_CCM_CSCMR1, + .enable_shift = MXC_CCM_CSCMR1_USB_PHY_CLK_SEL_OFFSET, + .disable = _clk_disable, +}; + +static struct clk rtc_clk = { + .name = "rtc_clk", + .parent = &ckil_clk, + .secondary = &ipg_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG14_OFFSET, + .disable = _clk_disable, +}; + +static struct clk ata_clk = { + .name = "ata_clk", + .parent = &ipg_clk, + .secondary = &spba_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG0_OFFSET, + .disable = _clk_disable, + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, +}; + +static struct clk owire_clk = { + .name = "owire_clk", + .parent = &ipg_perclk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG11_OFFSET, + .disable = _clk_disable, +}; + + +static struct clk fec_clk[] = { + { + .name = "fec_clk", + .parent = &ipg_clk, + .secondary = &fec_clk[1], + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR2, + .enable_shift = MXC_CCM_CCGR2_CG12_OFFSET, + .disable = _clk_disable, + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, + }, + { + .name = "fec_sec1_clk", + .parent = &tmax2_clk, + .secondary = &fec_clk[2], + }, + { + .name = "fec_sec2_clk", + .parent = &aips_tz2_clk, + .secondary = &emi_fast_clk, + }, +}; + +static struct clk sahara_clk[] = { + { + .name = "sahara_clk", + .parent = &ahb_clk, + .secondary = &sahara_clk[1], + .enable_reg = MXC_CCM_CCGR4, + .enable_shift = MXC_CCM_CCGR4_CG7_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "sahara_sec_clk", + .parent = &tmax1_clk, + .secondary = &emi_fast_clk, + } +}; + +static struct clk scc_clk[] = { + { + .name = "scc_clk", + .parent = &ahb_clk, + .secondary = &scc_clk[1], + .enable_reg = MXC_CCM_CCGR1, + .enable_shift = MXC_CCM_CCGR1_CG15_OFFSET, + .enable = _clk_enable, + .disable = _clk_disable, + }, + { + .name = "scc_sec_clk", + .parent = &tmax1_clk, + .secondary = &emi_fast_clk, + } +}; + +static int _clk_gpu3d_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CBCMR); + mux = _get_mux(parent, &axi_a_clk, &axi_b_clk, &emi_slow_clk, &ahb_clk); + reg = (reg & ~MXC_CCM_CBCMR_GPU_CLK_SEL_MASK) | + (mux << MXC_CCM_CBCMR_GPU_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CBCMR); + + return 0; +} + +static struct clk gpu3d_clk = { + .name = "gpu3d_clk", + .parent = &axi_a_clk, + .set_parent = _clk_gpu3d_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG1_OFFSET, + .disable = _clk_disable, + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, +}; + +static struct clk garb_clk = { + .name = "garb_clk", + .parent = &axi_a_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR5, + .enable_shift = MXC_CCM_CCGR5_CG2_OFFSET, + .disable = _clk_disable, +}; + +static struct clk emi_garb_clk = { + .name = "emi_garb_clk", + .parent = &axi_a_clk, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR6, + .enable_shift = MXC_CCM_CCGR6_CG4_OFFSET, + .disable = _clk_disable, +}; + +static int _clk_gpu2d_set_parent(struct clk *clk, struct clk *parent) +{ + u32 reg, mux; + + reg = __raw_readl(MXC_CCM_CBCMR); + mux = _get_mux(parent, &axi_a_clk, &axi_b_clk, &emi_slow_clk, &ahb_clk); + reg = (reg & ~MXC_CCM_CBCMR_GPU2D_CLK_SEL_MASK) | + (mux << MXC_CCM_CBCMR_GPU2D_CLK_SEL_OFFSET); + __raw_writel(reg, MXC_CCM_CBCMR); + + return 0; +} + +static struct clk gpu2d_clk = { + .name = "gpu2d_clk", + .parent = &axi_a_clk, + .set_parent = _clk_gpu2d_set_parent, + .enable = _clk_enable, + .enable_reg = MXC_CCM_CCGR6, + .enable_shift = MXC_CCM_CCGR6_CG7_OFFSET, + .disable = _clk_disable, + .flags = AHB_HIGH_SET_POINT | CPU_FREQ_TRIG_UPDATE, +}; + +static struct clk *mxc_clks[] = { + &osc_clk, + &ckih_clk, + &ckih2_clk, + &ckil_clk, + &fpm_clk, + &fpm_div2_clk, + &pll1_main_clk, + &pll1_sw_clk, + &pll2_sw_clk, + &pll3_sw_clk, + &gpc_dvfs_clk, + &lp_apm_clk, + &cpu_clk, + &periph_apm_clk, + &main_bus_clk, + &axi_a_clk, + &axi_b_clk, + &ahb_clk, + &ahb_max_clk, + &ipg_clk, + &ipg_perclk, + &ahbmux1_clk, + &ahbmux2_clk, + &aips_tz1_clk, + &aips_tz2_clk, + &sdma_clk[0], + &sdma_clk[1], + &ipu_clk[0], + &ipu_clk[1], + &ipu_di_clk[0], + &ipu_di_clk[1], + &tve_clk, + &csi0_clk, + &csi1_clk, + &uart_main_clk, + &uart1_clk[0], + &uart1_clk[1], + &uart2_clk[0], + &uart2_clk[1], + &uart3_clk[0], + &uart3_clk[1], + &spba_clk, + &i2c_clk[0], + &i2c_clk[1], + &hsi2c_clk, + &hsi2c_serial_clk, + &gpt_clk[0], + &gpt_clk[1], + &gpt_clk[2], + &pwm1_clk[0], + &pwm1_clk[1], + &pwm1_clk[2], + &pwm2_clk[0], + &pwm2_clk[1], + &pwm2_clk[2], + &cspi_main_clk, + &cspi1_clk[0], + &cspi1_clk[1], + &cspi2_clk[0], + &cspi2_clk[1], + &cspi3_clk[0], + &cspi3_clk[1], + &ssi_lp_apm_clk, + &ssi1_clk[0], + &ssi1_clk[1], + &ssi1_clk[2], + &ssi2_clk[0], + &ssi2_clk[1], + &ssi2_clk[2], + &ssi_ext1_clk, + &ssi_ext2_clk, + &iim_clk, + &tmax1_clk, + &tmax2_clk, + &tmax3_clk, + &usboh3_clk[0], + &usboh3_clk[1], + &usb_ahb_clk, + &usb_phy_clk, + &usb_utmi_clk, + &usb_clk, + &esdhc1_clk[0], + &esdhc1_clk[1], + &esdhc2_clk[0], + &esdhc2_clk[1], + &esdhc3_clk[0], + &esdhc3_clk[1], + &esdhc4_clk[0], + &esdhc4_clk[1], + &esdhc_dep_clks, + &sim_clk[0], + &sim_clk[1], + &emi_slow_clk, + &ddr_clk, + &emi_enfc_clk, + &emi_fast_clk, + &emi_intr_clk, + &spdif_xtal_clk, + &spdif0_clk[0], + &spdif0_clk[1], + &spdif1_clk[0], + &spdif1_clk[1], + &arm_axi_clk, + &vpu_clk[0], + &vpu_clk[1], + &vpu_clk[2], + &lpsr_clk, + &pgc_clk, + &rtc_clk, + &ata_clk, + &owire_clk, + &fec_clk[0], + &fec_clk[1], + &fec_clk[2], + &mipi_hsc1_clk, + &mipi_hsc2_clk, + &mipi_esc_clk, + &mipi_hsp_clk, + &sahara_clk[0], + &sahara_clk[1], + &gpu3d_clk, + &garb_clk, + &emi_garb_clk, + &ddr_hf_clk, + &gpu2d_clk, + &scc_clk[0], + &scc_clk[1], +}; + +static void clk_tree_init(void) +{ + u32 reg, reg2, dp_ctl; + + ipg_perclk.set_parent(&ipg_perclk, &lp_apm_clk); + + /* + *Initialise the IPG PER CLK dividers to 3. IPG_PER_CLK should be at + * 8MHz, its derived from lp_apm. + */ + reg = __raw_readl(MXC_CCM_CBCDR); + reg &= ~MXC_CCM_CBCDR_PERCLK_PRED1_MASK; + reg &= ~MXC_CCM_CBCDR_PERCLK_PRED2_MASK; + reg &= ~MXC_CCM_CBCDR_PERCLK_PODF_MASK; + reg |= (2 << MXC_CCM_CBCDR_PERCLK_PRED1_OFFSET); + __raw_writel(reg, MXC_CCM_CBCDR); + + /* set pll1_main_clk parent */ + pll1_main_clk.parent = &osc_clk; + dp_ctl = __raw_readl(pll_base[0] + MXC_PLL_DP_CTL); + if ((dp_ctl & MXC_PLL_DP_CTL_REF_CLK_SEL_MASK) == 0) + pll1_main_clk.parent = &fpm_clk; + /* set pll2_sw_clk parent */ + pll2_sw_clk.parent = &osc_clk; + dp_ctl = __raw_readl(pll_base[1] + MXC_PLL_DP_CTL); + if ((dp_ctl & MXC_PLL_DP_CTL_REF_CLK_SEL_MASK) == 0) + pll2_sw_clk.parent = &fpm_clk; + /* set pll3_clk parent */ + pll3_sw_clk.parent = &osc_clk; + dp_ctl = __raw_readl(pll_base[2] + MXC_PLL_DP_CTL); + if ((dp_ctl & MXC_PLL_DP_CTL_REF_CLK_SEL_MASK) == 0) + pll3_sw_clk.parent = &fpm_clk; + + /* set emi_slow_clk parent */ + emi_slow_clk.parent = &main_bus_clk; + reg = __raw_readl(MXC_CCM_CBCDR); + if ((reg & MXC_CCM_CBCDR_EMI_CLK_SEL) != 0) + emi_slow_clk.parent = &ahb_clk; + + /* set ipg_perclk parent */ + ipg_perclk.parent = &lp_apm_clk; + reg = __raw_readl(MXC_CCM_CBCMR); + if ((reg & MXC_CCM_CBCMR_PERCLK_IPG_CLK_SEL) != 0) { + ipg_perclk.parent = &ipg_clk; + } else { + if ((reg & MXC_CCM_CBCMR_PERCLK_LP_APM_CLK_SEL) == 0) + ipg_perclk.parent = &main_bus_clk; + } + + /* set DDR clock parent */ + reg = __raw_readl(MXC_CCM_CBCMR) & MXC_CCM_CBCMR_DDR_CLK_SEL_MASK; + reg >>= MXC_CCM_CBCMR_DDR_CLK_SEL_OFFSET; + reg2 = __raw_readl(MXC_CCM_CBCDR) & MXC_CCM_CBCDR_DDR_HF_SEL; + reg2 >>= MXC_CCM_CBCDR_DDR_HF_SEL_OFFSET; + + if (reg2) { + ddr_clk.parent = &ddr_hf_clk; + } else { + if (reg == 0) { + ddr_clk.parent = &axi_a_clk; + } else if (reg == 1) { + ddr_clk.parent = &axi_b_clk; + } else if (reg == 2) { + ddr_clk.parent = &emi_slow_clk; + } else { + ddr_clk.parent = &ahb_clk; + } + } +} + +int __init mx51_clocks_init(unsigned long ckil, unsigned long osc, unsigned long ckih1, unsigned long ckih2) +{ + struct clk **clkp; + int i = 0, j = 0, reg; + int wp_cnt = 0; + + /* Turn off all possible clocks */ + if (mxc_jtag_enabled) { + __raw_writel(1 << MXC_CCM_CCGR0_CG0_OFFSET | + 1 << MXC_CCM_CCGR0_CG1_OFFSET | + 1 << MXC_CCM_CCGR0_CG2_OFFSET | + 1 << MXC_CCM_CCGR0_CG3_OFFSET | + 1 << MXC_CCM_CCGR0_CG4_OFFSET | + 1 << MXC_CCM_CCGR0_CG8_OFFSET | + 1 << MXC_CCM_CCGR0_CG9_OFFSET | + 1 << MXC_CCM_CCGR0_CG12_OFFSET | + 1 << MXC_CCM_CCGR0_CG13_OFFSET | + 1 << MXC_CCM_CCGR0_CG14_OFFSET, MXC_CCM_CCGR0); + } else { + __raw_writel(1 << MXC_CCM_CCGR0_CG0_OFFSET | + 1 << MXC_CCM_CCGR0_CG1_OFFSET | + 1 << MXC_CCM_CCGR0_CG2_OFFSET | + 1 << MXC_CCM_CCGR0_CG3_OFFSET | + 1 << MXC_CCM_CCGR0_CG8_OFFSET | + 1 << MXC_CCM_CCGR0_CG9_OFFSET | + 1 << MXC_CCM_CCGR0_CG12_OFFSET | + 1 << MXC_CCM_CCGR0_CG13_OFFSET | + 1 << MXC_CCM_CCGR0_CG14_OFFSET, MXC_CCM_CCGR0); + } + __raw_writel(0, MXC_CCM_CCGR1); + __raw_writel(0, MXC_CCM_CCGR2); + __raw_writel(0, MXC_CCM_CCGR3); + __raw_writel(1 << MXC_CCM_CCGR4_CG8_OFFSET, MXC_CCM_CCGR4); + + __raw_writel(1 << MXC_CCM_CCGR5_CG2_OFFSET | + 3 << MXC_CCM_CCGR5_CG6_OFFSET | + 1 << MXC_CCM_CCGR5_CG7_OFFSET | + 1 << MXC_CCM_CCGR5_CG8_OFFSET | + 3 << MXC_CCM_CCGR5_CG9_OFFSET | + 1 << MXC_CCM_CCGR5_CG10_OFFSET | + 3 << MXC_CCM_CCGR5_CG11_OFFSET, MXC_CCM_CCGR5); + + __raw_writel(1 << MXC_CCM_CCGR6_CG4_OFFSET, MXC_CCM_CCGR6); + + ckil_clk.rate = ckil; + osc_clk.rate = osc; + ckih_clk.rate = ckih1; + ckih2_clk.rate = ckih2; + + clk_tree_init(); + + for (clkp = mxc_clks; clkp < mxc_clks + ARRAY_SIZE(mxc_clks); clkp++) + clk_register(*clkp); + + /*Setup the LPM bypass bits */ + reg = __raw_readl(MXC_CCM_CLPCR); + reg |= MXC_CCM_CLPCR_BYPASS_HSC_LPM_HS + | MXC_CCM_CLPCR_BYPASS_IPU_LPM_HS + | MXC_CCM_CLPCR_BYPASS_RTIC_LPM_HS + | MXC_CCM_CLPCR_BYPASS_SCC_LPM_HS + | MXC_CCM_CLPCR_BYPASS_SDMA_LPM_HS; + __raw_writel(reg, MXC_CCM_CLPCR); + + /* This will propagate to all children and init all the clock rates */ + propagate_rate(&osc_clk); + propagate_rate(&ckih_clk); + propagate_rate(&ckih2_clk); + propagate_rate(&ckil_clk); + propagate_rate(&pll1_sw_clk); + propagate_rate(&pll2_sw_clk); + + clk_enable(&cpu_clk); + clk_enable(&main_bus_clk); + + reg = __raw_readl(MXC_CCM_CBCDR) & MXC_CCM_CBCDR_DDR_HF_SEL; + reg >>= MXC_CCM_CBCDR_DDR_HF_SEL_OFFSET; + + if (reg) + clk_set_parent(&ddr_clk, &ddr_hf_clk); + else + clk_set_parent(&ddr_clk, &axi_a_clk); + + if (cpu_is_mx51_rev(CHIP_REV_2_0) < 0) { + clk_set_parent(&vpu_clk[0], &ahb_clk); + clk_set_parent(&vpu_clk[1], &ahb_clk); + } else { + clk_set_parent(&vpu_clk[0], &axi_a_clk); + clk_set_parent(&vpu_clk[1], &axi_a_clk); + } + + clk_set_parent(&gpu3d_clk, &axi_a_clk); + clk_set_parent(&gpu2d_clk, &axi_a_clk); + + /* move cspi to 24MHz */ + clk_set_parent(&cspi_main_clk, &lp_apm_clk); + clk_set_rate(&cspi_main_clk, 12000000); + /*move the spdif0 to spdif_xtal_ckl */ + clk_set_parent(&spdif0_clk[0], &spdif_xtal_clk); + /*set the SPDIF dividers to 1 */ + reg = __raw_readl(MXC_CCM_CDCDR); + reg &= ~MXC_CCM_CDCDR_SPDIF0_CLK_PODF_MASK; + reg &= ~MXC_CCM_CDCDR_SPDIF0_CLK_PRED_MASK; + __raw_writel(reg, MXC_CCM_CDCDR); + + /* move the spdif1 to 24MHz */ + clk_set_parent(&spdif1_clk[0], &spdif_xtal_clk); + /* set the spdif1 dividers to 1 */ + reg = __raw_readl(MXC_CCM_CDCDR); + reg &= ~MXC_CCM_CDCDR_SPDIF1_CLK_PODF_MASK; + reg &= ~MXC_CCM_CDCDR_SPDIF1_CLK_PRED_MASK; + __raw_writel(reg, MXC_CCM_CDCDR); + + /* Move SSI clocks to SSI_LP_APM clock */ + clk_set_parent(&ssi_lp_apm_clk, &lp_apm_clk); + + clk_set_parent(&ssi1_clk[0], &ssi_lp_apm_clk); + /* set the SSI dividers to divide by 2 */ + reg = __raw_readl(MXC_CCM_CS1CDR); + reg &= ~MXC_CCM_CS1CDR_SSI1_CLK_PODF_MASK; + reg &= ~MXC_CCM_CS1CDR_SSI1_CLK_PRED_MASK; + reg |= 1 << MXC_CCM_CS1CDR_SSI1_CLK_PRED_OFFSET; + __raw_writel(reg, MXC_CCM_CS1CDR); + + clk_set_parent(&ssi2_clk[0], &ssi_lp_apm_clk); + reg = __raw_readl(MXC_CCM_CS2CDR); + reg &= ~MXC_CCM_CS2CDR_SSI2_CLK_PODF_MASK; + reg &= ~MXC_CCM_CS2CDR_SSI2_CLK_PRED_MASK; + reg |= 1 << MXC_CCM_CS2CDR_SSI2_CLK_PRED_OFFSET; + __raw_writel(reg, MXC_CCM_CS2CDR); + + /* Change the SSI_EXT1_CLK to be sourced from SSI1_CLK_ROOT */ + clk_set_parent(&ssi_ext1_clk, &ssi1_clk[0]); + clk_set_parent(&ssi_ext2_clk, &ssi2_clk[0]); + + /* move usb_phy_clk to 24MHz */ + clk_set_parent(&usb_phy_clk, &osc_clk); + + /* set usboh3_clk to pll2 */ + clk_set_parent(&usboh3_clk[0], &pll2_sw_clk); + reg = __raw_readl(MXC_CCM_CSCDR1); + reg &= ~MXC_CCM_CSCDR1_USBOH3_CLK_PODF_MASK; + reg &= ~MXC_CCM_CSCDR1_USBOH3_CLK_PRED_MASK; + reg |= 4 << MXC_CCM_CSCDR1_USBOH3_CLK_PRED_OFFSET; + reg |= 1 << MXC_CCM_CSCDR1_USBOH3_CLK_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CSCDR1); + + /* Set the current working point. */ + cpu_wp_tbl = get_cpu_wp(&cpu_wp_nr); + /* Update the cpu working point table based on the PLL1 freq + * at boot time + */ + if (pll1_main_clk.rate <= cpu_wp_tbl[cpu_wp_nr - 1].cpu_rate) + wp_cnt = 1; + else if (pll1_main_clk.rate <= cpu_wp_tbl[1].cpu_rate && + pll1_main_clk.rate > cpu_wp_tbl[2].cpu_rate) + wp_cnt = cpu_wp_nr - 1; + else + wp_cnt = cpu_wp_nr; + + cpu_wp_tbl[0].cpu_rate = pll1_main_clk.rate; + + if (wp_cnt == 1) { + cpu_wp_tbl[0] = cpu_wp_tbl[cpu_wp_nr - 1]; + memset(&cpu_wp_tbl[cpu_wp_nr - 1], 0, sizeof(struct cpu_wp)); + memset(&cpu_wp_tbl[cpu_wp_nr - 2], 0, sizeof(struct cpu_wp)); + } else if (wp_cnt < cpu_wp_nr) { + for (i = 0; i < wp_cnt; i++) + cpu_wp_tbl[i] = cpu_wp_tbl[i+1]; + memset(&cpu_wp_tbl[i], 0, sizeof(struct cpu_wp)); + } + + if (wp_cnt < cpu_wp_nr) { + set_num_cpu_wp(wp_cnt); + cpu_wp_tbl = get_cpu_wp(&cpu_wp_nr); + } + + + for (j = 0; j < cpu_wp_nr; j++) { + if ((ddr_clk.parent == &ddr_hf_clk)) { + /* Change the CPU podf divider based on the boot up + * pll1 rate. + */ + cpu_wp_tbl[j].cpu_podf = + (pll1_main_clk.rate / cpu_wp_tbl[j].cpu_rate) + - 1; + if (pll1_main_clk.rate/(cpu_wp_tbl[j].cpu_podf + 1) > + cpu_wp_tbl[j].cpu_rate) { + cpu_wp_tbl[j].cpu_podf++; + cpu_wp_tbl[j].cpu_rate = + pll1_main_clk.rate/ + (1000 * (cpu_wp_tbl[j].cpu_podf + 1)); + cpu_wp_tbl[j].cpu_rate *= 1000; + } + if (pll1_main_clk.rate/(cpu_wp_tbl[j].cpu_podf + 1) < + cpu_wp_tbl[j].cpu_rate) { + cpu_wp_tbl[j].cpu_rate = pll1_main_clk.rate; + } + } + cpu_wp_tbl[j].pll_rate = pll1_main_clk.rate; + } + /* Set the current working point. */ + for (i = 0; i < cpu_wp_nr; i++) { + if (clk_get_rate(&cpu_clk) == cpu_wp_tbl[i].cpu_rate) { + cpu_curr_wp = i; + break; + } + } + if (i > cpu_wp_nr) + BUG(); + + propagate_rate(&osc_clk); + propagate_rate(&pll1_sw_clk); + propagate_rate(&pll2_sw_clk); + + /*Allow for automatic gating of the EMI internal clock. + * If this is done, emi_intr CCGR bits should be set to 11. + */ + reg = __raw_readl((IO_ADDRESS(M4IF_BASE_ADDR) + 0x8c)); + reg &= ~0x1; + __raw_writel(reg, (IO_ADDRESS(M4IF_BASE_ADDR) + 0x8c)); + + clk_set_parent(&arm_axi_clk, &axi_a_clk); + clk_set_parent(&ipu_clk[0], &axi_b_clk); + clk_set_parent(&uart_main_clk, &pll2_sw_clk); + + clk_set_parent(&emi_slow_clk, &ahb_clk); + clk_set_rate(&emi_slow_clk, clk_round_rate(&emi_slow_clk, 130000000)); + + /* Change the NFC clock rate to be 1:4 ratio with emi clock. */ + clk_set_rate(&emi_enfc_clk, clk_round_rate(&emi_enfc_clk, + (clk_get_rate(&emi_slow_clk))/4)); + + mxc_timer_init(&gpt_clk[0], IO_ADDRESS(GPT1_BASE_ADDR), MXC_INT_GPT); + return 0; +} + +/*! + * Setup cpu clock based on working point. + * @param wp cpu freq working point + * @return 0 on success or error code on failure. + */ +static int cpu_clk_set_wp(int wp) +{ + struct cpu_wp *p; + u32 reg; + u32 stat; + + if (wp == cpu_curr_wp) + return 0; + + p = &cpu_wp_tbl[wp]; + + /* + * If DDR clock is sourced from PLL1, we cannot drop PLL1 freq. + * Use the ARM_PODF to change the freq of the core, leave the PLL1 + * freq unchanged. + */ + if (ddr_clk.parent == &ddr_hf_clk) { + reg = __raw_readl(MXC_CCM_CACRR); + reg &= ~MXC_CCM_CACRR_ARM_PODF_MASK; + reg |= cpu_wp_tbl[wp].cpu_podf << MXC_CCM_CACRR_ARM_PODF_OFFSET; + __raw_writel(reg, MXC_CCM_CACRR); + cpu_curr_wp = wp; + cpu_clk.rate = cpu_wp_tbl[wp].cpu_rate; + } else { + struct timespec nstimeofday; + struct timespec curtime; + + /* Change the ARM clock to requested frequency */ + /* First move the ARM clock to step clock which is running + * at 24MHz. + */ + + /* Change the source of pll1_sw_clk to be the step_clk */ + reg = __raw_readl(MXC_CCM_CCSR); + reg |= MXC_CCM_CCSR_PLL1_SW_CLK_SEL; + __raw_writel(reg, MXC_CCM_CCSR); + + /* Stop the PLL */ + reg = __raw_readl(MXC_DPLL1_BASE + MXC_PLL_DP_CTL); + reg &= ~MXC_PLL_DP_CTL_UPEN; + __raw_writel(reg, MXC_DPLL1_BASE + MXC_PLL_DP_CTL); + + /* PDF and MFI */ + reg = p->pdf | p->mfi << MXC_PLL_DP_OP_MFI_OFFSET; + __raw_writel(reg, MXC_DPLL1_BASE + MXC_PLL_DP_OP); + + /* MFD */ + __raw_writel(p->mfd, MXC_DPLL1_BASE + MXC_PLL_DP_MFD); + + /* MFI */ + __raw_writel(p->mfn, MXC_DPLL1_BASE + MXC_PLL_DP_MFN); + + reg = __raw_readl(MXC_DPLL1_BASE + MXC_PLL_DP_CTL); + reg |= MXC_PLL_DP_CTL_UPEN; + /* Set the UPEN bits */ + __raw_writel(reg, MXC_DPLL1_BASE + MXC_PLL_DP_CTL); + /* Forcefully restart the PLL */ + reg |= MXC_PLL_DP_CTL_RST; + __raw_writel(reg, MXC_DPLL1_BASE + MXC_PLL_DP_CTL); + + /* Wait for the PLL to lock */ + getnstimeofday(&nstimeofday); + do { + getnstimeofday(&curtime); + if ((curtime.tv_nsec - nstimeofday.tv_nsec) > SPIN_DELAY) + panic("pll1 relock failed\n"); + stat = __raw_readl(MXC_DPLL1_BASE + MXC_PLL_DP_CTL) & + MXC_PLL_DP_CTL_LRF; + } while (!stat); + + reg = __raw_readl(MXC_CCM_CCSR); + /* Move the PLL1 back to the pll1_main_clk */ + reg &= ~MXC_CCM_CCSR_PLL1_SW_CLK_SEL; + __raw_writel(reg, MXC_CCM_CCSR); + + cpu_curr_wp = wp; + + pll1_sw_clk.rate = cpu_wp_tbl[wp].cpu_rate; + pll1_main_clk.rate = pll1_sw_clk.rate; + cpu_clk.rate = pll1_sw_clk.rate; + } + +#if defined(CONFIG_CPU_FREQ) + cpufreq_trig_needed = 1; +#endif + return 0; +} diff --git a/arch/arm/mach-mx51/cpu.c b/arch/arm/mach-mx51/cpu.c new file mode 100644 index 000000000000..f4b404ad777f --- /dev/null +++ b/arch/arm/mach-mx51/cpu.c @@ -0,0 +1,66 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mach-mx51/cpu.c + * + * @brief This file contains the CPU initialization code. + * + * @ingroup MSL_MX51 + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <mach/hardware.h> +#include <asm/io.h> +#include "crm_regs.h" + +/*! + * CPU initialization. It is called by fixup_mxc_board() + */ +void __init mxc_cpu_init(void) +{ + if (!system_rev) + mxc_set_system_rev(0x51, CHIP_REV_1_0); +} + +static int __init post_cpu_init(void) +{ + void __iomem *base; + unsigned int reg; + + /* Set ALP bits to 000. Set ALP_EN bit in Arm Memory Controller reg. */ + reg = 0x8; + __raw_writel(reg, MXC_CORTEXA8_PLAT_AMC); + + base = IO_ADDRESS(AIPS1_BASE_ADDR); + __raw_writel(0x0, base + 0x40); + __raw_writel(0x0, base + 0x44); + __raw_writel(0x0, base + 0x48); + __raw_writel(0x0, base + 0x4C); + reg = __raw_readl(base + 0x50) & 0x00FFFFFF; + __raw_writel(reg, base + 0x50); + + base = IO_ADDRESS(AIPS2_BASE_ADDR); + __raw_writel(0x0, base + 0x40); + __raw_writel(0x0, base + 0x44); + __raw_writel(0x0, base + 0x48); + __raw_writel(0x0, base + 0x4C); + reg = __raw_readl(base + 0x50) & 0x00FFFFFF; + __raw_writel(reg, base + 0x50); + + return 0; +} + +postcore_initcall(post_cpu_init); diff --git a/arch/arm/mach-mx51/crm_regs.h b/arch/arm/mach-mx51/crm_regs.h new file mode 100644 index 000000000000..7e4c0f52203d --- /dev/null +++ b/arch/arm/mach-mx51/crm_regs.h @@ -0,0 +1,682 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ARCH_ARM_MACH_MX51_CRM_REGS_H__ +#define __ARCH_ARM_MACH_MX51_CRM_REGS_H__ + +#define MXC_CCM_BASE ((char *)IO_ADDRESS(CCM_BASE_ADDR)) +#define MXC_DPLL1_BASE IO_ADDRESS(PLL1_BASE_ADDR) +#define MXC_DPLL2_BASE IO_ADDRESS(PLL2_BASE_ADDR) +#define MXC_DPLL3_BASE IO_ADDRESS(PLL3_BASE_ADDR) + +/* PLL Register Offsets */ +#define MXC_PLL_DP_CTL 0x00 +#define MXC_PLL_DP_CONFIG 0x04 +#define MXC_PLL_DP_OP 0x08 +#define MXC_PLL_DP_MFD 0x0C +#define MXC_PLL_DP_MFN 0x10 +#define MXC_PLL_DP_MFNMINUS 0x14 +#define MXC_PLL_DP_MFNPLUS 0x18 +#define MXC_PLL_DP_HFS_OP 0x1C +#define MXC_PLL_DP_HFS_MFD 0x20 +#define MXC_PLL_DP_HFS_MFN 0x24 +#define MXC_PLL_DP_MFN_TOGC 0x28 +#define MXC_PLL_DP_DESTAT 0x2c + +/* PLL Register Bit definitions */ +#define MXC_PLL_DP_CTL_MUL_CTRL 0x2000 +#define MXC_PLL_DP_CTL_DPDCK0_2_EN 0x1000 +#define MXC_PLL_DP_CTL_DPDCK0_2_OFFSET 12 +#define MXC_PLL_DP_CTL_ADE 0x800 +#define MXC_PLL_DP_CTL_REF_CLK_DIV 0x400 +#define MXC_PLL_DP_CTL_REF_CLK_SEL_MASK (3 << 8) +#define MXC_PLL_DP_CTL_REF_CLK_SEL_OFFSET 8 +#define MXC_PLL_DP_CTL_HFSM 0x80 +#define MXC_PLL_DP_CTL_PRE 0x40 +#define MXC_PLL_DP_CTL_UPEN 0x20 +#define MXC_PLL_DP_CTL_RST 0x10 +#define MXC_PLL_DP_CTL_RCP 0x8 +#define MXC_PLL_DP_CTL_PLM 0x4 +#define MXC_PLL_DP_CTL_BRM0 0x2 +#define MXC_PLL_DP_CTL_LRF 0x1 + +#define MXC_PLL_DP_CONFIG_BIST 0x8 +#define MXC_PLL_DP_CONFIG_SJC_CE 0x4 +#define MXC_PLL_DP_CONFIG_AREN 0x2 +#define MXC_PLL_DP_CONFIG_LDREQ 0x1 + +#define MXC_PLL_DP_OP_MFI_OFFSET 4 +#define MXC_PLL_DP_OP_MFI_MASK (0xF << 4) +#define MXC_PLL_DP_OP_PDF_OFFSET 0 +#define MXC_PLL_DP_OP_PDF_MASK 0xF + +#define MXC_PLL_DP_MFD_OFFSET 0 +#define MXC_PLL_DP_MFD_MASK 0x07FFFFFF + +#define MXC_PLL_DP_MFN_OFFSET 0x0 +#define MXC_PLL_DP_MFN_MASK 0x07FFFFFF + +#define MXC_PLL_DP_MFN_TOGC_TOG_DIS (1 << 17) +#define MXC_PLL_DP_MFN_TOGC_TOG_EN (1 << 16) +#define MXC_PLL_DP_MFN_TOGC_CNT_OFFSET 0x0 +#define MXC_PLL_DP_MFN_TOGC_CNT_MASK 0xFFFF + +#define MXC_PLL_DP_DESTAT_TOG_SEL (1 << 31) +#define MXC_PLL_DP_DESTAT_MFN 0x07FFFFFF + +/* Register addresses of CCM*/ +#define MXC_CCM_CCR (MXC_CCM_BASE + 0x00) +#define MXC_CCM_CCDR (MXC_CCM_BASE + 0x04) +#define MXC_CCM_CSR (MXC_CCM_BASE + 0x08) +#define MXC_CCM_CCSR (MXC_CCM_BASE + 0x0C) +#define MXC_CCM_CACRR (MXC_CCM_BASE + 0x10) +#define MXC_CCM_CBCDR (MXC_CCM_BASE + 0x14) +#define MXC_CCM_CBCMR (MXC_CCM_BASE + 0x18) +#define MXC_CCM_CSCMR1 (MXC_CCM_BASE + 0x1C) +#define MXC_CCM_CSCMR2 (MXC_CCM_BASE + 0x20) +#define MXC_CCM_CSCDR1 (MXC_CCM_BASE + 0x24) +#define MXC_CCM_CS1CDR (MXC_CCM_BASE + 0x28) +#define MXC_CCM_CS2CDR (MXC_CCM_BASE + 0x2C) +#define MXC_CCM_CDCDR (MXC_CCM_BASE + 0x30) +#define MXC_CCM_CHSCDR (MXC_CCM_BASE + 0x34) +#define MXC_CCM_CSCDR2 (MXC_CCM_BASE + 0x38) +#define MXC_CCM_CSCDR3 (MXC_CCM_BASE + 0x3C) +#define MXC_CCM_CSCDR4 (MXC_CCM_BASE + 0x40) +#define MXC_CCM_CWDR (MXC_CCM_BASE + 0x44) +#define MXC_CCM_CDHIPR (MXC_CCM_BASE + 0x48) +#define MXC_CCM_CDCR (MXC_CCM_BASE + 0x4C) +#define MXC_CCM_CTOR (MXC_CCM_BASE + 0x50) +#define MXC_CCM_CLPCR (MXC_CCM_BASE + 0x54) +#define MXC_CCM_CISR (MXC_CCM_BASE + 0x58) +#define MXC_CCM_CIMR (MXC_CCM_BASE + 0x5C) +#define MXC_CCM_CCOSR (MXC_CCM_BASE + 0x60) +#define MXC_CCM_CGPR (MXC_CCM_BASE + 0x64) +#define MXC_CCM_CCGR0 (MXC_CCM_BASE + 0x68) +#define MXC_CCM_CCGR1 (MXC_CCM_BASE + 0x6C) +#define MXC_CCM_CCGR2 (MXC_CCM_BASE + 0x70) +#define MXC_CCM_CCGR3 (MXC_CCM_BASE + 0x74) +#define MXC_CCM_CCGR4 (MXC_CCM_BASE + 0x78) +#define MXC_CCM_CCGR5 (MXC_CCM_BASE + 0x7C) +#define MXC_CCM_CCGR6 (MXC_CCM_BASE + 0x80) +#define MXC_CCM_CMEOR (MXC_CCM_BASE + 0x84) + +/* Define the bits in register CCR */ +#define MXC_CCM_CCR_COSC_EN (1 << 12) +#define MXC_CCM_CCR_FPM_MULT_MASK (1 << 11) +#define MXC_CCM_CCR_CAMP2_EN (1 << 10) +#define MXC_CCM_CCR_CAMP1_EN (1 << 9) +#define MXC_CCM_CCR_FPM_EN (1 << 8) +#define MXC_CCM_CCR_OSCNT_OFFSET (0) +#define MXC_CCM_CCR_OSCNT_MASK (0xFF) + +/* Define the bits in register CCDR */ +#define MXC_CCM_CCDR_HSC_HS_MASK (0x1 << 18) +#define MXC_CCM_CCDR_IPU_HS_MASK (0x1 << 17) +#define MXC_CCM_CCDR_EMI_HS_MASK (0x1 << 16) + +/* Define the bits in register CSR */ +#define MXC_CCM_CSR_COSR_READY (1 << 5) +#define MXC_CCM_CSR_LVS_VALUE (1 << 4) +#define MXC_CCM_CSR_CAMP2_READY (1 << 3) +#define MXC_CCM_CSR_CAMP1_READY (1 << 2) +#define MXC_CCM_CSR_FPM_READY (1 << 1) +#define MXC_CCM_CSR_REF_EN_B (1 << 0) + +/* Define the bits in register CCSR */ +#define MXC_CCM_CCSR_LP_APM_SEL (0x1 << 9) +#define MXC_CCM_CCSR_STEP_SEL_OFFSET (7) +#define MXC_CCM_CCSR_STEP_SEL_MASK (0x3 << 7) +#define MXC_CCM_CCSR_PLL2_PODF_OFFSET (5) +#define MXC_CCM_CCSR_PLL2_PODF_MASK (0x3 << 5) +#define MXC_CCM_CCSR_PLL3_PODF_OFFSET (3) +#define MXC_CCM_CCSR_PLL3_PODF_MASK (0x3 << 3) +#define MXC_CCM_CCSR_PLL1_SW_CLK_SEL (1 << 2) +#define MXC_CCM_CCSR_PLL2_SW_CLK_SEL (1 << 1) +#define MXC_CCM_CCSR_PLL3_SW_CLK_SEL (1 << 0) + +/* Define the bits in register CACRR */ +#define MXC_CCM_CACRR_ARM_PODF_OFFSET (0) +#define MXC_CCM_CACRR_ARM_PODF_MASK (0x7) + +/* Define the bits in register CBCDR */ +#define MXC_CCM_CBCDR_EMI_CLK_SEL (0x1 << 26) +#define MXC_CCM_CBCDR_PERIPH_CLK_SEL (0x1 << 25) +#define MXC_CCM_CBCDR_DDR_HF_SEL_OFFSET (30) +#define MXC_CCM_CBCDR_DDR_HF_SEL (0x1 << 30) +#define MXC_CCM_CBCDR_DDR_PODF_OFFSET (27) +#define MXC_CCM_CBCDR_DDR_PODF_MASK (0x7 << 27) +#define MXC_CCM_CBCDR_EMI_PODF_OFFSET (22) +#define MXC_CCM_CBCDR_EMI_PODF_MASK (0x7 << 22) +#define MXC_CCM_CBCDR_AXI_B_PODF_OFFSET (19) +#define MXC_CCM_CBCDR_AXI_B_PODF_MASK (0x7 << 19) +#define MXC_CCM_CBCDR_AXI_A_PODF_OFFSET (16) +#define MXC_CCM_CBCDR_AXI_A_PODF_MASK (0x7 << 16) +#define MXC_CCM_CBCDR_NFC_PODF_OFFSET (13) +#define MXC_CCM_CBCDR_NFC_PODF_MASK (0x7 << 13) +#define MXC_CCM_CBCDR_AHB_PODF_OFFSET (10) +#define MXC_CCM_CBCDR_AHB_PODF_MASK (0x7 << 10) +#define MXC_CCM_CBCDR_IPG_PODF_OFFSET (8) +#define MXC_CCM_CBCDR_IPG_PODF_MASK (0x3 << 8) +#define MXC_CCM_CBCDR_PERCLK_PRED1_OFFSET (6) +#define MXC_CCM_CBCDR_PERCLK_PRED1_MASK (0x3 << 6) +#define MXC_CCM_CBCDR_PERCLK_PRED2_OFFSET (3) +#define MXC_CCM_CBCDR_PERCLK_PRED2_MASK (0x7 << 3) +#define MXC_CCM_CBCDR_PERCLK_PODF_OFFSET (0) +#define MXC_CCM_CBCDR_PERCLK_PODF_MASK (0x7) + +/* Define the bits in register CBCMR */ +#define MXC_CCM_CBCMR_VPU_AXI_CLK_SEL_OFFSET (14) +#define MXC_CCM_CBCMR_VPU_AXI_CLK_SEL_MASK (0x3 << 14) +#define MXC_CCM_CBCMR_PERIPH_CLK_SEL_OFFSET (12) +#define MXC_CCM_CBCMR_PERIPH_CLK_SEL_MASK (0x3 << 12) +#define MXC_CCM_CBCMR_DDR_CLK_SEL_OFFSET (10) +#define MXC_CCM_CBCMR_DDR_CLK_SEL_MASK (0x3 << 10) +#define MXC_CCM_CBCMR_ARM_AXI_CLK_SEL_OFFSET (8) +#define MXC_CCM_CBCMR_ARM_AXI_CLK_SEL_MASK (0x3 << 8) +#define MXC_CCM_CBCMR_IPU_HSP_CLK_SEL_OFFSET (6) +#define MXC_CCM_CBCMR_IPU_HSP_CLK_SEL_MASK (0x3 << 6) +#define MXC_CCM_CBCMR_GPU_CLK_SEL_OFFSET (4) +#define MXC_CCM_CBCMR_GPU_CLK_SEL_MASK (0x3 << 4) +#define MXC_CCM_CBCMR_GPU2D_CLK_SEL_OFFSET (14) +#define MXC_CCM_CBCMR_GPU2D_CLK_SEL_MASK (0x3 << 14) +#define MXC_CCM_CBCMR_PERCLK_LP_APM_CLK_SEL (0x1 << 1) +#define MXC_CCM_CBCMR_PERCLK_IPG_CLK_SEL (0x1 << 0) + +/* Define the bits in register CSCMR1 */ +#define MXC_CCM_CSCMR1_SSI_EXT2_CLK_SEL_OFFSET (30) +#define MXC_CCM_CSCMR1_SSI_EXT2_CLK_SEL_MASK (0x3 << 30) +#define MXC_CCM_CSCMR1_SSI_EXT1_CLK_SEL_OFFSET (28) +#define MXC_CCM_CSCMR1_SSI_EXT1_CLK_SEL_MASK (0x3 << 28) +#define MXC_CCM_CSCMR1_USB_PHY_CLK_SEL_OFFSET (26) +#define MXC_CCM_CSCMR1_USB_PHY_CLK_SEL (0x1 << 26) +#define MXC_CCM_CSCMR1_UART_CLK_SEL_OFFSET (24) +#define MXC_CCM_CSCMR1_UART_CLK_SEL_MASK (0x3 << 24) +#define MXC_CCM_CSCMR1_USBOH3_CLK_SEL_OFFSET (22) +#define MXC_CCM_CSCMR1_USBOH3_CLK_SEL_MASK (0x3 << 22) +#define MXC_CCM_CSCMR1_ESDHC1_MSHC1_CLK_SEL_OFFSET (20) +#define MXC_CCM_CSCMR1_ESDHC1_MSHC1_CLK_SEL_MASK (0x3 << 20) +#define MXC_CCM_CSCMR1_ESDHC3_CLK_SEL (0x1 << 19) +#define MXC_CCM_CSCMR1_ESDHC4_CLK_SEL (0x1 << 18) +#define MXC_CCM_CSCMR1_ESDHC2_MSHC2_CLK_SEL_OFFSET (16) +#define MXC_CCM_CSCMR1_ESDHC2_MSHC2_CLK_SEL_MASK (0x3 << 16) +#define MXC_CCM_CSCMR1_SSI1_CLK_SEL_OFFSET (14) +#define MXC_CCM_CSCMR1_SSI1_CLK_SEL_MASK (0x3 << 14) +#define MXC_CCM_CSCMR1_SSI2_CLK_SEL_OFFSET (12) +#define MXC_CCM_CSCMR1_SSI2_CLK_SEL_MASK (0x3 << 12) +#define MXC_CCM_CSCMR1_SSI3_CLK_SEL (0x1 << 11) +#define MXC_CCM_CSCMR1_VPU_RCLK_SEL (0x1 << 10) +#define MXC_CCM_CSCMR1_SSI_APM_CLK_SEL_OFFSET (8) +#define MXC_CCM_CSCMR1_SSI_APM_CLK_SEL_MASK (0x3 << 8) +#define MXC_CCM_CSCMR1_TVE_CLK_SEL (0x1 << 7) +#define MXC_CCM_CSCMR1_TVE_EXT_CLK_SEL (0x1 << 6) +#define MXC_CCM_CSCMR1_CSPI_CLK_SEL_OFFSET (4) +#define MXC_CCM_CSCMR1_CSPI_CLK_SEL_MASK (0x3 << 4) +#define MXC_CCM_CSCMR1_SPDIF_CLK_SEL_OFFSET (2) +#define MXC_CCM_CSCMR1_SPDIF_CLK_SEL_MASK (0x3 << 2) +#define MXC_CCM_CSCMR1_SSI_EXT2_COM_CLK_SEL (0x1 << 1) +#define MXC_CCM_CSCMR1_SSI_EXT1_COM_CLK_SEL (0x1) + +/* Define the bits in register CSCMR2 */ +#define MXC_CCM_CSCMR2_DI_CLK_SEL_OFFSET(n) (26+n*3) +#define MXC_CCM_CSCMR2_DI_CLK_SEL_MASK(n) (0x7 << (26+n*3)) +#define MXC_CCM_CSCMR2_CSI_MCLK2_CLK_SEL_OFFSET (24) +#define MXC_CCM_CSCMR2_CSI_MCLK2_CLK_SEL_MASK (0x3 << 24) +#define MXC_CCM_CSCMR2_CSI_MCLK1_CLK_SEL_OFFSET (22) +#define MXC_CCM_CSCMR2_CSI_MCLK1_CLK_SEL_MASK (0x3 << 22) +#define MXC_CCM_CSCMR2_ESC_CLK_SEL_OFFSET (20) +#define MXC_CCM_CSCMR2_ESC_CLK_SEL_MASK (0x3 << 20) +#define MXC_CCM_CSCMR2_HSC2_CLK_SEL_OFFSET (18) +#define MXC_CCM_CSCMR2_HSC2_CLK_SEL_MASK (0x3 << 18) +#define MXC_CCM_CSCMR2_HSC1_CLK_SEL_OFFSET (16) +#define MXC_CCM_CSCMR2_HSC1_CLK_SEL_MASK (0x3 << 16) +#define MXC_CCM_CSCMR2_HSI2C_CLK_SEL_OFFSET (14) +#define MXC_CCM_CSCMR2_HSI2C_CLK_SEL_MASK (0x3 << 14) +#define MXC_CCM_CSCMR2_FIRI_CLK_SEL_OFFSET (12) +#define MXC_CCM_CSCMR2_FIRI_CLK_SEL_MASK (0x3 << 12) +#define MXC_CCM_CSCMR2_SIM_CLK_SEL_OFFSET (10) +#define MXC_CCM_CSCMR2_SIM_CLK_SEL_MASK (0x3 << 10) +#define MXC_CCM_CSCMR2_SLIMBUS_COM (0x1 << 9) +#define MXC_CCM_CSCMR2_SLIMBUS_CLK_SEL_OFFSET (6) +#define MXC_CCM_CSCMR2_SLIMBUS_CLK_SEL_MASK (0x7 << 6) +#define MXC_CCM_CSCMR2_SPDIF1_COM (1 << 5) +#define MXC_CCM_CSCMR2_SPDIF0_COM (1 << 4) +#define MXC_CCM_CSCMR2_SPDIF1_CLK_SEL_OFFSET (2) +#define MXC_CCM_CSCMR2_SPDIF1_CLK_SEL_MASK (0x3 << 2) +#define MXC_CCM_CSCMR2_SPDIF0_CLK_SEL_OFFSET (0) +#define MXC_CCM_CSCMR2_SPDIF0_CLK_SEL_MASK (0x3) + +/* Define the bits in register CSCDR1 */ +#define MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PRED_OFFSET (22) +#define MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PRED_MASK (0x7 << 22) +#define MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PODF_OFFSET (19) +#define MXC_CCM_CSCDR1_ESDHC2_MSHC2_CLK_PODF_MASK (0x7 << 19) +#define MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PRED_OFFSET (16) +#define MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PRED_MASK (0x7 << 16) +#define MXC_CCM_CSCDR1_PGC_CLK_PODF_OFFSET (14) +#define MXC_CCM_CSCDR1_PGC_CLK_PODF_MASK (0x3 << 14) +#define MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PODF_OFFSET (11) +#define MXC_CCM_CSCDR1_ESDHC1_MSHC1_CLK_PODF_MASK (0x7 << 11) +#define MXC_CCM_CSCDR1_USBOH3_CLK_PRED_OFFSET (8) +#define MXC_CCM_CSCDR1_USBOH3_CLK_PRED_MASK (0x7 << 8) +#define MXC_CCM_CSCDR1_USBOH3_CLK_PODF_OFFSET (6) +#define MXC_CCM_CSCDR1_USBOH3_CLK_PODF_MASK (0x3 << 6) +#define MXC_CCM_CSCDR1_UART_CLK_PRED_OFFSET (3) +#define MXC_CCM_CSCDR1_UART_CLK_PRED_MASK (0x7 << 3) +#define MXC_CCM_CSCDR1_UART_CLK_PODF_OFFSET (0) +#define MXC_CCM_CSCDR1_UART_CLK_PODF_MASK (0x7) + +/* Define the bits in register CS1CDR and CS2CDR */ +#define MXC_CCM_CS1CDR_SSI_EXT1_CLK_PRED_OFFSET (22) +#define MXC_CCM_CS1CDR_SSI_EXT1_CLK_PRED_MASK (0x7 << 22) +#define MXC_CCM_CS1CDR_SSI_EXT1_CLK_PODF_OFFSET (16) +#define MXC_CCM_CS1CDR_SSI_EXT1_CLK_PODF_MASK (0x3F << 16) +#define MXC_CCM_CS1CDR_SSI1_CLK_PRED_OFFSET (6) +#define MXC_CCM_CS1CDR_SSI1_CLK_PRED_MASK (0x7 << 6) +#define MXC_CCM_CS1CDR_SSI1_CLK_PODF_OFFSET (0) +#define MXC_CCM_CS1CDR_SSI1_CLK_PODF_MASK (0x3F) + +#define MXC_CCM_CS2CDR_SSI_EXT2_CLK_PRED_OFFSET (22) +#define MXC_CCM_CS2CDR_SSI_EXT2_CLK_PRED_MASK (0x7 << 22) +#define MXC_CCM_CS2CDR_SSI_EXT2_CLK_PODF_OFFSET (16) +#define MXC_CCM_CS2CDR_SSI_EXT2_CLK_PODF_MASK (0x3F << 16) +#define MXC_CCM_CS2CDR_SSI2_CLK_PRED_OFFSET (6) +#define MXC_CCM_CS2CDR_SSI2_CLK_PRED_MASK (0x7 << 6) +#define MXC_CCM_CS2CDR_SSI2_CLK_PODF_OFFSET (0) +#define MXC_CCM_CS2CDR_SSI2_CLK_PODF_MASK (0x3F) + +/* Define the bits in register CDCDR */ +#define MXC_CCM_CDCDR_TVE_CLK_PRED_OFFSET (28) +#define MXC_CCM_CDCDR_TVE_CLK_PRED_MASK (0x7 << 28) +#define MXC_CCM_CDCDR_SPDIF0_CLK_PRED_OFFSET (25) +#define MXC_CCM_CDCDR_SPDIF0_CLK_PRED_MASK (0x7 << 25) +#define MXC_CCM_CDCDR_SPDIF0_CLK_PODF_OFFSET (19) +#define MXC_CCM_CDCDR_SPDIF0_CLK_PODF_MASK (0x3F << 19) +#define MXC_CCM_CDCDR_SPDIF1_CLK_PRED_OFFSET (16) +#define MXC_CCM_CDCDR_SPDIF1_CLK_PRED_MASK (0x7 << 16) +#define MXC_CCM_CDCDR_SPDIF1_CLK_PODF_OFFSET (9) +#define MXC_CCM_CDCDR_SPDIF1_CLK_PODF_MASK (0x3F << 9) +#define MXC_CCM_CDCDR_DI_CLK_PRED_OFFSET (6) +#define MXC_CCM_CDCDR_DI_CLK_PRED_MASK (0x7 << 6) +#define MXC_CCM_CDCDR_USB_PHY_PRED_OFFSET (3) +#define MXC_CCM_CDCDR_USB_PHY_PRED_MASK (0x7 << 3) +#define MXC_CCM_CDCDR_USB_PHY_PODF_OFFSET (0) +#define MXC_CCM_CDCDR_USB_PHY_PODF_MASK (0x7) + +/* Define the bits in register CHSCCDR */ +#define MXC_CCM_CHSCCDR_ESC_CLK_PRED_OFFSET (12) +#define MXC_CCM_CHSCCDR_ESC_CLK_PRED_MASK (0x7 << 12) +#define MXC_CCM_CHSCCDR_ESC_CLK_PODF_OFFSET (6) +#define MXC_CCM_CHSCCDR_ESC_CLK_PODF_MASK (0x3F << 6) +#define MXC_CCM_CHSCCDR_HSC2_CLK_PODF_OFFSET (3) +#define MXC_CCM_CHSCCDR_HSC2_CLK_PODF_MASK (0x7 << 3) +#define MXC_CCM_CHSCCDR_HSC1_CLK_PODF_OFFSET (0) +#define MXC_CCM_CHSCCDR_HSC1_CLK_PODF_MASK (0x7) + +/* Define the bits in register CSCDR2 */ +#define MXC_CCM_CSCDR2_CSPI_CLK_PRED_OFFSET (25) +#define MXC_CCM_CSCDR2_CSPI_CLK_PRED_MASK (0x7 << 25) +#define MXC_CCM_CSCDR2_CSPI_CLK_PODF_OFFSET (19) +#define MXC_CCM_CSCDR2_CSPI_CLK_PODF_MASK (0x3F << 19) +#define MXC_CCM_CSCDR2_SIM_CLK_PRED_OFFSET (16) +#define MXC_CCM_CSCDR2_SIM_CLK_PRED_MASK (0x7 << 16) +#define MXC_CCM_CSCDR2_SIM_CLK_PODF_OFFSET (9) +#define MXC_CCM_CSCDR2_SIM_CLK_PODF_MASK (0x3F << 9) +#define MXC_CCM_CSCDR2_SLIMBUS_CLK_PRED_OFFSET (6) +#define MXC_CCM_CSCDR2_SLIMBUS_PRED_MASK (0x7 << 6) +#define MXC_CCM_CSCDR2_SLIMBUS_PODF_OFFSET (0) +#define MXC_CCM_CSCDR2_SLIMBUS_PODF_MASK (0x3F) + +/* Define the bits in register CSCDR3 */ +#define MXC_CCM_CSCDR3_HSI2C_CLK_PRED_OFFSET (16) +#define MXC_CCM_CSCDR3_HSI2C_CLK_PRED_MASK (0x7 << 16) +#define MXC_CCM_CSCDR3_HSI2C_CLK_PODF_OFFSET (9) +#define MXC_CCM_CSCDR3_HSI2C_CLK_PODF_MASK (0x3F << 9) +#define MXC_CCM_CSCDR3_FIRI_CLK_PRED_OFFSET (6) +#define MXC_CCM_CSCDR3_FIRI_CLK_PRED_MASK (0x7 << 6) +#define MXC_CCM_CSCDR3_FIRI_CLK_PODF_OFFSET (0) +#define MXC_CCM_CSCDR3_FIRI_CLK_PODF_MASK (0x3F) + +/* Define the bits in register CSCDR4 */ +#define MXC_CCM_CSCDR4_CSI_MCLK2_CLK_PRED_OFFSET (16) +#define MXC_CCM_CSCDR4_CSI_MCLK2_CLK_PRED_MASK (0x7 << 16) +#define MXC_CCM_CSCDR4_CSI_MCLK2_CLK_PODF_OFFSET (9) +#define MXC_CCM_CSCDR4_CSI_MCLK2_CLK_PODF_MASK (0x3F << 9) +#define MXC_CCM_CSCDR4_CSI_MCLK1_CLK_PRED_OFFSET (6) +#define MXC_CCM_CSCDR4_CSI_MCLK1_CLK_PRED_MASK (0x7 << 6) +#define MXC_CCM_CSCDR4_CSI_MCLK1_CLK_PODF_OFFSET (0) +#define MXC_CCM_CSCDR4_CSI_MCLK1_CLK_PODF_MASK (0x3F) + +/* Define the bits in register CDHIPR */ +#define MXC_CCM_CDHIPR_ARM_PODF_BUSY (1 << 16) +#define MXC_CCM_CDHIPR_DDR_HF_CLK_SEL_BUSY (1 << 8) +#define MXC_CCM_CDHIPR_DDR_PODF_BUSY (1 << 7) +#define MXC_CCM_CDHIPR_EMI_CLK_SEL_BUSY (1 << 6) +#define MXC_CCM_CDHIPR_PERIPH_CLK_SEL_BUSY (1 << 5) +#define MXC_CCM_CDHIPR_NFC_IPG_INT_MEM_PODF_BUSY (1 << 4) +#define MXC_CCM_CDHIPR_AHB_PODF_BUSY (1 << 3) +#define MXC_CCM_CDHIPR_EMI_PODF_BUSY (1 << 2) +#define MXC_CCM_CDHIPR_AXI_B_PODF_BUSY (1 << 1) +#define MXC_CCM_CDHIPR_AXI_A_PODF_BUSY (1 << 0) + +/* Define the bits in register CDCR */ +#define MXC_CCM_CDCR_ARM_FREQ_SHIFT_DIVIDER (0x1 << 2) +#define MXC_CCM_CDCR_PERIPH_CLK_DVFS_PODF_OFFSET (0) +#define MXC_CCM_CDCR_PERIPH_CLK_DVFS_PODF_MASK (0x3) + +/* Define the bits in register CLPCR */ +#define MXC_CCM_CLPCR_BYPASS_HSC_LPM_HS (0x1 << 23) +#define MXC_CCM_CLPCR_BYPASS_SCC_LPM_HS (0x1 << 22) +#define MXC_CCM_CLPCR_BYPASS_MAX_LPM_HS (0x1 << 21) +#define MXC_CCM_CLPCR_BYPASS_SDMA_LPM_HS (0x1 << 20) +#define MXC_CCM_CLPCR_BYPASS_EMI_LPM_HS (0x1 << 19) +#define MXC_CCM_CLPCR_BYPASS_IPU_LPM_HS (0x1 << 18) +#define MXC_CCM_CLPCR_BYPASS_RTIC_LPM_HS (0x1 << 17) +#define MXC_CCM_CLPCR_BYPASS_RNGC_LPM_HS (0x1 << 16) +#define MXC_CCM_CLPCR_COSC_PWRDOWN (0x1 << 11) +#define MXC_CCM_CLPCR_STBY_COUNT_OFFSET (9) +#define MXC_CCM_CLPCR_STBY_COUNT_MASK (0x3 << 9) +#define MXC_CCM_CLPCR_VSTBY (0x1 << 8) +#define MXC_CCM_CLPCR_DIS_REF_OSC (0x1 << 7) +#define MXC_CCM_CLPCR_SBYOS (0x1 << 6) +#define MXC_CCM_CLPCR_ARM_CLK_DIS_ON_LPM (0x1 << 5) +#define MXC_CCM_CLPCR_LPSR_CLK_SEL_OFFSET (3) +#define MXC_CCM_CLPCR_LPSR_CLK_SEL_MASK (0x3 << 3) +#define MXC_CCM_CLPCR_LPM_OFFSET (0) +#define MXC_CCM_CLPCR_LPM_MASK (0x3) + +/* Define the bits in register CISR */ +#define MXC_CCM_CISR_ARM_PODF_LOADED (0x1 << 25) +#define MXC_CCM_CISR_NFC_IPG_INT_MEM_PODF_LOADED (0x1 << 21) +#define MXC_CCM_CISR_AHB_PODF_LOADED (0x1 << 20) +#define MXC_CCM_CISR_EMI_PODF_LOADED (0x1 << 19) +#define MXC_CCM_CISR_AXI_B_PODF_LOADED (0x1 << 18) +#define MXC_CCM_CISR_AXI_A_PODF_LOADED (0x1 << 17) +#define MXC_CCM_CISR_DIVIDER_LOADED (0x1 << 16) +#define MXC_CCM_CISR_COSC_READY (0x1 << 6) +#define MXC_CCM_CISR_CKIH2_READY (0x1 << 5) +#define MXC_CCM_CISR_CKIH_READY (0x1 << 4) +#define MXC_CCM_CISR_FPM_READY (0x1 << 3) +#define MXC_CCM_CISR_LRF_PLL3 (0x1 << 2) +#define MXC_CCM_CISR_LRF_PLL2 (0x1 << 1) +#define MXC_CCM_CISR_LRF_PLL1 (0x1) + +/* Define the bits in register CIMR */ +#define MXC_CCM_CIMR_MASK_ARM_PODF_LOADED (0x1 << 25) +#define MXC_CCM_CIMR_MASK_NFC_IPG_INT_MEM_PODF_LOADED (0x1 << 21) +#define MXC_CCM_CIMR_MASK_EMI_PODF_LOADED (0x1 << 20) +#define MXC_CCM_CIMR_MASK_AXI_C_PODF_LOADED (0x1 << 19) +#define MXC_CCM_CIMR_MASK_AXI_B_PODF_LOADED (0x1 << 18) +#define MXC_CCM_CIMR_MASK_AXI_A_PODF_LOADED (0x1 << 17) +#define MXC_CCM_CIMR_MASK_DIVIDER_LOADED (0x1 << 16) +#define MXC_CCM_CIMR_MASK_COSC_READY (0x1 << 5) +#define MXC_CCM_CIMR_MASK_CKIH_READY (0x1 << 4) +#define MXC_CCM_CIMR_MASK_FPM_READY (0x1 << 3) +#define MXC_CCM_CIMR_MASK_LRF_PLL3 (0x1 << 2) +#define MXC_CCM_CIMR_MASK_LRF_PLL2 (0x1 << 1) +#define MXC_CCM_CIMR_MASK_LRF_PLL1 (0x1) + +/* Define the bits in register CCOSR */ +#define MXC_CCM_CCOSR_CKO2_EN_OFFSET (0x1 << 24) +#define MXC_CCM_CCOSR_CKO2_DIV_OFFSET (21) +#define MXC_CCM_CCOSR_CKO2_DIV_MASK (0x7 << 21) +#define MXC_CCM_CCOSR_CKO2_SEL_OFFSET (16) +#define MXC_CCM_CCOSR_CKO2_SEL_MASK (0x1F << 16) +#define MXC_CCM_CCOSR_CKOL_EN (0x1 << 7) +#define MXC_CCM_CCOSR_CKOL_DIV_OFFSET (4) +#define MXC_CCM_CCOSR_CKOL_DIV_MASK (0x7 << 4) +#define MXC_CCM_CCOSR_CKOL_SEL_OFFSET (0) +#define MXC_CCM_CCOSR_CKOL_SEL_MASK (0xF) + +/* Define the bits in registers CGPR */ +#define MXC_CCM_CGPR_EFUSE_PROG_SUPPLY_GATE (0x1 << 4) +#define MXC_CCM_CGPR_FPM_SEL (0x1 << 3) +#define MXC_CCM_CGPR_VL_L2BIST_CLKDIV_OFFSET (0) +#define MXC_CCM_CGPR_VL_L2BIST_CLKDIV_MASK (0x7) + +/* Define the bits in registers CCGRx */ +#define MXC_CCM_CCGR_CG_MASK 0x3 + +#define MXC_CCM_CCGR0_CG15_OFFSET 30 +#define MXC_CCM_CCGR0_CG15_MASK (0x3 << 30) +#define MXC_CCM_CCGR0_CG14_OFFSET 28 +#define MXC_CCM_CCGR0_CG14_MASK (0x3 << 28) +#define MXC_CCM_CCGR0_CG13_OFFSET 26 +#define MXC_CCM_CCGR0_CG13_MASK (0x3 << 26) +#define MXC_CCM_CCGR0_CG12_OFFSET 24 +#define MXC_CCM_CCGR0_CG12_MASK (0x3 << 24) +#define MXC_CCM_CCGR0_CG11_OFFSET 22 +#define MXC_CCM_CCGR0_CG11_MASK (0x3 << 22) +#define MXC_CCM_CCGR0_CG10_OFFSET 20 +#define MXC_CCM_CCGR0_CG10_MASK (0x3 << 20) +#define MXC_CCM_CCGR0_CG9_OFFSET 18 +#define MXC_CCM_CCGR0_CG9_MASK (0x3 << 18) +#define MXC_CCM_CCGR0_CG8_OFFSET 16 +#define MXC_CCM_CCGR0_CG8_MASK (0x3 << 16) +#define MXC_CCM_CCGR0_CG7_OFFSET 14 +#define MXC_CCM_CCGR0_CG6_OFFSET 12 +#define MXC_CCM_CCGR0_CG5_OFFSET 10 +#define MXC_CCM_CCGR0_CG5_MASK (0x3 << 10) +#define MXC_CCM_CCGR0_CG4_OFFSET 8 +#define MXC_CCM_CCGR0_CG4_MASK (0x3 << 8) +#define MXC_CCM_CCGR0_CG3_OFFSET 6 +#define MXC_CCM_CCGR0_CG3_MASK (0x3 << 6) +#define MXC_CCM_CCGR0_CG2_OFFSET 4 +#define MXC_CCM_CCGR0_CG2_MASK (0x3 << 4) +#define MXC_CCM_CCGR0_CG1_OFFSET 2 +#define MXC_CCM_CCGR0_CG1_MASK (0x3 << 2) +#define MXC_CCM_CCGR0_CG0_OFFSET 0 +#define MXC_CCM_CCGR0_CG0_MASK 0x3 + +#define MXC_CCM_CCGR1_CG15_OFFSET 30 +#define MXC_CCM_CCGR1_CG14_OFFSET 28 +#define MXC_CCM_CCGR1_CG13_OFFSET 26 +#define MXC_CCM_CCGR1_CG12_OFFSET 24 +#define MXC_CCM_CCGR1_CG11_OFFSET 22 +#define MXC_CCM_CCGR1_CG10_OFFSET 20 +#define MXC_CCM_CCGR1_CG9_OFFSET 18 +#define MXC_CCM_CCGR1_CG8_OFFSET 16 +#define MXC_CCM_CCGR1_CG7_OFFSET 14 +#define MXC_CCM_CCGR1_CG6_OFFSET 12 +#define MXC_CCM_CCGR1_CG5_OFFSET 10 +#define MXC_CCM_CCGR1_CG4_OFFSET 8 +#define MXC_CCM_CCGR1_CG3_OFFSET 6 +#define MXC_CCM_CCGR1_CG2_OFFSET 4 +#define MXC_CCM_CCGR1_CG1_OFFSET 2 +#define MXC_CCM_CCGR1_CG0_OFFSET 0 + +#define MXC_CCM_CCGR2_CG15_OFFSET 30 +#define MXC_CCM_CCGR2_CG14_OFFSET 28 +#define MXC_CCM_CCGR2_CG13_OFFSET 26 +#define MXC_CCM_CCGR2_CG12_OFFSET 24 +#define MXC_CCM_CCGR2_CG11_OFFSET 22 +#define MXC_CCM_CCGR2_CG10_OFFSET 20 +#define MXC_CCM_CCGR2_CG9_OFFSET 18 +#define MXC_CCM_CCGR2_CG8_OFFSET 16 +#define MXC_CCM_CCGR2_CG7_OFFSET 14 +#define MXC_CCM_CCGR2_CG6_OFFSET 12 +#define MXC_CCM_CCGR2_CG5_OFFSET 10 +#define MXC_CCM_CCGR2_CG4_OFFSET 8 +#define MXC_CCM_CCGR2_CG3_OFFSET 6 +#define MXC_CCM_CCGR2_CG2_OFFSET 4 +#define MXC_CCM_CCGR2_CG1_OFFSET 2 +#define MXC_CCM_CCGR2_CG0_OFFSET 0 + +#define MXC_CCM_CCGR3_CG15_OFFSET 30 +#define MXC_CCM_CCGR3_CG14_OFFSET 28 +#define MXC_CCM_CCGR3_CG13_OFFSET 26 +#define MXC_CCM_CCGR3_CG12_OFFSET 24 +#define MXC_CCM_CCGR3_CG11_OFFSET 22 +#define MXC_CCM_CCGR3_CG10_OFFSET 20 +#define MXC_CCM_CCGR3_CG9_OFFSET 18 +#define MXC_CCM_CCGR3_CG8_OFFSET 16 +#define MXC_CCM_CCGR3_CG7_OFFSET 14 +#define MXC_CCM_CCGR3_CG6_OFFSET 12 +#define MXC_CCM_CCGR3_CG5_OFFSET 10 +#define MXC_CCM_CCGR3_CG4_OFFSET 8 +#define MXC_CCM_CCGR3_CG3_OFFSET 6 +#define MXC_CCM_CCGR3_CG2_OFFSET 4 +#define MXC_CCM_CCGR3_CG1_OFFSET 2 +#define MXC_CCM_CCGR3_CG0_OFFSET 0 + +#define MXC_CCM_CCGR4_CG15_OFFSET 30 +#define MXC_CCM_CCGR4_CG14_OFFSET 28 +#define MXC_CCM_CCGR4_CG13_OFFSET 26 +#define MXC_CCM_CCGR4_CG12_OFFSET 24 +#define MXC_CCM_CCGR4_CG11_OFFSET 22 +#define MXC_CCM_CCGR4_CG10_OFFSET 20 +#define MXC_CCM_CCGR4_CG9_OFFSET 18 +#define MXC_CCM_CCGR4_CG8_OFFSET 16 +#define MXC_CCM_CCGR4_CG7_OFFSET 14 +#define MXC_CCM_CCGR4_CG6_OFFSET 12 +#define MXC_CCM_CCGR4_CG5_OFFSET 10 +#define MXC_CCM_CCGR4_CG4_OFFSET 8 +#define MXC_CCM_CCGR4_CG3_OFFSET 6 +#define MXC_CCM_CCGR4_CG2_OFFSET 4 +#define MXC_CCM_CCGR4_CG1_OFFSET 2 +#define MXC_CCM_CCGR4_CG0_OFFSET 0 + +#define MXC_CCM_CCGR5_CG15_OFFSET 30 +#define MXC_CCM_CCGR5_CG14_OFFSET 28 +#define MXC_CCM_CCGR5_CG14_MASK (0x3 << 28) +#define MXC_CCM_CCGR5_CG13_OFFSET 26 +#define MXC_CCM_CCGR5_CG13_MASK (0x3 << 26) +#define MXC_CCM_CCGR5_CG12_OFFSET 24 +#define MXC_CCM_CCGR5_CG12_MASK (0x3 << 24) +#define MXC_CCM_CCGR5_CG11_OFFSET 22 +#define MXC_CCM_CCGR5_CG11_MASK (0x3 << 22) +#define MXC_CCM_CCGR5_CG10_OFFSET 20 +#define MXC_CCM_CCGR5_CG10_MASK (0x3 << 20) +#define MXC_CCM_CCGR5_CG9_OFFSET 18 +#define MXC_CCM_CCGR5_CG9_MASK (0x3 << 18) +#define MXC_CCM_CCGR5_CG8_OFFSET 16 +#define MXC_CCM_CCGR5_CG8_MASK (0x3 << 16) +#define MXC_CCM_CCGR5_CG7_OFFSET 14 +#define MXC_CCM_CCGR5_CG7_MASK (0x3 << 14) +#define MXC_CCM_CCGR5_CG6_OFFSET 12 +#define MXC_CCM_CCGR5_CG5_OFFSET 10 +#define MXC_CCM_CCGR5_CG4_OFFSET 8 +#define MXC_CCM_CCGR5_CG3_OFFSET 6 +#define MXC_CCM_CCGR5_CG2_OFFSET 4 +#define MXC_CCM_CCGR5_CG2_MASK (0x3 << 4) +#define MXC_CCM_CCGR5_CG1_OFFSET 2 +#define MXC_CCM_CCGR5_CG0_OFFSET 0 +#define MXC_CCM_CCGR6_CG7_OFFSET 14 +#define MXC_CCM_CCGR6_CG7_MASK (0x3 << 14) +#define MXC_CCM_CCGR6_CG6_OFFSET 12 +#define MXC_CCM_CCGR6_CG6_MASK (0x3 << 12) +#define MXC_CCM_CCGR6_CG5_OFFSET 10 +#define MXC_CCM_CCGR6_CG5_MASK (0x3 << 10) +#define MXC_CCM_CCGR6_CG4_OFFSET 8 +#define MXC_CCM_CCGR6_CG4_MASK (0x3 << 8) +#define MXC_CCM_CCGR6_CG3_OFFSET 6 +#define MXC_CCM_CCGR6_CG2_OFFSET 4 +#define MXC_CCM_CCGR6_CG1_OFFSET 2 +#define MXC_CCM_CCGR6_CG0_OFFSET 0 + +#define MXC_CORTEXA8_BASE IO_ADDRESS(ARM_BASE_ADDR) +#define MXC_GPC_BASE IO_ADDRESS(GPC_BASE_ADDR) +#define MXC_DPTC_LP_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x80) +#define MXC_DPTC_GP_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x100) +#define MXC_DVFS_CORE_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x180) +#define MXC_DPTC_PER_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x1C0) +#define MXC_PGC_IPU_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x220) +#define MXC_PGC_VPU_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x240) +#define MXC_PGC_GPU_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x260) +#define MXC_SRPG_NEON_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x280) +#define MXC_SRPG_ARM_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x2A0) +#define MXC_SRPG_EMPGC0_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x2C0) +#define MXC_SRPG_EMPGC1_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x2D0) +#define MXC_SRPG_MEGAMIX_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x2E0) +#define MXC_SRPG_EMI_BASE IO_ADDRESS(GPC_BASE_ADDR + 0x300) + +/* CORTEXA8 platform */ +#define MXC_CORTEXA8_PLAT_PVID (MXC_CORTEXA8_BASE + 0x0) +#define MXC_CORTEXA8_PLAT_GPC (MXC_CORTEXA8_BASE + 0x4) +#define MXC_CORTEXA8_PLAT_PIC (MXC_CORTEXA8_BASE + 0x8) +#define MXC_CORTEXA8_PLAT_LPC (MXC_CORTEXA8_BASE + 0xC) +#define MXC_CORTEXA8_PLAT_NEON_LPC (MXC_CORTEXA8_BASE + 0x10) +#define MXC_CORTEXA8_PLAT_ICGC (MXC_CORTEXA8_BASE + 0x14) +#define MXC_CORTEXA8_PLAT_AMC (MXC_CORTEXA8_BASE + 0x18) +#define MXC_CORTEXA8_PLAT_NMC (MXC_CORTEXA8_BASE + 0x20) +#define MXC_CORTEXA8_PLAT_NMS (MXC_CORTEXA8_BASE + 0x24) + +/* DVFS CORE */ +#define MXC_DVFSTHRS (MXC_DVFS_CORE_BASE + 0x00) +#define MXC_DVFSCOUN (MXC_DVFS_CORE_BASE + 0x04) +#define MXC_DVFSSIG1 (MXC_DVFS_CORE_BASE + 0x08) +#define MXC_DVFSSIG0 (MXC_DVFS_CORE_BASE + 0x0C) +#define MXC_DVFSGPC0 (MXC_DVFS_CORE_BASE + 0x10) +#define MXC_DVFSGPC1 (MXC_DVFS_CORE_BASE + 0x14) +#define MXC_DVFSGPBT (MXC_DVFS_CORE_BASE + 0x18) +#define MXC_DVFSEMAC (MXC_DVFS_CORE_BASE + 0x1C) +#define MXC_DVFSCNTR (MXC_DVFS_CORE_BASE + 0x20) +#define MXC_DVFSLTR0_0 (MXC_DVFS_CORE_BASE + 0x24) +#define MXC_DVFSLTR0_1 (MXC_DVFS_CORE_BASE + 0x28) +#define MXC_DVFSLTR1_0 (MXC_DVFS_CORE_BASE + 0x2C) +#define MXC_DVFSLTR1_1 (MXC_DVFS_CORE_BASE + 0x30) +#define MXC_DVFSPT0 (MXC_DVFS_CORE_BASE + 0x34) +#define MXC_DVFSPT1 (MXC_DVFS_CORE_BASE + 0x38) +#define MXC_DVFSPT2 (MXC_DVFS_CORE_BASE + 0x3C) +#define MXC_DVFSPT3 (MXC_DVFS_CORE_BASE + 0x40) + +/* GPC */ +#define MXC_GPC_CNTR (MXC_GPC_BASE + 0x0) +#define MXC_GPC_PGR (MXC_GPC_BASE + 0x4) +#define MXC_GPC_VCR (MXC_GPC_BASE + 0x8) +#define MXC_GPC_ALL_PU (MXC_GPC_BASE + 0xC) +#define MXC_GPC_NEON (MXC_GPC_BASE + 0x10) +#define MXC_GPC_PGR_ARMPG_OFFSET 8 +#define MXC_GPC_PGR_ARMPG_MASK (3 << 8) + +/* PGC */ +#define MXC_PGC_IPU_PGCR (MXC_PGC_IPU_BASE + 0x0) +#define MXC_PGC_IPU_PGSR (MXC_PGC_IPU_BASE + 0xC) +#define MXC_PGC_VPU_PGCR (MXC_PGC_VPU_BASE + 0x0) +#define MXC_PGC_VPU_PGSR (MXC_PGC_VPU_BASE + 0xC) +#define MXC_PGC_GPU_PGCR (MXC_PGC_GPU_BASE + 0x0) +#define MXC_PGC_GPU_PGSR (MXC_PGC_GPU_BASE + 0xC) + +#define MXC_PGCR_PCR 1 +#define MXC_SRPGCR_PCR 1 +#define MXC_EMPGCR_PCR 1 +#define MXC_PGSR_PSR 1 + + +#define MXC_CORTEXA8_PLAT_LPC_DSM (1 << 0) +#define MXC_CORTEXA8_PLAT_LPC_DBG_DSM (1 << 1) + +/* SRPG */ +#define MXC_SRPG_NEON_SRPGCR (MXC_SRPG_NEON_BASE + 0x0) +#define MXC_SRPG_NEON_PUPSCR (MXC_SRPG_NEON_BASE + 0x4) +#define MXC_SRPG_NEON_PDNSCR (MXC_SRPG_NEON_BASE + 0x8) + +#define MXC_SRPG_ARM_SRPGCR (MXC_SRPG_ARM_BASE + 0x0) +#define MXC_SRPG_ARM_PUPSCR (MXC_SRPG_ARM_BASE + 0x4) +#define MXC_SRPG_ARM_PDNSCR (MXC_SRPG_ARM_BASE + 0x8) + +#define MXC_SRPG_EMPGC0_SRPGCR (MXC_SRPG_EMPGC0_BASE + 0x0) +#define MXC_SRPG_EMPGC0_PUPSCR (MXC_SRPG_EMPGC0_BASE + 0x4) +#define MXC_SRPG_EMPGC0_PDNSCR (MXC_SRPG_EMPGC0_BASE + 0x8) + +#define MXC_SRPG_EMPGC1_SRPGCR (MXC_SRPG_EMPGC1_BASE + 0x0) +#define MXC_SRPG_EMPGC1_PUPSCR (MXC_SRPG_EMPGC1_BASE + 0x4) +#define MXC_SRPG_EMPGC1_PDNSCR (MXC_SRPG_EMPGC1_BASE + 0x8) + +#define MXC_SRPG_MEGAMIX_SRPGCR (MXC_SRPG_MEGAMIX_BASE + 0x0) +#define MXC_SRPG_MEGAMIX_PUPSCR (MXC_SRPG_MEGAMIX_BASE + 0x4) +#define MXC_SRPG_MEGAMIX_PDNSCR (MXC_SRPG_MEGAMIX_BASE + 0x8) + +#define MXC_SRPGC_EMI_SRPGCR (MXC_SRPGC_EMI_BASE + 0x0) +#define MXC_SRPGC_EMI_PUPSCR (MXC_SRPGC_EMI_BASE + 0x4) +#define MXC_SRPGC_EMI_PDNSCR (MXC_SRPGC_EMI_BASE + 0x8) + +#endif /* __ARCH_ARM_MACH_MX51_CRM_REGS_H__ */ diff --git a/arch/arm/mach-mx51/devices.c b/arch/arm/mach-mx51/devices.c new file mode 100644 index 000000000000..0947206dc2a9 --- /dev/null +++ b/arch/arm/mach-mx51/devices.c @@ -0,0 +1,1168 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> +#include <linux/uio_driver.h> +#include <linux/mxc_scc2_driver.h> +#include <linux/pwm_backlight.h> +#include <mach/hardware.h> +#include <mach/spba.h> +#include <asm/mach-types.h> +#include "iomux.h" +#include "crm_regs.h" +#include <mach/sdma.h> +#include "sdma_script_code.h" + +/* Flag used to indicate when IRAM has been initialized */ +int iram_ready; + +void mxc_sdma_get_script_info(sdma_script_start_addrs * sdma_script_addr) +{ + /* AP<->BP */ + sdma_script_addr->mxc_sdma_ap_2_ap_addr = ap_2_ap_ADDR; + sdma_script_addr->mxc_sdma_ap_2_bp_addr = -1; + sdma_script_addr->mxc_sdma_bp_2_ap_addr = -1; + sdma_script_addr->mxc_sdma_ap_2_ap_fixed_addr = -1; + + /*misc */ + sdma_script_addr->mxc_sdma_loopback_on_dsp_side_addr = -1; + sdma_script_addr->mxc_sdma_mcu_interrupt_only_addr = -1; + + /* firi */ + sdma_script_addr->mxc_sdma_firi_2_per_addr = -1; + sdma_script_addr->mxc_sdma_firi_2_mcu_addr = -1; + sdma_script_addr->mxc_sdma_per_2_firi_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_firi_addr = -1; + + /* uart */ + sdma_script_addr->mxc_sdma_uart_2_per_addr = uart_2_per_ADDR; + sdma_script_addr->mxc_sdma_uart_2_mcu_addr = uart_2_mcu_ADDR; + + /* UART SH */ + sdma_script_addr->mxc_sdma_uartsh_2_per_addr = uartsh_2_per_ADDR; + sdma_script_addr->mxc_sdma_uartsh_2_mcu_addr = uartsh_2_mcu_ADDR; + + /* SHP */ + sdma_script_addr->mxc_sdma_per_2_shp_addr = per_2_shp_ADDR; + sdma_script_addr->mxc_sdma_shp_2_per_addr = shp_2_per_ADDR; + sdma_script_addr->mxc_sdma_mcu_2_shp_addr = mcu_2_shp_ADDR; + sdma_script_addr->mxc_sdma_shp_2_mcu_addr = shp_2_mcu_ADDR; + + /* ATA */ + sdma_script_addr->mxc_sdma_mcu_2_ata_addr = mcu_2_ata_ADDR; + sdma_script_addr->mxc_sdma_ata_2_mcu_addr = ata_2_mcu_ADDR; + + /* app */ + sdma_script_addr->mxc_sdma_app_2_per_addr = app_2_per_ADDR; + sdma_script_addr->mxc_sdma_app_2_mcu_addr = app_2_mcu_ADDR; + sdma_script_addr->mxc_sdma_per_2_app_addr = per_2_app_ADDR; + sdma_script_addr->mxc_sdma_mcu_2_app_addr = mcu_2_app_ADDR; + + /* MSHC */ + sdma_script_addr->mxc_sdma_mshc_2_mcu_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_mshc_addr = -1; + + /* spdif */ + sdma_script_addr->mxc_sdma_spdif_2_mcu_addr = -1; + sdma_script_addr->mxc_sdma_mcu_2_spdif_addr = mcu_2_spdif_ADDR; + + /* IPU */ + sdma_script_addr->mxc_sdma_ext_mem_2_ipu_addr = ext_mem__ipu_ram_ADDR; + + /* DVFS */ + sdma_script_addr->mxc_sdma_dptc_dvfs_addr = -1; + + /* core */ + sdma_script_addr->mxc_sdma_start_addr = (unsigned short *)sdma_code; + sdma_script_addr->mxc_sdma_ram_code_start_addr = RAM_CODE_START_ADDR; + sdma_script_addr->mxc_sdma_ram_code_size = RAM_CODE_SIZE; +} + +static void mxc_nop_release(struct device *dev) +{ + /* Nothing */ +} + +#if defined(CONFIG_W1_MASTER_MXC) || defined(CONFIG_W1_MASTER_MXC_MODULE) +static struct resource w1_resources[] = { + { + .start = MXC_INT_OWIRE, + .flags = IORESOURCE_IRQ, + } +}; + +static struct mxc_w1_config mxc_w1_data = { + .search_rom_accelerator = 1, +}; + +static struct platform_device mxc_w1_devices = { + .name = "mxc_w1", + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_w1_data, + }, + .num_resources = ARRAY_SIZE(w1_resources), + .resource = w1_resources, + .id = 0 +}; + +static void mxc_init_owire(void) +{ + (void)platform_device_register(&mxc_w1_devices); +} +#else +static inline void mxc_init_owire(void) +{ +} +#endif + +#if defined(CONFIG_RTC_DRV_MXC_V2) || defined(CONFIG_RTC_DRV_MXC_V2_MODULE) +static struct mxc_srtc_platform_data srtc_data = { + .srtc_sec_mode_addr = 0x83F98840, +}; + +static struct resource rtc_resources[] = { + { + .start = SRTC_BASE_ADDR, + .end = SRTC_BASE_ADDR + 0x40, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_INT_SRTC_NTZ, + .flags = IORESOURCE_IRQ, + }, +}; +static struct platform_device mxc_rtc_device = { + .name = "mxc_rtc", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &srtc_data, + }, + .num_resources = ARRAY_SIZE(rtc_resources), + .resource = rtc_resources, +}; +static void mxc_init_rtc(void) +{ + (void)platform_device_register(&mxc_rtc_device); +} +#else +static inline void mxc_init_rtc(void) +{ +} +#endif + +#if defined(CONFIG_MXC_WATCHDOG) || defined(CONFIG_MXC_WATCHDOG_MODULE) + +static struct resource wdt_resources[] = { + { + .start = WDOG1_BASE_ADDR, + .end = WDOG1_BASE_ADDR + 0x30, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device mxc_wdt_device = { + .name = "mxc_wdt", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(wdt_resources), + .resource = wdt_resources, +}; + +static void mxc_init_wdt(void) +{ + (void)platform_device_register(&mxc_wdt_device); +} +#else +static inline void mxc_init_wdt(void) +{ +} +#endif + +#if defined(CONFIG_MXC_PWM) +static struct resource pwm_resources[] = { + { + .start = PWM1_BASE_ADDR, + .end = PWM1_BASE_ADDR + 0x14, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device mxc_pwm_device = { + .name = "mxc_pwm", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(pwm_resources), + .resource = pwm_resources, +}; + +static void mxc_init_pwm(void) +{ + printk(KERN_INFO "mxc_pwm_device registered\n"); + if (platform_device_register(&mxc_pwm_device) < 0) + printk(KERN_ERR "registration of mxc_pwm device failed\n"); +} +#else +static void mxc_init_pwm(void) +{ + +} +#endif + +#if defined(CONFIG_BACKLIGHT_PWM) +static struct platform_pwm_backlight_data mxc_pwm_backlight_data = { + .pwm_id = 0, + .max_brightness = 255, + .dft_brightness = 128, + .pwm_period_ns = 78770, +}; + +static struct platform_device mxc_pwm_backlight_device = { + .name = "pwm-backlight", + .id = -1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_pwm_backlight_data, + }, +}; + +static void mxc_init_pwm_backlight(void) +{ + printk(KERN_INFO "pwm-backlight device registered\n"); + if (platform_device_register(&mxc_pwm_backlight_device) < 0) + printk(KERN_ERR + "registration of pwm-backlight device failed\n"); +} +#else +static void mxc_init_pwm_backlight(void) +{ + +} +#endif + +#if defined(CONFIG_MXC_IPU_V3) || defined(CONFIG_MXC_IPU_V3_MODULE) +static struct mxc_ipu_config mxc_ipu_data = { + .rev = 1, +}; + +static struct resource ipu_resources[] = { + { + .start = IPU_CTRL_BASE_ADDR, + .end = IPU_CTRL_BASE_ADDR + SZ_512M, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_INT_IPU_SYN, + .flags = IORESOURCE_IRQ, + }, + { + .start = MXC_INT_IPU_ERR, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_ipu_device = { + .name = "mxc_ipu", + .id = -1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_ipu_data, + }, + .num_resources = ARRAY_SIZE(ipu_resources), + .resource = ipu_resources, +}; + +static void mxc_init_ipu(void) +{ + void __iomem *reg_hsc_mcd = IO_ADDRESS(MIPI_HSC_BASE_ADDR); + void __iomem *reg_hsc_mxt_conf = IO_ADDRESS(MIPI_HSC_BASE_ADDR + 0x800); + struct clk *clk; + uint32_t temp; + + /* Select IPUv3 h/w version */ + if (cpu_is_mx51_rev(CHIP_REV_2_0) > 0) + mxc_ipu_data.rev = 2; + + mxc_ipu_data.di_clk[1] = clk_get(NULL, "ipu_di1_clk"); + clk = clk_get(NULL, "tve_clk"); + clk_set_parent(mxc_ipu_data.di_clk[1], clk); + clk_put(clk); + + /* Temporarily setup MIPI module to legacy mode */ + clk = clk_get(NULL, "mipi_hsp_clk"); + if (!IS_ERR(clk)) { + clk_enable(clk); + + /* Temporarily setup MIPI module to legacy mode */ + __raw_writel(0xF00, reg_hsc_mcd); + + /* CSI mode reserved*/ + temp = __raw_readl(reg_hsc_mxt_conf); + __raw_writel(temp | 0x0FF, reg_hsc_mxt_conf); + + if (cpu_is_mx51_rev(CHIP_REV_2_0) > 0) { + temp = __raw_readl(reg_hsc_mxt_conf); + __raw_writel(temp | 0x10000, reg_hsc_mxt_conf); + } + + clk_disable(clk); + clk_put(clk); + } + platform_device_register(&mxc_ipu_device); +} +#else +static inline void mxc_init_ipu(void) +{ +} +#endif + +#if defined(CONFIG_MXC_VPU) || defined(CONFIG_MXC_VPU_MODULE) +static struct resource vpu_resources[] = { + [0] = { + .start = VPU_IRAM_BASE_ADDR, + .end = VPU_IRAM_BASE_ADDR + VPU_IRAM_SIZE, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IO_ADDRESS(SRC_BASE_ADDR), + .end = IO_ADDRESS(SRC_BASE_ADDR), + .flags = IORESOURCE_MEM, + }, +}; + +/*! Platform Data for MXC VPU */ +static struct platform_device mxcvpu_device = { + .name = "mxc_vpu", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(vpu_resources), + .resource = vpu_resources, +}; + +static inline void mxc_init_vpu(void) +{ + if (platform_device_register(&mxcvpu_device) < 0) + printk(KERN_ERR "Error: Registering the VPU.\n"); +} +#else +static inline void mxc_init_vpu(void) +{ +} +#endif + +/*! + * This is platform device structure for adding SCC + */ +#if defined(CONFIG_MXC_SECURITY_SCC) || defined(CONFIG_MXC_SECURITY_SCC_MODULE) +static struct platform_device mxc_scc_device = { + .name = "mxc_scc", + .id = 0, +}; + +static void mxc_init_scc(void) +{ + platform_device_register(&mxc_scc_device); +} +#else +static inline void mxc_init_scc(void) +{ + uint32_t reg_value; + uint32_t reg_mask = 0; + uint8_t *UMID_base; + uint32_t *MAP_base; + uint8_t i; + uint32_t partition_no; + uint32_t scc_partno; + void *scm_ram_base; + void *scc_base; + uint8_t iram_partitions = 16; + + if (cpu_is_mx51_rev(CHIP_REV_2_0) < 0) + iram_partitions = 12; + + scc_base = ioremap((uint32_t) SCC_BASE_ADDR, 0x140); + if (scc_base == NULL) { + printk(KERN_ERR "FAILED TO MAP IRAM REGS\n"); + return; + } + scm_ram_base = ioremap((uint32_t) IRAM_BASE_ADDR, IRAM_SIZE); + if (scm_ram_base == NULL) { + printk(KERN_ERR "FAILED TO MAP IRAM\n"); + return; + } + + for (partition_no = 0; partition_no < iram_partitions; partition_no++) { + /*De-allocate a Partition*/ + reg_value = ((partition_no << SCM_ZCMD_PART_SHIFT) & + SCM_ZCMD_PART_MASK) | ((0x03 << + SCM_ZCMD_CCMD_SHIFT) + & SCM_ZCMD_CCMD_MASK); + __raw_writel(reg_value, scc_base + SCM_ZCMD_REG); + msleep(1); + while ((__raw_readl(scc_base + SCM_STATUS_REG) & + SCM_STATUS_SRS_READY) != SCM_STATUS_SRS_READY) ; + + /*In Supervisor mode claims a partition for it's own use + by writing zero to SMID register.*/ + __raw_writel(0, scc_base + (SCM_SMID0_REG + 8 * partition_no)); + + reg_mask |= (3 << (2 * (partition_no))); + } + + msleep(1); + reg_value = __raw_readl(scc_base + SCM_PART_OWNERS_REG); + + if ((reg_value & reg_mask) != reg_mask) { + printk(KERN_ERR "FAILED TO ACQUIRE IRAM PARTITION\n"); + iounmap(scm_ram_base); + iounmap(scc_base); + return; + } + + for (partition_no = 0; partition_no < iram_partitions; partition_no++) { + MAP_base = scm_ram_base + (partition_no * 0x2000); + UMID_base = (uint8_t *) MAP_base + 0x10; + + for (i = 0; i < 16; i++) + UMID_base[i] = 0; + + MAP_base[0] = SCM_PERM_NO_ZEROIZE | SCM_PERM_HD_SUP_DISABLE | + SCM_PERM_HD_READ | SCM_PERM_HD_WRITE | SCM_PERM_HD_EXECUTE | + SCM_PERM_TH_READ | SCM_PERM_TH_WRITE ; + } + + /* Freeing 2 partitions for SCC2 */ + scc_partno = iram_partitions - (SCC_IRAM_SIZE / SZ_8K); + for (partition_no = scc_partno; partition_no < iram_partitions; + partition_no++) { + reg_value = ((partition_no << SCM_ZCMD_PART_SHIFT) & + SCM_ZCMD_PART_MASK) | ((0x03 << + SCM_ZCMD_CCMD_SHIFT) + & SCM_ZCMD_CCMD_MASK); + __raw_writel(reg_value, scc_base + SCM_ZCMD_REG); + msleep(1); + while ((__raw_readl(scc_base + SCM_STATUS_REG) & + SCM_STATUS_SRS_READY) != SCM_STATUS_SRS_READY) ; + } + iounmap(scm_ram_base); + iounmap(scc_base); + printk(KERN_INFO "IRAM READY\n"); + iram_ready = 1; +} +#endif + +/* SPI controller and device data */ +#if defined(CONFIG_SPI_MXC) || defined(CONFIG_SPI_MXC_MODULE) + +#ifdef CONFIG_SPI_MXC_SELECT1 +/*! + * Resource definition for the CSPI1 + */ +static struct resource mxcspi1_resources[] = { + [0] = { + .start = CSPI1_BASE_ADDR, + .end = CSPI1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CSPI1, + .end = MXC_INT_CSPI1, + .flags = IORESOURCE_IRQ, + }, +}; + +extern void mx51_babbage_gpio_spi_chipselect_active(int cspi_mode, int status, + int chipselect); +extern void mx51_babbage_gpio_spi_chipselect_inactive(int cspi_mode, int status, + int chipselect); +/*! Platform Data for MXC CSPI1 */ +static struct mxc_spi_master mxcspi1_data = { + .maxchipselect = 4, + .spi_version = 23, +}; + +/*! Device Definition for MXC CSPI1 */ +static struct platform_device mxcspi1_device = { + .name = "mxc_spi", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxcspi1_data, + }, + .num_resources = ARRAY_SIZE(mxcspi1_resources), + .resource = mxcspi1_resources, +}; + +#endif /* CONFIG_SPI_MXC_SELECT1 */ + +#ifdef CONFIG_SPI_MXC_SELECT2 +/*! + * Resource definition for the CSPI2 + */ +static struct resource mxcspi2_resources[] = { + [0] = { + .start = CSPI2_BASE_ADDR, + .end = CSPI2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CSPI2, + .end = MXC_INT_CSPI2, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC CSPI2 */ +static struct mxc_spi_master mxcspi2_data = { + .maxchipselect = 4, + .spi_version = 23, +}; + +/*! Device Definition for MXC CSPI2 */ +static struct platform_device mxcspi2_device = { + .name = "mxc_spi", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxcspi2_data, + }, + .num_resources = ARRAY_SIZE(mxcspi2_resources), + .resource = mxcspi2_resources, +}; +#endif /* CONFIG_SPI_MXC_SELECT2 */ + +#ifdef CONFIG_SPI_MXC_SELECT3 +/*! + * Resource definition for the CSPI3 + */ +static struct resource mxcspi3_resources[] = { + [0] = { + .start = CSPI3_BASE_ADDR, + .end = CSPI3_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_CSPI, + .end = MXC_INT_CSPI, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC CSPI3 */ +static struct mxc_spi_master mxcspi3_data = { + .maxchipselect = 4, + .spi_version = 7, +}; + +/*! Device Definition for MXC CSPI3 */ +static struct platform_device mxcspi3_device = { + .name = "mxc_spi", + .id = 2, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxcspi3_data, + }, + .num_resources = ARRAY_SIZE(mxcspi3_resources), + .resource = mxcspi3_resources, +}; +#endif /* CONFIG_SPI_MXC_SELECT3 */ + +void __init mxc_init_spi(void) +{ + /* SPBA configuration for CSPI2 - MCU is set */ + spba_take_ownership(SPBA_CSPI1, SPBA_MASTER_A); +#ifdef CONFIG_SPI_MXC_SELECT1 + if (machine_is_mx51_babbage()) { + mxcspi1_data.chipselect_active = + mx51_babbage_gpio_spi_chipselect_active; + mxcspi1_data.chipselect_inactive = + mx51_babbage_gpio_spi_chipselect_inactive; + } + if (platform_device_register(&mxcspi1_device) < 0) + printk(KERN_ERR "Error: Registering the SPI Controller_1\n"); +#endif /* CONFIG_SPI_MXC_SELECT1 */ +#ifdef CONFIG_SPI_MXC_SELECT2 + if (platform_device_register(&mxcspi2_device) < 0) + printk(KERN_ERR "Error: Registering the SPI Controller_2\n"); +#endif /* CONFIG_SPI_MXC_SELECT2 */ +#ifdef CONFIG_SPI_MXC_SELECT3 + if (platform_device_register(&mxcspi3_device) < 0) + printk(KERN_ERR "Error: Registering the SPI Controller_3\n"); +#endif /* CONFIG_SPI_MXC_SELECT3 */ +} +#else +void __init mxc_init_spi(void) +{ +} +#endif + +/* I2C controller and device data */ +#if defined(CONFIG_I2C_MXC) || defined(CONFIG_I2C_MXC_MODULE) + +#ifdef CONFIG_I2C_MXC_SELECT1 +/*! + * Resource definition for the I2C1 + */ +static struct resource mxci2c1_resources[] = { + [0] = { + .start = I2C1_BASE_ADDR, + .end = I2C1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C1, + .end = MXC_INT_I2C1, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c1_data = { + .i2c_clk = 100000, +}; +#endif + +#ifdef CONFIG_I2C_MXC_SELECT2 +/*! + * Resource definition for the I2C2 + */ +static struct resource mxci2c2_resources[] = { + [0] = { + .start = I2C2_BASE_ADDR, + .end = I2C2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C2, + .end = MXC_INT_I2C2, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c2_data = { + .i2c_clk = 100000, +}; +#endif + +#ifdef CONFIG_I2C_MXC_SELECT3 +/*! + * Resource definition for the I2C3 + */ +static struct resource mxci2c3_resources[] = { + [0] = { + .start = I2C3_BASE_ADDR, + .end = I2C3_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_I2C3, + .end = MXC_INT_I2C3, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c3_data = { + .i2c_clk = 100000, +}; +#endif + +/*! Device Definition for MXC I2C1 */ +static struct platform_device mxci2c_devices[] = { +#ifdef CONFIG_I2C_MXC_SELECT1 + { + .name = "mxc_i2c", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c1_data, + }, + .num_resources = ARRAY_SIZE(mxci2c1_resources), + .resource = mxci2c1_resources,}, +#endif +#ifdef CONFIG_I2C_MXC_SELECT2 + { + .name = "mxc_i2c", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c2_data, + }, + .num_resources = ARRAY_SIZE(mxci2c2_resources), + .resource = mxci2c2_resources,}, +#endif +#ifdef CONFIG_I2C_MXC_SELECT3 + { + .name = "mxc_i2c", + .id = 2, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c3_data, + }, + .num_resources = ARRAY_SIZE(mxci2c3_resources), + .resource = mxci2c3_resources,}, +#endif +}; + +static inline void mxc_init_i2c(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mxci2c_devices); i++) { + if (platform_device_register(&mxci2c_devices[i]) < 0) + dev_err(&mxci2c_devices[i].dev, + "Unable to register I2C device\n"); + } +} +#else +static inline void mxc_init_i2c(void) +{ +} +#endif + +#if defined(CONFIG_I2C_MXC_HS) || defined(CONFIG_I2C_MXC_HS_MODULE) +static struct resource mxci2c_hs_resources[] = { + [0] = { + .start = HSI2C_DMA_BASE_ADDR, + .end = HSI2C_DMA_BASE_ADDR + SZ_16K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_HS_I2C, + .end = MXC_INT_HS_I2C, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for MXC I2C */ +static struct mxc_i2c_platform_data mxci2c_hs_data = { + .i2c_clk = 400000, +}; + +static struct platform_device mxci2c_hs_device = { + .name = "mxc_i2c_hs", + .id = 3, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxci2c_hs_data, + }, + .num_resources = ARRAY_SIZE(mxci2c_hs_resources), + .resource = mxci2c_hs_resources +}; + +static inline void mxc_init_i2c_hs(void) +{ + if (platform_device_register(&mxci2c_hs_device) < 0) + dev_err(&mxci2c_hs_device.dev, + "Unable to register High Speed I2C device\n"); +} +#else +static inline void mxc_init_i2c_hs(void) +{ +} +#endif + +#if defined(CONFIG_FB_MXC_TVOUT_TVE) || defined(CONFIG_FB_MXC_TVOUT_TVE_MODULE) +static struct resource tve_resources[] = { + { + .start = TVE_BASE_ADDR, + .end = TVE_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MXC_INT_TVE, + .flags = IORESOURCE_IRQ, + }, +}; +static struct tve_platform_data tve_data = { + .dac_reg = "VVIDEO", + .dig_reg = "VDIG", +}; + +static struct platform_device mxc_tve_device = { + .name = "tve", + .dev = { + .platform_data = &tve_data, + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(tve_resources), + .resource = tve_resources, +}; + +void __init mxc_init_tve(void) +{ + platform_device_register(&mxc_tve_device); +} +#else +static inline void mxc_init_tve(void) +{ +} +#endif + +/*! + * Resource definition for the DVFS CORE + */ +static struct resource dvfs_core_resources[] = { + [0] = { + .start = MXC_DVFS_CORE_BASE, + .end = MXC_DVFS_CORE_BASE + 4 * SZ_16 - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_GPC1, + .end = MXC_INT_GPC1, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Platform Data for DVFS CORE */ +struct mxc_dvfs_platform_data dvfs_core_data = { + .reg_id = "SW1", + .clk1_id = "cpu_clk", + .clk2_id = "gpc_dvfs_clk", + .gpc_cntr_reg_addr = MXC_GPC_CNTR, + .gpc_vcr_reg_addr = MXC_GPC_VCR, + .ccm_cdcr_reg_addr = MXC_CCM_CDCR, + .ccm_cacrr_reg_addr = MXC_CCM_CACRR, + .ccm_cdhipr_reg_addr = MXC_CCM_CDHIPR, + .dvfs_thrs_reg_addr = MXC_DVFSTHRS, + .dvfs_coun_reg_addr = MXC_DVFSCOUN, + .dvfs_emac_reg_addr = MXC_DVFSEMAC, + .dvfs_cntr_reg_addr = MXC_DVFSCNTR, + .prediv_mask = 0x1F800, + .prediv_offset = 11, + .prediv_val = 3, + .div3ck_mask = 0xE0000000, + .div3ck_offset = 29, + .div3ck_val = 2, + .emac_val = 0x08, + .upthr_val = 25, + .dnthr_val = 9, + .pncthr_val = 33, + .upcnt_val = 10, + .dncnt_val = 10, + .delay_time = 30, + .num_wp = 3, +}; + +/*! Device Definition for MXC DVFS core */ +static struct platform_device mxc_dvfs_core_device = { + .name = "mxc_dvfs_core", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &dvfs_core_data, + }, + .num_resources = ARRAY_SIZE(dvfs_core_resources), + .resource = dvfs_core_resources, +}; + +static inline void mxc_init_dvfs(void) +{ + if (platform_device_register(&mxc_dvfs_core_device) < 0) + dev_err(&mxc_dvfs_core_device.dev, + "Unable to register DVFS core device\n"); +} + +struct mxc_gpio_port mxc_gpio_ports[] = { + [0] = { + .chip.label = "gpio-0", + .base = IO_ADDRESS(GPIO1_BASE_ADDR), + .irq = MXC_INT_GPIO1_LOW, + .irq_high = MXC_INT_GPIO1_HIGH, + .virtual_irq_start = MXC_GPIO_IRQ_START + }, + [1] = { + .chip.label = "gpio-1", + .base = IO_ADDRESS(GPIO2_BASE_ADDR), + .irq = MXC_INT_GPIO2_LOW, + .irq_high = MXC_INT_GPIO2_HIGH, + .virtual_irq_start = MXC_GPIO_IRQ_START + 32 * 1 + }, + [2] = { + .chip.label = "gpio-2", + .base = IO_ADDRESS(GPIO3_BASE_ADDR), + .irq = MXC_INT_GPIO3_LOW, + .irq_high = MXC_INT_GPIO3_HIGH, + .virtual_irq_start = MXC_GPIO_IRQ_START + 32 * 2 + }, + [3] = { + .chip.label = "gpio-3", + .base = IO_ADDRESS(GPIO4_BASE_ADDR), + .irq = MXC_INT_GPIO4_LOW, + .irq_high = MXC_INT_GPIO4_HIGH, + .virtual_irq_start = MXC_GPIO_IRQ_START + 32 * 3 + } +}; + +int __init mxc_register_gpios(void) +{ + return mxc_gpio_init(mxc_gpio_ports, ARRAY_SIZE(mxc_gpio_ports)); +} + +static struct platform_device mxc_dma_device = { + .name = "mxc_dma", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, +}; + +static inline void mxc_init_dma(void) +{ + (void)platform_device_register(&mxc_dma_device); +} + +static struct resource spdif_resources[] = { + { + .start = SPDIF_BASE_ADDR, + .end = SPDIF_BASE_ADDR + 0x50, + .flags = IORESOURCE_MEM, + }, +}; + +static struct mxc_spdif_platform_data mxc_spdif_data = { + .spdif_tx = 1, + .spdif_rx = 0, + .spdif_clk_44100 = 0, /* spdif_ext_clk source for 44.1KHz */ + .spdif_clk_48000 = 7, /* audio osc source */ + .spdif_clkid = 0, + .spdif_clk = NULL, /* spdif bus clk */ +}; + +static struct platform_device mxc_alsa_spdif_device = { + .name = "mxc_alsa_spdif", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_spdif_data, + }, + .num_resources = ARRAY_SIZE(spdif_resources), + .resource = spdif_resources, +}; + +static inline void mxc_init_spdif(void) +{ + mxc_spdif_data.spdif_core_clk = clk_get(NULL, "spdif_xtal_clk"); + clk_put(mxc_spdif_data.spdif_core_clk); + platform_device_register(&mxc_alsa_spdif_device); +} + +static struct platform_device mx51_lpmode_device = { + .name = "mx51_lpmode", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, +}; + +static inline void mx51_init_lpmode(void) +{ + (void)platform_device_register(&mx51_lpmode_device); +} + +static struct platform_device busfreq_device = { + .name = "busfreq", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, +}; + +static inline void mxc_init_busfreq(void) +{ + (void)platform_device_register(&busfreq_device); +} + +#if defined(CONFIG_MXC_IIM) || defined(CONFIG_MXC_IIM_MODULE) +static struct resource mxc_iim_resources[] = { + { + .start = IIM_BASE_ADDR, + .end = IIM_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device mxc_iim_device = { + .name = "mxc_iim", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(mxc_iim_resources), + .resource = mxc_iim_resources +}; + +static inline void mxc_init_iim(void) +{ + if (platform_device_register(&mxc_iim_device) < 0) + dev_err(&mxc_iim_device.dev, + "Unable to register mxc iim device\n"); +} +#else +static inline void mxc_init_iim(void) +{ +} +#endif + +static struct resource mxc_gpu_resources[] = { + [0] = { + .start = MXC_INT_GPU2_IRQ, + .end = MXC_INT_GPU2_IRQ, + .name = "gpu_2d_irq", + .flags = IORESOURCE_IRQ,}, + [1] = { + .start = MXC_INT_GPU, + .end = MXC_INT_GPU, + .name = "gpu_3d_irq", + .flags = IORESOURCE_IRQ,}, +}; + +static struct platform_device gpu_device = { + .name = "mxc_gpu", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(mxc_gpu_resources), + .resource = mxc_gpu_resources, +}; + +static void __init mxc_init_gpu(void) +{ + platform_device_register(&gpu_device); +} + +static struct resource mxc_gpu2d_resources[] = { + { + .start = GPU2D_BASE_ADDR, + .end = GPU2D_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .flags = IORESOURCE_MEM, + }, +}; + +#if defined(CONFIG_UIO_PDRV_GENIRQ) || defined(CONFIG_UIO_PDRV_GENIRQ_MODULE) +static struct clk *gpu_clk; + +int gpu2d_open(struct uio_info *info, struct inode *inode) +{ + gpu_clk = clk_get(NULL, "gpu2d_clk"); + if (IS_ERR(gpu_clk)) + return PTR_ERR(gpu_clk); + + return clk_enable(gpu_clk); +} + +int gpu2d_release(struct uio_info *info, struct inode *inode) +{ + if (IS_ERR(gpu_clk)) + return PTR_ERR(gpu_clk); + + clk_disable(gpu_clk); + clk_put(gpu_clk); + return 0; +} + +static int gpu2d_mmap(struct uio_info *info, struct vm_area_struct *vma) +{ + int mi = vma->vm_pgoff; + if (mi < 0) + return -EINVAL; + + vma->vm_flags |= VM_IO | VM_RESERVED; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + return remap_pfn_range(vma, + vma->vm_start, + info->mem[mi].addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); +} + +static struct uio_info gpu2d_info = { + .name = "imx_gpu2d", + .version = "1", + .irq = MXC_INT_GPU2_IRQ, + .open = gpu2d_open, + .release = gpu2d_release, + .mmap = gpu2d_mmap, +}; + +static struct platform_device mxc_gpu2d_device = { + .name = "uio_pdrv_genirq", + .dev = { + .release = mxc_nop_release, + .platform_data = &gpu2d_info, + .coherent_dma_mask = 0xFFFFFFFF, + }, + .num_resources = ARRAY_SIZE(mxc_gpu2d_resources), + .resource = mxc_gpu2d_resources, +}; + +static inline void mxc_init_gpu2d(void) +{ + dma_alloc_coherent(&mxc_gpu2d_device.dev, SZ_8K, &mxc_gpu2d_resources[1].start, GFP_DMA); + mxc_gpu2d_resources[1].end = mxc_gpu2d_resources[1].start + SZ_8K - 1; + + platform_device_register(&mxc_gpu2d_device); +} +#else +static inline void mxc_init_gpu2d(void) +{ +} +#endif + +int __init mxc_init_devices(void) +{ + mxc_init_wdt(); + mxc_init_spi(); + mxc_init_i2c(); + mxc_init_i2c_hs(); + mxc_init_rtc(); + mxc_init_scc(); + mxc_init_dma(); + mxc_init_owire(); + mxc_init_ipu(); + mxc_init_vpu(); + mxc_init_spdif(); + mxc_init_tve(); + mx51_init_lpmode(); + mxc_init_busfreq(); + mxc_init_dvfs(); + mxc_init_iim(); + mxc_init_gpu(); + mxc_init_gpu2d(); + mxc_init_pwm(); + mxc_init_pwm_backlight(); + return 0; +} diff --git a/arch/arm/mach-mx51/dma.c b/arch/arm/mach-mx51/dma.c new file mode 100644 index 000000000000..a27d7e2a2349 --- /dev/null +++ b/arch/arm/mach-mx51/dma.c @@ -0,0 +1,666 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/init.h> +#include <linux/device.h> +#include <asm/dma.h> +#include <mach/hardware.h> + +#include "serial.h" + +#define MXC_MMC_BUFFER_ACCESS 0x20 +#define MXC_SDHC_MMC_WML 64 +#define MXC_SDHC_SD_WML 256 +#define MXC_SSI_TX0_REG 0x0 +#define MXC_SSI_TX1_REG 0x4 +#define MXC_SSI_RX0_REG 0x8 +#define MXC_SSI_RX1_REG 0xC +#define MXC_SSI_TXFIFO_WML 0x4 +#define MXC_SSI_RXFIFO_WML 0x6 +#define MXC_SPDIF_TXFIFO_WML 0x8 +#define MXC_SPDIF_TX_REG 0x2C + +typedef struct mxc_sdma_info_entry_s { + mxc_dma_device_t device; + mxc_sdma_channel_params_t *chnl_info; +} mxc_sdma_info_entry_t; + +static mxc_sdma_channel_params_t mxc_sdma_uart1_rx_params = { + .chnl_params = { + .watermark_level = UART1_UFCR_RXTL, + .per_address = UART1_BASE_ADDR, + .peripheral_type = UART, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART1_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART1_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart1_tx_params = { + .chnl_params = { + .watermark_level = UART1_UFCR_TXTL, + .per_address = UART1_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART1_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART1_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart2_rx_params = { + .chnl_params = { + .watermark_level = UART2_UFCR_RXTL, + .per_address = UART2_BASE_ADDR, + .peripheral_type = UART, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART2_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART2_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart2_tx_params = { + .chnl_params = { + .watermark_level = UART2_UFCR_TXTL, + .per_address = UART2_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART2_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART2_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart3_rx_params = { + .chnl_params = { + .watermark_level = UART3_UFCR_RXTL, + .per_address = UART3_BASE_ADDR, + .peripheral_type = UART_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_UART3_RX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART3_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_uart3_tx_params = { + .chnl_params = { + .watermark_level = UART3_UFCR_TXTL, + .per_address = UART3_BASE_ADDR + MXC_UARTUTXD, + .peripheral_type = UART_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_UART3_TX, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_UART3_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_mmc1_width1_params = { + .chnl_params = { + .watermark_level = MXC_SDHC_MMC_WML, + .per_address = + MMC_SDHC1_BASE_ADDR + MXC_MMC_BUFFER_ACCESS, + .peripheral_type = MMC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SDHC1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MMC1, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_mmc1_width4_params = { + .chnl_params = { + .watermark_level = MXC_SDHC_SD_WML, + .per_address = + MMC_SDHC1_BASE_ADDR + MXC_MMC_BUFFER_ACCESS, + .peripheral_type = MMC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SDHC1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MMC1, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_mmc2_width1_params = { + .chnl_params = { + .watermark_level = MXC_SDHC_MMC_WML, + .per_address = + MMC_SDHC2_BASE_ADDR + MXC_MMC_BUFFER_ACCESS, + .peripheral_type = MMC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SDHC2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MMC2, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_mmc2_width4_params = { + .chnl_params = { + .watermark_level = MXC_SDHC_SD_WML, + .per_address = + MMC_SDHC2_BASE_ADDR + MXC_MMC_BUFFER_ACCESS, + .peripheral_type = MMC, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SDHC2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MMC2, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_8bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_16bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI1_RX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi1_24bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI1_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI1_TX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI1_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_rx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_tx0_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX0_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX1, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_8bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX2, + .bd_number = 32, + .word_size = TRANSFER_8BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_16bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX2, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_rx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_RXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_RX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_SSI2_RX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_RX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ssi2_24bit_tx1_params = { + .chnl_params = { + .watermark_level = MXC_SSI_TXFIFO_WML, + .per_address = SSI2_BASE_ADDR + MXC_SSI_TX1_REG, + .peripheral_type = SSI_SP, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SSI2_TX2, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SSI2_TX, + .chnl_priority = 2, +}; + +static mxc_sdma_channel_params_t mxc_sdma_memory_params = { + .chnl_params = { + .peripheral_type = MEMORY, + .transfer_type = emi_2_emi, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_MEMORY, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ata_rx_params = { + .chnl_params = { + .watermark_level = MXC_IDE_DMA_WATERMARK, + .per_address = ATA_DMA_BASE_ADDR, + .peripheral_type = ATA, + .transfer_type = per_2_emi, + .event_id = DMA_REQ_ATA_TX_END, + .event_id2 = DMA_REQ_ATA_RX, + .bd_number = MXC_IDE_DMA_BD_NR, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ATA_RX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_ata_tx_params = { + .chnl_params = { + .watermark_level = MXC_IDE_DMA_WATERMARK, + .per_address = ATA_DMA_BASE_ADDR + 0x18, + .peripheral_type = ATA, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_ATA_TX_END, + .event_id2 = DMA_REQ_ATA_TX, + .bd_number = MXC_IDE_DMA_BD_NR, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_ATA_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_spdif_16bit_tx_params = { + .chnl_params = { + .watermark_level = MXC_SPDIF_TXFIFO_WML, + .per_address = SPDIF_BASE_ADDR + MXC_SPDIF_TX_REG, + .peripheral_type = SPDIF, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SPDIF, + .bd_number = 32, + .word_size = TRANSFER_16BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SPDIF_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_channel_params_t mxc_sdma_spdif_32bit_tx_params = { + .chnl_params = { + .watermark_level = MXC_SPDIF_TXFIFO_WML, + .per_address = SPDIF_BASE_ADDR + MXC_SPDIF_TX_REG, + .peripheral_type = SPDIF, + .transfer_type = emi_2_per, + .event_id = DMA_REQ_SPDIF, + .bd_number = 32, + .word_size = TRANSFER_32BIT, + }, + .channel_num = MXC_DMA_CHANNEL_SPDIF_TX, + .chnl_priority = MXC_SDMA_DEFAULT_PRIORITY, +}; + +static mxc_sdma_info_entry_t mxc_sdma_active_dma_info[] = { + {MXC_DMA_UART1_RX, &mxc_sdma_uart1_rx_params}, + {MXC_DMA_UART1_TX, &mxc_sdma_uart1_tx_params}, + {MXC_DMA_UART2_RX, &mxc_sdma_uart2_rx_params}, + {MXC_DMA_UART2_TX, &mxc_sdma_uart2_tx_params}, + {MXC_DMA_UART3_RX, &mxc_sdma_uart3_rx_params}, + {MXC_DMA_UART3_TX, &mxc_sdma_uart3_tx_params}, + {MXC_DMA_MMC1_WIDTH_1, &mxc_sdma_mmc1_width1_params}, + {MXC_DMA_MMC1_WIDTH_4, &mxc_sdma_mmc1_width4_params}, + {MXC_DMA_MMC2_WIDTH_1, &mxc_sdma_mmc2_width1_params}, + {MXC_DMA_MMC2_WIDTH_4, &mxc_sdma_mmc2_width4_params}, + {MXC_DMA_SSI1_8BIT_RX0, &mxc_sdma_ssi1_8bit_rx0_params}, + {MXC_DMA_SSI1_8BIT_TX0, &mxc_sdma_ssi1_8bit_tx0_params}, + {MXC_DMA_SSI1_16BIT_RX0, &mxc_sdma_ssi1_16bit_rx0_params}, + {MXC_DMA_SSI1_16BIT_TX0, &mxc_sdma_ssi1_16bit_tx0_params}, + {MXC_DMA_SSI1_24BIT_RX0, &mxc_sdma_ssi1_24bit_rx0_params}, + {MXC_DMA_SSI1_24BIT_TX0, &mxc_sdma_ssi1_24bit_tx0_params}, + {MXC_DMA_SSI1_8BIT_RX1, &mxc_sdma_ssi1_8bit_rx1_params}, + {MXC_DMA_SSI1_8BIT_TX1, &mxc_sdma_ssi1_8bit_tx1_params}, + {MXC_DMA_SSI1_16BIT_RX1, &mxc_sdma_ssi1_16bit_rx1_params}, + {MXC_DMA_SSI1_16BIT_TX1, &mxc_sdma_ssi1_16bit_tx1_params}, + {MXC_DMA_SSI1_24BIT_RX1, &mxc_sdma_ssi1_24bit_rx1_params}, + {MXC_DMA_SSI1_24BIT_TX1, &mxc_sdma_ssi1_24bit_tx1_params}, + {MXC_DMA_SSI2_8BIT_RX0, &mxc_sdma_ssi2_8bit_rx0_params}, + {MXC_DMA_SSI2_8BIT_TX0, &mxc_sdma_ssi2_8bit_tx0_params}, + {MXC_DMA_SSI2_16BIT_RX0, &mxc_sdma_ssi2_16bit_rx0_params}, + {MXC_DMA_SSI2_16BIT_TX0, &mxc_sdma_ssi2_16bit_tx0_params}, + {MXC_DMA_SSI2_24BIT_RX0, &mxc_sdma_ssi2_24bit_rx0_params}, + {MXC_DMA_SSI2_24BIT_TX0, &mxc_sdma_ssi2_24bit_tx0_params}, + {MXC_DMA_SSI2_8BIT_RX1, &mxc_sdma_ssi2_8bit_rx1_params}, + {MXC_DMA_SSI2_8BIT_TX1, &mxc_sdma_ssi2_8bit_tx1_params}, + {MXC_DMA_SSI2_16BIT_RX1, &mxc_sdma_ssi2_16bit_rx1_params}, + {MXC_DMA_SSI2_16BIT_TX1, &mxc_sdma_ssi2_16bit_tx1_params}, + {MXC_DMA_SSI2_24BIT_RX1, &mxc_sdma_ssi2_24bit_rx1_params}, + {MXC_DMA_SSI2_24BIT_TX1, &mxc_sdma_ssi2_24bit_tx1_params}, + {MXC_DMA_MEMORY, &mxc_sdma_memory_params}, + {MXC_DMA_ATA_RX, &mxc_sdma_ata_rx_params}, + {MXC_DMA_ATA_TX, &mxc_sdma_ata_tx_params}, + {MXC_DMA_SPDIF_16BIT_TX, &mxc_sdma_spdif_16bit_tx_params}, + {MXC_DMA_SPDIF_32BIT_TX, &mxc_sdma_spdif_32bit_tx_params}, +}; + +static int mxc_sdma_info_entrys = + sizeof(mxc_sdma_active_dma_info) / sizeof(mxc_sdma_active_dma_info[0]); + +/*! + * This functions Returns the SDMA paramaters associated for a module + * + * @param channel_id the ID of the module requesting DMA + * @return returns the sdma parameters structure for the device + */ +mxc_sdma_channel_params_t *mxc_sdma_get_channel_params(mxc_dma_device_t + channel_id) +{ + mxc_sdma_info_entry_t *p = mxc_sdma_active_dma_info; + int i; + + for (i = 0; i < mxc_sdma_info_entrys; i++, p++) { + if (p->device == channel_id) + return p->chnl_info; + + } + return NULL; +} + +/*! + * This functions marks the SDMA channels that are statically allocated + * + * @param chnl the channel array used to store channel information + */ +void mxc_get_static_channels(mxc_dma_channel_t *chnl) +{ +#ifdef CONFIG_SDMA_IRAM + int i; + for (i = MXC_DMA_CHANNEL_IRAM; i < MAX_DMA_CHANNELS; i++) + chnl[i].dynamic = 0; +#endif +} + +EXPORT_SYMBOL(mxc_sdma_get_channel_params); +EXPORT_SYMBOL(mxc_get_static_channels); diff --git a/arch/arm/mach-mx51/dummy_gpio.c b/arch/arm/mach-mx51/dummy_gpio.c new file mode 100644 index 000000000000..8d9537d31026 --- /dev/null +++ b/arch/arm/mach-mx51/dummy_gpio.c @@ -0,0 +1,113 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/errno.h> +#include <linux/module.h> + +void gpio_uart_active(int port, int no_irda) {} +EXPORT_SYMBOL(gpio_uart_active); + +void gpio_uart_inactive(int port, int no_irda) {} +EXPORT_SYMBOL(gpio_uart_inactive); + +void gpio_gps_active(void) {} +EXPORT_SYMBOL(gpio_gps_active); + +void gpio_gps_inactive(void) {} +EXPORT_SYMBOL(gpio_gps_inactive); + +void config_uartdma_event(int port) {} +EXPORT_SYMBOL(config_uartdma_event); + +void gpio_spi_active(int cspi_mod) {} +EXPORT_SYMBOL(gpio_spi_active); + +void gpio_spi_inactive(int cspi_mod) {} +EXPORT_SYMBOL(gpio_spi_inactive); + +void gpio_owire_active(void) {} +EXPORT_SYMBOL(gpio_owire_active); + +void gpio_owire_inactive(void) {} +EXPORT_SYMBOL(gpio_owire_inactive); + +void gpio_i2c_active(int i2c_num) {} +EXPORT_SYMBOL(gpio_i2c_active); + +void gpio_i2c_inactive(int i2c_num) {} +EXPORT_SYMBOL(gpio_i2c_inactive); + +void gpio_i2c_hs_active(void) {} +EXPORT_SYMBOL(gpio_i2c_hs_active); + +void gpio_i2c_hs_inactive(void) {} +EXPORT_SYMBOL(gpio_i2c_hs_inactive); + +void gpio_pmic_active(void) {} +EXPORT_SYMBOL(gpio_pmic_active); + +void gpio_activate_audio_ports(void) {} +EXPORT_SYMBOL(gpio_activate_audio_ports); + +void gpio_sdhc_active(int module) {} +EXPORT_SYMBOL(gpio_sdhc_active); + +void gpio_sdhc_inactive(int module) {} +EXPORT_SYMBOL(gpio_sdhc_inactive); + +void gpio_sensor_select(int sensor) {} + +void gpio_sensor_active(unsigned int csi) {} +EXPORT_SYMBOL(gpio_sensor_active); + +void gpio_sensor_inactive(unsigned int csi) {} +EXPORT_SYMBOL(gpio_sensor_inactive); + +void gpio_ata_active(void) {} +EXPORT_SYMBOL(gpio_ata_active); + +void gpio_ata_inactive(void) {} +EXPORT_SYMBOL(gpio_ata_inactive); + +void gpio_nand_active(void) {} +EXPORT_SYMBOL(gpio_nand_active); + +void gpio_nand_inactive(void) {} +EXPORT_SYMBOL(gpio_nand_inactive); + +void gpio_keypad_active(void) {} +EXPORT_SYMBOL(gpio_keypad_active); + +void gpio_keypad_inactive(void) {} +EXPORT_SYMBOL(gpio_keypad_inactive); + +int gpio_usbotg_hs_active(void) +{ + return 0; +} +EXPORT_SYMBOL(gpio_usbotg_hs_active); + +void gpio_usbotg_hs_inactive(void) {} +EXPORT_SYMBOL(gpio_usbotg_hs_inactive); + +void gpio_fec_active(void) {} +EXPORT_SYMBOL(gpio_fec_active); + +void gpio_fec_inactive(void) {} +EXPORT_SYMBOL(gpio_fec_inactive); + +void gpio_spdif_active(void) {} +EXPORT_SYMBOL(gpio_spdif_active); + +void gpio_spdif_inactive(void) {} +EXPORT_SYMBOL(gpio_spdif_inactive); diff --git a/arch/arm/mach-mx51/iomux.c b/arch/arm/mach-mx51/iomux.c new file mode 100644 index 000000000000..3af813347118 --- /dev/null +++ b/arch/arm/mach-mx51/iomux.c @@ -0,0 +1,244 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup GPIO_MX51 Board GPIO and Muxing Setup + * @ingroup MSL_MX51 + */ +/*! + * @file mach-mx51/iomux.c + * + * @brief I/O Muxing control functions + * + * @ingroup GPIO_MX51 + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <mach/hardware.h> +#include <mach/gpio.h> +#include <mach/irqs.h> +#include "iomux.h" + +/*! + * IOMUX register (base) addresses + */ +#define IOMUXGPR0 (IO_ADDRESS(IOMUXC_BASE_ADDR)) +#define IOMUXGPR1 (IO_ADDRESS(IOMUXC_BASE_ADDR) + 0x004) +#define IOMUXSW_MUX_CTL (IO_ADDRESS(IOMUXC_BASE_ADDR)) +#define IOMUXSW_MUX_END (IO_ADDRESS(IOMUXC_BASE_ADDR) + MUX_I_END) +#define IOMUXSW_PAD_CTL (IO_ADDRESS(IOMUXC_BASE_ADDR) + PAD_I_START) +#define IOMUXSW_INPUT_CTL (IO_ADDRESS(IOMUXC_BASE_ADDR)) + +#define MUX_PIN_NUM_MAX ((MUX_I_END >> 2) + 1) + +static u8 iomux_pin_res_table[MUX_PIN_NUM_MAX]; +static DEFINE_SPINLOCK(gpio_mux_lock); + +static inline void * _get_mux_reg(iomux_pin_name_t pin) +{ + u32 mux_reg = PIN_TO_IOMUX_MUX(pin); + + if (cpu_is_mx51_rev(CHIP_REV_2_0) < 0) { + if ((pin == MX51_PIN_NANDF_RB5) || + (pin == MX51_PIN_NANDF_RB6) || + (pin == MX51_PIN_NANDF_RB7)) + ; /* Do nothing */ + else if (mux_reg >= 0x2FC) + mux_reg += 8; + else if (mux_reg >= 0x130) + mux_reg += 0xC; + } + return IOMUXSW_MUX_CTL + mux_reg; +} + +static inline void * _get_pad_reg(iomux_pin_name_t pin) +{ + u32 pad_reg = PIN_TO_IOMUX_PAD(pin); + + if (cpu_is_mx51_rev(CHIP_REV_2_0) < 0) { + if ((pin == MX51_PIN_NANDF_RB5) || + (pin == MX51_PIN_NANDF_RB6) || + (pin == MX51_PIN_NANDF_RB7)) + ; /* Do nothing */ + else if (pad_reg == 0x4D0 - PAD_I_START) + pad_reg += 0x4C; + else if (pad_reg == 0x860 - PAD_I_START) + pad_reg += 0x9C; + else if (pad_reg >= 0x804 - PAD_I_START) + pad_reg += 0xB0; + else if (pad_reg >= 0x7FC - PAD_I_START) + pad_reg += 0xB4; + else if (pad_reg >= 0x4E4 - PAD_I_START) + pad_reg += 0xCC; + else + pad_reg += 8; + } + return IOMUXSW_PAD_CTL + pad_reg; +} + +static inline void * _get_mux_end(void) +{ + if (cpu_is_mx51_rev(CHIP_REV_2_0) < 0) + return(IO_ADDRESS(IOMUXC_BASE_ADDR) + (0x3F8 - 4)); + else + return(IO_ADDRESS(IOMUXC_BASE_ADDR) + (0x3F0 - 4)); +} + +/*! + * This function is used to configure a pin through the IOMUX module. + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param config a configuration as defined in \b #iomux_pin_cfg_t + * + * @return 0 if successful; Non-zero otherwise + */ +static int iomux_config_mux(iomux_pin_name_t pin, iomux_pin_cfg_t config) +{ + u32 ret = 0; + u32 pin_index = PIN_TO_IOMUX_INDEX(pin); + void __iomem *mux_reg = _get_mux_reg(pin); + u32 mux_data = 0; + u8 *rp; + + BUG_ON((mux_reg > _get_mux_end()) || (mux_reg < IOMUXSW_MUX_CTL)); + spin_lock(&gpio_mux_lock); + + if (config == IOMUX_CONFIG_GPIO) + mux_data = PIN_TO_ALT_GPIO(pin); + else + mux_data = config; + + __raw_writel(mux_data, mux_reg); + + /* + * Log a warning if a pin changes ownership + */ + rp = iomux_pin_res_table + pin_index; + if ((mux_data & *rp) && (*rp != mux_data)) { + /* + * Don't call printk if we're tweaking the console uart or + * we'll deadlock. + */ + printk(KERN_ERR "iomux_config_mux: Warning: iomux pin" + " config changed, reg=%p, " + " prev=0x%x new=0x%x\n", mux_reg, *rp, mux_data); + ret = -EINVAL; + } + *rp = mux_data; + spin_unlock(&gpio_mux_lock); + return ret; +} + +/*! + * Request ownership for an IO pin. This function has to be the first one + * being called before that pin is used. The caller has to check the + * return value to make sure it returns 0. + * + * @param pin a name defined by \b iomux_pin_name_t + * @param config a configuration as defined in \b #iomux_pin_cfg_t + * + * @return 0 if successful; Non-zero otherwise + */ +int mxc_request_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t config) +{ + int ret = iomux_config_mux(pin, config); + int gpio = IOMUX_TO_GPIO(pin); + + if (!ret && (gpio < MXC_GPIO_IRQS) && ((config == IOMUX_CONFIG_GPIO) + || (config == PIN_TO_ALT_GPIO(pin)))) + ret |= gpio_request(gpio, NULL); + + return ret; +} +EXPORT_SYMBOL(mxc_request_iomux); + +/*! + * Release ownership for an IO pin + * + * @param pin a name defined by \b iomux_pin_name_t + * @param config config as defined in \b #iomux_pin_ocfg_t + */ +void mxc_free_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t config) +{ + u32 pin_index = PIN_TO_IOMUX_INDEX(pin); + u8 *rp = iomux_pin_res_table + pin_index; + int gpio = IOMUX_TO_GPIO(pin); + + *rp = 0; + if ((gpio < MXC_GPIO_IRQS) + && ((config == IOMUX_CONFIG_GPIO) + || (config == PIN_TO_ALT_GPIO(pin)))) + gpio_free(gpio); + +} +EXPORT_SYMBOL(mxc_free_iomux); + +/*! + * This function configures the pad value for a IOMUX pin. + * + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param config the ORed value of elements defined in \b #iomux_pad_config_t + */ +void mxc_iomux_set_pad(iomux_pin_name_t pin, u32 config) +{ + void __iomem *pad_reg = _get_pad_reg(pin); + + BUG_ON(pad_reg < IOMUXSW_PAD_CTL); + __raw_writel(config, pad_reg); +} +EXPORT_SYMBOL(mxc_iomux_set_pad); + +unsigned int mxc_iomux_get_pad(iomux_pin_name_t pin) +{ + void __iomem *pad_reg = _get_pad_reg(pin); + + return __raw_readl(pad_reg); +} +EXPORT_SYMBOL(mxc_iomux_get_pad); + +/*! + * This function configures input path. + * + * @param input index of input select register as defined in \b #iomux_input_select_t + * @param config the binary value of elements defined in \b #iomux_input_config_t + * */ +void mxc_iomux_set_input(iomux_input_select_t input, u32 config) +{ + void __iomem *reg; + + if (cpu_is_mx51_rev(CHIP_REV_2_0) < 0) { + if (input == MUX_IN_IPU_IPP_DI_0_IND_DISPB_SD_D_SELECT_INPUT) + input -= 4; + else if (input == MUX_IN_IPU_IPP_DI_1_IND_DISPB_SD_D_SELECT_INPUT) + input -= 3; + else if (input >= MUX_IN_KPP_IPP_IND_COL_6_SELECT_INPUT) + input -= 2; + else if (input >= MUX_IN_HSC_MIPI_MIX_PAR_SISG_TRIG_SELECT_INPUT) + input -= 5; + else if (input >= MUX_IN_HSC_MIPI_MIX_IPP_IND_SENS1_DATA_EN_SELECT_INPUT) + input -= 3; + else if (input >= MUX_IN_ECSPI2_IPP_IND_SS_B_3_SELECT_INPUT) + input -= 2; + else if (input >= MUX_IN_CCM_PLL1_BYPASS_CLK_SELECT_INPUT) + input -= 1; + + reg = IOMUXSW_INPUT_CTL + (input << 2) + INPUT_CTL_START_TO1; + } else { + reg = IOMUXSW_INPUT_CTL + (input << 2) + INPUT_CTL_START; + } + + BUG_ON(input >= MUX_INPUT_NUM_MUX); + __raw_writel(config, reg); +} +EXPORT_SYMBOL(mxc_iomux_set_input); diff --git a/arch/arm/mach-mx51/iomux.h b/arch/arm/mach-mx51/iomux.h new file mode 100644 index 000000000000..e314ab26ac9b --- /dev/null +++ b/arch/arm/mach-mx51/iomux.h @@ -0,0 +1,246 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MACH_MX51_IOMUX_H__ +#define __MACH_MX51_IOMUX_H__ + +#include <linux/types.h> +#include <mach/gpio.h> +#include "mx51_pins.h" + +/*! + * @file mach-mx51/iomux.h + * + * @brief I/O Muxing control definitions and functions + * + * @ingroup GPIO_MX51 + */ + +typedef unsigned int iomux_pin_name_t; + +/*! + * various IOMUX output functions + */ +typedef enum iomux_config { + IOMUX_CONFIG_ALT0, /*!< used as alternate function 0 */ + IOMUX_CONFIG_ALT1, /*!< used as alternate function 1 */ + IOMUX_CONFIG_ALT2, /*!< used as alternate function 2 */ + IOMUX_CONFIG_ALT3, /*!< used as alternate function 3 */ + IOMUX_CONFIG_ALT4, /*!< used as alternate function 4 */ + IOMUX_CONFIG_ALT5, /*!< used as alternate function 5 */ + IOMUX_CONFIG_ALT6, /*!< used as alternate function 6 */ + IOMUX_CONFIG_ALT7, /*!< used as alternate function 7 */ + IOMUX_CONFIG_GPIO, /*!< added to help user use GPIO mode */ + IOMUX_CONFIG_SION = 0x1 << 4, /*!< used as LOOPBACK:MUX SION bit */ +} iomux_pin_cfg_t; + +/*! + * various IOMUX pad functions + */ +typedef enum iomux_pad_config { + PAD_CTL_SRE_SLOW = 0x0 << 0, + PAD_CTL_SRE_FAST = 0x1 << 0, + PAD_CTL_DRV_LOW = 0x0 << 1, + PAD_CTL_DRV_MEDIUM = 0x1 << 1, + PAD_CTL_DRV_HIGH = 0x2 << 1, + PAD_CTL_DRV_MAX = 0x3 << 1, + PAD_CTL_ODE_OPENDRAIN_NONE = 0x0 << 3, + PAD_CTL_ODE_OPENDRAIN_ENABLE = 0x1 << 3, + PAD_CTL_100K_PD = 0x0 << 4, + PAD_CTL_47K_PU = 0x1 << 4, + PAD_CTL_100K_PU = 0x2 << 4, + PAD_CTL_22K_PU = 0x3 << 4, + PAD_CTL_PUE_KEEPER = 0x0 << 6, + PAD_CTL_PUE_PULL = 0x1 << 6, + PAD_CTL_PKE_NONE = 0x0 << 7, + PAD_CTL_PKE_ENABLE = 0x1 << 7, + PAD_CTL_HYS_NONE = 0x0 << 8, + PAD_CTL_HYS_ENABLE = 0x1 << 8, + PAD_CTL_DDR_INPUT_CMOS = 0x0 << 9, + PAD_CTL_DDR_INPUT_DDR = 0x1 << 9, + PAD_CTL_DRV_VOT_LOW = 0x0 << 13, + PAD_CTL_DRV_VOT_HIGH = 0x1 << 13, +} iomux_pad_config_t; + +/*! + * various IOMUX input select register index + */ +typedef enum iomux_input_select { + MUX_IN_AUDMUX_P4_INPUT_DA_AMX_SELECT_I = 0, + MUX_IN_AUDMUX_P4_INPUT_DB_AMX_SELECT_I, + MUX_IN_AUDMUX_P4_INPUT_TXCLK_AMX_SELECT_INPUT, + MUX_IN_AUDMUX_P4_INPUT_TXFS_AMX_SELECT_INPUT, + MUX_IN_AUDMUX_P5_INPUT_DA_AMX_SELECT_INPUT, + MUX_IN_AUDMUX_P5_INPUT_DB_AMX_SELECT_INPUT, + MUX_IN_AUDMUX_P5_INPUT_RXCLK_AMX_SELECT_INPUT, + MUX_IN_AUDMUX_P5_INPUT_RXFS_AMX_SELECT, + MUX_IN_AUDMUX_P5_INPUT_TXCLK_AMX_SELECT_INPUT, + MUX_IN_AUDMUX_P5_INPUT_TXFS_AMX_SELECT_INPUT, + MUX_IN_AUDMUX_P6_INPUT_DA_AMX_SELECT_INPUT, + MUX_IN_AUDMUX_P6_INPUT_DB_AMX_SELECT_INPUT, + MUX_IN_AUDMUX_P6_INPUT_RXCLK_AMX_SELECT_INPUT, + MUX_IN_AUDMUX_P6_INPUT_RXFS_AMX_SELECT_INPUT, + MUX_IN_AUDMUX_P6_INPUT_TXCLK_AMX_SELECT_INPUT, + MUX_IN_AUDMUX_P6_INPUT_TXFS_AMX_SELECT_INPUT, + MUX_IN_CCM_IPP_DI_CLK_SELECT_INPUT, + /* TO2 */ + MUX_IN_CCM_IPP_DI1_CLK_SELECT_INPUT, + MUX_IN_CCM_PLL1_BYPASS_CLK_SELECT_INPUT, + MUX_IN_CCM_PLL2_BYPASS_CLK_SELECT_INPUT, + MUX_IN_CSPI_IPP_CSPI_CLK_IN_SELECT_INPUT, + MUX_IN_CSPI_IPP_IND_MISO_SELECT_INPUT, + MUX_IN_CSPI_IPP_IND_MOSI_SELECT_INPUT, + MUX_IN_CSPI_IPP_IND_SS_B_1_SELECT_INPUT, + MUX_IN_CSPI_IPP_IND_SS_B_2_SELECT_INPUT, + MUX_IN_CSPI_IPP_IND_SS_B_3_SELECT_INPUT, + MUX_IN_DPLLIP1_L1T_TOG_EN_SELECT_INPUT, + /* TO2 */ + MUX_IN_ECSPI2_IPP_IND_SS_B_1_SELECT_INPUT, + MUX_IN_ECSPI2_IPP_IND_SS_B_3_SELECT_INPUT, + MUX_IN_EMI_IPP_IND_RDY_INT_SELECT_INPUT, + MUX_IN_ESDHC3_IPP_DAT0_IN_SELECT_INPUT, + MUX_IN_ESDHC3_IPP_DAT1_IN_SELECT_INPUT, + MUX_IN_ESDHC3_IPP_DAT2_IN_SELECT_INPUT, + MUX_IN_ESDHC3_IPP_DAT3_IN_SELECT_INPUT, + MUX_IN_FEC_FEC_COL_SELECT_INPUT, + MUX_IN_FEC_FEC_CRS_SELECT_INPUT, + MUX_IN_FEC_FEC_MDI_SELECT_INPUT, + MUX_IN_FEC_FEC_RDATA_0_SELECT_INPUT, + MUX_IN_FEC_FEC_RDATA_1_SELECT_INPUT, + MUX_IN_FEC_FEC_RDATA_2_SELECT_INPUT, + MUX_IN_FEC_FEC_RDATA_3_SELECT_INPUT, + MUX_IN_FEC_FEC_RX_CLK_SELECT_INPUT, + MUX_IN_FEC_FEC_RX_DV_SELECT_INPUT, + MUX_IN_FEC_FEC_RX_ER_SELECT_INPUT, + MUX_IN_FEC_FEC_TX_CLK_SELECT_INPUT, + MUX_IN_GPIO3_IPP_IND_G_IN_1_SELECT_INPUT, + MUX_IN_GPIO3_IPP_IND_G_IN_2_SELECT_INPUT, + MUX_IN_GPIO3_IPP_IND_G_IN_3_SELECT_INPUT, + MUX_IN_GPIO3_IPP_IND_G_IN_4_SELECT_INPUT, + MUX_IN_GPIO3_IPP_IND_G_IN_5_SELECT_INPUT, + MUX_IN_GPIO3_IPP_IND_G_IN_6_SELECT_INPUT, + MUX_IN_GPIO3_IPP_IND_G_IN_7_SELECT_INPUT, + MUX_IN_GPIO3_IPP_IND_G_IN_8_SELECT_INPUT, + /* TO2 */ + MUX_IN_GPIO3_IPP_IND_G_IN_12_SELECT_INPUT, + MUX_IN_HSC_MIPI_MIX_IPP_IND_SENS1_DATA_EN_SELECT_INPUT, + MUX_IN_HSC_MIPI_MIX_IPP_IND_SENS2_DATA_EN_SELECT_INPUT, + /* TO2 */ + MUX_IN_HSC_MIPI_MIX_PAR_VSYNC_SELECT_INPUT, + /* TO2 */ + MUX_IN_HSC_MIPI_MIX_PAR_DI_WAIT_SELECT_INPUT, + MUX_IN_HSC_MIPI_MIX_PAR_SISG_TRIG_SELECT_INPUT, + MUX_IN_I2C1_IPP_SCL_IN_SELECT_INPUT, + MUX_IN_I2C1_IPP_SDA_IN_SELECT_INPUT, + MUX_IN_I2C2_IPP_SCL_IN_SELECT_INPUT, + MUX_IN_I2C2_IPP_SDA_IN_SELECT_INPUT, + + MUX_IN_IPU_IPP_DI_0_IND_DISPB_SD_D_SELECT_INPUT, + + MUX_IN_IPU_IPP_DI_1_IND_DISPB_SD_D_SELECT_INPUT, + + MUX_IN_KPP_IPP_IND_COL_6_SELECT_INPUT, + MUX_IN_KPP_IPP_IND_COL_7_SELECT_INPUT, + MUX_IN_KPP_IPP_IND_ROW_4_SELECT_INPUT, + MUX_IN_KPP_IPP_IND_ROW_5_SELECT_INPUT, + MUX_IN_KPP_IPP_IND_ROW_6_SELECT_INPUT, + MUX_IN_KPP_IPP_IND_ROW_7_SELECT_INPUT, + MUX_IN_UART1_IPP_UART_RTS_B_SELECT_INPUT, + MUX_IN_UART1_IPP_UART_RXD_MUX_SELECT_INPUT, + MUX_IN_UART2_IPP_UART_RTS_B_SELECT_INPUT, + MUX_IN_UART2_IPP_UART_RXD_MUX_SELECT_INPUT, + MUX_IN_UART3_IPP_UART_RTS_B_SELECT_INPUT, + MUX_IN_UART3_IPP_UART_RXD_MUX_SELECT_INPUT, + MUX_IN_USBOH3_IPP_IND_UH3_CLK_SELECT_INPUT, + MUX_IN_USBOH3_IPP_IND_UH3_DATA_0_SELECT_INPUT, + MUX_IN_USBOH3_IPP_IND_UH3_DATA_1_SELECT_INPUT, + MUX_IN_USBOH3_IPP_IND_UH3_DATA_2_SELECT_INPUT, + MUX_IN_USBOH3_IPP_IND_UH3_DATA_3_SELECT_INPUT, + MUX_IN_USBOH3_IPP_IND_UH3_DATA_4_SELECT_INPUT, + MUX_IN_USBOH3_IPP_IND_UH3_DATA_5_SELECT_INPUT, + MUX_IN_USBOH3_IPP_IND_UH3_DATA_6_SELECT_INPUT, + MUX_IN_USBOH3_IPP_IND_UH3_DATA_7_SELECT_INPUT, + MUX_IN_USBOH3_IPP_IND_UH3_DIR_SELECT_INPUT, + MUX_IN_USBOH3_IPP_IND_UH3_NXT_SELECT_INPUT, + MUX_IN_USBOH3_IPP_IND_UH3_STP_SELECT_INPUT, + MUX_INPUT_NUM_MUX, +} iomux_input_select_t; + +/*! + * various IOMUX input functions + */ +typedef enum iomux_input_config { + INPUT_CTL_PATH0 = 0x0, + INPUT_CTL_PATH1, + INPUT_CTL_PATH2, + INPUT_CTL_PATH3, + INPUT_CTL_PATH4, + INPUT_CTL_PATH5, + INPUT_CTL_PATH6, + INPUT_CTL_PATH7, +} iomux_input_config_t; + +struct mxc_iomux_pin_cfg { + iomux_pin_name_t pin; + u8 mux_mode; + u16 pad_cfg; + u8 in_select; + u8 in_mode; +}; + +/*! + * Request ownership for an IO pin. This function has to be the first one + * being called before that pin is used. The caller has to check the + * return value to make sure it returns 0. + * + * @param pin a name defined by \b iomux_pin_name_t + * @param config config as defined in \b #iomux_pin_ocfg_t + * + * @return 0 if successful; Non-zero otherwise + */ +int mxc_request_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t config); + +/*! + * Release ownership for an IO pin + * + * @param pin a name defined by \b iomux_pin_name_t + * @param config config as defined in \b #iomux_pin_ocfg_t + */ +void mxc_free_iomux(iomux_pin_name_t pin, iomux_pin_cfg_t config); + +/*! + * This function configures the pad value for a IOMUX pin. + * + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @param config the ORed value of elements defined in + * \b #iomux_pad_config_t + */ +void mxc_iomux_set_pad(iomux_pin_name_t pin, u32 config); + +/*! + * This function gets the current pad value for a IOMUX pin. + * + * @param pin a pin number as defined in \b #iomux_pin_name_t + * @return current pad value + */ +unsigned int mxc_iomux_get_pad(iomux_pin_name_t pin); + +/*! + * This function configures input path. + * + * @param input index of input select register as defined in + * \b #iomux_input_select_t + * @param config the binary value of elements defined in \b #iomux_input_config_t + */ +void mxc_iomux_set_input(iomux_input_select_t input, u32 config); + +#endif /* __MACH_MX51_IOMUX_H__ */ diff --git a/arch/arm/mach-mx51/lpmodes.c b/arch/arm/mach-mx51/lpmodes.c new file mode 100644 index 000000000000..32af9ccc4f6a --- /dev/null +++ b/arch/arm/mach-mx51/lpmodes.c @@ -0,0 +1,309 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx51_lpmodes.c + * + * @brief Driver for the Freescale Semiconductor MXC low power modes setup. + * + * MX51 is designed to play and video with minimal power consumption. + * This driver enables the platform to enter and exit audio and video low + * power modes. + * + * @ingroup PM + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <mach/clock.h> +#include <mach/hardware.h> +#include <linux/regulator/machine.h> +#include "crm_regs.h" + +#define ARM_LP_CLK 166250000 +#define GP_LPM_VOLTAGE 775000 +#define GP_NORMAL_VOLTAGE 1050000 + +static int org_cpu_rate; +int lp_video_mode; +int lp_audio_mode; +static struct device *lpmode_dev; +struct regulator *gp_core; + +void enter_lp_video_mode(void) +{ +} + +void exit_lp_video_mode(void) +{ +} + +void enter_lp_audio_mode(void) +{ + struct clk *tclk; + int ret; + + struct clk *p_clk; + struct clk *amode_parent_clk; + + + tclk = clk_get(NULL, "ipu_clk"); + if (clk_get_usecount(tclk) != 0) { + printk(KERN_INFO + "Cannot enter AUDIO LPM mode - display is still active\n"); + return; + } + + tclk = clk_get(NULL, "cpu_clk"); + org_cpu_rate = clk_get_rate(tclk); + +#ifdef CHANGE_DDR2_TO_PLL2 + tclk = clk_get(NULL, "ddr_clk"); + clk_set_parent(tclk, clk_get(NULL, "axi_a_clk")); + + /* Set CPU clock to be derived from PLL2 instead of PLL1 */ + tclk = clk_get(NULL, "pll1_sw_clk"); + clk_set_parent(tclk, clk_get(NULL, "pll2")); + clk_enable(tclk); + + tclk = clk_get(NULL, "ddr_clk"); + clk_set_parent(tclk, clk_get(NULL, "ddr_hf_clk")); +#endif + + /*Change the DDR freq to 133Mhz. */ + tclk = clk_get(NULL, "ddr_hf_clk"); + clk_set_rate(tclk, clk_round_rate(tclk, 133000000)); + + tclk = clk_get(NULL, "cpu_clk"); + ret = clk_set_rate(tclk, ARM_LP_CLK); + if (ret != 0) + printk(KERN_DEBUG "cannot set CPU clock rate\n"); + clk_put(tclk); + /* Set the voltage to 0.775v for the GP domain. */ + ret = regulator_set_voltage(gp_core, GP_LPM_VOLTAGE, GP_LPM_VOLTAGE); + if (ret < 0) + printk(KERN_DEBUG "COULD NOT SET GP VOLTAGE!!!\n"); + + tclk = clk_get(NULL, "periph_apm_clk"); + amode_parent_clk = clk_get(NULL, "lp_apm"); + p_clk = clk_get_parent(tclk); + /* Make sure osc_clk is the parent of lp_apm. */ + clk_set_parent(amode_parent_clk, clk_get(NULL, "osc")); + /* Set the parent of periph_apm_clk to be lp_apm */ + clk_set_parent(tclk, amode_parent_clk); + + amode_parent_clk = tclk; + + tclk = clk_get(NULL, "main_bus_clk"); + p_clk = clk_get_parent(tclk); + /* Set the parent of main_bus_clk to be periph_apm_clk */ + clk_set_parent(tclk, amode_parent_clk); + + clk_set_rate(clk_get(NULL, "axi_a_clk"), 24000000); + clk_set_rate(clk_get(NULL, "axi_b_clk"), 24000000); + clk_set_rate(clk_get(NULL, "ahb_clk"), 24000000); + clk_set_rate(clk_get(NULL, "emi_slow_clk"), 24000000); + clk_set_rate(clk_get(NULL, "nfc_clk"), 12000000); + + /* disable PLL3 */ + tclk = clk_get(NULL, "pll3"); + if (tclk->usecount == 1) + clk_disable(tclk); + + /* disable PLL2 */ + tclk = clk_get(NULL, "pll2"); + if (tclk->usecount == 1) + clk_disable(tclk); + + /* disable PLL1 */ + tclk = clk_get(NULL, "pll1_main_clk"); + if (tclk->usecount == 1) + clk_disable(tclk); + + lp_audio_mode = 1; +} + +void exit_lp_audio_mode(void) +{ + struct clk *tclk; + struct clk *p_clk; + struct clk *rmode_parent_clk; + int ret; + + /* Set the voltage to 1.05v for the GP domain. */ + ret = regulator_set_voltage(gp_core, + GP_NORMAL_VOLTAGE, GP_NORMAL_VOLTAGE); + if (ret < 0) + printk(KERN_DEBUG "COULD NOT SET GP VOLTAGE!!!\n"); + + rmode_parent_clk = clk_get(NULL, "pll2"); + clk_enable(rmode_parent_clk); + + tclk = clk_get(NULL, "main_bus_clk"); + p_clk = clk_get_parent(tclk); + + /* Set the dividers before setting the parent clock. */ + clk_set_rate(clk_get(NULL, "axi_a_clk"), 6000000); + clk_set_rate(clk_get(NULL, "axi_b_clk"), 4800000); + clk_set_rate(clk_get(NULL, "ahb_clk"), 4800000); + clk_set_rate(clk_get(NULL, "emi_slow_clk"), 4800000); + clk_set_rate(clk_get(NULL, "nfc_clk"), 1200000); + /* Set the parent of main_bus_clk to be pll2 */ + clk_set_parent(tclk, rmode_parent_clk); + +#ifdef CHANGE_DDR2_TO_PLL2 + tclk = clk_get(NULL, "ddr_clk"); + clk_set_parent(tclk, clk_get(NULL, "axi_a_clk")); + + /* Set CPU clock to be derived from PLL1 instead of PLL2 */ + tclk = clk_get(NULL, "pll1_sw_clk"); + clk_set_parent(tclk, clk_get(NULL, "pll1_main_clk")); + clk_disable(tclk); + + tclk = clk_get(NULL, "ddr_clk"); + clk_set_parent(tclk, clk_get(NULL, "ddr_hf_clk")); +#endif + + tclk = clk_get(NULL, "cpu_clk"); + ret = clk_set_rate(tclk, org_cpu_rate); + if (ret != 0) + printk(KERN_DEBUG "cannot set CPU clock rate\n"); + clk_put(tclk); + + tclk = clk_get(NULL, "cpu_clk"); + + ret = clk_set_rate(tclk, org_cpu_rate); + if (ret != 0) + printk(KERN_DEBUG "cannot set CPU clock rate\n"); + + + /*Change the DDR freq to 200MHz*/ + tclk = clk_get(NULL, "ddr_hf_clk"); + clk_set_rate(tclk, clk_round_rate(tclk, 200000000)); + lp_audio_mode = 0; + +} + +static ssize_t lp_curr_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (lp_video_mode) + return sprintf(buf, "in lp_video_mode\n"); + else if (lp_audio_mode) + return sprintf(buf, "in lp_audio_mode\n"); + else + return sprintf(buf, "in normal mode\n"); +} + +static ssize_t set_lp_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + printk(KERN_DEBUG "In set_lp_mode() \n"); + + if (strstr(buf, "enable_lp_video") != NULL) { + if (!lp_video_mode) + enter_lp_video_mode(); + } else if (strstr(buf, "disable_lp_video") != NULL) { + if (lp_video_mode) + exit_lp_video_mode(); + } else if (strstr(buf, "enable_lp_audio") != NULL) { + if (!lp_audio_mode) + enter_lp_audio_mode(); + } else if (strstr(buf, "disable_lp_audio") != NULL) { + if (lp_audio_mode) + exit_lp_audio_mode(); + } + return size; +} + +static DEVICE_ATTR(lp_modes, 0644, lp_curr_mode, set_lp_mode); + +/*! + * This is the probe routine for the lp_mode driver. + * + * @param pdev The platform device structure + * + * @return The function returns 0 on success + * + */ +static int __devinit mx51_lpmode_probe(struct platform_device *pdev) +{ + u32 res = 0; + lpmode_dev = &pdev->dev; + + res = sysfs_create_file(&lpmode_dev->kobj, &dev_attr_lp_modes.attr); + if (res) { + printk(KERN_ERR + "lpmode_dev: Unable to register sysdev entry for lpmode_dev"); + return res; + } + + if (res != 0) { + printk(KERN_ERR "lpmode_dev: Unable to start"); + return res; + } + gp_core = regulator_get(NULL, "SW1"); + lp_video_mode = 0; + lp_audio_mode = 0; + + return 0; +} + +static struct platform_driver mx51_lpmode_driver = { + .driver = { + .name = "mx51_lpmode", + }, + .probe = mx51_lpmode_probe, +}; + +/*! + * Initialise the mx51_lpmode_driver. + * + * @return The function always returns 0. + */ + +static int __init lpmode_init(void) +{ + if (platform_driver_register(&mx51_lpmode_driver) != 0) { + printk(KERN_ERR "mx37_lpmode_driver register failed\n"); + return -ENODEV; + } + + printk(KERN_INFO "LPMode driver module loaded\n"); + return 0; +} + +static void __exit lpmode_cleanup(void) +{ + sysfs_remove_file(&lpmode_dev->kobj, &dev_attr_lp_modes.attr); + + /* Unregister the device structure */ + platform_driver_unregister(&mx51_lpmode_driver); +} + +module_init(lpmode_init); +module_exit(lpmode_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("LPMode driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-mx51/mm.c b/arch/arm/mach-mx51/mm.c new file mode 100644 index 000000000000..cf3bc459d58b --- /dev/null +++ b/arch/arm/mach-mx51/mm.c @@ -0,0 +1,84 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/mm.h> +#include <linux/init.h> +#include <mach/hardware.h> +#include <asm/pgtable.h> +#include <asm/mach/map.h> + +/*! + * @file mach-mx51/mm.c + * + * @brief This file creates static mapping between physical to virtual memory. + * + * @ingroup Memory_MX51 + */ + +/*! + * This structure defines the MX51 memory map. + */ +static struct map_desc mx51_io_desc[] __initdata = { + { + .virtual = IRAM_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(IRAM_BASE_ADDR), + .length = IRAM_SIZE, + .type = MT_DEVICE}, + { + .virtual = DEBUG_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(DEBUG_BASE_ADDR), + .length = DEBUG_SIZE, + .type = MT_DEVICE}, + { + .virtual = TZIC_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(TZIC_BASE_ADDR), + .length = TZIC_SIZE, + .type = MT_DEVICE}, + { + .virtual = AIPS1_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(AIPS1_BASE_ADDR), + .length = AIPS1_SIZE, + .type = MT_DEVICE}, + { + .virtual = SPBA0_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(SPBA0_BASE_ADDR), + .length = SPBA0_SIZE, + .type = MT_DEVICE}, + { + .virtual = AIPS2_BASE_ADDR_VIRT, + .pfn = __phys_to_pfn(AIPS2_BASE_ADDR), + .length = AIPS2_SIZE, + .type = MT_DEVICE}, + { + .virtual = NFC_BASE_ADDR_AXI_VIRT, + .pfn = __phys_to_pfn(NFC_BASE_ADDR_AXI), + .length = NFC_AXI_SIZE, + .type = MT_DEVICE}, +}; + +/*! + * This function initializes the memory map. It is called during the + * system startup to create static physical to virtual memory map for + * the IO modules. + */ +void __init mx51_map_io(void) +{ + u32 tzic_addr; + if (cpu_is_mx51_rev(CHIP_REV_2_0) < 0) + tzic_addr = 0x8FFFC000; + else + tzic_addr = 0xE0003000; + + mx51_io_desc[2].pfn = __phys_to_pfn(tzic_addr); + iotable_init(mx51_io_desc, ARRAY_SIZE(mx51_io_desc)); +} diff --git a/arch/arm/mach-mx51/mx51_3stack.c b/arch/arm/mach-mx51/mx51_3stack.c new file mode 100644 index 000000000000..a2fdabc7965a --- /dev/null +++ b/arch/arm/mach-mx51/mx51_3stack.c @@ -0,0 +1,1300 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <asm/mach/keypad.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/nodemask.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/ata.h> +#include <linux/pmic_external.h> +#include <linux/ipu.h> +#if defined(CONFIG_MTD) || defined(CONFIG_MTD_MODULE) +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> + +#include <asm/mach/flash.h> +#endif + +#include <linux/regulator/consumer.h> +#include <mach/hardware.h> +#include <mach/spba.h> +#include <asm/irq.h> +#include <asm/setup.h> +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <asm/mach/time.h> +#include <mach/common.h> +#include <mach/memory.h> +#include <mach/gpio.h> +#include <mach/mmc.h> + +#include "board-mx51_3stack.h" +#include "iomux.h" +#include "crm_regs.h" + +/*! + * @file mach-mx51/mx51_3stack.c + * + * @brief This file contains the board specific initialization routines. + * + * @ingroup MSL_MX51 + */ +extern void __init mx51_3stack_io_init(void); +extern struct cpu_wp *(*get_cpu_wp)(int *wp); +extern void (*set_num_cpu_wp)(int num); +static int num_cpu_wp = 3; + +/* working point(wp): 0 - 800MHz; 1 - 166.25MHz; */ +static struct cpu_wp cpu_wp_auto[] = { + { + .pll_rate = 1000000000, + .cpu_rate = 1000000000, + .pdf = 0, + .mfi = 10, + .mfd = 11, + .mfn = 5, + .cpu_podf = 0, + .cpu_voltage = 1175000,}, + { + .pll_rate = 800000000, + .cpu_rate = 800000000, + .pdf = 0, + .mfi = 8, + .mfd = 2, + .mfn = 1, + .cpu_podf = 0, + .cpu_voltage = 1100000,}, + { + .pll_rate = 800000000, + .cpu_rate = 166250000, + .pdf = 4, + .mfi = 8, + .mfd = 2, + .mfn = 1, + .cpu_podf = 4, + .cpu_voltage = 1000000,}, +}; + +struct cpu_wp *mx51_3stack_get_cpu_wp(int *wp) +{ + *wp = num_cpu_wp; + return cpu_wp_auto; +} + +void mx51_3stack_set_num_cpu_wp(int num) +{ + num_cpu_wp = num; + return; +} + +static void mxc_nop_release(struct device *dev) +{ + /* Nothing */ +} + +#if defined(CONFIG_KEYBOARD_MXC) || defined(CONFIG_KEYBOARD_MXC_MODULE) +static u16 keymapping[24] = { + KEY_1, KEY_2, KEY_3, KEY_F1, KEY_UP, KEY_F2, + KEY_4, KEY_5, KEY_6, KEY_LEFT, KEY_SELECT, KEY_RIGHT, + KEY_7, KEY_8, KEY_9, KEY_F3, KEY_DOWN, KEY_F4, + KEY_0, KEY_OK, KEY_ESC, KEY_ENTER, KEY_MENU, KEY_BACK, +}; + +static struct resource mxc_kpp_resources[] = { + [0] = { + .start = MXC_INT_KPP, + .end = MXC_INT_KPP, + .flags = IORESOURCE_IRQ, + } +}; + +static struct keypad_data keypad_plat_data = { + .rowmax = 4, + .colmax = 6, + .irq = MXC_INT_KPP, + .learning = 0, + .delay = 2, + .matrix = keymapping, +}; + +/* mxc keypad driver */ +static struct platform_device mxc_keypad_device = { + .name = "mxc_keypad", + .id = 0, + .num_resources = ARRAY_SIZE(mxc_kpp_resources), + .resource = mxc_kpp_resources, + .dev = { + .release = mxc_nop_release, + .platform_data = &keypad_plat_data, + }, +}; + +static void mxc_init_keypad(void) +{ + (void)platform_device_register(&mxc_keypad_device); +} +#else +static inline void mxc_init_keypad(void) +{ +} +#endif + +/* NAND Flash Partitions */ +#ifdef CONFIG_MTD_PARTITIONS + +static struct mtd_partition nand_flash_partitions[] = { + { + .name = "bootloader", + .offset = 0, + .size = 3 * 1024 * 1024}, + { + .name = "nand.kernel", + .offset = MTDPART_OFS_APPEND, + .size = 5 * 1024 * 1024}, + { + .name = "nand.rootfs", + .offset = MTDPART_OFS_APPEND, + .size = 256 * 1024 * 1024}, + { + .name = "nand.userfs1", + .offset = MTDPART_OFS_APPEND, + .size = 256 * 1024 * 1024}, + { + .name = "nand.userfs2", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL}, +}; + +#endif + +/* MTD NAND flash */ +#if defined(CONFIG_MTD_NAND_MXC) \ + || defined(CONFIG_MTD_NAND_MXC_MODULE) \ + || defined(CONFIG_MTD_NAND_MXC_V2) \ + || defined(CONFIG_MTD_NAND_MXC_V2_MODULE) \ + || defined(CONFIG_MTD_NAND_MXC_V3) \ + || defined(CONFIG_MTD_NAND_MXC_V3_MODULE) + +extern void gpio_nand_active(void); +extern void gpio_nand_inactive(void); + +static int nand_init(void) +{ + /* Configure the pins */ + gpio_nand_active(); + return 0; +} + +static void nand_exit(void) +{ + /* Free the pins */ + gpio_nand_inactive(); +} + +static struct flash_platform_data mxc_nand_data = { + #ifdef CONFIG_MTD_PARTITIONS + .parts = nand_flash_partitions, + .nr_parts = ARRAY_SIZE(nand_flash_partitions), + #endif + .width = 1, + .init = nand_init, + .exit = nand_exit, +}; + +static struct platform_device mxc_nandv2_mtd_device = { + .name = "mxc_nandv2_flash", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_nand_data, + }, +}; + +static void mxc_init_nand_mtd(void) +{ + (void)platform_device_register(&mxc_nandv2_mtd_device); +} +#else +static inline void mxc_init_nand_mtd(void) +{ +} +#endif + +/* i.MX MTD NAND Flash Controller */ + +#if defined(CONFIG_MTD_NAND_IMX_NFC) || defined(CONFIG_MTD_NAND_IMX_NFC_MODULE) + +/* Resources for this device. */ + +static struct resource imx_nfc_resources[] = { + { + .flags = IORESOURCE_MEM, + .start = NFC_BASE_ADDR_AXI + 0x0000, + .end = NFC_BASE_ADDR_AXI + 0x1200 - 1, + .name = IMX_NFC_BUFFERS_ADDR_RES_NAME, + }, + { + .flags = IORESOURCE_MEM, + .start = NFC_BASE_ADDR_AXI + 0x1E00, + .end = NFC_BASE_ADDR_AXI + 0x1E44 - 1, + .name = IMX_NFC_PRIMARY_REGS_ADDR_RES_NAME, + }, + { + .flags = IORESOURCE_MEM, + .start = NFC_BASE_ADDR + 0x00, + .end = NFC_BASE_ADDR + 0x34 - 1, + .name = IMX_NFC_SECONDARY_REGS_ADDR_RES_NAME, + }, + { + .flags = IORESOURCE_IRQ, + .start = MXC_INT_NFC, + .end = MXC_INT_NFC, + .name = IMX_NFC_INTERRUPT_RES_NAME, + }, +}; + +/* + * Platform-specific information about this device. Some of the details depend + * on the SoC. See imx_init_nfc() below for code that fills in the rest. + */ + +static struct imx_nfc_platform_data imx_nfc_platform_data = { + .nfc_major_version = 3, + .nfc_minor_version = 2, + .force_ce = false, + .target_cycle_in_ns = 30, + .clock_name = "nfc_clk", + .set_page_size = 0, + .interleave = false, + #ifdef CONFIG_MTD_PARTITIONS + .partitions = nand_flash_partitions, + .partition_count = ARRAY_SIZE(nand_flash_partitions), + #endif +}; + +/* The structure that represents the NFC device. */ + +static struct platform_device imx_nfc_device = { + .name = IMX_NFC_DRIVER_NAME, + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &imx_nfc_platform_data, + }, + .resource = imx_nfc_resources, + .num_resources = ARRAY_SIZE(imx_nfc_resources), +}; + +/** + * imx_init_nfc() - Sets up the NFC for this platform. + * + * This function sets up data structures representing the NFC device on this + * platform and registers the device with the platform management system. + */ + +static void imx_nfc_init(void) +{ + (void)platform_device_register(&imx_nfc_device); +} + +#else + +static inline void imx_nfc_init(void) +{ +} + +#endif /* i.MX MTD NAND Flash Controller */ + +#if defined(CONFIG_FB_MXC_SYNC_PANEL) || \ + defined(CONFIG_FB_MXC_SYNC_PANEL_MODULE) + +static struct mxc_fb_platform_data fb_data[] = { + { + .interface_pix_fmt = IPU_PIX_FMT_RGB666, + }, + { + .interface_pix_fmt = IPU_PIX_FMT_YUV444, + }, +}; + +static struct platform_device mxc_fb_device[] = { + { + .name = "mxc_sdc_fb", + .id = 0, + .dev = { + .release = mxc_nop_release, + .coherent_dma_mask = 0xFFFFFFFF, + .platform_data = &fb_data[0], + }, + }, + { + .name = "mxc_sdc_fb", + .id = 1, + .dev = { + .release = mxc_nop_release, + .coherent_dma_mask = 0xFFFFFFFF, + .platform_data = &fb_data[1], + }, + }, + { + .name = "mxc_sdc_fb", + .id = 2, + .dev = { + .release = mxc_nop_release, + .coherent_dma_mask = 0xFFFFFFFF, + }, + }, +}; + +static void lcd_reset_to2(void) +{ + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_DI1_D1_CS), 0); + ipu_reset_disp_panel(); + + return; +} + +static void lcd_reset(void) +{ + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_RS), 0); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_RS), "ser_rs"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_RS), 0); + /* do reset */ + msleep(10); /* tRES >= 100us */ + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_RS), 1); + msleep(60); +} + +static struct mxc_lcd_platform_data lcd_data = { + .core_reg = "VIOHI", + .io_reg = "SW4", + .reset = lcd_reset, +}; + +static struct platform_device mxc_lcd_device = { + .name = "lcd_spi", + .dev = { + .release = mxc_nop_release, + .platform_data = &lcd_data, + }, +}; + +static void wvga_reset(void) +{ + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_DI1_D1_CS), 1); +} + +static struct mxc_lcd_platform_data lcd_wvga_data = { + .reset = wvga_reset, +}; + +static struct platform_device lcd_wvga_device = { + .name = "lcd_claa", + .dev = { + .release = mxc_nop_release, + .platform_data = &lcd_wvga_data, + }, +}; + +static void mxc_init_fb(void) +{ + + if (cpu_is_mx51_rev(CHIP_REV_2_0) > 0) + lcd_data.reset = lcd_reset_to2; + + (void)platform_device_register(&mxc_lcd_device); + (void)platform_device_register(&lcd_wvga_device); + + (void)platform_device_register(&mxc_fb_device[0]); + (void)platform_device_register(&mxc_fb_device[1]); + (void)platform_device_register(&mxc_fb_device[2]); +} +#else +static inline void mxc_init_fb(void) +{ +} +#endif + +static struct platform_device mxcbl_device = { + .name = "mxc_mc13892_bl", +}; + +static inline void mxc_init_bl(void) +{ + platform_device_register(&mxcbl_device); +} + +void si4702_reset(void) +{ + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_DTACK), 0); + msleep(100); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_DTACK), 1); + msleep(100); +} + +void si4702_clock_ctl(int flag) +{ +} + +static void si4702_gpio_get(void) +{ + /* reset pin */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_DTACK), "eim_dtack"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_DTACK), 0); +} + +static void si4702_gpio_put(void) +{ +} + +static struct mxc_fm_platform_data si4702_data = { + .reg_vio = "SW4", + .reg_vdd = "VIOHI", + .gpio_get = si4702_gpio_get, + .gpio_put = si4702_gpio_put, + .reset = si4702_reset, + .clock_ctl = si4702_clock_ctl, + .sksnr = 0, + .skcnt = 0, + .band = 0, + .space = 100, + .seekth = 0xa, +}; + +#if defined(CONFIG_I2C_MXC) || defined(CONFIG_I2C_MXC_MODULE) + +#ifdef CONFIG_I2C_MXC_SELECT1 +static struct i2c_board_info mxc_i2c0_board_info[] __initdata = { +}; +#endif +#ifdef CONFIG_I2C_MXC_SELECT2 +static struct i2c_board_info mxc_i2c1_board_info[] __initdata = { + { + .type = "wm8903-i2c", + .addr = 0x1a, + }, + { + .type = "sgtl5000-i2c", + .addr = 0x0a, + }, + { + .type = "tsc2007", + .addr = 0x48, + .irq = IOMUX_TO_IRQ(MX51_PIN_GPIO1_5), + }, + { + .type = "si4702", + .addr = 0x10, + .platform_data = (void *)&si4702_data, + }, +}; +#endif +#if defined(CONFIG_I2C_MXC_HS) || defined(CONFIG_I2C_MXC_HS_MODULE) +static struct mxc_camera_platform_data camera_data = { + .io_regulator = "SW4", + .analog_regulator = "VIOHI", + .mclk = 24000000, + .csi = 0, +}; +static struct mxc_lightsensor_platform_data ls_data = { + .vdd_reg = NULL, + .rext = 100, +}; + +static struct i2c_board_info mxc_i2c_hs_board_info[] __initdata = { + { + .type = "ov3640", + .addr = 0x3C, + .platform_data = (void *)&camera_data, + }, + { + .type = "isl29003", + .addr = 0x44, + .platform_data = &ls_data, + }, +}; +#endif + +#endif + +static u32 cpld_base_addr; + +/*lan9217 device*/ +#if defined(CONFIG_SMSC911X) || defined(CONFIG_SMSC911X_MODULE) +static struct resource smsc911x_resources[] = { + { + .flags = IORESOURCE_MEM, + }, + { + .start = LAN9217_IRQ, + .end = LAN9217_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; +static struct platform_device smsc_lan9217_device = { + .name = "smsc911x", + .id = 0, + .dev = { + .release = mxc_nop_release, + }, + .num_resources = ARRAY_SIZE(smsc911x_resources), + .resource = smsc911x_resources, +}; +static void mxc_init_enet(void) +{ + if (cpld_base_addr) { + smsc_lan9217_device.resource[0].start = + LAN9217_BASE_ADDR(cpld_base_addr); + smsc_lan9217_device.resource[0].end = + LAN9217_BASE_ADDR(cpld_base_addr) + 0x100; + (void)platform_device_register(&smsc_lan9217_device); + } +} +#else +static inline void mxc_init_enet(void) +{ +} +#endif + +#if defined(CONFIG_IMX_SIM) || defined(CONFIG_IMX_SIM_MODULE) +/* Used to configure the SIM bus */ +static struct mxc_sim_platform_data sim_data = { + .clk_rate = 4000000, + .clock_sim = "sim_clk", + .power_sim = NULL, + .init = NULL, + .exit = NULL, + .detect = 0, +}; + +/*! + * Resource definition for the SIM + */ +static struct resource mxc_sim_resources[] = { + [0] = { + .start = SIM_BASE_ADDR, + .end = SIM_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_SIM_IPB, + .end = MXC_INT_SIM_IPB, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = MXC_INT_SIM_DAT, + .end = MXC_INT_SIM_DAT, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Device Definition for IMX SIM */ +static struct platform_device mxc_sim_device = { + .name = "mxc_sim", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &sim_data, + }, + .num_resources = ARRAY_SIZE(mxc_sim_resources), + .resource = mxc_sim_resources, +}; + +static inline void mxc_init_sim(void) +{ + (void)platform_device_register(&mxc_sim_device); +} +#else +static inline void mxc_init_sim(void) +{ +} +#endif + +#if defined(CONFIG_MMC_IMX_ESDHCI) || defined(CONFIG_MMC_IMX_ESDHCI_MODULE) +/*! + * Get WP pin value to detect write protection + */ +int sdhc_write_protect(struct device *dev) +{ + unsigned short rc = 0; + + if (to_platform_device(dev)->id == 0) + rc = gpio_get_value(IOMUX_TO_GPIO(MX51_PIN_GPIO1_1)); + else + rc = 0; + return rc; +} + +/* + * Probe for the card. If present the GPIO data would be set. + */ +unsigned int sdhc_get_card_det_status(struct device *dev) +{ + int ret; + + if (to_platform_device(dev)->id == 0) { + ret = gpio_get_value(IOMUX_TO_GPIO(MX51_PIN_GPIO1_0)); + return ret; + } else { /* config the det pin for SDHC2 */ + return 0; + } +} + +static struct mxc_mmc_platform_data mmc1_data = { + .ocr_mask = MMC_VDD_32_33, + .caps = MMC_CAP_4_BIT_DATA, + .min_clk = 150000, + .max_clk = 52000000, + .card_inserted_state = 0, + .status = sdhc_get_card_det_status, + .wp_status = sdhc_write_protect, + .clock_mmc = "esdhc_clk", + .power_mmc = NULL, +}; +static struct mxc_mmc_platform_data mmc2_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30 | + MMC_VDD_31_32, + .caps = MMC_CAP_4_BIT_DATA, + .min_clk = 150000, + .max_clk = 50000000, + .card_inserted_state = 0, + .status = sdhc_get_card_det_status, + .wp_status = sdhc_write_protect, + .clock_mmc = "esdhc_clk", +}; + +/*! + * Resource definition for the SDHC1 + */ +static struct resource mxcsdhc1_resources[] = { + [0] = { + .start = MMC_SDHC1_BASE_ADDR, + .end = MMC_SDHC1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_MMC_SDHC1, + .end = MXC_INT_MMC_SDHC1, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = IOMUX_TO_IRQ(MX51_PIN_GPIO1_0), + .end = IOMUX_TO_IRQ(MX51_PIN_GPIO1_0), + .flags = IORESOURCE_IRQ, + }, +}; + +/*! + * Resource definition for the SDHC2 + */ +static struct resource mxcsdhc2_resources[] = { + [0] = { + .start = MMC_SDHC2_BASE_ADDR, + .end = MMC_SDHC2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_MMC_SDHC2, + .end = MXC_INT_MMC_SDHC2, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Device Definition for MXC SDHC1 */ +static struct platform_device mxcsdhc1_device = { + .name = "mxsdhci", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mmc1_data, + }, + .num_resources = ARRAY_SIZE(mxcsdhc1_resources), + .resource = mxcsdhc1_resources, +}; + +/*! Device Definition for MXC SDHC2 */ +static struct platform_device mxcsdhc2_device = { + .name = "mxsdhci", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mmc2_data, + }, + .num_resources = ARRAY_SIZE(mxcsdhc2_resources), + .resource = mxcsdhc2_resources, +}; + +static inline void mxc_init_mmc(void) +{ + (void)platform_device_register(&mxcsdhc1_device); + (void)platform_device_register(&mxcsdhc2_device); +} +#else +static inline void mxc_init_mmc(void) +{ +} +#endif + +static u32 brd_io; +static void expio_ack_irq(u32 irq); + +static void mxc_expio_irq_handler(u32 irq, struct irq_desc *desc) +{ + u32 imr_val; + u32 int_valid; + u32 expio_irq; + + desc->chip->mask(irq); /* irq = gpio irq number */ + + imr_val = __raw_readw(brd_io + INTR_MASK_REG); + int_valid = __raw_readw(brd_io + INTR_STATUS_REG) & ~imr_val; + + if (unlikely(!int_valid)) + goto out; + + expio_irq = MXC_BOARD_IRQ_START; + for (; int_valid != 0; int_valid >>= 1, expio_irq++) { + struct irq_desc *d; + if ((int_valid & 1) == 0) + continue; + d = irq_desc + expio_irq; + if (unlikely(!(d->handle_irq))) { + printk(KERN_ERR "\nEXPIO irq: %d unhandled\n", + expio_irq); + BUG(); /* oops */ + } + d->handle_irq(expio_irq, d); + } + + out: + desc->chip->ack(irq); + desc->chip->unmask(irq); +} + +/* + * Disable an expio pin's interrupt by setting the bit in the imr. + * @param irq an expio virtual irq number + */ +static void expio_mask_irq(u32 irq) +{ + u16 reg; + u32 expio = MXC_IRQ_TO_EXPIO(irq); + /* mask the interrupt */ + reg = __raw_readw(brd_io + INTR_MASK_REG); + reg |= (1 << expio); + __raw_writew(reg, brd_io + INTR_MASK_REG); +} + +/* + * Acknowledge an expanded io pin's interrupt by clearing the bit in the isr. + * @param irq an expanded io virtual irq number + */ +static void expio_ack_irq(u32 irq) +{ + u32 expio = MXC_IRQ_TO_EXPIO(irq); + /* clear the interrupt status */ + __raw_writew(1 << expio, brd_io + INTR_RESET_REG); + __raw_writew(0, brd_io + INTR_RESET_REG); + /* mask the interrupt */ + expio_mask_irq(irq); +} + +/* + * Enable a expio pin's interrupt by clearing the bit in the imr. + * @param irq a expio virtual irq number + */ +static void expio_unmask_irq(u32 irq) +{ + u16 reg; + u32 expio = MXC_IRQ_TO_EXPIO(irq); + /* unmask the interrupt */ + reg = __raw_readw(brd_io + INTR_MASK_REG); + reg &= ~(1 << expio); + __raw_writew(reg, brd_io + INTR_MASK_REG); +} + +static struct irq_chip expio_irq_chip = { + .ack = expio_ack_irq, + .mask = expio_mask_irq, + .unmask = expio_unmask_irq, +}; + +static int __init mxc_expio_init(void) +{ + int i; + + brd_io = (u32) ioremap(BOARD_IO_ADDR(CS5_BASE_ADDR), SZ_4K); + if (brd_io == 0) + return -ENOMEM; + + if ((__raw_readw(brd_io + MAGIC_NUMBER1_REG) != 0xAAAA) || + (__raw_readw(brd_io + MAGIC_NUMBER2_REG) != 0x5555) || + (__raw_readw(brd_io + MAGIC_NUMBER3_REG) != 0xCAFE)) { + pr_info("3-Stack Debug board not detected \n"); + cpld_base_addr = 0; + return -ENODEV; + } else { + cpld_base_addr = CS5_BASE_ADDR; + } + + pr_info("3-Stack Debug board detected, rev = 0x%04X\n", + readw(brd_io + CPLD_CODE_VER_REG)); + + /* + * Configure INT line as GPIO input + */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_GPIO1_6), "gpio1_6"); + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_GPIO1_6)); + + /* disable the interrupt and clear the status */ + __raw_writew(0, brd_io + INTR_MASK_REG); + __raw_writew(0xFFFF, brd_io + INTR_RESET_REG); + __raw_writew(0, brd_io + INTR_RESET_REG); + __raw_writew(0x1F, brd_io + INTR_MASK_REG); + for (i = MXC_BOARD_IRQ_START; i < (MXC_BOARD_IRQ_START + MXC_BOARD_IRQS); + i++) { + set_irq_chip(i, &expio_irq_chip); + set_irq_handler(i, handle_level_irq); + set_irq_flags(i, IRQF_VALID); + } + set_irq_type(EXPIO_PARENT_INT, IRQF_TRIGGER_LOW); + set_irq_chained_handler(EXPIO_PARENT_INT, mxc_expio_irq_handler); + + return 0; +} + +#if defined(CONFIG_PATA_FSL) || defined(CONFIG_PATA_FSL_MODULE) +extern void gpio_ata_active(void); +extern void gpio_ata_inactive(void); + +static int ata_init(struct platform_device *pdev) +{ + /* Configure the pins */ + gpio_ata_active(); + return 0; +} + +static void ata_exit(void) +{ + /* Free the pins */ + gpio_ata_inactive(); +} + +static struct fsl_ata_platform_data ata_data = { + .udma_mask = ATA_UDMA3, + .mwdma_mask = ATA_MWDMA2, + .pio_mask = ATA_PIO4, + .fifo_alarm = MXC_IDE_DMA_WATERMARK / 2, + .max_sg = MXC_IDE_DMA_BD_NR, + .init = ata_init, + .exit = ata_exit, + .core_reg = NULL, + .io_reg = NULL, +}; + +static struct resource pata_fsl_resources[] = { + [0] = { + .start = ATA_BASE_ADDR, + .end = ATA_BASE_ADDR + 0x000000C8, + .flags = IORESOURCE_MEM,}, + [2] = { + .start = MXC_INT_ATA, + .end = MXC_INT_ATA, + .flags = IORESOURCE_IRQ,}, +}; + +static struct platform_device pata_fsl_device = { + .name = "pata_fsl", + .id = -1, + .num_resources = ARRAY_SIZE(pata_fsl_resources), + .resource = pata_fsl_resources, + .dev = { + .platform_data = &ata_data, + .coherent_dma_mask = ~0,}, +}; + +static void __init mxc_init_pata(void) +{ + (void)platform_device_register(&pata_fsl_device); +} +#else /* CONFIG_PATA_FSL */ +static void __init mxc_init_pata(void) +{ +} +#endif /* CONFIG_PATA_FSL */ + +#if defined(CONFIG_TOUCHSCREEN_TSC2007) \ + || defined(CONFIG_TOUCHSCREEN_TSC2007_MODULE) + +static int __init mxc_init_touchscreen(void) +{ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_GPIO1_5), "gpio1_5"); + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_GPIO1_5)); + + return 0; +} +#else +static int __init mxc_init_touchscreen(void) +{ + return 0; +} +#endif + +static int __init mxc_init_srpgconfig(void) +{ + struct clk *gpcclk = clk_get(NULL, "gpc_dvfs_clk"); + clk_enable(gpcclk); + + /* Setup the number of clock cycles to wait for SRPG + * power up and power down requests. + */ + __raw_writel(0x010F0201, MXC_SRPG_ARM_PUPSCR); + __raw_writel(0x010F0201, MXC_SRPG_NEON_PUPSCR); + __raw_writel(0x00000008, MXC_SRPG_EMPGC0_PUPSCR); + __raw_writel(0x00000008, MXC_SRPG_EMPGC1_PUPSCR); + + __raw_writel(0x01010101, MXC_SRPG_ARM_PDNSCR); + __raw_writel(0x01010101, MXC_SRPG_NEON_PDNSCR); + __raw_writel(0x00000018, MXC_SRPG_EMPGC0_PDNSCR); + __raw_writel(0x00000018, MXC_SRPG_EMPGC1_PDNSCR); + + clk_disable(gpcclk); + clk_put(gpcclk); + + return 0; +} + +#if defined(CONFIG_SND_SOC_IMX_3STACK_WM8903) \ + || defined(CONFIG_SND_SOC_IMX_3STACK_WM8903_MODULE) +static struct mxc_audio_platform_data wm8903_data; + +static struct platform_device mxc_wm8903_device = { + .name = "imx-3stack-wm8903", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &wm8903_data, + }, +}; + +static void __init mxc_init_wm8903(void) +{ + wm8903_data.ssi_clk[0] = clk_get(NULL, "ssi_clk.0"); + clk_put(wm8903_data.ssi_clk[0]); + + wm8903_data.ssi_clk[1] = clk_get(NULL, "ssi_clk.1"); + clk_put(wm8903_data.ssi_clk[1]); + + wm8903_data.ssi_num = 1; + wm8903_data.src_port = 2; + wm8903_data.ext_port = 3; + + (void)platform_device_register(&mxc_wm8903_device); +} +#else +static void __init mxc_init_wm8903(void) +{ +} +#endif + +#if defined(CONFIG_SND_SOC_IMX_3STACK_SGTL5000) \ + || defined(CONFIG_SND_SOC_IMX_3STACK_SGTL5000_MODULE) +static int mxc_sgtl5000_plat_init(void); +static int mxc_sgtl5000_plat_finit(void); +static int mxc_sgtl5000_amp_enable(int enable); + +int headphone_det_status(void) +{ + return gpio_get_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A26)); +} + +static struct mxc_audio_platform_data sgtl5000_data = { + .ssi_num = 1, + .src_port = 2, + .ext_port = 3, + .hp_irq = IOMUX_TO_IRQ(MX51_PIN_EIM_A26), + .hp_status = headphone_det_status, + .amp_enable = mxc_sgtl5000_amp_enable, + .vddio = 1800000, + .vdda = 1800000, + .vddd = 1200000, + .sysclk = 12000000, + .init = mxc_sgtl5000_plat_init, + .finit = mxc_sgtl5000_plat_finit, +}; + +static struct platform_device mxc_sgtl5000_device = { + .name = "imx-3stack-sgtl5000", + .dev = { + .release = mxc_nop_release, + .platform_data = &sgtl5000_data, + }, +}; + +static int mxc_sgtl5000_plat_init(void) +{ + struct regulator *reg; + reg = regulator_get(&mxc_sgtl5000_device.dev, "GPO2"); + if (IS_ERR(reg)) + return -EINVAL; + sgtl5000_data.priv = reg; + return 0; +} + +static int mxc_sgtl5000_plat_finit(void) +{ + struct regulator *reg; + reg = sgtl5000_data.priv; + if (reg) { + regulator_put(reg); + sgtl5000_data.priv = NULL; + } + return 0; +} + +static int mxc_sgtl5000_amp_enable(int enable) +{ + struct regulator *reg; + reg = sgtl5000_data.priv; + + if (!reg) + return -EINVAL; + if (enable) + regulator_enable(reg); + else + regulator_disable(reg); + return 0; +} + +static void mxc_init_sgtl5000(void) +{ + platform_device_register(&mxc_sgtl5000_device); +} +#else +static inline void mxc_init_sgtl5000(void) +{ +} +#endif + +static void bt_reset(void) +{ + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_D19), 1); +} + +static struct mxc_bt_platform_data mxc_bt_data = { + .bt_vdd = NULL, + .bt_vdd_parent = NULL, + .bt_vusb = "SW4", + .bt_vusb_parent = NULL, + .bt_reset = bt_reset, +}; + +static struct platform_device mxc_bt_device = { + .name = "mxc_bt", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_bt_data, + }, +}; + +static void mxc_init_bluetooth(void) +{ + (void)platform_device_register(&mxc_bt_device); +} + +static void mxc_unifi_hardreset(int pin_level) +{ + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_D19), pin_level & 0x01); +} + +static struct mxc_unifi_platform_data unifi_data = { + .hardreset = mxc_unifi_hardreset, + .reg_vdd_vpa = "VSD", + .reg_1v5_dd = "VGEN1", + .host_id = 1, +}; + +struct mxc_unifi_platform_data *get_unifi_plat_data(void) +{ + return &unifi_data; +} + +EXPORT_SYMBOL(get_unifi_plat_data); + +/*! + * Board specific fixup function. It is called by \b setup_arch() in + * setup.c file very early on during kernel starts. It allows the user to + * statically fill in the proper values for the passed-in parameters. None of + * the parameters is used currently. + * + * @param desc pointer to \b struct \b machine_desc + * @param tags pointer to \b struct \b tag + * @param cmdline pointer to the command line + * @param mi pointer to \b struct \b meminfo + */ +static void __init fixup_mxc_board(struct machine_desc *desc, struct tag *tags, + char **cmdline, struct meminfo *mi) +{ + mxc_cpu_init(); + + get_cpu_wp = mx51_3stack_get_cpu_wp; + set_num_cpu_wp = mx51_3stack_set_num_cpu_wp; +#ifdef CONFIG_DISCONTIGMEM + do { + int nid; + mi->nr_banks = MXC_NUMNODES; + for (nid = 0; nid < mi->nr_banks; nid++) + SET_NODE(mi, nid); + + } while (0); +#endif +} + +#if defined(CONFIG_GPS_IOCTRL) || defined(CONFIG_GPS_IOCTRL_MODULE) +static struct mxc_gps_platform_data gps_data = { + .core_reg = "VIOHI", + .analog_reg = "SW4", +}; + +static struct platform_device mxc_gps_device = { + .name = "gps_ioctrl", + .id = -1, + .dev = { + .platform_data = &gps_data, + }, +}; + +static void __init mxc_init_gps(void) +{ + (void)platform_device_register(&mxc_gps_device); +} + +int gpio_gps_access(int para) +{ + iomux_pin_name_t pin; + pin = (para & 0x1) ? MX51_PIN_EIM_CS2 : MX51_PIN_EIM_CRE; + + if (para & 0x4) /* Read GPIO */ + return gpio_get_value(IOMUX_TO_GPIO(pin)); + else if (para & 0x2) /* Write GPIO */ + gpio_set_value(IOMUX_TO_GPIO(pin), 1); + else + gpio_set_value(IOMUX_TO_GPIO(pin), 0); + return 0; +} +EXPORT_SYMBOL(gpio_gps_access); +#else +static void __init mxc_init_gps(void) +{ +} +#endif + +/*! + * Board specific initialization. + */ +static void __init mxc_board_init(void) +{ + int err; + + mxc_cpu_common_init(); + mxc_register_gpios(); + mx51_3stack_io_init(); + early_console_setup(saved_command_line); + mxc_init_devices(); + + mxc_expio_init(); + mxc_init_enet(); + mxc_init_pata(); + mxc_init_fb(); + mxc_init_bl(); + mxc_init_keypad(); + mxc_init_nand_mtd(); + imx_nfc_init(); + mxc_init_mmc(); + mxc_init_sim(); + mxc_init_srpgconfig(); + mx51_3stack_init_mc13892(); + +#if defined(CONFIG_I2C_MXC) || defined(CONFIG_I2C_MXC_MODULE) + +#ifdef CONFIG_I2C_MXC_SELECT1 + i2c_register_board_info(0, mxc_i2c0_board_info, + ARRAY_SIZE(mxc_i2c0_board_info)); +#endif +#ifdef CONFIG_I2C_MXC_SELECT2 + i2c_register_board_info(1, mxc_i2c1_board_info, + ARRAY_SIZE(mxc_i2c1_board_info)); +#endif +#if defined(CONFIG_I2C_MXC_HS) || defined(CONFIG_I2C_MXC_HS_MODULE) + i2c_register_board_info(3, mxc_i2c_hs_board_info, + ARRAY_SIZE(mxc_i2c_hs_board_info)); +#endif + +#endif + mxc_init_touchscreen(); + mxc_init_wm8903(); + mxc_init_sgtl5000(); + mxc_init_bluetooth(); + mxc_init_gps(); + + err = mxc_request_iomux(MX51_PIN_EIM_D19, IOMUX_CONFIG_GPIO); + if (err) + printk(KERN_ERR "Error: bt reset request gpio failed!\n"); + else { + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_D19), "eim_d19"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_D19), 0); + } +} + +static void __init mx51_3stack_timer_init(void) +{ + mx51_clocks_init(32768, 24000000, 22579200, 24576000); +} + +static struct sys_timer mxc_timer = { + .init = mx51_3stack_timer_init, +}; + +/* + * The following uses standard kernel macros define in arch.h in order to + * initialize __mach_desc_MX51_3STACK data structure. + */ +/* *INDENT-OFF* */ +MACHINE_START(MX51_3DS, "Freescale MX51 3-Stack Board") + /* Maintainer: Freescale Semiconductor, Inc. */ + .phys_io = AIPS1_BASE_ADDR, + .io_pg_offst = ((AIPS1_BASE_ADDR_VIRT) >> 18) & 0xfffc, + .boot_params = PHYS_OFFSET + 0x100, + .fixup = fixup_mxc_board, + .map_io = mx51_map_io, + .init_irq = mxc_init_irq, + .init_machine = mxc_board_init, + .timer = &mxc_timer, +MACHINE_END diff --git a/arch/arm/mach-mx51/mx51_3stack_gpio.c b/arch/arm/mach-mx51/mx51_3stack_gpio.c new file mode 100644 index 000000000000..dd08add0fa5b --- /dev/null +++ b/arch/arm/mach-mx51/mx51_3stack_gpio.c @@ -0,0 +1,851 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <mach/hardware.h> +#include <mach/clock.h> +#include <mach/gpio.h> + +#include "iomux.h" + +/*! + * @file mach-mx51/mx51_3stack_gpio.c + * + * @brief This file contains all the GPIO setup functions for the board. + * + * @ingroup GPIO + */ +#define ATA_PAD_CONFIG (PAD_CTL_DRV_HIGH | PAD_CTL_DRV_VOT_HIGH) + +static struct mxc_iomux_pin_cfg __initdata mxc_iomux_pins[] = { + /* CSI0 */ + { + MX51_PIN_CSI1_D8, IOMUX_CONFIG_ALT3, + PAD_CTL_PKE_ENABLE, + MUX_IN_GPIO3_IPP_IND_G_IN_12_SELECT_INPUT, + INPUT_CTL_PATH1, + }, + { + MX51_PIN_CSI1_D9, IOMUX_CONFIG_ALT3, + PAD_CTL_PKE_ENABLE, + }, + { + MX51_PIN_CSI1_D10, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D11, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D12, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D13, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D14, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D15, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D16, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D17, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D18, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D19, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_VSYNC, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_SRE_SLOW), + }, + { + MX51_PIN_CSI1_HSYNC, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_SRE_SLOW), + }, + { + MX51_PIN_EIM_A26, IOMUX_CONFIG_ALT5 | IOMUX_CONFIG_SION, + (PAD_CTL_SRE_SLOW | PAD_CTL_DRV_MEDIUM | PAD_CTL_100K_PU | + PAD_CTL_HYS_ENABLE | PAD_CTL_DRV_VOT_HIGH), + MUX_IN_HSC_MIPI_MIX_IPP_IND_SENS2_DATA_EN_SELECT_INPUT, + INPUT_CTL_PATH0, + }, + { /* SPI1 */ + MX51_PIN_CSPI1_MISO, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_CSPI1_MOSI, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_CSPI1_RDY, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_CSPI1_SCLK, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_OWIRE_LINE, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | + PAD_CTL_ODE_OPENDRAIN_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP2_DAT15, IOMUX_CONFIG_ALT5, + }, + { + MX51_PIN_DI_GP2, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_DI_GP3, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_KEY_COL0, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_KEY_COL1, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_KEY_COL2, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_KEY_COL3, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_KEY_COL4, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_KEY_COL5, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_KEY_ROW0, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_KEY_ROW1, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_KEY_ROW2, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_KEY_ROW3, IOMUX_CONFIG_ALT0, + }, + { /* AUD3_TXD */ + MX51_PIN_AUD3_BB_TXD, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | + PAD_CTL_ODE_OPENDRAIN_NONE | PAD_CTL_100K_PU | + PAD_CTL_HYS_NONE | PAD_CTL_DDR_INPUT_CMOS | PAD_CTL_DRV_VOT_LOW), + }, + { /* AUD3_RXD */ + MX51_PIN_AUD3_BB_RXD, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | + PAD_CTL_ODE_OPENDRAIN_NONE | PAD_CTL_100K_PU | + PAD_CTL_HYS_NONE | PAD_CTL_DDR_INPUT_CMOS | PAD_CTL_DRV_VOT_LOW), + }, + { /* AUD3_CLK */ + MX51_PIN_AUD3_BB_CK, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | + PAD_CTL_ODE_OPENDRAIN_NONE | PAD_CTL_100K_PU | + PAD_CTL_HYS_NONE | PAD_CTL_DDR_INPUT_CMOS | PAD_CTL_DRV_VOT_LOW), + }, + { /* AUD3_FS */ + MX51_PIN_AUD3_BB_FS, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | + PAD_CTL_ODE_OPENDRAIN_NONE | PAD_CTL_100K_PU | + PAD_CTL_HYS_NONE | PAD_CTL_DDR_INPUT_CMOS | PAD_CTL_DRV_VOT_LOW), + }, + { + MX51_PIN_EIM_D16, IOMUX_CONFIG_ALT1, + (PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_EIM_A27, IOMUX_CONFIG_ALT2, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { + MX51_PIN_EIM_EB2, IOMUX_CONFIG_ALT1, + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | PAD_CTL_100K_PD, + }, + { + MX51_PIN_EIM_DTACK, IOMUX_CONFIG_GPIO, + (PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PU), + }, + { + MX51_PIN_EIM_CS2, IOMUX_CONFIG_GPIO, + (PAD_CTL_DRV_HIGH | PAD_CTL_HYS_NONE | PAD_CTL_PUE_KEEPER | + PAD_CTL_100K_PU | PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_EIM_CRE, IOMUX_CONFIG_GPIO, + (PAD_CTL_DRV_HIGH | PAD_CTL_HYS_NONE | PAD_CTL_PUE_KEEPER | + PAD_CTL_100K_PU | PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DI_GP4, IOMUX_CONFIG_ALT2, + }, + { + MX51_PIN_DISPB2_SER_DIN, IOMUX_CONFIG_GPIO, + 0, + MUX_IN_GPIO3_IPP_IND_G_IN_5_SELECT_INPUT, + INPUT_CTL_PATH1, + }, + { + MX51_PIN_DISPB2_SER_RS, IOMUX_CONFIG_GPIO, + }, + { + MX51_PIN_DISPB2_SER_DIO, IOMUX_CONFIG_GPIO, + }, + { /* TO2 */ + MX51_PIN_DI1_D1_CS, IOMUX_CONFIG_ALT4, + }, + { /* TO2 */ + MX51_PIN_DI1_D0_CS, IOMUX_CONFIG_ALT1, + }, + { /* TO2 */ + MX51_PIN_DI1_PIN11, IOMUX_CONFIG_ALT1, + }, + { /* TO2 */ + MX51_PIN_DI1_PIN12, IOMUX_CONFIG_ALT1, + }, + { /* TO2 */ + MX51_PIN_DI1_PIN13, IOMUX_CONFIG_ALT1, + }, +#ifdef CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL + { + MX51_PIN_DISP1_DAT0, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT1, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT2, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT3, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT4, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT5, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT6, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT7, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT8, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT9, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT10, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT11, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT12, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT13, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT14, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT15, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT16, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT17, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT18, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT19, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT20, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT21, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT22, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_DISP1_DAT23, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_DRV_LOW | PAD_CTL_SRE_FAST), + }, +#endif + { + MX51_PIN_I2C1_CLK, IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION, + 0x1E4, + }, + { + MX51_PIN_I2C1_DAT, IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION, + 0x1E4, + }, + { + MX51_PIN_GPIO1_6, IOMUX_CONFIG_GPIO, + }, + { + MX51_PIN_GPIO1_7, IOMUX_CONFIG_ALT2, + (PAD_CTL_DRV_HIGH | PAD_CTL_PUE_PULL | + PAD_CTL_100K_PU | PAD_CTL_PKE_ENABLE | + PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_GPIO1_2, IOMUX_CONFIG_ALT2 | IOMUX_CONFIG_SION, + (PAD_CTL_SRE_FAST | PAD_CTL_ODE_OPENDRAIN_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_100K_PU | PAD_CTL_HYS_ENABLE), + MUX_IN_I2C2_IPP_SCL_IN_SELECT_INPUT, INPUT_CTL_PATH3, + }, + { + MX51_PIN_GPIO1_3, IOMUX_CONFIG_ALT2 | IOMUX_CONFIG_SION, + (PAD_CTL_SRE_FAST | PAD_CTL_ODE_OPENDRAIN_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_100K_PU | PAD_CTL_HYS_ENABLE), + MUX_IN_I2C2_IPP_SDA_IN_SELECT_INPUT, INPUT_CTL_PATH3, + }, + { + MX51_PIN_USBH1_STP, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_PUE_KEEPER | + PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_CLK */ + MX51_PIN_USBH1_CLK, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_PUE_KEEPER | + PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE | PAD_CTL_DDR_INPUT_CMOS), + }, + { /* USBH1_DIR */ + MX51_PIN_USBH1_DIR, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_PUE_KEEPER | + PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE | PAD_CTL_DDR_INPUT_CMOS), + }, + { /* USBH1_NXT */ + MX51_PIN_USBH1_NXT, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_PUE_KEEPER | + PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE | PAD_CTL_DDR_INPUT_CMOS), + }, + { /* USBH1_DATA0 */ + MX51_PIN_USBH1_DATA0, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_DATA1 */ + MX51_PIN_USBH1_DATA1, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_DATA2 */ + MX51_PIN_USBH1_DATA2, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_DATA3 */ + MX51_PIN_USBH1_DATA3, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_DATA4 */ + MX51_PIN_USBH1_DATA4, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_DATA5 */ + MX51_PIN_USBH1_DATA5, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_DATA6 */ + MX51_PIN_USBH1_DATA6, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_DATA7 */ + MX51_PIN_USBH1_DATA7, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USB1_OC */ + MX51_PIN_GPIO1_9, IOMUX_CONFIG_ALT1, + (PAD_CTL_SRE_SLOW | PAD_CTL_DRV_LOW | PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | + PAD_CTL_HYS_ENABLE), + }, + { /* USB1_PWR */ + MX51_PIN_GPIO1_8, IOMUX_CONFIG_ALT1, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_NONE | PAD_CTL_HYS_ENABLE), + }, + { + MX51_PIN_SD1_CMD, IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD1_CLK, IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD1_DATA0, IOMUX_CONFIG_ALT0, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD1_DATA1, IOMUX_CONFIG_ALT0, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD1_DATA2, IOMUX_CONFIG_ALT0, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD1_DATA3, IOMUX_CONFIG_ALT0, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_GPIO1_0, IOMUX_CONFIG_GPIO | IOMUX_CONFIG_SION, + (PAD_CTL_HYS_ENABLE | PAD_CTL_100K_PU), + }, + { + MX51_PIN_GPIO1_1, IOMUX_CONFIG_GPIO | IOMUX_CONFIG_SION, + (PAD_CTL_HYS_ENABLE | PAD_CTL_100K_PU), + }, + { + MX51_PIN_SD2_CMD, IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD2_CLK, IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD2_DATA0, IOMUX_CONFIG_ALT0, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD2_DATA1, IOMUX_CONFIG_ALT0, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD2_DATA2, IOMUX_CONFIG_ALT0, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD2_DATA3, IOMUX_CONFIG_ALT0, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_UART1_RXD, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + MUX_IN_UART1_IPP_UART_RXD_MUX_SELECT_INPUT, + INPUT_CTL_PATH0, + }, + { + MX51_PIN_UART1_TXD, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_UART1_RTS, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH), + MUX_IN_UART1_IPP_UART_RTS_B_SELECT_INPUT, + INPUT_CTL_PATH0, + }, + { + MX51_PIN_UART1_CTS, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH), + }, + { + MX51_PIN_UART2_RXD, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + MUX_IN_UART2_IPP_UART_RXD_MUX_SELECT_INPUT, + INPUT_CTL_PATH2, + }, + { + MX51_PIN_UART2_TXD, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_EIM_D26, IOMUX_CONFIG_ALT4, + (PAD_CTL_HYS_NONE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_KEEPER | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + MUX_IN_UART2_IPP_UART_RTS_B_SELECT_INPUT, + INPUT_CTL_PATH3, + }, + { + MX51_PIN_EIM_D25, IOMUX_CONFIG_ALT4, + (PAD_CTL_HYS_NONE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_KEEPER | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_UART3_RXD, IOMUX_CONFIG_ALT1, + (PAD_CTL_HYS_NONE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_KEEPER | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + MUX_IN_UART3_IPP_UART_RXD_MUX_SELECT_INPUT, + INPUT_CTL_PATH4, + }, + { + MX51_PIN_UART3_TXD, IOMUX_CONFIG_ALT1, + (PAD_CTL_HYS_NONE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_KEEPER | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_EIM_D27, IOMUX_CONFIG_ALT3, + (PAD_CTL_HYS_NONE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_KEEPER | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + MUX_IN_UART3_IPP_UART_RTS_B_SELECT_INPUT, + INPUT_CTL_PATH3, + }, + { + MX51_PIN_EIM_D24, IOMUX_CONFIG_ALT3, + (PAD_CTL_HYS_NONE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_KEEPER | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + }, +}; + +static struct mxc_iomux_pin_cfg __initdata ata_iomux_pins[] = { + { + MX51_PIN_NANDF_ALE, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_CS2, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_CS3, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_CS4, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_CS5, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_CS6, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_RE_B, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_WE_B, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_CLE, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_RB0, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_WP_B, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + /* TO 2.0 */ + { + MX51_PIN_GPIO_NAND, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + /* TO 1.0 */ + { + MX51_PIN_NANDF_RB5, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_RB1, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D0, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D1, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D2, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D3, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D4, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D5, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D6, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D7, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D8, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D9, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D10, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D11, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D12, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D13, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D14, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, + { + MX51_PIN_NANDF_D15, IOMUX_CONFIG_ALT1, + ATA_PAD_CONFIG, + }, +}; + +static struct mxc_iomux_pin_cfg __initdata nand_iomux_pins[] = { + { + MX51_PIN_NANDF_CS0, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_NANDF_CS1, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_NANDF_CS2, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_NANDF_CS3, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_NANDF_CS4, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_NANDF_CS5, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_NANDF_CS6, IOMUX_CONFIG_ALT0, + }, + { + MX51_PIN_NANDF_CS7, IOMUX_CONFIG_ALT0, + }, + /* TO2 */ + { + MX51_PIN_GPIO_NAND, IOMUX_CONFIG_ALT0, + }, + /* TO1 */ + { + MX51_PIN_NANDF_RB5, IOMUX_CONFIG_ALT0, + }, +}; + +static struct mxc_iomux_pin_cfg __initdata sim_iomux_pins[] = { + { + MX51_PIN_NANDF_CS4, IOMUX_CONFIG_ALT6, + PAD_CTL_DRV_HIGH | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_47K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_ODE_OPENDRAIN_NONE | PAD_CTL_PKE_ENABLE, + }, + { + MX51_PIN_NANDF_CS5, IOMUX_CONFIG_ALT6, + PAD_CTL_DRV_HIGH | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_47K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_ODE_OPENDRAIN_NONE | PAD_CTL_PKE_ENABLE, + }, + { + MX51_PIN_NANDF_CS6, IOMUX_CONFIG_ALT6, + PAD_CTL_DRV_HIGH | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_100K_PD | + PAD_CTL_PUE_PULL | PAD_CTL_ODE_OPENDRAIN_NONE | PAD_CTL_PKE_ENABLE, + }, + { + MX51_PIN_NANDF_CS7, IOMUX_CONFIG_ALT6, + PAD_CTL_DRV_HIGH | PAD_CTL_DRV_VOT_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_22K_PU | + PAD_CTL_PUE_PULL | PAD_CTL_ODE_OPENDRAIN_NONE | PAD_CTL_PKE_ENABLE, + }, +}; + +static int __initdata enable_ata = { 0 }; +static int __init ata_setup(char *__unused) +{ + enable_ata = 1; + return 1; +} + +__setup("ata", ata_setup); + +static int __initdata enable_sim = { 0 }; +static int __init sim_setup(char *__unused) +{ + enable_sim = 1; + return 1; +} + +__setup("sim", sim_setup); + +void __init mx51_3stack_io_init(void) +{ + int i, num; + struct mxc_iomux_pin_cfg *pin_ptr; + + for (i = 0; i < ARRAY_SIZE(mxc_iomux_pins); i++) { + mxc_request_iomux(mxc_iomux_pins[i].pin, + mxc_iomux_pins[i].mux_mode); + if (mxc_iomux_pins[i].pad_cfg) + mxc_iomux_set_pad(mxc_iomux_pins[i].pin, + mxc_iomux_pins[i].pad_cfg); + if (mxc_iomux_pins[i].in_select) + mxc_iomux_set_input(mxc_iomux_pins[i].in_select, + mxc_iomux_pins[i].in_mode); + } + + if (enable_ata) { + pin_ptr = ata_iomux_pins; + num = ARRAY_SIZE(ata_iomux_pins); + } else if (enable_sim) { + pin_ptr = sim_iomux_pins; + num = ARRAY_SIZE(sim_iomux_pins); + } else { + pin_ptr = nand_iomux_pins; + num = ARRAY_SIZE(nand_iomux_pins); + } + + for (i = 0; i < num; i++) { + mxc_request_iomux(pin_ptr[i].pin, pin_ptr[i].mux_mode); + if (pin_ptr[i].pad_cfg) + mxc_iomux_set_pad(pin_ptr[i].pin, pin_ptr[i].pad_cfg); + if (pin_ptr[i].in_select) + mxc_iomux_set_input(pin_ptr[i].in_select, + pin_ptr[i].in_mode); + } + + /* TO3 doesn't need pad to drive CSI_DATA_EN[0] high */ + if (cpu_is_mx51_rev(CHIP_REV_3_0) > 0) + mxc_request_iomux(MX51_PIN_EIM_A26, IOMUX_CONFIG_ALT0); + + /* Camera low power */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_CSI1_D8), "csi1_d8"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_CSI1_D8), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_CSI1_D8), 0); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_EB2), "eim_eb2"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_EB2), 0); /* TO1 */ + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_EB2), 0); /* TO1 */ + + /* Camera reset */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_CSI1_D9), "csi1_d9"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_CSI1_D9), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_CSI1_D9), 1); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_DI1_D1_CS), "di1_d1_cs"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_DI1_D1_CS), 0); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_GPIO1_0), "gpio1_0"); + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_GPIO1_0)); /* SD1 CD */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_GPIO1_1), "gpio1_1"); + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_GPIO1_1)); /* SD1 WP */ + + /* EIM_D16 */ + /* osc_en is shared by SPDIF */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_D16), "eim_d16"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_D16), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_D16), 1); + + /* LCD related gpio */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_DI1_D1_CS), "di1_d1_cs"); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_DIO), "dispb2_ser_di0"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_DI1_D1_CS), 0); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_DIO), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_DIO), 0); + + /* GPS related gpio */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_CS2), "eim_cs2"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_CS2), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_CS2), 0); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_CRE), "eim_cre"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_CRE), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_CRE), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_CRE), 1); +} diff --git a/arch/arm/mach-mx51/mx51_3stack_pmic_mc13892.c b/arch/arm/mach-mx51/mx51_3stack_pmic_mc13892.c new file mode 100644 index 000000000000..3cefe8caccaf --- /dev/null +++ b/arch/arm/mach-mx51/mx51_3stack_pmic_mc13892.c @@ -0,0 +1,352 @@ +/* + * mx51-3stack-pmic-mc13892.c -- i.MX51 3STACK Driver for Atlas MC13892 PMIC + */ + /* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + + /* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/pmic_external.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/mc13892/core.h> +#include <mach/irqs.h> +#include "iomux.h" + +/* + * Convenience conversion. + * Here atm, maybe there is somewhere better for this. + */ +#define mV_to_uV(mV) (mV * 1000) +#define uV_to_mV(uV) (uV / 1000) +#define V_to_uV(V) (mV_to_uV(V * 1000)) +#define uV_to_V(uV) (uV_to_mV(uV) / 1000) + +#define STANDBYSECINV_LSH 11 +#define STANDBYSECINV_WID 1 + +/* Coin cell charger enable */ +#define CIONCHEN_LSH 23 +#define CIONCHEN_WID 1 +/* Coin cell charger voltage setting */ +#define VCOIN_LSH 20 +#define VCOIN_WID 3 + +/* Coin Charger voltage */ +#define VCOIN_2_5V 0x0 +#define VCOIN_2_7V 0x1 +#define VCOIN_2_8V 0x2 +#define VCOIN_2_9V 0x3 +#define VCOIN_3_0V 0x4 +#define VCOIN_3_1V 0x5 +#define VCOIN_3_2V 0x6 +#define VCOIN_3_3V 0x7 + +/* Keeps VSRTC and CLK32KMCU on for all states */ +#define DRM_LSH 4 +#define DRM_WID 1 + +/* CPU */ +static struct regulator_consumer_supply sw1_consumers[] = { + { + .supply = "cpu_vcc", + } +}; + +struct mc13892; + +static struct regulator_init_data sw1_init = { + .constraints = { + .name = "SW1", + .min_uV = mV_to_uV(600), + .max_uV = mV_to_uV(1375), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .valid_modes_mask = 0, + .always_on = 1, + .boot_on = 1, + .initial_state = PM_SUSPEND_MEM, + .state_mem = { + .uV = 700000, + .mode = REGULATOR_MODE_NORMAL, + .enabled = 1, + }, + }, + .num_consumer_supplies = ARRAY_SIZE(sw1_consumers), + .consumer_supplies = sw1_consumers, +}; + +static struct regulator_init_data sw2_init = { + .constraints = { + .name = "SW2", + .min_uV = mV_to_uV(900), + .max_uV = mV_to_uV(1850), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .always_on = 1, + .boot_on = 1, + .initial_state = PM_SUSPEND_MEM, + .state_mem = { + .uV = 1200000, + .mode = REGULATOR_MODE_NORMAL, + .enabled = 1, + }, + } +}; + +static struct regulator_init_data sw3_init = { + .constraints = { + .name = "SW3", + .min_uV = mV_to_uV(1100), + .max_uV = mV_to_uV(1850), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .always_on = 1, + .boot_on = 1, + } +}; + +static struct regulator_init_data sw4_init = { + .constraints = { + .name = "SW4", + .min_uV = mV_to_uV(1100), + .max_uV = mV_to_uV(1850), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .always_on = 1, + .boot_on = 1, + } +}; + +static struct regulator_init_data viohi_init = { + .constraints = { + .name = "VIOHI", + .boot_on = 1, + } +}; + +static struct regulator_init_data vusb_init = { + .constraints = { + .name = "VUSB", + .boot_on = 1, + } +}; + +static struct regulator_init_data swbst_init = { + .constraints = { + .name = "SWBST", + } +}; + +static struct regulator_init_data vdig_init = { + .constraints = { + .name = "VDIG", + .min_uV = mV_to_uV(1050), + .max_uV = mV_to_uV(1800), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vpll_init = { + .constraints = { + .name = "VPLL", + .min_uV = mV_to_uV(1050), + .max_uV = mV_to_uV(1800), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vusb2_init = { + .constraints = { + .name = "VUSB2", + .min_uV = mV_to_uV(2400), + .max_uV = mV_to_uV(2775), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vvideo_init = { + .constraints = { + .name = "VVIDEO", + .min_uV = mV_to_uV(2500), + .max_uV = mV_to_uV(2775), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vaudio_init = { + .constraints = { + .name = "VAUDIO", + .min_uV = mV_to_uV(2300), + .max_uV = mV_to_uV(3000), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vsd_init = { + .constraints = { + .name = "VSD", + .min_uV = mV_to_uV(1800), + .max_uV = mV_to_uV(3150), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vcam_init = { + .constraints = { + .name = "VCAM", + .min_uV = mV_to_uV(2500), + .max_uV = mV_to_uV(3000), + .valid_ops_mask = + REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_FAST | REGULATOR_MODE_NORMAL, + } +}; + +static struct regulator_init_data vgen1_init = { + .constraints = { + .name = "VGEN1", + .min_uV = mV_to_uV(1200), + .max_uV = mV_to_uV(3150), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vgen2_init = { + .constraints = { + .name = "VGEN2", + .min_uV = mV_to_uV(1200), + .max_uV = mV_to_uV(3150), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vgen3_init = { + .constraints = { + .name = "VGEN3", + .min_uV = mV_to_uV(1800), + .max_uV = mV_to_uV(2900), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data gpo1_init = { + .constraints = { + .name = "GPO1", + } +}; + +static struct regulator_init_data gpo2_init = { + .constraints = { + .name = "GPO2", + } +}; + +static struct regulator_init_data gpo3_init = { + .constraints = { + .name = "GPO3", + } +}; + +static struct regulator_init_data gpo4_init = { + .constraints = { + .name = "GPO4", + } +}; + +/*! + * the event handler for power on event + */ +static void power_on_evt_handler(void) +{ + pr_info("pwr on event1 is received \n"); +} + +static int mc13892_regulator_init(struct mc13892 *mc13892) +{ + unsigned int value; + pmic_event_callback_t power_key_event; + int register_mask; + + printk("Initializing regulators for 3-stack.\n"); + if (mxc_cpu_is_rev(CHIP_REV_2_0) < 0) + sw2_init.constraints.state_mem.uV = 1100000; + else if (mxc_cpu_is_rev(CHIP_REV_2_0) >= 1) { + sw2_init.constraints.state_mem.uV = 1250000; + sw1_init.constraints.state_mem.uV = 1000000; + } + + /* subscribe PWRON1 event to enable ON_OFF key */ + power_key_event.param = NULL; + power_key_event.func = (void *)power_on_evt_handler; + pmic_event_subscribe(EVENT_PWRONI, power_key_event); + + /* Bit 4 DRM: keep VSRTC and CLK32KMCU on for all states */ +#if defined(CONFIG_RTC_DRV_MXC_V2) || defined(CONFIG_RTC_DRV_MXC_V2_MODULE) + value = BITFVAL(DRM, 1); + register_mask = BITFMASK(DRM); + pmic_write_reg(REG_POWER_CTL0, value, register_mask); +#endif + /* Set the STANDBYSECINV bit, so that STANDBY pin is + * interpreted as active low. + */ + value = BITFVAL(STANDBYSECINV, 1); + register_mask = BITFMASK(STANDBYSECINV); + pmic_write_reg(REG_POWER_CTL2, value, register_mask); + + /* Enable coin cell charger */ + value = BITFVAL(CIONCHEN, 1) | BITFVAL(VCOIN, VCOIN_3_0V); + register_mask = BITFMASK(CIONCHEN) | BITFMASK(VCOIN); + pmic_write_reg(REG_POWER_CTL0, value, register_mask); + + mc13892_register_regulator(mc13892, MC13892_SW1, &sw1_init); + mc13892_register_regulator(mc13892, MC13892_SW2, &sw2_init); + mc13892_register_regulator(mc13892, MC13892_SW3, &sw3_init); + mc13892_register_regulator(mc13892, MC13892_SW4, &sw4_init); + mc13892_register_regulator(mc13892, MC13892_SWBST, &swbst_init); + mc13892_register_regulator(mc13892, MC13892_VIOHI, &viohi_init); + mc13892_register_regulator(mc13892, MC13892_VPLL, &vpll_init); + mc13892_register_regulator(mc13892, MC13892_VDIG, &vdig_init); + mc13892_register_regulator(mc13892, MC13892_VSD, &vsd_init); + mc13892_register_regulator(mc13892, MC13892_VUSB2, &vusb2_init); + mc13892_register_regulator(mc13892, MC13892_VVIDEO, &vvideo_init); + mc13892_register_regulator(mc13892, MC13892_VAUDIO, &vaudio_init); + mc13892_register_regulator(mc13892, MC13892_VCAM, &vcam_init); + mc13892_register_regulator(mc13892, MC13892_VGEN1, &vgen1_init); + mc13892_register_regulator(mc13892, MC13892_VGEN2, &vgen2_init); + mc13892_register_regulator(mc13892, MC13892_VGEN3, &vgen3_init); + mc13892_register_regulator(mc13892, MC13892_VUSB, &vusb_init); + mc13892_register_regulator(mc13892, MC13892_GPO1, &gpo1_init); + mc13892_register_regulator(mc13892, MC13892_GPO2, &gpo2_init); + mc13892_register_regulator(mc13892, MC13892_GPO3, &gpo3_init); + mc13892_register_regulator(mc13892, MC13892_GPO4, &gpo4_init); + + return 0; +} + +static struct mc13892_platform_data mc13892_plat = { + .init = mc13892_regulator_init, +}; + +static struct i2c_board_info __initdata mc13892_i2c_device = { + I2C_BOARD_INFO("mc13892", 0x08), + .irq = IOMUX_TO_IRQ(MX51_PIN_GPIO1_5), + .platform_data = &mc13892_plat, +}; + +int __init mx51_3stack_init_mc13892(void) +{ + return i2c_register_board_info(1, &mc13892_i2c_device, 1); +} diff --git a/arch/arm/mach-mx51/mx51_babbage.c b/arch/arm/mach-mx51/mx51_babbage.c new file mode 100644 index 000000000000..c8310cd7700d --- /dev/null +++ b/arch/arm/mach-mx51/mx51_babbage.c @@ -0,0 +1,1089 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/nodemask.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/ata.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> +#include <linux/spi/flash.h> +#include <linux/regulator/consumer.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <mach/common.h> +#include <mach/hardware.h> +#include <mach/spba.h> +#include <asm/irq.h> +#include <asm/setup.h> +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <asm/mach/time.h> +#include <asm/mach/keypad.h> +#include <mach/memory.h> +#include <mach/gpio.h> +#include <mach/mmc.h> +#include "board-mx51_babbage.h" +#include "iomux.h" +#include "crm_regs.h" +#include <mach/mxc_edid.h> + +/*! + * @file mach-mx51/mx51_babbage.c + * + * @brief This file contains the board specific initialization routines. + * + * @ingroup MSL_MX51 + */ +extern void __init mx51_babbage_io_init(void); +extern struct cpu_wp *(*get_cpu_wp)(int *wp); +extern void (*set_num_cpu_wp)(int num); +static int num_cpu_wp = 3; + +/* working point(wp): 0 - 800MHz; 1 - 166.25MHz; */ +static struct cpu_wp cpu_wp_auto[] = { + { + .pll_rate = 1000000000, + .cpu_rate = 1000000000, + .pdf = 0, + .mfi = 10, + .mfd = 11, + .mfn = 5, + .cpu_podf = 0, + .cpu_voltage = 1175000,}, + { + .pll_rate = 800000000, + .cpu_rate = 800000000, + .pdf = 0, + .mfi = 8, + .mfd = 2, + .mfn = 1, + .cpu_podf = 0, + .cpu_voltage = 1100000,}, + { + .pll_rate = 800000000, + .cpu_rate = 166250000, + .pdf = 4, + .mfi = 8, + .mfd = 2, + .mfn = 1, + .cpu_podf = 4, + .cpu_voltage = 1000000,}, +}; + +static struct fb_videomode video_modes[] = { + { + /* 720p60 TV output */ + "720P60", 60, 1280, 720, 7418, + 220, 110, + 20, 5, + 40, 5, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT | FB_SYNC_EXT, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* MITSUBISHI LVDS panel */ + "XGA", 60, 1024, 768, 15385, + 220, 40, + 21, 7, + 60, 10, + 0, + FB_VMODE_NONINTERLACED, + 0,}, +}; + +struct cpu_wp *mx51_babbage_get_cpu_wp(int *wp) +{ + *wp = num_cpu_wp; + return cpu_wp_auto; +} + +void mx51_babbage_set_num_cpu_wp(int num) +{ + num_cpu_wp = num; + return; +} +static void mxc_nop_release(struct device *dev) +{ + /* Nothing */ +} + +#if defined(CONFIG_KEYBOARD_MXC) || defined(CONFIG_KEYBOARD_MXC_MODULE) +static u16 keymapping[24] = { + KEY_1, KEY_2, KEY_3, KEY_F1, KEY_UP, KEY_F2, + KEY_4, KEY_5, KEY_6, KEY_LEFT, KEY_SELECT, KEY_RIGHT, + KEY_7, KEY_8, KEY_9, KEY_F3, KEY_DOWN, KEY_F4, + KEY_0, KEY_OK, KEY_ESC, KEY_ENTER, KEY_MENU, KEY_BACK, +}; + +static struct resource mxc_kpp_resources[] = { + [0] = { + .start = MXC_INT_KPP, + .end = MXC_INT_KPP, + .flags = IORESOURCE_IRQ, + } +}; + +static struct keypad_data keypad_plat_data = { + .rowmax = 4, + .colmax = 6, + .irq = MXC_INT_KPP, + .learning = 0, + .delay = 2, + .matrix = keymapping, +}; + +/* mxc keypad driver */ +static struct platform_device mxc_keypad_device = { + .name = "mxc_keypad", + .id = 0, + .num_resources = ARRAY_SIZE(mxc_kpp_resources), + .resource = mxc_kpp_resources, + .dev = { + .release = mxc_nop_release, + .platform_data = &keypad_plat_data, + }, +}; + +static void mxc_init_keypad(void) +{ + (void)platform_device_register(&mxc_keypad_device); +} +#else +static inline void mxc_init_keypad(void) +{ +} +#endif + +#if defined(CONFIG_FB_MXC_SYNC_PANEL) || \ + defined(CONFIG_FB_MXC_SYNC_PANEL_MODULE) +static struct resource mxcfb_resources[] = { + [0] = { + .flags = IORESOURCE_MEM, + }, +}; + +static struct mxc_fb_platform_data fb_data[] = { + { + .interface_pix_fmt = IPU_PIX_FMT_RGB24, + .mode_str = "1024x768M-16@60", + }, + { + .interface_pix_fmt = IPU_PIX_FMT_RGB565, + .mode_str = "1024x768M-16@60", + }, +}; + +static struct platform_device mxc_fb_device[] = { + { + .name = "mxc_sdc_fb", + .id = 0, + .dev = { + .release = mxc_nop_release, + .coherent_dma_mask = 0xFFFFFFFF, + .platform_data = &fb_data[0], + }, + .num_resources = ARRAY_SIZE(mxcfb_resources), + .resource = mxcfb_resources, + }, + { + .name = "mxc_sdc_fb", + .id = 1, + .dev = { + .release = mxc_nop_release, + .coherent_dma_mask = 0xFFFFFFFF, + .platform_data = &fb_data[1], + }, + }, + { + .name = "mxc_sdc_fb", + .id = 2, + .dev = { + .release = mxc_nop_release, + .coherent_dma_mask = 0xFFFFFFFF, + }, + }, +}; + +static int __initdata enable_vga = { 0 }; +static int __initdata enable_wvga = { 0 }; +static int __initdata enable_tv = { 0 }; +static int __initdata enable_mitsubishi_xga = { 0 }; + +static void wvga_reset(void) +{ + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_DI1_D1_CS), 1); +} + +static struct mxc_lcd_platform_data lcd_wvga_data = { + .reset = wvga_reset, +}; + +static struct platform_device lcd_wvga_device = { + .name = "lcd_claa", + .dev = { + .release = mxc_nop_release, + .platform_data = &lcd_wvga_data, + }, +}; + +static int handle_edid(int *pixclk) +{ +#if 0 + int err = 0; + int dvi = 0; + int fb0 = 0; + int fb1 = 1; + struct fb_var_screeninfo screeninfo; + struct i2c_adapter *adp; + + memset(&screeninfo, 0, sizeof(screeninfo)); + + adp = i2c_get_adapter(1); + + if (cpu_is_mx51_rev(CHIP_REV_3_0) > 0) { + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_CSI2_HSYNC), 1); + msleep(1); + } + err = read_edid(adp, &screeninfo, &dvi); + if (cpu_is_mx51_rev(CHIP_REV_3_0) > 0) + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_CSI2_HSYNC), 0); + + if (!err) { + printk(KERN_INFO " EDID read\n"); + if (!dvi) { + enable_vga = 1; + fb0 = 1; /* fb0 will be VGA */ + fb1 = 0; /* fb1 will be DVI or TV */ + } + + /* Handle TV modes */ + /* This logic is fairly complex yet still doesn't handle all + possibilities. Once a customer knows the platform + configuration, this should be simplified to what is desired. + */ + if (screeninfo.xres == 1920 && screeninfo.yres != 1200) { + /* MX51 can't handle clock speeds for anything larger.*/ + if (!enable_tv) + enable_tv = 1; + if (enable_vga || enable_wvga || enable_tv == 2) + enable_tv = 2; + fb_data[0].mode = &(video_modes[0]); + if (!enable_wvga) + fb_data[1].mode_str = "800x600M-16@60"; + } else if (screeninfo.xres > 1280 && screeninfo.yres > 1024) { + if (!enable_wvga) { + fb_data[fb0].mode_str = "1280x1024M-16@60"; + fb_data[fb1].mode_str = NULL; + } else { + /* WVGA is preset so the DVI can't be > this. */ + fb_data[0].mode_str = "1024x768M-16@60"; + } + } else if (screeninfo.xres > 0 && screeninfo.yres > 0) { + if (!enable_wvga) { + fb_data[fb0].mode = + kzalloc(sizeof(struct fb_videomode), + GFP_KERNEL); + fb_var_to_videomode(fb_data[fb0].mode, + &screeninfo); + fb_data[fb0].mode_str = NULL; + if (screeninfo.xres >= 1280 && + screeninfo.yres > 720) + fb_data[fb1].mode_str = NULL; + else if (screeninfo.xres > 1024 && + screeninfo.yres > 768) + fb_data[fb1].mode_str = + "800x600M-16@60"; + else if (screeninfo.xres > 800 && + screeninfo.yres > 600) + fb_data[fb1].mode_str = + "1024x768M-16@60"; + } else { + /* A WVGA panel was specified and an EDID was + read thus there is a DVI monitor attached. */ + if (screeninfo.xres >= 1024) + fb_data[0].mode_str = "1024x768M-16@60"; + else if (screeninfo.xres >= 800) + fb_data[0].mode_str = "800x600M-16@60"; + else + fb_data[0].mode_str = "640x480M-16@60"; + } + } + } +#endif + return 0; +} + +static int __init mxc_init_fb(void) +{ + int pixclk = 0; + + if (!machine_is_mx51_babbage()) + return 0; + + if (cpu_is_mx51_rev(CHIP_REV_1_1) == 1) { + enable_vga = 1; + fb_data[0].mode_str = NULL; + fb_data[1].mode_str = NULL; + } + + if (enable_wvga) { + fb_data[1].interface_pix_fmt = IPU_PIX_FMT_RGB565; + fb_data[1].mode_str = "800x480M-16@55"; + } + + if (enable_mitsubishi_xga) { + fb_data[0].interface_pix_fmt = IPU_PIX_FMT_LVDS666; + fb_data[0].mode = &(video_modes[1]); + + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_DI1_D0_CS), 0); + msleep(1); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_DI1_D0_CS), 1); + + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_CSI2_D12), 1); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_CSI2_D13), 1); + } + + /* DVI Detect */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_NANDF_D12), "nandf_d12"); + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_NANDF_D12)); + /* DVI Reset - Assert for i2c disabled mode */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_DIN), "dispb2_ser_din"); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_DIN), 0); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_DIN), 0); + /* DVI Power-down */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_DIO), "dispb2_ser_di0"); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_DIO), 1); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_DIO), 0); + + (void)platform_device_register(&lcd_wvga_device); + + if (cpu_is_mx51_rev(CHIP_REV_1_1) == 2) + handle_edid(&pixclk); + + if (enable_vga) + printk(KERN_INFO "VGA monitor is primary\n"); + else if (enable_wvga) + printk(KERN_INFO "WVGA LCD panel is primary\n"); + else if (!enable_tv) + printk(KERN_INFO "DVI monitor is primary\n"); + + if (enable_tv) { + printk(KERN_INFO "TV is specified as %d\n", enable_tv); + if (!fb_data[0].mode) { + fb_data[0].mode = &(video_modes[0]); + if (!enable_wvga) + fb_data[1].mode_str = "800x600M-16@60"; + } + } + + if (enable_tv) { + struct clk *clk, *di_clk; + clk = clk_get(NULL, "pll3"); + di_clk = clk_get(NULL, "ipu_di0_clk"); + clk_disable(clk); + clk_disable(di_clk); + clk_set_rate(clk, 297000000); + clk_set_rate(di_clk, 297000000 / 4); + clk_enable(clk); + clk_enable(di_clk); + clk_put(di_clk); + clk_put(clk); + } + + /* Once a customer knows the platform configuration, + this should be simplified to what is desired. + */ + if (enable_vga || enable_wvga || enable_tv == 2) { + (void)platform_device_register(&mxc_fb_device[1]); /* VGA */ + if (fb_data[0].mode_str || fb_data[0].mode) + (void)platform_device_register(&mxc_fb_device[0]); + } else { + (void)platform_device_register(&mxc_fb_device[0]); /* DVI */ + if (fb_data[1].mode_str || fb_data[1].mode) + (void)platform_device_register(&mxc_fb_device[1]); + } + + (void)platform_device_register(&mxc_fb_device[2]); + + return 0; +} +device_initcall(mxc_init_fb); + +static int __init vga_setup(char *__unused) +{ + enable_vga = 1; + return 1; +} + +__setup("vga", vga_setup); + +static int __init wvga_setup(char *__unused) +{ + enable_wvga = 1; + return 1; +} + +__setup("wvga", wvga_setup); + +static int __init mitsubishi_xga_setup(char *__unused) +{ + enable_mitsubishi_xga = 1; + return 1; +} + +__setup("mitsubishi_xga", mitsubishi_xga_setup); + +static int __init tv_setup(char *s) +{ + enable_tv = 1; + if (strcmp(s, "2") == 0 || strcmp(s, "=2") == 0) + enable_tv = 2; + return 1; +} + +__setup("tv", tv_setup); +#else +static inline void mxc_init_fb(void) +{ +} +#endif + +static void dvi_reset(void) +{ + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_DIN), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_DIN), 0); + msleep(50); + + /* do reset */ + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_DIN), 1); + msleep(20); /* tRES >= 50us */ + + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_DISPB2_SER_DIN), 0); +} + +static struct mxc_lcd_platform_data dvi_data = { + .core_reg = "VGEN1", + .io_reg = "VGEN3", + .reset = dvi_reset, +}; + +static void vga_reset(void) +{ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_A19), "eim_a19"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_A19), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A19), 0); + msleep(50); + /* do reset */ + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A19), 1); + msleep(10); /* tRES >= 50us */ + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A19), 0); +} + +static struct mxc_lcd_platform_data vga_data = { + .core_reg = "VCAM", + .io_reg = "VGEN3", + .analog_reg = "VAUDIO", + .reset = vga_reset, +}; + +static void si4702_reset(void) +{ + return; + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A21), 0); + msleep(100); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A21), 1); + msleep(100); +} + +static void si4702_clock_ctl(int flag) +{ + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A18), flag); + msleep(100); +} + +static void si4702_gpio_get(void) +{ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_A18), "eim_a18"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_A18), 0); +} + +static void si4702_gpio_put(void) +{ +} + +static struct mxc_fm_platform_data si4702_data = { + .reg_vio = "SW4", + .reg_vdd = "VIOHI", + .gpio_get = si4702_gpio_get, + .gpio_put = si4702_gpio_put, + .reset = si4702_reset, + .clock_ctl = si4702_clock_ctl, +}; + +#if defined(CONFIG_I2C_MXC) || defined(CONFIG_I2C_MXC_MODULE) + +#ifdef CONFIG_I2C_MXC_SELECT1 +static struct mxc_camera_platform_data camera_data = { + .io_regulator = "SW4", + .analog_regulator = "VIOHI", + .mclk = 24000000, + .csi = 0, +}; + +static struct i2c_board_info mxc_i2c0_board_info[] __initdata = { + { + .type = "ov3640", + .addr = 0x3C, + .platform_data = (void *)&camera_data, + }, +}; +#endif +#ifdef CONFIG_I2C_MXC_SELECT2 +static struct i2c_board_info mxc_i2c1_board_info[] __initdata = { + { + .type = "sgtl5000-i2c", + .addr = 0x0a, + }, +}; +#endif + +#if defined(CONFIG_I2C_MXC_HS) || defined(CONFIG_I2C_MXC_HS_MODULE) +static struct i2c_board_info mxc_i2c_hs_board_info[] __initdata = { + { + .type = "sii9022", + .addr = 0x39, + .platform_data = &dvi_data, + }, + { + .type = "ch7026", + .addr = 0x75, + .platform_data = &vga_data, + }, + { + .type = "si4702", + .addr = 0x10, + .platform_data = (void *)&si4702_data, + }, +}; +#endif + +#endif + +#if defined(CONFIG_MTD) || defined(CONFIG_MTD_MODULE) +static struct mtd_partition mxc_spi_nor_partitions[] = { + { + .name = "bootloader", + .offset = 0, + .size = 0x00040000,}, + { + .name = "kernel", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL,}, + +}; + +static struct mtd_partition mxc_dataflash_partitions[] = { + { + .name = "bootloader", + .offset = 0, + .size = 0x000100000,}, + { + .name = "kernel", + .offset = MTDPART_OFS_APPEND, + .size = MTDPART_SIZ_FULL,}, +}; + +static struct flash_platform_data mxc_spi_flash_data[] = { + { + .name = "mxc_spi_nor", + .parts = mxc_spi_nor_partitions, + .nr_parts = ARRAY_SIZE(mxc_spi_nor_partitions), + .type = "sst25vf016b",}, + { + .name = "mxc_dataflash", + .parts = mxc_dataflash_partitions, + .nr_parts = ARRAY_SIZE(mxc_dataflash_partitions), + .type = "at45db321d",} +}; +#endif + +static struct spi_board_info mxc_spi_nor_device[] __initdata = { +#if defined(CONFIG_MTD) || defined(CONFIG_MTD_MODULE) + { + .modalias = "mxc_spi_nor", + .max_speed_hz = 25000000, /* max spi clock (SCK) speed in HZ */ + .bus_num = 1, + .chip_select = 1, + .platform_data = &mxc_spi_flash_data[0],}, +#endif +}; + +static struct spi_board_info mxc_dataflash_device[] __initdata = { +#if defined(CONFIG_MTD) || defined(CONFIG_MTD_MODULE) + { + .modalias = "mxc_dataflash", + .max_speed_hz = 25000000, /* max spi clock (SCK) speed in HZ */ + .bus_num = 1, + .chip_select = 1, + .platform_data = &mxc_spi_flash_data[1],}, +#endif +}; + +#if defined(CONFIG_MMC_IMX_ESDHCI) || defined(CONFIG_MMC_IMX_ESDHCI_MODULE) +static int sdhc_write_protect(struct device *dev) +{ + unsigned short rc = 0; + + if (to_platform_device(dev)->id == 0) + rc = gpio_get_value(IOMUX_TO_GPIO(MX51_PIN_GPIO1_1)); + else + rc = gpio_get_value(IOMUX_TO_GPIO(MX51_PIN_GPIO1_5)); + + return rc; +} + +static unsigned int sdhc_get_card_det_status(struct device *dev) +{ + int ret; + + if (to_platform_device(dev)->id == 0) { + ret = gpio_get_value(IOMUX_TO_GPIO(MX51_PIN_GPIO1_0)); + return ret; + } else { /* config the det pin for SDHC2 */ + if (board_is_rev(BOARD_REV_2)) + /* BB2.5 */ + ret = gpio_get_value(IOMUX_TO_GPIO(MX51_PIN_GPIO1_6)); + else + /* BB2.0 */ + ret = gpio_get_value(IOMUX_TO_GPIO(MX51_PIN_GPIO1_4)); + return ret; + } +} + +static struct mxc_mmc_platform_data mmc1_data = { + .ocr_mask = MMC_VDD_31_32, + .caps = MMC_CAP_4_BIT_DATA, + .min_clk = 400000, + .max_clk = 52000000, + .card_inserted_state = 1, + .status = sdhc_get_card_det_status, + .wp_status = sdhc_write_protect, + .clock_mmc = "esdhc_clk", + .power_mmc = NULL, +}; + +static struct mxc_mmc_platform_data mmc2_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30 | + MMC_VDD_31_32, + .caps = MMC_CAP_4_BIT_DATA, + .min_clk = 150000, + .max_clk = 50000000, + .card_inserted_state = 0, + .status = sdhc_get_card_det_status, + .wp_status = sdhc_write_protect, + .clock_mmc = "esdhc_clk", +}; + +/*! + * Resource definition for the SDHC1 + */ +static struct resource mxcsdhc1_resources[] = { + [0] = { + .start = MMC_SDHC1_BASE_ADDR, + .end = MMC_SDHC1_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_MMC_SDHC1, + .end = MXC_INT_MMC_SDHC1, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = IOMUX_TO_IRQ(MX51_PIN_GPIO1_0), + .end = IOMUX_TO_IRQ(MX51_PIN_GPIO1_0), + .flags = IORESOURCE_IRQ, + }, +}; + +/*! + * Resource definition for the SDHC2 + */ +static struct resource mxcsdhc2_resources[] = { + [0] = { + .start = MMC_SDHC2_BASE_ADDR, + .end = MMC_SDHC2_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_MMC_SDHC2, + .end = MXC_INT_MMC_SDHC2, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = IOMUX_TO_IRQ(MX51_PIN_GPIO1_4), + .end = IOMUX_TO_IRQ(MX51_PIN_GPIO1_4), + .flags = IORESOURCE_IRQ, + }, +}; + +/*! Device Definition for MXC SDHC1 */ +static struct platform_device mxcsdhc1_device = { + .name = "mxsdhci", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mmc1_data, + }, + .num_resources = ARRAY_SIZE(mxcsdhc1_resources), + .resource = mxcsdhc1_resources, +}; + +/*! Device Definition for MXC SDHC2 */ +static struct platform_device mxcsdhc2_device = { + .name = "mxsdhci", + .id = 1, + .dev = { + .release = mxc_nop_release, + .platform_data = &mmc2_data, + }, + .num_resources = ARRAY_SIZE(mxcsdhc2_resources), + .resource = mxcsdhc2_resources, +}; + +static inline void mxc_init_mmc(void) +{ + if (board_is_rev(BOARD_REV_2)) { + /* BB2.5 */ + mxcsdhc2_resources[2].start = + IOMUX_TO_IRQ(MX51_PIN_GPIO1_6); /* SD2 CD */ + mxcsdhc2_resources[2].end = + IOMUX_TO_IRQ(MX51_PIN_GPIO1_6); /* SD2 CD */ + } + + (void)platform_device_register(&mxcsdhc1_device); + (void)platform_device_register(&mxcsdhc2_device); +} +#else +static inline void mxc_init_mmc(void) +{ +} +#endif + +#if defined(CONFIG_SND_SOC_IMX_3STACK_SGTL5000) \ + || defined(CONFIG_SND_SOC_IMX_3STACK_SGTL5000_MODULE) +static int mxc_sgtl5000_amp_enable(int enable); + +static int headphone_det_status(void) +{ + if (cpu_is_mx51_rev(CHIP_REV_1_1) == 2) + return (gpio_get_value(IOMUX_TO_GPIO(MX51_PIN_NANDF_D14)) == 0); + + return gpio_get_value(IOMUX_TO_GPIO(MX51_PIN_NANDF_CS0)); +} + +static struct mxc_audio_platform_data sgtl5000_data = { + .ssi_num = 1, + .src_port = 2, + .ext_port = 3, + .hp_irq = IOMUX_TO_IRQ(MX51_PIN_NANDF_CS0), + .hp_status = headphone_det_status, + .vddio_reg = "VVIDEO", + .vdda_reg = "VDIG", + .vddd_reg = "VGEN1", + .amp_enable = mxc_sgtl5000_amp_enable, + .vddio = 2775000, + .vdda = 1650000, + .vddd = 1200000, + .sysclk = 12288000, +}; + +static struct platform_device mxc_sgtl5000_device = { + .name = "imx-3stack-sgtl5000", + .dev = { + .release = mxc_nop_release, + .platform_data = &sgtl5000_data, + }, +}; + +static int mxc_sgtl5000_amp_enable(int enable) +{ + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A23), enable ? 1 : 0); + return 0; +} + +static void mxc_init_sgtl5000(void) +{ + if (cpu_is_mx51_rev(CHIP_REV_1_1) == 2) { + sgtl5000_data.sysclk = 26000000; + sgtl5000_data.vddd_reg = NULL; + sgtl5000_data.vddd = 0; + } + + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_A23), "eim_a23"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_A23), 0); + + platform_device_register(&mxc_sgtl5000_device); +} +#else +static inline void mxc_init_sgtl5000(void) +{ +} +#endif + +#if defined CONFIG_FEC +static struct resource mxc_fec_resources[] = { + { + .start = FEC_BASE_ADDR, + .end = FEC_BASE_ADDR + 0xfff, + .flags = IORESOURCE_MEM + }, { + .start = MXC_INT_FEC, + .end = MXC_INT_FEC, + .flags = IORESOURCE_IRQ + }, +}; + +struct platform_device mxc_fec_device = { + .name = "fec", + .id = 0, + .num_resources = ARRAY_SIZE(mxc_fec_resources), + .resource = mxc_fec_resources, +}; + +static __init int mxc_init_fec(void) +{ + return platform_device_register(&mxc_fec_device); +} +#else +static inline int mxc_init_fec(void) +{ + return 0; +} +#endif + +#if defined(CONFIG_GPIO_BUTTON_MXC) || \ + defined(CONFIG_GPIO_BUTTON_MXC_MODULE) + +#define MXC_BUTTON_GPIO_PIN MX51_PIN_EIM_DTACK + +static struct mxc_gpio_button_data gpio_button_data = { + .name = "Power Button (CM)", + .gpio = MXC_BUTTON_GPIO_PIN, + .irq = IOMUX_TO_IRQ(MXC_BUTTON_GPIO_PIN), + .key = KEY_POWER, +}; + +static struct platform_device gpio_button_device = { + .name = "gpio_button", + .dev = { + .release = mxc_nop_release, + .platform_data = &gpio_button_data, + }, +}; + +static inline void mxc_init_gpio_button(void) +{ + gpio_request(IOMUX_TO_GPIO(MXC_BUTTON_GPIO_PIN), "button"); + gpio_direction_input(IOMUX_TO_GPIO(MXC_BUTTON_GPIO_PIN)); + platform_device_register(&gpio_button_device); +} +#else +static inline void mxc_init_gpio_button(void) +{ +} +#endif + +/*! + * Board specific fixup function. It is called by \b setup_arch() in + * setup.c file very early on during kernel starts. It allows the user to + * statically fill in the proper values for the passed-in parameters. None of + * the parameters is used currently. + * + * @param desc pointer to \b struct \b machine_desc + * @param tags pointer to \b struct \b tag + * @param cmdline pointer to the command line + * @param mi pointer to \b struct \b meminfo + */ +static void __init fixup_mxc_board(struct machine_desc *desc, struct tag *tags, + char **cmdline, struct meminfo *mi) +{ + char *str; + int size = SZ_512M - SZ_32M; + struct tag *t; + + mxc_cpu_init(); + + get_cpu_wp = mx51_babbage_get_cpu_wp; + set_num_cpu_wp = mx51_babbage_set_num_cpu_wp; + + for_each_tag(t, tags) { + if (t->hdr.tag != ATAG_CMDLINE) + continue; + str = t->u.cmdline.cmdline; + str = strstr(str, "mem="); + if (str != NULL) { + str += 4; + size = memparse(str, &str); + if (size == 0 || size == SZ_512M) + return; + } + } + + for_each_tag(t, tags) { + if (t->hdr.tag != ATAG_MEM) + continue; + + t->u.mem.size = size; +#if defined(CONFIG_FB_MXC_SYNC_PANEL) || \ + defined(CONFIG_FB_MXC_SYNC_PANEL_MODULE) + mxcfb_resources[0].start = t->u.mem.start + size; + mxcfb_resources[0].end = t->u.mem.start + SZ_512M - 1; +#endif + } +} + +#define PWGT1SPIEN (1<<15) +#define PWGT2SPIEN (1<<16) +#define USEROFFSPI (1<<3) + +static void mxc_power_off(void) +{ + /* We can do power down one of two ways: + Set the power gating + Set USEROFFSPI */ + + /* Set the power gate bits to power down */ + pmic_write_reg(REG_POWER_MISC, (PWGT1SPIEN|PWGT2SPIEN), + (PWGT1SPIEN|PWGT2SPIEN)); +} + +/*! + * Power Key interrupt handler. + */ +static irqreturn_t power_key_int(int irq, void *dev_id) +{ + pr_info(KERN_INFO "PWR key pressed\n"); + return 0; +} + +/*! + * Power Key initialization. + */ +static int __init mxc_init_power_key(void) +{ + /* Set power key as wakeup resource */ + int irq, ret; + irq = IOMUX_TO_IRQ(MX51_PIN_EIM_A27); + set_irq_type(irq, IRQF_TRIGGER_RISING); + ret = request_irq(irq, power_key_int, 0, "power_key", 0); + if (ret) + pr_info("register on-off key interrupt failed\n"); + else + enable_irq_wake(irq); + return ret; +} + +late_initcall(mxc_init_power_key); + +/*! + * Board specific initialization. + */ +static void __init mxc_board_init(void) +{ + mxc_cpu_common_init(); + mxc_register_gpios(); + mx51_babbage_io_init(); + early_console_setup(saved_command_line); + + mxc_init_devices(); + + mxc_init_keypad(); + mxc_init_mmc(); + mxc_init_gpio_button(); + mx51_babbage_init_mc13892(); + + if (board_is_rev(BOARD_REV_2)) + /* BB2.5 */ + spi_register_board_info(mxc_dataflash_device, + ARRAY_SIZE(mxc_dataflash_device)); + else + /* BB2.0 */ + spi_register_board_info(mxc_spi_nor_device, + ARRAY_SIZE(mxc_spi_nor_device)); + +#if defined(CONFIG_I2C_MXC) || defined(CONFIG_I2C_MXC_MODULE) + +#ifdef CONFIG_I2C_MXC_SELECT1 + i2c_register_board_info(0, mxc_i2c0_board_info, + ARRAY_SIZE(mxc_i2c0_board_info)); +#endif +#ifdef CONFIG_I2C_MXC_SELECT2 + i2c_register_board_info(1, mxc_i2c1_board_info, + ARRAY_SIZE(mxc_i2c1_board_info)); +#endif +#if defined(CONFIG_I2C_MXC_HS) || defined(CONFIG_I2C_MXC_HS_MODULE) + if (cpu_is_mx51_rev(CHIP_REV_2_0) >= 1) { + vga_data.core_reg = NULL; + vga_data.io_reg = NULL; + vga_data.analog_reg = NULL; + } + i2c_register_board_info(3, mxc_i2c_hs_board_info, + ARRAY_SIZE(mxc_i2c_hs_board_info)); +#endif + +#endif + pm_power_off = mxc_power_off; + mxc_init_fec(); + mxc_init_sgtl5000(); +} + +static void __init mx51_babbage_timer_init(void) +{ + mx51_clocks_init(32768, 24000000, 22579200, 24576000); +} + +static struct sys_timer mxc_timer = { + .init = mx51_babbage_timer_init, +}; + +/* + * The following uses standard kernel macros define in arch.h in order to + * initialize __mach_desc_MX51_BABBAGE data structure. + */ +/* *INDENT-OFF* */ +MACHINE_START(MX51_BABBAGE, "Freescale MX51 Babbage Board") + /* Maintainer: Freescale Semiconductor, Inc. */ + .phys_io = AIPS1_BASE_ADDR, + .io_pg_offst = ((AIPS1_BASE_ADDR_VIRT) >> 18) & 0xfffc, + .boot_params = PHYS_OFFSET + 0x100, + .fixup = fixup_mxc_board, + .map_io = mx51_map_io, + .init_irq = mxc_init_irq, + .init_machine = mxc_board_init, + .timer = &mxc_timer, +MACHINE_END diff --git a/arch/arm/mach-mx51/mx51_babbage_gpio.c b/arch/arm/mach-mx51/mx51_babbage_gpio.c new file mode 100644 index 000000000000..d3836a7795e8 --- /dev/null +++ b/arch/arm/mach-mx51/mx51_babbage_gpio.c @@ -0,0 +1,801 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <mach/hardware.h> +#include <mach/gpio.h> + +#include "iomux.h" + +/*! + * @file mach-mx51/mx51_babbage_gpio.c + * + * @brief This file contains all the GPIO setup functions for the board. + * + * @ingroup GPIO + */ + +static struct mxc_iomux_pin_cfg __initdata mxc_iomux_pins[] = { + { + MX51_PIN_EIM_A16, IOMUX_CONFIG_GPIO, + }, + { + MX51_PIN_EIM_A17, IOMUX_CONFIG_GPIO, + }, + { + MX51_PIN_EIM_A18, IOMUX_CONFIG_GPIO, + }, + { + MX51_PIN_EIM_A19, IOMUX_CONFIG_GPIO, + }, + { + MX51_PIN_EIM_A20, IOMUX_CONFIG_GPIO, + (PAD_CTL_PKE_ENABLE), + }, + { + MX51_PIN_EIM_A21, IOMUX_CONFIG_GPIO, + }, + { + MX51_PIN_EIM_A22, IOMUX_CONFIG_GPIO, + }, + { + MX51_PIN_EIM_A23, IOMUX_CONFIG_GPIO, + }, + { /*MDIO */ + MX51_PIN_EIM_EB2, IOMUX_CONFIG_ALT3, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_ODE_OPENDRAIN_ENABLE | + PAD_CTL_22K_PU | PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_PULL), + }, + { /*RDATA[1] */ + + MX51_PIN_EIM_EB3, IOMUX_CONFIG_ALT3, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE), + }, + { /*RDATA[2] */ + MX51_PIN_EIM_CS2, IOMUX_CONFIG_ALT3, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE), + }, + { /*RDATA[3] */ + MX51_PIN_EIM_CS3, IOMUX_CONFIG_ALT3, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE), + }, + { /*RX_ER */ + MX51_PIN_EIM_CS4, IOMUX_CONFIG_ALT3, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE), + }, + { /*CRS */ + MX51_PIN_EIM_CS5, IOMUX_CONFIG_ALT3, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE), + }, + { + MX51_PIN_EIM_DTACK, IOMUX_CONFIG_GPIO, + (PAD_CTL_PKE_ENABLE | PAD_CTL_100K_PU), + }, + { + MX51_PIN_EIM_LBA, IOMUX_CONFIG_GPIO, + }, + { + MX51_PIN_NANDF_RB2, IOMUX_CONFIG_ALT1, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE), + }, + { + MX51_PIN_NANDF_RB3, IOMUX_CONFIG_ALT1, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE), + }, + { + MX51_PIN_NANDF_RB4, IOMUX_CONFIG_ALT1, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE), + }, + { /*RDATA[0] */ + MX51_PIN_NANDF_RB6, IOMUX_CONFIG_ALT1, + (PAD_CTL_DRV_VOT_HIGH | PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE), + }, + { /*TDATA[0] */ + MX51_PIN_NANDF_RB7, IOMUX_CONFIG_ALT1, + (PAD_CTL_DRV_VOT_HIGH | PAD_CTL_DRV_HIGH), + }, + { + MX51_PIN_NANDF_CS0, IOMUX_CONFIG_GPIO, + PAD_CTL_100K_PU, + }, + { + MX51_PIN_NANDF_CS1, IOMUX_CONFIG_GPIO, + }, + { /*TX_ER */ + MX51_PIN_NANDF_CS2, IOMUX_CONFIG_ALT2, + (PAD_CTL_DRV_VOT_HIGH | PAD_CTL_DRV_HIGH), + }, + { + MX51_PIN_NANDF_CS3, IOMUX_CONFIG_ALT2, + (PAD_CTL_DRV_VOT_HIGH | PAD_CTL_DRV_HIGH), + }, + { /*TDATA[1] */ + MX51_PIN_NANDF_CS4, IOMUX_CONFIG_ALT2, + (PAD_CTL_DRV_VOT_HIGH | PAD_CTL_DRV_HIGH), + }, + { /*TDATA[2] */ + MX51_PIN_NANDF_CS5, IOMUX_CONFIG_ALT2, + (PAD_CTL_DRV_VOT_HIGH | PAD_CTL_DRV_HIGH), + }, + { /*TDATA[3] */ + MX51_PIN_NANDF_CS6, IOMUX_CONFIG_ALT2, + (PAD_CTL_DRV_VOT_HIGH | PAD_CTL_DRV_HIGH), + }, + { /*TX_EN */ + MX51_PIN_NANDF_CS7, IOMUX_CONFIG_ALT1, + (PAD_CTL_DRV_VOT_HIGH | PAD_CTL_DRV_HIGH), + }, + { /*TX_CLK */ + MX51_PIN_NANDF_RDY_INT, IOMUX_CONFIG_ALT1, + (PAD_CTL_DRV_VOT_HIGH | PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE), + }, + { + MX51_PIN_GPIO1_8, IOMUX_CONFIG_GPIO | IOMUX_CONFIG_SION, + (PAD_CTL_SRE_SLOW | PAD_CTL_DRV_MEDIUM | PAD_CTL_100K_PU | + PAD_CTL_HYS_ENABLE | PAD_CTL_DRV_VOT_HIGH), + }, + { + MX51_PIN_DI_GP4, IOMUX_CONFIG_ALT4, + }, + { + MX51_PIN_DISPB2_SER_DIN, IOMUX_CONFIG_GPIO, + 0, + MUX_IN_GPIO3_IPP_IND_G_IN_5_SELECT_INPUT, + INPUT_CTL_PATH1, + }, +#ifdef CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL + { /* DISP2_DAT16 */ + MX51_PIN_DISP1_DAT22, IOMUX_CONFIG_ALT5, + }, + { /* DISP2_DAT17 */ + MX51_PIN_DISP1_DAT23, IOMUX_CONFIG_ALT5, + }, + { + MX51_PIN_DI1_D1_CS, IOMUX_CONFIG_ALT4, + (PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_KEEPER | PAD_CTL_DRV_HIGH | + PAD_CTL_SRE_FAST), + MUX_IN_GPIO3_IPP_IND_G_IN_4_SELECT_INPUT, INPUT_CTL_PATH1, + }, +#endif + /* LVDS GPIO control */ + { + MX51_PIN_DI1_D0_CS, IOMUX_CONFIG_ALT4, + (PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_KEEPER | PAD_CTL_DRV_HIGH | + PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_CSI2_D12, IOMUX_CONFIG_ALT3, + (PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_KEEPER | PAD_CTL_DRV_HIGH | + PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_CSI2_D13, IOMUX_CONFIG_ALT3, + (PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_KEEPER | PAD_CTL_DRV_HIGH | + PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_NANDF_D12, IOMUX_CONFIG_GPIO, + 0, + }, + { + MX51_PIN_I2C1_CLK, IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION, + 0x1E4, + }, + { + MX51_PIN_I2C1_DAT, IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION, + 0x1E4, + }, + { + MX51_PIN_GPIO1_2, IOMUX_CONFIG_ALT1, + }, + { + MX51_PIN_GPIO1_3, IOMUX_CONFIG_ALT2 | IOMUX_CONFIG_SION, + (PAD_CTL_SRE_FAST | PAD_CTL_ODE_OPENDRAIN_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_100K_PU | PAD_CTL_HYS_ENABLE), + MUX_IN_I2C2_IPP_SDA_IN_SELECT_INPUT, INPUT_CTL_PATH3, + }, + { + MX51_PIN_USBH1_STP, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_PUE_KEEPER | + PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_CLK */ + MX51_PIN_USBH1_CLK, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_PUE_KEEPER | + PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE | PAD_CTL_DDR_INPUT_CMOS), + }, + { /* USBH1_DIR */ + MX51_PIN_USBH1_DIR, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_PUE_KEEPER | + PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE | PAD_CTL_DDR_INPUT_CMOS), + }, + { /* USBH1_NXT */ + MX51_PIN_USBH1_NXT, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_PUE_KEEPER | + PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE | PAD_CTL_DDR_INPUT_CMOS), + }, + { /* USBH1_DATA0 */ + MX51_PIN_USBH1_DATA0, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_DATA1 */ + MX51_PIN_USBH1_DATA1, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_DATA2 */ + MX51_PIN_USBH1_DATA2, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_DATA3 */ + MX51_PIN_USBH1_DATA3, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_DATA4 */ + MX51_PIN_USBH1_DATA4, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_DATA5 */ + MX51_PIN_USBH1_DATA5, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_DATA6 */ + MX51_PIN_USBH1_DATA6, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { /* USBH1_DATA7 */ + MX51_PIN_USBH1_DATA7, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_HYS_ENABLE), + }, + { + MX51_PIN_SD1_CMD, IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION, + (PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_47K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD1_CLK, IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION, + (PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_47K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD1_DATA0, IOMUX_CONFIG_ALT0, + (PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_47K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD1_DATA1, IOMUX_CONFIG_ALT0, + (PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_47K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD1_DATA2, IOMUX_CONFIG_ALT0, + (PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_47K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD1_DATA3, IOMUX_CONFIG_ALT0, + (PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_47K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_GPIO1_0, IOMUX_CONFIG_GPIO | IOMUX_CONFIG_SION, + (PAD_CTL_HYS_ENABLE | PAD_CTL_100K_PU), + }, + { + MX51_PIN_GPIO1_1, IOMUX_CONFIG_GPIO | IOMUX_CONFIG_SION, + (PAD_CTL_HYS_ENABLE | PAD_CTL_100K_PU), + }, + { + MX51_PIN_SD2_CMD, IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD2_CLK, IOMUX_CONFIG_ALT0 | IOMUX_CONFIG_SION, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD2_DATA0, IOMUX_CONFIG_ALT0, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD2_DATA1, IOMUX_CONFIG_ALT0, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD2_DATA2, IOMUX_CONFIG_ALT0, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_SD2_DATA3, IOMUX_CONFIG_ALT0, + (PAD_CTL_DRV_MAX | PAD_CTL_22K_PU | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_GPIO1_4, IOMUX_CONFIG_GPIO | IOMUX_CONFIG_SION, + (PAD_CTL_HYS_ENABLE | PAD_CTL_100K_PU), + }, + { + MX51_PIN_GPIO1_5, IOMUX_CONFIG_GPIO | IOMUX_CONFIG_SION, + (PAD_CTL_HYS_ENABLE | PAD_CTL_100K_PU), + }, + { + MX51_PIN_GPIO1_6, IOMUX_CONFIG_GPIO | IOMUX_CONFIG_SION, + (PAD_CTL_HYS_ENABLE | PAD_CTL_100K_PU), + }, + { /* Detect pin GPIO BB2.0 and BB2.5 */ + MX51_PIN_UART3_RXD, IOMUX_CONFIG_ALT3, + (PAD_CTL_HYS_NONE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_KEEPER | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_UART1_RXD, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + MUX_IN_UART1_IPP_UART_RXD_MUX_SELECT_INPUT, + INPUT_CTL_PATH0, + }, + { + MX51_PIN_UART1_TXD, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + }, + { + MX51_PIN_UART1_RTS, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH), + MUX_IN_UART1_IPP_UART_RTS_B_SELECT_INPUT, + INPUT_CTL_PATH0, + }, + { + MX51_PIN_UART1_CTS, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH), + }, + { + MX51_PIN_AUD3_BB_TXD, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_100K_PU | PAD_CTL_HYS_NONE | PAD_CTL_DDR_INPUT_CMOS | + PAD_CTL_DRV_VOT_LOW), + }, + { + MX51_PIN_AUD3_BB_RXD, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_100K_PU | PAD_CTL_HYS_NONE | PAD_CTL_DDR_INPUT_CMOS | + PAD_CTL_DRV_VOT_LOW), + }, + { + MX51_PIN_AUD3_BB_CK, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_100K_PU | PAD_CTL_HYS_NONE | PAD_CTL_DDR_INPUT_CMOS | + PAD_CTL_DRV_VOT_LOW), + }, + { + MX51_PIN_AUD3_BB_FS, IOMUX_CONFIG_ALT0, + (PAD_CTL_SRE_FAST | PAD_CTL_DRV_HIGH | PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_100K_PU | PAD_CTL_HYS_NONE | PAD_CTL_DDR_INPUT_CMOS | + PAD_CTL_DRV_VOT_LOW), + }, + { + MX51_PIN_CSPI1_SS1, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PKE_ENABLE | PAD_CTL_DRV_HIGH | + PAD_CTL_SRE_FAST), + }, + /* Camera on expansion board */ + { /* camera reset */ + MX51_PIN_EIM_D23, IOMUX_CONFIG_ALT1, + (PAD_CTL_HYS_NONE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_KEEPER | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + }, + { /* camera low power */ + MX51_PIN_CSI2_D19, IOMUX_CONFIG_ALT3, + (PAD_CTL_HYS_NONE | PAD_CTL_PKE_ENABLE | PAD_CTL_DRV_LOW | + PAD_CTL_SRE_SLOW), + }, + { /* CSI1_DATA_EN need to be pulled up */ + MX51_PIN_DI_GP3, IOMUX_CONFIG_ALT3, + (PAD_CTL_HYS_NONE | PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST), + MUX_IN_HSC_MIPI_MIX_IPP_IND_SENS2_DATA_EN_SELECT_INPUT, + INPUT_CTL_PATH1, + }, + { + MX51_PIN_CSI1_D10, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D11, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D12, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D13, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D14, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D15, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D16, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D17, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D18, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_D19, IOMUX_CONFIG_ALT0, PAD_CTL_HYS_NONE, + }, + { + MX51_PIN_CSI1_VSYNC, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_SRE_SLOW), + }, + { + MX51_PIN_CSI1_HSYNC, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_NONE | PAD_CTL_SRE_SLOW), + }, + { + MX51_PIN_EIM_D18, IOMUX_CONFIG_GPIO, + (PAD_CTL_DRV_HIGH | PAD_CTL_PKE_ENABLE | + PAD_CTL_PUE_KEEPER | PAD_CTL_100K_PU | PAD_CTL_SRE_FAST), + }, +}; + +static int __initdata enable_w1 = { 0 }; +static int __init w1_setup(char *__unused) +{ + enable_w1 = 1; + return 1; +} + +__setup("w1", w1_setup); + +void __init mx51_babbage_io_init(void) +{ + int i; + + /* Work-around For external USB HUB chip to use default configuration + by reseting hub with i2c lines pulled low */ + mxc_request_iomux(MX51_PIN_GPIO1_7, IOMUX_CONFIG_GPIO); + mxc_iomux_set_pad(MX51_PIN_GPIO1_7, PAD_CTL_DRV_HIGH | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_GPIO1_7), "gpio1_7"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_GPIO1_7), 0); + + if (cpu_is_mx51_rev(CHIP_REV_1_1) == 1) { + /* Drive I2C1 SDA line low */ + mxc_request_iomux(MX51_PIN_GPIO1_3, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX51_PIN_GPIO1_3, PAD_CTL_DRV_HIGH | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_GPIO1_3), "gpio1_3"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_GPIO1_3), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_GPIO1_3), 0); + + /* Drive I2C1 SCL line low */ + mxc_request_iomux(MX51_PIN_GPIO1_2, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX51_PIN_GPIO1_2, PAD_CTL_DRV_HIGH | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_GPIO1_2), "gpio1_2"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_GPIO1_2), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_GPIO1_2), 0); + + msleep(5); + mxc_free_iomux(MX51_PIN_GPIO1_2, IOMUX_CONFIG_ALT2); + mxc_free_iomux(MX51_PIN_GPIO1_3, IOMUX_CONFIG_ALT2); + } + + /* USB HUB RESET - De-assert USB HUB RESET_N */ + msleep(1); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_GPIO1_7), 0); + msleep(1); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_GPIO1_7), 1); + + for (i = 0; i < ARRAY_SIZE(mxc_iomux_pins); i++) { + mxc_request_iomux(mxc_iomux_pins[i].pin, + mxc_iomux_pins[i].mux_mode); + if (mxc_iomux_pins[i].pad_cfg) + mxc_iomux_set_pad(mxc_iomux_pins[i].pin, + mxc_iomux_pins[i].pad_cfg); + if (mxc_iomux_pins[i].in_select) + mxc_iomux_set_input(mxc_iomux_pins[i].in_select, + mxc_iomux_pins[i].in_mode); + } + + gpio_request(IOMUX_TO_GPIO(MX51_PIN_GPIO1_8), "gpio1_8"); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_GPIO1_0), "gpio1_0"); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_GPIO1_1), "gpio1_1"); + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_GPIO1_8)); + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_GPIO1_0)); /* SD1 CD */ + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_GPIO1_1)); /* SD1 WP */ + if (board_is_rev(BOARD_REV_2)) { + /* SD2 CD for BB2.5 */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_GPIO1_6), "gpio1_6"); + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_GPIO1_6)); + } else { + /* SD2 CD for BB2.0 */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_GPIO1_4), "gpio1_4"); + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_GPIO1_4)); + } + gpio_request(IOMUX_TO_GPIO(MX51_PIN_GPIO1_5), "gpio1_5"); + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_GPIO1_5)); /* SD2 WP */ + + /* reset FEC PHY */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_A20), "eim_a20"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_A20), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A20), 0); + msleep(10); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A20), 1); + + /* reset FM */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_A21), "eim_a21"); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A21), 0); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_A21), 0); + msleep(10); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A21), 1); + + if (cpu_is_mx51_rev(CHIP_REV_1_1) == 1) { + /* MX51_PIN_EIM_CRE - De-assert USB PHY RESETB */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_CRE), "eim_cre"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_CRE), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_CRE), 1); + + /* hphone_det_b */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_NANDF_CS0), "nandf_cs0"); + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_NANDF_CS0)); + } else { + mxc_free_iomux(MX51_PIN_EIM_D21, IOMUX_CONFIG_ALT2); + mxc_free_iomux(MX51_PIN_EIM_A24, IOMUX_CONFIG_ALT2); + mxc_free_iomux(MX51_PIN_EIM_A25, IOMUX_CONFIG_ALT2); + mxc_free_iomux(MX51_PIN_EIM_D18, IOMUX_CONFIG_ALT2); + mxc_free_iomux(MX51_PIN_EIM_D20, IOMUX_CONFIG_ALT2); + mxc_free_iomux(MX51_PIN_EIM_D21, IOMUX_CONFIG_ALT2); + mxc_free_iomux(MX51_PIN_EIM_D16, IOMUX_CONFIG_ALT2); + mxc_free_iomux(MX51_PIN_EIM_D17, IOMUX_CONFIG_ALT2); + mxc_free_iomux(MX51_PIN_EIM_D19, IOMUX_CONFIG_ALT2); + mxc_free_iomux(MX51_PIN_GPIO1_2, IOMUX_CONFIG_ALT2); + mxc_free_iomux(MX51_PIN_GPIO1_3, IOMUX_CONFIG_ALT2); + mxc_free_iomux(MX51_PIN_EIM_LBA, IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX51_PIN_NANDF_CS0, IOMUX_CONFIG_GPIO); + + /* i2c1 SDA */ + mxc_request_iomux(MX51_PIN_EIM_D16, + IOMUX_CONFIG_ALT4 | IOMUX_CONFIG_SION); + mxc_iomux_set_input(MUX_IN_I2C1_IPP_SDA_IN_SELECT_INPUT, + INPUT_CTL_PATH0); + mxc_iomux_set_pad(MX51_PIN_EIM_D16, PAD_CTL_SRE_FAST | + PAD_CTL_ODE_OPENDRAIN_ENABLE | + PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_HYS_ENABLE); + + /* i2c1 SCL */ + mxc_request_iomux(MX51_PIN_EIM_D19, + IOMUX_CONFIG_ALT4 | IOMUX_CONFIG_SION); + mxc_iomux_set_input(MUX_IN_I2C1_IPP_SCL_IN_SELECT_INPUT, + INPUT_CTL_PATH0); + mxc_iomux_set_pad(MX51_PIN_EIM_D19, PAD_CTL_SRE_FAST | + PAD_CTL_ODE_OPENDRAIN_ENABLE | + PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_HYS_ENABLE); + + /* i2c2 SDA */ + mxc_request_iomux(MX51_PIN_KEY_COL5, + IOMUX_CONFIG_ALT3 | IOMUX_CONFIG_SION); + mxc_iomux_set_input(MUX_IN_I2C2_IPP_SDA_IN_SELECT_INPUT, + INPUT_CTL_PATH1); + mxc_iomux_set_pad(MX51_PIN_KEY_COL5, + PAD_CTL_SRE_FAST | + PAD_CTL_ODE_OPENDRAIN_ENABLE | + PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_HYS_ENABLE); + + /* i2c2 SCL */ + mxc_request_iomux(MX51_PIN_KEY_COL4, + IOMUX_CONFIG_ALT3 | IOMUX_CONFIG_SION); + mxc_iomux_set_input(MUX_IN_I2C2_IPP_SCL_IN_SELECT_INPUT, + INPUT_CTL_PATH1); + mxc_iomux_set_pad(MX51_PIN_KEY_COL4, + PAD_CTL_SRE_FAST | + PAD_CTL_ODE_OPENDRAIN_ENABLE | + PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_HYS_ENABLE); + + /* Drive 26M_OSC_EN line high */ + mxc_request_iomux(MX51_PIN_DI1_PIN12, IOMUX_CONFIG_ALT4); + mxc_iomux_set_pad(MX51_PIN_DI1_PIN12, PAD_CTL_DRV_HIGH | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_DI1_PIN12), "di1_pin12"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_DI1_PIN12), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_DI1_PIN12), 1); + + /* Drive USB_CLK_EN_B line low */ + mxc_request_iomux(MX51_PIN_EIM_D17, IOMUX_CONFIG_ALT1); + mxc_iomux_set_pad(MX51_PIN_EIM_D17, PAD_CTL_DRV_HIGH | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_D17), "eim_d17"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_D17), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_D17), 0); + + /* MX51_PIN_EIM_D21 - De-assert USB PHY RESETB */ + mxc_request_iomux(MX51_PIN_EIM_D21, IOMUX_CONFIG_ALT1); + mxc_iomux_set_pad(MX51_PIN_EIM_D21, PAD_CTL_DRV_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_PUE_KEEPER | + PAD_CTL_100K_PU | PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_D21), "eim_d21"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_D21), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_D21), 1); + + /* hphone_det_b */ + mxc_request_iomux(MX51_PIN_NANDF_D14, IOMUX_CONFIG_ALT3); + mxc_iomux_set_pad(MX51_PIN_NANDF_D14, PAD_CTL_100K_PU); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_NANDF_D14), "nandf_d14"); + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_NANDF_D14)); + + /* audio_clk_en_b */ + mxc_request_iomux(MX51_PIN_CSPI1_RDY, IOMUX_CONFIG_ALT3); + mxc_iomux_set_pad(MX51_PIN_CSPI1_RDY, PAD_CTL_DRV_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_PUE_KEEPER | + PAD_CTL_100K_PU | PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_CSPI1_RDY), "cspi1_rdy"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_CSPI1_RDY), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_CSPI1_RDY), 0); + + /* power key */ + mxc_request_iomux(MX51_PIN_EIM_A27, IOMUX_CONFIG_ALT1); + mxc_iomux_set_pad(MX51_PIN_EIM_A27, PAD_CTL_SRE_FAST | + PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_DRV_HIGH | PAD_CTL_100K_PU | + PAD_CTL_HYS_NONE); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_A27), "eim_a27"); + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_EIM_A27)); + } + + if (cpu_is_mx51_rev(CHIP_REV_3_0) > 0) { + /* DVI_I2C_ENB = 0 tristates the DVI I2C level shifter */ + mxc_request_iomux(MX51_PIN_CSI2_HSYNC, IOMUX_CONFIG_ALT3); + mxc_iomux_set_pad(MX51_PIN_CSI2_HSYNC, PAD_CTL_DRV_HIGH | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_CSI2_HSYNC), "csi2_hsync"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_CSI2_HSYNC), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_CSI2_HSYNC), 0); + /* TO3 doesn't need pad to drive CSI_DATA_EN[0] high */ + mxc_request_iomux(MX51_PIN_DI_GP3, IOMUX_CONFIG_ALT0); + } + + /* Deassert VGA reset to free i2c bus */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_A19), "eim_a19"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_A19), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A19), 1); + + /* LCD related gpio */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_DI1_D1_CS), "di1_d1_cs"); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_DI1_D0_CS), "di1_d0_cs"); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_CSI2_D12), "csi2_d12"); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_CSI2_D13), "csi2_d13"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_DI1_D1_CS), 0); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_DI1_D0_CS), 0); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_CSI2_D12), 0); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_CSI2_D13), 0); + + /* Camera reset */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_D23), "eim_d23"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_D23), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_D23), 1); + + /* Camera low power */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_CSI2_D19), "csi2_d19"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_CSI2_D19), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_CSI2_D19), 0); + + /* OSC_EN */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_D18), "eim_d18"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_D18), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_D18), 1); + + if (enable_w1) { + /* OneWire */ + mxc_request_iomux(MX51_PIN_OWIRE_LINE, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX51_PIN_OWIRE_LINE, PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE | + PAD_CTL_ODE_OPENDRAIN_ENABLE | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST | + PAD_CTL_100K_PU | PAD_CTL_PUE_PULL); + } else { + /* SPDIF Out */ + mxc_request_iomux(MX51_PIN_OWIRE_LINE, IOMUX_CONFIG_ALT6); + mxc_iomux_set_pad(MX51_PIN_OWIRE_LINE, PAD_CTL_DRV_HIGH | + PAD_CTL_PKE_ENABLE | PAD_CTL_PUE_PULL | + PAD_CTL_100K_PU | PAD_CTL_SRE_FAST); + } +} + +/* workaround for ecspi chipselect pin may not keep correct level when idle */ +void mx51_babbage_gpio_spi_chipselect_active(int cspi_mode, int status, + int chipselect) +{ + u32 gpio; + + switch (cspi_mode) { + case 1: + switch (chipselect) { + case 0x1: + mxc_request_iomux(MX51_PIN_CSPI1_SS0, + IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX51_PIN_CSPI1_SS0, + PAD_CTL_HYS_ENABLE | + PAD_CTL_PKE_ENABLE | + PAD_CTL_DRV_HIGH | PAD_CTL_SRE_FAST); + break; + case 0x2: + gpio = IOMUX_TO_GPIO(MX51_PIN_CSPI1_SS0); + mxc_request_iomux(MX51_PIN_CSPI1_SS0, + IOMUX_CONFIG_GPIO); + gpio_request(gpio, "cspi1_ss0"); + gpio_direction_output(gpio, 0); + gpio_set_value(gpio, 1 & (~status)); + break; + default: + break; + } + break; + case 2: + break; + case 3: + break; + default: + break; + } +} +EXPORT_SYMBOL(mx51_babbage_gpio_spi_chipselect_active); + +void mx51_babbage_gpio_spi_chipselect_inactive(int cspi_mode, int status, + int chipselect) +{ + switch (cspi_mode) { + case 1: + switch (chipselect) { + case 0x1: + mxc_free_iomux(MX51_PIN_CSPI1_SS0, IOMUX_CONFIG_ALT0); + mxc_request_iomux(MX51_PIN_CSPI1_SS0, + IOMUX_CONFIG_GPIO); + mxc_free_iomux(MX51_PIN_CSPI1_SS0, IOMUX_CONFIG_GPIO); + break; + case 0x2: + mxc_free_iomux(MX51_PIN_CSPI1_SS0, IOMUX_CONFIG_GPIO); + break; + default: + break; + } + break; + case 2: + break; + case 3: + break; + default: + break; + } +} +EXPORT_SYMBOL(mx51_babbage_gpio_spi_chipselect_inactive); diff --git a/arch/arm/mach-mx51/mx51_babbage_pmic_mc13892.c b/arch/arm/mach-mx51/mx51_babbage_pmic_mc13892.c new file mode 100644 index 000000000000..050e062930ed --- /dev/null +++ b/arch/arm/mach-mx51/mx51_babbage_pmic_mc13892.c @@ -0,0 +1,362 @@ +/* + * mx51-3stack-pmic-mc13892.c -- i.MX51 3STACK Driver for Atlas MC13892 PMIC + */ + /* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + + /* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/err.h> +#include <linux/pmic_external.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/mc13892/core.h> +#include <mach/irqs.h> +#include "iomux.h" + +/* + * Convenience conversion. + * Here atm, maybe there is somewhere better for this. + */ +#define mV_to_uV(mV) (mV * 1000) +#define uV_to_mV(uV) (uV / 1000) +#define V_to_uV(V) (mV_to_uV(V * 1000)) +#define uV_to_V(uV) (uV_to_mV(uV) / 1000) + +/* Coin cell charger enable */ +#define CIONCHEN_LSH 23 +#define CIONCHEN_WID 1 +/* Coin cell charger voltage setting */ +#define VCOIN_LSH 20 +#define VCOIN_WID 3 + +/* Coin Charger voltage */ +#define VCOIN_2_5V 0x0 +#define VCOIN_2_7V 0x1 +#define VCOIN_2_8V 0x2 +#define VCOIN_2_9V 0x3 +#define VCOIN_3_0V 0x4 +#define VCOIN_3_1V 0x5 +#define VCOIN_3_2V 0x6 +#define VCOIN_3_3V 0x7 + +/* Keeps VSRTC and CLK32KMCU on for all states */ +#define DRM_LSH 4 +#define DRM_WID 1 + +/* regulator standby mask */ +#define GEN1_STBY_MASK (1 << 1) +#define IOHI_STBY_MASK (1 << 4) +#define DIG_STBY_MASK (1 << 10) +#define GEN2_STBY_MASK (1 << 13) +#define PLL_STBY_MASK (1 << 16) +#define USB2_STBY_MASK (1 << 19) + +#define GEN3_STBY_MASK (1 << 1) +#define CAM_STBY_MASK (1 << 7) +#define VIDEO_STBY_MASK (1 << 13) +#define AUDIO_STBY_MASK (1 << 16) +#define SD_STBY_MASK (1 << 19) + +/* 0x92412 */ +#define REG_MODE_0_ALL_MASK (GEN1_STBY_MASK | IOHI_STBY_MASK |\ + DIG_STBY_MASK | GEN2_STBY_MASK |\ + PLL_STBY_MASK | USB2_STBY_MASK) +/* 0x92082 */ +#define REG_MODE_1_ALL_MASK (GEN3_STBY_MASK | CAM_STBY_MASK |\ + VIDEO_STBY_MASK | AUDIO_STBY_MASK |\ + SD_STBY_MASK) + +/* CPU */ +static struct regulator_consumer_supply sw1_consumers[] = { + { + .supply = "cpu_vcc", + } +}; + +struct mc13892; + +static struct regulator_init_data sw1_init = { + .constraints = { + .name = "SW1", + .min_uV = mV_to_uV(600), + .max_uV = mV_to_uV(1375), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .valid_modes_mask = 0, + .always_on = 1, + .boot_on = 1, + .initial_state = PM_SUSPEND_MEM, + .state_mem = { + .uV = 700000, + .mode = REGULATOR_MODE_NORMAL, + .enabled = 1, + }, + }, + .num_consumer_supplies = ARRAY_SIZE(sw1_consumers), + .consumer_supplies = sw1_consumers, +}; + +static struct regulator_init_data sw2_init = { + .constraints = { + .name = "SW2", + .min_uV = mV_to_uV(900), + .max_uV = mV_to_uV(1850), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .always_on = 1, + .boot_on = 1, + .initial_state = PM_SUSPEND_MEM, + .state_mem = { + .uV = 900000, + .mode = REGULATOR_MODE_NORMAL, + .enabled = 1, + }, + } +}; + +static struct regulator_init_data sw3_init = { + .constraints = { + .name = "SW3", + .min_uV = mV_to_uV(1100), + .max_uV = mV_to_uV(1850), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .always_on = 1, + .boot_on = 1, + } +}; + +static struct regulator_init_data sw4_init = { + .constraints = { + .name = "SW4", + .min_uV = mV_to_uV(1100), + .max_uV = mV_to_uV(1850), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .always_on = 1, + .boot_on = 1, + } +}; + +static struct regulator_init_data viohi_init = { + .constraints = { + .name = "VIOHI", + .boot_on = 1, + } +}; + +static struct regulator_init_data vusb_init = { + .constraints = { + .name = "VUSB", + .boot_on = 1, + } +}; + +static struct regulator_init_data swbst_init = { + .constraints = { + .name = "SWBST", + } +}; + +static struct regulator_init_data vdig_init = { + .constraints = { + .name = "VDIG", + .min_uV = mV_to_uV(1050), + .max_uV = mV_to_uV(1800), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vpll_init = { + .constraints = { + .name = "VPLL", + .min_uV = mV_to_uV(1050), + .max_uV = mV_to_uV(1800), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vusb2_init = { + .constraints = { + .name = "VUSB2", + .min_uV = mV_to_uV(2400), + .max_uV = mV_to_uV(2775), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vvideo_init = { + .constraints = { + .name = "VVIDEO", + .min_uV = mV_to_uV(2775), + .max_uV = mV_to_uV(2775), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .always_on = 1, + .apply_uV =1, + } +}; + +static struct regulator_init_data vaudio_init = { + .constraints = { + .name = "VAUDIO", + .min_uV = mV_to_uV(2300), + .max_uV = mV_to_uV(3000), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vsd_init = { + .constraints = { + .name = "VSD", + .min_uV = mV_to_uV(1800), + .max_uV = mV_to_uV(3150), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vcam_init = { + .constraints = { + .name = "VCAM", + .min_uV = mV_to_uV(2500), + .max_uV = mV_to_uV(3000), + .valid_ops_mask = + REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_FAST | REGULATOR_MODE_NORMAL, + } +}; + +static struct regulator_init_data vgen1_init = { + .constraints = { + .name = "VGEN1", + .min_uV = mV_to_uV(1200), + .max_uV = mV_to_uV(3150), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vgen2_init = { + .constraints = { + .name = "VGEN2", + .min_uV = mV_to_uV(1200), + .max_uV = mV_to_uV(3150), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vgen3_init = { + .constraints = { + .name = "VGEN3", + .min_uV = mV_to_uV(1800), + .max_uV = mV_to_uV(2900), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data gpo1_init = { + .constraints = { + .name = "GPO1", + } +}; + +static struct regulator_init_data gpo2_init = { + .constraints = { + .name = "GPO2", + } +}; + +static struct regulator_init_data gpo3_init = { + .constraints = { + .name = "GPO3", + } +}; + +static struct regulator_init_data gpo4_init = { + .constraints = { + .name = "GPO4", + } +}; + +static int mc13892_regulator_init(struct mc13892 *mc13892) +{ + unsigned int value, register_mask; + printk("Initializing regulators for Babbage.\n"); + if (mxc_cpu_is_rev(CHIP_REV_2_0) < 0) + sw2_init.constraints.state_mem.uV = 1100000; + else if (mxc_cpu_is_rev(CHIP_REV_2_0) >= 1) { + sw2_init.constraints.state_mem.uV = 1250000; + sw1_init.constraints.state_mem.uV = 1000000; + } + /* enable standby controll for all regulators */ + pmic_read_reg(REG_MODE_0, &value, 0xffffff); + value |= REG_MODE_0_ALL_MASK; + pmic_write_reg(REG_MODE_0, value, 0xffffff); + + pmic_read_reg(REG_MODE_1, &value, 0xffffff); + value |= REG_MODE_1_ALL_MASK; + pmic_write_reg(REG_MODE_1, value, 0xffffff); + + /* Enable coin cell charger */ + value = BITFVAL(CIONCHEN, 1) | BITFVAL(VCOIN, VCOIN_3_0V); + register_mask = BITFMASK(CIONCHEN) | BITFMASK(VCOIN); + pmic_write_reg(REG_POWER_CTL0, value, register_mask); + +#if defined(CONFIG_RTC_DRV_MXC_V2) || defined(CONFIG_RTC_DRV_MXC_V2_MODULE) + value = BITFVAL(DRM, 1); + register_mask = BITFMASK(DRM); + pmic_write_reg(REG_POWER_CTL0, value, register_mask); +#endif + + mc13892_register_regulator(mc13892, MC13892_SW1, &sw1_init); + mc13892_register_regulator(mc13892, MC13892_SW2, &sw2_init); + mc13892_register_regulator(mc13892, MC13892_SW3, &sw3_init); + mc13892_register_regulator(mc13892, MC13892_SW4, &sw4_init); + mc13892_register_regulator(mc13892, MC13892_SWBST, &swbst_init); + mc13892_register_regulator(mc13892, MC13892_VIOHI, &viohi_init); + mc13892_register_regulator(mc13892, MC13892_VPLL, &vpll_init); + mc13892_register_regulator(mc13892, MC13892_VDIG, &vdig_init); + mc13892_register_regulator(mc13892, MC13892_VSD, &vsd_init); + mc13892_register_regulator(mc13892, MC13892_VUSB2, &vusb2_init); + mc13892_register_regulator(mc13892, MC13892_VVIDEO, &vvideo_init); + mc13892_register_regulator(mc13892, MC13892_VAUDIO, &vaudio_init); + mc13892_register_regulator(mc13892, MC13892_VCAM, &vcam_init); + mc13892_register_regulator(mc13892, MC13892_VGEN1, &vgen1_init); + mc13892_register_regulator(mc13892, MC13892_VGEN2, &vgen2_init); + mc13892_register_regulator(mc13892, MC13892_VGEN3, &vgen3_init); + mc13892_register_regulator(mc13892, MC13892_VUSB, &vusb_init); + mc13892_register_regulator(mc13892, MC13892_GPO1, &gpo1_init); + mc13892_register_regulator(mc13892, MC13892_GPO2, &gpo2_init); + mc13892_register_regulator(mc13892, MC13892_GPO3, &gpo3_init); + mc13892_register_regulator(mc13892, MC13892_GPO4, &gpo4_init); + + return 0; +} + +static struct mc13892_platform_data mc13892_plat = { + .init = mc13892_regulator_init, +}; + +static struct spi_board_info __initdata mc13892_spi_device = { + .modalias = "pmic_spi", + .irq = IOMUX_TO_IRQ(MX51_PIN_GPIO1_8), + .max_speed_hz = 1000000, /* max spi SCK clock speed in HZ */ + .bus_num = 1, + .chip_select = 0, + .platform_data = &mc13892_plat, +}; + + +int __init mx51_babbage_init_mc13892(void) +{ + return spi_register_board_info(&mc13892_spi_device, 1); +} diff --git a/arch/arm/mach-mx51/mx51_pins.h b/arch/arm/mach-mx51/mx51_pins.h new file mode 100644 index 000000000000..c0905a025556 --- /dev/null +++ b/arch/arm/mach-mx51/mx51_pins.h @@ -0,0 +1,361 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_ARCH_MXC_MX51_PINS_H__ +#define __ASM_ARCH_MXC_MX51_PINS_H__ + +/*! + * @file arch-mxc/mx51_pins.h + * + * @brief MX51 I/O Pin List + * + * @ingroup GPIO_MX51 + */ + +#ifndef __ASSEMBLY__ + +/*! + * @name IOMUX/PAD Bit field definitions + */ + +/*! @{ */ + +/*! + * In order to identify pins more effectively, each mux-controlled pin's + * enumerated value is constructed in the following way: + * + * ------------------------------------------------------------------- + * 31-29 | 28 - 24 | 23 - 21 | 20 - 10| 9 - 0 + * ------------------------------------------------------------------- + * IO_P | IO_I | GPIO_I | PAD_I | MUX_I + * ------------------------------------------------------------------- + * + * Bit 0 to 9 contains MUX_I used to identify the register + * offset (0-based. base is IOMUX_module_base) defined in the Section + * "sw_pad_ctl & sw_mux_ctl details" of the IC Spec. The + * similar field definitions are used for the pad control register. + * For example, the MX51_PIN_ETM_D0 is defined in the enumeration: + * ( (0x28 - MUX_I_START) << MUX_I)|( (0x250 - PAD_I_START) << PAD_I) + * It means the mux control register is at register offset 0x28. The pad control + * register offset is: 0x250 and also occupy the least significant bits + * within the register. + */ + +/*! + * Starting bit position within each entry of \b iomux_pins to represent the + * MUX control register offset + */ +#define MUX_I 0 +/*! + * Starting bit position within each entry of \b iomux_pins to represent the + * PAD control register offset + */ +#define PAD_I 10 +/*! + * Starting bit position within each entry of \b iomux_pins to represent which + * mux mode is for GPIO (0-based) + */ +#define GPIO_I 21 + +#define NON_GPIO_PORT 0x7 +#define PIN_TO_MUX_MASK ((1 << (PAD_I - MUX_I)) - 1) +#define PIN_TO_PAD_MASK ((1 << (GPIO_I - PAD_I)) - 1) +#define PIN_TO_ALT_GPIO_MASK ((1 << (MUX_IO_I - GPIO_I)) - 1) + +#define NON_MUX_I PIN_TO_MUX_MASK +#define MUX_I_START 0x001C +#define PAD_I_START 0x3F0 +#define INPUT_CTL_START 0x8C4 +#define INPUT_CTL_START_TO1 0x928 +#define MUX_I_END (PAD_I_START - 4) + +#define _MXC_BUILD_PIN(gp, gi, ga, mi, pi) \ + (((gp) << MUX_IO_P) | ((gi) << MUX_IO_I) | \ + ((mi) << MUX_I) | \ + ((pi - PAD_I_START) << PAD_I) | \ + ((ga) << GPIO_I)) + +#define _MXC_BUILD_GPIO_PIN(gp, gi, ga, mi, pi) \ + _MXC_BUILD_PIN(gp, gi, ga, mi, pi) + +#define _MXC_BUILD_NON_GPIO_PIN(mi, pi) \ + _MXC_BUILD_PIN(NON_GPIO_PORT, 0, 0, mi, pi) + +#define PIN_TO_IOMUX_MUX(pin) ((pin >> MUX_I) & PIN_TO_MUX_MASK) +#define PIN_TO_IOMUX_PAD(pin) ((pin >> PAD_I) & PIN_TO_PAD_MASK) +#define PIN_TO_ALT_GPIO(pin) ((pin >> GPIO_I) & PIN_TO_ALT_GPIO_MASK) +#define PIN_TO_IOMUX_INDEX(pin) (PIN_TO_IOMUX_MUX(pin) >> 2) + +/*! @} End IOMUX/PAD Bit field definitions */ + +/*! + * This enumeration is constructed based on the Section + * "sw_pad_ctl & sw_mux_ctl details" of the MX51 IC Spec. Each enumerated + * value is constructed based on the rules described above. + */ +enum iomux_pins { + MX51_PIN_EIM_DA0 = _MXC_BUILD_NON_GPIO_PIN(0x1C, 0x7A8), + MX51_PIN_EIM_DA1 = _MXC_BUILD_NON_GPIO_PIN(0x20, 0x7A8), + MX51_PIN_EIM_DA2 = _MXC_BUILD_NON_GPIO_PIN(0x24, 0x7A8), + MX51_PIN_EIM_DA3 = _MXC_BUILD_NON_GPIO_PIN(0x28, 0x7A8), + MX51_PIN_EIM_DA4 = _MXC_BUILD_NON_GPIO_PIN(0x2C, 0x7AC), + MX51_PIN_EIM_DA5 = _MXC_BUILD_NON_GPIO_PIN(0x30, 0x7AC), + MX51_PIN_EIM_DA6 = _MXC_BUILD_NON_GPIO_PIN(0x34, 0x7AC), + MX51_PIN_EIM_DA7 = _MXC_BUILD_NON_GPIO_PIN(0x38, 0x7AC), + MX51_PIN_EIM_DA8 = _MXC_BUILD_NON_GPIO_PIN(0x3C, 0x7B0), + MX51_PIN_EIM_DA9 = _MXC_BUILD_NON_GPIO_PIN(0x40, 0x7B0), + MX51_PIN_EIM_DA10 = _MXC_BUILD_NON_GPIO_PIN(0x44, 0x7B0), + MX51_PIN_EIM_DA11 = _MXC_BUILD_NON_GPIO_PIN(0x48, 0x7B0), + MX51_PIN_EIM_DA12 = _MXC_BUILD_NON_GPIO_PIN(0x4C, 0x7BC), + MX51_PIN_EIM_DA13 = _MXC_BUILD_NON_GPIO_PIN(0x50, 0x7BC), + MX51_PIN_EIM_DA14 = _MXC_BUILD_NON_GPIO_PIN(0x54, 0x7BC), + MX51_PIN_EIM_DA15 = _MXC_BUILD_NON_GPIO_PIN(0x58, 0x7BC), + MX51_PIN_EIM_D16 = _MXC_BUILD_GPIO_PIN(1, 0, 1, 0x5C, 0x3F0), + MX51_PIN_EIM_D17 = _MXC_BUILD_GPIO_PIN(1, 1, 1, 0x60, 0x3F4), + MX51_PIN_EIM_D18 = _MXC_BUILD_GPIO_PIN(1, 2, 1, 0x64, 0x3F8), + MX51_PIN_EIM_D19 = _MXC_BUILD_GPIO_PIN(1, 3, 1, 0x68, 0x3FC), + MX51_PIN_EIM_D20 = _MXC_BUILD_GPIO_PIN(1, 4, 1, 0x6C, 0x400), + MX51_PIN_EIM_D21 = _MXC_BUILD_GPIO_PIN(1, 5, 1, 0x70, 0x404), + MX51_PIN_EIM_D22 = _MXC_BUILD_GPIO_PIN(1, 6, 1, 0x74, 0x408), + MX51_PIN_EIM_D23 = _MXC_BUILD_GPIO_PIN(1, 7, 1, 0x78, 0x40C), + MX51_PIN_EIM_D24 = _MXC_BUILD_GPIO_PIN(1, 8, 1, 0x7C, 0x410), + MX51_PIN_EIM_D25 = _MXC_BUILD_NON_GPIO_PIN(0x80, 0x414), + MX51_PIN_EIM_D26 = _MXC_BUILD_NON_GPIO_PIN(0x84, 0x418), + MX51_PIN_EIM_D27 = _MXC_BUILD_GPIO_PIN(1, 9, 1, 0x88, 0x41C), + MX51_PIN_EIM_D28 = _MXC_BUILD_NON_GPIO_PIN(0x8C, 0x420), + MX51_PIN_EIM_D29 = _MXC_BUILD_NON_GPIO_PIN(0x90, 0x424), + MX51_PIN_EIM_D30 = _MXC_BUILD_NON_GPIO_PIN(0x94, 0x428), + MX51_PIN_EIM_D31 = _MXC_BUILD_NON_GPIO_PIN(0x98, 0x42C), + MX51_PIN_EIM_A16 = _MXC_BUILD_GPIO_PIN(1, 10, 1, 0x9C, 0x430), + MX51_PIN_EIM_A17 = _MXC_BUILD_GPIO_PIN(1, 11, 1, 0xA0, 0x434), + MX51_PIN_EIM_A18 = _MXC_BUILD_GPIO_PIN(1, 12, 1, 0xA4, 0x438), + MX51_PIN_EIM_A19 = _MXC_BUILD_GPIO_PIN(1, 13, 1, 0xA8, 0x43C), + MX51_PIN_EIM_A20 = _MXC_BUILD_GPIO_PIN(1, 14, 1, 0xAC, 0x440), + MX51_PIN_EIM_A21 = _MXC_BUILD_GPIO_PIN(1, 15, 1, 0xB0, 0x444), + MX51_PIN_EIM_A22 = _MXC_BUILD_GPIO_PIN(1, 16, 1, 0xB4, 0x448), + MX51_PIN_EIM_A23 = _MXC_BUILD_GPIO_PIN(1, 17, 1, 0xB8, 0x44C), + MX51_PIN_EIM_A24 = _MXC_BUILD_GPIO_PIN(1, 18, 1, 0xBC, 0x450), + MX51_PIN_EIM_A25 = _MXC_BUILD_GPIO_PIN(1, 19, 1, 0xC0, 0x454), + MX51_PIN_EIM_A26 = _MXC_BUILD_GPIO_PIN(1, 20, 1, 0xC4, 0x458), + MX51_PIN_EIM_A27 = _MXC_BUILD_GPIO_PIN(1, 21, 1, 0xC8, 0x45C), + MX51_PIN_EIM_EB0 = _MXC_BUILD_NON_GPIO_PIN(0xCC, 0x460), + MX51_PIN_EIM_EB1 = _MXC_BUILD_NON_GPIO_PIN(0xD0, 0x464), + MX51_PIN_EIM_EB2 = _MXC_BUILD_GPIO_PIN(1, 22, 1, 0xD4, 0x468), + MX51_PIN_EIM_EB3 = _MXC_BUILD_GPIO_PIN(1, 23, 1, 0xD8, 0x46C), + MX51_PIN_EIM_OE = _MXC_BUILD_GPIO_PIN(1, 24, 1, 0xDC, 0x470), + MX51_PIN_EIM_CS0 = _MXC_BUILD_GPIO_PIN(1, 25, 1, 0xE0, 0x474), + MX51_PIN_EIM_CS1 = _MXC_BUILD_GPIO_PIN(1, 26, 1, 0xE4, 0x478), + MX51_PIN_EIM_CS2 = _MXC_BUILD_GPIO_PIN(1, 27, 1, 0xE8, 0x47C), + MX51_PIN_EIM_CS3 = _MXC_BUILD_GPIO_PIN(1, 28, 1, 0xEC, 0x480), + MX51_PIN_EIM_CS4 = _MXC_BUILD_GPIO_PIN(1, 29, 1, 0xF0, 0x484), + MX51_PIN_EIM_CS5 = _MXC_BUILD_GPIO_PIN(1, 30, 1, 0xF4, 0x488), + MX51_PIN_EIM_DTACK = _MXC_BUILD_GPIO_PIN(1, 31, 1, 0xF8, 0x48C), + MX51_PIN_EIM_LBA = _MXC_BUILD_GPIO_PIN(2, 1, 1, 0xFC, 0x494), + MX51_PIN_EIM_CRE = _MXC_BUILD_GPIO_PIN(2, 2, 1, 0x100, 0x4A0), + MX51_PIN_DRAM_CS1 = _MXC_BUILD_NON_GPIO_PIN(0x104, 0x4D0), + MX51_PIN_NANDF_WE_B = _MXC_BUILD_GPIO_PIN(2, 3, 3, 0x108, 0x4E4), + MX51_PIN_NANDF_RE_B = _MXC_BUILD_GPIO_PIN(2, 4, 3, 0x10C, 0x4E8), + MX51_PIN_NANDF_ALE = _MXC_BUILD_GPIO_PIN(2, 5, 3, 0x110, 0x4EC), + MX51_PIN_NANDF_CLE = _MXC_BUILD_GPIO_PIN(2, 6, 3, 0x114, 0x4F0), + MX51_PIN_NANDF_WP_B = _MXC_BUILD_GPIO_PIN(2, 7, 3, 0x118, 0x4F4), + MX51_PIN_NANDF_RB0 = _MXC_BUILD_GPIO_PIN(2, 8, 3, 0x11C, 0x4F8), + MX51_PIN_NANDF_RB1 = _MXC_BUILD_GPIO_PIN(2, 9, 3, 0x120, 0x4FC), + MX51_PIN_NANDF_RB2 = _MXC_BUILD_GPIO_PIN(2, 10, 3, 0x124, 0x500), + MX51_PIN_NANDF_RB3 = _MXC_BUILD_GPIO_PIN(2, 11, 3, 0x128, 0x504), + MX51_PIN_GPIO_NAND = _MXC_BUILD_GPIO_PIN(2, 12, 3, 0x12C, 0x514), + MX51_PIN_NANDF_RB4 = MX51_PIN_GPIO_NAND, + MX51_PIN_NANDF_RB5 = _MXC_BUILD_GPIO_PIN(2, 13, 3, 0x130, 0x5D8), + MX51_PIN_NANDF_RB6 = _MXC_BUILD_GPIO_PIN(2, 14, 3, 0x134, 0x5DC), + MX51_PIN_NANDF_RB7 = _MXC_BUILD_GPIO_PIN(2, 15, 3, 0x138, 0x5E0), + MX51_PIN_NANDF_CS0 = _MXC_BUILD_GPIO_PIN(2, 16, 3, 0x130, 0x518), + MX51_PIN_NANDF_CS1 = _MXC_BUILD_GPIO_PIN(2, 17, 3, 0x134, 0x51C), + MX51_PIN_NANDF_CS2 = _MXC_BUILD_GPIO_PIN(2, 18, 3, 0x138, 0x520), + MX51_PIN_NANDF_CS3 = _MXC_BUILD_GPIO_PIN(2, 19, 3, 0x13C, 0x524), + MX51_PIN_NANDF_CS4 = _MXC_BUILD_GPIO_PIN(2, 20, 3, 0x140, 0x528), + MX51_PIN_NANDF_CS5 = _MXC_BUILD_GPIO_PIN(2, 21, 3, 0x144, 0x52C), + MX51_PIN_NANDF_CS6 = _MXC_BUILD_GPIO_PIN(2, 22, 3, 0x148, 0x530), + MX51_PIN_NANDF_CS7 = _MXC_BUILD_GPIO_PIN(2, 23, 3, 0x14C, 0x534), + MX51_PIN_NANDF_RDY_INT = _MXC_BUILD_GPIO_PIN(2, 24, 3, 0x150, 0x538), + MX51_PIN_NANDF_D15 = _MXC_BUILD_GPIO_PIN(2, 25, 3, 0x154, 0x53C), + MX51_PIN_NANDF_D14 = _MXC_BUILD_GPIO_PIN(2, 26, 3, 0x158, 0x540), + MX51_PIN_NANDF_D13 = _MXC_BUILD_GPIO_PIN(2, 27, 3, 0x15C, 0x544), + MX51_PIN_NANDF_D12 = _MXC_BUILD_GPIO_PIN(2, 28, 3, 0x160, 0x548), + MX51_PIN_NANDF_D11 = _MXC_BUILD_GPIO_PIN(2, 29, 3, 0x164, 0x54C), + MX51_PIN_NANDF_D10 = _MXC_BUILD_GPIO_PIN(2, 30, 3, 0x168, 0x550), + MX51_PIN_NANDF_D9 = _MXC_BUILD_GPIO_PIN(2, 31, 3, 0x16C, 0x554), + MX51_PIN_NANDF_D8 = _MXC_BUILD_GPIO_PIN(3, 0, 3, 0x170, 0x558), + MX51_PIN_NANDF_D7 = _MXC_BUILD_GPIO_PIN(3, 1, 3, 0x174, 0x55C), + MX51_PIN_NANDF_D6 = _MXC_BUILD_GPIO_PIN(3, 2, 3, 0x178, 0x560), + MX51_PIN_NANDF_D5 = _MXC_BUILD_GPIO_PIN(3, 3, 3, 0x17C, 0x564), + MX51_PIN_NANDF_D4 = _MXC_BUILD_GPIO_PIN(3, 4, 3, 0x180, 0x568), + MX51_PIN_NANDF_D3 = _MXC_BUILD_GPIO_PIN(3, 5, 3, 0x184, 0x56C), + MX51_PIN_NANDF_D2 = _MXC_BUILD_GPIO_PIN(3, 6, 3, 0x188, 0x570), + MX51_PIN_NANDF_D1 = _MXC_BUILD_GPIO_PIN(3, 7, 3, 0x18C, 0x574), + MX51_PIN_NANDF_D0 = _MXC_BUILD_GPIO_PIN(3, 8, 3, 0x190, 0x578), + MX51_PIN_CSI1_D8 = _MXC_BUILD_GPIO_PIN(2, 12, 3, 0x194, 0x57C), + MX51_PIN_CSI1_D9 = _MXC_BUILD_GPIO_PIN(2, 13, 3, 0x198, 0x580), + MX51_PIN_CSI1_D10 = _MXC_BUILD_NON_GPIO_PIN(0x19C, 0x584), + MX51_PIN_CSI1_D11 = _MXC_BUILD_NON_GPIO_PIN(0x1A0, 0x588), + MX51_PIN_CSI1_D12 = _MXC_BUILD_NON_GPIO_PIN(0x1A4, 0x58C), + MX51_PIN_CSI1_D13 = _MXC_BUILD_NON_GPIO_PIN(0x1A8, 0x590), + MX51_PIN_CSI1_D14 = _MXC_BUILD_NON_GPIO_PIN(0x1AC, 0x594), + MX51_PIN_CSI1_D15 = _MXC_BUILD_NON_GPIO_PIN(0x1B0, 0x598), + MX51_PIN_CSI1_D16 = _MXC_BUILD_NON_GPIO_PIN(0x1B4, 0x59C), + MX51_PIN_CSI1_D17 = _MXC_BUILD_NON_GPIO_PIN(0x1B8, 0x5A0), + MX51_PIN_CSI1_D18 = _MXC_BUILD_NON_GPIO_PIN(0x1BC, 0x5A4), + MX51_PIN_CSI1_D19 = _MXC_BUILD_NON_GPIO_PIN(0x1C0, 0x5A8), + MX51_PIN_CSI1_VSYNC = _MXC_BUILD_GPIO_PIN(2, 14, 3, 0x1C4, 0x5AC), + MX51_PIN_CSI1_HSYNC = _MXC_BUILD_GPIO_PIN(2, 15, 3, 0x1C8, 0x5B0), + MX51_PIN_CSI1_PIXCLK = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x5B4), + MX51_PIN_CSI1_MCLK = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x5B8), + MX51_PIN_CSI1_PKE0 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x860), + MX51_PIN_CSI2_D12 = _MXC_BUILD_GPIO_PIN(3, 9, 3, 0x1CC, 0x5BC), + MX51_PIN_CSI2_D13 = _MXC_BUILD_GPIO_PIN(3, 10, 3, 0x1D0, 0x5C0), + MX51_PIN_CSI2_D14 = _MXC_BUILD_GPIO_PIN(3, 11, 3, 0x1D4, 0x5C4), + MX51_PIN_CSI2_D15 = _MXC_BUILD_GPIO_PIN(3, 12, 3, 0x1D8, 0x5C8), + MX51_PIN_CSI2_D16 = _MXC_BUILD_GPIO_PIN(3, 11, 3, 0x1DC, 0x5CC), + MX51_PIN_CSI2_D17 = _MXC_BUILD_GPIO_PIN(3, 12, 3, 0x1E0, 0x5D0), + MX51_PIN_CSI2_D18 = _MXC_BUILD_GPIO_PIN(3, 11, 3, 0x1E4, 0x5D4), + MX51_PIN_CSI2_D19 = _MXC_BUILD_GPIO_PIN(3, 12, 3, 0x1E8, 0x5D8), + MX51_PIN_CSI2_VSYNC = _MXC_BUILD_GPIO_PIN(3, 13, 3, 0x1EC, 0x5DC), + MX51_PIN_CSI2_HSYNC = _MXC_BUILD_GPIO_PIN(3, 14, 3, 0x1F0, 0x5E0), + MX51_PIN_CSI2_PIXCLK = _MXC_BUILD_GPIO_PIN(3, 15, 3, 0x1F4, 0x5E4), + MX51_PIN_CSI2_PKE0 = _MXC_BUILD_NON_GPIO_PIN(NON_MUX_I, 0x81C), + MX51_PIN_I2C1_CLK = _MXC_BUILD_GPIO_PIN(3, 16, 3, 0x1F8, 0x5E8), + MX51_PIN_I2C1_DAT = _MXC_BUILD_GPIO_PIN(3, 17, 3, 0x1FC, 0x5EC), + MX51_PIN_AUD3_BB_TXD = _MXC_BUILD_GPIO_PIN(3, 18, 3, 0x200, 0x5F0), + MX51_PIN_AUD3_BB_RXD = _MXC_BUILD_GPIO_PIN(3, 19, 3, 0x204, 0x5F4), + MX51_PIN_AUD3_BB_CK = _MXC_BUILD_GPIO_PIN(3, 20, 3, 0x208, 0x5F8), + MX51_PIN_AUD3_BB_FS = _MXC_BUILD_GPIO_PIN(3, 21, 3, 0x20C, 0x5FC), + MX51_PIN_CSPI1_MOSI = _MXC_BUILD_GPIO_PIN(3, 22, 3, 0x210, 0x600), + MX51_PIN_CSPI1_MISO = _MXC_BUILD_GPIO_PIN(3, 23, 3, 0x214, 0x604), + MX51_PIN_CSPI1_SS0 = _MXC_BUILD_GPIO_PIN(3, 24, 3, 0x218, 0x608), + MX51_PIN_CSPI1_SS1 = _MXC_BUILD_GPIO_PIN(3, 25, 3, 0x21C, 0x60C), + MX51_PIN_CSPI1_RDY = _MXC_BUILD_GPIO_PIN(3, 26, 3, 0x220, 0x610), + MX51_PIN_CSPI1_SCLK = _MXC_BUILD_GPIO_PIN(3, 27, 3, 0x224, 0x614), + MX51_PIN_UART1_RXD = _MXC_BUILD_GPIO_PIN(3, 28, 3, 0x228, 0x618), + MX51_PIN_UART1_TXD = _MXC_BUILD_GPIO_PIN(3, 29, 3, 0x22C, 0x61C), + MX51_PIN_UART1_RTS = _MXC_BUILD_GPIO_PIN(3, 30, 3, 0x230, 0x620), + MX51_PIN_UART1_CTS = _MXC_BUILD_GPIO_PIN(3, 31, 3, 0x234, 0x624), + MX51_PIN_UART2_RXD = _MXC_BUILD_GPIO_PIN(0, 20, 3, 0x238, 0x628), + MX51_PIN_UART2_TXD = _MXC_BUILD_GPIO_PIN(0, 21, 3, 0x23C, 0x62C), + MX51_PIN_UART3_RXD = _MXC_BUILD_GPIO_PIN(0, 22, 3, 0x240, 0x630), + MX51_PIN_UART3_TXD = _MXC_BUILD_GPIO_PIN(0, 23, 3, 0x244, 0x634), + MX51_PIN_OWIRE_LINE = _MXC_BUILD_GPIO_PIN(0, 24, 3, 0x248, 0x638), + MX51_PIN_KEY_ROW0 = _MXC_BUILD_NON_GPIO_PIN(0x24C, 0x63C), + MX51_PIN_KEY_ROW1 = _MXC_BUILD_NON_GPIO_PIN(0x250, 0x640), + MX51_PIN_KEY_ROW2 = _MXC_BUILD_NON_GPIO_PIN(0x254, 0x644), + MX51_PIN_KEY_ROW3 = _MXC_BUILD_NON_GPIO_PIN(0x258, 0x648), + MX51_PIN_KEY_COL0 = _MXC_BUILD_NON_GPIO_PIN(0x25C, 0x64C), + MX51_PIN_KEY_COL1 = _MXC_BUILD_NON_GPIO_PIN(0x260, 0x650), + MX51_PIN_KEY_COL2 = _MXC_BUILD_NON_GPIO_PIN(0x264, 0x654), + MX51_PIN_KEY_COL3 = _MXC_BUILD_NON_GPIO_PIN(0x268, 0x658), + MX51_PIN_KEY_COL4 = _MXC_BUILD_NON_GPIO_PIN(0x26C, 0x65C), + MX51_PIN_KEY_COL5 = _MXC_BUILD_NON_GPIO_PIN(0x270, 0x660), + MX51_PIN_USBH1_CLK = _MXC_BUILD_GPIO_PIN(0, 25, 2, 0x278, 0x678), + MX51_PIN_USBH1_DIR = _MXC_BUILD_GPIO_PIN(0, 26, 2, 0x27C, 0x67C), + MX51_PIN_USBH1_STP = _MXC_BUILD_GPIO_PIN(0, 27, 2, 0x280, 0x680), + MX51_PIN_USBH1_NXT = _MXC_BUILD_GPIO_PIN(0, 28, 2, 0x284, 0x684), + MX51_PIN_USBH1_DATA0 = _MXC_BUILD_GPIO_PIN(0, 11, 2, 0x288, 0x688), + MX51_PIN_USBH1_DATA1 = _MXC_BUILD_GPIO_PIN(0, 12, 2, 0x28C, 0x68C), + MX51_PIN_USBH1_DATA2 = _MXC_BUILD_GPIO_PIN(0, 13, 2, 0x290, 0x690), + MX51_PIN_USBH1_DATA3 = _MXC_BUILD_GPIO_PIN(0, 14, 2, 0x294, 0x694), + MX51_PIN_USBH1_DATA4 = _MXC_BUILD_GPIO_PIN(0, 15, 2, 0x298, 0x698), + MX51_PIN_USBH1_DATA5 = _MXC_BUILD_GPIO_PIN(0, 16, 2, 0x29C, 0x69C), + MX51_PIN_USBH1_DATA6 = _MXC_BUILD_GPIO_PIN(0, 17, 2, 0x2A0, 0x6A0), + MX51_PIN_USBH1_DATA7 = _MXC_BUILD_GPIO_PIN(0, 18, 2, 0x2A4, 0x6A4), + MX51_PIN_DI1_PIN11 = _MXC_BUILD_GPIO_PIN(2, 0, 4, 0x2A8, 0x6A8), + MX51_PIN_DI1_PIN12 = _MXC_BUILD_GPIO_PIN(2, 1, 4, 0x2AC, 0x6AC), + MX51_PIN_DI1_PIN13 = _MXC_BUILD_GPIO_PIN(2, 2, 4, 0x2B0, 0x6B0), + MX51_PIN_DI1_D0_CS = _MXC_BUILD_GPIO_PIN(2, 3, 4, 0x2B4, 0x6B4), + MX51_PIN_DI1_D1_CS = _MXC_BUILD_GPIO_PIN(2, 4, 4, 0x2B8, 0x6B8), + MX51_PIN_DISPB2_SER_DIN = _MXC_BUILD_GPIO_PIN(2, 5, 4, 0x2BC, 0x6BC), + MX51_PIN_DISPB2_SER_DIO = _MXC_BUILD_GPIO_PIN(2, 6, 4, 0x2C0, 0x6C0), + MX51_PIN_DISPB2_SER_CLK = _MXC_BUILD_GPIO_PIN(2, 7, 4, 0x2C4, 0x6C4), + MX51_PIN_DISPB2_SER_RS = _MXC_BUILD_GPIO_PIN(2, 8, 4, 0x2C8, 0x6C8), + MX51_PIN_DISP1_DAT0 = _MXC_BUILD_NON_GPIO_PIN(0x2CC, 0x6CC), + MX51_PIN_DISP1_DAT1 = _MXC_BUILD_NON_GPIO_PIN(0x2D0, 0x6D0), + MX51_PIN_DISP1_DAT2 = _MXC_BUILD_NON_GPIO_PIN(0x2D4, 0x6D4), + MX51_PIN_DISP1_DAT3 = _MXC_BUILD_NON_GPIO_PIN(0x2D8, 0x6D8), + MX51_PIN_DISP1_DAT4 = _MXC_BUILD_NON_GPIO_PIN(0x2DC, 0x6DC), + MX51_PIN_DISP1_DAT5 = _MXC_BUILD_NON_GPIO_PIN(0x2E0, 0x6E0), + MX51_PIN_DISP1_DAT6 = _MXC_BUILD_NON_GPIO_PIN(0x2E4, 0x6E4), + MX51_PIN_DISP1_DAT7 = _MXC_BUILD_NON_GPIO_PIN(0x2E8, 0x6E8), + MX51_PIN_DISP1_DAT8 = _MXC_BUILD_NON_GPIO_PIN(0x2EC, 0x6EC), + MX51_PIN_DISP1_DAT9 = _MXC_BUILD_NON_GPIO_PIN(0x2F0, 0x6F0), + MX51_PIN_DISP1_DAT10 = _MXC_BUILD_NON_GPIO_PIN(0x2F4, 0x6F4), + MX51_PIN_DISP1_DAT11 = _MXC_BUILD_NON_GPIO_PIN(0x2F8, 0x6F8), + MX51_PIN_DISP1_DAT12 = _MXC_BUILD_NON_GPIO_PIN(0x2FC, 0x6FC), + MX51_PIN_DISP1_DAT13 = _MXC_BUILD_NON_GPIO_PIN(0x300, 0x700), + MX51_PIN_DISP1_DAT14 = _MXC_BUILD_NON_GPIO_PIN(0x304, 0x704), + MX51_PIN_DISP1_DAT15 = _MXC_BUILD_NON_GPIO_PIN(0x308, 0x708), + MX51_PIN_DISP1_DAT16 = _MXC_BUILD_NON_GPIO_PIN(0x30C, 0x70C), + MX51_PIN_DISP1_DAT17 = _MXC_BUILD_NON_GPIO_PIN(0x310, 0x710), + MX51_PIN_DISP1_DAT18 = _MXC_BUILD_NON_GPIO_PIN(0x314, 0x714), + MX51_PIN_DISP1_DAT19 = _MXC_BUILD_NON_GPIO_PIN(0x318, 0x718), + MX51_PIN_DISP1_DAT20 = _MXC_BUILD_NON_GPIO_PIN(0x31C, 0x71C), + MX51_PIN_DISP1_DAT21 = _MXC_BUILD_NON_GPIO_PIN(0x320, 0x720), + MX51_PIN_DISP1_DAT22 = _MXC_BUILD_NON_GPIO_PIN(0x324, 0x724), + MX51_PIN_DISP1_DAT23 = _MXC_BUILD_NON_GPIO_PIN(0x328, 0x728), + MX51_PIN_DI1_PIN3 = _MXC_BUILD_NON_GPIO_PIN(0x32C, 0x72C), + MX51_PIN_DI1_PIN2 = _MXC_BUILD_NON_GPIO_PIN(0x330, 0x734), + MX51_PIN_DI_GP1 = _MXC_BUILD_NON_GPIO_PIN(0x334, 0x73C), + MX51_PIN_DI_GP2 = _MXC_BUILD_NON_GPIO_PIN(0x338, 0x740), + MX51_PIN_DI_GP3 = _MXC_BUILD_NON_GPIO_PIN(0x33C, 0x744), + MX51_PIN_DI2_PIN4 = _MXC_BUILD_NON_GPIO_PIN(0x340, 0x748), + MX51_PIN_DI2_PIN2 = _MXC_BUILD_NON_GPIO_PIN(0x344, 0x74C), + MX51_PIN_DI2_PIN3 = _MXC_BUILD_NON_GPIO_PIN(0x348, 0x750), + MX51_PIN_DI2_DISP_CLK = _MXC_BUILD_NON_GPIO_PIN(0x34C, 0x754), + MX51_PIN_DI_GP4 = _MXC_BUILD_NON_GPIO_PIN(0x350, 0x758), + MX51_PIN_DISP2_DAT0 = _MXC_BUILD_NON_GPIO_PIN(0x354, 0x75C), + MX51_PIN_DISP2_DAT1 = _MXC_BUILD_NON_GPIO_PIN(0x358, 0x760), + MX51_PIN_DISP2_DAT2 = _MXC_BUILD_NON_GPIO_PIN(0x35C, 0x764), + MX51_PIN_DISP2_DAT3 = _MXC_BUILD_NON_GPIO_PIN(0x360, 0x768), + MX51_PIN_DISP2_DAT4 = _MXC_BUILD_NON_GPIO_PIN(0x364, 0x76C), + MX51_PIN_DISP2_DAT5 = _MXC_BUILD_NON_GPIO_PIN(0x368, 0x770), + MX51_PIN_DISP2_DAT6 = _MXC_BUILD_GPIO_PIN(0, 19, 5, 0x36C, 0x774), + MX51_PIN_DISP2_DAT7 = _MXC_BUILD_GPIO_PIN(0, 29, 5, 0x370, 0x778), + MX51_PIN_DISP2_DAT8 = _MXC_BUILD_GPIO_PIN(0, 30, 5, 0x374, 0x77C), + MX51_PIN_DISP2_DAT9 = _MXC_BUILD_GPIO_PIN(0, 31, 5, 0x378, 0x780), + MX51_PIN_DISP2_DAT10 = _MXC_BUILD_NON_GPIO_PIN(0x37C, 0x784), + MX51_PIN_DISP2_DAT11 = _MXC_BUILD_NON_GPIO_PIN(0x380, 0x788), + MX51_PIN_DISP2_DAT12 = _MXC_BUILD_NON_GPIO_PIN(0x384, 0x78C), + MX51_PIN_DISP2_DAT13 = _MXC_BUILD_NON_GPIO_PIN(0x388, 0x790), + MX51_PIN_DISP2_DAT14 = _MXC_BUILD_NON_GPIO_PIN(0x38C, 0x794), + MX51_PIN_DISP2_DAT15 = _MXC_BUILD_NON_GPIO_PIN(0x390, 0x798), + MX51_PIN_SD1_CMD = _MXC_BUILD_NON_GPIO_PIN(0x394, 0x79C), + MX51_PIN_SD1_CLK = _MXC_BUILD_NON_GPIO_PIN(0x398, 0x7A0), + MX51_PIN_SD1_DATA0 = _MXC_BUILD_NON_GPIO_PIN(0x39C, 0x7A4), + MX51_PIN_SD1_DATA1 = _MXC_BUILD_NON_GPIO_PIN(0x3A0, 0x7A8), + MX51_PIN_SD1_DATA2 = _MXC_BUILD_NON_GPIO_PIN(0x3A4, 0x7AC), + MX51_PIN_SD1_DATA3 = _MXC_BUILD_NON_GPIO_PIN(0x3A8, 0x7B0), + MX51_PIN_GPIO1_0 = _MXC_BUILD_GPIO_PIN(0, 0, 1, 0x3AC, 0x7B4), + MX51_PIN_GPIO1_1 = _MXC_BUILD_GPIO_PIN(0, 1, 1, 0x3B0, 0x7B8), + MX51_PIN_SD2_CMD = _MXC_BUILD_NON_GPIO_PIN(0x3B4, 0x7BC), + MX51_PIN_SD2_CLK = _MXC_BUILD_NON_GPIO_PIN(0x3B8, 0x7C0), + MX51_PIN_SD2_DATA0 = _MXC_BUILD_NON_GPIO_PIN(0x3BC, 0x7C4), + MX51_PIN_SD2_DATA1 = _MXC_BUILD_NON_GPIO_PIN(0x3C0, 0x7C8), + MX51_PIN_SD2_DATA2 = _MXC_BUILD_NON_GPIO_PIN(0x3C4, 0x7CC), + MX51_PIN_SD2_DATA3 = _MXC_BUILD_NON_GPIO_PIN(0x3C8, 0x7D0), + MX51_PIN_GPIO1_2 = _MXC_BUILD_GPIO_PIN(0, 2, 0, 0x3CC, 0x7D4), + MX51_PIN_GPIO1_3 = _MXC_BUILD_GPIO_PIN(0, 3, 0, 0x3D0, 0x7D8), + MX51_PIN_PMIC_INT_REQ = _MXC_BUILD_NON_GPIO_PIN(0x3D4, 0x7FC), + MX51_PIN_GPIO1_4 = _MXC_BUILD_GPIO_PIN(0, 4, 0, 0x3D8, 0x804), + MX51_PIN_GPIO1_5 = _MXC_BUILD_GPIO_PIN(0, 5, 0, 0x3DC, 0x808), + MX51_PIN_GPIO1_6 = _MXC_BUILD_GPIO_PIN(0, 6, 0, 0x3E0, 0x80C), + MX51_PIN_GPIO1_7 = _MXC_BUILD_GPIO_PIN(0, 7, 0, 0x3E4, 0x810), + MX51_PIN_GPIO1_8 = _MXC_BUILD_GPIO_PIN(0, 8, 0, 0x3E8, 0x814), + MX51_PIN_GPIO1_9 = _MXC_BUILD_GPIO_PIN(0, 9, 0, 0x3EC, 0x818), +}; + +#endif /* __ASSEMBLY__ */ +#endif /* __ASM_ARCH_MXC_MX51_PINS_H__ */ diff --git a/arch/arm/mach-mx51/pm.c b/arch/arm/mach-mx51/pm.c new file mode 100644 index 000000000000..56f809dbabff --- /dev/null +++ b/arch/arm/mach-mx51/pm.c @@ -0,0 +1,168 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/suspend.h> +#include <asm/cacheflush.h> +#include <asm/tlb.h> +#include <asm/mach/map.h> +#include <mach/hardware.h> +#include "crm_regs.h" + +static struct device *pm_dev; +struct clk *gpc_dvfs_clk; +extern void cpu_do_suspend_workaround(u32 sdclk_iomux_addr); +extern void cpu_cortexa8_do_idle(void *); + +extern int iram_ready; +void *suspend_iram_base; +void (*suspend_in_iram)(void *sdclk_iomux_addr) = NULL; + +static int mx51_suspend_enter(suspend_state_t state) +{ + void __iomem *sdclk_iomux_addr = IO_ADDRESS(IOMUXC_BASE_ADDR + 0x4b8); + + if (gpc_dvfs_clk == NULL) + gpc_dvfs_clk = clk_get(NULL, "gpc_dvfs_clk"); + /* gpc clock is needed for SRPG */ + clk_enable(gpc_dvfs_clk); + switch (state) { + case PM_SUSPEND_MEM: + mxc_cpu_lp_set(STOP_POWER_OFF); + break; + case PM_SUSPEND_STANDBY: + mxc_cpu_lp_set(WAIT_UNCLOCKED_POWER_OFF); + break; + default: + return -EINVAL; + } + + if (tzic_enable_wake(0) != 0) + return -EAGAIN; + + if (state == PM_SUSPEND_MEM) { + local_flush_tlb_all(); + flush_cache_all(); + + /* Run the suspend code from iRAM. */ + suspend_in_iram(sdclk_iomux_addr); + + /*clear the EMPGC0/1 bits */ + __raw_writel(0, MXC_SRPG_EMPGC0_SRPGCR); + __raw_writel(0, MXC_SRPG_EMPGC1_SRPGCR); + } else { + if ((mxc_cpu_is_rev(CHIP_REV_2_0)) < 0) { + /* do cpu_idle_workaround */ + u32 l2_iram_addr = IDLE_IRAM_BASE_ADDR; + if (!iram_ready) + return 0; + if (l2_iram_addr > 0x1FFE8000) + cpu_cortexa8_do_idle(IO_ADDRESS(l2_iram_addr)); + } else { + cpu_do_idle(); + } + } + clk_disable(gpc_dvfs_clk); + + return 0; +} + +/* + * Called after processes are frozen, but before we shut down devices. + */ +static int mx51_suspend_prepare(void) +{ + return 0; +} + +/* + * Called before devices are re-setup. + */ +static void mx51_suspend_finish(void) +{ +} + +/* + * Called after devices are re-setup, but before processes are thawed. + */ +static void mx51_suspend_end(void) +{ +} + +static int mx51_pm_valid(suspend_state_t state) +{ + return (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX); +} + +struct platform_suspend_ops mx51_suspend_ops = { + .valid = mx51_pm_valid, + .prepare = mx51_suspend_prepare, + .enter = mx51_suspend_enter, + .finish = mx51_suspend_finish, + .end = mx51_suspend_end, +}; + + +static int __devinit mx51_pm_probe(struct platform_device *pdev) +{ + pm_dev = &pdev->dev; + return 0; +} + +static struct platform_driver mx51_pm_driver = { + .driver = { + .name = "mx51_pm", + }, + .probe = mx51_pm_probe, +}; + +static int __init pm_init(void) +{ + pr_info("Static Power Management for Freescale i.MX51\n"); + if (platform_driver_register(&mx51_pm_driver) != 0) { + printk(KERN_ERR "mx51_pm_driver register failed\n"); + return -ENODEV; + } + suspend_set_ops(&mx51_suspend_ops); + /* Move suspend routine into iRAM */ + suspend_iram_base = IO_ADDRESS(SUSPEND_IRAM_BASE_ADDR); + memcpy(suspend_iram_base, cpu_do_suspend_workaround, SZ_4K); + /* Need to remap the area here since we want the memory region + to be executable. */ + suspend_iram_base = __arm_ioremap(SUSPEND_IRAM_BASE_ADDR, SZ_4K, + MT_HIGH_VECTORS); + suspend_in_iram = (void *)suspend_iram_base; + + printk(KERN_INFO "PM driver module loaded\n"); + + return 0; +} + + +static void __exit pm_cleanup(void) +{ + /* Unregister the device structure */ + platform_driver_unregister(&mx51_pm_driver); +} + +module_init(pm_init); +module_exit(pm_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("PM driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-mx51/sdma_script_code.h b/arch/arm/mach-mx51/sdma_script_code.h new file mode 100644 index 000000000000..9b8de26ab74a --- /dev/null +++ b/arch/arm/mach-mx51/sdma_script_code.h @@ -0,0 +1,170 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All 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 */ + +/*! + * @file sdma_script_code.h + * @brief This file contains functions of SDMA scripts code initialization + * + * The file was generated automatically. Based on sdma scripts library. + * + * @ingroup SDMA + */ +/******************************************************************************* + + SDMA RELEASE LABEL: "SS15_ELVIS" + +*******************************************************************************/ + +#ifndef __SDMA_SCRIPT_CODE_H__ +#define __SDMA_SCRIPT_CODE_H__ + +/*! +* SDMA ROM scripts start addresses and sizes +*/ + +#define start_ADDR 0 +#define start_SIZE 24 + +#define core_ADDR 80 +#define core_SIZE 232 + +#define common_ADDR 312 +#define common_SIZE 330 + +#define ap_2_ap_ADDR 642 +#define ap_2_ap_SIZE 41 + +#define app_2_mcu_ADDR 683 +#define app_2_mcu_SIZE 64 + +#define mcu_2_app_ADDR 747 +#define mcu_2_app_SIZE 70 + +#define uart_2_mcu_ADDR 817 +#define uart_2_mcu_SIZE 75 + +#define shp_2_mcu_ADDR 892 +#define shp_2_mcu_SIZE 69 + +#define mcu_2_shp_ADDR 961 +#define mcu_2_shp_SIZE 72 + +#define app_2_per_ADDR 1033 +#define app_2_per_SIZE 66 + +#define per_2_app_ADDR 1099 +#define per_2_app_SIZE 74 + +#define per_2_shp_ADDR 1173 +#define per_2_shp_SIZE 78 + +#define shp_2_per_ADDR 1251 +#define shp_2_per_SIZE 72 + +#define uartsh_2_mcu_ADDR 1323 +#define uartsh_2_mcu_SIZE 69 + +#define mcu_2_ata_ADDR 1392 +#define mcu_2_ata_SIZE 81 + +#define ata_2_mcu_ADDR 1473 +#define ata_2_mcu_SIZE 96 + +#define loop_DMAs_routines_ADDR 1569 +#define loop_DMAs_routines_SIZE 227 + +#define test_ADDR 1796 +#define test_SIZE 63 + +#define signature_ADDR 1023 +#define signature_SIZE 1 + +/*! +* SDMA RAM scripts start addresses and sizes +*/ + +#define ext_mem__ipu_ram_ADDR 6144 +#define ext_mem__ipu_ram_SIZE 123 + +#define mcu_2_spdif_ADDR 6267 +#define mcu_2_spdif_SIZE 59 + +#define uart_2_per_ADDR 6326 +#define uart_2_per_SIZE 73 + +#define uartsh_2_per_ADDR 6399 +#define uartsh_2_per_SIZE 67 + +/*! +* SDMA RAM image start address and size +*/ + +#define RAM_CODE_START_ADDR 6144 +#define RAM_CODE_SIZE 322 + +/*! +* Buffer that holds the SDMA RAM image +*/ +__attribute__ ((__aligned__(4))) +#ifndef CONFIG_XIP_KERNEL +const +#endif +static const short sdma_code[] = { + 0x0e70, 0x0611, 0x5616, 0xc13c, 0x7d2a, 0x5ade, 0x008e, 0xc14e, + 0x7c26, 0x5be0, 0x5ef0, 0x5ce8, 0x0688, 0x08ff, 0x0011, 0x28ff, + 0x00bc, 0x53f6, 0x05df, 0x7d0b, 0x6dc5, 0x03df, 0x7d03, 0x6bd5, + 0xd84f, 0x982b, 0x6b05, 0xc681, 0x7e27, 0x7f29, 0x982b, 0x6d01, + 0x03df, 0x7d05, 0x6bd5, 0xc6ab, 0x7e18, 0x7f1a, 0x982b, 0x6b05, + 0xc621, 0x7e07, 0x7f06, 0x52de, 0x53e6, 0xc159, 0x7dd7, 0x0200, + 0x9803, 0x0007, 0x6004, 0x680c, 0x53f6, 0x028e, 0x00a3, 0xc256, + 0x048b, 0x0498, 0x0454, 0x068a, 0x982b, 0x0207, 0x680c, 0x6ddf, + 0x0107, 0x68ff, 0x60d0, 0x9834, 0x0207, 0x68ff, 0x6d28, 0x0107, + 0x6004, 0x680c, 0x9834, 0x0007, 0x68ff, 0x60d0, 0x9834, 0x0288, + 0x03a5, 0x3b03, 0x3d03, 0x4d00, 0x7d0a, 0x0804, 0x00a5, 0x00da, + 0x7d1a, 0x02a0, 0x7b01, 0x65d8, 0x7eee, 0x65ff, 0x7eec, 0x0804, + 0x02d0, 0x7d11, 0x4b00, 0x7c0f, 0x008a, 0x3003, 0x6dcf, 0x6bdf, + 0x0015, 0x0015, 0x7b02, 0x65d8, 0x0000, 0x7edd, 0x63ff, 0x7edb, + 0x3a03, 0x6dcd, 0x6bdd, 0x008a, 0x7b02, 0x65d8, 0x0000, 0x7ed3, + 0x65ff, 0x7ed1, 0x0006, 0xc1d9, 0xc1e3, 0x57db, 0x52f3, 0x6a01, + 0x008f, 0x00d5, 0x7d01, 0x008d, 0x05a0, 0x5deb, 0x56fb, 0x0478, + 0x7d28, 0x0479, 0x7c16, 0x0015, 0x0015, 0x0388, 0x620a, 0x0808, + 0x7801, 0x0217, 0x5a06, 0x7f1d, 0x620a, 0x0808, 0x7801, 0x0217, + 0x5a26, 0x7f17, 0x2301, 0x4b00, 0x7cf1, 0x0b70, 0x0311, 0x5313, + 0x98aa, 0x0015, 0x0015, 0x0015, 0x7804, 0x620b, 0x5a06, 0x620b, + 0x5a26, 0x7c07, 0x0000, 0x55eb, 0x4d00, 0x7d06, 0xc1fa, 0x57db, + 0x9880, 0x0007, 0x680c, 0xc213, 0xc20a, 0x987d, 0xc1e3, 0x57db, + 0x52f3, 0x6ad5, 0x56fb, 0x028e, 0x1a94, 0x6ac3, 0x62c8, 0x0269, + 0x7d1e, 0x1e94, 0x6ee3, 0x62d0, 0x5aeb, 0x62c8, 0x0248, 0x6ed3, + 0x6ac8, 0x2694, 0x52eb, 0x6ad5, 0x6ee3, 0x62c8, 0x026e, 0x7d27, + 0x6ac8, 0x7f23, 0x2501, 0x4d00, 0x7d26, 0x028e, 0x1a98, 0x6ac3, + 0x62c8, 0x6ec3, 0x0260, 0x7df1, 0x62d0, 0xc27a, 0x98fb, 0x6ee3, + 0x008f, 0x2001, 0x00d5, 0x7d01, 0x008d, 0x05a0, 0x62c8, 0x026e, + 0x7d0e, 0x6ac8, 0x7f0a, 0x2001, 0x7cf9, 0x6add, 0x7f06, 0x0000, + 0x4d00, 0x7d09, 0xc1fa, 0x57db, 0x98ba, 0x0007, 0x6aff, 0x62d0, + 0xc27a, 0x0458, 0x0454, 0x6add, 0x7ff8, 0xc20a, 0x98b7, 0xc1d9, + 0xc1e3, 0x57db, 0x52f3, 0x6ad5, 0x56fb, 0x028e, 0x1a94, 0x5202, + 0x0269, 0x7d17, 0x1e94, 0x5206, 0x0248, 0x5a06, 0x2694, 0x5206, + 0x026e, 0x7d26, 0x6ac8, 0x7f22, 0x2501, 0x4d00, 0x7d27, 0x028e, + 0x1a98, 0x5202, 0x0260, 0x7df3, 0x6add, 0x7f18, 0x62d0, 0xc27a, + 0x993e, 0x008f, 0x2001, 0x00d5, 0x7d01, 0x008d, 0x05a0, 0x5206, + 0x026e, 0x7d0e, 0x6ac8, 0x7f0a, 0x2001, 0x7cf9, 0x6add, 0x7f06, + 0x0000, 0x4d00, 0x7d0b, 0xc1fa, 0x57db, 0x9904, 0x0007, 0x6aff, + 0x6add, 0x7ffc, 0x62d0, 0xc27a, 0x0458, 0x0454, 0x6add, 0x7ff6, + 0xc20a, 0x9901 +}; +#endif diff --git a/arch/arm/mach-mx51/serial.c b/arch/arm/mach-mx51/serial.c new file mode 100644 index 000000000000..89cb943b396f --- /dev/null +++ b/arch/arm/mach-mx51/serial.c @@ -0,0 +1,169 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @file mach-mx51/serial.c + * + * @brief This file contains the UART initiliazation. + * + * @ingroup MSL_MX51 + */ +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/serial.h> +#include <mach/hardware.h> +#include <mach/mxc_uart.h> +#include <mach/spba.h> +#include "serial.h" +#include "board-mx51_3stack.h" + +#if defined(CONFIG_SERIAL_MXC) || defined(CONFIG_SERIAL_MXC_MODULE) + +/*! + * This is an array where each element holds information about a UART port, + * like base address of the UART, interrupt numbers etc. This structure is + * passed to the serial_core.c file. Based on which UART is used, the core file + * passes back the appropriate port structure as an argument to the control + * functions. + */ +static uart_mxc_port mxc_ports[] = { + [0] = { + .port = { + .membase = (void *)IO_ADDRESS(UART1_BASE_ADDR), + .mapbase = UART1_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART1_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 0, + }, + .ints_muxed = UART1_MUX_INTS, + .irqs = {UART1_INT2, UART1_INT3}, + .mode = UART1_MODE, + .ir_mode = UART1_IR, + .enabled = UART1_ENABLED, + .hardware_flow = UART1_HW_FLOW, + .cts_threshold = UART1_UCR4_CTSTL, + .dma_enabled = UART1_DMA_ENABLE, + .dma_rxbuf_size = UART1_DMA_RXBUFSIZE, + .rx_threshold = UART1_UFCR_RXTL, + .tx_threshold = UART1_UFCR_TXTL, + .shared = UART1_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART1_TX, + .dma_rx_id = MXC_DMA_UART1_RX, + .rxd_mux = MXC_UART_RXDMUX, + }, + [1] = { + .port = { + .membase = (void *)IO_ADDRESS(UART2_BASE_ADDR), + .mapbase = UART2_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART2_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 1, + }, + .ints_muxed = UART2_MUX_INTS, + .irqs = {UART2_INT2, UART2_INT3}, + .mode = UART2_MODE, + .ir_mode = UART2_IR, + .enabled = UART2_ENABLED, + .hardware_flow = UART2_HW_FLOW, + .cts_threshold = UART2_UCR4_CTSTL, + .dma_enabled = UART2_DMA_ENABLE, + .dma_rxbuf_size = UART2_DMA_RXBUFSIZE, + .rx_threshold = UART2_UFCR_RXTL, + .tx_threshold = UART2_UFCR_TXTL, + .shared = UART2_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART2_TX, + .dma_rx_id = MXC_DMA_UART2_RX, + .rxd_mux = MXC_UART_RXDMUX, + }, + [2] = { + .port = { + .membase = (void *)IO_ADDRESS(UART3_BASE_ADDR), + .mapbase = UART3_BASE_ADDR, + .iotype = SERIAL_IO_MEM, + .irq = UART3_INT1, + .fifosize = 32, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 2, + }, + .ints_muxed = UART3_MUX_INTS, + .irqs = {UART3_INT2, UART3_INT3}, + .mode = UART3_MODE, + .ir_mode = UART3_IR, + .enabled = UART3_ENABLED, + .hardware_flow = UART3_HW_FLOW, + .cts_threshold = UART3_UCR4_CTSTL, + .dma_enabled = UART3_DMA_ENABLE, + .dma_rxbuf_size = UART3_DMA_RXBUFSIZE, + .rx_threshold = UART3_UFCR_RXTL, + .tx_threshold = UART3_UFCR_TXTL, + .shared = UART3_SHARED_PERI, + .dma_tx_id = MXC_DMA_UART3_TX, + .dma_rx_id = MXC_DMA_UART3_RX, + .rxd_mux = MXC_UART_RXDMUX, + }, +}; + +static struct platform_device mxc_uart_device1 = { + .name = "mxcintuart", + .id = 0, + .dev = { + .platform_data = &mxc_ports[0], + }, +}; + +static struct platform_device mxc_uart_device2 = { + .name = "mxcintuart", + .id = 1, + .dev = { + .platform_data = &mxc_ports[1], + }, +}; + +static struct platform_device mxc_uart_device3 = { + .name = "mxcintuart", + .id = 2, + .dev = { + .platform_data = &mxc_ports[2], + }, +}; + +static int __init mxc_init_uart(void) +{ + /* Register all the MXC UART platform device structures */ + platform_device_register(&mxc_uart_device1); + platform_device_register(&mxc_uart_device2); + + /* Grab ownership of shared UARTs 3 and 4, only when enabled */ +#if UART3_ENABLED == 1 +#if UART3_DMA_ENABLE == 1 + spba_take_ownership(UART3_SHARED_PERI, (SPBA_MASTER_A | SPBA_MASTER_C)); +#else + spba_take_ownership(UART3_SHARED_PERI, SPBA_MASTER_A); +#endif /* UART3_DMA_ENABLE */ + platform_device_register(&mxc_uart_device3); +#endif /* UART3_ENABLED */ + + return 0; +} + +#else +static int __init mxc_init_uart(void) +{ + return 0; +} +#endif + +arch_initcall(mxc_init_uart); diff --git a/arch/arm/mach-mx51/serial.h b/arch/arm/mach-mx51/serial.h new file mode 100644 index 000000000000..ff4928c3b002 --- /dev/null +++ b/arch/arm/mach-mx51/serial.h @@ -0,0 +1,127 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ARCH_ARM_MACH_MX51_SERIAL_H__ +#define __ARCH_ARM_MACH_MX51_SERIAL_H__ + +#include <mach/mxc_uart.h> + +/* UART 1 configuration */ +/*! + * This option allows to choose either an interrupt-driven software controlled + * hardware flow control (set this option to 0) or hardware-driven hardware + * flow control (set this option to 1). + */ +/* UART used as wakeup source */ +#define UART1_HW_FLOW 0 +/*! + * This specifies the threshold at which the CTS pin is deasserted by the + * RXFIFO. Set this value in Decimal to anything from 0 to 32 for + * hardware-driven hardware flow control. Read the HW spec while specifying + * this value. When using interrupt-driven software controlled hardware + * flow control set this option to -1. + */ +#define UART1_UCR4_CTSTL 16 +/*! + * This is option to enable (set this option to 1) or disable DMA data transfer + */ +#define UART1_DMA_ENABLE 0 +/*! + * Specify the size of the DMA receive buffer. The minimum buffer size is 512 + * bytes. The buffer size should be a multiple of 256. + */ +#define UART1_DMA_RXBUFSIZE 1024 +/*! + * Specify the MXC UART's Receive Trigger Level. This controls the threshold at + * which a maskable interrupt is generated by the RxFIFO. Set this value in + * Decimal to anything from 0 to 32. Read the HW spec while specifying this + * value. + */ +#define UART1_UFCR_RXTL 16 +/*! + * Specify the MXC UART's Transmit Trigger Level. This controls the threshold at + * which a maskable interrupt is generated by the TxFIFO. Set this value in + * Decimal to anything from 0 to 32. Read the HW spec while specifying this + * value. + */ +#define UART1_UFCR_TXTL 16 +/* UART 2 configuration */ +#define UART2_HW_FLOW 0 +#define UART2_UCR4_CTSTL -1 +#define UART2_DMA_ENABLE 0 +#define UART2_DMA_RXBUFSIZE 512 +#define UART2_UFCR_RXTL 16 +#define UART2_UFCR_TXTL 16 +/* UART 3 configuration */ +#define UART3_HW_FLOW 1 +#define UART3_UCR4_CTSTL 16 +#define UART3_DMA_ENABLE 1 +#define UART3_DMA_RXBUFSIZE 1024 +#define UART3_UFCR_RXTL 16 +#define UART3_UFCR_TXTL 16 +/* + * UART Chip level Configuration that a user may not have to edit. These + * configuration vary depending on how the UART module is integrated with + * the ARM core + */ +/* + * Is the MUXED interrupt output sent to the ARM core + */ +#define INTS_NOTMUXED 0 +#define INTS_MUXED 1 +/* UART 1 configuration */ +/*! + * This define specifies whether the muxed ANDed interrupt line or the + * individual interrupts from the UART port is integrated with the ARM core. + * There exists a define like this for each UART port. Valid values that can + * be used are \b INTS_NOTMUXED or \b INTS_MUXED. + */ +#define UART1_MUX_INTS INTS_MUXED +/*! + * This define specifies the transmitter interrupt number or the interrupt + * number of the ANDed interrupt in case the interrupts are muxed. There exists + * a define like this for each UART port. + */ +#define UART1_INT1 MXC_INT_UART1 +/*! + * This define specifies the receiver interrupt number. If the interrupts of + * the UART are muxed, then we specify here a dummy value -1. There exists a + * define like this for each UART port. + */ +#define UART1_INT2 -1 +/*! + * This specifies the master interrupt number. If the interrupts of the UART + * are muxed, then we specify here a dummy value of -1. There exists a define + * like this for each UART port. + */ +#define UART1_INT3 -1 +/*! + * This specifies if the UART is a shared peripheral. It holds the shared + * peripheral number if it is shared or -1 if it is not shared. There exists + * a define like this for each UART port. + */ +#define UART1_SHARED_PERI -1 +/* UART 2 configuration */ +#define UART2_MUX_INTS INTS_MUXED +#define UART2_INT1 MXC_INT_UART2 +#define UART2_INT2 -1 +#define UART2_INT3 -1 +#define UART2_SHARED_PERI -1 +/* UART 3 configuration */ +#define UART3_MUX_INTS INTS_MUXED +#define UART3_INT1 MXC_INT_UART3 +#define UART3_INT2 -1 +#define UART3_INT3 -1 +#define UART3_SHARED_PERI SPBA_UART3 + +#endif /* __ARCH_ARM_MACH_MX51_SERIAL_H__ */ diff --git a/arch/arm/mach-mx51/suspend.S b/arch/arm/mach-mx51/suspend.S new file mode 100644 index 000000000000..5cfd9be91eab --- /dev/null +++ b/arch/arm/mach-mx51/suspend.S @@ -0,0 +1,153 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/linkage.h> + +#define ARM_CTRL_DCACHE 1 << 2 +#define ARM_CTRL_ICACHE 1 << 12 +#define ARM_AUXCR_L2EN 1 << 1 + + +/* + * cpu_do_suspend_workaround() + * + * Suspend the processor (eg, wait for interrupt). + * + * IRQs are already disabled. + */ +ENTRY(cpu_do_suspend_workaround) + stmfd sp!, {r4,r5,r6,r7,r9,r10,r11} @ Save registers + + mov r6, r0 @save iomux address + /* Disable L1 caches */ + mrc p15, 0, r0, c1, c0, 0 @ R0 = system control reg + bic r0, r0, #ARM_CTRL_ICACHE @ Disable ICache + bic r0, r0, #ARM_CTRL_DCACHE @ Disable DCache + mcr p15, 0, r0, c1, c0, 0 @ Update system control reg + + mrc p15, 1, r0, c0, c0, 1 @ Read CLIDR + ands r3, r0, #0x7000000 @ Isolate level of coherency + mov r3, r3, lsr #23 @ Cache level value (naturally aligned) + beq FinishedClean + mov r10, #0 +Loop1Clean: + add r2, r10, r10, lsr #1 @ Work out cache level + mov r1, r0, lsr r2 @ R0 bottom 3 bits = Cache Type for this level + and r1, r1, #7 @ Get those 3 bits alone + cmp r1, #2 + blt SkipClean @ No cache or only instruction cache at this level + mcr p15, 2, r10, c0, c0, 0 @ Write the Cache Size selection register + mov r1, #0 + .long 0xF57FF06F @ ISB + mrc p15, 1, r1, c0, c0, 0 @ Reads current Cache Size ID register + and r2, r1, #7 @ Extract the line length field + add r2, r2, #4 @ Add 4 for the line length offset (log2 16 bytes) + ldr r4, =0x3FF + ands r4, r4, r1, lsr #3 @ R4 is the max number on the way size (right aligned) + clz r5, r4 @ R5 is the bit position of the way size increment + ldr r7, =0x00007FFF + ands r7, r7, r1, lsr #13 @ R7 is the max number of the index size (right aligned) +Loop2Clean: + mov r9, r4 @ R9 working copy of the max way size (right aligned) +Loop3Clean: + orr r11, r10, r9, lsl r5 @ Factor in the way number and cache number into R11 + orr r11, r11, r7, lsl r2 @ Factor in the index number + mcr p15, 0, r11, c7, c14, 2 @ Clean and invalidate by set/way + subs r9, r9, #1 @ Decrement the way number + bge Loop3Clean + subs r7, r7, #1 @ Decrement the index + bge Loop2Clean +SkipClean: + add r10, r10, #2 @ Increment the cache number + cmp r3, r10 + bgt Loop1Clean + +FinishedClean: + + /* Disable L2 cache */ + mrc p15, 0, r0, c1, c0, 1 @ R0 = auxiliary control reg + bic r0, r0, #ARM_AUXCR_L2EN @ Disable L2 cache + mcr p15, 0, r0, c1, c0, 1 @ Update aux control reg + + /*Set the DDR drive strength to low */ + ldr r10, [r6] + and r10, r10, #0xF1 @ clear bits 2-1 + str r10, [r6] + + .long 0xe320f003 @ Opcode for WFI + + /*Set the DDR drive strength to max */ + orr r10, r10, #0x06 @ set bits 2-1 + str r10, [r6] + + mov r0, #0 + mcr p15, 0, r0, c7, c5, 0 @ Invalidate inst cache + + /* Invalidate data caches */ + mrc p15, 1, r0, c0, c0, 1 @ Read CLIDR + ands r3, r0, #0x7000000 @ Isolate level of coherency + mov r3, r3, lsr #23 @ Cache level value (naturally aligned) + beq FinishedInvalidate + mov r10, #0 +Loop1Invalidate: + add r2, r10, r10, lsr #1 @ Work out cache level + mov r1, r0, lsr r2 @ R0 bottom 3 bits = Cache Type for this level + and r1, r1, #7 @ Get those 3 bits alone + cmp r1, #2 + blt SkipInvalidate @ No cache or only instruction cache at this level + mcr p15, 2, r10, c0, c0, 0 @ Write the Cache Size selection register + mov r1, #0 + .long 0xF57FF06F @ ISB + mrc p15, 1, r1, c0, c0, 0 @ Reads current Cache Size ID register + and r2, r1, #7 @ Extract the line length field + add r2, r2, #4 @ Add 4 for the line length offset (log2 16 bytes) + ldr r4, =0x3FF + ands r4, r4, r1, lsr #3 @ R4 is the max number on the way size (right aligned) + clz r5, r4 @ R5 is the bit position of the way size increment + ldr r7, =0x00007FFF + ands r7, r7, r1, lsr #13 @ R7 is the max number of the index size (right aligned) +Loop2Invalidate: + mov r9, r4 @ R9 working copy of the max way size (right aligned) +Loop3Invalidate: + orr r11, r10, r9, lsl r5 @ Factor in the way number and cache number into R11 + orr r11, r11, r7, lsl r2 @ Factor in the index number + mcr p15, 0, r11, c7, c6, 2 @ Invalidate by set/way + subs r9, r9, #1 @ Decrement the way number + bge Loop3Invalidate + subs r7, r7, #1 @ Decrement the index + bge Loop2Invalidate +SkipInvalidate: + add r10, r10, #2 @ Increment the cache number + cmp r3, r10 + bgt Loop1Invalidate + +FinishedInvalidate: + + /* Enable L2 cache */ + mrc p15, 0, r0, c1, c0, 1 @ R0 = auxiliary control reg + orr r0, r0, #ARM_AUXCR_L2EN @ Enable L2 cache + mcr p15, 0, r0, c1, c0, 1 @ Update aux control reg + + /* Enable L1 caches */ + mrc p15, 0, r0, c1, c0, 0 @ R0 = system control reg + orr r0, r0, #ARM_CTRL_ICACHE @ Enable ICache + orr r0, r0, #ARM_CTRL_DCACHE @ Enable DCache + mcr p15, 0, r0, c1, c0, 0 @ Update system control reg + + /* Restore registers */ + ldmfd sp!, {r4,r5,r6,r7,r9,r10,r11} + mov pc, lr + + .type cpu_do_suspend, #object +ENTRY(cpu_do_suspend) + .word cpu_do_suspend_workaround + .size cpu_do_suspend_workaround, . - cpu_do_suspend_workaround diff --git a/arch/arm/mach-mx51/system.c b/arch/arm/mach-mx51/system.c new file mode 100644 index 000000000000..a6af4625efe5 --- /dev/null +++ b/arch/arm/mach-mx51/system.c @@ -0,0 +1,191 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <asm/io.h> +#include <mach/hardware.h> +#include <asm/proc-fns.h> +#include <asm/system.h> +#include "crm_regs.h" + +/*! + * @defgroup MSL_MX51 i.MX51 Machine Specific Layer (MSL) + */ + +/*! + * @file mach-mx51/system.c + * @brief This file contains idle and reset functions. + * + * @ingroup MSL_MX51 + */ + +extern int mxc_jtag_enabled; +extern int iram_ready; +static struct clk *gpc_dvfs_clk; + +extern void cpu_cortexa8_do_idle(void *addr); + + +/* set cpu low power mode before WFI instruction */ +void mxc_cpu_lp_set(enum mxc_cpu_pwr_mode mode) +{ + u32 plat_lpc, gpc_pgr, arm_srpgcr, ccm_clpcr; + u32 empgc0, empgc1; + int stop_mode = 0; + + /* always allow platform to issue a deep sleep mode request */ + plat_lpc = __raw_readl(MXC_CORTEXA8_PLAT_LPC) & + ~(MXC_CORTEXA8_PLAT_LPC_DSM); + ccm_clpcr = __raw_readl(MXC_CCM_CLPCR) & ~(MXC_CCM_CLPCR_LPM_MASK); + arm_srpgcr = __raw_readl(MXC_SRPG_ARM_SRPGCR) & ~(MXC_SRPGCR_PCR); + empgc0 = __raw_readl(MXC_SRPG_EMPGC0_SRPGCR) & ~(MXC_SRPGCR_PCR); + empgc1 = __raw_readl(MXC_SRPG_EMPGC1_SRPGCR) & ~(MXC_SRPGCR_PCR); + + gpc_pgr = __raw_readl(MXC_GPC_PGR) & ~(MXC_GPC_PGR_ARMPG_MASK); + + switch (mode) { + case WAIT_CLOCKED: + break; + case WAIT_UNCLOCKED: + ccm_clpcr |= (0x1 << MXC_CCM_CLPCR_LPM_OFFSET); + break; + case WAIT_UNCLOCKED_POWER_OFF: + case STOP_POWER_OFF: + plat_lpc |= MXC_CORTEXA8_PLAT_LPC_DSM + | MXC_CORTEXA8_PLAT_LPC_DBG_DSM; + if (mode == WAIT_UNCLOCKED_POWER_OFF) { + ccm_clpcr |= (0x1 << MXC_CCM_CLPCR_LPM_OFFSET); + ccm_clpcr &= ~MXC_CCM_CLPCR_VSTBY; + stop_mode = 0; + } else { + ccm_clpcr |= (0x2 << MXC_CCM_CLPCR_LPM_OFFSET); + ccm_clpcr |= (0x3 << MXC_CCM_CLPCR_STBY_COUNT_OFFSET); + ccm_clpcr |= MXC_CCM_CLPCR_VSTBY; + ccm_clpcr |= MXC_CCM_CLPCR_SBYOS; + stop_mode = 1; + } + + arm_srpgcr |= MXC_SRPGCR_PCR; + gpc_pgr |= (0x1 << MXC_GPC_PGR_ARMPG_OFFSET); + if (stop_mode) { + empgc0 |= MXC_SRPGCR_PCR; + empgc1 |= MXC_SRPGCR_PCR; + } + + if (tzic_enable_wake(1) != 0) + return; + break; + case STOP_POWER_ON: + ccm_clpcr |= (0x2 << MXC_CCM_CLPCR_LPM_OFFSET); + break; + default: + printk(KERN_WARNING "UNKNOWN cpu power mode: %d\n", mode); + return; + } + + __raw_writel(plat_lpc, MXC_CORTEXA8_PLAT_LPC); + __raw_writel(ccm_clpcr, MXC_CCM_CLPCR); + __raw_writel(gpc_pgr, MXC_GPC_PGR); + __raw_writel(arm_srpgcr, MXC_SRPG_ARM_SRPGCR); + __raw_writel(arm_srpgcr, MXC_SRPG_NEON_SRPGCR); + if (stop_mode) { + __raw_writel(empgc0, MXC_SRPG_EMPGC0_SRPGCR); + __raw_writel(empgc1, MXC_SRPG_EMPGC1_SRPGCR); + } +} + +void mxc_pg_enable(struct platform_device *pdev) +{ + if (pdev == NULL) + return; + + if (strcmp(pdev->name, "mxc_ipu") == 0) { + __raw_writel(MXC_PGCR_PCR, MXC_PGC_IPU_PGCR); + __raw_writel(MXC_PGSR_PSR, MXC_PGC_IPU_PGSR); + } else if (strcmp(pdev->name, "mxc_vpu") == 0) { + __raw_writel(MXC_PGCR_PCR, MXC_PGC_VPU_PGCR); + __raw_writel(MXC_PGSR_PSR, MXC_PGC_VPU_PGSR); + } +} + +EXPORT_SYMBOL(mxc_pg_enable); + +void mxc_pg_disable(struct platform_device *pdev) +{ + if (pdev == NULL) + return; + + if (strcmp(pdev->name, "mxc_ipu") == 0) { + __raw_writel(0x0, MXC_PGC_IPU_PGCR); + if (__raw_readl(MXC_PGC_IPU_PGSR) & MXC_PGSR_PSR) + dev_dbg(&pdev->dev, "power gating successful\n"); + __raw_writel(MXC_PGSR_PSR, MXC_PGC_IPU_PGSR); + } else if (strcmp(pdev->name, "mxc_vpu") == 0) { + __raw_writel(0x0, MXC_PGC_VPU_PGCR); + if (__raw_readl(MXC_PGC_VPU_PGSR) & MXC_PGSR_PSR) + dev_dbg(&pdev->dev, "power gating successful\n"); + __raw_writel(MXC_PGSR_PSR, MXC_PGC_VPU_PGSR); + } +} + +EXPORT_SYMBOL(mxc_pg_disable); + +/* To change the idle power mode, need to set arch_idle_mode to a different + * power mode as in enum mxc_cpu_pwr_mode. + * May allow dynamically changing the idle mode. + */ +static int arch_idle_mode = WAIT_UNCLOCKED_POWER_OFF; +/*! + * This function puts the CPU into idle mode. It is called by default_idle() + * in process.c file. + */ +void arch_idle(void) +{ + if (likely(!mxc_jtag_enabled)) { + if (gpc_dvfs_clk == NULL) + gpc_dvfs_clk = clk_get(NULL, "gpc_dvfs_clk"); + /* gpc clock is needed for SRPG */ + clk_enable(gpc_dvfs_clk); + mxc_cpu_lp_set(arch_idle_mode); + if ((mxc_cpu_is_rev(CHIP_REV_2_0)) < 0) { + u32 l2_iram_addr = IDLE_IRAM_BASE_ADDR; + + if (!iram_ready) + return; + + if (l2_iram_addr > 0x1FFE8000) + cpu_cortexa8_do_idle(IO_ADDRESS(l2_iram_addr)); + } else { + cpu_do_idle(); + } + clk_disable(gpc_dvfs_clk); + } +} + +/* + * This function resets the system. It is called by machine_restart(). + * + * @param mode indicates different kinds of resets + */ +void arch_reset(char mode) +{ + /* Workaround to reset NFC_CONFIG3 register + * due to the chip warm reset does not reset it + */ + __raw_writel(0x20600, IO_ADDRESS(NFC_BASE_ADDR) + 0x28); + + /* Assert SRS signal */ + mxc_wd_reset(); +} diff --git a/arch/arm/mach-mx51/usb.h b/arch/arm/mach-mx51/usb.h new file mode 100644 index 000000000000..7cc14d9119d1 --- /dev/null +++ b/arch/arm/mach-mx51/usb.h @@ -0,0 +1,112 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +extern int usbotg_init(struct platform_device *pdev); +extern void usbotg_uninit(struct fsl_usb2_platform_data *pdata); +extern int gpio_usbotg_hs_active(void); +extern void gpio_usbotg_hs_inactive(void); +extern struct platform_device *host_pdev_register(struct resource *res, + int n_res, struct fsl_usb2_platform_data *config); + +extern int fsl_usb_host_init(struct platform_device *pdev); +extern void fsl_usb_host_uninit(struct fsl_usb2_platform_data *pdata); +extern int gpio_usbotg_utmi_active(void); +extern void gpio_usbotg_utmi_inactive(void); + +/* + * Determine which platform_data struct to use for the DR controller, + * based on which transceiver is configured. + * PDATA is a pointer to it. + */ +#if defined(CONFIG_ISP1301_MXC) +static struct fsl_usb2_platform_data __maybe_unused dr_1301_config; +#define PDATA (&dr_1301_config) +#elif defined(CONFIG_MC13783_MXC) +static struct fsl_usb2_platform_data __maybe_unused dr_13783_config; +#define PDATA (&dr_13783_config) +#elif defined(CONFIG_UTMI_MXC) +static struct fsl_usb2_platform_data __maybe_unused dr_utmi_config; +#define PDATA (&dr_utmi_config) +#endif + + +/* + * Used to set pdata->operating_mode before registering the platform_device. + * If OTG is configured, the controller operates in OTG mode, + * otherwise it's either host or device. + */ +#ifdef CONFIG_USB_OTG +#define DR_UDC_MODE FSL_USB2_DR_OTG +#define DR_HOST_MODE FSL_USB2_DR_OTG +#else +#define DR_UDC_MODE FSL_USB2_DR_DEVICE +#define DR_HOST_MODE FSL_USB2_DR_HOST +#endif + + +#ifdef CONFIG_USB_EHCI_ARC_OTG +static inline void dr_register_host(struct resource *r, int rs) +{ + PDATA->operating_mode = DR_HOST_MODE; + host_pdev_register(r, rs, PDATA); +} +#else +static inline void dr_register_host(struct resource *r, int rs) +{ +} +#endif + +#ifdef CONFIG_USB_GADGET_ARC +static struct platform_device dr_udc_device; + +static inline void dr_register_udc(void) +{ + PDATA->operating_mode = DR_UDC_MODE; + dr_udc_device.dev.platform_data = PDATA; + + if (platform_device_register(&dr_udc_device)) + printk(KERN_ERR "usb: can't register DR gadget\n"); + else + printk(KERN_INFO "usb: DR gadget (%s) registered\n", + PDATA->transceiver); +} +#else +static inline void dr_register_udc(void) +{ +} +#endif + +#ifdef CONFIG_USB_OTG +static struct platform_device dr_otg_device; + +/* + * set the proper operating_mode and + * platform_data pointer, then register the + * device. + */ +static inline void dr_register_otg(void) +{ + PDATA->operating_mode = FSL_USB2_DR_OTG; + dr_otg_device.dev.platform_data = PDATA; + + if (platform_device_register(&dr_otg_device)) + printk(KERN_ERR "usb: can't register otg device\n"); + else + printk(KERN_INFO "usb: DR OTG registered\n"); +} +#else +static inline void dr_register_otg(void) +{ +} +#endif diff --git a/arch/arm/mach-mx51/usb_dr.c b/arch/arm/mach-mx51/usb_dr.c new file mode 100644 index 000000000000..f6748524318e --- /dev/null +++ b/arch/arm/mach-mx51/usb_dr.c @@ -0,0 +1,144 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <mach/arc_otg.h> +#include <mach/hardware.h> +#include "usb.h" + +static int usbotg_init_ext(struct platform_device *pdev); +static void usbotg_uninit_ext(struct fsl_usb2_platform_data *pdata); + +/* + * platform data structs + * - Which one to use is determined by CONFIG options in usb.h + * - operating_mode plugged at run time + */ +static struct fsl_usb2_platform_data __maybe_unused dr_utmi_config = { + .name = "DR", + .platform_init = usbotg_init_ext, + .platform_uninit = usbotg_uninit_ext, + .phy_mode = FSL_USB2_PHY_UTMI_WIDE, + .power_budget = 500, /* 500 mA max power */ + .gpio_usb_active = gpio_usbotg_hs_active, + .gpio_usb_inactive = gpio_usbotg_hs_inactive, + .transceiver = "utmi", +}; + + +/* + * resources + */ +static struct resource otg_resources[] = { + [0] = { + .start = (u32)(USB_OTGREGS_BASE), + .end = (u32)(USB_OTGREGS_BASE + 0x1ff), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_USB_OTG, + .flags = IORESOURCE_IRQ, + }, +}; + + +static u64 dr_udc_dmamask = ~(u32) 0; +static void dr_udc_release(struct device *dev) +{ +} + +static u64 dr_otg_dmamask = ~(u32) 0; +static void dr_otg_release(struct device *dev) +{ +} + +/* + * platform device structs + * dev.platform_data field plugged at run time + */ +static struct platform_device dr_udc_device = { + .name = "fsl-usb2-udc", + .id = -1, + .dev = { + .release = dr_udc_release, + .dma_mask = &dr_udc_dmamask, + .coherent_dma_mask = 0xffffffff, + }, + .resource = otg_resources, + .num_resources = ARRAY_SIZE(otg_resources), +}; + +static struct platform_device __maybe_unused dr_otg_device = { + .name = "fsl-usb2-otg", + .id = -1, + .dev = { + .release = dr_otg_release, + .dma_mask = &dr_otg_dmamask, + .coherent_dma_mask = 0xffffffff, + }, + .resource = otg_resources, + .num_resources = ARRAY_SIZE(otg_resources), +}; + +/* Notes: configure USB clock*/ +static int usbotg_init_ext(struct platform_device *pdev) +{ + struct clk *usb_clk; + + usb_clk = clk_get(NULL, "usboh3_clk"); + clk_enable(usb_clk); + clk_put(usb_clk); + + usb_clk = clk_get(NULL, "usb_phy_clk"); + clk_enable(usb_clk); + clk_put(usb_clk); + + /*derive clock from oscillator */ + usb_clk = clk_get(NULL, "usb_utmi_clk"); + clk_disable(usb_clk); + clk_put(usb_clk); + + return usbotg_init(pdev); +} + +static void usbotg_uninit_ext(struct fsl_usb2_platform_data *pdata) +{ + struct clk *usb_clk; + + usb_clk = clk_get(NULL, "usboh3_clk"); + clk_disable(usb_clk); + clk_put(usb_clk); + + usb_clk = clk_get(NULL, "usb_phy_clk"); + clk_disable(usb_clk); + clk_put(usb_clk); + + usbotg_uninit(pdata); +} + +static int __init usb_dr_init(void) +{ + pr_debug("%s: \n", __func__); + + dr_register_otg(); + dr_register_host(otg_resources, ARRAY_SIZE(otg_resources)); + dr_register_udc(); + + return 0; +} + +module_init(usb_dr_init); diff --git a/arch/arm/mach-mx51/usb_h1.c b/arch/arm/mach-mx51/usb_h1.c new file mode 100644 index 000000000000..869ec45f0726 --- /dev/null +++ b/arch/arm/mach-mx51/usb_h1.c @@ -0,0 +1,114 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <mach/arc_otg.h> +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include "usb.h" +#include "iomux.h" + + +/* + * USB Host1 HS port + */ +static int gpio_usbh1_active(void) +{ + /* Set USBH1_STP to GPIO and toggle it */ + mxc_request_iomux(MX51_PIN_USBH1_STP, IOMUX_CONFIG_GPIO | + IOMUX_CONFIG_SION); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_USBH1_STP), "usbh1_stp"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_USBH1_STP), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_USBH1_STP), 1); + + /* Signal only used on MX51-3DS for reset to PHY.*/ + if (machine_is_mx51_3ds()) { + mxc_request_iomux(MX51_PIN_EIM_D17, IOMUX_CONFIG_ALT1); + mxc_iomux_set_pad(MX51_PIN_EIM_D17, PAD_CTL_DRV_HIGH | + PAD_CTL_HYS_NONE | PAD_CTL_PUE_KEEPER | + PAD_CTL_100K_PU | PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PKE_ENABLE | PAD_CTL_SRE_FAST); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_D17), "eim_d17"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_D17), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_D17), 1); + } + + msleep(100); + + return 0; +} + +void gpio_usbh1_setback_stp(void) +{ + /* setback USBH1_STP to be function */ + mxc_request_iomux(MX51_PIN_USBH1_STP, IOMUX_CONFIG_ALT0); + mxc_iomux_set_pad(MX51_PIN_USBH1_STP, PAD_CTL_SRE_FAST | + PAD_CTL_DRV_HIGH | PAD_CTL_ODE_OPENDRAIN_NONE | + PAD_CTL_PUE_KEEPER | PAD_CTL_PKE_ENABLE | + PAD_CTL_HYS_ENABLE | PAD_CTL_DDR_INPUT_CMOS | + PAD_CTL_DRV_VOT_LOW); + gpio_free(IOMUX_TO_GPIO(MX51_PIN_USBH1_STP)); +} +EXPORT_SYMBOL(gpio_usbh1_setback_stp); + +static void gpio_usbh1_inactive(void) +{ + /* Signal only used on MX51-3DS for reset to PHY.*/ + if (machine_is_mx51_3ds()) { + gpio_free(IOMUX_TO_GPIO(MX51_PIN_EIM_D17)); + mxc_free_iomux(MX51_PIN_EIM_D17, IOMUX_CONFIG_GPIO); + } + + mxc_free_iomux(MX51_PIN_USBH1_STP, IOMUX_CONFIG_GPIO); + gpio_free(IOMUX_TO_GPIO(MX51_PIN_USBH1_STP)); +} + +static struct fsl_usb2_platform_data usbh1_config = { + .name = "Host 1", + .platform_init = fsl_usb_host_init, + .platform_uninit = fsl_usb_host_uninit, + .operating_mode = FSL_USB2_MPH_HOST, + .phy_mode = FSL_USB2_PHY_ULPI, + .power_budget = 500, /* 500 mA max power */ + .gpio_usb_active = gpio_usbh1_active, + .gpio_usb_inactive = gpio_usbh1_inactive, + .transceiver = "isp1504", +}; + +static struct resource usbh1_resources[] = { + [0] = { + .start = (u32) (USB_H1REGS_BASE), + .end = (u32) (USB_H1REGS_BASE + 0x1ff), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_USB_H1, + .flags = IORESOURCE_IRQ, + }, +}; + +static int __init usbh1_init(void) +{ + pr_debug("%s: \n", __func__); + + host_pdev_register(usbh1_resources, + ARRAY_SIZE(usbh1_resources), &usbh1_config); + + return 0; +} + +module_init(usbh1_init); diff --git a/arch/arm/mach-mx51/usb_h2.c b/arch/arm/mach-mx51/usb_h2.c new file mode 100644 index 000000000000..c9fc225910a9 --- /dev/null +++ b/arch/arm/mach-mx51/usb_h2.c @@ -0,0 +1,90 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <asm/mach-types.h> +#include <mach/arc_otg.h> +#include "usb.h" +#include "iomux.h" + +/* + * USB Host2 HS port + */ +static int gpio_usbh2_active(void) +{ + /* Set USBH2_STP to GPIO and toggle it */ + mxc_request_iomux(MX51_PIN_EIM_A26, IOMUX_CONFIG_GPIO); + gpio_request(IOMUX_TO_GPIO(MX51_PIN_EIM_A26), "eim_a26"); + gpio_direction_output(IOMUX_TO_GPIO(MX51_PIN_EIM_A26), 0); + gpio_set_value(IOMUX_TO_GPIO(MX51_PIN_EIM_A26), 1); + + msleep(100); + + return 0; +} + +void gpio_usbh2_setback_stp(void) +{ + /* setback USBH2_STP to be function */ + mxc_request_iomux(MX51_PIN_EIM_A26, IOMUX_CONFIG_ALT2); +} +EXPORT_SYMBOL(gpio_usbh2_setback_stp); + +static void gpio_usbh2_inactive(void) +{ + gpio_free(IOMUX_TO_GPIO(MX51_PIN_EIM_A26)); + mxc_free_iomux(MX51_PIN_EIM_A26, IOMUX_CONFIG_GPIO); +} + +static struct fsl_usb2_platform_data usbh2_config = { + .name = "Host 2", + .platform_init = fsl_usb_host_init, + .platform_uninit = fsl_usb_host_uninit, + .operating_mode = FSL_USB2_MPH_HOST, + .phy_mode = FSL_USB2_PHY_ULPI, + .power_budget = 500, /* 500 mA max power */ + .gpio_usb_active = gpio_usbh2_active, + .gpio_usb_inactive = gpio_usbh2_inactive, + .transceiver = "isp1504", +}; + +static struct resource usbh2_resources[] = { + [0] = { + .start = (u32) (USB_H2REGS_BASE), + .end = (u32) (USB_H2REGS_BASE + 0x1ff), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MXC_INT_USB_H2, + .flags = IORESOURCE_IRQ, + }, +}; + +static int __init usbh2_init(void) +{ + pr_debug("%s: \n", __func__); + + if (machine_is_mx51_3ds() || + (machine_is_mx51_babbage() && (cpu_is_mx51_rev(CHIP_REV_2_0) >= 1))) + return 0; + + host_pdev_register(usbh2_resources, ARRAY_SIZE(usbh2_resources), + &usbh2_config); + return 0; +} + +module_init(usbh2_init); diff --git a/arch/arm/mach-mx51/wfi.S b/arch/arm/mach-mx51/wfi.S new file mode 100644 index 000000000000..adb9a4f374b6 --- /dev/null +++ b/arch/arm/mach-mx51/wfi.S @@ -0,0 +1,426 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/linkage.h> + +#define ARM_CTRL_DCACHE 1 << 2 +#define ARM_AUXCR_L2EN 1 << 1 +/* + * cpu_cortexa8_do_idle() + * + * Idle the processor (eg, wait for interrupt). + * + * IRQs are already disabled. + */ +ENTRY(cpu_cortexa8_do_idle) + + mrc p15, 0, r1, c1, c0, 1 @ R1 = auxiliary control reg + ands r2, r1, #ARM_AUXCR_L2EN @ Check if L2 is disabled + beq SkipL2Access + + mrc p15, 0, r2, c1, c0, 0 @ R2 = system control reg + bic r2, r2, #ARM_CTRL_DCACHE @ Disable DCache + mcr p15, 0, r2, c1, c0, 0 @ Update system control reg + + bic r1, r1, #ARM_AUXCR_L2EN @ Disable L2 cache + mcr p15, 0, r1, c1, c0, 1 @ Update aux control reg + + ldr r1, =(0x0 << 6) @ A[6] = 0 + mcr p15, 0, r1, c15, c9, 2 @ Read L2 tag RAM into L2 data 0 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x00] @ Save tag info + + ldr r1, =(0x1 << 6) @ A[6] = 1 + mcr p15, 0, r1, c15, c9, 2 @ Read L2 tag RAM into L2 data 0 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x04] @ Save tag info + + ldr r1, =(0x0 << 3) @ A[6:3] = b0000 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x08] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0x0C] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0x10] @ Store data info + + ldr r1, =(0x1 << 3) @ A[6:3] = b0001 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x14] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0x18] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0x1C] @ Store data info + + ldr r1, =(0x2 << 3) @ A[6:3] = b0010 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x20] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0x24] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0x28] @ Store data info + + ldr r1, =(0x3 << 3) @ A[6:3] = b0011 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x2C] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0x30] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0x34] @ Store data info + + ldr r1, =(0x4 << 3) @ A[6:3] = b0100 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x38] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0x3C] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0x40] @ Store data info + + ldr r1, =(0x5 << 3) @ A[6:3] = b0101 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x44] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0x48] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0x4C] @ Store data info + + ldr r1, =(0x6 << 3) @ A[6:3] = b0110 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x50] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0x54] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0x58] @ Store data info + + ldr r1, =(0x7 << 3) @ A[6:3] = b0111 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x5C] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0x60] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0x64] @ Store data info + + ldr r1, =(0x8 << 3) @ A[6:3] = b1000 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x68] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0x6C] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0x70] @ Store data info + + ldr r1, =(0x9 << 3) @ A[6:3] = b1001 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x74] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0x78] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0x7C] @ Store data info + + ldr r1, =(0xA << 3) @ A[6:3] = b1010 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x80] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0x84] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0x88] @ Store data info + + ldr r1, =(0xB << 3) @ A[6:3] = b1011 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x8C] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0x90] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0x94] @ Store data info + + ldr r1, =(0xC << 3) @ A[6:3] = b1100 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0x98] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0x9C] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0xA0] @ Store data info + + ldr r1, =(0xD << 3) @ A[6:3] = b1101 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0xA4] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0xA8] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0xAC] @ Store data info + + ldr r1, =(0xE << 3) @ A[6:3] = b1110 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0xB0] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0xB4] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0xB8] @ Store data info + + ldr r1, =(0xF << 3) @ A[6:3] = b1111 + mcr p15, 0, r1, c15, c9, 3 @ Read L2 Data RAM into L2 data 0-2 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0xBC] @ Store data info + mrc p15, 0, r2, c15, c8, 1 @ Move L2 data 1 register to R2 + str r2, [r0, #0xC0] @ Store data info + mrc p15, 0, r2, c15, c8, 5 @ Move L2 data 2 register to R2 + str r2, [r0, #0xC4] @ Store data info + + ldr r1, =(0x2 << 29) | (0x0 << 6) @ WAY = A[31:29] = 2, A[6] = 0 + mcr p15, 0, r1, c15, c9, 2 @ Read L2 tag RAM into L2 data 0 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0xC8] @ Save tag info + + ldr r1, =(0x2 << 29) | (0x1 << 6) @ WAY = A[31:29] = 2, A[6] = 1 + mcr p15, 0, r1, c15, c9, 2 @ Read L2 tag RAM into L2 data 0 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0xCC] @ Save tag info + + ldr r1, =(0x4 << 29) | (0x0 << 6) @ WAY = A[31:29] = 4, A[6] = 0 + mcr p15, 0, r1, c15, c9, 2 @ Read L2 tag RAM into L2 data 0 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0xD0] @ Save tag info + + ldr r1, =(0x4 << 29) | (0x1 << 6) @ WAY = A[31:29] = 4, A[6] = 1 + mcr p15, 0, r1, c15, c9, 2 @ Read L2 tag RAM into L2 data 0 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0xD4] @ Save tag info + + ldr r1, =(0x6 << 29) | (0x0 << 6) @ WAY = A[31:29] = 6, A[6] = 0 + mcr p15, 0, r1, c15, c9, 2 @ Read L2 tag RAM into L2 data 0 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0xD8] @ Save tag info + + ldr r1, =(0x6 << 29) | (0x1 << 6) @ WAY = A[31:29] = 6, A[6] = 1 + mcr p15, 0, r1, c15, c9, 2 @ Read L2 tag RAM into L2 data 0 register + mrc p15, 0, r2, c15, c8, 0 @ Move L2 data 0 register to R2 + str r2, [r0, #0xDC] @ Save tag info + + .long 0xe320f003 @ Opcode for WFI + + ldr r1, =(0x0 << 6) @ A[6] = 0 + ldr r2, [r0, #0x00] @ Load tag info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + mcr p15, 0, r1, c15, c8, 2 @ Write L2 data 0 register to L2 tag RAM + + ldr r1, =(0x1 << 6) @ A[6] = 1 + ldr r2, [r0, #0x04] @ Load tag info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + mcr p15, 0, r1, c15, c8, 2 @ Write L2 data 0 register to L2 tag RAM + + ldr r1, =(0x0 << 3) @ A[6:3] = b0000 + ldr r2, [r0, #0x08] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0x0C] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0x10] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0x1 << 3) @ A[6:3] = b0001 + ldr r2, [r0, #0x14] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0x18] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0x1C] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0x2 << 3) @ A[6:3] = b0010 + ldr r2, [r0, #0x20] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0x24] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0x28] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0x3 << 3) @ A[6:3] = b0011 + ldr r2, [r0, #0x2C] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0x30] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0x34] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0x4 << 3) @ A[6:3] = b0100 + ldr r2, [r0, #0x38] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0x3C] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0x40] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0x5 << 3) @ A[6:3] = b0101 + ldr r2, [r0, #0x44] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0x48] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0x4C] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0x6 << 3) @ A[6:3] = b0110 + ldr r2, [r0, #0x50] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0x54] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0x58] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0x7 << 3) @ A[6:3] = b0111 + ldr r2, [r0, #0x5C] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0x60] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0x64] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0x8 << 3) @ A[6:3] = b1000 + ldr r2, [r0, #0x68] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0x6C] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0x70] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0x9 << 3) @ A[6:3] = b1001 + ldr r2, [r0, #0x74] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0x78] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0x7C] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0xA << 3) @ A[6:3] = b1010 + ldr r2, [r0, #0x80] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0x84] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0x88] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0xB << 3) @ A[6:3] = b1011 + ldr r2, [r0, #0x8C] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0x90] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0x94] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0xC << 3) @ A[6:3] = b1100 + ldr r2, [r0, #0x98] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0x9C] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0xA0] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0xD << 3) @ A[6:3] = b1101 + ldr r2, [r0, #0xA4] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0xA8] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0xAC] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0xE << 3) @ A[6:3] = b1110 + ldr r2, [r0, #0xB0] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0xB4] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0xB8] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0xF << 3) @ A[6:3] = b1111 + ldr r2, [r0, #0xBC] @ Load data info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + ldr r2, [r0, #0xC0] @ Load data info + mcr p15, 0, r2, c15, c8, 1 @ Move R2 to L2 data 1 register + ldr r2, [r0, #0xC4] @ Load data info + mcr p15, 0, r2, c15, c8, 5 @ Move R2 to L2 data 2 register + mcr p15, 0, r1, c15, c8, 3 @ Write L2 data 0-2 registers to L2 data RAM + + ldr r1, =(0x2 << 29) | (0x0 << 6) @ WAY = A[31:29] = 2, A[6] = 0 + ldr r2, [r0, #0xC8] @ Load tag info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + mcr p15, 0, r1, c15, c8, 2 @ Write L2 data 0 register to L2 tag RAM + + ldr r1, =(0x2 << 29) | (0x1 << 6) @ WAY = A[31:29] = 2, A[6] = 1 + ldr r2, [r0, #0xCC] @ Load tag info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + mcr p15, 0, r1, c15, c8, 2 @ Write L2 data 0 register to L2 tag RAM + + ldr r1, =(0x4 << 29) | (0x0 << 6) @ WAY = A[31:29] = 4, A[6] = 0 + ldr r2, [r0, #0xD0] @ Load tag info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + mcr p15, 0, r1, c15, c8, 2 @ Write L2 data 0 register to L2 tag RAM + + ldr r1, =(0x4 << 29) | (0x1 << 6) @ WAY = A[31:29] = 4, A[6] = 1 + ldr r2, [r0, #0xD4] @ Load tag info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + mcr p15, 0, r1, c15, c8, 2 @ Write L2 data 0 register to L2 tag RAM + + ldr r1, =(0x6 << 29) | (0x0 << 6) @ WAY = A[31:29] = 6, A[6] = 0 + ldr r2, [r0, #0xD8] @ Load tag info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + mcr p15, 0, r1, c15, c8, 2 @ Write L2 data 0 register to L2 tag RAM + + ldr r1, =(0x6 << 29) | (0x1 << 6) @ WAY = A[31:29] = 6, A[6] = 1 + ldr r2, [r0, #0xDC] @ Load tag info + mcr p15, 0, r2, c15, c8, 0 @ Move R2 to L2 data 0 register + mcr p15, 0, r1, c15, c8, 2 @ Write L2 data 0 register to L2 tag RAM + + mrc p15, 0, r1, c1, c0, 1 @ R1 = auxiliary control reg + orr r1, r1, #ARM_AUXCR_L2EN @ Enable L2 cache + mcr p15, 0, r1, c1, c0, 1 @ Update aux control reg + + mrc p15, 0, r2, c1, c0, 0 @ R2 = system control reg + orr r2, r2, #ARM_CTRL_DCACHE @ Enable DCache + mcr p15, 0, r2, c1, c0, 0 @ Update system control reg + + b Done + +SkipL2Access: + .long 0xe320f003 @ Opcode for WFI + +Done: + mov pc, lr + + .type cortexa8_idle_workaround, #object +ENTRY(cortexa8_idle_workaround) + .word cpu_cortexa8_do_idle + .size cortexa8_idle_workaround, . - cortexa8_idle_workaround diff --git a/arch/arm/mach-stmp378x/Kconfig b/arch/arm/mach-stmp378x/Kconfig new file mode 100644 index 000000000000..e0176671985b --- /dev/null +++ b/arch/arm/mach-stmp378x/Kconfig @@ -0,0 +1,59 @@ +if ARCH_STMP378X + +config FB_STMP37XX_HX8238A + depends on ARCH_STMP37XX + bool "HX8238A" + ---help--- + Use HX8238A dotclock LCD panel for STMP37XX + +config FB_STMP37XX_LMS350 + depends on ARCH_STMP378X + bool "LMS350" + ---help--- + Use LMS350 dotclock LCD panel for STMP378X + +config FB_STMP37XX_LMS430 + depends on ARCH_STMP378X + bool "LMS430" + ---help--- + Use LMS430 dotclock LCD panel for STMP378X + +config FB_STMP378X_TVENC + depends on ARCH_STMP378X + bool "TVENC" + ---help--- + Use TVOUT encoder for STMP378X + +config STMP3XXX_UNIQUE_ID + bool "Support for UniqueID on boot media" + default y + +config STMP3XXX_UNIQUE_ID_OTP + bool "UniqueID on OTP" + depends on STMP3XXX_UNIQUE_ID + default y + +config STMP378X_RAM_FREQ_SCALING + bool "RAM frequency scaling support" + depends on ARCH_STMP378X + default y + +choice + prompt "Select STMP378x RAM chip" + depends on STMP378X_RAM_FREQ_SCALING + +config STMP378X_RAM_MDDR + bool "mDDR SDRAM" +config STMP378X_RAM_DDR + bool "DDR SDRAM" + +endchoice + +config DMA_ZONE_SIZE + int "DMA memory zone size" + range 0 32 + default 12 + help + This is the size in MB for the DMA zone. The DMA zone is used for + dedicated memory for large contiguous video buffers +endif diff --git a/arch/arm/mach-stmp378x/Makefile b/arch/arm/mach-stmp378x/Makefile index d156f76b379f..df1f08140638 100644 --- a/arch/arm/mach-stmp378x/Makefile +++ b/arch/arm/mach-stmp378x/Makefile @@ -1,2 +1,29 @@ -obj-$(CONFIG_ARCH_STMP378X) += stmp378x.o -obj-$(CONFIG_MACH_STMP378X) += stmp378x_devb.o +# +# Makefile for the linux kernel. +# +obj-y += power.o persistent.o + +# Power Management +obj-$(CONFIG_PM) += pm.o sleep.o + +# Chip family select +obj-$(CONFIG_ARCH_STMP37XX) += stmp37xx.o stmp37xx_lcdif.o +obj-$(CONFIG_ARCH_STMP378X) += stmp378x.o stmp378x_lcdif.o + +obj-$(CONFIG_MACH_STMP37XX) += stmp37xx_devb.o +obj-$(CONFIG_MACH_STMP378X) += stmp378x_devb.o stmp378x_i2c.o stmp378x_pwm_led.o + +# LCD panels support +obj-$(CONFIG_FB_STMP37XX_HX8238A) += lcd_hx8238a.o +obj-$(CONFIG_FB_STMP37XX_LMS350) += lcd_lms350.o +obj-$(CONFIG_FB_STMP37XX_LMS430) += lcd_lms430.o + +# TVOUT support +obj-$(CONFIG_FB_STMP378X_TVENC) += tvenc.o + +obj-$(CONFIG_STMP3XXX_UNIQUE_ID_OTP) += otp.o +obj-$(CONFIG_STMP3XXX_UNIQUE_ID_FILE) += unique-id-file.o + +obj-$(CONFIG_INPUT_STMP3XXX_ROTDEC) += stmp378x_devb_rotdec.o +obj-$(CONFIG_STMP378X_RAM_FREQ_SCALING) += emi.o + diff --git a/arch/arm/mach-stmp378x/emi.S b/arch/arm/mach-stmp378x/emi.S new file mode 100644 index 000000000000..2b7625597ed3 --- /dev/null +++ b/arch/arm/mach-stmp378x/emi.S @@ -0,0 +1,153 @@ +/* + * Freescale STMP378X low level RAM frequency manipulation + * + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <mach/hardware.h> +#include <asm/system.h> +#include <asm/pgtable-hwdef.h> +#include <mach/regs-power.h> +#include <mach/regs-clkctrl.h> + +#define SCALING_DATA_EMI_DIV_OFFSET 0 +#define SCALING_DATA_FRAC_DIV_OFFSET 4 +#define SCALING_DATA_CUR_FREQ_OFFSET 8 +#define SCALING_DATA_NEW_FREQ_OFFSET 12 + +.global cpu_arm926_switch_mm + +.align 8 +ENTRY(stmp3xxx_ram_freq_scale) + stmfd sp!, {r1 - r9, lr} + + ldr r5, [r0, #SCALING_DATA_NEW_FREQ_OFFSET] + ldr r6, [r0, #SCALING_DATA_CUR_FREQ_OFFSET] + ldr r7, [r0, #SCALING_DATA_EMI_DIV_OFFSET] + ldr r8, [r0, #SCALING_DATA_FRAC_DIV_OFFSET] + + adr r9, __stmp_temp_stack + + @ clean cache + ldr r1, __stmp_flush_cache_addr + mov lr, pc + mov pc, r1 + + @ put DRAM into self refresh + ldr r0, __stmp_dram_ctl00 + ldr r1, [r0, #0x20] + orr r1, r1, #(1 << 8) + str r1, [r0, #0x20] + @ wait for it to actually happen + ldr r0, __stmp_dram_emi00 +1: ldr r1, [r0, #0x10] + tst r1, #(1 << 1) + beq 1b + nop + + @ prepare for change + cmp r5, #24 + bgt 2f + bl stmp3xxx_ram_24M_set_timings + b 100f +2: cmp r5, #48 + bgt 3f + bl stmp3xxx_ram_48M_set_timings + b 100f +3: cmp r5, #60 + bgt 4f + bl stmp3xxx_ram_60M_set_timings + b 100f +4: cmp r5, #80 + bgt 5f + bl stmp3xxx_ram_80M_set_timings + b 100f +5: cmp r5, #96 + bgt 6f + bl stmp3xxx_ram_96M_set_timings + b 100f +6: cmp r5, #120 + bgt 7f + bl stmp3xxx_ram_120M_set_timings + b 100f +7: cmp r5, #133 + bgt 8f + bl stmp3xxx_ram_133M_set_timings + b 100f +8: bl stmp3xxx_ram_150M_set_timings + +100: + @ RAM to clk from xtal + mov r0, #(HW_CLKCTRL_CLKSEQ & 0x000000FF) + orr r0, r0, #(HW_CLKCTRL_CLKSEQ & 0x0000FF00) + orr r0, r0, #(HW_CLKCTRL_CLKSEQ & 0x00FF0000) + orr r0, r0, #(HW_CLKCTRL_CLKSEQ & 0xFF000000) + mov r1, #(1<<6) + str r1, [r0, #4] + mov r0, #(HW_CLKCTRL_EMI & 0x000000FF) + orr r0, r0, #(HW_CLKCTRL_EMI & 0x0000FF00) + orr r0, r0, #(HW_CLKCTRL_EMI & 0x00FF0000) + orr r0, r0, #(HW_CLKCTRL_EMI & 0xFF000000) +101: ldr r1, [r0] + tst r1, #BM_CLKCTRL_EMI_BUSY_REF_XTAL + bne 101b + + bl __stmp_emi_set_values + + @ EMI back to PLL + mov r0, #(HW_CLKCTRL_CLKSEQ & 0x000000FF) + orr r0, r0, #(HW_CLKCTRL_CLKSEQ & 0x0000FF00) + orr r0, r0, #(HW_CLKCTRL_CLKSEQ & 0x00FF0000) + orr r0, r0, #(HW_CLKCTRL_CLKSEQ & 0xFF000000) + mov r1, #(1<<6) + str r1, [r0, #8] + + mov r0, #(HW_CLKCTRL_EMI & 0x000000FF) + orr r0, r0, #(HW_CLKCTRL_EMI & 0x0000FF00) + orr r0, r0, #(HW_CLKCTRL_EMI & 0x00FF0000) + orr r0, r0, #(HW_CLKCTRL_EMI & 0xFF000000) +1: ldr r1, [r0] + tst r1, #BM_CLKCTRL_EMI_BUSY_REF_EMI + bne 1b + bic r1, #BM_CLKCTRL_EMI_DCC_RESYNC_ENABLE + str r1, [r0] + + @ restore normal DRAM mode + ldr r0, __stmp_dram_ctl00 + ldr r1, [r0, #0x20] + bic r1, r1, #(1 << 8) + str r1, [r0, #0x20] + @ wait for it to actually happen + ldr r0, __stmp_dram_emi00 +102: ldr r1, [r0, #0x10] + tst r1, #(1 << 1) + bne 102b + + @ restore regs and return + ldmfd sp!, {r1 - r9, lr} + mov pc, lr + + .space 0x100 +__stmp_temp_stack: + .word 0 + +#include "emi.inc" + +__stmp_flush_cache_addr: + .word arm926_flush_kern_cache_all + +ENTRY(stmp3xxx_ram_funcs_sz) + .word . - stmp3xxx_ram_freq_scale diff --git a/arch/arm/mach-stmp378x/emi.inc b/arch/arm/mach-stmp378x/emi.inc new file mode 100644 index 000000000000..0e003c994a2b --- /dev/null +++ b/arch/arm/mach-stmp378x/emi.inc @@ -0,0 +1,707 @@ +/* + * Freescale STMP378X low level RAM timings tables for Micron mDDR + * + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +__stmp_emi_set_values: + stmfd r9!, {r0 - r4, lr} + mov r1, #(HW_CLKCTRL_EMI & 0x000000FF) + orr r1, r1, #(HW_CLKCTRL_EMI & 0x0000FF00) + orr r1, r1, #(HW_CLKCTRL_EMI & 0x00FF0000) + orr r1, r1, #(HW_CLKCTRL_EMI & 0xFF000000) + + mov r3, #BM_CLKCTRL_EMI_DCC_RESYNC_ENABLE + + mov r0, #(HW_CLKCTRL_FRAC & 0x000000FF) + orr r0, r0, #(HW_CLKCTRL_FRAC & 0x0000FF00) + orr r0, r0, #(HW_CLKCTRL_FRAC & 0x00FF0000) + orr r0, r0, #(HW_CLKCTRL_FRAC & 0xFF000000) + ldr r2, [r0] + + and r4, r2, #BM_CLKCTRL_FRAC_EMIFRAC + lsr r4, r4, #8 + /* new pll div > cur pll div? */ + cmp r4, r8 + bgt 1f + bic r4, r2, #BM_CLKCTRL_FRAC_EMIFRAC + orr r4, r4, r8, lsl #8 + str r4, [r0] + nop + nop + nop + +1: ldr r4, [r1] + and r4, r4, #BM_CLKCTRL_EMI_DIV_EMI + /* new emi div > cur emi div? */ + cmp r4, r7 + bgt 2f + mov r4, r7 + orr r4, r4, #0x100 + orr r4, r4, r3 + str r4, [r1] +11: ldr r4, [r1] + tst r4, #BM_CLKCTRL_EMI_BUSY_REF_EMI + bne 11b + tst r4, #BM_CLKCTRL_EMI_BUSY_REF_XTAL + bne 11b + tst r4, #BM_CLKCTRL_EMI_BUSY_DCC_RESYNC + bne 11b + +2: ldr r2, [r0] + + and r4, r2, #BM_CLKCTRL_FRAC_EMIFRAC + lsr r4, r4, #8 + /* new pll div != cur pll div? */ + cmp r4, r8 + beq 3f + bic r4, r2, #BM_CLKCTRL_FRAC_EMIFRAC + orr r4, r4, r8, lsl #8 + str r4, [r0] + nop + nop + nop + +3: ldr r4, [r1] + and r4, r4, #BM_CLKCTRL_EMI_DIV_EMI + /* new emi div != cur emi div? */ + cmp r4, r7 + beq 4f + mov r4, r7 + orr r4, r4, #0x100 + orr r4, r4, r3 + str r4, [r1] +31: ldr r4, [r1] + tst r4, #BM_CLKCTRL_EMI_BUSY_REF_EMI + bne 31b + tst r4, #BM_CLKCTRL_EMI_BUSY_REF_XTAL + bne 31b + tst r4, #BM_CLKCTRL_EMI_BUSY_DCC_RESYNC + bne 31b + +4: ldmfd r9!, {r0 - r4, lr} + mov pc, lr + +stmp3xxx_ram_24M_set_timings: + ldr r0, __stmp_dram_ctl00 + adr r1, __stmp_dram_24M_values +1: ldr r2, [r1] + ldr r3, [r1, #4] + mov r4, r2, lsl #2 + str r3, [r0, r4] + add r1, r1, #8 + cmp r2, #40 + bne 1b + mov pc, lr + +stmp3xxx_ram_48M_set_timings: + ldr r0, __stmp_dram_ctl00 + adr r1, __stmp_dram_48M_values +1: ldr r2, [r1] + ldr r3, [r1, #4] + mov r4, r2, lsl #2 + str r3, [r0, r4] + add r1, r1, #8 + cmp r2, #40 + bne 1b + mov pc, lr + +stmp3xxx_ram_60M_set_timings: + ldr r0, __stmp_dram_ctl00 + adr r1, __stmp_dram_60M_values +1: ldr r2, [r1] + ldr r3, [r1, #4] + mov r4, r2, lsl #2 + str r3, [r0, r4] + add r1, r1, #8 + cmp r2, #40 + bne 1b + mov pc, lr + +stmp3xxx_ram_80M_set_timings: + ldr r0, __stmp_dram_ctl00 + adr r1, __stmp_dram_80M_values +1: ldr r2, [r1] + ldr r3, [r1, #4] + mov r4, r2, lsl #2 + str r3, [r0, r4] + add r1, r1, #8 + cmp r2, #40 + bne 1b + mov pc, lr + +stmp3xxx_ram_96M_set_timings: + ldr r0, __stmp_dram_ctl00 + adr r1, __stmp_dram_96M_values +1: ldr r2, [r1] + ldr r3, [r1, #4] + mov r4, r2, lsl #2 + str r3, [r0, r4] + add r1, r1, #8 + cmp r2, #40 + bne 1b + mov pc, lr + +stmp3xxx_ram_120M_set_timings: + ldr r0, __stmp_dram_ctl00 + adr r1, __stmp_dram_120M_values +1: ldr r2, [r1] + ldr r3, [r1, #4] + mov r4, r2, lsl #2 + str r3, [r0, r4] + add r1, r1, #8 + cmp r2, #40 + bne 1b + mov pc, lr + +stmp3xxx_ram_133M_set_timings: + ldr r0, __stmp_dram_ctl00 + adr r1, __stmp_dram_133M_values +1: ldr r2, [r1] + ldr r3, [r1, #4] + str r3, [r0, r2, lsl #2] + add r1, r1, #8 + cmp r2, #40 + bne 1b + mov pc, lr + +stmp3xxx_ram_150M_set_timings: + ldr r0, __stmp_dram_ctl00 + adr r1, __stmp_dram_150M_values +1: ldr r2, [r1] + ldr r3, [r1, #4] + str r3, [r0, r2, lsl #2] + add r1, r1, #8 + cmp r2, #40 + bne 1b + mov pc, lr + +__stmp_dram_ctl00: + .word IO_ADDRESS(0x800E0000) +__stmp_dram_emi00: + .word IO_ADDRESS(0x80020000) +__stmp_power_vdddctrl: + .word IO_ADDRESS(0x80044040) + +stmp3xxx_ram_save_timings: + ldr r0, __stmp_dram_ctl00 + adr r1, __stmp_dram_saved_values +1: ldr r2, [r1] + mov r4, r2, lsl #2 + ldr r3, [r0, r4] + str r3, [r1, #4] + add r1, r1, #8 + cmp r2, #40 + bne 1b + mov pc, lr + +#ifdef CONFIG_STMP378X_RAM_MDDR +__stmp_dram_24M_values: + .word 4 + .word 0x01000101 + .word 7 + .word 0x01000101 + .word 12 + .word 0x02010002 + .word 13 + .word 0x06060a02 + .word 15 + .word 0x01030000 + .word 17 + .word 0x2d000102 + .word 18 + .word 0x20200000 + .word 19 + .word 0x027f1414 + .word 20 + .word 0x01021608 + .word 21 + .word 0x00000002 + .word 26 + .word 0x000000b3 + .word 32 + .word 0x00030687 + .word 33 + .word 0x00000003 + .word 34 + .word 0x000012c1 + .word 40 + .word 0x00010000 + +__stmp_dram_48M_values: + .word 4 + .word 0x01000101 + .word 7 + .word 0x01000101 + .word 13 + .word 0x06060a02 + .word 12 + .word 0x02010002 + .word 15 + .word 0x02040000 + .word 17 + .word 0x2d000104 + .word 18 + .word 0x1f1f0000 + .word 19 + .word 0x027f0a0a + .word 20 + .word 0x02030a10 + .word 21 + .word 0x00000004 + .word 26 + .word 0x0000016f + .word 32 + .word 0x00060d17 + .word 33 + .word 0x00000006 + .word 34 + .word 0x00002582 + .word 40 + .word 0x00020000 + +__stmp_dram_60M_values: +__stmp_dram_80M_values: + .word 4 + .word 0x01000101 + .word 7 + .word 0x01000101 + .word 12 + .word 0x02020002 + .word 13 + .word 0x06060a02 + .word 15 + .word 0x02040000 + .word 17 + .word 0x2d000005 + .word 18 + .word 0x1f1f0000 + .word 19 + .word 0x027f0a0a + .word 20 + .word 0x02040a10 + .word 21 + .word 0x00000006 + .word 26 + .word 0x000001cc + .word 32 + .word 0x00081060 + .word 33 + .word 0x00000008 + .word 34 + .word 0x00002ee5 + .word 40 + .word 0x00020000 + +__stmp_dram_96M_values: + .word 4 + .word 0x00000101 + .word 7 + .word 0x01000001 + .word 12 + .word 0x02020002 + .word 13 + .word 0x06070a02 + .word 15 + .word 0x03050000 + .word 17 + .word 0x2d000808 + .word 18 + .word 0x1f1f0000 + .word 19 + .word 0x020c1010 + .word 20 + .word 0x0305101c + .word 21 + .word 0x00000007 + .word 26 + .word 0x000002e6 + .word 32 + .word 0x000c1a3b + .word 33 + .word 0x0000000c + .word 34 + .word 0x00004b0d + .word 40 + .word 0x00030000 + +__stmp_dram_120M_values: + .word 4 + .word 0x00000101 + .word 7 + .word 0x01000001 + .word 12 + .word 0x02020002 + .word 13 + .word 0x06070a02 + .word 15 + .word 0x03050000 + .word 17 + .word 0x2300080a + .word 18 + .word 0x1f1f0000 + .word 19 + .word 0x020c1010 + .word 20 + .word 0x0306101c + .word 21 + .word 0x00000009 + .word 26 + .word 0x000003a1 + .word 32 + .word 0x000f20ca + .word 33 + .word 0x0000000f + .word 34 + .word 0x00005dca + .word 40 + .word 0x00040000 + +__stmp_dram_133M_values: +__stmp_dram_150M_values: + .word 4 + .word 0x00000101 + .word 7 + .word 0x01000001 + .word 12 + .word 0x02020002 + .word 13 + .word 0x06070a02 + .word 15 + .word 0x03050000 + .word 17 + .word 0x2000080a + .word 18 + .word 0x1f1f0000 + .word 19 + .word 0x020c1010 + .word 20 + .word 0x0306101c + .word 21 + .word 0x0000000a + .word 26 + .word 0x00000408 + .word 32 + .word 0x0010245f + .word 33 + .word 0x00000010 + .word 34 + .word 0x00006808 + .word 40 + .word 0x00040000 + +#elif CONFIG_STMP378X_RAM_DDR +/* XXX: not quite ready yet */ +__stmp_dram_24M_values: + .word 4 + .word 0x01000101 + .word 7 + .word 0x01000101 + .word 11 + .word 0x00070206 + .word 12 + .word 0x01010000 @ t_wr 1, t_rrd 1, t_cke 0 + .word 13 + .word 0x04040a01 @ t_wtr 1 + .word 15 + .word 0x01020000 @ t_rp 1, t_dal 2 + .word 17 + .word 0x3d000302 @ t_rc 2 + .word 20 + .word 0x01020508 + .word 21 + .word 0x00000002 @ t_rfc 2 + .word 26 + .word 0x000000b3 /* 0xd20 */ @ t_ref + .word 32 + .word 0x00020690 @ t_xsnr 2, t_rasmax 0x690 + .word 33 + .word 0x000000c8 @ t_xsr 0xc8 + .word 34 + .word 0x000012c1 @ t_init + .word 40 + .word 0x00010000 + +@ not yet +__stmp_dram_48M_values: + .word 4 + .word 0x01000101 + .word 7 + .word 0x01000101 + .word 11 + .word 0x00070206 + .word 12 + .word 0x01010000 @ t_wr 1, t_rrd 1, t_cke 0 + .word 13 + .word 0x04040a01 @ t_wtr 1 + .word 15 + .word 0x01020000 @ t_rp 1, t_dal 2 + .word 17 + .word 0x39000104 @ t_rc 4 + .word 19 + .word 0x027f1010 + .word 20 + .word 0x02030a10 + .word 21 + .word 0x00000004 @ t_rfc + .word 26 + .word 0x00000173 /* 0x1a42 */ @ t_ref + .word 32 + .word 0x00040d21 @ t_xsnr 4, t_rasmax 0xd21 + .word 33 + .word 0x000000c8 @ t_xsr 0xc8 + .word 34 + .word 0x00002586 @ t_init + .word 40 + .word 0x00010000 + +__stmp_dram_60M_values: + .word 4 + .word 0x01000101 + .word 7 + .word 0x01000101 + .word 11 + .word 0x00070206 + .word 12 + .word 0x01010000 @ t_wr 1, t_rrd 1, t_cke 0 + .word 13 + .word 0x04040a01 @ t_wtr 1 + .word 15 + .word 0x01020000 @ t_rp 1, t_dal 2 + .word 17 + .word 0x3d000105 @ t_rc 5 + .word 19 + .word 0x027f1313 + .word 20 + .word 0x01031523 @ t_rcd 1, t_rasmin 3 + .word 21 + .word 0x00000005 @ t_rfc 5 + .word 26 + .word 0x000001cc /* 0x20cd */ @ t_ref + .word 32 + .word 0x00051068 @ t_xsnr 5, t_rasmax 0x1068 + .word 33 + .word 0x000000c8 @ t_xsr 0xc8 + .word 34 + .word 0x00002ee5 @ t_init + .word 40 + .word 0x00010000 + +__stmp_dram_80M_values: + .word 4 + .word 0x00000101 + .word 7 + .word 0x01000001 + .word 11 + .word 0x00070206 + .word 12 + .word 0x02010000 @ t_wr 2, t_rrd 1, t_cke 0 + .word 13 + .word 0x04040a01 @ t_wtr 1 + .word 15 + .word 0x02040000 @ t_rp 2, t_dal 4 + .word 17 + .word 0x20001c05 @ dll_start_point 0x20, dll_increment 0x1c, t_rc 5 + .word 19 + .word 0x027f1313 + .word 20 + .word 0x02041522 @ t_rcd 2, t_rasmin 4, wr_dqs_shift 0x22 + .word 21 + .word 0x00000006 @ t_rfc 6 + .word 26 + .word 0x00000269 @ t_ref + .word 32 + .word 0x000615d6 @ t_xsnr 6, t_rasmax 0x15d6 + .word 33 + .word 0x000000c8 @ t_xsr 0xc8 + .word 34 + .word 0x00003e80 @ t_init + .word 40 + .word 0x00010000 + +__stmp_dram_96M_values: + .word 4 + .word 0x00000101 + .word 7 + .word 0x01000001 + .word 11 + .word 0x00070206 + .word 12 + .word 0x02020000 @ t_wr 2, t_rrd 2, t_cke 0 + .word 13 + .word 0x04040a01 @ t_wtr 1 + .word 15 + .word 0x02040000 @ t_rp 2, t_dal 4 + .word 17 + .word 0x2f001706 @ dll_start_point 0x2f, dll_increment 0x17, t_rc 6 + .word 19 + .word 0x027f1a1a + .word 20 + .word 0x02051c21 @ t_rcd 2, t_rasmin 5, wr_dqs_shift 0x22 + .word 21 + .word 0x00000007 @ t_rfc 7 + .word 26 + .word 0x000002e6 /* 0x347b */ @ t_ref + .word 32 + .word 0x00081a3e @ t_xsnr 8, t_rasmax 0x1a3e + .word 33 + .word 0x000000c8 @ t_xsr 0xc8 + .word 34 + .word 0x00004b0d @ t_init + .word 40 + .word 0x00010000 + +__stmp_dram_120M_values: + .word 4 + .word 0x00000101 + .word 7 + .word 0x01000001 + .word 11 + .word 0x00070206 + .word 12 + .word 0x02020000 @ t_wr 2, t_rrd 2, t_cke 0 + .word 13 + .word 0x04040a01 @ t_wtr 1 + .word 15 + .word 0x02040000 @ t_rp 2, t_dal 4 + .word 17 + .word 0x26001308 @ dll_start_point 0x26, dll_increment 0x13, t_rc 8 + .word 19 + .word 0x027f1a1a + .word 20 + .word 0x02061c23 @ t_rcd 2, t_rasmin 6 + .word 21 + .word 0x00000009 @ t_rfc 9 + .word 26 + .word 0x000003a1 /* 0x41a6 */ @ t_ref + .word 32 + .word 0x000a20ca @ t_xsnr 9, t_rasmax 0x20ca + .word 33 + .word 0x000000c8 @ t_xsr 0xc8 + .word 34 + .word 0x00005dca @ t_init + .word 40 + .word 0x00010000 + +__stmp_dram_133M_values: + .word 4 + .word 0x00000101 + .word 7 + .word 0x01000001 + .word 11 + .word 0x00070204 + .word 12 + .word 0x02020000 + .word 13 + .word 0x04040a01 + .word 15 + .word 0x02040000 + .word 17 + .word 0x19000f08 @ t_rc 0xa + .word 19 + .word 0x02021313 + .word 20 + .word 0x02061521 + .word 21 + .word 0x0000000a + .word 26 + .word 0x000003f7 /* 0x48b9 */ + .word 32 + .word 0x000a23cd + .word 33 + .word 0x000000c8 @ t_xsr 0xc8 + .word 34 + .word 0x00006665 + .word 40 + .word 0x00010000 + +__stmp_dram_150M_values: + .word 4 + .word 0x00000101 + .word 7 + .word 0x01000001 + .word 11 + .word 0x00070206 + .word 12 + .word 0x02020000 @ t_wr 2, t_rrd 2, t_cke 0 + .word 13 + .word 0x05050a02 @ t_wtr 2 + .word 15 + .word 0x03060000 @ t_rp 3, t_dal 6 + .word 17 + .word 0x18000d0c @ dll_start_point 0x18, dll_increment 0xd, t_rc 0xc + .word 19 + .word 0x027f0f0f + .word 20 + .word 0x03071121 @ t_rcd 3, t_rasmin 7 + .word 21 + .word 0x0000000c @ t_rfc 0xc + .word 26 + .word 0x000001cc /* 0x20cd */ @ t_ref + .word 32 + .word 0x000c2860 @ t_xsnr 0xc, t_rasmax 0x2860 + .word 33 + .word 0x000000c8 @ t_xsr 0xc8 + .word 34 + .word 0x00007554 @ t_init + .word 40 + .word 0x00010000 + +#else +#error RAM chip not defined +#endif + +stmp3xxx_ram_restore_timings: + ldr r0, __stmp_dram_ctl00 + adr r1, __stmp_dram_saved_values +1: ldr r2, [r1] + ldr r3, [r1, #4] + mov r4, r2, lsl #2 + str r3, [r0, r4] + add r1, r1, #8 + cmp r2, #40 + bne 1b + mov pc, lr + +__stmp_dram_saved_values: + .word 4 + .word 0 + .word 7 + .word 0 + .word 12 + .word 0 + .word 13 + .word 0 + .word 15 + .word 0 + .word 17 + .word 0 + .word 18 + .word 0 + .word 19 + .word 0 + .word 20 + .word 0 + .word 21 + .word 0 + .word 26 + .word 0 + .word 32 + .word 0 + .word 33 + .word 0 + .word 34 + .word 0 + .word 40 + .word 0 diff --git a/arch/arm/mach-stmp378x/include/mach/ddi_bc.h b/arch/arm/mach-stmp378x/include/mach/ddi_bc.h new file mode 100644 index 000000000000..2018d167aeda --- /dev/null +++ b/arch/arm/mach-stmp378x/include/mach/ddi_bc.h @@ -0,0 +1,736 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +//////////////////////////////////////////////////////////////////////////////// +//! \addtogroup ddi_bc +//! @{ +// +// Copyright (c) 2004-2005 SigmaTel, Inc. +// +//! \file ddi_bc.h +//! \brief Header file for the Battery Charger device driver. +//! \date 06/2005 +//! +//! This file contains externally visible declarations for the Battery Charger +//! device driver. +//! +//! \see ddi_bc.c and related files. +//! \todo [PUBS] Add definitions for TBDs in this file. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _DDI_BC_H +#define _DDI_BC_H + +#include <linux/types.h> + +//////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Definitions +//////////////////////////////////////////////////////////////////////////////// +#define DDI_BC_MAX_RESTART_CYCLES 100 + +#define DDI_BC_LIION_CHARGING_VOLTAGE 4200 +#define DDI_BC_ALKALINE_NIMH_CHARGING_VOLTAGE 1750 + +//! \brief Defines battery charger states. +typedef enum _ddi_bc_State { + //! \brief TBD + DDI_BC_STATE_UNINITIALIZED = 0, + //! \brief TBD + DDI_BC_STATE_BROKEN = 1, + //! \brief TBD + DDI_BC_STATE_DISABLED = 2, + //! \brief TBD + DDI_BC_STATE_WAITING_TO_CHARGE = 3, + //! \brief TBD + DDI_BC_STATE_CONDITIONING = 4, + //! \brief TBD + DDI_BC_STATE_CHARGING = 5, + //! \brief TBD + DDI_BC_STATE_TOPPING_OFF = 6, + //! \brief TBD + DDI_BC_STATE_DCDC_MODE_WAITING_TO_CHARGE = 7, + +} ddi_bc_State_t; + +typedef enum _ddi_bc_BrokenReason { + //! \brief TBD + DDI_BC_BROKEN_UNINITIALIZED = 0, + //! \brief TBD + DDI_BC_BROKEN_CHARGING_TIMEOUT = 1, + //! \brief TBD + DDI_BC_BROKEN_FORCED_BY_APPLICATION = 2, + //! \brief TBD + DDI_BC_BROKEN_EXTERNAL_BATTERY_VOLTAGE_DETECTED = 3, + //! \brief TBD + DDI_BC_BROKEN_NO_BATTERY_DETECTED = 4, + +} ddi_bc_BrokenReason_t; + +//! \brief Defines the battery charger configuration. +typedef struct _ddi_bc_Cfg { + //! \brief Units in milliseconds. + //! + //! This field configures the expected period between calls to + //! ddi_bc_StateMachine. If die temperature monitoring is + //! enabled, then the data sheet recommends the period be around + //! 100ms or less. + //! + //! Note that this period defines the minimum time resolution of + //! the battery charger. + + uint32_t u32StateMachinePeriod; + + //! \brief Units in mA/s. + //! + //! This field configures the slope of the current ramp. Any + //! time the battery charger increases its current draw, it will + //! ramp up the current no faster than this rate. + //! + //! Note that the minimum time resolution of the battery charger + //! is the configured period between calls to advance the state + //! machine. Also, the hardware has a minimum current resolution + //! of 10mA. If the given ramp slope cannot be expressed + //! exactly, then the largest expressible smaller slope will be + //! the result. If the actual period between calls to + //! ddi_bc_StateMachine is irregular, the current may ramp faster + //! than indicated. + + uint16_t u16CurrentRampSlope; + + //! \brief Units in millivolts. + //! + //! This field configures the threshold conditioning voltage. If + //! the battery’s voltage is below this value, it will be + //! conditioned until its voltage rises above the maximum + //! conditioning voltage. After that, the battery will be + //! charged normally. + //! + //! Note that the hardware has a minimum resolution of 8mV. If + //! the given voltage cannot be expressed exactly, then the + //! smallest expressible larger value will be used. + + uint16_t u16ConditioningThresholdVoltage; + + //! \brief Units in millivolts. + //! + //! This field configures the maximum conditioning voltage. If + //! the battery charger is conditioning a battery, normal + //! charging begins when the voltage rises above this value. + //! + //! This value should be slightly higher than the threshold + //! conditioning voltage because it is measured while a + //! conditioning current is actually flowing to the battery. + //! With a conditioning current of 0.1C, reasonable values for + //! the threshold and maximum conditioning voltages are 2.9V + //! and 3.0V respectively. + //! + //! Note that the hardware has a minimum resolution of 8mV. If + //! the given voltage cannot be expressed exactly, then the + //! smallest expressible larger value will be used. + + uint16_t u16ConditioningMaxVoltage; + + //! \brief Units in milliamps. + //! + //! This field configures the maximum conditioning current. + //! This is the maximum current that will be offered to a + //! battery while it is being conditioned. A typical value is + //! 0.1C. + //! + //! Note that the hardware has a minimum resolution of 10mA + //! (see the data sheet for details). If the given current + //! cannot be expressed exactly, then the largest expressible + //! smaller value will be used. + + uint16_t u16ConditioningCurrent; + + //! \brief Units in milliseconds. + //! + //! This field configures the conditioning time-out. This is + //! the maximum amount of time that a battery will be + //! conditioned before the battery charger declares it to be + //! broken. + //! + //! Note that the minimum time resolution of the battery + //! charger is the configured period between calls to advance + //! the state machine. If the given time-out cannot be + //! expressed exactly, then the shortest expressible longer + //! value will be used. + + uint32_t u32ConditioningTimeout; + + //! \brief Units in millivolts. + //! + //! This field configures the final charging voltage. At this + //! writing, only two values are permitted: 4100 or 4200. + + uint16_t u16ChargingVoltage; + + //! \brief Units in milliamps. + //! + //! This field configures the maximum current offered to a + //! charging battery. + //! + //! Note that the hardware has a minimum resolution of 10mA + //! (see the data sheet for details). If the given current + //! cannot be expressed exactly, then the largest expressible + //! smaller value will be used. + + uint16_t u16ChargingCurrent; + + //! \brief Units in milliamps. + //! + //! This field configures the current flow below which a + //! charging battery is regarded as fully charged (typical + //! 0.1C). At this point, the battery will be topped off. + //! + //! Note that the hardware has a minimum resolution of 10mA + //! (see the data sheet for details). If the given current + //! cannot be expressed exactly, then the largest expressible + //! smaller value will be used. + + uint16_t u16ChargingThresholdCurrent; + + //! \brief Units in milliamps. + //! + //! When charging while the DCDC converter's are enabled, the charger + //! is suppling current to both the battery and the Vbat input of the + //! DCDC converter. Once the total battery charger current falls + //! below this level, the charger will then stop charging until the + //! the battery voltage reaches the BC_LOW_DCDCMODE_BATTERY_VOLTAGE + //! threshold or until the DCDCs are no longer enabled. + //! + //! Typically, this value should be left at 180 to avoid the risk + //! of topping off the battery too long in DCDC mode and avoid + //! exceeding the BC_CHARGING_TIMEOUT time which would put the charger + //! driver in the broken state and completely disable charging. + //! + //! Note that the hardware has a minimum resolution of 10mA + //! (see the data sheet for details). If the given current + //! cannot be expressed exactly, then the largest expressible + //! smaller value will be used. + uint16_t u16DdcdModeChargingThresholdCurrent; + + //! \brief Units in milliseconds. + //! + //! This field configures the charging time-out. This is the + //! maximum amount of time that a battery will be charged + //! before the battery charger declares it to be broken. + //! + //! Note that the minimum time resolution of the battery + //! charger is the configured period between calls to advance + //! the state machine. If the given time-out cannot be + //! expressed exactly, then the shortest expressible longer + //! value will be used. + + uint32_t u32ChargingTimeout; + + //! \brief Units in milliseconds. + //! + //! This field configures the top-off period. This is the + //! amount of time a battery will be held in the Topping Off + //! state before it is declared fully charged. + //! + //! Note that the minimum time resolution of the battery + //! charger is the configured period between calls to advance + //! the state machine. If the given time-out cannot be + //! expressed exactly, then the shortest expressible longer + //! value will be used. + + uint32_t u32TopOffPeriod; + + //! \brief Units in milliseconds. + //! + //! This field configures the top-off period when the DCDC + //! converters are enabled. To avoid topping off the LiIon + //! battery too long and reducing it's long term capacity, + //! This time should be kept failry short. + //! + //! Note that the minimum time resolution of the battery + //! charger is the configured period between calls to advance + //! the state machine. If the given time-out cannot be + //! expressed exactly, then the shortest expressible longer + //! value will be used. + uint32_t u32DcdcModeTopOffPeriod; + + //! \brief Causes the battery charger to use an externally generated bias current + //! + //! If cleared, this causes the battery charger to use an + //! externally generated bias current, which is expected to be + //! quite precise. Otherwise, the battery charger will + //! generate a lesser-quality bias current internally. + + uint8_t useInternalBias:1; + + //! \brief Indicates that the battery charger is to monitor the die temperature. + //! + //! If set, this field indicates that the battery charger is to + //! monitor the die temperature. See below for fields that + //! configure the details. + + uint8_t monitorDieTemp:1; + + //! \brief Indicates that the battery charger is to monitor the battery temperature. + //! + //! If set, this field indicates that the battery charger is to + //! monitor the battery temperature. See below for fields that + //! configure the details. + + uint8_t monitorBatteryTemp:1; + + //! \brief Units in degrees centigrade. + //! + //! Note that the hardware reports die temperature in ranges of + //! 10 degree resolution minimum (see the data sheet for + //! details). If the battery charger is monitoring the die + //! temperature, and it rises to a range that includes a + //! temperature greater than or equal to this value, the + //! charging current will be clamped to the safe current. + + int8_t u8DieTempHigh; + + //! \brief Units in degrees centigrade. + //! + //! Note that the hardware reports die temperature in ranges of + //! 10 degrees minimum (see the data sheet for details). If the + //! charging current is being clamped because of a high die + //! temperature, and it falls to a range that doesn’t include a + //! temperatures greater than or equal to this value, the + //! charging current clamp will be released. + + int8_t u8DieTempLow; + + //! \brief Units in milliamps. + //! + //! If the battery charger detects a high die temperature, it + //! will clamp the charging current at or below this value. + + uint16_t u16DieTempSafeCurrent; + + //! \brief If the battery charger is monitoring the battery + //! temperature, this field indicates the LRADC channel to + //! read. + + uint8_t u8BatteryTempChannel; + + //! \brief If the battery charger is monitoring the battery + //! temperature, and it rises to a measurement greater than or + //! equal to this value, the charging current will be clamped + //! to the corresponding safe current. + + uint16_t u16BatteryTempHigh; + + //! \brief If the charging current is being clamped because of a high + //! battery temperature, and it falls below this value, the + //! charging current clamp will be released. + + uint16_t u16BatteryTempLow; + + //! \brief Units in milliamps. + //! + //! If the battery charger detects a high battery temperature, + //! it will clamp the charging current at or below this value. + + uint16_t u16BatteryTempSafeCurrent; + + //! \brief Units in millivolts. + //! + //! In the WaitingToCharge state, if we are in DCDC + //! operating modes, if the battery voltage measurement + //! is below this value, we immediately proceed with charging. + //! the low criteria for this value is that it must be high + //! to not risk the battery voltage getting too low. The + //! upper criteria is that you do not want the IR voltage + //! drop under heavy loads to make you start charging too soon + //! because the goal in DCDC operating mode is to not be constantly + //! topping off the battery which can shorten its life + + uint16_t u16LowDcdcBatteryVoltage_mv; + + uint32_t u32StateMachineNonChargingPeriod; +} ddi_bc_Cfg_t; + +//! Status returned by Battery Charger functions. + +typedef enum _ddi_bc_Status { + //! \brief TBD + DDI_BC_STATUS_SUCCESS = 0, + //! \brief TBD + DDI_BC_STATUS_HARDWARE_DISABLED, + //! \brief TBD + DDI_BC_STATUS_BAD_BATTERY_MODE, + //! \brief TBD + DDI_BC_STATUS_CLOCK_GATE_CLOSED, + //! \brief TBD + DDI_BC_STATUS_NOT_INITIALIZED, + //! \brief TBD + DDI_BC_STATUS_ALREADY_INITIALIZED, + //! \brief TBD + DDI_BC_STATUS_BROKEN, + //! \brief TBD + DDI_BC_STATUS_NOT_BROKEN, + //! \brief TBD + DDI_BC_STATUS_NOT_DISABLED, + //! \brief TBD + DDI_BC_STATUS_BAD_ARGUMENT, + //! \brief TBD + DDI_BC_STATUS_CFG_BAD_BATTERY_TEMP_CHANNEL, + //! \brief TBD + DDI_BC_STATUS_CFG_BAD_CHARGING_VOLTAGE, +} ddi_bc_Status_t; + +///////////////////////////////////////////////////////////////////////////////// +// BCM Event Codes +// +// These are the codes that might be published to PMI Subscribers. +///////////////////////////////////////////////////////////////////////////////// + +#define DDI_BC_EVENT_GROUP (11<<10) + +//! \brief TBD +//! \todo [PUBS] Add definition(s)... +typedef enum { + // Use the error code group value to make events unique for the EOI + //! \brief TBD + ddi_bc_MinEventCode = DDI_BC_EVENT_GROUP, + //! \brief TBD + ddi_bc_WaitingToChargeCode, + //! \brief TBD + ddi_bc_State_ConditioningCode, + //! \brief TBD + ddi_bc_State_Topping_OffCode, + //! \brief TBD + ddi_bc_State_BrokenCode, + //! \brief TBD + ddi_bc_SettingChargeCode, + //! \brief TBD + ddi_bc_RaisingDieTempAlarmCode, + //! \brief TBD + ddi_bc_DroppingDieTempAlarmCode, + + //! \brief TBD + ddi_bc_MaxEventCode, + //! \brief TBD + ddi_bc_DcdcModeWaitingToChargeCode +} ddi_bc_Event_t; + +//////////////////////////////////////////////////////////////////////////////// +// Prototypes +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +//! \brief Initialize the Battery Charger. +//! +//! \fntype Function +//! +//! This function initializes the Battery Charger. +//! +//! \param[in] pCfg A pointer to the new configuration. +//! +//! \retval DDI_BC_STATUS_SUCCESS +//! If the operation succeeded. +//! \retval DDI_BC_STATUS_ALREADY_INITIALIZED +//! If the Battery Charger is already initialized. +//! \retval DDI_BC_STATUS_HARDWARE_DISABLED +//! If the Battery Charger hardware is disabled by a laser fuse. +//! \retval DDI_BC_STATUS_BAD_BATTERY_MODE +//! If the power supply is set up for a non-rechargeable battery. +//! \retval DDI_BC_STATUS_CLOCK_GATE_CLOSED +//! If the clock gate for the power supply registers is closed. +//! \retval DDI_BC_STATUS_CFG_BAD_CHARGING_VOLTAGE +//! If the charging voltage is not either 4100 or 4200. +//! \retval DDI_BC_STATUS_CFG_BAD_BATTERY_TEMP_CHANNEL +//! If the LRADC channel number for monitoring battery temperature +//! is bad. +//! +//! \internal +//! \see To view the function definition, see ddi_bc_init.c. +//////////////////////////////////////////////////////////////////////////////// +extern ddi_bc_Status_t ddi_bc_Init(ddi_bc_Cfg_t * pCfg); +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the Battery Charger configuration. +//! +//! \fntype Function +//! +//! This function reports the Battery Charger configuration. +//! +//! Note that, if the Battery Charger has not yet been initialized, the data +//! returned by this function is unknown. +//! +//! \param[in,out] pCfg A pointer to a structure that will receive the data. +//! +//! \internal +//! \see To view the function definition, see ddi_bc_api.c. +//////////////////////////////////////////////////////////////////////////////// +extern void ddi_bc_QueryCfg(ddi_bc_Cfg_t * pCfg); +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Shut down the Battery Charger. +//! +//! \fntype Function +//! +//! This function immediately shuts down the Battery Charger hardware and +//! returns the state machine to the Uninitialized state. Use this function to +//! safely “mummify” the battery charger before retiring it from memory. +//! +//! \internal +//! \see To view the function definition, see ddi_bc_api.c. +//////////////////////////////////////////////////////////////////////////////// +extern void ddi_bc_ShutDown(void); +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Advances the state machine. +//! +//! \fntype Function +//! +//! This function advances the state machine. +//! +//! \retval DDI_BC_STATUS_SUCCESS If all goes well +//! \retval DDI_BC_STATUS_NOT_INITIALIZED If the Battery Charger is not yet +//! initialized. +//! \retval DDI_BC_STATUS_BROKEN If the battery violated a time-out +//! and has been declared broken. +//! +//! \internal +//! \see To view the function definition, see ddi_bc_api.c. +//////////////////////////////////////////////////////////////////////////////// +extern ddi_bc_Status_t ddi_bc_StateMachine(void); +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Get the Battery Charger's current state. +//! +//! \fntype Function +//! +//! This function returns the current state. +//! +//! \retval The current state. +//! +//! \internal +//! \see To view the function definition, see ddi_bc_api.c. +//////////////////////////////////////////////////////////////////////////////// +extern ddi_bc_State_t ddi_bc_GetState(void); +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Disable the Battery Charger. +//! +//! \fntype Function +//! +//! This function forces the Battery Charger into the Disabled state. +//! +//! \retval DDI_BC_STATUS_SUCCESS If all goes well +//! \retval DDI_BC_STATUS_NOT_INITIALIZED If the Battery Charger is not yet +//! initialized. +//! +//! \internal +//! \see To view the function definition, see ddi_bc_api.c. +//////////////////////////////////////////////////////////////////////////////// +extern ddi_bc_Status_t ddi_bc_SetDisable(void); +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Enable the Battery Charger. +//! +//! \fntype Function +//! +//! If the Battery Charger is in the Disabled state, this function moves it to +//! the Waiting to Charge state. +//! +//! \retval DDI_BC_STATUS_SUCCESS If all goes well +//! \retval DDI_BC_STATUS_NOT_INITIALIZED If the Battery Charger is not yet +//! initialized. +//! \retval DDI_BC_STATUS_NOT_DISABLED If the Battery Charger is not +//! disabled. +//! +//! \internal +//! \see To view the function definition, see ddi_bc_api.c. +//////////////////////////////////////////////////////////////////////////////// +extern ddi_bc_Status_t ddi_bc_SetEnable(void); +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Declare the battery to be broken. +//! +//! \fntype Function +//! +//! This function forces the Battery Charger into the Broken state. +//! +//! \retval DDI_BC_STATUS_SUCCESS If all goes well +//! \retval DDI_BC_STATUS_NOT_INITIALIZED If the Battery Charger is not yet +//! initialized. +//! +//! \internal +//! \see To view the function definition, see ddi_bc_api.c. +//////////////////////////////////////////////////////////////////////////////// +extern ddi_bc_Status_t ddi_bc_SetBroken(void); +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Declare the battery to be fixed. +//! +//! \fntype Function +//! +//! If the Battery Charger is in the Broken state, this function moves it to +//! the Disabled state. +//! +//! \retval DDI_BC_STATUS_SUCCESS If all goes well +//! \retval DDI_BC_STATUS_NOT_INITIALIZED If the Battery Charger is not yet +//! initialized. +//! \retval DDI_BC_STATUS_NOT_BROKEN If the Battery Charger is not broken. +//! +//! \internal +//! \see To view the function definition, see ddi_bc_api.c. +//////////////////////////////////////////////////////////////////////////////// +extern ddi_bc_Status_t ddi_bc_SetFixed(void); +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Set the current limit. +//! +//! \fntype Function +//! +//! This function applies a limit to the current that the Battery Charger can +//! draw. +//! +//! \param[in] u16Limit The maximum current the Battery Charger can draw +//! (in mA). +//! +//! \retval The expressible version of the limit. +//! +//! \internal +//! \see To view the function definition, see ddi_bc_api.c. +//////////////////////////////////////////////////////////////////////////////// +extern uint16_t ddi_bc_SetCurrentLimit(uint16_t u16Limit); + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the current limit. +//! +//! \fntype Function +//! +//! This function reports the limit to the current that the Battery Charger can +//! draw. +//! +//! \retval The current limit. +//! +//! \internal +//! \see To view the function definition, see ddi_bc_api.c. +//////////////////////////////////////////////////////////////////////////////// +extern uint16_t ddi_bc_GetCurrentLimit(void); + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Set the current threshold. +//! +//! \fntype Function +//! +//! +//! \param[in] u16Current Current threshold where charger deactivates (in mA). +//! +//! +//////////////////////////////////////////////////////////////////////////////// +extern uint16_t ddi_bc_SetCurrentThreshold(uint16_t u16Current); + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Set the battery charger state machine period. +//! +//! \fntype Function +//! +//! This function sets a new state machine period. The Period and Slope should +//! be coordinated to achieve the minimal ramp step current which will minimize +//! transients on the system. +//! +//! \param[in] u32StateMachinePeriod (in milliseconds) +//! \param[in] u16CurrentRampSlope (in mA/s) +//! +//! \retval SUCCESS If all goes well +//! \retval ERROR_DDI_BCM_NOT_INITIALIZED If the Battery Charger is not yet +//! initialized. +//! +//////////////////////////////////////////////////////////////////////////////// +extern ddi_bc_Status_t ddi_bc_SetNewPeriodAndSlope(uint32_t + u32StateMachinePeriod, + uint16_t + u16CurrentRampSlope); + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the state machine period. +//! +//! \fntype Function +//! +//! This function reports the battery charger period. +//! +//! \retval The battery charger period (in milliseconds). +//! +//////////////////////////////////////////////////////////////////////////////// +extern uint32_t ddi_bc_GetStateMachinePeriod(void); + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the current ramp slope. +//! +//! \fntype Function +//! +//! This function reports the current ramp slope. +//! +//! \retval The current ramp slope (in mA/s). +//! +//////////////////////////////////////////////////////////////////////////////// +extern uint32_t ddi_bc_GetCurrentRampSlope(void); + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the time spent in the present state (milliseconds) +//! +//! \fntype Function +//! +//! This function reports the time spent in the present charging state. Note that +//! for the states that actually charge the battery, this time does not include the +//! time spent under alarm conditions such as die termperature alarm or battery +//! temperature alarm. +//! +//! \retval The time spent in the current state in milliseconds. +//! +//////////////////////////////////////////////////////////////////////////////// +uint32_t ddi_bc_GetStateTime(void); + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the reason for being in the broken state +//! +//! \fntype Function +//! +//! +//! \retval ddi_bc_BrokenReason_t enumeration +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_BrokenReason_t ddi_bc_GetBrokenReason(void); + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Restart the charge cycle +//! +//! \fntype Function +//! +//! \retval SUCCESS +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_Status_t ddi_bc_ForceChargingToStart(void); + +//////////////////////////////////////////////////////////////////////////////// +// End of file +//////////////////////////////////////////////////////////////////////////////// +#endif // _DDI_BC_H +//! @} diff --git a/arch/arm/mach-stmp378x/include/mach/i2c.h b/arch/arm/mach-stmp378x/include/mach/i2c.h new file mode 100644 index 000000000000..05a57f6351e3 --- /dev/null +++ b/arch/arm/mach-stmp378x/include/mach/i2c.h @@ -0,0 +1,48 @@ +/* + * Freescale STMP378X I2C low-level/dma functions + * + * Author: Dmitrij Frasenyak <sed@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef _ARM_ARCH_I2C_H +#define _ARM_ARCH_I2C_H + +#include <linux/device.h> +#include <linux/module.h> + +#include <linux/completion.h> +#include <linux/i2c.h> + +#define I2C_READ 1 +#define I2C_WRITE 0 + +void hw_i2c_clear_dma_interrupt(void); +int hw_i2c_init(struct device *dev); +void hw_i2c_stop(struct device *dev); +void hw_i2c_setup_write(u8 addr, void *buff, int len, int flags); +void hw_i2c_setup_read(u8 addr, void *buff, int len, int flags); +void hw_i2c_run(int dir); +void hw_i2c_reset_dma(void); +void hw_i2c_finish_read(void *buff, int len); + +struct stmp378x_i2c_dev { + struct device *dev; + int irq_dma; + int irq_err; + struct completion cmd_complete; + u32 cmd_err; + struct i2c_adapter adapter; +}; + +#endif diff --git a/arch/arm/mach-stmp378x/include/mach/lcdif.h b/arch/arm/mach-stmp378x/include/mach/lcdif.h new file mode 100644 index 000000000000..ef5647a7a618 --- /dev/null +++ b/arch/arm/mach-stmp378x/include/mach/lcdif.h @@ -0,0 +1,497 @@ +/* + * Freescale STMP378X LCDIF interfaces + * + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef _ARCH_ARM_LCDIF_H +#define _ARCH_ARM_LCDIF_H + +#include <linux/types.h> +#include <linux/fb.h> +#include <linux/list.h> +#include <linux/backlight.h> +#include <linux/dma-mapping.h> +#include <linux/regulator/consumer.h> +#include <mach/dma.h> +#include <mach/platform.h> + +#include "regs-lcdif.h" +#include "regs-apbh.h" + +enum { + SPI_MOSI = 0, + SPI_SCLK, + SPI_CS, +}; + +struct stmp3xxx_lcd_dma_chain_info { + dma_addr_t *dma_addr_p; + unsigned offset; +}; + +enum { + STMP3XXX_LCD_PANEL_SYSTEM = 0, + STMP3XXX_LCD_PANEL_VSYNC, + STMP3XXX_LCD_PANEL_DOTCLK, + STMP3XXX_LCD_PANEL_DVI, +}; + +struct stmp3xxx_platform_bl_data; +struct stmp3xxx_platform_fb_entry { + char name[16]; + u16 x_res; + u16 y_res; + u16 bpp; + u32 cycle_time_ns; + int lcd_type; + int (*init_panel) (struct device * dev, dma_addr_t phys, int memsize, + struct stmp3xxx_platform_fb_entry * pentry); + void (*release_panel) (struct device * dev, + struct stmp3xxx_platform_fb_entry * pentry); + int (*blank_panel) (int blank); + void (*run_panel) (void); + void (*stop_panel) (void); + int (*pan_display) (dma_addr_t phys); + int (*update_panel) (void *p, + struct stmp3xxx_platform_fb_entry * pentry); + struct list_head link; + struct stmp3xxx_platform_bl_data *bl_data; +}; + +struct stmp3xxx_platform_fb_data { + struct list_head list; + struct stmp3xxx_platform_fb_entry *cur; +}; + +#define STMP3XXX_LCDIF_PANEL_INIT 1 +#define STMP3XXX_LCDIF_PANEL_RELEASE 2 + +struct stmp3xxx_platform_bl_data { + struct list_head list; + struct regulator *regulator; + int bl_gpio; + int bl_max_intensity; + int bl_cons_intensity; + int bl_default_intensity; + int (*init_bl) (struct stmp3xxx_platform_bl_data * data); + int (*set_bl_intensity) (struct stmp3xxx_platform_bl_data * data, + struct backlight_device * bd, int suspended); + void (*free_bl) (struct stmp3xxx_platform_bl_data * data); +}; + +static inline void stmp3xxx_lcd_register_entry(struct stmp3xxx_platform_fb_entry + *pentry, + struct stmp3xxx_platform_fb_data + *pdata) +{ + list_add_tail(&pentry->link, &pdata->list); + if (!pdata->cur) + pdata->cur = pentry; +} + +static inline void stmp3xxx_lcd_move_pentry_up(struct stmp3xxx_platform_fb_entry + *pentry, + struct stmp3xxx_platform_fb_data + *pdata) +{ + list_del(&pentry->link); + list_add(&pentry->link, &pdata->list); +} + +static inline int stmp3xxx_lcd_iterate_pdata(struct stmp3xxx_platform_fb_data + *pdata, + int (*func) (struct + stmp3xxx_platform_fb_entry + * pentry, void *data, + int ret_prev), + void *data) +{ + struct stmp3xxx_platform_fb_entry *pentry; + int ret = 0; + list_for_each_entry(pentry, &pdata->list, link) { + ret = func(pentry, data, ret); + } + return ret; +} + +static inline void stmp3xxx_lcd_set_bl_pdata(struct stmp3xxx_platform_bl_data + *pdata) +{ + extern struct platform_device stmp3xxx_backlight; + stmp3xxx_backlight.dev.platform_data = pdata; +} + +void stmp3xxx_init_lcdif(void); +int stmp3xxx_lcdif_dma_init(struct device *dev, dma_addr_t phys, int memsize, + int lcd_master); +void stmp3xxx_lcdif_dma_release(void); +void stmp3xxx_lcdif_run(void); +void stmp3xxx_lcdif_stop(void); +int stmp3xxx_lcdif_pan_display(dma_addr_t addr); + +int stmp3xxx_lcdif_register_client(struct notifier_block *nb); +void stmp3xxx_lcdif_unregister_client(struct notifier_block *nb); +void stmp3xxx_lcdif_notify_clients(unsigned long event, + struct stmp3xxx_platform_fb_entry *pentry); + +#ifndef FBIO_WAITFORVSYNC +#define FBIO_WAITFORVSYNC _IOW('F', 0x20, u_int32_t) +#endif + +#define LCD_DMA_CHANNEL 0 + +static inline void setup_dotclk_panel(u16 v_pulse_width, + u16 v_period, + u16 v_wait_cnt, + u16 v_active, + u16 h_pulse_width, + u16 h_period, + u16 h_wait_cnt, + u16 h_active, int enable_present) +{ + u32 val; + + stmp3xxx_clearl(BM_LCDIF_CTRL_DATA_SHIFT_DIR, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + + stmp3xxx_clearl(BM_LCDIF_CTRL_SHIFT_NUM_BITS, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + + stmp3xxx_clearl(BM_LCDIF_CTRL1_BYTE_PACKING_FORMAT, + REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + stmp3xxx_setl(BF(7, LCDIF_CTRL1_BYTE_PACKING_FORMAT) | + BM_LCDIF_CTRL1_RECOVER_ON_UNDERFLOW, + REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_TRANSFER_COUNT); + val &= ~(BM_LCDIF_TRANSFER_COUNT_V_COUNT | + BM_LCDIF_TRANSFER_COUNT_H_COUNT); + val |= BF(h_active, LCDIF_TRANSFER_COUNT_H_COUNT) | + BF(v_active, LCDIF_TRANSFER_COUNT_V_COUNT); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_TRANSFER_COUNT); + + stmp3xxx_clearl(BM_LCDIF_CTRL_VSYNC_MODE, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + stmp3xxx_clearl(BM_LCDIF_CTRL_WAIT_FOR_VSYNC_EDGE, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + stmp3xxx_clearl(BM_LCDIF_CTRL_DVI_MODE, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + stmp3xxx_setl(BM_LCDIF_CTRL_DOTCLK_MODE, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + stmp3xxx_setl(BM_LCDIF_CTRL_BYPASS_COUNT, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + + stmp3xxx_clearl(BM_LCDIF_CTRL_WORD_LENGTH | + BM_LCDIF_CTRL_INPUT_DATA_SWIZZLE | + BM_LCDIF_CTRL_LCD_DATABUS_WIDTH, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + stmp3xxx_setl(BF(3, LCDIF_CTRL_WORD_LENGTH) | /* 24 bit */ + BM_LCDIF_CTRL_DATA_SELECT | /* data mode */ + BF(0, LCDIF_CTRL_INPUT_DATA_SWIZZLE) | /* no swap */ + BF(3, LCDIF_CTRL_LCD_DATABUS_WIDTH), /* 24 bit */ + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_VDCTRL0); + val &= ~(BM_LCDIF_VDCTRL0_VSYNC_POL | + BM_LCDIF_VDCTRL0_HSYNC_POL | + BM_LCDIF_VDCTRL0_ENABLE_POL | + BM_LCDIF_VDCTRL0_DOTCLK_POL); + val |= BM_LCDIF_VDCTRL0_ENABLE_POL | + BM_LCDIF_VDCTRL0_DOTCLK_POL; + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_VDCTRL0); + + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_VDCTRL0); + val &= ~(BM_LCDIF_VDCTRL0_VSYNC_OEB); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_VDCTRL0); /* vsync is output */ + + /* + * need enable sig for true RGB i/f. Or, if not true RGB, leave it + * zero. + */ + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_VDCTRL0); + val |= BM_LCDIF_VDCTRL0_ENABLE_PRESENT; + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_VDCTRL0); + + /* + * For DOTCLK mode, count VSYNC_PERIOD in terms of complete hz lines + */ + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_VDCTRL0); + val &= ~(BM_LCDIF_VDCTRL0_VSYNC_PERIOD_UNIT | + BM_LCDIF_VDCTRL0_VSYNC_PULSE_WIDTH_UNIT); + val |= BM_LCDIF_VDCTRL0_VSYNC_PERIOD_UNIT | + BM_LCDIF_VDCTRL0_VSYNC_PULSE_WIDTH_UNIT; + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_VDCTRL0); + + stmp3xxx_clearl(BM_LCDIF_VDCTRL0_VSYNC_PULSE_WIDTH, + REGS_LCDIF_BASE + HW_LCDIF_VDCTRL0); + stmp3xxx_setl(v_pulse_width, REGS_LCDIF_BASE + HW_LCDIF_VDCTRL0); + + stmp3xxx_clearl(BM_LCDIF_VDCTRL1_VSYNC_PERIOD, + REGS_LCDIF_BASE + HW_LCDIF_VDCTRL1); + stmp3xxx_setl(v_period, REGS_LCDIF_BASE + HW_LCDIF_VDCTRL1); + + stmp3xxx_clearl(BM_LCDIF_VDCTRL2_HSYNC_PERIOD | + BM_LCDIF_VDCTRL2_HSYNC_PULSE_WIDTH, + REGS_LCDIF_BASE + HW_LCDIF_VDCTRL2); + + stmp3xxx_setl(BF(h_pulse_width, LCDIF_VDCTRL2_HSYNC_PULSE_WIDTH) | + BF(h_period, LCDIF_VDCTRL2_HSYNC_PERIOD), + REGS_LCDIF_BASE + HW_LCDIF_VDCTRL2); + + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_VDCTRL4); + val &= ~BM_LCDIF_VDCTRL4_DOTCLK_H_VALID_DATA_CNT; + val |= BF(h_active, LCDIF_VDCTRL4_DOTCLK_H_VALID_DATA_CNT); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_VDCTRL4); + + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_VDCTRL3); + val &= ~(BM_LCDIF_VDCTRL3_HORIZONTAL_WAIT_CNT | + BM_LCDIF_VDCTRL3_VERTICAL_WAIT_CNT); + val |= BF(h_wait_cnt, LCDIF_VDCTRL3_HORIZONTAL_WAIT_CNT) | + BF(v_wait_cnt, LCDIF_VDCTRL3_VERTICAL_WAIT_CNT); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_VDCTRL3); + + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_VDCTRL4); + val |= BM_LCDIF_VDCTRL4_SYNC_SIGNALS_ON; + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_VDCTRL4); +} + +static inline void release_dotclk_panel(void) +{ + stmp3xxx_clearl(BM_LCDIF_CTRL_DOTCLK_MODE, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + __raw_writel(0, REGS_LCDIF_BASE + HW_LCDIF_VDCTRL0); + __raw_writel(0, REGS_LCDIF_BASE + HW_LCDIF_VDCTRL1); + __raw_writel(0, REGS_LCDIF_BASE + HW_LCDIF_VDCTRL2); + __raw_writel(0, REGS_LCDIF_BASE + HW_LCDIF_VDCTRL3); +} + +static inline void dotclk_dma_chain_init(int memsize, dma_addr_t video_phys, + struct stmp3xxx_dma_descriptor + *video_dma_descriptor, + struct stmp3xxx_lcd_dma_chain_info + *dma_chain_info, + unsigned *dma_chain_info_pos) +{ + unsigned i, bytes_left; + dma_addr_t phys = video_phys; + bytes_left = memsize; + + for (i = 0; bytes_left > 0; ++i) { + unsigned this_chain = bytes_left < 0xff00 ? bytes_left : 0xff00; + /* Count of 0 in the DMA word means 65536 */ + unsigned xfer_count = this_chain & 65535; + stmp3xxx_dma_allocate_command(STMP3XXX_DMA + (LCD_DMA_CHANNEL, + STMP3XXX_BUS_APBH), + &video_dma_descriptor[i]); + if (i != 0) { + /* Chain previous command to this one */ + video_dma_descriptor[i - 1].command->next = + video_dma_descriptor[i].handle; + /* Enable DMA chaining, disable IRQ and semaphore + * on previous command + */ + video_dma_descriptor[i - 1].command->cmd &= + ~(BM_APBH_CHn_CMD_IRQONCMPLT | + BM_APBH_CHn_CMD_SEMAPHORE); + } + video_dma_descriptor[i].command->cmd = + BF(xfer_count, APBH_CHn_CMD_XFER_COUNT) | + BF(1, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_CHAIN | + BF(2, APBH_CHn_CMD_COMMAND); /* DMA read */ + video_dma_descriptor[i].command->pio_words[0] = + BM_LCDIF_CTRL_RUN | + BF(1, LCDIF_CTRL_INPUT_DATA_SWIZZLE) | + BM_LCDIF_CTRL_DATA_SHIFT_DIR | + BM_LCDIF_CTRL_DOTCLK_MODE | + BM_LCDIF_CTRL_BYPASS_COUNT | BM_LCDIF_CTRL_DATA_SELECT; + video_dma_descriptor[i].command->buf_ptr = phys; + dma_chain_info[*dma_chain_info_pos].dma_addr_p = + &video_dma_descriptor[i].command->buf_ptr; + dma_chain_info[*dma_chain_info_pos].offset = phys - video_phys; + ++*dma_chain_info_pos; + phys += this_chain; + bytes_left -= this_chain; + } + video_dma_descriptor[i - 1].command->next = + video_dma_descriptor[0].handle; + pr_debug("%s: Used %u DMA chains to cover %u bytes\n", __func__, i, + memsize); +} + +static inline void setup_dvi_panel(u16 h_active, u16 v_active, + u16 h_blanking, u16 v_lines, + u16 v1_blank_start, u16 v1_blank_end, + u16 v2_blank_start, u16 v2_blank_end, + u16 f1_start, u16 f1_end, + u16 f2_start, u16 f2_end) +{ + u32 val; + /* 32bit packed format (RGB) */ + stmp3xxx_clearl(BM_LCDIF_CTRL1_BYTE_PACKING_FORMAT, + REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + stmp3xxx_setl(BF(0x7, LCDIF_CTRL1_BYTE_PACKING_FORMAT) | + BM_LCDIF_CTRL1_RECOVER_ON_UNDERFLOW, + REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_TRANSFER_COUNT); + val &= ~(BM_LCDIF_TRANSFER_COUNT_V_COUNT | + BM_LCDIF_TRANSFER_COUNT_H_COUNT); + val |= BF(h_active, LCDIF_TRANSFER_COUNT_H_COUNT) | + BF(v_active, LCDIF_TRANSFER_COUNT_V_COUNT); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_TRANSFER_COUNT); + + /* set lcdif to DVI mode */ + stmp3xxx_setl(BM_LCDIF_CTRL_DVI_MODE, REGS_LCDIF_BASE + HW_LCDIF_CTRL); + stmp3xxx_clearl(BM_LCDIF_CTRL_VSYNC_MODE, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + stmp3xxx_clearl(BM_LCDIF_CTRL_DOTCLK_MODE, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + + stmp3xxx_setl(BM_LCDIF_CTRL_BYPASS_COUNT, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + /* convert input RGB -> YCbCr */ + stmp3xxx_setl(BM_LCDIF_CTRL_RGB_TO_YCBCR422_CSC, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + /* interlace odd and even fields */ + stmp3xxx_setl(BM_LCDIF_CTRL1_INTERLACE_FIELDS, + REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + + stmp3xxx_clearl(BM_LCDIF_CTRL_WORD_LENGTH | + BM_LCDIF_CTRL_INPUT_DATA_SWIZZLE | + BM_LCDIF_CTRL_LCD_DATABUS_WIDTH, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + stmp3xxx_setl(BF(3, LCDIF_CTRL_WORD_LENGTH) | /* 24 bit */ + BM_LCDIF_CTRL_DATA_SELECT | /* data mode */ + BF(0, LCDIF_CTRL_INPUT_DATA_SWIZZLE) | /* no swap */ + BF(1, LCDIF_CTRL_LCD_DATABUS_WIDTH), /* 8 bit */ + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + + /* LCDIF_DVI */ + /* set frame size */ + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_DVICTRL0); + val &= ~(BM_LCDIF_DVICTRL0_H_ACTIVE_CNT | + BM_LCDIF_DVICTRL0_H_BLANKING_CNT | + BM_LCDIF_DVICTRL0_V_LINES_CNT); + val |= BF(1440, LCDIF_DVICTRL0_H_ACTIVE_CNT) | + BF(h_blanking, LCDIF_DVICTRL0_H_BLANKING_CNT) | + BF(v_lines, LCDIF_DVICTRL0_V_LINES_CNT); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_DVICTRL0); + + /* set start/end of field-1 and start of field-2 */ + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_DVICTRL1); + val &= ~(BM_LCDIF_DVICTRL1_F1_START_LINE | + BM_LCDIF_DVICTRL1_F1_END_LINE | + BM_LCDIF_DVICTRL1_F2_START_LINE); + val |= BF(f1_start, LCDIF_DVICTRL1_F1_START_LINE) | + BF(f1_end, LCDIF_DVICTRL1_F1_END_LINE) | + BF(f2_start, LCDIF_DVICTRL1_F2_START_LINE); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_DVICTRL1); + + /* set first vertical blanking interval and end of filed-2 */ + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_DVICTRL2); + val &= ~(BM_LCDIF_DVICTRL2_F2_END_LINE | + BM_LCDIF_DVICTRL2_V1_BLANK_START_LINE | + BM_LCDIF_DVICTRL2_V1_BLANK_END_LINE); + val |= BF(f2_end, LCDIF_DVICTRL2_F2_END_LINE) | + BF(v1_blank_start, LCDIF_DVICTRL2_V1_BLANK_START_LINE) | + BF(v1_blank_end, LCDIF_DVICTRL2_V1_BLANK_END_LINE); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_DVICTRL2); + + /* set second vertical blanking interval */ + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_DVICTRL3); + val &= ~(BM_LCDIF_DVICTRL3_V2_BLANK_START_LINE | + BM_LCDIF_DVICTRL3_V2_BLANK_END_LINE); + val |= BF(v2_blank_start, LCDIF_DVICTRL3_V2_BLANK_START_LINE) | + BF(v2_blank_end, LCDIF_DVICTRL3_V2_BLANK_END_LINE); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_DVICTRL3); + + /* fill the rest area black color if the input frame + * is not 720 pixels/line + */ + if (h_active != 720) { + /* the input frame can't be less then (720-256) pixels/line */ + if (720 - h_active > 0xff) + h_active = 720 - 0xff; + + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_DVICTRL4); + val &= ~(BM_LCDIF_DVICTRL4_H_FILL_CNT | + BM_LCDIF_DVICTRL4_Y_FILL_VALUE | + BM_LCDIF_DVICTRL4_CB_FILL_VALUE | + BM_LCDIF_DVICTRL4_CR_FILL_VALUE); + val |= BF(720 - h_active, LCDIF_DVICTRL4_H_FILL_CNT) | + BF(16, LCDIF_DVICTRL4_Y_FILL_VALUE) | + BF(128, LCDIF_DVICTRL4_CB_FILL_VALUE) | + BF(128, LCDIF_DVICTRL4_CR_FILL_VALUE); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_DVICTRL4); + } + + /* Color Space Conversion RGB->YCbCr */ + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_CSC_COEFF0); + val &= ~(BM_LCDIF_CSC_COEFF0_C0 | + BM_LCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER); + val |= BF(0x41, LCDIF_CSC_COEFF0_C0) | + BF(3, LCDIF_CSC_COEFF0_CSC_SUBSAMPLE_FILTER); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_CSC_COEFF0); + + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_CSC_COEFF1); + val &= ~(BM_LCDIF_CSC_COEFF1_C1 | BM_LCDIF_CSC_COEFF1_C2); + val |= BF(0x81, LCDIF_CSC_COEFF1_C1) | + BF(0x19, LCDIF_CSC_COEFF1_C2); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_CSC_COEFF1); + + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_CSC_COEFF2); + val &= ~(BM_LCDIF_CSC_COEFF2_C3 | BM_LCDIF_CSC_COEFF2_C4); + val |= BF(0x3DB, LCDIF_CSC_COEFF2_C3) | + BF(0x3B6, LCDIF_CSC_COEFF2_C4); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_CSC_COEFF2); + + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_CSC_COEFF3); + val &= ~(BM_LCDIF_CSC_COEFF3_C5 | BM_LCDIF_CSC_COEFF3_C6); + val |= BF(0x70, LCDIF_CSC_COEFF3_C5) | + BF(0x70, LCDIF_CSC_COEFF3_C6); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_CSC_COEFF3); + + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_CSC_COEFF4); + val &= ~(BM_LCDIF_CSC_COEFF4_C7 | BM_LCDIF_CSC_COEFF4_C8); + val |= BF(0x3A2, LCDIF_CSC_COEFF4_C7) | BF(0x3EE, LCDIF_CSC_COEFF4_C8); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_CSC_COEFF4); + + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_CSC_OFFSET); + val &= ~(BM_LCDIF_CSC_OFFSET_CBCR_OFFSET | BM_LCDIF_CSC_OFFSET_Y_OFFSET); + val |= BF(0x80, LCDIF_CSC_OFFSET_CBCR_OFFSET) | + BF(0x10, LCDIF_CSC_OFFSET_Y_OFFSET); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_CSC_OFFSET); + + val = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_CSC_LIMIT); + val &= ~(BM_LCDIF_CSC_LIMIT_CBCR_MIN | + BM_LCDIF_CSC_LIMIT_CBCR_MAX | + BM_LCDIF_CSC_LIMIT_Y_MIN | + BM_LCDIF_CSC_LIMIT_Y_MAX); + val |= BF(16, LCDIF_CSC_LIMIT_CBCR_MIN) | + BF(240, LCDIF_CSC_LIMIT_CBCR_MAX) | + BF(16, LCDIF_CSC_LIMIT_Y_MIN) | + BF(235, LCDIF_CSC_LIMIT_Y_MAX); + __raw_writel(val, REGS_LCDIF_BASE + HW_LCDIF_CSC_LIMIT); +} + +static inline void release_dvi_panel(void) +{ + stmp3xxx_clearl(BM_LCDIF_CTRL_DVI_MODE, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); +} + +#endif /* _ARCH_ARM_LCDIF_H */ diff --git a/arch/arm/mach-stmp378x/include/mach/pins.h b/arch/arm/mach-stmp378x/include/mach/pins.h index 93f952d35969..f36296a9a2c0 100644 --- a/arch/arm/mach-stmp378x/include/mach/pins.h +++ b/arch/arm/mach-stmp378x/include/mach/pins.h @@ -19,6 +19,32 @@ #define __ASM_ARCH_PINS_H /* + * The number of pin banks and pins per a bank on STMP378x + */ +#define STMP3XXX_PINMUX_NR_BANKS 4 +#define STMP3XXX_PINMUX_BANK_SIZE 32 + +/* + * Macro to convert a pin bank/number pair to a raw pin number + * STMP3XXX_PINMUX_BANK_SIZE and STMP3XXX_PINMUX_NR_BANKS should be + * defined before including this header. + */ +#define STMP3XXX_PINID(bank, pin) (bank * STMP3XXX_PINMUX_BANK_SIZE + pin) +#define STMP3XXX_PINID_TO_BANK(pinid) (pinid / STMP3XXX_PINMUX_BANK_SIZE) +#define STMP3XXX_PINID_TO_PINNUM(pinid) (pinid % STMP3XXX_PINMUX_BANK_SIZE) + +/* + * Special invalid pin identificator to show a pin doesn't exist + */ +#define PINID_NO_PIN STMP3XXX_PINID(STMP3XXX_PINMUX_NR_BANKS, 0) + +static inline int stmp3xxx_valid_pin(unsigned pin) +{ + return STMP3XXX_PINID_TO_BANK(pin) < STMP3XXX_PINMUX_NR_BANKS && + STMP3XXX_PINID_TO_PINNUM(pin) < STMP3XXX_PINMUX_BANK_SIZE; +} + +/* * Define all STMP378x pins, a pin name corresponds to a STMP378x hardware * interface this pin belongs to. */ diff --git a/arch/arm/mach-stmp378x/include/mach/regs-apbh.h b/arch/arm/mach-stmp378x/include/mach/regs-apbh.h index dbcf85b6ac2a..af6371168a4b 100644 --- a/arch/arm/mach-stmp378x/include/mach/regs-apbh.h +++ b/arch/arm/mach-stmp378x/include/mach/regs-apbh.h @@ -56,7 +56,7 @@ #define HW_APBH_CH14_NXTCMDAR (0x50 + 14 * 0x70) #define HW_APBH_CH15_NXTCMDAR (0x50 + 15 * 0x70) -#define HW_APBH_CHn_NXTCMDAR 0x50 +#define HW_APBH_CHn_NXTCMDAR(n) (0x50 + n * 0x70) #define BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER 0 #define BV_APBH_CHn_CMD_COMMAND__DMA_WRITE 1 @@ -92,10 +92,24 @@ #define HW_APBH_CH14_SEMA (0x80 + 14 * 0x70) #define HW_APBH_CH15_SEMA (0x80 + 15 * 0x70) -#define HW_APBH_CHn_SEMA 0x80 +#define HW_APBH_CHn_SEMA(n) (0x80 + n * 0x70) #define BM_APBH_CHn_SEMA_INCREMENT_SEMA 0x000000FF #define BP_APBH_CHn_SEMA_INCREMENT_SEMA 0 #define BM_APBH_CHn_SEMA_PHORE 0x00FF0000 #define BP_APBH_CHn_SEMA_PHORE 16 +#define BV_SSP_CTRL0_BUS_WIDTH__ONE_BIT 0x0 +#define BV_SSP_CTRL0_BUS_WIDTH__FOUR_BIT 0x1 +#define BV_SSP_CTRL0_BUS_WIDTH__EIGHT_BIT 0x2 + +#define BV_SSP_CTRL1_WORD_LENGTH__FOUR_BITS 0x3 +#define BV_SSP_CTRL1_WORD_LENGTH__EIGHT_BITS 0x7 +#define BV_SSP_CTRL1_WORD_LENGTH__SIXTEEN_BITS 0xF + +#define BV_SSP_CTRL1_SSP_MODE__SPI 0x0 +#define BV_SSP_CTRL1_SSP_MODE__SSI 0x1 +#define BV_SSP_CTRL1_SSP_MODE__SD_MMC 0x3 +#define BV_SSP_CTRL1_SSP_MODE__MS 0x4 +#define BV_SSP_CTRL1_SSP_MODE__CE_ATA 0x7 + #endif diff --git a/arch/arm/mach-stmp378x/include/mach/regs-apbx.h b/arch/arm/mach-stmp378x/include/mach/regs-apbx.h index 3b934a4d27f0..06d5f8374e00 100644 --- a/arch/arm/mach-stmp378x/include/mach/regs-apbx.h +++ b/arch/arm/mach-stmp378x/include/mach/regs-apbx.h @@ -56,7 +56,7 @@ #define HW_APBX_CH14_NXTCMDAR (0x110 + 14 * 0x70) #define HW_APBX_CH15_NXTCMDAR (0x110 + 15 * 0x70) -#define HW_APBX_CHn_NXTCMDAR 0x110 +#define HW_APBX_CHn_NXTCMDAR(n) (0x110 + n * 0x70) #define BM_APBX_CHn_CMD_COMMAND 0x00000003 #define BP_APBX_CHn_CMD_COMMAND 0 #define BV_APBX_CHn_CMD_COMMAND__NO_DMA_XFER 0 @@ -90,7 +90,7 @@ #define HW_APBX_CH14_BAR (0x130 + 14 * 0x70) #define HW_APBX_CH15_BAR (0x130 + 15 * 0x70) -#define HW_APBX_CHn_BAR 0x130 +#define HW_APBX_CHn_BAR(n) (0x130 + n * 0x70) #define HW_APBX_CH0_SEMA (0x140 + 0 * 0x70) #define HW_APBX_CH1_SEMA (0x140 + 1 * 0x70) @@ -109,7 +109,7 @@ #define HW_APBX_CH14_SEMA (0x140 + 14 * 0x70) #define HW_APBX_CH15_SEMA (0x140 + 15 * 0x70) -#define HW_APBX_CHn_SEMA 0x140 +#define HW_APBX_CHn_SEMA(n) (0x140 + n * 0x70) #define BM_APBX_CHn_SEMA_INCREMENT_SEMA 0x000000FF #define BP_APBX_CHn_SEMA_INCREMENT_SEMA 0 #define BM_APBX_CHn_SEMA_PHORE 0x00FF0000 diff --git a/arch/arm/mach-stmp378x/include/mach/regs-audioin.h b/arch/arm/mach-stmp378x/include/mach/regs-audioin.h index 641ac6126f83..a62a6f33f4ed 100644 --- a/arch/arm/mach-stmp378x/include/mach/regs-audioin.h +++ b/arch/arm/mach-stmp378x/include/mach/regs-audioin.h @@ -55,9 +55,19 @@ #define BP_AUDIOIN_ADCVOL_SELECT_LEFT 12 #define BM_AUDIOIN_ADCVOL_MUTE 0x01000000 +#define BP_AUDIOIN_ADCVOLUME_VOLUME_LEFT 16 +#define BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT 0x00FF0000 +#define BM_AUDIOIN_ADCVOLUME_VOLUME_UPDATE_RIGHT 0x00001000 +#define BP_AUDIOIN_ADCVOLUME_VOLUME_RIGHT 0 +#define BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT 0x000000FF + #define HW_AUDIOIN_MICLINE 0x60 #define HW_AUDIOIN_ANACLKCTRL 0x70 #define BM_AUDIOIN_ANACLKCTRL_CLKGATE 0x80000000 #define HW_AUDIOIN_DATA 0x80 + +#define BM_AUDIOIN_MICLINE_MIC_SELECT 0x01000000 +#define BP_AUDIOIN_MICLINE_MIC_RESISTOR 20 +#define BM_AUDIOIN_MICLINE_MIC_RESISTOR 0x00300000 diff --git a/arch/arm/mach-stmp378x/include/mach/regs-audioout.h b/arch/arm/mach-stmp378x/include/mach/regs-audioout.h index f533e23694a0..59c5328e9577 100644 --- a/arch/arm/mach-stmp378x/include/mach/regs-audioout.h +++ b/arch/arm/mach-stmp378x/include/mach/regs-audioout.h @@ -51,6 +51,13 @@ #define HW_AUDIOOUT_DACDEBUG 0x40 +#define BP_AUDIOOUT_DACVOLUME_VOLUME_LEFT 16 +#define BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT 0x00FF0000 +#define BM_AUDIOOUT_DACVOLUME_VOLUME_UPDATE_RIGHT 0x00001000 +#define BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT 0x00000100 +#define BP_AUDIOOUT_DACVOLUME_VOLUME_RIGHT 0 +#define BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT 0x000000FF + #define HW_AUDIOOUT_HPVOL 0x50 #define BM_AUDIOOUT_HPVOL_MUTE 0x01000000 #define BM_AUDIOOUT_HPVOL_EN_MSTR_ZCD 0x02000000 diff --git a/arch/arm/mach-stmp378x/include/mach/regs-dcp.h b/arch/arm/mach-stmp378x/include/mach/regs-dcp.h index fdedd00c0e28..ac53087f39c2 100644 --- a/arch/arm/mach-stmp378x/include/mach/regs-dcp.h +++ b/arch/arm/mach-stmp378x/include/mach/regs-dcp.h @@ -63,19 +63,22 @@ #define BM_DCP_PACKET2_CIPHER_CFG 0xFF000000 #define BP_DCP_PACKET2_CIPHER_CFG 24 +#define BV_DCP_PACKET2_CIPHER_MODE__ECB 0x00 +#define BV_DCP_PACKET2_CIPHER_SELECT__AES128 0x00 + #define HW_DCP_CH0CMDPTR (0x100 + 0 * 0x40) #define HW_DCP_CH1CMDPTR (0x100 + 1 * 0x40) #define HW_DCP_CH2CMDPTR (0x100 + 2 * 0x40) #define HW_DCP_CH3CMDPTR (0x100 + 3 * 0x40) -#define HW_DCP_CHnCMDPTR 0x100 +#define HW_DCP_CHnCMDPTR(n) (0x100 + n * 0x40) #define HW_DCP_CH0SEMA (0x110 + 0 * 0x40) #define HW_DCP_CH1SEMA (0x110 + 1 * 0x40) #define HW_DCP_CH2SEMA (0x110 + 2 * 0x40) #define HW_DCP_CH3SEMA (0x110 + 3 * 0x40) -#define HW_DCP_CHnSEMA 0x110 +#define HW_DCP_CHnSEMA(n) (0x110 + n * 0x40) #define BM_DCP_CHnSEMA_INCREMENT 0x000000FF #define BP_DCP_CHnSEMA_INCREMENT 0 @@ -84,4 +87,24 @@ #define HW_DCP_CH2STAT (0x120 + 2 * 0x40) #define HW_DCP_CH3STAT (0x120 + 3 * 0x40) -#define HW_DCP_CHnSTAT 0x120 +#define HW_DCP_CHnSTAT(n) (0x120 + n * 0x40) + +#define BV_DCP_CTRL_CHANNEL_INTERRUPT_ENABLE__CH0 0x01 +#define BV_DCP_CTRL_CHANNEL_INTERRUPT_ENABLE__CH1 0x02 +#define BV_DCP_CTRL_CHANNEL_INTERRUPT_ENABLE__CH2 0x04 +#define BV_DCP_CTRL_CHANNEL_INTERRUPT_ENABLE__CH3 0x08 + +#define HW_DCP_PACKET2_ADDR (REGS_DCP_BASE + 0x000000a0) +#define BP_DCP_PACKET2_CIPHER_CFG 24 +#define BM_DCP_PACKET2_CIPHER_CFG 0xFF000000 +#define BP_DCP_PACKET2_HASH_SELECT 16 +#define BM_DCP_PACKET2_HASH_SELECT 0x000F0000 +#define BV_DCP_PACKET2_HASH_SELECT__SHA1 0x00 +#define BV_DCP_PACKET2_HASH_SELECT__CRC32 0x01 +#define BP_DCP_PACKET2_KEY_SELECT 8 + +#define BV_DCP_PACKET2_CIPHER_MODE__ECB 0x00 +#define BV_DCP_PACKET2_CIPHER_MODE__CBC 0x01 +#define BP_DCP_PACKET2_CIPHER_SELECT 0 +#define BM_DCP_PACKET2_CIPHER_SELECT 0x0000000F +#define BV_DCP_PACKET2_CIPHER_SELECT__AES128 0x00 diff --git a/arch/arm/mach-stmp378x/include/mach/regs-gpmi.h b/arch/arm/mach-stmp378x/include/mach/regs-gpmi.h index 2cc8bbe91687..47a150bbb582 100644 --- a/arch/arm/mach-stmp378x/include/mach/regs-gpmi.h +++ b/arch/arm/mach-stmp378x/include/mach/regs-gpmi.h @@ -53,6 +53,9 @@ #define BV_GPMI_ECCCTRL_ECC_CMD__ENCODE_4_BIT 1 #define BV_GPMI_ECCCTRL_ECC_CMD__DECODE_8_BIT 2 #define BV_GPMI_ECCCTRL_ECC_CMD__ENCODE_8_BIT 3 +#define BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY 0x100 +#define BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE 0x1FF +#define BV_GPMI_ECCCTRL_BUFFER_MASK__AUXILIARY 0x100 #define HW_GPMI_CTRL1 0x60 #define BM_GPMI_CTRL1_GPMI_MODE 0x00000001 diff --git a/arch/arm/mach-stmp378x/include/mach/regs-icoll.h b/arch/arm/mach-stmp378x/include/mach/regs-icoll.h index f996e80f40e7..57febd8559e7 100644 --- a/arch/arm/mach-stmp378x/include/mach/regs-icoll.h +++ b/arch/arm/mach-stmp378x/include/mach/regs-icoll.h @@ -37,9 +37,14 @@ #define HW_ICOLL_STAT 0x70 -#define HW_ICOLL_INTERRUPTn 0x120 - -#define HW_ICOLL_INTERRUPTn 0x120 +#define HW_ICOLL_INTERRUPTn(n) (0x120 + n * 0x10) #define BM_ICOLL_INTERRUPTn_ENABLE 0x00000004 +#define HW_ICOLL_PRIORITYn(n) (0x60 + n * 0x10) + +#define BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL0 0x1 +#define BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL1 0x2 +#define BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL2 0x4 +#define BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL3 0x8 + #endif diff --git a/arch/arm/mach-stmp378x/include/mach/regs-lradc.h b/arch/arm/mach-stmp378x/include/mach/regs-lradc.h index cb8cb06f8277..3b76d5f6bed8 100644 --- a/arch/arm/mach-stmp378x/include/mach/regs-lradc.h +++ b/arch/arm/mach-stmp378x/include/mach/regs-lradc.h @@ -51,6 +51,9 @@ #define BM_LRADC_CTRL2_BL_ENABLE 0x00400000 #define BM_LRADC_CTRL2_DIVIDE_BY_TWO 0xFF000000 #define BP_LRADC_CTRL2_DIVIDE_BY_TWO 24 +#define BM_LRADC_CTRL2_TEMPSENSE_PWD 0x00008000 +#define BV_LRADC_CTRL2_TEMPSENSE_PWD__ENABLE 0x0 +#define BV_LRADC_CTRL2_TEMPSENSE_PWD__DISABLE 0x1 #define HW_LRADC_CTRL3 0x30 #define BM_LRADC_CTRL3_CYCLE_TIME 0x00000300 @@ -69,7 +72,7 @@ #define HW_LRADC_CH6 (0x50 + 6 * 0x10) #define HW_LRADC_CH7 (0x50 + 7 * 0x10) -#define HW_LRADC_CHn 0x50 +#define HW_LRADC_CHn(chn) (0x50 + chn * 0x10) #define BM_LRADC_CHn_VALUE 0x0003FFFF #define BP_LRADC_CHn_VALUE 0 #define BM_LRADC_CHn_NUM_SAMPLES 0x1F000000 @@ -81,7 +84,7 @@ #define HW_LRADC_DELAY2 (0xD0 + 2 * 0x10) #define HW_LRADC_DELAY3 (0xD0 + 3 * 0x10) -#define HW_LRADC_DELAYn 0xD0 +#define HW_LRADC_DELAYn(chn) (0xD0 + chn * 0x10) #define BM_LRADC_DELAYn_DELAY 0x000007FF #define BP_LRADC_DELAYn_DELAY 0 #define BM_LRADC_DELAYn_LOOP_COUNT 0x0000F800 @@ -97,3 +100,17 @@ #define BP_LRADC_CTRL4_LRADC6SELECT 24 #define BM_LRADC_CTRL4_LRADC7SELECT 0xF0000000 #define BP_LRADC_CTRL4_LRADC7SELECT 28 + +#define BV_TIMROT_ROTCTRL_SELECT_A__ROTARYA 0x6 +#define BV_TIMROT_ROTCTRL_SELECT_A__ROTARYB 0x7 + +#define HW_LRADC_CONVERSION 0x130 +#define BM_LRADC_CONVERSION_AUTOMATIC 0x00100000 +#define BV_LRADC_CONVERSION_AUTOMATIC__DISABLE 0x0 +#define BV_LRADC_CONVERSION_AUTOMATIC__ENABLE 0x1 +#define BP_LRADC_CONVERSION_SCALE_FACTOR 16 +#define BM_LRADC_CONVERSION_SCALE_FACTOR 0x00030000 + +#define BM_LRADC_CTRL1_LRADC7_IRQ 0x00000080 +#define BV_LRADC_CTRL1_LRADC7_IRQ__CLEAR 0x0 +#define BV_LRADC_CTRL1_LRADC7_IRQ__PENDING 0x1 diff --git a/arch/arm/mach-stmp378x/include/mach/regs-ocotp.h b/arch/arm/mach-stmp378x/include/mach/regs-ocotp.h index f0af64d9937e..06f151f9e55d 100644 --- a/arch/arm/mach-stmp378x/include/mach/regs-ocotp.h +++ b/arch/arm/mach-stmp378x/include/mach/regs-ocotp.h @@ -29,6 +29,8 @@ #define BM_OCOTP_CTRL_RELOAD_SHADOWS 0x00002000 #define BM_OCOTP_CTRL_WR_UNLOCK 0xFFFF0000 #define BP_OCOTP_CTRL_WR_UNLOCK 16 +#define BP_OCOTP_CTRL_ADDR 0 +#define BM_OCOTP_CTRL_ADDR 0x0000001F #define HW_OCOTP_DATA 0x10 @@ -37,4 +39,6 @@ #define HW_OCOTP_CUST2 (0x20 + 2 * 0x10) #define HW_OCOTP_CUST3 (0x20 + 3 * 0x10) -#define HW_OCOTP_CUSTn 0x20 +#define HW_OCOTP_CUSTn(n) (0x20 + n * 0x10) + +#define HW_OCOTP_CUSTCAP 0x110 diff --git a/arch/arm/mach-stmp378x/include/mach/regs-power.h b/arch/arm/mach-stmp378x/include/mach/regs-power.h index e454c830f076..e3438ecb6eab 100644 --- a/arch/arm/mach-stmp378x/include/mach/regs-power.h +++ b/arch/arm/mach-stmp378x/include/mach/regs-power.h @@ -35,6 +35,8 @@ #define HW_POWER_5VCTRL 0x10 #define BM_POWER_5VCTRL_ENABLE_LINREG_ILIMIT 0x00000040 +#define HW_POWER_LOOPCTRL 0xb0 + #define HW_POWER_MINPWR 0x20 #define HW_POWER_CHARGE 0x30 @@ -60,4 +62,235 @@ #define BM_POWER_DEBUG_AVALIDPIOLOCK 0x00000004 #define BM_POWER_DEBUG_VBUSVALIDPIOLOCK 0x00000008 +#define BP_POWER_STS_PSWITCH 20 +#define BM_POWER_STS_PSWITCH 0x00300000 +#define BM_POWER_CTRL_POLARITY_PSWITCH 0x00040000 + +#define BM_POWER_CTRL_CLKGATE 0x40000000 +#define BM_POWER_CTRL_PSWITCH_MID_TRAN 0x08000000 +#define BM_POWER_CTRL_DCDC4P2_BO_IRQ 0x01000000 +#define BM_POWER_CTRL_ENIRQ_DCDC4P2_BO 0x00800000 +#define BM_POWER_CTRL_VDD5V_DROOP_IRQ 0x00400000 +#define BM_POWER_CTRL_ENIRQ_VDD5V_DROOP 0x00200000 +#define BM_POWER_CTRL_PSWITCH_IRQ 0x00100000 +#define BM_POWER_CTRL_PSWITCH_IRQ_SRC 0x00080000 +#define BM_POWER_CTRL_POLARITY_PSWITCH 0x00040000 +#define BM_POWER_CTRL_ENIRQ_PSWITCH 0x00020000 +#define BM_POWER_CTRL_POLARITY_DC_OK 0x00010000 +#define BM_POWER_CTRL_DC_OK_IRQ 0x00008000 +#define BM_POWER_CTRL_ENIRQ_DC_OK 0x00004000 +#define BM_POWER_CTRL_BATT_BO_IRQ 0x00002000 +#define BM_POWER_CTRL_ENIRQBATT_BO 0x00001000 +#define BM_POWER_CTRL_VDDIO_BO_IRQ 0x00000800 +#define BM_POWER_CTRL_ENIRQ_VDDIO_BO 0x00000400 +#define BM_POWER_CTRL_VDDA_BO_IRQ 0x00000200 +#define BM_POWER_CTRL_ENIRQ_VDDA_BO 0x00000100 +#define BM_POWER_CTRL_VDDD_BO_IRQ 0x00000080 +#define BM_POWER_CTRL_ENIRQ_VDDD_BO 0x00000040 +#define BM_POWER_CTRL_POLARITY_VBUSVALID 0x00000020 +#define BM_POWER_CTRL_VBUSVALID_IRQ 0x00000010 +#define BM_POWER_CTRL_ENIRQ_VBUS_VALID 0x00000008 +#define BM_POWER_CTRL_POLARITY_VDD5V_GT_VDDIO 0x00000004 +#define BM_POWER_CTRL_VDD5V_GT_VDDIO_IRQ 0x00000002 +#define BM_POWER_CTRL_ENIRQ_VDD5V_GT_VDDIO 0x00000001 + +#define BP_POWER_5VCTRL_VBUSDROOP_TRSH 28 +#define BM_POWER_5VCTRL_VBUSDROOP_TRSH 0x30000000 +#define BP_POWER_5VCTRL_HEADROOM_ADJ 24 +#define BM_POWER_5VCTRL_HEADROOM_ADJ 0x07000000 +#define BM_POWER_5VCTRL_PWD_CHARGE_4P2 0x00100000 +#define BP_POWER_5VCTRL_CHARGE_4P2_ILIMIT 12 +#define BM_POWER_5VCTRL_CHARGE_4P2_ILIMIT 0x0003F000 +#define BP_POWER_5VCTRL_VBUSVALID_TRSH 8 +#define BM_POWER_5VCTRL_VBUSVALID_TRSH 0x00000700 +#define BM_POWER_5VCTRL_PWDN_5VBRNOUT 0x00000080 +#define BM_POWER_5VCTRL_ENABLE_LINREG_ILIMIT 0x00000040 +#define BM_POWER_5VCTRL_DCDC_XFER 0x00000020 +#define BM_POWER_5VCTRL_VBUSVALID_5VDETECT 0x00000010 +#define BM_POWER_5VCTRL_VBUSVALID_TO_B 0x00000008 +#define BM_POWER_5VCTRL_ILIMIT_EQ_ZERO 0x00000004 +#define BM_POWER_5VCTRL_PWRUP_VBUS_CMPS 0x00000002 +#define BM_POWER_5VCTRL_ENABLE_DCDC 0x00000001 + +#define BM_POWER_MINPWR_LOWPWR_4P2 0x00004000 +#define BM_POWER_MINPWR_VDAC_DUMP_CTRL 0x00002000 +#define BM_POWER_MINPWR_PWD_BO 0x00001000 +#define BM_POWER_MINPWR_USE_VDDXTAL_VBG 0x00000800 +#define BM_POWER_MINPWR_PWD_ANA_CMPS 0x00000400 +#define BM_POWER_MINPWR_ENABLE_OSC 0x00000200 +#define BM_POWER_MINPWR_SELECT_OSC 0x00000100 +#define BM_POWER_MINPWR_VBG_OFF 0x00000080 +#define BM_POWER_MINPWR_DOUBLE_FETS 0x00000040 +#define BM_POWER_MINPWR_HALF_FETS 0x00000020 +#define BM_POWER_MINPWR_LESSANA_I 0x00000010 +#define BM_POWER_MINPWR_PWD_XTAL24 0x00000008 +#define BM_POWER_MINPWR_DC_STOPCLK 0x00000004 +#define BM_POWER_MINPWR_EN_DC_PFM 0x00000002 +#define BM_POWER_MINPWR_DC_HALFCLK 0x00000001 + +#define BP_POWER_CHARGE_ADJ_VOLT 24 +#define BM_POWER_CHARGE_ADJ_VOLT 0x07000000 +#define BM_POWER_CHARGE_RSRVD3 0x00800000 +#define BM_POWER_CHARGE_ENABLE_LOAD 0x00400000 +#define BM_POWER_CHARGE_ENABLE_CHARGER_RESISTORS 0x00200000 +#define BM_POWER_CHARGE_ENABLE_FAULT_DETECT 0x00100000 +#define BM_POWER_CHARGE_CHRG_STS_OFF 0x00080000 +#define BM_POWER_CHARGE_USE_EXTERN_R 0x00020000 +#define BM_POWER_CHARGE_PWD_BATTCHRG 0x00010000 +#define BP_POWER_CHARGE_STOP_ILIMIT 8 +#define BM_POWER_CHARGE_STOP_ILIMIT 0x00000F00 +#define BP_POWER_CHARGE_BATTCHRG_I 0 +#define BM_POWER_CHARGE_BATTCHRG_I 0x0000003F + +#define BP_POWER_VDDDCTRL_ADJTN 28 +#define BM_POWER_VDDDCTRL_ADJTN 0xF0000000 +#define BM_POWER_VDDDCTRL_PWDN_BRNOUT 0x00800000 +#define BM_POWER_VDDDCTRL_DISABLE_STEPPING 0x00400000 +#define BM_POWER_VDDDCTRL_ENABLE_LINREG 0x00200000 +#define BM_POWER_VDDDCTRL_DISABLE_FET 0x00100000 +#define BP_POWER_VDDDCTRL_LINREG_OFFSET 16 +#define BM_POWER_VDDDCTRL_LINREG_OFFSET 0x00030000 +#define BP_POWER_VDDDCTRL_BO_OFFSET 8 +#define BM_POWER_VDDDCTRL_BO_OFFSET 0x00000700 +#define BP_POWER_VDDDCTRL_TRG 0 +#define BM_POWER_VDDDCTRL_TRG 0x0000001F + +#define BM_POWER_VDDACTRL_PWDN_BRNOUT 0x00080000 +#define BM_POWER_VDDACTRL_DISABLE_STEPPING 0x00040000 +#define BM_POWER_VDDACTRL_ENABLE_LINREG 0x00020000 +#define BM_POWER_VDDACTRL_DISABLE_FET 0x00010000 +#define BP_POWER_VDDACTRL_LINREG_OFFSET 12 +#define BM_POWER_VDDACTRL_LINREG_OFFSET 0x00003000 +#define BP_POWER_VDDACTRL_BO_OFFSET 8 +#define BM_POWER_VDDACTRL_BO_OFFSET 0x00000700 +#define BP_POWER_VDDACTRL_TRG 0 +#define BM_POWER_VDDACTRL_TRG 0x0000001F + +#define BP_POWER_VDDIOCTRL_ADJTN 20 +#define BM_POWER_VDDIOCTRL_ADJTN 0x00F00000 +#define BM_POWER_VDDIOCTRL_PWDN_BRNOUT 0x00040000 +#define BM_POWER_VDDIOCTRL_DISABLE_STEPPING 0x00020000 +#define BM_POWER_VDDIOCTRL_DISABLE_FET 0x00010000 +#define BP_POWER_VDDIOCTRL_LINREG_OFFSET 12 +#define BM_POWER_VDDIOCTRL_LINREG_OFFSET 0x00003000 +#define BP_POWER_VDDIOCTRL_BO_OFFSET 8 +#define BM_POWER_VDDIOCTRL_BO_OFFSET 0x00000700 +#define BP_POWER_VDDIOCTRL_TRG 0 +#define BM_POWER_VDDIOCTRL_TRG 0x0000001F + +#define HW_POWER_VDDMEMCTRL (REGS_POWER_BASE + 0x00000070) +#define BM_POWER_VDDMEMCTRL_PULLDOWN_ACTIVE 0x00000400 +#define BM_POWER_VDDMEMCTRL_ENABLE_ILIMIT 0x00000200 +#define BM_POWER_VDDMEMCTRL_ENABLE_LINREG 0x00000100 +#define BP_POWER_VDDMEMCTRL_TRG 0 +#define BM_POWER_VDDMEMCTRL_TRG 0x0000001F + +#define HW_POWER_DCDC4P2 (REGS_POWER_BASE + 0x00000080) +#define BP_POWER_DCDC4P2_DROPOUT_CTRL 28 +#define BM_POWER_DCDC4P2_DROPOUT_CTRL 0xF0000000 + +#define BP_POWER_DCDC4P2_ISTEAL_THRESH 24 +#define BM_POWER_DCDC4P2_ISTEAL_THRESH 0x03000000 +#define BM_POWER_DCDC4P2_ENABLE_4P2 0x00800000 +#define BM_POWER_DCDC4P2_ENABLE_DCDC 0x00400000 +#define BM_POWER_DCDC4P2_HYST_DIR 0x00200000 +#define BM_POWER_DCDC4P2_HYST_THRESH 0x00100000 +#define BP_POWER_DCDC4P2_TRG 16 +#define BM_POWER_DCDC4P2_TRG 0x00070000 +#define BP_POWER_DCDC4P2_BO 8 +#define BM_POWER_DCDC4P2_BO 0x00001F00 +#define BP_POWER_DCDC4P2_CMPTRIP 0 +#define BM_POWER_DCDC4P2_CMPTRIP 0x0000001F + +#define HW_POWER_MISC (REGS_POWER_BASE + 0x00000090) +#define BP_POWER_MISC_FREQSEL 4 +#define BM_POWER_MISC_FREQSEL 0x00000070 +#define BM_POWER_MISC_RSRVD1 0x00000008 +#define BM_POWER_MISC_DELAY_TIMING 0x00000004 +#define BM_POWER_MISC_TEST 0x00000002 +#define BM_POWER_MISC_SEL_PLLCLK 0x00000001 + +#define HW_POWER_DCLIMITS (REGS_POWER_BASE + 0x000000a0) +#define BP_POWER_DCLIMITS_POSLIMIT_BUCK 8 +#define BM_POWER_DCLIMITS_POSLIMIT_BUCK 0x00007F00 +#define BP_POWER_DCLIMITS_NEGLIMIT 0 +#define BM_POWER_DCLIMITS_NEGLIMIT 0x0000007F + +#define BM_POWER_LOOPCTRL_TOGGLE_DIF 0x00100000 +#define BM_POWER_LOOPCTRL_HYST_SIGN 0x00080000 +#define BM_POWER_LOOPCTRL_EN_CM_HYST 0x00040000 +#define BM_POWER_LOOPCTRL_EN_DF_HYST 0x00020000 +#define BM_POWER_LOOPCTRL_CM_HYST_THRESH 0x00010000 +#define BM_POWER_LOOPCTRL_DF_HYST_THRESH 0x00008000 +#define BM_POWER_LOOPCTRL_RCSCALE_THRESH 0x00004000 +#define BP_POWER_LOOPCTRL_EN_RCSCALE 12 +#define BM_POWER_LOOPCTRL_EN_RCSCALE 0x00003000 +#define BP_POWER_LOOPCTRL_DC_FF 8 +#define BM_POWER_LOOPCTRL_DC_FF 0x00000700 +#define BP_POWER_LOOPCTRL_DC_R 4 +#define BM_POWER_LOOPCTRL_DC_R 0x000000F0 +#define BP_POWER_LOOPCTRL_DC_C 0 +#define BM_POWER_LOOPCTRL_DC_C 0x00000003 + +#define BP_POWER_STS_PWRUP_SOURCE 24 +#define BM_POWER_STS_PWRUP_SOURCE 0x3F000000 +#define BP_POWER_STS_PSWITCH 20 +#define BM_POWER_STS_PSWITCH 0x00300000 +#define BM_POWER_STS_AVALID_STATUS 0x00020000 +#define BM_POWER_STS_BVALID_STATUS 0x00010000 +#define BM_POWER_STS_VBUSVALID_STATUS 0x00008000 +#define BM_POWER_STS_SESSEND_STATUS 0x00004000 +#define BM_POWER_STS_BATT_BO 0x00002000 +#define BM_POWER_STS_VDD5V_FAULT 0x00001000 +#define BM_POWER_STS_CHRGSTS 0x00000800 +#define BM_POWER_STS_DCDC_4P2_BO 0x00000400 +#define BM_POWER_STS_DC_OK 0x00000200 +#define BM_POWER_STS_VDDIO_BO 0x00000100 +#define BM_POWER_STS_VDDA_BO 0x00000080 +#define BM_POWER_STS_VDDD_BO 0x00000040 +#define BM_POWER_STS_VDD5V_GT_VDDIO 0x00000020 +#define BM_POWER_STS_VDD5V_DROOP 0x00000010 +#define BM_POWER_STS_AVALID 0x00000008 +#define BM_POWER_STS_BVALID 0x00000004 +#define BM_POWER_STS_VBUSVALID 0x00000002 +#define BM_POWER_STS_SESSEND 0x00000001 + +#define HW_POWER_SPEED (REGS_POWER_BASE + 0x000000d0) +#define BP_POWER_SPEED_STATUS 16 +#define BM_POWER_SPEED_STATUS 0x00FF0000 +#define BP_POWER_SPEED_CTRL 0 +#define BM_POWER_SPEED_CTRL 0x00000003 + +#define HW_POWER_BATTMONITOR 0xe0 +#define BP_POWER_BATTMONITOR_BATT_VAL 16 +#define BM_POWER_BATTMONITOR_BATT_VAL 0x03FF0000 +#define BM_POWER_BATTMONITOR_EN_BATADJ 0x00000400 +#define BM_POWER_BATTMONITOR_PWDN_BATTBRNOUT 0x00000200 +#define BM_POWER_BATTMONITOR_BRWNOUT_PWD 0x00000100 +#define BP_POWER_BATTMONITOR_BRWNOUT_LVL 0 +#define BM_POWER_BATTMONITOR_BRWNOUT_LVL 0x0000001F + +#define BP_POWER_RESET_UNLOCK 16 +#define BM_POWER_RESET_UNLOCK 0xFFFF0000 +#define BV_POWER_RESET_UNLOCK__KEY 0x3E77 +#define BM_POWER_RESET_PWD_OFF 0x00000002 +#define BM_POWER_RESET_PWD 0x00000001 + +#define BM_POWER_DEBUG_VBUSVALIDPIOLOCK 0x00000008 +#define BM_POWER_DEBUG_AVALIDPIOLOCK 0x00000004 +#define BM_POWER_DEBUG_BVALIDPIOLOCK 0x00000002 +#define BM_POWER_DEBUG_SESSENDPIOLOCK 0x00000001 + +#define HW_POWER_SPECIAL (REGS_POWER_BASE + 0x00000120) +#define BP_POWER_SPECIAL_TEST 0 +#define BM_POWER_SPECIAL_TEST 0xFFFFFFFF + +#define HW_POWER_VERSION (REGS_POWER_BASE + 0x00000130) +#define BP_POWER_VERSION_MAJOR 24 +#define BM_POWER_VERSION_MAJOR 0xFF000000 +#define BP_POWER_VERSION_MINOR 16 +#define BM_POWER_VERSION_MINOR 0x00FF0000 +#define BP_POWER_VERSION_STEP 0 +#define BM_POWER_VERSION_STEP 0x0000FFFF + #endif diff --git a/arch/arm/mach-stmp378x/include/mach/regs-pwm.h b/arch/arm/mach-stmp378x/include/mach/regs-pwm.h index 0d0f9e56ec77..2b653c20e302 100644 --- a/arch/arm/mach-stmp378x/include/mach/regs-pwm.h +++ b/arch/arm/mach-stmp378x/include/mach/regs-pwm.h @@ -31,7 +31,7 @@ #define HW_PWM_ACTIVE2 (0x10 + 2 * 0x20) #define HW_PWM_ACTIVE3 (0x10 + 3 * 0x20) -#define HW_PWM_ACTIVEn 0x10 +#define HW_PWM_ACTIVEn(n) (0x10 + n * 0x20) #define BM_PWM_ACTIVEn_ACTIVE 0x0000FFFF #define BP_PWM_ACTIVEn_ACTIVE 0 #define BM_PWM_ACTIVEn_INACTIVE 0xFFFF0000 @@ -42,7 +42,7 @@ #define HW_PWM_PERIOD2 (0x20 + 2 * 0x20) #define HW_PWM_PERIOD3 (0x20 + 3 * 0x20) -#define HW_PWM_PERIODn 0x20 +#define HW_PWM_PERIODn(n) (0x20 + n * 0x20) #define BM_PWM_PERIODn_PERIOD 0x0000FFFF #define BP_PWM_PERIODn_PERIOD 0 #define BM_PWM_PERIODn_ACTIVE_STATE 0x00030000 diff --git a/arch/arm/mach-stmp378x/include/mach/regs-pxp.h b/arch/arm/mach-stmp378x/include/mach/regs-pxp.h index 54d297896de8..321467d90173 100644 --- a/arch/arm/mach-stmp378x/include/mach/regs-pxp.h +++ b/arch/arm/mach-stmp378x/include/mach/regs-pxp.h @@ -26,10 +26,26 @@ #define BM_PXP_CTRL_ENABLE 0x00000001 #define BP_PXP_CTRL_ENABLE 0 #define BM_PXP_CTRL_IRQ_ENABLE 0x00000002 + #define BM_PXP_CTRL_OUTPUT_RGB_FORMAT 0x000000F0 #define BP_PXP_CTRL_OUTPUT_RGB_FORMAT 4 +#define BV_PXP_CTRL_OUTPUT_RGB_FORMAT__ARGB8888 0x0 +#define BV_PXP_CTRL_OUTPUT_RGB_FORMAT__RGB888 0x1 +#define BV_PXP_CTRL_OUTPUT_RGB_FORMAT__RGB888P 0x2 +#define BV_PXP_CTRL_OUTPUT_RGB_FORMAT__ARGB1555 0x3 +#define BV_PXP_CTRL_OUTPUT_RGB_FORMAT__RGB565 0x4 +#define BV_PXP_CTRL_OUTPUT_RGB_FORMAT__RGB555 0x5 + #define BM_PXP_CTRL_ROTATE 0x00000300 #define BP_PXP_CTRL_ROTATE 8 + +#define BM_PXP_CTRL_S0_FORMAT 0x0000F000 +#define BV_PXP_CTRL_S0_FORMAT__RGB888 0x1 +#define BV_PXP_CTRL_S0_FORMAT__RGB565 0x4 +#define BV_PXP_CTRL_S0_FORMAT__RGB555 0x5 +#define BV_PXP_CTRL_S0_FORMAT__YUV422 0x8 +#define BV_PXP_CTRL_S0_FORMAT__YUV420 0x9 + #define BM_PXP_CTRL_HFLIP 0x00000400 #define BM_PXP_CTRL_VFLIP 0x00000800 #define BM_PXP_CTRL_S0_FORMAT 0x0000F000 @@ -138,3 +154,13 @@ #define BP_PXP_OLnPARAM_FORMAT 4 #define BM_PXP_OLnPARAM_ALPHA 0x0000FF00 #define BP_PXP_OLnPARAM_ALPHA 8 +#define BV_PXP_OLnPARAM_FORMAT__ARGB8888 0x0 +#define BV_PXP_OLnPARAM_FORMAT__RGB888 0x1 +#define BV_PXP_OLnPARAM_FORMAT__ARGB1555 0x3 +#define BV_PXP_OLnPARAM_FORMAT__RGB565 0x4 +#define BV_PXP_OLnPARAM_FORMAT__RGB555 0x5 +#define BM_PXP_OLnPARAM_ENABLE_COLORKEY 0x00000008 +#define BV_PXP_OLnPARAM_ALPHA_CNTL__Embedded 0x0 +#define BV_PXP_OLnPARAM_ALPHA_CNTL__Override 0x1 +#define BV_PXP_OLnPARAM_ALPHA_CNTL__Multiply 0x2 +#define BV_PXP_OLnPARAM_ALPHA_CNTL__ROPs 0x3 diff --git a/arch/arm/mach-stmp378x/include/mach/regs-timrot.h b/arch/arm/mach-stmp378x/include/mach/regs-timrot.h index b5527957c67f..8a927c4821e4 100644 --- a/arch/arm/mach-stmp378x/include/mach/regs-timrot.h +++ b/arch/arm/mach-stmp378x/include/mach/regs-timrot.h @@ -65,4 +65,17 @@ #define HW_TIMROT_TIMCOUNTn 0x30 +#define BV_TIMROT_ROTCTRL_OVERSAMPLE__8X 0x0 +#define BV_TIMROT_ROTCTRL_OVERSAMPLE__4X 0x1 +#define BV_TIMROT_ROTCTRL_OVERSAMPLE__2X 0x2 +#define BV_TIMROT_ROTCTRL_OVERSAMPLE__1X 0x3 +#define BM_TIMROT_ROTCTRL_POLARITY_B 0x00000200 +#define BM_TIMROT_ROTCTRL_POLARITY_A 0x00000100 +#define BP_TIMROT_ROTCTRL_SELECT_B 4 +#define BM_TIMROT_ROTCTRL_SELECT_B 0x00000070 +#define BV_TIMROT_ROTCTRL_SELECT_B__ROTARYA 0x6 +#define BV_TIMROT_ROTCTRL_SELECT_B__ROTARYB 0x7 +#define BV_TIMROT_ROTCTRL_SELECT_A__ROTARYA 0x6 +#define BV_TIMROT_ROTCTRL_SELECT_A__ROTARYB 0x7 + #endif diff --git a/arch/arm/mach-stmp378x/lcd_hx8238a.c b/arch/arm/mach-stmp378x/lcd_hx8238a.c new file mode 100644 index 000000000000..51a06e7e7083 --- /dev/null +++ b/arch/arm/mach-stmp378x/lcd_hx8238a.c @@ -0,0 +1,350 @@ +/* + * Freescale STMP37XX/STMP378X dotclk panel initialization + * + * Embedded Alley Solutions, Inc <source@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/clk.h> + +#include <mach/regs-lcdif.h> +#include <mach/regs-lradc.h> +#include <mach/regs-pinctrl.h> +#include <mach/regs-clkctrl.h> +#include <mach/regs-pwm.h> +#include <mach/regs-apbh.h> +#include <mach/gpio.h> +#include <mach/pins.h> +#include <mach/lcdif.h> +#include <mach/cpu.h> +#include <mach/stmp3xxx.h> + +#include "common.h" + +#define MAX_CHAIN_LEN 10 + +#define DOTCLK_H_ACTIVE 960 +#define DOTCLK_H_PULSE_WIDTH 2 +#define DOTCLK_HF_PORCH 1 +#define DOTCLK_HB_PORCH 67 +#define DOTCLK_H_WAIT_CNT (DOTCLK_H_PULSE_WIDTH + (3 * DOTCLK_HB_PORCH)) +#define DOTCLK_H_PERIOD (DOTCLK_H_WAIT_CNT + DOTCLK_HF_PORCH + DOTCLK_H_ACTIVE) + +#define DOTCLK_V_PULSE_WIDTH 2 +#define DOTCLK_V_ACTIVE 240 +#define DOTCLK_VF_PORCH 1 +#define DOTCLK_VB_PORCH 16 +#define DOTCLK_V_WAIT_CNT (DOTCLK_V_PULSE_WIDTH + DOTCLK_VB_PORCH) +#define DOTCLK_V_PERIOD (DOTCLK_VF_PORCH + DOTCLK_V_ACTIVE + DOTCLK_V_WAIT_CNT) + +static struct stmp3xxx_platform_bl_data bl_data; +extern struct pin_group lcd_pins; +extern unsigned lcd_spi_pins[]; + +static void spi_write(u32 val) +{ + u32 mask; + + gpio_set_value(lcd_spi_pins[SPI_MOSI], 0); + gpio_set_value(lcd_spi_pins[SPI_SCLK], 0); + gpio_set_value(lcd_spi_pins[SPI_CS], 0); + + for (mask = 0x00800000; mask != 0; mask >>= 1) { + gpio_set_value(lcd_spi_pins[SPI_SCLK], 0); + if (val & mask) + gpio_set_value(lcd_spi_pins[SPI_MOSI], 1); + else + gpio_set_value(lcd_spi_pins[SPI_MOSI], 0); + + gpio_set_value(lcd_spi_pins[SPI_SCLK], 1); + } + + udelay(10); + gpio_set_value(lcd_spi_pins[SPI_MOSI], 0); + gpio_set_value(lcd_spi_pins[SPI_SCLK], 0); + gpio_set_value(lcd_spi_pins[SPI_CS], 1); +} + +static void write_reg(u16 reg, u16 val) +{ + pr_debug("%s: writing %x to %x\n", __func__, reg, val); + spi_write(0x00700000 | reg); + spi_write(0x00720000 | val); +} + +static void init_panel_hw(void) +{ + int i; + const unsigned short seq[] = { + 0x02, 0x0200, + 0x03, 0x6164, + 0x0E, 0x3380, + 0x1E, 0x00D2, + 0x01, 0x733F, + 0x04, 0x0448, + 0x05, 0xBC54, + 0x0A, 0x4008, + 0x0B, 0xD400, + 0x0D, 0x3229, + 0x0F, 0x0000, + 0x30, 0x0000, + 0x31, 0x0407, + 0x32, 0x0202, + 0x33, 0x0000, + 0x34, 0x0505, + 0x35, 0x0003, + 0x36, 0x0707, + 0x37, 0x0000, + 0x3A, 0x0904, + 0x3B, 0x0904, + }; + + for (i = 0; i < sizeof(seq) / sizeof(seq[0]); i += 2) + write_reg(seq[i], seq[i + 1]); +} + +static int init_pinmux(void) +{ + return stmp3xxx_request_pin_group(&lcd_pins, "lcd_hx8238a"); +} + +static int init_pinmux_spi(void) +{ + int ret = -EINVAL; + + ret = gpio_request(lcd_spi_pins[SPI_MOSI], "lcd_hx8238a"); + if (ret) + goto out_1; + + ret = gpio_request(lcd_spi_pins[SPI_SCLK], "lcd_hx8238a"); + if (ret) + goto out_2; + ret = gpio_request(lcd_spi_pins[SPI_CS], "lcd_hx8238a"); + if (ret) + goto out_3; + + /* Enable these pins as outputs */ + gpio_direction_output(lcd_spi_pins[SPI_MOSI], 0); + gpio_direction_output(lcd_spi_pins[SPI_SCLK], 0); + gpio_direction_output(lcd_spi_pins[SPI_CS], 1); + + return 0; + +out_3: + gpio_free(lcd_spi_pins[SPI_SCLK]); +out_2: + gpio_free(lcd_spi_pins[SPI_MOSI]); +out_1: + return ret; +} + +static void uninit_pinmux(void) +{ + stmp3xxx_release_pin_group(&lcd_pins, "lcd_hx8238a"); +} + +static void uninit_pinmux_spi(void) +{ + gpio_free(lcd_spi_pins[SPI_MOSI]); + gpio_free(lcd_spi_pins[SPI_SCLK]); + gpio_free(lcd_spi_pins[SPI_CS]); +} + +static struct clk *lcd_clk; + +static int init_panel(struct device *dev, dma_addr_t phys, int memsize, + struct stmp3xxx_platform_fb_entry *pentry) +{ + int ret = 0; + + lcd_clk = clk_get(dev, "lcdif"); + if (IS_ERR(lcd_clk)) { + ret = PTR_ERR(lcd_clk); + goto out_1; + } + ret = clk_enable(lcd_clk); + if (ret) { + clk_put(lcd_clk); + goto out_1; + } + ret = clk_set_rate(lcd_clk, + 1000000/pentry->cycle_time_ns); /* kHz */ + if (ret) { + clk_disable(lcd_clk); + clk_put(lcd_clk); + goto out_1; + } + + ret = init_pinmux(); + if (ret) + goto out_1; + ret = init_pinmux_spi(); + if (ret) + goto out_2; + init_panel_hw(); + + ret = stmp3xxx_lcdif_dma_init(dev, phys, memsize, 0); + if (ret) + goto out_3; + + setup_dotclk_panel(DOTCLK_V_PULSE_WIDTH, DOTCLK_V_PERIOD, + DOTCLK_V_WAIT_CNT, DOTCLK_V_ACTIVE, + DOTCLK_H_PULSE_WIDTH, DOTCLK_H_PERIOD, + DOTCLK_V_WAIT_CNT, DOTCLK_H_ACTIVE, 1); + + stmp3xxx_lcd_set_bl_pdata(pentry->bl_data); + stmp3xxx_lcdif_notify_clients(STMP3XXX_LCDIF_PANEL_INIT, pentry); + return 0; +out_3: + uninit_pinmux_spi(); +out_2: + uninit_pinmux(); +out_1: + return ret; +} + +static void release_panel(struct device *dev, + struct stmp3xxx_platform_fb_entry *pentry) +{ + stmp3xxx_lcdif_notify_clients(STMP3XXX_LCDIF_PANEL_RELEASE, pentry); + uninit_pinmux_spi(); + uninit_pinmux(); + release_dotclk_panel(); + stmp3xxx_lcdif_dma_release(); + clk_disable(lcd_clk); + clk_put(lcd_clk); +} + +static int blank_panel(int blank) +{ + int ret = 0; + + switch (blank) { + case FB_BLANK_NORMAL: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + stmp3xxx_clearl(BM_LCDIF_CTRL_RUN, REGS_LCDIF_BASE + HW_LCDIF_CTRL); + break; + + case FB_BLANK_UNBLANK: + stmp3xxx_setl(BM_LCDIF_CTRL_RUN, REGS_LCDIF_BASE + HW_LCDIF_CTRL); + break; + + default: + ret = -EINVAL; + } + return ret; +} + +static struct stmp3xxx_platform_fb_entry fb_entry = { + .name = "hx8238a", + .x_res = 240, + .y_res = 320, + .bpp = 32, + .cycle_time_ns = 150, + .lcd_type = STMP3XXX_LCD_PANEL_DOTCLK, + .init_panel = init_panel, + .release_panel = release_panel, + .blank_panel = blank_panel, + .run_panel = stmp3xxx_lcdif_run, + .stop_panel = stmp3xxx_lcdif_stop, + .pan_display = stmp3xxx_lcdif_pan_display, + .bl_data = &bl_data, +}; + +static struct clk *pwm_clk; +static int init_bl(struct stmp3xxx_platform_bl_data *data) +{ + int ret = 0; + + pwm_clk = clk_get(NULL, "pwm"); + if (IS_ERR(pwm_clk)) { + ret = PTR_ERR(pwm_clk); + goto out; + } + clk_enable(pwm_clk); + stmp3xxx_reset_block(REGS_PWM_BASE, 1); + + ret = stmp3xxx_request_pin(PINID_PWM2, PIN_FUN1, "lcd_hx8238a"); + if (ret) + goto out_mux; + stmp3xxx_pin_voltage(PINID_PWM2, PIN_12MA, "lcd_hx8238a"); + stmp3xxx_pin_strength(PINID_PWM2, PIN_3_3V, "lcd_hx8238a"); + + stmp3xxx_clearl(BM_PWM_CTRL_PWM2_ENABLE, REGS_PWM_BASE + HW_PWM_CTRL); + stmp3xxx_setl(BM_PWM_CTRL_PWM2_ANA_CTRL_ENABLE, REGS_PWM_BASE + HW_PWM_CTRL); + __raw_writel(BF(10, PWM_ACTIVEn_INACTIVE) | + BF(5, PWM_ACTIVEn_ACTIVE), + REGS_PWM_BASE + HW_PWM_ACTIVEn(2)); + __raw_writel(BF(1, PWM_PERIODn_CDIV) | /* divide by 2 */ + BF(2, PWM_PERIODn_INACTIVE_STATE) | /* low */ + BF(3, PWM_PERIODn_ACTIVE_STATE) | /* high */ + BF(14, PWM_PERIODn_PERIOD), + REGS_PWM_BASE + HW_PWM_PERIODn(2)); + return 0; + +out_mux: + clk_put(pwm_clk); +out: + return ret; +} + +static void free_bl(struct stmp3xxx_platform_bl_data *data) +{ + stmp3xxx_clearl(BM_PWM_CTRL_PWM2_ENABLE, REGS_PWM_BASE + HW_PWM_CTRL); + stmp3xxx_release_pin(PINID_PWM2, "lcd_hx8238a"); + clk_disable(pwm_clk); + clk_put(pwm_clk); +} + +static void set_bl_intensity(struct stmp3xxx_platform_bl_data *data, + struct backlight_device *bd, int suspended) +{ + int intensity = bd->props.brightness; + + if (bd->props.power != FB_BLANK_UNBLANK) + intensity = 0; + if (bd->props.fb_blank != FB_BLANK_UNBLANK) + intensity = 0; + if (suspended) + intensity = 0; + + stmp3xxx_clearl(BM_PWM_CTRL_PWM2_ENABLE, REGS_PWM_BASE + HW_PWM_CTRL); + if (intensity) { + HW_LRADC_CTRL2_CLR(BM_LRADC_CTRL2_BL_BRIGHTNESS); + HW_LRADC_CTRL2_SET(BM_LRADC_CTRL2_BL_ENABLE | + BM_LRADC_CTRL2_BL_MUX_SELECT | + BF(intensity - 1, LRADC_CTRL2_BL_BRIGHTNESS)); + stmp3xxx_setl(BM_PWM_CTRL_PWM2_ENABLE, REGS_PWM_BASE + HW_PWM_CTRL); + } +} + +static struct stmp3xxx_platform_bl_data bl_data = { + .bl_max_intensity = (BM_LRADC_CTRL2_BL_BRIGHTNESS >> + BP_LRADC_CTRL2_BL_BRIGHTNESS) + 1, + .bl_default_intensity = 0x10, + .init_bl = init_bl, + .free_bl = free_bl, + .set_bl_intensity = set_bl_intensity, +}; + +static int __init register_devices(void) +{ + stmp3xxx_lcd_register_entry(&fb_entry, + stmp3xxx_framebuffer.dev.platform_data); + return 0; +} +subsys_initcall(register_devices); diff --git a/arch/arm/mach-stmp378x/lcd_lms350.c b/arch/arm/mach-stmp378x/lcd_lms350.c new file mode 100644 index 000000000000..c24a9b67c0da --- /dev/null +++ b/arch/arm/mach-stmp378x/lcd_lms350.c @@ -0,0 +1,520 @@ +/* + * Freescale STMP378X Samsung LMS350 LCD panel initialization + * + * Embedded Alley Solutions, Inc <source@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/notifier.h> +#include <linux/regulator/consumer.h> + +#include <mach/regs-lcdif.h> +#include <mach/regs-lradc.h> +#include <mach/regs-pinctrl.h> +#include <mach/regs-clkctrl.h> +#include <mach/regs-pwm.h> +#include <mach/regs-apbh.h> +#include <mach/gpio.h> +#include <mach/pins.h> +#include <mach/pinmux.h> +#include <mach/lcdif.h> +#include <mach/stmp3xxx.h> +#include <mach/platform.h> +#include <mach/cputype.h> + + +#define DOTCLK_H_ACTIVE 320 +#define DOTCLK_H_PULSE_WIDTH 3 +#define DOTCLK_HF_PORCH 5 +#define DOTCLK_HB_PORCH 4 +#define DOTCLK_H_WAIT_CNT (DOTCLK_H_PULSE_WIDTH + (3 * DOTCLK_HB_PORCH)) +#define DOTCLK_H_PERIOD (DOTCLK_H_WAIT_CNT + DOTCLK_HF_PORCH + DOTCLK_H_ACTIVE) + +#define DOTCLK_V_PULSE_WIDTH 2 +#define DOTCLK_V_ACTIVE 240 +#define DOTCLK_VF_PORCH 2 +#define DOTCLK_VB_PORCH 5 +#define DOTCLK_V_WAIT_CNT (DOTCLK_V_PULSE_WIDTH + DOTCLK_VB_PORCH) +#define DOTCLK_V_PERIOD (DOTCLK_VF_PORCH + DOTCLK_V_ACTIVE + DOTCLK_V_WAIT_CNT) + +static struct stmp3xxx_platform_bl_data bl_data; +extern struct pin_group lcd_pins; +extern unsigned lcd_spi_pins[]; + +static void spi_write(u32 val) +{ + u32 mask; + + gpio_set_value(lcd_spi_pins[SPI_MOSI], 0); + gpio_set_value(lcd_spi_pins[SPI_SCLK], 1); + gpio_set_value(lcd_spi_pins[SPI_CS], 0); + + for (mask = 0x00800000; mask != 0; mask >>= 1) { + gpio_set_value(lcd_spi_pins[SPI_SCLK], 0); + if (val & mask) + gpio_set_value(lcd_spi_pins[SPI_MOSI], 1); + else + gpio_set_value(lcd_spi_pins[SPI_MOSI], 0); + + gpio_set_value(lcd_spi_pins[SPI_SCLK], 1); + } + + udelay(10); + gpio_set_value(lcd_spi_pins[SPI_MOSI], 1); + gpio_set_value(lcd_spi_pins[SPI_SCLK], 1); + gpio_set_value(lcd_spi_pins[SPI_CS], 1); +} + +static void write_reg(u16 reg, u16 val) +{ + pr_debug("%s: writing %x to %x\n", __func__, reg, val); + spi_write(0x00740000 | reg); + spi_write(0x00760000 | val); +} + +static const unsigned short pon_seq[] = { + /* power on */ + 0x07, 0x0000, 20, + 0x12, 0x1618, 0, + 0x11, 0x222f, 0, + 0x13, 0x40ca, 0, + 0x10, 0x3108, 300, + 0x12, 0x1658, 250, + 0x01, 0x2b1d, 0, + 0x02, 0x0300, 0, + 0x03, 0xD040, 0, + 0x08, (DOTCLK_VB_PORCH + DOTCLK_V_PULSE_WIDTH) - 2, 0, + 0x09, ((DOTCLK_H_PULSE_WIDTH / 3) + DOTCLK_HB_PORCH) - 2, 0, + 0x76, 0x2213, 0, + 0x0b, 0x33e1, 0, + 0x0c, 0x0020, 0, + 0x76, 0x0000, 0, + 0x0d, 0x0000, 0, + 0x0e, 0x0000, 0, + 0x14, 0x0000, 0, + 0x15, 0x0803, 0, + 0x16, 0x0000, 0, + 0x30, 0x0209, 0, + 0x31, 0x0404, 0, + 0x32, 0x0e07, 0, + 0x33, 0x0602, 0, + 0x34, 0x0707, 0, + 0x35, 0x0707, 0, + 0x36, 0x0707, 0, + 0x37, 0x0206, 0, + 0x38, 0x0f06, 0, + 0x39, 0x0611, 20, +}; + +static const unsigned short don_seq[] = { + /* display on */ + 0x07, 0x0001, 150, + 0x07, 0x0101, 150, + 0x76, 0x2213, 0, + 0x1c, 0x6650, 0, + 0x0b, 0x33e0, 0, + 0x76, 0x0000, 0, + 0x07, 0x0103, 0, +}; + + +static const unsigned short doff_seq[] = { + /* display off */ + 0x0b, 0x33e1, 0, + 0x07, 0x0102, 150, + 0x07, 0x0100, 150, + 0x12, 0x0000, 0, + 0x10, 0x0000, 0, +}; + +static const unsigned short poff_seq[] = { + /* power off */ + /* called after display off */ + 0x07, 0x0000, 0, + 0x10, 0x0000, 0, + 0x11, 0x0000, 0, +}; + +static const unsigned short sby_seq[] = { + /* standby */ + /* called after display off */ + 0x10, 0x0001, 0 +}; + +static const unsigned short csby_seq[] = { + /* cancel standby */ + /* called after display on */ + 0x10, 0x0000, 0 +}; + +static void display_off(void) +{ + int i; + const unsigned short *seq; + + seq = doff_seq; + for (i = 0; i < ARRAY_SIZE(doff_seq); i += 3) { + write_reg(seq[i], seq[i + 1]); + if (seq[i + 2]) + udelay(seq[i + 2]); + } +} + +static void display_on(void) +{ + int i; + const unsigned short *seq; + + seq = don_seq; + for (i = 0; i < ARRAY_SIZE(don_seq); i += 3) { + write_reg(seq[i], seq[i + 1]); + if (seq[i + 2]) + udelay(seq[i + 2]); + } +} + +static void init_panel_hw(void) +{ + int i; + const unsigned short *seq; + + seq = pon_seq; + for (i = 0; i < ARRAY_SIZE(pon_seq); i += 3) { + write_reg(seq[i], seq[i + 1]); + if (seq[i + 2]) + udelay(seq[i + 2]); + } + display_on(); +} + +static int init_pinmux(void) +{ + return stmp3xxx_request_pin_group(&lcd_pins, "lcd_lms350"); +} + +static int init_pinmux_spi(void) +{ + int ret = -EINVAL; + + ret = gpio_request(lcd_spi_pins[SPI_MOSI], "lcd_lms350"); + if (ret) + goto out_1; + + ret = gpio_request(lcd_spi_pins[SPI_SCLK], "lcd_lms350"); + if (ret) + goto out_2; + ret = gpio_request(lcd_spi_pins[SPI_CS], "lcd_lms350"); + if (ret) + goto out_3; + + /* Enable these pins as outputs */ + gpio_direction_output(lcd_spi_pins[SPI_MOSI], 1); + gpio_direction_output(lcd_spi_pins[SPI_SCLK], 1); + gpio_direction_output(lcd_spi_pins[SPI_CS], 1); + + return 0; +out_3: + gpio_free(lcd_spi_pins[SPI_SCLK]); +out_2: + gpio_free(lcd_spi_pins[SPI_MOSI]); +out_1: + return ret; +} + +static void uninit_pinmux(void) +{ + stmp3xxx_release_pin_group(&lcd_pins, "lcd_lms350"); +} + +static void uninit_pinmux_spi(void) +{ + gpio_free(lcd_spi_pins[SPI_MOSI]); + gpio_free(lcd_spi_pins[SPI_SCLK]); + gpio_free(lcd_spi_pins[SPI_CS]); +} + +static struct clk *lcd_clk; + +static int init_panel(struct device *dev, dma_addr_t phys, int memsize, + struct stmp3xxx_platform_fb_entry *pentry) +{ + int ret = 0; + + lcd_clk = clk_get(dev, "lcdif"); + if (IS_ERR(lcd_clk)) { + ret = PTR_ERR(lcd_clk); + goto out_1; + } + ret = clk_enable(lcd_clk); + if (ret) { + clk_put(lcd_clk); + goto out_1; + } + ret = clk_set_rate(lcd_clk, + 1000000/pentry->cycle_time_ns); /* kHz */ + if (ret) { + clk_disable(lcd_clk); + clk_put(lcd_clk); + goto out_1; + } + + /* + * Make sure we do a high-to-low transition to reset the panel. + * First make it low for 100 msec, hi for 10 msec, low for 10 msec, + * then hi. + */ + stmp3xxx_clearl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); /* low */ + mdelay(100); + stmp3xxx_setl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); /* high */ + mdelay(10); + stmp3xxx_clearl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); /* low */ + + /* For the Samsung, Reset must be held low at least 30 uSec + * Therefore, we'll hold it low for about 10 mSec just to be sure. + * Then we'll wait 1 mSec afterwards. + */ + mdelay(10); + stmp3xxx_setl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); /* high */ + mdelay(1); + + ret = init_pinmux(); + if (ret) + goto out_1; + ret = init_pinmux_spi(); + if (ret) + goto out_2; + init_panel_hw(); + + setup_dotclk_panel(DOTCLK_V_PULSE_WIDTH, DOTCLK_V_PERIOD, + DOTCLK_V_WAIT_CNT, DOTCLK_V_ACTIVE, + DOTCLK_H_PULSE_WIDTH, DOTCLK_H_PERIOD, + DOTCLK_H_WAIT_CNT, DOTCLK_H_ACTIVE, 0); + + ret = stmp3xxx_lcdif_dma_init(dev, phys, memsize, 1); + if (ret) + goto out_3; + + stmp3xxx_lcd_set_bl_pdata(pentry->bl_data); + stmp3xxx_lcdif_notify_clients(STMP3XXX_LCDIF_PANEL_INIT, pentry); + return 0; +out_3: + uninit_pinmux_spi(); +out_2: + uninit_pinmux(); +out_1: + return ret; +} + +static void release_panel(struct device *dev, + struct stmp3xxx_platform_fb_entry *pentry) +{ + stmp3xxx_lcdif_notify_clients(STMP3XXX_LCDIF_PANEL_RELEASE, pentry); + display_off(); + uninit_pinmux_spi(); + uninit_pinmux(); + release_dotclk_panel(); + stmp3xxx_lcdif_dma_release(); + clk_disable(lcd_clk); + clk_put(lcd_clk); +} + +static int blank_panel(int blank) +{ + int ret = 0, count; + + switch (blank) { + case FB_BLANK_NORMAL: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + stmp3xxx_clearl(BM_LCDIF_CTRL_BYPASS_COUNT, REGS_LCDIF_BASE + HW_LCDIF_CTRL); + for (count = 10000; count; count--) { + if (__raw_readl(REGS_LCDIF_BASE + HW_LCDIF_STAT) & BM_LCDIF_STAT_TXFIFO_EMPTY) + break; + udelay(1); + } + break; + + case FB_BLANK_UNBLANK: + stmp3xxx_setl(BM_LCDIF_CTRL_BYPASS_COUNT, REGS_LCDIF_BASE + HW_LCDIF_CTRL); + break; + + default: + ret = -EINVAL; + } + return ret; +} + +static void stop_panel(void) +{ + stmp3xxx_lcdif_stop(); + display_off(); +} + +static void run_panel(void) +{ + display_on(); + stmp3xxx_lcdif_run(); +} + +static struct stmp3xxx_platform_fb_entry fb_entry = { + .name = "lms350", + .x_res = 240, + .y_res = 320, + .bpp = 32, + .cycle_time_ns = 200, + .lcd_type = STMP3XXX_LCD_PANEL_DOTCLK, + .init_panel = init_panel, + .release_panel = release_panel, + .blank_panel = blank_panel, + .run_panel = run_panel, + .stop_panel = stop_panel, + .pan_display = stmp3xxx_lcdif_pan_display, + .bl_data = &bl_data, +}; + +static struct clk *pwm_clk; + +static int init_bl(struct stmp3xxx_platform_bl_data *data) +{ + int ret = 0; + + pwm_clk = clk_get(NULL, "pwm"); + if (IS_ERR(pwm_clk)) { + ret = PTR_ERR(pwm_clk); + goto out; + } + clk_enable(pwm_clk); + stmp3xxx_reset_block(REGS_PWM_BASE, 1); + + ret = stmp3xxx_request_pin(PINID_PWM2, PIN_FUN1, "lcd_lms350"); + if (ret) + goto out_mux; + + stmp3xxx_pin_voltage(PINID_PWM2, PIN_8MA, "lcd_lms350"); + stmp3xxx_pin_strength(PINID_PWM2, PIN_3_3V, "lcd_lms350"); + + __raw_writel(BF(0, PWM_ACTIVEn_INACTIVE) | + BF(0, PWM_ACTIVEn_ACTIVE), + REGS_PWM_BASE + HW_PWM_ACTIVEn(2)); + __raw_writel(BF(6, PWM_PERIODn_CDIV) | /* divide by 64 */ + BF(2, PWM_PERIODn_INACTIVE_STATE) | /* low */ + BF(3, PWM_PERIODn_ACTIVE_STATE) | /* high */ + BF(599, PWM_PERIODn_PERIOD), REGS_PWM_BASE + HW_PWM_PERIODn(2)); + stmp3xxx_setl(BM_PWM_CTRL_PWM2_ENABLE, REGS_PWM_BASE + HW_PWM_CTRL); + + return 0; + +out_mux: + clk_put(pwm_clk); +out: + return ret; +} + +static void free_bl(struct stmp3xxx_platform_bl_data *data) +{ + __raw_writel(BF(0, PWM_ACTIVEn_INACTIVE) | + BF(0, PWM_ACTIVEn_ACTIVE), + REGS_PWM_BASE + HW_PWM_ACTIVEn(2)); + __raw_writel(BF(6, PWM_PERIODn_CDIV) | /* divide by 64 */ + BF(2, PWM_PERIODn_INACTIVE_STATE) | /* low */ + BF(3, PWM_PERIODn_ACTIVE_STATE) | /* high */ + BF(599, PWM_PERIODn_PERIOD), + REGS_PWM_BASE + HW_PWM_PERIODn(2)); + stmp3xxx_clearl(BM_PWM_CTRL_PWM2_ENABLE, REGS_PWM_BASE + HW_PWM_CTRL); + stmp3xxx_pin_voltage(PINID_PWM2, PIN_4MA, "lcd_lms350"); + stmp3xxx_pin_strength(PINID_PWM2, PIN_1_8V, "lcd_lms350"); + + stmp3xxx_release_pin(PINID_PWM2, "lcd_lms350"); + clk_disable(pwm_clk); + clk_put(pwm_clk); +} + +static int values[] = { 6, 9, 12, 15, 19, 24, 30, 40, 55, 75, 100 }; +static int power[] = { + 0, 1500, 3600, 6100, 10300, + 15500, 74200, 114200, 155200, + 190100, 191000 +}; + +static int bl_to_power(int br) +{ + int base; + int rem; + + if (br > 100) + br = 100; + base = power[br/10]; + rem = br % 10; + if (!rem) + return base; + else + return base + (rem * (power[br/10 + 1]) - base) / 10; +} + +static int set_bl_intensity(struct stmp3xxx_platform_bl_data *data, + struct backlight_device *bd, int suspended) +{ + int intensity = bd->props.brightness; + int scaled_int; + + if (bd->props.power != FB_BLANK_UNBLANK) + intensity = 0; + if (bd->props.fb_blank != FB_BLANK_UNBLANK) + intensity = 0; + if (suspended) + intensity = 0; + + /* + * This is not too cool but what can we do? + * Luminance changes non-linearly... + */ + if (regulator_set_current_limit(data->regulator, bl_to_power(intensity), + bl_to_power(intensity))) + return -EBUSY; + + scaled_int = values[intensity/10]; + if (scaled_int < 100) { + int rem = intensity - 10 * (intensity/10); /* r = i % 10;*/ + scaled_int += rem*(values[intensity/10 + 1] - + values[intensity/10])/10; + } + __raw_writel(BF(scaled_int, PWM_ACTIVEn_INACTIVE) | + BF(0, PWM_ACTIVEn_ACTIVE), REGS_PWM_BASE + HW_PWM_ACTIVEn(2)); + __raw_writel(BF(6, PWM_PERIODn_CDIV) | /* divide by 64 */ + BF(2, PWM_PERIODn_INACTIVE_STATE) | /* low */ + BF(3, PWM_PERIODn_ACTIVE_STATE) | /* high */ + BF(599, PWM_PERIODn_PERIOD), + REGS_PWM_BASE + HW_PWM_PERIODn(2)); + return 0; +} + +static struct stmp3xxx_platform_bl_data bl_data = { + .bl_max_intensity = 100, + .bl_default_intensity = 50, + .bl_cons_intensity = 50, + .init_bl = init_bl, + .free_bl = free_bl, + .set_bl_intensity = set_bl_intensity, +}; + +static int __init register_devices(void) +{ + stmp3xxx_lcd_register_entry(&fb_entry, + stmp3xxx_framebuffer.dev.platform_data); + return 0; +} +subsys_initcall(register_devices); diff --git a/arch/arm/mach-stmp378x/lcd_lms430.c b/arch/arm/mach-stmp378x/lcd_lms430.c new file mode 100644 index 000000000000..bc30ff40f850 --- /dev/null +++ b/arch/arm/mach-stmp378x/lcd_lms430.c @@ -0,0 +1,363 @@ +/* + * Freescale STMP378X Samsung LMS430 LCD panel initialization + * + * Embedded Alley Solutions, Inc <source@embeddedalley.com> + * + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/notifier.h> +#include <linux/regulator/consumer.h> + +#include <mach/regs-lcdif.h> +#include <mach/regs-lradc.h> +#include <mach/regs-pinctrl.h> +#include <mach/regs-clkctrl.h> +#include <mach/regs-pwm.h> +#include <mach/regs-apbh.h> +#include <mach/gpio.h> +#include <mach/pins.h> +#include <mach/lcdif.h> +#include <mach/pinmux.h> +#include <mach/stmp3xxx.h> +#include <mach/platform.h> + +#define DOTCLK_H_ACTIVE 480 +#define DOTCLK_H_PULSE_WIDTH 1 +#define DOTCLK_HF_PORCH 8 +#define DOTCLK_HB_PORCH 15 +#define DOTCLK_H_WAIT_CNT (DOTCLK_H_PULSE_WIDTH + (3 * DOTCLK_HB_PORCH)) +#define DOTCLK_H_PERIOD (DOTCLK_H_WAIT_CNT + DOTCLK_HF_PORCH + DOTCLK_H_ACTIVE) + +#define DOTCLK_V_PULSE_WIDTH 1 +#define DOTCLK_V_ACTIVE 272 +#define DOTCLK_VF_PORCH 4 +#define DOTCLK_VB_PORCH 12 +#define DOTCLK_V_WAIT_CNT (DOTCLK_V_PULSE_WIDTH + DOTCLK_VB_PORCH) +#define DOTCLK_V_PERIOD (DOTCLK_VF_PORCH + DOTCLK_V_ACTIVE + DOTCLK_V_WAIT_CNT) + +static struct stmp3xxx_platform_bl_data bl_data; +extern struct pin_group lcd_pins; +extern unsigned lcd_spi_pins[]; + +static int init_pinmux(void) +{ + return stmp3xxx_request_pin_group(&lcd_pins, "lcd_lms430"); +} + +static int init_pinmux_spi(void) +{ + int ret = -EINVAL; + + ret = gpio_request(lcd_spi_pins[SPI_MOSI], "lcd_lms430"); + if (ret) + goto out_1; + + ret = gpio_request(lcd_spi_pins[SPI_SCLK], "lcd_lms430"); + if (ret) + goto out_2; + ret = gpio_request(lcd_spi_pins[SPI_CS], "lcd_lms430"); + if (ret) + goto out_3; + + /* Enable these pins as outputs */ + gpio_direction_output(lcd_spi_pins[SPI_MOSI], 1); + gpio_direction_output(lcd_spi_pins[SPI_SCLK], 1); + gpio_direction_output(lcd_spi_pins[SPI_CS], 1); + + return 0; +out_3: + gpio_free(lcd_spi_pins[SPI_SCLK]); +out_2: + gpio_free(lcd_spi_pins[SPI_MOSI]); +out_1: + return ret; +} + +static void uninit_pinmux(void) +{ + stmp3xxx_release_pin_group(&lcd_pins, "lcd_lms430"); +} + +static void uninit_pinmux_spi(void) +{ + gpio_free(lcd_spi_pins[SPI_MOSI]); + gpio_free(lcd_spi_pins[SPI_SCLK]); + gpio_free(lcd_spi_pins[SPI_CS]); +} + +static struct clk *lcd_clk; + +static int init_panel(struct device *dev, dma_addr_t phys, int memsize, + struct stmp3xxx_platform_fb_entry *pentry) +{ + int ret = 0; + + lcd_clk = clk_get(dev, "lcdif"); + if (IS_ERR(lcd_clk)) { + ret = PTR_ERR(lcd_clk); + goto out_1; + } + ret = clk_enable(lcd_clk); + if (ret) { + clk_put(lcd_clk); + goto out_1; + } + ret = clk_set_rate(lcd_clk, 1000000 / pentry->cycle_time_ns); /* kHz */ + if (ret) { + clk_disable(lcd_clk); + clk_put(lcd_clk); + goto out_1; + } + + /* + * Make sure we do a high-to-low transition to reset the panel. + * First make it low for 100 msec, hi for 10 msec, low for 10 msec, + * then hi. + */ + stmp3xxx_clearl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); /* low */ + mdelay(100); + stmp3xxx_setl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); /* high */ + mdelay(10); + stmp3xxx_clearl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); /* low */ + + /* For the Samsung, Reset must be held low at least 30 uSec + * Therefore, we'll hold it low for about 10 mSec just to be sure. + * Then we'll wait 1 mSec afterwards. + */ + mdelay(10); + stmp3xxx_setl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); /* high */ + mdelay(1); + + ret = init_pinmux(); + if (ret) + goto out_1; + ret = init_pinmux_spi(); + if (ret) + goto out_2; + + setup_dotclk_panel(DOTCLK_V_PULSE_WIDTH, DOTCLK_V_PERIOD, + DOTCLK_V_WAIT_CNT, DOTCLK_V_ACTIVE, + DOTCLK_H_PULSE_WIDTH, DOTCLK_H_PERIOD, + DOTCLK_H_WAIT_CNT, DOTCLK_H_ACTIVE, 0); + + ret = stmp3xxx_lcdif_dma_init(dev, phys, memsize, 1); + if (ret) + goto out_3; + + stmp3xxx_lcd_set_bl_pdata(pentry->bl_data); + stmp3xxx_lcdif_notify_clients(STMP3XXX_LCDIF_PANEL_INIT, pentry); + return 0; +out_3: + uninit_pinmux_spi(); +out_2: + uninit_pinmux(); +out_1: + return ret; +} + +static void release_panel(struct device *dev, + struct stmp3xxx_platform_fb_entry *pentry) +{ + stmp3xxx_lcdif_notify_clients(STMP3XXX_LCDIF_PANEL_RELEASE, pentry); + uninit_pinmux_spi(); + uninit_pinmux(); + release_dotclk_panel(); + stmp3xxx_lcdif_dma_release(); + clk_disable(lcd_clk); + clk_put(lcd_clk); +} + +static int blank_panel(int blank) +{ + int ret = 0, count; + + switch (blank) { + case FB_BLANK_NORMAL: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + stmp3xxx_clearl(BM_LCDIF_CTRL_BYPASS_COUNT, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + for (count = 10000; count; count--) { + if (__raw_readl(REGS_LCDIF_BASE + HW_LCDIF_STAT) & + BM_LCDIF_STAT_TXFIFO_EMPTY) + break; + udelay(1); + } + break; + + case FB_BLANK_UNBLANK: + stmp3xxx_setl(BM_LCDIF_CTRL_BYPASS_COUNT, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + break; + + default: + ret = -EINVAL; + } + return ret; +} + +static struct stmp3xxx_platform_fb_entry fb_entry = { + .name = "lms430", + .x_res = 272, + .y_res = 480, + .bpp = 32, + .cycle_time_ns = 150, + .lcd_type = STMP3XXX_LCD_PANEL_DOTCLK, + .init_panel = init_panel, + .release_panel = release_panel, + .blank_panel = blank_panel, + .run_panel = stmp3xxx_lcdif_run, + .stop_panel = stmp3xxx_lcdif_stop, + .pan_display = stmp3xxx_lcdif_pan_display, + .bl_data = &bl_data, +}; + +static struct clk *pwm_clk; + +static int init_bl(struct stmp3xxx_platform_bl_data *data) +{ + int ret = 0; + + pwm_clk = clk_get(NULL, "pwm"); + if (IS_ERR(pwm_clk)) { + ret = PTR_ERR(pwm_clk); + goto out; + } + clk_enable(pwm_clk); + stmp3xxx_reset_block(REGS_PWM_BASE, 1); + + ret = stmp3xxx_request_pin(PINID_PWM2, PIN_FUN1, "lcd_lms430"); + if (ret) + goto out_mux; + + stmp3xxx_pin_voltage(PINID_PWM2, PIN_8MA, "lcd_lms430"); + stmp3xxx_pin_strength(PINID_PWM2, PIN_3_3V, "lcd_lms430"); + + __raw_writel(BF(0, PWM_ACTIVEn_INACTIVE) | + BF(0, PWM_ACTIVEn_ACTIVE), REGS_PWM_BASE + HW_PWM_ACTIVEn(2)); + __raw_writel(BF(6, PWM_PERIODn_CDIV) | /* divide by 64 */ + BF(2, PWM_PERIODn_INACTIVE_STATE) | /* low */ + BF(3, PWM_PERIODn_ACTIVE_STATE) | /* high */ + BF(599, PWM_PERIODn_PERIOD), + REGS_PWM_BASE + HW_PWM_PERIODn(2)); + stmp3xxx_setl(BM_PWM_CTRL_PWM2_ENABLE, REGS_PWM_BASE + HW_PWM_CTRL); + + return 0; + +out_mux: + clk_put(pwm_clk); +out: + return ret; +} + +static void free_bl(struct stmp3xxx_platform_bl_data *data) +{ + __raw_writel(BF(0, PWM_ACTIVEn_INACTIVE) | + BF(0, PWM_ACTIVEn_ACTIVE), + REGS_PWM_BASE + HW_PWM_ACTIVEn(2)); + __raw_writel(BF(6, PWM_PERIODn_CDIV) | /* divide by 64 */ + BF(2, PWM_PERIODn_INACTIVE_STATE) | /* low */ + BF(3, PWM_PERIODn_ACTIVE_STATE) | /* high */ + BF(599, PWM_PERIODn_PERIOD), + REGS_PWM_BASE + HW_PWM_PERIODn(2)); + stmp3xxx_clearl(BM_PWM_CTRL_PWM2_ENABLE, REGS_PWM_BASE + HW_PWM_CTRL); + stmp3xxx_pin_voltage(PINID_PWM2, PIN_4MA, "lcd_lms430"); + stmp3xxx_pin_strength(PINID_PWM2, PIN_1_8V, "lcd_lms430"); + + stmp3xxx_release_pin(PINID_PWM2, "lcd_lms430"); + clk_disable(pwm_clk); + clk_put(pwm_clk); +} + +static int values[] = { 0, 4, 9, 14, 20, 27, 35, 45, 57, 75, 100 }; + +static int power[] = { + 0, 1500, 3600, 6100, 10300, + 15500, 74200, 114200, 155200, + 190100, 191000 +}; + +static int bl_to_power(int br) +{ + int base; + int rem; + + if (br > 100) + br = 100; + base = power[br / 10]; + rem = br % 10; + if (!rem) + return base; + else + return base + (rem * (power[br / 10 + 1]) - base) / 10; +} + +static int set_bl_intensity(struct stmp3xxx_platform_bl_data *data, + struct backlight_device *bd, int suspended) +{ + int intensity = bd->props.brightness; + int scaled_int; + + if (bd->props.power != FB_BLANK_UNBLANK) + intensity = 0; + if (bd->props.fb_blank != FB_BLANK_UNBLANK) + intensity = 0; + if (suspended) + intensity = 0; + + /* + * This is not too cool but what can we do? + * Luminance changes non-linearly... + */ + if (regulator_set_current_limit + (data->regulator, bl_to_power(intensity), bl_to_power(intensity))) + return -EBUSY; + + scaled_int = values[intensity / 10]; + if (scaled_int < 100) { + int rem = intensity - 10 * (intensity / 10); /* r = i % 10; */ + scaled_int += rem * (values[intensity / 10 + 1] - + values[intensity / 10]) / 10; + } + __raw_writel(BF(scaled_int, PWM_ACTIVEn_INACTIVE) | + BF(0, PWM_ACTIVEn_ACTIVE), + REGS_PWM_BASE + HW_PWM_ACTIVEn(2)); + __raw_writel(BF(6, PWM_PERIODn_CDIV) | /* divide by 64 */ + BF(2, PWM_PERIODn_INACTIVE_STATE) | /* low */ + BF(3, PWM_PERIODn_ACTIVE_STATE) | /* high */ + BF(399, PWM_PERIODn_PERIOD), + REGS_PWM_BASE + HW_PWM_PERIODn(2)); + return 0; +} + +static struct stmp3xxx_platform_bl_data bl_data = { + .bl_max_intensity = 100, + .bl_default_intensity = 50, + .bl_cons_intensity = 50, + .init_bl = init_bl, + .free_bl = free_bl, + .set_bl_intensity = set_bl_intensity, +}; + +static int __init register_devices(void) +{ + stmp3xxx_lcd_register_entry(&fb_entry, + stmp3xxx_framebuffer.dev.platform_data); + return 0; +} + +subsys_initcall(register_devices); diff --git a/arch/arm/mach-stmp378x/otp.c b/arch/arm/mach-stmp378x/otp.c new file mode 100644 index 000000000000..3dba6bb1a51b --- /dev/null +++ b/arch/arm/mach-stmp378x/otp.c @@ -0,0 +1,434 @@ +/* + * Unique ID manipulation: Freescale STMP378X OTP bits read/write procedures + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kobject.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/fcntl.h> +#include <linux/mutex.h> +#include <linux/clk.h> +#include <linux/err.h> + +#include <mach/unique-id.h> +#include <mach/regs-ocotp.h> +#include <mach/regs-power.h> +#include <mach/platform.h> + +static DEFINE_MUTEX(otp_mutex); +static unsigned otp_mode; +static unsigned long otp_hclk_saved; +static u32 otp_voltage_saved; + +static int otp_full; /* = 0. By default, show/set only customer bits */ +#define OTP_USER_OFFSET 0 +#define OTP_USER_SIZE 4 + +/** + * otp_wait_busy - wait for completion of operation + * + * @flags: flags that should be clear in addition to _BUSY and _ERROR + * + * Returns 0 on success or -ETIMEDOUT on error + **/ +static int otp_wait_busy(u32 flags) +{ + int count; + u32 c; + + for (count = 10000; count >= 0; count--) { + c = __raw_readl(REGS_OCOTP_BASE + HW_OCOTP_CTRL); + if (!(c & (BM_OCOTP_CTRL_BUSY | BM_OCOTP_CTRL_ERROR | flags))) + break; + cpu_relax(); + } + if (count < 0) + return -ETIMEDOUT; + return 0; +} + +/** + * otp_open - open OTP bits for read or write access + * + * @mode: either O_RDONLY or O_WRONLY + * + * Returns 0 on success, error code otherwise + **/ +static int otp_open(int mode) +{ + int r; + struct clk *hclk; + int err; + + if (!mutex_trylock(&otp_mutex)) { + printk(KERN_ERR"%s: already opened\n", __func__); + return -EAGAIN; + } + + if (mode == O_RDONLY) { + pr_debug("%s: read-only mode\n", __func__); + + r = otp_wait_busy(0); + if (r) { + err = -ETIMEDOUT; + goto out; + } + + /* 2. Set RD_BANK_OPEN */ + stmp3xxx_setl(BM_OCOTP_CTRL_RD_BANK_OPEN, REGS_OCOTP_BASE + HW_OCOTP_CTRL); + udelay(10); + + otp_wait_busy(0); + } + + else if (mode == O_WRONLY) { + pr_debug("%s: write-only mode\n", __func__); + hclk = clk_get(NULL, "hclk"); + if (IS_ERR(hclk)) { + err = PTR_ERR(hclk); + goto out; + } + + /* + WARNING ACHTUNG UWAGA + + the code below changes HCLK clock rate to 24M. This is + required to write OTP bits (7.2.2 in STMP378x Target + Specification), and might affect LCD operation, for example. + Moreover, this hacky code changes VDDIO to 2.8V; and resto- + res it only on otp_close(). This may affect... anything. + + You are warned now. + */ + otp_hclk_saved = clk_get_rate(hclk); + clk_set_rate(hclk, 24000); + /* Set the voltage to 2.8V */ + otp_voltage_saved = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + __raw_writel( + (otp_voltage_saved & ~BM_POWER_VDDIOCTRL_TRG) | 0x00, REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + + r = otp_wait_busy(BM_OCOTP_CTRL_RD_BANK_OPEN); + if (r < 0) { + __raw_writel(otp_voltage_saved, REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + clk_set_rate(hclk, otp_hclk_saved); + clk_put(hclk); + err = -ETIMEDOUT; + goto out; + } + + clk_put(hclk); + } + + else { + pr_debug("%s: unknown mode '%d'\n", __func__, mode); + err = -EINVAL; + goto out; + } + + otp_mode = mode; + return 0; +out: + mutex_unlock(&otp_mutex); + pr_debug("%s: status %d\n", __func__, err); + return err; +} + +/** + * otp_close - close the OTP bits after opening by otp_open + **/ +static void otp_close(void) +{ + struct clk *hclk; + + if (!mutex_is_locked(&otp_mutex)) { + printk(KERN_ERR"%s: wasn't opened\n", __func__); + return; + } + + if (otp_mode == O_RDONLY) { + /* 5. clear RD_BANK_OPEN */ + stmp3xxx_clearl(BM_OCOTP_CTRL_RD_BANK_OPEN, REGS_OCOTP_BASE + HW_OCOTP_CTRL); + } + + else if (otp_mode == O_WRONLY) { + hclk = clk_get(NULL, "hclk"); + clk_set_rate(hclk, otp_hclk_saved); + __raw_writel(otp_voltage_saved, REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + otp_wait_busy(0); + stmp3xxx_setl(BM_OCOTP_CTRL_RELOAD_SHADOWS, REGS_OCOTP_BASE + HW_OCOTP_CTRL); + otp_wait_busy(BM_OCOTP_CTRL_RELOAD_SHADOWS); + } + + else { + return; /* -EINVAL. Who does really check close? */ + } + + otp_mode = 0; + mutex_unlock(&otp_mutex); +} + +/** + * otp_read_bits - read the content of OTP + * + * @start: offset from 0, in u32's + * @len: number of OTP u32's to read + * @bits: caller-allocated buffer to save bits + * @size: size of @bits + * + * Returns number of u32's saved to buffer + **/ +static size_t otp_read_bits(int start, int len, u32 *bits, size_t size) +{ + int ofs; + + BUG_ON(!mutex_is_locked(&otp_mutex)); + + /* read all stuff that caller needs */ + if (start + len > 4 * 8) /* 4 banks, 8 registers each */ + len = 4 * 8 - start; + + for (ofs = start; ofs < len; ofs++) { + if (size/sizeof(*bits) <= 0) /* we drained out the buffer */ + break; + *bits = __raw_readl(REGS_OCOTP_BASE + HW_OCOTP_CUSTn(ofs)); + bits++; + size -= sizeof(*bits); + } + + return ofs - start; /* number of u32's that we saved to buffer */ +} + +/** + * otp_write_bits - store OTP bits + * + * @offset: offset from 0, in u32's + * @data: the u32 to write + * @magic: the magic value to be stored in UNLOCK field + * + **/ +static int otp_write_bits(int offset, u32 data, u32 magic) +{ + u32 c; + int r; + + BUG_ON(!mutex_is_locked(&otp_mutex)); + + if (offset < 0 || offset > 0x1F) + return -EINVAL; + + c = __raw_readl(REGS_OCOTP_BASE + HW_OCOTP_CTRL); + c &= ~BM_OCOTP_CTRL_ADDR; + c |= BF(offset, OCOTP_CTRL_ADDR); + c |= BF(magic, OCOTP_CTRL_WR_UNLOCK); + __raw_writel(c, REGS_OCOTP_BASE + HW_OCOTP_CTRL); + + __raw_writel(data, REGS_OCOTP_BASE + HW_OCOTP_DATA); + + r = otp_wait_busy(0); + if (r < 0) + return r; + + udelay(2); + return 0; +} + +static ssize_t otp_id_show(void *context, char *page, int ascii) +{ + char s[60]; + int ret; + int n, i, j, r; + u32 otp_bits[4 * 8]; + + r = otp_open(O_RDONLY); + if (r < 0) + return 0; + n = otp_read_bits(0, 4 * 8, otp_bits, sizeof(otp_bits)); + otp_close(); + + ret = 0; + + + if (ascii) { + + strcpy(page, ""); + ret = 0; + + if (otp_full) { + for (i = 0; i < 4; i++) { + + ret += sprintf(s, "Bank %d: ", i); + strcat(page, s); + + for (j = 0; j < 8; j++) { + + if (i * 4 + j > n) + break; + ret += sprintf(s, "%08X ", + otp_bits[i * 4 + j]); + strcat(page, s); + } + + strcat(page, "\n"); + ret++; + } + } else { + for (i = 0; i < OTP_USER_SIZE; i++) { + ret += sprintf(s, "%08X ", + otp_bits[i + OTP_USER_OFFSET]); + strcat(page, s); + } + strcat(page, "\n"); + ret++; + } + } else { + + if (otp_full) { + memcpy(page, otp_bits, sizeof(otp_bits)); + ret = sizeof(otp_bits); + } else { + memcpy(page, otp_bits + OTP_USER_OFFSET, + OTP_USER_SIZE * sizeof(u32)); + ret = OTP_USER_SIZE * sizeof(u32); + } + } + + return ret; +} + +static int otp_check_dry_run(const char *page, size_t count) +{ + if (count >= 3 && memcmp(page, "+++", 3) == 0) + return 3; + return 0; +} + +static ssize_t otp_id_store(void *context, const char *page, + size_t count, int ascii) +{ + int r = 0; + const char *p, *cp, *d; + unsigned long index, value; + char tmps[20]; /* subject of strtoul */ + int dry_run; + + r = otp_open(O_WRONLY); + if (r < 0) { + printk(KERN_ERR"Cannot open OTP in WRITE mode\n"); + return r; + } + + if (ascii) { + + dry_run = otp_check_dry_run(page, count); + if (dry_run > 0) + page += dry_run; + + index = 0; + cp = page; + + memset(tmps, 0, sizeof(tmps)); + + for (index = 0, cp = page; cp != NULL; index++) { + p = strchr(cp, ','); + + d = strchr(cp, ':'); + if (d && (!p || d < p)) { + strncpy(tmps, cp, + min_t(int, d - cp, sizeof(tmps) - 1)); + r = strict_strtoul(tmps, 0, &index); + if (r < 0) { + pr_debug("Cannot parse '%s'\n", tmps); + break; + } + cp = d + 1; + } + + memset(tmps, 0, sizeof(tmps)); + + if (!p) + strncpy(tmps, cp, sizeof(tmps)); + else + strncpy(tmps, cp, + min_t(int, p - cp, sizeof(tmps) - 1)); + r = strict_strtoul(tmps, 0, &value); + if (r < 0) { + pr_debug("Cannot parse '%s'\n", tmps); + break; + } + + memset(tmps, 0, sizeof(tmps)); + + cp = p ? ++p : NULL; + + if (!otp_full) { + index += OTP_USER_OFFSET; + if (index > OTP_USER_SIZE) { + printk(KERN_ERR"Cannot write at " + "offset %ld\n", index); + continue; + } + } + + r = 0; + if (!dry_run) { + pr_debug("Index %ld, value 0x%08lx\n", + index, value); + r = otp_write_bits(index, value, 0x3e77); + } else + printk(KERN_NOTICE + "Dry-run: writing 0x%08lX => [%ld]\n", + value, index); + if (r < 0) + break; + } + } else { + printk(KERN_ERR"Binary write is not supported\n"); + r = -ENOSYS; + } + otp_close(); + return (r >= 0) ? count : r; +} + +static struct uid_ops otp_ops = { + .id_show = otp_id_show, + .id_store = otp_id_store, +}; + +static int __init_or_module otp_init(void) +{ + void *p; + + mutex_init(&otp_mutex); + p = uid_provider_init("otp", &otp_ops, NULL); + if (IS_ERR(p)) + return PTR_ERR(p); + return 0; +} + +static void __exit otp_remove(void) +{ + uid_provider_remove("otp"); +} + +module_param(otp_full, int, 0600); +module_init(otp_init); +module_exit(otp_remove); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>"); +MODULE_DESCRIPTION("Unique ID: OTP"); diff --git a/arch/arm/mach-stmp378x/persistent.c b/arch/arm/mach-stmp378x/persistent.c new file mode 100644 index 000000000000..84bc8089c145 --- /dev/null +++ b/arch/arm/mach-stmp378x/persistent.c @@ -0,0 +1,257 @@ +/* + * Freescale STMP378X Persistent bits manipulation driver + * + * Author: Pantelis Antoniou <pantelis@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/sysdev.h> +#include <linux/bitops.h> +#include <linux/platform_device.h> +#include <linux/sysfs.h> +#include <mach/hardware.h> +#include <asm/irq.h> +#include <mach/stmp3xxx.h> +#include <mach/platform.h> + +#include <mach/regs-rtc.h> + +struct stmp3xxx_persistent_data { + struct device *dev; + struct stmp3xxx_platform_persistent_data *pdata; + int count; + struct attribute_group attr_group; + /* attribute ** follow */ + /* device_attribute follow */ +}; + +#define pd_attribute_ptr(x) \ + ((struct attribute **)((x) + 1)) +#define pd_device_attribute_ptr(x) \ + ((struct device_attribute *)(pd_attribute_ptr(x) + (x)->count + 1)) + +static inline u32 persistent_reg_read(int reg) +{ + u32 msk; + + /* wait for stable value */ + msk = BF(0x01 << reg, RTC_STAT_STALE_REGS); + while (__raw_readl(REGS_RTC_BASE + HW_RTC_STAT) & msk) + cpu_relax(); + + return __raw_readl(REGS_RTC_BASE + 0x60 + (reg * 0x10)); +} + +static inline void persistent_reg_wait_settle(int reg) +{ + u32 msk; + + /* wait until the change is propagated */ + msk = BF(0x01 << reg, RTC_STAT_NEW_REGS); + while (__raw_readl(REGS_RTC_BASE + HW_RTC_STAT) & msk) + cpu_relax(); +} + +static inline void persistent_reg_write(u32 val, int reg) +{ + __raw_writel(val, REGS_RTC_BASE + 0x60 + (reg * 0x10)); + persistent_reg_wait_settle(reg); +} + +static inline void persistent_reg_set(u32 val, int reg) +{ + __raw_writel(val, REGS_RTC_BASE + 0x60 + (reg * 0x10) + 0x4); + persistent_reg_wait_settle(reg); +} + +static inline void persistent_reg_clr(u32 val, int reg) +{ + __raw_writel(val, REGS_RTC_BASE + 0x60 + (reg * 0x10) + 0x8); + persistent_reg_wait_settle(reg); +} + +static inline void persistent_reg_tog(u32 val, int reg) +{ + __raw_writel(val, REGS_RTC_BASE + 0x60 + (reg * 0x10) + 0xc); + persistent_reg_wait_settle(reg); +} + +static ssize_t +persistent_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct stmp3xxx_persistent_data *pd = platform_get_drvdata(pdev); + struct device_attribute *devattr = pd_device_attribute_ptr(pd); + const struct stmp3xxx_persistent_bit_config *pb; + int idx; + u32 val; + + idx = attr - devattr; + if ((unsigned int)idx >= pd->count) + return -EINVAL; + + pb = &pd->pdata->bit_config_tab[idx]; + + /* read value and shift */ + val = persistent_reg_read(pb->reg); + val >>= pb->start; + val &= (1 << pb->width) - 1; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t +persistent_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct stmp3xxx_persistent_data *pd = platform_get_drvdata(pdev); + struct device_attribute *devattr = pd_device_attribute_ptr(pd); + const struct stmp3xxx_persistent_bit_config *pb; + int idx, r; + unsigned long val, msk; + + idx = attr - devattr; + if ((unsigned int)idx >= pd->count) + return -EINVAL; + + pb = &pd->pdata->bit_config_tab[idx]; + + /* get value to write */ + r = strict_strtoul(buf, 10, &val); + if (r != 0) + return r; + + /* verify it fits */ + if ((unsigned int)val > (1 << pb->width) - 1) + return -EINVAL; + + /* lockless update, first clear the area */ + msk = ((1 << pb->width) - 1) << pb->start; + persistent_reg_clr(msk, pb->reg); + + /* shift into position */ + val <<= pb->start; + persistent_reg_set(val, pb->reg); + + return count; +} + + +static int __devinit stmp3xxx_persistent_probe(struct platform_device *pdev) +{ + struct stmp3xxx_persistent_data *pd; + struct stmp3xxx_platform_persistent_data *pdata; + const struct stmp3xxx_persistent_bit_config *pb; + struct attribute **attr; + struct device_attribute *devattr; + int i, cnt, size; + int err; + + pdata = pdev->dev.platform_data; + if (pdata == NULL) + return -ENODEV; + + cnt = pdata->bit_config_cnt; + size = sizeof(*pd) + + (cnt + 1) * sizeof(struct atrribute *) + + cnt * sizeof(struct device_attribute); + pd = kzalloc(size, GFP_KERNEL); + if (pd == NULL) + return -ENOMEM; + pd->dev = &pdev->dev; + pd->pdata = pdata; + platform_set_drvdata(pdev, pd); + pd->count = cnt; + attr = pd_attribute_ptr(pd); + devattr = pd_device_attribute_ptr(pd); + + /* build the attributes structures */ + pd->attr_group.attrs = attr; + pb = pdata->bit_config_tab; + for (i = 0; i < cnt; i++) { + devattr[i].attr.name = pb[i].name; + devattr[i].attr.mode = S_IWUSR | S_IRUGO; + devattr[i].show = persistent_show; + devattr[i].store = persistent_store; + attr[i] = &devattr[i].attr; + } + + err = sysfs_create_group(&pdev->dev.kobj, &pd->attr_group); + if (err != 0) { + platform_set_drvdata(pdev, NULL); + kfree(pd); + return err; + } + + return 0; +} + +static int stmp3xxx_persistent_remove(struct platform_device *pdev) +{ + struct stmp3xxx_persistent_data *pd; + + pd = platform_get_drvdata(pdev); + sysfs_remove_group(&pdev->dev.kobj, &pd->attr_group); + platform_set_drvdata(pdev, NULL); + kfree(pd); + + return 0; +} + +#ifdef CONFIG_PM +static int +stmp3xxx_persistent_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int stmp3xxx_persistent_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define stmp3xxx_persistent_suspend NULL +#define stmp3xxx_persistent_resume NULL +#endif + +static struct platform_driver stmp3xxx_persistent_driver = { + .probe = stmp3xxx_persistent_probe, + .remove = stmp3xxx_persistent_remove, + .suspend = stmp3xxx_persistent_suspend, + .resume = stmp3xxx_persistent_resume, + .driver = { + .name = "stmp3xxx-persistent", + .owner = THIS_MODULE, + }, +}; + +static int __init stmp3xxx_persistent_init(void) +{ + return platform_driver_register(&stmp3xxx_persistent_driver); +} + +static void __exit stmp3xxx_persistent_exit(void) +{ + platform_driver_unregister(&stmp3xxx_persistent_driver); +} + +MODULE_AUTHOR("Pantelis Antoniou <pantelis@embeddedalley.com>"); +MODULE_DESCRIPTION("Persistent bits user-access driver"); +MODULE_LICENSE("GPL"); + +module_init(stmp3xxx_persistent_init); +module_exit(stmp3xxx_persistent_exit); diff --git a/arch/arm/mach-stmp378x/pm.c b/arch/arm/mach-stmp378x/pm.c new file mode 100644 index 000000000000..bbd9fee881a0 --- /dev/null +++ b/arch/arm/mach-stmp378x/pm.c @@ -0,0 +1,439 @@ +/* + * Static Power Management support for Freescale STMP37XX/STMP378X + * + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/suspend.h> +#include <linux/rtc.h> +#include <linux/pm.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/kthread.h> + +#include <asm/cacheflush.h> +#include <asm/mach-types.h> + +#include <asm/mach/time.h> + +#include <mach/platform.h> +#include <mach/dma.h> +#include <mach/regs-icoll.h> +#include <mach/regs-rtc.h> +#include <mach/regs-clkctrl.h> +#include <mach/regs-pinctrl.h> +#include <mach/regs-power.h> + +#include "sleep.h" + +#define PENDING_IRQ_RETRY 100 +static void *saved_sram; +static int saved_sleep_state; + +static inline void do_standby(void) +{ + void (*stmp37xx_cpu_standby_ptr) (void); + struct clk *cpu_clk; + struct clk *osc_clk; + struct clk *pll_clk; + struct clk *hbus_clk; + struct clk *cpu_parent = NULL; + int cpu_rate = 0; + int hbus_rate = 0; + int i, pending_irq; + + /* + * 1) switch clock domains from PLL to 24MHz + * 2) lower voltage (TODO) + * 3) switch EMI to 24MHz and turn PLL off (done in sleep.S) + */ + + /* save portion of SRAM to be used by suspend function. */ + memcpy(saved_sram, (void *)STMP3XXX_OCRAM_BASE, + stmp_standby_alloc_sz); + + /* make sure SRAM copy gets physically written into SDRAM. + * SDRAM will be placed into self-refresh during power down + */ + flush_cache_all(); + + /* copy suspend function into SRAM */ + memcpy((void *)STMP3XXX_OCRAM_BASE, stmp37xx_cpu_standby, + stmp_standby_alloc_sz); + + /* now switch the CPU to ref_xtal */ + cpu_clk = clk_get(NULL, "cpu"); + osc_clk = clk_get(NULL, "osc_24M"); + pll_clk = clk_get(NULL, "pll"); + hbus_clk = clk_get(NULL, "hclk"); + + if (!IS_ERR(cpu_clk) && !IS_ERR(osc_clk)) { + cpu_rate = clk_get_rate(cpu_clk); + cpu_parent = clk_get_parent(cpu_clk); + hbus_rate = clk_get_rate(hbus_clk); + clk_set_parent(cpu_clk, osc_clk); + } + + local_irq_disable(); + local_fiq_disable(); + + stmp3xxx_dma_suspend(); + stmp3xxx_suspend_timer(); + + stmp3xxx_setl(BM_POWER_CTRL_ENIRQ_PSWITCH, REGS_POWER_BASE + HW_POWER_CTRL); + stmp3xxx_setl(BM_ICOLL_INTERRUPTn_ENABLE, REGS_ICOLL_BASE + HW_ICOLL_INTERRUPTn(IRQ_VDD5V)); + + /* clear pending interrupt, if any */ + for (i = 0; i < PENDING_IRQ_RETRY; i++) { + pending_irq = __raw_readl(REGS_ICOLL_BASE + HW_ICOLL_STAT) & 0x7f; + if (pending_irq == 0x7f) + break; + pr_info("irqn = %u\n", pending_irq); + /* Tell ICOLL to release IRQ line */ + __raw_writel(0x0, REGS_ICOLL_BASE + HW_ICOLL_VECTOR); + /* ACK current interrupt */ + __raw_writel(BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL0, + REGS_ICOLL_BASE + HW_ICOLL_LEVELACK); + /* Barrier */ + (void) __raw_readl(REGS_ICOLL_BASE + HW_ICOLL_STAT); + } + /* do suspend */ + stmp37xx_cpu_standby_ptr = (void *)STMP3XXX_OCRAM_BASE; + stmp37xx_cpu_standby_ptr(); + + pr_info("wakeup irq source = %d\n", __raw_readl(REGS_ICOLL_BASE + HW_ICOLL_STAT)); + saved_sleep_state = 0; /* waking from standby */ + stmp3xxx_clearl(BM_POWER_CTRL_PSWITCH_IRQ, REGS_POWER_BASE + HW_POWER_CTRL); + stmp3xxx_resume_timer(); + stmp3xxx_dma_resume(); + + local_fiq_enable(); + local_irq_enable(); + + if (cpu_parent) { + clk_set_parent(cpu_clk, cpu_parent); + clk_set_rate(cpu_clk, cpu_rate); + clk_set_rate(hbus_clk, hbus_rate); + } + + clk_put(hbus_clk); + clk_put(pll_clk); + clk_put(osc_clk); + clk_put(cpu_clk); + + /* restoring portion of SRAM that was used by suspend function */ + memcpy((void *)STMP3XXX_OCRAM_BASE, saved_sram, + stmp_standby_alloc_sz); +} + +static u32 clk_regs[] = { + HW_CLKCTRL_PLLCTRL0, + HW_CLKCTRL_XTAL, + HW_CLKCTRL_PIX, + HW_CLKCTRL_SSP, + HW_CLKCTRL_GPMI, + HW_CLKCTRL_FRAC, + HW_CLKCTRL_CLKSEQ, +}; + +static noinline void do_mem(void) +{ + void (*stmp37xx_cpu_suspend_ptr) (u32); + struct sleep_data saved_context; + int i; + struct clk *cpu_clk; + struct clk *osc_clk; + struct clk *pll_clk; + struct clk *hbus_clk; + int cpu_rate = 0; + int hbus_rate = 0; + + saved_context.fingerprint = SLEEP_DATA_FINGERPRINT; + + saved_context.old_c00 = __raw_readl(0xC0000000); + saved_context.old_c04 = __raw_readl(0xC0000004); + __raw_writel((u32)&saved_context, (void *)0xC0000000); + + local_irq_disable(); + local_fiq_disable(); + + stmp3xxx_dma_suspend(); + stmp3xxx_suspend_timer(); + + /* clocks */ + for (i = 0; i < ARRAY_SIZE(clk_regs); i++) + saved_context.clks[i] = + __raw_readl(clk_regs[i]); + + /* interrupt collector */ + saved_context.icoll_ctrl = __raw_readl(REGS_ICOLL_BASE + HW_ICOLL_CTRL); + if (machine_is_stmp37xx()) { +#ifdef CONFIG_MACH_STMP37XX + for (i = 0; i < 16; i++) + saved_context.icoll.prio[i] = __raw_readl(REGS_ICOLL_BASE + HW_ICOLL_PRIORITYn(i)); +#endif + } else if (machine_is_stmp378x()) { +#ifdef CONFIG_MACH_STMP378X + for (i = 0; i < 128; i++) + saved_context.icoll.intr[i] = __raw_readl(REGS_ICOLL_BASE + HW_ICOLL_INTERRUPTn(i)); +#endif + } + + /* save pinmux state */ + for (i = 0; i < 0x100; i++) + saved_context.pinmux[i] = + __raw_readl(REGS_PINCTRL_BASE + (i<<4)); + + cpu_clk = clk_get(NULL, "cpu"); + osc_clk = clk_get(NULL, "osc_24M"); + pll_clk = clk_get(NULL, "pll"); + hbus_clk = clk_get(NULL, "hclk"); + + cpu_rate = clk_get_rate(cpu_clk); + hbus_rate = clk_get_rate(hbus_clk); + + /* save portion of SRAM to be used by suspend function. */ + memcpy(saved_sram, (void *)STMP3XXX_OCRAM_BASE, stmp_s2ram_alloc_sz); + + /* set the PERSISTENT_SLEEP_BIT for bootloader */ + stmp3xxx_setl(1 << 10, + REGS_RTC_BASE + HW_RTC_PERSISTENT1); /* XXX: temp */ + + /* + * make sure SRAM copy gets physically written into SDRAM. + * SDRAM will be placed into self-refresh during power down + */ + flush_cache_all(); + + /*copy suspend function into SRAM */ + memcpy((void *)STMP3XXX_OCRAM_BASE, stmp37xx_cpu_suspend, + stmp_s2ram_alloc_sz); + + /* do suspend */ + stmp37xx_cpu_suspend_ptr = (void *)STMP3XXX_OCRAM_BASE; + stmp37xx_cpu_suspend_ptr(0); + + saved_sleep_state = 1; /* waking from non-standby state */ + + /* restoring portion of SRAM that was used by suspend function */ + memcpy((void *)STMP3XXX_OCRAM_BASE, saved_sram, stmp_s2ram_alloc_sz); + + /* clocks */ + for (i = 0; i < ARRAY_SIZE(clk_regs); i++) + __raw_writel(saved_context.clks[i], + clk_regs[i]); + + /* interrupt collector */ + __raw_writel(saved_context.icoll_ctrl, REGS_ICOLL_BASE + HW_ICOLL_CTRL); + if (machine_is_stmp37xx()) { +#ifdef CONFIG_MACH_STMP37XX + for (i = 0; i < 16; i++) + __raw_writel(saved_context.icoll.prio[i], REGS_ICOLL_BASE + HW_ICOLL_PRIORITYn(i)); +#endif + } else if (machine_is_stmp378x()) { +#ifdef CONFIG_MACH_STMP378X + for (i = 0; i < 128; i++) + __raw_writel(saved_context.icoll.intr[i], REGS_ICOLL_BASE + HW_ICOLL_INTERRUPTn(i)); +#endif + } + + /* restore pinmux state */ + for (i = 0; i < 0x100; i++) + __raw_writel(saved_context.pinmux[i], + REGS_PINCTRL_BASE + (i<<4)); + + clk_set_rate(cpu_clk, cpu_rate); + clk_set_rate(hbus_clk, hbus_rate); + + __raw_writel(saved_context.old_c00, 0xC0000000); + __raw_writel(saved_context.old_c04, 0xC0000004); + + clk_put(hbus_clk); + clk_put(pll_clk); + clk_put(osc_clk); + clk_put(cpu_clk); + + stmp3xxx_resume_timer(); + stmp3xxx_dma_resume(); + + local_fiq_enable(); + local_irq_enable(); +} + +static int stmp37xx_pm_enter(suspend_state_t state) +{ + switch (state) { + case PM_SUSPEND_STANDBY: + do_standby(); + break; + case PM_SUSPEND_MEM: + do_mem(); + break; + } + return 0; +} + +static int stmp37xx_pm_valid(suspend_state_t state) +{ + return (state == PM_SUSPEND_STANDBY) || + (state == PM_SUSPEND_MEM); +} + +static suspend_state_t saved_state; + +static int stmp37xx_pm_begin(suspend_state_t state) +{ + saved_state = state; + return 0; +} + +static void stmp37xx_pm_end(void) +{ + /*XXX: Nothing to do */ +} + +suspend_state_t stmp37xx_pm_get_target(void) +{ + return saved_state; +} +EXPORT_SYMBOL(stmp37xx_pm_get_target); + +/** + * stmp37xx_pm_get_sleep_state - get sleep state we waking from + * + * returns boolean: 0 if waking up from standby, 1 otherwise + */ +int stmp37xx_pm_sleep_was_deep(void) +{ + return saved_sleep_state; +} +EXPORT_SYMBOL(stmp37xx_pm_sleep_was_deep); + +static struct platform_suspend_ops stmp37xx_suspend_ops = { + .enter = stmp37xx_pm_enter, + .valid = stmp37xx_pm_valid, + .begin = stmp37xx_pm_begin, + .end = stmp37xx_pm_end, +}; + +void stmp37xx_pm_idle(void) +{ + local_irq_disable(); + local_fiq_disable(); + if (need_resched()) { + local_fiq_enable(); + local_irq_enable(); + return; + } + + stmp3xxx_setl(1<<12, REGS_CLKCTRL_BASE + HW_CLKCTRL_CPU); + __asm__ __volatile__ ("mcr p15, 0, r0, c7, c0, 4"); + + local_fiq_enable(); + local_irq_enable(); +} + +static void stmp37xx_pm_power_off(void) +{ + __raw_writel((0x3e77 << 16) | 1, REGS_POWER_BASE + HW_POWER_RESET); +} + +struct stmp37xx_pswitch_state { + int dev_running; +}; + +static DECLARE_COMPLETION(suspend_request); + +static int suspend_thread_fn(void *data) +{ + while (1) { + wait_for_completion(&suspend_request); + pm_suspend(PM_SUSPEND_STANDBY); + } + return 0; +} + +static struct stmp37xx_pswitch_state pswitch_state = { + .dev_running = 0, +}; + +static irqreturn_t pswitch_interrupt(int irq, void *dev) +{ + int pin_value, i; + + /* check if irq by pswitch */ + if (!(__raw_readl(REGS_POWER_BASE + HW_POWER_CTRL) & BM_POWER_CTRL_PSWITCH_IRQ)) + return IRQ_HANDLED; + for (i = 0; i < 3000; i++) { + pin_value = __raw_readl(REGS_POWER_BASE + HW_POWER_STS) & + BF(0x1, POWER_STS_PSWITCH); + if (pin_value == 0) + break; + mdelay(1); + } + if (i < 3000) { + pr_info("pswitch goto suspend\n"); + complete(&suspend_request); + } else { + pr_info("release pswitch to power down\n"); + for (i = 0; i < 5000; i++) { + pin_value = __raw_readl(REGS_POWER_BASE + HW_POWER_STS) & + BF(0x1, POWER_STS_PSWITCH); + if (pin_value == 0) + break; + mdelay(1); + } + pr_info("pswitch power down\n"); + stmp37xx_pm_power_off(); + } + stmp3xxx_clearl(BM_POWER_CTRL_PSWITCH_IRQ, REGS_POWER_BASE + HW_POWER_CTRL); + return IRQ_HANDLED; +} + +static struct irqaction pswitch_irq = { + .name = "pswitch", + .flags = IRQF_DISABLED | IRQF_SHARED, + .handler = pswitch_interrupt, + .dev_id = &pswitch_state, +}; + +static void init_pswitch(void) +{ + kthread_run(suspend_thread_fn, NULL, "pswitch"); + stmp3xxx_clearl(BM_POWER_CTRL_PSWITCH_IRQ, REGS_POWER_BASE + HW_POWER_CTRL); + stmp3xxx_setl(BM_POWER_CTRL_POLARITY_PSWITCH | + BM_POWER_CTRL_ENIRQ_PSWITCH, REGS_POWER_BASE + HW_POWER_CTRL); + stmp3xxx_clearl(BM_POWER_CTRL_PSWITCH_IRQ, REGS_POWER_BASE + HW_POWER_CTRL); + setup_irq(IRQ_VDD5V, &pswitch_irq); +} + +static int __init stmp37xx_pm_init(void) +{ + saved_sram = kmalloc(0x4000, GFP_ATOMIC); + if (!saved_sram) { + printk(KERN_ERR + "PM Suspend: can't allocate memory to save portion of SRAM\n"); + return -ENOMEM; + } + + pm_power_off = stmp37xx_pm_power_off; + pm_idle = stmp37xx_pm_idle; + suspend_set_ops(&stmp37xx_suspend_ops); + init_pswitch(); + return 0; +} + +late_initcall(stmp37xx_pm_init); diff --git a/arch/arm/mach-stmp378x/power.c b/arch/arm/mach-stmp378x/power.c new file mode 100644 index 000000000000..57d89b586743 --- /dev/null +++ b/arch/arm/mach-stmp378x/power.c @@ -0,0 +1,537 @@ +/* + * Freescale STMP378X voltage regulator low-level driver + * + * Embedded Alley Solutions, Inc <source@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* #define DEBUG */ + +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/machine.h> +#include <mach/platform.h> +#include <mach/power.h> +#include <mach/regulator.h> +#include <mach/regs-power.h> +#include <mach/stmp3xxx.h> + +static int get_voltage(struct stmp3xxx_regulator *sreg) +{ + struct stmp3xxx_platform_regulator_data *rdata = sreg->rdata; + u32 val = __raw_readl(rdata->control_reg) & 0x1f; + int uv = rdata->min_voltage + val * + (rdata->max_voltage - rdata->min_voltage) / 0x1f; + return uv; +} + +static int get_bo_voltage(struct stmp3xxx_regulator *sreg) +{ + int uv; + int offs; + + if (!sreg->parent) + return -EINVAL; + + uv = get_voltage(sreg->parent); + offs = (__raw_readl(sreg->parent->rdata->control_reg) & ~0x700) >> 8; + return uv - 25000*offs; +} + +static int set_voltage(struct stmp3xxx_regulator *sreg, int uv) +{ + u32 val, reg, i; + + pr_debug("%s: uv %d, min %d, max %d\n", __func__, + uv, sreg->rdata->min_voltage, sreg->rdata->max_voltage); + + if (uv < sreg->rdata->min_voltage || uv > sreg->rdata->max_voltage) + return -EINVAL; + + val = (uv - sreg->rdata->min_voltage) * 0x1f / + (sreg->rdata->max_voltage - sreg->rdata->min_voltage); + reg = (__raw_readl(sreg->rdata->control_reg) & ~0x1f); + pr_debug("%s: calculated val %d\n", __func__, val); + __raw_writel(val | reg, sreg->rdata->control_reg); + for (i = 20; i; i--) { + if (__raw_readl(REGS_POWER_BASE + HW_POWER_STS) & BM_POWER_STS_DC_OK) + break; + udelay(1); + } + + if (i) + goto out; + + __raw_writel(val | reg, sreg->rdata->control_reg); + for (i = 40000; i; i--) { + if (__raw_readl(REGS_POWER_BASE + HW_POWER_STS) & BM_POWER_STS_DC_OK) + break; + udelay(1); + } + + if (i) + goto out; + + for (i = 40000; i; i--) { + if (__raw_readl(REGS_POWER_BASE + HW_POWER_STS) & BM_POWER_STS_DC_OK) + break; + udelay(1); + } + +out: + return !i; +} + +static int set_bo_voltage(struct stmp3xxx_regulator *sreg, int bo_uv) +{ + int uv; + int offs; + u32 reg; + int i; + + if (!sreg->parent) + return -EINVAL; + + uv = get_voltage(sreg->parent); + offs = (uv - bo_uv) / 25000; + if (offs < 0 || offs > 7) + return -EINVAL; + + reg = (__raw_readl(sreg->parent->rdata->control_reg) & ~0x700); + pr_debug("%s: calculated offs %d\n", __func__, offs); + __raw_writel((offs << 8) | reg, sreg->parent->rdata->control_reg); + + for (i = 10000; i; i--) { + if (__raw_readl(REGS_POWER_BASE + HW_POWER_STS) & BM_POWER_STS_DC_OK) + break; + udelay(1); + } + + if (i) + goto out; + + for (i = 10000; i; i--) { + if (__raw_readl(REGS_POWER_BASE + HW_POWER_STS) & BM_POWER_STS_DC_OK) + break; + udelay(1); + } + +out: + return !i; +} + +static int enable(struct stmp3xxx_regulator *sreg) +{ + /* XXX: TODO */ + return 0; +} + +static int disable(struct stmp3xxx_regulator *sreg) +{ + /* XXX: TODO */ + return 0; +} + +static int is_enabled(struct stmp3xxx_regulator *sreg) +{ + /* XXX: TODO */ + return 1; +} + +static int set_mode(struct stmp3xxx_regulator *sreg, int mode) +{ + int ret = 0; + u32 val; + + switch (mode) { + case REGULATOR_MODE_FAST: + val = __raw_readl(sreg->rdata->control_reg); + __raw_writel(val | (1 << 17), sreg->rdata->control_reg); + break; + + case REGULATOR_MODE_NORMAL: + val = __raw_readl(sreg->rdata->control_reg); + __raw_writel(val & ~(1<<17), sreg->rdata->control_reg); + break; + + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int get_mode(struct stmp3xxx_regulator *sreg) +{ + u32 val = __raw_readl(sreg->rdata->control_reg) & (1 << 17); + + return val ? REGULATOR_MODE_FAST : REGULATOR_MODE_NORMAL; +} + +static struct stmp3xxx_platform_regulator_data vddd_data = { + .name = "vddd", + .set_voltage = set_voltage, + .get_voltage = get_voltage, + .enable = enable, + .disable = disable, + .is_enabled = is_enabled, + .set_mode = set_mode, + .get_mode = get_mode, + .control_reg = (u32)(REGS_POWER_BASE + HW_POWER_VDDDCTRL), + .min_voltage = 800000, + .max_voltage = 1575000, +}; + +static struct stmp3xxx_platform_regulator_data vdddbo_data = { + .name = "vddd_bo", + .parent_name = "vddd", + .set_voltage = set_bo_voltage, + .get_voltage = get_bo_voltage, + .enable = enable, + .disable = disable, + .is_enabled = is_enabled, + .set_mode = set_mode, + .get_mode = get_mode, + .min_voltage = 800000, + .max_voltage = 1575000, +}; + +static struct stmp3xxx_platform_regulator_data vdda_data = { + .name = "vdda", + .set_voltage = set_voltage, + .get_voltage = get_voltage, + .enable = enable, + .disable = disable, + .is_enabled = is_enabled, + .set_mode = set_mode, + .get_mode = get_mode, + .control_reg = (u32)(REGS_POWER_BASE + HW_POWER_VDDACTRL), + .min_voltage = 1500000, + .max_voltage = 2275000, +}; + +static struct stmp3xxx_platform_regulator_data vddio_data = { + .name = "vddio", + .set_voltage = set_voltage, + .get_voltage = get_voltage, + .enable = enable, + .disable = disable, + .is_enabled = is_enabled, + .set_mode = set_mode, + .get_mode = get_mode, + .control_reg = (u32)(REGS_POWER_BASE + HW_POWER_VDDIOCTRL), + .min_voltage = 2800000, + .max_voltage = 3575000, +}; + +static struct regulator_init_data vddd_init = { + .constraints = { + .name = "vddd", + .min_uV = 800000, + .max_uV = 1575000, + .valid_modes_mask = REGULATOR_MODE_FAST | + REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_MODE, + .input_uV = 5000000, + .always_on = 1, + } +}; + +static struct regulator_init_data vdddbo_init = { + .constraints = { + .name = "vdddbo", + .min_uV = 800000, + .max_uV = 1575000, + .valid_modes_mask = REGULATOR_MODE_FAST | + REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_MODE, + .input_uV = 5000000, + .always_on = 1, + } +}; + + +static struct regulator_init_data vdda_init = { + .constraints = { + .name = "vdda", + .min_uV = 1500000, + .max_uV = 2275000, + .valid_modes_mask = REGULATOR_MODE_FAST | + REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_MODE, + .input_uV = 5000000, + .always_on = 1, + } +}; + + +static struct regulator_init_data vddio_init = { + .constraints = { + .name = "vddio", + .min_uV = 2800000, + .max_uV = 3575000, + .valid_modes_mask = REGULATOR_MODE_FAST | + REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_MODE, + .input_uV = 5000000, + .always_on = 1, + } +}; + +/* now the current regulators */ +/* Restriction: .... no set_current call on root regulator */ +static int main_add_current(struct stmp3xxx_regulator *sreg, + int uA) +{ + + pr_debug("%s: enter reg %s, uA=%d\n", + __func__, sreg->regulator.name, uA); + if (uA > 0 && (sreg->cur_current + uA > sreg->rdata->max_current)) + return -EINVAL; + else + sreg->cur_current += uA; + return 0; +} + +static int cur_reg_set_current(struct stmp3xxx_regulator *sreg, int uA) +{ + int ret = 0; + unsigned long flags; + + pr_debug("%s: enter reg %s, uA=%d\n", + __func__, sreg->regulator.name, uA); + + if (sreg->parent) { + spin_lock_irqsave(&sreg->parent->lock, flags); + ret = main_add_current(sreg->parent, uA - sreg->cur_current); + spin_unlock_irqrestore(&sreg->parent->lock, flags); + } + + + if ((!ret) || (!sreg->parent)) + goto out; + + if (sreg->mode == REGULATOR_MODE_FAST) + return ret; + + while (ret) { + wait_event(sreg->parent->wait_q , + (uA - sreg->cur_current < + sreg->parent->rdata->max_current - + sreg->parent->cur_current)); + spin_lock_irqsave(&sreg->parent->lock, flags); + ret = main_add_current(sreg->parent, uA - sreg->cur_current); + spin_unlock_irqrestore(&sreg->parent->lock, flags); + } +out: + if (sreg->parent && (uA - sreg->cur_current < 0)) + wake_up_all(&sreg->parent->wait_q); + sreg->cur_current = uA; + return 0; + +} + +static int cur_reg_get_current(struct stmp3xxx_regulator *sreg) +{ + return sreg->cur_current; +} + +static int enable_cur_reg(struct stmp3xxx_regulator *sreg) +{ + /* XXX: TODO */ + return 0; +} + +static int disable_cur_reg(struct stmp3xxx_regulator *sreg) +{ + /* XXX: TODO */ + return 0; +} + +static int cur_reg_is_enabled(struct stmp3xxx_regulator *sreg) +{ + /* XXX: TODO */ + return 1; +} + +static int cur_reg_set_mode(struct stmp3xxx_regulator *sreg, int mode) +{ + int ret = 0; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + case REGULATOR_MODE_FAST: + sreg->mode = mode; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int cur_reg_get_mode(struct stmp3xxx_regulator *sreg) +{ + return sreg->mode; +} + +static struct stmp3xxx_platform_regulator_data overall_cur_data = { + .name = "overall_current", + .set_current = cur_reg_set_current, + .get_current = cur_reg_get_current, + .enable = enable_cur_reg, + .disable = disable_cur_reg, + .is_enabled = cur_reg_is_enabled, + .set_mode = cur_reg_set_mode, + .get_mode = cur_reg_get_mode, + .max_current = 0x7fffffff, +}; + +static struct regulator_init_data overall_cur_init = { + .constraints = { + .name = "overall_current", + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_FAST, + .valid_ops_mask = REGULATOR_CHANGE_CURRENT | + REGULATOR_CHANGE_MODE, + .max_uA = 0x7fffffff, + .min_uA = 0x0, + .always_on = 1, + } +}; + +static struct stmp3xxx_platform_regulator_data sibling_cur_data = { + .parent_name = "overall_current", + .set_current = cur_reg_set_current, + .get_current = cur_reg_get_current, + .enable = enable_cur_reg, + .disable = disable_cur_reg, + .is_enabled = cur_reg_is_enabled, + .set_mode = cur_reg_set_mode, + .get_mode = cur_reg_get_mode, +}; + +static struct platform_device *devices[] = { + &stmp3xxx_keyboard, + &stmp3xxx_touchscreen, + &stmp3xxx_appuart, + &stmp3xxx_dbguart, + &stmp3xxx_watchdog, + &stmp3xxx_rtc, + &stmp3xxx_framebuffer, + &stmp3xxx_backlight, + &stmp3xxx_rotdec, + &stmp378x_i2c, + &stmp3xxx_persistent, + &stmp3xxx_dcp_bootstream, + &stmp3xxx_dcp, + &stmp3xxx_battery, + &stmp378x_pxp, +}; + +static int sibling_current_devices_num; + +int stmp3xxx_platform_add_regulator(const char *name, int count) +{ + int i; + pr_debug("%s: name %s, count %d\n", __func__, name, count); + for (i = sibling_current_devices_num; + i < sibling_current_devices_num + count; + i++) { + struct regulator_init_data *sibling_init = + kzalloc(sizeof(struct regulator_init_data), + GFP_KERNEL); + struct stmp3xxx_regulator *curr_reg = + kzalloc(sizeof(struct stmp3xxx_regulator), + GFP_KERNEL); + struct stmp3xxx_platform_regulator_data *d = + kzalloc(sizeof(struct stmp3xxx_platform_regulator_data), + GFP_KERNEL); + if (!d || !curr_reg || !sibling_init) + return -ENOMEM; + + sibling_init->constraints.valid_modes_mask = + REGULATOR_MODE_NORMAL | REGULATOR_MODE_FAST; + sibling_init->constraints.valid_ops_mask = + REGULATOR_CHANGE_CURRENT | REGULATOR_CHANGE_MODE; + sibling_init->constraints.max_uA = 0x7fffffff; + sibling_init->constraints.min_uA = 0x0; + + memcpy(d, &sibling_cur_data, sizeof(sibling_cur_data)); + d->parent_name = kstrdup(sibling_cur_data.parent_name, + GFP_KERNEL); + snprintf(d->name, 80, "%s-%d", + name, i - sibling_current_devices_num + 1); + sibling_init->constraints.name = kstrdup(d->name, GFP_KERNEL); + sibling_init->constraints.always_on = 1; + curr_reg->rdata = d; + stmp3xxx_register_regulator(curr_reg, 101 + i, sibling_init); + } + sibling_current_devices_num += count; + return 0; +} + +static struct stmp3xxx_regulator vddd_reg = { + .rdata = &vddd_data, +}; + +static struct stmp3xxx_regulator vdda_reg = { + .rdata = &vdda_data, +}; + +static struct stmp3xxx_regulator vddio_reg = { + .rdata = &vddio_data, +}; + +static struct stmp3xxx_regulator vdddbo_reg = { + .rdata = &vdddbo_data, +}; + +static struct stmp3xxx_regulator overall_cur_reg = { + .rdata = &overall_cur_data, +}; + + +static int __init regulators_init(void) +{ + int i; + int retval = 0; + u32 vddio = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDIOCTRL) & ~0x1f; + pr_debug("regulators_init \n"); + __raw_writel(vddio | 0x14, REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + vdddbo_reg.parent = &vddd_reg; + stmp3xxx_register_regulator(&vddd_reg, STMP3XXX_VDDD, &vddd_init); + stmp3xxx_register_regulator(&vdddbo_reg, STMP3XXX_VDDDBO, &vdddbo_init); + stmp3xxx_register_regulator(&vdda_reg, STMP3XXX_VDDA, &vdda_init); + stmp3xxx_register_regulator(&vddio_reg, STMP3XXX_VDDIO, &vddio_init); + stmp3xxx_register_regulator(&overall_cur_reg, + STMP3XXX_OVERALL_CUR, &overall_cur_init); + + for (i = 0; i < ARRAY_SIZE(devices); i++) { + retval = stmp3xxx_platform_add_regulator(devices[i]->name, 1); + if (retval) + return retval; + } + stmp3xxx_platform_add_regulator("mmc_ssp", 2); + stmp3xxx_platform_add_regulator("charger", 1); + stmp3xxx_platform_add_regulator("power-test", 1); + stmp3xxx_platform_add_regulator("cpufreq", 1); + return 0; +} +postcore_initcall(regulators_init); diff --git a/arch/arm/mach-stmp378x/sleep.S b/arch/arm/mach-stmp378x/sleep.S new file mode 100644 index 000000000000..acbfa326bebd --- /dev/null +++ b/arch/arm/mach-stmp378x/sleep.S @@ -0,0 +1,540 @@ +/* + * Freescale STMP37XX/STMP378X low level sleep states support + * + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <mach/hardware.h> +#include <asm/pgtable-hwdef.h> +#include <mach/regs-power.h> +#include <mach/regs-clkctrl.h> +#include "sleep.h" + +#define HW_DRAM_CTL06 IO_ADDRESS(0x800E0018) +#define HW_DRAM_CTL08 IO_ADDRESS(0x800E0020) +#define HW_EMI_STAT IO_ADDRESS(0x80020010) +#define HW_RTC_PERSISTENT0 \ + IO_ADDRESS(0x8005C060) + +#define PHYS_RAM_START 0x40000000 + +.global cpu_arm926_switch_mm + + .text + +.align 8 +ENTRY(stmp37xx_cpu_standby) + @ save registers on stack + stmfd sp!, {r0 - r9, lr} + + adr r9, __stmp_temp_stack + + @ clean cache + ldr r1, __stmp_flush_cache_addr + mov lr, pc + mov pc, r1 + + @ put DRAM into self refresh + mov r0, #(HW_DRAM_CTL08 & 0x000000FF) + orr r0, r0, #(HW_DRAM_CTL08 & 0x0000FF00) + orr r0, r0, #(HW_DRAM_CTL08 & 0x00FF0000) + orr r0, r0, #(HW_DRAM_CTL08 & 0xFF000000) + ldr r1, [r0] + orr r1, r1, #(1 << 8) + str r1, [r0] + @ wait for it to actually happen + mov r0, #(HW_EMI_STAT & 0x000000FF) + orr r0, r0, #(HW_EMI_STAT & 0x0000FF00) + orr r0, r0, #(HW_EMI_STAT & 0x00FF0000) + orr r0, r0, #(HW_EMI_STAT & 0xFF000000) +1: ldr r1, [r0] + teq r1, #(1 << 1) + beq 1b + nop + nop + nop + nop + nop + +#ifdef CONFIG_STMP378X_RAM_FREQ_SCALING + @ RAM to clk from xtal + mov lr, pc + b stmp3xxx_ram_save_timings + mov lr, pc + b stmp3xxx_ram_24M_set_timings + + mov r0, #(HW_CLKCTRL_CLKSEQ & 0x000000FF) + orr r0, r0, #(HW_CLKCTRL_CLKSEQ & 0x0000FF00) + orr r0, r0, #(HW_CLKCTRL_CLKSEQ & 0x00FF0000) + orr r0, r0, #(HW_CLKCTRL_CLKSEQ & 0xFF000000) + ldr r4, [r0] + mov r1, #(1<<6) + str r1, [r0, #4] +1: ldr r1, [r0] + tst r1, #BM_CLKCTRL_EMI_BUSY_REF_XTAL + bne 1b + + @ save RAM divisors + mov r0, #(HW_CLKCTRL_FRAC & 0x000000FF) + orr r0, r0, #(HW_CLKCTRL_FRAC & 0x0000FF00) + orr r0, r0, #(HW_CLKCTRL_FRAC & 0x00FF0000) + orr r0, r0, #(HW_CLKCTRL_FRAC & 0xFF000000) + ldr r8, [r0] + and r8, r8, #(0x3F << 8) + lsr r8, r8, #8 + mov r0, #(HW_CLKCTRL_EMI & 0x000000FF) + orr r0, r0, #(HW_CLKCTRL_EMI & 0x0000FF00) + orr r0, r0, #(HW_CLKCTRL_EMI & 0x00FF0000) + orr r0, r0, #(HW_CLKCTRL_EMI & 0xFF000000) + ldr r7, [r0] + and r7, r7, #0x3F + + @ shut the PLL down + mov r0, #(HW_CLKCTRL_PLLCTRL0 & 0x000000FF) + orr r0, r0, #(HW_CLKCTRL_PLLCTRL0 & 0x0000FF00) + orr r0, r0, #(HW_CLKCTRL_PLLCTRL0 & 0x00FF0000) + orr r0, r0, #(HW_CLKCTRL_PLLCTRL0 & 0xFF000000) + mov r1, #(1<<16) + str r1, [r0, #0x08] @ clear + + @ set vddd to minimum + mov r0, #(HW_POWER_VDDDCTRL & 0x000000FF) + orr r0, r0, #(HW_POWER_VDDDCTRL & 0x0000FF00) + orr r0, r0, #(HW_POWER_VDDDCTRL & 0x00FF0000) + orr r0, r0, #(HW_POWER_VDDDCTRL & 0xFF000000) + ldr r6, [r0] + bic r1, r6, #0xFF + bic r1, r1, #0x30 + orr r1, r1, #0xa + str r1, [r0] + /* now wait 1000 us = 24000 cycles */ + mov r0, #24 << 10 +3: sub r0, r0, #1 + cmp r0, #0 + bne 3b + nop +#endif + + @ do enter standby + mov r0, #(HW_CLKCTRL_CPU & 0x000000FF) + orr r0, r0, #(HW_CLKCTRL_CPU & 0x0000FF00) + orr r0, r0, #(HW_CLKCTRL_CPU & 0x00FF0000) + orr r0, r0, #(HW_CLKCTRL_CPU & 0xFF000000) + mov r1, #(1<<12) + str r1, [r0, #4] + mov r2, #0 + mcr p15, 0, r2, c7, c0, 4 + nop + + @ sleeping now... + + @ remove INTERRUPT_WAIT bit + str r1, [r0, #8] + nop + nop + nop + +#ifdef CONFIG_STMP378X_RAM_FREQ_SCALING + @ restore vddd + mov r0, #(HW_POWER_VDDDCTRL & 0x000000FF) + orr r0, r0, #(HW_POWER_VDDDCTRL & 0x0000FF00) + orr r0, r0, #(HW_POWER_VDDDCTRL & 0x00FF0000) + orr r0, r0, #(HW_POWER_VDDDCTRL & 0xFF000000) + ldr r1, [r0] + str r6, [r0] + /* now wait 1000 us = 24000 cycles */ + mov r0, #24 << 10 +12: sub r0, r0, #1 + cmp r0, #0 + bne 12b + nop + + @ put the PLL back up + mov r0, #(HW_CLKCTRL_PLLCTRL0 & 0x000000FF) + orr r0, r0, #(HW_CLKCTRL_PLLCTRL0 & 0x0000FF00) + orr r0, r0, #(HW_CLKCTRL_PLLCTRL0 & 0x00FF0000) + orr r0, r0, #(HW_CLKCTRL_PLLCTRL0 & 0xFF000000) + mov r1, #(1<<16) + str r1, [r0, #0x04] @ set + /* now wait 10 us = 240 cycles */ + mov r0, #240 +11: sub r0, r0, #1 + cmp r0, #0 + bne 11b + nop + + @ set divisors and switch EMI back to PLL + mov lr, pc + b stmp3xxx_ram_restore_timings + mov lr, pc + b __stmp_emi_set_values + + mov r0, #(HW_CLKCTRL_CLKSEQ & 0x000000FF) + orr r0, r0, #(HW_CLKCTRL_CLKSEQ & 0x0000FF00) + orr r0, r0, #(HW_CLKCTRL_CLKSEQ & 0x00FF0000) + orr r0, r0, #(HW_CLKCTRL_CLKSEQ & 0xFF000000) + mov r1, #(1<<6) + str r1, [r0, #8] + + mov r0, #(HW_CLKCTRL_EMI & 0x000000FF) + orr r0, r0, #(HW_CLKCTRL_EMI & 0x0000FF00) + orr r0, r0, #(HW_CLKCTRL_EMI & 0x00FF0000) + orr r0, r0, #(HW_CLKCTRL_EMI & 0xFF000000) + ldr r1, [r0] + bic r1, #BM_CLKCTRL_EMI_DCC_RESYNC_ENABLE + str r1, [r0] +#endif + + @ restore normal DRAM mode + mov r0, #(HW_DRAM_CTL08 & 0x000000FF) + orr r0, r0, #(HW_DRAM_CTL08 & 0x0000FF00) + orr r0, r0, #(HW_DRAM_CTL08 & 0x00FF0000) + orr r0, r0, #(HW_DRAM_CTL08 & 0xFF000000) + ldr r1, [r0] + bic r1, r1, #(1 << 8) + str r1, [r0] + @ wait for it to actually happen + mov r0, #(HW_EMI_STAT & 0x000000FF) + orr r0, r0, #(HW_EMI_STAT & 0x0000FF00) + orr r0, r0, #(HW_EMI_STAT & 0x00FF0000) + orr r0, r0, #(HW_EMI_STAT & 0xFF000000) +102: ldr r1, [r0] + tst r1, #(1 << 1) + bne 102b + + nop + nop + nop + + @ restore regs and return + ldmfd sp!, {r0 - r9, pc} + + .space 0x100 +__stmp_temp_stack: + .word 0 + +#ifdef CONFIG_STMP378X_RAM_FREQ_SCALING +#include "emi.inc" +#endif + +__stmp_flush_cache_addr: + .word arm926_flush_kern_cache_all + +ENTRY(stmp_standby_alloc_sz) + .word . - stmp37xx_cpu_standby + +ENTRY(stmp37xx_cpu_suspend) + @ save registers on stack + stmfd sp!, {r1 - r12, lr} + + @ save context + mov r0, #0xd3 @ SVC, Interrupts disabled + msr cpsr, r0 + mov r1, #0xC0000000 + ldr r1, [r1] + mrc p15, 0, r0, c1, c0, 0 + str r0, [r1, #MMUCTL_OFFS] + mrc p15, 0, r0, c15, c1, 0 + str r0, [r1, #MMUCPACCESS_OFS] + mrc p15, 0, r0, c2, c0, 0 + str r0, [r1, #MMUTTB_OFFS] + mrc p15, 0, r0, c3, c0, 0 + str r0, [r1, #MMUDOMAIN_OFFS] + mrc p15, 0, r0, c13, c0, 0 + str r0, [r1, #MMUPID_OFFS] + + str sp, [r1, #SVC_SP_OFFS] + mrs r0, spsr + str r0, [r1, #SVC_SPSR_OFFS] + + add r2, r1, #FIQ_SPSR_OFFS + mov r0, #0xd1 @ FIQ, Interrupts disabled + msr cpsr, r0 + mrs r3, spsr + stmia r2!, {r3, r8-r12, sp, lr} + + add r2, r1, #ABT_SPSR_OFFS + mov r0, #0xd7 @ ABT, Interrupts disabled + msr cpsr, r0 + mrs r3, spsr + stmia r2!, {r3, sp, lr} + + add r2, r1, #IRQ_SPSR_OFFS + mov r0, #0xd2 @ IRQ, Interrupts disabled + msr cpsr, r0 + mrs r3, spsr + stmia r2!, {r3, sp, lr} + + add r2, r1, #UND_SPSR_OFFS + mov r0, #0xdb @ UND, Interrupts disabled + msr cpsr, r0 + mrs r3, spsr + stmia r2!, {r3, sp, lr} + + add r2, r1, #SYS_SP_OFFS + mov r0, #0xdf @ SYS, Interrupts disabled + msr cpsr, r0 + stmia r2!, {sp, lr} + + add r2, r1, #SVC_R8_OFFS + mov r0, #0xd3 @ Back to SVC, Interrupts disabled + msr cpsr, r0 + + @ save entry point + sub r1, r1, #(0xC0000000 - PHYS_RAM_START) + mov r0, #0xC0000000 + str r1, [r0] + ldr r1, __stmp_resume_point + sub r1, r1, #(0xC0000000 - PHYS_RAM_START) + str r1, [r0, #4] + mov r0, #0 + + @ clean cache + ldr r1, __stmp_flush_cache_addr2 + mov lr, pc + mov pc, r1 + + @ enable internal xtal + mov r2, #(HW_POWER_MINPWR & 0x000000FF) + orr r2, r2, #(HW_POWER_MINPWR & 0x0000FF00) + orr r2, r2, #(HW_POWER_MINPWR & 0x00FF0000) + orr r2, r2, #(HW_POWER_MINPWR & 0xFF000000) + ldr r1, [r2] + orr r1, r1, #(1<<9) + str r1, [r2] + orr r1, r1, #(1<<8) + str r1, [r2] + + @ enable RTC/RAM clocks + mov r0, #(HW_RTC_PERSISTENT0 & 0x000000FF) + orr r0, r0, #(HW_RTC_PERSISTENT0 & 0x0000FF00) + orr r0, r0, #(HW_RTC_PERSISTENT0 & 0x00FF0000) + orr r0, r0, #(HW_RTC_PERSISTENT0 & 0xFF000000) + mov r1, #((1<<4)|(1<<5)|1) + str r1, [r0, #4] + + @ put DRAM into self refresh + mov r0, #(HW_DRAM_CTL08 & 0x000000FF) + orr r0, r0, #(HW_DRAM_CTL08 & 0x0000FF00) + orr r0, r0, #(HW_DRAM_CTL08 & 0x00FF0000) + orr r0, r0, #(HW_DRAM_CTL08 & 0xFF000000) + ldr r1, [r0] + orr r1, r1, #(1 << 8) + str r1, [r0] + @ wait for it to actually happen + mov r0, #(HW_EMI_STAT & 0x000000FF) + orr r0, r0, #(HW_EMI_STAT & 0x0000FF00) + orr r0, r0, #(HW_EMI_STAT & 0x00FF0000) + orr r0, r0, #(HW_EMI_STAT & 0xFF000000) +1: ldr r1, [r0] + teq r1, #(1 << 1) + beq 1b + nop + nop + nop + nop + nop + nop + + @ power off RAM + mov r0, #(HW_DRAM_CTL06 & 0x000000FF) + orr r0, r0, #(HW_DRAM_CTL06 & 0x0000FF00) + orr r0, r0, #(HW_DRAM_CTL06 & 0x00FF0000) + orr r0, r0, #(HW_DRAM_CTL06 & 0xFF000000) + ldr r1, [r0] + orr r1, r1, #(1<<24) + str r1, [r0] + nop + nop + nop + nop + + @ do enter sleep + mov r0, #(HW_POWER_RESET & 0x000000FF) + orr r0, r0, #(HW_POWER_RESET & 0x0000FF00) + orr r0, r0, #(HW_POWER_RESET & 0x00FF0000) + orr r0, r0, #(HW_POWER_RESET & 0xFF000000) + mov r1, #0xFF000000 + orr r1, r1, #0x00FF0000 + str r1, [r0, #8] + mov r1, #0x3E000000 + orr r1, r1, #0x00770000 + str r1, [r0, #4] + mov r1, #2 + str r1, [r0, #8] + mov r1, #1 + str r1, [r0, #4] + nop + nop + nop + nop + nop + nop + + @ sleeping now... + +__restore_context: + mov r0, #0 + mcr p15, 0, r0, c7, c10, 4 @ Drain write buffer + mcr p15, 0, r0, c8, c7, 0 @ Invalidate TLBs + mcr p15, 0, r0, c7, c7, 0 @ Invalidate I & D cache + nop + nop + + mov r0, #0xd3 + msr cpsr, r0 + + bl __create_temp_page_tables + mov r3, r4 + + mov r1, #PHYS_RAM_START + ldr r1, [r1] + ldr r2, [r1, #MMUDOMAIN_OFFS] + ldr r4, [r1, #MMUCPACCESS_OFS] + ldr r5, [r1, #MMUPID_OFFS] + ldr r6, =__resume_after_mmu + ldr r7, [r1, #MMUCTL_OFFS] + ldr r8, [r1, #MMUTTB_OFFS] + add r1, r1, #(0xC0000000 - PHYS_RAM_START) + mov r0, #0 +@ mcr p15, 0, r4, c15, c1, 0 @ cpaccess + mcr p15, 0, r5, c13, c0, 0 @ pid + mcr p15, 0, r2, c3, c0, 0 @ domain + mcr p15, 0, r3, c2, c0, 0 @ ttb + b 1f + .align 5 +1: mov r0, r0 + mcr p15, 0, r7, c1, c0, 0 @ mmuctl + nop + mrc p15, 0, r0, c3, c0, 0 @ read id + mov r0, r0 + mov r0, r0 + sub pc, r6, r5, lsr #32 + nop + nop + nop +__resume_after_mmu: + mov r0, #0 + mcr p15, 0, r0, c8, c7, 0 @ Invalidate TLBs + mcr p15, 0, r0, c7, c7, 0 @ Invalidate I & D cache + + mov r0, r8 + bl cpu_arm926_switch_mm + + mov r0, #0xd1 @FIQ, Interrupts disabled + ldr r2, [r1, #FIQ_SPSR_OFFS] + add r3, r1, #FIQ_R8_OFFS + msr cpsr, r0 + msr spsr, r2 + ldmia r3!, {r8-r12, sp, lr} + + mov r0, #0xd7 @ABT, Interrupts disabled + ldr r2, [r1, #ABT_SPSR_OFFS] + add r3, r1, #ABT_SP_OFFS + msr cpsr, r0 + msr spsr, r2 + ldmia r3!, {sp, lr} + + mov r0, #0xd2 @IRQ, Interrupts disabled + ldr r2, [r1, #IRQ_SPSR_OFFS] + add r3, r1, #IRQ_SP_OFFS + msr cpsr, r0 + msr spsr, r2 + ldmia r3!, {sp, lr} + + mov r0, #0xdb @UND, Interrupts disabled + ldr r2, [r1, #UND_SPSR_OFFS] + add r3, r1, #UND_SP_OFFS + msr cpsr, r0 + msr spsr, r2 + ldmia r3!, {sp, lr} + + mov r0, #0xdf @SYS, Interrupts disabled + add r3, r1, #SYS_SP_OFFS + msr cpsr, r0 + ldmia r3!, {sp, lr} + + mov r0, #0xd3 @SVC, interrupts disabled + ldr r2, [r1, #SVC_SPSR_OFFS] + ldr r3, [r1, #SVC_SP_OFFS] + msr cpsr, r0 + msr spsr, r2 + mov sp, r3 + +#if 0 + @ select CPU bypass, will be cleared afterwards + ldr r0, =HW_CLKCTRL_CLKSEQ + ldr r2, =HW_CLKCTRL_HBUS + ldr r4, =HW_CLKCTRL_CPU + mov r1, #(1<<7) + ldr r3, [r2] + bic r3, r3, #BM_CLKCTRL_HBUS_DIV + orr r3, r3, #1 + ldr r5, [r4] + bic r5, r5, #BM_CLKCTRL_CPU_DIV_CPU + orr r5, r5, #1 + str r1, [r0, #4] + str r3, [r2] + str r5, [r4] +#endif + @ restore regs and return + ldmfd sp!, {r1 - r12, lr} + mov pc, lr + +__stmp_flush_cache_addr2: + .word arm926_flush_kern_cache_all +__stmp_resume_point: + .word __restore_context +ENTRY(stmp_s2ram_alloc_sz) + .word . - stmp37xx_cpu_suspend + +__create_temp_page_tables: + ldr r4, =(__temp_ttb - 0xC0000000 + PHYS_RAM_START) + + /* + * Clear the 16K level 1 swapper page table + */ + mov r0, r4 + mov r3, #0 + add r6, r0, #0x4000 +1: str r3, [r0], #4 + str r3, [r0], #4 + str r3, [r0], #4 + str r3, [r0], #4 + teq r0, r6 + bne 1b + + /* + * Create identity mapping for the area close to where we are to + * cater for the MMU enable. + */ + mov r6, pc, lsr #20 @ kind of where we are + ldr r7, =(PMD_TYPE_SECT | PMD_SECT_BUFFERABLE | PMD_SECT_CACHEABLE | PMD_BIT4 | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ) + + orr r3, r7, r6, lsl #20 @ flags + kernel base + str r3, [r4, r6, lsl #2] @ identity mapping + + mov r6, r6, lsl #20 + add r6, r6, #(0xC0000000-PHYS_RAM_START) + str r3, [r4, r6, lsr #18] + + mov pc, lr + .ltorg + + .section ".sdata", "a" + .align 14 +__temp_ttb: + .space 0x8000 diff --git a/arch/arm/mach-stmp378x/sleep.h b/arch/arm/mach-stmp378x/sleep.h new file mode 100644 index 000000000000..79264d7a2580 --- /dev/null +++ b/arch/arm/mach-stmp378x/sleep.h @@ -0,0 +1,120 @@ +/* + * Deep Sleep related defines + * + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __PM_H__ +#define __PM_H__ + +#include <mach/regs-clkctrl.h> + +#define MMUTTB1_MASK 0x00003FE0 +#define MMUTTBC_MASK 0xFFFFFFFC + +#define LINK_OFFS 0x08 +#define MMUCTL_OFFS 0x0C +#define MMUAUXCTL_OFFS 0x10 +#define MMUCPACCESS_OFS 0x14 +#define MMUTTB_OFFS 0x18 +#define MMUPID_OFFS 0x1C +#define MMUDOMAIN_OFFS 0x20 +#define SVC_R8_OFFS 0x2C +#define SVC_SP_OFFS 0x40 +#define SVC_SPSR_OFFS 0x44 +#define FIQ_SPSR_OFFS 0x48 +#define FIQ_R8_OFFS 0x4C +#define FIQ_SP_OFFS 0x60 +#define ABT_R8_OFFS 0x68 +#define ABT_SPSR_OFFS 0x7C +#define ABT_SP_OFFS 0x80 +#define IRQ_R8_OFFS 0x88 +#define IRQ_SPSR_OFFS 0x9C +#define IRQ_SP_OFFS 0xA0 +#define UND_SPSR_OFFS 0xA8 +#define UND_SP_OFFS 0xAC +#define SYS_SPSR_OFFS 0xB4 +#define SYS_SP_OFFS 0xB8 + +#ifndef __ASSEMBLER__ +#define SLEEP_DATA_FINGERPRINT 0xdeadbeef +struct sleep_data { + u32 fingerprint; + u32 wake_addr; + u32 link_addr; + u32 mmuctl; + u32 mmuauxctl; + u32 mmucpaccess; + u32 mmuttb; + u32 mmupid; + u32 mmudomain; + u32 svc_r6; + u32 svc_r7; + u32 svc_r8; + u32 svc_r9; + u32 svc_r10; + u32 svc_r11; + u32 svc_r12; + u32 svc_sp; + u32 svc_spsr; + u32 fiq_spsr; + u32 fiq_r8; + u32 fiq_r9; + u32 fiq_r10; + u32 fiq_r11; + u32 fiq_r12; + u32 fiq_sp; + u32 fiq_lr; + u32 abt_r8; + u32 abt_r9; + u32 abt_r10; + u32 abt_r11; + u32 abt_r12; + u32 abt_spsr; + u32 abt_sp; + u32 abt_lr; + u32 irq_r8; + u32 irq_r9; + u32 irq_r10; + u32 irq_r11; + u32 irq_r12; + u32 irq_spsr; + u32 irq_sp; + u32 irq_lr; + u32 und_spsr; + u32 und_sp; + u32 und_lr; + u32 sys_spsr; + u32 sys_sp; + u32 sys_lr; + u32 pinmux[0x100]; + u32 icoll_ctrl; + union { + u32 prio[0x10]; + u32 intr[0x80]; + } icoll; + u32 clks[16]; + u32 old_c00; + u32 old_c04; +}; + +extern int stmp_s2ram_alloc_sz; +void stmp37xx_cpu_suspend(void); +extern int stmp_standby_alloc_sz; +void stmp37xx_cpu_standby(void); +void stmp3xxx_suspend_timer(void); +void stmp3xxx_resume_timer(void); + +#endif /* __ASSEMBLER__ */ +#endif /* __PM_H__ */ diff --git a/arch/arm/mach-stmp378x/stmp378x.c b/arch/arm/mach-stmp378x/stmp378x.c index ddd49a760fd4..eb418c6eaadc 100644 --- a/arch/arm/mach-stmp378x/stmp378x.c +++ b/arch/arm/mach-stmp378x/stmp378x.c @@ -42,6 +42,7 @@ #include <mach/regs-apbx.h> #include <mach/regs-pxp.h> #include <mach/regs-i2c.h> +#include <mach/regs-ocotp.h> #include "stmp378x.h" /* @@ -64,14 +65,14 @@ static void stmp378x_mask_irq(unsigned int irq) { /* IRQ disable */ stmp3xxx_clearl(BM_ICOLL_INTERRUPTn_ENABLE, - REGS_ICOLL_BASE + HW_ICOLL_INTERRUPTn + irq * 0x10); + REGS_ICOLL_BASE + HW_ICOLL_INTERRUPTn(irq)); } static void stmp378x_unmask_irq(unsigned int irq) { /* IRQ enable */ stmp3xxx_setl(BM_ICOLL_INTERRUPTn_ENABLE, - REGS_ICOLL_BASE + HW_ICOLL_INTERRUPTn + irq * 0x10); + REGS_ICOLL_BASE + HW_ICOLL_INTERRUPTn(irq)); } static struct irq_chip stmp378x_chip = { @@ -293,7 +294,24 @@ struct platform_device stmp378x_i2c = { .num_resources = ARRAY_SIZE(i2c_resources), }; +struct platform_device stmp378x_audio = { + .name = "stmp378x-audio", + .id = -1, +}; + void __init stmp378x_map_io(void) { iotable_init(stmp378x_io_desc, ARRAY_SIZE(stmp378x_io_desc)); } + +int get_evk_board_version() +{ + int boardid; + boardid = __raw_readl(REGS_OCOTP_BASE + HW_OCOTP_CUSTCAP); + boardid &= 0x30000000; + boardid = boardid >> 28; + + return boardid; +} + +EXPORT_SYMBOL_GPL(get_evk_board_version); diff --git a/arch/arm/mach-stmp378x/stmp378x_devb.c b/arch/arm/mach-stmp378x/stmp378x_devb.c index 90d8fe6f10fe..201c679ae427 100644 --- a/arch/arm/mach-stmp378x/stmp378x_devb.c +++ b/arch/arm/mach-stmp378x/stmp378x_devb.c @@ -23,6 +23,7 @@ #include <linux/clk.h> #include <linux/err.h> #include <linux/spi/spi.h> +#include <linux/input.h> #include <asm/setup.h> #include <asm/mach-types.h> @@ -32,8 +33,10 @@ #include <mach/pinmux.h> #include <mach/platform.h> #include <mach/stmp3xxx.h> -#include <mach/mmc.h> #include <mach/gpmi.h> +#include <mach/mmc.h> +#include <mach/lcdif.h> +#include <mach/ddi_bc.h> #include "stmp378x.h" @@ -53,8 +56,21 @@ static struct platform_device *devices[] = { &stmp3xxx_battery, &stmp378x_pxp, &stmp378x_i2c, + &stmp3xxx_spdif, + &stmp378x_audio, }; +int usb_host_wakeup_irq(struct device *wkup_dev) +{ + return 0; +} +EXPORT_SYMBOL(usb_host_wakeup_irq); + +void usb_host_set_wakeup(struct device *wkup_dev, bool para) +{ +} +EXPORT_SYMBOL(usb_host_set_wakeup); + static struct pin_desc i2c_pins_desc[] = { { PINID_I2C_SCL, PIN_FUN1, PIN_4MA, PIN_3_3V, 0 }, { PINID_I2C_SDA, PIN_FUN1, PIN_4MA, PIN_3_3V, 0 }, @@ -77,14 +93,14 @@ static struct pin_group dbguart_pins[] = { }, }; -static int dbguart_pins_control(int id, int request) +static int dbguart_pinmux(int request, int id) { int r = 0; if (request) - r = stmp3xxx_request_pin_group(&dbguart_pins[id], "debug uart"); + r = stmp3xxx_request_pin_group(&dbguart_pins[id], "dbguart"); else - stmp3xxx_release_pin_group(&dbguart_pins[id], "debug uart"); + stmp3xxx_release_pin_group(&dbguart_pins[id], "dbguart"); return r; } @@ -104,104 +120,6 @@ static struct pin_desc appuart_pins_1[] = { #endif }; -static struct pin_desc mmc_pins_desc[] = { - { PINID_SSP1_DATA0, PIN_FUN1, PIN_8MA, PIN_3_3V, 1 }, - { PINID_SSP1_DATA1, PIN_FUN1, PIN_8MA, PIN_3_3V, 1 }, - { PINID_SSP1_DATA2, PIN_FUN1, PIN_8MA, PIN_3_3V, 1 }, - { PINID_SSP1_DATA3, PIN_FUN1, PIN_8MA, PIN_3_3V, 1 }, - { PINID_SSP1_CMD, PIN_FUN1, PIN_8MA, PIN_3_3V, 1 }, - { PINID_SSP1_SCK, PIN_FUN1, PIN_8MA, PIN_3_3V, 0 }, - { PINID_SSP1_DETECT, PIN_FUN1, PIN_8MA, PIN_3_3V, 0 }, -}; - -static struct pin_group mmc_pins = { - .pins = mmc_pins_desc, - .nr_pins = ARRAY_SIZE(mmc_pins_desc), -}; - -static int stmp3xxxmmc_get_wp(void) -{ - return gpio_get_value(PINID_PWM4); -} - -static int stmp3xxxmmc_hw_init_ssp1(void) -{ - int ret; - - ret = stmp3xxx_request_pin_group(&mmc_pins, "mmc"); - if (ret) - goto out; - - /* Configure write protect GPIO pin */ - ret = gpio_request(PINID_PWM4, "mmc wp"); - if (ret) - goto out_wp; - - gpio_direction_input(PINID_PWM4); - - /* Configure POWER pin as gpio to drive power to MMC slot */ - ret = gpio_request(PINID_PWM3, "mmc power"); - if (ret) - goto out_power; - - gpio_direction_output(PINID_PWM3, 0); - mdelay(100); - - return 0; - -out_power: - gpio_free(PINID_PWM4); -out_wp: - stmp3xxx_release_pin_group(&mmc_pins, "mmc"); -out: - return ret; -} - -static void stmp3xxxmmc_hw_release_ssp1(void) -{ - gpio_free(PINID_PWM3); - gpio_free(PINID_PWM4); - stmp3xxx_release_pin_group(&mmc_pins, "mmc"); -} - -static void stmp3xxxmmc_cmd_pullup_ssp1(int enable) -{ - stmp3xxx_pin_pullup(PINID_SSP1_CMD, enable, "mmc"); -} - -static unsigned long -stmp3xxxmmc_setclock_ssp1(void __iomem *base, unsigned long hz) -{ - struct clk *ssp, *parent; - char *p; - long r; - - ssp = clk_get(NULL, "ssp"); - - /* using SSP1, no timeout, clock rate 1 */ - writel(BF(2, SSP_TIMING_CLOCK_DIVIDE) | - BF(0xFFFF, SSP_TIMING_TIMEOUT), - base + HW_SSP_TIMING); - - p = (hz > 1000000) ? "io" : "osc_24M"; - parent = clk_get(NULL, p); - clk_set_parent(ssp, parent); - r = clk_set_rate(ssp, 2 * hz / 1000); - clk_put(parent); - clk_put(ssp); - - return hz; -} - -static struct stmp3xxxmmc_platform_data mmc_data = { - .hw_init = stmp3xxxmmc_hw_init_ssp1, - .hw_release = stmp3xxxmmc_hw_release_ssp1, - .get_wp = stmp3xxxmmc_get_wp, - .cmd_pullup = stmp3xxxmmc_cmd_pullup_ssp1, - .setclock = stmp3xxxmmc_setclock_ssp1, -}; - - static struct pin_group appuart_pins[] = { [0] = { .pins = appuart_pins_0, @@ -213,6 +131,15 @@ static struct pin_group appuart_pins[] = { }, }; +static int appuart_pinmux(int req, int id) +{ + if (req) + return stmp3xxx_request_pin_group(&appuart_pins[id], "appuart"); + else + stmp3xxx_release_pin_group(&appuart_pins[id], "appuart"); + return 0; +} + static struct pin_desc ssp1_pins_desc[] = { { PINID_SSP1_SCK, PIN_FUN1, PIN_8MA, PIN_3_3V, 0, }, { PINID_SSP1_CMD, PIN_FUN1, PIN_4MA, PIN_3_3V, 0, }, @@ -265,24 +192,150 @@ static struct pin_group gpmi_pins = { .nr_pins = ARRAY_SIZE(gpmi_pins_desc), }; -static struct mtd_partition gpmi_partitions[] = { +static int gpmi_pinmux(int req) +{ + if (req) + return stmp3xxx_request_pin_group(&gpmi_pins, "gpmi"); + else + stmp3xxx_release_pin_group(&gpmi_pins, "gpmi"); + return 0; +} + +const char *gpmi_part_probes[] = { "cmdlinepart", NULL }; + +#define UID_SIZE SZ_1M +#define UID_OFFSET (20*SZ_1M) + +struct mtd_partition gpmi_partitions_chip0[] = { + [0] = { + .offset = 0, + .size = UID_OFFSET, + .name = "Boot#0", + .mask_flags = 0, + }, + /* This partition is managed by UBI */ + [1] = { + .offset = UID_OFFSET + UID_SIZE, + .size = MTDPART_SIZ_FULL, + .name = "UBI#0", + .mask_flags = 0, + }, +}; + +struct mtd_partition gpmi_partitions_chip1[] = { [0] = { - .name = "boot", - .size = 10 * SZ_1M, - .offset = 0, + .offset = 0, + .size = UID_OFFSET, + .name = "Boot#1", + .mask_flags = 0, }, + /* This partition is managed by UBI */ [1] = { - .name = "data", - .size = MTDPART_SIZ_FULL, - .offset = MTDPART_OFS_APPEND, + .offset = UID_OFFSET, + .size = MTDPART_SIZ_FULL, + .name = "UBI#1", + .mask_flags = 0, }, }; -static struct gpmi_platform_data gpmi_data = { - .pins = &gpmi_pins, - .nr_parts = ARRAY_SIZE(gpmi_partitions), - .parts = gpmi_partitions, - .part_types = { "cmdline", NULL }, +static char *gpmi_concat_parts[] = { + [0] = "UBI#0", + [1] = "UBI#1", + [2] = NULL, +}; + +static struct gpmi_platform_data gpmi_partitions = { + .uid_offset = UID_OFFSET, + .uid_size = UID_SIZE, + .io_uA = 70000, + .items = 2, + .concat_name = "UBI", + .concat_parts = gpmi_concat_parts, + .pinmux = gpmi_pinmux, + .parts = { + [0] = { + .part_probe_types = gpmi_part_probes, + .nr_partitions = ARRAY_SIZE(gpmi_partitions_chip0), + .partitions = gpmi_partitions_chip0, + }, + [1] = { + .part_probe_types = gpmi_part_probes, + .nr_partitions = ARRAY_SIZE(gpmi_partitions_chip1), + .partitions = gpmi_partitions_chip1, + }, + }, +}; + +static struct pin_desc lcd_hx8238a_desc[] = { + { PINID_LCD_D00, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D01, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D02, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D03, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D04, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D05, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D06, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D07, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D08, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D09, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D10, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D11, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D12, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D13, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D14, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D15, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D16, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_D17, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_RESET, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_VSYNC, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_HSYNC, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_ENABLE, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_LCD_DOTCK, PIN_FUN1, PIN_12MA, PIN_3_3V, 0 }, + { PINID_GPMI_D13, PIN_FUN2, PIN_12MA, PIN_3_3V, 0 }, + { PINID_GPMI_D12, PIN_FUN2, PIN_12MA, PIN_3_3V, 0 }, + { PINID_GPMI_D11, PIN_FUN2, PIN_12MA, PIN_3_3V, 0 }, + { PINID_GPMI_D10, PIN_FUN2, PIN_12MA, PIN_3_3V, 0 }, + { PINID_GPMI_D09, PIN_FUN2, PIN_12MA, PIN_3_3V, 0 }, + { PINID_GPMI_D08, PIN_FUN2, PIN_12MA, PIN_3_3V, 0 }, +}; + +struct pin_group lcd_pins = { + .pins = lcd_hx8238a_desc, + .nr_pins = ARRAY_SIZE(lcd_hx8238a_desc), +}; + +unsigned lcd_spi_pins[] = { + [SPI_MOSI] = PINID_LCD_WR, + [SPI_SCLK] = PINID_LCD_RS, + [SPI_CS] = PINID_LCD_CS, +}; + +static struct pin_desc spdif_pins_desc[] = { + { PINID_ROTARYA, PIN_FUN3, PIN_4MA, PIN_1_8V, 0, }, +}; + +struct pin_group spdif_pins = { + .pins = spdif_pins_desc, + .nr_pins = ARRAY_SIZE(spdif_pins_desc), +}; + +int spdif_pinmux(int req) +{ + if (req) + return stmp3xxx_request_pin_group(&spdif_pins, "spdif"); + else + stmp3xxx_release_pin_group(&spdif_pins, "spdif"); + return 0; +} +EXPORT_SYMBOL_GPL(spdif_pinmux); + +static struct stmp3xxxmmc_platform_data mmc_data = { + .hw_init = stmp3xxxmmc_hw_init_ssp1, + .hw_release = stmp3xxxmmc_hw_release_ssp1, + .get_wp = stmp3xxxmmc_get_wp, + .cmd_pullup = stmp3xxxmmc_cmd_pullup_ssp1, + .setclock = stmp3xxxmmc_setclock_ssp1, + .read_uA = 50000, + .write_uA = 70000, }; static struct spi_board_info spi_board_info[] __initdata = { @@ -297,6 +350,48 @@ static struct spi_board_info spi_board_info[] __initdata = { #endif }; +/* battery info data */ +static ddi_bc_Cfg_t battery_data = { + .u32StateMachinePeriod = 100, /* ms */ + .u16CurrentRampSlope = 75, /* mA/s */ + .u16ConditioningThresholdVoltage = 2900, /* mV */ + .u16ConditioningMaxVoltage = 3000, /* mV */ + .u16ConditioningCurrent = 60, /* mA */ + .u32ConditioningTimeout = 4*60*60*1000, /* ms (4 hours) */ + .u16ChargingVoltage = 4200, /* mV */ + /* FIXME: the current comparator could have h/w bugs in current + * detection through POWER_STS.CHRGSTS bit */ + .u16ChargingCurrent = 600, /* mA 600 */ + .u16ChargingThresholdCurrent = 60, /* mA 60 */ + .u32ChargingTimeout = 4*60*60*1000,/* ms (4 hours) */ + .u32TopOffPeriod = 30*60*1000, /* ms (30 minutes) */ + .useInternalBias = 0, /* ext bias current */ + .monitorDieTemp = 1, /* Monitor the die */ + .u8DieTempHigh = 115, /* deg centigrade */ + .u8DieTempLow = 96, /* deg centigrade */ + .u16DieTempSafeCurrent = 400, /* mA */ + .monitorBatteryTemp = 0, /* Monitor the battery*/ + .u8BatteryTempChannel = 1, /* LRADC 1 */ + .u16BatteryTempHigh = 642, /* Unknown units */ + .u16BatteryTempLow = 497, /* Unknown units */ + .u16BatteryTempSafeCurrent = 0, /* mA */ +}; + +static struct stmpkbd_keypair keyboard_data[] = { + { 100, KEY_F4 }, + { 306, KEY_F5 }, + { 626, KEY_F6 }, + { 932, KEY_F7 }, + { 1260, KEY_F8 }, + { 1584, KEY_F9 }, + { 1907, KEY_F10 }, + { 2207, KEY_F11 }, + { 2525, KEY_F12 }, + { 2831, KEY_F13}, + { 3134, KEY_F14 }, + { -1, 0 }, +}; + static void __init stmp378x_devb_init(void) { stmp3xxx_pinmux_init(NR_REAL_IRQS); @@ -304,13 +399,15 @@ static void __init stmp378x_devb_init(void) /* init stmp3xxx platform */ stmp3xxx_init(); - stmp3xxx_dbguart.dev.platform_data = dbguart_pins_control; - stmp3xxx_appuart.dev.platform_data = appuart_pins; + stmp3xxx_dbguart.dev.platform_data = dbguart_pinmux; + stmp3xxx_appuart.dev.platform_data = appuart_pinmux; + stmp3xxx_gpmi.dev.platform_data = &gpmi_partitions; stmp3xxx_mmc.dev.platform_data = &mmc_data; - stmp3xxx_gpmi.dev.platform_data = &gpmi_data; stmp3xxx_spi1.dev.platform_data = &ssp1_pins; stmp3xxx_spi2.dev.platform_data = &ssp2_pins; stmp378x_i2c.dev.platform_data = &i2c_pins; + stmp3xxx_battery.dev.platform_data = &battery_data; + stmp3xxx_keyboard.dev.platform_data = &keyboard_data; /* register spi devices */ spi_register_board_info(spi_board_info, ARRAY_SIZE(spi_board_info)); diff --git a/arch/arm/mach-stmp378x/stmp378x_devb_rotdec.c b/arch/arm/mach-stmp378x/stmp378x_devb_rotdec.c new file mode 100644 index 000000000000..05270fe39419 --- /dev/null +++ b/arch/arm/mach-stmp378x/stmp378x_devb_rotdec.c @@ -0,0 +1,47 @@ +/* + * Freescale STMP378X Rotary Encoder module pin multiplexing + * + * Author: Drew Benedetti <drewb@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/module.h> +#include <mach/pins.h> +#include <mach/pinmux.h> + +#define ROTARY_FUN PIN_FUN1 + +#define TITLE "stmp3xxx-rotdec" + +int rotdec_pinmux_request(void) +{ + int rc = 0; + + rc |= stmp3xxx_request_pin(PINID_ROTARYA, ROTARY_FUN, TITLE); + rc |= stmp3xxx_request_pin(PINID_ROTARYB, ROTARY_FUN, TITLE); + + return rc; +} +EXPORT_SYMBOL_GPL(rotdec_pinmux_request); + +void rotdec_pinmux_free(void) +{ + stmp3xxx_release_pin(PINID_ROTARYA, TITLE); + stmp3xxx_release_pin(PINID_ROTARYB, TITLE); +} +EXPORT_SYMBOL_GPL(rotdec_pinmux_free); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Drew Benedetti <drewb@embeddedalley.com>"); diff --git a/arch/arm/mach-stmp378x/stmp378x_i2c.c b/arch/arm/mach-stmp378x/stmp378x_i2c.c new file mode 100644 index 000000000000..f5e96fca5de8 --- /dev/null +++ b/arch/arm/mach-stmp378x/stmp378x_i2c.c @@ -0,0 +1,281 @@ +/* + * Freescale STMP378X I2C low-level/dma functions + * + * Author: Dmitrij Frasenyak <sed@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> + +#include <linux/dma-mapping.h> +#include <mach/hardware.h> +#include <mach/regs-i2c.h> +#include <mach/regs-apbx.h> +#include <mach/dma.h> +#include <mach/i2c.h> +#include <mach/platform.h> +#include <mach/pinmux.h> + +#define STMP378X_APBX_I2C 3 + +static unsigned int dma_channel = + STMP3XXX_DMA(STMP378X_APBX_I2C, STMP3XXX_BUS_APBX); + + +static struct stmp3xxx_dma_descriptor i2c_dma_read[2]; +static struct stmp3xxx_dma_descriptor i2c_dma_write; +static dma_addr_t i2c_buf_phys; +static u8 *i2c_buf_virt; + + +/* + * Select device to read from + */ + +u32 cmd_i2c_select[4] = { + 0, /* Chain to i2c_read */ + + (BF(1, APBX_CHn_CMD_XFER_COUNT) | + /* BM_APBX_CHn_CMD_SEMAPHORE | */ + BF(1, APBX_CHn_CMD_CMDWORDS) | + BM_APBX_CHn_CMD_WAIT4ENDCMD | + BM_APBX_CHn_CMD_CHAIN | + BM_APBX_CHn_CMD_IRQONCMPLT | /* For debug*/ + BF(BV_APBX_CHn_CMD_COMMAND__DMA_READ, APBX_CHn_CMD_COMMAND)), + + 0, /* dma handler */ + + BM_I2C_CTRL0_RETAIN_CLOCK | + BM_I2C_CTRL0_PRE_SEND_START | + BM_I2C_CTRL0_MASTER_MODE | + BM_I2C_CTRL0_DIRECTION | + BF(1, I2C_CTRL0_XFER_COUNT) + +}; + +u32 cmd_i2c_write[4] = { + 0, + + (BM_APBX_CHn_CMD_SEMAPHORE | + BF(1, APBX_CHn_CMD_CMDWORDS) | + BM_APBX_CHn_CMD_WAIT4ENDCMD | + BM_APBX_CHn_CMD_IRQONCMPLT | + BF(BV_APBX_CHn_CMD_COMMAND__DMA_READ, APBX_CHn_CMD_COMMAND)), + + 0, /* dma handler */ + + BM_I2C_CTRL0_PRE_SEND_START | + BM_I2C_CTRL0_MASTER_MODE | +/* BM_I2C_CTRL0_POST_SEND_STOP | */ + BM_I2C_CTRL0_DIRECTION + +}; + + +u32 cmd_i2c_read[4] = { + 0, + + (BM_APBX_CHn_CMD_SEMAPHORE | + BF(1, APBX_CHn_CMD_CMDWORDS) | + BM_APBX_CHn_CMD_WAIT4ENDCMD | + BM_APBX_CHn_CMD_IRQONCMPLT | + BF(BV_APBX_CHn_CMD_COMMAND__DMA_WRITE, APBX_CHn_CMD_COMMAND)), + + 0, /* dma handler */ + + BM_I2C_CTRL0_SEND_NAK_ON_LAST | +/* BM_I2C_CTRL0_POST_SEND_STOP | */ + BM_I2C_CTRL0_MASTER_MODE | + 0 +}; + + +int hw_i2c_init_dma(struct device *dev) +{ + int ret; + + ret = stmp3xxx_dma_request(dma_channel, dev, "i2c"); + if (ret) { + dev_err(dev, "stmp3xxx_dma_request failed: error %d\n", ret); + return ret; + } + + i2c_buf_virt = + dma_alloc_coherent( + dev, + PAGE_SIZE, + &i2c_buf_phys, + GFP_KERNEL); + + if (i2c_buf_virt == NULL) + return -ENOMEM; + + + stmp3xxx_dma_allocate_command( + dma_channel, + &i2c_dma_read[0]); + + stmp3xxx_dma_allocate_command( + dma_channel, + &i2c_dma_read[1]); + + stmp3xxx_dma_allocate_command( + dma_channel, + &i2c_dma_write); + + stmp3xxx_dma_reset_channel(dma_channel); + stmp3xxx_dma_clear_interrupt(dma_channel); + stmp3xxx_dma_enable_interrupt(dma_channel); + return 0; +}; + +void hw_i2c_free_dma(struct device *dev) +{ + stmp3xxx_dma_free_command( + dma_channel, + &i2c_dma_write); + + stmp3xxx_dma_free_command( + dma_channel, + &i2c_dma_read[1]); + + stmp3xxx_dma_free_command( + dma_channel, + &i2c_dma_read[0]); + + dma_free_coherent( + dev, + PAGE_SIZE, + i2c_buf_virt, + i2c_buf_phys); + + stmp3xxx_dma_release(dma_channel); +} + +void hw_i2c_clear_dma_interrupt(void) +{ + stmp3xxx_dma_clear_interrupt(dma_channel); +} +EXPORT_SYMBOL(hw_i2c_clear_dma_interrupt); + +void hw_i2c_setup_write(u8 addr, void *buff, int len, int flags) +{ + + memcpy(i2c_dma_write.command, &cmd_i2c_write, sizeof(cmd_i2c_write)); + + i2c_dma_write.command->cmd |= + BF(len+1, APBX_CHn_CMD_XFER_COUNT); + + i2c_dma_write.command->pio_words[0] |= + BF(len+1, I2C_CTRL0_XFER_COUNT) | flags; + + i2c_dma_write.command->buf_ptr = i2c_buf_phys; + i2c_buf_virt[0] = addr | I2C_WRITE ; + memcpy(&i2c_buf_virt[1], buff, len); +} +EXPORT_SYMBOL(hw_i2c_setup_write); + +void hw_i2c_finish_read(void *buff, int len) +{ + memcpy(buff, &i2c_buf_virt[1], len); + +} +EXPORT_SYMBOL(hw_i2c_finish_read); + +void hw_i2c_setup_read(u8 addr, void *buff, int len, int flags) +{ + + if (len > (PAGE_SIZE - 4)) + BUG(); + + memcpy(i2c_dma_read[0].command, + &cmd_i2c_select, + sizeof(cmd_i2c_select)); + + memcpy(i2c_dma_read[1].command, + &cmd_i2c_read, + sizeof(cmd_i2c_read)); + + i2c_dma_read[0].command->next = i2c_dma_read[1].handle; + i2c_dma_read[0].command->buf_ptr = i2c_buf_phys ; + i2c_buf_virt[0] = addr | I2C_READ ; + + i2c_dma_read[1].command->cmd |= BF(len, APBX_CHn_CMD_XFER_COUNT); + + i2c_dma_read[1].command->pio_words[0] |= + BF(len, I2C_CTRL0_XFER_COUNT) | flags; + + i2c_dma_read[1].command->buf_ptr = (u32)i2c_buf_phys + 1 ; + memcpy(&i2c_buf_virt[1], buff, len); + +} +EXPORT_SYMBOL(hw_i2c_setup_read); + +void hw_i2c_run(int dir) +{ + if (dir == I2C_WRITE) + stmp3xxx_dma_go(dma_channel, &i2c_dma_write, 1); + else + stmp3xxx_dma_go(dma_channel, &i2c_dma_read[0], 1); +} +EXPORT_SYMBOL(hw_i2c_run); + +void hw_i2c_reset_dma(void) +{ + stmp3xxx_dma_reset_channel(dma_channel); + stmp3xxx_dma_clear_interrupt(dma_channel); +} +EXPORT_SYMBOL(hw_i2c_reset_dma); + + +int hw_i2c_init(struct device *dev) +{ + if (stmp3xxx_request_pin_group(dev->platform_data, "i2c")) + return -1; + + + /* Take controller out of reset */ + stmp3xxx_clearl(BM_I2C_CTRL0_SFTRST | + BM_I2C_CTRL0_CLKGATE, + REGS_I2C_BASE + HW_I2C_CTRL0); + udelay(10); + +/* * Set timing + * High time = 120 clks; read bit at 48 for 95Khz/24mhz + * Low time = 128 clks; write bit at 48 for 95khz/24mhz +*/ + +/* + Don't set 400khz by default; stfm1000 needs 100khz at the start. + __raw_writel(0x00780030, REGS_I2C_BASE + HW_I2C_TIMING0); + __raw_writel(0x001F000F, REGS_I2C_BASE + HW_I2C_TIMING1); + __raw_writel(0x0015000D, REGS_I2C_BASE + HW_I2C_TIMING2); +*/ + dev_dbg(dev, "I2C module version %x\n ", + __raw_readl(REGS_I2C_BASE + HW_I2C_VERSION)); + hw_i2c_init_dma(dev); + return 0; +} +EXPORT_SYMBOL(hw_i2c_init); + +void hw_i2c_stop(struct device *dev) +{ + stmp3xxx_setl(BM_I2C_CTRL0_SFTRST, + REGS_I2C_BASE + HW_I2C_CTRL0); + hw_i2c_reset_dma(); + hw_i2c_free_dma(dev); + stmp3xxx_release_pin_group(dev->platform_data, "i2c"); +} +EXPORT_SYMBOL(hw_i2c_stop); diff --git a/arch/arm/mach-stmp378x/stmp378x_lcdif.c b/arch/arm/mach-stmp378x/stmp378x_lcdif.c new file mode 100644 index 000000000000..abd1d7234950 --- /dev/null +++ b/arch/arm/mach-stmp378x/stmp378x_lcdif.c @@ -0,0 +1,208 @@ +/* + * Freescale STMP378X LCDIF low-level routines + * + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* #define DEBUG */ + +#include <linux/dma-mapping.h> +#include <linux/delay.h> + +#include <mach/hardware.h> +#include <mach/dma.h> +#include <mach/dma.h> +#include <mach/regs-lcdif.h> +#include <mach/regs-pinctrl.h> +#include <mach/lcdif.h> + +#define MAX_CHAIN_LEN 10 + +static struct stmp3xxx_dma_descriptor video_dma_descriptor[MAX_CHAIN_LEN]; +static struct stmp3xxx_lcd_dma_chain_info dma_chain_info[MAX_CHAIN_LEN]; +static unsigned dma_chain_info_pos; + +void stmp3xxx_init_lcdif(void) +{ + /* Reset controller */ + stmp3xxx_setl(BM_LCDIF_CTRL_SFTRST, REGS_LCDIF_BASE + HW_LCDIF_CTRL); + udelay(10); + + /* Take controller out of reset */ + stmp3xxx_clearl(BM_LCDIF_CTRL_SFTRST | BM_LCDIF_CTRL_CLKGATE, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + + /* Setup the bus protocol */ + stmp3xxx_clearl(BM_LCDIF_CTRL1_MODE86, + REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + stmp3xxx_clearl(BM_LCDIF_CTRL1_BUSY_ENABLE, + REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + + /* Take display out of reset */ + stmp3xxx_setl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + + /* VSYNC is an input by default */ + stmp3xxx_setl(BM_LCDIF_VDCTRL0_VSYNC_OEB, + REGS_LCDIF_BASE + HW_LCDIF_VDCTRL0); + + /* Reset display */ + stmp3xxx_clearl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + udelay(10); + stmp3xxx_setl(BM_LCDIF_CTRL1_RESET, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + udelay(10); +} + +EXPORT_SYMBOL(stmp3xxx_init_lcdif); + +static int stmp378x_lcd_master = 1; +int stmp3xxx_lcdif_dma_init(struct device *dev, dma_addr_t phys, int memsize, + int lcd_master) +{ + int ret = 0; + + stmp378x_lcd_master = lcd_master; + if (lcd_master) { + stmp3xxx_setl(BM_LCDIF_CTRL_LCDIF_MASTER, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + + __raw_writel(phys, REGS_LCDIF_BASE + HW_LCDIF_CUR_BUF); + __raw_writel(phys, REGS_LCDIF_BASE + HW_LCDIF_NEXT_BUF); + } else { + ret = + stmp3xxx_dma_request(STMP3XXX_DMA + (LCD_DMA_CHANNEL, STMP3XXX_BUS_APBH), + dev, "lcdif"); + if (ret) { + dev_err(dev, + "stmp3xxx_dma_request failed: error %d\n", ret); + goto out; + } + + stmp3xxx_dma_reset_channel(STMP3XXX_DMA + (LCD_DMA_CHANNEL, + STMP3XXX_BUS_APBH)); + + stmp3xxx_dma_clear_interrupt(STMP3XXX_DMA + (LCD_DMA_CHANNEL, + STMP3XXX_BUS_APBH)); + stmp3xxx_dma_enable_interrupt(STMP3XXX_DMA + (LCD_DMA_CHANNEL, + STMP3XXX_BUS_APBH)); + + dotclk_dma_chain_init(memsize, phys, video_dma_descriptor, + dma_chain_info, &dma_chain_info_pos); + } +out: + return ret; +} + +EXPORT_SYMBOL(stmp3xxx_lcdif_dma_init); + +void stmp3xxx_lcdif_dma_release(void) +{ + int i; + + if (stmp378x_lcd_master) { + stmp3xxx_clearl(BM_LCDIF_CTRL_LCDIF_MASTER, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + return; + } + + for (i = 0; i < dma_chain_info_pos; i++) + stmp3xxx_dma_free_command(STMP3XXX_DMA + (LCD_DMA_CHANNEL, STMP3XXX_BUS_APBH), + &video_dma_descriptor[i]); + stmp3xxx_dma_release(STMP3XXX_DMA(LCD_DMA_CHANNEL, STMP3XXX_BUS_APBH)); + + dma_chain_info_pos = 0; +} + +EXPORT_SYMBOL(stmp3xxx_lcdif_dma_release); + +void stmp3xxx_lcdif_run(void) +{ + if (stmp378x_lcd_master) { + stmp3xxx_setl(BM_LCDIF_CTRL_LCDIF_MASTER, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + stmp3xxx_setl(BM_LCDIF_CTRL_RUN, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + } else { + video_dma_descriptor[dma_chain_info_pos - 1].command->cmd &= + ~BM_APBH_CHn_CMD_SEMAPHORE; + stmp3xxx_dma_go(STMP3XXX_DMA + (LCD_DMA_CHANNEL, STMP3XXX_BUS_APBH), + video_dma_descriptor, 1); + } +} + +EXPORT_SYMBOL(stmp3xxx_lcdif_run); + +void stmp3xxx_lcdif_stop(void) +{ + if (stmp378x_lcd_master) { + stmp3xxx_clearl(BM_LCDIF_CTRL_RUN, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + stmp3xxx_clearl(BM_LCDIF_CTRL_LCDIF_MASTER, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + udelay(100); + } else { + video_dma_descriptor[dma_chain_info_pos - 1].command->cmd |= + BM_APBH_CHn_CMD_SEMAPHORE; + udelay(100); + } +} + +EXPORT_SYMBOL(stmp3xxx_lcdif_stop); + +int stmp3xxx_lcdif_pan_display(dma_addr_t addr) +{ + if (stmp378x_lcd_master) + __raw_writel(addr, REGS_LCDIF_BASE + HW_LCDIF_NEXT_BUF); + else { + int i; + /* Modify the chain addresses */ + for (i = 0; i < dma_chain_info_pos; ++i) { + *dma_chain_info[i].dma_addr_p = addr + + dma_chain_info[i].offset; + barrier(); + } + } + return 0; +} + +EXPORT_SYMBOL(stmp3xxx_lcdif_pan_display); + +static BLOCKING_NOTIFIER_HEAD(lcdif_client_list); + +int stmp3xxx_lcdif_register_client(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&lcdif_client_list, nb); +} + +EXPORT_SYMBOL(stmp3xxx_lcdif_register_client); + +void stmp3xxx_lcdif_unregister_client(struct notifier_block *nb) +{ + blocking_notifier_chain_unregister(&lcdif_client_list, nb); +} + +EXPORT_SYMBOL(stmp3xxx_lcdif_unregister_client); + +void stmp3xxx_lcdif_notify_clients(unsigned long event, + struct stmp3xxx_platform_fb_entry *pentry) +{ + blocking_notifier_call_chain(&lcdif_client_list, event, pentry); +} + +EXPORT_SYMBOL(stmp3xxx_lcdif_notify_clients); diff --git a/arch/arm/mach-stmp378x/stmp378x_pwm_led.c b/arch/arm/mach-stmp378x/stmp378x_pwm_led.c new file mode 100644 index 000000000000..7fe4892d326b --- /dev/null +++ b/arch/arm/mach-stmp378x/stmp378x_pwm_led.c @@ -0,0 +1,54 @@ +/* + * Freescale STMP378X PWM LEDs pin multiplexing + * + * Author: Drew Bendetti <drewb@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/module.h> +#include <mach/pinmux.h> + +#define PWM_PINID(n) STMP3XXX_PINID(1, 26 + n) + +int pwm_led_pinmux_request(int pwmn, char *title) +{ + int rc = 0; + + /* PIN_FUN1 is PWM for these pins */ + rc = stmp3xxx_request_pin(PWM_PINID(pwmn), PIN_FUN1, title); + if (rc) + return rc; + + stmp3xxx_pin_voltage(PWM_PINID(pwmn), PIN_3_3V, title); + /* pwm0-3 support 4,8,12mA; pwm4 supports 8,16,24mA + * I'm forcing 8 here since it's the only one in common + */ + stmp3xxx_pin_strength(PWM_PINID(pwmn), PIN_8MA, title); + + return 0; +} +EXPORT_SYMBOL_GPL(pwm_led_pinmux_request); + +void pwm_led_pinmux_free(int pwmn, char *title) +{ + stmp3xxx_pin_voltage(PWM_PINID(pwmn), PIN_4MA, title); + stmp3xxx_pin_strength(PWM_PINID(pwmn), PIN_1_8V, title); + + stmp3xxx_release_pin(PWM_PINID(pwmn), title); +} +EXPORT_SYMBOL_GPL(pwm_led_pinmux_free); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Drew Benedetti <drewb@embeddedalley.com>"); diff --git a/arch/arm/mach-stmp378x/tvenc.c b/arch/arm/mach-stmp378x/tvenc.c new file mode 100644 index 000000000000..85697b4a05c1 --- /dev/null +++ b/arch/arm/mach-stmp378x/tvenc.c @@ -0,0 +1,285 @@ +/* + * Freescale STMP378X dvi panel initialization + * + * Embedded Alley Solutions, Inc <source@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* #define DEBUG */ + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/clk.h> + +#include <mach/regs-lcdif.h> +#include <mach/regs-lradc.h> +#include <mach/regs-pinctrl.h> +#include <mach/regs-clkctrl.h> +#include <mach/regs-pwm.h> +#include <mach/regs-apbh.h> +#include <mach/gpio.h> +#include <mach/pins.h> +#include <mach/pinmux.h> +#include <mach/lcdif.h> +#include <mach/stmp3xxx.h> +#include <mach/platform.h> + +#include <mach/regs-tvenc.h> + +enum { + TVENC_MODE_OFF = 0, + TVENC_MODE_NTSC, + TVENC_MODE_PAL, +}; + +/* NTSC 720x480 mode */ +#define NTSC_X_RES 720 +#define NTSC_Y_RES 480 +#define NTSC_H_BLANKING 262 +#define NTSC_V_LINES 525 + +/* PAL 720x576 mode */ +#define PAL_X_RES 720 +#define PAL_Y_RES 576 +#define PAL_H_BLANKING 274 +#define PAL_V_LINES 625 + +/* frame size */ +#define DVI_H_BLANKING(m) (m == TVENC_MODE_NTSC ? \ + NTSC_H_BLANKING : PAL_H_BLANKING) +#define DVI_V_LINES(m) (m == TVENC_MODE_NTSC ? \ + NTSC_V_LINES : PAL_V_LINES) +#define DVI_H_ACTIVE(m) (m == TVENC_MODE_NTSC ? NTSC_X_RES : PAL_X_RES) +#define DVI_V_ACTIVE(m) (m == TVENC_MODE_NTSC ? NTSC_Y_RES : PAL_Y_RES) +/* fileds range */ +#define DVI_F1_START(m) 1 +#define DVI_F1_END(m) (DVI_V_LINES(m) / 2) +#define DVI_F2_START(m) (DVI_F1_END(m) + 1) +#define DVI_F2_END(m) DVI_V_LINES(m) +/* blanking range */ +#define DVI_V1_BLANK_START(m) DVI_F1_END(m) +#define DVI_V1_BLANK_END(m) (DVI_V1_BLANK_START(m) + \ + (DVI_V_LINES(m) - DVI_V_ACTIVE(m)) / 2) +#define DVI_V2_BLANK_START(m) DVI_F2_END(m) +#define DVI_V2_BLANK_END(m) ((DVI_V2_BLANK_START(m) + \ + (DVI_V_LINES(m) - DVI_V_ACTIVE(m)) / 2 - 1) % \ + DVI_V_LINES(m)) + +static struct clk *lcd_clk; +static struct clk *clk_ref_vid; +static struct clk *clk_tv108M_ng; +static struct clk *clk_tv27M; + +static int tvenc_mode; + +static void init_tvenc_hw(int mode) +{ + /* Reset module */ + stmp3xxx_setl(BM_TVENC_CTRL_SFTRST, REGS_TVENC_BASE + HW_TVENC_CTRL); + udelay(10); + + /* Take module out of reset */ + stmp3xxx_clearl(BM_TVENC_CTRL_SFTRST | BM_TVENC_CTRL_CLKGATE, + REGS_TVENC_BASE + HW_TVENC_CTRL); + + if (mode == TVENC_MODE_NTSC) { + /* Config NTSC-M mode, 8-bit Y/C in, SYNC out */ + stmp3xxx_clearl(BM_TVENC_CONFIG_SYNC_MODE | + BM_TVENC_CONFIG_PAL_SHAPE | + BM_TVENC_CONFIG_YGAIN_SEL | + BM_TVENC_CONFIG_CGAIN, + REGS_TVENC_BASE + HW_TVENC_CONFIG); + stmp3xxx_setl(BM_TVENC_CONFIG_FSYNC_PHS | + BF(0x4, TVENC_CONFIG_SYNC_MODE), + REGS_TVENC_BASE + HW_TVENC_CONFIG); + + /* 859 pixels/line for NTSC */ + __raw_writel(857, REGS_TVENC_BASE + HW_TVENC_SYNCOFFSET); + + __raw_writel(0x21F07C1F, REGS_TVENC_BASE + HW_TVENC_COLORSUB0); + stmp3xxx_clearl(BM_TVENC_COLORBURST_NBA | + BM_TVENC_COLORBURST_PBA, + REGS_TVENC_BASE + HW_TVENC_COLORBURST); + stmp3xxx_setl(BF(0xc8, TVENC_COLORBURST_NBA) | + BF(0, TVENC_COLORBURST_PBA), + REGS_TVENC_BASE + HW_TVENC_COLORBURST); + } else if (mode == TVENC_MODE_PAL) { + /* Config PAL-B mode, 8-bit Y/C in, SYNC out */ + stmp3xxx_clearl(BM_TVENC_CONFIG_SYNC_MODE | + BM_TVENC_CONFIG_ENCD_MODE | + BM_TVENC_CONFIG_YGAIN_SEL | + BM_TVENC_CONFIG_CGAIN | + BM_TVENC_CONFIG_FSYNC_PHS, + REGS_TVENC_BASE + HW_TVENC_CONFIG); + stmp3xxx_setl(BM_TVENC_CONFIG_PAL_SHAPE | + BF(1, TVENC_CONFIG_YGAIN_SEL) | BF(1, + TVENC_CONFIG_CGAIN) + | BF(0x1, TVENC_CONFIG_ENCD_MODE) | BF(0x4, + TVENC_CONFIG_SYNC_MODE), + REGS_TVENC_BASE + HW_TVENC_CONFIG); + + /* 863 pixels/line for PAL */ + __raw_writel(863, REGS_TVENC_BASE + HW_TVENC_SYNCOFFSET); + + __raw_writel(0x2A098ACB, REGS_TVENC_BASE + HW_TVENC_COLORSUB0); + stmp3xxx_clearl(BM_TVENC_COLORBURST_NBA | + BM_TVENC_COLORBURST_PBA, + REGS_TVENC_BASE + HW_TVENC_COLORBURST); + stmp3xxx_setl(BF(0xd6, TVENC_COLORBURST_NBA) | + BF(0x2a, TVENC_COLORBURST_PBA), + REGS_TVENC_BASE + HW_TVENC_COLORBURST); + } + + /* Power up DAC */ + __raw_writel(BM_TVENC_DACCTRL_GAINDN | + BM_TVENC_DACCTRL_GAINUP | + BM_TVENC_DACCTRL_PWRUP1 | + BM_TVENC_DACCTRL_DUMP_TOVDD1 | + BF(3, TVENC_DACCTRL_RVAL), + REGS_TVENC_BASE + HW_TVENC_DACCTRL); + + /* set all to zero is a requirement for NTSC */ + __raw_writel(0, REGS_TVENC_BASE + HW_TVENC_MACROVISION0); + __raw_writel(0, REGS_TVENC_BASE + HW_TVENC_MACROVISION1); + __raw_writel(0, REGS_TVENC_BASE + HW_TVENC_MACROVISION2); + __raw_writel(0, REGS_TVENC_BASE + HW_TVENC_MACROVISION3); + __raw_writel(0, REGS_TVENC_BASE + HW_TVENC_MACROVISION4); +} + +static int init_panel(struct device *dev, dma_addr_t phys, int memsize, + struct stmp3xxx_platform_fb_entry *pentry) +{ + int ret = 0; + + lcd_clk = clk_get(dev, "lcdif"); + clk_enable(lcd_clk); + clk_set_rate(lcd_clk, 1000000 / pentry->cycle_time_ns); /* kHz */ + + clk_ref_vid = clk_get(NULL, "ref_vid"); + clk_tv108M_ng = clk_get(NULL, "tv108M_ng"); + clk_tv27M = clk_get(NULL, "tv27M"); + clk_enable(clk_ref_vid); + clk_enable(clk_tv108M_ng); + clk_enable(clk_tv27M); + + tvenc_mode = pentry->x_res == NTSC_Y_RES ? TVENC_MODE_NTSC : + TVENC_MODE_PAL; + + init_tvenc_hw(tvenc_mode); + + setup_dvi_panel(DVI_H_ACTIVE(tvenc_mode), DVI_V_ACTIVE(tvenc_mode), + DVI_H_BLANKING(tvenc_mode), DVI_V_LINES(tvenc_mode), + DVI_V1_BLANK_START(tvenc_mode), + DVI_V1_BLANK_END(tvenc_mode), + DVI_V2_BLANK_START(tvenc_mode), + DVI_V2_BLANK_END(tvenc_mode), + DVI_F1_START(tvenc_mode), DVI_F1_END(tvenc_mode), + DVI_F2_START(tvenc_mode), DVI_F2_END(tvenc_mode)); + + ret = stmp3xxx_lcdif_dma_init(dev, phys, memsize, 1); + + return ret; +} + +static void release_panel(struct device *dev, + struct stmp3xxx_platform_fb_entry *pentry) +{ + release_dvi_panel(); + + stmp3xxx_lcdif_dma_release(); + + clk_disable(clk_ref_vid); + clk_disable(clk_tv108M_ng); + clk_disable(clk_tv27M); + clk_disable(lcd_clk); + clk_put(clk_ref_vid); + clk_put(clk_tv108M_ng); + clk_put(clk_tv27M); + clk_put(lcd_clk); +} + +static int blank_panel(int blank) +{ + int ret = 0, count; + + switch (blank) { + case FB_BLANK_NORMAL: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + stmp3xxx_clearl(BM_LCDIF_CTRL_BYPASS_COUNT, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + + /* Wait until current transfer is complete, max 30ms */ + for (count = 30000; count > 0; count--) { + if (__raw_readl(REGS_LCDIF_BASE + HW_LCDIF_STAT) & + BM_LCDIF_STAT_TXFIFO_EMPTY) + break; + udelay(1); + } + break; + + case FB_BLANK_UNBLANK: + stmp3xxx_setl(BM_LCDIF_CTRL_BYPASS_COUNT, + REGS_LCDIF_BASE + HW_LCDIF_CTRL); + break; + + default: + ret = -EINVAL; + } + return ret; +} + +static struct stmp3xxx_platform_fb_entry ntsc_fb_entry = { + .name = "tvenc_ntsc", + /* x/y swapped */ + .x_res = NTSC_Y_RES, + .y_res = NTSC_X_RES, + .bpp = 32, + /* the pix_clk should be near 27Mhz for proper syncronization */ + .cycle_time_ns = 74, + .lcd_type = STMP3XXX_LCD_PANEL_DVI, + .init_panel = init_panel, + .release_panel = release_panel, + .blank_panel = blank_panel, + .run_panel = stmp3xxx_lcdif_run, + .pan_display = stmp3xxx_lcdif_pan_display, +}; + +static struct stmp3xxx_platform_fb_entry pal_fb_entry = { + .name = "tvenc_pal", + /* x/y swapped */ + .x_res = PAL_Y_RES, + .y_res = PAL_X_RES, + .bpp = 32, + /* the pix_clk should be near 27Mhz for proper syncronization */ + .cycle_time_ns = 74, + .lcd_type = STMP3XXX_LCD_PANEL_DVI, + .init_panel = init_panel, + .release_panel = release_panel, + .blank_panel = blank_panel, + .run_panel = stmp3xxx_lcdif_run, + .pan_display = stmp3xxx_lcdif_pan_display, +}; + +static int __init register_devices(void) +{ + stmp3xxx_lcd_register_entry(&ntsc_fb_entry, + stmp3xxx_framebuffer.dev.platform_data); + stmp3xxx_lcd_register_entry(&pal_fb_entry, + stmp3xxx_framebuffer.dev.platform_data); + + return 0; +} + +subsys_initcall(register_devices); diff --git a/arch/arm/mm/cache-l2x0.c b/arch/arm/mm/cache-l2x0.c index ca69ff5981a6..6688b9bd17ec 100644 --- a/arch/arm/mm/cache-l2x0.c +++ b/arch/arm/mm/cache-l2x0.c @@ -17,6 +17,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/init.h> +#include <linux/module.h> #include <linux/spinlock.h> #include <linux/io.h> @@ -25,7 +26,6 @@ #define CACHE_LINE_SIZE 32 #ifdef CONFIG_OPROFILE_ARM11_EVTMON -#include <linux/module.h> #define L2_ENABLE_BIT 0x1 #define L2_EVTBUS_BIT 0x100000 #define L2_CTL_REG (l2x0_base + L2X0_CTRL) diff --git a/arch/arm/oprofile/Makefile b/arch/arm/oprofile/Makefile index 88e31f549f50..18c80fa3ad21 100644 --- a/arch/arm/oprofile/Makefile +++ b/arch/arm/oprofile/Makefile @@ -9,6 +9,7 @@ DRIVER_OBJS = $(addprefix ../../../drivers/oprofile/, \ oprofile-y := $(DRIVER_OBJS) common.o backtrace.o oprofile-$(CONFIG_CPU_XSCALE) += op_model_xscale.o oprofile-$(CONFIG_OPROFILE_ARM11_CORE) += op_model_arm11_core.o +oprofile-$(CONFIG_OPROFILE_ARM11_EVTMON) += op_model_arm11_evtmon.o oprofile-$(CONFIG_OPROFILE_ARMV6) += op_model_v6.o oprofile-$(CONFIG_OPROFILE_MPCORE) += op_model_mpcore.o oprofile-$(CONFIG_OPROFILE_ARMV7) += op_model_v7.o diff --git a/arch/arm/oprofile/evtmon_regs.h b/arch/arm/oprofile/evtmon_regs.h new file mode 100644 index 000000000000..5d7764aa3514 --- /dev/null +++ b/arch/arm/oprofile/evtmon_regs.h @@ -0,0 +1,86 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * @file evtmon_regs.h + * + * @brief EVTMON Register definitions + * + * @ingroup MXC_Oprofile + */ +#ifndef _EVTMON_REGS_INCLUDED_ +#define _EVTMON_REGS_INCLUDED_ + +#include <mach/hardware.h> + +#define MXC_CRM_AP_BASE (IO_ADDRESS(CRM_AP_BASE_ADDR)) +#define ECT_CTI_BASE (IO_ADDRESS(ECT_CTIO_BASE_ADDR)) +#define CLKCTR_BASE_ADDR (IO_ADDRESS(CLKCTL_BASE_ADDR)) +#define L2CC_BASE_ADDRESS (IO_ADDRESS(L2CC_BASE_ADDR)) +#define L2EM_BASE_ADDRESS (IO_ADDRESS(EVTMON_BASE_ADDR)) + +/* L2 INT settings */ +#define L2EM_ENABLE_OVERFLOWINT 0x1 +#define L2EM_ENABLE_CNTINCRINT 0x3 +#define L2EM_ENABLE_MASK 0x1 +#define L2EM_INT_EDGE 0x2 +#define L2EM_INT_HIGH 0x4 +#define L2EM_INT_CLK_CYCLES (0x0 << 3) + +/* Reg definitions for EVTMON */ +#define L2EM_CTRL (L2EM_BASE_ADDRESS + 0x0) +#define L2EM_STAT (L2EM_BASE_ADDRESS + 0x4) +#define L2EM_CC(nr) (L2EM_BASE_ADDRESS + 0x8 +(4*nr)) +#define L2EM_CNT(nr) (L2EM_BASE_ADDRESS + 0x20 +(4*nr)) + +/* Reg definitions for CLK_CTL */ +#define CLKCTL_SET_CTRL (CLKCTR_BASE_ADDR + 0x04) + +/* Reg definitions for ECT */ +#define ECT_CTI_CONTROL (ECT_CTI_BASE + 0x0) +#define ECT_CTI_LOCK (ECT_CTI_BASE + 0x8) +#define ECT_CTI_INEN(nr) (ECT_CTI_BASE + 0x20 + (4*nr)) +#define ECT_CTI_OUTEN(nr) (ECT_CTI_BASE + 0xA0 + (4*nr)) +#define ECT_CTI_INTACK (ECT_CTI_BASE + 0x10) +#define ECT_CTI_TRIGOUTSTATUS (ECT_CTI_BASE + 0x134) + +#define ENABLE_L2CACHE 0x1 +#define EVTMON_ENABLE 0x001 /* Enable EVTMON */ +#define EVT_UNUSED 0x100 +#define MAX_PMUCOUNTERS 3 + +#ifdef CONFIG_OPROFILE_ARM11_EVTMON +#define ECT_WORKAROUND +#endif + +#ifdef ECT_WORKAROUND +#define ENABLE_CTI_CLOCK 0x00000020 +#define UNLOCK_ECT_CODE 0x0ACCE550 +#define ECT_CTI_CHAN_2 0x4 +#define ECT_CTI_CHAN_3 0x8 +#define ECT_CTI_TRIGIN_1 1 +#define ECT_CTI_TRIGIN_7 7 +#define ECT_CTI_TRIGOUT_2 2 +#define ECT_CTI_TRIGOUT_6 6 +#define ENABLE_ECT 0x1 +#define ACK_TRIG_OUT_2 0x4 +#define EM_SET_INT L2EM_ENABLE_CNTINCRINT +#define EVENT_OVERFLOW_INT MXC_INT_ECT +#else +#define EVENT_OVERFLOW_INT ARM11_PMU_IRQ +#define EM_SET_INT L2EM_ENABLE_OVERFLOWINT +#endif + +int l2em_configure_counter(int nr, int type); +void write_l2counter(int nr, u32 val); +#endif diff --git a/arch/arm/oprofile/op_model_arm11.c b/arch/arm/oprofile/op_model_arm11.c new file mode 100644 index 000000000000..573269e36a48 --- /dev/null +++ b/arch/arm/oprofile/op_model_arm11.c @@ -0,0 +1,477 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup MXC_Oprofile ARM11 Driver for Oprofile + */ + +/*! + * @file op_model_arm11.c + * + *Based on the op_model_xscale.c driver by author Zwane Mwaikambo + * + * @ingroup MXC_Oprofile + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/oprofile.h> +#include <linux/interrupt.h> +#include <mach/hardware.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include "op_counter.h" +#include "op_arm_model.h" +#include "evtmon_regs.h" + +/*! + * defines used in ARM11 performance unit + */ +#define PMU_ENABLE 0x001 /* Enable counters */ +#define EVTMON_ENABLE 0x001 /* Enable EVTMON */ +#define PMN_RESET 0x002 /* Reset event counters */ +#define CCNT_RESET 0x004 /* Reset clock counter */ +#define PMU_RESET (CCNT_RESET | PMN_RESET) +#define PMU_CNT64 0x008 /* Make CCNT count every 64th cycle */ + +#define PMU_FLAG_CR0 0x080 +#define PMU_FLAG_CR1 0x100 +#define PMU_FLAG_CC 0x200 + +/*! + * Different types of events that can be counted by the ARM11 PMU + * as used by Oprofile userspace. + */ +#define EVT_ICACHE_MISS 0x00 +#define EVT_STALL_INSTR 0x01 +#define EVT_DATA_STALL 0x02 +#define EVT_ITLB_MISS 0x03 +#define EVT_DTLB_MISS 0x04 +#define EVT_BRANCH 0x05 +#define EVT_BRANCH_MISS 0x06 +#define EVT_INSTRUCTION 0x07 +#define EVT_DCACHE_FULL_STALL_CONTIG 0x09 +#define EVT_DCACHE_ACCESS 0x0A +#define EVT_DCACHE_MISS 0x0B +#define EVT_DCACE_WRITE_BACK 0x0C +#define EVT_PC_CHANGED 0x0D +#define EVT_TLB_MISS 0x0F +#define EVT_BCU_REQUEST 0x10 +#define EVT_BCU_FULL 0x11 +#define EVT_BCU_DRAIN 0x12 +#define EVT_ETMEXTOT0 0x20 +#define EVT_ETMEXTOT1 0x21 +/* EVT_CCNT is not hardware defined */ +#define EVT_CCNT 0xFE +#define EVT_INCREMENT 0xFF +#define EVT_UNUSED 0x100 + +#define ECT_WORKAROUND + +#define COUNTER_MSB 0x80000000 +#define ENABLE_L2CACHE 0x1 +#define ENABLE_EVTBUS 0x100000 +#define PMU_OVERFLOWBIT_MASK 0x700 +#define VAR_NUM 0x0 +#define REV_NUM 0x2 + +#ifdef ECT_WORKAROUND +#define ENABLE_CTI_CLOCK 0x00000020 +#define UNLOCK_ECT_CODE 0x0ACCE550 +#define ECT_CTI_CHAN_2 0x4 +#define ECT_CTI_CHAN_3 0x8 +#define ECT_CTI_TRIGIN_1 1 +#define ECT_CTI_TRIGIN_7 7 +#define ECT_CTI_TRIGOUT_2 2 +#define ECT_CTI_TRIGOUT_6 6 +#define ENABLE_ECT 0x1 +#define ACK_TRIG_OUT_2 0x4 +#define EM_SET_INT L2EM_ENABLE_CNTINCRINT +#define EVENT_OVERFLOW_INT INT_ECT +#else +#define EVENT_OVERFLOW_INT ARM11_PMU_IRQ +#define EM_SET_INT L2EM_ENABLE_OVERFLOWINT +#endif + +struct pmu_counter { + volatile unsigned long ovf; + unsigned long reset_counter; +}; + +static unsigned int r0p2_or_older_core; +enum { CCNT, PMN0, PMN1, MAX_PMUCOUNTERS }; +enum { EMC0 = MAX_PMUCOUNTERS, EMC1, EMC2, EMC3, MAX_L2COUNTERS }; + +static struct pmu_counter results[MAX_L2COUNTERS]; + +struct pmu_type { + int id; + char *name; + int num_counters; + unsigned int int_enable; + unsigned int cnt_ovf[MAX_L2COUNTERS]; + unsigned int int_mask[MAX_L2COUNTERS]; +}; + +static struct pmu_type pmu_parms[] = { + { + .id = 0, + .name = "arm/arm11", + .num_counters = MAX_L2COUNTERS, + .int_mask = {[PMN0] = 0x10,[PMN1] = 0x20, + [CCNT] = 0x40,[EMC0] = 0x800,[EMC1] = 0x400,[EMC2] = + 0x200,[EMC3] = 0x100}, + .cnt_ovf = {[CCNT] = 0x400,[PMN0] = 0x100, + [PMN1] = 0x200,[EMC0] = 0x1,[EMC1] = 0x2,[EMC2] = + 0x4,[EMC3] = 0x8}, + }, +}; + +static struct pmu_type *pmu; + +extern void l2_evtbus_enable(void); +extern void l2_evtbus_disable(void); + +/*! + * function is used to write the EVTMON counter configuration register. + */ +static int l2em_configure_counter(int nr, int type) +{ + /* Configure the counter event source */ + __raw_writel(((type << 2) & 0x7c), L2EM_CC(nr)); + if (type) + __raw_writel((__raw_readl(L2EM_CC(nr)) | EM_SET_INT), + L2EM_CC(nr)); + + return 0; +} + +/*! + * function is used to write the EVTMON counters + */ +static void write_l2counter(int nr, u32 val) +{ + __raw_writel(val, L2EM_CNT(nr)); +} + +/*! + * function is used to write the control register for the ARM11 performance counters + */ +static void write_pmnc(u32 val) +{ + pr_debug("PMC value written is %#08x\n", val); + __asm__ __volatile__("mcr p15, 0, %0, c15, c12, 0"::"r"(val)); +} + +/*! + * function is used to read the control register for the ARM11 performance counters + */ +static u32 read_pmnc(void) +{ + u32 val; + pr_debug("In function %s\n", __FUNCTION__); + __asm__ __volatile__("mrc p15, 0, %0, c15, c12, 0":"=r"(val)); + pr_debug("PMC value read is %#08x\n", val); + return val; +} + +/*! + * function is used to read the ARM11 performance counters + */ +static u32 read_counter(int counter) +{ + u32 val = 0; + pr_debug("In function %s\n", __FUNCTION__); + + switch (counter) { + case CCNT: + __asm__ __volatile__("mrc p15, 0, %0, c15, c12, 1":"=r"(val)); + break; + case PMN0: + __asm__ __volatile__("mrc p15, 0, %0, c15, c12, 2":"=r"(val)); + break; + case PMN1: + __asm__ __volatile__("mrc p15, 0, %0, c15, c12, 3":"=r"(val)); + break; + } + + pr_debug("counter %d value read is %#08x\n", counter, val); + return val; +} + +/*! + * function is used to write to the ARM11 performance counters + */ +static void write_counter(int counter, u32 val) +{ + pr_debug("counter %d value written is %#08x\n", counter, val); + + switch (counter) { + case CCNT: + __asm__ __volatile__("mcr p15, 0, %0, c15, c12, 1": :"r"(val)); + break; + case PMN0: + __asm__ __volatile__("mcr p15, 0, %0, c15, c12, 2": :"r"(val)); + break; + case PMN1: + __asm__ __volatile__("mcr p15, 0, %0, c15, c12, 3": :"r"(val)); + break; + } +} + +/*! + * function is used to check the status of the ARM11 performance counters + */ +static int arm11_setup_ctrs(void) +{ + u32 pmnc = 0; + int i; + + for (i = CCNT; i < MAX_L2COUNTERS; i++) { + if (counter_config[i].enabled) + continue; + counter_config[i].event = EVT_UNUSED; + } + + if (counter_config[PMN0].enabled) + pmnc |= (counter_config[PMN0].event << 20); + + if (counter_config[PMN1].enabled) + pmnc |= (counter_config[PMN1].event << 12); + + pr_debug("arm11_setup_ctrs: pmnc: %#08x\n", pmnc); + write_pmnc(pmnc); + + for (i = CCNT; i < MAX_L2COUNTERS; i++) { + if (counter_config[i].event == EVT_UNUSED) { + counter_config[i].event = 0; + pmu->int_enable &= ~pmu->int_mask[i]; + pr_debug + ("arm11_setup_ctrs: The counter event is %d for counter%d\n", + counter_config[i].event, i); + continue; + } + + results[i].reset_counter = counter_config[i].count; + if (i < MAX_PMUCOUNTERS) + write_counter(i, -(u32) counter_config[i].count); + else { + write_l2counter(i - EMC0, + -(u32) counter_config[i].count); + l2em_configure_counter(i - EMC0, + counter_config[i].event); + } + pmu->int_enable |= pmu->int_mask[i]; + pr_debug + ("arm11_setup_ctrs: The values of int mask and enables are %x, %x\n", + pmu->int_mask[i], pmu->int_enable); + + pr_debug("arm11_setup_ctrs: counter%d %#08x from %#08lx\n", i, + read_counter(i), counter_config[i].count); + } + + return 0; +} + +/*! + * function is the interrupt service handler for the ARM11 performance counters + */ +static irqreturn_t arm11_pmu_interrupt(int irq, void *arg, struct pt_regs *regs) +{ + int i; + u32 pmnc, emcs; + + /* Disable L2_EVTMON */ + emcs = __raw_readl(L2EM_STAT); + __raw_writel((__raw_readl(L2EM_CTRL) & ~EVTMON_ENABLE), L2EM_CTRL); + + /* Disable ARM11 PMU while retaining interrupts and overflow bits */ + pmnc = read_pmnc(); + pmnc &= ~(PMU_ENABLE | PMU_OVERFLOWBIT_MASK); + write_pmnc(pmnc); + + /* Read the overflow flag bits */ + pmnc = read_pmnc(); + +#ifdef ECT_WORKAROUND + for (i = CCNT; i < MAX_PMUCOUNTERS; i++) { +#else + for (i = CCNT; i < MAX_L2COUNTERS; i++) { +#endif + /* Process the counters only if respective overflow interrupt is enabled */ + if (!(pmu->int_mask[i] & pmu->int_enable)) { + continue; + } + + /* As per ARM11 errata ARM11 cores with revision less than or equal to R0P2 + * have known bug i.e., missing overflow interrupt for two events(event + * no 0x7 and 0x22) due to double increament for cycle. In this case we will + * discard the sample as the pc value belongs to different interrupt. + */ + if (r0p2_or_older_core && !(pmnc & pmu->cnt_ovf[i]) + && !(read_counter(i) & COUNTER_MSB)) { + write_counter(i, -(u32) (results[i].reset_counter)); + } + + /* Check for the overflowed counter by checking set overflow flag bits */ + if (!(pmnc & pmu->cnt_ovf[i]) && !(emcs & pmu->cnt_ovf[i])) { + continue; + } + + /* Reload the overflowed counter with preset value and + * add the sample for respective event. + */ + pr_debug("arm11_pmu_interrupt: writing to file\n"); + if (i < MAX_PMUCOUNTERS) + write_counter(i, -(u32) results[i].reset_counter); + else + write_l2counter(i - EMC0, + -(u32) counter_config[i].count); + + oprofile_add_sample(regs, i); + } + + /* Clear overflow flags */ + write_pmnc(pmnc); + +#ifdef ECT_WORKAROUND + /* + * If ECTTRIGOUT signal is interrupt it should be acknowledged + * until trigger is off. + */ + while (__raw_readl(ECT_CTI_TRIGOUTSTATUS) & ECT_CTI_CHAN_2) + __raw_writel(ACK_TRIG_OUT_2, ECT_CTI_INTACK); +#endif + + /* Re-enable ARM11 PMU */ + pmnc |= PMU_ENABLE; + write_pmnc(pmnc); + + /* Re-enable L2_EVTMON */ + __raw_writel((__raw_readl(L2EM_CTRL) | L2EM_ENABLE_MASK), L2EM_CTRL); + + return IRQ_HANDLED; +} + +/*! + * function used to start the ARM11 performance counters + */ +static void arm11_pmu_stop(void) +{ + u32 pmnc = read_pmnc(); + + pmnc &= ~PMU_ENABLE; + write_pmnc(pmnc); + /* Disable the EVTMON */ + __raw_writel((__raw_readl(L2EM_CTRL) & ~EVTMON_ENABLE), L2EM_CTRL); + + /* Disable the EVTBUS */ + l2_evtbus_disable(); + + free_irq(EVENT_OVERFLOW_INT, results); +} + +/*! + * function used to start the ARM11 performance counters + */ +static int arm11_pmu_start(void) +{ + int ret; + u32 pmnc = read_pmnc(); + + ret = request_irq(EVENT_OVERFLOW_INT, arm11_pmu_interrupt, SA_INTERRUPT, + "ARM11 PMU", (void *)results); + pr_debug("requested IRQ\n"); + + if (ret < 0) { + printk(KERN_ERR + "oprofile: unable to request IRQ%d for ARM11 PMU\n", + ARM11_PMU_IRQ); + return ret; + } + + /* Enable the EVTBUS */ + l2_evtbus_enable(); + +#ifdef ECT_WORKAROUND + __raw_writel(ENABLE_CTI_CLOCK, CLKCTL_SET_CTRL); + /* Unlock the AHB Interface */ + __raw_writel(UNLOCK_ECT_CODE, ECT_CTI_LOCK); + /* Trigger to Channel Mapping */ + __raw_writel(ECT_CTI_CHAN_2, ECT_CTI_INEN(ECT_CTI_TRIGIN_1)); + /* Channel to triggers mapping */ + __raw_writel(ECT_CTI_CHAN_2, ECT_CTI_OUTEN(ECT_CTI_TRIGOUT_2)); + /* Trigger to Channel Mapping */ + __raw_writel(ECT_CTI_CHAN_3, ECT_CTI_INEN(ECT_CTI_TRIGIN_7)); + /* Channel to triggers mapping */ + __raw_writel(ECT_CTI_CHAN_3, ECT_CTI_OUTEN(ECT_CTI_TRIGOUT_6)); + /* Enable CTI Logic */ + __raw_writel(ENABLE_ECT, ECT_CTI_CONTROL); +#endif + pmnc |= pmu->int_enable; + pmnc |= PMU_ENABLE; + + write_pmnc(pmnc); + pr_debug("arm11_pmu_start: pmnc: %#08x mask: %08x\n", pmnc, + pmu->int_enable); + + /* Enable EVTMON with Edge triggered interrupt of one Clock Cycle */ + __raw_writel((__raw_readl(L2EM_CTRL) | + (L2EM_INT_EDGE | L2EM_INT_CLK_CYCLES)), L2EM_CTRL); + __raw_writel((__raw_readl(L2EM_CTRL) | L2EM_ENABLE_MASK), L2EM_CTRL); + + return 0; +} + +/*! + * function detect the ARM11 performance counters + */ +static int arm11_detect_pmu(void) +{ + int ret = 0; + u32 id, rev; + + id = (read_cpuid(CPUID_ID) >> 0x10) & 0xF; + + switch (id) { + case 7: + pmu = &pmu_parms[0]; + rev = read_cpuid(CPUID_ID); + /* Check if the ARM11 core is less than or equal to R0P2 */ + if ((((rev >> 0x14) & 0xF) == VAR_NUM) + && (((rev & 0xF) <= REV_NUM))) { + r0p2_or_older_core = 1; + } + break; + default: + ret = -ENODEV; + break; + } + + if (!ret) { + op_arm_spec.name = pmu->name; + op_arm_spec.num_counters = pmu->num_counters; + pr_debug("arm11_detect_pmu: detected %s PMU\n", pmu->name); + } + + return ret; +} + +struct op_arm_model_spec op_arm_spec = { + .init = arm11_detect_pmu, + .setup_ctrs = arm11_setup_ctrs, + .start = arm11_pmu_start, + .stop = arm11_pmu_stop, +}; diff --git a/arch/arm/oprofile/op_model_arm11_core.c b/arch/arm/oprofile/op_model_arm11_core.c index ad80752cb9fb..86b7d86bb721 100644 --- a/arch/arm/oprofile/op_model_arm11_core.c +++ b/arch/arm/oprofile/op_model_arm11_core.c @@ -13,7 +13,12 @@ #include "op_counter.h" #include "op_arm_model.h" #include "op_model_arm11_core.h" +#include "evtmon_regs.h" +#define NEED_OPROFILE_CCNT_FIX +#ifdef NEED_OPROFILE_CCNT_FIX /* Workaround to correctly map from user space counters to kernel space counters */ +struct op_counter_config tmp; +#endif /* * ARM11 PMU support */ @@ -63,6 +68,12 @@ int arm11_setup_pmu(void) arm11_write_pmnc(PMCR_OFL_PMN0 | PMCR_OFL_PMN1 | PMCR_OFL_CCNT | PMCR_C | PMCR_P); +#ifdef NEED_OPROFILE_CCNT_FIX /* Workaround to correctly map from user space counters to kernel space counters */ + tmp = counter_config[PMN0]; + counter_config[PMN0] = counter_config[PMN1]; + counter_config[PMN1] = counter_config[CCNT]; + counter_config[CCNT] = tmp; +#endif for (pmnc = 0, cnt = PMN0; cnt <= CCNT; cnt++) { unsigned long event; @@ -119,16 +130,49 @@ static irqreturn_t arm11_pmu_interrupt(int irq, void *arg) unsigned int cnt; u32 pmnc; +#ifdef ECT_WORKAROUND + /* Disable L2_EVTMON */ + __raw_writel((__raw_readl(L2EM_CTRL) & ~EVTMON_ENABLE), L2EM_CTRL); + + /* Disable ARM11 PMU while retaining interrupts and overflow bits */ + pmnc = arm11_read_pmnc(); + pmnc &= ~(PMU_ENABLE | PMU_OVERFLOWBIT_MASK); + arm11_write_pmnc(pmnc); + + while (__raw_readl(ECT_CTI_TRIGOUTSTATUS) & ECT_CTI_CHAN_2) + __raw_writel(ACK_TRIG_OUT_2, ECT_CTI_INTACK); + + pmnc = arm11_read_pmnc(); + + /* Re-enable ARM11 PMU */ + pmnc |= PMU_ENABLE; + arm11_write_pmnc(pmnc); + + /* Re-enable L2_EVTMON */ + __raw_writel((__raw_readl(L2EM_CTRL) | L2EM_ENABLE_MASK), L2EM_CTRL); +#else pmnc = arm11_read_pmnc(); +#endif for (cnt = PMN0; cnt <= CCNT; cnt++) { if ((pmnc & (PMCR_OFL_PMN0 << cnt)) && (pmnc & (PMCR_IEN_PMN0 << cnt))) { arm11_reset_counter(cnt); +#ifdef NEED_OPROFILE_CCNT_FIX /* Workaround to correctly map from user space counters to kernel space counters */ + if (cnt == PMN0) + oprofile_add_sample(regs, CPU_COUNTER(smp_processor_id(), PMN1)); + else if (cnt == PMN1) + oprofile_add_sample(regs, CPU_COUNTER(smp_processor_id(), CCNT)); + else if (cnt == CCNT) + oprofile_add_sample(regs, CPU_COUNTER(smp_processor_id(), PMN0)); +#else oprofile_add_sample(regs, CPU_COUNTER(smp_processor_id(), cnt)); +#endif } } +#ifndef ECT_WORKAROUND /* Clear counter flag(s) */ arm11_write_pmnc(pmnc); +#endif return IRQ_HANDLED; } diff --git a/arch/arm/oprofile/op_model_arm11_core.h b/arch/arm/oprofile/op_model_arm11_core.h index 6f8538e5a960..b59d15107d8d 100644 --- a/arch/arm/oprofile/op_model_arm11_core.h +++ b/arch/arm/oprofile/op_model_arm11_core.h @@ -35,6 +35,8 @@ #define CCNT 2 #define CPU_COUNTER(cpu, counter) ((cpu) * 3 + (counter)) +#define PMU_ENABLE 0x001 /* Enable counters */ +#define PMU_OVERFLOWBIT_MASK 0x700 int arm11_setup_pmu(void); int arm11_start_pmu(void); @@ -42,4 +44,15 @@ int arm11_stop_pmu(void); int arm11_request_interrupts(int *, int); void arm11_release_interrupts(int *, int); +#ifdef CONFIG_OPROFILE_ARM11_EVTMON +extern int arm11_evtmon_setup_ctrs(void); +extern void arm11_evtmon_stop(void); +extern int arm11_evtmon_start(void); +extern int arm11_evtmon_detect(void); +#else +#define arm11_evtmon_setup_ctrs() do {} while(0) +#define arm11_evtmon_stop() do {} while(0) +#define arm11_evtmon_start() do {} while(0) +#define arm11_evtmon_detect() do {} while(0) +#endif #endif diff --git a/arch/arm/oprofile/op_model_arm11_evtmon.c b/arch/arm/oprofile/op_model_arm11_evtmon.c new file mode 100644 index 000000000000..5ac3fa1f074a --- /dev/null +++ b/arch/arm/oprofile/op_model_arm11_evtmon.c @@ -0,0 +1,188 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup MXC_Oprofile ARM11 EVTMON Driver for Oprofile + */ + +/*! + * @file op_model_arm11_evtmon.c + * + *Based on the op_model_xscale.c driver by author Zwane Mwaikambo + * + * @ingroup MXC_Oprofile + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/oprofile.h> +#include <linux/interrupt.h> +#include <mach/hardware.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include "op_counter.h" +#include "op_arm_model.h" +#include "evtmon_regs.h" + +struct pmu_counter { + volatile unsigned long ovf; + unsigned long reset_counter; +}; + +enum { EMC0 = MAX_PMUCOUNTERS, EMC1, EMC2, EMC3, MAX_L2COUNTERS }; + +static struct pmu_counter results[MAX_L2COUNTERS]; + +struct pmu_type { + int id; + char *name; + int num_counters; + unsigned int int_enable; + unsigned int cnt_ovf[MAX_L2COUNTERS]; + unsigned int int_mask[MAX_L2COUNTERS]; +}; + +static struct pmu_type pmu_parms[] = { + { + .id = 0, + .int_mask = {[EMC0] = 0x800,[EMC1] = 0x400,[EMC2] = + 0x200,[EMC3] = 0x100}, + .cnt_ovf = {[EMC0] = 0x1,[EMC1] = 0x2,[EMC2] = 0x4,[EMC3] = 0x8}, + }, +}; + +static struct pmu_type *pmu; + +extern void l2x0_evtbus_enable(void); +extern void l2x0_evtbus_disable(void); + +/*! + * function is used to write the EVTMON counter configuration register. + */ +int l2em_configure_counter(int nr, int type) +{ + /* Configure the counter event source */ + __raw_writel(((type << 2) & 0x7c), L2EM_CC(nr)); + if (type) + __raw_writel((__raw_readl(L2EM_CC(nr)) | EM_SET_INT), + L2EM_CC(nr)); + + return 0; +} + +/*! + * function is used to write the EVTMON counters + */ +void write_l2counter(int nr, u32 val) +{ + __raw_writel(val, L2EM_CNT(nr)); +} + +/*! + * function is used to check the status of the ARM11 evtmon counters + */ +int arm11_evtmon_setup_ctrs(void) +{ + int i; + + for (i = EMC0; i < MAX_L2COUNTERS; i++) { + if (counter_config[i].enabled) + continue; + counter_config[i].event = EVT_UNUSED; + } + + for (i = EMC0; i < MAX_L2COUNTERS; i++) { + if (counter_config[i].event == EVT_UNUSED) { + counter_config[i].event = 0; + pmu->int_enable &= ~pmu->int_mask[i]; + pr_debug + ("arm11_setup_ctrs: The counter event is %lu for counter%d\n", + counter_config[i].event, i); + continue; + } + + results[i].reset_counter = counter_config[i].count; + write_l2counter(i - EMC0, -(u32) counter_config[i].count); + l2em_configure_counter(i - EMC0, counter_config[i].event); + + pmu->int_enable |= pmu->int_mask[i]; + pr_debug + ("arm11_setup_ctrs: The values of int mask and enables are %x, %x\n", + pmu->int_mask[i], pmu->int_enable); + + } + + return 0; +} + +/*! + * function used to start the ARM11 evtmon counters + */ +void arm11_evtmon_stop(void) +{ + + /* Disable the EVTMON */ + __raw_writel((__raw_readl(L2EM_CTRL) & ~EVTMON_ENABLE), L2EM_CTRL); + + /* Disable the EVTBUS */ + l2x0_evtbus_disable(); + +} + +/*! + * function used to start the ARM11 evtmon counters + */ +int arm11_evtmon_start(void) +{ + + /* Enable the EVTBUS */ + l2x0_evtbus_enable(); + +#ifdef ECT_WORKAROUND + __raw_writel(ENABLE_CTI_CLOCK, CLKCTL_SET_CTRL); + /* Unlock the AHB Interface */ + __raw_writel(UNLOCK_ECT_CODE, ECT_CTI_LOCK); + /* Trigger to Channel Mapping */ + __raw_writel(ECT_CTI_CHAN_2, ECT_CTI_INEN(ECT_CTI_TRIGIN_1)); + /* Channel to triggers mapping */ + __raw_writel(ECT_CTI_CHAN_2, ECT_CTI_OUTEN(ECT_CTI_TRIGOUT_2)); + /* Trigger to Channel Mapping */ + __raw_writel(ECT_CTI_CHAN_3, ECT_CTI_INEN(ECT_CTI_TRIGIN_7)); + /* Channel to triggers mapping */ + __raw_writel(ECT_CTI_CHAN_3, ECT_CTI_OUTEN(ECT_CTI_TRIGOUT_6)); + /* Enable CTI Logic */ + __raw_writel(ENABLE_ECT, ECT_CTI_CONTROL); +#endif + + /* Enable EVTMON with Edge triggered interrupt of one Clock Cycle */ + __raw_writel((__raw_readl(L2EM_CTRL) | + (L2EM_INT_EDGE | L2EM_INT_CLK_CYCLES)), L2EM_CTRL); + __raw_writel((__raw_readl(L2EM_CTRL) | L2EM_ENABLE_MASK), L2EM_CTRL); + + return 0; +} + +/*! + * function detect the ARM11 evtmon counters + */ +int arm11_evtmon_detect(void) +{ + int ret = 0; + + pmu = &pmu_parms[0]; + op_armv6_spec.num_counters = MAX_L2COUNTERS; + + return ret; +} diff --git a/arch/arm/oprofile/op_model_v6.c b/arch/arm/oprofile/op_model_v6.c index fe581383d3e2..a8bf2ab6cd0d 100644 --- a/arch/arm/oprofile/op_model_v6.c +++ b/arch/arm/oprofile/op_model_v6.c @@ -28,16 +28,21 @@ #include "op_counter.h" #include "op_arm_model.h" #include "op_model_arm11_core.h" +#include "evtmon_regs.h" static int irqs[] = { #ifdef CONFIG_ARCH_OMAP2 3, #endif +#ifdef CONFIG_ARCH_MXC + EVENT_OVERFLOW_INT, +#endif }; static void armv6_pmu_stop(void) { arm11_stop_pmu(); + arm11_evtmon_stop(); arm11_release_interrupts(irqs, ARRAY_SIZE(irqs)); } @@ -46,21 +51,31 @@ static int armv6_pmu_start(void) int ret; ret = arm11_request_interrupts(irqs, ARRAY_SIZE(irqs)); - if (ret >= 0) + if (ret >= 0){ ret = arm11_start_pmu(); + arm11_evtmon_start(); + } return ret; } +static int armv6_setup_pmu(void) +{ + arm11_setup_pmu(); + arm11_evtmon_setup_ctrs(); + return 0; +} + static int armv6_detect_pmu(void) { + arm11_evtmon_detect(); return 0; } struct op_arm_model_spec op_armv6_spec = { .init = armv6_detect_pmu, .num_counters = 3, - .setup_ctrs = arm11_setup_pmu, + .setup_ctrs = armv6_setup_pmu, .start = armv6_pmu_start, .stop = armv6_pmu_stop, .name = "arm/armv6", diff --git a/arch/arm/oprofile/op_model_v7.c b/arch/arm/oprofile/op_model_v7.c index f20295f14adb..e3bf4fa07e10 100644 --- a/arch/arm/oprofile/op_model_v7.c +++ b/arch/arm/oprofile/op_model_v7.c @@ -371,6 +371,9 @@ static int irqs[] = { #ifdef CONFIG_ARCH_OMAP3 INT_34XX_BENCH_MPU_EMUL, #endif +#ifdef CONFIG_ARCH_MXC + MXC_INT_PMU, +#endif }; static void armv7_pmnc_stop(void) diff --git a/arch/arm/plat-mxc/Kconfig b/arch/arm/plat-mxc/Kconfig index 8986b7412235..e09407b66a5e 100644 --- a/arch/arm/plat-mxc/Kconfig +++ b/arch/arm/plat-mxc/Kconfig @@ -22,9 +22,30 @@ config ARCH_MX2 config ARCH_MX3 bool "MX3-based" select CPU_V6 - select COMMON_CLKDEV +# select COMMON_CLKDEV + help + This enables support for systems based on the Freescale i.MX31 and i.MX32 + +config ARCH_MX25 + bool "MX25-based" + select MX25_OPTIONS + help + This enables support for systems based on the Freescale i.MX25 + +config ARCH_MX35 + bool "MX35-based" + help + This enables support for systems based on Freescale i.MX35 + +config ARCH_MX37 + bool "MX37-based" help - This enables support for systems based on the Freescale i.MX3 family + This enables support for systems based on Freescale i.MX37 + +config ARCH_MX51 + bool "MX51-based" + help + This enables support for systems based on Freescale i.MX51 endchoice @@ -32,8 +53,106 @@ source "arch/arm/mach-mx1/Kconfig" source "arch/arm/mach-mx2/Kconfig" source "arch/arm/mach-mx3/Kconfig" +source "arch/arm/mach-mx25/Kconfig" +source "arch/arm/mach-mx35/Kconfig" +source "arch/arm/mach-mx37/Kconfig" +source "arch/arm/mach-mx51/Kconfig" + endmenu +config MXC_TZIC + bool + depends on ARCH_MXC + +config MXC_DSP_BRINGUP + bool + depends on ARCH_MXC + +config ARCH_HAS_EVTMON + bool + depends on ARCH_MXC + +config MXC_EMMA + bool + depends on ARCH_MXC + +config MXC_FB_IRAM + bool + depends on ARCH_MXC + +config DMA_ZONE_SIZE + int "DMA memory zone size" + range 0 64 + default 24 + help + This is the size in MB for the DMA zone. The DMA zone is used for + dedicated memory for large contiguous video buffers + +# set iff we need the 1504 transceiver code +config ISP1504_MXC + bool + select ISP1504_MXC_OTG if USB_GADGET && USB_EHCI_HCD && USB_OTG + default y if USB_EHCI_FSL_1504 || USB_GADGET_FSL_1504 + +config ISP1504_MXC_OTG + tristate + help + Support for USB OTG pin detect using the ISP1504 transceiver on MXC platforms. + +# set iff we need the UTMI transceiver code +config UTMI_MXC + bool + select UTMI_MXC_OTG if ARCH_MX25 && USB_GADGET && USB_EHCI_HCD && USB_OTG + default y if USB_EHCI_FSL_UTMI || USB_GADGET_FSL_UTMI + depends on ARCH_MX25 || ARCH_MX35 || ARCH_MX37 || ARCH_MX51 + +config UTMI_MXC_OTG + tristate + help + Support for USB OTG pin detect using the UTMI transceiver on MXC platforms. + +# set iff we need the 1301 transceiver code +config ISP1301_MXC + bool + default y if USB_EHCI_FSL_1301 || USB_GADGET_FSL_1301 + select I2C_MXC + +# set iff we need the mx13783 transceiver code +config MC13783_MXC + bool + default y if USB_EHCI_FSL_MC13783 || USB_GADGET_FSL_MC13783 + select SPI_MXC + +choice + prompt "Select serial USB transceiver mode" + depends on ISP1301_MXC || MC13783_MXC + default MXC_USB_SU6 + +config MXC_USB_SU6 + bool "Single Ended Unidirectional Mode" + help + If you say yes to this option, the serial tranceiver operates in SU6 mode. + This option will work for either the Freescale MC13783 or Philips ISP1301 + transceiver. + +config MXC_USB_SB3 + bool "Single Ended Bidirectional Mode" + help + If you say yes to this option, the serial tranceiver operates in SB3 mode. + Not recommended for the Freescale MC13783. + +config MXC_USB_DU6 + bool "Differential Unidirectional Mode" + help + If you say yes to this option, the serial tranceiver operates in DU6 mode. + +config MXC_USB_DB4 + bool "Differential Bidirectional Mode" + help + If you say yes to this option, the serial tranceiver operates in DB4 mode. + +endchoice + config MXC_IRQ_PRIOR bool "Use IRQ priority" depends on ARCH_MXC @@ -56,6 +175,10 @@ config ARCH_HAS_RNGA bool depends on ARCH_MXC +config ARCH_HAS_RNGC + bool + depends on ARCH_MXC + config ARCH_MXC_IOMUX_V3 bool endif diff --git a/arch/arm/plat-mxc/Makefile b/arch/arm/plat-mxc/Makefile index e3212c8ff421..56da7aa5d278 100644 --- a/arch/arm/plat-mxc/Makefile +++ b/arch/arm/plat-mxc/Makefile @@ -3,9 +3,59 @@ # # Common support -obj-y := irq.o clock.o gpio.o time.o devices.o cpu.o system.o +obj-y := cpu.o cpu_common.o gpio.o clock.o wdog.o snoop.o io.o time.o obj-$(CONFIG_ARCH_MX1) += iomux-mx1-mx2.o dma-mx1-mx2.o obj-$(CONFIG_ARCH_MX2) += iomux-mx1-mx2.o dma-mx1-mx2.o obj-$(CONFIG_ARCH_MXC_IOMUX_V3) += iomux-v3.o obj-$(CONFIG_MXC_PWM) += pwm.o + +ifneq ($(CONFIG_ARCH_MX27),y) +obj-y += spba.o sdma/ +endif + +ifeq ($(CONFIG_MXC_TZIC),y) +obj-y += tzic.o +else +obj-y += irq.o +endif + +obj-$(CONFIG_ARCH_MX27) += dma_mx2.o usb_common.o +obj-$(CONFIG_ARCH_MX3) += dptc.o usb_common.o entry-pm.o +obj-$(CONFIG_ARCH_MX35) += usb_common.o serialxc.o +obj-$(CONFIG_ARCH_MX37) += usb_common.o utmixc.o dptc.o dvfs_core.o +obj-$(CONFIG_ARCH_MX51) += usb_common.o utmixc.o dvfs_core.o + +# LEDs support +obj-$(CONFIG_LEDS) += leds.o + +# CPU FREQ support +obj-$(CONFIG_CPU_FREQ_IMX) += cpufreq.o + +# USB support +obj-$(CONFIG_ISP1504_MXC) += isp1504xc.o +obj-$(CONFIG_ISP1301_MXC) += isp1301xc.o +obj-$(CONFIG_MC13783_MXC) += mc13783_xc.o + +# obj-$(CONFIG_USB_EHCI_FSL_UTMI) += utmixc.o +ifneq ($(strip $(CONFIG_USB_EHCI_FSL_UTMI) $(CONFIG_USB_GADGET_FSL_UTMI)),) +obj-y += utmixc.o +endif + +ifneq ($(CONFIG_USB_EHCI_ARC_H1)$(CONFIG_USB_EHCI_ARC_H2),) +ifneq ($(CONFIG_ARCH_MX51),y) +obj-y += serialxc.o +else +obj-y += isp1504xc.o +endif +endif + +ifneq ($(CONFIG_ARCH_MX25)$(CONFIG_USB),) +obj-y += usb_common.o +endif + +ifeq ($(CONFIG_ARCH_MX25),y) +ifneq ($(CONFIG_USB_EHCI_ARC_H2),) +obj-y += serialxc.o +endif +endif diff --git a/arch/arm/plat-mxc/clock.c b/arch/arm/plat-mxc/clock.c index 92e13566cd4f..4ba52f106bfb 100644 --- a/arch/arm/plat-mxc/clock.c +++ b/arch/arm/plat-mxc/clock.c @@ -4,7 +4,7 @@ * Copyright (C) 2004 - 2005 Nokia corporation * Written by Tuukka Tikkanen <tuukka.tikkanen@elektrobit.com> * Modified for omap shared clock framework by Tony Lindgren <tony@atomide.com> - * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright 2008 Juergen Beisert, kernel@pengutronix.de * * This program is free software; you can redistribute it and/or @@ -25,6 +25,7 @@ /* #define DEBUG */ #include <linux/clk.h> +#include <linux/cpufreq.h> #include <linux/err.h> #include <linux/errno.h> #include <linux/init.h> @@ -35,13 +36,27 @@ #include <linux/mutex.h> #include <linux/platform_device.h> #include <linux/proc_fs.h> +#include <linux/seq_file.h> #include <linux/semaphore.h> #include <linux/string.h> #include <mach/clock.h> +#if (defined(CONFIG_ARCH_MX51) || defined(CONFIG_ARCH_MX37)) +extern int dvfs_core_is_active; +extern int lp_high_freq; +extern int lp_med_freq; +extern void dvfs_core_set_bus_freq(void); +#else +int dvfs_core_is_active; +void dvfs_core_set_bus_freq(void) +{ +}; +#endif + static LIST_HEAD(clocks); static DEFINE_MUTEX(clocks_mutex); +static DEFINE_SPINLOCK(clockfw_lock); /*------------------------------------------------------------------------- * Standard clock functions defined in include/linux/clk.h @@ -119,14 +134,16 @@ EXPORT_SYMBOL(clk_get); static void __clk_disable(struct clk *clk) { - if (clk == NULL || IS_ERR(clk)) + if (clk == NULL || IS_ERR(clk) || !clk->usecount) return; - __clk_disable(clk->parent); - __clk_disable(clk->secondary); + if (!(--clk->usecount)) { + if (clk->disable) + clk->disable(clk); - if (!(--clk->usecount) && clk->disable) - clk->disable(clk); + __clk_disable(clk->parent); + __clk_disable(clk->secondary); + } } static int __clk_enable(struct clk *clk) @@ -134,12 +151,13 @@ static int __clk_enable(struct clk *clk) if (clk == NULL || IS_ERR(clk)) return -EINVAL; - __clk_enable(clk->parent); - __clk_enable(clk->secondary); - - if (clk->usecount++ == 0 && clk->enable) - clk->enable(clk); + if (clk->usecount++ == 0) { + __clk_enable(clk->parent); + __clk_enable(clk->secondary); + if (clk->enable) + clk->enable(clk); + } return 0; } @@ -148,15 +166,36 @@ static int __clk_enable(struct clk *clk) */ int clk_enable(struct clk *clk) { + unsigned long flags; int ret = 0; if (clk == NULL || IS_ERR(clk)) return -EINVAL; - mutex_lock(&clocks_mutex); + spin_lock_irqsave(&clockfw_lock, flags); + ret = __clk_enable(clk); - mutex_unlock(&clocks_mutex); + spin_unlock_irqrestore(&clockfw_lock, flags); + + if ((clk->flags & CPU_FREQ_TRIG_UPDATE) + && (clk_get_usecount(clk) == 1)) { +#if defined(CONFIG_CPU_FREQ) + if (dvfs_core_is_active) + dvfs_core_set_bus_freq(); +#if (defined(CONFIG_ARCH_MX51) || defined(CONFIG_ARCH_MX37)) + else if ((lp_high_freq == 0 && lp_med_freq == 0) || + (lp_high_freq == 1) || + (lp_high_freq == 0 && lp_med_freq == 1)) +#else + else +#endif + cpufreq_update_policy(0); +#else + if (dvfs_core_is_active) + dvfs_core_set_bus_freq(); +#endif + } return ret; } EXPORT_SYMBOL(clk_enable); @@ -167,15 +206,56 @@ EXPORT_SYMBOL(clk_enable); */ void clk_disable(struct clk *clk) { + unsigned long flags; + if (clk == NULL || IS_ERR(clk)) return; - mutex_lock(&clocks_mutex); + spin_lock_irqsave(&clockfw_lock, flags); + __clk_disable(clk); - mutex_unlock(&clocks_mutex); + + spin_unlock_irqrestore(&clockfw_lock, flags); + + if ((clk->flags & CPU_FREQ_TRIG_UPDATE) + && (clk_get_usecount(clk) == 0)) { +#if defined(CONFIG_CPU_FREQ) + if (dvfs_core_is_active) + dvfs_core_set_bus_freq(); +#if (defined(CONFIG_ARCH_MX51) || defined(CONFIG_ARCH_MX37)) + else if (lp_high_freq == 0) +#else + else +#endif + cpufreq_update_policy(0); +#else + if (dvfs_core_is_active) + dvfs_core_set_bus_freq(); +#endif + } } + EXPORT_SYMBOL(clk_disable); +/*! + * @brief Function to get the usage count for the requested clock. + * + * This function returns the reference count for the clock. + * + * @param clk Handle to clock to disable. + * + * @return Returns the usage count for the requested clock. + */ +int clk_get_usecount(struct clk *clk) +{ + if (clk == NULL || IS_ERR(clk)) + return 0; + + return clk->usecount; +} + +EXPORT_SYMBOL(clk_get_usecount); + /* Retrieve the *current* clock rate. If the clock itself * does not provide a special calculation routine, ask * its parent and so on, until one is able to return @@ -186,10 +266,7 @@ unsigned long clk_get_rate(struct clk *clk) if (clk == NULL || IS_ERR(clk)) return 0UL; - if (clk->get_rate) - return clk->get_rate(clk); - - return clk_get_rate(clk->parent); + return clk->rate; } EXPORT_SYMBOL(clk_get_rate); @@ -216,19 +293,48 @@ long clk_round_rate(struct clk *clk, unsigned long rate) } EXPORT_SYMBOL(clk_round_rate); +/* Propagate rate to children */ +void propagate_rate(struct clk *tclk) +{ + struct clk *clkp; + + if (tclk == NULL || IS_ERR(tclk)) + return; + + pr_debug("mxc clock: finding children of %s-%d\n", tclk->name, + tclk->id); + list_for_each_entry(clkp, &clocks, node) { + if (likely(clkp->parent != tclk)) + continue; + pr_debug("mxc clock: %s-%d: recalculating rate: old = %lu, ", + clkp->name, clkp->id, clkp->rate); + if (likely((u32) clkp->recalc)) + clkp->recalc(clkp); + else + clkp->rate = tclk->rate; + pr_debug("new = %lu\n", clkp->rate); + propagate_rate(clkp); + } +} + /* Set the clock to the requested clock rate. The rate must * match a supported rate exactly based on what clk_round_rate returns */ int clk_set_rate(struct clk *clk, unsigned long rate) { + unsigned long flags; int ret = -EINVAL; if (clk == NULL || IS_ERR(clk) || clk->set_rate == NULL || rate == 0) return ret; - mutex_lock(&clocks_mutex); + spin_lock_irqsave(&clockfw_lock, flags); + ret = clk->set_rate(clk, rate); - mutex_unlock(&clocks_mutex); + if (unlikely((ret == 0) && (clk->flags & RATE_PROPAGATES))) + propagate_rate(clk); + + spin_unlock_irqrestore(&clockfw_lock, flags); return ret; } @@ -237,17 +343,35 @@ EXPORT_SYMBOL(clk_set_rate); /* Set the clock's parent to another clock source */ int clk_set_parent(struct clk *clk, struct clk *parent) { + unsigned long flags; int ret = -EINVAL; + struct clk *prev_parent = clk->parent; if (clk == NULL || IS_ERR(clk) || parent == NULL || IS_ERR(parent) || clk->set_parent == NULL) return ret; - mutex_lock(&clocks_mutex); + if (clk->usecount != 0) { + clk_enable(parent); + } + + spin_lock_irqsave(&clockfw_lock, flags); ret = clk->set_parent(clk, parent); - if (ret == 0) + if (ret == 0) { clk->parent = parent; - mutex_unlock(&clocks_mutex); + if (clk->recalc) { + clk->recalc(clk); + } else { + clk->rate = parent->rate; + } + if (unlikely(clk->flags & RATE_PROPAGATES)) + propagate_rate(clk); + } + spin_unlock_irqrestore(&clockfw_lock, flags); + + if (clk->usecount != 0) { + clk_disable(prev_parent); + } return ret; } @@ -295,43 +419,164 @@ void clk_unregister(struct clk *clk) EXPORT_SYMBOL(clk_unregister); #ifdef CONFIG_PROC_FS -static int mxc_clock_read_proc(char *page, char **start, off_t off, - int count, int *eof, void *data) + +static void *mxc_proc_clocks_seq_start(struct seq_file *file, loff_t *index) { - struct clk *clkp; - char *p = page; - int len; + unsigned int i; + unsigned int name_length; + unsigned int longest_length = 0; + struct clk *current_clock = 0; + struct clk *clock; + + /* Examine the clock list. */ + + i = 0; + + list_for_each_entry(clock, &clocks, node) { + if (i++ == *index) + current_clock = clock; + name_length = strlen(clock->name); + if (name_length > longest_length) + longest_length = name_length; + } - list_for_each_entry(clkp, &clocks, node) { - p += sprintf(p, "%s-%d:\t\t%lu, %d", clkp->name, clkp->id, - clk_get_rate(clkp), clkp->usecount); - if (clkp->parent) - p += sprintf(p, ", %s-%d\n", clkp->parent->name, - clkp->parent->id); - else - p += sprintf(p, "\n"); + /* Check if we found the indicated clock. */ + + if (!current_clock) + return NULL; + + /* Stash the length of the longest clock name for later use. */ + + file->private = (void *) longest_length; + + /* Return success. */ + + return current_clock; +} + +static void *mxc_proc_clocks_seq_next(struct seq_file *file, void *data, + loff_t *index) +{ + struct clk *current_clock = (struct clk *) data; + + /* Check for nonsense. */ + + if (!current_clock) + return NULL; + + /* Check if the current clock is the last. */ + + if (list_is_last(¤t_clock->node, &clocks)) + return NULL; + + /* Move to the next clock structure. */ + + current_clock = list_entry(current_clock->node.next, + typeof(*current_clock), node); + + (*index)++; + + /* Return the new current clock. */ + + return current_clock; + +} + +static void mxc_proc_clocks_seq_stop(struct seq_file *file, void *data) +{ +} + +static int mxc_proc_clocks_seq_show(struct seq_file *file, void *data) +{ + int result; + struct clk *clock = (struct clk *) data; + struct clk *parent = clock->parent; + unsigned int longest_length = (unsigned int) file->private; + unsigned long range_divisor; + const char *range_units; + + if (clock->rate >= 1000000) { + range_divisor = 1000000; + range_units = "MHz"; + } else if (clock->rate >= 1000) { + range_divisor = 1000; + range_units = "KHz"; + } else { + range_divisor = 1; + range_units = "Hz"; } - len = (p - page) - off; - if (len < 0) - len = 0; + if (parent) + result = seq_printf(file, + "%s-%-d%*s %s-%-d%*s %c%c%c%c%c%c %3d", + clock->name, + clock->id, + longest_length - strlen(clock->name), "", + parent->name, + parent->id, + longest_length - strlen(parent->name), "", + (clock->flags & RATE_PROPAGATES) ? 'P' : '_', + (clock->flags & ALWAYS_ENABLED) ? 'A' : '_', + (clock->flags & RATE_FIXED) ? 'F' : '_', + (clock->flags & CPU_FREQ_TRIG_UPDATE) ? 'T' : '_', + (clock->flags & AHB_HIGH_SET_POINT) ? 'H' : '_', + (clock->flags & AHB_MED_SET_POINT) ? 'M' : '_', + clock->usecount); + else + result = seq_printf(file, + "%s-%-d%*s %*s %c%c%c%c%c%c %3d", + clock->name, + clock->id, + longest_length - strlen(clock->name), "", + longest_length + 2, "", + (clock->flags & RATE_PROPAGATES) ? 'P' : '_', + (clock->flags & ALWAYS_ENABLED) ? 'A' : '_', + (clock->flags & RATE_FIXED) ? 'F' : '_', + (clock->flags & CPU_FREQ_TRIG_UPDATE) ? 'T' : '_', + (clock->flags & AHB_HIGH_SET_POINT) ? 'H' : '_', + (clock->flags & AHB_MED_SET_POINT) ? 'M' : '_', + clock->usecount); + + if (result) + return result; + + result = seq_printf(file, " %10lu (%lu%s)\n", + clock->rate, + clock->rate / range_divisor, range_units); + + return result; + +} - *eof = (len <= count) ? 1 : 0; - *start = page + off; +static const struct seq_operations mxc_proc_clocks_seq_ops = { + .start = mxc_proc_clocks_seq_start, + .next = mxc_proc_clocks_seq_next, + .stop = mxc_proc_clocks_seq_stop, + .show = mxc_proc_clocks_seq_show +}; - return len; +static int mxc_proc_clocks_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &mxc_proc_clocks_seq_ops); } +static const struct file_operations mxc_proc_clocks_ops = { + .open = mxc_proc_clocks_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + static int __init mxc_setup_proc_entry(void) { struct proc_dir_entry *res; - res = create_proc_read_entry("cpu/clocks", 0, NULL, - mxc_clock_read_proc, NULL); + res = create_proc_entry("cpu/clocks", 0, NULL); if (!res) { printk(KERN_ERR "Failed to create proc/cpu/clocks\n"); return -ENOMEM; } + res->proc_fops = &mxc_proc_clocks_ops; return 0; } diff --git a/arch/arm/plat-mxc/cpu_common.c b/arch/arm/plat-mxc/cpu_common.c new file mode 100644 index 000000000000..07e92e17289a --- /dev/null +++ b/arch/arm/plat-mxc/cpu_common.c @@ -0,0 +1,84 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <asm/setup.h> +#include <mach/common.h> +#include <mach/hardware.h> + +extern int mxc_early_serial_console_init(char *); + +/*! + * @file plat-mxc/cpu_common.c + * + * @brief This file contains the common CPU initialization code. + * + * @ingroup MSL_MX31 MSL_MXC91321 + */ + +static void __init system_rev_setup(char **p) +{ + system_rev = simple_strtoul(*p, NULL, 16); +} + +__early_param("system_rev=", system_rev_setup); + +int mxc_jtag_enabled; /* OFF: 0 (default), ON: 1 */ + +/* + * Here are the JTAG options from the command line. By default JTAG + * is OFF which means JTAG is not connected and WFI is enabled + * + * "on" -- JTAG is connected, so WFI is disabled + * "off" -- JTAG is disconnected, so WFI is enabled + */ + +static void __init jtag_wfi_setup(char **p) +{ + if (memcmp(*p, "on", 2) == 0) { + mxc_jtag_enabled = 1; + *p += 2; + } else if (memcmp(*p, "off", 3) == 0) { + mxc_jtag_enabled = 0; + *p += 3; + } +} + +__early_param("jtag=", jtag_wfi_setup); + +void __init mxc_cpu_common_init(void) +{ + mxc_set_cpu_type((((system_rev >> 20) & 0xF) * 10) + ((system_rev >> 16) & 0xF)); + pr_info("CPU is %s%x Revision %u.%u\n", + (mxc_cpu() < 0x100) ? "i.MX" : "MXC", + mxc_cpu(), mxc_cpu_rev_major(), mxc_cpu_rev_minor()); +} + +/** + * early_console_setup - setup debugging console + * + * Consoles started here require little enough setup that we can start using + * them very early in the boot process, either right after the machine + * vector initialization, or even before if the drivers can detect their hw. + * + * Returns non-zero if a console couldn't be setup. + * This function is developed based on + * early_console_setup function as defined in arch/ia64/kernel/setup.c + */ +void __init early_console_setup(char *cmdline) +{ +#ifdef CONFIG_SERIAL_MXC_CONSOLE + mxc_early_serial_console_init(cmdline); +#endif +} diff --git a/arch/arm/plat-mxc/cpufreq.c b/arch/arm/plat-mxc/cpufreq.c new file mode 100644 index 000000000000..bc3d1c97526c --- /dev/null +++ b/arch/arm/plat-mxc/cpufreq.c @@ -0,0 +1,347 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file cpufreq.c + * + * @brief A driver for the Freescale Semiconductor i.MXC CPUfreq module. + * + * The CPUFREQ driver is for controling CPU frequency. It allows you to change + * the CPU clock speed on the fly. + * + * @ingroup PM + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/cpufreq.h> +#include <linux/init.h> +#include <linux/proc_fs.h> +#include <linux/regulator/consumer.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <mach/hardware.h> +#include <asm/setup.h> +#include <mach/clock.h> +#include <asm/cacheflush.h> +#include <linux/hrtimer.h> + +int cpu_freq_khz_min; +int cpu_freq_khz_max; +int arm_lpm_clk; +int arm_normal_clk; +int cpufreq_suspended; + +static struct clk *cpu_clk; +static struct regulator *gp_regulator; +static struct cpu_wp *cpu_wp_tbl; +static struct cpufreq_frequency_table imx_freq_table[4]; +extern int low_bus_freq_mode; +extern int high_bus_freq_mode; +extern int dvfs_core_is_active; +extern int cpu_wp_nr; +extern char *gp_reg_id; + +extern int set_low_bus_freq(void); +extern int set_high_bus_freq(int high_bus_speed); +extern int low_freq_bus_used(void); + +#ifdef CONFIG_ARCH_MX51 +extern struct cpu_wp *(*get_cpu_wp)(int *wp); +#endif + +static int set_cpu_freq(int freq) +{ + int ret = 0; + int org_cpu_rate; + int gp_volt = 0; + int i; + + org_cpu_rate = clk_get_rate(cpu_clk); + + if (org_cpu_rate == freq) + return ret; + + for (i = 0; i < cpu_wp_nr; i++) { + if (freq == cpu_wp_tbl[i].cpu_rate) + gp_volt = cpu_wp_tbl[i].cpu_voltage; + } + + if (gp_volt == 0) + return ret; + + /*Set the voltage for the GP domain. */ + if (freq > org_cpu_rate) { + ret = regulator_set_voltage(gp_regulator, gp_volt, gp_volt); + if (ret < 0) { + printk(KERN_DEBUG "COULD NOT SET GP VOLTAGE!!!!\n"); + return ret; + } + } + + ret = clk_set_rate(cpu_clk, freq); + if (ret != 0) { + printk(KERN_DEBUG "cannot set CPU clock rate\n"); + return ret; + } + + if (freq < org_cpu_rate) { + ret = regulator_set_voltage(gp_regulator, gp_volt, gp_volt); + if (ret < 0) { + printk(KERN_DEBUG "COULD NOT SET GP VOLTAGE!!!!\n"); + return ret; + } + } + + return ret; +} + +static int mxc_verify_speed(struct cpufreq_policy *policy) +{ + if (policy->cpu != 0) + return -EINVAL; + + return cpufreq_frequency_table_verify(policy, imx_freq_table); +} + +static unsigned int mxc_get_speed(unsigned int cpu) +{ + if (cpu) + return 0; + + return clk_get_rate(cpu_clk) / 1000; +} + +static int calc_frequency_khz(int target, unsigned int relation) +{ + int i; + + if ((target * 1000) == clk_get_rate(cpu_clk)) + return target; + + if (relation == CPUFREQ_RELATION_H) { + for (i = cpu_wp_nr - 1; i >= 0; i--) { + if (imx_freq_table[i].frequency <= target) + return imx_freq_table[i].frequency; + } + } else if (relation == CPUFREQ_RELATION_L) { + for (i = 0; i < cpu_wp_nr; i++) { + if (imx_freq_table[i].frequency >= target) + return imx_freq_table[i].frequency; + } + } + printk(KERN_ERR "Error: No valid cpufreq relation\n"); + return cpu_freq_khz_max; +} + +static int mxc_set_target(struct cpufreq_policy *policy, + unsigned int target_freq, unsigned int relation) +{ + struct cpufreq_freqs freqs; + int freq_Hz; + int low_freq_bus_ready = 0; + int ret = 0; + + if (cpufreq_suspended) + return 0; + + if (dvfs_core_is_active) { + target_freq = clk_get_rate(cpu_clk) / 1000; + freq_Hz = calc_frequency_khz(target_freq, relation) * 1000; + if (freq_Hz == arm_lpm_clk) + freqs.old = cpu_wp_tbl[cpu_wp_nr - 2].cpu_rate / 1000; + else + freqs.old = arm_lpm_clk / 1000; + + freqs.new = freq_Hz / 1000; + freqs.cpu = 0; + freqs.flags = 0; + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + return ret; + } + /* + * Some governors do not respects CPU and policy lower limits + * which leads to bad things (division by zero etc), ensure + * that such things do not happen. + */ + if (target_freq < policy->cpuinfo.min_freq) + target_freq = policy->cpuinfo.min_freq; + + if (target_freq < policy->min) + target_freq = policy->min; + + freq_Hz = calc_frequency_khz(target_freq, relation) * 1000; + + freqs.old = clk_get_rate(cpu_clk) / 1000; + freqs.new = freq_Hz / 1000; + freqs.cpu = 0; + freqs.flags = 0; + low_freq_bus_ready = low_freq_bus_used(); + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + if (!dvfs_core_is_active) { + if ((freq_Hz == arm_lpm_clk) && (!low_bus_freq_mode) + && (low_freq_bus_ready)) { + if (freqs.old != freqs.new) + ret = set_cpu_freq(freq_Hz); + set_low_bus_freq(); + + } else { + set_high_bus_freq(0); + ret = set_cpu_freq(freq_Hz); + } + } + + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + return ret; +} + +static int __init mxc_cpufreq_driver_init(struct cpufreq_policy *policy) +{ + int ret; + int i; + + printk(KERN_INFO "i.MXC CPU frequency driver\n"); + + if (policy->cpu != 0) + return -EINVAL; + + cpu_clk = clk_get(NULL, "cpu_clk"); + if (IS_ERR(cpu_clk)) { + printk(KERN_ERR "%s: failed to get cpu clock\n", __func__); + return PTR_ERR(cpu_clk); + } + + gp_regulator = regulator_get(NULL, gp_reg_id); + if (IS_ERR(gp_regulator)) { + clk_put(cpu_clk); + printk(KERN_ERR "%s: failed to get gp regulator\n", __func__); + return PTR_ERR(gp_regulator); + } + + /* Set the current working point. */ + cpu_wp_tbl = get_cpu_wp(&cpu_wp_nr); + + cpu_freq_khz_min = cpu_wp_tbl[0].cpu_rate / 1000; + cpu_freq_khz_max = cpu_wp_tbl[0].cpu_rate / 1000; + + for (i = 0; i < cpu_wp_nr; i++) { + imx_freq_table[cpu_wp_nr - 1 - i].index = cpu_wp_nr - i; + imx_freq_table[cpu_wp_nr - 1 - i].frequency = + cpu_wp_tbl[i].cpu_rate / 1000; + + if ((cpu_wp_tbl[i].cpu_rate / 1000) < cpu_freq_khz_min) + cpu_freq_khz_min = cpu_wp_tbl[i].cpu_rate / 1000; + + if ((cpu_wp_tbl[i].cpu_rate / 1000) > cpu_freq_khz_max) + cpu_freq_khz_max = cpu_wp_tbl[i].cpu_rate / 1000; + } + + imx_freq_table[i].index = 0; + imx_freq_table[i].frequency = CPUFREQ_TABLE_END; + + policy->cur = clk_get_rate(cpu_clk) / 1000; + policy->governor = CPUFREQ_DEFAULT_GOVERNOR; + policy->min = policy->cpuinfo.min_freq = cpu_freq_khz_min; + policy->max = policy->cpuinfo.max_freq = cpu_freq_khz_max; + + arm_lpm_clk = cpu_freq_khz_min * 1000; + arm_normal_clk = cpu_freq_khz_max * 1000; + + /* Manual states, that PLL stabilizes in two CLK32 periods */ + policy->cpuinfo.transition_latency = 10; + + ret = cpufreq_frequency_table_cpuinfo(policy, imx_freq_table); + + if (ret < 0) { + clk_put(cpu_clk); + regulator_put(gp_regulator); + printk(KERN_ERR "%s: failed to register i.MXC CPUfreq\n", + __func__); + return ret; + } + + cpufreq_frequency_table_get_attr(imx_freq_table, policy->cpu); + return 0; +} + +static int mxc_cpufreq_suspend(struct cpufreq_policy *policy, + pm_message_t state) +{ + struct cpufreq_freqs freqs; + int ret = 0; + cpufreq_suspended = 1; + + freqs.old = clk_get_rate(cpu_clk) / 1000; + freqs.new = arm_normal_clk / 1000; + freqs.cpu = 0; + freqs.flags = 0; + + if (clk_get_rate(cpu_clk) != arm_normal_clk) { + set_high_bus_freq(1); + ret = set_cpu_freq(arm_normal_clk); + } + return ret; +} + +static int mxc_cpufreq_resume(struct cpufreq_policy *policy) +{ + cpufreq_suspended = 0; + return 0; +} + +static int mxc_cpufreq_driver_exit(struct cpufreq_policy *policy) +{ + cpufreq_frequency_table_put_attr(policy->cpu); + + /* Reset CPU to 665MHz */ + if (!dvfs_core_is_active) + set_cpu_freq(arm_normal_clk); + if (!high_bus_freq_mode) + set_high_bus_freq(1); + + clk_put(cpu_clk); + regulator_put(gp_regulator); + return 0; +} + +static struct cpufreq_driver mxc_driver = { + .flags = CPUFREQ_STICKY, + .verify = mxc_verify_speed, + .target = mxc_set_target, + .get = mxc_get_speed, + .init = mxc_cpufreq_driver_init, + .exit = mxc_cpufreq_driver_exit, + .suspend = mxc_cpufreq_suspend, + .resume = mxc_cpufreq_resume, + .name = "imx", +}; + +static int __devinit mxc_cpufreq_init(void) +{ + return cpufreq_register_driver(&mxc_driver); +} + +static void mxc_cpufreq_exit(void) +{ + cpufreq_unregister_driver(&mxc_driver); +} + +module_init(mxc_cpufreq_init); +module_exit(mxc_cpufreq_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("CPUfreq driver for i.MX"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-mxc/dma_mx2.c b/arch/arm/plat-mxc/dma_mx2.c new file mode 100644 index 000000000000..5a7e93b31d57 --- /dev/null +++ b/arch/arm/plat-mxc/dma_mx2.c @@ -0,0 +1,1316 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* Front-end to the DMA handling. This handles the allocation/freeing + * of DMA channels, and provides a unified interface to the machines + * DMA facilities. + */ + +/*! + * @file plat-mxc/dma_mx2.c + * @brief This file contains functions for DMA API + * + * @ingroup DMA_MX27 + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/mman.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/clk.h> + +#include <linux/proc_fs.h> +#include <linux/io.h> + +#include <mach/hardware.h> +#include <mach/dma.h> +#include <asm/delay.h> + +#include <asm/atomic.h> + +/* commented temperily for mx27 compilation +#define DMA_PM +*/ +#ifdef DMA_PM +#include <linux/pm.h> +#include <mach/apmc.h> +struct apmc_user *dma_apmc_user; +struct pm_dev *dma_pm; +#define DMA_PMST_RESUME 0 +#define DMA_PMST_STANDBY 1 +#define DMA_PMST_SUSPEND 2 +static unsigned int dma_pm_status = DMA_PMST_RESUME; +#endif + +/*! + * This variable is used to controll the clock of DMA. + * It counts the number of actived channels + */ +static atomic_t g_dma_actived = ATOMIC_INIT(0); + +/*! + * This variable point a proc file which contains the information + * of DMA channels + */ +static struct proc_dir_entry *g_proc_dir; + +/*! + * The dma channels + */ +static mxc_dma_channel_t g_dma_channels[MAX_DMA_CHANNELS]; +static mx2_dma_priv_t g_dma_privates[MXC_DMA_CHANNELS]; +static mx2_dma_bd_t g_dma_bd_table[MXC_DMA_CHANNELS][MAX_BD_SIZE]; + +static DEFINE_SPINLOCK(dma_list_lock); + +static struct clk *dma_clk; + +/*!@brief flush buffer descriptor ring*/ +#define flush_dma_bd(private) \ + { \ + atomic_set(&(private->bd_used), 0); \ + private->bd_rd = private->bd_wr;\ + } + +/*!@brief get next buffer discriptor */ +#define next_dma_bd(private) \ + ({ \ + int bd_next = (private->bd_rd+1)%MAX_BD_SIZE; \ + (bd_next == private->bd_wr) ? NULL: private->bd_ring+bd_next;\ + }) + +static inline int consume_dma_bd(mxc_dma_channel_t * dma, int error); +/*! + *@brief allocate a dma channel. + * + *@param idx Requested channel NO. + * @li MXC_INVLAID_CHANNEL System allocates a free channel which is not statically allocated. + * @li Others User requests a specific channel + *@return @li MXC_INVLAID_CHANNEL Failure + * @li Others Success + */ +static inline int get_dma_channel(int idx) +{ + int i; + mxc_dma_channel_t *p; + + if ((idx >= MAX_DMA_CHANNELS) && (idx != MXC_DMA_DYNAMIC_CHANNEL)) { + return -1; + } + if (idx != MXC_DMA_DYNAMIC_CHANNEL) { + p = g_dma_channels + idx; + BUG_ON(p->dynamic != 0); + if (xchg(&p->lock, 1) != 0) { + return -1; + } + return idx; + } + + p = g_dma_channels; + for (i = 0; (i < MAX_DMA_CHANNELS); i++, p++) { + if (p->dynamic && (xchg(&p->lock, 1) == 0)) { + return i; + } + } + return -1; +} + +/*! + *@brief release a dma channel. + * + *@param idx channel number + *@return none; + */ +static inline void put_dma_channel(int idx) +{ + mxc_dma_channel_t *p; + + if ((idx < MAX_DMA_CHANNELS) && (idx >= 0)) { + p = g_dma_channels + idx; + (void)xchg(&p->lock, 0); + } +} + +/*! + *@brief Get dma list for /proc/dma + */ +static int mxc_get_dma_list(char *buf) +{ + mxc_dma_channel_t *dma; + char *p = buf; + int i; + + for (i = 0, dma = g_dma_channels; i < MAX_DMA_CHANNELS; i++, dma++) { + if (dma->lock) { + p += sprintf(p, "dma channel %2d: %s\n", i, + dma->dev_name ? dma->dev_name : "unknown"); + } else { + p += sprintf(p, "dma channel %2d: unused\n", i); + } + } + + return p - buf; +} + +/*!@brief save the mask of dma interrupts*/ +#define save_dma_interrupt(flags) \ + flags = __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DIMR) + +/*!@brief restore the mask of dma interrupts*/ +#define restore_dma_interrupt(flags) \ + __raw_writel(flags, IO_ADDRESS(DMA_BASE_ADDR) + DMA_DIMR) + +/*!@brief disable interrupt of dma channel*/ +static inline void mask_dma_interrupt(int channel) +{ + unsigned long reg; + save_dma_interrupt(reg); + reg |= 1 << channel; /*mask interrupt; */ + restore_dma_interrupt(reg); +} + +/*!@brief enable interrupt of dma channel */ +static inline void unmask_dma_interrupt(int channel) +{ + unsigned long reg; + save_dma_interrupt(reg); + reg &= ~(1 << channel); /*unmask interrupt; */ + restore_dma_interrupt(reg); +} + +/*!@brief get interrupt event of dma channel */ +static unsigned long inline __get_dma_interrupt(int channel) +{ + unsigned long mode; + mode = 0; + if (__raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DISR) & (1 << channel)) + mode |= DMA_DONE; + + if (__raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DBTOSR) & + (1 << channel)) + mode |= DMA_BURST_TIMEOUT; + if (__raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DSESR) & (1 << channel)) + mode |= DMA_TRANSFER_ERROR; + + if (__raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DBOSR) & (1 << channel)) + mode |= DMA_BUFFER_OVERFLOW; + if (__raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DRTOSR) & + (1 << channel)) + mode |= DMA_REQUEST_TIMEOUT; + return mode; +} + +/*! + *@brief clean all event of dma interrupt and return the valid event. + */ +static unsigned long inline __clear_dma_interrupt(int channel) +{ + unsigned long mode; + mode = __get_dma_interrupt(channel); + __raw_writel(1 << channel, IO_ADDRESS(DMA_BASE_ADDR) + DMA_DISR); + __raw_writel(1 << channel, IO_ADDRESS(DMA_BASE_ADDR) + DMA_DBTOSR); + __raw_writel(1 << channel, IO_ADDRESS(DMA_BASE_ADDR) + DMA_DRTOSR); + __raw_writel(1 << channel, IO_ADDRESS(DMA_BASE_ADDR) + DMA_DSESR); + __raw_writel(1 << channel, IO_ADDRESS(DMA_BASE_ADDR) + DMA_DBOSR); + + return mode; +} + +/*!@brief This function enables dma clocks without lock */ +static void inline __enable_dma_clk(void) +{ + unsigned long reg; + clk_enable(dma_clk); + reg = __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DCR); + reg |= 0x1; + __raw_writel(reg, IO_ADDRESS(DMA_BASE_ADDR) + DMA_DCR); +} + +/*!@brief This function disables dma clocks without lock */ +static void inline __disable_dma_clk(void) +{ + unsigned long reg; + reg = __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DCR); + reg &= ~0x1; + __raw_writel(reg, IO_ADDRESS(DMA_BASE_ADDR) + DMA_DCR); + clk_disable(dma_clk); +} + +/*!@brief This function enables dma clocks with lock */ +static void inline enable_dma_clk(void) +{ + unsigned long flags; + spin_lock_irqsave(&dma_list_lock, flags); + if (atomic_read(&g_dma_actived) == 0) { + __enable_dma_clk(); + } + spin_unlock_irqrestore(&dma_list_lock, flags); + return; +} + +/*!@brief This function disables dma clocks without locked */ +static void inline disable_dma_clk(void) +{ + unsigned long flags; + spin_lock_irqsave(&dma_list_lock, flags); + if (atomic_read(&g_dma_actived) == 0) { + __disable_dma_clk(); + } + spin_unlock_irqrestore(&dma_list_lock, flags); + return; +} + +/*!@brief select a buffer to transfer and + * setup dma channel for current transfer + */ +static void setup_dmac(mxc_dma_channel_t * dma) +{ + mx2_dma_priv_t *priv = (mx2_dma_priv_t *) dma->private; + dma_regs_t *dma_base = (dma_regs_t *) (priv->dma_base); + mx2_dma_bd_t *p, *q; + unsigned long ctrl_val; + + if (dma->active == 0) { + printk(KERN_ERR + "dma channel %d is not enabled, when receiving this channel 's interrupt\n", + dma->channel); + return; + } + if (atomic_read(&(priv->bd_used)) <= 0) { + printk(KERN_ERR "dma channel %d is empty\n", dma->channel); + dma->active = 0; + atomic_dec(&g_dma_actived); + return; + } + /* BUSY: transfering + * PEND: Wait for set to DMAC. + * s1: no transfering: + * set first(one BUSY). if there are more than one tranfer. set second &repeat is enabled(two BUSY). + * + * s2: transfering & just on transfer + * one BUSY. set the tranesfer and set repeat bit(two BUSY) + * s3: transfering & repeat has set + * has two BUSY. + */ + p = priv->bd_ring + priv->bd_rd; + q = next_dma_bd(priv); + if (!(p->state & DMA_BD_ST_BUSY)) { + /*NOTICE:: This is first buffer or dma chain does not support chain-buffer. So CEN must clear & set again */ + ctrl_val = + __raw_readl(&(dma_base->Ctl)) & + (~(DMA_CTL_ACRPT | DMA_CTL_RPT | DMA_CTL_CEN)); + __raw_writel(ctrl_val, &(dma_base->Ctl)); + if (p->mode != dma->mode) { + dma->mode = p->mode; /* bi-dir channel do mode change */ + if (dma->mode == MXC_DMA_MODE_READ) { + DMA_CTL_SET_SMOD(ctrl_val, + priv->dma_info->sourceType); + DMA_CTL_SET_SSIZ(ctrl_val, + priv->dma_info->sourcePort); + DMA_CTL_SET_DMOD(ctrl_val, + priv->dma_info->destType); + DMA_CTL_SET_DSIZ(ctrl_val, + priv->dma_info->destPort); + } else { + DMA_CTL_SET_SMOD(ctrl_val, + priv->dma_info->destType); + DMA_CTL_SET_SSIZ(ctrl_val, + priv->dma_info->destPort); + DMA_CTL_SET_DMOD(ctrl_val, + priv->dma_info->sourceType); + DMA_CTL_SET_DSIZ(ctrl_val, + priv->dma_info->sourcePort); + } + } + __raw_writel(p->src_addr, &(dma_base->SourceAddr)); + __raw_writel(p->dst_addr, &(dma_base->DestAddr)); + __raw_writel(p->count, &(dma_base->Count)); + p->state |= DMA_BD_ST_BUSY; + p->state &= ~(DMA_BD_ST_PEND); + ctrl_val |= DMA_CTL_CEN; + __raw_writel(ctrl_val, &(dma_base->Ctl)); + if (q && priv->dma_chaining) { /*DO chain-buffer */ + __raw_writel(q->src_addr, &(dma_base->SourceAddr)); + __raw_writel(q->dst_addr, &(dma_base->DestAddr)); + __raw_writel(q->count, &(dma_base->Count)); + q->state |= DMA_BD_ST_BUSY; + q->state &= ~(DMA_BD_ST_PEND); + ctrl_val |= DMA_CTL_ACRPT | DMA_CTL_RPT | DMA_CTL_CEN; + __raw_writel(ctrl_val, &(dma_base->Ctl)); + } + } else { /* Just dma channel which supports dma buffer can run to there */ + BUG_ON(!priv->dma_chaining); + if (q) { /* p is tranfering, then q must be set into dma controller */ + /*WARNING:: [1] dangerous area begin. + * If the p is completed during MCU run in this erea, the dma channel is crashed. + */ + __raw_writel(q->src_addr, &(dma_base->SourceAddr)); + __raw_writel(q->dst_addr, &(dma_base->DestAddr)); + __raw_writel(q->count, &(dma_base->Count)); + /*WARNING:: [2] dangerous area end */ + ctrl_val = + __raw_readl(&(dma_base->Ctl)) | (DMA_CTL_ACRPT | + DMA_CTL_RPT | + DMA_CTL_CEN); + __raw_writel(ctrl_val, &(dma_base->Ctl)); + + /* WARNING:: This is workaround and it is dangerous: + * the judgement is not safety. + */ + if (!__get_dma_interrupt(dma->channel)) { + q->state |= DMA_BD_ST_BUSY; + q->state &= ~(DMA_BD_ST_PEND); + } else { + /*Waiting re-enable is in ISR */ + printk(KERN_ERR + "Warning:: The privous transfer is completed. Maybe the chain buffer is stopped."); + } + } else { /* Last buffer is transfering: just clear RPT bit */ + ctrl_val = + __raw_readl(&(dma_base->Ctl)) & + (~(DMA_CTL_ACRPT | DMA_CTL_RPT)); + __raw_writel(ctrl_val, &(dma_base->Ctl)); + } + } +} + +/*! + * @brief interrupt handler of dma channel + */ +static irqreturn_t dma_irq_handler(int irq, void *dev_id) +{ + mxc_dma_channel_t *dma = (mxc_dma_channel_t *) dev_id; + mx2_dma_priv_t *priv = (mx2_dma_priv_t *) (dma ? dma->private : NULL); + dma_regs_t *dma_base; + int state, error = MXC_DMA_DONE; + + BUG_ON(priv == NULL); + + dma_base = (dma_regs_t *) priv->dma_base; + + state = __clear_dma_interrupt(dma->channel); + + priv->trans_bytes += dma_base->transferd; + if (state != DMA_DONE) { + if (state & DMA_REQUEST_TIMEOUT) { + error = MXC_DMA_REQUEST_TIMEOUT; + } else { + error = MXC_DMA_TRANSFER_ERROR; + } + } + if (consume_dma_bd(dma, error)) { + disable_dma_clk(); + if (dma->cb_fn) { + dma->cb_fn(dma->cb_args, error, priv->trans_bytes); + } + priv->trans_bytes = 0; + } else { + disable_dma_clk(); + } + return IRQ_HANDLED; +} + +/*! + *@brief Set DMA channel parameters + * + *@param dma Requested channel NO. + *@param dma_info Channel configuration + *@return @li 0 Success + * @li others Failure + */ +static int setup_dma_channel(mxc_dma_channel_t * dma, mx2_dma_info_t * dma_info) +{ + mx2_dma_priv_t *priv = (mx2_dma_priv_t *) (dma ? dma->private : NULL); + dma_regs_t *dma_base; + unsigned long reg; + + if (!dma_info || !priv) { + return -1; + } + + if (dma_info->sourceType > 3) { + return -1; + } + if (dma_info->destType > 3) { + return -1; + } + if (dma_info->destPort > 3) { + return -1; + } + if (dma_info->sourcePort > 3) { + return -1; + } + if (dma_info->M2D_Valid) { + /*add for second dma */ + if (dma_info->W < dma_info->X) { + return -1; + } + } + + priv->dma_chaining = dma_info->dma_chaining; + priv->ren = dma_info->ren; + + if (dma_info->sourceType != DMA_TYPE_FIFO + && dma_info->destType != DMA_TYPE_FIFO) { + if (dma_info->ren) { + printk(KERN_INFO + "Warning:request enable just affect source or destination port is FIFO !\n"); + priv->ren = 0; + } + } + + if (dma_info->M2D_Valid) { + if (dma_info->msel) { + __raw_writel(dma_info->W, + IO_ADDRESS(DMA_BASE_ADDR) + DMA_WSRB); + __raw_writel(dma_info->X, + IO_ADDRESS(DMA_BASE_ADDR) + DMA_XSRB); + __raw_writel(dma_info->Y, + IO_ADDRESS(DMA_BASE_ADDR) + DMA_YSRB); + + } else { + __raw_writel(dma_info->W, + IO_ADDRESS(DMA_BASE_ADDR) + DMA_WSRA); + __raw_writel(dma_info->X, + IO_ADDRESS(DMA_BASE_ADDR) + DMA_XSRA); + __raw_writel(dma_info->Y, + IO_ADDRESS(DMA_BASE_ADDR) + DMA_YSRA); + } + } + + dma_base = (dma_regs_t *) (priv->dma_base); + + __raw_writel(dma_info->burstLength, &(dma_base->BurstLength)); + __raw_writel(dma_info->request, &(dma_base->RequestSource)); + + if (dma_info->ren) { + reg = dma_info->busuntils & 0x1FFFF; + if (dma_info->rto_en) { + reg |= 0xE000; + } + __raw_writel(reg, &(dma_base->BusUtilt)); + } else { + __raw_writel(dma_info->busuntils, &(dma_base->BusUtilt)); + } + + reg = __raw_readl(&(dma_base->Ctl)) & (~(DMA_CTL_ACRPT | DMA_CTL_RPT)); + + if (dma_info->dir) { + reg |= DMA_CTL_MDIR; + } else { + reg &= ~DMA_CTL_MDIR; + } + + if (priv->ren) { + reg |= DMA_CTL_REN; + } else { + reg &= ~DMA_CTL_REN; + } + + if ((dma_info->M2D_Valid) && (dma_info->msel)) { + reg |= DMA_CTL_MSEL; + } else { + reg &= ~DMA_CTL_MSEL; + } + + if (dma_info->mode) { + DMA_CTL_SET_SMOD(reg, dma_info->destType); + DMA_CTL_SET_SSIZ(reg, dma_info->destPort); + DMA_CTL_SET_DMOD(reg, dma_info->sourceType); + DMA_CTL_SET_DSIZ(reg, dma_info->sourcePort); + } else { + DMA_CTL_SET_SMOD(reg, dma_info->sourceType); + DMA_CTL_SET_SSIZ(reg, dma_info->sourcePort); + DMA_CTL_SET_DMOD(reg, dma_info->destType); + DMA_CTL_SET_DSIZ(reg, dma_info->destPort); + } + + __raw_writel(reg, &(dma_base->Ctl)); + + __clear_dma_interrupt(dma->channel); + unmask_dma_interrupt(dma->channel); + + disable_dma_clk(); + return 0; +} + +/*!@brief setup interrupt and setup dma channel by dma parameter */ +static inline int __init_dma_channel(mxc_dma_channel_t * chan, + mx2_dma_info_t * dma_info) +{ + mx2_dma_priv_t *dma_private = (mx2_dma_priv_t *) chan->private; + dma_regs_t *dma_base; + int ret; + + mask_dma_interrupt(chan->channel); + ret = + request_irq(dma_private->dma_irq, dma_irq_handler, + IRQF_DISABLED | IRQF_SHARED, chan->dev_name, + (void *)chan); + if (ret) { + printk(KERN_ERR + "%s: unable to request IRQ %d for DMA channel\n", + chan->dev_name, dma_private->dma_irq); + return ret; + } + + enable_dma_clk(); + + dma_base = (dma_regs_t *) (dma_private->dma_base); + __raw_writel(0, &(dma_base->Ctl)); + + ret = 0; + if ((ret = setup_dma_channel(chan, dma_info))) { + free_irq(dma_private->dma_irq, (void *)chan); + } + disable_dma_clk(); + return 0; +} + +/*!@brief initialize buffer descriptor ring.*/ +static inline void init_dma_bd(mx2_dma_priv_t * private) +{ + int i; + mx2_dma_bd_t *pbd; + private->bd_rd = private->bd_wr = 0; + atomic_set(&(private->bd_used), 0); + for (i = 0, pbd = private->bd_ring; i < MAX_BD_SIZE; i++, pbd++) { + pbd->state = 0; + } +} + +/*!@brief add dma buffer into buffer descriptor ring */ +static inline int fill_dma_bd(mxc_dma_channel_t * dma, + mxc_dma_requestbuf_t * buf, int num, + mxc_dma_mode_t mode) +{ + int i, wr; + unsigned long flags, mask; + mx2_dma_priv_t *priv = dma->private; + mx2_dma_bd_t *p, *q; + + if ((atomic_read(&(priv->bd_used)) + num) > MAX_BD_SIZE) { + return -EBUSY; + } + + for (i = 0; i < num; i++) { + wr = priv->bd_wr; + p = priv->bd_ring + wr; + p->mode = mode; + p->count = buf[i].num_of_bytes; + p->src_addr = buf[i].src_addr; + p->dst_addr = buf[i].dst_addr; + if (i == num - 1) { + p->state = DMA_BD_ST_LAST | DMA_BD_ST_PEND; + } else { + p->state = DMA_BD_ST_PEND; + } + priv->bd_wr = (wr + 1) % MAX_BD_SIZE; + atomic_inc(&(priv->bd_used)); + + if (atomic_read(&(priv->bd_used)) != 2) + continue; + /* Disable interrupt of this channel */ + local_irq_save(flags); + local_irq_disable(); + save_dma_interrupt(mask); + mask_dma_interrupt(dma->channel); + local_irq_restore(flags); + /*TODO :: + * If channel is transfering and supports chain_buffer, + * when the new buffer is 2st buffer , repeat must be enabled + */ + if (priv->dma_chaining && dma->active) { + q = priv->bd_ring + priv->bd_rd; + if (q && (q->state & DMA_BD_ST_BUSY)) { + if (atomic_read(&(priv->bd_used)) == 2) { + setup_dmac(dma); + } + } + } + restore_dma_interrupt(mask); + } + return 0; +} + +/*!@brief add sg-list into buffer descriptor ring */ +static inline int fill_dma_bd_by_sg(mxc_dma_channel_t * dma, + struct scatterlist *sg, int num, + int real_bytes, mxc_dma_mode_t mode) +{ + int i, wr, total_bytes = real_bytes; + unsigned long flags, mask; + mx2_dma_priv_t *priv = dma->private; + mx2_dma_bd_t *p, *q; + if ((atomic_read(&(priv->bd_used)) + num) > MAX_BD_SIZE) { + return -EBUSY; + } + + for (i = 0; i < num && ((real_bytes <= 0) || (total_bytes > 0)); i++) { + wr = priv->bd_wr; + p = priv->bd_ring + wr; + p->mode = mode; + if (real_bytes > 0) { + if (sg[i].length >= total_bytes) { + p->count = total_bytes; + } else { + p->count = sg[i].length; + } + total_bytes -= p->count; + } else { + p->count = sg[i].length; + } + if (mode == MXC_DMA_MODE_READ) { + p->src_addr = priv->dma_info->per_address; + p->dst_addr = sg[i].dma_address; + } else { + p->dst_addr = priv->dma_info->per_address; + p->src_addr = sg[i].dma_address; + } + if ((i == num - 1) || ((real_bytes > 0) && (total_bytes == 0))) { + p->state = DMA_BD_ST_LAST | DMA_BD_ST_PEND; + } else { + p->state = DMA_BD_ST_PEND; + } + priv->bd_wr = (wr + 1) % MAX_BD_SIZE; + atomic_inc(&(priv->bd_used)); + + if (atomic_read(&(priv->bd_used)) != 2) + continue; + /* Disable interrupt of this channel */ + local_irq_save(flags); + local_irq_disable(); + save_dma_interrupt(mask); + mask_dma_interrupt(dma->channel); + local_irq_restore(flags); + /*TODO :: + * If channel is transfering and supports chain_buffer, + * when the new buffer is 2st buffer , repeat must be enabled + */ + if (priv->dma_chaining && dma->active) { + q = next_dma_bd(priv); + if (q && (q->state & DMA_BD_ST_BUSY)) { + if ((atomic_read(&(priv->bd_used))) == 2) { + setup_dmac(dma); + } + } + } + restore_dma_interrupt(mask); + } + return 0; +} + +/*!@brief select next buffer descripter to transfer. + * return 1: need call call-back function. 0: Not need call call-back. + * it just is called in ISR + */ +static inline int consume_dma_bd(mxc_dma_channel_t * dma, int error) +{ + mx2_dma_priv_t *priv = dma->private; + mx2_dma_bd_t *p; + int notify = 0; + if (priv == NULL) { + printk(KERN_ERR + "request dma channel %d which is not initialize completed.!\n", + dma->channel); + return 1; + } + if (error != MXC_DMA_DONE) { + for (p = priv->bd_ring + priv->bd_rd; + atomic_read(&(priv->bd_used)) > 0;) { + priv->bd_rd = (priv->bd_rd + 1) % MAX_BD_SIZE; + atomic_dec(&(priv->bd_used)); + if (p->state & DMA_BD_ST_LAST) { + p->state = 0; + break; + } + p->state = 0; + } + notify = 1; + } else { + p = priv->bd_ring + priv->bd_rd; + priv->bd_rd = (priv->bd_rd + 1) % MAX_BD_SIZE; + atomic_dec(&(priv->bd_used)); + notify = (p->state & DMA_BD_ST_LAST) == DMA_BD_ST_LAST; + } + if (atomic_read(&(priv->bd_used)) <= 0) { + dma->active = 0; + atomic_dec(&g_dma_actived); + } else { + setup_dmac(dma); + } + return notify; +} + +/*! + * This function is generally called by the driver at open time. + * The DMA driver would do any initialization steps that is required + * to get the channel ready for data transfer. + * + * @param channel_id a pre-defined id. The peripheral driver would specify + * the id associated with its peripheral. This would be + * used by the DMA driver to identify the peripheral + * requesting DMA and do the necessary setup on the + * channel associated with the particular peripheral. + * The DMA driver could use static or dynamic DMA channel + * allocation. + * @param dev_name module name or device name + * @return returns a negative number on error if request for a DMA channel did not + * succeed, returns the channel number to be used on success. + */ +int mxc_dma_request_ext(mxc_dma_device_t channel_id, char *dev_name, + struct dma_channel_info *info) +{ + mxc_dma_channel_t *dma; + mx2_dma_priv_t *dma_private = NULL; + mx2_dma_info_t *dma_info = mxc_dma_get_info(channel_id); + int index; + int ret; + + if (dma_info == NULL) { + return -EINVAL; + } + + if ((index = get_dma_channel(dma_info->dma_chan)) < 0) { + return -ENODEV; + } + + dma = g_dma_channels + index; + dma_private = (mx2_dma_priv_t *) dma->private; + if (dma_private == NULL) { + printk(KERN_ERR + "request dma channel %d which is not initialize completed.!\n", + index); + ret = -EFAULT; + goto exit; + } + + dma->active = 0; + dma_private->dma_info = NULL; + dma->cb_fn = NULL; + dma->cb_args = NULL; + dma->dev_name = dev_name; + dma->mode = dma_info->mode ? MXC_DMA_MODE_WRITE : MXC_DMA_MODE_READ; + init_dma_bd(dma_private); + + if (!(ret = __init_dma_channel(dma, dma_info))) { + dma_private->dma_info = dma_info; + return index; + } + exit: + put_dma_channel(index); + return ret; +} + +/*! + * This function is generally called by the driver at close time. The DMA + * driver would do any cleanup associated with this channel. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @return returns a negative number on error or 0 on success + */ +int mxc_dma_free(int channel_num) +{ + mxc_dma_channel_t *dma; + mx2_dma_priv_t *dma_private; + + if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) { + return -EINVAL; + } + + dma = g_dma_channels + channel_num; + dma_private = (mx2_dma_priv_t *) dma->private; + if (dma_private == NULL) { + printk(KERN_ERR + "Free dma %d which is not completed initialization \n", + channel_num); + return -EFAULT; + } + if (dma->lock) { + if (dma->active) { /*Channel is busy */ + mxc_dma_disable(channel_num); + } + + dma_private = (mx2_dma_priv_t *) dma->private; + + enable_dma_clk(); + mask_dma_interrupt(channel_num); + disable_dma_clk(); + + free_irq(dma_private->dma_irq, (void *)dma); + put_dma_channel(channel_num); + } + return 0; +} + +/*! + * This function would just configure the buffers specified by the user into + * dma channel. The caller must call mxc_dma_enable to start this transfer. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @param dma_buf an array of physical addresses to the user defined + * buffers. The caller must guarantee the dma_buf is + * available until the transfer is completed. + * @param num_buf number of buffers in the array + * @param mode specifies whether this is READ or WRITE operation + * @return This function returns a negative number on error if buffer could not be + * added with DMA for transfer. On Success, it returns 0 + */ +int mxc_dma_config(int channel_num, mxc_dma_requestbuf_t * dma_buf, int num_buf, + mxc_dma_mode_t mode) +{ + mxc_dma_channel_t *dma; + mx2_dma_priv_t *dma_private; + + if ((dma_buf == NULL) || (num_buf < 1)) { + return -EINVAL; + } + + if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) { + return -EINVAL; + } + + dma = g_dma_channels + channel_num; + dma_private = (mx2_dma_priv_t *) dma->private; + if (dma_private == NULL) { + printk(KERN_ERR + "config dma %d which is not completed initialization \n", + channel_num); + return -EFAULT; + } + + if (dma->lock == 0) { + return -ENODEV; + } + + /*TODO: dma chainning can not support on bi-dir channel */ + if (dma_private->dma_chaining && (dma->mode != mode)) { + return -EINVAL; + } + + /*TODO: fill dma buffer into driver . + * If driver is no enought buffer to save them , it will return -EBUSY + */ + if (fill_dma_bd(dma, dma_buf, num_buf, mode)) { + return -EBUSY; + } + + return 0; +} + +/*! + * This function would just configure the scatterlist specified by the + * user into dma channel. This is a slight variation of mxc_dma_config(), + * it is provided for the convenience of drivers that have a scatterlist + * passed into them. It is the calling driver's responsibility to have the + * correct physical address filled in the "dma_address" field of the + * scatterlist. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @param sg a scatterlist of buffers. The caller must guarantee + * the dma_buf is available until the transfer is + * completed. + * @param num_buf number of buffers in the array + * @param num_of_bytes total number of bytes to transfer. If set to 0, this + * would imply to use the length field of the scatterlist + * for each DMA transfer. Else it would calculate the size + * for each DMA transfer. + * @param mode specifies whether this is READ or WRITE operation + * @return This function returns a negative number on error if buffer could not + * be added with DMA for transfer. On Success, it returns 0 + */ +int mxc_dma_sg_config(int channel_num, struct scatterlist *sg, + int num_buf, int num_of_bytes, mxc_dma_mode_t mode) +{ + mxc_dma_channel_t *dma; + mx2_dma_priv_t *dma_private; + + if ((sg == NULL) || (num_buf < 1) || (num_of_bytes < 0)) { + return -EINVAL; + } + + if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) { + return -EINVAL; + } + + dma = g_dma_channels + channel_num; + dma_private = (mx2_dma_priv_t *) dma->private; + if (dma_private == NULL) { + printk(KERN_ERR + "config_sg dma %d which is not completed initialization \n", + channel_num); + return -EFAULT; + } + + if (dma->lock == 0) { + return -ENODEV; + } + + /*TODO: dma chainning can not support on bi-dir channel */ + if (dma_private->dma_chaining && (dma->mode != mode)) { + return -EINVAL; + } + + /*TODO: fill dma buffer into driver . + * If driver is no enought buffer to save them , it will return -EBUSY + */ + if (fill_dma_bd_by_sg(dma, sg, num_buf, num_of_bytes, mode)) { + return -EBUSY; + } + return 0; +} + +/*! + * This function is provided if the driver would like to set/change its + * callback function. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @param callback a callback function to provide notification on transfer + * completion, user could specify NULL if he does not wish + * to be notified + * @param arg an argument that gets passed in to the callback + * function, used by the user to do any driver specific + * operations. + * @return this function returns an error if the callback could not be set + * for the channel + */ +int mxc_dma_callback_set(int channel_num, mxc_dma_callback_t callback, + void *arg) +{ + mxc_dma_channel_t *dma; + + if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) { + return -EINVAL; + } + dma = g_dma_channels + channel_num; + + if (!dma->lock) { + return -ENODEV; + } + + if (dma->active) { + return -EBUSY; + } + dma->cb_fn = callback; + dma->cb_args = arg; + return 0; + +} + +/*! + * This stops the DMA channel and any ongoing transfers. Subsequent use of + * mxc_dma_enable() will restart the channel and restart the transfer. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @return returns a negative number on error or 0 on success + */ +int mxc_dma_disable(int channel_num) +{ + mxc_dma_channel_t *dma; + mx2_dma_priv_t *priv; + unsigned long ctrl_val; + + if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) { + return -EINVAL; + } + + dma = g_dma_channels + channel_num; + + if (dma->lock == 0) { + return -EINVAL; + } + + if (!dma->active) { + return -EINVAL; + } + + priv = (mx2_dma_priv_t *) dma->private; + if (priv == NULL) { + printk(KERN_ERR "disable a uncompleted dma channel %d\n", + channel_num); + return -EFAULT; + } + + dma->active = 0; + enable_dma_clk(); + + __clear_dma_interrupt(channel_num); + ctrl_val = + __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_CCR(channel_num)); + ctrl_val &= ~DMA_CTL_CEN; /* clear CEN bit */ + __raw_writel(ctrl_val, + IO_ADDRESS(DMA_BASE_ADDR) + DMA_CCR(channel_num)); + disable_dma_clk(); + atomic_dec(&g_dma_actived); + + /*TODO: Clear all request buffers */ + flush_dma_bd(priv); + return 0; +} + +/*! + * This starts DMA transfer. Or it restarts DMA on a stopped channel + * previously stopped with mxc_dma_disable(). + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @return returns a negative number on error or 0 on success + */ +int mxc_dma_enable(int channel_num) +{ + mxc_dma_channel_t *dma; + mx2_dma_priv_t *priv; + + if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) { + return -EINVAL; + } + + dma = g_dma_channels + channel_num; + + if (dma->lock == 0) { + return -EINVAL; + } + + priv = (mx2_dma_priv_t *) dma->private; + if (priv == NULL) { + printk(KERN_ERR "enable a uncompleted dma channel %d\n", + channel_num); + return -EFAULT; + } + + if (dma->active) { + return 0; + } + dma->active = 1; + priv->trans_bytes = 0; + + enable_dma_clk(); + + atomic_inc(&g_dma_actived); + __clear_dma_interrupt(channel_num); + + setup_dmac(dma); + disable_dma_clk(); + return 0; +} + +/*! +*@brief Dump DMA registers +* +*@param channel Requested channel NO. +*@return none +*/ + +void mxc_dump_dma_register(int channel) +{ + mxc_dma_channel_t *dma = &g_dma_channels[channel]; + mx2_dma_priv_t *priv = (mx2_dma_priv_t *) dma->private; + dma_regs_t *dma_base; + + printk(KERN_INFO "======== Dump dma channel %d \n", channel); + if ((unsigned)channel >= MXC_DMA_CHANNELS) { + printk(KERN_INFO "Channel number is invalid \n"); + return; + } + if (!dma->lock) { + printk(KERN_INFO "Channel is not allocated \n"); + return; + } + + printk(KERN_INFO "g_dma_actived = %d\n", atomic_read(&g_dma_actived)); + + enable_dma_clk(); + dma_base = (dma_regs_t *) (priv->dma_base); + printk(KERN_INFO "DMA COMMON REGISTER\n"); + printk(KERN_INFO "DMA CONTROL DMA_DCR: %08x\n", + __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DCR)); + printk(KERN_INFO "DMA Interrupt status DMA_DISR: %08x\n", + __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DISR)); + printk(KERN_INFO "DMA Interrupt Mask DMA_DIMR: %08x\n", + __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DIMR)); + printk(KERN_INFO "DMA Burst Time Out DMA_DBTOSR: %08x\n", + __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DBTOSR)); + printk(KERN_INFO "DMA request Time Out DMA_DRTOSR: %08x\n", + __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DRTOSR)); + printk(KERN_INFO "DMA Transfer Error DMA_DSESR: %08x\n", + __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DSESR)); + printk(KERN_INFO "DMA DMA_Overflow DMA_DBOSR: %08x\n", + __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DBOSR)); + printk(KERN_INFO "DMA Burst Time OutCtl DMA_BurstTOCtl: %08x\n", + __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_DBTOCR)); + + printk(KERN_INFO "DMA 2D X size: %08x\n", + __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_XSRA)); + printk(KERN_INFO "DMA 2D Y size: %08x\n", + __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_YSRA)); + printk(KERN_INFO "DMA 2D Z size: %08x\n", + __raw_readl(IO_ADDRESS(DMA_BASE_ADDR) + DMA_WSRA)); + + printk(KERN_INFO "DMA Chan %2d Sourc SourceAddr: %08x\n", channel, + __raw_readl(&(dma_base->SourceAddr))); + printk(KERN_INFO "DMA Chan %2d dest DestAddr: %08x\n", channel, + __raw_readl(&(dma_base->DestAddr))); + printk(KERN_INFO "DMA Chan %2d count Count: %08x\n", channel, + __raw_readl(&(dma_base->Count))); + printk(KERN_INFO "DMA Chan %2d Ctl Ctl: %08x\n", channel, + __raw_readl(&(dma_base->Ctl))); + printk(KERN_INFO "DMA Chan %2d request RequestSource: %08x\n", + channel, __raw_readl(&(dma_base->RequestSource))); + printk(KERN_INFO "DMA Chan %2d burstL BurstLength: %08x\n", channel, + __raw_readl(&(dma_base->BurstLength))); + printk(KERN_INFO "DMA Chan %2d requestTO ReqTimeout: %08x\n", channel, + __raw_readl(&(dma_base->ReqTimeout))); + printk(KERN_INFO "DMA Chan %2d BusUtilt BusUtilt: %08x\n", channel, + __raw_readl(&(dma_base->BusUtilt))); + + disable_dma_clk(); +} + +#ifdef DMA_PM + +static int channel_in_use(void) +{ + int i; + for (i = 0; i < MXC_DMA_CHANNELS; i++) { + if (dma_chan[i].lock) + return 1; + } + return 0; +} + +int mxc_dma_pm_standby(void) +{ + unsigned long reg; + if (dma_pm_status == DMA_PMST_STANDBY) + return 0; + + if (!channel_in_use()) { + /*Disable DMA */ + __disable_dma_clk(); + dma_pm_status = DMA_PMST_STANDBY; + return 0; + } + return -1; +} + +int mxc_dma_pm_resume(void) +{ + unsigned long reg; + if (dma_pm_status == DMA_PMST_RESUME) + return 0; + + /*Enable HCLK_DMA and DMA(ipg clock) */ + dma_pm_status = DMA_PMST_RESUME; + return 0; +} + +int mxc_dma_pm_suspend(void) +{ + unsigned long reg; + if (dma_pm_status == DMA_PMST_SUSPEND) + return 0; + + if (!channel_in_use()) { + /*Disable DMA */ + __disable_dma_clk(); + dma_pm_status = DMA_PMST_SUSPEND; + return 0; + } + return -1; +} + +int mxc_dma_pm_handler(struct pm_dev *dev, pm_request_t rqst, void *data) +{ + int ret = 0; + switch (rqst) { + /*APM doesn't send PM_STANDBY and PM_STANDBY_RESUME request now. */ + case PM_SUSPEND: + ret = dma_pm_suspend(); + break; + case PM_RESUME: + ret = dma_pm_resume(); + break; + } + return ret; +} + +#endif /*DMA_PM */ + +int __init mxc_dma_init(void) +{ + int i; + mxc_dma_channel_t *dma = g_dma_channels; + mx2_dma_priv_t *private = g_dma_privates; + + memset(dma, 0, sizeof(mxc_dma_channel_t) * MXC_DMA_CHANNELS); + for (i = 0; i < MXC_DMA_CHANNELS; i++, dma++, private++) { + dma->channel = i; + dma->private = private; + private->dma_base = + (unsigned int)(IO_ADDRESS(DMA_BASE_ADDR + DMA_CH_BASE(i))); + private->dma_irq = i + MXC_DMA_INTR_0; /*Dma channel interrupt number */ + private->bd_ring = &g_dma_bd_table[i][0]; + } + + mxc_dma_load_info(g_dma_channels); + + dma_clk = clk_get(NULL, "dma_clk"); + clk_enable(dma_clk); + + __raw_writel(0x2, IO_ADDRESS(DMA_BASE_ADDR) + DMA_DCR); /*reset DMA; */ + + disable_dma_clk(); + + /*use module init because create_proc after init_dma */ + g_proc_dir = create_proc_entry("dma", 0, NULL); + g_proc_dir->read_proc = (read_proc_t *) mxc_get_dma_list; + g_proc_dir->data = NULL; + +#ifdef DMA_PM + /* Register the device with power management. */ + dma_pm = pm_register(PM_DMA_DEV, PM_SYS_UNKNOWN, dma_pm_handler); +#endif + + return 0; +} + +arch_initcall(mxc_dma_init); + +EXPORT_SYMBOL(mxc_dma_request_ext); +EXPORT_SYMBOL(mxc_dma_free); +EXPORT_SYMBOL(mxc_dma_callback_set); +EXPORT_SYMBOL(mxc_dma_enable); +EXPORT_SYMBOL(mxc_dma_disable); +EXPORT_SYMBOL(mxc_dma_config); +EXPORT_SYMBOL(mxc_dma_sg_config); +EXPORT_SYMBOL(mxc_dump_dma_register); diff --git a/arch/arm/plat-mxc/dptc.c b/arch/arm/plat-mxc/dptc.c new file mode 100644 index 000000000000..6b7f5599909e --- /dev/null +++ b/arch/arm/plat-mxc/dptc.c @@ -0,0 +1,621 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dptc.c + * + * @brief Driver for the Freescale Semiconductor MXC DPTC module. + * + * The DPTC driver is designed to control the MXC DPTC hardware. + * hardware. Upon initialization, the DPTC driver initializes the DPTC hardware + * sets up driver nodes attaches to the DPTC interrupt and initializes internal + * data structures. When the DPTC interrupt occurs the driver checks the cause + * of the interrupt (lower frequency, increase frequency or emergency) and changes + * the CPU voltage according to translation table that is loaded into the driver. + * The driver read method is used to read the log buffer. + * Driver ioctls are used to change driver parameters and enable/disable the + * DVFS operation. + * + * @ingroup PM + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include <mach/clock.h> +#include <mach/gpio.h> +#include <mach/hardware.h> +#include <mach/mxc_dptc.h> + +/* + * Convenience conversion. + * Here atm, maybe there is somewhere better for this. + */ +#define mV_to_uV(mV) (mV * 1000) +#define uV_to_mV(uV) (uV / 1000) +#define V_to_uV(V) (mV_to_uV(V * 1000)) +#define uV_to_V(uV) (uV_to_mV(uV) / 1000) + +enum { + DPTC_PTVAI_NOCHANGE = 0x0, + DPTC_PTVAI_DECREASE, + DPTC_PTVAI_INCREASE, + DPTC_PTVAI_EMERG, +}; + +struct device *dev_data0; +struct device *dev_data1; + +/*! + * In case the MXC device has multiple DPTC modules, this structure is used to + * store information specific to each DPTC module. + */ +struct dptc_device { + /* DPTC delayed work */ + struct delayed_work dptc_work; + /* DPTC spinlock */ + spinlock_t lock; + /* DPTC regulator */ + struct regulator *dptc_reg; + /* DPTC clock */ + struct clk *dptc_clk; + /* DPTC is active flag */ + int dptc_is_active; + /* turbo mode active flag */ + int turbo_mode_active; + /* DPTC current working point */ + int curr_wp; + /* DPTC vai bits */ + u32 ptvai; + /* The interrupt number used by the DPTC device */ + int irq; + /* DPTC platform data pointer */ + struct mxc_dptc_data *dptc_platform_data; +}; + +static void update_dptc_wp(struct dptc_device *drv_data, u32 wp) +{ + struct mxc_dptc_data *dptc_data = drv_data->dptc_platform_data; + int voltage_uV; + int ret = 0; + + voltage_uV = dptc_data->dptc_wp_allfreq[wp].voltage * 1000; + + __raw_writel(dptc_data->dptc_wp_allfreq[wp].dcvr0, + dptc_data->dcvr0_reg_addr); + __raw_writel(dptc_data->dptc_wp_allfreq[wp].dcvr1, + dptc_data->dcvr0_reg_addr + 0x4); + __raw_writel(dptc_data->dptc_wp_allfreq[wp].dcvr2, + dptc_data->dcvr0_reg_addr + 0x8); + __raw_writel(dptc_data->dptc_wp_allfreq[wp].dcvr3, + dptc_data->dcvr0_reg_addr + 0xC); + + /* Set the voltage */ + ret = regulator_set_voltage(drv_data->dptc_reg, voltage_uV, voltage_uV); + if (ret < 0) + printk(KERN_DEBUG "COULD NOT SET VOLTAGE!!!!!\n"); + + pr_debug("dcvr0-3: 0x%x, 0x%x, 0x%x, 0x%x; vol: %d\n", + dptc_data->dptc_wp_allfreq[wp].dcvr0, + dptc_data->dptc_wp_allfreq[wp].dcvr1, + dptc_data->dptc_wp_allfreq[wp].dcvr2, + dptc_data->dptc_wp_allfreq[wp].dcvr3, + dptc_data->dptc_wp_allfreq[wp].voltage); +} + +static irqreturn_t dptc_irq(int irq, void *dev_id) +{ + struct device *dev = dev_id; + struct dptc_device *drv_data = dev->driver_data; + struct mxc_dptc_data *dptc_data = dev->platform_data; + u32 dptccr = __raw_readl(dptc_data->dptccr_reg_addr); + u32 gpc_cntr = __raw_readl(dptc_data->gpc_cntr_reg_addr); + + gpc_cntr = (gpc_cntr & dptc_data->dptccr); + + if (gpc_cntr) { + drv_data->ptvai = + (dptccr & dptc_data->vai_mask) >> dptc_data->vai_offset; + pr_debug("dptc_irq: vai = 0x%x (0x%x)!!!!!!!\n", + drv_data->ptvai, dptccr); + + /* disable DPTC and mask its interrupt */ + dptccr = (dptccr & ~(dptc_data->dptc_enable_bit)) | + (dptc_data->irq_mask); + dptccr = (dptccr & ~(dptc_data->dptc_nvcr_bit)); + __raw_writel(dptccr, dptc_data->dptccr_reg_addr); + + if (drv_data->turbo_mode_active == 1) + schedule_delayed_work(&drv_data->dptc_work, 0); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static void dptc_workqueue_handler(struct work_struct *work1) +{ + struct delayed_work *dptc_work_tmp = + container_of(work1, struct delayed_work, work); + struct dptc_device *drv_data = + container_of(dptc_work_tmp, struct dptc_device, dptc_work); + struct mxc_dptc_data *dptc_data = drv_data->dptc_platform_data; + u32 dptccr = __raw_readl(dptc_data->dptccr_reg_addr); + + switch (drv_data->ptvai) { + case DPTC_PTVAI_DECREASE: + drv_data->curr_wp++; + break; + case DPTC_PTVAI_INCREASE: + case DPTC_PTVAI_EMERG: + drv_data->curr_wp--; + if (drv_data->curr_wp < 0) { + /* already max voltage */ + drv_data->curr_wp = 0; + printk(KERN_WARNING "dptc: already maximum voltage\n"); + } + break; + + /* Unknown interrupt cause */ + default: + BUG(); + } + + if (drv_data->curr_wp > dptc_data->dptc_wp_supported + || drv_data->curr_wp < 0) { + panic("Can't support this working point: %d\n", + drv_data->curr_wp); + } + update_dptc_wp(drv_data, drv_data->curr_wp); + + /* Enable DPTC and unmask its interrupt */ + dptccr = (dptccr & ~(dptc_data->irq_mask)) | + dptc_data->dptc_nvcr_bit | dptc_data->dptc_enable_bit; + __raw_writel(dptccr, dptc_data->dptccr_reg_addr); +} + +/* Start DPTC unconditionally */ +static int start_dptc(struct device *dev) +{ + struct mxc_dptc_data *dptc_data = dev->platform_data; + struct dptc_device *drv_data = dev->driver_data; + u32 dptccr; + unsigned long flags; + unsigned long clk_rate; + int voltage_uV; + + /* Get the voltage */ + voltage_uV = regulator_get_voltage(drv_data->dptc_reg); + drv_data->curr_wp = + (dptc_data->dptc_wp_allfreq[0].voltage - (voltage_uV / 1000)) / 25; + + update_dptc_wp(drv_data, drv_data->curr_wp); + + /* Set the voltage */ + spin_lock_irqsave(&drv_data->lock, flags); + + clk_rate = clk_get_rate(drv_data->dptc_clk); + + if (clk_rate < dptc_data->clk_max_val) + goto err; + + if (dptc_data->gpc_irq_bit != 0x0) { + /* Enable ARM domain frequency and/or voltage update needed + and enable ARM IRQ */ + __raw_writel(dptc_data->gpc_irq_bit | dptc_data->gpc_adu, + dptc_data->gpc_cntr_reg_addr); + } + + dptccr = __raw_readl(dptc_data->dptccr_reg_addr); + + /* Enable DPTC and unmask its interrupt */ + dptccr = ((dptccr & ~(dptc_data->irq_mask)) | dptc_data->enable_config); + + __raw_writel(dptccr, dptc_data->dptccr_reg_addr); + + spin_unlock_irqrestore(&drv_data->lock, flags); + + drv_data->dptc_is_active = 1; + drv_data->turbo_mode_active = 1; + + pr_info("DPTC has been started \n"); + + return 0; + +err: + spin_unlock_irqrestore(&drv_data->lock, flags); + pr_info("DPTC is not enabled\n"); + return -1; +} + +/* Stop DPTC unconditionally */ +static void stop_dptc(struct device *dev) +{ + struct mxc_dptc_data *dptc_data = dev->platform_data; + struct dptc_device *drv_data = dev->driver_data; + u32 dptccr; + + dptccr = __raw_readl(dptc_data->dptccr_reg_addr); + + /* disable DPTC and mask its interrupt */ + dptccr = ((dptccr & ~(dptc_data->dptc_enable_bit)) | + dptc_data->irq_mask) & (~dptc_data->dptc_nvcr_bit); + + __raw_writel(dptccr, dptc_data->dptccr_reg_addr); + + /* Restore Turbo Mode voltage to highest wp */ + update_dptc_wp(drv_data, 0); + drv_data->curr_wp = 0; + + regulator_put(drv_data->dptc_reg); + + pr_info("DPTC has been stopped\n"); +} + +/* + This function does not change the working point. It can be + called from an interrupt context. +*/ +void dptc_suspend(int id) +{ + struct mxc_dptc_data *dptc_data; + struct dptc_device *drv_data; + u32 dptccr; + + switch (id) { + case DPTC_GP_ID: + dptc_data = dev_data0->platform_data; + drv_data = dev_data0->driver_data; + break; + case DPTC_LP_ID: + if (dev_data1 == NULL) + return; + + dptc_data = dev_data1->platform_data; + drv_data = dev_data1->driver_data; + break; + /* Unknown DPTC ID */ + default: + return; + } + + if (!drv_data->dptc_is_active) + return; + + dptccr = __raw_readl(dptc_data->dptccr_reg_addr); + + /* Disable DPTC and mask its interrupt */ + dptccr = (dptccr & ~(dptc_data->dptc_enable_bit)) | dptc_data->irq_mask; + + __raw_writel(dptccr, dptc_data->dptccr_reg_addr); +} +EXPORT_SYMBOL(dptc_suspend); + +/* + This function does not change the working point. It can be + called from an interrupt context. +*/ +void dptc_resume(int id) +{ + struct mxc_dptc_data *dptc_data; + struct dptc_device *drv_data; + u32 dptccr; + + switch (id) { + case DPTC_GP_ID: + dptc_data = dev_data0->platform_data; + drv_data = dev_data0->driver_data; + break; + case DPTC_LP_ID: + if (dev_data1 == NULL) + return; + + dptc_data = dev_data1->platform_data; + drv_data = dev_data1->driver_data; + break; + /* Unknown DPTC ID */ + default: + return; + } + + if (!drv_data->dptc_is_active) + return; + + __raw_writel(dptc_data->dptc_wp_allfreq[0].dcvr0, + dptc_data->dcvr0_reg_addr); + __raw_writel(dptc_data->dptc_wp_allfreq[0].dcvr1, + dptc_data->dcvr0_reg_addr + 0x4); + __raw_writel(dptc_data->dptc_wp_allfreq[0].dcvr2, + dptc_data->dcvr0_reg_addr + 0x8); + __raw_writel(dptc_data->dptc_wp_allfreq[0].dcvr3, + dptc_data->dcvr0_reg_addr + 0xC); + + dptccr = __raw_readl(dptc_data->dptccr_reg_addr); + + /* Enable DPTC and unmask its interrupt */ + dptccr = (dptccr & ~(dptc_data->irq_mask)) | dptc_data->dptc_enable_bit; + + __raw_writel(dptccr, dptc_data->dptccr_reg_addr); +} +EXPORT_SYMBOL(dptc_resume); + +/*! + * This function is called to put the DPTC in a low power state. + * + */ +void dptc_disable(struct device *dev) +{ + struct dptc_device *drv_data = dev->driver_data; + + if (!(drv_data->dptc_is_active)) + return; + + stop_dptc(dev); + drv_data->dptc_is_active = 0; + drv_data->turbo_mode_active = 0; +} + +/*! + * This function is called to resume the DPTC from a low power state. + * + */ +int dptc_enable(struct device *dev) +{ + struct dptc_device *drv_data = dev->driver_data; + + if (drv_data->dptc_is_active) + return 0; + + return start_dptc(dev); +} + +static ssize_t dptc_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dptc_device *drv_data = dev->driver_data; + + if (drv_data->dptc_is_active) + return sprintf(buf, "DPTC is enabled\n"); + else + return sprintf(buf, "DPTC is disabled\n"); +} + +static ssize_t dptc_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + if (strstr(buf, "0") != NULL) { + dptc_disable(dev); + } else if (strstr(buf, "1") != NULL) { + dptc_enable(dev); + } + + return size; +} + +static DEVICE_ATTR(enable, 0644, dptc_show, dptc_store); + +/*! + * This is the probe routine for the DPTC driver. + * + * @param pdev The platform device structure + * + * @return The function returns 0 on success + * + */ +static int __devinit mxc_dptc_probe(struct platform_device *pdev) +{ + struct dptc_device *dptc_device_data; + int ret = 0; + struct resource *res; + u32 dptccr = 0; + struct clk *ckih_clk; + struct mxc_dptc_data *dptc_data = pdev->dev.platform_data; + + if (dptc_data == NULL) { + printk(KERN_ERR "DPTC: Pointer to DPTC data is NULL\ + not started\n"); + return -1; + } + + dptc_device_data = kzalloc(sizeof(struct dptc_device), GFP_KERNEL); + if (!dptc_device_data) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + ret = -ENODEV; + goto err1; + } + + /* + * Request the DPTC interrupt + */ + dptc_device_data->irq = platform_get_irq(pdev, 0); + if (dptc_device_data->irq < 0) { + ret = dptc_device_data->irq; + goto err1; + } + + ret = + request_irq(dptc_device_data->irq, dptc_irq, IRQF_SHARED, + pdev->name, &pdev->dev); + if (ret) { + printk(KERN_ERR "DPTC: Unable to attach to DPTC interrupt\n"); + goto err1; + } + + dptc_device_data->curr_wp = 0; + dptc_device_data->dptc_is_active = 0; + dptc_device_data->turbo_mode_active = 0; + dptc_device_data->ptvai = 0; + + dptccr = __raw_readl(dptc_data->dptccr_reg_addr); + + printk(KERN_INFO "DPTC mxc_dptc_probe()\n"); + + spin_lock_init(&dptc_device_data->lock); + + if (dptc_data->dptc_wp_allfreq == NULL) { + ckih_clk = clk_get(NULL, "ckih"); + if (cpu_is_mx31() & + (mxc_cpu_is_rev(CHIP_REV_2_0) < 0) & + (clk_get_rate(ckih_clk) == 27000000)) + printk(KERN_ERR "DPTC: DPTC not supported on TO1.x \ + & ckih = 27M\n"); + else + printk(KERN_ERR "DPTC: Pointer to DPTC table is NULL\ + not started\n"); + goto err1; + } + + dptc_device_data->dptc_reg = regulator_get(NULL, dptc_data->reg_id); + if (IS_ERR(dptc_device_data->dptc_reg)) { + clk_put(dptc_device_data->dptc_clk); + printk(KERN_ERR "%s: failed to get regulator\n", __func__); + goto err1; + } + + INIT_DELAYED_WORK(&dptc_device_data->dptc_work, dptc_workqueue_handler); + + /* Enable Reference Circuits */ + dptccr = (dptccr & ~(dptc_data->dcr_mask)) | dptc_data->init_config; + __raw_writel(dptccr, dptc_data->dptccr_reg_addr); + + ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_enable.attr); + if (ret) { + printk(KERN_ERR + "DPTC: Unable to register sysdev entry for dptc"); + goto err1; + } + + if (ret != 0) { + printk(KERN_ERR "DPTC: Unable to start"); + goto err1; + } + + dptc_device_data->dptc_clk = clk_get(NULL, dptc_data->clk_id); + + if (pdev->id == 0) + dev_data0 = &pdev->dev; + else + dev_data1 = &pdev->dev; + + dptc_device_data->dptc_platform_data = pdev->dev.platform_data; + + /* Set driver data */ + platform_set_drvdata(pdev, dptc_device_data); + + return 0; + +err1: + dev_err(&pdev->dev, "Failed to probe DPTC\n"); + kfree(dptc_device_data); + return ret; +} + +/*! + * This function is called to put DPTC in a low power state. + * + * @param pdev the device structure + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxc_dptc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct dptc_device *drv_data = pdev->dev.driver_data; + + if (drv_data->dptc_is_active) + stop_dptc(&pdev->dev); + + return 0; +} + +/*! + * This function is called to resume the MU from a low power state. + * + * @param dev the device structure + * @param level the stage in device suspension process that we want the + * device to be put in + * + * @return The function always returns 0. + */ +static int mxc_dptc_resume(struct platform_device *pdev) +{ + struct dptc_device *drv_data = pdev->dev.driver_data; + + if (drv_data->dptc_is_active) + return start_dptc(&pdev->dev); + + return 0; +} + +static struct platform_driver mxc_dptc_driver = { + .driver = { + .name = "mxc_dptc", + .owner = THIS_MODULE, + }, + .probe = mxc_dptc_probe, + .suspend = mxc_dptc_suspend, + .resume = mxc_dptc_resume, +}; + +/*! + * This function is called to resume the MU from a low power state. + * + * @param dev the device structure used to give information on which MU + * device (0 through 3 channels) to suspend + * @param level the stage in device suspension process that we want the + * device to be put in + * + * @return The function always returns 0. + */ + +static int __init dptc_init(void) +{ + if (platform_driver_register(&mxc_dptc_driver) != 0) { + printk(KERN_ERR "mxc_dptc_driver register failed\n"); + return -ENODEV; + } + + printk(KERN_INFO "DPTC driver module loaded\n"); + + return 0; +} + +static void __exit dptc_cleanup(void) +{ + /* Unregister the device structure */ + platform_driver_unregister(&mxc_dptc_driver); + + printk("DPTC driver module unloaded\n"); +} + +module_init(dptc_init); +module_exit(dptc_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("DPTC driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-mxc/dvfs_core.c b/arch/arm/plat-mxc/dvfs_core.c new file mode 100644 index 000000000000..d14bac145344 --- /dev/null +++ b/arch/arm/plat-mxc/dvfs_core.c @@ -0,0 +1,802 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dvfs_core.c + * + * @brief A simplied driver for the Freescale Semiconductor MXC DVFS module. + * + * Upon initialization, the DVFS driver initializes the DVFS hardware + * sets up driver nodes attaches to the DVFS interrupt and initializes internal + * data structures. When the DVFS interrupt occurs the driver checks the cause + * of the interrupt (lower frequency, increase frequency or emergency) and + * changes the CPU voltage according to translation table that is loaded into + * the driver. + * + * @ingroup PM + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/device.h> +#include <linux/sysdev.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> +#include <linux/input.h> +#include <linux/platform_device.h> +#include <linux/cpufreq.h> +#include <mach/hardware.h> +#include <mach/mxc_dvfs.h> + +#define MXC_DVFSTHRS_UPTHR_MASK 0x0FC00000 +#define MXC_DVFSTHRS_UPTHR_OFFSET 22 +#define MXC_DVFSTHRS_DNTHR_MASK 0x003F0000 +#define MXC_DVFSTHRS_DNTHR_OFFSET 16 +#define MXC_DVFSTHRS_PNCTHR_MASK 0x0000003F +#define MXC_DVFSTHRS_PNCTHR_OFFSET 0 + +#define MXC_DVFSCOUN_DNCNT_MASK 0x00FF0000 +#define MXC_DVFSCOUN_DNCNT_OFFSET 16 +#define MXC_DVFSCOUN_UPCNT_MASK 0x000000FF +#define MXC_DVFSCOUN_UPCNT_OFFSET 0 + +#define MXC_DVFSEMAC_EMAC_MASK 0x000001FF +#define MXC_DVFSEMAC_EMAC_OFFSET 0 + +#define MXC_DVFSCNTR_DVFEV 0x10000000 +#define MXC_DVFSCNTR_LBMI 0x08000000 +#define MXC_DVFSCNTR_LBFL 0x06000000 +#define MXC_DVFSCNTR_DVFIS 0x01000000 +#define MXC_DVFSCNTR_FSVAIM 0x00400000 +#define MXC_DVFSCNTR_FSVAI_MASK 0x00300000 +#define MXC_DVFSCNTR_FSVAI_OFFSET 20 +#define MXC_DVFSCNTR_WFIM 0x00080000 +#define MXC_DVFSCNTR_WFIM_OFFSET 19 +#define MXC_DVFSCNTR_MAXF_MASK 0x00040000 +#define MXC_DVFSCNTR_MAXF_OFFSET 18 +#define MXC_DVFSCNTR_MINF_MASK 0x00020000 +#define MXC_DVFSCNTR_MINF_OFFSET 17 +#define MXC_DVFSCNTR_LTBRSR_MASK 0x00000018 +#define MXC_DVFSCNTR_LTBRSR_OFFSET 3 +#define MXC_DVFSCNTR_DVFEN 0x00000001 + +#define MXC_GPCCNTR_GPCIRQ 0x00100000 +#define MXC_GPCCNTR_DVFS0CR 0x00010000 +#define MXC_GPCCNTR_ADU 0x00008000 +#define MXC_GPCCNTR_STRT 0x00004000 +#define MXC_GPCCNTR_FUPD 0x00002000 +#define MXC_GPCCNTR_HTRI_MASK 0x0000000F +#define MXC_GPCCNTR_HTRI_OFFSET 0 +#define MXC_GPCCNTR_GPCIRQM 0x00200000 + +#define MXC_GPCVCR_VINC_MASK 0x00020000 +#define MXC_GPCVCR_VINC_OFFSET 17 +#define MXC_GPCVCR_VCNTU_MASK 0x00010000 +#define MXC_GPCVCR_VCNTU_OFFSET 16 +#define MXC_GPCVCR_VCNT_MASK 0x00007FFF +#define MXC_GPCVCR_VCNT_OFFSET 0 + +extern void setup_pll(void); +static struct delayed_work dvfs_core_work; +static struct mxc_dvfs_platform_data *dvfs_data; +static struct device *dvfs_dev; +static struct cpu_wp *cpu_wp_tbl; +int dvfs_core_resume; +int curr_wp; +int old_wp; +int dvfs_core_is_active; +int cpufreq_trig_needed; +struct timeval core_prev_intr; + +/* + * Clock structures + */ +static struct clk *pll1_sw_clk; +static struct clk *cpu_clk; +static struct clk *dvfs_clk; +static struct regulator *core_regulator; + +extern int cpu_wp_nr; +#ifdef CONFIG_ARCH_MX51 +extern struct cpu_wp *(*get_cpu_wp)(int *wp); +#endif + +enum { + FSVAI_FREQ_NOCHANGE = 0x0, + FSVAI_FREQ_INCREASE, + FSVAI_FREQ_DECREASE, + FSVAI_FREQ_EMERG, +}; + +/* + * Load tracking buffer source: 1 for ld_add; 0 for pre_ld_add; 2 for after EMA + */ +#define DVFS_LTBRSR (2 << MXC_DVFSCNTR_LTBRSR_OFFSET) + +extern struct dvfs_wp dvfs_core_setpoint[2]; +extern int low_bus_freq_mode; +extern int high_bus_freq_mode; +extern int set_low_bus_freq(void); +extern int set_high_bus_freq(int high_bus_speed); +extern int low_freq_bus_used(void); +extern void propagate_rate(struct clk *tclk); + +DEFINE_SPINLOCK(mxc_dvfs_core_lock); + +void dvfs_core_set_bus_freq(void) +{ + u32 reg; + int low_freq_bus_ready = 0; + + /* Mask DVFS irq */ + reg = __raw_readl(dvfs_data->dvfs_cntr_reg_addr); + /* FSVAIM=1 */ + reg |= MXC_DVFSCNTR_FSVAIM; + __raw_writel(reg, dvfs_data->dvfs_cntr_reg_addr); + + low_freq_bus_ready = low_freq_bus_used(); + + if ((curr_wp == cpu_wp_nr - 1) && (!low_bus_freq_mode) + && (low_freq_bus_ready)) + set_low_bus_freq(); + else if (!low_freq_bus_ready) + set_high_bus_freq(0); + /* Enable DVFS interrupt */ + /* FSVAIM=0 */ + reg = (reg & ~MXC_DVFSCNTR_FSVAIM); + /* LBFL=1 */ + reg = (reg & ~MXC_DVFSCNTR_LBFL); + reg |= MXC_DVFSCNTR_LBFL; + __raw_writel(reg, dvfs_data->dvfs_cntr_reg_addr); +} + +static void dvfs_load_config(int set_point) +{ + u32 reg; + reg = 0; + + reg |= dvfs_core_setpoint[set_point].upthr << MXC_DVFSTHRS_UPTHR_OFFSET; + reg |= dvfs_core_setpoint[set_point].downthr << + MXC_DVFSTHRS_DNTHR_OFFSET; + reg |= dvfs_core_setpoint[set_point].panicthr; + __raw_writel(reg, dvfs_data->dvfs_thrs_reg_addr); + + reg = 0; + reg |= dvfs_core_setpoint[set_point].downcnt << + MXC_DVFSCOUN_DNCNT_OFFSET; + reg |= dvfs_core_setpoint[set_point].upcnt << MXC_DVFSCOUN_UPCNT_OFFSET; + __raw_writel(reg, dvfs_data->dvfs_coun_reg_addr); +} + +static int set_cpu_freq(int wp) +{ + int arm_podf; + int podf; + int vinc = 0; + int ret = 0; + int org_cpu_rate; + unsigned long rate = 0; + int gp_volt = 0; + u32 reg; + u32 reg1; + + if (cpu_wp_tbl[wp].pll_rate != cpu_wp_tbl[old_wp].pll_rate) { + /* PLL_RELOCK, set ARM_FREQ_SHIFT_DIVIDER */ + reg = __raw_readl(dvfs_data->ccm_cdcr_reg_addr); + reg &= 0xFFFFFFFB; + __raw_writel(reg, dvfs_data->ccm_cdcr_reg_addr); + org_cpu_rate = clk_get_rate(cpu_clk); + rate = cpu_wp_tbl[wp].cpu_rate; + + if (org_cpu_rate == rate) + return ret; + + gp_volt = cpu_wp_tbl[wp].cpu_voltage; + + if (gp_volt == 0) + return ret; + + /*Set the voltage for the GP domain. */ + if (rate > org_cpu_rate) { + ret = regulator_set_voltage(core_regulator, gp_volt, + gp_volt); + if (ret < 0) { + printk(KERN_DEBUG "COULD NOT SET GP VOLTAGE\n"); + return ret; + } + udelay(dvfs_data->delay_time); + } + + setup_pll(); + /* START the GPC main control FSM */ + /* set VINC */ + reg = __raw_readl(dvfs_data->gpc_vcr_reg_addr); + reg &= ~(MXC_GPCVCR_VINC_MASK | MXC_GPCVCR_VCNTU_MASK | + MXC_GPCVCR_VCNT_MASK); + + if (rate > org_cpu_rate) + reg |= 1 << MXC_GPCVCR_VINC_OFFSET; + + reg |= (1 << MXC_GPCVCR_VCNTU_OFFSET) | + (100 << MXC_GPCVCR_VCNT_OFFSET); + __raw_writel(reg, dvfs_data->gpc_vcr_reg_addr); + + reg = __raw_readl(dvfs_data->gpc_cntr_reg_addr); + reg |= MXC_GPCCNTR_FUPD; + reg |= MXC_GPCCNTR_ADU; + __raw_writel(reg, dvfs_data->gpc_cntr_reg_addr); + + reg |= MXC_GPCCNTR_STRT; + __raw_writel(reg, dvfs_data->gpc_cntr_reg_addr); + while (__raw_readl(dvfs_data->gpc_cntr_reg_addr) & 0x4000) + udelay(10); + + if (rate < org_cpu_rate) { + ret = regulator_set_voltage(core_regulator, + gp_volt, gp_volt); + if (ret < 0) { + printk(KERN_DEBUG + "COULD NOT SET GP VOLTAGE!!!!\n"); + return ret; + } + old_wp = wp; + } + + clk_set_rate(cpu_clk, rate); + } else { + podf = cpu_wp_tbl[wp].cpu_podf; + gp_volt = cpu_wp_tbl[wp].cpu_voltage; + + /* Change arm_podf only */ + /* set ARM_FREQ_SHIFT_DIVIDER */ + reg = __raw_readl(dvfs_data->ccm_cdcr_reg_addr); + reg &= 0xFFFFFFFB; + reg |= 1 << 2; + __raw_writel(reg, dvfs_data->ccm_cdcr_reg_addr); + + /* Get ARM_PODF */ + reg = __raw_readl(dvfs_data->ccm_cacrr_reg_addr); + arm_podf = reg & 0x07; + if (podf == arm_podf) { + printk(KERN_DEBUG + "No need to change freq and voltage!!!!\n"); + return 0; + } + + /* Check if FSVAI indicate freq up */ + if (podf < arm_podf) { + ret = regulator_set_voltage(core_regulator, + gp_volt, gp_volt); + if (ret < 0) { + printk(KERN_DEBUG + "COULD NOT SET GP VOLTAGE!!!!\n"); + return 0; + } + udelay(dvfs_data->delay_time); + vinc = 1; + dvfs_load_config(0); + } else { + vinc = 0; + dvfs_load_config(1); + } + + arm_podf = podf; + /* Set ARM_PODF */ + reg &= 0xFFFFFFF8; + reg |= arm_podf; + + reg1 = __raw_readl(dvfs_data->ccm_cdhipr_reg_addr); + if ((reg1 & 0x00010000) == 0) + __raw_writel(reg, dvfs_data->ccm_cacrr_reg_addr); + else { + printk(KERN_DEBUG "ARM_PODF still in busy!!!!\n"); + return 0; + } + + /* START the GPC main control FSM */ + reg = __raw_readl(dvfs_data->gpc_cntr_reg_addr); + reg |= MXC_GPCCNTR_FUPD; + /* ADU=1, select ARM domain */ + reg |= MXC_GPCCNTR_ADU; + __raw_writel(reg, dvfs_data->gpc_cntr_reg_addr); + /* set VINC */ + reg = __raw_readl(dvfs_data->gpc_vcr_reg_addr); + reg &= + ~(MXC_GPCVCR_VINC_MASK | MXC_GPCVCR_VCNTU_MASK | + MXC_GPCVCR_VCNT_MASK); + reg |= (1 << MXC_GPCVCR_VCNTU_OFFSET) | + (100 << MXC_GPCVCR_VCNT_OFFSET) | + (vinc << MXC_GPCVCR_VINC_OFFSET); + __raw_writel(reg, dvfs_data->gpc_vcr_reg_addr); + + reg = __raw_readl(dvfs_data->gpc_cntr_reg_addr); + reg &= (~(MXC_GPCCNTR_ADU | MXC_GPCCNTR_FUPD)); + reg |= MXC_GPCCNTR_ADU | MXC_GPCCNTR_FUPD | MXC_GPCCNTR_STRT; + __raw_writel(reg, dvfs_data->gpc_cntr_reg_addr); + + /* Wait for arm podf Enable */ + while ((__raw_readl(dvfs_data->gpc_cntr_reg_addr) & + MXC_GPCCNTR_STRT) == MXC_GPCCNTR_STRT) { + printk(KERN_DEBUG "Waiting arm_podf enabled!\n"); + udelay(10); + } + + if (vinc == 0) { + ret = regulator_set_voltage(core_regulator, + gp_volt, gp_volt); + if (ret < 0) { + printk(KERN_DEBUG + "COULD NOT SET GP VOLTAGE!!!!\n"); + return ret; + } + udelay(dvfs_data->delay_time); + } + + propagate_rate(pll1_sw_clk); +#if defined(CONFIG_CPU_FREQ) + cpufreq_trig_needed = 1; +#endif + old_wp = wp; + } + + return ret; +} + +static int start_dvfs(void) +{ + u32 reg; + unsigned long flags; + + if (dvfs_core_is_active) + return 0; + + spin_lock_irqsave(&mxc_dvfs_core_lock, flags); + + clk_enable(dvfs_clk); + + /* config reg GPC_CNTR */ + reg = __raw_readl(dvfs_data->gpc_cntr_reg_addr); + + /* GPCIRQ=1, select ARM IRQ */ + reg |= MXC_GPCCNTR_GPCIRQ; + /* ADU=1, select ARM domain */ + reg |= MXC_GPCCNTR_ADU; + __raw_writel(reg, dvfs_data->gpc_cntr_reg_addr); + + /* Set PREDIV bits */ + reg = __raw_readl(dvfs_data->dvfs_cntr_reg_addr); + reg = (reg & ~(dvfs_data->prediv_mask)); + reg |= (dvfs_data->prediv_val) << (dvfs_data->prediv_offset); + __raw_writel(reg, dvfs_data->dvfs_cntr_reg_addr); + + /* Enable DVFS interrupt */ + reg = __raw_readl(dvfs_data->dvfs_cntr_reg_addr); + /* FSVAIM=0 */ + reg = (reg & ~MXC_DVFSCNTR_FSVAIM); + /* Set MAXF, MINF */ + reg = (reg & ~(MXC_DVFSCNTR_MAXF_MASK | MXC_DVFSCNTR_MINF_MASK)); + reg |= 1 << MXC_DVFSCNTR_MAXF_OFFSET; + /* Select ARM domain */ + reg |= MXC_DVFSCNTR_DVFIS; + /* Enable DVFS frequency adjustment interrupt */ + reg = (reg & ~MXC_DVFSCNTR_FSVAIM); + /* Set load tracking buffer register source */ + reg = (reg & ~MXC_DVFSCNTR_LTBRSR_MASK); + reg |= DVFS_LTBRSR; + /* Set DIV3CK */ + reg = (reg & ~(dvfs_data->div3ck_mask)); + reg |= (dvfs_data->div3ck_val) << (dvfs_data->div3ck_offset); + /* Enable DVFS */ + reg |= MXC_DVFSCNTR_DVFEN; + __raw_writel(reg, dvfs_data->dvfs_cntr_reg_addr); + + dvfs_core_is_active = 1; + + spin_unlock_irqrestore(&mxc_dvfs_core_lock, flags); + + printk(KERN_DEBUG "DVFS is started\n"); + + return 0; +} + +/*! + * This function is called for module initialization. + * It sets up the DVFS hardware. + * It sets default values for DVFS thresholds and counters. The default + * values was chosen from a set of different reasonable values. They was tested + * and the default values in the driver gave the best results. + * More work should be done to find optimal values. + * + * @return 0 if successful; non-zero otherwise. + * + */ +static int init_dvfs_controller(void) +{ + /* DVFS loading config */ + dvfs_load_config(0); + + /* Set EMAC value */ + __raw_writel((dvfs_data->emac_val << MXC_DVFSEMAC_EMAC_OFFSET), + dvfs_data->dvfs_emac_reg_addr); + + return 0; +} + +static irqreturn_t dvfs_irq(int irq, void *dev_id) +{ + u32 reg; + + /* Check if DVFS0 (ARM) id requesting for freqency/voltage update */ + if ((__raw_readl(dvfs_data->gpc_cntr_reg_addr) & MXC_GPCCNTR_DVFS0CR) == + 0) + return IRQ_NONE; + + /* Mask DVFS irq */ + reg = __raw_readl(dvfs_data->dvfs_cntr_reg_addr); + /* FSVAIM=1 */ + reg |= MXC_DVFSCNTR_FSVAIM; + __raw_writel(reg, dvfs_data->dvfs_cntr_reg_addr); + + /* Mask GPC1 irq */ + reg = __raw_readl(dvfs_data->gpc_cntr_reg_addr); + reg |= MXC_GPCCNTR_GPCIRQM | 0x1000000; + __raw_writel(reg, dvfs_data->gpc_cntr_reg_addr); + + schedule_delayed_work(&dvfs_core_work, 0); + + return IRQ_HANDLED; +} + +static void dvfs_core_workqueue_handler(struct work_struct *work) +{ + u32 fsvai; + u32 reg; + u32 curr_cpu; + int ret = 0; + int maxf = 0, minf = 0; + int low_freq_bus_ready = 0; + + /* Check DVFS frequency adjustment interrupt status */ + reg = __raw_readl(dvfs_data->dvfs_cntr_reg_addr); + fsvai = (reg & MXC_DVFSCNTR_FSVAI_MASK) >> MXC_DVFSCNTR_FSVAI_OFFSET; + + /* Check FSVAI, FSVAI=0 is error */ + if (fsvai == FSVAI_FREQ_NOCHANGE) { + /* Do nothing. Freq change is not required */ + goto END; + } + + curr_cpu = clk_get_rate(cpu_clk); + + /* If FSVAI indicate freq down, + check arm-clk is not in lowest frequency 200 MHz */ + if (fsvai == FSVAI_FREQ_DECREASE) { + if (curr_cpu == cpu_wp_tbl[cpu_wp_nr - 1].cpu_rate) { + minf = 1; + goto END; + } else { + /* freq down */ + curr_wp++; + if (curr_wp >= cpu_wp_nr) { + curr_wp = cpu_wp_nr - 1; + goto END; + } + + if (curr_wp == cpu_wp_nr - 1) + minf = 1; + } + } else { + if (curr_cpu == cpu_wp_tbl[0].cpu_rate) { + maxf = 1; + goto END; + } else { + /* freq up */ + curr_wp = 0; + maxf = 1; + } + } + + low_freq_bus_ready = low_freq_bus_used(); + if ((curr_wp == cpu_wp_nr - 1) && (!low_bus_freq_mode) + && (low_freq_bus_ready)) { + ret = set_cpu_freq(curr_wp); + set_low_bus_freq(); + } else { + set_high_bus_freq(0); + ret = set_cpu_freq(curr_wp); + } + +#if defined(CONFIG_CPU_FREQ) + if (cpufreq_trig_needed == 1) { + cpufreq_trig_needed = 0; + cpufreq_update_policy(0); + } +#endif + +END: /* Set MAXF, MINF */ + reg = __raw_readl(dvfs_data->dvfs_cntr_reg_addr); + reg = (reg & ~(MXC_DVFSCNTR_MAXF_MASK | MXC_DVFSCNTR_MINF_MASK)); + reg |= maxf << MXC_DVFSCNTR_MAXF_OFFSET; + reg |= minf << MXC_DVFSCNTR_MINF_OFFSET; + + /* Enable DVFS interrupt */ + /* FSVAIM=0 */ + reg = (reg & ~MXC_DVFSCNTR_FSVAIM); + reg |= FSVAI_FREQ_NOCHANGE; + /* LBFL=1 */ + reg = (reg & ~MXC_DVFSCNTR_LBFL); + reg |= MXC_DVFSCNTR_LBFL; + __raw_writel(reg, dvfs_data->dvfs_cntr_reg_addr); + /*Unmask GPC1 IRQ */ + reg = __raw_readl(dvfs_data->gpc_cntr_reg_addr); + reg &= ~MXC_GPCCNTR_GPCIRQM; + __raw_writel(reg, dvfs_data->gpc_cntr_reg_addr); +} + +/*! + * This function disables the DVFS module. + */ +static void stop_dvfs(void) +{ + u32 reg = 0; + unsigned long flags; + u32 curr_cpu; + + if (dvfs_core_is_active) { + spin_lock_irqsave(&mxc_dvfs_core_lock, flags); + + /* Mask dvfs irq, disable DVFS */ + reg = __raw_readl(dvfs_data->dvfs_cntr_reg_addr); + /* FSVAIM=1 */ + reg |= MXC_DVFSCNTR_FSVAIM; + __raw_writel(reg, dvfs_data->dvfs_cntr_reg_addr); + + dvfs_core_is_active = 0; + spin_unlock_irqrestore(&mxc_dvfs_core_lock, flags); + + curr_wp = 0; + if (!high_bus_freq_mode) + set_high_bus_freq(1); + + curr_cpu = clk_get_rate(cpu_clk); + if (curr_cpu != cpu_wp_tbl[curr_wp].cpu_rate) { + set_cpu_freq(curr_wp); + /* disable DVFS */ + reg = __raw_readl(dvfs_data->dvfs_cntr_reg_addr); + reg = (reg & ~MXC_DVFSCNTR_DVFEN); + __raw_writel(reg, dvfs_data->dvfs_cntr_reg_addr); +#if defined(CONFIG_CPU_FREQ) + if (cpufreq_trig_needed == 1) { + cpufreq_trig_needed = 0; + cpufreq_update_policy(0); + } +#endif + } + + clk_disable(dvfs_clk); + } + + printk(KERN_DEBUG "DVFS is stopped\n"); +} + +static ssize_t dvfs_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (dvfs_core_is_active) + return sprintf(buf, "DVFS is enabled\n"); + else + return sprintf(buf, "DVFS is disabled\n"); +} + +static ssize_t dvfs_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + if (strstr(buf, "1") != NULL) { + if (start_dvfs() != 0) + printk(KERN_ERR "Failed to start DVFS\n"); + } else if (strstr(buf, "0") != NULL) + stop_dvfs(); + + return size; +} + +static DEVICE_ATTR(enable, 0644, dvfs_enable_show, dvfs_enable_store); + +/*! + * This is the probe routine for the DVFS driver. + * + * @param pdev The platform device structure + * + * @return The function returns 0 on success + */ +static int __devinit mxc_dvfs_core_probe(struct platform_device *pdev) +{ + int err = 0; + struct resource *res; + int irq; + + printk(KERN_INFO "mxc_dvfs_core_probe\n"); + dvfs_dev = &pdev->dev; + dvfs_data = pdev->dev.platform_data; + + INIT_DELAYED_WORK(&dvfs_core_work, dvfs_core_workqueue_handler); + + pll1_sw_clk = clk_get(NULL, "pll1_sw_clk"); + if (IS_ERR(pll1_sw_clk)) { + printk(KERN_INFO "%s: failed to get pll1_sw_clk\n", __func__); + return PTR_ERR(pll1_sw_clk); + } + + cpu_clk = clk_get(NULL, dvfs_data->clk1_id); + if (IS_ERR(cpu_clk)) { + printk(KERN_ERR "%s: failed to get cpu clock\n", __func__); + return PTR_ERR(cpu_clk); + } + + dvfs_clk = clk_get(NULL, dvfs_data->clk2_id); + if (IS_ERR(dvfs_clk)) { + printk(KERN_ERR "%s: failed to get dvfs clock\n", __func__); + return PTR_ERR(dvfs_clk); + } + + core_regulator = regulator_get(NULL, dvfs_data->reg_id); + if (IS_ERR(core_regulator)) { + clk_put(cpu_clk); + clk_put(dvfs_clk); + printk(KERN_ERR "%s: failed to get gp regulator\n", __func__); + return PTR_ERR(core_regulator); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + err = -ENODEV; + goto err1; + } + + /* + * Request the DVFS interrupt + */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + err = irq; + goto err1; + } + + /* request the DVFS interrupt */ + err = request_irq(irq, dvfs_irq, IRQF_SHARED, "dvfs", dvfs_dev); + if (err) + printk(KERN_ERR + "DVFS: Unable to attach to DVFS interrupt,err = %d", + err); + + clk_enable(dvfs_clk); + err = init_dvfs_controller(); + if (err) { + printk(KERN_ERR "DVFS: Unable to initialize DVFS"); + return err; + } + clk_disable(dvfs_clk); + + err = sysfs_create_file(&dvfs_dev->kobj, &dev_attr_enable.attr); + if (err) { + printk(KERN_ERR + "DVFS: Unable to register sysdev entry for DVFS"); + return err; + } + + /* Set the current working point. */ + cpu_wp_tbl = get_cpu_wp(&cpu_wp_nr); + old_wp = 0; + curr_wp = 0; + dvfs_core_resume = 0; + cpufreq_trig_needed = 0; + + return err; + +err1: + dev_err(&pdev->dev, "Failed to probe DVFS CORE\n"); + return err; +} + +/*! + * This function is called to put DVFS in a low power state. + * + * @param pdev the device structure + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxc_dvfs_core_suspend(struct platform_device *pdev, + pm_message_t state) +{ + if (dvfs_core_is_active) { + dvfs_core_resume = 1; + stop_dvfs(); + } + + return 0; +} + +/*! + * This function is called to resume the MU from a low power state. + * + * @param dev the device structure + * @param level the stage in device suspension process that we want the + * device to be put in + * + * @return The function always returns 0. + */ +static int mxc_dvfs_core_resume(struct platform_device *pdev) +{ + if (dvfs_core_resume) { + dvfs_core_resume = 0; + start_dvfs(); + } + + return 0; +} + +static struct platform_driver mxc_dvfs_core_driver = { + .driver = { + .name = "mxc_dvfs_core", + }, + .probe = mxc_dvfs_core_probe, + .suspend = mxc_dvfs_core_suspend, + .resume = mxc_dvfs_core_resume, +}; + +static int __init dvfs_init(void) +{ + if (platform_driver_register(&mxc_dvfs_core_driver) != 0) { + printk(KERN_ERR "mxc_dvfs_core_driver register failed\n"); + return -ENODEV; + } + + dvfs_core_is_active = 0; + printk(KERN_INFO "DVFS driver module loaded\n"); + return 0; +} + +static void __exit dvfs_cleanup(void) +{ + stop_dvfs(); + + /* release the DVFS interrupt */ + free_irq(MXC_INT_GPC1, NULL); + + sysfs_remove_file(&dvfs_dev->kobj, &dev_attr_enable.attr); + + /* Unregister the device structure */ + platform_driver_unregister(&mxc_dvfs_core_driver); + + clk_put(cpu_clk); + clk_put(dvfs_clk); + + dvfs_core_is_active = 0; + printk(KERN_INFO "DVFS driver module unloaded\n"); + +} + +module_init(dvfs_init); +module_exit(dvfs_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("DVFS driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-mxc/entry-pm.S b/arch/arm/plat-mxc/entry-pm.S new file mode 100644 index 000000000000..4a3af0e16191 --- /dev/null +++ b/arch/arm/plat-mxc/entry-pm.S @@ -0,0 +1,315 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file plat-mxc/entry-pm.S + * + * @brief This file contains common pm entry . + * + * @ingroup MXC_PM + */ + +#include <asm/assembler.h> +#include <asm/ptrace.h> +#include <asm/memory.h> +#include <asm/system.h> +#include <mach/hardware.h> +#include <asm/asm-offsets.h> +#include <asm/thread_info.h> +#include <asm/proc-fns.h> +#include <asm/vfpmacros.h> + +#define WAIT_MODE 111 +#define DOZE_MODE 112 +#define STOP_MODE 113 +#define DSM_MODE 114 + +#define PM_XLOAD_SIZE 0x04 +#define PM_XLOAD_ENTRY 0x08 +#define PM_XLOAD_SUSPEND_MODE 0x0C +#define PM_XLOAD_CORE_SP 0x10 + +#define PROCINFO_PROC_FNS 36 +#define PROC_FIN_FN 12 +#define PROC_IDLE_FN 20 + +#ifdef CONFIG_FIQ +#define ARM_CONTEXT_SIZE 12 +#else +#define ARM_CONTEXT_SIZE 8 +#endif + +#ifdef CONFIG_PM_VERBOSE +resume_str: + .string "Resume from DSM..." + .size resume_str, . - resume_str + +.macro show_resume_str + ldr r0, =resume_str + bl printk +.endm + +#else +.macro show_resume_str +.endm +#endif + + .data + .align 3 +arm_core_context: + .rept ARM_CONTEXT_SIZE + .long 0 + .endr + +#ifdef CONFIG_VFP + .text + .align 5 +arm_vfp_save: + mov ip, sp + stmdb sp!, {r0-r8, fp, ip, lr, pc} + sub fp, ip, #4 + mov r1, #THREAD_SIZE + sub r1, r1, #1 + bic r0, sp, r1 + ldr r8, [r0, #TI_CPU] + add r4, r0, #TI_VFPSTATE + + ldr r3, =last_VFP_context + VFPFMRX r2, FPEXC + tst r2, #FPEXC_EN + bne 1f + + ldr r4, [r3, r8, lsl #2] + cmp r4, #0 + beq dead_vfp +1: + bic r1, r2, #FPEXC_EN + VFPFMXR FPEXC, r1 + /*TODO: SMP */ + VFPFSTMIA r4, r1 + VFPFMRX r5, FPSCR + tst r2, #FPEXC_EX + VFPFMRX r6, FPINST, NE + tstne r2, #FPEXC_FP2V + VFPFMRX r7, FPINST2, NE + stmia r4, {r2, r5, r6, r7} + + mov r1, #0 + str r1, [r3, r8, lsl #2] +dead_vfp: + ldmia sp, {r0-r8, fp, sp, pc} +#endif +/* + * The function just be called in this file + * Current r0 ~r4 are not saved. + * Otherwise, the working registers should be saved + */ + .text + .align 5 +arm_core_save: + mov ip, sp + stmdb sp!, {r8, r9, sl, fp, ip, lr, pc} + sub fp, ip, #4 + ldr r0, =arm_core_context + mov r3, r0 + /* SVC mode */ + mrs r1, spsr @Save spsr + mrs r2, cpsr @Save cpsr + stmia r0!, {r1, r2} + /* Abort mode */ + msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | ABT_MODE + stmia r0!, {sp} @Save stack pointer for abort mode + msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | UND_MODE + stmia r0!, {sp} @Save stack pointer for undefine mode + msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | IRQ_MODE + stmia r0!, {sp} @Save stack pointer for irq mode +#ifdef CONFIG_FIQ + msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | FIQ_MODE + /*Save general register and sp for fiq mode*/ + stmia r0!, {r8-r9, sl, fp, ip, sp} +#endif + ldr r0, [r3, #4] + msr cpsr_c, r0 + ldmia sp, {r8-r9, sl, fp, sp, pc} + +/* + * The function just be called in this file + * Current r0 ~r4 are not saved. + * Otherwise, the working registers should be saved + */ +arm_core_restore: + mov ip, sp + stmdb sp!, {fp, ip, lr, pc} + sub fp, ip, #4 + ldr r0, =arm_core_context + mov r3, r0 + /* SVC mode */ + add r0, r0, #8 @skip svc mode + /* Abort mode */ + msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | ABT_MODE + ldmia r0!, {sp} @restore stack pointer for abort mode + msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | UND_MODE + ldmia r0!, {sp} @restore stack pointer for undefine mode + msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | IRQ_MODE + ldmia r0!, {sp} @restore stack pointer for irq mode +#ifdef CONFIG_FIQ + msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | FIQ_MODE + /*Save general register and sp for fiq mode*/ + ldmia r0!, {r8-r9, sl, fp, ip, sp} +#endif + ldmia r3!, {r1, r2} + msr cpsr, r2 @restore cpsr + msr spsr, r1 @restore spsr + ldmia sp, {fp, sp, pc} + +mxc_cp15_context: + .rept 16 + .long 0 + .endr + + .align 5 +mxc_cp15_restore: + /* Physical address */ + adr r0, mxc_cp15_context + ldmia r0, {r1-r9} +#ifndef CONFIG_PM_DEBUG + @Add dynamic check to skip this block when debug + sub lr, lr, #PHYS_OFFSET + add lr, lr, #PAGE_OFFSET +#endif + mcr p15, 0, r3, c1, c0, 2 @CP Access Register + mcr p15, 0, r2, c1, c0, 1 @Aux Control register + +#ifndef CONFIG_PM_DEBUG + mcr p15, 0, r0, c7, c5, 6 @flush BTAC/BTB + mcr p15, 0, r0, c7, c7, 0 @invalidate both caches + mcr p15, 0, r0, c8, c7, 0 @Inval TLBs +#endif + + mcr p15, 0, r4, c13, c0, 0 @PID + mcr p15, 0, r5, c13, c0, 1 @Context ID + + mcr p15, 0, r6, c3, c0, 0 @Domain Access Register + mcr p15, 0, r7, c2, c0, 0 @TTB0 + mcr p15, 0, r8, c2, c0, 1 @TTB1 + mcr p15, 0, r9, c2, c0, 2 @TTBC + + mcr p15, 0, r1, c1, c0, 0 @Control Register + /* mcu enabled */ + mrc p15, 0, r0, c2, c0, 0 + + mov pc, lr + nop + nop + nop + nop + nop + nop + nop + nop + +mxc_cp15_save: + mov ip, sp + stmdb sp!, {r8-r9, fp, ip, lr, pc} + sub fp, ip, #4 + ldr r0, =mxc_cp15_context +/* System Control Registers */ + mrc p15, 0, r1, c1, c0, 0 @Control Register + mrc p15, 0, r2, c1, c0, 1 @Aux Control Register + mrc p15, 0, r3, c1, c0, 2 @CP access Register + +/* Memory management Registers */ + mrc p15, 0, r4, c13, c0, 0 @PID + mrc p15, 0, r5, c13, c0, 1 @Context ID + + mrc p15, 0, r6, c3, c0, 0 @Domain Access Register + + mrc p15, 0, r7, c2, c0, 0 @TTB0 + mrc p15, 0, r8, c2, c0, 1 @TTB1 + mrc p15, 0, r9, c2, c0, 2 @TTBC + stmia r0, {r1-r9} + ldmia sp, {r8, r9, fp, sp, pc} + +/* + * int __mxc_pm_arch_entry(u32 entry, u32 size) + */ + .align 5 + .globl mxc_pm_arch_entry +mxc_pm_arch_entry: + mov ip, sp + stmdb sp!, {r4-r9, sl, fp, ip, lr, pc} + sub fp, ip, #4 + sub sp, sp, #4 + mov r8, r0 @save entry + mov r9, r1 @save entry size +#ifdef CONFIG_VFP + bl arm_vfp_save +#endif + /* r0 ~r3, ip is dirty*/ + bl arm_core_save @save arm context + bl mxc_cp15_save + mov r0, sp + mov r1, r8 @restore entry + mov r2, r9 @restore entry size + bl __mxc_pm_xload_setup +1: bl cpu_v6_proc_fin + bl cpu_v6_do_idle + nop + nop + nop + nop +__mxc_pm_arch_leave: + adr r0, __mxc_pm_xload_info + ldr sp, [r0, #PM_XLOAD_CORE_SP] + +#ifndef CONFIG_PM_DEBUG + sub sp, sp, #PAGE_OFFSET + add sp, sp, #PHYS_OFFSET +#endif + bl mxc_cp15_restore +#ifndef CONFIG_PM_DEBUG + sub sp, sp, #PHYS_OFFSET + add sp, sp, #PAGE_OFFSET +#endif + show_resume_str + bl arm_core_restore + ldmib sp, {r4-r9, sl, fp, sp, pc} + +__mxc_pm_xload_info: + adr pc, __mxc_pm_xload_entry @Jump instruction + .long __mxc_pm_xload_end - __mxc_pm_xload_info @loader size + .long (__mxc_pm_arch_leave - PAGE_OFFSET + PHYS_OFFSET) @resume entry + .long 0 @suspend state + .long 0 @Core Stack pointer +__mxc_pm_xload_entry: + adr r0, __mxc_pm_xload_info + ldr pc, [r0, #PM_XLOAD_ENTRY] +__mxc_pm_xload_end: + +/* + * __mxc_pm_xload_setup(u32 sp, u32 entry, u32 size) + * r0~r6 is dirty + */ +__mxc_pm_xload_setup: + ldr r3, =__mxc_pm_xload_info + str r0, [r3, #PM_XLOAD_CORE_SP] + ldr r4, [r3, #PM_XLOAD_SIZE] + cmp r2, r4 + blo 2f +1: ldr r5, [r3], #4 + str r5, [r1], #4 + subs r4, r4, #4 + bhi 1b + b 3f +2: str r3, [r1] +3: mov pc, lr diff --git a/arch/arm/plat-mxc/gpio.c b/arch/arm/plat-mxc/gpio.c index 7506d963be4b..a4ff5d768484 100644 --- a/arch/arm/plat-mxc/gpio.c +++ b/arch/arm/plat-mxc/gpio.c @@ -20,12 +20,32 @@ */ #include <linux/init.h> +#include <linux/interrupt.h> #include <linux/io.h> #include <linux/irq.h> #include <linux/gpio.h> +#include <linux/sysdev.h> +#include <mach/gpio.h> #include <mach/hardware.h> #include <asm-generic/bug.h> +#if defined(CONFIG_ARCH_MX2) +#else +/* gpio and gpio based interrupt handling */ +#define GPIO_DR 0x00 +#define GPIO_GDIR 0x04 +#define GPIO_PSR 0x08 +#define GPIO_ICR1 0x0C +#define GPIO_ICR2 0x10 +#define GPIO_IMR 0x14 +#define GPIO_ISR 0x18 +#define GPIO_INT_LOW_LEV 0x0 +#define GPIO_INT_HIGH_LEV 0x1 +#define GPIO_INT_RISE_EDGE 0x2 +#define GPIO_INT_FALL_EDGE 0x3 +#define GPIO_INT_NONE 0x4 +#endif + static struct mxc_gpio_port *mxc_gpio_ports; static int gpio_table_size; @@ -162,16 +182,23 @@ static void mxc_gpio_irq_handler(struct mxc_gpio_port *port, u32 irq_stat) } } -#if defined(CONFIG_ARCH_MX3) || defined(CONFIG_ARCH_MX1) -/* MX1 and MX3 has one interrupt *per* gpio port */ -static void mx3_gpio_irq_handler(u32 irq, struct irq_desc *desc) +#ifndef CONFIG_ARCH_MX2 +/* one interrupt *per* gpio port */ +static void gpio_irq_handler(u32 irq, struct irq_desc *desc) { u32 irq_stat; + u32 mask = 0xFFFFFFFF; struct mxc_gpio_port *port = (struct mxc_gpio_port *)get_irq_data(irq); - irq_stat = __raw_readl(port->base + GPIO_ISR) & - __raw_readl(port->base + GPIO_IMR); +#ifdef MXC_GPIO_SPLIT_IRQ_2 + if (irq == port->irq) + mask = 0x0000FFFF; + else + mask = 0xFFFF0000; +#endif + irq_stat = __raw_readl(port->base + GPIO_ISR) & + (__raw_readl(port->base + GPIO_IMR) & mask); mxc_gpio_irq_handler(port, irq_stat); } #endif @@ -197,11 +224,44 @@ static void mx2_gpio_irq_handler(u32 irq, struct irq_desc *desc) } #endif +/* + * Set interrupt number "irq" in the GPIO as a wake-up source. + * While system is running all registered GPIO interrupts need to have + * wake-up enabled. When system is suspended, only selected GPIO interrupts + * need to have wake-up enabled. + * @param irq interrupt source number + * @param enable enable as wake-up if equal to non-zero + * @return This function returns 0 on success. + */ +static int gpio_set_wake_irq(u32 irq, u32 enable) +{ + u32 gpio = irq_to_gpio(irq); + u32 gpio_idx = gpio & 0x1F; + struct mxc_gpio_port *port = &mxc_gpio_ports[gpio / 32]; + + if (enable) { + port->suspend_wakeup |= (1 << gpio_idx); + if (port->irq_high && (gpio_idx >= 16)) + enable_irq_wake(port->irq_high); + else + enable_irq_wake(port->irq); + } else { + port->suspend_wakeup &= ~(1 << gpio_idx); + if (port->irq_high && (gpio_idx >= 16)) + disable_irq_wake(port->irq_high); + else + disable_irq_wake(port->irq); + } + + return 0; +} + static struct irq_chip gpio_irq_chip = { .ack = gpio_ack_irq, .mask = gpio_mask_irq, .unmask = gpio_unmask_irq, .set_type = gpio_set_irq_type, + .set_wake = gpio_set_wake_irq, }; static void _set_gpio_direction(struct gpio_chip *chip, unsigned offset, @@ -252,9 +312,98 @@ static int mxc_gpio_direction_output(struct gpio_chip *chip, return 0; } +#ifdef CONFIG_PM +/*! + * This function puts the GPIO in low-power mode/state. + * All the interrupts that are enabled are first saved. + * Only those interrupts which registers as a wake source by calling + * enable_irq_wake are enabled. All other interrupts are disabled. + * + * @param dev the system device structure used to give information + * on GPIO to suspend + * @param mesg the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxc_gpio_suspend(struct sys_device *dev, pm_message_t mesg) +{ + int i; + struct mxc_gpio_port *port = mxc_gpio_ports; + + for (i = 0; i < gpio_table_size; i++) { + void __iomem *isr_reg; + void __iomem *imr_reg; + + isr_reg = port[i].base + GPIO_ISR; + imr_reg = port[i].base + GPIO_IMR; + + if (__raw_readl(isr_reg) & port[i].suspend_wakeup) + return -EPERM; + + port[i].saved_wakeup = __raw_readl(imr_reg); + __raw_writel(port[i].suspend_wakeup, imr_reg); + } + + return 0; +} + +/*! + * This function brings the GPIO back from low-power state. + * All the interrupts enabled before suspension are re-enabled from + * the saved information. + * + * @param dev the system device structure used to give information + * on GPIO to resume + * + * @return The function always returns 0. + */ +static int mxc_gpio_resume(struct sys_device *dev) +{ + int i; + struct mxc_gpio_port *port = mxc_gpio_ports; + + for (i = 0; i < gpio_table_size; i++) { + void __iomem *isr_reg; + void __iomem *imr_reg; + + isr_reg = port[i].base + GPIO_ISR; + imr_reg = port[i].base + GPIO_IMR; + + __raw_writel(port[i].saved_wakeup, imr_reg); + } + + return 0; +} +#else +#define mxc_gpio_suspend NULL +#define mxc_gpio_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct sysdev_class mxc_gpio_sysclass = { + .name = "mxc_gpio", + .suspend = mxc_gpio_suspend, + .resume = mxc_gpio_resume, +}; + +/*! + * This structure represents GPIO as a system device. + * 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, it is represented and treated a little differently. + */ +static struct sys_device mxc_gpio_device = { + .id = 0, + .cls = &mxc_gpio_sysclass, +}; + int __init mxc_gpio_init(struct mxc_gpio_port *port, int cnt) { int i, j; + int ret = 0; /* save for local usage */ mxc_gpio_ports = port; @@ -284,10 +433,13 @@ int __init mxc_gpio_init(struct mxc_gpio_port *port, int cnt) /* its a serious configuration bug when it fails */ BUG_ON( gpiochip_add(&port[i].chip) < 0 ); -#if defined(CONFIG_ARCH_MX3) || defined(CONFIG_ARCH_MX1) - /* setup one handler for each entry */ - set_irq_chained_handler(port[i].irq, mx3_gpio_irq_handler); +#ifndef CONFIG_ARCH_MX2 + set_irq_chained_handler(port[i].irq, gpio_irq_handler); set_irq_data(port[i].irq, &port[i]); + if (port[i].irq_high) { + set_irq_chained_handler(port[i].irq_high, gpio_irq_handler); + set_irq_data(port[i].irq_high, &port[i]); + } #endif } @@ -296,5 +448,10 @@ int __init mxc_gpio_init(struct mxc_gpio_port *port, int cnt) set_irq_chained_handler(port[0].irq, mx2_gpio_irq_handler); set_irq_data(port[0].irq, port); #endif - return 0; + + ret = sysdev_class_register(&mxc_gpio_sysclass); + if (ret == 0) + ret = sysdev_register(&mxc_gpio_device); + + return ret; } diff --git a/arch/arm/plat-mxc/include/mach/arc_otg.h b/arch/arm/plat-mxc/include/mach/arc_otg.h new file mode 100644 index 000000000000..2d58f2e6d6fd --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/arc_otg.h @@ -0,0 +1,343 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_ARCH_MXC_ARC_OTG_H__ +#define __ASM_ARCH_MXC_ARC_OTG_H__ + +#define USB_OTGREGS_BASE (OTG_BASE_ADDR + 0x000) +#define USB_H1REGS_BASE (OTG_BASE_ADDR + 0x200) +#define USB_H2REGS_BASE (OTG_BASE_ADDR + 0x400) +#ifdef CONFIG_ARCH_MX51 +#define USB_H3REGS_BASE (OTG_BASE_ADDR + 0x600) +#define USB_OTHERREGS_BASE (OTG_BASE_ADDR + 0x800) +#else +#define USB_OTHERREGS_BASE (OTG_BASE_ADDR + 0x600) +#endif + + +#define USBOTG_REG32(offset) (*((volatile u32 *)(IO_ADDRESS(USB_OTGREGS_BASE + (offset))))) +#define USBOTG_REG16(offset) (*((volatile u16 *)(IO_ADDRESS(USB_OTGREGS_BASE + (offset))))) + +#define USBH1_REG32(offset) (*((volatile u32 *)(IO_ADDRESS(USB_H1REGS_BASE + (offset))))) +#define USBH1_REG16(offset) (*((volatile u16 *)(IO_ADDRESS(USB_H1REGS_BASE + (offset))))) + +#define USBH2_REG32(offset) (*((volatile u32 *)(IO_ADDRESS(USB_H2REGS_BASE + (offset))))) +#define USBH2_REG16(offset) (*((volatile u16 *)(IO_ADDRESS(USB_H2REGS_BASE + (offset))))) + +#define USBOTHER_REG(offset) (*((volatile u32 *)(IO_ADDRESS(USB_OTHERREGS_BASE + (offset))))) + +/* + * OTG registers + */ +#define UOG_ID USBOTG_REG32(0x00) /* Host ID */ +#define UOG_HWGENERAL USBOTG_REG32(0x04) /* Host General */ +#define UOG_HWHOST USBOTG_REG32(0x08) /* Host h/w params */ +#define UOG_HWTXBUF USBOTG_REG32(0x10) /* TX buffer h/w params */ +#define UOG_HWRXBUF USBOTG_REG32(0x14) /* RX buffer h/w params */ +#define UOG_CAPLENGTH USBOTG_REG16(0x100) /* Capability register length */ +#define UOG_HCIVERSION USBOTG_REG16(0x102) /* Host Interface version */ +#define UOG_HCSPARAMS USBOTG_REG32(0x104) /* Host control structural params */ +#define UOG_HCCPARAMS USBOTG_REG32(0x108) /* control capability params */ +#define UOG_DCIVERSION USBOTG_REG32(0x120) /* device interface version */ +/* start EHCI registers: */ +#define UOG_USBCMD USBOTG_REG32(0x140) /* USB command register */ +#define UOG_USBSTS USBOTG_REG32(0x144) /* USB status register */ +#define UOG_USBINTR USBOTG_REG32(0x148) /* interrupt enable register */ +#define UOG_FRINDEX USBOTG_REG32(0x14c) /* USB frame index */ +/* segment (0x150) addr bits 63:32 if needed */ +#define UOG_PERIODICLISTBASE USBOTG_REG32(0x154) /* host crtlr frame list base addr */ +#define UOG_DEVICEADDR USBOTG_REG32(0x154) /* device crtlr device address */ +#define UOG_ASYNCLISTADDR USBOTG_REG32(0x158) /* host ctrlr next async addr */ +#define UOG_EPLISTADDR USBOTG_REG32(0x158) /* device ctrlr endpoint list addr */ +#define UOG_BURSTSIZE USBOTG_REG32(0x160) /* host ctrlr embedded TT async buf status */ +#define UOG_TXFILLTUNING USBOTG_REG32(0x164) /* TX FIFO fill tuning */ +#define UOG_ULPIVIEW USBOTG_REG32(0x170) /* ULPI viewport */ +#define UOG_CFGFLAG USBOTG_REG32(0x180) /* configflag (supports HS) */ +#define UOG_PORTSC1 USBOTG_REG32(0x184) /* port status and control */ +/* end EHCI registers: */ +#define UOG_OTGSC USBOTG_REG32(0x1a4) /* OTG status and control */ +#define UOG_USBMODE USBOTG_REG32(0x1a8) /* USB device mode */ +#define UOG_ENDPTSETUPSTAT USBOTG_REG32(0x1ac) /* endpoint setup status */ +#define UOG_ENDPTPRIME USBOTG_REG32(0x1b0) /* endpoint initialization */ +#define UOG_ENDPTFLUSH USBOTG_REG32(0x1b4) /* endpoint de-initialize */ +#define UOG_ENDPTSTAT USBOTG_REG32(0x1b8) /* endpoint status */ +#define UOG_ENDPTCOMPLETE USBOTG_REG32(0x1bc) /* endpoint complete */ +#define UOG_EPCTRL0 USBOTG_REG32(0x1c0) /* endpoint control0 */ +#define UOG_EPCTRL1 USBOTG_REG32(0x1c4) /* endpoint control1 */ +#define UOG_EPCTRL2 USBOTG_REG32(0x1c8) /* endpoint control2 */ +#define UOG_EPCTRL3 USBOTG_REG32(0x1cc) /* endpoint control3 */ +#define UOG_EPCTRL4 USBOTG_REG32(0x1d0) /* endpoint control4 */ +#define UOG_EPCTRL5 USBOTG_REG32(0x1d4) /* endpoint control5 */ +#define UOG_EPCTRL6 USBOTG_REG32(0x1d8) /* endpoint control6 */ +#define UOG_EPCTRL7 USBOTG_REG32(0x1dc) /* endpoint control7 */ + +/* + * Host 1 registers + */ +#define UH1_ID USBH1_REG32(0x00) /* Host ID */ +#define UH1_HWGENERAL USBH1_REG32(0x04) /* Host General */ +#define UH1_HWHOST USBH1_REG32(0x08) /* Host h/w params */ +#define UH1_HWTXBUF USBH1_REG32(0x10) /* TX buffer h/w params */ +#define UH1_HWRXBUF USBH1_REG32(0x14) /* RX buffer h/w params */ +#define UH1_CAPLENGTH USBH1_REG16(0x100) /* Capability register length */ +#define UH1_HCIVERSION USBH1_REG16(0x102) /* Host Interface version */ +#define UH1_HCSPARAMS USBH1_REG32(0x104) /* Host control structural params */ +#define UH1_HCCPARAMS USBH1_REG32(0x108) /* control capability params */ +/* start EHCI registers: */ +#define UH1_USBCMD USBH1_REG32(0x140) /* USB command register */ +#define UH1_USBSTS USBH1_REG32(0x144) /* USB status register */ +#define UH1_USBINTR USBH1_REG32(0x148) /* interrupt enable register */ +#define UH1_FRINDEX USBH1_REG32(0x14c) /* USB frame index */ +/* segment (0x150) addr bits 63:32 if needed */ +#define UH1_PERIODICLISTBASE USBH1_REG32(0x154) /* host crtlr frame list base addr */ +#define UH1_ASYNCLISTADDR USBH1_REG32(0x158) /* host ctrlr nest async addr */ +#define UH1_BURSTSIZE USBH1_REG32(0x160) /* host ctrlr embedded TT async buf status */ +#define UH1_TXFILLTUNING USBH1_REG32(0x164) /* TX FIFO fill tuning */ +/* configured_flag (0x180) configflag (supports HS) */ +#define UH1_PORTSC1 USBH1_REG32(0x184) /* port status and control */ +/* end EHCI registers: */ +#define UH1_USBMODE USBH1_REG32(0x1a8) /* USB device mode */ + +/* + * Host 2 registers + */ +#define UH2_ID USBH2_REG32(0x00) /* Host ID */ +#define UH2_HWGENERAL USBH2_REG32(0x04) /* Host General */ +#define UH2_HWHOST USBH2_REG32(0x08) /* Host h/w params */ +#define UH2_HWTXBUF USBH2_REG32(0x10) /* TX buffer h/w params */ +#define UH2_HWRXBUF USBH2_REG32(0x14) /* RX buffer h/w params */ +#define UH2_CAPLENGTH USBH2_REG16(0x100) /* Capability register length */ +#define UH2_HCIVERSION USBH2_REG16(0x102) /* Host Interface version */ +#define UH2_HCSPARAMS USBH2_REG32(0x104) /* Host control structural params */ +#define UH2_HCCPARAMS USBH2_REG32(0x108) /* control capability params */ +/* start EHCI registers: */ +#define UH2_USBCMD USBH2_REG32(0x140) /* USB command register */ +#define UH2_USBSTS USBH2_REG32(0x144) /* USB status register */ +#define UH2_USBINTR USBH2_REG32(0x148) /* interrupt enable register */ +#define UH2_FRINDEX USBH2_REG32(0x14c) /* USB frame index */ +/* segment (0x150) addr bits 63:32 if needed */ +#define UH2_PERIODICLISTBASE USBH2_REG32(0x154) /* host crtlr frame list base addr */ +#define UH2_ASYNCLISTADDR USBH2_REG32(0x158) /* host ctrlr nest async addr */ +#define UH2_BURSTSIZE USBH2_REG32(0x160) /* host ctrlr embedded TT async buf status */ +#define UH2_TXFILLTUNING USBH2_REG32(0x164) /* TX FIFO fill tuning */ +#define UH2_ULPIVIEW USBH2_REG32(0x170) /* ULPI viewport */ +/* configured_flag (0x180) configflag (supports HS) */ +#define UH2_PORTSC1 USBH2_REG32(0x184) /* port status and control */ +/* end EHCI registers */ +#define UH2_USBMODE USBH2_REG32(0x1a8) /* USB device mode */ + +/* + * other regs (not part of ARC core) + */ +#define USBCTRL USBOTHER_REG(0x00) /* USB Control register */ +#define USB_OTG_MIRROR USBOTHER_REG(0x04) /* USB OTG mirror register */ +#define USB_PHY_CTR_FUNC USBOTHER_REG(0x08) /* OTG UTMI PHY Function Control register */ +#define USB_PHY_CTR_FUNC2 USBOTHER_REG(0x0c) /* OTG UTMI PHY Function Control register */ +#define USB_CTRL_1 USBOTHER_REG(0x10) /* USB Cotrol Register 1*/ +#define USBCTRL_HOST2 USBOTHER_REG(0x14) /* USB Cotrol Register 1*/ +#define USBCTRL_HOST3 USBOTHER_REG(0x18) /* USB Cotrol Register 1*/ + +/* + * register bits + */ + +/* x_PORTSCx */ +#define PORTSC_PTS_MASK (3 << 30) /* parallel xcvr select mask */ +#define PORTSC_PTS_UTMI (0 << 30) /* UTMI/UTMI+ */ +#define PORTSC_PTS_PHILIPS (1 << 30) /* Philips classic */ +#define PORTSC_PTS_ULPI (2 << 30) /* ULPI */ +#define PORTSC_PTS_SERIAL (3 << 30) /* serial */ +#define PORTSC_STS (1 << 29) /* serial xcvr select */ +#define PORTSC_PTW (1 << 28) /* UTMI width */ +#define PORTSC_PHCD (1 << 23) /* Low Power Suspend */ +#define PORTSC_PORT_POWER (1 << 12) /* port power */ +#define PORTSC_LS_MASK (3 << 10) /* Line State mask */ +#define PORTSC_LS_SE0 (0 << 10) /* SE0 */ +#define PORTSC_LS_K_STATE (1 << 10) /* K-state */ +#define PORTSC_LS_J_STATE (2 << 10) /* J-state */ +#define PORTSC_PORT_RESET (1 << 8) /* Port reset */ +#define PORTSC_PORT_SUSPEND (1 << 7) /* Suspend */ +#define PORTSC_PORT_FORCE_RESUME (1 << 6) /* Force port resume */ +#define PORTSC_OVER_CURRENT_CHG (1 << 5) /* over current change */ +#define PORTSC_OVER_CURRENT_ACT (1 << 4) /* over currrent active */ +#define PORTSC_PORT_EN_DIS_CHANGE (1 << 3) /* port {en,dis}able change */ +#define PORTSC_PORT_ENABLE (1 << 2) /* port enabled */ +#define PORTSC_CONNECT_STATUS_CHANGE (1 << 1) /* connect status change */ +#define PORTSC_CURRENT_CONNECT_STATUS (1 << 0) /* current connect status */ + +#define PORTSC_W1C_BITS \ + ( PORTSC_CONNECT_STATUS_CHANGE | \ + PORTSC_PORT_EN_DIS_CHANGE | \ + PORTSC_OVER_CURRENT_CHG ) + +/* UOG_OTGSC Register Bits */ +/* control bits: */ +#define OTGSC_CTRL_VBUS_DISCHARGE (1 << 0) +#define OTGSC_CTRL_VBUS_CHARGE (1 << 1) +#define OTGSC_CTRL_OTG_TERM (1 << 3) /* controls DM pulldown */ +#define OTGSC_CTRL_DATA_PULSING (1 << 4) +#define OTGSC_CTRL_USB_ID_PU (1 << 5) /* enable ID pullup */ +/* current status: (R/O) */ +#define OTGSC_STS_USB_ID (1 << 8) /* 0=A-device 1=B-device */ +#define OTGSC_STS_A_VBUS_VALID (1 << 9) +#define OTGSC_STS_A_SESSION_VALID (1 << 10) +#define OTGSC_STS_B_SESSION_VALID (1 << 11) +#define OTGSC_STS_B_SESSION_END (1 << 12) +#define OTGSC_STS_1ms_TIMER (1 << 13) +#define OTGSC_STS_DATA_PULSE (1 << 14) +/* interrupt status: (write to clear) */ +#define OTGSC_IS_MASK (0x7f << 16) +#define OTGSC_IS_USB_ID (1 << 16) +#define OTGSC_IS_A_VBUS_VALID (1 << 17) +#define OTGSC_IS_A_SESSION_VALID (1 << 18) +#define OTGSC_IS_B_SESSION_VALID (1 << 19) +#define OTGSC_IS_B_SESSION_END (1 << 20) +#define OTGSC_IS_1ms_TIMER (1 << 21) +#define OTGSC_IS_DATA_PULSE (1 << 22) +/* interrupt enables: */ +#define OTGSC_IE_MASK (0x7f << 24) +#define OTGSC_IE_USB_ID (1 << 24) +#define OTGSC_IE_A_VBUS_VALID (1 << 25) +#define OTGSC_IE_A_SESSION_VALID (1 << 26) +#define OTGSC_IE_B_SESSION_VALID (1 << 27) +#define OTGSC_IE_B_SESSION_END (1 << 28) +#define OTGSC_IE_1ms_TIMER (1 << 29) +#define OTGSC_IE_DATA_PULSE (1 << 30) + +#if 1 /* FIXME these here for compatibility between my names and Leo's */ +/* OTG interrupt enable bit masks */ +#define OTGSC_INTERRUPT_ENABLE_BITS_MASK OTGSC_IE_MASK +#define OTGSC_INTSTS_MASK OTGSC_IS_MASK + +/* OTG interrupt status bit masks */ +#define OTGSC_INTERRUPT_STATUS_BITS_MASK OTGSC_IS_MASK +#endif + +/* x_USBMODE */ +#define USBMODE_SLOM (1 << 3) /* setup lockout mode */ +#define USBMODE_ES (1 << 2) /* (big) endian select */ +#define USBMODE_CM_MASK (3 << 0) /* controller mode mask */ +#define USBMODE_CM_HOST (3 << 0) /* host */ +#define USBMODE_CM_DEVICE (2 << 0) /* device */ +#define USBMODE_CM_reserved (1 << 0) /* reserved */ + +/* USBCTRL */ +#define UCTRL_OWIR (1 << 31) /* OTG wakeup intr request received */ +#define UCTRL_OSIC_MASK (3 << 29) /* OTG Serial Interface Config: */ +#define UCTRL_OSIC_DU6 (0 << 29) /* Differential/unidirectional 6 wire */ +#define UCTRL_OSIC_DB4 (1 << 29) /* Differential/bidirectional 4 wire */ +#define UCTRL_OSIC_SU6 (2 << 29) /* single-ended/unidirectional 6 wire */ +#define UCTRL_OSIC_SB3 (3 << 29) /* single-ended/bidirectional 3 wire */ + +#define UCTRL_OUIE (1 << 28) /* OTG ULPI intr enable */ +#define UCTRL_OWIE (1 << 27) /* OTG wakeup intr enable */ +#define UCTRL_OBPVAL_RXDP (1 << 26) /* OTG RxDp status in bypass mode */ +#define UCTRL_OBPVAL_RXDM (1 << 25) /* OTG RxDm status in bypass mode */ +#define UCTRL_OPM (1 << 24) /* OTG power mask */ +#define UCTRL_H2WIR (1 << 23) /* HOST2 wakeup intr request received */ +#define UCTRL_H2SIC_MASK (3 << 21) /* HOST2 Serial Interface Config: */ +#define UCTRL_H2SIC_DU6 (0 << 21) /* Differential/unidirectional 6 wire */ +#define UCTRL_H2SIC_DB4 (1 << 21) /* Differential/bidirectional 4 wire */ +#define UCTRL_H2SIC_SU6 (2 << 21) /* single-ended/unidirectional 6 wire */ +#define UCTRL_H2SIC_SB3 (3 << 21) /* single-ended/bidirectional 3 wire */ + +#ifdef CONFIG_ARCH_MX51 +#define UCTRL_H2UIE (1 << 8) /* HOST2 ULPI intr enable */ +#define UCTRL_H2WIE (1 << 7) /* HOST2 wakeup intr enable */ +#define UCTRL_H2PP 0 /* Power Polarity for uh2 */ +#define UCTRL_H2PM (1 << 4) /* HOST2 power mask */ +#else +#define UCTRL_H2UIE (1 << 20) /* HOST2 ULPI intr enable */ +#define UCTRL_H2WIE (1 << 19) /* HOST2 wakeup intr enable */ +#define UCTRL_H2PP (1 << 18) /* Power Polarity for uh2 */ +#define UCTRL_H2PM (1 << 16) /* HOST2 power mask */ +#endif + +#define UCTRL_H1WIR (1 << 15) /* HOST1 wakeup intr request received */ +#define UCTRL_H1SIC_MASK (3 << 13) /* HOST1 Serial Interface Config: */ +#define UCTRL_H1SIC_DU6 (0 << 13) /* Differential/unidirectional 6 wire */ +#define UCTRL_H1SIC_DB4 (1 << 13) /* Differential/bidirectional 4 wire */ +#define UCTRL_H1SIC_SU6 (2 << 13) /* single-ended/unidirectional 6 wire */ +#define UCTRL_H1SIC_SB3 (3 << 13) /* single-ended/bidirectional 3 wire */ +#define UCTRL_OLOCKD (1 << 13) /* otg lock disable */ +#define UCTRL_H2LOCKD (1 << 12) /* HOST2 lock disable */ +#define UCTRL_H1UIE (1 << 12) /* Host1 ULPI interrupt enable */ + +#define UCTRL_PP (1 << 11) /* power polarity bit */ +#define UCTRL_H1WIE (1 << 11) /* HOST1 wakeup intr enable */ +#define UCTRL_H1BPVAL_RXDP (1 << 10) /* HOST1 RxDp status in bypass mode */ +#define UCTRL_XCSO (1 << 10) /* Xcvr Clock Select for OTG port */ +#define UCTRL_H1BPVAL_RXDM (1 << 9) /* HOST1 RxDm status in bypass mode */ +#define UCTRL_XCSH2 (1 << 9) /* Xcvr Clock Select for Host port */ +#define UCTRL_H1PM (1 << 8) /* HOST1 power mask */ +#define UCTRL_IP_PULIDP (1 << 8) /* Ipp_Puimpel_Pullup_Dp */ + +#define UCTRL_IP_PUE_UP (1 << 7) /* ipp_pue_pullup_dp */ +#define UCTRL_IP_PUE_DOWN (1 << 6) /* ipp_pue_pulldwn_dpdm */ +#define UCTRL_H2DT (1 << 5) /* HOST2 TLL disabled */ +#define UCTRL_H1DT (1 << 4) /* HOST1 TLL disabled */ +#define UCTRL_USBTE (1 << 4) /* USBT Transceiver enable */ +#define UCTRL_OCPOL (1 << 3) /* OverCurrent Polarity */ +#define UCTRL_OCE (1 << 2) /* OverCurrent Enable */ +#define UCTRL_H2OCPOL (1 << 2) /* OverCurrent Polarity of Host2 */ +#define UCTRL_H2OCS (1 << 1) /* Host OverCurrent State */ +#define UCTRL_BPE (1 << 0) /* bypass mode enable */ +#define UCTRL_OTD (1 << 0) /* OTG TLL Disable */ +#define UCTRL_OOCS (1 << 0) /* OTG OverCurrent State */ + +/* USBCMD */ +#define UCMD_RUN_STOP (1 << 0) /* controller run/stop */ +#define UCMD_RESET (1 << 1) /* controller reset */ +#define UCMD_ITC_NO_THRESHOLD ~(0xff << 16) /* Interrupt Threshold Control */ + +/* OTG_MIRROR */ +#define OTGM_SESEND (1 << 4) /* B device session end */ +#define OTGM_VBUSVAL (1 << 3) /* Vbus valid */ +#define OTGM_BSESVLD (1 << 2) /* B session Valid */ +#define OTGM_ASESVLD (1 << 1) /* A session Valid */ +#define OTGM_IDIDG (1 << 0) /* OTG ID pin status */ + /* 1=high: Operate as B-device */ + /* 0=low : Operate as A-device */ + +/* USB_PHY_CTRL_FUNC */ +#define USB_UTMI_PHYCTRL_UTMI_ENABLE (1 << 24) +#define USB_UTMI_PHYCTRL_CHGRDETEN (1 << 24) /* Enable Charger Detector */ +#define USB_UTMI_PHYCTRL_CHGRDETON (1 << 23) /* Charger Detector Power On Control */ +#define USB_UTMI_PHYCTRL_OC_POL (1 << 9) /* OTG Polarity of Overcurrent */ +#define USB_UTMI_PHYCTRL_OC_DIS (1 << 8) /* OTG Disable Overcurrent Event */ +/* USB_PHY_CTRL_FUNC2*/ +#define USB_UTMI_PHYCTRL2_PLLDIV_MASK 0x3 +#define USB_UTMI_PHYCTRL2_PLLDIV_SHIFT 0 +#define USB_UTMI_PHYCTRL2_HSDEVSEL_MASK 0x3 +#define USB_UTMI_PHYCTRL2_HSDEVSEL_SHIFT 19 + +/* USB_CTRL_1 */ +#define USB_CTRL_UH1_EXT_CLK_EN (1 << 25) +#define USB_CTRL_UH2_EXT_CLK_EN (1 << 26) + +/* ULPIVIEW register bits */ +#define ULPIVW_OFF (0x170) +#define ULPIVW_WU (1 << 31) /* Wakeup */ +#define ULPIVW_RUN (1 << 30) /* read/write run */ +#define ULPIVW_WRITE (1 << 29) /* 0=read 1=write */ +#define ULPIVW_SS (1 << 27) /* SyncState */ +#define ULPIVW_PORT_MASK 0x07 /* Port field */ +#define ULPIVW_PORT_SHIFT 24 +#define ULPIVW_ADDR_MASK 0xFF /* data address field */ +#define ULPIVW_ADDR_SHIFT 16 +#define ULPIVW_RDATA_MASK 0xFF /* read data field */ +#define ULPIVW_RDATA_SHIFT 8 +#define ULPIVW_WDATA_MASK 0xFF /* write data field */ +#define ULPIVW_WDATA_SHIFT 0 + +#define HCSPARAMS_PPC (0x1<<4) /* Port Power Control */ +#endif diff --git a/arch/arm/plat-mxc/include/mach/audio_controls.h b/arch/arm/plat-mxc/include/mach/audio_controls.h new file mode 100644 index 000000000000..810bf50b16a8 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/audio_controls.h @@ -0,0 +1,220 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file include/asm-arm/arch-mxc/audio_controls.h + * @brief this file implements the mxc sound driver interface to OSS framework + * @ingroup SOUND_DRV + */ +#ifndef __ASM_ARCH_MXC_AUDIO_CONTROLS_H__ +#define __ASM_ARCH_MXC_AUDIO_CONTROLS_H__ + +/*! + * This ioctl can be used to get the adder configuration, use the audio control + * SNDCTL_MC13783_READ_OUT_MIXER.\n + * Possible returned values are : + * @see MC13783_AUDIO_ADDER_STEREO + * @see MC13783_AUDIO_ADDER_STEREO_OPPOSITE + * @see MC13783_AUDIO_ADDER_MONO + * @see MC13783_AUDIO_ADDER_MONO_OPPOSITE + * + */ +#define SNDCTL_MC13783_READ_OUT_ADDER _SIOR('Z', 6, int) + +/*! + * To set the adder configuration, use the audio control + * SNDCTL_MC13783_WRITE_OUT_MIXER. Possible arguments are : \n + * @see MC13783_AUDIO_ADDER_STEREO + * @see MC13783_AUDIO_ADDER_STEREO_OPPOSITE + * @see MC13783_AUDIO_ADDER_MONO + * @see MC13783_AUDIO_ADDER_MONO_OPPOSITE + * + */ +#define SNDCTL_MC13783_WRITE_OUT_ADDER _SIOWR('Z', 7, int) + +/*! + * To get the codec balance configuration, use the audio control + * SNDCTL_MC13783_READ_OUT_BALANCE.\n + * Range is 0 (-21 dB left) to 100 (-21 dB right), linear, 3dB step ; + * 50 is no balance. + * \n Examples: + * \n 0 : -21dB left 50 : balance deactivated 100 : -21 dB right + * + */ +#define SNDCTL_MC13783_READ_OUT_BALANCE _SIOR('Z', 8, int) + +/*! + * To set the codec balance configuration, use the audio control + * SNDCTL_MC13783_WRITE_OUT_BALANCE.\n + * Range is 0 (-21 dB left) to 100 (-21 dB right), linear, 3dB step ; + * 50 is no balance. + * \n Examples: + * \n 0 : -21dB left 50 : balance deactivated 100 : -21 dB right + * + */ +#define SNDCTL_MC13783_WRITE_OUT_BALANCE _SIOWR('Z', 9, int) + +/*! + * To set the codec filter configuration, use the audio control + * SNDCTL_MC13783_WRITE_CODEC_FILTER. + * The new configuration replaces the old one.\n + * Possible arguments are : + * @see MC13783_CODEC_FILTER_DISABLE + * @see MC13783_CODEC_FILTER_HIGH_PASS_IN + * @see MC13783_CODEC_FILTER_HIGH_PASS_OUT + * @see MC13783_CODEC_FILTER_DITHERING \n + * + */ +#define SNDCTL_MC13783_WRITE_CODEC_FILTER _SIOWR('Z', 20, int) + +/*! + * To get the codec filter configuration, use the audio control : + * SNDCTL_MC13783_READ_CODEC_FILTER. + * The new configuration replaces the old one.\n + * Possible returned values are : + * @see MC13783_CODEC_FILTER_DISABLE + * @see MC13783_CODEC_FILTER_HIGH_PASS_IN + * @see MC13783_CODEC_FILTER_HIGH_PASS_OUT + * @see MC13783_CODEC_FILTER_DITHERING \n + * + */ +#define SNDCTL_MC13783_READ_CODEC_FILTER _SIOR('Z', 21, int) + +/* + * To set the clock configuration, use the audio control + * SNDCTL_MC13783_WRITE_MASTER_CLOCK. \n + * Possible arguments are : \n + * 1 : to MCU master \n + * 2 : to MC13783 master + */ +#define SNDCTL_MC13783_WRITE_MASTER_CLOCK _SIOR('Z', 30, int) + +/*! + * To set the output port, use the audio control + * SNDCTL_MC13783_WRITE_PORT.\n + * Possible returned values are : + * \n 1 : to port 4 + * \n 2 : to port 5 + * Possible returned values are : + * \n 1 : port 4 + * \n 2 : port 5 + */ +#define SNDCTL_MC13783_WRITE_PORT _SIOR('Z', 31, int) + +/*! + * Argument for the mc13783 adder configuration + * @see SNDCTL_MC13783_WRITE_OUT_ADDER + * @see SNDCTL_MC13783_READ_OUT_ADDER + */ +#define MC13783_AUDIO_ADDER_STEREO 0x1 +/*! + * Argument for the mc13783 adder configuration + * @see SNDCTL_MC13783_WRITE_OUT_ADDER + * @see SNDCTL_MC13783_READ_OUT_ADDER + */ +#define MC13783_AUDIO_ADDER_STEREO_OPPOSITE 0x2 +/*! + * Argument for the mc13783 adder configuration + * @see SNDCTL_MC13783_WRITE_OUT_ADDER + * @see SNDCTL_MC13783_READ_OUT_ADDER + */ +#define MC13783_AUDIO_ADDER_MONO 0x4 +/*! + * Argument for the mc13783 adder configuration + * @see SNDCTL_MC13783_WRITE_OUT_ADDER + * @see SNDCTL_MC13783_READ_OUT_ADDER + */ +#define MC13783_AUDIO_ADDER_MONO_OPPOSITE 0x8 + +/*! + * Argument for the mc13783 codec filter configuration + * @see SNDCTL_MC13783_WRITE_CODEC_FILTER + * @see SNDCTL_MC13783_READ_CODEC_FILTER + */ +#define MC13783_CODEC_FILTER_DISABLE 0x0 +/*! + * Argument for the mc13783 codec filter configuration + * @see SNDCTL_MC13783_WRITE_CODEC_FILTER + * @see SNDCTL_MC13783_READ_CODEC_FILTER + */ +#define MC13783_CODEC_FILTER_HIGH_PASS_IN 0x1 +/*! + * Argument for the mc13783 codec filter configuration + * @see SNDCTL_MC13783_WRITE_CODEC_FILTER + * @see SNDCTL_MC13783_READ_CODEC_FILTER + */ +#define MC13783_CODEC_FILTER_HIGH_PASS_OUT 0x2 +/*! + * Argument for the mc13783 codec filter configuration + * @see SNDCTL_MC13783_WRITE_CODEC_FILTER + * @see SNDCTL_MC13783_READ_CODEC_FILTER + */ +#define MC13783_CODEC_FILTER_DITHERING 0x4 + +/*! + * Argument for the system audio clocking selection + * @see MXC_AUDIO_CLOCKING_MCU_MASTER + * @see SNDCTL_CLK_SET_MASTER + */ +#define MXC_AUDIO_CLOCKING_MC13783_MASTER 0x0 + +/*! + * Argument for the system audio clocking selection + * @see MXC_AUDIO_CLOCKING_MC13783_MASTER + * @see SNDCTL_CLK_SET_MASTER + */ +#define MXC_AUDIO_CLOCKING_MCU_MASTER 0x1 + +/*! + * Argument for the DAM output port selection + * @see SNDCTL_DAM_SET_OUT_PORT + * @see MXC_DAM_OUT_PORT_AD2 + */ +#define MXC_DAM_OUT_PORT_AD1 0x0 + +/*! + * Argument for the DAM output port selection + * @see SNDCTL_DAM_SET_OUT_PORT + * @see MXC_DAM_OUT_PORT_AD1 + */ +#define MXC_DAM_OUT_PORT_AD2 0x1 + +/*! + * Argument for the mc13783 codec filter configuration + * @see SNDCTL_MC13783_WRITE_CODEC_FILTER + * @see SNDCTL_MC13783_READ_CODEC_FILTER + */ +#define MC13783_CODEC_FILTER_DISABLE 0x0 + +/*! + * Argument for the mc13783 codec filter configuration + * @see SNDCTL_MC13783_WRITE_CODEC_FILTER + * @see SNDCTL_MC13783_READ_CODEC_FILTER + */ +#define MC13783_CODEC_FILTER_HIGH_PASS_IN 0x1 + +/*! + * Argument for the mc13783 codec filter configuration + * @see SNDCTL_MC13783_WRITE_CODEC_FILTER + * @see SNDCTL_MC13783_READ_CODEC_FILTER + */ +#define MC13783_CODEC_FILTER_HIGH_PASS_OUT 0x2 + +/*! + * Argument for the mc13783 codec filter configuration + * @see SNDCTL_MC13783_WRITE_CODEC_FILTER + * @see SNDCTL_MC13783_READ_CODEC_FILTER + */ +#define MC13783_CODEC_FILTER_DITHERING 0x4 + +#endif /* __ASM_ARCH_MXC_AUDIO_CONTROLS_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/clock.h b/arch/arm/plat-mxc/include/mach/clock.h index 43a82d0c534d..e9333bb1729b 100644 --- a/arch/arm/plat-mxc/include/mach/clock.h +++ b/arch/arm/plat-mxc/include/mach/clock.h @@ -1,5 +1,5 @@ /* - * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright 2008 Juergen Beisert, kernel@pengutronix.de * * This program is free software; you can redistribute it and/or @@ -38,6 +38,8 @@ struct clk { struct clk *parent; /* Secondary clock to enable/disable with this clock */ struct clk *secondary; + /* Current clock rate */ + unsigned long rate; /* Reference count of clock enable/disable */ __s8 usecount; /* Register bit position for clock's enable/disable control. */ @@ -45,8 +47,9 @@ struct clk { /* Register address for clock's enable/disable control. */ void __iomem *enable_reg; u32 flags; - /* get the current clock rate (always a fresh value) */ - unsigned long (*get_rate) (struct clk *); + /* Function ptr to recalculate the clock's rate based on parent + clock's rate */ + void (*recalc) (struct clk *); /* Function ptr to set the clock to a new rate. The rate must match a supported rate returned from round_rate. Leave blank if clock is not programmable */ @@ -66,6 +69,16 @@ struct clk { int clk_register(struct clk *clk); void clk_unregister(struct clk *clk); +int clk_get_usecount(struct clk *clk); +int clk_set_pll_dither(struct clk *clk, unsigned int pll_ppm); + +/* Clock flags */ +#define RATE_PROPAGATES (1 << 0) /* Program children too */ +#define ALWAYS_ENABLED (1 << 1) /* Clock cannot be disabled */ +#define RATE_FIXED (1 << 2) /* Fixed clock rate */ +#define CPU_FREQ_TRIG_UPDATE (1 << 3) /* CPUFREQ trig update */ +#define AHB_HIGH_SET_POINT (1 << 4) /* Requires max AHB clock */ +#define AHB_MED_SET_POINT (1 << 5) /* Requires med AHB clock */ unsigned long mxc_decode_pll(unsigned int pll, u32 f_ref); diff --git a/arch/arm/plat-mxc/include/mach/common.h b/arch/arm/plat-mxc/include/mach/common.h index 02c3cd004db3..b66644df843c 100644 --- a/arch/arm/plat-mxc/include/mach/common.h +++ b/arch/arm/plat-mxc/include/mach/common.h @@ -16,16 +16,26 @@ struct clk; extern void mx1_map_io(void); extern void mx21_map_io(void); +extern void mx25_map_io(void); extern void mx27_map_io(void); extern void mx31_map_io(void); extern void mx35_map_io(void); +extern void mx37_map_io(void); +extern void mx51_map_io(void); extern void mxc_init_irq(void); -extern void mxc_timer_init(struct clk *timer_clk); +extern void mxc_timer_init(struct clk *timer_clk, void __iomem *base, int irq); extern int mx1_clocks_init(unsigned long fref); extern int mx21_clocks_init(unsigned long lref, unsigned long fref); +extern int mx25_clocks_init(unsigned long fref); extern int mx27_clocks_init(unsigned long fref); extern int mx31_clocks_init(unsigned long fref); extern int mx35_clocks_init(void); +extern int mx37_clocks_init(unsigned long ckil, unsigned long osc, unsigned long ckih1, unsigned long ckih2); +extern int mx51_clocks_init(unsigned long ckil, unsigned long osc, unsigned long ckih1, unsigned long ckih2); +extern int mxc_init_devices(void); +extern void mxc_cpu_init(void) __init; +extern void mxc_cpu_common_init(void); +extern void __init early_console_setup(char *); extern int mxc_register_gpios(void); extern int mxc_register_device(struct platform_device *pdev, void *data); extern void mxc_set_cpu_type(unsigned int type); diff --git a/arch/arm/plat-mxc/include/mach/dma.h b/arch/arm/plat-mxc/include/mach/dma.h new file mode 100644 index 000000000000..d2f6091cebb3 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/dma.h @@ -0,0 +1,293 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * 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_MXC_DMA_H__ +#define __ASM_ARCH_MXC_DMA_H__ + +#include <linux/scatterlist.h> + +#define MXC_DMA_DYNAMIC_CHANNEL 255 + +#define MXC_DMA_DONE 0x0 +#define MXC_DMA_REQUEST_TIMEOUT 0x1 +#define MXC_DMA_TRANSFER_ERROR 0x2 + +/*! This defines the list of device ID's for DMA */ +typedef enum mxc_dma_device { + MXC_DMA_UART1_RX, + MXC_DMA_UART1_TX, + MXC_DMA_UART2_RX, + MXC_DMA_UART2_TX, + MXC_DMA_UART3_RX, + MXC_DMA_UART3_TX, + MXC_DMA_UART4_RX, + MXC_DMA_UART4_TX, + MXC_DMA_UART5_RX, + MXC_DMA_UART5_TX, + MXC_DMA_UART6_RX, + MXC_DMA_UART6_TX, + MXC_DMA_MMC1_WIDTH_1, + MXC_DMA_MMC1_WIDTH_4, + MXC_DMA_MMC2_WIDTH_1, + MXC_DMA_MMC2_WIDTH_4, + MXC_DMA_SSI1_8BIT_RX0, + MXC_DMA_SSI1_8BIT_TX0, + MXC_DMA_SSI1_16BIT_RX0, + MXC_DMA_SSI1_16BIT_TX0, + MXC_DMA_SSI1_24BIT_RX0, + MXC_DMA_SSI1_24BIT_TX0, + MXC_DMA_SSI1_8BIT_RX1, + MXC_DMA_SSI1_8BIT_TX1, + MXC_DMA_SSI1_16BIT_RX1, + MXC_DMA_SSI1_16BIT_TX1, + MXC_DMA_SSI1_24BIT_RX1, + MXC_DMA_SSI1_24BIT_TX1, + MXC_DMA_SSI2_8BIT_RX0, + MXC_DMA_SSI2_8BIT_TX0, + MXC_DMA_SSI2_16BIT_RX0, + MXC_DMA_SSI2_16BIT_TX0, + MXC_DMA_SSI2_24BIT_RX0, + MXC_DMA_SSI2_24BIT_TX0, + MXC_DMA_SSI2_8BIT_RX1, + MXC_DMA_SSI2_8BIT_TX1, + MXC_DMA_SSI2_16BIT_RX1, + MXC_DMA_SSI2_16BIT_TX1, + MXC_DMA_SSI2_24BIT_RX1, + MXC_DMA_SSI2_24BIT_TX1, + MXC_DMA_FIR_RX, + MXC_DMA_FIR_TX, + MXC_DMA_CSPI1_RX, + MXC_DMA_CSPI1_TX, + MXC_DMA_CSPI2_RX, + MXC_DMA_CSPI2_TX, + MXC_DMA_CSPI3_RX, + MXC_DMA_CSPI3_TX, + MXC_DMA_ATA_RX, + MXC_DMA_ATA_TX, + MXC_DMA_MEMORY, + MXC_DMA_FIFO_MEMORY, + MXC_DMA_DSP_PACKET_DATA0_RD, + MXC_DMA_DSP_PACKET_DATA0_WR, + MXC_DMA_DSP_PACKET_DATA1_RD, + MXC_DMA_DSP_PACKET_DATA1_WR, + MXC_DMA_DSP_LOG0_CHNL, + MXC_DMA_DSP_LOG1_CHNL, + MXC_DMA_DSP_LOG2_CHNL, + MXC_DMA_DSP_LOG3_CHNL, + MXC_DMA_CSI_RX, + MXC_DMA_SPDIF_16BIT_TX, + MXC_DMA_SPDIF_16BIT_RX, + MXC_DMA_SPDIF_32BIT_TX, + MXC_DMA_SPDIF_32BIT_RX, + MXC_DMA_ASRC_A_RX, + MXC_DMA_ASRC_A_TX, + MXC_DMA_ASRC_B_RX, + MXC_DMA_ASRC_B_TX, + MXC_DMA_ASRC_C_RX, + MXC_DMA_ASRC_C_TX, + MXC_DMA_ASRCA_ESAI, + MXC_DMA_ASRCB_ESAI, + MXC_DMA_ASRCC_ESAI, + MXC_DMA_ASRCA_SSI1_TX0, + MXC_DMA_ASRCA_SSI1_TX1, + MXC_DMA_ASRCA_SSI2_TX0, + MXC_DMA_ASRCA_SSI2_TX1, + MXC_DMA_ASRCB_SSI1_TX0, + MXC_DMA_ASRCB_SSI1_TX1, + MXC_DMA_ASRCB_SSI2_TX0, + MXC_DMA_ASRCB_SSI2_TX1, + MXC_DMA_ESAI_16BIT_RX, + MXC_DMA_ESAI_16BIT_TX, + MXC_DMA_ESAI_24BIT_RX, + MXC_DMA_ESAI_24BIT_TX, + MXC_DMA_TEST_RAM2D2RAM, + MXC_DMA_TEST_RAM2RAM2D, + MXC_DMA_TEST_RAM2D2RAM2D, + MXC_DMA_TEST_RAM2RAM, + MXC_DMA_TEST_HW_CHAINING, + MXC_DMA_TEST_SW_CHAINING +} mxc_dma_device_t; + +/*! This defines the prototype of callback funtion registered by the drivers */ +typedef void (*mxc_dma_callback_t) (void *arg, int error_status, + unsigned int count); + +/*! This defines the type of DMA transfer requested */ +typedef enum mxc_dma_mode { + MXC_DMA_MODE_READ, + MXC_DMA_MODE_WRITE, +} mxc_dma_mode_t; + +/*! This defines the DMA channel parameters */ +typedef struct mxc_dma_channel { + unsigned int active:1; /*!< When there has a active tranfer, it is set to 1 */ + unsigned int lock; /*!< Defines the channel is allocated or not */ + int curr_buf; /*!< Current buffer */ + mxc_dma_mode_t mode; /*!< Read or Write */ + unsigned int channel; /*!< Channel info */ + unsigned int dynamic:1; /*!< Channel not statically allocated when 1 */ + char *dev_name; /*!< Device name */ + void *private; /*!< Private structure for platform */ + mxc_dma_callback_t cb_fn; /*!< The callback function */ + void *cb_args; /*!< The argument of callback function */ +} mxc_dma_channel_t; + +/*! This structure contains the information about a dma transfer */ +typedef struct mxc_dma_requestbuf { + dma_addr_t src_addr; /*!< source address */ + dma_addr_t dst_addr; /*!< destination address */ + int num_of_bytes; /*!< the length of this transfer : bytes */ +} mxc_dma_requestbuf_t; + +/*! This struct contains the information for asrc special*/ +struct dma_channel_asrc_info { + u32 channs; /*!< data channels in asrc */ +}; + +/*! This struct contains the information for device special*/ +struct dma_channel_info { + struct dma_channel_asrc_info asrc; /*!< asrc special information */ +}; + +#if defined(CONFIG_ARCH_MX27) || defined(CONFIG_ARCH_MX21) +#include <mach/mx2_dma.h> +#else +#include <mach/sdma.h> +#endif + +/*! + * This function is generally called by the driver at open time. + * The DMA driver would do any initialization steps that is required + * to get the channel ready for data transfer. + * + * @param channel_id a pre-defined id. The peripheral driver would specify + * the id associated with its peripheral. This would be + * used by the DMA driver to identify the peripheral + * requesting DMA and do the necessary setup on the + * channel associated with the particular peripheral. + * The DMA driver could use static or dynamic DMA channel + * allocation. + * @param dev_name module name or device name + * @param data the customized parameter for special channel. + * @return returns a negative number on error if request for a DMA channel did not + * succeed, returns the channel number to be used on success. + */ +extern int mxc_dma_request_ext(mxc_dma_device_t channel_id, char *dev_name, + struct dma_channel_info *info); + +static inline int mxc_dma_request(mxc_dma_device_t channel_id, char *dev_name) +{ + return mxc_dma_request_ext(channel_id, dev_name, NULL); +} + +/*! + * This function is generally called by the driver at close time. The DMA + * driver would do any cleanup associated with this channel. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @return returns a negative number on error or 0 on success + */ +extern int mxc_dma_free(int channel_num); + +/*! + * This function would just configure the buffers specified by the user into + * dma channel. The caller must call mxc_dma_enable to start this transfer. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @param dma_buf an array of physical addresses to the user defined + * buffers. The caller must guarantee the dma_buf is + * available until the transfer is completed. + * @param num_buf number of buffers in the array + * @param mode specifies whether this is READ or WRITE operation + * @return This function returns a negative number on error if buffer could not be + * added with DMA for transfer. On Success, it returns 0 + */ +extern int mxc_dma_config(int channel_num, mxc_dma_requestbuf_t *dma_buf, + int num_buf, mxc_dma_mode_t mode); + +/*! + * This function would just configure the scatterlist specified by the + * user into dma channel. This is a slight variation of mxc_dma_config(), + * it is provided for the convenience of drivers that have a scatterlist + * passed into them. It is the calling driver's responsibility to have the + * correct physical address filled in the "dma_address" field of the + * scatterlist. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @param sg a scatterlist of buffers. The caller must guarantee + * the dma_buf is available until the transfer is + * completed. + * @param num_buf number of buffers in the array + * @param num_of_bytes total number of bytes to transfer. If set to 0, this + * would imply to use the length field of the scatterlist + * for each DMA transfer. Else it would calculate the size + * for each DMA transfer. + * @param mode specifies whether this is READ or WRITE operation + * @return This function returns a negative number on error if buffer could not + * be added with DMA for transfer. On Success, it returns 0 + */ +extern int mxc_dma_sg_config(int channel_num, struct scatterlist *sg, + int num_buf, int num_of_bytes, + mxc_dma_mode_t mode); + +/*! + * This function is provided if the driver would like to set/change its + * callback function. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @param callback a callback function to provide notification on transfer + * completion, user could specify NULL if he does not wish + * to be notified + * @param arg an argument that gets passed in to the callback + * function, used by the user to do any driver specific + * operations. + * @return this function returns a negative number on error if the callback + * could not be set for the channel or 0 on success + */ +extern int mxc_dma_callback_set(int channel_num, mxc_dma_callback_t callback, + void *arg); + +/*! + * This stops the DMA channel and any ongoing transfers. Subsequent use of + * mxc_dma_enable() will restart the channel and restart the transfer. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @return returns a negative number on error or 0 on success + */ +extern int mxc_dma_disable(int channel_num); + +/*! + * This starts DMA transfer. Or it restarts DMA on a stopped channel + * previously stopped with mxc_dma_disable(). + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @return returns a negative number on error or 0 on success + */ +extern int mxc_dma_enable(int channel_num); + +#endif diff --git a/arch/arm/plat-mxc/include/mach/dptc.h b/arch/arm/plat-mxc/include/mach/dptc.h new file mode 100644 index 000000000000..ac897bbea2a7 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/dptc.h @@ -0,0 +1,186 @@ + +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ASM_ARCH_MXC_DPTC_H__ +#define __ASM_ARCH_MXC_DPTC_H__ + +#include <mach/dvfs_dptc_struct.h> + +/*! + * DPTC proc file system entry name + */ +#define PROC_NODE_NAME "dptc" + +int __init init_dptc_controller(dvfs_dptc_params_s * params); + +/*! + * This function enables the DPTC module. this function updates the DPTC + * thresholds, updates the PMIC, unmasks the DPTC interrupt and enables + * the DPTC module + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + * + * @return 0 if DPTC module was enabled else returns -EINVAL. + */ +int start_dptc(dvfs_dptc_params_s * params); +/*! + * This function disables the DPTC module. + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + * + * @return 0 if DPTC module was disabled else returns -EINVAL. + */ +int stop_dptc(dvfs_dptc_params_s * params); +/*! + * This function updates the drivers current working point index. This index is + * used for access the current DTPC table entry and it corresponds to the + * current CPU working point measured by the DPTC hardware. + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + * @param new_wp New working point index value to be set. + * + */ +void set_dptc_wp(dvfs_dptc_params_s * params, int new_wp); +/*! + * This function updates the DPTC threshold registers. + * + * @param dvfs_dptc_tables_ptr pointer to the DPTC translation table. + * @param wp current wp value. + * @param freq_index translation table index of the current CPU + * frequency. + * + */ +void update_dptc_thresholds(dvfs_dptc_tables_s * dptc_tables_ptr, + int wp, int freq_index); +/*! + * This function adds a new entry to the DPTC log buffer. + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + * @param dptc_log pointer to the DPTC log buffer structure. + * @param wp value of the working point index written + * to the log buffer. + * @param freq_index value of the frequency index written to + * the log buffer. + * + * @return number of log buffer entries. + * + */ + +void add_dptc_log_entry(dvfs_dptc_params_s * params, + dptc_log_s * dptc_log, int wp, int freq_index); + +/*! + * This function updates the CPU voltage, produced by PMIC, by calling PMIC + * driver functions. + * + * @param dptc_tables_ptr pointer to the DPTC translation table. + * @param wp current wp value. + */ +void set_pmic_voltage(dvfs_dptc_tables_s * dptc_tables_ptr, int wp); + +/*! + * This function enables the DPTC reference circuits. + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + * @param rc_state each high bit specifies which + * reference circuite to enable. + * @return 0 on success, error code on failure + */ +int enable_ref_circuits(dvfs_dptc_params_s * params, unsigned char rc_state); + +/*! + * This function disables the DPTC reference circuits. + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + * @param rc_state each high bit specifies which + * reference circuite to disable + * @return 0 on success, error code on failure + */ +int disable_ref_circuits(dvfs_dptc_params_s * params, unsigned char rc_state); + +/*! + * This function is the DPTC Interrupt handler. + * This function wakes-up the dptc_workqueue_handler function that handles the + * DPTC interrupt. + */ +void dptc_irq(void); + +/*! + * This function updates the drivers current frequency index.This index is + * used for access the current DTPC table entry and it corresponds to the + * current CPU frequency (each CPU frequency has a separate index number + * according to the loaded DPTC table). + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + * @param freq_index New frequency index value to be set. + * + * @return 0 if the frequency index was updated (the new index is a + * valid index and the DPTC module isn't active) else returns + * -EINVAL. + * + */ +int set_dptc_curr_freq(dvfs_dptc_params_s * params, unsigned int freq_index); + +#ifdef CONFIG_MXC_DVFS_SDMA +/* + * DPTC SDMA callback. + * Updates the PMIC voltage + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + */ +void dptc_sdma_callback(dvfs_dptc_params_s * params); +#endif + +/*! + * This function is called to put the DPTC in a low power state. + * + * @param pdev the device structure used to give information on which + * device to suspend (not relevant for DPTC) + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +int mxc_dptc_suspend(struct platform_device *pdev, pm_message_t state); + +/*! + * This function is called to put the DPTC in a low power state. + * + */ +void dptc_suspend(void); + +/*! + * This function is called to resume the DPTC from a low power state. + * + * @param pdev the device structure used to give information on which + * device to suspend (not relevant for DPTC) + * + * @return The function always returns 0. + */ +int mxc_dptc_resume(struct platform_device *dev); + +/*! + * This function is called to resume the DPTC from a low power state. + * + */ +void dptc_resume(void); + +/*! + * This function initializes DPTC according to turbo mode status + * + * @param status Turbo mode disable, 1 - turbo mode enabled + * + */ +void dptc_set_turbo_mode(unsigned int status); + +#endif /* __ASM_ARCH_MXC_DPTC_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/dvfs_dptc_struct.h b/arch/arm/plat-mxc/include/mach/dvfs_dptc_struct.h new file mode 100644 index 000000000000..006bcf293d63 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/dvfs_dptc_struct.h @@ -0,0 +1,169 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file arch-mxc/dvfs_dptc_struct.h + * + * @brief MXC dvfs & dptc structure definitions file. + * + * @ingroup PM_MX27 PM_MX31 PM_MXC91321 PM_MXC91311 + */ +#ifndef __ASM_ARCH_MXC_DVFS_DPTC_STRUCT_H__ +#define __ASM_ARCH_MXC_DVFS_DPTC_STRUCT_H__ + +#include <linux/semaphore.h> +#include <mach/pm_api.h> + +/*! + * Number of entries in the DPTC log buffer + */ +#define LOG_ENTRIES 1024 + +/*! + * Log buffer Structure.\n + * This structure records the DPTC changes. \n + * This structure can be read by the user using the proc file system DPTC read entry. + */ +typedef struct { + /*! + * Index to the head of the log buffer + */ + int head; + + /*! + * Index to the tail of the log buffer + */ + int tail; + + /*! + * Mutex to allow access to the log buffer + */ + struct semaphore mutex; + + /*! + * Array of log buffer entries + */ + dptc_log_entry_s entries[LOG_ENTRIES]; +} dptc_log_s; + +/*! + * DPTC driver data structure.\n + * Holds all driver parameters and data structures. + */ +typedef struct { + /*! + * This variable holds the current frequency index + */ + int current_freq_index; + + /*! + * Boolean variable, if TRUE the DPTC module is enabled + * if FALSE the DPTC module is disabled + */ + int dptc_is_active; + + /*! + * Boolean variable, if TRUE turbo mode enable + * if FALSE turbo mode disabled + */ + int turbo_mode_active; + + /*! + * Boolean variable, if TRUE the DVFS module is enabled + * if FALSE the DPTC module is disabled + */ + int dvfs_is_active; + + /*! + * Boolean variable, if TRUE the DPTC module is suspended + */ + int suspended; + + unsigned char rc_state; + + /*! + * Pointer to the DVFS & DPTC translation table + */ + dvfs_dptc_tables_s *dvfs_dptc_tables_ptr; + + /*! + * The DPTC log buffer + */ + dptc_log_s dptc_log_buffer; + + /*! + * The DVFS log buffer + */ + unsigned char *dvfs_log_buffer; + + /*! + * The DVFS log buffer physical address (for SDMA) + */ + dma_addr_t dvfs_log_buffer_phys; + +#ifdef CONFIG_MXC_DVFS_SDMA + /*! + * SDMA channel number + */ + int sdma_channel; + + /*! + * This holds the previous working point + */ + int prev_wp; + + /*! + * Wait entry for predictive DVFS + */ + wait_queue_head_t dvfs_pred_wait; +#endif + + /*! + * This holds the current DVFS mode + */ + unsigned int dvfs_mode; + + /*! + * Log buffer read pointer + */ + unsigned char *read_ptr; + + /* + * Number of characters in log buffer + */ + int chars_in_buffer; +} dvfs_dptc_params_s; + +/*! + * This struct contains the array with values of supported frequencies in Hz + */ +typedef struct { + /* + * Number of supported states + */ + unsigned int num_of_states; + /*! + * Array of frequencies + */ + unsigned int *freqs; +} dvfs_states_table; + +/* + * if not defined define TREU and FALSE values. + */ +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif /* TRUE */ + +#endif diff --git a/arch/arm/plat-mxc/include/mach/entry-macro.S b/arch/arm/plat-mxc/include/mach/entry-macro.S index 5f01d60da845..92f553baf447 100644 --- a/arch/arm/plat-mxc/include/mach/entry-macro.S +++ b/arch/arm/plat-mxc/include/mach/entry-macro.S @@ -1,6 +1,6 @@ /* * Copyright (C) 2007 Lennert Buytenhek <buytenh@wantstofly.org> - * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. */ /* @@ -18,10 +18,14 @@ .endm .macro get_irqnr_preamble, base, tmp +#ifdef CONFIG_MXC_TZIC + ldr \base, =TZIC_IO_ADDRESS(TZIC_BASE_ADDR) +#else ldr \base, =AVIC_IO_ADDRESS(AVIC_BASE_ADDR) #ifdef CONFIG_MXC_IRQ_PRIOR ldr r4, [\base, #AVIC_NIMASK] #endif +#endif .endm .macro arch_ret_to_user, tmp1, tmp2 @@ -31,6 +35,32 @@ @ and returns its number in irqnr @ and returns if an interrupt occured in irqstat .macro get_irqnr_and_base, irqnr, irqstat, base, tmp +#ifdef CONFIG_MXC_TZIC + @ Load offset & priority of the highest priority + @ interrupt pending. + @ 0xD80 is HIPND0 register + ldr \irqnr, =0 + ldr \irqstat, =0x0D80 +1000: + ldr \tmp, [\irqstat, \base] + cmp \tmp, #0 + bne 1001f + addeq \irqnr, \irqnr, #32 + addeq \irqstat, \irqstat, #4 + cmp \irqnr, #128 + blo 1000b + b 2001f +1001: ldr \irqstat, =1 +1002: tst \tmp, \irqstat + bne 2002f + movs \tmp, \tmp, lsr #1 + addne \irqnr, \irqnr, #1 + bne 1002b +2001: + ldr \irqnr, =0 +2002: + movs \irqnr, \irqnr +#else @ Load offset & priority of the highest priority @ interrupt pending from AVIC_NIVECSR ldr \irqstat, [\base, #0x40] @@ -44,6 +74,7 @@ strne \tmp, [\base, #AVIC_NIMASK] streq r4, [\base, #AVIC_NIMASK] #endif +#endif .endm @ irq priority table (not used) diff --git a/arch/arm/plat-mxc/include/mach/fsl_usb.h b/arch/arm/plat-mxc/include/mach/fsl_usb.h new file mode 100644 index 000000000000..2149d38c91c5 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/fsl_usb.h @@ -0,0 +1,86 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * USB Host side, platform-specific functionality. + */ + +#include <linux/usb/fsl_xcvr.h> +#include <mach/arc_otg.h> + +static void fsl_setup_phy(struct ehci_hcd *ehci, + enum fsl_usb2_phy_modes phy_mode, + int port_offset); + +static inline void fsl_platform_usb_setup(struct ehci_hcd *ehci) +{ + struct fsl_usb2_platform_data *pdata; + + pdata = ehci_to_hcd(ehci)->self.controller->platform_data; + fsl_setup_phy(ehci, pdata->phy_mode, 0); +} + +static inline void fsl_platform_set_host_mode(struct usb_hcd *hcd) +{ + unsigned int temp; + struct fsl_usb2_platform_data *pdata; + + pdata = hcd->self.controller->platform_data; + + if (pdata->xcvr_ops && pdata->xcvr_ops->set_host) + pdata->xcvr_ops->set_host(); + + /* set host mode */ + temp = readl(hcd->regs + 0x1a8); + writel(temp | USBMODE_CM_HOST, hcd->regs + 0x1a8); +} + +/* Needed for enable PP and i2c/serial transceivers */ +static inline void +fsl_platform_set_vbus_power(struct fsl_usb2_platform_data *pdata, int on) +{ + u32 temp; + + /* HCSPARAMS */ + temp = readl(pdata->regs + 0x104); + /* Port Power Control */ + if (temp & HCSPARAMS_PPC) { + temp = readl(pdata->regs + FSL_SOC_USB_PORTSC1); + writel(temp | PORT_POWER, pdata->regs + FSL_SOC_USB_PORTSC1); + } + + if (pdata->xcvr_ops && pdata->xcvr_ops->set_vbus_power) + pdata->xcvr_ops->set_vbus_power(pdata->xcvr_ops, pdata, on); +} + +/* Set USB AHB burst length for host */ +static inline void fsl_platform_set_ahb_burst(struct usb_hcd *hcd) +{ + struct fsl_usb2_platform_data *pdata; + unsigned int temp; + + pdata = hcd->self.controller->platform_data; + if (pdata->change_ahb_burst) { + temp = readl(hcd->regs + FSL_SOC_USB_SBUSCFG); + writel((temp & (~(0x7))) | pdata->ahb_burst_mode, + hcd->regs + FSL_SOC_USB_SBUSCFG); + } + + /* Increase TX fifo threshold for USB+ATA for i.mx35 2.0 */ + if (cpu_is_mx35_rev(CHIP_REV_2_0) >= 1) { + temp = readl(hcd->regs + FSL_SOC_USB_TXFILLTUNING); + /* Change TX FIFO threshold to be 0x20 */ + writel((temp & (~(0x3f << 16))) | (0x20 << 16), + hcd->regs + FSL_SOC_USB_TXFILLTUNING); + } +} diff --git a/arch/arm/plat-mxc/include/mach/fsl_usb_gadget.h b/arch/arm/plat-mxc/include/mach/fsl_usb_gadget.h new file mode 100644 index 000000000000..d3c581e70765 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/fsl_usb_gadget.h @@ -0,0 +1,40 @@ +/* + * Copyright 2005-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * USB Gadget side, platform-specific functionality. + */ + +#include <linux/usb/fsl_xcvr.h> + +/* Needed for i2c/serial transceivers */ +static inline void +fsl_platform_set_device_mode(struct fsl_usb2_platform_data *pdata) +{ + if (pdata->xcvr_ops && pdata->xcvr_ops->set_device) + pdata->xcvr_ops->set_device(); +} + +static inline void +fsl_platform_pullup_enable(struct fsl_usb2_platform_data *pdata) +{ + if (pdata->xcvr_ops && pdata->xcvr_ops->pullup) + pdata->xcvr_ops->pullup(1); +} + +static inline void +fsl_platform_pullup_disable(struct fsl_usb2_platform_data *pdata) +{ + if (pdata->xcvr_ops && pdata->xcvr_ops->pullup) + pdata->xcvr_ops->pullup(0); +} diff --git a/arch/arm/plat-mxc/include/mach/gpio.h b/arch/arm/plat-mxc/include/mach/gpio.h index 894d2f87c856..9f382e64ccfd 100644 --- a/arch/arm/plat-mxc/include/mach/gpio.h +++ b/arch/arm/plat-mxc/include/mach/gpio.h @@ -33,9 +33,12 @@ struct mxc_gpio_port { void __iomem *base; int irq; + int irq_high; int virtual_irq_start; struct gpio_chip chip; u32 both_edges; + u32 suspend_wakeup; + u32 saved_wakeup; }; int mxc_gpio_init(struct mxc_gpio_port*, int); diff --git a/arch/arm/plat-mxc/include/mach/hardware.h b/arch/arm/plat-mxc/include/mach/hardware.h index 42e4ee37ca1f..b110686e3651 100644 --- a/arch/arm/plat-mxc/include/mach/hardware.h +++ b/arch/arm/plat-mxc/include/mach/hardware.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright 2008 Juergen Beisert, kernel@pengutronix.de * * This program is free software; you can redistribute it and/or @@ -22,9 +22,33 @@ #include <asm/sizes.h> +/* + * --------------------------------------------------------------------------- + * Processor specific defines + * --------------------------------------------------------------------------- + */ +#define CHIP_REV_1_0 0x10 +#define CHIP_REV_1_1 0x11 +#define CHIP_REV_1_2 0x12 +#define CHIP_REV_1_3 0x13 +#define CHIP_REV_2_0 0x20 +#define CHIP_REV_2_1 0x21 +#define CHIP_REV_2_2 0x22 +#define CHIP_REV_2_3 0x23 +#define CHIP_REV_3_0 0x30 +#define CHIP_REV_3_1 0x31 +#define CHIP_REV_3_2 0x32 + +#define BOARD_REV_1 0x000 +#define BOARD_REV_2 0x100 + #ifdef CONFIG_ARCH_MX3 #include <mach/mx3x.h> #include <mach/mx31.h> +#endif + +#ifdef CONFIG_ARCH_MX35 +#include <mach/mx3x.h> #include <mach/mx35.h> #endif @@ -42,6 +66,44 @@ # include <mach/mx1.h> #endif +#ifdef CONFIG_ARCH_MX37 +#include <mach/mx37.h> +#endif + +#ifdef CONFIG_ARCH_MX51 +#include <mach/mx51.h> +#endif + +#ifdef CONFIG_ARCH_MX25 +#include <mach/mx25.h> +#endif + +#ifndef __ASSEMBLY__ +extern unsigned int system_rev; +#define board_is_rev(rev) (((system_rev & 0x0F00) == rev) ? 1 : 0) +#endif + #include <mach/mxc.h> +/*! + * Register an interrupt handler for the SMN as well as the SCC. In some + * implementations, the SMN is not connected at all, and in others, it is + * on the same interrupt line as the SCM. Comment this line out accordingly + */ +#define USE_SMN_INTERRUPT + +/*! + * This option is used to set or clear the RXDMUXSEL bit in control reg 3. + * Certain platforms need this bit to be set in order to receive Irda data. + */ +#define MXC_UART_IR_RXDMUX 0x0004 +/*! + * This option is used to set or clear the RXDMUXSEL bit in control reg 3. + * Certain platforms need this bit to be set in order to receive UART data. + */ +#define MXC_UART_RXDMUX 0x0004 + +#ifndef MXC_INT_FORCE +#define MXC_INT_FORCE -1 +#endif #endif /* __ASM_ARCH_MXC_HARDWARE_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/hw_events.h b/arch/arm/plat-mxc/include/mach/hw_events.h new file mode 100644 index 000000000000..f0aa3ad7a266 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/hw_events.h @@ -0,0 +1,65 @@ +/* + * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * hw_events.h + * include the headset/cvbs interrupt detect + */ + +#ifndef HW_EVENT_H +#define HW_EVENT_H + +#define HW_EVENT_GROUP 2 +#define HWE_DEF_PRIORITY 1 +#define HWE_HIGH_PRIORITY 0 + +typedef enum { + + HWE_PHONEJACK_PLUG = 0, + HWE_BAT_CHARGER_PLUG, + HWE_BAT_CHARGER_OVERVOLTAGE, + HWE_BAT_BATTERY_LOW, + HWE_BAT_POWER_FAILED, + HWE_BAT_CHARGER_FULL, + HWE_POWER_KEY, +} HW_EVENT_T; + +typedef enum { + + PJT_NONE = 0, + PJT_CVBS, + PJT_HEADSET, +} PHONEJACK_TYPE; + +typedef enum { + + PWRK_UNPRESS = 0, + PWRK_PRESS, +} POWERKEY_TYPE; + +typedef enum { + + UNPLUG = 0, + PLUGGED, +} PLUG_TYPE; + +struct mxc_hw_event { + unsigned int event; + int args; +}; + +#ifdef __KERNEL__ +extern int hw_event_send(int priority, struct mxc_hw_event *new_event); +#endif + +#endif /* HW_EVENT_H */ diff --git a/arch/arm/plat-mxc/include/mach/io.h b/arch/arm/plat-mxc/include/mach/io.h index b4f2de769466..311bb6337b64 100644 --- a/arch/arm/plat-mxc/include/mach/io.h +++ b/arch/arm/plat-mxc/include/mach/io.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. */ /* @@ -14,29 +14,33 @@ /* Allow IO space to be anywhere in the memory */ #define IO_SPACE_LIMIT 0xffffffff -#ifdef CONFIG_ARCH_MX3 -#define __arch_ioremap __mx3_ioremap -#define __arch_iounmap __iounmap +extern void __iomem *__mxc_ioremap(unsigned long cookie, size_t size, + unsigned int mtype); -static inline void __iomem * -__mx3_ioremap(unsigned long phys_addr, size_t size, unsigned int mtype) -{ - if (mtype == MT_DEVICE) { - /* Access all peripherals below 0x80000000 as nonshared device - * but leave l2cc alone. - */ - if ((phys_addr < 0x80000000) && ((phys_addr < 0x30000000) || - (phys_addr >= 0x30000000 + SZ_1M))) - mtype = MT_DEVICE_NONSHARED; - } - - return __arm_ioremap(phys_addr, size, mtype); -} -#endif +#define __arch_ioremap(a, s, f) __mxc_ioremap(a, s, f) +#define __arch_iounmap __iounmap /* io address mapping macro */ #define __io(a) __typesafe_io(a) #define __mem_pci(a) (a) +/*! + * This function is called to read a CPLD register over CSPI. + * + * @param offset number of the cpld register to be read + * + * @return Returns 0 on success -1 on failure. + */ +unsigned int __weak spi_cpld_read(unsigned int offset); + +/*! + * This function is called to write to a CPLD register over CSPI. + * + * @param offset number of the cpld register to be written + * @param reg_val value to be written + * + * @return Returns 0 on success -1 on failure. + */ +unsigned int __weak spi_cpld_write(unsigned int offset, unsigned int reg_val); #endif diff --git a/arch/arm/plat-mxc/include/mach/irqs.h b/arch/arm/plat-mxc/include/mach/irqs.h index 518a36504b88..59a9c4548634 100644 --- a/arch/arm/plat-mxc/include/mach/irqs.h +++ b/arch/arm/plat-mxc/include/mach/irqs.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. */ /* @@ -12,9 +12,9 @@ #define __ASM_ARCH_MXC_IRQS_H__ /* - * So far all i.MX SoCs have 64 internal interrupts + * So far all i.MX SoCs have 128 internal interrupts */ -#define MXC_INTERNAL_IRQS 64 +#define MXC_INTERNAL_IRQS 128 #define MXC_GPIO_IRQ_START MXC_INTERNAL_IRQS @@ -22,8 +22,14 @@ #define MXC_GPIO_IRQS (32 * 4) #elif defined CONFIG_ARCH_MX2 #define MXC_GPIO_IRQS (32 * 6) -#elif defined CONFIG_ARCH_MX3 +#elif defined CONFIG_ARCH_MX25 +#define MXC_GPIO_IRQS (32 * 4) +#elif defined CONFIG_ARCH_MX3 || defined CONFIG_ARCH_MX35 +#define MXC_GPIO_IRQS (32 * 3) +#elif defined CONFIG_ARCH_MX37 #define MXC_GPIO_IRQS (32 * 3) +#elif defined CONFIG_ARCH_MX51 +#define MXC_GPIO_IRQS (32 * 4) #endif /* @@ -33,7 +39,14 @@ * within sensible limits. */ #define MXC_BOARD_IRQ_START (MXC_INTERNAL_IRQS + MXC_GPIO_IRQS) +#ifdef CONFIG_MXC_PSEUDO_IRQS +#define MXC_PSEUDO_IO_BASE (MXC_BOARD_IRQ_START + 16) +#define MXC_MAX_PSEUDO_IO_LINES 16 +#define MXC_BOARD_IRQS 32 +#else #define MXC_BOARD_IRQS 16 +#define MXC_MAX_PSEUDO_IO_LINES 0 +#endif #define MXC_IPU_IRQ_START (MXC_BOARD_IRQ_START + MXC_BOARD_IRQS) @@ -52,4 +65,17 @@ extern int imx_irq_set_priority(unsigned char irq, unsigned char prio); /* switch betwean IRQ and FIQ */ extern int mxc_set_irq_fiq(unsigned int irq, unsigned int type); +#define MXC_IRQ_TO_EXPIO(irq) ((irq) - MXC_BOARD_IRQ_START) + +/* + * This function is used to get the AVIC Lo and Hi interrupts + * that are enabled as wake up sources to wake up the core from suspend + */ +void mxc_get_wake_irq(u32 * wake_src[]); + +/* Define interrupt number for OProfile */ +#if defined CONFIG_ARCH_MX51 +#define MXC_INT_PMU 77 +#endif + #endif /* __ASM_ARCH_MXC_IRQS_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/memory.h b/arch/arm/plat-mxc/include/mach/memory.h index 6065e00176ed..c898f884a371 100644 --- a/arch/arm/plat-mxc/include/mach/memory.h +++ b/arch/arm/plat-mxc/include/mach/memory.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. */ /* @@ -11,6 +11,9 @@ #ifndef __ASM_ARCH_MXC_MEMORY_H__ #define __ASM_ARCH_MXC_MEMORY_H__ +#include <asm/page.h> +#include <asm/sizes.h> + #if defined CONFIG_ARCH_MX1 #define PHYS_OFFSET UL(0x08000000) #elif defined CONFIG_ARCH_MX2 @@ -24,6 +27,19 @@ #define PHYS_OFFSET UL(0x80000000) #endif +#ifdef CONFIG_ARCH_MX51 +#define PHYS_OFFSET UL(0x90000000) +#endif + +#ifdef CONFIG_ARCH_MX37 +#define PHYS_OFFSET UL(0x40000000) +#endif + +#ifndef PHYS_OFFSET +#define PHYS_OFFSET UL(0x80000000) +#endif + +/* Size of contiguous memory for DMA and other h/w blocks */ #if defined(CONFIG_MX1_VIDEO) /* * Increase size of DMA-consistent memory region. @@ -40,4 +56,35 @@ #define CONSISTENT_DMA_SIZE SZ_8M #endif /* CONFIG_MX3_VIDEO */ +#ifdef CONFIG_ARCH_MX51 +#define CONSISTENT_DMA_SIZE (64 * SZ_1M) +#else +#define CONSISTENT_DMA_SIZE (32 * SZ_1M) +#endif + +#ifndef __ASSEMBLY__ + +#ifdef CONFIG_DMA_ZONE_SIZE +#define MXC_DMA_ZONE_SIZE ((CONFIG_DMA_ZONE_SIZE * SZ_1M) >> PAGE_SHIFT) +#else +#define MXC_DMA_ZONE_SIZE ((12 * SZ_1M) >> PAGE_SHIFT) +#endif + +static inline void __arch_adjust_zones(int node, unsigned long *zone_size, + unsigned long *zhole_size) +{ + if (node != 0) + return; + /* Create separate zone to reserve memory for DMA */ + zone_size[1] = zone_size[0] - MXC_DMA_ZONE_SIZE; + zone_size[0] = MXC_DMA_ZONE_SIZE; + zhole_size[1] = zhole_size[0]; + zhole_size[0] = 0; +} + +#define arch_adjust_zones(node, size, holes) \ + __arch_adjust_zones(node, size, holes) + +#endif + #endif /* __ASM_ARCH_MXC_MEMORY_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/mmc.h b/arch/arm/plat-mxc/include/mach/mmc.h index de2128dada5c..80cc61c175ea 100644 --- a/arch/arm/plat-mxc/include/mach/mmc.h +++ b/arch/arm/plat-mxc/include/mach/mmc.h @@ -33,4 +33,21 @@ struct imxmmc_platform_data { void (*setpower)(struct device *, unsigned int vdd); }; +struct mxc_mmc_platform_data { + unsigned int ocr_mask; /* available voltages */ + unsigned int vendor_ver; + unsigned int caps; + unsigned int min_clk; + unsigned int max_clk; + unsigned int clk_flg; /* 1 clock enable, 0 not */ + unsigned int reserved:16; + unsigned int card_fixed:1; + unsigned int card_inserted_state:1; +// u32 (*translate_vdd)(struct device *, unsigned int); + unsigned int (*status) (struct device *); + int (*wp_status) (struct device *); + char *power_mmc; + char *clock_mmc; +}; + #endif diff --git a/arch/arm/plat-mxc/include/mach/mtd-xip.h b/arch/arm/plat-mxc/include/mach/mtd-xip.h index 1ab1bba5688d..0e74f19b1eae 100644 --- a/arch/arm/plat-mxc/include/mach/mtd-xip.h +++ b/arch/arm/plat-mxc/include/mach/mtd-xip.h @@ -4,6 +4,7 @@ * Do not include this file directly. It's included from linux/mtd/xip.h * * Copyright (C) 2008 Darius Augulis <augulis.darius@gmail.com>, Teltonika, Inc. + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. * * 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,12 +12,14 @@ * */ +#include <linux/clocksource.h> +#include <mach/hardware.h> +#include <mach/system.h> #include <mach/mxc_timer.h> #ifndef __ARCH_IMX_MTD_XIP_H__ #define __ARCH_IMX_MTD_XIP_H__ -#ifdef CONFIG_ARCH_MX1 /* AITC registers */ #define AITC_BASE IO_ADDRESS(AVIC_BASE_ADDR) #define NIPNDH (AITC_BASE + 0x58) @@ -26,9 +29,34 @@ /* MTD macros */ #define xip_irqpending() ((__raw_readl(INTENABLEH) & __raw_readl(NIPNDH)) \ || (__raw_readl(INTENABLEL) & __raw_readl(NIPNDL))) +#ifdef CONFIG_ARCH_MX1 #define xip_currtime() (__raw_readl(TIMER_BASE + MXC_TCN)) #define xip_elapsed_since(x) (signed)((__raw_readl(TIMER_BASE + MXC_TCN) - (x)) / 96) #define xip_cpu_idle() asm volatile ("mcr p15, 0, %0, c7, c0, 4" :: "r" (0)) +#else + +extern struct clocksource *mtd_xip_clksrc; + +#define xip_currtime() (unsigned long)clocksource_read(mtd_xip_clksrc) + +#if CLOCK_TICK_RATE > 1000000 +#define NUMERATOR 1 +#define DENOMINATOR (CLOCK_TICK_RATE/1000000 + 1) +#else +#define NUMERATOR (1000000/CLOCK_TICK_RATE) +#define DENOMINATOR 1 +#endif + +static inline unsigned long xip_elapsed_since(unsigned long x) +{ + return (((xip_currtime() - x) * NUMERATOR) / DENOMINATOR); +} + +/* + * Wait For Interrupt command for XIP kernel to put CPU in Idle mode + */ +#define xip_cpu_idle() arch_idle() + #endif /* CONFIG_ARCH_MX1 */ #endif /* __ARCH_IMX_MTD_XIP_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/mx25.h b/arch/arm/plat-mxc/include/mach/mx25.h new file mode 100644 index 000000000000..6b1622906c20 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/mx25.h @@ -0,0 +1,462 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file arch-mxc/mx25.h + * @brief This file contains register definitions. + * + * @ingroup MSL_MX25 + */ + +#ifndef __ASM_ARCH_MXC_MX25_H__ +#define __ASM_ARCH_MXC_MX25_H__ + +#ifndef __ASM_ARCH_MXC_HARDWARE_H__ +#error "Do not include directly." +#endif + +/*! + * Define this option to specify we are using the newer SDMA module. + */ +#define MXC_SDMA_V2 + +/* + * MX25 memory map: + * + * Virt Phys Size What + * --------------------------------------------------------------------------- + * FC000000 43F00000 1M AIPS 1 + * FC100000 50000000 1M SPBA + * FC200000 53F00000 1M AIPS 2 + * FC300000 60000000 1M ROMPATCH (128M) + * FC400000 68000000 1M ASIC (128M) + * FC500000 78000000 128K FBC RAM (IRAM) + * 80000000 256M SDRAM0 + * 90000000 256M SDRAM1 + * A0000000 128M CS0 Flash + * A8000000 128M CS1 Flash + * B0000000 32M CS2 SRAM + * B2000000 32M CS3 + * B4000000 32M CS4 + * B6000000 32M CS5 + * FC520000 B8000000 64K SDRAM, WEIM, M3IF, EMI controllers + * FC530000 BB000000 8K NFC + */ + +/* + * IRAM + */ +#define IRAM_BASE_ADDR 0x78000000 /* internal ram */ +#define IRAM_BASE_ADDR_VIRT 0xFC500000 +#define IRAM_SIZE SZ_128K + +#ifndef CONFIG_SDMA_IRAM +#define CONFIG_SDMA_IRAM_SIZE 0 +#endif +#ifdef CONFIG_SND_MXC_SOC_IRAM +#define SND_RAM_SIZE 0x10000 +#else +#define SND_RAM_SIZE 0 +#endif + +#define SND_RAM_BASE_ADDR (IRAM_BASE_ADDR + CONFIG_SDMA_IRAM_SIZE) + +#define USB_IRAM_BASE_ADDR (SND_RAM_BASE_ADDR + SND_RAM_SIZE) +#ifdef CONFIG_USB_STATIC_IRAM_PPH +#define USB_IRAM_SIZE (2*SZ_8K) +#else +#define USB_IRAM_SIZE 0 +#endif + +/* + * AIPS 1 + */ +#define AIPS1_BASE_ADDR 0x43F00000 +#define AIPS1_BASE_ADDR_VIRT 0xFC000000 +#define AIPS1_SIZE SZ_1M + +#define MAX_BASE_ADDR (AIPS1_BASE_ADDR + 0x00004000) +#define CLKCTL_BASE_ADDR (AIPS1_BASE_ADDR + 0x00008000) +#define ETB_SLOT4_BASE_ADDR (AIPS1_BASE_ADDR + 0x0000C000) +#define ETB_SLOT5_BASE_ADDR (AIPS1_BASE_ADDR + 0x00010000) +#define AAPE_BASE_ADDR (AIPS1_BASE_ADDR + 0x00014000) +#define I2C_BASE_ADDR (AIPS1_BASE_ADDR + 0x00080000) +#define I2C3_BASE_ADDR (AIPS1_BASE_ADDR + 0x00084000) +#define CAN1_BASE_ADDR (AIPS1_BASE_ADDR + 0x00088000) +#define CAN3_BASE_ADDR (AIPS1_BASE_ADDR + 0x0008C000) +#define UART1_BASE_ADDR (AIPS1_BASE_ADDR + 0x00090000) +#define UART2_BASE_ADDR (AIPS1_BASE_ADDR + 0x00094000) +#define I2C2_BASE_ADDR (AIPS1_BASE_ADDR + 0x00098000) +#define OWIRE_BASE_ADDR (AIPS1_BASE_ADDR + 0x0009C000) +#define ATA_BASE_ADDR (AIPS1_BASE_ADDR + 0x000A0000) +#define CSPI1_BASE_ADDR (AIPS1_BASE_ADDR + 0x000A4000) +#define KPP_BASE_ADDR (AIPS1_BASE_ADDR + 0x000A8000) +#define IOMUXC_BASE_ADDR (AIPS1_BASE_ADDR + 0x000AC000) +#define AUDMUX_BASE_ADDR (AIPS1_BASE_ADDR + 0x000B0000) +#define ECT_A_BASE_ADDR (AIPS1_BASE_ADDR + 0x000B8000) +#define ECT_B_BASE_ADDR (AIPS1_BASE_ADDR + 0x000BC000) + +/* + * SPBA global module enabled #0 + */ +#define SPBA0_BASE_ADDR 0x50000000 +#define SPBA0_BASE_ADDR_VIRT 0xFC100000 +#define SPBA0_SIZE SZ_1M + +#define CSPI3_BASE_ADDR (SPBA0_BASE_ADDR + 0x00004000) +#define UART4_BASE_ADDR (SPBA0_BASE_ADDR + 0x00008000) +#define UART3_BASE_ADDR (SPBA0_BASE_ADDR + 0x0000C000) +#define CSPI2_BASE_ADDR (SPBA0_BASE_ADDR + 0x00010000) +#define SSI2_BASE_ADDR (SPBA0_BASE_ADDR + 0x00014000) +#define ESAI_BASE_ADDR (SPBA0_BASE_ADDR + 0x00018000) +#define ATA_DMA_BASE_ADDR (SPBA0_BASE_ADDR + 0x00020000) +#define SIM1_BASE_ADDR (SPBA0_BASE_ADDR + 0x00024000) +#define SIM2_BASE_ADDR (SPBA0_BASE_ADDR + 0x00028000) +#define UART5_BASE_ADDR (SPBA0_BASE_ADDR + 0x0002C000) +#define TSC_BASE_ADDR (SPBA0_BASE_ADDR + 0x00030000) +#define SSI1_BASE_ADDR (SPBA0_BASE_ADDR + 0x00034000) +#define FEC_BASE_ADDR (SPBA0_BASE_ADDR + 0x00038000) +#define SPBA_CTRL_BASE_ADDR (SPBA0_BASE_ADDR + 0x0003C000) + +/*! + * defines for SPBA modules + */ +#define SPBA_CSPI3 (0x1 << 2) +#define SPBA_UART4 (0x2 << 2) +#define SPBA_UART3 (0x3 << 2) +#define SPBA_CSPI2 (0x4 << 2) +#define SPBA_SSI2 (0x5 << 2) +#define SPBA_ESAI (0x6 << 2) +#define SPBA_ATA (0x8 << 2) +#define SPBA_SIM1 (0x9 << 2) +#define SPBA_SIM2 (0xA << 2) +#define SPBA_UART5 (0xB << 2) +#define SPBA_ANALOG (0xC << 2) +#define SPBA_SSI1 (0xD << 2) +#define SPBA_FEC (0xE << 2) + +/*! + * Defines for modules using static and dynamic DMA channels + */ +#define MXC_DMA_CHANNEL_IRAM 30 +#define MXC_DMA_CHANNEL_UART1_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART1_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART2_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART2_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART3_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART3_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART4_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART4_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART5_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART5_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MMC1 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SSI1_RX MXC_DMA_DYNAMIC_CHANNEL +#ifdef CONFIG_SDMA_IRAM +#define MXC_DMA_CHANNEL_SSI1_TX (MXC_DMA_CHANNEL_IRAM + 1) +#else +#define MXC_DMA_CHANNEL_SSI1_TX MXC_DMA_DYNAMIC_CHANNEL +#endif +#define MXC_DMA_CHANNEL_SSI2_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SSI2_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI1_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI1_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI2_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI2_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI3_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI3_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ATA_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ATA_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MEMORY MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ESAI_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ESAI_TX MXC_DMA_DYNAMIC_CHANNEL + +/* + * AIPS 2 + */ +#define AIPS2_BASE_ADDR 0x53F00000 +#define AIPS2_BASE_ADDR_VIRT 0xFC200000 +#define AIPS2_SIZE SZ_1M + +#define CCM_BASE_ADDR (AIPS2_BASE_ADDR + 0x00080000) +#define GPT4_BASE_ADDR (AIPS2_BASE_ADDR + 0x00084000) +#define GPT3_BASE_ADDR (AIPS2_BASE_ADDR + 0x00088000) +#define GPT2_BASE_ADDR (AIPS2_BASE_ADDR + 0x0008C000) +#define GPT1_BASE_ADDR (AIPS2_BASE_ADDR + 0x00090000) +#define EPIT1_BASE_ADDR (AIPS2_BASE_ADDR + 0x00094000) +#define EPIT2_BASE_ADDR (AIPS2_BASE_ADDR + 0x00098000) +#define GPIO4_BASE_ADDR (AIPS2_BASE_ADDR + 0x0009C000) +#define PWM2_BASE_ADDR (AIPS2_BASE_ADDR + 0x000A0000) +#define GPIO3_BASE_ADDR (AIPS2_BASE_ADDR + 0x000AD000) +#define PWM3_BASE_ADDR (AIPS2_BASE_ADDR + 0x000A8000) +#define SCC_BASE_ADDR (AIPS2_BASE_ADDR + 0x000AC000) +#define RNGB_BASE_ADDR (AIPS2_BASE_ADDR + 0x000B0000) +#define MMC_SDHC1_BASE_ADDR (AIPS2_BASE_ADDR + 0x000B4000) +#define MMC_SDHC2_BASE_ADDR (AIPS2_BASE_ADDR + 0x000B8000) +#define LCDC_BASE_ADDR (AIPS2_BASE_ADDR + 0x000BC000) +#define SLCDC_BASE_ADDR (AIPS2_BASE_ADDR + 0x000C0000) +#define PWM4_BASE_ADDR (AIPS2_BASE_ADDR + 0x000C8000) +#define GPIO1_BASE_ADDR (AIPS2_BASE_ADDR + 0x000CC000) +#define GPIO2_BASE_ADDR (AIPS2_BASE_ADDR + 0x000D0000) +#define SDMA_BASE_ADDR (AIPS2_BASE_ADDR + 0x000D4000) +#define WDOG1_BASE_ADDR (AIPS2_BASE_ADDR + 0x000DC000) +#define PWM1_BASE_ADDR (AIPS2_BASE_ADDR + 0x000E0000) +#define RTIC_BASE_ADDR (AIPS2_BASE_ADDR + 0x000EC000) +#define IIM_BASE_ADDR (AIPS2_BASE_ADDR + 0x000F0000) +#define USBOTG_BASE_ADDR (AIPS2_BASE_ADDR + 0x000F4000) +#define OTG_BASE_ADDR USBOTG_BASE_ADDR +#define CSI_BASE_ADDR (AIPS2_BASE_ADDR + 0x000F8000) +#define DRYICE_BASE_ADDR (AIPS2_BASE_ADDR + 0x000FC000) +#define SRTC_BASE_ADDR (DRYICE_BASE_ADDR) + +/* + * ROMP and ASIC + */ +#define ROMP_BASE_ADDR 0x60000000 +#define ROMP_BASE_ADDR_VIRT 0xFC300000 +#define ROMP_SIZE SZ_1M + +#define ASIC_BASE_ADDR 0x68000000 +#define ASIC_BASE_ADDR_VIRT 0xFC400000 +#define ASIC_SIZE SZ_1M +#define AVIC_BASE_ADDR ASIC_BASE_ADDR +#define AVIC_BASE_ADDR_VIRT ASIC_BASE_ADDR_VIRT +#define AVIC_SIZE ASIC_SIZE + +/* + * SDRAM, WEIM, M3IF, EMI controllers + */ +#define X_MEMC_BASE_ADDR 0xB8000000 +#define X_MEMC_BASE_ADDR_VIRT 0xFC520000 +#define X_MEMC_SIZE SZ_64K + +#define SDRAMC_BASE_ADDR (X_MEMC_BASE_ADDR + 0x1000) +#define WEIM_BASE_ADDR (X_MEMC_BASE_ADDR + 0x2000) +#define M3IF_BASE_ADDR (X_MEMC_BASE_ADDR + 0x3000) +#define EMI_CTL_BASE_ADDR (X_MEMC_BASE_ADDR + 0x4000) + +/* + * NFC controller + */ +#define NFC_BASE_ADDR 0xBB000000 +#define NFC_BASE_ADDR_VIRT 0xFC530000 +#define NFC_SIZE SZ_8K + +/* + * Memory regions and CS + */ +#define CSD0_BASE_ADDR 0x80000000 +#define CSD1_BASE_ADDR 0x90000000 + +#define SDRAM_BASE_ADDR CSD0_BASE_ADDR + +#define CS0_BASE_ADDR 0xA0000000 +#define CS1_BASE_ADDR 0xA8000000 +#define CS2_BASE_ADDR 0xB0000000 +#define CS3_BASE_ADDR 0xB2000000 +#define CS4_BASE_ADDR 0xB4000000 +#define CS4_SIZE SZ_32M +#define CS5_BASE_ADDR 0xB6000000 +#define CS5_SIZE SZ_32M + +/*! + * This macro defines the physical to virtual address mapping for all the + * peripheral modules. It is used by passing in the physical address as x + * and returning the virtual address. If the physical address is not mapped, + * it returns 0xDEADBEEF + */ +#define IO_ADDRESS(x) \ + (void __force __iomem *) \ + (((x >= AIPS1_BASE_ADDR) && (x < (AIPS1_BASE_ADDR + AIPS1_SIZE))) ? AIPS1_IO_ADDRESS(x):\ + ((x >= SPBA0_BASE_ADDR) && (x < (SPBA0_BASE_ADDR + SPBA0_SIZE))) ? SPBA0_IO_ADDRESS(x):\ + ((x >= AIPS2_BASE_ADDR) && (x < (AIPS2_BASE_ADDR + AIPS2_SIZE))) ? AIPS2_IO_ADDRESS(x):\ + ((x >= ROMP_BASE_ADDR) && (x < (ROMP_BASE_ADDR + ROMP_SIZE))) ? ROMP_IO_ADDRESS(x):\ + ((x >= ASIC_BASE_ADDR) && (x < (ASIC_BASE_ADDR + AVIC_SIZE))) ? ASIC_IO_ADDRESS(x):\ + ((x >= IRAM_BASE_ADDR) && (x < (IRAM_BASE_ADDR + IRAM_SIZE))) ? IRAM_IO_ADDRESS(x):\ + ((x >= X_MEMC_BASE_ADDR) && (x < (X_MEMC_BASE_ADDR + X_MEMC_SIZE))) ? X_MEMC_IO_ADDRESS(x):\ + ((x >= NFC_BASE_ADDR) && (x < (NFC_BASE_ADDR + NFC_SIZE))) ? NFC_IO_ADDRESS(x):\ + 0xDEADBEEF) + +/* + * define the address mapping macros: in physical address order + */ + +#define AIPS1_IO_ADDRESS(x) \ + (((x) - AIPS1_BASE_ADDR) + AIPS1_BASE_ADDR_VIRT) + +#define SPBA0_IO_ADDRESS(x) \ + (((x) - SPBA0_BASE_ADDR) + SPBA0_BASE_ADDR_VIRT) + +#define AIPS2_IO_ADDRESS(x) \ + (((x) - AIPS2_BASE_ADDR) + AIPS2_BASE_ADDR_VIRT) + +#define ROMP_IO_ADDRESS(x) \ + (((x) - ROMP_BASE_ADDR) + ROMP_BASE_ADDR_VIRT) + +#define ASIC_IO_ADDRESS(x) \ + (((x) - ASIC_BASE_ADDR) + ASIC_BASE_ADDR_VIRT) + +/* for entry-macro.S */ +#define AVIC_IO_ADDRESS(x) ASIC_IO_ADDRESS(x) + +#define IRAM_IO_ADDRESS(x) \ + (((x) - IRAM_BASE_ADDR) + IRAM_BASE_ADDR_VIRT) + +#define X_MEMC_IO_ADDRESS(x) \ + (((x) - X_MEMC_BASE_ADDR) + X_MEMC_BASE_ADDR_VIRT) + +#define NFC_IO_ADDRESS(x) \ + (((x) - NFC_BASE_ADDR) + NFC_BASE_ADDR_VIRT) + +#define IS_MEM_DEVICE_NONSHARED(x) 0 + +/* + * DMA request assignments + */ +#define DMA_REQ_EXTREQ0 0 +#define DMA_REQ_CCM 1 +#define DMA_REQ_ATA_TX_END 2 +#define DMA_REQ_ATA_TX 3 +#define DMA_REQ_ATA_RX 4 +#define DMA_REQ_CSPI2_RX 6 +#define DMA_REQ_CSPI2_TX 7 +#define DMA_REQ_CSPI1_RX 8 +#define DMA_REQ_CSPI1_TX 9 +#define DMA_REQ_UART3_RX 10 +#define DMA_REQ_UART3_TX 11 +#define DMA_REQ_UART4_RX 12 +#define DMA_REQ_UART4_TX 13 +#define DMA_REQ_EXTREQ1 14 +#define DMA_REQ_EXTREQ2 15 +#define DMA_REQ_UART2_RX 16 +#define DMA_REQ_UART2_TX 17 +#define DMA_REQ_UART1_RX 18 +#define DMA_REQ_UART1_TX 19 +#define DMA_REQ_SSI2_RX1 22 +#define DMA_REQ_SSI2_TX1 23 +#define DMA_REQ_SSI2_RX0 24 +#define DMA_REQ_SSI2_TX0 25 +#define DMA_REQ_SSI1_RX1 26 +#define DMA_REQ_SSI1_TX1 27 +#define DMA_REQ_SSI1_RX0 28 +#define DMA_REQ_SSI1_TX0 29 +#define DMA_REQ_NFC 30 +#define DMA_REQ_ECT 31 +#define DMA_REQ_ESAI_RX 32 +#define DMA_REQ_ESAI_TX 33 +#define DMA_REQ_CSPI3_RX 34 +#define DMA_REQ_CSPI3_TX 35 +#define DMA_REQ_SIM2_RX 36 +#define DMA_REQ_SIM2_TX 37 +#define DMA_REQ_SIM1_RX 38 +#define DMA_REQ_SIM1_TX 39 +#define DMA_REQ_TSC_GCQ 44 +#define DMA_REQ_TSC_TCQ 45 +#define DMA_REQ_UART5_RX 46 +#define DMA_REQ_UART5_TX 47 + +/* + * Interrupt numbers + */ +#define MXC_INT_CSPI3 0 +#define MXC_INT_GPT4 1 +#define MXC_INT_OWIRE 2 +#define MXC_INT_I2C 3 +#define MXC_INT_I2C2 4 +#define MXC_INT_UART4 5 +#define MXC_INT_RTIC 6 +#define MXC_INT_ESAI 7 +#define MXC_INT_SDHC2 8 +#define MXC_INT_SDHC1 9 +#define MXC_INT_I2C3 10 +#define MXC_INT_SSI2 11 +#define MXC_INT_SSI1 12 +#define MXC_INT_CSPI2 13 +#define MXC_INT_CSPI1 14 +#define MXC_INT_ATA 15 +#define MXC_INT_GPIO3 16 +#define MXC_INT_CSI 17 +#define MXC_INT_UART3 18 +#define MXC_INT_IIM 19 +#define MXC_INT_SIM1 20 +#define MXC_INT_SIM2 21 +#define MXC_INT_RNG 22 +#define MXC_INT_GPIO4 23 +#define MXC_INT_KPP 24 +#define MXC_INT_DRYICE_NORM 25 +#define MXC_INT_PWM 26 +#define MXC_INT_EPIT2 27 +#define MXC_INT_EPIT1 28 +#define MXC_INT_GPT3 29 +#define MXC_INT_POWER_FAIL 30 +#define MXC_INT_CRM 31 +#define MXC_INT_UART2 32 +#define MXC_INT_NANDFC 33 +#define MXC_INT_SDMA 34 +#define MXC_INT_USB_HTG 35 +#define MXC_INT_PWM2 36 +#define MXC_INT_USB_OTG 37 +#define MXC_INT_SLCDC 38 +#define MXC_INT_LCDC 39 +#define MXC_INT_UART5 40 +#define MXC_INT_PWM3 41 +#define MXC_INT_PWM4 42 +#define MXC_INT_CAN1 43 +#define MXC_INT_CAN2 44 +#define MXC_INT_UART1 45 +#define MXC_INT_TSC 46 +#define MXC_INT_ECT 48 +#define MXC_INT_SCC_SCM 49 +#define MXC_INT_SCC_SMN 50 +#define MXC_INT_GPIO2 51 +#define MXC_INT_GPIO1 52 +#define MXC_INT_GPT2 53 +#define MXC_INT_GPT1 54 +#define MXC_INT_WDOG 55 +#define MXC_INT_DRYICE_SEC 56 +#define MXC_INT_FEC 57 +#define MXC_INT_EXT_INT5 58 +#define MXC_INT_EXT_INT4 59 +#define MXC_INT_EXT_INT3 60 +#define MXC_INT_EXT_INT2 61 +#define MXC_INT_EXT_INT1 62 +#define MXC_INT_EXT_INT0 63 + +#define MXC_INT_GPT MXC_INT_GPT1 + +/* gpio and gpio based interrupt handling */ +#define GPIO_DR 0x00 +#define GPIO_GDIR 0x04 +#define GPIO_PSR 0x08 +#define GPIO_ICR1 0x0C +#define GPIO_ICR2 0x10 +#define GPIO_IMR 0x14 +#define GPIO_ISR 0x18 +#define GPIO_INT_LOW_LEV 0x0 +#define GPIO_INT_HIGH_LEV 0x1 +#define GPIO_INT_RISE_EDGE 0x2 +#define GPIO_INT_FALL_EDGE 0x3 +#define GPIO_INT_NONE 0x4 + +#define MXC_TIMER_GPT1 1 +#define MXC_TIMER_GPT2 2 +#define MXC_TIMER_GPT3 3 +#define MXC_TIMER_GPT4 4 + +/*! + * NFMS bit in RCSR register for pagesize of nandflash + */ +#define NFMS (*((volatile u32 *)IO_ADDRESS(CCM_BASE_ADDR + 0x28))) +#define NFMS_NF_DWIDTH 14 +#define NFMS_NF_PG_SZ 8 + +#endif /* __ASM_ARCH_MXC_MX25_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/mx2_dma.h b/arch/arm/plat-mxc/include/mach/mx2_dma.h new file mode 100644 index 000000000000..8f5ae658f96b --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/mx2_dma.h @@ -0,0 +1,261 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ASM_ARCH_MXC_MX2_H__ +#define __ASM_ARCH_MXC_MX2_H__ + +/*! + * @defgroup DMA_MX27 DMA driver for i.MX27 + */ + +/*! + *@file arch-mxc/mx2_dma.h + *@brief DMA driver header file + * + * @ingroup DMA_MX27 + * + */ + +#include <mach/hardware.h> +#include <asm/dma.h> +#include <stdarg.h> + +#define MXC_DMA_INTR_0 32 + +#define DMA_DCR 0x000 /* 32bit dma control reg */ +#define DMA_DISR 0x004 /* 32bit dma interrupt status reg */ +#define DMA_DIMR 0x008 /* 32bit dma interrupt mask reg */ +#define DMA_DBTOSR 0x00c /* 32bit dma burst timeout stat reg */ +#define DMA_DRTOSR 0x010 /* 32bit dma req timeout status reg */ +#define DMA_DSESR 0x014 /* 32bit dma transfer err status reg */ +#define DMA_DBOSR 0x018 /* 32bit dma buffer overflow stat reg */ +#define DMA_DBTOCR 0x01c /* 32bit dma burst timeout ctrl reg */ + +#define DMA_WSRA 0x040 /* 32bit dma W-size A reg */ +#define DMA_XSRA 0x044 /* 32bit dma X-size A reg */ +#define DMA_YSRA 0x048 /* 32bit dma Y-size A reg */ +#define DMA_WSRB 0x04C /* 32bit dma W-size B reg */ +#define DMA_XSRB 0x050 /* 32bit dma X-size B reg */ +#define DMA_YSRB 0x054 /* 32bit dma Y-size B reg */ + +#define DMA_CH_BASE(x) (0x080+0x040*(x)) + +#define DMA_SAR(x) (DMA_CH_BASE(x)+0x000) +#define DMA_DAR(x) (DMA_CH_BASE(x)+0x004) +#define DMA_CNTR(x) (DMA_CH_BASE(x)+0x008) +#define DMA_CCR(x) (DMA_CH_BASE(x)+0x00C) /* 32bit dma ch0 control reg */ +#define DMA_RSSR(x) (DMA_CH_BASE(x)+0x010) /* 32bit dma ch0 req source sel reg */ +#define DMA_BLR(x) (DMA_CH_BASE(x)+0x014) /* 32bit dma ch0 burst lenght reg */ +#define DMA_RTOR(x) (DMA_CH_BASE(x)+0x018) /* 32bit dma ch0 req time out reg */ +#define DMA_BUCR(x) (DMA_CH_BASE(x)+0x018) /* 32bit dma ch0 bus utilization reg */ +#define DMA_CCNR(x) (DMA_CH_BASE(x)+0x01C) /* 32bit dma ch0 */ + +#define DMA_TCR 0x480 /*32bit dma test control reg */ +#define DMA_TFIFOA 0x484 /* 32bit dma test fifo A reg */ +#define DMA_TDRR 0x488 /* 32bit dma test request reg */ +#define DMA_TDIPR 0x48c /* 32bit dma test in progress reg */ +#define DMA_TFIFOB 0x490 /* 32bit dma test fifo B reg */ + +/*! + * This defines maximum DMA address + */ +#define MAX_DMA_ADDRESS 0xffffffff + +#define MXC_DMA_CHANNELS 16 +#define MAX_DMA_CHANNELS MXC_DMA_CHANNELS + +#define MX_DMA_CHANNELS MXC_DMA_CHANNELS + +/*!@def DMA_MEM_SIZE_8 DMA access port size, 8 bit*/ +/*!@def DMA_MEM_SIZE_16 DMA access port size, 16 bit*/ +/*!@def DMA_MEM_SIZE_32 DMA access port size, 32 bit*/ +#define DMA_MEM_SIZE_8 0x1 +#define DMA_MEM_SIZE_16 0x2 +#define DMA_MEM_SIZE_32 0x0 + +/*!@def DMA_TYPE_LINEAR DMA transfer type, linear*/ +/*!@def DMA_TYPE_2D DMA transfer type, 2D*/ +/*!@def DMA_TYPE_FIFO DMA transfer type, FIFO*/ +/*!@def DMA_TYPE_EBE DMA transfer type, end-of-burst enable FIFO*/ +#define DMA_TYPE_LINEAR 0x0 +#define DMA_TYPE_2D 0x01 +#define DMA_TYPE_FIFO 0x2 +#define DMA_TYPE_EBE 0x3 + +/*!@def DMA_DONE DMA transfer done*/ +/*!@def DMA_BURST_TIMEOUT DMA transfer timeout error*/ +/*!@def DMA_REQUEST_TIMEOUT DMA transfer request timeout error*/ +/*!@def DMA_TRANSFER_ERROR DMA transfer error*/ +/*!@def DMA_BUFFER_OVERFLOW DMA transfer buffer overflow error*/ +#define DMA_DONE 0x1000 +#define DMA_BURST_TIMEOUT 0x1 +#define DMA_REQUEST_TIMEOUT 0x2 +#define DMA_TRANSFER_ERROR 0x4 +#define DMA_BUFFER_OVERFLOW 0x8 + +/*!@brief DMA control register*/ +typedef struct { + volatile u32 CEN:1; /*!< Dma channel enable */ + volatile u32 FRC:1; /*!< Force a dma cycle bit */ + volatile u32 RPT:1; /*!< Repeat bit */ + volatile u32 REN:1; /*!< Request enable bit */ + volatile u32 SSIZ:2; /*!< Source port size, 2 bit in length */ + volatile u32 DSIZ:2; /*!< Dest port size, 2 bit in length */ + volatile u32 MSEL:1; /*!< 2D memory register set bit */ + volatile u32 MDIR:1; /*!< Transfer direction, inversed or normal */ + volatile u32 SMOD:2; /*!< Source mode, 2 bit in length */ + volatile u32 DMOD:2; /*!< Dest mode, 2 bit in length */ + volatile u32 ACRPT:1; /*!< Auto clear repeat bit */ + volatile u32 Reserver:17; /*!< Reserved bits */ + +} dma_regs_control; + +#define DMA_CTL_CEN 0x1 +#define DMA_CTL_FRC 0x2 +#define DMA_CTL_RPT 0x4 +#define DMA_CTL_REN 0x8 + +#define DMA_CTL_MSEL 0x100 +#define DMA_CTL_MDIR 0x200 +#define DMA_CTL_ACRPT 0x4000 + +#define DMA_CTL_GET_SSIZ(x) (((x)>>4)&0x3) +#define DMA_CTL_GET_DSIZ(x) (((x)>>6)&0x3) +#define DMA_CTL_GET_SMOD(x) (((x)>>10)&0x3) +#define DMA_CTL_GET_DMOD(x) (((x)>>12)&0x3) + +#define DMA_CTL_SET_SSIZ(x,value) do{ \ + (x)&=~(0x3<<4); \ + (x)|=(value)<<4; \ + }while(0) + +#define DMA_CTL_SET_DSIZ(x,value) do{ \ + (x)&=~(0x3<<6); \ + (x)|=(value)<<6; \ + }while(0) + +#define DMA_CTL_SET_SMOD(x,value) do{ \ + (x)&=~(0x3<<10); \ + (x)|=(value)<<10; \ + }while(0) + +#define DMA_CTL_SET_DMOD(x,value) do{ \ + (x)&=~(0x3<<12); \ + (x)|=(value)<<12; \ + }while(0) + +typedef struct { + volatile u32 SourceAddr; + volatile u32 DestAddr; + volatile u32 Count; + volatile u32 Ctl; + volatile u32 RequestSource; + volatile u32 BurstLength; + union { + volatile u32 ReqTimeout; + volatile u32 BusUtilt; + }; + volatile u32 transferd; +} dma_regs_t; + +#ifndef TRANSFER_32BIT +/*! + * This defines DMA access data size + */ + +#define TRANSFER_8BIT DMA_MEM_SIZE_8 +#define TRANSFER_16BIT DMA_MEM_SIZE_16 +#define TRANSFER_32BIT DMA_MEM_SIZE_32 + +#endif + +/*! + * This defines maximum device name length passed during mxc_request_dma(). + */ +#define MAX_DEVNAME_LENGTH 32 +#define MAX_BD_SIZE 32 + +/*! + * Structure containing dma channel parameters. + */ +typedef struct { + unsigned long dma_chan; /*!< the dma channel information: dynamic or channel number */ + u32 mode:1; /*!< the initialized dma mode, 0 for dma read, 1 for dma write */ + u32 rto_en:1; /*!< enable request-timeout. It is valid when REN=1 */ + u32 dir:1; /*!< Transfer direction, 0 for increment, 1 for decrement */ + u32 dma_chaining:1; /*!< Autoclear bit for chainbuffer */ + u32 ren:1; /*!< enable transfer based request signal */ + u32 M2D_Valid:1; /*!< enable 2D address module. 0 for disable it. 1 for enabled it */ + u32 msel:1; /*!<2D memory selection, 0 for set A, 1 for set B */ + u32 burstLength; /*!< Channel burst length */ + u32 request; /*!< Request source. */ + u32 busuntils; /*!< when REN=0, Bus utilization, otherwise it it request timeout */ + u32 sourceType; /*!< Source type, see DMA_TYPE_* */ + u32 sourcePort; /*!< Source port size, see DMA_MEM_SIZE_* */ + u32 destType; /*!< Destination type, see DMA_TYPE_* */ + u32 destPort; /*!< Destination port size, see DMA_MEM_SIZE_* */ + __u32 per_address; /*< Peripheral source/destination + * physical address + */ + u32 W; /*!< 2D Wide-size */ + u32 X; /*!< 2D X-size */ + u32 Y; /*!< 2D Y-size */ +} mx2_dma_info_t; + +/*! + * Structure of dma buffer descriptor + */ +typedef struct { + unsigned long state; /*!< dma bd state */ + int mode; /*!< the dma mode of this bd */ + unsigned long count; /*!< the length of the dma transfer */ + unsigned long src_addr; /*!< the source address of the dma transfer */ + unsigned long dst_addr; /*!< the destination address of the dma transfer */ +} mx2_dma_bd_t; + +/*! + * the states of dma buffer descriptor + */ +#define DMA_BD_ST_BUSY 0x20000000 /*!< dma bd is transfering or has be configured into controller */ +#define DMA_BD_ST_PEND 0x10000000 /*!< dma bd is waiting to be configured into controller */ +#define DMA_BD_ST_LAST 0x08000000 /*!< dma bd is the last dma bd which is built in one dma transfer request + * When completed this bd, the callback function must be called. + */ + +/*! + * This structure containing the private information for MX2 + */ +typedef struct mx2_dma_priv_s { + unsigned int dma_chaining:1; /* 1: using headware dma chaining feature */ + unsigned int ren:1; /* 1: dma start besed on request signal */ + unsigned long trans_bytes; /* To store the transfered data bytes in this transfer */ + mx2_dma_info_t *dma_info; /* To store the pointer for dma parameter for reading and wirting */ + int bd_rd; /* the read index of bd ring */ + int bd_wr; /* the write index of bd ring */ + atomic_t bd_used; /* the valid bd number in bd ring */ + mx2_dma_bd_t *bd_ring; /* the pointer of bd ring */ + unsigned long dma_base; /* register base address of this channel */ + int dma_irq; /* irq number of this channel */ +} mx2_dma_priv_t; + +/*! + * @brief get the dma info by channel_id + */ +extern mx2_dma_info_t *mxc_dma_get_info(mxc_dma_device_t channel_id); + +/*! + * @brief: scan dma parameter list . And collect information about which channels are dynamic . + */ +extern void mxc_dma_load_info(mxc_dma_channel_t * dma); + +#endif diff --git a/arch/arm/plat-mxc/include/mach/mx31.h b/arch/arm/plat-mxc/include/mach/mx31.h index 0b06941b6139..2ca31d6413a6 100644 --- a/arch/arm/plat-mxc/include/mach/mx31.h +++ b/arch/arm/plat-mxc/include/mach/mx31.h @@ -3,6 +3,14 @@ */ #define MX31_IRAM_BASE_ADDR 0x1FFC0000 /* internal ram */ #define MX31_IRAM_SIZE SZ_16K +#define MX31_IRAM_BASE_ADDR_VIRT 0xFC340000 + +#define USB_IRAM_BASE_ADDR (MX31_IRAM_BASE_ADDR) +#ifdef CONFIG_USB_STATIC_IRAM +#define USB_IRAM_SIZE (2*SZ_8K) +#else +#define USB_IRAM_SIZE 0 +#endif #define OTG_BASE_ADDR (AIPS1_BASE_ADDR + 0x00088000) #define ATA_BASE_ADDR (AIPS1_BASE_ADDR + 0x0008C000) @@ -14,21 +22,98 @@ #define SIM1_BASE_ADDR (SPBA0_BASE_ADDR + 0x00018000) #define IIM_BASE_ADDR (SPBA0_BASE_ADDR + 0x0001C000) +/*! + * defines for SPBA modules + */ +#define SPBA_SDHC1 0x04 +#define SPBA_SDHC2 0x08 +#define SPBA_SIM 0x18 +#define SPBA_IIM 0x1C + #define CSPI3_BASE_ADDR (AIPS2_BASE_ADDR + 0x00084000) #define FIRI_BASE_ADDR (AIPS2_BASE_ADDR + 0x0008C000) #define SCM_BASE_ADDR (AIPS2_BASE_ADDR + 0x000AE000) #define SMN_BASE_ADDR (AIPS2_BASE_ADDR + 0x000AF000) #define MPEG4_ENC_BASE_ADDR (AIPS2_BASE_ADDR + 0x000C8000) +#define VPU_BASE_ADDR MPEG4_ENC_BASE_ADDR #define MX31_NFC_BASE_ADDR (X_MEMC_BASE_ADDR + 0x0000) +#define NFC_BASE_ADDR (X_MEMC_BASE_ADDR + 0x0000) +#define NFC_SIZE 0x1000 +#define NFC_IO_ADDRESS(x) 0 + +/* + * VL2CC for i.MX32 + */ +#define VL2CC_BASE_ADDR 0xE0000000 + +/*! + * Defines for modules using static and dynamic DMA channels + */ + +#ifdef CONFIG_SDMA_IRAM +#define MXC_DMA_CHANNEL_IRAM 30 +#endif /*CONFIG_SDMA_IRAM */ + +#define MXC_DMA_CHANNEL_UART1_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART1_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART2_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART2_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART3_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART3_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART4_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART4_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART5_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART5_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MMC1 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MMC2 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SSI1_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SSI1_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SSI2_RX MXC_DMA_DYNAMIC_CHANNEL + +#ifdef CONFIG_SDMA_IRAM +#define MXC_DMA_CHANNEL_SSI2_TX (MXC_DMA_CHANNEL_IRAM + 1) +#else /*CONFIG_SDMA_IRAM */ +#define MXC_DMA_CHANNEL_SSI2_TX MXC_DMA_DYNAMIC_CHANNEL +#endif /*CONFIG_SDMA_IRAM */ + +#define MXC_DMA_CHANNEL_FIR_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_FIR_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI1_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI1_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI2_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI2_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ATA_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ATA_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MEMORY MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_FIFO_MEMORY MXC_DMA_DYNAMIC_CHANNEL + +/* + * DMA request assignments + */ +#define DMA_REQ_SDHC2 21 +#define DMA_REQ_SDHC1 20 +#define DMA_REQ_FIRI_TX 17 +#define DMA_REQ_FIRI_RX 16 +#define DMA_REQ_UART4_TX 13 +#define DMA_REQ_UART4_RX 12 +#define DMA_REQ_CSPI3_TX 11 +#define DMA_REQ_CSPI3_RX 10 +#define DMA_REQ_UART5_TX 11 +#define DMA_REQ_UART5_RX 10 +#define DMA_REQ_UART3_TX 9 +#define DMA_REQ_UART3_RX 8 +#define DMA_REQ_SIM 5 #define MXC_INT_MPEG4_ENCODER 5 #define MXC_INT_FIRI 7 #define MX31_INT_MMC_SDHC2 8 +#define MXC_INT_MMC_SDHC2 8 #define MXC_INT_MMC_SDHC1 9 #define MX31_INT_SSI2 11 #define MX31_INT_SSI1 12 #define MXC_INT_MBX 16 +#define MXC_INT_VPU MXC_INT_MBX #define MXC_INT_CSPI3 17 #define MXC_INT_SIM2 20 #define MXC_INT_SIM1 21 @@ -43,3 +128,13 @@ #define MXC_INT_CCM 53 #define MXC_INT_PCMCIA 54 +#define ARM11_PMU_IRQ MXC_INT_EVTMON + +/*! + * NFMS bit in RCSR register for pagesize of nandflash + */ +#define NFMS (*((volatile u32 *)IO_ADDRESS(CCM_BASE_ADDR+0xc))) +#define NFMS_BIT 30 +#define NFMS_NF_DWIDTH 31 +#define NFMS_NF_PG_SZ 30 + diff --git a/arch/arm/plat-mxc/include/mach/mx35.h b/arch/arm/plat-mxc/include/mach/mx35.h index 6465fefb42e3..da83e11b4070 100644 --- a/arch/arm/plat-mxc/include/mach/mx35.h +++ b/arch/arm/plat-mxc/include/mach/mx35.h @@ -1,11 +1,56 @@ + +/*! + * Define this option to specify we are using the newer SDMA module. + */ +#define MXC_SDMA_V2 + /* * IRAM */ #define MX35_IRAM_BASE_ADDR 0x10000000 /* internal ram */ +#define IRAM_BASE_ADDR MX35_IRAM_BASE_ADDR +#define IRAM_BASE_ADDR_VIRT 0xFC600000 #define MX35_IRAM_SIZE SZ_128K +#ifndef CONFIG_SDMA_IRAM +#define CONFIG_SDMA_IRAM_SIZE 0 +#endif +#ifdef CONFIG_SND_MXC_SOC_IRAM +#define SND_RAM_SIZE 0x10000 +#else +#define SND_RAM_SIZE 0 +#endif + +#define SND_RAM_BASE_ADDR (MX35_IRAM_BASE_ADDR + CONFIG_SDMA_IRAM_SIZE) +#define MLB_IRAM_ADDR_OFFSET (CONFIG_SDMA_IRAM_SIZE + SND_RAM_SIZE) + #define MXC_FEC_BASE_ADDR 0x50038000 #define MX35_NFC_BASE_ADDR 0xBB000000 +#define NFC_BASE_ADDR MX35_NFC_BASE_ADDR +#define NFC_BASE_ADDR_VIRT 0xFC700000 +#define NFC_SIZE SZ_1M +#define NFC_IO_ADDRESS(x) (((x) - NFC_BASE_ADDR) + NFC_BASE_ADDR_VIRT) + + +#define MMC_SDHC1_BASE_ADDR (AIPS2_BASE_ADDR + 0x000B4000) +#define MMC_SDHC2_BASE_ADDR (AIPS2_BASE_ADDR + 0x000B8000) +#define MMC_SDHC3_BASE_ADDR (AIPS2_BASE_ADDR + 0x000BC000) +#define CAN1_BASE_ADDR (AIPS2_BASE_ADDR + 0x000E4000) +#define CAN2_BASE_ADDR (AIPS2_BASE_ADDR + 0x000E8000) +#define IIM_BASE_ADDR (AIPS2_BASE_ADDR + 0x000F0000) +#define OTG_BASE_ADDR (AIPS2_BASE_ADDR + 0x000F4000) +#define MLB_BASE_ADDR (AIPS2_BASE_ADDR + 0x000F8000) +#define SPDIF_BASE_ADDR (SPBA0_BASE_ADDR + 0x00028000) +#define ATA_BASE_ADDR (SPBA0_BASE_ADDR + 0x00020000) +#define ASRC_BASE_ADDR (SPBA0_BASE_ADDR + 0x0002C000) +#define ESAI_BASE_ADDR (SPBA0_BASE_ADDR + 0x00034000) +#define RNGC_BASE_ADDR (AIPS2_BASE_ADDR + 0x000B0000) + +#define SPBA_MSHC 0x24 +#define SPBA_SPDIR 0x28 +#define SPBA_ASRC 0x2C +#define SPBA_ESAI 0x34 +#define SPBA_FEC 0x38 /* * Interrupt numbers @@ -16,6 +61,8 @@ #define MXC_INT_MMC_SDHC3 9 #define MX35_INT_SSI1 11 #define MX35_INT_SSI2 12 +#define MXC_INT_SSI1 MX35_INT_SSI1 +#define MXC_INT_SSI2 MX35_INT_SSI2 #define MXC_INT_GPU2D 16 #define MXC_INT_ASRC 17 #define MXC_INT_USBHS 35 @@ -26,4 +73,84 @@ #define MXC_INT_MLB 46 #define MXC_INT_SPDIF 47 #define MXC_INT_FEC 57 +#define MXC_INT_RNGC MXC_INT_RNGA + +#define MXC_INT_FORCE MXC_INT_RESV1 + +/*! + * Defines for modules using static and dynamic DMA channels + */ +#define MXC_DMA_CHANNEL_IRAM 30 +#define MXC_DMA_CHANNEL_UART1_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART1_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART2_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART2_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART3_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART3_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MMC1 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MMC2 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MMC3 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SSI1_RX MXC_DMA_DYNAMIC_CHANNEL +#ifdef CONFIG_SDMA_IRAM +#define MXC_DMA_CHANNEL_SSI1_TX (MXC_DMA_CHANNEL_IRAM + 1) +#else +#define MXC_DMA_CHANNEL_SSI1_TX MXC_DMA_DYNAMIC_CHANNEL +#endif +#define MXC_DMA_CHANNEL_SSI2_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SSI2_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI1_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI1_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI2_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI2_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ATA_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ATA_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MEMORY MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SPDIF_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SPDIF_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCA_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCA_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCB_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCB_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCC_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCC_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ESAI_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ESAI_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCA_ESAI MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCB_ESAI MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCC_ESAI MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCA_SSI1_TX0 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCA_SSI1_TX1 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCA_SSI2_TX0 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCA_SSI2_TX1 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCB_SSI1_TX0 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCB_SSI1_TX1 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCB_SSI2_TX0 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ASRCB_SSI2_TX1 MXC_DMA_DYNAMIC_CHANNEL + +#define DMA_REQ_ASRC_DMA6 41 +#define DMA_REQ_ASRC_DMA5 40 +#define DMA_REQ_ASRC_DMA4 39 +#define DMA_REQ_ASRC_DMA3 38 +#define DMA_REQ_ASRC_DMA2 37 +#define DMA_REQ_ASRC_DMA1 36 +#define DMA_REQ_RSVD3 35 +#define DMA_REQ_RSVD2 34 +#define DMA_REQ_ESAI_TX 33 +#define DMA_REQ_ESAI_RX 32 +#define DMA_REQ_IPU 21 +#define DMA_REQ_RSVD1 20 +#define DMA_REQ_SPDIF_TX 13 +#define DMA_REQ_SPDIF_RX 12 +#define DMA_REQ_UART3_TX 11 +#define DMA_REQ_UART3_RX 10 +#define DMA_REQ_MSHC 5 +#define DMA_REQ_DPTC 1 +#define DMA_REQ_DVFS 1 +/*! + * NFMS bit in RCSR register for pagesize of nandflash + */ +#define NFMS (*((volatile u32 *)IO_ADDRESS(CCM_BASE_ADDR+0x18))) +#define NFMS_BIT 8 +#define NFMS_NF_DWIDTH 14 +#define NFMS_NF_PG_SZ 8 diff --git a/arch/arm/plat-mxc/include/mach/mx37.h b/arch/arm/plat-mxc/include/mach/mx37.h new file mode 100644 index 000000000000..7539900cbacd --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/mx37.h @@ -0,0 +1,474 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_ARCH_MXC_MX37_H__ +#define __ASM_ARCH_MXC_MX37_H__ + +#ifndef __ASM_ARCH_MXC_HARDWARE_H__ +#error "Do not include directly." +#endif + +/*! + * @file arch-mxc/mx37.h + * @brief This file contains register definitions. + * + * @ingroup MSL_MX37 + */ + +/*! + * Define this option to specify we are using the newer SDMA module. + */ +#define MXC_SDMA_V2 + +/* + * IRAM + */ +#define IRAM_BASE_ADDR 0x10000000 /* internal ram */ +#define IRAM_BASE_ADDR_VIRT 0xF8000000 +#define IRAM_SIZE (9*SZ_8K) /* 72KB */ + +#if defined(CONFIG_MXC_SECURITY_SCC2) \ + || defined(CONFIG_MXC_SECURITY_SCC2_MODULE) +#define SCC_IRAM_SIZE SZ_16K +#else +#define SCC_IRAM_SIZE 0 +#endif + +/*#ifndef CONFIG_SDMA_IRAM +#define CONFIG_SDMA_IRAM_SIZE 0 +#endif*/ +#ifdef CONFIG_SDMA_IRAM +#define SDMA_IRAM_SIZE CONFIG_SDMA_IRAM_SIZE +#else +#define SDMA_IRAM_SIZE 0 +#endif + +#ifdef CONFIG_SND_MXC_SOC_IRAM +#define SND_RAM_SIZE 0x6000 +#else +#define SND_RAM_SIZE 0 +#endif + +#ifdef CONFIG_USB_STATIC_IRAM +#define USB_IRAM_SIZE SZ_8K +#else +#define USB_IRAM_SIZE 0 +#endif + +#if (IRAM_SIZE < (SCC_IRAM_SIZE + SDMA_IRAM_SIZE + SND_RAM_SIZE + \ + USB_IRAM_SIZE)) +#error "IRAM size exceeded" +#endif + +#ifdef CONFIG_MXC_VPU_IRAM +#define VPU_IRAM_SIZE (IRAM_BASE_ADDR + IRAM_SIZE - VPU_IRAM_BASE_ADDR) +#else +#define VPU_IRAM_SIZE 0 +#endif + +#define SCC_IRAM_BASE_ADDR (IRAM_BASE_ADDR + IRAM_SIZE - SCC_IRAM_SIZE) +#define SDMA_RAM_BASE_ADDR (IRAM_BASE_ADDR) +#define SND_RAM_BASE_ADDR (IRAM_BASE_ADDR + SDMA_IRAM_SIZE) +#define USB_IRAM_BASE_ADDR (SND_RAM_BASE_ADDR + SND_RAM_SIZE) +#define VPU_IRAM_BASE_ADDR (USB_IRAM_BASE_ADDR + USB_IRAM_SIZE) + +/* + * NFC + */ +#define NFC_BASE_ADDR_AXI 0x7FFF0000 /* NAND flash AXI */ +#define NFC_BASE_ADDR_AXI_VIRT 0xF9000000 +#define NFC_AXI_SIZE SZ_64K + +/* + * L2CC + */ +#define L2CC_BASE_ADDR 0xB0000000 + +#define PLATFORM_BASE_ADDR 0xB0400000 +#define PLATFORM_BASE_ADDR_VIRT 0xFA000000 +#define PLATFORM_SIZE SZ_1M +#define EVTMON_BASE_ADDR (PLATFORM_BASE_ADDR + 0x00000000) +#define ARM1176_BASE_ADDR (PLATFORM_BASE_ADDR + 0x00004000) + +#define TZIC_BASE_ADDR 0xB0800000 +#define TZIC_BASE_ADDR_VIRT 0xFA100000 +#define TZIC_SIZE SZ_1M + +#define DEBUG_BASE_ADDR 0xB0C00000 +#define DEBUG_BASE_ADDR_VIRT 0xFA200000 +#define DEBUG_SIZE SZ_1M +#define ETB_BASE_ADDR (DEBUG_BASE_ADDR + 0x00001000) +#define ETM_BASE_ADDR (DEBUG_BASE_ADDR + 0x00002000) +#define TPIU_BASE_ADDR (DEBUG_BASE_ADDR + 0x00003000) +#define CTI0_BASE_ADDR (DEBUG_BASE_ADDR + 0x00004000) +#define CTI1_BASE_ADDR (DEBUG_BASE_ADDR + 0x00005000) +#define CTI2_BASE_ADDR (DEBUG_BASE_ADDR + 0x00006000) + +/* + * AIPS 1 + */ +#define AIPS1_BASE_ADDR 0xC3F00000 +#define AIPS1_BASE_ADDR_VIRT 0xFC000000 +#define AIPS1_SIZE SZ_1M + +#define MAX_BASE_ADDR (AIPS1_BASE_ADDR + 0x00080000) +#define GPIO1_BASE_ADDR (AIPS1_BASE_ADDR + 0x00084000) +#define GPIO2_BASE_ADDR (AIPS1_BASE_ADDR + 0x00088000) +#define GPIO3_BASE_ADDR (AIPS1_BASE_ADDR + 0x0008C000) +#define KPP_BASE_ADDR (AIPS1_BASE_ADDR + 0x00094000) +#define WDOG1_BASE_ADDR (AIPS1_BASE_ADDR + 0x00098000) +#define WDOG2_BASE_ADDR (AIPS1_BASE_ADDR + 0x0009C000) +#define GPT1_BASE_ADDR (AIPS1_BASE_ADDR + 0x000A0000) +#define SRTC_BASE_ADDR (AIPS1_BASE_ADDR + 0x000A4000) +#define IOMUXC_BASE_ADDR (AIPS1_BASE_ADDR + 0x000A8000) +#define IIM_BASE_ADDR (AIPS1_BASE_ADDR + 0x000AC000) +#define CSU_BASE_ADDR (AIPS1_BASE_ADDR + 0x000B0000) +#define SDMA_BASE_ADDR (AIPS1_BASE_ADDR + 0x000B4000) +#define SCC_BASE_ADDR (AIPS1_BASE_ADDR + 0x000BC000) +#define ROMCP_BASE_ADDR (AIPS1_BASE_ADDR + 0x000C0000) +#define RTIC_BASE_ADDR (AIPS1_BASE_ADDR + 0x000C4000) +#define VPU_BASE_ADDR (AIPS1_BASE_ADDR + 0x000D0000) +#define OTG_BASE_ADDR (AIPS1_BASE_ADDR + 0x000D4000) +#define ATA_BASE_ADDR (AIPS1_BASE_ADDR + 0x000D8000) +#define MSHC1_BASE_ADDR (AIPS1_BASE_ADDR + 0x000E0000) +#define FEC_BASE_ADDR (AIPS1_BASE_ADDR + 0x000E8000) +#define RNGC_BASE_ADDR (AIPS1_BASE_ADDR + 0x000EC000) +#define TVE_BASE_ADDR (AIPS1_BASE_ADDR + 0x000F0000) + +/* + * SPBA global module enabled #0 + */ +#define SPBA0_BASE_ADDR 0xC0000000 +#define SPBA0_BASE_ADDR_VIRT 0xFC100000 +#define SPBA0_SIZE SZ_1M + +#define MMC_SDHC1_BASE_ADDR (SPBA0_BASE_ADDR + 0x00004000) +#define MMC_SDHC2_BASE_ADDR (SPBA0_BASE_ADDR + 0x00008000) +#define UART3_BASE_ADDR (SPBA0_BASE_ADDR + 0x0000C000) +#define CSPI2_BASE_ADDR (SPBA0_BASE_ADDR + 0x00010000) +#define SSI2_BASE_ADDR (SPBA0_BASE_ADDR + 0x00014000) +#define MMC_SDHC3_BASE_ADDR (SPBA0_BASE_ADDR + 0x00020000) +#define SPDIF_BASE_ADDR (SPBA0_BASE_ADDR + 0x00028000) +#define ATA_DMA_BASE_ADDR (SPBA0_BASE_ADDR + 0x00034000) +#define SPBA_CTRL_BASE_ADDR (SPBA0_BASE_ADDR + 0x0003C000) + +/*! + * defines for SPBA modules + */ +#define SPBA_SDHC1 0x04 +#define SPBA_SDHC2 0x08 +#define SPBA_UART3 0x0C +#define SPBA_CSPI2 0x10 +#define SPBA_SSI2 0x14 +#define SPBA_SDHC3 0x20 +#define SPBA_SPDIF 0x28 +#define SPBA_ATA 0x34 + +/*! + * Defines for modules using static and dynamic DMA channels + */ +#define MXC_DMA_CHANNEL_IRAM 30 +#define MXC_DMA_CHANNEL_SPDIF_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART1_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART1_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART2_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART2_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART3_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART3_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MMC1 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MMC2 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SSI1_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SSI1_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SSI2_RX MXC_DMA_DYNAMIC_CHANNEL +#ifdef CONFIG_SDMA_IRAM +#define MXC_DMA_CHANNEL_SSI2_TX (MXC_DMA_CHANNEL_IRAM + 1) +#else /*CONFIG_SDMA_IRAM */ +#define MXC_DMA_CHANNEL_SSI2_TX MXC_DMA_DYNAMIC_CHANNEL +#endif /*CONFIG_SDMA_IRAM */ +#define MXC_DMA_CHANNEL_CSPI1_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI1_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI2_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI2_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI3_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI3_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ATA_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ATA_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MEMORY MXC_DMA_DYNAMIC_CHANNEL + +/* + * AIPS 2 + */ +#define AIPS2_BASE_ADDR 0xE3F00000 +#define AIPS2_BASE_ADDR_VIRT 0xFC200000 +#define AIPS2_SIZE SZ_1M + +#define PLL0_BASE_ADDR (AIPS2_BASE_ADDR + 0x00080000) +#define PLL1_BASE_ADDR (AIPS2_BASE_ADDR + 0x00084000) +#define PLL2_BASE_ADDR (AIPS2_BASE_ADDR + 0x00088000) +#define CCM_BASE_ADDR (AIPS2_BASE_ADDR + 0x0008C000) +#define GPC_BASE_ADDR (AIPS2_BASE_ADDR + 0x00090000) +#define SRC_BASE_ADDR (AIPS2_BASE_ADDR + 0x00094000) +#define EPIT1_BASE_ADDR (AIPS2_BASE_ADDR + 0x00098000) +#define EPIT2_BASE_ADDR (AIPS2_BASE_ADDR + 0x0009C000) +#define PWM_BASE_ADDR (AIPS2_BASE_ADDR + 0x000A0000) +#define OWIRE_BASE_ADDR (AIPS2_BASE_ADDR + 0x000A4000) +#define CSPI3_BASE_ADDR (AIPS2_BASE_ADDR + 0x000A8000) +#define CSPI1_BASE_ADDR (AIPS2_BASE_ADDR + 0x000AC000) +#define UART1_BASE_ADDR (AIPS2_BASE_ADDR + 0x000B0000) +#define UART2_BASE_ADDR (AIPS2_BASE_ADDR + 0x000BC000) +#define I2C3_BASE_ADDR (AIPS2_BASE_ADDR + 0x000C0000) +#define I2C2_BASE_ADDR (AIPS2_BASE_ADDR + 0x000C4000) +#define I2C_BASE_ADDR (AIPS2_BASE_ADDR + 0x000C8000) +#define SSI1_BASE_ADDR (AIPS2_BASE_ADDR + 0x000CC000) +#define AUDMUX_BASE_ADDR (AIPS2_BASE_ADDR + 0x000D0000) +#define EMI_BASE_ADDR (AIPS2_BASE_ADDR + 0x000DBF00) + +#define M4IF_BASE_ADDR (AIPS2_BASE_ADDR + 0x000D8000) +#define ESDCTL_BASE_ADDR (AIPS2_BASE_ADDR + 0x000D9000) +#define WEIM_BASE_ADDR (AIPS2_BASE_ADDR + 0x000DA000) +#define NFC_BASE_ADDR (AIPS2_BASE_ADDR + 0x000DB000) + +/* + * Memory regions and CS + */ +#define IPU_CTRL_BASE_ADDR 0x80000000 +#define CSD0_BASE_ADDR 0x40000000 +#define CSD1_BASE_ADDR 0x50000000 + +#define CS0_BASE_ADDR 0x60000000 +#define CS1_BASE_ADDR 0x68000000 +#define CS2_BASE_ADDR 0x70000000 + +/*! + * This macro defines the physical to virtual address mapping for all the + * peripheral modules. It is used by passing in the physical address as x + * and returning the virtual address. If the physical address is not mapped, + * it returns 0xDEADBEEF + */ +#define IO_ADDRESS(x) \ + (void __force __iomem *) \ + (((x >= (unsigned long)IRAM_BASE_ADDR) && (x < (unsigned long)IRAM_BASE_ADDR + IRAM_SIZE)) ? IRAM_IO_ADDRESS(x):\ + ((x >= (unsigned long)PLATFORM_BASE_ADDR) && (x < (unsigned long)PLATFORM_BASE_ADDR + PLATFORM_SIZE)) ? PLATFORM_IO_ADDRESS(x):\ + ((x >= (unsigned long)TZIC_BASE_ADDR) && (x < (unsigned long)TZIC_BASE_ADDR + TZIC_SIZE)) ? TZIC_IO_ADDRESS(x):\ + ((x >= (unsigned long)DEBUG_BASE_ADDR) && (x < (unsigned long)DEBUG_BASE_ADDR + DEBUG_SIZE)) ? DEBUG_IO_ADDRESS(x):\ + ((x >= (unsigned long)SPBA0_BASE_ADDR) && (x < (unsigned long)SPBA0_BASE_ADDR + SPBA0_SIZE)) ? SPBA0_IO_ADDRESS(x):\ + ((x >= (unsigned long)AIPS1_BASE_ADDR) && (x < (unsigned long)AIPS1_BASE_ADDR + AIPS1_SIZE)) ? AIPS1_IO_ADDRESS(x):\ + ((x >= (unsigned long)AIPS2_BASE_ADDR) && (x < (unsigned long)AIPS2_BASE_ADDR + AIPS2_SIZE)) ? AIPS2_IO_ADDRESS(x):\ + ((x >= (unsigned long)NFC_BASE_ADDR_AXI) && (x < (unsigned long)NFC_BASE_ADDR_AXI + NFC_AXI_SIZE)) ? NFC_BASE_ADDR_AXI_IO_ADDRESS(x):\ + 0xDEADBEEF) + +/* + * define the address mapping macros: in physical address order + */ + +#define IRAM_IO_ADDRESS(x) \ + (((x) - IRAM_BASE_ADDR) + IRAM_BASE_ADDR_VIRT) + +#define PLATFORM_IO_ADDRESS(x) \ + (((x) - PLATFORM_BASE_ADDR) + PLATFORM_BASE_ADDR_VIRT) + +#define TZIC_IO_ADDRESS(x) \ + (((x) - TZIC_BASE_ADDR) + TZIC_BASE_ADDR_VIRT) + +#define DEBUG_IO_ADDRESS(x) \ + (((x) - DEBUG_BASE_ADDR) + DEBUG_BASE_ADDR_VIRT) + +#define SPBA0_IO_ADDRESS(x) \ + (((x) - SPBA0_BASE_ADDR) + SPBA0_BASE_ADDR_VIRT) + +#define AIPS1_IO_ADDRESS(x) \ + (((x) - AIPS1_BASE_ADDR) + AIPS1_BASE_ADDR_VIRT) + +#define AIPS2_IO_ADDRESS(x) \ + (((x) - AIPS2_BASE_ADDR) + AIPS2_BASE_ADDR_VIRT) + +#define NFC_BASE_ADDR_AXI_IO_ADDRESS(x) \ + (((x) - NFC_BASE_ADDR_AXI) + NFC_BASE_ADDR_AXI_VIRT) + +#define IS_MEM_DEVICE_NONSHARED(x) ((x) >= 0x80000000) + +/* + * DMA request assignments + */ +#define DMA_REQ_RESV47 47 +#define DMA_REQ_VPU 46 +#define DMA_REQ_SPDIF_TX 45 +#define DMA_REQ_UART3_TX 44 +#define DMA_REQ_UART3_RX 43 +#define DMA_REQ_I2C2 42 +#define DMA_REQ_I2C1 41 +#define DMA_REQ_SDHC3 40 +#define DMA_REQ_CSPI3_TX 39 +#define DMA_REQ_CSPI3_RX 38 +#define DMA_REQ_RESV37 37 +#define DMA_REQ_IPU 36 +#define DMA_REQ_RESV35 35 +#define DMA_REQ_EPIT2 34 +#define DMA_REQ_RESV33 33 +#define DMA_REQ_RESV32 32 +#define DMA_REQ_ECT 31 +#define DMA_REQ_NFC 30 +#define DMA_REQ_SSI1_TX1 29 +#define DMA_REQ_SSI1_RX1 28 +#define DMA_REQ_SSI1_TX2 27 +#define DMA_REQ_SSI1_RX2 26 +#define DMA_REQ_SSI2_TX1 25 +#define DMA_REQ_SSI2_RX1 24 +#define DMA_REQ_SSI2_TX2 23 +#define DMA_REQ_SSI2_RX2 22 +#define DMA_REQ_SDHC2 21 +#define DMA_REQ_SDHC1 20 +#define DMA_REQ_UART1_TX 19 +#define DMA_REQ_UART1_RX 18 +#define DMA_REQ_UART2_TX 17 +#define DMA_REQ_UART2_RX 16 +#define DMA_REQ_GPIO1_0 15 +#define DMA_REQ_GPIO1_1 14 +#define DMA_REQ_RESV13 13 +#define DMA_REQ_RESV12 12 +#define DMA_REQ_RESV11 11 +#define DMA_REQ_RESV10 10 +#define DMA_REQ_CSPI1_TX 9 +#define DMA_REQ_CSPI1_RX 8 +#define DMA_REQ_CSPI2_TX 7 +#define DMA_REQ_CSPI2_RX 6 +#define DMA_REQ_RESV5 5 +#define DMA_REQ_ATA_TX_END 4 +#define DMA_REQ_ATA_TX 3 +#define DMA_REQ_ATA_RX 2 +#define DMA_REQ_GPC 1 +#define DMA_REQ_RESV0 0 + +/* + * Interrupt numbers + */ +#define MXC_INT_BASE 0 +#define MXC_INT_RESV0 0 +#define MXC_INT_MMC_SDHC1 1 +#define MXC_INT_MMC_SDHC2 2 +#define MXC_INT_MMC_SDHC3 3 +#define MXC_INT_RESV4 4 +#define MXC_INT_RESV5 5 +#define MXC_INT_SDMA 6 +#define MXC_INT_IOMUX 7 +#define MXC_INT_RESV8 8 +#define MXC_INT_VPU 9 +#define MXC_INT_IPU_ERR 10 +#define MXC_INT_IPU_SYN 11 +#define MXC_INT_RESV12 12 +#define MXC_INT_RESV13 13 +#define MXC_INT_RNG 14 +#define MXC_INT_EMI 15 +#define MXC_INT_RESV16 16 +#define MXC_INT_RESV17 17 +#define MXC_INT_USB_OTG 18 +#define MXC_INT_RESV19 19 +#define MXC_INT_RESV20 20 +#define MXC_INT_SCC_SMN 21 +#define MXC_INT_SCC_STZ 22 +#define MXC_INT_SCC_SCM 23 +#define MXC_INT_SRTC_NTZ 24 +#define MXC_INT_SRTC_TZ 25 +#define MXC_INT_RTIC 26 +#define MXC_INT_CSU 27 +#define MXC_INT_RESV28 28 +#define MXC_INT_SSI1 29 +#define MXC_INT_SSI2 30 +#define MXC_INT_UART1 31 +#define MXC_INT_UART2 32 +#define MXC_INT_UART3 33 +#define MXC_INT_RESV34 34 +#define MXC_INT_RESV35 35 +#define MXC_INT_CSPI1 36 +#define MXC_INT_CSPI2 37 +#define MXC_INT_CSPI3 38 +#define MXC_INT_GPT 39 +#define MXC_INT_EPIT1 40 +#define MXC_INT_EPIT2 41 +#define MXC_INT_GPIO1_INT7 42 +#define MXC_INT_GPIO1_INT6 43 +#define MXC_INT_GPIO1_INT5 44 +#define MXC_INT_GPIO1_INT4 45 +#define MXC_INT_GPIO1_INT3 46 +#define MXC_INT_GPIO1_INT2 47 +#define MXC_INT_GPIO1_INT1 48 +#define MXC_INT_GPIO1_INT0 49 +#define MXC_INT_GPIO1_LOW 50 +#define MXC_INT_GPIO1_HIGH 51 +#define MXC_INT_GPIO2_LOW 52 +#define MXC_INT_GPIO2_HIGH 53 +#define MXC_INT_GPIO3_LOW 54 +#define MXC_INT_GPIO3_HIGH 55 +#define MXC_INT_RESV56 56 +#define MXC_INT_RESV57 57 +#define MXC_INT_WDOG1 58 +#define MXC_INT_WDOG2 59 +#define MXC_INT_KPP 60 +#define MXC_INT_PWM 61 +#define MXC_INT_I2C 62 +#define MXC_INT_I2C2 63 +#define MXC_INT_I2C3 64 +#define MXC_INT_MSHC1 65 +#define MXC_INT_RESV66 66 +#define MXC_INT_RESV67 67 +#define MXC_INT_RESV68 68 +#define MXC_INT_IIM 69 +#define MXC_INT_ATA 70 +#define MXC_INT_CCM1 71 +#define MXC_INT_CCM2 72 +#define MXC_INT_GPC1 73 +#define MXC_INT_GPC2 74 +#define MXC_INT_SRC 75 +#define MXC_INT_EVTMON 76 +#define MXC_INT_PER_MEASURE 77 +#define MXC_INT_DECODE_ERR 78 +#define MXC_INT_EVT_COUNT 79 +#define MXC_INT_SLAVE_ERR 80 +#define MXC_INT_RESV81 81 +#define MXC_INT_RESV82 82 +#define MXC_INT_RESV83 83 +#define MXC_INT_RESV84 84 +#define MXC_INT_RESV85 85 +#define MXC_INT_RESV86 86 +#define MXC_INT_FEC 87 +#define MXC_INT_OWIRE 88 +#define MXC_INT_CTI0 89 +#define MXC_INT_CTM0 90 +#define MXC_INT_SPDIF 91 +#define MXC_INT_TVOUT 92 + +/*! + * Interrupt Number for ARM11 PMU + */ +#define ARM11_PMU_IRQ MXC_INT_EVTMON + +/* gpio and gpio based interrupt handling */ +#define GPIO_DR 0x00 +#define GPIO_GDIR 0x04 +#define GPIO_PSR 0x08 +#define GPIO_ICR1 0x0C +#define GPIO_ICR2 0x10 +#define GPIO_IMR 0x14 +#define GPIO_ISR 0x18 +#define GPIO_INT_LOW_LEV 0x0 +#define GPIO_INT_HIGH_LEV 0x1 +#define GPIO_INT_RISE_EDGE 0x2 +#define GPIO_INT_FALL_EDGE 0x3 +#define GPIO_INT_NONE 0x4 + +/*! + * Macro to convert elv, llv, ulv to a data which is used to set DCVR0, DCVR1, + * DCVR2, or DCVR3. + */ +#define DCVR(elv, llv, ulv) ((elv << 0) | (llv << 10) | (ulv << 21)) + +#endif /* __ASM_ARCH_MXC_MX37_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/mx3x.h b/arch/arm/plat-mxc/include/mach/mx3x.h index b559a4bb5769..d297a832d3ad 100644 --- a/arch/arm/plat-mxc/include/mach/mx3x.h +++ b/arch/arm/plat-mxc/include/mach/mx3x.h @@ -8,8 +8,8 @@ * published by the Free Software Foundation. */ -#ifndef __ASM_ARCH_MXC_MX31_H__ -#define __ASM_ARCH_MXC_MX31_H__ +#ifndef __ASM_ARCH_MXC_MX3X_H__ +#define __ASM_ARCH_MXC_MX3X_H__ #ifndef __ASM_ARCH_MXC_HARDWARE_H__ #error "Do not include directly." @@ -99,6 +99,11 @@ #define MSHC1_BASE_ADDR (SPBA0_BASE_ADDR + 0x00024000) #define SPBA_CTRL_BASE_ADDR (SPBA0_BASE_ADDR + 0x0003C000) +#define SPBA_UART3 0x0C +#define SPBA_CSPI2 0x10 +#define SPBA_SSI2 0x14 +#define SPBA_ATA 0x20 + /* * AIPS 2 */ @@ -118,7 +123,7 @@ #define GPIO2_BASE_ADDR (AIPS2_BASE_ADDR + 0x000D0000) #define SDMA_BASE_ADDR (AIPS2_BASE_ADDR + 0x000D4000) #define RTC_BASE_ADDR (AIPS2_BASE_ADDR + 0x000D8000) -#define WDOG_BASE_ADDR (AIPS2_BASE_ADDR + 0x000DC000) +#define WDOG1_BASE_ADDR (AIPS2_BASE_ADDR + 0x000DC000) #define PWM_BASE_ADDR (AIPS2_BASE_ADDR + 0x000E0000) #define RTIC_BASE_ADDR (AIPS2_BASE_ADDR + 0x000EC000) @@ -168,6 +173,7 @@ ((x >= AVIC_BASE_ADDR) && (x < (AVIC_BASE_ADDR + AVIC_SIZE))) ? AVIC_IO_ADDRESS(x):\ ((x >= CS4_BASE_ADDR) && (x < (CS4_BASE_ADDR + CS4_SIZE))) ? CS4_IO_ADDRESS(x):\ ((x >= X_MEMC_BASE_ADDR) && (x < (X_MEMC_BASE_ADDR + X_MEMC_SIZE))) ? X_MEMC_IO_ADDRESS(x):\ + ((x >= NFC_BASE_ADDR) && (x < (NFC_BASE_ADDR + NFC_SIZE))) ? NFC_IO_ADDRESS(x):\ 0xDEADBEEF) /* @@ -204,8 +210,38 @@ (((x) - X_MEMC_BASE_ADDR) + X_MEMC_BASE_ADDR_VIRT) /* + * DMA request assignments + */ +#define DMA_REQ_ECT 31 +#define DMA_REQ_NFC 30 +#define DMA_REQ_SSI1_TX1 29 +#define DMA_REQ_SSI1_RX1 28 +#define DMA_REQ_SSI1_TX2 27 +#define DMA_REQ_SSI1_RX2 26 +#define DMA_REQ_SSI2_TX1 25 +#define DMA_REQ_SSI2_RX1 24 +#define DMA_REQ_SSI2_TX2 23 +#define DMA_REQ_SSI2_RX2 22 +#define DMA_REQ_UART1_TX 19 +#define DMA_REQ_UART1_RX 18 +#define DMA_REQ_UART2_TX 17 +#define DMA_REQ_UART2_RX 16 +#define DMA_REQ_EXTREQ1 15 +#define DMA_REQ_EXTREQ2 14 +#define DMA_REQ_CSPI1_TX 9 +#define DMA_REQ_CSPI1_RX 8 +#define DMA_REQ_CSPI2_TX 7 +#define DMA_REQ_CSPI2_RX 6 +#define DMA_REQ_ATA_RX 4 +#define DMA_REQ_ATA_TX 3 +#define DMA_REQ_ATA_TX_END 2 +#define DMA_REQ_CCM 1 +#define DMA_REQ_EXTREQ0 0 + +/* * Interrupt numbers */ +#define MXC_INT_RESV1 1 #define MXC_INT_I2C3 3 #define MXC_INT_I2C2 4 #define MXC_INT_RTIC 6 @@ -224,6 +260,7 @@ #define MXC_INT_EPIT1 28 #define MXC_INT_GPT 29 #define MXC_INT_POWER_FAIL 30 +#define MXC_INT_DVFS 31 #define MXC_INT_UART2 32 #define MXC_INT_NANDFC 33 #define MXC_INT_SDMA 34 @@ -245,6 +282,8 @@ #define MXC_INT_EXT_WDOG 62 #define MXC_INT_EXT_TV 63 +#define ARM11_PMU_IRQ MXC_INT_EVTMON + #define PROD_SIGNATURE 0x1 /* For MX31 */ /* silicon revisions specific to i.MX31 */ diff --git a/arch/arm/plat-mxc/include/mach/mx51.h b/arch/arm/plat-mxc/include/mach/mx51.h new file mode 100644 index 000000000000..ab9ddb2c07e1 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/mx51.h @@ -0,0 +1,511 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ASM_ARCH_MXC_MX51_H__ +#define __ASM_ARCH_MXC_MX51_H__ + +#ifndef __ASM_ARCH_MXC_HARDWARE_H__ +#error "Do not include directly." +#endif + +/*! + * @file arch-mxc/mx51.h + * @brief This file contains register definitions. + * + * @ingroup MSL_MX51 + */ + +/*! + * Register an interrupt handler for the SMN as well as the SCC. In some + * implementations, the SMN is not connected at all, and in others, it is + * on the same interrupt line as the SCM. Comment this line out accordingly + */ +#define USE_SMN_INTERRUPT + +/*! + * This option is used to set or clear the RXDMUXSEL bit in control reg 3. + * Certain platforms need this bit to be set in order to receive Irda data. + */ +#define MXC_UART_IR_RXDMUX 0x0004 +/*! + * This option is used to set or clear the RXDMUXSEL bit in control reg 3. + * Certain platforms need this bit to be set in order to receive UART data. + */ +#define MXC_UART_RXDMUX 0x0004 + +/*! + * This option is used to set or clear the dspdma bit in the SDMA config + * register. + */ +#define MXC_SDMA_DSPDMA 0 + +/*! + * Define this option to specify we are using the newer SDMA module. + */ +#define MXC_SDMA_V2 + + /* + * IRAM + */ +#define IRAM_BASE_ADDR 0x1FFE0000 /* internal ram */ +#define IRAM_BASE_ADDR_VIRT 0xFA3E0000 +#define IRAM_PARTITIONS 16 +#define IRAM_PARTITIONS_TO1 12 +#define IRAM_SIZE (IRAM_PARTITIONS*SZ_8K) /* 128KB */ + +#if defined(CONFIG_MXC_SECURITY_SCC2) \ + || defined(CONFIG_MXC_SECURITY_SCC2_MODULE) +#define SCC_IRAM_SIZE SZ_16K +#else +#define SCC_IRAM_SIZE 0 +#endif + +#ifdef CONFIG_SDMA_IRAM +#define SDMA_IRAM_SIZE CONFIG_SDMA_IRAM_SIZE +#else +#define SDMA_IRAM_SIZE 0 +#endif + +#ifdef CONFIG_SND_MXC_SOC_IRAM +#define SND_RAM_SIZE 0x6000 +#else +#define SND_RAM_SIZE 0 +#endif + +#ifdef CONFIG_MXC_VPU_IRAM +#define VPU_IRAM_SIZE 0x7000 +#else +#define VPU_IRAM_SIZE 0 +#endif + +#if (IRAM_SIZE < (SDMA_IRAM_SIZE + SND_RAM_SIZE + VPU_IRAM_SIZE + \ + SCC_IRAM_SIZE)) +#error "IRAM size exceeded" +#endif + +#define SCC_IRAM_BASE_ADDR (IRAM_BASE_ADDR + IRAM_SIZE - SCC_IRAM_SIZE) +#define VPU_IRAM_BASE_ADDR (SCC_IRAM_BASE_ADDR - VPU_IRAM_SIZE) +#define SND_RAM_BASE_ADDR (VPU_IRAM_BASE_ADDR - SND_RAM_SIZE) +#define SDMA_IRAM_BASE_ADDR (SND_RAM_BASE_ADDR - SDMA_IRAM_SIZE) +#define IDLE_IRAM_BASE_ADDR (SDMA_IRAM_BASE_ADDR - SZ_4K) +#define SUSPEND_IRAM_BASE_ADDR (IDLE_IRAM_BASE_ADDR - SZ_4K) +/* + * NFC + */ +#define NFC_BASE_ADDR_AXI 0xCFFF0000 /* NAND flash AXI */ +#define NFC_BASE_ADDR_AXI_VIRT 0xF9000000 +#define NFC_AXI_SIZE SZ_64K + +/* + * Graphics Memory of GPU + */ +#define GPU_BASE_ADDR 0x20000000 +#define GPU2D_BASE_ADDR 0xD0000000 + +#define TZIC_BASE_ADDR 0x8FFFC000 +#define TZIC_BASE_ADDR_VIRT 0xFA100000 +#define TZIC_SIZE SZ_16K + +#define DEBUG_BASE_ADDR 0x60000000 +#define DEBUG_BASE_ADDR_VIRT 0xFA200000 +#define DEBUG_SIZE SZ_1M +#define ETB_BASE_ADDR (DEBUG_BASE_ADDR + 0x00001000) +#define ETM_BASE_ADDR (DEBUG_BASE_ADDR + 0x00002000) +#define TPIU_BASE_ADDR (DEBUG_BASE_ADDR + 0x00003000) +#define CTI0_BASE_ADDR (DEBUG_BASE_ADDR + 0x00004000) +#define CTI1_BASE_ADDR (DEBUG_BASE_ADDR + 0x00005000) +#define CTI2_BASE_ADDR (DEBUG_BASE_ADDR + 0x00006000) +#define CTI3_BASE_ADDR (DEBUG_BASE_ADDR + 0x00007000) +#define CORTEX_DBG_BASE_ADDR (DEBUG_BASE_ADDR + 0x00008000) + +/* + * SPBA global module enabled #0 + */ +#define SPBA0_BASE_ADDR 0x70000000 +#define SPBA0_BASE_ADDR_VIRT 0xFB100000 +#define SPBA0_SIZE SZ_1M + +#define MMC_SDHC1_BASE_ADDR (SPBA0_BASE_ADDR + 0x00004000) +#define MMC_SDHC2_BASE_ADDR (SPBA0_BASE_ADDR + 0x00008000) +#define UART3_BASE_ADDR (SPBA0_BASE_ADDR + 0x0000C000) +#define CSPI1_BASE_ADDR (SPBA0_BASE_ADDR + 0x00010000) +#define SSI2_BASE_ADDR (SPBA0_BASE_ADDR + 0x00014000) +#define MMC_SDHC3_BASE_ADDR (SPBA0_BASE_ADDR + 0x00020000) +#define MMC_SDHC4_BASE_ADDR (SPBA0_BASE_ADDR + 0x00024000) +#define SPDIF_BASE_ADDR (SPBA0_BASE_ADDR + 0x00028000) +#define ATA_DMA_BASE_ADDR (SPBA0_BASE_ADDR + 0x00030000) +#define SLIM_DMA_BASE_ADDR (SPBA0_BASE_ADDR + 0x00034000) +#define HSI2C_DMA_BASE_ADDR (SPBA0_BASE_ADDR + 0x00038000) +#define SPBA_CTRL_BASE_ADDR (SPBA0_BASE_ADDR + 0x0003C000) + +/*! + * defines for SPBA modules + */ +#define SPBA_SDHC1 0x04 +#define SPBA_SDHC2 0x08 +#define SPBA_UART3 0x0C +#define SPBA_CSPI1 0x10 +#define SPBA_SSI2 0x14 +#define SPBA_SDHC3 0x20 +#define SPBA_SDHC4 0x24 +#define SPBA_SPDIF 0x28 +#define SPBA_ATA 0x30 +#define SPBA_SLIM 0x34 +#define SPBA_HSI2C 0x38 +#define SPBA_CTRL 0x3C + +/* + * AIPS 1 + */ +#define AIPS1_BASE_ADDR 0x73F00000 +#define AIPS1_BASE_ADDR_VIRT 0xFB000000 +#define AIPS1_SIZE SZ_1M + +#define OTG_BASE_ADDR (AIPS1_BASE_ADDR + 0x00080000) +#define GPIO1_BASE_ADDR (AIPS1_BASE_ADDR + 0x00084000) +#define GPIO2_BASE_ADDR (AIPS1_BASE_ADDR + 0x00088000) +#define GPIO3_BASE_ADDR (AIPS1_BASE_ADDR + 0x0008C000) +#define GPIO4_BASE_ADDR (AIPS1_BASE_ADDR + 0x00090000) +#define KPP_BASE_ADDR (AIPS1_BASE_ADDR + 0x00094000) +#define WDOG1_BASE_ADDR (AIPS1_BASE_ADDR + 0x00098000) +#define WDOG2_BASE_ADDR (AIPS1_BASE_ADDR + 0x0009C000) +#define GPT1_BASE_ADDR (AIPS1_BASE_ADDR + 0x000A0000) +#define SRTC_BASE_ADDR (AIPS1_BASE_ADDR + 0x000A4000) +#define IOMUXC_BASE_ADDR (AIPS1_BASE_ADDR + 0x000A8000) +#define EPIT1_BASE_ADDR (AIPS1_BASE_ADDR + 0x000AC000) +#define EPIT2_BASE_ADDR (AIPS1_BASE_ADDR + 0x000B0000) +#define PWM1_BASE_ADDR (AIPS1_BASE_ADDR + 0x000B4000) +#define PWM2_BASE_ADDR (AIPS1_BASE_ADDR + 0x000B8000) +#define UART1_BASE_ADDR (AIPS1_BASE_ADDR + 0x000BC000) +#define UART2_BASE_ADDR (AIPS1_BASE_ADDR + 0x000C0000) +#define SRC_BASE_ADDR (AIPS1_BASE_ADDR + 0x000D0000) +#define CCM_BASE_ADDR (AIPS1_BASE_ADDR + 0x000D4000) +#define GPC_BASE_ADDR (AIPS1_BASE_ADDR + 0x000D8000) + +/*! + * Defines for modules using static and dynamic DMA channels + */ +#define MXC_DMA_CHANNEL_IRAM 30 +#define MXC_DMA_CHANNEL_SPDIF_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART1_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART1_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART2_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART2_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART3_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_UART3_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MMC1 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MMC2 MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SSI1_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SSI1_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_SSI2_RX MXC_DMA_DYNAMIC_CHANNEL +#ifdef CONFIG_SDMA_IRAM +#define MXC_DMA_CHANNEL_SSI2_TX (MXC_DMA_CHANNEL_IRAM + 1) +#else /*CONFIG_SDMA_IRAM */ +#define MXC_DMA_CHANNEL_SSI2_TX MXC_DMA_DYNAMIC_CHANNEL +#endif /*CONFIG_SDMA_IRAM */ +#define MXC_DMA_CHANNEL_CSPI1_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI1_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI2_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI2_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI3_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_CSPI3_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ATA_RX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_ATA_TX MXC_DMA_DYNAMIC_CHANNEL +#define MXC_DMA_CHANNEL_MEMORY MXC_DMA_DYNAMIC_CHANNEL + +/* + * AIPS 2 + */ +#define AIPS2_BASE_ADDR 0x83F00000 +#define AIPS2_BASE_ADDR_VIRT 0xFB200000 +#define AIPS2_SIZE SZ_1M + +#define PLL1_BASE_ADDR (AIPS2_BASE_ADDR + 0x00080000) +#define PLL2_BASE_ADDR (AIPS2_BASE_ADDR + 0x00084000) +#define PLL3_BASE_ADDR (AIPS2_BASE_ADDR + 0x00088000) +#define AHBMAX_BASE_ADDR (AIPS2_BASE_ADDR + 0x00094000) +#define IIM_BASE_ADDR (AIPS2_BASE_ADDR + 0x00098000) +#define CSU_BASE_ADDR (AIPS2_BASE_ADDR + 0x0009C000) +#define ARM_BASE_ADDR (AIPS2_BASE_ADDR + 0x000A0000) +#define OWIRE_BASE_ADDR (AIPS2_BASE_ADDR + 0x000A4000) +#define FIRI_BASE_ADDR (AIPS2_BASE_ADDR + 0x000A8000) +#define CSPI2_BASE_ADDR (AIPS2_BASE_ADDR + 0x000AC000) +#define SDMA_BASE_ADDR (AIPS2_BASE_ADDR + 0x000B0000) +#define SCC_BASE_ADDR (AIPS2_BASE_ADDR + 0x000B4000) +#define ROMCP_BASE_ADDR (AIPS2_BASE_ADDR + 0x000B8000) +#define RTIC_BASE_ADDR (AIPS2_BASE_ADDR + 0x000BC000) +#define CSPI3_BASE_ADDR (AIPS2_BASE_ADDR + 0x000C0000) +#define I2C2_BASE_ADDR (AIPS2_BASE_ADDR + 0x000C4000) +#define I2C1_BASE_ADDR (AIPS2_BASE_ADDR + 0x000C8000) +#define SSI1_BASE_ADDR (AIPS2_BASE_ADDR + 0x000CC000) +#define AUDMUX_BASE_ADDR (AIPS2_BASE_ADDR + 0x000D0000) +#define M4IF_BASE_ADDR (AIPS2_BASE_ADDR + 0x000D8000) +#define ESDCTL_BASE_ADDR (AIPS2_BASE_ADDR + 0x000D9000) +#define WEIM_BASE_ADDR (AIPS2_BASE_ADDR + 0x000DA000) +#define NFC_BASE_ADDR (AIPS2_BASE_ADDR + 0x000DB000) +#define EMI_BASE_ADDR (AIPS2_BASE_ADDR + 0x000DBF00) +#define MIPI_HSC_BASE_ADDR (AIPS2_BASE_ADDR + 0x000DC000) +#define ATA_BASE_ADDR (AIPS2_BASE_ADDR + 0x000E0000) +#define SIM_BASE_ADDR (AIPS2_BASE_ADDR + 0x000E4000) +#define SSI3BASE_ADDR (AIPS2_BASE_ADDR + 0x000E8000) +#define FEC_BASE_ADDR (AIPS2_BASE_ADDR + 0x000EC000) +#define TVE_BASE_ADDR (AIPS2_BASE_ADDR + 0x000F0000) +#define VPU_BASE_ADDR (AIPS2_BASE_ADDR + 0x000F4000) +#define SAHARA_BASE_ADDR (AIPS2_BASE_ADDR + 0x000F8000) + +/* + * Memory regions and CS + */ +#define GPU_CTRL_BASE_ADDR 0x30000000 +#define IPU_CTRL_BASE_ADDR 0x40000000 +#define CSD0_BASE_ADDR 0x90000000 +#define CSD1_BASE_ADDR 0xA0000000 +#define CS0_BASE_ADDR 0xB0000000 +#define CS1_BASE_ADDR 0xB8000000 +#define CS2_BASE_ADDR 0xC0000000 +#define CS3_BASE_ADDR 0xC8000000 +#define CS4_BASE_ADDR 0xCC000000 +#define CS5_BASE_ADDR 0xCE000000 + +/*! + * This macro defines the physical to virtual address mapping for all the + * peripheral modules. It is used by passing in the physical address as x + * and returning the virtual address. If the physical address is not mapped, + * it returns 0xDEADBEEF + */ +#define IO_ADDRESS(x) \ + (void __force __iomem *) \ + ((((x) >= (unsigned long)IRAM_BASE_ADDR) && \ + ((x) < (unsigned long)IRAM_BASE_ADDR + IRAM_SIZE)) ? \ + IRAM_IO_ADDRESS(x):\ + (((x) >= (unsigned long)TZIC_BASE_ADDR) && \ + ((x) < (unsigned long)TZIC_BASE_ADDR + TZIC_SIZE)) ? \ + TZIC_IO_ADDRESS(x):\ + (((x) >= (unsigned long)DEBUG_BASE_ADDR) && \ + ((x) < (unsigned long)DEBUG_BASE_ADDR + DEBUG_SIZE)) ? \ + DEBUG_IO_ADDRESS(x):\ + (((x) >= (unsigned long)SPBA0_BASE_ADDR) && \ + ((x) < (unsigned long)SPBA0_BASE_ADDR + SPBA0_SIZE)) ? \ + SPBA0_IO_ADDRESS(x):\ + (((x) >= (unsigned long)AIPS1_BASE_ADDR) && \ + ((x) < (unsigned long)AIPS1_BASE_ADDR + AIPS1_SIZE)) ? \ + AIPS1_IO_ADDRESS(x):\ + (((x) >= (unsigned long)AIPS2_BASE_ADDR) && \ + ((x) < (unsigned long)AIPS2_BASE_ADDR + AIPS2_SIZE)) ? \ + AIPS2_IO_ADDRESS(x):\ + (((x) >= (unsigned long)NFC_BASE_ADDR_AXI) && \ + ((x) < (unsigned long)NFC_BASE_ADDR_AXI + NFC_AXI_SIZE)) ? \ + NFC_BASE_ADDR_AXI_IO_ADDRESS(x):\ + 0xDEADBEEF) + +/* + * define the address mapping macros: in physical address order + */ +#define IRAM_IO_ADDRESS(x) \ + (((x) - IRAM_BASE_ADDR) + IRAM_BASE_ADDR_VIRT) + +#define TZIC_IO_ADDRESS(x) \ + (((x) - TZIC_BASE_ADDR) + TZIC_BASE_ADDR_VIRT) + +#define DEBUG_IO_ADDRESS(x) \ + (((x) - DEBUG_BASE_ADDR) + DEBUG_BASE_ADDR_VIRT) + +#define SPBA0_IO_ADDRESS(x) \ + (((x) - SPBA0_BASE_ADDR) + SPBA0_BASE_ADDR_VIRT) + +#define AIPS1_IO_ADDRESS(x) \ + (((x) - AIPS1_BASE_ADDR) + AIPS1_BASE_ADDR_VIRT) + +#define AIPS2_IO_ADDRESS(x) \ + (((x) - AIPS2_BASE_ADDR) + AIPS2_BASE_ADDR_VIRT) + +#define NFC_BASE_ADDR_AXI_IO_ADDRESS(x) \ + (((x) - NFC_BASE_ADDR_AXI) + NFC_BASE_ADDR_AXI_VIRT) + +#define IS_MEM_DEVICE_NONSHARED(x) 0 + +/* + * DMA request assignments + */ +#define DMA_REQ_SSI3_TX1 47 +#define DMA_REQ_SSI3_RX1 46 +#define DMA_REQ_SPDIF 45 +#define DMA_REQ_UART3_TX 44 +#define DMA_REQ_UART3_RX 43 +#define DMA_REQ_SLIM_B_TX 42 +#define DMA_REQ_SDHC4 41 +#define DMA_REQ_SDHC3 40 +#define DMA_REQ_CSPI_TX 39 +#define DMA_REQ_CSPI_RX 38 +#define DMA_REQ_SSI3_TX2 37 +#define DMA_REQ_IPU 36 +#define DMA_REQ_SSI3_RX2 35 +#define DMA_REQ_EPIT2 34 +#define DMA_REQ_CTI2_1 33 +#define DMA_REQ_EMI_WR 32 +#define DMA_REQ_CTI2_0 31 +#define DMA_REQ_EMI_RD 30 +#define DMA_REQ_SSI1_TX1 29 +#define DMA_REQ_SSI1_RX1 28 +#define DMA_REQ_SSI1_TX2 27 +#define DMA_REQ_SSI1_RX2 26 +#define DMA_REQ_SSI2_TX1 25 +#define DMA_REQ_SSI2_RX1 24 +#define DMA_REQ_SSI2_TX2 23 +#define DMA_REQ_SSI2_RX2 22 +#define DMA_REQ_SDHC2 21 +#define DMA_REQ_SDHC1 20 +#define DMA_REQ_UART1_TX 19 +#define DMA_REQ_UART1_RX 18 +#define DMA_REQ_UART2_TX 17 +#define DMA_REQ_UART2_RX 16 +#define DMA_REQ_GPU 15 +#define DMA_REQ_EXTREQ1 14 +#define DMA_REQ_FIRI_TX 13 +#define DMA_REQ_FIRI_RX 12 +#define DMA_REQ_HS_I2C_RX 11 +#define DMA_REQ_HS_I2C_TX 10 +#define DMA_REQ_CSPI2_TX 9 +#define DMA_REQ_CSPI2_RX 8 +#define DMA_REQ_CSPI1_TX 7 +#define DMA_REQ_CSPI1_RX 6 +#define DMA_REQ_SLIM_B 5 +#define DMA_REQ_ATA_TX_END 4 +#define DMA_REQ_ATA_TX 3 +#define DMA_REQ_ATA_RX 2 +#define DMA_REQ_GPC 1 +#define DMA_REQ_VPU 0 + +/* + * Interrupt numbers + */ +#define MXC_INT_BASE 0 +#define MXC_INT_RESV0 0 +#define MXC_INT_MMC_SDHC1 1 +#define MXC_INT_MMC_SDHC2 2 +#define MXC_INT_MMC_SDHC3 3 +#define MXC_INT_MMC_SDHC4 4 +#define MXC_INT_RESV5 5 +#define MXC_INT_SDMA 6 +#define MXC_INT_IOMUX 7 +#define MXC_INT_NFC 8 +#define MXC_INT_VPU 9 +#define MXC_INT_IPU_ERR 10 +#define MXC_INT_IPU_SYN 11 +#define MXC_INT_GPU 12 +#define MXC_INT_RESV13 13 +#define MXC_INT_USB_H1 14 +#define MXC_INT_EMI 15 +#define MXC_INT_USB_H2 16 +#define MXC_INT_USB_H3 17 +#define MXC_INT_USB_OTG 18 +#define MXC_INT_SAHARA_H0 19 +#define MXC_INT_SAHARA_H1 20 +#define MXC_INT_SCC_SMN 21 +#define MXC_INT_SCC_STZ 22 +#define MXC_INT_SCC_SCM 23 +#define MXC_INT_SRTC_NTZ 24 +#define MXC_INT_SRTC_TZ 25 +#define MXC_INT_RTIC 26 +#define MXC_INT_CSU 27 +#define MXC_INT_SLIM_B 28 +#define MXC_INT_SSI1 29 +#define MXC_INT_SSI2 30 +#define MXC_INT_UART1 31 +#define MXC_INT_UART2 32 +#define MXC_INT_UART3 33 +#define MXC_INT_RESV34 34 +#define MXC_INT_RESV35 35 +#define MXC_INT_CSPI1 36 +#define MXC_INT_CSPI2 37 +#define MXC_INT_CSPI 38 +#define MXC_INT_GPT 39 +#define MXC_INT_EPIT1 40 +#define MXC_INT_EPIT2 41 +#define MXC_INT_GPIO1_INT7 42 +#define MXC_INT_GPIO1_INT6 43 +#define MXC_INT_GPIO1_INT5 44 +#define MXC_INT_GPIO1_INT4 45 +#define MXC_INT_GPIO1_INT3 46 +#define MXC_INT_GPIO1_INT2 47 +#define MXC_INT_GPIO1_INT1 48 +#define MXC_INT_GPIO1_INT0 49 +#define MXC_INT_GPIO1_LOW 50 +#define MXC_INT_GPIO1_HIGH 51 +#define MXC_INT_GPIO2_LOW 52 +#define MXC_INT_GPIO2_HIGH 53 +#define MXC_INT_GPIO3_LOW 54 +#define MXC_INT_GPIO3_HIGH 55 +#define MXC_INT_GPIO4_LOW 56 +#define MXC_INT_GPIO4_HIGH 57 +#define MXC_INT_WDOG1 58 +#define MXC_INT_WDOG2 59 +#define MXC_INT_KPP 60 +#define MXC_INT_PWM1 61 +#define MXC_INT_I2C1 62 +#define MXC_INT_I2C2 63 +#define MXC_INT_HS_I2C 64 +#define MXC_INT_RESV65 65 +#define MXC_INT_RESV66 66 +#define MXC_INT_SIM_IPB 67 +#define MXC_INT_SIM_DAT 68 +#define MXC_INT_IIM 69 +#define MXC_INT_ATA 70 +#define MXC_INT_CCM1 71 +#define MXC_INT_CCM2 72 +#define MXC_INT_GPC1 73 +#define MXC_INT_GPC2 74 +#define MXC_INT_SRC 75 +#define MXC_INT_NM 76 +#define MXC_INT_PMU 77 +#define MXC_INT_CTI_IRQ 78 +#define MXC_INT_CTI1_TG0 79 +#define MXC_INT_CTI1_TG1 80 +#define MXC_INT_MCG_ERR 81 +#define MXC_INT_MCG_TMR 82 +#define MXC_INT_MCG_FUNC 83 +#define MXC_INT_GPU2_IRQ 84 +#define MXC_INT_GPU2_BUSY 85 +#define MXC_INT_RESV86 86 +#define MXC_INT_FEC 87 +#define MXC_INT_OWIRE 88 +#define MXC_INT_CTI1_TG2 89 +#define MXC_INT_SJC 90 +#define MXC_INT_SPDIF 91 +#define MXC_INT_TVE 92 +#define MXC_INT_FIRI 93 +#define MXC_INT_PWM2 94 +#define MXC_INT_SLIM_EXP 95 +#define MXC_INT_SSI3 96 +#define MXC_INT_EMI_BOOT 97 +#define MXC_INT_CTI1_TG3 98 +#define MXC_INT_SMC_RX 99 +#define MXC_INT_VPU_IDLE 100 +#define MXC_INT_EMI_NFC 101 +#define MXC_INT_GPU_IDLE 102 + +/* gpio and gpio based interrupt handling */ +#define GPIO_DR 0x00 +#define GPIO_GDIR 0x04 +#define GPIO_PSR 0x08 +#define GPIO_ICR1 0x0C +#define GPIO_ICR2 0x10 +#define GPIO_IMR 0x14 +#define GPIO_ISR 0x18 +#define GPIO_INT_LOW_LEV 0x0 +#define GPIO_INT_HIGH_LEV 0x1 +#define GPIO_INT_RISE_EDGE 0x2 +#define GPIO_INT_FALL_EDGE 0x3 +#define GPIO_INT_NONE 0x4 + +#endif /* __ASM_ARCH_MXC_MX51_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/mxc.h b/arch/arm/plat-mxc/include/mach/mxc.h index 5fa2a07f4eaf..f1cbce461cac 100644 --- a/arch/arm/plat-mxc/include/mach/mxc.h +++ b/arch/arm/plat-mxc/include/mach/mxc.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright (C) 2008 Juergen Beisert (kernel@pengutronix.de) * * This program is free software; you can redistribute it and/or @@ -26,9 +26,13 @@ #define MXC_CPU_MX1 1 #define MXC_CPU_MX21 21 +#define MXC_CPU_MX25 25 #define MXC_CPU_MX27 27 #define MXC_CPU_MX31 31 +#define MXC_CPU_MX32 32 #define MXC_CPU_MX35 35 +#define MXC_CPU_MX37 37 +#define MXC_CPU_MX51 51 #ifndef __ASSEMBLY__ extern unsigned int __mxc_cpu_type; @@ -58,6 +62,18 @@ extern unsigned int __mxc_cpu_type; # define cpu_is_mx21() (0) #endif +#ifdef CONFIG_ARCH_MX25 +# ifdef mxc_cpu_type +# undef mxc_cpu_type +# define mxc_cpu_type __mxc_cpu_type +# else +# define mxc_cpu_type MXC_CPU_MX25 +# endif +# define cpu_is_mx25() (mxc_cpu_type == MXC_CPU_MX25) +#else +# define cpu_is_mx25() (0) +#endif + #ifdef CONFIG_MACH_MX27 # ifdef mxc_cpu_type # undef mxc_cpu_type @@ -94,6 +110,558 @@ extern unsigned int __mxc_cpu_type; # define cpu_is_mx35() (0) #endif +#ifdef CONFIG_ARCH_MX37 +# ifdef mxc_cpu_type +# undef mxc_cpu_type +# define mxc_cpu_type __mxc_cpu_type +# else +# define mxc_cpu_type MXC_CPU_MX37 +# endif +# define cpu_is_mx37() (mxc_cpu_type == MXC_CPU_MX37) +#else +# define cpu_is_mx37() (0) +#endif + +#ifdef CONFIG_ARCH_MX51 +# ifdef mxc_cpu_type +# undef mxc_cpu_type +# define mxc_cpu_type __mxc_cpu_type +# else +# define mxc_cpu_type MXC_CPU_MX51 +# endif +# define cpu_is_mx51() (mxc_cpu_type == MXC_CPU_MX51) +#else +# define cpu_is_mx51() (0) +#endif + +#define cpu_is_mx32() (0) + +/* + * Create inline functions to test for cpu revision + * Function name is cpu_is_<cpu name>_rev(rev) + * + * Returns: + * 0 - not the cpu queried + * 1 - cpu and revision match + * 2 - cpu matches, but cpu revision is greater than queried rev + * -1 - cpu matches, but cpu revision is less than queried rev + */ +#ifndef __ASSEMBLY__ +extern unsigned int system_rev; +#define mxc_set_system_rev(part, rev) ({ \ + system_rev = (part << 12) | rev; \ +}) +#define mxc_cpu() (system_rev >> 12) +#define mxc_cpu_rev() (system_rev & 0xFF) +#define mxc_cpu_rev_major() ((system_rev >> 4) & 0xF) +#define mxc_cpu_rev_minor() (system_rev & 0xF) +#define mxc_cpu_is_rev(rev) \ + ((mxc_cpu_rev() == rev) ? 1 : ((mxc_cpu_rev() < rev) ? -1 : 2)) +#define cpu_rev(type) \ +static inline int type## _rev (int rev) \ +{ \ + return (type() ? mxc_cpu_is_rev(rev) : 0); \ +} +cpu_rev(cpu_is_mx21); +cpu_rev(cpu_is_mx25); +cpu_rev(cpu_is_mx27); +cpu_rev(cpu_is_mx31); +cpu_rev(cpu_is_mx35); +cpu_rev(cpu_is_mx37); +cpu_rev(cpu_is_mx51); + + +#include <linux/types.h> + +/*! + * This structure is used to define the One wire platform data. + * It includes search rom accelerator. + */ +struct mxc_w1_config { + int search_rom_accelerator; +}; +/*! + * This structure is used to define the SPI master controller's platform + * data. It includes the SPI bus number and the maximum number of + * slaves/chips it supports. + */ +struct mxc_spi_master { + /*! + * SPI Master's bus number. + */ + unsigned int bus_num; + /*! + * SPI Master's maximum number of chip selects. + */ + unsigned int maxchipselect; + /*! + * CSPI Hardware Version. + */ + unsigned int spi_version; + /*! + * CSPI chipselect pin table. + * Workaround for ecspi chipselect pin may not keep correct level when + * idle. + */ + void (*chipselect_active) (int cspi_mode, int status, int chipselect); + void (*chipselect_inactive) (int cspi_mode, int status, int chipselect); +}; + +struct mxc_ipu_config { + int rev; + struct clk *di_clk[2]; +}; + +struct mxc_ir_platform_data { + int uart_ir_mux; + int ir_rx_invert; + int ir_tx_invert; + struct clk *uart_clk; +}; + +struct mxc_i2c_platform_data { + u32 i2c_clk; +}; + +/* + * This struct is to define the number of SSIs on a platform, + * DAM source port config, DAM external port config, + * regulator names, and other stuff audio needs. + */ +struct mxc_audio_platform_data { + int ssi_num; + int src_port; + int ext_port; + + int intr_id_hp; + int ext_ram; + struct clk *ssi_clk[2]; + char *regulator1; + char *regulator2; + + int hp_irq; + int (*hp_status) (void); + + char *vddio_reg; + char *vdda_reg; + char *vddd_reg; + int vddio; /* voltage of VDDIO (uv) */ + int vdda; /* voltage of vdda (uv) */ + int vddd; /* voltage of vddd (uv), 0 if not connected */ + int sysclk; + + int (*init) (void); /* board specific init */ + int (*amp_enable) (int enable); + int (*finit) (void); /* board specific finit */ + void *priv; /* used by board specific functions */ +}; + +struct mxc_spdif_platform_data { + int spdif_tx; + int spdif_rx; + int spdif_clk_44100; + int spdif_clk_48000; + int spdif_clkid; + struct clk *spdif_clk; + struct clk *spdif_core_clk; + struct clk *spdif_audio_clk; +}; + +struct mxc_asrc_platform_data { + struct clk *asrc_core_clk; + struct clk *asrc_audio_clk; + unsigned int channel_bits; +}; + +struct mxc_bt_platform_data { + char *bt_vdd; + char *bt_vdd_parent; + char *bt_vusb; + char *bt_vusb_parent; + void (*bt_reset) (void); +}; + +struct mxc_lightsensor_platform_data { + char *vdd_reg; + int rext; +}; + +struct mxc_fb_platform_data { + struct fb_videomode *mode; + char *mode_str; + u32 interface_pix_fmt; +}; + +struct mxc_lcd_platform_data { + char *io_reg; + char *core_reg; + char *analog_reg; + void (*reset) (void); +}; + +struct mxc_dvfs_platform_data { + /** Supply voltage regulator name string */ + char *reg_id; + /* CPU clock name string */ + char *clk1_id; + /* DVFS clock name string */ + char *clk2_id; + /* GPC control reg address */ + void __iomem *gpc_cntr_reg_addr; + /* GPC voltage counter reg address */ + void __iomem *gpc_vcr_reg_addr; + /* CCM DVFS control reg address */ + void __iomem *ccm_cdcr_reg_addr; + /* CCM ARM clock root reg address */ + void __iomem *ccm_cacrr_reg_addr; + /* CCM divider handshake in-progree reg address */ + void __iomem *ccm_cdhipr_reg_addr; + /* DVFS threshold reg address */ + void __iomem *dvfs_thrs_reg_addr; + /* DVFS counters reg address */ + void __iomem *dvfs_coun_reg_addr; + /* DVFS EMAC reg address */ + void __iomem *dvfs_emac_reg_addr; + /* DVFS control reg address */ + void __iomem *dvfs_cntr_reg_addr; + /* PREDIV mask */ + u32 prediv_mask; + /* PREDIV offset */ + int prediv_offset; + /* PREDIV value */ + int prediv_val; + /* DIV3CK mask */ + u32 div3ck_mask; + /* DIV3CK offset */ + int div3ck_offset; + /* DIV3CK value */ + int div3ck_val; + /* EMAC value */ + int emac_val; + /* Frequency increase threshold. Increase frequency change request + will be sent if DVFS counter value will be more than this value */ + int upthr_val; + /* Frequency decrease threshold. Decrease frequency change request + will be sent if DVFS counter value will be less than this value */ + int dnthr_val; + /* Panic threshold. Panic frequency change request + will be sent if DVFS counter value will be more than this value */ + int pncthr_val; + /* The amount of times the up threshold should be exceeded + before DVFS will trigger frequency increase request */ + int upcnt_val; + /* The amount of times the down threshold should be exceeded + before DVFS will trigger frequency decrease request */ + int dncnt_val; + /* Delay time in us */ + int delay_time; + /* Number of woking points supported */ + int num_wp; +}; + +struct mxc_tsc_platform_data { + char *vdd_reg; + int penup_threshold; + void (*active) (void); + void (*inactive) (void); +}; + +struct mxc_tvout_platform_data { + char *io_reg; + char *core_reg; + char *analog_reg; + u32 detect_line; +}; + +struct mxc_tvin_platform_data { + char *dvddio_reg; + char *dvdd_reg; + char *avdd_reg; + char *pvdd_reg; + void (*pwdn) (int pwdn); + void (*reset) (void); +}; + +/*! Platform data for the IDE drive structure. */ +struct mxc_ide_platform_data { + char *power_drive; /*!< The power pointer */ + char *power_io; /*!< The power pointer */ +}; + +struct mxc_camera_platform_data { + char *core_regulator; + char *io_regulator; + char *analog_regulator; + char *gpo_regulator; + u32 mclk; + u32 csi; +}; + +/*gpo1-3 is in fixed state by hardware design, + * only deal with reset pin and clock_enable pin + * only poll mode can be used to control the chip, + * interrupt mode is not supported by 3ds*/ +struct mxc_fm_platform_data { + char *reg_vio; + char *reg_vdd; + void (*gpio_get) (void); + void (*gpio_put) (void); + void (*reset) (void); + void (*clock_ctl) (int flag); + u8 sksnr; /*0,disable;1,most stop;0xf,fewest stop*/ + u8 skcnt; /*0,disable;1,most stop;0xf,fewest stop*/ + /* + 00 = 87.5-108 MHz (USA,Europe) (Default). + 01 = 76-108 MHz (Japan wide band). + 10 = 76-90 MHz (Japan). + 11 = Reserved. + */ + u8 band; + /* + 00 = 200 kHz (USA, Australia) (default). + 01 = 100 kHz (Europe, Japan). + 10 = 50 kHz. + */ + u8 space; + u8 seekth; +}; + +struct mxc_mma7450_platform_data { + char *reg_dvdd_io; + char *reg_avdd; + void (*gpio_pin_get) (void); + void (*gpio_pin_put) (void); + int int1; + int int2; +}; + +struct mxc_keyp_platform_data { + u16 *matrix; + void (*active) (void); + void (*inactive) (void); + char *vdd_reg; +}; + +struct mxc_unifi_platform_data { + void (*hardreset) (int pin_level); + void (*enable) (int en); + /* power parameters */ + char *reg_gpo1; + char *reg_gpo2; + char *reg_1v5_ana_bb; + char *reg_vdd_vpa; + char *reg_1v5_dd; + + int host_id; + + void *priv; +}; + +struct mxc_gps_platform_data { + char *core_reg; + char *analog_reg; + struct regulator *gps_regu_core; + struct regulator *gps_regu_analog; +}; + +struct mxc_mlb_platform_data { + u32 buf_address; + u32 phy_address; + char *reg_nvcc; + char *mlb_clk; +}; + +struct flexcan_platform_data { + char *core_reg; + char *io_reg; + void (*xcvr_enable) (int id, int en); + void (*active) (int id); + void (*inactive) (int id); +}; + +struct mxc_srtc_platform_data { + u32 srtc_sec_mode_addr; +}; + +struct tve_platform_data { + char *dac_reg; + char *dig_reg; +}; + +/* The name that links the i.MX NAND Flash Controller driver to its devices. */ + +#define IMX_NFC_DRIVER_NAME ("imx_nfc") + +/* Resource names for the i.MX NAND Flash Controller driver. */ + +#define IMX_NFC_BUFFERS_ADDR_RES_NAME \ + ("i.MX NAND Flash Controller Buffer") +#define IMX_NFC_PRIMARY_REGS_ADDR_RES_NAME \ + ("i.MX NAND Flash Controller Primary Registers") +#define IMX_NFC_SECONDARY_REGS_ADDR_RES_NAME \ + ("i.MX NAND Flash Controller Secondary Registers") +#define IMX_NFC_INTERRUPT_RES_NAME \ + ("i.MX NAND Flash Controller Interrupt") + +/** + * struct imx_nfc_platform_data - i.MX NFC driver platform data. + * + * This structure communicates information to the i.MX NFC driver that can't be + * expressed as resources. + * + * @nfc_major_version: The "major version" of the NFC hardware. + * @nfc_minor_version: The "minor version" of the NFC hardware. + * @force_ce: If true, this flag causes the driver to assert the + * hardware chip enable signal for the currently selected + * chip as long as the MTD NAND Flash HAL has the chip + * selected (not just when an I/O transaction is in + * progress). + * @target_cycle_in_ns: The target read and write cycle period, in nanoseconds. + * NAND Flash part data sheets give minimum times for read + * and write cycles in nanoseconds (usually tRC and tWC, + * respectively). Set this value to the maximum of these + * two parameters. The driver will set the NFC clock as + * close as possible without violating this value. + * @clock_name: The name of the clock used by the NAND Flash controller. + * @init: A pointer to a function the driver must call so the + * platform can prepare for this device to operate. This + * pointer may be NULL. + * @exit: A pointer to a function the driver must call so the + * platform clean up after this device stops operating. + * This pointer may be NULL. + * @set_page_size: A pointer to a function the driver can call to set the + * page size. This pointer may be NULL. + * + * For some i.MX SoC's, the NFC gets information about the + * page size from signals driven by a system register + * outside the NFC. The address and format of this external + * register varies across SoC's. In other SoC's, the NFC + * still receives this signal, but it is overridden by a + * page size register in the NFC itself. + * + * For SoC's where the page size *must* be set in an + * external register, the driver must rely on a platform- + * specific function, and this member must point to it. + * + * For SoC's where the NFC has its own page size register, + * the driver will set that register itself and ignore the + * external signals. In this case, there's no need for the + * platform-specific function and this member must be NULL. + * + * This function accepts the page size in bytes (MTD calls + * this the "writesize") discovered by the NAND Flash MTD + * base driver (e.g., 512, 2048, 4096). This size refers + * specifically to the the data bytes in the page, *not* + * including out-of-band bytes. The return value is zero if + * the operation succeeded. The driver does *not* view a + * non-zero value as an error code - only an indication of + * failure. The driver will decide for itself what error + * code to return to its caller. + * @interleave: Indicates that the driver should "interleave" the NAND + * Flash chips it finds. If true, the driver will aggregate + * the chips "horizontally" such that MTD will see a single + * chip with a potentially very large page size. This can + * improve write performance for some applications. + * @partitions: An optional pointer to an array of partitions. If this + * is NULL, the driver will create a single MTD that + * represents the entire medium. + * @partition_count: The number of elements in the partition array. + */ + +struct imx_nfc_platform_data { + unsigned int nfc_major_version; + unsigned int nfc_minor_version; + int force_ce; + unsigned int target_cycle_in_ns; + char *clock_name; + int (*init)(void); + void (*exit)(void); + int (*set_page_size)(unsigned int data_size_in_bytes); + int interleave; + struct mtd_partition *partitions; + unsigned int partition_count; +}; + +extern void mxc_wd_reset(void); +unsigned long board_get_ckih_rate(void); + +int mxc_snoop_set_config(u32 num, unsigned long base, int size); +int mxc_snoop_get_status(u32 num, u32 * statl, u32 * stath); + +struct platform_device; +void mxc_pg_enable(struct platform_device *pdev); +void mxc_pg_disable(struct platform_device *pdev); + +struct mxc_unifi_platform_data *get_unifi_plat_data(void); + +struct mxc_sim_platform_data { + unsigned int clk_rate; + char *clock_sim; + char *power_sim; + int (*init)(struct platform_device *pdev); + void (*exit)(void); + unsigned int detect; /* 1 have detect pin, 0 not */ +}; + +#endif /* __ASSEMBLY__ */ + +#define MUX_IO_P 29 +#define MUX_IO_I 24 +#define IOMUX_TO_GPIO(pin) ((((unsigned int)pin >> MUX_IO_P) * 32) + ((pin >> MUX_IO_I) & ((1 << (MUX_IO_P - MUX_IO_I)) -1))) +#define IOMUX_TO_IRQ(pin) (MXC_GPIO_IRQ_START + IOMUX_TO_GPIO(pin)) + +/* DMA driver defines */ +#define MXC_IDE_DMA_WATERMARK 32 /* DMA watermark level in bytes */ +#define MXC_IDE_DMA_BD_NR (512/3/4) /* Number of BDs per channel */ + +#ifndef IS_MEM_DEVICE_NONSHARED +/* all peripherals on MXC so far are below 0x80000000 but leave L2CC alone */ +#define IS_MEM_DEVICE_NONSHARED(x) ((x) < 0x80000000 && (x) != L2CC_BASE_ADDR) +#endif +/*! + * DPTC GP and LP ID + */ +#define DPTC_GP_ID 0 +#define DPTC_LP_ID 1 + +#ifndef __ASSEMBLY__ + +struct cpu_wp { + u32 pll_reg; + u32 pll_rate; + u32 cpu_rate; + u32 pdr0_reg; + u32 pdf; + u32 mfi; + u32 mfd; + u32 mfn; + u32 cpu_voltage; + u32 cpu_podf; +}; + +#ifndef CONFIG_ARCH_MX51 +struct cpu_wp *get_cpu_wp(int *wp); +#endif + +enum mxc_cpu_pwr_mode { + WAIT_CLOCKED, /* wfi only */ + WAIT_UNCLOCKED, /* WAIT */ + WAIT_UNCLOCKED_POWER_OFF, /* WAIT + SRPG */ + STOP_POWER_ON, /* just STOP */ + STOP_POWER_OFF, /* STOP + SRPG */ +}; + +void mxc_cpu_lp_set(enum mxc_cpu_pwr_mode mode); +int tzic_enable_wake(int is_idle); +void gpio_activate_audio_ports(void); +void gpio_inactivate_audio_ports(void); +void gpio_activate_bt_audio_port(void); +void gpio_inactivate_bt_audio_port(void); +void gpio_activate_esai_ports(void); +void gpio_deactivate_esai_ports(void); + +#endif + #if defined(CONFIG_ARCH_MX3) || defined(CONFIG_ARCH_MX2) #define CSCR_U(n) (IO_ADDRESS(WEIM_BASE_ADDR) + n * 0x10) #define CSCR_L(n) (IO_ADDRESS(WEIM_BASE_ADDR) + n * 0x10 + 0x4) diff --git a/arch/arm/plat-mxc/include/mach/mxc_dptc.h b/arch/arm/plat-mxc/include/mach/mxc_dptc.h new file mode 100644 index 000000000000..b42fc36af3e1 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/mxc_dptc.h @@ -0,0 +1,111 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup DPTC Dynamic Process and Temperatur Compensation (DPTC) Driver + */ + +/*! + * @file arch-mxc/mxc_dptc.h + * + * @brief This file contains the DPTC configuration structure definition. + * + * + * @ingroup DPTC + */ + +#ifndef __ASM_ARCH_MXC_DPTC_H__ +#define __ASM_ARCH_MXC_DPTC_H__ + +#ifdef __KERNEL__ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/workqueue.h> +#include <linux/device.h> + +#define DPTC_WP_SUPPORTED 17 +#define DPTC_GP_WP_SUPPORTED 7 +#define DPTC_LP_WP_SUPPORTED 9 + +struct dptc_wp { + u32 dcvr0; + u32 dcvr1; + u32 dcvr2; + u32 dcvr3; + u32 voltage; +}; + +/*! + * This structure is used to define the dptc controller's platform + * data. It includes the regulator name string and DPTC clock name string. + */ +struct mxc_dptc_data { + /** Regulator name string */ + char *reg_id; + /* DPTC clock name string */ + char *clk_id; + /* Control reg address */ + unsigned int dptccr_reg_addr; + /* Comparator value reg 0 address */ + unsigned int dcvr0_reg_addr; + /* GPC control reg address */ + unsigned int gpc_cntr_reg_addr; + /* DPTC interrupt status bit */ + unsigned int dptccr; + /* The number of DPTC working points */ + unsigned int dptc_wp_supported; + /* Maximum value of DPTC clock rate */ + unsigned long clk_max_val; + /* DPTC working points */ + struct dptc_wp *dptc_wp_allfreq; + /* DPTC enable bit */ + u32 dptc_enable_bit; + /* DPTC ADU bit */ + int gpc_adu; + /* VAI mask */ + u32 vai_mask; + /* VAI offset */ + int vai_offset; + /* Mask DPTC interrupt */ + u32 irq_mask; + /* DPTC no voltage change request bit */ + u32 dptc_nvcr_bit; + /* ARM interrrupt bit */ + u32 gpc_irq_bit; + /* dptc init config */ + u32 init_config; + /* dptc enable config */ + u32 enable_config; + /* dptc counting range mask */ + u32 dcr_mask; +}; + +/*! + * This function is called to put the DPTC in a low power state. + * + * @param id The DPTC device id. DPTC_GP_ID is for DPTC GP; + * DPTC_LP_ID is for DPTC LP + */ +void dptc_suspend(int id); +/*! + * This function is called to resume the DPTC from a low power state. + * + * @param id The DPTC device id. DPTC_GP_ID is for DPTC GP; + * DPTC_LP_ID is for DPTC LP + */ +void dptc_resume(int id); + +#endif /* __KERNEL__ */ + +#endif /* __ASM_ARCH_MXC_DPTC_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/mxc_dvfs.h b/arch/arm/plat-mxc/include/mach/mxc_dvfs.h new file mode 100644 index 000000000000..99ddb077d9d1 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/mxc_dvfs.h @@ -0,0 +1,51 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup DVFS Dynamic Voltage and Frequency Scaling (DVFS) Driver + */ + +/*! + * @file arch-mxc/mxc_dvfs.h + * + * @brief This file contains the DVFS configuration structure definition. + * + * + * @ingroup DVFS + */ + +#ifndef __ASM_ARCH_MXC_DVFS_H__ +#define __ASM_ARCH_MXC_DVFS_H__ + +#ifdef __KERNEL__ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/workqueue.h> +#include <linux/device.h> + +/* + * DVFS structure + */ +struct dvfs_wp { + int upthr; + int downthr; + int panicthr; + int upcnt; + int downcnt; + int emac; +}; + +#endif /* __KERNEL__ */ + +#endif /* __ASM_ARCH_MXC_DVFS_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/mxc_edid.h b/arch/arm/plat-mxc/include/mach/mxc_edid.h new file mode 100644 index 000000000000..aac49ad93ec5 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/mxc_edid.h @@ -0,0 +1,33 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxc_edid.h + * + * @brief MXC EDID tools + * + * @ingroup Framebuffer + */ + +#ifndef MXC_EDID_H +#define MXC_EDID_H + +int read_edid(struct i2c_adapter *adp, + struct fb_var_screeninfo *einfo, + int *dvi); + +#endif diff --git a/arch/arm/plat-mxc/include/mach/mxc_gpc.h b/arch/arm/plat-mxc/include/mach/mxc_gpc.h new file mode 100644 index 000000000000..d3b3ae2d1b13 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/mxc_gpc.h @@ -0,0 +1,74 @@ + +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup LPMD Low-Level Power Management Driver + */ + +/*! + * @file arch-mxc/mxc_gpc.h + * + * @brief This file contains the chip level configuration details and + * public API declarations for GPC module + * + * @ingroup LPMD + */ + +#ifndef __ASM_ARCH_MXC_GPC_H__ +#define __ASM_ARCH_MXC_GPC_H__ + +/* AP Power Gating modules */ +typedef enum { + POWER_GATING_MODULE_AP_EMBEDDED_MEM_DEEPSLEEP, + POWER_GATING_MODULE_DISPLAY_BUFFER, + POWER_GATING_MODULE_EMI_DEEPSLEEP, + POWER_GATING_MODULE_IPU_STOP, + POWER_GATING_MODULE_L2_MEM_STOP, + POWER_GATING_MODULE_ARM_PLATFORM_STOP, +} mxc_pm_ap_power_gating_modules_t; + +/* AP Power Gating pull-down config of modules */ +typedef enum { + POWER_GATING_PULL_DOWN_DISPLAY_BUFFER, + POWER_GATING_PULL_DOWN_EMI, + POWER_GATING_PULL_DOWN_IPU, + POWER_GATING_PULL_DOWN_L2_MEM, + POWER_GATING_PULL_DOWN_ARMPLATFORM, +} mxc_pm_ap_power_gating_pulldown_t; + +/*! + * This function enables/disables the AP power gating by writing the APPCR + * register of the GPC module. + * + * @param enable Enable/Disable module power down + * 0 - disable; 1 - enable + * @param modules The desired module to be power gated + * + */ +void mxc_gpc_powergate_module(int enable, + mxc_pm_ap_power_gating_modules_t module); + +/*! + * This function enables/disables the AP power gating pull down selection of a + * module by writing the APPCR register of the GPC module. + * + * @param enable Enable/Disable module pull down + * 0 - disable; 1 - enable + * @param modules The desired module to be pulled down + * + */ +void mxc_gpc_powergate_pulldown(int enable, + mxc_pm_ap_power_gating_pulldown_t pulldown); + +#endif diff --git a/arch/arm/plat-mxc/include/mach/mxc_pm.h b/arch/arm/plat-mxc/include/mach/mxc_pm.h new file mode 100644 index 000000000000..b4d389a6611e --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/mxc_pm.h @@ -0,0 +1,252 @@ + +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup LPMD Low-Level Power Management Driver + */ + +/*! + * @file arch-mxc/mxc_pm.h + * + * @brief This file contains the chip level configuration details and + * public API declarations for CRM_AP module + * + * @ingroup LPMD + */ + +#ifndef __ASM_ARCH_MXC_PM_H__ +#define __ASM_ARCH_MXC_PM_H__ + +#define WAIT_MODE 111 +#define DOZE_MODE 112 +#define STOP_MODE 113 +#define DSM_MODE 114 +/* + * MXC91231 Break-Point Frequency below which is low frequency and + * above which is high frequency + */ +#define BREAKPT_FREQ ((long)(400000000)) + +#define GATE_STOP_WAIT 9 +#define GATE_STOP 10 + +/* + * Used for MHz conversion + */ +#define MEGA_HERTZ 1000000 + +/* + * If invalid frequency value other than the following + * CORE_133 - ARM desired to run @133MHz, LoV (1.2V) + * CORE_266 - ARM desired to run @266MHz, LoV (1.2V) + * CORE_399 - ARM desired to run @399MHz, LoV (1.2V) + * CORE_532 - ARM desired to run @133MHz, HiV (1.6V) + * are passed then this error is returned, + */ +#define ERR_FREQ_INVALID 1 + +/* + * For MXC91231 Pass1, Integer DVFS greater than 133MHz is not allowed + * due to the hardware issue + */ +#define INTEGER_DVFS_NOT_ALLOW 1 + +/* + * If PLL freq is less than desired ARM frequency during Integer + * DVFS, then return this error + */ +#define PLL_LESS_ARM_ERR 2 + +/* + * Frequency change within the same-lo voltage is not approved. + * Inorder to do Integer DFS, move to the high voltage range and + * then set LFDF and move to the low voltage range + */ +#define INT_DFS_LOW_NOT_ALLOW 3 + +/* + * If the desired AHB or IPG exceeds 133MHz or 66.5MHz respectively, + * then return this error + */ +#define AHB_IPG_EXCEED_LIMIT 4 + +/* + * If the desired ARM frequency is too low to get by PLL scaling + * and the mxc_pm_pllscale API is called, return this error: + */ +#define PLL_DVFS_FREQ_TOO_LOW 5 + +/* + * Invalid frequencies requested + */ +#define MXC_PM_INVALID_PARAM 6 + +/* + * If AHB and/or IPG frequencies are greater than maximum allowed + */ +#define FREQ_OUT_OF_RANGE 2 + +/* + * If AHB and/or IPG frequencies are other than 100 or 50Mhz + */ +#define BUS_FREQ_INVALID 2 + +/* + * If MAX_PDF is greater than max value (8) then return this error + */ +#define AHB_MAX_DIV_ERR 3 + +/* + * If IPG_PDF is greater than max value (2) then return this error + */ +#define IPG_MAX_DIV_ERR 4 + +/* + * If ARM freq is out of range i.e., less than 133 or greater than + * 399 then return this error + */ +#define INVALID_ARM_FREQ 5 + +/* + * This file includes all platform APIs. Some of the APIs are not + * appicable to some platforms. So, this error is used to indicate + * that a particular API is not available + */ +#define MXC_PM_API_NOT_SUPPORTED 6 + +/* + * Error when frequency scaling is attempted while switch between MPLL and + * TPLL is in progress on MXC91321 + */ +#define ERR_DFSP_SWITCH 2 + +/*! + * Additional define for stop mode + */ +#define PM_SUSPEND_STOP ((__force suspend_state_t) 2) + +/*! + * CKOH pins configuration + */ +#define CKOH_AP_SEL 1 +#define CKOH_AHB_SEL 2 +#define CKOH_IP_SEL 3 + +/*! + * Defines for Stop and DSM mode acknowledgements + */ +#define MXC_PM_LOWPWR_ACK_SDMA 0x01 +#define MXC_PM_LOWPWR_ACK_IPU 0x02 +#define MXC_PM_LOWPWR_ACK_MAX 0x04 +#define MXC_PM_LOWPWR_ACK_MQSPI 0x08 +#define MXC_PM_LOWPWR_ACK_USB 0x10 +#define MXC_PM_LOWPWR_ACK_RTIC 0x20 + +/* + * PMIC configuration + */ +#define MXC_PMIC_1_2_VOLT 0xC +#define MXC_PMIC_1_6_VOLT 0x1C +#define MXC_PMIC_1_0_VOLT 0x4 +#if defined(CONFIG_ARCH_MXC91321) || defined(CONFIG_ARCH_MXC91231) +#define MXC_PMIC_DVS_SPEED 0x1 +#else +#define MXC_PMIC_DVS_SPEED 0x3 +#endif + +/*! + * Implementing Level 1 CRM Gate Control. Level 2 gate control + * is provided at module level using LPMD registers + * + * @param group The desired clock gate control register bits. + * Possible values are 0 through 6 + * @param opt The desired option requesting clock to run during stop + * and wait modes or just during the stop mode. Possible + * values are GATE_STOP_WAIT and GATE_STOP. + * + */ +void mxc_pm_clockgate(int group, int opt); + +/*! + * Implementing steps required to transition to low-power modes + * + * @param mode The desired low-power mode. Possible values are, + * WAIT_MODE, STOP_MODE or DSM_MODE + * + */ +void mxc_pm_lowpower(int mode); + +/*! + * Enables acknowledgement from module when entering stop or DSM mode. + * + * @param ack The desired module acknowledgement to enable. + * + */ +void mxc_pm_lp_ack_enable(int ack); + +/*! + * Disables acknowledgement from module when entering stop or DSM mode. + * + * @param ack The desired module acknowledgement to disable. + * + */ +void mxc_pm_lp_ack_disable(int ack); + +/*! + * Implementing steps required to set Integer Scaling + * + * @param armfreq The desired ARM frequency. AHB and IP + * frequency are changed depending on ARM + * frequency and the divider values. + * @param ahbfreq The desired AHB frequency + * @param ipfreq The desired IP frequency + * + * @return Returns 0 on success or + * Returns -PLL_LESS_ARM_ERR if pllfreq is less than + * desired core freq + */ +int mxc_pm_intscale(long armfreq, long ahbfreq, long ipfreq); + +/*! + * To calculate MFI, MFN, MFD values. Using this the output frequency + * whose value is calculated using, + * 2 * REF_FREQ * (MF / PDF), where + * REF_FREQ is 26 Mhz + * MF = MFI + (MFN + MFD) + * PDF is assumed to be 1 + * + * @param armfreq The desired ARM frequency + * @param ahbfreq The desired AHB frequency + * @param ipfreq The desired IP frequency + * + * @return Returns 0 on success or + * Returns -1 on error + */ +int mxc_pm_pllscale(long armfreq, long ahbfreq, long ipfreq); + +/*! + * To change AP core frequency and/or voltage suitably + * + * @param armfreq The desired ARM frequency + * @param ahbfreq The desired AHB frequency + * @param ipfreq The desired IP frequency + * + * @return Returns -ERR_FREQ_INVALID on failure + * Returns 0 on success + */ +int mxc_pm_dvfs(unsigned long armfreq, long ahbfreq, long ipfreq); + +extern void mxc_pm_arch_entry(void *entry, u32 size); + +#endif diff --git a/arch/arm/plat-mxc/include/mach/mxc_scc.h b/arch/arm/plat-mxc/include/mach/mxc_scc.h new file mode 100644 index 000000000000..d23f6c3e9913 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/mxc_scc.h @@ -0,0 +1,45 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file arch-mxc/mxc_scc.h + * + * @brief This is intended to be the file which contains all of code or changes + * needed to port the driver. + * + * @ingroup MXCSCC + */ + +#ifndef __ASM_ARCH_MXC_SCC_H__ +#define __ASM_ARCH_MXC_SCC_H__ + +#include <mach/hardware.h> + +/*! + * Expected to come from platform header files. + * This symbol must be the address of the SCC + */ +#define SCC_BASE SCC_BASE_ADDR + +/*! + * This must be the interrupt line number of the SCM interrupt. + */ +#define INT_SCC_SCM MXC_INT_SCC_SCM + +/*! + * if #USE_SMN_INTERRUPT is defined, this must be the interrupt line number of + * the SMN interrupt. + */ +#define INT_SCC_SMN MXC_INT_SCC_SMN + +#endif diff --git a/arch/arm/plat-mxc/include/mach/mxc_timer.h b/arch/arm/plat-mxc/include/mach/mxc_timer.h new file mode 100644 index 000000000000..59fe2dceeb07 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/mxc_timer.h @@ -0,0 +1,157 @@ +/* + * mxc_timer.h + * + * Copyright (C) 2008 Juergen Beisert (kernel@pengutronix.de) + * + * Platform independent (i.MX1, i.MX2, i.MX3) definition for timer handling. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PLAT_MXC_TIMER_H +#define __PLAT_MXC_TIMER_H + +#include <linux/clk.h> +#include <mach/hardware.h> + +#ifdef CONFIG_ARCH_IMX +#define TIMER_BASE IO_ADDRESS(TIM1_BASE_ADDR) +#define TIMER_INTERRUPT TIM1_INT + +#define TCTL_VAL TCTL_CLK_PCLK1 +#define TCTL_IRQEN (1<<4) +#define TCTL_FRR (1<<8) +#define TCTL_CLK_PCLK1 (1<<1) +#define TCTL_CLK_PCLK1_4 (2<<1) +#define TCTL_CLK_TIN (3<<1) +#define TCTL_CLK_32 (4<<1) + +#define MXC_TCTL 0x00 +#define MXC_TPRER 0x04 +#define MXC_TCMP 0x08 +#define MXC_TCR 0x0c +#define MXC_TCN 0x10 +#define MXC_TSTAT 0x14 +#define TSTAT_CAPT (1<<1) +#define TSTAT_COMP (1<<0) + +static inline void gpt_irq_disable(void) +{ + unsigned int tmp; + + tmp = __raw_readl(TIMER_BASE + MXC_TCTL); + __raw_writel(tmp & ~TCTL_IRQEN, TIMER_BASE + MXC_TCTL); +} + +static inline void gpt_irq_enable(void) +{ + __raw_writel(__raw_readl(TIMER_BASE + MXC_TCTL) | TCTL_IRQEN, + TIMER_BASE + MXC_TCTL); +} + +static void gpt_irq_acknowledge(void) +{ + __raw_writel(0, TIMER_BASE + MXC_TSTAT); +} + +#elif defined(CONFIG_ARCH_MX2) +#define TIMER_BASE IO_ADDRESS(GPT1_BASE_ADDR) +#define TIMER_INTERRUPT MXC_INT_GPT1 + +#define MXC_TCTL 0x00 +#define TCTL_VAL TCTL_CLK_PCLK1 +#define TCTL_CLK_PCLK1 (1<<1) +#define TCTL_CLK_PCLK1_4 (2<<1) +#define TCTL_IRQEN (1<<4) +#define TCTL_FRR (1<<8) +#define MXC_TPRER 0x04 +#define MXC_TCMP 0x08 +#define MXC_TCR 0x0c +#define MXC_TCN 0x10 +#define MXC_TSTAT 0x14 +#define TSTAT_CAPT (1<<1) +#define TSTAT_COMP (1<<0) + +static inline void gpt_irq_disable(void) +{ + unsigned int tmp; + + tmp = __raw_readl(TIMER_BASE + MXC_TCTL); + __raw_writel(tmp & ~TCTL_IRQEN, TIMER_BASE + MXC_TCTL); +} + +static inline void gpt_irq_enable(void) +{ + __raw_writel(__raw_readl(TIMER_BASE + MXC_TCTL) | TCTL_IRQEN, + TIMER_BASE + MXC_TCTL); +} + +static void gpt_irq_acknowledge(void) +{ + __raw_writel(TSTAT_CAPT | TSTAT_COMP, TIMER_BASE + MXC_TSTAT); +} + +#else +#define TIMER_BASE IO_ADDRESS(GPT1_BASE_ADDR) +#define TIMER_INTERRUPT MXC_INT_GPT + +#define MXC_TCTL 0x00 +#define TCTL_VAL (TCTL_CLK_HIGH_FREQ | TCTL_WAITEN) +#define TCTL_CLK_IPG (1<<6) +#define TCTL_CLK_HIGH_FREQ (2<<6) +#define TCTL_FRR (1<<9) +#define TCTL_WAITEN (1<<3) + +#define MXC_TPRER 0x04 +#define MXC_TSTAT 0x08 +#define TSTAT_OF1 (1<<0) +#define TSTAT_OF2 (1<<1) +#define TSTAT_OF3 (1<<2) +#define TSTAT_IF1 (1<<3) +#define TSTAT_IF2 (1<<4) +#define TSTAT_ROV (1<<5) +#define MXC_IR 0x0c +#define MXC_TCMP 0x10 +#define MXC_TCMP2 0x14 +#define MXC_TCMP3 0x18 +#define MXC_TCR 0x1c +#define MXC_TCN 0x24 + +static inline void gpt_irq_disable(void) +{ + __raw_writel(0, TIMER_BASE + MXC_IR); +} + +static inline void gpt_irq_enable(void) +{ + __raw_writel(1<<0, TIMER_BASE + MXC_IR); +} + +static inline void gpt_irq_acknowledge(void) +{ + __raw_writel(TSTAT_OF1, TIMER_BASE + MXC_TSTAT); +} +#endif /* CONFIG_ARCH_MX3 */ + +#define TCTL_SWR (1<<15) +#define TCTL_CC (1<<10) +#define TCTL_OM (1<<9) +#define TCTL_CAP_RIS (1<<6) +#define TCTL_CAP_FAL (2<<6) +#define TCTL_CAP_RIS_FAL (3<<6) +#define TCTL_CAP_ENA (1<<5) +#define TCTL_TEN (1<<0) + +#endif diff --git a/arch/arm/plat-mxc/include/mach/mxc_uart.h b/arch/arm/plat-mxc/include/mach/mxc_uart.h new file mode 100644 index 000000000000..d1db8023d724 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/mxc_uart.h @@ -0,0 +1,275 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup UART Universal Asynchronous Receiver Transmitter (UART) Driver + */ + +/*! + * @file arch-mxc/mxc_uart.h + * + * @brief This file contains the UART configuration structure definition. + * + * + * @ingroup UART + */ + +#ifndef __ASM_ARCH_MXC_UART_H__ +#define __ASM_ARCH_MXC_UART_H__ + +#ifdef __KERNEL__ + +#include <linux/serial_core.h> +#include <mach/dma.h> + +/* + * The modes of the UART ports + */ +#define MODE_DTE 0 +#define MODE_DCE 1 +/* + * Is the UART configured to be a IR port + */ +#define IRDA 0 +#define NO_IRDA 1 + +/*! + * This structure is used to store the the physical and virtual + * addresses of the UART DMA receive buffer. + */ +typedef struct { + /*! + * DMA Receive buffer virtual address + */ + char *rx_buf; + /*! + * DMA Receive buffer physical address + */ + dma_addr_t rx_handle; +} mxc_uart_rxdmamap; + +/*! + * This structure is a way for the low level driver to define their own + * \b uart_port structure. This structure includes the core \b uart_port + * structure that is provided by Linux as an element and has other + * elements that are specifically required by this low-level driver. + */ +typedef struct { + /*! + * The port structure holds all the information about the UART + * port like base address, and so on. + */ + struct uart_port port; + /*! + * Flag to determine if the interrupts are muxed. + */ + int ints_muxed; + /*! + * Array that holds the receive and master interrupt numbers + * when the interrupts are not muxed. + */ + int irqs[2]; + /*! + * Flag to determine the DTE/DCE mode. + */ + int mode; + /*! + * Flag to hold the IR mode of the port. + */ + int ir_mode; + /*! + * Flag to enable/disable the UART port. + */ + int enabled; + /*! + * Flag to indicate if we wish to use hardware-driven hardware + * flow control. + */ + int hardware_flow; + /*! + * Holds the threshold value at which the CTS line is deasserted in + * case we use hardware-driven hardware flow control. + */ + unsigned int cts_threshold; + /*! + * Flag to enable/disable DMA data transfer. + */ + int dma_enabled; + /*! + * Holds the DMA receive buffer size. + */ + int dma_rxbuf_size; + /*! + * DMA Receive buffers information + */ + mxc_uart_rxdmamap *rx_dmamap; + /*! + * DMA RX buffer id + */ + int dma_rxbuf_id; + /*! + * DMA Transmit buffer virtual address + */ + char *tx_buf; + /*! + * DMA Transmit buffer physical address + */ + dma_addr_t tx_handle; + /*! + * Holds the RxFIFO threshold value. + */ + unsigned int rx_threshold; + /*! + * Holds the TxFIFO threshold value. + */ + unsigned int tx_threshold; + /*! + * Information whether this is a shared UART + */ + unsigned int shared; + /*! + * Clock id for UART clock + */ + struct clk *clk; + /*! + * Information whether RXDMUXSEL must be set or not for IR port + */ + int rxd_mux; + int ir_tx_inv; + int ir_rx_inv; + /*! + * DMA ID for transmit + */ + mxc_dma_device_t dma_tx_id; + /*! + * DMA ID for receive + */ + mxc_dma_device_t dma_rx_id; +} uart_mxc_port; + +/* Address offsets of the UART registers */ +#define MXC_UARTURXD 0x000 /* Receive reg */ +#define MXC_UARTUTXD 0x040 /* Transmitter reg */ +#define MXC_UARTUCR1 0x080 /* Control reg 1 */ +#define MXC_UARTUCR2 0x084 /* Control reg 2 */ +#define MXC_UARTUCR3 0x088 /* Control reg 3 */ +#define MXC_UARTUCR4 0x08C /* Control reg 4 */ +#define MXC_UARTUFCR 0x090 /* FIFO control reg */ +#define MXC_UARTUSR1 0x094 /* Status reg 1 */ +#define MXC_UARTUSR2 0x098 /* Status reg 2 */ +#define MXC_UARTUESC 0x09C /* Escape character reg */ +#define MXC_UARTUTIM 0x0A0 /* Escape timer reg */ +#define MXC_UARTUBIR 0x0A4 /* BRM incremental reg */ +#define MXC_UARTUBMR 0x0A8 /* BRM modulator reg */ +#define MXC_UARTUBRC 0x0AC /* Baud rate count reg */ +#define MXC_UARTONEMS 0x0B0 /* One millisecond reg */ +#define MXC_UARTUTS 0x0B4 /* Test reg */ + +/* Bit definations of UCR1 */ +#define MXC_UARTUCR1_ADEN 0x8000 +#define MXC_UARTUCR1_ADBR 0x4000 +#define MXC_UARTUCR1_TRDYEN 0x2000 +#define MXC_UARTUCR1_IDEN 0x1000 +#define MXC_UARTUCR1_RRDYEN 0x0200 +#define MXC_UARTUCR1_RXDMAEN 0x0100 +#define MXC_UARTUCR1_IREN 0x0080 +#define MXC_UARTUCR1_TXMPTYEN 0x0040 +#define MXC_UARTUCR1_RTSDEN 0x0020 +#define MXC_UARTUCR1_SNDBRK 0x0010 +#define MXC_UARTUCR1_TXDMAEN 0x0008 +#define MXC_UARTUCR1_ATDMAEN 0x0004 +#define MXC_UARTUCR1_DOZE 0x0002 +#define MXC_UARTUCR1_UARTEN 0x0001 + +/* Bit definations of UCR2 */ +#define MXC_UARTUCR2_ESCI 0x8000 +#define MXC_UARTUCR2_IRTS 0x4000 +#define MXC_UARTUCR2_CTSC 0x2000 +#define MXC_UARTUCR2_CTS 0x1000 +#define MXC_UARTUCR2_PREN 0x0100 +#define MXC_UARTUCR2_PROE 0x0080 +#define MXC_UARTUCR2_STPB 0x0040 +#define MXC_UARTUCR2_WS 0x0020 +#define MXC_UARTUCR2_RTSEN 0x0010 +#define MXC_UARTUCR2_ATEN 0x0008 +#define MXC_UARTUCR2_TXEN 0x0004 +#define MXC_UARTUCR2_RXEN 0x0002 +#define MXC_UARTUCR2_SRST 0x0001 + +/* Bit definations of UCR3 */ +#define MXC_UARTUCR3_DTREN 0x2000 +#define MXC_UARTUCR3_PARERREN 0x1000 +#define MXC_UARTUCR3_FRAERREN 0x0800 +#define MXC_UARTUCR3_DSR 0x0400 +#define MXC_UARTUCR3_DCD 0x0200 +#define MXC_UARTUCR3_RI 0x0100 +#define MXC_UARTUCR3_RXDSEN 0x0040 +#define MXC_UARTUCR3_AWAKEN 0x0010 +#define MXC_UARTUCR3_DTRDEN 0x0008 +#define MXC_UARTUCR3_RXDMUXSEL 0x0004 +#define MXC_UARTUCR3_INVT 0x0002 + +/* Bit definations of UCR4 */ +#define MXC_UARTUCR4_CTSTL_OFFSET 10 +#define MXC_UARTUCR4_CTSTL_MASK (0x3F << 10) +#define MXC_UARTUCR4_INVR 0x0200 +#define MXC_UARTUCR4_ENIRI 0x0100 +#define MXC_UARTUCR4_REF16 0x0040 +#define MXC_UARTUCR4_IRSC 0x0020 +#define MXC_UARTUCR4_TCEN 0x0008 +#define MXC_UARTUCR4_OREN 0x0002 +#define MXC_UARTUCR4_DREN 0x0001 + +/* Bit definations of UFCR */ +#define MXC_UARTUFCR_RFDIV 0x0200 /* Ref freq div is set to 2 */ +#define MXC_UARTUFCR_RFDIV_OFFSET 7 +#define MXC_UARTUFCR_RFDIV_MASK (0x7 << 7) +#define MXC_UARTUFCR_TXTL_OFFSET 10 +#define MXC_UARTUFCR_DCEDTE 0x0040 + +/* Bit definations of URXD */ +#define MXC_UARTURXD_ERR 0x4000 +#define MXC_UARTURXD_OVRRUN 0x2000 +#define MXC_UARTURXD_FRMERR 0x1000 +#define MXC_UARTURXD_BRK 0x0800 +#define MXC_UARTURXD_PRERR 0x0400 + +/* Bit definations of USR1 */ +#define MXC_UARTUSR1_PARITYERR 0x8000 +#define MXC_UARTUSR1_RTSS 0x4000 +#define MXC_UARTUSR1_TRDY 0x2000 +#define MXC_UARTUSR1_RTSD 0x1000 +#define MXC_UARTUSR1_FRAMERR 0x0400 +#define MXC_UARTUSR1_RRDY 0x0200 +#define MXC_UARTUSR1_AGTIM 0x0100 +#define MXC_UARTUSR1_DTRD 0x0080 +#define MXC_UARTUSR1_AWAKE 0x0010 + +/* Bit definations of USR2 */ +#define MXC_UARTUSR2_TXFE 0x4000 +#define MXC_UARTUSR2_IDLE 0x1000 +#define MXC_UARTUSR2_RIDELT 0x0400 +#define MXC_UARTUSR2_RIIN 0x0200 +#define MXC_UARTUSR2_DCDDELT 0x0040 +#define MXC_UARTUSR2_DCDIN 0x0020 +#define MXC_UARTUSR2_TXDC 0x0008 +#define MXC_UARTUSR2_ORE 0x0002 +#define MXC_UARTUSR2_RDR 0x0001 +#define MXC_UARTUSR2_BRCD 0x0004 + +/* Bit definations of UTS */ +#define MXC_UARTUTS_LOOP 0x1000 + +#endif /* __KERNEL__ */ + +#endif /* __ASM_ARCH_MXC_UART_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/mxc_vpu.h b/arch/arm/plat-mxc/include/mach/mxc_vpu.h new file mode 100644 index 000000000000..8a42c7de5634 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/mxc_vpu.h @@ -0,0 +1,94 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +/*! + * @defgroup VPU Video Processor Unit Driver + */ + +/*! + * @file arch-mxc/mxc_vpu.h + * + * @brief VPU system initialization and file operation definition + * + * @ingroup VPU + */ + +#ifndef __ASM_ARCH_MXC_VPU_H__ +#define __ASM_ARCH_MXC_VPU_H__ + +#include <linux/fs.h> + +struct vpu_mem_desc { + u32 size; + dma_addr_t phy_addr; + u32 cpu_addr; /* cpu address to free the dma mem */ + u32 virt_uaddr; /* virtual user space address */ +}; + +#define VPU_IOC_MAGIC 'V' + +#define VPU_IOC_PHYMEM_ALLOC _IO(VPU_IOC_MAGIC, 0) +#define VPU_IOC_PHYMEM_FREE _IO(VPU_IOC_MAGIC, 1) +#define VPU_IOC_WAIT4INT _IO(VPU_IOC_MAGIC, 2) +#define VPU_IOC_PHYMEM_DUMP _IO(VPU_IOC_MAGIC, 3) +#define VPU_IOC_REG_DUMP _IO(VPU_IOC_MAGIC, 4) +#define VPU_IOC_VL2CC_FLUSH _IO(VPU_IOC_MAGIC, 5) +#define VPU_IOC_IRAM_SETTING _IO(VPU_IOC_MAGIC, 6) +#define VPU_IOC_CLKGATE_SETTING _IO(VPU_IOC_MAGIC, 7) +#define VPU_IOC_GET_WORK_ADDR _IO(VPU_IOC_MAGIC, 8) +#define VPU_IOC_GET_PIC_PARA_ADDR _IO(VPU_IOC_MAGIC, 9) +#define VPU_IOC_GET_USER_DATA_ADDR _IO(VPU_IOC_MAGIC, 10) +#define VPU_IOC_SYS_SW_RESET _IO(VPU_IOC_MAGIC, 11) +#define VPU_IOC_GET_SHARE_MEM _IO(VPU_IOC_MAGIC, 12) + +#define BIT_CODE_RUN 0x000 +#define BIT_CODE_DOWN 0x004 +#define BIT_INT_CLEAR 0x00C +#define BIT_INT_STATUS 0x010 + +#define BIT_WORK_CTRL_BUF_BASE 0x100 +#define BIT_WORK_CTRL_BUF_REG(i) (BIT_WORK_CTRL_BUF_BASE + i * 4) +#define BIT_CODE_BUF_ADDR BIT_WORK_CTRL_BUF_REG(0) +#define BIT_WORK_BUF_ADDR BIT_WORK_CTRL_BUF_REG(1) +#define BIT_PARA_BUF_ADDR BIT_WORK_CTRL_BUF_REG(2) +#define BIT_BIT_STREAM_CTRL BIT_WORK_CTRL_BUF_REG(3) +#define BIT_FRAME_MEM_CTRL BIT_WORK_CTRL_BUF_REG(4) +#define BIT_BIT_STREAM_PARAM BIT_WORK_CTRL_BUF_REG(5) + +#define BIT_RESET_CTRL 0x11C + +/* i could be 0, 1, 2, 3 */ +#define BIT_RD_PTR_BASE 0x120 +#define BIT_RD_PTR_REG(i) (BIT_RD_PTR_BASE + i * 8) +#define BIT_WR_PTR_REG(i) (BIT_RD_PTR_BASE + i * 8 + 4) + +/* i could be 0, 1, 2, 3 */ +#define BIT_FRM_DIS_FLG_BASE (cpu_is_mx51() ? 0x150 : 0x140) +#define BIT_FRM_DIS_FLG_REG(i) (BIT_FRM_DIS_FLG_BASE + i * 4) + +#define BIT_BUSY_FLAG 0x160 +#define BIT_RUN_COMMAND 0x164 +#define BIT_INT_ENABLE 0x170 + +#define BITVAL_PIC_RUN 8 + +#define VPU_SLEEP_REG_VALUE 10 +#define VPU_WAKE_REG_VALUE 11 + +int vl2cc_init(u32 vl2cc_hw_base); +void vl2cc_enable(void); +void vl2cc_flush(void); +void vl2cc_disable(void); +void vl2cc_cleanup(void); + +#endif diff --git a/arch/arm/plat-mxc/include/mach/pcmcia.h b/arch/arm/plat-mxc/include/mach/pcmcia.h new file mode 100644 index 000000000000..c50302bbbac5 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/pcmcia.h @@ -0,0 +1,218 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_ARCH_MXC_PCMCIA_H__ +#define __ASM_ARCH_MXC_PCMCIA_H__ + +#include <mach/hardware.h> + +#define WINDOW_SIZE 0x1000000 /* The size of a window: 16M */ +#define PCMCIA_WINDOWS 5 /* How many windows / socket */ +#define SOCKET_NO 1 /* How many sockets */ + +#define ATTRIBUTE_MEMORY_WINDOW 0 +#define IO_WINDOW 1 +#define COMMON_MEMORY_WINDOW 2 + +/* + * PCMCIA socket + */ +#define PCMCIAPrtSp WINDOW_SIZE /* PCMCIA window size */ +#define PCMCIASp (4*PCMCIAPrtSp) /* PCMCIA Space [byte] */ +#define PCMCIAIOSp PCMCIAPrtSp /* PCMCIA I/O Space [byte] */ +#define PCMCIAAttrSp PCMCIAPrtSp /* PCMCIA Attribute Space [byte] */ +#define PCMCIAMemSp PCMCIAPrtSp /* PCMCIA Memory Space [byte] */ + +#define PCMCIA0Sp PCMCIASp /* PCMCIA 0 Space [byte] */ +#define PCMCIA0IOSp PCMCIAIOSp /* PCMCIA 0 I/O Space [byte] */ +#define PCMCIA0AttrSp PCMCIAAttrSp /* PCMCIA 0 Attribute Space [byte] */ +#define PCMCIA0MemSp PCMCIAMemSp /* PCMCIA 0 Memory Space [byte] */ + +#define _PCMCIA(Nb) /* PCMCIA [0..1] */ \ + (PCMCIA_MEM_BASE_ADDR + (Nb) * PCMCIASp) + +#define _PCMCIAAttr(Nb) _PCMCIA (Nb) /* PCMCIA I/O [0..1] */ + +#define _PCMCIAIO(Nb) /* PCMCIA Attribute [0..1] */ \ + (_PCMCIA (Nb) + (IO_WINDOW) * PCMCIAPrtSp) +#define _PCMCIAMem(Nb) /* PCMCIA Memory [0..1] */ \ + (_PCMCIA (Nb) + (COMMON_MEMORY_WINDOW) * PCMCIAPrtSp) + +#define _PCMCIA0 _PCMCIA (0) /* PCMCIA 0 */ +#define _PCMCIA0IO _PCMCIAIO (0) /* PCMCIA 0 I/O */ +#define _PCMCIA0Attr _PCMCIAAttr (0) /* PCMCIA 0 Attribute */ +#define _PCMCIA0Mem _PCMCIAMem (0) /* PCMCIA 0 Memory */ + +/* + * Module: PCMCIA, Addr Range: 0xB8004000 - 0xB8004FFF, Size: 4 Kbyte + */ +#define PCMCIA_BASE_ADDR (PCMCIA_CTL_BASE_ADDR) /* PCMCIA Base Address */ +#define PCMCIA_IO_ADDR(x) (* (volatile u32 *)PCMCIA_IO_ADDRESS(x)) + +#define _reg_PCMCIA_PIPR PCMCIA_IO_ADDR(PCMCIA_BASE_ADDR + 0x00) /* PCMCIA input pins register */ +#define _reg_PCMCIA_PSCR PCMCIA_IO_ADDR(PCMCIA_BASE_ADDR + 0x04) /* PCMCIA Status Changed Register */ +#define _reg_PCMCIA_PER PCMCIA_IO_ADDR(PCMCIA_BASE_ADDR + 0x08) /* PCMCIA Enable Register */ + +/* win: 0-4 */ +#define _reg_PCMCIA_PBR(win) PCMCIA_IO_ADDR(PCMCIA_BASE_ADDR + 0x0C + 4 * (win)) /* PCMCIA Base Register x */ +#define _reg_PCMCIA_POR(win) PCMCIA_IO_ADDR(PCMCIA_BASE_ADDR + 0x28 + 4 * (win)) /* PCMCIA Option Register x */ +#define _reg_PCMCIA_POFR(win) PCMCIA_IO_ADDR(PCMCIA_BASE_ADDR + 0x44 + 4 * (win)) /* PCMCIA Offset Register x */ + +#define _reg_PCMCIA_PGCR PCMCIA_IO_ADDR(PCMCIA_BASE_ADDR + 0x60) /* PCMCIA General Control Register */ +#define _reg_PCMCIA_PGSR PCMCIA_IO_ADDR(PCMCIA_BASE_ADDR + 0x64) /* PCMCIA General Status Register */ + +/* PCMCIA_PIPR - PCMCIA Input Pins Register - fields */ +#define PCMCIA_PIPR_POWERON (1 << 8) /* card indicates "power on" */ +#define PCMCIA_PIPR_RDY (1 << 7) /* card is ready */ +#define PCMCIA_PIPR_BVD2 (1 << 6) /* battery voltage 2/SPKR in */ +#define PCMCIA_PIPR_BVD1 (1 << 5) /* battery voltage 1/STSCHG */ +#define PCMCIA_PIPR_CD (3 << 3) /* card detect 1 and 2 */ +#define PCMCIA_PIPR_WP (1 << 2) /* write protect switch enabled */ +#define PCMCIA_PIPR_VS (3 << 0) /* voltage sense bits */ +#define PCMCIA_PIPR_VS_5V (1 << 0) /* 5v */ + +/* PCMCIA_PSCR - PCMCIA Status Change Register - fields */ +#define PCMCIA_PSCR_POWC (1 << 11) /* */ +#define PCMCIA_PSCR_RDYR (1 << 10) /* */ +#define PCMCIA_PSCR_RDYF (1 << 9) /* */ +#define PCMCIA_PSCR_RDYH (1 << 8) /* */ +#define PCMCIA_PSCR_RDYL (1 << 7) /* */ +#define PCMCIA_PSCR_BVDC2 (1 << 6) /* */ +#define PCMCIA_PSCR_BVDC1 (1 << 5) /* */ +#define PCMCIA_PSCR_CDC2 (1 << 4) /* */ +#define PCMCIA_PSCR_CDC1 (1 << 3) /* */ +#define PCMCIA_PSCR_WPC (1 << 2) /* */ +#define PCMCIA_PSCR_VSC2 (1 << 1) /* */ +#define PCMCIA_PSCR_VSC1 (1 << 0) /* */ + +/* PCMCIA_PER - PCMCIA Enable Register - fields */ +#define PCMCIA_PER_ERRINTEN (1 << 12) /* error interrupt enable */ +#define PCMCIA_PER_POWERONEN (1 << 11) /* power on interrupt enable */ +#define PCMCIA_PER_RDYRE (1 << 10) /* RDY/nIREQ pin rising edge */ +#define PCMCIA_PER_RDYFE (1 << 9) /* RDY/nIREQ pin falling edge */ +#define PCMCIA_PER_RDYHE (1 << 8) /* RDY/nIREQ pin high */ +#define PCMCIA_PER_RDYLE (1 << 7) /* RDY/nIREQ pin low */ +#define PCMCIA_PER_BVDE2 (1 << 6) /* battery voltage 2/SPKR in */ +#define PCMCIA_PER_BVDE1 (1 << 5) /* battery voltage 1/STSCHG */ +#define PCMCIA_PER_CDE2 (1 << 4) /* card detect 2 */ +#define PCMCIA_PER_CDE1 (1 << 3) /* card detect 1 */ +#define PCMCIA_PER_WPE (1 << 2) /* write protect */ +#define PCMCIA_PER_VSE2 (1 << 1) /* voltage sense 2 */ +#define PCMCIA_PER_VSE1 (1 << 0) /* voltage sense 1 */ + +/* PCMCIA_POR[0-4] - PCMCIA Option Registers 0-4 - fields */ +#define PCMCIA_POR_PV (1 << 29) /* set iff bank is valid */ +#define PCMCIA_POR_WPEN (1 << 28) /* write protect (WP) input signal is enabled */ +#define PCMCIA_POR_WP (1 << 27) /* write protected */ + +#define PCMCIA_POR_PRS_SHIFT (25) +#define PCMCIA_POR_PRS(x) (((x) & 0x3) << PCMCIA_POR_PRS_SHIFT ) +#define PCMCIA_POR_PRS_MASK PCMCIA_POR_PRS(3) /* PCMCIA region select */ +#define PCMCIA_POR_PRS_COMMON (0) /* values of POR_PRS field */ +#define PCMCIA_POR_PRS_TRUE_IDE (1) +#define PCMCIA_POR_PRS_ATTRIBUTE (2) +#define PCMCIA_POR_PRS_IO (3) + +#define PCMCIA_POR_PPS_8 (1 << 24) /* PCMCIA Port size = 8bits */ +#define PCMCIA_POR_PPS_16 (0 << 24) /* PCMCIA Port size = 16bits */ + +#define PCMCIA_POR_PSL_SHIFT (17) /* strobe length */ +#define PCMCIA_POR_PSL(x) (((x) & 0x7F) << PCMCIA_POR_PSL_SHIFT) +#define PCMCIA_POR_PSL_MASK PCMCIA_POR_PSL(0x7f) + +#define PCMCIA_POR_PSST_SHIFT (11) /* strobe setup time */ +#define PCMCIA_POR_PSST(x) (((x) & 0x3F) << PCMCIA_POR_PSST_SHIFT) +#define PCMCIA_POR_PSST_MASK PCMCIA_POR_PSST(0x3f) + +#define PCMCIA_POR_PSHT_SHIFT (5) /* strobe hold time */ +#define PCMCIA_POR_PSHT(x) (((x) & 0x3F) << PCMCIA_POR_PSHT_SHIFT) +#define PCMCIA_POR_PSHT_MASK PCMCIA_POR_PSHT(0x3f) + +#define PCMCIA_POR_BSIZE_SHIFT (0) /* bank size */ +#define PCMCIA_POR_BSIZE(x) (((x) & 0x1F) << PCMCIA_POR_BSIZE_SHIFT) +#define PCMCIA_POR_BSIZE_MASK PCMCIA_POR_BSIZE(0x1F) + +/* some handy BSIZE values */ +#define POR_BSIZE_1 PCMCIA_POR_BSIZE(0x00) +#define POR_BSIZE_2 PCMCIA_POR_BSIZE(0x01) +#define POR_BSIZE_4 PCMCIA_POR_BSIZE(0x03) +#define POR_BSIZE_8 PCMCIA_POR_BSIZE(0x02) +#define POR_BSIZE_16 PCMCIA_POR_BSIZE(0x06) +#define POR_BSIZE_32 PCMCIA_POR_BSIZE(0x07) +#define POR_BSIZE_64 PCMCIA_POR_BSIZE(0x05) +#define POR_BSIZE_128 PCMCIA_POR_BSIZE(0x04) +#define POR_BSIZE_256 PCMCIA_POR_BSIZE(0x0C) +#define POR_BSIZE_512 PCMCIA_POR_BSIZE(0x0D) +#define POR_BSIZE_1K PCMCIA_POR_BSIZE(0x0F) +#define POR_BSIZE_2K PCMCIA_POR_BSIZE(0x0E) + +#define POR_BSIZE_4K PCMCIA_POR_BSIZE(0x0A) +#define POR_BSIZE_8K PCMCIA_POR_BSIZE(0x0B) +#define POR_BSIZE_16K PCMCIA_POR_BSIZE(0x09) +#define POR_BSIZE_32K PCMCIA_POR_BSIZE(0x08) +#define POR_BSIZE_64K PCMCIA_POR_BSIZE(0x18) +#define POR_BSIZE_128K PCMCIA_POR_BSIZE(0x19) +#define POR_BSIZE_256K PCMCIA_POR_BSIZE(0x1B) +#define POR_BSIZE_512K PCMCIA_POR_BSIZE(0x1A) +#define POR_BSIZE_1M PCMCIA_POR_BSIZE(0x1E) +#define POR_BSIZE_2M PCMCIA_POR_BSIZE(0x1F) +#define POR_BSIZE_4M PCMCIA_POR_BSIZE(0x1D) +#define POR_BSIZE_8M PCMCIA_POR_BSIZE(0x1C) +#define POR_BSIZE_16M PCMCIA_POR_BSIZE(0x14) +#define POR_BSIZE_32M PCMCIA_POR_BSIZE(0x15) +#define POR_BSIZE_64M PCMCIA_POR_BSIZE(0x17) + +/* Window size */ +#define POR_1 0x1 +#define POR_2 0x2 +#define POR_4 0x4 +#define POR_8 0x8 +#define POR_16 0x10 +#define POR_32 0x20 +#define POR_64 0x40 +#define POR_128 0x80 +#define POR_256 0x100 +#define POR_512 0x200 + +#define POR_1K 0x400 +#define POR_2K 0x800 +#define POR_4K 0x1000 +#define POR_8K 0x2000 +#define POR_16K 0x4000 +#define POR_32K 0x8000 +#define POR_64K 0x10000 +#define POR_128K 0x20000 +#define POR_256K 0x40000 +#define POR_512K 0x80000 + +#define POR_1M 0x100000 +#define POR_2M 0x200000 +#define POR_4M 0x400000 +#define POR_8M 0x800000 +#define POR_16M 0x1000000 +#define POR_32M 0x2000000 +#define POR_64M 0x4000000 + +/* PCMCIA_PGCR - PCMCIA General Control Register - fields */ +#define PCMCIA_PGCR_LPMEN (1 << 3) /* Low power Mode Enable */ +#define PCMCIA_PGCR_SPKREN (1 << 2) /* SPKROUT routing enable */ +#define PCMCIA_PGCR_POE (1 << 1) /* Controller out enable */ +#define PCMCIA_PGCR_RESET (1 << 0) /* Card reset */ + +/* PCMCIA_PGSR - PCMCIA General Status Register - fields */ +#define PCMCIA_PGSR_NWINE (1 << 4) /* No Window error */ +#define PCMCIA_PGSR_LPE (1 << 3) /* Low Power error */ +#define PCMCIA_PGSR_SE (1 << 2) /* Size error */ +#define PCMCIA_PGSR_CDE (1 << 1) /* Card Detect error */ +#define PCMCIA_PGSR_WPE (1 << 0) /* Write Protect error */ + +#endif /* __ASM_ARCH_MXC_PCMCIA_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/pmic_audio.h b/arch/arm/plat-mxc/include/mach/pmic_audio.h new file mode 100644 index 000000000000..8ec3d2c642e4 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/pmic_audio.h @@ -0,0 +1,2315 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_ARCH_MXC_PMIC_AUDIO_H__ +#define __ASM_ARCH_MXC_PMIC_AUDIO_H__ + +/*! + * @defgroup PMIC_AUDIO PMIC Audio Driver + * @ingroup PMIC_DRVRS + */ + +/*! + * @file arch-mxc/pmic_audio.h + * @brief External definitions for the PMIC Audio Client driver. + * + * The PMIC Audio driver and this API were developed to support the + * audio playback, recording, and mixing capabilities of the power + * management ICs that are available from Freescale Semiconductor, Inc. + * + * The following table shows which audio-related capabilities are supported + * by each power management IC: + * + * @ingroup PMIC_AUDIO + */ + +#include <linux/pmic_status.h> +#include <linux/pmic_external.h> + +/*************************************************************************** + * TYPEDEFS AND ENUMERATIONS * + ***************************************************************************/ + +/*! + * @name General Setup and Audio Device Access Typedefs and Enumerations + * Typedefs and enumerations that are used for initial access to the + * PMIC Audio hardware. + */ +/*@{*/ + +/*! + * @typedef PMIC_AUDIO_HANDLE + * @brief Define typedef for a handle to the PMIC Audio hardware. + * + * Define a "handle" that is returned when the PMIC Audio hardware + * is opened. This handle grants exclusive access to the PMIC Audio + * hardware and must be used in all subsequent function calls. When access + * to the PMIC Audio hardware is no longer required, then a close + * operation must be done with this handle. The handle is no longer valid + * if the close operation was successful. + */ +typedef long *PMIC_AUDIO_HANDLE; + +/*! + * @enum PMIC_AUDIO_EVENTS + * @brief Identify the audio events that have been detected and should be + * handled. + * + * This enumeration defines all of the possible PMIC Audio events. Multiple + * events may be selected when defining a mask and multiple events may be + * signalled together. + * + * Note that the MICROPHONE_DETECT and MICROPHONE_REMOVED events may also be + * used to signal the operation of a serial or parallel microphone switch + * when used with a combined headset+microphone device. In that case the + * HEADSET_DETECT state must also be checked to determine if it's only the + * microphone switch being operated or whether the microphone has truly been + * inserted/removed (along with the headset). + */ +typedef enum { + HEADSET_DETECTED = 1, /*!< Detected headset insertion. */ + HEADSET_STEREO = 2, /*!< Detected stereo headset device. */ + HEADSET_MONO = 4, /*!< Detected mono headset device. */ + HEADSET_THERMAL_SHUTDOWN = 8, /*!< Detected output amplifier + shutdown due to thermal + limits . */ + HEADSET_SHORT_CIRCUIT = 16, /*!< Detected output amplifier + short circuit condition + . */ + HEADSET_REMOVED = 32, /*!< Detected headset removal. */ + MICROPHONE_DETECTED = 64, /*!< Detected microphone insertion. */ + MICROPHONE_REMOVED = 128, /*!< Detected microphone removal. */ + PTT_BUTTON_PRESS = 256, /*!< Detected PTT button down + . */ + PTT_BUTTON_RANGE = 512, /*!< Detected PTT button within + voltage range + . */ + PTT_SHORT_OR_INVALID = 1024 /*!< Detected PTT button outside + of voltage range or invalid + device . */ +} PMIC_AUDIO_EVENTS; + +/*! + * @typedef PMIC_AUDIO_CALLBACK + * @brief Typedef for PMIC Audio event notification callback function. + * + * Define a typedef for the PMIC Audio event notification callback + * function. The signalled events are passed to the function as the first + * argument. The callback function should then process whatever events it + * can and then return the set of unhandled events (if any). + */ +typedef PMIC_AUDIO_EVENTS(*PMIC_AUDIO_CALLBACK) (const PMIC_AUDIO_EVENTS event); + +typedef struct { + int hs_state; + int event_type; +} PMIC_HS_STATE; + +/*! + * @enum PMIC_AUDIO_SOURCE + * @brief Select an audio signal processing component. + * + * This enumeration defines all of the possible PMIC audio signal handling + * components which can be acquired by calling pmic_audio_open(). + * + * Note that the EXTERNAL_STEREO_IN selection is used to simply gain access + * to the stereo input pins. The stereo input signal can then be routed + * directly to the output amplifiers. In this case, no signal processing is + * done by either the Voice CODEC or the Stereo DAC. + */ +typedef enum { + STEREO_DAC, /*!< Open connection to Stereo DAC. */ + VOICE_CODEC, /*!< Open connection to Voice CODEC. */ + EXTERNAL_STEREO_IN /*!< Open connection to external stereo inputs. */ +} PMIC_AUDIO_SOURCE; + +/*@}*/ + +/*! + * @name Data Bus Setup and Configuration Typedefs and Enumerations + * Typedefs and enumerations that are used to define and configure + * the data bus protocol in order to communicate with the Stereo DAC + * or the Voice CODEC. + */ +/*@{*/ + +/*! + * @enum PMIC_AUDIO_DATA_BUS + * @brief Select the data bus used to transfer data between the host and + * Voice CODEC and/or the Stereo DAC. + * + * This enumeration defines all of the possible PMIC audio data buses that + * can be used to transfer data between the host and the Voice CODEC and/or + * the Stereo DAC on the PMIC. + * + * Note that the same data bus may be used to transfer audio data to/from + * the Voice CODEC and the Stereo DAC. However, in this case, the data bus + * must be configured for network mode with different timeslots assigned to + * the Voice CODEC and the Stereo DAC. Also, the sampling rates must be + * identical for both the Voice CODEC and the Stereo DAC in order to avoid + * a data bus timing conflict and audio signal distortion. + */ +typedef enum { + AUDIO_DATA_BUS_1, /*!< Use data bus 1 for audio data. */ + AUDIO_DATA_BUS_2 /*!< Use data bus 2 for audio data. */ +} PMIC_AUDIO_DATA_BUS; + +/*! + * @enum PMIC_AUDIO_BUS_PROTOCOL + * @brief Select the data bus protocol to be used. + * + * This enumeration defines all of the possible PMIC audio data bus protocols + * that may be selected. + */ +typedef enum { + NORMAL_MSB_JUSTIFIED_MODE, /*!< Transmit and receive audio data + in normal MSB-justified mode. */ + NETWORK_MODE, /*!< Transmit and receive audio data + in network mode. */ + I2S_MODE, /*!< Transmit and receive audio data + in I2S mode. */ + SPD_IF_MODE /*!< Transmit and receive audio data + in SPD/IF mode . */ +} PMIC_AUDIO_BUS_PROTOCOL; + +/*! + * @enum PMIC_AUDIO_BUS_MODE + * @brief Select the data bus mode to be used. + * + * This enumeration defines all of the possible PMIC audio data bus modes + * that may be selected. When configured in BUS_MASTER_MODE, the PMIC is + * responsible for supplying the data bus clock signals. Alternatively, + * when configured in BUS_SLAVE_MODE, the PMIC will use the data bus clock + * signals that are supplied by the bus master. + */ +typedef enum { + BUS_MASTER_MODE = 0, /*!< Operate as bus master. */ + BUS_SLAVE_MODE = 1 /*!< Operate as bus slave. */ +} PMIC_AUDIO_BUS_MODE; + +/*! + * @enum PMIC_AUDIO_CLOCK_IN_SOURCE + * @brief Select the clock signal source when in bus master mode. + * + * This enumeration defines all of the possible PMIC audio clock signal + * sources that may be selected. One of these clock signal sources must + * be selected in order to use either the Voice CODEC or the Stereo DAC. + * + * When configured in BUS_MASTER_MODE, the PMIC's onboard PLL circuits + * will also be driven by the selected clock input signal. + */ +typedef enum { + CLOCK_IN_DEFAULT, /*!< Just use default (power-up) clock input. */ + CLOCK_IN_CLIA, /*!< Use the CLIA clock source (Stereo DAC + default) . */ + CLOCK_IN_CLIB, /*!< Use the CLIB clock source (Voice CODEC + default) . */ + CLOCK_IN_CLKIN, /*!< Use the CLKIN clock source + . */ + CLOCK_IN_MCLK, /*!< Disable the internal PLL and use the MCLK + clock source (Stereo DAC only) + . */ + CLOCK_IN_FSYNC, /*!< Internal PLL input from external framesync + (Stereo DAC only) . */ + CLOCK_IN_BITCLK /*!< Internal PLL input from external bitclock + (Stereo DAC only) */ +} PMIC_AUDIO_CLOCK_IN_SOURCE; + +/*! + * @enum PMIC_AUDIO_CLOCK_INVERT + * @brief Select whether to invert the frame sync or bit clock signals. + * + * This enumeration enables or disables the inversion of the incoming + * frame sync or bit clock signals. + */ +typedef enum { + NO_INVERT = 0, /*!< Do not invert the clock signals. */ + INVERT_BITCLOCK = 1, /*!< Invert the BCLK input signal. */ + INVERT_FRAMESYNC = 2 /*!< Invert the FSYNC input signal. */ +} PMIC_AUDIO_CLOCK_INVERT; + +/*! + * @enum PMIC_AUDIO_NUMSLOTS + * @brief Select whether to invert the frame sync or bit clock signals. + * + * This enumeration defines all of the possible number of timeslots that may + * be selected when the PMIC is configured as the data bus master. One of these + * options must be selected if the Stereo DAC is to provide the data bus + * clock signals. + * + * Note that the Voice CODEC currently only allows USE_4_TIMESLOTS when + * operating in data bus master mode. + */ +typedef enum { + USE_2_TIMESLOTS, /*!< Configure for 2 timeslots. */ + USE_4_TIMESLOTS, /*!< Configure for 4 timeslots. */ + USE_8_STAR_TIMESLOTS, /*!< Configure for 8 (Left, Right, 6 other) timeslots. */ + USE_8_TIMESLOTS /*!< Configure for 8 timeslots. */ +} PMIC_AUDIO_NUMSLOTS; + +/*! + * @enum PMIC_AUDIO_STDAC_SAMPLING_RATE + * @brief Select the audio data sampling rate for the Stereo DAC. + * + * This enumeration defines all of the possible sampling rates currently + * supported by the Stereo DAC. One of these sampling rates must be selected + * and it must match that of the audio stream or else signal distortion will + * occur. + */ +typedef enum { + STDAC_RATE_8_KHZ, /*!< Use 8 kHz sampling rate. */ + STDAC_RATE_11_025_KHZ, /*!< Use 11.025 kHz sampling rate. */ + STDAC_RATE_12_KHZ, /*!< Use 12 kHz sampling rate. */ + STDAC_RATE_16_KHZ, /*!< Use 16 kHz sampling rate. */ + STDAC_RATE_22_050_KHZ, /*!< Use 22.050 kHz sampling rate. */ + STDAC_RATE_24_KHZ, /*!< Use 24 kHz sampling rate. */ + STDAC_RATE_32_KHZ, /*!< Use 32 kHz sampling rate. */ + STDAC_RATE_44_1_KHZ, /*!< Use 44.1 kHz sampling rate. */ + STDAC_RATE_48_KHZ, /*!< Use 48 kHz sampling rate. */ + STDAC_RATE_64_KHZ, /*!< Use 64 kHz sampling rate + . */ + STDAC_RATE_96_KHZ /*!< Use 96 kHz sampling rate. + . */ +} PMIC_AUDIO_STDAC_SAMPLING_RATE; + +/*! + * @enum PMIC_AUDIO_VCODEC_SAMPLING_RATE + * @brief Select the audio data sampling rate for the Voice CODEC. + * + * This enumeration defines all of the possible sampling rates currently + * supported by the Voice CODEC. One of these sampling rates must be selected + * and it must match that of the audio stream or else signal distortion will + * occur. + */ +typedef enum { + VCODEC_RATE_8_KHZ, /*!< Use 8 kHz sampling rate. */ + VCODEC_RATE_16_KHZ, /*!< Use 16 kHz sampling rate. */ +} PMIC_AUDIO_VCODEC_SAMPLING_RATE; + +/*! + * @enum PMIC_AUDIO_ANTI_POP_RAMP_SPEED + * @brief Select the anti-pop circuitry's ramp up speed. + * + * This enumeration defines all of the possible ramp up speeds for the + * anti-pop circuitry. A slow ramp up speed may be required in order to + * avoid the popping noise that is typically generated during the insertion + * or removal of a headset or microphone. + */ +typedef enum { + ANTI_POP_RAMP_FAST, /*!< Select fast ramp up. */ + ANTI_POP_RAMP_SLOW /*!< Select slow ramp up. */ +} PMIC_AUDIO_ANTI_POP_RAMP_SPEED; + +/*@}*/ + +/*! + * @name General Voice CODEC Configuration Typedefs and Enumerations + * Typedefs and enumerations that are used to define and configure + * the basic operating options for the Voice CODEC. + */ +/*@{*/ + +/*! + * @enum PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ + * @brief Select the Voice CODEC input clock frequency. + * + * This enumeration defines all of the supported Voice CODEC input clock + * frequencies. One of these frequencies must be selected in order to + * properly configure the Voice CODEC to operate at the required sampling + * rate. + */ +typedef enum { + VCODEC_CLI_13MHZ, /*!< Clock frequency is 13MHz. */ + VCODEC_CLI_15_36MHZ, /*!< Clock frequency is 15.36MHz. */ + VCODEC_CLI_16_8MHZ, /*!< Clock frequency is 16.8MHz + . */ + VCODEC_CLI_26MHZ, /*!< Clock frequency is 26MHz. */ + VCODEC_CLI_33_6MHZ, /*!< Clock frequency is 33.6MHz. */ +} PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ; + +/*! + * @enum PMIC_AUDIO_VCODEC_CONFIG + * @brief Select the Voice CODEC configuration options. + * + * This enumeration is used to enable/disable each of the Voice CODEC options. + * This includes the use of highpass digital filters and audio signal + * loopback modes. + * + * Note that resetting the digital filters is now handled by the + * pmic_audio_digital_filter_reset() API. + */ +typedef enum { + DITHERING = 1, /*!< Enable/disable dithering. */ + INPUT_HIGHPASS_FILTER = 2, /*!< Enable/disable the input high + pass digital filter. */ + OUTPUT_HIGHPASS_FILTER = 4, /*!< Enable/disable the output high + pass digital filter. */ + ANALOG_LOOPBACK = 8, /*!< Enable/disable the analog + loopback path + . */ + DIGITAL_LOOPBACK = 16, /*!< Enable/disable the digital + loopback path. */ + VCODEC_MASTER_CLOCK_OUTPUTS = 32, /*!< Enable/disable the bus master + clock outputs. */ + TRISTATE_TS = 64 /*!< Enable/disable FSYNC, BITCLK, + and TX tristate. */ +} PMIC_AUDIO_VCODEC_CONFIG; + +/*@}*/ + +/*! + * @name General Stereo DAC Configuration Typedefs and Enumerations + * Typedefs and enumerations that are used to define and configure + * the basic operating options for the Stereo DAC. + */ +/*@{*/ + +/*! + * @enum PMIC_AUDIO_STDAC_CLOCK_IN_FREQ + * @brief Select the Stereo DAC input clock frequency. + * + * This enumeration defines all of the supported Stereo DAC input clock + * frequencies. One of these frequencies must be selected in order to + * properly configure the Stereo DAC to operate at the required sampling + * rate. + */ +typedef enum { + STDAC_CLI_3_36864MHZ, /*!< Clock frequency is 3.36864MHz + . */ + STDAC_CLI_12MHZ, /*!< Clock frequency is 12MHz. + . */ + STDAC_CLI_13MHZ, /*!< Clock frequency is 13MHz. */ + STDAC_CLI_15_36MHZ, /*!< Clock frequency is 15.36MHz. */ + STDAC_CLI_16_8MHZ, /*!< Clock frequency is 16.8MHz + . */ + STDAC_CLI_26MHZ, /*!< Clock frequency is 26MHz. */ + STDAC_CLI_33_6MHZ, /*!< Clock frequency is 33.6MHz. */ + STDAC_MCLK_PLL_DISABLED, /*!< Use MCLK and disable internal PLL. */ + STDAC_FSYNC_IN_PLL, /*!< Use FSYNC as internal PLL input. */ + STDAC_BCLK_IN_PLL /*!< Use BCLK as internal PLL input. */ +} PMIC_AUDIO_STDAC_CLOCK_IN_FREQ; + +/*! + * @enum PMIC_AUDIO_STDAC_CONFIG + * @brief Select the Stereo DAC configuration options. + * + * This enumeration is used to enable/disable each of the Stereo DAC options. + */ +typedef enum { + STDAC_MASTER_CLOCK_OUTPUTS = 1 /*!< Enable/disable the bus master clock + outputs. */ +} PMIC_AUDIO_STDAC_CONFIG; + +/*@}*/ + +/*! + * @name Voice CODEC Audio Port Mixing Typedefs and Enumerations + * Typedefs and enumerations that are used for setting up the audio mixer + * within the Voice CODEC. + */ +/*@{*/ + +/*! + * @enum PMIC_AUDIO_VCODEC_TIMESLOT + * @brief Select the Stereo DAC configuration options. + * + * This enumeration is used to select the timeslot for both the primary and + * secondary (for mc13783-only) audio channels to the Voice CODEC. + */ +typedef enum { + USE_TS0, /*!< Use timeslot 0 for audio signal source + . */ + USE_TS1, /*!< Use timeslot 1 for audio signal source + . */ + USE_TS2, /*!< Use timeslot 2 for audio signal source + . */ + USE_TS3 /*!< Use timeslot 3 for audio signal source + . */ +} PMIC_AUDIO_VCODEC_TIMESLOT; + +/*! + * @enum PMIC_AUDIO_VCODEC_MIX_IN_GAIN + * @brief Select the secondary channel input gain for the Voice CODEC mixer. + * + * This enumeration selects the secondary channel input gain for the Voice + * CODEC mixer. + */ +typedef enum { + VCODEC_NO_MIX, /*!< No audio mixing . */ + VCODEC_MIX_IN_0DB, /*!< Mix with 0dB secondary channel gain + . */ + VCODEC_MIX_IN_MINUS_6DB, /*!< Mix with -6dB secondary channel gain + . */ + VCODEC_MIX_IN_MINUS_12DB, /*!< Mix with -12dB secondary channel gain + . */ +} PMIC_AUDIO_VCODEC_MIX_IN_GAIN; + +/*! + * @enum PMIC_AUDIO_VCODEC_MIX_OUT_GAIN + * @brief Select the output gain for the Voice CODEC mixer. + * + * This enumeration selects the output gain for the Voice CODEC mixer. + */ +typedef enum { + VCODEC_MIX_OUT_0DB, /*!< Select 0dB mixer output gain + . */ + VCODEC_MIX_OUT_MINUS_6DB, /*!< Select -6dB mixer output gain + . */ +} PMIC_AUDIO_VCODEC_MIX_OUT_GAIN; + +/*@}*/ + +/*! + * @name Stereo DAC Audio Port Mixing Typedefs and Enumerations + * Typedefs and enumerations that are used for setting up the audio mixer + * within the Stereo DAC. + */ +/*@{*/ + +/*! + * @enum PMIC_AUDIO_STDAC_TIMESLOTS + * @brief Select the timeslots used to transmit the left and right audio + * channels to the Stereo DAC. + * + * This enumeration is used to select the timeslots used to transmit the + * data corresponding to the left and right audio channels to the Stereo + * DAC. + */ +typedef enum { + USE_TS0_TS1, /*!< Use timeslots 0 and 1 for left and + right channels, respectively. */ + USE_TS2_TS3, /*!< Use timeslots 2 and 3 for left and + right channels, respectively + . */ + USE_TS4_TS5, /*!< Use timeslots 4 and 5 for left and + right channels, respectively + . */ + USE_TS6_TS7 /*!< Use timeslots 6 and 7 for left and + right channels, respectively + . */ +} PMIC_AUDIO_STDAC_TIMESLOTS; + +/*! + * @enum PMIC_AUDIO_STDAC_MIX_IN_GAIN + * @brief Select the secondary channel input gain for the Stereo DAC mixer. + * + * This enumeration is used to select the secondary channel input gain for + * the Stereo DAC mixer. + */ +typedef enum { + STDAC_NO_MIX, /*!< No mixing, keep separate left + and right audio channels. */ + STDAC_MIX_IN_0DB, /*!< Mix left and right audio channels + together with 0dB secondary + channel gain. */ + STDAC_MIX_IN_MINUS_6DB, /*!< Mix left and right audio channels + together with -6dB secondary + channel gain. */ + STDAC_MIX_IN_MINUS_12DB /*!< Mix left and right audio channels + together with -12dB secondary + channel gain . */ +} PMIC_AUDIO_STDAC_MIX_IN_GAIN; + +/*! + * @enum PMIC_AUDIO_STDAC_MIX_OUT_GAIN + * @brief Select the output gain for the Stereo DAC mixer. + * + * This enumeration is used to select the output gain for the Stereo DAC + * mixer. + */ +typedef enum { + STDAC_MIX_OUT_0DB, /*!< Select 0dB mixer output gain. */ + STDAC_MIX_OUT_MINUS_6DB, /*!< Select -6dB mixer output gain + . */ +} PMIC_AUDIO_STDAC_MIX_OUT_GAIN; + +/*@}*/ + +/*! + * @name Microphone Input Typedefs and Enumerations + * Typedefs and enumerations that are used for selecting and setting up + * one or more or microphone inputs for recording. + */ +/*@{*/ + +/*! + * @enum PMIC_AUDIO_MIC_BIAS + * @brief Select the microphone bias circuit to be enabled/disabled. + * + * This enumeration lists all of the available microphone bias circuits that + * may be enabled or disabled. + */ +typedef enum { + NO_BIAS = 0, /*!< No microphone bias circuit selected. */ + MIC_BIAS1 = 1, /*!< Enable/disable microphone bias 1 circuit. */ + MIC_BIAS2 = 2, /*!< Enable/disable microphone bias 2 circuit. */ +} PMIC_AUDIO_MIC_BIAS; + +/*! + * @enum PMIC_AUDIO_INPUT_PORT + * @brief Select an audio input port for recording. + * + * This enumeration lists all of the available audio input ports that may + * be selected for a recording operation. + */ +typedef enum { + NO_MIC, /*!< No microphone input selected. */ + MIC1_LEFT, /*!< Enable left/mono channel microphone input + . */ + MIC1_RIGHT_MIC_MONO, /*!< Enable right channel microphone input. */ + MIC2_AUX, /*!< Enable auxiliary microphone input. */ + TXIN_EXT /*!< Enable external mono input. */ +} PMIC_AUDIO_INPUT_PORT; + +/*! + * @enum PMIC_AUDIO_INPUT_MIC_STATE + * @brief Control whether the input microphone is on/off. + * + * This enumeration allows the currently selected input microphone amplifier + * to be turned on/off. + */ +typedef enum { + MICROPHONE_ON, /*!< Turn microphone input on for recording. */ + MICROPHONE_OFF /*!< Turn microphone input off (mute). */ +} PMIC_AUDIO_INPUT_MIC_STATE; + +/*! + * @enum PMIC_AUDIO_INPUT_CONFIG + * @brief Enable/disable the audio input options. + * + * This enumeration allows for enabling/disabling any of the audio input + * section options. + */ +typedef enum { + MIC_AMP_AUTO_DISABLE = 1 /*!< Enable/disable automatic disabling of + microphone input amplifiers following + headset insertion/removal */ +} PMIC_AUDIO_INPUT_CONFIG; + +/*! + * @enum PMIC_AUDIO_MIC_AMP_MODE + * @brief Select the operating mode for the microphone amplifiers. + * + * This enumeration is used to select the operating mode for the microphone + * amplifier. + */ +typedef enum { + AMP_OFF, /*!< Disable input amplifier. */ + VOLTAGE_TO_VOLTAGE, /*!< Operate input amplifier in + voltage-to-voltage mode + . */ + CURRENT_TO_VOLTAGE /*!< Operate input amplifier in + current-to-voltage mode */ +} PMIC_AUDIO_MIC_AMP_MODE; + +/*! + * @enum PMIC_AUDIO_MIC_GAIN + * @brief Select the microphone amplifier gain level. + * + * This enumeration lists all of the available microphone amplifier gain + * levels. + */ +typedef enum { + MIC_GAIN_MINUS_8DB, /*!< Select -8dB microphone amplifier gain + . */ + MIC_GAIN_MINUS_7DB, /*!< Select -7dB microphone amplifier gain + . */ + MIC_GAIN_MINUS_6DB, /*!< Select -6dB microphone amplifier gain + . */ + MIC_GAIN_MINUS_5DB, /*!< Select -5dB microphone amplifier gain + . */ + MIC_GAIN_MINUS_4DB, /*!< Select -4dB microphone amplifier gain + . */ + MIC_GAIN_MINUS_3DB, /*!< Select -3dB microphone amplifier gain + . */ + MIC_GAIN_MINUS_2DB, /*!< Select -2dB microphone amplifier gain + . */ + MIC_GAIN_MINUS_1DB, /*!< Select -1dB microphone amplifier gain + . */ + MIC_GAIN_0DB, /*!< Select 0dB microphone amplifier gain. */ + MIC_GAIN_PLUS_1DB, /*!< Select 1dB microphone amplifier gain. */ + MIC_GAIN_PLUS_2DB, /*!< Select 2dB microphone amplifier gain. */ + MIC_GAIN_PLUS_3DB, /*!< Select 3dB microphone amplifier gain. */ + MIC_GAIN_PLUS_4DB, /*!< Select 4dB microphone amplifier gain. */ + MIC_GAIN_PLUS_5DB, /*!< Select 5dB microphone amplifier gain. */ + MIC_GAIN_PLUS_6DB, /*!< Select 6dB microphone amplifier gain. */ + MIC_GAIN_PLUS_7DB, /*!< Select 7dB microphone amplifier gain. */ + MIC_GAIN_PLUS_8DB, /*!< Select 8dB microphone amplifier gain. */ + MIC_GAIN_PLUS_9DB, /*!< Select 9dB microphone amplifier gain. */ + MIC_GAIN_PLUS_10DB, /*!< Select 10dB microphone amplifier gain. */ + MIC_GAIN_PLUS_11DB, /*!< Select 11dB microphone amplifier gain. */ + MIC_GAIN_PLUS_12DB, /*!< Select 12dB microphone amplifier gain. */ + MIC_GAIN_PLUS_13DB, /*!< Select 13dB microphone amplifier gain. */ + MIC_GAIN_PLUS_14DB, /*!< Select 14dB microphone amplifier gain. */ + MIC_GAIN_PLUS_15DB, /*!< Select 15dB microphone amplifier gain. */ + MIC_GAIN_PLUS_16DB, /*!< Select 16dB microphone amplifier gain. */ + MIC_GAIN_PLUS_17DB, /*!< Select 17dB microphone amplifier gain. */ + MIC_GAIN_PLUS_18DB, /*!< Select 18dB microphone amplifier gain. */ + MIC_GAIN_PLUS_19DB, /*!< Select 19dB microphone amplifier gain. */ + MIC_GAIN_PLUS_20DB, /*!< Select 20dB microphone amplifier gain. */ + MIC_GAIN_PLUS_21DB, /*!< Select 21dB microphone amplifier gain. */ + MIC_GAIN_PLUS_22DB, /*!< Select 22dB microphone amplifier gain. */ + MIC_GAIN_PLUS_23DB, /*!< Select 23dB microphone amplifier gain. */ + MIC_GAIN_PLUS_24DB, /*!< Select 24dB microphone amplifier gain + . */ + MIC_GAIN_PLUS_25DB, /*!< Select 25dB microphone amplifier gain + . */ + MIC_GAIN_PLUS_26DB, /*!< Select 26dB microphone amplifier gain + . */ + MIC_GAIN_PLUS_27DB, /*!< Select 27dB microphone amplifier gain + . */ + MIC_GAIN_PLUS_28DB, /*!< Select 28dB microphone amplifier gain + . */ + MIC_GAIN_PLUS_29DB, /*!< Select 29dB microphone amplifier gain + . */ + MIC_GAIN_PLUS_30DB, /*!< Select 30dB microphone amplifier gain + . */ + MIC_GAIN_PLUS_31DB /*!< Select 31dB microphone amplifier gain + . */ +} PMIC_AUDIO_MIC_GAIN; + +/*@}*/ + +/*! + * @name Audio Output Section Typedefs and Enumerations + * Typedefs and enumerations that are used for selecting and setting up + * one or more or audio output ports for playback. + */ +/*@{*/ + +/*! + * @enum PMIC_AUDIO_OUTPUT_PORT + * @brief Select the audio output port. + * + * This enumeration lists all of the available audio output ports. One or + * more may be selected as desired to handle the output audio stream from + * either the Voice CODEC or the Stereo DAC. + */ +typedef enum { + MONO_SPEAKER = 1, /*!< Select mono output speaker. */ + MONO_LOUDSPEAKER = 2, /*!< Select mono loudspeaker + . */ + MONO_ALERT = 4, /*!< Select mono alert output */ + MONO_EXTOUT = 8, /*!< Select mono external output */ + MONO_CDCOUT = 16, /*!< Select dedicated Voice CODEC output + . */ + STEREO_LEFT_LOW_POWER = 32, /*!< Select stereo left channel low power + output . */ + STEREO_HEADSET_LEFT = 64, /*!< Select stereo headset left channel. */ + STEREO_HEADSET_RIGHT = 128, /*!< Select stereo headset right channel. */ + STEREO_OUT_LEFT = 256, /*!< Select stereo external left channel + output . */ + STEREO_OUT_RIGHT = 512 /*!< Select stereo external right channel + output . */ +} PMIC_AUDIO_OUTPUT_PORT; + +/*! + * @enum PMIC_AUDIO_OUTPUT_CONFIG + * @brief Enable/disable the audio output section options. + * + * This enumeration is used to enable/disable any of the audio output section + * options. + */ +typedef enum { + MONO_SPEAKER_INVERT_OUT_ONLY = 1, /*!< Enable/disable the non-inverted + mono speaker output */ + MONO_LOUDSPEAKER_COMMON_BIAS = 2, /*!< Enable/disable the loudspeaker + output amplifier common bias + . */ + HEADSET_DETECT_ENABLE = 4, /*!< Enable/disable headset + insertion/removal detection + . */ + STEREO_HEADSET_AMP_AUTO_DISABLE = 8 /*!< Enable/disable automatic + disabling of the stereo headset + output amplifiers following + headset insertion/removal. */ +} PMIC_AUDIO_OUTPUT_CONFIG; + +/*! + * @enum PMIC_AUDIO_STEREO_IN_GAIN + * @brief Select the amplifier gain for the external stereo inputs. + * + * This enumeration is used to select the amplifier gain level to be used for + * the external stereo inputs. + */ +typedef enum { + STEREO_IN_GAIN_0DB, /*!< Select 0dB external stereo signal + input gain. */ + STEREO_IN_GAIN_PLUS_18DB /*!< Select 18dB external stereo signal + input gain . */ +} PMIC_AUDIO_STEREO_IN_GAIN; + +/*! + * @enum PMIC_AUDIO_OUTPUT_PGA_GAIN + * @brief Select the output PGA amplifier gain level. + * + * This enumeration is used to select the output PGA amplifier gain level. + */ +typedef enum { + OUTPGA_GAIN_MINUS_33DB, /*!< Select -33dB output PGA gain + . */ + OUTPGA_GAIN_MINUS_30DB, /*!< Select -30dB output PGA gain + . */ + OUTPGA_GAIN_MINUS_27DB, /*!< Select -27dB output PGA gain + . */ + OUTPGA_GAIN_MINUS_24DB, /*!< Select -24dB output PGA gain. */ + OUTPGA_GAIN_MINUS_21DB, /*!< Select -21dB output PGA gain. */ + OUTPGA_GAIN_MINUS_18DB, /*!< Select -18dB output PGA gain. */ + OUTPGA_GAIN_MINUS_15DB, /*!< Select -15dB output PGA gain. */ + OUTPGA_GAIN_MINUS_12DB, /*!< Select -12dB output PGA gain. */ + OUTPGA_GAIN_MINUS_9DB, /*!< Select -9dB output PGA gain. */ + OUTPGA_GAIN_MINUS_6DB, /*!< Select -6dB output PGA gain. */ + OUTPGA_GAIN_MINUS_3DB, /*!< Select -3dB output PGA gain. */ + OUTPGA_GAIN_0DB, /*!< Select 0dB output PGA gain. */ + OUTPGA_GAIN_PLUS_3DB, /*!< Select 3dB output PGA gain. */ + OUTPGA_GAIN_PLUS_6DB, /*!< Select 6dB output PGA gain. */ + OUTPGA_GAIN_PLUS_9DB, /*!< Select 9dB output PGA gain. + . */ + OUTPGA_GAIN_PLUS_12DB, /*!< Select 12dB output PGA gain + . */ + OUTPGA_GAIN_PLUS_15DB, /*!< Select 15dB output PGA gain + . */ + OUTPGA_GAIN_PLUS_18DB, /*!< Select 18dB output PGA gain + . */ + OUTPGA_GAIN_PLUS_21DB /*!< Select 21dB output PGA gain + . */ +} PMIC_AUDIO_OUTPUT_PGA_GAIN; + +/*! + * @enum PMIC_AUDIO_OUTPUT_BALANCE_GAIN + * @brief Select the left/right channel balance gain level. + * + * This enumeration is used to select the balance gain level that is to be + * separately applied to the left and right audio channels. + */ +typedef enum { + BAL_GAIN_MINUS_21DB, /*!< Select -21dB channel balance + gain . */ + BAL_GAIN_MINUS_18DB, /*!< Select -18dB channel balance + gain . */ + BAL_GAIN_MINUS_15DB, /*!< Select -15dB channel balance + gain . */ + BAL_GAIN_MINUS_12DB, /*!< Select -12dB channel balance + gain . */ + BAL_GAIN_MINUS_9DB, /*!< Select -9dB channel balance + gain . */ + BAL_GAIN_MINUS_6DB, /*!< Select -6dB channel balance + gain . */ + BAL_GAIN_MINUS_3DB, /*!< Select -3dB channel balance + gain . */ + BAL_GAIN_0DB /*!< Select 0dB channel balance gain. */ +} PMIC_AUDIO_OUTPUT_BALANCE_GAIN; + +/*! + * @enum PMIC_AUDIO_MONO_ADDER_MODE + * @brief Select the output mono adder operating mode. + * + * This enumeration is used to select the operating mode for the mono adder + * in the audio output section. + */ +typedef enum { + MONO_ADDER_OFF, /*!< Disable mono adder (keep separate + left and right channels). */ + MONO_ADD_LEFT_RIGHT, /*!< Add left and right channels. */ + MONO_ADD_OPPOSITE_PHASE, /*!< Add left and right channels but + with outputs in opposite phase + . */ + STEREO_OPPOSITE_PHASE /*!< Keep separate left and right + channels but invert phase of + left channel . */ +} PMIC_AUDIO_MONO_ADDER_MODE; + +/*! + * @enum PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN + * @brief Select the mono adder output amplifier gain level. + * + * This enumeration is used to select the output amplifier gain level for + * the mono adder. + */ +typedef enum { + MONOADD_GAIN_MINUS_6DB, /*!< Select -6dB mono adder output gain + . */ + MONOADD_GAIN_MINUS_3DB, /*!< Select -3dB mono adder output gain + . */ + MONOADD_GAIN_0DB /*!< Select 0dB mono adder output gain. */ +} PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN; + +/*@}*/ + +/*************************************************************************** + * PMIC-SPECIFIC DEFINITIONS * + ***************************************************************************/ + +/*! + * @name Definition of PMIC-specific Capabilities + * Constants that are used to define PMIC-specific capabilities. + */ +/*@{*/ + +/*! + * Define the minimum Stereo DAC sampling rate (Hz). + */ +extern const unsigned MIN_STDAC_SAMPLING_RATE_HZ; +/*! + * Define the maximum Stereo DAC sampling rate (Hz). + */ +extern const unsigned MAX_STDAC_SAMPLING_RATE_HZ; + +/*@}*/ + +#define DEBUG_AUDIO + +#ifdef __KERNEL__ + +/*************************************************************************** + * PMIC API DEFINITIONS * + ***************************************************************************/ + +/*! + * @name General Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Audio + * hardware. + */ +/*@{*/ + +/*! + * This function enables the Headset detection mechanism in hardware + */ +PMIC_STATUS pmic_audio_set_autodetect(int val); + +/*! + * @brief Request exclusive access to the PMIC Audio hardware. + * + * Attempt to open and gain exclusive access to a key PMIC audio hardware + * component (e.g., the Stereo DAC or the Voice CODEC). Depending upon the + * type of audio operation that is desired and the nature of the audio data + * stream, the Stereo DAC and/or the Voice CODEC will be a required hardware + * component and needs to be acquired by calling this function. + * + * If the open request is successful, then a numeric handle is returned + * and this handle must be used in all subsequent function calls to complete + * the configuration of either the Stereo DAC or the Voice CODEC and along + * with any other associated audio hardware components that will be needed. + * + * The same handle must also be used in the close call when use of the PMIC + * audio hardware is no longer required. + * + * The open request will fail if the requested audio hardware component has + * already been acquired by a previous open call but not yet closed. + * + * @param[out] handle Device handle to be used for subsequent PMIC + * Connectivity API calls. + * @param[in] device The required PMIC audio hardware component. + * + * @retval PMIC_SUCCESS If the open request was successful + * @retval PMIC_PARAMETER_ERROR If the handle argument is NULL. + * @retval PMIC_ERROR If the audio hardware component is + * unavailable. + */ +PMIC_STATUS pmic_audio_open(PMIC_AUDIO_HANDLE * const handle, + const PMIC_AUDIO_SOURCE device); + +/*! + * @brief Terminate further access to the PMIC audio hardware. + * + * Terminate further access to the PMIC audio hardware that was previously + * acquired by calling pmic_audio_open(). This now allows another thread to + * successfully call pmic_audio_open() to gain access. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the close request was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_close(const PMIC_AUDIO_HANDLE handle); + +/*! + * @brief Configure the data bus protocol to be used. + * + * Provide the parameters needed to properly configure the audio data bus + * protocol so that data can be read/written to either the Stereo DAC or + * the Voice CODEC. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] busID Select data bus to be used. + * @param[in] protocol Select the data bus protocol. + * @param[in] masterSlave Select the data bus timing mode. + * @param[in] numSlots Define the number of timeslots (only if in + * master mode). + * + * @retval PMIC_SUCCESS If the protocol was successful configured. + * @retval PMIC_PARAMETER_ERROR If the handle or the protocol parameters + * are invalid. + */ +PMIC_STATUS pmic_audio_set_protocol(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_DATA_BUS busID, + const PMIC_AUDIO_BUS_PROTOCOL protocol, + const PMIC_AUDIO_BUS_MODE masterSlave, + const PMIC_AUDIO_NUMSLOTS numSlots); + +/*! + * @brief Retrieve the current data bus protocol configuration. + * + * Retrieve the parameters that define the current audio data bus protocol. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] busID The data bus being used. + * @param[out] protocol The data bus protocol being used. + * @param[out] masterSlave The data bus timing mode being used. + * @param[out] numSlots The number of timeslots being used (if in + * master mode). + * + * @retval PMIC_SUCCESS If the protocol was successful retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_get_protocol(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_DATA_BUS * const busID, + PMIC_AUDIO_BUS_PROTOCOL * const protocol, + PMIC_AUDIO_BUS_MODE * const masterSlave, + PMIC_AUDIO_NUMSLOTS * const numSlots); + +/*! + * @brief Enable the Stereo DAC or the Voice CODEC. + * + * Explicitly enable the Stereo DAC or the Voice CODEC to begin audio + * playback or recording as required. This should only be done after + * successfully configuring all of the associated audio components (e.g., + * microphones, amplifiers, etc.). + * + * @param[in] handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the device was successful enabled. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the device could not be enabled. + */ +PMIC_STATUS pmic_audio_enable(const PMIC_AUDIO_HANDLE handle); + +/*! + * @brief Disable the Stereo DAC or the Voice CODEC. + * + * Explicitly disable the Stereo DAC or the Voice CODEC to end audio + * playback or recording as required. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the device was successful disabled. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the device could not be disabled. + */ +PMIC_STATUS pmic_audio_disable(const PMIC_AUDIO_HANDLE handle); + +/*! + * @brief Reset the selected audio hardware control registers to their + * power on state. + * + * This resets all of the audio hardware control registers currently + * associated with the device handle back to their power on states. For + * example, if the handle is associated with the Stereo DAC and a + * specific output port and output amplifiers, then this function will + * reset all of those components to their power on state. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the reset operation was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the reset was unsuccessful. + */ +PMIC_STATUS pmic_audio_reset(const PMIC_AUDIO_HANDLE handle); + +/*! + * @brief Reset all audio hardware control registers to their power on state. + * + * This resets all of the audio hardware control registers back to their + * power on states. Use this function with care since it also invalidates + * (i.e., automatically closes) all currently opened device handles. + * + * @retval PMIC_SUCCESS If the reset operation was successful. + * @retval PMIC_ERROR If the reset was unsuccessful. + */ +PMIC_STATUS pmic_audio_reset_all(void); + +/*! + * @brief Set the Audio callback function. + * + * Register a callback function that will be used to signal PMIC audio + * events. For example, the OSS audio driver should register a callback + * function in order to be notified of headset connect/disconnect events. + * + * @param[in] func A pointer to the callback function. + * @param[in] eventMask A mask selecting events to be notified. + * @param[in] hs_state To know the headset state. + * + * @retval PMIC_SUCCESS If the callback was successfully + * registered. + * @retval PMIC_PARAMETER_ERROR If the handle or the eventMask is invalid. + */ +PMIC_STATUS pmic_audio_set_callback(void *func, + const PMIC_AUDIO_EVENTS eventMask, + PMIC_HS_STATE * hs_state); + +/*! + * @brief Deregisters the existing audio callback function. + * + * Deregister the callback function that was previously registered by calling + * pmic_audio_set_callback(). + * + * + * @retval PMIC_SUCCESS If the callback was successfully + * deregistered. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_clear_callback(void); + +/*! + * @brief Get the current audio callback function settings. + * + * Get the current callback function and event mask. + * + * @param[out] func The current callback function. + * @param[out] eventMask The current event selection mask. + * + * @retval PMIC_SUCCESS If the callback information was + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_get_callback(PMIC_AUDIO_CALLBACK * const func, + PMIC_AUDIO_EVENTS * const eventMask); + +/*! + * @brief Enable the anti-pop circuitry to avoid extra noise when inserting + * or removing a external device (e.g., a headset). + * + * Enable the use of the built-in anti-pop circuitry to prevent noise from + * being generated when an external audio device is inserted or removed + * from an audio plug. A slow ramp speed may be needed to avoid extra noise. + * + * @param[in] rampSpeed The desired anti-pop circuitry ramp speed. + * + * @retval PMIC_SUCCESS If the anti-pop circuitry was successfully + * enabled. + * @retval PMIC_ERROR If the anti-pop circuitry could not be + * enabled. + */ +PMIC_STATUS pmic_audio_antipop_enable(const PMIC_AUDIO_ANTI_POP_RAMP_SPEED + rampSpeed); + +/*! + * @brief Disable the anti-pop circuitry. + * + * Disable the use of the built-in anti-pop circuitry to prevent noise from + * being generated when an external audio device is inserted or removed + * from an audio plug. + * + * @retval PMIC_SUCCESS If the anti-pop circuitry was successfully + * disabled. + * @retval PMIC_ERROR If the anti-pop circuitry could not be + * disabled. + */ +PMIC_STATUS pmic_audio_antipop_disable(void); + +/*! + * @brief Performs a reset of the Voice CODEC/Stereo DAC digital filter. + * + * This function performs a reset of the digital filter using the back-to-back + * SPI write procedure. + * + * @retval PMIC_SUCCESS If the digital filter was successfully + * reset. + * @retval PMIC_ERROR If the digital filter could not be reset. + */ +PMIC_STATUS pmic_audio_digital_filter_reset(const PMIC_AUDIO_HANDLE handle); + +/*! + * @brief Get the most recent PTT button voltage reading. + * + * This function returns the most recent reading for the PTT button voltage. + * The value may be used during the processing of the PTT_BUTTON_RANGE event + * as part of the headset ID detection process. + * + * @retval PMIC_SUCCESS If the most recent PTT button voltage was + * returned. + * @retval PMIC_PARAMETER_ERROR If a NULL pointer argument was given. + */ +PMIC_STATUS pmic_audio_get_ptt_button_level(unsigned int *const level); + +#ifdef DEBUG_AUDIO + +/*! + * @brief Provide a hexadecimal dump of all PMIC audio registers (DEBUG only). + * + * This function is intended strictly for debugging purposes only (i.e., + * the DEBUG macro must be defined) and will print the current values of the + * following PMIC registers: + * + * - AUD_CODEC (Voice CODEC state) + * - ST_DAC (Stereo DAC state) + * - RX_AUD_AMPS (audio input section state) + * - TX_AUD_AMPS (audio output section state) + * + * The register fields will also be decoded. + */ +void pmic_audio_dump_registers(void); + +#endif /* DEBUG */ + +/*@}*/ + +/*! + * @name General Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice + * CODEC hardware. + */ +/*@{*/ + +/*! + * @brief Set the Voice CODEC clock source and operating characteristics. + * + * Define the Voice CODEC clock source and operating characteristics. This + * must be done before the Voice CODEC is enabled. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] clockIn Select the clock signal source. + * @param[in] clockFreq Select the clock signal frequency. + * @param[in] samplingRate Select the audio data sampling rate. + * @param[in] invert Enable inversion of the frame sync and/or + * bit clock inputs. + * + * @retval PMIC_SUCCESS If the Voice CODEC clock settings were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or clock configuration was + * invalid. + * @retval PMIC_ERROR If the Voice CODEC clock configuration + * could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_clock(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_CLOCK_IN_SOURCE + clockIn, + const PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ + clockFreq, + const PMIC_AUDIO_VCODEC_SAMPLING_RATE + samplingRate, + const PMIC_AUDIO_CLOCK_INVERT invert); + +/*! + * @brief Get the Voice CODEC clock source and operating characteristics. + * + * Get the current Voice CODEC clock source and operating characteristics. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] clockIn The clock signal source. + * @param[out] clockFreq The clock signal frequency. + * @param[out] samplingRate The audio data sampling rate. + * @param[out] invert Inversion of the frame sync and/or + * bit clock inputs is enabled/disabled. + * + * @retval PMIC_SUCCESS If the Voice CODEC clock settings were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle invalid. + * @retval PMIC_ERROR If the Voice CODEC clock configuration + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_clock(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_CLOCK_IN_SOURCE * + const clockIn, + PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ * + const clockFreq, + PMIC_AUDIO_VCODEC_SAMPLING_RATE * + const samplingRate, + PMIC_AUDIO_CLOCK_INVERT * const invert); + +/*! + * @brief Set the Voice CODEC primary audio channel timeslot. + * + * Set the Voice CODEC primary audio channel timeslot. This function must be + * used if the default timeslot for the primary audio channel is to be changed. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] timeslot Select the primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC primary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC primary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_TIMESLOT + timeslot); + +/*! + * @brief Get the current Voice CODEC primary audio channel timeslot. + * + * Get the current Voice CODEC primary audio channel timeslot. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] timeslot The primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC primary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC primary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_VCODEC_TIMESLOT * + const timeslot); + +/*! + * @brief Set the Voice CODEC secondary recording audio channel timeslot. + * + * Set the Voice CODEC secondary audio channel timeslot. This function must be + * used if the default timeslot for the secondary audio channel is to be + * changed. The secondary audio channel timeslot is used to transmit the audio + * data that was recorded by the Voice CODEC from the secondary audio input + * channel. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] timeslot Select the secondary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC secondary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC secondary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_secondary_txslot(const PMIC_AUDIO_HANDLE + handle, + const + PMIC_AUDIO_VCODEC_TIMESLOT + timeslot); + +/*! + * @brief Get the Voice CODEC secondary recording audio channel timeslot. + * + * Get the Voice CODEC secondary audio channel timeslot. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] timeslot The secondary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC secondary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC secondary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_secondary_txslot(const PMIC_AUDIO_HANDLE + handle, + PMIC_AUDIO_VCODEC_TIMESLOT * + const timeslot); + +/*! + * @brief Set/Enable the Voice CODEC options. + * + * Set or enable various Voice CODEC options. The available options include + * the use of dithering, highpass digital filters, and loopback modes. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] config The Voice CODEC options to enable. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or Voice CODEC options + * were invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * successfully set/enabled. + */ +PMIC_STATUS pmic_audio_vcodec_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_CONFIG config); + +/*! + * @brief Clear/Disable the Voice CODEC options. + * + * Clear or disable various Voice CODEC options. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] config The Voice CODEC options to be cleared/disabled. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the Voice CODEC options + * were invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * cleared/disabled. + */ +PMIC_STATUS pmic_audio_vcodec_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_CONFIG + config); + +/*! + * @brief Get the current Voice CODEC options. + * + * Get the current Voice CODEC options. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] config The current set of Voice CODEC options. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_VCODEC_CONFIG * + const config); + +/*! + * @brief Enable the Voice CODEC bypass audio pathway. + * + * Enables the Voice CODEC bypass pathway for audio data. This allows direct + * output of the voltages on the TX data bus line to the output amplifiers + * (bypassing the digital-to-analog converters within the Voice CODEC). + * + * @param[in] handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC bypass was successfully + * enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC bypass could not be + * enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_bypass(const PMIC_AUDIO_HANDLE handle); + +/*! + * @brief Disable the Voice CODEC bypass audio pathway. + * + * Disables the Voice CODEC bypass pathway for audio data. This means that + * the TX data bus line will deliver digital data to the digital-to-analog + * converters within the Voice CODEC. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC bypass was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC bypass could not be + * disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_bypass(const PMIC_AUDIO_HANDLE handle); + +/*@}*/ + +/*! + * @name General Stereo DAC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Stereo + * DAC hardware. + */ +/*@{*/ + +/*! + * @brief Set the Stereo DAC clock source and operating characteristics. + * + * Define the Stereo DAC clock source and operating characteristics. This + * must be done before the Stereo DAC is enabled. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] clockIn Select the clock signal source. + * @param[in] clockFreq Select the clock signal frequency. + * @param[in] samplingRate Select the audio data sampling rate. + * @param[in] invert Enable inversion of the frame sync and/or + * bit clock inputs. + * + * @retval PMIC_SUCCESS If the Stereo DAC clock settings were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or clock configuration was + * invalid. + * @retval PMIC_ERROR If the Stereo DAC clock configuration + * could not be set. + */ +PMIC_STATUS pmic_audio_stdac_set_clock(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_CLOCK_IN_SOURCE clockIn, + const PMIC_AUDIO_STDAC_CLOCK_IN_FREQ + clockFreq, + const PMIC_AUDIO_STDAC_SAMPLING_RATE + samplingRate, + const PMIC_AUDIO_CLOCK_INVERT invert); + +/*! + * @brief Get the Stereo DAC clock source and operating characteristics. + * + * Get the current Stereo DAC clock source and operating characteristics. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] clockIn The clock signal source. + * @param[out] clockFreq The clock signal frequency. + * @param[out] samplingRate The audio data sampling rate. + * @param[out] invert Inversion of the frame sync and/or + * bit clock inputs is enabled/disabled. + * + * @retval PMIC_SUCCESS If the Stereo DAC clock settings were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle invalid. + * @retval PMIC_ERROR If the Stereo DAC clock configuration + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_clock(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_CLOCK_IN_SOURCE * + const clockIn, + PMIC_AUDIO_STDAC_SAMPLING_RATE * + const samplingRate, + PMIC_AUDIO_STDAC_CLOCK_IN_FREQ * + const clockFreq, + PMIC_AUDIO_CLOCK_INVERT * const invert); + +/*! + * @brief Set the Stereo DAC primary audio channel timeslot. + * + * Set the Stereo DAC primary audio channel timeslot. This function must be + * used if the default timeslot for the primary audio channel is to be changed. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] timeslot Select the primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Stereo DAC primary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Stereo DAC primary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_stdac_set_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_TIMESLOTS + timeslot); + +/*! + * @brief Get the current Stereo DAC primary audio channel timeslot. + * + * Get the current Stereo DAC primary audio channel timeslot. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] timeslot The primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Stereo DAC primary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC primary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STDAC_TIMESLOTS * + const timeslot); + +/*! + * @brief Set/Enable the Stereo DAC options. + * + * Set or enable various Stereo DAC options. The available options include + * enabling/disabling the bus master clock outputs. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] config The Stereo DAC options to enable. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or Stereo DAC options + * were invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * successfully set/enabled. + */ +PMIC_STATUS pmic_audio_stdac_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_CONFIG config); + +/*! + * @brief Clear/Disable the Stereo DAC options. + * + * Clear or disable various Stereo DAC options. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] config The Stereo DAC options to be cleared/disabled. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the Stereo DAC options + * were invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * cleared/disabled. + */ +PMIC_STATUS pmic_audio_stdac_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_CONFIG config); + +/*! + * @brief Get the current Stereo DAC options. + * + * Get the current Stereo DAC options. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] config The current set of Stereo DAC options. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STDAC_CONFIG * const config); + +/*@}*/ + +/*! + * @name Audio Input Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC audio + * input hardware. + */ +/*@{*/ + +/*! + * @brief Set/Enable the audio input section options. + * + * Set or enable various audio input section options. The only available + * option right now is to enable the automatic disabling of the microphone + * input amplifiers when a microphone/headset is inserted or removed. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] config The audio input section options to enable. + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio input section + * options were invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be successfully set/enabled. + */ +PMIC_STATUS pmic_audio_input_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_CONFIG config); + +/*! + * @brief Clear/Disable the audio input section options. + * + * Clear or disable various audio input section options. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] config The audio input section options to be + * cleared/disabled. + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the audio input section + * options were invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be cleared/disabled. + */ +PMIC_STATUS pmic_audio_input_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_CONFIG config); + +/*! + * @brief Get the current audio input section options. + * + * Get the current audio input section options. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] config The current set of audio input section options. + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be retrieved. + */ +PMIC_STATUS pmic_audio_input_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_CONFIG * const config); + +/*@}*/ + +/*! + * @name Audio Recording Using the Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice CODEC + * to perform audio recording. + */ +/*@{*/ + +/*! + * @brief Select the microphone inputs to be used for Voice CODEC recording. + * + * Select left and right microphone inputs for Voice CODEC + * recording. It is possible to disable or not use a particular microphone + * input channel by specifying NO_MIC as a parameter. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] leftChannel Select the left microphone input channel. + * @param[in] rightChannel Select the right microphone input channel. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input ports + * were invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be successfully enabled. + */ +PMIC_STATUS pmic_audio_vcodec_set_mic(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_PORT leftChannel, + const PMIC_AUDIO_INPUT_PORT rightChannel); + +/*! + * @brief Get the current microphone inputs being used for Voice CODEC + * recording. + * + * Get the left and right microphone inputs currently being + * used for Voice CODEC recording. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] leftChannel The left microphone input channel. + * @param[out] rightChannel The right microphone input channel. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_mic(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_PORT * const leftChannel, + PMIC_AUDIO_INPUT_PORT * + const rightChannel); + +/*! + * @brief Enable/disable the microphone input. + * + * This function enables/disables the current microphone input channel. The + * input amplifier is automatically turned off when the microphone input is + * disabled. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] leftChannel The left microphone input channel state. + * @param[in] rightChannel the right microphone input channel state. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully reconfigured. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input states + * were invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be reconfigured. + */ +PMIC_STATUS pmic_audio_vcodec_set_mic_on_off(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_MIC_STATE + leftChannel, + const PMIC_AUDIO_INPUT_MIC_STATE + rightChannel); + +/*! + * @brief Return the current state of the microphone inputs. + * + * This function returns the current state (on/off) of the microphone + * input channels. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] leftChannel The current left microphone input channel + * state. + * @param[out] rightChannel the current right microphone input channel + * state. + * + * @retval PMIC_SUCCESS If the microphone input channel states + * were successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input channel states + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_mic_on_off(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_MIC_STATE * + const leftChannel, + PMIC_AUDIO_INPUT_MIC_STATE * + const rightChannel); + +/*! + * @brief Set the microphone input amplifier mode and gain level. + * + * This function sets the current microphone input amplifier operating mode + * and gain level. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] leftChannelMode The left microphone input amplifier mode. + * @param[in] leftChannelGain The left microphone input amplifier gain level. + * @param[in] rightChannelMode The right microphone input amplifier mode. + * @param[in] rightChannelGain The right microphone input amplifier gain + * level. + * + * @retval PMIC_SUCCESS If the microphone input amplifiers were + * successfully reconfigured. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input amplifier + * modes or gain levels were invalid. + * @retval PMIC_ERROR If the microphone input amplifiers could + * not be reconfigured. + */ +PMIC_STATUS pmic_audio_vcodec_set_record_gain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_AMP_MODE + leftChannelMode, + const PMIC_AUDIO_MIC_GAIN + leftChannelGain, + const PMIC_AUDIO_MIC_AMP_MODE + rightChannelMode, + const PMIC_AUDIO_MIC_GAIN + rightChannelGain); + +/*! + * @brief Get the current microphone input amplifier mode and gain level. + * + * This function gets the current microphone input amplifier operating mode + * and gain level. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] leftChannelMode The left microphone input amplifier mode. + * @param[out] leftChannelGain The left microphone input amplifier gain level. + * @param[out] rightChannelMode The right microphone input amplifier mode. + * @param[out] rightChannelGain The right microphone input amplifier gain + * level. + * + * @retval PMIC_SUCCESS If the microphone input amplifier modes + * and gain levels were successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input amplifier modes + * and gain levels could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_record_gain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_MIC_AMP_MODE * + const leftChannelMode, + PMIC_AUDIO_MIC_GAIN * + const leftChannelGain, + PMIC_AUDIO_MIC_AMP_MODE * + const rightChannelMode, + PMIC_AUDIO_MIC_GAIN * + const rightChannelGain); + +/*! + * @brief Enable a microphone bias circuit. + * + * This function enables one of the available microphone bias circuits. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] biasCircuit The microphone bias circuit to be enabled. + * + * @retval PMIC_SUCCESS If the microphone bias circuit was + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or selected microphone bias + * circuit was invalid. + * @retval PMIC_ERROR If the microphone bias circuit could not + * be enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_micbias(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_BIAS + biasCircuit); + +/*! + * @brief Disable a microphone bias circuit. + * + * This function disables one of the available microphone bias circuits. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] biasCircuit The microphone bias circuit to be disabled. + * + * @retval PMIC_SUCCESS If the microphone bias circuit was + * successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or selected microphone bias + * circuit was invalid. + * @retval PMIC_ERROR If the microphone bias circuit could not + * be disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_micbias(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_BIAS + biasCircuit); + +/*@}*/ + +/*! + * @name Audio Playback Using the Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice CODEC + * to perform audio playback. + */ +/*@{*/ + +/*! + * @brief Configure and enable the Voice CODEC mixer. + * + * This function configures and enables the Voice CODEC mixer. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] rxSecondaryTimeslot The timeslot used for the secondary audio + * channel. + * @param[in] gainIn The secondary audio channel gain level. + * @param[in] gainOut The mixer output gain level. + * + * @retval PMIC_SUCCESS If the Voice CODEC mixer was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mixer configuration + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC mixer could not be + * reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_mixer(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_TIMESLOT + rxSecondaryTimeslot, + const PMIC_AUDIO_VCODEC_MIX_IN_GAIN + gainIn, + const PMIC_AUDIO_VCODEC_MIX_OUT_GAIN + gainOut); + +/*! + * @brief Disable the Voice CODEC mixer. + * + * This function disables the Voice CODEC mixer. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC mixer was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC mixer could not be + * disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_mixer(const PMIC_AUDIO_HANDLE handle); + +/*@}*/ + +/*! + * @name Audio Playback Using the Stereo DAC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Stereo DAC + * to perform audio playback. + */ +/*@{*/ + +/*! + * @brief Configure and enable the Stereo DAC mixer. + * + * This function configures and enables the Stereo DAC mixer. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] rxSecondaryTimeslot The timeslot used for the secondary audio + * channel. + * @param[in] gainIn The secondary audio channel gain level. + * @param[in] gainOut The mixer output gain level. + * + * @retval PMIC_SUCCESS If the Stereo DAC mixer was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mixer configuration + * was invalid. + * @retval PMIC_ERROR If the Stereo DAC mixer could not be + * reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_stdac_enable_mixer(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_TIMESLOTS + rxSecondaryTimeslot, + const PMIC_AUDIO_STDAC_MIX_IN_GAIN + gainIn, + const PMIC_AUDIO_STDAC_MIX_OUT_GAIN + gainOut); + +/*! + * @brief Disable the Stereo DAC mixer. + * + * This function disables the Stereo DAC mixer. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Stereo DAC mixer was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC mixer could not be + * disabled. + */ +PMIC_STATUS pmic_audio_stdac_disable_mixer(const PMIC_AUDIO_HANDLE handle); + +/*@}*/ + +/*! + * @name Audio Output Section Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC audio output + * section to support playback. + */ +/*@{*/ + +/*! + * @brief Select the audio output ports. + * + * This function selects the audio output ports to be used. This also enables + * the appropriate output amplifiers. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] port The audio output ports to be used. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * acquired. + * @retval PMIC_PARAMETER_ERROR If the handle or output ports were + * invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * acquired. + */ +PMIC_STATUS pmic_audio_output_set_port(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PORT port); + +/*! + * @brief Deselect/disable the audio output ports. + * + * This function disables the audio output ports that were previously enabled + * by calling pmic_audio_output_set_port(). + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] port The audio output ports to be disabled. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or output ports were + * invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * disabled. + */ +PMIC_STATUS pmic_audio_output_clear_port(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PORT port); + +/*! + * @brief Get the current audio output ports. + * + * This function retrieves the audio output ports that are currently being + * used. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] port The audio output ports currently being used. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_output_get_port(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_PORT * const port); + +/*! + * @brief Set the gain level for the external stereo inputs. + * + * This function sets the gain levels for the external stereo inputs. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] gain The external stereo input gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the gain level could not be set. + */ +PMIC_STATUS pmic_audio_output_set_stereo_in_gain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STEREO_IN_GAIN + gain); + +/*! + * @brief Get the current gain level for the external stereo inputs. + * + * This function retrieves the current gain levels for the external stereo + * inputs. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] gain The current external stereo input gain + * level. + * + * @retval PMIC_SUCCESS If the gain level was successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the gain level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_stereo_in_gain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STEREO_IN_GAIN * + const gain); + +/*! + * @brief Set the output PGA gain level. + * + * This function sets the audio output PGA gain level. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] gain The output PGA gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the gain level could not be set. + */ +PMIC_STATUS pmic_audio_output_set_pgaGain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PGA_GAIN + gain); + +/*! + * @brief Get the output PGA gain level. + * + * This function retrieves the current audio output PGA gain level. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] gain The current output PGA gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the gain level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_pgaGain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_PGA_GAIN * + const gain); + +/*! + * @brief Enable the output mixer. + * + * This function enables the output mixer for the audio stream that + * corresponds to the current handle (i.e., the Voice CODEC, Stereo DAC, or + * the external stereo inputs). + * + * @param[in] handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mixer was successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mixer could not be enabled. + */ +PMIC_STATUS pmic_audio_output_enable_mixer(const PMIC_AUDIO_HANDLE handle); + +/*! + * @brief Disable the output mixer. + * + * This function disables the output mixer for the audio stream that + * corresponds to the current handle (i.e., the Voice CODEC, Stereo DAC, or + * the external stereo inputs). + * + * @param[in] handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mixer was successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mixer could not be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_mixer(const PMIC_AUDIO_HANDLE handle); + +/*! + * @brief Configure and enable the output balance amplifiers. + * + * This function configures and enables the output balance amplifiers. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] leftGain The desired left channel gain level. + * @param[in] rightGain The desired right channel gain level. + * + * @retval PMIC_SUCCESS If the output balance amplifiers were + * successfully configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or gain levels were invalid. + * @retval PMIC_ERROR If the output balance amplifiers could not + * be reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_output_set_balance(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_BALANCE_GAIN + leftGain, + const PMIC_AUDIO_OUTPUT_BALANCE_GAIN + rightGain); + +/*! + * @brief Get the current output balance amplifier gain levels. + * + * This function retrieves the current output balance amplifier gain levels. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] leftGain The current left channel gain level. + * @param[out] rightGain The current right channel gain level. + * + * @retval PMIC_SUCCESS If the output balance amplifier gain levels + * were successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the output balance amplifier gain levels + * could be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_balance(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_BALANCE_GAIN * + const leftGain, + PMIC_AUDIO_OUTPUT_BALANCE_GAIN * + const rightGain); + +/*! + * @brief Configure and enable the output mono adder. + * + * This function configures and enables the output mono adder. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] mode The desired mono adder operating mode. + * + * @retval PMIC_SUCCESS If the mono adder was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mono adder mode was + * invalid. + * @retval PMIC_ERROR If the mono adder could not be reconfigured + * or enabled. + */ +PMIC_STATUS pmic_audio_output_enable_mono_adder(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MONO_ADDER_MODE + mode); + +/*! + * @brief Disable the output mono adder. + * + * This function disables the output mono adder. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mono adder was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mono adder could not be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_mono_adder(const PMIC_AUDIO_HANDLE + handle); + +/*! + * @brief Configure the mono adder output gain level. + * + * This function configures the mono adder output amplifier gain level. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] gain The desired output gain level. + * + * @retval PMIC_SUCCESS If the mono adder output amplifier gain + * level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the mono adder output amplifier gain + * level could not be reconfigured. + */ +PMIC_STATUS pmic_audio_output_set_mono_adder_gain(const PMIC_AUDIO_HANDLE + handle, + const + PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN + gain); + +/*! + * @brief Get the current mono adder output gain level. + * + * This function retrieves the current mono adder output amplifier gain level. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] gain The current output gain level. + * + * @retval PMIC_SUCCESS If the mono adder output amplifier gain + * level was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mono adder output amplifier gain + * level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_mono_adder_gain(const PMIC_AUDIO_HANDLE + handle, + PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN + * const gain); + +/*! + * @brief Set various audio output section options. + * + * This function sets one or more audio output section configuration + * options. The currently supported options include whether to disable + * the non-inverting mono speaker output, enabling the loudspeaker common + * bias circuit, enabling detection of headset insertion/removal, and + * whether to automatically disable the headset amplifiers when a headset + * insertion/removal has been detected. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] config The desired audio output section + * configuration options to be set. + * + * @retval PMIC_SUCCESS If the desired configuration options were + * all successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or configuration options + * were invalid. + * @retval PMIC_ERROR If the desired configuration options + * could not be set. + */ +PMIC_STATUS pmic_audio_output_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_CONFIG config); + +/*! + * @brief Clear various audio output section options. + * + * This function clears one or more audio output section configuration + * options. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[in] config The desired audio output section + * configuration options to be cleared. + * + * @retval PMIC_SUCCESS If the desired configuration options were + * all successfully cleared. + * @retval PMIC_PARAMETER_ERROR If the handle or configuration options + * were invalid. + * @retval PMIC_ERROR If the desired configuration options + * could not be cleared. + */ +PMIC_STATUS pmic_audio_output_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_CONFIG + config); + +/*! + * @brief Get the current audio output section options. + * + * This function retrieves the current audio output section configuration + * option settings. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] config The current audio output section + * configuration option settings. + * + * @retval PMIC_SUCCESS If the current configuration options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the current configuration options + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_CONFIG * + const config); + +/*! + * @brief Enable the phantom ground circuit that is used to help identify + * the type of headset that has been inserted. + * + * This function enables the phantom ground circuit that is used to help + * identify the type of headset (e.g., stereo or mono) that has been inserted. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the phantom ground circuit was + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the phantom ground circuit could not + * be enabled. + */ +PMIC_STATUS pmic_audio_output_enable_phantom_ground(void); + +/*! + * @brief Disable the phantom ground circuit that is used to help identify + * the type of headset that has been inserted. + * + * This function disables the phantom ground circuit that is used to help + * identify the type of headset (e.g., stereo or mono) that has been inserted. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the phantom ground circuit was + * successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the phantom ground circuit could not + * be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_phantom_ground(void); + +/*! + * @brief Enable/Disable fm output + * + * This function enables/disables the fm output. + * + * @param[in] enable True to enable and false to disable + * + * @retval PMIC_SUCCESS If the fm output was + * successfully enable or disabled + * @retval PMIC_ERROR If the operation fails + */ +PMIC_STATUS pmic_audio_fm_output_enable(bool enable); + +/*@}*/ + +#endif /* __KERNEL__ */ + +#endif /* __ASM_ARCH_MXC_PMIC_AUDIO_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/pmic_convity.h b/arch/arm/plat-mxc/include/mach/pmic_convity.h new file mode 100644 index 000000000000..b88bb54bad26 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/pmic_convity.h @@ -0,0 +1,873 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_ARCH_MXC_PMIC_CONVITY_H__ +#define __ASM_ARCH_MXC_PMIC_CONVITY_H__ + +/*! + * @defgroup PMIC_CONNECTIVITY PMIC Connectivity Driver + * @ingroup PMIC_DRVRS + */ + +/*! + * @file arch-mxc/pmic_convity.h + * @brief External definitions for the PMIC Connectivity Client driver. + * + * The PMIC Connectivity driver and this API were developed to support the + * external connectivity capabilities of several power management ICs that + * are available from Freescale Semiconductor, Inc. + * + * The following operating modes, in terms of external connectivity, are + * supported: + * + * + * + * @ingroup PMIC_CONNECTIVITY + */ + +#include <linux/pmic_status.h> +#include <linux/pmic_external.h> + +/*************************************************************************** + * TYPEDEFS AND ENUMERATIONS * + ***************************************************************************/ + +/*! + * @name General Setup and Configuration Typedefs and Enumerations + * Typedefs and enumerations that are used for initial access to and + * configuration of the PMIC Connectivity hardware. + */ +/*@{*/ + +#define DEBUG_CONVITY + +/*! + * @typedef PMIC_CONVITY_HANDLE + * @brief Define typedef for a handle to the PMIC Connectivity hardware. + * + * Define a "handle" that is returned when the PMIC Connectivity hardware + * is opened. This handle grants exclusive access to the PMIC Connectivity + * hardware and must be used in all subsequent function calls. When access + * to the PMIC Connectivity hardware is no longer required, then a close + * operation must be done with this handle. The handle is no longer valid + * if the close operation was successful. + */ +typedef long PMIC_CONVITY_HANDLE; + +/*! + * @enum PMIC_CONVITY_MODE + * @brief Select the main Connectivity operating mode. + * + * Defines all possible PMIC Connectivity main operating modes. Only one of + * these modes can be active at a time. + */ +typedef enum { + USB, /*!< Select USB mode (this is also the Reset/Default + mode). */ + RS232, /*!< Select RS-232 mode. for SC55112 */ + RS232_1, /*!< Select RS-232_1 mode. */ + RS232_2, /*!< Select RS-232_2 mode. */ + CEA936_MONO, /*!< Select CE-936 Mono mode . */ + CEA936_STEREO, /*!< Select CE-936 Stereo mode . */ + CEA936_TEST_RIGHT, /*!< Select CE-936 Right Channel Test mode + . */ + CEA936_TEST_LEFT /*!< Select CE-936 Left Channel Test mode + . */ +} PMIC_CONVITY_MODE; + +/*! + * @enum PMIC_CONVITY_EVENTS + * @brief Identify the connectivity events that have been detected and should + * be handled. + * + * Defines all possible PMIC Connectivity events. Multiple events may be + * selected when defining a mask. + */ +typedef enum { + USB_DETECT_4V4_RISE = 1, /*!< Detected 4.4V rising edge. */ + USB_DETECT_4V4_FALL = 2, /*!< Detected 4.4V falling edge. */ + USB_DETECT_2V0_RISE = 4, /*!< Detected 2.0V rising edge. */ + USB_DETECT_2V0_FALL = 8, /*!< Detected 2.0V falling edge. */ + USB_DETECT_0V8_RISE = 16, /*!< Detected 0.8V rising edge. */ + USB_DETECT_0V8_FALL = 32, /*!< Detected 0.8V falling edge. */ + USB_DETECT_MINI_A = 64, /*!< Detected USB mini A plug. */ + USB_DETECT_MINI_B = 128, /*!< Detected USB mini B plug. */ + USB_DETECT_NON_USB_ACCESSORY = 256, /*!< Detected a non-USB connection + . */ + USB_DETECT_FACTORY_MODE = 512, /*!< Detected a factory-mode + connection . */ + USB_DP_HI = 1024, + + USB_DM_HI = 2048 +} PMIC_CONVITY_EVENTS; + +/*! + * @typedef PMIC_CONVITY_CALLBACK + * @brief Typedef for PMIC Connectivity event notification callback function. + * + * Define a typedef for the PMIC Connectivity event notification callback + * function. The signalled events are passed to the function as the first + * argument. The callback function should then process whatever events it + * can and then return the set of unhandled events (if any). + */ +typedef void (*PMIC_CONVITY_CALLBACK) (const PMIC_CONVITY_EVENTS event); + +/*@}*/ + +/*! + * @name USB and USB On-The-Go Mode-specific Typedefs and Enumerations + * Typedefs and enumerations that are used only for setting up and controlling + * the USB and USB On-The-Go modes of operation. + */ +/*@{*/ + +/*! + * @enum PMIC_CONVITY_USB_DEVICE_TYPE + * @brief Select the USB device type (either A or B). + * + * Defines all possible USB device types. This must match the physical + * connector being used. + */ +typedef enum { + USB_A_DEVICE, + USB_B_DEVICE +} PMIC_CONVITY_USB_DEVICE_TYPE; + +/*! + * @enum PMIC_CONVITY_USB_SPEED + * @brief Select the USB transceiver operating speed. + * + * Defines all possible USB transceiver operating speeds. Only one + * speed setting may be used at a time. + */ +typedef enum { + USB_LOW_SPEED, /*!< Select 1.5 Mbps. */ + USB_FULL_SPEED, /*!< Select 12 Mbps. */ + USB_HIGH_SPEED /*!< Select 480 Mbps <b>(currently + not supported)</b>. */ +} PMIC_CONVITY_USB_SPEED; + +/*! + * @enum PMIC_CONVITY_USB_MODE + * @brief Select the USB transceiver operating mode. + * + * Defines all possible USB transceiver operating modes. Only one + * mode may be used at a time. The selected mode, in combination with + * the USB bus speed, determines the selection of pull-up and pull-down + * resistors. + */ +typedef enum { + USB_HOST, + USB_PERIPHERAL +} PMIC_CONVITY_USB_MODE; + +/*! + * @enum PMIC_CONVITY_USB_POWER_IN + * @brief Select the USB transceiver's power regulator input source. + * + * Defines all possible input power sources for the USB transceiver power + * regulator. Only one power supply source may be selected at a time. + */ +typedef enum { + + USB_POWER_INTERNAL_BOOST, /*!< Select internal power source + with boost. */ + + USB_POWER_VBUS, /*!< Select VBUS power source. */ + + USB_POWER_INTERNAL /*!< Select internal power source + . */ +} PMIC_CONVITY_USB_POWER_IN; + +/*! + * @enum PMIC_CONVITY_USB_POWER_OUT + * @brief Select the USB transceiver power regulator output voltage. + * + * Defines all possible output voltages for the USB transceiver power + * regulator. Only one power output voltage level may be selected at + * a time. + */ +typedef enum { + USB_POWER_2V775, /*!< Select 2.775V output voltage + . */ + USB_POWER_3V3 /*!< Select 3.3V output voltage. */ +} PMIC_CONVITY_USB_POWER_OUT; + +/*! + * @enum PMIC_CONVITY_USB_TRANSCEIVER_MODE + * @brief Select the USB transceiver operating mode. + * + * Defines all valid USB transceiver operating modes. Only one of the + * following USB transceiver modes may be selected at a time. + */ +typedef enum { + USB_TRANSCEIVER_OFF, /*!< USB transceiver currently off + . */ + USB_SINGLE_ENDED_UNIDIR, /*!< Select Single-ended + unidirectional transmit mode. */ + USB_SINGLE_ENDED_UNIDIR_TX, /*!< Select Single-ended + unidirectional transmit mode. */ + USB_SINGLE_ENDED_UNIDIR_RX, /*!< Select Single-ended + unidirectional receive mode. */ + USB_SINGLE_ENDED_BIDIR, /*!< Select Single-ended + bidirectional transmit mode. */ + USB_SINGLE_ENDED_LOW, /*!< Select USB SE0 mode. */ + USB_DIFFERENTIAL_UNIDIR_TX, /*!< Select Differential + unidirectional transmit mode + . */ + USB_DIFFERENTIAL_UNIDIR, /*!< Select Differential + unidirectional transmit mode + . */ + + USB_DIFFERENTIAL_UNIDIR_RX, /*!< Select Differential + unidirectional receive mode. */ + USB_DIFFERENTIAL_BIDIR, /*!< Select Differential + bidirectional transmit mode + */ + USB_SUSPEND_ON, /*!< Select Suspend mode. */ + USB_SUSPEND_OFF, /*!< Terminate Suspend mode. */ + USB_OTG_SRP_DLP_START, /*!< Start USB On-The-Go Session + Request Protocol using Data + Line Pulsing. */ + USB_OTG_SRP_DLP_STOP /*!< Terminate USB On-The-Go Session + Request Protocol using Data + Line Pulsing. */ +} PMIC_CONVITY_USB_TRANSCEIVER_MODE; + +/*! + * @enum PMIC_CONVITY_USB_OTG_CONFIG + * @brief Select the USB On-The-Go configuration options. + * + * Defines all possible USB On-The-Go configuration options. Multiple + * configuration options may be selected at the same time. However, only one + * VBUS current limit may be selected at a time. Selecting more than one + * VBUS current limit will result in undefined and implementation-dependent + * behavior. + */ +typedef enum { + USB_OTG_SE0CONN = 0x00001, /*!< Enable automatic + connection of a pull-up + resistor to VUSB when the + SE0 condition is detected. */ + USB_OTG_DLP_SRP = 0x00002, /*!< Enable use of the hardware + timer to control the + duration of the data line + pulse during the session + request protocol. */ + USB_PULL_OVERRIDE = 0x00004, /*!< Enable automatic disconnect + of pull-up and pull-down + resistors when transmitter + is enabled. */ + + USB_DP150K_PU = 0x00008, + + USB_VBUS_CURRENT_LIMIT_HIGH = 0x00010, /*!< Select current limit to 200mA + for VBUS regulator. */ + USB_VBUS_CURRENT_LIMIT_LOW = 0x00020, /*!< Select low current limit + for VBUS regulator. */ + USB_VBUS_CURRENT_LIMIT_LOW_10MS = 0x00040, /*!< Select low current limit + for VBUS regulator for + 10 ms . */ + USB_VBUS_CURRENT_LIMIT_LOW_20MS = 0x00080, /*!< Select low current limit + for VBUS regulator for + 20 ms . */ + USB_VBUS_CURRENT_LIMIT_LOW_30MS = 0x00100, /*!< Select low current limit + for VBUS regulator for + 30 ms . */ + USB_VBUS_CURRENT_LIMIT_LOW_40MS = 0x00200, /*!< Select low current limit + for VBUS regulator for + 40 ms . */ + USB_VBUS_CURRENT_LIMIT_LOW_50MS = 0x00400, /*!< Select low current limit + for VBUS regulator for + 50 ms . */ + USB_VBUS_CURRENT_LIMIT_LOW_60MS = 0x00800, /*!< Select low current limit + for VBUS regulator for + 60 ms . */ + + USB_VBUS_PULLDOWN = 0x01000, /*!< Enable VBUS pull-down. */ + + USB_USBCNTRL = 0x02000, + + USB_UDP_PD = 0x04000, + + USB_UDM_PD = 0x08000, + + USB_PU = 0x10000, + + USBXCVREN = 0x20000 +} PMIC_CONVITY_USB_OTG_CONFIG; +/*@}*/ + +/*! + * @name RS-232 Mode-specific Typedefs and Enumerations + * Typedefs and enumerations that are used only for setting up and controlling + * the RS-232 mode of operation. + */ +/*@{*/ + +/*! + * @enum PMIC_CONVITY_RS232_EXTERNAL + * @brief Select the RS-232 transceiver external connections. + * + * Defines all valid RS-232 transceiver external RX/TX connection options. + * Only one connection mode may be selected at a time. + */ +typedef enum { + RS232_TX_UDM_RX_UDP, /*!< Select RS-232 TX on UDM */ + RS232_TX_UDP_RX_UDM, /*!< Select RS-232 TX on UDP + . */ + RS232_TX_RX_EXTERNAL_DEFAULT /*!< Use power on default. */ +} PMIC_CONVITY_RS232_EXTERNAL; + +/*! + * @enum PMIC_CONVITY_RS232_INTERNAL + * @brief Select the RS-232 transceiver internal connections. + * + * Defines all valid RS-232 transceiver internal RX/TX connection options. + * Only one connection mode can be selected at a time. + */ +typedef enum { + RS232_TX_USE0VM_RX_UDATVP, /*!< Select RS-232 TX from USE0VM + . */ + RS232_TX_UDATVP_RX_URXVM, /*!< Select RS-232 TX from UDATVP + . */ + RS232_TX_UTXDI_RX_URXDO, /*!< Select RS-232 TX from UTXDI + . */ + RS232_TX_RX_INTERNAL_DEFAULT /*!< Use power on default. */ +} PMIC_CONVITY_RS232_INTERNAL; + +/*@}*/ + +/*! + * @name CEA-936 Mode-specific Typedefs and Enumerations + * Typedefs and enumerations that are used only for setting up and controlling + * the CEA-936 mode of operation. + */ +/*@{*/ + +/*! + * @enum PMIC_CONVITY_CEA936_EXIT_SIGNAL + * @brief Select the CEA-936 mode exit signal. + * + * Defines all valid CEA-936 connection termination signals. Only one + * termination signal can be selected at a time. + */ +typedef enum { + CEA936_UID_NO_PULLDOWN, /*!< No UID pull-down . */ + CEA936_UID_PULLDOWN_6MS, /*!< UID pull-down for 6 ms (+/-2 ms) + . */ + CEA936_UID_PULLDOWN, /*!< UID pulled down . */ + CEA936_UDMPULSE /*!< UDM pulsed . */ +} PMIC_CONVITY_CEA936_EXIT_SIGNAL; + +/*@}*/ + +/*************************************************************************** + * PMIC API DEFINITIONS * + ***************************************************************************/ + +/*! + * @name General Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Connectivity + * hardware. + */ +/*@{*/ + +/*! + * @brief Request exclusive access to the PMIC Connectivity hardware. + * + * Attempt to open and gain exclusive access to the PMIC Connectivity + * hardware. An initial operating mode (e.g., USB or RS-232) must also + * be specified. + * + * If the open request is successful, then a numeric handle is returned + * and this handle must be used in all subsequent function calls. The + * same handle must also be used in the close call when use of the PMIC + * connectivity hardware is no longer required. + * + * The open request will fail if another thread has already obtained the + * device handle and has not yet called pmic_convity_close() with it. + * + * @param handle Device handle to be used for subsequent PMIC + * Connectivity API calls. + * @param mode Initial connectivity operating mode. + * + * @retval PMIC_SUCCESS If the open request was successful + * @retval PMIC_ERROR If the connectivity hardware cannot be opened. + */ +PMIC_STATUS pmic_convity_open(PMIC_CONVITY_HANDLE * const handle, + const PMIC_CONVITY_MODE mode); + +/*! + * @brief Terminate further access to the PMIC Connectivity hardware. + * + * Terminate further access to the PMIC Connectivity hardware. This also + * allows another thread to successfully call pmic_convity_open() to gain + * access. + * + * @param handle Device handle from open() call. + * + * @retval PMIC_SUCCESS If the close request was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_convity_close(const PMIC_CONVITY_HANDLE handle); + +/*! + * @brief Set the PMIC Connectivity main operating mode. + * + * Change the current operating mode of the PMIC Connectivity hardware. + * The available connectivity operating modes are hardware-dependent and + * consists of one or more of the following: USB (including USB On-the-Go), + * RS-232, and CEA-936. Requesting an operating mode that is not supported + * by the PMIC hardware will return PMIC_NOT_SUPPORTED. + * + * @param handle Device handle from open() call. + * @param mode Desired operating mode. + * + * @retval PMIC_SUCCESS If the requested mode was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_NOT_SUPPORTED If the PMIC hardware does not support + * the desired operating mode. + */ +PMIC_STATUS pmic_convity_set_mode(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_MODE mode); + +/*! + * @brief Get the current PMIC Connectivity main operating mode. + * + * Get the current operating mode for the PMIC Connectivity hardware. + * + * @param handle Device handle from open() call. + * @param mode The current PMIC Connectivity operating mode. + * + * @retval PMIC_SUCCESS If the requested mode was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_convity_get_mode(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_MODE * const mode); + +/*! + * @brief Reset the Connectivity hardware to it's power on state. + * + * Restore all registers to the initial power-on/reset state. + * + * @param handle Device handle from open() call. + * + * @retval PMIC_SUCCESS If the reset was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_convity_reset(const PMIC_CONVITY_HANDLE handle); + +/*! + * @brief Set the Connectivity callback function. + * + * Register a callback function that will be used to signal PMIC Connectivity + * events. For example, the USB subsystem should register a callback function + * in order to be notified of device connect/disconnect events. Note, however, + * that non-USB events may also be signalled depending upon the PMIC hardware + * capabilities. Therefore, the callback function must be able to properly + * handle all of the possible events if support for non-USB peripherals is + * also to be included. + * + * @param handle Device handle from open() call. + * @param func A pointer to the callback function. + * @param eventMask A mask selecting events to be notified. + * + * @retval PMIC_SUCCESS If the callback was successfully registered. + * @retval PMIC_PARAMETER_ERROR If the handle or the eventMask is invalid. + */ +PMIC_STATUS pmic_convity_set_callback(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_CALLBACK func, + const PMIC_CONVITY_EVENTS eventMask); + +/*! + * @brief Deregisters the existing Connectivity callback function. + * + * Deregister the callback function that was previously registered by calling + * pmic_convity_set_callback(). + * + * @param handle Device handle from open() call. + * + * @retval PMIC_SUCCESS If the callback was successfully deregistered. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_convity_clear_callback(const PMIC_CONVITY_HANDLE handle); + +/*! + * @brief Get the current Connectivity callback function settings. + * + * Get the current callback function and event mask. + * + * @param handle Device handle from open() call. + * @param func The current callback function. + * @param eventMask The current event selection mask. + * + * @retval PMIC_SUCCESS If the callback information was successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_convity_get_callback(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_CALLBACK * const func, + PMIC_CONVITY_EVENTS * const eventMask); + +/*@}*/ + +/***************************************************************************/ + +/*! + * @name USB and USB On-The-Go APIs + * USB Connectivity mode-specific configuration and setup functions. + */ +/*@{*/ + +/*! + * @brief Set the USB transceiver's operating speed. + * + * Set the USB transceiver speed. + * + * @param handle Device handle from open() call. + * @param speed The desired USB transceiver speed. + * + * @retval PMIC_SUCCESS If the transceiver speed was successfully + * set. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_NOT_SUPPORTED If the high speed (480 Mbps) mode is + * requested. + */ +PMIC_STATUS pmic_convity_usb_set_speed(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_SPEED speed); + +/*! + * This function enables/disables VUSB and VBUS output. + * This API configures the VUSBEN and VBUSEN bits of USB register + * + * @param handle Device handle from open() call. + * @param out_type true, for VUSB + * false, for VBUS + * @param out if true, output is enabled + * if false, output is disabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ + +PMIC_STATUS pmic_convity_set_output(const PMIC_CONVITY_HANDLE handle, + bool out_type, bool out); + +/*! + * @brief Get the USB transceiver's operating speed. + * + * Get the USB transceiver speed. + * + * @param handle Device handle from open() call. + * @param speed The current USB transceiver speed. + * @param mode The current USB transceiver mode. + * + * @retval PMIC_SUCCESS If the transceiver speed was successfully + * set. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * obtained + */ +PMIC_STATUS pmic_convity_usb_get_speed(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_SPEED * const speed, + PMIC_CONVITY_USB_MODE * const mode); + +/*! + * @brief Set the USB transceiver's power supply configuration. + * + * Set the USB transceiver's power supply configuration. + * + * @param handle Device handle from open() call. + * @param pwrin USB transceiver regulator input power source. + * @param pwrout USB transceiver regulator output power level. + * + * @retval PMIC_SUCCESS If the USB transceiver's power supply + * configuration was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_NOT_SUPPORTED If the PMIC hardware does not support + * the desired configuration. + */ +PMIC_STATUS pmic_convity_usb_set_power_source(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_POWER_IN + pwrin, + const PMIC_CONVITY_USB_POWER_OUT + pwrout); + +/*! + * @brief Get the USB transceiver's power supply configuration. + * + * Get the USB transceiver's current power supply configuration. + * + * @param handle Device handle from open() call. + * @param pwrin USB transceiver regulator input power source + * @param pwrout USB transceiver regulator output power level + * + * @retval PMIC_SUCCESS If the USB transceiver's power supply + * configuration was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_convity_usb_get_power_source(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_POWER_IN * + const pwrin, + PMIC_CONVITY_USB_POWER_OUT * + const pwrout); + +/*! + * @brief Set the current USB transceiver operating mode. + * + * Set the USB transceiver's operating mode. + * + * @param handle Device handle from open() call. + * @param mode Desired operating mode. + * + * @retval PMIC_SUCCESS If the USB transceiver's operating mode + * was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_NOT_SUPPORTED If the desired USB transceiver mode is + * not supported by the PMIC hardware. + */ +PMIC_STATUS pmic_convity_usb_set_xcvr(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_TRANSCEIVER_MODE + mode); + +/*! + * @brief Get the current USB transceiver operating mode. + * + * Get the USB transceiver's current operating mode. + * + * @param handle Device handle from open() call. + * @param mode Current operating mode. + * + * @retval PMIC_SUCCESS If the USB transceiver's operating mode + * was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_convity_usb_get_xcvr(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_TRANSCEIVER_MODE * + const mode); + +/*! + * @brief Set the current USB On-The-Go data line pulse duration (ms). + * + * Set the Data Line Pulse duration (in milliseconds) for the USB OTG + * Session Request Protocol. + * + * Note that for mc13783 the duration is fixed at 7.5 ms and calling this + * function will simply return PMIC_NOT_SUPPORTED. + * + * @param handle Device handle from open() call. + * @param duration The data line pulse duration (ms). + * + * @retval PMIC_SUCCESS If the pulse duration was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or the data line pulse + * duration is invalid. + * @retval PMIC_NOT_SUPPORTED If the desired data line pulse duration + * is not supported by the PMIC hardware. + */ +PMIC_STATUS pmic_convity_usb_otg_set_dlp_duration(const PMIC_CONVITY_HANDLE + handle, + const unsigned int duration); + +/*! + * @brief Get the current USB On-The-Go data line pulse duration (ms). + * + * Get the current Data Line Pulse duration (in milliseconds) for the USB + * OTG Session Request Protocol. + * + * Note that the Data Line Pulse duration is fixed at 7.5 ms for the mc13783 + * PMIC. Therefore, calling this function while using the mc13783 PMIC will + * simply return PMIC_NOT_SUPPORTED. + * + * @param handle Device handle from open() call. + * @param duration The data line pulse duration (ms). + * + * @retval PMIC_SUCCESS If the pulse duration was successfully + * obtained. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_NOT_SUPPORTED If called using the mc13783 PMIC. + */ +PMIC_STATUS pmic_convity_usb_otg_get_dlp_duration(const PMIC_CONVITY_HANDLE + handle, + unsigned int *const duration); + +/*! + * @brief Start the USB OTG Host Negotiation Protocol (HNP) process. + * + * This function must be called during the start of the HNP process to + * properly reconfigure the pull-up resistor on the D+ line for both + * the USB A and B devices. + * + * @param handle device handle from open() call + * @param deviceType the USB device type (either A or B) + * + * @return PMIC_SUCCESS if the HNP was successfully started + */ +PMIC_STATUS pmic_convity_usb_otg_begin_hnp(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_DEVICE_TYPE + deviceType); + +/*! + * @brief Complete the USB OTG Host Negotiation Protocol (HNP) process. + * + * This function must be called during the end of the HNP process to + * properly reconfigure the pull-up resistor on the D+ line for both + * the USB A and B devices. + * + * @param handle device handle from open() call + * @param deviceType the USB device type (either A or B) + * + * @return PMIC_SUCCESS if the HNP was successfully ended + */ +PMIC_STATUS pmic_convity_usb_otg_end_hnp(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_DEVICE_TYPE + deviceType); + +/*! + * @brief Set the current USB On-The-Go configuration. + * + * Set the USB On-The-Go (OTG) configuration. Multiple configuration settings + * may be OR'd together in a single call. However, selecting conflicting + * settings (e.g., multiple VBUS current limits) will result in undefined + * behavior. + * + * @param handle Device handle from open() call. + * @param cfg Desired USB OTG configuration. + * + * @retval PMIC_SUCCESS If the OTG configuration was successfully + * set. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_NOT_SUPPORTED If the desired USB OTG configuration is + * not supported by the PMIC hardware. + */ +PMIC_STATUS pmic_convity_usb_otg_set_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_OTG_CONFIG + cfg); + +/*! + * @brief Clear the current USB On-The-Go configuration. + * + * Clears the USB On-The-Go (OTG) configuration. Multiple configuration settings + * may be OR'd together in a single call. However, selecting conflicting + * settings (e.g., multiple VBUS current limits) will result in undefined + * behavior. + * + * @param handle Device handle from open() call. + * @param cfg USB OTG configuration settings to be cleared. + * + * @retval PMIC_SUCCESS If the OTG configuration was successfully + * cleared. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_NOT_SUPPORTED If the desired USB OTG configuration is + * not supported by the PMIC hardware. + */ +PMIC_STATUS pmic_convity_usb_otg_clear_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_OTG_CONFIG + cfg); + +/*! + * @brief Get the current USB On-The-Go configuration. + * + * Get the current USB On-The-Go (OTG) configuration. + * + * @param handle Device handle from open() call. + * @param cfg The current USB OTG configuration. + * + * @retval PMIC_SUCCESS If the OTG configuration was successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_convity_usb_otg_get_config(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_OTG_CONFIG * + const cfg); + +/*@}*/ + +/***************************************************************************/ + +/*! + * @name RS-232 APIs + * RS-232 Connectivity mode-specific configuration and setup functions. + */ +/*@{*/ + +/*! + * @brief Set the current RS-232 operating configuration. + * + * Set the connectivity interface to the selected RS-232 operating mode. + * Note that the RS-232 operating mode will be automatically overridden + * if the USB_EN is asserted at any time (e.g., when a USB device is + * attached). + * + * @param handle Device handle from open() call. + * @param cfgInternal RS-232 transceiver internal connections. + * @param cfgExternal RS-232 transceiver external connections. + * + * @retval PMIC_SUCCESS If the requested RS-232 mode was set. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_NOT_SUPPORTED If the desired RS-232 configuration is + * not supported by the PMIC hardware. + */ +PMIC_STATUS pmic_convity_rs232_set_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_RS232_INTERNAL + cfgInternal, + const PMIC_CONVITY_RS232_EXTERNAL + cfgExternal); + +/*! + * @brief Get the current RS-232 operating configuration. + * + * Get the connectivity interface's current RS-232 operating mode. + * + * @param handle Device handle from open() call. + * @param cfgInternal RS-232 transceiver internal connections. + * @param cfgExternal RS-232 transceiver external connections. + * + * @retval PMIC_SUCCESS If the requested RS-232 mode was retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_convity_rs232_get_config(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_RS232_INTERNAL * + const cfgInternal, + PMIC_CONVITY_RS232_EXTERNAL * + const cfgExternal); + +/***************************************************************************/ + +/*@}*/ + +/*! + * @name CE-936 APIs + * CE-936 Connectivity mode-specific configuration and setup functions. + */ +/*@{*/ + +/*! + * @brief Send a signal to exit CEA-936 mode. + * + * Signal the attached device to exit the current CEA-936 operating mode. + * Returns an error if the current operating mode is not CEA-936. + * + * @param handle Device handle from open() call. + * @param signal Type of exit signal to be sent. + * + * @retval PMIC_SUCCESS If the CEA-936 exit mode signal was sent. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_NOT_SUPPORTED If the desired CEA-936 exit mode signal + * is not supported by the PMIC hardware. + */ +PMIC_STATUS pmic_convity_cea936_exit_signal(const PMIC_CONVITY_HANDLE handle, + const + PMIC_CONVITY_CEA936_EXIT_SIGNAL + signal); + +/*@}*/ + +#endif /* __ASM_ARCH_MXC_PMIC_CONVITY_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/pmic_power.h b/arch/arm/plat-mxc/include/mach/pmic_power.h new file mode 100644 index 000000000000..60f23ab9c888 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/pmic_power.h @@ -0,0 +1,1358 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ +#ifndef __ASM_ARCH_MXC_PMIC_POWER_H__ +#define __ASM_ARCH_MXC_PMIC_POWER_H__ + +/*! + * @defgroup PMIC_POWER PMIC Power Driver + * @ingroup PMIC_DRVRS + */ + +/*! + * @file arch-mxc/pmic_power.h + * @brief This is the header of PMIC power driver. + * + * @ingroup PMIC_POWER + */ + +#include <linux/ioctl.h> +#include <linux/pmic_status.h> +#include <linux/pmic_external.h> + +/*! + * @name IOCTL user space interface + */ +/*! @{ */ + +/*! + * Turn on a regulator. + */ +#define PMIC_REGULATOR_ON _IOWR('p', 0xf0, int) + +/*! + * Turn off a regulator. + */ +#define PMIC_REGULATOR_OFF _IOWR('p', 0xf1, int) + +/*! + * Set regulator configuration. + */ +#define PMIC_REGULATOR_SET_CONFIG _IOWR('p', 0xf2, int) + +/*! + * Get regulator configuration. + */ +#define PMIC_REGULATOR_GET_CONFIG _IOWR('p', 0xf3, int) + +/*! + * Miscellaneous Power Test. + */ +#define PMIC_POWER_CHECK_MISC _IOWR('p', 0xf4, int) + +/*! @} */ + +/*! + * This enumeration define all power interrupts + */ +typedef enum { + /*! + * BP turn on threshold detection + */ + PWR_IT_BPONI = 0, + /*! + * End of life / low battery detect + */ + PWR_IT_LOBATLI, + /*! + * Low battery warning + */ + PWR_IT_LOBATHI, + /*! + * ON1B event + */ + PWR_IT_ONOFD1I, + /*! + * ON2B event + */ + PWR_IT_ONOFD2I, + /*! + * ON3B event + */ + PWR_IT_ONOFD3I, + /*! + * System reset + */ + PWR_IT_SYSRSTI, + /*! + * Power ready + */ + PWR_IT_PWRRDYI, + /*! + * Power cut event + */ + PWR_IT_PCI, + /*! + * Warm start event + */ + PWR_IT_WARMI, + /*! + * Memory hold event + */ +} t_pwr_int; + +/*! + * VHOLD regulator output voltage setting. + */ +typedef enum { + VH_1_875V, /*!< 1.875V */ + VH_2_5V, /*!< 2.5V */ + VH_1_55V, /*!< 1.55V */ + VH_PASSTHROUGH, /*!< Pass-through mode */ +} t_vhold_voltage; + +/*! + * PMIC power control configuration. + */ + +typedef struct { + bool pc_enable; /*!< Power cut enable */ + unsigned char pc_timer; /*!< Power cut timer value */ + bool pc_count_enable; /*!< Power cut counter enable, + If TURE, Power cuts are disabled + when pc_count > pc_max_count; + If FALSE, Power cuts are not + disabled when + pc_count > pc_max_count */ + unsigned char pc_count; /*!< Power cut count */ + unsigned char pc_max_count; /*!< Power cut maximum count */ + bool warm_enable; /*!< User Off state enable */ + bool user_off_pc; /*!< Automatic transition to user off + during power cut */ + bool clk_32k_enable; /*!< 32 kHz output buffer enable + during memory hold */ + bool clk_32k_user_off; /*!< Keeps the CLK32KMCU active during + user off power cut modes */ + bool en_vbkup1; /*!< enable VBKUP1 regulator */ + bool auto_en_vbkup1; /*!< automatically enable VBKUP1 + regulator in the memory hold + and user of modes */ + t_vhold_voltage vhold_voltage; /*!< output voltage for VBKUP1 */ + bool en_vbkup2; /*!< enable VBKUP2 regulator */ + bool auto_en_vbkup2; /*!< automatically enable VBKUP2 + regulator in the memory hold + and user of modes */ + t_vhold_voltage vhold_voltage2; /*!< output voltage for VBKUP2 */ + unsigned char mem_timer; /*!< duration of the memory hold + timer */ + bool mem_allon; /*!< memory hold timer infinity mode, + If TRUE, the memory hold timer + will be set to infinity and + the mem_timer filed will be + ignored */ +} t_pc_config; + +/*! + * brief PMIC regulators. + */ + +typedef enum { + SW_SW1A = 0, /*!< SW1A or SW1 */ + SW_SW1B, /*!< SW1B */ + SW_SW2A, /*!< SW2A or SW2 */ + SW_SW2B, /*!< SW2B */ + SW_SW3, /*!< SW3 */ + SW_PLL, /*!< PLL */ + REGU_VAUDIO, /*!< VAUDIO */ + REGU_VIOHI, /*!< VIOHI */ + REGU_VIOLO, /*!< VIOLO */ + REGU_VDIG, /*!< VDIG */ + REGU_VGEN, /*!< VGEN */ + REGU_VRFDIG, /*!< VRFDIG */ + REGU_VRFREF, /*!< VRFREF */ + REGU_VRFCP, /*!< VRFCP */ + REGU_VSIM, /*!< VSIM */ + REGU_VESIM, /*!< VESIM */ + REGU_VCAM, /*!< VCAM */ + REGU_VRFBG, /*!< VRFBG */ + REGU_VVIB, /*!< VVIB */ + REGU_VRF1, /*!< VRF1 */ + REGU_VRF2, /*!< VRF2 */ + REGU_VMMC1, /*!< VMMC1 or VMMC */ + REGU_VMMC2, /*!< VMMC2 */ + REGU_GPO1, /*!< GPIO1 */ + REGU_GPO2, /*!< GPO2 */ + REGU_GPO3, /*!< GPO3 */ + REGU_GPO4, /*!< GPO4 */ + REGU_V1, /*!< V1 */ + REGU_V2, /*!< V2 */ + REGU_V3, /*!< V3 */ + REGU_V4, /*!< V4 */ +} t_pmic_regulator; + +/*! + * @enum t_pmic_regulator_voltage_sw1 + * @brief PMIC Switch mode regulator SW1 output voltages. + */ + +typedef enum { + SW1_1V = 0, /*!< 1.0 V */ + SW1_1_1V, /*!< 1.1 V */ + SW1_1_2V, /*!< 1.2 V */ + SW1_1_3V, /*!< 1.3 V */ + SW1_1_4V, /*!< 1.4 V */ + SW1_1_55V, /*!< 1.55 V */ + SW1_1_625V, /*!< 1.625 V */ + SW1_1_875V, /*!< 1.875 V */ +} t_pmic_regulator_voltage_sw1; + +/*! + * @enum t_pmic_regulator_voltage_sw1a + * @brief PMIC regulator SW1A output voltage. + */ +typedef enum { + SW1A_0_9V = 0, /*!< 0.900 V */ + SW1A_0_925V, /*!< 0.925 V */ + SW1A_0_95V, /*!< 0.950 V */ + SW1A_0_975V, /*!< 0.975 V */ + SW1A_1V, /*!< 1.000 V */ + SW1A_1_025V, /*!< 1.025 V */ + SW1A_1_05V, /*!< 1.050 V */ + SW1A_1_075V, /*!< 1.075 V */ + SW1A_1_1V, /*!< 1.100 V */ + SW1A_1_125V, /*!< 1.125 V */ + SW1A_1_15V, /*!< 1.150 V */ + SW1A_1_175V, /*!< 1.175 V */ + SW1A_1_2V, /*!< 1.200 V */ + SW1A_1_225V, /*!< 1.225 V */ + SW1A_1_25V, /*!< 1.250 V */ + SW1A_1_275V, /*!< 1.275 V */ + SW1A_1_3V, /*!< 1.300 V */ + SW1A_1_325V, /*!< 1.325 V */ + SW1A_1_35V, /*!< 1.350 V */ + SW1A_1_375V, /*!< 1.375 V */ + SW1A_1_4V, /*!< 1.400 V */ + SW1A_1_425V, /*!< 1.425 V */ + SW1A_1_45V, /*!< 1.450 V */ + SW1A_1_475V, /*!< 1.475 V */ + SW1A_1_5V, /*!< 1.500 V */ + SW1A_1_525V, /*!< 1.525 V */ + SW1A_1_55V, /*!< 1.550 V */ + SW1A_1_575V, /*!< 1.575 V */ + SW1A_1_6V, /*!< 1.600 V */ + SW1A_1_625V, /*!< 1.625 V */ + SW1A_1_65V, /*!< 1.650 V */ + SW1A_1_675V, /*!< 1.675 V */ + SW1A_1_7V, /*!< 1.700 V */ + SW1A_1_8V = 36, /*!< 1.800 V */ + SW1A_1_85V = 40, /*!< 1.850 V */ + SW1A_2V = 44, /*!< 2_000 V */ + SW1A_2_1V = 48, /*!< 2_100 V */ + SW1A_2_2V = 52, /*!< 2_200 V */ +} t_pmic_regulator_voltage_sw1a; + +/*! + * @enum t_pmic_regulator_voltage_sw1b + * @brief PMIC regulator SW1B output voltage. + */ +typedef enum { + SW1B_0_9V = 0, /*!< 0.900 V */ + SW1B_0_925V, /*!< 0.925 V */ + SW1B_0_95V, /*!< 0.950 V */ + SW1B_0_975V, /*!< 0.975 V */ + SW1B_1V, /*!< 1.000 V */ + SW1B_1_025V, /*!< 1.025 V */ + SW1B_1_05V, /*!< 1.050 V */ + SW1B_1_075V, /*!< 1.075 V */ + SW1B_1_1V, /*!< 1.100 V */ + SW1B_1_125V, /*!< 1.125 V */ + SW1B_1_15V, /*!< 1.150 V */ + SW1B_1_175V, /*!< 1.175 V */ + SW1B_1_2V, /*!< 1.200 V */ + SW1B_1_225V, /*!< 1.225 V */ + SW1B_1_25V, /*!< 1.250 V */ + SW1B_1_275V, /*!< 1.275 V */ + SW1B_1_3V, /*!< 1.300 V */ + SW1B_1_325V, /*!< 1.325 V */ + SW1B_1_35V, /*!< 1.350 V */ + SW1B_1_375V, /*!< 1.375 V */ + SW1B_1_4V, /*!< 1.400 V */ + SW1B_1_425V, /*!< 1.425 V */ + SW1B_1_45V, /*!< 1.450 V */ + SW1B_1_475V, /*!< 1.475 V */ + SW1B_1_5V, /*!< 1.500 V */ + SW1B_1_525V, /*!< 1.525 V */ + SW1B_1_55V, /*!< 1.550 V */ + SW1B_1_575V, /*!< 1.575 V */ + SW1B_1_6V, /*!< 1.600 V */ + SW1B_1_625V, /*!< 1.625 V */ + SW1B_1_65V, /*!< 1.650 V */ + SW1B_1_675V, /*!< 1.675 V */ + SW1B_1_7V, /*!< 1.700 V */ + SW1B_1_8V = 36, /*!< 1.800 V */ + SW1B_1_85V = 40, /*!< 1.850 V */ + SW1B_2V = 44, /*!< 2_000 V */ + SW1B_2_1V = 48, /*!< 2_100 V */ + SW1B_2_2V = 52, /*!< 2_200 V */ +} t_pmic_regulator_voltage_sw1b; + +/*! + * @enum t_pmic_regulator_voltage_sw2 + * @brief PMIC Switch mode regulator SW2 output voltages. + */ +typedef enum { + SW2_1V = 0, /*!< 1.0 V */ + SW2_1_1V, /*!< 1.1 V */ + SW2_1_2V, /*!< 1.2 V */ + SW2_1_3V, /*!< 1.3 V */ + SW2_1_4V, /*!< 1.4 V */ + SW2_1_55V, /*!< 1.55 V */ + SW2_1_625V, /*!< 1.625 V */ + SW2_1_875V, /*!< 1.875 V */ +} t_pmic_regulator_voltage_sw2; + +/*! + * @enum t_pmic_regulator_voltage_sw2a + * @brief PMIC regulator SW2A output voltage. + */ +typedef enum { + SW2A_0_9V = 0, /*!< 0.900 V */ + SW2A_0_925V, /*!< 0.925 V */ + SW2A_0_95V, /*!< 0.950 V */ + SW2A_0_975V, /*!< 0.975 V */ + SW2A_1V, /*!< 1.000 V */ + SW2A_1_025V, /*!< 1.025 V */ + SW2A_1_05V, /*!< 1.050 V */ + SW2A_1_075V, /*!< 1.075 V */ + SW2A_1_1V, /*!< 1.100 V */ + SW2A_1_125V, /*!< 1.125 V */ + SW2A_1_15V, /*!< 1.150 V */ + SW2A_1_175V, /*!< 1.175 V */ + SW2A_1_2V, /*!< 1.200 V */ + SW2A_1_225V, /*!< 1.225 V */ + SW2A_1_25V, /*!< 1.250 V */ + SW2A_1_275V, /*!< 1.275 V */ + SW2A_1_3V, /*!< 1.300 V */ + SW2A_1_325V, /*!< 1.325 V */ + SW2A_1_35V, /*!< 1.350 V */ + SW2A_1_375V, /*!< 1.375 V */ + SW2A_1_4V, /*!< 1.400 V */ + SW2A_1_425V, /*!< 1.425 V */ + SW2A_1_45V, /*!< 1.450 V */ + SW2A_1_475V, /*!< 1.475 V */ + SW2A_1_5V, /*!< 1.500 V */ + SW2A_1_525V, /*!< 1.525 V */ + SW2A_1_55V, /*!< 1.550 V */ + SW2A_1_575V, /*!< 1.575 V */ + SW2A_1_6V, /*!< 1.600 V */ + SW2A_1_625V, /*!< 1.625 V */ + SW2A_1_65V, /*!< 1.650 V */ + SW2A_1_675V, /*!< 1.675 V */ + SW2A_1_7V, /*!< 1.700 V */ + SW2A_1_8V = 36, /*!< 1.800 V */ + SW2A_1_9V = 40, /*!< 1.900 V */ + SW2A_2V = 44, /*!< 2_000 V */ + SW2A_2_1V = 48, /*!< 2_100 V */ + SW2A_2_2V = 52, /*!< 2_200 V */ +} t_pmic_regulator_voltage_sw2a; + +/*! + * @enum t_pmic_regulator_voltage_sw2b + * @brief PMIC regulator SW2B output voltage. + */ +typedef enum { + SW2B_0_9V = 0, /*!< 0.900 V */ + SW2B_0_925V, /*!< 0.925 V */ + SW2B_0_95V, /*!< 0.950 V */ + SW2B_0_975V, /*!< 0.975 V */ + SW2B_1V, /*!< 1.000 V */ + SW2B_1_025V, /*!< 1.025 V */ + SW2B_1_05V, /*!< 1.050 V */ + SW2B_1_075V, /*!< 1.075 V */ + SW2B_1_1V, /*!< 1.100 V */ + SW2B_1_125V, /*!< 1.125 V */ + SW2B_1_15V, /*!< 1.150 V */ + SW2B_1_175V, /*!< 1.175 V */ + SW2B_1_2V, /*!< 1.200 V */ + SW2B_1_225V, /*!< 1.225 V */ + SW2B_1_25V, /*!< 1.250 V */ + SW2B_1_275V, /*!< 1.275 V */ + SW2B_1_3V, /*!< 1.300 V */ + SW2B_1_325V, /*!< 1.325 V */ + SW2B_1_35V, /*!< 1.350 V */ + SW2B_1_375V, /*!< 1.375 V */ + SW2B_1_4V, /*!< 1.400 V */ + SW2B_1_425V, /*!< 1.425 V */ + SW2B_1_45V, /*!< 1.450 V */ + SW2B_1_475V, /*!< 1.475 V */ + SW2B_1_5V, /*!< 1.500 V */ + SW2B_1_525V, /*!< 1.525 V */ + SW2B_1_55V, /*!< 1.550 V */ + SW2B_1_575V, /*!< 1.575 V */ + SW2B_1_6V, /*!< 1.600 V */ + SW2B_1_625V, /*!< 1.625 V */ + SW2B_1_65V, /*!< 1.650 V */ + SW2B_1_675V, /*!< 1.675 V */ + SW2B_1_7V, /*!< 1.700 V */ + SW2B_1_8V = 36, /*!< 1.800 V */ + SW2B_1_9V = 40, /*!< 1.900 V */ + SW2B_2V = 44, /*!< 2_000 V */ + SW2B_2_1V = 48, /*!< 2_100 V */ + SW2B_2_2V = 52, /*!< 2_200 V */ +} t_pmic_regulator_voltage_sw2b; + +/*! + * @enum t_pmic_regulator_voltage_sw3 + * @brief PMIC Switch mode regulator SW3 output voltages. + */ +typedef enum { + SW3_5V = 0, /*!< 5.0 V */ + SW3_5_1V = 0, /*!< 5.1 V */ + SW3_5_6V, /*!< 5.6 V */ +} t_pmic_regulator_voltage_sw3; + +/*! + * @enum t_switcher_factor + * @brief PLL multiplication factor + */ +typedef enum { + FACTOR_28 = 0, /*!< 917 504 kHz */ + FACTOR_29, /*!< 950 272 kHz */ + FACTOR_30, /*!< 983 040 kHz */ + FACTOR_31, /*!< 1 015 808 kHz */ + FACTOR_32, /*!< 1 048 576 kHz */ + FACTOR_33, /*!< 1 081 344 kHz */ + FACTOR_34, /*!< 1 114 112 kHz */ + FACTOR_35, /*!< 1 146 880 kHz */ +} t_switcher_factor; + +/*! + * @enum t_pmic_regulator_voltage_violo + * @brief PMIC regulator VIOLO output voltage. + */ +typedef enum { + VIOLO_1_2V = 0, /*!< 1.2 V */ + VIOLO_1_3V, /*!< 1.3 V */ + VIOLO_1_5V, /*!< 1.5 V */ + VIOLO_1_8V, /*!< 1.8 V */ +} t_pmic_regulator_voltage_violo; + +/*! + * @enum t_pmic_regulator_voltage_vdig + * @brief PMIC regulator VDIG output voltage. + */ +typedef enum { + VDIG_1_2V = 0, /*!< 1.2 V */ + VDIG_1_3V, /*!< 1.3 V */ + VDIG_1_5V, /*!< 1.5 V */ + VDIG_1_8V, /*!< 1.8 V */ +} t_pmic_regulator_voltage_vdig; + +/*! + * @enum t_pmic_regulator_voltage_vgen + * @brief PMIC regulator VGEN output voltage. + */ +typedef enum { + VGEN_1_2V = 0, /*!< 1.2 V */ + VENG_1_3V, /*!< 1.3 V */ + VGEN_1_5V, /*!< 1.5 V */ + VGEN_1_8V, /*!< 1.8 V */ + VGEN_1_1V, /*!< 1.1 V */ + VGEN_2V, /*!< 2 V */ + VGEN_2_775V, /*!< 2.775 V */ + VGEN_2_4V, /*!< 2.4 V */ +} t_pmic_regulator_voltage_vgen; + +/*! + * @enum t_pmic_regulator_voltage_vrfdig + * @brief PMIC regulator VRFDIG output voltage. + */ +typedef enum { + VRFDIG_1_2V = 0, /*!< 1.2 V */ + VRFDIG_1_5V, /*!< 1.5 V */ + VRFDIG_1_8V, /*!< 1.8 V */ + VRFDIG_1_875V, /*!< 1.875 V */ +} t_pmic_regulator_voltage_vrfdig; + +/*! + * @enum t_pmic_regulator_voltage_vrfref + * @brief PMIC regulator VRFREF output voltage. + */ +typedef enum { + VRFREF_2_475V = 0, /*!< 2.475 V */ + VRFREF_2_6V, /*!< 2.600 V */ + VRFREF_2_7V, /*!< 2.700 V */ + VRFREF_2_775V, /*!< 2.775 V */ +} t_pmic_regulator_voltage_vrfref; + +/*! + * @enum t_pmic_regulator_voltage_vrfcp + * @brief PMIC regulator VRFCP output voltage. + */ +typedef enum { + VRFCP_2_7V = 0, /*!< 2.700 V */ + VRFCP_2_775V, /*!< 2.775 V */ +} t_pmic_regulator_voltage_vrfcp; + +/*! + * @enum t_pmic_regulator_voltage_vsim + * @brief PMIC linear regulator VSIM output voltage. + */ +typedef enum { + VSIM_1_8V = 0, /*!< 1.8 V */ + VSIM_2_9V, /*!< 2.90 V */ + VSIM_3V = 1, /*!< 3 V */ +} t_pmic_regulator_voltage_vsim; + +/*! + * @enum t_pmic_regulator_voltage_vesim + * @brief PMIC regulator VESIM output voltage. + */ +typedef enum { + VESIM_1_8V = 0, /*!< 1.80 V */ + VESIM_2_9V, /*!< 2.90 V */ +} t_pmic_regulator_voltage_vesim; + +/*! + * @enum t_pmic_regulator_voltage_vcam + * @brief PMIC regulator VCAM output voltage. + */ +typedef enum { + VCAM_1_5V = 0, /*!< 1.50 V */ + VCAM_1_8V, /*!< 1.80 V */ + VCAM_2_5V, /*!< 2.50 V */ + VCAM_2_55V, /*!< 2.55 V */ + VCAM_2_6V, /*!< 2.60 V */ + VCAM_2_75V, /*!< 2.75 V */ + VCAM_2_8V, /*!< 2.80 V */ + VCAM_3V, /*!< 3.00 V */ +} t_pmic_regulator_voltage_vcam; + +/*! + * @enum t_pmic_regulator_voltage_vvib + * @brief PMIC linear regulator V_VIB output voltage. + */ +typedef enum { + VVIB_1_3V = 0, /*!< 1.30 V */ + VVIB_1_8V, /*!< 1.80 V */ + VVIB_2V, /*!< 2 V */ + VVIB_3V, /*!< 3 V */ +} t_pmic_regulator_voltage_vvib; + +/*! + * @enum t_pmic_regulator_voltage_vrf1 + * @brief PMIC regulator VRF1 output voltage. + */ +typedef enum { + VRF1_1_5V = 0, /*!< 1.500 V */ + VRF1_1_875V, /*!< 1.875 V */ + VRF1_2_7V, /*!< 2.700 V */ + VRF1_2_775V, /*!< 2.775 V */ +} t_pmic_regulator_voltage_vrf1; + +/*! + * @enum t_pmic_regulator_voltage_vrf2 + * @brief PMIC regulator VRF2 output voltage. + */ +typedef enum { + VRF2_1_5V = 0, /*!< 1.500 V */ + VRF2_1_875V, /*!< 1.875 V */ + VRF2_2_7V, /*!< 2.700 V */ + VRF2_2_775V, /*!< 2.775 V */ +} t_pmic_regulator_voltage_vrf2; + +/*! + * @enum t_pmic_regulator_voltage_vmmc + * @brief PMIC linear regulator VMMC output voltage. + */ +typedef enum { + VMMC_OFF = 0, /*!< Output off */ + VMMC_1_6V, /*!< 1.6 V */ + VMMC_1_8V, /*!< 1.8 V */ + VMMC_2V, /*!< 2 V */ + VMMC_2_2V, /*!< 2.2 V */ + VMMC_2_4V, /*!< 2.4 V */ + VMMC_2_6V, /*!< 2.6 V */ + VMMC_2_8V, /*!< 2.8 V */ + VMMC_3V, /*!< 3 V */ + VMMC_3_2V, /*!< 3.2 V */ + VMMC_3_3V, /*!< 3.3 V */ + VMMC_3_4V, /*!< 3.4 V */ +} t_pmic_regulator_voltage_vmmc; + +/*! + * @enum t_pmic_regulator_voltage_vmmc1 + * @brief PMIC regulator VMMC1 output voltage. + */ +typedef enum { + VMMC1_1_6V = 0, /*!< 1.60 V */ + VMMC1_1_8V, /*!< 1.80 V */ + VMMC1_2V, /*!< 2.00 V */ + VMMC1_2_6V, /*!< 2.60 V */ + VMMC1_2_7V, /*!< 2.70 V */ + VMMC1_2_8V, /*!< 2.80 V */ + VMMC1_2_9V, /*!< 2.90 V */ + VMMC1_3V, /*!< 3.00 V */ +} t_pmic_regulator_voltage_vmmc1; + +/*! + * @enum t_pmic_regulator_voltage_vmmc2 + * @brief PMIC regulator VMMC2 output voltage. + */ +typedef enum { + VMMC2_1_6V = 0, /*!< 1.60 V */ + VMMC2_1_8V, /*!< 1.80 V */ + VMMC2_2V, /*!< 2.00 V */ + VMMC2_2_6V, /*!< 2.60 V */ + VMMC2_2_7V, /*!< 2.70 V */ + VMMC2_2_8V, /*!< 2.80 V */ + VMMC2_2_9V, /*!< 2.90 V */ + VMMC2_3V, /*!< 3.00 V */ +} t_pmic_regulator_voltage_vmmc2; + +/*! + * @enum t_pmic_regulator_voltage_v1 + * @brief PMIC linear regulator V1 output voltages. + */ +typedef enum { + V1_2_775V = 0, /*!< 2.775 V */ + V1_1_2V, /*!< 1.2 V */ + V1_1_3V, /*!< 1.3 V */ + V1_1_4V, /*!< 1.4 V */ + V1_1_55V, /*!< 1.55 V */ + V1_1_75V, /*!< 1.75 V */ + V1_1_875V, /*!< 1.875 V */ + V1_2_475V, /*!< 2.475 V */ +} t_pmic_regulator_voltage_v1; + +/*! + * @enum t_pmic_regulator_voltage_v2 + * @brief PMIC linear regulator V2 output voltage, V2 has fixed + * output voltage 2.775 volts. + */ +typedef enum { + V2_2_775V = 0, /*!< 2.775 V */ +} t_pmic_regulator_voltage_v2; + +/*! + * @enum t_pmic_regulator_voltage_v3 + * @brief PMIC linear regulator V3 output voltage. + */ +typedef enum { + V3_1_875V = 0, /*!< 1.875 V */ + V3_2_775V, /*!< 2.775 V */ +} t_pmic_regulator_voltage_v3; + +/*! + * @enum t_pmic_regulator_voltage_v4 + * @brief PMIC linear regulator V4 output voltage, V4 has fixed + * output voltage 2.775 volts. + */ +typedef enum { + V4_2_775V = 0, /*!< 2.775 V */ +} t_pmic_regulator_voltage_v4; + +/*! + * @union t_regulator_voltage + * @brief PMIC regulator output voltages. + */ +typedef union { + t_pmic_regulator_voltage_sw1 sw1; /*!< SW1 voltage */ + t_pmic_regulator_voltage_sw1a sw1a; /*!< SW1A voltage */ + t_pmic_regulator_voltage_sw1b sw1b; /*!< SW1B voltage */ + t_pmic_regulator_voltage_sw2 sw2; /*!< SW2 voltage */ + t_pmic_regulator_voltage_sw2a sw2a; /*!< SW2A voltage */ + t_pmic_regulator_voltage_sw2b sw2b; /*!< SW2B voltage */ + t_pmic_regulator_voltage_sw3 sw3; /*!< SW3 voltage */ + t_pmic_regulator_voltage_violo violo; /*!< VIOLO voltage */ + t_pmic_regulator_voltage_vdig vdig; /*!< VDIG voltage */ + t_pmic_regulator_voltage_vgen vgen; /*!< VGEN voltage */ + t_pmic_regulator_voltage_vrfdig vrfdig; /*!< VRFDIG voltage */ + t_pmic_regulator_voltage_vrfref vrfref; /*!< VRFREF voltage */ + t_pmic_regulator_voltage_vrfcp vrfcp; /*!< VRFCP voltage */ + t_pmic_regulator_voltage_vsim vsim; /*!< VSIM voltage */ + t_pmic_regulator_voltage_vesim vesim; /*!< VESIM voltage */ + t_pmic_regulator_voltage_vcam vcam; /*!< VCAM voltage */ + t_pmic_regulator_voltage_vvib vvib; /*!< VVIB voltage */ + t_pmic_regulator_voltage_vrf1 vrf1; /*!< VRF1 voltage */ + t_pmic_regulator_voltage_vrf2 vrf2; /*!< VRF2 voltage */ + t_pmic_regulator_voltage_vmmc vmmc; /*!< VMMC voltage */ + t_pmic_regulator_voltage_vmmc1 vmmc1; /*!< VMMC1 voltage */ + t_pmic_regulator_voltage_vmmc2 vmmc2; /*!< VMMC2 voltage */ + t_pmic_regulator_voltage_v1 v1; /*!< V1 voltage */ + t_pmic_regulator_voltage_v2 v2; /*!< V2 voltage */ + t_pmic_regulator_voltage_v3 v3; /*!< V3 voltage */ + t_pmic_regulator_voltage_v4 v4; /*!< V4 voltage */ +} t_regulator_voltage; + +/*! + * @enum t_pmic_regulator_sw_mode + * @brief define switch mode regulator mode. + * + * The synchronous rectifier can be disabled (and pulse-skipping enabled) + * to improve low current efficiency. Software should disable synchronous + * rectifier / enable the pulse skipping for average loads less than + * approximately 30 mA, depending on the quiescent current penalty due to + * synchronous mode. + */ +typedef enum { + SYNC_RECT = 0, + NO_PULSE_SKIP, + PULSE_SKIP, + LOW_POWER, +} t_pmic_regulator_sw_mode; + +/*! + * Generic PMIC switch mode regulator mode. + */ +typedef t_pmic_regulator_sw_mode t_regulator_sw_mode; +typedef t_pmic_regulator_sw_mode t_regulator_stby_mode; + +/*! + * @enum t_regulator_lp_mode + * @brief Low power mode control modes. + */ + +typedef enum { + /*! + * Low Power Mode is disabled + */ + LOW_POWER_DISABLED = 0, + /*! + * Low Power Mode is controlled by STANDBY pin and/or LVS pin + */ + LOW_POWER_CTRL_BY_PIN, + /*! + * Set Low Power mode no matter of hardware pins + */ + LOW_POWER_EN, + /*! + * Set Low Power mode and control by STANDBY + */ + LOW_POWER_AND_LOW_POWER_CTRL_BY_PIN, +} t_regulator_lp_mode; + +/*! + * @enum t_switcher_dvs_speed + * @brief DVS speed setting + */ +typedef enum { + /*! + * Transition speed is dictated by the current + * limit and input -output conditions + */ + DICTATED = 0, + /*! + * 25mV step each 4us + */ + DVS_4US, + /*! + * 25mV step each 8us + */ + DVS_8US, + /*! + * 25mV step each 16us + */ + DVS_16US, +} t_switcher_dvs_speed; + +/*! + * @struct t_regulator_config + * @brief regulator configuration. + * + */ + +typedef struct { + /*! + * Switch mode regulator operation mode. This field only applies to + * switch mode regulators. + */ + t_regulator_sw_mode mode; + /*! + * Switch mode stby regulator operation mode. This field only applies + * to switch mode regulators. + */ + t_regulator_stby_mode stby_mode; + /*! + * Regulator output voltage. + */ + t_regulator_voltage voltage; + /*! + * Regulator output voltage in LVS mode. + */ + t_regulator_voltage voltage_lvs; + /*! + * Regulator output voltage in standby mode. + */ + t_regulator_voltage voltage_stby; + /*! + * Regulator low power mode. + */ + t_regulator_lp_mode lp_mode; + /*! + * Switcher dvs speed + */ + t_switcher_dvs_speed dvs_speed; + /*! + * Switcher panic mode + */ + bool panic_mode; + /*! + * Switcher softstart + */ + bool softstart; + /*! + * PLL Multiplication factor + */ + t_switcher_factor factor; +} t_regulator_config; + +/*! + * @struct t_regulator_cfg_param + * @brief regulator configuration structure for IOCTL. + * + */ +typedef struct { + /*! + * Regulator. + */ + t_pmic_regulator regulator; + /*! + * Regulator configuration. + */ + t_regulator_config cfg; +} t_regulator_cfg_param; + +/*! + * This struct list all state reads in Power Up Sense + */ +struct t_p_up_sense { + /*! + * power up sense ictest + */ + bool state_ictest; + /*! + * power up sense clksel + */ + bool state_clksel; + /*! + * power up mode supply 1 + */ + bool state_pums1; + /*! + * power up mode supply 2 + */ + bool state_pums2; + /*! + * power up mode supply 3 + */ + bool state_pums3; + /*! + * power up sense charge mode 0 + */ + bool state_chrgmode0; + /*! + * power up sense charge mode 1 + */ + bool state_chrgmode1; + /*! + * power up sense USB mode + */ + bool state_umod; + /*! + * power up sense boot mode enable for USB/RS232 + */ + bool state_usben; + /*! + * power up sense switcher 1a1b joined + */ + bool state_sw_1a1b_joined; + /*! + * power up sense switcher 1a1b joined + */ + bool state_sw_2a2b_joined; +}; + +/*! + * This enumeration define all On_OFF button + */ +typedef enum { + /*! + * ON1B + */ + BT_ON1B = 0, + /*! + * ON2B + */ + BT_ON2B, + /*! + * ON3B + */ + BT_ON3B, +} t_button; + +#ifdef __KERNEL__ +/* EXPORTED FUNCTIONS */ + +/*! + * This function sets user power off in power control register and thus powers + * off the phone. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +void pmic_power_off(void); + +/*! + * This function sets the power control configuration. + * + * @param pc_config power control configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_set_pc_config(t_pc_config * pc_config); + +/*! + * This function retrives the power control configuration. + * + * @param pc_config pointer to power control configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_pc_config(t_pc_config * pc_config); + +/*! + * This function turns on a regulator. + * + * @param regulator The regulator to be turned on. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_on(t_pmic_regulator regulator); + +/*! + * This function turns off a regulator. + * + * @param regulator The regulator to be turned off. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_off(t_pmic_regulator regulator); + +/*! + * This function sets the regulator output voltage. + * + * @param regulator The regulator to be turned off. + * @param voltage The regulator output voltage. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_voltage(t_pmic_regulator regulator, + t_regulator_voltage voltage); + +/*! + * This function retrieves the regulator output voltage. + * + * @param regulator The regulator to be turned off. + * @param voltage Pointer to regulator output voltage. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_voltage(t_pmic_regulator regulator, + t_regulator_voltage * voltage); + +/*! + * This function sets the DVS voltage + * + * @param regulator The regulator to be configured. + * @param dvs The switch Dynamic Voltage Scaling + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_dvs(t_pmic_regulator regulator, + t_regulator_voltage dvs); + +/*! + * This function gets the DVS voltage + * + * @param regulator The regulator to be handled. + * @param dvs The switch Dynamic Voltage Scaling + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_dvs(t_pmic_regulator regulator, + t_regulator_voltage * dvs); + +/*! + * This function sets the standby voltage + * + * @param regulator The regulator to be configured. + * @param stby The switch standby voltage + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_stby(t_pmic_regulator regulator, + t_regulator_voltage stby); + +/*! + * This function gets the standby voltage + * + * @param regulator The regulator to be handled. + * @param stby The switch standby voltage + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_stby(t_pmic_regulator regulator, + t_regulator_voltage * stby); + +/*! + * This function sets the switchers mode. + * + * @param regulator The regulator to be configured. + * @param mode The switcher mode + * @param stby Switch between main and standby. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_mode(t_pmic_regulator regulator, + t_regulator_sw_mode mode, bool stby); + +/*! + * This function gets the switchers mode. + * + * @param regulator The regulator to be handled. + * @param mode The switcher mode. + * @param stby Switch between main and standby. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_mode(t_pmic_regulator regulator, + t_regulator_sw_mode * mode, bool stby); + +/*! + * This function sets the switch dvs speed + * + * @param regulator The regulator to be configured. + * @param speed The dvs speed. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_dvs_speed(t_pmic_regulator regulator, + t_switcher_dvs_speed speed); + +/*! + * This function gets the switch dvs speed + * + * @param regulator The regulator to be handled. + * @param speed The dvs speed. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_dvs_speed(t_pmic_regulator regulator, + t_switcher_dvs_speed * speed); + +/*! + * This function sets the switch panic mode + * + * @param regulator The regulator to be configured. + * @param panic_mode Enable or disable panic mode + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_panic_mode(t_pmic_regulator regulator, + bool panic_mode); + +/*! + * This function gets the switch panic mode + * + * @param regulator The regulator to be handled + * @param panic_mode Enable or disable panic mode + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_panic_mode(t_pmic_regulator regulator, + bool * panic_mode); + +/*! + * This function sets the switch softstart mode + * + * @param regulator The regulator to be configured. + * @param softstart Enable or disable softstart. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_softstart(t_pmic_regulator regulator, + bool softstart); + +/*! + * This function gets the switch softstart mode + * + * @param regulator The regulator to be handled + * @param softstart Enable or disable softstart. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_softstart(t_pmic_regulator regulator, + bool * softstart); + +/*! + * This function sets the PLL multiplication factor + * + * @param regulator The regulator to be configured. + * @param factor The multiplication factor. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_factor(t_pmic_regulator regulator, + t_switcher_factor factor); + +/*! + * This function gets the PLL multiplication factor + * + * @param regulator The regulator to be handled + * @param factor The multiplication factor. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_factor(t_pmic_regulator regulator, + t_switcher_factor * factor); + +/*! + * This function enables or disables low power mode. + * + * @param regulator The regulator to be configured. + * @param mode Select nominal or low power mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_lp_mode(t_pmic_regulator regulator, + t_regulator_lp_mode lp_mode); + +/*! + * This function gets low power mode. + * + * @param regulator The regulator to be handled + * @param mode Select nominal or low power mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_lp_mode(t_pmic_regulator regulator, + t_regulator_lp_mode * lp_mode); + +/*! + * This function sets the regulator configuration. + * + * @param regulator The regulator to be turned off. + * @param config The regulator output configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_config(t_pmic_regulator regulator, + t_regulator_config * config); + +/*! + * This function retrieves the regulator output configuration. + * + * @param regulator The regulator to be turned off. + * @param config Pointer to regulator configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_config(t_pmic_regulator regulator, + t_regulator_config * config); + +/*! + * This function enables automatically VBKUP2 in the memory hold modes. + * + * @param en if true, enable VBKUP2AUTOMH + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_vbkup2_auto_en(bool en); + +/*! + * This function gets state of automatically VBKUP2. + * + * @param en if true, VBKUP2AUTOMH is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_vbkup2_auto_state(bool * en); + +/*! + * This function enables battery detect function. + * + * @param en if true, enable BATTDETEN + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_bat_det_en(bool en); + +/*! + * This function gets state of battery detect function. + * + * @param en if true, BATTDETEN is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_bat_det_state(bool * en); + +/*! + * This function enables control of VVIB by VIBEN pin. + * + * @param en if true, enable VIBPINCTRL + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_vib_pin_en(bool en); + +/*! + * This function gets state of control of VVIB by VIBEN pin. + * @param en if true, VIBPINCTRL is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_gets_vib_pin_state(bool * en); + +/*! + * This function returns power up sense value + * + * @param p_up_sense value of power up sense + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_power_mode_sense(struct t_p_up_sense *p_up_sense); + +/*! + * This function configures the Regen assignment for all regulator + * + * @param regulator type of regulator + * @param en_dis if true, the regulator is enabled by regen. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_regen_assig(t_pmic_regulator regulator, bool en_dis); + +/*! + * This function gets the Regen assignment for all regulator + * + * @param regulator type of regulator + * @param en_dis return value, if true : + * the regulator is enabled by regen. + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_regen_assig(t_pmic_regulator regu, bool * en_dis); + +/*! + * This function sets the Regen polarity. + * + * @param en_dis If true regen is inverted. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_regen_inv(bool en_dis); + +/*! + * This function gets the Regen polarity. + * + * @param en_dis If true regen is inverted. + * + * @return This function returns 0 if successful. + */ + +PMIC_STATUS pmic_power_get_regen_inv(bool * en_dis); + +/*! + * This function enables esim control voltage. + * + * @param vesim if true, enable VESIMESIMEN + * @param vmmc1 if true, enable VMMC1ESIMEN + * @param vmmc2 if true, enable VMMC2ESIMEN + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_esim_v_en(bool vesim, bool vmmc1, bool vmmc2); + +/*! + * This function gets esim control voltage values. + * + * @param vesim if true, enable VESIMESIMEN + * @param vmmc1 if true, enable VMMC1ESIMEN + * @param vmmc2 if true, enable VMMC2ESIMEN + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_gets_esim_v_state(bool * vesim, + bool * vmmc1, bool * vmmc2); + +/*! + * This function enables auto reset after a system reset. + * + * @param en if true, the auto reset is enabled + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_auto_reset_en(bool en); + +/*! + * This function gets auto reset configuration. + * + * @param en if true, the auto reset is enabled + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_auto_reset_en(bool * en); + +/*! + * This function configures a system reset on a button. + * + * @param bt type of button. + * @param sys_rst if true, enable the system reset on this button + * @param deb_time sets the debounce time on this button pin + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_conf_button(t_button bt, bool sys_rst, int deb_time); + +/*! + * This function gets configuration of a button. + * + * @param bt type of button. + * @param sys_rst if true, the system reset is enabled on this button + * @param deb_time gets the debounce time on this button pin + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_conf_button(t_button bt, + bool * sys_rst, int *deb_time); + +/*! + * This function is used to subscribe on power event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_event_sub(t_pwr_int event, void *callback); + +/*! + * This function is used to un subscribe on power event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_event_unsub(t_pwr_int event, void *callback); + +#endif /* __KERNEL__ */ + +#endif /* __ASM_ARCH_MXC_PMIC_POWER_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/sdma.h b/arch/arm/plat-mxc/include/mach/sdma.h new file mode 100644 index 000000000000..d864b0680fbd --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/sdma.h @@ -0,0 +1,561 @@ + +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ASM_ARCH_MXC_SDMA_H__ +#define __ASM_ARCH_MXC_SDMA_H__ + +/*! + * @defgroup SDMA Smart Direct Memory Access (SDMA) Driver + */ + +/*! + * @file arch-mxc/sdma.h + * + * @brief This file contains the SDMA API declarations. + * + * SDMA is responsible on moving data between peripherals and memories (MCU, EMI and DSP). + * + * @ingroup SDMA + */ + +#include <linux/interrupt.h> +#include <asm/dma.h> +#include <stdarg.h> + +#include <mach/hardware.h> +#include <mach/dma.h> + +/*! + * This defines maximum DMA address + */ +#define MAX_DMA_ADDRESS 0xffffffff + +/*! + * This defines maximum number of DMA channels + */ +#ifdef CONFIG_MXC_SDMA_API +#define MAX_DMA_CHANNELS 32 +#define MAX_BD_NUMBER 16 +#define MXC_SDMA_DEFAULT_PRIORITY 1 +#define MXC_SDMA_MIN_PRIORITY 1 +#define MXC_SDMA_MAX_PRIORITY 7 +#else +#define MAX_DMA_CHANNELS 0 +#endif + +#define MXC_FIFO_MEM_DEST_FIXED 0x1 +#define MXC_FIFO_MEM_SRC_FIXED 0x2 + +#define SDMA_ASRC_INFO_WML_OFF 0 +#define SDMA_ASRC_INFO_WML_MASK ((1 << 10) - 1) +#define SDMA_ASRC_INFO_PS (1 << 10) +#define SDMA_ASRC_INFO_PA (1 << 11) +#define SDMA_ASRC_INFO_TXFR_DIR (1 << 14) +#define SDMA_ASRC_INFO_N_OFF (24) +#define SDMA_ASRC_INFO_N_MASK ((1 << 4) - 1) + +#define SDMA_ASRC_P2P_INFO_LWML_OFF 0 +#define SDMA_ASRC_P2P_INFO_LWML_MASK ((1 << 8) - 1) +#define SDMA_ASRC_P2P_INFO_PS (1 << 8) +#define SDMA_ASRC_P2P_INFO_PA (1 << 9) +#define SDMA_ASRC_P2P_INFO_SPDIF (1 << 10) +#define SDMA_ASRC_P2P_INFO_SP (1 << 11) +#define SDMA_ASRC_P2P_INFO_DP (1 << 12) +#define SDMA_ASRC_P2P_INFO_HWML_OFF 14 +#define SDMA_ASRC_P2P_INFO_HWML_MASK ((1 << 10) - 1) +#define SDMA_ASRC_P2P_INFO_LWE (1 << 28) +#define SDMA_ASRC_P2P_INFO_HWE (1 << 29) +#define SDMA_ASRC_P2P_INFO_CONT (1 << 31) + +/*! + * This enumerates transfer types + */ +typedef enum { + emi_2_per = 0, /*!< EMI memory to peripheral */ + emi_2_int, /*!< EMI memory to internal RAM */ + emi_2_emi, /*!< EMI memory to EMI memory */ + emi_2_dsp, /*!< EMI memory to DSP memory */ + per_2_int, /*!< Peripheral to internal RAM */ + per_2_emi, /*!< Peripheral to internal EMI memory */ + per_2_dsp, /*!< Peripheral to DSP memory */ + per_2_per, /*!< Peripheral to Peripheral */ + int_2_per, /*!< Internal RAM to peripheral */ + int_2_int, /*!< Internal RAM to Internal RAM */ + int_2_emi, /*!< Internal RAM to EMI memory */ + int_2_dsp, /*!< Internal RAM to DSP memory */ + dsp_2_per, /*!< DSP memory to peripheral */ + dsp_2_int, /*!< DSP memory to internal RAM */ + dsp_2_emi, /*!< DSP memory to EMI memory */ + dsp_2_dsp, /*!< DSP memory to DSP memory */ + emi_2_dsp_loop, /*!< EMI memory to DSP memory loopback */ + dsp_2_emi_loop, /*!< DSP memory to EMI memory loopback */ + dvfs_pll, /*!< DVFS script with PLL change */ + dvfs_pdr /*!< DVFS script without PLL change */ +} sdma_transferT; + +/*! + * This enumerates peripheral types + */ +typedef enum { + SSI, /*!< MCU domain SSI */ + SSI_SP, /*!< Shared SSI */ + MMC, /*!< MMC */ + SDHC, /*!< SDHC */ + UART, /*!< MCU domain UART */ + UART_SP, /*!< Shared UART */ + FIRI, /*!< FIRI */ + CSPI, /*!< MCU domain CSPI */ + CSPI_SP, /*!< Shared CSPI */ + SIM, /*!< SIM */ + ATA, /*!< ATA */ + CCM, /*!< CCM */ + EXT, /*!< External peripheral */ + MSHC, /*!< Memory Stick Host Controller */ + MSHC_SP, /*!< Shared Memory Stick Host Controller */ + DSP, /*!< DSP */ + MEMORY, /*!< Memory */ + FIFO_MEMORY, /*!< FIFO type Memory */ + SPDIF, /*!< SPDIF */ + IPU_MEMORY, /*!< IPU Memory */ + ASRC, /*!< ASRC */ + ESAI, /*!< ESAI */ +} sdma_periphT; + +#ifndef TRANSFER_32BIT +/*! + * This defines SDMA access data size + */ +#define TRANSFER_32BIT 0x00 +#define TRANSFER_8BIT 0x01 +#define TRANSFER_16BIT 0x02 +#define TRANSFER_24BIT 0x03 + +#endif + +/*! + * This defines maximum device name length passed during mxc_request_dma(). + */ +#define MAX_DEVNAME_LENGTH 32 + +/*! + * This defines SDMA interrupt callback function prototype. + */ +typedef void (*dma_callback_t) (void *arg); + +/*! + * Structure containing sdma channel parameters. + */ +typedef struct { + __u32 watermark_level; /*!< Lower/upper threshold that + * triggers SDMA event + * for p2p, this is event1 watermark level + */ + __u32 per_address; /*!< Peripheral source/destination + * physical address + * for p2p, this is destination address + */ + sdma_periphT peripheral_type; /*!< Peripheral type */ + sdma_transferT transfer_type; /*!< Transfer type */ + int event_id; /*!< Event number, + * needed by all channels + * that started by peripherals dma + * request (per_2_*,*_2_per) + * Not used for memory and DSP + * transfers. + */ + int event_id2; /*!< Second event number, + * used in ATA scripts only. + */ + int bd_number; /*!< Buffer descriptors number. + * If not set, single buffer + * descriptor will be used. + */ + dma_callback_t callback; /*! callback function */ + void *arg; /*! callback argument */ + unsigned long word_size:8; /*!< SDMA data access word size */ + unsigned long ext:1; /*!< 1: extend parameter structure */ +} dma_channel_params; + +typedef struct { + dma_channel_params common; + unsigned long p2p_dir:1; /*!< 0: per2 to per. + * the device of peripheral_type is per. + * 1: per to per2 + * the device of peripheral_type is per2 + */ + unsigned long info_bits; /*!< info field in context */ + unsigned long info_mask; /*!< info field mask in context */ + __u32 watermark_level2; /*!< event2 threshold that + * triggers SDMA event + * just valid for p2p. + */ + __u32 per_address2; /*!< Peripheral source + * physical address. + * just valid for p2p. + */ + struct dma_channel_info info; /*!< the channel special parameter */ +} dma_channel_ext_params; + +/*! + * Structure containing sdma request parameters. + */ +typedef struct { + /*! physical source memory address */ + __u8 *sourceAddr; + /*! physical destination memory address */ + __u8 *destAddr; + /*! amount of data to transfer, + * updated during mxc_dma_get_config + */ + __u16 count; + /*!< DONE bit of the buffer descriptor, + * updated during mxc_dma_get_config + * 0 - means the BD is done and closed by SDMA + * 1 - means the BD is still being processed by SDMA + */ + int bd_done; + /*!< CONT bit of the buffer descriptor, + * set it if full multi-buffer descriptor mechanism + * required. + */ + int bd_cont; + /*!< ERROR bit of the buffer descriptor, + * updated during mxc_dma_get_config. + * If it is set - there was an error during BD processing. + */ + int bd_error; +} dma_request_t; + +/*! + * Structure containing sdma request parameters. + */ +typedef struct { + /*! address of ap_2_ap script */ + int mxc_sdma_ap_2_ap_addr; + /*! address of ap_2_bp script */ + int mxc_sdma_ap_2_bp_addr; + /*! address of ap_2_ap_fixed script */ + int mxc_sdma_ap_2_ap_fixed_addr; + /*! address of bp_2_ap script */ + int mxc_sdma_bp_2_ap_addr; + /*! address of loopback_on_dsp_side script */ + int mxc_sdma_loopback_on_dsp_side_addr; + /*! address of mcu_interrupt_only script */ + int mxc_sdma_mcu_interrupt_only_addr; + + /*! address of firi_2_per script */ + int mxc_sdma_firi_2_per_addr; + /*! address of firi_2_mcu script */ + int mxc_sdma_firi_2_mcu_addr; + /*! address of per_2_firi script */ + int mxc_sdma_per_2_firi_addr; + /*! address of mcu_2_firi script */ + int mxc_sdma_mcu_2_firi_addr; + + /*! address of uart_2_per script */ + int mxc_sdma_uart_2_per_addr; + /*! address of uart_2_mcu script */ + int mxc_sdma_uart_2_mcu_addr; + /*! address of per_2_app script */ + int mxc_sdma_per_2_app_addr; + /*! address of mcu_2_app script */ + int mxc_sdma_mcu_2_app_addr; + /*! address of per_2_per script */ + int mxc_sdma_per_2_per_addr; + + /*! address of uartsh_2_per script */ + int mxc_sdma_uartsh_2_per_addr; + /*! address of uartsh_2_mcu script */ + int mxc_sdma_uartsh_2_mcu_addr; + /*! address of per_2_shp script */ + int mxc_sdma_per_2_shp_addr; + /*! address of mcu_2_shp script */ + int mxc_sdma_mcu_2_shp_addr; + + /*! address of ata_2_mcu script */ + int mxc_sdma_ata_2_mcu_addr; + /*! address of mcu_2_ata script */ + int mxc_sdma_mcu_2_ata_addr; + + /*! address of app_2_per script */ + int mxc_sdma_app_2_per_addr; + /*! address of app_2_mcu script */ + int mxc_sdma_app_2_mcu_addr; + /*! address of shp_2_per script */ + int mxc_sdma_shp_2_per_addr; + /*! address of shp_2_mcu script */ + int mxc_sdma_shp_2_mcu_addr; + + /*! address of mshc_2_mcu script */ + int mxc_sdma_mshc_2_mcu_addr; + /*! address of mcu_2_mshc script */ + int mxc_sdma_mcu_2_mshc_addr; + + /*! address of spdif_2_mcu script */ + int mxc_sdma_spdif_2_mcu_addr; + /*! address of mcu_2_spdif script */ + int mxc_sdma_mcu_2_spdif_addr; + + /*! address of asrc_2_mcu script */ + int mxc_sdma_asrc_2_mcu_addr; + + /*! address of ext_mem_2_ipu script */ + int mxc_sdma_ext_mem_2_ipu_addr; + + /*! address of descrambler script */ + int mxc_sdma_descrambler_addr; + + /*! address of dptc_dvfs script */ + int mxc_sdma_dptc_dvfs_addr; + + int mxc_sdma_utra_addr; + + /*! address where ram code starts */ + int mxc_sdma_ram_code_start_addr; + /*! size of the ram code */ + int mxc_sdma_ram_code_size; + /*! RAM image address */ + unsigned short *mxc_sdma_start_addr; +} sdma_script_start_addrs; + +/*! Structure to store the initialized dma_channel parameters */ +typedef struct mxc_sdma_channel_params { + /*! Channel type (static channel number or dynamic channel) */ + unsigned int channel_num; + /*! Channel priority [0x1(lowest) - 0x7(highest)] */ + unsigned int chnl_priority; + /*! Channel params */ + dma_channel_params chnl_params; +} mxc_sdma_channel_params_t; + +/*! Structure to store the initialized dma_channel extend parameters */ +typedef struct mxc_sdma_channel_ext_params { + /*! Channel type (static channel number or dynamic channel) */ + unsigned int channel_num; + /*! Channel priority [0x1(lowest) - 0x7(highest)] */ + unsigned int chnl_priority; + /*! Channel extend params */ + dma_channel_ext_params chnl_ext_params; +} mxc_sdma_channel_ext_params_t; + +/*! Private SDMA data structure */ +typedef struct mxc_dma_channel_private { + /*! ID of the buffer that was processed */ + unsigned int buf_tail; + /*! Tasklet for the channel */ + struct tasklet_struct chnl_tasklet; + /*! Flag indicates if interrupt is required after every BD transfer */ + int intr_after_every_bd; +} mxc_dma_channel_private_t; + +/*! + * Setup channel according to parameters. + * Must be called once after mxc_request_dma() + * + * @param channel channel number + * @param p channel parameters pointer + * @return 0 on success, error code on fail + */ +int mxc_dma_setup_channel(int channel, dma_channel_params * p); + +/*! + * Setup the channel priority. This can be used to change the default priority + * for the channel. + * + * @param channel channel number + * @param priority priority to be set for the channel + * + * @return 0 on success, error code on failure + */ +int mxc_dma_set_channel_priority(unsigned int channel, unsigned int priority); + +/*! + * Allocates dma channel. + * If channel's value is 0, then the function allocates a free channel + * dynamically and sets its value to channel. + * Else allocates requested channel if it is free. + * If the channel is busy or no free channels (in dynamic allocation) -EBUSY returned. + * + * @param channel pointer to channel number + * @param devicename device name + * @return 0 on success, error code on fail + */ +int mxc_request_dma(int *channel, const char *devicename); + +/*! + * Configures request parameters. Can be called multiple times after + * mxc_request_dma() and mxc_dma_setup_channel(). + * + * + * @param channel channel number + * @param p request parameters pointer + * @param bd_index index of buffer descriptor to set + * @return 0 on success, error code on fail + */ +/* int mxc_dma_set_config(int channel, dma_request_t *p, int bd_index); */ +int mxc_dma_set_config(int channel, dma_request_t * p, int bd_index); + +/*! + * Returns request parameters. + * + * @param channel channel number + * @param p request parameters pointer + * @param bd_index index of buffer descriptor to get + * @return 0 on success, error code on fail + */ +/* int mxc_dma_get_config(int channel, dma_request_t *p, int bd_index); */ +int mxc_dma_get_config(int channel, dma_request_t * p, int bd_index); + +/*! + * This function is used by MXC IPC's write_ex2. It passes the a pointer to the + * data control structure to iapi_write_ipcv2() + * + * @param channel SDMA channel number + * @param ctrl_ptr Data Control structure pointer + */ +int mxc_sdma_write_ipcv2(int channel, void *ctrl_ptr); + +/*! + * This function is used by MXC IPC's read_ex2. It passes the a pointer to the + * data control structure to iapi_read_ipcv2() + * + * @param channel SDMA channel number + * @param ctrl_ptr Data Control structure pointer + */ +int mxc_sdma_read_ipcv2(int channel, void *ctrl_ptr); + +/*! + * Starts dma channel. + * + * @param channel channel number + */ +int mxc_dma_start(int channel); + +/*! + * Stops dma channel. + * + * @param channel channel number + */ +int mxc_dma_stop(int channel); + +/*! + * Frees dma channel. + * + * @param channel channel number + */ +void mxc_free_dma(int channel); + +/*! + * Sets callback function. Used with standard dma api + * for supporting interrupts + * + * @param channel channel number + * @param callback callback function pointer + * @param arg argument for callback function + */ +void mxc_dma_set_callback(int channel, dma_callback_t callback, void *arg); + +/*! + * Allocates uncachable buffer. Uses hash table. + * + * @param size size of allocated buffer + * @return pointer to buffer + */ +void *sdma_malloc(size_t size); + +#ifdef CONFIG_SDMA_IRAM +/*! + * Allocates uncachable buffer from IRAM.. + * + * @param size size of allocated buffer + * @return pointer to buffer + */ +void *sdma_iram_malloc(size_t size); +#endif /*CONFIG_SDMA_IRAM */ + +/*! + * Frees uncachable buffer. Uses hash table. + */ +void sdma_free(void *buf); + +/*! + * Converts virtual to physical address. Uses hash table. + * + * @param buf virtual address pointer + * @return physical address value + */ +unsigned long sdma_virt_to_phys(void *buf); + +/*! + * Converts physical to virtual address. Uses hash table. + * + * @param buf physical address value + * @return virtual address pointer + */ +void *sdma_phys_to_virt(unsigned long buf); + +/*! + * Configures the BD_INTR bit on a buffer descriptor parameters. + * + * + * @param channel channel number + * @param bd_index index of buffer descriptor to set + * @param bd_intr flag to set or clear the BD_INTR bit + */ +void mxc_dma_set_bd_intr(int channel, int bd_index, int bd_intr); + +/*! + * Gets the BD_INTR bit on a buffer descriptor. + * + * + * @param channel channel number + * @param bd_index index of buffer descriptor to set + * + * @return returns the BD_INTR bit status + */ +int mxc_dma_get_bd_intr(int channel, int bd_index); + +/*! + * Stop the current transfer + * + * @param channel channel number + * @param buffer_number number of buffers (beginning with 0), + * whose done bits should be reset to 0 + */ +int mxc_dma_reset(int channel, int buffer_number); + +/*! + * This functions Returns the SDMA paramaters associated for a module + * + * @param channel_id the ID of the module requesting DMA + * @return returns the sdma parameters structure for the device + */ +mxc_sdma_channel_params_t *mxc_sdma_get_channel_params(mxc_dma_device_t + channel_id); + +/*! + * This functions marks the SDMA channels that are statically allocated + * + * @param chnl the channel array used to store channel information + */ +void mxc_get_static_channels(mxc_dma_channel_t * chnl); + +/*! + * Initializes SDMA driver + */ +int __init sdma_init(void); + +#define DEFAULT_ERR 1 + +#endif diff --git a/arch/arm/plat-mxc/include/mach/spba.h b/arch/arm/plat-mxc/include/mach/spba.h new file mode 100644 index 000000000000..8d018be8a0b7 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/spba.h @@ -0,0 +1,66 @@ + +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup SPBA Shared Peripheral Bus Arbiter (SPBA) + * @ingroup MSL_MX31 MSL_MX35 MSL_MX37 MSL_MX51 MSL_MXC91321 + */ + +/*! + * @file arch-mxc/spba.h + * @brief This file contains the Shared Peripheral Bus Arbiter (spba) API. + * + * @ingroup SPBA + */ + +#ifndef __ASM_ARCH_MXC_SPBA_H__ +#define __ASM_ARCH_MXC_SPBA_H__ + +#ifdef __KERNEL__ + +#define MXC_SPBA_RAR_MASK 0x7 + +/*! + * Defines three SPBA masters: A - ARM, C - SDMA (no master B for MX31) + */ +enum spba_masters { + SPBA_MASTER_A = 1, + SPBA_MASTER_B = 2, + SPBA_MASTER_C = 4, +}; + +/*! + * This function allows the three masters (A, B, C) to take ownership of a + * shared peripheral. + * + * @param mod specified module as defined in \b enum \b #spba_module + * @param master one of more (or-ed together) masters as defined in \b enum \b #spba_masters + * + * @return 0 if successful; -1 otherwise. + */ +int spba_take_ownership(int mod, int master); + +/*! + * This function releases the ownership for a shared peripheral. + * + * @param mod specified module as defined in \b enum \b #spba_module + * @param master one of more (or-ed together) masters as defined in \b enum \b #spba_masters + * + * @return 0 if successful; -1 otherwise. + */ +int spba_rel_ownership(int mod, int master); + +#endif /* __KERNEL__ */ + +#endif /* __ASM_ARCH_MXC_SPBA_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/system.h b/arch/arm/plat-mxc/include/mach/system.h index e56241af870e..126bc8713159 100644 --- a/arch/arm/plat-mxc/include/mach/system.h +++ b/arch/arm/plat-mxc/include/mach/system.h @@ -1,7 +1,7 @@ /* * Copyright (C) 1999 ARM Limited * Copyright (C) 2000 Deep Blue Solutions Ltd - * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2004-2008 Freescale Semiconductor, Inc. All 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 @@ -21,10 +21,7 @@ #ifndef __ASM_ARCH_MXC_SYSTEM_H__ #define __ASM_ARCH_MXC_SYSTEM_H__ -static inline void arch_idle(void) -{ - cpu_do_idle(); -} +extern void arch_idle(void); void arch_reset(char mode, const char *cmd); diff --git a/arch/arm/plat-mxc/include/mach/timex.h b/arch/arm/plat-mxc/include/mach/timex.h index 07b4a73c9d2f..46ebeabf8bce 100644 --- a/arch/arm/plat-mxc/include/mach/timex.h +++ b/arch/arm/plat-mxc/include/mach/timex.h @@ -22,10 +22,14 @@ #if defined CONFIG_ARCH_MX1 #define CLOCK_TICK_RATE 16000000 -#elif defined CONFIG_ARCH_MX2 +#elif defined CONFIG_ARCH_MX2 || defined CONFIG_ARCH_MX25 #define CLOCK_TICK_RATE 13300000 -#elif defined CONFIG_ARCH_MX3 +#elif defined CONFIG_ARCH_MX3 || defined CONFIG_ARCH_MX35 #define CLOCK_TICK_RATE 16625000 +#elif defined CONFIG_ARCH_MX37 +#define CLOCK_TICK_RATE 8000000 +#elif defined CONFIG_ARCH_MX51 +#define CLOCK_TICK_RATE 8000000 #endif #endif /* __ASM_ARCH_MXC_TIMEX_H__ */ diff --git a/arch/arm/plat-mxc/include/mach/uncompress.h b/arch/arm/plat-mxc/include/mach/uncompress.h index de6fe0365982..8314fafacfd5 100644 --- a/arch/arm/plat-mxc/include/mach/uncompress.h +++ b/arch/arm/plat-mxc/include/mach/uncompress.h @@ -27,6 +27,8 @@ #include <mach/hardware.h> +unsigned int system_rev; + #define UART(x) (*(volatile unsigned long *)(serial_port + (x))) #define USR2 0x98 diff --git a/arch/arm/plat-mxc/io.c b/arch/arm/plat-mxc/io.c new file mode 100644 index 000000000000..7681ecf8dfb6 --- /dev/null +++ b/arch/arm/plat-mxc/io.c @@ -0,0 +1,41 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * mxc custom ioremap implementation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <mach/hardware.h> +#include <linux/io.h> + +void *__iomem __mxc_ioremap(unsigned long cookie, size_t size, + unsigned int mtype) +{ + if (mtype == MT_DEVICE && IS_MEM_DEVICE_NONSHARED(cookie)) { + mtype = MT_DEVICE_NONSHARED; + } + return __arm_ioremap(cookie, size, mtype); +} + +EXPORT_SYMBOL(__mxc_ioremap); + +void __mxc_iounmap(void __iomem * addr) +{ + extern void __iounmap(volatile void __iomem * addr); + + __iounmap(addr); +} + +EXPORT_SYMBOL(__mxc_iounmap); diff --git a/arch/arm/plat-mxc/irq.c b/arch/arm/plat-mxc/irq.c index 8aee76304f8f..c98e5b3c7edf 100644 --- a/arch/arm/plat-mxc/irq.c +++ b/arch/arm/plat-mxc/irq.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright 2008 Juergen Beisert, kernel@pengutronix.de * * This program is free software; you can redistribute it and/or @@ -20,6 +20,7 @@ #include <linux/module.h> #include <linux/irq.h> #include <linux/io.h> +#include <linux/sysdev.h> #include <mach/common.h> #include <asm/mach/irq.h> #include <mach/hardware.h> @@ -46,6 +47,11 @@ static void __iomem *avic_base; +#define IRQ_BIT(irq) (1 << (irq)) + +static uint32_t saved_wakeup_low, saved_wakeup_high; +static uint32_t suspend_wakeup_low, suspend_wakeup_high; + int imx_irq_set_priority(unsigned char irq, unsigned char prio) { #ifdef CONFIG_MXC_IRQ_PRIOR @@ -102,13 +108,122 @@ static void mxc_unmask_irq(unsigned int irq) __raw_writel(irq, avic_base + AVIC_INTENNUM); } +/*! + * Set interrupt number "irq" in the AVIC as a wake-up source. + * + * @param irq interrupt source number + * @param enable enable as wake-up if equal to non-zero + * disble as wake-up if equal to zero + * + * @return This function returns 0 on success. + */ +static int mxc_set_wake_irq(unsigned int irq, unsigned int enable) +{ + uint32_t *wakeup_intr; + uint32_t irq_bit; + + if (irq < 32) { + wakeup_intr = &suspend_wakeup_low; + irq_bit = IRQ_BIT(irq); + } else { + wakeup_intr = &suspend_wakeup_high; + irq_bit = IRQ_BIT(irq - 32); + } + + if (enable) { + *wakeup_intr |= irq_bit; + } else { + *wakeup_intr &= ~irq_bit; + } + + return 0; +} + static struct irq_chip mxc_avic_chip = { .ack = mxc_mask_irq, .mask = mxc_mask_irq, .unmask = mxc_unmask_irq, + .set_wake = mxc_set_wake_irq, +}; + +#ifdef CONFIG_PM +/*! + * This function puts the AVIC in low-power mode/state. + * All the interrupts that are enabled are first saved. + * Only those interrupts which registers as a wake source by calling + * enable_irq_wake are enabled. All other interrupts are disabled. + * + * @param dev the system device structure used to give information + * on AVIC to suspend + * @param mesg the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxc_avic_suspend(struct sys_device *dev, pm_message_t mesg) +{ + saved_wakeup_high = __raw_readl(avic_base + AVIC_INTENABLEH); + saved_wakeup_low = __raw_readl(avic_base + AVIC_INTENABLEL); + + __raw_writel(suspend_wakeup_high, avic_base + AVIC_INTENABLEH); + __raw_writel(suspend_wakeup_low, avic_base + AVIC_INTENABLEL); + + return 0; +} + +/*! + * This function brings the AVIC back from low-power state. + * All the interrupts enabled before suspension are re-enabled from + * the saved information. + * + * @param dev the system device structure used to give information + * on AVIC to resume + * + * @return The function always returns 0. + */ +static int mxc_avic_resume(struct sys_device *dev) +{ + __raw_writel(saved_wakeup_high, avic_base + AVIC_INTENABLEH); + __raw_writel(saved_wakeup_low, avic_base + AVIC_INTENABLEL); + + return 0; +} + +#else +#define mxc_avic_suspend NULL +#define mxc_avic_resume NULL +#endif /* CONFIG_PM */ +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct sysdev_class mxc_avic_sysclass = { + .name = "mxc_irq", + .suspend = mxc_avic_suspend, + .resume = mxc_avic_resume, +}; + +/*! + * This structure represents AVIC as a system device. + * 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, it is represented and treated a little differently. + */ +static struct sys_device mxc_avic_device = { + .id = 0, + .cls = &mxc_avic_sysclass, }; /* + * This function is used to get the AVIC Lo and Hi interrupts + * that are enabled as wake up sources to wake up the core from suspend + */ +void mxc_get_wake_irq(u32 * wake_src[]) +{ + *wake_src[0] = __raw_readl(avic_base + AVIC_INTENABLEL); + *wake_src[1] = __raw_readl(avic_base + AVIC_INTENABLEH); +} + +/* * This function initializes the AVIC hardware and disables all the * interrupts. It registers the interrupt enable and disable functions * to the kernel for each interrupt source. @@ -142,14 +257,39 @@ void __init mxc_init_irq(void) for (i = 0; i < 8; i++) __raw_writel(0, avic_base + AVIC_NIPRIORITY(i)); - /* init architectures chained interrupt handler */ - mxc_register_gpios(); #ifdef CONFIG_FIQ /* Initialize FIQ */ init_FIQ(); #endif + if (MXC_INT_FORCE >= 32) + __raw_writel(1 << (MXC_INT_FORCE & 31), avic_base + AVIC_INTFRCH); + else if (MXC_INT_FORCE >= 0) + __raw_writel(1 << MXC_INT_FORCE, avic_base + AVIC_INTFRCL); + printk(KERN_INFO "MXC IRQ initialized\n"); } +/*! + * This function registers AVIC hardware as a system device. + * System devices will only be suspended with interrupts disabled, and + * after all other devices have been suspended. On resume, they will be + * resumed before any other devices, and also with interrupts disabled. + * + * @return This function returns 0 on success. + */ +static int __init mxc_avic_sysinit(void) +{ + int ret = 0; + + ret = sysdev_class_register(&mxc_avic_sysclass); + if (ret == 0) { + ret = sysdev_register(&mxc_avic_device); + } + + return ret; +} + +arch_initcall(mxc_avic_sysinit); + diff --git a/arch/arm/plat-mxc/isp1301xc.c b/arch/arm/plat-mxc/isp1301xc.c new file mode 100644 index 000000000000..dceea44fd4da --- /dev/null +++ b/arch/arm/plat-mxc/isp1301xc.c @@ -0,0 +1,290 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/usb/fsl_xcvr.h> +#include <linux/i2c.h> + +#include <mach/arc_otg.h> + +/* + * ISP1301 register addresses,all register of ISP1301 + * is one-byte length register + */ + +/* ISP1301: I2C device address */ +#define ISP1301_DEV_ADDR 0x2D + +/* ISP 1301 register set*/ +#define ISP1301_MODE_REG1_SET 0x04 +#define ISP1301_MODE_REG1_CLR 0x05 + +#define ISP1301_CTRL_REG1_SET 0x06 +#define ISP1301_CTRL_REG1_CLR 0x07 + +#define ISP1301_INT_SRC_REG 0x08 +#define ISP1301_INT_LAT_REG_SET 0x0a +#define ISP1301_INT_LAT_REG_CLR 0x0b +#define ISP1301_INT_FALSE_REG_SET 0x0c +#define ISP1301_INT_FALSE_REG_CLR 0x0d +#define ISP1301_INT_TRUE_REG_SET 0x0e +#define ISP1301_INT_TRUE_REG_CLR 0x0f + +#define ISP1301_CTRL_REG2_SET 0x10 +#define ISP1301_CTRL_REG2_CLR 0x11 + +#define ISP1301_MODE_REG2_SET 0x12 +#define ISP1301_MODE_REG2_CLR 0x13 + +#define ISP1301_BCD_DEV_REG0 0x14 +#define ISP1301_BCD_DEV_REG1 0x15 + +/* OTG Control register bit description */ +#define DP_PULLUP 0x01 +#define DM_PULLUP 0x02 +#define DP_PULLDOWN 0x04 +#define DM_PULLDOWN 0x08 +#define ID_PULLDOWN 0x10 +#define VBUS_DRV 0x20 +#define VBUS_DISCHRG 0x40 +#define VBUS_CHRG 0x80 + +/* Mode Control 1 register bit description */ +#define SPEED_REG 0x01 +#define SUSPEND_REG 0x02 +#define DAT_SE0 0x04 +#define TRANSP_EN 0x08 +#define BDIS_ACON_EN 0x10 +#define OE_INT_EN 0x20 +#define UART_EN 0x40 + +/* Mode Control 2 register bit description */ +#define SPD_SUSP_CTRL 0x02 +#define BI_DI 0x04 + +static int isp1301_attach(struct i2c_adapter *adapter); +static int isp1301_detach(struct i2c_client *client); + +static struct i2c_driver isp1301_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "isp1301 Client", + }, + .attach_adapter = isp1301_attach, + .detach_client = isp1301_detach, +}; + +static struct i2c_client isp1301_i2c_client = { + .name = "isp1301 I2C dev", + .addr = ISP1301_DEV_ADDR, + .driver = &isp1301_i2c_driver, +}; + +static unsigned short normal_i2c[] = { ISP1301_DEV_ADDR, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static int isp1301_detect_client(struct i2c_adapter *adapter, int address, + int kind) +{ + isp1301_i2c_client.adapter = adapter; + if (i2c_attach_client(&isp1301_i2c_client)) { + isp1301_i2c_client.adapter = NULL; + printk(KERN_ERR "isp1301_attach: i2c_attach_client failed\n"); + return -1; + } + + printk(KERN_INFO "isp1301 Detected\n"); + return 0; +} + +/*! + * isp1301 I2C attach function + * + * @param adapter struct i2c_adapter * + * @return Error code indicating success or failure + */ +static int isp1301_attach(struct i2c_adapter *adapter) +{ + return i2c_probe(adapter, &addr_data, &isp1301_detect_client); +} + +/*! + * isp1301 I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int isp1301_detach(struct i2c_client *client) +{ + int err; + + if (!isp1301_i2c_client.adapter) + return -1; + + err = i2c_detach_client(&isp1301_i2c_client); + isp1301_i2c_client.adapter = NULL; + + return err; +} + +static void isp1301_init(struct fsl_xcvr_ops *this) +{ + pr_debug("%s\n", __FUNCTION__); + + i2c_add_driver(&isp1301_i2c_driver); +} + +static void isp1301_uninit(struct fsl_xcvr_ops *this) +{ + // DDD do this for host only: + /* disable OTG VBUS */ + i2c_del_driver(&isp1301_i2c_driver); +} + +/* Write ISP1301 register*/ +static inline void isp1301_write_reg(char reg, char data) +{ + i2c_smbus_write_byte_data(&isp1301_i2c_client, reg, data); +} + +/* read ISP1301 register*/ +static inline char isp1301_read_reg(char reg) +{ + return i2c_smbus_read_byte_data(&isp1301_i2c_client, reg); +} + +/* set ISP1301 as USB host*/ +static inline void isp1301_set_serial_host(void) +{ + pr_debug("%s\n", __FUNCTION__); + + isp1301_write_reg(ISP1301_MODE_REG2_CLR, 0xFF); +#if defined(CONFIG_MXC_USB_SB3) || defined(CONFIG_MXC_USB_DB4) + isp1301_write_reg(ISP1301_MODE_REG2_SET, SPD_SUSP_CTRL | BI_DI); +#else + isp1301_write_reg(ISP1301_MODE_REG2_SET, SPD_SUSP_CTRL); +#endif + + isp1301_write_reg(ISP1301_MODE_REG1_CLR, 0xFF); +#if defined(CONFIG_MXC_USB_SB3) || defined(CONFIG_MXC_USB_SU6) + isp1301_write_reg(ISP1301_MODE_REG1_SET, DAT_SE0 | SPEED_REG); +#else + isp1301_write_reg(ISP1301_MODE_REG1_SET, SPEED_REG); +#endif + + /* configure transceiver for host mode */ + isp1301_write_reg(ISP1301_CTRL_REG1_SET, + (VBUS_DRV | DP_PULLDOWN | DM_PULLDOWN)); +} + +/* set ISP1301 as USB device */ +static inline void isp1301_set_serial_dev(void) +{ + pr_debug("%s\n", __FUNCTION__); + + isp1301_write_reg(ISP1301_MODE_REG2_CLR, 0xFF); +#if defined(CONFIG_MXC_USB_SB3) || defined(CONFIG_MXC_USB_DB4) + isp1301_write_reg(ISP1301_MODE_REG2_SET, SPD_SUSP_CTRL | BI_DI); +#else + isp1301_write_reg(ISP1301_MODE_REG2_SET, SPD_SUSP_CTRL); +#endif + + isp1301_write_reg(ISP1301_MODE_REG1_CLR, 0xFF); +#if defined(CONFIG_MXC_USB_SB3) || defined(CONFIG_MXC_USB_SU6) + isp1301_write_reg(ISP1301_MODE_REG1_SET, DAT_SE0 | SPEED_REG); +#else + isp1301_write_reg(ISP1301_MODE_REG1_SET, SPEED_REG); +#endif + + /* FS mode, DP pull down, DM pull down */ + isp1301_write_reg(ISP1301_CTRL_REG1_SET, + (DP_PULLDOWN | DM_PULLDOWN | DP_PULLUP)); +} + +static void isp1301_set_vbus_power(struct fsl_xcvr_ops *this, + struct fsl_usb2_platform_data *pdata, int on) +{ + pr_debug("%s(on=%d)\n", __FUNCTION__, on); + if (on) { + /* disable D+ pull-up */ + isp1301_write_reg(ISP1301_CTRL_REG1_CLR, DP_PULLUP); + /* enable D+ pull-down */ + isp1301_write_reg(ISP1301_CTRL_REG1_SET, DP_PULLDOWN); + /* turn on Vbus */ + isp1301_write_reg(ISP1301_CTRL_REG1_SET, VBUS_DRV); + } else { + /* D+ pull up, D- pull down */ + isp1301_write_reg(ISP1301_CTRL_REG1_SET, + (DP_PULLUP | DM_PULLDOWN)); + /* disable D- pull up, disable D+ pull down */ + isp1301_write_reg(ISP1301_CTRL_REG1_CLR, + (DM_PULLUP | DP_PULLDOWN)); + } +} + +/* + * Enable or disable the D+ pullup. + */ +static void isp1301_pullup(int on) +{ + pr_debug("%s(%d)\n", __func__, on); + + if (on) + isp1301_write_reg(ISP1301_CTRL_REG1_SET, DP_PULLUP); + else + isp1301_write_reg(ISP1301_CTRL_REG1_CLR, DP_PULLUP); +} + +static struct fsl_xcvr_ops isp1301_ops_otg = { + .name = "isp1301", + .xcvr_type = PORTSC_PTS_SERIAL, + .init = isp1301_init, + .uninit = isp1301_uninit, + .set_host = isp1301_set_serial_host, + .set_device = isp1301_set_serial_dev, + .set_vbus_power = isp1301_set_vbus_power, + .pullup = isp1301_pullup, +}; + +extern void fsl_usb_xcvr_register(struct fsl_xcvr_ops *xcvr_ops); + +static int __init isp1301xc_init(void) +{ + pr_debug("%s\n", __FUNCTION__); + + fsl_usb_xcvr_register(&isp1301_ops_otg); + + return 0; +} + +extern void fsl_usb_xcvr_unregister(struct fsl_xcvr_ops *xcvr_ops); + +static void __exit isp1301xc_exit(void) +{ + fsl_usb_xcvr_unregister(&isp1301_ops_otg); +} + +module_init(isp1301xc_init); +module_exit(isp1301xc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("isp1301"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-mxc/isp1504xc.c b/arch/arm/plat-mxc/isp1504xc.c new file mode 100644 index 000000000000..e9a126454d53 --- /dev/null +++ b/arch/arm/plat-mxc/isp1504xc.c @@ -0,0 +1,279 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/delay.h> +#include <linux/usb/fsl_xcvr.h> + +#include <mach/hardware.h> +#include <mach/arc_otg.h> +#include <asm/mach-types.h> + +/* ISP 1504 register addresses */ +#define ISP1504_VID_LOW 0x00 /* Vendor ID low */ +#define ISP1504_VID_HIGH 0x01 /* Vendor ID high */ +#define ISP1504_PID_LOW 0x02 /* Product ID low */ +#define ISP1504_PID_HIGH 0x03 /* Product ID high */ +#define ISP1504_FUNC 0x04 /* Function Control */ +#define ISP1504_ITFCTL 0x07 /* Interface Control */ +#define ISP1504_OTGCTL 0x0A /* OTG Control */ + +/* add to above register address to access Set/Clear functions */ +#define ISP1504_REG_SET 0x01 +#define ISP1504_REG_CLEAR 0x02 + +/* 1504 OTG Control Register bits */ +#define USE_EXT_VBUS_IND (1 << 7) /* Use ext. Vbus indicator */ +#define DRV_VBUS_EXT (1 << 6) /* Drive Vbus external */ +#define DRV_VBUS (1 << 5) /* Drive Vbus */ +#define CHRG_VBUS (1 << 4) /* Charge Vbus */ +#define DISCHRG_VBUS (1 << 3) /* Discharge Vbus */ +#define DM_PULL_DOWN (1 << 2) /* enable DM Pull Down */ +#define DP_PULL_DOWN (1 << 1) /* enable DP Pull Down */ +#define ID_PULL_UP (1 << 0) /* enable ID Pull Up */ + +/* 1504 OTG Function Control Register bits */ +#define SUSPENDM (1 << 6) /* places the PHY into + low-power mode */ +#define DRV_RESET (1 << 5) /* Active HIGH transceiver + reset */ + +/*! + * read ULPI register 'reg' thru VIEWPORT register 'view' + * + * @param reg register to read + * @param view the ULPI VIEWPORT register address + * @return return isp1504 register value + */ +static u8 isp1504_read(int reg, volatile u32 *view) +{ + u32 data; + + /* make sure interface is running */ + if (!(__raw_readl(view) && ULPIVW_SS)) { + __raw_writel(ULPIVW_WU, view); + do { /* wait for wakeup */ + data = __raw_readl(view); + } while (data & ULPIVW_WU); + } + + /* read the register */ + __raw_writel((ULPIVW_RUN | (reg << ULPIVW_ADDR_SHIFT)), view); + + do { /* wait for completion */ + data = __raw_readl(view); + } while (data & ULPIVW_RUN); + + return (u8) (data >> ULPIVW_RDATA_SHIFT) & ULPIVW_RDATA_MASK; +} + +/*! + * set bits into OTG ISP1504 register 'reg' thru VIEWPORT register 'view' + * + * @param bits set value + * @param reg which register + * @param view the ULPI VIEWPORT register address + */ +static void isp1504_set(u8 bits, int reg, volatile u32 *view) +{ + u32 data; + + /* make sure interface is running */ + if (!(__raw_readl(view) && ULPIVW_SS)) { + __raw_writel(ULPIVW_WU, view); + do { /* wait for wakeup */ + data = __raw_readl(view); + } while (data & ULPIVW_WU); + } + + __raw_writel((ULPIVW_RUN | ULPIVW_WRITE | + ((reg + ISP1504_REG_SET) << ULPIVW_ADDR_SHIFT) | + ((bits & ULPIVW_WDATA_MASK) << ULPIVW_WDATA_SHIFT)), + view); + + while (__raw_readl(view) & ULPIVW_RUN) /* wait for completion */ + continue; +} + +/*! + * clear bits in OTG ISP1504 register 'reg' thru VIEWPORT register 'view' + * + * @param bits bits to clear + * @param reg in this register + * @param view the ULPI VIEWPORT register address + */ +static void isp1504_clear(u8 bits, int reg, volatile u32 *view) +{ + __raw_writel((ULPIVW_RUN | ULPIVW_WRITE | + ((reg + ISP1504_REG_CLEAR) << ULPIVW_ADDR_SHIFT) | + ((bits & ULPIVW_WDATA_MASK) << ULPIVW_WDATA_SHIFT)), + view); + + while (__raw_readl(view) & ULPIVW_RUN) /* wait for completion */ + continue; +} + +extern int gpio_usbotg_hs_active(void); + +static void isp1508_fix(u32 *view) +{ + if (!machine_is_mx31_3ds()) + gpio_usbotg_hs_active(); + + /* Set bits IND_PASS_THRU and IND_COMPL */ + isp1504_set(0x60, ISP1504_ITFCTL, view); + + /* Set bit USE_EXT_VBUS_IND */ + isp1504_set(USE_EXT_VBUS_IND, ISP1504_OTGCTL, view); +} + +/*! + * set vbus power + * + * @param view viewport register + * @param on power on or off + */ +static void isp1504_set_vbus_power(struct fsl_xcvr_ops *this, + struct fsl_usb2_platform_data *pdata, int on) +{ + u32 *view = pdata->regs + ULPIVW_OFF; + + pr_debug("real %s(on=%d) view=0x%p\n", __FUNCTION__, on, view); + + pr_debug("ULPI Vendor ID 0x%x Product ID 0x%x\n", + (isp1504_read(ISP1504_VID_HIGH, view) << 8) | + isp1504_read(ISP1504_VID_LOW, view), + (isp1504_read(ISP1504_PID_HIGH, view) << 8) | + isp1504_read(ISP1504_PID_LOW, view)); + + pr_debug("OTG Control before=0x%x\n", + isp1504_read(ISP1504_OTGCTL, view)); + + if (on) { + isp1504_set(DRV_VBUS_EXT | /* enable external Vbus */ + DRV_VBUS | /* enable internal Vbus */ + USE_EXT_VBUS_IND | /* use external indicator */ + CHRG_VBUS, /* charge Vbus */ + ISP1504_OTGCTL, view); + + } else { + isp1508_fix(view); + + isp1504_clear(DRV_VBUS_EXT | /* disable external Vbus */ + DRV_VBUS, /* disable internal Vbus */ + ISP1504_OTGCTL, view); + + isp1504_set(USE_EXT_VBUS_IND | /* use external indicator */ + DISCHRG_VBUS, /* discharge Vbus */ + ISP1504_OTGCTL, view); + } + + pr_debug("OTG Control after = 0x%x\n", + isp1504_read(ISP1504_OTGCTL, view)); +} + +/*! + * set remote wakeup + * + * @param view viewport register + */ +static void isp1504_set_remote_wakeup(u32 * view) +{ + __raw_writel(~ULPIVW_WRITE & __raw_readl(view), view); + __raw_writel((1 << ULPIVW_PORT_SHIFT) | __raw_readl(view), view); + __raw_writel(ULPIVW_RUN | __raw_readl(view), view); + + while (__raw_readl(view) & ULPIVW_RUN) /* wait for completion */ + continue; +} + +static void isp1504_init(struct fsl_xcvr_ops *this) +{ + pr_debug("%s:\n", __FUNCTION__); +} + +static void isp1504_uninit(struct fsl_xcvr_ops *this) +{ + pr_debug("%s:\n", __FUNCTION__); +} + +static void isp1504_suspend(struct fsl_xcvr_ops *this) +{ + pr_debug("%s\n", __func__); + + /* send suspend command */ + isp1504_clear(SUSPENDM, ISP1504_FUNC, &UOG_ULPIVIEW); + pr_debug("%s.\n", __func__); +} + +/*! + * Set the 1504 transceiver to the proper mode for testing purposes. + * + * @param view the ULPI VIEWPORT register address + * @param test_mode Set the 1504 transceiver to disable bit stuffing and NRZI + */ + static void isp1504_set_test_mode(u32 *view, enum usb_test_mode test_mode) +{ + if (test_mode == USB_TEST_J || test_mode == USB_TEST_K) { + printk(KERN_INFO "udc: disable bit stuffing and NRZI\n"); + /* Disable bit-stuffing and NRZI encoding. */ + isp1504_set(0x10, 0x04, view); + } +} + +static struct fsl_xcvr_ops isp1504_ops = { + .name = "isp1504", + .xcvr_type = PORTSC_PTS_ULPI, + .init = isp1504_init, + .uninit = isp1504_uninit, + .suspend = isp1504_suspend, + .set_vbus_power = isp1504_set_vbus_power, + .set_remote_wakeup = isp1504_set_remote_wakeup, + .set_test_mode = isp1504_set_test_mode, +}; + +extern void fsl_usb_xcvr_register(struct fsl_xcvr_ops *xcvr_ops); +extern int fsl_usb_xcvr_suspend(struct fsl_xcvr_ops *xcvr_ops); + +static int __init isp1504xc_init(void) +{ + pr_debug("%s\n", __FUNCTION__); + + fsl_usb_xcvr_register(&isp1504_ops); + + /* suspend isp1504 */ + if (fsl_usb_xcvr_suspend(&isp1504_ops)) + pr_debug("%s: failed to suspend isp1504\n", __func__); + + return 0; +} + +extern void fsl_usb_xcvr_unregister(struct fsl_xcvr_ops *xcvr_ops); + +static void __exit isp1504xc_exit(void) +{ + fsl_usb_xcvr_unregister(&isp1504_ops); +} + +module_init(isp1504xc_init); +module_exit(isp1504xc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("isp1504 xcvr driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-mxc/leds.c b/arch/arm/plat-mxc/leds.c new file mode 100644 index 000000000000..e059d238f82b --- /dev/null +++ b/arch/arm/plat-mxc/leds.c @@ -0,0 +1,111 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * The LED can be used for debugging purpose. To enalbe the LEDs, in the + * config file, select: + * CONFIG_LEDS + * CONFIG_LEDS_TIMER --- enable the OS tick LED once every 50 ticks (.5sec) + * CONFIG_LEDS_CPU --- enable the cpu idle in/out LED (blink fast) + * + * The two LEDs can be disabled through user space by issuing: + * echo "claim" > /sys/devices/system/leds/leds0/event + * To release the LEDs back to the normal operation, do: + * echo "release" > /sys/devices/system/leds/leds0/event + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/ioport.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <asm/leds.h> + +#define LED_STATE_ENABLED (1 << 0) +#define LED_STATE_CLAIMED (1 << 1) + +static unsigned int led_state; +static unsigned int hw_led_state; + +static void mxc_leds_event(led_event_t evt) +{ + unsigned long flags; + + local_irq_save(flags); + + switch (evt) { + case led_start: + hw_led_state = MXC_BD_LED1 | MXC_BD_LED2; + led_state = LED_STATE_ENABLED; + break; + + case led_stop: + case led_halted: + hw_led_state = 0; + led_state &= ~LED_STATE_ENABLED; + MXC_BD_LED_OFF(MXC_BD_LED1 | MXC_BD_LED2); + break; + + case led_claim: + led_state |= LED_STATE_CLAIMED; + hw_led_state = 0; + break; + + case led_release: + led_state &= ~LED_STATE_CLAIMED; + hw_led_state = 0; + break; + +#ifdef CONFIG_LEDS_TIMER + case led_timer: + if (!(led_state & LED_STATE_CLAIMED)) + hw_led_state ^= MXC_BD_LED1; + break; +#endif + +#ifdef CONFIG_LEDS_CPU + case led_idle_start: + if (!(led_state & LED_STATE_CLAIMED)) + hw_led_state &= ~MXC_BD_LED2; + break; + + case led_idle_end: + if (!(led_state & LED_STATE_CLAIMED)) + hw_led_state |= MXC_BD_LED2; + break; +#endif + + default: + break; + } + + if (led_state & LED_STATE_ENABLED) { + MXC_BD_LED_OFF(~hw_led_state); + MXC_BD_LED_ON(hw_led_state); + } + + local_irq_restore(flags); +} + +static int __init mxc_leds_init(void) +{ + led_state = LED_STATE_ENABLED; + leds_event = mxc_leds_event; + return 0; +} + +core_initcall(mxc_leds_init); diff --git a/arch/arm/plat-mxc/mc13783_xc.c b/arch/arm/plat-mxc/mc13783_xc.c new file mode 100644 index 000000000000..3fad32f3b230 --- /dev/null +++ b/arch/arm/plat-mxc/mc13783_xc.c @@ -0,0 +1,299 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/usb/fsl_xcvr.h> +#include <mach/pmic_external.h> +#include <mach/pmic_convity.h> +#include <mach/arc_otg.h> + +/* Events to be passed to the thread */ +#define MC13783_USB_VBUS_ON 0x0001 +#define MC13783_USB_VBUS_OFF 0x0002 +#define MC13783_USB_DETECT_MINI_A 0x0004 +#define MC13783_USB_DETECT_MINI_B 0x0008 + +extern void otg_set_serial_peripheral(void); +extern void otg_set_serial_host(void); + +static unsigned int p_event; +static PMIC_CONVITY_EVENTS g_event; +static PMIC_CONVITY_HANDLE pmic_handle = (PMIC_CONVITY_HANDLE) NULL; + +static void xc_workqueue_handler(struct work_struct *work); + +DECLARE_WORK(xc_work, xc_workqueue_handler); + +DECLARE_MUTEX(pmic_mx); + +static void pmic_event_handler(const PMIC_CONVITY_EVENTS event) +{ + if (event & USB_DETECT_4V4_RISE) + pr_debug("%s: USB_DETECT_4V4_RISE\n", __func__); + + if (event & USB_DETECT_4V4_FALL) + pr_debug("%s: USB_DETECT_4V4_FALL\n", __func__); + + if (event & USB_DETECT_2V0_RISE) + pr_debug("%s: USB_DETECT_2V0_RISE\n", __func__); + + if (event & USB_DETECT_2V0_FALL) + pr_debug("%s: USB_DETECT_2V0_FALL\n", __func__); + + if (event & USB_DETECT_0V8_RISE) + pr_debug("%s: USB_DETECT_0V8_RISE\n", __func__); + + if (event & USB_DETECT_0V8_FALL) + pr_debug("%s: USB_DETECT_0V8_FALL\n", __func__); + + if (event & USB_DETECT_MINI_B) { + pr_debug("%s: USB_DETECT_MINI_B\n", __func__); + otg_set_serial_peripheral(); + g_event = USB_DETECT_MINI_B; + p_event = MC13783_USB_DETECT_MINI_B; + schedule_work(&xc_work); + } + if (event & USB_DETECT_MINI_A) { + pr_debug("%s: USB_DETECT_MINI_A\n", __func__); + otg_set_serial_host(); + g_event = USB_DETECT_MINI_A; + p_event = MC13783_USB_DETECT_MINI_A; + schedule_work(&xc_work); + } + + /* + * Mini-B cable insertion/removal does not generate cable-detect + * event, so we rely on the VBUS changes to identify a mini-b cable + * connect. This logic is only used if mini-b is the first cable that + * is connected after bootup. At all other times, removal of mini-a + * cable is used to initialize peripheral. + */ + if (g_event != USB_DETECT_MINI_A && g_event != USB_DETECT_MINI_B) { + if ((event & USB_DETECT_0V8_RISE) && + (event & USB_DETECT_2V0_RISE) && + (event & USB_DETECT_4V4_RISE)) { + otg_set_serial_peripheral(); + g_event = USB_DETECT_MINI_B; + p_event = MC13783_USB_DETECT_MINI_B; + schedule_work(&xc_work); + } + } +} + +static int usb_pmic_mod_init(void) +{ + PMIC_STATUS rs = PMIC_ERROR; + + init_MUTEX_LOCKED(&pmic_mx); + + rs = pmic_convity_open(&pmic_handle, USB); + if (rs != PMIC_SUCCESS) { + printk(KERN_ERR "pmic_convity_open returned error %d\n", rs); + return rs; + } + + rs = pmic_convity_set_callback(pmic_handle, pmic_event_handler, + USB_DETECT_4V4_RISE | USB_DETECT_4V4_FALL + | USB_DETECT_2V0_RISE | + USB_DETECT_2V0_FALL | USB_DETECT_0V8_RISE + | USB_DETECT_0V8_FALL | USB_DETECT_MINI_A + | USB_DETECT_MINI_B); + + if (rs != PMIC_SUCCESS) { + printk(KERN_ERR + "pmic_convity_set_callback returned error %d\n", rs); + return rs; + } + + return rs; +} + +static void usb_pmic_mod_exit(void) +{ + PMIC_STATUS rs; + + pmic_convity_set_mode(pmic_handle, RS232_1); + pmic_convity_clear_callback(pmic_handle); + + if (pmic_handle != (PMIC_CONVITY_HANDLE) NULL) { + rs = pmic_convity_close(pmic_handle); + if (rs != PMIC_SUCCESS) { + printk(KERN_ERR + "pmic_convity_close() returned error %d", rs); + } else { + pmic_handle = (PMIC_CONVITY_HANDLE) NULL; + } + } +} + +static inline void mc13783_set_host(void) +{ + PMIC_STATUS rs = PMIC_ERROR; + + rs = pmic_convity_usb_otg_clear_config(pmic_handle, USB_OTG_SE0CONN); + rs |= pmic_convity_usb_otg_clear_config(pmic_handle, USB_PU); + + rs |= pmic_convity_usb_otg_set_config(pmic_handle, USB_UDM_PD); + rs |= pmic_convity_usb_otg_set_config(pmic_handle, USB_UDP_PD); + + if (rs != PMIC_SUCCESS) + printk(KERN_ERR "mc13783_set_host failed\n"); + +} + +static inline void mc13783_set_peripheral(void) +{ + PMIC_STATUS rs = PMIC_ERROR; + + rs = pmic_convity_usb_otg_clear_config(pmic_handle, USB_UDM_PD); + rs |= pmic_convity_usb_otg_clear_config(pmic_handle, USB_UDP_PD); + + rs |= pmic_convity_usb_set_speed(pmic_handle, USB_FULL_SPEED); + rs |= pmic_convity_usb_otg_set_config(pmic_handle, USB_OTG_SE0CONN); + rs |= pmic_convity_usb_otg_set_config(pmic_handle, USB_PU); + + if (rs != PMIC_SUCCESS) + printk(KERN_ERR "mc13783_set_peripheral failed\n"); +} + +void mc13783_set_vbus_power(struct fsl_xcvr_ops *this, + struct fsl_usb2_platform_data *pdata, int on) +{ + if (on) { + p_event = MC13783_USB_VBUS_ON; + schedule_work(&xc_work); + } +} + +static struct fsl_xcvr_ops mc13783_ops_otg = { + .name = "mc13783", + .xcvr_type = PORTSC_PTS_SERIAL, + .set_host = mc13783_set_host, + .set_device = mc13783_set_peripheral, + .set_vbus_power = mc13783_set_vbus_power, +}; + +extern void fsl_usb_xcvr_register(struct fsl_xcvr_ops *xcvr_ops); + +static void xc_workqueue_handler(struct work_struct *work) +{ + PMIC_STATUS rs = PMIC_ERROR; + + down(&pmic_mx); + + switch (p_event) { + case MC13783_USB_VBUS_OFF: + mc13783_set_peripheral(); + break; + case MC13783_USB_VBUS_ON: + mc13783_set_host(); + break; + case MC13783_USB_DETECT_MINI_B: + rs = pmic_convity_set_output(pmic_handle, true, false); + rs |= + pmic_convity_usb_otg_clear_config(pmic_handle, + USB_VBUS_CURRENT_LIMIT_LOW_30MS); + + if (rs != PMIC_SUCCESS) + printk(KERN_ERR "MC13783_USB_VBUS_OFF failed\n"); + break; + case MC13783_USB_DETECT_MINI_A: + rs = pmic_convity_set_output(pmic_handle, true, true); + rs |= + pmic_convity_usb_otg_set_config(pmic_handle, + USB_VBUS_CURRENT_LIMIT_LOW_30MS); + + if (rs != PMIC_SUCCESS) + printk(KERN_ERR "MC13783_USB_VBUS_ON failed\n"); + break; + default: + break; + } + up(&pmic_mx); +} + +int mc13783xc_init(void) +{ + PMIC_STATUS rs = PMIC_ERROR; + +#if defined(CONFIG_MXC_USB_SB3) + int xc_mode = USB_SINGLE_ENDED_BIDIR; +#elif defined(CONFIG_MXC_USB_SU6) + int xc_mode = USB_SINGLE_ENDED_UNIDIR; +#elif defined(CONFIG_MXC_USB_DB4) + int xc_mode = USB_DIFFERENTIAL_BIDIR; +#else + int xc_mode = USB_DIFFERENTIAL_UNIDIR; +#endif + + rs = usb_pmic_mod_init(); + if (rs != PMIC_SUCCESS) { + usb_pmic_mod_exit(); + printk(KERN_ERR "usb_pmic_mod_init failed\n"); + return rs; + } + + rs = pmic_convity_usb_set_xcvr(pmic_handle, xc_mode); + rs |= pmic_convity_usb_otg_set_config(pmic_handle, USB_OTG_SE0CONN); + rs |= pmic_convity_usb_otg_set_config(pmic_handle, USBXCVREN); + rs |= pmic_convity_set_output(pmic_handle, false, true); + + rs |= pmic_convity_usb_otg_set_config(pmic_handle, USB_PULL_OVERRIDE); + rs |= pmic_convity_usb_otg_clear_config(pmic_handle, USB_USBCNTRL); + rs |= pmic_convity_usb_otg_clear_config(pmic_handle, USB_DP150K_PU); + + if (rs != PMIC_SUCCESS) + printk(KERN_ERR "pmic configuration failed\n"); + + fsl_usb_xcvr_register(&mc13783_ops_otg); + + mc13783_set_peripheral(); + + return rs; +} + +extern void fsl_usb_xcvr_unregister(struct fsl_xcvr_ops *xcvr_ops); + +void mc13783xc_uninit(void) +{ + /* Clear stuff from init */ + pmic_convity_usb_otg_clear_config(pmic_handle, USB_OTG_SE0CONN); + pmic_convity_usb_otg_clear_config(pmic_handle, USBXCVREN); + pmic_convity_set_output(pmic_handle, false, false); + pmic_convity_usb_otg_clear_config(pmic_handle, USB_PULL_OVERRIDE); + + /* Clear host mode */ + pmic_convity_usb_otg_clear_config(pmic_handle, USB_UDP_PD); + pmic_convity_usb_otg_clear_config(pmic_handle, USB_UDM_PD); + + /* Clear peripheral mode */ + pmic_convity_usb_otg_clear_config(pmic_handle, USB_PU); + + /* Vbus off */ + pmic_convity_set_output(pmic_handle, true, false); + pmic_convity_usb_otg_clear_config(pmic_handle, + USB_VBUS_CURRENT_LIMIT_LOW_30MS); + + usb_pmic_mod_exit(); + + fsl_usb_xcvr_unregister(&mc13783_ops_otg); +} + +module_init(mc13783xc_init); +module_exit(mc13783xc_uninit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("mc13783xc"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-mxc/pwm.c b/arch/arm/plat-mxc/pwm.c index ae34198a79dd..1c663acd89fa 100644 --- a/arch/arm/plat-mxc/pwm.c +++ b/arch/arm/plat-mxc/pwm.c @@ -6,6 +6,7 @@ * published by the Free Software Foundation. * * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com> + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. */ #include <linux/module.h> @@ -34,7 +35,12 @@ #define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16) #define MX3_PWMCR_EN (1 << 0) - +#define MX3_PWMCR_STOPEN (1 << 25) +#define MX3_PWMCR_DOZEEN (1 << 24) +#define MX3_PWMCR_WAITEN (1 << 23) +#define MX3_PWMCR_DBGEN (1 << 22) +#define MX3_PWMCR_CLKSRC_IPG (1 << 16) +#define MX3_PWMCR_CLKSRC_IPG_32k (3 << 16) struct pwm_device { struct list_head node; @@ -55,7 +61,7 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) if (pwm == NULL || period_ns == 0 || duty_ns > period_ns) return -EINVAL; - if (cpu_is_mx27() || cpu_is_mx3()) { + if (cpu_is_mx27() || cpu_is_mx3() || cpu_is_mx51()) { unsigned long long c; unsigned long period_cycles, duty_cycles, prescale; c = clk_get_rate(pwm->clk); @@ -73,7 +79,9 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) writel(duty_cycles, pwm->mmio_base + MX3_PWMSAR); writel(period_cycles, pwm->mmio_base + MX3_PWMPR); writel(MX3_PWMCR_PRESCALER(prescale - 1) | - MX3_PWMCR_CLKSRC_IPG_HIGH | MX3_PWMCR_EN, + MX3_PWMCR_CLKSRC_IPG_HIGH | + MX3_PWMCR_STOPEN | MX3_PWMCR_DOZEEN | + MX3_PWMCR_WAITEN | MX3_PWMCR_DBGEN, pwm->mmio_base + MX3_PWMCR); } else if (cpu_is_mx1() || cpu_is_mx21()) { /* The PWM subsystem allows for exact frequencies. However, @@ -105,6 +113,7 @@ EXPORT_SYMBOL(pwm_config); int pwm_enable(struct pwm_device *pwm) { + unsigned long reg; int rc = 0; if (!pwm->clk_enabled) { @@ -112,16 +121,27 @@ int pwm_enable(struct pwm_device *pwm) if (!rc) pwm->clk_enabled = 1; } + + reg = readl(pwm->mmio_base + MX3_PWMCR); + reg |= MX3_PWMCR_EN; + writel(reg, pwm->mmio_base + MX3_PWMCR); return rc; } EXPORT_SYMBOL(pwm_enable); void pwm_disable(struct pwm_device *pwm) { + unsigned long reg; + if (pwm->clk_enabled) { clk_disable(pwm->clk); pwm->clk_enabled = 0; } + + reg = readl(pwm->mmio_base + MX3_PWMCR); + reg &= ~MX3_PWMCR_EN; + writel(reg, pwm->mmio_base + MX3_PWMCR); + } EXPORT_SYMBOL(pwm_disable); diff --git a/arch/arm/plat-mxc/sdma/Makefile b/arch/arm/plat-mxc/sdma/Makefile new file mode 100644 index 000000000000..59f94b2da7d0 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/Makefile @@ -0,0 +1,18 @@ +ifneq ($(KBUILD_SRC),) +ccflags-y += -I$(KBUILD_SRC)/arch/arm/plat-mxc/sdma/iapi/include \ + -I$(KBUILD_SRC)/include/linux \ + -DMCU -DOS=LINUX \ + -DL_I_T_T_L_E_ENDIAN=0 -DB_I_G_ENDIAN=1 \ + -DENDIANNESS=L_I_T_T_L_E_ENDIAN +else +ccflags-y += -Iarch/arm/plat-mxc/sdma/iapi/include \ + -Iinclude/linux \ + -DMCU -DOS=LINUX \ + -DL_I_T_T_L_E_ENDIAN=0 -DB_I_G_ENDIAN=1 \ + -DENDIANNESS=L_I_T_T_L_E_ENDIAN +endif + +obj-y += dma_sdma.o +obj-$(CONFIG_MXC_SDMA_API) += sdma.o +obj-$(CONFIG_MXC_SDMA_API) += iapi/ +obj-$(CONFIG_MXC_SDMA_API) += sdma_malloc.o diff --git a/arch/arm/plat-mxc/sdma/dma_sdma.c b/arch/arm/plat-mxc/sdma/dma_sdma.c new file mode 100644 index 000000000000..5ca75d08bd08 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/dma_sdma.c @@ -0,0 +1,697 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file plat-mxc/sdma/dma_sdma.c + * @brief Front-end to the DMA handling. This handles the allocation/freeing + * of DMA channels, and provides a unified interface to the machines + * DMA facilities. This file contains functions for Smart DMA. + * + * @ingroup SDMA + */ + +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <mach/dma.h> +#include <mach/hardware.h> + +#ifdef CONFIG_MXC_SDMA_API + +static mxc_dma_channel_t mxc_sdma_channels[MAX_DMA_CHANNELS]; +static mxc_dma_channel_private_t mxc_sdma_private[MAX_DMA_CHANNELS]; + +extern struct clk *mxc_sdma_ahb_clk, *mxc_sdma_ipg_clk; + +/*! + * Tasket to handle processing the channel buffers + * + * @param arg channel id + */ +static void mxc_sdma_channeltasklet(unsigned long arg) +{ + dma_request_t request_t; + dma_channel_params chnl_param; + mxc_dma_channel_t *chnl_info; + mxc_dma_channel_private_t *data_priv; + int bd_intr = 0, error = MXC_DMA_DONE; + + chnl_info = &mxc_sdma_channels[arg]; + data_priv = chnl_info->private; + chnl_param = + mxc_sdma_get_channel_params(chnl_info->channel)->chnl_params; + + mxc_dma_get_config(arg, &request_t, data_priv->buf_tail); + + while (request_t.bd_done == 0) { + bd_intr = mxc_dma_get_bd_intr(arg, data_priv->buf_tail); + if ((data_priv->buf_tail += 1) >= chnl_param.bd_number) { + data_priv->buf_tail = 0; + } + chnl_info->active = 0; + if (request_t.bd_error) { + error = MXC_DMA_TRANSFER_ERROR; + } + + if (bd_intr != 0) { + chnl_info->cb_fn(chnl_info->cb_args, error, + request_t.count); + error = MXC_DMA_DONE; + } + + if (data_priv->buf_tail == chnl_info->curr_buf) { + break; + } + memset(&request_t, 0, sizeof(dma_request_t)); + mxc_dma_get_config(arg, &request_t, data_priv->buf_tail); + } +} + +/*! + * This function is generally called by the driver at open time. + * The DMA driver would do any initialization steps that is required + * to get the channel ready for data transfer. + * + * @param channel_id a pre-defined id. The peripheral driver would specify + * the id associated with its peripheral. This would be + * used by the DMA driver to identify the peripheral + * requesting DMA and do the necessary setup on the + * channel associated with the particular peripheral. + * The DMA driver could use static or dynamic DMA channel + * allocation. + * @param dev_name module name or device name + * @return returns a negative number on error if request for a DMA channel did not + * succeed, returns the channel number to be used on success. + */ +int mxc_dma_request_ext(mxc_dma_device_t channel_id, char *dev_name, + struct dma_channel_info *info) +{ + mxc_sdma_channel_params_t *chnl; + mxc_dma_channel_private_t *data_priv; + int ret = 0, i = 0, channel_num = 0; + mxc_sdma_channel_ext_params_t *p; + + chnl = mxc_sdma_get_channel_params(channel_id); + if (chnl == NULL) { + return -EINVAL; + } + + if (info) { + if (!chnl->chnl_params.ext) + return -EINVAL; + p = (mxc_sdma_channel_ext_params_t *)chnl; + memcpy(&p->chnl_ext_params.info, info, sizeof(info)); + } + + + /* Enable the SDMA clock */ + clk_enable(mxc_sdma_ahb_clk); + clk_enable(mxc_sdma_ipg_clk); + + channel_num = chnl->channel_num; + if (chnl->channel_num == MXC_DMA_DYNAMIC_CHANNEL) { + /* Get the first free channel */ + for (i = (MAX_DMA_CHANNELS - 1); i > 0; i--) { + /* See if channel is available */ + if ((mxc_sdma_channels[i].dynamic != 1) + || (mxc_sdma_channels[i].lock != 0)) { + continue; + } + channel_num = i; + /* Check to see if we can get this channel */ + ret = mxc_request_dma(&channel_num, dev_name); + if (ret == 0) { + break; + } else { + continue; + } + } + if (ret != 0) { + /* No free channel */ + goto err_ret; + } + } else { + if (mxc_sdma_channels[chnl->channel_num].lock == 1) { + ret = -ENODEV; + goto err_ret; + } + ret = mxc_request_dma(&channel_num, dev_name); + if (ret != 0) { + goto err_ret; + } + } + + ret = mxc_dma_setup_channel(channel_num, &chnl->chnl_params); + + if (ret == 0) { + if (chnl->chnl_priority != MXC_SDMA_DEFAULT_PRIORITY) { + ret = + mxc_dma_set_channel_priority(channel_num, + chnl->chnl_priority); + if (ret != 0) { + pr_info("Failed to set channel prority,\ + continue with the existing \ + priority\n"); + goto err_ret; + } + } + mxc_sdma_channels[channel_num].lock = 1; + if ((chnl->chnl_params.transfer_type == per_2_emi) + || (chnl->chnl_params.transfer_type == dsp_2_emi)) { + mxc_sdma_channels[channel_num].mode = MXC_DMA_MODE_READ; + } else { + mxc_sdma_channels[channel_num].mode = + MXC_DMA_MODE_WRITE; + } + mxc_sdma_channels[channel_num].channel = channel_id; + data_priv = mxc_sdma_channels[channel_num].private; + tasklet_init(&data_priv->chnl_tasklet, + mxc_sdma_channeltasklet, channel_num); + if ((channel_id == MXC_DMA_ATA_RX) + || (channel_id == MXC_DMA_ATA_TX)) { + data_priv->intr_after_every_bd = 0; + } else { + data_priv->intr_after_every_bd = 1; + } + } + err_ret: + if (ret != 0) { + clk_disable(mxc_sdma_ahb_clk); + clk_disable(mxc_sdma_ipg_clk); + channel_num = -ENODEV; + } + + return channel_num; +} + +/*! + * This function is generally called by the driver at close time. The DMA + * driver would do any cleanup associated with this channel. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @return returns a negative number on error or 0 on success + */ +int mxc_dma_free(int channel_num) +{ + mxc_dma_channel_private_t *data_priv; + + if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) { + return -EINVAL; + } + + if (mxc_sdma_channels[channel_num].lock != 1) { + return -ENODEV; + } + + mxc_free_dma(channel_num); + + /* Disable the SDMA clock */ + clk_disable(mxc_sdma_ahb_clk); + clk_disable(mxc_sdma_ipg_clk); + + mxc_sdma_channels[channel_num].lock = 0; + mxc_sdma_channels[channel_num].active = 0; + mxc_sdma_channels[channel_num].curr_buf = 0; + data_priv = mxc_sdma_channels[channel_num].private; + data_priv->buf_tail = 0; + tasklet_kill(&data_priv->chnl_tasklet); + + return 0; +} + +/*! + * Callback function called from the SDMA Interrupt routine + * + * @param arg driver specific argument that was registered + */ +static void mxc_dma_chnl_callback(void *arg) +{ + int priv; + mxc_dma_channel_private_t *data_priv; + + priv = (int)arg; + data_priv = mxc_sdma_channels[priv].private; + /* Process the buffers in a tasklet */ + tasklet_schedule(&data_priv->chnl_tasklet); +} + +/*! + * This function would just configure the buffers specified by the user into + * dma channel. The caller must call mxc_dma_enable to start this transfer. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @param dma_buf an array of physical addresses to the user defined + * buffers. The caller must guarantee the dma_buf is + * available until the transfer is completed. + * @param num_buf number of buffers in the array + * @param mode specifies whether this is READ or WRITE operation + * @return This function returns a negative number on error if buffer could not be + * added with DMA for transfer. On Success, it returns 0 + */ +int mxc_dma_config(int channel_num, mxc_dma_requestbuf_t * dma_buf, + int num_buf, mxc_dma_mode_t mode) +{ + int ret = 0, i = 0, prev_buf; + mxc_dma_channel_t *chnl_info; + mxc_dma_channel_private_t *data_priv; + mxc_sdma_channel_params_t *chnl; + dma_channel_params chnl_param; + dma_request_t request_t; + + if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) { + return -EINVAL; + } + + if (num_buf <= 0) { + return -EINVAL; + } + + chnl_info = &mxc_sdma_channels[channel_num]; + data_priv = chnl_info->private; + if (chnl_info->lock != 1) { + return -ENODEV; + } + + /* Check to see if all buffers are taken */ + if (chnl_info->active == 1) { + return -EBUSY; + } + + chnl = mxc_sdma_get_channel_params(chnl_info->channel); + chnl_param = chnl->chnl_params; + + /* Re-setup the SDMA channel if the transfer direction is changed */ + if ((chnl_param.peripheral_type != MEMORY) && (mode != chnl_info->mode)) { + if (chnl_param.peripheral_type == DSP) { + if (mode == MXC_DMA_MODE_READ) { + chnl_param.transfer_type = dsp_2_emi; + } else { + chnl_param.transfer_type = emi_2_dsp; + } + } else if (chnl_param.peripheral_type == FIFO_MEMORY) { + if (mode == MXC_DMA_MODE_READ) + chnl_param.per_address = MXC_FIFO_MEM_SRC_FIXED; + else + chnl_param.per_address = + MXC_FIFO_MEM_DEST_FIXED; + } else { + if (mode == MXC_DMA_MODE_READ) { + chnl_param.transfer_type = per_2_emi; + } else { + chnl_param.transfer_type = emi_2_per; + } + } + chnl_param.callback = mxc_dma_chnl_callback; + chnl_param.arg = (void *)channel_num; + ret = mxc_dma_setup_channel(channel_num, &chnl_param); + if (ret != 0) { + return ret; + } + if (chnl->chnl_priority != MXC_SDMA_DEFAULT_PRIORITY) { + ret = + mxc_dma_set_channel_priority(channel_num, + chnl->chnl_priority); + if (ret != 0) { + pr_info("Failed to set channel prority,\ + continue with the existing \ + priority\n"); + } + } + chnl_info->mode = mode; + } + + for (i = 0; i < num_buf; i++, dma_buf++) { + /* Check to see if all buffers are taken */ + if (chnl_info->active == 1) { + break; + } + request_t.destAddr = (__u8 *) dma_buf->dst_addr; + request_t.sourceAddr = (__u8 *) dma_buf->src_addr; + if (chnl_param.peripheral_type == ASRC) + request_t.count = dma_buf->num_of_bytes / 4; + else + request_t.count = dma_buf->num_of_bytes; + request_t.bd_cont = 1; + ret = mxc_dma_set_config(channel_num, &request_t, + chnl_info->curr_buf); + if (ret != 0) { + break; + } + if (data_priv->intr_after_every_bd == 0) { + if (i == num_buf - 1) { + mxc_dma_set_bd_intr(channel_num, + chnl_info->curr_buf, 1); + } else { + mxc_dma_set_bd_intr(channel_num, + chnl_info->curr_buf, 0); + } + } + + prev_buf = chnl_info->curr_buf; + if ((chnl_info->curr_buf += 1) >= chnl_param.bd_number) { + chnl_info->curr_buf = 0; + } + if (chnl_info->curr_buf == data_priv->buf_tail) { + if ((data_priv->intr_after_every_bd == 0) + && (i != num_buf - 1)) { + /* + * Set the BD_INTR flag on the last BD that + * was queued + */ + mxc_dma_set_bd_intr(channel_num, prev_buf, 1); + } + chnl_info->active = 1; + } + } + + if (i == 0) { + return -EBUSY; + } + return 0; +} + +/*! + * This function would just configure the scatterlist specified by the + * user into dma channel. This is a slight variation of mxc_dma_config(), + * it is provided for the convenience of drivers that have a scatterlist + * passed into them. It is the calling driver's responsibility to have the + * correct physical address filled in the "dma_address" field of the + * scatterlist. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @param sg a scatterlist of buffers. The caller must guarantee + * the dma_buf is available until the transfer is + * completed. + * @param num_buf number of buffers in the array + * @param num_of_bytes total number of bytes to transfer. If set to 0, this + * would imply to use the length field of the scatterlist + * for each DMA transfer. Else it would calculate the size + * for each DMA transfer. + * @param mode specifies whether this is READ or WRITE operation + * @return This function returns a negative number on error if buffer could not + * be added with DMA for transfer. On Success, it returns 0 + */ +int mxc_dma_sg_config(int channel_num, struct scatterlist *sg, + int num_buf, int num_of_bytes, mxc_dma_mode_t mode) +{ + int ret = 0, i = 0; + mxc_dma_requestbuf_t *dma_buf; + + if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) { + return -EINVAL; + } + + if (mxc_sdma_channels[channel_num].lock != 1) { + return -ENODEV; + } + + dma_buf = + (mxc_dma_requestbuf_t *) kmalloc(num_buf * + sizeof(mxc_dma_requestbuf_t), + GFP_KERNEL); + + if (dma_buf == NULL) { + return -EFAULT; + } + + for (i = 0; i < num_buf; i++) { + if (mode == MXC_DMA_MODE_READ) { + (dma_buf + i)->dst_addr = sg->dma_address; + } else { + (dma_buf + i)->src_addr = sg->dma_address; + } + + if ((num_of_bytes > sg->length) || (num_of_bytes == 0)) { + (dma_buf + i)->num_of_bytes = sg->length; + } else { + (dma_buf + i)->num_of_bytes = num_of_bytes; + } + sg++; + num_of_bytes -= (dma_buf + i)->num_of_bytes; + } + + ret = mxc_dma_config(channel_num, dma_buf, num_buf, mode); + kfree(dma_buf); + return ret; +} + +/*! + * This function is provided if the driver would like to set/change its + * callback function. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @param callback a callback function to provide notification on transfer + * completion, user could specify NULL if he does not wish + * to be notified + * @param arg an argument that gets passed in to the callback + * function, used by the user to do any driver specific + * operations. + * @return this function returns a negative number on error if the callback + * could not be set for the channel or 0 on success + */ +int mxc_dma_callback_set(int channel_num, + mxc_dma_callback_t callback, void *arg) +{ + if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) { + return -EINVAL; + } + + if (mxc_sdma_channels[channel_num].lock != 1) { + return -ENODEV; + } + + mxc_sdma_channels[channel_num].cb_fn = callback; + mxc_sdma_channels[channel_num].cb_args = arg; + + mxc_dma_set_callback(channel_num, mxc_dma_chnl_callback, + (void *)channel_num); + + return 0; +} + +/*! + * This stops the DMA channel and any ongoing transfers. Subsequent use of + * mxc_dma_enable() will restart the channel and restart the transfer. + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @return returns a negative number on error or 0 on success + */ +int mxc_dma_disable(int channel_num) +{ + if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) { + return -EINVAL; + } + + if (mxc_sdma_channels[channel_num].lock != 1) { + return -ENODEV; + } + + mxc_dma_stop(channel_num); + return 0; +} + +/*! + * This starts DMA transfer. Or it restarts DMA on a stopped channel + * previously stopped with mxc_dma_disable(). + * + * @param channel_num the channel number returned at request time. This + * would be used by the DMA driver to identify the calling + * driver and do the necessary cleanup on the channel + * associated with the particular peripheral + * @return returns a negative number on error or 0 on success + */ +int mxc_dma_enable(int channel_num) +{ + if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) { + return -EINVAL; + } + + if (mxc_sdma_channels[channel_num].lock != 1) { + return -ENODEV; + } + + mxc_dma_start(channel_num); + return 0; +} + +/*! + * Initializes dma structure with dma_operations + * + * @param dma dma structure + * @return returns 0 on success + */ +static int __init mxc_dma_init(void) +{ + int i; + for (i = 0; i < MAX_DMA_CHANNELS; i++) { + mxc_sdma_channels[i].active = 0; + mxc_sdma_channels[i].lock = 0; + mxc_sdma_channels[i].curr_buf = 0; + mxc_sdma_channels[i].dynamic = 1; + mxc_sdma_private[i].buf_tail = 0; + mxc_sdma_channels[i].private = &mxc_sdma_private[i]; + } + /* + * Make statically allocated channels unavailable for dynamic channel + * requests + */ + mxc_get_static_channels(mxc_sdma_channels); + + return 0; +} + +arch_initcall(mxc_dma_init); + +#else +int mxc_request_dma(int *channel, const char *devicename) +{ + return -ENODEV; +} + +int mxc_dma_setup_channel(int channel, dma_channel_params * p) +{ + return -ENODEV; +} + +int mxc_dma_set_channel_priority(unsigned int channel, unsigned int priority) +{ + return -ENODEV; +} + +int mxc_dma_set_config(int channel, dma_request_t * p, int bd_index) +{ + return -ENODEV; +} + +int mxc_dma_get_config(int channel, dma_request_t * p, int bd_index) +{ + return -ENODEV; +} + +int mxc_dma_start(int channel) +{ + return -ENODEV; +} + +int mxc_dma_stop(int channel) +{ + return -ENODEV; +} + +void mxc_free_dma(int channel) +{ +} + +void mxc_dma_set_callback(int channel, dma_callback_t callback, void *arg) +{ +} + +void *sdma_malloc(size_t size) +{ + return 0; +} + +void sdma_free(void *buf) +{ +} + +void *sdma_phys_to_virt(unsigned long buf) +{ + return 0; +} + +unsigned long sdma_virt_to_phys(void *buf) +{ + return 0; +} + +int mxc_dma_request(mxc_dma_device_t channel_id, char *dev_name) +{ + return -ENODEV; +} + +int mxc_dma_free(int channel_num) +{ + return -ENODEV; +} + +int mxc_dma_config(int channel_num, mxc_dma_requestbuf_t * dma_buf, + int num_buf, mxc_dma_mode_t mode) +{ + return -ENODEV; +} + +int mxc_dma_sg_config(int channel_num, struct scatterlist *sg, + int num_buf, int num_of_bytes, mxc_dma_mode_t mode) +{ + return -ENODEV; +} + +int mxc_dma_callback_set(int channel_num, mxc_dma_callback_t callback, + void *arg) +{ + return -ENODEV; +} + +int mxc_dma_disable(int channel_num) +{ + return -ENODEV; +} + +int mxc_dma_enable(int channel_num) +{ + return -ENODEV; +} + +EXPORT_SYMBOL(mxc_request_dma); +EXPORT_SYMBOL(mxc_dma_setup_channel); +EXPORT_SYMBOL(mxc_dma_set_channel_priority); +EXPORT_SYMBOL(mxc_dma_set_config); +EXPORT_SYMBOL(mxc_dma_get_config); +EXPORT_SYMBOL(mxc_dma_start); +EXPORT_SYMBOL(mxc_dma_stop); +EXPORT_SYMBOL(mxc_free_dma); +EXPORT_SYMBOL(mxc_dma_set_callback); +EXPORT_SYMBOL(sdma_malloc); +EXPORT_SYMBOL(sdma_free); +EXPORT_SYMBOL(sdma_phys_to_virt); +EXPORT_SYMBOL(sdma_virt_to_phys); + +#endif + +EXPORT_SYMBOL(mxc_dma_request_ext); +EXPORT_SYMBOL(mxc_dma_free); +EXPORT_SYMBOL(mxc_dma_config); +EXPORT_SYMBOL(mxc_dma_sg_config); +EXPORT_SYMBOL(mxc_dma_callback_set); +EXPORT_SYMBOL(mxc_dma_disable); +EXPORT_SYMBOL(mxc_dma_enable); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Linux SDMA API"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-mxc/sdma/iapi/Makefile b/arch/arm/plat-mxc/sdma/iapi/Makefile new file mode 100644 index 000000000000..b6a5d6aebda0 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for I.API sources. +# + +obj-y := src/
\ No newline at end of file diff --git a/arch/arm/plat-mxc/sdma/iapi/include/epm.h b/arch/arm/plat-mxc/sdma/iapi/include/epm.h new file mode 100644 index 000000000000..a12fff923a15 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/include/epm.h @@ -0,0 +1,187 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ASM_ARCH_MXC_SDMA_REGS_H__ +#define __ASM_ARCH_MXC_SDMA_REGS_H__ + +#include <mach/hardware.h> + +/* SDMA Reg definition */ +#define SDMA_BASE_IO_ADDR IO_ADDRESS(SDMA_BASE_ADDR) + +#define SDMA_H_C0PTR *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x000)) +#define SDMA_H_INTR *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x004)) +#define SDMA_H_STATSTOP *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x008)) +#define SDMA_H_START *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x00C)) +#define SDMA_H_EVTOVR *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x010)) +#define SDMA_H_DSPOVR *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x014)) +#define SDMA_H_HOSTOVR *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x018)) +#define SDMA_H_EVTPEND *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x01C)) +#define SDMA_H_DSPENBL *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x020)) +#define SDMA_H_RESET *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x024)) +#define SDMA_H_EVTERR *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x028)) +#define SDMA_H_INTRMSK *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x02C)) +#define SDMA_H_PSW *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x030)) +#define SDMA_H_EVTERRDBG *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x034)) +#define SDMA_H_CONFIG *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x038)) +#define SDMA_ONCE_ENB *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x040)) +#define SDMA_ONCE_DATA *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x044)) +#define SDMA_ONCE_INSTR *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x048)) +#define SDMA_ONCE_STAT *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x04C)) +#define SDMA_ONCE_CMD *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x050)) +#define SDMA_EVT_MIRROR *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x054)) +#define SDMA_ILLINSTADDR *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x058)) +#define SDMA_CHN0ADDR *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x05C)) +#define SDMA_ONCE_RTB *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x060)) +#define SDMA_XTRIG_CONF1 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x070)) +#define SDMA_XTRIG_CONF2 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x074)) + +#ifdef MXC_SDMA_V2 +#define SDMA_CHNENBL_0 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x200)) +#define SDMA_CHNENBL_1 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x204)) +#define SDMA_CHNENBL_2 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x208)) +#define SDMA_CHNENBL_3 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x20C)) +#define SDMA_CHNENBL_4 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x210)) +#define SDMA_CHNENBL_5 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x214)) +#define SDMA_CHNENBL_6 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x218)) +#define SDMA_CHNENBL_7 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x21C)) +#define SDMA_CHNENBL_8 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x220)) +#define SDMA_CHNENBL_9 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x224)) +#define SDMA_CHNENBL_10 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x228)) +#define SDMA_CHNENBL_11 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x22C)) +#define SDMA_CHNENBL_12 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x230)) +#define SDMA_CHNENBL_13 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x234)) +#define SDMA_CHNENBL_14 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x238)) +#define SDMA_CHNENBL_15 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x23C)) +#define SDMA_CHNENBL_16 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x240)) +#define SDMA_CHNENBL_17 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x244)) +#define SDMA_CHNENBL_18 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x248)) +#define SDMA_CHNENBL_19 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x24C)) +#define SDMA_CHNENBL_20 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x250)) +#define SDMA_CHNENBL_21 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x254)) +#define SDMA_CHNENBL_22 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x258)) +#define SDMA_CHNENBL_23 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x25C)) +#define SDMA_CHNENBL_24 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x260)) +#define SDMA_CHNENBL_25 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x264)) +#define SDMA_CHNENBL_26 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x268)) +#define SDMA_CHNENBL_27 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x26C)) +#define SDMA_CHNENBL_28 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x270)) +#define SDMA_CHNENBL_29 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x274)) +#define SDMA_CHNENBL_30 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x278)) +#define SDMA_CHNENBL_31 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x27C)) +#define SDMA_CHNENBL_32 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x280)) +#define SDMA_CHNENBL_33 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x284)) +#define SDMA_CHNENBL_34 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x288)) +#define SDMA_CHNENBL_35 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x28C)) +#define SDMA_CHNENBL_36 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x290)) +#define SDMA_CHNENBL_37 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x294)) +#define SDMA_CHNENBL_38 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x298)) +#define SDMA_CHNENBL_39 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x29C)) +#define SDMA_CHNENBL_40 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x2A0)) +#define SDMA_CHNENBL_41 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x2A4)) +#define SDMA_CHNENBL_42 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x2A8)) +#define SDMA_CHNENBL_43 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x2AC)) +#define SDMA_CHNENBL_44 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x2B0)) +#define SDMA_CHNENBL_45 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x2B4)) +#define SDMA_CHNENBL_46 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x2B8)) +#define SDMA_CHNENBL_47 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x2BC)) + +#define SDMA_ONCE_COUNT *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x300)) +#define SDMA_ONCE_ECTL *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x304)) +#define SDMA_ONCE_EAA *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x308)) +#define SDMA_ONCE_EAB *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x30C)) +#define SDMA_ONCE_EAM *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x310)) +#define SDMA_ONCE_ED *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x314)) +#define SDMA_ONCE_EDM *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x318)) +#define SDMA_ONCE_PCMATCH *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x31C)) + +#else + +#define SDMA_CHNENBL_0 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x080)) +#define SDMA_CHNENBL_1 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x084)) +#define SDMA_CHNENBL_2 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x088)) +#define SDMA_CHNENBL_3 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x08C)) +#define SDMA_CHNENBL_4 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x090)) +#define SDMA_CHNENBL_5 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x094)) +#define SDMA_CHNENBL_6 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x098)) +#define SDMA_CHNENBL_7 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x09C)) +#define SDMA_CHNENBL_8 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0A0)) +#define SDMA_CHNENBL_9 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0A4)) +#define SDMA_CHNENBL_10 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0A8)) +#define SDMA_CHNENBL_11 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0AC)) +#define SDMA_CHNENBL_12 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0B0)) +#define SDMA_CHNENBL_13 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0B4)) +#define SDMA_CHNENBL_14 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0B8)) +#define SDMA_CHNENBL_15 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0BC)) +#define SDMA_CHNENBL_16 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0C0)) +#define SDMA_CHNENBL_17 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0C4)) +#define SDMA_CHNENBL_18 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0C8)) +#define SDMA_CHNENBL_19 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0CC)) +#define SDMA_CHNENBL_20 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0D0)) +#define SDMA_CHNENBL_21 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0D4)) +#define SDMA_CHNENBL_22 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0D8)) +#define SDMA_CHNENBL_23 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0DC)) +#define SDMA_CHNENBL_24 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0E0)) +#define SDMA_CHNENBL_25 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0E4)) +#define SDMA_CHNENBL_26 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0E8)) +#define SDMA_CHNENBL_27 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0EC)) +#define SDMA_CHNENBL_28 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0F0)) +#define SDMA_CHNENBL_29 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0F4)) +#define SDMA_CHNENBL_30 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0F8)) +#define SDMA_CHNENBL_31 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x0FC)) + +#define SDMA_ONCE_COUNT *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x200)) +#define SDMA_ONCE_ECTL *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x204)) +#define SDMA_ONCE_EAA *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x208)) +#define SDMA_ONCE_EAB *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x20C)) +#define SDMA_ONCE_EAM *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x210)) +#define SDMA_ONCE_ED *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x214)) +#define SDMA_ONCE_EDM *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x218)) +#define SDMA_ONCE_PCMATCH *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x21C)) + +#endif /* MXC_SDMA_V2 */ + +#define SDMA_CHNPRI_0 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x100)) +#define SDMA_CHNPRI_1 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x104)) +#define SDMA_CHNPRI_2 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x108)) +#define SDMA_CHNPRI_3 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x10C)) +#define SDMA_CHNPRI_4 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x110)) +#define SDMA_CHNPRI_5 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x114)) +#define SDMA_CHNPRI_6 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x118)) +#define SDMA_CHNPRI_7 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x11C)) +#define SDMA_CHNPRI_8 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x120)) +#define SDMA_CHNPRI_9 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x124)) +#define SDMA_CHNPRI_10 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x128)) +#define SDMA_CHNPRI_11 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x12C)) +#define SDMA_CHNPRI_12 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x130)) +#define SDMA_CHNPRI_13 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x134)) +#define SDMA_CHNPRI_14 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x138)) +#define SDMA_CHNPRI_15 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x13C)) +#define SDMA_CHNPRI_16 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x140)) +#define SDMA_CHNPRI_17 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x144)) +#define SDMA_CHNPRI_18 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x148)) +#define SDMA_CHNPRI_19 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x14C)) +#define SDMA_CHNPRI_20 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x150)) +#define SDMA_CHNPRI_21 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x154)) +#define SDMA_CHNPRI_22 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x158)) +#define SDMA_CHNPRI_23 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x15C)) +#define SDMA_CHNPRI_24 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x160)) +#define SDMA_CHNPRI_25 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x164)) +#define SDMA_CHNPRI_26 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x168)) +#define SDMA_CHNPRI_27 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x16C)) +#define SDMA_CHNPRI_28 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x170)) +#define SDMA_CHNPRI_29 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x174)) +#define SDMA_CHNPRI_30 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x178)) +#define SDMA_CHNPRI_31 *((volatile unsigned long *)(SDMA_BASE_IO_ADDR + 0x17C)) + +#endif /* _mcuEpm_h */ diff --git a/arch/arm/plat-mxc/sdma/iapi/include/iapi.h b/arch/arm/plat-mxc/sdma/iapi/include/iapi.h new file mode 100644 index 000000000000..d7300218057b --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/include/iapi.h @@ -0,0 +1,49 @@ +/****************************************************************************** + * + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapi.h + * + * $Id iapi.h $ + * + * Description: + * Unique include for the whole IAPI library. + * + * + * http//compass.mot.com/go/115342679 + * + * $Log iapi.h $ + * + * ***************************************************************************/ + +#ifndef _iapi_h +#define _iapi_h + +/* **************************************************************************** + * Include File Section + * ***************************************************************************/ + +#include "sdmaStruct.h" +#include "iapiDefaults.h" +#include "iapiLow.h" +#include "iapiMiddle.h" +#include "iapiHigh.h" + +#ifdef MCU +#include "iapiLowMcu.h" +#include "iapiMiddleMcu.h" +#endif /* MCU */ + + + +#endif /* _iapi_h */ diff --git a/arch/arm/plat-mxc/sdma/iapi/include/iapiDefaults.h b/arch/arm/plat-mxc/sdma/iapi/include/iapiDefaults.h new file mode 100644 index 000000000000..b03a53ae1893 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/include/iapiDefaults.h @@ -0,0 +1,128 @@ +/****************************************************************************** + * + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiDefaults.h + * + * $Id iapiDefaults.h $ + * + * Description: + * This library is written in C to guarantee functionality and integrity in + * the usage of SDMA virtual DMA channels. This API (Application Programming + * Interface) allow SDMA channels' access in an OPEN, READ, WRITE, CLOSE + * fashion. + * + * + * + * + * $Log iapiDefaults.h $ + * + *****************************************************************************/ + + +#ifndef _iapi_defaults_h +#define _iapi_defaults_h + +/****************************************************************************** + * Include File Section + *****************************************************************************/ +#include "epm.h" +#include "sdmaStruct.h" + +/* **************************************************************************** + * Macro-command Section + * ***************************************************************************/ + +/** + * Error codes + * lower 5 bits free to include channel number when available + * and bit number 6 must be set when channel number is available + * + * Note : + * 1) Abbreviations / naming convention : + * - BD : Buffer Descriptor + * - CC : Channel Context + * - CCB : Channel Control Block + * - CD : Channel Descriptor + * - B : Buffer + * - CH : Channel + * + */ +#define IAPI_SUCCESS 0 +#define IAPI_FAILURE -1 +#define IAPI_ERR_CH_AVAILABLE 0x00020 +#define IAPI_ERR_NO_ERROR 0x00000 +#define IAPI_ERR_NO_CCB_DEFINED 0x01000 +#define IAPI_ERR_BD_UNINITIALIZED 0x02000 +#define IAPI_ERR_BD_ALLOCATED 0x03000 +#define IAPI_ERR_BD_ALLOCATION 0x04000 +#define IAPI_ERR_CCB_ALLOC_FAILED 0x05000 +#define IAPI_ERR_CCB_UNINITIALIZED 0x06000 +#define IAPI_ERR_CC_ALREADY_DEFINED 0x07000 +#define IAPI_ERR_CC_ALLOC_FAILED 0x08000 +#define IAPI_ERR_CD_ALREADY_DEFINED 0x09000 +#define IAPI_ERR_CD_ALLOC_FAILED 0x0A000 +#define IAPI_ERR_CD_CHANGE_CH_NUMBER 0x0B000 +#define IAPI_ERR_CD_CHANGE_CCB_PTR 0x0C000 +#define IAPI_ERR_CD_CHANGE_UNKNOWN 0x0D000 +#define IAPI_ERR_CD_CHANGE 0x0E000 +#define IAPI_ERR_CD_UNINITIALIZED 0x0F000 +#define IAPI_ERR_CLOSE 0x10000 +#define IAPI_ERR_B_ALLOC_FAILED 0x11000 +#define IAPI_ERR_CONFIG_OVERRIDE 0x12000 +#define IAPI_ERR_CH_IN_USE 0x13000 +#define IAPI_ERR_CALLBACKSYNCH_UNKNOWN 0x14000 +#define IAPI_ERR_INVALID_PARAMETER 0x15000 +#define IAPI_ERR_TRUST 0x16000 +#define IAPI_ERR_CHANNEL_UNINITIALIZED 0x17000 +#define IAPI_ERR_RROR_BIT_READ 0x18000 +#define IAPI_ERR_RROR_BIT_WRITE 0x19000 +#define IAPI_ERR_NOT_ALLOWED 0x1A000 +#define IAPI_ERR_NO_OS_FN 0x1B000 + + +/* + * Global Variable Section + */ + +/* + * Table to hold pointers to the callback functions registered by the users of + *I.API + */ +extern void (* callbackIsrTable[CH_NUM])(channelDescriptor * cd_p, void * arg); + +/* + * Table to hold user registered data pointers, to be privided in the callback + *function + */ +extern void * userArgTable[CH_NUM]; + +/* channelDescriptor data structure filled with default data*/ +extern channelDescriptor iapi_ChannelDefaults; + +/* Global variable to hold the last error encountered in I.API operations*/ +extern unsigned int iapi_errno; + +/* Used in synchronization, to mark started channels*/ +extern volatile unsigned long iapi_SDMAIntr; + +/* Hold a pointer to the start of the CCB array, to be used in the IRQ routine + *to find the channel descriptor for the channed sending the interrupt to the + *core. + */ +extern channelControlBlock * iapi_CCBHead; + +/* configs_data structure filled with default data*/ +extern configs_data iapi_ConfigDefaults; + +#endif /* iapiDefaults_h */ diff --git a/arch/arm/plat-mxc/sdma/iapi/include/iapiHigh.h b/arch/arm/plat-mxc/sdma/iapi/include/iapiHigh.h new file mode 100644 index 000000000000..14cfae539eb8 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/include/iapiHigh.h @@ -0,0 +1,136 @@ +/****************************************************************************** + * + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiHigh.h + * + * $Id iapiHigh.h $ + * + * Description: + * prototypes for high level function of I.API + * + * + * http://venerque.sps.mot.com/pjt/sfs/www/iapi/softsim_api.pdf + * + * $Log iapiHigh.h + * + * ***************************************************************************/ + +#ifndef _iapiHigh_h +#define _iapiHigh_h + +/* **************************************************************************** + * Include File Section + *****************************************************************************/ +#include "sdmaStruct.h" + +/* **************************************************************************** + * Macro-command Section + *****************************************************************************/ +enum { + IAPI_CHANGE_CHANDESC, + IAPI_CHANGE_BDNUM, + IAPI_CHANGE_BUFFSIZE, + IAPI_CHANGE_CHANBLOCK, + IAPI_CHANGE_INSTANCE, + IAPI_CHANGE_OWNERSHIP, + IAPI_CHANGE_SYNCH, + IAPI_CHANGE_TRUST, + IAPI_CHANGE_CALLBACKFUNC, + IAPI_CHANGE_CHANCCB, + IAPI_CHANGE_PRIORITY, + IAPI_CHANGE_BDWRAP, + IAPI_CHANGE_WATERMARK, + IAPI_CHANGE_SET_BDCONT, + IAPI_CHANGE_UNSET_BDCONT, + IAPI_CHANGE_SET_BDEXTD, + IAPI_CHANGE_UNSET_BDEXTD, + IAPI_CHANGE_EVTMASK1, + IAPI_CHANGE_EVTMASK2, + IAPI_CHANGE_PERIPHADDR, + IAPI_CHANGE_SET_BDINTR, + IAPI_CHANGE_UNSET_BDINTR, + IAPI_CHANGE_SET_TRANSFER_CD, + IAPI_CHANGE_FORCE_CLOSE, + IAPI_CHANGE_SET_TRANSFER, + IAPI_CHANGE_USER_ARG, + IAPI_CHANGE_SET_BUFFERADDR, + IAPI_CHANGE_SET_EXTDBUFFERADDR, + IAPI_CHANGE_SET_COMMAND, + IAPI_CHANGE_SET_COUNT, + IAPI_CHANGE_SET_STATUS, + IAPI_CHANGE_GET_BUFFERADDR, + IAPI_CHANGE_GET_EXTDBUFFERADDR, + IAPI_CHANGE_GET_COMMAND, + IAPI_CHANGE_GET_COUNT, + IAPI_CHANGE_GET_STATUS, + IAPI_CHANGE_SET_ENDIANNESS +}; + + +/* + * Public Function Prototype Section + */ +int iapi_Open ( channelDescriptor * cd_p, unsigned char channelNumber ); +int iapi_Close( channelDescriptor * cd_p ); +int iapi_Read ( channelDescriptor * cd_p, void * buf, unsigned short nbyte ); +int iapi_Write( channelDescriptor * cd_p, void * buf, unsigned short nbyte ); +int iapi_MemCopy(channelDescriptor * cd_p, void* dest, void* src, + unsigned long size); +int iapi_IoCtl( channelDescriptor * cd_p, unsigned long ctlRequest, + unsigned long param); + + +int iapi_Read_ipcv2( channelDescriptor * cd_p, void * data_control_struct_ipcv2); + +int iapi_Write_ipcv2( channelDescriptor * cd_p, void * data_control_struct_ipcv2); + +#ifdef MCU +int iapi_Init(channelDescriptor * cd_p, configs_data * config_p, + unsigned short* ram_image, unsigned short code_size, + unsigned long start_addr); +#endif /* MCU */ +#ifdef DSP +int iapi_Init(channelDescriptor * cd_p); +#endif /* DSP */ + +int iapi_StartChannel(unsigned char channel); +int iapi_StopChannel(unsigned char channel); +int iapi_SynchChannel(unsigned char channel); + +int iapi_GetChannelNumber(channelDescriptor * cd_p); +unsigned long iapi_GetError(channelDescriptor * cd_p); +int iapi_GetCount(channelDescriptor * cd_p); +int iapi_GetCountAll(channelDescriptor * cd_p); + +#ifndef IRQ_KEYWORD +#define IRQ_KEYWORD +#endif /* IRQ_KEYWORD */ + +IRQ_KEYWORD void IRQ_Handler(void); + +#ifdef MCU +int iapi_GetScript(channelDescriptor * cd_p, void * buf, unsigned short size, + unsigned long address); +int iapi_GetContext(channelDescriptor * cd_p, void * buf, + unsigned char channel); +int iapi_SetScript(channelDescriptor * cd_p, void * buf, unsigned short nbyte, + unsigned long destAddr); +int iapi_SetContext(channelDescriptor * cd_p, void * buf, + unsigned char channel); +int iapi_AssignScript(channelDescriptor * cd_p, script_data * data_p); + +int iapi_SetChannelEventMapping(unsigned char event, unsigned long channel_map); +#endif /* MCU */ + +#endif /* _iapiHigh_h */ diff --git a/arch/arm/plat-mxc/sdma/iapi/include/iapiLow.h b/arch/arm/plat-mxc/sdma/iapi/include/iapiLow.h new file mode 100644 index 000000000000..43aff7a4e903 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/include/iapiLow.h @@ -0,0 +1,78 @@ +/****************************************************************************** + * + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiLow.h + * + * $Id iapiLow.h $ + * + * Description: + * prototypes for low level function of I.API + * + * + * + * + * $Log iapiLow.h + * + * ***************************************************************************/ + +#ifndef _iapiLow_h +#define _iapiLow_h + +/* **************************************************************************** + * Boolean identifiers + *****************************************************************************/ +#define NO_OS 0 +#define LINUX 1 +#define SYMBIAN 2 +#define WINCE 3 + +/* **************************************************************************** + * Include File Section + *****************************************************************************/ +#include "sdmaStruct.h" +#include "iapiDefaults.h" +#include "iapiOS.h" +#ifdef MCU +#include "iapiLowMcu.h" +#endif /*MCU*/ +#if OS == NO_OS +#include <stdlib.h> +#elif OS == LINUX +#include <linux/types.h> +#endif + + +/* **************************************************************************** + * Macro-command Section + *****************************************************************************/ + +#define GOTO_SLEEP(x) (iapi_GotoSleep)(x) +#define INIT_SLEEP(x) (iapi_InitSleep)(x) + +/* **************************************************************************** + * Public Function Prototype Section + *****************************************************************************/ +void iapi_lowStartChannel ( unsigned char channel ); +void iapi_lowStopChannel ( unsigned char channel ); +void iapi_AttachCallbackISR (channelDescriptor * cd_p, + void (* func_p)(channelDescriptor * cd_p, void * arg)); +void iapi_DetachCallbackISR (channelDescriptor * cd_p); +void iapi_ChangeCallbackISR (channelDescriptor * cd_p, + void (* func_p)(channelDescriptor * cd_p, void * arg)); +void iapi_lowSynchChannel ( unsigned char channel ); +void iapi_SetBufferDescriptor(bufferDescriptor *bd_p, unsigned char command, + unsigned char status, unsigned short count, + void * buffAddr, void * extBufferAddr); + +#endif /* _iapiLow_h */ diff --git a/arch/arm/plat-mxc/sdma/iapi/include/iapiLowDsp.h b/arch/arm/plat-mxc/sdma/iapi/include/iapiLowDsp.h new file mode 100644 index 000000000000..0adaa2548b9e --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/include/iapiLowDsp.h @@ -0,0 +1,50 @@ +/* + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* **************************************************************************** + * + * File: iapiLowDsp.h + * + * $Id iapiLowDsp.h $ + * + * Description: + * prototypes for low level function of I.API for DSP side only + * + * + * + * + * $Log iapiLowDsp.h + * + * ***************************************************************************/ + +#ifndef _iapiLowDsp_h +#define _iapiLowDsp_h + +/* **************************************************************************** + * Include File Section + *****************************************************************************/ + + +/* **************************************************************************** + * Public Function Prototype Section + *****************************************************************************/ +/* WARNING !!!!! + * This file is empty and it is normal, because there is no low level functions + * dedicated to the DSP but the file (iapi_LowDsp.h) must still exist because + * some project directly links the file. Previously, there were function + * iapi_EnableInterrupts,iapi_DisableInterrupts,iapi_WaitCore,iapi_StartChannel + * iapi_StopChannel but they are common to both MCU and DSP, so they have been + * moved to iapi_Low.h file. + */ + +#endif /* _iapiLowDsp_h */ diff --git a/arch/arm/plat-mxc/sdma/iapi/include/iapiLowMcu.h b/arch/arm/plat-mxc/sdma/iapi/include/iapiLowMcu.h new file mode 100644 index 000000000000..12bea564c116 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/include/iapiLowMcu.h @@ -0,0 +1,60 @@ +/****************************************************************************** + * + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiLowMcu.h + * + * $Id iapiLowMcu.h $ + * + * Description: + * prototypes for low level function of I.API of MCU side only + * + * + * + * + * $Log iapiLowMcu.h $ + * + * ***************************************************************************/ + +#ifndef _iapiLowMcu_h +#define _iapiLowMcu_h + +/****************************************************************************** + * Include File Section + *****************************************************************************/ +#include "sdmaStruct.h" +#include "iapiDefaults.h" + +/* **************************************************************************** + * Public Function Prototype Section + * ***************************************************************************/ + + +void iapi_InitChannelTables ( void ); +int iapi_ChannelConfig(unsigned char channel, unsigned eventOverride, + unsigned mcuOverride, unsigned dspOverride); +int iapi_Channel0Command(channelDescriptor * cd_p, void * buf, + unsigned short nbyte, unsigned char command); +void iapi_lowGetScript(channelDescriptor * cd_p, void * buf, unsigned short size, + unsigned long address); +void iapi_lowGetContext(channelDescriptor * cd_p, void * buf, + unsigned char channel); +void iapi_lowSetScript(channelDescriptor * cd_p, void * buf, unsigned short nbyte, + unsigned long destAddr); +void iapi_lowSetContext(channelDescriptor * cd_p, void * buf, + unsigned char channel); +int iapi_lowAssignScript(channelDescriptor * cd_p, script_data * data_p); + +int iapi_lowSetChannelEventMapping(unsigned char event, unsigned long channel_map); + +#endif /* _iapiLowMcu_h */ diff --git a/arch/arm/plat-mxc/sdma/iapi/include/iapiMiddle.h b/arch/arm/plat-mxc/sdma/iapi/include/iapiMiddle.h new file mode 100644 index 000000000000..2470ffcaabf1 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/include/iapiMiddle.h @@ -0,0 +1,52 @@ +/****************************************************************************** + * + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiMiddle.h + * + * $Id iapiMiddle.h $ + * + * Description: + * prototypes for middle level function of I.API + * + * + * + * + * $Log iapiMiddle.h + * + * ***************************************************************************/ + +#ifndef _iapiMiddle_h +#define _iapiMiddle_h + +/* **************************************************************************** + * Include File Section + ******************************************************************************/ +#include "sdmaStruct.h" +#ifdef MCU +#include "iapiMiddleMcu.h" +#endif /* MCU */ + +/* **************************************************************************** + * Public Function Prototype Section + ******************************************************************************/ +bufferDescriptor * iapi_AllocBD (channelControlBlock * ccb_p); +int iapi_AllocContext(contextData ** ctxd_p, unsigned char channel); +int iapi_AllocChannelDesc(channelDescriptor ** cd_p, unsigned char channel); +int iapi_ChangeChannelDesc (channelDescriptor * cd_p, + unsigned char whatToChange, unsigned long newval); +void iapi_InitializeCallbackISR (void (* func_p)(channelDescriptor * cd_p, + void * arg)); +int iapi_InitializeMemory (channelControlBlock * ccb_p); + +#endif /* iapiMiddle_h */ diff --git a/arch/arm/plat-mxc/sdma/iapi/include/iapiMiddleMcu.h b/arch/arm/plat-mxc/sdma/iapi/include/iapiMiddleMcu.h new file mode 100644 index 000000000000..a47c02d4440b --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/include/iapiMiddleMcu.h @@ -0,0 +1,41 @@ +/****************************************************************************** + * + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiMiddleMcu.h + * + * $Id iapiMiddleMcu.h $ + * + * Description: + * prototypes for middle level function of I.API + * + * + * + * + * $Log iapiMiddleMcu.h + * + * ***************************************************************************/ + +#ifndef _iapiMiddleMcu_h +#define _iapiMiddleMcu_h + +/* **************************************************************************** + * Include File Section + ******************************************************************************/ +#include "sdmaStruct.h" + +/* **************************************************************************** + * Public Function Prototype Section + ******************************************************************************/ + +#endif /* iapiMiddleMcu_h */ diff --git a/arch/arm/plat-mxc/sdma/iapi/include/iapiOS.h b/arch/arm/plat-mxc/sdma/iapi/include/iapiOS.h new file mode 100644 index 000000000000..17186dae0fd5 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/include/iapiOS.h @@ -0,0 +1,96 @@ +/****************************************************************************** + * + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiOS.h + * + * $Id iapiOS.h $ + * + * Description: + * prototypes for OS level function of I.API + * + * + * + * + * $Log iapiOS.h + * + * ***************************************************************************/ + +#ifndef _iapiOS_h +#define _iapiOS_h + +/* **************************************************************************** + * Boolean identifiers + *****************************************************************************/ +#define NO_OS 0 +#define LINUX 1 +#define SYMBIAN 2 +#define WINCE 3 + +/* **************************************************************************** + * Include File Section + *****************************************************************************/ +#include "sdmaStruct.h" +#include "iapiDefaults.h" +#ifdef MCU +#include "iapiLowMcu.h" +#endif /*MCU*/ +#if OS == NO_OS +#include <stdlib.h> +#elif OS == LINUX +#include <linux/types.h> +#endif + +/* **************************************************************************** + * Macro-command Section + *****************************************************************************/ +#define SDMA_ERAM 0 +#define SDMA_IRAM 1 +#ifdef CONFIG_SDMA_IRAM +#define MALLOC(x, s) (s == SDMA_ERAM)? (* iapi_Malloc)(x):(* iapi_iram_Malloc)(x) +#else /*CONFIG_SDMA_IRAM */ +#define MALLOC(x, s) (* iapi_Malloc)(x) +#endif /*CONFIG_SDMA_IRAM */ +#define FREE(x) if (x!=NULL) (* iapi_Free)(x) + +#define GOTO_SLEEP(x) (iapi_GotoSleep)(x) +#define INIT_SLEEP(x) (iapi_InitSleep)(x) + +/* **************************************************************************** + * Public Function Prototype Section + *****************************************************************************/ + +#ifdef CONFIG_SDMA_IRAM +extern void*(* iapi_iram_Malloc) (size_t size); +#endif /*CONFIG_SDMA_IRAM*/ + +extern void*(* iapi_Malloc) (size_t size); +extern void (* iapi_Free) (void * ptr); + +extern void*(* iapi_Virt2Phys) (void * ptr); +extern void*(* iapi_Phys2Virt) (void * ptr); + +extern void (* iapi_WakeUp)(int); +extern void (* iapi_GotoSleep)(int); +extern void (* iapi_InitSleep)(int); + +extern void*(* iapi_memcpy)(void *dest, const void *src, size_t count); +extern void*(* iapi_memset)(void *dest, int c, size_t count); + +extern void (* iapi_EnableInterrupts)(void); +extern void (* iapi_DisableInterrupts)(void); + +extern int (* iapi_GetChannel)(int); +extern int (* iapi_ReleaseChannel)(int); + +#endif /* _iapiOS_h */ diff --git a/arch/arm/plat-mxc/sdma/iapi/include/sdmaStruct.h b/arch/arm/plat-mxc/sdma/iapi/include/sdmaStruct.h new file mode 100644 index 000000000000..bc42dabba554 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/include/sdmaStruct.h @@ -0,0 +1,426 @@ +/****************************************************************************** + * + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: sdmaStruct.h + * + * $Id sdmaStruct.h $ + * + * Description: provides necessary definitions and inclusion for ipcmStruct.c + * + * $Log $ + * + *****************************************************************************/ +#ifndef _sdmaStruct_h +#define _sdmaStruct_h + +/* **************************************************************************** + * Include File Section + ******************************************************************************/ + +/* **************************************************************************** + * Macro-command Section + ******************************************************************************/ + +/** + * Identifier NULL + */ +#ifndef NULL +#define NULL 0 +#endif + +/** + * Boolean identifiers + */ +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +/** + * Number of channels + */ +#define CH_NUM 32 +/** + * Number of events + */ +#ifdef MXC_SDMA_V2 +#define EVENTS_NUM 48 +#else +#define EVENTS_NUM 32 +#endif +/** + * Channel configuration + */ +#define DONT_OWN_CHANNEL 0 +#define OWN_CHANNEL 1 + +/** + * Ownership (value defined to computed decimal value) + */ +#define CH_OWNSHP_OFFSET_EVT 0 +#define CH_OWNSHP_OFFSET_MCU 1 +#define CH_OWNSHP_OFFSET_DSP 2 +/** + * Indexof the greg which holds address to start a script from when channel + * becomes current. + */ +#define SDMA_NUMBER_GREGS 8 + +/** + * Channel contexts management + */ + +#define CHANNEL_CONTEXT_BASE_ADDRESS 0x800 +/** + * Buffer descriptor status values. + */ +#define BD_DONE 0x01 +#define BD_WRAP 0x02 +#define BD_CONT 0x04 +#define BD_INTR 0x08 +#define BD_RROR 0x10 +#define BD_LAST 0x20 +#define BD_EXTD 0x80 + + +/** + * Data Node descriptor status values. + */ +#define DND_END_OF_FRAME 0x80 +#define DND_END_OF_XFER 0x40 +#define DND_DONE 0x20 +#define DND_UNUSED 0x01 + +/** + * IPCV2 descriptor status values. + */ +#define BD_IPCV2_END_OF_FRAME 0x40 + + +#define IPCV2_MAX_NODES 50 +/** + * Error bit set in the CCB status field by the SDMA, + * in setbd routine, in case of a transfer error + */ +#define DATA_ERROR 0x10000000 + +/** + * Buffer descriptor commands. + */ +#define C0_ADDR 0x01 +#define C0_LOAD 0x02 +#define C0_DUMP 0x03 +#define C0_SETCTX 0x07 +#define C0_GETCTX 0x03 +#define C0_SETDM 0x01 +#define C0_SETPM 0x04 +#define C0_GETDM 0x02 +#define C0_GETPM 0x08 +/** + * Transfer types, encoded in the BD command field + */ +#define TRANSFER_32BIT 0x00 +#define TRANSFER_8BIT 0x01 +#define TRANSFER_16BIT 0x02 +#define TRANSFER_24BIT 0x03 +/** + * Change endianness indicator in the BD command field + */ +#define CHANGE_ENDIANNESS 0x80 +/** + * Size in bytes + */ +#define SDMA_BD_SIZE 8 +#define SDMA_EXTENDED_BD_SIZE 12 +#define BD_NUMBER 4 +/** + * Channel interrupt policy + */ +#define DEFAULT_POLL 0 +#define CALLBACK_ISR 1 +/** + * Channel status + */ +#define UNINITIALIZED 0 +#define INITIALIZED 1 + +/** + * IoCtl particular values + */ +#define SET_BIT_ALL 0xFFFFFFFF +#define BD_NUM_OFFSET 16 +#define BD_NUM_MASK 0xFFFF0000 + +/** + * Maximum values for IoCtl calls, used in high or middle level calls + */ +#define MAX_BD_NUM 256 +#define MAX_BD_SIZE 65536 +#define MAX_BLOCKING 2 +#define MAX_SYNCH 2 +#define MAX_OWNERSHIP 8 +#define MAX_CH_PRIORITY 8 +#define MAX_TRUST 2 +#define MAX_WML 256 + + +/** + * Access to channelDescriptor fields + */ +enum { + IAPI_CHANNELNUMBER, + IAPI_BUFFERDESCNUMBER, + IAPI_BUFFERSIZE, + IAPI_BLOCKING, + IAPI_CALLBACKSYNCH, + IAPI_OWNERSHIP, + IAPI_PRIORITY, + IAPI_TRUST, + IAPI_UNUSED, + IAPI_CALLBACKISR_PTR, + IAPI_CCB_PTR, + IAPI_BDWRAP, + IAPI_WML +}; + +/** + * Default values for channel descriptor - nobody ownes the channel + */ +#define CD_DEFAULT_OWNERSHIP 7 + + +/** + * User Type Section + */ + +/** + * Command/Mode/Count of buffer descriptors + */ + +#if (ENDIANNESS==B_I_G_ENDIAN) +typedef struct iapi_modeCount_ipcv2 { + unsigned long status : 8; /**< L, E , D bits stored here */ + unsigned long reserved : 8; + unsigned long count : 16; /**< <size of the buffer pointed by this BD */ +} modeCount_ipcv2; +#else +typedef struct iapi_modeCount_ipcv2 { + unsigned long count : 16; /**<size of the buffer pointed by this BD */ + unsigned long reserved : 8; /**Reserved*/ + unsigned long status : 8; /**< L, E , D bits stored here */ +} modeCount_ipcv2; +#endif +/** + * Data Node descriptor - IPCv2 + * (differentiated between evolutions of SDMA) + */ +typedef struct iapi_dataNodeDescriptor { + modeCount_ipcv2 mode; /**<command, status and count */ + void * bufferAddr; /**<address of the buffer described */ +} dataNodeDescriptor; + +#if (ENDIANNESS==B_I_G_ENDIAN) +typedef struct iapi_modeCount_ipcv1_v2 { + unsigned long endianness: 1; + unsigned long reserved: 7; + unsigned long status : 8; /**< E,R,I,C,W,D status bits stored here */ + unsigned long count : 16; /**< size of the buffer pointed by this BD */ +} modeCount_ipcv1_v2; +#else +typedef struct iapi_modeCount_ipcv1_v2 { + unsigned long count : 16; /**<size of the buffer pointed by this BD */ + unsigned long status : 8; /**< E,R,I,C,W,D status bits stored here */ + unsigned long reserved: 7; + unsigned long endianness: 1; +} modeCount_ipcv1_v2; +#endif +/** + * Buffer descriptor + * (differentiated between evolutions of SDMA) + */ +typedef struct iapi_bufferDescriptor_ipcv1_v2 { + modeCount_ipcv1_v2 mode; /**<command, status and count */ + void * bufferAddr; /**<address of the buffer described */ + void * extBufferAddr; /**<extended buffer address */ +} bufferDescriptor_ipcv1_v2; + + +/** + * Mode/Count of data node descriptors - IPCv2 + */ + +#if (ENDIANNESS==B_I_G_ENDIAN) +typedef struct iapi_modeCount { + unsigned long command : 8; /**< command mostlky used for channel 0 */ + unsigned long status : 8; /**< E,R,I,C,W,D status bits stored here */ + unsigned long count : 16; /**< size of the buffer pointed by this BD */ +} modeCount; +#else +typedef struct iapi_modeCount { + unsigned long count : 16; /**<size of the buffer pointed by this BD */ + unsigned long status : 8; /**< E,R,I,C,W,D status bits stored here */ + unsigned long command : 8; /**< command mostlky used for channel 0 */ +} modeCount; +#endif + + +/** + * Buffer descriptor + * (differentiated between evolutions of SDMA) + */ +typedef struct iapi_bufferDescriptor { + modeCount mode; /**<command, status and count */ + void * bufferAddr; /**<address of the buffer described */ + void * extBufferAddr; /**<extended buffer address */ +} bufferDescriptor; + + + +struct iapi_channelControlBlock; +struct iapi_channelDescriptor; +/** + * Channel Descriptor + */ +typedef struct iapi_channelDescriptor { + unsigned char channelNumber ;/**<stores the channel number */ + unsigned char bufferDescNumber;/**<number of BD's automatically allocated for this channel */ + unsigned short bufferSize ;/**<size (in bytes) of buffer descriptors */ + unsigned long blocking :3;/**<blocking / non blocking feature selection */ + unsigned long callbackSynch :1;/**<polling/ callback method selection */ + unsigned long ownership :3;/**<ownership of the channel (host+dedicated+event)*/ + unsigned long priority :3;/**<reflects the SDMA channel priority register */ + unsigned long trust :1;/**<trusted buffers or kernel allocated */ + unsigned long useDataSize :1;/**<indicates if the dataSize field is meaningfull */ + unsigned long dataSize :2;/**<data transfer size - 8,16,24 or 32 bits*/ + unsigned long forceClose :1;/**<if TRUE, close channel even with BD owned by SDMA*/ + unsigned long scriptId :7;/**<number of the script */ + unsigned long watermarkLevel:10;/**<Watermark level for the peripheral access*/ + unsigned long eventMask1; /**<First Event mask */ + unsigned long eventMask2; /**<Second Event mask */ + unsigned long peripheralAddr; /**<Address of the peripheral or its fifo when needed */ + void (* callbackISR_ptr)(struct iapi_channelDescriptor*, void*); /**<pointer to the callback function (or NULL) */ + struct iapi_channelControlBlock * ccb_ptr; /**<pointer to the channel control block associated to this channel */ +} channelDescriptor; + +/** + * Channel Status + */ +typedef struct iapi_channelStatus { + unsigned long unused :29;/**<*/ + unsigned long openedInit : 1;/**<channel is initialized or not */ + unsigned long stateDirection: 1;/**<sdma is reading/writing (as seen from channel owner core) */ + unsigned long execute : 1;/**<channel is being processed (started) or not */ +} channelStatus; + +/** + * Channel control Block + */ +typedef struct iapi_channelControlBlock { + bufferDescriptor * currentBDptr; /**<current buffer descriptor processed */ + bufferDescriptor * baseBDptr; /**<first element of buffer descriptor array */ + channelDescriptor * channelDescriptor; /**<pointer to the channel descriptor */ + channelStatus status; /**<open/close ; started/stopped ; read/write */ +} channelControlBlock; + +/** + * Context structure. + */ +#if (ENDIANNESS==B_I_G_ENDIAN) +typedef struct iapi_stateRegisters { + unsigned long sf : 1;/**<source falut while loading data */ + unsigned long unused0: 1;/**<*/ + unsigned long rpc :14;/**<return program counter */ + unsigned long t : 1;/**<test bit:status of arithmetic & test instruction*/ + unsigned long unused1: 1;/**<*/ + unsigned long pc :14;/**<program counter */ + unsigned long lm : 2;/**<loop mode */ + unsigned long epc :14;/**<loop end program counter */ + unsigned long df : 1;/**<destiantion falut while storing data */ + unsigned long unused2: 1;/**<*/ + unsigned long spc :14;/**<loop start program counter */ +} stateRegiters; +#else +typedef struct iapi_stateRegisters { + unsigned long pc :14;/**<program counter */ + unsigned long unused1: 1;/**<*/ + unsigned long t : 1;/**<test bit: status of arithmetic & test instruction*/ + unsigned long rpc :14;/**<return program counter */ + unsigned long unused0: 1;/**<*/ + unsigned long sf : 1;/**<source falut while loading data */ + unsigned long spc :14;/**<loop start program counter */ + unsigned long unused2: 1;/**<*/ + unsigned long df : 1;/**<destiantion falut while storing data */ + unsigned long epc :14;/**<loop end program counter */ + unsigned long lm : 2;/**<loop mode */ +} stateRegiters; +#endif + +/** + * This is SDMA version of SDMA + */ +typedef struct iapi_contextData { + stateRegiters channelState; /**<channel state bits */ + unsigned long gReg[ SDMA_NUMBER_GREGS ]; /**<general registers */ + unsigned long mda; /**<burst dma destination address register */ + unsigned long msa; /**<burst dma source address register */ + unsigned long ms; /**<burst dma status register */ + unsigned long md; /**<burst dma data register */ + unsigned long pda; /**<peripheral dma destination address register */ + unsigned long psa; /**<peripheral dma source address register */ + unsigned long ps; /**<peripheral dma status register */ + unsigned long pd; /**<peripheral dma data register */ + unsigned long ca; /**<CRC polynomial register */ + unsigned long cs; /**<CRC accumulator register */ + unsigned long dda; /**<dedicated core destination address register */ + unsigned long dsa; /**<dedicated core source address register */ + unsigned long ds; /**<dedicated core status register */ + unsigned long dd; /**<dedicated core data register */ + unsigned long scratch0; /**<scratch */ + unsigned long scratch1; /**<scratch */ + unsigned long scratch2; /**<scratch */ + unsigned long scratch3; /**<scratch */ + unsigned long scratch4; /**<scratch */ + unsigned long scratch5; /**<scratch */ + unsigned long scratch6; /**<scratch */ + unsigned long scratch7; /**<scratch */ + +} contextData; + +/** + *This structure holds the necessary data for the assignment in the + * dynamic channel-script association + */ +typedef struct iapi_script_data { + unsigned short load_address;/**<start address of the script*/ + unsigned long wml; /**<parameters for the channel descriptor*/ + unsigned long shp_addr; /**<shared peripheral base address*/ + unsigned long per_addr; /**<peripheral base address for p2p source*/ + unsigned long event_mask1; /**<First Event mask */ + unsigned long event_mask2; /**<Second Event mask */ +} script_data; + +/** + *This structure holds the the useful bits of the CONFIG register + */ +typedef struct iapi_configs_data { + unsigned long dspdma :1; /*indicates if the DSPDMA is used */ + unsigned long rtdobs :1; /*indicates if Real-Time Debug pins are enabled */ + unsigned long acr :1; /**indicates if AHB freq /core freq = 2 or 1 */ + unsigned long csm :2; /**indicates which context switch mode is selected*/ +} configs_data; + +#endif /* _sdmaStruct_h */ diff --git a/arch/arm/plat-mxc/sdma/iapi/src/Makefile b/arch/arm/plat-mxc/sdma/iapi/src/Makefile new file mode 100644 index 000000000000..4c112bf22384 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/src/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for I.API sources. +# +ifneq ($(KBUILD_SRC),) +ccflags-y += -I$(KBUILD_SRC)/arch/arm/plat-mxc/sdma/iapi/include \ + -I$(KBUILD_SRC)/include/linux \ + -DMCU -DOS=LINUX \ + -DL_I_T_T_L_E_ENDIAN=0 -DB_I_G_ENDIAN=1 \ + -DENDIANNESS=L_I_T_T_L_E_ENDIAN +else +ccflags-y += -Iarch/arm/plat-mxc/sdma/iapi/include \ + -Iinclude/linux \ + -DMCU -DOS=LINUX \ + -DL_I_T_T_L_E_ENDIAN=0 -DB_I_G_ENDIAN=1 \ + -DENDIANNESS=L_I_T_T_L_E_ENDIAN +endif + +obj-y += iapiLow.o iapiLowMcu.o iapiMiddle.o iapiMiddleMcu.o iapiHigh.o iapiDefaults.o iapiOS.o diff --git a/arch/arm/plat-mxc/sdma/iapi/src/iapiDefaults.c b/arch/arm/plat-mxc/sdma/iapi/src/iapiDefaults.c new file mode 100644 index 000000000000..345c240cb15c --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/src/iapiDefaults.c @@ -0,0 +1,110 @@ +/****************************************************************************** + * + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiDefaults.c + * + * $Id iapiDefaults.c $ + * + * Description: + * This library is written in C to guarantee functionality and integrity in + * the usage of SDMA virtual DMA channels. This API (Application Programming + * Interface) allow SDMA channels' access in an OPEN, READ, WRITE, CLOSE + * fashion. + * + * Usage: + * + * Files: + * + * +* / + * + * $Log iapiDefaults.c $ + * + *****************************************************************************/ + +/* **************************************************************************** + * Include File Section + ******************************************************************************/ +#include "iapiDefaults.h" + +/* **************************************************************************** + * Global Variable Section + ******************************************************************************/ + +/** + * @brief System Call-back ISRs Table + */ +void (* callbackIsrTable[CH_NUM])(channelDescriptor* cd_p, void* arg); + +/** + * @brief User registered pointers table + */ +void * userArgTable[CH_NUM]; + +/** + * @brief Pointer to the first CCB in the CCB array + */ +channelControlBlock * iapi_CCBHead = NULL; + + +/**Default channel description. + * + * Initialization values are:\n + * - channelNumber = 0 + * - bufferDescNumber = 1 + * - bufferSize = 8 + * - blocking = 0 + * - callbackSynch = DEFAULT_POLL + * - ownership = CD_DEFAULT_OWNERSHIP + * - priority = 1 + * - trust = TRUE + * - useDataSize = 0 + * - dataSize = 0 + * - forceClose = 0 + * - scriptId = 0 + * - watermarkLevel = 0 + * - eventMask1 = 0 + * - eventMask2 = 0 + * - peripheralAddr = NULL + * - callbackISR_ptr = NULL + * - iapi_channelControlBlock = NULL + */ +channelDescriptor iapi_ChannelDefaults = {0, 1, 8, 0, DEFAULT_POLL, + CD_DEFAULT_OWNERSHIP, 1, TRUE, 0, 0, 0, 0, + 0, 0x00, 0x00, 0x00, NULL, NULL}; + +/** + * Integrated error management + */ +unsigned int iapi_errno = 0; +volatile unsigned long iapi_SDMAIntr = 0; + +/* Default config register. + * Initialization values are: + * dspdma used + * Real-Time Debug pins disabled + * AHB freq / core freq = 2 + * dynamic context switch +*/ +configs_data iapi_ConfigDefaults = {1, 0, 0, 3}; + +#ifdef SDMA_SKYE +/* Default sdma State : UNDEF + *possible value are UNDEF, OPEN, LOCK, CLOSED, CLOSE_LOCK + */ + +sdmaState iapi_SdmaState= UNDEF; +#endif + +/* ***************************************************************************/ diff --git a/arch/arm/plat-mxc/sdma/iapi/src/iapiHigh.c b/arch/arm/plat-mxc/sdma/iapi/src/iapiHigh.c new file mode 100644 index 000000000000..abdd2d89ab67 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/src/iapiHigh.c @@ -0,0 +1,2798 @@ +/****************************************************************************** + * + * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiHigh.c + * + * $Id iapiHigh.c $ + * + * Description: + * This library is written in C to guarantee functionality and integrity in + * the usage of SDMA virtual DMA channels. This API (Application Programming + * Interface) allow SDMA channels' access in an OPEN, READ, WRITE, CLOSE + * fashion. + * These are the HIGH level functions of the I.API. + * + * + * / + * + * $Log iapiHigh.c $ + * + *****************************************************************************/ + +/* **************************************************************************** + * Include File Section + *****************************************************************************/ +#include <stdarg.h> +#include <string.h> + +#include "epm.h" +#include "iapi.h" + +/* **************************************************************************** + * External Reference Section (for compatibility with already developed code) + *****************************************************************************/ +static void iapi_read_ipcv2_callback(struct iapi_channelDescriptor* cd_p, void* data); + +/* **************************************************************************** + * Global Variable Section + *****************************************************************************/ +#define MAX_CHANNEL 32 + +static dataNodeDescriptor* dnd_read_control_struct[MAX_CHANNEL]; + +/* MASK to get Nullify all the bits of Status in Data Node descriptor apart from L, E and D*/ + +#define GET_LED_MASK 0xE0 + +/*Table defines mapping of Data Node Descriptor to Buffer Descriptor status*/ + +static unsigned char dnd_2_bd_status[]= +{ +0x85, /*00 L = 0, E = 0, D = 0*/ +0x00, /*01*/ +0x00, /*02*/ +0x00, /*03*/ +0x00, /*04*/ +0x00, /*05*/ +0x00, /*06*/ +0x00, /*07*/ +0x00, /*08*/ +0x00, /*09*/ +0x00, /*0A*/ +0x00, /*0B*/ +0x00, /*0C*/ +0x00, /*0D*/ +0x00, /*0E*/ +0x00, /*0F*/ +0x00, /*10*/ +0x00,/*11*/ +0x00,/*12*/ +0x00,/*13*/ +0x00,/*14*/ +0x00,/*15*/ +0x00,/*16*/ +0x00,/*17*/ +0x00,/*18*/ +0x00,/*19*/ +0x00,/*1A*/ +0x00,/*1B*/ +0x00,/*1C*/ +0x00,/*1D*/ +0x00,/*1E*/ +0x00,/*1F*/ +0x84,/*20 L = 0, E = 0, D = 1 */ +0x00,/*21*/ +0x00,/*22*/ +0x00,/*23*/ +0x00,/*24*/ +0x00,/*25*/ +0x00,/*26*/ +0x00,/*27*/ +0x00,/*28*/ +0x00,/*29*/ +0x00,/*2A*/ +0x00,/*2B*/ +0x00,/*2C*/ +0x00,/*2D*/ +0x00,/*2E*/ +0x00,/*2F*/ +0x00,/*30*/ +0x00,/*31*/ +0x00,/*32*/ +0x00,/*33*/ +0x00,/*34*/ +0x00,/*35*/ +0x00,/*36*/ +0x00,/*37*/ +0x00,/*38*/ +0x00,/*39*/ +0x00,/*3A*/ +0x00,/*3B*/ +0x00,/*3C*/ +0x00,/*3D*/ +0x00,/*3E*/ +0x00,/*3F*/ +0xAB,/*40 L = 0, E = 1, D = 0*/ +0x00,/*41*/ +0x00,/*42*/ +0x00,/*43*/ +0x00,/*44*/ +0x00,/*45*/ +0x00,/*46*/ +0x00,/*47*/ +0x00,/*48*/ +0x00,/*49*/ +0x00,/*4A*/ +0x00,/*4B*/ +0x00,/*4C*/ +0x00,/*4D*/ +0x00,/*4E*/ +0x00,/*4F*/ +0x00,/*50*/ +0x00,/*51*/ +0x00,/*52*/ +0x00,/*53*/ +0x00,/*54*/ +0x00,/*55*/ +0x00,/*56*/ +0x00,/*57*/ +0x00,/*58*/ +0x00,/*59*/ +0x00,/*5A*/ +0x00,/*5B*/ +0x00,/*5C*/ +0x00,/*5D*/ +0x00,/*5E*/ +0x00,/*5F*/ +0xAA,/*60 L = 0, E = 1, D = 1*/ +0x00,/*61*/ +0x00,/*62*/ +0x00,/*63*/ +0x00,/*64*/ +0x00,/*65*/ +0x00,/*66*/ +0x00,/*67*/ +0x00,/*68*/ +0x00,/*69*/ +0x00,/*6A*/ +0x00,/*6B*/ +0x00,/*6C*/ +0x00,/*6D*/ +0x00,/*6E*/ +0x00,/*6F*/ +0x00,/*70*/ +0x00,/*71*/ +0x00,/*72*/ +0x00,/*73*/ +0x00,/*74*/ +0x00,/*75*/ +0x00,/*76*/ +0x00,/*77*/ +0x00,/*78*/ +0x00,/*79*/ +0x00,/*7A*/ +0x00,/*7B*/ +0x00,/*7C*/ +0x00,/*7D*/ +0x00,/*7E*/ +0x00,/*7F*/ +0xC5,/*80 L = 1, E = 0, D = 0*/ +0x00,/*81*/ +0x00,/*82*/ +0x00,/*83*/ +0x00,/*84*/ +0x00,/*85*/ +0x00,/*86*/ +0x00,/*87*/ +0x00,/*88*/ +0x00,/*89*/ +0x00,/*8A*/ +0x00,/*8B*/ +0x00,/*8C*/ +0x00,/*8D*/ +0x00,/*8E*/ +0x00,/*8F*/ +0x00,/*90*/ +0x00,/*91*/ +0x00,/*92*/ +0x00,/*93*/ +0x00,/*94*/ +0x00,/*95*/ +0x00,/*96*/ +0x00,/*97*/ +0x00,/*98*/ +0x00,/*99*/ +0x00,/*9A*/ +0x00,/*9B*/ +0x00,/*9C*/ +0x00,/*9D*/ +0x00,/*9E*/ +0x00,/*9F*/ +0xC4,/*A0 L = 1, E = 0, D = 1*/ +0x00,/*A1*/ +0x00,/*A2*/ +0x00,/*A3*/ +0x00,/*A4*/ +0x00,/*A5*/ +0x00,/*A6*/ +0x00,/*A7*/ +0x00,/*A8*/ +0x00,/*A9*/ +0x00,/*AA*/ +0x00,/*AB*/ +0x00,/*AC*/ +0x00,/*AD*/ +0x00,/*AE*/ +0x00,/*AF*/ +0x00,/*B0*/ +0x00,/*B1*/ +0x00,/*B2*/ +0x00,/*B3*/ +0x00,/*B4*/ +0x00,/*B5*/ +0x00,/*B6*/ +0x00,/*B7*/ +0x00,/*B8*/ +0x00,/*B9*/ +0x00,/*BA*/ +0x00,/*BB*/ +0x00,/*BC*/ +0x00,/*BD*/ +0x00,/*BE*/ +0x00,/*BF*/ +0xEB,/*C0 L = 1, E = 1, D = 0*/ +0x00,/*C1*/ +0x00,/*C2*/ +0x00,/*C3*/ +0x00,/*C4*/ +0x00,/*C5*/ +0x00,/*C6*/ +0x00,/*C7*/ +0x00,/*C8*/ +0x00,/*C9*/ +0x00,/*CA*/ +0x00,/*CB*/ +0x00,/*CC*/ +0x00,/*CD*/ +0x00,/*CE*/ +0x00,/*CF*/ +0x00,/*D0*/ +0x00,/*D1*/ +0x00,/*D2*/ +0x00,/*D3*/ +0x00,/*D4*/ +0x00,/*D5*/ +0x00,/*D6*/ +0x00,/*D7*/ +0x00,/*D8*/ +0x00,/*D9*/ +0x00,/*DA*/ +0x00,/*DB*/ +0x00,/*DC*/ +0x00,/*DD*/ +0x00,/*DE*/ +0x00,/*DF*/ +0xEA,/*E0 L = 1, E = 1, D = 1*/ +0x00,/*E1*/ +0x00,/*E2*/ +0x00,/*E3*/ +0x00,/*E4*/ +0x00,/*E5*/ +0x00,/*E6*/ +0x00,/*E7*/ +0x00,/*E8*/ +0x00,/*E9*/ +0x00,/*EA*/ +0x00,/*EB*/ +0x00,/*EC*/ +0x00,/*ED*/ +0x00,/*EE*/ +0x00,/*EF*/ +0x00,/*F0*/ +0x00,/*F1*/ +0x00,/*F2*/ +0x00,/*F3*/ +0x00,/*F4*/ +0x00,/*F5*/ +0x00,/*F6*/ +0x00,/*F7*/ +0x00,/*F8*/ +0x00,/*F9*/ +0x00,/*FA*/ +0x00,/*FB*/ +0x00,/*FC*/ +0x00,/*FD*/ +0x00,/*FE*/ +0x00/*FF*/ +}; +/* **************************************************************************** + * Function Section + *****************************************************************************/ + +/* ***************************************************************************/ +/**Opens an SDMA channel to be used by the library. + * + * <b>Algorithm:</b>\n + * + * - Check if initialization is necessary. + * - Check that user initialized OS dependant functions. + * - Test validity of input parameters + * - Check whole channel control block data structure + * - Finish initializations (tables with default values) + * - Initialize channel 0 is dedicated to communications with SDMA + * - Check channel control block definition + * - if the channel descriptor is not initialized, initialize it with + * the default value + * - If buffer descriptor already allocated, exit with iapi_errno filled + * complete the lowest bits with the number of 'D' bits set + * - Buffer Descriptors allocation + * - Channel's configuration properties (mcu side only) + * - read/write direction => enable/disable channel setting + * + * @param *cd_p -If channelNumber is 0, it is pointer to channel descriptor for the channnel 0 to be opened and + has default values. + * For other channels,this function should be called after channel 0 has been opened, and it is channel descriptor for + channel 0.Must be allocated. + * @param channelNumber channel to be opened + * + * @return + * - IAPI_SUCCESS : OK + * - -iapi_errno : close failed, return negated value of iapi_errno + */ +int +iapi_Open (channelDescriptor * cd_p, unsigned char channelNumber) +{ + channelControlBlock * ccb_p; + channelControlBlock * local_ccb_p; + channelDescriptor * local_cd_p; + bufferDescriptor * bd_p; + unsigned char index = 0; + int result = IAPI_SUCCESS; +#ifdef MCU + volatile unsigned long * channelPriorityMatx; +#endif /* MCU */ + + + /* + * 1. Check if initialization is necessary + */ + if (cd_p == NULL){ + result = IAPI_ERR_CD_UNINITIALIZED | + IAPI_ERR_CH_AVAILABLE | channelNumber; + iapi_errno = result; + return -result; + } + + /* Verify these functions every time*/ + if((iapi_GetChannel == NULL)||(iapi_ReleaseChannel == NULL)) + { + result = IAPI_ERR_NO_OS_FN | channelNumber; + iapi_errno = result; + return -result; + } + + /* Try to aquire channel*/ + if(iapi_GetChannel(channelNumber) != 0) + { + result = IAPI_ERR_CH_IN_USE | channelNumber; + iapi_errno = result; + return -result; + } + + if (channelNumber == 0 && cd_p->ccb_ptr == NULL){ + /* Verify that the user initialized all OS dependant functions required + * by the library. + */ + if((iapi_Malloc == NULL)||(iapi_Free == NULL)||(iapi_Virt2Phys == NULL)|| + (iapi_Phys2Virt == NULL)||(iapi_GotoSleep == NULL)|| + (iapi_WakeUp == NULL)||(iapi_InitSleep == NULL)||(iapi_memset == NULL)|| + (iapi_memcpy == NULL)) + { + result = IAPI_ERR_NO_OS_FN | channelNumber; + iapi_errno = result; + iapi_ReleaseChannel(channelNumber); + return -result; + } + /* Whole channel control block data structure */ + ccb_p = (channelControlBlock *) + MALLOC(CH_NUM*sizeof(channelControlBlock), SDMA_IRAM); + if (ccb_p == NULL){ + result = IAPI_ERR_CCB_ALLOC_FAILED | + IAPI_ERR_CH_AVAILABLE | channelNumber; + iapi_errno = result; + iapi_ReleaseChannel(channelNumber); + return -result; + } + /* Zero-out the CCB structures array just allocated*/ + iapi_memset(ccb_p, 0x00, CH_NUM*sizeof(channelControlBlock)); + /* Save the address of the CCB structures array*/ + iapi_CCBHead = ccb_p; + + cd_p->ccb_ptr = (struct iapi_channelControlBlock *)ccb_p; + ccb_p->channelDescriptor = cd_p; +#ifdef MCU + /* finish initializations */ + iapi_InitChannelTables(); +#endif /* MCU */ + /* Channel 0 is dedicated to communications with SDMA */ + cd_p->ownership = ((DONT_OWN_CHANNEL << CH_OWNSHP_OFFSET_EVT ) | + ( OWN_CHANNEL << CH_OWNSHP_OFFSET_MCU ) | + (DONT_OWN_CHANNEL << CH_OWNSHP_OFFSET_DSP )); + cd_p->bufferDescNumber = 1; + } + + /* + * 2. Check channel control block + */ + ccb_p = cd_p->ccb_ptr; + if (ccb_p == NULL){ + result = IAPI_ERR_NO_CCB_DEFINED | IAPI_ERR_CH_AVAILABLE|channelNumber; + iapi_errno = result; + iapi_ReleaseChannel(channelNumber); + return -result; + } + + /* Control block & Descriptor associated with the channel being worked on */ + local_ccb_p = &ccb_p[channelNumber]; + local_cd_p = ccb_p[channelNumber].channelDescriptor; + + /* If the channel is not initialized, initialize it with the default value */ + if (local_cd_p == NULL){ + result = iapi_AllocChannelDesc (&local_cd_p, channelNumber); + if ( result!= IAPI_SUCCESS) + { + iapi_ReleaseChannel(channelNumber); + return result; //is allready negated from iapi_AllocChannelDesc + } + + local_cd_p->ccb_ptr = (struct iapi_channelControlBlock *)local_ccb_p; + local_ccb_p->channelDescriptor = local_cd_p; + } + + /* + * 3. If buffer descriptor already allocated, exit with iapi_errno filled + */ + if ( local_ccb_p->baseBDptr != NULL ){ + bd_p = (bufferDescriptor *)iapi_Phys2Virt(local_ccb_p->baseBDptr); + result = IAPI_ERR_BD_ALLOCATED; + for (index=1 ; index< local_cd_p->bufferDescNumber ; index++){ + if ((bd_p->mode.status & BD_DONE) == BD_DONE){ + /* complete the lowest bits with the number of 'D' bits set */ + result++; + } + bd_p++; + } + iapi_errno = result; + iapi_ReleaseChannel(channelNumber); + return -result; + } + + /* + * 4. Buffer Descriptors allocation + */ + iapi_InitializeMemory(local_ccb_p); + +#ifdef MCU + /* + * 5. Channel's configuration properties (mcu side only) + */ + iapi_ChannelConfig( channelNumber, + ( local_cd_p->ownership >> CH_OWNSHP_OFFSET_EVT ) & 1UL, + ( local_cd_p->ownership >> CH_OWNSHP_OFFSET_MCU ) & 1UL, + ( local_cd_p->ownership >> CH_OWNSHP_OFFSET_DSP ) & 1UL); +#endif /* MCU */ + + /* Setting interrupt handling */ + iapi_ChangeCallbackISR(local_cd_p, local_cd_p->callbackISR_ptr); + + /* Call initialization fn for polling synch on this channel*/ + INIT_SLEEP(channelNumber); + + /* No user arg pointer yet*/ + userArgTable[cd_p->channelNumber]= NULL; + + /* + * 6. read/write direction => enable/disable channel + */ +#ifdef MCU + channelPriorityMatx = &SDMA_CHNPRI_0; + channelPriorityMatx[channelNumber] = 1; +#endif /* MCU */ + + local_ccb_p->status.openedInit = TRUE; + iapi_ReleaseChannel(channelNumber); + return IAPI_SUCCESS; +} + +/* ***************************************************************************/ +/** Attempts to read nbyte from the data buffer descriptor associated with the + * channel channelNumber, into the user's data buffer pointed to by buf. + * + * <b>Algorithm:</b>\n + * - Check data structures are properly initialized: + * - Channel descriptor validity + * - Control block & Descriptor associated with the channel being worked on + * - Check initialization has been done for trusted channels + * - If transfer data size is used, check validity of combination transfer + * size/requested bytes + * - Set the 'D' done bits on all buffer descriptors + * - Starting of the channel + * - Synchronization mechanism handling: + * - for callback: just exit function + * - for polling: call the synchronization function then read data from + * buffer until either nbyte parameter is reached or all buffer descriptors + * have been processed. + * + * <b>Notes:</b>\n + * 1) Virtual DMA SDMA channels are unidirectional, an iapi_Read authorized + * on a channel means that we are expecting to receive from the SDMA. The + * meaning of an interrupt received from the SDMA is therefore that the + * data has been copied from the SDMA to the host's data buffers and is + * already passed on upper layers of the application.\n + * + * @param *cd_p chanenl descriptor for the channel to read from + * @param *buf buffer to receive the data + * @param nbyte number of bytes to read from channel + * + * @return + * - number of bytes read + * - -iapi_errno : in case of failure return negated value of iapi_errno + */ +int +iapi_Read (channelDescriptor * cd_p, void * buf, unsigned short nbyte) +{ + int index = 0; + int readBytes; + int toRead; + int result = IAPI_SUCCESS; + unsigned int copyFinished; + int bufsize; + bufferDescriptor * bd_p; + channelControlBlock * ccb_p; + unsigned char * local_buf; + unsigned char chNum; + unsigned char div; + + iapi_errno = IAPI_ERR_NO_ERROR; + + /* + * 1. Check data structures are properly initialized + */ + /* Channel descriptor validity */ + if (cd_p == NULL){ + result = IAPI_ERR_CD_UNINITIALIZED; + iapi_errno = result; + return -result; + } + + /* Channel control block validity */ + if (cd_p->ccb_ptr == NULL){ + result = IAPI_ERR_CCB_UNINITIALIZED; + iapi_errno = result; + return -result; + } + + /* Control block & Descriptor associated with the channel being worked on */ + chNum = cd_p->channelNumber; + ccb_p = cd_p->ccb_ptr; + + /* Try to aquire channel*/ + if(iapi_GetChannel(chNum) != 0) + { + result = IAPI_ERR_CH_IN_USE | chNum; + iapi_errno = result; + return -result; + } + + /* Check if channel is already opened/initialized */ + if (ccb_p->status.openedInit == FALSE) { + result = IAPI_ERR_CHANNEL_UNINITIALIZED | IAPI_ERR_CH_AVAILABLE | chNum; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } + + /* Buffer descriptor validity */ + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + if (bd_p == NULL ){ + result = IAPI_ERR_BD_UNINITIALIZED | IAPI_ERR_CH_AVAILABLE | chNum; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } + + + /* Check initialization has been done for trusted channels */ + if (cd_p->trust == TRUE) { + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + for(index=0 ; index < cd_p->bufferDescNumber ; index++){ + if ((bd_p->bufferAddr == NULL) || (bd_p->mode.count == 0)){ + result = IAPI_ERR_BD_UNINITIALIZED | IAPI_ERR_CH_AVAILABLE | chNum; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } + bd_p++; + } + } + + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + /*If transfer data size is used, check that the required read length is + * divisible by transfer data size expressed in bytes + */ + if(cd_p->useDataSize) + { + /*Check for divisibility only if data size different then 8bit*/ + if(cd_p->dataSize != TRANSFER_8BIT) + { + switch(cd_p->dataSize) + { + case TRANSFER_32BIT: + div = 4; + break; + case TRANSFER_16BIT: + div = 2; + break; + case TRANSFER_24BIT: + div = 3; + break; + /*we should not get to default*/ + default: + result = IAPI_ERR_INVALID_PARAMETER | chNum; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } + /*check the total number of bytes requested*/ + if((nbyte % div) != 0) + { + result = IAPI_ERR_INVALID_PARAMETER | chNum; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } + /*now check the length of every BD*/ + for(index=0 ; index < cd_p->bufferDescNumber ; index++) + { + if((bd_p->mode.count % div) != 0) + { + result = IAPI_ERR_INVALID_PARAMETER | chNum; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } + bd_p++; + } + } + } + + /* + * 2. Set the 'D' done bits on all buffer descriptors + */ + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + for (index=0 ; index < cd_p->bufferDescNumber ; index++) { + bd_p->mode.status |= BD_DONE; + bd_p++; + } + + /* + * 3. Starting of the channel + */ + iapi_lowStartChannel (chNum); + ccb_p->status.execute = TRUE; + readBytes = 0; + + /* + * 4. Synchronization mechanism handling + */ + if( cd_p->callbackSynch == DEFAULT_POLL){ + iapi_SynchChannel(chNum); + + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + toRead = nbyte; + copyFinished = FALSE; + local_buf = (unsigned char *)buf; + + /* + * Check the 'RROR' bit on all buffer descriptors, set error number + * and return IAPI_FAILURE if set. + */ + for (index=0 ; index < cd_p->bufferDescNumber ; index++) + { + if(bd_p->mode.status & BD_RROR) + { + result = IAPI_ERR_RROR_BIT_READ | chNum; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } + bd_p++; + } + + + /* + * 5. Read loop + */ + + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + while (!copyFinished) + { + if (!(bd_p->mode.status & BD_DONE)) + { + if (cd_p->trust == FALSE) { + bufsize = cd_p->bufferSize; + } else { + bufsize = bd_p->mode.count; + } + /*if L bit is set, read only "count" bytes and exit the loop*/ + if(bd_p->mode.status & BD_LAST) + { + bufsize = bd_p->mode.count; + copyFinished = TRUE; + } + if (toRead > bufsize) + { + if (cd_p->trust == FALSE) + { + iapi_memcpy(local_buf, iapi_Phys2Virt(bd_p->bufferAddr), bufsize); + local_buf += bufsize; + } + readBytes += bufsize; + toRead -= bufsize; + /*advance bd_p only if bit L is not set. The loop will exit anyway.*/ + if(!(bd_p->mode.status & BD_LAST)) + { + if (bd_p->mode.status & BD_WRAP) + { + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + } + else if(((bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr) + + (cd_p->bufferDescNumber - 1)*sizeof(bufferDescriptor)) != bd_p) + { + bd_p++; + } + else + { + /* finished here : end of buffer descriptors */ + copyFinished = TRUE; + } + } + } + else + { + if (cd_p->trust == FALSE) + { + iapi_memcpy(local_buf, iapi_Phys2Virt(bd_p->bufferAddr), toRead); + local_buf += toRead; + } + readBytes += toRead; + toRead = 0; + /* finished successfully : readBytes = nbytes */ + copyFinished = TRUE; + } + } + else + { + /* finished here : buffer not already done*/ + copyFinished = TRUE; + } + } + iapi_ReleaseChannel(chNum); + } + + /* + *If synchronization type is callback, the user of I.API must + *release the channel + */ + return readBytes; +} + +/* ***************************************************************************/ +/*Attempts to write nbyte from the buffer pointed to by buf to the channel + * data buffers associated with the opened channel number channelNumber + * + * <b>Algorithm:</b>\n + * + * - Check data structures are properly initialized: + * - Channel descriptor validity + * - Channel control block validity + * - Buffer descriptor validity + * - If transfer data size is used, check validity of combination transfer + * size/requested bytes + * - Write loop\n + * Write occurs in the buffer acceded form buffer descriptor and continues + * to the "next" buffer which can be:\n + * -# the last BD of the ring so re-start from beginning\n + * -# the last BD of the BD array but no ring so finish\n + * -# (general case) the next BD in the BD array\n + * And copy continues until data fit in the current buffer or the nbyte + * parameter is reached. + * - Starting of the channel + * + * <b>Notes:</b>\n + * 1) Virtual DMA SDMA channels are unidirectionnal, an iapi_Write authorized + * on a channel means that we are expecting to send to the SDMA. The + * meaning of an interrupt received from the SDMA is therfore that the + * data has been delivered to the SDMA. + * + * @param *cd_p chanenl descriptor for the channel to write to + * @param *buf buffer with data to be written + * @param nbyte number of bytes to write to channel + * + * @return + * - number of bytes written + * - -iapi_errno if failure + */ +int +iapi_Write (channelDescriptor * cd_p, void * buf, unsigned short nbyte) +{ + unsigned int writtenBytes = 0; + unsigned int toWrite; + int result = IAPI_SUCCESS; + unsigned int copyFinished; + unsigned int buffsize; + unsigned int index = 0; + bufferDescriptor * bd_p; + channelControlBlock * ccb_p; + unsigned char * local_buf; + unsigned char chNum; + unsigned char div; + + iapi_errno = IAPI_ERR_NO_ERROR; + + /* + * 1. Check data structures are properly initialized + */ + /* Channel descriptor validity */ + if (cd_p == NULL){ + result = IAPI_ERR_CD_UNINITIALIZED; + iapi_errno = result; + return -result; + } + + /* Channel control block validity */ + if (cd_p->ccb_ptr == NULL){ + result = IAPI_ERR_CCB_UNINITIALIZED; + iapi_errno = result; + return -result; + } + + /* Control block & Descriptpor associated with the channel being worked on */ + chNum = cd_p->channelNumber; + ccb_p = cd_p->ccb_ptr; + + /* Try to aquire channel*/ + if(iapi_GetChannel(chNum) != 0) + { + result = IAPI_ERR_CH_IN_USE | chNum; + iapi_errno = result; + return -result; + } + + /* Buffer descriptor validity */ + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + if (bd_p == NULL ){ + result = IAPI_ERR_BD_UNINITIALIZED | IAPI_ERR_CH_AVAILABLE | chNum; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } + + /* Check initialization has been done for trusted channels */ + if (cd_p->trust == TRUE) { + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + for(index=0 ; index < cd_p->bufferDescNumber ; index++){ + if ((bd_p->bufferAddr == NULL) || (bd_p->mode.count == 0)){ + result = IAPI_ERR_BD_UNINITIALIZED | IAPI_ERR_CH_AVAILABLE | chNum; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } + bd_p++; + } + } + + + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + /*If transfer data size is used, check that the required write length is + * divisible by transfer data size expressed in bytes + */ + if(cd_p->useDataSize) + { + /*Check for divisibility only if data size different then 8bit*/ + if(cd_p->dataSize != TRANSFER_8BIT) + { + switch(cd_p->dataSize) + { + case TRANSFER_32BIT: + div = 4; + break; + case TRANSFER_16BIT: + div = 2; + break; + case TRANSFER_24BIT: + div = 3; + break; + /*we should not get to default*/ + default: + result = IAPI_ERR_INVALID_PARAMETER | chNum; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } + /*check the total number of bytes requested*/ + if((nbyte % div) != 0) + { + result = IAPI_ERR_INVALID_PARAMETER | chNum; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } + /*now check the length of every BD*/ + for(index=0 ; index < cd_p->bufferDescNumber ; index++) + { + if((bd_p->mode.count % div) != 0) + { + result = IAPI_ERR_INVALID_PARAMETER | chNum; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } + bd_p++; + } + } + } + + /* + * 2. Write loop + */ + + local_buf = (unsigned char *)buf; + toWrite = nbyte; + copyFinished = FALSE; + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + + while (!copyFinished){ + + /* variable buffsize contains the nb of bytes that the SDMA will transfer at each pass of the while loop*/ + + /* in NON trusted mode, buffsize is copied from Channel descriptor bufferSize (same size for all transfers) */ + if (cd_p->trust == FALSE) { + buffsize = cd_p->bufferSize; + } + /* in TRUSTED mode, it's up to the user to specify the size of each buffer thru an IoCtl call */ + /* This IoCtl has directly modified the bd_p->mode.count */ + /* therefore, buffersize is copied from the bd_p->mode.count */ + else { + buffsize = bd_p->mode.count; + } + + /* in any mode (trusted or non trusted), the transfer size must be overridden by */ + /* "toWrite" when there is less remaining bytes to transfer than the current buffer size */ + if (toWrite < buffsize) { + buffsize = toWrite; + } + + + if (!(bd_p->mode.status & BD_DONE)){ + /* More data to write than a single buffer can contain */ + if (cd_p->trust == FALSE ){ + iapi_memcpy(iapi_Phys2Virt(bd_p->bufferAddr), local_buf, buffsize); + local_buf += buffsize; + } + + /* update the BD count that will be used by the SDMA to transfer the proper nb of bytes */ + bd_p->mode.count = buffsize; + + bd_p->mode.status |= BD_DONE; + writtenBytes += buffsize; + toWrite -= buffsize; + /* Prepares access to the "next" buffer */ + /* - case 1 - finished successfully : writtenBytes = nbytes */ + if (toWrite == 0) { + copyFinished = TRUE; + } + /* - case 2 - Last BD and WRAP bit set so re-start from beginning */ + /*else if ((bd_p->mode.status & BD_WRAP)){ + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + }*/ + /* - case 3 - Last BD of the BD but nor ring*/ + else if (((bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr) + + (cd_p->bufferDescNumber - 1) * sizeof(bufferDescriptor)) == bd_p){ + copyFinished = TRUE; + } + /* - case 4 - general : next BD in the BD array */ + else { + bd_p++; + } + + } else { + /* finished here : buffer not already done */ + copyFinished = TRUE; + } + } + + ccb_p->currentBDptr = ccb_p->baseBDptr; + + /* + * 3. Starting of the channel + */ + iapi_lowStartChannel(chNum); + ccb_p->status.execute = TRUE; + + if( cd_p->callbackSynch == DEFAULT_POLL) + { + iapi_SynchChannel(chNum); + /* + * Check the 'RROR' bit on all buffer descriptors, set error number + * and return IAPI_FAILURE if set. + */ + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + for (index=0 ; index < cd_p->bufferDescNumber ; index++) + { + if(bd_p->mode.status & BD_RROR) + { + result = IAPI_ERR_RROR_BIT_WRITE | chNum; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } + bd_p++; + } + iapi_ReleaseChannel(chNum); + } + + /* + *If synchronization type is callback, the user of I.API must + *release the channel + */ + return writtenBytes; +} + + + + +/* ***************************************************************************/ +/* This function is used to receive data from the SDMA. + * + * <b>Algorithm:</b>\n + * + * The data control structure would be copied to IPCv1 complied Buffer + * Descriptor Array. This array shall be allocated from non cacheable memory. + * It would then provide this buffer descriptor array as an input to SDMA using + * channel control block and then configure the Host Enable (HE) or + * DSP enable (DE) bit of SDMA for the channel used for this transfer depending + * on the source. + * + * <b>Notes:</b>\n + * Virtual DMA channels are unidirectional, an iapi_Write_ipcv2 authorized + * on a channel means that source processor is expecting to send to the destination + * processor. The meaning of an interrupt received from the SDMA notifies that the + * data has been delivered to the destination processor. + * + * @param *cd_p chanenl descriptor for the channel to receive from + * @param *data_control_struct_ipcv2 + + * Data Control structure: + * ------------------------- + * | Data Node Descriptor 1| + * ------------------------- + * | Data Node Descriptor 2| + * ------------------------- + * | : | + * | : | + * ------------------------- + * |Data Node Descriptor n | + * ------------------------- + * + * Data Node Descriptor (Buffer Descriptor): + *------------------------------------------------------------------------------ + *| 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 … 0| + *------------------------------------------------------------------------------ + *| L E D R R R R R |<---- Reserved ----> |<- Length-> | + *------------------------------------------------------------------------------ + *| <---------------------------- Data Ptr ----------------------------------->| + *------------------------------------------------------------------------------ + * + * L bit (LAST): If set, means that this buffer of data is the last buffer of the frame + * E bit (END): If set, we reached the end of the buffers passed to the function + * D bit (DONE): Only valid on the read callback. When set, means that the buffer has been + * filled by the SDMA. + * Length: Length of data pointed by this node in bytes + * Data Ptr: Pointer to the data pointed to by this node. + * The Function Shall not be called for the same channel unless the Read callback has been + * received for channel for which it has been called already. + * + * @return + * - IAPI_SUCCESS on success, IAPI_ERROR otherwise + * + *- -iapi_errno if failure + */ + +int iapi_Read_ipcv2( channelDescriptor * cd_p, void * data_control_struct_ipcv2) +{ + channelControlBlock * ccb_p; + + +/* The Parameters passed are considered to be validated by the upper layers*/ + + bufferDescriptor_ipcv1_v2 *bd_ipcv2_p; + dataNodeDescriptor *dnd_p = (dataNodeDescriptor*)data_control_struct_ipcv2; + + ccb_p = cd_p->ccb_ptr; + iapi_errno = IAPI_ERR_NO_ERROR; + + if(ccb_p->baseBDptr == NULL) +{ + iapi_errno = IAPI_ERR_BD_UNINITIALIZED; + return -(IAPI_ERR_BD_UNINITIALIZED); +} + + ccb_p->currentBDptr = ccb_p->baseBDptr; + + /* Copy the data Node descriptor information to new BDs */ + bd_ipcv2_p = (bufferDescriptor_ipcv1_v2*)iapi_Phys2Virt(ccb_p->baseBDptr); + + while(1) + { + bd_ipcv2_p->bufferAddr = dnd_p->bufferAddr; + bd_ipcv2_p->mode.count = dnd_p->mode.count; +#ifdef MCU + bd_ipcv2_p->mode.endianness = 1; +#endif +#ifdef DSP + bd_ipcv2_p->mode.endianness = 0; +#endif + + bd_ipcv2_p->mode.status = dnd_2_bd_status[dnd_p->mode.status & GET_LED_MASK]; + + if((dnd_p->mode.status & DND_END_OF_XFER) != 0) + { + /* Break the loop at End of Transfer */ + break; + + } + bd_ipcv2_p++; + dnd_p++; + + } + /* + * Store the buffer address + */ + dnd_read_control_struct[cd_p->channelNumber] = (dataNodeDescriptor*)data_control_struct_ipcv2; + /* + * Register the Call Back + */ + + iapi_AttachCallbackISR(cd_p, iapi_read_ipcv2_callback); + + /* + * Starting of the channel + */ + iapi_lowStartChannel(cd_p->channelNumber); + ccb_p->status.execute = TRUE; + + return IAPI_SUCCESS; + +} + + +/* ***************************************************************************/ +/* + * The function is used send a group of buffers to SDMA. + * <b>Algorithm:</b>\n + * + * The data control structure would be copied to IPCv1 complied Buffer + * Descriptor Array. This array shall be allocated from non cacheable memory. + * It would then provide this buffer descriptor array as an input to SDMA using + * channel control block and then configure the Host Enable (HE) or + * DSP enable (DE) bit of SDMA for the channel used for this transfer depending + * on the source. + * The Function Shall not be called for the same channel unless the Read callback has been + * received for channel for which it has been called already. + * + * <b>Notes:</b>\n + * Virtual DMA channels are unidirectional, an iapi_Write_ipcv2 authorized + * on a channel means that source processor is expecting to send to the destination + * processor. The meaning of an interrupt received from the SDMA notifies that the + * data has been delivered to the destination processor. + * + * @param *cd_p chanenl descriptor for the channel to write to + * @param *data_control_struct_ipcv2 + + * Data Control structure: + * ------------------------- + * | Data Node Descriptor 1| + * ------------------------- + * | Data Node Descriptor 2| + * ------------------------- + * | : | + * | : | + * ------------------------- + * |Data Node Descriptor n | + * ------------------------- + * + * Data Node Descriptor (Buffer Descriptor): + *------------------------------------------------------------------------------ + *| 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 … 0| + *------------------------------------------------------------------------------ + *| L E D R R R R R |<---- Reserved ----> |<- Length-> | + *------------------------------------------------------------------------------ + *| <---------------------------- Data Ptr ----------------------------------->| + *------------------------------------------------------------------------------ + * + * L bit (LAST): If set, means that this buffer of data is the last buffer of the frame + * E bit (END): If set, we reached the end of the buffers passed to the function + * D bit (DONE): Only valid on the read callback. When set, means that the buffer has been + * filled by the SDMA. + * Length: Length of data pointed by this node in bytes + * Data Ptr: Pointer to the data pointed to by this node. + * + * + * @return + * - iapi sucess on success. + * - -iapi_errno if failure + */ + +int iapi_Write_ipcv2( channelDescriptor * cd_p, void * data_control_struct_ipcv2) +{ + + channelControlBlock * ccb_p; + +/* The Parameters passed are considered to be validated by the upper layers*/ + + bufferDescriptor_ipcv1_v2 *bd_ipcv2_p; + dataNodeDescriptor *dnd_p = (dataNodeDescriptor*)data_control_struct_ipcv2; + ccb_p = cd_p->ccb_ptr; + iapi_errno = IAPI_ERR_NO_ERROR; + + if(ccb_p->baseBDptr == NULL) +{ + iapi_errno = IAPI_ERR_BD_UNINITIALIZED; + return -(IAPI_ERR_BD_UNINITIALIZED); +} + + + ccb_p->currentBDptr = ccb_p->baseBDptr; + + bd_ipcv2_p = (bufferDescriptor_ipcv1_v2*)iapi_Phys2Virt(ccb_p->currentBDptr); + /* Copy the data Node descriptor information to new BDs */ + while(1) + { + bd_ipcv2_p->bufferAddr = dnd_p->bufferAddr; + bd_ipcv2_p->mode.count = dnd_p->mode.count; + +#ifdef MCU + bd_ipcv2_p->mode.endianness = 1; +#endif +#ifdef DSP + bd_ipcv2_p->mode.endianness = 0; +#endif + + bd_ipcv2_p->mode.status = dnd_2_bd_status[dnd_p->mode.status & GET_LED_MASK]; + + if((dnd_p->mode.status & DND_END_OF_XFER) != 0) + { + /* Break the loop at End of Transfer */ + break; + } + bd_ipcv2_p++; + dnd_p++; + + } + + /* + * Starting of the channel + */ + iapi_lowStartChannel(cd_p->channelNumber); + ccb_p->status.execute = TRUE; + + return IAPI_SUCCESS; + +} + +/* ***************************************************************************/ +/** Call back ISR for the IPCv2 Receive. + * + * <b>Algorithm:</b>\n + * - This would copy back the informationfrom IPCv1 BD to IPCv2 BD on + * the receiving processor + * + * @return + * - void + */ + +void iapi_read_ipcv2_callback(struct iapi_channelDescriptor* cd_p, void* data) +{ + dataNodeDescriptor *dnd_p = dnd_read_control_struct[cd_p->channelNumber];//cd_p->ccb_ptr->channelDNDBuffer; + bufferDescriptor_ipcv1_v2 *bd_ipcv2_p = (bufferDescriptor_ipcv1_v2*)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + int index = MAX_BD_NUM - 1; + + + do + { + dnd_p->mode.status = 0; + dnd_p->mode.count = bd_ipcv2_p->mode.count; + + dnd_p->mode.status |= bd_ipcv2_p->mode.status & BD_DONE ? 0x00 : DND_DONE ; + dnd_p->mode.status |= bd_ipcv2_p->mode.status & BD_IPCV2_END_OF_FRAME ? DND_END_OF_FRAME : 0x00; + dnd_p->mode.status |= bd_ipcv2_p->mode.status & BD_LAST ? DND_END_OF_XFER : 0x00; + cd_p->ccb_ptr->currentBDptr = (bufferDescriptor*)iapi_Virt2Phys(bd_ipcv2_p); + + if((bd_ipcv2_p->mode.status & BD_LAST) != 0 || + (bd_ipcv2_p->mode.status & BD_CONT) == 0 + ) + break; + dnd_p++; + bd_ipcv2_p++; + + }while(index--); + + /*Call back the Original ISR */ + cd_p->callbackISR_ptr(cd_p, data); +} + +/* ***************************************************************************/ +/**Terminates a channel. + * + * <b>Algorithm:</b>\n + * - Check input parameters ans data structures + * - Check that all buffes have been processed (test all 'D' bits) + * - Stop the channel execution + * - Free alocated memory structures + * - Re-instantiate default interrupt handling + * + * @param *cd_p chanenl descriptor for the channel to close + * + * @return + * - IAPI_SUCCESS : OK + * - -iapi_errno : close failed + */ +int +iapi_Close (channelDescriptor * cd_p) +{ + int index = 0; + int result = IAPI_SUCCESS; + unsigned char chNum; + bufferDescriptor * bd_p; + channelControlBlock * ccb_p; + + /* + * 1. Check input parameters ans data structures + */ + if (cd_p != NULL){ + if (cd_p->ccb_ptr != NULL) { + chNum = cd_p->channelNumber; + ccb_p = cd_p->ccb_ptr; + } else { + result = IAPI_ERR_NO_CCB_DEFINED | IAPI_ERR_CH_AVAILABLE; + iapi_errno = result; + return -result; + } + } else { + result = IAPI_ERR_CD_UNINITIALIZED | IAPI_ERR_CH_AVAILABLE; + iapi_errno = result; + return -result; + } + /* Try to aquire channel*/ + if(iapi_GetChannel(chNum) != 0) + { + result = IAPI_ERR_CH_IN_USE | chNum; + iapi_errno = result; + return -result; + } + + /* + * 2. Check that all buffes have been processed (test all 'D' bits), + * only if the forceClose bit in channel descriptor is set to FALSE + */ + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + if (bd_p == NULL){ + result = IAPI_ERR_BD_UNINITIALIZED | IAPI_ERR_CH_AVAILABLE | chNum; + iapi_errno = result; + return -result; + } + if(cd_p->forceClose == FALSE) + { + for (index=cd_p->bufferDescNumber ; index>0 ; index--){ + if (bd_p->mode.status & BD_DONE){ + result = IAPI_ERR_CLOSE | IAPI_ERR_CH_AVAILABLE | chNum; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } + bd_p++; + } + } + /*if the closing is forced,mark channel unused,set BD ownership to processor*/ + else + { + ccb_p->status.execute = FALSE; + for (index=cd_p->bufferDescNumber ; index>0 ; index--) + { + bd_p->mode.status &= ~BD_DONE; + bd_p++; + } + } + + /* + * 3. Stop the channel execution + */ + iapi_lowStopChannel(chNum); + + /* + * 4. Free alocated memory structures + */ + if (cd_p->trust == FALSE ){ + bd_p = (bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr); + for (index=cd_p->bufferDescNumber ; index>0 ; index--){ + FREE (iapi_Phys2Virt(bd_p->bufferAddr)); + bd_p++; + } + } + + /* + * 5. Re-instantiate default interrupt handling + */ + iapi_DetachCallbackISR (cd_p); + FREE ((bufferDescriptor *)iapi_Phys2Virt(ccb_p->baseBDptr)); + FREE (cd_p); + ccb_p->baseBDptr = NULL; + ccb_p->currentBDptr = NULL; + ccb_p->channelDescriptor = NULL; + ccb_p->status.openedInit = FALSE; + + iapi_ReleaseChannel(chNum); + + return IAPI_SUCCESS; +} + +/* ***************************************************************************/ +/**The request argument selects the control function to be performed. + * + * <b>Algorithm:</b>\n + * + * - Check data structures are properly initialized: + * - Channel descriptor validity + * - Channel control block validity + * - The ctlRequest parameter contains in the lower 16 bits the control code of + * the change to be performed, and in the upper 16 bits, the BD to be + * modified if the change affects a BD od the channel. + * - Selection of the parameter to change and appropriate sanity checks: + * - Channel Descriptor: changes the pointer to the channel descriptor + * structure, the pointer to the new channel descriptor is given in the third + * argument call + * - Buffer Descriptor Number: changes the number of buffer descriptor for the + * channel + * - Buffer size: changes the size of the data buffers pointed to by the + * buffer descriptor; note that all buffer descriptors are assumed to have the + * same size for a given buffer descripotr chain + * - Blocking policy: changes the blocking policy for the read and write calls + * - Ownership: changes direction: turnaround + * - Synchronization method: changes the callback type, default or user. The* + * callback function table is set accordingly + * - Trust property: trust can only be changed through ChangeChannelDesc first + * request, this guarantees the close/open sequence for the channel + * - Callback Interrupt service routine pointer: changes the callback function + * pointer, when this method is used, to replace it with a new one + * - Channel control block pointer: not available + * - Priority: changes the channel priority directly in SDMA register + * - Watermark level: changes the value of the peripheral watermark level that + * passed to the script. The new value is passed in the third parameter call. + * - Wrap bit: changes to set to 1 the Wrap bit of the last buffer descriptor + * + * @param *cd_p channel descriptor for the channel to modify + * @param ctlRequest request control code and, if tha case, number of BD to be + * changed + * @param param parameter for the modification + * + * @return + * - IAPI_SUCCESS : OK + * - -iapi_errno : operation failed + */ +int +iapi_IoCtl (channelDescriptor * cd_p, unsigned long ctlRequest, + unsigned long param) +{ + int retvalue; + int result = IAPI_SUCCESS; + unsigned char chNum; + unsigned long clean_ctlRequest; /* lower 16 bits of the ctlRequest*/ + unsigned long bd_num; /* upper 16 bits of the ctlRequest*/ + + /* + * 1. Check data structures are properly initialized + */ + /* Channel descriptor validity */ + if (cd_p == NULL){ + result = IAPI_ERR_CD_UNINITIALIZED; + iapi_errno = result; + return -result; + } + + /* Channel control block validity */ + if (cd_p->ccb_ptr == NULL){ + result = IAPI_ERR_CCB_UNINITIALIZED; + iapi_errno = result; + return -result; + } + + /* Control block & Descriptor associated with the channel being worked on */ + chNum = cd_p->channelNumber; + + /* Remove, if exists, BD number specified in upper bits of ctlRequest*/ + clean_ctlRequest = ctlRequest & (~BD_NUM_MASK); + + /* Extract, if exists, BD number specified in upper bits of ctlRequest*/ + bd_num = (ctlRequest & BD_NUM_MASK) >> BD_NUM_OFFSET; + + /* Check that the bd_num is valid*/ + if(bd_num > cd_p->bufferDescNumber) + { + result = IAPI_ERR_INVALID_PARAMETER | chNum; + iapi_errno = result; + return -result; + } + + /*All checks OK, try to aquire channel*/ + if(iapi_GetChannel(chNum) != 0) + { + result = IAPI_ERR_CH_IN_USE | chNum; + iapi_errno = result; + return -result; + } + + /* + * 2. Selection of the parameter to change and appropriate sanity checks + */ + switch (clean_ctlRequest){ + + /* + * Channel Descriptor + * --- Changes the pointer to the channel descriptor structure: the pointer + * to the new channel descriptor is given in the third argument call. + */ + case IAPI_CHANGE_CHANDESC: + if ((void *) param == NULL) { + result = IAPI_ERR_INVALID_PARAMETER; + iapi_errno = result; + iapi_ReleaseChannel(chNum); + return -result; + } else { + channelDescriptor * chParam = (channelDescriptor *)param; + if (chParam->channelNumber != chNum){ + /* Release ch so it can be aquired by the Close fn*/ + iapi_ReleaseChannel(chNum); + result = iapi_Close(cd_p); + if (result == IAPI_SUCCESS){ + FREE((void*)cd_p); + iapi_AllocChannelDesc (&cd_p, chParam->channelNumber); + iapi_memcpy((void*)cd_p, (void*)chParam, sizeof (channelDescriptor)); + /* Channel is released allready, so Open can get the channel*/ + result = iapi_Open(cd_p, chParam->channelNumber); + if(result != IAPI_SUCCESS) + { + return result; /* error code already set in iapi_Open*/ + } + } else { + return result; /* error code already set in iapi_Close*/ + } + } else { + result = IAPI_ERR_CD_CHANGE | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_ReleaseChannel(chNum); + iapi_errno = result; + return -result; + } + return IAPI_SUCCESS; + } + + /* + * Buffer Descriptor Number + * --- Changes the number of buffer descriptor for the channel. + */ + case IAPI_CHANGE_BDNUM: + result = iapi_ChangeChannelDesc(cd_p, IAPI_BUFFERDESCNUMBER, param); + iapi_ReleaseChannel(chNum); + return result; + + /* + * Buffer size + * --- Changes the size of the data buffers pointed to by the buffer + * descriptor; note that all buffer descriptors are assumed to have the + * same size for a given buffer descripotr chain. + */ + case IAPI_CHANGE_BUFFSIZE: + result = iapi_ChangeChannelDesc(cd_p, IAPI_BUFFERSIZE, param); + iapi_ReleaseChannel(chNum); + return result; + + /* + * Blocking policy + * --- Changes the blocking policy for the read and write calls. + */ + case IAPI_CHANGE_CHANBLOCK: + result = iapi_ChangeChannelDesc(cd_p, IAPI_BLOCKING, param); + iapi_ReleaseChannel(chNum); + return result; + + /* + * Ownership + * --- Changes direction: turnaround + */ + case IAPI_CHANGE_OWNERSHIP: + result = iapi_ChangeChannelDesc(cd_p, IAPI_OWNERSHIP, param); + iapi_ReleaseChannel(chNum); + return result; + + /* + * Synchronization method + * --- Changes the callback type, default or user. The callback function + * table is set accordingly. + */ + case IAPI_CHANGE_SYNCH: + result = iapi_ChangeChannelDesc(cd_p, IAPI_CALLBACKSYNCH, param); + iapi_ReleaseChannel(chNum); + return result; + + /* + * Trust property + * --- trust can only be changed through ChangeChannelDesc first request, + * this guarantees the close/open sequence for the channel. + */ + case IAPI_CHANGE_TRUST: + result = iapi_ChangeChannelDesc(cd_p, IAPI_TRUST, param); + iapi_ReleaseChannel(chNum); + return result; + + /* + * Callback Interrupt service routine pointer + * --- Cahnges the callback function pointer, when this method is used, to + * replace it with a new one. + */ + case IAPI_CHANGE_CALLBACKFUNC: + result = iapi_ChangeChannelDesc(cd_p, IAPI_CALLBACKISR_PTR, param); + iapi_ReleaseChannel(chNum); + return result; + + /* + * Channel control block pointer + * --- NA + */ + case IAPI_CHANGE_CHANCCB: + result = iapi_ChangeChannelDesc(cd_p, IAPI_CCB_PTR, param); + iapi_ReleaseChannel(chNum); + return result; + +#ifdef MCU + /* + * Priority + * --- Changes the channel priority directly in SDMA register + */ + case IAPI_CHANGE_PRIORITY: + { + volatile unsigned long * ChannelPriorities = &SDMA_CHNPRI_0; + if(param < MAX_CH_PRIORITY) + { + ChannelPriorities[ cd_p->channelNumber ] = param; + } + else + { + iapi_ReleaseChannel(chNum); + return IAPI_FAILURE; + } + } + break; +#endif /* MCU */ + + /* + * Wrap + * --- Set to 1 the wrap bit of the last buffer descriptor of the array. + * it provides the possibility to have a circular buffer structure. + */ + case IAPI_CHANGE_BDWRAP: + { + result = iapi_ChangeChannelDesc(cd_p,IAPI_BDWRAP , param); + iapi_ReleaseChannel(chNum); + return result; + } + + /* + * Watermark + * --- Changes the value of the peripheral watermark level that triggers + * a DMA request. It impacts context of the channel, therefore channel 0 + * must be started to update the context with this new value. + */ + case IAPI_CHANGE_WATERMARK: + { + result = iapi_ChangeChannelDesc(cd_p,IAPI_WML , param); + iapi_ReleaseChannel(chNum); + return result; + } + /* + * INTR + * --- Set the INTR bit on specified BD or on all BD's if SET_BIT_ALL + * is passed as parameter. + */ + case IAPI_CHANGE_SET_BDINTR: + { + bufferDescriptor * bde_p; + int j = 0; + + retvalue = IAPI_SUCCESS; + if(param == SET_BIT_ALL) + { + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + for(j = 0; j < cd_p->bufferDescNumber; j++) + { + bde_p->mode.status |= BD_INTR; + bde_p++; + } + } + else if(param < cd_p->bufferDescNumber) + { + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr) + + param; + bde_p->mode.status |= BD_INTR; + } + else + { + retvalue = IAPI_FAILURE; + } + iapi_ReleaseChannel(chNum); + return retvalue; + } + /* + * INTR + * --- Unset the INTR bit on specified BD or on all BD's if SET_BIT_ALL + * is passed as parameter. + */ + case IAPI_CHANGE_UNSET_BDINTR: + { + bufferDescriptor * bde_p; + + int j = 0; + + retvalue = IAPI_SUCCESS; + if(param == SET_BIT_ALL) + { + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + for(j = 0; j < cd_p->bufferDescNumber; j++) + { + bde_p->mode.status &= ~BD_INTR; + bde_p++; + } + } + else if(param < cd_p->bufferDescNumber) + { + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr) + + param; + bde_p->mode.status &= ~BD_INTR; + } + else + { + retvalue = IAPI_FAILURE; + } + iapi_ReleaseChannel(chNum); + return retvalue; + } +/* + * EventMask1 + * --- Changes the value of the eventMask1 + */ + case IAPI_CHANGE_EVTMASK1: + { + cd_p->eventMask1 = param; + } + break; + /* + * EventMask2 + * --- Changes the value of the eventMask2 + */ + case IAPI_CHANGE_EVTMASK2: + { + cd_p->eventMask2 = param; + } + break; + /* + * Peripheral Address + * --- Changes the value of the peripheralAddr + */ + case IAPI_CHANGE_PERIPHADDR: + { + cd_p->peripheralAddr = param; + } + break; + /* + * Cont + * --- Set the CONT bit on specified BD on all BD's if SET_BIT_ALL + * is passed as parameter. + */ + case IAPI_CHANGE_SET_BDCONT: + { + bufferDescriptor * bde_p; + int j = 0; + + retvalue = IAPI_SUCCESS; + if(param == SET_BIT_ALL) + { + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + for(j = 0; j < cd_p->bufferDescNumber; j++) + { + bde_p->mode.status |= BD_CONT; + bde_p++; + } + } + else if(param < cd_p->bufferDescNumber) + { + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr) + + param; + bde_p->mode.status |= BD_CONT; + } + else + { + retvalue = IAPI_FAILURE; + } + iapi_ReleaseChannel(chNum); + return retvalue; + } + /* + * Cont + * --- Unset the CONT bit on specified BD or on all BD's if SET_BIT_ALL + * is passed as parameter. + */ + case IAPI_CHANGE_UNSET_BDCONT: + { + bufferDescriptor * bde_p; + + int j = 0; + + retvalue = IAPI_SUCCESS; + if(param == SET_BIT_ALL) + { + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + for(j = 0; j < cd_p->bufferDescNumber; j++) + { + bde_p->mode.status &= ~BD_CONT; + bde_p++; + } + } + else if(param < cd_p->bufferDescNumber) + { + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr) + + param; + bde_p->mode.status &= ~BD_CONT; + } + else + { + retvalue = IAPI_FAILURE; + } + iapi_ReleaseChannel(chNum); + return retvalue; + } + + /* + * EXTD + * --- Set the EXTD bit on specified BD or on all BD's if SET_BIT_ALL + * is passed as parameter. + */ + case IAPI_CHANGE_SET_BDEXTD: + { + bufferDescriptor * bde_p; + int j = 0; + + retvalue = IAPI_SUCCESS; + if(param == SET_BIT_ALL) + { + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + for(j = 0; j < cd_p->bufferDescNumber; j++) + { + bde_p->mode.status |= BD_EXTD; + bde_p++; + } + } + else if(param < cd_p->bufferDescNumber) + { + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr) + + param; + bde_p->mode.status |= BD_EXTD; + } + else + { + retvalue = IAPI_FAILURE; + } + iapi_ReleaseChannel(chNum); + return retvalue; + } + /* + * EXTD + * --- Unset the EXTD bit on specified BD or on all BD's if SET_BIT_ALL + * is passed as parameter. + */ + case IAPI_CHANGE_UNSET_BDEXTD: + { + bufferDescriptor * bde_p; + + int j = 0; + + retvalue = IAPI_SUCCESS; + if(param == SET_BIT_ALL) + { + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + for(j = 0; j < cd_p->bufferDescNumber; j++) + { + bde_p->mode.status &= ~BD_EXTD; + bde_p++; + } + } + else if(param < cd_p->bufferDescNumber) + { + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr) + + param; + bde_p->mode.status &= ~BD_EXTD; + } + else + { + retvalue = IAPI_FAILURE; + } + iapi_ReleaseChannel(chNum); + return retvalue; + } + + /* + * TRANSFER SIZE to be used for this channel + * --- Set the transfer size used indicator and code for transfer size in + * the CD + */ +case IAPI_CHANGE_SET_TRANSFER_CD: + { + bufferDescriptor * bde_p; + int j = 0; + retvalue = IAPI_SUCCESS; + if((param == TRANSFER_8BIT) || (param == TRANSFER_16BIT) || + (param == TRANSFER_24BIT) || (param == TRANSFER_32BIT)) + { + cd_p->useDataSize = TRUE; + cd_p->dataSize = param; + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + for(j = 0; j < cd_p->bufferDescNumber; j++) + { + bde_p->mode.command = param; + bde_p++; + } + } + else + { + retvalue = IAPI_FAILURE; + } + iapi_ReleaseChannel(chNum); + return retvalue; + } + + + /* + * USER_ARG + * --- Set the user selectable pointer to be received by the callback + * function, if IRQ synch is used + */ + case IAPI_CHANGE_USER_ARG: + { + userArgTable[cd_p->channelNumber]= (void*)param; + iapi_ReleaseChannel(chNum); + return IAPI_SUCCESS; + } + /* + * FORCE_CLOSE + * --- Set the forceClose bit in channelDescriptor to value passed in param. + * If this bit is TRUE, the channel in closed even if some BD are still + * owned by the SDMA. + */ + case IAPI_CHANGE_FORCE_CLOSE: + { + retvalue = IAPI_SUCCESS; + if((param == TRUE) || (param == FALSE)) + { + cd_p->forceClose = param; + } + else + { + iapi_errno = IAPI_ERR_INVALID_PARAMETER | cd_p->channelNumber; + retvalue = -iapi_errno; + } + iapi_ReleaseChannel(chNum); + return retvalue; + } + /* + * TRANSFER type + * --- Set the last 2 bits in the command field of the BD to specify the + * transfer type 8, 16, 24, or 32 bits on all BD's, allready set in the CD + */ + case IAPI_CHANGE_SET_TRANSFER: + { + bufferDescriptor * bde_p; + int j = 0; + + retvalue = IAPI_SUCCESS; + if((param == TRANSFER_8BIT)||(param == TRANSFER_16BIT)|| + (param == TRANSFER_24BIT)||(param == TRANSFER_32BIT)) + { + bde_p = cd_p->ccb_ptr->baseBDptr; + for(j = 0; j < cd_p->bufferDescNumber; j++) + { + bde_p->mode.command = param; + bde_p++; + } + } + else + { + retvalue = IAPI_FAILURE; + } + iapi_ReleaseChannel(chNum); + return retvalue; + } + /* + * BUFFER address + * --- Change buffer address in BD specified in the upper 16 bits of the + * ctlRequest. + */ + case IAPI_CHANGE_SET_BUFFERADDR: + { + bufferDescriptor * bde_p; + retvalue = IAPI_SUCCESS; + + /* Get pointer to the BD structure to change*/ + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + bde_p+= bd_num; + + /* DO NOT translate address to physical */ + bde_p->bufferAddr = (void*)param; + + iapi_ReleaseChannel(chNum); + return retvalue; + } + /* + * BUFFER address + * --- Get the buffer address from the BD specified in the upper 16 bits of the + * ctlRequest. + */ + case IAPI_CHANGE_GET_BUFFERADDR: + { + bufferDescriptor * bde_p; + retvalue = IAPI_SUCCESS; + + /* Get pointer to the BD structure to change*/ + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + bde_p+= bd_num; + /* Translate to virtual*/ + *((unsigned long*)param) = (unsigned long)bde_p->bufferAddr; + + iapi_ReleaseChannel(chNum); + return retvalue; + } + /* + * EXTENDED BUFFER address + * --- Change extended buffer address in BD specified in the upper 16 bits + * of the ctlRequest. + */ + case IAPI_CHANGE_SET_EXTDBUFFERADDR: + { + bufferDescriptor * bde_p; + retvalue = IAPI_SUCCESS; + + /* Get pointer to the BD structure to change*/ + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + bde_p+= bd_num; + + /* DO NOT translate address to physical. The user might want something else + *here + */ + bde_p->extBufferAddr = (void*)param; + + iapi_ReleaseChannel(chNum); + return retvalue; + } + /* + * EXTENDED BUFFER address + * --- Get extended buffer address from the BD specified in the upper 16 bits + * of the ctlRequest. + */ + case IAPI_CHANGE_GET_EXTDBUFFERADDR: + { + bufferDescriptor * bde_p; + retvalue = IAPI_SUCCESS; + + /* Get pointer to the BD structure to change*/ + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + bde_p+= bd_num; + + /* DO NOT translate address to vitual - user knows what is here. + */ + *((unsigned long*)param) = (unsigned long)bde_p->extBufferAddr; + + iapi_ReleaseChannel(chNum); + return retvalue; + } + /* + * COMMAND field + * --- Change command field in BD specified in the upper 16 bits of the + * ctlRequest. + */ + case IAPI_CHANGE_SET_COMMAND: + { + bufferDescriptor * bde_p; + retvalue = IAPI_SUCCESS; + + /* Get pointer to the BD structure to change*/ + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + bde_p+= bd_num; + /* Update command field*/ + bde_p->mode.command = param; + + iapi_ReleaseChannel(chNum); + return retvalue; + } + /* + * COMMAND field + * --- Get the command field from the BD specified in the upper 16 bits + * of the ctlRequest. + */ + case IAPI_CHANGE_GET_COMMAND: + { + bufferDescriptor * bde_p; + retvalue = IAPI_SUCCESS; + + /* Get pointer to the BD structure to change*/ + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + bde_p+= bd_num; + /* Get the command field*/ + *((unsigned long*)param) = bde_p->mode.command; + + iapi_ReleaseChannel(chNum); + return retvalue; + } + /* + * COUNT field + * --- Change count field in BD specified in the upper 16 bits of the + * ctlRequest. + */ + case IAPI_CHANGE_SET_COUNT: + { + bufferDescriptor * bde_p; + retvalue = IAPI_SUCCESS; + + /* Get pointer to the BD structure to change*/ + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + bde_p+= bd_num; + /* Update count field*/ + bde_p->mode.count = param; + + iapi_ReleaseChannel(chNum); + return retvalue; + } + /* + * COUNT field + * --- Get the count field of the BD specified in the upper 16 bits of the + * ctlRequest. + */ + case IAPI_CHANGE_GET_COUNT: + { + bufferDescriptor * bde_p; + retvalue = IAPI_SUCCESS; + + /* Get pointer to the BD structure to change*/ + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + bde_p+= bd_num; + /* Update count field*/ + *((unsigned long*)param) = bde_p->mode.count; + + iapi_ReleaseChannel(chNum); + return retvalue; + } + /* + * STATUS field + * --- Change status field in BD specified in the upper 16 bits of the + * ctlRequest. + */ + case IAPI_CHANGE_SET_STATUS: + { + bufferDescriptor * bde_p; + retvalue = IAPI_SUCCESS; + + /* Get pointer to the BD structure to change*/ + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + bde_p+= bd_num; + /* Update status field*/ + bde_p->mode.status = param; + + iapi_ReleaseChannel(chNum); + return retvalue; + } + /* + * STATUS field + * --- Get the status field of the BD specified in the upper 16 bits + * of the ctlRequest. + */ + case IAPI_CHANGE_GET_STATUS: + { + bufferDescriptor * bde_p; + retvalue = IAPI_SUCCESS; + + /* Get pointer to the BD structure to change*/ + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + bde_p+= bd_num; + /* Update status field*/ + *((unsigned long*)param) = bde_p->mode.status; + + iapi_ReleaseChannel(chNum); + return retvalue; + } + +#ifdef MCU + /* + * Endianness + * --- Set the ENDIANNESS indicator in the command filed of the specified BD + * or on all BD's if SET_BIT_ALL is passed as parameter. + */ + case IAPI_CHANGE_SET_ENDIANNESS: + { + bufferDescriptor * bde_p; + int j = 0; + + retvalue = IAPI_SUCCESS; + if(param == SET_BIT_ALL) + { + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + for(j = 0; j < cd_p->bufferDescNumber; j++) + { + bde_p->mode.command = CHANGE_ENDIANNESS; + bde_p++; + } + } + else if(param < cd_p->bufferDescNumber) + { + bde_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr) + + param; + bde_p->mode.command = CHANGE_ENDIANNESS; + } + else + { + retvalue = IAPI_FAILURE; + } + iapi_ReleaseChannel(chNum); + return retvalue; + } +#endif + +#ifdef SDMA_SKYE +#ifdef MCU + + /* + * SDMA State + * --- Enter the SDMA into LOCK Mode. No RAM updation allowed except same Context + * update with same PC Value. + */ + case IAPI_ENTER_LOCK_MODE: + { + if(param == RESET_CLEAR_LOCK) + { + SDMA_SDMA_LOCK = (1 << RESET_CLR_BIT_OFFSET); + SDMA_SDMA_LOCK = (1 << LOCK_BIT_OFFSET); + iapi_SdmaState = LOCK; + } + else if(param == RESET_NOCLEAR_LOCK) + { + SDMA_SDMA_LOCK = (1 << LOCK_BIT_OFFSET); + iapi_SdmaState = LOCK; + } + + } + break; + +#endif +#endif + default: + retvalue = IAPI_ERR_CD_CHANGE_UNKNOWN | IAPI_ERR_CH_AVAILABLE | chNum; + iapi_errno = retvalue; + iapi_ReleaseChannel(chNum); + return -retvalue; + } + + + iapi_ReleaseChannel(chNum); + return IAPI_SUCCESS; +} +/* ***************************************************************************/ +/**Initialization of the SDMA - opening of channel 0, download RAM image. + * + * <b>Algorithm:</b>\n + * - open channel 0 + * - if ram_image pointer passed is not NULL, download RAM image to SDMA + * + * @param + * - cd_p channel descriptor pointer for channel 0 + * - ram_image pointer to RAM image to download, or NULL if this operation + * is not required + * - code_size size of the RAM image, in bytes + * - start_addr start address for the RAM image + * + * @return + * - IAPI_SUCCESS if all operations were successful + * - negated I.API error code if any operation failed + */ +#ifdef MCU +int +iapi_Init(channelDescriptor * cd_p, configs_data * config_p, unsigned short* ram_image, + unsigned short code_size, unsigned long start_addr) +{ +#endif +#ifdef DSP +int +iapi_Init(channelDescriptor * cd_p) +{ +#endif + +int retvalue = IAPI_SUCCESS; /* Variable to store the results from I.API calls */ + + /* Check initialization not allredy done*/ + if(iapi_CCBHead != NULL) + { + retvalue = IAPI_ERR_NOT_ALLOWED; + iapi_errno = retvalue; + return -retvalue; + } + /* Be sure SDMA has not started yet */ +#ifdef MCU + SDMA_H_C0PTR = 0x0; +#endif +#ifdef DSP + SDMA_D_C0PTR = 0x0; +#endif + + /*Try to open channel 0*/ + retvalue = iapi_Open(cd_p, 0); + if(retvalue != IAPI_SUCCESS) + { + return retvalue; + } + +#ifdef MCU + /* Set Command Channel (Channel Zero) */ + SDMA_CHN0ADDR = 0x4050; + + /* Set bits of CONFIG register but with static context switching */ + SDMA_H_CONFIG = (config_p->dspdma << 12) | (config_p->rtdobs << 11) | + (config_p->acr << 4) | (0); + + /* Send the address for the host channel table to the SDMA*/ + SDMA_H_C0PTR = (unsigned long)iapi_Virt2Phys(iapi_CCBHead); + /* If required, download the RAM image for SDMA*/ + if(ram_image != NULL) + { + retvalue = iapi_SetScript(cd_p, (void*)ram_image, code_size, + start_addr); + } + + /* Set bits of CONFIG register with given context switching mode */ + SDMA_H_CONFIG = (config_p->dspdma << 12) | (config_p->rtdobs << 11) | + (config_p->acr << 4) | (config_p->csm); + +#endif +#ifdef DSP + /* Send the address for the host channel table to the SDMA*/ + SDMA_D_C0PTR = (unsigned long)iapi_Virt2Phys(iapi_CCBHead); +#endif + +#ifdef SDMA_SKYE + iapi_SdmaState = OPEN; +#endif + + return retvalue; +} + + +/* ***************************************************************************/ +/**High layer interface for starting a channel + * + * <b>Algorithm:</b>\n + * - call low layer function for starting a channel + * + * @return + * - IAPI_SUCCESS + */ +int +iapi_StartChannel(unsigned char channel) +{ + iapi_lowStartChannel(channel); + return IAPI_SUCCESS; +} +/* ***************************************************************************/ +/**High layer interface for stopping a channel + * + * <b>Algorithm:</b>\n + * - call low layer function for stopping a channel + * + * @return + * - IAPI_SUCCESS + */ +int +iapi_StopChannel(unsigned char channel) +{ + iapi_lowStopChannel(channel); + return IAPI_SUCCESS; +} + +/* ***************************************************************************/ +/**High layer interface for synchronising a channel + * + * <b>Algorithm:</b>\n + * - call low layer function for stopping a channel + * + * @return + * - IAPI_SUCCESS + */ +int iapi_SynchChannel(unsigned char channel) +{ + iapi_lowSynchChannel(channel); + return IAPI_SUCCESS; +} + +#ifdef MCU +/* ***************************************************************************/ +/**High layer interface for getting program memory data from SDMA + * + * <b>Algorithm:</b>\n + * - call coresponding low layer function + * + * @return + * - IAPI_SUCCESS + */ +int +iapi_GetScript(channelDescriptor * cd_p, void * buf, unsigned short size, + unsigned long address) +{ + iapi_lowGetScript(cd_p, buf, size, address); + return IAPI_SUCCESS; +} + +/* ***************************************************************************/ +/**High layer interface for getting data memory from SDMA + * + * <b>Algorithm:</b>\n + * - call coresponding low layer function + * + * @return + * - IAPI_SUCCESS + */ +int +iapi_GetContext(channelDescriptor * cd_p, void * buf, + unsigned char channel) +{ + iapi_lowGetContext(cd_p, buf, channel); + return IAPI_SUCCESS; +} + +/* ***************************************************************************/ +/**High layer interface for set program memory data to SDMA - e.g. scripts + * + * <b>Algorithm:</b>\n + * - call coresponding low layer function + * + * @return + * - IAPI_SUCCESS + */ +int +iapi_SetScript(channelDescriptor * cd_p, void * buf, unsigned short nbyte, + unsigned long destAddr) +{ + iapi_lowSetScript(cd_p, buf, nbyte, destAddr); + return IAPI_SUCCESS; +} + +/* ***************************************************************************/ +/**High layer interface for set data memory to SDMA - e.g. contexts. + * + * <b>Algorithm:</b>\n + * - call coresponding low layer function + * + * @return + * - IAPI_SUCCESS + */ +int +iapi_SetContext(channelDescriptor * cd_p, void * buf, + unsigned char channel) +{ + iapi_lowSetContext(cd_p, buf, channel); + return IAPI_SUCCESS; +} + +/* ***************************************************************************/ +/**High layer interface used to associate specified channel with a script. + * + * <b>Algorithm:</b>\n + * - call coresponding low layer function + * + * @return + * - IAPI_SUCCESS + */ +int +iapi_AssignScript(channelDescriptor * cd_p, script_data * data_p) +{ + /* VERIFY THAT THE CHANNEL IT IS OPENED !!!!*/ + return iapi_lowAssignScript(cd_p, data_p); +} + +/* ***************************************************************************/ +/**High layer interface used to associate specified channel with a script. + * + * <b>Algorithm:</b>\n + * - call coresponding low layer function + * + * @return + * - IAPI_SUCCESS + */ +int +iapi_SetChannelEventMapping(unsigned char event, unsigned long channel_map) +{ + return iapi_lowSetChannelEventMapping(event, channel_map); +} +#endif + + + +#ifdef DSP +#define SDMA_DI SDMA_D_INTR +void IRQ_Handler(); +#pragma interrupt IRQ_Handler +#endif + +#ifdef MCU +#define SDMA_DI SDMA_H_INTR +#endif + +#ifndef IRQ_KEYWORD +#define IRQ_KEYWORD +#endif /* IRQ_KEYWORD */ + +/* ***************************************************************************/ +/** + *@brief Find the first set bit in data parameter. + * + * Find the first set bit in unsigned integer parameter data. Data is scanned + * from MSB to LSB, searching for the set bit. The value returned is the + * offset from the most significant bit of data. If bit 31 is set, the value + * returned is zero. If no bits are set, a value of 32 is returned. This is compliant + * with the MCore FF1 instruction. + * + * + * + * @param + * - data: variable to check + * + * @return + * - the offset of the most significant bit set from the MSB + */ +unsigned int +quartz_FF1( unsigned int data ) +{ + register unsigned int result = 0; + while ( (result <= 31 ) && !( data & 0x80000000U) ) + { + data <<= 1U; + result++; + } + + return result; +} + +IRQ_KEYWORD +void +IRQ_Handler(void) +{ + unsigned int intrReg;/* interrupt register mask for clearing the interrupt bit */ + unsigned char chNum; /* SDMA channel number generating the a IRQ*/ + + /* Disable interrupts */ + iapi_DisableInterrupts(); + /* + * Clear interrupt in SDMA DI register => ACK to the SDMA the IT request. + * Get each interrupt number, clear them one after the other. + */ + if(SDMA_DI != 0) + { + chNum = (unsigned char)(CH_NUM - 1 - quartz_FF1(SDMA_DI)); + intrReg = (unsigned int)(1 << chNum); + } + else + { + chNum = 32; + intrReg = 0; + } + + while (intrReg != 0) + { + SDMA_DI &= intrReg; + iapi_SDMAIntr |= intrReg; + iapi_WakeUp(chNum); + if (callbackIsrTable[chNum] != NULL) + { + /* release channel before callback, so IoCtl's are available*/ + iapi_ReleaseChannel(chNum); + callbackIsrTable[chNum](iapi_CCBHead[chNum].channelDescriptor, + userArgTable[chNum]); + } + + chNum = (unsigned char)(CH_NUM - 1 - quartz_FF1(SDMA_DI)); + intrReg = (unsigned int)(1 << chNum); + } + + /* Enable interrupts */ + iapi_EnableInterrupts(); +} + +/* ***************************************************************************/ +/** + *@brief Perform a memory copy operation, in the memory of the same processor + * + * Size bytes are copied from the src address to dest address. It is used + * the channel pointed by cd_p, which must be configured prior to this call: + * opened, associated with the script to perform the operation - DSP_2_DSP, + * or MCU_2_MCU - and have the synchronization option set. + * + * + * + * @param + * - cd_p: channel configured to perform DSP_2_DSP or MCU_2_MCU transfers + * - dest: destination memory address + * - src : source memory address + * - size: number of bytes to copy from src to dest + * + * @return + * - the offset of the most significant bit set from the MSB + */ + +int iapi_MemCopy(channelDescriptor * cd_p, void* dest, void* src, unsigned long size) +{ + int result = IAPI_SUCCESS; + bufferDescriptor * bd_p; + + /* Channel descriptor validity */ + if (cd_p == NULL) + { + result = IAPI_ERR_CD_UNINITIALIZED; + iapi_errno = result; + return -result; + } + + /* Check and set correct parameter */ + if(cd_p->trust != TRUE) + { + result = iapi_ChangeChannelDesc(cd_p, IAPI_TRUST, TRUE); + } + + if(cd_p->bufferDescNumber != 1) + { + result = iapi_ChangeChannelDesc(cd_p, IAPI_BUFFERDESCNUMBER, 1); + if(result != IAPI_SUCCESS) + { + return result; + } + } + + if(cd_p->bufferSize != size) + { + result = iapi_ChangeChannelDesc(cd_p, IAPI_BUFFERSIZE, size); + if(result != IAPI_SUCCESS) + { + return result; + } + } + /* Set addresses*/ + bd_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + bd_p->bufferAddr = iapi_Virt2Phys(src); + bd_p->extBufferAddr = iapi_Virt2Phys(dest); + + /* Set mode*/ + bd_p->mode.count = size; + bd_p->mode.command = 0x00; + bd_p->mode.status = BD_INTR|BD_EXTD|BD_DONE|BD_WRAP; + + /*Decide if we sleep or not*/ + if(cd_p->callbackSynch == DEFAULT_POLL) + { + iapi_StartChannel(cd_p->channelNumber); + /* Call synchronization routine*/ + iapi_SynchChannel(cd_p->channelNumber); + } + else + { + /* Just start the channel*/ + iapi_StartChannel(cd_p->channelNumber); + } + + return result; +} + +/* ***************************************************************************/ +/**Return the channel number from the channel descriptor + * + * @param cd_p pointer to channel descriptor to obtain the channel number + * + * @return + * - the channel number + * + */ +int iapi_GetChannelNumber(channelDescriptor * cd_p) +{ + return cd_p->channelNumber; +} + +/* ***************************************************************************/ +/**Return the error bit from the current BD of the channel + * + * + * @param cd_p pointer to channel descriptor + * + * @return + * - 0 if no error detected + * - BD_RROR | DATA_ERROR if error detected + * + */ +unsigned long iapi_GetError(channelDescriptor * cd_p) +{ + return ((cd_p->ccb_ptr->currentBDptr->mode.status & BD_RROR) | + (*(unsigned long*)&cd_p->ccb_ptr->status & DATA_ERROR)); +} + +/* ***************************************************************************/ +/**Return the count from the current BD of the channel + * + * + * @param cd_p pointer to channel descriptor + * + * @return + * - count field of the current BD for the channel + * + */ +int iapi_GetCount(channelDescriptor * cd_p) +{ + return (int)(cd_p->ccb_ptr->currentBDptr->mode.count); +} + +/* ***************************************************************************/ +/**Return the sum of counts for all the BD's owned by the processor for + * the channel specified by the received parameter. + * + * + * @param cd_p pointer to channel descriptor + * + * @return + * - sum of count fields + * + */ +int iapi_GetCountAll(channelDescriptor * cd_p) +{ + int retval = 0; + int i = 0; + bufferDescriptor* bd_p; + + bd_p = cd_p->ccb_ptr->baseBDptr; + + while((i < cd_p->bufferDescNumber) && ((bd_p->mode.status & BD_DONE) == 0)) + { + retval += bd_p->mode.count; + i++; + bd_p++; + } + return retval; +} diff --git a/arch/arm/plat-mxc/sdma/iapi/src/iapiLow.c b/arch/arm/plat-mxc/sdma/iapi/src/iapiLow.c new file mode 100644 index 000000000000..205969f82533 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/src/iapiLow.c @@ -0,0 +1,149 @@ +/****************************************************************************** + * + * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiLow.c + * + * $Id iapiLow.c $ + * + * Description: + * This library is written in C to guarantee functionality and integrity in + * the usage of SDMA virtual DMA channels. This API (Application Programming + * Interface) allow SDMA channels' access in an OPEN, READ, WRITE, CLOSE + * fashion. + * These are the LOW level functions of the I.API. + * + * + * / + * + * $Log iapiLow.c $ + * + *****************************************************************************/ + +/* **************************************************************************** + * Include File Section + *****************************************************************************/ +#include "epm.h" +#include "iapiLow.h" + +/** + * Function Section + */ + + +/* ***************************************************************************/ +/**Records an ISR callback function pointer into the ISR callback + * function table + * + * @param cd_p channel descriptor to attach callback to + * @param func_p pointer to the callback function to be registered + * + * @return none + */ +void +iapi_AttachCallbackISR (channelDescriptor * cd_p, + void (* func_p)(channelDescriptor * cd_p, void * arg)) + +{ + if (cd_p->callbackSynch == CALLBACK_ISR) { + iapi_DisableInterrupts(); + callbackIsrTable[cd_p->channelNumber] = func_p; + iapi_EnableInterrupts(); + } else if (cd_p->callbackSynch == DEFAULT_POLL) { + callbackIsrTable[cd_p->channelNumber] = NULL; + } else { + iapi_errno = IAPI_ERR_CALLBACKSYNCH_UNKNOWN | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + } +} + + +/* ***************************************************************************/ +/**Detaches (removes) an ISR callback function pointer from the ISR callback + * function table + * + * <b>Algorithm:</b>\n + * - Attach a null function to replace the original one. + * + * @param cd_p channel descriptor to detach callback from + * + * @return none + */ +void +iapi_DetachCallbackISR (channelDescriptor * cd_p) + +{ + iapi_AttachCallbackISR (cd_p, NULL); +} + +/* ***************************************************************************/ +/**Updates an ISR callback function pointer into the ISR callback function + * table + * + * <b>Algorithm:</b>\n + * - Detach the old function pointer (if any) and attach the new one + * + * @param cd_p channel descriptor to attach callback to + * @param func_p pointer to the callback function to be registered + * + * @return none + */ +void +iapi_ChangeCallbackISR (channelDescriptor * cd_p, + void (* func_p)(channelDescriptor * cd_p, void * arg)) +{ + iapi_DetachCallbackISR(cd_p); + iapi_AttachCallbackISR(cd_p, func_p); +} + +/* ***************************************************************************/ +/**Loop while the channel is not done on the SDMA + * + * <b>Algorithm:</b>\n + * - Loop doing nothing but checking the I.API global variable to indicate + * that the channel has been completed (interrupt from SDMA) + * + * <b>Notes:</b>\n + * - The ISR must update the I.API global variable iapi_SDMAIntr. + * + * @param channel channel number to poll on + * + * @return none + */ +void +iapi_lowSynchChannel (unsigned char channel) +{ + while (!((1UL << channel) & iapi_SDMAIntr)) ; + iapi_SDMAIntr &= ~(1UL << channel); +} + +/* ***************************************************************************/ +/**Fill the buffer descriptor with the values given in parameter. + * + * @return none + */ +void +iapi_SetBufferDescriptor( bufferDescriptor * bd_p, unsigned char command, + unsigned char status, unsigned short count, + void * buffAddr, void * extBufferAddr) +{ + bd_p->mode.command = command; + bd_p->mode.status = status; + bd_p->mode.count = count; + if (buffAddr != NULL) { + bd_p->bufferAddr = iapi_Virt2Phys(buffAddr); + } else { + bd_p->bufferAddr = buffAddr; + } + bd_p->extBufferAddr = extBufferAddr; +} diff --git a/arch/arm/plat-mxc/sdma/iapi/src/iapiLowDsp.c b/arch/arm/plat-mxc/sdma/iapi/src/iapiLowDsp.c new file mode 100644 index 000000000000..df7d44a9bd46 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/src/iapiLowDsp.c @@ -0,0 +1,79 @@ +/****************************************************************************** + * + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiLowDsp.c + * + * $Id iapiLowDsp.c $ + * + * Description: + * This library is written in C to guarantee functionality and integrity in + * the usage of SDMA virtual DMA channels. This API (Application Programming + * Interface) allow SDMA channels' access in an OPEN, READ, WRITE, CLOSE + * fashion. + * These are the LOW level functions of the I.API specific to MCU. + * + * + * + * + * $Log iapiLowDsp.c $ + * + *****************************************************************************/ + +/* **************************************************************************** + * Include File Section + *****************************************************************************/ +#include "epm.h" +#include "iapiLow.h" + +/* **************************************************************************** + * Function Section + *****************************************************************************/ +#ifdef DSP + +/* ***************************************************************************/ +/**Starts the channel (core specific register) + * + * <b>Algorithm:</b>\n + * - Bit numbered "channel" of DspEnStartReg register is set + * + * @param channel channel to start + * + * @return none + */ +void +iapi_lowStartChannel (unsigned char channel) +{ + SDMA_D_START |= (1 << channel); +} + +/* ***************************************************************************/ +/**Stops the channel (core specific register) + * + * <b>Algorithm:</b> + * - Bit numbered "channel" of DspEnStopReg register is cleared + * + * <b>Notes:</b>\n + * - This is a write one to clear register + * + * @param channel channel to stop + * + * @return none + */ +void +iapi_lowStopChannel (unsigned char channel) +{ + SDMA_D_STATSTOP &= (1 << channel); +} + +#endif /* DSP */ diff --git a/arch/arm/plat-mxc/sdma/iapi/src/iapiLowMcu.c b/arch/arm/plat-mxc/sdma/iapi/src/iapiLowMcu.c new file mode 100644 index 000000000000..886c70c4c3d3 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/src/iapiLowMcu.c @@ -0,0 +1,518 @@ +/****************************************************************************** + * + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiLowMcu.c + * + * $Id iapiLowMcu.c $ + * + * Description: + * This library is written in C to guarantee functionality and integrity in + * the usage of SDMA virtual DMA channels. This API (Application Programming + * Interface) allow SDMA channels' access in an OPEN, READ, WRITE, CLOSE + * fashion. + * These are the LOW level functions of the I.API specific to MCU. + * + * + * http://compass/mot.com/go/115342679 + * + * $Log iapiLowMcu.c $ + * + *****************************************************************************/ + +/* **************************************************************************** + * Include File Section + *****************************************************************************/ +#include <string.h> + +#include "epm.h" +#include "iapiLow.h" + +/* **************************************************************************** + * Function Section + *****************************************************************************/ +#ifdef MCU + +/* ***************************************************************************/ +/**Send a command on SDMA's channel zero. + * Check if buffer descriptor is already used by the sdma, if yes return + * an error as c0BDNum is wrong. + * + * <b>Notes</b>\n + * There is an upgrade in the script on the Context load command and + * the fact that the context structure has a fixed length of 20 or 24 + * depending on SDMA versions. + * + * @return + * - IAPI_SUCCESS + * - -iapi_errno if failure + */ +int +iapi_Channel0Command( channelDescriptor * cd_p, void * buf, + unsigned short nbyte, unsigned char command) +{ + channelControlBlock * ccb_p; + bufferDescriptor * bd_p; + int result = IAPI_SUCCESS; + unsigned char chNum; + + + /* + * Check data structures are properly initialized + */ + /* Channel descriptor validity */ + if (cd_p == NULL){ + result = IAPI_ERR_CD_UNINITIALIZED; + iapi_errno = result; + return -result; + } + + /* Channel control block validity */ + if (cd_p->ccb_ptr == NULL){ + result = IAPI_ERR_CCB_UNINITIALIZED; + iapi_errno = result; + return -result; + } + + /* Control block & Descriptpor associated with the channel being worked on */ + chNum = cd_p->channelNumber; + ccb_p = cd_p->ccb_ptr; + + /* Is channel already in use ? */ + if (ccb_p->baseBDptr != NULL ) { + result = IAPI_ERR_BD_ALLOCATED | IAPI_ERR_CH_AVAILABLE | chNum; + iapi_errno = result; + return -result; + } + + /* Allocation of buffer descriptors */ + bd_p = (bufferDescriptor *)MALLOC(sizeof( bufferDescriptor ), SDMA_ERAM); + if (bd_p != NULL) { + ccb_p->baseBDptr = (bufferDescriptor *)iapi_Virt2Phys(bd_p); + } else { + result = IAPI_ERR_BD_ALLOCATION | IAPI_ERR_CH_AVAILABLE | chNum; + iapi_errno = result; + return -result; + } + + /* Buffer descriptor setting */ + iapi_SetBufferDescriptor( bd_p, command , BD_WRAP|BD_DONE|BD_INTR , nbyte, + buf,NULL); + + /* Actually the transfer */ + iapi_lowStartChannel( cd_p->channelNumber ); + iapi_lowSynchChannel( cd_p->channelNumber ); + + /* Cleaning of allocation */ + FREE( bd_p ); + ccb_p->baseBDptr = NULL; + + return IAPI_SUCCESS; + +} + +/* ***************************************************************************/ +/**Starts the channel (core specific register) + * + * <b>Algorithm:</b>\n + * - Bit numbered "channel" of HostEnStartReg register is set + * + * @param channel channel to start + * + * @return none + */ +void +iapi_lowStartChannel (unsigned char channel) +{ + SDMA_H_START |= 1 << channel; +} + +/* ***************************************************************************/ +/**Stops the channel (core specific register) + * + * <b>Algorithm:</b> + * - Bit numbered "channel" of HostEnStopReg register is cleared + * + * <b>Notes:</b>\n + * - This is a write one to clear register + * + * @param channel channel to stop + * + * @return none + */ +void +iapi_lowStopChannel (unsigned char channel) +{ + SDMA_H_STATSTOP &= 1 << channel; +} + +/* ***************************************************************************/ +/**Initialize the initial priority of registers and channel enable + * RAM from the MCU side. No channels are enabled, all priorities are set to 0. + * + * @return none + */ +void +iapi_InitChannelTables(void) +{ + + /* No channel is enabled*/ + iapi_memset((void *)&SDMA_CHNENBL_0, 0x00, sizeof(unsigned long)*EVENTS_NUM); + /* All channels have priority 0*/ + iapi_memset((void *)&SDMA_CHNPRI_0, 0x00, sizeof(unsigned long)*CH_NUM); +} + +/* ***************************************************************************/ +/** The host enable (HE), hosts override (HO), dsp enable (DE), dsp override + * (DO) registers are involved here. + * Host and Dsp enable registers are here to signify that the MCU or DSP side + * have prepared the appropriate buffers and are now ready. If the channel is + * owned by the MCU the override bit for that channel needs to be cleared : + * the host allows the channel to be used.\n + * + * Then the override bits can define (mcuOverride dspOverride):\n + * - 0 0 channel is public: transfer to/from MCU to DSP + * - 0 1 channel if owned by DSP + * - 1 0 channel if owned by MCU + * - 1 1 channel zero config + * + * See also :\n + * IAPI Table 1.1 "Channel configuration properties" + * + * @param channel channel to configure + * @param eventOverride event ownership + * @param mcuOverride ARM ownership + * @param dspOverride DSP ownership + * + * @return + * - -iapi_errno if the 3 override parameters are all set + * - IAPI_SUCCESS in other cases (valid cases) + */ +int +iapi_ChannelConfig (unsigned char channel, unsigned eventOverride, + unsigned mcuOverride, unsigned dspOverride) +{ + int result = IAPI_SUCCESS; + + if ( ( eventOverride == 1 ) && + ( mcuOverride == 1 ) && + ( dspOverride == 1 ) ){ + result = IAPI_ERR_CONFIG_OVERRIDE ; + iapi_errno = result; + return -result; + } else { + /* + * DSP side + */ + if ( dspOverride ){ + SDMA_H_DSPOVR &= ~( 1 << channel ); + } else { + SDMA_H_DSPOVR |= ( 1 << channel ); + } + /* + * Event + */ + if ( eventOverride ){ + SDMA_H_EVTOVR &= ~( 1 << channel ); + } else { + SDMA_H_EVTOVR |= ( 1 << channel ); + } + /* + * MCU side + */ + if ( mcuOverride ) { + SDMA_H_HOSTOVR &= ~( 1 << channel ); + } else { + SDMA_H_HOSTOVR |= ( 1 << channel ); + } + } + return IAPI_SUCCESS; +} +/* ***************************************************************************/ +/**Load the context data of a channel from SDMA + * + * <b>Algorithm:</b>\n + * - Setup BD with appropiate parameters + * - Start channel + * - Poll for answer + * + * @param *cd_p channel descriptor for channel 0 + * @param *buf pointer to receive context data + * @param channel channel for which the context data is requested + * + * @return none + */ +void +iapi_lowGetContext(channelDescriptor * cd_p, void * buf, unsigned char channel) +{ + bufferDescriptor * bd_p; + + bd_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + + /*Setup buffer descriptor with channel 0 command*/ + iapi_SetBufferDescriptor(&bd_p[0], + C0_GETDM, + (unsigned char)(BD_DONE | BD_INTR | BD_WRAP | BD_EXTD), + (unsigned short)sizeof(contextData)/4, + buf, + (void *)(CHANNEL_CONTEXT_BASE_ADDRESS + (sizeof(contextData)*channel/4))); + /* Receive, polling method*/ + iapi_lowStartChannel(cd_p->channelNumber); + iapi_lowSynchChannel(cd_p->channelNumber); +} +/* ***************************************************************************/ +/**Read "size" byte /2 at SDMA address (address) and write them in buf + * + * <b>Algorithm:</b>\n + * - Setup BD with appropiate parameters (C0_GETPM) + * - Start channel + * - Poll for answer + * + * <b>Notes</b>\n + * - Parameter "size" is in bytes, it represents the size of "buf", e.g. + * the size in bytes of the script to be loaded. + * - Parameter "address" denotes the RAM address for the script in SDMA + * + * @param *cd_p channel descriptor for channel 0 + * @param *buf pointer to receive the data + * @param size number of bytes to read + * @param address address in SDMA RAM to start reading from + * + * @return none + */ +void +iapi_lowGetScript(channelDescriptor * cd_p, void * buf, unsigned short size, + unsigned long address) +{ + bufferDescriptor * bd_p; + + bd_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + + /*Setup buffer descriptor with channel 0 command*/ + iapi_SetBufferDescriptor(&bd_p[0], + C0_GETPM, + (unsigned char)(BD_DONE | BD_INTR | BD_WRAP | BD_EXTD), + (unsigned short)size/2,/*count in shorts*/ + buf, + (void *)address); + /* Receive, polling method*/ + iapi_lowStartChannel(cd_p->channelNumber); + iapi_lowSynchChannel(cd_p->channelNumber); +} + +/* ***************************************************************************/ +/**Load a SDMA script to SDMA + * + * <b>Algorithm:</b>\n + * - Setup BD with appropiate parameters (C0_SETPM) + * - Start channel + * - Poll for answer + * + * <b>Notes</b>\b + * - Parameter "size" is in bytes, it represents the size of "buf", e.g. + * the size in bytes of the script to be uploaded. + * - Parameter "address" denotes the RAM address for the script in SDMA + * + * @param *cd_p channel descriptor for channel 0 + * @param *buf pointer to the script + * @param size size of the script, in bytes + * @param address address in SDMA RAM to place the script + * + * @return none + */ +void +iapi_lowSetScript(channelDescriptor * cd_p, void * buf, unsigned short size, + unsigned long address) +{ + bufferDescriptor * bd_p; + + bd_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + + /*Setup buffer descriptor with channel 0 command*/ + iapi_SetBufferDescriptor(&bd_p[0], + C0_SETPM, + (unsigned char)(BD_DONE | BD_INTR | BD_WRAP | BD_EXTD), + (unsigned short)size/2,/*count in shorts*/ + buf, + (void *)(address)); + /* Receive, polling method*/ + iapi_lowStartChannel(cd_p->channelNumber); + iapi_lowSynchChannel(cd_p->channelNumber); +} + + +/* ***************************************************************************/ +/**Load the context for a channel to SDMA + * + * <b>Algorithm:</b>\n + * - Send context and poll for answer. + * + * @param *cd_p channel descriptor for channel 0 + * @param *buf pointer to context data + * @param channel channel to place the context for + * + * @return none + */ +void +iapi_lowSetContext(channelDescriptor * cd_p, void * buf, unsigned char channel) +{ + + bufferDescriptor * local_bd_p; +#ifdef SDMA_SKYE + + unsigned char command =0; + + local_bd_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + + + command = channel <<3 ; + command = command | C0_SETCTX; + iapi_SetBufferDescriptor( &local_bd_p[0], + command, + (unsigned char)(BD_DONE | BD_INTR | BD_WRAP), + (unsigned short)(sizeof(contextData)/4), + buf, + NULL); +#else + + local_bd_p = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + + + + + iapi_SetBufferDescriptor( &local_bd_p[0], + C0_SETDM, + (unsigned char)(BD_DONE | BD_INTR | BD_WRAP|BD_EXTD), + (unsigned short)(sizeof(contextData)/4), + buf, + (void *)(2048+(sizeof(contextData)/4)*channel)); +#endif + /* Send */ + iapi_lowStartChannel( cd_p->channelNumber ); + iapi_lowSynchChannel( cd_p->channelNumber ); + +} + +/* ***************************************************************************/ +/**Associate specified channel with the script starting at the + * specified address. Channel 0 command is used to load the set-up context + * for the channel. The address used must be generated by the GUI tool + * used to create RAM images for SDMA. + * + * <b>Algorithm:</b>\n + * - Set-up and load the context. + * + * @param *cd_p pointer to the channel descriptor of the channel + * @param *data_p: pointer to the data identifying the script to be associated + * with the channel + * + * @return + * - IAPI_SUCCESS : OK + * - -iapi_errno : operation failed, return negated value of iapi_errno + */ + +int +iapi_lowAssignScript(channelDescriptor * cd_p, script_data * data_p) +{ + contextData * chContext; /* context to be loaded for the channel */ + channelDescriptor * cd0_p; /* pointer to channel descriptor of channel 0*/ + int result = IAPI_SUCCESS; + + /*Verify passed data*/ + if(cd_p == NULL || data_p == NULL) + { + result = IAPI_ERR_INVALID_PARAMETER; + iapi_errno = result; + return -result; + } + + /* Allocate context and initialize PC to required script start adress*/ + chContext = (contextData *) MALLOC(sizeof(contextData), SDMA_ERAM); + if (chContext == NULL) + { + result = IAPI_ERR_B_ALLOC_FAILED | cd_p->channelNumber; + iapi_errno = result; + return -result; + } + + iapi_memset(chContext, 0x00, sizeof(contextData)); + chContext->channelState.pc = data_p->load_address; + + /* Send by context the event mask,base address for peripheral + * and watermark level + */ + chContext->gReg[0] = data_p->event_mask2; + chContext->gReg[1] = data_p->event_mask1; + chContext->gReg[6] = data_p->shp_addr; + chContext->gReg[7] = data_p->wml; + if (data_p->per_addr) + chContext->gReg[2] = data_p->per_addr; + + /* Set transmited data to the CD*/ + cd_p->watermarkLevel = data_p->wml; + cd_p->eventMask1 = data_p->event_mask1; + cd_p->eventMask2 = data_p->event_mask2; + + /* Get the cd0_p*/ + cd0_p = (cd_p->ccb_ptr - cd_p->channelNumber)->channelDescriptor; + + /*load the context*/ + iapi_lowSetContext(cd0_p, chContext, cd_p->channelNumber); + + /* release allocated memory*/ + FREE(chContext); + + return IAPI_SUCCESS; +} + +/* ***************************************************************************/ +/** Set the channels to be triggered by an event. The for every channel that + *must be triggered by the event, the corresponding bit from channel_map + *parameter must be set to 1. (e.g. for the event to trigger channels 31 and + *0 one must pass 0x80000001) + * + * + * <b>Algorithm:</b>\n + * - Update the register from Channel Enable RAM with the channel_map + * + * @param event event for which to set the channel association + * @param channel_map channels to be triggered by event. Put the corresponding + * bit from this 32-bit value to 1 for every channel that should be + * triggered by the event. + * + * @return + * - IAPI_SUCCESS : OK + * - -iapi_errno : operation failed, return negated value of iapi_errno + */ +int +iapi_lowSetChannelEventMapping(unsigned char event, unsigned long channel_map) +{ + volatile unsigned long * channelEnableMatx; + int result = IAPI_SUCCESS; + + /* Check validity of event*/ + if (event < EVENTS_NUM) + { + channelEnableMatx = &SDMA_CHNENBL_0; + channelEnableMatx[event] |= channel_map; + return result; + } + else + { + result = IAPI_ERR_INVALID_PARAMETER | event; + iapi_errno = result; + return -result; + } +} + +#endif /* MCU */ diff --git a/arch/arm/plat-mxc/sdma/iapi/src/iapiMiddle.c b/arch/arm/plat-mxc/sdma/iapi/src/iapiMiddle.c new file mode 100644 index 000000000000..b2423ed2fbd6 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/src/iapiMiddle.c @@ -0,0 +1,623 @@ +/****************************************************************************** + * + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiMiddle.c + * + * $Id iapiMiddle.c $ + * + * Description: + * This library is written in C to guarantee functionality and integrity in + * the usage of SDMA virtual DMA channels. This API (Application Programming + * Interface) allow SDMA channels' access in an OPEN, READ, WRITE, CLOSE + * fashion. + * These are the MIDDLE level functions of the I.API. + * + * + * + * + * $Log iapiMiddle.c $ + * + *****************************************************************************/ + + +/* **************************************************************************** + * Include File Section + *****************************************************************************/ +#include "epm.h" +#include <string.h> + +#include "iapiLow.h" +#include "iapiMiddle.h" + +/* **************************************************************************** + * Global Variable Section + *****************************************************************************/ + + +/* **************************************************************************** + * Function Section + *****************************************************************************/ + +/* ***************************************************************************/ +/**Allocates one Buffer Descriptor structure using information present in the + * channel descriptor. + * + * @param *ccb_p channel control block used to get the channel descriptor + * + * @return + * - pointer on the new Buffer Descriptor + * - NULL if allocation failed + * + */ +bufferDescriptor * +iapi_AllocBD (channelControlBlock * ccb_p) + +{ + bufferDescriptor * ptrBD = NULL; + + if (ccb_p->channelDescriptor->bufferDescNumber != 0){ +#ifdef CONFIG_SDMA_IRAM + channelDescriptor * cd_p = ccb_p->channelDescriptor; + if(cd_p->channelNumber >= MXC_DMA_CHANNEL_IRAM) { + ptrBD = (bufferDescriptor *) + MALLOC( ccb_p->channelDescriptor->bufferDescNumber * + sizeof(bufferDescriptor), SDMA_IRAM); + } else +#endif /*CONFIG_SDMA_IRAM*/ + { + ptrBD = (bufferDescriptor *) + MALLOC( ccb_p->channelDescriptor->bufferDescNumber * + sizeof(bufferDescriptor), SDMA_ERAM); + } + } + if (ptrBD != NULL) { + ptrBD->mode.command = 0; + ptrBD->mode.status = 0; + ptrBD->mode.count = 0; + ptrBD->bufferAddr = NULL; + } + + return ptrBD; +} + +/* ***************************************************************************/ +/**Allocate one channel context data structure. + * + * @param **ctxd_p pointer to context data to be allocated + * @param channel channel number of context data structure + * + * @return + * - IAPI_SUCCESS + * - -iapi_errno if allocation failed + */ +int +iapi_AllocContext(contextData ** ctxd_p, unsigned char channel) +{ + contextData * ctxData; + int result = IAPI_SUCCESS; + + if (*ctxd_p != NULL){ + result = IAPI_ERR_CC_ALREADY_DEFINED | IAPI_ERR_CH_AVAILABLE | channel; + iapi_errno = result; + return -result; + } + + ctxData = (contextData *)MALLOC(sizeof(contextData), SDMA_ERAM); + + if (ctxData !=NULL) { + *ctxd_p = ctxData; + return IAPI_SUCCESS; + + } else { + *ctxd_p = NULL; + result = IAPI_ERR_CC_ALLOC_FAILED | IAPI_ERR_CH_AVAILABLE | channel; + iapi_errno = result; + return -result; + } +} + +/* ***************************************************************************/ +/**Allocates channel description and fill in with default values. + * + * <b>Algorithm:</b>\n + * - Check channel properties. + * - Then modifies the properties of the channel description with default + * + * @param **cd_p pointer to channel descriptor to be allocated + * @param channel channel number of channel descriptor + * + * @return + * - IAPI_SUCCESS + * - -iapi_errno if allocation failed + * + */ +int +iapi_AllocChannelDesc (channelDescriptor ** cd_p, unsigned char channel) +{ +#ifdef MCU + volatile unsigned long * chPriorities = &SDMA_CHNPRI_0; +#endif /* MCU */ + channelDescriptor * tmpCDptr; + int result = IAPI_SUCCESS; + + + if (*cd_p != NULL){ + result = IAPI_ERR_CD_ALREADY_DEFINED | IAPI_ERR_CH_AVAILABLE | channel; + iapi_errno = result; + return -result; + } + + tmpCDptr = (channelDescriptor *)MALLOC(sizeof(channelDescriptor), SDMA_ERAM); + + if (tmpCDptr != NULL){ + iapi_memcpy(tmpCDptr, &iapi_ChannelDefaults, sizeof (channelDescriptor)); + tmpCDptr->channelNumber = channel; +#ifdef MCU + if (chPriorities[channel] != 0) { + tmpCDptr->priority = chPriorities[channel]; + } else { + chPriorities[channel] = tmpCDptr->priority; + } +#endif + * cd_p = tmpCDptr ; + return IAPI_SUCCESS; + } else { + * cd_p = NULL; + result = IAPI_ERR_CD_ALLOC_FAILED | IAPI_ERR_CH_AVAILABLE | channel; + iapi_errno = result; + return -result; + } +} + +/* ***************************************************************************/ +/**Changes channel description information after performing sanity checks. + * + * <b>Algorithm:</b>\n + * - Check channel properties. + * - Then modifies the properties of the channel description. + * + * @param *cd_p channel descriptor of the channel to change + * @param whatToChange control code indicating the desired change + * @param newval new value + * + * @return + * - IAPI_SUCCESS + * - IAPI_FAILURE if change failed + * + */ +int +iapi_ChangeChannelDesc (channelDescriptor * cd_p, unsigned char whatToChange, + unsigned long newval) +{ + bufferDescriptor * tmpBDptr; + unsigned char index = 0; + int result = IAPI_SUCCESS; + + /* verify parameter validity */ + if (cd_p == NULL) { + result = IAPI_ERR_CD_UNINITIALIZED; + iapi_errno = result; + return -result; + } + + /* verify channel descriptor initialization */ + if (cd_p->ccb_ptr == NULL){ + result = IAPI_ERR_CCB_UNINITIALIZED | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_errno = result; + return -result; + } + + /* verify channel is not in use */ + tmpBDptr = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + for (index=cd_p->bufferDescNumber ; index>0 ; index--){ + if (tmpBDptr->mode.status & BD_DONE){ + result = IAPI_ERR_CH_IN_USE | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_errno = result; + return -result; + } + tmpBDptr++; + } + + /* Select the change accorded to the selector given in parameter */ + switch (whatToChange){ + + /* + * Channel Number + */ + case IAPI_CHANNELNUMBER: + /* Channel number can not be changed (description remains attached) */ + result = IAPI_ERR_CD_CHANGE_CH_NUMBER | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_errno = result; + return -result; + + /* + * Buffer Descriptor Number + */ + case IAPI_BUFFERDESCNUMBER: + if (newval < MAX_BD_NUM){ + if (newval != cd_p->bufferDescNumber){ + /* Free memory used for previous old data */ + if (cd_p->ccb_ptr->baseBDptr != NULL){ + tmpBDptr = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + for (index=0 ; index < cd_p->bufferDescNumber ; index++){ + if (tmpBDptr->bufferAddr != NULL){ + if (cd_p->trust == FALSE) { + FREE(iapi_Phys2Virt(tmpBDptr->bufferAddr)); + } + } + tmpBDptr ++ ; + } + FREE((bufferDescriptor *)iapi_Phys2Virt((cd_p->ccb_ptr)->baseBDptr)); + } + (cd_p->ccb_ptr)->baseBDptr = NULL; + (cd_p->ccb_ptr)->currentBDptr = NULL; + /* Allocate and initialize structures */ + cd_p->bufferDescNumber = (unsigned char)newval; + cd_p->ccb_ptr->status.openedInit = FALSE; + if (IAPI_SUCCESS !=iapi_InitializeMemory (cd_p->ccb_ptr)) + { + result = IAPI_ERR_BD_ALLOCATION | cd_p->channelNumber; + iapi_errno = result; + return -result; + } + cd_p->ccb_ptr->status.openedInit = TRUE; + } + break; + } else { + result = IAPI_ERR_INVALID_PARAMETER | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_errno = result; + return -result; + } + + /* + * Buffer size + */ + case IAPI_BUFFERSIZE: + if (newval < MAX_BD_SIZE) + { + if (newval != cd_p->bufferSize) + { + /* Free memory used for previous old data */ + if (cd_p->ccb_ptr->baseBDptr != NULL) + { + tmpBDptr = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + for (index=0 ; index < cd_p->bufferDescNumber ; index++) + { + if (cd_p->trust == FALSE) + { + FREE(iapi_Phys2Virt(tmpBDptr->bufferAddr)); + } + tmpBDptr ++ ; + } + FREE((bufferDescriptor *)iapi_Phys2Virt((cd_p->ccb_ptr)->baseBDptr)); + } + (cd_p->ccb_ptr)->baseBDptr = NULL; + (cd_p->ccb_ptr)->currentBDptr = NULL; + /* Allocate and initialize structures */ + cd_p->bufferSize = (unsigned short)newval; + cd_p->ccb_ptr->status.openedInit = FALSE; + if (IAPI_SUCCESS !=iapi_InitializeMemory (cd_p->ccb_ptr)) + { + result = IAPI_ERR_BD_ALLOCATION | cd_p->channelNumber; + iapi_errno = result; + return -result; + } + cd_p->ccb_ptr->status.openedInit = TRUE; + } + break; + } + else + { + result = IAPI_ERR_INVALID_PARAMETER | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_errno = result; + return -result; + } + + + /* + * Blocking / non blocking feature + */ + case IAPI_BLOCKING: + if (newval < MAX_BLOCKING){ + cd_p->blocking = newval; + break; + } else { + result = IAPI_ERR_INVALID_PARAMETER | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_errno = result; + return -result; + } + + /* + * Synchronization method + */ + case IAPI_CALLBACKSYNCH: + if (newval < MAX_SYNCH){ + cd_p->callbackSynch = newval; + iapi_ChangeCallbackISR( cd_p, cd_p->callbackISR_ptr); + break; + } else { + result = IAPI_ERR_INVALID_PARAMETER | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_errno = result; + return -result; + } + + + /* + * Ownership of the channel + */ + case IAPI_OWNERSHIP: +#ifdef DSP + result = IAPI_ERR_NOT_ALLOWED | cd_p->channelNumber; + iapi_errno = result; + return -result; +#endif /* DSP */ +#ifdef MCU + if (newval < MAX_OWNERSHIP){ + cd_p->ownership = newval; + iapi_ChannelConfig( cd_p->channelNumber, + ( newval >> CH_OWNSHP_OFFSET_EVT ) & 1, + ( newval >> CH_OWNSHP_OFFSET_MCU ) & 1, + ( newval >> CH_OWNSHP_OFFSET_DSP ) & 1); + break; + } else { + result = IAPI_ERR_INVALID_PARAMETER | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_errno = result; + return -result; + } + +#endif /* MCU */ + + /* + * Priority + */ + case IAPI_PRIORITY: +#ifdef DSP + result = IAPI_ERR_NOT_ALLOWED | cd_p->channelNumber; + iapi_errno = result; + return -result; +#endif /* DSP */ + +#ifdef MCU + if (newval < MAX_CH_PRIORITY){ + volatile unsigned long * ChannelPriorities = &SDMA_CHNPRI_0; + ChannelPriorities[ cd_p->channelNumber ] = newval; + break; + } else { + result = IAPI_ERR_INVALID_PARAMETER | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_errno = result; + return -result; + } +#endif /* MCU */ + + + /* + * "Trust" property + */ + case IAPI_TRUST: + if (newval < MAX_TRUST){ + if (cd_p->trust != newval){ + cd_p->trust = newval; + if (newval == FALSE) { + if (IAPI_SUCCESS !=iapi_InitializeMemory (cd_p->ccb_ptr)) + { + result = IAPI_ERR_BD_ALLOCATION | cd_p->channelNumber; + iapi_errno = result; + return -result; + } + } + } + break; + } else { + result = IAPI_ERR_INVALID_PARAMETER | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_errno = result; + return -result; + } + + /* + * Callback function pointer + */ + case IAPI_CALLBACKISR_PTR: + if ( (void *)newval != NULL){ + { + union { + void * voidstar; + void (* funcptr)(channelDescriptor * cd_p, void * arg); + } value; + value.voidstar = (void*) newval; + cd_p->callbackISR_ptr = value.funcptr; + } + iapi_ChangeCallbackISR( cd_p, cd_p->callbackISR_ptr); + break; + } else { + result = IAPI_ERR_INVALID_PARAMETER | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_errno = result; + return -result; + } + + + /* + * Channel Control Block pointer + */ + case IAPI_CCB_PTR: + cd_p->ccb_ptr = (channelControlBlock *)newval; + cd_p->ccb_ptr->channelDescriptor = cd_p; + break; + + /* + * WRAP/UNWRAP + */ + case IAPI_BDWRAP: + /* point to first BD */ + tmpBDptr = (bufferDescriptor *)iapi_Phys2Virt(cd_p->ccb_ptr->baseBDptr); + /* to point to last BD */ + tmpBDptr += cd_p->bufferDescNumber - 1; + if (newval == TRUE){ + /* wrap last BD */ + tmpBDptr->mode.status |= BD_WRAP; + break; + } + else if (newval == FALSE){ + /* unwrap last BD */ + tmpBDptr->mode.status &= ~BD_WRAP; + break; + } + else { + result = IAPI_ERR_INVALID_PARAMETER | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_errno = result; + return -result; + } + + + /* + * Watermark level + */ + case IAPI_WML: +#ifdef DSP + result = IAPI_ERR_NOT_ALLOWED | cd_p->channelNumber; + iapi_errno = result; + return -result; +#endif /* DSP */ +#ifdef MCU + if (newval < MAX_WML){ + if (cd_p->watermarkLevel != newval){ + cd_p->watermarkLevel = newval; + } + break; + } + else { + result = IAPI_ERR_INVALID_PARAMETER | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_errno = result; + return -result; + } +#endif /* MCU */ + + /* + * Detect errors + */ + default: + result = IAPI_ERR_CD_CHANGE_UNKNOWN | IAPI_ERR_CH_AVAILABLE | + cd_p->channelNumber; + iapi_errno = result; + return -result; + } + + return IAPI_SUCCESS; +} + + +/* ***************************************************************************/ +/**Initialize a table of function pointers that contain the interrupt Service + * Routine callback pointers for the SDMA channels with a default value + * + * <b>Algorithm:</b>\n + * - Loop on each element of the global IAPI variable callbackIsrTable + * + * @param *func_p default callback functon for all SDMA channels + * + * @return none + */ +void +iapi_InitializeCallbackISR(void(* func_p)(channelDescriptor * cd_p, void * arg)) +{ + unsigned long chCnt; + + for (chCnt = 0 ; chCnt < CH_NUM ; chCnt++){ + callbackIsrTable[chCnt] = func_p; + } +} + +/* ***************************************************************************/ +/**For the specified channel control block, attach the array of buffer + * descriptors, the channel description structure and initialize channel's + * status using information in the channel descriptor. + * + * @param *ccb_p pointer to channel control block + * + * @return none + * + */ +int +iapi_InitializeMemory (channelControlBlock * ccb_p) +{ + bufferDescriptor * bd_p; + unsigned char index; + int result = IAPI_SUCCESS; + + /* Attach the array of Buffer descriptors */ + bd_p = iapi_AllocBD( ccb_p ); + if (bd_p != NULL) + { + ccb_p->baseBDptr = (bufferDescriptor *)iapi_Virt2Phys(bd_p); + ccb_p->currentBDptr = ccb_p->baseBDptr; + for(index=0 ;index < ccb_p->channelDescriptor->bufferDescNumber-1 ; index++) + { + if (ccb_p->channelDescriptor->trust == TRUE) + { + iapi_SetBufferDescriptor(bd_p, + (unsigned char)ccb_p->channelDescriptor->dataSize, + BD_CONT|BD_EXTD, ccb_p->channelDescriptor->bufferSize, + NULL, NULL); + } + else + { + if (ccb_p->channelDescriptor->bufferSize != 0) + { + iapi_SetBufferDescriptor(bd_p, + (unsigned char)ccb_p->channelDescriptor->dataSize, + BD_CONT|BD_EXTD, ccb_p->channelDescriptor->bufferSize, + MALLOC(ccb_p->channelDescriptor->bufferSize, SDMA_ERAM), NULL); + } + } + bd_p++; + } + + if (ccb_p->channelDescriptor->trust == TRUE) + { + iapi_SetBufferDescriptor(bd_p, + (unsigned char)ccb_p->channelDescriptor->dataSize, + BD_EXTD|BD_WRAP|BD_INTR, ccb_p->channelDescriptor->bufferSize, + NULL, NULL); + } + else + { + if (ccb_p->channelDescriptor->bufferSize != 0) + { + iapi_SetBufferDescriptor(bd_p, + (unsigned char)ccb_p->channelDescriptor->dataSize, + BD_EXTD|BD_WRAP|BD_INTR, + ccb_p->channelDescriptor->bufferSize, + MALLOC(ccb_p->channelDescriptor->bufferSize, SDMA_ERAM), NULL); + } + } + } + else + { + result = IAPI_ERR_BD_ALLOCATION; + return -result; + } + return result; +} diff --git a/arch/arm/plat-mxc/sdma/iapi/src/iapiMiddleMcu.c b/arch/arm/plat-mxc/sdma/iapi/src/iapiMiddleMcu.c new file mode 100644 index 000000000000..8dc814418e4c --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/src/iapiMiddleMcu.c @@ -0,0 +1,52 @@ +/****************************************************************************** + * + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiMiddleMcu.c + * + * $Id iapiMiddleMcu.c $ + * + * Description: + * This library is written in C to guarantee functionality and integrity in + * the usage of SDMA virtual DMA channels. This API (Application Programming + * Interface) allow SDMA channels' access in an OPEN, READ, WRITE, CLOSE + * fashion. + * These are the MIDDLE level functions of the I.API specific to MCU. + * + * + * + * + * $Log iapiMiddleMcu.c $ + * + *****************************************************************************/ + +/* **************************************************************************** + * Include File Section + *****************************************************************************/ +#include "epm.h" +#include <string.h> + +#include "iapiLow.h" +#include "iapiMiddle.h" + +/* **************************************************************************** + * Global Variable Section + *****************************************************************************/ + +/*extern void * __HEAP_START; +extern void * __HEAP_END; +*/ + +/* **************************************************************************** + * Function Section + *****************************************************************************/ diff --git a/arch/arm/plat-mxc/sdma/iapi/src/iapiOS.c b/arch/arm/plat-mxc/sdma/iapi/src/iapiOS.c new file mode 100644 index 000000000000..643cab549742 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/iapi/src/iapiOS.c @@ -0,0 +1,64 @@ +/****************************************************************************** + * + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + ****************************************************************************** + * + * File: iapiOS.c + * + * $Id iapiOS.c $ + * + * Description: + * This library is written in C to guarantee functionality and integrity in + * the usage of SDMA virtual DMA channels. This API (Application Programming + * Interface) allow SDMA channels' access in an OPEN, READ, WRITE, CLOSE + * fashion. + * These are the OS level functions of the I.API - are OS dependant and must + * be provided by the user of I.API. + * + * + * / + * + * $Log iapiOS.c $ + * + *****************************************************************************/ + +/* **************************************************************************** + * Include File Section + *****************************************************************************/ +#include "epm.h" +#include "iapiLow.h" + +/** + * Function Section + */ +#ifdef CONFIG_SDMA_IRAM +void*(* iapi_iram_Malloc) (size_t size); +#endif /*CONFIG_SDMA_IRAM*/ + +void*(* iapi_Malloc) (size_t size); +void (* iapi_Free) (void * ptr); + +void*(* iapi_Virt2Phys) (void * ptr); +void*(* iapi_Phys2Virt) (void * ptr); + +void (* iapi_WakeUp)(int); +void (* iapi_GotoSleep)(int); +void (* iapi_InitSleep)(int); + +void*(* iapi_memcpy)(void *dest, const void *src, size_t count); +void*(* iapi_memset)(void *dest, int c, size_t count); + +void (* iapi_EnableInterrupts)(void); +void (* iapi_DisableInterrupts)(void); + +int (* iapi_GetChannel)(int); +int (* iapi_ReleaseChannel)(int); diff --git a/arch/arm/plat-mxc/sdma/sdma.c b/arch/arm/plat-mxc/sdma/sdma.c new file mode 100644 index 000000000000..6bdce3375134 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/sdma.c @@ -0,0 +1,1481 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file plat-mxc/sdma/sdma.c + * @brief This file contains functions for Smart DMA API + * + * SDMA (Smart DMA) is used for transferring data between MCU and peripherals + * + * @ingroup SDMA + */ + +#include <linux/init.h> +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/semaphore.h> +#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/proc_fs.h> + +#include <asm/uaccess.h> +#include <asm/irq.h> +#include <mach/dma.h> +#include <mach/hardware.h> + +#include "iapi.h" + +#define M3_BASE_ADDRESS CSD0_BASE_ADDR +#define CHAD(ch) sdma_data[0].cd->ccb_ptr[ch].channelDescriptor + +/*! + * SDMA status mutex + */ +static struct semaphore sdma_status_mutex; + +/*! + * SDMA channel sleep queues + */ +//static struct semaphore sdma_sleep_mutex[MAX_DMA_CHANNELS]; +static wait_queue_head_t sdma_sleep_queue[MAX_DMA_CHANNELS]; + +/*! + * SDMA channel synchronization + */ +static struct semaphore sdma_synch_mutex[MAX_DMA_CHANNELS]; + +/*! + * SDMA buffers pool initialization function + */ +extern void init_sdma_pool(void); + +/*! + * Flags are save and restored during interrupt handler + */ +unsigned long flags; + +struct clk *mxc_sdma_ahb_clk, *mxc_sdma_ipg_clk; + +/*! + * Structure containing sdma channels information. + */ +typedef struct { + /*! Channel number */ + int channel; + /*! Channel usage name */ + int in_use; + /*! Name of device using the channel */ + char devicename[MAX_DEVNAME_LENGTH]; + /*! Transfer type. Needed for setting SDMA script */ + sdma_transferT transfer_type; + /*! Peripheral type. Needed for setting SDMA script */ + sdma_periphT peripheral_type; + /*! Watermark level of device's fifo */ + __u32 watermark_level; + /*! Peripheral event id */ + int event_id; + /*! Peripheral event id2 (for channels that use 2 events) */ + int event_id2; + /*! Running status (boolean) */ + int running; + /*! buffer descriptors number */ + int bd_number; + /*! callback function */ + dma_callback_t callback; + /*! callback argument */ + void *arg; + /*! SDMA data access word size */ + unsigned long word_size:8; + /*! channel descriptor pointer */ + channelDescriptor *cd; +} sdma_struct; + +/*! + * Used to save the status of channels. + */ +static sdma_struct sdma_data[MAX_DMA_CHANNELS]; + +/*! + * Stores the start address of the SDMA scripts + */ +static sdma_script_start_addrs sdma_script_addrs; + +extern void mxc_sdma_get_script_info(sdma_script_start_addrs * sdma_script_add); + +/*! + * Init sleep mutex of the channel + * + * @param channel channel number + */ +static void sdma_init_sleep(int channel) +{ + init_waitqueue_head(&sdma_sleep_queue[channel]); +} + +/*! + * Puts channel to sleep + * + * @param channel channel number + */ +static void sdma_sleep_channel(int channel) +{ + while ((iapi_SDMAIntr & (1 << channel)) == 0) { + wait_event_interruptible(sdma_sleep_queue[channel], + ((iapi_SDMAIntr & (1 << channel)) != + 0)); + } +} + +/*! + * Wake up channel from sleep + * + * @param channel channel number + */ +static void sdma_wakeup_channel(int channel) +{ + wake_up_interruptible(&sdma_sleep_queue[channel]); +} + +/*! + * Sdma interrupt handler routine. + * Calls channels callback function + * + * @param irq the interrupt number + * @param dev_id driver private data + * @return the function returns \b IRQ_RETVAL(1) - interrupt was handled + */ +static irqreturn_t sdma_int_handler(int irq, void *dev_id) +{ + IRQ_Handler(); + return IRQ_RETVAL(1); +} + +/*! + * I.API channel callback function + * + * @param cd channel descriptor structure + * @param channel_data SDMA struct of the current channel + */ +static void iapi_interrupt_callback(channelDescriptor * cd, + sdma_struct * channel_data) +{ + int channel; + dma_callback_t callback; + void *arg; + + channel = channel_data->channel; + + channel_data->running = 0; + + arg = channel_data->arg; + + if (arg == 0) { + arg = (void *)&channel; + } + + callback = channel_data->callback; + + if (callback != 0) { + callback(arg); + } +} + +/*! + * Returns pc of SDMA script according to peripheral and transfer type + * + * @param peripheral_type peripheral type + * @param transfer_type transfer type + * + * @return PC of SDMA script +*/ +static unsigned short sdma_get_pc(sdma_periphT peripheral_type, + sdma_transferT transfer_type) +{ + int res = 0; + + if (peripheral_type == MEMORY) { + switch (transfer_type) { + case emi_2_int: + res = sdma_script_addrs.mxc_sdma_ap_2_ap_addr; + break; + case emi_2_emi: + res = sdma_script_addrs.mxc_sdma_ap_2_ap_addr; + break; + case int_2_emi: + res = sdma_script_addrs.mxc_sdma_ap_2_ap_addr; + break; + default: + res = -EINVAL; + } + } else if (peripheral_type == DSP) { + switch (transfer_type) { + case emi_2_dsp: + res = sdma_script_addrs.mxc_sdma_ap_2_bp_addr; + break; + case dsp_2_emi: + res = sdma_script_addrs.mxc_sdma_bp_2_ap_addr; + break; + case dsp_2_emi_loop: + res = + sdma_script_addrs. + mxc_sdma_loopback_on_dsp_side_addr; + break; + case emi_2_dsp_loop: + res = + sdma_script_addrs.mxc_sdma_mcu_interrupt_only_addr; + break; + default: + res = -EINVAL; + } + } else if (peripheral_type == FIRI) { + switch (transfer_type) { + case per_2_int: + res = sdma_script_addrs.mxc_sdma_firi_2_per_addr; + break; + case per_2_emi: + res = sdma_script_addrs.mxc_sdma_firi_2_mcu_addr; + break; + case int_2_per: + res = sdma_script_addrs.mxc_sdma_per_2_firi_addr; + break; + case emi_2_per: + res = sdma_script_addrs.mxc_sdma_mcu_2_firi_addr; + break; + default: + res = -EINVAL; + } + } else if (peripheral_type == UART) { + switch (transfer_type) { + case per_2_int: + res = sdma_script_addrs.mxc_sdma_uart_2_per_addr; + break; + case per_2_emi: + res = sdma_script_addrs.mxc_sdma_uart_2_mcu_addr; + break; + case int_2_per: + res = sdma_script_addrs.mxc_sdma_per_2_app_addr; + break; + case emi_2_per: + res = sdma_script_addrs.mxc_sdma_mcu_2_app_addr; + break; + default: + res = -EINVAL; + } + } else if (peripheral_type == UART_SP) { + switch (transfer_type) { + case per_2_int: + res = sdma_script_addrs.mxc_sdma_uartsh_2_per_addr; + break; + case per_2_emi: + res = sdma_script_addrs.mxc_sdma_uartsh_2_mcu_addr; + break; + case int_2_per: + res = sdma_script_addrs.mxc_sdma_per_2_shp_addr; + break; + case emi_2_per: + res = sdma_script_addrs.mxc_sdma_mcu_2_shp_addr; + break; + default: + res = -EINVAL; + } + } else if (peripheral_type == ATA) { + switch (transfer_type) { + case per_2_emi: + res = sdma_script_addrs.mxc_sdma_ata_2_mcu_addr; + break; + case emi_2_per: + res = sdma_script_addrs.mxc_sdma_mcu_2_ata_addr; + break; + default: + res = -EINVAL; + } + } else if (peripheral_type == CSPI || peripheral_type == EXT || + peripheral_type == SSI) { + switch (transfer_type) { + case per_2_int: + res = sdma_script_addrs.mxc_sdma_app_2_per_addr; + break; + case per_2_emi: + res = sdma_script_addrs.mxc_sdma_app_2_mcu_addr; + break; + case int_2_per: + res = sdma_script_addrs.mxc_sdma_per_2_app_addr; + break; + case emi_2_per: + res = sdma_script_addrs.mxc_sdma_mcu_2_app_addr; + break; + default: + res = -EINVAL; + } + } else if (peripheral_type == SSI_SP || peripheral_type == MMC || + peripheral_type == SDHC || peripheral_type == CSPI_SP || + peripheral_type == ESAI || peripheral_type == MSHC_SP) { + switch (transfer_type) { + case per_2_int: + res = sdma_script_addrs.mxc_sdma_shp_2_per_addr; + break; + case per_2_emi: + res = sdma_script_addrs.mxc_sdma_shp_2_mcu_addr; + break; + case int_2_per: + res = sdma_script_addrs.mxc_sdma_per_2_shp_addr; + break; + case emi_2_per: + res = sdma_script_addrs.mxc_sdma_mcu_2_shp_addr; + break; + default: + res = -EINVAL; + } + } else if (peripheral_type == ASRC) { + switch (transfer_type) { + case per_2_emi: + res = sdma_script_addrs.mxc_sdma_asrc_2_mcu_addr; + break; + case emi_2_per: + res = sdma_script_addrs.mxc_sdma_asrc_2_mcu_addr; + break; + case per_2_per: + res = sdma_script_addrs.mxc_sdma_per_2_per_addr; + break; + default: + res = -EINVAL; + } + } else if (peripheral_type == MSHC) { + switch (transfer_type) { + case per_2_emi: + res = sdma_script_addrs.mxc_sdma_mshc_2_mcu_addr; + break; + case emi_2_per: + res = sdma_script_addrs.mxc_sdma_mcu_2_mshc_addr; + break; + default: + res = -EINVAL; + } + } else if (peripheral_type == CCM) { + switch (transfer_type) { + case per_2_emi: + res = sdma_script_addrs.mxc_sdma_dptc_dvfs_addr; + break; + default: + res = -EINVAL; + } + } else if (peripheral_type == FIFO_MEMORY) { + res = sdma_script_addrs.mxc_sdma_ap_2_ap_fixed_addr; + } else if (peripheral_type == SPDIF) { + switch (transfer_type) { + case per_2_emi: + res = sdma_script_addrs.mxc_sdma_spdif_2_mcu_addr; + break; + case emi_2_per: + res = sdma_script_addrs.mxc_sdma_mcu_2_spdif_addr; + break; + default: + res = -EINVAL; + } + } else if (peripheral_type == IPU_MEMORY) { + if (transfer_type == emi_2_per) { + res = sdma_script_addrs.mxc_sdma_ext_mem_2_ipu_addr; + } else { + res = -EINVAL; + } + } + + if (res < 0) { + printk(KERN_ERR "SDMA script not found\n"); + } + + return res; + +} + +static inline int sdma_asrc_set_info(dma_channel_params *p, + script_data *pcontext, int eflags) +{ + dma_channel_ext_params *ep = (dma_channel_ext_params *) p; + unsigned int wml, tmp, wml1, wml2; + struct dma_channel_asrc_info *info = &(ep->info.asrc); + wml = 0; + if (p->transfer_type == per_2_per) { + if (!p->ext) + return wml; + wml1 = p->watermark_level; + wml2 = ep->watermark_level2; + if (info->channs) { + wml |= (info->channs & SDMA_ASRC_INFO_N_MASK) << + SDMA_ASRC_INFO_N_OFF; + if (ep->p2p_dir) + wml2 *= info->channs & SDMA_ASRC_INFO_N_MASK; + else + wml1 *= info->channs & SDMA_ASRC_INFO_N_MASK; + } + if (info->channs & 1) { + if (ep->p2p_dir) + wml |= SDMA_ASRC_P2P_INFO_PS; + else + wml |= SDMA_ASRC_P2P_INFO_PA; + } + if (wml1 > wml2) { + tmp = wml2 & SDMA_ASRC_P2P_INFO_LWML_MASK; + wml |= tmp << SDMA_ASRC_P2P_INFO_LWML_OFF; + tmp = wml1 & SDMA_ASRC_P2P_INFO_HWML_MASK; + wml |= tmp << SDMA_ASRC_P2P_INFO_HWML_OFF; + if (eflags & (1 << 31)) + wml |= SDMA_ASRC_P2P_INFO_LWE; + if (eflags & (1 << 30)) + wml |= SDMA_ASRC_P2P_INFO_HWE; + } else { + tmp = wml1 & SDMA_ASRC_P2P_INFO_LWML_MASK; + wml |= tmp << SDMA_ASRC_P2P_INFO_LWML_OFF; + tmp = wml2 & SDMA_ASRC_P2P_INFO_HWML_MASK; + wml |= tmp << SDMA_ASRC_P2P_INFO_HWML_OFF; + wml |= eflags >> 2; + tmp = pcontext->event_mask2; + pcontext->event_mask2 = pcontext->event_mask1; + pcontext->event_mask1 = tmp; + } + } else { + if (p->ext && info->channs) { + wml |= (info->channs & SDMA_ASRC_INFO_N_MASK) << + SDMA_ASRC_INFO_N_OFF; + tmp = (info->channs * p->watermark_level) & + SDMA_ASRC_INFO_WML_MASK; + wml |= tmp << SDMA_ASRC_INFO_WML_OFF; + } else { + tmp = (p->watermark_level & SDMA_ASRC_INFO_WML_MASK); + wml |= tmp << SDMA_ASRC_INFO_WML_OFF; + } + + if (p->transfer_type == per_2_emi) + wml |= SDMA_ASRC_INFO_TXFR_DIR; + + if (p->ext && (info->channs & 1)) { + if (p->transfer_type == per_2_emi) + wml |= SDMA_ASRC_INFO_PS; + else + wml |= SDMA_ASRC_INFO_PA; + } + wml |= eflags; + } + return wml; +} + +/*! + * Downloads channel context according to channel parameters + * + * @param channel channel number + * @param p channel parameters + */ +static int sdma_load_context(int channel, dma_channel_params * p) +{ + script_data context; + int res; + int event1_greater_than_32; + int event2_greater_than_32; + dma_channel_ext_params *ep = (dma_channel_ext_params *) p; + + res = 0; + + memset(&context, 0, sizeof(script_data)); + context.load_address = sdma_get_pc(p->peripheral_type, + p->transfer_type); + + if (context.load_address > 0) { + if ((p->peripheral_type != MEMORY) + && (p->peripheral_type != DSP)) { + /* Handle multiple event channels differently */ + if (p->event_id2) { + if (p->event_id2 < 32) { + context.event_mask2 = + 0x1 << p->event_id2; + event2_greater_than_32 = 0; + } else { + context.event_mask2 = + 0x1 << (p->event_id2 - 32); + event2_greater_than_32 = 1 << 31; + } + if (p->event_id < 32) { + context.event_mask1 = + 0x1 << p->event_id; + event1_greater_than_32 = 0; + } else { + context.event_mask1 = + 0x1 << (p->event_id - 32); + event1_greater_than_32 = 1 << 30; + } + } else { + event1_greater_than_32 = 0; + event2_greater_than_32 = 0; + if (p->event_id < 32) { + context.event_mask1 = + 0x1 << p->event_id; + context.event_mask2 = 0; + } else { + context.event_mask1 = 0; + context.event_mask2 = + 0x1 << (p->event_id - 32); + } + } + + if (p->ext) + context.wml = ep->info_bits; + /* Watermark Level */ + if (p->peripheral_type == ASRC) { + context.wml |= sdma_asrc_set_info(p, + &context, + event2_greater_than_32 + | + event1_greater_than_32); + } else + context.wml |= event2_greater_than_32 | + event1_greater_than_32 | p->watermark_level; + + /* Address */ + context.shp_addr = (unsigned long)(p->per_address); + if (p->ext) + context.per_addr = ep->per_address2; + iapi_IoCtl(sdma_data[channel].cd, + IAPI_CHANGE_PERIPHADDR, p->per_address); + } else { + context.wml = M3_BASE_ADDRESS; + } + + sdma_data[channel].transfer_type = p->transfer_type; + sdma_data[channel].peripheral_type = p->peripheral_type; + sdma_data[channel].watermark_level = p->watermark_level; + iapi_AssignScript(sdma_data[channel].cd, &context); + } else { + res = context.load_address; + } + + return res; +} + +/*! + * Setup channel according to parameters. Must be called once after mxc_request_dma() + * + * @param channel channel number + * @param p channel parameters pointer + * @return 0 on success, error code on fail + */ +int mxc_dma_setup_channel(int channel, dma_channel_params * p) +{ + int err = 0; + int i; + + mxc_dma_stop(channel); + + for (i = 0; i < sdma_data[channel].bd_number; i++) { + iapi_IoCtl(sdma_data[channel].cd, + (i << BD_NUM_OFFSET) | + IAPI_CHANGE_SET_STATUS, (unsigned long)0); + } + + sdma_data[channel].bd_number = (p->bd_number <= 0) ? 1 : p->bd_number; + + sdma_data[channel].word_size = p->word_size; + + sdma_data[channel].event_id = p->event_id; + sdma_data[channel].event_id2 = p->event_id2; + + sdma_data[channel].callback = p->callback; + + sdma_data[channel].arg = p->arg; + + err = iapi_IoCtl(sdma_data[channel].cd, + IAPI_CHANGE_BDNUM, sdma_data[channel].bd_number); + + if (err < 0) { + printk(KERN_ERR "Failed allocating buffer \ +descriptors (0x%x)\n", err); + err = -ENOMEM; + goto setup_channel_fail; + } + + if (channel != 0) { + switch (p->transfer_type) { + case dsp_2_per: + break; + case emi_2_per: + case int_2_per: + case per_2_int: + case per_2_emi: + case per_2_per: + /* + * Peripheral <------> Memory + * evtOvr = 0 dspOvr = 1 + */ + iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_OWNERSHIP, + (OWN_CHANNEL << CH_OWNSHP_OFFSET_EVT) | + (OWN_CHANNEL << CH_OWNSHP_OFFSET_MCU) | + (DONT_OWN_CHANNEL << CH_OWNSHP_OFFSET_DSP)); + if (p->event_id) { + err = iapi_SetChannelEventMapping(p->event_id, + 0x1 << + channel); + } + if (!err && p->event_id2) { + err = iapi_SetChannelEventMapping(p->event_id2, + 0x1 << + channel); + } + break; + case emi_2_dsp: + case int_2_dsp: + case dsp_2_int: + case dsp_2_emi: + case dsp_2_dsp: + /* + * DSP <-----------> Memory + * evtOvr = 1 dspOvr = 0 + */ + iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_OWNERSHIP, + (DONT_OWN_CHANNEL << CH_OWNSHP_OFFSET_EVT) | + (OWN_CHANNEL << CH_OWNSHP_OFFSET_MCU) | + (OWN_CHANNEL << CH_OWNSHP_OFFSET_DSP)); + break; + case emi_2_int: + case emi_2_emi: + case int_2_int: + case int_2_emi: + case emi_2_dsp_loop: + case dsp_2_emi_loop: + /* evtOvr = 1 dspOvr = 1 */ + iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_OWNERSHIP, + (DONT_OWN_CHANNEL << CH_OWNSHP_OFFSET_EVT) | + (OWN_CHANNEL << CH_OWNSHP_OFFSET_MCU) | + (DONT_OWN_CHANNEL << CH_OWNSHP_OFFSET_DSP)); + break; + case per_2_dsp: + /* evtOvr = 0 dspOvr = 0 */ + iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_OWNERSHIP, + (OWN_CHANNEL << CH_OWNSHP_OFFSET_EVT) | + (DONT_OWN_CHANNEL << CH_OWNSHP_OFFSET_MCU) | + (OWN_CHANNEL << CH_OWNSHP_OFFSET_DSP)); + err = iapi_SetChannelEventMapping(p->event_id, + 0x1 << channel); + break; + default: + break; + printk(KERN_ERR "Wrong SDMA transfer type\n"); + err = -EINVAL; + } + if (err == 0) { + err = sdma_load_context(channel, p); + iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_PRIORITY, + MXC_SDMA_DEFAULT_PRIORITY); + } + } + setup_channel_fail: + return err; +} + +/*! + * Setup the channel priority. This can be used to change the default priority + * for the channel. + * + * @param channel channel number + * @param priority priority to be set for the channel + * + * @return 0 on success, error code on failure + */ +int mxc_dma_set_channel_priority(unsigned int channel, unsigned int priority) +{ + if (priority < MXC_SDMA_MIN_PRIORITY + || priority > MXC_SDMA_MAX_PRIORITY) { + return -EINVAL; + } + return iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_PRIORITY, + priority); +} + +/*! + * Allocates dma channel. + * If channel's value is 0, then the function allocates a free channel + * dynamically and sets its value to channel. + * Else allocates requested channel if it is free. + * If the channel is busy or no free channels (in dynamic allocation) -EBUSY returned. + * + * @param channel pointer to channel number + * @param devicename device name + * @return 0 on success, error code on fail + */ +int mxc_request_dma(int *channel, const char *devicename) +{ + int i, res; + + res = 0; + + down(&sdma_status_mutex); + + /* Dynamic allocation */ + if (*channel == 0) { + for (i = MAX_DMA_CHANNELS - 1; i > 0; i--) { +#ifdef CONFIG_SDMA_IRAM + /*TODO:It will be removed after DPTC used UDMA interface */ + if (i >= MXC_DMA_CHANNEL_IRAM) + continue; +#endif /*CONFIG_SDMA_IRAM */ + if (!sdma_data[i].in_use) { + *channel = i; + break; + } + } + } + + if (*channel > 0 && *channel < MAX_DMA_CHANNELS && + sdma_data[*channel].in_use == 0) { + res = iapi_Open(sdma_data[0].cd, *channel); + + if (res < 0) { + printk(KERN_ERR "Failed iapi_Open channel %d, 0x%x\n", + *channel, res); + } else { + sdma_data[*channel].in_use = 1; + strcpy(sdma_data[*channel].devicename, devicename); + sdma_data[*channel].cd = CHAD(*channel); + + iapi_IoCtl(sdma_data[*channel].cd, IAPI_CHANGE_SYNCH, + CALLBACK_ISR); + iapi_IoCtl(sdma_data[*channel].cd, + IAPI_CHANGE_CALLBACKFUNC, + (unsigned long)iapi_interrupt_callback); + iapi_IoCtl(sdma_data[*channel].cd, + IAPI_CHANGE_USER_ARG, + (unsigned long)&(sdma_data[*channel])); + } + } else { + res = -EBUSY; + } + + up(&sdma_status_mutex); + + return res; +} + +/*! + * Configures request parameters. Can be called multiple times after + * mxc_request_dma() and mxc_dma_setup_channel(). + * + * + * @param channel channel number + * @param p request parameters pointer + * @param bd_index index of buffer descriptor to set + * @return 0 on success, error code on fail + */ +int mxc_dma_set_config(int channel, dma_request_t * p, int bd_index) +{ + unsigned char param; + + if (!sdma_data[channel].in_use) { + return -EINVAL; + } + + iapi_IoCtl(sdma_data[channel].cd, + (bd_index << BD_NUM_OFFSET) | + IAPI_CHANGE_SET_TRANSFER_CD, sdma_data[channel].word_size); + + param = BD_DONE | BD_INTR | BD_EXTD; + + if (sdma_data[channel].bd_number > 1 && p->bd_cont == 1) { + param |= BD_CONT; + } + + if (bd_index == sdma_data[channel].bd_number - 1) { + param |= BD_WRAP; + } + + switch (sdma_data[channel].transfer_type) { + case emi_2_per: + case dsp_2_per: + case int_2_per: + case emi_2_dsp: + case int_2_dsp: + case emi_2_dsp_loop: + iapi_IoCtl(sdma_data[channel].cd, + (bd_index << BD_NUM_OFFSET) | + IAPI_CHANGE_SET_BUFFERADDR, + (unsigned long)p->sourceAddr); + break; + case per_2_int: + case per_2_emi: + case per_2_dsp: + case dsp_2_int: + case dsp_2_emi: + case dsp_2_dsp: + case dsp_2_emi_loop: + iapi_IoCtl(sdma_data[channel].cd, + (bd_index << BD_NUM_OFFSET) | + IAPI_CHANGE_SET_BUFFERADDR, + (unsigned long)p->destAddr); + break; + case emi_2_int: + case emi_2_emi: + case int_2_int: + case int_2_emi: + iapi_IoCtl(sdma_data[channel].cd, + (bd_index << BD_NUM_OFFSET) | + IAPI_CHANGE_SET_BUFFERADDR, + (unsigned long)p->sourceAddr); + iapi_IoCtl(sdma_data[channel].cd, + (bd_index << BD_NUM_OFFSET) | + IAPI_CHANGE_SET_EXTDBUFFERADDR, + (unsigned long)p->destAddr); + break; + default: + break; + } + + /* Change the endianness for DSP to MCU Data transfers */ + if (sdma_data[channel].transfer_type == dsp_2_emi || + sdma_data[channel].transfer_type == emi_2_dsp) { + iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_SET_ENDIANNESS, + SET_BIT_ALL); + } + + iapi_IoCtl(sdma_data[channel].cd, + (bd_index << BD_NUM_OFFSET) | + IAPI_CHANGE_SET_COUNT, p->count); + + iapi_IoCtl(sdma_data[channel].cd, + (bd_index << BD_NUM_OFFSET) | IAPI_CHANGE_SET_STATUS, param); + + return 0; +} + +/*! + * Configures the BD_INTR bit on a buffer descriptor parameters. + * + * + * @param channel channel number + * @param bd_index index of buffer descriptor to set + * @param bd_intr flag to set or clear the BD_INTR bit + * @return 0 on success, error code on fail + */ +void mxc_dma_set_bd_intr(int channel, int bd_index, int bd_intr) +{ + unsigned long param; + + iapi_IoCtl(sdma_data[channel].cd, + (bd_index << BD_NUM_OFFSET) | + IAPI_CHANGE_GET_STATUS, (unsigned long)¶m); + + if (bd_intr) { + param |= BD_INTR; + } else { + param &= ~BD_INTR; + } + iapi_IoCtl(sdma_data[channel].cd, + (bd_index << BD_NUM_OFFSET) | IAPI_CHANGE_SET_STATUS, param); + +} + +/*! + * Gets the BD_INTR bit on a buffer descriptor. + * + * + * @param channel channel number + * @param bd_index index of buffer descriptor to set + * + * @return returns the BD_INTR bit status + */ +int mxc_dma_get_bd_intr(int channel, int bd_index) +{ + unsigned long bd_status = 0; + + iapi_IoCtl(sdma_data[channel].cd, + (bd_index << BD_NUM_OFFSET) | + IAPI_CHANGE_GET_STATUS, (unsigned long)&bd_status); + + return (bd_status & BD_INTR); +} + +/*! + * Stop the current transfer + * + * @param channel channel number + * @param buffer_number number of buffers (beginning with 0), + * whose done bits should be reset to 0 + */ +int mxc_dma_reset(int channel, int buffer_number) +{ + unsigned char param = 0; + int i = 0; + + if (!sdma_data[channel].in_use) { + return -EINVAL; + } + + /* clear the BD_DONE bits for all the necessary buffers */ + for (i = 0; i < buffer_number; i++) { + + iapi_IoCtl(sdma_data[channel].cd, (i << BD_NUM_OFFSET) | + IAPI_CHANGE_GET_STATUS, (unsigned long)¶m); + + /* clear the BD_DONE bit of the buffer */ + param = param & (~BD_DONE); + + iapi_IoCtl(sdma_data[channel].cd, (i << BD_NUM_OFFSET) | + IAPI_CHANGE_SET_STATUS, param); + } + + return 0; +} + +/*! + * Returns request parameters. + * + * @param channel channel number + * @param p request parameters pointer + * @param bd_index index of buffer descriptor to get + * @return 0 on success, error code on fail + */ +int mxc_dma_get_config(int channel, dma_request_t * p, int bd_index) +{ + int err = 0; + unsigned long bd_status; + unsigned long bd_count; + __u8 *sourceAddr; + __u8 *destAddr; + + iapi_IoCtl(sdma_data[channel].cd, + (bd_index << BD_NUM_OFFSET) | + IAPI_CHANGE_GET_STATUS, (unsigned long)&bd_status); + iapi_IoCtl(sdma_data[channel].cd, + (bd_index << BD_NUM_OFFSET) | + IAPI_CHANGE_GET_COUNT, (unsigned long)&bd_count); + iapi_IoCtl(sdma_data[channel].cd, + (bd_index << BD_NUM_OFFSET) | + IAPI_CHANGE_GET_BUFFERADDR, (unsigned long)&sourceAddr); + + switch (sdma_data[channel].transfer_type) { + case emi_2_per: + case dsp_2_per: + case int_2_per: + case emi_2_dsp: + case int_2_dsp: + case emi_2_dsp_loop: + p->sourceAddr = sourceAddr; + break; + case per_2_int: + case per_2_emi: + case per_2_dsp: + case dsp_2_int: + case dsp_2_emi: + case dsp_2_dsp: + case dsp_2_emi_loop: + p->destAddr = sourceAddr; + break; + case emi_2_int: + case emi_2_emi: + case int_2_int: + case int_2_emi: + p->sourceAddr = sourceAddr; + iapi_IoCtl(sdma_data[channel].cd, + (bd_index << BD_NUM_OFFSET) | + IAPI_CHANGE_GET_EXTDBUFFERADDR, + (unsigned long)&destAddr); + p->destAddr = destAddr; + break; + default: + break; + } + + p->count = bd_count; + p->bd_done = bd_status & BD_DONE; + p->bd_cont = bd_status & BD_CONT; + p->bd_error = bd_status & BD_RROR; + + return err; +} + +/*! + * This function is used by MXC IPC's write_ex2. It passes the pointer to the + * data control structure to iapi_write_ipcv2() + * + * @param channel SDMA channel number + * @param ctrl_ptr Data Control structure pointer + */ +int mxc_sdma_write_ipcv2(int channel, void *ctrl_ptr) +{ + return iapi_Write_ipcv2(sdma_data[channel].cd, ctrl_ptr); +} + +/*! + * This function is used by MXC IPC's read_ex2. It passes the pointer to the + * data control structure to iapi_read_ipcv2() + * + * @param channel SDMA channel number + * @param ctrl_ptr Data Control structure pointer + */ +int mxc_sdma_read_ipcv2(int channel, void *ctrl_ptr) +{ + return iapi_Read_ipcv2(sdma_data[channel].cd, ctrl_ptr); +} + +/*! + * Starts dma channel. + * + * @param channel channel number + */ +int mxc_dma_start(int channel) +{ + if (sdma_data[channel].running == 0) { + sdma_data[channel].running = 1; + iapi_StartChannel(channel); + } + + return 0; +} + +/*! + * Stops dma channel. + * + * @param channel channel number + */ +int mxc_dma_stop(int channel) +{ + iapi_StopChannel(channel); + sdma_data[channel].running = 0; + + return 0; +} + +/*! + * Frees dma channel. + * + * @param channel channel number + */ +void mxc_free_dma(int channel) +{ + int i; + + mxc_dma_stop(channel); + + if (sdma_data[channel].event_id != 0) { + iapi_SetChannelEventMapping(sdma_data[channel].event_id, 0x0); + } + if (sdma_data[channel].event_id2 != 0) { + iapi_SetChannelEventMapping(sdma_data[channel].event_id2, 0x0); + } + + sdma_data[channel].event_id = 0; + + iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_PRIORITY, 0x0); + iapi_IoCtl(sdma_data[channel].cd, IAPI_CHANGE_OWNERSHIP, + (OWN_CHANNEL << CH_OWNSHP_OFFSET_EVT) | + (OWN_CHANNEL << CH_OWNSHP_OFFSET_MCU) | + (OWN_CHANNEL << CH_OWNSHP_OFFSET_DSP)); + + for (i = 0; i < sdma_data[channel].bd_number; i++) { + iapi_IoCtl(sdma_data[channel].cd, + (i << BD_NUM_OFFSET) | + IAPI_CHANGE_SET_STATUS, (unsigned long)0); + } + + iapi_Close(sdma_data[channel].cd); + + strcpy(sdma_data[channel].devicename, "not used"); + + sdma_data[channel].in_use = 0; +} + +/*! + * Initializes channel's priorities + * + */ +static void __init init_priorities(void) +{ + iapi_IoCtl(sdma_data[0].cd, IAPI_CHANGE_PRIORITY, 0x7); +} + +/*! + * Initializes events table + */ +static void __init init_event_table(void) +{ + int channel; + + for (channel = 0; channel < MAX_DMA_CHANNELS; channel++) { + iapi_SetChannelEventMapping(channel, 0); + } +} + +/*! + * Sets callback function. Used with standard dma api + * for supporting interrupts + * + * @param channel channel number + * @param callback callback function pointer + * @param arg argument for callback function + */ +void mxc_dma_set_callback(int channel, dma_callback_t callback, void *arg) +{ + sdma_data[channel].callback = callback; + sdma_data[channel].arg = arg; +} + +/*! + * Synchronization function used by I.API + * + * @param channel channel number + */ +static int getChannel(int channel) +{ + if (irqs_disabled() || in_atomic()) { + if (down_trylock(&sdma_synch_mutex[channel])) { + return -EBUSY; + } + } else { + if (down_interruptible(&sdma_synch_mutex[channel])) { + return -EBUSY; + } + } + + return 0; +} + +/*! + * Synchronization function used by I.API + * + * @param channel channel number + */ +static int releaseChannel(int channel) +{ + up(&sdma_synch_mutex[channel]); + return 0; +} + +/*! + * Unmask interrupt function. Used by I.API + * + */ +static void unmask_sdma_interrupt(void) +{ + /* Commented out tp take care of the PREEMPT_RT option + * local_irq_restore(flags); + */ +} + +/*! + * Mask interrupt function. Used by I.API + * + */ +static void mask_sdma_interrupt(void) +{ + /* Commented to take of the PREEMPT_RT option + * local_irq_save(flags); + */ +} + +/*! + * Initializes I.API + */ +static void __init init_iapi_struct(void) +{ + channelDescriptor *cd; + + printk(KERN_INFO "Using SDMA I.API\n"); + + iapi_Malloc = &sdma_malloc; +#ifdef CONFIG_SDMA_IRAM + iapi_iram_Malloc = &sdma_iram_malloc; +#endif /*CONFIG_SDMA_IRAM */ + + iapi_Free = &sdma_free; + iapi_Virt2Phys = (void *(*)(void *))&sdma_virt_to_phys; + iapi_Phys2Virt = (void *(*)(void *))&sdma_phys_to_virt; + iapi_memset = &memset; + iapi_memcpy = &memcpy; + + iapi_GotoSleep = &sdma_sleep_channel; + iapi_WakeUp = &sdma_wakeup_channel; + iapi_InitSleep = &sdma_init_sleep; + iapi_ReleaseChannel = &releaseChannel; + iapi_GetChannel = &getChannel; + + iapi_EnableInterrupts = &unmask_sdma_interrupt; + iapi_DisableInterrupts = &mask_sdma_interrupt; + + cd = kmalloc(sizeof(channelDescriptor), GFP_KERNEL); + + memset(cd, 0, sizeof(channelDescriptor)); + + sdma_data[0].cd = cd; +} + +/*! + * Initializes channel synchronization mutexes + */ +static void __init init_mutexes(void) +{ + int i; + + for (i = 0; i < MAX_DMA_CHANNELS; i++) { + init_MUTEX(&sdma_synch_mutex[i]); + } + + init_MUTEX(&sdma_status_mutex); +} + +/*! + * Channels status read proc file system function + * + * @param buf pointer to the buffer the data shuld be written to. + * @param start pointer to the pointer where the new data is + * written to. + * procedure should update the start pointer to point to + * where in the buffer the data was written. + * @param offset offset from start of the file + * @param count number of bytes to read. + * @param eof pointer to eof flag. sould be set to 1 when + * reaching eof. + * @param data driver specific data pointer. + * + * @return number byte read from the log buffer. + */ +static int proc_read_channels(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + char *log; + char *log_ptr; + char tmp[48]; + int i; + + log = kmalloc(4096, GFP_KERNEL); + memset(log, 0, 4096); + log_ptr = log; + + for (i = 0; i < MAX_DMA_CHANNELS; i++) { + if (sdma_data[i].in_use == 0) { + continue; + } + + memset(tmp, 0, 48); + sprintf(tmp, "Channel %d: %s\n", i, sdma_data[i].devicename); + + strcpy(log_ptr, tmp); + log_ptr += strlen(tmp); + } + + if (offset > strlen(log)) { + *eof = 1; + count = 0; + } else { + if (offset + count > strlen(log)) { + count = strlen(log) - offset; + *eof = 1; + } else { + *eof = 0; + } + + memcpy(buf, log, count); + *start = buf; + kfree(log); + } + + return count; +} + +/*! + * SDMA proc file system read function + */ +static int __init init_proc_fs(void) +{ + struct proc_dir_entry *sdma_proc_dir; + int res; + + res = 0; + + sdma_proc_dir = proc_mkdir("sdma", NULL); + create_proc_read_entry("channels", 0, sdma_proc_dir, + proc_read_channels, NULL); + + if (res < 0) { + printk(KERN_WARNING "Failed create SDMA proc entry\n"); + } + + return res; +} + +/*! + * Initializes SDMA private data + */ +static void __init init_sdma_data(void) +{ + int i; + + memset(sdma_data, 0, sizeof(sdma_struct) * MAX_DMA_CHANNELS); + sdma_data[0].in_use = 1; + strcpy(sdma_data[0].devicename, "MCU"); + + for (i = 0; i < MAX_DMA_CHANNELS; i++) { + sdma_data[i].channel = i; + } +} + +#if defined(CONFIG_MXC_SUPER_GEM) +/*! + * Initialize the Super GEM SDMA channel + * + * @return returns -1 on error, 0 on success. + */ +static int __init init_super_gem(void) +{ + channelDescriptor *cd; + script_data context; + int res = 0; + + res = iapi_Open(sdma_data[0].cd, MXC_DMA_CHANNEL_GEM); + if (res < 0) { + return -1; + } + sdma_data[MXC_DMA_CHANNEL_GEM].in_use = 1; + cd = CHAD(MXC_DMA_CHANNEL_GEM); + memset(&context, 0, sizeof(script_data)); + context.load_address = sdma_script_addrs.mxc_sdma_utra_addr; + context.wml = M3_BASE_ADDRESS; + res = iapi_AssignScript(cd, &context); + if (res < 0) { + iapi_Close(cd); + sdma_data[MXC_DMA_CHANNEL_GEM].in_use = 0; + return -1; + } + res = + iapi_IoCtl(cd, IAPI_CHANGE_OWNERSHIP, + (OWN_CHANNEL << CH_OWNSHP_OFFSET_EVT) | + (DONT_OWN_CHANNEL << CH_OWNSHP_OFFSET_MCU) | + (OWN_CHANNEL << CH_OWNSHP_OFFSET_DSP)); + if (res < 0) { + iapi_Close(cd); + sdma_data[MXC_DMA_CHANNEL_GEM].in_use = 0; + return -1; + } + /* Set EP=1, which is required to start SuperGem script the first time */ + /* This can be done only on the AP side */ + SDMA_H_EVTPEND |= 1 << MXC_DMA_CHANNEL_GEM; + + res = + iapi_SetChannelEventMapping(DMA_REQ_GEM, 1 << MXC_DMA_CHANNEL_GEM); + if (res < 0) { + iapi_Close(cd); + sdma_data[MXC_DMA_CHANNEL_GEM].in_use = 0; + return -1; + } + + return 0; +} +#endif + +/*! + * Initializes dma + */ +int __init sdma_init(void) +{ + int res = 0; + configs_data confreg_data; + + /* Initialize to the default values */ + confreg_data = iapi_ConfigDefaults; + + confreg_data.dspdma = 0; + /* Set ACR bit */ + mxc_sdma_ahb_clk = clk_get(NULL, "sdma_ahb_clk"); + mxc_sdma_ipg_clk = clk_get(NULL, "sdma_ipg_clk"); + clk_enable(mxc_sdma_ahb_clk); + clk_enable(mxc_sdma_ipg_clk); + if (clk_get_rate(mxc_sdma_ahb_clk) / clk_get_rate(mxc_sdma_ipg_clk) < 2) { + printk(KERN_INFO "Setting SDMA ACR\n"); + confreg_data.acr = 1; + } + + init_sdma_data(); + + init_sdma_pool(); + + res = request_irq(MXC_INT_SDMA, sdma_int_handler, 0, "mxcsdma", 0); + + if (res < 0) { + goto sdma_init_fail; + } + + init_mutexes(); + + init_iapi_struct(); + + mxc_sdma_get_script_info(&sdma_script_addrs); + + res = iapi_Init(sdma_data[0].cd, &confreg_data, + sdma_script_addrs.mxc_sdma_start_addr, + sdma_script_addrs.mxc_sdma_ram_code_size * 2, + sdma_script_addrs.mxc_sdma_ram_code_start_addr); + + if (res < 0) { + free_irq(MXC_INT_SDMA, 0); + goto sdma_init_fail; + } + + init_priorities(); + + init_event_table(); + +#if defined(CONFIG_MXC_SUPER_GEM) + res = init_super_gem(); + if (res < 0) { + free_irq(MXC_INT_SDMA, 0); + goto sdma_init_fail; + } +#endif + + init_proc_fs(); + + printk(KERN_INFO "MXC DMA API initialized\n"); + + clk_disable(mxc_sdma_ahb_clk); + clk_disable(mxc_sdma_ipg_clk); + return res; + + sdma_init_fail: + printk(KERN_ERR "Error 0x%x in sdma_init\n", res); + clk_disable(mxc_sdma_ahb_clk); + clk_disable(mxc_sdma_ipg_clk); + return res; + +} + +arch_initcall(sdma_init); + +EXPORT_SYMBOL(mxc_request_dma); +EXPORT_SYMBOL(mxc_free_dma); +EXPORT_SYMBOL(mxc_dma_setup_channel); +EXPORT_SYMBOL(mxc_dma_set_channel_priority); +EXPORT_SYMBOL(mxc_dma_set_config); +EXPORT_SYMBOL(mxc_dma_get_config); +EXPORT_SYMBOL(mxc_dma_set_bd_intr); +EXPORT_SYMBOL(mxc_dma_get_bd_intr); +EXPORT_SYMBOL(mxc_dma_reset); +EXPORT_SYMBOL(mxc_sdma_write_ipcv2); +EXPORT_SYMBOL(mxc_sdma_read_ipcv2); +EXPORT_SYMBOL(mxc_dma_start); +EXPORT_SYMBOL(mxc_dma_stop); +EXPORT_SYMBOL(sdma_malloc); +EXPORT_SYMBOL(sdma_free); +EXPORT_SYMBOL(mxc_dma_set_callback); +EXPORT_SYMBOL(sdma_virt_to_phys); +EXPORT_SYMBOL(sdma_phys_to_virt); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Linux SDMA API"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-mxc/sdma/sdma_malloc.c b/arch/arm/plat-mxc/sdma/sdma_malloc.c new file mode 100644 index 000000000000..21118b42c855 --- /dev/null +++ b/arch/arm/plat-mxc/sdma/sdma_malloc.c @@ -0,0 +1,387 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file plat-mxc/sdma/sdma_malloc.c + * @brief This file contains functions for SDMA non-cacheable buffers allocation + * + * SDMA (Smart DMA) is used for transferring data between MCU and peripherals + * + * @ingroup SDMA + */ + +#include <linux/init.h> +#include <linux/types.h> +#include <linux/mm.h> +#include <asm/dma.h> +#include <mach/hardware.h> + +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> + +#define DEBUG 0 + +#if DEBUG +#define DPRINTK(fmt, args...) printk("%s: " fmt, __FUNCTION__ , ## args) +#else +#define DPRINTK(fmt, args...) +#endif + +#ifdef CONFIG_SDMA_IRAM +#define IRAM_VIRT_BASE IRAM_BASE_ADDR_VIRT +#define IRAM_PHYS_BASE IRAM_BASE_ADDR +#if (CONFIG_SDMA_IRAM_SIZE&0x3FF) +#error "IRAM size of SDMA should be multiple of 1Kbytes" +#else +#define IRAM_SDMA_SIZE CONFIG_SDMA_IRAM_SIZE /* 4K */ +#endif +#define IRAM_UNIT_SIZE 512 +#define IRAM_POOL_SIZE (IRAM_SDMA_SIZE/IRAM_UNIT_SIZE) + +#define IS_IRAM_VIRT(x) (((x)<IRAM_VIRT_BASE)?0:\ + (((x) - IRAM_VIRT_BASE)>IRAM_SDMA_SIZE)?0:1) + +#define IS_IRAM_PHYS(x) (((x)<IRAM_PHYS_BASE)?0:\ + (((x) - IRAM_PHYS_BASE)>IRAM_SDMA_SIZE)?0:1) +#endif /*CONFIG_SDMA_IRAM */ + +/*! + * Defines SDMA non-cacheable buffers pool + */ +static struct dma_pool *pool; + +#ifdef CONFIG_SDMA_IRAM +typedef struct iram_head_s { + struct list_head list; +} iram_head_t; + +static spinlock_t iram_pool_lock = SPIN_LOCK_UNLOCKED; +static struct list_head iram_free_list; +static unsigned char iram_pool_flag[IRAM_POOL_SIZE]; + +static void sdma_iram_free(void *buf); +#endif /*CONFIG_SDMA_IRAM */ + +/*! + * SDMA memory conversion hashing structure + */ +typedef struct { + struct list_head node; + int use_count; + /*! Virtual address */ + void *virt; + /*! Physical address */ + unsigned long phys; +} virt_phys_struct; + +static struct list_head buf_map; + +/*! + * Defines the size of each buffer in SDMA pool. + * The size must be at least 512 bytes, because + * sdma channel control blocks array size is 512 bytes + */ +#define SDMA_POOL_SIZE 1024 + +/*! + * Adds new buffer structure into conversion hash tables + * + * @param vf SDMA memory conversion hashing structure + * + * @return 1 on success, 0 on fail + */ +static int add_entry(virt_phys_struct * vf) +{ + virt_phys_struct *p; + + vf->phys &= PAGE_MASK; + vf->virt = (void *)((u32) vf->virt & PAGE_MASK); + + list_for_each_entry(p, &buf_map, node) { + if (p->virt == vf->virt) { + p->use_count++; + return 0; + } + } + + p = kmalloc(sizeof(virt_phys_struct), GFP_KERNEL); + if (p == 0) { + return -ENOMEM; + } + + *p = *vf; + p->use_count = 1; + list_add_tail(&p->node, &buf_map); + + DPRINTK("added vaddr 0x%p, paddr 0x%08X to list\n", p->virt, p->phys); + + return 0; +} + +/*! + * Deletes buffer stracture from conversion hash tables + * + * @param buf SDMA memory buffer virtual addr + * + * @return 0 on success, -1 on fail + */ +static int delete_entry(void *buf) +{ + virt_phys_struct *p; + + buf = (void *)((u32) buf & PAGE_MASK); + + list_for_each_entry(p, &buf_map, node) { + if (p->virt == buf) { + p->use_count--; + break; + } + } + + if (p->use_count == 0) { + list_del(&p->node); + kfree(p); + } + + return 0; +} + +/*! + * Virtual to physical address conversion functio + * + * @param buf pointer to virtual address + * + * @return physical address + */ +unsigned long sdma_virt_to_phys(void *buf) +{ + u32 offset = (u32) buf & (~PAGE_MASK); + virt_phys_struct *p; + + DPRINTK("searching for vaddr 0x%p\n", buf); + +#ifdef CONFIG_SDMA_IRAM + if (IS_IRAM_VIRT((unsigned long)buf)) { + if ((unsigned long)buf & (IRAM_UNIT_SIZE - 1)) { + printk(KERN_WARNING "%s buffer offset = %ld\n", + __FUNCTION__, (unsigned long)buf); + } + return (unsigned long)buf + IRAM_PHYS_BASE - IRAM_VIRT_BASE; + } +#endif /*CONFIG_SDMA_IRAM */ + + list_for_each_entry(p, &buf_map, node) { + if ((u32) p->virt == ((u32) buf & PAGE_MASK)) { + return p->phys | offset; + } + } + + if (virt_addr_valid(buf)) { + return virt_to_phys(buf); + } + + printk(KERN_WARNING + "SDMA malloc: could not translate virt address 0x%p\n", buf); + return 0; +} + +/*! + * Physical to virtual address conversion functio + * + * @param buf pointer to physical address + * + * @return virtual address + */ +void *sdma_phys_to_virt(unsigned long buf) +{ + u32 offset = buf & (~PAGE_MASK); + virt_phys_struct *p; + +#ifdef CONFIG_SDMA_IRAM + if (IS_IRAM_PHYS((unsigned long)buf)) { + if (buf & (IRAM_UNIT_SIZE - 1)) { + printk(KERN_WARNING "%s buffer offset = %ld\n", + __FUNCTION__, (unsigned long)buf); + } + return (void *)buf + IRAM_VIRT_BASE - IRAM_PHYS_BASE; + } +#endif /*CONFIG_SDMA_IRAM */ + + list_for_each_entry(p, &buf_map, node) { + if (p->phys == (buf & PAGE_MASK)) { + return (void *)((u32) p->virt | offset); + } + } + + printk(KERN_WARNING + "SDMA malloc: could not translate phys address 0x%lx\n", buf); + return 0; +} + +/*! + * Allocates uncacheable buffer + * + * @param size size of allocated buffer + * @return pointer to buffer + */ +void *sdma_malloc(size_t size) +{ + void *buf; + dma_addr_t dma_addr; + virt_phys_struct vf; + + if (size > SDMA_POOL_SIZE) { + printk(KERN_WARNING + "size in sdma_malloc is more than %d bytes\n", + SDMA_POOL_SIZE); + buf = 0; + } else { + buf = dma_pool_alloc(pool, GFP_KERNEL, &dma_addr); + if (buf > 0) { + vf.virt = buf; + vf.phys = dma_addr; + + if (add_entry(&vf) < 0) { + dma_pool_free(pool, buf, dma_addr); + buf = 0; + } + } + } + + DPRINTK("allocated vaddr 0x%p\n", buf); + return buf; +} + +/*! + * Frees uncacheable buffer + * + * @param buf buffer pointer for deletion + */ +void sdma_free(void *buf) +{ +#ifdef CONFIG_SDMA_IRAM + if (IS_IRAM_VIRT((unsigned long)buf)) { + sdma_iram_free(buf); + return; + } +#endif /*CONFIG_SDMA_IRAM */ + + dma_pool_free(pool, buf, sdma_virt_to_phys(buf)); + delete_entry(buf); +} + +#ifdef CONFIG_SDMA_IRAM +/*! + * Allocates uncacheable buffer from IRAM + */ +void *sdma_iram_malloc(size_t size) +{ + void *buf = NULL; + int index = -1; + unsigned long flags; + if (size > IRAM_UNIT_SIZE) { + printk(KERN_WARNING + "size in sdma_iram_malloc is more than %d bytes\n", + IRAM_UNIT_SIZE); + } else { + spin_lock_irqsave(&iram_pool_lock, flags); + if (!list_empty(&iram_free_list)) { + buf = + list_entry(iram_free_list.next, iram_head_t, list); + list_del(iram_free_list.next); + index = + ((unsigned long)buf - + IRAM_VIRT_BASE) / IRAM_UNIT_SIZE; + if (index < 0 || index >= IRAM_POOL_SIZE) { + spin_unlock_irqrestore(&iram_pool_lock, flags); + printk(KERN_ERR "The iram pool has crashed\n"); + return NULL; + } + if (iram_pool_flag[index]) { + spin_unlock_irqrestore(&iram_pool_lock, flags); + printk(KERN_WARNING + "iram block %d already has been allocated \n", + index); + } + iram_pool_flag[index] = 1; + } + spin_unlock_irqrestore(&iram_pool_lock, flags); + if ((unsigned long)buf & (IRAM_UNIT_SIZE - 1)) { + printk(KERN_WARNING + "the start address is not align of %d, buffer offset %ld\n", + IRAM_UNIT_SIZE, (unsigned long)buf); + + buf = PTR_ALIGN(buf, IRAM_UNIT_SIZE); + } + } + return buf; +} + +/*! + * Free uncacheable buffer into IRAM. + */ +static void sdma_iram_free(void *buf) +{ + iram_head_t *p; + int index; + unsigned long flags; + /* The check of parameter will be done in sdma_free */ + index = ((unsigned long)buf - IRAM_VIRT_BASE) / IRAM_UNIT_SIZE; + spin_lock_irqsave(&iram_pool_lock, flags); + p = (iram_head_t *) ((unsigned long)buf & (~(IRAM_UNIT_SIZE - 1))); + list_add_tail(&(p->list), &iram_free_list); + if (iram_pool_flag[index]) { + iram_pool_flag[index] = 0; + spin_unlock_irqrestore(&iram_pool_lock, flags); + } else { + printk(KERN_WARNING + "Free %p which IRAM block %d is already freed\n", buf, + index); + spin_unlock_irqrestore(&iram_pool_lock, flags); + } +} + +/*! + * Initialized the free list of IRAM. + */ +static void iram_pool_init(void) +{ + int i; + iram_head_t *p; + memset(iram_pool_flag, 0, IRAM_POOL_SIZE); + INIT_LIST_HEAD(&iram_free_list); + for (i = 0; i < IRAM_POOL_SIZE; i++) { + p = (iram_head_t *) (IRAM_VIRT_BASE + i * IRAM_UNIT_SIZE); + list_add_tail(&(p->list), &iram_free_list); + } +} +#endif /*CONFIG_SDMA_IRAM */ + +/*! + * SDMA buffers pool initialization function + */ +void __init init_sdma_pool(void) +{ +#ifdef CONFIG_SDMA_IRAM + iram_pool_init(); +#endif /*CONFIG_SDMA_IRAM */ + + pool = dma_pool_create("SDMA", NULL, SDMA_POOL_SIZE, 0, 0); + + INIT_LIST_HEAD(&buf_map); +} + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Linux SDMA API"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-mxc/serialxc.c b/arch/arm/plat-mxc/serialxc.c new file mode 100644 index 000000000000..cf18bfdc8a76 --- /dev/null +++ b/arch/arm/plat-mxc/serialxc.c @@ -0,0 +1,64 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/usb/fsl_xcvr.h> + +#include <mach/hardware.h> +#include <mach/arc_otg.h> + +static void usb_serial_init(struct fsl_xcvr_ops *this) +{ +} + +static void usb_serial_uninit(struct fsl_xcvr_ops *this) +{ +} + +static struct fsl_xcvr_ops serial_ops = { + .name = "serial", + .xcvr_type = PORTSC_PTS_SERIAL, + .init = usb_serial_init, + .uninit = usb_serial_uninit, +}; + +extern void fsl_usb_xcvr_register(struct fsl_xcvr_ops *xcvr_ops); + +static int __init serialxc_init(void) +{ + pr_debug("%s\n", __FUNCTION__); + + fsl_usb_xcvr_register(&serial_ops); + + return 0; +} + +extern void fsl_usb_xcvr_unregister(struct fsl_xcvr_ops *xcvr_ops); + +static void __exit serialxc_exit(void) +{ + fsl_usb_xcvr_unregister(&serial_ops); +} + +module_init(serialxc_init); +module_exit(serialxc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("serial xcvr driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-mxc/snoop.c b/arch/arm/plat-mxc/snoop.c new file mode 100644 index 000000000000..b6e48c7e59d0 --- /dev/null +++ b/arch/arm/plat-mxc/snoop.c @@ -0,0 +1,133 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <mach/hardware.h> +#include <linux/io.h> + +#ifdef M4IF_BASE_ADDR +#define SNOOP_V2 +#define MAX_SNOOP 2 +#define g_snoop_base (IO_ADDRESS(M4IF_BASE_ADDR) + 0x4C) +#elif defined(M3IF_BASE_ADDR) +#define MAX_SNOOP 1 +#define g_snoop_base (IO_ADDRESS(M3IF_BASE_ADDR) + 0x28) +#else +#define MAX_SNOOP 0 +#define g_snoop_base 0 +#endif + +/* M3IF Snooping Configuration Register 0 (M3IFSCFG0) READ/WRITE*/ +#define SBAR(x) (x * 0x14) +/* M3IF Snooping Configuration Register 1 (M3IFSCFG1) READ/WRITE*/ +#define SERL(x) ((x * 0x14) + 0x4) +/* M3IF Snooping Configuration Register 2 (M3IFSCFG2) READ/WRITE*/ +#define SERH(x) ((x * 0x14) + 0x8) +/* M3IF Snooping Status Register 0 (M3IFSSR0) READ/WRITE */ +#define SSRL(x) ((x * 0x14) + 0xC) +/* M3IF Snooping Status Register 1 (M3IFSSR1) */ +#define SSRH(x) ((x * 0x14) + 0x10) + +#if MAX_SNOOP + +int mxc_snoop_set_config(u32 num, unsigned long base, int size) +{ + u32 reg; + uint32_t msb; + uint32_t seg_size; + uint32_t window_size = 0; + int i; + + if (num >= MAX_SNOOP) { + return -EINVAL; + } + + /* Setup M3IF for snooping */ + if (size) { + + if (base == 0) { + return -EINVAL; + } + + msb = fls(size); + if (!(size & ((1UL << msb) - 1))) + msb--; /* Already aligned to power 2 */ + if (msb < 11) + msb = 11; + + window_size = (1UL << msb); + seg_size = window_size / 64; + + msb -= 11; + + reg = base & ~((1UL << msb) - 1); + reg |= msb << 1; + reg |= 1; /* enable snooping */ + reg |= 0x80; /* Set pulse width to default (M4IF only) */ + __raw_writel(reg, g_snoop_base + SBAR(num)); + + reg = 0; + for (i = 0; i < 32; i++) { + if (i * seg_size >= size) + break; + reg |= 1UL << i; + } + __raw_writel(reg, g_snoop_base + SERL(num)); + + reg = 0; + for (i = 32; i < 64; i++) { + if (i * seg_size >= size) + break; + reg |= 1UL << (i - 32); + } + __raw_writel(reg, g_snoop_base + SERH(num)); + + pr_debug + ("Snooping unit # %d enabled: window size = 0x%X, M3IFSCFG0=0x%08X, M3IFSCFG1=0x%08X, M3IFSCFG2=0x%08X\n", + num, window_size, __raw_readl(g_snoop_base + SBAR(num)), + __raw_readl(g_snoop_base + SERL(num)), + __raw_readl(g_snoop_base + SERH(num))); + } else { + __raw_writel(0, g_snoop_base + SBAR(num)); + } + + return window_size; +} + +EXPORT_SYMBOL(mxc_snoop_set_config); + +int mxc_snoop_get_status(u32 num, u32 * statl, u32 * stath) +{ + if (num >= MAX_SNOOP) { + return -EINVAL; + } + + *statl = __raw_readl(g_snoop_base + SSRL(num)); + *stath = __raw_readl(g_snoop_base + SSRH(num)); + /* DPRINTK("status = 0x%08X%08X\n", stat[1], stat[0]); */ + +#ifdef SNOOP_V2 + __raw_writel(*statl, g_snoop_base + SSRL(num)); + __raw_writel(*stath, g_snoop_base + SSRH(num)); +#else + __raw_writel(0x0, g_snoop_base + SSRL(num)); + __raw_writel(0x0, g_snoop_base + SSRH(num)); +#endif + return 0; +} + +EXPORT_SYMBOL(mxc_snoop_get_status); + +#endif /* MAX_SNOOP */ diff --git a/arch/arm/plat-mxc/spba.c b/arch/arm/plat-mxc/spba.c new file mode 100644 index 000000000000..a7f21a9e36df --- /dev/null +++ b/arch/arm/plat-mxc/spba.c @@ -0,0 +1,133 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/types.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/io.h> +#include <mach/hardware.h> +#include <mach/spba.h> + +/*! + * @file plat-mxc/spba.c + * + * @brief This file contains the SPBA API implementation details. + * + * @ingroup SPBA + */ + +static DEFINE_SPINLOCK(spba_lock); + +#define SPBA_MASTER_MIN 1 +#define SPBA_MASTER_MAX 7 + +/*! + * the base addresses for the SPBA modules + */ +static unsigned long spba_base = (unsigned long)IO_ADDRESS(SPBA_CTRL_BASE_ADDR); + +/*! + * SPBA clock + */ +static struct clk *spba_clk; +/*! + * This function allows the three masters (A, B, C) to take ownership of a + * shared peripheral. + * + * @param mod specified module as defined in \b enum \b #spba_module + * @param master one of more (or-ed together) masters as defined in \b enum \b #spba_masters + * + * @return 0 if successful; -1 otherwise. + */ +int spba_take_ownership(int mod, int master) +{ + unsigned long spba_flags; + __u32 rtn_val = -1; + + if (master < SPBA_MASTER_MIN || master > SPBA_MASTER_MAX) { + printk("spba_take_ownership() invalide master= %d\n", master); + BUG(); /* oops */ + } + + if (spba_clk == NULL) + spba_clk = clk_get(NULL, "spba_clk"); + + clk_enable(spba_clk); + + spin_lock_irqsave(&spba_lock, spba_flags); + __raw_writel(master, spba_base + mod); + + if ((__raw_readl(spba_base + mod) & MXC_SPBA_RAR_MASK) == master) { + rtn_val = 0; + } + + spin_unlock_irqrestore(&spba_lock, spba_flags); + + clk_disable(spba_clk); + return rtn_val; +} + +/*! + * This function releases the ownership for a shared peripheral. + * + * @param mod specified module as defined in \b enum \b #spba_module + * @param master one of more (or-ed together) masters as defined in \b enum \b #spba_masters + * + * @return 0 if successful; -1 otherwise. + */ +int spba_rel_ownership(int mod, int master) +{ + unsigned long spba_flags; + volatile unsigned long rar; + + if (master < SPBA_MASTER_MIN || master > SPBA_MASTER_MAX) { + printk("spba_take_ownership() invalide master= %d\n", master); + BUG(); /* oops */ + } + + if (spba_clk == NULL) + spba_clk = clk_get(NULL, "spba_clk"); + + clk_enable(spba_clk); + + if ((__raw_readl(spba_base + mod) & master) == 0) { + clk_disable(spba_clk); + return 0; /* does not own it */ + } + + spin_lock_irqsave(&spba_lock, spba_flags); + + /* Since only the last 3 bits are writeable, doesn't need to mask off + bits 31-3 */ + rar = __raw_readl(spba_base + mod) & (~master); + __raw_writel(rar, spba_base + mod); + + if ((__raw_readl(spba_base + mod) & master) != 0) { + spin_unlock_irqrestore(&spba_lock, spba_flags); + clk_disable(spba_clk); + return -1; + } + + spin_unlock_irqrestore(&spba_lock, spba_flags); + + clk_disable(spba_clk); + + return 0; +} + +EXPORT_SYMBOL(spba_take_ownership); +EXPORT_SYMBOL(spba_rel_ownership); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("SPBA"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-mxc/time.c b/arch/arm/plat-mxc/time.c index 88fb3a57e029..fbf86e42c3f4 100644 --- a/arch/arm/plat-mxc/time.c +++ b/arch/arm/plat-mxc/time.c @@ -50,6 +50,7 @@ /* MX31, MX35 */ #define MX3_TCTL_WAITEN (1 << 3) #define MX3_TCTL_CLK_IPG (1 << 6) +#define MX3_TCTL_CLK_PER (2 << 6) #define MX3_TCTL_FRR (1 << 9) #define MX3_IR 0x0c #define MX3_TSTAT 0x08 @@ -57,6 +58,8 @@ #define MX3_TCN 0x24 #define MX3_TCMP 0x10 +#define timer_is_v2() (!(cpu_is_mx1() || cpu_is_mx2()) || cpu_is_mx25()) + static struct clock_event_device clockevent_mxc; static enum clock_event_mode clockevent_mode = CLOCK_EVT_MODE_UNUSED; @@ -66,7 +69,7 @@ static inline void gpt_irq_disable(void) { unsigned int tmp; - if (cpu_is_mx3()) + if (timer_is_v2()) __raw_writel(0, timer_base + MX3_IR); else { tmp = __raw_readl(timer_base + MXC_TCTL); @@ -76,7 +79,7 @@ static inline void gpt_irq_disable(void) static inline void gpt_irq_enable(void) { - if (cpu_is_mx3()) + if (timer_is_v2()) __raw_writel(1<<0, timer_base + MX3_IR); else { __raw_writel(__raw_readl(timer_base + MXC_TCTL) | MX1_2_TCTL_IRQEN, @@ -90,7 +93,7 @@ static void gpt_irq_acknowledge(void) __raw_writel(0, timer_base + MX1_2_TSTAT); if (cpu_is_mx2()) __raw_writel(MX2_TSTAT_CAPT | MX2_TSTAT_COMP, timer_base + MX1_2_TSTAT); - if (cpu_is_mx3()) + if (timer_is_v2()) __raw_writel(MX3_TSTAT_OF1, timer_base + MX3_TSTAT); } @@ -117,7 +120,7 @@ static int __init mxc_clocksource_init(struct clk *timer_clk) { unsigned int c = clk_get_rate(timer_clk); - if (cpu_is_mx3()) + if (timer_is_v2()) clocksource_mxc.read = mx3_get_cycles; clocksource_mxc.mult = clocksource_hz2mult(c, @@ -180,7 +183,7 @@ static void mxc_set_mode(enum clock_event_mode mode, if (mode != clockevent_mode) { /* Set event time into far-far future */ - if (cpu_is_mx3()) + if (timer_is_v2()) __raw_writel(__raw_readl(timer_base + MX3_TCN) - 3, timer_base + MX3_TCMP); else @@ -233,7 +236,7 @@ static irqreturn_t mxc_timer_interrupt(int irq, void *dev_id) struct clock_event_device *evt = &clockevent_mxc; uint32_t tstat; - if (cpu_is_mx3()) + if (timer_is_v2()) tstat = __raw_readl(timer_base + MX3_TSTAT); else tstat = __raw_readl(timer_base + MX1_2_TSTAT); @@ -264,7 +267,7 @@ static int __init mxc_clockevent_init(struct clk *timer_clk) { unsigned int c = clk_get_rate(timer_clk); - if (cpu_is_mx3()) + if (timer_is_v2()) clockevent_mxc.set_next_event = mx3_set_next_event; clockevent_mxc.mult = div_sc(c, NSEC_PER_SEC, @@ -281,10 +284,9 @@ static int __init mxc_clockevent_init(struct clk *timer_clk) return 0; } -void __init mxc_timer_init(struct clk *timer_clk) +void __init mxc_timer_init(struct clk *timer_clk, void __iomem *base, int irq) { uint32_t tctl_val; - int irq; clk_enable(timer_clk); @@ -303,8 +305,10 @@ void __init mxc_timer_init(struct clk *timer_clk) timer_base = IO_ADDRESS(GPT1_BASE_ADDR); irq = MXC_INT_GPT; #endif - } else - BUG(); + } + if (base) { + timer_base = base; + } /* * Initialise to a known state (all timers off, and timing reset) @@ -313,8 +317,8 @@ void __init mxc_timer_init(struct clk *timer_clk) __raw_writel(0, timer_base + MXC_TCTL); __raw_writel(0, timer_base + MXC_TPRER); /* see datasheet note */ - if (cpu_is_mx3()) - tctl_val = MX3_TCTL_CLK_IPG | MX3_TCTL_FRR | MX3_TCTL_WAITEN | MXC_TCTL_TEN; + if (timer_is_v2()) + tctl_val = MX3_TCTL_CLK_PER | MX3_TCTL_FRR | MX3_TCTL_WAITEN | MXC_TCTL_TEN; else tctl_val = MX1_2_TCTL_FRR | MX1_2_TCTL_CLK_PCLK1 | MXC_TCTL_TEN; diff --git a/arch/arm/plat-mxc/tzic.c b/arch/arm/plat-mxc/tzic.c new file mode 100644 index 000000000000..630d9f789f95 --- /dev/null +++ b/arch/arm/plat-mxc/tzic.c @@ -0,0 +1,181 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <mach/hardware.h> +#include <linux/io.h> +#include <asm/irq.h> +#include <asm/mach/irq.h> + +/* + ***************************************** + * TZIC Registers * + ***************************************** + */ +#define TZIC_BASE IO_ADDRESS(TZIC_BASE_ADDR) +#define TZIC_INTCNTL (TZIC_BASE + 0x0000) /* control register */ +#define TZIC_INTTYPE (TZIC_BASE + 0x0004) /* Controller type register */ +#define TZIC_IMPID (TZIC_BASE + 0x0008) /* Distributor Implementer Identification Register */ +#define TZIC_PRIOMASK (TZIC_BASE + 0x000C) /* Priority Mask Reg */ +#define TZIC_SYNCCTRL (TZIC_BASE + 0x0010) /* Synchronizer Control register */ +#define TZIC_DSMINT (TZIC_BASE + 0x0014) /* DSM interrupt Holdoffregister */ +#define TZIC_INTSEC0 (TZIC_BASE + 0x0080) /* interrupt security register 0 */ +#define TZIC_ENSET0 (TZIC_BASE + 0x0100) /* Enable Set Register 0 */ +#define TZIC_ENCLEAR0 (TZIC_BASE + 0x0180) /* Enable Clear Register 0 */ +#define TZIC_SRCSET0 (TZIC_BASE + 0x0200) /* Source Set Register 0 */ +#define TZIC_SRCCLAR0 (TZIC_BASE + 0x0280) /* Source Clear Register 0 */ +#define TZIC_PRIORITY0 (TZIC_BASE + 0x0400) /* Priority Register 0 */ +#define TZIC_PND0 (TZIC_BASE + 0x0D00) /* Pending Register 0 */ +#define TZIC_HIPND0 (TZIC_BASE + 0x0D80) /* High Priority Pending Register */ +#define TZIC_WAKEUP0 (TZIC_BASE + 0x0E00) /* Wakeup Config Register */ +#define TZIC_SWINT (TZIC_BASE + 0x0F00) /* Software Interrupt Rigger Register */ +#define TZIC_ID0 (TZIC_BASE + 0x0FD0) /* Indentification Register 0 */ + +#define TZIC_NUM_IRQS 128 + +/*! + * Disable interrupt number "irq" in the TZIC + * + * @param irq interrupt source number + */ +static void mxc_mask_irq(unsigned int irq) +{ + int index, off; + + index = irq >> 5; + off = irq & 0x1F; + __raw_writel(1 << off, TZIC_ENCLEAR0 + (index << 2)); +} + +/*! + * Enable interrupt number "irq" in the TZIC + * + * @param irq interrupt source number + */ +static void mxc_unmask_irq(unsigned int irq) +{ + int index, off; + + index = irq >> 5; + off = irq & 0x1F; + __raw_writel(1 << off, TZIC_ENSET0 + (index << 2)); +} + +static unsigned int wakeup_intr[4]; + +/*! + * Set interrupt number "irq" in the TZIC as a wake-up source. + * + * @param irq interrupt source number + * @param enable enable as wake-up if equal to non-zero + * disble as wake-up if equal to zero + * + * @return This function returns 0 on success. + */ +static int mxc_set_wake_irq(unsigned int irq, unsigned int enable) +{ + unsigned int index, off; + + index = irq >> 5; + off = irq & 0x1F; + + if (index > 3) + return -1; + + if (enable) + wakeup_intr[index] |= (1 << off); + else + wakeup_intr[index] &= ~(1 << off); + + return 0; +} + +static struct irq_chip mxc_tzic_chip = { + .name = "MXC_TZIC", + .ack = mxc_mask_irq, + .mask = mxc_mask_irq, + .unmask = mxc_unmask_irq, + .set_wake = mxc_set_wake_irq, +}; + +/*! + * This function initializes the TZIC hardware and disables all the + * interrupts. It registers the interrupt enable and disable functions + * to the kernel for each interrupt source. + */ +void __init mxc_init_irq(void) +{ + int i; + + /* put the TZIC into the reset value with + * all interrupts disabled + */ + i = __raw_readl(TZIC_INTCNTL); + + __raw_writel(0x80010001, TZIC_INTCNTL); + i = __raw_readl(TZIC_INTCNTL); + __raw_writel(0x1f, TZIC_PRIOMASK); + i = __raw_readl(TZIC_PRIOMASK); + __raw_writel(0x02, TZIC_SYNCCTRL); + i = __raw_readl(TZIC_SYNCCTRL); + for (i = 0; i < 4; i++) { + __raw_writel(0xFFFFFFFF, TZIC_INTSEC0 + i * 4); + } + /* disable all interrupts */ + for (i = 0; i < 4; i++) { + __raw_writel(0xFFFFFFFF, TZIC_ENCLEAR0 + i * 4); + } + + /* all IRQ no FIQ Warning :: No selection */ + + for (i = 0; i < TZIC_NUM_IRQS; i++) { + set_irq_chip(i, &mxc_tzic_chip); + set_irq_handler(i, handle_level_irq); + set_irq_flags(i, IRQF_VALID); + } + + printk(KERN_INFO "MXC IRQ initialized\n"); +} + +/*! + * enable wakeup interrupt + * + * @param is_idle 1 if called in idle loop (enset registers); + * 0 to be used when called from low power entry + * @return 0 if successful; non-zero otherwise + */ +int tzic_enable_wake(int is_idle) +{ + unsigned int i, v; + + __raw_writel(1, TZIC_DSMINT); + if (unlikely(__raw_readl(TZIC_DSMINT) == 0)) + return -EAGAIN; + + if (likely(is_idle)) { + for (i = 0; i < 4; i++) { + v = __raw_readl(TZIC_ENSET0 + i * 4); + __raw_writel(v, TZIC_WAKEUP0 + i * 4); + } + } else { + for (i = 0; i < 4; i++) { + v = wakeup_intr[i]; + __raw_writel(v, TZIC_WAKEUP0 + i * 4); + } + } + return 0; +} diff --git a/arch/arm/plat-mxc/usb_common.c b/arch/arm/plat-mxc/usb_common.c new file mode 100644 index 000000000000..a1f53fd9f3be --- /dev/null +++ b/arch/arm/plat-mxc/usb_common.c @@ -0,0 +1,860 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * + * otg_{get,set}_transceiver() are from arm/plat-omap/usb.c. + * which is Copyright (C) 2004 Texas Instruments, Inc. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + *@defgroup USB ARC OTG USB Driver + */ + +/*! + * @file usb_common.c + * + * @brief platform related part of usb driver. + * @ingroup USB + */ + +/*! + *Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/usb/otg.h> +#include <linux/usb/fsl_xcvr.h> +#include <linux/regulator/consumer.h> +#include <mach/arc_otg.h> +#include <mach/hardware.h> +#include <asm/mach-types.h> + +#define MXC_NUMBER_USB_TRANSCEIVER 6 +struct fsl_xcvr_ops *g_xc_ops[MXC_NUMBER_USB_TRANSCEIVER] = { NULL }; + +static struct clk *usb_clk; +static struct clk *usb_ahb_clk; + +extern int gpio_usbotg_hs_active(void); +extern int gpio_usbotg_hs_inactive(void); + +/* + * make sure USB_CLK is running at 60 MHz +/- 1000 Hz + */ +static int fsl_check_usbclk(void) +{ + unsigned long freq; + + usb_ahb_clk = clk_get(NULL, "usb_ahb_clk"); + if (clk_enable(usb_ahb_clk)) { + printk(KERN_ERR "clk_enable(usb_ahb_clk) failed\n"); + return -EINVAL; + } + clk_put(usb_ahb_clk); + + usb_clk = clk_get(NULL, "usb_clk"); + freq = clk_get_rate(usb_clk); + clk_put(usb_clk); + if ((freq < 59999000) || (freq > 60001000)) { + printk(KERN_ERR "USB_CLK=%lu, should be 60MHz\n", freq); + return -1; + } + + return 0; +} + +void fsl_usb_xcvr_register(struct fsl_xcvr_ops *xcvr_ops) +{ + int i; + + pr_debug("%s\n", __func__); + for (i = 0; i < MXC_NUMBER_USB_TRANSCEIVER; i++) { + if (g_xc_ops[i] == NULL) { + g_xc_ops[i] = xcvr_ops; + return; + } + } + + pr_debug("Failed %s\n", __func__); +} +EXPORT_SYMBOL(fsl_usb_xcvr_register); + +void fsl_platform_set_test_mode (struct fsl_usb2_platform_data *pdata, enum usb_test_mode mode) +{ + if (pdata->xcvr_ops && pdata->xcvr_ops->set_test_mode) + pdata->xcvr_ops->set_test_mode((u32 *)(pdata->regs + ULPIVW_OFF), mode); +} +EXPORT_SYMBOL(fsl_platform_set_test_mode); + +void fsl_usb_xcvr_unregister(struct fsl_xcvr_ops *xcvr_ops) +{ + int i; + + pr_debug("%s\n", __func__); + for (i = 0; i < MXC_NUMBER_USB_TRANSCEIVER; i++) { + if (g_xc_ops[i] == xcvr_ops) { + g_xc_ops[i] = NULL; + return; + } + } + + pr_debug("Failed %s\n", __func__); +} +EXPORT_SYMBOL(fsl_usb_xcvr_unregister); + +static struct fsl_xcvr_ops *fsl_usb_get_xcvr(char *name) +{ + int i; + + pr_debug("%s\n", __func__); + if (name == NULL) { + printk(KERN_ERR "get_xcvr(): No tranceiver name\n"); + return NULL; + } + + for (i = 0; i < MXC_NUMBER_USB_TRANSCEIVER; i++) { + if (strcmp(g_xc_ops[i]->name, name) == 0) { + return g_xc_ops[i]; + } + } + pr_debug("Failed %s\n", __func__); + return NULL; +} + +/* The dmamask must be set for EHCI to work */ +static u64 ehci_dmamask = ~(u32) 0; + +/*! + * Register an instance of a USB host platform device. + * + * @param res: resource pointer + * @param n_res: number of resources + * @param config: config pointer + * + * @return newly-registered platform_device + * + * The USB controller supports 3 host interfaces, and the + * kernel can be configured to support some number of them. + * Each supported host interface is registered as an instance + * of the "fsl-ehci" device. Call this function multiple times + * to register each host interface. + */ +static int instance_id = 0; +struct platform_device *host_pdev_register(struct resource *res, int n_res, + struct fsl_usb2_platform_data *config) +{ + struct platform_device *pdev; + int rc; + + pr_debug("register host res=0x%p, size=%d\n", res, n_res); + + pdev = platform_device_register_simple("fsl-ehci", + instance_id, res, n_res); + if (IS_ERR(pdev)) { + pr_debug("can't register %s Host, %ld\n", + config->name, PTR_ERR(pdev)); + return NULL; + } + + pdev->dev.coherent_dma_mask = 0xffffffff; + pdev->dev.dma_mask = &ehci_dmamask; + + /* + * platform_device_add_data() makes a copy of + * the platform_data passed in. That makes it + * impossible to share the same config struct for + * all OTG devices (host,gadget,otg). So, just + * set the platorm_data pointer ourselves. + */ + rc = platform_device_add_data(pdev, config, + sizeof(struct fsl_usb2_platform_data)); + if (rc) { + platform_device_unregister(pdev); + return NULL; + } + + printk(KERN_INFO "usb: %s host (%s) registered\n", config->name, + config->transceiver); + pr_debug("pdev=0x%p dev=0x%p resources=0x%p pdata=0x%p\n", + pdev, &pdev->dev, pdev->resource, pdev->dev.platform_data); + + instance_id++; + + return pdev; +} + +#if defined(CONFIG_USB_OTG) +static struct otg_transceiver *xceiv; + +/** + * otg_get_transceiver - find the (single) OTG transceiver driver + * + * Returns the transceiver driver, after getting a refcount to it; or + * null if there is no such transceiver. The caller is responsible for + * releasing that count. + */ +struct otg_transceiver *otg_get_transceiver(void) +{ + pr_debug("%s xceiv=0x%p\n", __func__, xceiv); + if (xceiv) + get_device(xceiv->dev); + return xceiv; +} +EXPORT_SYMBOL(otg_get_transceiver); + +int otg_set_transceiver(struct otg_transceiver *x) +{ + pr_debug("%s xceiv=0x%p x=0x%p\n", __func__, xceiv, x); + if (xceiv && x) + return -EBUSY; + xceiv = x; + return 0; +} +EXPORT_SYMBOL(otg_set_transceiver); + +static struct resource *otg_resources; + +struct resource *otg_get_resources(void) +{ + return otg_resources; +} +EXPORT_SYMBOL(otg_get_resources); + +int otg_set_resources(struct resource *resources) +{ + otg_resources = resources; + return 0; +} +EXPORT_SYMBOL(otg_set_resources); +#endif + +static void usbh1_set_serial_xcvr(void) +{ + pr_debug("%s: \n", __func__); + USBCTRL &= ~(UCTRL_H1SIC_MASK | UCTRL_BPE); /* disable bypass mode */ + USBCTRL |= UCTRL_H1SIC_SU6 | /* single-ended / unidir. */ + UCTRL_H1WIE | UCTRL_H1DT | /* disable H1 TLL */ + UCTRL_H1PM; /* power mask */ +} + +static void usbh1_set_ulpi_xcvr(void) +{ + pr_debug("%s: \n", __func__); + + /* Stop then Reset */ + UH1_USBCMD &= ~UCMD_RUN_STOP; + while (UH1_USBCMD & UCMD_RUN_STOP) ; + + UH1_USBCMD |= UCMD_RESET; + while (UH1_USBCMD & UCMD_RESET) ; + + /* Select the clock from external PHY */ + USB_CTRL_1 |= USB_CTRL_UH1_EXT_CLK_EN; + + /* select ULPI PHY PTS=2 */ + UH1_PORTSC1 = (UH1_PORTSC1 & ~PORTSC_PTS_MASK) | PORTSC_PTS_ULPI; + + USBCTRL |= UCTRL_H1WIE; /* HOST1 wakeup intr enable */ + USBCTRL |= UCTRL_H1UIE; /* Host1 ULPI interrupt enable */ + USBCTRL &= ~UCTRL_H1PM; /* HOST1 power mask */ + + /* Interrupt Threshold Control:Immediate (no threshold) */ + UH1_USBCMD &= UCMD_ITC_NO_THRESHOLD; + + UH1_USBCMD |= UCMD_RESET; /* reset the controller */ + + /* allow controller to reset, and leave time for + * the ULPI transceiver to reset too. + */ + msleep(100); + + /* Turn off the usbpll for ulpi tranceivers */ + clk_disable(usb_clk); +} +static void usbh2_set_ulpi_xcvr(void) +{ + u32 tmp; + + pr_debug("%s\n", __func__); + USBCTRL &= ~(UCTRL_H2SIC_MASK | UCTRL_BPE); + USBCTRL |= UCTRL_H2WIE | /* wakeup intr enable */ + UCTRL_H2UIE | /* ULPI intr enable */ + UCTRL_H2DT | /* disable H2 TLL */ + UCTRL_H2PM; /* power mask */ + + /* must set ULPI phy before turning off clock */ + tmp = UH2_PORTSC1 & ~PORTSC_PTS_MASK; + tmp |= PORTSC_PTS_ULPI; + UH2_PORTSC1 = tmp; + + UH2_USBCMD |= UCMD_RESET; /* reset the controller */ + + /* allow controller to reset, and leave time for + * the ULPI transceiver to reset too. + */ + msleep(100); + + /* Turn off the usbpll for ulpi tranceivers */ + clk_disable(usb_clk); +} + +static void usbh2_set_serial_xcvr(void) +{ + pr_debug("%s: \n", __func__); + + /* Stop then Reset */ + UH2_USBCMD &= ~UCMD_RUN_STOP; + while (UH2_USBCMD & UCMD_RUN_STOP) ; + + UH2_USBCMD |= UCMD_RESET; + while (UH2_USBCMD & UCMD_RESET) ; + + USBCTRL &= ~(UCTRL_H2SIC_MASK); /* Disable bypass mode */ + USBCTRL &= ~(UCTRL_H2PM); /* Power Mask */ + USBCTRL &= ~UCTRL_H2OCPOL; /* OverCurrent Polarity is Low Active */ + USBCTRL &= ~UCTRL_H2WIE; /* Wakeup intr disable */ + USBCTRL |= UCTRL_IP_PUE_DOWN | /* ipp_pue_pulldwn_dpdm */ + UCTRL_USBTE | /* USBT is enabled */ + UCTRL_H2DT; /* Disable H2 TLL */ + + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 0) { + /* Disable Host2 bus Lock for i.MX35 1.0 */ + USBCTRL |= UCTRL_H2LOCKD; + /* USBOTG_PWR low active */ + USBCTRL &= ~UCTRL_PP; + /* OverCurrent Polarity is Low Active */ + USBCTRL &= ~UCTRL_OCPOL; + } else if (cpu_is_mx35_rev(CHIP_REV_2_0) >= 1) { + /* i.MX35 2.0 OTG and Host2 have seperate OC/PWR polarity */ + USBCTRL &= ~UCTRL_H2PP; + USBCTRL &= ~UCTRL_H2OCPOL; + } else if (cpu_is_mx25()) { + /* + * USBH2_PWR and USBH2_OC are active high. + * Must force xcvr clock to "internal" so that + * we can write to PTS field after it's been + * cleared by ehci_turn_off_all_ports(). + */ + USBCTRL |= UCTRL_H2PP | UCTRL_H2OCPOL | UCTRL_XCSH2; + /* Disable Host2 bus Lock */ + USBCTRL |= UCTRL_H2LOCKD; + } + + USBCTRL &= ~(UCTRL_PP); + UH2_PORTSC1 = (UH2_PORTSC1 & (~PORTSC_PTS_MASK)) | PORTSC_PTS_SERIAL; + + if (UH2_HCSPARAMS & HCSPARAMS_PPC) + UH2_PORTSC1 |= PORTSC_PORT_POWER; + + /* Reset controller before set host mode */ + UH2_USBCMD |= UCMD_RESET; + while (UH2_USBCMD & UCMD_RESET) ; + + msleep(100); +} + +/*! + * Register remote wakeup by this usb controller + * + * @param pdev: platform_device for this usb controller + * + * @return 0 or negative error code in case not supportted. + */ +static int usb_register_remote_wakeup(struct platform_device *pdev) +{ + struct resource *res; + int irq; + + pr_debug("%s: pdev=0x%p \n", __func__, pdev); + if (!cpu_is_mx51()) + return -ECANCELED; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, + "Found HC with no IRQ. Check %s setup!\n", + dev_name(&pdev->dev)); + return -ENODEV; + } + irq = res->start; + pdev->dev.power.can_wakeup = 1; + enable_irq_wake(irq); + + return 0; +} + +extern void gpio_usbh1_setback_stp(void); +extern void gpio_usbh2_setback_stp(void); + +int fsl_usb_host_init(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + struct fsl_xcvr_ops *xops; + + pr_debug("%s: pdev=0x%p pdata=0x%p\n", __func__, pdev, pdata); + + xops = fsl_usb_get_xcvr(pdata->transceiver); + if (!xops) { + printk(KERN_ERR "%s transceiver ops missing\n", pdata->name); + return -EINVAL; + } + pdata->xcvr_ops = xops; + pdata->xcvr_type = xops->xcvr_type; + pdata->pdev = pdev; + + if (fsl_check_usbclk() != 0) + return -EINVAL; + + pr_debug("%s: grab pins\n", __func__); + if (pdata->gpio_usb_active()) + return -EINVAL; + + if (clk_enable(usb_clk)) { + printk(KERN_ERR "clk_enable(usb_clk) failed\n"); + return -EINVAL; + } + + if (cpu_is_mx51()) { + usb_clk = clk_get(NULL, "usboh3_clk"); + clk_enable(usb_clk); + clk_put(usb_clk); + } + + /* enable board power supply for xcvr */ + if (pdata->xcvr_pwr) { + if (pdata->xcvr_pwr->regu1) + regulator_enable(pdata->xcvr_pwr->regu1); + if (pdata->xcvr_pwr->regu2) + regulator_enable(pdata->xcvr_pwr->regu2); + } + + if (xops->init) + xops->init(xops); + + if (usb_register_remote_wakeup(pdev)) + pr_debug("%s port is not a wakeup source.\n", pdata->name); + + if (xops->xcvr_type == PORTSC_PTS_SERIAL) { + if (cpu_is_mx35()) { + usbh2_set_serial_xcvr(); + /* Close the internal 60Mhz */ + USBCTRL &= ~UCTRL_XCSH2; + } else if (cpu_is_mx25()) + usbh2_set_serial_xcvr(); + else + usbh1_set_serial_xcvr(); + } else if (xops->xcvr_type == PORTSC_PTS_ULPI) { + if (!strcmp("Host 1", pdata->name)) { + usbh1_set_ulpi_xcvr(); + if (cpu_is_mx51()) { +#ifdef CONFIG_USB_EHCI_ARC_H1 + gpio_usbh1_setback_stp(); + /* disable remote wakeup irq */ + USBCTRL &= ~UCTRL_H1WIE; +#endif + } + } + if (!strcmp("Host 2", pdata->name)) { + usbh2_set_ulpi_xcvr(); + if (cpu_is_mx51()) { +#ifdef CONFIG_USB_EHCI_ARC_H2 + gpio_usbh2_setback_stp(); +#endif + } + } + } + + pr_debug("%s: %s success\n", __func__, pdata->name); + return 0; +} +EXPORT_SYMBOL(fsl_usb_host_init); + +void fsl_usb_host_uninit(struct fsl_usb2_platform_data *pdata) +{ + pr_debug("%s\n", __func__); + + if (pdata->xcvr_ops && pdata->xcvr_ops->uninit) + pdata->xcvr_ops->uninit(pdata->xcvr_ops); + + pdata->regs = NULL; + + pdata->gpio_usb_inactive(); + if (pdata->xcvr_type == PORTSC_PTS_SERIAL) { + /* Workaround an IC issue for ehci driver. + * when turn off root hub port power, EHCI set + * PORTSC reserved bits to be 0, but PTS with 0 + * means UTMI interface, so here force the Host2 + * port use the internal 60Mhz. + */ + if (cpu_is_mx35()) + USBCTRL |= UCTRL_XCSH2; + clk_disable(usb_clk); + } + + /* disable board power supply for xcvr */ + if (pdata->xcvr_pwr) { + if (pdata->xcvr_pwr->regu1) + regulator_disable(pdata->xcvr_pwr->regu1); + if (pdata->xcvr_pwr->regu2) + regulator_disable(pdata->xcvr_pwr->regu2); + } + + if (cpu_is_mx51()) { + usb_clk = clk_get(NULL, "usboh3_clk"); + clk_disable(usb_clk); + clk_put(usb_clk); + } +} +EXPORT_SYMBOL(fsl_usb_host_uninit); + +static void otg_set_serial_xcvr(void) +{ + pr_debug("%s\n", __func__); +} + +void otg_set_serial_host(void) +{ + pr_debug("%s\n", __func__); + /* set USBCTRL for host operation + * disable: bypass mode, + * set: single-ended/unidir/6 wire, OTG wakeup intr enable, + * power mask + */ + USBCTRL &= ~UCTRL_OSIC_MASK; +#if defined(CONFIG_ARCH_MX27) || defined(CONFIG_ARCH_MX3) + USBCTRL &= ~UCTRL_BPE; +#endif + +#if defined(CONFIG_MXC_USB_SB3) + USBCTRL |= UCTRL_OSIC_SB3 | UCTRL_OWIE | UCTRL_OPM; +#elif defined(CONFIG_MXC_USB_SU6) + USBCTRL |= UCTRL_OSIC_SU6 | UCTRL_OWIE | UCTRL_OPM; +#elif defined(CONFIG_MXC_USB_DB4) + USBCTRL |= UCTRL_OSIC_DB4 | UCTRL_OWIE | UCTRL_OPM; +#else + USBCTRL |= UCTRL_OSIC_DU6 | UCTRL_OWIE | UCTRL_OPM; +#endif + + USB_OTG_MIRROR = OTGM_VBUSVAL | OTGM_ASESVLD; /* 0xa */ +} +EXPORT_SYMBOL(otg_set_serial_host); + +void otg_set_serial_peripheral(void) +{ + /* set USBCTRL for device operation + * disable: bypass mode + * set: differential/unidir/6 wire, OTG wakeup intr enable, + * power mask + */ + USBCTRL &= ~UCTRL_OSIC_MASK; +#if defined(CONFIG_ARCH_MX27) || defined(CONFIG_ARCH_MX3) + USBCTRL &= ~UCTRL_BPE; +#endif + +#if defined(CONFIG_MXC_USB_SB3) + USBCTRL |= UCTRL_OSIC_SB3 | UCTRL_OWIE | UCTRL_OPM; +#elif defined(CONFIG_MXC_USB_SU6) + USBCTRL |= UCTRL_OSIC_SU6 | UCTRL_OWIE | UCTRL_OPM; +#elif defined(CONFIG_MXC_USB_DB4) + USBCTRL |= UCTRL_OSIC_DB4 | UCTRL_OWIE | UCTRL_OPM; +#else + USBCTRL |= UCTRL_OSIC_DU6 | UCTRL_OWIE | UCTRL_OPM; +#endif + + USB_OTG_MIRROR = OTGM_VBUSVAL | OTGM_BSESVLD | OTGM_IDIDG; /* oxd */ +} +EXPORT_SYMBOL(otg_set_serial_peripheral); + +static void otg_set_ulpi_xcvr(void) +{ + u32 tmp; + + pr_debug("%s\n", __func__); + USBCTRL &= ~UCTRL_OSIC_MASK; +#if defined(CONFIG_ARCH_MX27) || defined(CONFIG_ARCH_MX3) + USBCTRL &= ~UCTRL_BPE; +#endif + USBCTRL |= UCTRL_OUIE | /* ULPI intr enable */ + UCTRL_OWIE | /* OTG wakeup intr enable */ + UCTRL_OPM; /* power mask */ + + /* must set ULPI phy before turning off clock */ + tmp = UOG_PORTSC1 & ~PORTSC_PTS_MASK; + tmp |= PORTSC_PTS_ULPI; + UOG_PORTSC1 = tmp; + + /* need to reset the controller here so that the ID pin + * is correctly detected. + */ + UOG_USBCMD |= UCMD_RESET; + + /* allow controller to reset, and leave time for + * the ULPI transceiver to reset too. + */ + msleep(100); + + /* Turn off the usbpll for ulpi tranceivers */ + clk_disable(usb_clk); +} + +int fsl_usb_xcvr_suspend(struct fsl_xcvr_ops *xcvr_ops) +{ + if (!machine_is_mx31_3ds()) + return -ECANCELED; + + if (xcvr_ops->xcvr_type == PORTSC_PTS_ULPI) { + if (fsl_check_usbclk() != 0) + return -EINVAL; + if (gpio_usbotg_hs_active()) + return -EINVAL; + clk_enable(usb_clk); + + otg_set_ulpi_xcvr(); + + if (xcvr_ops->suspend) + /* suspend transceiver */ + xcvr_ops->suspend(xcvr_ops); + + gpio_usbotg_hs_inactive(); + clk_disable(usb_clk); + } + return 0; +} +EXPORT_SYMBOL(fsl_usb_xcvr_suspend); + +static void otg_set_utmi_xcvr(void) +{ + u32 tmp; + + /* Stop then Reset */ + UOG_USBCMD &= ~UCMD_RUN_STOP; + while (UOG_USBCMD & UCMD_RUN_STOP) ; + + UOG_USBCMD |= UCMD_RESET; + while ((UOG_USBCMD) & (UCMD_RESET)) ; + + if (cpu_is_mx51()) { + /* OTG Polarity of Overcurrent is Low active */ + USB_PHY_CTR_FUNC |= USB_UTMI_PHYCTRL_OC_POL; + /* Enable OTG Overcurrent Event */ + USB_PHY_CTR_FUNC &= ~USB_UTMI_PHYCTRL_OC_DIS; + } else if (cpu_is_mx25()) { + USBCTRL |= UCTRL_OCPOL; + USBCTRL &= ~UCTRL_PP; + } else { + /* USBOTG_PWR low active */ + USBCTRL &= ~UCTRL_PP; + /* OverCurrent Polarity is Low Active */ + USBCTRL &= ~UCTRL_OCPOL; + + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 0) + /* OTG Lock Disable */ + USBCTRL |= UCTRL_OLOCKD; + } + + USBCTRL &= ~UCTRL_OPM; /* OTG Power Mask */ + USBCTRL &= ~UCTRL_OWIE; /* OTG Wakeup Intr Disable */ + + /* set UTMI xcvr */ + tmp = UOG_PORTSC1 & ~PORTSC_PTS_MASK; + tmp |= PORTSC_PTS_UTMI; + UOG_PORTSC1 = tmp; + + if (cpu_is_mx51()) { + /* Set the PHY clock to 19.2MHz */ + USB_PHY_CTR_FUNC2 &= ~USB_UTMI_PHYCTRL2_PLLDIV_MASK; + USB_PHY_CTR_FUNC2 |= 0x01; + } else if (machine_is_mx37_3ds()) { + /* Reference voltage for HS disconnect envelope detector */ + /* adjust the Squelch level */ + USB_PHY_CTR_FUNC2 &= ~(USB_UTMI_PHYCTRL2_HSDEVSEL_MASK << + USB_UTMI_PHYCTRL2_HSDEVSEL_SHIFT); + } + + /* Workaround an IC issue for ehci driver: + * when turn off root hub port power, EHCI set + * PORTSC reserved bits to be 0, but PTW with 0 + * means 8 bits tranceiver width, here change + * it back to be 16 bits and do PHY diable and + * then enable. + */ + UOG_PORTSC1 |= PORTSC_PTW; + + if (cpu_is_mx35() || cpu_is_mx25()) { + /* Enable UTMI interface in PHY control Reg */ + USB_PHY_CTR_FUNC &= ~USB_UTMI_PHYCTRL_UTMI_ENABLE; + USB_PHY_CTR_FUNC |= USB_UTMI_PHYCTRL_UTMI_ENABLE; + } + + /* need to reset the controller here so that the ID pin + * is correctly detected. + */ + /* Stop then Reset */ + UOG_USBCMD &= ~UCMD_RUN_STOP; + while (UOG_USBCMD & UCMD_RUN_STOP) ; + + UOG_USBCMD |= UCMD_RESET; + while ((UOG_USBCMD) & (UCMD_RESET)) ; + + /* allow controller to reset, and leave time for + * the ULPI transceiver to reset too. + */ + msleep(100); + + /* Turn off the usbpll for mx25 UTMI tranceivers */ + /* DDD: can we do this UTMI xcvrs on all boards? */ + if (cpu_is_mx25()) { + clk_disable(usb_clk); + } else if (cpu_is_mx37()) { + /* fix USB PHY Power Gating leakage issue for i.MX37 */ + USB_PHY_CTR_FUNC &= ~USB_UTMI_PHYCTRL_CHGRDETON; + USB_PHY_CTR_FUNC &= ~USB_UTMI_PHYCTRL_CHGRDETEN; + } +} + +static int otg_used = 0; + +int usbotg_init(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + struct fsl_xcvr_ops *xops; + + pr_debug("%s: pdev=0x%p pdata=0x%p\n", __func__, pdev, pdata); + + xops = fsl_usb_get_xcvr(pdata->transceiver); + if (!xops) { + printk(KERN_ERR "DR transceiver ops missing\n"); + return -EINVAL; + } + pdata->xcvr_ops = xops; + pdata->xcvr_type = xops->xcvr_type; + pdata->pdev = pdev; + + if (!otg_used) { + if (fsl_check_usbclk() != 0) + return -EINVAL; + + pr_debug("%s: grab pins\n", __func__); + if (pdata->gpio_usb_active()) + return -EINVAL; + + if (clk_enable(usb_clk)) { + printk(KERN_ERR "clk_enable(usb_clk) failed\n"); + return -EINVAL; + } + + if (xops->init) + xops->init(xops); + + UOG_PORTSC1 = UOG_PORTSC1 & ~PORTSC_PHCD; + if (xops->xcvr_type == PORTSC_PTS_SERIAL) { + if (pdata->operating_mode == FSL_USB2_DR_HOST) { + otg_set_serial_host(); + /* need reset */ + UOG_USBCMD |= UCMD_RESET; + msleep(100); + } else if (pdata->operating_mode == FSL_USB2_DR_DEVICE) + otg_set_serial_peripheral(); + otg_set_serial_xcvr(); + } else if (xops->xcvr_type == PORTSC_PTS_ULPI) { + otg_set_ulpi_xcvr(); + } else if (xops->xcvr_type == PORTSC_PTS_UTMI) { + otg_set_utmi_xcvr(); + } + } + + if (usb_register_remote_wakeup(pdev)) + pr_debug("DR is not a wakeup source.\n"); + + otg_used++; + pr_debug("%s: success\n", __func__); + return 0; +} +EXPORT_SYMBOL(usbotg_init); + +void usbotg_uninit(struct fsl_usb2_platform_data *pdata) +{ + pr_debug("%s\n", __func__); + + otg_used--; + if (!otg_used) { + if (pdata->xcvr_ops && pdata->xcvr_ops->uninit) + pdata->xcvr_ops->uninit(pdata->xcvr_ops); + + pdata->regs = NULL; + + if (machine_is_mx31_3ds()) { + if (pdata->xcvr_ops && pdata->xcvr_ops->suspend) + pdata->xcvr_ops->suspend(pdata->xcvr_ops); + clk_disable(usb_clk); + } + msleep(1); + UOG_PORTSC1 = UOG_PORTSC1 | PORTSC_PHCD; + pdata->gpio_usb_inactive(); + if (pdata->xcvr_type == PORTSC_PTS_SERIAL) + clk_disable(usb_clk); + clk_disable(usb_ahb_clk); + } +} +EXPORT_SYMBOL(usbotg_uninit); + +int usb_host_wakeup_irq(struct device *wkup_dev) +{ + int wakeup_req = 0; + struct fsl_usb2_platform_data *pdata = wkup_dev->platform_data; + + if (!strcmp("Host 1", pdata->name)) { + wakeup_req = USBCTRL & UCTRL_H1WIR; + } else if (!strcmp("DR", pdata->name)) { + wakeup_req = USBCTRL & UCTRL_OWIR; + /* If DR is in device mode, let udc handle it */ + if (wakeup_req && ((UOG_USBMODE & 0x3) == 0x2)) + wakeup_req = 0; + } + + return wakeup_req; +} +EXPORT_SYMBOL(usb_host_wakeup_irq); + +void usb_host_set_wakeup(struct device *wkup_dev, bool para) +{ + struct fsl_usb2_platform_data *pdata = wkup_dev->platform_data; + + /* If this device may wakeup */ + if (device_may_wakeup(wkup_dev) && para) { + if (!strcmp("Host 1", pdata->name)) { + USBCTRL |= UCTRL_H1WIE; + } else if (!strcmp("DR", pdata->name)) { + USBCTRL |= UCTRL_OWIE; + /* Enable OTG ID Wakeup */ + USBCTRL_HOST2 |= (1 << 5); + } + } + + if (!para) { + if (!strcmp("Host 1", pdata->name)) + USBCTRL &= ~UCTRL_H1WIE; + else if (!strcmp("DR", pdata->name)) { + USBCTRL &= ~UCTRL_OWIE; + USBCTRL_HOST2 &= ~(1 << 5); + } + } +} +EXPORT_SYMBOL(usb_host_set_wakeup); diff --git a/arch/arm/plat-mxc/utmixc.c b/arch/arm/plat-mxc/utmixc.c new file mode 100644 index 000000000000..a3b2bad03444 --- /dev/null +++ b/arch/arm/plat-mxc/utmixc.c @@ -0,0 +1,106 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/usb/fsl_xcvr.h> +#include <linux/pmic_external.h> + +#include <mach/hardware.h> +#include <mach/arc_otg.h> +#include <asm/mach-types.h> + +static struct regulator *usbotg_regux; + +static void usb_utmi_init(struct fsl_xcvr_ops *this) +{ +#if defined(CONFIG_MXC_PMIC_MC13892_MODULE) || defined(CONFIG_MXC_PMIC_MC13892) + if (machine_is_mx51_3ds()) { + unsigned int value; + + /* VUSBIN */ + pmic_read_reg(REG_USB1, &value, 0xffffff); + value |= 0x1; + value |= (0x1 << 3); + pmic_write_reg(REG_USB1, value, 0xffffff); + } +#endif +} + +static void usb_utmi_uninit(struct fsl_xcvr_ops *this) +{ +} + +/*! + * set vbus power + * + * @param view viewport register + * @param on power on or off + */ +static void set_power(struct fsl_xcvr_ops *this, + struct fsl_usb2_platform_data *pdata, int on) +{ + struct device *dev = &pdata->pdev->dev; + + pr_debug("real %s(on=%d) pdata=0x%p\n", __func__, on, pdata); + if (machine_is_mx37_3ds()) { + if (on) { + if (!board_is_rev(BOARD_REV_2)) + usbotg_regux = regulator_get(dev, "DCDC2"); + else + usbotg_regux = regulator_get(dev, "SWBST"); + + regulator_enable(usbotg_regux); + } else { + regulator_disable(usbotg_regux); + regulator_put(usbotg_regux); + } + } +} + +static struct fsl_xcvr_ops utmi_ops = { + .name = "utmi", + .xcvr_type = PORTSC_PTS_UTMI, + .init = usb_utmi_init, + .uninit = usb_utmi_uninit, + .set_vbus_power = set_power, +}; + +extern void fsl_usb_xcvr_register(struct fsl_xcvr_ops *xcvr_ops); + +static int __init utmixc_init(void) +{ + fsl_usb_xcvr_register(&utmi_ops); + return 0; +} + +extern void fsl_usb_xcvr_unregister(struct fsl_xcvr_ops *xcvr_ops); + +static void __exit utmixc_exit(void) +{ + fsl_usb_xcvr_unregister(&utmi_ops); +} + +module_init(utmixc_init); +module_exit(utmixc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("utmi xcvr driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-mxc/wdog.c b/arch/arm/plat-mxc/wdog.c new file mode 100644 index 000000000000..8f242e707f58 --- /dev/null +++ b/arch/arm/plat-mxc/wdog.c @@ -0,0 +1,68 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file plat-mxc/wdog.c + * @brief This file contains watchdog timer implementations. + * + * This file contains watchdog timer implementations for timer tick. + * + * @ingroup WDOG + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <mach/hardware.h> + +#define WDOG_WT 0x8 /* WDOG WT starting bit inside WCR */ +#define WCR_WOE_BIT (1 << 6) +#define WCR_WDA_BIT (1 << 5) +#define WCR_SRS_BIT (1 << 4) +#define WCR_WRE_BIT (1 << 3) +#define WCR_WDE_BIT (1 << 2) +#define WCR_WDBG_BIT (1 << 1) +#define WCR_WDZST_BIT (1 << 0) + +/* + * WatchDog + */ +#define WDOG_WCR 0 /* 16bit watchdog control reg */ +#define WDOG_WSR 2 /* 16bit watchdog service reg */ +#define WDOG_WRSR 4 /* 16bit watchdog reset status reg */ + +/*! + * The base addresses for the WDOG modules + */ +static void __iomem *wdog_base[2] = { + IO_ADDRESS(WDOG1_BASE_ADDR), +#ifdef WDOG2_BASE_ADDR + IO_ADDRESS(WDOG2_BASE_ADDR), +#endif +}; + +void mxc_wd_reset(void) +{ + u16 reg; + struct clk *clk; + + clk = clk_get(NULL, "wdog_clk"); + clk_enable(clk); + + reg = __raw_readw(wdog_base[0] + WDOG_WCR) & ~WCR_SRS_BIT; + reg |= WCR_WDE_BIT; + __raw_writew(reg, wdog_base[0] + WDOG_WCR); +} diff --git a/arch/arm/plat-stmp3xxx/Kconfig b/arch/arm/plat-stmp3xxx/Kconfig index 2cf37c35951b..e211f2604307 100644 --- a/arch/arm/plat-stmp3xxx/Kconfig +++ b/arch/arm/plat-stmp3xxx/Kconfig @@ -32,6 +32,8 @@ config MACH_STMP378X endchoice +source arch/arm/mach-stmp378x/Kconfig + endmenu endif diff --git a/arch/arm/plat-stmp3xxx/Makefile b/arch/arm/plat-stmp3xxx/Makefile index 31dd518f37a5..50114dca7261 100644 --- a/arch/arm/plat-stmp3xxx/Makefile +++ b/arch/arm/plat-stmp3xxx/Makefile @@ -2,4 +2,18 @@ # Makefile for the linux kernel. # # Object file lists. -obj-y += core.o timer.o irq.o dma.o clock.o pinmux.o devices.o +obj-y += core.o timer.o irq.o dma.o clock.o pinmux.o devices.o \ + lradc.o spi.o mmc.o \ + power-test.o + +obj-$(CONFIG_ARCH_STMP378X) += dcp-bootstream.o + +# Power Management +obj-$(CONFIG_CPU_FREQ) += cpufreq.o + +obj-$(CONFIG_STMP3XXX_UNIQUE_ID) += unique-id.o + +# charging/current limitation testing +obj-m += power-test.o + + diff --git a/arch/arm/plat-stmp3xxx/clock.c b/arch/arm/plat-stmp3xxx/clock.c index 5d2f19a09e44..b59ffbb2debe 100644 --- a/arch/arm/plat-stmp3xxx/clock.c +++ b/arch/arm/plat-stmp3xxx/clock.c @@ -421,6 +421,83 @@ static long cpu_round_rate(struct clk *clk, u32 rate) return r; } +static int emi_set_rate(struct clk *clk, u32 rate) +{ + int ret = 0; + + if (rate < 24000) + return -EINVAL; + else { + int i; + struct stmp3xxx_emi_scaling_data sc_data; + int (*scale)(struct stmp3xxx_emi_scaling_data *) = + (void *)STMP3XXX_OCRAM_BASE; + void *saved_ocram; + u32 clkctrl_emi; + u32 clkctrl_frac; + int div = 1; + /* + * We've been setting div to HW_CLKCTRL_CPU_RD() & 0x3f so far. + * TODO: verify 1 is still valid. + */ + + if (!stmp3xxx_ram_funcs_sz) + goto out; + + for (clkctrl_emi = div; clkctrl_emi < 0x3f; + clkctrl_emi += div) { + clkctrl_frac = + (pll_clk.rate * 18 + rate * clkctrl_emi / 2) / + (rate * clkctrl_emi); + if (clkctrl_frac >= 18 && clkctrl_frac <= 35) { + pr_debug("%s: clkctrl_frac found %d for %d\n", + __func__, clkctrl_frac, clkctrl_emi); + if (pll_clk.rate * 18 / + clkctrl_frac / clkctrl_emi / 100 == + rate / 100) + break; + } + } + if (clkctrl_emi >= 0x3f) + return -EINVAL; + pr_debug("%s: clkctrl_emi %d, clkctrl_frac %d\n", + __func__, clkctrl_emi, clkctrl_frac); + + saved_ocram = kmalloc(stmp3xxx_ram_funcs_sz, GFP_KERNEL); + if (!saved_ocram) + return -ENOMEM; + memcpy(saved_ocram, scale, stmp3xxx_ram_funcs_sz); + memcpy(scale, stmp3xxx_ram_freq_scale, stmp3xxx_ram_funcs_sz); + + sc_data.emi_div = clkctrl_emi; + sc_data.frac_div = clkctrl_frac; + sc_data.cur_freq = clk->rate / 1000; + sc_data.new_freq = rate / 1000; + + local_irq_disable(); + local_fiq_disable(); + + scale(&sc_data); + + local_fiq_enable(); + local_irq_enable(); + + for (i = 10000; i; i--) + if (!clk_is_busy(clk)) + break; + memcpy(scale, saved_ocram, stmp3xxx_ram_funcs_sz); + kfree(saved_ocram); + + if (!i) { + printk(KERN_ERR "couldn't set up EMI divisor\n"); + ret = -ETIMEDOUT; + goto out; + } + } +out: + return ret; +} + static long emi_get_rate(struct clk *clk) { long rate = clk->parent->rate * 18; @@ -652,6 +729,8 @@ static struct clk_ops lcdif_ops = { static struct clk_ops emi_ops = { .get_rate = emi_get_rate, + .set_rate = emi_set_rate, + .set_parent = clkseq_set_parent, }; /* List of on-chip clocks */ @@ -860,6 +939,50 @@ static struct clk usb_clk = { .ops = &min_ops, }; +static struct clk vid_clk = { + .parent = &osc_24M, +#ifdef CONFIG_MACH_STMP378X + .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_FRAC1, + .enable_shift = 31, + .enable_negate = 1, +#endif + .flags = RATE_PROPAGATES, + .ops = &min_ops, +}; + +static struct clk clk_tv108M_ng = { + .parent = &vid_clk, +#ifdef CONFIG_MACH_STMP378X + .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_TV, + .enable_shift = 31, + .enable_negate = 1, +#endif + .flags = FIXED_RATE, + .ops = &min_ops, +}; + +static struct clk clk_tv54M = { + .parent = &vid_clk, +#ifdef CONFIG_MACH_STMP378X + .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_TV, + .enable_shift = 30, + .enable_negate = 1, +#endif + .flags = FIXED_RATE, + .ops = &min_ops, +}; + +static struct clk clk_tv27M = { + .parent = &vid_clk, +#ifdef CONFIG_MACH_STMP378X + .enable_reg = REGS_CLKCTRL_BASE + HW_CLKCTRL_TV, + .enable_shift = 30, + .enable_negate = 1, +#endif + .flags = FIXED_RATE, + .ops = &min_ops, +}; + /* list of all the clocks */ static struct clk_lookup onchip_clks[] = { { @@ -922,6 +1045,21 @@ static struct clk_lookup onchip_clks[] = { }, { .con_id = "usb", .clk = &usb_clk, + }, { + .con_id = "ref_vid", + .clk = &vid_clk, + }, { + .con_id = "tv108M_ng", + .clk = &clk_tv108M_ng, + }, { + .con_id = "tv54M", + .clk = &clk_tv54M, + }, { + .con_id = "tv27M", + .clk = &clk_tv27M, + }, { + .con_id = "saif", + .clk = &saif_clk, }, }; diff --git a/arch/arm/plat-stmp3xxx/clock.h b/arch/arm/plat-stmp3xxx/clock.h index a6611e1a3510..1008cb0c8a7a 100644 --- a/arch/arm/plat-stmp3xxx/clock.h +++ b/arch/arm/plat-stmp3xxx/clock.h @@ -48,6 +48,23 @@ struct clk { struct clk_ops *ops; }; +struct stmp3xxx_emi_scaling_data { + u32 emi_div; + u32 frac_div; + u32 cur_freq; + u32 new_freq; +}; + +#ifdef CONFIG_STMP378X_RAM_FREQ_SCALING +extern void stmp3xxx_ram_freq_scale(struct stmp3xxx_emi_scaling_data *); +extern u32 stmp3xxx_ram_funcs_sz; +#else +static inline void stmp3xxx_ram_freq_scale(struct stmp3xxx_emi_scaling_data *p) +{ +} +static u32 stmp3xxx_ram_funcs_sz; +#endif + #endif /* __ASSEMBLER__ */ /* Flags */ diff --git a/arch/arm/plat-stmp3xxx/core.c b/arch/arm/plat-stmp3xxx/core.c index 37b8a09148a4..6ada910d18c0 100644 --- a/arch/arm/plat-stmp3xxx/core.c +++ b/arch/arm/plat-stmp3xxx/core.c @@ -23,6 +23,8 @@ #include <mach/platform.h> #include <mach/dma.h> #include <mach/regs-clkctrl.h> +#include <mach/regs-rtc.h> +#include <mach/system.h> static int __stmp3xxx_reset_block(void __iomem *hwreg, int just_enable) { @@ -114,15 +116,25 @@ int stmp3xxx_reset_block(void __iomem *hwreg, int just_enable) } EXPORT_SYMBOL(stmp3xxx_reset_block); -struct platform_device stmp3xxx_dbguart = { - .name = "stmp3xxx-dbguart", - .id = -1, -}; +static void stmp3xxx_machine_restart(char mode, const char *cmd) +{ + arch_reset(mode, cmd); + printk(KERN_ERR"stmp3xxx_machine_restart failed -- System halted\n"); + for (;;) + continue; +} void __init stmp3xxx_init(void) { /* Turn off auto-slow and other tricks */ stmp3xxx_clearl(0x7f00000, REGS_CLKCTRL_BASE + HW_CLKCTRL_HBUS); + /* Re-route machine restart to our own handler */ + arm_pm_restart = stmp3xxx_machine_restart; + stmp3xxx_dma_init(); + + stmp3xxx_setl(BM_RTC_PERSISTENT0_XTAL32KHZ_PWRUP | + BM_RTC_PERSISTENT0_XTAL24MHZ_PWRUP, + REGS_RTC_BASE + HW_RTC_PERSISTENT0); } diff --git a/arch/arm/plat-stmp3xxx/cpufreq.c b/arch/arm/plat-stmp3xxx/cpufreq.c new file mode 100644 index 000000000000..aa83d33bc64c --- /dev/null +++ b/arch/arm/plat-stmp3xxx/cpufreq.c @@ -0,0 +1,475 @@ +/* + * CPU frequency scaling for Freescale STMP37XX/STMP378X + * + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* #define DEBUG */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/cpufreq.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +//#include <linux/regulator/regulator-drv.h> +#include <linux/notifier.h> + +#include <mach/hardware.h> +#include <linux/io.h> +#include <asm/system.h> +#include <mach/regulator.h> +#include <mach/power.h> +#include <mach/regs-digctl.h> +#include <mach/regs-clkctrl.h> +#include <mach/platform.h> + +#define VERY_HI_RATE 2000000000 +#define CLKCTRL_PLL_PWD_BIT 16 +#define CLKCTRL_PLL_BYPASS 0x1ff + +static struct profile { + int cpu; + int ahb; + int emi; + int ss; + int vddd; + int vddd_bo; + int cur; + int vddio; + int vdda; + int pll_off; +} profiles[] = { + { 24000, 24000, 24000, 3, 1000000, + 925000, 150000, 3075000, 1725000, 1 }, + { 64000, 64000, 48000, 3, 1000000, + 925000, 150000, 3300000, 1750000, 0 }, + { 261820, 130910, 130910, 0, 1225000, + 1125000, 173000, 3300000, 1750000, 0 }, + { 360000, 120000, 120000, 0, 1350000, + 1250000, 200000, 3300000, 1750000, 0 }, + { 392730, 130910, 130910, 0, 1400000, + 1300000, 225000, 3300000, 1750000, 0 }, + { 454740, 151580, 151580, 0, 1550000, + 1450000, 355000, 3300000, 1750000, 0 }, +}; + +static struct stmp3xxx_cpufreq { + struct cpufreq_policy policy; + struct regulator *regulator; + struct notifier_block nb; + struct notifier_block init_nb; + int freq_id; + int next_freq_id; + spinlock_t lock; +} cpufreq_bdata; + +static u32 clkseq_setting; + +static int reg_callback(struct notifier_block *, unsigned long, void *); +static int init_reg_callback(struct notifier_block *, unsigned long, void *); + +static inline void __set_new_policy(struct cpufreq_policy *policy) +{ + spin_lock(&cpufreq_bdata.lock); + + if (policy) + cpufreq_bdata.policy = *policy; + else + memset(&cpufreq_bdata.policy, 0, sizeof(cpufreq_bdata.policy)); + + if (cpufreq_bdata.regulator) + goto out; + + cpufreq_bdata.regulator = regulator_get(NULL, "cpufreq-1"); + if (!cpufreq_bdata.regulator || IS_ERR(cpufreq_bdata.regulator)) + cpufreq_bdata.regulator = NULL; + else { + regulator_set_mode(cpufreq_bdata.regulator, + REGULATOR_MODE_FAST); + if (cpufreq_bdata.regulator) + regulator_register_notifier( + cpufreq_bdata.regulator, + &cpufreq_bdata.nb); + } + +out: + spin_unlock(&cpufreq_bdata.lock); +} + +static int stmp3xxx_verify_speed(struct cpufreq_policy *policy) +{ + struct clk *cpu_clk; + + pr_debug("%s: entered, policy %p\n", __func__, policy); + + __set_new_policy(policy); + + if (policy->cpu) + return -EINVAL; + + cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq, + policy->cpuinfo.max_freq); + cpu_clk = clk_get(NULL, "cpu"); + if (IS_ERR(cpu_clk)) + return PTR_ERR(cpu_clk); + + pr_debug("%s: policy->min %d, policy->max %d\n", + __func__, policy->min, policy->max); + policy->min = clk_round_rate(cpu_clk, policy->min); + policy->max = clk_round_rate(cpu_clk, policy->max); + pr_debug("%s: after rounding rate: policy->min %d, policy->max %d\n", + __func__, policy->min, policy->max); + cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq, + policy->cpuinfo.max_freq); + clk_put(cpu_clk); + + return 0; +} + +static unsigned int stmp3xxx_getspeed(unsigned int cpu) +{ + struct clk *cpu_clk; + unsigned long rate; + + pr_debug("%s: entered\n", __func__); + if (cpu) + return 0; + + cpu_clk = clk_get(NULL, "cpu"); + if (IS_ERR(cpu_clk)) + return 0; + rate = clk_get_rate(cpu_clk); + pr_debug("%s: got cpu speed %ld\n", __func__, rate); + clk_put(cpu_clk); + + return rate; +} + +static int set_op(unsigned int target_freq) +{ + struct clk *cpu_clk, *ahb_clk, *emi_clk; + struct regulator *vddd, *vdddbo, *cur_limit, *vddio, *vdda; + struct cpufreq_freqs freqs; + int ret = 0, i; + + cur_limit = cpufreq_bdata.regulator; + pr_debug("%s: entered\n", __func__); + cpu_clk = clk_get(NULL, "cpu"); + if (IS_ERR(cpu_clk)) { + ret = PTR_ERR(cpu_clk); + goto out_cpu; + } + ahb_clk = clk_get(NULL, "hclk"); + if (IS_ERR(ahb_clk)) { + ret = PTR_ERR(ahb_clk); + goto out_ahb; + } + emi_clk = clk_get(NULL, "emi"); + if (IS_ERR(emi_clk)) { + ret = PTR_ERR(emi_clk); + goto out_emi; + } + + vddd = regulator_get(NULL, "vddd"); + vdddbo = regulator_get(NULL, "vddd_bo"); + if (IS_ERR(vdddbo)) + vdddbo = NULL; + vddio = regulator_get(NULL, "vddio"); + if (IS_ERR(vddio)) { + vddio = NULL; + pr_warning("unable to get vddio"); + } + vdda = regulator_get(NULL, "vdda"); + if (IS_ERR(vdda)) { + vdda = NULL; + pr_warning("unable to get vddio"); + } + + freqs.old = clk_get_rate(cpu_clk); + freqs.cpu = 0; + for (i = 0; i < ARRAY_SIZE(profiles) - 1; i++) { + if (profiles[i].cpu <= target_freq && + target_freq < profiles[i + 1].cpu) { + freqs.new = profiles[i].cpu; + cpufreq_bdata.next_freq_id = i; + break; + } + if (!vddd && profiles[i].cpu > freqs.old) { + /* can't safely set more than now */ + freqs.new = profiles[i - 1].cpu; + break; + } + } + + pr_debug("target_freq %d, new %d\n", target_freq, freqs.new); + if (i == ARRAY_SIZE(profiles) - 1) { + freqs.new = profiles[i].cpu; + cpufreq_bdata.next_freq_id = i; + } + + if (IS_ERR(vddd)) { + ret = PTR_ERR(vddd); + if (!cpufreq_bdata.init_nb.notifier_call) { + /* we only register once */ + cpufreq_bdata.init_nb.notifier_call = init_reg_callback; + bus_register_notifier(&platform_bus_type, + &cpufreq_bdata.init_nb); + } + goto out_vddd; + } + + pr_debug("i %d: freqs.old %d, freqs.new %d\n", + i, freqs.old, freqs.new); + + spin_lock(&cpufreq_bdata.lock); + + if (freqs.old == 24000 && freqs.new > 24000) { + /* turn pll on */ + stmp3xxx_setl(CLKCTRL_PLL_PWD_BIT, REGS_CLKCTRL_BASE + HW_CLKCTRL_PLLCTRL0); + udelay(10); + } else if (freqs.old > 24000 && freqs.new == 24000) + clkseq_setting = __raw_readl(REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ); + + if (cur_limit && (freqs.old < freqs.new)) { + ret = regulator_set_current_limit(cur_limit, profiles[i].cur, profiles[i].cur); + if (ret) + goto out_cur; + } + + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + if (freqs.old > freqs.new) { + int ss = profiles[i].ss; + clk_set_rate(cpu_clk, profiles[i].cpu); + clk_set_rate(ahb_clk, profiles[i].ahb); + clk_set_rate(emi_clk, profiles[i].emi); + __raw_writel(BF(ss, DIGCTL_ARMCACHE_VALID_SS) | + BF(ss, DIGCTL_ARMCACHE_DRTY_SS) | + BF(ss, DIGCTL_ARMCACHE_CACHE_SS) | + BF(ss, DIGCTL_ARMCACHE_DTAG_SS) | + BF(ss, DIGCTL_ARMCACHE_ITAG_SS), REGS_DIGCTL_BASE + HW_DIGCTL_ARMCACHE); + if (vddd && vdddbo && vddio && vdda) { + ret = regulator_set_voltage(vddd, profiles[i].vddd, profiles[i].vddd); + if (ret) + ret = regulator_set_voltage(vddd, + profiles[i].vddd, + profiles[i].vddd); + regulator_set_voltage(vdddbo, profiles[i].vddd_bo, profiles[i].vddd_bo); + + ret = regulator_set_voltage(vddio, profiles[i].vddio, + profiles[i].vddio); + if (ret) + ret = regulator_set_voltage(vddio, + profiles[i].vddio, + profiles[i].vddio); + ret = regulator_set_voltage(vdda, profiles[i].vdda, + profiles[i].vdda); + if (ret) + ret = regulator_set_voltage(vdda, + profiles[i].vdda, + profiles[i].vdda); + } + } else { + int ss = profiles[i].ss; + if (vddd && vdddbo && vddio && vdda) { + ret = regulator_set_voltage(vddd, profiles[i].vddd, profiles[i].vddd); + if (ret) + ret = regulator_set_voltage(vddd, + profiles[i].vddd, + profiles[i].vddd); + regulator_set_voltage(vdddbo, profiles[i].vddd_bo, profiles[i].vddd_bo); + ret = regulator_set_voltage(vddio, profiles[i].vddio, + profiles[i].vddio); + if (ret) + ret = regulator_set_voltage(vddio, + profiles[i].vddio, + profiles[i].vddio); + ret = regulator_set_voltage(vdda, profiles[i].vdda, + profiles[i].vdda); + if (ret) + ret = regulator_set_voltage(vdda, + profiles[i].vdda, + profiles[i].vdda); + } + __raw_writel(BF(ss, DIGCTL_ARMCACHE_VALID_SS) | + BF(ss, DIGCTL_ARMCACHE_DRTY_SS) | + BF(ss, DIGCTL_ARMCACHE_CACHE_SS) | + BF(ss, DIGCTL_ARMCACHE_DTAG_SS) | + BF(ss, DIGCTL_ARMCACHE_ITAG_SS), REGS_DIGCTL_BASE + HW_DIGCTL_ARMCACHE); + clk_set_rate(cpu_clk, profiles[i].cpu); + clk_set_rate(ahb_clk, profiles[i].ahb); + clk_set_rate(emi_clk, profiles[i].emi); + } + udelay(100); + + if (freqs.old > 24000 && freqs.new == 24000) { + /* turn pll off */ + stmp3xxx_clearl(CLKCTRL_PLL_PWD_BIT, REGS_CLKCTRL_BASE + HW_CLKCTRL_PLLCTRL0); + __raw_writel(CLKCTRL_PLL_BYPASS, REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ); + } else if (freqs.old == 24000 && freqs.new > 24000) + __raw_writel(clkseq_setting, REGS_CLKCTRL_BASE + HW_CLKCTRL_CLKSEQ); + + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + if (cur_limit && (freqs.old > freqs.new)) /* will not fail */ + regulator_set_current_limit(cur_limit, profiles[i].cur, profiles[i].cur); + + cpufreq_bdata.freq_id = i; + +out_cur: + spin_unlock(&cpufreq_bdata.lock); + if (vddd) + regulator_put(vddd); + if (vddio) + regulator_put(vddio); + if (vdda) + regulator_put(vdda); +out_vddd: + clk_put(emi_clk); +out_emi: + clk_put(ahb_clk); +out_ahb: + clk_put(cpu_clk); +out_cpu: + + return ret; +} + +static int stmp3xxx_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + return set_op(target_freq); +} + +static int reg_callback(struct notifier_block *self, unsigned long event, + void *data) +{ + struct stmp3xxx_cpufreq *_temp = + container_of(self, struct stmp3xxx_cpufreq, nb); + struct cpufreq_policy *policy = &_temp->policy; + int max_prof = ARRAY_SIZE(profiles) - 1; + int ret = -EINVAL; + + pr_debug("%s: entered, _temp %p, policy %p, cpu %d, freq_id %d\n", + __func__, _temp, policy, policy->cpu, _temp->freq_id); + + if (policy) + policy = cpufreq_cpu_get(policy->cpu); + if (!policy) { + printk(KERN_ERR "%s: couldn't get cpufreq policy\n", __func__); + goto out; + } + + /* FIXME: Need a lock: set policy by user VS async USB event */ + switch (event) { + case STMP3XXX_REG5V_IS_USB: + pr_debug("%s: limiting max_freq to %d\n", __func__, + profiles[max_prof - 1].cpu); + policy->user_policy.min = profiles[0].cpu; + policy->user_policy.max = profiles[max_prof - 1].cpu; + if (_temp->freq_id > max_prof - 1) + set_op(profiles[max_prof - 1].cpu); + break; + + case STMP3XXX_REG5V_NOT_USB: + pr_debug("%s: undo limiting max_freq to %d\n", __func__, + profiles[max_prof - 1].cpu); + policy->user_policy.min = profiles[0].cpu; + policy->user_policy.max = profiles[max_prof].cpu; + break; + + default: + pr_info("%s: unknown event %ld\n", __func__, event); + break; + } + cpufreq_cpu_put(policy); + ret = cpufreq_update_policy(policy->cpu); +out: + return ret; +} + +static int init_reg_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + int ret; + struct stmp3xxx_cpufreq *_temp = + container_of(self, struct stmp3xxx_cpufreq, init_nb); + + ret = set_op(profiles[_temp->next_freq_id].cpu); + if (ret == 0) + bus_unregister_notifier(&platform_bus_type, + &cpufreq_bdata.init_nb); + return ret; +} + +static int __init stmp3xxx_cpu_init(struct cpufreq_policy *policy) +{ + struct clk *cpu_clk = clk_get(NULL, "cpu"); + + pr_debug("%s: entered\n", __func__); + if (IS_ERR(cpu_clk)) + return PTR_ERR(cpu_clk); + + if (policy->cpu != 0) + return -EINVAL; + + policy->cur = policy->min = policy->max = clk_get_rate(cpu_clk); + + pr_debug("got CPU clock rate %d\n", policy->cur); + policy->governor = CPUFREQ_DEFAULT_GOVERNOR; + policy->cpuinfo.min_freq = profiles[0].cpu; + policy->cpuinfo.max_freq = profiles[ARRAY_SIZE(profiles) - 1].cpu; + policy->cpuinfo.transition_latency = 1000000; /* 1 ms, assumed */ + clk_put(cpu_clk); + + return 0; +} + +static struct cpufreq_driver stmp3xxx_driver = { + .flags = CPUFREQ_STICKY, + .verify = stmp3xxx_verify_speed, + .target = stmp3xxx_target, + .get = stmp3xxx_getspeed, + .init = stmp3xxx_cpu_init, + .name = "stmp3xxx", +}; + +static int __init stmp3xxx_cpufreq_init(void) +{ + spin_lock_init(&cpufreq_bdata.lock); + cpufreq_bdata.nb.notifier_call = reg_callback; + return cpufreq_register_driver(&stmp3xxx_driver); +} + +static int __init stmp3xxx_reg_init(void) +{ + pr_debug("%s: enter\n", __func__); + if (!cpufreq_bdata.regulator) + __set_new_policy(&cpufreq_bdata.policy); + + if (cpufreq_bdata.regulator) + regulator_set_current_limit(cpufreq_bdata.regulator, + profiles[cpufreq_bdata.freq_id].cur, + profiles[cpufreq_bdata.freq_id].cur); + return 0 ; +} + +arch_initcall(stmp3xxx_cpufreq_init); +late_initcall(stmp3xxx_reg_init); diff --git a/arch/arm/plat-stmp3xxx/dcp-bootstream.c b/arch/arm/plat-stmp3xxx/dcp-bootstream.c new file mode 100644 index 000000000000..133971d77a42 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/dcp-bootstream.c @@ -0,0 +1,303 @@ +/* + * Freescale STMP378X DCP driver for bootstream update. Only handles the OTP KEY + * case and can only encrypt/decrypt. + * + * Author: Pantelis Antoniou <pantelis@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/sysdev.h> +#include <linux/bitops.h> +#include <linux/platform_device.h> +#include <linux/sysfs.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/uaccess.h> + +#include <mach/hardware.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <mach/stmp3xxx.h> +#include <mach/platform.h> +#include <mach/regs-dcp.h> +#include <mach/dcp_bootstream_ioctl.h> + +/* use this channel (same as the ROM) */ +#define ROM_DCP_CHAN 3 + +/* Defines the channel mask for the rom dcp channel */ +#define ROM_DCP_CHAN_MASK (1 << ROM_DCP_CHAN) + +/* Defines the initialization value for the dcp control register */ +#define DCP_CTRL_INIT \ + (BM_DCP_CTRL_GATHER_RESIDUAL_WRITES | \ + BM_DCP_CTRL_ENABLE_CONTEXT_CACHING) + +/* Defines the initialization value for the dcp channel control register */ +#define DCP_CHANNELCTRL_INIT \ + BF(ROM_DCP_CHAN_MASK, DCP_CHANNELCTRL_ENABLE_CHANNEL) + +/* DCP work packet 1 value used to calculate CBC-MAC over the image header */ +#define DCP_PKT1_ENCRYPT \ + (BM_DCP_PACKET1_DECR_SEMAPHORE | \ + BM_DCP_PACKET1_ENABLE_CIPHER | \ + BM_DCP_PACKET1_CIPHER_ENCRYPT | \ + BM_DCP_PACKET1_CIPHER_INIT | \ + BM_DCP_PACKET1_OTP_KEY) + +/* DCP work packet 1 value used to decrypt DEK in key dictionary */ +#define DCP_PKT1_DECRYPT \ + (BM_DCP_PACKET1_DECR_SEMAPHORE | \ + BM_DCP_PACKET1_ENABLE_CIPHER | \ + BM_DCP_PACKET1_CIPHER_INIT | \ + BM_DCP_PACKET1_OTP_KEY) + +/* DCP (decryption) work packet definition */ +struct hw_dcp_packet { + uint32_t pNext; /* next dcp work packet address */ + uint32_t pkt1; /* dcp work packet 1 (control 0) */ + uint32_t pkt2; /* dcp work packet 2 (control 1) */ + uint32_t pSrc; /* source buffer address */ + uint32_t pDst; /* destination buffer address */ + uint32_t size; /* buffer size in bytes */ + uint32_t pPayload; /* payload buffer address */ + uint32_t stat; /* dcp status (written by dcp) */ +}; + +struct dma_area { + struct hw_dcp_packet hw_packet; + uint16_t block[16]; +}; + +struct stmp3xxx_dcp_bootstream_data { + struct device *dev; + struct dma_area *dma_area; + dma_addr_t dma_area_phys; +}; + +/* Only one instance allowed, so this is OK */ +static struct stmp3xxx_dcp_bootstream_data *global_dbd; + +static int stmp3xxx_dcp_bootstream_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct stmp3xxx_dcp_bootstream_data *dbd = global_dbd; + struct dma_area *da = dbd->dma_area; + void __user *argp = (void __user *)arg; + unsigned long timeout; + + /* be paranoid */ + if (dbd == NULL) + return -EBADF; + + if (cmd != DBS_ENC && cmd != DBS_DEC) + return -EINVAL; + + /* copy to (aligned) block */ + if (copy_from_user(da->block, argp, 16)) + return -EFAULT; + + /* Soft reset and remove the clock gate */ + stmp3xxx_setl(BM_DCP_CTRL_SFTRST, REGS_DCP_BASE + HW_DCP_CTRL); + + /* At 24Mhz, it takes no more than 4 clocks (160 ns) Maximum for + * the part to reset, reading the register twice should + * be sufficient to get 4 clks delay. + */ + __raw_readl(REGS_DCP_BASE + HW_DCP_CTRL); + __raw_readl(REGS_DCP_BASE + HW_DCP_CTRL); + + stmp3xxx_clearl(BM_DCP_CTRL_SFTRST | BM_DCP_CTRL_CLKGATE, + REGS_DCP_BASE + HW_DCP_CTRL); + + /* Initialize control registers */ + __raw_writel(DCP_CTRL_INIT, REGS_DCP_BASE + HW_DCP_CTRL); + __raw_writel(DCP_CHANNELCTRL_INIT, REGS_DCP_BASE + HW_DCP_CHANNELCTRL); + + /* The loader does not enable context switching. Give the context + * buffer pointer an illegal address so if context switching is + * inadvertantly enabled, the dcp will return an error instead of + * trashing good memory. The dcp dma cannot access rom, so any rom + * address will do. + */ + __raw_writel(0xFFFF0000, REGS_DCP_BASE + HW_DCP_CONTEXT); + + stmp3xxx_clearl(-1, REGS_DCP_BASE + HW_DCP_CHnSTAT(ROM_DCP_CHAN)); + stmp3xxx_clearl(-1, REGS_DCP_BASE + HW_DCP_STAT); + + da->hw_packet.pNext = 0; + da->hw_packet.pkt1 = BM_DCP_PACKET1_DECR_SEMAPHORE | + BM_DCP_PACKET1_ENABLE_CIPHER | BM_DCP_PACKET1_OTP_KEY | + BM_DCP_PACKET1_INTERRUPT | + (cmd == DBS_ENC ? BM_DCP_PACKET1_CIPHER_ENCRYPT : 0); + da->hw_packet.pkt2 = BF(0, DCP_PACKET2_CIPHER_CFG) | + BF(0, DCP_PACKET2_KEY_SELECT) | + BF(BV_DCP_PACKET2_CIPHER_MODE__ECB, DCP_PACKET2_CIPHER_MODE) | + BF(BV_DCP_PACKET2_CIPHER_SELECT__AES128, DCP_PACKET2_CIPHER_SELECT); + da->hw_packet.pSrc = dbd->dma_area_phys + + offsetof(struct dma_area, block); + da->hw_packet.pDst = da->hw_packet.pSrc; /* in-place */ + da->hw_packet.size = 16; + da->hw_packet.pPayload = 0; + da->hw_packet.stat = 0; + + /* Load the work packet pointer and bump the channel semaphore */ + __raw_writel(dbd->dma_area_phys + + offsetof(struct dma_area, hw_packet), + REGS_DCP_BASE + HW_DCP_CHnCMDPTR(ROM_DCP_CHAN)); + __raw_writel(BF(1, DCP_CHnSEMA_INCREMENT), + REGS_DCP_BASE + HW_DCP_CHnSEMA(ROM_DCP_CHAN)); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout) && + (__raw_readl(REGS_DCP_BASE + HW_DCP_STAT) & + BF(ROM_DCP_CHAN_MASK, DCP_STAT_IRQ)) == 0) + cpu_relax(); + + if (!time_before(jiffies, timeout)) { + dev_err(dbd->dev, "Timeout while waiting STAT\n"); + return -ETIMEDOUT; + } + + if ((__raw_readl(HW_DCP_CHnSTAT(ROM_DCP_CHAN)) & 0xff) != 0) { + dev_err(dbd->dev, "Channel stat error 0x%02x\n", + __raw_readl(REGS_DCP_BASE + + HW_DCP_CHnSTAT(ROM_DCP_CHAN)) & 0xff); + return -EFAULT; + } + + if (copy_to_user(argp, da->block, 16)) + return -EFAULT; + + return 0; +} + +static struct file_operations stmp3xxx_dcp_bootstream_fops = { + .owner = THIS_MODULE, + .ioctl = stmp3xxx_dcp_bootstream_ioctl, +}; + +static struct miscdevice stmp3xxx_dcp_bootstream_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "dcpboot", + .fops = &stmp3xxx_dcp_bootstream_fops, +}; + +static int __devinit stmp3xxx_dcp_bootstream_probe(struct platform_device *pdev) +{ + struct stmp3xxx_dcp_bootstream_data *dbd; + int err; + + /* we only allow a single device */ + if (global_dbd != NULL) + return -ENODEV; + + dbd = kzalloc(sizeof(*dbd), GFP_KERNEL); + if (dbd == NULL) + return -ENOMEM; + memset(dbd, 0, sizeof(*dbd)); + + dbd->dev = &pdev->dev; + platform_set_drvdata(pdev, dbd); + + err = misc_register(&stmp3xxx_dcp_bootstream_misc); + if (err != 0) { + dev_err(&pdev->dev, "Unable to register misc device\n"); + goto err_done; + } + + dbd->dma_area = dma_alloc_coherent(&pdev->dev, sizeof(*dbd->dma_area), + &dbd->dma_area_phys, GFP_KERNEL); + if (dbd->dma_area == NULL) { + dev_err(&pdev->dev, "Unable to allocate DMAable memory\n"); + goto err_dereg; + } + + global_dbd = dbd; + + return 0; + +err_dereg: + misc_deregister(&stmp3xxx_dcp_bootstream_misc); +err_done: + kfree(dbd); + return err; +} + +static int stmp3xxx_dcp_bootstream_remove(struct platform_device *pdev) +{ + struct stmp3xxx_dcp_bootstream_data *dbd; + + dbd = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); + + dma_free_coherent(&pdev->dev, sizeof(*dbd->dma_area), + dbd->dma_area, dbd->dma_area_phys); + misc_deregister(&stmp3xxx_dcp_bootstream_misc); + + kfree(dbd); + + global_dbd = NULL; + + return 0; +} + +#ifdef CONFIG_PM +static int stmp3xxx_dcp_bootstream_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return 0; +} + +static int stmp3xxx_dcp_bootstream_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define stmp3xxx_dcp_bootstream_suspend NULL +#define stmp3xxx_dcp_bootstream_resume NULL +#endif + +static struct platform_driver stmp3xxx_dcp_bootstream_driver = { + .probe = stmp3xxx_dcp_bootstream_probe, + .remove = stmp3xxx_dcp_bootstream_remove, + .suspend = stmp3xxx_dcp_bootstream_suspend, + .resume = stmp3xxx_dcp_bootstream_resume, + .driver = { + .name = "stmp3xxx-dcpboot", + .owner = THIS_MODULE, + }, +}; + +static int __init stmp3xxx_dcp_bootstream_init(void) +{ + return platform_driver_register(&stmp3xxx_dcp_bootstream_driver); +} + +static void __exit stmp3xxx_dcp_bootstream_exit(void) +{ + platform_driver_unregister(&stmp3xxx_dcp_bootstream_driver); +} + +MODULE_AUTHOR("Pantelis Antoniou <pantelis@embeddedalley.com>"); +MODULE_DESCRIPTION("DCP bootstream driver"); +MODULE_LICENSE("GPL"); + +module_init(stmp3xxx_dcp_bootstream_init); +module_exit(stmp3xxx_dcp_bootstream_exit); diff --git a/arch/arm/plat-stmp3xxx/devices.c b/arch/arm/plat-stmp3xxx/devices.c index 68fed4b8746a..a7b9ddb034bf 100644 --- a/arch/arm/plat-stmp3xxx/devices.c +++ b/arch/arm/plat-stmp3xxx/devices.c @@ -20,16 +20,26 @@ #include <linux/device.h> #include <linux/platform_device.h> #include <linux/dma-mapping.h> +#include <linux/fsl_devices.h> +#include <linux/list.h> +#include <linux/delay.h> #include <mach/dma.h> #include <mach/platform.h> #include <mach/stmp3xxx.h> +#include <mach/lcdif.h> #include <mach/regs-lcdif.h> #include <mach/regs-uartapp.h> #include <mach/regs-gpmi.h> #include <mach/regs-usbctrl.h> +#include <mach/regs-usbphy.h> #include <mach/regs-ssp.h> #include <mach/regs-rtc.h> +#include <mach/regs-digctl.h> +#include <mach/regs-ocotp.h> +#include <mach/lcdif.h> +#include <mach/regs-power.h> +#include <mach/regs-clkctrl.h> static u64 common_dmamask = DMA_BIT_MASK(32); @@ -63,6 +73,7 @@ static struct resource appuart_resources[] = { }, }; + struct platform_device stmp3xxx_appuart = { .name = "stmp3xxx-appuart", .id = 0, @@ -74,6 +85,11 @@ struct platform_device stmp3xxx_appuart = { }, }; +struct platform_device stmp3xxx_dbguart = { + .name = "stmp3xxx-dbguart", + .id = -1, +}; + struct platform_device stmp3xxx_watchdog = { .name = "stmp3xxx_wdt", .id = -1, @@ -98,12 +114,22 @@ struct platform_device stmp3xxx_touchscreen = { .num_resources = ARRAY_SIZE(ts_resource), }; +static struct resource keyboard_resource[] = { + { + .flags = IORESOURCE_IRQ, + .start = IRQ_LRADC_CH0, + .end = IRQ_LRADC_CH0, + }, +}; + /* * Keypad device */ struct platform_device stmp3xxx_keyboard = { .name = "stmp3xxx-keyboard", .id = -1, + .resource = keyboard_resource, + .num_resources = ARRAY_SIZE(keyboard_resource), }; static struct resource gpmi_resources[] = { @@ -155,7 +181,7 @@ static struct resource mmc1_resource[] = { struct platform_device stmp3xxx_mmc = { .name = "stmp3xxx-mmc", - .id = 1, + .id = -1, .dev = { .dma_mask = &common_dmamask, .coherent_dma_mask = DMA_BIT_MASK(32), @@ -176,23 +202,48 @@ static struct resource usb_resources[] = { }, }; +static struct fsl_usb2_platform_data udc_platform_data = { + .operating_mode = FSL_USB2_DR_DEVICE, + .phy_mode = FSL_USB2_PHY_UTMI, + .port_enables = FSL_USB2_DONT_REMAP, + .platform_init = NULL, + .platform_uninit = NULL, +}; + struct platform_device stmp3xxx_udc = { .name = "fsl-usb2-udc", .id = -1, .dev = { .dma_mask = &common_dmamask, .coherent_dma_mask = DMA_BIT_MASK(32), + .platform_data = &udc_platform_data, }, .resource = usb_resources, .num_resources = ARRAY_SIZE(usb_resources), }; +static void usb_host_phy_resume(struct fsl_usb2_platform_data *pdata) +{ + stmp3xxx_clearl(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, + REGS_USBPHY_BASE + HW_USBPHY_CTRL); +} + +static struct fsl_usb2_platform_data ehci_platform_data = { + .operating_mode = FSL_USB2_DR_HOST, + .phy_mode = FSL_USB2_PHY_UTMI, + .port_enables = FSL_USB2_DONT_REMAP, + .platform_init = NULL, + .platform_uninit = NULL, + .platform_resume = usb_host_phy_resume, +}; + struct platform_device stmp3xxx_ehci = { .name = "fsl-ehci", .id = -1, .dev = { .dma_mask = &common_dmamask, .coherent_dma_mask = DMA_BIT_MASK(32), + .platform_data = &ehci_platform_data, }, .resource = usb_resources, .num_resources = ARRAY_SIZE(usb_resources), @@ -291,12 +342,17 @@ static struct resource fb_resource[] = { }, }; +static struct stmp3xxx_platform_fb_data stmp3xxx_framebuffer_pdata = { + .list = LIST_HEAD_INIT(stmp3xxx_framebuffer_pdata.list), +}; + struct platform_device stmp3xxx_framebuffer = { .name = "stmp3xxx-fb", .id = -1, .dev = { .dma_mask = &common_dmamask, .coherent_dma_mask = DMA_BIT_MASK(32), + .platform_data = &stmp3xxx_framebuffer_pdata, }, .num_resources = ARRAY_SIZE(fb_resource), .resource = fb_resource, @@ -337,9 +393,85 @@ struct platform_device stmp3xxx_rotdec = { .id = -1, }; +static const struct stmp3xxx_persistent_bit_config +stmp3xxx_persistent_bit_tab[] = { + { .reg = 0, .start = 0, .width = 1, + .name = "CLOCKSOURCE" }, + { .reg = 0, .start = 1, .width = 1, + .name = "ALARM_WAKE_EN" }, + { .reg = 0, .start = 2, .width = 1, + .name = "ALARM_EN" }, + { .reg = 0, .start = 3, .width = 1, + .name = "CLK_SECS" }, + { .reg = 0, .start = 4, .width = 1, + .name = "XTAL24MHZ_PWRUP" }, + { .reg = 0, .start = 5, .width = 1, + .name = "XTAL32MHZ_PWRUP" }, + { .reg = 0, .start = 6, .width = 1, + .name = "XTAL32_FREQ" }, + { .reg = 0, .start = 7, .width = 1, + .name = "ALARM_WAKE" }, + { .reg = 0, .start = 8, .width = 5, + .name = "MSEC_RES" }, + { .reg = 0, .start = 13, .width = 1, + .name = "DISABLE_XTALOK" }, + { .reg = 0, .start = 14, .width = 2, + .name = "LOWERBIAS" }, + { .reg = 0, .start = 16, .width = 1, + .name = "DISABLE_PSWITCH" }, + { .reg = 0, .start = 17, .width = 1, + .name = "AUTO_RESTART" }, + { .reg = 0, .start = 18, .width = 14, + .name = "SPARE_ANALOG" }, + + { .reg = 1, .start = 0, .width = 1, + .name = "FORCE_RECOVERY" }, + { .reg = 1, .start = 1, .width = 1, + .name = "NAND_SECONDARY_BOOT" }, + { .reg = 1, .start = 2, .width = 1, + .name = "NAND_SDK_BLOCK_REWRITE" }, + { .reg = 1, .start = 3, .width = 1, + .name = "SD_SPEED_ENABLE" }, + { .reg = 1, .start = 4, .width = 1, + .name = "SD_INIT_SEQ_1_DISABLE" }, + { .reg = 1, .start = 5, .width = 1, + .name = "SD_CMD0_DISABLE" }, + { .reg = 1, .start = 6, .width = 1, + .name = "SD_INIT_SEQ_2_ENABLE" }, + { .reg = 1, .start = 7, .width = 1, + .name = "OTG_ATL_ROLE_BIT" }, + { .reg = 1, .start = 8, .width = 1, + .name = "OTG_HNP_BIT" }, + { .reg = 1, .start = 9, .width = 1, + .name = "USB_LOW_POWER_MODE" }, + { .reg = 1, .start = 10, .width = 1, + .name = "SKIP_CHECKDISK" }, + { .reg = 1, .start = 11, .width = 1, + .name = "USB_BOOT_PLAYER_MODE" }, + { .reg = 1, .start = 12, .width = 1, + .name = "ENUMERATE_500MA_TWICE" }, + { .reg = 1, .start = 13, .width = 19, + .name = "SPARE_GENERAL" }, + + { .reg = 2, .start = 0, .width = 32, + .name = "SPARE_2" }, + { .reg = 3, .start = 0, .width = 32, + .name = "SPARE_3" }, + { .reg = 4, .start = 0, .width = 32, + .name = "SPARE_4" }, + { .reg = 5, .start = 0, .width = 32, + .name = "SPARE_5" }, +}; + +static struct stmp3xxx_platform_persistent_data stmp3xxx_persistent_data = { + .bit_config_tab = stmp3xxx_persistent_bit_tab, + .bit_config_cnt = ARRAY_SIZE(stmp3xxx_persistent_bit_tab), +}; + struct platform_device stmp3xxx_persistent = { .name = "stmp3xxx-persistent", .id = -1, + .dev.platform_data = &stmp3xxx_persistent_data, }; struct platform_device stmp3xxx_dcp_bootstream = { @@ -387,3 +519,28 @@ struct platform_device stmp3xxx_battery = { .resource = battery_resource, .num_resources = ARRAY_SIZE(battery_resource), }; + +struct resource viim_resources[] = { + [0] = { + .start = REGS_DIGCTL_PHYS, + .end = REGS_DIGCTL_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = REGS_OCOTP_PHYS, + .end = REGS_OCOTP_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device stmp3xxx_viim = { + .name = "mxs_viim", + .id = 0, + .resource = viim_resources, + .num_resources = ARRAY_SIZE(viim_resources), +}; + +struct platform_device stmp3xxx_spdif = { + .name = "stmp3xxx-spdif", + .id = -1, +}; diff --git a/arch/arm/plat-stmp3xxx/dma.c b/arch/arm/plat-stmp3xxx/dma.c index d2f497764dce..72a5745c94fb 100644 --- a/arch/arm/plat-stmp3xxx/dma.c +++ b/arch/arm/plat-stmp3xxx/dma.c @@ -94,15 +94,15 @@ int stmp3xxx_dma_read_semaphore(int channel) switch (STMP3XXX_DMA_BUS(channel)) { case STMP3XXX_BUS_APBH: - sem = __raw_readl(REGS_APBH_BASE + HW_APBH_CHn_SEMA + - STMP3XXX_DMA_CHANNEL(channel) * 0x70); + sem = __raw_readl(REGS_APBH_BASE + + HW_APBH_CHn_SEMA(STMP3XXX_DMA_CHANNEL(channel))); sem &= BM_APBH_CHn_SEMA_PHORE; sem >>= BP_APBH_CHn_SEMA_PHORE; break; case STMP3XXX_BUS_APBX: - sem = __raw_readl(REGS_APBX_BASE + HW_APBX_CHn_SEMA + - STMP3XXX_DMA_CHANNEL(channel) * 0x70); + sem = __raw_readl(REGS_APBX_BASE + + HW_APBX_CHn_SEMA(STMP3XXX_DMA_CHANNEL(channel))); sem &= BM_APBX_CHn_SEMA_PHORE; sem >>= BP_APBX_CHn_SEMA_PHORE; break; @@ -187,13 +187,13 @@ void stmp3xxx_dma_go(int channel, switch (STMP3XXX_DMA_BUS(channel)) { case STMP3XXX_BUS_APBH: - c = REGS_APBH_BASE + HW_APBH_CHn_NXTCMDAR + 0x70 * ch; - s = REGS_APBH_BASE + HW_APBH_CHn_SEMA + 0x70 * ch; + c = REGS_APBH_BASE + HW_APBH_CHn_NXTCMDAR(ch); + s = REGS_APBH_BASE + HW_APBH_CHn_SEMA(ch); break; case STMP3XXX_BUS_APBX: - c = REGS_APBX_BASE + HW_APBX_CHn_NXTCMDAR + 0x70 * ch; - s = REGS_APBX_BASE + HW_APBX_CHn_SEMA + 0x70 * ch; + c = REGS_APBX_BASE + HW_APBX_CHn_NXTCMDAR(ch); + s = REGS_APBX_BASE + HW_APBX_CHn_SEMA(ch); break; default: @@ -212,13 +212,13 @@ int stmp3xxx_dma_running(int channel) { switch (STMP3XXX_DMA_BUS(channel)) { case STMP3XXX_BUS_APBH: - return (__raw_readl(REGS_APBH_BASE + HW_APBH_CHn_SEMA + - 0x70 * STMP3XXX_DMA_CHANNEL(channel))) & + return (__raw_readl(REGS_APBH_BASE + + HW_APBH_CHn_SEMA(STMP3XXX_DMA_CHANNEL(channel)))) & BM_APBH_CHn_SEMA_PHORE; case STMP3XXX_BUS_APBX: - return (__raw_readl(REGS_APBX_BASE + HW_APBX_CHn_SEMA + - 0x70 * STMP3XXX_DMA_CHANNEL(channel))) & + return (__raw_readl(REGS_APBX_BASE + + HW_APBX_CHn_SEMA(STMP3XXX_DMA_CHANNEL(channel)))) & BM_APBX_CHn_SEMA_PHORE; default: BUG(); @@ -323,7 +323,7 @@ void stmp37xx_circ_advance_active(struct stmp37xx_circ_dma_chain *chain, unsigned count) { void __iomem *c; - u32 mask_clr, mask; + u32 mask_clr, mask, reg; BUG_ON(chain->free_count < count); chain->free_count -= count; @@ -333,12 +333,12 @@ void stmp37xx_circ_advance_active(struct stmp37xx_circ_dma_chain *chain, switch (chain->bus) { case STMP3XXX_BUS_APBH: - c = REGS_APBH_BASE + HW_APBH_CHn_SEMA + 0x70 * chain->channel; + c = REGS_APBH_BASE + HW_APBH_CHn_SEMA(chain->channel); mask_clr = BM_APBH_CHn_SEMA_INCREMENT_SEMA; mask = BF(count, APBH_CHn_SEMA_INCREMENT_SEMA); break; case STMP3XXX_BUS_APBX: - c = REGS_APBX_BASE + HW_APBX_CHn_SEMA + 0x70 * chain->channel; + c = REGS_APBX_BASE + HW_APBX_CHn_SEMA(chain->channel); mask_clr = BM_APBX_CHn_SEMA_INCREMENT_SEMA; mask = BF(count, APBX_CHn_SEMA_INCREMENT_SEMA); break; @@ -349,8 +349,11 @@ void stmp37xx_circ_advance_active(struct stmp37xx_circ_dma_chain *chain, /* Set counting semaphore (kicks off transfer). Assumes peripheral has been set up correctly */ - stmp3xxx_clearl(mask_clr, c); - stmp3xxx_setl(mask, c); + reg = __raw_readl(c); + reg &= ~mask_clr; + __raw_writel(reg, c); + reg |= mask; + __raw_writel(reg, c); } EXPORT_SYMBOL(stmp37xx_circ_advance_active); @@ -373,6 +376,7 @@ EXPORT_SYMBOL(stmp37xx_circ_advance_cooked); void stmp3xxx_dma_set_alt_target(int channel, int function) { + u32 reg; #if defined(CONFIG_ARCH_STMP37XX) unsigned bits = 4; #elif defined(CONFIG_ARCH_STMP378X) @@ -398,8 +402,11 @@ void stmp3xxx_dma_set_alt_target(int channel, int function) default: BUG(); } - stmp3xxx_clearl(mask << shift, c); - stmp3xxx_setl(mask << shift, c); + reg = __raw_readl(c); + reg &= ~(mask << shift); + __raw_writel(reg, c); + reg |= mask; + __raw_writel(reg, c); } EXPORT_SYMBOL(stmp3xxx_dma_set_alt_target); diff --git a/arch/arm/plat-stmp3xxx/gpmi.c b/arch/arm/plat-stmp3xxx/gpmi.c new file mode 100644 index 000000000000..f62c70aa13a8 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/gpmi.c @@ -0,0 +1,40 @@ +/* + * Freescale STMP37XX/STMP378X GPMI module pin multiplexing + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/module.h> +#include <mach/platform.h> + +int gpmi_pinmux_request(char *title) +{ + int err = 0; + + err = stmp3xxx_request_pin_group(&gpmi_pins, title); + + return err; +} +EXPORT_SYMBOL_GPL(gpmi_pinmux_request); + +void gpmi_pinmux_free(char *title) +{ + stmp3xxx_release_pin_group(&gpmi_pins, title); +} +EXPORT_SYMBOL_GPL(gpmi_pinmux_free); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>"); diff --git a/arch/arm/plat-stmp3xxx/include/mach/arc_otg.h b/arch/arm/plat-stmp3xxx/include/mach/arc_otg.h new file mode 100644 index 000000000000..e0213b21f4ff --- /dev/null +++ b/arch/arm/plat-stmp3xxx/include/mach/arc_otg.h @@ -0,0 +1,97 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_ARCH_MXC_ARC_OTG_H__ +#define __ASM_ARCH_MXC_ARC_OTG_H__ + +#include <mach/hardware.h> + +/* + * register bits + */ + +/* x_PORTSCx */ +#define PORTSC_PTS_MASK (3 << 30)/* parallel xcvr select mask */ +#define PORTSC_PTS_UTMI (0 << 30)/* UTMI/UTMI+ */ +#define PORTSC_PTS_PHILIPS (1 << 30)/* Philips classic */ +#define PORTSC_PTS_ULPI (2 << 30)/* ULPI */ +#define PORTSC_PTS_SERIAL (3 << 30)/* serial */ +#define PORTSC_STS (1 << 29)/* serial xcvr select */ +#define PORTSC_PTW (1 << 28)/* UTMI width */ +#define PORTSC_PORT_POWER (1 << 12)/* port power */ +#define PORTSC_LS_MASK (3 << 10)/* Line State mask */ +#define PORTSC_LS_SE0 (0 << 10)/* SE0 */ +#define PORTSC_LS_K_STATE (1 << 10)/* K-state */ +#define PORTSC_LS_J_STATE (2 << 10)/* J-state */ +#define PORTSC_PORT_RESET (1 << 8)/* Port reset */ +#define PORTSC_PORT_SUSPEND (1 << 7)/* Suspend */ +#define PORTSC_PORT_FORCE_RESUME (1 << 6)/* Force port resume */ +#define PORTSC_OVER_CURRENT_CHG (1 << 5)/* over current change */ +#define PORTSC_OVER_CURRENT_ACT (1 << 4)/* over currrent active */ +#define PORTSC_PORT_EN_DIS_CHANGE (1 << 3)/* port {en,dis}able change */ +#define PORTSC_PORT_ENABLE (1 << 2)/* port enabled */ +#define PORTSC_CONNECT_STATUS_CHANGE (1 << 1)/* connect status change */ +#define PORTSC_CURRENT_CONNECT_STATUS (1 << 0)/* current connect status */ + +#define PORTSC_W1C_BITS (PORTSC_CONNECT_STATUS_CHANGE | \ + PORTSC_PORT_EN_DIS_CHANGE | \ + PORTSC_OVER_CURRENT_CHG) + +/* UOG_OTGSC Register Bits */ +/* control bits: */ +#define OTGSC_CTRL_VBUS_DISCHARGE (1 << 0) +#define OTGSC_CTRL_VBUS_CHARGE (1 << 1) +#define OTGSC_CTRL_OTG_TERM (1 << 3)/* controls DM pulldown */ +#define OTGSC_CTRL_DATA_PULSING (1 << 4) +#define OTGSC_CTRL_USB_ID_PU (1 << 5)/* enable ID pullup */ +/* current status: (R/O) */ +#define OTGSC_STS_USB_ID (1 << 8)/* 0=A-device 1=B-device */ +#define OTGSC_STS_A_VBUS_VALID (1 << 9) +#define OTGSC_STS_A_SESSION_VALID (1 << 10) +#define OTGSC_STS_B_SESSION_VALID (1 << 11) +#define OTGSC_STS_B_SESSION_END (1 << 12) +#define OTGSC_STS_1ms_TIMER (1 << 13) +#define OTGSC_STS_DATA_PULSE (1 << 14) +/* interrupt status: (write to clear) */ +#define OTGSC_IS_MASK (0x7f << 16) +#define OTGSC_IS_USB_ID (1 << 16) +#define OTGSC_IS_A_VBUS_VALID (1 << 17) +#define OTGSC_IS_A_SESSION_VALID (1 << 18) +#define OTGSC_IS_B_SESSION_VALID (1 << 19) +#define OTGSC_IS_B_SESSION_END (1 << 20) +#define OTGSC_IS_1ms_TIMER (1 << 21) +#define OTGSC_IS_DATA_PULSE (1 << 22) +/* interrupt enables: */ +#define OTGSC_IE_MASK (0x7f << 24) +#define OTGSC_IE_USB_ID (1 << 24) +#define OTGSC_IE_A_VBUS_VALID (1 << 25) +#define OTGSC_IE_A_SESSION_VALID (1 << 26) +#define OTGSC_IE_B_SESSION_VALID (1 << 27) +#define OTGSC_IE_B_SESSION_END (1 << 28) +#define OTGSC_IE_1ms_TIMER (1 << 29) +#define OTGSC_IE_DATA_PULSE (1 << 30) + +/* x_USBMODE */ +#define USBMODE_SLOM (1 << 3) /* setup lockout mode */ +#define USBMODE_ES (1 << 2) /* (big) endian select */ +#define USBMODE_CM_MASK (3 << 0) /* controller mode mask */ +#define USBMODE_CM_HOST (3 << 0) /* host */ +#define USBMODE_CM_DEVICE (2 << 0) /* device */ +#define USBMODE_CM_reserved (1 << 0) /* reserved */ + +/* USBCMD */ +#define UCMD_RUN_STOP (1 << 0) /* controller run/stop */ +#define UCMD_RESET (1 << 1) /* controller reset */ +#define UCMD_ITC_NO_THRESHOLD (~(0xff << 16))/* Interrupt Threshold Control */ + +#define HCSPARAMS_PPC (0x1<<4) /* Port Power Control */ +#endif diff --git a/arch/arm/plat-stmp3xxx/include/mach/dcp_bootstream_ioctl.h b/arch/arm/plat-stmp3xxx/include/mach/dcp_bootstream_ioctl.h new file mode 100644 index 000000000000..aec63a5bfef4 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/include/mach/dcp_bootstream_ioctl.h @@ -0,0 +1,32 @@ +/* + * Freescale STMP378X DCP driver for bootstream update. Only handles the OTP KEY + * case and can only encrypt/decrypt. + * + * Author: Pantelis Antoniou <pantelis@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef DCP_BOOTSTREAM_IOCTL_H +#define DCP_BOOTSTREAM_IOCTL_H + +/* remember to have included the proper _IO definition + * file before hand. + * For user space it's <sys/ioctl.h> + */ + +#define DBS_IOCTL_BASE 'd' + +#define DBS_ENC _IOW(DBS_IOCTL_BASE, 0x00, uint8_t[16]) +#define DBS_DEC _IOW(DBS_IOCTL_BASE, 0x01, uint8_t[16]) + +#endif diff --git a/arch/arm/plat-stmp3xxx/include/mach/fsl_usb.h b/arch/arm/plat-stmp3xxx/include/mach/fsl_usb.h new file mode 100644 index 000000000000..87cc7d8a7abf --- /dev/null +++ b/arch/arm/plat-stmp3xxx/include/mach/fsl_usb.h @@ -0,0 +1,60 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * USB Host side, platform-specific functionality. + */ + +#include <linux/usb/fsl_xcvr.h> +#include <mach/arc_otg.h> + +/* ehci_arc_hc_driver.flags value */ +#define FSL_PLATFORM_HC_FLAGS (HCD_USB2 | HCD_MEMORY) + +static void fsl_setup_phy(struct ehci_hcd *ehci, + enum fsl_usb2_phy_modes phy_mode, + int port_offset); + +static inline void fsl_platform_usb_setup(struct ehci_hcd *ehci) +{ + struct fsl_usb2_platform_data *pdata; + + pdata = ehci_to_hcd(ehci)->self.controller->platform_data; + fsl_setup_phy(ehci, pdata->phy_mode, 0); +} + +static inline void fsl_platform_set_host_mode(struct usb_hcd *hcd) +{ + unsigned int temp; + struct fsl_usb2_platform_data *pdata; + + pdata = hcd->self.controller->platform_data; + + if (pdata->xcvr_ops && pdata->xcvr_ops->set_host) + pdata->xcvr_ops->set_host(); + + /* set host mode */ + temp = readl(hcd->regs + 0x1a8); + writel(temp | USBMODE_CM_HOST, hcd->regs + 0x1a8); +} + +/* Needed for i2c/serial transceivers */ +static inline void +fsl_platform_set_vbus_power(struct fsl_usb2_platform_data *pdata, int on) +{ +} + +/* Set USB AHB burst length for host */ +static inline void fsl_platform_set_ahb_burst(struct usb_hcd *hcd) +{ +} diff --git a/arch/arm/plat-stmp3xxx/include/mach/fsl_usb_gadget.h b/arch/arm/plat-stmp3xxx/include/mach/fsl_usb_gadget.h new file mode 100644 index 000000000000..638bfa4be52b --- /dev/null +++ b/arch/arm/plat-stmp3xxx/include/mach/fsl_usb_gadget.h @@ -0,0 +1,40 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * USB Gadget side, platform-specific functionality. + */ + +#include <linux/usb/fsl_xcvr.h> + +/* Needed for i2c/serial transceivers */ +static inline void +fsl_platform_set_device_mode(struct fsl_usb2_platform_data *pdata) +{ +} + +static inline void +fsl_platform_pullup_enable(struct fsl_usb2_platform_data *pdata) +{ +} + +static inline void +fsl_platform_pullup_disable(struct fsl_usb2_platform_data *pdata) +{ +} + +static inline void +fsl_platform_set_test_mode(struct fsl_usb2_platform_data *pdata, + enum usb_test_mode mode) +{ +} diff --git a/arch/arm/plat-stmp3xxx/include/mach/gpmi.h b/arch/arm/plat-stmp3xxx/include/mach/gpmi.h index e166432910ad..b70b6e39754a 100644 --- a/arch/arm/plat-stmp3xxx/include/mach/gpmi.h +++ b/arch/arm/plat-stmp3xxx/include/mach/gpmi.h @@ -1,12 +1,28 @@ #ifndef __MACH_GPMI_H +#include <linux/mtd/mtd.h> #include <linux/mtd/partitions.h> #include <mach/regs-gpmi.h> +#define GPMI_PART_CONCAT 0x8000 /* indicates that partitions + should be concatenated */ + struct gpmi_platform_data { - void *pins; - int nr_parts; - struct mtd_partition *parts; - const char *part_types[]; + + u_int32_t uid_offset; + u_int32_t uid_size; + + int items; + int io_uA; + char *concat_name; + char **concat_parts; + int (*pinmux) (int req); + + struct { + const char **part_probe_types; + int nr_partitions; + struct mtd_partition *partitions; + } parts[]; + }; #endif diff --git a/arch/arm/plat-stmp3xxx/include/mach/lradc.h b/arch/arm/plat-stmp3xxx/include/mach/lradc.h new file mode 100644 index 000000000000..56b686896f29 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/include/mach/lradc.h @@ -0,0 +1,60 @@ +/* + * Freescale STMP37XX/STMP378X LRADC helper interface + * + * Embedded Alley Solutions, Inc <source@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_PLAT_LRADC_H +#define __ASM_PLAT_LRADC_H + +int hw_lradc_use_channel(int); +int hw_lradc_unuse_channel(int); +extern u32 hw_lradc_vddio(void); +void hw_lradc_set_delay_trigger_kick(int trigger, int value); +void hw_lradc_configure_channel(int channel, int enable_div2, + int enable_acc, int samples); +int hw_lradc_present(int channel); +int hw_lradc_init_ladder(int channel, int trigger, unsigned sampling); +int hw_lradc_stop_ladder(int channel, int trigger); +void hw_lradc_set_delay_trigger(int trigger, u32 trigger_lradc, + u32 delay_triggers, u32 loops, u32 delays); +void hw_lradc_clear_delay_trigger(int trigger, u32 trigger_lradc, + u32 delay_triggers); + + +#define LRADC_CH0 0 +#define LRADC_CH1 1 +#define LRADC_CH2 2 +#define LRADC_CH3 3 +#define LRADC_CH4 4 +#define LRADC_CH5 5 +#define LRADC_CH6 6 +#define LRADC_CH7 7 +#define LRADC_TOUCH_X_PLUS LRADC_CH2 +#define LRADC_TOUCH_Y_PLUS LRADC_CH3 +#define LRADC_TOUCH_X_MINUS LRADC_CH4 +#define LRADC_TOUCH_Y_MINUS LRADC_CH5 +#define VDDIO_VOLTAGE_CH LRADC_CH6 +#define BATTERY_VOLTAGE_CH LRADC_CH7 + +#define LRADC_CLOCK_6MHZ 0 +#define LRADC_CLOCK_4MHZ 1 +#define LRADC_CLOCK_3MHZ 2 +#define LRADC_CLOCK_2MHZ 3 + +#define LRADC_DELAY_TRIGGER_BUTTON 0 +#define LRADC_DELAY_TRIGGER_BATTERY 1 +#define LRADC_DELAY_TRIGGER_TOUCHSCREEN 2 + +#endif /* __ASM_PLAT_LRADC_H */ diff --git a/arch/arm/plat-stmp3xxx/include/mach/memory.h b/arch/arm/plat-stmp3xxx/include/mach/memory.h index 7b875a07a1a7..b152f0ef30c0 100644 --- a/arch/arm/plat-stmp3xxx/include/mach/memory.h +++ b/arch/arm/plat-stmp3xxx/include/mach/memory.h @@ -14,9 +14,50 @@ #ifndef __ASM_ARCH_MEMORY_H #define __ASM_ARCH_MEMORY_H +#include <asm/page.h> +#include <asm/sizes.h> + /* * Physical DRAM offset. */ #define PHYS_OFFSET UL(0x40000000) +#ifndef __ASSEMBLY__ + +#ifdef CONFIG_DMA_ZONE_SIZE +#define MXC_DMA_ZONE_SIZE ((CONFIG_DMA_ZONE_SIZE * SZ_1M) >> PAGE_SHIFT) +#else +#define MXC_DMA_ZONE_SIZE ((12 * SZ_1M) >> PAGE_SHIFT) +#endif + +static inline void __arch_adjust_zones(int node, unsigned long *zone_size, + unsigned long *zhole_size) +{ + if (node != 0) + return; + /* Create separate zone to reserve memory for DMA */ + zone_size[1] = zone_size[0] - MXC_DMA_ZONE_SIZE; + zone_size[0] = MXC_DMA_ZONE_SIZE; + zhole_size[1] = zhole_size[0]; + zhole_size[0] = 0; +} + +#define arch_adjust_zones(node, size, holes) \ + __arch_adjust_zones(node, size, holes) + +#endif +/* + * Virtual view <-> DMA view memory address translations + * virt_to_bus: Used to translate the virtual address to an + * address suitable to be passed to set_dma_addr + * bus_to_virt: Used to convert an address for DMA operations + * to an address that the kernel can use. + */ +#define __virt_to_bus(x) __virt_to_phys(x) +#define __bus_to_virt(x) __phys_to_virt(x) + +#define ISA_DMA_THRESHOLD (0x0003ffffULL) + +#define CONSISTENT_DMA_SIZE SZ_32M + #endif diff --git a/arch/arm/plat-stmp3xxx/include/mach/mmc.h b/arch/arm/plat-stmp3xxx/include/mach/mmc.h index ba81e1543761..5b68bff809ca 100644 --- a/arch/arm/plat-stmp3xxx/include/mach/mmc.h +++ b/arch/arm/plat-stmp3xxx/include/mach/mmc.h @@ -4,11 +4,20 @@ #include <mach/regs-ssp.h> struct stmp3xxxmmc_platform_data { - int (*get_wp)(void); - unsigned long (*setclock)(void __iomem *base, unsigned long); - void (*cmd_pullup)(int); - int (*hw_init)(void); + int (*hw_init)(void); void (*hw_release)(void); + void (*cmd_pullup)(int enable); + int (*get_wp)(void); + unsigned long (*setclock)(unsigned long hz); + int read_uA; + int write_uA; }; + +extern unsigned long stmp3xxxmmc_setclock_ssp1(unsigned long hz); +extern void stmp3xxxmmc_cmd_pullup_ssp1(int enable); +extern void stmp3xxxmmc_hw_release_ssp1(void); +extern int stmp3xxxmmc_hw_init_ssp1(void); +extern int stmp3xxxmmc_get_wp(void); + #endif diff --git a/arch/arm/plat-stmp3xxx/include/mach/ocram-malloc.h b/arch/arm/plat-stmp3xxx/include/mach/ocram-malloc.h new file mode 100644 index 000000000000..c8bee5352972 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/include/mach/ocram-malloc.h @@ -0,0 +1,26 @@ +/* + * Freescale STMP37XX/STMP378X OCRAM allocator interface + * + * Embedded Alley Solutions, Inc <source@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_PLAT_OCRAM_MALLOC_H +#define __ASM_PLAT_OCRAM_MALLOC_H + +extern int ocram_malloc_init(void); + +extern void *ocram_malloc(size_t size, dma_addr_t *phys); +extern void ocram_free(void *tofree); + +#endif /* __ASM_PLAT_OCRAM_MALLOC_H */ diff --git a/arch/arm/plat-stmp3xxx/include/mach/pinmux.h b/arch/arm/plat-stmp3xxx/include/mach/pinmux.h index cc5af82279ad..763f59572977 100644 --- a/arch/arm/plat-stmp3xxx/include/mach/pinmux.h +++ b/arch/arm/plat-stmp3xxx/include/mach/pinmux.h @@ -24,7 +24,6 @@ #include <asm-generic/gpio.h> /* Pin definitions */ -#include "pins.h" #include <mach/pins.h> /* @@ -94,6 +93,10 @@ int stmp3xxx_request_pin(unsigned id, enum pin_fun fun, const char *label); /* Release pin */ void stmp3xxx_release_pin(unsigned id, const char *label); +int stmp3xxx_request_pin_group(struct pin_group *pin_group, const char *label); + +void stmp3xxx_release_pin_group(struct pin_group *pin_group, const char *label); + void stmp3xxx_set_pin_type(unsigned id, enum pin_fun fun); /* diff --git a/arch/arm/plat-stmp3xxx/include/mach/pins.h b/arch/arm/plat-stmp3xxx/include/mach/pins.h deleted file mode 100644 index c573318e1caa..000000000000 --- a/arch/arm/plat-stmp3xxx/include/mach/pins.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Freescale STMP37XX/STMP378X Pin multiplexing interface definitions - * - * Author: Vladislav Buzov <vbuzov@embeddedalley.com> - * - * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. - * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. - */ - -/* - * The code contained herein is licensed under the GNU General Public - * License. You may obtain a copy of the GNU General Public License - * Version 2 or later at the following locations: - * - * http://www.opensource.org/licenses/gpl-license.html - * http://www.gnu.org/copyleft/gpl.html - */ -#ifndef __ASM_PLAT_PINS_H -#define __ASM_PLAT_PINS_H - -#define STMP3XXX_PINID(bank, pin) (bank * 32 + pin) -#define STMP3XXX_PINID_TO_BANK(pinid) (pinid / 32) -#define STMP3XXX_PINID_TO_PINNUM(pinid) (pinid % 32) - -/* - * Special invalid pin identificator to show a pin doesn't exist - */ -#define PINID_NO_PIN STMP3XXX_PINID(0xFF, 0xFF) - -#endif /* __ASM_PLAT_PINS_H */ diff --git a/arch/arm/plat-stmp3xxx/include/mach/power.h b/arch/arm/plat-stmp3xxx/include/mach/power.h new file mode 100644 index 000000000000..ac90f8621099 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/include/mach/power.h @@ -0,0 +1,67 @@ +/* + * Freescale STMP37XX/STMP378X voltage regulator structure declarations + * + * Embedded Alley Solutions, Inc <sources@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __VOLTAGE_H +#define __VOLTAGE_H +#include <linux/completion.h> +//#include <linux/regulator/consumer.h> +#include <linux/regulator/driver.h> + +struct stmp3xxx_regulator { + struct regulator_desc regulator; + struct stmp3xxx_regulator *parent; + struct stmp3xxx_platform_regulator_data *rdata; + struct completion done; + + spinlock_t lock; + wait_queue_head_t wait_q; + struct notifier_block nb; + + int mode; + int cur_voltage; + int cur_current; + int next_current; +}; + + +struct stmp3xxx_platform_regulator_data { + char name[80]; + char *parent_name; + int (*reg_register)(struct stmp3xxx_regulator *sreg); + int (*set_voltage)(struct stmp3xxx_regulator *sreg, int uv); + int (*get_voltage)(struct stmp3xxx_regulator *sreg); + int (*set_current)(struct stmp3xxx_regulator *sreg, int uA); + int (*get_current)(struct stmp3xxx_regulator *sreg); + int (*enable)(struct stmp3xxx_regulator *sreg); + int (*disable)(struct stmp3xxx_regulator *sreg); + int (*is_enabled)(struct stmp3xxx_regulator *sreg); + int (*set_mode)(struct stmp3xxx_regulator *sreg, int mode); + int (*get_mode)(struct stmp3xxx_regulator *sreg); + int (*get_optimum_mode)(struct stmp3xxx_regulator *sreg, + int input_uV, int output_uV, int load_uA); + u32 control_reg; + int min_voltage; + int max_voltage; + int max_current; + struct regulation_constraints *constraints; +}; + +int stmp3xxx_register_regulator( + struct stmp3xxx_regulator *reg_data, int reg, + struct regulator_init_data *initdata); + +#endif /* __VOLTAGE_H */ diff --git a/arch/arm/plat-stmp3xxx/include/mach/pwm-led.h b/arch/arm/plat-stmp3xxx/include/mach/pwm-led.h new file mode 100644 index 000000000000..03e530727488 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/include/mach/pwm-led.h @@ -0,0 +1,25 @@ +/* + * Freescale STMP37XX/STMP378X PWM LED arch-dependent structure + * and functions declarations + * + * Author: Drew Benedetti <drewb@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_PLAT_PWM_LED_H +#define __ASM_PLAT_PWM_LED_H + +extern int pwm_led_pinmux_request(int, char *); +extern void pwm_led_pinmux_free(int, char *); + +#endif /* __ASM_PLAT_PWM_LED_H */ diff --git a/arch/arm/plat-stmp3xxx/include/mach/regulator.h b/arch/arm/plat-stmp3xxx/include/mach/regulator.h new file mode 100644 index 000000000000..01880bd157a7 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/include/mach/regulator.h @@ -0,0 +1,23 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __PLAT_REGULATOR_H_ +#define __PLAT_REGULATOR_H_ +#define STMP3XXX_REG5V_NOT_USB 0 +#define STMP3XXX_REG5V_IS_USB 1 +#define STMP3XXX_VDDD 0 +#define STMP3XXX_VDDA 1 +#define STMP3XXX_VDDIO 2 +#define STMP3XXX_VDDDBO 3 +#define STMP3XXX_OVERALL_CUR 4 + +#endif diff --git a/arch/arm/plat-stmp3xxx/include/mach/rotdec.h b/arch/arm/plat-stmp3xxx/include/mach/rotdec.h new file mode 100644 index 000000000000..6e81bb32d3b9 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/include/mach/rotdec.h @@ -0,0 +1,25 @@ +/* + * Freescale STMP37XX/STMP378X dev board rotary encoder arch-dependent + * structure and functions declarations + * + * Author: Drew Benedetti <drewb@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_PLAT_ROTDEC_H +#define __ASM_PLAT_ROTDEC_H + +extern int rotdec_pinmux_request(void); +extern void rotdec_pinmux_free(void); + +#endif /* __ASM_PLAT_ROTDEC_H */ diff --git a/arch/arm/plat-stmp3xxx/include/mach/stmp3xxx.h b/arch/arm/plat-stmp3xxx/include/mach/stmp3xxx.h index 2e300feaa4cf..a697124b5ce7 100644 --- a/arch/arm/plat-stmp3xxx/include/mach/stmp3xxx.h +++ b/arch/arm/plat-stmp3xxx/include/mach/stmp3xxx.h @@ -3,7 +3,7 @@ * * Embedded Alley Solutions, Inc <source@embeddedalley.com> * - * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. */ @@ -19,9 +19,43 @@ #define __ASM_PLAT_STMP3XXX_H #include <linux/irq.h> +#include <linux/suspend.h> +#include <linux/platform_device.h> extern struct sys_timer stmp3xxx_timer; +struct stmpkbd_keypair { + int raw; + int kcode; +}; + +struct stmp37xx_spi_platform_data { + unsigned irq_pin; + + int (*hw_init)(void *spi); + int (*hw_release)(void *spi); +}; + +struct stmp3xxx_persistent_bit_config { + int reg; + int start; + int width; + const char *name; +}; + +struct stmp3xxx_platform_persistent_data { + const struct stmp3xxx_persistent_bit_config *bit_config_tab; + int bit_config_cnt; +}; + +#define STMP3XXX_USB_DONT_REMAP 0x00000001 +struct stmp3xxx_usb_platform_data { + unsigned flags; + int (*phy_enable)(struct platform_device *); + void (*hw_init)(void); + void (*hw_release)(void); +}; + void stmp3xxx_init_irq(struct irq_chip *chip); void stmp3xxx_init(void); int stmp3xxx_reset_block(void __iomem *hwreg, int just_enable); @@ -32,18 +66,32 @@ extern struct platform_device stmp3xxx_dbguart, stmp3xxx_keyboard, stmp3xxx_gpmi, stmp3xxx_mmc, + stmp3xxx_mmc2, stmp3xxx_udc, stmp3xxx_ehci, + stmp3xxx_usb, stmp3xxx_rtc, stmp3xxx_spi1, stmp3xxx_spi2, stmp3xxx_backlight, stmp3xxx_rotdec, + stmp3xxx_ssp1, + stmp3xxx_ssp2, stmp3xxx_dcp, stmp3xxx_dcp_bootstream, stmp3xxx_persistent, stmp3xxx_framebuffer, - stmp3xxx_battery; + stmp3xxx_battery, + stmp378x_i2c, + stmp378x_pxp, + stmp378x_audio, + stmp3xxx_viim, + stmp3xxx_spdif; +#ifdef CONFIG_PM +suspend_state_t stmp37xx_pm_get_target(void); +int stmp37xx_pm_sleep_was_deep(void); +#endif + int stmp3xxx_ssp1_device_register(void); int stmp3xxx_ssp2_device_register(void); @@ -51,4 +99,6 @@ struct pin_group; void stmp3xxx_release_pin_group(struct pin_group *pin_group, const char *label); int stmp3xxx_request_pin_group(struct pin_group *pin_group, const char *label); +int get_evk_board_version(void); + #endif /* __ASM_PLAT_STMP3XXX_H */ diff --git a/arch/arm/plat-stmp3xxx/include/mach/unique-id.h b/arch/arm/plat-stmp3xxx/include/mach/unique-id.h new file mode 100644 index 000000000000..ee22ec2502c5 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/include/mach/unique-id.h @@ -0,0 +1,30 @@ +/* + * Unique ID interface for ID storage providers + * + * Embedded Alley Solutions, Inc <source@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __UNIQUE_ID_H +#define __UNIQUE_ID_H + +struct uid_ops { + ssize_t (*id_show)(void *context, char *page, int ascii); + ssize_t (*id_store)(void *context, const char *page, + size_t count, int ascii); +}; + +struct kobject *uid_provider_init(const char *name, + struct uid_ops *ops, void *context); +void uid_provider_remove(const char *name); +#endif diff --git a/arch/arm/plat-stmp3xxx/lradc.c b/arch/arm/plat-stmp3xxx/lradc.c new file mode 100644 index 000000000000..ed0b89323add --- /dev/null +++ b/arch/arm/plat-stmp3xxx/lradc.c @@ -0,0 +1,332 @@ +/* + * Freescale STMP37XX/STMP378X LRADC helper routines + * + * Embedded Alley Solutions, Inc <source@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/sysdev.h> +#include <linux/bitops.h> +#include <linux/irq.h> +#include <mach/hardware.h> +#include <linux/delay.h> +#include <mach/platform.h> +#include <mach/stmp3xxx.h> +#include <mach/regs-lradc.h> +#include <mach/lradc.h> + +static int channels[8]; + +int hw_lradc_use_channel(int channel) +{ + if (channel < 0 || channel > 7) + return -EINVAL; + channels[channel]++; + return 0; +} +EXPORT_SYMBOL(hw_lradc_use_channel); + +int hw_lradc_unuse_channel(int channel) +{ + if (channel < 0 || channel > 7) + return -EINVAL; + channels[channel]--; + return 0; +} +EXPORT_SYMBOL(hw_lradc_unuse_channel); + +void hw_lradc_reinit(int enable_ground_ref, unsigned freq) +{ + stmp3xxx_setl(BM_LRADC_CTRL0_SFTRST, REGS_LRADC_BASE + HW_LRADC_CTRL0); + udelay(1); + stmp3xxx_clearl(BM_LRADC_CTRL0_SFTRST, + REGS_LRADC_BASE + HW_LRADC_CTRL0); + + /* Clear the Clock Gate for normal operation */ + stmp3xxx_clearl(BM_LRADC_CTRL0_CLKGATE, + REGS_LRADC_BASE + HW_LRADC_CTRL0); + + if (enable_ground_ref) + stmp3xxx_setl(BM_LRADC_CTRL0_ONCHIP_GROUNDREF, + REGS_LRADC_BASE + HW_LRADC_CTRL0); + else + stmp3xxx_clearl(BM_LRADC_CTRL0_ONCHIP_GROUNDREF, + REGS_LRADC_BASE + HW_LRADC_CTRL0); + + stmp3xxx_clearl(BM_LRADC_CTRL3_CYCLE_TIME, + REGS_LRADC_BASE + HW_LRADC_CTRL3); + stmp3xxx_setl(BF(freq, LRADC_CTRL3_CYCLE_TIME), + REGS_LRADC_BASE + HW_LRADC_CTRL3); + + stmp3xxx_clearl(BM_LRADC_CTRL4_LRADC6SELECT | + BM_LRADC_CTRL4_LRADC7SELECT, + REGS_LRADC_BASE + HW_LRADC_CTRL4); + stmp3xxx_setl(BF(VDDIO_VOLTAGE_CH, LRADC_CTRL4_LRADC6SELECT), + REGS_LRADC_BASE + HW_LRADC_CTRL4); + stmp3xxx_setl(BF(BATTERY_VOLTAGE_CH, LRADC_CTRL4_LRADC7SELECT), + REGS_LRADC_BASE + HW_LRADC_CTRL4); +} + +int hw_lradc_init_ladder(int channel, int trigger, unsigned sampling) +{ + /* + * check if the lradc channel is present in this product + */ + if (!hw_lradc_present(channel)) + return -ENODEV; + + hw_lradc_configure_channel(channel, !0 /* div2 */ , + 0 /* acc */ , + 0 /* num_samples */ ); + + /* Setup the trigger loop forever */ + hw_lradc_set_delay_trigger(trigger, 1 << channel, + 1 << trigger, 0, sampling); + + /* Clear the accumulator & NUM_SAMPLES */ + stmp3xxx_clearl(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn(channel)); + return 0; +} + +EXPORT_SYMBOL(hw_lradc_init_ladder); + +int hw_lradc_stop_ladder(int channel, int trigger) +{ + /* + * check if the lradc channel is present in this product + */ + if (!hw_lradc_present(channel)) + return -ENODEV; + hw_lradc_clear_delay_trigger(trigger, 1 << channel, 1 << trigger); + return 0; +} + +EXPORT_SYMBOL(hw_lradc_stop_ladder); + +int hw_lradc_present(int channel) +{ + if (channel < 0 || channel > 7) + return 0; + return __raw_readl(REGS_LRADC_BASE + HW_LRADC_STATUS) + & (1 << (16 + channel)); +} + +EXPORT_SYMBOL(hw_lradc_present); + +void hw_lradc_configure_channel(int channel, int enable_div2, + int enable_acc, int samples) +{ + if (enable_div2) + stmp3xxx_setl(BF(1 << channel, LRADC_CTRL2_DIVIDE_BY_TWO), + REGS_LRADC_BASE + HW_LRADC_CTRL2); + else + stmp3xxx_clearl(BF(1 << channel, LRADC_CTRL2_DIVIDE_BY_TWO), + REGS_LRADC_BASE + HW_LRADC_CTRL2); + + /* Clear the accumulator & NUM_SAMPLES */ + stmp3xxx_clearl(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn(channel)); + + /* Sets NUM_SAMPLES bitfield of HW_LRADC_CHn register. */ + stmp3xxx_clearl(BM_LRADC_CHn_NUM_SAMPLES, + REGS_LRADC_BASE + HW_LRADC_CHn(channel)); + stmp3xxx_setl(BF(samples, LRADC_CHn_NUM_SAMPLES), + REGS_LRADC_BASE + HW_LRADC_CHn(channel)); + + if (enable_acc) + stmp3xxx_setl(BM_LRADC_CHn_ACCUMULATE, + REGS_LRADC_BASE + HW_LRADC_CHn(channel)); + else + stmp3xxx_clearl(BM_LRADC_CHn_ACCUMULATE, + REGS_LRADC_BASE + HW_LRADC_CHn(channel)); +} + +EXPORT_SYMBOL(hw_lradc_configure_channel); + +void hw_lradc_set_delay_trigger(int trigger, u32 trigger_lradc, + u32 delay_triggers, u32 loops, u32 delays) +{ + /* set TRIGGER_LRADCS in HW_LRADC_DELAYn */ + stmp3xxx_setl(BF(trigger_lradc, LRADC_DELAYn_TRIGGER_LRADCS), + REGS_LRADC_BASE + HW_LRADC_DELAYn(trigger)); + stmp3xxx_setl(BF(delay_triggers, LRADC_DELAYn_TRIGGER_DELAYS), + REGS_LRADC_BASE + HW_LRADC_DELAYn(trigger)); + + stmp3xxx_clearl(BM_LRADC_DELAYn_LOOP_COUNT | BM_LRADC_DELAYn_DELAY, + REGS_LRADC_BASE + HW_LRADC_DELAYn(trigger)); + stmp3xxx_setl(BF(loops, LRADC_DELAYn_LOOP_COUNT), + REGS_LRADC_BASE + HW_LRADC_DELAYn(trigger)); + stmp3xxx_setl(BF(delays, LRADC_DELAYn_DELAY), + REGS_LRADC_BASE + HW_LRADC_DELAYn(trigger)); +} + +EXPORT_SYMBOL(hw_lradc_set_delay_trigger); + +void hw_lradc_clear_delay_trigger(int trigger, u32 trigger_lradc, + u32 delay_triggers) +{ + stmp3xxx_clearl(BF(trigger_lradc, LRADC_DELAYn_TRIGGER_LRADCS), + REGS_LRADC_BASE + HW_LRADC_DELAYn(trigger)); + stmp3xxx_clearl(BF(delay_triggers, LRADC_DELAYn_TRIGGER_DELAYS), + REGS_LRADC_BASE + HW_LRADC_DELAYn(trigger)); +} + +EXPORT_SYMBOL(hw_lradc_clear_delay_trigger); + +void hw_lradc_set_delay_trigger_kick(int trigger, int value) +{ + if (value) + stmp3xxx_setl(BM_LRADC_DELAYn_KICK, + REGS_LRADC_BASE + HW_LRADC_DELAYn(trigger)); + else + stmp3xxx_clearl(BM_LRADC_DELAYn_KICK, + REGS_LRADC_BASE + HW_LRADC_DELAYn(trigger)); +} + +EXPORT_SYMBOL(hw_lradc_set_delay_trigger_kick); + +u32 hw_lradc_vddio(void) +{ + /* Clear the Soft Reset and Clock Gate for normal operation */ + stmp3xxx_clearl(BM_LRADC_CTRL0_SFTRST | BM_LRADC_CTRL0_CLKGATE, + REGS_LRADC_BASE + HW_LRADC_CTRL0); + + /* + * Clear the divide by two for channel 6 since + * it has a HW divide-by-two built in. + */ + stmp3xxx_clearl(BF(1 << VDDIO_VOLTAGE_CH, LRADC_CTRL2_DIVIDE_BY_TWO), + REGS_LRADC_BASE + HW_LRADC_CTRL2); + + /* Clear the accumulator & NUM_SAMPLES */ + stmp3xxx_clearl(0xFFFFFFFF, + REGS_LRADC_BASE + HW_LRADC_CHn(VDDIO_VOLTAGE_CH)); + + /* Clear the interrupt flag */ + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC6_IRQ, + REGS_LRADC_BASE + HW_LRADC_CTRL1); + + /* + * Get VddIO; this is the max scale value for the button resistor + * ladder. + * schedule ch 6: + */ + stmp3xxx_setl(BF(1 << VDDIO_VOLTAGE_CH, LRADC_CTRL0_SCHEDULE), + REGS_LRADC_BASE + HW_LRADC_CTRL0); + + /* wait for completion */ + while ((__raw_readl(REGS_LRADC_BASE + HW_LRADC_CTRL1) + & BM_LRADC_CTRL1_LRADC6_IRQ) != BM_LRADC_CTRL1_LRADC6_IRQ) + cpu_relax(); + + /* Clear the interrupt flag */ + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC6_IRQ, + REGS_LRADC_BASE + HW_LRADC_CTRL1); + + /* read ch 6 value. */ + return __raw_readl(REGS_LRADC_BASE + HW_LRADC_CHn(6)) + & BM_LRADC_CHn_VALUE; +} + +EXPORT_SYMBOL(hw_lradc_vddio); + +static u32 lradc_registers[0x16]; +static int do_gate; + +static int hw_lradc_suspend(struct sys_device *dev, pm_message_t state) +{ + int i; + + do_gate = 1; + for (i = 0; i < ARRAY_SIZE(channels); i++) + if (channels[i] > 0) { + do_gate = 0; + break; + } + + for (i = 0; i < ARRAY_SIZE(lradc_registers); i++) + lradc_registers[i] = __raw_readl(REGS_LRADC_BASE + (i << 4)); + + if (do_gate) + stmp3xxx_setl(BM_LRADC_CTRL0_CLKGATE, + REGS_LRADC_BASE + HW_LRADC_CTRL0); + return 0; +} + +static int hw_lradc_resume(struct sys_device *dev) +{ + int i; + + if (do_gate) { + stmp3xxx_setl(BM_LRADC_CTRL0_SFTRST, + REGS_LRADC_BASE + HW_LRADC_CTRL0); + udelay(10); + stmp3xxx_clearl(BM_LRADC_CTRL0_SFTRST | + BM_LRADC_CTRL0_CLKGATE, + REGS_LRADC_BASE + HW_LRADC_CTRL0); + } + for (i = 0; i < ARRAY_SIZE(lradc_registers); i++) + __raw_writel(lradc_registers[i], REGS_LRADC_BASE + (i << 4)); + return 0; +} + +static struct sysdev_class stmp3xxx_lradc_sysclass = { + .name = "stmp3xxx-lradc", +#ifdef CONFIG_PM + .suspend = hw_lradc_suspend, + .resume = hw_lradc_resume, +#endif +}; + +static struct sys_device stmp3xxx_lradc_device = { + .id = -1, + .cls = &stmp3xxx_lradc_sysclass, +}; + +static int __initdata lradc_freq = LRADC_CLOCK_6MHZ; + +static int __init lradc_freq_setup(char *str) +{ + long freq; + + if (strict_strtol(str, 0, &freq) < 0) + return 0; + + if (freq < 0) + return 0; + if (freq >= 6) + lradc_freq = LRADC_CLOCK_6MHZ; + else if (freq >= 4) + lradc_freq = LRADC_CLOCK_4MHZ; + else if (freq >= 3) + lradc_freq = LRADC_CLOCK_3MHZ; + else if (freq >= 2) + lradc_freq = LRADC_CLOCK_2MHZ; + else + return 0; + return 1; +} + +__setup("lradc_freq=", lradc_freq_setup); + +static int __init hw_lradc_init(void) +{ + hw_lradc_reinit(0, lradc_freq); + sysdev_class_register(&stmp3xxx_lradc_sysclass); + sysdev_register(&stmp3xxx_lradc_device); + return 0; +} + +subsys_initcall(hw_lradc_init); diff --git a/arch/arm/plat-stmp3xxx/mmc.c b/arch/arm/plat-stmp3xxx/mmc.c new file mode 100644 index 000000000000..75dc3a9f282a --- /dev/null +++ b/arch/arm/plat-stmp3xxx/mmc.c @@ -0,0 +1,148 @@ +/* + * Freescale STMP37XX/STMP378X MMC pin multiplexing + * + * Embedded Alley Solutions, Inc <source@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/gpio.h> +#include <mach/pinmux.h> +#include <mach/stmp3xxx.h> +#include <mach/platform.h> +#include <mach/mmc.h> +#include <mach/regs-ssp.h> + +#if defined(CONFIG_MACH_STMP378X) +#define MMC_POWER PINID_PWM3 +#define MMC_WP PINID_PWM4 +#elif defined(CONFIG_MACH_STMP37XX) +#define MMC_POWER PINID_PWM3 +#define MMC_WP PINID_PWM4 +#else +#define MMC_POWER PINID_NO_PIN +#define MMC_WP PINID_NO_PIN +#endif + +static int mmc_drive_power; +static int mmc_wp_supported; + +static struct pin_desc mmc_pins_desc[] = { + { PINID_SSP1_DATA0, PIN_FUN1, PIN_8MA, PIN_3_3V, 1 }, + { PINID_SSP1_DATA1, PIN_FUN1, PIN_8MA, PIN_3_3V, 1 }, + { PINID_SSP1_DATA2, PIN_FUN1, PIN_8MA, PIN_3_3V, 1 }, + { PINID_SSP1_DATA3, PIN_FUN1, PIN_8MA, PIN_3_3V, 1 }, + { PINID_SSP1_CMD, PIN_FUN1, PIN_8MA, PIN_3_3V, 1 }, + { PINID_SSP1_SCK, PIN_FUN1, PIN_8MA, PIN_3_3V, 0 }, + { PINID_SSP1_DETECT, PIN_FUN1, PIN_8MA, PIN_3_3V, 0 }, +}; + +static struct pin_group mmc_pins = { + .pins = mmc_pins_desc, + .nr_pins = ARRAY_SIZE(mmc_pins_desc), +}; + +int stmp3xxxmmc_get_wp(void) +{ + if (mmc_wp_supported) + return gpio_get_value(MMC_WP); + + return 0; +} + +int stmp3xxxmmc_hw_init_ssp1(void) +{ + int ret; + + mmc_drive_power = stmp3xxx_valid_pin(MMC_POWER); + mmc_wp_supported = stmp3xxx_valid_pin(MMC_WP); + + ret = stmp3xxx_request_pin_group(&mmc_pins, "mmc"); + if (ret) + goto out; + + if (mmc_wp_supported) { + /* Configure write protect GPIO pin */ + ret = gpio_request(MMC_WP, "mmc wp"); + if (ret) + goto out_wp; + + gpio_set_value(MMC_WP, 0); + gpio_direction_input(MMC_WP); + } + + if (mmc_drive_power) { + /* Configure POWER pin as gpio to drive power to MMC slot */ + ret = gpio_request(MMC_POWER, "mmc power"); + if (ret) + goto out_power; + + gpio_direction_output(MMC_POWER, 0); + mdelay(100); + } + + return 0; + +out_power: + if (mmc_wp_supported) + gpio_free(MMC_WP); +out_wp: + stmp3xxx_release_pin_group(&mmc_pins, "mmc"); +out: + return ret; +} + +void stmp3xxxmmc_hw_release_ssp1(void) +{ + if (mmc_drive_power) + gpio_free(MMC_POWER); + + if (mmc_wp_supported) + gpio_free(MMC_WP); + + stmp3xxx_release_pin_group(&mmc_pins, "mmc"); +} + +void stmp3xxxmmc_cmd_pullup_ssp1(int enable) +{ + stmp3xxx_pin_pullup(PINID_SSP1_CMD, enable, "mmc"); +} + +unsigned long stmp3xxxmmc_setclock_ssp1(unsigned long hz) +{ + struct clk *ssp = clk_get(NULL, "ssp"), *parent; + char *p; + long r; + + /* using SSP1, no timeout, clock rate 1 */ + __raw_writel(BF(2, SSP_TIMING_CLOCK_DIVIDE) | + BF(0xFFFF, SSP_TIMING_TIMEOUT), + REGS_SSP1_BASE + HW_SSP_TIMING); + + if (hz > 1000000) + p = "io"; + else + p = "osc_24M"; + + parent = clk_get(NULL, p); + clk_set_parent(ssp, parent); + r = clk_set_rate(ssp, 2 * hz / 1000); + clk_put(parent); + clk_put(ssp); + + return hz; +} diff --git a/arch/arm/plat-stmp3xxx/pinmux.c b/arch/arm/plat-stmp3xxx/pinmux.c index 6d6b1a468eda..375578d58c27 100644 --- a/arch/arm/plat-stmp3xxx/pinmux.c +++ b/arch/arm/plat-stmp3xxx/pinmux.c @@ -15,7 +15,7 @@ * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ -#define DEBUG +//#define DEBUG #include <linux/module.h> #include <linux/kernel.h> #include <linux/errno.h> diff --git a/arch/arm/plat-stmp3xxx/power-test.c b/arch/arm/plat-stmp3xxx/power-test.c new file mode 100644 index 000000000000..66f15f562798 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/power-test.c @@ -0,0 +1,213 @@ +/* + * Power consumption test module + * + * Author: Dmitrij Frasenyak <sed@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/sched.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +static struct regulator *reg; +static struct regulator *freg; + +static struct timer_list pt_timer; +static int timer_delay = 5*60*1000; /* 5min */ +static DEFINE_MUTEX(run_mutex); + + +#define REG_GET() do {\ + if (!reg) {\ + reg = regulator_get(NULL, "power-test-1");\ + if (!reg || IS_ERR(reg)) {\ + reg = NULL ; return -ENODEV;\ + } \ + } \ +} while (0); + +static void timer_func(unsigned long data) +{ + regulator_set_current_limit(reg, 0, 0); + mutex_unlock(&run_mutex); +} + +static ssize_t pt_mode_set(struct device *d, struct device_attribute *attr, + const char *buf, size_t size) +{ + REG_GET(); + if (buf[0] == 'f') + regulator_set_mode(reg, REGULATOR_MODE_FAST); + else + regulator_set_mode(reg, REGULATOR_MODE_NORMAL); + return size; +} + +static ssize_t pt_mode_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + REG_GET(); + if (regulator_get_mode(reg) == REGULATOR_MODE_FAST) + return snprintf(buf, 5, "fast\n"); + else + return snprintf(buf, 7, "normal\n"); +} + +static ssize_t pt_val_fset(struct device *d, struct device_attribute *attr, + const char *buf, size_t size) +{ + int i, ret; + ret = sscanf(buf, "%u", &i); + if (ret != 1) + return -EINVAL; + + if (!freg) { + freg = regulator_get(NULL, "stmp3xxx-bl-1"); + if (!freg || IS_ERR(freg)) { + freg = NULL ; return -ENODEV; + } + } + regulator_set_mode(freg, REGULATOR_MODE_NORMAL); + + if (!regulator_set_current_limit(freg, i, i)) + printk(KERN_ERR "got backlight reg\n"); + else + printk(KERN_ERR "failed to get backlight reg"); + + return size; +} + + +static ssize_t pt_val_set(struct device *d, struct device_attribute *attr, + const char *buf, size_t size) +{ + int i, ret; + REG_GET(); + + ret = sscanf(buf, "%u", &i); + if (ret != 1) + return -EINVAL; + + mutex_lock(&run_mutex); + if (!regulator_set_current_limit(reg, i, i)) { + mod_timer(&pt_timer, + jiffies + msecs_to_jiffies(timer_delay)); + return size; + } else { + mutex_unlock(&run_mutex); + return -EPERM; + } + +} + +static ssize_t pt_val_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + REG_GET(); + return sprintf(buf, "%d\n", regulator_get_current_limit(reg)); +} + +static ssize_t pt_timeout_set(struct device *d, struct device_attribute *attr, + const char *buf, size_t size) +{ + int i, ret; + REG_GET(); + + ret = sscanf(buf, "%u", &i); + if (ret != 1) + return -EINVAL; + + mutex_lock(&run_mutex); + timer_delay = 1000*i ; + mutex_unlock(&run_mutex); + + return size; +} + +static ssize_t pt_timeout_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + REG_GET(); + return sprintf(buf, "%d\n", timer_delay); +} + +static DEVICE_ATTR(mode, 0644, pt_mode_show, pt_mode_set); +static DEVICE_ATTR(val, 0644, pt_val_show, pt_val_set); +static DEVICE_ATTR(fval, 0644, pt_val_show, pt_val_fset); +static DEVICE_ATTR(timeout, 0644, pt_timeout_show, pt_timeout_set); + +static int stmp3xxx_power_test_remove(struct platform_device *pdev) +{ + if (reg) + regulator_put(reg); + + device_remove_file(&pdev->dev, &dev_attr_mode); + device_remove_file(&pdev->dev, &dev_attr_val); + device_remove_file(&pdev->dev, &dev_attr_timeout); + device_remove_file(&pdev->dev, &dev_attr_fval); + return 0; +} + +static int stmp3xxx_power_test_probe(struct platform_device *pdev) +{ + int ret; + init_timer(&pt_timer); + pt_timer.data = 0; + pt_timer.function = timer_func; + + ret = device_create_file(&pdev->dev, &dev_attr_mode); + ret |= device_create_file(&pdev->dev, &dev_attr_val); + ret |= device_create_file(&pdev->dev, &dev_attr_fval); + ret |= device_create_file(&pdev->dev, &dev_attr_timeout); + return ret; +} + +static struct platform_driver stmp3xxx_power_test_driver = { + .probe = stmp3xxx_power_test_probe, + .remove = stmp3xxx_power_test_remove, + .driver = { + .name = "stmp3xxx-power-test", + .owner = THIS_MODULE, + }, +}; + +struct platform_device stmp3xxx_pt = { + .name = "stmp3xxx-power-test", + .id = -1, +}; + +static int __init stmp3xxx_power_test_init(void) +{ + + platform_device_register(&stmp3xxx_pt); + return platform_driver_register(&stmp3xxx_power_test_driver); +} + +static void __exit stmp3xxx_power_test_exit(void) +{ + platform_driver_unregister(&stmp3xxx_power_test_driver); + platform_device_unregister(&stmp3xxx_pt); +} + +MODULE_AUTHOR("<sed@embeddedalley.com>"); +MODULE_DESCRIPTION("Power test driver"); +MODULE_LICENSE("GPL"); + +module_init(stmp3xxx_power_test_init); +module_exit(stmp3xxx_power_test_exit); diff --git a/arch/arm/plat-stmp3xxx/rotdec.c b/arch/arm/plat-stmp3xxx/rotdec.c new file mode 100644 index 000000000000..d390102c1559 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/rotdec.c @@ -0,0 +1,39 @@ +/* + * Freescale STMP378X Rotary Encoder module pin multiplexing + * + * Author: Drew Benedetti <drewb@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/module.h> +#include <mach/pins.h> +#include "pinmux.h" + +#define TITLE "stmp3xxx-rotdec" + +int rotdec_pinmux_request(void) +{ + return stmp3xxx_request_pin_group(&rotdec_pins, TITLE); +} +EXPORT_SYMBOL_GPL(rotdec_pinmux_request); + +void rotdec_pinmux_free(void) +{ + stmp3xxx_release_pin_group(&spdif_pins, TITLE); +} +EXPORT_SYMBOL_GPL(rotdec_pinmux_free); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Drew Benedetti <drewb@embeddedalley.com>"); diff --git a/arch/arm/plat-stmp3xxx/spi.c b/arch/arm/plat-stmp3xxx/spi.c new file mode 100644 index 000000000000..21242dfd5c9a --- /dev/null +++ b/arch/arm/plat-stmp3xxx/spi.c @@ -0,0 +1,106 @@ +/* + * Freescale STMP37XX/STMP378X SPI module pin multiplexing + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/spi/spi.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <mach/stmp3xxx.h> +#include <mach/pinmux.h> + +/* + These pins are: + SCK (SSPx_SCK) + MOSI (SSPx_CMD) + MISO (SSPx_DATA0) + SSn (SSPx_DATA3) + Please add new pins in the same order, thanks :) +*/ +static struct pin_desc ssp_pins_desc[2][4] = { + [0] = { + { PINID_SSP1_SCK, PIN_FUN1, PIN_8MA, PIN_3_3V, 0, }, + { PINID_SSP1_CMD, PIN_FUN1, PIN_4MA, PIN_3_3V, 0, }, + { PINID_SSP1_DATA0, PIN_FUN1, PIN_4MA, PIN_3_3V, 0, }, + { PINID_SSP1_DATA3, PIN_FUN1, PIN_4MA, PIN_3_3V, 0, }, + }, + [1] = { +#if defined(CONFIG_ARCH_STMP37XX) + { PINID_GPMI_IRQ, PIN_FUN3, PIN_8MA, PIN_3_3V, 0, }, + { PINID_GPMI_RDY2, PIN_FUN3, PIN_4MA, PIN_3_3V, 0, }, + { PINID_EMI_CE2N, PIN_FUN3, PIN_4MA, PIN_3_3V, 0, }, + { PINID_GPMI_D03, PIN_FUN3, PIN_4MA, PIN_3_3V, 0, }, +#elif defined(CONFIG_ARCH_STMP378X) + { PINID_GPMI_WRN, PIN_FUN3, PIN_8MA, PIN_3_3V, 0, }, + { PINID_GPMI_RDY1, PIN_FUN3, PIN_4MA, PIN_3_3V, 0, }, + { PINID_GPMI_D00, PIN_FUN3, PIN_4MA, PIN_3_3V, 0, }, + { PINID_GPMI_D03, PIN_FUN3, PIN_4MA, PIN_3_3V, 0, }, +#endif + }, +}; + +static struct pin_group ssp_pins[2] = { + [0] = { + .pins = ssp_pins_desc[0], + .nr_pins = ARRAY_SIZE(ssp_pins_desc[0]), + }, + [1] = { + .pins = ssp_pins_desc[1], + .nr_pins = ARRAY_SIZE(ssp_pins_desc[1]), + }, +}; + +int stmp37xx_spi_pins_request(char *id, int ssp) +{ + return stmp3xxx_request_pin_group(&ssp_pins[ssp-1], id); +} +EXPORT_SYMBOL_GPL(stmp37xx_spi_pins_request); + +void stmp37xx_spi_pins_release(char *id, int ssp) +{ + stmp3xxx_release_pin_group(&ssp_pins[ssp-1], id); +} +EXPORT_SYMBOL_GPL(stmp37xx_spi_pins_release); + +int stmp37xx_spi_enc_init(void *spi_dev) +{ + struct spi_device *spi = spi_dev; + struct stmp37xx_spi_platform_data *data = spi->dev.platform_data; + + gpio_request(data->irq_pin, dev_name(&spi->dev)); + gpio_direction_input(data->irq_pin); + set_irq_type(gpio_to_irq(data->irq_pin), IRQ_TYPE_EDGE_FALLING); + spi->irq = gpio_to_irq(data->irq_pin); + dev_dbg(&spi->dev, "Assigned IRQ %d(%s)\n", spi->irq, __func__); + return 0; +} + +int stmp37xx_spi_enc_release(void *spi_dev) +{ + struct spi_device *spi = spi_dev; + struct stmp37xx_spi_platform_data *data = spi->dev.platform_data; + + set_irq_type(data->irq_pin, IRQ_TYPE_NONE); + gpio_free(data->irq_pin); + return 0; +} + +MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-stmp3xxx/unique-id.c b/arch/arm/plat-stmp3xxx/unique-id.c new file mode 100644 index 000000000000..b25cab17555a --- /dev/null +++ b/arch/arm/plat-stmp3xxx/unique-id.c @@ -0,0 +1,198 @@ +/* + * Unique ID manipulation sysfs access generic functions + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kobject.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/err.h> +#include <linux/timer.h> +#include <linux/spinlock.h> + +#include <mach/unique-id.h> + +static int unlock; +static spinlock_t u_lock; +static const unsigned long UID_AUTOLOCK_TIMEOUT = HZ * 60 * 3; +static struct timer_list u_timer; + +static void uid_timer_autolock(unsigned long param) +{ + struct timer_list *tmr = (struct timer_list *)param; + + if (spin_trylock(&u_lock)) { + if (unlock) + pr_debug("%s: locked down.\n", __func__); + unlock = 0; + spin_unlock(&u_lock); + } + mod_timer(tmr, jiffies + UID_AUTOLOCK_TIMEOUT); +} + +static LIST_HEAD(uid_provider_list); + +struct uid_provider { + struct kobject *kobj; + struct list_head list; + struct uid_ops *ops; + void *context; +}; + +static struct uid_provider *uid_provider_find(const char *name); + +#define UID_FWD_SYSFS_FILE(var, file, param) \ + static ssize_t var##_show(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf) \ + { \ + struct uid_provider *p = \ + uid_provider_find(kobject_name(kobj)); \ + ssize_t r; \ + BUG_ON(p == NULL); \ + r = (p->ops && p->ops->file##_show) ? \ + p->ops->file##_show(p->context, buf, param) : 0;\ + return r; \ + } \ + \ + static ssize_t var##_store(struct kobject *kobj, \ + struct kobj_attribute *attr, const char *buf, \ + size_t count) \ + { \ + struct uid_provider *p = \ + uid_provider_find(kobject_name(kobj)); \ + ssize_t r; \ + int ul; \ + BUG_ON(p == NULL); \ + spin_lock(&u_lock); \ + ul = unlock; \ + spin_unlock(&u_lock); \ + if (ul) \ + r = (p->ops && p->ops->file##_store) ? \ + p->ops->file##_store(p->context, buf, count, param) \ + : count; \ + else \ + r = -EACCES; \ + return r; \ + } + +struct kobject *uid_kobj; + +#define UID_ATTR(_name, _varname) \ + static struct kobj_attribute _varname##_attr = \ + __ATTR(_name, 0644, _varname##_show, _varname##_store) + +UID_FWD_SYSFS_FILE(id, id, 1); +UID_FWD_SYSFS_FILE(id_bin, id, 0); +UID_ATTR(id, id); +UID_ATTR(id.bin, id_bin); + +static struct attribute *uid_attrs[] = { + &id_attr.attr, + &id_bin_attr.attr, + NULL +}; + +static struct attribute_group uid_attr_group = { + .attrs = uid_attrs, +}; + +struct kobject *uid_provider_init(const char *name, + struct uid_ops *ops, void *context) +{ + struct uid_provider *new; + int err; + + new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!new) { + err = -ENOMEM; + goto out; + } + + new->kobj = kobject_create_and_add(name, uid_kobj); + if (!new->kobj) { + err = -ENOMEM; + goto out; + } + new->ops = ops; + new->context = context; + + err = sysfs_create_group(new->kobj, &uid_attr_group); + if (err) + goto out2; + + list_add_tail(&new->list, &uid_provider_list); + return new->kobj; +out2: + kobject_del(new->kobj); +out: + kfree(new); + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(uid_provider_init); + +static struct uid_provider *uid_provider_find(const char *name) +{ + struct uid_provider *p; + + list_for_each_entry(p, &uid_provider_list, list) { + if (strcmp(kobject_name(p->kobj), name) == 0) + return p; + } + return NULL; +} + +void uid_provider_remove(const char *name) +{ + struct uid_provider *p; + + p = uid_provider_find(name); + if (!p) + return; + kobject_del(p->kobj); + list_del(&p->list); + kfree(p); +} +EXPORT_SYMBOL_GPL(uid_provider_remove); + +static int uid_sysfs_init(void) +{ + int error; + + uid_kobj = kobject_create_and_add("uid", NULL); + if (!uid_kobj) { + error = -ENOMEM; + goto out1; + } + + spin_lock_init(&u_lock); + setup_timer(&u_timer, uid_timer_autolock, (unsigned long)&u_timer); + + /* try to lock each 3 minutes */ + mod_timer(&u_timer, jiffies + UID_AUTOLOCK_TIMEOUT); + return 0; + +out1: + printk(KERN_ERR"%s failed, error %d.", __func__, error); + return error; +} + +module_param(unlock, int, 0600) +core_initcall(uid_sysfs_init); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>"); +MODULE_DESCRIPTION("Unique ID simple framework"); diff --git a/drivers/Makefile b/drivers/Makefile index bc4205d2fc3c..4a9a1c55b598 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_INPUT) += input/ obj-$(CONFIG_I2O) += message/ obj-$(CONFIG_RTC_LIB) += rtc/ obj-y += i2c/ media/ +obj-$(CONFIG_I2C_SLAVE) += i2c-slave/ obj-$(CONFIG_PPS) += pps/ obj-$(CONFIG_W1) += w1/ obj-$(CONFIG_POWER_SUPPLY) += power/ @@ -90,6 +91,7 @@ obj-y += lguest/ obj-$(CONFIG_CPU_FREQ) += cpufreq/ obj-$(CONFIG_CPU_IDLE) += cpuidle/ obj-y += idle/ +obj-$(CONFIG_ARCH_MXC) += mxc/ obj-$(CONFIG_MMC) += mmc/ obj-$(CONFIG_MEMSTICK) += memstick/ obj-$(CONFIG_NEW_LEDS) += leds/ diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig index b17c57f85032..589039639525 100644 --- a/drivers/ata/Kconfig +++ b/drivers/ata/Kconfig @@ -751,5 +751,14 @@ config PATA_BF54X If unsure, say N. +config PATA_FSL + tristate "Freescale on-chip PATA support" + depends on (ARCH_MX51 || ARCH_MX37 || ARCH_MX35 || ARCH_MX3 || ARCH_MX27) + help + On Freescale processors, say Y here if you wish to use the on-chip + ATA interface. + If you are unsure, say N to this. + endif # ATA_SFF + endif # ATA diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile index 38906f9bbb4e..de9fda120642 100644 --- a/drivers/ata/Makefile +++ b/drivers/ata/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_PATA_PLATFORM) += pata_platform.o obj-$(CONFIG_PATA_AT91) += pata_at91.o obj-$(CONFIG_PATA_OF_PLATFORM) += pata_of_platform.o obj-$(CONFIG_PATA_ICSIDE) += pata_icside.o +obj-$(CONFIG_PATA_FSL) += pata_fsl.o # Should be last but two libata driver obj-$(CONFIG_PATA_ACPI) += pata_acpi.o # Should be last but one libata driver diff --git a/drivers/ata/pata_fsl.c b/drivers/ata/pata_fsl.c new file mode 100644 index 000000000000..c1d05282da03 --- /dev/null +++ b/drivers/ata/pata_fsl.c @@ -0,0 +1,1042 @@ +/* + * Freescale integrated PATA driver + */ + +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/blkdev.h> +#include <scsi/scsi_host.h> +#include <linux/ata.h> +#include <linux/libata.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> +#include <mach/dma.h> + +#define DRV_NAME "pata_fsl" + +struct pata_fsl_priv { + int ultra; + u8 *fsl_ata_regs; + struct clk *clk; + int dma_rchan; + int dma_wchan; + int dma_done; + int dma_dir; + unsigned int adma_des_paddr; + unsigned int *adma_des_tp; +}; + +struct adma_bd { + unsigned char *sg_buf; + unsigned char *work_buf; + unsigned int dma_address; + int length; +}; + +struct adma_bulk { + struct adma_bd adma_bd_table[64]; + struct ata_queued_cmd *qc; + int sg_ents; + int reserved[2]; +}; + +enum { + /* various constants */ + FSL_ATA_MAX_SG_LEN = ATA_DMA_BOUNDARY << 1, + + /* offsets to registers */ + FSL_ATA_TIMING_REGS = 0x00, + FSL_ATA_FIFO_FILL = 0x20, + FSL_ATA_CONTROL = 0x24, + FSL_ATA_INT_PEND = 0x28, + FSL_ATA_INT_EN = 0x2C, + FSL_ATA_INT_CLEAR = 0x30, + FSL_ATA_FIFO_ALARM = 0x34, + FSL_ATA_ADMA_ERROR_STATUS = 0x38, + FSL_ATA_SYS_DMA_BADDR = 0x3C, + FSL_ATA_ADMA_SYS_ADDR = 0x40, + FSL_ATA_BLOCK_COUNT = 0x48, + FSL_ATA_BURST_LENGTH = 0x4C, + FSL_ATA_SECTOR_SIZE = 0x50, + FSL_ATA_DRIVE_DATA = 0xA0, + FSL_ATA_DRIVE_CONTROL = 0xD8, + + /* bits within FSL_ATA_CONTROL */ + FSL_ATA_CTRL_DMA_SRST = 0x1000, + FSL_ATA_CTRL_DMA_64ADMA = 0x800, + FSL_ATA_CTRL_DMA_32ADMA = 0x400, + FSL_ATA_CTRL_DMA_STAT_STOP = 0x200, + FSL_ATA_CTRL_DMA_ENABLE = 0x100, + FSL_ATA_CTRL_FIFO_RST_B = 0x80, + FSL_ATA_CTRL_ATA_RST_B = 0x40, + FSL_ATA_CTRL_FIFO_TX_EN = 0x20, + FSL_ATA_CTRL_FIFO_RCV_EN = 0x10, + FSL_ATA_CTRL_DMA_PENDING = 0x08, + FSL_ATA_CTRL_DMA_ULTRA = 0x04, + FSL_ATA_CTRL_DMA_WRITE = 0x02, + FSL_ATA_CTRL_IORDY_EN = 0x01, + + /* bits within the interrupt control registers */ + FSL_ATA_INTR_ATA_INTRQ1 = 0x80, + FSL_ATA_INTR_FIFO_UNDERFLOW = 0x40, + FSL_ATA_INTR_FIFO_OVERFLOW = 0x20, + FSL_ATA_INTR_CTRL_IDLE = 0x10, + FSL_ATA_INTR_ATA_INTRQ2 = 0x08, + FSL_ATA_INTR_DMA_ERR = 0x04, + FSL_ATA_INTR_DMA_TRANS_OVER = 0x02, + + /* ADMA Addr Descriptor Attribute Filed */ + FSL_ADMA_DES_ATTR_VALID = 0x01, + FSL_ADMA_DES_ATTR_END = 0x02, + FSL_ADMA_DES_ATTR_INT = 0x04, + FSL_ADMA_DES_ATTR_SET = 0x10, + FSL_ADMA_DES_ATTR_TRAN = 0x20, + FSL_ADMA_DES_ATTR_LINK = 0x30, +}; + +/* + * This structure contains the timing parameters for + * ATA bus timing in the 5 PIO modes. The timings + * are in nanoseconds, and are converted to clock + * cycles before being stored in the ATA controller + * timing registers. + */ +static struct { + short t0, t1, t2_8, t2_16, t2i, t4, t9, tA; +} pio_specs[] = { + [0] = { + .t0 = 600, .t1 = 70, .t2_8 = 290, .t2_16 = 165, .t2i = 0, .t4 = + 30, .t9 = 20, .tA = 50,}, + [1] = { + .t0 = 383, .t1 = 50, .t2_8 = 290, .t2_16 = 125, .t2i = 0, .t4 = + 20, .t9 = 15, .tA = 50,}, + [2] = { + .t0 = 240, .t1 = 30, .t2_8 = 290, .t2_16 = 100, .t2i = 0, .t4 = + 15, .t9 = 10, .tA = 50,}, + [3] = { + .t0 = 180, .t1 = 30, .t2_8 = 80, .t2_16 = 80, .t2i = 0, .t4 = + 10, .t9 = 10, .tA = 50,}, + [4] = { + .t0 = 120, .t1 = 25, .t2_8 = 70, .t2_16 = 70, .t2i = 0, .t4 = + 10, .t9 = 10, .tA = 50,}, + }; + +#define NR_PIO_SPECS (sizeof pio_specs / sizeof pio_specs[0]) + +/* + * This structure contains the timing parameters for + * ATA bus timing in the 3 MDMA modes. The timings + * are in nanoseconds, and are converted to clock + * cycles before being stored in the ATA controller + * timing registers. + */ +static struct { + short t0M, tD, tH, tJ, tKW, tM, tN, tJNH; +} mdma_specs[] = { + [0] = { + .t0M = 480, .tD = 215, .tH = 20, .tJ = 20, .tKW = 215, .tM = 50, .tN = + 15, .tJNH = 20,}, + [1] = { + .t0M = 150, .tD = 80, .tH = 15, .tJ = 5, .tKW = 50, .tM = 30, .tN = + 10, .tJNH = 15,}, + [2] = { + .t0M = 120, .tD = 70, .tH = 10, .tJ = 5, .tKW = 25, .tM = 25, .tN = + 10, .tJNH = 10,}, + }; + +#define NR_MDMA_SPECS (sizeof mdma_specs / sizeof mdma_specs[0]) + +/* + * This structure contains the timing parameters for + * ATA bus timing in the 6 UDMA modes. The timings + * are in nanoseconds, and are converted to clock + * cycles before being stored in the ATA controller + * timing registers. + */ +static struct { + short t2CYC, tCYC, tDS, tDH, tDVS, tDVH, tCVS, tCVH, tFS_min, tLI_max, + tMLI, tAZ, tZAH, tENV_min, tSR, tRFS, tRP, tACK, tSS, tDZFS; +} udma_specs[] = { + [0] = { + .t2CYC = 235, .tCYC = 114, .tDS = 15, .tDH = 5, .tDVS = 70, .tDVH = + 6, .tCVS = 70, .tCVH = 6, .tFS_min = 0, .tLI_max = + 100, .tMLI = 20, .tAZ = 10, .tZAH = 20, .tENV_min = + 20, .tSR = 50, .tRFS = 75, .tRP = 160, .tACK = 20, .tSS = + 50, .tDZFS = 80,}, + [1] = { + .t2CYC = 156, .tCYC = 75, .tDS = 10, .tDH = 5, .tDVS = 48, .tDVH = + 6, .tCVS = 48, .tCVH = 6, .tFS_min = 0, .tLI_max = + 100, .tMLI = 20, .tAZ = 10, .tZAH = 20, .tENV_min = + 20, .tSR = 30, .tRFS = 70, .tRP = 125, .tACK = 20, .tSS = + 50, .tDZFS = 63,}, + [2] = { + .t2CYC = 117, .tCYC = 55, .tDS = 7, .tDH = 5, .tDVS = 34, .tDVH = + 6, .tCVS = 34, .tCVH = 6, .tFS_min = 0, .tLI_max = + 100, .tMLI = 20, .tAZ = 10, .tZAH = 20, .tENV_min = + 20, .tSR = 20, .tRFS = 60, .tRP = 100, .tACK = 20, .tSS = + 50, .tDZFS = 47,}, + [3] = { + .t2CYC = 86, .tCYC = 39, .tDS = 7, .tDH = 5, .tDVS = 20, .tDVH = + 6, .tCVS = 20, .tCVH = 6, .tFS_min = 0, .tLI_max = + 100, .tMLI = 20, .tAZ = 10, .tZAH = 20, .tENV_min = + 20, .tSR = 20, .tRFS = 60, .tRP = 100, .tACK = 20, .tSS = + 50, .tDZFS = 35,}, + [4] = { + .t2CYC = 57, .tCYC = 25, .tDS = 5, .tDH = 5, .tDVS = 7, .tDVH = + 6, .tCVS = 7, .tCVH = 6, .tFS_min = 0, .tLI_max = + 100, .tMLI = 20, .tAZ = 10, .tZAH = 20, .tENV_min = + 20, .tSR = 50, .tRFS = 60, .tRP = 100, .tACK = 20, .tSS = + 50, .tDZFS = 25,}, + [5] = { + .t2CYC = 38, .tCYC = 17, .tDS = 4, .tDH = 5, .tDVS = 5, .tDVH = + 6, .tCVS = 10, .tCVH = 10, .tFS_min = + 0, .tLI_max = 75, .tMLI = 20, .tAZ = 10, .tZAH = + 20, .tENV_min = 20, .tSR = 20, .tRFS = + 50, .tRP = 85, .tACK = 20, .tSS = 50, .tDZFS = 40,}, +}; + +#define NR_UDMA_SPECS (sizeof udma_specs / sizeof udma_specs[0]) + +struct fsl_ata_time_regs { + u8 time_off, time_on, time_1, time_2w; + u8 time_2r, time_ax, time_pio_rdx, time_4; + u8 time_9, time_m, time_jn, time_d; + u8 time_k, time_ack, time_env, time_rpx; + u8 time_zah, time_mlix, time_dvh, time_dzfs; + u8 time_dvs, time_cvh, time_ss, time_cyc; +}; + +static struct regulator *io_reg; +static struct regulator *core_reg; +static struct adma_bulk adma_info; + +static void +update_timing_config(struct fsl_ata_time_regs *tp, struct ata_host *host) +{ + u32 *lp = (u32 *) tp; + struct pata_fsl_priv *priv = host->private_data; + u32 *ctlp = (u32 *) priv->fsl_ata_regs; + int i; + + for (i = 0; i < 5; i++) { + __raw_writel(*lp, ctlp); + lp++; + ctlp++; + } +} + +/*! + * Calculate values for the ATA bus timing registers and store + * them into the hardware. + * + * @param xfer_mode specifies XFER xfer_mode + * @param pdev specifies platform_device + * + * @return EINVAL speed out of range, or illegal mode + */ +static int set_ata_bus_timing(u8 xfer_mode, struct platform_device *pdev) +{ + struct ata_host *host = dev_get_drvdata(&pdev->dev); + struct pata_fsl_priv *priv = host->private_data; + + /* get the bus clock cycle time, in ns */ + int T = 1 * 1000 * 1000 * 1000 / clk_get_rate(priv->clk); + struct fsl_ata_time_regs tr = { 0 }; + + /* + * every mode gets the same t_off and t_on + */ + tr.time_off = 3; + tr.time_on = 3; + + if (xfer_mode >= XFER_UDMA_0) { + int speed = xfer_mode - XFER_UDMA_0; + if (speed >= NR_UDMA_SPECS) + return -EINVAL; + + tr.time_ack = (udma_specs[speed].tACK + T) / T; + tr.time_env = (udma_specs[speed].tENV_min + T) / T; + tr.time_rpx = (udma_specs[speed].tRP + T) / T + 2; + tr.time_zah = (udma_specs[speed].tZAH + T) / T; + tr.time_mlix = (udma_specs[speed].tMLI + T) / T; + tr.time_dvh = (udma_specs[speed].tDVH + T) / T + 1; + tr.time_dzfs = (udma_specs[speed].tDZFS + T) / T; + + tr.time_dvs = (udma_specs[speed].tDVS + T) / T; + tr.time_cvh = (udma_specs[speed].tCVH + T) / T; + tr.time_ss = (udma_specs[speed].tSS + T) / T; + tr.time_cyc = (udma_specs[speed].tCYC + T) / T; + } else if (xfer_mode >= XFER_MW_DMA_0) { + int speed = xfer_mode - XFER_MW_DMA_0; + if (speed >= NR_MDMA_SPECS) + return -EINVAL; + + tr.time_m = (mdma_specs[speed].tM + T) / T; + tr.time_jn = (mdma_specs[speed].tJNH + T) / T; + tr.time_d = (mdma_specs[speed].tD + T) / T; + + tr.time_k = (mdma_specs[speed].tKW + T) / T; + } else { + int speed = xfer_mode - XFER_PIO_0; + if (speed >= NR_PIO_SPECS) + return -EINVAL; + + tr.time_1 = (pio_specs[speed].t1 + T) / T; + tr.time_2w = (pio_specs[speed].t2_8 + T) / T; + + tr.time_2r = (pio_specs[speed].t2_8 + T) / T; + tr.time_ax = (pio_specs[speed].tA + T) / T + 2; + tr.time_pio_rdx = 1; + tr.time_4 = (pio_specs[speed].t4 + T) / T; + + tr.time_9 = (pio_specs[speed].t9 + T) / T; + } + + update_timing_config(&tr, host); + + return 0; +} + +static void pata_fsl_set_piomode(struct ata_port *ap, struct ata_device *adev) +{ + set_ata_bus_timing(adev->pio_mode, to_platform_device(ap->dev)); +} + +static void pata_fsl_set_dmamode(struct ata_port *ap, struct ata_device *adev) +{ + struct pata_fsl_priv *priv = ap->host->private_data; + + priv->ultra = adev->dma_mode >= XFER_UDMA_0; + + set_ata_bus_timing(adev->dma_mode, to_platform_device(ap->dev)); +} + +static int pata_fsl_port_start(struct ata_port *ap) +{ + return 0; +} + +static void pata_adma_bulk_unmap(struct ata_queued_cmd *qc) +{ + int i; + struct adma_bd *bdp = adma_info.adma_bd_table; + if (adma_info.qc == NULL) + return; + BUG_ON(adma_info.qc != qc); + + adma_info.qc = NULL; + + for (i = 0; i < adma_info.sg_ents; i++) { + if (bdp->work_buf != bdp->sg_buf) { + if (qc->dma_dir == DMA_FROM_DEVICE) { + memcpy(bdp->sg_buf, bdp->work_buf, bdp->length); + dma_cache_maint(bdp->sg_buf, bdp->length, + DMA_FROM_DEVICE); + } + dma_free_coherent(qc->ap->dev, bdp->length, + bdp->work_buf, bdp->dma_address); + } + bdp->work_buf = bdp->sg_buf = NULL; + bdp++; + } +} + +static int pata_adma_bulk_map(struct ata_queued_cmd *qc) +{ + unsigned int si; + struct scatterlist *sg; + struct adma_bd *bdp = adma_info.adma_bd_table; + + BUG_ON(adma_info.qc); + + adma_info.qc = qc; + adma_info.sg_ents = 0; + + for_each_sg(qc->sg, sg, qc->n_elem, si) { + /* + * The ADMA mode is used setup the ADMA descriptor table + */ + bdp->sg_buf = sg_virt(sg); + bdp->length = sg->length; + if (sg->dma_address & 0xFFF) { + bdp->work_buf = + dma_alloc_coherent(qc->ap->dev, bdp->length, + &bdp->dma_address, GFP_KERNEL); + if (!bdp->work_buf) { + printk(KERN_WARNING + "can not allocate aligned buffer\n"); + goto fail; + } + if (qc->dma_dir == DMA_TO_DEVICE) + memcpy(bdp->work_buf, bdp->sg_buf, bdp->length); + } else { + bdp->work_buf = bdp->sg_buf; + bdp->dma_address = sg->dma_address; + } + + adma_info.sg_ents++; + bdp++; + } + return 0; + fail: + pata_adma_bulk_unmap(qc); + return -1; +} + +static void dma_callback(void *arg, int error_status, unsigned int count) +{ + struct ata_port *ap = arg; + struct pata_fsl_priv *priv = ap->host->private_data; + u8 *ata_regs = priv->fsl_ata_regs; + + priv->dma_done = 1; + /* + * DMA is finished, so unmask INTRQ from the drive to allow the + * normal ISR to fire. + */ + __raw_writel(FSL_ATA_INTR_ATA_INTRQ2, ata_regs + FSL_ATA_INT_EN); +} + +static irqreturn_t pata_fsl_adma_intr(int irq, void *dev_instance) +{ + struct ata_host *host = dev_instance; + struct pata_fsl_priv *priv = host->private_data; + u8 *ata_regs = priv->fsl_ata_regs; + unsigned int handled = 0; + unsigned int i; + unsigned long flags; + unsigned int pending = __raw_readl(ata_regs + FSL_ATA_INT_PEND); + + if (FSL_ATA_INTR_DMA_TRANS_OVER & pending) { + priv->dma_done = 1; + __raw_writel(pending, ata_regs + FSL_ATA_INT_CLEAR); + handled = 1; + } else if (FSL_ATA_INTR_DMA_ERR & pending) { + printk(KERN_ERR "dma err status 0x%x ...\n", + __raw_readl(ata_regs + FSL_ATA_ADMA_ERROR_STATUS)); + __raw_writel(pending, ata_regs + FSL_ATA_INT_CLEAR); + handled = 1; + i = __raw_readl(ata_regs + FSL_ATA_CONTROL) && 0xFF; + i |= FSL_ATA_CTRL_DMA_SRST | FSL_ATA_CTRL_DMA_32ADMA | + FSL_ATA_CTRL_DMA_ENABLE; + __raw_writel(i, ata_regs + FSL_ATA_CONTROL); + } + + spin_lock_irqsave(&host->lock, flags); + + for (i = 0; i < host->n_ports; i++) { + struct ata_port *ap; + + ap = host->ports[i]; + if (ap && !(ap->flags & ATA_FLAG_DISABLED)) { + struct ata_queued_cmd *qc; + + qc = ata_qc_from_tag(ap, ap->link.active_tag); + raw_local_irq_restore(flags); + pata_adma_bulk_unmap(qc); + raw_local_irq_save(flags); + if (qc && (!(qc->tf.flags & ATA_TFLAG_POLLING)) && + (qc->flags & ATA_QCFLAG_ACTIVE)) + handled |= ata_sff_host_intr(ap, qc); + } + } + + spin_unlock_irqrestore(&host->lock, flags); + + return IRQ_RETVAL(handled); +} + +static int pata_fsl_check_atapi_dma(struct ata_queued_cmd *qc) +{ + return 1; /* ATAPI DMA not yet supported */ +} + +unsigned long pata_fsl_bmdma_mode_filter(struct ata_device *adev, + unsigned long xfer_mask) +{ + /* Capability of the controller has been specified in the + * platform data. Do not filter any modes, just return + * the xfer_mask */ + return xfer_mask; +} + +static void pata_fsl_bmdma_setup(struct ata_queued_cmd *qc) +{ + int chan, i; + int dma_mode = 0, dma_ultra; + u32 ata_control; + struct ata_port *ap = qc->ap; + struct pata_fsl_priv *priv = ap->host->private_data; + u8 *ata_regs = priv->fsl_ata_regs; + struct fsl_ata_platform_data *plat = ap->dev->platform_data; + int err; + unsigned int si; + + priv->dma_dir = qc->dma_dir; + + /* + * Configure the on-chip ATA interface hardware. + */ + dma_ultra = priv->ultra ? FSL_ATA_CTRL_DMA_ULTRA : 0; + + ata_control = FSL_ATA_CTRL_FIFO_RST_B | + FSL_ATA_CTRL_ATA_RST_B | FSL_ATA_CTRL_DMA_PENDING | dma_ultra; + if (plat->adma_flag) + ata_control |= FSL_ATA_CTRL_DMA_32ADMA | + FSL_ATA_CTRL_DMA_ENABLE; + + if (qc->dma_dir == DMA_TO_DEVICE) { + chan = priv->dma_wchan; + ata_control |= FSL_ATA_CTRL_FIFO_TX_EN | FSL_ATA_CTRL_DMA_WRITE; + dma_mode = MXC_DMA_MODE_WRITE; + } else { + chan = priv->dma_rchan; + ata_control |= FSL_ATA_CTRL_FIFO_RCV_EN; + dma_mode = MXC_DMA_MODE_READ; + } + + __raw_writel(ata_control, ata_regs + FSL_ATA_CONTROL); + __raw_writel(plat->fifo_alarm, ata_regs + FSL_ATA_FIFO_ALARM); + + if (plat->adma_flag) { + i = FSL_ATA_INTR_DMA_TRANS_OVER | FSL_ATA_INTR_DMA_ERR; + __raw_writel(FSL_ATA_INTR_ATA_INTRQ2 | i, + ata_regs + FSL_ATA_INT_EN); + } else { + __raw_writel(FSL_ATA_INTR_ATA_INTRQ1, + ata_regs + FSL_ATA_INT_EN); + /* + * Set up the DMA completion callback. + */ + mxc_dma_callback_set(chan, dma_callback, (void *)ap); + } + + /* + * Copy the sg list to an array. + */ + if (plat->adma_flag) { + struct adma_bd *bdp = adma_info.adma_bd_table; + pata_adma_bulk_map(qc); + for (i = 0; i < adma_info.sg_ents; i++) { + priv->adma_des_tp[i << 1] = bdp->length << 12; + priv->adma_des_tp[i << 1] |= FSL_ADMA_DES_ATTR_SET; + priv->adma_des_tp[i << 1] |= FSL_ADMA_DES_ATTR_VALID; + priv->adma_des_tp[(i << 1) + 1] = bdp->dma_address; + priv->adma_des_tp[(i << 1) + 1] |= + FSL_ADMA_DES_ATTR_TRAN; + priv->adma_des_tp[(i << 1) + 1] |= + FSL_ADMA_DES_ATTR_VALID; + if (adma_info.sg_ents == (i + 1)) + priv->adma_des_tp[(i << 1) + 1] |= + FSL_ADMA_DES_ATTR_END; + bdp++; + } + __raw_writel((qc->nbytes / qc->sect_size), ata_regs + + FSL_ATA_BLOCK_COUNT); + __raw_writel(plat->fifo_alarm, ata_regs + FSL_ATA_BURST_LENGTH); + __raw_writel(priv->adma_des_paddr, + ata_regs + FSL_ATA_ADMA_SYS_ADDR); + } else { + int nr_sg = 0; + struct scatterlist tmp[64], *tsg, *sg; + tsg = tmp; + for_each_sg(qc->sg, sg, qc->n_elem, si) { + memcpy(tsg, sg, sizeof(*sg)); + tsg++; + nr_sg++; + } + err = mxc_dma_sg_config(chan, tmp, nr_sg, 0, dma_mode); + if (err) + printk(KERN_ERR "pata_fsl_bmdma_setup: error %d\n", + err); + } +} + +static void pata_fsl_bmdma_start(struct ata_queued_cmd *qc) +{ + struct ata_port *ap = qc->ap; + struct pata_fsl_priv *priv = ap->host->private_data; + u8 *ata_regs = priv->fsl_ata_regs; + struct fsl_ata_platform_data *plat = ap->dev->platform_data; + int chan; + int err; + unsigned i; + + if (1 == plat->adma_flag) { + i = FSL_ATA_CTRL_DMA_32ADMA | FSL_ATA_CTRL_DMA_ENABLE; + /* The adma mode is used, set dma_start_stop to 1 */ + __raw_writel(i | __raw_readl(ata_regs + FSL_ATA_CONTROL) | + FSL_ATA_CTRL_DMA_STAT_STOP, + ata_regs + FSL_ATA_CONTROL); + } else { + /* + * Start the channel. + */ + chan = qc->dma_dir == DMA_TO_DEVICE ? priv->dma_wchan : + priv->dma_rchan; + + err = mxc_dma_enable(chan); + if (err) + printk(KERN_ERR "%s: : error %d\n", __func__, err); + } + + priv->dma_done = 0; + + ata_sff_exec_command(ap, &qc->tf); +} + +static void pata_fsl_bmdma_stop(struct ata_queued_cmd *qc) +{ + struct ata_port *ap = qc->ap; + struct pata_fsl_priv *priv = ap->host->private_data; + u8 *ata_regs = priv->fsl_ata_regs; + struct fsl_ata_platform_data *plat = ap->dev->platform_data; + unsigned i; + + if (plat->adma_flag) { + /* The adma mode is used, set dma_start_stop to 0 */ + i = FSL_ATA_CTRL_DMA_32ADMA | FSL_ATA_CTRL_DMA_ENABLE; + __raw_writel((i | __raw_readl(ata_regs + FSL_ATA_CONTROL)) & + (~FSL_ATA_CTRL_DMA_STAT_STOP), + ata_regs + FSL_ATA_CONTROL); + } + + /* do a dummy read as in ata_bmdma_stop */ +#if 0 + ata_sff_dma_pause(ap); +#endif +} + +static u8 pata_fsl_bmdma_status(struct ata_port *ap) +{ + struct pata_fsl_priv *priv = ap->host->private_data; + + return priv->dma_done ? ATA_DMA_INTR : 0; +} + +static void pata_fsl_dma_init(struct ata_port *ap) +{ + struct pata_fsl_priv *priv = ap->host->private_data; + + priv->dma_rchan = -1; + priv->dma_wchan = -1; + + priv->dma_rchan = mxc_dma_request(MXC_DMA_ATA_RX, "MXC ATA RX"); + if (priv->dma_rchan < 0) { + dev_printk(KERN_ERR, ap->dev, "couldn't get RX DMA channel\n"); + goto err_out; + } + + priv->dma_wchan = mxc_dma_request(MXC_DMA_ATA_TX, "MXC ATA TX"); + if (priv->dma_wchan < 0) { + dev_printk(KERN_ERR, ap->dev, "couldn't get TX DMA channel\n"); + goto err_out; + } + + dev_printk(KERN_ERR, ap->dev, "rchan=%d wchan=%d\n", priv->dma_rchan, + priv->dma_wchan); + return; + + err_out: + ap->mwdma_mask = 0; + ap->udma_mask = 0; + mxc_dma_free(priv->dma_rchan); + mxc_dma_free(priv->dma_wchan); + kfree(priv); +} + +#if 0 +static u8 pata_fsl_irq_ack(struct ata_port *ap, unsigned int chk_drq) +{ + unsigned int bits = chk_drq ? ATA_BUSY | ATA_DRQ : ATA_BUSY; + u8 status; + + status = ata_sff_busy_wait(ap, bits, 1000); + if (status & bits) + if (ata_msg_err(ap)) + printk(KERN_ERR "abnormal status 0x%X\n", status); + + return status; +} +#endif + +static void ata_dummy_noret(struct ata_port *ap) +{ + return; +} + +static struct scsi_host_template pata_fsl_sht = { + .module = THIS_MODULE, + .name = DRV_NAME, + .ioctl = ata_scsi_ioctl, + .queuecommand = ata_scsi_queuecmd, + .can_queue = ATA_DEF_QUEUE, + .this_id = ATA_SHT_THIS_ID, + .sg_tablesize = LIBATA_MAX_PRD, + .cmd_per_lun = ATA_SHT_CMD_PER_LUN, + .emulated = ATA_SHT_EMULATED, + .use_clustering = ATA_SHT_USE_CLUSTERING, + .proc_name = DRV_NAME, + .dma_boundary = FSL_ATA_MAX_SG_LEN, + .slave_configure = ata_scsi_slave_config, + .slave_destroy = ata_scsi_slave_destroy, + .bios_param = ata_std_bios_param, +}; + +static struct ata_port_operations pata_fsl_port_ops = { + .inherits = &ata_bmdma_port_ops, + .set_piomode = pata_fsl_set_piomode, + .set_dmamode = pata_fsl_set_dmamode, + + .check_atapi_dma = pata_fsl_check_atapi_dma, + .cable_detect = ata_cable_unknown, + .mode_filter = pata_fsl_bmdma_mode_filter, + + .bmdma_setup = pata_fsl_bmdma_setup, + .bmdma_start = pata_fsl_bmdma_start, + .bmdma_stop = pata_fsl_bmdma_stop, + .bmdma_status = pata_fsl_bmdma_status, + + .qc_prep = ata_noop_qc_prep, + + .sff_data_xfer = ata_sff_data_xfer_noirq, + .sff_irq_clear = ata_dummy_noret, + .sff_irq_on = ata_sff_irq_on, + + .port_start = pata_fsl_port_start, +}; + +static void fsl_setup_port(struct ata_ioports *ioaddr) +{ + unsigned int shift = 2; + + ioaddr->data_addr = ioaddr->cmd_addr + (ATA_REG_DATA << shift); + ioaddr->error_addr = ioaddr->cmd_addr + (ATA_REG_ERR << shift); + ioaddr->feature_addr = ioaddr->cmd_addr + (ATA_REG_FEATURE << shift); + ioaddr->nsect_addr = ioaddr->cmd_addr + (ATA_REG_NSECT << shift); + ioaddr->lbal_addr = ioaddr->cmd_addr + (ATA_REG_LBAL << shift); + ioaddr->lbam_addr = ioaddr->cmd_addr + (ATA_REG_LBAM << shift); + ioaddr->lbah_addr = ioaddr->cmd_addr + (ATA_REG_LBAH << shift); + ioaddr->device_addr = ioaddr->cmd_addr + (ATA_REG_DEVICE << shift); + ioaddr->status_addr = ioaddr->cmd_addr + (ATA_REG_STATUS << shift); + ioaddr->command_addr = ioaddr->cmd_addr + (ATA_REG_CMD << shift); +} + +/** + * pata_fsl_probe - attach a platform interface + * @pdev: platform device + * + * Register a platform bus integrated ATA host controller + * + * The 3 platform device resources are used as follows: + * + * - I/O Base (IORESOURCE_MEM) virt. addr. of ATA controller regs + * - CTL Base (IORESOURCE_MEM) unused + * - IRQ (IORESOURCE_IRQ) platform IRQ assigned to ATA + * + */ +static int __devinit pata_fsl_probe(struct platform_device *pdev) +{ + int ret = 0; + struct resource *io_res; + struct ata_host *host; + struct ata_port *ap; + struct fsl_ata_platform_data *plat = (struct fsl_ata_platform_data *) + pdev->dev.platform_data; + struct pata_fsl_priv *priv; + u8 *ata_regs; + unsigned int int_enable; + + /* + * Set up resources + */ + if (unlikely(pdev->num_resources != 3)) { + dev_err(&pdev->dev, "invalid number of resources\n"); + return -EINVAL; + } + /* + * Get an ata_host structure for this device + */ + host = ata_host_alloc(&pdev->dev, 1); + if (!host) + return -ENOMEM; + ap = host->ports[0]; + + /* + * Allocate private data + */ + priv = kzalloc(sizeof(struct pata_fsl_priv), GFP_KERNEL); + if (priv == NULL) { + ret = -ENOMEM; + goto err0; + } + host->private_data = priv; + + /* + * Set up resources + */ + io_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ata_regs = + (u8 *) ioremap(io_res->start, io_res->end - io_res->start + 1); + priv->fsl_ata_regs = ata_regs; + ap->ioaddr.cmd_addr = (void *)(ata_regs + FSL_ATA_DRIVE_DATA); + ap->ioaddr.ctl_addr = (void *)(ata_regs + FSL_ATA_DRIVE_CONTROL); + ap->ioaddr.altstatus_addr = ap->ioaddr.ctl_addr; + ap->ops = &pata_fsl_port_ops; + ap->pio_mask = plat->pio_mask; /* support pio 0~4 */ + ap->mwdma_mask = plat->mwdma_mask; /* support mdma 0~2 */ + ap->udma_mask = plat->udma_mask; + pata_fsl_sht.sg_tablesize = plat->max_sg; + fsl_setup_port(&ap->ioaddr); + + if (plat->adma_flag) { + priv->adma_des_tp = + dma_alloc_coherent(&(pdev->dev), + (2 * plat->max_sg) * + sizeof(unsigned int), + &(priv->adma_des_paddr), GFP_DMA); + if (priv->adma_des_tp == NULL) { + ret = -ENOMEM; + goto err1; + } + } + /* + * Do platform-specific initialization (e.g. allocate pins, + * turn on clock). After this call it is assumed that + * plat->get_clk_rate() can be called to calculate + * timing. + */ + if (plat->init && plat->init(pdev)) { + ret = -ENODEV; + goto err2; + } + + priv->clk = clk_get(&pdev->dev, "ata_clk"); + clk_enable(priv->clk); + + /* Deassert the reset bit to enable the interface */ + __raw_writel(FSL_ATA_CTRL_ATA_RST_B, ata_regs + FSL_ATA_CONTROL); + + /* Enable Core regulator & IO Regulator */ + if (plat->core_reg != NULL) { + core_reg = regulator_get(&pdev->dev, plat->core_reg); + if (regulator_enable(core_reg)) + printk(KERN_INFO "enable core regulator error.\n"); + msleep(100); + + } else + core_reg = NULL; + + if (plat->io_reg != NULL) { + io_reg = regulator_get(&pdev->dev, plat->io_reg); + if (regulator_enable(io_reg)) + printk(KERN_INFO "enable io regulator error.\n"); + msleep(100); + + } else + io_reg = NULL; + + /* Set initial timing and mode */ + set_ata_bus_timing(XFER_PIO_4, pdev); + + /* get DMA ready */ + if (plat->adma_flag == 0) + pata_fsl_dma_init(ap); + + /* + * Enable the ATA INTRQ interrupt from the bus, but + * only allow the CPU to see it (INTRQ2) at this point. + * INTRQ1, which goes to the DMA, will be enabled later. + */ + int_enable = FSL_ATA_INTR_DMA_TRANS_OVER | FSL_ATA_INTR_DMA_ERR | + FSL_ATA_INTR_ATA_INTRQ2; + if (plat->adma_flag) + __raw_writel(int_enable, ata_regs + FSL_ATA_INT_EN); + else + __raw_writel(FSL_ATA_INTR_ATA_INTRQ2, + ata_regs + FSL_ATA_INT_EN); + + /* activate */ + if (plat->adma_flag) + ret = ata_host_activate(host, platform_get_irq(pdev, 0), + pata_fsl_adma_intr, 0, &pata_fsl_sht); + else + ret = ata_host_activate(host, platform_get_irq(pdev, 0), + ata_sff_interrupt, 0, &pata_fsl_sht); + + if (!ret) + return ret; + + clk_disable(priv->clk); + regulator_disable(core_reg); + regulator_disable(io_reg); + err2: + if (plat->adma_flag && priv->adma_des_tp) + dma_free_coherent(&(pdev->dev), + (2 * plat->max_sg + + 1) * sizeof(unsigned int), priv->adma_des_tp, + priv->adma_des_paddr); + err1: + iounmap(ata_regs); + kfree(priv); + err0: + ata_host_detach(host); + return ret; + +} + +/** + * pata_fsl_remove - unplug a platform interface + * @pdev: platform device + * + * A platform bus ATA device has been unplugged. Perform the needed + * cleanup. Also called on module unload for any active devices. + */ +static int __devexit pata_fsl_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ata_host *host = dev_get_drvdata(dev); + struct pata_fsl_priv *priv = host->private_data; + struct fsl_ata_platform_data *plat = (struct fsl_ata_platform_data *) + pdev->dev.platform_data; + u8 *ata_regs = priv->fsl_ata_regs; + + __raw_writel(0, ata_regs + FSL_ATA_INT_EN); /* Disable interrupts */ + + ata_host_detach(host); + + clk_disable(priv->clk); + clk_put(priv->clk); + priv->clk = NULL; + + /* Disable Core regulator & IO Regulator */ + if (plat->core_reg != NULL) { + regulator_disable(core_reg); + regulator_put(core_reg); + } + if (plat->io_reg != NULL) { + regulator_disable(io_reg); + regulator_put(io_reg); + } + + if (plat->exit) + plat->exit(); + + if (plat->adma_flag && priv->adma_des_tp) + dma_free_coherent(&(pdev->dev), + (2 * plat->max_sg) * + sizeof(unsigned int), priv->adma_des_tp, + priv->adma_des_paddr); + iounmap(ata_regs); + + kfree(priv); + + return 0; +} + +#ifdef CONFIG_PM +static int pata_fsl_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct ata_host *host = dev_get_drvdata(&pdev->dev); + struct pata_fsl_priv *priv = host->private_data; + struct fsl_ata_platform_data *plat = (struct fsl_ata_platform_data *) + pdev->dev.platform_data; + u8 *ata_regs = priv->fsl_ata_regs; + + ata_host_suspend(host, state); + + /* Disable interrupts. */ + __raw_writel(0, ata_regs + FSL_ATA_INT_EN); + + clk_disable(priv->clk); + + if (plat->exit) + plat->exit(); + + return 0; +} + +static int pata_fsl_resume(struct platform_device *pdev) +{ + struct ata_host *host = dev_get_drvdata(&pdev->dev); + struct pata_fsl_priv *priv = host->private_data; + struct fsl_ata_platform_data *plat = (struct fsl_ata_platform_data *) + pdev->dev.platform_data; + u8 *ata_regs = priv->fsl_ata_regs; + unsigned char int_enable; + + if (plat->init && plat->init(pdev)) + return -ENODEV; + + clk_enable(priv->clk); + + /* Deassert the reset bit to enable the interface */ + __raw_writel(FSL_ATA_CTRL_ATA_RST_B, ata_regs + FSL_ATA_CONTROL); + + /* Set initial timing and mode */ + set_ata_bus_timing(XFER_PIO_4, pdev); + + /* + * Enable hardware interrupts. + */ + int_enable = FSL_ATA_INTR_DMA_TRANS_OVER | FSL_ATA_INTR_DMA_ERR | + FSL_ATA_INTR_ATA_INTRQ2; + if (1 == plat->adma_flag) + __raw_writel(int_enable, ata_regs + FSL_ATA_INT_EN); + else + __raw_writel(FSL_ATA_INTR_ATA_INTRQ2, + ata_regs + FSL_ATA_INT_EN); + + ata_host_resume(host); + + return 0; +} +#endif + +static struct platform_driver pata_fsl_driver = { + .probe = pata_fsl_probe, + .remove = __devexit_p(pata_fsl_remove), +#ifdef CONFIG_PM + .suspend = pata_fsl_suspend, + .resume = pata_fsl_resume, +#endif + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pata_fsl_init(void) +{ + return platform_driver_register(&pata_fsl_driver); + + return 0; +} + +static void __exit pata_fsl_exit(void) +{ + platform_driver_unregister(&pata_fsl_driver); +} + +module_init(pata_fsl_init); +module_exit(pata_fsl_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("low-level driver for Freescale ATA"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 6a06913b01d3..bf04030c9d2b 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -431,6 +431,29 @@ config SGI_MBCS If you have an SGI Altix with an attached SABrick say Y or M here, otherwise say N. +config FM_SI4702 + tristate "SI4702 FM device driver" + depends on (MACH_MX31_3DS || MACH_MX35_3DS || MACH_MX37_3DS || MACH_MX51_3DS) + default n + +config MXC_IIM + tristate "MXC IIM device driver" + depends on ARCH_MXC + help + Support for access to MXC IIM device, most people should say N here. + +config MXS_VIIM + tristate "MXS Virtual IIM device driver" + depends on ARCH_STMP3XXX + help + Support for access to MXS Virtual IIM device, most people should say N here. + +config IMX_SIM + tristate "IMX SIM support" + depends on (ARCH_MX51 || MACH_MX25_3DS) + ---help--- + Say Y to enable the SIM driver support. + source "drivers/serial/Kconfig" config UNIX98_PTYS diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 66f779ad4f4c..c711f02de8f7 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -9,6 +9,11 @@ FONTMAPFILE = cp437.uni obj-y += mem.o random.o tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o tty_buffer.o tty_port.o +obj-$(CONFIG_FM_SI4702) += mxc_si4702.o +obj-$(CONFIG_MXC_IIM) += mxc_iim.o +obj-$(CONFIG_MXS_VIIM) += mxs_viim.o +obj-$(CONFIG_IMX_SIM) += imx_sim.o + obj-$(CONFIG_LEGACY_PTYS) += pty.o obj-$(CONFIG_UNIX98_PTYS) += pty.o obj-y += misc.o @@ -97,7 +102,6 @@ obj-$(CONFIG_NSC_GPIO) += nsc_gpio.o obj-$(CONFIG_CS5535_GPIO) += cs5535_gpio.o obj-$(CONFIG_GPIO_TB0219) += tb0219.o obj-$(CONFIG_TELCLOCK) += tlclk.o - obj-$(CONFIG_MWAVE) += mwave/ obj-$(CONFIG_AGP) += agp/ obj-$(CONFIG_PCMCIA) += pcmcia/ diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index ce66a70184f7..108a752c9132 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -60,6 +60,30 @@ config HW_RANDOM_AMD If unsure, say Y. +config HW_RANDOM_FSL_RNGA + tristate "Freescale RNGA Random Number Generator" + depends on HW_RANDOM && ARCH_HAS_RNGA && !MXC_SECURITY_RNG + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on Freescale i.MX processors. + + To compile this driver as a module, choose M here: the + module will be called fsl-rnga. + + If unsure, say Y. + +config HW_RANDOM_FSL_RNGC + tristate "Freescale RNGC Random Number Generator" + depends on HW_RANDOM && ARCH_HAS_RNGC && !MXC_SECURITY_RNG + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on Freescale i.MX processors. + + To compile this driver as a module, choose M here: the + module will be called fsl-rngc. + + If unsure, say Y. + config HW_RANDOM_GEODE tristate "AMD Geode HW Random Number Generator support" depends on HW_RANDOM && X86_32 && PCI diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index 676828ba8123..c6170069f998 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -7,6 +7,8 @@ rng-core-y := core.o obj-$(CONFIG_HW_RANDOM_TIMERIOMEM) += timeriomem-rng.o obj-$(CONFIG_HW_RANDOM_INTEL) += intel-rng.o obj-$(CONFIG_HW_RANDOM_AMD) += amd-rng.o +obj-$(CONFIG_HW_RANDOM_FSL_RNGA) += fsl-rnga.o +obj-$(CONFIG_HW_RANDOM_FSL_RNGC) += fsl-rngc.o obj-$(CONFIG_HW_RANDOM_GEODE) += geode-rng.o obj-$(CONFIG_HW_RANDOM_N2RNG) += n2-rng.o n2-rng-y := n2-drv.o n2-asm.o diff --git a/drivers/char/hw_random/fsl-rnga.c b/drivers/char/hw_random/fsl-rnga.c new file mode 100644 index 000000000000..a5c8065604d4 --- /dev/null +++ b/drivers/char/hw_random/fsl-rnga.c @@ -0,0 +1,238 @@ +/* + * RNG driver for Freescale RNGA + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG) + * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com> + * + * derived from + * + * Hardware driver for the AMD 768 Random Number Generator (RNG) + * (c) Copyright 2001 Red Hat Inc <alan@redhat.com> + * + * derived from + * + * Hardware driver for Intel i810 Random Number Generator (RNG) + * Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com> + * Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/hw_random.h> +#include <linux/io.h> + +/* RNGA Registers */ +#define RNGA_CONTROL 0x00 +#define RNGA_STATUS 0x04 +#define RNGA_ENTROPY 0x08 +#define RNGA_OUTPUT_FIFO 0x0c +#define RNGA_MODE 0x10 +#define RNGA_VERIFICATION_CONTROL 0x14 +#define RNGA_OSC_CONTROL_COUNTER 0x18 +#define RNGA_OSC1_COUNTER 0x1c +#define RNGA_OSC2_COUNTER 0x20 +#define RNGA_OSC_COUNTER_STATUS 0x24 + +/* RNGA Registers Range */ +#define RNG_ADDR_RANGE 0x28 + +/* RNGA Control Register */ +#define RNGA_CONTROL_SLEEP 0x00000010 +#define RNGA_CONTROL_CLEAR_INT 0x00000008 +#define RNGA_CONTROL_MASK_INTS 0x00000004 +#define RNGA_CONTROL_HIGH_ASSURANCE 0x00000002 +#define RNGA_CONTROL_GO 0x00000001 + +#define RNGA_STATUS_LEVEL_MASK 0x0000ff00 + +/* RNGA Status Register */ +#define RNGA_STATUS_OSC_DEAD 0x80000000 +#define RNGA_STATUS_SLEEP 0x00000010 +#define RNGA_STATUS_ERROR_INT 0x00000008 +#define RNGA_STATUS_FIFO_UNDERFLOW 0x00000004 +#define RNGA_STATUS_LAST_READ_STATUS 0x00000002 +#define RNGA_STATUS_SECURITY_VIOLATION 0x00000001 + +static struct platform_device *rng_dev; + +static int fsl_rnga_data_present(struct hwrng *rng) +{ + int level; + u32 rng_base = (u32) rng->priv; + + /* how many random numbers is in FIFO? [0-16] */ + level = ((__raw_readl(rng_base + RNGA_STATUS) & + RNGA_STATUS_LEVEL_MASK) >> 8); + + return level > 0 ? 1 : 0; +} + +static int fsl_rnga_data_read(struct hwrng *rng, u32 * data) +{ + int err; + u32 ctrl, rng_base = (u32) rng->priv; + + /* retrieve a random number from FIFO */ + *data = __raw_readl(rng_base + RNGA_OUTPUT_FIFO); + + /* some error while reading this random number? */ + err = __raw_readl(rng_base + RNGA_STATUS) & RNGA_STATUS_ERROR_INT; + + /* if error: clear error interrupt, but doesn't return random number */ + if (err) { + dev_dbg(&rng_dev->dev, "Error while reading random number!\n"); + ctrl = __raw_readl(rng_base + RNGA_CONTROL); + __raw_writel(ctrl | RNGA_CONTROL_CLEAR_INT, + rng_base + RNGA_CONTROL); + return 0; + } else + return 4; +} + +static int fsl_rnga_init(struct hwrng *rng) +{ + u32 ctrl, osc, rng_base = (u32) rng->priv; + + /* wake up */ + ctrl = __raw_readl(rng_base + RNGA_CONTROL); + __raw_writel(ctrl & ~RNGA_CONTROL_SLEEP, rng_base + RNGA_CONTROL); + + /* verify if oscillator is working */ + osc = __raw_readl(rng_base + RNGA_STATUS); + if (osc & RNGA_STATUS_OSC_DEAD) { + dev_err(&rng_dev->dev, "RNGA Oscillator is dead!\n"); + return -ENODEV; + } + + /* go running */ + ctrl = __raw_readl(rng_base + RNGA_CONTROL); + __raw_writel(ctrl | RNGA_CONTROL_GO, rng_base + RNGA_CONTROL); + + return 0; +} + +static void fsl_rnga_cleanup(struct hwrng *rng) +{ + u32 ctrl, rng_base = (u32) rng->priv; + + ctrl = __raw_readl(rng_base + RNGA_CONTROL); + + /* stop rnga */ + __raw_writel(ctrl & ~RNGA_CONTROL_GO, rng_base + RNGA_CONTROL); +} + +static struct hwrng fsl_rnga = { + .name = "fsl-rnga", + .init = fsl_rnga_init, + .cleanup = fsl_rnga_cleanup, + .data_present = fsl_rnga_data_present, + .data_read = fsl_rnga_data_read +}; + +static int __init fsl_rnga_probe(struct platform_device *pdev) +{ + int err = -ENODEV; + struct clk *clk; + struct resource *res, *mem; + void __iomem *rng_base = NULL; + + if (rng_dev) + return -EBUSY; + + clk = clk_get(NULL, "rng_clk"); + + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Could not get rng_clk!\n"); + err = PTR_ERR(clk); + return err; + } + + clk_enable(clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res) + return -ENOENT; + + mem = request_mem_region(res->start, res->end - res->start, pdev->name); + + if (mem == NULL) + return -EBUSY; + + dev_set_drvdata(&pdev->dev, mem); + rng_base = ioremap(res->start, res->end - res->start); + + fsl_rnga.priv = (unsigned long)rng_base; + + err = hwrng_register(&fsl_rnga); + if (err) { + dev_err(&pdev->dev, "FSL RNGA registering failed (%d)\n", err); + return err; + } + + rng_dev = pdev; + + dev_info(&pdev->dev, "FSL RNGA Registered.\n"); + + return 0; +} + +static int __exit fsl_rnga_remove(struct platform_device *pdev) +{ + struct resource *mem = dev_get_drvdata(&pdev->dev); + void __iomem *rng_base = (void __iomem *)fsl_rnga.priv; + + hwrng_unregister(&fsl_rnga); + + release_resource(mem); + + iounmap(rng_base); + + return 0; +} + +static struct platform_driver fsl_rnga_driver = { + .driver = { + .name = "fsl_rnga", + .owner = THIS_MODULE, + }, + .remove = __exit_p(fsl_rnga_remove), +}; + +static int __init mod_init(void) +{ + return platform_driver_probe(&fsl_rnga_driver, fsl_rnga_probe); +} + +static void __exit mod_exit(void) +{ + platform_driver_unregister(&fsl_rnga_driver); +} + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("H/W RNGA driver for i.MX"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/fsl-rngc.c b/drivers/char/hw_random/fsl-rngc.c new file mode 100644 index 000000000000..9bf78e846fa0 --- /dev/null +++ b/drivers/char/hw_random/fsl-rngc.c @@ -0,0 +1,372 @@ +/* + * RNG driver for Freescale RNGC + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG) + * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com> + * + * derived from + * + * Hardware driver for the AMD 768 Random Number Generator (RNG) + * (c) Copyright 2001 Red Hat Inc <alan@redhat.com> + * + * derived from + * + * Hardware driver for Intel i810 Random Number Generator (RNG) + * Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com> + * Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/hw_random.h> +#include <linux/io.h> +#include <asm/hardware.h> + +#define RNGC_VERSION_MAJOR3 3 + +#define RNGC_VERSION_ID 0x0000 +#define RNGC_COMMAND 0x0004 +#define RNGC_CONTROL 0x0008 +#define RNGC_STATUS 0x000C +#define RNGC_ERROR 0x0010 +#define RNGC_FIFO 0x0014 +#define RNGC_VERIF_CTRL 0x0020 +#define RNGC_OSC_CTRL_COUNT 0x0028 +#define RNGC_OSC_COUNT 0x002C +#define RNGC_OSC_COUNT_STATUS 0x0030 + +#define RNGC_VERID_ZEROS_MASK 0x0f000000 +#define RNGC_VERID_RNG_TYPE_MASK 0xf0000000 +#define RNGC_VERID_RNG_TYPE_SHIFT 28 +#define RNGC_VERID_CHIP_VERSION_MASK 0x00ff0000 +#define RNGC_VERID_CHIP_VERSION_SHIFT 16 +#define RNGC_VERID_VERSION_MAJOR_MASK 0x0000ff00 +#define RNGC_VERID_VERSION_MAJOR_SHIFT 8 +#define RNGC_VERID_VERSION_MINOR_MASK 0x000000ff +#define RNGC_VERID_VERSION_MINOR_SHIFT 0 + +#define RNGC_CMD_ZEROS_MASK 0xffffff8c +#define RNGC_CMD_SW_RST 0x00000040 +#define RNGC_CMD_CLR_ERR 0x00000020 +#define RNGC_CMD_CLR_INT 0x00000010 +#define RNGC_CMD_SEED 0x00000002 +#define RNGC_CMD_SELF_TEST 0x00000001 + +#define RNGC_CTRL_ZEROS_MASK 0xfffffc8c +#define RNGC_CTRL_CTL_ACC 0x00000200 +#define RNGC_CTRL_VERIF_MODE 0x00000100 +#define RNGC_CTRL_MASK_ERROR 0x00000040 + +#define RNGC_CTRL_MASK_DONE 0x00000020 +#define RNGC_CTRL_AUTO_SEED 0x00000010 +#define RNGC_CTRL_FIFO_UFLOW_MASK 0x00000003 +#define RNGC_CTRL_FIFO_UFLOW_SHIFT 0 + +#define RNGC_CTRL_FIFO_UFLOW_ZEROS_ERROR 0 +#define RNGC_CTRL_FIFO_UFLOW_ZEROS_ERROR2 1 +#define RNGC_CTRL_FIFO_UFLOW_BUS_XFR 2 +#define RNGC_CTRL_FIFO_UFLOW_ZEROS_INTR 3 + +#define RNGC_STATUS_ST_PF_MASK 0x00c00000 +#define RNGC_STATUS_ST_PF_SHIFT 22 +#define RNGC_STATUS_ST_PF_TRNG 0x00800000 +#define RNGC_STATUS_ST_PF_PRNG 0x00400000 +#define RNGC_STATUS_ERROR 0x00010000 +#define RNGC_STATUS_FIFO_SIZE_MASK 0x0000f000 +#define RNGC_STATUS_FIFO_SIZE_SHIFT 12 +#define RNGC_STATUS_FIFO_LEVEL_MASK 0x00000f00 +#define RNGC_STATUS_FIFO_LEVEL_SHIFT 8 +#define RNGC_STATUS_NEXT_SEED_DONE 0x00000040 +#define RNGC_STATUS_SEED_DONE 0x00000020 +#define RNGC_STATUS_ST_DONE 0x00000010 +#define RNGC_STATUS_RESEED 0x00000008 +#define RNGC_STATUS_SLEEP 0x00000004 +#define RNGC_STATUS_BUSY 0x00000002 +#define RNGC_STATUS_SEC_STATE 0x00000001 + +#define RNGC_ERROR_STATUS_ZEROS_MASK 0xffffffc0 +#define RNGC_ERROR_STATUS_BAD_KEY 0x00000040 +#define RNGC_ERROR_STATUS_RAND_ERR 0x00000020 +#define RNGC_ERROR_STATUS_FIFO_ERR 0x00000010 +#define RNGC_ERROR_STATUS_STAT_ERR 0x00000008 +#define RNGC_ERROR_STATUS_ST_ERR 0x00000004 +#define RNGC_ERROR_STATUS_OSC_ERR 0x00000002 +#define RNGC_ERROR_STATUS_LFSR_ERR 0x00000001 + +#define RNG_ADDR_RANGE 0x34 + +static DECLARE_COMPLETION(rng_self_testing); +static DECLARE_COMPLETION(rng_seed_done); + +static struct platform_device *rng_dev; + +int irq_rng; + +static int fsl_rngc_data_present(struct hwrng *rng) +{ + int level; + u32 rngc_base = (u32) rng->priv; + + /* how many random numbers are in FIFO? [0-16] */ + level = (__raw_readl(rngc_base + RNGC_STATUS) & + RNGC_STATUS_FIFO_LEVEL_MASK) >> RNGC_STATUS_FIFO_LEVEL_SHIFT; + + return level > 0 ? 1 : 0; +} + +static int fsl_rngc_data_read(struct hwrng *rng, u32 * data) +{ + int err; + u32 rngc_base = (u32) rng->priv; + + /* retrieve a random number from FIFO */ + *data = __raw_readl(rngc_base + RNGC_FIFO); + + /* is there some error while reading this random number? */ + err = __raw_readl(rngc_base + RNGC_STATUS) & RNGC_STATUS_ERROR; + + /* if error happened doesn't return random number */ + return err ? 0 : 4; +} + +static irqreturn_t rngc_irq(int irq, void *dev) +{ + int handled = 0; + u32 rngc_base = (u32) dev; + + /* is the seed creation done? */ + if (__raw_readl(rngc_base + RNGC_STATUS) & RNGC_STATUS_SEED_DONE) { + complete(&rng_seed_done); + __raw_writel(RNGC_CMD_CLR_INT | RNGC_CMD_CLR_ERR, + rngc_base + RNGC_COMMAND); + handled = 1; + } + + /* is the self test done? */ + if (__raw_readl(rngc_base + RNGC_STATUS) & RNGC_STATUS_ST_DONE) { + complete(&rng_self_testing); + __raw_writel(RNGC_CMD_CLR_INT | RNGC_CMD_CLR_ERR, + rngc_base + RNGC_COMMAND); + handled = 1; + } + + /* is there any error? */ + if (__raw_readl(rngc_base + RNGC_STATUS) & RNGC_STATUS_ERROR) { + /* clear interrupt */ + __raw_writel(RNGC_CMD_CLR_INT | RNGC_CMD_CLR_ERR, + rngc_base + RNGC_COMMAND); + handled = 1; + } + + return handled; +} + +static int fsl_rngc_init(struct hwrng *rng) +{ + int err; + u32 cmd, ctrl, osc, rngc_base = (u32) rng->priv; + + INIT_COMPLETION(rng_self_testing); + INIT_COMPLETION(rng_seed_done); + + err = __raw_readl(rngc_base + RNGC_STATUS) & RNGC_STATUS_ERROR; + if (err) { + /* is this a bad keys error ? */ + if (__raw_readl(rngc_base + RNGC_ERROR) & + RNGC_ERROR_STATUS_BAD_KEY) { + dev_err(&rng_dev->dev, "Can't start, Bad Keys.\n"); + return -EIO; + } + } + + /* mask all interrupts, will be unmasked soon */ + ctrl = __raw_readl(rngc_base + RNGC_CONTROL); + __raw_writel(ctrl | RNGC_CTRL_MASK_DONE | RNGC_CTRL_MASK_ERROR, + rngc_base + RNGC_CONTROL); + + /* verify if oscillator is working */ + osc = __raw_readl(rngc_base + RNGC_ERROR); + if (osc & RNGC_ERROR_STATUS_OSC_ERR) { + dev_err(&rng_dev->dev, "RNGC Oscillator is dead!\n"); + return -EIO; + } + + err = request_irq(irq_rng, rngc_irq, 0, "fsl_rngc", (void *)rng->priv); + if (err) { + dev_err(&rng_dev->dev, "Can't get interrupt working.\n"); + return -EIO; + } + + /* do self test, repeat until get success */ + do { + /* clear error */ + cmd = __raw_readl(rngc_base + RNGC_COMMAND); + __raw_writel(cmd | RNGC_CMD_CLR_ERR, rngc_base + RNGC_COMMAND); + + /* unmask all interrupt */ + ctrl = __raw_readl(rngc_base + RNGC_CONTROL); + __raw_writel(ctrl & ~(RNGC_CTRL_MASK_DONE | + RNGC_CTRL_MASK_ERROR), rngc_base + RNGC_CONTROL); + + /* run self test */ + cmd = __raw_readl(rngc_base + RNGC_COMMAND); + __raw_writel(cmd | RNGC_CMD_SELF_TEST, + rngc_base + RNGC_COMMAND); + + wait_for_completion(&rng_self_testing); + + } while (__raw_readl(rngc_base + RNGC_ERROR) & + RNGC_ERROR_STATUS_ST_ERR); + + /* clear interrupt. Is it really necessary here? */ + __raw_writel(RNGC_CMD_CLR_INT | RNGC_CMD_CLR_ERR, + rngc_base + RNGC_COMMAND); + + /* create seed, repeat while there is some statistical error */ + do { + /* clear error */ + cmd = __raw_readl(rngc_base + RNGC_COMMAND); + __raw_writel(cmd | RNGC_CMD_CLR_ERR, rngc_base + RNGC_COMMAND); + + /* seed creation */ + cmd = __raw_readl(rngc_base + RNGC_COMMAND); + __raw_writel(cmd | RNGC_CMD_SEED, rngc_base + RNGC_COMMAND); + + wait_for_completion(&rng_seed_done); + + } while (__raw_readl(rngc_base + RNGC_ERROR) & + RNGC_ERROR_STATUS_STAT_ERR); + + err = __raw_readl(rngc_base + RNGC_ERROR) & + (RNGC_ERROR_STATUS_STAT_ERR | + RNGC_ERROR_STATUS_RAND_ERR | + RNGC_ERROR_STATUS_FIFO_ERR | + RNGC_ERROR_STATUS_ST_ERR | + RNGC_ERROR_STATUS_OSC_ERR | + RNGC_ERROR_STATUS_LFSR_ERR); + + if (err) { + dev_err(&rng_dev->dev, "FSL RNGC appears inoperable.\n"); + return -EIO; + } + + return 0; +} + +static struct hwrng fsl_rngc = { + .name = "fsl-rngc", + .init = fsl_rngc_init, + .data_present = fsl_rngc_data_present, + .data_read = fsl_rngc_data_read +}; + +static int __init fsl_rngc_probe(struct platform_device *pdev) +{ + int err = -ENODEV; + struct clk *clk; + struct resource *res, *mem; + void __iomem *rngc_base = NULL; + + if (rng_dev) + return -EBUSY; + + clk = clk_get(NULL, "rng_clk"); + + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Can not get rng_clk\n"); + err = PTR_ERR(clk); + return err; + } + + clk_enable(clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res) + return -ENOENT; + + mem = request_mem_region(res->start, res->end - res->start, pdev->name); + + if (mem == NULL) + return -EBUSY; + + dev_set_drvdata(&pdev->dev, mem); + rngc_base = ioremap(res->start, res->end - res->start); + + fsl_rngc.priv = (unsigned long)rngc_base; + + irq_rng = platform_get_irq(pdev, 0); + + err = hwrng_register(&fsl_rngc); + if (err) { + dev_err(&pdev->dev, "FSL RNGC registering failed (%d)\n", err); + return err; + } + + rng_dev = pdev; + + dev_info(&pdev->dev, "FSL RNGC Registered.\n"); + + return 0; +} + +static int __exit fsl_rngc_remove(struct platform_device *pdev) +{ + struct resource *mem = dev_get_drvdata(&pdev->dev); + void __iomem *rngc_base = (void __iomem *)fsl_rngc.priv; + + hwrng_unregister(&fsl_rngc); + + release_resource(mem); + + iounmap(rngc_base); + + return 0; +} + +static struct platform_driver fsl_rngc_driver = { + .driver = { + .name = "fsl_rngc", + .owner = THIS_MODULE, + }, + .remove = __exit_p(fsl_rngc_remove), +}; + +static int __init mod_init(void) +{ + return platform_driver_probe(&fsl_rngc_driver, fsl_rngc_probe); +} + +static void __exit mod_exit(void) +{ + platform_driver_unregister(&fsl_rngc_driver); +} + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("H/W RNGC driver for i.MX"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/imx_sim.c b/drivers/char/imx_sim.c new file mode 100644 index 000000000000..4d10f11e8012 --- /dev/null +++ b/drivers/char/imx_sim.c @@ -0,0 +1,1497 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_sim.c + * + * @brief Driver for Freescale IMX SIM interface + * + */ + +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/delay.h> + +#include <linux/sched.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/poll.h> +#include <linux/miscdevice.h> +#include <linux/clk.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/mxc_sim_interface.h> + +#include <asm/io.h> +#include <mach/hardware.h> + +#define SIM_INTERNAL_CLK 0 +#define SIM_RFU -1 + +/* Default communication parameters: FI=372, DI=1, PI1=5V, II=50mA, WWT=10 */ +#define SIM_PARAM_DEFAULT { 0, 1, 1, 5, 1, 0, 0, 0, 10 } + +/* Transmit and receive buffer sizes */ +#define SIM_XMT_BUFFER_SIZE 256 +#define SIM_RCV_BUFFER_SIZE 256 + +/* Interface character references */ +#define SIM_IFC_TXI(letter, number) (letter + number * 4) +#define SIM_IFC_TA1 SIM_IFC_TXI(0, 0) +#define SIM_IFC_TB1 SIM_IFC_TXI(0, 1) +#define SIM_IFC_TC1 SIM_IFC_TXI(0, 2) +#define SIM_IFC_TD1 SIM_IFC_TXI(0, 3) +#define SIM_IFC_TA2 SIM_IFC_TXI(1, 0) +#define SIM_IFC_TB2 SIM_IFC_TXI(1, 1) +#define SIM_IFC_TC2 SIM_IFC_TXI(1, 2) +#define SIM_IFC_TD2 SIM_IFC_TXI(1, 3) +#define SIM_IFC_TA3 SIM_IFC_TXI(2, 0) +#define SIM_IFC_TB3 SIM_IFC_TXI(2, 1) +#define SIM_IFC_TC3 SIM_IFC_TXI(2, 2) +#define SIM_IFC_TD3 SIM_IFC_TXI(2, 3) +#define SIM_IFC_TA4 SIM_IFC_TXI(3, 0) +#define SIM_IFC_TB4 SIM_IFC_TXI(3, 1) +#define SIM_IFC_TC4 SIM_IFC_TXI(3, 2) +#define SIM_IFC_TD4 SIM_IFC_TXI(3, 3) + +/* ATR and OPS states */ +#define SIM_STATE_REMOVED 0 +#define SIM_STATE_OPERATIONAL_IDLE 1 +#define SIM_STATE_OPERATIONAL_COMMAND 2 +#define SIM_STATE_OPERATIONAL_RESPONSE 3 +#define SIM_STATE_OPERATIONAL_STATUS1 4 +#define SIM_STATE_OPERATIONAL_STATUS2 5 +#define SIM_STATE_OPERATIONAL_PTS 6 +#define SIM_STATE_DETECTED_ATR_T0 7 +#define SIM_STATE_DETECTED_ATR_TS 8 +#define SIM_STATE_DETECTED_ATR_TXI 9 +#define SIM_STATE_DETECTED_ATR_THB 10 +#define SIM_STATE_DETECTED_ATR_TCK 11 + +/* Definitions of the offset of the SIM hardware registers */ +#define PORT1_CNTL 0x00 /* 00 */ +#define SETUP 0x04 /* 04 */ +#define PORT1_DETECT 0x08 /* 08 */ +#define PORT1_XMT_BUF 0x0C /* 0c */ +#define PORT1_RCV_BUF 0x10 /* 10 */ +#define PORT0_CNTL 0x14 /* 14 */ +#define CNTL 0x18 /* 18 */ +#define CLK_PRESCALER 0x1C /* 1c */ +#define RCV_THRESHOLD 0x20 /* 20 */ +#define ENABLE 0x24 /* 24 */ +#define XMT_STATUS 0x28 /* 28 */ +#define RCV_STATUS 0x2C /* 2c */ +#define INT_MASK 0x30 /* 30 */ +#define PORTO_XMT_BUF 0x34 /* 34 */ +#define PORT0_RCV_BUF 0x38 /* 38 */ +#define PORT0_DETECT 0x3C /* 3c */ +#define DATA_FORMAT 0x40 /* 40 */ +#define XMT_THRESHOLD 0x44 /* 44 */ +#define GUARD_CNTL 0x48 /* 48 */ +#define OD_CONFIG 0x4C /* 4c */ +#define RESET_CNTL 0x50 /* 50 */ +#define CHAR_WAIT 0x54 /* 54 */ +#define GPCNT 0x58 /* 58 */ +#define DIVISOR 0x5C /* 5c */ +#define BWT 0x60 /* 60 */ +#define BGT 0x64 /* 64 */ +#define BWT_H 0x68 /* 68 */ +#define XMT_FIFO_STAT 0x6C /* 6c */ +#define RCV_FIFO_CNT 0x70 /* 70 */ +#define RCV_FIFO_WPTR 0x74 /* 74 */ +#define RCV_FIFO_RPTR 0x78 /* 78 */ + +/* SIM port[0|1]_cntl register bits */ +#define SIM_PORT_CNTL_SFPD (1<<7) +#define SIM_PORT_CNTL_3VOLT (1<<6) +#define SIM_PORT_CNTL_SCSP (1<<5) +#define SIM_PORT_CNTL_SCEN (1<<4) +#define SIM_PORT_CNTL_SRST (1<<3) +#define SIM_PORT_CNTL_STEN (1<<2) +#define SIM_PORT_CNTL_SVEN (1<<1) +#define SIM_PORT_CNTL_SAPD (1<<0) + +/* SIM od_config register bits */ +#define SIM_OD_CONFIG_OD_P1 (1<<1) +#define SIM_OD_CONFIG_OD_P0 (1<<0) + +/* SIM enable register bits */ +#define SIM_ENABLE_XMTEN (1<<1) +#define SIM_ENABLE_RCVEN (1<<0) + +/* SIM int_mask register bits */ +#define SIM_INT_MASK_RFEM (1<<13) +#define SIM_INT_MASK_BGTM (1<<12) +#define SIM_INT_MASK_BWTM (1<<11) +#define SIM_INT_MASK_RTM (1<<10) +#define SIM_INT_MASK_CWTM (1<<9) +#define SIM_INT_MASK_GPCM (1<<8) +#define SIM_INT_MASK_TDTFM (1<<7) +#define SIM_INT_MASK_TFOM (1<<6) +#define SIM_INT_MASK_XTM (1<<5) +#define SIM_INT_MASK_TFEIM (1<<4) +#define SIM_INT_MASK_ETCIM (1<<3) +#define SIM_INT_MASK_OIM (1<<2) +#define SIM_INT_MASK_TCIM (1<<1) +#define SIM_INT_MASK_RIM (1<<0) + +/* SIM xmt_status register bits */ +#define SIM_XMT_STATUS_GPCNT (1<<8) +#define SIM_XMT_STATUS_TDTF (1<<7) +#define SIM_XMT_STATUS_TFO (1<<6) +#define SIM_XMT_STATUS_TC (1<<5) +#define SIM_XMT_STATUS_ETC (1<<4) +#define SIM_XMT_STATUS_TFE (1<<3) +#define SIM_XMT_STATUS_XTE (1<<0) + +/* SIM rcv_status register bits */ +#define SIM_RCV_STATUS_BGT (1<<11) +#define SIM_RCV_STATUS_BWT (1<<10) +#define SIM_RCV_STATUS_RTE (1<<9) +#define SIM_RCV_STATUS_CWT (1<<8) +#define SIM_RCV_STATUS_CRCOK (1<<7) +#define SIM_RCV_STATUS_LRCOK (1<<6) +#define SIM_RCV_STATUS_RDRF (1<<5) +#define SIM_RCV_STATUS_RFD (1<<4) +#define SIM_RCV_STATUS_RFE (1<<1) +#define SIM_RCV_STATUS_OEF (1<<0) + +/* SIM cntl register bits */ +#define SIM_CNTL_BWTEN (1<<15) +#define SIM_CNTL_XMT_CRC_LRC (1<<14) +#define SIM_CNTL_CRCEN (1<<13) +#define SIM_CNTL_LRCEN (1<<12) +#define SIM_CNTL_CWTEN (1<<11) +#define SIM_CNTL_SAMPLE12 (1<<4) +#define SIM_CNTL_ONACK (1<<3) +#define SIM_CNTL_ANACK (1<<2) +#define SIM_CNTL_ICM (1<<1) +#define SIM_CNTL_GPCNT_CLK_SEL(x) ((x&0x03)<<9) +#define SIM_CNTL_GPCNT_CLK_SEL_MASK (0x03<<9) +#define SIM_CNTL_BAUD_SEL(x) ((x&0x07)<<6) +#define SIM_CNTL_BAUD_SEL_MASK (0x07<<6) + +/* SIM rcv_threshold register bits */ +#define SIM_RCV_THRESHOLD_RTH(x) ((x&0x0f)<<9) +#define SIM_RCV_THRESHOLD_RTH_MASK (0x0f<<9) +#define SIM_RCV_THRESHOLD_RDT(x) ((x&0x1ff)<<0) +#define SIM_RCV_THRESHOLD_RDT_MASK (0x1ff<<0) + +/* SIM xmt_threshold register bits */ +#define SIM_XMT_THRESHOLD_XTH(x) ((x&0x0f)<<4) +#define SIM_XMT_THRESHOLD_XTH_MASK (0x0f<<4) +#define SIM_XMT_THRESHOLD_TDT(x) ((x&0x0f)<<0) +#define SIM_XMT_THRESHOLD_TDT_MASK (0x0f<<0) + +/* SIM guard_cntl register bits */ +#define SIM_GUARD_CNTL_RCVR11 (1<<8) +#define SIM_GIARD_CNTL_GETU(x) (x&0xff) +#define SIM_GIARD_CNTL_GETU_MASK (0xff) + +/* SIM port[0|]_detect register bits */ +#define SIM_PORT_DETECT_SPDS (1<<3) +#define SIM_PORT_DETECT_SPDP (1<<2) +#define SIM_PORT_DETECT_SDI (1<<1) +#define SIM_PORT_DETECT_SDIM (1<<0) + +/* END of REGS definitions */ + +/* ATR parser data (the parser state is stored in the main device structure) */ +typedef struct { + uint8_t T0; /* ATR T0 */ + uint8_t TS; /* ATR TS */ + /* ATR TA1, TB1, TC1, TD1, TB1, ... , TD4 */ + uint8_t TXI[16]; + uint8_t THB[15]; /* ATR historical bytes */ + uint8_t TCK; /* ATR checksum */ + uint16_t ifc_valid; /* valid interface characters */ + uint8_t ifc_current_valid; /* calid ifcs in the current batch */ + uint8_t cnt; /* number of current batch */ + uint8_t num_hb; /* number of historical bytes */ +} sim_atrparser_t; + +/* Main SIM driver structure */ +typedef struct { + /* card inserted = 1, ATR received = 2, card removed = 0 */ + int present; + /* current ATR or OPS state */ + int state; + /* current power state */ + int power; + /* error code occured during transfer */ + int errval; + struct clk *clk; /* Clock id */ + uint8_t clk_flag; + struct resource *res; /* IO map memory */ + void __iomem *ioaddr; /* Mapped address */ + int ipb_irq; /* sim ipb IRQ num */ + int dat_irq; /* sim dat IRQ num */ + /* parser for incoming ATR stream */ + sim_atrparser_t atrparser; + /* raw ATR stream received */ + sim_atr_t atr; + /* communication parameters according to ATR */ + sim_param_t param_atr; + /* current communication parameters */ + sim_param_t param; + /* current TPDU or PTS transfer */ + sim_xfer_t xfer; + /* transfer is on the way = 1, idle = 2 */ + int xfer_ongoing; + /* remaining bytes to transmit for the current transfer */ + int xmt_remaining; + /* transmit position */ + int xmt_pos; + /* receive position / number of bytes received */ + int rcv_count; + uint8_t rcv_buffer[SIM_RCV_BUFFER_SIZE]; + uint8_t xmt_buffer[SIM_XMT_BUFFER_SIZE]; + /* transfer completion notifier */ + struct completion xfer_done; + /* async notifier for card and ATR detection */ + struct fasync_struct *fasync; + /* Platform specific data */ + struct mxc_sim_platform_data *plat_data; +} sim_t; + +static int sim_param_F[] = { + SIM_INTERNAL_CLK, 372, 558, 744, 1116, 1488, 1860, SIM_RFU, + SIM_RFU, 512, 768, 1024, 1536, 2048, SIM_RFU, SIM_RFU +}; + +static int sim_param_D[] = { + SIM_RFU, 64 * 1, 64 * 2, 64 * 4, 64 * 8, 64 * 16, SIM_RFU, SIM_RFU, + SIM_RFU, SIM_RFU, 64 * 1 / 2, 64 * 1 / 4, 64 * 1 / 8, 64 * 1 / 16, + 64 * 1 / 32, 64 * 1 / 64 +}; + +static struct miscdevice sim_dev; + +/* Function: sim_calc_param + * + * Description: determine register values depending on communication parameters + * + * Parameters: + * uint32_t fi ATR frequency multiplier index + * uint32_t di ATR frequency divider index + * uint32_t* ptr_divisor location to store divisor result + * uint32_t* ptr_sample12 location to store sample12 result + * + * Return Values: + * SIM_OK calculation finished without errors + * -SIM_E_PARAM_DIVISOR_RANGE calculated divisor > 255 + * -SIM_E_PARAM_FBYD_NOTDIVBY8OR12 F/D not divisable by 12 (as required) + * -SIM_E_PARAM_FBYD_WITHFRACTION F/D has a remainder + * -SIM_E_PARAM_DI_INVALID frequency multiplyer index not supported + * -SIM_E_PARAM_FI_INVALID frequency divider index not supported + */ + +static int sim_calc_param(uint32_t fi, uint32_t di, uint32_t *ptr_divisor, + uint32_t *ptr_sample12) +{ + int32_t errval = SIM_OK; + int32_t f = sim_param_F[fi]; + int32_t d = sim_param_D[di]; + int32_t stage2_fra = (64 * f) % d; + int32_t stage2_div = (64 * f) / d; + uint32_t sample12 = 1; + uint32_t divisor = 31; + + pr_debug("%s entering.\n", __func__); + if ((f > 0) || (d > 0)) { + if (stage2_fra == 0) { + if ((stage2_div % 12) == 0) { + sample12 = 1; + divisor = stage2_div / 12; + } else if ((stage2_div % 8) == 0) { + sample12 = 0; + divisor = stage2_div / 8; + } else + sample12 = -1; + if (sample12 >= 0) { + if (divisor < 256) { + pr_debug("fi=%i", fi); + pr_debug("di=%i", di); + pr_debug("f=%i", f); + pr_debug("d=%i/64", d); + pr_debug("div=%i", stage2_div); + pr_debug("divisor=%i", divisor); + pr_debug("sample12=%i\n", sample12); + + *ptr_divisor = divisor; + *ptr_sample12 = sample12; + errval = SIM_OK; + } else + errval = -SIM_E_PARAM_DIVISOR_RANGE; + } else + errval = -SIM_E_PARAM_FBYD_NOTDIVBY8OR12; + } else + errval = -SIM_E_PARAM_FBYD_WITHFRACTION; + } else + errval = -SIM_E_PARAM_FI_INVALID; + + return errval; +}; + +/* Function: sim_set_param + * + * Description: apply communication parameters (setup devisor and sample12) + * + * Parameters: + * sim_t* sim pointer to SIM device handler + * sim_param_t* param pointer to communication parameters + * + * Return Values: + * see function sim_calc_param + */ + +static int sim_set_param(sim_t *sim, sim_param_t *param) +{ + uint32_t divisor, sample12, reg_data; + int errval; + + pr_debug("%s entering.\n", __func__); + errval = sim_calc_param(param->FI, param->DI, &divisor, &sample12); + if (errval == SIM_OK) { + __raw_writel(divisor, sim->ioaddr + DIVISOR); + if (sample12) { + reg_data = __raw_readl(sim->ioaddr + CNTL); + reg_data |= SIM_CNTL_SAMPLE12; + __raw_writel(reg_data, sim->ioaddr + CNTL); + } else { + reg_data = __raw_readl(sim->ioaddr + CNTL); + reg_data &= ~SIM_CNTL_SAMPLE12; + __raw_writel(reg_data, sim->ioaddr + CNTL); + } + } + + return errval; +}; + +/* Function: sim_atr_received + * + * Description: this function is called whenever a valid ATR has been received. + * It determines the communication parameters from the ATR received and notifies + * the user space application with SIGIO. + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_atr_received(sim_t *sim) +{ + sim_param_t param_default = SIM_PARAM_DEFAULT; + sim->param_atr = param_default; + + pr_debug("%s entering.\n", __func__); + if (sim->atrparser.ifc_valid & (1 << (SIM_IFC_TA1))) { + sim->param_atr.FI = sim->atrparser.TXI[SIM_IFC_TA1] >> 4; + sim->param_atr.DI = sim->atrparser.TXI[SIM_IFC_TA1] & 0x0f; + } + if (sim->atrparser.ifc_valid & (1 << (SIM_IFC_TB1))) { + sim->param_atr.PI1 = (sim->atrparser.TXI[SIM_IFC_TB1] >> 4) + & 0x07; + sim->param_atr.II = sim->atrparser.TXI[SIM_IFC_TB1] & 0x07f; + } + if (sim->atrparser.ifc_valid & (1 << (SIM_IFC_TC1))) + sim->param_atr.N = sim->atrparser.TXI[SIM_IFC_TC1]; + + if (sim->atrparser.ifc_valid & (1 << (SIM_IFC_TD1))) + sim->param_atr.T = sim->atrparser.TXI[SIM_IFC_TD1] & 0x0f; + + if (sim->atrparser.ifc_valid & (1 << (SIM_IFC_TB2))) + sim->param_atr.PI2 = sim->atrparser.TXI[SIM_IFC_TB2]; + + if (sim->atrparser.ifc_valid & (1 << (SIM_IFC_TC2))) + sim->param_atr.WWT = sim->atrparser.TXI[SIM_IFC_TC2]; + + if (sim->fasync) + kill_fasync(&sim->fasync, SIGIO, POLL_IN); + +}; + +/* Function: sim_xmt_fill + * + * Description: fill the transmit FIFO until the FIFO is full or + * the end of the transmission has been reached. + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_xmt_fill(sim_t *sim) +{ + uint32_t reg_data; + int bytesleft; + + reg_data = __raw_readl(sim->ioaddr + XMT_FIFO_STAT); + bytesleft = 16 - ((reg_data >> 8) & 0x0f); + + pr_debug("txfill: remaining=%i bytesleft=%i\n", + sim->xmt_remaining, bytesleft); + if (bytesleft > sim->xmt_remaining) + bytesleft = sim->xmt_remaining; + + sim->xmt_remaining -= bytesleft; + for (; bytesleft > 0; bytesleft--) { + __raw_writel(sim->xmt_buffer[sim->xmt_pos], + sim->ioaddr + PORT1_XMT_BUF); + sim->xmt_pos++; + }; +/* FIXME: optimization - keep filling until fifo full */ +}; + +/* Function: sim_xmt_start + * + * Description: initiate a transfer + * + * Parameters: + * sim_t* sim pointer to SIM device handler + * int pos position in the xfer transmit buffer + * int count number of bytes to be transmitted + */ + +static void sim_xmt_start(sim_t *sim, int pos, int count) +{ + uint32_t reg_data; + + pr_debug("tx\n"); + sim->xmt_remaining = count; + sim->xmt_pos = pos; + sim_xmt_fill(sim); + + if (sim->xmt_remaining) { + reg_data = __raw_readl(sim->ioaddr + INT_MASK); + reg_data &= ~SIM_INT_MASK_TDTFM; + __raw_writel(reg_data, sim->ioaddr + INT_MASK); + } else { + reg_data = __raw_readl(sim->ioaddr + INT_MASK); + reg_data &= ~SIM_INT_MASK_TCIM; + __raw_writel(reg_data, sim->ioaddr + INT_MASK); + __raw_writel(SIM_XMT_STATUS_TC | SIM_XMT_STATUS_TDTF, + sim->ioaddr + XMT_STATUS); + reg_data = __raw_readl(sim->ioaddr + ENABLE); + reg_data |= SIM_ENABLE_XMTEN; + __raw_writel(reg_data, sim->ioaddr + ENABLE); + } +}; + +/* Function: sim_atr_add + * + * Description: add a byte to the raw ATR string + * + * Parameters: + * sim_t* sim pointer to SIM device handler + * uint8_t data byte to be added + */ + +static void sim_atr_add(sim_t *sim, uint8_t data) +{ + pr_debug("%s entering.\n", __func__); + if (sim->atr.size < SIM_ATR_LENGTH_MAX) + sim->atr.t[sim->atr.size++] = data; + else + printk(KERN_ERR "sim.c: ATR received is too big!\n"); +}; + +/* Function: sim_fsm + * + * Description: main finite state machine running in ISR context. + * + * Parameters: + * sim_t* sim pointer to SIM device handler + * uint8_t data byte received + */ + +static void sim_fsm(sim_t *sim, uint16_t data) +{ + uint32_t temp, i = 0; + switch (sim->state) { + + pr_debug("%s stat is %d \n", __func__, sim->state); + /* OPS FSM */ + + case SIM_STATE_OPERATIONAL_IDLE: + printk(KERN_INFO "data received unexpectidly (%04x)\n", data); + break; + + case SIM_STATE_OPERATIONAL_COMMAND: + if (data == sim->xmt_buffer[1]) { + if (sim->xfer.rcv_length) { + sim->state = SIM_STATE_OPERATIONAL_RESPONSE; + } else { + sim->state = SIM_STATE_OPERATIONAL_STATUS1; + if (sim->xfer.xmt_length > 5) + sim_xmt_start(sim, 5, + sim->xfer.xmt_length - 5); + }; + } else if (((data & 0xf0) == 0x60) | ((data & 0xf0) == 0x90)) { + sim->xfer.sw1 = data; + sim->state = SIM_STATE_OPERATIONAL_STATUS2; + } else { + sim->errval = -SIM_E_NACK; + complete(&sim->xfer_done); + }; + break; + + case SIM_STATE_OPERATIONAL_RESPONSE: + sim->rcv_buffer[sim->rcv_count] = data; + sim->rcv_count++; + if (sim->rcv_count == sim->xfer.rcv_length) + sim->state = SIM_STATE_OPERATIONAL_STATUS1; + break; + + case SIM_STATE_OPERATIONAL_STATUS1: + sim->xfer.sw1 = data; + sim->state = SIM_STATE_OPERATIONAL_STATUS2; + break; + + case SIM_STATE_OPERATIONAL_STATUS2: + sim->xfer.sw2 = data; + sim->state = SIM_STATE_OPERATIONAL_IDLE; + complete(&sim->xfer_done); + break; + + case SIM_STATE_OPERATIONAL_PTS: + sim->rcv_buffer[sim->rcv_count] = data; + sim->rcv_count++; + if (sim->rcv_count == sim->xfer.rcv_length) + sim->state = SIM_STATE_OPERATIONAL_IDLE; + break; + + /* ATR FSM */ + + case SIM_STATE_DETECTED_ATR_T0: + sim_atr_add(sim, data); + pr_debug("T0 %02x\n", data); + sim->atrparser.T0 = data; + sim->state = SIM_STATE_DETECTED_ATR_TS; + break; + + case SIM_STATE_DETECTED_ATR_TS: + sim_atr_add(sim, data); + pr_debug("TS %02x\n", data); + sim->atrparser.TS = data; + if (data & 0xf0) { + sim->atrparser.ifc_current_valid = (data >> 4) & 0x0f; + sim->atrparser.num_hb = data & 0x0f; + sim->atrparser.ifc_valid = 0; + sim->state = SIM_STATE_DETECTED_ATR_TXI; + sim->atrparser.cnt = 0; + } else { + goto sim_fsm_atr_thb; + }; + break; + + case SIM_STATE_DETECTED_ATR_TXI: + sim_atr_add(sim, data); + i = ffs(sim->atrparser.ifc_current_valid) - 1; + pr_debug("T%c%i %02x\n", 'A' + i, sim->atrparser.cnt + 1, data); + sim->atrparser.TXI[SIM_IFC_TXI(i, sim->atrparser.cnt)] = data; + sim->atrparser.ifc_valid |= 1 << SIM_IFC_TXI(i, + sim->atrparser. + cnt); + sim->atrparser.ifc_current_valid &= ~(1 << i); + + if (sim->atrparser.ifc_current_valid == 0) { + if (i == 3) { + sim->atrparser.ifc_current_valid = (data >> 4) + & 0x0f; + sim->atrparser.cnt++; + + if (sim->atrparser.cnt >= 4) { + /* error */ + printk(KERN_ERR "ERROR !\n"); + break; + }; + + if (sim->atrparser.ifc_current_valid == 0) + goto sim_fsm_atr_thb; + } else { +sim_fsm_atr_thb: + if (sim->atrparser.num_hb) { + sim->state = SIM_STATE_DETECTED_ATR_THB; + sim->atrparser.cnt = 0; + } else { + goto sim_fsm_atr_tck; + }; + }; + }; + break; + + case SIM_STATE_DETECTED_ATR_THB: + sim_atr_add(sim, data); + pr_debug("THB%i %02x\n", i, data); + sim->atrparser.THB[sim->atrparser.cnt] = data; + sim->atrparser.cnt++; + + if (sim->atrparser.cnt == sim->atrparser.num_hb) { +sim_fsm_atr_tck: + i = sim->atrparser.ifc_valid & (1 << (SIM_IFC_TD1)); + temp = sim->atrparser.TXI[SIM_IFC_TD1] & 0x0f; + if ((i && temp) == SIM_PROTOCOL_T1) + sim->state = SIM_STATE_DETECTED_ATR_TCK; + else + goto sim_fsm_atr_received; + }; + break; + + case SIM_STATE_DETECTED_ATR_TCK: + sim_atr_add(sim, data); + /* checksum not required for T=0 */ + sim->atrparser.TCK = data; +sim_fsm_atr_received: + sim->state = SIM_STATE_OPERATIONAL_IDLE; + sim->present = SIM_PRESENT_OPERATIONAL; + sim_atr_received(sim); + break; + }; +}; + +/* Function: sim_irq_handler + * + * Description: interrupt service routine. + * + * Parameters: + * int irq interrupt number + * void *dev_id pointer to SIM device handler + * + * Return values: + * IRQ_HANDLED OS specific + */ + +static irqreturn_t sim_irq_handler(int irq, void *dev_id) +{ + uint32_t reg_data, reg_data0, reg_data1; + + sim_t *sim = (sim_t *) dev_id; + + pr_debug("%s entering\n", __func__); + + reg_data0 = __raw_readl(sim->ioaddr + XMT_STATUS); + reg_data1 = __raw_readl(sim->ioaddr + INT_MASK); + if ((reg_data0 & SIM_XMT_STATUS_TC) + && (!(reg_data1 & SIM_INT_MASK_TCIM))) { + pr_debug("TC_IRQ\n"); + __raw_writel(SIM_XMT_STATUS_TC, sim->ioaddr + XMT_STATUS); + reg_data = __raw_readl(sim->ioaddr + INT_MASK); + reg_data |= SIM_INT_MASK_TCIM; + __raw_writel(reg_data, sim->ioaddr + INT_MASK); + reg_data = __raw_readl(sim->ioaddr + ENABLE); + reg_data &= ~SIM_ENABLE_XMTEN; + __raw_writel(reg_data, sim->ioaddr + ENABLE); + }; + + reg_data0 = __raw_readl(sim->ioaddr + XMT_STATUS); + reg_data1 = __raw_readl(sim->ioaddr + INT_MASK); + if ((reg_data0 & SIM_XMT_STATUS_TDTF) + && (!(reg_data1 & SIM_INT_MASK_TDTFM))) { + pr_debug("TDTF_IRQ\n"); + __raw_writel(SIM_XMT_STATUS_TDTF, sim->ioaddr + XMT_STATUS); + sim_xmt_fill(sim); + + if (sim->xmt_remaining == 0) { + __raw_writel(SIM_XMT_STATUS_TC, + sim->ioaddr + XMT_STATUS); + reg_data = __raw_readl(sim->ioaddr + INT_MASK); + reg_data &= ~SIM_INT_MASK_TCIM; + reg_data |= SIM_INT_MASK_TDTFM; + __raw_writel(reg_data, sim->ioaddr + INT_MASK); + }; + }; + + reg_data0 = __raw_readl(sim->ioaddr + RCV_STATUS); + reg_data1 = __raw_readl(sim->ioaddr + INT_MASK); + if ((reg_data0 & SIM_RCV_STATUS_RDRF) + && (!(reg_data1 & SIM_INT_MASK_RIM))) { + pr_debug("%s RDRF_IRQ\n", __func__); + __raw_writel(SIM_RCV_STATUS_RDRF, sim->ioaddr + RCV_STATUS); + + while (__raw_readl(sim->ioaddr + RCV_FIFO_CNT)) { + uint32_t data; + data = __raw_readl(sim->ioaddr + PORT1_RCV_BUF); + pr_debug("RX = %02x state = %i\n", data, sim->state); + if (data & 0x700) { + if (sim->xfer_ongoing) { + /* error */ + printk(KERN_ERR "ERROR !\n"); + return IRQ_HANDLED; + }; + } else + sim_fsm(sim, data); + }; + }; + + reg_data0 = __raw_readl(sim->ioaddr + PORT0_DETECT); + if (reg_data0 & SIM_PORT_DETECT_SDI) { + pr_debug("%s PD_IRQ\n", __func__); + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + reg_data |= SIM_PORT_DETECT_SDI; + __raw_writel(reg_data, sim->ioaddr + PORT0_DETECT); + + reg_data0 = __raw_readl(sim->ioaddr + PORT0_DETECT); + if (reg_data0 & SIM_PORT_DETECT_SPDP) { + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + reg_data &= ~SIM_PORT_DETECT_SPDS; + __raw_writel(reg_data, sim->ioaddr + PORT0_DETECT); + + if (sim->present != SIM_PRESENT_REMOVED) { + pr_debug("Removed sim card\n"); + sim->present = SIM_PRESENT_REMOVED; + sim->state = SIM_STATE_REMOVED; + + if (sim->fasync) + kill_fasync(&sim->fasync, + SIGIO, POLL_IN); + }; + } else { + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + reg_data |= SIM_PORT_DETECT_SPDS; + __raw_writel(reg_data, sim->ioaddr + PORT0_DETECT); + + if (sim->present == SIM_PRESENT_REMOVED) { + pr_debug("Inserted sim card\n"); + sim->state = SIM_STATE_DETECTED_ATR_T0; + sim->present = SIM_PRESENT_DETECTED; + + if (sim->fasync) + kill_fasync(&sim->fasync, + SIGIO, POLL_IN); + }; + }; + }; + + return IRQ_HANDLED; +}; + +/* Function: sim_power_on + * + * Description: run the power on sequence + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_power_on(sim_t *sim) +{ + uint32_t reg_data; + + /* power on sequence */ + pr_debug("%s Powering on the sim port.\n", __func__); + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data |= SIM_PORT_CNTL_SVEN; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + msleep(10); + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data |= SIM_PORT_CNTL_SCEN; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + msleep(10); + reg_data = SIM_RCV_THRESHOLD_RTH(0) | SIM_RCV_THRESHOLD_RDT(1); + __raw_writel(reg_data, sim->ioaddr + RCV_THRESHOLD); + __raw_writel(SIM_RCV_STATUS_RDRF, sim->ioaddr + RCV_STATUS); + reg_data = __raw_readl(sim->ioaddr + INT_MASK); + reg_data &= ~SIM_INT_MASK_RIM; + __raw_writel(reg_data, sim->ioaddr + INT_MASK); + __raw_writel(31, sim->ioaddr + DIVISOR); + reg_data = __raw_readl(sim->ioaddr + CNTL); + reg_data |= SIM_CNTL_SAMPLE12; + __raw_writel(reg_data, sim->ioaddr + CNTL); + reg_data = __raw_readl(sim->ioaddr + ENABLE); + reg_data |= SIM_ENABLE_RCVEN; + __raw_writel(reg_data, sim->ioaddr + ENABLE); + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data |= SIM_PORT_CNTL_SRST; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + pr_debug("%s port0_ctl is 0x%x.\n", __func__, + __raw_readl(sim->ioaddr + PORT0_CNTL)); + sim->power = SIM_POWER_ON; +}; + +/* Function: sim_power_off + * + * Description: run the power off sequence + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_power_off(sim_t *sim) +{ + uint32_t reg_data; + + pr_debug("%s entering.\n", __func__); + /* sim_power_off sequence */ + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data &= ~SIM_PORT_CNTL_SCEN; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + reg_data = __raw_readl(sim->ioaddr + ENABLE); + reg_data &= ~SIM_ENABLE_RCVEN; + __raw_writel(reg_data, sim->ioaddr + ENABLE); + reg_data = __raw_readl(sim->ioaddr + INT_MASK); + reg_data |= SIM_INT_MASK_RIM; + __raw_writel(reg_data, sim->ioaddr + INT_MASK); + __raw_writel(0, sim->ioaddr + RCV_THRESHOLD); + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data &= ~SIM_PORT_CNTL_SRST; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data &= ~SIM_PORT_CNTL_SVEN; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + sim->power = SIM_POWER_OFF; +}; + +/* Function: sim_start + * + * Description: ramp up the SIM interface + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_start(sim_t *sim) +{ + uint32_t reg_data, clk_rate, clk_div = 0; + + pr_debug("%s entering.\n", __func__); + /* Configuring SIM for Operation */ + reg_data = SIM_XMT_THRESHOLD_XTH(0) | SIM_XMT_THRESHOLD_TDT(4); + __raw_writel(reg_data, sim->ioaddr + XMT_THRESHOLD); + __raw_writel(0, sim->ioaddr + SETUP); + /* ~ 4 MHz */ + clk_rate = clk_get_rate(sim->clk); + clk_div = clk_rate / sim->plat_data->clk_rate; + if (clk_rate % sim->plat_data->clk_rate) + clk_div++; + pr_debug("%s prescaler is 0x%x.\n", __func__, clk_div); + __raw_writel(clk_div, sim->ioaddr + CLK_PRESCALER); + + reg_data = SIM_CNTL_GPCNT_CLK_SEL(0) | SIM_CNTL_BAUD_SEL(7) + | SIM_CNTL_SAMPLE12 | SIM_CNTL_ANACK | SIM_CNTL_ICM; + __raw_writel(reg_data, sim->ioaddr + CNTL); + __raw_writel(31, sim->ioaddr + DIVISOR); + reg_data = __raw_readl(sim->ioaddr + OD_CONFIG); + reg_data |= SIM_OD_CONFIG_OD_P0; + __raw_writel(reg_data, sim->ioaddr + OD_CONFIG); + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data |= SIM_PORT_CNTL_3VOLT | SIM_PORT_CNTL_STEN; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + + /* presense detect */ + pr_debug("%s p0_det is 0x%x \n", __func__, + __raw_readl(sim->ioaddr + PORT0_DETECT)); + if (__raw_readl(sim->ioaddr + PORT0_DETECT) & SIM_PORT_DETECT_SPDP) { + pr_debug("%s card removed \n", __func__); + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + reg_data &= ~SIM_PORT_DETECT_SPDS; + __raw_writel(reg_data, sim->ioaddr + PORT0_DETECT); + sim->present = SIM_PRESENT_REMOVED; + sim->state = SIM_STATE_REMOVED; + } else { + pr_debug("%s card inserted \n", __func__); + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + reg_data |= SIM_PORT_DETECT_SPDS; + __raw_writel(reg_data, sim->ioaddr + PORT0_DETECT); + sim->present = SIM_PRESENT_DETECTED; + sim->state = SIM_STATE_DETECTED_ATR_T0; + }; + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + reg_data |= SIM_PORT_DETECT_SDI; + reg_data &= ~SIM_PORT_DETECT_SDIM; + __raw_writel(reg_data, sim->ioaddr + PORT0_DETECT); + + /* + * Since there is no PD0 layout on MX51, assume + * that there is a SIM card in slot defaulty. + * */ + if (0 == (sim->plat_data->detect)) { + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + reg_data |= SIM_PORT_DETECT_SPDS; + __raw_writel(reg_data, sim->ioaddr + PORT0_DETECT); + sim->present = SIM_PRESENT_DETECTED; + sim->state = SIM_STATE_DETECTED_ATR_T0; + } + + if (sim->present == SIM_PRESENT_DETECTED) + sim_power_on(sim); + +}; + +/* Function: sim_stop + * + * Description: shut down the SIM interface + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_stop(sim_t *sim) +{ + pr_debug("%s entering.\n", __func__); + __raw_writel(0, sim->ioaddr + SETUP); + __raw_writel(0, sim->ioaddr + ENABLE); + __raw_writel(0, sim->ioaddr + PORT0_CNTL); + __raw_writel(0x06, sim->ioaddr + CNTL); + __raw_writel(0, sim->ioaddr + CLK_PRESCALER); + __raw_writel(0, sim->ioaddr + SETUP); + __raw_writel(0, sim->ioaddr + OD_CONFIG); + __raw_writel(0, sim->ioaddr + XMT_THRESHOLD); + __raw_writel(0xb8, sim->ioaddr + XMT_STATUS); + __raw_writel(4, sim->ioaddr + RESET_CNTL); + mdelay(1); +}; + +/* Function: sim_data_reset + * + * Description: reset a SIM structure to default values + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_data_reset(sim_t *sim) +{ + sim_param_t param_default = SIM_PARAM_DEFAULT; + sim->present = SIM_PRESENT_REMOVED; + sim->state = SIM_STATE_REMOVED; + sim->power = SIM_POWER_OFF; + sim->errval = SIM_OK; + memset(&sim->atrparser, 0, sizeof(sim->atrparser)); + memset(&sim->atr, 0, sizeof(sim->atr)); + sim->param_atr = param_default; + memset(&sim->param, 0, sizeof(sim->param)); + memset(&sim->xfer, 0, sizeof(sim->xfer)); + sim->xfer_ongoing = 0; + sim->xmt_remaining = 0; + sim->xmt_pos = 0; + sim->rcv_count = 0; + memset(sim->rcv_buffer, 0, SIM_RCV_BUFFER_SIZE); + memset(sim->xmt_buffer, 0, SIM_XMT_BUFFER_SIZE); +}; + +/* Function: sim_cold_reset + * + * Description: cold reset the SIM interface, including card + * power down and interface hardware reset. + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_cold_reset(sim_t *sim) +{ + pr_debug("%s entering.\n", __func__); + if (sim->present != SIM_PRESENT_REMOVED) { + sim_power_off(sim); + sim_stop(sim); + sim_data_reset(sim); + sim->state = SIM_STATE_DETECTED_ATR_T0; + sim->present = SIM_PRESENT_DETECTED; + msleep(50); + sim_start(sim); + sim_power_on(sim); + }; +}; + +/* Function: sim_warm_reset + * + * Description: warm reset the SIM interface: just invoke the + * reset signal and reset the SIM structure for the interface. + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static void sim_warm_reset(sim_t *sim) +{ + uint32_t reg_data; + + pr_debug("%s entering.\n", __func__); + if (sim->present != SIM_PRESENT_REMOVED) { + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data |= SIM_PORT_CNTL_SRST; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + sim_data_reset(sim); + msleep(50); + reg_data = __raw_readl(sim->ioaddr + PORT0_CNTL); + reg_data &= ~SIM_PORT_CNTL_SRST; + __raw_writel(reg_data, sim->ioaddr + PORT0_CNTL); + }; +}; + +/* Function: sim_card_lock + * + * Description: physically lock the SIM card. + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static int sim_card_lock(sim_t *sim) +{ + int errval; + + pr_debug("%s entering.\n", __func__); + /* place holder for true physcial locking */ + if (sim->present != SIM_PRESENT_REMOVED) + errval = SIM_OK; + else + errval = -SIM_E_NOCARD; + return errval; +}; + +/* Function: sim_card_eject + * + * Description: physically unlock and eject the SIM card. + * + * Parameters: + * sim_t* sim pointer to SIM device handler + */ + +static int sim_card_eject(sim_t *sim) +{ + int errval; + + pr_debug("%s entering.\n", __func__); + /* place holder for true physcial locking */ + if (sim->present != SIM_PRESENT_REMOVED) + errval = SIM_OK; + else + errval = -SIM_E_NOCARD; + return errval; +}; + +/* Function: sim_ioctl + * + * Description: handle ioctl calls + * + * Parameters: OS specific + */ + +static int sim_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret, errval = SIM_OK; + unsigned long timeout; + + sim_t *sim = (sim_t *) file->private_data; + + pr_debug("%s entering.\n", __func__); + switch (cmd) { + pr_debug("ioctl cmd %d is issued...\n", cmd); + + case SIM_IOCTL_GET_ATR: + if (sim->present != SIM_PRESENT_OPERATIONAL) { + errval = -SIM_E_NOCARD; + break; + }; + ret = copy_to_user((sim_atr_t *) arg, &sim->atr, + sizeof(sim_atr_t)); + if (ret) + errval = -SIM_E_ACCESS; + break; + + case SIM_IOCTL_GET_PARAM_ATR: + if (sim->present != SIM_PRESENT_OPERATIONAL) { + errval = -SIM_E_NOCARD; + break; + }; + ret = copy_to_user((sim_param_t *) arg, &sim->param_atr, + sizeof(sim_param_t)); + if (ret) + errval = -SIM_E_ACCESS; + break; + + case SIM_IOCTL_GET_PARAM: + ret = copy_to_user((sim_param_t *) arg, &sim->param, + sizeof(sim_param_t)); + if (ret) + errval = -SIM_E_ACCESS; + break; + + case SIM_IOCTL_SET_PARAM: + ret = copy_from_user(&sim->param, (sim_param_t *) arg, + sizeof(sim_param_t)); + if (ret) + errval = -SIM_E_ACCESS; + else + errval = sim_set_param(sim, &sim->param); + break; + + case SIM_IOCTL_POWER_ON: + if (sim->power == SIM_POWER_ON) { + errval = -SIM_E_POWERED_ON; + break; + }; + sim_power_on(sim); + break; + + case SIM_IOCTL_POWER_OFF: + if (sim->power == SIM_POWER_OFF) { + errval = -SIM_E_POWERED_OFF; + break; + }; + sim_power_off(sim); + break; + + case SIM_IOCTL_COLD_RESET: + if (sim->power == SIM_POWER_OFF) { + errval = -SIM_E_POWERED_OFF; + break; + }; + sim_cold_reset(sim); + break; + + case SIM_IOCTL_WARM_RESET: + sim_warm_reset(sim); + if (sim->power == SIM_POWER_OFF) { + errval = -SIM_E_POWERED_OFF; + break; + }; + break; + + case SIM_IOCTL_XFER: + if (sim->present != SIM_PRESENT_OPERATIONAL) { + errval = -SIM_E_NOCARD; + break; + }; + + ret = copy_from_user(&sim->xfer, (sim_xfer_t *) arg, + sizeof(sim_xfer_t)); + if (ret) { + errval = -SIM_E_ACCESS; + break; + }; + + ret = copy_from_user(sim->xmt_buffer, sim->xfer.xmt_buffer, + sim->xfer.xmt_length); + if (ret) { + errval = -SIM_E_ACCESS; + break; + }; + + sim->rcv_count = 0; + sim->xfer.sw1 = 0; + sim->xfer.sw2 = 0; + + if (sim->xfer.type == SIM_XFER_TYPE_TPDU) { + if (sim->xfer.xmt_length < 5) { + errval = -SIM_E_TPDUSHORT; + break; + } + sim->state = SIM_STATE_OPERATIONAL_COMMAND; + } else if (sim->xfer.type == SIM_XFER_TYPE_PTS) { + if (sim->xfer.xmt_length == 0) { + errval = -SIM_E_PTSEMPTY; + break; + } + sim->state = SIM_STATE_OPERATIONAL_PTS; + } else { + errval = -SIM_E_INVALIDXFERTYPE; + break; + }; + + if (sim->xfer.xmt_length > SIM_XMT_BUFFER_SIZE) { + errval = -SIM_E_INVALIDXMTLENGTH; + break; + }; + + if (sim->xfer.rcv_length > SIM_XMT_BUFFER_SIZE) { + errval = -SIM_E_INVALIDRCVLENGTH; + break; + }; + + sim->errval = 0; + sim->xfer_ongoing = 1; + init_completion(&sim->xfer_done); + sim_xmt_start(sim, 0, 5); + timeout = + wait_for_completion_interruptible_timeout(&sim->xfer_done, + sim->xfer. + timeout); + sim->xfer_ongoing = 0; + + if (sim->errval) { + errval = sim->errval; + break; + }; + + if (timeout == 0) { + errval = -SIM_E_TIMEOUT; + break; + } + + ret = copy_to_user(sim->xfer.rcv_buffer, sim->rcv_buffer, + sim->xfer.rcv_length); + if (ret) { + errval = -SIM_E_ACCESS; + break; + }; + + ret = copy_to_user((sim_xfer_t *) arg, &sim->xfer, + sizeof(sim_xfer_t)); + if (ret) + errval = -SIM_E_ACCESS; + break; + + case SIM_IOCTL_GET_PRESENSE: + if (put_user(sim->present, (int *)arg)) + errval = -SIM_E_ACCESS; + break; + + case SIM_IOCTL_CARD_LOCK: + errval = sim_card_lock(sim); + break; + + case SIM_IOCTL_CARD_EJECT: + errval = sim_card_eject(sim); + break; + + }; + + return errval; +}; + +/* Function: sim_fasync + * + * Description: async handler + * + * Parameters: OS specific + */ + +static int sim_fasync(int fd, struct file *file, int mode) +{ + sim_t *sim = (sim_t *) file->private_data; + pr_debug("%s entering.\n", __func__); + return fasync_helper(fd, file, mode, &sim->fasync); +} + +/* Function: sim_open + * + * Description: ramp up interface when being opened + * + * Parameters: OS specific + */ + +static int sim_open(struct inode *inode, struct file *file) +{ + int errval = SIM_OK; + + sim_t *sim = dev_get_drvdata(sim_dev.parent); + file->private_data = sim; + + pr_debug("%s entering.\n", __func__); + if (!sim->ioaddr) { + errval = -ENOMEM; + return errval; + } + + if (!(sim->clk_flag)) { + pr_debug("\n%s enable the clock\n", __func__); + clk_enable(sim->clk); + sim->clk_flag = 1; + } + + sim_start(sim); + + return errval; +}; + +/* Function: sim_release + * + * Description: shut down interface when being closed + * + * Parameters: OS specific + */ + +static int sim_release(struct inode *inode, struct file *file) +{ + uint32_t reg_data; + + sim_t *sim = (sim_t *) file->private_data; + + pr_debug("%s entering.\n", __func__); + if (sim->clk_flag) { + pr_debug("\n%s disable the clock\n", __func__); + clk_disable(sim->clk); + sim->clk_flag = 0; + } + + /* disable presense detection */ + reg_data = __raw_readl(sim->ioaddr + PORT0_DETECT); + __raw_writel(reg_data | SIM_PORT_DETECT_SDIM, + sim->ioaddr + PORT0_DETECT); + + if (sim->present != SIM_PRESENT_REMOVED) { + sim_power_off(sim); + if (sim->fasync) + kill_fasync(&sim->fasync, SIGIO, POLL_IN); + }; + + sim_stop(sim); + + sim_fasync(-1, file, 0); + + pr_debug("exit\n"); + return 0; +}; + +static const struct file_operations sim_fops = { + .open = sim_open, + .ioctl = sim_ioctl, + .fasync = sim_fasync, + .release = sim_release +}; + +static struct miscdevice sim_dev = { + MISC_DYNAMIC_MINOR, + "mxc_sim", + &sim_fops +}; + +/*****************************************************************************\ + * * + * Driver init/exit * + * * +\*****************************************************************************/ + +static int sim_probe(struct platform_device *pdev) +{ + int ret = 0; + struct mxc_sim_platform_data *sim_plat = pdev->dev.platform_data; + + sim_t *sim = kzalloc(sizeof(sim_t), GFP_KERNEL); + + if (sim == 0) { + ret = -ENOMEM; + printk(KERN_ERR "Can't get the MEMORY\n"); + return ret; + }; + + BUG_ON(pdev == NULL); + + sim->plat_data = sim_plat; + sim->clk_flag = 0; + + sim->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!sim->res) { + ret = -ENOMEM; + printk(KERN_ERR "Can't get the MEMORY\n"); + goto out; + } + + /* request the sim clk and sim_serial_clk */ + sim->clk = clk_get(NULL, sim->plat_data->clock_sim); + if (IS_ERR(sim->clk)) { + ret = PTR_ERR(sim->clk); + printk(KERN_ERR "Get CLK ERROR !\n"); + goto out; + } + pr_debug("sim clock:%lu\n", clk_get_rate(sim->clk)); + + sim->ipb_irq = platform_get_irq(pdev, 0); + sim->dat_irq = platform_get_irq(pdev, 1); + if (!(sim->ipb_irq | sim->dat_irq)) { + ret = -ENOMEM; + goto out1; + } + + if (!request_mem_region(sim->res->start, + sim->res->end - + sim->res->start + 1, pdev->name)) { + printk(KERN_ERR "request_mem_region failed\n"); + ret = -ENOMEM; + goto out1; + } + + sim->ioaddr = (void *)ioremap(sim->res->start, sim->res->end - + sim->res->start + 1); + if (sim->ipb_irq) + ret = request_irq(sim->ipb_irq, sim_irq_handler, + 0, "mxc_sim_ipb", sim); + if (sim->dat_irq) + ret |= request_irq(sim->dat_irq, sim_irq_handler, + 0, "mxc_sim_dat", sim); + + if (ret) { + printk(KERN_ERR "Can't get the irq\n"); + goto out2; + }; + + platform_set_drvdata(pdev, sim); + sim_dev.parent = &(pdev->dev); + + misc_register(&sim_dev); + + return ret; +out2: + if (sim->ipb_irq) + free_irq(sim->ipb_irq, sim); + if (sim->dat_irq) + free_irq(sim->dat_irq, sim); + release_mem_region(sim->res->start, + sim->res->end - sim->res->start + 1); +out1: + clk_put(sim->clk); +out: + kfree(sim); + return ret; +} + +static int sim_remove(struct platform_device *pdev) +{ + sim_t *sim = platform_get_drvdata(pdev); + + clk_put(sim->clk); + + if (sim->ipb_irq) + free_irq(sim->ipb_irq, sim); + if (sim->dat_irq) + free_irq(sim->dat_irq, sim); + + iounmap(sim->ioaddr); + + kfree(sim); + release_mem_region(sim->res->start, + sim->res->end - sim->res->start + 1); + + misc_deregister(&sim_dev); + return 0; +} + +static struct platform_driver sim_driver = { + .driver = { + .name = "mxc_sim", + }, + .probe = sim_probe, + .remove = sim_remove, + .suspend = NULL, + .resume = NULL, +}; + +static int __init sim_drv_init(void) +{ + return platform_driver_register(&sim_driver); +} + +static void __exit sim_drv_exit(void) +{ + platform_driver_unregister(&sim_driver); +} + +module_init(sim_drv_init); +module_exit(sim_drv_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC SIM Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/mxc_iim.c b/drivers/char/mxc_iim.c new file mode 100644 index 000000000000..b407d34759a9 --- /dev/null +++ b/drivers/char/mxc_iim.c @@ -0,0 +1,161 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/mm.h> +#include <linux/clk.h> +#include <linux/miscdevice.h> + +static unsigned long iim_reg_base, iim_reg_end, iim_reg_size; +static struct clk *iim_clk; +static struct device *iim_dev; + +/*! + * MXC IIM interface - memory map function + * This function maps 4KB IIM registers from IIM base address. + * + * @param file struct file * + * @param vma structure vm_area_struct * + * + * @return Return 0 on success or negative error code on error + */ +static int mxc_iim_mmap(struct file *file, struct vm_area_struct *vma) +{ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Remap-pfn-range will mark the range VM_IO and VM_RESERVED */ + if (remap_pfn_range(vma, + vma->vm_start, + iim_reg_base >> PAGE_SHIFT, + iim_reg_size, + vma->vm_page_prot)) + return -EAGAIN; + + return 0; +} + +/*! + * MXC IIM interface - open function + * + * @param inode struct inode * + * @param filp struct file * + * + * @return Return 0 on success or negative error code on error + */ +static int mxc_iim_open(struct inode *inode, struct file *filp) +{ + iim_clk = clk_get(NULL, "iim_clk"); + if (IS_ERR(iim_clk)) { + dev_err(iim_dev, "No IIM clock defined\n"); + return -ENODEV; + } + clk_enable(iim_clk); + + return 0; +} + +/*! + * MXC IIM interface - release function + * + * @param inode struct inode * + * @param filp struct file * + * + * @return Return 0 on success or negative error code on error + */ +static int mxc_iim_release(struct inode *inode, struct file *filp) +{ + clk_disable(iim_clk); + clk_put(iim_clk); + return 0; +} + +static const struct file_operations mxc_iim_fops = { + .mmap = mxc_iim_mmap, + .open = mxc_iim_open, + .release = mxc_iim_release, +}; + +static struct miscdevice mxc_iim_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "mxc_iim", + .fops = &mxc_iim_fops, +}; + +/*! + * This function is called by the driver framework to get iim base/end address + * and register iim misc device. + * + * @param dev The device structure for IIM passed in by the driver + * framework. + * + * @return Returns 0 on success or negative error code on error + */ +static int mxc_iim_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret; + + iim_dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (IS_ERR(res)) { + dev_err(iim_dev, "Unable to get IIM resource\n"); + return -ENODEV; + } + + iim_reg_base = res->start; + iim_reg_end = res->end; + iim_reg_size = iim_reg_end - iim_reg_base + 1; + + ret = misc_register(&mxc_iim_miscdev); + if (ret) + return ret; + + return 0; +} + +static int mxc_iim_remove(struct platform_device *pdev) +{ + misc_deregister(&mxc_iim_miscdev); + return 0; +} + +static struct platform_driver mxc_iim_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "mxc_iim", + }, + .probe = mxc_iim_probe, + .remove = mxc_iim_remove, +}; + +static int __init mxc_iim_dev_init(void) +{ + return platform_driver_register(&mxc_iim_driver); +} + +static void __exit mxc_iim_dev_cleanup(void) +{ + platform_driver_unregister(&mxc_iim_driver); +} + +module_init(mxc_iim_dev_init); +module_exit(mxc_iim_dev_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC IIM driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR); diff --git a/drivers/char/mxc_si4702.c b/drivers/char/mxc_si4702.c new file mode 100644 index 000000000000..d9fcce6cb439 --- /dev/null +++ b/drivers/char/mxc_si4702.c @@ -0,0 +1,1221 @@ +/* + * linux/drivers/char/mxc_si4702.c + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/cdev.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/mxc_si4702.h> +#include <asm/uaccess.h> +#include <mach/hardware.h> + +#define SI4702_DEV_NAME "si4702" +#define DEV_MAJOR 0 /* this could be module param */ +#define DEV_MINOR 0 +#define DEV_BASE_MINOR 0 +#define DEV_MINOR_COUNT 256 +#define SI4702_I2C_ADDR 0x10 /* 7bits I2C address */ +#define DELAY_WAIT 0xffff /* loop_counter max value */ +/* register define */ +#define SI4702_DEVICEID 0x00 +#define SI4702_CHIPID 0x01 +#define SI4702_POWERCFG 0x02 +#define SI4702_CHANNEL 0x03 +#define SI4702_SYSCONFIG1 0x04 +#define SI4702_SYSCONFIG2 0x05 +#define SI4702_SYSCONFIG3 0x06 +#define SI4702_TEST1 0x07 +#define SI4702_TEST2 0x08 +#define SI4702_B00TCONFIG 0x09 +#define SI4702_STATUSRSSI 0x0A +#define SI4702_READCHAN 0x0B +#define SI4702_REG_NUM 0x10 +#define SI4702_REG_BYTE (SI4702_REG_NUM * 2) +#define SI4702_DEVICE_ID 0x1242 +#define SI4702_RW_REG_NUM (SI4702_STATUSRSSI - SI4702_POWERCFG) +#define SI4702_RW_OFFSET \ + (SI4702_REG_NUM - SI4702_STATUSRSSI + SI4702_POWERCFG) + +#define SI4702_SPACE_MASK 0x0030 +#define SI4702_SPACE_200K 0x0 +#define SI4702_SPACE_100K 0x10 +#define SI4702_SPACE_50K 0x20 + +#define SI4702_BAND_MASK 0x00c0 +#define SI4702_BAND_LSB 6 + +#define SI4702_SEEKTH_MASK 0xff00 +#define SI4702_SEEKTH_LSB 8 + +#define SI4702_SNR_MASK 0x00f0 +#define SI4702_SNR_LSB 4 + +#define SI4702_CNT_MASK 0x000f +#define SI4702_CNT_LSB 0 + +#define SI4702_VOL_MASK 0x000f +#define SI4702_VOL_LSB 0 + +#define SI4702_CHAN_MASK 0x03ff +#define SI4702_TUNE_BIT 0x8000 +#define SI4702_STC_BIT 0x4000 +#define SI4702_DMUTE_BIT 0x4000 +#define SI4702_SEEKUP_BIT 0x0200 +#define SI4702_SEEK_BIT 0x0100 +#define SI4702_SF_BIT 0x2000 +#define SI4702_ENABLE_BIT 0x0001 +#define SI4702_DISABLE_BIT 0x0040 + +enum { + BAND_USA = 0, + BAND_JAP_W, + BAND_JAP +}; + +struct si4702_info { + int min_band; + int max_band; + int space; + int volume; + int channel; + int mute; +}; + +struct si4702_drvdata { + struct regulator *vio; + struct regulator *vdd; + struct class *radio_class; + struct si4702_info info; + /*by default, dev major is zero, and it's alloc dynamicaly. */ + int major; + int minor; + struct cdev *cdev; + int count; /* open count */ + struct i2c_client *client; + unsigned char reg_rw_buf[SI4702_REG_BYTE]; + struct mxc_fm_platform_data *plat_data; +}; + +static struct si4702_drvdata *si4702_drvdata; + +DEFINE_SPINLOCK(count_lock); + +#ifdef DEBUG +static void si4702_dump_reg(void) +{ + int i, j; + unsigned char *reg_rw_buf; + + if (NULL == si4702_drvdata) + return; + + reg_rw_buf = si4702_drvdata->reg_rw_buf; + + for (i = 0; i < 10; i++) { + j = i * 2 + 12; + pr_debug("reg[%02d] = %04x\n", i, + ((reg_rw_buf[j] << 8) & 0xFF00) + + (reg_rw_buf[j + 1] & 0x00FF)); + } + for (; i < 16; i++) { + j = (i - 10) * 2; + pr_debug("reg[%02d] = %04x\n", i, + ((reg_rw_buf[j] << 8) & 0xFF00) + + (reg_rw_buf[j + 1] & 0x00FF)); + } +} +#else +static void si4702_dump_reg(void) +{ +} +#endif /* DEBUG */ + +/* + *check the si4702 spec for the read/write concequence. + * + *0 2 A F0 A F + *------------------------------- + * buf:0 2 A F + */ +#define REG_to_BUF(reg) (((reg >= 0) && (reg < SI4702_STATUSRSSI))?\ + (reg - SI4702_STATUSRSSI + SI4702_REG_NUM):\ + ((reg >= SI4702_STATUSRSSI) && (reg < SI4702_REG_NUM))?\ + (reg - SI4702_STATUSRSSI) : -1) + +static int si4702_read_reg(const int reg, u16 *value) +{ + int ret, index; + unsigned char *reg_rw_buf; + + if (NULL == si4702_drvdata) + return -1; + + reg_rw_buf = si4702_drvdata->reg_rw_buf; + + index = REG_to_BUF(reg); + + if (-1 == index) + return -1; + + ret = + i2c_master_recv(si4702_drvdata->client, reg_rw_buf, + SI4702_REG_BYTE); + + *value = (reg_rw_buf[index * 2] << 8) & 0xFF00; + *value |= reg_rw_buf[index * 2 + 1] & 0x00FF; + + return ret < 0 ? ret : 0; +} + +static int si4702_write_reg(const int reg, const u16 value) +{ + int index, ret; + unsigned char *reg_rw_buf; + + if (NULL == si4702_drvdata) + return -1; + + reg_rw_buf = si4702_drvdata->reg_rw_buf; + + index = REG_to_BUF(reg); + + if (-1 == index) + return -1; + + reg_rw_buf[index * 2] = (value & 0xFF00) >> 8; + reg_rw_buf[index * 2 + 1] = value & 0x00FF; + + ret = i2c_master_send(si4702_drvdata->client, + ®_rw_buf[SI4702_RW_OFFSET * 2], + (SI4702_STATUSRSSI - SI4702_POWERCFG) * 2); + return ret < 0 ? ret : 0; +} + +static void si4702_gpio_get(void) +{ + if (NULL == si4702_drvdata) + return; + + si4702_drvdata->plat_data->gpio_get(); +} + +static void si4702_gpio_put(void) +{ + if (NULL == si4702_drvdata) + return; + + si4702_drvdata->plat_data->gpio_put(); +} + +static void si4702_reset(void) +{ + if (NULL == si4702_drvdata) + return; + + si4702_drvdata->plat_data->reset(); +} + +static void si4702_clock_en(int flag) +{ + if (NULL == si4702_drvdata) + return; + + si4702_drvdata->plat_data->clock_ctl(flag); +} + +static int si4702_id_detect(struct i2c_client *client) +{ + int ret, index; + unsigned int ID = 0; + unsigned char reg_rw_buf[SI4702_REG_BYTE]; + + si4702_gpio_get(); + si4702_reset(); + si4702_clock_en(1); + + ret = i2c_master_recv(client, (char *)reg_rw_buf, SI4702_REG_BYTE); + + si4702_gpio_put(); + + if (ret < 0) + return ret; + + index = REG_to_BUF(SI4702_DEVICEID); + if (index < 0) + return index; + + ID = (reg_rw_buf[index * 2] << 8) & 0xFF00; + ID |= reg_rw_buf[index * 2 + 1] & 0x00FF; + + return ID; +} + +/* valid args 50/100/200 */ +static int si4702_set_space(int space) +{ + u16 reg; + int ret; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG2, ®); + if (ret == -1) + return ret; + + reg &= ~SI4702_SPACE_MASK; + switch (space) { + case 50: + reg |= SI4702_SPACE_50K; + break; + case 100: + reg |= SI4702_SPACE_100K; + break; + case 200: + ret |= SI4702_SPACE_200K; + break; + default: + return -1; + } + + ret = si4702_write_reg(SI4702_SYSCONFIG2, reg); + if (ret == -1) + return ret; + + info = &si4702_drvdata->info; + info->space = space; + return 0; +} + +static int si4702_set_band_range(int band) +{ + u16 reg; + int ret, band_min, band_max; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + switch (band) { + case BAND_USA: + band_min = 87500; + band_max = 108000; + break; + case BAND_JAP_W: + band_min = 76000; + band_max = 108000; + break; + case BAND_JAP: + band_min = 76000; + band_max = 90000; + break; + default: + return -1; + } + + ret = si4702_read_reg(SI4702_SYSCONFIG2, ®); + if (ret == -1) + return ret; + + reg = (reg & ~SI4702_BAND_MASK) + | ((band << SI4702_BAND_LSB) & SI4702_BAND_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG2, reg); + if (ret == -1) + return ret; + + info = &si4702_drvdata->info; + info->min_band = band_min; + info->max_band = band_max; + return 0; +} + +static int si4702_set_seekth(u8 seekth) +{ + u16 reg; + int ret; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG2, ®); + if (ret == -1) + return ret; + + reg = + (reg & ~SI4702_SEEKTH_MASK) | ((seekth << SI4702_SEEKTH_LSB) & + SI4702_SEEKTH_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG2, reg); + if (ret == -1) + return ret; + + return 0; +} + +static int si4702_set_sksnr(u8 sksnr) +{ + u16 reg; + int ret; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG3, ®); + if (ret == -1) + return ret; + + reg = + (reg & ~SI4702_SNR_MASK) | ((sksnr << SI4702_SNR_LSB) & + SI4702_SNR_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG3, reg); + if (ret == -1) + return ret; + + return 0; +} + +static int si4702_set_skcnt(u8 skcnt) +{ + u16 reg; + int ret; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG3, ®); + if (ret == -1) + return ret; + + reg = (reg & ~SI4702_CNT_MASK) | (skcnt & SI4702_CNT_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG3, reg); + if (ret == -1) + return ret; + + return 0; +} + +static int si4702_set_vol(int vol) +{ + u16 reg; + int ret; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG2, ®); + if (ret == -1) + return ret; + + reg = (reg & ~SI4702_VOL_MASK) | (vol & SI4702_VOL_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG2, reg); + if (ret == -1) + return ret; + + info = &si4702_drvdata->info; + info->volume = vol; + + return 0; +} + +static u8 si4702_channel_select(u32 freq) +{ + u16 loop_counter = 0; + s16 channel; + u16 si4702_reg_data; + u8 error_ind = 0; + struct i2c_client *client; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + info = &si4702_drvdata->info; + client = si4702_drvdata->client; + + dev_info(&client->dev, "Input frequnce is %d\n", freq); + if (freq < 76000 || freq > 108000) { + dev_err(&client->dev, "Input frequnce is invalid\n"); + return -1; + } + /* convert freq to channel */ + channel = (freq - info->min_band) / info->space; + + si4702_reg_data = SI4702_TUNE_BIT | (channel & SI4702_CHAN_MASK); + /* set channel */ + error_ind = si4702_write_reg(SI4702_CHANNEL, si4702_reg_data); + if (error_ind) { + dev_err(&client->dev, "Failed to set channel\n"); + return -1; + } + dev_info(&client->dev, "Set channel to %d\n", channel); + + /* wait for STC == 1 */ + do { + error_ind = + si4702_read_reg(SI4702_STATUSRSSI, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, "Failed to read setted STC\n"); + return -1; + } + if ((si4702_reg_data & SI4702_STC_BIT) != 0) + break; + } while (++loop_counter < DELAY_WAIT); + + /* check loop_counter */ + if (loop_counter >= DELAY_WAIT) { + dev_err(&client->dev, "Can't wait for STC bit set"); + return -1; + } + dev_info(&client->dev, "loop counter %d\n", loop_counter); + + loop_counter = 0; + /* clear tune bit */ + error_ind = si4702_write_reg(SI4702_CHANNEL, 0); + + if (error_ind) { + dev_err(&client->dev, "Failed to set stop tune\n"); + return -1; + } + + /* wait for STC == 0 */ + do { + error_ind = + si4702_read_reg(SI4702_STATUSRSSI, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, "Failed to set read STC\n"); + return -1; + } + if ((si4702_reg_data & SI4702_STC_BIT) == 0) + break; + } while (++loop_counter < DELAY_WAIT); + + /* check loop_counter */ + if (loop_counter >= DELAY_WAIT) { + dev_err(&client->dev, "Can't wait for STC bit clean"); + return -1; + } + dev_info(&client->dev, "loop counter %d\n", loop_counter); + + /* read RSSI */ + error_ind = si4702_read_reg(SI4702_READCHAN, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, "Failed to read RSSI\n"); + return -1; + } + + channel = si4702_reg_data & SI4702_CHAN_MASK; + dev_info(&client->dev, "seek finish: channel(%d)\n", channel); + + return 0; +} + +static s32 si4702_channel_seek(s16 dir) +{ + u16 loop_counter = 0; + u16 si4702_reg_data, reg_power_cfg; + u8 error_ind = 0; + u32 channel, freq; + struct i2c_client *client; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + info = &si4702_drvdata->info; + client = si4702_drvdata->client; + + error_ind = si4702_read_reg(SI4702_POWERCFG, ®_power_cfg); + + if (info->mute) { + /* check disable mute */ + reg_power_cfg &= ~SI4702_DMUTE_BIT; + } else { + reg_power_cfg |= SI4702_DMUTE_BIT; + } + + if (dir) { + reg_power_cfg |= SI4702_SEEKUP_BIT; + } else { + reg_power_cfg &= ~SI4702_SEEKUP_BIT; + } + /* start seek */ + reg_power_cfg |= SI4702_SEEK_BIT; + error_ind = si4702_write_reg(SI4702_POWERCFG, reg_power_cfg); + + if (error_ind) { + dev_err(&client->dev, "Failed to set seek start bit\n"); + return -1; + } + + /* wait STC == 1 */ + do { + error_ind = + si4702_read_reg(SI4702_STATUSRSSI, &si4702_reg_data); + if (error_ind) { + dev_err(&client->dev, "Failed to read STC bit\n"); + return -1; + } + + if ((si4702_reg_data & SI4702_STC_BIT) != 0) + break; + } while (++loop_counter < DELAY_WAIT); + + /* clear seek bit */ + reg_power_cfg &= ~SI4702_SEEK_BIT; + error_ind = si4702_write_reg(SI4702_POWERCFG, reg_power_cfg); + if (error_ind) { + dev_err(&client->dev, "Failed to stop seek\n"); + return -1; + } + + if (loop_counter >= DELAY_WAIT) { + dev_err(&client->dev, "Can't wait for STC bit set\n"); + return -1; + } + + /* check whether SF==1 (seek failed bit) */ + if ((si4702_reg_data & SI4702_SF_BIT) != 0) { + dev_err(&client->dev, "Failed to seek any channel\n"); + return -1; + } + + loop_counter = 0; + /* wait STC == 0 */ + do { + error_ind = + si4702_read_reg(SI4702_STATUSRSSI, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, + "Failed to wait STC bit to clear\n"); + return -1; + } + if ((si4702_reg_data & SI4702_STC_BIT) == 0) + break; + } while (++loop_counter < DELAY_WAIT); + + /* check loop_counter */ + if (loop_counter >= DELAY_WAIT) { + dev_err(&client->dev, "Can't wait for STC bit clean"); + return -1; + } + + error_ind = si4702_read_reg(SI4702_READCHAN, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, "I2C simulate failed\n"); + return -1; + } + + channel = si4702_reg_data & SI4702_CHAN_MASK; + freq = channel * info->space + info->min_band; + dev_err(&client->dev, + "seek finish: channel(%d), freq(%dKHz)\n", channel, freq); + + return 0; +} + +static int si4702_startup(void) +{ + u16 magic = 0, id; + struct i2c_client *client; + struct mxc_fm_platform_data *data; + + if (NULL == si4702_drvdata) + return -1; + + if (si4702_drvdata->vio) + regulator_enable(si4702_drvdata->vio); + if (si4702_drvdata->vdd) + regulator_enable(si4702_drvdata->vdd); + data = si4702_drvdata->plat_data; + client = si4702_drvdata->client; + + /* read prior to write, otherwise write op will fail */ + si4702_read_reg(SI4702_DEVICEID, &id); + dev_err(&client->dev, "si4702: DEVICEID: 0x%x\n", id); + + si4702_clock_en(1); + msleep(100); + + /* disable mute, stereo, seek down, powerup */ + si4702_write_reg(SI4702_POWERCFG, SI4702_DMUTE_BIT | SI4702_ENABLE_BIT); + msleep(500); + si4702_read_reg(SI4702_TEST1, &magic); + if (magic != 0x3C04) + dev_err(&client->dev, "magic number 0x%x.\n", magic); + /* close tune, set channel to 0 */ + si4702_write_reg(SI4702_CHANNEL, 0); + /* disable interrupt, disable GPIO */ + si4702_write_reg(SI4702_SYSCONFIG1, 0); + /* set volume to middle level */ + si4702_set_vol(0xf); + + si4702_set_space(data->space); + si4702_set_band_range(data->band); + si4702_set_seekth(data->seekth); + si4702_set_skcnt(data->skcnt); + si4702_set_sksnr(data->sksnr); + + return 0; +} + +static void si4702_shutdown(void) +{ + if (NULL == si4702_drvdata) + return; + + si4702_write_reg(SI4702_POWERCFG, SI4702_DMUTE_BIT | + SI4702_ENABLE_BIT | SI4702_DISABLE_BIT); + msleep(100); + si4702_clock_en(0); + + if (si4702_drvdata->vdd) + regulator_disable(si4702_drvdata->vdd); + if (si4702_drvdata->vio) + regulator_disable(si4702_drvdata->vio); +} + +enum { + FM_STARTUP = 0, + FM_SHUTDOWN, + FM_RESET, + FM_VOLUP, + FM_VOLDOWN, + FM_SEEK_UP, + FM_SEEK_DOWN, + FM_MUTEON, + FM_MUTEDIS, + FM_SEL, + FM_SEEKTH, + FM_DL, + FM_CTL_MAX +}; + +static const char *const fm_control[FM_CTL_MAX] = { + [FM_STARTUP] = "start", + [FM_SHUTDOWN] = "halt", + [FM_RESET] = "reset", + [FM_VOLUP] = "volup", + [FM_VOLDOWN] = "voldown", + [FM_SEEK_UP] = "seeku", + [FM_SEEK_DOWN] = "seekd", + [FM_MUTEON] = "mute", + [FM_MUTEDIS] = "muted", + [FM_SEL] = "select", + [FM_SEEKTH] = "seekth", + [FM_DL] = "delay" +}; + +static int cmd(unsigned int index, int arg) +{ + struct i2c_client *client; + struct mxc_fm_platform_data *plat_data; + + if (NULL == si4702_drvdata) + return -1; + + client = si4702_drvdata->client; + plat_data = si4702_drvdata->plat_data; + + switch (index) { + case FM_SHUTDOWN: + dev_err(&client->dev, "FM_SHUTDOWN\n"); + si4702_shutdown(); + break; + case FM_STARTUP: + dev_err(&client->dev, "FM_STARTUP\n"); + si4702_reset(); + si4702_startup(); + break; + case FM_RESET: + dev_err(&client->dev, "FM_RESET\n"); + si4702_reset(); + break; + case FM_SEEK_DOWN: + dev_err(&client->dev, "SEEK DOWN\n"); + si4702_channel_seek(0); + break; + case FM_SEEK_UP: + dev_err(&client->dev, "SEEK UP\n"); + si4702_channel_seek(1); + break; + case FM_SEL: + dev_err(&client->dev, "select %d\n", arg * 100); + si4702_channel_select(arg * 100); + break; + case FM_SEEKTH: + dev_err(&client->dev, "seekth = %d\n", arg); + si4702_set_seekth(arg); + break; + case FM_DL: + dev_err(&client->dev, "delay = %d\n", arg); + break; + default: + dev_err(&client->dev, "error command\n"); + break; + } + return 0; +} + +static ssize_t si4702_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct si4702_drvdata *drv_data = dev_get_drvdata(dev); + u16 device_id; + + dev_err(&(drv_data->client->dev), "si4702 show\n"); + si4702_read_reg(SI4702_DEVICEID, &device_id); + pr_info("device id %x\n", device_id); + si4702_dump_reg(); + return 0; +} + +static ssize_t si4702_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int state = 0; + const char *const *s; + char *p = NULL; + int error; + int len, arg = 0; + struct si4702_drvdata *drv_data = dev_get_drvdata(dev); + struct i2c_client *client = drv_data->client; + + dev_err(&client->dev, "si4702 store %d\n", count); + + p = memchr(buf, ' ', count); + if (p) { + len = p - buf; + *p = '\0'; + } else + len = count; + + len -= 1; + dev_err(&client->dev, "cmd %s\n", buf); + + for (s = &fm_control[state]; state < FM_CTL_MAX; s++, state++) { + if (*s && !strncmp(buf, *s, len)) { + break; + } + } + if (state < FM_CTL_MAX && *s) { + if (p) + arg = simple_strtoul(p + 1, NULL, 0); + dev_err(&client->dev, "arg = %d\n", arg); + error = cmd(state, arg); + } else { + dev_err(&client->dev, "error cmd\n"); + error = -EINVAL; + } + + return error ? error : count; +} + +static int ioctl_si4702(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int mute = 0; + u16 data; + int error; + u8 volume; + unsigned int freq; + int dir; + struct i2c_client *client; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + info = &si4702_drvdata->info; + client = si4702_drvdata->client; + + dev_err(&client->dev, "ioctl, cmd: 0x%x, arg: 0x%lx\n", cmd, arg); + + switch (cmd) { + case SI4702_SETVOLUME: + /* get volume from user */ + if (copy_from_user(&volume, argp, sizeof(u8))) { + + dev_err(&client->dev, + "ioctl, copy volume value from user failed\n"); + return -EFAULT; + } + dev_err(&client->dev, "volume %d\n", volume); + /* refill the register value */ + volume &= 0x0f; + if (info->mute) + error = si4702_write_reg(SI4702_POWERCFG, 0x0001); + else + error = si4702_write_reg(SI4702_POWERCFG, 0x4001); + + error = si4702_write_reg(SI4702_CHANNEL, 0); + error = si4702_write_reg(SI4702_SYSCONFIG1, 0); + error = si4702_write_reg(SI4702_SYSCONFIG2, 0x0f10 | volume); + if (error) { + dev_err(&client->dev, "ioctl, set volume failed\n"); + return -EFAULT; + } + /* renew the device info */ + info->volume = volume; + + break; + case SI4702_GETVOLUME: + /* just copy volume value to user */ + if (copy_to_user(argp, &(info->volume), sizeof(unsigned int))) { + dev_err(&client->dev, "ioctl, copy to user failed\n"); + return -EFAULT; + } + break; + case SI4702_MUTEON: + mute = 1; + case SI4702_MUTEOFF: + if (mute) { + /* enable mute */ + si4702_read_reg(SI4702_POWERCFG, &data); + data &= 0x00FF; + error = si4702_write_reg(SI4702_POWERCFG, data); + } else { + si4702_read_reg(SI4702_POWERCFG, &data); + data &= 0x00FF; + data |= 0x4000; + error = si4702_write_reg(SI4702_POWERCFG, data); + } + if (error) { + dev_err(&client->dev, "ioctl, set mute failed\n"); + return -EFAULT; + } + break; + case SI4702_SELECT: + if (copy_from_user(&freq, argp, sizeof(unsigned int))) { + + dev_err(&client->dev, + "ioctl, copy frequence from user failed\n"); + return -EFAULT; + } + /* check frequence */ + if (freq > info->max_band || freq < info->min_band) { + dev_err(&client->dev, + "the frequence select is out of band\n"); + return -EINVAL; + } + if (si4702_channel_select(freq)) { + dev_err(&client->dev, + "ioctl, failed to select channel\n"); + return -EFAULT; + } + break; + case SI4702_SEEK: + if (copy_from_user(&dir, argp, sizeof(int))) { + + dev_err(&client->dev, "ioctl, copy from user failed\n"); + return -EFAULT; + } + /* get seeked channel */ + dir = si4702_channel_seek(dir); + if (dir == -1) { + return -EAGAIN; + } else if (dir == -2) { + return -EFAULT; + } + if (copy_to_user(argp, &dir, sizeof(int))) { + + dev_err(&client->dev, + "ioctl, copy seek frequnce to user failed\n"); + return -EFAULT; + } + break; + default: + dev_err(&client->dev, "SI4702: Invalid ioctl command\n"); + return -EINVAL; + + } + return 0; +} + +static int open_si4702(struct inode *inode, struct file *file) +{ + struct i2c_client *client; + + if (NULL == si4702_drvdata) + return -1; + + client = si4702_drvdata->client; + + spin_lock(&count_lock); + if (si4702_drvdata->count != 0) { + dev_err(&client->dev, "device has been open already\n"); + spin_unlock(&count_lock); + return -EBUSY; + } + si4702_drvdata->count++; + spin_unlock(&count_lock); + + /* request and active GPIO */ + si4702_gpio_get(); + /* reset the si4702 from it's reset pin */ + si4702_reset(); + + /* startup si4702 */ + if (si4702_startup()) { + spin_lock(&count_lock); + si4702_drvdata->count--; + spin_unlock(&count_lock); + return -ENODEV; + } + + return 0; +} + +static int release_si4702(struct inode *inode, struct file *file) +{ + struct i2c_client *client; + + if (NULL == si4702_drvdata) + return -1; + + client = si4702_drvdata->client; + + dev_err(&client->dev, "release\n"); + /* software shutdown */ + si4702_shutdown(); + /* inactive, free GPIO, cut power */ + si4702_gpio_put(); + + spin_lock(&count_lock); + si4702_drvdata->count--; + spin_unlock(&count_lock); + + return 0; +} + +static int si4702_suspend(struct i2c_client *client, pm_message_t state) +{ + return 0; +} + +static int si4702_resume(struct i2c_client *client) +{ + return 0; +} + +static struct device_attribute si4702_dev_attr = { + .attr = { + .name = "si4702_ctl", + .mode = S_IRUSR | S_IWUSR, + }, + .show = si4702_show, + .store = si4702_store, +}; + +static struct file_operations si4702_fops = { + .owner = THIS_MODULE, + .open = open_si4702, + .release = release_si4702, + .ioctl = ioctl_si4702, +}; + +static int __devinit si4702_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct mxc_fm_platform_data *plat_data; + struct si4702_drvdata *drv_data; + struct device *dev; + + dev_info(&client->dev, "si4702 device probe process start.\n"); + + plat_data = (struct mxc_fm_platform_data *)client->dev.platform_data; + if (plat_data == NULL) { + dev_err(&client->dev, "lack of platform data!\n"); + return -ENODEV; + } + + drv_data = kmalloc(sizeof(struct si4702_drvdata), GFP_KERNEL); + if (drv_data == NULL) { + dev_err(&client->dev, "lack of kernel memory!\n"); + return -ENOMEM; + } + memset(drv_data, 0, sizeof(struct si4702_drvdata)); + drv_data->plat_data = plat_data; + drv_data->major = DEV_MAJOR; + drv_data->minor = DEV_MINOR; + drv_data->count = 0; + + /*enable power supply */ + if (plat_data->reg_vio != NULL) { + drv_data->vio = regulator_get(&client->dev, plat_data->reg_vio); + if (drv_data->vio == ERR_PTR(-ENOENT)) + goto free_drv_data; + regulator_enable(drv_data->vio); + } + + /* here, we assume that vio and vdd are not the same */ + if (plat_data->reg_vdd != NULL) { + drv_data->vdd = regulator_get(&client->dev, plat_data->reg_vdd); + if (drv_data->vdd == ERR_PTR(-ENOENT)) + goto disable_vio; + regulator_enable(drv_data->vdd); + } + + /*attach client and check device id */ + if (SI4702_DEVICE_ID != si4702_id_detect(client)) { + dev_err(&client->dev, "id wrong.\n"); + goto disable_vdd; + } + dev_info(&client->dev, "chip id %x detect.\n", SI4702_DEVICE_ID); + drv_data->client = client; + + /*user interface begain */ + /*create device file in sysfs as a user interface, + * also for debug support */ + ret = device_create_file(&client->dev, &si4702_dev_attr); + if (ret) { + dev_err(&client->dev, "create device file failed!\n"); + goto gpio_put; /* shall i use some meanful error code? */ + } + + /*create a char dev for application code access */ + if (drv_data->major) { + ret = register_chrdev(drv_data->major, "si4702", &si4702_fops);; + } else { + ret = register_chrdev(0, "si4702", &si4702_fops); + } + + if (drv_data->major == 0) + drv_data->major = ret; + + /* create class and device for udev information */ + drv_data->radio_class = class_create(THIS_MODULE, "radio"); + if (IS_ERR(drv_data->radio_class)) { + dev_err(&client->dev, "SI4702: failed to create radio class\n"); + goto char_dev_remove; + } + + dev = device_create(drv_data->radio_class, NULL, + MKDEV(drv_data->major, drv_data->minor), NULL, "si4702"); + if (IS_ERR(dev)) { + dev_err(&client->dev, + "SI4702: failed to create radio class device\n"); + goto class_remove; + } + /*User interface end */ + dev_set_drvdata(&client->dev, drv_data); + si4702_drvdata = drv_data; + + si4702_gpio_get(); + dev_info(&client->dev, "si4702 device probe successfully.\n"); + si4702_shutdown(); + + return 0; + +class_remove: + class_destroy(drv_data->radio_class); +char_dev_remove: + unregister_chrdev(drv_data->major, "si4702"); + device_remove_file(&client->dev, &si4702_dev_attr); +gpio_put: + si4702_gpio_put(); +disable_vdd: + if (plat_data->reg_vdd) { + regulator_disable(drv_data->vdd); + regulator_put(drv_data->vdd); + } +disable_vio: + if (plat_data->reg_vio) { + regulator_disable(drv_data->vio); + regulator_put(drv_data->vio); + } + +free_drv_data: + kfree(drv_data); + + return -ENODEV; +} + +static int __devexit si4702_remove(struct i2c_client *client) +{ + struct mxc_fm_platform_data *plat_data; + struct si4702_drvdata *drv_data = dev_get_drvdata(&client->dev); + + plat_data = (struct mxc_fm_platform_data *)client->dev.platform_data; + + device_destroy(drv_data->radio_class, + MKDEV(drv_data->major, drv_data->minor)); + class_destroy(drv_data->radio_class); + + unregister_chrdev(drv_data->major, "si4702"); + device_remove_file(&client->dev, &si4702_dev_attr); + si4702_gpio_put(); + + if (plat_data->reg_vdd) + regulator_put(drv_data->vdd); + + if (plat_data->reg_vio) + regulator_put(drv_data->vio); + + kfree(si4702_drvdata); + si4702_drvdata = NULL; + + return 0; +} + +static const struct i2c_device_id si4702_id[] = { + {"si4702", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, si4702_id); + +static struct i2c_driver i2c_si4702_driver = { + .driver = { + .name = "si4702", + }, + .probe = si4702_probe, + .remove = si4702_remove, + .suspend = si4702_suspend, + .resume = si4702_resume, + .id_table = si4702_id, +}; + +static int __init init_si4702(void) +{ + /*add to i2c driver */ + pr_info("add si4702 i2c driver\n"); + return i2c_add_driver(&i2c_si4702_driver); +} + +static void __exit exit_si4702(void) +{ + i2c_del_driver(&i2c_si4702_driver); +} + +module_init(init_si4702); +module_exit(exit_si4702); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("SI4702 FM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/mxs_viim.c b/drivers/char/mxs_viim.c new file mode 100644 index 000000000000..2510afa6c13e --- /dev/null +++ b/drivers/char/mxs_viim.c @@ -0,0 +1,175 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/mm.h> +#include <linux/miscdevice.h> + +static unsigned long iim_reg_base0, iim_reg_end0, iim_reg_size0; +static unsigned long iim_reg_base1, iim_reg_end1, iim_reg_size1; +static struct device *iim_dev; + +/*! + * MXS Virtual IIM interface - memory map function + * This function maps one page size VIIM registers from VIIM base address0 + * if the size of the required virtual memory space is less than or equal to + * one page size, otherwise this function will also map one page size VIIM + * registers from VIIM base address1. + * + * @param file struct file * + * @param vma structure vm_area_struct * + * + * @return Return 0 on success or negative error code on error + */ +static int mxs_viim_mmap(struct file *file, struct vm_area_struct *vma) +{ + size_t size = vma->vm_end - vma->vm_start; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Remap-pfn-range will mark the range VM_IO and VM_RESERVED */ + if (remap_pfn_range(vma, + vma->vm_start, + iim_reg_base0 >> PAGE_SHIFT, + iim_reg_size0, + vma->vm_page_prot)) + return -EAGAIN; + + if (size > iim_reg_size0) { + if (remap_pfn_range(vma, + vma->vm_start + iim_reg_size0, + iim_reg_base1 >> PAGE_SHIFT, + iim_reg_size1, + vma->vm_page_prot)) + return -EAGAIN; + } + + return 0; +} + +/*! + * MXS Virtual IIM interface - open function + * + * @param inode struct inode * + * @param filp struct file * + * + * @return Return 0 on success or negative error code on error + */ +static int mxs_viim_open(struct inode *inode, struct file *filp) +{ + return 0; +} + +/*! + * MXS Virtual IIM interface - release function + * + * @param inode struct inode * + * @param filp struct file * + * + * @return Return 0 on success or negative error code on error + */ +static int mxs_viim_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +static const struct file_operations mxs_viim_fops = { + .mmap = mxs_viim_mmap, + .open = mxs_viim_open, + .release = mxs_viim_release, +}; + +static struct miscdevice mxs_viim_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "mxs_viim", + .fops = &mxs_viim_fops, +}; + +/*! + * This function is called by the driver framework to get virtual iim base/end + * address and register iim misc device. + * + * @param dev The device structure for Virtual IIM passed in by the + * driver framework. + * + * @return Returns 0 on success or negative error code on error + */ +static int mxs_viim_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret; + + iim_dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (IS_ERR(res)) { + dev_err(iim_dev, "Unable to get Virtual IIM resource 0\n"); + return -ENODEV; + } + + iim_reg_base0 = res->start; + iim_reg_end0 = res->end; + iim_reg_size0 = iim_reg_end0 - iim_reg_base0 + 1; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (IS_ERR(res)) { + dev_err(iim_dev, "Unable to get Virtual IIM resource 1\n"); + return -ENODEV; + } + + iim_reg_base1 = res->start; + iim_reg_end1 = res->end; + iim_reg_size1 = iim_reg_end1 - iim_reg_base1 + 1; + + ret = misc_register(&mxs_viim_miscdev); + if (ret) + return ret; + + return 0; +} + +static int mxs_viim_remove(struct platform_device *pdev) +{ + misc_deregister(&mxs_viim_miscdev); + return 0; +} + +static struct platform_driver mxs_viim_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "mxs_viim", + }, + .probe = mxs_viim_probe, + .remove = mxs_viim_remove, +}; + +static int __init mxs_viim_dev_init(void) +{ + return platform_driver_register(&mxs_viim_driver); +} + +static void __exit mxs_viim_dev_cleanup(void) +{ + platform_driver_unregister(&mxs_viim_driver); +} + +module_init(mxs_viim_dev_init); +module_exit(mxs_viim_dev_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXS Virtual IIM driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR); diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig index 5b27692372bf..0b9f988564c1 100644 --- a/drivers/crypto/Kconfig +++ b/drivers/crypto/Kconfig @@ -209,4 +209,16 @@ config CRYPTO_DEV_PPC4XX help This option allows you to have support for AMCC crypto acceleration. +config CRYPTO_DEV_STMP3XXX_DCP + tristate "Support for the STMP3xxx DCP engine" + depends on ARCH_STMP3XXX + select CRYPTO_ALGAPI + select CRYPTO_BLKCIPHER + help + Say 'Y' here to use the STMP3XXX DCP AES + engine for the CryptoAPI AES algorithm. + + To compile this driver as a module, choose M here: the module + will be called geode-aes. + endif # CRYPTO_HW diff --git a/drivers/crypto/Makefile b/drivers/crypto/Makefile index 9bf4a2bc8846..f69ef96f599e 100644 --- a/drivers/crypto/Makefile +++ b/drivers/crypto/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_CRYPTO_DEV_HIFN_795X) += hifn_795x.o obj-$(CONFIG_CRYPTO_DEV_TALITOS) += talitos.o obj-$(CONFIG_CRYPTO_DEV_IXP4XX) += ixp4xx_crypto.o obj-$(CONFIG_CRYPTO_DEV_PPC4XX) += amcc/ +obj-$(CONFIG_CRYPTO_DEV_STMP3XXX_DCP) += stmp3xxx_dcp.o diff --git a/drivers/crypto/stmp3xxx_dcp.c b/drivers/crypto/stmp3xxx_dcp.c new file mode 100644 index 000000000000..d5762fe8c813 --- /dev/null +++ b/drivers/crypto/stmp3xxx_dcp.c @@ -0,0 +1,1401 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * Based on geode-aes.c + * Copyright (C) 2004-2006, Advanced Micro Devices, Inc. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/crypto.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <crypto/algapi.h> +#include <crypto/aes.h> +#include <crypto/sha.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/delay.h> + +#include <linux/io.h> +#include <linux/delay.h> + +#include <asm/cacheflush.h> + +#include "stmp3xxx_dcp.h" + +/* SHA1 is busted */ +#define DISABLE_SHA1 + +struct stmp3xxx_dcp { + struct device *dev; + spinlock_t lock; + struct mutex op_mutex[STMP3XXX_DCP_NUM_CHANNELS]; + struct completion op_wait[STMP3XXX_DCP_NUM_CHANNELS]; + int wait[STMP3XXX_DCP_NUM_CHANNELS]; + int dcp_vmi_irq; + int dcp_irq; +}; + +/* cipher flags */ +#define STMP3XXX_DCP_ENC 0x0001 +#define STMP3XXX_DCP_DEC 0x0002 +#define STMP3XXX_DCP_ECB 0x0004 +#define STMP3XXX_DCP_CBC 0x0008 +#define STMP3XXX_DCP_CBC_INIT 0x0010 +#define STMP3XXX_DCP_OTPKEY 0x0020 + +/* hash flags */ +#define STMP3XXX_DCP_INIT 0x0001 +#define STMP3XXX_DCP_UPDATE 0x0002 +#define STMP3XXX_DCP_FINAL 0x0004 + +#define STMP3XXX_DCP_AES 0x1000 +#define STMP3XXX_DCP_SHA1 0x2000 +#define STMP3XXX_DCP_CRC32 0x3000 +#define STMP3XXX_DCP_COPY 0x4000 +#define STMP3XXX_DCP_FILL 0x5000 +#define STMP3XXX_DCP_MODE_MASK 0xf000 + +struct stmp3xxx_dcp_op { + + unsigned int flags; + + void *src; + dma_addr_t src_phys; + + void *dst; + dma_addr_t dst_phys; + + int len; + + /* the key contains the IV for block modes */ + union { + struct { + u8 key[2 * AES_KEYSIZE_128] + __attribute__ ((__aligned__(32))); + dma_addr_t key_phys; + int keylen; + } cipher; + struct { + u8 digest[SHA1_DIGEST_SIZE] + __attribute__ ((__aligned__(32))); + dma_addr_t digest_phys; + int digestlen; + int init; + } hash; + }; + + union { + struct crypto_blkcipher *blk; + struct crypto_cipher *cip; + struct crypto_hash *hash; + } fallback; + + struct stmp3xxx_dcp_hw_packet pkt + __attribute__ ((__aligned__(32))); +}; + +struct stmp3xxx_dcp_hash_coherent_block { + struct stmp3xxx_dcp_hw_packet pkt[2] + __attribute__ ((__aligned__(32))); + u8 digest[SHA1_DIGEST_SIZE] + __attribute__ ((__aligned__(32))); + u8 rembuf[32]; +}; + +struct stmp3xxx_dcp_hash_op { + + unsigned int flags; + + void *src; + dma_addr_t src_phys; + + void *dst; + dma_addr_t dst_phys; + + int len; + + /* the key contains the IV for block modes */ + union { + struct { + u8 key[2 * AES_KEYSIZE_128] + __attribute__ ((__aligned__(32))); + dma_addr_t key_phys; + int keylen; + } cipher; + struct { + u8 digest[SHA1_DIGEST_SIZE] + __attribute__ ((__aligned__(32))); + dma_addr_t digest_phys; + int digestlen; + int init; + } hash; + }; + + union { + struct crypto_blkcipher *blk; + struct crypto_cipher *cip; + struct crypto_hash *hash; + } fallback; + + struct stmp3xxx_dcp_hash_coherent_block *hw; + dma_addr_t hw_phys; +}; + +/* only one */ +static struct stmp3xxx_dcp *global_sdcp; + +static void dcp_perform_op(struct stmp3xxx_dcp_op *op) +{ + struct stmp3xxx_dcp *sdcp = global_sdcp; + struct mutex *mutex; + struct stmp3xxx_dcp_hw_packet *pkt; + int chan; + u32 pkt1, pkt2; + unsigned long timeout; + dma_addr_t pkt_phys; + u32 stat; + + pkt1 = BM_DCP_PACKET1_DECR_SEMAPHORE | BM_DCP_PACKET1_INTERRUPT; + + switch (op->flags & STMP3XXX_DCP_MODE_MASK) { + + case STMP3XXX_DCP_AES: + + chan = CIPHER_CHAN; + + /* key is at the payload */ + pkt1 |= BM_DCP_PACKET1_ENABLE_CIPHER; + if ((op->flags & STMP3XXX_DCP_OTPKEY) == 0) + pkt1 |= BM_DCP_PACKET1_PAYLOAD_KEY; + if (op->flags & STMP3XXX_DCP_ENC) + pkt1 |= BM_DCP_PACKET1_CIPHER_ENCRYPT; + if (op->flags & STMP3XXX_DCP_CBC_INIT) + pkt1 |= BM_DCP_PACKET1_CIPHER_INIT; + + pkt2 = BF(0, DCP_PACKET2_CIPHER_CFG) | + BF(0, DCP_PACKET2_KEY_SELECT) | + BF(BV_DCP_PACKET2_CIPHER_SELECT__AES128, + DCP_PACKET2_CIPHER_SELECT); + + if (op->flags & STMP3XXX_DCP_ECB) + pkt2 |= BF(BV_DCP_PACKET2_CIPHER_MODE__ECB, + DCP_PACKET2_CIPHER_MODE); + else if (op->flags & STMP3XXX_DCP_CBC) + pkt2 |= BF(BV_DCP_PACKET2_CIPHER_MODE__CBC, + DCP_PACKET2_CIPHER_MODE); + + break; + + case STMP3XXX_DCP_SHA1: + + chan = HASH_CHAN; + + pkt1 |= BM_DCP_PACKET1_ENABLE_HASH; + if (op->flags & STMP3XXX_DCP_INIT) + pkt1 |= BM_DCP_PACKET1_HASH_INIT; + if (op->flags & STMP3XXX_DCP_FINAL) { + pkt1 |= BM_DCP_PACKET1_HASH_TERM; + BUG_ON(op->hash.digest == NULL); + } + + pkt2 = BF(BV_DCP_PACKET2_HASH_SELECT__SHA1, + DCP_PACKET2_HASH_SELECT); + break; + + default: + dev_err(sdcp->dev, "Unsupported mode\n"); + return; + } + + mutex = &sdcp->op_mutex[chan]; + pkt = &op->pkt; + + pkt->pNext = 0; + pkt->pkt1 = pkt1; + pkt->pkt2 = pkt2; + pkt->pSrc = (u32)op->src_phys; + pkt->pDst = (u32)op->dst_phys; + pkt->size = op->len; + pkt->pPayload = chan == CIPHER_CHAN ? + (u32)op->cipher.key_phys : (u32)op->hash.digest_phys; + pkt->stat = 0; + + pkt_phys = dma_map_single(sdcp->dev, pkt, sizeof(*pkt), + DMA_BIDIRECTIONAL); + if (dma_mapping_error(sdcp->dev, pkt_phys)) { + dev_err(sdcp->dev, "Unable to map packet descriptor\n"); + return; + } + + /* submit the work */ + mutex_lock(mutex); + + stmp3xxx_clearl(-1, REGS_DCP_BASE + HW_DCP_CHnSTAT(chan)); + + /* Load the work packet pointer and bump the channel semaphore */ + __raw_writel((u32)pkt_phys, REGS_DCP_BASE + HW_DCP_CHnCMDPTR(chan)); + + /* XXX wake from interrupt instead of looping */ + timeout = jiffies + msecs_to_jiffies(1000); + + sdcp->wait[chan] = 0; + __raw_writel(BF(1, DCP_CHnSEMA_INCREMENT), REGS_DCP_BASE + HW_DCP_CHnSEMA(chan)); + while (time_before(jiffies, timeout) && sdcp->wait[chan] == 0) + cpu_relax(); + + if (!time_before(jiffies, timeout)) { + dev_err(sdcp->dev, "Timeout while waiting STAT 0x%08x\n", + __raw_readl(REGS_DCP_BASE + HW_DCP_STAT)); + goto out; + } + + stat = __raw_readl(REGS_DCP_BASE + HW_DCP_CHnSTAT(chan)); + if ((stat & 0xff) != 0) + dev_err(sdcp->dev, "Channel stat error 0x%02x\n", + __raw_readl(REGS_DCP_BASE + HW_DCP_CHnSTAT(chan)) & 0xff); +out: + mutex_unlock(mutex); + + dma_unmap_single(sdcp->dev, pkt_phys, sizeof(*pkt), DMA_TO_DEVICE); +} + +static int dcp_aes_setkey_cip(struct crypto_tfm *tfm, const u8 *key, + unsigned int len) +{ + struct stmp3xxx_dcp_op *op = crypto_tfm_ctx(tfm); + unsigned int ret; + + op->cipher.keylen = len; + + if (len == AES_KEYSIZE_128) { + memcpy(op->cipher.key, key, len); + return 0; + } + + if (len != AES_KEYSIZE_192 && len != AES_KEYSIZE_256) { + /* not supported at all */ + tfm->crt_flags |= CRYPTO_TFM_RES_BAD_KEY_LEN; + return -EINVAL; + } + + /* + * The requested key size is not supported by HW, do a fallback + */ + op->fallback.blk->base.crt_flags &= ~CRYPTO_TFM_REQ_MASK; + op->fallback.blk->base.crt_flags |= (tfm->crt_flags & + CRYPTO_TFM_REQ_MASK); + + ret = crypto_cipher_setkey(op->fallback.cip, key, len); + if (ret) { + tfm->crt_flags &= ~CRYPTO_TFM_RES_MASK; + tfm->crt_flags |= (op->fallback.blk->base.crt_flags & + CRYPTO_TFM_RES_MASK); + } + return ret; +} + +static void dcp_aes_encrypt(struct crypto_tfm *tfm, u8 *out, const u8 *in) +{ + struct stmp3xxx_dcp *sdcp = global_sdcp; + struct stmp3xxx_dcp_op *op = crypto_tfm_ctx(tfm); + + if (unlikely(op->cipher.keylen != AES_KEYSIZE_128)) { + crypto_cipher_encrypt_one(op->fallback.cip, out, in); + return; + } + + op->src = (void *) in; + op->dst = (void *) out; + op->flags = STMP3XXX_DCP_AES | STMP3XXX_DCP_ENC | STMP3XXX_DCP_ECB; + op->len = AES_KEYSIZE_128; + + /* map the data */ + op->src_phys = dma_map_single(sdcp->dev, (void *)in, AES_KEYSIZE_128, + DMA_TO_DEVICE); + if (dma_mapping_error(sdcp->dev, op->src_phys)) { + dev_err(sdcp->dev, "Unable to map source\n"); + return; + } + + op->dst_phys = dma_map_single(sdcp->dev, out, AES_KEYSIZE_128, + DMA_FROM_DEVICE); + if (dma_mapping_error(sdcp->dev, op->dst_phys)) { + dev_err(sdcp->dev, "Unable to map dest\n"); + goto err_unmap_src; + } + + op->cipher.key_phys = dma_map_single(sdcp->dev, op->cipher.key, + AES_KEYSIZE_128, DMA_TO_DEVICE); + if (dma_mapping_error(sdcp->dev, op->cipher.key_phys)) { + dev_err(sdcp->dev, "Unable to map key\n"); + goto err_unmap_dst; + } + + /* perform the operation */ + dcp_perform_op(op); + + dma_unmap_single(sdcp->dev, op->cipher.key_phys, AES_KEYSIZE_128, + DMA_TO_DEVICE); +err_unmap_dst: + dma_unmap_single(sdcp->dev, op->dst_phys, op->len, DMA_FROM_DEVICE); +err_unmap_src: + dma_unmap_single(sdcp->dev, op->src_phys, op->len, DMA_TO_DEVICE); +} + +static void dcp_aes_decrypt(struct crypto_tfm *tfm, u8 *out, const u8 *in) +{ + struct stmp3xxx_dcp *sdcp = global_sdcp; + struct stmp3xxx_dcp_op *op = crypto_tfm_ctx(tfm); + + if (unlikely(op->cipher.keylen != AES_KEYSIZE_128)) { + crypto_cipher_decrypt_one(op->fallback.cip, out, in); + return; + } + + op->src = (void *) in; + op->dst = (void *) out; + op->flags = STMP3XXX_DCP_AES | STMP3XXX_DCP_DEC | STMP3XXX_DCP_ECB; + op->len = AES_KEYSIZE_128; + + /* map the data */ + op->src_phys = dma_map_single(sdcp->dev, (void *)in, AES_KEYSIZE_128, + DMA_TO_DEVICE); + if (dma_mapping_error(sdcp->dev, op->src_phys)) { + dev_err(sdcp->dev, "Unable to map source\n"); + return; + } + + op->dst_phys = dma_map_single(sdcp->dev, out, AES_KEYSIZE_128, + DMA_FROM_DEVICE); + if (dma_mapping_error(sdcp->dev, op->dst_phys)) { + dev_err(sdcp->dev, "Unable to map dest\n"); + goto err_unmap_src; + } + + op->cipher.key_phys = dma_map_single(sdcp->dev, op->cipher.key, + AES_KEYSIZE_128, DMA_TO_DEVICE); + if (dma_mapping_error(sdcp->dev, op->cipher.key_phys)) { + dev_err(sdcp->dev, "Unable to map key\n"); + goto err_unmap_dst; + } + + /* perform the operation */ + dcp_perform_op(op); + + dma_unmap_single(sdcp->dev, op->cipher.key_phys, AES_KEYSIZE_128, + DMA_TO_DEVICE); +err_unmap_dst: + dma_unmap_single(sdcp->dev, op->dst_phys, op->len, DMA_FROM_DEVICE); +err_unmap_src: + dma_unmap_single(sdcp->dev, op->src_phys, op->len, DMA_TO_DEVICE); +} + +static int fallback_init_cip(struct crypto_tfm *tfm) +{ + const char *name = tfm->__crt_alg->cra_name; + struct stmp3xxx_dcp_op *op = crypto_tfm_ctx(tfm); + + op->fallback.cip = crypto_alloc_cipher(name, 0, + CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK); + + if (IS_ERR(op->fallback.cip)) { + printk(KERN_ERR "Error allocating fallback algo %s\n", name); + return PTR_ERR(op->fallback.cip); + } + + return 0; +} + +static void fallback_exit_cip(struct crypto_tfm *tfm) +{ + struct stmp3xxx_dcp_op *op = crypto_tfm_ctx(tfm); + + crypto_free_cipher(op->fallback.cip); + op->fallback.cip = NULL; +} + +static struct crypto_alg dcp_aes_alg = { + .cra_name = "aes", + .cra_driver_name = "dcp-aes", + .cra_priority = 300, + .cra_alignmask = 15, + .cra_flags = CRYPTO_ALG_TYPE_CIPHER | + CRYPTO_ALG_NEED_FALLBACK, + .cra_init = fallback_init_cip, + .cra_exit = fallback_exit_cip, + .cra_blocksize = AES_KEYSIZE_128, + .cra_ctxsize = sizeof(struct stmp3xxx_dcp_op), + .cra_module = THIS_MODULE, + .cra_list = LIST_HEAD_INIT(dcp_aes_alg.cra_list), + .cra_u = { + .cipher = { + .cia_min_keysize = AES_MIN_KEY_SIZE, + .cia_max_keysize = AES_MAX_KEY_SIZE, + .cia_setkey = dcp_aes_setkey_cip, + .cia_encrypt = dcp_aes_encrypt, + .cia_decrypt = dcp_aes_decrypt + } + } +}; + +static int dcp_aes_setkey_blk(struct crypto_tfm *tfm, const u8 *key, + unsigned int len) +{ + struct stmp3xxx_dcp_op *op = crypto_tfm_ctx(tfm); + unsigned int ret; + + op->cipher.keylen = len; + + if (len == AES_KEYSIZE_128) { + memcpy(op->cipher.key, key, len); + return 0; + } + + if (len != AES_KEYSIZE_192 && len != AES_KEYSIZE_256) { + /* not supported at all */ + tfm->crt_flags |= CRYPTO_TFM_RES_BAD_KEY_LEN; + return -EINVAL; + } + + /* + * The requested key size is not supported by HW, do a fallback + */ + op->fallback.blk->base.crt_flags &= ~CRYPTO_TFM_REQ_MASK; + op->fallback.blk->base.crt_flags |= (tfm->crt_flags & + CRYPTO_TFM_REQ_MASK); + + ret = crypto_blkcipher_setkey(op->fallback.blk, key, len); + if (ret) { + tfm->crt_flags &= ~CRYPTO_TFM_RES_MASK; + tfm->crt_flags |= (op->fallback.blk->base.crt_flags & + CRYPTO_TFM_RES_MASK); + } + return ret; +} + +static int fallback_blk_dec(struct blkcipher_desc *desc, + struct scatterlist *dst, struct scatterlist *src, + unsigned int nbytes) +{ + unsigned int ret; + struct crypto_blkcipher *tfm; + struct stmp3xxx_dcp_op *op = crypto_blkcipher_ctx(desc->tfm); + + tfm = desc->tfm; + desc->tfm = op->fallback.blk; + + ret = crypto_blkcipher_decrypt_iv(desc, dst, src, nbytes); + + desc->tfm = tfm; + return ret; +} + +static int fallback_blk_enc(struct blkcipher_desc *desc, + struct scatterlist *dst, struct scatterlist *src, + unsigned int nbytes) +{ + unsigned int ret; + struct crypto_blkcipher *tfm; + struct stmp3xxx_dcp_op *op = crypto_blkcipher_ctx(desc->tfm); + + tfm = desc->tfm; + desc->tfm = op->fallback.blk; + + ret = crypto_blkcipher_encrypt_iv(desc, dst, src, nbytes); + + desc->tfm = tfm; + return ret; +} + +static int fallback_init_blk(struct crypto_tfm *tfm) +{ + const char *name = tfm->__crt_alg->cra_name; + struct stmp3xxx_dcp_op *op = crypto_tfm_ctx(tfm); + + op->fallback.blk = crypto_alloc_blkcipher(name, 0, + CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK); + + if (IS_ERR(op->fallback.blk)) { + printk(KERN_ERR "Error allocating fallback algo %s\n", name); + return PTR_ERR(op->fallback.blk); + } + + return 0; +} + +static void fallback_exit_blk(struct crypto_tfm *tfm) +{ + struct stmp3xxx_dcp_op *op = crypto_tfm_ctx(tfm); + + crypto_free_blkcipher(op->fallback.blk); + op->fallback.blk = NULL; +} + +static int +dcp_aes_ecb_decrypt(struct blkcipher_desc *desc, + struct scatterlist *dst, struct scatterlist *src, + unsigned int nbytes) +{ + struct stmp3xxx_dcp *sdcp = global_sdcp; + struct stmp3xxx_dcp_op *op = crypto_blkcipher_ctx(desc->tfm); + struct blkcipher_walk walk; + int err; + + if (unlikely(op->cipher.keylen != AES_KEYSIZE_128)) + return fallback_blk_dec(desc, dst, src, nbytes); + + blkcipher_walk_init(&walk, dst, src, nbytes); + + /* key needs to be mapped only once */ + op->cipher.key_phys = dma_map_single(sdcp->dev, op->cipher.key, + AES_KEYSIZE_128, DMA_TO_DEVICE); + if (dma_mapping_error(sdcp->dev, op->cipher.key_phys)) { + dev_err(sdcp->dev, "Unable to map key\n"); + return -ENOMEM; + } + + err = blkcipher_walk_virt(desc, &walk); + while (err == 0 && (nbytes = walk.nbytes) > 0) { + op->src = walk.src.virt.addr, + op->dst = walk.dst.virt.addr; + op->flags = STMP3XXX_DCP_AES | STMP3XXX_DCP_DEC | + STMP3XXX_DCP_ECB; + op->len = nbytes - (nbytes % AES_KEYSIZE_128); + + /* map the data */ + op->src_phys = dma_map_single(sdcp->dev, op->src, op->len, + DMA_TO_DEVICE); + if (dma_mapping_error(sdcp->dev, op->src_phys)) { + dev_err(sdcp->dev, "Unable to map source\n"); + err = -ENOMEM; + break; + } + + op->dst_phys = dma_map_single(sdcp->dev, op->dst, op->len, + DMA_FROM_DEVICE); + if (dma_mapping_error(sdcp->dev, op->dst_phys)) { + dma_unmap_single(sdcp->dev, op->src_phys, op->len, + DMA_TO_DEVICE); + dev_err(sdcp->dev, "Unable to map dest\n"); + err = -ENOMEM; + break; + } + + /* perform! */ + dcp_perform_op(op); + + dma_unmap_single(sdcp->dev, op->dst_phys, op->len, + DMA_FROM_DEVICE); + dma_unmap_single(sdcp->dev, op->src_phys, op->len, + DMA_TO_DEVICE); + + nbytes -= op->len; + err = blkcipher_walk_done(desc, &walk, nbytes); + } + + dma_unmap_single(sdcp->dev, op->cipher.key_phys, AES_KEYSIZE_128, + DMA_TO_DEVICE); + + return err; +} + +static int +dcp_aes_ecb_encrypt(struct blkcipher_desc *desc, + struct scatterlist *dst, struct scatterlist *src, + unsigned int nbytes) +{ + struct stmp3xxx_dcp *sdcp = global_sdcp; + struct stmp3xxx_dcp_op *op = crypto_blkcipher_ctx(desc->tfm); + struct blkcipher_walk walk; + int err, ret; + + if (unlikely(op->cipher.keylen != AES_KEYSIZE_128)) + return fallback_blk_enc(desc, dst, src, nbytes); + + blkcipher_walk_init(&walk, dst, src, nbytes); + + /* key needs to be mapped only once */ + op->cipher.key_phys = dma_map_single(sdcp->dev, op->cipher.key, + AES_KEYSIZE_128, DMA_TO_DEVICE); + if (dma_mapping_error(sdcp->dev, op->cipher.key_phys)) { + dev_err(sdcp->dev, "Unable to map key\n"); + return -ENOMEM; + } + + err = blkcipher_walk_virt(desc, &walk); + + err = 0; + while (err == 0 && (nbytes = walk.nbytes) > 0) { + op->src = walk.src.virt.addr, + op->dst = walk.dst.virt.addr; + op->flags = STMP3XXX_DCP_AES | STMP3XXX_DCP_ENC | + STMP3XXX_DCP_ECB; + op->len = nbytes - (nbytes % AES_KEYSIZE_128); + + /* map the data */ + op->src_phys = dma_map_single(sdcp->dev, op->src, op->len, + DMA_TO_DEVICE); + if (dma_mapping_error(sdcp->dev, op->src_phys)) { + dev_err(sdcp->dev, "Unable to map source\n"); + err = -ENOMEM; + break; + } + + op->dst_phys = dma_map_single(sdcp->dev, op->dst, op->len, + DMA_FROM_DEVICE); + if (dma_mapping_error(sdcp->dev, op->dst_phys)) { + dma_unmap_single(sdcp->dev, op->src_phys, op->len, + DMA_TO_DEVICE); + dev_err(sdcp->dev, "Unable to map dest\n"); + err = -ENOMEM; + break; + } + + /* perform! */ + dcp_perform_op(op); + + dma_unmap_single(sdcp->dev, op->dst_phys, op->len, + DMA_FROM_DEVICE); + dma_unmap_single(sdcp->dev, op->src_phys, op->len, + DMA_TO_DEVICE); + + nbytes -= op->len; + ret = blkcipher_walk_done(desc, &walk, nbytes); + } + + dma_unmap_single(sdcp->dev, op->cipher.key_phys, AES_KEYSIZE_128, + DMA_TO_DEVICE); + + return err; +} + + +static struct crypto_alg dcp_aes_ecb_alg = { + .cra_name = "ecb(aes)", + .cra_driver_name = "dcp-ecb-aes", + .cra_priority = 400, + .cra_alignmask = 15, + .cra_flags = CRYPTO_ALG_TYPE_BLKCIPHER | + CRYPTO_ALG_NEED_FALLBACK, + .cra_init = fallback_init_blk, + .cra_exit = fallback_exit_blk, + .cra_blocksize = AES_KEYSIZE_128, + .cra_ctxsize = sizeof(struct stmp3xxx_dcp_op), + .cra_type = &crypto_blkcipher_type, + .cra_module = THIS_MODULE, + .cra_list = LIST_HEAD_INIT(dcp_aes_ecb_alg.cra_list), + .cra_u = { + .blkcipher = { + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .setkey = dcp_aes_setkey_blk, + .encrypt = dcp_aes_ecb_encrypt, + .decrypt = dcp_aes_ecb_decrypt + } + } +}; + +static int +dcp_aes_cbc_decrypt(struct blkcipher_desc *desc, + struct scatterlist *dst, struct scatterlist *src, + unsigned int nbytes) +{ + struct stmp3xxx_dcp *sdcp = global_sdcp; + struct stmp3xxx_dcp_op *op = crypto_blkcipher_ctx(desc->tfm); + struct blkcipher_walk walk; + int err, blockno; + + if (unlikely(op->cipher.keylen != AES_KEYSIZE_128)) + return fallback_blk_dec(desc, dst, src, nbytes); + + blkcipher_walk_init(&walk, dst, src, nbytes); + + blockno = 0; + err = blkcipher_walk_virt(desc, &walk); + while (err == 0 && (nbytes = walk.nbytes) > 0) { + op->src = walk.src.virt.addr, + op->dst = walk.dst.virt.addr; + op->flags = STMP3XXX_DCP_AES | STMP3XXX_DCP_DEC | + STMP3XXX_DCP_CBC; + if (blockno == 0) { + op->flags |= STMP3XXX_DCP_CBC_INIT; + memcpy(op->cipher.key + AES_KEYSIZE_128, walk.iv, + AES_KEYSIZE_128); + } + op->len = nbytes - (nbytes % AES_KEYSIZE_128); + + /* key (+iv) needs to be mapped only once */ + op->cipher.key_phys = dma_map_single(sdcp->dev, op->cipher.key, + AES_KEYSIZE_128 * 2, DMA_BIDIRECTIONAL); + if (dma_mapping_error(sdcp->dev, op->cipher.key_phys)) { + dev_err(sdcp->dev, "Unable to map key\n"); + err = -ENOMEM; + break; + } + + /* map the data */ + op->src_phys = dma_map_single(sdcp->dev, op->src, op->len, + DMA_TO_DEVICE); + if (dma_mapping_error(sdcp->dev, op->src_phys)) { + dma_unmap_single(sdcp->dev, op->cipher.key_phys, + AES_KEYSIZE_128 * 2, DMA_BIDIRECTIONAL); + dev_err(sdcp->dev, "Unable to map source\n"); + err = -ENOMEM; + break; + } + + op->dst_phys = dma_map_single(sdcp->dev, op->dst, op->len, + DMA_FROM_DEVICE); + if (dma_mapping_error(sdcp->dev, op->dst_phys)) { + dma_unmap_single(sdcp->dev, op->cipher.key_phys, + AES_KEYSIZE_128 * 2, DMA_BIDIRECTIONAL); + dma_unmap_single(sdcp->dev, op->src_phys, op->len, + DMA_TO_DEVICE); + dev_err(sdcp->dev, "Unable to map dest\n"); + err = -ENOMEM; + break; + } + + /* perform! */ + dcp_perform_op(op); + + dma_unmap_single(sdcp->dev, op->cipher.key_phys, + AES_KEYSIZE_128 * 2, DMA_BIDIRECTIONAL); + dma_unmap_single(sdcp->dev, op->dst_phys, op->len, + DMA_FROM_DEVICE); + dma_unmap_single(sdcp->dev, op->src_phys, op->len, + DMA_TO_DEVICE); + + nbytes -= op->len; + err = blkcipher_walk_done(desc, &walk, nbytes); + + blockno++; + } + + return err; +} + +static int +dcp_aes_cbc_encrypt(struct blkcipher_desc *desc, + struct scatterlist *dst, struct scatterlist *src, + unsigned int nbytes) +{ + struct stmp3xxx_dcp *sdcp = global_sdcp; + struct stmp3xxx_dcp_op *op = crypto_blkcipher_ctx(desc->tfm); + struct blkcipher_walk walk; + int err, ret, blockno; + + if (unlikely(op->cipher.keylen != AES_KEYSIZE_128)) + return fallback_blk_enc(desc, dst, src, nbytes); + + blkcipher_walk_init(&walk, dst, src, nbytes); + + blockno = 0; + + err = blkcipher_walk_virt(desc, &walk); + while (err == 0 && (nbytes = walk.nbytes) > 0) { + op->src = walk.src.virt.addr, + op->dst = walk.dst.virt.addr; + op->flags = STMP3XXX_DCP_AES | STMP3XXX_DCP_ENC | + STMP3XXX_DCP_CBC; + if (blockno == 0) { + op->flags |= STMP3XXX_DCP_CBC_INIT; + memcpy(op->cipher.key + AES_KEYSIZE_128, walk.iv, + AES_KEYSIZE_128); + } + op->len = nbytes - (nbytes % AES_KEYSIZE_128); + + /* key needs to be mapped only once */ + op->cipher.key_phys = dma_map_single(sdcp->dev, op->cipher.key, + AES_KEYSIZE_128 * 2, DMA_BIDIRECTIONAL); + if (dma_mapping_error(sdcp->dev, op->cipher.key_phys)) { + dev_err(sdcp->dev, "Unable to map key\n"); + return -ENOMEM; + } + + /* map the data */ + op->src_phys = dma_map_single(sdcp->dev, op->src, op->len, + DMA_TO_DEVICE); + if (dma_mapping_error(sdcp->dev, op->src_phys)) { + dma_unmap_single(sdcp->dev, op->cipher.key_phys, + AES_KEYSIZE_128 * 2, DMA_BIDIRECTIONAL); + dev_err(sdcp->dev, "Unable to map source\n"); + err = -ENOMEM; + break; + } + + op->dst_phys = dma_map_single(sdcp->dev, op->dst, op->len, + DMA_FROM_DEVICE); + if (dma_mapping_error(sdcp->dev, op->dst_phys)) { + dma_unmap_single(sdcp->dev, op->cipher.key_phys, + AES_KEYSIZE_128 * 2, DMA_BIDIRECTIONAL); + dma_unmap_single(sdcp->dev, op->src_phys, op->len, + DMA_TO_DEVICE); + dev_err(sdcp->dev, "Unable to map dest\n"); + err = -ENOMEM; + break; + } + + /* perform! */ + dcp_perform_op(op); + + dma_unmap_single(sdcp->dev, op->cipher.key_phys, + AES_KEYSIZE_128 * 2, DMA_BIDIRECTIONAL); + dma_unmap_single(sdcp->dev, op->dst_phys, op->len, + DMA_FROM_DEVICE); + dma_unmap_single(sdcp->dev, op->src_phys, op->len, + DMA_TO_DEVICE); + + nbytes -= op->len; + ret = blkcipher_walk_done(desc, &walk, nbytes); + + blockno++; + } + + return err; +} + +static struct crypto_alg dcp_aes_cbc_alg = { + .cra_name = "cbc(aes)", + .cra_driver_name = "dcp-cbc-aes", + .cra_priority = 400, + .cra_alignmask = 15, + .cra_flags = CRYPTO_ALG_TYPE_BLKCIPHER | + CRYPTO_ALG_NEED_FALLBACK, + .cra_init = fallback_init_blk, + .cra_exit = fallback_exit_blk, + .cra_blocksize = AES_KEYSIZE_128, + .cra_ctxsize = sizeof(struct stmp3xxx_dcp_op), + .cra_type = &crypto_blkcipher_type, + .cra_module = THIS_MODULE, + .cra_list = LIST_HEAD_INIT(dcp_aes_cbc_alg.cra_list), + .cra_u = { + .blkcipher = { + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .setkey = dcp_aes_setkey_blk, + .encrypt = dcp_aes_cbc_encrypt, + .decrypt = dcp_aes_cbc_decrypt, + .ivsize = AES_KEYSIZE_128, + } + } +}; + +#ifndef DISABLE_SHA1 + +static void dcp_perform_hash_op(struct stmp3xxx_dcp_hash_op *op) +{ + struct stmp3xxx_dcp *sdcp = global_sdcp; + struct mutex *mutex; + int chan; + struct stmp3xxx_dcp_hw_packet *pkt; + unsigned long timeout; + u32 stat; + int size1, descno; + + BUG_ON((op->flags & STMP3XXX_DCP_MODE_MASK) != STMP3XXX_DCP_SHA1); + + chan = HASH_CHAN; + + switch (op->flags & (STMP3XXX_DCP_INIT | STMP3XXX_DCP_UPDATE | + STMP3XXX_DCP_FINAL)) { + + case STMP3XXX_DCP_INIT | STMP3XXX_DCP_UPDATE: + + BUG_ON(op->len <= 1); + + pkt = &op->hw->pkt[0]; + pkt->pNext = 0; + pkt->pkt1 = BM_DCP_PACKET1_HASH_INIT | + BM_DCP_PACKET1_DECR_SEMAPHORE | + BM_DCP_PACKET1_INTERRUPT | + BM_DCP_PACKET1_ENABLE_HASH; + pkt->pkt2 = BF(BV_DCP_PACKET2_HASH_SELECT__SHA1, + DCP_PACKET2_HASH_SELECT); + pkt->pSrc = (u32)op->src_phys; + pkt->pDst = 0; + pkt->size = op->len - 1; + pkt->pPayload = 0; + pkt->stat = 0; + + /* save last byte */ + op->hw->rembuf[1] = ((u8 *)op->src)[op->len - 1]; + + descno = 1; + break; + + case STMP3XXX_DCP_UPDATE: + + BUG_ON(op->len <= 1); + + op->hw->rembuf[0] = op->hw->rembuf[1]; + + pkt = &op->hw->pkt[0]; + pkt->pNext = 0; + pkt->pkt1 = BM_DCP_PACKET1_CHAIN_CONTIGUOUS | + BM_DCP_PACKET1_ENABLE_HASH; + pkt->pkt2 = BF(BV_DCP_PACKET2_HASH_SELECT__SHA1, + DCP_PACKET2_HASH_SELECT); + pkt->pSrc = (u32)op->hw_phys + offsetof( + struct stmp3xxx_dcp_hash_coherent_block, rembuf); + pkt->pDst = 0; + pkt->size = 1; + pkt->pPayload = 0; + pkt->stat = 0; + + pkt = &op->hw->pkt[1]; + pkt->pNext = 0; + pkt->pkt1 = BM_DCP_PACKET1_DECR_SEMAPHORE | + BM_DCP_PACKET1_INTERRUPT | + BM_DCP_PACKET1_ENABLE_HASH; + pkt->pkt2 = BF(BV_DCP_PACKET2_HASH_SELECT__SHA1, + DCP_PACKET2_HASH_SELECT); + pkt->pSrc = (u32)op->src_phys; + pkt->pDst = 0; + pkt->size = op->len - 1; + pkt->pPayload = 0; + pkt->stat = 0; + + /* save last byte */ + op->hw->rembuf[1] = ((u8 *)op->src)[op->len - 1]; + + descno = 2; + + break; + + case STMP3XXX_DCP_FINAL: + + op->hw->rembuf[0] = op->hw->rembuf[1]; + + pkt = &op->hw->pkt[0]; + pkt->pNext = 0; + pkt->pkt1 = BM_DCP_PACKET1_HASH_TERM | + BM_DCP_PACKET1_DECR_SEMAPHORE | + BM_DCP_PACKET1_INTERRUPT | + BM_DCP_PACKET1_ENABLE_HASH; + pkt->pkt2 = BF(BV_DCP_PACKET2_HASH_SELECT__SHA1, + DCP_PACKET2_HASH_SELECT); + pkt->pSrc = (u32)op->hw_phys + offsetof( + struct stmp3xxx_dcp_hash_coherent_block, rembuf); + pkt->pDst = 0; + pkt->size = 1; + pkt->pPayload = (u32)op->hw_phys + offsetof( + struct stmp3xxx_dcp_hash_coherent_block, digest); + pkt->stat = 0; + + descno = 1; + + break; + + /* all others BUG */ + default: + BUG(); + return; + } + + mutex = &sdcp->op_mutex[chan]; + + /* submit the work */ + mutex_lock(mutex); + + stmp3xxx_clearl(-1, REGS_DCP_BASE + HW_DCP_CHnSTAT(chan)); + + mb(); + /* Load the work packet pointer and bump the channel semaphore */ + __raw_writel(chan, (u32)op->hw_phys + + offsetof(struct stmp3xxx_dcp_hash_coherent_block, pkt[0]), REGS_DCP_BASE + HW_DCP_CHnCMDPTR); + + /* XXX wake from interrupt instead of looping */ + timeout = jiffies + msecs_to_jiffies(1000); + + __raw_writel(chan, BF(descno, DCP_CHnSEMA_INCREMENT), REGS_DCP_BASE + HW_DCP_CHnSEMA); + + while (time_before(jiffies, timeout) && + ((__raw_readl(chanREGS_DCP_BASE + HW_DCP_CHnSEMA) >> 16) & 0xff) != 0) + cpu_relax(); + + if (!time_before(jiffies, timeout)) { + dev_err(sdcp->dev, "Timeout while waiting STAT 0x%08x\n", + __raw_readl(REGS_DCP_BASE + HW_DCP_STAT)); + goto out; + } + + stat = __raw_readl(REGS_DCP_BASE + HW_DCP_CHnSTAT(chan)); + if ((stat & 0xff) != 0) + dev_err(sdcp->dev, "Channel stat error 0x%02x\n", + __raw_readl(REGS_DCP_BASE + HW_DCP_CHnSTAT(chan)) & 0xff); + +out: + mutex_unlock(mutex); + + mdelay(500); +} + +static int fallback_init_hash(struct crypto_tfm *tfm) +{ + const char *name = tfm->__crt_alg->cra_name; + struct stmp3xxx_dcp_hash_op *op = crypto_tfm_ctx(tfm); + struct stmp3xxx_dcp *sdcp = global_sdcp; + + op->fallback.hash = crypto_alloc_hash(name, 0, + CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK); + if (IS_ERR(op->fallback.hash)) { + printk(KERN_ERR "Error allocating fallback algo %s\n", name); + return PTR_ERR(op->fallback.hash); + } + + op->hw = dma_alloc_coherent(sdcp->dev, sizeof(*op->hw), &op->hw_phys, + GFP_KERNEL); + if (op->hw == NULL) { + printk(KERN_ERR "Error allocating coherent block for %s\n", + name); + crypto_free_hash(op->fallback.hash); + op->fallback.hash = NULL; + return -ENOMEM; + } + memset(op->hw, 0, sizeof(*op->hw)); + + return 0; +} + +static void fallback_exit_hash(struct crypto_tfm *tfm) +{ + struct stmp3xxx_dcp_hash_op *op = crypto_tfm_ctx(tfm); + struct stmp3xxx_dcp *sdcp = global_sdcp; + + dma_free_coherent(sdcp->dev, sizeof(*op->hw), op->hw, op->hw_phys); + crypto_free_hash(op->fallback.hash); + op->fallback.hash = NULL; +} + +static void dcp_sha1_init(struct crypto_tfm *tfm) +{ + struct stmp3xxx_dcp_hash_op *op = crypto_tfm_ctx(tfm); + + op->hash.init = 1; + memset(op->hw->rembuf, 0, sizeof(op->hw->rembuf)); + memset(op->hw->digest, 0, sizeof(op->hw->digest)); +} + +static void dcp_sha1_update(struct crypto_tfm *tfm, + const uint8_t *data, unsigned int length) +{ + struct stmp3xxx_dcp *sdcp = global_sdcp; + struct stmp3xxx_dcp_hash_op *op = crypto_tfm_ctx(tfm); + + op->src = (void *)data; + op->dst = NULL; + op->flags = STMP3XXX_DCP_SHA1 | STMP3XXX_DCP_UPDATE; + if (op->hash.init) { + op->hash.init = 0; + op->flags |= STMP3XXX_DCP_INIT; + } + op->len = length; + + /* map the data */ + op->src_phys = dma_map_single(sdcp->dev, op->src, op->len, + DMA_TO_DEVICE); + if (dma_mapping_error(sdcp->dev, op->src_phys)) { + dev_err(sdcp->dev, "Unable to map source\n"); + return; + } + op->dst_phys = 0; + + /* perform! */ + dcp_perform_hash_op(op); + + dma_unmap_single(sdcp->dev, op->src_phys, op->len, DMA_TO_DEVICE); +} + +static void dcp_sha1_final(struct crypto_tfm *tfm, uint8_t *out) +{ + struct stmp3xxx_dcp_hash_op *op = crypto_tfm_ctx(tfm); + int i; + const uint8_t *digest; + + op->src = NULL; + op->dst = NULL; + op->flags = STMP3XXX_DCP_SHA1 | STMP3XXX_DCP_FINAL; + op->len = 0; + + /* perform! */ + dcp_perform_hash_op(op); + + /* hardware reverses the digest (for some unexplicable reason) */ + digest = op->hw->digest + SHA1_DIGEST_SIZE; + for (i = 0; i < SHA1_DIGEST_SIZE; i++) + *out++ = *--digest; + memcpy(out, op->hw->digest, SHA1_DIGEST_SIZE); +} + +static struct crypto_alg dcp_sha1_alg = { + .cra_name = "sha1", + .cra_driver_name = "dcp-sha1", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_DIGEST | + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = SHA1_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct stmp3xxx_dcp_hash_op), + .cra_module = THIS_MODULE, + .cra_list = LIST_HEAD_INIT(dcp_sha1_alg.cra_list), + .cra_init = fallback_init_hash, + .cra_exit = fallback_exit_hash, + .cra_u = { + .digest = { + .dia_digestsize = SHA1_DIGEST_SIZE, + .dia_init = dcp_sha1_init, + .dia_update = dcp_sha1_update, + .dia_final = dcp_sha1_final, + } + } +}; +#endif + +static irqreturn_t dcp_common_irq(int irq, void *context) +{ + struct stmp3xxx_dcp *sdcp = context; + u32 msk; + + /* check */ + msk = __raw_readl(REGS_DCP_BASE + HW_DCP_STAT) & BF(0x0f, DCP_STAT_IRQ); + if (msk == 0) + return IRQ_NONE; + + /* clear this channel */ + stmp3xxx_clearl(msk, REGS_DCP_BASE + HW_DCP_STAT); + if (msk & BF(0x01, DCP_STAT_IRQ)) + sdcp->wait[0]++; + if (msk & BF(0x02, DCP_STAT_IRQ)) + sdcp->wait[1]++; + if (msk & BF(0x04, DCP_STAT_IRQ)) + sdcp->wait[2]++; + if (msk & BF(0x08, DCP_STAT_IRQ)) + sdcp->wait[3]++; + return IRQ_HANDLED; +} + +static irqreturn_t dcp_vmi_irq(int irq, void *context) +{ + return dcp_common_irq(irq, context); +} + +static irqreturn_t dcp_irq(int irq, void *context) +{ + return dcp_common_irq(irq, context); +} + +static int stmp3xxx_dcp_probe(struct platform_device *pdev) +{ + struct stmp3xxx_dcp *sdcp = NULL; + struct resource *r; + int i, ret; + + if (global_sdcp != NULL) { + dev_err(&pdev->dev, "Only one instance allowed\n"); + ret = -ENODEV; + goto err; + } + + /* allocate memory */ + sdcp = kzalloc(sizeof(*sdcp), GFP_KERNEL); + if (sdcp == NULL) { + dev_err(&pdev->dev, "Failed to allocate structure\n"); + ret = -ENOMEM; + goto err; + } + + sdcp->dev = &pdev->dev; + spin_lock_init(&sdcp->lock); + + for (i = 0; i < STMP3XXX_DCP_NUM_CHANNELS; i++) { + mutex_init(&sdcp->op_mutex[i]); + init_completion(&sdcp->op_wait[i]); + } + + platform_set_drvdata(pdev, sdcp); + + /* Soft reset and remove the clock gate */ + stmp3xxx_setl(BM_DCP_CTRL_SFTRST, REGS_DCP_BASE + HW_DCP_CTRL); + + /* At 24Mhz, it takes no more than 4 clocks (160 ns) Maximum for + * the part to reset, reading the register twice should + * be sufficient to get 4 clks delay. + */ + __raw_readl(REGS_DCP_BASE + HW_DCP_CTRL); + __raw_readl(REGS_DCP_BASE + HW_DCP_CTRL); + + stmp3xxx_clearl(BM_DCP_CTRL_SFTRST | BM_DCP_CTRL_CLKGATE, REGS_DCP_BASE + HW_DCP_CTRL); + + /* Initialize control registers */ + __raw_writel(STMP3XXX_DCP_CTRL_INIT, REGS_DCP_BASE + HW_DCP_CTRL); + __raw_writel(STMP3XXX_DCP_CHANNELCTRL_INIT, REGS_DCP_BASE + HW_DCP_CHANNELCTRL); + + /* We do not enable context switching. Give the context + * buffer pointer an illegal address so if context switching is + * inadvertantly enabled, the dcp will return an error instead of + * trashing good memory. The dcp dma cannot access rom, so any rom + * address will do. + */ + __raw_writel(0xFFFF0000, REGS_DCP_BASE + HW_DCP_CONTEXT); + + for (i = 0; i < STMP3XXX_DCP_NUM_CHANNELS; i++) + stmp3xxx_clearl(-1, REGS_DCP_BASE + HW_DCP_CHnSTAT(i)); + stmp3xxx_clearl(-1, REGS_DCP_BASE + HW_DCP_STAT); + + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!r) { + dev_err(&pdev->dev, "can't get IRQ resource (0)\n"); + ret = -EIO; + goto err_unregister_sha1; + } + sdcp->dcp_vmi_irq = r->start; + ret = request_irq(sdcp->dcp_vmi_irq, dcp_vmi_irq, 0, "stmp3xxx-dcp", + sdcp); + if (ret != 0) { + dev_err(&pdev->dev, "can't request_irq (0)\n"); + goto err_unregister_sha1; + } + + r = platform_get_resource(pdev, IORESOURCE_IRQ, 1); + if (!r) { + dev_err(&pdev->dev, "can't get IRQ resource (1)\n"); + ret = -EIO; + goto err_free_irq0; + } + sdcp->dcp_irq = r->start; + ret = request_irq(sdcp->dcp_irq, dcp_irq, 0, "stmp3xxx-dcp", sdcp); + if (ret != 0) { + dev_err(&pdev->dev, "can't request_irq (1)\n"); + goto err_free_irq0; + } + + global_sdcp = sdcp; + + ret = crypto_register_alg(&dcp_aes_alg); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to register aes crypto\n"); + goto err_kfree; + } + + ret = crypto_register_alg(&dcp_aes_ecb_alg); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to register aes ecb crypto\n"); + goto err_unregister_aes; + } + + ret = crypto_register_alg(&dcp_aes_cbc_alg); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to register aes cbc crypto\n"); + goto err_unregister_aes_ecb; + } + +#ifndef DISABLE_SHA1 + ret = crypto_register_alg(&dcp_sha1_alg); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to register aes cbc crypto\n"); + goto err_unregister_aes_cbc; + } +#endif + + dev_notice(&pdev->dev, "DCP crypto enabled.!\n"); + return 0; + +err_free_irq0: + free_irq(sdcp->dcp_vmi_irq, sdcp); +err_unregister_sha1: +#ifndef DISABLE_SHA1 + crypto_unregister_alg(&dcp_sha1_alg); +err_unregister_aes_cbc: +#endif + crypto_unregister_alg(&dcp_aes_cbc_alg); +err_unregister_aes_ecb: + crypto_unregister_alg(&dcp_aes_ecb_alg); +err_unregister_aes: + crypto_unregister_alg(&dcp_aes_alg); +err_kfree: + kfree(sdcp); +err: + + return ret; +} + +static int stmp3xxx_dcp_remove(struct platform_device *pdev) +{ + struct stmp3xxx_dcp *sdcp; + + sdcp = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); + + free_irq(sdcp->dcp_irq, sdcp); + free_irq(sdcp->dcp_vmi_irq, sdcp); +#ifndef DISABLE_SHA1 + crypto_unregister_alg(&dcp_sha1_alg); +#endif + crypto_unregister_alg(&dcp_aes_cbc_alg); + crypto_unregister_alg(&dcp_aes_ecb_alg); + crypto_unregister_alg(&dcp_aes_alg); + kfree(sdcp); + global_sdcp = NULL; + + return 0; +} + + +#ifdef CONFIG_PM +static int stmp3xxx_dcp_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return 0; +} + +static int stmp3xxx_dcp_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define stmp3xxx_dcp_suspend NULL +#define stmp3xxx_dcp_resume NULL +#endif + +static struct platform_driver stmp3xxx_dcp_driver = { + .probe = stmp3xxx_dcp_probe, + .remove = stmp3xxx_dcp_remove, + .suspend = stmp3xxx_dcp_suspend, + .resume = stmp3xxx_dcp_resume, + .driver = { + .name = "stmp3xxx-dcp", + .owner = THIS_MODULE, + }, +}; + +static int __init +stmp3xxx_dcp_init(void) +{ + return platform_driver_register(&stmp3xxx_dcp_driver); +} + +static void __exit +stmp3xxx_dcp_exit(void) +{ + platform_driver_unregister(&stmp3xxx_dcp_driver); +} + +MODULE_AUTHOR("Pantelis Antoniou <pantelis@embeddedalley.com>"); +MODULE_DESCRIPTION("STMP3XXX DCP Crypto Driver"); +MODULE_LICENSE("GPL"); + +module_init(stmp3xxx_dcp_init); +module_exit(stmp3xxx_dcp_exit); diff --git a/drivers/crypto/stmp3xxx_dcp.h b/drivers/crypto/stmp3xxx_dcp.h new file mode 100644 index 000000000000..c08de5dd8af4 --- /dev/null +++ b/drivers/crypto/stmp3xxx_dcp.h @@ -0,0 +1,68 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef _STMP3XXX_DCP_H_ +#define _STMP3XXX_DCP_H_ + +#include <mach/platform.h> +#include <mach/stmp3xxx.h> +#include <mach/regs-dcp.h> + +#define CIPHER_CHAN 1 +#define CIPHER_MASK (1 << CIPHER_CHAN) + +#define HASH_CHAN 0 +#define HASH_MASK (1 << HASH_CHAN) + +#define ALL_MASK (CIPHER_MASK | HASH_MASK) + +/* Defines the initialization value for the dcp control register */ +#define STMP3XXX_DCP_CTRL_INIT \ + (BM_DCP_CTRL_GATHER_RESIDUAL_WRITES | \ + BM_DCP_CTRL_ENABLE_CONTEXT_CACHING | \ + BV_DCP_CTRL_CHANNEL_INTERRUPT_ENABLE__CH0 | \ + BV_DCP_CTRL_CHANNEL_INTERRUPT_ENABLE__CH1 | \ + BV_DCP_CTRL_CHANNEL_INTERRUPT_ENABLE__CH2 | \ + BV_DCP_CTRL_CHANNEL_INTERRUPT_ENABLE__CH3) + +/* Defines the initialization value for the dcp channel control register */ +#define STMP3XXX_DCP_CHANNELCTRL_INIT \ + BF(ALL_MASK, DCP_CHANNELCTRL_ENABLE_CHANNEL) + +/* DCP work packet 1 value for encryption */ +#define STMP3XXX_DCP_PKT1_ENCRYPT \ + (BM_DCP_PACKET1_DECR_SEMAPHORE | \ + BM_DCP_PACKET1_ENABLE_CIPHER | \ + BM_DCP_PACKET1_CIPHER_ENCRYPT | \ + BM_DCP_PACKET1_CIPHER_INIT) + +/* DCP work packet 1 value for decryption */ +#define DCP_PKT1_DECRYPT \ + (BM_DCP_PACKET1_DECR_SEMAPHORE | \ + BM_DCP_PACKET1_ENABLE_CIPHER | \ + BM_DCP_PACKET1_CIPHER_INIT) + +/* DCP (decryption) work packet definition */ +struct stmp3xxx_dcp_hw_packet { + uint32_t pNext; /* next dcp work packet address */ + uint32_t pkt1; /* dcp work packet 1 (control 0) */ + uint32_t pkt2; /* dcp work packet 2 (control 1) */ + uint32_t pSrc; /* source buffer address */ + uint32_t pDst; /* destination buffer address */ + uint32_t size; /* buffer size in bytes */ + uint32_t pPayload; /* payload buffer address */ + uint32_t stat; /* dcp status (written by dcp) */ +}; + +#define STMP3XXX_DCP_NUM_CHANNELS 4 + +#endif diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 2d5016691d40..cacaa13534d7 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1026,4 +1026,14 @@ config HWMON_DEBUG_CHIP a problem with I2C support and want to see more of what is going on. +config MXC_MMA7450 + tristate "MMA7450 device driver" + depends on MACH_MX31_3DS + default n + +config SENSORS_ISL29003 + tristate "ISL29003 Light Sensor" + depends on MACH_MX37_3DS || MACH_MX51_3DS + default y + endif # HWMON diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index b793dce6bed5..24368b04323b 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -88,7 +88,9 @@ obj-$(CONFIG_SENSORS_VT1211) += vt1211.o obj-$(CONFIG_SENSORS_VT8231) += vt8231.o obj-$(CONFIG_SENSORS_W83627EHF) += w83627ehf.o obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o +obj-$(CONFIG_MXC_MMA7450) += mxc_mma7450.o obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o +obj-$(CONFIG_SENSORS_ISL29003) += isl29003.o ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/hwmon/isl29003.c b/drivers/hwmon/isl29003.c new file mode 100644 index 000000000000..9f1afe6faadb --- /dev/null +++ b/drivers/hwmon/isl29003.c @@ -0,0 +1,438 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/hwmon/isl29003.c + * + * @brief ISL29003 light sensor Driver + * + * @ingroup + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/regulator/consumer.h> +#include <mach/hardware.h> + +enum isl29003_width { + ISL29003_WIDTH_16 = 0, + ISL29003_WIDTH_12, + ISL29003_WIDTH_8, + ISL29003_WIDTH_4, +}; + +enum isl29003_gain { + ISL29003_GAIN_1000 = 0, + ISL29003_GAIN_4000, + ISL29003_GAIN_16000, + ISL29003_GAIN_64000, +}; + +enum isl29003_mode { + ISL29003_MODE_DIODE1 = 0, + ISL29003_MODE_DIODE2, + ISL29003_MODE_DIODE1_2, +}; + +struct isl29003_param { + enum isl29003_width width; + enum isl29003_gain gain; + enum isl29003_mode mode; +}; + +/* bit definition for ISL29003_CMD reg */ +#define ENABLE 7 +#define ADCPD 6 +#define TIMEING_MODE 5 +#define MODE 2 +#define WIDTH 0 + +/* bit definition for ISL29003_CTRL reg */ +#define INT_FLAG 5 +#define GAIN 2 +#define INT_PERSIST 0 + +enum isl29003_reg { + ISL29003_CMD = 0, + ISL29003_CTRL, + ISL29003_THRS_HI, + ISL29003_THRS_LO, + ISL29003_LSB_S, + ISL29003_MSB_S, + ISL29003_LSB_T, + ISL29003_MSB_T, + ISL29003_SYNC_IIC = 0x80, + ISL29003_CLAR_INT = 0x40 +}; + +/* default configure for ISL29003 */ +#define ISL29003_WIDTH_DEFAULT ISL29003_WIDTH_16 +#define ISL29003_GAIN_DEFAULT ISL29003_GAIN_16000 +#define ISL29003_MODE_DEFAULT ISL29003_MODE_DIODE1 + +/* range table for different GAIN settings */ +int range[4] = { 973, 3892, 15568, 62272 }; + +/* width table for different WIDTH settings */ +int width[4] = { 16, 1, 256, 16 }; + +struct isl29003_data { + struct i2c_client *client; + struct device *hwmon_dev; + struct regulator *vdd_reg; + struct isl29003_param param; + int lux_coeff; + unsigned char enable; +}; + +static struct i2c_client *isl29003_client; + +/*! + * This function do the isl29003 register read. + */ +int isl29003_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/*! + * This function do the isl29003 register write. + */ +int isl29003_write(struct i2c_client *client, u8 reg, char value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +/*! + * This function do the isl29003 config and enable. + */ +static int isl29003_on(void) +{ + unsigned char cmd; + int err = 0; + struct mxc_lightsensor_platform_data *ls_data; + struct isl29003_data *data = i2c_get_clientdata(isl29003_client); + + if (data->enable) + goto exit; + + ls_data = (struct mxc_lightsensor_platform_data *) + (isl29003_client->dev).platform_data; + + /* coeff=range*100k/rext/2^n */ + data->lux_coeff = range[data->param.gain] * 100 / + ls_data->rext / width[data->param.width]; + + if (data->vdd_reg) + regulator_enable(data->vdd_reg); + msleep(100); + + cmd = data->param.gain << GAIN; + if (isl29003_write(isl29003_client, ISL29003_CTRL, cmd)) { + err = -ENODEV; + goto exit; + } + + cmd = (data->param.width << WIDTH) | (data->param.mode << MODE) | + (1 << ENABLE); + if (isl29003_write(isl29003_client, ISL29003_CMD, cmd)) { + err = -ENODEV; + goto exit; + } + + data->enable = 1; + + pr_info("isl29003 on\n"); + return 0; +exit: + return err; +} + +/*! + * This function shut down the isl29003. + */ +static int isl29003_off(void) +{ + struct isl29003_data *data = i2c_get_clientdata(isl29003_client); + int cmd; + + if (!data->enable) + return 0; + + cmd = isl29003_read(isl29003_client, ISL29003_CMD); + if (cmd < 0) + return -ENODEV; + + cmd = ((cmd | (1 << ADCPD)) & (~(1 << ENABLE))); + if (isl29003_write(isl29003_client, ISL29003_CMD, (char)cmd)) + return -ENODEV; + + if (data->vdd_reg) + regulator_disable(data->vdd_reg); + + data->enable = 0; + + pr_info("isl29003 off\n"); + return 0; +} + +/*! + * This function read the isl29003 lux registers and convert them to the lux + * value. + * + * @output buffer this param holds the lux value, when =-1, read fail + * + * @return 0 + */ +static int isl29003_read_lux(void) +{ + int d; + int lux; + struct isl29003_data *data = i2c_get_clientdata(isl29003_client); + + d = isl29003_read(isl29003_client, ISL29003_MSB_S); + if (d < 0) + goto err; + + lux = d; + d = isl29003_read(isl29003_client, ISL29003_LSB_S); + if (d < 0) + goto err; + + lux = (lux << 8) + d; + + if (data->param.width < ISL29003_WIDTH_8) + lux = (data->lux_coeff * lux) >> 12; + else + lux = data->lux_coeff * lux; + + return lux; +err: + return -1; +} + +static ssize_t ls_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + char *endp; + int enable = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + if (enable == 1) { + if (isl29003_on()) + pr_info("device open fail\n"); + } + if (enable == 0) { + if (isl29003_off()) + pr_info("device powerdown fail\n"); + } + + return count; +} + +static SENSOR_DEVICE_ATTR(enable, S_IWUGO, NULL, ls_enable, 0); + +static ssize_t show_lux(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", isl29003_read_lux()); +} + +static SENSOR_DEVICE_ATTR(lux, S_IRUGO, show_lux, NULL, 0); + +static int isl29003_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + int err = 0; + struct isl29003_data *data; + struct regulator *vdd_reg; + struct mxc_lightsensor_platform_data *ls_data; + + ls_data = (struct mxc_lightsensor_platform_data *) + (client->dev).platform_data; + + if (ls_data && ls_data->vdd_reg) + vdd_reg = regulator_get(&client->dev, ls_data->vdd_reg); + else + vdd_reg = NULL; + + /* check the existence of the device */ + if (vdd_reg) + regulator_enable(vdd_reg); + msleep(100); + + if (isl29003_write(client, ISL29003_CMD, 0)) + err = -ENODEV; + + if (!err) + if (isl29003_read(client, ISL29003_CMD)) + err = -ENODEV; + + if (vdd_reg) + regulator_disable(vdd_reg); + if (err < 0) + goto exit1; + + isl29003_client = client; + data = kzalloc(sizeof(struct isl29003_data), GFP_KERNEL); + if (data == NULL) { + err = -ENOMEM; + goto exit1; + } + + i2c_set_clientdata(client, data); + data->client = client; + + data->param.width = ISL29003_WIDTH_DEFAULT; + data->param.gain = ISL29003_GAIN_DEFAULT; + data->param.mode = ISL29003_MODE_DEFAULT; + + data->enable = 0; + + err = device_create_file(&client->dev, + &sensor_dev_attr_enable.dev_attr); + if (err) + goto exit2; + + err = device_create_file(&client->dev, &sensor_dev_attr_lux.dev_attr); + if (err) + goto exit_remove1; + + /* Register sysfs hooks */ + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove2; + } + + data->vdd_reg = vdd_reg; + return 0; + +exit_remove2: + device_remove_file(&client->dev, &sensor_dev_attr_lux.dev_attr); +exit_remove1: + device_remove_file(&client->dev, &sensor_dev_attr_enable.dev_attr); +exit2: + kfree(data); +exit1: + if (vdd_reg) { + regulator_put(vdd_reg); + vdd_reg = NULL; + } + isl29003_client = NULL; + return err; +} + +static int isl29003_i2c_remove(struct i2c_client *client) +{ + struct isl29003_data *data = i2c_get_clientdata(client); + + if (data->vdd_reg) { + regulator_put(data->vdd_reg); + data->vdd_reg = NULL; + } + hwmon_device_unregister(data->hwmon_dev); + device_remove_file(&client->dev, &sensor_dev_attr_enable.dev_attr); + device_remove_file(&client->dev, &sensor_dev_attr_lux.dev_attr); + kfree(data); + return 0; +} + +static int isl29003_suspend(struct i2c_client *client, pm_message_t message) +{ + int cmd; + + struct isl29003_data *data = i2c_get_clientdata(client); + + if (!data->enable) + goto exit; + + cmd = isl29003_read(client, ISL29003_CMD); + if (cmd < 0) + goto err; + + cmd = (cmd | (1 << ADCPD)); + if (isl29003_write(client, ISL29003_CMD, (char)cmd)) + goto err; +exit: + return 0; +err: + return -ENODEV; +} + +static int isl29003_resume(struct i2c_client *client) +{ + int cmd; + + struct isl29003_data *data = i2c_get_clientdata(client); + + if (!data->enable) + goto exit; + + cmd = isl29003_read(client, ISL29003_CMD); + if (cmd < 0) + goto err; + + cmd = (cmd & (~(1 << ADCPD))); + if (isl29003_write(client, ISL29003_CMD, (char)cmd)) + goto err; +exit: + return 0; +err: + return -ENODEV; +} + +static const struct i2c_device_id isl29003_id[] = { + {"isl29003", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, isl29003_id); + +static struct i2c_driver isl29003_driver = { + .driver = { + .name = "isl29003", + }, + .probe = isl29003_i2c_probe, + .remove = isl29003_i2c_remove, + .suspend = isl29003_suspend, + .resume = isl29003_resume, + .id_table = isl29003_id, +}; + +static int __init isl29003_init(void) +{ + return i2c_add_driver(&isl29003_driver);; +} + +static void __exit isl29003_cleanup(void) +{ + i2c_del_driver(&isl29003_driver); +} + +module_init(isl29003_init); +module_exit(isl29003_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("ISL29003 light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/mxc_mma7450.c b/drivers/hwmon/mxc_mma7450.c new file mode 100644 index 000000000000..2ee23200a909 --- /dev/null +++ b/drivers/hwmon/mxc_mma7450.c @@ -0,0 +1,788 @@ +/* + * linux/drivers/hwmon/mma7450.c + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*include file*/ +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/input-polldev.h> +#include <linux/hwmon.h> +#include <linux/regulator/consumer.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <mach/hardware.h> + +/*macro define*/ +#define MMA7450_I2C_ADDR 0x1D +#define DEVICE_NAME "mma7450" +#define POLL_INTERVAL 100 +#define DEBUG + +#define INPUT_FUZZ 4 +#define INPUT_FLAT 4 + +enum { + REG_XOUTL = 0x00, + REG_XOUTH, + REG_YOUTL, + REG_YOUTH, + REG_ZOUTL, + REG_ZOUTH, + REG_XOUT8, + REG_YOUT8, + REG_ZOUT8, + REG_STATUS, + REG_DETSRC, + REG_TOUT, + REG_RESERVED_0, + REG_I2CAD, + REG_USRINF, + REG_WHOAMI, + REG_XOFFL, + REG_XOFFH, + REG_YOFFL, + REG_YOFFH, + REG_ZOFFL, + REG_ZOFFH, + REG_MCTL, + REG_INTRST, + REG_CTL1, + REG_CTL2, + REG_LDTH, + REG_PDTH, + REG_PD, + REG_LT, + REG_TW, + REG_REVERVED_1, +}; + +enum { + MOD_STANDBY = 0, + MOD_MEASURE, + MOD_LEVEL_D, + MOD_PULSE_D, +}; + +enum { + INT_1L_2P = 0, + INT_1P_2L, + INT_1SP_2P, +}; + +struct mma7450_status { + u8 mod; + u8 ctl1; + u8 ctl2; +}; + +/*forward declear*/ +static ssize_t mma7450_show(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t mma7450_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count); +static int mma7450_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int mma7450_remove(struct i2c_client *client); +static int mma7450_suspend(struct i2c_client *client, pm_message_t state); +static int mma7450_resume(struct i2c_client *client); +static void mma_bh_handler(struct work_struct *work); + +/*definition*/ +static struct regulator *reg_dvdd_io; +static struct regulator *reg_avdd; +static struct i2c_client *mma7450_client; +static struct device *hwmon_dev; +static struct input_polled_dev *mma7450_idev; +static struct mxc_mma7450_platform_data *plat_data; +static u8 mma7450_mode; +static struct device_attribute mma7450_dev_attr = { + .attr = { + .name = "mma7450_ctl", + .mode = S_IRUSR | S_IWUSR, + }, + .show = mma7450_show, + .store = mma7450_store, +}; + +static const struct i2c_device_id mma7450_id[] = { + {"mma7450", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, mma7450_id); + +static struct i2c_driver i2c_mma7450_driver = { + .driver = { + .name = "mma7450", + }, + .probe = mma7450_probe, + .remove = mma7450_remove, + .suspend = mma7450_suspend, + .resume = mma7450_resume, + .id_table = mma7450_id, +}; + +static struct mma7450_status mma_status = { + .mod = 0, + .ctl1 = 0, + .ctl2 = 0, +}; + +DECLARE_WORK(mma_work, mma_bh_handler); + +#ifdef DEBUG +enum { + MMA_REG_R = 0, + MMA_REG_W, + MMA_SET_MOD, + MMA_SET_L_THR, + MMA_SET_P_THR, + MMA_SET_INTP, + MMA_SET_INTB, + MMA_SET_G, + MMA_I2C_EABLE, + MMA_OFF_X, + MMA_OFF_Y, + MMA_OFF_Z, + MMA_SELF_TEST, + MMA_SET_LDPL, + MMA_SET_PDPL, + MMA_SET_PDV, + MMA_SET_LTV, + MMA_SET_TW, + MMA_CMD_MAX +}; + +static char *command[MMA_CMD_MAX] = { + [MMA_REG_R] = "readreg", + [MMA_REG_W] = "writereg", + [MMA_SET_MOD] = "setmod", + [MMA_SET_L_THR] = "setlt", + [MMA_SET_P_THR] = "setpt", + [MMA_SET_INTP] = "setintp", + [MMA_SET_INTB] = "setintb", + [MMA_SET_G] = "setg", + [MMA_I2C_EABLE] = "setie", + [MMA_OFF_X] = "setxo", + [MMA_OFF_Y] = "setyo", + [MMA_OFF_Z] = "setzo", + [MMA_SELF_TEST] = "selft", + [MMA_SET_LDPL] = "setldp", + [MMA_SET_PDPL] = "setpdp", + [MMA_SET_PDV] = "setpdv", + [MMA_SET_LTV] = "setltv", + [MMA_SET_TW] = "settw", +}; + +static void set_mod(u8 mode) +{ + int ret; + + ret = i2c_smbus_read_byte_data(mma7450_client, REG_MCTL); + /* shall I test the ret value? */ + ret = (ret & ~0x3) | (mode & 0x3); + mma_status.mod = ret; + i2c_smbus_write_byte_data(mma7450_client, REG_MCTL, ret); +} + +static void set_level_thr(u8 lth) +{ + i2c_smbus_write_byte_data(mma7450_client, REG_LDTH, lth); +} + +static void set_pulse_thr(u8 pth) +{ + i2c_smbus_write_byte_data(mma7450_client, REG_PDTH, pth); +} + +static void set_int_pin(u8 pin) +{ + int ret; + + ret = i2c_smbus_read_byte_data(mma7450_client, REG_CTL1); + ret = (ret & ~0x1) | (pin & 0x1); + mma_status.ctl1 = ret; + i2c_smbus_write_byte_data(mma7450_client, REG_CTL1, ret); +} + +static void set_int_bit(u8 bit) +{ + int ret; + + ret = i2c_smbus_read_byte_data(mma7450_client, REG_CTL1); + ret = (ret & ~0x6) | ((bit << 1) & 0x6); + mma_status.ctl1 = ret; + i2c_smbus_write_byte_data(mma7450_client, REG_CTL1, ret); +} + +static void set_g_level(u8 gl) +{ + int ret; + + ret = i2c_smbus_read_byte_data(mma7450_client, REG_MCTL); + ret = (ret & ~0xC) | ((gl << 2) & 0xC); + i2c_smbus_write_byte_data(mma7450_client, REG_MCTL, ret); +} + +static void set_i2c_enable(u8 i2c_e) +{ + int ret; + + ret = i2c_smbus_read_byte_data(mma7450_client, REG_I2CAD); + ret = (ret & ~0x80) | ((i2c_e << 7) & 0x80); + i2c_smbus_write_byte_data(mma7450_client, REG_I2CAD, ret); +} + +static void set_x_offset(u16 xo) +{ + u8 data; + + data = (xo & 0xFF); + i2c_smbus_write_byte_data(mma7450_client, REG_XOFFL, data); + data = (xo & 0xFF00) >> 8; + i2c_smbus_write_byte_data(mma7450_client, REG_XOFFH, data); +} + +static void set_y_offset(u16 yo) +{ + u8 data; + + data = (yo & 0xFF); + i2c_smbus_write_byte_data(mma7450_client, REG_YOFFL, data); + data = (yo & 0xFF00) >> 8; + i2c_smbus_write_byte_data(mma7450_client, REG_YOFFH, data); +} + +static void set_z_offset(u16 zo) +{ + u8 data; + + data = (zo & 0xFF); + i2c_smbus_write_byte_data(mma7450_client, REG_ZOFFL, data); + data = (zo & 0xFF00) >> 8; + i2c_smbus_write_byte_data(mma7450_client, REG_ZOFFH, data); +} + +static void selftest(u8 st) +{ + int ret; + + ret = i2c_smbus_read_byte_data(mma7450_client, REG_MCTL); + ret = (ret & ~0x10) | ((st << 4) & 0x10); + i2c_smbus_write_byte_data(mma7450_client, REG_MCTL, ret); +} + +static void set_level_det_p(u8 ldp) +{ + int ret; + + ret = i2c_smbus_read_byte_data(mma7450_client, REG_CTL2); + ret = (ret & ~0x1) | ((ldp << 0) & 0x1); + mma_status.ctl2 = ret; + i2c_smbus_write_byte_data(mma7450_client, REG_CTL2, ret); +} + +static void set_pulse_det_p(u8 pdp) +{ + int ret; + + ret = i2c_smbus_read_byte_data(mma7450_client, REG_CTL2); + ret = (ret & ~0x2) | ((pdp << 1) & 0x2); + mma_status.ctl2 = ret; + i2c_smbus_write_byte_data(mma7450_client, REG_CTL2, ret); +} + +static void set_pulse_duration(u8 pd) +{ + i2c_smbus_write_byte_data(mma7450_client, REG_PD, pd); +} + +static void set_latency_time(u8 lt) +{ + i2c_smbus_write_byte_data(mma7450_client, REG_LT, lt); +} + +static void set_time_window(u8 tw) +{ + i2c_smbus_write_byte_data(mma7450_client, REG_TW, tw); +} + +static void parse_arg(const char *arg, int *reg, int *value) +{ + const char *p; + + for (p = arg;; p++) { + if (*p == ' ' || *p == '\0') + break; + } + + p++; + + *reg = simple_strtoul(arg, NULL, 16); + *value = simple_strtoul(p, NULL, 16); +} + +static void cmd_read_reg(const char *arg) +{ + int reg, value, ret; + + parse_arg(arg, ®, &value); + ret = i2c_smbus_read_byte_data(mma7450_client, reg); + dev_info(&mma7450_client->dev, "read reg0x%x = %x\n", reg, ret); +} + +static void cmd_write_reg(const char *arg) +{ + int reg, value, ret; + + parse_arg(arg, ®, &value); + ret = i2c_smbus_write_byte_data(mma7450_client, reg, value); + dev_info(&mma7450_client->dev, "write reg result %s\n", + ret ? "failed" : "success"); +} + +static int exec_command(const char *buf, size_t count) +{ + const char *p, *s; + const char *arg; + int i, value = 0; + + for (p = buf;; p++) { + if (*p == ' ' || *p == '\0' || p - buf >= count) + break; + } + arg = p + 1; + + for (i = MMA_REG_R; i < MMA_CMD_MAX; i++) { + s = command[i]; + if (s && !strncmp(buf, s, p - buf)) { + dev_info(&mma7450_client->dev, "command %s\n", s); + goto mma_exec_command; + } + } + + dev_err(&mma7450_client->dev, "command is not found\n"); + return -1; + + mma_exec_command: + if (i != MMA_REG_R && i != MMA_REG_W) + value = simple_strtoul(arg, NULL, 16); + + switch (i) { + case MMA_REG_R: + cmd_read_reg(arg); + break; + case MMA_REG_W: + cmd_write_reg(arg); + break; + case MMA_SET_MOD: + set_mod(value); + break; + case MMA_SET_L_THR: + set_level_thr(value); + break; + case MMA_SET_P_THR: + set_pulse_thr(value); + break; + case MMA_SET_INTP: + set_int_pin(value); + break; + case MMA_SET_INTB: + set_int_bit(value); + break; + case MMA_SET_G: + set_g_level(value); + break; + case MMA_I2C_EABLE: + set_i2c_enable(value); + break; + case MMA_OFF_X: + set_x_offset(value); + break; + case MMA_OFF_Y: + set_y_offset(value); + break; + case MMA_OFF_Z: + set_z_offset(value); + break; + case MMA_SELF_TEST: + selftest(value); + break; + case MMA_SET_LDPL: + set_level_det_p(value); + break; + case MMA_SET_PDPL: + set_pulse_det_p(value); + break; + case MMA_SET_PDV: + set_pulse_duration(value); + break; + case MMA_SET_LTV: + set_latency_time(value); + break; + case MMA_SET_TW: + set_time_window(value); + break; + default: + dev_err(&mma7450_client->dev, "command is not found\n"); + break; + } + + return 0; +} + +static ssize_t mma7450_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret, reg; + + for (reg = REG_XOUTL; reg < REG_REVERVED_1; reg++) { + ret = i2c_smbus_read_byte_data(mma7450_client, reg); + dev_info(&mma7450_client->dev, "reg0x%02x:\t%03d\t0x%02x\n", + reg, (s8) ret, ret); + } + + return 0; +} + +static ssize_t mma7450_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + exec_command(buf, count); + + return count; +} + +#else + +static ssize_t mma7450_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return 0; +} + +static ssize_t mma7450_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + return count; +} + +#endif + +static void report_abs(void) +{ + u8 status, mod = mma_status.mod; + s16 x, y, z; + + status = i2c_smbus_read_byte_data(mma7450_client, REG_STATUS); + if (!(status & 0x01)) { /* data ready in measurement mode? */ + return; + } + if ((mod & 0x0c) == 0) { /* 8g range */ + x = 0xFF & i2c_smbus_read_byte_data(mma7450_client, REG_XOUTL); + x |= 0xFF00 & + (i2c_smbus_read_byte_data(mma7450_client, REG_XOUTH) << 8); + y = 0xFF & i2c_smbus_read_byte_data(mma7450_client, REG_YOUTL); + y |= 0xFF00 & + (i2c_smbus_read_byte_data(mma7450_client, REG_YOUTH) << 8); + z = 0xFF & i2c_smbus_read_byte_data(mma7450_client, REG_ZOUTL); + z |= 0xFF00 & + (i2c_smbus_read_byte_data(mma7450_client, REG_ZOUTH) << 8); + } else { /* 2g/4g range */ + x = 0xFF & i2c_smbus_read_byte_data(mma7450_client, REG_XOUT8); + y = 0xFF & i2c_smbus_read_byte_data(mma7450_client, REG_YOUT8); + z = 0xFF & i2c_smbus_read_byte_data(mma7450_client, REG_ZOUT8); + } + + status = i2c_smbus_read_byte_data(mma7450_client, REG_STATUS); + if (status & 0x02) { /* data is overwrite */ + return; + } + + /* convert signed 10bits to signed 16bits */ + x = (short)(x << 6) >> 6; + y = (short)(y << 6) >> 6; + z = (short)(z << 6) >> 6; + + input_report_abs(mma7450_idev->input, ABS_X, x); + input_report_abs(mma7450_idev->input, ABS_Y, y); + input_report_abs(mma7450_idev->input, ABS_Z, z); + input_sync(mma7450_idev->input); +} + +static void mma_bh_handler(struct work_struct *work) +{ +} + +static void mma7450_dev_poll(struct input_polled_dev *dev) +{ + report_abs(); +} + +static irqreturn_t mma7450_interrupt(int irq, void *dev_id) +{ + struct input_dev *input_dev = dev_id; + u8 int_bit, int_pin; + + int_bit = mma_status.ctl1 & 0x6; + int_pin = mma_status.ctl1 & 0x1; + + switch (mma_status.mod & 0x03) { + case 1: + /*only int1 report data ready int */ + if (plat_data->int1 != irq) + goto error_bad_int; + schedule_work(&mma_work); + break; + case 2: + /* for level and pulse detection mode, + * choice tasklet to handle interrupt quickly. + * Currently, leave it doing nothing*/ + if (plat_data->int1 == irq) { + if ((int_bit == 0) && (int_pin != 0)) + goto error_bad_int; + if ((int_bit == 0x2) && (int_pin != 0x1)) + goto error_bad_int; + if (int_bit == 0x4) + goto error_bad_int; + } + if (plat_data->int2 == irq) { + if ((int_bit == 0) && (int_pin != 0x1)) + goto error_bad_int; + if ((int_bit == 0x2) && (int_pin != 0)) + goto error_bad_int; + if (int_bit == 0x4) + goto error_bad_int; + } + + dev_info(&input_dev->dev, "motion detected in level mod\n"); + + break; + case 3: + if (plat_data->int1 == irq) { + if ((int_bit == 0) && (int_pin != 0x1)) + goto error_bad_int; + if ((int_bit == 0x2) && (int_pin != 0)) + goto error_bad_int; + if ((int_bit == 0x4) && (int_pin != 0x1)) + goto error_bad_int; + } + if (plat_data->int2 == irq) { + if ((int_bit == 0) && (int_pin != 0)) + goto error_bad_int; + if ((int_bit == 0x2) && (int_pin != 0x1)) + goto error_bad_int; + if ((int_bit == 0x4) && (int_pin != 0)) + goto error_bad_int; + } + + if (mma_status.ctl2 & 0x02) + dev_info(&input_dev->dev, + "freefall detected in pulse mod\n"); + else + dev_info(&input_dev->dev, + "motion detected in pulse mod\n"); + + break; + case 0: + default: + break; + } + error_bad_int: + return IRQ_RETVAL(1); +} + +static int mma7450_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct input_dev *idev; + + plat_data = + (struct mxc_mma7450_platform_data *)client->dev.platform_data; + if (plat_data == NULL) { + dev_err(&client->dev, "lack of platform data!\n"); + return -ENODEV; + } + + /*enable power supply */ + /*when to power on/off the power is to be considered later */ + /*shall I check the return value */ + reg_dvdd_io = regulator_get(&client->dev, plat_data->reg_dvdd_io); + if (reg_dvdd_io != ERR_PTR(-ENOENT)) + regulator_enable(reg_dvdd_io); + else + return -EINVAL; + + reg_avdd = regulator_get(&client->dev, plat_data->reg_avdd); + if (reg_avdd != ERR_PTR(-ENOENT)) + regulator_enable(reg_avdd); + else { + regulator_put(reg_dvdd_io); + return -EINVAL; + } + + /*bind the right device to the driver */ + ret = i2c_smbus_read_byte_data(client, REG_I2CAD); + if (MMA7450_I2C_ADDR != (0x7F & ret)) { /*compare the address value */ + dev_err(&client->dev, + "read chip ID 0x%x is not equal to 0x%x!\n", ret, + MMA7450_I2C_ADDR); + goto error_disable_power; + } + mma7450_client = client; + + /*interrupt register */ + /*when to register interrupt is to be considered later */ + + /*create device file in sysfs as user interface */ + ret = device_create_file(&client->dev, &mma7450_dev_attr); + if (ret) { + dev_err(&client->dev, "create device file failed!\n"); + goto error_disable_power; + } + + /*register to hwmon device */ + hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(hwmon_dev)) { + dev_err(&client->dev, "hwmon register failed!\n"); + ret = PTR_ERR(hwmon_dev); + goto error_rm_dev_file; + } + + /*input poll device register */ + mma7450_idev = input_allocate_polled_device(); + if (!mma7450_idev) { + dev_err(&client->dev, "alloc poll device failed!\n"); + ret = -ENOMEM; + goto error_rm_hwmon_dev; + } + mma7450_idev->poll = mma7450_dev_poll; + mma7450_idev->poll_interval = POLL_INTERVAL; + idev = mma7450_idev->input; + idev->name = DEVICE_NAME; + idev->id.bustype = BUS_I2C; + idev->dev.parent = &client->dev; + idev->evbit[0] = BIT_MASK(EV_ABS); + + input_set_abs_params(idev, ABS_X, -512, 512, INPUT_FUZZ, INPUT_FLAT); + input_set_abs_params(idev, ABS_Y, -512, 512, INPUT_FUZZ, INPUT_FLAT); + input_set_abs_params(idev, ABS_Z, -512, 512, INPUT_FUZZ, INPUT_FLAT); + ret = input_register_polled_device(mma7450_idev); + if (ret) { + dev_err(&client->dev, "register poll device failed!\n"); + goto error_free_poll_dev; + } + + /* configure gpio as input for interrupt monitor */ + plat_data->gpio_pin_get(); + + set_irq_type(plat_data->int1, IRQF_TRIGGER_RISING); + /* register interrupt handle */ + ret = request_irq(plat_data->int1, mma7450_interrupt, + IRQF_TRIGGER_RISING, DEVICE_NAME, idev); + + if (ret) { + dev_err(&client->dev, "request_irq(%d) returned error %d\n", + plat_data->int1, ret); + goto error_rm_poll_dev; + } + + set_irq_type(plat_data->int2, IRQF_TRIGGER_RISING); + ret = request_irq(plat_data->int2, mma7450_interrupt, + IRQF_TRIGGER_RISING, DEVICE_NAME, idev); + if (ret) { + dev_err(&client->dev, "request_irq(%d) returned error %d\n", + plat_data->int2, ret); + goto error_free_irq1; + } + + dev_info(&client->dev, "mma7450 device is probed successfully.\n"); + + set_mod(1); + return 0; /*what value shall be return */ + + /*error handle */ + error_free_irq1: + free_irq(plat_data->int1, 0); + error_rm_poll_dev: + input_unregister_polled_device(mma7450_idev); + error_free_poll_dev: + input_free_polled_device(mma7450_idev); + error_rm_hwmon_dev: + hwmon_device_unregister(hwmon_dev); + error_rm_dev_file: + device_remove_file(&client->dev, &mma7450_dev_attr); + error_disable_power: + regulator_disable(reg_dvdd_io); /*shall I check the return value */ + regulator_disable(reg_avdd); + regulator_put(reg_dvdd_io); + regulator_put(reg_avdd); + + return ret; +} + +static int mma7450_remove(struct i2c_client *client) +{ + free_irq(plat_data->int2, mma7450_idev->input); + free_irq(plat_data->int1, mma7450_idev->input); + plat_data->gpio_pin_put(); + input_unregister_polled_device(mma7450_idev); + input_free_polled_device(mma7450_idev); + hwmon_device_unregister(hwmon_dev); + device_remove_file(&client->dev, &mma7450_dev_attr); + regulator_disable(reg_dvdd_io); /*shall I check the return value */ + regulator_disable(reg_avdd); + regulator_put(reg_dvdd_io); + regulator_put(reg_avdd); + return 0; +} + +static int mma7450_suspend(struct i2c_client *client, pm_message_t state) +{ + mma7450_mode = i2c_smbus_read_byte_data(mma7450_client, REG_MCTL); + i2c_smbus_write_byte_data(mma7450_client, REG_MCTL, + mma7450_mode & ~0x3); + return 0; +} + +static int mma7450_resume(struct i2c_client *client) +{ + i2c_smbus_write_byte_data(mma7450_client, REG_MCTL, mma7450_mode); + return 0; +} + +static int __init init_mma7450(void) +{ + /*register driver */ + printk(KERN_INFO "add mma i2c driver\n"); + return i2c_add_driver(&i2c_mma7450_driver); +} + +static void __exit exit_mma7450(void) +{ + printk(KERN_INFO "del mma i2c driver.\n"); + return i2c_del_driver(&i2c_mma7450_driver); +} + +module_init(init_mma7450); +module_exit(exit_mma7450); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MMA7450 sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c-slave/Kconfig b/drivers/i2c-slave/Kconfig new file mode 100644 index 000000000000..89717e15e784 --- /dev/null +++ b/drivers/i2c-slave/Kconfig @@ -0,0 +1,39 @@ +# +# I2C slave subsystem configuration +# + +menuconfig I2C_SLAVE + bool "I2C Slave support" + depends on HAS_IOMEM + ---help--- + I2C (pronounce: I-square-C) is a slow serial bus protocol used in + many micro controller applications and developed by Philips. + + If you want I2C Slave support, you should say Y here. + + This I2C Slave support can also be built as a module. If so, the module + will be called i2c-slave. + +if I2C_SLAVE + +config I2C_SLAVE_CORE + tristate "I2C SLAVE CORE" + default y + ---help--- + i2c slave core. + +config MXC_I2C_SLAVE + tristate "MXC I2C SLAVE" + depends on I2C_SLAVE_CORE + default y + ---help--- + mxc i2c slave support. + +config I2C_SLAVE_CLIENT + tristate "I2C SLAVE CLIENT" + default y + ---help--- + i2c slave client that used when the master is on the same board. + this is for i2c master which is on the same board with the slave. + +endif # I2C_SLAVE diff --git a/drivers/i2c-slave/Makefile b/drivers/i2c-slave/Makefile new file mode 100644 index 000000000000..a7b08c919af9 --- /dev/null +++ b/drivers/i2c-slave/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the i2c slave. +# + +i2c_slave-objs := i2c_slave_ring_buffer.o i2c_slave_device.o i2c_slave_core.o +obj-$(CONFIG_I2C_SLAVE_CORE) += i2c_slave.o +obj-$(CONFIG_MXC_I2C_SLAVE) += mxc_i2c_slave.o +obj-$(CONFIG_I2C_SLAVE_CLIENT) += i2c_slave_client.o diff --git a/drivers/i2c-slave/i2c_slave_client.c b/drivers/i2c-slave/i2c_slave_client.c new file mode 100644 index 000000000000..4068fe172b83 --- /dev/null +++ b/drivers/i2c-slave/i2c_slave_client.c @@ -0,0 +1,81 @@ +/* + * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> + +struct i2c_client *i2c_slave_client; +static int i2c_slave_client_probe(struct i2c_client *adapter); +static int i2c_slave_client_remove(struct i2c_client *client); +static struct i2c_driver i2c_slave_client_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "i2c-slave-client", + }, + .probe = i2c_slave_client_probe, + .remove = i2c_slave_client_remove, +}; + +/*! + * ov2640 I2C attach function + * + * @param adapter struct i2c_adapter * + * @return Error code indicating success or failure + */ +static int i2c_slave_client_probe(struct i2c_client *client) +{ + i2c_slave_client = client; + return 0; +} + +/*! + * ov2640 I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int i2c_slave_client_remove(struct i2c_client *client) +{ + return 0; +} + +/*! + * ov2640 init function + * + * @return Error code indicating success or failure + */ +static __init int i2c_slave_client_init(void) +{ + return i2c_add_driver(&i2c_slave_client_driver); +} + +/*! + * OV2640 cleanup function + * + * @return Error code indicating success or failure + */ +static void __exit i2c_slave_client_clean(void) +{ + i2c_del_driver(&i2c_slave_client_driver); +} + +module_init(i2c_slave_client_init); +module_exit(i2c_slave_client_clean); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("I2c Slave Client Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c-slave/i2c_slave_core.c b/drivers/i2c-slave/i2c_slave_core.c new file mode 100644 index 000000000000..0592d53a6fad --- /dev/null +++ b/drivers/i2c-slave/i2c_slave_core.c @@ -0,0 +1,358 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/device.h> +#include <asm/uaccess.h> +#include "i2c_slave_device.h" + +int i2c_slave_major; + +static ssize_t i2c_slave_read(struct file *fd, char __user *buf, size_t len, + loff_t *ptr) +{ + i2c_slave_device_t *dev; + void *kbuf; + int ret; + + if (len == 0) + return 0; + + kbuf = kmalloc(len, GFP_KERNEL); + if (!kbuf) { + ret = -ENOMEM; + goto error0; + } + + dev = (i2c_slave_device_t *) fd->private_data; + if (!dev) { + ret = -ENODEV; + goto error1; + } + + ret = i2c_slave_device_read(dev, len, kbuf); + if (ret <= 0 || copy_to_user(buf, kbuf, len)) { + ret = -EFAULT; + } + + error1: + kfree(kbuf); + error0: + return ret; +} + +static ssize_t i2c_slave_write(struct file *fd, const char __user *buf, + size_t len, loff_t *ptr) +{ + i2c_slave_device_t *dev; + void *kbuf; + int ret; + + if (len == 0) + return 0; + + kbuf = kmalloc(len, GFP_KERNEL); + if (!kbuf) { + ret = -ENOMEM; + goto error0; + } + if (copy_from_user(kbuf, buf, len)) { + ret = -EFAULT; + goto error1; + } + + dev = (i2c_slave_device_t *) fd->private_data; + if (!dev) { + ret = -ENODEV; + goto error1; + } + + ret = i2c_slave_device_write(dev, len, (u8 *) kbuf); + + error1: + kfree(kbuf); + error0: + return ret; +} + +static int i2c_slave_ioctl(struct inode *inode, struct file *fd, + unsigned code, unsigned long value) +{ + /*todo */ + return 0; +} + +static int i2c_slave_open(struct inode *inode, struct file *fd) +{ + int ret; + unsigned int id; + i2c_slave_device_t *dev; + id = iminor(inode); + + if (id >= I2C_SLAVE_DEVICE_MAX) { + ret = -ENODEV; + goto error; + } + + dev = i2c_slave_device_find(id); + if (!dev) { + ret = -ENODEV; + goto error; + } + + i2c_slave_rb_clear(dev->receive_buffer); + i2c_slave_rb_clear(dev->send_buffer); + + if (i2c_slave_device_start(dev)) { + ret = -EBUSY; + goto error; + } + + fd->private_data = (void *)dev; + ret = 0; + + error: + return ret; +} + +static int i2c_slave_release(struct inode *inode, struct file *fd) +{ + int ret; + unsigned int id; + i2c_slave_device_t *dev; + id = iminor(inode); + + if (id >= I2C_SLAVE_DEVICE_MAX) { + ret = -ENODEV; + goto error; + } + + dev = i2c_slave_device_find(id); + if (!dev) { + ret = -ENODEV; + goto error; + } + + if (i2c_slave_device_stop(dev)) { + ret = -EBUSY; + goto error; + } + + ret = 0; + + error: + return ret; +} + +static const struct file_operations i2c_slave_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = i2c_slave_read, + .write = i2c_slave_write, + .ioctl = i2c_slave_ioctl, + .open = i2c_slave_open, + .release = i2c_slave_release, +}; + +static int i2c_slave_bus_match(struct device *dev, struct device_driver *drv) +{ + return 0; +} + +/* +static int i2c_slave_bus_probe(struct device *dev) +{ + struct device_driver *driver = dev->driver; + + if (driver) { + return driver->probe(dev); + } else { + dev_err(dev, "%s: no driver\n", __func__); + return -ENODEV; + } +} +*/ + +static int i2c_slave_bus_remove(struct device *dev) +{ + struct device_driver *driver = dev->driver; + + if (driver) { + if (!driver->remove) { + return 0; + } else { + return driver->remove(dev); + } + } else { + + dev_err(dev, "%s: no driver\n", __func__); + return -ENODEV; + } +} + +static void i2c_slave_bus_shutdown(struct device *dev) +{ + struct device_driver *driver = dev->driver; + + if (driver) { + + driver->shutdown(dev); + } else { + + dev_err(dev, "%s: no driver\n", __func__); + return; + } +} +static int i2c_slave_bus_suspend(struct device *dev, pm_message_t state) +{ + struct device_driver *driver = dev->driver; + + if (driver) { + + if (!driver->suspend) { + return 0; + } else { + return driver->suspend(dev, state); + } + } else { + + dev_err(dev, "%s: no driver\n", __func__); + return -ENODEV; + } +} + +static int i2c_slave_bus_resume(struct device *dev) +{ + struct device_driver *driver = dev->driver; + + if (driver) { + + if (!driver->resume) { + return 0; + } else { + return driver->resume(dev); + } + } else { + + dev_err(dev, "%s: no driver\n", __func__); + return -ENODEV; + } +} + +struct bus_type i2c_slave_bus_type = { + .name = "i2c-slave", + .match = i2c_slave_bus_match, + .remove = i2c_slave_bus_remove, + .shutdown = i2c_slave_bus_shutdown, + .suspend = i2c_slave_bus_suspend, + .resume = i2c_slave_bus_resume, +}; + +EXPORT_SYMBOL_GPL(i2c_slave_bus_type); + +static int i2c_slave_driver_probe(struct device *dev) +{ + return 0; +} + +static int i2c_slave_driver_remove(struct device *dev) +{ + return 0; +} + +static int i2c_slave_driver_shutdown(struct device *dev) +{ + return 0; +} + +static int i2c_slave_driver_suspend(struct device *dev, pm_message_t state) +{ + return 0; +} + +static int i2c_slave_driver_resume(struct device *dev) +{ + return 0; +} + +extern struct class *i2c_slave_class; + +static struct device_driver i2c_slave_driver = { + .name = "i2c-slave", + .owner = THIS_MODULE, + .bus = &i2c_slave_bus_type, + .probe = i2c_slave_driver_probe, + .remove = i2c_slave_driver_remove, + .shutdown = i2c_slave_driver_shutdown, + .suspend = i2c_slave_driver_suspend, + .resume = i2c_slave_driver_resume, +}; + +static int __init i2c_slave_dev_init(void) +{ + int ret; + + printk(KERN_INFO "i2c slave /dev entries driver\n"); + + ret = bus_register(&i2c_slave_bus_type); + if (ret) { + printk(KERN_ERR "%s: bus_register error\n", __func__); + goto out; + } + + i2c_slave_class = class_create(THIS_MODULE, "i2c-slave"); + if (IS_ERR(i2c_slave_class)) { + pr_err("%s: class_create error\n", __func__); + goto out_unreg_bus; + } + + i2c_slave_major = register_chrdev(0, "i2c-slave", &i2c_slave_fops); + if (i2c_slave_major <= 0) { + pr_err("%s: register_chrdev error\n", __func__); + goto out_unreg_class; + } + + ret = driver_register(&i2c_slave_driver); + if (ret) { + pr_err("%s: driver_register error\n", __func__); + goto out_unreg_chrdev; + } + + return 0; + + out_unreg_chrdev: + unregister_chrdev(i2c_slave_major, "i2c-slave"); + out_unreg_class: + class_destroy(i2c_slave_class); + out_unreg_bus: + bus_unregister(&i2c_slave_bus_type); + out: + pr_err("%s: init error\n", __func__); + return ret; +} + +static void __exit i2c_dev_exit(void) +{ + driver_unregister(&i2c_slave_driver); + class_destroy(i2c_slave_class); + unregister_chrdev(i2c_slave_major, "i2c-slave"); + bus_unregister(&i2c_slave_bus_type); +} + +module_init(i2c_slave_dev_init); +module_exit(i2c_dev_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("I2C Slave Driver Core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c-slave/i2c_slave_device.c b/drivers/i2c-slave/i2c_slave_device.c new file mode 100644 index 000000000000..bfe39809928e --- /dev/null +++ b/drivers/i2c-slave/i2c_slave_device.c @@ -0,0 +1,270 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/major.h> +#include <linux/mm.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/kdev_t.h> +#include "i2c_slave_device.h" +static i2c_slave_device_t *i2c_slave_devices[I2C_SLAVE_DEVICE_MAX]; +struct class *i2c_slave_class; +static int i2c_slave_device_get_id(void) +{ + int i; + for (i = 0; i < I2C_SLAVE_DEVICE_MAX; i++) { + if (!i2c_slave_devices[i]) + return i; + } + return -1; +} + +i2c_slave_device_t *i2c_slave_device_find(int id) +{ + if (id >= 0 && id < I2C_SLAVE_DEVICE_MAX) + return i2c_slave_devices[id]; + + else + return NULL; +} +void i2c_slave_device_set_name(i2c_slave_device_t *device, char *name) +{ + device->name = name; +} + +void i2c_slave_device_set_address(i2c_slave_device_t *device, u8 address) +{ + device->address = address; +} + +u8 i2c_slave_device_get_addr(i2c_slave_device_t *device) +{ + return device->address; +} + +int i2c_slave_device_set_freq(i2c_slave_device_t *device, u32 freq) +{ + /*TODO: freq check */ + device->scl_freq = freq; + return 0; +} + +u32 i2c_slave_device_get_freq(i2c_slave_device_t *device) +{ + return device->scl_freq; +} + +/*used by the specific i2c device to register itself to the core.*/ +i2c_slave_device_t *i2c_slave_device_alloc(void) +{ + int id; + i2c_slave_device_t *device; + id = i2c_slave_device_get_id(); + if (id < 0) { + goto error; + } + device = + (i2c_slave_device_t *) kzalloc(sizeof(i2c_slave_device_t), + GFP_KERNEL); + if (!device) { + printk(KERN_ERR "%s: alloc device error\n", __func__); + goto error_device; + } + device->receive_buffer = i2c_slave_rb_alloc(PAGE_SIZE); + if (!device->receive_buffer) { + printk(KERN_ERR "%s: alloc receive buffer error\n", __func__); + goto error_receive_buffer; + } + device->send_buffer = i2c_slave_rb_alloc(PAGE_SIZE); + if (!device->send_buffer) { + printk(KERN_ERR "%s: alloc send buffer error\n", __func__); + goto error_send_buffer; + } + device->id = id; + return device; + + error_send_buffer: + kfree(device->receive_buffer); + error_receive_buffer: + kfree((void *)device); + error_device: + pr_debug(KERN_ERR "%s: no memory\n", __func__); + error: + return 0; +} + +void i2c_slave_device_free(i2c_slave_device_t *dev) +{ + i2c_slave_rb_release(dev->receive_buffer); + i2c_slave_rb_release(dev->send_buffer); + kfree(dev); +} + +int i2c_slave_device_register(i2c_slave_device_t *device) +{ + device->dev = device_create(i2c_slave_class, NULL, + MKDEV(i2c_slave_major, device->id), + NULL, "slave-i2c-%d", device->id); + if (!device->dev) { + return -1; + } + i2c_slave_devices[device->id] = device; + return 0; +} + +void i2c_slave_device_unregister(i2c_slave_device_t *device) +{ + device_destroy(i2c_slave_class, MKDEV(i2c_slave_major, device->id)); + i2c_slave_devices[device->id] = 0; + i2c_slave_device_free(device); +} + +/* + this two functions are used by i2c slave core to start or stop the specific i2c device. +*/ +int i2c_slave_device_start(i2c_slave_device_t *device) +{ + return device->start(device); +} + +int i2c_slave_device_stop(i2c_slave_device_t *device) +{ + return device->stop(device); +} + +/* + this two functions are used by i2c slave core to get data by the specific i2c slave device + or send data to it to feed i2c master's need. + + @mod: async(1) or sync(0) mode. +*/ +int i2c_slave_device_read(i2c_slave_device_t *device, int num, u8 *data) +{ + int read_num, read_total = 0; + int step = 1000; + u8 *read_buf = data; + printk(KERN_INFO "%s: device id=%d, num=%d\n", __func__, device->id, + num); + read_num = i2c_slave_rb_consume(device->receive_buffer, num, read_buf); + read_total += read_num; + read_buf += read_num; + step--; + while ((read_total < num) && step) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ / 10); + if (!signal_pending(current)) { + } else { + /*TODO*/ break; + } + read_num = + i2c_slave_rb_consume(device->receive_buffer, + num - read_total, read_buf); + num -= read_num; + read_buf += read_num; + step--; + } + return read_total; +} +int i2c_slave_device_write(i2c_slave_device_t *device, int num, u8 *data) +{ + int write_num, write_total = 0; + int step = 1000; + u8 *buf_index = data; + write_num = i2c_slave_rb_produce(device->send_buffer, num, buf_index); + write_total += write_num; + buf_index += write_num; + step--; + while (write_total < num && step) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ / 10); + if (!signal_pending(current)) { + } else { + /*TODO*/ step = 0; + break; + } + write_num = + i2c_slave_rb_produce(device->send_buffer, num - write_total, + buf_index); + write_total += write_num; + buf_index += write_num; + step--; + } + while (step && i2c_slave_rb_num(device->send_buffer)) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ / 10); + if (!signal_pending(current)) { + step--; + } else { + /*TODO*/ step = 0; + break; + } + } + if (!step) { + write_total -= i2c_slave_rb_num(device->send_buffer); + i2c_slave_rb_clear(device->send_buffer); + } + return write_total; +} + +/* + * this 2 functions used by the specific i2c slave device when they got data from master(produce), + * or is request by master(consume). + */ +int i2c_slave_device_produce(i2c_slave_device_t *device, int num, u8 *data) +{ + int ret; + ret = i2c_slave_rb_produce(device->receive_buffer, num, data); + return ret; +} +int i2c_slave_device_consume(i2c_slave_device_t *device, int num, u8 *data) +{ + return i2c_slave_rb_consume(device->send_buffer, num, data); +} + +EXPORT_SYMBOL(i2c_slave_device_set_name); +EXPORT_SYMBOL(i2c_slave_device_set_address); +EXPORT_SYMBOL(i2c_slave_device_get_addr); +EXPORT_SYMBOL(i2c_slave_device_find); +EXPORT_SYMBOL(i2c_slave_device_set_freq); +EXPORT_SYMBOL(i2c_slave_device_get_freq); + +/* +* used by the specific i2c device to register itself to the core. +*/ +EXPORT_SYMBOL(i2c_slave_device_alloc); +EXPORT_SYMBOL(i2c_slave_device_free); +EXPORT_SYMBOL(i2c_slave_device_register); +EXPORT_SYMBOL(i2c_slave_device_unregister); + +/* + this two functions are used by i2c slave core to start or stop the specific i2c device. +*/ +EXPORT_SYMBOL(i2c_slave_device_start); +EXPORT_SYMBOL(i2c_slave_device_stop); + +/* + this two functions are used by i2c slave core to get data by the specific i2c slave device + or send data to it for it to feed i2c master's need. + + @mod: async(1) or sync(0) mode. +*/ +EXPORT_SYMBOL(i2c_slave_device_read); +EXPORT_SYMBOL(i2c_slave_device_write); + +/* +* this 2 functions used by the specific i2c slave device when they got data from master, +* or is request by master. +*/ +EXPORT_SYMBOL(i2c_slave_device_produce); +EXPORT_SYMBOL(i2c_slave_device_consume); diff --git a/drivers/i2c-slave/i2c_slave_device.h b/drivers/i2c-slave/i2c_slave_device.h new file mode 100644 index 000000000000..e08ea569e030 --- /dev/null +++ b/drivers/i2c-slave/i2c_slave_device.h @@ -0,0 +1,79 @@ +/* + * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __I2C_SLAVE_H__ +#define __I2C_SLAVE_H__ + +#include <linux/list.h> +#include "i2c_slave_ring_buffer.h" + +#define I2C_SLAVE_DEVICE_MAX 256 +extern int i2c_slave_major; + +typedef struct i2c_slave_device { + struct list_head list; + u8 address; + u32 scl_freq; + char *name; + i2c_slave_ring_buffer_t *receive_buffer; + i2c_slave_ring_buffer_t *send_buffer; + int (*start) (struct i2c_slave_device *); + int (*stop) (struct i2c_slave_device *); + /*int (*set_freq)(struct i2c_slave_device*); + int (*set_addr)(struct i2c_slave_device*);*/ + void *private_data; + struct device *dev; + int id; +} i2c_slave_device_t; + +/* + used by the specific device to set some infomations. +*/ +void i2c_slave_device_set_name(i2c_slave_device_t *device, char *name); +void i2c_slave_device_set_address(i2c_slave_device_t *device, u8 address); +i2c_slave_device_t *i2c_slave_device_find(int id); +u8 i2c_slave_device_get_addr(i2c_slave_device_t *device); +int i2c_slave_device_set_freq(i2c_slave_device_t *device, u32 freq); +u32 i2c_slave_device_get_freq(i2c_slave_device_t *device); + +/* +**used by the specific i2c device to register itself to the core. +*/ +i2c_slave_device_t *i2c_slave_device_alloc(void); +void i2c_slave_device_free(i2c_slave_device_t *); +int i2c_slave_device_register(i2c_slave_device_t *device); +void i2c_slave_device_unregister(i2c_slave_device_t *device); + +/* + this two functions are used by i2c slave core to start or stop the specific i2c device. +*/ +int i2c_slave_device_start(i2c_slave_device_t *device); +int i2c_slave_device_stop(i2c_slave_device_t *device); + +/* + this two functions are used by i2c slave core to get data by the specific i2c slave device + or send data to it for it to feed i2c master's need. + + @mod: async(1) or sync(0) mode. +*/ +int i2c_slave_device_read(i2c_slave_device_t *device, int num, u8 *data); +int i2c_slave_device_write(i2c_slave_device_t *device, int num, u8 *data); + +/* +*this 2 functions used by the specific i2c slave device when they got data from master, +*or is requested by master. +*/ +int i2c_slave_device_produce(i2c_slave_device_t *device, int num, u8 *data); +int i2c_slave_device_consume(i2c_slave_device_t *device, int num, u8 *data); + +#endif diff --git a/drivers/i2c-slave/i2c_slave_ring_buffer.c b/drivers/i2c-slave/i2c_slave_ring_buffer.c new file mode 100644 index 000000000000..4a55e099235f --- /dev/null +++ b/drivers/i2c-slave/i2c_slave_ring_buffer.c @@ -0,0 +1,185 @@ +/* + * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/slab.h> +#include <linux/hardirq.h> +#include "i2c_slave_ring_buffer.h" + +i2c_slave_ring_buffer_t *i2c_slave_rb_alloc(int size) +{ + i2c_slave_ring_buffer_t *ring_buf; + + ring_buf = + (i2c_slave_ring_buffer_t *) kzalloc(sizeof(i2c_slave_ring_buffer_t), + GFP_KERNEL); + if (!ring_buf) { + pr_debug("%s: alloc ring_buf error\n", __func__); + goto error; + } + + ring_buf->buffer = kmalloc(size, GFP_KERNEL); + if (!ring_buf->buffer) { + pr_debug("%s: alloc buffer error\n", __func__); + goto error1; + } + + ring_buf->total = size; + + ring_buf->lock = __SPIN_LOCK_UNLOCKED(ring_buf->lock); + return ring_buf; + + error1: + kfree(ring_buf); + error: + return NULL; +} + +void i2c_slave_rb_release(i2c_slave_ring_buffer_t *ring) +{ + unsigned long flags; + + spin_lock_irqsave(ring->lock, flags); + kfree(ring->buffer); + spin_unlock_irqrestore(ring->lock, flags); + kfree(ring); +} + +int i2c_slave_rb_produce(i2c_slave_ring_buffer_t *ring, int num, char *data) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(ring->lock, flags); + + if (ring->start < ring->end) { + if ((ring->start + num) < ring->end) { /*have enough space */ + memcpy(&ring->buffer[ring->start], data, num); + ring->start += num; + ret = num; + } else { /*space not enough, just copy part of it. */ + ret = ring->end - ring->start; + memcpy(&ring->buffer[ring->start], data, ret); + ring->start += ret; + ring->full = 1; + } + } else if (ring->start >= ring->end && !ring->full) { + if (ring->start + num <= ring->total) { /*space enough */ + ret = num; + memcpy(&ring->buffer[ring->start], data, ret); + ring->start += ret; + } else { /*turn ring->start around */ + ret = ring->total - ring->start; + memcpy(&ring->buffer[ring->start], data, ret); + ring->start = 0; + num -= ret; + data += ret; + if (num < ring->end) { /*space enough */ + ret += num; + memcpy(&ring->buffer[ring->start], data, num); + } else { /*space not enough, just copy part of it. */ + ret += ring->end; + memcpy(&ring->buffer[ring->start], data, + ring->end); + ring->start = ring->end; + ring->full = 1; + } + } + } else { /*(ring->data == ring->end) && ring->full ) : full */ + ret = 0; + } + + spin_unlock_irqrestore(ring->lock, flags); + return ret; +} + +int i2c_slave_rb_consume(i2c_slave_ring_buffer_t *ring, int num, char *data) +{ + int ret; + unsigned long flags; + spin_lock_irqsave(ring->lock, flags); + if (num <= 0) { + ret = 0; + goto out; + } + + if (ring->start > ring->end) { + if (num <= ring->start - ring->end) { /*enough */ + ret = num; + memcpy(data, &ring->buffer[ring->end], ret); + ring->end += ret; + } else { /*not enough */ + ret = ring->start - ring->end; + memcpy(data, &ring->buffer[ring->end], ret); + ring->end += ret; + } + } else if (ring->start < ring->end || ring->full) { + if (num <= ring->total - ring->end) { + ret = ring->total - ring->end; + memcpy(data, &ring->buffer[ring->end], ret); + ring->end += ret; + } else if (num <= ring->total - ring->end + ring->start) { + ret = ring->total - ring->end; + memcpy(data, &ring->buffer[ring->end], ret); + ring->end = 0; + data += ret; + num -= ret; + memcpy(data, &ring->buffer[ring->end], num); + ring->end = num; + ret += num; + } else { + ret = ring->total - ring->end; + memcpy(data, &ring->buffer[ring->end], ret); + ring->end = 0; + data += ret; + num -= ret; + memcpy(data, &ring->buffer[ring->end], ring->start); + ring->end = ring->start; + ret += ring->start; + } + ring->full = 0; + } else { /*empty */ + ret = 0; + } + + out: + spin_unlock_irqrestore(ring->lock, flags); + + return ret; +} + +int i2c_slave_rb_num(i2c_slave_ring_buffer_t *ring) +{ + int ret; + unsigned long flags; + spin_lock_irqsave(ring->lock, flags); + if (ring->start > ring->end) { + ret = ring->start - ring->end; + } else if (ring->start < ring->end) { + ret = ring->total - ring->end + ring->start; + } else if (ring->full) { + ret = ring->total; + } else { + ret = 0; + } + spin_unlock_irqrestore(ring->lock, flags); + return ret; +} + +void i2c_slave_rb_clear(i2c_slave_ring_buffer_t *ring) +{ + unsigned long flags; + spin_lock_irqsave(ring->lock, flags); + + ring->start = ring->end = 0; + ring->full = 0; + spin_unlock_irqrestore(ring->lock, flags); +} diff --git a/drivers/i2c-slave/i2c_slave_ring_buffer.h b/drivers/i2c-slave/i2c_slave_ring_buffer.h new file mode 100644 index 000000000000..1068e5f1b527 --- /dev/null +++ b/drivers/i2c-slave/i2c_slave_ring_buffer.h @@ -0,0 +1,39 @@ +#ifndef __BUFFER_MANAGER_H__ +#define __BUFFER_MANAGER_H__ +/* + * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/types.h> +#include <linux/spinlock.h> + +typedef struct i2c_slave_ring_buffer { + int start; + int end; + int total; + bool full; + char *buffer; + spinlock_t lock; +} i2c_slave_ring_buffer_t; + +i2c_slave_ring_buffer_t *i2c_slave_rb_alloc(int size); + +void i2c_slave_rb_release(i2c_slave_ring_buffer_t *ring); + +int i2c_slave_rb_produce(i2c_slave_ring_buffer_t *ring, int num, char *data); + +int i2c_slave_rb_consume(i2c_slave_ring_buffer_t *ring, int num, char *data); + +int i2c_slave_rb_num(i2c_slave_ring_buffer_t *ring); + +void i2c_slave_rb_clear(i2c_slave_ring_buffer_t *ring); + +#endif diff --git a/drivers/i2c-slave/mxc_i2c_slave.c b/drivers/i2c-slave/mxc_i2c_slave.c new file mode 100644 index 000000000000..39e067303f6b --- /dev/null +++ b/drivers/i2c-slave/mxc_i2c_slave.c @@ -0,0 +1,329 @@ +/* + * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <asm/io.h> +#include <linux/pm.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include "i2c_slave_device.h" +#include "mxc_i2c_slave.h" +#include "mxc_i2c_slave_reg.h" + +struct mxc_i2c_slave_clk { + int reg_value; + int div; +}; + +static const struct mxc_i2c_slave_clk i2c_clk_table[] = { + {0x20, 22}, {0x21, 24}, {0x22, 26}, {0x23, 28}, + {0, 30}, {1, 32}, {0x24, 32}, {2, 36}, + {0x25, 36}, {0x26, 40}, {3, 42}, {0x27, 44}, + {4, 48}, {0x28, 48}, {5, 52}, {0x29, 56}, + {6, 60}, {0x2A, 64}, {7, 72}, {0x2B, 72}, + {8, 80}, {0x2C, 80}, {9, 88}, {0x2D, 96}, + {0xA, 104}, {0x2E, 112}, {0xB, 128}, {0x2F, 128}, + {0xC, 144}, {0xD, 160}, {0x30, 160}, {0xE, 192}, + {0x31, 192}, {0x32, 224}, {0xF, 240}, {0x33, 256}, + {0x10, 288}, {0x11, 320}, {0x34, 320}, {0x12, 384}, + {0x35, 384}, {0x36, 448}, {0x13, 480}, {0x37, 512}, + {0x14, 576}, {0x15, 640}, {0x38, 640}, {0x16, 768}, + {0x39, 768}, {0x3A, 896}, {0x17, 960}, {0x3B, 1024}, + {0x18, 1152}, {0x19, 1280}, {0x3C, 1280}, {0x1A, 1536}, + {0x3D, 1536}, {0x3E, 1792}, {0x1B, 1920}, {0x3F, 2048}, + {0x1C, 2304}, {0x1D, 2560}, {0x1E, 3072}, {0x1F, 3840}, + {0, 0} +}; + +extern void gpio_i2c_active(int i2c_num); +extern void gpio_i2c_inactive(int i2c_num); + +static irqreturn_t interrupt_handler(int irq, void *dev_id) +{ + u16 status, ctl; + int num; + u16 data; + struct mxc_i2c_slave_device *mxc_i2c = + (struct mxc_i2c_slave_device *)dev_id; + + status = readw(mxc_i2c->reg_base + MXC_I2SR); + ctl = readw(mxc_i2c->reg_base + MXC_I2CR); + + dev_dbg(mxc_i2c->dev->dev, "status=%x, ctl=%x\n", status, ctl); + + if (status & MXC_I2SR_IAAS) { + if (status & MXC_I2SR_SRW) { + /*slave send */ + num = + i2c_slave_device_consume(mxc_i2c->dev, 1, + (u8 *) &data); + if (num < 1) { + /*FIXME:not ready to send data */ + printk(KERN_ERR + " i2c-slave:%s:data not ready\n", + __func__); + } else { + ctl |= MXC_I2CR_MTX; + writew(ctl, mxc_i2c->reg_base + MXC_I2CR); + /*send data */ + writew(data, mxc_i2c->reg_base + MXC_I2DR); + } + + } else { + /*slave receive */ + ctl &= ~MXC_I2CR_MTX; + writew(ctl, mxc_i2c->reg_base + MXC_I2CR); + + /*dummy read */ + data = readw(mxc_i2c->reg_base + MXC_I2DR); + } + } else { + /*slave send */ + if (ctl & MXC_I2CR_MTX) { + if (!(status & MXC_I2SR_RXAK)) { /*ACK received */ + num = + i2c_slave_device_consume(mxc_i2c->dev, 1, + (u8 *) &data); + if (num < 1) { + /*FIXME:not ready to send data */ + printk(KERN_ERR + " i2c-slave:%s:data not ready\n", + __func__); + } else { + ctl |= MXC_I2CR_MTX; + writew(ctl, + mxc_i2c->reg_base + MXC_I2CR); + writew(data, + mxc_i2c->reg_base + MXC_I2DR); + } + } else { + /*no ACK. */ + /*dummy read */ + ctl &= ~MXC_I2CR_MTX; + writew(ctl, mxc_i2c->reg_base + MXC_I2CR); + data = readw(mxc_i2c->reg_base + MXC_I2DR); + } + } else { /*read */ + ctl &= ~MXC_I2CR_MTX; + writew(ctl, mxc_i2c->reg_base + MXC_I2CR); + + /*read */ + data = readw(mxc_i2c->reg_base + MXC_I2DR); + i2c_slave_device_produce(mxc_i2c->dev, 1, + (u8 *) &data); + } + + } + + writew(0x0, mxc_i2c->reg_base + MXC_I2SR); + + return IRQ_HANDLED; +} + +static int start(i2c_slave_device_t *device) +{ + volatile unsigned int cr; + unsigned int addr; + struct mxc_i2c_slave_device *mxc_dev; + + mxc_dev = (struct mxc_i2c_slave_device *)device->private_data; + if (!mxc_dev) { + dev_err(device->dev, "%s: get mxc_dev error\n", __func__); + return -ENODEV; + } + + clk_enable(mxc_dev->clk); + /* Set the frequency divider */ + writew(mxc_dev->clkdiv, mxc_dev->reg_base + MXC_IFDR); + + /* Set the Slave bit */ + cr = readw(mxc_dev->reg_base + MXC_I2CR); + cr &= (!MXC_I2CR_MSTA); + writew(cr, mxc_dev->reg_base + MXC_I2CR); + + /*Set Slave Address */ + addr = mxc_dev->dev->address << 1; + writew(addr, mxc_dev->reg_base + MXC_IADR); + + /* Clear the status register */ + writew(0x0, mxc_dev->reg_base + MXC_I2SR); + + /* Enable I2C and its interrupts */ + writew(MXC_I2CR_IEN, mxc_dev->reg_base + MXC_I2CR); + writew(MXC_I2CR_IEN | MXC_I2CR_IIEN, mxc_dev->reg_base + MXC_I2CR); + + return 0; + +} + +static int stop(i2c_slave_device_t *device) +{ + struct mxc_i2c_slave_device *mxc_dev; + + mxc_dev = (struct mxc_i2c_slave_device *)device->private_data; + + writew(0x0, mxc_dev->reg_base + MXC_I2CR); + clk_disable(mxc_dev->clk); + + return 0; +} + +static int mxc_i2c_slave_probe(struct platform_device *pdev) +{ + int i; + u32 clk_freq; + struct resource *res; + struct mxc_i2c_slave_device *mxc_dev; + + mxc_dev = kzalloc(sizeof(struct mxc_i2c_slave_device), GFP_KERNEL); + if (!mxc_dev) { + goto error0; + } + mxc_dev->dev = i2c_slave_device_alloc(); + if (mxc_dev->dev == 0) { + dev_err(&pdev->dev, "%s: i2c_slave_device_alloc error\n", + __func__); + goto error1; + } + + i2c_slave_device_set_address(mxc_dev->dev, MXC_I2C_SLAVE_ADDRESS); + i2c_slave_device_set_freq(mxc_dev->dev, MXC_I2C_SLAVE_FREQ); + i2c_slave_device_set_name(mxc_dev->dev, MXC_I2C_SLAVE_NAME); + mxc_dev->dev->start = start; + mxc_dev->dev->stop = stop; + + mxc_dev->dev->private_data = mxc_dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "%s: get resource error\n", __func__); + goto error2; + } + mxc_dev->reg_base = IO_ADDRESS(res->start); + + mxc_dev->irq = platform_get_irq(pdev, 0); + if (mxc_dev->irq < 0) { + dev_err(&pdev->dev, "%s: get irq error\n", __func__); + goto error2; + } + if (request_irq(mxc_dev->irq, interrupt_handler, + 0, mxc_dev->dev->name, mxc_dev) < 0) { + dev_err(&pdev->dev, "%s: request_irq error\n", __func__); + goto error2; + } + + /*i2c id on soc */ + mxc_dev->id = pdev->id; + + gpio_i2c_active(mxc_dev->id); + + /*clock */ + mxc_dev->clk = clk_get(&pdev->dev, "i2c_clk"); + clk_freq = clk_get_rate(mxc_dev->clk); + mxc_dev->clkdiv = -1; + if (mxc_dev->dev->scl_freq) { + /* Calculate divider and round up any fractional part */ + int div = (clk_freq + mxc_dev->dev->scl_freq - 1) / + mxc_dev->dev->scl_freq; + for (i = 0; i2c_clk_table[i].div != 0; i++) { + if (i2c_clk_table[i].div >= div) { + mxc_dev->clkdiv = i2c_clk_table[i].reg_value; + break; + } + } + } + if (mxc_dev->clkdiv == -1) { + i--; + mxc_dev->clkdiv = 0x1F; /* Use max divider */ + } + dev_dbg(&pdev->dev, + "i2c slave speed is %d/%d = %d bps, reg val = 0x%02X\n", + clk_freq, i2c_clk_table[i].div, clk_freq / i2c_clk_table[i].div, + mxc_dev->clkdiv); + + if (i2c_slave_device_register(mxc_dev->dev) < 0) { + dev_err(&pdev->dev, "%s: i2c_slave_device_register error\n", + __func__); + goto error3; + } + + platform_set_drvdata(pdev, (void *)mxc_dev); + + /*start(mxc_dev->dev); */ + return 0; + + error3: + error2: + i2c_slave_device_free(mxc_dev->dev); + error1: + kfree(mxc_dev); + error0: + return -ENODEV; +} + +static int mxc_i2c_slave_remove(struct platform_device *pdev) +{ + struct mxc_i2c_slave_device *mxc_dev; + mxc_dev = (struct mxc_i2c_slave_device *)platform_get_drvdata(pdev); + + i2c_slave_device_unregister(mxc_dev->dev); + kfree((void *)mxc_dev); + + return 0; +} + +static int mxc_i2c_slave_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return 0; +} + +static int mxc_i2c_slave_resume(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver mxci2c_slave_driver = { + .driver = { + .name = "mxc_i2c_slave", + .owner = THIS_MODULE, + }, + .probe = mxc_i2c_slave_probe, + .remove = mxc_i2c_slave_remove, + .suspend = mxc_i2c_slave_suspend, + .resume = mxc_i2c_slave_resume, +}; + +static int __init mxc_i2c_slave_init(void) +{ + /* Register the device driver structure. */ + return platform_driver_register(&mxci2c_slave_driver); +} + +/*! + * This function is used to cleanup all resources before the driver exits. + */ +static void __exit mxc_i2c_slave_exit(void) +{ + platform_driver_unregister(&mxci2c_slave_driver); +} + +module_init(mxc_i2c_slave_init); +module_exit(mxc_i2c_slave_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC I2C Slave Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c-slave/mxc_i2c_slave.h b/drivers/i2c-slave/mxc_i2c_slave.h new file mode 100644 index 000000000000..7b8d5143588e --- /dev/null +++ b/drivers/i2c-slave/mxc_i2c_slave.h @@ -0,0 +1,44 @@ +/* + * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __MXC_I2C_SLAVE_H__ +#define __MXC_I2C_SLAVE_H__ + +#include <linux/clk.h> +#include "i2c_slave_device.h" + +#define MXC_I2C_SLAVE_NAME "MXC_I2C_SLAVE" +#define MXC_I2C_SLAVE_ADDRESS 0x55 +#define MXC_I2C_SLAVE_FREQ 1000*100 + +struct mxc_i2c_slave_device { + /*! + * The default clock divider value to be used. + */ + unsigned int clkdiv; + + /*! + * The clock source for the device. + */ + struct clk *clk; + + /*i2c id on soc */ + int id; + + int irq; + unsigned long reg_base; + bool state; /*0:stop, 1:start */ + i2c_slave_device_t *dev; +}; + +#endif diff --git a/drivers/i2c-slave/mxc_i2c_slave_reg.h b/drivers/i2c-slave/mxc_i2c_slave_reg.h new file mode 100644 index 000000000000..6450ad6e3db9 --- /dev/null +++ b/drivers/i2c-slave/mxc_i2c_slave_reg.h @@ -0,0 +1,41 @@ +/* + * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __MXC_I2C_SLAVE_REG_H__ +#define __MXC_I2C_SLAVE_REG_H__ + +/* Address offsets of the I2C registers */ +#define MXC_IADR 0x00 /* Address Register */ +#define MXC_IFDR 0x04 /* Freq div register */ +#define MXC_I2CR 0x08 /* Control regsiter */ +#define MXC_I2SR 0x0C /* Status register */ +#define MXC_I2DR 0x10 /* Data I/O register */ + +/* Bit definitions of I2CR */ +#define MXC_I2CR_IEN 0x0080 +#define MXC_I2CR_IIEN 0x0040 +#define MXC_I2CR_MSTA 0x0020 +#define MXC_I2CR_MTX 0x0010 +#define MXC_I2CR_TXAK 0x0008 +#define MXC_I2CR_RSTA 0x0004 + +/* Bit definitions of I2SR */ +#define MXC_I2SR_ICF 0x0080 +#define MXC_I2SR_IAAS 0x0040 +#define MXC_I2SR_IBB 0x0020 +#define MXC_I2SR_IAL 0x0010 +#define MXC_I2SR_SRW 0x0004 +#define MXC_I2SR_IIF 0x0002 +#define MXC_I2SR_RXAK 0x0001 + +#endif /* __MXC_I2C_REG_H__ */ diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 8206442fbabd..b92f6b35166a 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -339,6 +339,10 @@ config I2C_DESIGNWARE config I2C_GPIO tristate "GPIO-based bitbanging I2C" depends on GENERIC_GPIO + +config I2C_PARPORT + tristate "Parallel port adapter" + depends on PARPORT select I2C_ALGOBIT help This is a very simple bitbanging I2C driver utilizing the @@ -412,9 +416,34 @@ config I2C_MPC This driver can also be built as a module. If so, the module will be called i2c-mpc. +config I2C_MXC + tristate "MXC I2C support" + depends on I2C && ARCH_MXC + help + If you say yes to this option, support will be included for Freescale + MXC I2C modules. + + This driver can also be built as a module. + +config I2C_MXC_HS + tristate "MXC HIGH SPEED I2C support" + depends on I2C && ARCH_MXC + help + If you say yes to this option, support will be included for Freescale + MXC HIGH SPEED I2C modules. + + This driver can also be built as a module. + +config I2C_STMP378X + tristate "STMP378x I2C adapter" + depends on MACH_STMP378X + help + TBD + config I2C_MV64XXX tristate "Marvell mv64xxx I2C Controller" depends on (MV64X60 || PLAT_ORION) && EXPERIMENTAL + help If you say yes to this option, support will be included for the built-in I2C interface on the Marvell 64xxx line of host bridges. diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index e654263bfc01..e18e3b889f52 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -71,6 +71,9 @@ obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o obj-$(CONFIG_I2C_STUB) += i2c-stub.o obj-$(CONFIG_SCx200_ACB) += scx200_acb.o obj-$(CONFIG_SCx200_I2C) += scx200_i2c.o +obj-$(CONFIG_I2C_MXC) += mxc_i2c.o +obj-$(CONFIG_I2C_MXC_HS) += mxc_i2c_hs.o +obj-$(CONFIG_I2C_STMP378X) += i2c-stmp378x.o ifeq ($(CONFIG_I2C_DEBUG_BUS),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/i2c/busses/i2c-s6000.c b/drivers/i2c/busses/i2c-s6000.c index c91359f4965c..a19af5101d1b 100644 --- a/drivers/i2c/busses/i2c-s6000.c +++ b/drivers/i2c/busses/i2c-s6000.c @@ -276,7 +276,7 @@ static int __devinit s6i2c_probe(struct platform_device *dev) } iface->res = request_mem_region(iface->res->start, resource_size(iface->res), - dev->dev.bus_id); + dev_name(&dev->dev)); if (!iface->res) { rc = -EBUSY; goto err_out; diff --git a/drivers/i2c/busses/i2c-stmp378x.c b/drivers/i2c/busses/i2c-stmp378x.c new file mode 100644 index 000000000000..2013a1bf9dff --- /dev/null +++ b/drivers/i2c/busses/i2c-stmp378x.c @@ -0,0 +1,340 @@ +/* + * Freescale STMP378X I2C bus driver + * + * Author: Dmitrij Frasenyak <sed@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* #define DEBUG */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/completion.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/io.h> + +#include <mach/platform.h> +#include <mach/regs-i2c.h> +#include <mach/regs-apbx.h> +#include <mach/i2c.h> +#include <mach/platform.h> + +static void reset_i2c_module(void) +{ + u32 ctrl; + int count; + count = 1000; + stmp3xxx_setl(BM_I2C_CTRL0_SFTRST, REGS_I2C_BASE + HW_I2C_CTRL0); + udelay(10); /* Reseting the module can take multiple clocks.*/ + while (--count && (!(__raw_readl(REGS_I2C_BASE + HW_I2C_CTRL0) & BM_I2C_CTRL0_CLKGATE))) + udelay(1); + + if (!count) { + printk(KERN_ERR "timeout reseting the module\n"); + BUG(); + } + + /* take controller out of reset */ + stmp3xxx_clearl(BM_I2C_CTRL0_SFTRST | BM_I2C_CTRL0_CLKGATE, REGS_I2C_BASE + HW_I2C_CTRL0); + udelay(10); + stmp3xxx_setl(0x0000FF00, REGS_I2C_BASE + HW_I2C_CTRL1); /* Wil catch all error (IRQ mask) */ +} + +/* + * Low level master read/write transaction. + */ +static int stmp378x_i2c_xfer_msg(struct i2c_adapter *adap, + struct i2c_msg *msg, int stop) +{ + struct stmp378x_i2c_dev *dev = i2c_get_adapdata(adap); + int err; + + init_completion(&dev->cmd_complete); + dev->cmd_err = 0; + + dev_dbg(dev->dev, " Start XFER ===>\n"); + dev_dbg(dev->dev, "addr: 0x%04x, len: %d, flags: 0x%x, stop: %d\n", + msg->addr, msg->len, msg->flags, stop); + + if ((msg->len == 0) || (msg->len > (PAGE_SIZE - 1))) + return -EINVAL; + + if (msg->flags & I2C_M_RD) { + hw_i2c_setup_read(msg->addr , + msg->buf , + msg->len, + stop ? BM_I2C_CTRL0_POST_SEND_STOP : 0); + + hw_i2c_run(1); /* read */ + } else { + hw_i2c_setup_write(msg->addr , + msg->buf , + msg->len, + stop ? BM_I2C_CTRL0_POST_SEND_STOP : 0); + + hw_i2c_run(0); /* write */ + } + + err = wait_for_completion_interruptible_timeout( + &dev->cmd_complete, + msecs_to_jiffies(1000) + ); + + if (err < 0) { + dev_dbg(dev->dev, "controler is timed out\n"); + return -ETIMEDOUT; + } + if ((!dev->cmd_err) && (msg->flags & I2C_M_RD)) + hw_i2c_finish_read(msg->buf, msg->len); + + dev_dbg(dev->dev, "<============= Done with err=%d\n", dev->cmd_err); + + + return dev->cmd_err; +} + + +static int +stmp378x_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + int i; + int err; + + if (!msgs->len) + return -EINVAL; + + for (i = 0; i < num; i++) { + err = stmp378x_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1))); + if (err) + break; + } + + if (err == 0) + err = num; + + return err; +} + +static u32 +stmp378x_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); +} + +/* + * Debug. Don't need dma_irq for the final version + */ + +static irqreturn_t +stmp378x_i2c_dma_isr(int this_irq, void *dev_id) +{ + hw_i2c_clear_dma_interrupt(); + return IRQ_HANDLED; + +} + +#define I2C_IRQ_MASK 0x000000FF + +static irqreturn_t +stmp378x_i2c_isr(int this_irq, void *dev_id) +{ + struct stmp378x_i2c_dev *dev = dev_id; + u32 stat, ctrl; + u32 done_mask = + BM_I2C_CTRL1_DATA_ENGINE_CMPLT_IRQ | + BM_I2C_CTRL1_BUS_FREE_IRQ ; + + stat = __raw_readl(REGS_I2C_BASE + HW_I2C_CTRL1) & I2C_IRQ_MASK; + if (!stat) + return IRQ_NONE; + + if (stat & BM_I2C_CTRL1_NO_SLAVE_ACK_IRQ) { + dev->cmd_err = -EREMOTEIO; + + /* + * Stop DMA + * Clear NAK + */ + stmp3xxx_setl(BM_I2C_CTRL1_CLR_GOT_A_NAK, REGS_I2C_BASE + HW_I2C_CTRL1); + hw_i2c_reset_dma(); + reset_i2c_module(); + + complete(&dev->cmd_complete); + + goto done; + } + +/* Don't care about BM_I2C_CTRL1_OVERSIZE_XFER_TERM_IRQ */ + if (stat & ( + BM_I2C_CTRL1_EARLY_TERM_IRQ | + BM_I2C_CTRL1_MASTER_LOSS_IRQ | + BM_I2C_CTRL1_SLAVE_STOP_IRQ | + BM_I2C_CTRL1_SLAVE_IRQ + )) { + dev->cmd_err = -EIO; + complete(&dev->cmd_complete); + goto done; + } + if ((stat & done_mask) == done_mask) + complete(&dev->cmd_complete); + + +done: + stmp3xxx_clearl(stat, REGS_I2C_BASE + HW_I2C_CTRL1); + return IRQ_HANDLED; +} + +static const struct i2c_algorithm stmp378x_i2c_algo = { + .master_xfer = stmp378x_i2c_xfer, + .functionality = stmp378x_i2c_func, +}; + + +static int +stmp378x_i2c_probe(struct platform_device *pdev) +{ + struct stmp378x_i2c_dev *dev; + struct i2c_adapter *adap; + struct resource *irq; + u32 ctrl; + int err = 0; + + /* NOTE: driver uses the static register mapping */ + dev = kzalloc(sizeof(struct stmp378x_i2c_dev), GFP_KERNEL); + if (!dev) { + dev_err(&pdev->dev, "no mem \n"); + return -ENOMEM; + } + + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); /* Error */ + if (!irq) { + dev_err(&pdev->dev, "no err_irq resource\n"); + err = -ENODEV; + goto nores; + } + dev->irq_err = irq->start; + + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 1); /* DMA */ + if (!irq) { + dev_err(&pdev->dev, "no dma_irq resource\n"); + err = -ENODEV; + goto nores; + } + + dev->irq_dma = irq->start; + dev->dev = &pdev->dev; + + err = request_irq(dev->irq_err, stmp378x_i2c_isr, 0, pdev->name, dev); + if (err) { + dev_err(&pdev->dev, "Can't get IRQ\n"); + goto no_err_irq; + } + + err = request_irq(dev->irq_dma, + stmp378x_i2c_dma_isr, + 0, pdev->name, dev); + if (err) { + dev_err(&pdev->dev, "Can't get IRQ\n"); + goto no_dma_irq; + } + + err = hw_i2c_init(&pdev->dev); + if (err) { + dev_err(&pdev->dev, "HW Init failed\n"); + goto init_failed; + } + + stmp3xxx_setl(0x0000FF00, REGS_I2C_BASE + HW_I2C_CTRL1); /* Will catch all error (IRQ mask) */ + + adap = &dev->adapter; + i2c_set_adapdata(adap, dev); + adap->owner = THIS_MODULE; + adap->class = I2C_CLASS_HWMON; + strncpy(adap->name, "378x I2C adapter", sizeof(adap->name)); + adap->algo = &stmp378x_i2c_algo; + adap->dev.parent = &pdev->dev; + + adap->nr = pdev->id; + err = i2c_add_numbered_adapter(adap); + if (err) { + dev_err(&pdev->dev, "Failed to add adapter\n"); + goto no_i2c_adapter; + + } + + return 0; + +no_i2c_adapter: + hw_i2c_stop(dev->dev); +init_failed: + free_irq(dev->irq_dma, dev); +no_dma_irq: + free_irq(dev->irq_err, dev); +no_err_irq: +nores: + kfree(dev); + return err; +} + +static int +stmp378x_i2c_remove(struct platform_device *pdev) +{ + struct stmp378x_i2c_dev *dev = platform_get_drvdata(pdev); + int res; + + res = i2c_del_adapter(&dev->adapter); + if (res) + return -EBUSY; + + hw_i2c_stop(dev->dev); + + platform_set_drvdata(pdev, NULL); + + free_irq(dev->irq_err, dev); + free_irq(dev->irq_dma, dev); + + kfree(dev); + return 0; +} + +static struct platform_driver stmp378x_i2c_driver = { + .probe = stmp378x_i2c_probe, + .remove = __devexit_p(stmp378x_i2c_remove), + .driver = { + .name = "i2c_stmp", + .owner = THIS_MODULE, + }, +}; + +/* I2C may be needed to bring up other drivers */ + +static int __init stmp378x_i2c_init_driver(void) +{ + return platform_driver_register(&stmp378x_i2c_driver); +} +subsys_initcall(stmp378x_i2c_init_driver); + +static void __exit stmp378x_i2c_exit_driver(void) +{ + platform_driver_unregister(&stmp378x_i2c_driver); +} +module_exit(stmp378x_i2c_exit_driver); + +MODULE_AUTHOR("old_chap@embeddedalley.com"); +MODULE_DESCRIPTION("IIC for Freescale STMP378x"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/mxc_i2c.c b/drivers/i2c/busses/mxc_i2c.c new file mode 100644 index 000000000000..8bada57c1c82 --- /dev/null +++ b/drivers/i2c/busses/mxc_i2c.c @@ -0,0 +1,801 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_i2c.c + * + * @brief Driver for the Freescale Semiconductor MXC I2C buses. + * + * Based on i2c driver algorithm for PCF8584 adapters + * + * @ingroup MXCI2C + */ + +/* + * Include Files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <mach/hardware.h> +#include "mxc_i2c_reg.h" + +/*! + * In case the MXC device has multiple I2C modules, this structure is used to + * store information specific to each I2C module. + */ +typedef struct { + /*! + * This structure is used to identify the physical i2c bus along with + * the access algorithms necessary to access it. + */ + struct i2c_adapter adap; + + /*! + * This waitqueue is used to wait for the data transfer to complete. + */ + wait_queue_head_t wq; + + /*! + * The base address of the I2C device. + */ + void __iomem *membase; + + /*! + * The interrupt number used by the I2C device. + */ + int irq; + + /*! + * The default clock divider value to be used. + */ + unsigned int clkdiv; + + /*! + * The clock source for the device. + */ + struct clk *clk; + + /*! + * The current power state of the device + */ + bool low_power; + + /*! + * Boolean to indicate if data was transferred + */ + bool transfer_done; + + /*! + * Boolean to indicate if we received an ACK for the data transmitted + */ + bool tx_success; +} mxc_i2c_device; + +struct clk_div_table { + int reg_value; + int div; +}; + +static const struct clk_div_table i2c_clk_table[] = { + {0x20, 22}, {0x21, 24}, {0x22, 26}, {0x23, 28}, + {0, 30}, {1, 32}, {0x24, 32}, {2, 36}, + {0x25, 36}, {0x26, 40}, {3, 42}, {0x27, 44}, + {4, 48}, {0x28, 48}, {5, 52}, {0x29, 56}, + {6, 60}, {0x2A, 64}, {7, 72}, {0x2B, 72}, + {8, 80}, {0x2C, 80}, {9, 88}, {0x2D, 96}, + {0xA, 104}, {0x2E, 112}, {0xB, 128}, {0x2F, 128}, + {0xC, 144}, {0xD, 160}, {0x30, 160}, {0xE, 192}, + {0x31, 192}, {0x32, 224}, {0xF, 240}, {0x33, 256}, + {0x10, 288}, {0x11, 320}, {0x34, 320}, {0x12, 384}, + {0x35, 384}, {0x36, 448}, {0x13, 480}, {0x37, 512}, + {0x14, 576}, {0x15, 640}, {0x38, 640}, {0x16, 768}, + {0x39, 768}, {0x3A, 896}, {0x17, 960}, {0x3B, 1024}, + {0x18, 1152}, {0x19, 1280}, {0x3C, 1280}, {0x1A, 1536}, + {0x3D, 1536}, {0x3E, 1792}, {0x1B, 1920}, {0x3F, 2048}, + {0x1C, 2304}, {0x1D, 2560}, {0x1E, 3072}, {0x1F, 3840}, + {0, 0} +}; + +extern void gpio_i2c_active(int i2c_num); +extern void gpio_i2c_inactive(int i2c_num); + +/*! + * Transmit a \b STOP signal to the slave device. + * + * @param dev the mxc i2c structure used to get to the right i2c device + */ +static void mxc_i2c_stop(mxc_i2c_device * dev) +{ + unsigned int cr, sr; + int retry = 16; + + cr = readw(dev->membase + MXC_I2CR); + cr &= ~(MXC_I2CR_MSTA | MXC_I2CR_MTX); + writew(cr, dev->membase + MXC_I2CR); + + /* Wait till the Bus Busy bit is reset */ + sr = readw(dev->membase + MXC_I2SR); + while (retry-- && ((sr & MXC_I2SR_IBB))) { + udelay(3); + sr = readw(dev->membase + MXC_I2SR); + } + if (retry <= 0) + dev_err(&dev->adap.dev, "Could not set I2C Bus Busy bit" + " to zero.\n"); +} + +/*! + * Wait for the transmission of the data byte to complete. This function waits + * till we get a signal from the interrupt service routine indicating completion + * of the address cycle or we time out. + * + * @param dev the mxc i2c structure used to get to the right i2c device + * @param trans_flag transfer flag + * + * + * @return The function returns 0 on success or -1 if an ack was not received + */ + +static int mxc_i2c_wait_for_tc(mxc_i2c_device * dev, int trans_flag) +{ + int retry = 16; + + while (retry-- && !dev->transfer_done) { + wait_event_interruptible_timeout(dev->wq, + dev->transfer_done, + dev->adap.timeout); + } + dev->transfer_done = false; + + if (retry <= 0) { + /* Unable to send data */ + dev_err(&dev->adap.dev, "Data not transmitted\n"); + return -1; + } + + if (!dev->tx_success) { + /* An ACK was not received for transmitted byte */ + dev_err(&dev->adap.dev, "ACK not received \n"); + return -1; + } + + return 0; +} + +/*! + * Transmit a \b START signal to the slave device. + * + * @param dev the mxc i2c structure used to get to the right i2c device + * @param *msg pointer to a message structure that contains the slave + * address + * + * @return The function returns EBUSY on failure, 0 on success. + */ +static int mxc_i2c_start(mxc_i2c_device *dev, struct i2c_msg *msg) +{ + volatile unsigned int cr, sr; + unsigned int addr_trans; + int retry = 16; + + /* + * Set the slave address and the requested transfer mode + * in the data register + */ + addr_trans = msg->addr << 1; + if (msg->flags & I2C_M_RD) { + addr_trans |= 0x01; + } + + /* Set the Master bit */ + cr = readw(dev->membase + MXC_I2CR); + cr |= MXC_I2CR_MSTA; + writew(cr, dev->membase + MXC_I2CR); + + /* Wait till the Bus Busy bit is set */ + sr = readw(dev->membase + MXC_I2SR); + while (retry-- && (!(sr & MXC_I2SR_IBB))) { + udelay(3); + sr = readw(dev->membase + MXC_I2SR); + } + if (retry <= 0) { + dev_err(&dev->adap.dev, "Could not grab Bus ownership\n"); + return -EBUSY; + } + + /* Set the Transmit bit */ + cr = readw(dev->membase + MXC_I2CR); + cr |= MXC_I2CR_MTX; + writew(cr, dev->membase + MXC_I2CR); + + writew(addr_trans, dev->membase + MXC_I2DR); + return 0; +} + +/*! + * Transmit a \b REPEAT START to the slave device + * + * @param dev the mxc i2c structure used to get to the right i2c device + * @param *msg pointer to a message structure that contains the slave + * address + */ +static int mxc_i2c_repstart(mxc_i2c_device *dev, struct i2c_msg *msg) +{ + volatile unsigned int cr, sr; + unsigned int addr_trans; + int retry = 16; + + /* + * Set the slave address and the requested transfer mode + * in the data register + */ + addr_trans = msg->addr << 1; + if (msg->flags & I2C_M_RD) { + addr_trans |= 0x01; + } + cr = readw(dev->membase + MXC_I2CR); + cr |= MXC_I2CR_RSTA; + writew(cr, dev->membase + MXC_I2CR); + /* Wait till the Bus Busy bit is set */ + sr = readw(dev->membase + MXC_I2SR); + while (retry-- && (!(sr & MXC_I2SR_IBB))) { + udelay(3); + sr = readw(dev->membase + MXC_I2SR); + } + if (retry <= 0) { + dev_err(&dev->adap.dev, "Could not grab Bus ownership\n"); + return -EBUSY; + } + writew(addr_trans, dev->membase + MXC_I2DR); + return 0; +} + +/*! + * Read the received data. The function waits till data is available or times + * out. Generates a stop signal if this is the last message to be received. + * Sends an ack for all the bytes received except the last byte. + * + * @param dev the mxc i2c structure used to get to the right i2c device + * @param *msg pointer to a message structure that contains the slave + * address and a pointer to the receive buffer + * @param last indicates that this is the last message to be received + * @param addr_comp flag indicates that we just finished the address cycle + * + * @return The function returns the number of bytes read or -1 on time out. + */ +static int mxc_i2c_readbytes(mxc_i2c_device * dev, struct i2c_msg *msg, + int last, int addr_comp) +{ + int i; + char *buf = msg->buf; + int len = msg->len; + volatile unsigned int cr; + + cr = readw(dev->membase + MXC_I2CR); + /* + * Clear MTX to switch to receive mode. + */ + cr &= ~MXC_I2CR_MTX; + /* + * Clear the TXAK bit to gen an ack when receiving only one byte. + */ + if (len == 1) { + cr |= MXC_I2CR_TXAK; + } else { + cr &= ~MXC_I2CR_TXAK; + } + writew(cr, dev->membase + MXC_I2CR); + /* + * Dummy read only at the end of an address cycle + */ + if (addr_comp > 0) { + readw(dev->membase + MXC_I2DR); + } + + for (i = 0; i < len; i++) { + /* + * Wait for data transmission to complete + */ + if (mxc_i2c_wait_for_tc(dev, msg->flags)) { + mxc_i2c_stop(dev); + return -1; + } + /* + * Do not generate an ACK for the last byte + */ + if (i == (len - 2)) { + cr = readw(dev->membase + MXC_I2CR); + cr |= MXC_I2CR_TXAK; + writew(cr, dev->membase + MXC_I2CR); + } else if (i == (len - 1)) { + if (last) { + mxc_i2c_stop(dev); + } + } + /* Read the data */ + *buf++ = readw(dev->membase + MXC_I2DR); + } + + return i; +} + +/*! + * Write the data to the data register. Generates a stop signal if this is + * the last message to be sent or if no ack was received for the data sent. + * + * @param dev the mxc i2c structure used to get to the right i2c device + * @param *msg pointer to a message structure that contains the slave + * address and data to be sent + * @param last indicates that this is the last message to be received + * + * @return The function returns the number of bytes written or -1 on time out + * or if no ack was received for the data that was sent. + */ +static int mxc_i2c_writebytes(mxc_i2c_device * dev, struct i2c_msg *msg, + int last) +{ + int i; + char *buf = msg->buf; + int len = msg->len; + volatile unsigned int cr; + + cr = readw(dev->membase + MXC_I2CR); + /* Set MTX to switch to transmit mode */ + cr |= MXC_I2CR_MTX; + writew(cr, dev->membase + MXC_I2CR); + + for (i = 0; i < len; i++) { + /* + * Write the data + */ + writew(*buf++, dev->membase + MXC_I2DR); + if (mxc_i2c_wait_for_tc(dev, msg->flags)) { + mxc_i2c_stop(dev); + return -1; + } + } + if (last > 0) { + mxc_i2c_stop(dev); + } + + return i; +} + +/*! + * Function enables the I2C module and initializes the registers. + * + * @param dev the mxc i2c structure used to get to the right i2c device + * @param trans_flag transfer flag + */ +static void mxc_i2c_module_en(mxc_i2c_device * dev, int trans_flag) +{ + clk_enable(dev->clk); + /* Set the frequency divider */ + writew(dev->clkdiv, dev->membase + MXC_IFDR); + /* Clear the status register */ + writew(0x0, dev->membase + MXC_I2SR); + /* Enable I2C and its interrupts */ + writew(MXC_I2CR_IEN, dev->membase + MXC_I2CR); + writew(MXC_I2CR_IEN | MXC_I2CR_IIEN, dev->membase + MXC_I2CR); +} + +/*! + * Disables the I2C module. + * + * @param dev the mxc i2c structure used to get to the right i2c device + */ +static void mxc_i2c_module_dis(mxc_i2c_device * dev) +{ + writew(0x0, dev->membase + MXC_I2CR); + clk_disable(dev->clk); +} + +/*! + * The function is registered in the adapter structure. It is called when an MXC + * driver wishes to transfer data to a device connected to the I2C device. + * + * @param adap adapter structure for the MXC i2c device + * @param msgs[] array of messages to be transferred to the device + * @param num number of messages to be transferred to the device + * + * @return The function returns the number of messages transferred, + * \b -EREMOTEIO on I2C failure and a 0 if the num argument is + * less than 0. + */ +static int mxc_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], + int num) +{ + mxc_i2c_device *dev = (mxc_i2c_device *) (i2c_get_adapdata(adap)); + int i, ret = 0, addr_comp = 0; + volatile unsigned int sr; + int retry = 5; + + if (dev->low_power) { + dev_err(&dev->adap.dev, "I2C Device in low power mode\n"); + return -EREMOTEIO; + } + + if (num < 1) { + return 0; + } + + mxc_i2c_module_en(dev, msgs[0].flags); + sr = readw(dev->membase + MXC_I2SR); + /* + * Check bus state + */ + + while ((sr & MXC_I2SR_IBB) && retry--) { + udelay(5); + sr = readw(dev->membase + MXC_I2SR); + } + + if ((sr & MXC_I2SR_IBB) && retry < 0) { + mxc_i2c_module_dis(dev); + dev_err(&dev->adap.dev, "Bus busy\n"); + return -EREMOTEIO; + } + + //gpio_i2c_active(dev->adap.id); + dev->transfer_done = false; + dev->tx_success = false; + for (i = 0; i < num && ret >= 0; i++) { + addr_comp = 0; + /* + * Send the slave address and transfer direction in the + * address cycle + */ + if (i == 0) { + /* + * Send a start or repeat start signal + */ + if (mxc_i2c_start(dev, &msgs[0])) + return -EREMOTEIO; + /* Wait for the address cycle to complete */ + if (mxc_i2c_wait_for_tc(dev, msgs[0].flags)) { + mxc_i2c_stop(dev); + //gpio_i2c_inactive(dev->adap.id); + mxc_i2c_module_dis(dev); + return -EREMOTEIO; + } + addr_comp = 1; + } else { + /* + * Generate repeat start only if required i.e the address + * changed or the transfer direction changed + */ + if ((msgs[i].addr != msgs[i - 1].addr) || + ((msgs[i].flags & I2C_M_RD) != + (msgs[i - 1].flags & I2C_M_RD))) { + mxc_i2c_repstart(dev, &msgs[i]); + /* Wait for the address cycle to complete */ + if (mxc_i2c_wait_for_tc(dev, msgs[i].flags)) { + mxc_i2c_stop(dev); + //gpio_i2c_inactive(dev->adap.id); + mxc_i2c_module_dis(dev); + return -EREMOTEIO; + } + addr_comp = 1; + } + } + + /* Transfer the data */ + if (msgs[i].flags & I2C_M_RD) { + /* Read the data */ + ret = mxc_i2c_readbytes(dev, &msgs[i], (i + 1 == num), + addr_comp); + if (ret < 0) { + dev_err(&dev->adap.dev, "mxc_i2c_readbytes:" + " fail.\n"); + break; + } + } else { + /* Write the data */ + ret = mxc_i2c_writebytes(dev, &msgs[i], (i + 1 == num)); + if (ret < 0) { + dev_err(&dev->adap.dev, "mxc_i2c_writebytes:" + " fail.\n"); + break; + } + } + } + + //gpio_i2c_inactive(dev->adap.id); + mxc_i2c_module_dis(dev); + /* + * Decrease by 1 as we do not want Start message to be included in + * the count + */ + return (i < 0 ? ret : i); +} + +/*! + * Returns the i2c functionality supported by this driver. + * + * @param adap adapter structure for this i2c device + * + * @return Returns the functionality that is supported. + */ +static u32 mxc_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +/*! + * Stores the pointers for the i2c algorithm functions. The algorithm functions + * is used by the i2c bus driver to talk to the i2c bus + */ +static struct i2c_algorithm mxc_i2c_algorithm = { + .master_xfer = mxc_i2c_xfer, + .functionality = mxc_i2c_func +}; + +/*! + * Interrupt Service Routine. It signals to the process about the data transfer + * completion. Also sets a flag if bus arbitration is lost. + * @param irq the interrupt number + * @param dev_id driver private data + * + * @return The function returns \b IRQ_HANDLED. + */ +static irqreturn_t mxc_i2c_handler(int irq, void *dev_id) +{ + mxc_i2c_device *dev = dev_id; + volatile unsigned int sr, cr; + + sr = readw(dev->membase + MXC_I2SR); + cr = readw(dev->membase + MXC_I2CR); + + /* + * Clear the interrupt bit + */ + writew(0x0, dev->membase + MXC_I2SR); + + if (sr & MXC_I2SR_IAL) { + dev_err(&dev->adap.dev, "Bus Arbitration lost\n"); + } else { + /* Interrupt due byte transfer completion */ + dev->tx_success = true; + /* Check if RXAK is received in Transmit mode */ + if ((cr & MXC_I2CR_MTX) && (sr & MXC_I2SR_RXAK)) { + dev->tx_success = false; + } + dev->transfer_done = true; + wake_up_interruptible(&dev->wq); + } + + return IRQ_HANDLED; +} + +/*! + * This function is called to put the I2C adapter in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which I2C + * to suspend + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure. + */ +static int mxci2c_suspend(struct platform_device *pdev, pm_message_t state) +{ + mxc_i2c_device *mxcdev = platform_get_drvdata(pdev); + volatile unsigned int sr = 0; + + if (mxcdev == NULL) { + return -1; + } + + /* Prevent further calls to be processed */ + mxcdev->low_power = true; + /* Wait till we finish the current transfer */ + sr = readw(mxcdev->membase + MXC_I2SR); + while (sr & MXC_I2SR_IBB) { + msleep(10); + sr = readw(mxcdev->membase + MXC_I2SR); + } + gpio_i2c_inactive(mxcdev->adap.id); + + return 0; +} + +/*! + * This function is called to bring the I2C adapter back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which I2C + * to resume + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxci2c_resume(struct platform_device *pdev) +{ + mxc_i2c_device *mxcdev = platform_get_drvdata(pdev); + + if (mxcdev == NULL) + return -1; + + mxcdev->low_power = false; + gpio_i2c_active(mxcdev->adap.id); + + return 0; +} + +/*! + * This function is called during the driver binding process. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions + * + * @return The function always returns 0. + */ +static int mxci2c_probe(struct platform_device *pdev) +{ + mxc_i2c_device *mxc_i2c; + struct mxc_i2c_platform_data *i2c_plat_data = pdev->dev.platform_data; + struct resource *res; + int id = pdev->id; + u32 clk_freq; + int ret = 0; + int i; + + mxc_i2c = kzalloc(sizeof(mxc_i2c_device), GFP_KERNEL); + if (!mxc_i2c) { + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + ret = -ENODEV; + goto err1; + } + mxc_i2c->membase = IO_ADDRESS(res->start); + + /* + * Request the I2C interrupt + */ + mxc_i2c->irq = platform_get_irq(pdev, 0); + if (mxc_i2c->irq < 0) { + ret = mxc_i2c->irq; + goto err1; + } + + ret = request_irq(mxc_i2c->irq, mxc_i2c_handler, + 0, pdev->name, mxc_i2c); + if (ret < 0) { + goto err1; + } + + init_waitqueue_head(&mxc_i2c->wq); + + mxc_i2c->low_power = false; + + gpio_i2c_active(id); + + mxc_i2c->clk = clk_get(&pdev->dev, "i2c_clk"); + clk_freq = clk_get_rate(mxc_i2c->clk); + mxc_i2c->clkdiv = -1; + if (i2c_plat_data->i2c_clk) { + /* Calculate divider and round up any fractional part */ + int div = (clk_freq + i2c_plat_data->i2c_clk - 1) / + i2c_plat_data->i2c_clk; + for (i = 0; i2c_clk_table[i].div != 0; i++) { + if (i2c_clk_table[i].div >= div) { + mxc_i2c->clkdiv = i2c_clk_table[i].reg_value; + break; + } + } + } + if (mxc_i2c->clkdiv == -1) { + i--; + mxc_i2c->clkdiv = 0x1F; /* Use max divider */ + } + dev_dbg(&pdev->dev, "i2c speed is %d/%d = %d bps, reg val = 0x%02X\n", + clk_freq, i2c_clk_table[i].div, + clk_freq / i2c_clk_table[i].div, mxc_i2c->clkdiv); + + /* + * Set the adapter information + */ + strlcpy(mxc_i2c->adap.name, pdev->name, 48); + mxc_i2c->adap.id = mxc_i2c->adap.nr = id; + mxc_i2c->adap.algo = &mxc_i2c_algorithm; + mxc_i2c->adap.timeout = 1; + platform_set_drvdata(pdev, mxc_i2c); + i2c_set_adapdata(&mxc_i2c->adap, mxc_i2c); + if ((ret = i2c_add_numbered_adapter(&mxc_i2c->adap)) < 0) { + goto err2; + } + + printk(KERN_INFO "MXC I2C driver\n"); + return 0; + + err2: + free_irq(mxc_i2c->irq, mxc_i2c); + gpio_i2c_inactive(id); + err1: + dev_err(&pdev->dev, "failed to probe i2c adapter\n"); + kfree(mxc_i2c); + return ret; +} + +/*! + * Dissociates the driver from the I2C device. + * + * @param pdev the device structure used to give information on which I2C + * to remove + * + * @return The function always returns 0. + */ +static int mxci2c_remove(struct platform_device *pdev) +{ + mxc_i2c_device *mxc_i2c = platform_get_drvdata(pdev); + int id = pdev->id; + + free_irq(mxc_i2c->irq, mxc_i2c); + i2c_del_adapter(&mxc_i2c->adap); + gpio_i2c_inactive(id); + clk_put(mxc_i2c->clk); + platform_set_drvdata(pdev, NULL); + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxci2c_driver = { + .driver = { + .name = "mxc_i2c", + .owner = THIS_MODULE, + }, + .probe = mxci2c_probe, + .remove = mxci2c_remove, + .suspend_late = mxci2c_suspend, + .resume_early = mxci2c_resume, +}; + +/*! + * Function requests the interrupts and registers the i2c adapter structures. + * + * @return The function returns 0 on success and a non-zero value on failure. + */ +static int __init mxc_i2c_init(void) +{ + /* Register the device driver structure. */ + return platform_driver_register(&mxci2c_driver); +} + +/*! + * This function is used to cleanup all resources before the driver exits. + */ +static void __exit mxc_i2c_exit(void) +{ + platform_driver_unregister(&mxci2c_driver); +} + +subsys_initcall(mxc_i2c_init); +module_exit(mxc_i2c_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC I2C driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/mxc_i2c_hs.c b/drivers/i2c/busses/mxc_i2c_hs.c new file mode 100644 index 000000000000..d31b5759ecda --- /dev/null +++ b/drivers/i2c/busses/mxc_i2c_hs.c @@ -0,0 +1,550 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <mach/hardware.h> +#include "mxc_i2c_hs_reg.h" + +typedef struct { + struct device *dev; + + void __iomem *reg_base_virt; + unsigned long reg_base_phy; + int irq; + unsigned int speed; + struct clk *ipg_clk; + struct clk *serial_clk; + bool low_power; + + struct i2c_msg *msg; + int index; +} mxc_i2c_hs; + +struct clk_div_table { + int reg_value; + int div; +}; + +static const struct clk_div_table i2c_clk_table[] = { + {0x0, 16}, {0x1, 18}, {0x2, 20}, {0x3, 22}, + {0x20, 24}, {0x21, 26}, {0x22, 28}, {0x23, 30}, + {0x4, 32}, {0x5, 36}, {0x6, 40}, {0x7, 44}, + {0x24, 48}, {0x25, 52}, {0x26, 56}, {0x27, 60}, + {0x8, 64}, {0x9, 72}, {0xa, 80}, {0xb, 88}, + {0x28, 96}, {0x29, 104}, {0x2a, 112}, {0x2b, 120}, + {0xc, 128}, {0xd, 144}, {0xe, 160}, {0xf, 176}, + {0x2c, 192}, {0x2d, 208}, {0x2e, 224}, {0x2f, 240}, + {0x10, 256}, {0x11, 288}, {0x12, 320}, {0x13, 352}, + {0x30, 384}, {0x31, 416}, {0x32, 448}, {0x33, 480}, + {0x14, 512}, {0x15, 576}, {0x16, 640}, {0x17, 704}, + {0x34, 768}, {0x35, 832}, {0x36, 896}, {0x37, 960}, + {0x18, 1024}, {0x19, 1152}, {0x1a, 1280}, {0x1b, 1408}, + {0x38, 1536}, {0x39, 1664}, {0x3a, 1792}, {0x3b, 1920}, + {0x1c, 2048}, {0x1d, 2304}, {0x1e, 2560}, {0x1f, 2816}, + {0x3c, 3072}, {0x3d, 3328}, {0x3E, 3584}, {0x3F, 3840}, + {-1, -1} +}; + +static struct i2c_adapter *adap; + +extern void gpio_i2c_hs_inactive(void); +extern void gpio_i2c_hs_active(void); + +static u16 reg_read(mxc_i2c_hs *i2c_hs, u32 reg_offset) +{ + return __raw_readw(i2c_hs->reg_base_virt + reg_offset); +} + +static void reg_write(mxc_i2c_hs *i2c_hs, u32 reg_offset, u16 data) +{ + __raw_writew(data, i2c_hs->reg_base_virt + reg_offset); +} + +static void reg_set_mask(mxc_i2c_hs *i2c_hs, u32 reg_offset, u16 mask) +{ + u16 value; + + value = reg_read(i2c_hs, reg_offset); + value |= mask; + reg_write(i2c_hs, reg_offset, value); +} +static void reg_clear_mask(mxc_i2c_hs *i2c_hs, u32 reg_offset, u16 mask) +{ + u16 value; + + value = reg_read(i2c_hs, reg_offset); + value &= ~mask; + reg_write(i2c_hs, reg_offset, value); +} + +static void mxci2c_hs_set_div(mxc_i2c_hs *i2c_hs) +{ + unsigned long clk_freq; + int i; + int div = -1;; + + clk_freq = clk_get_rate(i2c_hs->serial_clk); + if (i2c_hs->speed) { + div = (clk_freq + i2c_hs->speed - 1) / i2c_hs->speed; + for (i = 0; i2c_clk_table[i].div >= 0; i++) { + if (i2c_clk_table[i].div >= div) { + div = i2c_clk_table[i].reg_value; + reg_write(i2c_hs, HIFSFDR, div); + break; + } + } + } +} + +static int mxci2c_hs_enable(mxc_i2c_hs *i2c_hs) +{ + gpio_i2c_hs_active(); + clk_enable(i2c_hs->ipg_clk); + clk_enable(i2c_hs->serial_clk); + mxci2c_hs_set_div(i2c_hs); + reg_write(i2c_hs, HICR, reg_read(i2c_hs, HICR) | HICR_HIEN); + + return 0; +} + +static int mxci2c_hs_disable(mxc_i2c_hs *i2c_hs) +{ + reg_write(i2c_hs, HICR, reg_read(i2c_hs, HICR) & (~HICR_HIEN)); + clk_disable(i2c_hs->ipg_clk); + clk_disable(i2c_hs->serial_clk); + + return 0; +} + +static int mxci2c_hs_bus_busy(mxc_i2c_hs *i2c_hs) +{ + u16 value; + int retry = 1000; + + while (retry--) { + value = reg_read(i2c_hs, HISR); + if (value & HISR_HIBB) { + udelay(1); + } else { + break; + } + } + + if (retry <= 0) { + dev_dbg(NULL, "%s: Bus Busy!\n", __func__); + return 1; + } else { + return 0; + } +} + +static int mxci2c_hs_start(mxc_i2c_hs *i2c_hs, int repeat_start, u16 address) +{ + u16 mask; + int ret = 0; + + mxci2c_hs_bus_busy(i2c_hs); + + /*7 bit address */ + reg_clear_mask(i2c_hs, HICR, HICR_ADDR_MODE); + + /*send start */ + if (repeat_start) + mask = HICR_RSTA; + else + mask = HICR_MSTA; + reg_set_mask(i2c_hs, HICR, mask); + + return ret; +} + +static int mxci2c_hs_stop(mxc_i2c_hs *i2c_hs) +{ + reg_clear_mask(i2c_hs, HICR, HICR_MSTA); + reg_clear_mask(i2c_hs, HICR, HICR_HIIEN); + + return 0; +} + +static int mxci2c_wait_writefifo(mxc_i2c_hs *i2c_hs) +{ + int i, num, left; + int retry, ret = 0; + + retry = 10000; + while (retry--) { + udelay(10); + if (reg_read(i2c_hs, HISR) & (HISR_TDE | HISR_TDC_ZERO)) { + if (i2c_hs->index < i2c_hs->msg->len) { + left = i2c_hs->msg->len - i2c_hs->index; + num = + (left > + HITFR_MAX_COUNT) ? HITFR_MAX_COUNT : left; + for (i = 0; i < num; i++) { + reg_write(i2c_hs, HITDR, + i2c_hs->msg->buf[i2c_hs-> + index + i]); + } + i2c_hs->index += num; + } else { + if (reg_read(i2c_hs, HISR) & HISR_TDC_ZERO) { + msleep(1); + break; + } + } + } + } + + if (retry <= 0) { + printk(KERN_ERR "%s:wait error\n", __func__); + ret = -1; + } + + return ret; +} + +static int mxci2c_wait_readfifo(mxc_i2c_hs *i2c_hs) +{ + int i, num, left; + int retry, ret = 0; + u16 value; + + retry = 10000; + while (retry--) { + udelay(10); + value = reg_read(i2c_hs, HISR); + if (value & (HISR_RDF | HISR_RDC_ZERO)) { + if (i2c_hs->index < i2c_hs->msg->len) { + left = i2c_hs->msg->len - i2c_hs->index; + num = + (left > + HITFR_MAX_COUNT) ? HITFR_MAX_COUNT : left; + for (i = 0; i < num; i++) { + i2c_hs->msg->buf[i2c_hs->index + i] = + reg_read(i2c_hs, HIRDR); + } + i2c_hs->index += num; + } else { + if (value & HISR_RDC_ZERO) { + break; + } + } + } + } + + if (retry <= 0) { + printk(KERN_ERR "%s:wait error\n", __func__); + ret = -1; + } + + return ret; +} + +static int mxci2c_hs_read(mxc_i2c_hs *i2c_hs, int repeat_start, + struct i2c_msg *msg) +{ + int ret; + + if (msg->len > HIRDCR_MAX_COUNT) { + printk(KERN_ERR "%s: error: msg too long, max longth 256\n", + __func__); + return -1; + } + + ret = 0; + i2c_hs->msg = msg; + i2c_hs->index = 0; + + /*set address */ + reg_write(i2c_hs, HIMADR, HIMADR_LSB_ADR(msg->addr)); + + /*receive mode */ + reg_clear_mask(i2c_hs, HICR, HICR_MTX); + + reg_clear_mask(i2c_hs, HICR, HICR_HIIEN); + + /*FIFO*/ reg_set_mask(i2c_hs, HIRFR, HIRFR_RFEN | HIRFR_RFWM(7)); + reg_set_mask(i2c_hs, HIRFR, HIRFR_RFLSH); + + /*TDCR*/ + reg_write(i2c_hs, HIRDCR, HIRDCR_RDC_EN | HIRDCR_RDC(msg->len)); + + mxci2c_hs_start(i2c_hs, repeat_start, msg->addr); + + ret = mxci2c_wait_readfifo(i2c_hs); + + if (ret < 0) + return ret; + else + return msg->len; +} + +static int mxci2c_hs_write(mxc_i2c_hs *i2c_hs, int repeat_start, + struct i2c_msg *msg) +{ + int ret, i; + + if (msg->len > HITDCR_MAX_COUNT) { + printk(KERN_ERR "%s: error: msg too long, max longth 256\n", + __func__); + return -1; + } + + ret = 0; + i2c_hs->msg = msg; + i2c_hs->index = 0; + + /*set address */ + reg_write(i2c_hs, HIMADR, HIMADR_LSB_ADR(msg->addr)); + + /*transmit mode */ + reg_set_mask(i2c_hs, HICR, HICR_MTX); + + reg_clear_mask(i2c_hs, HICR, HICR_HIIEN); + + /* TDCR */ + reg_write(i2c_hs, HITDCR, HITDCR_TDC_EN | HITDCR_TDC(msg->len)); + + /* FIFO */ + reg_set_mask(i2c_hs, HITFR, HITFR_TFEN); + reg_set_mask(i2c_hs, HITFR, HITFR_TFLSH); + + if (msg->len > HITFR_MAX_COUNT) + i2c_hs->index = HITFR_MAX_COUNT; + else { + i2c_hs->index = msg->len; + } + + for (i = 0; i < i2c_hs->index; i++) { + reg_write(i2c_hs, HITDR, msg->buf[i]); + } + + mxci2c_hs_start(i2c_hs, repeat_start, msg->addr); + + ret = mxci2c_wait_writefifo(i2c_hs); + + if (ret < 0) + return ret; + else + return msg->len; +} + +static int mxci2c_hs_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], + int num) +{ + int i; + int ret = -EIO; + + mxc_i2c_hs *i2c_hs = (mxc_i2c_hs *) (i2c_get_adapdata(adap)); + + if (i2c_hs->low_power) { + dev_err(&adap->dev, "I2C Device in low power mode\n"); + return -EREMOTEIO; + } + + if (num < 1) { + return 0; + } + + mxci2c_hs_enable(i2c_hs); + + for (i = 0; i < num; i++) { + if (msgs[i].flags & I2C_M_RD) { + ret = mxci2c_hs_read(i2c_hs, 0, &msgs[i]); + if (ret < 0) + break; + } else { + ret = mxci2c_hs_write(i2c_hs, 0, &msgs[i]); + if (ret < 0) + break; + } + mxci2c_hs_stop(i2c_hs); + } + mxci2c_hs_stop(i2c_hs); + + mxci2c_hs_disable(i2c_hs); + + if (ret < 0) + return ret; + + return i; +} + +static u32 mxci2c_hs_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +/*! + * Stores the pointers for the i2c algorithm functions. The algorithm functions + * is used by the i2c bus driver to talk to the i2c bus + */ +static struct i2c_algorithm mxci2c_hs_algorithm = { + .master_xfer = mxci2c_hs_xfer, + .functionality = mxci2c_hs_func +}; + +static int mxci2c_hs_probe(struct platform_device *pdev) +{ + mxc_i2c_hs *i2c_hs; + struct mxc_i2c_platform_data *i2c_plat_data = pdev->dev.platform_data; + struct resource *res; + int id = pdev->id; + int ret = 0; + + i2c_hs = kzalloc(sizeof(mxc_i2c_hs), GFP_KERNEL); + if (!i2c_hs) { + return -ENOMEM; + } + + i2c_hs->dev = &pdev->dev; + + i2c_hs->speed = i2c_plat_data->i2c_clk; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + ret = -ENODEV; + goto err1; + } + i2c_hs->reg_base_virt = IO_ADDRESS(res->start); + i2c_hs->reg_base_phy = res->start; + + i2c_hs->ipg_clk = clk_get(&pdev->dev, "hsi2c_clk"); + i2c_hs->serial_clk = clk_get(&pdev->dev, "hsi2c_serial_clk"); + + /* + * Request the I2C interrupt + */ + i2c_hs->irq = platform_get_irq(pdev, 0); + if (i2c_hs->irq < 0) { + ret = i2c_hs->irq; + goto err1; + } + + i2c_hs->low_power = false; + + /* + * Set the adapter information + */ + adap = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL); + if (!adap) { + ret = -ENODEV; + goto err1; + } + strlcpy(adap->name, pdev->name, 48); + adap->id = adap->nr = id; + adap->algo = &mxci2c_hs_algorithm; + adap->timeout = 1; + platform_set_drvdata(pdev, i2c_hs); + i2c_set_adapdata(adap, i2c_hs); + ret = i2c_add_numbered_adapter(adap); + if (ret < 0) { + goto err2; + } + + printk(KERN_INFO "MXC HS I2C driver\n"); + return 0; + + err2: + kfree(adap); + err1: + dev_err(&pdev->dev, "failed to probe high speed i2c adapter\n"); + kfree(i2c_hs); + return ret; +} + +static int mxci2c_hs_suspend(struct platform_device *pdev, pm_message_t state) +{ + mxc_i2c_hs *i2c_hs = platform_get_drvdata(pdev); + + if (i2c_hs == NULL) { + return -1; + } + + /* Prevent further calls to be processed */ + i2c_hs->low_power = true; + + gpio_i2c_hs_inactive(); + + return 0; +} + +static int mxci2c_hs_resume(struct platform_device *pdev) +{ + mxc_i2c_hs *i2c_hs = platform_get_drvdata(pdev); + + if (i2c_hs == NULL) + return -1; + + i2c_hs->low_power = false; + gpio_i2c_hs_active(); + + return 0; +} + +static int mxci2c_hs_remove(struct platform_device *pdev) +{ + mxc_i2c_hs *i2c_hs = platform_get_drvdata(pdev); + + i2c_del_adapter(adap); + gpio_i2c_hs_inactive(); + platform_set_drvdata(pdev, NULL); + kfree(i2c_hs); + return 0; +} + +static struct platform_driver mxci2c_hs_driver = { + .driver = { + .name = "mxc_i2c_hs", + .owner = THIS_MODULE, + }, + .probe = mxci2c_hs_probe, + .remove = mxci2c_hs_remove, + .suspend = mxci2c_hs_suspend, + .resume = mxci2c_hs_resume, +}; + +/*! + * Function requests the interrupts and registers the i2c adapter structures. + * + * @return The function returns 0 on success and a non-zero value on failure. + */ +static int __init mxci2c_hs_init(void) +{ + /* Register the device driver structure. */ + return platform_driver_register(&mxci2c_hs_driver); +} + +/*! + * This function is used to cleanup all resources before the driver exits. + */ +static void __exit mxci2c_hs_exit(void) +{ + platform_driver_unregister(&mxci2c_hs_driver); +} + +subsys_initcall(mxci2c_hs_init); +module_exit(mxci2c_hs_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC HIGH SPEED I2C driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/mxc_i2c_hs_reg.h b/drivers/i2c/busses/mxc_i2c_hs_reg.h new file mode 100644 index 000000000000..fe6bb9a8f1ff --- /dev/null +++ b/drivers/i2c/busses/mxc_i2c_hs_reg.h @@ -0,0 +1,97 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MXC_I2C_HS_REG_H__ +#define __MXC_I2C_HS_REG_H__ + +#define HISADR 0x00 + +#define HIMADR 0x04 +#define HIMADR_LSB_ADR(x) ((x) << 1) +#define HIMADR_MSB_ADR(x) (((x) & 0x7) << 8) + +#define HICR 0x08 +#define HICR_HIEN 0x1 +#define HICR_DMA_EN_RX 0x2 +#define HICR_DMA_EN_TR 0x4 +#define HICR_RSTA 0x8 +#define HICR_TXAK 0x10 +#define HICR_MTX 0x20 +#define HICR_MSTA 0x40 +#define HICR_HIIEN 0x80 +#define HICR_ADDR_MODE 0x100 +#define HICR_MST_CODE(x) (((x)&0x7) << 9) +#define HICR_HSM_EN 0x1000 +#define HICR_SAMC(x) (((x)&0x3) << 13) +#define SAMC_7_10 0 +#define SMAC_7 1 +#define SMAC_10 2 + +#define HISR 0x0c +#define HISR_RDF 0x1 +#define HISR_TDE 0x2 +#define HISR_HIAAS 0x4 +#define HISR_HIAL 0x8 +#define HISR_BTD 0x10 +#define HISR_RDC_ZERO 0x20 +#define HISR_TDC_ZERO 0x40 +#define HISR_RXAK 0x80 +#define HISR_HIBB 0x100 +#define HISR_SRW 0x200 +#define HISR_SADDR_MODE 0x400 +#define HISR_SHS_MODE 0x800 + +#define HIIMR 0x10 +#define HIIMR_RDF 0x1 +#define HIIMR_TDE 0x2 +#define HIIMR_AAS 0x4 +#define HIIMR_AL 0x8 +#define HIIMR_BTD 0x10 +#define HIIMR_RDC 0x20 +#define HIIMR_TDC 0x40 +#define HIIMR_RXAK 0x80 + +#define HITDR 0x14 + +#define HIRDR 0x18 + +#define HIFSFDR 0x1c + +#define HIHSFDR 0x20 + +#define HITFR 0x24 +#define HITFR_TFEN 0x1 +#define HITFR_TFLSH 0x2 +#define HITFR_TFWM(x) (((x) & 0x7) << 2) +#define HITFR_TFC(x) (((x) >> 8) & 0xF) +#define HITFR_MAX_COUNT 8 + +#define HIRFR 0x28 +#define HIRFR_RFEN 0x1 +#define HIRFR_RFLSH 0x2 +#define HIRFR_RFWM(x) (((x) & 0x7) << 2) +#define HIRFR_RFC(x) (((x) >> 8) & 0xF) +#define HIRFR_MAX_COUNT 8 + +#define HITDCR 0x2c +#define HITDCR_TDC(x) ((x) & 0xFF) +#define HITDCR_TDC_EN 0x100 +#define HITDCR_TDC_RSTA 0x200 +#define HITDCR_MAX_COUNT 0xFF + +#define HIRDCR 0x30 +#define HIRDCR_RDC(x) ((x) & 0xFF) +#define HIRDCR_RDC_EN 0x100 +#define HIRDCR_RDC_RSTA 0x200 +#define HIRDCR_MAX_COUNT 0xFF + +#endif diff --git a/drivers/i2c/busses/mxc_i2c_reg.h b/drivers/i2c/busses/mxc_i2c_reg.h new file mode 100644 index 000000000000..4fc76c8aa811 --- /dev/null +++ b/drivers/i2c/busses/mxc_i2c_reg.h @@ -0,0 +1,40 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MXC_I2C_REG_H__ +#define __MXC_I2C_REG_H__ + +/* Address offsets of the I2C registers */ +#define MXC_IADR 0x00 /* Address Register */ +#define MXC_IFDR 0x04 /* Freq div register */ +#define MXC_I2CR 0x08 /* Control regsiter */ +#define MXC_I2SR 0x0C /* Status register */ +#define MXC_I2DR 0x10 /* Data I/O register */ + +/* Bit definitions of I2CR */ +#define MXC_I2CR_IEN 0x0080 +#define MXC_I2CR_IIEN 0x0040 +#define MXC_I2CR_MSTA 0x0020 +#define MXC_I2CR_MTX 0x0010 +#define MXC_I2CR_TXAK 0x0008 +#define MXC_I2CR_RSTA 0x0004 + +/* Bit definitions of I2SR */ +#define MXC_I2SR_ICF 0x0080 +#define MXC_I2SR_IAAS 0x0040 +#define MXC_I2SR_IBB 0x0020 +#define MXC_I2SR_IAL 0x0010 +#define MXC_I2SR_SRW 0x0004 +#define MXC_I2SR_IIF 0x0002 +#define MXC_I2SR_RXAK 0x0001 + +#endif /* __MXC_I2C_REG_H__ */ diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index a6b989a9dc07..e9cc94865465 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -310,6 +310,13 @@ config KEYBOARD_SUNKBD To compile this driver as a module, choose M here: the module will be called sunkbd. +config KEYBOARD_MPR084 + tristate "Freescale MPR084 Touch Keypad Driver" + depends on ARCH_MX37 + help + This is the Keypad driver for the Freescale Proximity Capacitive + Touch Sensor controller chip. + config KEYBOARD_SH_KEYSC tristate "SuperH KEYSC keypad support" depends on SUPERH @@ -317,6 +324,7 @@ config KEYBOARD_SH_KEYSC Say Y here if you want to use a keypad attached to the KEYSC block on SuperH processors such as sh7722 and sh7343. + To compile this driver as a module, choose M here: the module will be called sh_keysc. @@ -361,4 +369,24 @@ config KEYBOARD_XTKBD To compile this driver as a module, choose M here: the module will be called xtkbd. +config KEYBOARD_MXC + tristate "MXC Keypad Driver" + depends on ARCH_MXC + help + This is the Keypad driver for the Freescale MXC application + processors. + +config KEYBOARD_STMP3XXX + tristate "STMP3xxx keyboard" + depends on ARCH_STMP3XXX + help + -to be written- + + +config KEYBOARD_MC9S08DZ60 + tristate "mc9s08dz60 keyboard" + depends on MXC_PMIC_MC9S08DZ60 + help + -to be written- + endif diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index b5b5eae9724f..54ae2d46dfc9 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -31,3 +31,7 @@ obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o obj-$(CONFIG_KEYBOARD_TOSA) += tosakbd.o obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o +obj-$(CONFIG_KEYBOARD_MXC) += mxc_keyb.o +obj-$(CONFIG_KEYBOARD_MPR084) += mpr084.o +obj-$(CONFIG_KEYBOARD_STMP3XXX) += stmp3xxx-kbd.o +obj-$(CONFIG_KEYBOARD_MC9S08DZ60) += mc9s08dz60_keyb.o diff --git a/drivers/input/keyboard/mc9s08dz60_keyb.c b/drivers/input/keyboard/mc9s08dz60_keyb.c new file mode 100644 index 000000000000..8ec9f646c7c7 --- /dev/null +++ b/drivers/input/keyboard/mc9s08dz60_keyb.c @@ -0,0 +1,248 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9s08dz60keyb.c + * + * @brief Driver for the Freescale Semiconductor MXC keypad port. + * + * The keypad driver is designed as a standard Input driver which interacts + * with low level keypad port hardware. Upon opening, the Keypad driver + * initializes the keypad port. When the keypad interrupt happens the driver + * calles keypad polling timer and scans the keypad matrix for key + * press/release. If all key press/release happened it comes out of timer and + * waits for key press interrupt. The scancode for key press and release events + * are passed to Input subsytem. + * + * @ingroup keypad + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <mach/hardware.h> +#include <linux/kd.h> +#include <linux/fs.h> +#include <linux/kbd_kern.h> +#include <linux/ioctl.h> +#include <linux/poll.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/input.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/mfd/mc9s08dz60/pmic.h> +#include <asm/mach/keypad.h> + +#define MOD_NAME "mc9s08dz60-keyb" +/* + * Module header file + */ + +/*! Input device structure. */ +static struct input_dev *mc9s08dz60kbd_dev; +static unsigned int key_status; +static int keypad_irq; +static unsigned int key_code_map[8] = { + KEY_LEFT, + KEY_DOWN, + 0, + 0, + KEY_UP, + KEY_RIGHT, + 0, + 0, +}; +static unsigned int keycodes_size = 8; + +static void read_key_handler(struct work_struct *work); +static DECLARE_WORK(key_pad_event, read_key_handler); + + +static void read_key_handler(struct work_struct *work) +{ + unsigned int val1, val2; + int pre_val, curr_val, i; + val1 = val2 = 0xff; + mcu_pmic_read_reg(REG_MCU_KPD_1, &val1, 0xff); + mcu_pmic_read_reg(REG_MCU_KPD_2, &val2, 0xff); + pr_debug("key pressed, 0x%02x%02x\n", val2, val1); + for (i = 0; i < 8; i++) { + curr_val = (val1 >> i) & 0x1; + if (curr_val > 0) + input_event(mc9s08dz60kbd_dev, EV_KEY, + key_code_map[i], 1); + else { + pre_val = (key_status >> i) & 0x1; + if (pre_val > 0) + input_event(mc9s08dz60kbd_dev, EV_KEY, + key_code_map[i], 0); + } + } + key_status = val1; + +} + +static irqreturn_t mc9s08dz60kpp_interrupt(int irq, void *dev_id) +{ + schedule_work(&key_pad_event); + return IRQ_RETVAL(1); +} + +/*! + * This function is called when the keypad driver is opened. + * Since keypad initialization is done in __init, nothing is done in open. + * + * @param dev Pointer to device inode + * + * @result The function always return 0 + */ +static int mc9s08dz60kpp_open(struct input_dev *dev) +{ + return 0; +} + +/*! + * This function is called close the keypad device. + * Nothing is done in this function, since every thing is taken care in + * __exit function. + * + * @param dev Pointer to device inode + * + */ +static void mc9s08dz60kpp_close(struct input_dev *dev) +{ +} + + +/*! + * This function is called during the driver binding process. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions. + * + * @return The function returns 0 on successful registration. Otherwise returns + * specific error code. + */ +static int mc9s08dz60kpp_probe(struct platform_device *pdev) +{ + int i, irq; + int retval; + + retval = mcu_pmic_write_reg(REG_MCU_KPD_CONTROL, 0x1, 0x1); + if (retval != 0) { + pr_info("mc9s08dz60 keypad: mcu not detected!\n"); + return retval; + } + + irq = platform_get_irq(pdev, 0); + retval = request_irq(irq, mc9s08dz60kpp_interrupt, + 0, MOD_NAME, MOD_NAME); + if (retval) { + pr_debug("KPP: request_irq(%d) returned error %d\n", + irq, retval); + return retval; + } + keypad_irq = irq; + + mc9s08dz60kbd_dev = input_allocate_device(); + if (!mc9s08dz60kbd_dev) { + pr_info(KERN_ERR "mc9s08dz60kbd_dev: \ + not enough memory for input device\n"); + retval = -ENOMEM; + goto err1; + } + + mc9s08dz60kbd_dev->name = "mc9s08dz60kpd"; + mc9s08dz60kbd_dev->id.bustype = BUS_HOST; + mc9s08dz60kbd_dev->open = mc9s08dz60kpp_open; + mc9s08dz60kbd_dev->close = mc9s08dz60kpp_close; + + retval = input_register_device(mc9s08dz60kbd_dev); + if (retval < 0) { + pr_info(KERN_ERR + "mc9s08dz60kbd_dev: failed to register input device\n"); + goto err2; + } + + __set_bit(EV_KEY, mc9s08dz60kbd_dev->evbit); + + for (i = 0; i < keycodes_size; i++) + __set_bit(key_code_map[i], mc9s08dz60kbd_dev->keybit); + + device_init_wakeup(&pdev->dev, 1); + + pr_info("mc9s08dz60 keypad probed\n"); + + return 0; + +err2: + input_free_device(mc9s08dz60kbd_dev); +err1: + free_irq(irq, MOD_NAME); + return retval; +} + +/*! + * Dissociates the driver from the kpp device. + * + * @param pdev the device structure used to give information on which SDHC + * to remove + * + * @return The function always returns 0. + */ +static int mc9s08dz60kpp_remove(struct platform_device *pdev) +{ + free_irq(keypad_irq, MOD_NAME); + input_unregister_device(mc9s08dz60kbd_dev); + + if (mc9s08dz60kbd_dev) + input_free_device(mc9s08dz60kbd_dev); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mc9s08dz60kpd_driver = { + .driver = { + .name = "mc9s08dz60keypad", + .bus = &platform_bus_type, + }, + .probe = mc9s08dz60kpp_probe, + .remove = mc9s08dz60kpp_remove +}; + +static int __init mc9s08dz60kpp_init(void) +{ + pr_info(KERN_INFO "mc9s08dz60 keypad loaded\n"); + platform_driver_register(&mc9s08dz60kpd_driver); + return 0; +} + +static void __exit mc9s08dz60kpp_cleanup(void) +{ + platform_driver_unregister(&mc9s08dz60kpd_driver); +} + +module_init(mc9s08dz60kpp_init); +module_exit(mc9s08dz60kpp_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Keypad Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/keyboard/mpr084.c b/drivers/input/keyboard/mpr084.c new file mode 100644 index 000000000000..4b0c4883751e --- /dev/null +++ b/drivers/input/keyboard/mpr084.c @@ -0,0 +1,500 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file linux/drivers/input/keyboard/mpr084.c + * + * @brief Driver for the Freescale MPR084 I2C Touch Sensor KeyPad module. + * + * + * + * @ingroup Keypad + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/string.h> +#include <linux/bcd.h> +#include <linux/list.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/kthread.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/regulator/consumer.h> +#include <asm/mach/irq.h> +#include <mach/gpio.h> + +/* + * Definitions + */ +#define DEBUG 0 + +#define KEY_COUNT 8 + +/* + *Registers in MPR084 + */ +#define MPR084_FIFO_ADDR 0x00 +#define MPR084_FAULT_ADDR 0x01 +#define MPR084_TPS_ADDR 0x02 +#define MPR084_TPC_ADDR 0x03 +#define MPR084_STR1_ADDR 0x04 +#define MPR084_STR2_ADDR 0x05 +#define MPR084_STR3_ADDR 0x06 +#define MPR084_STR4_ADDR 0x07 +#define MPR084_STR5_ADDR 0x08 +#define MPR084_STR6_ADDR 0x09 +#define MPR084_STR7_ADDR 0x0A +#define MPR084_STR8_ADDR 0x0B +#define MPR084_ECEM_ADDR 0x0C +#define MPR084_MNTP_ADDR 0x0D +#define MPR084_MTC_ADDR 0x0E +#define MPR084_TASP_ADDR 0x0F +#define MPR084_SC_ADDR 0x10 +#define MPR084_LPC_ADDR 0x11 +#define MPR084_SKT_ADDR 0x12 +#define MPR084_CONFIG_ADDR 0x13 +#define MPR084_SI_ADDR 0x14 +#define MPR084_ADDR_MINI MPR084_FIFO_ADDR +#define MPR084_ADDR_MAX MPR084_SI_ADDR + +/* FIFO registers */ +#define MPR084_FIFO_MORE_DATA_FLAG 0x80 +#define MPR084_FIFO_NO_DATA_FLAG 0x40 +#define MPR084_FIFO_OVERFLOW_FLAG 0x20 +#define MPR084_FIFO_PAD_IS_TOUCHED 0x10 +#define MPR084_FIFO_POSITION_MASK 0x0F + +#define DRIVER_NAME "mpr084" + +struct mpr084_data { + struct i2c_client *client; + struct device_driver driver; + struct input_dev *idev; + struct task_struct *tstask; + struct completion kpirq_completion; + int kpirq; + int kp_thread_cnt; + int opened; +}; + +static int kpstatus[KEY_COUNT]; +static struct mxc_keyp_platform_data *keypad; +static const unsigned short *mxckpd_keycodes; +static struct regulator *vdd_reg; + +static int mpr084_read_register(struct mpr084_data *data, + unsigned char regaddr, int *value) +{ + int ret = 0; + unsigned char regvalue; + + ret = i2c_master_send(data->client, ®addr, 1); + if (ret < 0) + goto err; + udelay(20); + ret = i2c_master_recv(data->client, ®value, 1); + if (ret < 0) + goto err; + *value = regvalue; + + return ret; +err: + return -ENODEV; +} + +static int mpr084_write_register(struct mpr084_data *data, + u8 regaddr, u8 regvalue) +{ + int ret = 0; + unsigned char msgbuf[2]; + + msgbuf[0] = regaddr; + msgbuf[1] = regvalue; + ret = i2c_master_send(data->client, msgbuf, 2); + if (ret < 0) { + printk(KERN_ERR "%s - Error in writing to I2C Register %d \n", + __func__, regaddr); + return ret; + } + + return ret; +} + + +static irqreturn_t mpr084_keypadirq(int irq, void *v) +{ + struct mpr084_data *d = v; + + disable_irq(d->kpirq); + complete(&d->kpirq_completion); + return IRQ_HANDLED; +} + +static int mpr084ts_thread(void *v) +{ + struct mpr084_data *d = v; + int ret = 0, fifo = 0; + int index = 0, currentstatus = 0; + + if (d->kp_thread_cnt) + return -EINVAL; + d->kp_thread_cnt = 1; + while (1) { + + if (kthread_should_stop()) + break; + /* Wait for keypad interrupt */ + if (wait_for_completion_interruptible_timeout + (&d->kpirq_completion, HZ) <= 0) + continue; + + ret = mpr084_read_register(d, MPR084_FIFO_ADDR, &fifo); + if (ret < 0) { + printk(KERN_ERR + "%s: Err in reading keypad FIFO register \n\n", + __func__); + } else { + if (fifo & MPR084_FIFO_OVERFLOW_FLAG) + printk(KERN_ERR + "%s: FIFO overflow \n\n", __func__); + while (!(fifo & MPR084_FIFO_NO_DATA_FLAG)) { + index = fifo & MPR084_FIFO_POSITION_MASK; + currentstatus = + fifo & MPR084_FIFO_PAD_IS_TOUCHED; + /*Scan key map for changes */ + if ((currentstatus) ^ (kpstatus[index])) { + if (!(currentstatus)) { + /*Key released. */ + input_event(d->idev, EV_KEY, + mxckpd_keycodes + [index], 0); + } else { + /* Key pressed. */ + input_event(d->idev, EV_KEY, + mxckpd_keycodes + [index], 1); + } + /*Store current keypad status */ + kpstatus[index] = currentstatus; + } + mpr084_read_register(d, MPR084_FIFO_ADDR, + &fifo); + if (fifo & MPR084_FIFO_OVERFLOW_FLAG) + printk(KERN_ERR + "%s: FIFO overflow \n\n", + __func__); + } + } + /* Re-enable interrupts */ + enable_irq(d->kpirq); + } + + d->kp_thread_cnt = 0; + return 0; +} + +/*! + * This function puts the Keypad controller in low-power mode/state. + * + * @param pdev the device structure used to give information on Keypad + * to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int mpr084_suspend(struct i2c_client *client, pm_message_t state) +{ + struct mpr084_data *d = i2c_get_clientdata(client); + + if (!IS_ERR(d->tstask) && d->opened) + kthread_stop(d->tstask); + + return 0; +} + +/*! + * This function brings the Keypad controller back from low-power state. + * + * @param pdev the device structure used to give information on Keypad + * to resume + * + * @return The function always returns 0. + */ +static int mpr084_resume(struct i2c_client *client) +{ + struct mpr084_data *d = i2c_get_clientdata(client); + + if (d->opened) + d->tstask = kthread_run(mpr084ts_thread, d, DRIVER_NAME "kpd"); + + return 0; +} + +static int mpr084_idev_open(struct input_dev *idev) +{ + struct mpr084_data *d = input_get_drvdata(idev); + int ret = 0; + + d->tstask = kthread_run(mpr084ts_thread, d, DRIVER_NAME "kpd"); + if (IS_ERR(d->tstask)) + ret = PTR_ERR(d->tstask); + else + d->opened++; + return ret; +} + +static void mpr084_idev_close(struct input_dev *idev) +{ + struct mpr084_data *d = input_get_drvdata(idev); + + if (!IS_ERR(d->tstask)) + kthread_stop(d->tstask); + if (d->opened > 0) + d->opened--; +} + +static int mpr084_driver_register(struct mpr084_data *data) +{ + struct input_dev *idev; + int ret = 0; + + if (data->kpirq) { + ret = + request_irq(data->kpirq, mpr084_keypadirq, + IRQF_TRIGGER_FALLING, DRIVER_NAME, data); + if (!ret) { + init_completion(&data->kpirq_completion); + set_irq_wake(data->kpirq, 1); + } else { + printk(KERN_ERR "%s: cannot grab irq %d\n", + __func__, data->kpirq); + } + + } + idev = input_allocate_device(); + data->idev = idev; + input_set_drvdata(idev, data); + idev->name = DRIVER_NAME; + idev->open = mpr084_idev_open; + idev->close = mpr084_idev_close; + if (!ret) + ret = input_register_device(idev); + + return ret; +} + +static int mpr084_i2c_remove(struct i2c_client *client) +{ + struct mpr084_data *d = i2c_get_clientdata(client); + + free_irq(d->kpirq, d); + input_unregister_device(d->idev); + if (keypad->inactive) + keypad->inactive(); + + /*Disable the Regulator*/ + if (keypad->vdd_reg) { + regulator_disable(vdd_reg); + regulator_put(vdd_reg); + } + + return 0; +} + +static int mpr084_configure(struct mpr084_data *data) +{ + int ret = 0, regValue = 0; + + ret = mpr084_write_register(data, MPR084_TPC_ADDR, 0x1d); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR1_ADDR, 0x10); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR2_ADDR, 0x10); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR3_ADDR, 0x10); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR4_ADDR, 0x10); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR5_ADDR, 0x10); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR6_ADDR, 0x10); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR7_ADDR, 0x10); + if (ret < 0) + goto err; + ret = mpr084_write_register(data, MPR084_STR8_ADDR, 0x10); + if (ret < 0) + goto err; + /* channel enable mask: enable all */ + ret = mpr084_write_register(data, MPR084_ECEM_ADDR, 0xff); + if (ret < 0) + goto err; + /*two conccurrent touch position allowed */ + ret = mpr084_write_register(data, MPR084_MNTP_ADDR, 0x02); + if (ret < 0) + goto err; + + /* master tick period*/ + ret = mpr084_write_register(data, MPR084_MTC_ADDR, 0x05); + if (ret < 0) + goto err; + + + /*Sample period */ + ret = mpr084_write_register(data, MPR084_TASP_ADDR, 0x02); + if (ret < 0) + goto err; + + + /* disable sournder*/ + ret = mpr084_write_register(data, MPR084_SC_ADDR, 0x00); + if (ret < 0) + goto err; + + /* stuck key timeout */ + ret = mpr084_write_register(data, MPR084_SKT_ADDR, 0x01); + if (ret < 0) + goto err; + + /*enabled IRQEN, RUNE, IRQR */ + ret = mpr084_read_register(data, MPR084_CONFIG_ADDR, ®Value); + if (ret < 0) { + printk(KERN_ERR + "%s: Err in reading keypad CONFIGADDR register \n\n", + __func__); + goto err; + } + regValue |= 0x03; + ret = mpr084_write_register(data, MPR084_CONFIG_ADDR, regValue); + if (ret < 0) + goto err; + return ret; +err: + return -ENODEV; +} + +static int mpr084_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct mpr084_data *data; + int err = 0, i = 0; +#if DEBUG + int regValue = 0; +#endif + data = kzalloc(sizeof(struct mpr084_data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + i2c_set_clientdata(client, data); + data->client = client; + data->kpirq = client->irq; + err = mpr084_driver_register(data); + if (err < 0) + goto exit_free; + keypad = (struct mxc_keyp_platform_data *)(client->dev).platform_data; + if (keypad->active) + keypad->active(); + + /*Enable the Regulator*/ + if (keypad && keypad->vdd_reg) { + vdd_reg = regulator_get(&client->dev, keypad->vdd_reg); + if (!IS_ERR(vdd_reg)) + regulator_enable(vdd_reg); + else + vdd_reg = NULL; + } else + vdd_reg = NULL; + + mxckpd_keycodes = keypad->matrix; + data->idev->keycode = &mxckpd_keycodes; + data->idev->keycodesize = sizeof(unsigned char); + data->idev->keycodemax = KEY_COUNT; + data->idev->id.bustype = BUS_I2C; + __set_bit(EV_KEY, data->idev->evbit); + for (i = 0; i < 8; i++) + __set_bit(mxckpd_keycodes[i], data->idev->keybit); + err = mpr084_configure(data); + if (err == -ENODEV) { + free_irq(data->kpirq, data); + input_unregister_device(data->idev); + goto exit_free; + } + +#if DEBUG + for (i = MPR084_ADDR_MINI; i <= MPR084_ADDR_MAX; i++) { + err = mpr084_read_register(data, i, ®Value); + if (err < 0) { + printk(KERN_ERR + "%s: Err in reading keypad CONFIGADDR register \n\n", + __func__); + goto exit_free; + } + printk("MPR084 Register id: %d, Value:%d \n", i, regValue); + + } +#endif + memset(kpstatus, 0, sizeof(kpstatus)); + printk(KERN_INFO "%s: Device Attached\n", __func__); + return 0; +exit_free: + /*disable the Regulator*/ + if (vdd_reg) { + regulator_disable(vdd_reg); + regulator_put(vdd_reg); + vdd_reg = NULL; + } + kfree(data); + return err; +} + +static const struct i2c_device_id mpr084_id[] = { + { "mpr084", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, mpr084_id); + +static struct i2c_driver mpr084_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = mpr084_i2c_probe, + .remove = mpr084_i2c_remove, + .suspend = mpr084_suspend, + .resume = mpr084_resume, + .command = NULL, + .id_table = mpr084_id, +}; +static int __init mpr084_init(void) +{ + return i2c_add_driver(&mpr084_driver); +} + +static void __exit mpr084_exit(void) +{ + i2c_del_driver(&mpr084_driver); +} + +MODULE_AUTHOR("Freescale Semiconductor Inc"); +MODULE_DESCRIPTION("MPR084 Touch KeyPad Controller driver"); +MODULE_LICENSE("GPL"); +module_init(mpr084_init); +module_exit(mpr084_exit); diff --git a/drivers/input/keyboard/mxc_keyb.c b/drivers/input/keyboard/mxc_keyb.c new file mode 100644 index 000000000000..248b8e560903 --- /dev/null +++ b/drivers/input/keyboard/mxc_keyb.c @@ -0,0 +1,1036 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_keyb.c + * + * @brief Driver for the Freescale Semiconductor MXC keypad port. + * + * The keypad driver is designed as a standard Input driver which interacts + * with low level keypad port hardware. Upon opening, the Keypad driver + * initializes the keypad port. When the keypad interrupt happens the driver + * calles keypad polling timer and scans the keypad matrix for key + * press/release. If all key press/release happened it comes out of timer and + * waits for key press interrupt. The scancode for key press and release events + * are passed to Input subsytem. + * + * @ingroup keypad + */ + +/*! + * Comment KPP_DEBUG to disable debug messages + */ +#define KPP_DEBUG 0 + +#if KPP_DEBUG +#define DEBUG +#include <linux/kernel.h> +#endif + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <mach/hardware.h> +#include <linux/kd.h> +#include <linux/fs.h> +#include <linux/kbd_kern.h> +#include <linux/ioctl.h> +#include <linux/poll.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/input.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <asm/mach/keypad.h> + +/* + * Module header file + */ +#include "mxc_keyb.h" + +/*! + * This structure holds the keypad private data structure. + */ +static struct keypad_priv kpp_dev; + +/*! Indicates if the key pad device is enabled. */ +static unsigned int key_pad_enabled; + +/*! Input device structure. */ +static struct input_dev *mxckbd_dev = NULL; + +/*! KPP clock handle. */ +static struct clk *kpp_clk; + +/*! This static variable indicates whether a key event is pressed/released. */ +static unsigned short KPress; + +/*! cur_rcmap and prev_rcmap array is used to detect key press and release. */ +static unsigned short *cur_rcmap; /* max 64 bits (8x8 matrix) */ +static unsigned short *prev_rcmap; + +/*! + * Debounce polling period(10ms) in system ticks. + */ +static unsigned short KScanRate = (10 * HZ) / 1000; + +static struct keypad_data *keypad; + +static int has_leaning_key; +/*! + * These arrays are used to store press and release scancodes. + */ +static short **press_scancode; +static short **release_scancode; + +static const unsigned short *mxckpd_keycodes; +static unsigned short mxckpd_keycodes_size; + +#define press_left_code 30 +#define press_right_code 29 +#define press_up_code 28 +#define press_down_code 27 + +#define rel_left_code 158 +#define rel_right_code 157 +#define rel_up_code 156 +#define rel_down_code 155 +/*! + * These functions are used to configure and the GPIO pins for keypad to + * activate and deactivate it. + */ +extern void gpio_keypad_active(void); +extern void gpio_keypad_inactive(void); + +/*! + * This function is called for generating scancodes for key press and + * release on keypad for the board. + * + * @param row Keypad row pressed on the keypad matrix. + * @param col Keypad col pressed on the keypad matrix. + * @param press Indicated key press/release. + * + * @return Key press/release Scancode. + */ +static signed short mxc_scan_matrix_leaning_key(int row, int col, int press) +{ + static unsigned first_row; + static unsigned first_set = 0, flag = 0; + signed short scancode = -1; + + if (press) { + if ((3 == col) && ((3 == row) || + (4 == row) || (5 == row) || (6 == row))) { + if (first_set == 0) { + first_set = 1; + first_row = row; + } else { + first_set = 0; + if (((first_row == 6) || (first_row == 3)) + && ((row == 6) || (row == 3))) + scancode = press_down_code; + else if (((first_row == 3) || (first_row == 5)) + && ((row == 3) || (row == 5))) + scancode = press_left_code; + else if (((first_row == 6) || (first_row == 4)) + && ((row == 6) || (row == 4))) + scancode = press_right_code; + else if (((first_row == 4) || (first_row == 5)) + && ((row == 4) || (row == 5))) + scancode = press_up_code; + KPress = 1; + kpp_dev.iKeyState = KStateUp; + pr_debug("Press (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + } + } else { + /* + * check for other keys only + * if the cursor key presses + * are not detected may be + * this needs better logic + */ + if ((0 == (cur_rcmap[3] & BITSET(0, 3))) && + (0 == (cur_rcmap[4] & BITSET(0, 3))) && + (0 == (cur_rcmap[5] & BITSET(0, 3))) && + (0 == (cur_rcmap[6] & BITSET(0, 3)))) { + scancode = ((col * kpp_dev.kpp_rows) + row); + KPress = 1; + kpp_dev.iKeyState = KStateUp; + flag = 1; + pr_debug("Press (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + } + } + } else { + if ((flag == 0) && (3 == col) + && ((3 == row) || (4 == row) || (5 == row) + || (6 == row))) { + if (first_set == 0) { + first_set = 1; + first_row = row; + } else { + first_set = 0; + if (((first_row == 6) || (first_row == 3)) + && ((row == 6) || (row == 3))) + scancode = rel_down_code; + else if (((first_row == 3) || (first_row == 5)) + && ((row == 3) || (row == 5))) + scancode = rel_left_code; + else if (((first_row == 6) || (first_row == 4)) + && ((row == 6) || (row == 4))) + scancode = rel_right_code; + else if (((first_row == 4) || (first_row == 5)) + && ((row == 4) || (row == 5))) + scancode = rel_up_code; + KPress = 0; + kpp_dev.iKeyState = KStateDown; + pr_debug("Release (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + } + } else { + /* + * check for other keys only + * if the cursor key presses + * are not detected may be + * this needs better logic + */ + if ((0 == (prev_rcmap[3] & BITSET(0, 3))) && + (0 == (prev_rcmap[4] & BITSET(0, 3))) && + (0 == (cur_rcmap[5] & BITSET(0, 3))) && + (0 == (cur_rcmap[6] & BITSET(0, 3)))) { + scancode = ((col * kpp_dev.kpp_rows) + row) + + MXC_KEYRELEASE; + KPress = 0; + flag = 0; + kpp_dev.iKeyState = KStateDown; + pr_debug("Release (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + } + } + } + return scancode; +} + +/*! + * This function is called to scan the keypad matrix to find out the key press + * and key release events. Make scancode and break scancode are generated for + * key press and key release events. + * + * The following scanning sequence are done for + * keypad row and column scanning, + * -# Write 1's to KPDR[15:8], setting column data to 1's + * -# Configure columns as totem pole outputs(for quick discharging of keypad + * capacitance) + * -# Configure columns as open-drain + * -# Write a single column to 0, others to 1. + * -# Sample row inputs and save data. Multiple key presses can be detected on + * a single column. + * -# Repeat steps the above steps for remaining columns. + * -# Return all columns to 0 in preparation for standby mode. + * -# Clear KPKD and KPKR status bit(s) by writing to a 1, + * Set the KPKR synchronizer chain by writing "1" to KRSS register, + * Clear the KPKD synchronizer chain by writing "1" to KDSC register + * + * @result Number of key pressed/released. + */ +static int mxc_kpp_scan_matrix(void) +{ + unsigned short reg_val; + int col, row; + short scancode = 0; + int keycnt = 0; /* How many keys are still pressed */ + + /* + * wmb() linux kernel function which guarantees orderings in write + * operations + */ + wmb(); + + /* save cur keypad matrix to prev */ + + memcpy(prev_rcmap, cur_rcmap, kpp_dev.kpp_rows * sizeof(prev_rcmap[0])); + memset(cur_rcmap, 0, kpp_dev.kpp_rows * sizeof(cur_rcmap[0])); + + for (col = 0; col < kpp_dev.kpp_cols; col++) { /* Col */ + /* 2. Write 1.s to KPDR[15:8] setting column data to 1.s */ + reg_val = __raw_readw(KPDR); + reg_val |= 0xff00; + __raw_writew(reg_val, KPDR); + + /* + * 3. Configure columns as totem pole outputs(for quick + * discharging of keypad capacitance) + */ + reg_val = __raw_readw(KPCR); + reg_val &= 0x00ff; + __raw_writew(reg_val, KPCR); + + udelay(2); + + /* + * 4. Configure columns as open-drain + */ + reg_val = __raw_readw(KPCR); + reg_val |= ((1 << kpp_dev.kpp_cols) - 1) << 8; + __raw_writew(reg_val, KPCR); + + /* + * 5. Write a single column to 0, others to 1. + * 6. Sample row inputs and save data. Multiple key presses + * can be detected on a single column. + * 7. Repeat steps 2 - 6 for remaining columns. + */ + + /* Col bit starts at 8th bit in KPDR */ + reg_val = __raw_readw(KPDR); + reg_val &= ~(1 << (8 + col)); + __raw_writew(reg_val, KPDR); + + /* Delay added to avoid propagating the 0 from column to row + * when scanning. */ + + udelay(5); + + /* Read row input */ + reg_val = __raw_readw(KPDR); + for (row = 0; row < kpp_dev.kpp_rows; row++) { /* sample row */ + if (TEST_BIT(reg_val, row) == 0) { + cur_rcmap[row] = BITSET(cur_rcmap[row], col); + keycnt++; + } + } + } + + /* + * 8. Return all columns to 0 in preparation for standby mode. + * 9. Clear KPKD and KPKR status bit(s) by writing to a .1., + * set the KPKR synchronizer chain by writing "1" to KRSS register, + * clear the KPKD synchronizer chain by writing "1" to KDSC register + */ + reg_val = 0x00; + __raw_writew(reg_val, KPDR); + reg_val = __raw_readw(KPDR); + reg_val = __raw_readw(KPSR); + reg_val |= KBD_STAT_KPKD | KBD_STAT_KPKR | KBD_STAT_KRSS | + KBD_STAT_KDSC; + __raw_writew(reg_val, KPSR); + + /* Check key press status change */ + + /* + * prev_rcmap array will contain the previous status of the keypad + * matrix. cur_rcmap array will contains the present status of the + * keypad matrix. If a bit is set in the array, that (row, col) bit is + * pressed, else it is not pressed. + * + * XORing these two variables will give us the change in bit for + * particular row and column. If a bit is set in XOR output, then that + * (row, col) has a change of status from the previous state. From + * the diff variable the key press and key release of row and column + * are found out. + * + * If the key press is determined then scancode for key pressed + * can be generated using the following statement: + * scancode = ((row * 8) + col); + * + * If the key release is determined then scancode for key release + * can be generated using the following statement: + * scancode = ((row * 8) + col) + MXC_KEYRELEASE; + */ + for (row = 0; row < kpp_dev.kpp_rows; row++) { + unsigned char diff; + + /* + * Calculate the change in the keypad row status + */ + diff = prev_rcmap[row] ^ cur_rcmap[row]; + + for (col = 0; col < kpp_dev.kpp_cols; col++) { + if ((diff >> col) & 0x1) { + /* There is a status change on col */ + if ((prev_rcmap[row] & BITSET(0, col)) == 0) { + /* + * Previous state is 0, so now + * a key is pressed + */ + if (has_leaning_key) { + scancode = + mxc_scan_matrix_leaning_key + (row, col, 1); + } else { + scancode = + ((row * kpp_dev.kpp_cols) + + col); + KPress = 1; + kpp_dev.iKeyState = KStateUp; + } + pr_debug("Press (%d, %d) scan=%d " + "Kpress=%d\n", + row, col, scancode, KPress); + press_scancode[row][col] = + (short)scancode; + } else { + /* + * Previous state is not 0, so + * now a key is released + */ + if (has_leaning_key) { + scancode = + mxc_scan_matrix_leaning_key + (row, col, 0); + } else { + scancode = + (row * kpp_dev.kpp_cols) + + col + MXC_KEYRELEASE; + KPress = 0; + kpp_dev.iKeyState = KStateDown; + } + + pr_debug + ("Release (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + release_scancode[row][col] = + (short)scancode; + keycnt++; + } + } + } + } + + /* + * This switch case statement is the + * implementation of state machine of debounce + * logic for key press/release. + * The explaination of state machine is as + * follows: + * + * KStateUp State: + * This is in intial state of the state machine + * this state it checks for any key presses. + * The key press can be checked using the + * variable KPress. If KPress is set, then key + * press is identified and switches the to + * KStateFirstDown state for key press to + * debounce. + * + * KStateFirstDown: + * After debounce delay(10ms), if the KPress is + * still set then pass scancode generated to + * input device and change the state to + * KStateDown, else key press debounce is not + * satisfied so change the state to KStateUp. + * + * KStateDown: + * In this state it checks for any key release. + * If KPress variable is cleared, then key + * release is indicated and so, switch the + * state to KStateFirstUp else to state + * KStateDown. + * + * KStateFirstUp: + * After debounce delay(10ms), if the KPress is + * still reset then pass the key release + * scancode to input device and change + * the state to KStateUp else key release is + * not satisfied so change the state to + * KStateDown. + */ + switch (kpp_dev.iKeyState) { + case KStateUp: + if (KPress) { + /* First Down (must debounce). */ + kpp_dev.iKeyState = KStateFirstDown; + } else { + /* Still UP.(NO Changes) */ + kpp_dev.iKeyState = KStateUp; + } + break; + + case KStateFirstDown: + if (KPress) { + for (row = 0; row < kpp_dev.kpp_rows; row++) { + for (col = 0; col < kpp_dev.kpp_cols; col++) { + if ((press_scancode[row][col] != -1)) { + /* Still Down, so add scancode */ + scancode = + press_scancode[row][col]; + input_event(mxckbd_dev, EV_KEY, + mxckpd_keycodes + [scancode], 1); + if (mxckpd_keycodes[scancode] == + KEY_LEFTSHIFT) { + input_event(mxckbd_dev, + EV_KEY, + KEY_3, 1); + } + kpp_dev.iKeyState = KStateDown; + press_scancode[row][col] = -1; + } + } + } + } else { + /* Just a bounce */ + kpp_dev.iKeyState = KStateUp; + } + break; + + case KStateDown: + if (KPress) { + /* Still down (no change) */ + kpp_dev.iKeyState = KStateDown; + } else { + /* First Up. Must debounce */ + kpp_dev.iKeyState = KStateFirstUp; + } + break; + + case KStateFirstUp: + if (KPress) { + /* Just a bounce */ + kpp_dev.iKeyState = KStateDown; + } else { + for (row = 0; row < kpp_dev.kpp_rows; row++) { + for (col = 0; col < kpp_dev.kpp_cols; col++) { + if ((release_scancode[row][col] != -1)) { + scancode = + release_scancode[row][col]; + scancode = + scancode - MXC_KEYRELEASE; + input_event(mxckbd_dev, EV_KEY, + mxckpd_keycodes + [scancode], 0); + if (mxckpd_keycodes[scancode] == + KEY_LEFTSHIFT) { + input_event(mxckbd_dev, + EV_KEY, + KEY_3, 0); + } + kpp_dev.iKeyState = KStateUp; + release_scancode[row][col] = -1; + } + } + } + } + break; + + default: + return -EBADRQC; + break; + } + + return keycnt; +} + +/*! + * This function is called to start the timer for scanning the keypad if there + * is any key press. Currently this interval is set to 10 ms. When there are + * no keys pressed on the keypad we return back, waiting for a keypad key + * press interrupt. + * + * @param data Opaque data passed back by kernel. Not used. + */ +static void mxc_kpp_handle_timer(unsigned long data) +{ + unsigned short reg_val; + int i; + + if (key_pad_enabled == 0) { + return; + } + if (mxc_kpp_scan_matrix() == 0) { + /* + * Stop scanning and wait for interrupt. + * Enable press interrupt and disable release interrupt. + */ + __raw_writew(0x00FF, KPDR); + reg_val = __raw_readw(KPSR); + reg_val |= (KBD_STAT_KPKR | KBD_STAT_KPKD); + reg_val |= KBD_STAT_KRSS | KBD_STAT_KDSC; + __raw_writew(reg_val, KPSR); + reg_val |= KBD_STAT_KDIE; + reg_val &= ~KBD_STAT_KRIE; + __raw_writew(reg_val, KPSR); + + /* + * No more keys pressed... make sure unwanted key codes are + * not given upstairs + */ + for (i = 0; i < kpp_dev.kpp_rows; i++) { + memset(press_scancode[i], -1, + sizeof(press_scancode[0][0]) * kpp_dev.kpp_cols); + memset(release_scancode[i], -1, + sizeof(release_scancode[0][0]) * + kpp_dev.kpp_cols); + } + return; + } + + /* + * There are still some keys pressed, continue to scan. + * We shall scan again in 10 ms. This has to be tuned according + * to the requirement. + */ + kpp_dev.poll_timer.expires = jiffies + KScanRate; + kpp_dev.poll_timer.function = mxc_kpp_handle_timer; + add_timer(&kpp_dev.poll_timer); +} + +/*! + * This function is the keypad Interrupt handler. + * This function checks for keypad status register (KPSR) for key press + * and interrupt. If key press interrupt has occurred, then the key + * press interrupt in the KPSR are disabled. + * It then calls mxc_kpp_scan_matrix to check for any key pressed/released. + * If any key is found to be pressed, then a timer is set to call + * mxc_kpp_scan_matrix function for every 10 ms. + * + * @param irq The Interrupt number + * @param dev_id Driver private data + * + * @result The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + * \b IRQ_RETVAL is defined in include/linux/interrupt.h. + */ +static irqreturn_t mxc_kpp_interrupt(int irq, void *dev_id) +{ + unsigned short reg_val; + + /* Delete the polling timer */ + del_timer(&kpp_dev.poll_timer); + reg_val = __raw_readw(KPSR); + + /* Check if it is key press interrupt */ + if (reg_val & KBD_STAT_KPKD) { + /* + * Disable key press(KDIE status bit) interrupt + */ + reg_val &= ~KBD_STAT_KDIE; + __raw_writew(reg_val, KPSR); + } else { + /* spurious interrupt */ + return IRQ_RETVAL(0); + } + /* + * Check if any keys are pressed, if so start polling. + */ + mxc_kpp_handle_timer(0); + + return IRQ_RETVAL(1); +} + +/*! + * This function is called when the keypad driver is opened. + * Since keypad initialization is done in __init, nothing is done in open. + * + * @param dev Pointer to device inode + * + * @result The function always return 0 + */ +static int mxc_kpp_open(struct input_dev *dev) +{ + return 0; +} + +/*! + * This function is called close the keypad device. + * Nothing is done in this function, since every thing is taken care in + * __exit function. + * + * @param dev Pointer to device inode + * + */ +static void mxc_kpp_close(struct input_dev *dev) +{ +} + +#ifdef CONFIG_PM +/*! + * This function puts the Keypad controller in low-power mode/state. + * If Keypad is enabled as a wake source(i.e. it can resume the system + * from suspend mode), the Keypad controller doesn't enter low-power state. + * + * @param pdev the device structure used to give information on Keypad + * to suspend + * @param state the power state the device is entering + * + * @return return -1 when the keypad is pressed. Otherwise, return 0 + */ +static int mxc_kpp_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* When the keypad is still pressed, clean up registers and timers */ + if (timer_pending(&kpp_dev.poll_timer)) + return -1; + + if (device_may_wakeup(&pdev->dev)) { + enable_irq_wake(keypad->irq); + } else { + disable_irq(keypad->irq); + key_pad_enabled = 0; + clk_disable(kpp_clk); + gpio_keypad_inactive(); + } + + return 0; +} + +/*! + * This function brings the Keypad controller back from low-power state. + * If Keypad is enabled as a wake source(i.e. it can resume the system + * from suspend mode), the Keypad controller doesn't enter low-power state. + * + * @param pdev the device structure used to give information on Keypad + * to resume + * + * @return The function always returns 0. + */ +static int mxc_kpp_resume(struct platform_device *pdev) +{ + if (device_may_wakeup(&pdev->dev)) { + disable_irq_wake(keypad->irq); + } else { + gpio_keypad_active(); + clk_enable(kpp_clk); + key_pad_enabled = 1; + enable_irq(keypad->irq); + } + + return 0; +} + +#else +#define mxc_kpp_suspend NULL +#define mxc_kpp_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This function is called to free the allocated memory for local arrays + */ +static void mxc_kpp_free_allocated(void) +{ + + int i; + + if (press_scancode) { + for (i = 0; i < kpp_dev.kpp_rows; i++) { + if (press_scancode[i]) + kfree(press_scancode[i]); + } + kfree(press_scancode); + } + + if (release_scancode) { + for (i = 0; i < kpp_dev.kpp_rows; i++) { + if (release_scancode[i]) + kfree(release_scancode[i]); + } + kfree(release_scancode); + } + + if (cur_rcmap) + kfree(cur_rcmap); + + if (prev_rcmap) + kfree(prev_rcmap); + + if (mxckbd_dev) + input_free_device(mxckbd_dev); +} + +/*! + * This function is called during the driver binding process. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions. + * + * @return The function returns 0 on successful registration. Otherwise returns + * specific error code. + */ +static int mxc_kpp_probe(struct platform_device *pdev) +{ + int i, irq; + int retval; + unsigned int reg_val; + + keypad = (struct keypad_data *)pdev->dev.platform_data; + + kpp_dev.kpp_cols = keypad->colmax; + kpp_dev.kpp_rows = keypad->rowmax; + key_pad_enabled = 0; + + /* + * Request for IRQ number for keypad port. The Interrupt handler + * function (mxc_kpp_interrupt) is called when ever interrupt occurs on + * keypad port. + */ + irq = platform_get_irq(pdev, 0); + keypad->irq = irq; + retval = request_irq(irq, mxc_kpp_interrupt, 0, MOD_NAME, MOD_NAME); + if (retval) { + pr_debug("KPP: request_irq(%d) returned error %d\n", + MXC_INT_KPP, retval); + return retval; + } + + /* Enable keypad clock */ + kpp_clk = clk_get(&pdev->dev, "kpp_clk"); + clk_enable(kpp_clk); + + /* IOMUX configuration for keypad */ + gpio_keypad_active(); + + /* Configure keypad */ + + /* Enable number of rows in keypad (KPCR[7:0]) + * Configure keypad columns as open-drain (KPCR[15:8]) + * + * Configure the rows/cols in KPP + * LSB nibble in KPP is for 8 rows + * MSB nibble in KPP is for 8 cols + */ + reg_val = __raw_readw(KPCR); + reg_val |= (1 << keypad->rowmax) - 1; /* LSB */ + reg_val |= ((1 << keypad->colmax) - 1) << 8; /* MSB */ + __raw_writew(reg_val, KPCR); + + /* Write 0's to KPDR[15:8] */ + reg_val = __raw_readw(KPDR); + reg_val &= 0x00ff; + __raw_writew(reg_val, KPDR); + + /* Configure columns as output, rows as input (KDDR[15:0]) */ + reg_val = __raw_readw(KDDR); + reg_val |= 0xff00; + reg_val &= 0xff00; + __raw_writew(reg_val, KDDR); + + reg_val = __raw_readw(KPSR); + reg_val &= ~(KBD_STAT_KPKR | KBD_STAT_KPKD); + reg_val |= KBD_STAT_KPKD; + reg_val |= KBD_STAT_KRSS | KBD_STAT_KDSC; + __raw_writew(reg_val, KPSR); + reg_val |= KBD_STAT_KDIE; + reg_val &= ~KBD_STAT_KRIE; + __raw_writew(reg_val, KPSR); + + has_leaning_key = keypad->learning; + mxckpd_keycodes = keypad->matrix; + mxckpd_keycodes_size = keypad->rowmax * keypad->colmax; + + if ((keypad->matrix == (void *)0) + || (mxckpd_keycodes_size == 0)) { + retval = -ENODEV; + goto err1; + } + + mxckbd_dev = input_allocate_device(); + if (!mxckbd_dev) { + printk(KERN_ERR + "mxckbd_dev: not enough memory for input device\n"); + retval = -ENOMEM; + goto err1; + } + + mxckbd_dev->keycode = (void *)mxckpd_keycodes; + mxckbd_dev->keycodesize = sizeof(mxckpd_keycodes[0]); + mxckbd_dev->keycodemax = mxckpd_keycodes_size; + mxckbd_dev->name = "mxckpd"; + mxckbd_dev->id.bustype = BUS_HOST; + mxckbd_dev->open = mxc_kpp_open; + mxckbd_dev->close = mxc_kpp_close; + + retval = input_register_device(mxckbd_dev); + if (retval < 0) { + printk(KERN_ERR + "mxckbd_dev: failed to register input device\n"); + goto err2; + } + + /* allocate required memory */ + press_scancode = kmalloc(kpp_dev.kpp_rows * sizeof(press_scancode[0]), + GFP_KERNEL); + release_scancode = + kmalloc(kpp_dev.kpp_rows * sizeof(release_scancode[0]), GFP_KERNEL); + + if (!press_scancode || !release_scancode) { + retval = -ENOMEM; + goto err3; + } + + for (i = 0; i < kpp_dev.kpp_rows; i++) { + press_scancode[i] = kmalloc(kpp_dev.kpp_cols + * sizeof(press_scancode[0][0]), + GFP_KERNEL); + release_scancode[i] = + kmalloc(kpp_dev.kpp_cols * sizeof(release_scancode[0][0]), + GFP_KERNEL); + + if (!press_scancode[i] || !release_scancode[i]) { + retval = -ENOMEM; + goto err3; + } + } + + cur_rcmap = + kmalloc(kpp_dev.kpp_rows * sizeof(cur_rcmap[0]), GFP_KERNEL); + prev_rcmap = + kmalloc(kpp_dev.kpp_rows * sizeof(prev_rcmap[0]), GFP_KERNEL); + + if (!cur_rcmap || !prev_rcmap) { + retval = -ENOMEM; + goto err3; + } + + __set_bit(EV_KEY, mxckbd_dev->evbit); + + for (i = 0; i < mxckpd_keycodes_size; i++) + __set_bit(mxckpd_keycodes[i], mxckbd_dev->keybit); + + for (i = 0; i < kpp_dev.kpp_rows; i++) { + memset(press_scancode[i], -1, + sizeof(press_scancode[0][0]) * kpp_dev.kpp_cols); + memset(release_scancode[i], -1, + sizeof(release_scancode[0][0]) * kpp_dev.kpp_cols); + } + memset(cur_rcmap, 0, kpp_dev.kpp_rows * sizeof(cur_rcmap[0])); + memset(prev_rcmap, 0, kpp_dev.kpp_rows * sizeof(prev_rcmap[0])); + + key_pad_enabled = 1; + /* Initialize the polling timer */ + init_timer(&kpp_dev.poll_timer); + + /* By default, devices should wakeup if they can */ + /* So keypad is set as "should wakeup" as it can */ + device_init_wakeup(&pdev->dev, 1); + + return 0; + + err3: + mxc_kpp_free_allocated(); + err2: + input_free_device(mxckbd_dev); + err1: + free_irq(irq, MOD_NAME); + clk_disable(kpp_clk); + clk_put(kpp_clk); + return retval; +} + +/*! + * Dissociates the driver from the kpp device. + * + * @param pdev the device structure used to give information on which SDHC + * to remove + * + * @return The function always returns 0. + */ +static int mxc_kpp_remove(struct platform_device *pdev) +{ + unsigned short reg_val; + + /* + * Clear the KPKD status flag (write 1 to it) and synchronizer chain. + * Set KDIE control bit, clear KRIE control bit (avoid false release + * events. Disable the keypad GPIO pins. + */ + __raw_writew(0x00, KPCR); + __raw_writew(0x00, KPDR); + __raw_writew(0x00, KDDR); + + reg_val = __raw_readw(KPSR); + reg_val |= KBD_STAT_KPKD; + reg_val &= ~KBD_STAT_KRSS; + reg_val |= KBD_STAT_KDIE; + reg_val &= ~KBD_STAT_KRIE; + __raw_writew(reg_val, KPSR); + + gpio_keypad_inactive(); + clk_disable(kpp_clk); + clk_put(kpp_clk); + + KPress = 0; + + del_timer(&kpp_dev.poll_timer); + + free_irq(keypad->irq, MOD_NAME); + input_unregister_device(mxckbd_dev); + + mxc_kpp_free_allocated(); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_kpd_driver = { + .driver = { + .name = "mxc_keypad", + .bus = &platform_bus_type, + }, + .suspend = mxc_kpp_suspend, + .resume = mxc_kpp_resume, + .probe = mxc_kpp_probe, + .remove = mxc_kpp_remove +}; + +/*! + * This function is called for module initialization. + * It registers keypad char driver and requests for KPP irq number. This + * function does the initialization of the keypad device. + * + * The following steps are used for keypad configuration,\n + * -# Enable number of rows in the keypad control register (KPCR[7:0}).\n + * -# Write 0's to KPDR[15:8]\n + * -# Configure keypad columns as open-drain (KPCR[15:8])\n + * -# Configure columns as output, rows as input (KDDR[15:0])\n + * -# Clear the KPKD status flag (write 1 to it) and synchronizer chain\n + * -# Set KDIE control bit, clear KRIE control bit\n + * In this function the keypad queue initialization is done. + * The keypad IOMUX configuration are done here.* + + * + * @return 0 on success and a non-zero value on failure. + */ +static int __init mxc_kpp_init(void) +{ + printk(KERN_INFO "MXC keypad loaded\n"); + platform_driver_register(&mxc_kpd_driver); + return 0; +} + +/*! + * This function is called whenever the module is removed from the kernel. It + * unregisters the keypad driver from kernel and frees the irq number. + * This function puts the keypad to standby mode. The keypad interrupts are + * disabled. It calls gpio_keypad_inactive function to switch gpio + * configuration into default state. + * + */ +static void __exit mxc_kpp_cleanup(void) +{ + platform_driver_unregister(&mxc_kpd_driver); +} + +module_init(mxc_kpp_init); +module_exit(mxc_kpp_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Keypad Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/keyboard/mxc_keyb.h b/drivers/input/keyboard/mxc_keyb.h new file mode 100644 index 000000000000..d231eef50088 --- /dev/null +++ b/drivers/input/keyboard/mxc_keyb.h @@ -0,0 +1,191 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup keypad Keypad Driver + */ + +/*! + * @file mxc_keyb.h + * + * @brief MXC keypad header file. + * + * @ingroup keypad + */ +#ifndef __MXC_KEYB_H__ +#define __MXC_KEYB_H__ + +/*! + * Keypad Module Name + */ +#define MOD_NAME "mxckpd" + +/*! + * Keypad irq number + */ +#define KPP_IRQ MXC_INT_KPP + +/*! + * XLATE mode selection + */ +#define KEYPAD_XLATE 0 + +/*! + * RAW mode selection + */ +#define KEYPAD_RAW 1 + +/*! + * Maximum number of keys. + */ +#define MAXROW 8 +#define MAXCOL 8 +#define MXC_MAXKEY (MAXROW * MAXCOL) + +/*! + * This define indicates break scancode for every key release. A constant + * of 128 is added to the key press scancode. + */ +#define MXC_KEYRELEASE 128 + +/* + * _reg_KPP_KPCR _reg_KPP_KPSR _reg_KPP_KDDR _reg_KPP_KPDR + * Keypad Control Register Address + */ +#define KPCR IO_ADDRESS(KPP_BASE_ADDR + 0x00) + +/* + * Keypad Status Register Address + */ +#define KPSR IO_ADDRESS(KPP_BASE_ADDR + 0x02) + +/* + * Keypad Data Direction Address + */ +#define KDDR IO_ADDRESS(KPP_BASE_ADDR + 0x04) + +/* + * Keypad Data Register + */ +#define KPDR IO_ADDRESS(KPP_BASE_ADDR + 0x06) + +/* + * Key Press Interrupt Status bit + */ +#define KBD_STAT_KPKD 0x01 + +/* + * Key Release Interrupt Status bit + */ +#define KBD_STAT_KPKR 0x02 + +/* + * Key Depress Synchronizer Chain Status bit + */ +#define KBD_STAT_KDSC 0x04 + +/* + * Key Release Synchronizer Status bit + */ +#define KBD_STAT_KRSS 0x08 + +/* + * Key Depress Interrupt Enable Status bit + */ +#define KBD_STAT_KDIE 0x100 + +/* + * Key Release Interrupt Enable + */ +#define KBD_STAT_KRIE 0x200 + +/* + * Keypad Clock Enable + */ +#define KBD_STAT_KPPEN 0x400 + +/*! + * Buffer size of keypad queue. Should be a power of 2. + */ +#define KPP_BUF_SIZE 128 + +/*! + * Test whether bit is set for integer c + */ +#define TEST_BIT(c, n) ((c) & (0x1 << (n))) + +/*! + * Set nth bit in the integer c + */ +#define BITSET(c, n) ((c) | (1 << (n))) + +/*! + * Reset nth bit in the integer c + */ +#define BITRESET(c, n) ((c) & ~(1 << (n))) + +/*! + * This enum represents the keypad state machine to maintain debounce logic + * for key press/release. + */ +enum KeyState { + + /*! + * Key press state. + */ + KStateUp, + + /*! + * Key press debounce state. + */ + KStateFirstDown, + + /*! + * Key release state. + */ + KStateDown, + + /*! + * Key release debounce state. + */ + KStateFirstUp +}; + +/*! + * Keypad Private Data Structure + */ +typedef struct keypad_priv { + + /*! + * Keypad state machine. + */ + enum KeyState iKeyState; + + /*! + * Number of rows configured in the keypad matrix + */ + unsigned long kpp_rows; + + /*! + * Number of Columns configured in the keypad matrix + */ + unsigned long kpp_cols; + + /*! + * Timer used for Keypad polling. + */ + struct timer_list poll_timer; + +} keypad_priv; + +#endif /* __MXC_KEYB_H__ */ diff --git a/drivers/input/keyboard/stmp3xxx-kbd.c b/drivers/input/keyboard/stmp3xxx-kbd.c new file mode 100644 index 000000000000..7dcf6987e750 --- /dev/null +++ b/drivers/input/keyboard/stmp3xxx-kbd.c @@ -0,0 +1,302 @@ +/* + * Keypad ladder driver for Freescale STMP37XX/STMP378X boards + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <mach/regs-lradc.h> +#include <mach/lradc.h> +#include <mach/stmp3xxx.h> +#include <mach/platform.h> + +#define BUTTON_PRESS_THRESHOLD 3300 +#define LRADC_NOISE_MARGIN 100 + +/* this value represents the the lradc value at 3.3V ( 3.3V / 0.000879 V/b ) */ +#define TARGET_VDDIO_LRADC_VALUE 3754 + +struct stmpkbd_data { + struct input_dev *input; + int last_button; + int irq; + struct stmpkbd_keypair *keycodes; +}; + +static int delay1 = 500; +static int delay2 = 200; + +static int stmpkbd_open(struct input_dev *dev); +static void stmpkbd_close(struct input_dev *dev); + +static struct stmpkbd_data *stmpkbd_data_alloc(struct platform_device *pdev, + struct stmpkbd_keypair *keys) +{ + struct stmpkbd_data *d = kzalloc(sizeof(*d), GFP_KERNEL); + + if (!d) + return NULL; + + if (!keys) { + dev_err(&pdev->dev, + "No keycodes in platform_data, bailing out.\n"); + kfree(d); + return NULL; + } + d->keycodes = keys; + + d->input = input_allocate_device(); + if (!d->input) { + kfree(d); + return NULL; + } + + d->input->phys = "onboard"; + d->input->uniq = "0000'0000"; + d->input->name = pdev->name; + d->input->id.bustype = BUS_HOST; + d->input->open = stmpkbd_open; + d->input->close = stmpkbd_close; + d->input->dev.parent = &pdev->dev; + + set_bit(EV_KEY, d->input->evbit); + set_bit(EV_REL, d->input->evbit); + set_bit(EV_REP, d->input->evbit); + + + d->last_button = -1; + + while (keys->raw >= 0) { + set_bit(keys->kcode, d->input->keybit); + keys++; + } + + return d; +} + +static inline struct input_dev *GET_INPUT_DEV(struct stmpkbd_data *d) +{ + BUG_ON(!d); + return d->input; +} + +static void stmpkbd_data_free(struct stmpkbd_data *d) +{ + if (!d) + return; + if (d->input) + input_free_device(d->input); + kfree(d); +} + +static unsigned stmpkbd_decode_button(struct stmpkbd_keypair *codes, + int raw_button) + +{ + pr_debug("Decoding %d\n", raw_button); + while (codes->raw != -1) { + if ((raw_button > (codes->raw - LRADC_NOISE_MARGIN)) && + (raw_button < (codes->raw + LRADC_NOISE_MARGIN))) { + pr_debug("matches code 0x%x = %d\n", + codes->kcode, codes->kcode); + return codes->kcode; + } + codes++; + } + return (unsigned)-1; /* invalid key */ +} + + +static irqreturn_t stmpkbd_irq_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct stmpkbd_data *devdata = platform_get_drvdata(pdev); + u16 raw_button, normalized_button, vddio; + unsigned btn; + + raw_button = __raw_readl(REGS_LRADC_BASE + + HW_LRADC_CHn(LRADC_CH0)) & BM_LRADC_CHn_VALUE; + vddio = hw_lradc_vddio(); + BUG_ON(vddio == 0); + + normalized_button = (raw_button * TARGET_VDDIO_LRADC_VALUE) / + vddio; + + if (normalized_button < BUTTON_PRESS_THRESHOLD && + devdata->last_button < 0) { + + btn = stmpkbd_decode_button(devdata->keycodes, + normalized_button); + + if (btn < KEY_MAX) { + devdata->last_button = btn; + input_report_key(GET_INPUT_DEV(devdata), + devdata->last_button, !0); + } else + dev_err(&pdev->dev, "Invalid button: raw = %d, " + "normalized = %d, vddio = %d\n", + raw_button, normalized_button, vddio); + } else if (devdata->last_button > 0 && + normalized_button >= BUTTON_PRESS_THRESHOLD) { + + input_report_key(GET_INPUT_DEV(devdata), + devdata->last_button, 0); + devdata->last_button = -1; + + } + + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC0_IRQ, REGS_LRADC_BASE + HW_LRADC_CTRL1); + return IRQ_HANDLED; +} + +static int stmpkbd_open(struct input_dev *dev) +{ + /* enable clock */ + return 0; +} + +static void stmpkbd_close(struct input_dev *dev) +{ + /* disable clock */ +} + +static void stmpkbd_hwinit(struct platform_device *pdev) +{ + hw_lradc_init_ladder(LRADC_CH0, LRADC_DELAY_TRIGGER_BUTTON, 200); + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC0_IRQ, REGS_LRADC_BASE + HW_LRADC_CTRL1); + stmp3xxx_setl(BM_LRADC_CTRL1_LRADC0_IRQ_EN, REGS_LRADC_BASE + HW_LRADC_CTRL1); + hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_BUTTON, !0); +} + +static int stmpkbd_suspend(struct platform_device *pdev, pm_message_t state) +{ +#ifdef CONFIG_PM + struct input_dev *idev = platform_get_drvdata(pdev); + + hw_lradc_stop_ladder(LRADC_CH0, LRADC_DELAY_TRIGGER_BUTTON); + hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_BUTTON, 0); + hw_lradc_unuse_channel(LRADC_CH0); + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC0_IRQ_EN, REGS_LRADC_BASE + HW_LRADC_CTRL1); + stmpkbd_close(idev); +#endif + return 0; +} + +static int stmpkbd_resume(struct platform_device *pdev) +{ +#ifdef CONFIG_PM + struct input_dev *idev = platform_get_drvdata(pdev); + + stmp3xxx_setl(BM_LRADC_CTRL1_LRADC0_IRQ_EN, REGS_LRADC_BASE + HW_LRADC_CTRL1); + stmpkbd_open(idev); + hw_lradc_use_channel(LRADC_CH0); + stmpkbd_hwinit(pdev); +#endif + return 0; +} + +static int __devinit stmpkbd_probe(struct platform_device *pdev) +{ + int err = 0; + int irq = platform_get_irq(pdev, 0); + struct stmpkbd_data *d; + + /* Create and register the input driver. */ + d = stmpkbd_data_alloc(pdev, + (struct stmpkbd_keypair *)pdev->dev.platform_data); + if (!d) { + dev_err(&pdev->dev, "Cannot allocate driver structures\n"); + err = -ENOMEM; + goto err_out; + } + + d->irq = irq; + err = request_irq(irq, stmpkbd_irq_handler, + IRQF_DISABLED, pdev->name, pdev); + if (err) { + dev_err(&pdev->dev, "Cannot request keypad IRQ\n"); + goto err_free_dev; + } + + platform_set_drvdata(pdev, d); + + /* Register the input device */ + err = input_register_device(GET_INPUT_DEV(d)); + if (err) + goto err_free_irq; + + /* these two have to be set after registering the input device */ + d->input->rep[REP_DELAY] = delay1; + d->input->rep[REP_PERIOD] = delay2; + + hw_lradc_use_channel(LRADC_CH0); + stmpkbd_hwinit(pdev); + + return 0; + +err_free_irq: + platform_set_drvdata(pdev, NULL); + free_irq(irq, pdev); +err_free_dev: + stmpkbd_data_free(d); +err_out: + return err; +} + +static int __devexit stmpkbd_remove(struct platform_device *pdev) +{ + struct stmpkbd_data *d = platform_get_drvdata(pdev); + + hw_lradc_unuse_channel(LRADC_CH0); + input_unregister_device(GET_INPUT_DEV(d)); + free_irq(d->irq, pdev); + stmpkbd_data_free(d); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver stmpkbd_driver = { + .probe = stmpkbd_probe, + .remove = __devexit_p(stmpkbd_remove), + .suspend = stmpkbd_suspend, + .resume = stmpkbd_resume, + .driver = { + .name = "stmp3xxx-keyboard", + }, +}; + +static int __init stmpkbd_init(void) +{ + return platform_driver_register(&stmpkbd_driver); +} + +static void __exit stmpkbd_exit(void) +{ + platform_driver_unregister(&stmpkbd_driver); +} + +module_init(stmpkbd_init); +module_exit(stmpkbd_exit); +MODULE_DESCRIPTION("Freescale STMP3xxxx keyboard driver"); +MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 1acfa3a05aad..afee839ba338 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -40,6 +40,15 @@ config INPUT_M68K_BEEP tristate "M68k Beeper support" depends on M68K +config INPUT_STMP3XXX_ROTDEC + tristate "STMP3xxx Rotary Decoder support" + depends on MACH_STMP378X + select INPUT_POLLDEV + help + Say Y here for support for the rotary decoder on STMP3xxx + + If compiled as a module, it will be called stmp3xxx_rotdec + config INPUT_APANEL tristate "Fujitsu Lifebook Application Panel buttons" depends on X86 && I2C && LEDS_CLASS diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 0d979fd4cd57..0cb5e795cfb4 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -26,3 +26,4 @@ obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o obj-$(CONFIG_INPUT_UINPUT) += uinput.o obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o obj-$(CONFIG_INPUT_YEALINK) += yealink.o +obj-$(CONFIG_INPUT_STMP3XXX_ROTDEC) += stmp3xxx_rotdec.o diff --git a/drivers/input/misc/stmp3xxx_rotdec.c b/drivers/input/misc/stmp3xxx_rotdec.c new file mode 100644 index 000000000000..310313eea64f --- /dev/null +++ b/drivers/input/misc/stmp3xxx_rotdec.c @@ -0,0 +1,172 @@ +/* + * Freescale STMP3XXX Rotary Encoder Driver + * + * Author: Drew Benedetti <drewb@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/input-polldev.h> +#include <mach/regs-timrot.h> +#include <mach/rotdec.h> +#include <mach/platform.h> + +static int relative; +static unsigned int poll_interval = 500; + +void stmp3xxx_rotdec_flush(struct input_polled_dev *dev) +{ + /* in relative mode, reading the counter resets it */ + if (relative) + __raw_readl(REGS_TIMROT_BASE + HW_TIMROT_ROTCOUNT); +} + +void stmp3xxx_rotdec_poll(struct input_polled_dev *dev) +{ + s16 cnt = __raw_readl(REGS_TIMROT_BASE + HW_TIMROT_ROTCOUNT) & BM_TIMROT_ROTCOUNT_UPDOWN; + if (relative) + input_report_rel(dev->input, REL_WHEEL, cnt); + else + input_report_abs(dev->input, ABS_WHEEL, cnt); +} + +struct input_polled_dev *rotdec; +static u32 rotctrl; + +static int stmp3xxx_rotdec_probe(struct platform_device *pdev) +{ + int rc = 0; + + /* save original state of HW_TIMROT_ROTCTRL */ + rotctrl = __raw_readl(REGS_TIMROT_BASE + HW_TIMROT_ROTCTRL); + + if (!(rotctrl & BM_TIMROT_ROTCTRL_ROTARY_PRESENT)) { + dev_info(&pdev->dev, "No rotary decoder present\n"); + rc = -ENODEV; + goto err_rotdec_present; + } else { + /* I had to add some extra line breaks in here + * to avoid lines >80 chars wide + */ + __raw_writel( + BF(0x0, TIMROT_ROTCTRL_DIVIDER) | /* 32kHz divider - 1 */ + BF(BV_TIMROT_ROTCTRL_OVERSAMPLE__2X, + TIMROT_ROTCTRL_OVERSAMPLE) | + BF(BV_TIMROT_ROTCTRL_SELECT_B__ROTARYB, + TIMROT_ROTCTRL_SELECT_B) | + BF(BV_TIMROT_ROTCTRL_SELECT_A__ROTARYA, + TIMROT_ROTCTRL_SELECT_A) + , REGS_TIMROT_BASE + HW_TIMROT_ROTCTRL); + stmp3xxx_clearl( + BM_TIMROT_ROTCTRL_POLARITY_B | + BM_TIMROT_ROTCTRL_POLARITY_A + , REGS_TIMROT_BASE + HW_TIMROT_ROTCTRL); + + if (relative) + stmp3xxx_setl(BM_TIMROT_ROTCTRL_RELATIVE, REGS_TIMROT_BASE + HW_TIMROT_ROTCTRL); + else + stmp3xxx_clearl(BM_TIMROT_ROTCTRL_RELATIVE, REGS_TIMROT_BASE + HW_TIMROT_ROTCTRL); + + rc = rotdec_pinmux_request(); + if (rc) { + dev_err(&pdev->dev, + "Pin request failed (err=%d)\n", rc); + goto err_pinmux; + } + + /* set up input_polled_dev */ + rotdec = input_allocate_polled_device(); + if (!rotdec) { + dev_err(&pdev->dev, + "Unable to allocate polled device\n"); + rc = -ENOMEM; + goto err_alloc_polldev; + } + rotdec->flush = stmp3xxx_rotdec_flush; + rotdec->poll = stmp3xxx_rotdec_poll; + rotdec->poll_interval = poll_interval; /* msec */ + + rotdec->input->name = "stmp3xxx-rotdec"; + if (relative) + input_set_capability(rotdec->input, EV_REL, REL_WHEEL); + else { + input_set_capability(rotdec->input, EV_ABS, ABS_WHEEL); + input_set_abs_params(rotdec->input, ABS_WHEEL, + -32768, 32767, 0, 0); + } + + rc = input_register_polled_device(rotdec); + if (rc) { + dev_err(&pdev->dev, + "Unable to register rotary decoder (err=%d)\n", + rc); + goto err_reg_polldev; + } + } + + return 0; + +err_reg_polldev: + input_free_polled_device(rotdec); +err_alloc_polldev: + rotdec_pinmux_free(); +err_pinmux: + /* restore original register state */ + __raw_writel(rotctrl, REGS_TIMROT_BASE + HW_TIMROT_ROTCTRL); + +err_rotdec_present: + return rc; +} + +static int stmp3xxx_rotdec_remove(struct platform_device *pdev) +{ + input_unregister_polled_device(rotdec); + input_free_polled_device(rotdec); + + rotdec_pinmux_free(); + + /* restore original register state */ + __raw_writel(rotctrl, REGS_TIMROT_BASE + HW_TIMROT_ROTCTRL); + + return 0; +} + +static struct platform_driver stmp3xxx_rotdec_driver = { + .probe = stmp3xxx_rotdec_probe, + .remove = stmp3xxx_rotdec_remove, + .driver = { + .name = "stmp3xxx-rotdec", + }, +}; + +static int __init stmp3xxx_rotdec_init(void) +{ + return platform_driver_register(&stmp3xxx_rotdec_driver); +} + +static void __exit stmp3xxx_rotdec_exit(void) +{ + platform_driver_unregister(&stmp3xxx_rotdec_driver); +} + +module_init(stmp3xxx_rotdec_init); +module_exit(stmp3xxx_rotdec_exit); + +module_param(relative, bool, 0600); +module_param(poll_interval, uint, 0600); + +MODULE_AUTHOR("Drew Benedetti <drewb@embeddedalley.com>"); +MODULE_DESCRIPTION("STMP3xxx rotary decoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 72e2712c7e2a..8046d3402298 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -225,6 +225,38 @@ config TOUCHSCREEN_HP7XX To compile this driver as a module, choose M here: the module will be called jornada720_ts. +config TOUCHSCREEN_MXC + tristate "MXC touchscreen input driver" + depends on MXC_MC13783_ADC || MXC_MC13892_ADC + help + Say Y here if you have an MXC based board with touchscreen + attached to it. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called mxc_ts. + +config TOUCHSCREEN_IMX_ADC + tristate "Freescale i.MX ADC touchscreen input driver" + depends on IMX_ADC + help + Say Y here if you have a Freescale i.MX based board with a + touchscreen interfaced to the processor's integrated ADC. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called imx_adc_ts. + +config TOUCHSCREEN_STMP3XXX + tristate "STMP3XXX LRADC-based touchscreen" + depends on ARCH_STMP3XXX + select SERIO + help + Say Y here if you want to enable TMP3XXX LRADC-based touchscreen. + module will be called stmp3xxx_ts. + config TOUCHSCREEN_HTCPEN tristate "HTC Shift X9500 touchscreen" depends on ISA diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 3e1c5e0b952f..bbe22e707e69 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -23,6 +23,9 @@ obj-$(CONFIG_TOUCHSCREEN_MK712) += mk712.o obj-$(CONFIG_TOUCHSCREEN_HP600) += hp680_ts_input.o obj-$(CONFIG_TOUCHSCREEN_HP7XX) += jornada720_ts.o obj-$(CONFIG_TOUCHSCREEN_HTCPEN) += htcpen.o +obj-$(CONFIG_TOUCHSCREEN_MXC) += mxc_ts.o +obj-$(CONFIG_TOUCHSCREEN_IMX_ADC) += imx_adc_ts.o +obj-$(CONFIG_TOUCHSCREEN_STMP3XXX) += stmp3xxx_ts.o obj-$(CONFIG_TOUCHSCREEN_USB_COMPOSITE) += usbtouchscreen.o obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o diff --git a/drivers/input/touchscreen/imx_adc_ts.c b/drivers/input/touchscreen/imx_adc_ts.c new file mode 100644 index 000000000000..ec43a16213ae --- /dev/null +++ b/drivers/input/touchscreen/imx_adc_ts.c @@ -0,0 +1,114 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file imx_adc_ts.c + * + * @brief Driver for the Freescale Semiconductor i.MX ADC touchscreen. + * + * This touchscreen driver is designed as a standard input driver. It is a + * wrapper around the low level ADC driver. Much of the hardware configuration + * and touchscreen functionality is implemented in the low level ADC driver. + * During initialization, this driver creates a kernel thread. This thread + * then calls the ADC driver to obtain touchscreen values continously. These + * values are then passed to the input susbsystem. + * + * @ingroup touchscreen + */ + +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/freezer.h> +#include <linux/imx_adc.h> + +#define IMX_ADC_TS_NAME "imx_adc_ts" + +static struct input_dev *imx_inputdev; +static u32 input_ts_installed; + +static int ts_thread(void *arg) +{ + struct t_touch_screen ts_sample; + int wait = 0; + daemonize("imx_adc_ts"); + while (input_ts_installed) { + try_to_freeze(); + + memset(&ts_sample, 0, sizeof(ts_sample)); + if (0 != imx_adc_get_touch_sample(&ts_sample, !wait)) + continue; + + input_report_abs(imx_inputdev, ABS_X, ts_sample.x_position); + input_report_abs(imx_inputdev, ABS_Y, ts_sample.y_position); + input_report_abs(imx_inputdev, ABS_PRESSURE, + ts_sample.contact_resistance); + input_sync(imx_inputdev); + wait = ts_sample.contact_resistance; + msleep(10); + } + + return 0; +} + +static int __init imx_adc_ts_init(void) +{ + int retval; + + if (!is_imx_adc_ready()) + return -ENODEV; + + imx_inputdev = input_allocate_device(); + if (!imx_inputdev) { + pr_err("imx_ts_init: not enough memory for input device\n"); + return -ENOMEM; + } + + imx_inputdev->name = IMX_ADC_TS_NAME; + imx_inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + imx_inputdev->keybit[BIT_WORD(BTN_TOUCH)] |= BIT_MASK(BTN_TOUCH); + imx_inputdev->absbit[0] = + BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | BIT_MASK(ABS_PRESSURE); + retval = input_register_device(imx_inputdev); + if (retval < 0) { + input_free_device(imx_inputdev); + return retval; + } + + input_ts_installed = 1; + kthread_run(ts_thread, NULL, "ts_thread"); + pr_info("i.MX ADC input touchscreen loaded.\n"); + return 0; +} + +static void __exit imx_adc_ts_exit(void) +{ + input_ts_installed = 0; + input_unregister_device(imx_inputdev); + + if (imx_inputdev) { + input_free_device(imx_inputdev); + imx_inputdev = NULL; + } +} + +late_initcall(imx_adc_ts_init); +module_exit(imx_adc_ts_exit); + +MODULE_DESCRIPTION("i.MX ADC input touchscreen driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/mxc_ts.c b/drivers/input/touchscreen/mxc_ts.c new file mode 100644 index 000000000000..b354d81c5465 --- /dev/null +++ b/drivers/input/touchscreen/mxc_ts.c @@ -0,0 +1,118 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_ts.c + * + * @brief Driver for the Freescale Semiconductor MXC touchscreen. + * + * The touchscreen driver is designed as a standard input driver which is a + * wrapper over low level PMIC driver. Most of the hardware configuration and + * touchscreen functionality is implemented in the low level PMIC driver. During + * initialization, this driver creates a kernel thread. This thread then calls + * PMIC driver to obtain touchscreen values continously. These values are then + * passed to the input susbsystem. + * + * @ingroup touchscreen + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/freezer.h> +#include <linux/pmic_external.h> +#include <linux/pmic_adc.h> + +#define MXC_TS_NAME "mxc_ts" + +static struct input_dev *mxc_inputdev = NULL; +static u32 input_ts_installed; + +static int ts_thread(void *arg) +{ + t_touch_screen ts_sample; + s32 wait = 0; + + daemonize("mxc_ts"); + while (input_ts_installed) { + try_to_freeze(); + memset(&ts_sample, 0, sizeof(t_touch_screen)); + if (0 != pmic_adc_get_touch_sample(&ts_sample, !wait)) + continue; + if (!(ts_sample.contact_resistance || wait)) + continue; + + input_report_abs(mxc_inputdev, ABS_X, ts_sample.x_position); + input_report_abs(mxc_inputdev, ABS_Y, ts_sample.y_position); + input_report_abs(mxc_inputdev, ABS_PRESSURE, + ts_sample.contact_resistance); + input_sync(mxc_inputdev); + + wait = ts_sample.contact_resistance; + msleep(20); + } + + return 0; +} + +static int __init mxc_ts_init(void) +{ + int retval; + + if (!is_pmic_adc_ready()) + return -ENODEV; + + mxc_inputdev = input_allocate_device(); + if (!mxc_inputdev) { + printk(KERN_ERR + "mxc_ts_init: not enough memory for input device\n"); + return -ENOMEM; + } + + mxc_inputdev->name = MXC_TS_NAME; + mxc_inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + mxc_inputdev->keybit[BIT_WORD(BTN_TOUCH)] |= BIT_MASK(BTN_TOUCH); + mxc_inputdev->absbit[0] = + BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | BIT_MASK(ABS_PRESSURE); + retval = input_register_device(mxc_inputdev); + if (retval < 0) { + input_free_device(mxc_inputdev); + return retval; + } + + input_ts_installed = 1; + kernel_thread(ts_thread, NULL, CLONE_VM | CLONE_FS); + printk("mxc input touchscreen loaded\n"); + return 0; +} + +static void __exit mxc_ts_exit(void) +{ + input_ts_installed = 0; + input_unregister_device(mxc_inputdev); + + if (mxc_inputdev) { + input_free_device(mxc_inputdev); + mxc_inputdev = NULL; + } +} + +late_initcall(mxc_ts_init); +module_exit(mxc_ts_exit); + +MODULE_DESCRIPTION("MXC input touchscreen driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/stmp3xxx_ts.c b/drivers/input/touchscreen/stmp3xxx_ts.c new file mode 100644 index 000000000000..dcd9e7cc1712 --- /dev/null +++ b/drivers/input/touchscreen/stmp3xxx_ts.c @@ -0,0 +1,387 @@ +/* + * Freesclae STMP37XX/STMP378X Touchscreen driver + * + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* #define DEBUG*/ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/input.h> +#include <linux/interrupt.h> + +#include <mach/lradc.h> +#include <mach/hardware.h> +#include <mach/platform.h> +#include <mach/regs-lradc.h> + +#define TOUCH_DEBOUNCE_TOLERANCE 100 + +struct stmp3xxx_ts_info { + int touch_irq; + int device_irq; + struct input_dev *idev; + enum { + TS_STATE_DISABLED, + TS_STATE_TOUCH_DETECT, + TS_STATE_TOUCH_VERIFY, + TS_STATE_X_PLANE, + TS_STATE_Y_PLANE, + } state; + u16 x; + u16 y; + int sample_count; +}; + +static inline void enter_state_touch_detect(struct stmp3xxx_ts_info *info) +{ + stmp3xxx_clearl(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn(2)); + stmp3xxx_clearl(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn(3)); + stmp3xxx_clearl(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn(4)); + stmp3xxx_clearl(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn(5)); + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC5_IRQ, REGS_LRADC_BASE + HW_LRADC_CTRL1); + stmp3xxx_clearl(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ, REGS_LRADC_BASE + HW_LRADC_CTRL1); + /* + * turn off the yplus and yminus pullup and pulldown, and turn off touch + * detect (enables yminus, and xplus through a resistor.On a press, + * xplus is pulled down) + */ + stmp3xxx_clearl(BM_LRADC_CTRL0_YMINUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_YPLUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_XMINUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_XPLUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_setl(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + + hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 0); + info->state = TS_STATE_TOUCH_DETECT; + info->sample_count = 0; +} + +static inline void enter_state_disabled(struct stmp3xxx_ts_info *info) +{ + stmp3xxx_clearl(BM_LRADC_CTRL0_YMINUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_YPLUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_XMINUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_XPLUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + + hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 0); + info->state = TS_STATE_DISABLED; + info->sample_count = 0; +} + + +static inline void enter_state_x_plane(struct stmp3xxx_ts_info *info) +{ + stmp3xxx_setl(BM_LRADC_CTRL0_YMINUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_setl(BM_LRADC_CTRL0_YPLUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_XMINUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_XPLUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + + hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1); + + info->state = TS_STATE_X_PLANE; + info->sample_count = 0; +} + +static inline void enter_state_y_plane(struct stmp3xxx_ts_info *info) +{ + stmp3xxx_clearl(BM_LRADC_CTRL0_YMINUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_YPLUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_setl(BM_LRADC_CTRL0_XMINUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_setl(BM_LRADC_CTRL0_XPLUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + + hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1); + info->state = TS_STATE_Y_PLANE; + info->sample_count = 0; +} + +static inline void enter_state_touch_verify(struct stmp3xxx_ts_info *info) +{ + stmp3xxx_clearl(BM_LRADC_CTRL0_YMINUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_YPLUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_XMINUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_clearl(BM_LRADC_CTRL0_XPLUS_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + stmp3xxx_setl(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE, REGS_LRADC_BASE + HW_LRADC_CTRL0); + + info->state = TS_STATE_TOUCH_VERIFY; + hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1); + info->sample_count = 0; +} + +static void process_lradc(struct stmp3xxx_ts_info *info, u16 x, u16 y, + int pressure) +{ + switch (info->state) { + case TS_STATE_X_PLANE: + pr_debug("%s: x plane state, sample_count %d\n", __func__, + info->sample_count); + if (info->sample_count < 2) { + info->x = x; + info->sample_count++; + } else { + if (abs(info->x - x) > TOUCH_DEBOUNCE_TOLERANCE) + info->sample_count = 1; + else { + u16 x_c = info->x * (info->sample_count - 1); + info->x = (x_c + x) / info->sample_count; + info->sample_count++; + } + } + if (info->sample_count > 4) + enter_state_y_plane(info); + else + hw_lradc_set_delay_trigger_kick( + LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1); + break; + + case TS_STATE_Y_PLANE: + pr_debug("%s: y plane state, sample_count %d\n", __func__, + info->sample_count); + if (info->sample_count < 2) { + info->y = y; + info->sample_count++; + } else { + if (abs(info->y - y) > TOUCH_DEBOUNCE_TOLERANCE) + info->sample_count = 1; + else { + u16 y_c = info->y * (info->sample_count - 1); + info->y = (y_c + y) / info->sample_count; + info->sample_count++; + } + } + if (info->sample_count > 4) + enter_state_touch_verify(info); + else + hw_lradc_set_delay_trigger_kick( + LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1); + break; + + case TS_STATE_TOUCH_VERIFY: + pr_debug("%s: touch verify state, sample_count %d\n", __func__, + info->sample_count); + pr_debug("%s: x %d, y %d\n", __func__, info->x, info->y); + input_report_abs(info->idev, ABS_X, info->x); + input_report_abs(info->idev, ABS_Y, info->y); + input_report_abs(info->idev, ABS_PRESSURE, pressure); + input_sync(info->idev); + /* fall through */ + case TS_STATE_TOUCH_DETECT: + pr_debug("%s: touch detect state, sample_count %d\n", __func__, + info->sample_count); + if (pressure) { + input_report_abs(info->idev, ABS_PRESSURE, pressure); + enter_state_x_plane(info); + hw_lradc_set_delay_trigger_kick( + LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1); + } else + enter_state_touch_detect(info); + break; + + default: + printk(KERN_ERR "%s: unknown touchscreen state %d\n", __func__, + info->state); + } +} + +static irqreturn_t ts_handler(int irq, void *dev_id) +{ + struct stmp3xxx_ts_info *info = dev_id; + u16 x_plus, y_plus; + int pressure = 0; + + if (irq == info->touch_irq) + stmp3xxx_clearl(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ, REGS_LRADC_BASE + HW_LRADC_CTRL1); + else if (irq == info->device_irq) + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC5_IRQ, REGS_LRADC_BASE + HW_LRADC_CTRL1); + + /* get x, y values */ + x_plus = __raw_readl(REGS_LRADC_BASE + HW_LRADC_CHn(LRADC_TOUCH_X_PLUS)) & BM_LRADC_CHn_VALUE; + y_plus = __raw_readl(REGS_LRADC_BASE + HW_LRADC_CHn(LRADC_TOUCH_Y_PLUS)) & BM_LRADC_CHn_VALUE; + + /* pressed? */ + if (__raw_readl(REGS_LRADC_BASE + HW_LRADC_STATUS) & BM_LRADC_STATUS_TOUCH_DETECT_RAW) + pressure = 1; + + pr_debug("%s: irq %d, x_plus %d, y_plus %d, pressure %d\n", + __func__, irq, x_plus, y_plus, pressure); + + process_lradc(info, x_plus, y_plus, pressure); + + return IRQ_HANDLED; +} + +static int stmp3xxx_ts_probe(struct platform_device *pdev) +{ + struct input_dev *idev; + struct stmp3xxx_ts_info *info; + int ret = 0; + struct resource *res; + + idev = input_allocate_device(); + info = kzalloc(sizeof(struct stmp3xxx_ts_info), GFP_KERNEL); + if (idev == NULL || info == NULL) { + ret = -ENOMEM; + goto out_nomem; + } + + idev->name = "STMP3XXX touchscreen"; + idev->evbit[0] = BIT(EV_ABS); + input_set_abs_params(idev, ABS_X, 0, 0xFFF, 0, 0); + input_set_abs_params(idev, ABS_Y, 0, 0xFFF, 0, 0); + input_set_abs_params(idev, ABS_PRESSURE, 0, 1, 0, 0); + + ret = input_register_device(idev); + if (ret) + goto out_nomem; + + info->idev = idev; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + printk(KERN_ERR "%s: couldn't get IRQ resource\n", __func__); + ret = -ENODEV; + goto out_nodev; + } + info->touch_irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 1); + if (!res) { + printk(KERN_ERR "%s: couldn't get IRQ resource\n", __func__); + ret = -ENODEV; + goto out_nodev; + } + info->device_irq = res->start; + + ret = request_irq(info->touch_irq, ts_handler, IRQF_DISABLED, + "stmp3xxx_ts_touch", info); + if (ret) + goto out_nodev; + + ret = request_irq(info->device_irq, ts_handler, IRQF_DISABLED, + "stmp3xxx_ts_dev", info); + if (ret) { + free_irq(info->touch_irq, info); + goto out_nodev; + } + enter_state_touch_detect(info); + + hw_lradc_use_channel(LRADC_CH2); + hw_lradc_use_channel(LRADC_CH3); + hw_lradc_use_channel(LRADC_CH5); + hw_lradc_configure_channel(LRADC_CH2, 0, 0, 0); + hw_lradc_configure_channel(LRADC_CH3, 0, 0, 0); + hw_lradc_configure_channel(LRADC_CH5, 0, 0, 0); + + /* Clear the accumulator & NUM_SAMPLES for the channels */ + stmp3xxx_clearl(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn(LRADC_CH2)); + stmp3xxx_clearl(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn(LRADC_CH3)); + stmp3xxx_clearl(0xFFFFFFFF, REGS_LRADC_BASE + HW_LRADC_CHn(LRADC_CH5)); + + hw_lradc_set_delay_trigger(LRADC_DELAY_TRIGGER_TOUCHSCREEN, + 0x3c, 0, 0, 8); + + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC5_IRQ, REGS_LRADC_BASE + HW_LRADC_CTRL1); + stmp3xxx_clearl(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ, REGS_LRADC_BASE + HW_LRADC_CTRL1); + + stmp3xxx_setl(BM_LRADC_CTRL1_LRADC5_IRQ_EN, REGS_LRADC_BASE + HW_LRADC_CTRL1); + stmp3xxx_setl(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, REGS_LRADC_BASE + HW_LRADC_CTRL1); + + platform_set_drvdata(pdev, info); + device_init_wakeup(&pdev->dev, 1); + goto out; + +out_nodev: + input_free_device(idev); +out_nomem: + kfree(idev); + kfree(info); +out: + return ret; +} + +static int stmp3xxx_ts_remove(struct platform_device *pdev) +{ + struct stmp3xxx_ts_info *info = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + + hw_lradc_unuse_channel(LRADC_CH2); + hw_lradc_unuse_channel(LRADC_CH3); + hw_lradc_unuse_channel(LRADC_CH5); + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC5_IRQ_EN, REGS_LRADC_BASE + HW_LRADC_CTRL1); + stmp3xxx_clearl(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, REGS_LRADC_BASE + HW_LRADC_CTRL1); + + free_irq(info->device_irq, info); + free_irq(info->touch_irq, info); + input_free_device(info->idev); + + enter_state_disabled(info); + kfree(info->idev); + kfree(info); + return 0; +} + +static int stmp3xxx_ts_suspend(struct platform_device *pdev, + pm_message_t state) +{ +#ifdef CONFIG_PM + if (!device_may_wakeup(&pdev->dev)) { + hw_lradc_unuse_channel(LRADC_CH2); + hw_lradc_unuse_channel(LRADC_CH3); + hw_lradc_unuse_channel(LRADC_CH5); + } +#endif + return 0; +} + +static int stmp3xxx_ts_resume(struct platform_device *pdev) +{ +#ifdef CONFIG_PM + if (!device_may_wakeup(&pdev->dev)) { + hw_lradc_use_channel(LRADC_CH2); + hw_lradc_use_channel(LRADC_CH3); + hw_lradc_use_channel(LRADC_CH5); + } +#endif + return 0; +} + +static struct platform_driver stmp3xxx_ts_driver = { + .probe = stmp3xxx_ts_probe, + .remove = stmp3xxx_ts_remove, + .suspend = stmp3xxx_ts_suspend, + .resume = stmp3xxx_ts_resume, + .driver = { + .name = "stmp3xxx_ts", + }, +}; + +static int __init stmp3xxx_ts_init(void) +{ + return platform_driver_register(&stmp3xxx_ts_driver); +} + +static void __exit stmp3xxx_ts_exit(void) +{ + platform_driver_unregister(&stmp3xxx_ts_driver); +} + +module_init(stmp3xxx_ts_init); +module_exit(stmp3xxx_ts_exit); diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 7c8e7122aaa9..f5dea97da61f 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -17,6 +17,14 @@ config LEDS_CLASS comment "LED drivers" +config LEDS_STMP378X + tristate "Support for PWM LEDs on STMP378X" + depends on LEDS_CLASS && MACH_STMP378X + help + This option enables support for the LEDs connected to PWM + outputs on the Freescale STMP378X. + + config LEDS_ATMEL_PWM tristate "LED Support using Atmel PWM outputs" depends on LEDS_CLASS && ATMEL_PWM @@ -24,6 +32,10 @@ config LEDS_ATMEL_PWM This option enables support for LEDs driven using outputs of the dedicated PWM controller found on newer Atmel SOCs. +config LEDS_MC13892 + tristate "LED Support for mc13892 pmic" + depends on LEDS_CLASS && MXC_MC13892_LIGHT + config LEDS_LOCOMO tristate "LED Support for Locomo device" depends on LEDS_CLASS && SHARP_LOCOMO diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index e8cdcf77a4c3..b830a51c62f7 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -7,6 +7,8 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o # LED Platform Drivers obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o +obj-$(CONFIG_LEDS_MC13892) += leds-mc13892.o +obj-$(CONFIG_LEDS_STMP378X) += leds-stmp378x-pwm.o obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o diff --git a/drivers/leds/leds-mc13892.c b/drivers/leds/leds-mc13892.c new file mode 100644 index 000000000000..9edd20446235 --- /dev/null +++ b/drivers/leds/leds-mc13892.c @@ -0,0 +1,152 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/pmic_light.h> + +static void mc13892_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct platform_device *dev = to_platform_device(led_cdev->dev->parent); + int led_ch; + + switch (dev->id) { + case 'r': + led_ch = LIT_RED; + break; + case 'g': + led_ch = LIT_GREEN; + break; + case 'b': + led_ch = LIT_BLUE; + break; + default: + return; + } + + /* set current with medium value, in case current is too large */ + mc13892_bklit_set_current(led_ch, LIT_CURR_12); + /* max duty cycle is 63, brightness needs to be divided by 4 */ + mc13892_bklit_set_dutycycle(led_ch, value / 4); + +} + +static int mc13892_led_remove(struct platform_device *dev) +{ + struct led_classdev *led_cdev = platform_get_drvdata(dev); + + led_classdev_unregister(led_cdev); + kfree(led_cdev->name); + kfree(led_cdev); + + return 0; +} + +#define LED_NAME_LEN 16 + +static int mc13892_led_probe(struct platform_device *dev) +{ + int ret; + struct led_classdev *led_cdev; + char *name; + + led_cdev = kzalloc(sizeof(struct led_classdev), GFP_KERNEL); + if (led_cdev == NULL) { + dev_err(&dev->dev, "No memory for device\n"); + return -ENOMEM; + } + name = kzalloc(LED_NAME_LEN, GFP_KERNEL); + if (name == NULL) { + dev_err(&dev->dev, "No memory for device\n"); + ret = -ENOMEM; + goto exit_err; + } + + strcpy(name, dev->name); + ret = strlen(dev->name); + if (ret > LED_NAME_LEN - 2) { + dev_err(&dev->dev, "led name is too long\n"); + goto exit_err1; + } + name[ret] = dev->id; + name[ret + 1] = '\0'; + led_cdev->name = name; + led_cdev->brightness_set = mc13892_led_set; + + ret = led_classdev_register(&dev->dev, led_cdev); + if (ret < 0) { + dev_err(&dev->dev, "led_classdev_register failed\n"); + goto exit_err1; + } + + platform_set_drvdata(dev, led_cdev); + + return 0; + exit_err1: + kfree(led_cdev->name); + exit_err: + kfree(led_cdev); + return ret; +} + +#ifdef CONFIG_PM +static int mc13892_led_suspend(struct platform_device *dev, pm_message_t state) +{ + struct led_classdev *led_cdev = platform_get_drvdata(dev); + + led_classdev_suspend(led_cdev); + return 0; +} + +static int mc13892_led_resume(struct platform_device *dev) +{ + struct led_classdev *led_cdev = platform_get_drvdata(dev); + + led_classdev_resume(led_cdev); + return 0; +} +#else +#define mc13892_led_suspend NULL +#define mc13892_led_resume NULL +#endif + +static struct platform_driver mc13892_led_driver = { + .probe = mc13892_led_probe, + .remove = mc13892_led_remove, + .suspend = mc13892_led_suspend, + .resume = mc13892_led_resume, + .driver = { + .name = "pmic_leds", + .owner = THIS_MODULE, + }, +}; + +static int __init mc13892_led_init(void) +{ + return platform_driver_register(&mc13892_led_driver); +} + +static void __exit mc13892_led_exit(void) +{ + platform_driver_unregister(&mc13892_led_driver); +} + +module_init(mc13892_led_init); +module_exit(mc13892_led_exit); + +MODULE_DESCRIPTION("Led driver for PMIC mc13892"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-stmp378x-pwm.c b/drivers/leds/leds-stmp378x-pwm.c new file mode 100644 index 000000000000..f0865db4eb90 --- /dev/null +++ b/drivers/leds/leds-stmp378x-pwm.c @@ -0,0 +1,190 @@ +/* + * Freescale STMP378X PWM LED driver + * + * Author: Drew Benedetti <drewb@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <mach/hardware.h> +#include <mach/regs-pwm.h> +#include <mach/regs-clkctrl.h> +#include <mach/pwm-led.h> +#include <mach/stmp3xxx.h> + +/* Up to 5 PWM lines are available. */ +#define PWM_MAX 5 + +/* PWM enables are the lowest PWM_MAX bits of HW_PWM_CTRL register */ +#define BM_PWM_CTRL_PWM_ENABLE(n) ((1<<(n)) & ((1<<(PWM_MAX))-1)) +#define BF_PWM_PERIODn_SETTINGS \ + (BF_PWM_PERIODn_CDIV(5) | /* divide by 64 */ \ + BF_PWM_PERIODn_INACTIVE_STATE(2) | /* low */ \ + BF_PWM_PERIODn_ACTIVE_STATE(3) | /* high */ \ + BF_PWM_PERIODn_PERIOD(LED_FULL)) /* 255 cycles */ + +struct stmp378x_led { + struct led_classdev led_dev; + int in_use; +}; + +static struct stmp378x_led leds[PWM_MAX]; + +static struct clk *pwm_clk; + +static void stmp378x_pwm_led_brightness_set(struct led_classdev *pled, + enum led_brightness value) +{ + unsigned int pwmn; + + pwmn = container_of(pled, struct stmp378x_led, led_dev) - leds; + + if (pwmn < PWM_MAX && leds[pwmn].in_use) { + HW_PWM_CTRL_CLR(BM_PWM_CTRL_PWM_ENABLE(pwmn)); + HW_PWM_ACTIVEn_WR(pwmn, BF_PWM_ACTIVEn_INACTIVE(value) | + BF_PWM_ACTIVEn_ACTIVE(0)); + HW_PWM_PERIODn_WR(pwmn, BF_PWM_PERIODn_SETTINGS); + HW_PWM_CTRL_SET(BM_PWM_CTRL_PWM_ENABLE(pwmn)); + } +} + +static int stmp378x_pwm_led_probe(struct platform_device *pdev) +{ + struct led_classdev *led; + unsigned int pwmn; + int leds_in_use = 0, rc = 0; + int i; + + stmp3xxx_reset_block(REGS_PWM_BASE, 1); + + pwm_clk = clk_get(&pdev->dev, "pwm"); + if (IS_ERR(pwm_clk)) { + rc = PTR_ERR(pwm_clk); + return rc; + } + + clk_enable(pwm_clk); + + for (i = 0; i < pdev->num_resources; i++) { + + if (pdev->resource[i].flags & IORESOURCE_DISABLED) + continue; + + pwmn = pdev->resource[i].start; + if (pwmn >= PWM_MAX) { + dev_err(&pdev->dev, "PWM %d doesn't exist\n", pwmn); + continue; + } + + rc = pwm_led_pinmux_request(pwmn, "stmp378x_pwm_led"); + if (rc) { + dev_err(&pdev->dev, + "PWM %d is not available (err=%d)\n", + pwmn, rc); + continue; + } + + led = &leds[pwmn].led_dev; + + led->flags = pdev->resource[i].flags; + led->name = pdev->resource[i].name; + led->brightness = LED_HALF; + led->flags = 0; + led->brightness_set = stmp378x_pwm_led_brightness_set; + led->default_trigger = 0; + + rc = led_classdev_register(&pdev->dev, led); + if (rc < 0) { + dev_err(&pdev->dev, + "Unable to register LED device %d (err=%d)\n", + pwmn, rc); + pwm_led_pinmux_free(pwmn, "stmp378x_pwm_led"); + continue; + } + + /* PWM LED is available now */ + leds[pwmn].in_use = !0; + leds_in_use++; + + /* Set default brightness */ + stmp378x_pwm_led_brightness_set(led, LED_HALF); + } + + if (leds_in_use == 0) { + dev_info(&pdev->dev, "No PWM LEDs available\n"); + clk_disable(pwm_clk); + clk_put(pwm_clk); + return -ENODEV; + } + + return 0; +} + +static int stmp378x_pwm_led_remove(struct platform_device *pdev) +{ + unsigned int pwmn; + + for (pwmn = 0; pwmn < PWM_MAX; pwmn++) { + + if (!leds[pwmn].in_use) + continue; + + /* Disable LED */ + HW_PWM_CTRL_CLR(BM_PWM_CTRL_PWM_ENABLE(pwmn)); + HW_PWM_ACTIVEn_WR(pwmn, BF_PWM_ACTIVEn_INACTIVE(0) | + BF_PWM_ACTIVEn_ACTIVE(0)); + HW_PWM_PERIODn_WR(pwmn, BF_PWM_PERIODn_SETTINGS); + + led_classdev_unregister(&leds[pwmn].led_dev); + pwm_led_pinmux_free(pwmn, "stmp378x_pwm_led"); + + leds[pwmn].led_dev.name = 0; + leds[pwmn].in_use = 0; + } + + clk_disable(pwm_clk); + clk_put(pwm_clk); + + return 0; +} + + +static struct platform_driver stmp378x_pwm_led_driver = { + .probe = stmp378x_pwm_led_probe, + .remove = stmp378x_pwm_led_remove, + .driver = { + .name = "stmp378x-pwm-led", + }, +}; + +static int __init stmp378x_pwm_led_init(void) +{ + return platform_driver_register(&stmp378x_pwm_led_driver); +} + +static void __exit stmp378x_pwm_led_exit(void) +{ + platform_driver_unregister(&stmp378x_pwm_led_driver); +} + +module_init(stmp378x_pwm_led_init); +module_exit(stmp378x_pwm_led_exit); + +MODULE_AUTHOR("Drew Benedetti <drewb@embeddedalley.com>"); +MODULE_DESCRIPTION("STMP378X PWM LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index 3315cac875e5..14b1af783e1a 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -406,4 +406,6 @@ config RADIO_TEA5764_XTAL Say Y here if TEA5764 have a 32768 Hz crystal in circuit, say N here if TEA5764 reference frequency is connected in FREQIN. +source "drivers/media/radio/stfm1000/Kconfig" + endif # RADIO_ADAPTERS diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 0f2b35b3e560..d8f720f16782 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -21,4 +21,6 @@ obj-$(CONFIG_USB_SI470X) += radio-si470x.o obj-$(CONFIG_USB_MR800) += radio-mr800.o obj-$(CONFIG_RADIO_TEA5764) += radio-tea5764.o +obj-$(CONFIG_RADIO_STFM1000) += stfm1000/ + EXTRA_CFLAGS += -Isound diff --git a/drivers/media/radio/stfm1000/Kconfig b/drivers/media/radio/stfm1000/Kconfig new file mode 100644 index 000000000000..ef30bf87de0b --- /dev/null +++ b/drivers/media/radio/stfm1000/Kconfig @@ -0,0 +1,26 @@ +config RADIO_STFM1000 + tristate "STFM1000 support" + depends on I2C && VIDEO_V4L2 && ARCH_STMP3XXX + select I2C_ALGOBIT + ---help--- + Choose Y here if you have this FM radio card, and then fill in the + port address below. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called stfm1000. + +config RADIO_STFM1000_ALSA + tristate "STFM1000 audio support" + depends on RADIO_STFM1000 && SND + select SND_PCM + ---help--- + This is a video4linux driver for direct (DMA) audio in + STFM1000 using ALSA + + To compile this driver as a module, choose M here: the + module will be called stfm1000-alsa. diff --git a/drivers/media/radio/stfm1000/Makefile b/drivers/media/radio/stfm1000/Makefile new file mode 100644 index 000000000000..01f354001a64 --- /dev/null +++ b/drivers/media/radio/stfm1000/Makefile @@ -0,0 +1,14 @@ +stfm1000-objs := stfm1000-core.o stfm1000-i2c.o stfm1000-precalc.o stfm1000-filter.o stfm1000-rds.o + +clean-files += stfm1000-precalc.o + +obj-$(CONFIG_RADIO_STFM1000) += stfm1000.o +obj-$(CONFIG_RADIO_STFM1000_ALSA) += stfm1000-alsa.o + +stfm1000-core.o: $(obj)/stfm1000-precalc.h + +hostprogs-$(CONFIG_RADIO_STFM1000) := gen-precalc +$(obj)/stfm1000-precalc.c: $(obj)/gen-precalc $(src)/stfm1000-regs.h + $(obj)/gen-precalc >$@ + +EXTRA_CFLAGS += -Idrivers/media/radio diff --git a/drivers/media/radio/stfm1000/gen-precalc.c b/drivers/media/radio/stfm1000/gen-precalc.c new file mode 100644 index 000000000000..d3797dbef815 --- /dev/null +++ b/drivers/media/radio/stfm1000/gen-precalc.c @@ -0,0 +1,62 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* generate precalculated tables */ +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> + +#include "stfm1000-regs.h" + +static void generate_tune1(void) +{ + int start, end; + int ndiv; // N Divider in PLL + int incr; // Increment in PLL + int cicosr; // CIC oversampling ratio + int sdnominal; // value to serve pilot/interpolator loop in SD + int i, temp; // used in tuning table construction + + start = STFM1000_FREQUENCY_100KHZ_MIN; + end = start + STFM1000_FREQUENCY_100KHZ_RANGE; + + printf("const struct stfm1000_tune1\n" + "stfm1000_tune1_table[STFM1000_FREQUENCY_100KHZ_RANGE] = {\n"); + + for (i = start; i < end; i++) { + + ndiv = (int)((i+14)/15) - 48; + incr = i - (int)(i/15)*15; + cicosr = (int)(i*2/3.0/16.0 + 0.5); + sdnominal = (int)(i*100.0e3/1.5/(double)cicosr/2.0/2.0*2.0*8.0*256.0/228.0e3*65536); + + temp = 0x00000000; // clear + temp = temp | ((cicosr<<9) & STFM1000_TUNE1_CICOSR); // bits[14:9] 0x00007E00 + temp = temp | ((ndiv<<4) & STFM1000_TUNE1_PLL_DIV); // bits[8:4] 0x000001F0 + temp = temp | ((incr) & STFM1000_TUNE1_PLL_DIV); // bits[3:0] 0x0000000F + + printf("\t[%d - STFM1000_FREQUENCY_100KHZ_MIN] = " + "{ .tune1 = 0x%08x, .sdnom = 0x%08x },\n", + i, temp, sdnominal); + } + printf("};\n"); + +} + +int main(int argc, char *argv[]) +{ + printf("#include \"stfm1000-regs.h\"\n\n"); + + generate_tune1(); + + return 0; +} diff --git a/drivers/media/radio/stfm1000/stfm1000-alsa.c b/drivers/media/radio/stfm1000/stfm1000-alsa.c new file mode 100644 index 000000000000..d1da4475bc07 --- /dev/null +++ b/drivers/media/radio/stfm1000/stfm1000-alsa.c @@ -0,0 +1,660 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/math64.h> + +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <linux/version.h> /* for KERNEL_VERSION MACRO */ +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <mach/regs-dri.h> +#include <mach/regs-apbx.h> +#include <mach/regs-clkctrl.h> + +#include "stfm1000.h" + +#define STFM1000_PERIODS 16 + +static int stfm1000_snd_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; /* two channels */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 20; + return 0; +} + +static int stfm1000_snd_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct stfm1000 *stfm1000 = snd_kcontrol_chip(kcontrol); + + (void)stfm1000; + ucontrol->value.integer.value[0] = 0; /* left */ + ucontrol->value.integer.value[1] = 0; /* right */ + return 0; +} + +static int stfm1000_snd_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct stfm1000 *stfm1000 = snd_kcontrol_chip(kcontrol); + int change; + int left, right; + + (void)stfm1000; + + left = ucontrol->value.integer.value[0]; + if (left < 0) + left = 0; + if (left > 20) + left = 20; + right = ucontrol->value.integer.value[1]; + if (right < 0) + right = 0; + if (right > 20) + right = 20; + + change = 1; + return change; +} + +static struct snd_kcontrol_new stfm1000_snd_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Radio Volume", + .index = 0, + .info = stfm1000_snd_volume_info, + .get = stfm1000_snd_volume_get, + .put = stfm1000_snd_volume_put, + .private_value = 0, + }, +}; + +static struct snd_pcm_hardware stfm1000_snd_capture = { + + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = SZ_256K, + .period_bytes_min = SZ_4K, + .period_bytes_max = SZ_4K, + .periods_min = STFM1000_PERIODS, + .periods_max = STFM1000_PERIODS, +}; + +static int stfm1000_snd_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct stfm1000 *stfm1000 = snd_pcm_substream_chip(substream); + int err; + + /* should never happen, just a sanity check */ + BUG_ON(stfm1000 == NULL); + + mutex_lock(&stfm1000->deffered_work_lock); + stfm1000->read_count = 0; + stfm1000->read_offset = 0; + + stfm1000->substream = substream; + runtime->private_data = stfm1000; + runtime->hw = stfm1000_snd_capture; + + mutex_unlock(&stfm1000->deffered_work_lock); + + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) { + printk(KERN_ERR "%s: snd_pcm_hw_constraint_integer " + "SNDRV_PCM_HW_PARAM_PERIODS failed\n", __func__); + return err; + } + + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS, 2); + if (err < 0) { + printk(KERN_ERR "%s: snd_pcm_hw_constraint_integer " + "SNDRV_PCM_HW_PARAM_PERIODS failed\n", __func__); + return err; + } + + return 0; +} + +static int stfm1000_snd_capture_close(struct snd_pcm_substream *substream) +{ + struct stfm1000 *stfm1000 = snd_pcm_substream_chip(substream); + + (void)stfm1000; /* nothing */ + return 0; +} + +static int stfm1000_snd_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct stfm1000 *stfm1000 = snd_pcm_substream_chip(substream); + unsigned int period_size, periods; + int ret; + + periods = params_periods(hw_params); + period_size = params_period_bytes(hw_params); + + if (period_size < 0x100 || period_size > 0x10000) + return -EINVAL; + if (periods < STFM1000_PERIODS) + return -EINVAL; + if (period_size * periods > 1024 * 1024) + return -EINVAL; + + stfm1000->blocks = periods; + stfm1000->blksize = period_size; + stfm1000->bufsize = params_buffer_bytes(hw_params); + + ret = snd_pcm_lib_malloc_pages(substream, stfm1000->bufsize); + if (ret < 0) { /* 0 & 1 are valid returns */ + printk(KERN_ERR "%s: snd_pcm_lib_malloc_pages() failed\n", + __func__); + return ret; + } + + /* the dri buffer is twice as large as the audio buffer */ + stfm1000->dri_bufsz = (stfm1000->bufsize / 4) * + sizeof(struct stfm1000_dri_sample); + stfm1000->dri_buf = dma_alloc_coherent(&stfm1000->radio.dev, + stfm1000->dri_bufsz, &stfm1000->dri_phys, GFP_KERNEL); + if (stfm1000->dri_buf == NULL) { + printk(KERN_ERR "%s: dma_alloc_coherent() failed\n", __func__); + snd_pcm_lib_free_pages(substream); + return -ENOMEM; + } + + return ret; +} + +static int stfm1000_snd_hw_free(struct snd_pcm_substream *substream) +{ + struct stfm1000 *stfm1000 = snd_pcm_substream_chip(substream); + + if (stfm1000->dri_buf) { + dma_free_coherent(&stfm1000->radio.dev, + (stfm1000->bufsize / 4) * + sizeof(struct stfm1000_dri_sample), + stfm1000->dri_buf, stfm1000->dri_phys); + stfm1000->dri_buf = NULL; + stfm1000->dri_phys = 0; + } + snd_pcm_lib_free_pages(substream); + return 0; +} + + +static int stfm1000_snd_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct stfm1000 *stfm1000 = snd_pcm_substream_chip(substream); + + stfm1000->substream = substream; + + if (snd_pcm_format_width(runtime->format) != 16 || + !snd_pcm_format_signed(runtime->format) || + snd_pcm_format_big_endian(runtime->format)) { + printk(KERN_INFO "STFM1000: ALSA capture_prepare illegal format\n"); + return -EINVAL; + } + + /* really shouldn't happen */ + BUG_ON(stfm1000->blocks > stfm1000->desc_num); + + mutex_lock(&stfm1000->deffered_work_lock); + + if (stfm1000->now_recording != 0) { + printk(KERN_INFO "STFM1000: ALSA capture_prepare still running\n"); + mutex_unlock(&stfm1000->deffered_work_lock); + return -EBUSY; + } + stfm1000->now_recording = 1; + + mutex_unlock(&stfm1000->deffered_work_lock); + + return 0; + +} + +static void stfm1000_snd_capture_trigger_start(struct work_struct *work) +{ + struct stfm1000 *stfm1000; + + stfm1000 = container_of(work, struct stfm1000, + snd_capture_start_work.work); + + mutex_lock(&stfm1000->deffered_work_lock); + + BUG_ON(stfm1000->now_recording != 1); + + stfm1000_bring_up(stfm1000); + + mutex_unlock(&stfm1000->deffered_work_lock); +} + +static void stfm1000_snd_capture_trigger_stop(struct work_struct *work) +{ + struct stfm1000 *stfm1000; + + stfm1000 = container_of(work, struct stfm1000, + snd_capture_stop_work.work); + + mutex_lock(&stfm1000->deffered_work_lock); + + stfm1000->stopping_recording = 1; + + stfm1000_take_down(stfm1000); + + BUG_ON(stfm1000->now_recording != 1); + stfm1000->now_recording = 0; + + stfm1000->stopping_recording = 0; + + mutex_unlock(&stfm1000->deffered_work_lock); +} + +static int execute_non_atomic(work_func_t fn, struct execute_work *ew) +{ + if (!in_atomic() && !in_interrupt()) { + fn(&ew->work); + return 0; + } + + INIT_WORK(&ew->work, fn); + schedule_work(&ew->work); + + return 1; +} + +static int stfm1000_snd_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct stfm1000 *stfm1000 = runtime->private_data; + int err = 0; + + (void)stfm1000; + + switch (cmd) { + + case SNDRV_PCM_TRIGGER_START: + execute_non_atomic(stfm1000_snd_capture_trigger_start, + &stfm1000->snd_capture_start_work); + break; + + case SNDRV_PCM_TRIGGER_STOP: + execute_non_atomic(stfm1000_snd_capture_trigger_stop, + &stfm1000->snd_capture_stop_work); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + stmp3xxx_dma_unfreeze(stfm1000->dma_ch); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + stmp3xxx_dma_freeze(stfm1000->dma_ch); + break; + + default: + err = -EINVAL; + break; + } + + return err; +} + +static snd_pcm_uframes_t +stfm1000_snd_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct stfm1000 *stfm1000 = runtime->private_data; + + if (stfm1000->read_count) { + stfm1000->read_count -= snd_pcm_lib_period_bytes(substream); + stfm1000->read_offset += snd_pcm_lib_period_bytes(substream); + if (stfm1000->read_offset == substream->runtime->dma_bytes) + stfm1000->read_offset = 0; + } + + return bytes_to_frames(runtime, stfm1000->read_offset); +} + +static struct snd_pcm_ops stfm1000_snd_capture_ops = { + .open = stfm1000_snd_capture_open, + .close = stfm1000_snd_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = stfm1000_snd_hw_params, + .hw_free = stfm1000_snd_hw_free, + .prepare = stfm1000_snd_capture_prepare, + .trigger = stfm1000_snd_capture_trigger, + .pointer = stfm1000_snd_capture_pointer, +}; + +static void stfm1000_snd_free(struct snd_card *card) +{ + struct stfm1000 *stfm1000 = card->private_data; + + free_irq(IRQ_DRI_ATTENTION, stfm1000); + free_irq(IRQ_DRI_DMA, stfm1000); +} + +static int stfm1000_alsa_instance_init(struct stfm1000 *stfm1000) +{ + int ret, i; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_kcontrol *ctl; + + mutex_init(&stfm1000->deffered_work_lock); + + /* request dma channel */ + stfm1000->desc_num = STFM1000_PERIODS; + stfm1000->dma_ch = STMP3xxx_DMA(5, STMP3XXX_BUS_APBX); + ret = stmp3xxx_dma_request(stfm1000->dma_ch, &stfm1000->radio.dev, + "stmp3xxx dri"); + if (ret != 0) { + printk(KERN_ERR "%s: stmp3xxx_dma_request failed\n", __func__); + goto err; + } + + stfm1000->dma = kzalloc(sizeof(*stfm1000->dma) * stfm1000->desc_num, + GFP_KERNEL); + if (stfm1000->dma == NULL) { + printk(KERN_ERR "%s: stmp3xxx_dma_request failed\n", __func__); + ret = -ENOMEM; + goto err_rel_dma; + } + + for (i = 0; i < stfm1000->desc_num; i++) { + ret = stmp3xxx_dma_allocate_command(stfm1000->dma_ch, + &stfm1000->dma[i]); + if (ret != 0) { + printk(KERN_ERR "%s: stmp3xxx_dma_allocate_command " + "failed\n", __func__); + goto err_free_dma; + } + } + + /* allocate ALSA card structure (we only need an extra pointer + * back to stfm1000) */ + card = snd_card_new(-1, NULL, THIS_MODULE, 0); + if (card == NULL) { + ret = -ENOMEM; + printk(KERN_ERR "%s: snd_card_new failed\n", __func__); + goto err_free_dma; + } + stfm1000->card = card; + card->private_data = stfm1000; /* point back */ + + /* mixer controls */ + strcpy(card->driver, "stfm1000"); + card->private_free = stfm1000_snd_free; + + strcpy(card->mixername, "stfm1000 mixer"); + for (i = 0; i < ARRAY_SIZE(stfm1000_snd_controls); i++) { + ctl = snd_ctl_new1(&stfm1000_snd_controls[i], stfm1000); + if (ctl == NULL) { + printk(KERN_ERR "%s: snd_ctl_new1 failed\n", __func__); + goto err_free_controls; + } + ret = snd_ctl_add(card, ctl); + if (ret != 0) { + printk(KERN_ERR "%s: snd_ctl_add failed\n", __func__); + goto err_free_controls; + } + } + + /* PCM */ + ret = snd_pcm_new(card, "STFM1000 PCM", 0, 0, 1, &pcm); + if (ret != 0) { + printk(KERN_ERR "%s: snd_ctl_add failed\n", __func__); + goto err_free_controls; + } + stfm1000->pcm = pcm; + pcm->private_data = stfm1000; /* point back */ + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &stfm1000_snd_capture_ops); + pcm->info_flags = 0; + strcpy(pcm->name, "STFM1000 PCM"); + + snd_card_set_dev(card, &stfm1000->radio.dev); + strcpy(card->shortname, "STFM1000"); + + ret = snd_pcm_lib_preallocate_pages_for_all(stfm1000->pcm, + SNDRV_DMA_TYPE_CONTINUOUS, card->dev, SZ_256K, SZ_256K); + if (ret != 0) { + printk(KERN_ERR "%s: snd_pcm_lib_preallocate_pages_for_all " + "failed\n", __func__); + goto err_free_pcm; + } + + ret = request_irq(IRQ_DRI_DMA, stfm1000_dri_dma_irq, 0, "stfm1000", + stfm1000); + if (ret != 0) { + printk(KERN_ERR "%s: request_irq failed\n", __func__); + goto err_free_prealloc; + } + + ret = request_irq(IRQ_DRI_ATTENTION, stfm1000_dri_attn_irq, 0, + "stfm1000", stfm1000); + if (ret != 0) { + printk(KERN_ERR "%s: request_irq failed\n", __func__); + goto err_rel_irq; + } + + ret = snd_card_register(stfm1000->card); + if (ret != 0) { + printk(KERN_ERR "%s: snd_card_register failed\n", __func__); + goto err_rel_irq2; + } + + /* Enable completion interrupt */ + stmp3xxx_dma_clear_interrupt(stfm1000->dma_ch); + stmp3xxx_dma_enable_interrupt(stfm1000->dma_ch); + + printk(KERN_INFO "%s/alsa: %s registered\n", "STFM1000", + card->longname); + + return 0; + +err_rel_irq2: + free_irq(IRQ_DRI_ATTENTION, stfm1000); + +err_rel_irq: + free_irq(IRQ_DRI_DMA, stfm1000); + +err_free_prealloc: + snd_pcm_lib_preallocate_free_for_all(stfm1000->pcm); + +err_free_pcm: + /* XXX TODO */ + +err_free_controls: + /* XXX TODO */ + +/* err_free_card: */ + snd_card_free(stfm1000->card); + +err_free_dma: + for (i = stfm1000->desc_num - 1; i >= 0; i--) { + if (stfm1000->dma[i].command != NULL) + stmp3xxx_dma_free_command(stfm1000->dma_ch, + &stfm1000->dma[i]); + } + +err_rel_dma: + stmp3xxx_dma_release(stfm1000->dma_ch); +err: + return ret; +} + +static void stfm1000_alsa_instance_release(struct stfm1000 *stfm1000) +{ + int i; + + stmp3xxx_dma_clear_interrupt(stfm1000->dma_ch); + stmp3xxx_arch_dma_reset_channel(stfm1000->dma_ch); + + snd_card_free(stfm1000->card); + + for (i = stfm1000->desc_num - 1; i >= 0; i--) + stmp3xxx_dma_free_command(stfm1000->dma_ch, &stfm1000->dma[i]); + + kfree(stfm1000->dma); + + stmp3xxx_dma_release(stfm1000->dma_ch); +} + +static void stfm1000_alsa_dma_irq(struct stfm1000 *stfm1000) +{ + struct snd_pcm_runtime *runtime; + int desc; + s16 *src, *dst; + + if (stfm1000->stopping_recording) + return; + + if (stfm1000->read_count >= stfm1000->blksize * + (stfm1000->blocks - 2)) { + printk(KERN_ERR "irq: overrun %d - Blocks in %d\n", + stfm1000->read_count, stfm1000->blocks); + return; + } + + /* someone has brutally killed user-space */ + if (stfm1000->substream == NULL || + stfm1000->substream->runtime == NULL) + return; + + BUG_ON(stfm1000->substream == NULL); + BUG_ON(stfm1000->substream->runtime == NULL); + + desc = stfm1000->read_offset / stfm1000->blksize; + runtime = stfm1000->substream->runtime; + + if (runtime->dma_area == NULL) + printk(KERN_INFO "runtime->dma_area = NULL\n"); + BUG_ON(runtime->dma_area == NULL); + if (stfm1000->dri_buf == NULL) + printk(KERN_INFO "stfm1000->dri_buf = NULL\n"); + BUG_ON(stfm1000->dri_buf == NULL); + + if (desc >= stfm1000->blocks) { + printk(KERN_INFO "desc=%d ->blocks=%d\n", + desc, stfm1000->blocks); + printk(KERN_INFO "->read_offset=%x ->blksize=%x\n", + stfm1000->read_offset, stfm1000->blksize); + } + BUG_ON(desc >= stfm1000->blocks); + + src = stfm1000->dri_buf + desc * (stfm1000->blksize * 2); + dst = (void *)runtime->dma_area + desc * stfm1000->blksize; + + /* perform filtering */ + stfm1000_decode_block(stfm1000, src, dst, stfm1000->blksize / 4); + + stfm1000->read_count += stfm1000->blksize; + + if (stfm1000->read_count >= + snd_pcm_lib_period_bytes(stfm1000->substream)) + snd_pcm_period_elapsed(stfm1000->substream); +} + +static void stfm1000_alsa_attn_irq(struct stfm1000 *stfm1000) +{ + /* nothing */ +} + +struct stfm1000_alsa_ops stfm1000_default_alsa_ops = { + .init = stfm1000_alsa_instance_init, + .release = stfm1000_alsa_instance_release, + .dma_irq = stfm1000_alsa_dma_irq, + .attn_irq = stfm1000_alsa_attn_irq, +}; + +static int stfm1000_alsa_init(void) +{ + struct stfm1000 *stfm1000 = NULL; + struct list_head *list; + int ret; + + stfm1000_alsa_ops = &stfm1000_default_alsa_ops; + + list_for_each(list, &stfm1000_devlist) { + stfm1000 = list_entry(list, struct stfm1000, devlist); + ret = (*stfm1000_alsa_ops->init)(stfm1000); + if (ret != 0) { + printk(KERN_ERR "stfm1000 ALSA driver for DMA sound " + "failed init.\n"); + return ret; + } + stfm1000->alsa_initialized = 1; + } + + printk(KERN_INFO "stfm1000 ALSA driver for DMA sound loaded\n"); + + return 0; +} + +static void stfm1000_alsa_exit(void) +{ + struct stfm1000 *stfm1000 = NULL; + struct list_head *list; + + list_for_each(list, &stfm1000_devlist) { + stfm1000 = list_entry(list, struct stfm1000, devlist); + + if (!stfm1000->alsa_initialized) + continue; + + stfm1000_take_down(stfm1000); + (*stfm1000_alsa_ops->release)(stfm1000); + stfm1000->alsa_initialized = 0; + } + + printk(KERN_INFO "stfm1000 ALSA driver for DMA sound unloaded\n"); +} + +/* We initialize this late, to make sure the sound system is up and running */ +late_initcall(stfm1000_alsa_init); +module_exit(stfm1000_alsa_exit); + +MODULE_AUTHOR("Pantelis Antoniou"); +MODULE_DESCRIPTION("An ALSA PCM driver for the STFM1000 chip."); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/radio/stfm1000/stfm1000-core.c b/drivers/media/radio/stfm1000/stfm1000-core.c new file mode 100644 index 000000000000..5086100bb480 --- /dev/null +++ b/drivers/media/radio/stfm1000/stfm1000-core.c @@ -0,0 +1,2459 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/math64.h> + +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <linux/version.h> /* for KERNEL_VERSION MACRO */ +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/device.h> +#include <linux/freezer.h> +#include <linux/kthread.h> +#include <mach/regs-dri.h> +#include <mach/regs-apbx.h> +#include <mach/regs-clkctrl.h> + +#include "stfm1000.h" + +static DEFINE_MUTEX(devlist_lock); +static unsigned int stfm1000_devcount; + +LIST_HEAD(stfm1000_devlist); +EXPORT_SYMBOL(stfm1000_devlist); + +/* alsa interface */ +struct stfm1000_alsa_ops *stfm1000_alsa_ops; +EXPORT_SYMBOL(stfm1000_alsa_ops); + +/* region, 0=US, 1=europe */ +static int georegion = 1; /* default is europe */ +static int rds_enable = 1; /* default is enabled */ + +static int sw_tune(struct stfm1000 *stfm1000, u32 freq); + +static const const char *stfm1000_get_rev_txt(u32 id) +{ + switch (id) { + case 0x01: return "TA1"; + case 0x02: return "TA2"; + case 0x11: return "TB1"; + case 0x12: return "TB2"; + } + return NULL; +} + +static const struct stfm1000_reg stfm1000_tb2_powerup[] = { + STFM1000_REG(REF, 0x00200000), + STFM1000_DELAY(20), + STFM1000_REG(DATAPATH, 0x00010210), + STFM1000_REG(TUNE1, 0x0004CF01), + STFM1000_REG(SDNOMINAL, 0x1C5EBCF0), + STFM1000_REG(PILOTTRACKING, 0x000001B6), + STFM1000_REG(INITIALIZATION1, 0x9fb80008), + STFM1000_REG(INITIALIZATION2, 0x8516e444 | STFM1000_DEEMPH_50_75B), + STFM1000_REG(INITIALIZATION3, 0x1402190b), + STFM1000_REG(INITIALIZATION4, 0x525bf052), + STFM1000_REG(INITIALIZATION5, 0x1000d106), + STFM1000_REG(INITIALIZATION6, 0x000062cb), + STFM1000_REG(AGC_CONTROL1, 0x1BCB2202), + STFM1000_REG(AGC_CONTROL2, 0x000020F0), + STFM1000_REG(CLK1, 0x10000000), + STFM1000_REG(CLK1, 0x20000000), + STFM1000_REG(CLK1, 0x00000000), + STFM1000_REG(CLK2, 0x7f000000), + STFM1000_REG(REF, 0x00B8222D), + STFM1000_REG(CLK1, 0x30000000), + STFM1000_REG(CLK1, 0x30002000), + STFM1000_REG(CLK1, 0x10002000), + STFM1000_REG(LNA, 0x0D080009), + STFM1000_DELAY(10), + STFM1000_REG(MIXFILT, 0x00008000), + STFM1000_REG(MIXFILT, 0x00000000), + STFM1000_REG(MIXFILT, 0x00007205), + STFM1000_REG(ADC, 0x001B3282), + STFM1000_REG(ATTENTION, 0x0000003F), + STFM1000_END, +}; + +static const struct stfm1000_reg stfm1000_ta2_powerup[] = { + STFM1000_REG(REF, 0x00200000), + STFM1000_DELAY(20), + STFM1000_REG(DATAPATH, 0x00010210), + STFM1000_REG(TUNE1, 0x00044F01), + STFM1000_REG(SDNOMINAL, 0x1C5EBCF0), + STFM1000_REG(PILOTTRACKING, 0x000001B6), + STFM1000_REG(INITIALIZATION1, 0x9fb80008), + STFM1000_REG(INITIALIZATION2, 0x8506e444), + STFM1000_REG(INITIALIZATION3, 0x1402190b), + STFM1000_REG(INITIALIZATION4, 0x525bf052), + STFM1000_REG(INITIALIZATION5, 0x7000d106), + STFM1000_REG(INITIALIZATION6, 0x0000c2cb), + STFM1000_REG(AGC_CONTROL1, 0x002c8402), + STFM1000_REG(AGC_CONTROL2, 0x00140050), + STFM1000_REG(CLK1, 0x10000000), + STFM1000_REG(CLK1, 0x20000000), + STFM1000_REG(CLK1, 0x00000000), + STFM1000_REG(CLK2, 0x7f000000), + STFM1000_REG(REF, 0x0030222D), + STFM1000_REG(CLK1, 0x30000000), + STFM1000_REG(CLK1, 0x30002000), + STFM1000_REG(CLK1, 0x10002000), + STFM1000_REG(LNA, 0x05080009), + STFM1000_REG(MIXFILT, 0x00008000), + STFM1000_REG(MIXFILT, 0x00000000), + STFM1000_REG(MIXFILT, 0x00007200), + STFM1000_REG(ADC, 0x00033000), + STFM1000_REG(ATTENTION, 0x0000003F), + STFM1000_END, +}; + +static const struct stfm1000_reg stfm1000_powerdown[] = { + STFM1000_REG(DATAPATH, 0x00010210), + STFM1000_REG(REF, 0), + STFM1000_REG(LNA, 0), + STFM1000_REG(MIXFILT, 0), + STFM1000_REG(CLK1, 0x20000000), + STFM1000_REG(CLK1, 0), + STFM1000_REG(CLK2, 0), + STFM1000_REG(ADC, 0), + STFM1000_REG(TUNE1, 0), + STFM1000_REG(SDNOMINAL, 0), + STFM1000_REG(PILOTTRACKING, 0), + STFM1000_REG(INITIALIZATION1, 0), + STFM1000_REG(INITIALIZATION2, 0), + STFM1000_REG(INITIALIZATION3, 0), + STFM1000_REG(INITIALIZATION4, 0), + STFM1000_REG(INITIALIZATION5, 0), + STFM1000_REG(INITIALIZATION6, 0x00007E00), + STFM1000_REG(AGC_CONTROL1, 0), + STFM1000_REG(AGC_CONTROL2, 0), + STFM1000_REG(DATAPATH, 0x00000200), +}; + +struct stfm1000_tuner_pmi { + u32 min; + u32 max; + u32 freq; + u32 pll_xtal; /* 1 = pll, 0 = xtal */ +}; + +#define PLL 1 +#define XTAL 0 + +static const struct stfm1000_tuner_pmi stfm1000_pmi_lookup[] = { + { .min = 76100, .max = 76500, .freq = 19200, .pll_xtal = PLL }, + { .min = 79700, .max = 79900, .freq = 19200, .pll_xtal = PLL }, + { .min = 80800, .max = 81200, .freq = 19200, .pll_xtal = PLL }, + { .min = 82100, .max = 82600, .freq = 19200, .pll_xtal = PLL }, + { .min = 86800, .max = 87200, .freq = 19200, .pll_xtal = PLL }, + { .min = 88100, .max = 88600, .freq = 19200, .pll_xtal = PLL }, + { .min = 89800, .max = 90500, .freq = 19200, .pll_xtal = PLL }, + { .min = 91400, .max = 91900, .freq = 19200, .pll_xtal = PLL }, + { .min = 92800, .max = 93300, .freq = 19200, .pll_xtal = PLL }, + { .min = 97400, .max = 97900, .freq = 19200, .pll_xtal = PLL }, + { .min = 98800, .max = 99200, .freq = 19200, .pll_xtal = PLL }, + { .min = 100200, .max = 100400, .freq = 19200, .pll_xtal = PLL }, + { .min = 103500, .max = 103900, .freq = 19200, .pll_xtal = PLL }, + { .min = 104800, .max = 105200, .freq = 19200, .pll_xtal = PLL }, + { .min = 106100, .max = 106500, .freq = 19200, .pll_xtal = PLL }, + + { .min = 76600, .max = 77000, .freq = 20000, .pll_xtal = PLL }, + { .min = 77800, .max = 78300, .freq = 20000, .pll_xtal = PLL }, + { .min = 79200, .max = 79600, .freq = 20000, .pll_xtal = PLL }, + { .min = 80600, .max = 80700, .freq = 20000, .pll_xtal = PLL }, + { .min = 83900, .max = 84400, .freq = 20000, .pll_xtal = PLL }, + { .min = 85300, .max = 85800, .freq = 20000, .pll_xtal = PLL }, + { .min = 94200, .max = 94700, .freq = 20000, .pll_xtal = PLL }, + { .min = 95600, .max = 96100, .freq = 20000, .pll_xtal = PLL }, + { .min = 100500, .max = 100800, .freq = 20000, .pll_xtal = PLL }, + { .min = 101800, .max = 102200, .freq = 20000, .pll_xtal = PLL }, + { .min = 103100, .max = 103400, .freq = 20000, .pll_xtal = PLL }, + { .min = 106600, .max = 106900, .freq = 20000, .pll_xtal = PLL }, + { .min = 107800, .max = 108000, .freq = 20000, .pll_xtal = PLL }, + + { .min = 0, .max = 0, .freq = 24000, .pll_xtal = XTAL } +}; + +int stfm1000_power_up(struct stfm1000 *stfm1000) +{ + struct stfm1000_reg *reg, *pwrup_reg; + const struct stfm1000_reg *orig_reg, *treg; + int ret, size; + + mutex_lock(&stfm1000->state_lock); + + /* Enable DRI clock for 24Mhz. */ + HW_CLKCTRL_XTAL_CLR(BM_CLKCTRL_XTAL_DRI_CLK24M_GATE); + + orig_reg = stfm1000->revid == STFM1000_CHIP_REV_TA2 ? + stfm1000_ta2_powerup : stfm1000_tb2_powerup; + + /* find size of the set */ + for (treg = orig_reg; treg->regno != STFM1000_REG_END; treg++) + ; + size = (treg + 1 - orig_reg) * sizeof(*treg); + + /* allocate copy */ + pwrup_reg = kmalloc(size, GFP_KERNEL); + if (pwrup_reg == NULL) { + printk(KERN_ERR "%s: out of memory\n", __func__); + ret = -ENOMEM; + goto out; + } + + /* copy it */ + memcpy(pwrup_reg, orig_reg, size); + + /* fixup region of INITILIZATION2 */ + for (reg = pwrup_reg; reg->regno != STFM1000_REG_END; reg++) { + + /* we only care for INITIALIZATION2 register */ + if (reg->regno != STFM1000_INITIALIZATION2) + continue; + + /* geographic region select */ + if (stfm1000->georegion == 0) /* USA */ + reg->value &= ~STFM1000_DEEMPH_50_75B; + else /* Europe */ + reg->value |= STFM1000_DEEMPH_50_75B; + + /* RDS enabled */ + if (stfm1000->revid == STFM1000_CHIP_REV_TB2) { + if (stfm1000->rds_enable) + reg->value |= STFM1000_RDS_ENABLE; + else + reg->value &= ~STFM1000_RDS_ENABLE; + } + } + + ret = stfm1000_write_regs(stfm1000, pwrup_reg); + + kfree(pwrup_reg); +out: + mutex_unlock(&stfm1000->state_lock); + + return ret; +} + +int stfm1000_power_down(struct stfm1000 *stfm1000) +{ + int ret; + + mutex_lock(&stfm1000->state_lock); + + /* Disable DRI clock for 24Mhz. */ + HW_CLKCTRL_XTAL_CLR(BM_CLKCTRL_XTAL_DRI_CLK24M_GATE); + + ret = stfm1000_write_regs(stfm1000, stfm1000_powerdown); + + /* Disable DRI clock for 24Mhz. */ + /* XXX bug warning, disabling the DRI clock is bad news */ + /* doing so causes noise to be received from the DRI */ + /* interface. Leave it on for now */ + /* HW_CLKCTRL_XTAL_CLR(BM_CLKCTRL_XTAL_DRI_CLK24M_GATE); */ + + mutex_unlock(&stfm1000->state_lock); + + return ret; +} + +int stfm1000_dcdc_update(struct stfm1000 *stfm1000, u32 freq) +{ + const struct stfm1000_tuner_pmi *pmi; + int i; + + /* search for DCDC frequency */ + pmi = stfm1000_pmi_lookup; + for (i = 0; i < ARRAY_SIZE(stfm1000_pmi_lookup); i++, pmi++) { + if (freq >= pmi->min && freq <= pmi->max) + break; + } + if (i >= ARRAY_SIZE(stfm1000_pmi_lookup)) + return -1; + + /* adjust DCDC frequency so that it is out of Tuner PLL range */ + /* XXX there is no adjustment API (os_pmi_SetDcdcFreq)*/ + return 0; +} + +static void Mute_Audio(struct stfm1000 *stfm1000) +{ + stfm1000->mute = 1; +} + +static void Unmute_Audio(struct stfm1000 *stfm1000) +{ + stfm1000->mute = 0; +} + +static const struct stfm1000_reg sd_dp_on_regs[] = { + STFM1000_REG_SETBITS(DATAPATH, STFM1000_DP_EN), + STFM1000_DELAY(3), + STFM1000_REG_SETBITS(DATAPATH, STFM1000_DB_ACCEPT), + STFM1000_REG_CLRBITS(AGC_CONTROL1, STFM1000_B2_BYPASS_AGC_CTL), + STFM1000_REG_CLRBITS(DATAPATH, STFM1000_DB_ACCEPT), + STFM1000_END, +}; + +static int SD_DP_On(struct stfm1000 *stfm1000) +{ + int ret; + + ret = stfm1000_write_regs(stfm1000, sd_dp_on_regs); + if (ret != 0) + return ret; + + return 0; +} + +static const struct stfm1000_reg sd_dp_off_regs[] = { + STFM1000_REG_SETBITS(DATAPATH, STFM1000_DB_ACCEPT), + STFM1000_REG_CLRBITS(DATAPATH, STFM1000_DP_EN), + STFM1000_REG_SETBITS(AGC_CONTROL1, STFM1000_B2_BYPASS_AGC_CTL), + STFM1000_REG_CLRBITS(PILOTTRACKING, STFM1000_B2_PILOTTRACKING_EN), + STFM1000_REG_CLRBITS(DATAPATH, STFM1000_DB_ACCEPT), + STFM1000_END, +}; + +static int SD_DP_Off(struct stfm1000 *stfm1000) +{ + int ret; + + ret = stfm1000_write_regs(stfm1000, sd_dp_off_regs); + if (ret != 0) + return ret; + + return 0; +} + +static int DRI_Start_Stream(struct stfm1000 *stfm1000) +{ + dma_addr_t dma_buffer_phys; + int i, next; + u32 cmd; + + /* we must not be gated */ + BUG_ON(HW_CLKCTRL_XTAL_RD() & BM_CLKCTRL_XTAL_DRI_CLK24M_GATE); + + /* hw_dri_SetReset */ + HW_DRI_CTRL_CLR(BM_DRI_CTRL_SFTRST | BM_DRI_CTRL_CLKGATE); + HW_DRI_CTRL_SET(BM_DRI_CTRL_SFTRST); + while ((HW_DRI_CTRL_RD() & BM_DRI_CTRL_CLKGATE) == 0) + cpu_relax(); + HW_DRI_CTRL_CLR(BM_DRI_CTRL_SFTRST | BM_DRI_CTRL_CLKGATE); + + /* DRI enable/config */ + HW_DRI_TIMING_WR(BF_DRI_TIMING_GAP_DETECTION_INTERVAL(0x10) | + BF_DRI_TIMING_PILOT_REP_RATE(0x08)); + + /* XXX SDK bug */ + /* While the SDK enables the gate here, everytime the stream */ + /* is started, doing so, causes the DRI to input audio noise */ + /* at any subsequent starts */ + /* Enable DRI clock for 24Mhz. */ + /* HW_CLKCTRL_XTAL_CLR(BM_CLKCTRL_XTAL_DRI_CLK24M_GATE); */ + + stmp3xxx_arch_dma_reset_channel(stfm1000->dma_ch); + + dma_buffer_phys = stfm1000->dri_phys; + + for (i = 0; i < stfm1000->blocks; i++) { + next = (i + 1) % stfm1000->blocks; + + /* link */ + stfm1000->dma[i].command->next = stfm1000->dma[next].handle; + stfm1000->dma[i].next_descr = &stfm1000->dma[next]; + + /* receive DRI is 8 bytes per 4 samples */ + cmd = BF_APBX_CHn_CMD_XFER_COUNT(stfm1000->blksize * 2) | + BM_APBX_CHn_CMD_IRQONCMPLT | + BM_APBX_CHn_CMD_CHAIN | + BF_APBX_CHn_CMD_COMMAND( + BV_APBX_CHn_CMD_COMMAND__DMA_WRITE); + + stfm1000->dma[i].command->cmd = cmd; + stfm1000->dma[i].command->buf_ptr = dma_buffer_phys; + stfm1000->dma[i].command->pio_words[0] = + BM_DRI_CTRL_OVERFLOW_IRQ_EN | + BM_DRI_CTRL_PILOT_SYNC_LOSS_IRQ_EN | + BM_DRI_CTRL_ATTENTION_IRQ_EN | + /* BM_DRI_CTRL_STOP_ON_OFLOW_ERROR | */ + /* BM_DRI_CTRL_STOP_ON_PILOT_ERROR | */ + BM_DRI_CTRL_ENABLE_INPUTS; + + dma_buffer_phys += stfm1000->blksize * 2; + + } + + /* Enable completion interrupt */ + stmp3xxx_dma_clear_interrupt(stfm1000->dma_ch); + stmp3xxx_dma_enable_interrupt(stfm1000->dma_ch); + + /* clear DRI interrupts pending */ + HW_DRI_CTRL_CLR(BM_DRI_CTRL_OVERFLOW_IRQ | + BM_DRI_CTRL_PILOT_SYNC_LOSS_IRQ | + BM_DRI_CTRL_ATTENTION_IRQ); + + /* Stop DRI on error */ + HW_DRI_CTRL_CLR(BM_DRI_CTRL_STOP_ON_OFLOW_ERROR | + BM_DRI_CTRL_STOP_ON_PILOT_ERROR); + + /* Reacquire data stream */ + HW_DRI_CTRL_SET(BM_DRI_CTRL_REACQUIRE_PHASE | + BM_DRI_CTRL_OVERFLOW_IRQ_EN | + BM_DRI_CTRL_PILOT_SYNC_LOSS_IRQ_EN | + BM_DRI_CTRL_ATTENTION_IRQ_EN | + BM_DRI_CTRL_ENABLE_INPUTS); + + stmp3xxx_dma_go(stfm1000->dma_ch, stfm1000->dma, 1); + + /* Turn on DRI hardware (don't forget to leave RUN bit ON) */ + HW_DRI_CTRL_SET(BM_DRI_CTRL_RUN); + + return 0; +} + +static int DRI_Stop_Stream(struct stfm1000 *stfm1000) +{ + int desc; + + /* disable interrupts */ + HW_DRI_CTRL_CLR(BM_DRI_CTRL_OVERFLOW_IRQ_EN | + BM_DRI_CTRL_PILOT_SYNC_LOSS_IRQ_EN | + BM_DRI_CTRL_ATTENTION_IRQ_EN); + + /* Freeze DMA channel for a moment */ + stmp3xxx_dma_freeze(stfm1000->dma_ch); + + /* all descriptors, set sema bit */ + for (desc = 0; desc < stfm1000->blocks; desc++) + stfm1000->dma[desc].command->cmd |= BM_APBX_CHn_CMD_SEMAPHORE; + + /* Let the current DMA transaction finish */ + stmp3xxx_dma_unfreeze(stfm1000->dma_ch); + msleep(5); + + /* dma shutdown */ + stmp3xxx_arch_dma_reset_channel(stfm1000->dma_ch); + + /* Turn OFF data lines and stop controller */ + HW_DRI_CTRL_CLR(BM_DRI_CTRL_ENABLE_INPUTS | BM_DRI_CTRL_RUN); + + /* hw_dri_SetReset */ + HW_DRI_CTRL_SET(BM_DRI_CTRL_SFTRST | BM_DRI_CTRL_CLKGATE); + + /* XXX SDK bug */ + /* While the SDK enables the gate here, everytime the stream */ + /* is started, doing so, causes the DRI to input audio noise */ + /* at any subsequent starts */ + /* Enable DRI clock for 24Mhz. */ + /* Disable DRI clock for 24Mhz. */ + /* HW_CLKCTRL_XTAL_SET(BM_CLKCTRL_XTAL_DRI_CLK24M_GATE); */ + + return 0; +} + +static int DRI_On(struct stfm1000 *stfm1000) +{ + int ret; + + if (stfm1000->active) + DRI_Start_Stream(stfm1000); + + ret = stfm1000_set_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_SAI_EN); + return ret; +} + +static int DRI_Off(struct stfm1000 *stfm1000) +{ + int ret; + + if (stfm1000->active) + DRI_Stop_Stream(stfm1000); + + ret = stfm1000_clear_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_SAI_EN); + + return 0; +} + +static int SD_Set_Channel_Filter(struct stfm1000 *stfm1000) +{ + int bypass_setting; + int sig_qual; + u32 tmp; + int ret; + + /* + * set channel filter + * + * B2_NEAR_CHAN_MIX_REG_MASK values from T-Spec + * 000 : 0 kHz mix. + * 001 : +100 kHz mix. + * 010 : +200 kHz mix. + * 011 : +300 kHz mix. + * 100 : -400 kHz mix. + * 101 : -300 kHz mix. + * 110 : -200 kHz mix. + * 111 : -100 kHz mix. + */ + + /* get near channel amplitude */ + ret = stfm1000_write_masked(stfm1000, STFM1000_INITIALIZATION3, + STFM1000_B2_NEAR_CHAN_MIX(0x01), + STFM1000_B2_NEAR_CHAN_MIX_MASK); + if (ret != 0) + return ret; + + msleep(10); /* wait for the signal quality to settle */ + + ret = stfm1000_read(stfm1000, STFM1000_SIGNALQUALITY, &tmp); + if (ret != 0) + return ret; + + sig_qual = (tmp & STFM1000_NEAR_CHAN_AMPLITUDE_MASK) >> + STFM1000_NEAR_CHAN_AMPLITUDE_SHIFT; + + bypass_setting = 0; + + /* check near channel amplitude vs threshold */ + if (sig_qual < stfm1000->adj_chan_th) { + /* get near channel amplitude again */ + ret = stfm1000_write_masked(stfm1000, STFM1000_INITIALIZATION3, + STFM1000_B2_NEAR_CHAN_MIX(0x05), + STFM1000_B2_NEAR_CHAN_MIX_MASK); + if (ret != 0) + return ret; + + msleep(10); /* wait for the signal quality to settle */ + + ret = stfm1000_read(stfm1000, STFM1000_SIGNALQUALITY, &tmp); + if (ret != 0) + return ret; + + sig_qual = (tmp & STFM1000_NEAR_CHAN_AMPLITUDE_MASK) >> + STFM1000_NEAR_CHAN_AMPLITUDE_SHIFT; + + if (sig_qual < stfm1000->adj_chan_th) + bypass_setting = 2; + } + + /* set filter settings */ + ret = stfm1000_write_masked(stfm1000, STFM1000_INITIALIZATION1, + STFM1000_B2_BYPASS_FILT(bypass_setting), + STFM1000_B2_BYPASS_FILT_MASK); + if (ret != 0) + return ret; + + return 0; +} + +static int SD_Look_For_Pilot_TA2(struct stfm1000 *stfm1000) +{ + int i; + u32 pilot; + int ret; + + /* assume pilot */ + stfm1000->pilot_present = 1; + + for (i = 0; i < 3; i++) { + + ret = stfm1000_read(stfm1000, STFM1000_PILOTCORRECTION, + &pilot); + if (ret != 0) + return ret; + + pilot &= STFM1000_PILOTEST_TA2_MASK; + pilot >>= STFM1000_PILOTEST_TA2_SHIFT; + + /* out of range? */ + if (pilot < 0xe2 || pilot >= 0xb5) { + stfm1000->pilot_present = 0; + break; + } + } + + return 0; +} + + +static int SD_Look_For_Pilot_TB2(struct stfm1000 *stfm1000) +{ + int i; + u32 pilot; + int ret; + + /* assume pilot */ + stfm1000->pilot_present = 1; + + for (i = 0; i < 3; i++) { + + ret = stfm1000_read(stfm1000, STFM1000_PILOTCORRECTION, + &pilot); + if (ret != 0) + return ret; + + pilot &= STFM1000_PILOTEST_TB2_MASK; + pilot >>= STFM1000_PILOTEST_TB2_SHIFT; + + /* out of range? */ + if (pilot < 0x1e || pilot >= 0x7f) { + stfm1000->pilot_present = 0; + break; + } + } + + return 0; +} + +static int SD_Look_For_Pilot(struct stfm1000 *stfm1000) +{ + int ret; + + if (stfm1000->revid == STFM1000_CHIP_REV_TA2) + ret = SD_Look_For_Pilot_TA2(stfm1000); + else + ret = SD_Look_For_Pilot_TB2(stfm1000); + + if (ret != 0) + return ret; + + if (!stfm1000->pilot_present) { + ret = stfm1000_clear_bits(stfm1000, STFM1000_PILOTTRACKING, + STFM1000_B2_PILOTTRACKING_EN); + if (ret != 0) + return ret; + + /* set force mono parameters for the filter */ + stfm1000->filter_parms.pCoefForcedMono = 1; + + /* yeah, I know, it's stupid */ + stfm1000->rds_state.demod.pCoefForcedMono = + stfm1000->filter_parms.pCoefForcedMono; + } + + return 0; +} + +static int SD_Gear_Shift_Pilot_Tracking(struct stfm1000 *stfm1000) +{ + static const struct { + int delay; + u32 value; + } track_table[] = { + { .delay = 10, .value = 0x81b6 }, + { .delay = 6, .value = 0x82a5 }, + { .delay = 6, .value = 0x8395 }, + { .delay = 8, .value = 0x8474 }, + { .delay = 20, .value = 0x8535 }, + { .delay = 50, .value = 0x8632 }, + { .delay = 0, .value = 0x8810 }, + }; + int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(track_table); i++) { + ret = stfm1000_write(stfm1000, STFM1000_PILOTTRACKING, + track_table[i].value); + if (ret != 0) + return ret; + + if (i < ARRAY_SIZE(track_table) - 1) /* last one no delay */ + msleep(track_table[i].delay); + } + + return 0; +} + +static int SD_Optimize_Channel(struct stfm1000 *stfm1000) +{ + int ret; + + ret = stfm1000_set_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_DB_ACCEPT); + if (ret != 0) + return ret; + + ret = stfm1000_write(stfm1000, STFM1000_PILOTTRACKING, + STFM1000_B2_PILOTTRACKING_EN | + STFM1000_B2_PILOTLPF_TIMECONSTANT(0x01) | + STFM1000_B2_PFDSCALE(0x0B) | + STFM1000_B2_PFDFILTER_SPEEDUP(0x06)); /* 0x000081B6 */ + if (ret != 0) + return ret; + + ret = SD_Set_Channel_Filter(stfm1000); + if (ret != 0) + return ret; + + ret = SD_Look_For_Pilot(stfm1000); + if (ret != 0) + return ret; + + if (stfm1000->pilot_present) { + ret = SD_Gear_Shift_Pilot_Tracking(stfm1000); + if (ret != 0) + return ret; + } + + ret = stfm1000_clear_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_DB_ACCEPT); + if (ret != 0) + return ret; + + return 0; +} + +static int Monitor_STFM_Quality(struct stfm1000 *stfm1000) +{ + u32 tmp, rssi_dc_est, tone_data; + u32 lna_rms, bias, agc_out, lna_th, lna, ref; + u16 rssi_mantissa, rssi_exponent, rssi_decoded; + u16 prssi; + s16 mpx_dc; + int rssi_log; + int bypass_filter; + int ret; + + ret = stfm1000_set_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_DB_ACCEPT); + if (ret != 0) + return ret; + + /* Get Rssi register readings from STFM1000 */ + stfm1000_read(stfm1000, STFM1000_RSSI_TONE, &tmp); + rssi_dc_est = tmp & 0xffff; + tone_data = (tmp >> 16) & 0x0fff; + + rssi_mantissa = (rssi_dc_est & 0xffe0) >> 5; /* 11Msb */ + rssi_exponent = rssi_dc_est & 0x001f; /* 5 lsb */ + rssi_decoded = (u32)rssi_mantissa << rssi_exponent; + + /* Convert Rsst to 10log(Rssi) */ + for (prssi = 20; prssi > 0; prssi--) + if (rssi_decoded >= (1 << prssi)) + break; + + rssi_log = (3 * rssi_decoded >> prssi) + (3 * prssi - 3); + /* clamp to positive */ + if (rssi_log < 0) + rssi_log = 0; + /* Compensate for errors in truncation/approximation by adding 1 */ + rssi_log++; + + stfm1000->rssi_dc_est_log = rssi_log; + stfm1000->signal_strength = stfm1000->rssi_dc_est_log; + + /* determine absolute value */ + if (tmp & 0x0800) + mpx_dc = ((tmp >> 16) & 0x0fff) | 0xf000; + else + mpx_dc = (tmp >> 16) & 0x0fff; + stfm1000->mpx_dc = mpx_dc; + mpx_dc = mpx_dc < 0 ? -mpx_dc : mpx_dc; + + if (stfm1000->tuning_grid_50KHz) + stfm1000->is_station = rssi_log > stfm1000->tune_rssi_th; + else + stfm1000->is_station = rssi_log > stfm1000->tune_rssi_th && + mpx_dc > stfm1000->tune_mpx_dc_th; + + /* weak signal? */ + if (stfm1000->rssi_dc_est_log < + (stfm1000->filter_parms.pCoefLmrGaTh - 20)) { + + if (stfm1000->pilot_present) + bypass_filter = 1; /* Filter settings #2 */ + else + bypass_filter = 0; + + /* configure filter for narrow band */ + ret = stfm1000_write_masked(stfm1000, STFM1000_AGC_CONTROL1, + STFM1000_B2_BYPASS_FILT(bypass_filter), + STFM1000_B2_BYPASS_FILT_MASK); + if (ret != 0) + return ret; + + /* Turn off pilot tracking */ + ret = stfm1000_clear_bits(stfm1000, STFM1000_PILOTTRACKING, + STFM1000_B2_PILOTTRACKING_EN); + if (ret != 0) + return ret; + + /* enable "forced mono" in black box */ + stfm1000->filter_parms.pCoefForcedMono = 1; + + /* yeah, I know, it's stupid */ + stfm1000->rds_state.demod.pCoefForcedMono = + stfm1000->filter_parms.pCoefForcedMono; + + /* Set weak signal flag */ + stfm1000->weak_signal = 1; + + if (stfm1000->revid == STFM1000_CHIP_REV_TA2) { + + /* read AGC_STAT register */ + ret = stfm1000_read(stfm1000, STFM1000_AGC_STAT, &tmp); + if (ret != 0) + return ret; + + lna_rms = (tmp & STFM1000_LNA_RMS_MASK) >> + STFM1000_LNA_RMS_SHIFT; + + /* Check the energy level from LNA Power Meter A/D */ + if (lna_rms == 0) + bias = STFM1000_IBIAS2_DN | STFM1000_IBIAS1_UP; + else + bias = STFM1000_IBIAS2_UP | STFM1000_IBIAS1_DN; + + if (lna_rms == 0 || lna_rms > 2) { + ret = stfm1000_write_masked(stfm1000, + STFM1000_LNA, bias, + STFM1000_IBIAS2_UP | + STFM1000_IBIAS2_DN | + STFM1000_IBIAS1_UP | + STFM1000_IBIAS1_DN); + if (ret != 0) + return ret; + } + + } else { + + /* Set LNA bias */ + + /* read AGC_STAT register */ + ret = stfm1000_read(stfm1000, STFM1000_AGC_STAT, &tmp); + if (ret != 0) + return ret; + + agc_out = (tmp & STFM1000_AGCOUT_STAT_MASK) >> + STFM1000_AGCOUT_STAT_SHIFT; + + /* read LNA register (this is a cached register) */ + ret = stfm1000_read(stfm1000, STFM1000_LNA, &lna); + if (ret != 0) + return ret; + + /* read REF register (this is a cached register) */ + ret = stfm1000_read(stfm1000, STFM1000_REF, &ref); + if (ret != 0) + return ret; + +/* work around the 80 line width problem */ +#undef LNADEF +#define LNADEF STFM1000_LNA_AMP1_IMPROVE_DISTORTION + if (agc_out == 31) { + if (rssi_log <= 16) { + if (lna & STFM1000_IBIAS1_DN) + lna &= ~STFM1000_IBIAS1_DN; + else { + lna |= STFM1000_IBIAS1_UP; + ref &= ~LNADEF; + } + } + if (rssi_log >= 26) { + if (lna & STFM1000_IBIAS1_UP) { + lna &= ~STFM1000_IBIAS1_UP; + ref |= LNADEF; + } else + lna |= STFM1000_IBIAS1_DN; + } + } else { + lna &= ~STFM1000_IBIAS1_UP; + lna |= STFM1000_IBIAS1_DN; + ref |= LNADEF; + } +#undef LNADEF + + ret = stfm1000_write_masked(stfm1000, STFM1000_LNA, + lna, STFM1000_IBIAS1_UP | STFM1000_IBIAS1_DN); + if (ret != 0) + return ret; + + ret = stfm1000_write_masked(stfm1000, STFM1000_REF, + ref, STFM1000_LNA_AMP1_IMPROVE_DISTORTION); + if (ret != 0) + return ret; + } + + } else if (stfm1000->rssi_dc_est_log > + (stfm1000->filter_parms.pCoefLmrGaTh - 17)) { + + bias = STFM1000_IBIAS2_UP | STFM1000_IBIAS1_DN; + + ret = stfm1000_write_masked(stfm1000, STFM1000_LNA, + bias, STFM1000_IBIAS2_UP | STFM1000_IBIAS2_DN | + STFM1000_IBIAS1_UP | STFM1000_IBIAS1_DN); + if (ret != 0) + return ret; + + ret = SD_Set_Channel_Filter(stfm1000); + if (ret != 0) + return ret; + + ret = SD_Look_For_Pilot(stfm1000); + if (ret != 0) + return ret; + + if (stfm1000->pilot_present) { + if (stfm1000->prev_pilot_present || + stfm1000->weak_signal) { + + /* gear shift pilot tracking */ + ret = SD_Gear_Shift_Pilot_Tracking( + stfm1000); + if (ret != 0) + return ret; + + /* set force mono parameters for the + * filter */ + stfm1000->filter_parms. + pCoefForcedMono = stfm1000-> + force_mono; + + /* yeah, I know, it's stupid */ + stfm1000->rds_state.demod. + pCoefForcedMono = stfm1000-> + filter_parms. + pCoefForcedMono; + } + } else { + ret = stfm1000_clear_bits(stfm1000, + STFM1000_PILOTTRACKING, + STFM1000_B2_PILOTTRACKING_EN); + if (ret != 0) + return ret; + + /* set force mono parameters for the filter */ + stfm1000->filter_parms.pCoefForcedMono = 1; + + /* yeah, I know, it's stupid */ + stfm1000->rds_state.demod.pCoefForcedMono = + stfm1000->filter_parms.pCoefForcedMono; + } + + /* Reset weak signal flag */ + stfm1000->weak_signal = 0; + stfm1000->prev_pilot_present = stfm1000->pilot_present; + + } else { + + ret = SD_Look_For_Pilot(stfm1000); + if (ret != 0) + return ret; + + if (!stfm1000->pilot_present) { + ret = stfm1000_clear_bits(stfm1000, + STFM1000_PILOTTRACKING, + STFM1000_B2_PILOTTRACKING_EN); + if (ret != 0) + return ret; + + /* set force mono parameters for the filter */ + stfm1000->filter_parms.pCoefForcedMono = 1; + + /* yeah, I know, it's stupid */ + stfm1000->rds_state.demod.pCoefForcedMono = + stfm1000->filter_parms.pCoefForcedMono; + + /* Reset weak signal flag */ + stfm1000->weak_signal = 0; + stfm1000->prev_pilot_present = stfm1000->pilot_present; + } + + } + + if (stfm1000->revid == STFM1000_CHIP_REV_TA2) { + + /* read AGC_STAT register */ + ret = stfm1000_read(stfm1000, STFM1000_AGC_STAT, &tmp); + if (ret != 0) + return ret; + + agc_out = (tmp & STFM1000_AGCOUT_STAT_MASK) >> + STFM1000_AGCOUT_STAT_SHIFT; + lna_rms = (tmp & STFM1000_LNA_RMS_MASK) >> + STFM1000_LNA_RMS_SHIFT; + + ret = stfm1000_read(stfm1000, STFM1000_AGC_CONTROL1, &tmp); + if (ret != 0) + return ret; + + /* extract LNATH */ + lna_th = (tmp & STFM1000_B2_LNATH_MASK) >> + STFM1000_B2_LNATH_SHIFT; + + if (lna_rms > lna_th && agc_out <= 1) { + + ret = stfm1000_write_masked(stfm1000, STFM1000_LNA, + STFM1000_USEATTEN(1), STFM1000_USEATTEN_MASK); + if (ret != 0) + return ret; + + } else if (agc_out > 15) { + + ret = stfm1000_write_masked(stfm1000, STFM1000_LNA, + STFM1000_USEATTEN(0), STFM1000_USEATTEN_MASK); + if (ret != 0) + return ret; + } + } + + /* disable buffered writes */ + ret = stfm1000_clear_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_DB_ACCEPT); + if (ret != 0) + return ret; + + return ret; +} + +static int Is_Station(struct stfm1000 *stfm1000) +{ + u32 tmp, rssi_dc_est, tone_data; + u16 rssi_mantissa, rssi_exponent, rssi_decoded; + u16 prssi; + s16 mpx_dc; + int rssi_log; + + /* Get Rssi register readings from STFM1000 */ + stfm1000_read(stfm1000, STFM1000_RSSI_TONE, &tmp); + rssi_dc_est = tmp & 0xffff; + tone_data = (tmp >> 16) & 0x0fff; + + rssi_mantissa = (rssi_dc_est & 0xffe0) >> 5; /* 11Msb */ + rssi_exponent = rssi_dc_est & 0x001f; /* 5 lsb */ + rssi_decoded = (u32)rssi_mantissa << rssi_exponent; + + /* Convert Rsst to 10log(Rssi) */ + for (prssi = 20; prssi > 0; prssi--) + if (rssi_decoded >= (1 << prssi)) + break; + + rssi_log = (3 * rssi_decoded >> prssi) + (3 * prssi - 3); + /* clamp to positive */ + if (rssi_log < 0) + rssi_log = 0; + /* Compensate for errors in truncation/approximation by adding 1 */ + rssi_log++; + + stfm1000->rssi_dc_est_log = rssi_log; + stfm1000->signal_strength = stfm1000->rssi_dc_est_log; + + /* determine absolute value */ + if (tmp & 0x0800) + mpx_dc = ((tmp >> 16) & 0x0fff) | 0xf000; + else + mpx_dc = (tmp >> 16) & 0x0fff; + stfm1000->mpx_dc = mpx_dc; + mpx_dc = mpx_dc < 0 ? -mpx_dc : mpx_dc; + + if (stfm1000->tuning_grid_50KHz) + stfm1000->is_station = rssi_log > stfm1000->tune_rssi_th; + else + stfm1000->is_station = rssi_log > stfm1000->tune_rssi_th && + mpx_dc > stfm1000->tune_mpx_dc_th; + + return 0; +} + +int Monitor_STFM_AGC(struct stfm1000 *stfm1000) +{ + /* we don't do any AGC for now */ + return 0; +} + +static int Take_Down(struct stfm1000 *stfm1000) +{ + Mute_Audio(stfm1000); + + DRI_Off(stfm1000); + + SD_DP_Off(stfm1000); + + return 0; +} + +static int Bring_Up(struct stfm1000 *stfm1000) +{ + SD_DP_On(stfm1000); + + SD_Optimize_Channel(stfm1000); + + DRI_On(stfm1000); + + Unmute_Audio(stfm1000); + + if (stfm1000->rds_enable) + stfm1000_rds_reset(&stfm1000->rds_state); + + stfm1000->rds_sync = stfm1000->rds_enable; /* force sync (if RDS) */ + stfm1000->rds_demod_running = 0; + stfm1000->rssi_dc_est_log = 0; + stfm1000->signal_strength = 0; + + stfm1000->next_quality_monitor = jiffies + msecs_to_jiffies( + stfm1000->quality_monitor_period); + stfm1000->next_agc_monitor = jiffies + msecs_to_jiffies( + stfm1000->agc_monitor_period); + stfm1000->rds_pkt_bad = 0; + stfm1000->rds_pkt_good = 0; + stfm1000->rds_pkt_recovered = 0; + stfm1000->rds_pkt_lost_sync = 0; + stfm1000->rds_bit_overruns = 0; + + return 0; +} + +/* These are not used yet */ + +static int Lock_Station(struct stfm1000 *stfm1000) +{ + int ret; + + ret = SD_Optimize_Channel(stfm1000); + if (ret != 0) + return ret; + + /* AGC monitor start? */ + + return ret; +} + +static const struct stfm1000_reg sd_unlock_regs[] = { + STFM1000_REG_SETBITS(DATAPATH, STFM1000_DB_ACCEPT), + STFM1000_REG_CLRBITS(PILOTTRACKING, STFM1000_B2_PILOTTRACKING_EN), + STFM1000_REG_CLRBITS(DATAPATH, STFM1000_DB_ACCEPT), + STFM1000_END, +}; + +static int Unlock_Station(struct stfm1000 *stfm1000) +{ + int ret; + + ret = stfm1000_write_regs(stfm1000, sd_unlock_regs); + return ret; +} + +irqreturn_t stfm1000_dri_dma_irq(int irq, void *dev_id) +{ + struct stfm1000 *stfm1000 = dev_id; + u32 err_mask, irq_mask; + u32 ctrl; + int handled = 0; + +#ifdef CONFIG_ARCH_STMP37XX + err_mask = 1 << (16 + stfm1000->dma_ch); +#endif +#ifdef CONFIG_ARCH_STMP378X + err_mask = 1 << stfm1000->dma_ch; +#endif + irq_mask = 1 << stfm1000->dma_ch; + +#ifdef CONFIG_ARCH_STMP37XX + ctrl = HW_APBX_CTRL1_RD(); +#endif +#ifdef CONFIG_ARCH_STMP378X + ctrl = HW_APBX_CTRL2_RD(); +#endif + + if (ctrl & err_mask) { + handled = 1; + printk(KERN_WARNING "%s: DMA audio channel %d error\n", + __func__, stfm1000->dma_ch); +#ifdef CONFIG_ARCH_STMP37XX + HW_APBX_CTRL1_CLR(err_mask); +#endif +#ifdef CONFIG_ARCH_STMP378X + HW_APBX_CTRL2_CLR(err_mask); +#endif + } + + if (HW_APBX_CTRL1_RD() & irq_mask) { + handled = 1; + stmp3xxx_dma_clear_interrupt(stfm1000->dma_ch); + + if (stfm1000->alsa_initialized) { + BUG_ON(stfm1000_alsa_ops->dma_irq == NULL); + (*stfm1000_alsa_ops->dma_irq)(stfm1000); + } + } + + return handled ? IRQ_HANDLED : IRQ_NONE; +} +EXPORT_SYMBOL(stfm1000_dri_dma_irq); + +irqreturn_t stfm1000_dri_attn_irq(int irq, void *dev_id) +{ + struct stfm1000 *stfm1000 = dev_id; + int handled = 1; + u32 mask; + + (void)stfm1000; + mask = HW_DRI_CTRL_RD(); + mask &= BM_DRI_CTRL_OVERFLOW_IRQ | BM_DRI_CTRL_PILOT_SYNC_LOSS_IRQ | + BM_DRI_CTRL_ATTENTION_IRQ; + + HW_DRI_CTRL_CLR(mask); + + printk(KERN_INFO "DRI_ATTN:%s%s%s\n", + (mask & BM_DRI_CTRL_OVERFLOW_IRQ) ? " OV" : "", + (mask & BM_DRI_CTRL_PILOT_SYNC_LOSS_IRQ) ? " SL" : "", + (mask & BM_DRI_CTRL_ATTENTION_IRQ) ? " AT" : ""); + + if (stfm1000->alsa_initialized) { + BUG_ON(stfm1000_alsa_ops->attn_irq == NULL); + (*stfm1000_alsa_ops->attn_irq)(stfm1000); + } + + return handled ? IRQ_HANDLED : IRQ_NONE; +} +EXPORT_SYMBOL(stfm1000_dri_attn_irq); + +void stfm1000_decode_block(struct stfm1000 *stfm1000, const s16 *src, s16 *dst, + int count) +{ + int i; + + if (stfm1000->mute) { + memset(dst, 0, count * sizeof(s16) * 2); + return; + + } + + for (i = 0; i < count; i++, dst += 2, src += 4) { + + stfm1000_filter_decode(&stfm1000->filter_parms, + src[0], src[1], src[2]); + + dst[0] = stfm1000_filter_value_left(&stfm1000->filter_parms); + dst[1] = stfm1000_filter_value_right(&stfm1000->filter_parms); + } + + stfm1000->rssi = stfm1000->filter_parms.RssiDecoded; + stfm1000->stereo = stfm1000->pilot_present && + !stfm1000->filter_parms.pCoefForcedMono; + + /* RDS processing */ + if (stfm1000->rds_demod_running) { + /* rewind */ + src -= count * 4; + stfm1000_rds_demod(&stfm1000->rds_state, src, count); + } + +} +EXPORT_SYMBOL(stfm1000_decode_block); + +void stfm1000_take_down(struct stfm1000 *stfm1000) +{ + mutex_lock(&stfm1000->state_lock); + stfm1000->active = 0; + Take_Down(stfm1000); + mutex_unlock(&stfm1000->state_lock); +} +EXPORT_SYMBOL(stfm1000_take_down); + +void stfm1000_bring_up(struct stfm1000 *stfm1000) +{ + mutex_lock(&stfm1000->state_lock); + + stfm1000->active = 1; + + stfm1000_filter_reset(&stfm1000->filter_parms); + + Bring_Up(stfm1000); + + mutex_unlock(&stfm1000->state_lock); +} +EXPORT_SYMBOL(stfm1000_bring_up); + +void stfm1000_tune_current(struct stfm1000 *stfm1000) +{ + mutex_lock(&stfm1000->state_lock); + sw_tune(stfm1000, stfm1000->freq); + mutex_unlock(&stfm1000->state_lock); +} +EXPORT_SYMBOL(stfm1000_tune_current); + +/* Alternate ZIF Tunings to avoid EMI */ +const struct stfm1000_tune1 +stfm1000_board_emi_tuneups[STFM1000_FREQUENCY_100KHZ_RANGE] = { +#undef TUNE_ENTRY +#define TUNE_ENTRY(f, t1, sd) \ + [(f) - STFM1000_FREQUENCY_100KHZ_MIN] = \ + { .tune1 = (t1), .sdnom = (sd) } + TUNE_ENTRY(765, 0x84030, 0x1BF5E50D), /* 061215 Jon, IF +0kHz */ + TUNE_ENTRY(780, 0x84240, 0x1BA5162F), /* 061215 Jon, IF +0kHz */ + TUNE_ENTRY(795, 0x84250, 0x1C2D2F39), /* 061215 Jon, IF +0kHz */ + TUNE_ENTRY(810, 0x84460, 0x1BDD207E), /* 061215 Jon, IF +0kHz */ + TUNE_ENTRY(825, 0x84470, 0x1C6138CD), /* 061215 Jon, IF +0kHz */ + TUNE_ENTRY(839, 0xC4680, 0x1C11F704), /* 061215 Jon, IF +100kHz */ + TUNE_ENTRY(840, 0x84680, 0x1c11f704), + TUNE_ENTRY(855, 0x84890, 0x1BC71C71), /* 061215 Jon, IF +0kHz */ + TUNE_ENTRY(870, 0x848A0, 0x1C43DE10), /* 061215 Jon, IF +0kHz */ + TUNE_ENTRY(885, 0x84AB0, 0x1BF9B021), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(899, 0xC4CC0, 0x1BB369A9), /* 061025 Arthur, IF +100kHz */ + TUNE_ENTRY(900, 0x84CC0, 0x1BB369A9), /* 061025 Arthur, IF 0kHz */ + TUNE_ENTRY(915, 0x84CD0, 0x1C299A5B), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(930, 0x84ee0, 0x1be3e6aa), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(945, 0x84ef0, 0x1c570f8b), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(959, 0xC5100, 0x1c11f704), + TUNE_ENTRY(960, 0x85100, 0x1c11f704), + TUNE_ENTRY(975, 0x85310, 0x1bd03d57), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(990, 0x85320, 0x1c3dc822), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(1005, 0x85530, 0x1bfc93ff), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(1019, 0xC5740, 0x1BBE683C), /* 061025 Arthur, IF +100kHz */ + TUNE_ENTRY(1020, 0x85740, 0x1bbe683c), /* 061025 Arthur, IF +0kHz */ + TUNE_ENTRY(1035, 0x85750, 0x1c26dab6), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(1050, 0x85960, 0x1be922b4), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(1065, 0x85970, 0x1c4f357c), /* 061101 Arthur, IF +0kHz */ + TUNE_ENTRY(1079, 0xC5B80, 0x1c11f704), + TUNE_ENTRY(1080, 0x85B80, 0x1c11f704), +#undef TUNE_ENTRY +}; + +static const struct stfm1000_tune1 *stfm1000_board_emi_tune(int freq100) +{ + const struct stfm1000_tune1 *tune1; + + if ((unsigned int)(freq100 - STFM1000_FREQUENCY_100KHZ_MIN) >= + STFM1000_FREQUENCY_100KHZ_RANGE) + return NULL; + + tune1 = &stfm1000_board_emi_tuneups[freq100 - + STFM1000_FREQUENCY_100KHZ_MIN]; + if (tune1->tune1 == 0 && tune1->sdnom == 0) + return NULL; + return tune1; +} + +/* freq in kHz */ +static int sw_tune(struct stfm1000 *stfm1000, u32 freq) +{ + u32 freq100 = freq / 100; + int tune_cap; + int i2s_clock; + int mix_reg; + int if_freq, fe_freq; + u32 tune1, sdnom, agc1; + const struct stfm1000_tune1 *tp; + int ret; + + if_freq = 0; + mix_reg = 1; + switch (mix_reg) { + case 0: if_freq = -2; break; + case 1: if_freq = -1; break; + case 2: if_freq = 0; break; + case 3: if_freq = 1; break; + case 4: if_freq = 2; break; + } + + /* handle board specific EMI tuning */ + tp = stfm1000_board_emi_tune(freq100); + if (tp != NULL) { + tune1 = tp->tune1; + sdnom = tp->sdnom; + } else { + fe_freq = freq100 + if_freq; + + /* clamp into range */ + if (fe_freq < STFM1000_FREQUENCY_100KHZ_MIN) + fe_freq = STFM1000_FREQUENCY_100KHZ_MIN; + else if (fe_freq > STFM1000_FREQUENCY_100KHZ_MAX) + fe_freq = STFM1000_FREQUENCY_100KHZ_MAX; + + tp = &stfm1000_tune1_table[fe_freq - + STFM1000_FREQUENCY_100KHZ_MIN]; + + /* bits [14:0], [20:18] */ + tune1 = (tp->tune1 & 0x7fff) | (mix_reg << 18); + sdnom = tp->sdnom; + } + + agc1 = stfm1000->revid == STFM1000_CHIP_REV_TA2 ? 0x0400 : 0x2200; + + ret = stfm1000_write_masked(stfm1000, STFM1000_AGC_CONTROL1, + agc1, 0x3f00); + if (ret != 0) + goto err; + + ret = stfm1000_write_masked(stfm1000, STFM1000_TUNE1, tune1, + 0xFFFF7FFF); /* do not set bit-15 */ + if (ret != 0) + goto err; + + /* keep this around */ + stfm1000->sdnominal_pivot = sdnom; + + ret = stfm1000_write(stfm1000, STFM1000_SDNOMINAL, sdnom); + if (ret != 0) + goto err; + + /* fix for seek-not-stopping on alternate tunings */ + ret = stfm1000_set_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_DB_ACCEPT); + if (ret != 0) + goto err; + + ret = stfm1000_clear_bits(stfm1000, STFM1000_DATAPATH, + STFM1000_DB_ACCEPT); + if (ret != 0) + goto err; + + ret = stfm1000_set_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_DRI_CLK_EN); + if (ret != 0) + goto err; + + /* 6MHz spur fix */ + if ((freq100 >= 778 && freq100 <= 782) || + (freq100 >= 838 && freq100 <= 842) || + (freq100 >= 898 && freq100 <= 902) || + (freq100 >= 958 && freq100 <= 962) || + (freq100 >= 1018 && freq100 <= 1022) || + (freq100 >= 1078 && freq100 <= 1080)) + i2s_clock = 5; /* 4.8MHz */ + else + i2s_clock = 4; + + ret = stfm1000_write_masked(stfm1000, STFM1000_DATAPATH, + STFM1000_SAI_CLK_DIV(i2s_clock), STFM1000_SAI_CLK_DIV_MASK); + if (ret != 0) + goto err; + + ret = stfm1000_set_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_DRI_CLK_EN); + if (ret != 0) + goto err; + + if (tune1 & 0xf) + ret = stfm1000_set_bits(stfm1000, STFM1000_CLK1, + STFM1000_ENABLE_TAPDELAYFIX); + else + ret = stfm1000_clear_bits(stfm1000, STFM1000_CLK1, + STFM1000_ENABLE_TAPDELAYFIX); + + if (ret != 0) + goto err; + + tune_cap = (int)(stfm1000->tune_cap_a_f - + stfm1000->tune_cap_b_f * freq100); + if (tune_cap < 4) + tune_cap = 4; + ret = stfm1000_write_masked(stfm1000, STFM1000_LNA, + STFM1000_ANTENNA_TUNECAP(tune_cap), + STFM1000_ANTENNA_TUNECAP_MASK); + if (ret != 0) + goto err; + + /* set signal strenth to 0 */ + /* stfm1000_dcdc_update(); */ + + /* cmp_rds_setRdsStatus(0) */ + /* cmp_rds_ResetGroupCallbacks(); */ + stfm1000->freq = freq; + + return 0; +err: + return -1; +} + +static const struct v4l2_queryctrl radio_qctrl[] = { + { + .id = V4L2_CID_AUDIO_MUTE, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .default_value = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, +}; + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + strlcpy(v->driver, "radio-stfm1000", sizeof(v->driver)); + strlcpy(v->card, "STFM1000 Radio", sizeof(v->card)); + sprintf(v->bus_info, "i2c"); + v->version = KERNEL_VERSION(0, 0, 1); + v->capabilities = V4L2_CAP_TUNER; + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + u32 tmp, rssi_dc_est, tone_data; + u16 rssi_mantissa, rssi_exponent, rssi_decoded; + u16 prssi; + s16 mpx_dc; + int rssi_log; + int ret; + + if (v->index > 0) + return -EINVAL; + + mutex_lock(&stfm1000->state_lock); + + strcpy(v->name, "FM"); + v->type = V4L2_TUNER_RADIO; + v->rangelow = (u32)(87.5 * 16000); + v->rangehigh = (u32)(108 * 16000); + v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + v->capability = V4L2_TUNER_CAP_LOW; + v->audmode = V4L2_TUNER_MODE_STEREO; + v->signal = 0; /* tr_getsigstr(); */ + + msleep(50); + + ret = stfm1000_read(stfm1000, STFM1000_RSSI_TONE, &tmp); + if (ret != 0) + goto out; + + rssi_dc_est = tmp & 0xffff; + tone_data = (tmp >> 16) & 0x0fff; + + rssi_mantissa = (rssi_dc_est & 0xffe0) >> 5; /* 11Msb */ + rssi_exponent = rssi_dc_est & 0x001f; /* 5 lsb */ + rssi_decoded = (u32)rssi_mantissa << rssi_exponent; + + /* Convert Rsst to 10log(Rssi) */ + for (prssi = 20; prssi > 0; prssi--) + if (rssi_decoded >= (1 << prssi)) + break; + + rssi_log = (3 * rssi_decoded >> prssi) + (3 * prssi - 3); + /* clamp to positive */ + if (rssi_log < 0) + rssi_log = 0; + /* Compensate for errors in truncation/approximation by adding 1 */ + rssi_log++; + + stfm1000->rssi_dc_est_log = rssi_log; + stfm1000->signal_strength = stfm1000->rssi_dc_est_log; + + /* determine absolute value */ + if (tmp & 0x0800) + mpx_dc = ((tmp >> 16) & 0x0fff) | 0xf000; + else + mpx_dc = (tmp >> 16) & 0x0fff; + stfm1000->mpx_dc = mpx_dc; + mpx_dc = mpx_dc < 0 ? -mpx_dc : mpx_dc; + + v->signal = rssi_decoded & 0xffff; + +out: + mutex_unlock(&stfm1000->state_lock); + + return ret; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + (void)stfm1000; + + if (v->index > 0) + return -EINVAL; + + return 0; +} + +static int vidioc_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + mutex_lock(&stfm1000->state_lock); + + /* convert from the crazy linux value to our decimal based values */ + stfm1000->freq = (u32)div_u64((u64)(125 * (u64)f->frequency), 2000); + + if (stfm1000->active) + Take_Down(stfm1000); + + sw_tune(stfm1000, stfm1000->freq); + + if (stfm1000->active) + Bring_Up(stfm1000); + + mutex_unlock(&stfm1000->state_lock); + + return 0; +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + f->type = V4L2_TUNER_RADIO; + f->frequency = stfm1000->freq * 16; + + return 0; +} + +static int vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + int i; + + (void)stfm1000; + + for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { + if (qc->id && qc->id == radio_qctrl[i].id) { + memcpy(qc, &radio_qctrl[i], sizeof(*qc)); + return 0; + } + } + return -EINVAL; +} + +static int vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + switch (ctrl->id) { + + case V4L2_CID_AUDIO_MUTE: + ctrl->value = stfm1000->mute; + return 0; + + } + return -EINVAL; +} + +static int vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + int ret; + + mutex_lock(&stfm1000->state_lock); + + ret = -EINVAL; + + switch (ctrl->id) { + + case V4L2_CID_AUDIO_MUTE: + stfm1000->mute = ctrl->value; + ret = 0; + break; + } + + mutex_unlock(&stfm1000->state_lock); + + return ret; +} + +static int vidioc_g_audio(struct file *file, void *priv, + struct v4l2_audio *a) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + (void)stfm1000; + + if (a->index > 1) + return -EINVAL; + + strcpy(a->name, "Radio"); + a->capability = V4L2_AUDCAP_STEREO; + return 0; +} + +static int vidioc_s_audio(struct file *file, void *priv, + struct v4l2_audio *a) +{ + if (a->index > 1) + return -EINVAL; + return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + (void)stfm1000; + + *i = 0; + + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + (void)stfm1000; + + if (i != 0) + return -EINVAL; + + return 0; +} + +const struct v4l2_ioctl_ops stfm_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_queryctrl = vidioc_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_g_audio = vidioc_g_audio, + .vidioc_s_audio = vidioc_s_audio, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, +}; + +static int stfm1000_open(struct inode *inode, struct file *file) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + mutex_lock(&stfm1000->state_lock); + stfm1000->users = 1; + mutex_unlock(&stfm1000->state_lock); + + return 0; +} +static int stfm1000_close(struct inode *inode, struct file *file) +{ + struct stfm1000 *stfm1000 = stfm1000_from_file(file); + + if (!stfm1000) + return -ENODEV; + + stfm1000->users = 0; + if (stfm1000->removed) + kfree(stfm1000); + return 0; +} + +static const struct file_operations stfm1000_fops = { + .owner = THIS_MODULE, + .open = stfm1000_open, + .release = stfm1000_close, + .ioctl = video_ioctl2, +#ifdef CONFIG_COMPAT + .compat_ioctl = v4l_compat_ioctl32, +#endif + .llseek = no_llseek, +}; + +/* sysfs */ + +#define STFM1000_RO_ATTR(var) \ +static ssize_t stfm1000_show_ ## var(struct device *d, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct i2c_client *client = to_i2c_client(d); \ + struct stfm1000 *stfm1000 = i2c_get_clientdata(client); \ + return sprintf(buf, "%d\n", stfm1000->var); \ +} \ +static DEVICE_ATTR(var, 0444, stfm1000_show_ ##var, NULL) + +#define STFM1000_RW_ATTR(var) \ +static ssize_t stfm1000_show_ ## var(struct device *d, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct i2c_client *client = to_i2c_client(d); \ + struct stfm1000 *stfm1000 = i2c_get_clientdata(client); \ + return sprintf(buf, "%u\n", stfm1000->var); \ +} \ +static ssize_t stfm1000_store_ ## var(struct device *d, \ + struct device_attribute *attr, const char *buf, size_t size) \ +{ \ + struct i2c_client *client = to_i2c_client(d); \ + struct stfm1000 *stfm1000 = i2c_get_clientdata(client); \ + unsigned long v; \ + \ + strict_strtoul(buf, 0, &v); \ + stfm1000_commit_ ## var(stfm1000, v); \ + return size; \ +} \ +static DEVICE_ATTR(var, 0644, stfm1000_show_ ##var, stfm1000_store_ ##var) + +#define STFM1000_RW_ATTR_SIMPLE(var) \ +static void stfm1000_commit_ ## var(struct stfm1000 *stfm1000, \ + unsigned long value) \ +{ \ + stfm1000->var = value; \ +} \ +STFM1000_RW_ATTR(var) + +STFM1000_RO_ATTR(weak_signal); +STFM1000_RO_ATTR(pilot_present); +STFM1000_RO_ATTR(stereo); +STFM1000_RO_ATTR(rssi); +STFM1000_RO_ATTR(mpx_dc); +STFM1000_RO_ATTR(signal_strength); +STFM1000_RW_ATTR_SIMPLE(rds_signal_th); +STFM1000_RO_ATTR(rds_present); +STFM1000_RO_ATTR(is_station); + +static void stfm1000_commit_georegion(struct stfm1000 *stfm1000, + unsigned long value) +{ + /* don't do anything for illegal region */ + if (value != 0 && value != 1) + return; + + mutex_lock(&stfm1000->state_lock); + + stfm1000->georegion = value; + if (stfm1000->georegion == 0) + stfm1000_clear_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_DEEMPH_50_75B); + else + stfm1000_set_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_DEEMPH_50_75B); + + mutex_unlock(&stfm1000->state_lock); +} +STFM1000_RW_ATTR(georegion); + +static void stfm1000_commit_freq(struct stfm1000 *stfm1000, + unsigned long value) +{ + mutex_lock(&stfm1000->state_lock); + + /* clamp */ + if (value < STFM1000_FREQUENCY_100KHZ_MIN * 100) + value = STFM1000_FREQUENCY_100KHZ_MIN * 100; + else if (value > STFM1000_FREQUENCY_100KHZ_MAX * 100) + value = STFM1000_FREQUENCY_100KHZ_MAX * 100; + + stfm1000->freq = value; + + if (stfm1000->active) + Take_Down(stfm1000); + + sw_tune(stfm1000, stfm1000->freq); + + if (stfm1000->active) + Bring_Up(stfm1000); + + mutex_unlock(&stfm1000->state_lock); +} +STFM1000_RW_ATTR(freq); + +static void stfm1000_commit_mute(struct stfm1000 *stfm1000, + unsigned long value) +{ + stfm1000->mute = !!value; +} +STFM1000_RW_ATTR(mute); + +static void stfm1000_commit_force_mono(struct stfm1000 *stfm1000, + unsigned long value) +{ + stfm1000->force_mono = !!value; + /* set force mono parameters for the filter */ + stfm1000->filter_parms.pCoefForcedMono = stfm1000->force_mono; + + /* yeah, I know, it's stupid */ + stfm1000->rds_state.demod.pCoefForcedMono = + stfm1000->filter_parms.pCoefForcedMono; +} +STFM1000_RW_ATTR(force_mono); + +STFM1000_RW_ATTR_SIMPLE(monitor_period); +STFM1000_RW_ATTR_SIMPLE(quality_monitor); +STFM1000_RW_ATTR_SIMPLE(quality_monitor_period); +STFM1000_RW_ATTR_SIMPLE(agc_monitor_period); +STFM1000_RW_ATTR_SIMPLE(tune_rssi_th); +STFM1000_RW_ATTR_SIMPLE(tune_mpx_dc_th); + +static void stfm1000_commit_rds_enable(struct stfm1000 *stfm1000, + unsigned long value) +{ + /* don't do anything for illegal values (or for not TB2) */ + if ((value != 0 && value != 1) || + stfm1000->revid == STFM1000_CHIP_REV_TA2) + return; + + mutex_lock(&stfm1000->state_lock); + + stfm1000->rds_enable = value; + if (stfm1000->rds_enable == 0) + stfm1000_clear_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_RDS_ENABLE); + else + stfm1000_set_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_RDS_ENABLE); + + mutex_unlock(&stfm1000->state_lock); +} +STFM1000_RW_ATTR(rds_enable); + +static void stfm1000_commit_rds_sync(struct stfm1000 *stfm1000, + unsigned long value) +{ + stfm1000->rds_sync = stfm1000->rds_enable && !!value; +} +STFM1000_RW_ATTR(rds_sync); + +STFM1000_RW_ATTR_SIMPLE(rds_pkt_good); +STFM1000_RW_ATTR_SIMPLE(rds_pkt_bad); +STFM1000_RW_ATTR_SIMPLE(rds_pkt_recovered); +STFM1000_RW_ATTR_SIMPLE(rds_pkt_lost_sync); +STFM1000_RW_ATTR_SIMPLE(rds_bit_overruns); +STFM1000_RW_ATTR_SIMPLE(rds_info); + +static void stfm1000_commit_rds_sdnominal_adapt(struct stfm1000 *stfm1000, + unsigned long value) +{ + stfm1000->rds_sdnominal_adapt = !!value; + stfm1000->rds_state.demod.sdnom_adapt = stfm1000->rds_sdnominal_adapt; +} +STFM1000_RW_ATTR(rds_sdnominal_adapt); + +static void stfm1000_commit_rds_phase_pop(struct stfm1000 *stfm1000, + unsigned long value) +{ + stfm1000->rds_phase_pop = !!value; + stfm1000->rds_state.demod.PhasePoppingEnabled = + stfm1000->rds_phase_pop; +} +STFM1000_RW_ATTR(rds_phase_pop); + +STFM1000_RW_ATTR_SIMPLE(tuning_grid_50KHz); + +static ssize_t stfm1000_show_rds_ps(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(d); + struct stfm1000 *stfm1000 = i2c_get_clientdata(client); + char ps[9]; + + if (stfm1000_rds_get_ps(&stfm1000->rds_state, ps, sizeof(ps)) <= 0) + ps[0] = '\0'; + + return sprintf(buf, "%s\n", ps); +} +static DEVICE_ATTR(rds_ps, 0444, stfm1000_show_rds_ps, NULL); + +static ssize_t stfm1000_show_rds_text(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(d); + struct stfm1000 *stfm1000 = i2c_get_clientdata(client); + char text[65]; + + if (stfm1000_rds_get_text(&stfm1000->rds_state, text, + sizeof(text)) <= 0) + text[0] = '\0'; + + return sprintf(buf, "%s\n", text); +} +static DEVICE_ATTR(rds_text, 0444, stfm1000_show_rds_text, NULL); + +static struct device_attribute *stfm1000_attrs[] = { + &dev_attr_agc_monitor_period, + &dev_attr_force_mono, + &dev_attr_freq, + &dev_attr_georegion, + &dev_attr_is_station, + &dev_attr_monitor_period, + &dev_attr_mpx_dc, + &dev_attr_mute, + &dev_attr_pilot_present, + &dev_attr_quality_monitor, + &dev_attr_quality_monitor_period, + &dev_attr_rds_bit_overruns, + &dev_attr_rds_enable, + &dev_attr_rds_info, + &dev_attr_rds_phase_pop, + &dev_attr_rds_pkt_bad, + &dev_attr_rds_pkt_good, + &dev_attr_rds_pkt_lost_sync, + &dev_attr_rds_pkt_recovered, + &dev_attr_rds_present, + &dev_attr_rds_ps, + &dev_attr_rds_sdnominal_adapt, + &dev_attr_rds_signal_th, + &dev_attr_rds_sync, + &dev_attr_rds_text, + &dev_attr_rssi, + &dev_attr_signal_strength, + &dev_attr_stereo, + &dev_attr_tune_mpx_dc_th, + &dev_attr_tune_rssi_th, + &dev_attr_tuning_grid_50KHz, + &dev_attr_weak_signal, + NULL, +}; + +/* monitor thread */ + +static void rds_process(struct stfm1000 *stfm1000) +{ + int count, bit; + int mix_reg, sdnominal_reg; + u32 sdnom, sdnom_new, limit; + u8 buf[8]; + + if (!stfm1000->rds_enable) + return; + + if (stfm1000->rds_sync && + stfm1000->rssi_dc_est_log > stfm1000->rds_signal_th) { + if (stfm1000->rds_info) + printk(KERN_INFO "RDS: sync\n"); + stfm1000_rds_reset(&stfm1000->rds_state); + stfm1000->rds_demod_running = 1; + stfm1000->rds_sync = 0; + } + + if (!stfm1000->rds_demod_running) + return; + + /* process mix reg requests */ + spin_lock_irq(&stfm1000->rds_lock); + mix_reg = stfm1000_rds_mix_msg_get(&stfm1000->rds_state); + spin_unlock_irq(&stfm1000->rds_lock); + + if (mix_reg != -1) { + + if (stfm1000->rds_info) + printk(KERN_INFO "RDS: new RDS_MIXOFFSET %d\n", + mix_reg & 1); + + /* update register */ + if (mix_reg & 1) + stfm1000_set_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_RDS_MIXOFFSET); + else + stfm1000_clear_bits(stfm1000, STFM1000_INITIALIZATION2, + STFM1000_RDS_MIXOFFSET); + + /* signal it's processed */ + spin_lock_irq(&stfm1000->rds_lock); + stfm1000_rds_mix_msg_processed(&stfm1000->rds_state, mix_reg); + spin_unlock_irq(&stfm1000->rds_lock); + } + + /* process sdnominal reg requests */ + spin_lock_irq(&stfm1000->rds_lock); + sdnominal_reg = stfm1000_rds_sdnominal_msg_get(&stfm1000->rds_state); + spin_unlock_irq(&stfm1000->rds_lock); + + /* any change? */ + if (sdnominal_reg != 0) { + + stfm1000_read(stfm1000, STFM1000_SDNOMINAL, &sdnom); + + sdnom_new = sdnom + sdnominal_reg; + + /* Limit SDNOMINAL to within 244 ppm of its ideal value */ + limit = stfm1000->sdnominal_pivot + + (stfm1000->sdnominal_pivot >> 12); + if (sdnom_new > limit) + sdnom_new = limit; + + limit = stfm1000->sdnominal_pivot - + (stfm1000->sdnominal_pivot >> 12); + if (sdnom_new < limit) + sdnom_new = limit; + + /* write the register */ + stfm1000_write(stfm1000, STFM1000_SDNOMINAL, sdnom_new); + + /* signal it's processed */ + spin_lock_irq(&stfm1000->rds_lock); + stfm1000_rds_sdnominal_msg_processed(&stfm1000->rds_state, + sdnominal_reg); + spin_unlock_irq(&stfm1000->rds_lock); + } + + /* pump bits out & pass them to the process function */ + spin_lock_irq(&stfm1000->rds_lock); + while (stfm1000_rds_bits_available(&stfm1000->rds_state) > 128) { + count = 0; + while (count++ < 128 && + (bit = stmf1000_rds_get_bit( + &stfm1000->rds_state)) >= 0) { + spin_unlock_irq(&stfm1000->rds_lock); + + /* push bit for packet processing */ + stfm1000_rds_packet_bit(&stfm1000->rds_state, bit); + + spin_lock_irq(&stfm1000->rds_lock); + } + } + spin_unlock_irq(&stfm1000->rds_lock); + + /* now we're free to process non-interrupt related work */ + while (stfm1000_rds_packet_dequeue(&stfm1000->rds_state, buf) == 0) { + + if (stfm1000->rds_info) + printk(KERN_INFO "RDS-PKT: %02x %02x %02x %02x " + "%02x %02x %02x %02x\n", + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7]); + + stfm1000_rds_process_packet(&stfm1000->rds_state, buf); + } + + /* update our own counters */ + stfm1000->rds_pkt_good += stfm1000->rds_state.pkt.good_packets; + stfm1000->rds_pkt_bad += stfm1000->rds_state.pkt.bad_packets; + stfm1000->rds_pkt_recovered += + stfm1000->rds_state.pkt.recovered_packets; + stfm1000->rds_pkt_lost_sync += + stfm1000->rds_state.pkt.sync_lost_packets; + stfm1000->rds_bit_overruns += + stfm1000->rds_state.demod.RdsDemodSkippedBitCnt; + + /* zero them now */ + stfm1000->rds_state.pkt.good_packets = 0; + stfm1000->rds_state.pkt.bad_packets = 0; + stfm1000->rds_state.pkt.recovered_packets = 0; + stfm1000->rds_state.pkt.sync_lost_packets = 0; + stfm1000->rds_state.demod.RdsDemodSkippedBitCnt = 0; + + /* reset requested from RDS handler? */ + if (stfm1000_rds_get_reset_req(&stfm1000->rds_state)) { + if (stfm1000->rds_info) + printk(KERN_INFO "RDS: reset requested\n"); + stfm1000_rds_reset(&stfm1000->rds_state); + + stfm1000->rds_sync = stfm1000->rds_enable; /* force sync (if RDS) */ + stfm1000->rds_demod_running = 0; + stfm1000->rssi_dc_est_log = 0; + stfm1000->signal_strength = 0; + } +} + +void stfm1000_monitor_signal(struct stfm1000 *stfm1000, int bit) +{ + set_bit(bit, &stfm1000->thread_events); + return wake_up_interruptible(&stfm1000->thread_wait); +} + +static int stfm1000_monitor_thread(void *data) +{ + struct stfm1000 *stfm1000 = data; + int ret; + + printk(KERN_INFO "stfm1000: monitor thread started\n"); + + set_freezable(); + + /* Hmm, linux becomes *very* unhappy without this ... */ + while (!kthread_should_stop()) { + + ret = wait_event_interruptible_timeout(stfm1000->thread_wait, + stfm1000->thread_events == 0, + msecs_to_jiffies(stfm1000->monitor_period)); + + stfm1000->thread_events = 0; + + if (kthread_should_stop()) + break; + + try_to_freeze(); + + mutex_lock(&stfm1000->state_lock); + + /* we must be active */ + if (!stfm1000->active) + goto next; + + if (stfm1000->rds_enable) + rds_process(stfm1000); + + /* perform quality monitor */ + if (time_after_eq(jiffies, stfm1000->next_quality_monitor)) { + + /* full quality monitor? */ + if (stfm1000->quality_monitor) + Monitor_STFM_Quality(stfm1000); + else /* simple */ + Is_Station(stfm1000); + + while (time_after_eq(jiffies, + stfm1000->next_quality_monitor)) + stfm1000->next_quality_monitor += + msecs_to_jiffies( + stfm1000->quality_monitor_period); + } + + /* perform AGC monitor (if enabled) */ + if (stfm1000->agc_monitor && time_after_eq(jiffies, + stfm1000->next_agc_monitor)) { + Monitor_STFM_AGC(stfm1000); + while (time_after_eq(jiffies, + stfm1000->next_agc_monitor)) + stfm1000->next_agc_monitor += + msecs_to_jiffies( + stfm1000->agc_monitor_period); + } +next: + mutex_unlock(&stfm1000->state_lock); + } + + printk(KERN_INFO "stfm1000: monitor thread stopped\n"); + + return 0; +} + +static u64 stfm1000_dma_mask = DMA_32BIT_MASK; + +static int stfm1000_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct device *dev; + struct stfm1000 *stfm1000; + struct video_device *vd; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + int ret; + u32 id; + const char *idtxt; + int i; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_warn(&adapter->dev, + "I2C doesn't support I2C_FUNC_SMBUS_BYTE_DATA\n"); + return -EIO; + } + + /* make sure the dma masks are set correctly */ + dev = &client->dev; + if (!dev->dma_mask) + dev->dma_mask = &stfm1000_dma_mask; + if (!dev->coherent_dma_mask) + dev->coherent_dma_mask = DMA_32BIT_MASK; + + stfm1000 = kzalloc(sizeof(*stfm1000), GFP_KERNEL); + if (!stfm1000) + return -ENOMEM; + + stfm1000->client = client; + i2c_set_clientdata(client, stfm1000); + + mutex_init(&stfm1000->xfer_lock); + mutex_init(&stfm1000->state_lock); + + vd = &stfm1000->radio; + + strcpy(vd->name, "stfm1000"); + vd->vfl_type = VID_TYPE_TUNER; + vd->fops = &stfm1000_fops; + vd->ioctl_ops = &stfm_ioctl_ops; + + /* vd->debug = V4L2_DEBUG_IOCTL | V4L2_DEBUG_IOCTL_ARG; */ + + vd->parent = &client->dev; + + ret = video_register_device(vd, VFL_TYPE_RADIO, -1); + if (ret != 0) { + dev_warn(&adapter->dev, + "Cannot register radio device\n"); + goto out; + } + + spin_lock_init(&stfm1000->rds_lock); + + stfm1000_setup_reg_set(stfm1000); + + /* stfm1000->dbgflg |= STFM1000_DBGFLG_I2C; */ + + ret = stfm1000_read(stfm1000, STFM1000_CHIPID, &id); + if (ret < 0) { + dev_warn(&adapter->dev, + "Cannot read ID register\n"); + goto out; + } + stfm1000->revid = id & 0xff; + + /* NOTE: the tables are precalculated */ + stfm1000->tune_rssi_th = 28; + stfm1000->tune_mpx_dc_th = 300; + stfm1000->adj_chan_th = 100; + stfm1000->pilot_est_th = 25; + stfm1000->agc_monitor = 0; /* AGC monitor disabled */ + stfm1000->quality_monitor = 1; + stfm1000->weak_signal = 0; + stfm1000->prev_pilot_present = 0; + stfm1000->tune_cap_a_f = (u32)(72.4 * 65536); + stfm1000->tune_cap_b_f = (u32)(0.07 * 65536); + + /* only TB2 supports RDS */ + stfm1000->rds_enable = stfm1000->revid == STFM1000_CHIP_REV_TB2 && + rds_enable; + stfm1000->rds_present = 0; + stfm1000->rds_signal_th = 33; + + stfm1000->freq = 92600; + + stfm1000->georegion = georegion; + stfm1000->rssi = 0; + stfm1000->stereo = 0; + stfm1000->force_mono = 0; + stfm1000->monitor_period = 100; + stfm1000->quality_monitor_period = 1000; + stfm1000->agc_monitor_period = 200; + + stfm1000->rds_sdnominal_adapt = 0; + stfm1000->rds_phase_pop = 1; + + /* enable info about RDS */ + stfm1000->rds_info = 0; + + ret = stfm1000_power_up(stfm1000); + if (ret != 0) { + printk(KERN_ERR "%s: stfm1000_power_up failed\n", + __func__); + goto out; + } + + if (stfm1000_alsa_ops && stfm1000_alsa_ops->init) { + ret = (*stfm1000_alsa_ops->init)(stfm1000); + if (ret != 0) + goto out; + stfm1000->alsa_initialized = 1; + } + + ret = 0; + for (i = 0; stfm1000_attrs[i]; i++) { + ret = device_create_file(dev, stfm1000_attrs[i]); + if (ret) + break; + } + if (ret) { + while (--i >= 0) + device_remove_file(dev, stfm1000_attrs[i]); + goto out; + } + + /* add it to the list */ + mutex_lock(&devlist_lock); + stfm1000->idx = stfm1000_devcount++; + list_add_tail(&stfm1000->devlist, &stfm1000_devlist); + mutex_unlock(&devlist_lock); + + init_waitqueue_head(&stfm1000->thread_wait); + stfm1000->thread = kthread_run(stfm1000_monitor_thread, stfm1000, + "stfm1000-%d", stfm1000->idx); + if (stfm1000->thread == NULL) { + printk(KERN_ERR "stfm1000: kthread_run failed\n"); + goto out; + } + + idtxt = stfm1000_get_rev_txt(stfm1000->revid); + if (idtxt == NULL) + printk(KERN_INFO "STFM1000: Loaded for unknown revision id " + "0x%02x\n", stfm1000->revid); + else + printk(KERN_INFO "STFM1000: Loaded for revision %s\n", idtxt); + + return 0; + +out: + kfree(stfm1000); + return ret; +} + +static int stfm1000_remove(struct i2c_client *client) +{ + struct stfm1000 *stfm1000 = i2c_get_clientdata(client); + struct device *dev = &client->dev; + int i; + + kthread_stop(stfm1000->thread); + + for (i = 0; stfm1000_attrs[i]; i++) + device_remove_file(dev, stfm1000_attrs[i]); + + if (stfm1000->alsa_initialized) { + BUG_ON(stfm1000_alsa_ops->release == NULL); + (*stfm1000_alsa_ops->release)(stfm1000); + stfm1000->alsa_initialized = 0; + } + + stfm1000_power_down(stfm1000); + + video_unregister_device(&stfm1000->radio); + + mutex_lock(&devlist_lock); + list_del(&stfm1000->devlist); + mutex_unlock(&devlist_lock); + + kfree(stfm1000); + return 0; +} + +static const struct i2c_device_id stfm1000_id[] = { + { "stfm1000", 0xC0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, stfm1000_id); + +static struct i2c_driver stfm1000_i2c_driver = { + .driver = { + .name = "stfm1000", + }, + .probe = stfm1000_probe, + .remove = stfm1000_remove, + .id_table = stfm1000_id, +}; + +static int __init +stfm1000_init(void) +{ + /* pull those in */ + (void)Lock_Station; + (void)Unlock_Station; + return i2c_add_driver(&stfm1000_i2c_driver); +} + +static void __exit +stfm1000_exit(void) +{ + i2c_del_driver(&stfm1000_i2c_driver); + + stfm1000_alsa_ops = NULL; +} + +module_init(stfm1000_init); +module_exit(stfm1000_exit); + +MODULE_AUTHOR("Pantelis Antoniou"); +MODULE_DESCRIPTION("A driver for the STFM1000 chip."); +MODULE_LICENSE("GPL"); + +module_param(georegion, int, 0400); +module_param(rds_enable, int, 0400); diff --git a/drivers/media/radio/stfm1000/stfm1000-filter.c b/drivers/media/radio/stfm1000/stfm1000-filter.c new file mode 100644 index 000000000000..df42524a5da7 --- /dev/null +++ b/drivers/media/radio/stfm1000/stfm1000-filter.c @@ -0,0 +1,860 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/init.h> + +#include "stfm1000.h" + +void stfm1000_filter_reset(struct stfm1000_filter_parms *sdf) +{ + sdf->Left = 0; + sdf->Right = 0; + sdf->RssiDecoded = 0; + sdf->RssiMant = 0; + sdf->RssiExp = 0; + sdf->RssiLb = 0; + sdf->TrueRssi = 0; + sdf->Prssi = 0; + sdf->RssiLog = 0; + sdf->ScaledTrueRssi = 0; + sdf->FilteredRssi = 0; + sdf->PrevFilteredRssi = 0; + sdf->DecRssi = 0; + sdf->ScaledRssiDecoded = 0; + sdf->ScaledRssiDecodedZ = 0; + sdf->ScaledRssiDecodedZz = 0; + sdf->Echo = 0; + sdf->EchoLb = 0; + sdf->TrueEcho = 0; + sdf->FilteredEchoLpr = 0; + sdf->PrevFilteredEchoLpr = 0; + sdf->FilteredEchoLmr = 0; + sdf->PrevFilteredEchoLmr = 0; + sdf->GatedEcho = 0; + sdf->ControlLpr = 0; + sdf->ControlLmr = 0; + sdf->LprBw = 0; + sdf->LmrBw = 0; + + sdf->LprXz = 0; + sdf->LprXzz = 0; + sdf->LprYz = 0; + sdf->LprYzz = 0; + sdf->LmrXz = 0; + sdf->LmrXzz = 0; + sdf->LmrYz = 0; + sdf->LmrYzz = 0; + sdf->FilteredLpr = 0; + sdf->FilteredLmr = 0; + + sdf->B0B = 0; + sdf->B0S = 0; + sdf->B0M = 0; + sdf->B1over2B = 0; + sdf->B1over2S = 0; + sdf->B1over2M = 0; + sdf->A1over2B = 0; + sdf->A1over2S = 0; + sdf->A1over2M = 0; + sdf->A2B = 0; + sdf->A2S = 0; + sdf->A2M = 0; + + sdf->AdjBw = 0; + + sdf->pCoefLprBwThLo = 20 << 8; + sdf->pCoefLprBwThHi = 30 << 8; + sdf->pCoefLmrBwThLo = 40 << 8; + sdf->pCoefLmrBwThHi = 50 << 8; + sdf->pCoefLprBwSlSc = 4800; /* SDK-2287 */ + sdf->pCoefLprBwSlSh = 10; /* SDK-2287 */ + sdf->pCoefLmrBwSlSc = 4800; /* SDK-2287 */ + sdf->pCoefLmrBwSlSh = 10; /* SDK-2287 */ + sdf->pCoefLprGaSlSc = 0; + sdf->pCoefLprGaSlSh = 0; + + sdf->ScaledControlLmr = 0; + + sdf->LprGa = 32767; + sdf->LmrGa = 32767; + + sdf->pCoefLprGaTh = 20; /* 25 */ + sdf->pCoefLmrGaTh = 55; /* 60 50 */ + + sdf->MuteAudio = 0; + sdf->PrevMuteAudio = 0; + sdf->MuteActionFlag = 0; + sdf->ScaleAudio = 0; + + /* *** Programmable initial setup for stereo path filters */ + sdf->LprB0 = 18806; /* -3dB cutoff = 17 kHz */ + sdf->LprB1over2 = 18812; /* -3dB cutoff = 17 kHz */ + sdf->LprA1over2 = -16079; /* -3dB cutoff = 17 kHz */ + sdf->LprA2 = -11125; /* -3dB cutoff = 17 kHz */ + sdf->LmrB0 = 18806; /* -3dB cutoff = 17 kHz */ + sdf->LmrB1over2 = 18812; /* -3dB cutoff = 17 kHz */ + sdf->LmrA1over2 = -16079; /* -3dB cutoff = 17 kHz */ + sdf->LmrA2 = -11125; /* -3dB cutoff = 17 kHz */ + + sdf->pCoefForceLockLmrBw = 0; /* Force Lock LMR BW = LPR BW + * XXX BUG WARNING - + * This control doesn't work! */ + + sdf->pCoefForcedMono = 0; /* Do not set this = + * Quality Monitor will overwrite it */ + sdf->pCoefBypassBlend = 0; /* BUG WARNING - + * This control doesn't work! */ + sdf->pCoefBypassSoftmute = 0; /* BUG WARNING - + * This control doesn't work! */ + sdf->pCoefBypassBwCtl = 0; /* BUG WARNING - + * This control doesn't work! */ + + /* There's a bug or something in the attack/decay section b/c + * setting these coef's to anything */ + /* higher than 100ms or so causes the RSSI to be artificially low - + * Needs investigation! 15DEC06 */ + sdf->pCoefRssiAttack = 65386; /* changed to 100ms to avoid + * stereo crackling + * 60764 corresponds to 3 */ + sdf->pCoefRssiDecay = 65386; /* changed to 100ms to avoid + * stereo crackling + * 65530 corresponds to 10 */ + sdf->pCoefEchoLprAttack = 52239; /* corresponds to 1 */ + sdf->pCoefEchoLprDecay = 64796; /* corresponds to 20 */ + sdf->pCoefEchoLmrAttack = 52239; /* corresponds to 1 */ + sdf->pCoefEchoLmrDecay = 65520; /* corresponds to 20 */ + sdf->pCoefEchoTh = 100; + sdf->pCoefEchoScLpr = (u16) (0.9999 * 32767.0); + sdf->pCoefEchoScLmr = (u16) (0.9999 * 32767.0); +} + +void stfm1000_filter_decode(struct stfm1000_filter_parms *sdf, s16 Lpr, + s16 Lmr, u16 Rssi) +{ + s16 temp1_reg; /* mimics 16 bit register */ + s16 temp2_reg; /* mimics 16 bit register */ + s16 temp3_reg; /* mimics 16 bit register */ + s16 temp4_reg; /* mimics 16 bit register */ +#ifndef _TUNER_STFM_MUTE + s16 temp5_reg; /* mimics 16 bit register */ +#endif + s32 temp2_reg_32; /*eI 108 27th Feb 06 temp variables. */ + + /* **************************************************************** */ + /* *** Stereo Processing ****************************************** */ + /* **************************************************************** */ + /* *** This block operates at Fs = 44.1kHz */ + /* ******** */ + /* *** LPR path filter (2nd order IIR) */ + + sdf->Acc_signed = sdf->LprB0 * Lpr + 2 * (sdf->LprB1over2 * sdf->LprXz) + + sdf->LprB0 * sdf->LprXzz + 2 * (sdf->LprA1over2 * sdf->LprYz) + + sdf->LprA2 * sdf->LprYzz; + + sdf->FilteredLpr = sdf->Acc_signed >> 15; + + sdf->LprXzz = sdf->LprXz; /* update taps */ + sdf->LprXz = Lpr; + sdf->LprYzz = sdf->LprYz; + sdf->LprYz = sdf->FilteredLpr; + + /* *** LMR path filter (2nd order IIR) */ + sdf->Acc_signed = sdf->LmrB0 * Lmr + 2 * (sdf->LmrB1over2 * sdf->LmrXz) + + sdf->LmrB0 * sdf->LmrXzz + 2 * (sdf->LmrA1over2 * sdf->LmrYz) + + sdf->LmrA2 * sdf->LmrYzz; + + sdf->FilteredLmr = sdf->Acc_signed >> 15; + + sdf->LmrXzz = sdf->LmrXz; /* update taps */ + sdf->LmrXz = Lmr; + sdf->LmrYzz = sdf->LmrYz; + sdf->LmrYz = sdf->FilteredLmr; + + /* *** Stereo Matrix */ + if (0 == sdf->pCoefBypassBlend) + temp1_reg = sdf->LmrGa * sdf->FilteredLmr >> 15; /* Blend */ + else + temp1_reg = sdf->FilteredLmr; + + if (sdf->pCoefForcedMono) /* Forced Mono */ + temp1_reg = 0; + + if (0 == sdf->pCoefBypassSoftmute) { + temp2_reg = sdf->LprGa * sdf->FilteredLpr >> 15; /* LPR */ + temp3_reg = sdf->LprGa * temp1_reg >> 15; /* LMR */ + } else { + temp2_reg = sdf->FilteredLpr; + temp3_reg = temp1_reg; + } + + temp4_reg = (temp2_reg + temp3_reg) / 2; /* Matrix */ + +#ifndef _TUNER_STFM_MUTE + temp5_reg = (temp2_reg - temp3_reg) / 2; +#endif + +#if 0 + /* *** DC Cut Filter (leaky bucket estimate) */ + if (0 == sdf->pCoefBypassDcCut) { + sdf->LeftLb_i32 = + sdf->LeftLb_i32 + temp4_reg - (sdf->LeftLb_i32 >> 8); + temp2_reg = temp4_reg - (sdf->LeftLb_i32 >> 8); /* signal - + dc_estimate */ + + sdf->RightLb_i32 = + sdf->RightLb_i32 + temp5_reg - (sdf->RightLb_i32 >> 8); + temp3_reg = temp5_reg - (sdf->RightLb_i32 >> 8); /* signal - + dc_estimate */ + } else { + temp2_reg = temp4_reg; + temp3_reg = temp5_reg; + } +#endif +#ifdef _TUNER_STFM_MUTE + /* *** Mute Audio */ + if (sdf->MuteAudio != sdf->PrevMuteAudio) /* Mute transition */ + sdf->MuteActionFlag = 1; /* set flag */ + sdf->PrevMuteAudio = sdf->MuteAudio; /* update history */ + + if (sdf->MuteActionFlag) { + if (0 == sdf->MuteAudio) { /* Mute to zero */ + /* gradual mute down */ + sdf->ScaleAudio = sdf->ScaleAudio - sdf->pCoefMuteStep; + + /* eI-117:Oct28:as per C++ code */ + /* if (0 < sdf->ScaleAudio) */ + if (0 > sdf->ScaleAudio) { + sdf->ScaleAudio = 0; /* Minimum scale + * factor */ + sdf->MuteActionFlag = 0; /* End Mute Action */ + } + } else { /* Un-Mute to one */ + /* gradual mute up */ + sdf->ScaleAudio = sdf->ScaleAudio + sdf->pCoefMuteStep; + if (0 > sdf->ScaleAudio) { /* look for rollover + * beyong 32767 */ + sdf->ScaleAudio = 32767; /* Maximum scale + * factor */ + sdf->MuteActionFlag = 0; /* End Mute Action */ + } + } /* end else */ + } /* end if (sdf->MuteActionFlag) */ + +/*! Output Processed Sample */ + + sdf->Left = (temp2_reg * sdf->ScaleAudio) >> 15; /* Scale */ + sdf->Right = (temp3_reg * sdf->ScaleAudio) >> 15; /* Scale */ + +#else /* !_TUNER_STFM_MUTE */ + + sdf->Left = temp4_reg; + sdf->Right = temp5_reg; + +#endif /* !_TUNER_STFM_MUTE */ + + /* *** End Stereo Processing ************************************** */ + /* **************************************************************** */ + + /* **************************************************************** */ + /* *** Signal Quality Indicators ********************************** */ + /* **************************************************************** */ + /* *** This block operates at Fs = 44.1kHz */ + /* ******** */ + /* *** RSSI */ + /* ******** */ + /* Decode Floating Point RSSI data */ + /*! Input RSSI sample */ + sdf->RssiMant = (Rssi & 0xFFE0) >> 5; /* 11 msb's */ + sdf->RssiExp = Rssi & 0x001F; /* 5 lsb's */ + sdf->RssiDecoded = sdf->RssiMant << sdf->RssiExp; + + /* *** Convert RSSI to 10*Log10(RSSI) */ + /* This is easily accomplished in DSP code using the CLZ instruction */ + /* rather than using all these comparisons. */ + /* The basic idea is this: */ + /* if x >= 2^P */ + /* f(x) = 3*x>>P + (3*P-3) */ + /* Approx. is valid over the range of sdf->RssiDecoded in [0, 2^21] */ + /* *** */ + if (sdf->RssiDecoded >= 1048576) + sdf->Prssi = 20; + else if (sdf->RssiDecoded >= 524288) + sdf->Prssi = 19; + else if (sdf->RssiDecoded >= 262144) + sdf->Prssi = 18; + else if (sdf->RssiDecoded >= 131072) + sdf->Prssi = 17; + else if (sdf->RssiDecoded >= 65536) + sdf->Prssi = 16; + else if (sdf->RssiDecoded >= 32768) + sdf->Prssi = 15; + else if (sdf->RssiDecoded >= 16384) + sdf->Prssi = 14; + else if (sdf->RssiDecoded >= 8192) + sdf->Prssi = 13; + else if (sdf->RssiDecoded >= 4096) + sdf->Prssi = 12; + else if (sdf->RssiDecoded >= 2048) + sdf->Prssi = 11; + else if (sdf->RssiDecoded >= 1024) + sdf->Prssi = 10; + else if (sdf->RssiDecoded >= 512) + sdf->Prssi = 9; + else if (sdf->RssiDecoded >= 256) + sdf->Prssi = 8; + else if (sdf->RssiDecoded >= 128) + sdf->Prssi = 7; + else if (sdf->RssiDecoded >= 64) + sdf->Prssi = 6; + else if (sdf->RssiDecoded >= 32) + sdf->Prssi = 5; + else if (sdf->RssiDecoded >= 16) + sdf->Prssi = 4; + else if (sdf->RssiDecoded >= 8) + sdf->Prssi = 3; + else if (sdf->RssiDecoded >= 4) + sdf->Prssi = 2; + else if (sdf->RssiDecoded >= 2) + sdf->Prssi = 1; + else + sdf->Prssi = 0; + sdf->RssiLog = + (3 * sdf->RssiDecoded >> sdf->Prssi) + (3 * sdf->Prssi - 3); + + if (0 > sdf->RssiLog) /* Clamp to positive */ + sdf->RssiLog = 0; + + /* Compensate for errors in truncation/approximation by adding 1 */ + sdf->RssiLog = sdf->RssiLog + 1; + + /* Leaky Bucket Filter DC estimate of RSSI */ + sdf->RssiLb = sdf->RssiLb + sdf->RssiLog - (sdf->RssiLb >> 3); + sdf->TrueRssi = sdf->RssiLb >> 3; + + /* Scale up so we have some room for precision */ + sdf->ScaledTrueRssi = sdf->TrueRssi << 8; + /* ************ */ + /* *** end RSSI */ + /* ************ */ + + /* ******** */ + /* *** Echo */ + /* ******** */ + /* *** Isolate Echo information as higher frequency info */ + /* using [1 -2 1] highpass FIR */ + sdf->ScaledRssiDecoded = sdf->RssiDecoded >> 4; + sdf->Echo = + (s16) ((sdf->ScaledRssiDecoded - + 2 * sdf->ScaledRssiDecodedZ + sdf->ScaledRssiDecodedZz)); + sdf->ScaledRssiDecodedZz = sdf->ScaledRssiDecodedZ; + sdf->ScaledRssiDecodedZ = sdf->ScaledRssiDecoded; + /* ************ */ + /* *** end Echo */ + /* ************ */ + /* *** End Signal Quality Indicators ******************************* */ + /* ***************************************************************** */ + + /* ***************************************************************** */ + /* *** Weak Signal Processing ************************************** */ + /* ***************************************************************** */ + /* *** This block operates at Fs = 44.1/16 = 2.75 Khz + * *eI 108 28th Feb 06 WSP and SM executes at 2.75Khz */ + /* decimate by 16 STFM_FILTER_BLOCK_MULTIPLE is 16 */ + if (0 == sdf->DecRssi) { + /* *** Filter RSSI via attack/decay structure */ + if (sdf->ScaledTrueRssi > sdf->PrevFilteredRssi) + sdf->Acc = + sdf->pCoefRssiAttack * + sdf->PrevFilteredRssi + (65535 - + sdf->pCoefRssiAttack) + * sdf->ScaledTrueRssi; + else + sdf->Acc = + sdf->pCoefRssiDecay * + sdf->PrevFilteredRssi + (65535 - + sdf->pCoefRssiDecay) + * sdf->ScaledTrueRssi; + + sdf->FilteredRssi = sdf->Acc >> 16; + sdf->PrevFilteredRssi = sdf->FilteredRssi; + + /* *** Form Echo "energy" representation */ + if (0 > sdf->Echo) + sdf->Echo = -sdf->Echo; /* ABS() */ + + /* Threshold compare */ + sdf->GatedEcho = (s16) (sdf->Echo - sdf->pCoefEchoTh); + if (0 > sdf->GatedEcho) /* Clamp to (+)ve */ + sdf->GatedEcho = 0; + + /* *** Leaky bucket DC estimate of Echo energy */ + sdf->EchoLb = sdf->EchoLb + sdf->GatedEcho - + (sdf->EchoLb >> 3); + sdf->TrueEcho = sdf->EchoLb >> 3; + + /* *** Filter Echo via attack/decay structure for LPR */ + if (sdf->TrueEcho > sdf->PrevFilteredEchoLpr) + sdf->Acc = + sdf->pCoefEchoLprAttack * + sdf->PrevFilteredEchoLpr + + (65535 - sdf->pCoefEchoLprAttack) * + sdf->TrueEcho; + else + sdf->Acc = + sdf->pCoefEchoLprDecay * + sdf->PrevFilteredEchoLpr + + (65535 - sdf->pCoefEchoLprDecay) * + sdf->TrueEcho; + + sdf->FilteredEchoLpr = sdf->Acc >> 16; + sdf->PrevFilteredEchoLpr = sdf->FilteredEchoLpr; + + /* *** Filter Echo via attack/decay structure for LMR */ + if (sdf->TrueEcho > sdf->PrevFilteredEchoLmr) + sdf->Acc = sdf->pCoefEchoLmrAttack * + sdf->PrevFilteredEchoLmr + + (65535 - sdf->pCoefEchoLmrAttack) + * sdf->TrueEcho; + else + sdf->Acc = + sdf->pCoefEchoLmrDecay * + sdf->PrevFilteredEchoLmr + (65535 - + sdf->pCoefEchoLmrDecay) + * sdf->TrueEcho; + + sdf->FilteredEchoLmr = sdf->Acc >> 16; + sdf->PrevFilteredEchoLmr = sdf->FilteredEchoLmr; + + /* *** Form control variables */ + /* Generically speaking, ctl = f(RSSI, Echo) = + * RSSI - (a*Echo)<<b, where a,b are programmable */ + sdf->ControlLpr = sdf->FilteredRssi - + ((sdf->pCoefEchoScLpr * + sdf->FilteredEchoLpr << sdf->pCoefEchoShLpr) >> 15); + if (0 > sdf->ControlLpr) + sdf->ControlLpr = 0; /* Clamp to positive */ + + sdf->ControlLmr = sdf->FilteredRssi - + ((sdf->pCoefEchoScLmr * + sdf->FilteredEchoLmr << sdf->pCoefEchoShLmr) >> 15); + if (0 > sdf->ControlLmr) + sdf->ControlLmr = 0; /* Clamp to positive */ + + /* *** Define LPR_BW = f(control LPR) */ + /* Assume that 5 kHz and 17 kHz are limits of LPR_BW control */ + if (sdf->ControlLpr <= sdf->pCoefLprBwThLo) + sdf->LprBw = 5000; /* lower limit is 5 kHz */ + else if (sdf->ControlLpr >= sdf->pCoefLprBwThHi) + sdf->LprBw = 17000; /* upper limit is 17 kHz */ + else + sdf->LprBw = 17000 - + ((sdf->pCoefLprBwSlSc * + (sdf->pCoefLprBwThHi - + sdf->ControlLpr)) >> sdf->pCoefLprBwSlSh); + + /* *** Define LMR_BW = f(control LMR) */ + /* Assume that 5 kHz and 17 kHz are limits of LPR_BW control */ + if (0 == sdf->pCoefForceLockLmrBw) { /* only do these calc's + * if LMR BW not + * ForceLocked */ + if (sdf->ControlLmr <= sdf->pCoefLmrBwThLo) + sdf->LmrBw = 5000; /* lower limit is + * 5 kHz */ + else if (sdf->ControlLmr >= sdf->pCoefLmrBwThHi) + sdf->LmrBw = 17000; /* upper limit is + * 17 kHz */ + else + sdf->LmrBw = 17000 - + ((sdf->pCoefLmrBwSlSc * + (sdf->pCoefLmrBwThHi - + sdf->ControlLmr)) >> + sdf->pCoefLmrBwSlSh); + } + /* *** Define LMR_Gain = f(control LMR) + * Assume that Blending occurs across 20 dB range of + * control LMR. For sake of listenability, approximate + * antilog blending curve + * To simplify antilog approx, scale control LMR back into + * "RSSI in dB range" [0,60] */ + sdf->ScaledControlLmr = sdf->ControlLmr >> 8; + + /* how far below blend threshold are we? */ + temp1_reg = sdf->pCoefLmrGaTh - sdf->ScaledControlLmr; + if (0 > temp1_reg) /* We're not below threshold, + * so no blending needed */ + temp1_reg = 0; + temp2_reg = 20 - temp1_reg; /* Blend range = 20 dB */ + if (0 > temp2_reg) + temp2_reg = 0; /* if beyond that range, + * then clamp to 0 */ + + /* We want stereo separation (n dB) to rolloff linearly over + * the 20 dB wide blend region. + * this necessitates a particular rolloff for the blend + * parameter, which is not obvious. + * See sw_audio/log_approx.m for calculation of this rolloff, + * implemented below... + * Note that stereo_separation (in dB) = 20*log10((1+a)/(1-a)), + * where a = blend scaler + * appropriately scaled for 2^15. This relationship sits at + * the heart of why this curve is needed. */ + if (15 <= temp2_reg) + temp3_reg = 264 * temp2_reg + 27487; + else if (10 <= temp2_reg) + temp3_reg = 650 * temp2_reg + 21692; + else if (5 <= temp2_reg) + temp3_reg = 1903 * temp2_reg + 9166; + else + temp3_reg = 3736 * temp2_reg; + + sdf->LmrGa = temp3_reg; + + if (32767 < sdf->LmrGa) + sdf->LmrGa = 32767; /* Clamp to '1' */ + + /* *** Define LPR_Gain = f(control LPR) + * Assume that SoftMuting occurs across 20 dB range of + * control LPR + * For sake of listenability, approximate antilog softmute + * curve To simplify antilog approx, scale control LPR back + * into "RSSI in dB range" [0,60] */ + sdf->ScaledControlLpr = sdf->ControlLpr >> 8; + /* how far below softmute threshold are we? */ + temp1_reg = sdf->pCoefLprGaTh - sdf->ScaledControlLpr; + if (0 > temp1_reg) /* We're not below threshold, + * so no softmute needed */ + temp1_reg = 0; + temp2_reg = 20 - temp1_reg; /* SoftmMute range = 20 dB */ + if (0 > temp2_reg) + temp2_reg = 0; /* if beyond that range, + * then clamp to 0 */ + /* Form 100*10^((temp2_reg-20)/20) approximation (antilog) + * over range [0,20] dB + * approximation in range [0,100], but we only want to + * softmute down to -20 dB, no further */ + if (16 < temp2_reg) + temp3_reg = 9 * temp2_reg - 80; + else if (12 < temp2_reg) + temp3_reg = 6 * temp2_reg - 33; + else if (8 < temp2_reg) + temp3_reg = 4 * temp2_reg - 8; + else + temp3_reg = 2 * temp2_reg + 9; + + sdf->LprGa = 328 * temp3_reg; /* close to 32767*(1/100) */ + + if (32767 < sdf->LprGa) + sdf->LprGa = 32767; /* Clamp to '1' */ + + if (3277 > sdf->LprGa) + sdf->LprGa = 3277; /* Clamp to 0.1*32767 = + * -20 dB min gain */ + + /* *************** Bandwidth Sweep Algorithm ************ */ + /* *** Calculate 2nd order filter coefficients as function + * of desired BW. We do this by constructing piece-wise + * linear filter coef's as f(BW), which is why we break the + * calc's into different BW regions below. + * coef(BW) = S*(M*BW + B) + * For more info, see sw_audio/ws_filter.m checked into CVS */ + if (0 == sdf->pCoefBypassBwCtl) { /* if ==1, then we just go + * with default coef set */ + /* determine if we run thru loop once or twice... */ + if (1 == sdf->pCoefForceLockLmrBw) + temp4_reg = 1; /* run thru once only to calc. + * LPR coef's */ + else + temp4_reg = 2; /* run thru twice to calc. + * LPR and LMR coef's */ + + /* Here's the big coef. calc. loop */ + for (temp1_reg = 0; temp1_reg < temp4_reg; + temp1_reg++) { + + if (0 == temp1_reg) + temp2_reg = (s16) sdf->LprBw; + else + temp2_reg = (s16) sdf->LmrBw; + + + if (6000 > temp2_reg) { + /* interval = [4.4kHz, 6.0kHz) */ + sdf->B0M = 22102; + sdf->B0B = -2209; + sdf->B0S = 1; + + sdf->B1over2M = 22089; + sdf->B1over2B = -2205; + sdf->B1over2S = 1; + + sdf->A1over2M = 31646; + sdf->A1over2B = -15695; + sdf->A1over2S = 2; + + sdf->A2M = -24664; + sdf->A2B = 11698; + sdf->A2S = 2; + } else if (8000 > temp2_reg) { + /* interval = [6.0kHz, 8.0kHz) */ + sdf->B0M = 22102; + sdf->B0B = -2209; + sdf->B0S = 1; + + sdf->B1over2M = 22089; + sdf->B1over2B = -2205; + sdf->B1over2S = 1; + + sdf->A1over2M = 31646; + sdf->A1over2B = -15695; + sdf->A1over2S = 2; + + sdf->A2M = -31231; + sdf->A2B = 18468; + sdf->A2S = 1; + } else if (10000 > temp2_reg) { + /* interval = [8.0kHz, 10.0kHz) */ + sdf->B0M = 28433; + sdf->B0B = -4506; + sdf->B0S = 1; + + sdf->B1over2M = 28462; + sdf->B1over2B = -4584; + sdf->B1over2S = 1; + + sdf->A1over2M = 31646; + sdf->A1over2B = -15695; + sdf->A1over2S = 2; + + sdf->A2M = -14811; + sdf->A2B = 12511; + sdf->A2S = 1; + } else if (12000 > temp2_reg) { + /* interval = [10.0kHz, 12.0kHz) */ + sdf->B0M = 28433; + sdf->B0B = -4506; + sdf->B0S = 1; + + sdf->B1over2M = 28462; + sdf->B1over2B = -4584; + sdf->B1over2S = 1; + + sdf->A1over2M = 31646; + sdf->A1over2B = -15695; + sdf->A1over2S = 2; + + sdf->A2M = -181; + sdf->A2B = 5875; + sdf->A2S = 1; + } else if (14000 > temp2_reg) { + /* interval = [12.0kHz, 14.0kHz) */ + sdf->B0M = 18291; + sdf->B0B = -4470; + sdf->B0S = 2; + + sdf->B1over2M = 18461; + sdf->B1over2B = -4597; + sdf->B1over2S = 2; + + sdf->A1over2M = 31646; + sdf->A1over2B = -15695; + sdf->A1over2S = 2; + + sdf->A2M = 14379; + sdf->A2B = -2068; + sdf->A2S = 1; + } else if (16000 > temp2_reg) { + /* interval = [14.0kHz, 16.0kHz) */ + sdf->B0M = 18291; + sdf->B0B = -4470; + sdf->B0S = 2; + + sdf->B1over2M = 18461; + sdf->B1over2B = -4597; + sdf->B1over2S = 2; + + sdf->A1over2M = 31646; + sdf->A1over2B = -15695; + sdf->A1over2S = 2; + + sdf->A2M = 30815; + sdf->A2B = -12481; + sdf->A2S = 1; + } else if (18000 > temp2_reg) { + /* interval = [16.0kHz, 18.0kHz) */ + sdf->B0M = 24740; + sdf->B0B = -9152; + sdf->B0S = 2; + + sdf->B1over2M = 24730; + sdf->B1over2B = -9142; + sdf->B1over2S = 2; + + sdf->A1over2M = 31646; + sdf->A1over2B = -15695; + sdf->A1over2S = 2; + + sdf->A2M = 25631; + sdf->A2B = -13661; + sdf->A2S = 2; + } else { + /* interval = [18.0kHz, 19.845kHz) */ + sdf->B0M = 24740; + sdf->B0B = -9152; + sdf->B0S = 2; + + sdf->B1over2M = 24730; + sdf->B1over2B = -9142; + sdf->B1over2S = 2; + + sdf->A1over2M = 31646; + sdf->A1over2B = -15695; + sdf->A1over2S = 2; + + sdf->A2M = 19382; + sdf->A2B = -12183; + sdf->A2S = 4; + } + + if (0 == temp1_reg) { + /* The piece-wise linear eq's are + * based on a scaled version + * (32768/22050) of BW */ + + /* Note 32768/22050 <-> 2*(16384/22050) + * <-> 2*((16384/22050)*32768)>>15 */ + sdf->AdjBw = ((temp2_reg << 1) * + 24348) >> 15; + + /* temp = mx */ + temp3_reg = (sdf->B0M * + sdf->AdjBw) >> 15; + + /* y = S*(mx + b) */ + sdf->LprB0 = sdf->B0S * + (temp3_reg + sdf->B0B); + + /* temp = mx */ + temp3_reg = (sdf->B1over2M * + sdf->AdjBw) >> 15; + + /* y = S*(mx + b) */ + sdf->LprB1over2 = sdf->B1over2S * + (temp3_reg + sdf->B1over2B); + + /* temp = mx */ + temp3_reg = (sdf->A1over2M * + sdf->AdjBw) >> 15; + + /* y = S*(mx + b) */ + sdf->LprA1over2 = -sdf->A1over2S * + (temp3_reg + sdf->A1over2B); + + /* temp = mx */ + temp3_reg = (sdf->A2M * + sdf->AdjBw) >> 15; + + /* y = S*(mx + b) */ + sdf->LprA2 = -sdf->A2S * + (temp3_reg + sdf->A2B); + /* *** end LPR channel -- + * LPR coefficients now ready for + * Stereo Path next time */ + } else { + /* The piece-wise linear eq's are + * based on a scaled version + * (32768/22050) of BW */ + + /* Note 32768/22050 <-> 2*(16384/22050) + * <-> 2*((16384/22050)*32768)>>15 */ + sdf->AdjBw = ((temp2_reg << 1) * + 24348) >> 15; + + /* temp = mx */ + temp3_reg = (sdf->B0M * + sdf->AdjBw) >> 15; + + /* y = S*(mx + b) */ + sdf->LmrB0 = sdf->B0S * + (temp3_reg + sdf->B0B); + + /* temp = mx */ + temp3_reg = (sdf->B1over2M * + sdf->AdjBw) >> 15; + + /* y = S*(mx + b) */ + sdf->LmrB1over2 = sdf->B1over2S * + (temp3_reg + sdf->B1over2B); + + /* temp = mx */ + temp3_reg = (sdf->A1over2M * + sdf->AdjBw) >> 15; + + /* y = S*(mx + b) */ + sdf->LmrA1over2 = -sdf->A1over2S * + (temp3_reg + sdf->A1over2B); + + /* temp = mx */ + temp3_reg = (sdf->A2M * + sdf->AdjBw) >> 15; + + /* y = S*(mx + b) */ + sdf->LmrA2 = -sdf->A2S * + (temp3_reg + sdf->A2B); + /* *** end LMR channel -- LMR + * coefficients now ready for Stereo + * Path next time */ + } + } /* end for (temp1_reg=0... */ + if (1 == sdf->pCoefForceLockLmrBw) { + /* if Force Lock LMR BW = LPR BW */ + /* then set LMR coef's = LPR coef's */ + sdf->LmrB0 = sdf->LprB0; + sdf->LmrB1over2 = sdf->LprB1over2; + sdf->LmrA1over2 = sdf->LprA1over2; + sdf->LmrA2 = sdf->LprA2; + } + + } /* end if (0 == sdf->pCoef_BypassBwCtl) */ + /* eI 108 24th Feb 06 Streo Matrix part moved after + * weak signal processing. */ + if (0 == sdf->pCoefBypassBlend) + temp1_reg = sdf->LmrGa; /* Blend */ + else + temp1_reg = 1; + + if (sdf->pCoefForcedMono) /* Forced Mono */ + temp1_reg = 0; + + if (0 == sdf->pCoefBypassSoftmute) { + + /* SoftMute applied to LPR */ + sdf->temp2_reg_sm = sdf->LprGa; + + temp2_reg_32 = sdf->LprGa * temp1_reg; + + /* SoftMute applied to LMR */ + sdf->temp3_reg_sm = (temp2_reg_32) >> 15; + } else { + sdf->temp2_reg_sm = 1; /* eI 108 24th Feb 06 update + * global variable for IIR + * filter. */ + sdf->temp3_reg_sm = temp1_reg; + } + + } /* end if (0 == sdf->DecRssi) */ + + sdf->DecRssi = ((sdf->DecRssi + 1) % 16); /* end decimation + * by 16 */ + + /* *** End Weak Signal Processing ********************************** */ + /* ***************************************************************** */ +} diff --git a/drivers/media/radio/stfm1000/stfm1000-filter.h b/drivers/media/radio/stfm1000/stfm1000-filter.h new file mode 100644 index 000000000000..d24e3e9244b8 --- /dev/null +++ b/drivers/media/radio/stfm1000/stfm1000-filter.h @@ -0,0 +1,185 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef STFM1000_FILTER_H +#define STFM1000_FILTER_H + +/* STFM1000 Black Box Filter parameters */ +struct stfm1000_filter_parms { + s16 LprXzz; /* LPR x(n-2) stereo filter */ + s16 LmrXzz; /* LMR x(n-2) stereo filter */ + s16 LprYzz; /* LPR y(n-2) stereo filter */ + s16 LmrYzz; /* LMR y(n-2) stereo filter */ + + s16 LprXz; /* LPR x(n-1) stereo filter */ + s16 LmrXz; /* LMR x(n-1) stereo filter */ + s16 FilteredLpr; /* LPR filter output */ + s16 FilteredLmr; /* LMR filter output */ + + s16 LprB0; /* LPR stereo filter coef */ + s16 LprB1over2; /* LPR stereo filter coef */ + s16 LprA1over2; /* LPR stereo filter coef */ + s16 LprA2; /* LPR stereo filter coef */ + + s16 LmrB0; /* LMR stereo filter coef */ + s16 LmrB1over2; /* LMR stereo filter coef */ + s16 LmrA1over2; /* LMR stereo filter coef */ + s16 LmrA2; /* LMR stereo filter coef */ + + s16 LprYz; /* LPR y(n-1) stereo filter */ + s16 LmrYz; /* LMR y(n-1) stereo filter */ + + s16 Left; /* left channel audio out */ + s16 Right; /* right channel audio out */ + s32 LeftLb; /* left channel dc estimate */ + s32 RightLb; /* right channel dc estimate */ + + u32 RssiDecoded; /* integer decoded RSSI */ + + u16 RssiMant; /* mantissa of float-coded RSSI */ + s16 RssiLog; /* 10log10(decoded RSSI) */ + + u16 RssiExp; /* exponent of float-coded RSSI */ + u16 RssiLb; /* leaky bucket dc of rssi */ + + u16 Prssi; /* power of 2 for RSSI */ + u16 TrueRssi; /* DC estimate of log RSSI */ + + u16 ScaledRssiDecoded; /* scaled log RSSI */ + s16 Echo; /* Echo info from HiPass(RSSI) */ + u16 ScaledRssiDecodedZ; /* history buffer for above */ + u16 ScaledRssiDecodedZz;/* ditto */ + + u16 ScaledTrueRssi; /* scaled version for precision */ + u16 FilteredRssi; /* Attack/Decay filtered RSSI */ + u16 PrevFilteredRssi; /* previous version of above */ + + u16 EchoLb; /* DC estimate of Echo energy */ + u16 TrueEcho; /* scaled version of above */ + u16 FilteredEchoLpr; /* Attack/Decay filt. Echo */ + u16 PrevFilteredEchoLpr;/* previous version of above */ + u16 FilteredEchoLmr; /* Attack/Decay filt. Echo */ + u16 PrevFilteredEchoLmr;/* previous version of above */ + s16 GatedEcho; /* Echo gated by threshold */ + + s16 ControlLpr; /* master control for LPR */ + s16 ControlLmr; /* master control for LMR */ + u16 LprBw; /* LPR Bandwidth desired */ + u16 LmrBw; /* LMR Bandwidth desired */ + u16 LprGa; /* LPR Gain (SoftMute) desired */ + u16 LmrGa; /* LMR Gain (Blend) desired */ + u16 ScaledControlLmr; /* Scaled down version Ctl LMR */ + u16 ScaledControlLpr; /* Scaled down version Ctl LPR */ + + s16 B0M; /* BW ctl B0 coef slope */ + s16 B0B; /* BW ctl B0 coef y-intercept */ + + u16 B0S; /* BW ctl B0 coef scale */ + s16 B1over2M; /* BW ctl B1/2 coef slope */ + + s16 B1over2B; /* BW ctl B1/2 coef y-intercept */ + s16 A1over2B; /* BW ctl A1/2 coef y-intercept */ + + u16 B1over2S; /* BW ctl B1/2 coef scale */ + u16 A1over2S; /* BW ctl A1/2 coef scale */ + + s16 A1over2M; /* BW ctl A1/2 coef slope */ + u16 A2S; /* BW ctl A2 coef scale */ + + s16 A2M; /* BW ctl A2 coef slope */ + s16 A2B; /* BW ctl A2 coef y-intercept */ + + u16 AdjBw; /* Desired Filter BW scaled into range */ + + u16 DecRssi; /*! Decimation modulo counter */ + + s16 ScaleAudio; /*! Scale factor for Audio Mute */ + u8 MuteAudio; /*! Control for muting audio */ + u8 PrevMuteAudio; /*! History of control for muting audio */ + u8 MuteActionFlag; /*! Indicator of when mute ramping occurs */ + + u32 Acc; /* mimics H/W accumulator */ + s32 Acc_signed; + s16 temp1_reg; /* mimics 16 bit register */ + s16 temp2_reg; /* mimics 16 bit register */ + s16 temp3_reg; /* mimics 16 bit register */ + s16 temp4_reg; /* mimics 16 bit register */ + s16 temp5_reg; /* mimics 16 bit register */ + + /* *** Programmable Coefficients */ + u16 pCoefRssiAttack; /* prog coef RSSI attack */ + u16 pCoefRssiDecay; /* prog coef RSSI decay */ + u16 pCoefEchoLprAttack; /* prog coef Echo LPR attack */ + u16 pCoefEchoLprDecay; /* prog coef Echo LPR decay */ + u16 pCoefEchoLmrAttack; /* prog coef Echo LMR attack */ + u16 pCoefEchoLmrDecay; /* prog coef Echo LMR decay */ + + u16 pCoefEchoTh; /* prog coef Echo threshold */ + + u16 pCoefEchoScLpr; /* prog coef scale Echo LPR infl. */ + u16 pCoefEchoScLmr; /* prog coef scale Echo LMR infl. */ + u16 pCoefEchoShLpr; /* prog coef shift Echo LPR infl. */ + u16 pCoefEchoShLmr; /* prog coef shift Echo LMR infl. */ + + u16 pCoefLprBwThLo; /* prog coef Low Th LPR BW */ + u16 pCoefLprBwThHi; /* prog coef High Th LPR BW */ + u16 pCoefLmrBwThLo; /* prog coef Low Th LMR BW */ + u16 pCoefLmrBwThHi; /* prog coef High Th LMR BW */ + + u16 pCoefLprGaTh; /* prog coef Th LPR Gain (SoftMute) */ + u16 pCoefLmrGaTh; /* prog coef Th LMR Gain (Blend) */ + + u16 pCoefLprBwSlSc; /* prog coef Slope scale LPR BW */ + u16 pCoefLprBwSlSh; /* prog coef Slope shift LPR BW */ + u16 pCoefLmrBwSlSc; /* prog coef Slope scale LMR BW */ + u16 pCoefLmrBwSlSh; /* prog coef Slope shift LMR BW */ + u16 pCoefLprGaSlSc; /* prog coef Slope scale LPR Gain */ + u16 pCoefLprGaSlSh; /* prog coef Slope shift LPR Gain */ + + u8 pCoefForcedMono; /* Forced Mono control bit */ + u8 pCoefBypassBlend; /* Forced bypass of stereo blend */ + u8 pCoefBypassSoftmute; /* Forced bypass of softmute */ + u8 pCoefBypassDcCut; /* Forced bypass of audio DC Cut filter */ + + u8 pCoefBypassBwCtl; /* Forced bypass of bandwidth control */ + u8 pCoefForceLockLmrBw; /* prog flag to force LMR BW=LPR BW */ + + /* XXX added here, they were global */ + s16 temp2_reg_sm; + s16 temp3_reg_sm; + +}; + +/* STFM1000 Black Box Filter Function prototypes */ +void stfm1000_filter_reset(struct stfm1000_filter_parms *sdf); +void stfm1000_filter_decode(struct stfm1000_filter_parms *sdf, s16 Lpr, + s16 Lmr, u16 Rssi); + +static inline s16 +stfm1000_filter_value_left(struct stfm1000_filter_parms *sdf) +{ + return sdf->Left; +} + +static inline s16 +stfm1000_filter_value_right(struct stfm1000_filter_parms *sdf) +{ + return sdf->Right; +} + +static inline u32 +stfm1000_filter_value_rssi(struct stfm1000_filter_parms *sdf) +{ + return sdf->RssiDecoded; +} + +#endif diff --git a/drivers/media/radio/stfm1000/stfm1000-i2c.c b/drivers/media/radio/stfm1000/stfm1000-i2c.c new file mode 100644 index 000000000000..5ddeb3a5fd3a --- /dev/null +++ b/drivers/media/radio/stfm1000/stfm1000-i2c.c @@ -0,0 +1,452 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/io.h> +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <linux/i2c.h> +#include <linux/delay.h> + +#include <linux/version.h> /* for KERNEL_VERSION MACRO */ + +#include "stfm1000.h" + +#define stfm1000_i2c_debug(p, fmt, arg...) \ + do { \ + if ((p)->dbgflg & STFM1000_DBGFLG_I2C) \ + printk(KERN_INFO "stfm1000: " fmt, ##arg); \ + } while (0) + +static const char *reg_names[STFM1000_NUM_REGS] = { +#undef REGNAME +#define REGNAME(x) \ + [STFM1000_ ## x / 4] = #x "" + + REGNAME(TUNE1), + REGNAME(SDNOMINAL), + REGNAME(PILOTTRACKING), + REGNAME(INITIALIZATION1), + REGNAME(INITIALIZATION2), + REGNAME(INITIALIZATION3), + REGNAME(INITIALIZATION4), + REGNAME(INITIALIZATION5), + REGNAME(INITIALIZATION6), + REGNAME(REF), + REGNAME(LNA), + REGNAME(MIXFILT), + REGNAME(CLK1), + REGNAME(CLK2), + REGNAME(ADC), + REGNAME(AGC_CONTROL1), + REGNAME(AGC_CONTROL2), + REGNAME(DATAPATH), + REGNAME(RMS), + REGNAME(AGC_STAT), + REGNAME(SIGNALQUALITY), + REGNAME(DCEST), + REGNAME(RSSI_TONE), + REGNAME(PILOTCORRECTION), + REGNAME(ATTENTION), + REGNAME(CLK3), + REGNAME(CHIPID), +#undef REGNAME +}; + +static const int stfm1000_rw_regs[] = { + STFM1000_TUNE1, + STFM1000_SDNOMINAL, + STFM1000_PILOTTRACKING, + STFM1000_INITIALIZATION1, + STFM1000_INITIALIZATION2, + STFM1000_INITIALIZATION3, + STFM1000_INITIALIZATION4, + STFM1000_INITIALIZATION5, + STFM1000_INITIALIZATION6, + STFM1000_REF, + STFM1000_LNA, + STFM1000_MIXFILT, + STFM1000_CLK1, + STFM1000_CLK2, + STFM1000_ADC, + STFM1000_AGC_CONTROL1, + STFM1000_AGC_CONTROL2, + STFM1000_DATAPATH, + STFM1000_ATTENTION, /* it's both WR/RD */ +}; + +static const int stfm1000_ra_regs[] = { + STFM1000_RMS, + STFM1000_AGC_STAT, + STFM1000_SIGNALQUALITY, + STFM1000_DCEST, + STFM1000_RSSI_TONE, + STFM1000_PILOTCORRECTION, + STFM1000_ATTENTION, /* it's both WR/RD - always read */ + STFM1000_CLK3, + STFM1000_CHIPID +}; + +static int verify_writes; + +void stfm1000_setup_reg_set(struct stfm1000 *stfm1000) +{ + int i, reg; + + /* set up register sets (read/write) */ + for (i = 0; i < ARRAY_SIZE(stfm1000_rw_regs); i++) { + reg = stfm1000_rw_regs[i] / 4; + stfm1000->reg_rw_set[reg / 32] |= 1U << (reg & 31); + /* printk(KERN_INFO "STFM1000: rw <= %d\n", reg); */ + } + + /* for (i = 0; i < ARRAY_SIZE(stfm1000->reg_rw_set); i++) + printk("RW[%d] = 0x%08x\n", i, stfm1000->reg_rw_set[i]); */ + + /* set up register sets (read only) */ + for (i = 0; i < ARRAY_SIZE(stfm1000_ra_regs); i++) { + reg = stfm1000_ra_regs[i] / 4; + stfm1000->reg_ra_set[reg / 32] |= 1U << (reg & 31); + /* printk(KERN_INFO "STFM1000: rw <= %d\n", reg); */ + } + /* for (i = 0; i < ARRAY_SIZE(stfm1000->reg_ra_set); i++) + printk("RO[%d] = 0x%08x\n", i, stfm1000->reg_ra_set[i]); */ + + /* clear dirty */ + memset(stfm1000->reg_dirty_set, 0, sizeof(stfm1000->reg_dirty_set)); +} + +static int stfm1000_reg_is_rw(struct stfm1000 *stfm1000, int reg) +{ + reg >>= 2; + return !!(stfm1000->reg_rw_set[reg / 32] & (1 << (reg & 31))); +} + +static int stfm1000_reg_is_ra(struct stfm1000 *stfm1000, int reg) +{ + reg >>= 2; + return !!(stfm1000->reg_ra_set[reg / 32] & (1 << (reg & 31))); +} + +static int stfm1000_reg_is_dirty(struct stfm1000 *stfm1000, int reg) +{ + reg >>= 2; + return !!(stfm1000->reg_dirty_set[reg / 32] & (1 << (reg & 31))); +} + +static void stfm1000_reg_set_dirty(struct stfm1000 *stfm1000, int reg) +{ + reg >>= 2; + stfm1000->reg_dirty_set[reg / 32] |= 1 << (reg & 31); +} + +static inline int stfm1000_reg_is_writeable(struct stfm1000 *stfm1000, int reg) +{ + return stfm1000_reg_is_rw(stfm1000, reg); +} + +static inline int stfm1000_reg_is_readable(struct stfm1000 *stfm1000, int reg) +{ + return stfm1000_reg_is_rw(stfm1000, reg) || + stfm1000_reg_is_ra(stfm1000, reg); +} + +/********************************************************/ + +static int write_reg_internal(struct stfm1000 *stfm1000, int reg, u32 value) +{ + u8 values[5]; + int ret; + + stfm1000_i2c_debug(stfm1000, "%s(%s - 0x%02x, 0x%08x)\n", __func__, + reg_names[reg / 4], reg, value); + + values[0] = (u8)reg; + values[1] = (u8)value; + values[2] = (u8)(value >> 8); + values[3] = (u8)(value >> 16); + values[4] = (u8)(value >> 24); + ret = i2c_master_send(stfm1000->client, values, 5); + if (ret < 0) + return ret; + return 0; +} + +static int read_reg_internal(struct stfm1000 *stfm1000, int reg, u32 *value) +{ + u8 regb = reg; + u8 values[4]; + int ret; + + ret = i2c_master_send(stfm1000->client, ®b, 1); + if (ret < 0) + goto out; + ret = i2c_master_recv(stfm1000->client, values, 4); + if (ret < 0) + goto out; + *value = (u32)values[0] | ((u32)values[1] << 8) | + ((u32)values[2] << 16) | ((u32)values[3] << 24); + ret = 0; + + stfm1000_i2c_debug(stfm1000, "%s(%s - 0x%02x, 0x%08x)\n", __func__, + reg_names[reg / 4], reg, *value); +out: + return ret; +} + +int stfm1000_raw_write(struct stfm1000 *stfm1000, int reg, u32 value) +{ + int ret; + + mutex_lock(&stfm1000->xfer_lock); + ret = write_reg_internal(stfm1000, reg, value); + mutex_unlock(&stfm1000->xfer_lock); + + if (ret < 0) + dev_err(&stfm1000->client->dev, "%s: failed", __func__); + + return ret; +} + +int stfm1000_raw_read(struct stfm1000 *stfm1000, int reg, u32 *value) +{ + int ret; + + mutex_lock(&stfm1000->xfer_lock); + ret = read_reg_internal(stfm1000, reg, value); + mutex_unlock(&stfm1000->xfer_lock); + + if (ret < 0) + dev_err(&stfm1000->client->dev, "%s: failed", __func__); + + return ret; +} + +static inline void stfm1000_set_shadow_reg(struct stfm1000 *stfm1000, + int reg, u32 val) +{ + stfm1000->shadow_regs[reg / 4] = val; +} + +static inline u32 stfm1000_get_shadow_reg(struct stfm1000 *stfm1000, int reg) +{ + return stfm1000->shadow_regs[reg / 4]; +} + +int stfm1000_write(struct stfm1000 *stfm1000, int reg, u32 value) +{ + int ret; + + if (!stfm1000_reg_is_writeable(stfm1000, reg)) { + ret = -EINVAL; + goto out; + } + + mutex_lock(&stfm1000->xfer_lock); + + /* same value as last one written? */ + if (stfm1000_reg_is_dirty(stfm1000, reg) && + stfm1000_get_shadow_reg(stfm1000, reg) == value) { + ret = 0; + + stfm1000_i2c_debug(stfm1000, "%s - HIT " + "(%s - 0x%02x, 0x%08x)\n", __func__, + reg_names[reg / 4], reg, value); + + goto out_unlock; + } + + /* actually write the register */ + ret = write_reg_internal(stfm1000, reg, value); + if (ret < 0) + goto out_unlock; + + /* update shadow register & mark it as dirty */ + /* only if register is not read always */ + if (!stfm1000_reg_is_ra(stfm1000, reg)) { + stfm1000_set_shadow_reg(stfm1000, reg, value); + stfm1000_reg_set_dirty(stfm1000, reg); + } + +out_unlock: + mutex_unlock(&stfm1000->xfer_lock); + +out: + if (ret < 0) + dev_err(&stfm1000->client->dev, "%s: failed", __func__); + + if (verify_writes) { + u32 value2 = ~0; + + stfm1000_raw_read(stfm1000, reg, &value2); + + stfm1000_i2c_debug(stfm1000, "%s - VER " + "(%s - 0x%02x, W=0x%08x V=0x%08x) %s\n", __func__, + reg_names[reg / 4], reg, value, value2, + value == value2 ? "OK" : "** differs **"); + } + + return ret; +} + +int stfm1000_read(struct stfm1000 *stfm1000, int reg, u32 *value) +{ + int ret = 0; + + if (!stfm1000_reg_is_readable(stfm1000, reg)) { + ret = -EINVAL; + printk(KERN_INFO "%s: !readable(%d)\n", __func__, reg); + goto out; + } + + mutex_lock(&stfm1000->xfer_lock); + + /* if the register can be written & is dirty, use the shadow */ + if (stfm1000_reg_is_writeable(stfm1000, reg) && + stfm1000_reg_is_dirty(stfm1000, reg)) { + + *value = stfm1000_get_shadow_reg(stfm1000, reg); + ret = 0; + + stfm1000_i2c_debug(stfm1000, "%s - HIT " + "(%s - 0x%02x, 0x%08x)\n", __func__, + reg_names[reg / 4], reg, *value); + + goto out_unlock; + } + + /* register must be read */ + ret = read_reg_internal(stfm1000, reg, value); + if (ret < 0) + goto out; + + /* if the register is writeable, update shadow */ + if (stfm1000_reg_is_writeable(stfm1000, reg)) { + stfm1000_set_shadow_reg(stfm1000, reg, *value); + stfm1000_reg_set_dirty(stfm1000, reg); + } + +out_unlock: + mutex_unlock(&stfm1000->xfer_lock); + +out: + if (ret < 0) + dev_err(&stfm1000->client->dev, "%s: failed", __func__); + + return ret; +} + +int stfm1000_write_masked(struct stfm1000 *stfm1000, int reg, u32 value, + u32 mask) +{ + int ret = 0; + u32 old_value; + + if (!stfm1000_reg_is_writeable(stfm1000, reg)) { + ret = -EINVAL; + printk(KERN_ERR "%s: !writeable(%d)\n", __func__, reg); + goto out; + } + + mutex_lock(&stfm1000->xfer_lock); + + /* if the register wasn't written before, read it */ + if (!stfm1000_reg_is_dirty(stfm1000, reg)) { + ret = read_reg_internal(stfm1000, reg, &old_value); + if (ret != 0) + goto out_unlock; + } else /* register was written, use the last value */ + old_value = stfm1000_get_shadow_reg(stfm1000, reg); + + /* perform masking */ + value = (old_value & ~mask) | (value & mask); + + /* if we write the same value, don't bother */ + if (stfm1000_reg_is_dirty(stfm1000, reg) && value == old_value) { + ret = 0; + + stfm1000_i2c_debug(stfm1000, "%s - HIT " + "(%s - 0x%02x, 0x%08x)\n", __func__, + reg_names[reg / 4], reg, value); + + goto out_unlock; + } + + /* actually write the register to the chip */ + ret = write_reg_internal(stfm1000, reg, value); + if (ret < 0) + goto out_unlock; + + /* if no error, update the shadow register and mark it as dirty */ + stfm1000_set_shadow_reg(stfm1000, reg, value); + stfm1000_reg_set_dirty(stfm1000, reg); + +out_unlock: + mutex_unlock(&stfm1000->xfer_lock); + +out: + if (ret < 0) + dev_err(&stfm1000->client->dev, "%s: failed", __func__); + + if (verify_writes) { + u32 value2 = ~0; + + stfm1000_raw_read(stfm1000, reg, &value2); + + stfm1000_i2c_debug(stfm1000, "%s - VER " + "(%s - 0x%02x, W=0x%08x V=0x%08x) %s\n", __func__, + reg_names[reg / 4], reg, value, value2, + value == value2 ? "OK" : "** differs **"); + } + + return ret; +} + +int stfm1000_set_bits(struct stfm1000 *stfm1000, int reg, u32 value) +{ + return stfm1000_write_masked(stfm1000, reg, value, value); +} + +int stfm1000_clear_bits(struct stfm1000 *stfm1000, int reg, u32 value) +{ + return stfm1000_write_masked(stfm1000, reg, ~value, value); +} + +int stfm1000_write_regs(struct stfm1000 *stfm1000, + const struct stfm1000_reg *reg) +{ + int ret; + + for (; reg && reg->regno != STFM1000_REG_END; reg++) { + + if (reg->regno == STFM1000_REG_DELAY) { + msleep(reg->value); + continue; + } + + if (reg->regno & STFM1000_REG_SET_BITS_MASK) + ret = stfm1000_set_bits(stfm1000, reg->regno & 0xff, + reg->value); + else if (reg->regno & STFM1000_REG_CLEAR_BITS_MASK) + ret = stfm1000_clear_bits(stfm1000, reg->regno & 0xff, + reg->value); + else + ret = stfm1000_write(stfm1000, reg->regno, reg->value); + + if (ret != 0) { + printk(KERN_ERR "%s: failed to write reg 0x%x " + "with 0x%08x\n", + __func__, reg->regno, reg->value); + return ret; + } + } + return 0; +} diff --git a/drivers/media/radio/stfm1000/stfm1000-rds.c b/drivers/media/radio/stfm1000/stfm1000-rds.c new file mode 100644 index 000000000000..5f63052d00c2 --- /dev/null +++ b/drivers/media/radio/stfm1000/stfm1000-rds.c @@ -0,0 +1,1529 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/init.h> + +#include "stfm1000.h" + +#include "stfm1000-rds.h" + +#define bitstream_to_rds_state(b) \ + container_of(b, struct stfm1000_rds_state, bitstream) +#define demod_to_rds_state(d) \ + container_of(d, struct stfm1000_rds_state, demod) +#define pkt_to_rds_state(p) \ + container_of(p, struct stfm1000_rds_state, pkt) +#define text_to_rds_state(t) \ + container_of(t, struct stfm1000_rds_state, text) +#define rds_state_to_stfm1000(r) \ + container_of(r, struct stfm1000, rds_state) + +#define TADJSH 8 /*Shifts used in bitslice loop filter */ + +/* Reverse of Matlab's Fquant (see MatchedFilterDecomposition.m), so that */ +/* mixandsum code is easy; Used by rds_bitstream_stfmdemod.arm */ +const s16 u16_rds_basis[2*RDS_BASISLENGTH+8] = { + 14, 24, 34, 43, 50, 56, 60, 62, 62, + 60, 55, 49, 41, 32, 22, 11, 14, 24, + 34, 43, 50, 56, 60, 62, 62, 60, 55, + 49, 41, 32, 22, 11, 14, 24, 34, 43, + 50, 56, 60, 62 +}; + +static int bits_free(struct stfm1000_rds_bitstream *rdsb) +{ + /* Do not show the last one word free. */ + int FreeSpace = rdsb->TailBitCount - rdsb->HeadBitCount - 32; + + if (FreeSpace < 0) + FreeSpace = (RDS_BITBUFSIZE * 32) + FreeSpace; + return FreeSpace; +} + +static void put1bit(struct stfm1000_rds_bitstream *rdsb, int bit) +{ + int index = (rdsb->HeadBitCount >> 5); + u32 CurBit = (rdsb->HeadBitCount & 0x1f); + u32 CurWord = rdsb->buf[index]; + + if (CurBit == 0) + CurWord = 0; + + CurWord = CurWord | (((u32)bit & 1) << CurBit); + rdsb->buf[index] = CurWord; + rdsb->HeadBitCount++; + if (rdsb->HeadBitCount >= RDS_BITBUFSIZE * 32) + rdsb->HeadBitCount = 0; +} + +static int get1bit(struct stfm1000_rds_bitstream *rdsb) +{ + int Bit = 0; + int index = (rdsb->TailBitCount >> 5); + int CurBit = (rdsb->TailBitCount & 0x1f); + u32 CurWord = rdsb->buf[index]; + + Bit = (CurWord >> CurBit) & 1; + rdsb->TailBitCount++; + if (rdsb->TailBitCount == RDS_BITBUFSIZE*32) + rdsb->TailBitCount = 0; + + return Bit; +} + +static int bits_filled(struct stfm1000_rds_bitstream *rdsb) +{ + int FilledSpace = rdsb->HeadBitCount - rdsb->TailBitCount; + + if (FilledSpace < 0) + FilledSpace = (RDS_BITBUFSIZE * 32) + FilledSpace; + return FilledSpace; +} + +static void rds_mix_msg(struct stfm1000_rds_demod *rdsd, u8 MixSetting) +{ + if (rdsd->mix_msg_pending) + rdsd->mix_msg_overrun++; + rdsd->mix_msg = MixSetting; + rdsd->mix_msg_pending = 1; + + /* signal monitor thread */ + stfm1000_monitor_signal( + rds_state_to_stfm1000(demod_to_rds_state(rdsd)), + EVENT_RDS_MIXFILT); +} + +/* call with interrupts disabled please */ +int stfm1000_rds_mix_msg_get(struct stfm1000_rds_state *rds) +{ + struct stfm1000_rds_demod *rdsd = &rds->demod; + + if (!rdsd->mix_msg_pending) + return -1; + + return rdsd->mix_msg; +} + +/* call with interrupts disabled please */ +int stfm1000_rds_mix_msg_processed(struct stfm1000_rds_state *rds, int mix_msg) +{ + struct stfm1000_rds_demod *rdsd = &rds->demod; + + if (!rdsd->mix_msg_pending) + return -1; + + rdsd->mix_msg_pending = 0; + + /* update the completion indication bit */ + if ((mix_msg & 0x8) == 0) + rdsd->MixPopDone = 1; + + /* this is reflected off the hardware register */ + rdsd->rds_mix_offset = mix_msg & 1; + + if (rdsd->mix_msg != mix_msg) { + rdsd->mix_msg_processed_changed++; + return -1; + } + return 0; +} + +static void rds_sdnominal_msg(struct stfm1000_rds_demod *rdsd, int sdnominal) +{ + if (rdsd->sdnominal_msg_pending) + rdsd->sdnominal_msg_overrun++; + rdsd->sdnominal_msg = sdnominal; + rdsd->sdnominal_msg_pending = 1; + + /* signal monitor thread */ + stfm1000_monitor_signal( + rds_state_to_stfm1000(demod_to_rds_state(rdsd)), + EVENT_RDS_SDNOMINAL); +} + +/* call with interrupts disabled please */ +int stfm1000_rds_sdnominal_msg_get(struct stfm1000_rds_state *rds) +{ + struct stfm1000_rds_demod *rdsd = &rds->demod; + + if (!rdsd->sdnominal_msg_pending) + return 0; + + return rdsd->sdnominal_msg; +} + +/* call with interrupts disabled please */ +int stfm1000_rds_sdnominal_msg_processed(struct stfm1000_rds_state *rds, + int sdnominal_msg) +{ + struct stfm1000_rds_demod *rdsd = &rds->demod; + + if (!rdsd->sdnominal_msg_pending) + return -1; + + rdsd->sdnominal_msg_pending = 0; + return 0; +} + +void demod_loop(struct stfm1000_rds_bitstream *rdsb, + struct stfm1000_rds_demod *rdsd) +{ + s32 filter_out; + u32 freeSpace; + s32 decomp_hist_pp; + u8 phase; + + /* Check if we're at a half-basis point */ + if ((rdsd->i & (RDS_BASISLENGTH/2 - 1)) != 0) + return; /* Nope, return */ + + /* Yes, time to do our work */ + /* Rotate the length 3 history buffer */ + decomp_hist_pp = rdsd->decomp_hist_p; + rdsd->decomp_hist_p = rdsd->decomp_hist; + if ((rdsd->i & (RDS_BASISLENGTH-1)) == 0) { + rdsd->decomp_hist = rdsd->mixandsum1>>9; /* Grab output of + * mixandsum1/512 */ + rdsd->mixandsum1 = 0; /* Reset mixandsum #1 */ + } else { + rdsd->decomp_hist = rdsd->mixandsum2>>9; /*Grab output of + * mixandsum2/512 */ + rdsd->mixandsum2 = 0; /* Reset mixandsum #2 */ + } + + /* Form correlator/decimator output by convolving with the + * decomposition coefficients, DecompQuant from Matlab work. */ + filter_out = (-58*rdsd->decomp_hist + 59*decomp_hist_pp)>>7; + + /*Figure out which half-basis we are in (out of a bit-length cycle) */ + phase = rdsd->i*2/RDS_BASISLENGTH; + /*Now what we do depends on the phase variable */ + /*Phase 0: Bitslice and do timing alignment */ + /*others (1-3): Keep value for timing alignment */ + + if (phase == 0) { /*Main processing (bitslice) */ + u32 Ph; + u8 OldBit = rdsd->sliced_data; /* Save the previous value */ + + rdsd->return_num = 1; + if (filter_out >= 0) { /*This bit is "1" */ + /*return value is XOR of previous bit (still in + * sliced_data) w/ this */ + /* bit (1), which equals (NOT of the previous bit) */ + rdsd->return_rdsdemod = !OldBit; + rdsd->sliced_data = 1; /*Newest bit value is 1 */ + } else { /*This bit is "0" */ + /*return value is XOR of previous bit (still in + * sliced_data) w/ this */ + /* bit (0), which equals the previous bit */ + rdsd->return_rdsdemod = OldBit; + rdsd->sliced_data = 0; /*Newest bit value is 0 */ + } + + freeSpace = bits_free(rdsb); + + if (freeSpace > 0) + put1bit(rdsb, rdsd->return_rdsdemod); + else + rdsd->RdsDemodSkippedBitCnt++; + + /*Increment bits received counter */ + rdsd->BitAlignmentCounter++; + /*If mixer phase determination hasn't been done, start it */ + if ((rdsd->MixPhaseState == 0) && (!rdsd->MixPhaseDetInProg)) { + rdsd->MixPhaseDetInProg = 1; + /*Go to first mixer setting (0) */ + rds_mix_msg(rdsd, 0); + } + + /* Do bit-slicing time adaption after the mixer phase + * determination */ + if (!(rdsd->MixPhaseDetInProg) && !(rdsd->Synchronous)) { + + /* Bitslice Timing Adjust Code (runs after + * MixPhaseDetInProg and if RDS is not synchronous to + * the FM pilot. */ + + u8 BigPh2; /* Expecting a large value in + * PhaseValue[2] */ + u32 MaxRMS = 0; /*Largest phase RMS */ + s8 MaxPh = 0; /*Index of largest phase RMS */ + s32 zerocross; + + /* Locate the largest phase RMS + * (should be at phase zero) */ + for (Ph = 0; Ph < 4; Ph++) + if (rdsd->Ph_RMS[Ph] > MaxRMS) { + MaxRMS = rdsd->Ph_RMS[Ph]; + MaxPh = Ph; + } + + /* During each bit time we expect the four phases to + * take one of the following patterns, where 1 + * corresponds to maximum modulation: + * 1, 0, -1, 0 Case I + * -1, 0, 1, 0 Case II + * 1, 1/2, 0, -1/2 Case III + * -1, -1/2, 0, 1/2 Case IV + * We need to distinguish between cases in order to do + * the timing adjustment. Below we compare the + * correlation of the samples with Case I and Case III + * to see which has a bigger abs(correlation). Thus + * BigPh2, if set, means that we decided on Case I or + * Case II; if BigPh2 clear, we decided Case III or IV. + */ + BigPh2 = abs(rdsd->PhaseValue[0]-rdsd->PhaseValue[2]) > + abs(rdsd->PhaseValue[0] + + ((rdsd->PhaseValue[1]- + rdsd->PhaseValue[3])>>1)); + /* If BigPh2, use the difference between phase 1 value + * (downgoing for Case I, upgoing for Case II) and + * phase 3 value (upgoing for Case I, downgoing for + * Case II, thus the subtraction) to indicate timing + * error. If not BigPh2, use the sum of the phase 1 + * value (downgoing for Case III, upgoing for Case IV) + * and phase 3 value (downgoing for Case III, upgoing + * for Case IV, thus the addition) to indicate timing + * error. If BigPh2, the slopes at phase 1 & phase 3 + * are approximately double that if not BigPh2. + * Since we are trying to measure timing, scale + * by 1/2 in the BigPh2 case. */ + if (BigPh2) + zerocross = (rdsd->PhaseValue[1]- + rdsd->PhaseValue[3])>>1; + else + zerocross = rdsd->PhaseValue[1]+ + rdsd->PhaseValue[3]; + /* Now if the prev bit was a "1", then the first zero + * crossing (phase 1 if BigPh2, phase 2 if !BigPh2) + * was a falling one, and if we were late then + * zerocross should be negative. If the prev bit was a + * "0", then the first zero crossing was a rising one, + * and if we were late then zerocross would be + * positive. If we are "late" it means that we need to + * do a shorter cycle of, say, 15 samples instead of + * 16, to "catch up" so that in the future we will be + * sampling earlier. We shorten the cycle by adding + * to i, so "late" is going to mean "increment i". + * Therefore "late" should be positive, which is done + * here by inverting zerocross if the previous bit was + * 1. You could say that this step reflects cases I + * and III into II and IV, respectively. */ + if (OldBit) + zerocross = -zerocross; + if (!rdsd->DisablePushing) { + /*The algorithm so far has a stable operating + * point 17 phases away from the correct one. + * The following code is experimental and may + * be deleterious in low SNR conditions, but is + * an attempt to move off of the incorrect + * operating point. */ + + if (MaxPh != 0) { + /* If it isn't the same MaxPh as the + * last non-zero one, clear the counter + */ + if (MaxPh != rdsd->PushLastMaxPh) { + /*Reset the counter */ + rdsd->PushCounter = 0; + /*Record which phase we're now + * counting */ + rdsd->PushLastMaxPh = MaxPh; + } + /* If the Max RMS is on the same + * non-zero phase, count up */ + rdsd->PushCounter++; + } + /* Once every 128 bits, check and then reset + * PushCounter */ + if (!(rdsd->BitAlignmentCounter & 0x0FF)) { + /*If 90% of the time the max phase has + * been from the same non-zero phase, + * decide that we are latched onto a 0 + * lock point. Do a large push of the + * timing. */ + if (rdsd->PushCounter > 230) { + s32 pshiph; + /*Convert from phase number to + * the number of filter + * output samples that we need + * to shift */ + if (rdsd->PushLastMaxPh >= 2) + pshiph = + 4 - (s8)rdsd-> + PushLastMaxPh; + else + pshiph = + -(s8)rdsd-> + PushLastMaxPh; + /* Scale by the number of i- + * phases per output sample */ + pshiph <<= + RDS_BASISSHIFTS-1; + /* Perform big pop to get near + * correct timing */ + rdsd->i += (RDS_BASISLENGTH<<1) + + pshiph; + /* Set status indicating big + * pop was needed. Reset all + * leaky-bucket and summation + * variables because the big + * timing shift has invalidated + * them. Ph_RMS values don't + * need to be reset because + * they will shift over to + * reasonable values again + * before their erroneous + * values could have effect. */ + rdsd->rds_big_timeshift = 1; + /*rdsd->Ph_RMS[0] = 0; */ + /*rdsd->Ph_RMS[1] = 0; */ + /*rdsd->Ph_RMS[2] = 0; */ + /*rdsd->Ph_RMS[3] = 0; */ + rdsd->mixandsum1 = 0; + rdsd->mixandsum2 = 0; + rdsd->SkipsAccum += + pshiph; + + /* Make adjustments in other + * values because of the push + * (they wouldn't otherwise be + * able to use the information + * that a push was needed in + * their future control + * decisions). */ + if (rdsd->PushLastMaxPh != 2) { + /* If we weren't + * pushing from phase + * two, accumulate (for + * use in adapting + * SDNOMINAL) the + * phases moved by + * pushing. Phase two + * pushes are not used; + * the push direction + * is arbitrary since + * Phase 2 is 180 + * degrees out. Also, + * phase 2 pushes don't + * result from + * reasonable slippage. + * */ + + if (rdsd->sdnom_adapt) + rdsd->SdnomSk + += pshiph; + + /* Modify timing_adj to + * account for half of + * the DC response that + * would have occurred + * in timing_adj if + * that control loop + * had seen the push + * happen. (Why half? + * Because the loop has + * already seen a + * history of zerocross + * values that heads it + * in the same + * direction as this + * adjustment, but may + * have seen as few as + * half of what it + * should have.) */ + rdsd->timing_adj += + pshiph << + (TADJSH+1); + } + /*Set countdown timer that will + * prevent any mixer popping + * until the Ph_RMS variables + * have had enough time to + * stabilize */ + + /* 2.5 time constants */ + rdsd->PushSafetyZone = 5; + } + /*Reset the push counter */ + rdsd->PushCounter = 0; + } /*end once every 128 bits */ + } /*end if !DisablePushing */ + + /* Further possible additions: + * + * 1. Pushes modify timing_adj to decrease convergence + * time. + * 2. Separate timing_adj into pilottracking and non-pt + * cases (avoids convergence time after stereo/mono + * transitions) + * + * Old loop filter was a leaky bucket integrator, and + * it always lagged behind if the FM station had RDS + * asynchronous to the pilot, because the control loop + * needs another integrator to converge on a frequency + * error. + * New loop filter = 1/(1-1/z) * (a-1/z) * k, + * where a = 1+1/256 and k = 1/1024. + * You can narrow the loop bandwidth by making "a" + * twice as close to 1 and halving k, e.g. a = 1+1/512 + * and k = 1/2048. + * (The way implemented, that narrowing loop BW by + * a factor of 2 can be done by incrementing TADJSH.) + * + * TGR 8/31/2007 */ + + /*Integrator, 1/(1-1/z) */ + rdsd->timing_adj += zerocross; + /*Limit to 1 phase every 8 samples */ + if (rdsd->SkipSafetyZone) { + rdsd->SkipSafetyZone--; + rdsd->sampskip = 0; + } else { + /*sampskip of non-zero is allowed, + * calculate what it really is */ + + /*Saturate timing_adj to 2's comp + * (2*TADJSH+4)-bit range. */ + if (rdsd->timing_adj > (1<<(2*TADJSH+3))-1) + rdsd->timing_adj = (1<<(2*TADJSH+3))-1; + if (rdsd->timing_adj < -(1<<(2*TADJSH+3))) + rdsd->timing_adj = -(1<<(2*TADJSH+3)); + + /* Zero, implemented after the integrator + * output. + * (a-1/z) = (1+1/256) - 1/z = (1-1/z) + 1/256. + * But (1 - 1/z) is timing_adj- + * prev_timing_adj = zerocross. */ + rdsd->sampskip = zerocross /* 1 - 1/z */ + /* 1/256 (with rounding) */ + + ((rdsd->timing_adj + + (1<<(TADJSH-1)))>>TADJSH); + /*Round and apply k */ + rdsd->sampskip += (1<<(TADJSH+1)); + rdsd->sampskip >>= (TADJSH+2); + /*Limit to [-1,+1] inclusive */ + if (rdsd->sampskip > 1) + rdsd->sampskip = 1; + if (rdsd->sampskip < -1) + rdsd->sampskip = -1; + /* If non-zero, start the skip safety zone, + * which excludes more sample skipping for a + * while. Note that the safety zone only + * applies to the skips -- pushes can still + * happen inside a SkipSafetyZone. */ + if (rdsd->sampskip) + rdsd->SkipSafetyZone = 8-1; + } + /********************************************** + * End Timing Adjust Code + **********************************************/ + + /********************************************** + * Begin Phase Popper Code + **********************************************/ + /* If Phase Popping is enabled and 1/2 of a + * time constant has gone by... */ + if (rdsd->PhasePoppingEnabled && + !(rdsd->BitAlignmentCounter & + ((1<<(RMSALPHASHIFTS-1))-1))) { + + u8 ForcePop = 0; /* Used to force a pop */ + + /*Record the maximum of the envelope */ + if (MaxRMS > rdsd->PhasePopMaxRMS) + rdsd->PhasePopMaxRMS = MaxRMS; + /* Also track MaxRMS into MixPhase0/1Mag, so + * that we can see what the largest RMS on each + * of those phases is. On synchronous stations + * (meaning the RDS carrier and bit rate are + * synchronized with the pilot), the right mix + * phase will always be big and the wrong phase + * small. On asynchronous stations (and + * stations without RDS), both phases will at + * some time or other have about the + * same amplitude on each of the phases. */ + if (rdsd->rds_mix_offset) { + if (MaxRMS > rdsd->MixPhase1Mag) + rdsd->MixPhase1Mag = MaxRMS; + } else { + if (MaxRMS > rdsd->MixPhase0Mag) + rdsd->MixPhase0Mag = MaxRMS; + } + /* Update PopSafetyZone and PushSafetyZone + * counters. With RMSALPHASHIFTS = 5, each + * tick is 16/1187.5 =~ 13.5 ms. */ + if (rdsd->PopSafetyZone) { + rdsd->PopSafetyZone--; + /* If safety zone just ended and this + * mix phase is giving smaller RMS than + * before the pop, then the pop was a + * mistake. Go back to previous mixer + * phase */ + if (!(rdsd->PopSafetyZone) + && (rdsd->PhasePopMaxRMS < + rdsd->PrePopRMS)) + ForcePop = 1; + } + /* If there is no recent push, and Phase 0 has + * the maximum RMS, and at least 1/7th of a + * second has passed since the last phase pop, + * and ((the RMS is less than 1/2 of + * PhasePopMaxRMS) or (the RMS is less than + * 100)), then try a phase pop. */ + if (/* (rdsd->Ph_RMS[0] == MaxRMS) && + * Phase 0 has maximum RMS */ + !(rdsd->PopSafetyZone)) { + /* and Long enough since last + * phase pop */ + + /* Eligible for a pop, see if one of + * the pop conditions is met */ + if ((MaxRMS<<1) < + rdsd->PhasePopMaxRMS) { + /*RMS decline from its peak */ + ForcePop = 1; + } else if ((MaxRMS>>RMSALPHASHIFTS) + < 50) { + /*RMS too small to receive, + * either there's no RDS or + * this is the wrong phase */ + ForcePop = 1; + } + } + if (ForcePop) { + + /*Pop to opposite setting */ + rds_mix_msg(rdsd, 0x8 | + !rdsd->rds_mix_offset); + + /*Save the pre-pop RMS so that later we + * can see if the pop was actually + * effective */ + rdsd->PrePopRMS = MaxRMS; + /*Reset the PhasePopMaxRMS. We rely on + * the PopSafetyZone to give time to + * get a new valid max RMS before we're + * eligible for the next phase pop. If + * there were no reset we'd be forever + * incrementing PhasePopMaxRMS due + * to just happenstance large-noise + * samples and it might eventually get + * some freakish large value causing + * frequent erroneous pops. */ + rdsd->PhasePopMaxRMS = 0; + /* Pop Safety zone length is decided by + * how much of an asynchronous + * frequency can be supported. Allowing + * 50 ppm of transmitter error (error + * between their own pilot, that we + * should be locked to, and their RDS + * carrier (which by RDS spec should be + * locked to their pilot, but we've + * recently found frequently isn't). + * 50ppm * 57kHz = 2.85Hz. + * (2.85 cycles/sec)(4 pops/cycle) + * = 11.4 pops/second. + * Safety zone = (1/11.4) seconds =~ 104 + * bits, round down to 96 bits which + * yields 6 ticks if RMSALPHASHIFTS = 5. + * */ + rdsd->PopSafetyZone = 96>> + (RMSALPHASHIFTS-1); + } + } + /****************************************************** + * End Phase Popper Code + ******************************************************/ + + /* SDNOMINAL adaption */ + if (rdsd->sdnom_adapt) { + rdsd->SdnomSk += rdsd->sampskip; + if (rdsd->pCoefForcedMono && + (rdsd->BitAlignmentCounter & 0xFFF) == + 0x800) { + + rds_sdnominal_msg(rdsd, + -(rdsd->SdnomSk<<9)); + + /*Reset skips counter */ + rdsd->SdnomSk = 0; + } + } + + rdsd->SkipsAccum += rdsd->sampskip; + /* Once per 3.45 seconds, print out signal strength, + * skips and pops. Then reset the variables totalling + * those occurrences */ + if (!(rdsd->BitAlignmentCounter & 0xFFF)) { + /* During very noisy input (or if no RDS, or no + * station present), timing_adj can go crazy, + * since it is the integral of noise. Although + * it is a saturated value (earlier, in the + * timing adjust code), the level at which we + * can saturate still leaves room for + * timing_adj to get too big. A large value of + * timing_adj is a persistent pathology because + * the phase is shifting so quickly that the + * push detector (which relies on stable + * phase-RMS values) never triggers, thus there + * is no implemented rescue besides this + * clearing that restores proper function. */ + if (abs(rdsd->SkipsAccum) > 300) + rdsd->timing_adj = 0; + /*Reset the accumulations. */ + rdsd->SkipsAccum = 0; + } + } /*End of bit timing adaption */ + + /* If mixer phase determination in progress, + * perform actions at certain times */ + if (rdsd->MixPhaseDetInProg) { + /*~10ms settling time after mixer phase change */ + #define MIXPHASE_STARTMEAS 12 + /*~20ms measurement window */ + #define MIXPHASE_ENDMEAS (MIXPHASE_STARTMEAS+24) + if (rdsd->BitAlignmentCounter == MIXPHASE_STARTMEAS) { + /*Reset the RMS variables */ + rdsd->Ph_RMS[0] = 0; + rdsd->Ph_RMS[1] = 0; + rdsd->Ph_RMS[2] = 0; + rdsd->Ph_RMS[3] = 0; + /* Don't reset mixandsum values because at + * least they have filtered continuously. All + * we really need for the mixer phase decision + * is a constant measurement window. */ + } else if (rdsd->BitAlignmentCounter == + MIXPHASE_ENDMEAS) { + /*Measurement = mean of RMS values */ + u32 Ndx, MeasVal = 0; + for (Ndx = 0; Ndx < 4; + MeasVal += rdsd->Ph_RMS[Ndx++]>>2); + /*Store measurement in correct place */ + if (rdsd->MixPhaseState == 1) { + rdsd->MixPhase0Mag = MeasVal; + /*Go to next mixer setting */ + rds_mix_msg(rdsd, 1); + } else if (rdsd->MixPhaseState == 2) { + u8 NextMixSetting; + rdsd->MixPhase1Mag = MeasVal; + /* Both measurements done now, see what + * mixer setting we need to use. + * 0 if MixPhase0Mag > MixPhase1Mag, + * 1 otherwise. */ + NextMixSetting = (rdsd->MixPhase0Mag + <= rdsd->MixPhase1Mag); + /* If the mixer setting needed is 1, + * that is already the current setting. + * Terminate mixer phase determination. + * Otherwise send message to switch the + * mixer phase setting. */ + if (NextMixSetting) { + rdsd->MixPhaseState = 3; + rdsd->MixPhaseDetInProg = 0; + } else + rds_mix_msg(rdsd, 0); + } + } + /* Reset BitAlignmentCounter if the Mixer just popped + * Change state, if required. States are: + * 0: Initial state, send msg causing RDS_MIXOFFSET=>0 + * 1: Measure with RDS_MIXOFFSET = 0. + * Lasts just over 30 ms. + * 2: Measure with RDS_MIXOFFSET = 1. + * Lasts just over 30 ms. + * 3: At final RDS_MIXOFFSET value. + * Lasts as long as RDS continues. */ + if (rdsd->MixPopDone) { + rdsd->MixPopDone = 0; + rdsd->BitAlignmentCounter = 0; + rdsd->MixPhaseState++; /*Go to next state */ + /* If we got to state 3, turn off mixer phase + * determination code */ + if (rdsd->MixPhaseState == 3) + rdsd->MixPhaseDetInProg = 0; + } + } + + /* Update status variables */ + rdsd->RDS_BIT_AMP_STAT_REG9 = rdsd->Ph_RMS[0]>>RMSALPHASHIFTS; + /*Saturate */ + if (rdsd->RDS_BIT_AMP_STAT_REG9 > 511) + rdsd->RDS_BIT_AMP_STAT_REG9 = 511; + } /*End phase 0 code */ + + /*************************************************** + * Actions common to all phases + ***************************************************/ + + /* Save the output of each phase for possible + * calculations during phase 0 */ + rdsd->PhaseValue[phase] = filter_out; + + /*So that we can measure signal amplitude and/or determine what (if */ + /* any) big jump is needed, maintain the RMS of each phase. Phase */ + /* 0 RMS is already in Ph_RMS[0] (see bitslicing code, earlier). */ + rdsd->Ph_RMS[phase] += abs(filter_out) - + (rdsd->Ph_RMS[phase]>>RMSALPHASHIFTS); +} + +#if defined(CONFIG_ARM) + +/* assembly version for ARM */ +#define RDS_MAC(_acc, _x, _y) \ + __asm__ __volatile__ ( \ + "smlabb %0, %1, %2, %0\n" \ + : "=&r" (_acc) \ + : "r" (_x), "r" (_y) \ + : "cc") + +#else + +/* all others, use standard C */ +#define RDS_MAC(_acc, _x, _y) \ + do { \ + (_acc) += (s16)(_x) * (s16)(_y); \ + } while (0) + +#endif + +static void rds_demod(const u16 *data, struct stfm1000_rds_demod *rdsd, + struct stfm1000_rds_bitstream *rbit, int total) +{ + register const s16 *basis0; + register const s16 *basis1; + register s16 val; + register int i; + register int sampskip; + register s32 acc1; + register s32 acc2; + + /* point to the table */ + basis0 = u16_rds_basis; + basis1 = basis0 + 8; + + rdsd->return_num = 0; + + /* restore state */ + i = rdsd->i; + acc1 = rdsd->mixandsum1; + acc2 = rdsd->mixandsum2; /* 64 bit */ + sampskip = rdsd->sampskip; + + while (total-- > 0) { + + val = data[3]; /* load RDS data */ + data += 4; + if (val == 0x7fff) /* illegal RDS sample */ + continue; + + RDS_MAC(acc1, val, basis0[i]); + RDS_MAC(acc2, val, basis1[i]); + + if (i == 4) { + i += sampskip; + sampskip = 0; + } + + if ((i & (RDS_BASISLENGTH / 2 - 1)) == 0) { + + /* save state */ + rdsd->mixandsum1 = acc1; + rdsd->mixandsum2 = acc2; + rdsd->i = i; + rdsd->sampskip = sampskip; + + demod_loop(rbit, rdsd); + + /* restore state */ + acc1 = rdsd->mixandsum1; + acc2 = rdsd->mixandsum2; + i = rdsd->i; + sampskip = rdsd->sampskip; + } + i = (i + 1) & 31; + } + + /* save state */ + rdsd->mixandsum1 = acc1; + rdsd->mixandsum2 = acc2; + rdsd->i = i; + rdsd->sampskip = sampskip; +} + +void stfm1000_rds_demod(struct stfm1000_rds_state *rds, const u16 *dri_data, + int total) +{ + rds_demod(dri_data, &rds->demod, &rds->bitstream, total); + + /* signal only when we have enough */ + if (bits_filled(&rds->bitstream) > 128) + stfm1000_monitor_signal(rds_state_to_stfm1000(rds), + EVENT_RDS_BITS); +} + +static void bitstream_reset(struct stfm1000_rds_bitstream *rdsb) +{ + memset(rdsb, 0, sizeof(*rdsb)); +} + +static void demod_reset(struct stfm1000_rds_demod *rdsd) +{ + memset(rdsd, 0, sizeof(*rdsd)); + rdsd->sdnom_adapt = 0; /* XXX this doesn't really work right */ + /* it causes underruns at ALSA */ + rdsd->PhasePoppingEnabled = 1; /* does this? */ +} + +static void packet_reset(struct stfm1000_rds_pkt *rdsp) +{ + memset(rdsp, 0, sizeof(*rdsp)); + rdsp->state = SYNC_OFFSET_A; +} + +static void text_reset(struct stfm1000_rds_text *rdst) +{ + memset(rdst, 0, sizeof(*rdst)); +} + +void stfm1000_rds_reset(struct stfm1000_rds_state *rds) +{ + bitstream_reset(&rds->bitstream); + demod_reset(&rds->demod); + packet_reset(&rds->pkt); + text_reset(&rds->text); + rds->reset_req = 0; +} + +int stfm1000_rds_bits_available(struct stfm1000_rds_state *rds) +{ + return bits_filled(&rds->bitstream); +} + +int stmf1000_rds_get_bit(struct stfm1000_rds_state *rds) +{ + if (bits_filled(&rds->bitstream) == 0) + return -1; + return get1bit(&rds->bitstream); +} + +int stmf1000_rds_avail_bits(struct stfm1000_rds_state *rds) +{ + return bits_filled(&rds->bitstream); +} + +static const u32 rds_ParityCheck[] = { + 0x31B, 0x38F, 0x2A7, 0x0F7, 0x1EE, + 0x3DC, 0x201, 0x1BB, 0x376, 0x355, + 0x313, 0x39F, 0x287, 0x0B7, 0x16E, + 0x2DC, 0x001, 0x002, 0x004, 0x008, + 0x010, 0x020, 0x040, 0x080, 0x100, + 0x200 +}; + +static int calc_syndrome(u32 rdscrc) +{ + int i; + u32 syndrome = 0; + int word = 0x1; + + for (i = 0; i < 26; i++) { + if (rdscrc & word) + syndrome ^= rds_ParityCheck[i]; + word <<= 1; + } + return syndrome; +} + +static u32 ecc_table[1024]; +static int ecc_table_generated; + +static void generate_ecc_table(void) +{ + int i, j, size; + u32 syndrome, word; + + for (i = 0; i < ECC_TBL_SIZE; i++) + ecc_table[i] = 0xFFFFFFFF; + ecc_table[0] = 0x0; + + for (j = 0; j < 5; j++) { + word = (1 << (j + 1)) - 1; /* 0x01 0x03 0x07 0x0f 0x1f */ + size = 26 - j; /* 26, 25, 24, 23, 22 */ + syndrome = 0; + for (i = 0; i < size; i++) { + syndrome = calc_syndrome(word); + ecc_table[syndrome] = word; + word <<= 1; + } + } +} + +static u32 ecc_correct(u32 rdsBits, int *recovered) +{ + u32 syndrome; + u32 errorBits; + + if (recovered) + *recovered = 0; + + /* Calculate Syndrome on Received Packet */ + syndrome = calc_syndrome(rdsBits); + + if (syndrome == 0) + return rdsBits; /* block is clean */ + + /* generate table first time we get here */ + if (!ecc_table_generated) { + generate_ecc_table(); + ecc_table_generated = 1; + } + + /* Attempt to recover block */ + errorBits = ecc_table[syndrome]; + if (errorBits == UNRECOVERABLE_RDS_BLOCK) + return UNRECOVERABLE_RDS_BLOCK; /* Block can not be recovered. + * it is bad packet */ + + rdsBits = rdsBits ^ errorBits; + if (recovered) + (*recovered)++; + return rdsBits; /* ECC correct */ +} + +/* The following table lists the RDS and RBDS Program Type codes + * and their meanings: + * PTY code RDS Program type RBDS Program type */ +static const struct stfm1000_rds_pty stc_tss_pty_tab[] = { + { 0, "No program type", "No program type"}, + { 1, "News", "News"}, + { 2, "Current affairs", "Information"}, + { 3, "Information", "Sports"}, + { 4, "Sports", "Talk"}, + { 5, "Education", "Rock"}, + { 6, "Drama", "Classic Rock"}, + { 7, "Culture", "Adult Hits"}, + { 8, "Science", "Soft Rock"}, + { 9, "Varied", "Top 40"}, + { 10, "Pop", "Music Country"}, + { 11, "Rock", "Music Oldies"}, + { 12, "M.O.R.", "Music Soft"}, + { 13, "Light classical", "Nostalgia"}, + { 14, "Serious", "Classical Jazz"}, + { 15, "Other Music", "Classical"}, + { 16, "Weather", "Rhythm and Blues"}, + { 17, "Finance", "Soft Rhythm and Blues"}, + { 18, "Children's programs", "Language"}, + { 19, "Social Affairs", "Religious Music"}, + { 20, "Religion", "Religious Talk"}, + { 21, "Phone In", "Personality"}, + { 22, "Travel", "Public"}, + { 23, "Leisure", "College"}, + { 24, "Jazz Music", "Unassigned"}, + { 25, "Country Music", "Unassigned"}, + { 26, "National Music", "Unassigned"}, + { 27, "Oldies Music", "Unassigned"}, + { 28, "Folk Music", "Unassigned"}, + { 29, "Documentary", "Weather"}, + { 30, "Alarm Test", "Emergency Test"}, + { 31, "Alarm", "Emergency"}, +}; + +#if 0 +static const char *rds_group_txt[] = { + [RDS_GROUP_TYPE_0A] = "Basic tuning and switching information (0A)", + [RDS_GROUP_TYPE_0B] = "Basic tuning and switching information (0B)", + [RDS_GROUP_TYPE_1A] = "Program item number and slow labeling codes", + [RDS_GROUP_TYPE_1B] = "Program item number", + [RDS_GROUP_TYPE_2A] = "Radio Text (2A)", + [RDS_GROUP_TYPE_2B] = "Radio Text (2B)", + [RDS_GROUP_TYPE_3A] = "Application identification for ODA only", + [RDS_GROUP_TYPE_3B] = "Open data applications", + [RDS_GROUP_TYPE_4A] = "Clock-time and date", + [RDS_GROUP_TYPE_4B] = "Open data applications", + [RDS_GROUP_TYPE_5A] = "Transparent Data Channels (32 ch.) or ODA (5A)", + [RDS_GROUP_TYPE_5B] = "Transparent Data Channels (32 ch.) or ODA (5B)", + [RDS_GROUP_TYPE_6A] = "In House Applications or ODA (6A)", + [RDS_GROUP_TYPE_6B] = "In House Applications or ODA (6B)", + [RDS_GROUP_TYPE_7A] = "Radio Paging or ODA", + [RDS_GROUP_TYPE_7B] = "Open Data Applications", + [RDS_GROUP_TYPE_8A] = "Traffic Message Channel or ODA", + [RDS_GROUP_TYPE_8B] = "Open Data Applications", + [RDS_GROUP_TYPE_9A] = "Emergency warning system or ODA", + [RDS_GROUP_TYPE_9B] = "Open Data Applications", + [RDS_GROUP_TYPE_10A] = "Program Type Name", + [RDS_GROUP_TYPE_10B] = "Open Data Applications (10B)", + [RDS_GROUP_TYPE_11A] = "Open Data Applications (11A)", + [RDS_GROUP_TYPE_11B] = "Open Data Applications (11B)", + [RDS_GROUP_TYPE_12A] = "Open Data Applications (12A)", + [RDS_GROUP_TYPE_12B] = "Open Data Applications (12B)", + [RDS_GROUP_TYPE_13A] = "Enhanced Radio Paging or ODA", + [RDS_GROUP_TYPE_13B] = "Open Data Applications", + [RDS_GROUP_TYPE_14A] = "Enhanced Other Networks information (14A)", + [RDS_GROUP_TYPE_14B] = "Enhanced Other Networks information (14B)", + [RDS_GROUP_TYPE_15A] = "Defined in RBDS", + [RDS_GROUP_TYPE_15B] = "Fast switching information", +}; +#endif + +static void dump_rds_packet(u8 *buf) +{ + u16 pi, offb; + + pi = (u16)(buf[0] << 8) | buf[1]; + offb = (u16)(buf[1] << 8) | buf[2]; + + printk(KERN_INFO "GRP: " + "PI=0x%04x " + "GT=%2d VER=%d TP=%d PTY=%2d " + "PS_SEG=%2d RT_AB=%2d RT_SEG=%2d\n", pi, + RDS_GROUP_TYPE(offb), RDS_VERSION(offb), RDS_TP(offb), + RDS_PTY(offb), + RDS_PS_SEG(offb), RDS_RT_AB(offb), RDS_RT_SEG(offb)); +} + +void stfm1000_rds_process_packet(struct stfm1000_rds_state *rds, u8 *buffer) +{ + struct stfm1000_rds_text *rdst = &rds->text; + /* char tempCallLetters[5] = {0}; */ + struct rds_group_data grp; + int grpno; + u32 offset; + char tps[9]; + int i, seg, idx; + + grp.piCode = ((u16)buffer[0] << 8) | buffer[1]; + grp.offsetB = ((u16)buffer[2] << 8) | buffer[3]; + grp.offsetC = ((u16)buffer[4] << 8) | buffer[5]; + grp.offsetD = ((u16)buffer[6] << 8) | buffer[7]; + + grpno = (grp.offsetB >> (8 + 3)) & 0x1f; + + if (rds_state_to_stfm1000(rds)->rds_info) + dump_rds_packet(buffer); + + /* Is this the first time through? */ + if (!rdst->bRds_detected) { + rdst->pi = grp.piCode; + rdst->tp = RDS_TP(grp.offsetB); + rdst->version = RDS_VERSION(grp.offsetB); + rdst->pty.id = RDS_PTY(grp.offsetB); + rdst->pty.pRds = stc_tss_pty_tab[rdst->pty.id].pRds; + rdst->pty.pRdbs = stc_tss_pty_tab[rdst->pty.id].pRdbs; + rdst->bRds_detected = 1; + } + + /* Have we process too many PI errors? */ + if (grp.piCode != rdst->pi) { + if (rdst->mismatch++ > 10) { + + /* requested reset of RDS */ + rds->reset_req = 1; + + /* signal monitor thread */ + stfm1000_monitor_signal(rds_state_to_stfm1000(rds), + EVENT_RDS_RESET); + + if (rds_state_to_stfm1000(rds)->rds_info) + printk(KERN_INFO "RDS: RESET!!!\n"); + + text_reset(rdst); + } + rdst->consecutiveGood = 0; + return; + } + + if (rdst->consecutiveGood++ > 10) + rdst->mismatch = 0; /* reset bad count */ + + if (rdst->consecutiveGood > rdst->consecutiveGoodMax) + rdst->consecutiveGoodMax = rdst->consecutiveGood; + + switch (grpno) { + case RDS_GROUP_TYPE_0A: + case RDS_GROUP_TYPE_0B: + /* Extract Service Name information */ + offset = RDS_PS_SEG(grp.offsetB) * 2; + rdst->wk_ps[offset] = buffer[6]; /* better */ + rdst->wk_ps[offset + 1] = buffer[7]; + rdst->wk_ps_mask |= 1 << RDS_PS_SEG(grp.offsetB); + + if (rds_state_to_stfm1000(rds)->rds_info) { + for (i = 0; i < 8; i++) { + if (rdst->wk_ps_mask & (1 << i)) { + tps[i * 2] = + rdst->wk_ps[i * 2]; + tps[i * 2 + 1] = + rdst->wk_ps[i * 2 + 1]; + } else { + tps[i * 2] = '_'; + tps[i * 2 + 1] = '_'; + } + } + tps[ARRAY_SIZE(tps) - 1] = '\0'; + if (rds_state_to_stfm1000(rds)->rds_info) + printk(KERN_INFO "RDS-PS (curr): %s\n", tps); + } + + if (rdst->wk_ps_mask != ALL_SEGMENT_BITS) + break; + + if (rdst->ps_valid) { + if (memcmp(rdst->ps, rdst->wk_ps, 8) != 0) { + memset(rdst->cp_ps, 0, 8); + memset(rdst->wk_ps, 0, 8); + rdst->wk_ps_mask = 0; + } + + memset(rdst->ps, 0, 8); + rdst->ps_valid = 0; + break; + } + + /* does working buffer == compare buffer */ + if (memcmp(rdst->cp_ps, rdst->wk_ps, 8) != 0) { + /* just copy from working to compare buffer */ + memcpy(rdst->cp_ps, rdst->wk_ps, 8); + rdst->wk_ps_mask = 0; + break; + } + + /* working buffer matches compare buffer, send to UI */ + memcpy(rdst->ps, rdst->cp_ps, 8); + rdst->ps_valid = 1; + + if (rds_state_to_stfm1000(rds)->rds_info) + printk(KERN_INFO "RDS: PS '%s'\n", rdst->ps); + + /* clear working mask-only */ + rdst->wk_ps_mask = 0; + break; + + case RDS_GROUP_TYPE_2A: + + /* Clear buffer */ + if (rdst->textAB_flag != RDS_RT_AB(grp.offsetB)) { + memset(rdst->wk_text, 0, 64); + rdst->wk_text_mask = 0; + rdst->textAB_flag = RDS_RT_AB(grp.offsetB); + } + + /* Extract Text */ + seg = RDS_RT_SEG(grp.offsetB); + idx = seg * 4; + + #define CNVT_EOT(x) ((x) != RDS_EOT ? (x) : 0) + rdst->wk_text[idx++] = CNVT_EOT(buffer[4]); + rdst->wk_text[idx++] = CNVT_EOT(buffer[5]); + rdst->wk_text[idx++] = CNVT_EOT(buffer[6]); + rdst->wk_text[idx++] = CNVT_EOT(buffer[7]); + + rdst->wk_text_mask |= 1 << seg; + /* scan msg data for EOT. If found set all higher + * mask bits */ + for (idx = 0; idx < 4; idx++) { + if (rdst->text[idx] == RDS_EOT) + break; + } + if (idx < 4) { + /* set current and all higher bits */ + for (idx = RDS_RT_SEG(grp.offsetB); idx < 16; + idx++) + rdst->wk_text_mask |= 1 << idx; + } + + /* Process buffer when filled */ + if (rdst->wk_text_mask != ALL_TEXT_BITS) + break; + + if (!rdst->text_valid) + rdst->text_valid = 1; + else if (memcmp(rdst->text, rdst->wk_text, 64) == 0) + break; + + memcpy(rdst->text, rdst->wk_text, 64); + + if (rds_state_to_stfm1000(rds)->rds_info) + printk(KERN_INFO "RDS: TEXT '%s'\n", rdst->text); + + memset(rdst->wk_text, 0, 64); + rdst->wk_text_mask = 0; + break; + + default: + break; + } +} + +int stfm1000_rds_packet_dequeue(struct stfm1000_rds_state *rds, u8 *buf) +{ + struct stfm1000_rds_pkt *pkt = &rds->pkt; + + if (pkt->buf_cnt == 0) + return -1; + + memcpy(buf, &pkt->buf_queue[pkt->buf_tail][0], 8); + if (++pkt->buf_tail >= RDS_PKT_QUEUE) + pkt->buf_tail = 0; + pkt->buf_cnt--; + + return 0; +} + +void stfm1000_rds_packet_bit(struct stfm1000_rds_state *rds, int bit) +{ + struct stfm1000_rds_pkt *pkt = &rds->pkt; + u32 rdsdata, rdscrc, rdscrc_c, rdscrc_cp; + int correct, correct2, recovered, recovered2; + int RetVal; + + /* Stick into shift register */ + pkt->rdsstream = ((pkt->rdsstream << 1) | bit) & 0x03ffffff; + pkt->bitsinfifo++; + pkt->bitcount++; + + /* wait for 26 bits of block */ + if (pkt->bitsinfifo < 26) + return; + + rdsdata = pkt->rdsstream & 0x03fffc00; /* 16 bits of Info. word */ + rdscrc = pkt->rdsstream & 0x3ff; /* 10 bits of Checkword */ + + switch (pkt->state) { + case SYNC_OFFSET_A: + + RetVal = calc_syndrome(pkt->rdsstream); + + switch (RetVal) { + case RDS_SYNDROME_OFFSETA: + pkt->state = OFFSET_B; + break; + case RDS_SYNDROME_OFFSETB: + pkt->state = OFFSET_C_CP; + break; + case RDS_SYNDROME_OFFSETC: + pkt->state = OFFSET_D; + break; + case RDS_SYNDROME_OFFSETCP: + pkt->state = OFFSET_D; + break; + case RDS_SYNDROME_OFFSETD: + pkt->state = OFFSET_A; + break; + default: + pkt->state = SYNC_OFFSET_A; + break; + } + if (pkt->state == SYNC_OFFSET_A) { + pkt->sync_lost_packets++; + /* XXX send info? */ + break; + } + + pkt->good_packets++; + + rdsdata = pkt->rdsstream & 0x03fffc00; + + /* Save type A packet in buffer */ + rdsdata >>= 10; + pkt->buffer[0] = (rdsdata >> 8); + pkt->buffer[1] = (rdsdata & 0xff); + pkt->bitsinfifo = 0; + + /* We found a block with zero errors, but it is not at the + * start of the group. */ + if (pkt->state == OFFSET_B) + pkt->discardpacket = 0; + else + pkt->discardpacket = 1; + break; + + case OFFSET_A: /* Type A: we are in sync now */ + rdscrc ^= RDS_OFFSETA; + correct = ecc_correct(rdsdata | rdscrc, &recovered); + if (correct == UNRECOVERABLE_RDS_BLOCK) { + pkt->bad_packets++; + pkt->discardpacket++; + pkt->state++; + pkt->bitsinfifo = 0; + break; + } + + if (recovered) + pkt->recovered_packets++; + pkt->good_packets++; + + /* Attempt to see, if we can get the entire group. + * Don't discard. */ + pkt->discardpacket = 0; + rdsdata = correct & 0x03fffc00; + + /* Save type A packet in buffer */ + rdsdata >>= 10; + pkt->buffer[0] = (rdsdata >> 8); + pkt->buffer[1] = (rdsdata & 0xff); + pkt->bitsinfifo = 0; + pkt->state++; + break; + + case OFFSET_B: /* Waiting for type B */ + rdscrc ^= RDS_OFFSETB; + correct = ecc_correct(rdsdata | rdscrc, &recovered); + if (correct == UNRECOVERABLE_RDS_BLOCK) { + pkt->bad_packets++; + pkt->discardpacket++; + pkt->state++; + pkt->bitsinfifo = 0; + break; + } + if (recovered) + pkt->recovered_packets++; + pkt->good_packets++; + + rdsdata = correct & 0x03fffc00; + + /* Save type B packet in buffer */ + rdsdata >>= 10; + pkt->buffer[2] = (rdsdata >> 8); + pkt->buffer[3] = (rdsdata & 0xff); + pkt->bitsinfifo = 0; + pkt->state++; + break; + + case OFFSET_C_CP: /* Waiting for type C or C' */ + rdscrc_c = rdscrc ^ RDS_OFFSETC; + rdscrc_cp = rdscrc ^ RDS_OFFSETCP; + correct = ecc_correct(rdsdata | rdscrc_c, &recovered); + correct2 = ecc_correct(rdsdata | rdscrc_cp, &recovered2); + if (correct == UNRECOVERABLE_RDS_BLOCK + && correct2 == UNRECOVERABLE_RDS_BLOCK) { + pkt->bad_packets++; + pkt->discardpacket++; + pkt->state++; + pkt->bitsinfifo = 0; + break; + } + + if (recovered || recovered2) + pkt->recovered_packets++; + pkt->good_packets++; + + if (correct == UNRECOVERABLE_RDS_BLOCK) + correct = correct2; + + rdsdata = correct & 0x03fffc00; + + /* Save type C packet in buffer */ + rdsdata >>= 10; + pkt->buffer[4] = (rdsdata >> 8); + pkt->buffer[5] = (rdsdata & 0xff); + pkt->bitsinfifo = 0; + pkt->state++; + break; + + case OFFSET_D: /* Waiting for type D */ + rdscrc ^= RDS_OFFSETD; + correct = ecc_correct(rdsdata | rdscrc, &recovered); + if (correct == UNRECOVERABLE_RDS_BLOCK) { + pkt->bad_packets++; + pkt->discardpacket++; + pkt->state = OFFSET_A; + pkt->bitsinfifo = 0; + break; + } + + if (recovered) + pkt->recovered_packets++; + pkt->good_packets++; + + rdsdata = correct & 0x03fffc00; + + /* Save type D packet in buffer */ + rdsdata >>= 10; + pkt->buffer[6] = (rdsdata >> 8); + pkt->buffer[7] = (rdsdata & 0xff); + + /* buffer it if all segments were ok */ + if (pkt->discardpacket) { + /* We're still in sync, so back to state 1 */ + pkt->state = OFFSET_A; + pkt->bitsinfifo = 0; + pkt->discardpacket = 0; + break; + } + + pkt->state++; + /* fall-through */ + + case PACKET_OUT: + pkt->GroupDropOnce = 1; + + /* queue packet */ + if (pkt->buf_cnt < RDS_PKT_QUEUE) { + memcpy(&pkt->buf_queue[pkt->buf_head][0], + pkt->buffer, 8); + if (++pkt->buf_head >= RDS_PKT_QUEUE) + pkt->buf_head = 0; + pkt->buf_cnt++; + } else + pkt->buf_overruns++; + + /* We're still in sync, so back to state 1 */ + pkt->state = OFFSET_A; + pkt->bitsinfifo = 0; + pkt->discardpacket = 0; + break; + + } + + /* Lots of errors? If so, go back to resync mode */ + if (pkt->discardpacket >= 10) { + pkt->state = SYNC_OFFSET_A; /* reset sync state */ + pkt->bitsinfifo = 26; /* resync a bit faster */ + } +} + +/* GROUP_TYPE 0A-0B (buffer must have enough space for 9 bytes) */ +int stfm1000_rds_get_ps(struct stfm1000_rds_state *rds, u8 *buffer, + int bufsize) +{ + struct stfm1000_rds_text *rdst = &rds->text; + + if (bufsize < 9) + return -1; + + if (!rdst->ps_valid) + return -1; + + memcpy(buffer, rdst->ps, 8); + buffer[8] = '\0'; + + return 8; +} + +/* GROUP_TYPE 2A (buffer must have enough space for 65 bytes) */ +int stfm1000_rds_get_text(struct stfm1000_rds_state *rds, u8 *buffer, + int bufsize) +{ + struct stfm1000_rds_text *rdst = &rds->text; + + if (bufsize < 9) + return -1; + + if (!rdst->text_valid) + return -1; + + memcpy(buffer, rdst->text, 64); + buffer[64] = '\0'; + + return 64; +} diff --git a/drivers/media/radio/stfm1000/stfm1000-rds.h b/drivers/media/radio/stfm1000/stfm1000-rds.h new file mode 100644 index 000000000000..44b9a610f86e --- /dev/null +++ b/drivers/media/radio/stfm1000/stfm1000-rds.h @@ -0,0 +1,364 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef STFM1000_RDS_H +#define STFM1000_RDS_H + +#include <linux/types.h> + +/* log2(number of samples in a filter basis) */ +#define RDS_BASISSHIFTS 4 + +/* number of samples in a filter basis */ +#define RDS_BASISLENGTH (1 << RDS_BASISSHIFTS) + +#define TIME_ADAPT_OVER 100 + +/* 2^(-this) is the RMS leaky bucket time constant */ +#define RMSALPHASHIFTS 5 + +#define PROCESS_RDS_BITS 128 + +#define RDS_BITBUFSIZE 1024 /* was 128 */ +struct stfm1000_rds_bitstream { + u32 buf[RDS_BITBUFSIZE]; /* bit buffer */ + int HeadBitCount; /* bit buffer head counter */ + int TailBitCount; /* bit buffer tail counter */ +}; + +struct stfm1000_rds_demod { + u32 mixandsum1; /* Accumulator for first + * basis filter */ + u32 mixandsum2; /* Accumulator for 2nd + * basis filter */ + u32 i; /* Phase Index, 32 phases per + * RDS bit */ + u32 return_num; /* Set if there is a new RDS bit */ + u32 BitAlignmentCounter; /* Counts bits for timing purposes */ + int sampskip; /* Requested timing shift (on i) */ + + int DisablePushing; /* Disables phase push algorithm + * (phase push happens when Ph_RMS[x], + * x != 0, is consistently the maximum + * Ph_RMS) */ + int MixPopDone; /* Last mixer phase set request is + * done */ + u8 rds_big_timeshift; /* If set, indicates a push or large + * timing shift occurred */ + int return_rdsdemod; /* Output, (most recent bit) XOR + * (prev bit) */ + u32 RDS_BIT_AMP_STAT_REG9; /* Size of bit (RMS of RDS signal at + * bitslicing instant, typically 220 + * to 270) */ + s32 decomp_hist; /* Most recent basis filter output */ + s32 decomp_hist_p; /* Previous basis filter output */ + s32 PhaseValue[4]; /* Half-basis phase samples over the + * most recent bit */ + u32 Ph_RMS[4]; /* RMS of the four half-basis phases */ + s32 timing_adj; /* Timing loop leaky-bucket + * accumulator */ + u32 MixPhase0Mag; /* Magnitude of RDS signal with RDS + * mixer phase 0 (from mixer phase + * determination) */ + u32 MixPhase1Mag; /* Magnitude of RDS signal with RDS + * mixer phase 1 (from mixer phase + * determination) */ + u32 PhasePopMaxRMS; /* Maximum RMS observed since last + * phase pop */ + u32 PrePopRMS; /* Max of Ph_RMS array right before the + * most recent phase pop */ + u8 MixPhaseState; /* State of RDS mixer phase + * determination state machine */ + int MixPhaseDetInProg; /* Set if RDS mix phase determination + * is in progress */ + int sliced_data; /* The most recent bit decision */ + u8 PopSafetyZone; /* Countdown timer, holds off next + * phase pop after a recent one */ + u8 PushSafetyZone; /* Countdown timer, holds off next + * phase pop after a timing push (b/c + * timing push resets Ph_RMS vars) */ + u8 SkipSafetyZone; /* Countdown timer, holds off next + * phase skip (small timing adj) */ + int Synchronous; /* RDS has been determined to be + * synchronous to pilot */ + u8 PushLastMaxPh; /* The index at which Ph_RMS is + * maximum ("x" in the above two + * comments) */ + s32 PushCounter; /* Counts instances of Ph_RMS[x], x!=0, + * being the maximum Ph_RMS */ + s32 SkipsAccum; /* Accumulation of all timing skips + * since RDS demod started */ + s32 SdnomSk; /* Skips counter used for SDNOMINAL + * adaption */ + + /* update this everytime it's changed & put it here */ + unsigned int rds_mix_offset : 1; + + unsigned int sdnom_adapt : 1; + unsigned int pCoefForcedMono : 1; /* copy of filter parameter */ + unsigned int PhasePoppingEnabled : 1; + + unsigned int mix_msg_pending : 1; + u8 mix_msg; + unsigned int mix_msg_overrun; + unsigned int mix_msg_processed_changed; + + unsigned int sdnominal_msg_pending : 1; + int sdnominal_msg; + unsigned int sdnominal_msg_overrun; + + u32 RdsDemodSkippedBitCnt; /* bit skipped by RDS demodulator due + * to unavailable space in buf[] + * (bit buffer) */ +}; + +#define RDS_OFFSETA 0x0fc +#define RDS_OFFSETB 0x198 +#define RDS_OFFSETC 0x168 +#define RDS_OFFSETCP 0x350 +#define RDS_OFFSETD 0x1b4 + +#define RDS_SYNDROME_OFFSETA 0x3d8 +#define RDS_SYNDROME_OFFSETB 0x3d4 +#define RDS_SYNDROME_OFFSETC 0x25c +#define RDS_SYNDROME_OFFSETCP 0x3cc +#define RDS_SYNDROME_OFFSETD 0x258 + +#define SYNC_OFFSET_A 0 /* default state */ +#define OFFSET_A 1 +#define OFFSET_B 2 +#define OFFSET_C_CP 3 +#define OFFSET_D 4 +#define PACKET_OUT 5 + +#define ECC_TBL_SIZE 1024 +#define UNRECOVERABLE_RDS_BLOCK 0xffffffff + +#define RDS_PKT_QUEUE 16 + +struct stfm1000_rds_pkt { + int state; /* Current state */ + u32 rdsstream; /* Current RDS data */ + u8 buffer[8]; /* temporary storage of RDS data */ + int discardpacket; /* discard packet count */ + int sync_lost_packets; /* sync lost */ + int good_packets; /* good packet */ + int bad_packets; /* bad packet */ + int recovered_packets; /* recovered packet */ + int bitsinfifo; /* bits count */ + int GroupDropOnce; /* Send Group Drop Message once */ + int bitcount; /* Counter for Number of Bits read */ + + /* queue the packets here */ + int buf_overruns; + int buf_head; + int buf_tail; + int buf_cnt; + int buf_queue[RDS_PKT_QUEUE][8]; +}; + +#define AUDIT 0 +#define ALL_SEGMENT_BITS 0xF +#define ALL_TEXT_BITS 0xFFFF + +struct stfm1000_rds_pty { + u8 id; /* Program Type ID */ + u8 *pRds; /* RDS description */ + u8 *pRdbs; /* RDBS description */ +}; + +struct stfm1000_rds_text { + u8 bRds_detected; /* Has the first packet come in yet? */ + u16 pi; /* Program Identification Code (PI) */ + struct stfm1000_rds_pty pty; /* Program Type (PTY)) */ + u8 tp; /* Traffic Program (TP) identification + * code */ + u8 ps[9]; /* Program Service Name Sent to UI */ + u8 altFreq[2]; /* Alternate frequency (AF) */ + u8 callLetters[5]; /* For US, stations call letters */ + + u8 text[65]; /* Radio Text A */ + + unsigned int version : 1; /* Is station broadcasting version + * A or B (B0) */ + unsigned int ps_valid : 1; /* station name is valid */ + unsigned int text_valid : 1; /* Text is valid */ + unsigned int textAB_flag : 1; /* Current flag setting, reset if flag + * changes */ + + /*------------------Working area--------------------------- */ + u8 cp_ps[8]; /* Compare buffer for PS */ + u8 wk_ps[8]; /* Program Service buffer */ + u8 wk_ps_mask; /* lower 4 bits must be set + * before copy */ + u8 wk_text[64]; /* Radio Text buffer */ + u16 wk_text_mask; /* all bits must be set before copy */ + + /*-------------------Counters------------------------------ */ + u32 messages; /* total number of messages recieved */ + u32 unsupported; /* call to unsupported group type */ + u32 mismatch; /* Mismatched values */ + u32 consecutiveGood; /* Consecutive good will clear bad */ + u32 consecutiveGoodMax; /* Max counter for paramaters */ +}; + +/* Maximum number of RDS groups described in the U.S. RBDS Standard. */ +#define MAX_RDS_GROUPS_SUPPORTED 32 + +/* Common Constants */ +#define RDS_LINE_FEED 0xA +#define RDS_EOT 0xD + +/* Offsets into OFFSETB */ +#define RDS_GROUP_TYPE(x) (((x) >> 12) & 0xF) +#define RDS_VERSION(x) (((x) >> 11) & 0x1) +#define RDS_TP(x) (((x) >> 10) & 0x1) +#define RDS_PTY(x) (((x) >> 5) & 0x1F) +#define RDS_PS_SEG(x) ((x) & 0x3) +#define RDS_RT_AB(x) (((x) >> 4) & 0x1) +#define RDS_RT_SEG(x) ((x) & 0xF) + +/* This values corresond to the Group Types defined */ +/* In the U.S. RBDS standard. */ +#define RDS_GROUP_TYPE_0A 0 /* Basic tuning and switching information */ +#define RDS_GROUP_TYPE_0B 1 /* Basic tuning and switching information */ +#define RDS_GROUP_TYPE_1A 2 /* Program item number and slow labeling + * codes */ +#define RDS_GROUP_TYPE_1B 3 /* Program item number */ +#define RDS_GROUP_TYPE_2A 4 /* Radio Text */ +#define RDS_GROUP_TYPE_2B 5 /* Radio Text */ +#define RDS_GROUP_TYPE_3A 6 /* Application identification for ODA + * only */ +#define RDS_GROUP_TYPE_3B 7 /* Open data applications */ +#define RDS_GROUP_TYPE_4A 8 /* Clock-time and date */ +#define RDS_GROUP_TYPE_4B 9 /* Open data applications */ +#define RDS_GROUP_TYPE_5A 10 /* Transparent Data Channels (32 channels) + * or ODA */ +#define RDS_GROUP_TYPE_5B 11 /* Transparent Data Channels (32 channels) + * or ODA */ +#define RDS_GROUP_TYPE_6A 12 /* In House Applications or ODA */ +#define RDS_GROUP_TYPE_6B 13 /* In House Applications or ODA */ +#define RDS_GROUP_TYPE_7A 14 /* Radio Paging or ODA */ +#define RDS_GROUP_TYPE_7B 15 /* Open Data Applications */ +#define RDS_GROUP_TYPE_8A 16 /* Traffic Message Channel or ODA */ +#define RDS_GROUP_TYPE_8B 17 /* Open Data Applications */ +#define RDS_GROUP_TYPE_9A 18 /* Emergency warning system or ODA */ +#define RDS_GROUP_TYPE_9B 19 /* Open Data Applications */ +#define RDS_GROUP_TYPE_10A 20 /* Program Type Name */ +#define RDS_GROUP_TYPE_10B 21 /* Open Data Applications */ +#define RDS_GROUP_TYPE_11A 22 /* Open Data Applications */ +#define RDS_GROUP_TYPE_11B 23 /* Open Data Applications */ +#define RDS_GROUP_TYPE_12A 24 /* Open Data Applications */ +#define RDS_GROUP_TYPE_12B 25 /* Open Data Applications */ +#define RDS_GROUP_TYPE_13A 26 /* Enhanced Radio Paging or ODA */ +#define RDS_GROUP_TYPE_13B 27 /* Open Data Applications */ +#define RDS_GROUP_TYPE_14A 28 /* Enhanced Other Networks information */ +#define RDS_GROUP_TYPE_14B 29 /* Enhanced Other Networks information */ +#define RDS_GROUP_TYPE_15A 30 /* Defined in RBDS */ +#define RDS_GROUP_TYPE_15B 31 /* Fast switching information */ +#define NUM_DEFINED_RDS_GROUPS 32 /* Number of groups defined in RBDS + * standard */ + +/* Structure representing Generic packet of 64 bits. */ +struct rds_group_data { + u16 piCode; /* * Program ID */ + u16 offsetB; /* subject to group type */ + u16 offsetC; /* subject to group type */ + u16 offsetD; /* subject to group type */ +}; + +/* Structure representing Group 0A (Service Name) */ +struct rds_group0A { + u16 piCode; /* * Program ID */ + u16 offsetB; /* subject to group type */ + u8 freq[2]; /* alt frequency 0=1 */ + u8 text[2]; /* Name segment */ +}; + +/* Structure representing Group 0B (Service Name) */ +struct rds_group0B { + u16 piCode; /* * Program ID */ + u16 offsetB; /* subject to group type */ + u16 piCode_dup; /* Duplicate PI Code */ + u8 text[2]; /* station text */ +}; + +/* Structure representing Group 2A (Radio Text) (64 char) */ +struct rds_group2A { + u16 piCode; /* * Program ID */ + u16 offsetB; /* subject to group type */ + u8 text[4]; +}; + +/* Structure representing Group 2B (Radio Text) (32 char) */ +struct rds_group2B { + u16 piCode; /* * Program ID */ + u16 offsetB; /* subject to group type */ + u16 piCode_dup; /* Duplicate PI Code */ + u8 text[2]; +}; + +/* Structure representing all groups */ +union rds_msg { + struct rds_group2B gt2B; + struct rds_group2A gt2A; + struct rds_group0B gt0B; + struct rds_group0A gt0A; + struct rds_group_data gt00; +}; + +struct stfm1000_rds_state { + struct stfm1000_rds_bitstream bitstream; + struct stfm1000_rds_demod demod; + struct stfm1000_rds_pkt pkt; + struct stfm1000_rds_text text; + unsigned int reset_req : 1; +}; + +/* callback from rds etc. */ +void stfm1000_rds_reset(struct stfm1000_rds_state *rds); +void stfm1000_rds_start(struct stfm1000_rds_state *rds); +void stfm1000_rds_stop(struct stfm1000_rds_state *rds); + +/* call these from the monitor thread, but with interrupts disabled */ +int stfm1000_rds_mix_msg_get(struct stfm1000_rds_state *rds); +int stfm1000_rds_mix_msg_processed(struct stfm1000_rds_state *rds, + int mix_msg); +int stfm1000_rds_sdnominal_msg_get(struct stfm1000_rds_state *rds); +int stfm1000_rds_sdnominal_msg_processed(struct stfm1000_rds_state *rds, + int sdnominal_msg); +int stfm1000_rds_bits_available(struct stfm1000_rds_state *rds); +int stmf1000_rds_get_bit(struct stfm1000_rds_state *rds); + +/* called from audio handler (interrupt) */ +void stfm1000_rds_demod(struct stfm1000_rds_state *rds, const u16 *dri_data, + int total); + +/* call these from monitor thread, interrupts enabled */ +void stfm1000_rds_packet_bit(struct stfm1000_rds_state *rds, int bit); +int stfm1000_rds_packet_dequeue(struct stfm1000_rds_state *rds, u8 *buf); +void stfm1000_rds_process_packet(struct stfm1000_rds_state *rds, u8 *buffer); + +static inline int stfm1000_rds_get_reset_req(struct stfm1000_rds_state *rds) +{ + return rds->reset_req; +} + +/* GROUP_TYPE 0A-0B */ +int stfm1000_rds_get_ps(struct stfm1000_rds_state *rds, u8 *buffer, + int bufsize); + +/* GROUP_TYPE 2A */ +int stfm1000_rds_get_text(struct stfm1000_rds_state *rds, u8 *buffer, + int bufsize); + +#endif diff --git a/drivers/media/radio/stfm1000/stfm1000-regs.h b/drivers/media/radio/stfm1000/stfm1000-regs.h new file mode 100644 index 000000000000..c9476b7d67e1 --- /dev/null +++ b/drivers/media/radio/stfm1000/stfm1000-regs.h @@ -0,0 +1,165 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef STFM1000_REGS_H +#define STFM1000_REGS_H + +/* registers */ +#define STFM1000_TUNE1 0x00 +#define STFM1000_SDNOMINAL 0x04 +#define STFM1000_PILOTTRACKING 0x08 +#define STFM1000_INITIALIZATION1 0x10 +#define STFM1000_INITIALIZATION2 0x14 +#define STFM1000_INITIALIZATION3 0x18 +#define STFM1000_INITIALIZATION4 0x1C +#define STFM1000_INITIALIZATION5 0x20 +#define STFM1000_INITIALIZATION6 0x24 +#define STFM1000_REF 0x28 +#define STFM1000_LNA 0x2C +#define STFM1000_MIXFILT 0x30 +#define STFM1000_CLK1 0x34 +#define STFM1000_CLK2 0x38 +#define STFM1000_ADC 0x3C +#define STFM1000_AGC_CONTROL1 0x44 +#define STFM1000_AGC_CONTROL2 0x48 +#define STFM1000_DATAPATH 0x5C +#define STFM1000_RMS 0x60 +#define STFM1000_AGC_STAT 0x64 +#define STFM1000_SIGNALQUALITY 0x68 +#define STFM1000_DCEST 0x6C +#define STFM1000_RSSI_TONE 0x70 +#define STFM1000_PILOTCORRECTION 0x74 +#define STFM1000_ATTENTION 0x78 +#define STFM1000_CLK3 0x7C +#define STFM1000_CHIPID 0x80 + +/* number of registers */ +#define STFM1000_NUM_REGS ((0x80 + 4) / 4) + +#define STFM1000_FREQUENCY_100KHZ_MIN 758 +#define STFM1000_FREQUENCY_100KHZ_RANGE 325 +#define STFM1000_FREQUENCY_100KHZ_MAX (STFM1000_FREQUENCY_100KHZ_MIN + \ + STFM1000_FREQUENCY_100KHZ_RANGE) + +#define STFM1000_TUNE1_B2_MIX 0x001C0000 +#define STFM1000_TUNE1_CICOSR 0x00007E00 +#define STFM1000_TUNE1_PLL_DIV 0x000001FF + +#define STFM1000_CHIP_REV_TA1 0x00000001 +#define STFM1000_CHIP_REV_TA2 0x00000002 +#define STFM1000_CHIP_REV_TB1 0x00000011 +#define STFM1000_CHIP_REV_TB2 0x00000012 + +/* DATAPATH bits we use */ +#define STFM1000_DP_EN 0x01000000 +#define STFM1000_DB_ACCEPT 0x00010000 +#define STFM1000_SAI_CLK_DIV_MASK 0x7c +#define STFM1000_SAI_CLK_DIV_SHIFT 2 +#define STFM1000_SAI_CLK_DIV(x) \ + (((x) << STFM1000_SAI_CLK_DIV_SHIFT) & STFM1000_SAI_CLK_DIV_MASK) +#define STFM1000_SAI_EN 0x00000001 + +/* AGC_CONTROL1 bits we use */ +#define STFM1000_B2_BYPASS_AGC_CTL 0x00004000 +#define STFM1000_B2_BYPASS_FILT_MASK 0x0000000C +#define STFM1000_B2_BYPASS_FILT_SHIFT 2 +#define STFM1000_B2_BYPASS_FILT(x) \ + (((x) << STFM1000_B2_BYPASS_FILT_SHIFT) & STFM1000_B2_BYPASS_FILT_MASK) +#define STFM1000_B2_LNATH_MASK 0x001F0000 +#define STFM1000_B2_LNATH_SHIFT 16 +#define STFM1000_B2_LNATH(x) \ + (((x) << STFM1000_B2_LNATH_SHIFT) & STFM1000_B2_LNATH_MASK) + +/* AGC_STAT bits we use */ +#define STFM1000_AGCOUT_STAT_MASK 0x1F000000 +#define STFM1000_AGCOUT_STAT_SHIFT 24 +#define STFM1000_LNA_RMS_MASK 0x00001F00 +#define STFM1000_LNA_RMS_SHIFT 8 + +/* PILOTTRACKING bits we use */ +#define STFM1000_B2_PILOTTRACKING_EN 0x00008000 +#define STFM1000_B2_PILOTLPF_TIMECONSTANT_MASK 0x00000f00 +#define STFM1000_B2_PILOTLPF_TIMECONSTANT_SHIFT 8 +#define STFM1000_B2_PILOTLPF_TIMECONSTANT(x) \ + (((x) << STFM1000_B2_PILOTLPF_TIMECONSTANT_SHIFT) & \ + STFM1000_B2_PILOTLPF_TIMECONSTANT_MASK) +#define STFM1000_B2_PFDSCALE_MASK 0x000000f0 +#define STFM1000_B2_PFDSCALE_SHIFT 4 +#define STFM1000_B2_PFDSCALE(x) \ + (((x) << STFM1000_B2_PFDSCALE_SHIFT) & STFM1000_B2_PFDSCALE_MASK) +#define STFM1000_B2_PFDFILTER_SPEEDUP_MASK 0x0000000f +#define STFM1000_B2_PFDFILTER_SPEEDUP_SHIFT 0 +#define STFM1000_B2_PFDFILTER_SPEEDUP(x) \ + (((x) << STFM1000_B2_PFDFILTER_SPEEDUP_SHIFT) & \ + STFM1000_B2_PFDFILTER_SPEEDUP_MASK) + +/* PILOTCORRECTION bits we use */ +#define STFM1000_PILOTEST_TA2_MASK 0xff000000 +#define STFM1000_PILOTEST_TA2_SHIFT 24 +#define STFM1000_PILOTEST_TB2_MASK 0xfe000000 +#define STFM1000_PILOTEST_TB2_SHIFT 25 + +/* INITIALIZATION1 bits we use */ +#define STFM1000_B2_BYPASS_FILT_MASK 0x0000000C +#define STFM1000_B2_BYPASS_FILT_SHIFT 2 +#define STFM1000_B2_BYPASS_FILT(x) \ + (((x) << STFM1000_B2_BYPASS_FILT_SHIFT) & STFM1000_B2_BYPASS_FILT_MASK) + +/* INITIALIZATION2 bits we use */ +#define STFM1000_DRI_CLK_EN 0x80000000 +#define STFM1000_DEEMPH_50_75B 0x00000100 +#define STFM1000_RDS_ENABLE 0x00100000 +#define STFM1000_RDS_MIXOFFSET 0x00200000 + +/* INITIALIZATION3 bits we use */ +#define STFM1000_B2_NEAR_CHAN_MIX_MASK 0x1c000000 +#define STFM1000_B2_NEAR_CHAN_MIX_SHIFT 26 +#define STFM1000_B2_NEAR_CHAN_MIX(x) \ + (((x) << STFM1000_B2_NEAR_CHAN_MIX_SHIFT) & \ + STFM1000_B2_NEAR_CHAN_MIX_MASK) + +/* CLK1 bits we use */ +#define STFM1000_ENABLE_TAPDELAYFIX 0x00000020 + +/* REF bits we use */ +#define STFM1000_LNA_AMP1_IMPROVE_DISTORTION 0x08000000 + +/* LNA bits we use */ +#define STFM1000_AMP2_IMPROVE_DISTORTION 0x08000000 +#define STFM1000_ANTENNA_TUNECAP_MASK 0x001F0000 +#define STFM1000_ANTENNA_TUNECAP_SHIFT 16 +#define STFM1000_ANTENNA_TUNECAP(x) \ + (((x) << STFM1000_ANTENNA_TUNECAP_SHIFT) & \ + STFM1000_ANTENNA_TUNECAP_MASK) +#define STFM1000_IBIAS2_UP 0x00000008 +#define STFM1000_IBIAS2_DN 0x00000004 +#define STFM1000_IBIAS1_UP 0x00000002 +#define STFM1000_IBIAS1_DN 0x00000001 +#define STFM1000_USEATTEN_MASK 0x00600000 +#define STFM1000_USEATTEN_SHIFT 21 +#define STFM1000_USEATTEN(x) \ + (((x) << STFM1000_USEATTEN_SHIFT) & STFM1000_USEATTEN_MASK) + +/* SIGNALQUALITY bits we use */ +#define STFM1000_NEAR_CHAN_AMPLITUDE_MASK 0x0000007F +#define STFM1000_NEAR_CHAN_AMPLITUDE_SHIFT 0 +#define STFM1000_NEAR_CHAN_AMPLITUDE(x) \ + (((x) << STFM1000_NEAR_CHAN_AMPLITUDE_SHIFT) & \ + STFM1000_NEAR_CHAN_AMPLITUDE_MASK) + +/* precalc tables elements */ +struct stfm1000_tune1 { + unsigned int tune1; /* at least 32 bit */ + unsigned int sdnom; +}; + +#endif diff --git a/drivers/media/radio/stfm1000/stfm1000.h b/drivers/media/radio/stfm1000/stfm1000.h new file mode 100644 index 000000000000..5ae6f38db3b0 --- /dev/null +++ b/drivers/media/radio/stfm1000/stfm1000.h @@ -0,0 +1,254 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef STFM1000_H +#define STFM1000_H + +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <linux/i2c.h> +#include <linux/list.h> +#include <linux/wait.h> +#include <linux/irq.h> +#include <media/videobuf-dma-sg.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <mach/dma.h> + +#include "stfm1000-regs.h" + +#include "stfm1000-filter.h" +#include "stfm1000-rds.h" + +struct stfm1000 { + struct list_head devlist; + int idx; + + struct i2c_client *client; + struct video_device radio; + + /* alsa */ + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + struct stmp3xxx_dma_descriptor *dma; + int desc_num; + int dma_ch; + int dma_irq; + int attn_irq; + + struct mutex state_lock; + int read_count; + int read_offset; + int blocks; + int blksize; + int bufsize; + + struct mutex deffered_work_lock; + struct execute_work snd_capture_start_work; + struct execute_work snd_capture_stop_work; + + int now_recording; + int alsa_initialized; + int stopping_recording; + + /* actual DRI buffer */ + dma_addr_t dri_phys; + void *dri_buf; + int dri_bufsz; + + /* various */ + u16 curvol; + int users; + int removed; + struct mutex xfer_lock; + u8 revid; + + unsigned int dbgflg; + + /* shadow registers */ + u32 shadow_regs[STFM1000_NUM_REGS]; + u32 reg_rw_set[(STFM1000_NUM_REGS + 31) / 32]; + u32 reg_ra_set[(STFM1000_NUM_REGS + 31) / 32]; + u32 reg_dirty_set[(STFM1000_NUM_REGS + 31) / 32]; + + /* tuning parameters (not everything is used for now) */ + u16 tune_rssi_th; /* sd_ctl_TuneRssiTh_u16 */ + u16 tune_mpx_dc_th; /* sd_ctl_TuneMpxDcTh_u16 */ + u16 adj_chan_th; /* sd_ctl_AdjChanTh_u16 */ + u16 pilot_est_th; /* sd_ctl_PilotEstTh_u16 */ + u16 coef_lna_turn_off_th; /* sd_ctl_pCoefLnaTurnOffTh_u16 */ + u16 coef_lna_turn_on_th; /* sd_ctl_pCoefLnaTurnOnTh_u16 */ + u16 reg_agc_ref_lna_off; /* sd_ctl_pRegAgcRefLnaOff_u16 */ + u16 reg_agc_ref_lna_on; /* sd_ctl_pRegAgcRefLnaOn_u16 */ + + u32 sdnominal_pivot; /* sd_ctl_SdnominalData_u32 */ + + /* jiffies of the next monitor cycle */ + unsigned long next_quality_monitor; + unsigned long next_agc_monitor; + + unsigned int mute : 1; /* XXX */ + unsigned int lna_driving : 1; /* sd_ctl_LnaDriving_u1 */ + unsigned int weak_signal : 1; /* sd_ctl_WeakSignal_u1 */ + unsigned int is_station : 1; /* XXX */ + unsigned int force_mono : 1; /* XXX */ + unsigned int signal_indicator : 1; /* XXX */ + unsigned int stereo_indicator : 1; /* XXX */ + unsigned int agc_monitor : 1; /* XXX */ + unsigned int quality_monitor : 1; /* XXX */ + unsigned int pilot_present : 1; /* sd_ctl_PilotPresent_u1 */ + unsigned int prev_pilot_present : 1; /* XXX */ + unsigned int stereo : 1; + unsigned int active : 1; /* set when audio enabled */ + unsigned int rds_enable : 1; /* set when rds is enabled */ + unsigned int rds_present : 1; /* RDS info present */ + unsigned int rds_sync : 1; /* RDS force sync */ + unsigned int rds_demod_running : 1; /* RDS demod is running ATM */ + unsigned int rds_sdnominal_adapt : 1; /* adapt for better recept. */ + unsigned int rds_phase_pop : 1; /* enable phase pop */ + unsigned int rds_info : 1; /* print debugging info RDS */ + unsigned int tuning_grid_50KHz : 1; /* tuning grid of 50Khz */ + u32 rssi; /* rssi last decoded frame */ + u16 rssi_dc_est_log; + u16 signal_strength; /* is rssi_dc_est_log */ + u16 rds_signal_th; /* RDS threshold */ + s16 mpx_dc; /* sd_ctl_ShadowToneData_i16 */ + + u32 tune_cap_a_f; /* float! sd_ctl_TuneCapA_f */ + u32 tune_cap_b_f; /* float! sd_ctl_TuneCapB_f */ + + int monitor_period; /* period of the monitor */ + int quality_monitor_period; /* update period in ms */ + int agc_monitor_period; /* update period in ms */ + + int georegion; /* current graphical region */ + + /* last tuned frequency */ + int freq; /* 88.0 = 8800 */ + + /* weak signal processing filter state */ + struct stfm1000_filter_parms filter_parms; + + /* state of rds */ + spinlock_t rds_lock; + struct stfm1000_rds_state rds_state; + unsigned int rds_pkt_bad; + unsigned int rds_pkt_good; + unsigned int rds_pkt_recovered; + unsigned int rds_pkt_lost_sync; + unsigned int rds_bit_overruns; + + /* monitor thread */ + wait_queue_head_t thread_wait; + unsigned long thread_events; + struct task_struct *thread; +}; + +#define EVENT_RDS_BITS 0 +#define EVENT_RDS_MIXFILT 1 +#define EVENT_RDS_SDNOMINAL 2 +#define EVENT_RDS_RESET 3 + +#define STFM1000_DBGFLG_I2C (1 << 0) + +static inline struct stfm1000 *stfm1000_from_file(struct file *file) +{ + return container_of(video_devdata(file), struct stfm1000, radio); +} + +/* in stfm1000-i2c.c */ + +/* setup reg set */ +void stfm1000_setup_reg_set(struct stfm1000 *stfm1000); + +/* direct access to registers bypassing the shadow register set */ +int stfm1000_raw_read(struct stfm1000 *stfm1000, int reg, u32 *value); +int stfm1000_raw_write(struct stfm1000 *stfm1000, int reg, u32 value); + +/* access using the shadow register set */ +int stfm1000_write(struct stfm1000 *stfm1000, int reg, u32 value); +int stfm1000_read(struct stfm1000 *stfm1000, int reg, u32 *value); +int stfm1000_write_masked(struct stfm1000 *stfm1000, int reg, u32 value, + u32 mask); +int stfm1000_set_bits(struct stfm1000 *stfm1000, int reg, u32 value); +int stfm1000_clear_bits(struct stfm1000 *stfm1000, int reg, u32 value); + +struct stfm1000_reg { + unsigned int regno; + u32 value; +}; + +#define STFM1000_REG_END -1 +#define STFM1000_REG_DELAY -2 + +#define STFM1000_REG_SET_BITS_MASK 0x1000 +#define STFM1000_REG_CLEAR_BITS_MASK 0x2000 + +#define STFM1000_REG(r, v) \ + { .regno = STFM1000_ ## r , .value = (v) } + +#define STFM1000_END \ + { .regno = STFM1000_REG_END } + +#define STFM1000_DELAY(x) \ + { .regno = STFM1000_REG_DELAY, .value = (x) } + +#define STFM1000_REG_SETBITS(r, v) \ + { .regno = STFM1000_ ## r | STFM1000_REG_SET_BITS_MASK, \ + .value = (v) } + +#define STFM1000_REG_CLRBITS(r, v) \ + { .regno = STFM1000_ ## r | STFM1000_REG_CLEAR_BITS_MASK, \ + .value = (v) } + +int stfm1000_write_regs(struct stfm1000 *stfm1000, + const struct stfm1000_reg *reg); + +/* in stfm1000-precalc.c */ +extern const struct stfm1000_tune1 +stfm1000_tune1_table[STFM1000_FREQUENCY_100KHZ_RANGE]; + +/* exported for use by alsa driver */ + +struct stfm1000_dri_sample { + /* L+R */ + u16 l_plus_r; + /* L-R */ + u16 l_minus_r; + /* Rx signal strength channel */ + u16 rssi; + /* Radio data service channel */ + u16 rds; +}; + +struct stfm1000_alsa_ops { + int (*init)(struct stfm1000 *stfm1000); + void (*release)(struct stfm1000 *stfm1000); + void (*dma_irq)(struct stfm1000 *stfm1000); + void (*attn_irq)(struct stfm1000 *stfm1000); +}; + +extern struct list_head stfm1000_devlist; +extern struct stfm1000_alsa_ops *stfm1000_alsa_ops; + +/* needed for setting the interrupt handlers from alsa */ +irqreturn_t stfm1000_dri_attn_irq(int irq, void *dev_id); +irqreturn_t stfm1000_dri_dma_irq(int irq, void *dev_id); +void stfm1000_decode_block(struct stfm1000 *stfm1000, const s16 *src, s16 *dst, int count); +void stfm1000_take_down(struct stfm1000 *stfm1000); +void stfm1000_bring_up(struct stfm1000 *stfm1000); +void stfm1000_tune_current(struct stfm1000 *stfm1000); + +void stfm1000_monitor_signal(struct stfm1000 *stfm1000, int bit); + +#endif diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index dcf9fa9264bb..7210c4f3c355 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -550,6 +550,45 @@ config VIDEO_W9966 Check out <file:Documentation/video4linux/w9966.txt> for more information. +config VIDEO_MXC_CAMERA + tristate "MXC Video For Linux Camera" + depends on VIDEO_DEV && ARCH_MXC + default y + ---help--- + This is the video4linux2 capture driver based on MXC IPU/eMMA module. + +source "drivers/media/video/mxc/capture/Kconfig" + +config VIDEO_MXC_OUTPUT + tristate "MXC Video For Linux Video Output" + depends on VIDEO_DEV && ARCH_MXC + default y + ---help--- + This is the video4linux2 output driver based on MXC IPU/eMMA module. + +source "drivers/media/video/mxc/output/Kconfig" + +config VIDEO_PXP + tristate "STMP3xxx PxP" + depends on VIDEO_DEV && VIDEO_V4L2 && ARCH_STMP3XXX + select VIDEOBUF_DMA_CONTIG + ---help--- + This is a video4linux driver for the Freescale PxP + (Pixel Pipeline). This module supports output overlay of + the STMP3xxx framebuffer on a video stream. + + To compile this driver as a module, choose M here: the + module will be called pxp. + +config VIDEO_MXC_OPL + tristate + depends on VIDEO_DEV && ARCH_MXC + default n + ---help--- + This is the ARM9-optimized OPL (Open Primitives Library) software + rotation/mirroring implementation. It may be used by eMMA video + capture or output device. + config VIDEO_CPIA tristate "CPiA Video For Linux" depends on VIDEO_V4L1 diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 9f2e3214a482..f361f073e356 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -88,6 +88,14 @@ obj-$(CONFIG_VIDEO_W9966) += w9966.o obj-$(CONFIG_VIDEO_PMS) += pms.o obj-$(CONFIG_VIDEO_VINO) += vino.o obj-$(CONFIG_VIDEO_STRADIS) += stradis.o +obj-$(CONFIG_VIDEO_MXC_IPU_CAMERA) += mxc/capture/ +obj-$(CONFIG_VIDEO_MXC_EMMA_CAMERA) += mxc/capture/ +obj-$(CONFIG_VIDEO_MXC_CSI_CAMERA) += mxc/capture/ +obj-$(CONFIG_VIDEO_MXC_IPU_OUTPUT) += mxc/output/ +obj-$(CONFIG_VIDEO_MXC_IPUV1_WVGA_OUTPUT) += mxc/output/ +obj-$(CONFIG_VIDEO_MXC_EMMA_OUTPUT) += mxc/output/ +obj-$(CONFIG_VIDEO_MXC_OPL) += mxc/opl/ +obj-$(CONFIG_VIDEO_PXP) += pxp.o obj-$(CONFIG_VIDEO_CPIA) += cpia.o obj-$(CONFIG_VIDEO_CPIA_PP) += cpia_pp.o obj-$(CONFIG_VIDEO_CPIA_USB) += cpia_usb.o diff --git a/drivers/media/video/mxc/capture/Kconfig b/drivers/media/video/mxc/capture/Kconfig new file mode 100644 index 000000000000..adab0a886f36 --- /dev/null +++ b/drivers/media/video/mxc/capture/Kconfig @@ -0,0 +1,123 @@ +if VIDEO_MXC_CAMERA + +menu "MXC Camera/V4L2 PRP Features support" +config VIDEO_MXC_IPU_CAMERA + bool + depends on VIDEO_MXC_CAMERA && MXC_IPU + default y + +config VIDEO_MXC_EMMA_CAMERA + tristate "MX27 eMMA support" + depends on VIDEO_MXC_CAMERA && MXC_EMMA && FB_MXC_SYNC_PANEL + select VIDEO_MXC_OPL + default y + +config VIDEO_MXC_CSI_CAMERA + tristate "MX25 CSI camera support" + depends on !VIDEO_MXC_EMMA_CAMERA + +config VIDEO_MXC_CSI_DMA + bool "CSI-DMA Still Image Capture support" + depends on VIDEO_MXC_EMMA_CAMERA + default n + ---help--- + Use CSI-DMA method instead of CSI-PrP link to capture still image. This allows + to use less physical contiguous memory to capture big resolution still image. But + with this method the CSC (Color Space Conversion) and resize are not supported. + If unsure, say N. + +choice + prompt "Select Camera/TV Decoder" + default MXC_CAMERA_OV3640 + depends on VIDEO_MXC_CAMERA + +config MXC_CAMERA_MC521DA + tristate "Magnachip mc521da camera support" + select I2C_MXC + depends on VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the mc521da Camera with your MXC system, say Y here. + +config MXC_EMMA_CAMERA_MICRON111 + tristate "Micron mt9v111 camera support with eMMA" + select I2C_MXC + depends on VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the mt9v111 Camera with your MXC system, say Y here. + +config MXC_CAMERA_OV2640_EMMA + tristate "OmniVision ov2640 camera support with eMMA" + depends on VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the ov2640 Camera with your MXC system, say Y here. + +config MXC_CAMERA_MICRON111 + tristate "Micron mt9v111 camera support" + select I2C_MXC + depends on ! VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the mt9v111 Camera with your MXC system, say Y here. + +config MXC_CAMERA_OV2640 + tristate "OmniVision ov2640 camera support" + depends on !VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the ov2640 Camera with your MXC system, say Y here. + +config MXC_CAMERA_OV3640 + tristate "OmniVision ov3640 camera support" + depends on !VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the ov3640 Camera with your MXC system, say Y here. + +config MXC_TVIN_ADV7180 + tristate "Analog Device adv7180 TV Decoder Input support" + depends on MACH_MX35_3DS + ---help--- + If you plan to use the adv7180 video decoder with your MXC system, say Y here. + +endchoice + +config MXC_IPU_PRP_VF_SDC + tristate "Pre-Processor VF SDC library" + depends on VIDEO_MXC_IPU_CAMERA && FB_MXC_SYNC_PANEL + default y + ---help--- + Use case PRP_VF_SDC: + Preprocessing image from smart sensor for viewfinder and + displaying it on synchronous display with SDC use case. + If SDC BG is selected, Rotation will not be supported. + CSI -> IC (PRP VF) -> MEM + MEM -> IC (ROT) -> MEM + MEM -> SDC (FG/BG) + +config MXC_IPU_PRP_VF_ADC + tristate "Pre-Processor VF ADC library" + depends on VIDEO_MXC_IPU_CAMERA && FB_MXC_ASYNC_PANEL + default y + ---help--- + Use case PRP_VF_ADC: + Preprocessing image from smart sensor for viewfinder and + displaying it on asynchronous display. + CSI -> IC (PRP VF) -> ADC2 + +config MXC_IPU_PRP_ENC + tristate "Pre-processor Encoder library" + depends on VIDEO_MXC_IPU_CAMERA + default y + ---help--- + Use case PRP_ENC: + Preprocessing image from smart sensor for encoder. + CSI -> IC (PRP ENC) -> MEM + +config MXC_IPU_CSI_ENC + tristate "IPU CSI Encoder library" + depends on VIDEO_MXC_IPU_CAMERA + default y + ---help--- + Use case IPU_CSI_ENC: + Get raw image with CSI from smart sensor for encoder. + CSI -> MEM +endmenu + +endif diff --git a/drivers/media/video/mxc/capture/Makefile b/drivers/media/video/mxc/capture/Makefile new file mode 100644 index 000000000000..112923c8fc8f --- /dev/null +++ b/drivers/media/video/mxc/capture/Makefile @@ -0,0 +1,39 @@ +ifeq ($(CONFIG_VIDEO_MXC_IPU_CAMERA),y) + obj-$(CONFIG_VIDEO_MXC_CAMERA) += mxc_v4l2_capture.o + obj-$(CONFIG_MXC_IPU_PRP_VF_ADC) += ipu_prp_vf_adc.o + obj-$(CONFIG_MXC_IPU_PRP_VF_SDC) += ipu_prp_vf_sdc.o ipu_prp_vf_sdc_bg.o + obj-$(CONFIG_MXC_IPU_PRP_ENC) += ipu_prp_enc.o ipu_still.o + obj-$(CONFIG_MXC_IPU_CSI_ENC) += ipu_csi_enc.o ipu_still.o +endif + +obj-$(CONFIG_VIDEO_MXC_CSI_CAMERA) += fsl_csi.o csi_v4l2_capture.o + +mx27_capture-objs := mx27_prphw.o mx27_prpsw.o emma_v4l2_capture.o +obj-$(CONFIG_VIDEO_MXC_EMMA_CAMERA) += mx27_csi.o mx27_capture.o + +mc521da_camera-objs := mc521da.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_MC521DA) += mc521da_camera.o + +emma_mt9v111_camera-objs := emma_mt9v111.o sensor_clock.o +obj-$(CONFIG_MXC_EMMA_CAMERA_MICRON111) += emma_mt9v111_camera.o + +mt9v111_camera-objs := mt9v111.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_MICRON111) += mt9v111_camera.o + +hv7161_camera-objs := hv7161.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_HV7161) += hv7161_camera.o + +s5k3aaex_camera-objs := s5k3aaex.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_S5K3AAEX) += s5k3aaex_camera.o + +emma_ov2640_camera-objs := emma_ov2640.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_OV2640_EMMA) += emma_ov2640_camera.o + +ov2640_camera-objs := ov2640.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_OV2640) += ov2640_camera.o + +ov3640_camera-objs := ov3640.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_OV3640) += ov3640_camera.o + +adv7180_tvin-objs := adv7180.o sensor_clock.o +obj-$(CONFIG_MXC_TVIN_ADV7180) += adv7180_tvin.o diff --git a/drivers/media/video/mxc/capture/adv7180.c b/drivers/media/video/mxc/capture/adv7180.c new file mode 100644 index 000000000000..07a68ecaa0a5 --- /dev/null +++ b/drivers/media/video/mxc/capture/adv7180.c @@ -0,0 +1,981 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file adv7180.c + * + * @brief Analog Device ADV7180 video decoder functions + * + * @ingroup Camera + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/wait.h> +#include <linux/videodev2.h> +#include <linux/workqueue.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-int-device.h> +#include "mxc_v4l2_capture.h" + +static struct regulator *dvddio_regulator; +static struct regulator *dvdd_regulator; +static struct regulator *avdd_regulator; +static struct regulator *pvdd_regulator; + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +static int adv7180_probe(struct i2c_client *adapter, + const struct i2c_device_id *id); +static int adv7180_detach(struct i2c_client *client); + +static const struct i2c_device_id adv7180_id[] = { + {"adv7180", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, adv7180_id); + +static struct i2c_driver adv7180_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "adv7180", + }, + .probe = adv7180_probe, + .remove = adv7180_detach, + .id_table = adv7180_id, +}; + +/*! + * Maintains the information on the current state of the sesor. + */ +struct sensor { + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_captureparm streamcap; + bool on; + + /* control settings */ + int brightness; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + v4l2_std_id std_id; +} adv7180_data; + +/*! List of input video formats supported. The video formats is corresponding + * with v4l2 id in video_fmt_t + */ +typedef enum { + ADV7180_NTSC = 0, /*!< Locked on (M) NTSC video signal. */ + ADV7180_PAL, /*!< (B, G, H, I, N)PAL video signal. */ + ADV7180_NOT_LOCKED, /*!< Not locked on a signal. */ +} video_fmt_idx; + +/*! Number of video standards supported (including 'not locked' signal). */ +#define ADV7180_STD_MAX (ADV7180_PAL + 1) + +/*! Video format structure. */ +typedef struct { + int v4l2_id; /*!< Video for linux ID. */ + char name[16]; /*!< Name (e.g., "NTSC", "PAL", etc.) */ + u16 raw_width; /*!< Raw width. */ + u16 raw_height; /*!< Raw height. */ + u16 active_width; /*!< Active width. */ + u16 active_height; /*!< Active height. */ +} video_fmt_t; + +/*! Description of video formats supported. + * + * PAL: raw=720x625, active=720x576. + * NTSC: raw=720x525, active=720x480. + */ +static video_fmt_t video_fmts[] = { + { /*! NTSC */ + .v4l2_id = V4L2_STD_NTSC, + .name = "NTSC", + .raw_width = 720 - 1, /* SENS_FRM_WIDTH */ + .raw_height = 288 - 1, /* SENS_FRM_HEIGHT */ + .active_width = 720, /* ACT_FRM_WIDTH plus 1 */ + .active_height = (480 / 2), /* ACT_FRM_WIDTH plus 1 */ + }, + { /*! (B, G, H, I, N) PAL */ + .v4l2_id = V4L2_STD_PAL, + .name = "PAL", + .raw_width = 720 - 1, + .raw_height = (576 / 2) + 24 * 2 - 1, + .active_width = 720, + .active_height = (576 / 2), + }, + { /*! Unlocked standard */ + .v4l2_id = V4L2_STD_ALL, + .name = "Autodetect", + .raw_width = 720 - 1, + .raw_height = (576 / 2) + 24 * 2 - 1, + .active_width = 720, + .active_height = (576 / 2), + }, +}; + +/*!* Standard index of ADV7180. */ +static video_fmt_idx video_idx = ADV7180_PAL; + +/*! @brief This mutex is used to provide mutual exclusion. + * + * Create a mutex that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * and variables that were defined above. + */ +static DECLARE_MUTEX(mutex); + +#define IF_NAME "adv7180" +#define ADV7180_INPUT_CTL 0x00 /* Input Control */ +#define ADV7180_STATUS_1 0x10 /* Status #1 */ +#define ADV7180_BRIGHTNESS 0x0a /* Brightness */ +#define ADV7180_IDENT 0x11 /* IDENT */ +#define ADV7180_VSYNC_FIELD_CTL_1 0x31 /* VSYNC Field Control #1 */ +#define ADV7180_MANUAL_WIN_CTL 0x3d /* Manual Window Control */ +#define ADV7180_SD_SATURATION_CB 0xe3 /* SD Saturation Cb */ +#define ADV7180_SD_SATURATION_CR 0xe4 /* SD Saturation Cr */ +#define ADV7180_PWR_MNG 0x0f /* Power Management */ + +/* supported controls */ +/* This hasn't been fully implemented yet. + * This is how it should work, though. */ +static struct v4l2_queryctrl adv7180_qctrl[] = { + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = 0, /* check this value */ + .maximum = 255, /* check this value */ + .step = 1, /* check this value */ + .default_value = 127, /* check this value */ + .flags = 0, + }, { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0, /* check this value */ + .maximum = 255, /* check this value */ + .step = 0x1, /* check this value */ + .default_value = 127, /* check this value */ + .flags = 0, + } +}; + +/*********************************************************************** + * I2C transfert. + ***********************************************************************/ + +/*! Read one register from a ADV7180 i2c slave device. + * + * @param *reg register in the device we wish to access. + * + * @return 0 if success, an error code otherwise. + */ +static inline int adv7180_read(u8 reg) +{ + int val; + val = i2c_smbus_read_byte_data(adv7180_data.i2c_client, reg); + if (val < 0) { + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:read reg error: reg=%2x \n", __func__, reg); + return -1; + } + return val; +} + +/*! Write one register of a ADV7180 i2c slave device. + * + * @param *reg register in the device we wish to access. + * + * @return 0 if success, an error code otherwise. + */ +static int adv7180_write_reg(u8 reg, u8 val) +{ + if (i2c_smbus_write_byte_data(adv7180_data.i2c_client, reg, val) < 0) { + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:write reg error:reg=%2x,val=%2x\n", __func__, + reg, val); + return -1; + } + return 0; +} + +/*********************************************************************** + * mxc_v4l2_capture interface. + ***********************************************************************/ + +/*! + * Return attributes of current video standard. + * Since this device autodetects the current standard, this function also + * sets the values that need to be changed if the standard changes. + * There is no set std equivalent function. + * + * @return None. + */ +static void adv7180_get_std(v4l2_std_id *std) +{ + int tmp; + int idx; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180_get_std\n"); + + /* Read the AD_RESULT to get the detect output video standard */ + tmp = adv7180_read(ADV7180_STATUS_1) & 0x70; + + down(&mutex); + if (tmp == 0x40) { + /* PAL */ + *std = V4L2_STD_PAL; + idx = ADV7180_PAL; + } else if (tmp == 0) { + /*NTSC*/ + *std = V4L2_STD_NTSC; + idx = ADV7180_NTSC; + } else { + *std = V4L2_STD_ALL; + idx = ADV7180_NOT_LOCKED; + dev_dbg(&adv7180_data.i2c_client->dev, + "Got invalid video standard! \n"); + } + up(&mutex); + + /* This assumes autodetect which this device uses. */ + if (*std != adv7180_data.std_id) { + video_idx = idx; + adv7180_data.std_id = *std; + adv7180_data.pix.width = video_fmts[video_idx].raw_width; + adv7180_data.pix.height = video_fmts[video_idx].raw_height; + } +} + +/*********************************************************************** + * IOCTL Functions from v4l2_int_ioctl_desc. + ***********************************************************************/ + +/*! + * ioctl_g_ifparm - V4L2 sensor interface handler for vidioc_int_g_ifparm_num + * s: pointer to standard V4L2 device structure + * p: pointer to standard V4L2 vidioc_int_g_ifparm_num ioctl structure + * + * Gets slave interface parameters. + * Calculates the required xclk value to support the requested + * clock parameters in p. This value is returned in the p + * parameter. + * + * vidioc_int_g_ifparm returns platform-specific information about the + * interface settings used by the sensor. + * + * Called on open. + */ +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_g_ifparm\n"); + + if (s == NULL) { + pr_err(" ERROR!! no slave device set!\n"); + return -1; + } + + /* Initialize structure to 0s then set any non-0 values. */ + memset(p, 0, sizeof(*p)); + p->if_type = V4L2_IF_TYPE_BT656; /* This is the only possibility. */ + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.nobt_hs_inv = 1; + + /* ADV7180 has a dedicated clock so no clock settings needed. */ + + return 0; +} + +/*! + * Sets the camera power. + * + * s pointer to the camera device + * on if 1, power is to be turned on. 0 means power is to be turned off + * + * ioctl_s_power - V4L2 sensor interface handler for vidioc_int_s_power_num + * @s: pointer to standard V4L2 device structure + * @on: power state to which device is to be set + * + * Sets devices power state to requrested state, if possible. + * This is called on open, close, suspend and resume. + */ +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_s_power\n"); + + if (on && !sensor->on) { + gpio_sensor_active(); + if (adv7180_write_reg(ADV7180_PWR_MNG, 0) != 0) + return -EIO; + } else if (!on && sensor->on) { + if (adv7180_write_reg(ADV7180_PWR_MNG, 0x24) != 0) + return -EIO; + gpio_sensor_inactive(); + } + + sensor->on = on; + + return 0; +} + +/*! + * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the sensor's video CAPTURE parameters. + */ +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_captureparm *cparm = &a->parm.capture; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_g_parm\n"); + + switch (a->type) { + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = sensor->streamcap.capability; + cparm->timeperframe = sensor->streamcap.timeperframe; + cparm->capturemode = sensor->streamcap.capturemode; + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + break; + + default: + pr_debug("ioctl_g_parm:type is unknown %d\n", a->type); + break; + } + + return 0; +} + +/*! + * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the sensor to use the input parameters, if possible. If + * not possible, reverts to the old parameters and returns the + * appropriate error code. + * + * This driver cannot change these settings. + */ +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_s_parm\n"); + + switch (a->type) { + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + break; + + default: + pr_debug(" type is unknown - %d\n", a->type); + break; + } + + return 0; +} + +/*! + * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the sensor's current pixel format in the v4l2_format + * parameter. + */ +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_g_fmt_cap\n"); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" Returning size of %dx%d\n", + sensor->pix.width, sensor->pix.height); + f->fmt.pix = sensor->pix; + break; + + case V4L2_BUF_TYPE_PRIVATE: { + v4l2_std_id std; + adv7180_get_std(&std); + f->fmt.pix.pixelformat = (u32)std; + } + break; + + default: + f->fmt.pix = sensor->pix; + break; + } + + return 0; +} + +/*! + * ioctl_queryctrl - V4L2 sensor interface handler for VIDIOC_QUERYCTRL ioctl + * @s: pointer to standard V4L2 device structure + * @qc: standard V4L2 VIDIOC_QUERYCTRL ioctl structure + * + * If the requested control is supported, returns the control information + * from the video_control[] array. Otherwise, returns -EINVAL if the + * control is not supported. + */ +static int ioctl_queryctrl(struct v4l2_int_device *s, + struct v4l2_queryctrl *qc) +{ + int i; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_queryctrl\n"); + + for (i = 0; i < ARRAY_SIZE(adv7180_qctrl); i++) + if (qc->id && qc->id == adv7180_qctrl[i].id) { + memcpy(qc, &(adv7180_qctrl[i]), + sizeof(*qc)); + return (0); + } + + return -EINVAL; +} + +/*! + * ioctl_g_ctrl - V4L2 sensor interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_G_CTRL ioctl structure + * + * If the requested control is supported, returns the control's current + * value from the video_control[] array. Otherwise, returns -EINVAL + * if the control is not supported. + */ +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int ret = 0; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_g_ctrl\n"); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_BRIGHTNESS\n"); + adv7180_data.brightness = adv7180_read(ADV7180_BRIGHTNESS); + vc->value = adv7180_data.brightness; + break; + case V4L2_CID_CONTRAST: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_CONTRAST\n"); + vc->value = adv7180_data.contrast; + break; + case V4L2_CID_SATURATION: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_SATURATION\n"); + adv7180_data.saturation = adv7180_read(ADV7180_SD_SATURATION_CB); + vc->value = adv7180_data.saturation; + break; + case V4L2_CID_HUE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_HUE\n"); + vc->value = adv7180_data.hue; + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + break; + case V4L2_CID_DO_WHITE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_DO_WHITE_BALANCE\n"); + break; + case V4L2_CID_RED_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_RED_BALANCE\n"); + vc->value = adv7180_data.red; + break; + case V4L2_CID_BLUE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_BLUE_BALANCE\n"); + vc->value = adv7180_data.blue; + break; + case V4L2_CID_GAMMA: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_GAMMA\n"); + break; + case V4L2_CID_EXPOSURE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_EXPOSURE\n"); + vc->value = adv7180_data.ae_mode; + break; + case V4L2_CID_AUTOGAIN: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_AUTOGAIN\n"); + break; + case V4L2_CID_GAIN: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_GAIN\n"); + break; + case V4L2_CID_HFLIP: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_HFLIP\n"); + break; + case V4L2_CID_VFLIP: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_VFLIP\n"); + break; + default: + dev_dbg(&adv7180_data.i2c_client->dev, + " Default case\n"); + vc->value = 0; + ret = -EPERM; + break; + } + + return ret; +} + +/*! + * ioctl_s_ctrl - V4L2 sensor interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_S_CTRL ioctl structure + * + * If the requested control is supported, sets the control's current + * value in HW (and updates the video_control[] array). Otherwise, + * returns -EINVAL if the control is not supported. + */ +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + u8 tmp; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_s_ctrl\n"); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_BRIGHTNESS\n"); + tmp = vc->value; + adv7180_write_reg(ADV7180_BRIGHTNESS, tmp); + adv7180_data.brightness = vc->value; + break; + case V4L2_CID_CONTRAST: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_CONTRAST\n"); + break; + case V4L2_CID_SATURATION: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_SATURATION\n"); + tmp = vc->value; + adv7180_write_reg(ADV7180_SD_SATURATION_CB, tmp); + adv7180_write_reg(ADV7180_SD_SATURATION_CR, tmp); + adv7180_data.saturation = vc->value; + break; + case V4L2_CID_HUE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_HUE\n"); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + break; + case V4L2_CID_DO_WHITE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_DO_WHITE_BALANCE\n"); + break; + case V4L2_CID_RED_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_RED_BALANCE\n"); + break; + case V4L2_CID_BLUE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_BLUE_BALANCE\n"); + break; + case V4L2_CID_GAMMA: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_GAMMA\n"); + break; + case V4L2_CID_EXPOSURE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_EXPOSURE\n"); + break; + case V4L2_CID_AUTOGAIN: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_AUTOGAIN\n"); + break; + case V4L2_CID_GAIN: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_GAIN\n"); + break; + case V4L2_CID_HFLIP: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_HFLIP\n"); + break; + case V4L2_CID_VFLIP: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_VFLIP\n"); + break; + default: + dev_dbg(&adv7180_data.i2c_client->dev, + " Default case\n"); + retval = -EPERM; + break; + } + + return retval; +} + +/*! + * ioctl_init - V4L2 sensor interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_init\n"); + return 0; +} + +/*! + * ioctl_dev_init - V4L2 sensor interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_dev_init\n"); + return 0; +} + +/*! + * This structure defines all the ioctls for this module. + */ +static struct v4l2_int_ioctl_desc adv7180_ioctl_desc[] = { + + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func *)ioctl_dev_init}, + + /*! + * Delinitialise the dev. at slave detach. + * The complement of ioctl_dev_init. + */ +/* {vidioc_int_dev_exit_num, (v4l2_int_ioctl_func *)ioctl_dev_exit}, */ + + {vidioc_int_s_power_num, (v4l2_int_ioctl_func *)ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func *)ioctl_g_ifparm}, +/* {vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *)ioctl_g_needs_reset}, */ +/* {vidioc_int_reset_num, (v4l2_int_ioctl_func *)ioctl_reset}, */ + {vidioc_int_init_num, (v4l2_int_ioctl_func *)ioctl_init}, + + /*! + * VIDIOC_ENUM_FMT ioctl for the CAPTURE buffer type. + */ +/* {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap}, */ + + /*! + * VIDIOC_TRY_FMT ioctl for the CAPTURE buffer type. + * This ioctl is used to negotiate the image capture size and + * pixel format without actually making it take effect. + */ +/* {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_try_fmt_cap}, */ + + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_g_fmt_cap}, + + /*! + * If the requested format is supported, configures the HW to use that + * format, returns error code if format not supported or HW can't be + * correctly configured. + */ +/* {vidioc_int_s_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_s_fmt_cap}, */ + + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func *)ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func *)ioctl_s_parm}, + {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func *)ioctl_queryctrl}, + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func *)ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func *)ioctl_s_ctrl}, +}; + +static struct v4l2_int_slave adv7180_slave = { + .ioctls = adv7180_ioctl_desc, + .num_ioctls = ARRAY_SIZE(adv7180_ioctl_desc), +}; + +static struct v4l2_int_device adv7180_int_device = { + .module = THIS_MODULE, + .name = "adv7180", + .type = v4l2_int_type_slave, + .u = { + .slave = &adv7180_slave, + }, +}; + + +/*********************************************************************** + * I2C client and driver. + ***********************************************************************/ + +/*! ADV7180 Reset function. + * + * @return None. + */ +static void adv7180_hard_reset(void) +{ + dev_dbg(&adv7180_data.i2c_client->dev, + "In adv7180:adv7180_hard_reset\n"); + + /*! Driver works fine without explicit register + * initialization. Furthermore, initializations takes about 2 seconds + * at startup... + */ + + /*! Set YPbPr input on AIN1,4,5 and normal + * operations(autodection of all stds). + */ + adv7180_write_reg(ADV7180_INPUT_CTL, 0x09); + + /*! Datasheet recommends: */ + adv7180_write_reg(ADV7180_VSYNC_FIELD_CTL_1, 0x02); + adv7180_write_reg(ADV7180_MANUAL_WIN_CTL, 0xa2); +} + +/*! ADV7180 I2C attach function. + * + * @param *adapter struct i2c_adapter *. + * + * @return Error code indicating success or failure. + */ + +/*! + * ADV7180 I2C probe function. + * Function set in i2c_driver struct. + * Called by insmod. + * + * @param *adapter I2C adapter descriptor. + * + * @return Error code indicating success or failure. + */ +static int adv7180_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rev_id; + int ret = 0; + struct mxc_tvin_platform_data *plat_data = client->dev.platform_data; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180_probe\n"); + + if (plat_data->dvddio_reg) { + dvddio_regulator = + regulator_get(&client->dev, plat_data->dvddio_reg); + if (!IS_ERR_VALUE((unsigned long)dvddio_regulator)) { + regulator_set_voltage(dvddio_regulator, 3300000, 3300000); + if (regulator_enable(dvddio_regulator) != 0) + return -ENODEV; + } + } + + if (plat_data->dvdd_reg) { + dvdd_regulator = + regulator_get(&client->dev, plat_data->dvdd_reg); + if (!IS_ERR_VALUE((unsigned long)dvdd_regulator)) { + regulator_set_voltage(dvdd_regulator, 1800000, 1800000); + if (regulator_enable(dvdd_regulator) != 0) + return -ENODEV; + } + } + + if (plat_data->avdd_reg) { + avdd_regulator = + regulator_get(&client->dev, plat_data->avdd_reg); + if (!IS_ERR_VALUE((unsigned long)avdd_regulator)) { + regulator_set_voltage(avdd_regulator, 1800000, 1800000); + if (regulator_enable(avdd_regulator) != 0) + return -ENODEV; + } + } + + if (plat_data->pvdd_reg) { + pvdd_regulator = + regulator_get(&client->dev, plat_data->pvdd_reg); + if (!IS_ERR_VALUE((unsigned long)pvdd_regulator)) { + regulator_set_voltage(pvdd_regulator, 1800000, 1800000); + if (regulator_enable(pvdd_regulator) != 0) + return -ENODEV; + } + } + + if (plat_data->reset) + plat_data->reset(); + + if (plat_data->pwdn) + plat_data->pwdn(1); + + msleep(1); + + /* Set initial values for the sensor struct. */ + memset(&adv7180_data, 0, sizeof(adv7180_data)); + adv7180_data.i2c_client = client; + adv7180_data.streamcap.timeperframe.denominator = 30; + adv7180_data.streamcap.timeperframe.numerator = 1; + adv7180_data.std_id = V4L2_STD_ALL; + video_idx = ADV7180_NOT_LOCKED; + adv7180_data.pix.width = video_fmts[video_idx].raw_width; + adv7180_data.pix.height = video_fmts[video_idx].raw_height; + adv7180_data.pix.pixelformat = V4L2_PIX_FMT_UYVY; /* YUV422 */ + adv7180_data.pix.priv = 1; /* 1 is used to indicate TV in */ + adv7180_data.on = true; + + gpio_sensor_active(); + + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:adv7180 probe i2c address is 0x%02X \n", + __func__, adv7180_data.i2c_client->addr); + + /*! Read the revision ID of the tvin chip */ + rev_id = adv7180_read(ADV7180_IDENT); + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:Analog Device adv7%2X0 detected! \n", __func__, + rev_id); + + /*! ADV7180 initialization. */ + adv7180_hard_reset(); + + pr_debug(" type is %d (expect %d)\n", + adv7180_int_device.type, v4l2_int_type_slave); + pr_debug(" num ioctls is %d\n", + adv7180_int_device.u.slave->num_ioctls); + + /* This function attaches this structure to the /dev/video0 device. + * The pointer in priv points to the mt9v111_data structure here.*/ + adv7180_int_device.priv = &adv7180_data; + ret = v4l2_int_device_register(&adv7180_int_device); + + return ret; +} + +/*! + * ADV7180 I2C detach function. + * Called on rmmod. + * + * @param *client struct i2c_client*. + * + * @return Error code indicating success or failure. + */ +static int adv7180_detach(struct i2c_client *client) +{ + struct mxc_tvin_platform_data *plat_data = client->dev.platform_data; + + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:Removing %s video decoder @ 0x%02X from adapter %s \n", + __func__, IF_NAME, client->addr << 1, client->adapter->name); + + if (plat_data->pwdn) + plat_data->pwdn(0); + + if (dvddio_regulator) { + regulator_disable(dvddio_regulator); + regulator_put(dvddio_regulator); + } + + if (dvdd_regulator) { + regulator_disable(dvdd_regulator); + regulator_put(dvdd_regulator); + } + + if (avdd_regulator) { + regulator_disable(avdd_regulator); + regulator_put(avdd_regulator); + } + + if (pvdd_regulator) { + regulator_disable(pvdd_regulator); + regulator_put(pvdd_regulator); + } + + v4l2_int_device_unregister(&adv7180_int_device); + + return 0; +} + +/*! + * ADV7180 init function. + * Called on insmod. + * + * @return Error code indicating success or failure. + */ +static __init int adv7180_init(void) +{ + u8 err = 0; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180_init\n"); + + /* Tells the i2c driver what functions to call for this driver. */ + err = i2c_add_driver(&adv7180_i2c_driver); + if (err != 0) + pr_err("%s:driver registration failed, error=%d \n", + __func__, err); + + return err; +} + +/*! + * ADV7180 cleanup function. + * Called on rmmod. + * + * @return Error code indicating success or failure. + */ +static void __exit adv7180_clean(void) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180_clean\n"); + i2c_del_driver(&adv7180_i2c_driver); + gpio_sensor_inactive(); +} + +module_init(adv7180_init); +module_exit(adv7180_clean); + +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Anolog Device ADV7180 video decoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/csi_v4l2_capture.c b/drivers/media/video/mxc/capture/csi_v4l2_capture.c new file mode 100644 index 000000000000..3266d2500081 --- /dev/null +++ b/drivers/media/video/mxc/capture/csi_v4l2_capture.c @@ -0,0 +1,1024 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/media/video/mxc/capture/csi_v4l2_capture.c + * This file is derived from mxc_v4l2_capture.c + * + * @brief MX25 Video For Linux 2 driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/io.h> +#include <linux/semaphore.h> +#include <linux/pagemap.h> +#include <linux/vmalloc.h> +#include <linux/types.h> +#include <linux/fb.h> +#include <linux/dma-mapping.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-int-device.h> +#include <linux/mxcfb.h> +#include "mxc_v4l2_capture.h" +#include "fsl_csi.h" + +static int video_nr = -1; +static cam_data *g_cam; + +static int csi_v4l2_master_attach(struct v4l2_int_device *slave); +static void csi_v4l2_master_detach(struct v4l2_int_device *slave); +static u8 camera_power(cam_data *cam, bool cameraOn); + +/*! Information about this driver. */ +static struct v4l2_int_master csi_v4l2_master = { + .attach = csi_v4l2_master_attach, + .detach = csi_v4l2_master_detach, +}; + +static struct v4l2_int_device csi_v4l2_int_device = { + .module = THIS_MODULE, + .name = "csi_v4l2_cap", + .type = v4l2_int_type_master, + .u = { + .master = &csi_v4l2_master, + }, +}; + +/*! + * Indicates whether the palette is supported. + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_UYVY or V4L2_PIX_FMT_YUV420 + * + * @return 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return (palette == V4L2_PIX_FMT_RGB565) || + (palette == V4L2_PIX_FMT_UYVY) || (palette == V4L2_PIX_FMT_YUV420); +} + +/*! + * start the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int start_preview(cam_data *cam) +{ + unsigned long fb_addr = (unsigned long)cam->v4l2_fb.base; + + __raw_writel(fb_addr, CSI_CSIDMASA_FB1); + __raw_writel(fb_addr, CSI_CSIDMASA_FB2); + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_DMA_REFLASH_RFF, CSI_CSICR3); + + csi_enable_int(0); + + return 0; +} + +/*! + * shut down the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int stop_preview(cam_data *cam) +{ + csi_disable_int(); + + /* set CSI_CSIDMASA_FB1 and CSI_CSIDMASA_FB2 to default value */ + __raw_writel(0, CSI_CSIDMASA_FB1); + __raw_writel(0, CSI_CSIDMASA_FB2); + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_DMA_REFLASH_RFF, CSI_CSICR3); + + return 0; +} + +/*************************************************************************** + * VIDIOC Functions. + **************************************************************************/ + +/*! + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int csi_v4l2_g_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + f->fmt.pix = cam->v2f.fmt.pix; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_OVERLAY\n"); + f->fmt.win = cam->win; + break; + default: + pr_debug(" type is invalid\n"); + retval = -EINVAL; + } + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + + return retval; +} + +/*! + * V4L2 - csi_v4l2_s_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int csi_v4l2_s_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + int size = 0; + int bytesperline = 0; + int *width, *height; + + pr_debug("In MVC: %s\n", __func__); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type=V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + if (!valid_mode(f->fmt.pix.pixelformat)) { + pr_err("ERROR: v4l2 capture: %s: format " + "not supported\n", __func__); + return -EINVAL; + } + + /* Handle case where size requested is larger than cuurent + * camera setting. */ + if ((f->fmt.pix.width > cam->crop_bounds.width) + || (f->fmt.pix.height > cam->crop_bounds.height)) { + /* Need the logic here, calling vidioc_s_param if + * camera can change. */ + pr_debug("csi_v4l2_s_fmt size changed\n"); + } + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + height = &f->fmt.pix.width; + width = &f->fmt.pix.height; + } else { + width = &f->fmt.pix.width; + height = &f->fmt.pix.height; + } + + if ((cam->crop_bounds.width / *width > 8) || + ((cam->crop_bounds.width / *width == 8) && + (cam->crop_bounds.width % *width))) { + *width = cam->crop_bounds.width / 8; + if (*width % 8) + *width += 8 - *width % 8; + pr_err("ERROR: v4l2 capture: width exceeds limit " + "resize to %d.\n", *width); + } + + if ((cam->crop_bounds.height / *height > 8) || + ((cam->crop_bounds.height / *height == 8) && + (cam->crop_bounds.height % *height))) { + *height = cam->crop_bounds.height / 8; + if (*height % 8) + *height += 8 - *height % 8; + pr_err("ERROR: v4l2 capture: height exceeds limit " + "resize to %d.\n", *height); + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_RGB565: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + csi_set_16bit_imagpara(f->fmt.pix.width, + f->fmt.pix.height); + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_UYVY: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + csi_set_16bit_imagpara(f->fmt.pix.width, + f->fmt.pix.height); + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_YUV420: + size = f->fmt.pix.width * f->fmt.pix.height * 3 / 2; + csi_set_12bit_imagpara(f->fmt.pix.width, + f->fmt.pix.height); + bytesperline = f->fmt.pix.width; + break; + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_RGB24: + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_NV12: + default: + pr_debug(" case not supported\n"); + break; + } + + if (f->fmt.pix.bytesperline < bytesperline) + f->fmt.pix.bytesperline = bytesperline; + else + bytesperline = f->fmt.pix.bytesperline; + + if (f->fmt.pix.sizeimage < size) + f->fmt.pix.sizeimage = size; + else + size = f->fmt.pix.sizeimage; + + cam->v2f.fmt.pix = f->fmt.pix; + + if (cam->v2f.fmt.pix.priv != 0) { + if (copy_from_user(&cam->offset, + (void *)cam->v2f.fmt.pix.priv, + sizeof(cam->offset))) { + retval = -EFAULT; + break; + } + } + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + pr_debug(" type=V4L2_BUF_TYPE_VIDEO_OVERLAY\n"); + cam->win = f->fmt.win; + break; + default: + retval = -EINVAL; + } + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + + return retval; +} + +/*! + * V4L2 - csi_v4l2_s_param function + * Allows setting of capturemode and frame rate. + * + * @param cam structure cam_data * + * @param parm structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int csi_v4l2_s_param(cam_data *cam, struct v4l2_streamparm *parm) +{ + struct v4l2_ifparm ifparm; + struct v4l2_format cam_fmt; + struct v4l2_streamparm currentparm; + int err = 0; + + pr_debug("In %s\n", __func__); + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_err(KERN_ERR "%s invalid type\n", __func__); + return -EINVAL; + } + + /* Stop the viewfinder */ + if (cam->overlay_on == true) + stop_preview(cam); + + currentparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* First check that this device can support the changes requested. */ + err = vidioc_int_g_parm(cam->sensor, ¤tparm); + if (err) { + pr_err("%s: vidioc_int_g_parm returned an error %d\n", + __func__, err); + goto exit; + } + + pr_debug(" Current capabilities are %x\n", + currentparm.parm.capture.capability); + pr_debug(" Current capturemode is %d change to %d\n", + currentparm.parm.capture.capturemode, + parm->parm.capture.capturemode); + pr_debug(" Current framerate is %d change to %d\n", + currentparm.parm.capture.timeperframe.denominator, + parm->parm.capture.timeperframe.denominator); + + err = vidioc_int_s_parm(cam->sensor, parm); + if (err) { + pr_err("%s: vidioc_int_s_parm returned an error %d\n", + __func__, err); + goto exit; + } + + vidioc_int_g_ifparm(cam->sensor, &ifparm); + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + pr_debug(" g_fmt_cap returns widthxheight of input as %d x %d\n", + cam_fmt.fmt.pix.width, cam_fmt.fmt.pix.height); + +exit: + return err; +} + +/*! + * V4L interface - open function + * + * @param inode structure inode * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int csi_v4l_open(struct inode *inode, struct file *file) +{ + struct v4l2_ifparm ifparm; + struct v4l2_format cam_fmt; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int err = 0; + + pr_debug(" device name is %s\n", dev->name); + + if (!cam) { + pr_err("ERROR: v4l2 capture: Internal error, " + "cam_data not found!\n"); + return -EBADF; + } + + down(&cam->busy_lock); + err = 0; + if (signal_pending(current)) + goto oops; + + if (cam->open_count++ == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + + vidioc_int_g_ifparm(cam->sensor, &ifparm); + + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + csi_enable_mclk(CSI_MCLK_I2C, true, true); + vidioc_int_init(cam->sensor); + } + + file->private_data = dev; + +oops: + up(&cam->busy_lock); + return err; +} + +/*! + * V4L interface - close function + * + * @param inode struct inode * + * @param file struct file * + * + * @return 0 success + */ +static int csi_v4l_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + int err = 0; + cam_data *cam = video_get_drvdata(dev); + + pr_debug("In MVC:%s\n", __func__); + + if (!cam) { + pr_err("ERROR: v4l2 capture: Internal error, " + "cam_data not found!\n"); + return -EBADF; + } + + /* for the case somebody hit the ctrl C */ + if (cam->overlay_pid == current->pid) { + err = stop_preview(cam); + cam->overlay_on = false; + } + + if (--cam->open_count == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + file->private_data = NULL; + csi_enable_mclk(CSI_MCLK_I2C, false, false); + } + + return err; +} + +/* + * V4L interface - read function + * + * @param file struct file * + * @param read buf char * + * @param count size_t + * @param ppos structure loff_t * + * + * @return bytes read + */ +static ssize_t csi_v4l_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + int err = 0; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + /* Stop the viewfinder */ + if (cam->overlay_on == true) + stop_preview(cam); + + if (cam->still_buf_vaddr == NULL) { + cam->still_buf_vaddr = dma_alloc_coherent(0, + PAGE_ALIGN + (cam->v2f.fmt. + pix.sizeimage), + &cam-> + still_buf, + GFP_DMA | GFP_KERNEL); + if (cam->still_buf_vaddr == NULL) { + pr_err("alloc dma memory failed\n"); + return -ENOMEM; + } + cam->still_counter = 0; + __raw_writel(cam->still_buf, CSI_CSIDMASA_FB2); + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_DMA_REFLASH_RFF, + CSI_CSICR3); + __raw_writel(__raw_readl(CSI_CSISR), CSI_CSISR); + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_FRMCNT_RST, + CSI_CSICR3); + csi_enable_int(1); + } + + wait_event_interruptible(cam->still_queue, cam->still_counter); + csi_disable_int(); + err = copy_to_user(buf, cam->still_buf_vaddr, + cam->v2f.fmt.pix.sizeimage); + + if (cam->still_buf_vaddr != NULL) { + dma_free_coherent(0, PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + cam->still_buf_vaddr, cam->still_buf); + cam->still_buf = 0; + cam->still_buf_vaddr = NULL; + } + + if (cam->overlay_on == true) + start_preview(cam); + + up(&cam->busy_lock); + if (err < 0) + return err; + + return cam->v2f.fmt.pix.sizeimage - err; +} + +/*! + * V4L interface - ioctl function + * + * @param inode struct inode* + * + * @param file struct file* + * + * @param ioctlnr unsigned int + * + * @param arg void* + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int csi_v4l_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int retval = 0; + + pr_debug("In MVC: %s, %x\n", __func__, ioctlnr); + wait_event_interruptible(cam->power_queue, cam->low_power == false); + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + /*! + * V4l2 VIDIOC_G_FMT ioctl + */ + case VIDIOC_G_FMT:{ + struct v4l2_format *gf = arg; + pr_debug(" case VIDIOC_G_FMT\n"); + retval = csi_v4l2_g_fmt(cam, gf); + break; + } + + /*! + * V4l2 VIDIOC_S_FMT ioctl + */ + case VIDIOC_S_FMT:{ + struct v4l2_format *sf = arg; + pr_debug(" case VIDIOC_S_FMT\n"); + retval = csi_v4l2_s_fmt(cam, sf); + vidioc_int_s_fmt_cap(cam->sensor, sf); + break; + } + + /*! + * V4l2 VIDIOC_OVERLAY ioctl + */ + case VIDIOC_OVERLAY:{ + int *on = arg; + pr_debug(" case VIDIOC_OVERLAY\n"); + if (*on) { + cam->overlay_on = true; + cam->overlay_pid = current->pid; + start_preview(cam); + } + if (!*on) { + stop_preview(cam); + cam->overlay_on = false; + } + break; + } + + /*! + * V4l2 VIDIOC_G_FBUF ioctl + */ + case VIDIOC_G_FBUF:{ + struct v4l2_framebuffer *fb = arg; + *fb = cam->v4l2_fb; + fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + break; + } + + /*! + * V4l2 VIDIOC_S_FBUF ioctl + */ + case VIDIOC_S_FBUF:{ + struct v4l2_framebuffer *fb = arg; + cam->v4l2_fb = *fb; + break; + } + + case VIDIOC_G_PARM:{ + struct v4l2_streamparm *parm = arg; + pr_debug(" case VIDIOC_G_PARM\n"); + vidioc_int_g_parm(cam->sensor, parm); + break; + } + + case VIDIOC_S_PARM:{ + struct v4l2_streamparm *parm = arg; + pr_debug(" case VIDIOC_S_PARM\n"); + retval = csi_v4l2_s_param(cam, parm); + break; + } + + case VIDIOC_QUERYCAP:{ + struct v4l2_capability *cap = arg; + pr_debug(" case VIDIOC_QUERYCAP\n"); + strcpy(cap->driver, "csi_v4l2"); + cap->version = KERNEL_VERSION(0, 1, 11); + cap->capabilities = V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_VIDEO_OUTPUT_OVERLAY | V4L2_CAP_READWRITE; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + break; + } + + case VIDIOC_S_CROP: + pr_debug(" case not supported\n"); + break; + + case VIDIOC_S_CTRL: + case VIDIOC_G_STD: + case VIDIOC_QBUF: + case VIDIOC_QUERYBUF: + case VIDIOC_REQBUFS: + case VIDIOC_DQBUF: + case VIDIOC_G_OUTPUT: + case VIDIOC_S_OUTPUT: + case VIDIOC_ENUMSTD: + case VIDIOC_G_CROP: + case VIDIOC_CROPCAP: + case VIDIOC_S_STD: + case VIDIOC_G_CTRL: + case VIDIOC_STREAMOFF: + case VIDIOC_STREAMON: + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_ENUMINPUT: + case VIDIOC_G_INPUT: + case VIDIOC_S_INPUT: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + case VIDIOC_ENUMOUTPUT: + default: + pr_debug(" case not supported\n"); + retval = -EINVAL; + break; + } + + up(&cam->busy_lock); + return retval; +} + +/* + * V4L interface - ioctl function + * + * @return None + */ +static int csi_v4l_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, csi_v4l_do_ioctl); +} + +/*! + * V4L interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int csi_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = video_devdata(file); + unsigned long size; + int res = 0; + cam_data *cam = video_get_drvdata(dev); + + pr_debug("%s\n", __func__); + pr_debug("\npgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + pr_err("ERROR: v4l2 capture: %s : " + "remap_pfn_range failed\n", __func__); + res = -ENOBUFS; + goto csi_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + +csi_mmap_exit: + up(&cam->busy_lock); + return res; +} + +/*! + * This structure defines the functions to be called in this driver. + */ +static struct file_operations csi_v4l_fops = { + .owner = THIS_MODULE, + .open = csi_v4l_open, + .release = csi_v4l_close, + .read = csi_v4l_read, + .ioctl = csi_v4l_ioctl, + .mmap = csi_mmap, +}; + +static struct video_device csi_v4l_template = { + .name = "Mx25 Camera", + .vfl_type = VID_TYPE_CAPTURE, + .fops = &csi_v4l_fops, + .release = video_device_release, +}; + +/*! + * This function can be used to release any platform data on closing. + */ +static void camera_platform_release(struct device *device) +{ +} + +/*! Device Definition for csi v4l2 device */ +static struct platform_device csi_v4l2_devices = { + .name = "csi_v4l2", + .dev = { + .release = camera_platform_release, + }, + .id = 0, +}; + +/*! + * initialize cam_data structure + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static void init_camera_struct(cam_data *cam) +{ + pr_debug("In MVC: %s\n", __func__); + + /* Default everything to 0 */ + memset(cam, 0, sizeof(cam_data)); + + init_MUTEX(&cam->param_lock); + init_MUTEX(&cam->busy_lock); + + cam->video_dev = video_device_alloc(); + if (cam->video_dev == NULL) + return; + + *(cam->video_dev) = csi_v4l_template; + + video_set_drvdata(cam->video_dev, cam); + dev_set_drvdata(&csi_v4l2_devices.dev, (void *)cam); + cam->video_dev->minor = -1; + + init_waitqueue_head(&cam->still_queue); + + cam->streamparm.parm.capture.capturemode = 0; + + cam->standard.index = 0; + cam->standard.id = V4L2_STD_UNKNOWN; + cam->standard.frameperiod.denominator = 30; + cam->standard.frameperiod.numerator = 1; + cam->standard.framelines = 480; + cam->standard_autodetect = true; + cam->streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->streamparm.parm.capture.timeperframe = cam->standard.frameperiod; + cam->streamparm.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + cam->overlay_on = false; + cam->capture_on = false; + cam->skip_frame = 0; + cam->v4l2_fb.flags = V4L2_FBUF_FLAG_OVERLAY; + + cam->v2f.fmt.pix.sizeimage = 480 * 640 * 2; + cam->v2f.fmt.pix.bytesperline = 640 * 2; + cam->v2f.fmt.pix.width = 640; + cam->v2f.fmt.pix.height = 480; + cam->v2f.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; + cam->win.w.width = 160; + cam->win.w.height = 160; + cam->win.w.left = 0; + cam->win.w.top = 0; + cam->still_counter = 0; + + csi_start_callback(cam); + init_waitqueue_head(&cam->power_queue); + spin_lock_init(&cam->int_lock); +} + +/*! + * camera_power function + * Turns Sensor power On/Off + * + * @param cam cam data struct + * @param cameraOn true to turn camera on, false to turn off power. + * + * @return status + */ +static u8 camera_power(cam_data *cam, bool cameraOn) +{ + pr_debug("In MVC: %s on=%d\n", __func__, cameraOn); + + if (cameraOn == true) { + csi_enable_mclk(CSI_MCLK_I2C, true, true); + vidioc_int_s_power(cam->sensor, 1); + } else { + csi_enable_mclk(CSI_MCLK_I2C, false, false); + vidioc_int_s_power(cam->sensor, 0); + } + return 0; +} + +/*! + * This function is called to put the sensor in a low power state. + * Refer to the document driver-model/driver.txt in the kernel source tree + * for more information. + * + * @param pdev the device structure used to give information on which I2C + * to suspend + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure. + */ +static int csi_v4l2_suspend(struct platform_device *pdev, pm_message_t state) +{ + cam_data *cam = platform_get_drvdata(pdev); + + pr_debug("In MVC: %s\n", __func__); + + if (cam == NULL) + return -1; + + cam->low_power = true; + + if (cam->overlay_on == true) + stop_preview(cam); + + camera_power(cam, false); + + return 0; +} + +/*! + * This function is called to bring the sensor back from a low power state. + * Refer to the document driver-model/driver.txt in the kernel source tree + * for more information. + * + * @param pdev the device structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int csi_v4l2_resume(struct platform_device *pdev) +{ + cam_data *cam = platform_get_drvdata(pdev); + + pr_debug("In MVC: %s\n", __func__); + + if (cam == NULL) + return -1; + + cam->low_power = false; + wake_up_interruptible(&cam->power_queue); + camera_power(cam, true); + + if (cam->overlay_on == true) + start_preview(cam); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver csi_v4l2_driver = { + .driver = { + .name = "csi_v4l2", + }, + .probe = NULL, + .remove = NULL, +#ifdef CONFIG_PM + .suspend = csi_v4l2_suspend, + .resume = csi_v4l2_resume, +#endif + .shutdown = NULL, +}; + +/*! + * Initializes the camera driver. + */ +static int csi_v4l2_master_attach(struct v4l2_int_device *slave) +{ + cam_data *cam = slave->u.slave->master->priv; + struct v4l2_format cam_fmt; + + pr_debug("In MVC: %s\n", __func__); + pr_debug(" slave.name = %s\n", slave->name); + pr_debug(" master.name = %s\n", slave->u.slave->master->name); + + cam->sensor = slave; + if (slave == NULL) { + pr_err("ERROR: v4l2 capture: slave parameter not valid.\n"); + return -1; + } + + csi_enable_mclk(CSI_MCLK_I2C, true, true); + vidioc_int_dev_init(slave); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* Used to detect TV in (type 1) vs. camera (type 0) */ + cam->device_type = cam_fmt.fmt.pix.priv; + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + + return 0; +} + +/*! + * Disconnects the camera driver. + */ +static void csi_v4l2_master_detach(struct v4l2_int_device *slave) +{ + pr_debug("In MVC: %s\n", __func__); + + vidioc_int_dev_exit(slave); +} + +/*! + * Entry point for the V4L2 + * + * @return Error code indicating success or failure + */ +static __init int camera_init(void) +{ + u8 err = 0; + + /* Register the device driver structure. */ + err = platform_driver_register(&csi_v4l2_driver); + if (err != 0) { + pr_err("ERROR: v4l2 capture:camera_init: " + "platform_driver_register failed.\n"); + return err; + } + + /* Create g_cam and initialize it. */ + g_cam = kmalloc(sizeof(cam_data), GFP_KERNEL); + if (g_cam == NULL) { + pr_err("ERROR: v4l2 capture: failed to register camera\n"); + platform_driver_unregister(&csi_v4l2_driver); + return -1; + } + init_camera_struct(g_cam); + + /* Set up the v4l2 device and register it */ + csi_v4l2_int_device.priv = g_cam; + /* This function contains a bug that won't let this be rmmod'd. */ + v4l2_int_device_register(&csi_v4l2_int_device); + + /* Register the platform device */ + err = platform_device_register(&csi_v4l2_devices); + if (err != 0) { + pr_err("ERROR: v4l2 capture: camera_init: " + "platform_device_register failed.\n"); + platform_driver_unregister(&csi_v4l2_driver); + kfree(g_cam); + g_cam = NULL; + return err; + } + + /* register v4l video device */ + if (video_register_device(g_cam->video_dev, VFL_TYPE_GRABBER, video_nr) + == -1) { + platform_device_unregister(&csi_v4l2_devices); + platform_driver_unregister(&csi_v4l2_driver); + kfree(g_cam); + g_cam = NULL; + pr_err("ERROR: v4l2 capture: video_register_device failed\n"); + return -1; + } + pr_debug(" Video device registered: %s #%d\n", + g_cam->video_dev->name, g_cam->video_dev->minor); + + return err; +} + +/*! + * Exit and cleanup for the V4L2 + */ +static void __exit camera_exit(void) +{ + pr_debug("In MVC: %s\n", __func__); + + if (g_cam->open_count) { + pr_err("ERROR: v4l2 capture:camera open " + "-- setting ops to NULL\n"); + } else { + pr_info("V4L2 freeing image input device\n"); + v4l2_int_device_unregister(&csi_v4l2_int_device); + csi_stop_callback(g_cam); + video_unregister_device(g_cam->video_dev); + platform_driver_unregister(&csi_v4l2_driver); + platform_device_unregister(&csi_v4l2_devices); + + kfree(g_cam); + g_cam = NULL; + } +} + +module_init(camera_init); +module_exit(camera_exit); + +module_param(video_nr, int, 0444); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2 capture driver for Mx25 based cameras"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/capture/emma_mt9v111.c b/drivers/media/video/mxc/capture/emma_mt9v111.c new file mode 100644 index 000000000000..73e9bba36d1e --- /dev/null +++ b/drivers/media/video/mxc/capture/emma_mt9v111.c @@ -0,0 +1,679 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mt9v111.c + * + * @brief mt9v111 camera driver functions + * + * @ingroup Camera + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include "mxc_v4l2_capture.h" +#include "mt9v111.h" + +#ifdef MT9V111_DEBUG +static u16 testpattern; +#endif + +static sensor_interface *interface_param; +static mt9v111_conf mt9v111_device; +static int reset_frame_rate = 30; + +#define MT9V111_FRAME_RATE_NUM 20 + +static mt9v111_image_format format[2] = { + { + .index = 0, + .width = 640, + .height = 480, + }, + { + .index = 1, + .width = 352, + .height = 288, + }, +}; + +static int mt9v111_attach(struct i2c_adapter *adapter); +static int mt9v111_detach(struct i2c_client *client); + +static struct i2c_driver mt9v111_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "MT9V111 Client", + }, + .attach_adapter = mt9v111_attach, + .detach_client = mt9v111_detach, +}; + +static struct i2c_client mt9v111_i2c_client = { + .name = "mt9v111 I2C dev", + .addr = MT9V111_I2C_ADDRESS, + .driver = &mt9v111_i2c_driver, +}; + +/* + * Function definitions + */ + +#ifdef MT9V111_DEBUG +static inline int mt9v111_read_reg(u8 reg) +{ + int val = i2c_smbus_read_word_data(&mt9v111_i2c_client, reg); + if (val != -1) + val = cpu_to_be16(val); + return val; +} +#endif + +static inline int mt9v111_write_reg(u8 reg, u16 val) +{ + pr_debug("write reg %x val %x.\n", reg, val); + return i2c_smbus_write_word_data(&mt9v111_i2c_client, reg, + cpu_to_be16(val)); +} + +/*! + * Initialize mt9v111_sensor_lib + * Libarary for Sensor configuration through I2C + * + * @param coreReg Core Registers + * @param ifpReg IFP Register + * + * @return status + */ +static u8 mt9v111_sensor_lib(mt9v111_coreReg * coreReg, mt9v111_IFPReg * ifpReg) +{ + u8 reg; + u16 data; + u8 error = 0; + + /* + * setup to IFP registers + */ + reg = MT9V111I_ADDR_SPACE_SEL; + data = ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + /* Operation Mode Control */ + reg = MT9V111I_MODE_CONTROL; + data = ifpReg->modeControl; + mt9v111_write_reg(reg, data); + + /* Output format */ + reg = MT9V111I_FORMAT_CONTROL; + data = ifpReg->formatControl; /* Set bit 12 */ + mt9v111_write_reg(reg, data); + + /* AE limit 4 */ + reg = MT9V111I_SHUTTER_WIDTH_LIMIT_AE; + data = ifpReg->gainLimitAE; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_OUTPUT_FORMAT_CTRL2; + data = ifpReg->outputFormatCtrl2; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_AE_SPEED; + data = ifpReg->AESpeed; + mt9v111_write_reg(reg, data); + + /* output image size */ + reg = MT9V111i_H_PAN; + data = 0x8000 | ifpReg->HPan; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_ZOOM; + data = 0x8000 | ifpReg->HZoom; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_SIZE; + data = 0x8000 | ifpReg->HSize; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_PAN; + data = 0x8000 | ifpReg->VPan; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_ZOOM; + data = 0x8000 | ifpReg->VZoom; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_SIZE; + data = 0x8000 | ifpReg->VSize; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_PAN; + data = ~0x8000 & ifpReg->HPan; + mt9v111_write_reg(reg, data); +#if 0 + reg = MT9V111I_UPPER_SHUTTER_DELAY_LIM; + data = ifpReg->upperShutterDelayLi; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_SHUTTER_60; + data = ifpReg->shutter_width_60; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_SEARCH_FLICK_60; + data = ifpReg->search_flicker_60; + mt9v111_write_reg(reg, data); +#endif + + /* + * setup to sensor core registers + */ + reg = MT9V111I_ADDR_SPACE_SEL; + data = coreReg->addressSelect; + mt9v111_write_reg(reg, data); + + /* enable changes and put the Sync bit on */ + reg = MT9V111S_OUTPUT_CTRL; + data = MT9V111S_OUTCTRL_SYNC | MT9V111S_OUTCTRL_CHIP_ENABLE | 0x3000; + mt9v111_write_reg(reg, data); + + /* min PIXCLK - Default */ + reg = MT9V111S_PIXEL_CLOCK_SPEED; + data = coreReg->pixelClockSpeed; + mt9v111_write_reg(reg, data); + + /* Setup image flipping / Dark rows / row/column skip */ + reg = MT9V111S_READ_MODE; + data = coreReg->readMode; + mt9v111_write_reg(reg, data); + + /*zoom 0 */ + reg = MT9V111S_DIGITAL_ZOOM; + data = coreReg->digitalZoom; + mt9v111_write_reg(reg, data); + + /* min H-blank */ + reg = MT9V111S_HOR_BLANKING; + data = coreReg->horizontalBlanking; + mt9v111_write_reg(reg, data); + + /* min V-blank */ + reg = MT9V111S_VER_BLANKING; + data = coreReg->verticalBlanking; + mt9v111_write_reg(reg, data); + + reg = MT9V111S_SHUTTER_WIDTH; + data = coreReg->shutterWidth; + mt9v111_write_reg(reg, data); + + reg = MT9V111S_SHUTTER_DELAY; + data = ifpReg->upperShutterDelayLi; + mt9v111_write_reg(reg, data); + + /* changes become effective */ + reg = MT9V111S_OUTPUT_CTRL; + data = MT9V111S_OUTCTRL_CHIP_ENABLE | 0x3000; + mt9v111_write_reg(reg, data); + + return error; +} + +/*! + * mt9v111 sensor interface Initialization + * @param param sensor_interface * + * @param width u32 + * @param height u32 + * @return None + */ +static void mt9v111_interface(sensor_interface *param, u32 width, u32 height) +{ + param->Vsync_pol = 0x0; + param->clk_mode = 0x0; /*gated */ + param->pixclk_pol = 0x0; + param->data_width = 0x1; + param->data_pol = 0x0; + param->ext_vsync = 0x0; + param->Vsync_pol = 0x0; + param->Hsync_pol = 0x0; + param->width = width - 1; + param->height = height - 1; + param->active_width = width; + param->active_height = height; + param->pixel_fmt = IPU_PIX_FMT_UYVY; + param->mclk = 27000000; +} + +/*! + * MT9V111 frame rate calculate + * + * @param frame_rate int * + * @param mclk int + * @return None + */ +static void mt9v111_rate_cal(int *frame_rate, int mclk) +{ + int num_clock_per_row; + int max_rate = 0; + + mt9v111_device.coreReg->horizontalBlanking = MT9V111_HORZBLANK_MIN; + + num_clock_per_row = (format[0].width + 114 + MT9V111_HORZBLANK_MIN) * 2; + max_rate = mclk / (num_clock_per_row * + (format[0].height + MT9V111_VERTBLANK_DEFAULT)); + + if ((*frame_rate > max_rate) || (*frame_rate == 0)) { + *frame_rate = max_rate; + } + + mt9v111_device.coreReg->verticalBlanking + = mclk / (*frame_rate * num_clock_per_row) - format[0].height; + + reset_frame_rate = *frame_rate; +} + +/*! + * MT9V111 sensor configuration + * + * @param frame_rate int * + * @param high_quality int + * @return sensor_interface * + */ +sensor_interface *mt9v111_config(int *frame_rate, int high_quality) +{ + u32 out_width, out_height; + + if (interface_param == NULL) + return NULL; + + mt9v111_device.coreReg->addressSelect = MT9V111I_SEL_SCA; + mt9v111_device.ifpReg->addrSpaceSel = MT9V111I_SEL_IFP; + + mt9v111_device.coreReg->windowHeight = MT9V111_WINHEIGHT; + mt9v111_device.coreReg->windowWidth = MT9V111_WINWIDTH; + mt9v111_device.coreReg->zoomColStart = 0; + mt9v111_device.coreReg->zomRowStart = 0; + mt9v111_device.coreReg->digitalZoom = 0x0; + + mt9v111_device.coreReg->verticalBlanking = MT9V111_VERTBLANK_DEFAULT; + mt9v111_device.coreReg->horizontalBlanking = MT9V111_HORZBLANK_MIN; + mt9v111_device.coreReg->pixelClockSpeed = 0; + mt9v111_device.coreReg->readMode = 0xd0a1; + + mt9v111_device.ifpReg->outputFormatCtrl2 = 0; + mt9v111_device.ifpReg->gainLimitAE = 0x300; + mt9v111_device.ifpReg->AESpeed = 0x80; + + /* here is the default value */ + mt9v111_device.ifpReg->formatControl = 0xc800; + mt9v111_device.ifpReg->modeControl = 0x708e; + mt9v111_device.ifpReg->awbSpeed = 0x4514; + mt9v111_device.coreReg->shutterWidth = 0xf8; + + out_width = 640; + out_height = 480; + + /*output size */ + mt9v111_device.ifpReg->HPan = 0; + mt9v111_device.ifpReg->HZoom = 640; + mt9v111_device.ifpReg->HSize = out_width; + mt9v111_device.ifpReg->VPan = 0; + mt9v111_device.ifpReg->VZoom = 480; + mt9v111_device.ifpReg->VSize = out_height; + + mt9v111_interface(interface_param, out_width, out_height); + set_mclk_rate(&interface_param->mclk); + mt9v111_rate_cal(frame_rate, interface_param->mclk); + mt9v111_sensor_lib(mt9v111_device.coreReg, mt9v111_device.ifpReg); + + return interface_param; +} + +/*! + * mt9v111 sensor set color configuration + * + * @param bright int + * @param saturation int + * @param red int + * @param green int + * @param blue int + * @return None + */ +static void +mt9v111_set_color(int bright, int saturation, int red, int green, int blue) +{ + u8 reg; + u16 data; + + switch (saturation) { + case 100: + mt9v111_device.ifpReg->awbSpeed = 0x4514; + break; + case 150: + mt9v111_device.ifpReg->awbSpeed = 0x6D14; + break; + case 75: + mt9v111_device.ifpReg->awbSpeed = 0x4D14; + break; + case 50: + mt9v111_device.ifpReg->awbSpeed = 0x5514; + break; + case 37: + mt9v111_device.ifpReg->awbSpeed = 0x5D14; + break; + case 25: + mt9v111_device.ifpReg->awbSpeed = 0x6514; + break; + default: + mt9v111_device.ifpReg->awbSpeed = 0x4514; + break; + } + + reg = MT9V111I_ADDR_SPACE_SEL; + data = mt9v111_device.ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + /* Operation Mode Control */ + reg = MT9V111I_AWB_SPEED; + data = mt9v111_device.ifpReg->awbSpeed; + mt9v111_write_reg(reg, data); +} + +/*! + * mt9v111 sensor get color configuration + * + * @param bright int * + * @param saturation int * + * @param red int * + * @param green int * + * @param blue int * + * @return None + */ +static void +mt9v111_get_color(int *bright, int *saturation, int *red, int *green, int *blue) +{ + *saturation = (mt9v111_device.ifpReg->awbSpeed & 0x3800) >> 11; + switch (*saturation) { + case 0: + *saturation = 100; + break; + case 1: + *saturation = 75; + break; + case 2: + *saturation = 50; + break; + case 3: + *saturation = 37; + break; + case 4: + *saturation = 25; + break; + case 5: + *saturation = 150; + break; + case 6: + *saturation = 0; + break; + default: + *saturation = 0; + break; + } +} + +/*! + * mt9v111 sensor set AE measurement window mode configuration + * + * @param ae_mode int + * @return None + */ +static void mt9v111_set_ae_mode(int ae_mode) +{ + u8 reg; + u16 data; + + mt9v111_device.ifpReg->modeControl &= 0xfff3; + mt9v111_device.ifpReg->modeControl |= (ae_mode & 0x03) << 2; + + reg = MT9V111I_ADDR_SPACE_SEL; + data = mt9v111_device.ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_MODE_CONTROL; + data = mt9v111_device.ifpReg->modeControl; + mt9v111_write_reg(reg, data); +} + +/*! + * mt9v111 sensor get AE measurement window mode configuration + * + * @param ae_mode int * + * @return None + */ +static void mt9v111_get_ae_mode(int *ae_mode) +{ + if (ae_mode != NULL) { + *ae_mode = (mt9v111_device.ifpReg->modeControl & 0xc) >> 2; + } +} + +/*! + * mt9v111 Reset function + * + * @return None + */ +static sensor_interface *mt9v111_reset(void) +{ + return mt9v111_config(&reset_frame_rate, 0); +} + +struct camera_sensor camera_sensor_if = { + .set_color = mt9v111_set_color, + .get_color = mt9v111_get_color, + .set_ae_mode = mt9v111_set_ae_mode, + .get_ae_mode = mt9v111_get_ae_mode, + .config = mt9v111_config, + .reset = mt9v111_reset, +}; + +#ifdef MT9V111_DEBUG +/*! + * Set sensor to test mode, which will generate test pattern. + * + * @return none + */ +static void mt9v111_test_pattern(bool flag) +{ + u16 data; + + /* switch to sensor registers */ + mt9v111_write_reg(MT9V111I_ADDR_SPACE_SEL, MT9V111I_SEL_SCA); + + if (flag == true) { + testpattern = MT9V111S_OUTCTRL_TEST_MODE; + + data = mt9v111_read_reg(MT9V111S_ROW_NOISE_CTRL) & 0xBF; + mt9v111_write_reg(MT9V111S_ROW_NOISE_CTRL, data); + + mt9v111_write_reg(MT9V111S_TEST_DATA, 0); + + /* changes take effect */ + data = MT9V111S_OUTCTRL_CHIP_ENABLE | testpattern | 0x3000; + mt9v111_write_reg(MT9V111S_OUTPUT_CTRL, data); + } else { + testpattern = 0; + + data = mt9v111_read_reg(MT9V111S_ROW_NOISE_CTRL) | 0x40; + mt9v111_write_reg(MT9V111S_ROW_NOISE_CTRL, data); + + /* changes take effect */ + data = MT9V111S_OUTCTRL_CHIP_ENABLE | testpattern | 0x3000; + mt9v111_write_reg(MT9V111S_OUTPUT_CTRL, data); + } +} +#endif + +/*! + * mt9v111 I2C detect_client function + * + * @param adapter struct i2c_adapter * + * @param address int + * @param kind int + * + * @return Error code indicating success or failure + */ +static int mt9v111_detect_client(struct i2c_adapter *adapter, int address, + int kind) +{ + mt9v111_i2c_client.adapter = adapter; + if (i2c_attach_client(&mt9v111_i2c_client)) { + mt9v111_i2c_client.adapter = NULL; + printk(KERN_ERR "mt9v111_attach: i2c_attach_client failed\n"); + return -1; + } + + interface_param = (sensor_interface *) + kmalloc(sizeof(sensor_interface), GFP_KERNEL); + if (!interface_param) { + printk(KERN_ERR "mt9v111_attach: kmalloc failed \n"); + return -1; + } + + printk(KERN_INFO "MT9V111 Detected\n"); + + return 0; +} + +static unsigned short normal_i2c[] = { MT9V111_I2C_ADDRESS, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +/*! + * mt9v111 I2C attach function + * + * @param adapter struct i2c_adapter * + * @return Error code indicating success or failure + */ +static int mt9v111_attach(struct i2c_adapter *adap) +{ + uint32_t mclk = 27000000; + struct clk *clk; + int err; + + clk = clk_get(NULL, "csi_clk"); + clk_enable(clk); + set_mclk_rate(&mclk); + + err = i2c_probe(adap, &addr_data, &mt9v111_detect_client); + + clk_disable(clk); + clk_put(clk); + + return err; +} + +/*! + * mt9v111 I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int mt9v111_detach(struct i2c_client *client) +{ + int err; + + if (!mt9v111_i2c_client.adapter) + return -1; + + err = i2c_detach_client(&mt9v111_i2c_client); + mt9v111_i2c_client.adapter = NULL; + + if (interface_param) + kfree(interface_param); + interface_param = NULL; + + return err; +} + +extern void gpio_sensor_active(void); + +/*! + * MT9V111 init function + * + * @return Error code indicating success or failure + */ +static __init int mt9v111_init(void) +{ + u8 err; + + gpio_sensor_active(); + + mt9v111_device.coreReg = (mt9v111_coreReg *) + kmalloc(sizeof(mt9v111_coreReg), GFP_KERNEL); + if (!mt9v111_device.coreReg) + return -1; + + memset(mt9v111_device.coreReg, 0, sizeof(mt9v111_coreReg)); + + mt9v111_device.ifpReg = (mt9v111_IFPReg *) + kmalloc(sizeof(mt9v111_IFPReg), GFP_KERNEL); + if (!mt9v111_device.ifpReg) { + kfree(mt9v111_device.coreReg); + mt9v111_device.coreReg = NULL; + return -1; + } + + memset(mt9v111_device.ifpReg, 0, sizeof(mt9v111_IFPReg)); + + err = i2c_add_driver(&mt9v111_i2c_driver); + + return err; +} + +extern void gpio_sensor_inactive(void); +/*! + * MT9V111 cleanup function + * + * @return Error code indicating success or failure + */ +static void __exit mt9v111_clean(void) +{ + if (mt9v111_device.coreReg) { + kfree(mt9v111_device.coreReg); + mt9v111_device.coreReg = NULL; + } + + if (mt9v111_device.ifpReg) { + kfree(mt9v111_device.ifpReg); + mt9v111_device.ifpReg = NULL; + } + + i2c_del_driver(&mt9v111_i2c_driver); + + gpio_sensor_inactive(); +} + +module_init(mt9v111_init); +module_exit(mt9v111_clean); + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(camera_sensor_if); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Mt9v111 Camera Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/emma_ov2640.c b/drivers/media/video/mxc/capture/emma_ov2640.c new file mode 100644 index 000000000000..ceffea4d52a9 --- /dev/null +++ b/drivers/media/video/mxc/capture/emma_ov2640.c @@ -0,0 +1,444 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> + +#include "mxc_v4l2_capture.h" + +enum ov2640_mode { + ov2640_mode_1600_1120, + ov2640_mode_800_600 +}; + +struct reg_value { + u8 reg; + u8 value; + int delay_ms; +}; + +static struct reg_value ov2640_setting_1600_1120[] = { + {0xff, 0x1, 0}, {0x12, 0x80, 1}, {0xff, 0, 0}, {0x2c, 0xff, 0}, + {0x2e, 0xdf, 0}, {0xff, 0x1, 0}, {0x3c, 0x32, 0}, {0x11, 0x01, 0}, + {0x09, 0x00, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, {0x14, 0x48, 0}, + {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, {0x3b, 0xfb, 0}, + {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, {0x39, 0x82, 0}, + {0x35, 0x88, 0}, {0x22, 0x0a, 0}, {0x37, 0x40, 0}, {0x23, 0x00, 0}, + {0x34, 0xa0, 0}, {0x36, 0x1a, 0}, {0x06, 0x02, 0}, {0x07, 0xc0, 0}, + {0x0d, 0xb7, 0}, {0x0e, 0x01, 0}, {0x4c, 0x00, 0}, {0x4a, 0x81, 0}, + {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, {0x26, 0x82, 0}, + {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x3f, 0}, {0x0c, 0x3c, 0}, + {0x5d, 0x55, 0}, {0x5e, 0x7d, 0}, {0x5f, 0x7d, 0}, {0x60, 0x55, 0}, + {0x61, 0x70, 0}, {0x62, 0x80, 0}, {0x7c, 0x05, 0}, {0x20, 0x80, 0}, + {0x28, 0x30, 0}, {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, {0x6e, 00, 0}, + {0x70, 0x02, 0}, {0x71, 0x94, 0}, {0x73, 0xc1, 0}, {0x3d, 0x34, 0}, + {0x5a, 0x57, 0}, {0x4f, 0xbb, 0}, {0x50, 0x9c, 0}, {0xff, 0x00, 0}, + {0xe5, 0x7f, 0}, {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0x44, 0x06, 0}, + {0xe0, 0x14, 0}, {0x76, 0xff, 0}, {0x33, 0xa0, 0}, {0x42, 0x20, 0}, + {0x43, 0x18, 0}, {0x4c, 0x00, 0}, {0x87, 0xd0, 0}, {0xd7, 0x03, 0}, + {0xd9, 0x10, 0}, {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, {0xc9, 0x80, 0}, + {0x7c, 0x00, 0}, {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, {0x7d, 0x48, 0}, + {0x7d, 0x48, 0}, {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, {0x7d, 0x10, 0}, + {0x7d, 0x0e, 0}, {0x90, 0x00, 0}, {0x91, 0x0e, 0}, {0x91, 0x1a, 0}, + {0x91, 0x31, 0}, {0x91, 0x5a, 0}, {0x91, 0x69, 0}, {0x91, 0x75, 0}, + {0x91, 0x7e, 0}, {0x91, 0x88, 0}, {0x91, 0x8f, 0}, {0x91, 0x96, 0}, + {0x91, 0xa3, 0}, {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, {0x91, 0xd7, 0}, + {0x91, 0xe8, 0}, {0x91, 0x20, 0}, {0x92, 0x00, 0}, {0x93, 0x06, 0}, + {0x93, 0xe3, 0}, {0x93, 0x03, 0}, {0x93, 0x03, 0}, {0x93, 0x00, 0}, + {0x93, 0x02, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x96, 0x00, 0}, {0x97, 0x08, 0}, {0x97, 0x19, 0}, {0x97, 0x02, 0}, + {0x97, 0x0c, 0}, {0x97, 0x24, 0}, {0x97, 0x30, 0}, {0x97, 0x28, 0}, + {0x97, 0x26, 0}, {0x97, 0x02, 0}, {0x97, 0x98, 0}, {0x97, 0x80, 0}, + {0x97, 0x00, 0}, {0x97, 0x00, 0}, {0xa4, 0x00, 0}, {0xa8, 0x00, 0}, + {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, {0xc7, 0x10, 0}, + {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, {0xb9, 0x7c, 0}, + {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, {0xb0, 0xc5, 0}, + {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, {0xa6, 0x00, 0}, + {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x1b, 0}, {0xa7, 0x31, 0}, + {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, + {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, + {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, + {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xc0, 0xc8, 0}, {0xc1, 0x96, 0}, + {0x86, 0x3d, 0}, {0x50, 0x00, 0}, {0x51, 0x90, 0}, {0x52, 0x18, 0}, + {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x88, 0}, {0x57, 0x00, 0}, + {0x5a, 0x90, 0}, {0x5b, 0x18, 0}, {0x5c, 0x05, 0}, {0xc3, 0xef, 0}, + {0x7f, 0x00, 0}, {0xda, 0x01, 0}, {0xe5, 0x1f, 0}, {0xe1, 0x67, 0}, + {0xe0, 0x00, 0}, {0xdd, 0x7f, 0}, {0x05, 0x00, 0} +}; + +static struct reg_value ov2640_setting_800_600[] = { + {0xff, 0, 0}, {0xff, 1, 0}, {0x12, 0x80, 1}, {0xff, 00, 0}, + {0x2c, 0xff, 0}, {0x2e, 0xdf, 0}, {0xff, 0x1, 0}, {0x3c, 0x32, 0}, + {0x11, 0x01, 0}, {0x09, 0x00, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, + {0x14, 0x48, 0}, {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, + {0x3b, 0xfb, 0}, {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, + {0x39, 0x92, 0}, {0x35, 0xda, 0}, {0x22, 0x1a, 0}, {0x37, 0xc3, 0}, + {0x23, 0x00, 0}, {0x34, 0xc0, 0}, {0x36, 0x1a, 0}, {0x06, 0x88, 0}, + {0x07, 0xc0, 0}, {0x0d, 0x87, 0}, {0x0e, 0x41, 0}, {0x4c, 0x00, 0}, + {0x4a, 0x81, 0}, {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, + {0x26, 0x82, 0}, {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x22, 0}, + {0x0c, 0x3c, 0}, {0x5d, 0x55, 0}, {0x5e, 0x7d, 0}, {0x5f, 0x7d, 0}, + {0x60, 0x55, 0}, {0x61, 0x70, 0}, {0x62, 0x80, 0}, {0x7c, 0x05, 0}, + {0x20, 0x80, 0}, {0x28, 0x30, 0}, {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, + {0x6e, 00, 0}, {0x70, 0x02, 0}, {0x71, 0x94, 0}, {0x73, 0xc1, 0}, + {0x12, 0x40, 0}, {0x17, 0x11, 0}, {0x18, 0x43, 0}, {0x19, 0x00, 0}, + {0x1a, 0x4b, 0}, {0x32, 0x09, 0}, {0x37, 0xc0, 0}, {0x4f, 0xca, 0}, + {0x50, 0xa8, 0}, {0x6d, 0x00, 0}, {0x3d, 0x38, 0}, {0xff, 0x00, 0}, + {0xe5, 0x7f, 0}, {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0x44, 0x06, 0}, + {0xe0, 0x14, 0}, {0x76, 0xff, 0}, {0x33, 0xa0, 0}, {0x42, 0x20, 0}, + {0x43, 0x18, 0}, {0x4c, 0x00, 0}, {0x87, 0xd0, 0}, {0x88, 0x3f, 0}, + {0xd7, 0x03, 0}, {0xd9, 0x10, 0}, {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, + {0xc9, 0x80, 0}, {0x7c, 0x00, 0}, {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, + {0x7d, 0x48, 0}, {0x7d, 0x48, 0}, {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, + {0x7d, 0x10, 0}, {0x7d, 0x0e, 0}, {0x90, 0x00, 0}, {0x91, 0x0e, 0}, + {0x91, 0x1a, 0}, {0x91, 0x31, 0}, {0x91, 0x5a, 0}, {0x91, 0x69, 0}, + {0x91, 0x75, 0}, {0x91, 0x7e, 0}, {0x91, 0x88, 0}, {0x91, 0x8f, 0}, + {0x91, 0x96, 0}, {0x91, 0xa3, 0}, {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, + {0x91, 0xd7, 0}, {0x91, 0xe8, 0}, {0x91, 0x20, 0}, {0x92, 0x00, 0}, + {0x93, 0x06, 0}, {0x93, 0xe3, 0}, {0x93, 0x03, 0}, {0x93, 0x03, 0}, + {0x93, 0x00, 0}, {0x93, 0x02, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x96, 0x00, 0}, {0x97, 0x08, 0}, {0x97, 0x19, 0}, + {0x97, 0x02, 0}, {0x97, 0x0c, 0}, {0x97, 0x24, 0}, {0x97, 0x30, 0}, + {0x97, 0x28, 0}, {0x97, 0x26, 0}, {0x97, 0x02, 0}, {0x97, 0x98, 0}, + {0x97, 0x80, 0}, {0x97, 0x00, 0}, {0x97, 0x00, 0}, {0xa4, 0x00, 0}, + {0xa8, 0x00, 0}, {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, + {0xc7, 0x10, 0}, {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, + {0xb9, 0x7c, 0}, {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, + {0xb0, 0xc5, 0}, {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, + {0xa6, 0x00, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x1b, 0}, + {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, + {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, + {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, + {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xc0, 0x64, 0}, + {0xc1, 0x4b, 0}, {0x86, 0x1d, 0}, {0x50, 0x00, 0}, {0x51, 0xc8, 0}, + {0x52, 0x96, 0}, {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x00, 0}, + {0x57, 0x00, 0}, {0x5a, 0xc8, 0}, {0x5b, 0x96, 0}, {0x5c, 0x00, 0}, + {0xc3, 0xef, 0}, {0x7f, 0x00, 0}, {0xda, 0x01, 0}, {0xe5, 0x1f, 0}, + {0xe1, 0x67, 0}, {0xe0, 0x00, 0}, {0xdd, 0x7f, 0}, {0x05, 0x00, 0} +}; + +static struct regulator *io_regulator; +static struct regulator *core_regulator; +static struct regulator *analog_regulator; +static struct regulator *gpo_regulator; +u32 mclk = 24000000; + +struct i2c_client *ov2640_i2c_client; + +static sensor_interface *interface_param; +static int reset_frame_rate = 30; +static int ov2640_probe(struct i2c_client *adapter, + const struct i2c_device_id *id); +static int ov2640_remove(struct i2c_client *client); + +static const struct i2c_device_id ov2640_id[] = { + {"ov2640", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ov2640_id); + +static struct i2c_driver ov2640_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ov2640", + }, + .probe = ov2640_probe, + .remove = ov2640_remove, + .id_table = ov2640_id, +}; + +/*! + * ov2640 I2C attach function + * + * @param adapter struct i2c_adapter * + * @return Error code indicating success or failure + */ +static int ov2640_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mxc_camera_platform_data *plat_data = client->dev.platform_data; + + ov2640_i2c_client = client; + mclk = plat_data->mclk; + + io_regulator = regulator_get(&client->dev, plat_data->io_regulator); + core_regulator = regulator_get(&client->dev, plat_data->core_regulator); + analog_regulator = + regulator_get(&client->dev, plat_data->analog_regulator); + gpo_regulator = regulator_get(&client->dev, plat_data->gpo_regulator); + + interface_param = (sensor_interface *) + kmalloc(sizeof(sensor_interface), GFP_KERNEL); + if (!interface_param) { + dev_dbg(&ov2640_i2c_client->dev, + "ov2640_probe: kmalloc failed \n"); + return -1; + } + + return 0; +} + +/*! + * ov2640 I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int ov2640_remove(struct i2c_client *client) +{ + kfree(interface_param); + interface_param = NULL; + + if (!IS_ERR_VALUE((unsigned long)io_regulator)) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } + + if (!IS_ERR_VALUE((unsigned long)core_regulator)) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } + + if (!IS_ERR_VALUE((unsigned long)gpo_regulator)) { + regulator_disable(gpo_regulator); + regulator_put(gpo_regulator); + } + + if (!IS_ERR_VALUE((unsigned long)analog_regulator)) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } + + return 0; +} + +static int ov2640_write_reg(u8 reg, u8 val) +{ + if (i2c_smbus_write_byte_data(ov2640_i2c_client, reg, val) < 0) { + dev_dbg(&ov2640_i2c_client->dev, + "%s:write reg errorr:reg=%x,val=%x\n", __func__, reg, + val); + return -1; + } + return 0; +} + +static int ov2640_init_mode(enum ov2640_mode mode) +{ + struct reg_value *setting; + int i, num; + + switch (mode) { + case ov2640_mode_1600_1120: + setting = ov2640_setting_1600_1120; + num = ARRAY_SIZE(ov2640_setting_1600_1120); + break; + case ov2640_mode_800_600: + setting = ov2640_setting_800_600; + num = ARRAY_SIZE(ov2640_setting_800_600); + break; + default: + return 0; + } + + for (i = 0; i < num; i++) { + ov2640_write_reg(setting[i].reg, setting[i].value); + if (setting[i].delay_ms > 0) + msleep(setting[i].delay_ms); + } + + return 0; +} + +/*! + * ov2640 sensor interface Initialization + * @param param sensor_interface * + * @param width u32 + * @param height u32 + * @return None + */ +static void ov2640_interface(sensor_interface *param, u32 width, u32 height) +{ + param->Vsync_pol = 0x0; + param->clk_mode = 0x0; /*gated */ + param->pixclk_pol = 0x0; + param->data_width = 0x1; + param->data_pol = 0x0; + param->ext_vsync = 0x0; + param->Vsync_pol = 0x0; + param->Hsync_pol = 0x0; + param->width = width - 1; + param->height = height - 1; + param->active_width = width; + param->active_height = height; + param->pixel_fmt = IPU_PIX_FMT_UYVY; + param->mclk = mclk; +} + +static void ov2640_set_color(int bright, int saturation, int red, int green, + int blue) +{ + +} + +static void ov2640_get_color(int *bright, int *saturation, int *red, int *green, + int *blue) +{ + +} +static void ov2640_set_ae_mode(int ae_mode) +{ + +} +static void ov2640_get_ae_mode(int *ae_mode) +{ + +} + +extern void gpio_sensor_active(void); + +static sensor_interface *ov2640_config(int *frame_rate, int high_quality) +{ + + u32 out_width, out_height; + + /*set io votage */ + if (!IS_ERR_VALUE((unsigned long)io_regulator)) { + regulator_set_voltage(io_regulator, 2800000, 2800000); + if (regulator_enable(io_regulator) != 0) { + dev_dbg(&ov2640_i2c_client->dev, + "%s:io set voltage error\n", __func__); + return NULL; + } else { + dev_dbg(&ov2640_i2c_client->dev, + "%s:io set voltage ok\n", __func__); + } + } + + /*core votage */ + if (!IS_ERR_VALUE((unsigned long)core_regulator)) { + regulator_set_voltage(core_regulator, 1300000, 1300000); + if (regulator_enable(core_regulator) != 0) { + dev_dbg(&ov2640_i2c_client->dev, + "%s:core set voltage error\n", __func__); + return NULL; + } else { + dev_dbg(&ov2640_i2c_client->dev, + "%s:core set voltage ok\n", __func__); + } + } + + /*GPO 3 */ + if (!IS_ERR_VALUE((unsigned long)gpo_regulator)) { + if (regulator_enable(gpo_regulator) != 0) { + dev_dbg(&ov2640_i2c_client->dev, + "%s:gpo3 enable error\n", __func__); + return NULL; + } else { + dev_dbg(&ov2640_i2c_client->dev, "%s:gpo3 enable ok\n", + __func__); + } + } + + if (!IS_ERR_VALUE((unsigned long)analog_regulator)) { + regulator_set_voltage(analog_regulator, 2000000, 2000000); + if (regulator_enable(analog_regulator) != 0) { + dev_dbg(&ov2640_i2c_client->dev, + "%s:analog set voltage error\n", __func__); + return NULL; + } else { + dev_dbg(&ov2640_i2c_client->dev, + "%s:analog set voltage ok\n", __func__); + } + } + + gpio_sensor_active(); + + if (high_quality) { + out_width = 1600; + out_height = 1120; + } else { + out_width = 800; + out_height = 600; + } + ov2640_interface(interface_param, out_width, out_height); + set_mclk_rate(&interface_param->mclk); + + if (high_quality) + ov2640_init_mode(ov2640_mode_1600_1120); + else + ov2640_init_mode(ov2640_mode_800_600); + + msleep(300); + + return interface_param; +} + +static sensor_interface *ov2640_reset(void) +{ + return ov2640_config(&reset_frame_rate, 0); +} + +struct camera_sensor camera_sensor_if = { + .set_color = ov2640_set_color, + .get_color = ov2640_get_color, + .set_ae_mode = ov2640_set_ae_mode, + .get_ae_mode = ov2640_get_ae_mode, + .config = ov2640_config, + .reset = ov2640_reset, +}; + +EXPORT_SYMBOL(camera_sensor_if); + +/*! + * ov2640 init function + * + * @return Error code indicating success or failure + */ +static __init int ov2640_init(void) +{ + u8 err; + + err = i2c_add_driver(&ov2640_i2c_driver); + + return err; +} + +extern void gpio_sensor_inactive(void); +/*! + * OV2640 cleanup function + * + * @return Error code indicating success or failure + */ +static void __exit ov2640_clean(void) +{ + i2c_del_driver(&ov2640_i2c_driver); + + gpio_sensor_inactive(); +} + +module_init(ov2640_init); +module_exit(ov2640_clean); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("OV2640 Camera Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/emma_v4l2_capture.c b/drivers/media/video/mxc/capture/emma_v4l2_capture.c new file mode 100644 index 000000000000..9cb08b26f1cd --- /dev/null +++ b/drivers/media/video/mxc/capture/emma_v4l2_capture.c @@ -0,0 +1,2074 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_v4l2_capture.c + * + * @brief MX27 Video For Linux 2 driver + * + * @ingroup MXC_V4L2_CAPTURE + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/pagemap.h> +#include <linux/vmalloc.h> +#include <linux/types.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/semaphore.h> +#include <linux/version.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> + +#include "mxc_v4l2_capture.h" +#include "mx27_prp.h" +#include "mx27_csi.h" + +static int csi_mclk_flag_backup; +static int video_nr = -1; +static cam_data *g_cam; + +/*! + * Free frame buffers + * + * @param cam Structure cam_data * + * + * @return status 0 success. + */ +static int mxc_free_frame_buf(cam_data *cam) +{ + int i; + + for (i = 0; i < FRAME_NUM; i++) { + if (cam->frame[i].vaddress != 0) { + dma_free_coherent(0, + cam->frame[i].buffer.length, + cam->frame[i].vaddress, + cam->frame[i].paddress); + cam->frame[i].vaddress = 0; + } + } + + return 0; +} + +/*! + * Allocate frame buffers + * + * @param cam Structure cam_data * + * + * @param count int number of buffer need to allocated + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_frame_buf(cam_data *cam, int count) +{ + int i; + + for (i = 0; i < count; i++) { + cam->frame[i].vaddress = + dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f. fmt.pix.sizeimage), + &cam->frame[i].paddress, + GFP_DMA | GFP_KERNEL); + if (cam->frame[i].vaddress == 0) { + pr_debug("mxc_allocate_frame_buf failed.\n"); + mxc_free_frame_buf(cam); + return -ENOBUFS; + } + cam->frame[i].buffer.index = i; + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + cam->frame[i].buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->frame[i].buffer.length = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->frame[i].buffer.memory = V4L2_MEMORY_MMAP; + cam->frame[i].buffer.m.offset = cam->frame[i].paddress; + cam->frame[i].index = i; + } + + return 0; +} + +/*! + * Free frame buffers status + * + * @param cam Structure cam_data * + * + * @return none + */ +static void mxc_free_frames(cam_data *cam) +{ + int i; + + for (i = 0; i < FRAME_NUM; i++) { + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + } + + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); +} + +/*! + * Return the buffer status + * + * @param cam Structure cam_data * + * @param buf Structure v4l2_buffer * + * + * @return status 0 success, EINVAL failed. + */ +static int mxc_v4l2_buffer_status(cam_data *cam, struct v4l2_buffer *buf) +{ + /* check range */ + if (buf->index < 0 || buf->index >= FRAME_NUM) { + pr_debug("mxc_v4l2_buffer_status buffers not allocated\n"); + return -EINVAL; + } + + memcpy(buf, &(cam->frame[buf->index].buffer), sizeof(*buf)); + return 0; +} + +/*! + * start the encoder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int mxc_streamon(cam_data *cam) +{ + struct mxc_v4l_frame *frame; + int err = 0; + + if (!cam) + return -EIO; + + if (list_empty(&cam->ready_q)) { + printk(KERN_ERR "mxc_streamon buffer not been queued yet\n"); + return -EINVAL; + } + + cam->capture_pid = current->pid; + + if (cam->enc_enable) { + err = cam->enc_enable(cam); + if (err != 0) { + return err; + } + } + + cam->ping_pong_csi = 0; + if (cam->enc_update_eba) { + frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + err = cam->enc_update_eba(frame->paddress, &cam->ping_pong_csi); + + frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + err |= + cam->enc_update_eba(frame->paddress, &cam->ping_pong_csi); + } else { + return -EINVAL; + } + + return err; +} + +/*! + * Shut down the encoder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int mxc_streamoff(cam_data *cam) +{ + int err = 0; + + if (!cam) + return -EIO; + + if (cam->enc_disable) { + err = cam->enc_disable(cam); + } + mxc_free_frames(cam); + return err; +} + +/*! + * Valid whether the palette is supported + * + * @param palette pixel format + * + * @return 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + /* + * MX27 PrP channel 2 supports YUV444, but YUV444 is not + * defined by V4L2 :( + */ + return ((palette == V4L2_PIX_FMT_YUYV) || + (palette == V4L2_PIX_FMT_YUV420)); +} + +/*! + * Valid and adjust the overlay window size, position + * + * @param cam structure cam_data * + * @param win struct v4l2_window * + * + * @return 0 + */ +static int verify_preview(cam_data *cam, struct v4l2_window *win) +{ + if (cam->output >= num_registered_fb) { + pr_debug("verify_preview No matched.\n"); + return -1; + } + cam->overlay_fb = (struct fb_info *)registered_fb[cam->output]; + + /* TODO: suppose 16bpp, 4 bytes alignment */ + win->w.left &= ~0x1; + + if (win->w.width + win->w.left > cam->overlay_fb->var.xres) + win->w.width = cam->overlay_fb->var.xres - win->w.left; + if (win->w.height + win->w.top > cam->overlay_fb->var.yres) + win->w.height = cam->overlay_fb->var.yres - win->w.top; + + /* + * TODO: suppose 16bpp. Rounded down to a multiple of 2 pixels for + * width according to PrP limitations. + */ + if ((cam->rotation == V4L2_MXC_ROTATE_90_RIGHT) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_VFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_HFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_LEFT)) + win->w.height &= ~0x1; + else + win->w.width &= ~0x1; + + return 0; +} + +/*! + * start the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int start_preview(cam_data *cam) +{ + int err = 0; + + err = prp_vf_select(cam); + if (err != 0) + return err; + + cam->overlay_pid = current->pid; + err = cam->vf_start_sdc(cam); + + return err; +} + +/*! + * shut down the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int stop_preview(cam_data *cam) +{ + int err = 0; + + err = prp_vf_deselect(cam); + return err; +} + +/*! + * V4L2 - mxc_v4l2_g_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_g_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + f->fmt.pix.width = cam->v2f.fmt.pix.width; + f->fmt.pix.height = cam->v2f.fmt.pix.height; + f->fmt.pix.sizeimage = cam->v2f.fmt.pix.sizeimage; + f->fmt.pix.pixelformat = cam->v2f.fmt.pix.pixelformat; + f->fmt.pix.bytesperline = cam->v2f.fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG; + retval = 0; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + f->fmt.win = cam->win; + break; + default: + retval = -EINVAL; + } + return retval; +} + +/*! + * V4L2 - mxc_v4l2_s_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + int size = 0; + int bytesperline = 0; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (!valid_mode(f->fmt.pix.pixelformat)) { + pr_debug("mxc_v4l2_s_fmt: format not supported\n"); + retval = -EINVAL; + } + + if (cam->rotation != V4L2_MXC_ROTATE_NONE) + pr_debug("mxc_v4l2_s_fmt: capture rotation ignored\n"); + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUYV: + f->fmt.pix.width &= ~0x1; /* Multiple of 2 */ + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_YUV420: + f->fmt.pix.width &= ~0x7; /* Multiple of 8 */ + f->fmt.pix.height &= ~0x1; /* Multiple of 2 */ + size = f->fmt.pix.width * f->fmt.pix.height * 3 / 2; + bytesperline = f->fmt.pix.width * 3 / 2; + break; + default: + /* Suppose it's YUV444 or 32bpp */ + size = f->fmt.pix.width * f->fmt.pix.height * 4; + bytesperline = f->fmt.pix.width * 4; + pr_info("mxc_v4l2_s_fmt: default assume" + " to be YUV444 interleaved.\n"); + break; + } + + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + if (f->fmt.pix.sizeimage > size) { + pr_debug("mxc_v4l2_s_fmt: sizeimage bigger than" + " needed.\n"); + size = f->fmt.pix.sizeimage; + } + f->fmt.pix.sizeimage = size; + + cam->v2f.fmt.pix.sizeimage = size; + cam->v2f.fmt.pix.bytesperline = bytesperline; + cam->v2f.fmt.pix.width = f->fmt.pix.width; + cam->v2f.fmt.pix.height = f->fmt.pix.height; + cam->v2f.fmt.pix.pixelformat = f->fmt.pix.pixelformat; + retval = 0; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + retval = verify_preview(cam, &f->fmt.win); + cam->win = f->fmt.win; + break; + default: + retval = -EINVAL; + } + return retval; +} + +/*! + * get control param + * + * @param cam structure cam_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42l_control(cam_data *cam, struct v4l2_control *c) +{ + int status = 0; + + switch (c->id) { + case V4L2_CID_HFLIP: + c->value = cam->rotation; + break; + case V4L2_CID_VFLIP: + c->value = cam->rotation; + break; + case V4L2_CID_MXC_ROT: + c->value = cam->rotation; + break; + case V4L2_CID_BRIGHTNESS: + c->value = cam->bright; + break; + case V4L2_CID_HUE: + c->value = cam->hue; + break; + case V4L2_CID_CONTRAST: + c->value = cam->contrast; + break; + case V4L2_CID_SATURATION: + c->value = cam->saturation; + break; + case V4L2_CID_RED_BALANCE: + c->value = cam->red; + break; + case V4L2_CID_BLUE_BALANCE: + c->value = cam->blue; + break; + case V4L2_CID_BLACK_LEVEL: + c->value = cam->ae_mode; + break; + default: + status = -EINVAL; + } + return status; +} + +/*! + * V4L2 - set_control function + * V4L2_CID_MXC_ROT is the extention for rotation/mirroring. + * + * @param cam structure cam_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42l_control(cam_data *cam, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + if (c->value == 1) { + if ((cam->rotation != V4L2_MXC_ROTATE_VERT_FLIP) && + (cam->rotation != V4L2_MXC_ROTATE_180)) + cam->rotation = V4L2_MXC_ROTATE_HORIZ_FLIP; + else + cam->rotation = V4L2_MXC_ROTATE_180; + } else { + if (cam->rotation == V4L2_MXC_ROTATE_HORIZ_FLIP) + cam->rotation = V4L2_MXC_ROTATE_NONE; + else if (cam->rotation == V4L2_MXC_ROTATE_180) + cam->rotation = V4L2_MXC_ROTATE_VERT_FLIP; + } + break; + case V4L2_CID_VFLIP: + if (c->value == 1) { + if ((cam->rotation != V4L2_MXC_ROTATE_HORIZ_FLIP) && + (cam->rotation != V4L2_MXC_ROTATE_180)) + cam->rotation = V4L2_MXC_ROTATE_VERT_FLIP; + else + cam->rotation = V4L2_MXC_ROTATE_180; + } else { + if (cam->rotation == V4L2_MXC_ROTATE_VERT_FLIP) + cam->rotation = V4L2_MXC_ROTATE_NONE; + if (cam->rotation == V4L2_MXC_ROTATE_180) + cam->rotation = V4L2_MXC_ROTATE_HORIZ_FLIP; + } + break; + case V4L2_CID_MXC_ROT: + switch (c->value) { + case V4L2_MXC_ROTATE_NONE: + case V4L2_MXC_ROTATE_VERT_FLIP: + case V4L2_MXC_ROTATE_HORIZ_FLIP: + case V4L2_MXC_ROTATE_180: + case V4L2_MXC_ROTATE_90_RIGHT: + case V4L2_MXC_ROTATE_90_RIGHT_VFLIP: + case V4L2_MXC_ROTATE_90_RIGHT_HFLIP: + case V4L2_MXC_ROTATE_90_LEFT: + cam->rotation = c->value; + break; + default: + return -EINVAL; + } + break; + case V4L2_CID_HUE: + cam->hue = c->value; + break; + case V4L2_CID_CONTRAST: + cam->contrast = c->value; + break; + case V4L2_CID_BRIGHTNESS: + cam->bright = c->value; + case V4L2_CID_SATURATION: + cam->saturation = c->value; + case V4L2_CID_RED_BALANCE: + cam->red = c->value; + case V4L2_CID_BLUE_BALANCE: + cam->blue = c->value; + csi_enable_mclk(CSI_MCLK_I2C, true, true); + cam->cam_sensor->set_color(cam->bright, cam->saturation, + cam->red, cam->green, cam->blue); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + break; + case V4L2_CID_BLACK_LEVEL: + cam->ae_mode = c->value & 0x03; + csi_enable_mclk(CSI_MCLK_I2C, true, true); + if (cam->cam_sensor->set_ae_mode) + cam->cam_sensor->set_ae_mode(cam->ae_mode); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + break; + case V4L2_CID_MXC_FLASH: + break; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 - mxc_v4l2_s_param function + * + * @param cam structure cam_data * + * + * @param parm structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_param(cam_data *cam, struct v4l2_streamparm *parm) +{ + sensor_interface *param; + csi_signal_cfg_t csi_param; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_debug("mxc_v4l2_s_param invalid type\n"); + return -EINVAL; + } + + if (parm->parm.capture.timeperframe.denominator > + cam->standard.frameperiod.denominator) { + pr_debug("mxc_v4l2_s_param frame rate %d larger " + "than standard supported %d\n", + parm->parm.capture.timeperframe.denominator, + cam->standard.frameperiod.denominator); + return -EINVAL; + } + + cam->streamparm.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + + csi_enable_mclk(CSI_MCLK_I2C, true, true); + param = cam->cam_sensor->config + (&parm->parm.capture.timeperframe.denominator, + parm->parm.capture.capturemode); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + + cam->streamparm.parm.capture.timeperframe = + parm->parm.capture.timeperframe; + + if ((parm->parm.capture.capturemode != 0) && + (parm->parm.capture.capturemode != V4L2_MODE_HIGHQUALITY)) { + pr_debug("mxc_v4l2_s_param frame un-supported capture mode\n"); + return -EINVAL; + } + + if (parm->parm.capture.capturemode == + cam->streamparm.parm.capture.capturemode) { + return 0; + } + + /* resolution changed, so need to re-program the CSI */ + csi_param.sens_clksrc = 0; + csi_param.clk_mode = param->clk_mode; + csi_param.pixclk_pol = param->pixclk_pol; + csi_param.data_width = param->data_width; + csi_param.data_pol = param->data_pol; + csi_param.ext_vsync = param->ext_vsync; + csi_param.Vsync_pol = param->Vsync_pol; + csi_param.Hsync_pol = param->Hsync_pol; + csi_init_interface(param->width, param->height, param->pixel_fmt, + csi_param); + + if (parm->parm.capture.capturemode != V4L2_MODE_HIGHQUALITY) { + cam->streamparm.parm.capture.capturemode = 0; + } else { + cam->streamparm.parm.capture.capturemode = + V4L2_MODE_HIGHQUALITY; + cam->streamparm.parm.capture.extendedmode = + parm->parm.capture.extendedmode; + cam->streamparm.parm.capture.readbuffers = 1; + } + return 0; +} + +/*! + * Dequeue one V4L capture buffer + * + * @param cam structure cam_data * + * @param buf structure v4l2_buffer * + * + * @return status 0 success, EINVAL invalid frame number, + * ETIME timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l_dqueue(cam_data *cam, struct v4l2_buffer *buf) +{ + int retval = 0; + struct mxc_v4l_frame *frame; + + if (!wait_event_interruptible_timeout(cam->enc_queue, + cam->enc_counter != 0, 10 * HZ)) { + printk(KERN_ERR "mxc_v4l_dqueue timeout enc_counter %x\n", + cam->enc_counter); + return -ETIME; + } else if (signal_pending(current)) { + printk(KERN_ERR "mxc_v4l_dqueue() interrupt received\n"); + return -ERESTARTSYS; + } + + cam->enc_counter--; + + frame = list_entry(cam->done_q.next, struct mxc_v4l_frame, queue); + list_del(cam->done_q.next); + if (frame->buffer.flags & V4L2_BUF_FLAG_DONE) { + frame->buffer.flags &= ~V4L2_BUF_FLAG_DONE; + } else if (frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + printk(KERN_ERR "VIDIOC_DQBUF: Buffer not filled.\n"); + frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + retval = -EINVAL; + } else if ((frame->buffer.flags & 0x7) == V4L2_BUF_FLAG_MAPPED) { + printk(KERN_ERR "VIDIOC_DQBUF: Buffer not queued.\n"); + retval = -EINVAL; + } + + buf->bytesused = cam->v2f.fmt.pix.sizeimage; + buf->index = frame->index; + buf->flags = frame->buffer.flags; + + return retval; +} + +/*! + * V4L interface - open function + * + * @param inode structure inode * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l_open(struct inode *inode, struct file *file) +{ + sensor_interface *param; + csi_signal_cfg_t csi_param; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int err = 0; + + if (!cam) { + pr_info("Internal error, cam_data not found!\n"); + return -ENODEV; + } + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + if (signal_pending(current)) + goto oops; + + if (cam->open_count++ == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + + err = prp_enc_select(cam); + + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + + csi_enable_mclk(CSI_MCLK_I2C, true, true); + param = cam->cam_sensor->reset(); + if (param == NULL) { + cam->open_count--; + csi_enable_mclk(CSI_MCLK_I2C, false, false); + err = -ENODEV; + goto oops; + } + csi_param.sens_clksrc = 0; + csi_param.clk_mode = param->clk_mode; + csi_param.pixclk_pol = param->pixclk_pol; + csi_param.data_width = param->data_width; + csi_param.data_pol = param->data_pol; + csi_param.ext_vsync = param->ext_vsync; + csi_param.Vsync_pol = param->Vsync_pol; + csi_param.Hsync_pol = param->Hsync_pol; + csi_init_interface(param->width, param->height, + param->pixel_fmt, csi_param); + cam->cam_sensor->get_color(&cam->bright, &cam->saturation, + &cam->red, &cam->green, &cam->blue); + if (cam->cam_sensor->get_ae_mode) + cam->cam_sensor->get_ae_mode(&cam->ae_mode); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + prp_init(cam); + + } + + file->private_data = dev; + oops: + up(&cam->busy_lock); + return err; +} + +/*! + * V4L interface - close function + * + * @param inode struct inode * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + int err = 0; + cam_data *cam = video_get_drvdata(dev); + + /* for the case somebody hit the ctrl C */ + if (cam->overlay_pid == current->pid) { + err = stop_preview(cam); + cam->overlay_on = false; + } + if (cam->capture_pid == current->pid) { + err |= mxc_streamoff(cam); + cam->capture_on = false; + wake_up_interruptible(&cam->enc_queue); + } + + if (--cam->open_count == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + pr_debug("mxc_v4l_close: release resource\n"); + + err |= prp_enc_deselect(cam); + + mxc_free_frame_buf(cam); + file->private_data = NULL; + + /* capture off */ + wake_up_interruptible(&cam->enc_queue); + mxc_free_frames(cam); + cam->enc_counter++; + prp_exit(cam); + } + + return err; +} + +#ifdef CONFIG_VIDEO_MXC_CSI_DMA +#include <mach/dma.h> + +#define CSI_DMA_STATUS_IDLE 0 /* DMA is not started */ +#define CSI_DMA_STATUS_WORKING 1 /* DMA is transfering the data */ +#define CSI_DMA_STATUS_DONE 2 /* One frame completes successfully */ +#define CSI_DMA_STATUS_ERROR 3 /* Error occurs during the DMA */ + +/* + * Sometimes the start of the DMA is not synchronized with the CSI + * SOF (Start of Frame) interrupt which will lead to incorrect + * captured image. In this case the driver will re-try capturing + * another frame. The following macro defines the maximum re-try + * times. + */ +#define CSI_DMA_RETRY 8 + +/* + * Size of the physical contiguous memory area used to hold image data + * transfered by DMA. It can be less than the size of the image data. + */ +#define CSI_MEM_SIZE (1024 * 600) + +/* Number of bytes for one DMA transfer */ +#define CSI_DMA_LENGTH (1024 * 200) + +static int g_dma_channel; +static int g_dma_status = CSI_DMA_STATUS_DONE; +static volatile int g_dma_completed; /* number of completed DMA transfers */ +static volatile int g_dma_copied; /* number of copied DMA transfers */ +static struct tasklet_struct g_dma_tasklet; +static char *g_user_buf; /* represents the buf passed by read() */ +static int g_user_count; /* represents the count passed by read() */ + +/*! + * @brief setup the DMA to transfer data + * There may be more than one DMA to transfer the whole image. Those + * DMAs work like chain. This function is used to setup the DMA in + * case there is enough space to hold the data. + * @param data pointer to the cam structure + */ +static void mxc_csi_dma_chaining(void *data) +{ + cam_data *cam = (cam_data *) data; + int count, chained = 0; + int max_dma = CSI_MEM_SIZE / CSI_DMA_LENGTH; + mxc_dma_requestbuf_t dma_request; + + while (chained * CSI_DMA_LENGTH < g_user_count) { + /* + * Calculate how many bytes the DMA should transfer. It may + * be less than CSI_DMA_LENGTH if the DMA is the last one. + */ + if ((chained + 1) * CSI_DMA_LENGTH > g_user_count) + count = g_user_count - chained * CSI_DMA_LENGTH; + else + count = CSI_DMA_LENGTH; + pr_debug("%s() DMA chained count = %d\n", __FUNCTION__, count); + + /* Config DMA */ + memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t)); + dma_request.dst_addr = cam->still_buf + + (chained % max_dma) * CSI_DMA_LENGTH; + dma_request.src_addr = (dma_addr_t) CSI_CSIRXFIFO_PHYADDR; + dma_request.num_of_bytes = count; + mxc_dma_config(g_dma_channel, &dma_request, 1, + MXC_DMA_MODE_READ); + + chained++; + } +} + +/*! + * @brief Copy image data from physical contiguous memory to user space buffer + * Once the data are copied, there will be more spare space in the + * physical contiguous memory to receive data from DMA. + * @param data pointer to the cam structure + */ +static void mxc_csi_dma_task(unsigned long data) +{ + cam_data *cam = (cam_data *) data; + int count; + int max_dma = CSI_MEM_SIZE / CSI_DMA_LENGTH; + + while (g_dma_copied < g_dma_completed) { + /* + * Calculate how many bytes the DMA has transfered. It may + * be less than CSI_DMA_LENGTH if the DMA is the last one. + */ + if ((g_dma_copied + 1) * CSI_DMA_LENGTH > g_user_count) + count = g_user_count - g_dma_copied * CSI_DMA_LENGTH; + else + count = CSI_DMA_LENGTH; + if (copy_to_user(g_user_buf + g_dma_copied * CSI_DMA_LENGTH, + cam->still_buf_vaddr + (g_dma_copied % max_dma) + * CSI_DMA_LENGTH, count)) + pr_debug("Warning: some bytes not copied\n"); + + g_dma_copied++; + } + + /* If the whole image has been captured */ + if (g_dma_copied * CSI_DMA_LENGTH >= g_user_count) { + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + } + + pr_debug("%s() DMA completed = %d copied = %d\n", + __FUNCTION__, g_dma_completed, g_dma_copied); +} + +/*! + * @brief DMA interrupt callback function + * @param data pointer to the cam structure + * @param error DMA error flag + * @param count number of bytes transfered by the DMA + */ +static void mxc_csi_dma_callback(void *data, int error, unsigned int count) +{ + cam_data *cam = (cam_data *) data; + int max_dma = CSI_MEM_SIZE / CSI_DMA_LENGTH; + unsigned long lock_flags; + + spin_lock_irqsave(&cam->int_lock, lock_flags); + + g_dma_completed++; + + if (error != MXC_DMA_DONE) { + g_dma_status = CSI_DMA_STATUS_ERROR; + pr_debug("%s() DMA error\n", __FUNCTION__); + } + + /* If the whole image has been captured */ + if ((g_dma_status != CSI_DMA_STATUS_ERROR) + && (g_dma_completed * CSI_DMA_LENGTH >= g_user_count)) + g_dma_status = CSI_DMA_STATUS_DONE; + + if ((g_dma_status == CSI_DMA_STATUS_WORKING) && + (g_dma_completed >= g_dma_copied + max_dma)) { + g_dma_status = CSI_DMA_STATUS_ERROR; + pr_debug("%s() Previous buffer over written\n", __FUNCTION__); + } + + /* Schedule the tasklet */ + tasklet_schedule(&g_dma_tasklet); + + spin_unlock_irqrestore(&cam->int_lock, lock_flags); + + pr_debug("%s() count = %d bytes\n", __FUNCTION__, count); +} + +/*! + * @brief CSI interrupt callback function + * @param data pointer to the cam structure + * @param status CSI interrupt status + */ +static void mxc_csi_irq_callback(void *data, unsigned long status) +{ + cam_data *cam = (cam_data *) data; + unsigned long lock_flags; + + spin_lock_irqsave(&cam->int_lock, lock_flags); + + /* Wait for SOF (Start of Frame) interrupt to sync the image */ + if (status & BIT_SOF_INT) { + if (g_dma_status == CSI_DMA_STATUS_IDLE) { + /* Start DMA transfer to capture image */ + mxc_dma_enable(g_dma_channel); + g_dma_status = CSI_DMA_STATUS_WORKING; + pr_debug("%s() DMA started.\n", __FUNCTION__); + } else if (g_dma_status == CSI_DMA_STATUS_WORKING) { + /* + * Another SOF occurs during DMA transfer. In this + * case the image is not synchronized so need to + * report error and probably try again. + */ + g_dma_status = CSI_DMA_STATUS_ERROR; + pr_debug("%s() Image is not synchronized with DMA - " + "SOF before DMA completes\n", __FUNCTION__); + } + } + + spin_unlock_irqrestore(&cam->int_lock, lock_flags); + + pr_debug("%s() g_dma_status = %d\n", __FUNCTION__, g_dma_status); +} + +/*! + * V4L interface - read function + * + * @param file struct file * + * @param read buf char * + * @param count size_t + * @param ppos structure loff_t * + * + * @return bytes read + */ +static ssize_t +mxc_v4l_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + int err = 0; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int retry = CSI_DMA_RETRY; + + g_user_buf = buf; + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + /* Video capture and still image capture are exclusive */ + if (cam->capture_on == true) { + err = -EBUSY; + goto exit0; + } + + /* The CSI-DMA can not do CSC */ + if (cam->v2f.fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV) { + pr_info("mxc_v4l_read support YUYV pixel format only\n"); + err = -EINVAL; + goto exit0; + } + + /* The CSI-DMA can not do resize or crop */ + if ((cam->v2f.fmt.pix.width != cam->crop_bounds.width) + || (cam->v2f.fmt.pix.height != cam->crop_bounds.height)) { + pr_info("mxc_v4l_read resize is not supported\n"); + pr_info("supported image size width = %d height = %d\n", + cam->crop_bounds.width, cam->crop_bounds.height); + err = -EINVAL; + goto exit0; + } + if ((cam->crop_current.left != cam->crop_bounds.left) + || (cam->crop_current.width != cam->crop_bounds.width) + || (cam->crop_current.top != cam->crop_bounds.top) + || (cam->crop_current.height != cam->crop_bounds.height)) { + pr_info("mxc_v4l_read cropping is not supported\n"); + err = -EINVAL; + goto exit0; + } + + cam->still_buf_vaddr = dma_alloc_coherent(0, + PAGE_ALIGN(CSI_MEM_SIZE), + &cam->still_buf, + GFP_DMA | GFP_KERNEL); + + if (!cam->still_buf_vaddr) { + pr_info("mxc_v4l_read failed at allocate still_buf\n"); + err = -ENOBUFS; + goto exit0; + } + + /* Initialize DMA */ + g_dma_channel = mxc_dma_request(MXC_DMA_CSI_RX, "CSI RX DMA"); + if (g_dma_channel < 0) { + pr_debug("mxc_v4l_read failed to request DMA channel\n"); + err = -EIO; + goto exit1; + } + + err = mxc_dma_callback_set(g_dma_channel, + (mxc_dma_callback_t) mxc_csi_dma_callback, + (void *)cam); + if (err != 0) { + pr_debug("mxc_v4l_read failed to set DMA callback\n"); + err = -EIO; + goto exit2; + } + + g_user_buf = buf; + if (cam->v2f.fmt.pix.sizeimage < count) + g_user_count = cam->v2f.fmt.pix.sizeimage; + else + g_user_count = count & ~0x3; + + tasklet_init(&g_dma_tasklet, mxc_csi_dma_task, (unsigned long)cam); + g_dma_status = CSI_DMA_STATUS_DONE; + csi_set_callback(mxc_csi_irq_callback, cam); + csi_enable_prpif(0); + + /* clear current SOF first */ + csi_clear_status(BIT_SOF_INT); + csi_enable_mclk(CSI_MCLK_RAW, true, true); + + do { + g_dma_completed = g_dma_copied = 0; + mxc_csi_dma_chaining(cam); + cam->still_counter = 0; + g_dma_status = CSI_DMA_STATUS_IDLE; + + if (!wait_event_interruptible_timeout(cam->still_queue, + cam->still_counter != 0, + 10 * HZ)) { + pr_info("mxc_v4l_read timeout counter %x\n", + cam->still_counter); + err = -ETIME; + goto exit3; + } + + if (g_dma_status == CSI_DMA_STATUS_DONE) + break; + + if (retry-- == 0) + break; + + pr_debug("Now retry image capture\n"); + } while (1); + + if (g_dma_status != CSI_DMA_STATUS_DONE) + err = -EIO; + + exit3: + csi_enable_prpif(1); + g_dma_status = CSI_DMA_STATUS_DONE; + csi_set_callback(0, 0); + csi_enable_mclk(CSI_MCLK_RAW, false, false); + tasklet_kill(&g_dma_tasklet); + + exit2: + mxc_dma_free(g_dma_channel); + + exit1: + dma_free_coherent(0, PAGE_ALIGN(CSI_MEM_SIZE), + cam->still_buf_vaddr, cam->still_buf); + cam->still_buf = 0; + + exit0: + up(&cam->busy_lock); + if (err < 0) + return err; + else + return g_user_count; +} +#else +/*! + * V4L interface - read function + * + * @param file struct file * + * @param read buf char * + * @param count size_t + * @param ppos structure loff_t * + * + * @return bytes read + */ +static ssize_t +mxc_v4l_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + int err = 0; + u8 *v_address; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + /* Video capture and still image capture are exclusive */ + if (cam->capture_on == true) { + err = -EBUSY; + goto exit0; + } + + v_address = dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->still_buf, GFP_DMA | GFP_KERNEL); + + if (!v_address) { + pr_info("mxc_v4l_read failed at allocate still_buf\n"); + err = -ENOBUFS; + goto exit0; + } + + if (prp_still_select(cam)) { + err = -EIO; + goto exit1; + } + + cam->still_counter = 0; + if (cam->csi_start(cam)) { + err = -EIO; + goto exit2; + } + + if (!wait_event_interruptible_timeout(cam->still_queue, + cam->still_counter != 0, + 10 * HZ)) { + pr_info("mxc_v4l_read timeout counter %x\n", + cam->still_counter); + err = -ETIME; + goto exit2; + } + err = copy_to_user(buf, v_address, cam->v2f.fmt.pix.sizeimage); + + exit2: + prp_still_deselect(cam); + + exit1: + dma_free_coherent(0, cam->v2f.fmt.pix.sizeimage, v_address, + cam->still_buf); + cam->still_buf = 0; + + exit0: + up(&cam->busy_lock); + if (err < 0) + return err; + else + return (cam->v2f.fmt.pix.sizeimage - err); +} +#endif /* CONFIG_VIDEO_MXC_CSI_DMA */ + +/*! + * V4L interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int +mxc_v4l_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int retval = 0; + unsigned long lock_flags; + + if (!cam) + return -EBADF; + + wait_event_interruptible(cam->power_queue, cam->low_power == false); + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + /*! + * V4l2 VIDIOC_QUERYCAP ioctl + */ + case VIDIOC_QUERYCAP:{ + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2"); + cap->version = KERNEL_VERSION(0, 1, 11); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OVERLAY | V4L2_CAP_STREAMING + | V4L2_CAP_READWRITE; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + + /*! + * V4l2 VIDIOC_G_FMT ioctl + */ + case VIDIOC_G_FMT:{ + struct v4l2_format *gf = arg; + retval = mxc_v4l2_g_fmt(cam, gf); + break; + } + + /*! + * V4l2 VIDIOC_S_FMT ioctl + */ + case VIDIOC_S_FMT:{ + struct v4l2_format *sf = arg; + retval = mxc_v4l2_s_fmt(cam, sf); + break; + } + + /*! + * V4l2 VIDIOC_REQBUFS ioctl + */ + case VIDIOC_REQBUFS:{ + struct v4l2_requestbuffers *req = arg; + if (req->count > FRAME_NUM) { + pr_info("VIDIOC_REQBUFS: not enough buffer\n"); + req->count = FRAME_NUM; + } + + if ((req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (req->memory != V4L2_MEMORY_MMAP)) { + pr_debug("VIDIOC_REQBUFS: wrong buffer type\n"); + retval = -EINVAL; + break; + } + + mxc_streamoff(cam); + mxc_free_frame_buf(cam); + + retval = mxc_allocate_frame_buf(cam, req->count); + break; + } + + /*! + * V4l2 VIDIOC_QUERYBUF ioctl + */ + case VIDIOC_QUERYBUF:{ + struct v4l2_buffer *buf = arg; + int index = buf->index; + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_debug + ("VIDIOC_QUERYBUFS: wrong buffer type\n"); + retval = -EINVAL; + break; + } + + memset(buf, 0, sizeof(buf)); + buf->index = index; + + down(&cam->param_lock); + retval = mxc_v4l2_buffer_status(cam, buf); + up(&cam->param_lock); + break; + } + + /*! + * V4l2 VIDIOC_QBUF ioctl + */ + case VIDIOC_QBUF:{ + struct v4l2_buffer *buf = arg; + int index = buf->index; + + pr_debug("VIDIOC_QBUF: %d\n", buf->index); + + spin_lock_irqsave(&cam->int_lock, lock_flags); + if ((cam->frame[index].buffer.flags & 0x7) == + V4L2_BUF_FLAG_MAPPED) { + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + if (cam->skip_frame > 0) { + list_add_tail(&cam->frame[index].queue, + &cam->working_q); + retval = + cam->enc_update_eba(cam-> + frame[index]. + paddress, + &cam-> + ping_pong_csi); + cam->skip_frame = 0; + } else { + list_add_tail(&cam->frame[index].queue, + &cam->ready_q); + } + } else if (cam->frame[index].buffer.flags & + V4L2_BUF_FLAG_QUEUED) { + pr_debug + ("VIDIOC_QBUF: buffer already queued\n"); + } else if (cam->frame[index].buffer. + flags & V4L2_BUF_FLAG_DONE) { + pr_debug + ("VIDIOC_QBUF: overwrite done buffer.\n"); + cam->frame[index].buffer.flags &= + ~V4L2_BUF_FLAG_DONE; + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + } + buf->flags = cam->frame[index].buffer.flags; + spin_unlock_irqrestore(&cam->int_lock, lock_flags); + break; + } + + /*! + * V4l2 VIDIOC_DQBUF ioctl + */ + case VIDIOC_DQBUF:{ + struct v4l2_buffer *buf = arg; + + retval = mxc_v4l_dqueue(cam, buf); + + break; + } + + /*! + * V4l2 VIDIOC_STREAMON ioctl + */ + case VIDIOC_STREAMON:{ + cam->capture_on = true; + retval = mxc_streamon(cam); + break; + } + + /*! + * V4l2 VIDIOC_STREAMOFF ioctl + */ + case VIDIOC_STREAMOFF:{ + retval = mxc_streamoff(cam); + cam->capture_on = false; + break; + } + + /*! + * V4l2 VIDIOC_G_CTRL ioctl + */ + case VIDIOC_G_CTRL:{ + retval = mxc_get_v42l_control(cam, arg); + break; + } + + /*! + * V4l2 VIDIOC_S_CTRL ioctl + */ + case VIDIOC_S_CTRL:{ + retval = mxc_set_v42l_control(cam, arg); + break; + } + + /*! + * V4l2 VIDIOC_CROPCAP ioctl + */ + case VIDIOC_CROPCAP:{ + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + cap->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + cap->bounds = cam->crop_bounds; + cap->defrect = cam->crop_defrect; + break; + } + + /*! + * V4l2 VIDIOC_G_CROP ioctl + */ + case VIDIOC_G_CROP:{ + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + crop->c = cam->crop_current; + break; + } + + /*! + * V4l2 VIDIOC_S_CROP ioctl + */ + case VIDIOC_S_CROP:{ + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = &cam->crop_bounds; + int i; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + + crop->c.top = (crop->c.top < b->top) ? b->top + : crop->c.top; + if (crop->c.top > b->top + b->height) + crop->c.top = b->top + b->height - 1; + if (crop->c.height > b->top + b->height - crop->c.top) + crop->c.height = + b->top + b->height - crop->c.top; + + crop->c.left = (crop->c.left < b->left) ? b->left + : crop->c.left; + if (crop->c.left > b->left + b->width) + crop->c.left = b->left + b->width - 1; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + crop->c.width &= ~0x1; + + /* + * MX27 PrP limitation: + * The right spare space (CSI_FRAME_X_SIZE + * - SOURCE_LINE_STRIDE - PICTURE_X_SIZE)) must be + * multiple of 32. + * So we tune the crop->c.left value to the closest + * desired cropping value and meet the PrP requirement. + */ + i = ((b->left + b->width) + - (crop->c.left + crop->c.width)) % 32; + if (i <= 16) { + if (crop->c.left + crop->c.width + i + <= b->left + b->width) + crop->c.left += i; + else if (crop->c.left - (32 - i) >= b->left) + crop->c.left -= 32 - i; + else { + retval = -EINVAL; + break; + } + } else { + if (crop->c.left - (32 - i) >= b->left) + crop->c.left -= 32 - i; + else if (crop->c.left + crop->c.width + i + <= b->left + b->width) + crop->c.left += i; + else { + retval = -EINVAL; + break; + } + } + + cam->crop_current = crop->c; + + break; + } + + /*! + * V4l2 VIDIOC_OVERLAY ioctl + */ + case VIDIOC_OVERLAY:{ + int *on = arg; + if (*on) { + cam->overlay_on = true; + retval = start_preview(cam); + } + if (!*on) { + retval = stop_preview(cam); + cam->overlay_on = false; + } + break; + } + + /*! + * V4l2 VIDIOC_G_FBUF ioctl + */ + case VIDIOC_G_FBUF:{ + struct v4l2_framebuffer *fb = arg; + struct fb_var_screeninfo *var; + + if (cam->output >= num_registered_fb) { + retval = -EINVAL; + break; + } + + var = ®istered_fb[cam->output]->var; + cam->v4l2_fb.fmt.width = var->xres; + cam->v4l2_fb.fmt.height = var->yres; + cam->v4l2_fb.fmt.bytesperline = + var->xres_virtual * var->bits_per_pixel; + cam->v4l2_fb.fmt.colorspace = V4L2_COLORSPACE_SRGB; + *fb = cam->v4l2_fb; + break; + } + + /*! + * V4l2 VIDIOC_S_FBUF ioctl + */ + case VIDIOC_S_FBUF:{ + struct v4l2_framebuffer *fb = arg; + cam->v4l2_fb.flags = fb->flags; + cam->v4l2_fb.fmt.pixelformat = fb->fmt.pixelformat; + break; + } + + case VIDIOC_G_PARM:{ + struct v4l2_streamparm *parm = arg; + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_debug("VIDIOC_G_PARM invalid type\n"); + retval = -EINVAL; + break; + } + parm->parm.capture = cam->streamparm.parm.capture; + break; + } + case VIDIOC_S_PARM:{ + struct v4l2_streamparm *parm = arg; + retval = mxc_v4l2_s_param(cam, parm); + break; + } + + /* linux v4l2 bug, kernel c0485619 user c0405619 */ + case VIDIOC_ENUMSTD:{ + struct v4l2_standard *e = arg; + *e = cam->standard; + pr_debug("VIDIOC_ENUMSTD call\n"); + retval = 0; + break; + } + + case VIDIOC_G_STD:{ + v4l2_std_id *e = arg; + *e = cam->standard.id; + break; + } + + case VIDIOC_S_STD:{ + break; + } + + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if (output->index >= num_registered_fb) { + retval = -EINVAL; + break; + } + + strncpy(output->name, + registered_fb[output->index]->fix.id, 31); + output->type = V4L2_OUTPUT_TYPE_ANALOG; + output->audioset = 0; + output->modulator = 0; + output->std = V4L2_STD_UNKNOWN; + + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = cam->output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + + if (*p_output_num >= num_registered_fb) { + retval = -EINVAL; + break; + } + + cam->output = *p_output_num; + break; + } + + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_ENUMINPUT: + case VIDIOC_G_INPUT: + case VIDIOC_S_INPUT: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&cam->busy_lock); + return retval; +} + +/* + * V4L interface - ioctl function + * + * @return None + */ +static int +mxc_v4l_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, mxc_v4l_do_ioctl); +} + +/*! + * V4L interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int mxc_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = video_devdata(file); + unsigned long size; + int res = 0; + cam_data *cam = video_get_drvdata(dev); + + pr_debug("pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + pr_debug("mxc_mmap: remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mxc_mmap_exit: + up(&cam->busy_lock); + return res; +} + +/*! + * V4L interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_poll(struct file *file, poll_table * wait) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + queue = &cam->enc_queue; + poll_wait(file, queue, wait); + + up(&cam->busy_lock); + return res; +} + +static struct +file_operations mxc_v4l_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l_open, + .release = mxc_v4l_close, + .read = mxc_v4l_read, + .ioctl = mxc_v4l_ioctl, + .mmap = mxc_mmap, + .poll = mxc_poll, +}; + +static struct video_device mxc_v4l_template = { + .name = "Mxc Camera", + .vfl_type = VID_TYPE_CAPTURE, + .fops = &mxc_v4l_fops, + .release = video_device_release, +}; + +static void camera_platform_release(struct device *device) +{ +} + +/*! Device Definition for Mt9v111 devices */ +static struct platform_device mxc_v4l2_devices = { + .name = "mxc_v4l2", + .dev = { + .release = camera_platform_release, + }, + .id = 0, +}; + +extern struct camera_sensor camera_sensor_if; + +/*! +* Camera V4l2 callback function. +* +* @return status +*/ +static void camera_callback(u32 mask, void *dev) +{ + struct mxc_v4l_frame *done_frame; + struct mxc_v4l_frame *ready_frame; + + cam_data *cam = (cam_data *) dev; + if (cam == NULL) + return; + + if (list_empty(&cam->working_q)) { + printk(KERN_ERR "camera_callback: working queue empty\n"); + return; + } + + done_frame = + list_entry(cam->working_q.next, struct mxc_v4l_frame, queue); + if (done_frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + done_frame->buffer.flags |= V4L2_BUF_FLAG_DONE; + done_frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + + if (list_empty(&cam->ready_q)) { + cam->skip_frame++; + } else { + ready_frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, + queue); + list_del(cam->ready_q.next); + list_add_tail(&ready_frame->queue, &cam->working_q); + cam->enc_update_eba(ready_frame->paddress, + &cam->ping_pong_csi); + } + + /* Added to the done queue */ + list_del(cam->working_q.next); + list_add_tail(&done_frame->queue, &cam->done_q); + + /* Wake up the queue */ + cam->enc_counter++; + wake_up_interruptible(&cam->enc_queue); + } else { + printk(KERN_ERR "camera_callback :buffer not queued\n"); + } +} + +/*! + * initialize cam_data structure + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static void init_camera_struct(cam_data *cam) +{ + int i; + + /* Default everything to 0 */ + memset(cam, 0, sizeof(cam_data)); + + init_MUTEX(&cam->param_lock); + init_MUTEX(&cam->busy_lock); + + cam->video_dev = video_device_alloc(); + if (cam->video_dev == NULL) + return; + + *(cam->video_dev) = mxc_v4l_template; + + video_set_drvdata(cam->video_dev, cam); + dev_set_drvdata(&mxc_v4l2_devices.dev, (void *)cam); + cam->video_dev->minor = -1; + + for (i = 0; i < FRAME_NUM; i++) { + cam->frame[i].width = 0; + cam->frame[i].height = 0; + cam->frame[i].paddress = 0; + } + + init_waitqueue_head(&cam->enc_queue); + init_waitqueue_head(&cam->still_queue); + + /* setup cropping */ + cam->crop_bounds.left = 0; + cam->crop_bounds.width = 640; + cam->crop_bounds.top = 0; + cam->crop_bounds.height = 480; + cam->crop_current = cam->crop_defrect = cam->crop_bounds; + cam->streamparm.parm.capture.capturemode = 0; + + cam->standard.index = 0; + cam->standard.id = V4L2_STD_UNKNOWN; + cam->standard.frameperiod.denominator = 30; + cam->standard.frameperiod.numerator = 1; + cam->standard.framelines = 480; + cam->streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->streamparm.parm.capture.timeperframe = cam->standard.frameperiod; + cam->streamparm.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + cam->overlay_on = false; + cam->capture_on = false; + cam->skip_frame = 0; + cam->v4l2_fb.capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + cam->v4l2_fb.flags = V4L2_FBUF_FLAG_PRIMARY; + + cam->v2f.fmt.pix.sizeimage = 352 * 288 * 3 / 2; + cam->v2f.fmt.pix.bytesperline = 288 * 3 / 2; + cam->v2f.fmt.pix.width = 288; + cam->v2f.fmt.pix.height = 352; + cam->v2f.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + cam->win.w.width = 160; + cam->win.w.height = 160; + cam->win.w.left = 0; + cam->win.w.top = 0; + + cam->cam_sensor = &camera_sensor_if; + cam->enc_callback = camera_callback; + + init_waitqueue_head(&cam->power_queue); + cam->int_lock = __SPIN_LOCK_UNLOCKED(cam->int_lock); + spin_lock_init(&cam->int_lock); +} + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +/*! + * camera_power function + * Turn Sensor power On/Off + * + * @param cameraOn true to turn camera on, otherwise shut down + * + * @return status + */ +static u8 camera_power(bool cameraOn) +{ + if (cameraOn == true) { + gpio_sensor_active(); + csi_enable_mclk(csi_mclk_flag_backup, true, true); + } else { + csi_mclk_flag_backup = csi_read_mclk_flag(); + csi_enable_mclk(csi_mclk_flag_backup, false, false); + gpio_sensor_inactive(); + } + return 0; +} + +/*! + * This function is called to put the sensor in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which I2C + * to suspend + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure. + */ +static int mxc_v4l2_suspend(struct platform_device *pdev, pm_message_t state) +{ + cam_data *cam = platform_get_drvdata(pdev); + + if (cam == NULL) { + return -1; + } + + cam->low_power = true; + + if (cam->overlay_on == true) + stop_preview(cam); + if ((cam->capture_on == true) && cam->enc_disable) { + cam->enc_disable(cam); + } + camera_power(false); + + return 0; +} + +/*! + * This function is called to bring the sensor back from a low power state.Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxc_v4l2_resume(struct platform_device *pdev) +{ + cam_data *cam = platform_get_drvdata(pdev); + + if (cam == NULL) { + return -1; + } + + cam->low_power = false; + wake_up_interruptible(&cam->power_queue); + + if (cam->overlay_on == true) + start_preview(cam); + if (cam->capture_on == true) + mxc_streamon(cam); + camera_power(true); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2_driver = { + .driver = { + .name = "mxc_v4l2", + .owner = THIS_MODULE, + .bus = &platform_bus_type, + }, + .probe = NULL, + .remove = NULL, + .suspend = mxc_v4l2_suspend, + .resume = mxc_v4l2_resume, + .shutdown = NULL, +}; + +/*! + * Entry point for the V4L2 + * + * @return Error code indicating success or failure + */ +static __init int camera_init(void) +{ + u8 err = 0; + cam_data *cam; + + g_cam = kmalloc(sizeof(cam_data), GFP_KERNEL); + if (g_cam == NULL) { + pr_debug("failed to mxc_v4l_register_camera\n"); + return -1; + } + + cam = g_cam; + init_camera_struct(cam); + + /* Register the I2C device */ + err = platform_device_register(&mxc_v4l2_devices); + if (err != 0) { + pr_debug("camera_init: platform_device_register failed.\n"); + video_device_release(cam->video_dev); + kfree(cam); + g_cam = NULL; + } + + /* Register the device driver structure. */ + err = platform_driver_register(&mxc_v4l2_driver); + if (err != 0) { + platform_device_unregister(&mxc_v4l2_devices); + pr_debug("camera_init: driver_register failed.\n"); + video_device_release(cam->video_dev); + kfree(cam); + g_cam = NULL; + return err; + } + + /* register v4l device */ + if (video_register_device(cam->video_dev, VFL_TYPE_GRABBER, video_nr) + == -1) { + platform_driver_unregister(&mxc_v4l2_driver); + platform_device_unregister(&mxc_v4l2_devices); + video_device_release(cam->video_dev); + kfree(cam); + g_cam = NULL; + pr_debug("video_register_device failed\n"); + return -1; + } + + return err; +} + +/*! + * Exit and cleanup for the V4L2 + * + */ +static void __exit camera_exit(void) +{ + pr_debug("unregistering video\n"); + + video_unregister_device(g_cam->video_dev); + + platform_driver_unregister(&mxc_v4l2_driver); + platform_device_unregister(&mxc_v4l2_devices); + + if (g_cam->open_count) { + pr_debug("camera open -- setting ops to NULL\n"); + } else { + pr_debug("freeing camera\n"); + mxc_free_frame_buf(g_cam); + kfree(g_cam); + g_cam = NULL; + } +} + +module_init(camera_init); +module_exit(camera_exit); + +module_param(video_nr, int, 0444); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2 capture driver for Mxc based cameras"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/capture/fsl_csi.c b/drivers/media/video/mxc/capture/fsl_csi.c new file mode 100644 index 000000000000..4f14f26eede1 --- /dev/null +++ b/drivers/media/video/mxc/capture/fsl_csi.c @@ -0,0 +1,276 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_csi.c, this file is derived from mx27_csi.c + * + * @brief mx25 CMOS Sensor interface functions + * + * @ingroup CSI + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <mach/clock.h> +#include <mach/hardware.h> + +#include "mxc_v4l2_capture.h" +#include "fsl_csi.h" + +static bool g_csi_mclk_on; +static csi_irq_callback_t g_callback; +static void *g_callback_data; +static struct clk csi_mclk; + +static irqreturn_t csi_irq_handler(int irq, void *data) +{ + cam_data *cam = (cam_data *) data; + unsigned long status = __raw_readl(CSI_CSISR); + unsigned long cr3 = __raw_readl(CSI_CSICR3); + unsigned int frame_count = (cr3 >> 16) & 0xFFFF; + + __raw_writel(status, CSI_CSISR); + + if (status & BIT_SOF_INT) { + /* reflash the embeded DMA controller */ + if (frame_count % 2 == 1) + __raw_writel(cr3 | BIT_DMA_REFLASH_RFF, CSI_CSICR3); + } + + if (status & BIT_DMA_TSF_DONE_FB2) { + if (frame_count == 2) { + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + } + } + + if (g_callback) + g_callback(g_callback_data, status); + + pr_debug("CSI status = 0x%08lX\n", status); + + return IRQ_HANDLED; +} + +static void csihw_reset_frame_count(void) +{ + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_FRMCNT_RST, CSI_CSICR3); +} + +static void csihw_reset(void) +{ + csihw_reset_frame_count(); + __raw_writel(CSICR1_RESET_VAL, CSI_CSICR1); + __raw_writel(CSICR2_RESET_VAL, CSI_CSICR2); + __raw_writel(CSICR3_RESET_VAL, CSI_CSICR3); +} + +/*! + * csi_init_interface + * Init csi interface + */ +void csi_init_interface(void) +{ + unsigned int val = 0; + unsigned int imag_para; + + val |= BIT_SOF_POL; + val |= BIT_REDGE; + val |= BIT_GCLK_MODE; + val |= BIT_HSYNC_POL; + val |= BIT_PACK_DIR; + val |= BIT_FCC; + val |= BIT_SWAP16_EN; + val |= 1 << SHIFT_MCLKDIV; + __raw_writel(val, CSI_CSICR1); + + imag_para = (640 << 16) | 960; + __raw_writel(imag_para, CSI_CSIIMAG_PARA); + + val = 0x1010; + val |= BIT_DMA_REFLASH_RFF; + __raw_writel(val, CSI_CSICR3); +} +EXPORT_SYMBOL(csi_init_interface); + +/*! + * csi_enable_mclk + * + * @param src enum define which source to control the clk + * CSI_MCLK_VF CSI_MCLK_ENC CSI_MCLK_RAW CSI_MCLK_I2C + * @param flag true to enable mclk, false to disable mclk + * @param wait true to wait 100ms make clock stable, false not wait + * + * @return 0 for success + */ +int32_t csi_enable_mclk(int src, bool flag, bool wait) +{ + if (flag == true) { + csi_mclk_enable(); + if (wait == true) + msleep(10); + pr_debug("Enable csi clock from source %d\n", src); + g_csi_mclk_on = true; + } else { + csi_mclk_disable(); + pr_debug("Disable csi clock from source %d\n", src); + g_csi_mclk_on = false; + } + + return 0; +} +EXPORT_SYMBOL(csi_enable_mclk); + +/*! + * csi_read_mclk_flag + * + * @return gcsi_mclk_source + */ +int csi_read_mclk_flag(void) +{ + return 0; +} +EXPORT_SYMBOL(csi_read_mclk_flag); + +void csi_start_callback(void *data) +{ + cam_data *cam = (cam_data *) data; + + if (request_irq(MXC_INT_CSI, csi_irq_handler, 0, "csi", cam) < 0) + pr_debug("CSI error: irq request fail\n"); + +} +EXPORT_SYMBOL(csi_start_callback); + +void csi_stop_callback(void *data) +{ + cam_data *cam = (cam_data *) data; + + free_irq(MXC_INT_CSI, cam); +} +EXPORT_SYMBOL(csi_stop_callback); + +void csi_enable_int(int arg) +{ + unsigned long cr1 = __raw_readl(CSI_CSICR1); + + cr1 |= BIT_SOF_INTEN; + if (arg == 1) { + /* still capture needs DMA intterrupt */ + cr1 |= BIT_FB1_DMA_DONE_INTEN; + cr1 |= BIT_FB2_DMA_DONE_INTEN; + } + __raw_writel(cr1, CSI_CSICR1); +} +EXPORT_SYMBOL(csi_enable_int); + +void csi_disable_int(void) +{ + unsigned long cr1 = __raw_readl(CSI_CSICR1); + + cr1 &= ~BIT_SOF_INTEN; + cr1 &= ~BIT_FB1_DMA_DONE_INTEN; + cr1 &= ~BIT_FB2_DMA_DONE_INTEN; + __raw_writel(cr1, CSI_CSICR1); +} +EXPORT_SYMBOL(csi_disable_int); + +void csi_set_16bit_imagpara(int width, int height) +{ + int imag_para = 0; + unsigned long cr3 = __raw_readl(CSI_CSICR3); + + imag_para = (width << 16) | (height * 2); + __raw_writel(imag_para, CSI_CSIIMAG_PARA); + + /* reflash the embeded DMA controller */ + __raw_writel(cr3 | BIT_DMA_REFLASH_RFF, CSI_CSICR3); +} +EXPORT_SYMBOL(csi_set_16bit_imagpara); + +void csi_set_12bit_imagpara(int width, int height) +{ + int imag_para = 0; + unsigned long cr3 = __raw_readl(CSI_CSICR3); + + imag_para = (width << 16) | (height * 3 / 2); + __raw_writel(imag_para, CSI_CSIIMAG_PARA); + + /* reflash the embeded DMA controller */ + __raw_writel(cr3 | BIT_DMA_REFLASH_RFF, CSI_CSICR3); +} +EXPORT_SYMBOL(csi_set_12bit_imagpara); + +static void csi_mclk_recalc(struct clk *clk) +{ + u32 div; + + div = (__raw_readl(CSI_CSICR1) & BIT_MCLKDIV) >> SHIFT_MCLKDIV; + if (div == 0) + div = 1; + else + div = div * 2; + + clk->rate = clk->parent->rate / div; +} + +void csi_mclk_enable(void) +{ + __raw_writel(__raw_readl(CSI_CSICR1) | BIT_MCLKEN, CSI_CSICR1); +} + +void csi_mclk_disable(void) +{ + __raw_writel(__raw_readl(CSI_CSICR1) & ~BIT_MCLKEN, CSI_CSICR1); +} + +int32_t __init csi_init_module(void) +{ + int ret = 0; + struct clk *per_clk; + + csihw_reset(); + csi_init_interface(); + + per_clk = clk_get(NULL, "csi_clk"); + if (IS_ERR(per_clk)) + return PTR_ERR(per_clk); + + clk_put(per_clk); + csi_mclk.name = "csi_mclk"; + csi_mclk.parent = per_clk; + clk_register(&csi_mclk); + clk_enable(per_clk); + csi_mclk_recalc(&csi_mclk); + + return ret; +} + +void __exit csi_cleanup_module(void) +{ + clk_disable(&csi_mclk); + clk_unregister(&csi_mclk); +} + +module_init(csi_init_module); +module_exit(csi_cleanup_module); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("fsl CSI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/fsl_csi.h b/drivers/media/video/mxc/capture/fsl_csi.h new file mode 100644 index 000000000000..41bfff03f07e --- /dev/null +++ b/drivers/media/video/mxc/capture/fsl_csi.h @@ -0,0 +1,197 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_csi.h + * + * @brief mx25 CMOS Sensor interface functions + * + * @ingroup CSI + */ + +#ifndef MX25_CSI_H +#define MX25_CSI_H + +#include <linux/io.h> + +/* reset values */ +#define CSICR1_RESET_VAL 0x40000800 +#define CSICR2_RESET_VAL 0x0 +#define CSICR3_RESET_VAL 0x0 + +/* csi control reg 1 */ +#define BIT_SWAP16_EN (0x1 << 31) +#define BIT_EXT_VSYNC (0x1 << 30) +#define BIT_EOF_INT_EN (0x1 << 29) +#define BIT_PRP_IF_EN (0x1 << 28) +#define BIT_CCIR_MODE (0x1 << 27) +#define BIT_COF_INT_EN (0x1 << 26) +#define BIT_SF_OR_INTEN (0x1 << 25) +#define BIT_RF_OR_INTEN (0x1 << 24) +#define BIT_SFF_DMA_DONE_INTEN (0x1 << 22) +#define BIT_STATFF_INTEN (0x1 << 21) +#define BIT_FB2_DMA_DONE_INTEN (0x1 << 20) +#define BIT_FB1_DMA_DONE_INTEN (0x1 << 19) +#define BIT_RXFF_INTEN (0x1 << 18) +#define BIT_SOF_POL (0x1 << 17) +#define BIT_SOF_INTEN (0x1 << 16) +#define BIT_MCLKDIV (0xF << 12) +#define BIT_HSYNC_POL (0x1 << 11) +#define BIT_CCIR_EN (0x1 << 10) +#define BIT_MCLKEN (0x1 << 9) +#define BIT_FCC (0x1 << 8) +#define BIT_PACK_DIR (0x1 << 7) +#define BIT_CLR_STATFIFO (0x1 << 6) +#define BIT_CLR_RXFIFO (0x1 << 5) +#define BIT_GCLK_MODE (0x1 << 4) +#define BIT_INV_DATA (0x1 << 3) +#define BIT_INV_PCLK (0x1 << 2) +#define BIT_REDGE (0x1 << 1) +#define BIT_PIXEL_BIT (0x1 << 0) + +#define SHIFT_MCLKDIV 12 + +/* control reg 3 */ +#define BIT_FRMCNT (0xFFFF << 16) +#define BIT_FRMCNT_RST (0x1 << 15) +#define BIT_DMA_REFLASH_RFF (0x1 << 14) +#define BIT_DMA_REFLASH_SFF (0x1 << 13) +#define BIT_DMA_REQ_EN_RFF (0x1 << 12) +#define BIT_DMA_REQ_EN_SFF (0x1 << 11) +#define BIT_STATFF_LEVEL (0x7 << 8) +#define BIT_HRESP_ERR_EN (0x1 << 7) +#define BIT_RXFF_LEVEL (0x7 << 4) +#define BIT_TWO_8BIT_SENSOR (0x1 << 3) +#define BIT_ZERO_PACK_EN (0x1 << 2) +#define BIT_ECC_INT_EN (0x1 << 1) +#define BIT_ECC_AUTO_EN (0x1 << 0) + +#define SHIFT_FRMCNT 16 + +/* csi status reg */ +#define BIT_SFF_OR_INT (0x1 << 25) +#define BIT_RFF_OR_INT (0x1 << 24) +#define BIT_DMA_TSF_DONE_SFF (0x1 << 22) +#define BIT_STATFF_INT (0x1 << 21) +#define BIT_DMA_TSF_DONE_FB2 (0x1 << 20) +#define BIT_DMA_TSF_DONE_FB1 (0x1 << 19) +#define BIT_RXFF_INT (0x1 << 18) +#define BIT_EOF_INT (0x1 << 17) +#define BIT_SOF_INT (0x1 << 16) +#define BIT_F2_INT (0x1 << 15) +#define BIT_F1_INT (0x1 << 14) +#define BIT_COF_INT (0x1 << 13) +#define BIT_HRESP_ERR_INT (0x1 << 7) +#define BIT_ECC_INT (0x1 << 1) +#define BIT_DRDY (0x1 << 0) + +#define CSI_MCLK_VF 1 +#define CSI_MCLK_ENC 2 +#define CSI_MCLK_RAW 4 +#define CSI_MCLK_I2C 8 +#endif + +#define CSI_CSICR1 (IO_ADDRESS(CSI_BASE_ADDR)) +#define CSI_CSICR2 (IO_ADDRESS(CSI_BASE_ADDR + 0x4)) +#define CSI_CSICR3 (IO_ADDRESS(CSI_BASE_ADDR + 0x8)) +#define CSI_STATFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0xC)) +#define CSI_CSIRXFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0x10)) +#define CSI_CSIRXCNT (IO_ADDRESS(CSI_BASE_ADDR + 0x14)) +#define CSI_CSISR (IO_ADDRESS(CSI_BASE_ADDR + 0x18)) + +#define CSI_CSIDBG (IO_ADDRESS(CSI_BASE_ADDR + 0x1C)) +#define CSI_CSIDMASA_STATFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0x20)) +#define CSI_CSIDMATS_STATFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0x24)) +#define CSI_CSIDMASA_FB1 (IO_ADDRESS(CSI_BASE_ADDR + 0x28)) +#define CSI_CSIDMASA_FB2 (IO_ADDRESS(CSI_BASE_ADDR + 0x2C)) +#define CSI_CSIFBUF_PARA (IO_ADDRESS(CSI_BASE_ADDR + 0x30)) +#define CSI_CSIIMAG_PARA (IO_ADDRESS(CSI_BASE_ADDR + 0x34)) + +#define CSI_CSIRXFIFO_PHYADDR (CSI_BASE_ADDR + 0x10) + +static inline void csi_clear_status(unsigned long status) +{ + __raw_writel(status, CSI_CSISR); +} + +struct csi_signal_cfg_t { + unsigned data_width:3; + unsigned clk_mode:2; + unsigned ext_vsync:1; + unsigned Vsync_pol:1; + unsigned Hsync_pol:1; + unsigned pixclk_pol:1; + unsigned data_pol:1; + unsigned sens_clksrc:1; +}; + +struct csi_config_t { + /* control reg 1 */ + unsigned int swap16_en:1; + unsigned int ext_vsync:1; + unsigned int eof_int_en:1; + unsigned int prp_if_en:1; + unsigned int ccir_mode:1; + unsigned int cof_int_en:1; + unsigned int sf_or_inten:1; + unsigned int rf_or_inten:1; + unsigned int sff_dma_done_inten:1; + unsigned int statff_inten:1; + unsigned int fb2_dma_done_inten:1; + unsigned int fb1_dma_done_inten:1; + unsigned int rxff_inten:1; + unsigned int sof_pol:1; + unsigned int sof_inten:1; + unsigned int mclkdiv:4; + unsigned int hsync_pol:1; + unsigned int ccir_en:1; + unsigned int mclken:1; + unsigned int fcc:1; + unsigned int pack_dir:1; + unsigned int gclk_mode:1; + unsigned int inv_data:1; + unsigned int inv_pclk:1; + unsigned int redge:1; + unsigned int pixel_bit:1; + + /* control reg 3 */ + unsigned int frmcnt:16; + unsigned int frame_reset:1; + unsigned int dma_reflash_rff:1; + unsigned int dma_reflash_sff:1; + unsigned int dma_req_en_rff:1; + unsigned int dma_req_en_sff:1; + unsigned int statff_level:3; + unsigned int hresp_err_en:1; + unsigned int rxff_level:3; + unsigned int two_8bit_sensor:1; + unsigned int zero_pack_en:1; + unsigned int ecc_int_en:1; + unsigned int ecc_auto_en:1; + /* fifo counter */ + unsigned int rxcnt; +}; + +typedef void (*csi_irq_callback_t) (void *data, unsigned long status); + +int32_t csi_enable_mclk(int src, bool flag, bool wait); +void csi_init_interface(void); +void csi_set_16bit_imagpara(int width, int height); +void csi_set_12bit_imagpara(int width, int height); +int csi_read_mclk_flag(void); +void csi_start_callback(void *data); +void csi_stop_callback(void *data); +void csi_enable_int(int arg); +void csi_disable_int(void); +void csi_mclk_enable(void); +void csi_mclk_disable(void); diff --git a/drivers/media/video/mxc/capture/ipu_csi_enc.c b/drivers/media/video/mxc/capture/ipu_csi_enc.c new file mode 100644 index 000000000000..fb3d0d7d5ec6 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_csi_enc.c @@ -0,0 +1,277 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file csi_enc.c + * + * @brief CSI Use case for video capture + * + * @ingroup IPU + */ + +#include <linux/dma-mapping.h> +#include <linux/ipu.h> +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +#ifdef CAMERA_DBG + #define CAMERA_TRACE(x) (printk)x +#else + #define CAMERA_TRACE(x) +#endif + +/* + * Function definitions + */ + +/*! + * csi ENC callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t csi_enc_callback(int irq, void *dev_id) +{ + cam_data *cam = (cam_data *) dev_id; + + if (cam->enc_callback == NULL) + return IRQ_HANDLED; + + cam->enc_callback(irq, dev_id); + return IRQ_HANDLED; +} + +/*! + * CSI ENC enable channel setup function + * + * @param cam struct cam_data * mxc capture instance + * + * @return status + */ +static int csi_enc_setup(cam_data *cam) +{ + ipu_channel_params_t params; + u32 pixel_fmt; + int err = 0; + dma_addr_t dummy = 0xdeadbeaf; + + CAMERA_TRACE("In csi_enc_setup\n"); + if (!cam) { + printk(KERN_ERR "cam private is NULL\n"); + return -ENXIO; + } + + memset(¶ms, 0, sizeof(ipu_channel_params_t)); + params.csi_mem.csi = cam->csi; + + if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) + pixel_fmt = IPU_PIX_FMT_YUV420P; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) + pixel_fmt = IPU_PIX_FMT_YUV422P; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) + pixel_fmt = IPU_PIX_FMT_UYVY; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12) + pixel_fmt = IPU_PIX_FMT_NV12; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24) + pixel_fmt = IPU_PIX_FMT_BGR24; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) + pixel_fmt = IPU_PIX_FMT_RGB24; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565) + pixel_fmt = IPU_PIX_FMT_RGB565; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR32) + pixel_fmt = IPU_PIX_FMT_BGR32; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB32) + pixel_fmt = IPU_PIX_FMT_RGB32; + else { + printk(KERN_ERR "format not supported\n"); + return -EINVAL; + } + + ipu_csi_enable_mclk_if(CSI_MCLK_ENC, cam->csi, true, true); + + err = ipu_init_channel(CSI_MEM, ¶ms); + if (err != 0) { + printk(KERN_ERR "ipu_init_channel %d\n", err); + return err; + } + + err = ipu_init_channel_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, + pixel_fmt, cam->v2f.fmt.pix.width, + cam->v2f.fmt.pix.height, + cam->v2f.fmt.pix.width, IPU_ROTATE_NONE, + dummy, dummy, + cam->offset.u_offset, + cam->offset.v_offset); + if (err != 0) { + printk(KERN_ERR "CSI_MEM output buffer\n"); + return err; + } + err = ipu_enable_channel(CSI_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel CSI_MEM\n"); + return err; + } + + return err; +} + +/*! + * function to update physical buffer address for encorder IDMA channel + * + * @param eba physical buffer address for encorder IDMA channel + * @param buffer_num int buffer 0 or buffer 1 + * + * @return status + */ +static int csi_enc_eba_update(dma_addr_t eba, int *buffer_num) +{ + int err = 0; + + pr_debug("eba %x\n", eba); + err = ipu_update_channel_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, + *buffer_num, eba); + if (err != 0) { + printk(KERN_ERR "err %d buffer_num %d\n", err, *buffer_num); + return err; + } + + ipu_select_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, *buffer_num); + + *buffer_num = (*buffer_num == 0) ? 1 : 0; + + return 0; +} + +/*! + * Enable encoder task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int csi_enc_enabling_tasks(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + CAMERA_TRACE("IPU:In csi_enc_enabling_tasks\n"); + + err = ipu_request_irq(IPU_IRQ_CSI0_OUT_EOF, + csi_enc_callback, 0, "Mxc Camera", cam); + if (err != 0) { + printk(KERN_ERR "Error registering rot irq\n"); + return err; + } + + err = csi_enc_setup(cam); + if (err != 0) { + printk(KERN_ERR "csi_enc_setup %d\n", err); + return err; + } + + return err; +} + +/*! + * Disable encoder task + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +static int csi_enc_disabling_tasks(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + ipu_free_irq(IPU_IRQ_CSI0_OUT_EOF, cam); + + err = ipu_disable_channel(CSI_MEM, true); + + ipu_uninit_channel(CSI_MEM); + + ipu_csi_enable_mclk_if(CSI_MCLK_ENC, cam->csi, false, false); + + return err; +} + +/*! + * function to select CSI ENC as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +int csi_enc_select(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam) { + cam->enc_update_eba = csi_enc_eba_update; + cam->enc_enable = csi_enc_enabling_tasks; + cam->enc_disable = csi_enc_disabling_tasks; + } else { + err = -EIO; + } + + return err; +} + +/*! + * function to de-select CSI ENC as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +int csi_enc_deselect(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam) { + cam->enc_update_eba = NULL; + cam->enc_enable = NULL; + cam->enc_disable = NULL; + } + + return err; +} + +/*! + * Init the Encorder channels + * + * @return Error code indicating success or failure + */ +__init int csi_enc_init(void) +{ + return 0; +} + +/*! + * Deinit the Encorder channels + * + */ +void __exit csi_enc_exit(void) +{ +} + +module_init(csi_enc_init); +module_exit(csi_enc_exit); + +EXPORT_SYMBOL(csi_enc_select); +EXPORT_SYMBOL(csi_enc_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("CSI ENC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_prp_enc.c b/drivers/media/video/mxc/capture/ipu_prp_enc.c new file mode 100644 index 000000000000..79eb8a615ee8 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_enc.c @@ -0,0 +1,455 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_enc.c + * + * @brief IPU Use case for PRP-ENC + * + * @ingroup IPU + */ + +#include <linux/dma-mapping.h> +#include <linux/ipu.h> +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +#ifdef CAMERA_DBG + #define CAMERA_TRACE(x) (printk)x +#else + #define CAMERA_TRACE(x) +#endif + +static ipu_rotate_mode_t grotation = IPU_ROTATE_NONE; + +/* + * Function definitions + */ + +/*! + * IPU ENC callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prp_enc_callback(int irq, void *dev_id) +{ + cam_data *cam = (cam_data *) dev_id; + + if (cam->enc_callback == NULL) + return IRQ_HANDLED; + + cam->enc_callback(irq, dev_id); + + return IRQ_HANDLED; +} + +/*! + * PrpENC enable channel setup function + * + * @param cam struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_enc_setup(cam_data * cam) +{ + ipu_channel_params_t enc; + int err = 0; + dma_addr_t dummy = 0xdeadbeaf; + + CAMERA_TRACE("In prp_enc_setup\n"); + if (!cam) { + printk(KERN_ERR "cam private is NULL\n"); + return -ENXIO; + } + memset(&enc, 0, sizeof(ipu_channel_params_t)); + + ipu_csi_get_window_size(&enc.csi_prp_enc_mem.in_width, + &enc.csi_prp_enc_mem.in_height, cam->csi); + + enc.csi_prp_enc_mem.in_pixel_fmt = IPU_PIX_FMT_UYVY; + enc.csi_prp_enc_mem.out_width = cam->v2f.fmt.pix.width; + enc.csi_prp_enc_mem.out_height = cam->v2f.fmt.pix.height; + enc.csi_prp_enc_mem.csi = cam->csi; + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + enc.csi_prp_enc_mem.out_width = cam->v2f.fmt.pix.height; + enc.csi_prp_enc_mem.out_height = cam->v2f.fmt.pix.width; + } + + if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_YUV420P; + pr_info("YUV420\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_YUV422P; + pr_info("YUV422P\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_NV12; + pr_info("NV12\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_BGR24; + pr_info("BGR24\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_RGB24; + pr_info("RGB24\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_RGB565; + pr_info("RGB565\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR32) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_BGR32; + pr_info("BGR32\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB32) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_RGB32; + pr_info("RGB32\n"); + } else { + printk(KERN_ERR "format not supported\n"); + return -EINVAL; + } + + err = ipu_init_channel(CSI_PRP_ENC_MEM, &enc); + if (err != 0) { + printk(KERN_ERR "ipu_init_channel %d\n", err); + return err; + } + + ipu_csi_enable_mclk_if(CSI_MCLK_ENC, cam->csi, true, true); + + grotation = cam->rotation; + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + if (cam->rot_enc_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_enc_buf_size[0], + cam->rot_enc_bufs_vaddr[0], + cam->rot_enc_bufs[0]); + } + if (cam->rot_enc_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_enc_buf_size[1], + cam->rot_enc_bufs_vaddr[1], + cam->rot_enc_bufs[1]); + } + cam->rot_enc_buf_size[0] = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->rot_enc_bufs_vaddr[0] = + (void *)dma_alloc_coherent(0, cam->rot_enc_buf_size[0], + &cam->rot_enc_bufs[0], + GFP_DMA | GFP_KERNEL); + if (!cam->rot_enc_bufs_vaddr[0]) { + printk(KERN_ERR "alloc enc_bufs0\n"); + return -ENOMEM; + } + cam->rot_enc_buf_size[1] = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->rot_enc_bufs_vaddr[1] = + (void *)dma_alloc_coherent(0, cam->rot_enc_buf_size[1], + &cam->rot_enc_bufs[1], + GFP_DMA | GFP_KERNEL); + if (!cam->rot_enc_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_enc_buf_size[0], + cam->rot_enc_bufs_vaddr[0], + cam->rot_enc_bufs[0]); + cam->rot_enc_bufs_vaddr[0] = NULL; + cam->rot_enc_bufs[0] = 0; + printk(KERN_ERR "alloc enc_bufs1\n"); + return -ENOMEM; + } + + err = ipu_init_channel_buffer(CSI_PRP_ENC_MEM, + IPU_OUTPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_width, + enc.csi_prp_enc_mem.out_height, + enc.csi_prp_enc_mem.out_width, + IPU_ROTATE_NONE, + cam->rot_enc_bufs[0], + cam->rot_enc_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "CSI_PRP_ENC_MEM err\n"); + return err; + } + + err = ipu_init_channel(MEM_ROT_ENC_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "MEM_ROT_ENC_MEM channel err\n"); + return err; + } + + err = ipu_init_channel_buffer(MEM_ROT_ENC_MEM, IPU_INPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_width, + enc.csi_prp_enc_mem.out_height, + enc.csi_prp_enc_mem.out_width, + cam->rotation, + cam->rot_enc_bufs[0], + cam->rot_enc_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "MEM_ROT_ENC_MEM input buffer\n"); + return err; + } + + err = + ipu_init_channel_buffer(MEM_ROT_ENC_MEM, IPU_OUTPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_height, + enc.csi_prp_enc_mem.out_width, + cam->v2f.fmt.pix.bytesperline / + bytes_per_pixel(enc.csi_prp_enc_mem. + out_pixel_fmt), + IPU_ROTATE_NONE, dummy, dummy, + cam->offset.u_offset, + cam->offset.v_offset); + if (err != 0) { + printk(KERN_ERR "MEM_ROT_ENC_MEM output buffer\n"); + return err; + } + + err = ipu_link_channels(CSI_PRP_ENC_MEM, MEM_ROT_ENC_MEM); + if (err < 0) { + printk(KERN_ERR + "link CSI_PRP_ENC_MEM-MEM_ROT_ENC_MEM\n"); + return err; + } + + err = ipu_enable_channel(CSI_PRP_ENC_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel CSI_PRP_ENC_MEM\n"); + return err; + } + err = ipu_enable_channel(MEM_ROT_ENC_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel MEM_ROT_ENC_MEM\n"); + return err; + } + + ipu_select_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, 1); + } else { + err = + ipu_init_channel_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_width, + enc.csi_prp_enc_mem.out_height, + cam->v2f.fmt.pix.bytesperline / + bytes_per_pixel(enc.csi_prp_enc_mem. + out_pixel_fmt), + cam->rotation, dummy, dummy, + cam->offset.u_offset, + cam->offset.v_offset); + if (err != 0) { + printk(KERN_ERR "CSI_PRP_ENC_MEM output buffer\n"); + return err; + } + err = ipu_enable_channel(CSI_PRP_ENC_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel CSI_PRP_ENC_MEM\n"); + return err; + } + } + + return err; +} + +/*! + * function to update physical buffer address for encorder IDMA channel + * + * @param eba physical buffer address for encorder IDMA channel + * @param buffer_num int buffer 0 or buffer 1 + * + * @return status + */ +static int prp_enc_eba_update(dma_addr_t eba, int *buffer_num) +{ + int err = 0; + + pr_debug("eba %x\n", eba); + if (grotation >= IPU_ROTATE_90_RIGHT) { + err = ipu_update_channel_buffer(MEM_ROT_ENC_MEM, + IPU_OUTPUT_BUFFER, *buffer_num, + eba); + } else { + err = ipu_update_channel_buffer(CSI_PRP_ENC_MEM, + IPU_OUTPUT_BUFFER, *buffer_num, + eba); + } + if (err != 0) { + printk(KERN_ERR "err %d buffer_num %d\n", err, *buffer_num); + return err; + } + + if (grotation >= IPU_ROTATE_90_RIGHT) { + ipu_select_buffer(MEM_ROT_ENC_MEM, IPU_OUTPUT_BUFFER, + *buffer_num); + } else { + ipu_select_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, + *buffer_num); + } + + *buffer_num = (*buffer_num == 0) ? 1 : 0; + return 0; +} + +/*! + * Enable encoder task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_enc_enabling_tasks(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + CAMERA_TRACE("IPU:In prp_enc_enabling_tasks\n"); + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + err = ipu_request_irq(IPU_IRQ_PRP_ENC_ROT_OUT_EOF, + prp_enc_callback, 0, "Mxc Camera", cam); + } else { + err = ipu_request_irq(IPU_IRQ_PRP_ENC_OUT_EOF, + prp_enc_callback, 0, "Mxc Camera", cam); + } + if (err != 0) { + printk(KERN_ERR "Error registering rot irq\n"); + return err; + } + + err = prp_enc_setup(cam); + if (err != 0) { + printk(KERN_ERR "prp_enc_setup %d\n", err); + return err; + } + + return err; +} + +/*! + * Disable encoder task + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +static int prp_enc_disabling_tasks(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_free_irq(IPU_IRQ_PRP_ENC_ROT_OUT_EOF, cam); + } else { + ipu_free_irq(IPU_IRQ_PRP_ENC_OUT_EOF, cam); + } + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_unlink_channels(CSI_PRP_ENC_MEM, MEM_ROT_ENC_MEM); + } + + err = ipu_disable_channel(CSI_PRP_ENC_MEM, true); + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + err |= ipu_disable_channel(MEM_ROT_ENC_MEM, true); + } + + ipu_uninit_channel(CSI_PRP_ENC_MEM); + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_uninit_channel(MEM_ROT_ENC_MEM); + } + + ipu_csi_enable_mclk_if(CSI_MCLK_ENC, cam->csi, false, false); + + return err; +} + +/*! + * function to select PRP-ENC as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +int prp_enc_select(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam) { + cam->enc_update_eba = prp_enc_eba_update; + cam->enc_enable = prp_enc_enabling_tasks; + cam->enc_disable = prp_enc_disabling_tasks; + } else { + err = -EIO; + } + + return err; +} + +/*! + * function to de-select PRP-ENC as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +int prp_enc_deselect(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + //err = prp_enc_disabling_tasks(cam); + + if (cam) { + cam->enc_update_eba = NULL; + cam->enc_enable = NULL; + cam->enc_disable = NULL; + if (cam->rot_enc_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_enc_buf_size[0], + cam->rot_enc_bufs_vaddr[0], + cam->rot_enc_bufs[0]); + cam->rot_enc_bufs_vaddr[0] = NULL; + cam->rot_enc_bufs[0] = 0; + } + if (cam->rot_enc_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_enc_buf_size[1], + cam->rot_enc_bufs_vaddr[1], + cam->rot_enc_bufs[1]); + cam->rot_enc_bufs_vaddr[1] = NULL; + cam->rot_enc_bufs[1] = 0; + } + } + + return err; +} + +/*! + * Init the Encorder channels + * + * @return Error code indicating success or failure + */ +__init int prp_enc_init(void) +{ + return 0; +} + +/*! + * Deinit the Encorder channels + * + */ +void __exit prp_enc_exit(void) +{ +} + +module_init(prp_enc_init); +module_exit(prp_enc_exit); + +EXPORT_SYMBOL(prp_enc_select); +EXPORT_SYMBOL(prp_enc_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP ENC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_prp_sw.h b/drivers/media/video/mxc/capture/ipu_prp_sw.h new file mode 100644 index 000000000000..0d51e2ae8670 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_sw.h @@ -0,0 +1,38 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_sw.h + * + * @brief This file contains the IPU PRP use case driver header. + * + * @ingroup IPU + */ + +#ifndef _INCLUDE_IPU__PRP_SW_H_ +#define _INCLUDE_IPU__PRP_SW_H_ + +int csi_enc_select(void *private); +int csi_enc_deselect(void *private); +int prp_enc_select(void *private); +int prp_enc_deselect(void *private); +int prp_vf_adc_select(void *private); +int prp_vf_sdc_select(void *private); +int prp_vf_sdc_select_bg(void *private); +int prp_vf_adc_deselect(void *private); +int prp_vf_sdc_deselect(void *private); +int prp_vf_sdc_deselect_bg(void *private); +int prp_still_select(void *private); +int prp_still_deselect(void *private); + +#endif diff --git a/drivers/media/video/mxc/capture/ipu_prp_vf_adc.c b/drivers/media/video/mxc/capture/ipu_prp_vf_adc.c new file mode 100644 index 000000000000..a7ac09ae87d4 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_vf_adc.c @@ -0,0 +1,601 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_vf_adc.c + * + * @brief IPU Use case for PRP-VF + * + * @ingroup IPU + */ + +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" +#include <mach/mxcfb.h> +#include <mach/ipu.h> +#include <linux/dma-mapping.h> + +/* + * Function definitions + */ + +/*! + * prpvf_start - start the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_start(void *private) +{ + cam_data *cam = (cam_data *) private; + ipu_channel_params_t vf; + ipu_channel_params_t params; + u32 format = IPU_PIX_FMT_RGB565; + u32 size = 2; + int err = 0; + + if (!cam) { + printk(KERN_ERR "prpvf_start private is NULL\n"); + return -ENXIO; + } + + if (cam->overlay_active == true) { + printk(KERN_ERR "prpvf_start already start.\n"); + return 0; + } + + mxcfb_set_refresh_mode(cam->overlay_fb, MXCFB_REFRESH_OFF, 0); + + memset(&vf, 0, sizeof(ipu_channel_params_t)); + ipu_csi_get_window_size(&vf.csi_prp_vf_adc.in_width, + &vf.csi_prp_vf_adc.in_height); + vf.csi_prp_vf_adc.in_pixel_fmt = IPU_PIX_FMT_UYVY; + vf.csi_prp_vf_adc.out_width = cam->win.w.width; + vf.csi_prp_vf_adc.out_height = cam->win.w.height; + vf.csi_prp_vf_adc.graphics_combine_en = 0; + vf.csi_prp_vf_adc.out_left = cam->win.w.left; + + /* hope to be removed when those offset taken cared by adc driver. */ +#ifdef CONFIG_FB_MXC_EPSON_QVGA_PANEL + vf.csi_prp_vf_adc.out_left += 12; +#endif +#ifdef CONFIG_FB_MXC_EPSON_PANEL + vf.csi_prp_vf_adc.out_left += 2; +#endif + + vf.csi_prp_vf_adc.out_top = cam->win.w.top; + + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + vf.csi_prp_vf_adc.out_width = cam->win.w.height; + vf.csi_prp_vf_adc.out_height = cam->win.w.width; + + size = cam->win.w.width * cam->win.w.height * size; + vf.csi_prp_vf_adc.out_pixel_fmt = format; + err = ipu_init_channel(CSI_PRP_VF_MEM, &vf); + if (err != 0) + return err; + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, true, true); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + cam->vf_bufs[0]); + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + cam->vf_bufs[1]); + } + cam->vf_bufs_size[0] = size; + cam->vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam-> + vf_bufs_size + [0], + &cam-> + vf_bufs[0], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + cam->vf_bufs_size[1] = size; + cam->vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam-> + vf_bufs_size + [1], + &cam-> + vf_bufs[1], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + IPU_ROTATE_NONE, + cam->vf_bufs[0], cam->vf_bufs[1], + 0, 0); + if (err != 0) + goto out_3; + + if (cam->rot_vf_bufs[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + } + if (cam->rot_vf_bufs[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + } + cam->rot_vf_buf_size[0] = PAGE_ALIGN(size); + cam->rot_vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam-> + rot_vf_buf_size + [0], + &cam-> + rot_vf_bufs + [0], + GFP_DMA | + GFP_KERNEL); + if (cam->rot_vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate rot_vf_bufs\n"); + err = -ENOMEM; + goto out_3; + } + cam->rot_vf_buf_size[1] = PAGE_ALIGN(size); + cam->rot_vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam-> + rot_vf_buf_size + [1], + &cam-> + rot_vf_bufs + [1], + GFP_DMA | + GFP_KERNEL); + if (cam->rot_vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate rot_vf_bufs\n"); + err = -ENOMEM; + goto out_3; + } + err = ipu_init_channel(MEM_ROT_VF_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "prpvf_start :Error " + "MEM_ROT_VF_MEM channel\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->vf_rotation, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "MEM_ROT_VF_MEM input buffer\n"); + goto out_2; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + IPU_ROTATE_NONE, + cam->rot_vf_bufs[0], + cam->rot_vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + + err = ipu_link_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); + if (err < 0) { + printk(KERN_ERR "prpvf_start: Error " + "linking CSI_PRP_VF_MEM-MEM_ROT_VF_MEM\n"); + goto out_2; + } + + ipu_disable_channel(ADC_SYS2, false); + ipu_uninit_channel(ADC_SYS2); + + params.adc_sys2.disp = DISP0; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = cam->win.w.left; + /* going to be removed when those offset taken cared by adc driver. */ +#ifdef CONFIG_FB_MXC_EPSON_QVGA_PANEL + params.adc_sys2.out_left += 12; +#endif +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.adc_sys2.out_left += 2; +#endif + params.adc_sys2.out_top = cam->win.w.top; + err = ipu_init_channel(ADC_SYS2, ¶ms); + if (err != 0) { + printk(KERN_ERR + "prpvf_start: Error initializing ADC SYS1\n"); + goto out_2; + } + + err = ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + IPU_ROTATE_NONE, + cam->rot_vf_bufs[0], + cam->rot_vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error initializing ADC SYS1 buffer\n"); + goto out_1; + } + + err = ipu_link_channels(MEM_ROT_VF_MEM, ADC_SYS2); + if (err < 0) { + printk(KERN_ERR + "Error linking MEM_ROT_VF_MEM-ADC_SYS2\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(MEM_ROT_VF_MEM); + ipu_enable_channel(ADC_SYS2); + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 1); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } +#ifndef CONFIG_MXC_IPU_PRP_VF_SDC + else if (cam->vf_rotation == IPU_ROTATE_NONE) { + vf.csi_prp_vf_adc.out_pixel_fmt = IPU_PIX_FMT_BGR32; + err = ipu_init_channel(CSI_PRP_VF_ADC, &vf); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing CSI_PRP_VF_ADC\n"); + return err; + } + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, true, true); + err = ipu_init_channel_buffer(CSI_PRP_VF_ADC, IPU_OUTPUT_BUFFER, + format, cam->win.w.width, + cam->win.w.height, + cam->win.w.width, IPU_ROTATE_NONE, + 0, 0, 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing CSI_PRP_VF_MEM\n"); + return err; + } + ipu_enable_channel(CSI_PRP_VF_ADC); + } +#endif + else { + size = cam->win.w.width * cam->win.w.height * size; + vf.csi_prp_vf_adc.out_pixel_fmt = format; + err = ipu_init_channel(CSI_PRP_VF_MEM, &vf); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing CSI_PRP_VF_MEM\n"); + return err; + } + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, true, true); + + if (cam->vf_bufs[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + cam->vf_bufs[0]); + } + if (cam->vf_bufs[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + cam->vf_bufs[1]); + } + cam->vf_bufs_size[0] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam-> + vf_bufs_size + [0], + &cam-> + vf_bufs[0], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate vf_bufs\n"); + err = -ENOMEM; + goto out_3; + } + cam->vf_bufs_size[1] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam-> + vf_bufs_size + [1], + &cam-> + vf_bufs[1], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate vf_bufs\n"); + err = -ENOMEM; + goto out_3; + } + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->vf_rotation, + cam->vf_bufs[0], cam->vf_bufs[1], + 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing CSI_PRP_VF_MEM\n"); + goto out_3; + } + + ipu_disable_channel(ADC_SYS2, false); + ipu_uninit_channel(ADC_SYS2); + + params.adc_sys2.disp = DISP0; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = cam->win.w.left; + // going to be removed when those offset taken cared by adc driver. +#ifdef CONFIG_FB_MXC_EPSON_QVGA_PANEL + params.adc_sys2.out_left += 12; +#endif +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.adc_sys2.out_left += 2; +#endif + params.adc_sys2.out_top = cam->win.w.top; + err = ipu_init_channel(ADC_SYS2, ¶ms); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing ADC_SYS2\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + IPU_ROTATE_NONE, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing ADC SYS1 buffer\n"); + goto out_1; + } + + err = ipu_link_channels(CSI_PRP_VF_MEM, ADC_SYS2); + if (err < 0) { + printk(KERN_ERR "prpvf_start: Error " + "linking MEM_ROT_VF_MEM-ADC_SYS2\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(ADC_SYS2); + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } + + cam->overlay_active = true; + return err; + + out_1: + ipu_uninit_channel(ADC_SYS2); + out_2: + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + ipu_uninit_channel(MEM_ROT_VF_MEM); + } + out_3: + ipu_uninit_channel(CSI_PRP_VF_MEM); + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + return err; +} + +/*! + * prpvf_stop - stop the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam->overlay_active == false) + return 0; + + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + ipu_unlink_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); + ipu_unlink_channels(MEM_ROT_VF_MEM, ADC_SYS2); + + ipu_disable_channel(CSI_PRP_VF_MEM, true); + ipu_disable_channel(MEM_ROT_VF_MEM, true); + ipu_disable_channel(ADC_SYS2, true); + + ipu_uninit_channel(CSI_PRP_VF_MEM); + ipu_uninit_channel(MEM_ROT_VF_MEM); + ipu_uninit_channel(ADC_SYS2); + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, false, false); + } +#ifndef CONFIG_MXC_IPU_PRP_VF_SDC + else if (cam->vf_rotation == IPU_ROTATE_NONE) { + ipu_disable_channel(CSI_PRP_VF_ADC, false); + ipu_uninit_channel(CSI_PRP_VF_ADC); + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, false, false); + } +#endif + else { + ipu_unlink_channels(CSI_PRP_VF_MEM, ADC_SYS2); + + ipu_disable_channel(CSI_PRP_VF_MEM, true); + ipu_disable_channel(ADC_SYS2, true); + + ipu_uninit_channel(CSI_PRP_VF_MEM); + ipu_uninit_channel(ADC_SYS2); + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, false, false); + } + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + + cam->overlay_active = false; + + mxcfb_set_refresh_mode(cam->overlay_fb, MXCFB_REFRESH_PARTIAL, 0); + return err; +} + +/*! + * function to select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_adc_select(void *private) +{ + cam_data *cam; + if (private) { + cam = (cam_data *) private; + cam->vf_start_adc = prpvf_start; + cam->vf_stop_adc = prpvf_stop; + cam->overlay_active = false; + } else { + return -EIO; + } + return 0; +} + +/*! + * function to de-select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_adc_deselect(void *private) +{ + cam_data *cam; + int err = 0; + err = prpvf_stop(private); + + if (private) { + cam = (cam_data *) private; + cam->vf_start_adc = NULL; + cam->vf_stop_adc = NULL; + } + return err; +} + +/*! + * Init viewfinder task. + * + * @return Error code indicating success or failure + */ +__init int prp_vf_adc_init(void) +{ + return 0; +} + +/*! + * Deinit viewfinder task. + * + * @return Error code indicating success or failure + */ +void __exit prp_vf_adc_exit(void) +{ +} + +module_init(prp_vf_adc_init); +module_exit(prp_vf_adc_exit); + +EXPORT_SYMBOL(prp_vf_adc_select); +EXPORT_SYMBOL(prp_vf_adc_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP VF ADC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_prp_vf_sdc.c b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc.c new file mode 100644 index 000000000000..369facdf29c2 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc.c @@ -0,0 +1,411 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_vf_sdc.c + * + * @brief IPU Use case for PRP-VF + * + * @ingroup IPU + */ + +#include <linux/dma-mapping.h> +#include <linux/console.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +/* + * Function definitions + */ + +/*! + * prpvf_start - start the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_start(void *private) +{ + struct fb_var_screeninfo fbvar; + struct fb_info *fbi = NULL; + cam_data *cam = (cam_data *) private; + ipu_channel_params_t vf; + u32 format = IPU_PIX_FMT_RGB565; + u32 size = 2, temp = 0; + int err = 0, i = 0; + + if (!cam) { + printk(KERN_ERR "private is NULL\n"); + return -EIO; + } + + if (cam->overlay_active == true) { + pr_debug("already started.\n"); + return 0; + } + + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strcmp(idstr, "DISP3 FG") == 0) + fbi = registered_fb[i]; + } + + if (fbi == NULL) { + printk(KERN_ERR "DISP3 FG fb not found\n"); + return -EPERM; + } + + fbvar = fbi->var; + fbvar.bits_per_pixel = 16; + fbvar.nonstd = 0; + fbvar.xres = fbvar.xres_virtual = cam->win.w.width; + fbvar.yres = cam->win.w.height; + fbvar.yres_virtual = cam->win.w.height * 2; + fbvar.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbvar); + + ipu_disp_set_window_pos(MEM_FG_SYNC, cam->win.w.left, + cam->win.w.top); + + memset(&vf, 0, sizeof(ipu_channel_params_t)); + ipu_csi_get_window_size(&vf.csi_prp_vf_mem.in_width, + &vf.csi_prp_vf_mem.in_height, cam->csi); + vf.csi_prp_vf_mem.in_pixel_fmt = IPU_PIX_FMT_UYVY; + vf.csi_prp_vf_mem.out_width = cam->win.w.width; + vf.csi_prp_vf_mem.out_height = cam->win.w.height; + vf.csi_prp_vf_mem.csi = cam->csi; + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + vf.csi_prp_vf_mem.out_width = cam->win.w.height; + vf.csi_prp_vf_mem.out_height = cam->win.w.width; + } + vf.csi_prp_vf_mem.out_pixel_fmt = format; + size = cam->win.w.width * cam->win.w.height * size; + + err = ipu_init_channel(CSI_PRP_VF_MEM, &vf); + if (err != 0) + goto out_5; + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, true, true); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + (dma_addr_t) cam->vf_bufs[0]); + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + (dma_addr_t) cam->vf_bufs[1]); + } + cam->vf_bufs_size[0] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[0], + (dma_addr_t *) & + cam->vf_bufs[0], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_4; + } + cam->vf_bufs_size[1] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[1], + (dma_addr_t *) & + cam->vf_bufs[1], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + pr_debug("vf_bufs %x %x\n", cam->vf_bufs[0], cam->vf_bufs[1]); + + if (cam->vf_rotation >= IPU_ROTATE_VERT_FLIP) { + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + IPU_ROTATE_NONE, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + goto out_3; + } + + err = ipu_init_channel(MEM_ROT_VF_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM channel\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->vf_rotation, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM input buffer\n"); + goto out_2; + } + + if (cam->vf_rotation < IPU_ROTATE_90_RIGHT) { + temp = vf.csi_prp_vf_mem.out_width; + vf.csi_prp_vf_mem.out_width = + vf.csi_prp_vf_mem.out_height; + vf.csi_prp_vf_mem.out_height = temp; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + IPU_ROTATE_NONE, + fbi->fix.smem_start + + (fbi->fix.line_length * + fbi->var.yres), + fbi->fix.smem_start, 0, 0); + + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + + err = ipu_link_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); + if (err < 0) { + printk(KERN_ERR + "Error link CSI_PRP_VF_MEM-MEM_ROT_VF_MEM\n"); + goto out_2; + } + + err = ipu_link_channels(MEM_ROT_VF_MEM, MEM_FG_SYNC); + if (err < 0) { + printk(KERN_ERR + "Error link MEM_ROT_VF_MEM-MEM_FG_SYNC\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(MEM_ROT_VF_MEM); + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 1); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } else { + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + format, cam->win.w.width, + cam->win.w.height, + cam->win.w.width, + cam->vf_rotation, + fbi->fix.smem_start + + (fbi->fix.line_length * + fbi->var.yres), + fbi->fix.smem_start, 0, 0); + if (err != 0) { + printk(KERN_ERR "Error initializing CSI_PRP_VF_MEM\n"); + goto out_4; + } + + err = ipu_link_channels(CSI_PRP_VF_MEM, MEM_FG_SYNC); + if (err < 0) { + printk(KERN_ERR "Error linking ipu channels\n"); + goto out_4; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } + + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_UNBLANK); + release_console_sem(); + + cam->overlay_active = true; + return err; + +out_1: + ipu_unlink_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); +out_2: + if (cam->vf_rotation >= IPU_ROTATE_VERT_FLIP) { + ipu_uninit_channel(MEM_ROT_VF_MEM); + } +out_3: + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + (dma_addr_t) cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + (dma_addr_t) cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } +out_4: + ipu_uninit_channel(CSI_PRP_VF_MEM); +out_5: + return err; +} + +/*! + * prpvf_stop - stop the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0, i = 0; + struct fb_info *fbi = NULL; + + if (cam->overlay_active == false) + return 0; + + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strcmp(idstr, "DISP3 FG") == 0) + fbi = registered_fb[i]; + } + + if (fbi == NULL) { + printk(KERN_ERR "DISP3 FG fb not found\n"); + return -EPERM; + } + + ipu_disp_set_window_pos(MEM_FG_SYNC, 0, 0); + + if (cam->vf_rotation >= IPU_ROTATE_VERT_FLIP) { + ipu_unlink_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); + ipu_unlink_channels(MEM_ROT_VF_MEM, MEM_FG_SYNC); + } else { + ipu_unlink_channels(CSI_PRP_VF_MEM, MEM_FG_SYNC); + } + + ipu_disable_channel(CSI_PRP_VF_MEM, true); + + if (cam->vf_rotation >= IPU_ROTATE_VERT_FLIP) { + ipu_disable_channel(MEM_ROT_VF_MEM, true); + ipu_uninit_channel(MEM_ROT_VF_MEM); + } + ipu_uninit_channel(CSI_PRP_VF_MEM); + + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_POWERDOWN); + release_console_sem(); + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, false, false); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + (dma_addr_t) cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + (dma_addr_t) cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + + cam->overlay_active = false; + return err; +} + +/*! + * function to select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_sdc_select(void *private) +{ + cam_data *cam; + int err = 0; + if (private) { + cam = (cam_data *) private; + cam->vf_start_sdc = prpvf_start; + cam->vf_stop_sdc = prpvf_stop; + cam->overlay_active = false; + } else + err = -EIO; + + return err; +} + +/*! + * function to de-select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return int + */ +int prp_vf_sdc_deselect(void *private) +{ + cam_data *cam; + int err = 0; + err = prpvf_stop(private); + + if (private) { + cam = (cam_data *) private; + cam->vf_start_sdc = NULL; + cam->vf_stop_sdc = NULL; + } + return err; +} + +/*! + * Init viewfinder task. + * + * @return Error code indicating success or failure + */ +__init int prp_vf_sdc_init(void) +{ + return 0; +} + +/*! + * Deinit viewfinder task. + * + * @return Error code indicating success or failure + */ +void __exit prp_vf_sdc_exit(void) +{ +} + +module_init(prp_vf_sdc_init); +module_exit(prp_vf_sdc_exit); + +EXPORT_SYMBOL(prp_vf_sdc_select); +EXPORT_SYMBOL(prp_vf_sdc_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP VF SDC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_prp_vf_sdc_bg.c b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc_bg.c new file mode 100644 index 000000000000..4035c7664022 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc_bg.c @@ -0,0 +1,413 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_vf_sdc_bg.c + * + * @brief IPU Use case for PRP-VF back-ground + * + * @ingroup IPU + */ +#include <linux/dma-mapping.h> +#include <linux/fb.h> +#include <linux/ipu.h> +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +static int buffer_num = 0; +static int buffer_ready = 0; + +/* + * Function definitions + */ + +/*! + * SDC V-Sync callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prpvf_sdc_vsync_callback(int irq, void *dev_id) +{ + pr_debug("buffer_ready %d buffer_num %d\n", buffer_ready, buffer_num); + if (buffer_ready > 0) { + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0); + buffer_ready--; + } + + return IRQ_HANDLED; +} + +/*! + * VF EOF callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prpvf_vf_eof_callback(int irq, void *dev_id) +{ + pr_debug("buffer_ready %d buffer_num %d\n", buffer_ready, buffer_num); + + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, buffer_num); + + buffer_num = (buffer_num == 0) ? 1 : 0; + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, buffer_num); + buffer_ready++; + return IRQ_HANDLED; +} + +/*! + * prpvf_start - start the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_start(void *private) +{ + cam_data *cam = (cam_data *) private; + ipu_channel_params_t vf; + u32 format; + u32 offset; + u32 bpp, size = 3; + int err = 0; + + if (!cam) { + printk(KERN_ERR "private is NULL\n"); + return -EIO; + } + + if (cam->overlay_active == true) { + pr_debug("already start.\n"); + return 0; + } + + format = cam->v4l2_fb.fmt.pixelformat; + if (cam->v4l2_fb.fmt.pixelformat == IPU_PIX_FMT_BGR24) { + bpp = 3, size = 3; + pr_info("BGR24\n"); + } else if (cam->v4l2_fb.fmt.pixelformat == IPU_PIX_FMT_RGB565) { + bpp = 2, size = 2; + pr_info("RGB565\n"); + } else if (cam->v4l2_fb.fmt.pixelformat == IPU_PIX_FMT_BGR32) { + bpp = 4, size = 4; + pr_info("BGR32\n"); + } else { + printk(KERN_ERR + "unsupported fix format from the framebuffer.\n"); + return -EINVAL; + } + + offset = cam->v4l2_fb.fmt.bytesperline * cam->win.w.top + + size * cam->win.w.left; + + if (cam->v4l2_fb.base == 0) { + printk(KERN_ERR "invalid frame buffer address.\n"); + } else { + offset += (u32) cam->v4l2_fb.base; + } + + memset(&vf, 0, sizeof(ipu_channel_params_t)); + ipu_csi_get_window_size(&vf.csi_prp_vf_mem.in_width, + &vf.csi_prp_vf_mem.in_height, cam->csi); + vf.csi_prp_vf_mem.in_pixel_fmt = IPU_PIX_FMT_UYVY; + vf.csi_prp_vf_mem.out_width = cam->win.w.width; + vf.csi_prp_vf_mem.out_height = cam->win.w.height; + vf.csi_prp_vf_mem.csi = cam->csi; + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + vf.csi_prp_vf_mem.out_width = cam->win.w.height; + vf.csi_prp_vf_mem.out_height = cam->win.w.width; + } + vf.csi_prp_vf_mem.out_pixel_fmt = format; + size = cam->win.w.width * cam->win.w.height * size; + + err = ipu_init_channel(CSI_PRP_VF_MEM, &vf); + if (err != 0) + goto out_4; + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, true, true); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + } + cam->vf_bufs_size[0] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[0], + &cam->vf_bufs[0], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + cam->vf_bufs_size[1] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[1], + &cam->vf_bufs[1], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + format, vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + IPU_ROTATE_NONE, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error initializing CSI_PRP_VF_MEM\n"); + goto out_3; + } + err = ipu_init_channel(MEM_ROT_VF_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM channel\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, + format, vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->vf_rotation, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM input buffer\n"); + goto out_2; + } + + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->overlay_fb->var.xres * bpp, + IPU_ROTATE_NONE, offset, 0, 0, 0); + + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + } else { + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + cam->overlay_fb->var.xres * bpp, + IPU_ROTATE_NONE, offset, 0, 0, 0); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + } + + ipu_clear_irq(IPU_IRQ_PRP_VF_OUT_EOF); + err = ipu_request_irq(IPU_IRQ_PRP_VF_OUT_EOF, prpvf_vf_eof_callback, + 0, "Mxc Camera", cam); + if (err != 0) { + printk(KERN_ERR + "Error registering IPU_IRQ_PRP_VF_OUT_EOF irq.\n"); + goto out_2; + } + + ipu_clear_irq(IPU_IRQ_BG_SF_END); + err = ipu_request_irq(IPU_IRQ_BG_SF_END, prpvf_sdc_vsync_callback, + 0, "Mxc Camera", NULL); + if (err != 0) { + printk(KERN_ERR "Error registering IPU_IRQ_BG_SF_END irq.\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(MEM_ROT_VF_MEM); + + buffer_num = 0; + buffer_ready = 0; + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + + cam->overlay_active = true; + return err; + + out_1: + ipu_free_irq(IPU_IRQ_PRP_VF_OUT_EOF, NULL); + out_2: + ipu_uninit_channel(MEM_ROT_VF_MEM); + out_3: + ipu_uninit_channel(CSI_PRP_VF_MEM); + out_4: + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + return err; +} + +/*! + * prpvf_stop - stop the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam->overlay_active == false) + return 0; + + ipu_free_irq(IPU_IRQ_BG_SF_END, NULL); + + ipu_free_irq(IPU_IRQ_PRP_VF_OUT_EOF, cam); + + ipu_disable_channel(CSI_PRP_VF_MEM, true); + ipu_disable_channel(MEM_ROT_VF_MEM, true); + ipu_uninit_channel(CSI_PRP_VF_MEM); + ipu_uninit_channel(MEM_ROT_VF_MEM); + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, false, false); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + + buffer_num = 0; + buffer_ready = 0; + cam->overlay_active = false; + return 0; +} + +/*! + * function to select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_sdc_select_bg(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->vf_start_sdc = prpvf_start; + cam->vf_stop_sdc = prpvf_stop; + cam->overlay_active = false; + } + + return 0; +} + +/*! + * function to de-select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_sdc_deselect_bg(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + err = prpvf_stop(private); + + if (cam) { + cam->vf_start_sdc = NULL; + cam->vf_stop_sdc = NULL; + } + return err; +} + +/*! + * Init viewfinder task. + * + * @return Error code indicating success or failure + */ +__init int prp_vf_sdc_init_bg(void) +{ + return 0; +} + +/*! + * Deinit viewfinder task. + * + * @return Error code indicating success or failure + */ +void __exit prp_vf_sdc_exit_bg(void) +{ +} + +module_init(prp_vf_sdc_init_bg); +module_exit(prp_vf_sdc_exit_bg); + +EXPORT_SYMBOL(prp_vf_sdc_select_bg); +EXPORT_SYMBOL(prp_vf_sdc_deselect_bg); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP VF SDC Backgroud Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_still.c b/drivers/media/video/mxc/capture/ipu_still.c new file mode 100644 index 000000000000..34cea8609c95 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_still.c @@ -0,0 +1,252 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_still.c + * + * @brief IPU Use case for still image capture + * + * @ingroup IPU + */ + +#include <linux/ipu.h> +#include <linux/semaphore.h> +#include <linux/ipu.h> +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +static int callback_eof_flag; + +#ifdef CONFIG_MXC_IPU_V1 +static int callback_flag; +/* + * Function definitions + */ +/*! + * CSI EOF callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prp_csi_eof_callback(int irq, void *dev_id) +{ + if (callback_flag == 2) { + ipu_select_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_enable_channel(CSI_MEM); + } + + callback_flag++; + return IRQ_HANDLED; +} +#endif + +/*! + * CSI callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prp_still_callback(int irq, void *dev_id) +{ + cam_data *cam = (cam_data *) dev_id; + + callback_eof_flag++; + if (callback_eof_flag < 5) + ipu_select_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, 0); + else { + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + } + + return IRQ_HANDLED; +} + +/*! + * start csi->mem task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_still_start(void *private) +{ + cam_data *cam = (cam_data *) private; + u32 pixel_fmt; + int err; + ipu_channel_params_t params; + + if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) + pixel_fmt = IPU_PIX_FMT_YUV420P; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) + pixel_fmt = IPU_PIX_FMT_YUV422P; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) + pixel_fmt = IPU_PIX_FMT_UYVY; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24) + pixel_fmt = IPU_PIX_FMT_BGR24; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) + pixel_fmt = IPU_PIX_FMT_RGB24; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565) + pixel_fmt = IPU_PIX_FMT_RGB565; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR32) + pixel_fmt = IPU_PIX_FMT_BGR32; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB32) + pixel_fmt = IPU_PIX_FMT_RGB32; + else { + printk(KERN_ERR "format not supported\n"); + return -EINVAL; + } + + ipu_csi_enable_mclk_if(CSI_MCLK_RAW, cam->csi, true, true); + + memset(¶ms, 0, sizeof(params)); + err = ipu_init_channel(CSI_MEM, ¶ms); + if (err != 0) + return err; + + err = ipu_init_channel_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, + pixel_fmt, cam->v2f.fmt.pix.width, + cam->v2f.fmt.pix.height, + cam->v2f.fmt.pix.width, IPU_ROTATE_NONE, + cam->still_buf, 0, 0, 0); + if (err != 0) + return err; + +#ifdef CONFIG_MXC_IPU_V1 + err = ipu_request_irq(IPU_IRQ_SENSOR_OUT_EOF, prp_still_callback, + 0, "Mxc Camera", cam); + if (err != 0) { + printk(KERN_ERR "Error registering irq.\n"); + return err; + } + callback_flag = 0; + callback_eof_flag = 0; + err = ipu_request_irq(IPU_IRQ_SENSOR_EOF, prp_csi_eof_callback, + 0, "Mxc Camera", NULL); + if (err != 0) { + printk(KERN_ERR "Error IPU_IRQ_SENSOR_EOF \n"); + return err; + } +#else + ipu_clear_irq(IPU_IRQ_CSI0_OUT_EOF); + err = ipu_request_irq(IPU_IRQ_CSI0_OUT_EOF, prp_still_callback, + 0, "Mxc Camera", cam); + if (err != 0) { + printk(KERN_ERR "Error registering irq.\n"); + return err; + } + + callback_eof_flag = 0; + + ipu_select_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_enable_channel(CSI_MEM); +#endif + + return err; +} + +/*! + * stop csi->mem encoder task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_still_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + +#ifdef CONFIG_MXC_IPU_V1 + ipu_free_irq(IPU_IRQ_SENSOR_EOF, NULL); + ipu_free_irq(IPU_IRQ_SENSOR_OUT_EOF, cam); +#else + ipu_free_irq(IPU_IRQ_CSI0_OUT_EOF, cam); +#endif + + ipu_disable_channel(CSI_MEM, true); + ipu_uninit_channel(CSI_MEM); + ipu_csi_enable_mclk_if(CSI_MCLK_RAW, cam->csi, false, false); + + return err; +} + +/*! + * function to select CSI_MEM as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +int prp_still_select(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->csi_start = prp_still_start; + cam->csi_stop = prp_still_stop; + } + + return 0; +} + +/*! + * function to de-select CSI_MEM as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +int prp_still_deselect(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + err = prp_still_stop(cam); + + if (cam) { + cam->csi_start = NULL; + cam->csi_stop = NULL; + } + + return err; +} + +/*! + * Init the Encorder channels + * + * @return Error code indicating success or failure + */ +__init int prp_still_init(void) +{ + return 0; +} + +/*! + * Deinit the Encorder channels + * + */ +void __exit prp_still_exit(void) +{ +} + +module_init(prp_still_init); +module_exit(prp_still_exit); + +EXPORT_SYMBOL(prp_still_select); +EXPORT_SYMBOL(prp_still_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP STILL IMAGE Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/mc521da.c b/drivers/media/video/mxc/capture/mc521da.c new file mode 100644 index 000000000000..a8ff84fb4c84 --- /dev/null +++ b/drivers/media/video/mxc/capture/mc521da.c @@ -0,0 +1,648 @@ +/* + * Copyright 2006-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc521da.c + * + * @brief MC521DA camera driver functions + * + * @ingroup Camera + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include "mxc_v4l2_capture.h" + +#define MC521DA_I2C_ADDRESS 0x22 +#define MC521DA_TERM 0xFF + +typedef struct { + u16 width; + u16 height; +} mc521da_image_format; + +struct mc521da_reg { + u8 reg; + u8 val; +}; + +static sensor_interface *interface_param = NULL; + +static mc521da_image_format format[2] = { + { + .width = 1600, + .height = 1200, + }, + { + .width = 640, + .height = 480, + }, +}; + +const static struct mc521da_reg mc521da_initial[] = { + /*---------------------------------------------------------- + * Sensor Setting Start + *---------------------------------------------------------- + */ + {0xff, 0x01}, /* Sensor setting start */ + {0x01, 0x10}, /* Wavetable script, generated by waveman */ + {0x10, 0x64}, + {0x03, 0x00}, {0x04, 0x06}, {0x05, 0x30}, {0x06, 0x02}, {0x08, 0x00}, + {0x03, 0x01}, {0x04, 0x41}, {0x05, 0x70}, {0x06, 0x03}, {0x08, 0x00}, + {0x03, 0x02}, {0x04, 0x55}, {0x05, 0x30}, {0x06, 0x03}, {0x08, 0x00}, + {0x03, 0x03}, {0x04, 0x5A}, {0x05, 0x30}, {0x06, 0x02}, {0x08, 0x00}, + {0x03, 0x04}, {0x04, 0x7A}, {0x05, 0x30}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x05}, {0x04, 0x9C}, {0x05, 0x30}, {0x06, 0x0F}, {0x08, 0x00}, + {0x03, 0x06}, {0x04, 0x73}, {0x05, 0x31}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x07}, {0x04, 0x2D}, {0x05, 0x3B}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x08}, {0x04, 0x32}, {0x05, 0x33}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x09}, {0x04, 0x67}, {0x05, 0x63}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x0a}, {0x04, 0x6C}, {0x05, 0x23}, {0x06, 0x0E}, {0x08, 0x00}, + {0x03, 0x0b}, {0x04, 0x71}, {0x05, 0x23}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x0c}, {0x04, 0x30}, {0x05, 0x2F}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x0d}, {0x04, 0x00}, {0x05, 0x00}, {0x06, 0x06}, {0x08, 0x00}, + {0x07, 0x0e}, + + /* Start Address */ + {0x10, 0x64}, {0x14, 0x10}, {0x15, 0x00}, + + /* SYNC */ + {0x18, 0x40}, {0x19, 0x00}, {0x1A, 0x03}, {0x1B, 0x00}, + + /* X-Y Mirror */ + {0x11, 0x00}, {0xda, 0x00}, /* X mirror OFF, Y Mirror OFF */ + + /* Frame height */ + {0x1c, 0x13}, {0x1d, 0x04}, {0x0e, 0x4b}, {0x0f, 0x05}, + {0x9e, 0x04}, {0x9d, 0xc6}, {0xcc, 0x14}, {0xcd, 0x05}, + + /* Frame width */ + {0x0c, 0x35}, {0x0d, 0x07}, {0x9b, 0x10}, {0x9c, 0x07}, + {0x93, 0x21}, + + {0x01, 0x01}, {0x40, 0x00}, {0x41, 0x00}, {0x42, 0xf0}, + {0x43, 0x03}, {0x44, 0x0a}, {0x45, 0x00}, {0x3b, 0x40}, + {0x38, 0x18}, {0x3c, 0x00}, {0x20, 0x00}, {0x21, 0x01}, + {0x22, 0x00}, {0x23, 0x01}, {0x24, 0x00}, {0x25, 0x01}, + {0x26, 0x00}, {0x27, 0x01}, {0xb9, 0x04}, {0xb8, 0xc3}, + {0xbb, 0x04}, {0xba, 0xc3}, {0xbf, 0x04}, {0xbe, 0xc3}, + + /* Ramp */ + {0x57, 0x07}, {0x56, 0xd6}, {0x55, 0x03}, {0x54, 0x74}, + {0x9f, 0x99}, {0x94, 0x80}, {0x91, 0x78}, {0x92, 0x8b}, + + /* Output Mode */ + {0x52, 0x10}, {0x51, 0x00}, + + /* Analog Gain and Output driver */ + {0x28, 0x00}, {0xdd, 0x82}, {0xdb, 0x00}, {0xdc, 0x00}, + + /* Update */ + {0x00, 0x84}, + + /* PLL ADC clock = 75 MHz */ + {0xb5, 0x60}, {0xb4, 0x02}, {0xb5, 0x20}, + + /*----------------------------------------------*/ + /* ISP Setting Start */ + /*----------------------------------------------*/ + {0xff, 0x02}, + {0x01, 0xbd}, {0x02, 0xf8}, {0x03, 0x3a}, {0x04, 0x00}, {0x0e, 0x00}, + + /* Output mode */ + {0x88, 0x00}, {0x87, 0x11}, + + /* Threshold */ + {0xb6, 0x1b}, {0x0d, 0xc0}, {0x24, 0x00}, {0x25, 0x00}, {0x26, 0x00}, + + /* Image Effect */ + {0x3f, 0x80}, {0x40, 0x00}, {0x41, 0x00}, {0x42, 0x80}, {0x43, 0x00}, + {0x44, 0x00}, {0x45, 0x00}, {0x46, 0x00}, {0x56, 0x80}, {0x57, 0x20}, + {0x58, 0x20}, {0x59, 0x02}, {0x5a, 0x00}, {0x5b, 0x78}, {0x5c, 0x7c}, + {0x5d, 0x84}, {0x5e, 0x85}, {0x5f, 0x78}, {0x60, 0x7e}, {0x61, 0x82}, + {0x62, 0x85}, {0x63, 0x00}, {0x64, 0x80}, {0x65, 0x00}, {0x66, 0x80}, + {0x67, 0x80}, {0x68, 0x80}, + + /* Auto Focus */ + {0x6e, 0x02}, {0x6f, 0xe5}, {0x70, 0x08}, {0x71, 0x01}, {0x72, 0x00}, + + /* Decimator */ + {0x78, 0xff}, {0x79, 0xff}, {0x7a, 0x70}, {0x7b, 0x00}, {0x7c, 0x00}, + {0x7d, 0x00}, {0x7e, 0xc8}, {0x7f, 0xc8}, {0x80, 0x96}, {0x81, 0x96}, + {0x82, 0x00}, {0x83, 0x00}, {0x84, 0x00}, {0x85, 0x00}, {0x86, 0x00}, + + /* Luminance Info */ + {0xf9, 0x20}, {0xb7, 0x7f}, {0xb8, 0x28}, {0xb9, 0x08}, + {0xf9, 0xa0}, {0xb7, 0x10}, {0xb9, 0x00}, + {0xf9, 0x40}, {0xb7, 0x7f}, {0xb8, 0x28}, {0xb9, 0x08}, + {0xf9, 0xc0}, {0xb7, 0x08}, {0xb9, 0x00}, + {0xf9, 0x60}, {0xb7, 0x7f}, {0xb8, 0x28}, {0xb9, 0x08}, + {0xf9, 0xe0}, {0xb7, 0x05}, {0xb9, 0x00}, + {0xf9, 0x00}, {0xb7, 0x03}, {0xb8, 0x2d}, {0xb9, 0xcd}, + {0xf9, 0x80}, {0xb7, 0x02}, {0xb9, 0x00}, + + /* AE */ + {0x8a, 0x00}, {0x89, 0xc0}, {0x8c, 0x32}, {0x8d, 0x96}, {0x8e, 0x25}, + {0x8f, 0x70}, {0x90, 0x12}, {0x91, 0x41}, {0x9e, 0x2e}, {0x9f, 0x2e}, + {0xa0, 0x0b}, {0xa1, 0x71}, {0xa2, 0xb0}, {0xa3, 0x09}, {0xa4, 0x89}, + {0xa5, 0x68}, {0xa6, 0x1a}, {0xa7, 0xb3}, {0xa8, 0xf0}, {0xa9, 0x19}, + {0xaa, 0x6a}, {0xab, 0x6b}, {0xac, 0x01}, {0xad, 0xe8}, {0xae, 0x48}, + {0xaf, 0x01}, {0xb0, 0x96}, {0xb1, 0xe6}, {0xb2, 0x03}, {0xb3, 0x00}, + {0xb4, 0x10}, {0xb5, 0x00}, {0xb6, 0x04}, {0xba, 0x44}, {0xbb, 0x3a}, + {0xbc, 0x01}, {0xbd, 0x08}, {0xbe, 0xa0}, {0xbf, 0x01}, {0xc0, 0x82}, + {0x8a, 0xe1}, {0x8b, 0x8c}, + + /* AWB */ + {0xc8, 0x00}, {0xc9, 0x00}, {0xca, 0x40}, {0xcb, 0xB0}, {0xcc, 0x40}, + {0xcd, 0xff}, {0xce, 0x19}, {0xcf, 0x40}, {0xd0, 0x01}, {0xd1, 0x43}, + {0xd2, 0x80}, {0xd3, 0x80}, {0xd4, 0xf1}, {0xdf, 0x00}, {0xe0, 0x8f}, + {0xe1, 0x8f}, {0xe2, 0x53}, {0xe3, 0x97}, {0xe4, 0x1f}, {0xe5, 0x3b}, + {0xe6, 0x9c}, {0xe7, 0x2e}, {0xe8, 0x03}, {0xe9, 0x02}, + + /* Neutral CCM */ + {0xfa, 0x00}, {0xd5, 0x3f}, {0xd6, 0x8c}, {0xd7, 0x43}, {0xd8, 0x08}, + {0xd9, 0x27}, {0xda, 0x7e}, {0xdb, 0x17}, {0xdc, 0x1a}, {0xdd, 0x47}, + {0xde, 0xa1}, + + /* Blue CCM */ + {0xfa, 0x01}, {0xd5, 0x3f}, {0xd6, 0x77}, {0xd7, 0x34}, {0xd8, 0x03}, + {0xd9, 0x18}, {0xda, 0x6e}, {0xdb, 0x16}, {0xdc, 0x0f}, {0xdd, 0x29}, + {0xde, 0x77}, + + /* Red CCM */ + {0xfa, 0x02}, {0xd5, 0x3f}, {0xd6, 0x7d}, {0xd7, 0x2f}, {0xd8, 0x0e}, + {0xd9, 0x1e}, {0xda, 0x76}, {0xdb, 0x18}, {0xdc, 0x29}, {0xdd, 0x51}, + {0xde, 0xba}, + + /* AWB */ + {0xea, 0x00}, {0xeb, 0x1a}, {0xc8, 0x33}, {0xc9, 0xc2}, + + {0xed, 0x02}, {0xee, 0x02}, + + /* AFD */ + {0xf0, 0x11}, {0xf1, 0x03}, {0xf2, 0x05}, {0xf5, 0x05}, {0xf6, 0x32}, + {0xf7, 0x32}, + + /* Lens Shading */ + {0xf9, 0x00}, {0x05, 0x04}, {0x06, 0xff}, {0x07, 0xf2}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xf2}, {0x0b, 0xff}, {0x0c, 0xff}, + {0xf9, 0x01}, {0x05, 0x04}, {0x06, 0xff}, {0x07, 0x8b}, {0x08, 0x16}, + {0x09, 0x16}, {0x0a, 0x8b}, {0x0b, 0xff}, {0x0c, 0xe0}, + {0xf9, 0x02}, {0x05, 0x04}, {0x06, 0xff}, {0x07, 0x8b}, {0x08, 0x16}, + {0x09, 0x16}, {0x0a, 0x8b}, {0x0b, 0xff}, {0x0c, 0xe0}, + {0xf9, 0x03}, {0x05, 0x04}, {0x06, 0xff}, {0x07, 0x7c}, {0x08, 0x26}, + {0x09, 0x26}, {0x0a, 0x7c}, {0x0b, 0xd0}, {0x0c, 0xe0}, + {0xf9, 0x04}, {0x05, 0x0d}, {0x06, 0x40}, {0x07, 0xa0}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xa0}, {0x0b, 0x40}, {0x0c, 0xe0}, + {0xf9, 0x05}, {0x05, 0x0d}, {0x06, 0x40}, {0x07, 0xa0}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xa0}, {0x0b, 0x40}, {0x0c, 0xa0}, + {0xf9, 0x06}, {0x05, 0x0d}, {0x06, 0x40}, {0x07, 0xa0}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xa0}, {0x0b, 0x40}, {0x0c, 0xa0}, + {0xf9, 0x07}, {0x05, 0x0d}, {0x06, 0x40}, {0x07, 0xa0}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xa0}, {0x0b, 0x40}, {0x0c, 0xa0}, + + /* Edge setting */ + {0x73, 0x68}, {0x74, 0x40}, {0x75, 0x00}, {0x76, 0xff}, {0x77, 0x80}, + {0x4f, 0x80}, {0x50, 0x82}, {0x51, 0x82}, {0x52, 0x08}, + + /* Interpolation Setting */ + {0x23, 0x7f}, {0x22, 0x08}, {0x18, 0xff}, {0x19, 0x00}, + {0x40, 0x00}, {0x53, 0xff}, {0x54, 0x0a}, {0x55, 0xc2}, + {0x1b, 0x18}, + + {0xfa, 0x00}, {0x15, 0x0c}, {0x22, 0x00}, {0x0e, 0xef}, {0x1f, 0x1d}, + {0x20, 0x2d}, {0x1c, 0x01}, {0x1d, 0x02}, {0x1e, 0x03}, {0x0e, 0xee}, + {0x12, 0x10}, {0x16, 0x10}, {0x17, 0x02}, {0x1a, 0x01}, + {0xfa, 0x04}, {0x0e, 0xef}, {0x1c, 0x01}, {0x1d, 0x02}, {0x1e, 0x03}, + {0x1f, 0x11}, {0x20, 0x11}, {0x0e, 0xee}, {0x12, 0x03}, {0x16, 0x10}, + {0x17, 0x02}, {0x1a, 0xee}, + {0xfa, 0x08}, {0x0e, 0xef}, {0x1c, 0x01}, {0x1d, 0x02}, {0x1e, 0x03}, + {0x1f, 0x00}, {0x20, 0x00}, {0x0e, 0xee}, {0x12, 0x03}, {0x16, 0x10}, + {0x17, 0x02}, {0x1a, 0x22}, + + /* Gamma Correction */ + {0x27, 0x62}, {0x28, 0x00}, {0x27, 0x62}, {0x28, 0x00}, {0x29, 0x00}, + {0x2a, 0x00}, {0x2f, 0x03}, {0x30, 0x10}, {0x31, 0x2b}, {0x32, 0x50}, + {0x33, 0x70}, {0x34, 0x90}, {0x35, 0xB0}, {0x36, 0xD0}, {0x37, 0x00}, + {0x38, 0x18}, {0x39, 0x57}, {0x3a, 0x89}, {0x3b, 0xac}, {0x3c, 0xc9}, + {0x3d, 0xde}, {0x3e, 0xef}, {0x2b, 0x00}, {0x2c, 0x00}, {0x2d, 0x40}, + {0x2e, 0xab}, + + /* Contrast */ + {0x47, 0x10}, {0x48, 0x1f}, {0x49, 0xe3}, {0x4a, 0xf0}, {0x4b, 0x08}, + {0x4c, 0x14}, {0x4d, 0xe9}, {0x4e, 0xf5}, {0x98, 0x8a}, + + {0xfa, 0x00}, + {MC521DA_TERM, MC521DA_TERM} +}; + +static int mc521da_attach(struct i2c_adapter *adapter); +static int mc521da_detach(struct i2c_client *client); + +static struct i2c_driver mc521da_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "MC521DA Client", + }, + .attach_adapter = mc521da_attach, + .detach_client = mc521da_detach, +}; + +static struct i2c_client mc521da_i2c_client = { + .name = "MC521DA I2C dev", + .addr = MC521DA_I2C_ADDRESS, + .driver = &mc521da_i2c_driver, +}; + +static inline int mc521da_read_reg(u8 reg) +{ + return i2c_smbus_read_byte_data(&mc521da_i2c_client, reg); +} + +static inline int mc521da_write_reg(u8 reg, u8 val) +{ + return i2c_smbus_write_byte_data(&mc521da_i2c_client, reg, val); +} + +static int mc521da_write_regs(const struct mc521da_reg reglist[]) +{ + int err; + const struct mc521da_reg *next = reglist; + + while (!((next->reg == MC521DA_TERM) && (next->val == MC521DA_TERM))) { + err = mc521da_write_reg(next->reg, next->val); + if (err) { + return err; + } + next++; + } + return 0; +} + +/*! + * mc521da sensor downscale function + * @param downscale bool + * @return Error code indicating success or failure + */ +static u8 mc521da_sensor_downscale(bool downscale) +{ + u8 data; + u32 i = 0; + + if (downscale == true) { + // VGA + mc521da_write_reg(0xff, 0x01); + + mc521da_write_reg(0x52, 0x30); + mc521da_write_reg(0x51, 0x00); + + mc521da_write_reg(0xda, 0x01); + mc521da_write_reg(0x00, 0x8C); + + /* Wait for changes to take effect */ + while (i < 256) { + i++; + data = mc521da_read_reg(0x00); + if ((data & 0x80) == 0) + break; + msleep(5); + } + + /* ISP */ + mc521da_write_reg(0xff, 0x02); + + mc521da_write_reg(0x03, 0x3b); /* Enable Decimator */ + + mc521da_write_reg(0x7a, 0x74); + mc521da_write_reg(0x7b, 0x01); + mc521da_write_reg(0x7e, 0x50); + mc521da_write_reg(0x7f, 0x50); + mc521da_write_reg(0x80, 0x3c); + mc521da_write_reg(0x81, 0x3c); + } else { + //UXGA + mc521da_write_reg(0xff, 0x01); + mc521da_write_reg(0x52, 0x10); + mc521da_write_reg(0x51, 0x00); + mc521da_write_reg(0xda, 0x00); + + /* update */ + mc521da_write_reg(0x00, 0x84); + + /* Wait for changes to take effect */ + while (i < 256) { + i++; + data = mc521da_read_reg(0x00); + if ((data & 0x80) == 0) + break; + msleep(5); + } + + /* ISP */ + mc521da_write_reg(0xff, 0x02); + + mc521da_write_reg(0x03, 0x3a); + + mc521da_write_reg(0x7a, 0x70); + mc521da_write_reg(0x7b, 0x00); + mc521da_write_reg(0x7e, 0xc8); + mc521da_write_reg(0x7f, 0xc8); + mc521da_write_reg(0x80, 0x96); + mc521da_write_reg(0x81, 0x96); + } + + return 0; +} + +/*! + * mc521da sensor interface Initialization + * @param param sensor_interface * + * @param width u32 + * @param height u32 + * @return None + */ +static void mc521da_interface(sensor_interface * param, u32 width, u32 height) +{ + param->clk_mode = 0x0; //gated + param->pixclk_pol = 0x0; + param->data_width = 0x1; + param->data_pol = 0x0; + param->ext_vsync = 0x0; + param->Vsync_pol = 0x0; + param->Hsync_pol = 0x0; + param->width = width - 1; + param->height = height - 1; + param->active_width = width; + param->active_height = height; + param->pixel_fmt = IPU_PIX_FMT_UYVY; +} + +extern void gpio_sensor_reset(bool flag); + +/*! + * mc521da Reset function + * + * @return None + */ +static sensor_interface *mc521da_reset(void) +{ + if (interface_param == NULL) + return NULL; + + mc521da_interface(interface_param, format[1].width, format[1].height); + set_mclk_rate(&interface_param->mclk); + + gpio_sensor_reset(true); + msleep(10); + gpio_sensor_reset(false); + msleep(50); + + return interface_param; +} + +/*! + * mc521da sensor configuration + * + * @param frame_rate int * + * @param high_quality int + * @return sensor_interface * + */ +static sensor_interface *mc521da_config(int *frame_rate, int high_quality) +{ + int num_clock_per_row, err; + int max_rate = 0; + int index = 1; + u16 frame_height; + + if (high_quality == 1) + index = 0; + + err = mc521da_write_regs(mc521da_initial); + if (err) { + /* Reduce the MCLK */ + interface_param->mclk = 20000000; + mc521da_reset(); + + printk(KERN_INFO "mc521da: mclk reduced\n"); + mc521da_write_regs(mc521da_initial); + } + + mc521da_interface(interface_param, format[index].width, + format[index].height); + + if (index == 0) { + mc521da_sensor_downscale(false); + } else { + mc521da_sensor_downscale(true); + } + + num_clock_per_row = 1845; + max_rate = interface_param->mclk * 3 * (index + 1) + / (2 * num_clock_per_row * 1300); + + if ((*frame_rate > max_rate) || (*frame_rate == 0)) { + *frame_rate = max_rate; + } + + frame_height = 1300 * max_rate / (*frame_rate); + + *frame_rate = interface_param->mclk * 3 * (index + 1) + / (2 * num_clock_per_row * frame_height); + + mc521da_write_reg(0xff, 0x01); + mc521da_write_reg(0xE, frame_height & 0xFF); + mc521da_write_reg(0xF, (frame_height & 0xFF00) >> 8); + mc521da_write_reg(0xCC, frame_height & 0xFF); + mc521da_write_reg(0xCD, (frame_height & 0xFF00) >> 8); + + return interface_param; +} + +/*! + * mc521da sensor set color configuration + * + * @param bright int + * @param saturation int + * @param red int + * @param green int + * @param blue int + * @return None + */ +static void +mc521da_set_color(int bright, int saturation, int red, int green, int blue) +{ + /* Select ISP */ + mc521da_write_reg(0xff, 0x02); + + mc521da_write_reg(0x41, bright); + mc521da_write_reg(0xca, red); + mc521da_write_reg(0xcb, green); + mc521da_write_reg(0xcc, blue); +} + +/*! + * mc521da sensor get color configuration + * + * @param bright int * + * @param saturation int * + * @param red int * + * @param green int * + * @param blue int * + * @return None + */ +static void +mc521da_get_color(int *bright, int *saturation, int *red, int *green, int *blue) +{ + *saturation = 0; + + /* Select ISP */ + mc521da_write_reg(0xff, 0x02); + + *bright = mc521da_read_reg(0x41); + *red = mc521da_read_reg(0xCA); + *green = mc521da_read_reg(0xCB); + *blue = mc521da_read_reg(0xCC); +} + +struct camera_sensor camera_sensor_if = { + set_color:mc521da_set_color, + get_color:mc521da_get_color, + config:mc521da_config, + reset:mc521da_reset, +}; + +/*! + * mc521da I2C detect_client function + * + * @param adapter struct i2c_adapter * + * @param address int + * @param kind int + * + * @return Error code indicating success or failure + */ +static int mc521da_detect_client(struct i2c_adapter *adapter, int address, + int kind) +{ + mc521da_i2c_client.adapter = adapter; + if (i2c_attach_client(&mc521da_i2c_client)) { + mc521da_i2c_client.adapter = NULL; + printk(KERN_ERR "mc521da_attach: i2c_attach_client failed\n"); + return -1; + } + + interface_param = (sensor_interface *) + kmalloc(sizeof(sensor_interface), GFP_KERNEL); + if (!interface_param) { + printk(KERN_ERR "mc521da_attach: kmalloc failed \n"); + return -1; + } + + interface_param->mclk = 25000000; + + printk(KERN_INFO "mc521da Detected\n"); + + return 0; +} + +static unsigned short normal_i2c[] = { MC521DA_I2C_ADDRESS, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static int mc521da_attach(struct i2c_adapter *adap) +{ + uint32_t mclk = 25000000; + struct clk *clk; + int err; + + clk = clk_get(NULL, "csi_clk"); + clk_enable(clk); + set_mclk_rate(&mclk); + + gpio_sensor_reset(true); + msleep(10); + gpio_sensor_reset(false); + msleep(100); + + err = i2c_probe(adap, &addr_data, &mc521da_detect_client); + + clk_disable(clk); + clk_put(clk); + + return err; +} + +/*! + * mc521da I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int mc521da_detach(struct i2c_client *client) +{ + int err; + + if (!mc521da_i2c_client.adapter) + return -1; + + err = i2c_detach_client(&mc521da_i2c_client); + mc521da_i2c_client.adapter = NULL; + + if (interface_param) + kfree(interface_param); + interface_param = NULL; + + return err; +} + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +/*! + * mc521da init function + * + * @return Error code indicating success or failure + */ +static __init int mc521da_init(void) +{ + gpio_sensor_active(); + return i2c_add_driver(&mc521da_i2c_driver); +} + +/*! + * mc521da cleanup function + * + * @return Error code indicating success or failure + */ +static void __exit mc521da_clean(void) +{ + i2c_del_driver(&mc521da_i2c_driver); + gpio_sensor_inactive(); +} + +module_init(mc521da_init); +module_exit(mc521da_clean); + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(camera_sensor_if); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MC521DA Camera Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/mt9v111.c b/drivers/media/video/mxc/capture/mt9v111.c new file mode 100644 index 000000000000..c95a20683924 --- /dev/null +++ b/drivers/media/video/mxc/capture/mt9v111.c @@ -0,0 +1,1076 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mt9v111.c + * + * @brief mt9v111 camera driver functions + * + * @ingroup Camera + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <media/v4l2-int-device.h> +#include "mxc_v4l2_capture.h" +#include "mt9v111.h" + +#ifdef MT9V111_DEBUG +static u16 testpattern = 0; +#endif + +static mt9v111_conf mt9v111_device; + +/*! + * Holds the current frame rate. + */ +static int reset_frame_rate = MT9V111_FRAME_RATE; + +struct sensor { + const struct mt9v111_platform_data *platform_data; + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_captureparm streamcap; + bool on; + + /* control settings */ + int brightness; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + +} mt9v111_data; + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +static int mt9v111_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int mt9v111_remove(struct i2c_client *client); + +static const struct i2c_device_id mt9v111_id[] = { + {"mt9v111", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, mt9v111_id); + +static struct i2c_driver mt9v111_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "mt9v111", + }, + .probe = mt9v111_probe, + .remove = mt9v111_remove, + .id_table = mt9v111_id, +/* To add power management add .suspend and .resume functions */ +}; + +/* + * Function definitions + */ + +#ifdef MT9V111_DEBUG +static inline int mt9v111_read_reg(u8 reg) +{ + int val = i2c_smbus_read_word_data(mt9v111_data.i2c_client, reg); + if (val != -1) + val = cpu_to_be16(val); + return val; +} +#endif + +/*! + * Writes to the register via I2C. + */ +static inline int mt9v111_write_reg(u8 reg, u16 val) +{ + pr_debug("In mt9v111_write_reg (0x%x, 0x%x)\n", reg, val); + pr_debug(" write reg %x val %x.\n", reg, val); + + return i2c_smbus_write_word_data(mt9v111_data.i2c_client, + reg, cpu_to_be16(val)); +} + +/*! + * Initialize mt9v111_sensor_lib + * Libarary for Sensor configuration through I2C + * + * @param coreReg Core Registers + * @param ifpReg IFP Register + * + * @return status + */ +static u8 mt9v111_sensor_lib(mt9v111_coreReg * coreReg, mt9v111_IFPReg * ifpReg) +{ + u8 reg; + u16 data; + u8 error = 0; + + pr_debug("In mt9v111_sensor_lib\n"); + + /* + * setup to IFP registers + */ + reg = MT9V111I_ADDR_SPACE_SEL; + data = ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + /* Operation Mode Control */ + reg = MT9V111I_MODE_CONTROL; + data = ifpReg->modeControl; + mt9v111_write_reg(reg, data); + + /* Output format */ + reg = MT9V111I_FORMAT_CONTROL; + data = ifpReg->formatControl; /* Set bit 12 */ + mt9v111_write_reg(reg, data); + + /* AE limit 4 */ + reg = MT9V111I_SHUTTER_WIDTH_LIMIT_AE; + data = ifpReg->gainLimitAE; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_OUTPUT_FORMAT_CTRL2; + data = ifpReg->outputFormatCtrl2; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_AE_SPEED; + data = ifpReg->AESpeed; + mt9v111_write_reg(reg, data); + + /* output image size */ + reg = MT9V111i_H_PAN; + data = 0x8000 | ifpReg->HPan; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_ZOOM; + data = 0x8000 | ifpReg->HZoom; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_SIZE; + data = 0x8000 | ifpReg->HSize; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_PAN; + data = 0x8000 | ifpReg->VPan; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_ZOOM; + data = 0x8000 | ifpReg->VZoom; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_SIZE; + data = 0x8000 | ifpReg->VSize; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_PAN; + data = ~0x8000 & ifpReg->HPan; + mt9v111_write_reg(reg, data); +#if 0 + reg = MT9V111I_UPPER_SHUTTER_DELAY_LIM; + data = ifpReg->upperShutterDelayLi; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_SHUTTER_60; + data = ifpReg->shutter_width_60; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_SEARCH_FLICK_60; + data = ifpReg->search_flicker_60; + mt9v111_write_reg(reg, data); +#endif + + /* + * setup to sensor core registers + */ + reg = MT9V111I_ADDR_SPACE_SEL; + data = coreReg->addressSelect; + mt9v111_write_reg(reg, data); + + /* enable changes and put the Sync bit on */ + reg = MT9V111S_OUTPUT_CTRL; + data = MT9V111S_OUTCTRL_SYNC | MT9V111S_OUTCTRL_CHIP_ENABLE | 0x3000; + mt9v111_write_reg(reg, data); + + /* min PIXCLK - Default */ + reg = MT9V111S_PIXEL_CLOCK_SPEED; + data = coreReg->pixelClockSpeed; + mt9v111_write_reg(reg, data); + + /* Setup image flipping / Dark rows / row/column skip */ + reg = MT9V111S_READ_MODE; + data = coreReg->readMode; + mt9v111_write_reg(reg, data); + + /* zoom 0 */ + reg = MT9V111S_DIGITAL_ZOOM; + data = coreReg->digitalZoom; + mt9v111_write_reg(reg, data); + + /* min H-blank */ + reg = MT9V111S_HOR_BLANKING; + data = coreReg->horizontalBlanking; + mt9v111_write_reg(reg, data); + + /* min V-blank */ + reg = MT9V111S_VER_BLANKING; + data = coreReg->verticalBlanking; + mt9v111_write_reg(reg, data); + + reg = MT9V111S_SHUTTER_WIDTH; + data = coreReg->shutterWidth; + mt9v111_write_reg(reg, data); + + reg = MT9V111S_SHUTTER_DELAY; + data = ifpReg->upperShutterDelayLi; + mt9v111_write_reg(reg, data); + + /* changes become effective */ + reg = MT9V111S_OUTPUT_CTRL; + data = MT9V111S_OUTCTRL_CHIP_ENABLE | 0x3000; + mt9v111_write_reg(reg, data); + + return error; +} + +/*! + * MT9V111 frame rate calculate + * + * @param frame_rate int * + * @param mclk int + * @return None + */ +static void mt9v111_rate_cal(int *frame_rate, int mclk) +{ + int num_clock_per_row; + int max_rate = 0; + + pr_debug("In mt9v111_rate_cal\n"); + + num_clock_per_row = (MT9V111_MAX_WIDTH + 114 + MT9V111_HORZBLANK_MIN) + * 2; + max_rate = mclk / (num_clock_per_row * + (MT9V111_MAX_HEIGHT + MT9V111_VERTBLANK_DEFAULT)); + + if ((*frame_rate > max_rate) || (*frame_rate == 0)) { + *frame_rate = max_rate; + } + + mt9v111_device.coreReg->verticalBlanking + = mclk / (*frame_rate * num_clock_per_row) - MT9V111_MAX_HEIGHT; + + reset_frame_rate = *frame_rate; +} + +/*! + * MT9V111 sensor configuration + */ +void mt9v111_config(void) +{ + pr_debug("In mt9v111_config\n"); + + mt9v111_device.coreReg->addressSelect = MT9V111I_SEL_SCA; + mt9v111_device.ifpReg->addrSpaceSel = MT9V111I_SEL_IFP; + + mt9v111_device.coreReg->windowHeight = MT9V111_WINHEIGHT; + mt9v111_device.coreReg->windowWidth = MT9V111_WINWIDTH; + mt9v111_device.coreReg->zoomColStart = 0; + mt9v111_device.coreReg->zomRowStart = 0; + mt9v111_device.coreReg->digitalZoom = 0x0; + + mt9v111_device.coreReg->verticalBlanking = MT9V111_VERTBLANK_DEFAULT; + mt9v111_device.coreReg->horizontalBlanking = MT9V111_HORZBLANK_MIN; + mt9v111_device.coreReg->pixelClockSpeed = 0; + mt9v111_device.coreReg->readMode = 0xd0a1; + + mt9v111_device.ifpReg->outputFormatCtrl2 = 0; + mt9v111_device.ifpReg->gainLimitAE = 0x300; + mt9v111_device.ifpReg->AESpeed = 0x80; + + /* here is the default value */ + mt9v111_device.ifpReg->formatControl = 0xc800; + mt9v111_device.ifpReg->modeControl = 0x708e; + mt9v111_device.ifpReg->awbSpeed = 0x4514; + mt9v111_device.coreReg->shutterWidth = 0xf8; + + /* output size */ + mt9v111_device.ifpReg->HPan = 0; + mt9v111_device.ifpReg->HZoom = MT9V111_MAX_WIDTH; + mt9v111_device.ifpReg->HSize = MT9V111_MAX_WIDTH; + mt9v111_device.ifpReg->VPan = 0; + mt9v111_device.ifpReg->VZoom = MT9V111_MAX_HEIGHT; + mt9v111_device.ifpReg->VSize = MT9V111_MAX_HEIGHT; +} + +/*! + * mt9v111 sensor set saturtionn + * + * @param saturation int + + * @return Error code of 0. + */ +static int mt9v111_set_saturation(int saturation) +{ + u8 reg; + u16 data; + pr_debug("In mt9v111_set_saturation(%d)\n", + saturation); + + switch (saturation) { + case 150: + mt9v111_device.ifpReg->awbSpeed = 0x6D14; + break; + case 100: + mt9v111_device.ifpReg->awbSpeed = 0x4514; + break; + case 75: + mt9v111_device.ifpReg->awbSpeed = 0x4D14; + break; + case 50: + mt9v111_device.ifpReg->awbSpeed = 0x5514; + break; + case 37: + mt9v111_device.ifpReg->awbSpeed = 0x5D14; + break; + case 25: + mt9v111_device.ifpReg->awbSpeed = 0x6514; + break; + default: + mt9v111_device.ifpReg->awbSpeed = 0x4514; + break; + } + + reg = MT9V111I_ADDR_SPACE_SEL; + data = mt9v111_device.ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + /* Operation Mode Control */ + reg = MT9V111I_AWB_SPEED; + data = mt9v111_device.ifpReg->awbSpeed; + mt9v111_write_reg(reg, data); + + return 0; +} + +/*! + * mt9v111 sensor set Auto Exposure measurement window mode configuration + * + * @param ae_mode int + * @return Error code of 0 (no Error) + */ +static int mt9v111_set_ae_mode(int ae_mode) +{ + u8 reg; + u16 data; + + pr_debug("In mt9v111_set_ae_mode(%d)\n", + ae_mode); + + /* Currently this driver only supports auto and manual exposure + * modes. */ + if ((ae_mode > 1) || (ae_mode << 0)) + return -EPERM; + + /* + * The auto exposure is set in bit 14. + * Other values are set for: + * -on the fly defect correction is on (bit 13). + * -aperature correction knee enabled (bit 12). + * -ITU_R BT656 synchronization codes are embedded in the image (bit 7) + * -AE measurement window is weighted sum of large and center windows + * (bits 2-3). + * -auto white balance is on (bit 1). + * -normal color processing (bit 4 = 0). + */ + /* V4L2_EXPOSURE_AUTO = 0; needs register setting of 0x708E */ + /* V4L2_EXPOSURE_MANUAL = 1 needs register setting of 0x308E */ + mt9v111_device.ifpReg->modeControl &= 0x3fff; + mt9v111_device.ifpReg->modeControl |= (ae_mode & 0x03) << 14; + mt9v111_data.ae_mode = ae_mode; + + reg = MT9V111I_ADDR_SPACE_SEL; + data = mt9v111_device.ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_MODE_CONTROL; + data = mt9v111_device.ifpReg->modeControl; + mt9v111_write_reg(reg, data); + + return 0; +} + +/*! + * mt9v111 sensor get AE measurement window mode configuration + * + * @param ae_mode int * + * @return None + */ +static void mt9v111_get_ae_mode(int *ae_mode) +{ + pr_debug("In mt9v111_get_ae_mode(%d)\n", *ae_mode); + + if (ae_mode != NULL) { + *ae_mode = (mt9v111_device.ifpReg->modeControl & 0xc) >> 2; + } +} + +#ifdef MT9V111_DEBUG +/*! + * Set sensor to test mode, which will generate test pattern. + * + * @return none + */ +static void mt9v111_test_pattern(bool flag) +{ + u16 data; + + /* switch to sensor registers */ + mt9v111_write_reg(MT9V111I_ADDR_SPACE_SEL, MT9V111I_SEL_SCA); + + if (flag == true) { + testpattern = MT9V111S_OUTCTRL_TEST_MODE; + + data = mt9v111_read_reg(MT9V111S_ROW_NOISE_CTRL) & 0xBF; + mt9v111_write_reg(MT9V111S_ROW_NOISE_CTRL, data); + + mt9v111_write_reg(MT9V111S_TEST_DATA, 0); + + /* changes take effect */ + data = MT9V111S_OUTCTRL_CHIP_ENABLE | testpattern | 0x3000; + mt9v111_write_reg(MT9V111S_OUTPUT_CTRL, data); + } else { + testpattern = 0; + + data = mt9v111_read_reg(MT9V111S_ROW_NOISE_CTRL) | 0x40; + mt9v111_write_reg(MT9V111S_ROW_NOISE_CTRL, data); + + /* changes take effect */ + data = MT9V111S_OUTCTRL_CHIP_ENABLE | testpattern | 0x3000; + mt9v111_write_reg(MT9V111S_OUTPUT_CTRL, data); + } +} +#endif + + +/* --------------- IOCTL functions from v4l2_int_ioctl_desc --------------- */ + +/*! + * ioctl_g_ifparm - V4L2 sensor interface handler for vidioc_int_g_ifparm_num + * s: pointer to standard V4L2 device structure + * p: pointer to standard V4L2 vidioc_int_g_ifparm_num ioctl structure + * + * Gets slave interface parameters. + * Calculates the required xclk value to support the requested + * clock parameters in p. This value is returned in the p + * parameter. + * + * vidioc_int_g_ifparm returns platform-specific information about the + * interface settings used by the sensor. + * + * Given the image capture format in pix, the nominal frame period in + * timeperframe, calculate the required xclk frequency. + * + * Called on open. + */ +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + pr_debug("In mt9v111:ioctl_g_ifparm\n"); + + if (s == NULL) { + pr_err(" ERROR!! no slave device set!\n"); + return -1; + } + + memset(p, 0, sizeof(*p)); + p->u.bt656.clock_curr = MT9V111_MCLK; + p->if_type = V4L2_IF_TYPE_BT656; + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.clock_min = MT9V111_CLK_MIN; + p->u.bt656.clock_max = MT9V111_CLK_MAX; + + return 0; +} + +/*! + * Sets the camera power. + * + * s pointer to the camera device + * on if 1, power is to be turned on. 0 means power is to be turned off + * + * ioctl_s_power - V4L2 sensor interface handler for vidioc_int_s_power_num + * @s: pointer to standard V4L2 device structure + * @on: power state to which device is to be set + * + * Sets devices power state to requrested state, if possible. + * This is called on suspend and resume. + */ +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + pr_debug("In mt9v111:ioctl_s_power\n"); + + sensor->on = on; + + if (on) + gpio_sensor_active(); + else + gpio_sensor_inactive(); + + return 0; +} + +/*! + * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the sensor's video CAPTURE parameters. + */ +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + int ret = 0; + struct v4l2_captureparm *cparm = &a->parm.capture; + /* s->priv points to mt9v111_data */ + + pr_debug("In mt9v111:ioctl_g_parm\n"); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = mt9v111_data.streamcap.capability; + cparm->timeperframe = + mt9v111_data.streamcap.timeperframe; + cparm->capturemode = mt9v111_data.streamcap.capturemode; + ret = 0; + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_err(" type is not V4L2_BUF_TYPE_VIDEO_CAPTURE " \ + "but %d\n", a->type); + ret = -EINVAL; + break; + + default: + pr_err(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the sensor to use the input parameters, if possible. If + * not possible, reverts to the old parameters and returns the + * appropriate error code. + */ +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + int ret = 0; + struct v4l2_captureparm *cparm = &a->parm.capture; + /* s->priv points to mt9v111_data */ + + pr_debug("In mt9v111:ioctl_s_parm\n"); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + + /* Check that the new frame rate is allowed. + * Changing the frame rate is not allowed on this + *camera. */ + if (cparm->timeperframe.denominator != + mt9v111_data.streamcap.timeperframe.denominator) { + pr_err("ERROR: mt9v111: ioctl_s_parm: " \ + "This camera does not allow frame rate " + "changes.\n"); + ret = -EINVAL; + } else { + mt9v111_data.streamcap.timeperframe = + cparm->timeperframe; + /* Call any camera functions to match settings. */ + } + + /* Check that new capture mode is supported. */ + if ((cparm->capturemode != 0) && + !(cparm->capturemode & V4L2_MODE_HIGHQUALITY)) { + pr_err("ERROR: mt9v111: ioctl_s_parm: " \ + "unsupported capture mode\n"); + ret = -EINVAL; + } else { + mt9v111_data.streamcap.capturemode = + cparm->capturemode; + /* Call any camera functions to match settings. */ + /* Right now this camera only supports 1 mode. */ + } + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_err(" type is not V4L2_BUF_TYPE_VIDEO_CAPTURE " \ + "but %d\n", a->type); + ret = -EINVAL; + break; + + default: + pr_err(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return 0; +} + +/*! + * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the sensor's current pixel format in the v4l2_format + * parameter. + */ +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + /* s->priv points to mt9v111_data */ + + pr_debug("In mt9v111:ioctl_g_fmt_cap.\n"); + pr_debug(" Returning size of %dx%d\n", + sensor->pix.width, sensor->pix.height); + + f->fmt.pix = sensor->pix; + + return 0; +} + +/*! + * ioctl_queryctrl - V4L2 sensor interface handler for VIDIOC_QUERYCTRL ioctl + * @s: pointer to standard V4L2 device structure + * @qc: standard V4L2 VIDIOC_QUERYCTRL ioctl structure + * + * If the requested control is supported, returns the control information + * from the video_control[] array. Otherwise, returns -EINVAL if the + * control is not supported. + */ +static int ioctl_queryctrl(struct v4l2_int_device *s, struct v4l2_queryctrl *qc) +{ + pr_debug("In mt9v111:ioctl_queryctrl\n"); + + return 0; +} + +/*! + * ioctl_g_ctrl - V4L2 sensor interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_G_CTRL ioctl structure + * + * If the requested control is supported, returns the control's current + * value from the video_control[] array. Otherwise, returns -EINVAL + * if the control is not supported. + */ +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + pr_debug("In mt9v111:ioctl_g_ctrl\n"); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + pr_debug(" V4L2_CID_BRIGHTNESS\n"); + vc->value = mt9v111_data.brightness; + break; + case V4L2_CID_CONTRAST: + pr_debug(" V4L2_CID_CONTRAST\n"); + vc->value = mt9v111_data.contrast; + break; + case V4L2_CID_SATURATION: + pr_debug(" V4L2_CID_SATURATION\n"); + vc->value = mt9v111_data.saturation; + break; + case V4L2_CID_HUE: + pr_debug(" V4L2_CID_HUE\n"); + vc->value = mt9v111_data.hue; + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + vc->value = 0; + break; + case V4L2_CID_DO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_DO_WHITE_BALANCE\n"); + vc->value = 0; + break; + case V4L2_CID_RED_BALANCE: + pr_debug(" V4L2_CID_RED_BALANCE\n"); + vc->value = mt9v111_data.red; + break; + case V4L2_CID_BLUE_BALANCE: + pr_debug(" V4L2_CID_BLUE_BALANCE\n"); + vc->value = mt9v111_data.blue; + break; + case V4L2_CID_GAMMA: + pr_debug(" V4L2_CID_GAMMA\n"); + vc->value = 0; + break; + case V4L2_CID_EXPOSURE: + pr_debug(" V4L2_CID_EXPOSURE\n"); + vc->value = mt9v111_data.ae_mode; + break; + case V4L2_CID_AUTOGAIN: + pr_debug(" V4L2_CID_AUTOGAIN\n"); + vc->value = 0; + break; + case V4L2_CID_GAIN: + pr_debug(" V4L2_CID_GAIN\n"); + vc->value = 0; + break; + case V4L2_CID_HFLIP: + pr_debug(" V4L2_CID_HFLIP\n"); + vc->value = 0; + break; + case V4L2_CID_VFLIP: + pr_debug(" V4L2_CID_VFLIP\n"); + vc->value = 0; + break; + default: + pr_debug(" Default case\n"); + return -EPERM; + break; + } + + return 0; +} + +/*! + * ioctl_s_ctrl - V4L2 sensor interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_S_CTRL ioctl structure + * + * If the requested control is supported, sets the control's current + * value in HW (and updates the video_control[] array). Otherwise, + * returns -EINVAL if the control is not supported. + */ +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + + pr_debug("In mt9v111:ioctl_s_ctrl %d\n", + vc->id); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + pr_debug(" V4L2_CID_BRIGHTNESS\n"); + break; + case V4L2_CID_CONTRAST: + pr_debug(" V4L2_CID_CONTRAST\n"); + break; + case V4L2_CID_SATURATION: + pr_debug(" V4L2_CID_SATURATION\n"); + retval = mt9v111_set_saturation(vc->value); + break; + case V4L2_CID_HUE: + pr_debug(" V4L2_CID_HUE\n"); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + break; + case V4L2_CID_DO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_DO_WHITE_BALANCE\n"); + break; + case V4L2_CID_RED_BALANCE: + pr_debug(" V4L2_CID_RED_BALANCE\n"); + break; + case V4L2_CID_BLUE_BALANCE: + pr_debug(" V4L2_CID_BLUE_BALANCE\n"); + break; + case V4L2_CID_GAMMA: + pr_debug(" V4L2_CID_GAMMA\n"); + break; + case V4L2_CID_EXPOSURE: + pr_debug(" V4L2_CID_EXPOSURE\n"); + retval = mt9v111_set_ae_mode(vc->value); + break; + case V4L2_CID_AUTOGAIN: + pr_debug(" V4L2_CID_AUTOGAIN\n"); + break; + case V4L2_CID_GAIN: + pr_debug(" V4L2_CID_GAIN\n"); + break; + case V4L2_CID_HFLIP: + pr_debug(" V4L2_CID_HFLIP\n"); + break; + case V4L2_CID_VFLIP: + pr_debug(" V4L2_CID_VFLIP\n"); + break; + default: + pr_debug(" Default case\n"); + retval = -EPERM; + break; + } + + return retval; +} + +/*! + * ioctl_init - V4L2 sensor interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + pr_debug("In mt9v111:ioctl_init\n"); + + return 0; +} + +/*! + * ioctl_dev_init - V4L2 sensor interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + uint32_t clock_rate = MT9V111_MCLK; + + pr_debug("In mt9v111:ioctl_dev_init\n"); + + gpio_sensor_active(); + + set_mclk_rate(&clock_rate); + mt9v111_rate_cal(&reset_frame_rate, clock_rate); + mt9v111_sensor_lib(mt9v111_device.coreReg, mt9v111_device.ifpReg); + + return 0; +} + +/*! + * This structure defines all the ioctls for this module and links them to the + * enumeration. + */ +static struct v4l2_int_ioctl_desc mt9v111_ioctl_desc[] = { + + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func *)ioctl_dev_init}, + + /*! + * Delinitialise the dev. at slave detach. + * The complement of ioctl_dev_init. + */ +/* {vidioc_int_dev_exit_num, (v4l2_int_ioctl_func *) ioctl_dev_exit}, */ + + {vidioc_int_s_power_num, (v4l2_int_ioctl_func *) ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func *) ioctl_g_ifparm}, +/* {vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *) ioctl_g_needs_reset}, */ +/* {vidioc_int_reset_num, (v4l2_int_ioctl_func *) ioctl_reset}, */ + {vidioc_int_init_num, (v4l2_int_ioctl_func *) ioctl_init}, + + /*! + * VIDIOC_ENUM_FMT ioctl for the CAPTURE buffer type. + */ +/* {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *) ioctl_enum_fmt_cap}, */ + + /*! + * VIDIOC_TRY_FMT ioctl for the CAPTURE buffer type. + * This ioctl is used to negotiate the image capture size and + * pixel format without actually making it take effect. + */ +/* {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *) ioctl_try_fmt_cap}, */ + + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func *) ioctl_g_fmt_cap}, + + /*! + * If the requested format is supported, configures the HW to use that + * format, returns error code if format not supported or HW can't be + * correctly configured. + */ +/* {vidioc_int_s_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_s_fmt_cap}, */ + + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func *) ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func *) ioctl_s_parm}, +/* {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func *) ioctl_queryctrl}, */ + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func *) ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func *) ioctl_s_ctrl}, +}; + +static struct v4l2_int_slave mt9v111_slave = { + .ioctls = mt9v111_ioctl_desc, + .num_ioctls = ARRAY_SIZE(mt9v111_ioctl_desc), +}; + +static struct v4l2_int_device mt9v111_int_device = { + .module = THIS_MODULE, + .name = "mt9v111", + .type = v4l2_int_type_slave, + .u = { + .slave = &mt9v111_slave, + }, +}; + +/*! + * mt9v111 I2C probe function + * Function set in i2c_driver struct. + * Called by insmod mt9v111_camera.ko. + * + * @return Error code indicating success or failure + */ +static int mt9v111_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + + pr_debug("In mt9v111_probe device id is %s\n", id->name); + + /* Set initial values for the sensor struct. */ + memset(&mt9v111_data, 0, sizeof(mt9v111_data)); + mt9v111_data.i2c_client = client; + pr_debug(" client name is %s\n", client->name); + mt9v111_data.pix.pixelformat = V4L2_PIX_FMT_UYVY; + mt9v111_data.pix.width = MT9V111_MAX_WIDTH; + mt9v111_data.pix.height = MT9V111_MAX_HEIGHT; + mt9v111_data.streamcap.capability = 0; /* No higher resolution or frame + * frame rate changes supported. + */ + mt9v111_data.streamcap.timeperframe.denominator = MT9V111_FRAME_RATE; + mt9v111_data.streamcap.timeperframe.numerator = 1; + + mt9v111_int_device.priv = &mt9v111_data; + + pr_debug(" type is %d (expect %d)\n", + mt9v111_int_device.type, v4l2_int_type_slave); + pr_debug(" num ioctls is %d\n", + mt9v111_int_device.u.slave->num_ioctls); + + /* This function attaches this structure to the /dev/video0 device. + * The pointer in priv points to the mt9v111_data structure here.*/ + retval = v4l2_int_device_register(&mt9v111_int_device); + + return retval; +} + +/*! + * Function set in i2c_driver struct. + * Called on rmmod mt9v111_camera.ko + */ +static int mt9v111_remove(struct i2c_client *client) +{ + pr_debug("In mt9v111_remove\n"); + + v4l2_int_device_unregister(&mt9v111_int_device); + return 0; +} + +/*! + * MT9V111 init function. + * Called by insmod mt9v111_camera.ko. + * + * @return Error code indicating success or failure + */ +static __init int mt9v111_init(void) +{ + u8 err; + + pr_debug("In mt9v111_init\n"); + + /* Allocate memory for state structures. */ + mt9v111_device.coreReg = (mt9v111_coreReg *) + kmalloc(sizeof(mt9v111_coreReg), GFP_KERNEL); + if (!mt9v111_device.coreReg) + return -1; + memset(mt9v111_device.coreReg, 0, sizeof(mt9v111_coreReg)); + + mt9v111_device.ifpReg = (mt9v111_IFPReg *) + kmalloc(sizeof(mt9v111_IFPReg), GFP_KERNEL); + if (!mt9v111_device.ifpReg) { + kfree(mt9v111_device.coreReg); + mt9v111_device.coreReg = NULL; + return -1; + } + memset(mt9v111_device.ifpReg, 0, sizeof(mt9v111_IFPReg)); + + /* Set contents of the just created structures. */ + mt9v111_config(); + + /* Tells the i2c driver what functions to call for this driver. */ + err = i2c_add_driver(&mt9v111_i2c_driver); + if (err != 0) + pr_err("%s:driver registration failed, error=%d \n", + __func__, err); + + return err; +} + +/*! + * MT9V111 cleanup function. + * Called on rmmod mt9v111_camera.ko + * + * @return Error code indicating success or failure + */ +static void __exit mt9v111_clean(void) +{ + pr_debug("In mt9v111_clean()\n"); + + i2c_del_driver(&mt9v111_i2c_driver); + gpio_sensor_inactive(); + + if (mt9v111_device.coreReg) { + kfree(mt9v111_device.coreReg); + mt9v111_device.coreReg = NULL; + } + + if (mt9v111_device.ifpReg) { + kfree(mt9v111_device.ifpReg); + mt9v111_device.ifpReg = NULL; + } +} + +module_init(mt9v111_init); +module_exit(mt9v111_clean); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Mt9v111 Camera Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/mt9v111.h b/drivers/media/video/mxc/capture/mt9v111.h new file mode 100644 index 000000000000..cf38cec4757c --- /dev/null +++ b/drivers/media/video/mxc/capture/mt9v111.h @@ -0,0 +1,431 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Camera Sensor Drivers + */ + +/*! + * @file mt9v111.h + * + * @brief MT9V111 Camera Header file + * + * This header file contains defines and structures for the iMagic mi8012 + * aka the Micron mt9v111 camera. + * + * @ingroup Camera + */ + +#ifndef MT9V111_H_ +#define MT9V111_H_ + +/*! + * Basic camera values + */ +#define MT9V111_FRAME_RATE 30 +#define MT9V111_MCLK 27000000 /* Desired clock rate */ +#define MT9V111_CLK_MIN 12000000 /* This clock rate yields 15 fps */ +#define MT9V111_CLK_MAX 27000000 +#define MT9V111_MAX_WIDTH 640 /* Max width for this camera */ +#define MT9V111_MAX_HEIGHT 480 /* Max height for this camera */ + +/*! + * mt9v111 IFP REGISTER BANK MAP + */ +#define MT9V111I_ADDR_SPACE_SEL 0x1 +#define MT9V111I_BASE_MAXTRIX_SIGN 0x2 +#define MT9V111I_BASE_MAXTRIX_SCALE15 0x3 +#define MT9V111I_BASE_MAXTRIX_SCALE69 0x4 +#define MT9V111I_APERTURE_GAIN 0x5 +#define MT9V111I_MODE_CONTROL 0x6 +#define MT9V111I_SOFT_RESET 0x7 +#define MT9V111I_FORMAT_CONTROL 0x8 +#define MT9V111I_BASE_MATRIX_CFK1 0x9 +#define MT9V111I_BASE_MATRIX_CFK2 0xa +#define MT9V111I_BASE_MATRIX_CFK3 0xb +#define MT9V111I_BASE_MATRIX_CFK4 0xc +#define MT9V111I_BASE_MATRIX_CFK5 0xd +#define MT9V111I_BASE_MATRIX_CFK6 0xe +#define MT9V111I_BASE_MATRIX_CFK7 0xf +#define MT9V111I_BASE_MATRIX_CFK8 0x10 +#define MT9V111I_BASE_MATRIX_CFK9 0x11 +#define MT9V111I_AWB_POSITION 0x12 +#define MT9V111I_AWB_RED_GAIN 0x13 +#define MT9V111I_AWB_BLUE_GAIN 0x14 +#define MT9V111I_DELTA_MATRIX_CF_SIGN 0x15 +#define MT9V111I_DELTA_MATRIX_CF_D1 0x16 +#define MT9V111I_DELTA_MATRIX_CF_D2 0x17 +#define MT9V111I_DELTA_MATRIX_CF_D3 0x18 +#define MT9V111I_DELTA_MATRIX_CF_D4 0x19 +#define MT9V111I_DELTA_MATRIX_CF_D5 0x1a +#define MT9V111I_DELTA_MATRIX_CF_D6 0x1b +#define MT9V111I_DELTA_MATRIX_CF_D7 0x1c +#define MT9V111I_DELTA_MATRIX_CF_D8 0x1d +#define MT9V111I_DELTA_MATRIX_CF_D9 0x1e +#define MT9V111I_LUMINANCE_LIMIT_WB 0x20 +#define MT9V111I_RBG_MANUUAL_WB 0x21 +#define MT9V111I_AWB_RED_LIMIT 0x22 +#define MT9V111I_AWB_BLUE_LIMIT 0x23 +#define MT9V111I_MATRIX_ADJUST_LIMIT 0x24 +#define MT9V111I_AWB_SPEED 0x25 +#define MT9V111I_H_BOUND_AE 0x26 +#define MT9V111I_V_BOUND_AE 0x27 +#define MT9V111I_H_BOUND_AE_CEN_WIN 0x2b +#define MT9V111I_V_BOUND_AE_CEN_WIN 0x2c +#define MT9V111I_BOUND_AWB_WIN 0x2d +#define MT9V111I_AE_PRECISION_TARGET 0x2e +#define MT9V111I_AE_SPEED 0x2f +#define MT9V111I_RED_AWB_MEASURE 0x30 +#define MT9V111I_LUMA_AWB_MEASURE 0x31 +#define MT9V111I_BLUE_AWB_MEASURE 0x32 +#define MT9V111I_LIMIT_SHARP_SATU_CTRL 0x33 +#define MT9V111I_LUMA_OFFSET 0x34 +#define MT9V111I_CLIP_LIMIT_OUTPUT_LUMI 0x35 +#define MT9V111I_GAIN_LIMIT_AE 0x36 +#define MT9V111I_SHUTTER_WIDTH_LIMIT_AE 0x37 +#define MT9V111I_UPPER_SHUTTER_DELAY_LIM 0x39 +#define MT9V111I_OUTPUT_FORMAT_CTRL2 0x3a +#define MT9V111I_IPF_BLACK_LEVEL_SUB 0x3b +#define MT9V111I_IPF_BLACK_LEVEL_ADD 0x3c +#define MT9V111I_ADC_LIMIT_AE_ADJ 0x3d +#define MT9V111I_GAIN_THRE_CCAM_ADJ 0x3e +#define MT9V111I_LINEAR_AE 0x3f +#define MT9V111I_THRESHOLD_EDGE_DEFECT 0x47 +#define MT9V111I_LUMA_SUM_MEASURE 0x4c +#define MT9V111I_TIME_ADV_SUM_LUMA 0x4d +#define MT9V111I_MOTION 0x52 +#define MT9V111I_GAMMA_KNEE_Y12 0x53 +#define MT9V111I_GAMMA_KNEE_Y34 0x54 +#define MT9V111I_GAMMA_KNEE_Y56 0x55 +#define MT9V111I_GAMMA_KNEE_Y78 0x56 +#define MT9V111I_GAMMA_KNEE_Y90 0x57 +#define MT9V111I_GAMMA_VALUE_Y0 0x58 +#define MT9V111I_SHUTTER_60 0x59 +#define MT9V111I_SEARCH_FLICK_60 0x5c +#define MT9V111I_RATIO_IMAGE_GAIN_BASE 0x5e +#define MT9V111I_RATIO_IMAGE_GAIN_DELTA 0x5f +#define MT9V111I_SIGN_VALUE_REG5F 0x60 +#define MT9V111I_AE_GAIN 0x62 +#define MT9V111I_MAX_GAIN_AE 0x67 +#define MT9V111I_LENS_CORRECT_CTRL 0x80 +#define MT9V111I_SHADING_PARAMETER1 0x81 +#define MT9V111I_SHADING_PARAMETER2 0x82 +#define MT9V111I_SHADING_PARAMETER3 0x83 +#define MT9V111I_SHADING_PARAMETER4 0x84 +#define MT9V111I_SHADING_PARAMETER5 0x85 +#define MT9V111I_SHADING_PARAMETER6 0x86 +#define MT9V111I_SHADING_PARAMETER7 0x87 +#define MT9V111I_SHADING_PARAMETER8 0x88 +#define MT9V111I_SHADING_PARAMETER9 0x89 +#define MT9V111I_SHADING_PARAMETER10 0x8A +#define MT9V111I_SHADING_PARAMETER11 0x8B +#define MT9V111I_SHADING_PARAMETER12 0x8C +#define MT9V111I_SHADING_PARAMETER13 0x8D +#define MT9V111I_SHADING_PARAMETER14 0x8E +#define MT9V111I_SHADING_PARAMETER15 0x8F +#define MT9V111I_SHADING_PARAMETER16 0x90 +#define MT9V111I_SHADING_PARAMETER17 0x91 +#define MT9V111I_SHADING_PARAMETER18 0x92 +#define MT9V111I_SHADING_PARAMETER19 0x93 +#define MT9V111I_SHADING_PARAMETER20 0x94 +#define MT9V111I_SHADING_PARAMETER21 0x95 +#define MT9V111i_FLASH_CTRL 0x98 +#define MT9V111i_LINE_COUNTER 0x99 +#define MT9V111i_FRAME_COUNTER 0x9A +#define MT9V111i_H_PAN 0xA5 +#define MT9V111i_H_ZOOM 0xA6 +#define MT9V111i_H_SIZE 0xA7 +#define MT9V111i_V_PAN 0xA8 +#define MT9V111i_V_ZOOM 0xA9 +#define MT9V111i_V_SIZE 0xAA + +#define MT9V111I_SEL_IFP 0x1 +#define MT9V111I_SEL_SCA 0x4 +#define MT9V111I_FC_RGB_OR_YUV 0x1000 + +/*! + * Mt9v111 SENSOR CORE REGISTER BANK MAP + */ +#define MT9V111S_ADDR_SPACE_SEL 0x1 +#define MT9V111S_COLUMN_START 0x2 +#define MT9V111S_WIN_HEIGHT 0x3 +#define MT9V111S_WIN_WIDTH 0x4 +#define MT9V111S_HOR_BLANKING 0x5 +#define MT9V111S_VER_BLANKING 0x6 +#define MT9V111S_OUTPUT_CTRL 0x7 +#define MT9V111S_ROW_START 0x8 +#define MT9V111S_SHUTTER_WIDTH 0x9 +#define MT9V111S_PIXEL_CLOCK_SPEED 0xa +#define MT9V111S_RESTART 0xb +#define MT9V111S_SHUTTER_DELAY 0xc +#define MT9V111S_RESET 0xd +#define MT9V111S_COLUMN_START_IN_ZOOM 0x12 +#define MT9V111S_ROW_START_IN_ZOOM 0x13 +#define MT9V111S_DIGITAL_ZOOM 0x1e +#define MT9V111S_READ_MODE 0x20 +#define MT9V111S_DAC_CTRL 0x27 +#define MT9V111S_GREEN1_GAIN 0x2b +#define MT9V111S_BLUE_GAIN 0x2c +#define MT9V111S_READ_GAIN 0x2d +#define MT9V111S_GREEN2_GAIN 0x2e +#define MT9V111S_ROW_NOISE_CTRL 0x30 +#define MT9V111S_DARK_TARGET_W 0x31 +#define MT9V111S_TEST_DATA 0x32 +#define MT9V111S_GLOBAL_GAIN 0x35 +#define MT9V111S_SENSOR_CORE_VERSION 0x36 +#define MT9V111S_DARK_TARGET_WO 0x37 +#define MT9V111S_VERF_DAC 0x41 +#define MT9V111S_VCM_VCL 0x42 +#define MT9V111S_DISABLE_BYPASS 0x58 +#define MT9V111S_CALIB_MEAN_TEST 0x59 +#define MT9V111S_DARK_G1_AVE 0x5B +#define MT9V111S_DARK_G2_AVE 0x5C +#define MT9V111S_DARK_R_AVE 0x5D +#define MT9V111S_DARK_B_AVE 0x5E +#define MT9V111S_CAL_THRESHOLD 0x5f +#define MT9V111S_CAL_G1 0x60 +#define MT9V111S_CAL_G2 0x61 +#define MT9V111S_CAL_CTRL 0x62 +#define MT9V111S_CAL_R 0x63 +#define MT9V111S_CAL_B 0x64 +#define MT9V111S_CHIP_ENABLE 0xF1 +#define MT9V111S_CHIP_VERSION 0xFF + +/* OUTPUT_CTRL */ +#define MT9V111S_OUTCTRL_SYNC 0x1 +#define MT9V111S_OUTCTRL_CHIP_ENABLE 0x2 +#define MT9V111S_OUTCTRL_TEST_MODE 0x40 + +/* READ_MODE */ +#define MT9V111S_RM_NOBADFRAME 0x1 +#define MT9V111S_RM_NODESTRUCT 0x2 +#define MT9V111S_RM_COLUMNSKIP 0x4 +#define MT9V111S_RM_ROWSKIP 0x8 +#define MT9V111S_RM_BOOSTEDRESET 0x1000 +#define MT9V111S_RM_COLUMN_LATE 0x10 +#define MT9V111S_RM_ROW_LATE 0x80 +#define MT9V111S_RM_RIGTH_TO_LEFT 0x4000 +#define MT9V111S_RM_BOTTOM_TO_TOP 0x8000 + +/*! I2C Slave Address */ +#define MT9V111_I2C_ADDRESS 0x48 + +/*! + * The image resolution enum for the mt9v111 sensor + */ +typedef enum { + MT9V111_OutputResolution_VGA = 0, /*!< VGA size */ + MT9V111_OutputResolution_QVGA, /*!< QVGA size */ + MT9V111_OutputResolution_CIF, /*!< CIF size */ + MT9V111_OutputResolution_QCIF, /*!< QCIF size */ + MT9V111_OutputResolution_QQVGA, /*!< QQVGA size */ + MT9V111_OutputResolution_SXGA /*!< SXGA size */ +} MT9V111_OutputResolution; + +enum { + MT9V111_WINWIDTH = 0x287, + MT9V111_WINWIDTH_DEFAULT = 0x287, + MT9V111_WINWIDTH_MIN = 0x9, + + MT9V111_WINHEIGHT = 0x1E7, + MT9V111_WINHEIGHT_DEFAULT = 0x1E7, + + MT9V111_HORZBLANK_DEFAULT = 0x26, + MT9V111_HORZBLANK_MIN = 0x9, + MT9V111_HORZBLANK_MAX = 0x3FF, + + MT9V111_VERTBLANK_DEFAULT = 0x4, + MT9V111_VERTBLANK_MIN = 0x3, + MT9V111_VERTBLANK_MAX = 0xFFF, +}; + +/*! + * Mt9v111 Core Register structure. + */ +typedef struct { + u32 addressSelect; /*!< select address bank for Core Register 0x4 */ + u32 columnStart; /*!< Starting Column */ + u32 windowHeight; /*!< Window Height */ + u32 windowWidth; /*!< Window Width */ + u32 horizontalBlanking; /*!< Horizontal Blank time, in pixels */ + u32 verticalBlanking; /*!< Vertical Blank time, in pixels */ + u32 outputControl; /*!< Register to control sensor output */ + u32 rowStart; /*!< Starting Row */ + u32 shutterWidth; + u32 pixelClockSpeed; /*!< pixel date rate */ + u32 restart; /*!< Abandon the readout of current frame */ + u32 shutterDelay; + u32 reset; /*!< reset the sensor to the default mode */ + u32 zoomColStart; /*!< Column start in the Zoom mode */ + u32 zomRowStart; /*!< Row start in the Zoom mode */ + u32 digitalZoom; /*!< 1 means zoom by 2 */ + u32 readMode; /*!< Readmode: aspects of the readout of the sensor */ + u32 dACStandbyControl; + u32 green1Gain; /*!< Gain Settings */ + u32 blueGain; + u32 redGain; + u32 green2Gain; + u32 rowNoiseControl; + u32 darkTargetwNC; + u32 testData; /*!< test mode */ + u32 globalGain; + u32 chipVersion; + u32 darkTargetwoNC; + u32 vREFDACs; + u32 vCMandVCL; + u32 disableBypass; + u32 calibMeanTest; + u32 darkG1average; + u32 darkG2average; + u32 darkRaverage; + u32 darkBaverage; + u32 calibThreshold; + u32 calibGreen1; + u32 calibGreen2; + u32 calibControl; + u32 calibRed; + u32 calibBlue; + u32 chipEnable; /*!< Image core Registers written by image flow processor */ +} mt9v111_coreReg; + +/*! + * Mt9v111 IFP Register structure. + */ +typedef struct { + u32 addrSpaceSel; /*!< select address bank for Core Register 0x1 */ + u32 baseMaxtrixSign; /*!< sign of coefficient for base color correction matrix */ + u32 baseMaxtrixScale15; /*!< scaling of color correction coefficient K1-5 */ + u32 baseMaxtrixScale69; /*!< scaling of color correction coefficient K6-9 */ + u32 apertureGain; /*!< sharpening */ + u32 modeControl; /*!< bit 7 CCIR656 sync codes are embedded in the image */ + u32 softReset; /*!< Image processing mode: 1 reset mode, 0 operational mode */ + u32 formatControl; /*!< bit12 1 for RGB565, 0 for YcrCb */ + u32 baseMatrixCfk1; /*!< K1 Color correction coefficient */ + u32 baseMatrixCfk2; /*!< K2 Color correction coefficient */ + u32 baseMatrixCfk3; /*!< K3 Color correction coefficient */ + u32 baseMatrixCfk4; /*!< K4 Color correction coefficient */ + u32 baseMatrixCfk5; /*!< K5 Color correction coefficient */ + u32 baseMatrixCfk6; /*!< K6 Color correction coefficient */ + u32 baseMatrixCfk7; /*!< K7 Color correction coefficient */ + u32 baseMatrixCfk8; /*!< K8 Color correction coefficient */ + u32 baseMatrixCfk9; /*!< K9 Color correction coefficient */ + u32 awbPosition; /*!< Current position of AWB color correction matrix */ + u32 awbRedGain; /*!< Current value of AWB red channel gain */ + u32 awbBlueGain; /*!< Current value of AWB blue channel gain */ + u32 deltaMatrixCFSign; /*!< Sign of coefficients of delta color correction matrix register */ + u32 deltaMatrixCFD1; /*!< D1 Delta coefficient */ + u32 deltaMatrixCFD2; /*!< D2 Delta coefficient */ + u32 deltaMatrixCFD3; /*!< D3 Delta coefficient */ + u32 deltaMatrixCFD4; /*!< D4 Delta coefficient */ + u32 deltaMatrixCFD5; /*!< D5 Delta coefficient */ + u32 deltaMatrixCFD6; /*!< D6 Delta coefficient */ + u32 deltaMatrixCFD7; /*!< D7 Delta coefficient */ + u32 deltaMatrixCFD8; /*!< D8 Delta coefficient */ + u32 deltaMatrixCFD9; /*!< D9 Delta coefficient */ + u32 lumLimitWB; /*!< Luminance range of pixels considered in WB statistics */ + u32 RBGManualWB; /*!< Red and Blue color channel gains for manual white balance */ + u32 awbRedLimit; /*!< Limits on Red channel gain adjustment through AWB */ + u32 awbBlueLimit; /*!< Limits on Blue channel gain adjustment through AWB */ + u32 matrixAdjLimit; /*!< Limits on color correction matrix adjustment through AWB */ + u32 awbSpeed; /*!< AWB speed and color saturation control */ + u32 HBoundAE; /*!< Horizontal boundaries of AWB measurement window */ + u32 VBoundAE; /*!< Vertical boundaries of AWB measurement window */ + u32 HBoundAECenWin; /*!< Horizontal boundaries of AE measurement window for backlight compensation */ + u32 VBoundAECenWin; /*!< Vertical boundaries of AE measurement window for backlight compensation */ + u32 boundAwbWin; /*!< Boundaries of AWB measurement window */ + u32 AEPrecisionTarget; /*!< Auto exposure target and precision control */ + u32 AESpeed; /*!< AE speed and sensitivity control register */ + u32 redAWBMeasure; /*!< Measure of the red channel value used by AWB */ + u32 lumaAWBMeasure; /*!< Measure of the luminance channel value used by AWB */ + u32 blueAWBMeasure; /*!< Measure of the blue channel value used by AWB */ + u32 limitSharpSatuCtrl; /*!< Automatic control of sharpness and color saturation */ + u32 lumaOffset; /*!< Luminance offset control (brightness control) */ + u32 clipLimitOutputLumi; /*!< Clipping limits for output luminance */ + u32 gainLimitAE; /*!< Imager gain limits for AE adjustment */ + u32 shutterWidthLimitAE; /*!< Shutter width (exposure time) limits for AE adjustment */ + u32 upperShutterDelayLi; /*!< Upper Shutter Delay Limit */ + u32 outputFormatCtrl2; /*!< Output Format Control 2 + 00 = 16-bit RGB565. + 01 = 15-bit RGB555. + 10 = 12-bit RGB444x. + 11 = 12-bit RGBx444. */ + u32 ipfBlackLevelSub; /*!< IFP black level subtraction */ + u32 ipfBlackLevelAdd; /*!< IFP black level addition */ + u32 adcLimitAEAdj; /*!< ADC limits for AE adjustment */ + u32 agimnThreCamAdj; /*!< Gain threshold for CCM adjustment */ + u32 linearAE; + u32 thresholdEdgeDefect; /*!< Edge threshold for interpolation and defect correction */ + u32 lumaSumMeasure; /*!< Luma measured by AE engine */ + u32 timeAdvSumLuma; /*!< Time-averaged luminance value tracked by auto exposure */ + u32 motion; /*!< 1 when motion is detected */ + u32 gammaKneeY12; /*!< Gamma knee points Y1 and Y2 */ + u32 gammaKneeY34; /*!< Gamma knee points Y3 and Y4 */ + u32 gammaKneeY56; /*!< Gamma knee points Y5 and Y6 */ + u32 gammaKneeY78; /*!< Gamma knee points Y7 and Y8 */ + u32 gammaKneeY90; /*!< Gamma knee points Y9 and Y10 */ + u32 gammaKneeY0; /*!< Gamma knee point Y0 */ + u32 shutter_width_60; + u32 search_flicker_60; + u32 ratioImageGainBase; + u32 ratioImageGainDelta; + u32 signValueReg5F; + u32 aeGain; + u32 maxGainAE; + u32 lensCorrectCtrl; + u32 shadingParameter1; /*!< Shade Parameters */ + u32 shadingParameter2; + u32 shadingParameter3; + u32 shadingParameter4; + u32 shadingParameter5; + u32 shadingParameter6; + u32 shadingParameter7; + u32 shadingParameter8; + u32 shadingParameter9; + u32 shadingParameter10; + u32 shadingParameter11; + u32 shadingParameter12; + u32 shadingParameter13; + u32 shadingParameter14; + u32 shadingParameter15; + u32 shadingParameter16; + u32 shadingParameter17; + u32 shadingParameter18; + u32 shadingParameter19; + u32 shadingParameter20; + u32 shadingParameter21; + u32 flashCtrl; /*!< Flash control */ + u32 lineCounter; /*!< Line counter */ + u32 frameCounter; /*!< Frame counter */ + u32 HPan; /*!< Horizontal pan in decimation */ + u32 HZoom; /*!< Horizontal zoom in decimation */ + u32 HSize; /*!< Horizontal output size iIn decimation */ + u32 VPan; /*!< Vertical pan in decimation */ + u32 VZoom; /*!< Vertical zoom in decimation */ + u32 VSize; /*!< Vertical output size in decimation */ +} mt9v111_IFPReg; + +/*! + * mt9v111 Config structure + */ +typedef struct { + mt9v111_coreReg *coreReg; /*!< Sensor Core Register Bank */ + mt9v111_IFPReg *ifpReg; /*!< IFP Register Bank */ +} mt9v111_conf; + +typedef struct { + u8 index; + u16 width; + u16 height; +} mt9v111_image_format; + +#endif /* MT9V111_H_ */ diff --git a/drivers/media/video/mxc/capture/mx27_csi.c b/drivers/media/video/mxc/capture/mx27_csi.c new file mode 100644 index 000000000000..24fce05be110 --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_csi.c @@ -0,0 +1,333 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_csi.c + * + * @brief CMOS Sensor interface functions + * + * @ingroup CSI + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <mach/clock.h> +#include <mach/hardware.h> + +#include "mx27_csi.h" + +static csi_config_t g_csi_cfg; /* csi hardware configuration */ +static bool gcsi_mclk_on = false; +static csi_irq_callback_t g_callback = 0; +static void *g_callback_data = 0; +static struct clk csi_mclk; + +static irqreturn_t csi_irq_handler(int irq, void *data) +{ + unsigned long status = __raw_readl(CSI_CSISR); + + __raw_writel(status, CSI_CSISR); + if (g_callback) + g_callback(g_callback_data, status); + + pr_debug("CSI status = 0x%08lX\n", status); + + return IRQ_HANDLED; +} + +static void csihw_set_config(csi_config_t * cfg) +{ + unsigned val = 0; + + /* control reg 1 */ + val |= cfg->swap16_en ? BIT_SWAP16_EN : 0; + val |= cfg->ext_vsync ? BIT_EXT_VSYNC : 0; + val |= cfg->eof_int_en ? BIT_EOF_INT_EN : 0; + val |= cfg->prp_if_en ? BIT_PRP_IF_EN : 0; + val |= cfg->ccir_mode ? BIT_CCIR_MODE : 0; + val |= cfg->cof_int_en ? BIT_COF_INT_EN : 0; + val |= cfg->sf_or_inten ? BIT_SF_OR_INTEN : 0; + val |= cfg->rf_or_inten ? BIT_RF_OR_INTEN : 0; + val |= cfg->statff_level << SHIFT_STATFF_LEVEL; + val |= cfg->staff_inten ? BIT_STATFF_INTEN : 0; + val |= cfg->rxff_level << SHIFT_RXFF_LEVEL; + val |= cfg->rxff_inten ? BIT_RXFF_INTEN : 0; + val |= cfg->sof_pol ? BIT_SOF_POL : 0; + val |= cfg->sof_inten ? BIT_SOF_INTEN : 0; + val |= cfg->mclkdiv << SHIFT_MCLKDIV; + val |= cfg->hsync_pol ? BIT_HSYNC_POL : 0; + val |= cfg->ccir_en ? BIT_CCIR_EN : 0; + val |= cfg->mclken ? BIT_MCLKEN : 0; + val |= cfg->fcc ? BIT_FCC : 0; + val |= cfg->pack_dir ? BIT_PACK_DIR : 0; + val |= cfg->gclk_mode ? BIT_GCLK_MODE : 0; + val |= cfg->inv_data ? BIT_INV_DATA : 0; + val |= cfg->inv_pclk ? BIT_INV_PCLK : 0; + val |= cfg->redge ? BIT_REDGE : 0; + + __raw_writel(val, CSI_CSICR1); + + /* control reg 3 */ + val = 0x0; + val |= cfg->csi_sup ? BIT_CSI_SUP : 0; + val |= cfg->zero_pack_en ? BIT_ZERO_PACK_EN : 0; + val |= cfg->ecc_int_en ? BIT_ECC_INT_EN : 0; + val |= cfg->ecc_auto_en ? BIT_ECC_AUTO_EN : 0; + + __raw_writel(val, CSI_CSICR3); + + /* rxfifo counter */ + __raw_writel(cfg->rxcnt, CSI_CSIRXCNT); + + /* update global config */ + memcpy(&g_csi_cfg, cfg, sizeof(csi_config_t)); +} + +static void csihw_reset_frame_count(void) +{ + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_FRMCNT_RST, CSI_CSICR3); +} + +static void csihw_reset(void) +{ + csihw_reset_frame_count(); + __raw_writel(CSICR1_RESET_VAL, CSI_CSICR1); + __raw_writel(CSICR2_RESET_VAL, CSI_CSICR2); + __raw_writel(CSICR3_RESET_VAL, CSI_CSICR3); +} + +/*! + * csi_init_interface + * Sets initial values for the CSI registers. + * The width and height of the sensor and the actual frame size will be + * set to the same values. + * @param width Sensor width + * @param height Sensor height + * @param pixel_fmt pixel format + * @param sig csi_signal_cfg_t + * + * @return 0 for success, -EINVAL for error + */ +int32_t csi_init_interface(uint16_t width, uint16_t height, + uint32_t pixel_fmt, csi_signal_cfg_t sig) +{ + csi_config_t cfg; + + /* Set the CSI_SENS_CONF register remaining fields */ + cfg.swap16_en = 1; + cfg.ext_vsync = sig.ext_vsync; + cfg.eof_int_en = 0; + cfg.prp_if_en = 1; + cfg.ccir_mode = 0; + cfg.cof_int_en = 0; + cfg.sf_or_inten = 0; + cfg.rf_or_inten = 0; + cfg.statff_level = 0; + cfg.staff_inten = 0; + cfg.rxff_level = 2; + cfg.rxff_inten = 0; + cfg.sof_pol = 1; + cfg.sof_inten = 0; + cfg.mclkdiv = 0; + cfg.hsync_pol = 1; + cfg.ccir_en = 0; + cfg.mclken = gcsi_mclk_on ? 1 : 0; + cfg.fcc = 1; + cfg.pack_dir = 0; + cfg.gclk_mode = 1; + cfg.inv_data = sig.data_pol; + cfg.inv_pclk = sig.pixclk_pol; + cfg.redge = 1; + cfg.csicnt1_rsv = 0; + + /* control reg 3 */ + cfg.frmcnt = 0; + cfg.frame_reset = 0; + cfg.csi_sup = 0; + cfg.zero_pack_en = 0; + cfg.ecc_int_en = 0; + cfg.ecc_auto_en = 0; + + csihw_set_config(&cfg); + + return 0; +} + +/*! + * csi_enable_prpif + * Enable or disable CSI-PrP interface + * @param enable Non-zero to enable, zero to disable + */ +void csi_enable_prpif(uint32_t enable) +{ + if (enable) { + g_csi_cfg.prp_if_en = 1; + g_csi_cfg.sof_inten = 0; + g_csi_cfg.pack_dir = 0; + } else { + g_csi_cfg.prp_if_en = 0; + g_csi_cfg.sof_inten = 1; + g_csi_cfg.pack_dir = 1; + } + + csihw_set_config(&g_csi_cfg); +} + +/*! + * csi_enable_mclk + * + * @param src enum define which source to control the clk + * CSI_MCLK_VF CSI_MCLK_ENC CSI_MCLK_RAW CSI_MCLK_I2C + * @param flag true to enable mclk, false to disable mclk + * @param wait true to wait 100ms make clock stable, false not wait + * + * @return 0 for success + */ +int32_t csi_enable_mclk(int src, bool flag, bool wait) +{ + if (flag == true) { + clk_enable(&csi_mclk); + if (wait == true) + msleep(10); + pr_debug("Enable csi clock from source %d\n", src); + gcsi_mclk_on = true; + } else { + clk_disable(&csi_mclk); + pr_debug("Disable csi clock from source %d\n", src); + gcsi_mclk_on = false; + } + + return 0; +} + +/*! + * csi_read_mclk_flag + * + * @return gcsi_mclk_source + */ +int csi_read_mclk_flag(void) +{ + return 0; +} + +void csi_set_callback(csi_irq_callback_t callback, void *data) +{ + g_callback = callback; + g_callback_data = data; +} + +static void _mclk_recalc(struct clk *clk) +{ + u32 div; + + div = (__raw_readl(CSI_CSICR1) & BIT_MCLKDIV) >> SHIFT_MCLKDIV; + div = (div + 1) * 2; + + clk->rate = clk->parent->rate / div; +} + +static unsigned long _mclk_round_rate(struct clk *clk, unsigned long rate) +{ + /* Keep CSI divider and change parent clock */ + if (clk->parent->round_rate) { + return clk->parent->round_rate(clk->parent, rate * 2); + } + return 0; +} + +static int _mclk_set_rate(struct clk *clk, unsigned long rate) +{ + int ret = -EINVAL; + + /* Keep CSI divider and change parent clock */ + if (clk->parent->set_rate) { + ret = clk->parent->set_rate(clk->parent, rate * 2); + if (ret == 0) { + clk->rate = clk->parent->rate / 2; + } + } + + return ret; +} + +static int _mclk_enable(struct clk *clk) +{ + __raw_writel(__raw_readl(CSI_CSICR1) | BIT_MCLKEN, CSI_CSICR1); + return 0; +} + +static void _mclk_disable(struct clk *clk) +{ + __raw_writel(__raw_readl(CSI_CSICR1) & ~BIT_MCLKEN, CSI_CSICR1); +} + +static struct clk csi_mclk = { + .name = "csi_clk", + .recalc = _mclk_recalc, + .round_rate = _mclk_round_rate, + .set_rate = _mclk_set_rate, + .enable = _mclk_enable, + .disable = _mclk_disable, +}; + +int32_t __init csi_init_module(void) +{ + int ret = 0; + struct clk *per_clk; + + per_clk = clk_get(NULL, "csi_perclk"); + if (IS_ERR(per_clk)) + return PTR_ERR(per_clk); + clk_put(per_clk); + csi_mclk.parent = per_clk; + clk_register(&csi_mclk); + clk_enable(per_clk); + csi_mclk.recalc(&csi_mclk); + + csihw_reset(); + + /* interrupt enable */ + ret = request_irq(MXC_INT_CSI, csi_irq_handler, 0, "csi", 0); + if (ret) + pr_debug("CSI error: irq request fail\n"); + + return ret; +} + +void __exit csi_cleanup_module(void) +{ + /* free irq */ + free_irq(MXC_INT_CSI, 0); + + clk_disable(&csi_mclk); +} + +module_init(csi_init_module); +module_exit(csi_cleanup_module); + +EXPORT_SYMBOL(csi_init_interface); +EXPORT_SYMBOL(csi_enable_mclk); +EXPORT_SYMBOL(csi_read_mclk_flag); +EXPORT_SYMBOL(csi_set_callback); +EXPORT_SYMBOL(csi_enable_prpif); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MX27 CSI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/mx27_csi.h b/drivers/media/video/mxc/capture/mx27_csi.h new file mode 100644 index 000000000000..9bd99781e626 --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_csi.h @@ -0,0 +1,167 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_csi.h + * + * @brief CMOS Sensor interface functions + * + * @ingroup CSI + */ + +#ifndef MX27_CSI_H +#define MX27_CSI_H + +#include <linux/io.h> + +/* reset values */ +#define CSICR1_RESET_VAL 0x40000800 +#define CSICR2_RESET_VAL 0x0 +#define CSICR3_RESET_VAL 0x0 + +/* csi control reg 1 */ +#define BIT_SWAP16_EN (0x1 << 31) +#define BIT_EXT_VSYNC (0x1 << 30) +#define BIT_EOF_INT_EN (0x1 << 29) +#define BIT_PRP_IF_EN (0x1 << 28) +#define BIT_CCIR_MODE (0x1 << 27) +#define BIT_COF_INT_EN (0x1 << 26) +#define BIT_SF_OR_INTEN (0x1 << 25) +#define BIT_RF_OR_INTEN (0x1 << 24) +#define BIT_STATFF_LEVEL (0x3 << 22) +#define BIT_STATFF_INTEN (0x1 << 21) +#define BIT_RXFF_LEVEL (0x3 << 19) +#define BIT_RXFF_INTEN (0x1 << 18) +#define BIT_SOF_POL (0x1 << 17) +#define BIT_SOF_INTEN (0x1 << 16) +#define BIT_MCLKDIV (0xF << 12) +#define BIT_HSYNC_POL (0x1 << 11) +#define BIT_CCIR_EN (0x1 << 10) +#define BIT_MCLKEN (0x1 << 9) +#define BIT_FCC (0x1 << 8) +#define BIT_PACK_DIR (0x1 << 7) +#define BIT_CLR_STATFIFO (0x1 << 6) +#define BIT_CLR_RXFIFO (0x1 << 5) +#define BIT_GCLK_MODE (0x1 << 4) +#define BIT_INV_DATA (0x1 << 3) +#define BIT_INV_PCLK (0x1 << 2) +#define BIT_REDGE (0x1 << 1) + +#define SHIFT_STATFF_LEVEL 22 +#define SHIFT_RXFF_LEVEL 19 +#define SHIFT_MCLKDIV 12 + +/* control reg 3 */ +#define BIT_FRMCNT (0xFFFF << 16) +#define BIT_FRMCNT_RST (0x1 << 15) +#define BIT_CSI_SUP (0x1 << 3) +#define BIT_ZERO_PACK_EN (0x1 << 2) +#define BIT_ECC_INT_EN (0x1 << 1) +#define BIT_ECC_AUTO_EN (0x1) + +#define SHIFT_FRMCNT 16 + +/* csi status reg */ +#define BIT_SFF_OR_INT (0x1 << 25) +#define BIT_RFF_OR_INT (0x1 << 24) +#define BIT_STATFF_INT (0x1 << 21) +#define BIT_RXFF_INT (0x1 << 18) +#define BIT_EOF_INT (0x1 << 17) +#define BIT_SOF_INT (0x1 << 16) +#define BIT_F2_INT (0x1 << 15) +#define BIT_F1_INT (0x1 << 14) +#define BIT_COF_INT (0x1 << 13) +#define BIT_ECC_INT (0x1 << 1) +#define BIT_DRDY (0x1 << 0) + +#define CSI_MCLK_VF 1 +#define CSI_MCLK_ENC 2 +#define CSI_MCLK_RAW 4 +#define CSI_MCLK_I2C 8 + +#define CSI_CSICR1 (IO_ADDRESS(CSI_BASE_ADDR)) +#define CSI_CSICR2 (IO_ADDRESS(CSI_BASE_ADDR + 0x4)) +#define CSI_CSISR (IO_ADDRESS(CSI_BASE_ADDR + 0x8)) +#define CSI_STATFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0xC)) +#define CSI_CSIRXFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0x10)) +#define CSI_CSIRXCNT (IO_ADDRESS(CSI_BASE_ADDR + 0x14)) +#define CSI_CSICR3 (IO_ADDRESS(CSI_BASE_ADDR + 0x1C)) + +#define CSI_CSIRXFIFO_PHYADDR (CSI_BASE_ADDR + 0x10) + +static __inline void csi_clear_status(unsigned long status) +{ + __raw_writel(status, CSI_CSISR); +} + +typedef struct { + unsigned data_width:3; + unsigned clk_mode:2; + unsigned ext_vsync:1; + unsigned Vsync_pol:1; + unsigned Hsync_pol:1; + unsigned pixclk_pol:1; + unsigned data_pol:1; + unsigned sens_clksrc:1; +} csi_signal_cfg_t; + +typedef struct { + /* control reg 1 */ + unsigned int swap16_en:1; + unsigned int ext_vsync:1; + unsigned int eof_int_en:1; + unsigned int prp_if_en:1; + unsigned int ccir_mode:1; + unsigned int cof_int_en:1; + unsigned int sf_or_inten:1; + unsigned int rf_or_inten:1; + unsigned int statff_level:2; + unsigned int staff_inten:1; + unsigned int rxff_level:2; + unsigned int rxff_inten:1; + unsigned int sof_pol:1; + unsigned int sof_inten:1; + unsigned int mclkdiv:4; + unsigned int hsync_pol:1; + unsigned int ccir_en:1; + unsigned int mclken:1; + unsigned int fcc:1; + unsigned int pack_dir:1; + unsigned int gclk_mode:1; + unsigned int inv_data:1; + unsigned int inv_pclk:1; + unsigned int redge:1; + unsigned int csicnt1_rsv:1; + + /* control reg 3 */ + unsigned int frmcnt:16; + unsigned int frame_reset:1; + unsigned int csi_sup:1; + unsigned int zero_pack_en:1; + unsigned int ecc_int_en:1; + unsigned int ecc_auto_en:1; + + /* fifo counter */ + unsigned int rxcnt; +} csi_config_t; + +typedef void (*csi_irq_callback_t) (void *data, unsigned long status); + +int32_t csi_enable_mclk(int src, bool flag, bool wait); +int32_t csi_init_interface(uint16_t width, uint16_t height, + uint32_t pixel_fmt, csi_signal_cfg_t sig); +int csi_read_mclk_flag(void); +void csi_set_callback(csi_irq_callback_t callback, void *data); +void csi_enable_prpif(uint32_t enable); + +#endif diff --git a/drivers/media/video/mxc/capture/mx27_prp.h b/drivers/media/video/mxc/capture/mx27_prp.h new file mode 100644 index 000000000000..e32e9029daff --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_prp.h @@ -0,0 +1,310 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_prp.h + * + * @brief Header file for MX27 V4L2 capture driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#ifndef __MX27_PRP_H__ +#define __MX27_PRP_H__ + +#define PRP_REG(ofs) (IO_ADDRESS(EMMA_BASE_ADDR) + ofs) + +/* Register definitions of PrP */ +#define PRP_CNTL PRP_REG(0x00) +#define PRP_INTRCNTL PRP_REG(0x04) +#define PRP_INTRSTATUS PRP_REG(0x08) +#define PRP_SOURCE_Y_PTR PRP_REG(0x0C) +#define PRP_SOURCE_CB_PTR PRP_REG(0x10) +#define PRP_SOURCE_CR_PTR PRP_REG(0x14) +#define PRP_DEST_RGB1_PTR PRP_REG(0x18) +#define PRP_DEST_RGB2_PTR PRP_REG(0x1C) +#define PRP_DEST_Y_PTR PRP_REG(0x20) +#define PRP_DEST_CB_PTR PRP_REG(0x24) +#define PRP_DEST_CR_PTR PRP_REG(0x28) +#define PRP_SOURCE_FRAME_SIZE PRP_REG(0x2C) +#define PRP_CH1_LINE_STRIDE PRP_REG(0x30) +#define PRP_SRC_PIXEL_FORMAT_CNTL PRP_REG(0x34) +#define PRP_CH1_PIXEL_FORMAT_CNTL PRP_REG(0x38) +#define PRP_CH1_OUT_IMAGE_SIZE PRP_REG(0x3C) +#define PRP_CH2_OUT_IMAGE_SIZE PRP_REG(0x40) +#define PRP_SOURCE_LINE_STRIDE PRP_REG(0x44) +#define PRP_CSC_COEF_012 PRP_REG(0x48) +#define PRP_CSC_COEF_345 PRP_REG(0x4C) +#define PRP_CSC_COEF_678 PRP_REG(0x50) +#define PRP_CH1_RZ_HORI_COEF1 PRP_REG(0x54) +#define PRP_CH1_RZ_HORI_COEF2 PRP_REG(0x58) +#define PRP_CH1_RZ_HORI_VALID PRP_REG(0x5C) +#define PRP_CH1_RZ_VERT_COEF1 PRP_REG(0x60) +#define PRP_CH1_RZ_VERT_COEF2 PRP_REG(0x64) +#define PRP_CH1_RZ_VERT_VALID PRP_REG(0x68) +#define PRP_CH2_RZ_HORI_COEF1 PRP_REG(0x6C) +#define PRP_CH2_RZ_HORI_COEF2 PRP_REG(0x70) +#define PRP_CH2_RZ_HORI_VALID PRP_REG(0x74) +#define PRP_CH2_RZ_VERT_COEF1 PRP_REG(0x78) +#define PRP_CH2_RZ_VERT_COEF2 PRP_REG(0x7C) +#define PRP_CH2_RZ_VERT_VALID PRP_REG(0x80) + +#define B_SET(b) (1 << (b)) + +/* Bit definitions for PrP control register */ +#define PRP_CNTL_RSTVAL 0x28 +#define PRP_CNTL_CH1EN B_SET(0) +#define PRP_CNTL_CH2EN B_SET(1) +#define PRP_CNTL_CSI B_SET(2) +#define PRP_CNTL_IN_32 B_SET(3) +#define PRP_CNTL_IN_RGB B_SET(4) +#define PRP_CNTL_IN_YUV420 0 +#define PRP_CNTL_IN_YUV422 PRP_CNTL_IN_32 +#define PRP_CNTL_IN_RGB16 PRP_CNTL_IN_RGB +#define PRP_CNTL_IN_RGB32 (PRP_CNTL_IN_RGB | PRP_CNTL_IN_32) +#define PRP_CNTL_CH1_RGB8 0 +#define PRP_CNTL_CH1_RGB16 B_SET(5) +#define PRP_CNTL_CH1_RGB32 B_SET(6) +#define PRP_CNTL_CH1_YUV422 (B_SET(5) | B_SET(6)) +#define PRP_CNTL_CH2_YUV420 0 +#define PRP_CNTL_CH2_YUV422 B_SET(7) +#define PRP_CNTL_CH2_YUV444 B_SET(8) +#define PRP_CNTL_CH1_LOOP B_SET(9) +#define PRP_CNTL_CH2_LOOP B_SET(10) +#define PRP_CNTL_AUTODROP B_SET(11) +#define PRP_CNTL_RST B_SET(12) +#define PRP_CNTL_CNTREN B_SET(13) +#define PRP_CNTL_WINEN B_SET(14) +#define PRP_CNTL_UNCHAIN B_SET(15) +#define PRP_CNTL_IN_SKIP_NONE 0 +#define PRP_CNTL_IN_SKIP_1_2 B_SET(16) +#define PRP_CNTL_IN_SKIP_1_3 B_SET(17) +#define PRP_CNTL_IN_SKIP_2_3 (B_SET(16) | B_SET(17)) +#define PRP_CNTL_IN_SKIP_1_4 B_SET(18) +#define PRP_CNTL_IN_SKIP_3_4 (B_SET(16) | B_SET(18)) +#define PRP_CNTL_IN_SKIP_2_5 (B_SET(17) | B_SET(18)) +#define PRP_CNTL_IN_SKIP_3_5 (B_SET(16) | B_SET(17) | B_SET(18)) +#define PRP_CNTL_CH1_SKIP_NONE 0 +#define PRP_CNTL_CH1_SKIP_1_2 B_SET(19) +#define PRP_CNTL_CH1_SKIP_1_3 B_SET(20) +#define PRP_CNTL_CH1_SKIP_2_3 (B_SET(19) | B_SET(20)) +#define PRP_CNTL_CH1_SKIP_1_4 B_SET(21) +#define PRP_CNTL_CH1_SKIP_3_4 (B_SET(19) | B_SET(21)) +#define PRP_CNTL_CH1_SKIP_2_5 (B_SET(20) | B_SET(21)) +#define PRP_CNTL_CH1_SKIP_3_5 (B_SET(19) | B_SET(20) | B_SET(21)) +#define PRP_CNTL_CH2_SKIP_NONE 0 +#define PRP_CNTL_CH2_SKIP_1_2 B_SET(22) +#define PRP_CNTL_CH2_SKIP_1_3 B_SET(23) +#define PRP_CNTL_CH2_SKIP_2_3 (B_SET(22) | B_SET(23)) +#define PRP_CNTL_CH2_SKIP_1_4 B_SET(24) +#define PRP_CNTL_CH2_SKIP_3_4 (B_SET(22) | B_SET(24)) +#define PRP_CNTL_CH2_SKIP_2_5 (B_SET(23) | B_SET(24)) +#define PRP_CNTL_CH2_SKIP_3_5 (B_SET(22) | B_SET(23) | B_SET(24)) +#define PRP_CNTL_FIFO_I128 0 +#define PRP_CNTL_FIFO_I96 B_SET(25) +#define PRP_CNTL_FIFO_I64 B_SET(26) +#define PRP_CNTL_FIFO_I32 (B_SET(25) | B_SET(26)) +#define PRP_CNTL_FIFO_O64 0 +#define PRP_CNTL_FIFO_O48 B_SET(27) +#define PRP_CNTL_FIFO_O32 B_SET(28) +#define PRP_CNTL_FIFO_O16 (B_SET(27) | B_SET(28)) +#define PRP_CNTL_CH2B1 B_SET(29) +#define PRP_CNTL_CH2B2 B_SET(30) +#define PRP_CNTL_CH2_FLOWEN B_SET(31) + +/* Bit definitions for PrP interrupt control register */ +#define PRP_INTRCNTL_RDERR B_SET(0) +#define PRP_INTRCNTL_CH1WERR B_SET(1) +#define PRP_INTRCNTL_CH2WERR B_SET(2) +#define PRP_INTRCNTL_CH1FC B_SET(3) +#define PRP_INTRCNTL_CH2FC B_SET(5) +#define PRP_INTRCNTL_LBOVF B_SET(7) +#define PRP_INTRCNTL_CH2OVF B_SET(8) + +/* Bit definitions for PrP interrupt status register */ +#define PRP_INTRSTAT_RDERR B_SET(0) +#define PRP_INTRSTAT_CH1WERR B_SET(1) +#define PRP_INTRSTAT_CH2WERR B_SET(2) +#define PRP_INTRSTAT_CH2BUF2 B_SET(3) +#define PRP_INTRSTAT_CH2BUF1 B_SET(4) +#define PRP_INTRSTAT_CH1BUF2 B_SET(5) +#define PRP_INTRSTAT_CH1BUF1 B_SET(6) +#define PRP_INTRSTAT_LBOVF B_SET(7) +#define PRP_INTRSTAT_CH2OVF B_SET(8) + +#define PRP_CHANNEL_1 0x1 +#define PRP_CHANNEL_2 0x2 + +/* PRP-CSI config */ +#define PRP_CSI_EN 0x80 +#define PRP_CSI_LOOP (0x40 | PRP_CSI_EN) +#define PRP_CSI_IRQ_FRM (0x08 | PRP_CSI_LOOP) +#define PRP_CSI_IRQ_CH1ERR (0x10 | PRP_CSI_LOOP) +#define PRP_CSI_IRQ_CH2ERR (0x20 | PRP_CSI_LOOP) +#define PRP_CSI_IRQ_ALL (0x38 | PRP_CSI_LOOP) +#define PRP_CSI_SKIP_NONE 0 +#define PRP_CSI_SKIP_1OF2 1 +#define PRP_CSI_SKIP_1OF3 2 +#define PRP_CSI_SKIP_2OF3 3 +#define PRP_CSI_SKIP_1OF4 4 +#define PRP_CSI_SKIP_3OF4 5 +#define PRP_CSI_SKIP_2OF5 6 +#define PRP_CSI_SKIP_4OF5 7 + +#define PRP_PIXIN_RGB565 0x2CA00565 +#define PRP_PIXIN_RGB888 0x41000888 +#define PRP_PIXIN_YUV420 0 +#define PRP_PIXIN_YUYV 0x22000888 +#define PRP_PIXIN_YVYU 0x20100888 +#define PRP_PIXIN_UYVY 0x03080888 +#define PRP_PIXIN_VYUY 0x01180888 +#define PRP_PIXIN_YUV422 0x62080888 + +#define PRP_PIX1_RGB332 0x14400322 +#define PRP_PIX1_RGB565 0x2CA00565 +#define PRP_PIX1_RGB888 0x41000888 +#define PRP_PIX1_YUYV 0x62000888 +#define PRP_PIX1_YVYU 0x60100888 +#define PRP_PIX1_UYVY 0x43080888 +#define PRP_PIX1_VYUY 0x41180888 +#define PRP_PIX1_UNUSED 0 + +#define PRP_PIX2_YUV420 0 +#define PRP_PIX2_YUV422 1 +#define PRP_PIX2_YUV444 4 +#define PRP_PIX2_UNUSED 8 + +#define PRP_ALGO_WIDTH_ANY 0 +#define PRP_ALGO_HEIGHT_ANY 0 +#define PRP_ALGO_WIDTH_BIL 1 +#define PRP_ALGO_WIDTH_AVG 2 +#define PRP_ALGO_HEIGHT_BIL 4 +#define PRP_ALGO_HEIGHT_AVG 8 +#define PRP_ALGO_BYPASS 0x10 + +typedef struct _emma_prp_ratio { + unsigned short num; + unsigned short den; +} emma_prp_ratio; + +/* + * The following definitions are for resizing. Definition values must not + * be changed otherwise decision logic will be wrong. + */ +#define SCALE_RETRY 16 /* retry times if ratio is not supported */ + +#define BC_COEF 3 +#define MAX_TBL 20 +#define SZ_COEF (1 << BC_COEF) + +#define ALGO_AUTO 0 +#define ALGO_BIL 1 +#define ALGO_AVG 2 + +typedef struct { + char tbl[20]; /* table entries */ + char len; /* table length used */ + char algo; /* ALGO_xxx */ + char ratio[20]; /* ratios used */ +} scale_t; + +/* + * structure for prp scaling. + * algorithm - bilinear or averaging for each axis + * PRP_ALGO_WIDTH_x | PRP_ALGO_HEIGHT_x | PRP_ALGO_BYPASS + * PRP_ALGO_BYPASS - Ch1 will not use Ch2 scaling with this flag + */ +typedef struct _emma_prp_scale { + unsigned char algo; + emma_prp_ratio width; + emma_prp_ratio height; +} emma_prp_scale; + +typedef struct emma_prp_cfg { + unsigned int in_pix; /* PRP_PIXIN_xxx */ + unsigned short in_width; /* image width, 32 - 2044 */ + unsigned short in_height; /* image height, 32 - 2044 */ + unsigned char in_csi; /* PRP_CSI_SKIP_x | PRP_CSI_LOOP */ + unsigned short in_line_stride; /* in_line_stride and in_line_skip */ + unsigned short in_line_skip; /* allow cropping from CSI */ + unsigned int in_ptr; /* bus address */ + /* + * in_csc[9] = 1 -> Y-16 + * if in_csc[1..9] == 0 + * in_csc[0] represents YUV range 0-3 = A0,A1,B0,B1; + * else + * in_csc[0..9] represents either format + */ + unsigned short in_csc[10]; + + unsigned char ch2_pix; /* PRP_PIX2_xxx */ + emma_prp_scale ch2_scale; /* resizing paramters */ + unsigned short ch2_width; /* 4-2044, 0 = scaled */ + unsigned short ch2_height; /* 4-2044, 0 = scaled */ + unsigned int ch2_ptr; /* bus addr */ + unsigned int ch2_ptr2; /* bus addr for 2nd buf (loop mode) */ + unsigned char ch2_csi; /* PRP_CSI_SKIP_x | PRP_CSI_LOOP */ + + unsigned int ch1_pix; /* PRP_PIX1_xxx */ + emma_prp_scale ch1_scale; /* resizing parameters */ + unsigned short ch1_width; /* 4-2044, 0 = scaled */ + unsigned short ch1_height; /* 4-2044, 0 = scaled */ + unsigned short ch1_stride; /* 4-4088, 0 = ch1_width */ + unsigned int ch1_ptr; /* bus addr */ + unsigned int ch1_ptr2; /* bus addr for 2nd buf (loop mode) */ + unsigned char ch1_csi; /* PRP_CSI_SKIP_x | PRP_CSI_LOOP */ + + /* + * channel resizing coefficients + * scale[0] for channel 1 width + * scale[1] for channel 1 height + * scale[2] for channel 2 width + * scale[3] for channel 2 height + */ + scale_t scale[4]; +} emma_prp_cfg; + +int prphw_reset(void); +int prphw_enable(int channel); +int prphw_disable(int channel); +int prphw_inptr(emma_prp_cfg *); +int prphw_ch1ptr(emma_prp_cfg *); +int prphw_ch1ptr2(emma_prp_cfg *); +int prphw_ch2ptr(emma_prp_cfg *); +int prphw_ch2ptr2(emma_prp_cfg *); +int prphw_cfg(emma_prp_cfg *); +int prphw_isr(void); +void prphw_init(void); +void prphw_exit(void); + +/* + * scale out coefficient table + * din in scale numerator + * dout in scale denominator + * inv in pre-scale dimension + * vout in/out post-scale output dimension + * pout out post-scale internal dimension [opt] + * retry in retry times (round the output length) when need + */ +int prp_scale(scale_t * pscale, int din, int dout, int inv, + unsigned short *vout, unsigned short *pout, int retry); + +int prp_init(void *dev_id); +void prp_exit(void *dev_id); +int prp_enc_select(void *data); +int prp_enc_deselect(void *data); +int prp_vf_select(void *data); +int prp_vf_deselect(void *data); +int prp_still_select(void *data); +int prp_still_deselect(void *data); + +#endif /* __MX27_PRP_H__ */ diff --git a/drivers/media/video/mxc/capture/mx27_prphw.c b/drivers/media/video/mxc/capture/mx27_prphw.c new file mode 100644 index 000000000000..c56a6df1716e --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_prphw.c @@ -0,0 +1,1099 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_prphw.c + * + * @brief MX27 Video For Linux 2 capture driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/clk.h> +#include <asm/io.h> +#include <linux/delay.h> + +#include "mx27_prp.h" + +#define PRP_MIN_IN_WIDTH 32 +#define PRP_MAX_IN_WIDTH 2044 +#define PRP_MIN_IN_HEIGHT 32 +#define PRP_MAX_IN_HEIGHT 2044 + +typedef struct _coeff_t { + unsigned long coeff[2]; + unsigned long cntl; +} coeff_t[2][2]; + +static coeff_t *PRP_RSZ_COEFF = (coeff_t *) PRP_CH1_RZ_HORI_COEF1; + +static unsigned char scale_get(scale_t * t, + unsigned char *i, unsigned char *out); +static int gcd(int x, int y); +static int ratio(int x, int y, int *den); +static int prp_scale_bilinear(scale_t * t, int coeff, int base, int nxt); +static int prp_scale_ave(scale_t * t, unsigned char base); +static int ave_scale(scale_t * t, int inv, int outv); +static int scale(scale_t * t, int inv, int outv); + +/*! + * @param t table + * @param i table index + * @param out bilinear # input pixels to advance + * average whether result is ready for output + * @return coefficient +*/ +static unsigned char scale_get(scale_t * t, unsigned char *i, + unsigned char *out) +{ + unsigned char c; + + c = t->tbl[*i]; + (*i)++; + *i %= t->len; + + if (out) { + if (t->algo == ALGO_BIL) { + for ((*out) = 1; + (*i) && ((*i) < t->len) && !t->tbl[(*i)]; (*i)++) { + (*out)++; + } + if ((*i) == t->len) + (*i) = 0; + } else + *out = c >> BC_COEF; + } + + c &= SZ_COEF - 1; + + if (c == SZ_COEF - 1) + c = SZ_COEF; + + return c; +} + +/*! + * @brief Get maximum common divisor. + * @param x First input value + * @param y Second input value + * @return Maximum common divisor of x and y + */ +static int gcd(int x, int y) +{ + int k; + + if (x < y) { + k = x; + x = y; + y = k; + } + + while ((k = x % y)) { + x = y; + y = k; + } + + return y; +} + +/*! + * @brief Get ratio. + * @param x First input value + * @param y Second input value + * @param den Denominator of the ratio (corresponding to y) + * @return Numerator of the ratio (corresponding to x) + */ +static int ratio(int x, int y, int *den) +{ + int g; + + if (!x || !y) + return 0; + + g = gcd(x, y); + *den = y / g; + + return x / g; +} + +/*! + * @brief Build PrP coefficient entry based on bilinear algorithm + * + * @param t The pointer to scale_t structure + * @param coeff The weighting coefficient + * @param base The base of the coefficient + * @param nxt Number of pixels to be read + * + * @return The length of current coefficient table on success + * -1 on failure + */ +static int prp_scale_bilinear(scale_t * t, int coeff, int base, int nxt) +{ + int i; + + if (t->len >= sizeof(t->tbl)) + return -1; + + coeff = ((coeff << BC_COEF) + (base >> 1)) / base; + if (coeff >= SZ_COEF - 1) + coeff--; + + coeff |= SZ_COEF; + t->tbl[(int)t->len++] = (unsigned char)coeff; + + for (i = 1; i < nxt; i++) { + if (t->len >= MAX_TBL) + return -1; + + t->tbl[(int)t->len++] = 0; + } + + return t->len; +} + +#define _bary(name) static const unsigned char name[] + +_bary(c1) = { +7}; + +_bary(c2) = { +4, 4}; + +_bary(c3) = { +2, 4, 2}; + +_bary(c4) = { +2, 2, 2, 2}; + +_bary(c5) = { +1, 2, 2, 2, 1}; + +_bary(c6) = { +1, 1, 2, 2, 1, 1}; + +_bary(c7) = { +1, 1, 1, 2, 1, 1, 1}; + +_bary(c8) = { +1, 1, 1, 1, 1, 1, 1, 1}; + +_bary(c9) = { +1, 1, 1, 1, 1, 1, 1, 1, 0}; + +_bary(c10) = { +0, 1, 1, 1, 1, 1, 1, 1, 1, 0}; + +_bary(c11) = { +0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0}; + +_bary(c12) = { +0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0}; + +_bary(c13) = { +0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0}; + +_bary(c14) = { +0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0}; + +_bary(c15) = { +0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0}; + +_bary(c16) = { +1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}; + +_bary(c17) = { +0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}; + +_bary(c18) = { +0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0}; + +_bary(c19) = { +0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0}; + +_bary(c20) = { +0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0}; + +static const unsigned char *ave_coeff[] = { + c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, + c11, c12, c13, c14, c15, c16, c17, c18, c19, c20 +}; + +/*! + * @brief Build PrP coefficient table based on average algorithm + * + * @param t The pointer to scale_t structure + * @param base The base of the coefficient + * + * @return The length of current coefficient table on success + * -1 on failure + */ +static int prp_scale_ave(scale_t * t, unsigned char base) +{ + if (t->len + base > sizeof(t->tbl)) + return -1; + + memcpy(&t->tbl[(int)t->len], ave_coeff[(int)base - 1], base); + t->len = (unsigned char)(t->len + base); + t->tbl[t->len - 1] |= SZ_COEF; + + return t->len; +} + +/*! + * @brief Build PrP coefficient table based on average algorithm + * + * @param t The pointer to scale_t structure + * @param inv Input resolution + * @param outv Output resolution + * + * @return The length of current coefficient table on success + * -1 on failure + */ +static int ave_scale(scale_t * t, int inv, int outv) +{ + int ratio_count; + + ratio_count = 0; + if (outv != 1) { + unsigned char a[20]; + int v; + + /* split n:m into multiple n[i]:1 */ + for (v = 0; v < outv; v++) + a[v] = (unsigned char)(inv / outv); + + inv %= outv; + if (inv) { + /* find start of next layer */ + v = (outv - inv) >> 1; + inv += v; + for (; v < inv; v++) + a[v]++; + } + + for (v = 0; v < outv; v++) { + if (prp_scale_ave(t, a[v]) < 0) + return -1; + + t->ratio[ratio_count] = a[v]; + ratio_count++; + } + } else if (prp_scale_ave(t, inv) < 0) { + return -1; + } else { + t->ratio[ratio_count++] = (char)inv; + ratio_count++; + } + + return t->len; +} + +/*! + * @brief Build PrP coefficient table + * + * @param t The pointer to scale_t structure + * @param inv input resolution reduced ratio + * @param outv output resolution reduced ratio + * + * @return The length of current coefficient table on success + * -1 on failure + */ +static int scale(scale_t * t, int inv, int outv) +{ + int v; /* overflow counter */ + int coeff, nxt; /* table output */ + + t->len = 0; + if (t->algo == ALGO_AUTO) { + /* automatic choice - bilinear for shrinking less than 2:1 */ + t->algo = ((outv != inv) && ((2 * outv) > inv)) ? + ALGO_BIL : ALGO_AVG; + } + + /* 1:1 resize must use averaging, bilinear will hang */ + if ((inv == outv) && (t->algo == ALGO_BIL)) { + pr_debug("Warning: 1:1 resize must use averaging algo\n"); + t->algo = ALGO_AVG; + } + + memset(t->tbl, 0, sizeof(t->tbl)); + if (t->algo == ALGO_BIL) { + t->ratio[0] = (char)inv; + t->ratio[1] = (char)outv; + } else + memset(t->ratio, 0, sizeof(t->ratio)); + + if (inv == outv) { + /* force scaling */ + t->ratio[0] = 1; + if (t->algo == ALGO_BIL) + t->ratio[1] = 1; + + return prp_scale_ave(t, 1); + } + + if (inv < outv) { + pr_debug("Upscaling not supported %d:%d\n", inv, outv); + return -1; + } + + if (t->algo != ALGO_BIL) + return ave_scale(t, inv, outv); + + v = 0; + if (inv >= 2 * outv) { + /* downscale: >=2:1 bilinear approximation */ + coeff = inv - 2 * outv; + v = 0; + nxt = 0; + do { + v += coeff; + nxt = 2; + while (v >= outv) { + v -= outv; + nxt++; + } + + if (prp_scale_bilinear(t, 1, 2, nxt) < 0) + return -1; + } while (v); + } else { + /* downscale: bilinear */ + int in_pos_inc = 2 * outv; + int out_pos = inv; + int out_pos_inc = 2 * inv; + int init_carry = inv - outv; + int carry = init_carry; + + v = outv + in_pos_inc; + do { + coeff = v - out_pos; + out_pos += out_pos_inc; + carry += out_pos_inc; + for (nxt = 0; v < out_pos; nxt++) { + v += in_pos_inc; + carry -= in_pos_inc; + } + if (prp_scale_bilinear(t, coeff, in_pos_inc, nxt) < 0) + return -1; + } while (carry != init_carry); + } + return t->len; +} + +/*! + * @brief Build PrP coefficient table + * + * @param pscale The pointer to scale_t structure which holdes + * coefficient tables + * @param din Scale ratio numerator + * @param dout Scale ratio denominator + * @param inv Input resolution + * @param vout Output resolution + * @param pout Internal output resolution + * @param retry Retry times (round the output length) when need + * + * @return Zero on success, others on failure + */ +int prp_scale(scale_t * pscale, int din, int dout, int inv, + unsigned short *vout, unsigned short *pout, int retry) +{ + int num; + int den; + unsigned short outv; + + /* auto-generation of values */ + if (!(dout && din)) { + if (!*vout) + dout = din = 1; + else { + din = inv; + dout = *vout; + } + } + + if (din < dout) { + pr_debug("Scale err, unsupported ratio %d : %d\n", din, dout); + return -1; + } + + lp_retry: + num = ratio(din, dout, &den); + if (!num) { + pr_debug("Scale err, unsupported ratio %d : %d\n", din, dout); + return -1; + } + + if (num > MAX_TBL || scale(pscale, num, den) < 0) { + dout++; + if (retry--) + goto lp_retry; + + pr_debug("Scale err, unsupported ratio %d : %d\n", num, den); + return -1; + } + + if (pscale->algo == ALGO_BIL) { + unsigned char i, j, k; + + outv = + (unsigned short)(inv / pscale->ratio[0] * pscale->ratio[1]); + inv %= pscale->ratio[0]; + for (i = j = 0; inv > 0; j++) { + unsigned char nxt; + + k = scale_get(pscale, &i, &nxt); + if (inv == 1 && k < SZ_COEF) { + /* needs 2 pixels for this output */ + break; + } + inv -= nxt; + } + outv = outv + j; + } else { + unsigned char i, tot; + + for (tot = i = 0; pscale->ratio[i]; i++) + tot = tot + pscale->ratio[i]; + + outv = (unsigned short)(inv / tot) * i; + inv %= tot; + for (i = 0; inv > 0; i++, outv++) + inv -= pscale->ratio[i]; + } + + if (!(*vout) || ((*vout) > outv)) + *vout = outv; + + if (pout) + *pout = outv; + + return 0; +} + +/*! + * @brief Reset PrP block + */ +int prphw_reset(void) +{ + unsigned long val; + unsigned long flag; + int i; + + flag = PRP_CNTL_RST; + val = PRP_CNTL_RSTVAL; + + __raw_writel(flag, PRP_CNTL); + + /* timeout */ + for (i = 0; i < 1000; i++) { + if (!(__raw_readl(PRP_CNTL) & flag)) { + pr_debug("PrP reset over\n"); + break; + } + msleep(1); + } + + /* verify reset value */ + if (__raw_readl(PRP_CNTL) != val) { + pr_info("PrP reset err, val = 0x%08X\n", __raw_readl(PRP_CNTL)); + return -1; + } + + return 0; +} + +/*! + * @brief Enable PrP channel. + * @param channel Channel number to be enabled + * @return Zero on success, others on failure + */ +int prphw_enable(int channel) +{ + unsigned long val; + + val = __raw_readl(PRP_CNTL); + if (channel & PRP_CHANNEL_1) + val |= PRP_CNTL_CH1EN; + if (channel & PRP_CHANNEL_2) + val |= (PRP_CNTL_CH2EN | PRP_CNTL_CH2_FLOWEN); + + __raw_writel(val, PRP_CNTL); + + return 0; +} + +/*! + * @brief Disable PrP channel. + * @param channel Channel number to be disable + * @return Zero on success, others on failure + */ +int prphw_disable(int channel) +{ + unsigned long val; + + val = __raw_readl(PRP_CNTL); + if (channel & PRP_CHANNEL_1) + val &= ~PRP_CNTL_CH1EN; + if (channel & PRP_CHANNEL_2) + val &= ~(PRP_CNTL_CH2EN | PRP_CNTL_CH2_FLOWEN); + + __raw_writel(val, PRP_CNTL); + + return 0; +} + +/*! + * @brief Set PrP input buffer address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_inptr(emma_prp_cfg * cfg) +{ + if (cfg->in_csi & PRP_CSI_EN) + return -1; + + __raw_writel(cfg->in_ptr, PRP_SOURCE_Y_PTR); + if (cfg->in_pix == PRP_PIXIN_YUV420) { + u32 size; + + size = cfg->in_line_stride * cfg->in_height; + __raw_writel(cfg->in_ptr + size, PRP_SOURCE_CB_PTR); + __raw_writel(cfg->in_ptr + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } + return 0; +} + +/*! + * @brief Set PrP channel 1 output buffer 1 address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_ch1ptr(emma_prp_cfg * cfg) +{ + if (cfg->ch1_pix == PRP_PIX1_UNUSED) + return -1; + + __raw_writel(cfg->ch1_ptr, PRP_DEST_RGB1_PTR); + + /* support double buffer in loop mode only */ + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (cfg->ch1_ptr2) + __raw_writel(cfg->ch1_ptr2, PRP_DEST_RGB2_PTR); + else + __raw_writel(cfg->ch1_ptr, PRP_DEST_RGB2_PTR); + } + + return 0; +} + +/*! + * @brief Set PrP channel 1 output buffer 2 address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_ch1ptr2(emma_prp_cfg * cfg) +{ + if (cfg->ch1_pix == PRP_PIX1_UNUSED || + (cfg->in_csi & PRP_CSI_LOOP) != PRP_CSI_LOOP) + return -1; + + if (cfg->ch1_ptr2) + __raw_writel(cfg->ch1_ptr2, PRP_DEST_RGB2_PTR); + else + return -1; + + return 0; +} + +/*! + * @brief Set PrP channel 2 output buffer 1 address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_ch2ptr(emma_prp_cfg * cfg) +{ + u32 size; + + if (cfg->ch2_pix == PRP_PIX2_UNUSED) + return -1; + + __raw_writel(cfg->ch2_ptr, PRP_DEST_Y_PTR); + + if (cfg->ch2_pix == PRP_PIX2_YUV420) { + size = cfg->ch2_width * cfg->ch2_height; + __raw_writel(cfg->ch2_ptr + size, PRP_DEST_CB_PTR); + __raw_writel(cfg->ch2_ptr + size + (size >> 2), + PRP_DEST_CR_PTR); + } + + __raw_writel(__raw_readl(PRP_CNTL) | PRP_CNTL_CH2B1, PRP_CNTL); + return 0; +} + +/*! + * @brief Set PrP channel 2 output buffer 2 address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_ch2ptr2(emma_prp_cfg * cfg) +{ + u32 size; + + if (cfg->ch2_pix == PRP_PIX2_UNUSED || + (cfg->in_csi & PRP_CSI_LOOP) != PRP_CSI_LOOP) + return -1; + + __raw_writel(cfg->ch2_ptr2, PRP_SOURCE_Y_PTR); + if (cfg->ch2_pix == PRP_PIX2_YUV420) { + size = cfg->ch2_width * cfg->ch2_height; + __raw_writel(cfg->ch2_ptr2 + size, PRP_SOURCE_CB_PTR); + __raw_writel(cfg->ch2_ptr2 + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } + + __raw_writel(__raw_readl(PRP_CNTL) | PRP_CNTL_CH2B2, PRP_CNTL); + return 0; +} + +/*! + * @brief Build CSC table + * @param csc CSC table + * in csc[0]=index 0..3 : A.1 A.0 B.1 B.0 + * csc[1]=direction 0 : YUV2RGB 1 : RGB2YUV + * out csc[0..4] are coefficients c[9] is offset + * csc[0..8] are coefficients c[9] is offset + */ +void csc_tbl(short csc[10]) +{ + static const unsigned short _r2y[][9] = { + {0x4D, 0x4B, 0x3A, 0x57, 0x55, 0x40, 0x40, 0x6B, 0x29}, + {0x42, 0x41, 0x32, 0x4C, 0x4A, 0x38, 0x38, 0x5E, 0x24}, + {0x36, 0x5C, 0x25, 0x3B, 0x63, 0x40, 0x40, 0x74, 0x18}, + {0x2F, 0x4F, 0x20, 0x34, 0x57, 0x38, 0x38, 0x66, 0x15}, + }; + static const unsigned short _y2r[][5] = { + {0x80, 0xb4, 0x2c, 0x5b, 0x0e4}, + {0x95, 0xcc, 0x32, 0x68, 0x104}, + {0x80, 0xca, 0x18, 0x3c, 0x0ec}, + {0x95, 0xe5, 0x1b, 0x44, 0x1e0}, + }; + unsigned short *_csc; + int _csclen; + + csc[9] = csc[0] & 1; + _csclen = csc[0] & 3; + + if (csc[1]) { + _csc = (unsigned short *)_r2y[_csclen]; + _csclen = sizeof(_r2y[0]); + } else { + _csc = (unsigned short *)_y2r[_csclen]; + _csclen = sizeof(_y2r[0]); + memset(csc + 5, 0, sizeof(short) * 4); + } + memcpy(csc, _csc, _csclen); +} + +/*! + * @brief Setup PrP resize coefficient registers + * + * @param ch PrP channel number + * @param dir Direction, 0 - horizontal, 1 - vertical + * @param scale The pointer to scale_t structure + */ +static void prp_set_scaler(int ch, int dir, scale_t * scale) +{ + int i; + unsigned int coeff[2]; + unsigned int valid; + + for (coeff[0] = coeff[1] = valid = 0, i = 19; i >= 0; i--) { + int j; + + j = i > 9 ? 1 : 0; + coeff[j] = (coeff[j] << BC_COEF) | + (scale->tbl[i] & (SZ_COEF - 1)); + + if (i == 5 || i == 15) + coeff[j] <<= 1; + + valid = (valid << 1) | (scale->tbl[i] >> BC_COEF); + } + + valid |= (scale->len << 24) | ((2 - scale->algo) << 31); + + for (i = 0; i < 2; i++) + (*PRP_RSZ_COEFF)[1 - ch][dir].coeff[i] = coeff[i]; + + (*PRP_RSZ_COEFF)[1 - ch][dir].cntl = valid; +} + +/*! + * @brief Setup PrP registers relevant to input. + * @param cfg Pointer to PrP configuration parameter + * @param prp_cntl Holds the value for PrP control register + * @return Zero on success, others on failure + */ +static int prphw_input_cfg(emma_prp_cfg * cfg, unsigned long *prp_cntl) +{ + unsigned long mask; + + switch (cfg->in_pix) { + case PRP_PIXIN_YUV420: + *prp_cntl |= PRP_CNTL_IN_YUV420; + mask = 0x7; + break; + case PRP_PIXIN_YUYV: + case PRP_PIXIN_YVYU: + case PRP_PIXIN_UYVY: + case PRP_PIXIN_VYUY: + *prp_cntl |= PRP_CNTL_IN_YUV422; + mask = 0x1; + break; + case PRP_PIXIN_RGB565: + *prp_cntl |= PRP_CNTL_IN_RGB16; + mask = 0x1; + break; + case PRP_PIXIN_RGB888: + *prp_cntl |= PRP_CNTL_IN_RGB32; + mask = 0; + break; + default: + pr_debug("Unsupported input pix format 0x%08X\n", cfg->in_pix); + return -1; + } + + /* align the input image width */ + if (cfg->in_width & mask) { + pr_debug("in_width misaligned. in_width=%d\n", cfg->in_width); + return -1; + } + + if ((cfg->in_width < PRP_MIN_IN_WIDTH) + || (cfg->in_width > PRP_MAX_IN_WIDTH)) { + pr_debug("Unsupported input width %d\n", cfg->in_width); + return -1; + } + + cfg->in_height &= ~1; /* truncate to make even */ + + if ((cfg->in_height < PRP_MIN_IN_HEIGHT) + || (cfg->in_height > PRP_MAX_IN_HEIGHT)) { + pr_debug("Unsupported input height %d\n", cfg->in_height); + return -1; + } + + if (!(cfg->in_csi & PRP_CSI_EN)) + if (!cfg->in_line_stride) + cfg->in_line_stride = cfg->in_width; + + __raw_writel(cfg->in_pix, PRP_SRC_PIXEL_FORMAT_CNTL); + __raw_writel((cfg->in_width << 16) | cfg->in_height, + PRP_SOURCE_FRAME_SIZE); + __raw_writel((cfg->in_line_skip << 16) | cfg->in_line_stride, + PRP_SOURCE_LINE_STRIDE); + + if (!(cfg->in_csi & PRP_CSI_EN)) { + __raw_writel(cfg->in_ptr, PRP_SOURCE_Y_PTR); + if (cfg->in_pix == PRP_PIXIN_YUV420) { + unsigned int size; + + size = cfg->in_line_stride * cfg->in_height; + __raw_writel(cfg->in_ptr + size, PRP_SOURCE_CB_PTR); + __raw_writel(cfg->in_ptr + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } + } + + /* always cropping */ + *prp_cntl |= PRP_CNTL_WINEN; + + /* color space conversion */ + if (!cfg->in_csc[1]) { + if (cfg->in_csc[0] > 3) { + pr_debug("in_csc invalid 0x%X\n", cfg->in_csc[0]); + return -1; + } + if ((cfg->in_pix == PRP_PIXIN_RGB565) + || (cfg->in_pix == PRP_PIXIN_RGB888)) + cfg->in_csc[1] = 1; + else + cfg->in_csc[0] = 0; + csc_tbl(cfg->in_csc); + } + + __raw_writel((cfg->in_csc[0] << 21) | (cfg->in_csc[1] << 11) + | cfg->in_csc[2], PRP_CSC_COEF_012); + __raw_writel((cfg->in_csc[3] << 21) | (cfg->in_csc[4] << 11) + | cfg->in_csc[5], PRP_CSC_COEF_345); + __raw_writel((cfg->in_csc[6] << 21) | (cfg->in_csc[7] << 11) + | cfg->in_csc[8] | (cfg->in_csc[9] << 31), + PRP_CSC_COEF_678); + + if (cfg->in_csi & PRP_CSI_EN) { + *prp_cntl |= PRP_CNTL_CSI; + + /* loop mode enable, ch1 ch2 together */ + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) + *prp_cntl |= (PRP_CNTL_CH1_LOOP | PRP_CNTL_CH2_LOOP); + } + + return 0; +} + +/*! + * @brief Setup PrP registers relevant to channel 2. + * @param cfg Pointer to PrP configuration parameter + * @param prp_cntl Holds the value for PrP control register + * @return Zero on success, others on failure + */ +static int prphw_ch2_cfg(emma_prp_cfg * cfg, unsigned long *prp_cntl) +{ + switch (cfg->ch2_pix) { + case PRP_PIX2_YUV420: + *prp_cntl |= PRP_CNTL_CH2_YUV420; + break; + case PRP_PIX2_YUV422: + *prp_cntl |= PRP_CNTL_CH2_YUV422; + break; + case PRP_PIX2_YUV444: + *prp_cntl |= PRP_CNTL_CH2_YUV444; + break; + case PRP_PIX2_UNUSED: + return 0; + default: + pr_debug("Unsupported channel 2 pix format 0x%08X\n", + cfg->ch2_pix); + return -1; + } + + if (cfg->ch2_pix == PRP_PIX2_YUV420) { + cfg->ch2_height &= ~1; /* ensure U/V presence */ + cfg->ch2_width &= ~7; /* ensure U/V word aligned */ + } else if (cfg->ch2_pix == PRP_PIX2_YUV422) { + cfg->ch2_width &= ~1; /* word aligned */ + } + + __raw_writel((cfg->ch2_width << 16) | cfg->ch2_height, + PRP_CH2_OUT_IMAGE_SIZE); + + if (cfg->ch2_pix == PRP_PIX2_YUV420) { + u32 size; + + /* Luminanance band start address */ + __raw_writel(cfg->ch2_ptr, PRP_DEST_Y_PTR); + + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (!cfg->ch2_ptr2) + __raw_writel(cfg->ch2_ptr, PRP_SOURCE_Y_PTR); + else + __raw_writel(cfg->ch2_ptr2, PRP_SOURCE_Y_PTR); + } + + /* Cb and Cr band start address */ + size = cfg->ch2_width * cfg->ch2_height; + __raw_writel(cfg->ch2_ptr + size, PRP_DEST_CB_PTR); + __raw_writel(cfg->ch2_ptr + size + (size >> 2), + PRP_DEST_CR_PTR); + + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (!cfg->ch2_ptr2) { + __raw_writel(cfg->ch2_ptr + size, + PRP_SOURCE_CB_PTR); + __raw_writel(cfg->ch2_ptr + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } else { + __raw_writel(cfg->ch2_ptr2 + size, + PRP_SOURCE_CB_PTR); + __raw_writel(cfg->ch2_ptr2 + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } + } + } else { /* Pixel interleaved YUV422 or YUV444 */ + __raw_writel(cfg->ch2_ptr, PRP_DEST_Y_PTR); + + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (!cfg->ch2_ptr2) + __raw_writel(cfg->ch2_ptr, PRP_SOURCE_Y_PTR); + else + __raw_writel(cfg->ch2_ptr2, PRP_SOURCE_Y_PTR); + } + } + *prp_cntl |= PRP_CNTL_CH2B1 | PRP_CNTL_CH2B2; + + return 0; +} + +/*! + * @brief Setup PrP registers relevant to channel 1. + * @param cfg Pointer to PrP configuration parameter + * @param prp_cntl Holds the value for PrP control register + * @return Zero on success, others on failure + */ +static int prphw_ch1_cfg(emma_prp_cfg * cfg, unsigned long *prp_cntl) +{ + int ch1_bpp = 0; + + switch (cfg->ch1_pix) { + case PRP_PIX1_RGB332: + *prp_cntl |= PRP_CNTL_CH1_RGB8; + ch1_bpp = 1; + break; + case PRP_PIX1_RGB565: + *prp_cntl |= PRP_CNTL_CH1_RGB16; + ch1_bpp = 2; + break; + case PRP_PIX1_RGB888: + *prp_cntl |= PRP_CNTL_CH1_RGB32; + ch1_bpp = 4; + break; + case PRP_PIX1_YUYV: + case PRP_PIX1_YVYU: + case PRP_PIX1_UYVY: + case PRP_PIX1_VYUY: + *prp_cntl |= PRP_CNTL_CH1_YUV422; + ch1_bpp = 2; + break; + case PRP_PIX1_UNUSED: + return 0; + default: + pr_debug("Unsupported channel 1 pix format 0x%08X\n", + cfg->ch1_pix); + return -1; + } + + /* parallel or cascade resize */ + if (cfg->ch1_scale.algo & PRP_ALGO_BYPASS) + *prp_cntl |= PRP_CNTL_UNCHAIN; + + /* word align */ + if (ch1_bpp == 2) + cfg->ch1_width &= ~1; + else if (ch1_bpp == 1) + cfg->ch1_width &= ~3; + + if (!cfg->ch1_stride) + cfg->ch1_stride = cfg->ch1_width; + + __raw_writel(cfg->ch1_pix, PRP_CH1_PIXEL_FORMAT_CNTL); + __raw_writel((cfg->ch1_width << 16) | cfg->ch1_height, + PRP_CH1_OUT_IMAGE_SIZE); + __raw_writel(cfg->ch1_stride * ch1_bpp, PRP_CH1_LINE_STRIDE); + __raw_writel(cfg->ch1_ptr, PRP_DEST_RGB1_PTR); + + /* double buffer for loop mode */ + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (cfg->ch1_ptr2) + __raw_writel(cfg->ch1_ptr2, PRP_DEST_RGB2_PTR); + else + __raw_writel(cfg->ch1_ptr, PRP_DEST_RGB2_PTR); + } + + return 0; +} + +/*! + * @brief Setup PrP registers. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_cfg(emma_prp_cfg * cfg) +{ + unsigned long prp_cntl = 0; + unsigned long val; + + /* input pixel format checking */ + if (prphw_input_cfg(cfg, &prp_cntl)) + return -1; + + if (prphw_ch2_cfg(cfg, &prp_cntl)) + return -1; + + if (prphw_ch1_cfg(cfg, &prp_cntl)) + return -1; + + /* register setting */ + __raw_writel(prp_cntl, PRP_CNTL); + + /* interrupt configuration */ + val = PRP_INTRCNTL_RDERR | PRP_INTRCNTL_LBOVF; + if (cfg->ch1_pix != PRP_PIX1_UNUSED) + val |= PRP_INTRCNTL_CH1FC | PRP_INTRCNTL_CH1WERR; + if (cfg->ch2_pix != PRP_PIX2_UNUSED) + val |= + PRP_INTRCNTL_CH2FC | PRP_INTRCNTL_CH2WERR | + PRP_INTRCNTL_CH2OVF; + __raw_writel(val, PRP_INTRCNTL); + + prp_set_scaler(1, 0, &cfg->scale[0]); /* Channel 1 width */ + prp_set_scaler(1, 1, &cfg->scale[1]); /* Channel 1 height */ + prp_set_scaler(0, 0, &cfg->scale[2]); /* Channel 2 width */ + prp_set_scaler(0, 1, &cfg->scale[3]); /* Channel 2 height */ + + return 0; +} + +/*! + * @brief Check PrP interrupt status. + * @return PrP interrupt status + */ +int prphw_isr(void) +{ + int status; + + status = __raw_readl(PRP_INTRSTATUS) & 0x1FF; + + if (status & (PRP_INTRSTAT_RDERR | PRP_INTRSTAT_CH1WERR | + PRP_INTRSTAT_CH2WERR)) + pr_debug("isr bus error. status= 0x%08X\n", status); + else if (status & PRP_INTRSTAT_CH2OVF) + pr_debug("isr ch 2 buffer overflow. status= 0x%08X\n", status); + else if (status & PRP_INTRSTAT_LBOVF) + pr_debug("isr line buffer overflow. status= 0x%08X\n", status); + + /* silicon bug?? enable bit does not self clear? */ + if (!(__raw_readl(PRP_CNTL) & PRP_CNTL_CH1_LOOP)) + __raw_writel(__raw_readl(PRP_CNTL) & (~PRP_CNTL_CH1EN), + PRP_CNTL); + if (!(__raw_readl(PRP_CNTL) & PRP_CNTL_CH2_LOOP)) + __raw_writel(__raw_readl(PRP_CNTL) & (~PRP_CNTL_CH2EN), + PRP_CNTL); + + __raw_writel(status, PRP_INTRSTATUS); /* clr irq */ + + return status; +} + +static struct clk *emma_clk; + +/*! + * @brief PrP module clock enable + */ +void prphw_init(void) +{ + emma_clk = clk_get(NULL, "emma_clk"); + clk_enable(emma_clk); +} + +/*! + * @brief PrP module clock disable + */ +void prphw_exit(void) +{ + clk_disable(emma_clk); + clk_put(emma_clk); +} diff --git a/drivers/media/video/mxc/capture/mx27_prpsw.c b/drivers/media/video/mxc/capture/mx27_prpsw.c new file mode 100644 index 000000000000..ce7db16913ec --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_prpsw.c @@ -0,0 +1,1042 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_prpsw.c + * + * @brief MX27 Video For Linux 2 capture driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <asm/cacheflush.h> +#include <asm/io.h> +#include <asm/irq.h> + +#include "mxc_v4l2_capture.h" +#include "mx27_prp.h" +#include "mx27_csi.h" +#include "../drivers/video/mxc/mx2fb.h" +#include "../opl/opl.h" + +#define MEAN_COEF (SZ_COEF >> 1) + +static char prp_dev[] = "emma_prp"; +static int g_still_on = 0; +static emma_prp_cfg g_prp_cfg; +static int g_vfbuf, g_rotbuf; +static struct tasklet_struct prp_vf_tasklet; + +/* + * The following variables represents the virtual address for the cacheable + * buffers accessed by SW rotation/mirroring. The rotation/mirroring in + * cacheable buffers has significant performance improvement than it in + * non-cacheable buffers. + */ +static char *g_vaddr_vfbuf[2] = { 0, 0 }; +static char *g_vaddr_rotbuf[2] = { 0, 0 }; +static char *g_vaddr_fb = 0; + +static int set_ch1_addr(emma_prp_cfg * cfg, cam_data * cam); +static int prp_v4l2_cfg(emma_prp_cfg * cfg, cam_data * cam); +static int prp_vf_mem_alloc(cam_data * cam); +static void prp_vf_mem_free(cam_data * cam); +static int prp_rot_mem_alloc(cam_data * cam); +static void prp_rot_mem_free(cam_data * cam); +static int prp_enc_update_eba(u32 eba, int *buffer_num); +static int prp_enc_enable(void *private); +static int prp_enc_disable(void *private); +static int prp_vf_start(void *private); +static int prp_vf_stop(void *private); +static int prp_still_start(void *private); +static int prp_still_stop(void *private); +static irqreturn_t prp_isr(int irq, void *dev_id); +static void rotation(unsigned long private); +static int prp_resize_check_ch1(emma_prp_cfg * cfg); +static int prp_resize_check_ch2(emma_prp_cfg * cfg); + +#define PRP_DUMP(val) pr_debug("%s\t = 0x%08X\t%d\n", #val, val, val) + +/*! + * @brief Dump PrP configuration parameters. + * @param cfg The pointer to PrP configuration parameter + */ +static void prp_cfg_dump(emma_prp_cfg * cfg) +{ + PRP_DUMP(cfg->in_pix); + PRP_DUMP(cfg->in_width); + PRP_DUMP(cfg->in_height); + PRP_DUMP(cfg->in_csi); + PRP_DUMP(cfg->in_line_stride); + PRP_DUMP(cfg->in_line_skip); + PRP_DUMP(cfg->in_ptr); + + PRP_DUMP(cfg->ch1_pix); + PRP_DUMP(cfg->ch1_width); + PRP_DUMP(cfg->ch1_height); + PRP_DUMP(cfg->ch1_scale.algo); + PRP_DUMP(cfg->ch1_scale.width.num); + PRP_DUMP(cfg->ch1_scale.width.den); + PRP_DUMP(cfg->ch1_scale.height.num); + PRP_DUMP(cfg->ch1_scale.height.den); + PRP_DUMP(cfg->ch1_stride); + PRP_DUMP(cfg->ch1_ptr); + PRP_DUMP(cfg->ch1_ptr2); + PRP_DUMP(cfg->ch1_csi); + + PRP_DUMP(cfg->ch2_pix); + PRP_DUMP(cfg->ch2_width); + PRP_DUMP(cfg->ch2_height); + PRP_DUMP(cfg->ch2_scale.algo); + PRP_DUMP(cfg->ch2_scale.width.num); + PRP_DUMP(cfg->ch2_scale.width.den); + PRP_DUMP(cfg->ch2_scale.height.num); + PRP_DUMP(cfg->ch2_scale.height.den); + PRP_DUMP(cfg->ch2_ptr); + PRP_DUMP(cfg->ch2_ptr2); + PRP_DUMP(cfg->ch2_csi); +} + +/*! + * @brief Set PrP channel 1 output address. + * @param cfg Pointer to emma_prp_cfg structure + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int set_ch1_addr(emma_prp_cfg * cfg, cam_data * cam) +{ + if (cam->rotation != V4L2_MXC_ROTATE_NONE) { + cfg->ch1_ptr = (unsigned int)cam->rot_vf_bufs[0]; + cfg->ch1_ptr2 = (unsigned int)cam->rot_vf_bufs[1]; + if ((cam->rotation == V4L2_MXC_ROTATE_90_RIGHT) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_VFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_HFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_LEFT)) + cfg->ch1_stride = cam->win.w.height; + else + cfg->ch1_stride = cam->win.w.width; + + if (cam->v4l2_fb.flags != V4L2_FBUF_FLAG_OVERLAY) { + struct fb_info *fb = cam->overlay_fb; + if (!fb) + return -1; + if (g_vaddr_fb) + iounmap(g_vaddr_fb); + g_vaddr_fb = ioremap_cached(fb->fix.smem_start, + fb->fix.smem_len); + if (!g_vaddr_fb) + return -1; + } + } else if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + cfg->ch1_ptr = (unsigned int)cam->vf_bufs[0]; + cfg->ch1_ptr2 = (unsigned int)cam->vf_bufs[1]; + cfg->ch1_stride = cam->win.w.width; + } else { + struct fb_info *fb = cam->overlay_fb; + + if (!fb) + return -1; + + cfg->ch1_ptr = fb->fix.smem_start; + cfg->ch1_ptr += cam->win.w.top * fb->var.xres_virtual + * (fb->var.bits_per_pixel >> 3) + + cam->win.w.left * (fb->var.bits_per_pixel >> 3); + cfg->ch1_ptr2 = cfg->ch1_ptr; + cfg->ch1_stride = fb->var.xres_virtual; + } + + return 0; +} + +/*! + * @brief Setup PrP configuration parameters. + * @param cfg Pointer to emma_prp_cfg structure + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_v4l2_cfg(emma_prp_cfg * cfg, cam_data * cam) +{ + cfg->in_pix = PRP_PIXIN_YUYV; + cfg->in_width = cam->crop_current.width; + cfg->in_height = cam->crop_current.height; + cfg->in_line_stride = cam->crop_current.left; + cfg->in_line_skip = cam->crop_current.top; + cfg->in_ptr = 0; + cfg->in_csi = PRP_CSI_LOOP; + memset(cfg->in_csc, 0, sizeof(cfg->in_csc)); + + if (cam->overlay_on) { + /* Convert V4L2 pixel format to PrP pixel format */ + switch (cam->v4l2_fb.fmt.pixelformat) { + case V4L2_PIX_FMT_RGB332: + cfg->ch1_pix = PRP_PIX1_RGB332; + break; + case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_BGR32: + cfg->ch1_pix = PRP_PIX1_RGB888; + break; + case V4L2_PIX_FMT_YUYV: + cfg->ch1_pix = PRP_PIX1_YUYV; + break; + case V4L2_PIX_FMT_UYVY: + cfg->ch1_pix = PRP_PIX1_UYVY; + break; + case V4L2_PIX_FMT_RGB565: + default: + cfg->ch1_pix = PRP_PIX1_RGB565; + break; + } + if ((cam->rotation == V4L2_MXC_ROTATE_90_RIGHT) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_VFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_HFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_LEFT)) { + cfg->ch1_width = cam->win.w.height; + cfg->ch1_height = cam->win.w.width; + } else { + cfg->ch1_width = cam->win.w.width; + cfg->ch1_height = cam->win.w.height; + } + + if (set_ch1_addr(cfg, cam)) + return -1; + } else { + cfg->ch1_pix = PRP_PIX1_UNUSED; + cfg->ch1_width = cfg->in_width; + cfg->ch1_height = cfg->in_height; + } + cfg->ch1_scale.algo = 0; + cfg->ch1_scale.width.num = cfg->in_width; + cfg->ch1_scale.width.den = cfg->ch1_width; + cfg->ch1_scale.height.num = cfg->in_height; + cfg->ch1_scale.height.den = cfg->ch1_height; + cfg->ch1_csi = PRP_CSI_EN; + + if (cam->capture_on || g_still_on) { + switch (cam->v2f.fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUYV: + cfg->ch2_pix = PRP_PIX2_YUV422; + break; + case V4L2_PIX_FMT_YUV420: + cfg->ch2_pix = PRP_PIX2_YUV420; + break; + /* + * YUV444 is not defined by V4L2. + * We support it in default case. + */ + default: + cfg->ch2_pix = PRP_PIX2_YUV444; + break; + } + cfg->ch2_width = cam->v2f.fmt.pix.width; + cfg->ch2_height = cam->v2f.fmt.pix.height; + } else { + cfg->ch2_pix = PRP_PIX2_UNUSED; + cfg->ch2_width = cfg->in_width; + cfg->ch2_height = cfg->in_height; + } + cfg->ch2_scale.algo = 0; + cfg->ch2_scale.width.num = cfg->in_width; + cfg->ch2_scale.width.den = cfg->ch2_width; + cfg->ch2_scale.height.num = cfg->in_height; + cfg->ch2_scale.height.den = cfg->ch2_height; + cfg->ch2_csi = PRP_CSI_EN; + + memset(cfg->scale, 0, sizeof(cfg->scale)); + cfg->scale[0].algo = cfg->ch1_scale.algo & 3; + cfg->scale[1].algo = (cfg->ch1_scale.algo >> 2) & 3; + cfg->scale[2].algo = cfg->ch2_scale.algo & 3; + cfg->scale[3].algo = (cfg->ch2_scale.algo >> 2) & 3; + + prp_cfg_dump(cfg); + + if (prp_resize_check_ch2(cfg)) + return -1; + + if (prp_resize_check_ch1(cfg)) + return -1; + + return 0; +} + +/*! + * @brief PrP interrupt handler + */ +static irqreturn_t prp_isr(int irq, void *dev_id) +{ + int status; + cam_data *cam = (cam_data *) dev_id; + + status = prphw_isr(); + + if (g_still_on && (status & PRP_INTRSTAT_CH2BUF1)) { + prp_still_stop(cam); + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + /* + * Still & video capture use the same PrP channel 2. + * They are execlusive. + */ + } else if (cam->capture_on) { + if (status & (PRP_INTRSTAT_CH2BUF1 | PRP_INTRSTAT_CH2BUF2)) { + cam->enc_callback(0, cam); + } + } + if (cam->overlay_on + && (status & (PRP_INTRSTAT_CH1BUF1 | PRP_INTRSTAT_CH1BUF2))) { + if (cam->rotation != V4L2_MXC_ROTATE_NONE) { + g_rotbuf = (status & PRP_INTRSTAT_CH1BUF1) ? 0 : 1; + tasklet_schedule(&prp_vf_tasklet); + } else if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + gwinfo.enabled = 1; + gwinfo.alpha_value = 255; + gwinfo.ck_enabled = 0; + gwinfo.xpos = cam->win.w.left; + gwinfo.ypos = cam->win.w.top; + gwinfo.xres = cam->win.w.width; + gwinfo.yres = cam->win.w.height; + gwinfo.xres_virtual = cam->win.w.width; + gwinfo.vs_reversed = 0; + if (status & PRP_INTRSTAT_CH1BUF1) + gwinfo.base = (unsigned long)cam->vf_bufs[0]; + else + gwinfo.base = (unsigned long)cam->vf_bufs[1]; + + mx2_gw_set(&gwinfo); + } + } + + return IRQ_HANDLED; +} + +/*! + * @brief PrP initialization. + * @param dev_id Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_init(void *dev_id) +{ + enable_irq(MXC_INT_EMMAPRP); + if (request_irq(MXC_INT_EMMAPRP, prp_isr, 0, prp_dev, dev_id)) + return -1; + prphw_init(); + + return 0; +} + +/*! + * @brief PrP initialization. + * @param dev_id Pointer to cam_data structure + */ +void prp_exit(void *dev_id) +{ + prphw_exit(); + disable_irq(MXC_INT_EMMAPRP); + free_irq(MXC_INT_EMMAPRP, dev_id); +} + +/*! + * @brief Update PrP channel 2 output buffer address. + * @param eba Physical address for PrP output buffer + * @param buffer_num The PrP channel 2 buffer number to be updated + * @return Zero on success, others on failure + */ +static int prp_enc_update_eba(u32 eba, int *buffer_num) +{ + if (*buffer_num) { + g_prp_cfg.ch2_ptr2 = eba; + prphw_ch2ptr2(&g_prp_cfg); + *buffer_num = 0; + } else { + g_prp_cfg.ch2_ptr = eba; + prphw_ch2ptr(&g_prp_cfg); + *buffer_num = 1; + } + + return 0; +} + +/*! + * @brief Enable PrP for encoding. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_enc_enable(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (prp_v4l2_cfg(&g_prp_cfg, cam)) + return -1; + + csi_enable_mclk(CSI_MCLK_ENC, true, true); + prphw_reset(); + + if (prphw_cfg(&g_prp_cfg)) + return -1; + + prphw_enable(cam->overlay_on ? (PRP_CHANNEL_1 | PRP_CHANNEL_2) + : PRP_CHANNEL_2); + + return 0; +} + +/*! + * @brief Disable PrP for encoding. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_enc_disable(void *private) +{ + prphw_disable(PRP_CHANNEL_2); + csi_enable_mclk(CSI_MCLK_ENC, false, false); + + return 0; +} + +/*! + * @brief Setup encoding functions. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_enc_select(void *private) +{ + int ret = 0; + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->enc_update_eba = prp_enc_update_eba; + cam->enc_enable = prp_enc_enable; + cam->enc_disable = prp_enc_disable; + } else + ret = -EIO; + + return ret; +} + +/*! + * @brief Uninstall encoding functions. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_enc_deselect(void *private) +{ + int ret = 0; + cam_data *cam = (cam_data *) private; + + ret = prp_enc_disable(private); + + if (cam) { + cam->enc_update_eba = NULL; + cam->enc_enable = NULL; + cam->enc_disable = NULL; + } + + return ret; +} + +/*! + * @brief Allocate memory for overlay. + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_vf_mem_alloc(cam_data * cam) +{ + int i; + + for (i = 0; i < 2; i++) { + cam->vf_bufs_size[i] = cam->win.w.width * cam->win.w.height * 2; + cam->vf_bufs_vaddr[i] = dma_alloc_coherent(0, + cam->vf_bufs_size[i], + &cam->vf_bufs[i], + GFP_DMA | + GFP_KERNEL); + if (!cam->vf_bufs_vaddr[i]) { + pr_debug("Failed to alloc memory for vf.\n"); + prp_vf_mem_free(cam); + return -1; + } + + g_vaddr_vfbuf[i] = + ioremap_cached(cam->vf_bufs[i], cam->vf_bufs_size[i]); + if (!g_vaddr_vfbuf[i]) { + pr_debug("Failed to ioremap_cached() for vf.\n"); + prp_vf_mem_free(cam); + return -1; + } + } + + return 0; +} + +/*! + * @brief Free memory for overlay. + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static void prp_vf_mem_free(cam_data * cam) +{ + int i; + + for (i = 0; i < 2; i++) { + if (cam->vf_bufs_vaddr[i]) { + dma_free_coherent(0, + cam->vf_bufs_size[i], + cam->vf_bufs_vaddr[i], + cam->vf_bufs[i]); + } + cam->vf_bufs[i] = 0; + cam->vf_bufs_vaddr[i] = 0; + cam->vf_bufs_size[i] = 0; + if (g_vaddr_vfbuf[i]) { + iounmap(g_vaddr_vfbuf[i]); + g_vaddr_vfbuf[i] = 0; + } + } +} + +/*! + * @brief Allocate intermediate memory for overlay rotation/mirroring. + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_rot_mem_alloc(cam_data * cam) +{ + int i; + + for (i = 0; i < 2; i++) { + cam->rot_vf_buf_size[i] = + cam->win.w.width * cam->win.w.height * 2; + cam->rot_vf_bufs_vaddr[i] = + dma_alloc_coherent(0, cam->rot_vf_buf_size[i], + &cam->rot_vf_bufs[i], + GFP_DMA | GFP_KERNEL); + if (!cam->rot_vf_bufs_vaddr[i]) { + pr_debug("Failed to alloc memory for vf rotation.\n"); + prp_rot_mem_free(cam); + return -1; + } + + g_vaddr_rotbuf[i] = + ioremap_cached(cam->rot_vf_bufs[i], + cam->rot_vf_buf_size[i]); + if (!g_vaddr_rotbuf[i]) { + pr_debug + ("Failed to ioremap_cached() for rotation buffer.\n"); + prp_rot_mem_free(cam); + return -1; + } + } + + return 0; +} + +/*! + * @brief Free intermedaite memory for overlay rotation/mirroring. + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static void prp_rot_mem_free(cam_data * cam) +{ + int i; + + for (i = 0; i < 2; i++) { + if (cam->rot_vf_bufs_vaddr[i]) { + dma_free_coherent(0, + cam->rot_vf_buf_size[i], + cam->rot_vf_bufs_vaddr[i], + cam->rot_vf_bufs[i]); + } + cam->rot_vf_bufs[i] = 0; + cam->rot_vf_bufs_vaddr[i] = 0; + cam->rot_vf_buf_size[i] = 0; + if (g_vaddr_rotbuf[i]) { + iounmap(g_vaddr_rotbuf[i]); + g_vaddr_rotbuf[i] = 0; + } + } +} + +/*! + * @brief Start overlay (view finder). + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_vf_start(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + prp_vf_mem_free(cam); + if (prp_vf_mem_alloc(cam)) { + pr_info("Error to allocate vf buffer\n"); + return -ENOMEM; + } + } + + if (cam->rotation != V4L2_MXC_ROTATE_NONE) { + prp_rot_mem_free(cam); + if (prp_rot_mem_alloc(cam)) { + pr_info("Error to allocate rotation buffer\n"); + prp_vf_mem_free(cam); + return -ENOMEM; + } + } + + if (prp_v4l2_cfg(&g_prp_cfg, cam)) { + prp_vf_mem_free(cam); + prp_rot_mem_free(cam); + return -1; + } + + csi_enable_mclk(CSI_MCLK_VF, true, true); + prphw_reset(); + + if (prphw_cfg(&g_prp_cfg)) { + prp_vf_mem_free(cam); + prp_rot_mem_free(cam); + return -1; + } + g_vfbuf = g_rotbuf = 0; + tasklet_init(&prp_vf_tasklet, rotation, (unsigned long)private); + + prphw_enable(cam->capture_on ? (PRP_CHANNEL_1 | PRP_CHANNEL_2) + : PRP_CHANNEL_1); + + return 0; +} + +/*! + * @brief Stop overlay (view finder). + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_vf_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + + prphw_disable(PRP_CHANNEL_1); + + csi_enable_mclk(CSI_MCLK_VF, false, false); + tasklet_kill(&prp_vf_tasklet); + + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + /* Disable graphic window */ + gwinfo.enabled = 0; + mx2_gw_set(&gwinfo); + + prp_vf_mem_free(cam); + } + prp_rot_mem_free(cam); + if (g_vaddr_fb) { + iounmap(g_vaddr_fb); + g_vaddr_fb = 0; + } + + return 0; +} + +/*! + * @brief Setup overlay functions. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_vf_select(void *private) +{ + int ret = 0; + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->vf_start_sdc = prp_vf_start; + cam->vf_stop_sdc = prp_vf_stop; + cam->overlay_active = false; + } else + ret = -EIO; + + return ret; +} + +/*! + * @brief Uninstall overlay functions. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_vf_deselect(void *private) +{ + int ret = 0; + cam_data *cam = (cam_data *) private; + + ret = prp_vf_stop(private); + + if (cam) { + cam->vf_start_sdc = NULL; + cam->vf_stop_sdc = NULL; + } + + return ret; +} + +/*! + * @brief Start still picture capture. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_still_start(void *private) +{ + cam_data *cam = (cam_data *) private; + + g_still_on = 1; + g_prp_cfg.ch2_ptr = (unsigned int)cam->still_buf; + g_prp_cfg.ch2_ptr2 = 0; + + if (prp_v4l2_cfg(&g_prp_cfg, cam)) + return -1; + + csi_enable_mclk(CSI_MCLK_RAW, true, true); + prphw_reset(); + + if (prphw_cfg(&g_prp_cfg)) { + g_still_on = 0; + return -1; + } + + prphw_enable(cam->overlay_on ? (PRP_CHANNEL_1 | PRP_CHANNEL_2) + : PRP_CHANNEL_2); + + return 0; +} + +/*! + * @brief Stop still picture capture. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_still_stop(void *private) +{ + prphw_disable(PRP_CHANNEL_2); + + csi_enable_mclk(CSI_MCLK_RAW, false, false); + + g_still_on = 0; + + return 0; +} + +/*! + * @brief Setup functions for still picture capture. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_still_select(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->csi_start = prp_still_start; + cam->csi_stop = prp_still_stop; + } + + return 0; +} + +/*! + * @brief Uninstall functions for still picture capture. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_still_deselect(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + err = prp_still_stop(cam); + + if (cam) { + cam->csi_start = NULL; + cam->csi_stop = NULL; + } + + return err; +} + +/*! + * @brief Perform software rotation or mirroring + * @param private Argument passed to the tasklet + */ +static void rotation(unsigned long private) +{ + char *src, *dst; + int width, height, s_stride, d_stride; + int size; + cam_data *cam = (cam_data *) private; + + src = g_vaddr_rotbuf[g_rotbuf]; + size = cam->rot_vf_buf_size[g_rotbuf]; + + if ((cam->rotation == V4L2_MXC_ROTATE_90_RIGHT) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_VFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_HFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_LEFT)) { + width = cam->win.w.height; + height = cam->win.w.width; + s_stride = cam->win.w.height << 1; + } else { + width = cam->win.w.width; + height = cam->win.w.height; + s_stride = cam->win.w.width << 1; + } + + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + dst = g_vaddr_vfbuf[g_vfbuf]; + d_stride = cam->win.w.width << 1; + } else { /* The destination is the framebuffer */ + struct fb_info *fb = cam->overlay_fb; + if (!fb) + return; + dst = g_vaddr_fb; + dst += cam->win.w.top * fb->var.xres_virtual + * (fb->var.bits_per_pixel >> 3) + + cam->win.w.left * (fb->var.bits_per_pixel >> 3); + d_stride = fb->var.xres_virtual << 1; + } + + /* + * Invalidate the data in cache before performing the SW rotaion + * or mirroring in case the image size is less than QVGA. For image + * larger than QVGA it is not invalidated becase the invalidation + * will consume much time while we don't see any artifacts on the + * output if we don't perform invalidation for them. + * Similarly we don't flush the data after SW rotation/mirroring. + */ + if (size < 320 * 240 * 2) + dmac_inv_range(src, src + size); + switch (cam->rotation) { + case V4L2_MXC_ROTATE_VERT_FLIP: + opl_vmirror_u16(src, s_stride, width, height, dst, d_stride); + break; + case V4L2_MXC_ROTATE_HORIZ_FLIP: + opl_hmirror_u16(src, s_stride, width, height, dst, d_stride); + break; + case V4L2_MXC_ROTATE_180: + opl_rotate180_u16(src, s_stride, width, height, dst, d_stride); + break; + case V4L2_MXC_ROTATE_90_RIGHT: + opl_rotate90_u16(src, s_stride, width, height, dst, d_stride); + break; + case V4L2_MXC_ROTATE_90_RIGHT_VFLIP: + opl_rotate90_vmirror_u16(src, s_stride, width, height, dst, + d_stride); + break; + case V4L2_MXC_ROTATE_90_RIGHT_HFLIP: + /* ROTATE_90_RIGHT_HFLIP = ROTATE_270_RIGHT_VFLIP */ + opl_rotate270_vmirror_u16(src, s_stride, width, height, dst, + d_stride); + break; + case V4L2_MXC_ROTATE_90_LEFT: + opl_rotate270_u16(src, s_stride, width, height, dst, d_stride); + break; + default: + return; + } + + /* Config and display the graphic window */ + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + gwinfo.enabled = 1; + gwinfo.alpha_value = 255; + gwinfo.ck_enabled = 0; + gwinfo.xpos = cam->win.w.left; + gwinfo.ypos = cam->win.w.top; + gwinfo.xres = cam->win.w.width; + gwinfo.yres = cam->win.w.height; + gwinfo.xres_virtual = cam->win.w.width; + gwinfo.vs_reversed = 0; + gwinfo.base = (unsigned long)cam->vf_bufs[g_vfbuf]; + mx2_gw_set(&gwinfo); + + g_vfbuf = g_vfbuf ? 0 : 1; + } +} + +/* + * @brief Check if the resize ratio is supported based on the input and output + * dimension + * @param input input dimension + * @param output output dimension + * @return output dimension (should equal the parameter *output*) + * -1 on failure + */ +static int check_simple(scale_t * scale, int input, int output) +{ + unsigned short int_out; /* PrP internel width or height */ + unsigned short orig_out = output; + + if (prp_scale(scale, input, output, input, &orig_out, &int_out, 0)) + return -1; /* resize failed */ + else + return int_out; +} + +/* + * @brief Check if the resize ratio is supported based on the input and output + * dimension + * @param input input dimension + * @param output output dimension + * @return output dimension, may be rounded. + * -1 on failure + */ +static int check_simple_retry(scale_t * scale, int input, int output) +{ + unsigned short int_out; /* PrP internel width or height */ + unsigned short orig_out = output; + + if (prp_scale(scale, input, output, input, &orig_out, &int_out, + SCALE_RETRY)) + return -1; /* resize failed */ + else + return int_out; +} + +/*! + * @brief Check if the resize ratio is supported by PrP channel 1 + * @param cfg Pointer to emma_prp_cfg structure + * @return Zero on success, others on failure + */ +static int prp_resize_check_ch1(emma_prp_cfg * cfg) +{ + int in_w, in_h, ch1_w, ch1_h, ch2_w, ch2_h, w, h; + scale_t *pscale = &cfg->scale[0]; /* Ch1 width resize coeff */ + + if (cfg->ch1_pix == PRP_PIX1_UNUSED) + return 0; + + in_w = cfg->in_width; + in_h = cfg->in_height; + ch1_w = cfg->ch1_width; + ch1_h = cfg->ch1_height; + ch2_w = cfg->ch2_width; + ch2_h = cfg->ch2_height; + + /* + * For channel 1, try parallel resize first. If the resize + * ratio is not exactly supported, try cascade resize. If it + * still fails, use parallel resize but with rounded value. + */ + w = check_simple(pscale, in_w, ch1_w); + h = check_simple(pscale + 1, in_h, ch1_h); + if ((w == ch1_w) && (h == ch1_h)) + goto exit_parallel; + + if (cfg->ch2_pix != PRP_PIX2_UNUSED) { + /* + * Channel 2 is already used. The pscale is still pointing + * to ch1 resize coeff for temporary use. + */ + w = check_simple(pscale, in_w, ch2_w); + h = check_simple(pscale + 1, in_h, ch2_h); + if ((w == ch2_w) && (h == ch2_h)) { + /* Try cascade resize now */ + w = check_simple(pscale, ch2_w, ch1_w); + h = check_simple(pscale + 1, ch2_h, ch1_h); + if ((w == ch1_w) && (h == ch1_h)) + goto exit_cascade; + } + } else { + /* + * Try cascade resize for width, width is multiple of 2. + * Channel 2 is not used. So we have more values to pick + * for channel 2 resize. + */ + for (w = in_w - 2; w > ch1_w; w -= 2) { + /* Ch2 width resize */ + if (check_simple(pscale + 2, in_w, w) != w) + continue; + /* Ch1 width resize */ + if (check_simple(pscale, w, ch1_w) != ch1_w) + continue; + break; + } + if ((ch2_w = w) > ch1_w) { + /* try cascade resize for height */ + for (h = in_h - 1; h > ch1_h; h--) { + /* Ch2 height resize */ + if (check_simple(pscale + 3, in_h, h) != h) + continue; + /* Ch1 height resize */ + if (check_simple(pscale + 1, h, ch1_h) != ch1_h) + continue; + break; + } + if ((ch2_h = h) > ch1_h) + goto exit_cascade; + } + } + + /* Have to try parallel resize again and round the dimensions */ + w = check_simple_retry(pscale, in_w, ch1_w); + h = check_simple_retry(pscale + 1, in_h, ch1_h); + if ((w != -1) && (h != -1)) + goto exit_parallel; + + pr_debug("Ch1 resize error.\n"); + return -1; + + exit_parallel: + cfg->ch1_scale.algo |= PRP_ALGO_BYPASS; + pr_debug("ch1 parallel resize.\n"); + pr_debug("original width = %d internel width = %d\n", ch1_w, w); + pr_debug("original height = %d internel height = %d\n", ch1_h, h); + return 0; + + exit_cascade: + cfg->ch1_scale.algo &= ~PRP_ALGO_BYPASS; + pr_debug("ch1 cascade resize.\n"); + pr_debug("[width] in : ch2 : ch1=%d : %d : %d\n", in_w, ch2_w, ch1_w); + pr_debug("[height] in : ch2 : ch1=%d : %d : %d\n", in_h, ch2_h, ch1_h); + return 0; +} + +/*! + * @brief Check if the resize ratio is supported by PrP channel 2 + * @param cfg Pointer to emma_prp_cfg structure + * @return Zero on success, others on failure + */ +static int prp_resize_check_ch2(emma_prp_cfg * cfg) +{ + int w, h; + scale_t *pscale = &cfg->scale[2]; /* Ch2 width resize coeff */ + + if (cfg->ch2_pix == PRP_PIX2_UNUSED) + return 0; + + w = check_simple_retry(pscale, cfg->in_width, cfg->ch2_width); + h = check_simple_retry(pscale + 1, cfg->in_height, cfg->ch2_height); + if ((w != -1) && (h != -1)) { + pr_debug("Ch2 resize.\n"); + pr_debug("Original width = %d internel width = %d\n", + cfg->ch2_width, w); + pr_debug("Original height = %d internel height = %d\n", + cfg->ch2_height, h); + return 0; + } else { + pr_debug("Ch2 resize error.\n"); + return -1; + } +} diff --git a/drivers/media/video/mxc/capture/mxc_v4l2_capture.c b/drivers/media/video/mxc/capture/mxc_v4l2_capture.c new file mode 100644 index 000000000000..1852d702e505 --- /dev/null +++ b/drivers/media/video/mxc/capture/mxc_v4l2_capture.c @@ -0,0 +1,2593 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/media/video/mxc/capture/mxc_v4l2_capture.c + * + * @brief Mxc Video For Linux 2 driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/io.h> +#include <linux/semaphore.h> +#include <linux/pagemap.h> +#include <linux/vmalloc.h> +#include <linux/types.h> +#include <linux/fb.h> +#include <linux/dma-mapping.h> +#include <linux/mxcfb.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-int-device.h> +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +static int video_nr = -1; +static cam_data *g_cam; + +/*! This data is used for the output to the display. */ +#define MXC_V4L2_CAPTURE_NUM_OUTPUTS 3 +#define MXC_V4L2_CAPTURE_NUM_INPUTS 2 +static struct v4l2_output mxc_capture_outputs[MXC_V4L2_CAPTURE_NUM_OUTPUTS] = { + { + .index = 0, + .name = "DISP3 BG", + .type = V4L2_OUTPUT_TYPE_ANALOG, + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN, + }, + { + .index = 1, + .name = "DISP0", + .type = V4L2_OUTPUT_TYPE_ANALOG, + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN, + }, + { + .index = 2, + .name = "DISP3 FG", + .type = V4L2_OUTPUT_TYPE_ANALOG, + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN, + }, +}; + +static struct v4l2_input mxc_capture_inputs[MXC_V4L2_CAPTURE_NUM_INPUTS] = { + { + .index = 0, + .name = "CSI IC MEM", + .type = V4L2_INPUT_TYPE_CAMERA, + .audioset = 0, + .tuner = 0, + .std = V4L2_STD_UNKNOWN, + .status = 0, + }, + { + .index = 1, + .name = "CSI MEM", + .type = V4L2_INPUT_TYPE_CAMERA, + .audioset = 0, + .tuner = 0, + .std = V4L2_STD_UNKNOWN, + .status = V4L2_IN_ST_NO_POWER, + }, +}; + +/*! List of TV input video formats supported. The video formats is corresponding + * to the v4l2_id in video_fmt_t. + * Currently, only PAL and NTSC is supported. Needs to be expanded in the + * future. + */ +typedef enum { + TV_NTSC = 0, /*!< Locked on (M) NTSC video signal. */ + TV_PAL, /*!< (B, G, H, I, N)PAL video signal. */ + TV_NOT_LOCKED, /*!< Not locked on a signal. */ +} video_fmt_idx; + +/*! Number of video standards supported (including 'not locked' signal). */ +#define TV_STD_MAX (TV_NOT_LOCKED + 1) + +/*! Video format structure. */ +typedef struct { + int v4l2_id; /*!< Video for linux ID. */ + char name[16]; /*!< Name (e.g., "NTSC", "PAL", etc.) */ + u16 raw_width; /*!< Raw width. */ + u16 raw_height; /*!< Raw height. */ + u16 active_width; /*!< Active width. */ + u16 active_height; /*!< Active height. */ + u16 active_top; /*!< Active top. */ + u16 active_left; /*!< Active left. */ +} video_fmt_t; + +/*! + * Description of video formats supported. + * + * PAL: raw=720x625, active=720x576. + * NTSC: raw=720x525, active=720x480. + */ +static video_fmt_t video_fmts[] = { + { /*! NTSC */ + .v4l2_id = V4L2_STD_NTSC, + .name = "NTSC", + .raw_width = 720 - 1, /* SENS_FRM_WIDTH */ + .raw_height = 288 - 1, /* SENS_FRM_HEIGHT */ + .active_width = 720, /* ACT_FRM_WIDTH plus 1 */ + .active_height = (480 / 2), /* ACT_FRM_HEIGHT plus 1 */ + .active_top = 12, + .active_left = 0, + }, + { /*! (B, G, H, I, N) PAL */ + .v4l2_id = V4L2_STD_PAL, + .name = "PAL", + .raw_width = 720 - 1, + .raw_height = (576 / 2) + 24 * 2 - 1, + .active_width = 720, + .active_height = (576 / 2), + .active_top = 0, + .active_left = 0, + }, + { /*! Unlocked standard */ + .v4l2_id = V4L2_STD_ALL, + .name = "Autodetect", + .raw_width = 720 - 1, + .raw_height = (576 / 2) + 24 * 2 - 1, + .active_width = 720, + .active_height = (576 / 2), + .active_top = 0, + .active_left = 0, + }, +}; + +/*!* Standard index of TV. */ +static video_fmt_idx video_index = TV_NOT_LOCKED; + +static int mxc_v4l2_master_attach(struct v4l2_int_device *slave); +static void mxc_v4l2_master_detach(struct v4l2_int_device *slave); +static u8 camera_power(cam_data *cam, bool cameraOn); + +/*! Information about this driver. */ +static struct v4l2_int_master mxc_v4l2_master = { + .attach = mxc_v4l2_master_attach, + .detach = mxc_v4l2_master_detach, +}; + +static struct v4l2_int_device mxc_v4l2_int_device = { + .module = THIS_MODULE, + .name = "mxc_v4l2_cap", + .type = v4l2_int_type_master, + .u = { + .master = &mxc_v4l2_master, + }, +}; + +/*************************************************************************** + * Functions for handling Frame buffers. + **************************************************************************/ + +/*! + * Free frame buffers + * + * @param cam Structure cam_data * + * + * @return status 0 success. + */ +static int mxc_free_frame_buf(cam_data *cam) +{ + int i; + + pr_debug("MVC: In mxc_free_frame_buf\n"); + + for (i = 0; i < FRAME_NUM; i++) { + if (cam->frame[i].vaddress != 0) { + dma_free_coherent(0, cam->frame[i].buffer.length, + cam->frame[i].vaddress, + cam->frame[i].paddress); + cam->frame[i].vaddress = 0; + } + } + + return 0; +} + +/*! + * Allocate frame buffers + * + * @param cam Structure cam_data* + * @param count int number of buffer need to allocated + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_frame_buf(cam_data *cam, int count) +{ + int i; + + pr_debug("In MVC:mxc_allocate_frame_buf - size=%d\n", + cam->v2f.fmt.pix.sizeimage); + + for (i = 0; i < count; i++) { + cam->frame[i].vaddress = + dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->frame[i].paddress, + GFP_DMA | GFP_KERNEL); + if (cam->frame[i].vaddress == 0) { + pr_err("ERROR: v4l2 capture: " + "mxc_allocate_frame_buf failed.\n"); + mxc_free_frame_buf(cam); + return -ENOBUFS; + } + cam->frame[i].buffer.index = i; + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + cam->frame[i].buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->frame[i].buffer.length = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->frame[i].buffer.memory = V4L2_MEMORY_MMAP; + cam->frame[i].buffer.m.offset = cam->frame[i].paddress; + cam->frame[i].index = i; + } + + return 0; +} + +/*! + * Free frame buffers status + * + * @param cam Structure cam_data * + * + * @return none + */ +static void mxc_free_frames(cam_data *cam) +{ + int i; + + pr_debug("In MVC:mxc_free_frames\n"); + + for (i = 0; i < FRAME_NUM; i++) { + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + } + + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); +} + +/*! + * Return the buffer status + * + * @param cam Structure cam_data * + * @param buf Structure v4l2_buffer * + * + * @return status 0 success, EINVAL failed. + */ +static int mxc_v4l2_buffer_status(cam_data *cam, struct v4l2_buffer *buf) +{ + pr_debug("In MVC:mxc_v4l2_buffer_status\n"); + + if (buf->index < 0 || buf->index >= FRAME_NUM) { + pr_err("ERROR: v4l2 capture: mxc_v4l2_buffer_status buffers " + "not allocated\n"); + return -EINVAL; + } + + memcpy(buf, &(cam->frame[buf->index].buffer), sizeof(*buf)); + return 0; +} + +/*************************************************************************** + * Functions for handling the video stream. + **************************************************************************/ + +/*! + * Indicates whether the palette is supported. + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return ((palette == V4L2_PIX_FMT_RGB565) || + (palette == V4L2_PIX_FMT_BGR24) || + (palette == V4L2_PIX_FMT_RGB24) || + (palette == V4L2_PIX_FMT_BGR32) || + (palette == V4L2_PIX_FMT_RGB32) || + (palette == V4L2_PIX_FMT_YUV422P) || + (palette == V4L2_PIX_FMT_UYVY) || + (palette == V4L2_PIX_FMT_YUV420) || + (palette == V4L2_PIX_FMT_NV12)); +} + +/*! + * Start the encoder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int mxc_streamon(cam_data *cam) +{ + struct mxc_v4l_frame *frame; + int err = 0; + + pr_debug("In MVC:mxc_streamon\n"); + + if (NULL == cam) { + pr_err("ERROR! cam parameter is NULL\n"); + return -1; + } + + if (list_empty(&cam->ready_q)) { + pr_err("ERROR: v4l2 capture: mxc_streamon buffer has not been " + "queued yet\n"); + return -EINVAL; + } + + cam->capture_pid = current->pid; + + if (cam->enc_enable) { + err = cam->enc_enable(cam); + if (err != 0) { + return err; + } + } + + cam->ping_pong_csi = 0; + if (cam->enc_update_eba) { + frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + err = cam->enc_update_eba(frame->buffer.m.offset, + &cam->ping_pong_csi); + + frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + err |= cam->enc_update_eba(frame->buffer.m.offset, + &cam->ping_pong_csi); + } else { + return -EINVAL; + } + + cam->capture_on = true; + + return err; +} + +/*! + * Shut down the encoder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int mxc_streamoff(cam_data *cam) +{ + int err = 0; + + pr_debug("In MVC:mxc_streamoff\n"); + + if (cam->capture_on == false) + return 0; + + if (cam->enc_disable) { + err = cam->enc_disable(cam); + } + mxc_free_frames(cam); + mxc_capture_inputs[cam->current_input].status |= V4L2_IN_ST_NO_POWER; + cam->capture_on = false; + return err; +} + +/*! + * Valid and adjust the overlay window size, position + * + * @param cam structure cam_data * + * @param win struct v4l2_window * + * + * @return 0 + */ +static int verify_preview(cam_data *cam, struct v4l2_window *win) +{ + int i = 0, width_bound = 0, height_bound = 0; + int *width, *height; + struct fb_info *bg_fbi = NULL; + bool foregound_fb; + + pr_debug("In MVC: verify_preview\n"); + + do { + cam->overlay_fb = (struct fb_info *)registered_fb[i]; + if (cam->overlay_fb == NULL) { + pr_err("ERROR: verify_preview frame buffer NULL.\n"); + return -1; + } + if (strcmp(cam->overlay_fb->fix.id, "DISP3 BG") == 0) + bg_fbi = cam->overlay_fb; + if (strcmp(cam->overlay_fb->fix.id, + mxc_capture_outputs[cam->output].name) == 0) { + if (strcmp(cam->overlay_fb->fix.id, "DISP3 FG") == 0) + foregound_fb = true; + break; + } + } while (++i < FB_MAX); + + if (foregound_fb) { + width_bound = bg_fbi->var.xres; + height_bound = bg_fbi->var.yres; + + if (win->w.width + win->w.left > bg_fbi->var.xres || + win->w.height + win->w.top > bg_fbi->var.yres) { + pr_err("ERROR: FG window position exceeds.\n"); + return -1; + } + } else { + /* 4 bytes alignment for BG */ + width_bound = cam->overlay_fb->var.xres; + height_bound = cam->overlay_fb->var.yres; + + if (cam->overlay_fb->var.bits_per_pixel == 24) { + win->w.left -= win->w.left % 4; + } else if (cam->overlay_fb->var.bits_per_pixel == 16) { + win->w.left -= win->w.left % 2; + } + + if (win->w.width + win->w.left > cam->overlay_fb->var.xres) + win->w.width = cam->overlay_fb->var.xres - win->w.left; + if (win->w.height + win->w.top > cam->overlay_fb->var.yres) + win->w.height = cam->overlay_fb->var.yres - win->w.top; + } + + /* stride line limitation */ + win->w.height -= win->w.height % 8; + win->w.width -= win->w.width % 8; + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + height = &win->w.width; + width = &win->w.height; + } else { + width = &win->w.width; + height = &win->w.height; + } + + if ((cam->crop_bounds.width / *width > 8) || + ((cam->crop_bounds.width / *width == 8) && + (cam->crop_bounds.width % *width))) { + *width = cam->crop_bounds.width / 8; + if (*width % 8) + *width += 8 - *width % 8; + if (*width + win->w.left > width_bound) { + pr_err("ERROR: v4l2 capture: width exceeds " + "resize limit.\n"); + return -1; + } + pr_err("ERROR: v4l2 capture: width exceeds limit. " + "Resize to %d.\n", + *width); + } + + if ((cam->crop_bounds.height / *height > 8) || + ((cam->crop_bounds.height / *height == 8) && + (cam->crop_bounds.height % *height))) { + *height = cam->crop_bounds.height / 8; + if (*height % 8) + *height += 8 - *height % 8; + if (*height + win->w.top > height_bound) { + pr_err("ERROR: v4l2 capture: height exceeds " + "resize limit.\n"); + return -1; + } + pr_err("ERROR: v4l2 capture: height exceeds limit " + "resize to %d.\n", + *height); + } + + return 0; +} + +/*! + * start the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int start_preview(cam_data *cam) +{ + int err = 0; + + pr_debug("MVC: start_preview\n"); + +#if defined(CONFIG_MXC_IPU_PRP_VF_SDC) || defined(CONFIG_MXC_IPU_PRP_VF_SDC_MODULE) + pr_debug(" This is an SDC display\n"); + if (cam->output == 0 || cam->output == 2) { + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) + err = prp_vf_sdc_select(cam); + else if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_PRIMARY) + err = prp_vf_sdc_select_bg(cam); + if (err != 0) + return err; + + err = cam->vf_start_sdc(cam); + } +#endif + +#if defined(CONFIG_MXC_IPU_PRP_VF_ADC) || defined(CONFIG_MXC_IPU_PRP_VF_ADC_MODULE) + pr_debug(" This is an ADC display\n"); + if (cam->output == 1) { + err = prp_vf_adc_select(cam); + if (err != 0) + return err; + + err = cam->vf_start_adc(cam); + } +#endif + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + return err; +} + +/*! + * shut down the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int stop_preview(cam_data *cam) +{ + int err = 0; + + pr_debug("MVC: stop preview\n"); + +#if defined(CONFIG_MXC_IPU_PRP_VF_ADC) || defined(CONFIG_MXC_IPU_PRP_VF_ADC_MODULE) + if (cam->output == 1) { + err = prp_vf_adc_deselect(cam); + } +#endif + +#if defined(CONFIG_MXC_IPU_PRP_VF_SDC) || defined(CONFIG_MXC_IPU_PRP_VF_SDC_MODULE) + if (cam->output == 0 || cam->output == 2) { + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) + err = prp_vf_sdc_deselect(cam); + else if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_PRIMARY) + err = prp_vf_sdc_deselect_bg(cam); + } +#endif + + return err; +} + +/*************************************************************************** + * VIDIOC Functions. + **************************************************************************/ + +/*! + * V4L2 - mxc_v4l2_g_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_g_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + + pr_debug("In MVC: mxc_v4l2_g_fmt type=%d\n", f->type); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + f->fmt.pix = cam->v2f.fmt.pix; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_OVERLAY\n"); + f->fmt.win = cam->win; + break; + default: + pr_debug(" type is invalid\n"); + retval = -EINVAL; + } + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + return retval; +} + +/*! + * V4L2 - mxc_v4l2_s_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + int size = 0; + int bytesperline = 0; + int *width, *height; + + pr_debug("In MVC: mxc_v4l2_s_fmt\n"); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type=V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + if (!valid_mode(f->fmt.pix.pixelformat)) { + pr_err("ERROR: v4l2 capture: mxc_v4l2_s_fmt: format " + "not supported\n"); + return -EINVAL; + } + + /* + * Force the capture window resolution to be crop bounds + * for CSI MEM input mode. + */ + if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI MEM") == 0) { + f->fmt.pix.width = cam->crop_bounds.width; + f->fmt.pix.height = cam->crop_bounds.height; + } + + /* Handle case where size requested is larger than cuurent + * camera setting. */ + if ((f->fmt.pix.width > cam->crop_bounds.width) + || (f->fmt.pix.height > cam->crop_bounds.height)) { + /* Need the logic here, calling vidioc_s_param if + * camera can change. */ + /* For the moment, just return an error. */ + return -EINVAL; + } + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + height = &f->fmt.pix.width; + width = &f->fmt.pix.height; + } else { + width = &f->fmt.pix.width; + height = &f->fmt.pix.height; + } + + /* stride line limitation */ + *width -= *width % 8; + *height -= *height % 8; + + if ((cam->crop_bounds.width / *width > 8) || + ((cam->crop_bounds.width / *width == 8) && + (cam->crop_bounds.width % *width))) { + *width = cam->crop_bounds.width / 8; + if (*width % 8) + *width += 8 - *width % 8; + pr_err("ERROR: v4l2 capture: width exceeds limit " + "resize to %d.\n", + *width); + } + + if ((cam->crop_bounds.height / *height > 8) || + ((cam->crop_bounds.height / *height == 8) && + (cam->crop_bounds.height % *height))) { + *height = cam->crop_bounds.height / 8; + if (*height % 8) + *height += 8 - *height % 8; + pr_err("ERROR: v4l2 capture: height exceeds limit " + "resize to %d.\n", + *height); + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_RGB565: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_BGR24: + size = f->fmt.pix.width * f->fmt.pix.height * 3; + bytesperline = f->fmt.pix.width * 3; + break; + case V4L2_PIX_FMT_RGB24: + size = f->fmt.pix.width * f->fmt.pix.height * 3; + bytesperline = f->fmt.pix.width * 3; + break; + case V4L2_PIX_FMT_BGR32: + size = f->fmt.pix.width * f->fmt.pix.height * 4; + bytesperline = f->fmt.pix.width * 4; + break; + case V4L2_PIX_FMT_RGB32: + size = f->fmt.pix.width * f->fmt.pix.height * 4; + bytesperline = f->fmt.pix.width * 4; + break; + case V4L2_PIX_FMT_YUV422P: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width; + break; + case V4L2_PIX_FMT_UYVY: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_YUV420: + size = f->fmt.pix.width * f->fmt.pix.height * 3 / 2; + bytesperline = f->fmt.pix.width; + break; + case V4L2_PIX_FMT_NV12: + size = f->fmt.pix.width * f->fmt.pix.height * 3 / 2; + bytesperline = f->fmt.pix.width; + break; + default: + break; + } + + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + if (f->fmt.pix.sizeimage < size) { + f->fmt.pix.sizeimage = size; + } else { + size = f->fmt.pix.sizeimage; + } + + cam->v2f.fmt.pix = f->fmt.pix; + + if (cam->v2f.fmt.pix.priv != 0) { + if (copy_from_user(&cam->offset, + (void *)cam->v2f.fmt.pix.priv, + sizeof(cam->offset))) { + retval = -EFAULT; + break; + } + } + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + pr_debug(" type=V4L2_BUF_TYPE_VIDEO_OVERLAY\n"); + retval = verify_preview(cam, &f->fmt.win); + cam->win = f->fmt.win; + break; + default: + retval = -EINVAL; + } + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + return retval; +} + +/*! + * get control param + * + * @param cam structure cam_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_g_ctrl(cam_data *cam, struct v4l2_control *c) +{ + int status = 0; + + pr_debug("In MVC:mxc_v4l2_g_ctrl\n"); + + /* probably don't need to store the values that can be retrieved, + * locally, but they are for now. */ + switch (c->id) { + case V4L2_CID_HFLIP: + /* This is handled in the ipu. */ + if (cam->rotation == IPU_ROTATE_HORIZ_FLIP) + c->value = 1; + break; + case V4L2_CID_VFLIP: + /* This is handled in the ipu. */ + if (cam->rotation == IPU_ROTATE_VERT_FLIP) + c->value = 1; + break; + case V4L2_CID_MXC_ROT: + /* This is handled in the ipu. */ + c->value = cam->rotation; + break; + case V4L2_CID_BRIGHTNESS: + c->value = cam->bright; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->bright = c->value; + break; + case V4L2_CID_HUE: + c->value = cam->hue; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->hue = c->value; + break; + case V4L2_CID_CONTRAST: + c->value = cam->contrast; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->contrast = c->value; + break; + case V4L2_CID_SATURATION: + c->value = cam->saturation; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->saturation = c->value; + break; + case V4L2_CID_RED_BALANCE: + c->value = cam->red; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->red = c->value; + break; + case V4L2_CID_BLUE_BALANCE: + c->value = cam->blue; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->blue = c->value; + break; + case V4L2_CID_BLACK_LEVEL: + c->value = cam->ae_mode; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->ae_mode = c->value; + break; + default: + status = vidioc_int_g_ctrl(cam->sensor, c); + } + + return status; +} + +/*! + * V4L2 - set_control function + * V4L2_CID_PRIVATE_BASE is the extention for IPU preprocessing. + * 0 for normal operation + * 1 for vertical flip + * 2 for horizontal flip + * 3 for horizontal and vertical flip + * 4 for 90 degree rotation + * @param cam structure cam_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_ctrl(cam_data *cam, struct v4l2_control *c) +{ + int ret = 0; + int tmp_rotation = IPU_ROTATE_NONE; + + pr_debug("In MVC:mxc_v4l2_s_ctrl\n"); + + switch (c->id) { + case V4L2_CID_HFLIP: + /* This is done by the IPU */ + if (c->value == 1) { + if ((cam->rotation != IPU_ROTATE_VERT_FLIP) && + (cam->rotation != IPU_ROTATE_180)) + cam->rotation = IPU_ROTATE_HORIZ_FLIP; + else + cam->rotation = IPU_ROTATE_180; + } else { + if (cam->rotation == IPU_ROTATE_HORIZ_FLIP) + cam->rotation = IPU_ROTATE_NONE; + if (cam->rotation == IPU_ROTATE_180) + cam->rotation = IPU_ROTATE_VERT_FLIP; + } + break; + case V4L2_CID_VFLIP: + /* This is done by the IPU */ + if (c->value == 1) { + if ((cam->rotation != IPU_ROTATE_HORIZ_FLIP) && + (cam->rotation != IPU_ROTATE_180)) + cam->rotation = IPU_ROTATE_VERT_FLIP; + else + cam->rotation = IPU_ROTATE_180; + } else { + if (cam->rotation == IPU_ROTATE_VERT_FLIP) + cam->rotation = IPU_ROTATE_NONE; + if (cam->rotation == IPU_ROTATE_180) + cam->rotation = IPU_ROTATE_HORIZ_FLIP; + } + break; + case V4L2_CID_MXC_ROT: + case V4L2_CID_MXC_VF_ROT: + /* This is done by the IPU */ + switch (c->value) { + case V4L2_MXC_ROTATE_NONE: + tmp_rotation = IPU_ROTATE_NONE; + break; + case V4L2_MXC_ROTATE_VERT_FLIP: + tmp_rotation = IPU_ROTATE_VERT_FLIP; + break; + case V4L2_MXC_ROTATE_HORIZ_FLIP: + tmp_rotation = IPU_ROTATE_HORIZ_FLIP; + break; + case V4L2_MXC_ROTATE_180: + tmp_rotation = IPU_ROTATE_180; + break; + case V4L2_MXC_ROTATE_90_RIGHT: + tmp_rotation = IPU_ROTATE_90_RIGHT; + break; + case V4L2_MXC_ROTATE_90_RIGHT_VFLIP: + tmp_rotation = IPU_ROTATE_90_RIGHT_VFLIP; + break; + case V4L2_MXC_ROTATE_90_RIGHT_HFLIP: + tmp_rotation = IPU_ROTATE_90_RIGHT_HFLIP; + break; + case V4L2_MXC_ROTATE_90_LEFT: + tmp_rotation = IPU_ROTATE_90_LEFT; + break; + default: + ret = -EINVAL; + } + + if (c->id == V4L2_CID_MXC_VF_ROT) + cam->vf_rotation = tmp_rotation; + else + cam->rotation = tmp_rotation; + + break; + case V4L2_CID_HUE: + cam->hue = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + break; + case V4L2_CID_CONTRAST: + cam->contrast = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + break; + case V4L2_CID_BRIGHTNESS: + cam->bright = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + break; + case V4L2_CID_SATURATION: + cam->saturation = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + break; + case V4L2_CID_RED_BALANCE: + cam->red = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + break; + case V4L2_CID_BLUE_BALANCE: + cam->blue = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + break; + case V4L2_CID_EXPOSURE: + cam->ae_mode = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + break; + case V4L2_CID_MXC_FLASH: +#ifdef CONFIG_MXC_IPU_V1 + ipu_csi_flash_strobe(true); +#endif + break; + default: + pr_debug(" default case\n"); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * V4L2 - mxc_v4l2_s_param function + * Allows setting of capturemode and frame rate. + * + * @param cam structure cam_data * + * @param parm structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_param(cam_data *cam, struct v4l2_streamparm *parm) +{ + struct v4l2_ifparm ifparm; + struct v4l2_format cam_fmt; + struct v4l2_streamparm currentparm; + ipu_csi_signal_cfg_t csi_param; + int err = 0; + + pr_debug("In mxc_v4l2_s_param\n"); + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_err(KERN_ERR "mxc_v4l2_s_param invalid type\n"); + return -EINVAL; + } + + /* Stop the viewfinder */ + if (cam->overlay_on == true) { + stop_preview(cam); + } + + currentparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* First check that this device can support the changes requested. */ + err = vidioc_int_g_parm(cam->sensor, ¤tparm); + if (err) { + pr_err("%s: vidioc_int_g_parm returned an error %d\n", + __func__, err); + goto exit; + } + + pr_debug(" Current capabilities are %x\n", + currentparm.parm.capture.capability); + pr_debug(" Current capturemode is %d change to %d\n", + currentparm.parm.capture.capturemode, + parm->parm.capture.capturemode); + pr_debug(" Current framerate is %d change to %d\n", + currentparm.parm.capture.timeperframe.denominator, + parm->parm.capture.timeperframe.denominator); + + /* This will change any camera settings needed. */ + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + err = vidioc_int_s_parm(cam->sensor, parm); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + if (err) { + pr_err("%s: vidioc_int_s_parm returned an error %d\n", + __func__, err); + goto exit; + } + + /* If resolution changed, need to re-program the CSI */ + /* Get new values. */ + vidioc_int_g_ifparm(cam->sensor, &ifparm); + + csi_param.data_width = 0; + csi_param.clk_mode = 0; + csi_param.ext_vsync = 0; + csi_param.Vsync_pol = 0; + csi_param.Hsync_pol = 0; + csi_param.pixclk_pol = 0; + csi_param.data_pol = 0; + csi_param.sens_clksrc = 0; + csi_param.pack_tight = 0; + csi_param.force_eof = 0; + csi_param.data_en_pol = 0; + csi_param.data_fmt = 0; + csi_param.csi = 0; + csi_param.mclk = 0; + + /* This may not work on other platforms. Check when adding a new one.*/ + pr_debug(" clock_curr=mclk=%d\n", ifparm.u.bt656.clock_curr); + if (ifparm.u.bt656.clock_curr == 0) { + csi_param.clk_mode = IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE; + } else { + csi_param.clk_mode = IPU_CSI_CLK_MODE_GATED_CLK; + } + + csi_param.pixclk_pol = ifparm.u.bt656.latch_clk_inv; + + if (ifparm.u.bt656.mode == V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT) { + csi_param.data_width = IPU_CSI_DATA_WIDTH_8; + } else if (ifparm.u.bt656.mode + == V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT) { + csi_param.data_width = IPU_CSI_DATA_WIDTH_10; + } else { + csi_param.data_width = IPU_CSI_DATA_WIDTH_8; + } + + csi_param.Vsync_pol = ifparm.u.bt656.nobt_vs_inv; + csi_param.Hsync_pol = ifparm.u.bt656.nobt_hs_inv; + csi_param.ext_vsync = ifparm.u.bt656.bt_sync_correct; + + /* if the capturemode changed, the size bounds will have changed. */ + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vidioc_int_g_fmt_cap(cam->sensor, &cam_fmt); + pr_debug(" g_fmt_cap returns widthxheight of input as %d x %d\n", + cam_fmt.fmt.pix.width, cam_fmt.fmt.pix.height); + + csi_param.data_fmt = cam_fmt.fmt.pix.pixelformat; + + cam->crop_bounds.top = cam->crop_bounds.left = 0; + cam->crop_bounds.width = cam_fmt.fmt.pix.width; + cam->crop_bounds.height = cam_fmt.fmt.pix.height; + + /* This essentially loses the data at the left and bottom of the image + * giving a digital zoom image, if crop_current is less than the full + * size of the image. */ + ipu_csi_set_window_size(cam->crop_current.width, + cam->crop_current.height, cam->csi); + ipu_csi_set_window_pos(cam->crop_current.left, + cam->crop_current.top, + cam->csi); + ipu_csi_init_interface(cam->crop_bounds.width, + cam->crop_bounds.height, + cam_fmt.fmt.pix.pixelformat, csi_param); + + +exit: + if (cam->overlay_on == true) + start_preview(cam); + + return err; +} + +/*! + * V4L2 - mxc_v4l2_s_std function + * + * Sets the TV standard to be used. + * + * @param cam structure cam_data * + * @param parm structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_std(cam_data *cam, v4l2_std_id e) +{ + bool change = false; + + if (e != cam->standard.id) { + change = true; + } + + pr_debug("In mxc_v4l2_s_std %Lx\n", e); + if (e == V4L2_STD_PAL) { + pr_debug(" Setting standard to PAL %Lx\n", V4L2_STD_PAL); + cam->standard.id = V4L2_STD_PAL; + video_index = TV_PAL; + cam->crop_current.top = 0; + } else if (e == V4L2_STD_NTSC) { + pr_debug(" Setting standard to NTSC %Lx\n", + V4L2_STD_NTSC); + /* Get rid of the white dot line in NTSC signal input */ + cam->standard.id = V4L2_STD_NTSC; + video_index = TV_NTSC; + cam->crop_current.top = 12; + } else { + cam->standard.id = V4L2_STD_ALL; + video_index = TV_NOT_LOCKED; + cam->crop_current.top = 0; + pr_err("ERROR: unrecognized std! %Lx (PAL=%Lx, NTSC=%Lx\n", + e, V4L2_STD_PAL, V4L2_STD_NTSC); + } + + cam->standard.index = video_index; + strcpy(cam->standard.name, video_fmts[video_index].name); + cam->crop_bounds.width = video_fmts[video_index].raw_width; + cam->crop_bounds.height = video_fmts[video_index].raw_height; + cam->crop_current.width = video_fmts[video_index].active_width; + cam->crop_current.height = video_fmts[video_index].active_height; + cam->crop_current.left = 0; + + return 0; +} + +/*! + * V4L2 - mxc_v4l2_g_std function + * + * Gets the TV standard from the TV input device. + * + * @param cam structure cam_data * + * + * @param e structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_g_std(cam_data *cam, v4l2_std_id *e) +{ + struct v4l2_format tv_fmt; + + pr_debug("In mxc_v4l2_g_std\n"); + + if (cam->device_type == 1) { + /* Use this function to get what the TV-In device detects the + * format to be. pixelformat is used to return the std value + * since the interface has no vidioc_g_std.*/ + tv_fmt.type = V4L2_BUF_TYPE_PRIVATE; + vidioc_int_g_fmt_cap(cam->sensor, &tv_fmt); + + /* If the TV-in automatically detects the standard, then if it + * changes, the settings need to change. */ + if (cam->standard_autodetect) { + if (cam->standard.id != tv_fmt.fmt.pix.pixelformat) { + pr_debug("MVC: mxc_v4l2_g_std: " + "Changing standard\n"); + mxc_v4l2_s_std(cam, tv_fmt.fmt.pix.pixelformat); + } + } + + *e = tv_fmt.fmt.pix.pixelformat; + } + + return 0; +} + +/*! + * Dequeue one V4L capture buffer + * + * @param cam structure cam_data * + * @param buf structure v4l2_buffer * + * + * @return status 0 success, EINVAL invalid frame number, + * ETIME timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l_dqueue(cam_data *cam, struct v4l2_buffer *buf) +{ + int retval = 0; + struct mxc_v4l_frame *frame; + + pr_debug("In MVC:mxc_v4l_dqueue\n"); + + if (!wait_event_interruptible_timeout(cam->enc_queue, + cam->enc_counter != 0, 10 * HZ)) { + pr_err("ERROR: v4l2 capture: mxc_v4l_dqueue timeout " + "enc_counter %x\n", + cam->enc_counter); + return -ETIME; + } else if (signal_pending(current)) { + pr_err("ERROR: v4l2 capture: mxc_v4l_dqueue() " + "interrupt received\n"); + return -ERESTARTSYS; + } + + cam->enc_counter--; + + frame = list_entry(cam->done_q.next, struct mxc_v4l_frame, queue); + list_del(cam->done_q.next); + if (frame->buffer.flags & V4L2_BUF_FLAG_DONE) { + frame->buffer.flags &= ~V4L2_BUF_FLAG_DONE; + } else if (frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + pr_err("ERROR: v4l2 capture: VIDIOC_DQBUF: " + "Buffer not filled.\n"); + frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + retval = -EINVAL; + } else if ((frame->buffer.flags & 0x7) == V4L2_BUF_FLAG_MAPPED) { + pr_err("ERROR: v4l2 capture: VIDIOC_DQBUF: " + "Buffer not queued.\n"); + retval = -EINVAL; + } + + buf->bytesused = cam->v2f.fmt.pix.sizeimage; + buf->index = frame->index; + buf->flags = frame->buffer.flags; + buf->m = cam->frame[frame->index].buffer.m; + + return retval; +} + +/*! + * V4L interface - open function + * + * @param inode structure inode * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l_open(struct inode *inode, struct file *file) +{ + struct v4l2_ifparm ifparm; + struct v4l2_format cam_fmt; + ipu_csi_signal_cfg_t csi_param; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int err = 0; + + pr_debug("\nIn MVC: mxc_v4l_open\n"); + pr_debug(" device name is %s\n", dev->name); + + if (!cam) { + pr_err("ERROR: v4l2 capture: Internal error, " + "cam_data not found!\n"); + return -EBADF; + } + + down(&cam->busy_lock); + err = 0; + if (signal_pending(current)) + goto oops; + + if (cam->open_count++ == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + + if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI MEM") == 0) { +#if defined(CONFIG_MXC_IPU_CSI_ENC) || defined(CONFIG_MXC_IPU_CSI_ENC_MODULE) + err = csi_enc_select(cam); +#endif + } else if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI IC MEM") == 0) { +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) + err = prp_enc_select(cam); +#endif + } + + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + + vidioc_int_g_ifparm(cam->sensor, &ifparm); + + csi_param.sens_clksrc = 0; + + csi_param.clk_mode = 0; + csi_param.data_pol = 0; + csi_param.ext_vsync = 0; + + csi_param.pack_tight = 0; + csi_param.force_eof = 0; + csi_param.data_en_pol = 0; + csi_param.mclk = ifparm.u.bt656.clock_curr; + + csi_param.pixclk_pol = ifparm.u.bt656.latch_clk_inv; + + /* Once we handle multiple inputs this will need to change. */ + csi_param.csi = 0; + + if (ifparm.u.bt656.mode + == V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT) + csi_param.data_width = IPU_CSI_DATA_WIDTH_8; + else if (ifparm.u.bt656.mode + == V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT) + csi_param.data_width = IPU_CSI_DATA_WIDTH_10; + else + csi_param.data_width = IPU_CSI_DATA_WIDTH_8; + + + csi_param.Vsync_pol = ifparm.u.bt656.nobt_vs_inv; + csi_param.Hsync_pol = ifparm.u.bt656.nobt_hs_inv; + + csi_param.csi = cam->csi; + + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vidioc_int_g_fmt_cap(cam->sensor, &cam_fmt); + + /* Reset the sizes. Needed to prevent carryover of last + * operation.*/ + cam->crop_bounds.top = cam->crop_bounds.left = 0; + cam->crop_bounds.width = cam_fmt.fmt.pix.width; + cam->crop_bounds.height = cam_fmt.fmt.pix.height; + + /* This also is the max crop size for this device. */ + cam->crop_defrect.top = cam->crop_defrect.left = 0; + cam->crop_defrect.width = cam_fmt.fmt.pix.width; + cam->crop_defrect.height = cam_fmt.fmt.pix.height; + + /* At this point, this is also the current image size. */ + cam->crop_current.top = cam->crop_current.left = 0; + cam->crop_current.width = cam_fmt.fmt.pix.width; + cam->crop_current.height = cam_fmt.fmt.pix.height; + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + csi_param.data_fmt = cam_fmt.fmt.pix.pixelformat; + pr_debug("On Open: Input to ipu size is %d x %d\n", + cam_fmt.fmt.pix.width, cam_fmt.fmt.pix.height); + ipu_csi_set_window_size(cam->crop_current.width, + cam->crop_current.width, + cam->csi); + ipu_csi_set_window_pos(cam->crop_current.left, + cam->crop_current.top, + cam->csi); + ipu_csi_init_interface(cam->crop_bounds.width, + cam->crop_bounds.height, + cam_fmt.fmt.pix.pixelformat, + csi_param); + + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + true, true); + vidioc_int_init(cam->sensor); + + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + false, false); +} + + file->private_data = dev; + + oops: + up(&cam->busy_lock); + return err; +} + +/*! + * V4L interface - close function + * + * @param inode struct inode * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + int err = 0; + cam_data *cam = video_get_drvdata(dev); + + pr_debug("In MVC:mxc_v4l_close\n"); + + if (!cam) { + pr_err("ERROR: v4l2 capture: Internal error, " + "cam_data not found!\n"); + return -EBADF; + } + + /* for the case somebody hit the ctrl C */ + if (cam->overlay_pid == current->pid) { + err = stop_preview(cam); + cam->overlay_on = false; + } + if (cam->capture_pid == current->pid) { + err |= mxc_streamoff(cam); + wake_up_interruptible(&cam->enc_queue); + } + + if (--cam->open_count == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + pr_info("mxc_v4l_close: release resource\n"); + + if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI MEM") == 0) { +#if defined(CONFIG_MXC_IPU_CSI_ENC) || defined(CONFIG_MXC_IPU_CSI_ENC_MODULE) + err |= csi_enc_deselect(cam); +#endif + } else if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI IC MEM") == 0) { +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) + err |= prp_enc_deselect(cam); +#endif + } + + mxc_free_frame_buf(cam); + file->private_data = NULL; + + /* capture off */ + wake_up_interruptible(&cam->enc_queue); + mxc_free_frames(cam); + cam->enc_counter++; + } + + return err; +} + +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_CSI_ENC) || \ + defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) || \ + defined(CONFIG_MXC_IPU_CSI_ENC_MODULE) +/* + * V4L interface - read function + * + * @param file struct file * + * @param read buf char * + * @param count size_t + * @param ppos structure loff_t * + * + * @return bytes read + */ +static ssize_t mxc_v4l_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + int err = 0; + u8 *v_address; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + /* Stop the viewfinder */ + if (cam->overlay_on == true) + stop_preview(cam); + + v_address = dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->still_buf, GFP_DMA | GFP_KERNEL); + + if (!v_address) { + err = -ENOBUFS; + goto exit0; + } + + err = prp_still_select(cam); + if (err != 0) { + err = -EIO; + goto exit1; + } + + cam->still_counter = 0; + err = cam->csi_start(cam); + if (err != 0) { + err = -EIO; + goto exit2; + } + + if (!wait_event_interruptible_timeout(cam->still_queue, + cam->still_counter != 0, + 10 * HZ)) { + pr_err("ERROR: v4l2 capture: mxc_v4l_read timeout counter %x\n", + cam->still_counter); + err = -ETIME; + goto exit2; + } + err = copy_to_user(buf, v_address, cam->v2f.fmt.pix.sizeimage); + + exit2: + prp_still_deselect(cam); + + exit1: + dma_free_coherent(0, cam->v2f.fmt.pix.sizeimage, v_address, + cam->still_buf); + cam->still_buf = 0; + + exit0: + if (cam->overlay_on == true) { + start_preview(cam); + } + + up(&cam->busy_lock); + if (err < 0) + return err; + + return (cam->v2f.fmt.pix.sizeimage - err); +} +#endif + +/*! + * V4L interface - ioctl function + * + * @param inode struct inode* + * + * @param file struct file* + * + * @param ioctlnr unsigned int + * + * @param arg void* + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int mxc_v4l_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int retval = 0; + unsigned long lock_flags; + + pr_debug("In MVC: mxc_v4l_do_ioctl %x\n", ioctlnr); + wait_event_interruptible(cam->power_queue, cam->low_power == false); + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + /*! + * V4l2 VIDIOC_QUERYCAP ioctl + */ + case VIDIOC_QUERYCAP: { + struct v4l2_capability *cap = arg; + pr_debug(" case VIDIOC_QUERYCAP\n"); + strcpy(cap->driver, "mxc_v4l2"); + cap->version = KERNEL_VERSION(0, 1, 11); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + break; + } + + /*! + * V4l2 VIDIOC_G_FMT ioctl + */ + case VIDIOC_G_FMT: { + struct v4l2_format *gf = arg; + pr_debug(" case VIDIOC_G_FMT\n"); + retval = mxc_v4l2_g_fmt(cam, gf); + break; + } + + /*! + * V4l2 VIDIOC_S_FMT ioctl + */ + case VIDIOC_S_FMT: { + struct v4l2_format *sf = arg; + pr_debug(" case VIDIOC_S_FMT\n"); + retval = mxc_v4l2_s_fmt(cam, sf); + break; + } + + /*! + * V4l2 VIDIOC_REQBUFS ioctl + */ + case VIDIOC_REQBUFS: { + struct v4l2_requestbuffers *req = arg; + pr_debug(" case VIDIOC_REQBUFS\n"); + + if (req->count > FRAME_NUM) { + pr_err("ERROR: v4l2 capture: VIDIOC_REQBUFS: " + "not enough buffers\n"); + req->count = FRAME_NUM; + } + + if ((req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (req->memory != V4L2_MEMORY_MMAP)) { + pr_err("ERROR: v4l2 capture: VIDIOC_REQBUFS: " + "wrong buffer type\n"); + retval = -EINVAL; + break; + } + + mxc_streamoff(cam); + mxc_free_frame_buf(cam); + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + + retval = mxc_allocate_frame_buf(cam, req->count); + break; + } + + /*! + * V4l2 VIDIOC_QUERYBUF ioctl + */ + case VIDIOC_QUERYBUF: { + struct v4l2_buffer *buf = arg; + int index = buf->index; + pr_debug(" case VIDIOC_QUERYBUF\n"); + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_err("ERROR: v4l2 capture: " + "VIDIOC_QUERYBUFS: " + "wrong buffer type\n"); + retval = -EINVAL; + break; + } + + memset(buf, 0, sizeof(buf)); + buf->index = index; + + down(&cam->param_lock); + retval = mxc_v4l2_buffer_status(cam, buf); + up(&cam->param_lock); + break; + } + + /*! + * V4l2 VIDIOC_QBUF ioctl + */ + case VIDIOC_QBUF: { + struct v4l2_buffer *buf = arg; + int index = buf->index; + pr_debug(" case VIDIOC_QBUF\n"); + + spin_lock_irqsave(&cam->int_lock, lock_flags); + cam->frame[index].buffer.m.offset = buf->m.offset; + if ((cam->frame[index].buffer.flags & 0x7) == + V4L2_BUF_FLAG_MAPPED) { + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + if (cam->skip_frame > 0) { + list_add_tail(&cam->frame[index].queue, + &cam->working_q); + retval = + cam->enc_update_eba(cam-> + frame[index]. + buffer.m.offset, + &cam-> + ping_pong_csi); + cam->skip_frame = 0; + } else { + list_add_tail(&cam->frame[index].queue, + &cam->ready_q); + } + } else if (cam->frame[index].buffer. + flags & V4L2_BUF_FLAG_QUEUED) { + pr_err("ERROR: v4l2 capture: VIDIOC_QBUF: " + "buffer already queued\n"); + retval = -EINVAL; + } else if (cam->frame[index].buffer. + flags & V4L2_BUF_FLAG_DONE) { + pr_err("ERROR: v4l2 capture: VIDIOC_QBUF: " + "overwrite done buffer.\n"); + cam->frame[index].buffer.flags &= + ~V4L2_BUF_FLAG_DONE; + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + retval = -EINVAL; + } + + buf->flags = cam->frame[index].buffer.flags; + spin_unlock_irqrestore(&cam->int_lock, lock_flags); + break; + } + + /*! + * V4l2 VIDIOC_DQBUF ioctl + */ + case VIDIOC_DQBUF: { + struct v4l2_buffer *buf = arg; + pr_debug(" case VIDIOC_DQBUF\n"); + + if ((cam->enc_counter == 0) && + (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + break; + } + + retval = mxc_v4l_dqueue(cam, buf); + + break; + } + + /*! + * V4l2 VIDIOC_STREAMON ioctl + */ + case VIDIOC_STREAMON: { + pr_debug(" case VIDIOC_STREAMON\n"); + retval = mxc_streamon(cam); + break; + } + + /*! + * V4l2 VIDIOC_STREAMOFF ioctl + */ + case VIDIOC_STREAMOFF: { + pr_debug(" case VIDIOC_STREAMOFF\n"); + retval = mxc_streamoff(cam); + break; + } + + /*! + * V4l2 VIDIOC_G_CTRL ioctl + */ + case VIDIOC_G_CTRL: { + pr_debug(" case VIDIOC_G_CTRL\n"); + retval = mxc_v4l2_g_ctrl(cam, arg); + break; + } + + /*! + * V4l2 VIDIOC_S_CTRL ioctl + */ + case VIDIOC_S_CTRL: { + pr_debug(" case VIDIOC_S_CTRL\n"); + retval = mxc_v4l2_s_ctrl(cam, arg); + break; + } + + /*! + * V4l2 VIDIOC_CROPCAP ioctl + */ + case VIDIOC_CROPCAP: { + struct v4l2_cropcap *cap = arg; + pr_debug(" case VIDIOC_CROPCAP\n"); + if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + cap->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + cap->bounds = cam->crop_bounds; + cap->defrect = cam->crop_defrect; + break; + } + + /*! + * V4l2 VIDIOC_G_CROP ioctl + */ + case VIDIOC_G_CROP: { + struct v4l2_crop *crop = arg; + pr_debug(" case VIDIOC_G_CROP\n"); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + crop->c = cam->crop_current; + break; + } + + /*! + * V4l2 VIDIOC_S_CROP ioctl + */ + case VIDIOC_S_CROP: { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = &cam->crop_bounds; + pr_debug(" case VIDIOC_S_CROP\n"); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + + crop->c.top = (crop->c.top < b->top) ? b->top + : crop->c.top; + if (crop->c.top > b->top + b->height) + crop->c.top = b->top + b->height - 1; + if (crop->c.height > b->top + b->height - crop->c.top) + crop->c.height = + b->top + b->height - crop->c.top; + + crop->c.left = (crop->c.left < b->left) ? b->left + : crop->c.left; + if (crop->c.left > b->left + b->width) + crop->c.left = b->left + b->width - 1; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + crop->c.width -= crop->c.width % 8; + crop->c.left -= crop->c.left % 4; + cam->crop_current = crop->c; + + pr_debug(" Cropping Input to ipu size %d x %d\n", + cam->crop_current.width, + cam->crop_current.height); + ipu_csi_set_window_size(cam->crop_current.width, + cam->crop_current.height, + cam->csi); + ipu_csi_set_window_pos(cam->crop_current.left, + cam->crop_current.top, + cam->csi); + break; + } + + /*! + * V4l2 VIDIOC_OVERLAY ioctl + */ + case VIDIOC_OVERLAY: { + int *on = arg; + pr_debug(" VIDIOC_OVERLAY on=%d\n", *on); + if (*on) { + cam->overlay_on = true; + cam->overlay_pid = current->pid; + retval = start_preview(cam); + } + if (!*on) { + retval = stop_preview(cam); + cam->overlay_on = false; + } + break; + } + + /*! + * V4l2 VIDIOC_G_FBUF ioctl + */ + case VIDIOC_G_FBUF: { + struct v4l2_framebuffer *fb = arg; + pr_debug(" case VIDIOC_G_FBUF\n"); + *fb = cam->v4l2_fb; + fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + break; + } + + /*! + * V4l2 VIDIOC_S_FBUF ioctl + */ + case VIDIOC_S_FBUF: { + struct v4l2_framebuffer *fb = arg; + pr_debug(" case VIDIOC_S_FBUF\n"); + cam->v4l2_fb = *fb; + break; + } + + case VIDIOC_G_PARM: { + struct v4l2_streamparm *parm = arg; + pr_debug(" case VIDIOC_G_PARM\n"); + vidioc_int_g_parm(cam->sensor, parm); + break; + } + + case VIDIOC_S_PARM: { + struct v4l2_streamparm *parm = arg; + pr_debug(" case VIDIOC_S_PARM\n"); + retval = mxc_v4l2_s_param(cam, parm); + break; + } + + /* linux v4l2 bug, kernel c0485619 user c0405619 */ + case VIDIOC_ENUMSTD: { + struct v4l2_standard *e = arg; + pr_debug(" case VIDIOC_ENUMSTD\n"); + *e = cam->standard; + break; + } + + case VIDIOC_G_STD: { + v4l2_std_id *e = arg; + pr_debug(" case VIDIOC_G_STD\n"); + retval = mxc_v4l2_g_std(cam, e); + break; + } + + case VIDIOC_S_STD: { + v4l2_std_id *e = arg; + pr_debug(" case VIDIOC_S_STD\n"); + retval = mxc_v4l2_s_std(cam, *e); + + break; + } + + case VIDIOC_ENUMOUTPUT: { + struct v4l2_output *output = arg; + pr_debug(" case VIDIOC_ENUMOUTPUT\n"); + if (output->index >= MXC_V4L2_CAPTURE_NUM_OUTPUTS) { + retval = -EINVAL; + break; + } + *output = mxc_capture_outputs[output->index]; + + break; + } + case VIDIOC_G_OUTPUT: { + int *p_output_num = arg; + pr_debug(" case VIDIOC_G_OUTPUT\n"); + *p_output_num = cam->output; + break; + } + + case VIDIOC_S_OUTPUT: { + int *p_output_num = arg; + pr_debug(" case VIDIOC_S_OUTPUT\n"); + if (*p_output_num >= MXC_V4L2_CAPTURE_NUM_OUTPUTS) { + retval = -EINVAL; + break; + } + cam->output = *p_output_num; + break; + } + + case VIDIOC_ENUMINPUT: { + struct v4l2_input *input = arg; + pr_debug(" case VIDIOC_ENUMINPUT\n"); + if (input->index >= MXC_V4L2_CAPTURE_NUM_INPUTS) { + retval = -EINVAL; + break; + } + *input = mxc_capture_inputs[input->index]; + break; + } + + case VIDIOC_G_INPUT: { + int *index = arg; + pr_debug(" case VIDIOC_G_INPUT\n"); + *index = cam->current_input; + break; + } + + case VIDIOC_S_INPUT: { + int *index = arg; + pr_debug(" case VIDIOC_S_INPUT\n"); + if (*index >= MXC_V4L2_CAPTURE_NUM_INPUTS) { + retval = -EINVAL; + break; + } + + if (*index == cam->current_input) + break; + + if ((mxc_capture_inputs[cam->current_input].status & + V4L2_IN_ST_NO_POWER) == 0) { + retval = mxc_streamoff(cam); + if (retval) + break; + mxc_capture_inputs[cam->current_input].status |= + V4L2_IN_ST_NO_POWER; + } + + if (strcmp(mxc_capture_inputs[*index].name, "CSI MEM") == 0) { +#if defined(CONFIG_MXC_IPU_CSI_ENC) || defined(CONFIG_MXC_IPU_CSI_ENC_MODULE) + retval = csi_enc_select(cam); + if (retval) + break; +#endif + } else if (strcmp(mxc_capture_inputs[*index].name, + "CSI IC MEM") == 0) { +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) + retval = prp_enc_select(cam); + if (retval) + break; +#endif + } + + mxc_capture_inputs[*index].status &= ~V4L2_IN_ST_NO_POWER; + cam->current_input = *index; + break; + } + + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + pr_debug(" case default or not supported\n"); + retval = -EINVAL; + break; + } + + up(&cam->busy_lock); + return retval; +} + +/* + * V4L interface - ioctl function + * + * @return None + */ +static int mxc_v4l_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + pr_debug("In MVC:mxc_v4l_ioctl\n"); + return video_usercopy(inode, file, cmd, arg, mxc_v4l_do_ioctl); +} + +/*! + * V4L interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int mxc_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = video_devdata(file); + unsigned long size; + int res = 0; + cam_data *cam = video_get_drvdata(dev); + + pr_debug("In MVC:mxc_mmap\n"); + pr_debug(" pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + pr_err("ERROR: v4l2 capture: mxc_mmap: " + "remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mxc_mmap_exit: + up(&cam->busy_lock); + return res; +} + +/*! + * V4L interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_poll(struct file *file, poll_table *wait) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + pr_debug("In MVC:mxc_poll\n"); + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + queue = &cam->enc_queue; + poll_wait(file, queue, wait); + + up(&cam->busy_lock); + + return res; +} + +/*! + * This structure defines the functions to be called in this driver. + */ +static struct file_operations mxc_v4l_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l_open, + .release = mxc_v4l_close, + .read = mxc_v4l_read, + .ioctl = mxc_v4l_ioctl, + .mmap = mxc_mmap, + .poll = mxc_poll, +}; + +static struct video_device mxc_v4l_template = { + .name = "Mxc Camera", + .vfl_type = VID_TYPE_CAPTURE, + .fops = &mxc_v4l_fops, + .release = video_device_release, +}; + +/*! + * This function can be used to release any platform data on closing. + */ +static void camera_platform_release(struct device *device) +{ +} + +/*! Device Definition for Mt9v111 devices */ +static struct platform_device mxc_v4l2_devices = { + .name = "mxc_v4l2", + .dev = { + .release = camera_platform_release, + }, + .id = 0, +}; + +/*! + * Camera V4l2 callback function. + * + * @param mask u32 + * + * @param dev void device structure + * + * @return status + */ +static void camera_callback(u32 mask, void *dev) +{ + struct mxc_v4l_frame *done_frame; + struct mxc_v4l_frame *ready_frame; + + cam_data *cam = (cam_data *) dev; + if (cam == NULL) + return; + + pr_debug("In MVC:camera_callback\n"); + + if (list_empty(&cam->working_q)) { + pr_err("ERROR: v4l2 capture: camera_callback: " + "working queue empty\n"); + return; + } + + done_frame = + list_entry(cam->working_q.next, struct mxc_v4l_frame, queue); + if (done_frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + done_frame->buffer.flags |= V4L2_BUF_FLAG_DONE; + done_frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + + if (list_empty(&cam->ready_q)) { + cam->skip_frame++; + } else { + ready_frame = list_entry(cam->ready_q.next, + struct mxc_v4l_frame, + queue); + list_del(cam->ready_q.next); + list_add_tail(&ready_frame->queue, &cam->working_q); + cam->enc_update_eba(ready_frame->buffer.m.offset, + &cam->ping_pong_csi); + } + + /* Added to the done queue */ + list_del(cam->working_q.next); + list_add_tail(&done_frame->queue, &cam->done_q); + + /* Wake up the queue */ + cam->enc_counter++; + wake_up_interruptible(&cam->enc_queue); + } else { + pr_err("ERROR: v4l2 capture: camera_callback: " + "buffer not queued\n"); + } +} + +/*! + * initialize cam_data structure + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static void init_camera_struct(cam_data *cam) +{ + pr_debug("In MVC: init_camera_struct\n"); + + /* Default everything to 0 */ + memset(cam, 0, sizeof(cam_data)); + + init_MUTEX(&cam->param_lock); + init_MUTEX(&cam->busy_lock); + + cam->video_dev = video_device_alloc(); + if (cam->video_dev == NULL) + return; + + *(cam->video_dev) = mxc_v4l_template; + + video_set_drvdata(cam->video_dev, cam); + dev_set_drvdata(&mxc_v4l2_devices.dev, (void *)cam); + cam->video_dev->minor = -1; + + init_waitqueue_head(&cam->enc_queue); + init_waitqueue_head(&cam->still_queue); + + /* setup cropping */ + cam->crop_bounds.left = 0; + cam->crop_bounds.width = 640; + cam->crop_bounds.top = 0; + cam->crop_bounds.height = 480; + cam->crop_current = cam->crop_defrect = cam->crop_bounds; + ipu_csi_set_window_size(cam->crop_current.width, + cam->crop_current.height, cam->csi); + ipu_csi_set_window_pos(cam->crop_current.left, + cam->crop_current.top, cam->csi); + cam->streamparm.parm.capture.capturemode = 0; + + cam->standard.index = 0; + cam->standard.id = V4L2_STD_UNKNOWN; + cam->standard.frameperiod.denominator = 30; + cam->standard.frameperiod.numerator = 1; + cam->standard.framelines = 480; + cam->standard_autodetect = true; + cam->streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->streamparm.parm.capture.timeperframe = cam->standard.frameperiod; + cam->streamparm.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + cam->overlay_on = false; + cam->capture_on = false; + cam->skip_frame = 0; + cam->v4l2_fb.flags = V4L2_FBUF_FLAG_OVERLAY; + + cam->v2f.fmt.pix.sizeimage = 352 * 288 * 3 / 2; + cam->v2f.fmt.pix.bytesperline = 288 * 3 / 2; + cam->v2f.fmt.pix.width = 288; + cam->v2f.fmt.pix.height = 352; + cam->v2f.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + cam->win.w.width = 160; + cam->win.w.height = 160; + cam->win.w.left = 0; + cam->win.w.top = 0; + + cam->csi = 0; /* Need to determine how to set this correctly with + * multiple video input devices. */ + + cam->enc_callback = camera_callback; + init_waitqueue_head(&cam->power_queue); + spin_lock_init(&cam->int_lock); +} + +/*! + * camera_power function + * Turns Sensor power On/Off + * + * @param cam cam data struct + * @param cameraOn true to turn camera on, false to turn off power. + * + * @return status + */ +static u8 camera_power(cam_data *cam, bool cameraOn) +{ + pr_debug("In MVC:camera_power on=%d\n", cameraOn); + + if (cameraOn == true) { + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + vidioc_int_s_power(cam->sensor, 1); + } else { + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + vidioc_int_s_power(cam->sensor, 0); + } + return 0; +} + +/*! + * This function is called to put the sensor in a low power state. + * Refer to the document driver-model/driver.txt in the kernel source tree + * for more information. + * + * @param pdev the device structure used to give information on which I2C + * to suspend + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure. + */ +static int mxc_v4l2_suspend(struct platform_device *pdev, pm_message_t state) +{ + cam_data *cam = platform_get_drvdata(pdev); + + pr_debug("In MVC:mxc_v4l2_suspend\n"); + + if (cam == NULL) { + return -1; + } + + cam->low_power = true; + + if (cam->overlay_on == true) + stop_preview(cam); + if ((cam->capture_on == true) && cam->enc_disable) { + cam->enc_disable(cam); + } + camera_power(cam, false); + + return 0; +} + +/*! + * This function is called to bring the sensor back from a low power state. + * Refer to the document driver-model/driver.txt in the kernel source tree + * for more information. + * + * @param pdev the device structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxc_v4l2_resume(struct platform_device *pdev) +{ + cam_data *cam = platform_get_drvdata(pdev); + + pr_debug("In MVC:mxc_v4l2_resume\n"); + + if (cam == NULL) { + return -1; + } + + cam->low_power = false; + wake_up_interruptible(&cam->power_queue); + camera_power(cam, true); + + if (cam->overlay_on == true) + start_preview(cam); + if (cam->capture_on == true) + mxc_streamon(cam); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2_driver = { + .driver = { + .name = "mxc_v4l2", + }, + .probe = NULL, + .remove = NULL, + .suspend = mxc_v4l2_suspend, + .resume = mxc_v4l2_resume, + .shutdown = NULL, +}; + +/*! + * Initializes the camera driver. + */ +static int mxc_v4l2_master_attach(struct v4l2_int_device *slave) +{ + cam_data *cam = slave->u.slave->master->priv; + struct v4l2_format cam_fmt; + + pr_debug("In MVC: mxc_v4l2_master_attach\n"); + pr_debug(" slave.name = %s\n", slave->name); + pr_debug(" master.name = %s\n", slave->u.slave->master->name); + + cam->sensor = slave; + if (slave == NULL) { + pr_err("ERROR: v4l2 capture: slave parameter not valid.\n"); + return -1; + } + + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + vidioc_int_dev_init(slave); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vidioc_int_g_fmt_cap(cam->sensor, &cam_fmt); + + /* Used to detect TV in (type 1) vs. camera (type 0)*/ + cam->device_type = cam_fmt.fmt.pix.priv; + + /* Set the input size to the ipu for this device */ + cam->crop_bounds.top = cam->crop_bounds.left = 0; + cam->crop_bounds.width = cam_fmt.fmt.pix.width; + cam->crop_bounds.height = cam_fmt.fmt.pix.height; + + /* This also is the max crop size for this device. */ + cam->crop_defrect.top = cam->crop_defrect.left = 0; + cam->crop_defrect.width = cam_fmt.fmt.pix.width; + cam->crop_defrect.height = cam_fmt.fmt.pix.height; + + /* At this point, this is also the current image size. */ + cam->crop_current.top = cam->crop_current.left = 0; + cam->crop_current.width = cam_fmt.fmt.pix.width; + cam->crop_current.height = cam_fmt.fmt.pix.height; + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + return 0; +} + +/*! + * Disconnects the camera driver. + */ +static void mxc_v4l2_master_detach(struct v4l2_int_device *slave) +{ + pr_debug("In MVC:mxc_v4l2_master_detach\n"); + vidioc_int_dev_exit(slave); +} + +/*! + * Entry point for the V4L2 + * + * @return Error code indicating success or failure + */ +static __init int camera_init(void) +{ + u8 err = 0; + + pr_debug("In MVC:camera_init\n"); + + /* Register the device driver structure. */ + err = platform_driver_register(&mxc_v4l2_driver); + if (err != 0) { + pr_err("ERROR: v4l2 capture:camera_init: " + "platform_driver_register failed.\n"); + return err; + } + + /* Create g_cam and initialize it. */ + if ((g_cam = kmalloc(sizeof(cam_data), GFP_KERNEL)) == NULL) { + pr_err("ERROR: v4l2 capture: failed to register camera\n"); + platform_driver_unregister(&mxc_v4l2_driver); + return -1; + } + init_camera_struct(g_cam); + + /* Set up the v4l2 device and register it*/ + mxc_v4l2_int_device.priv = g_cam; + /* This function contains a bug that won't let this be rmmod'd. */ + v4l2_int_device_register(&mxc_v4l2_int_device); + + /* Register the I2C device */ + err = platform_device_register(&mxc_v4l2_devices); + if (err != 0) { + pr_err("ERROR: v4l2 capture: camera_init: " + "platform_device_register failed.\n"); + platform_driver_unregister(&mxc_v4l2_driver); + kfree(g_cam); + g_cam = NULL; + return err; + } + + /* register v4l video device */ + if (video_register_device(g_cam->video_dev, VFL_TYPE_GRABBER, video_nr) + == -1) { + platform_device_unregister(&mxc_v4l2_devices); + platform_driver_unregister(&mxc_v4l2_driver); + kfree(g_cam); + g_cam = NULL; + pr_err("ERROR: v4l2 capture: video_register_device failed\n"); + return -1; + } + pr_debug(" Video device registered: %s #%d\n", + g_cam->video_dev->name, g_cam->video_dev->minor); + + return err; +} + +/*! + * Exit and cleanup for the V4L2 + */ +static void __exit camera_exit(void) +{ + pr_debug("In MVC: camera_exit\n"); + + pr_info("V4L2 unregistering video\n"); + + if (g_cam->open_count) { + pr_err("ERROR: v4l2 capture:camera open " + "-- setting ops to NULL\n"); + } else { + pr_info("V4L2 freeing image input device\n"); + v4l2_int_device_unregister(&mxc_v4l2_int_device); + video_unregister_device(g_cam->video_dev); + platform_driver_unregister(&mxc_v4l2_driver); + platform_device_unregister(&mxc_v4l2_devices); + + mxc_free_frame_buf(g_cam); + kfree(g_cam); + g_cam = NULL; + } +} + +module_init(camera_init); +module_exit(camera_exit); + +module_param(video_nr, int, 0444); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2 capture driver for Mxc based cameras"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/capture/mxc_v4l2_capture.h b/drivers/media/video/mxc/capture/mxc_v4l2_capture.h new file mode 100644 index 000000000000..a9c0c4d159da --- /dev/null +++ b/drivers/media/video/mxc/capture/mxc_v4l2_capture.h @@ -0,0 +1,199 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup MXC_V4L2_CAPTURE MXC V4L2 Video Capture Driver + */ +/*! + * @file mxc_v4l2_capture.h + * + * @brief mxc V4L2 capture device API Header file + * + * It include all the defines for frame operations, also three structure defines + * use case ops structure, common v4l2 driver structure and frame structure. + * + * @ingroup MXC_V4L2_CAPTURE + */ +#ifndef __MXC_V4L2_CAPTURE_H__ +#define __MXC_V4L2_CAPTURE_H__ + +#include <asm/uaccess.h> +#include <linux/list.h> +#include <linux/smp_lock.h> +#include <linux/ipu.h> +#include <linux/mxc_v4l2.h> + +#include <media/v4l2-dev.h> + +#define FRAME_NUM 3 + +/*! + * v4l2 frame structure. + */ +struct mxc_v4l_frame { + u32 paddress; + void *vaddress; + int count; + int width; + int height; + + struct v4l2_buffer buffer; + struct list_head queue; + int index; +}; + +/* Only for old version. Will go away soon. */ +typedef struct { + u8 clk_mode; + u8 ext_vsync; + u8 Vsync_pol; + u8 Hsync_pol; + u8 pixclk_pol; + u8 data_pol; + u8 data_width; + u8 pack_tight; + u8 force_eof; + u8 data_en_pol; + u16 width; + u16 height; + u32 pixel_fmt; + u32 mclk; + u16 active_width; + u16 active_height; +} sensor_interface; + +/* Sensor control function */ +/* Only for old version. Will go away soon. */ +struct camera_sensor { + void (*set_color) (int bright, int saturation, int red, int green, + int blue); + void (*get_color) (int *bright, int *saturation, int *red, int *green, + int *blue); + void (*set_ae_mode) (int ae_mode); + void (*get_ae_mode) (int *ae_mode); + sensor_interface *(*config) (int *frame_rate, int high_quality); + sensor_interface *(*reset) (void); + void (*get_std) (v4l2_std_id *std); + void (*set_std) (v4l2_std_id std); + unsigned int csi; +}; + +/*! + * common v4l2 driver structure. + */ +typedef struct _cam_data { + struct video_device *video_dev; + int device_type; + + /* semaphore guard against SMP multithreading */ + struct semaphore busy_lock; + + int open_count; + + /* params lock for this camera */ + struct semaphore param_lock; + + /* Encoder */ + struct list_head ready_q; + struct list_head done_q; + struct list_head working_q; + int ping_pong_csi; + spinlock_t int_lock; + struct mxc_v4l_frame frame[FRAME_NUM]; + int skip_frame; + wait_queue_head_t enc_queue; + int enc_counter; + dma_addr_t rot_enc_bufs[2]; + void *rot_enc_bufs_vaddr[2]; + int rot_enc_buf_size[2]; + enum v4l2_buf_type type; + + /* still image capture */ + wait_queue_head_t still_queue; + int still_counter; + dma_addr_t still_buf; + void *still_buf_vaddr; + + /* overlay */ + struct v4l2_window win; + struct v4l2_framebuffer v4l2_fb; + dma_addr_t vf_bufs[2]; + void *vf_bufs_vaddr[2]; + int vf_bufs_size[2]; + dma_addr_t rot_vf_bufs[2]; + void *rot_vf_bufs_vaddr[2]; + int rot_vf_buf_size[2]; + bool overlay_active; + int output; + struct fb_info *overlay_fb; + + /* v4l2 format */ + struct v4l2_format v2f; + int rotation; /* for IPUv1 and IPUv3, this means encoder rotation */ + int vf_rotation; /* viewfinder rotation only for IPUv1 and IPUv3 */ + struct v4l2_mxc_offset offset; + + /* V4l2 control bit */ + int bright; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + /* standard */ + struct v4l2_streamparm streamparm; + struct v4l2_standard standard; + bool standard_autodetect; + + /* crop */ + struct v4l2_rect crop_bounds; + struct v4l2_rect crop_defrect; + struct v4l2_rect crop_current; + + int (*enc_update_eba) (dma_addr_t eba, int *bufferNum); + int (*enc_enable) (void *private); + int (*enc_disable) (void *private); + void (*enc_callback) (u32 mask, void *dev); + int (*vf_start_adc) (void *private); + int (*vf_stop_adc) (void *private); + int (*vf_start_sdc) (void *private); + int (*vf_stop_sdc) (void *private); + int (*csi_start) (void *private); + int (*csi_stop) (void *private); + + /* misc status flag */ + bool overlay_on; + bool capture_on; + int overlay_pid; + int capture_pid; + bool low_power; + wait_queue_head_t power_queue; + unsigned int csi; + int current_input; + + /* camera sensor interface */ + struct camera_sensor *cam_sensor; /* old version */ + struct v4l2_int_device *sensor; +} cam_data; + +#if defined(CONFIG_MXC_IPU_V1) || defined(CONFIG_VIDEO_MXC_EMMA_CAMERA) \ + || defined(CONFIG_VIDEO_MXC_CSI_CAMERA_MODULE) \ + || defined(CONFIG_VIDEO_MXC_CSI_CAMERA) +void set_mclk_rate(uint32_t *p_mclk_freq); +#else +void set_mclk_rate(uint32_t *p_mclk_freq, uint32_t csi); +#endif +#endif /* __MXC_V4L2_CAPTURE_H__ */ diff --git a/drivers/media/video/mxc/capture/ov2640.c b/drivers/media/video/mxc/capture/ov2640.c new file mode 100644 index 000000000000..a1329b0b431d --- /dev/null +++ b/drivers/media/video/mxc/capture/ov2640.c @@ -0,0 +1,1080 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ov2640.c + * + * @brief ov2640 camera driver functions + * + * @ingroup Camera + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> + +#include <media/v4l2-int-device.h> +#include "mxc_v4l2_capture.h" + +#define MIN_FPS 5 +#define MAX_FPS 30 +#define DEFAULT_FPS 30 + +#define OV2640_XCLK_MIN 6000000 +#define OV2640_XCLK_MAX 27000000 + +/* +enum ov2640_mode { + ov2640_mode_1600_1120, + ov2640_mode_800_600 +}; +*/ + +struct reg_value { + u8 reg; + u8 value; + int delay_ms; +}; + +static struct reg_value ov2640_setting_1600_1120[] = { +#ifdef CONFIG_MACH_MX25_3DS + {0xff, 0x01, 0}, {0x12, 0x80, 5}, {0xff, 0x00, 0}, {0x2c, 0xff, 0}, + {0x2e, 0xdf, 0}, {0xff, 0x01, 0}, {0x3c, 0x32, 0}, {0x11, 0x00, 0}, + {0x09, 0x02, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, {0x14, 0x48, 0}, + {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, {0x3b, 0xfb, 0}, + {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, {0x39, 0x02, 0}, + {0x35, 0x58, 0}, {0x22, 0x0a, 0}, {0x37, 0x40, 0}, {0x23, 0x00, 0}, + {0x34, 0xa0, 0}, {0x36, 0x1a, 0}, {0x06, 0x02, 0}, {0x07, 0xc0, 0}, + {0x0d, 0xb7, 0}, {0x0e, 0x01, 0}, {0x4c, 0x00, 0}, {0x4a, 0x81, 0}, + {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, {0x26, 0x82, 0}, + {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x3f, 0}, {0x61, 0x70, 0}, + {0x62, 0x80, 0}, {0x7c, 0x05, 0}, {0x20, 0x80, 0}, {0x28, 0x30, 0}, + {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, {0x6e, 0x00, 0}, {0x70, 0x02, 0}, + {0x71, 0x94, 0}, {0x73, 0xc1, 0}, {0x3d, 0x34, 0}, {0x5a, 0x57, 0}, + {0x4f, 0xbb, 0}, {0x50, 0x9c, 0}, {0xff, 0x00, 0}, {0xe5, 0x7f, 0}, + {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0xe0, 0x14, 0}, {0x76, 0xff, 0}, + {0x33, 0xa0, 0}, {0x42, 0x20, 0}, {0x43, 0x18, 0}, {0x4c, 0x00, 0}, + {0x87, 0xd0, 0}, {0x88, 0x3f, 0}, {0xd7, 0x01, 0}, {0xd9, 0x10, 0}, + {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, {0xc9, 0x80, 0}, {0x7c, 0x00, 0}, + {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, {0x7d, 0x48, 0}, {0x7d, 0x48, 0}, + {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, {0x7d, 0x10, 0}, {0x7d, 0x0e, 0}, + {0x90, 0x00, 0}, {0x91, 0x0e, 0}, {0x91, 0x1a, 0}, {0x91, 0x31, 0}, + {0x91, 0x5a, 0}, {0x91, 0x69, 0}, {0x91, 0x75, 0}, {0x91, 0x7e, 0}, + {0x91, 0x88, 0}, {0x91, 0x8f, 0}, {0x91, 0x96, 0}, {0x91, 0xa3, 0}, + {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, {0x91, 0xd7, 0}, {0x91, 0xe8, 0}, + {0x91, 0x20, 0}, {0x92, 0x00, 0}, {0x93, 0x06, 0}, {0x93, 0xe3, 0}, + {0x93, 0x05, 0}, {0x93, 0x05, 0}, {0x93, 0x00, 0}, {0x93, 0x04, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x96, 0x00, 0}, + {0x97, 0x08, 0}, {0x97, 0x19, 0}, {0x97, 0x02, 0}, {0x97, 0x0c, 0}, + {0x97, 0x24, 0}, {0x97, 0x30, 0}, {0x97, 0x28, 0}, {0x97, 0x26, 0}, + {0x97, 0x02, 0}, {0x97, 0x98, 0}, {0x97, 0x80, 0}, {0x97, 0x00, 0}, + {0x97, 0x00, 0}, {0xc3, 0xed, 0}, {0xa4, 0x00, 0}, {0xa8, 0x00, 0}, + {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, {0xc7, 0x10, 0}, + {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, {0xb9, 0x7c, 0}, + {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, {0xb0, 0xc5, 0}, + {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, {0xc0, 0xc8, 0}, + {0xc1, 0x96, 0}, {0x86, 0x1d, 0}, {0x50, 0x00, 0}, {0x51, 0x90, 0}, + {0x52, 0x2c, 0}, {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x88, 0}, + {0x57, 0x00, 0}, {0x5a, 0x90, 0}, {0x5b, 0x2c, 0}, {0x5c, 0x05, 0}, + {0xc3, 0xed, 0}, {0x7f, 0x00, 0}, {0xda, 0x00, 0}, {0xe5, 0x1f, 0}, + {0xe1, 0x77, 0}, {0xe0, 0x00, 0}, {0xdd, 0x7f, 0}, {0x05, 0x00, 0}, + {0xff, 0x00, 0}, {0xe0, 0x04, 0}, {0xc0, 0xc8, 0}, {0xc1, 0x96, 0}, + {0x86, 0x3d, 0}, {0x50, 0x00, 0}, {0x51, 0x90, 0}, {0x52, 0x2c, 0}, + {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x88, 0}, {0x57, 0x00, 0}, + {0x5a, 0x40, 0}, {0x5b, 0xf0, 0}, {0x5c, 0x01, 0}, {0xd3, 0x82, 0}, + {0xe0, 0x00, 1000} +#else + {0xff, 0x1, 0}, {0x12, 0x80, 1}, {0xff, 0, 0}, {0x2c, 0xff, 0}, + {0x2e, 0xdf, 0}, {0xff, 0x1, 0}, {0x3c, 0x32, 0}, {0x11, 0x01, 0}, + {0x09, 0x00, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, {0x14, 0x48, 0}, + {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, {0x3b, 0xfb, 0}, + {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, {0x39, 0x82, 0}, + {0x35, 0x88, 0}, {0x22, 0x0a, 0}, {0x37, 0x40, 0}, {0x23, 0x00, 0}, + {0x34, 0xa0, 0}, {0x36, 0x1a, 0}, {0x06, 0x02, 0}, {0x07, 0xc0, 0}, + {0x0d, 0xb7, 0}, {0x0e, 0x01, 0}, {0x4c, 0x00, 0}, {0x4a, 0x81, 0}, + {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, {0x26, 0x82, 0}, + {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x3f, 0}, {0x0c, 0x3c, 0}, + {0x5d, 0x55, 0}, {0x5e, 0x7d, 0}, {0x5f, 0x7d, 0}, {0x60, 0x55, 0}, + {0x61, 0x70, 0}, {0x62, 0x80, 0}, {0x7c, 0x05, 0}, {0x20, 0x80, 0}, + {0x28, 0x30, 0}, {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, {0x6e, 00, 0}, + {0x70, 0x02, 0}, {0x71, 0x94, 0}, {0x73, 0xc1, 0}, {0x3d, 0x34, 0}, + {0x5a, 0x57, 0}, {0x4f, 0xbb, 0}, {0x50, 0x9c, 0}, {0xff, 0x00, 0}, + {0xe5, 0x7f, 0}, {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0x44, 0x06, 0}, + {0xe0, 0x14, 0}, {0x76, 0xff, 0}, {0x33, 0xa0, 0}, {0x42, 0x20, 0}, + {0x43, 0x18, 0}, {0x4c, 0x00, 0}, {0x87, 0xd0, 0}, {0xd7, 0x03, 0}, + {0xd9, 0x10, 0}, {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, {0xc9, 0x80, 0}, + {0x7c, 0x00, 0}, {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, {0x7d, 0x48, 0}, + {0x7d, 0x48, 0}, {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, {0x7d, 0x10, 0}, + {0x7d, 0x0e, 0}, {0x90, 0x00, 0}, {0x91, 0x0e, 0}, {0x91, 0x1a, 0}, + {0x91, 0x31, 0}, {0x91, 0x5a, 0}, {0x91, 0x69, 0}, {0x91, 0x75, 0}, + {0x91, 0x7e, 0}, {0x91, 0x88, 0}, {0x91, 0x8f, 0}, {0x91, 0x96, 0}, + {0x91, 0xa3, 0}, {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, {0x91, 0xd7, 0}, + {0x91, 0xe8, 0}, {0x91, 0x20, 0}, {0x92, 0x00, 0}, {0x93, 0x06, 0}, + {0x93, 0xe3, 0}, {0x93, 0x03, 0}, {0x93, 0x03, 0}, {0x93, 0x00, 0}, + {0x93, 0x02, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x96, 0x00, 0}, {0x97, 0x08, 0}, {0x97, 0x19, 0}, {0x97, 0x02, 0}, + {0x97, 0x0c, 0}, {0x97, 0x24, 0}, {0x97, 0x30, 0}, {0x97, 0x28, 0}, + {0x97, 0x26, 0}, {0x97, 0x02, 0}, {0x97, 0x98, 0}, {0x97, 0x80, 0}, + {0x97, 0x00, 0}, {0x97, 0x00, 0}, {0xa4, 0x00, 0}, {0xa8, 0x00, 0}, + {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, {0xc7, 0x10, 0}, + {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, {0xb9, 0x7c, 0}, + {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, {0xb0, 0xc5, 0}, + {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, {0xa6, 0x00, 0}, + {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x1b, 0}, {0xa7, 0x31, 0}, + {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, + {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, + {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, + {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xc0, 0xc8, 0}, {0xc1, 0x96, 0}, + {0x86, 0x3d, 0}, {0x50, 0x00, 0}, {0x51, 0x90, 0}, {0x52, 0x18, 0}, + {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x88, 0}, {0x57, 0x00, 0}, + {0x5a, 0x90, 0}, {0x5b, 0x18, 0}, {0x5c, 0x05, 0}, {0xc3, 0xef, 0}, + {0x7f, 0x00, 0}, {0xda, 0x01, 0}, {0xe5, 0x1f, 0}, {0xe1, 0x67, 0}, + {0xe0, 0x00, 0}, {0xdd, 0x7f, 0}, {0x05, 0x00, 0} +#endif +}; + +static struct reg_value ov2640_setting_800_600[] = { +#ifdef CONFIG_MACH_MX25_3DS + {0xff, 0x01, 0}, {0x12, 0x80, 5}, {0xff, 0x00, 0}, {0x2c, 0xff, 0}, + {0x2e, 0xdf, 0}, {0xff, 0x01, 0}, {0x3c, 0x32, 0}, {0x11, 0x00, 0}, + {0x09, 0x02, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, {0x14, 0x48, 0}, + {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, {0x3b, 0xfb, 0}, + {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, {0x39, 0x92, 0}, + {0x35, 0xda, 0}, {0x22, 0x1a, 0}, {0x37, 0xc3, 0}, {0x23, 0x00, 0}, + {0x34, 0xc0, 0}, {0x36, 0x1a, 0}, {0x06, 0x88, 0}, {0x07, 0xc0, 0}, + {0x0d, 0x87, 0}, {0x0e, 0x41, 0}, {0x4c, 0x00, 0}, + {0x48, 0x00, 0}, {0x5b, 0x00, 0}, {0x42, 0x03, 0}, {0x4a, 0x81, 0}, + {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, {0x26, 0x82, 0}, + {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x22, 0}, {0x0c, 0x3c, 0}, + {0x61, 0x70, 0}, {0x62, 0x80, 0}, {0x7c, 0x05, 0}, {0x20, 0x80, 0}, + {0x28, 0x30, 0}, {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, {0x6e, 0x00, 0}, + {0x70, 0x02, 0}, {0x71, 0x94, 0}, {0x73, 0xc1, 0}, {0x12, 0x40, 0}, + {0x17, 0x11, 0}, {0x18, 0x43, 0}, {0x19, 0x00, 0}, {0x1a, 0x4b, 0}, + {0x32, 0x09, 0}, {0x37, 0xc0, 0}, {0x4f, 0xca, 0}, {0x50, 0xa8, 0}, + {0x5a, 0x23, 0}, {0x6d, 0x00, 0}, {0x3d, 0x38, 0}, {0xff, 0x00, 0}, + {0xe5, 0x7f, 0}, {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0xe0, 0x14, 0}, + {0x76, 0xff, 0}, {0x33, 0xa0, 0}, {0x42, 0x20, 0}, {0x43, 0x18, 0}, + {0x4c, 0x00, 0}, {0x87, 0xd5, 0}, {0x88, 0x3f, 0}, {0xd7, 0x01, 0}, + {0xd9, 0x10, 0}, {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, {0xc9, 0x80, 0}, + {0x7c, 0x00, 0}, {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, {0x7d, 0x48, 0}, + {0x7d, 0x48, 0}, {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, {0x7d, 0x10, 0}, + {0x7d, 0x0e, 0}, {0x90, 0x00, 0}, {0x91, 0x0e, 0}, {0x91, 0x1a, 0}, + {0x91, 0x31, 0}, {0x91, 0x5a, 0}, {0x91, 0x69, 0}, {0x91, 0x75, 0}, + {0x91, 0x7e, 0}, {0x91, 0x88, 0}, {0x91, 0x8f, 0}, {0x91, 0x96, 0}, + {0x91, 0xa3, 0}, {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, {0x91, 0xd7, 0}, + {0x91, 0xe8, 0}, {0x91, 0x20, 0}, {0x92, 0x00, 0}, {0x93, 0x06, 0}, + {0x93, 0xe3, 0}, {0x93, 0x05, 0}, {0x93, 0x05, 0}, {0x93, 0x00, 0}, + {0x93, 0x04, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x96, 0x00, 0}, {0x97, 0x08, 0}, {0x97, 0x19, 0}, {0x97, 0x02, 0}, + {0x97, 0x0c, 0}, {0x97, 0x24, 0}, {0x97, 0x30, 0}, {0x97, 0x28, 0}, + {0x97, 0x26, 0}, {0x97, 0x02, 0}, {0x97, 0x98, 0}, {0x97, 0x80, 0}, + {0x97, 0x00, 0}, {0x97, 0x00, 0}, {0xc3, 0xed, 0}, {0xa4, 0x00, 0}, + {0xa8, 0x00, 0}, {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, + {0xc7, 0x10, 0}, {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, + {0xb9, 0x7c, 0}, {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, + {0xb0, 0xc5, 0}, {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, + {0xc0, 0x64, 0}, {0xc1, 0x4b, 0}, {0x8c, 0x00, 0}, {0x86, 0x3d, 0}, + {0x50, 0x00, 0}, {0x51, 0xc8, 0}, {0x52, 0x96, 0}, {0x53, 0x00, 0}, + {0x54, 0x00, 0}, {0x55, 0x00, 0}, {0x5a, 0xc8, 0}, {0x5b, 0x96, 0}, + {0x5c, 0x00, 0}, {0xd3, 0x82, 0}, {0xc3, 0xed, 0}, {0x7f, 0x00, 0}, + {0xda, 0x00, 0}, {0xe5, 0x1f, 0}, {0xe1, 0x67, 0}, {0xe0, 0x00, 0}, + {0xdd, 0x7f, 0}, {0x05, 0x00, 0}, {0xff, 0x00, 0}, {0xe0, 0x04, 0}, + {0xc0, 0x64, 0}, {0xc1, 0x4b, 0}, {0x8c, 0x00, 0}, {0x86, 0x3d, 0}, + {0x50, 0x00, 0}, {0x51, 0xc8, 0}, {0x52, 0x96, 0}, {0x53, 0x00, 0}, + {0x54, 0x00, 0}, {0x55, 0x00, 0}, {0x5a, 0xa0, 0}, {0x5b, 0x78, 0}, + {0x5c, 0x00, 0}, {0xd3, 0x82, 0}, {0xe0, 0x00, 1000} +#else + {0xff, 0, 0}, {0xff, 1, 0}, {0x12, 0x80, 1}, {0xff, 00, 0}, + {0x2c, 0xff, 0}, {0x2e, 0xdf, 0}, {0xff, 0x1, 0}, {0x3c, 0x32, 0}, + {0x11, 0x01, 0}, {0x09, 0x00, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, + {0x14, 0x48, 0}, {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, + {0x3b, 0xfb, 0}, {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, + {0x39, 0x92, 0}, {0x35, 0xda, 0}, {0x22, 0x1a, 0}, {0x37, 0xc3, 0}, + {0x23, 0x00, 0}, {0x34, 0xc0, 0}, {0x36, 0x1a, 0}, {0x06, 0x88, 0}, + {0x07, 0xc0, 0}, {0x0d, 0x87, 0}, {0x0e, 0x41, 0}, {0x4c, 0x00, 0}, + {0x4a, 0x81, 0}, {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, + {0x26, 0x82, 0}, {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x22, 0}, + {0x0c, 0x3c, 0}, {0x5d, 0x55, 0}, {0x5e, 0x7d, 0}, {0x5f, 0x7d, 0}, + {0x60, 0x55, 0}, {0x61, 0x70, 0}, {0x62, 0x80, 0}, {0x7c, 0x05, 0}, + {0x20, 0x80, 0}, {0x28, 0x30, 0}, {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, + {0x6e, 00, 0}, {0x70, 0x02, 0}, {0x71, 0x94, 0}, {0x73, 0xc1, 0}, + {0x12, 0x40, 0}, {0x17, 0x11, 0}, {0x18, 0x43, 0}, {0x19, 0x00, 0}, + {0x1a, 0x4b, 0}, {0x32, 0x09, 0}, {0x37, 0xc0, 0}, {0x4f, 0xca, 0}, + {0x50, 0xa8, 0}, {0x6d, 0x00, 0}, {0x3d, 0x38, 0}, {0xff, 0x00, 0}, + {0xe5, 0x7f, 0}, {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0x44, 0x06, 0}, + {0xe0, 0x14, 0}, {0x76, 0xff, 0}, {0x33, 0xa0, 0}, {0x42, 0x20, 0}, + {0x43, 0x18, 0}, {0x4c, 0x00, 0}, {0x87, 0xd0, 0}, {0x88, 0x3f, 0}, + {0xd7, 0x03, 0}, {0xd9, 0x10, 0}, {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, + {0xc9, 0x80, 0}, {0x7c, 0x00, 0}, {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, + {0x7d, 0x48, 0}, {0x7d, 0x48, 0}, {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, + {0x7d, 0x10, 0}, {0x7d, 0x0e, 0}, {0x90, 0x00, 0}, {0x91, 0x0e, 0}, + {0x91, 0x1a, 0}, {0x91, 0x31, 0}, {0x91, 0x5a, 0}, {0x91, 0x69, 0}, + {0x91, 0x75, 0}, {0x91, 0x7e, 0}, {0x91, 0x88, 0}, {0x91, 0x8f, 0}, + {0x91, 0x96, 0}, {0x91, 0xa3, 0}, {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, + {0x91, 0xd7, 0}, {0x91, 0xe8, 0}, {0x91, 0x20, 0}, {0x92, 0x00, 0}, + {0x93, 0x06, 0}, {0x93, 0xe3, 0}, {0x93, 0x03, 0}, {0x93, 0x03, 0}, + {0x93, 0x00, 0}, {0x93, 0x02, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x96, 0x00, 0}, {0x97, 0x08, 0}, {0x97, 0x19, 0}, + {0x97, 0x02, 0}, {0x97, 0x0c, 0}, {0x97, 0x24, 0}, {0x97, 0x30, 0}, + {0x97, 0x28, 0}, {0x97, 0x26, 0}, {0x97, 0x02, 0}, {0x97, 0x98, 0}, + {0x97, 0x80, 0}, {0x97, 0x00, 0}, {0x97, 0x00, 0}, {0xa4, 0x00, 0}, + {0xa8, 0x00, 0}, {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, + {0xc7, 0x10, 0}, {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, + {0xb9, 0x7c, 0}, {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, + {0xb0, 0xc5, 0}, {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, + {0xa6, 0x00, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x1b, 0}, + {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, + {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, + {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, + {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xc0, 0x64, 0}, + {0xc1, 0x4b, 0}, {0x86, 0x1d, 0}, {0x50, 0x00, 0}, {0x51, 0xc8, 0}, + {0x52, 0x96, 0}, {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x00, 0}, + {0x57, 0x00, 0}, {0x5a, 0xc8, 0}, {0x5b, 0x96, 0}, {0x5c, 0x00, 0}, + {0xc3, 0xef, 0}, {0x7f, 0x00, 0}, {0xda, 0x01, 0}, {0xe5, 0x1f, 0}, + {0xe1, 0x67, 0}, {0xe0, 0x00, 0}, {0xdd, 0x7f, 0}, {0x05, 0x00, 0} +#endif +}; + +/*! + * Maintains the information on the current state of the sesor. + */ +struct sensor { + const struct ov2640_platform_data *platform_data; + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_captureparm streamcap; + bool on; + + /* control settings */ + int brightness; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + u32 csi; + u32 mclk; + +} ov2640_data; + +static struct regulator *io_regulator; +static struct regulator *core_regulator; +static struct regulator *analog_regulator; +static struct regulator *gpo_regulator; + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +/* list of image formats supported by this sensor */ +/* +const static struct v4l2_fmtdesc ov2640_formats[] = { + { + .description = "YUYV (YUV 4:2:2), packed", + .pixelformat = V4L2_PIX_FMT_UYVY, + }, +}; + */ + +static int ov2640_init_mode(struct sensor *s) +{ + int ret = -1; + struct reg_value *setting; + int i, num; + + pr_debug("In ov2640:ov2640_init_mode capturemode is %d\n", + s->streamcap.capturemode); + + if (s->streamcap.capturemode & V4L2_MODE_HIGHQUALITY) { + s->pix.width = 1600; + s->pix.height = 1120; + setting = ov2640_setting_1600_1120; + num = ARRAY_SIZE(ov2640_setting_1600_1120); + } else { + s->pix.width = 800; + s->pix.height = 600; + setting = ov2640_setting_800_600; + num = ARRAY_SIZE(ov2640_setting_800_600); + } + + for (i = 0; i < num; i++) { + ret = i2c_smbus_write_byte_data(s->i2c_client, + setting[i].reg, + setting[i].value); + if (ret < 0) { + pr_err("write reg error: reg=%x, val=%x\n", + setting[i].reg, setting[i].value); + return ret; + } + if (setting[i].delay_ms > 0) + msleep(setting[i].delay_ms); + } + + return ret; +} + +/* At present only support change to 15fps(only for SVGA mode) */ +static int ov2640_set_fps(struct sensor *s, int fps) +{ + int ret = 0; + + if (i2c_smbus_write_byte_data(s->i2c_client, 0xff, 0x01) < 0) { + pr_err("in %s,change to sensor addr failed\n", __func__); + ret = -EPERM; + } + + /* change the camera framerate to 15fps(only for SVGA mode) */ + if (i2c_smbus_write_byte_data(s->i2c_client, 0x11, 0x01) < 0) { + pr_err("change camera to 15fps failed\n"); + ret = -EPERM; + } + + return ret; +} + +static int ov2640_set_format(struct sensor *s, int format) +{ + int ret = 0; + + if (i2c_smbus_write_byte_data(s->i2c_client, 0xff, 0x00) < 0) + ret = -EPERM; + + if (format == V4L2_PIX_FMT_RGB565) { + /* set RGB565 format */ + if (i2c_smbus_write_byte_data(s->i2c_client, 0xda, 0x08) < 0) + ret = -EPERM; + + if (i2c_smbus_write_byte_data(s->i2c_client, 0xd7, 0x03) < 0) + ret = -EPERM; + } else if (format == V4L2_PIX_FMT_YUV420) { + /* set YUV420 format */ + if (i2c_smbus_write_byte_data(s->i2c_client, 0xda, 0x00) < 0) + ret = -EPERM; + + if (i2c_smbus_write_byte_data(s->i2c_client, 0xd7, 0x1b) < 0) + ret = -EPERM; + } else { + pr_debug("format not supported\n"); + } + + return ret; +} + +/* --------------- IOCTL functions from v4l2_int_ioctl_desc --------------- */ + +/*! + * ioctl_g_ifparm - V4L2 sensor interface handler for vidioc_int_g_ifparm_num + * s: pointer to standard V4L2 device structure + * p: pointer to standard V4L2 vidioc_int_g_ifparm_num ioctl structure + * + * Gets slave interface parameters. + * Calculates the required xclk value to support the requested + * clock parameters in p. This value is returned in the p + * parameter. + * + * vidioc_int_g_ifparm returns platform-specific information about the + * interface settings used by the sensor. + * + * Given the image capture format in pix, the nominal frame period in + * timeperframe, calculate the required xclk frequency. + * + * Called on open. + */ +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + pr_debug("In ov2640:ioctl_g_ifparm\n"); + + if (s == NULL) { + pr_err(" ERROR!! no slave device set!\n"); + return -1; + } + + memset(p, 0, sizeof(*p)); + p->u.bt656.clock_curr = ov2640_data.mclk; + p->if_type = V4L2_IF_TYPE_BT656; + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.clock_min = OV2640_XCLK_MIN; + p->u.bt656.clock_max = OV2640_XCLK_MAX; + + return 0; +} + +/*! + * Sets the camera power. + * + * s pointer to the camera device + * on if 1, power is to be turned on. 0 means power is to be turned off + * + * ioctl_s_power - V4L2 sensor interface handler for vidioc_int_s_power_num + * @s: pointer to standard V4L2 device structure + * @on: power state to which device is to be set + * + * Sets devices power state to requrested state, if possible. + * This is called on open, close, suspend and resume. + */ +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + pr_debug("In ov2640:ioctl_s_power\n"); + + if (on && !sensor->on) { + gpio_sensor_active(); + if (io_regulator) + if (regulator_enable(io_regulator) != 0) + return -EIO; + if (core_regulator) + if (regulator_enable(core_regulator) != 0) + return -EIO; + if (gpo_regulator) + if (regulator_enable(gpo_regulator) != 0) + return -EIO; + if (analog_regulator) + if (regulator_enable(analog_regulator) != 0) + return -EIO; + } else if (!on && sensor->on) { + if (analog_regulator) + regulator_disable(analog_regulator); + if (core_regulator) + regulator_disable(core_regulator); + if (io_regulator) + regulator_disable(io_regulator); + if (gpo_regulator) + regulator_disable(gpo_regulator); + gpio_sensor_inactive(); + } + + sensor->on = on; + + return 0; +} + +/*! + * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the sensor's video CAPTURE parameters. + */ +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_captureparm *cparm = &a->parm.capture; + int ret = 0; + + pr_debug("In ov2640:ioctl_g_parm\n"); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = sensor->streamcap.capability; + cparm->timeperframe = sensor->streamcap.timeperframe; + cparm->capturemode = sensor->streamcap.capturemode; + ret = 0; + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_err(" type is not V4L2_BUF_TYPE_VIDEO_CAPTURE " \ + "but %d\n", a->type); + ret = -EINVAL; + break; + + default: + pr_err(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the sensor to use the input parameters, if possible. If + * not possible, reverts to the old parameters and returns the + * appropriate error code. + */ +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_fract *timeperframe = &a->parm.capture.timeperframe; + u32 tgt_fps; /* target frames per secound */ + int ret = 0; + + pr_debug("In ov2640:ioctl_s_parm\n"); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + + /* Check that the new frame rate is allowed. */ + if ((timeperframe->numerator == 0) + || (timeperframe->denominator == 0)) { + timeperframe->denominator = DEFAULT_FPS; + timeperframe->numerator = 1; + } + tgt_fps = timeperframe->denominator + / timeperframe->numerator; + + if (tgt_fps > MAX_FPS) { + timeperframe->denominator = MAX_FPS; + timeperframe->numerator = 1; + } else if (tgt_fps < MIN_FPS) { + timeperframe->denominator = MIN_FPS; + timeperframe->numerator = 1; + } + sensor->streamcap.timeperframe = *timeperframe; + sensor->streamcap.capturemode = + (u32)a->parm.capture.capturemode; + + ret = ov2640_init_mode(sensor); + if (tgt_fps == 15) + ov2640_set_fps(sensor, tgt_fps); + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_err(" type is not V4L2_BUF_TYPE_VIDEO_CAPTURE " \ + "but %d\n", a->type); + ret = -EINVAL; + break; + + default: + pr_err(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_s_fmt_cap - V4L2 sensor interface handler for ioctl_s_fmt_cap + * set camera output format and resolution format + * + * @s: pointer to standard V4L2 device structure + * @arg: pointer to parameter, according this to set camera + * + * Returns 0 if set succeed, else return -1 + */ +static int ioctl_s_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + u32 format = f->fmt.pix.pixelformat; + int size = 0, ret = 0; + + size = f->fmt.pix.width * f->fmt.pix.height; + switch (format) { + case V4L2_PIX_FMT_RGB565: + if (size > 640 * 480) + sensor->streamcap.capturemode = V4L2_MODE_HIGHQUALITY; + else + sensor->streamcap.capturemode = 0; + ret = ov2640_init_mode(sensor); + + ret = ov2640_set_format(sensor, V4L2_PIX_FMT_RGB565); + break; + case V4L2_PIX_FMT_UYVY: + if (size > 640 * 480) + sensor->streamcap.capturemode = V4L2_MODE_HIGHQUALITY; + else + sensor->streamcap.capturemode = 0; + ret = ov2640_init_mode(sensor); + break; + case V4L2_PIX_FMT_YUV420: + if (size > 640 * 480) + sensor->streamcap.capturemode = V4L2_MODE_HIGHQUALITY; + else + sensor->streamcap.capturemode = 0; + ret = ov2640_init_mode(sensor); + + /* YUYV: width * 2, YY: width */ + ret = ov2640_set_format(sensor, V4L2_PIX_FMT_YUV420); + break; + default: + pr_debug("case not supported\n"); + break; + } + + return ret; +} + +/*! + * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the sensor's current pixel format in the v4l2_format + * parameter. + */ +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + + pr_debug("In ov2640:ioctl_g_fmt_cap.\n"); + + f->fmt.pix = sensor->pix; + + return 0; +} + +/*! + * ioctl_g_ctrl - V4L2 sensor interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_G_CTRL ioctl structure + * + * If the requested control is supported, returns the control's current + * value from the video_control[] array. Otherwise, returns -EINVAL + * if the control is not supported. + */ +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int ret = 0; + + pr_debug("In ov2640:ioctl_g_ctrl\n"); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + vc->value = ov2640_data.brightness; + break; + case V4L2_CID_HUE: + vc->value = ov2640_data.hue; + break; + case V4L2_CID_CONTRAST: + vc->value = ov2640_data.contrast; + break; + case V4L2_CID_SATURATION: + vc->value = ov2640_data.saturation; + break; + case V4L2_CID_RED_BALANCE: + vc->value = ov2640_data.red; + break; + case V4L2_CID_BLUE_BALANCE: + vc->value = ov2640_data.blue; + break; + case V4L2_CID_EXPOSURE: + vc->value = ov2640_data.ae_mode; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/*! + * ioctl_s_ctrl - V4L2 sensor interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_S_CTRL ioctl structure + * + * If the requested control is supported, sets the control's current + * value in HW (and updates the video_control[] array). Otherwise, + * returns -EINVAL if the control is not supported. + */ +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + + pr_debug("In ov2640:ioctl_s_ctrl %d\n", vc->id); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + pr_debug(" V4L2_CID_BRIGHTNESS\n"); + break; + case V4L2_CID_CONTRAST: + pr_debug(" V4L2_CID_CONTRAST\n"); + break; + case V4L2_CID_SATURATION: + pr_debug(" V4L2_CID_SATURATION\n"); + break; + case V4L2_CID_HUE: + pr_debug(" V4L2_CID_HUE\n"); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + break; + case V4L2_CID_DO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_DO_WHITE_BALANCE\n"); + break; + case V4L2_CID_RED_BALANCE: + pr_debug(" V4L2_CID_RED_BALANCE\n"); + break; + case V4L2_CID_BLUE_BALANCE: + pr_debug(" V4L2_CID_BLUE_BALANCE\n"); + break; + case V4L2_CID_GAMMA: + pr_debug(" V4L2_CID_GAMMA\n"); + break; + case V4L2_CID_EXPOSURE: + pr_debug(" V4L2_CID_EXPOSURE\n"); + break; + case V4L2_CID_AUTOGAIN: + pr_debug(" V4L2_CID_AUTOGAIN\n"); + break; + case V4L2_CID_GAIN: + pr_debug(" V4L2_CID_GAIN\n"); + break; + case V4L2_CID_HFLIP: + pr_debug(" V4L2_CID_HFLIP\n"); + break; + case V4L2_CID_VFLIP: + pr_debug(" V4L2_CID_VFLIP\n"); + break; + default: + pr_debug(" Default case\n"); + retval = -EPERM; + break; + } + + return retval; +} + +/*! + * ioctl_init - V4L2 sensor interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + pr_debug("In ov2640:ioctl_init\n"); + + return 0; +} + +/*! + * ioctl_dev_init - V4L2 sensor interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + struct sensor *sensor = s->priv; + u32 tgt_xclk; /* target xclk */ + + pr_debug("In ov2640:ioctl_dev_init\n"); + + gpio_sensor_active(); + ov2640_data.on = true; + + tgt_xclk = ov2640_data.mclk; + tgt_xclk = min(tgt_xclk, (u32)OV2640_XCLK_MAX); + tgt_xclk = max(tgt_xclk, (u32)OV2640_XCLK_MIN); + ov2640_data.mclk = tgt_xclk; + + pr_debug(" Setting mclk to %d MHz\n", + tgt_xclk / 1000000); + set_mclk_rate(&ov2640_data.mclk); + + return ov2640_init_mode(sensor); +} + +/*! + * ioctl_dev_exit - V4L2 sensor interface handler for vidioc_int_dev_exit_num + * @s: pointer to standard V4L2 device structure + * + * Delinitialise the device when slave detaches to the master. + */ +static int ioctl_dev_exit(struct v4l2_int_device *s) +{ + pr_debug("In ov2640:ioctl_dev_exit\n"); + + gpio_sensor_inactive(); + + return 0; +} + +/*! + * This structure defines all the ioctls for this module and links them to the + * enumeration. + */ +static struct v4l2_int_ioctl_desc ov2640_ioctl_desc[] = { + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func *)ioctl_dev_init}, + {vidioc_int_dev_exit_num, (v4l2_int_ioctl_func*)ioctl_dev_exit}, + {vidioc_int_s_power_num, (v4l2_int_ioctl_func *)ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func *)ioctl_g_ifparm}, +/* {vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *)ioctl_g_needs_reset}, */ +/* {vidioc_int_reset_num, (v4l2_int_ioctl_func *)ioctl_reset}, */ + {vidioc_int_init_num, (v4l2_int_ioctl_func *)ioctl_init}, +/* {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap}, */ +/* {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_try_fmt_cap}, */ + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_g_fmt_cap}, + {vidioc_int_s_fmt_cap_num, (v4l2_int_ioctl_func*)ioctl_s_fmt_cap}, + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func *)ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func *)ioctl_s_parm}, +/* {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func *)ioctl_queryctrl}, */ + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func *)ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func *)ioctl_s_ctrl}, +}; + +static struct v4l2_int_slave ov2640_slave = { + .ioctls = ov2640_ioctl_desc, + .num_ioctls = ARRAY_SIZE(ov2640_ioctl_desc), +}; + +static struct v4l2_int_device ov2640_int_device = { + .module = THIS_MODULE, + .name = "ov2640", + .type = v4l2_int_type_slave, + .u = { + .slave = &ov2640_slave, + }, +}; + +/*! + * ov2640 I2C attach function + * Function set in i2c_driver struct. + * Called by insmod ov2640_camera.ko. + * + * @param client struct i2c_client* + * @return Error code indicating success or failure + */ +static int ov2640_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + struct mxc_camera_platform_data *plat_data = client->dev.platform_data; + + pr_debug("In ov2640_probe (RH_BT565)\n"); + + /* Set initial values for the sensor struct. */ + memset(&ov2640_data, 0, sizeof(ov2640_data)); + ov2640_data.i2c_client = client; + ov2640_data.mclk = 24000000; + ov2640_data.mclk = plat_data->mclk; + ov2640_data.pix.pixelformat = V4L2_PIX_FMT_UYVY; + ov2640_data.pix.width = 800; + ov2640_data.pix.height = 600; + ov2640_data.streamcap.capability = V4L2_MODE_HIGHQUALITY + | V4L2_CAP_TIMEPERFRAME; + ov2640_data.streamcap.capturemode = 0; + ov2640_data.streamcap.timeperframe.denominator = DEFAULT_FPS; + ov2640_data.streamcap.timeperframe.numerator = 1; + + if (plat_data->io_regulator) { + io_regulator = + regulator_get(&client->dev, plat_data->io_regulator); + if (!IS_ERR(io_regulator)) { + regulator_set_voltage(io_regulator, 2800000, 2800000); + if (regulator_enable(io_regulator) != 0) { + pr_err("%s:io set voltage error\n", __func__); + goto err1; + } else { + dev_dbg(&client->dev, + "%s:io set voltage ok\n", __func__); + } + } else + io_regulator = NULL; + } + + if (plat_data->core_regulator) { + core_regulator = + regulator_get(&client->dev, plat_data->core_regulator); + if (!IS_ERR(core_regulator)) { + regulator_set_voltage(core_regulator, + 1300000, 1300000); + if (regulator_enable(core_regulator) != 0) { + pr_err("%s:core set voltage error\n", __func__); + goto err2; + } else { + dev_dbg(&client->dev, + "%s:core set voltage ok\n", __func__); + } + } else + core_regulator = NULL; + } + + if (plat_data->analog_regulator) { + analog_regulator = + regulator_get(&client->dev, plat_data->analog_regulator); + if (!IS_ERR(analog_regulator)) { + regulator_set_voltage(analog_regulator, 2000000, 2000000); + if (regulator_enable(analog_regulator) != 0) { + pr_err("%s:analog set voltage error\n", + __func__); + goto err3; + } else { + dev_dbg(&client->dev, + "%s:analog set voltage ok\n", __func__); + } + } else + analog_regulator = NULL; + } + + if (plat_data->gpo_regulator) { + gpo_regulator = + regulator_get(&client->dev, plat_data->gpo_regulator); + if (!IS_ERR(gpo_regulator)) { + if (regulator_enable(gpo_regulator) != 0) { + pr_err("%s:gpo3 set voltage error\n", __func__); + goto err4; + } else { + dev_dbg(&client->dev, + "%s:gpo3 set voltage ok\n", __func__); + } + } else + gpo_regulator = NULL; + } + + /* This function attaches this structure to the /dev/video0 device. + * The pointer in priv points to the ov2640_data structure here.*/ + ov2640_int_device.priv = &ov2640_data; + retval = v4l2_int_device_register(&ov2640_int_device); + + return retval; + +err4: + if (analog_regulator) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } +err3: + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } +err2: + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } +err1: + return -1; +} + +/*! + * ov2640 I2C detach function + * Called on rmmod ov2640_camera.ko + * + * @param client struct i2c_client* + * @return Error code indicating success or failure + */ +static int ov2640_remove(struct i2c_client *client) +{ + pr_debug("In ov2640_remove\n"); + + v4l2_int_device_unregister(&ov2640_int_device); + + if (gpo_regulator) { + regulator_disable(gpo_regulator); + regulator_put(gpo_regulator); + } + + if (analog_regulator) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } + + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } + + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } + + return 0; +} + +static const struct i2c_device_id ov2640_id[] = { + {"ov2640", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ov2640_id); + +static struct i2c_driver ov2640_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ov2640", + }, + .probe = ov2640_probe, + .remove = ov2640_remove, + .id_table = ov2640_id, +/* To add power management add .suspend and .resume functions */ +}; + +/*! + * ov2640 init function + * Called by insmod ov2640_camera.ko. + * + * @return Error code indicating success or failure + */ +static __init int ov2640_init(void) +{ + u8 err; + + pr_debug("In ov2640_init\n"); + + err = i2c_add_driver(&ov2640_i2c_driver); + if (err != 0) + pr_err("%s:driver registration failed, error=%d \n", + __func__, err); + + return err; +} + +/*! + * OV2640 cleanup function + * Called on rmmod ov2640_camera.ko + * + * @return Error code indicating success or failure + */ +static void __exit ov2640_clean(void) +{ + pr_debug("In ov2640_clean\n"); + i2c_del_driver(&ov2640_i2c_driver); +} + +module_init(ov2640_init); +module_exit(ov2640_clean); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("OV2640 Camera Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ov3640.c b/drivers/media/video/mxc/capture/ov3640.c new file mode 100644 index 000000000000..47f6e8f3e4a5 --- /dev/null +++ b/drivers/media/video/mxc/capture/ov3640.c @@ -0,0 +1,1130 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-int-device.h> +#include "mxc_v4l2_capture.h" + +#define CAMERA_DBG + +#ifdef CAMERA_DBG + #define CAMERA_TRACE(x) (printk)x +#else + #define CAMERA_TRACE(x) +#endif + +#define OV3640_VOLTAGE_ANALOG 2800000 +#define OV3640_VOLTAGE_DIGITAL_CORE 1500000 +#define OV3640_VOLTAGE_DIGITAL_IO 1800000 + + +/* Check these values! */ +#define MIN_FPS 15 +#define MAX_FPS 30 +#define DEFAULT_FPS 30 + +#define OV3640_XCLK_MIN 6000000 +#define OV3640_XCLK_MAX 24000000 + +enum ov3640_mode { + ov3640_mode_MIN = 0, + ov3640_mode_VGA_640_480 = 0, + ov3640_mode_QVGA_320_240 = 1, + ov3640_mode_QXGA_2048_1536 = 2, + ov3640_mode_XGA_1024_768 = 3, + ov3640_mode_MAX = 3 +}; + +enum ov3640_frame_rate { + ov3640_15_fps, + ov3640_30_fps +}; + +struct reg_value { + u16 u16RegAddr; + u8 u8Val; + u8 u8Mask; + u32 u32Delay_ms; +}; + +struct ov3640_mode_info { + enum ov3640_mode mode; + u32 width; + u32 height; + struct reg_value *init_data_ptr; + u32 init_data_size; +}; + +/*! + * Maintains the information on the current state of the sesor. + */ +struct sensor { + const struct ov3640_platform_data *platform_data; + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_captureparm streamcap; + bool on; + + /* control settings */ + int brightness; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + u32 mclk; + int csi; +} ov3640_data; + +static struct reg_value ov3640_setting_15fps_QXGA_2048_1536[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x00, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, + {0x30bb, 0x08, 0, 0}, {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, + {0x3100, 0x02, 0, 0}, {0x3301, 0xde, 0, 0}, {0x3304, 0x00, 0, 0}, + {0x3400, 0x00, 0, 0}, {0x3404, 0x02, 0, 0}, {0x3600, 0xc4, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x3020, 0x01, 0, 0}, {0x3021, 0x1d, 0, 0}, + {0x3022, 0x00, 0, 0}, {0x3023, 0x0a, 0, 0}, {0x3024, 0x08, 0, 0}, + {0x3025, 0x00, 0, 0}, {0x3026, 0x06, 0, 0}, {0x3027, 0x00, 0, 0}, + {0x335f, 0x68, 0, 0}, {0x3360, 0x00, 0, 0}, {0x3361, 0x00, 0, 0}, + {0x3362, 0x68, 0, 0}, {0x3363, 0x00, 0, 0}, {0x3364, 0x00, 0, 0}, + {0x3403, 0x00, 0, 0}, {0x3088, 0x08, 0, 0}, {0x3089, 0x00, 0, 0}, + {0x308a, 0x06, 0, 0}, {0x308b, 0x00, 0, 0}, {0x307c, 0x10, 0, 0}, + {0x3090, 0xc0, 0, 0}, {0x304c, 0x84, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3012, 0x00, 0, 0}, + {0x3020, 0x01, 0, 0}, {0x3021, 0x1d, 0, 0}, {0x3022, 0x00, 0, 0}, + {0x3023, 0x0a, 0, 0}, {0x3024, 0x08, 0, 0}, {0x3025, 0x18, 0, 0}, + {0x3026, 0x06, 0, 0}, {0x3027, 0x0c, 0, 0}, {0x302a, 0x06, 0, 0}, + {0x302b, 0x20, 0, 0}, {0x3075, 0x44, 0, 0}, {0x300d, 0x00, 0, 0}, + {0x30d7, 0x00, 0, 0}, {0x3069, 0x40, 0, 0}, {0x303e, 0x01, 0, 0}, + {0x303f, 0x80, 0, 0}, {0x3302, 0x20, 0, 0}, {0x335f, 0x68, 0, 0}, + {0x3360, 0x18, 0, 0}, {0x3361, 0x0c, 0, 0}, {0x3362, 0x68, 0, 0}, + {0x3363, 0x08, 0, 0}, {0x3364, 0x04, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x08, 0, 0}, {0x3089, 0x00, 0, 0}, {0x308a, 0x06, 0, 0}, + {0x308b, 0x00, 0, 0}, +}; + +static struct reg_value ov3640_setting_15fps_XGA_1024_768[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x00, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, + {0x30bb, 0x08, 0, 0}, {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, + {0x3100, 0x02, 0, 0}, {0x3301, 0xde, 0, 0}, {0x3304, 0x00, 0, 0}, + {0x3400, 0x01, 0, 0}, {0x3404, 0x1d, 0, 0}, {0x3600, 0xc4, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x3020, 0x01, 0, 0}, {0x3021, 0x1d, 0, 0}, + {0x3022, 0x00, 0, 0}, {0x3023, 0x0a, 0, 0}, {0x3024, 0x08, 0, 0}, + {0x3025, 0x00, 0, 0}, {0x3026, 0x06, 0, 0}, {0x3027, 0x00, 0, 0}, + {0x335f, 0x68, 0, 0}, {0x3360, 0x00, 0, 0}, {0x3361, 0x00, 0, 0}, + {0x3362, 0x34, 0, 0}, {0x3363, 0x00, 0, 0}, {0x3364, 0x00, 0, 0}, + {0x3403, 0x00, 0, 0}, {0x3088, 0x04, 0, 0}, {0x3089, 0x00, 0, 0}, + {0x308a, 0x03, 0, 0}, {0x308b, 0x00, 0, 0}, {0x307c, 0x10, 0, 0}, + {0x3090, 0xc0, 0, 0}, {0x304c, 0x84, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3011, 0x01, 0, 0}, +}; + +static struct reg_value ov3640_setting_30fps_XGA_1024_768[] = { + {0x0, 0x0, 0} +}; + +static struct reg_value ov3640_setting_15fps_VGA_640_480[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x00, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, + {0x30bb, 0x08, 0, 0}, {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, + {0x3100, 0x02, 0, 0}, {0x3301, 0xde, 0, 0}, {0x3304, 0x00, 0, 0}, + {0x3400, 0x00, 0, 0}, {0x3404, 0x42, 0, 0}, {0x3600, 0xc4, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x3020, 0x01, 0, 0}, {0x3021, 0x1d, 0, 0}, + {0x3022, 0x00, 0, 0}, {0x3023, 0x0a, 0, 0}, {0x3024, 0x08, 0, 0}, + {0x3025, 0x00, 0, 0}, {0x3026, 0x06, 0, 0}, {0x3027, 0x00, 0, 0}, + {0x335f, 0x68, 0, 0}, {0x3360, 0x00, 0, 0}, {0x3361, 0x00, 0, 0}, + {0x3362, 0x12, 0, 0}, {0x3363, 0x80, 0, 0}, {0x3364, 0xe0, 0, 0}, + {0x3403, 0x00, 0, 0}, {0x3088, 0x02, 0, 0}, {0x3089, 0x80, 0, 0}, + {0x308a, 0x01, 0, 0}, {0x308b, 0xe0, 0, 0}, {0x307c, 0x10, 0, 0}, + {0x3090, 0xc0, 0, 0}, {0x304c, 0x84, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3011, 0x00, 0, 0}, +}; + +static struct reg_value ov3640_setting_30fps_VGA_640_480[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x82, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x0c, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x3300, 0x13, 0, 0}, {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, + {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0x10, 0x30, 0}, {0x3304, 0x00, 0x03, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x02, 0, 0}, {0x3600, 0xc0, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3012, 0x10, 0, 0}, + {0x3023, 0x06, 0, 0}, {0x3026, 0x03, 0, 0}, {0x3027, 0x04, 0, 0}, + {0x302a, 0x03, 0, 0}, {0x302b, 0x10, 0, 0}, {0x3075, 0x24, 0, 0}, + {0x300d, 0x01, 0, 0}, {0x30d7, 0x80, 0x80, 0}, {0x3069, 0x00, 0x40, 0}, + {0x303e, 0x00, 0, 0}, {0x303f, 0xc0, 0, 0}, {0x3302, 0x20, 0x20, 0}, + {0x335f, 0x34, 0, 0}, {0x3360, 0x0c, 0, 0}, {0x3361, 0x04, 0, 0}, + {0x3362, 0x12, 0, 0}, {0x3363, 0x88, 0, 0}, {0x3364, 0xe4, 0, 0}, + {0x3403, 0x42, 0, 0}, {0x3088, 0x02, 0, 0}, {0x3089, 0x80, 0, 0}, + {0x308a, 0x01, 0, 0}, {0x308b, 0xe0, 0, 0}, {0x3362, 0x12, 0, 0}, + {0x3363, 0x88, 0, 0}, {0x3364, 0xe4, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x02, 0, 0}, {0x3089, 0x80, 0, 0}, {0x308a, 0x01, 0, 0}, + {0x308b, 0xe0, 0, 0}, {0x300e, 0x37, 0, 0}, {0x300f, 0xe1, 0, 0}, + {0x3010, 0x22, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x84, 0, 0}, + {0x3014, 0x04, 0, 0}, {0x3015, 0x02, 0, 0}, {0x302e, 0x00, 0, 0}, + {0x302d, 0x00, 0, 0}, +}; + +static struct reg_value ov3640_setting_15fps_QVGA_320_240[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x00, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, + {0x30bb, 0x08, 0, 0}, {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, + {0x3100, 0x02, 0, 0}, {0x3301, 0xde, 0, 0}, {0x3304, 0x00, 0, 0}, + {0x3400, 0x00, 0, 0}, {0x3404, 0x42, 0, 0}, {0x3600, 0xc4, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x3020, 0x01, 0, 0}, {0x3021, 0x1d, 0, 0}, + {0x3022, 0x00, 0, 0}, {0x3023, 0x0a, 0, 0}, {0x3024, 0x08, 0, 0}, + {0x3025, 0x00, 0, 0}, {0x3026, 0x06, 0, 0}, {0x3027, 0x00, 0, 0}, + {0x335f, 0x68, 0, 0}, {0x3360, 0x00, 0, 0}, {0x3361, 0x00, 0, 0}, + {0x3362, 0x01, 0, 0}, {0x3363, 0x40, 0, 0}, {0x3364, 0xf0, 0, 0}, + {0x3403, 0x00, 0, 0}, {0x3088, 0x01, 0, 0}, {0x3089, 0x40, 0, 0}, + {0x308a, 0x00, 0, 0}, {0x308b, 0xf0, 0, 0}, {0x307c, 0x10, 0, 0}, + {0x3090, 0xc0, 0, 0}, {0x304c, 0x84, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3011, 0x01, 0, 0}, +}; + +static struct reg_value ov3640_setting_30fps_QVGA_320_240[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x82, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x0c, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x3300, 0x13, 0, 0}, {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, + {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0x10, 0x30, 0}, {0x3304, 0x00, 0x03, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x02, 0, 0}, {0x3600, 0xc0, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3012, 0x10, 0, 0}, + {0x3023, 0x06, 0, 0}, {0x3026, 0x03, 0, 0}, {0x3027, 0x04, 0, 0}, + {0x302a, 0x03, 0, 0}, {0x302b, 0x10, 0, 0}, {0x3075, 0x24, 0, 0}, + {0x300d, 0x01, 0, 0}, {0x30d7, 0x80, 0x80, 0}, {0x3069, 0x00, 0x40, 0}, + {0x303e, 0x00, 0, 0}, {0x303f, 0xc0, 0, 0}, {0x3302, 0x20, 0x20, 0}, + {0x335f, 0x34, 0, 0}, {0x3360, 0x0c, 0, 0}, {0x3361, 0x04, 0, 0}, + {0x3362, 0x34, 0, 0}, {0x3363, 0x08, 0, 0}, {0x3364, 0x04, 0, 0}, + {0x3403, 0x42, 0, 0}, {0x3088, 0x04, 0, 0}, {0x3089, 0x00, 0, 0}, + {0x308a, 0x03, 0, 0}, {0x308b, 0x00, 0, 0}, {0x3362, 0x12, 0, 0}, + {0x3363, 0x88, 0, 0}, {0x3364, 0xe4, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x02, 0, 0}, {0x3089, 0x80, 0, 0}, {0x308a, 0x01, 0, 0}, + {0x308b, 0xe0, 0, 0}, {0x300e, 0x37, 0, 0}, {0x300f, 0xe1, 0, 0}, + {0x3010, 0x22, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x84, 0, 0}, +}; + +static struct ov3640_mode_info ov3640_mode_info_data[2][ov3640_mode_MAX + 1] = { + { + {ov3640_mode_VGA_640_480, 640, 480, + ov3640_setting_15fps_VGA_640_480, + ARRAY_SIZE(ov3640_setting_15fps_VGA_640_480)}, + {ov3640_mode_QVGA_320_240, 320, 240, + ov3640_setting_15fps_QVGA_320_240, + ARRAY_SIZE(ov3640_setting_15fps_QVGA_320_240)}, + {ov3640_mode_XGA_1024_768, 1024, 768, + ov3640_setting_15fps_XGA_1024_768, + ARRAY_SIZE(ov3640_setting_15fps_XGA_1024_768)}, + {ov3640_mode_QXGA_2048_1536, 2048, 1536, + ov3640_setting_15fps_QXGA_2048_1536, + ARRAY_SIZE(ov3640_setting_15fps_QXGA_2048_1536)}, + }, + { + {ov3640_mode_VGA_640_480, 640, 480, + ov3640_setting_30fps_VGA_640_480, + ARRAY_SIZE(ov3640_setting_30fps_VGA_640_480)}, + {ov3640_mode_QVGA_320_240, 320, 240, + ov3640_setting_30fps_QVGA_320_240, + ARRAY_SIZE(ov3640_setting_30fps_QVGA_320_240)}, + {ov3640_mode_XGA_1024_768, 1024, 768, + ov3640_setting_30fps_XGA_1024_768, + ARRAY_SIZE(ov3640_setting_30fps_XGA_1024_768)}, + {ov3640_mode_QXGA_2048_1536, 0, 0, NULL, 0}, + }, +}; + +static struct regulator *io_regulator; +static struct regulator *core_regulator; +static struct regulator *analog_regulator; +static struct regulator *gpo_regulator; + +static int ov3640_probe(struct i2c_client *adapter, + const struct i2c_device_id *device_id); +static int ov3640_remove(struct i2c_client *client); + +static s32 ov3640_read_reg(u16 reg, u8 *val); +static s32 ov3640_write_reg(u16 reg, u8 val); + +static const struct i2c_device_id ov3640_id[] = { + {"ov3640", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ov3640_id); + +static struct i2c_driver ov3640_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ov3640", + }, + .probe = ov3640_probe, + .remove = ov3640_remove, + .id_table = ov3640_id, +}; + +extern void gpio_sensor_active(unsigned int csi_index); +extern void gpio_sensor_inactive(unsigned int csi); + +static s32 ov3640_write_reg(u16 reg, u8 val) +{ + u8 au8Buf[3] = {0}; + + au8Buf[0] = reg >> 8; + au8Buf[1] = reg & 0xff; + au8Buf[2] = val; + + if (i2c_master_send(ov3640_data.i2c_client, au8Buf, 3) < 0) { + pr_err("%s:write reg error:reg=%x,val=%x\n", + __func__, reg, val); + return -1; + } + + return 0; +} + +static s32 ov3640_read_reg(u16 reg, u8 *val) +{ + u8 au8RegBuf[2] = {0}; + u8 u8RdVal = 0; + + au8RegBuf[0] = reg >> 8; + au8RegBuf[1] = reg & 0xff; + + if (2 != i2c_master_send(ov3640_data.i2c_client, au8RegBuf, 2)) { + pr_err("%s:write reg error:reg=%x\n", + __func__, reg); + return -1; + } + + if (1 != i2c_master_recv(ov3640_data.i2c_client, &u8RdVal, 1)) { + pr_err("%s:read reg error:reg=%x,val=%x\n", + __func__, reg, u8RdVal); + return -1; + } + + *val = u8RdVal; + + return u8RdVal; +} + +static int ov3640_init_mode(enum ov3640_frame_rate frame_rate, + enum ov3640_mode mode) +{ + struct reg_value *pModeSetting = NULL; + s32 i = 0; + s32 iModeSettingArySize = 0; + register u32 Delay_ms = 0; + register u16 RegAddr = 0; + register u8 Mask = 0; + register u8 Val = 0; + u8 RegVal = 0; + int retval = 0; + + CAMERA_TRACE(("CAMERA_DBG Entry: ov3640_init_mode\n")); + + if (mode > ov3640_mode_MAX || mode < ov3640_mode_MIN) { + pr_err("Wrong ov3640 mode detected!\n"); + return -1; + } + + pModeSetting = ov3640_mode_info_data[frame_rate][mode].init_data_ptr; + iModeSettingArySize = + ov3640_mode_info_data[frame_rate][mode].init_data_size; + + ov3640_data.pix.width = ov3640_mode_info_data[frame_rate][mode].width; + ov3640_data.pix.height = ov3640_mode_info_data[frame_rate][mode].height; + + for (i = 0; i < iModeSettingArySize; ++i, ++pModeSetting) { + Delay_ms = pModeSetting->u32Delay_ms; + RegAddr = pModeSetting->u16RegAddr; + Val = pModeSetting->u8Val; + Mask = pModeSetting->u8Mask; + + if (Mask) { + retval = ov3640_read_reg(RegAddr, &RegVal); + if (retval < 0) + goto err; + + RegVal &= ~(u8)Mask; + Val &= Mask; + Val |= RegVal; + } + + retval = ov3640_write_reg(RegAddr, Val); + if (retval < 0) + goto err; + + if (Delay_ms) + msleep(Delay_ms); + } +err: + CAMERA_TRACE(("CAMERA_DBG Exit: ov3640_init_mode\n")); + + return retval; +} + +/* --------------- IOCTL functions from v4l2_int_ioctl_desc --------------- */ + +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + CAMERA_TRACE(("In ov3640:ioctl_g_ifparm\n")); + if (s == NULL) { + pr_err(" ERROR!! no slave device set!\n"); + return -1; + } + + memset(p, 0, sizeof(*p)); + p->u.bt656.clock_curr = ov3640_data.mclk; + pr_debug(" clock_curr=mclk=%d\n", ov3640_data.mclk); + p->if_type = V4L2_IF_TYPE_BT656; + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.clock_min = OV3640_XCLK_MIN; + p->u.bt656.clock_max = OV3640_XCLK_MAX; + p->u.bt656.bt_sync_correct = 1; /* Indicate external vsync */ + + return 0; +} + +/*! + * ioctl_s_power - V4L2 sensor interface handler for VIDIOC_S_POWER ioctl + * @s: pointer to standard V4L2 device structure + * @on: indicates power mode (on or off) + * + * Turns the power on or off, depending on the value of on and returns the + * appropriate error code. + */ +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + CAMERA_TRACE(("In ov3640:ioctl_s_power\n")); + + if (on && !sensor->on) { + gpio_sensor_active(ov3640_data.csi); + if (io_regulator) + if (regulator_enable(io_regulator) != 0) + return -EIO; + if (core_regulator) + if (regulator_enable(core_regulator) != 0) + return -EIO; + if (gpo_regulator) + if (regulator_enable(gpo_regulator) != 0) + return -EIO; + if (analog_regulator) + if (regulator_enable(analog_regulator) != 0) + return -EIO; + } else if (!on && sensor->on) { + if (analog_regulator) + regulator_disable(analog_regulator); + if (core_regulator) + regulator_disable(core_regulator); + if (io_regulator) + regulator_disable(io_regulator); + if (gpo_regulator) + regulator_disable(gpo_regulator); + gpio_sensor_inactive(ov3640_data.csi); + } + + sensor->on = on; + + return 0; +} + +/*! + * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the sensor's video CAPTURE parameters. + */ +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_captureparm *cparm = &a->parm.capture; + int ret = 0; + + CAMERA_TRACE(("In ov3640:ioctl_g_parm\n")); + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + CAMERA_TRACE((" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n")); + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = sensor->streamcap.capability; + cparm->timeperframe = sensor->streamcap.timeperframe; + cparm->capturemode = sensor->streamcap.capturemode; + ret = 0; + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + CAMERA_TRACE((" type is not " \ + "V4L2_BUF_TYPE_VIDEO_CAPTURE but %d\n", + a->type)); + ret = -EINVAL; + break; + + default: + pr_debug(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the sensor to use the input parameters, if possible. If + * not possible, reverts to the old parameters and returns the + * appropriate error code. + */ +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_fract *timeperframe = &a->parm.capture.timeperframe; + u32 tgt_fps; /* target frames per secound */ + enum ov3640_frame_rate frame_rate; + int ret = 0; + + CAMERA_TRACE(("In ov3640:ioctl_s_parm\n")); + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + CAMERA_TRACE((" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n")); + + /* Check that the new frame rate is allowed. */ + if ((timeperframe->numerator == 0) || + (timeperframe->denominator == 0)) { + timeperframe->denominator = DEFAULT_FPS; + timeperframe->numerator = 1; + } + + tgt_fps = timeperframe->denominator / + timeperframe->numerator; + + if (tgt_fps > MAX_FPS) { + timeperframe->denominator = MAX_FPS; + timeperframe->numerator = 1; + } else if (tgt_fps < MIN_FPS) { + timeperframe->denominator = MIN_FPS; + timeperframe->numerator = 1; + } + + /* Actual frame rate we use */ + tgt_fps = timeperframe->denominator / + timeperframe->numerator; + + if (tgt_fps == 15) + frame_rate = ov3640_15_fps; + else if (tgt_fps == 30) + frame_rate = ov3640_30_fps; + else { + pr_err(" The camera frame rate is not supported!\n"); + return -EINVAL; + } + + sensor->streamcap.timeperframe = *timeperframe; + sensor->streamcap.capturemode = + (u32)a->parm.capture.capturemode; + + ret = ov3640_init_mode(frame_rate, + sensor->streamcap.capturemode); + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_debug(" type is not " \ + "V4L2_BUF_TYPE_VIDEO_CAPTURE but %d\n", + a->type); + ret = -EINVAL; + break; + + default: + pr_debug(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the sensor's current pixel format in the v4l2_format + * parameter. + */ +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + + CAMERA_TRACE(("In ov3640:ioctl_g_fmt_cap.\n")); + + f->fmt.pix = sensor->pix; + + return 0; +} + +/*! + * ioctl_g_ctrl - V4L2 sensor interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_G_CTRL ioctl structure + * + * If the requested control is supported, returns the control's current + * value from the video_control[] array. Otherwise, returns -EINVAL + * if the control is not supported. + */ +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int ret = 0; + + CAMERA_TRACE(("In ov3640:ioctl_g_ctrl\n")); + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + vc->value = ov3640_data.brightness; + break; + case V4L2_CID_HUE: + vc->value = ov3640_data.hue; + break; + case V4L2_CID_CONTRAST: + vc->value = ov3640_data.contrast; + break; + case V4L2_CID_SATURATION: + vc->value = ov3640_data.saturation; + break; + case V4L2_CID_RED_BALANCE: + vc->value = ov3640_data.red; + break; + case V4L2_CID_BLUE_BALANCE: + vc->value = ov3640_data.blue; + break; + case V4L2_CID_EXPOSURE: + vc->value = ov3640_data.ae_mode; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/*! + * ioctl_s_ctrl - V4L2 sensor interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_S_CTRL ioctl structure + * + * If the requested control is supported, sets the control's current + * value in HW (and updates the video_control[] array). Otherwise, + * returns -EINVAL if the control is not supported. + */ +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + + pr_debug("In ov3640:ioctl_s_ctrl %d\n", + vc->id); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + CAMERA_TRACE((" V4L2_CID_BRIGHTNESS\n")); + break; + case V4L2_CID_CONTRAST: + CAMERA_TRACE((" V4L2_CID_CONTRAST\n")); + break; + case V4L2_CID_SATURATION: + CAMERA_TRACE((" V4L2_CID_SATURATION\n")); + break; + case V4L2_CID_HUE: + CAMERA_TRACE((" V4L2_CID_HUE\n")); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + CAMERA_TRACE((" V4L2_CID_AUTO_WHITE_BALANCE\n")); + break; + case V4L2_CID_DO_WHITE_BALANCE: + CAMERA_TRACE((" V4L2_CID_DO_WHITE_BALANCE\n")); + break; + case V4L2_CID_RED_BALANCE: + CAMERA_TRACE((" V4L2_CID_RED_BALANCE\n")); + break; + case V4L2_CID_BLUE_BALANCE: + CAMERA_TRACE((" V4L2_CID_BLUE_BALANCE\n")); + break; + case V4L2_CID_GAMMA: + CAMERA_TRACE((" V4L2_CID_GAMMA\n")); + break; + case V4L2_CID_EXPOSURE: + CAMERA_TRACE((" V4L2_CID_EXPOSURE\n")); + break; + case V4L2_CID_AUTOGAIN: + CAMERA_TRACE((" V4L2_CID_AUTOGAIN\n")); + break; + case V4L2_CID_GAIN: + CAMERA_TRACE((" V4L2_CID_GAIN\n")); + break; + case V4L2_CID_HFLIP: + CAMERA_TRACE((" V4L2_CID_HFLIP\n")); + break; + case V4L2_CID_VFLIP: + CAMERA_TRACE((" V4L2_CID_VFLIP\n")); + break; + default: + CAMERA_TRACE((" Default case\n")); + retval = -EPERM; + break; + } + + return retval; +} + +/*! + * ioctl_init - V4L2 sensor interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + CAMERA_TRACE(("In ov3640:ioctl_init\n")); + + return 0; +} + +/*! + * ioctl_dev_init - V4L2 sensor interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + struct sensor *sensor = s->priv; + u32 tgt_xclk; /* target xclk */ + u32 tgt_fps; /* target frames per secound */ + enum ov3640_frame_rate frame_rate; + + CAMERA_TRACE(("In ov3640:ioctl_dev_init\n")); + + gpio_sensor_active(ov3640_data.csi); + ov3640_data.on = true; + + /* mclk */ + tgt_xclk = ov3640_data.mclk; + tgt_xclk = min(tgt_xclk, (u32)OV3640_XCLK_MAX); + tgt_xclk = max(tgt_xclk, (u32)OV3640_XCLK_MIN); + ov3640_data.mclk = tgt_xclk; + + pr_debug(" Setting mclk to %d MHz\n", tgt_xclk / 1000000); + set_mclk_rate(&ov3640_data.mclk, ov3640_data.csi); + + /* Default camera frame rate is set in probe */ + tgt_fps = sensor->streamcap.timeperframe.denominator / + sensor->streamcap.timeperframe.numerator; + + if (tgt_fps == 15) + frame_rate = ov3640_15_fps; + else if (tgt_fps == 30) + frame_rate = ov3640_30_fps; + else + return -EINVAL; /* Only support 15fps or 30fps now. */ + + return ov3640_init_mode(frame_rate, + sensor->streamcap.capturemode); +} + +/*! + * This structure defines all the ioctls for this module and links them to the + * enumeration. + */ +static struct v4l2_int_ioctl_desc ov3640_ioctl_desc[] = { + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func *)ioctl_dev_init}, +/* {vidioc_int_dev_exit_num, (v4l2_int_ioctl_func *)ioctl_dev_exit}, */ + {vidioc_int_s_power_num, (v4l2_int_ioctl_func *)ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func *)ioctl_g_ifparm}, +/* {vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *)ioctl_g_needs_reset}, */ +/* {vidioc_int_reset_num, (v4l2_int_ioctl_func *)ioctl_reset}, */ + {vidioc_int_init_num, (v4l2_int_ioctl_func *)ioctl_init}, +/* {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap}, */ +/* {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_try_fmt_cap}, */ + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_g_fmt_cap}, +/* {vidioc_int_s_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_s_fmt_cap}, */ + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func *)ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func *)ioctl_s_parm}, +/* {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func *)ioctl_queryctrl}, */ + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func *)ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func *)ioctl_s_ctrl}, +}; + +static struct v4l2_int_slave ov3640_slave = { + .ioctls = ov3640_ioctl_desc, + .num_ioctls = ARRAY_SIZE(ov3640_ioctl_desc), +}; + +static struct v4l2_int_device ov3640_int_device = { + .module = THIS_MODULE, + .name = "ov3640", + .type = v4l2_int_type_slave, + .u = { + .slave = &ov3640_slave, + }, +}; + +/*! + * ov3640 I2C probe function + * + * @param adapter struct i2c_adapter * + * @return Error code indicating success or failure + */ +static int ov3640_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + struct mxc_camera_platform_data *plat_data = client->dev.platform_data; + + CAMERA_TRACE(("CAMERA_DBG Entry: ov3640_probe\n")); + + /* Set initial values for the sensor struct. */ + memset(&ov3640_data, 0, sizeof(ov3640_data)); + ov3640_data.mclk = 24000000; /* 6 - 54 MHz, typical 24MHz */ + ov3640_data.mclk = plat_data->mclk; + ov3640_data.csi = plat_data->csi; + + ov3640_data.i2c_client = client; + ov3640_data.pix.pixelformat = V4L2_PIX_FMT_UYVY; + ov3640_data.pix.width = 640; + ov3640_data.pix.height = 480; + ov3640_data.streamcap.capability = V4L2_MODE_HIGHQUALITY | + V4L2_CAP_TIMEPERFRAME; + ov3640_data.streamcap.capturemode = 0; + ov3640_data.streamcap.timeperframe.denominator = DEFAULT_FPS; + ov3640_data.streamcap.timeperframe.numerator = 1; + + if (plat_data->io_regulator) { + io_regulator = regulator_get(&client->dev, + plat_data->io_regulator); + if (!IS_ERR(io_regulator)) { + regulator_set_voltage(io_regulator, + OV3640_VOLTAGE_DIGITAL_IO, + OV3640_VOLTAGE_DIGITAL_IO); + if (regulator_enable(io_regulator) != 0) { + pr_err("%s:io set voltage error\n", __func__); + goto err1; + } else { + dev_dbg(&client->dev, + "%s:io set voltage ok\n", __func__); + } + } else + io_regulator = NULL; + } + + if (plat_data->core_regulator) { + core_regulator = regulator_get(&client->dev, + plat_data->core_regulator); + if (!IS_ERR(core_regulator)) { + regulator_set_voltage(core_regulator, + OV3640_VOLTAGE_DIGITAL_CORE, + OV3640_VOLTAGE_DIGITAL_CORE); + if (regulator_enable(core_regulator) != 0) { + pr_err("%s:core set voltage error\n", __func__); + goto err2; + } else { + dev_dbg(&client->dev, + "%s:core set voltage ok\n", __func__); + } + } else + core_regulator = NULL; + } + + if (plat_data->analog_regulator) { + analog_regulator = regulator_get(&client->dev, + plat_data->analog_regulator); + if (!IS_ERR(analog_regulator)) { + regulator_set_voltage(analog_regulator, + OV3640_VOLTAGE_ANALOG, + OV3640_VOLTAGE_ANALOG); + if (regulator_enable(analog_regulator) != 0) { + pr_err("%s:analog set voltage error\n", + __func__); + goto err3; + } else { + dev_dbg(&client->dev, + "%s:analog set voltage ok\n", __func__); + } + } else + analog_regulator = NULL; + } + + if (plat_data->gpo_regulator) { + gpo_regulator = regulator_get(&client->dev, + plat_data->gpo_regulator); + if (!IS_ERR(gpo_regulator)) { + if (regulator_enable(gpo_regulator) != 0) { + pr_err("%s:gpo3 enable error\n", __func__); + goto err4; + } else { + dev_dbg(&client->dev, + "%s:gpo3 enable ok\n", __func__); + } + } else + gpo_regulator = NULL; + } + + ov3640_int_device.priv = &ov3640_data; + retval = v4l2_int_device_register(&ov3640_int_device); + + return retval; + +err4: + if (analog_regulator) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } +err3: + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } +err2: + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } +err1: + return -1; +} + +/*! + * ov3640 I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int ov3640_remove(struct i2c_client *client) +{ + CAMERA_TRACE(("In ov3640_remove\n")); + + v4l2_int_device_unregister(&ov3640_int_device); + + if (gpo_regulator) { + regulator_disable(gpo_regulator); + regulator_put(gpo_regulator); + } + + if (analog_regulator) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } + + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } + + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } + + return 0; +} + +/*! + * ov3640 init function + * Called by insmod ov3640_camera.ko. + * + * @return Error code indicating success or failure + */ +static __init int ov3640_init(void) +{ + u8 err; + + CAMERA_TRACE(("CAMERA_DBG Entry: ov3640_init\n")); + + err = i2c_add_driver(&ov3640_i2c_driver); + if (err != 0) + pr_err("%s:driver registration failed, error=%d \n", + __func__, err); + + CAMERA_TRACE(("CAMERA_DBG Exit: ov3640_init\n")); + + return err; +} + +/*! + * OV3640 cleanup function + * Called on rmmod ov3640_camera.ko + * + * @return Error code indicating success or failure + */ +static void __exit ov3640_clean(void) +{ + CAMERA_TRACE(("CAMERA_DBG Entry: ov3640_clean\n")); + + i2c_del_driver(&ov3640_i2c_driver); + gpio_sensor_inactive(ov3640_data.csi); + + CAMERA_TRACE(("CAMERA_DBG Exit: ov3640_clean\n")); +} + +module_init(ov3640_init); +module_exit(ov3640_clean); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("OV3640 Camera Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("CSI"); diff --git a/drivers/media/video/mxc/capture/sensor_clock.c b/drivers/media/video/mxc/capture/sensor_clock.c new file mode 100644 index 000000000000..c15cf27c57cd --- /dev/null +++ b/drivers/media/video/mxc/capture/sensor_clock.c @@ -0,0 +1,87 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file sensor_clock.c + * + * @brief camera clock function + * + * @ingroup Camera + */ +#include <linux/init.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/clk.h> + +#if defined(CONFIG_MXC_IPU_V1) || defined(CONFIG_VIDEO_MXC_EMMA_CAMERA) \ + || defined(CONFIG_VIDEO_MXC_CSI_CAMERA_MODULE) \ + || defined(CONFIG_VIDEO_MXC_CSI_CAMERA) +/* + * set_mclk_rate + * + * @param p_mclk_freq mclk frequence + * + */ +void set_mclk_rate(uint32_t * p_mclk_freq) +{ + struct clk *clk; + uint32_t freq = 0; + + clk = clk_get(NULL, "csi_clk"); + + freq = clk_round_rate(clk, *p_mclk_freq); + clk_set_rate(clk, freq); + + *p_mclk_freq = freq; + + clk_put(clk); + pr_debug("mclk frequency = %d\n", *p_mclk_freq); +} +#else +/* + * set_mclk_rate + * + * @param p_mclk_freq mclk frequence + * @param csi csi 0 or csi 1 + * + */ +void set_mclk_rate(uint32_t *p_mclk_freq, uint32_t csi) +{ + struct clk *clk; + uint32_t freq = 0; + char *mclk; + + if (csi == 0) { + mclk = "csi_mclk1"; + } else if (csi == 1) { + mclk = "csi_mclk2"; + } else { + pr_debug("invalid csi num %d\n", csi); + return; + } + + clk = clk_get(NULL, mclk); + + freq = clk_round_rate(clk, *p_mclk_freq); + clk_set_rate(clk, freq); + + *p_mclk_freq = freq; + + clk_put(clk); + pr_debug("%s frequency = %d\n", mclk, *p_mclk_freq); +} +#endif + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(set_mclk_rate); diff --git a/drivers/media/video/mxc/opl/Makefile b/drivers/media/video/mxc/opl/Makefile new file mode 100644 index 000000000000..092a62c5ac4a --- /dev/null +++ b/drivers/media/video/mxc/opl/Makefile @@ -0,0 +1,5 @@ +opl-objs := opl_mod.o rotate90_u16.o rotate270_u16.o \ + rotate90_u16_qcif.o rotate270_u16_qcif.o \ + vmirror_u16.o hmirror_rotate180_u16.o + +obj-$(CONFIG_VIDEO_MXC_OPL) += opl.o diff --git a/drivers/media/video/mxc/opl/hmirror_rotate180_u16.c b/drivers/media/video/mxc/opl/hmirror_rotate180_u16.c new file mode 100644 index 000000000000..3119a128c1f2 --- /dev/null +++ b/drivers/media/video/mxc/opl/hmirror_rotate180_u16.c @@ -0,0 +1,259 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include "opl.h" + +static inline u32 rot_left_u16(u16 x, unsigned int n) +{ + return (x << n) | (x >> (16 - n)); +} + +static inline u32 rot_left_u32(u32 x, unsigned int n) +{ + return (x << n) | (x >> (32 - n)); +} + +static inline u32 byte_swap_u32(u32 x) +{ + u32 t1, t2, t3; + + t1 = x ^ ((x << 16) | x >> 16); + t2 = t1 & 0xff00ffff; + t3 = (x >> 8) | (x << 24); + return t3 ^ (t2 >> 8); +} + +static int opl_hmirror_u16_by1(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_hmirror_u16_by2(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_hmirror_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_hmirror_u16_by8(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); + +int opl_hmirror_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride) +{ + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + if (width % 8 == 0) + return opl_hmirror_u16_by8(src, src_line_stride, width, height, + dst, dst_line_stride, 0); + else if (width % 4 == 0) + return opl_hmirror_u16_by4(src, src_line_stride, width, height, + dst, dst_line_stride, 0); + else if (width % 2 == 0) + return opl_hmirror_u16_by2(src, src_line_stride, width, height, + dst, dst_line_stride, 0); + else /* (width % 1) */ + return opl_hmirror_u16_by1(src, src_line_stride, width, height, + dst, dst_line_stride, 0); +} + +int opl_rotate180_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride) +{ + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + if (width % 8 == 0) + return opl_hmirror_u16_by8(src, src_line_stride, width, height, + dst, dst_line_stride, 1); + else if (width % 4 == 0) + return opl_hmirror_u16_by4(src, src_line_stride, width, height, + dst, dst_line_stride, 1); + else if (width % 2 == 0) + return opl_hmirror_u16_by2(src, src_line_stride, width, height, + dst, dst_line_stride, 1); + else /* (width % 1) */ + return opl_hmirror_u16_by1(src, src_line_stride, width, height, + dst, dst_line_stride, 1); +} + +static int opl_hmirror_u16_by1(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + u16 pixel; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 1) * BYTES_PER_PIXEL + - (BYTES_PER_PIXEL - BYTES_PER_PIXEL); + for (j = 0; j < width; j++) { + pixel = *(u16 *) psrc; + *(u16 *) pdst = pixel; + psrc += BYTES_PER_PIXEL; + pdst -= BYTES_PER_PIXEL; + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +static int opl_hmirror_u16_by2(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + u32 pixelsin, pixelsout; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 2) * BYTES_PER_PIXEL; + for (j = 0; j < (width >> 1); j++) { + pixelsin = *(u32 *) psrc; + pixelsout = rot_left_u32(pixelsin, 16); + *(u32 *) pdst = pixelsout; + psrc += BYTES_PER_2PIXEL; + pdst -= BYTES_PER_2PIXEL; + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +static int opl_hmirror_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + + union doubleword { + u64 dw; + u32 w[2]; + }; + + union doubleword inbuf; + union doubleword outbuf; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 4) * BYTES_PER_PIXEL; + for (j = 0; j < (width >> 2); j++) { + inbuf.dw = *(u64 *) psrc; + outbuf.w[0] = rot_left_u32(inbuf.w[1], 16); + outbuf.w[1] = rot_left_u32(inbuf.w[0], 16); + *(u64 *) pdst = outbuf.dw; + psrc += BYTES_PER_4PIXEL; + pdst -= BYTES_PER_4PIXEL; + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + return OPLERR_SUCCESS; +} + +static int opl_hmirror_u16_by8(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 1) * BYTES_PER_PIXEL - 2; + for (j = (width >> 3); j > 0; j--) { + __asm__ volatile ( + "ldmia %0!,{r2-r5}\n\t" + "mov r6, r2\n\t" + "mov r7, r3\n\t" + "mov r2, r5, ROR #16\n\t" + "mov r3, r4, ROR #16\n\t" + "mov r4, r7, ROR #16\n\t" + "mov r5, r6, ROR #16\n\t" + "stmda %1!,{r2-r5}\n\t" + + :"+r"(psrc), "+r"(pdst) + :"0"(psrc), "1"(pdst) + :"r2", "r3", "r4", "r5", "r6", "r7", + "memory" + ); + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_hmirror_u16); +EXPORT_SYMBOL(opl_rotate180_u16); diff --git a/drivers/media/video/mxc/opl/opl.h b/drivers/media/video/mxc/opl/opl.h new file mode 100644 index 000000000000..24644c8e78fa --- /dev/null +++ b/drivers/media/video/mxc/opl/opl.h @@ -0,0 +1,162 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup OPLIP OPL Image Processing + */ +/*! + * @file opl.h + * + * @brief The OPL (Open Primitives Library) Image Processing library defines + * efficient functions for rotation and mirroring. + * + * It includes ARM9-optimized rotation and mirroring functions. It is derived + * from the original OPL project which is found at sourceforge.freescale.net. + * + * @ingroup OPLIP + */ +#ifndef __OPL_H__ +#define __OPL_H__ + +#include <linux/types.h> + +#define BYTES_PER_PIXEL 2 +#define CACHE_LINE_WORDS 8 +#define BYTES_PER_WORD 4 + +#define BYTES_PER_2PIXEL (BYTES_PER_PIXEL * 2) +#define BYTES_PER_4PIXEL (BYTES_PER_PIXEL * 4) +#define BYTES_PER_8PIXEL (BYTES_PER_PIXEL * 8) + +#define QCIF_Y_WIDTH 176 +#define QCIF_Y_HEIGHT 144 + +/*! Enumerations of opl error code */ +enum opl_error { + OPLERR_SUCCESS = 0, + OPLERR_NULL_PTR, + OPLERR_BAD_ARG, + OPLERR_DIV_BY_ZERO, + OPLERR_OVER_FLOW, + OPLERR_UNDER_FLOW, + OPLERR_MISALIGNED, +}; + +/*! + * @brief Rotate a 16bbp buffer 90 degrees clockwise. + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate90_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 180 degrees clockwise. + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate180_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 270 degrees clockwise + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate270_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride); + +/*! + * @brief Mirror a 16bpp buffer horizontally + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_hmirror_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride); + +/*! + * @brief Mirror a 16bpp buffer vertically + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_vmirror_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 90 degrees clockwise and mirror vertically + * It is equivalent to rotate 270 degree and mirror horizontally + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate90_vmirror_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 270 degrees clockwise and mirror vertically + * It is equivalent to rotate 90 degree and mirror horizontally + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate270_vmirror_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride); + +#endif /* __OPL_H__ */ diff --git a/drivers/media/video/mxc/opl/opl_mod.c b/drivers/media/video/mxc/opl/opl_mod.c new file mode 100644 index 000000000000..a581aadda252 --- /dev/null +++ b/drivers/media/video/mxc/opl/opl_mod.c @@ -0,0 +1,30 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> + +static __init int opl_init(void) +{ + return 0; +} + +static void __exit opl_exit(void) +{ +} + +module_init(opl_init); +module_exit(opl_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("OPL Software Rotation/Mirroring"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/opl/rotate270_u16.c b/drivers/media/video/mxc/opl/rotate270_u16.c new file mode 100644 index 000000000000..add87f1a9e44 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate270_u16.c @@ -0,0 +1,285 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include "opl.h" + +static int opl_rotate270_u16_by16(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror); +static int opl_rotate270_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_rotate270_vmirror_u16_both(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror); +int opl_rotate270_u16_qcif(const u8 * src, u8 * dst); + +int opl_rotate270_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride) +{ + return opl_rotate270_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 0); +} + +int opl_rotate270_vmirror_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride) +{ + return opl_rotate270_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 1); +} + +static int opl_rotate270_vmirror_u16_both(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int BLOCK_SIZE_PIXELS_BY4 = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + /* The QCIF algorithm doesn't support vertical mirroring */ + if (vmirror == 0 && width == QCIF_Y_WIDTH && height == QCIF_Y_HEIGHT + && src_line_stride == QCIF_Y_WIDTH * 2 + && src_line_stride == QCIF_Y_HEIGHT * 2) + return opl_rotate270_u16_qcif(src, dst); + else if (width % BLOCK_SIZE_PIXELS == 0 + && height % BLOCK_SIZE_PIXELS == 0) + return opl_rotate270_u16_by16(src, src_line_stride, width, + height, dst, dst_line_stride, + vmirror); + else if (width % BLOCK_SIZE_PIXELS_BY4 == 0 + && height % BLOCK_SIZE_PIXELS_BY4 == 0) + return opl_rotate270_u16_by4(src, src_line_stride, width, + height, dst, dst_line_stride, + vmirror); + else + return OPLERR_BAD_ARG; +} + +/* + * Rotate Counter Clockwise, divide RGB component into 16 row strips, read + * non sequentially and write sequentially. This is done in 16 line strips + * so that the cache is used better. Cachelines are 8 words = 32 bytes. Pixels + * are 2 bytes. The 16 reads will be cache misses, but the next 240 should + * be from cache. The writes to the output buffer will be sequential for 16 + * writes. + * + * Example: + * Input data matrix: output matrix + * + * 0 | 1 | 2 | 3 | 4 | 4 | 0 | 0 | 3 | + * 4 | 3 | 2 | 1 | 0 | 3 | 1 | 9 | 6 | + * 6 | 7 | 8 | 9 | 0 | 2 | 2 | 8 | 2 | + * 5 | 3 | 2 | 6 | 3 | 1 | 3 | 7 | 3 | + * ^ 0 | 4 | 6 | 5 | < Write the input data sequentially + * Read first column + * Start at the bottom + * Move to next column and repeat + * + * Loop over k decreasing (blocks) + * in_block_ptr = src + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_PIXELS) * (RGB_WIDTH_BYTES) + * out_block_ptr = dst + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_BYTES) + (RGB_WIDTH_PIXELS - 1) + * * RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + * + * Loop over i decreasing (width) + * Each pix: + * in_block_ptr += RGB_WIDTH_BYTES + * out_block_ptr += 4 + * + * Each row of block: + * in_block_ptr -= RGB_WIDTH_BYTES * BLOCK_SIZE_PIXELS - 2 + * out_block_ptr -= RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + 2 * BLOCK_SIZE_PIXELS; + * + * It may perform vertical mirroring too depending on the vmirror flag. + */ +static int opl_rotate270_u16_by16(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + - BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS + : dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS) * src_line_stride; + out_block_ptr = dst + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS * BYTES_PER_PIXEL) + + (width - 1) * dst_line_stride; + + /* + * For vertical mirroring the writing starts from the + * first line + */ + if (vmirror) + out_block_ptr -= dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + :"+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + :"0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + :"r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr -= IN_INDEX; + out_block_ptr -= OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +/* + * Rotate Counter Clockwise, divide RGB component into 4 row strips, read + * non sequentially and write sequentially. This is done in 4 line strips + * so that the cache is used better. Cachelines are 8 words = 32 bytes. Pixels + * are 2 bytes. The 4 reads will be cache misses, but the next 60 should + * be from cache. The writes to the output buffer will be sequential for 4 + * writes. + * + * Example: + * Input data matrix: output matrix + * + * 0 | 1 | 2 | 3 | 4 | 4 | 0 | 0 | 3 | + * 4 | 3 | 2 | 1 | 0 | 3 | 1 | 9 | 6 | + * 6 | 7 | 8 | 9 | 0 | 2 | 2 | 8 | 2 | + * 5 | 3 | 2 | 6 | 3 | 1 | 3 | 7 | 3 | + * ^ 0 | 4 | 6 | 5 | < Write the input data sequentially + * Read first column + * Start at the bottom + * Move to next column and repeat + * + * Loop over k decreasing (blocks) + * in_block_ptr = src + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_PIXELS) * (RGB_WIDTH_BYTES) + * out_block_ptr = dst + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_BYTES) + (RGB_WIDTH_PIXELS - 1) + * * RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + * + * Loop over i decreasing (width) + * Each pix: + * in_block_ptr += RGB_WIDTH_BYTES + * out_block_ptr += 4 + * + * Each row of block: + * in_block_ptr -= RGB_WIDTH_BYTES * BLOCK_SIZE_PIXELS - 2 + * out_block_ptr -= RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + 2 * BLOCK_SIZE_PIXELS; + * + * It may perform vertical mirroring too depending on the vmirror flag. + */ +static int opl_rotate270_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + - BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS + : dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS) * src_line_stride; + out_block_ptr = dst + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS * BYTES_PER_PIXEL) + + (width - 1) * dst_line_stride; + + /* + * For vertical mirroring the writing starts from the + * first line + */ + if (vmirror) + out_block_ptr -= dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + :"+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + :"0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + :"r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr -= IN_INDEX; + out_block_ptr -= OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_rotate270_u16); +EXPORT_SYMBOL(opl_rotate270_vmirror_u16); diff --git a/drivers/media/video/mxc/opl/rotate270_u16_qcif.S b/drivers/media/video/mxc/opl/rotate270_u16_qcif.S new file mode 100644 index 000000000000..4101eaac4554 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate270_u16_qcif.S @@ -0,0 +1,70 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/linkage.h> + + .text + .align 2 +ENTRY(opl_rotate270_u16_qcif) + STMFD sp!,{r4-r10} + MOV r12,#0x160 + MOV r10,#0x90 + MOV r3,r10,LSR #4 +.L1.16: + RSB r2,r3,r10,LSR #4 + MOV r5,r2,LSL #5 + MOV r4,r12,LSR #1 + SMULBB r4,r5,r4 + ADD r2,r1,r2,LSL #5 + ADD r5,r2,#0xc000 + ADD r5,r5,#0x4e0 + MOV r2,r12,LSR #1 + ADD r4,r0,r4 +.L1.52: + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + SUBS r2,r2,#1 + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + SUB r4,r4,#0x1500 + STMIA r5,{r6,r7} + SUB r5,r5,#0x138 + SUB r4,r4,#0xfe + BGT .L1.52 + SUBS r3,r3,#1 + BGT .L1.16 + LDMFD sp!,{r4-r10} + BX lr + .size opl_rotate270_u16_qcif, . - opl_rotate270_u16_qcif diff --git a/drivers/media/video/mxc/opl/rotate90_u16.c b/drivers/media/video/mxc/opl/rotate90_u16.c new file mode 100644 index 000000000000..dd7d445aa952 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate90_u16.c @@ -0,0 +1,220 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include "opl.h" + +static int opl_rotate90_u16_by16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_rotate90_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_rotate90_vmirror_u16_both(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror); +int opl_rotate90_u16_qcif(const u8 * src, u8 * dst); + +int opl_rotate90_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride) +{ + return opl_rotate90_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 0); +} + +int opl_rotate90_vmirror_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride) +{ + return opl_rotate90_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 1); +} + +static int opl_rotate90_vmirror_u16_both(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int BLOCK_SIZE_PIXELS_BY4 = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + /* The QCIF algorithm doesn't support vertical mirroring */ + if (vmirror == 0 && width == QCIF_Y_WIDTH && height == QCIF_Y_HEIGHT + && src_line_stride == QCIF_Y_WIDTH * 2 + && src_line_stride == QCIF_Y_HEIGHT * 2) + return opl_rotate90_u16_qcif(src, dst); + else if (width % BLOCK_SIZE_PIXELS == 0 + && height % BLOCK_SIZE_PIXELS == 0) + return opl_rotate90_u16_by16(src, src_line_stride, width, + height, dst, dst_line_stride, + vmirror); + else if (width % BLOCK_SIZE_PIXELS_BY4 == 0 + && height % BLOCK_SIZE_PIXELS_BY4 == 0) + return opl_rotate90_u16_by4(src, src_line_stride, width, height, + dst, dst_line_stride, vmirror); + else + return OPLERR_BAD_ARG; +} + +/* + * Performs clockwise rotation (and possibly vertical mirroring depending + * on the vmirror flag) using block sizes of 16x16 + * The algorithm is similar to 270 degree clockwise rotation algorithm + */ +static int opl_rotate90_u16_by16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int BLOCK_SIZE_BYTES = BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + + BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride - BLOCK_SIZE_BYTES + : dst_line_stride - BLOCK_SIZE_BYTES; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + src_line_stride * (height - 1) + - (src_line_stride * BLOCK_SIZE_PIXELS * + (height / BLOCK_SIZE_PIXELS - k)); + out_block_ptr = dst + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS * + ((height / BLOCK_SIZE_PIXELS) - k); + + /* + * For vertical mirroring the writing starts from the + * bottom line + */ + if (vmirror) + out_block_ptr += dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + :"+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + :"0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + :"r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr += IN_INDEX; + out_block_ptr += OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +/* + * Performs clockwise rotation (and possibly vertical mirroring depending + * on the vmirror flag) using block sizes of 4x4 + * The algorithm is similar to 270 degree clockwise rotation algorithm + */ +static int opl_rotate90_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + const int BLOCK_SIZE_BYTES = BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + + BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride - BLOCK_SIZE_BYTES + : dst_line_stride - BLOCK_SIZE_BYTES; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + src_line_stride * (height - 1) + - (src_line_stride * BLOCK_SIZE_PIXELS * + (height / BLOCK_SIZE_PIXELS - k)); + out_block_ptr = dst + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS + * ((height / BLOCK_SIZE_PIXELS) - k); + + /* + * For horizontal mirroring the writing starts from the + * bottom line + */ + if (vmirror) + out_block_ptr += dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + :"+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + :"0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + :"r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr += IN_INDEX; + out_block_ptr += OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_rotate90_u16); +EXPORT_SYMBOL(opl_rotate90_vmirror_u16); diff --git a/drivers/media/video/mxc/opl/rotate90_u16_qcif.S b/drivers/media/video/mxc/opl/rotate90_u16_qcif.S new file mode 100644 index 000000000000..8568a9e629e5 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate90_u16_qcif.S @@ -0,0 +1,71 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/linkage.h> + + .text + .align 2 +ENTRY(opl_rotate90_u16_qcif) + STMFD sp!,{r4-r10} + MOV r12,#0x160 + MOV r10,#0x90 + MOV r3,r10,LSR #4 +.L1.216: + RSB r2,r3,r10,LSR #4 + MOV r4,#0x20 + SMULBB r5,r4,r2 + MOV r4,#0x1600 + SMULBB r2,r4,r2 + ADD r4,r0,#0xc000 + ADD r4,r4,#0x4a0 + SUB r4,r4,r2 + MOV r2,r12,LSR #1 + ADD r5,r1,r5 +.L1.256: + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + SUBS r2,r2,#1 + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + ADD r4,r4,#0x1600 + STMIA r5!,{r6,r7} + ADD r5,r5,#0x100 + ADD r4,r4,#2 + BGT .L1.256 + SUBS r3,r3,#1 + BGT .L1.216 + LDMFD sp!,{r4-r10} + BX lr + .size opl_rotate90_u16_qcif, . - opl_rotate90_u16_qcif diff --git a/drivers/media/video/mxc/opl/vmirror_u16.c b/drivers/media/video/mxc/opl/vmirror_u16.c new file mode 100644 index 000000000000..57f805c08a81 --- /dev/null +++ b/drivers/media/video/mxc/opl/vmirror_u16.c @@ -0,0 +1,46 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/string.h> +#include "opl.h" + +int opl_vmirror_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride) +{ + const u8 *src_row_addr; + u8 *dst_row_addr; + int i; + + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + src_row_addr = src; + dst_row_addr = dst + (height - 1) * dst_line_stride; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* memcpy each row */ + memcpy(dst_row_addr, src_row_addr, BYTES_PER_PIXEL * width); + src_row_addr += src_line_stride; + dst_row_addr -= dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_vmirror_u16); diff --git a/drivers/media/video/mxc/output/Kconfig b/drivers/media/video/mxc/output/Kconfig new file mode 100644 index 000000000000..2153ad248907 --- /dev/null +++ b/drivers/media/video/mxc/output/Kconfig @@ -0,0 +1,28 @@ +config VIDEO_MXC_IPU_OUTPUT + bool "IPU v4l2 support" + depends on VIDEO_MXC_OUTPUT && MXC_IPU + default y + ---help--- + This is the video4linux2 driver for IPU post processing video output. + +config VIDEO_MXC_IPUV1_WVGA_OUTPUT + bool "IPUv1 WVGA v4l2 display support" + depends on VIDEO_MXC_OUTPUT && MXC_IPU + default n + ---help--- + This is the video4linux2 driver for IPUv1 WVGA post processing video output. + +config VIDEO_MXC_EMMA_OUTPUT + bool + depends on VIDEO_MXC_OUTPUT && MXC_EMMA && FB_MXC_SYNC_PANEL + default y + ---help--- + This is the video4linux2 driver for EMMA post processing video output. + +config VIDEO_MXC_OUTPUT_FBSYNC + bool "Synchronize the output with LCDC refresh" + depends on VIDEO_MXC_EMMA_OUTPUT + default y + ---help--- + Synchronize the post-processing with LCDC EOF (End of Frame) to + prevent tearing issue. If unsure, say Y. diff --git a/drivers/media/video/mxc/output/Makefile b/drivers/media/video/mxc/output/Makefile new file mode 100644 index 000000000000..1713fa3bf3ab --- /dev/null +++ b/drivers/media/video/mxc/output/Makefile @@ -0,0 +1,11 @@ +ifeq ($(CONFIG_VIDEO_MXC_EMMA_OUTPUT),y) + mx27_output-objs := mx27_v4l2_output.o mx27_pp.o + obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mx27_output.o +endif + +ifeq ($(CONFIG_VIDEO_MXC_IPU_OUTPUT),y) + obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mxc_v4l2_output.o +endif +ifeq ($(CONFIG_VIDEO_MXC_IPUV1_WVGA_OUTPUT),y) + obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mx31_v4l2_wvga_output.o +endif diff --git a/drivers/media/video/mxc/output/mx27_pp.c b/drivers/media/video/mxc/output/mx27_pp.c new file mode 100644 index 000000000000..a82328015fe2 --- /dev/null +++ b/drivers/media/video/mxc/output/mx27_pp.c @@ -0,0 +1,904 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_pp.c + * + * @brief MX27 V4L2 Video Output Driver + * + * Video4Linux2 Output Device using MX27 eMMA Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/module.h> +#include <linux/fb.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <asm/io.h> + +#include "mx27_pp.h" +#include "mxc_v4l2_output.h" + +#define SCALE_RETRY 32 /* to be more relax, less precise */ +#define PP_SKIP 1 +#define PP_TBL_MAX 40 + +static unsigned short scale_tbl[PP_TBL_MAX]; +static int g_hlen, g_vlen; + +static emma_pp_cfg g_pp_cfg; +static int g_disp_num = 0; +static char pp_dev[] = "emma_pp"; + +/*! + * @brief PP resizing routines + */ +static int gcd(int x, int y); +static int ratio(int x, int y, int *den); +static int scale_0d(int k, int coeff, int base, int nxt); +static int scale_1d(int inv, int outv, int k); +static int scale_1d_smart(int *inv, int *outv, int index); +static int scale_2d(emma_pp_scale * sz); + +static irqreturn_t pp_isr(int irq, void *dev_id); +static int set_output_addr(emma_pp_cfg * cfg, vout_data * vout); +static int pphw_reset(void); +static int pphw_enable(int flag); +static int pphw_ptr(emma_pp_cfg * cfg); +static int pphw_outptr(emma_pp_cfg * cfg); +static int pphw_cfg(emma_pp_cfg * cfg); +static int pphw_isr(void); +static void pphw_init(void); +static void pphw_exit(void); + +#define PP_DUMP(reg) pr_debug("%s\t = 0x%08X\n", #reg, __raw_readl(reg)) +void pp_dump(void) +{ + PP_DUMP(PP_CNTL); + PP_DUMP(PP_INTRCNTL); + PP_DUMP(PP_INTRSTATUS); + PP_DUMP(PP_SOURCE_Y_PTR); + PP_DUMP(PP_SOURCE_CB_PTR); + PP_DUMP(PP_SOURCE_CR_PTR); + PP_DUMP(PP_DEST_RGB_PTR); + PP_DUMP(PP_QUANTIZER_PTR); + PP_DUMP(PP_PROCESS_FRAME_PARA); + PP_DUMP(PP_SOURCE_FRAME_WIDTH); + PP_DUMP(PP_DEST_DISPLAY_WIDTH); + PP_DUMP(PP_DEST_IMAGE_SIZE); + PP_DUMP(PP_DEST_FRAME_FMT_CNTL); + PP_DUMP(PP_RESIZE_INDEX); + PP_DUMP(PP_CSC_COEF_0123); + PP_DUMP(PP_CSC_COEF_4); +} + +/*! + * @brief Set PP input address. + * @param ptr The pointer to the Y value of input + * @return Zero on success, others on failure + */ +int pp_ptr(unsigned long ptr) +{ + g_pp_cfg.ptr.y = ptr; + g_pp_cfg.ptr.u = g_pp_cfg.ptr.v = g_pp_cfg.ptr.qp = 0; + + return pphw_ptr(&g_pp_cfg); +} + +/*! + * @brief Enable or disable PP. + * @param flag Zero to disable PP, others to enable PP + * @return Zero on success, others on failure + */ +int pp_enable(int flag) +{ + return pphw_enable(flag); +} + +/*! + * @brief Get the display No. of last completed PP frame. + * @return The display No. of last completed PP frame. + */ +int pp_num_last(void) +{ + return (g_disp_num ? 0 : 1); +} + +/*! + * @brief Initialize PP. + * @param vout Pointer to _vout_data structure + * @return Zero on success, others on failure + */ +int pp_init(vout_data * vout) +{ + pphw_init(); + pphw_enable(0); + enable_irq(MXC_INT_EMMAPP); + return request_irq(MXC_INT_EMMAPP, pp_isr, 0, pp_dev, vout); +} + +/*! + * @brief Deinitialize PP. + * @param vout Pointer to _vout_data structure + */ +void pp_exit(vout_data * vout) +{ + disable_irq(MXC_INT_EMMAPP); + free_irq(MXC_INT_EMMAPP, vout); + pphw_enable(0); + pphw_exit(); +} + +/*! + * @brief Configure PP. + * @param vout Pointer to _vout_data structure + * @return Zero on success, others on failure + */ +int pp_cfg(vout_data * vout) +{ + if (!vout) + return -1; + + /* PP accepts YUV420 input only */ + if (vout->v2f.fmt.pix.pixelformat != V4L2_PIX_FMT_YUV420) { + pr_debug("unsupported pixel format.\n"); + return -1; + } + + g_pp_cfg.operation = 0; + + memset(g_pp_cfg.csc_table, 0, sizeof(g_pp_cfg.csc_table)); + + /* Convert output pixel format to PP required format */ + switch (vout->v4l2_fb.fmt.pixelformat) { + case V4L2_PIX_FMT_BGR32: + g_pp_cfg.red_width = 8; + g_pp_cfg.green_width = 8; + g_pp_cfg.blue_width = 8; + g_pp_cfg.red_offset = 8; + g_pp_cfg.green_offset = 16; + g_pp_cfg.blue_offset = 24; + g_pp_cfg.rgb_resolution = 32; + break; + case V4L2_PIX_FMT_RGB32: + g_pp_cfg.red_width = 8; + g_pp_cfg.green_width = 8; + g_pp_cfg.blue_width = 8; + g_pp_cfg.red_offset = 24; + g_pp_cfg.green_offset = 16; + g_pp_cfg.blue_offset = 8; + g_pp_cfg.rgb_resolution = 32; + break; + case V4L2_PIX_FMT_YUYV: + g_pp_cfg.red_width = 0; + g_pp_cfg.green_width = 0; + g_pp_cfg.blue_width = 0; + g_pp_cfg.red_offset = 0; + g_pp_cfg.green_offset = 0; + g_pp_cfg.blue_offset = PP_PIX_YUYV; + g_pp_cfg.rgb_resolution = 16; + break; + case V4L2_PIX_FMT_UYVY: + g_pp_cfg.red_width = 0; + g_pp_cfg.green_width = 0; + g_pp_cfg.blue_width = 0; + g_pp_cfg.red_offset = 0; + g_pp_cfg.green_offset = 0; + g_pp_cfg.blue_offset = PP_PIX_UYVY; + g_pp_cfg.rgb_resolution = 16; + break; + case V4L2_PIX_FMT_RGB565: + default: + g_pp_cfg.red_width = 5; + g_pp_cfg.green_width = 6; + g_pp_cfg.blue_width = 5; + g_pp_cfg.red_offset = 11; + g_pp_cfg.green_offset = 5; + g_pp_cfg.blue_offset = 0; + g_pp_cfg.rgb_resolution = 16; + break; + } + + if (vout->ipu_buf[0] != -1) + g_pp_cfg.ptr.y = + (unsigned int)vout->queue_buf_paddr[vout->ipu_buf[0]]; + else + g_pp_cfg.ptr.y = 0; + + g_pp_cfg.ptr.u = g_pp_cfg.ptr.v = g_pp_cfg.ptr.qp = 0; + + g_pp_cfg.dim.in.width = vout->v2f.fmt.pix.width; + g_pp_cfg.dim.in.height = vout->v2f.fmt.pix.height; + g_pp_cfg.dim.out.width = vout->crop_current.width; + g_pp_cfg.dim.out.height = vout->crop_current.height; + g_pp_cfg.dim.num.width = 0; + g_pp_cfg.dim.num.height = 0; + g_pp_cfg.dim.den.width = 0; + g_pp_cfg.dim.den.height = 0; + + if (scale_2d(&g_pp_cfg.dim)) { + pr_debug("unsupported resize ratio.\n"); + return -1; + } + + g_pp_cfg.dim.out.width = vout->crop_current.width; + g_pp_cfg.dim.out.height = vout->crop_current.height; + + g_pp_cfg.in_y_stride = 0; + if (set_output_addr(&g_pp_cfg, vout)) { + pr_debug("failed to set pp output address.\n"); + return -1; + } + + return pphw_cfg(&g_pp_cfg); +} + +irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id); + +/*! + * @brief PP IRQ handler. + */ +static irqreturn_t pp_isr(int irq, void *dev_id) +{ + int status; + vout_data *vout = dev_id; + + status = pphw_isr(); + if ((status & 0x1) == 0) { /* Not frame complete interrupt */ + pr_debug("not pp frame complete interrupt\n"); + return IRQ_HANDLED; + } + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + g_disp_num = g_disp_num ? 0 : 1; + g_pp_cfg.outptr = (unsigned int)vout->display_bufs[g_disp_num]; + pphw_outptr(&g_pp_cfg); + } + + return mxc_v4l2out_pp_in_irq_handler(irq, dev_id); +} + +/*! + * @brief Set PP output address. + * @param cfg Pointer to emma_pp_cfg structure + * @param vout Pointer to _vout_data structure + * @return Zero on success, others on failure + */ +static int set_output_addr(emma_pp_cfg * cfg, vout_data * vout) +{ + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + g_disp_num = 0; + cfg->outptr = (unsigned int)vout->display_bufs[g_disp_num]; + cfg->out_stride = vout->crop_current.width; + return 0; + } else { + struct fb_info *fb; + + fb = registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + if (!fb) + return -1; + + cfg->outptr = fb->fix.smem_start; + cfg->outptr += vout->crop_current.top * fb->var.xres_virtual + * (fb->var.bits_per_pixel >> 3) + + vout->crop_current.left * (fb->var.bits_per_pixel >> 3); + cfg->out_stride = fb->var.xres_virtual; + + return 0; + } +} + +/*! + * @brief Get maximum common divisor. + * @param x First input value + * @param y Second input value + * @return Maximum common divisor of x and y + */ +static int gcd(int x, int y) +{ + int k; + + if (x < y) { + k = x; + x = y; + y = k; + } + + while ((k = x % y)) { + x = y; + y = k; + } + + return y; +} + +/*! + * @brief Get ratio. + * @param x First input value + * @param y Second input value + * @param den Denominator of the ratio (corresponding to y) + * @return Numerator of the ratio (corresponding to x) + */ +static int ratio(int x, int y, int *den) +{ + int g; + + if (!x || !y) + return 0; + + g = gcd(x, y); + *den = y / g; + + return x / g; +} + +/*! + * @brief Build PP coefficient entry + * Build one or more coefficient entries for PP coefficient table based + * on given coefficient. + * + * @param k The index of the coefficient in coefficient table + * @param coeff The weighting coefficient + * @param base The base of the coefficient + * @param nxt Number of pixels to be read + * + * @return The index of the next coefficient entry on success + * -1 on failure + */ +static int scale_0d(int k, int coeff, int base, int nxt) +{ + if (k >= PP_TBL_MAX) { + /* no more space in table */ + pr_debug("no space in scale table, k = %d\n", k); + return -1; + } + + coeff = ((coeff << BC_COEF) + (base >> 1)) / base; + + /* + * Valid values for weighting coefficient are 0, 2 to 30, and 31. + * A value of 31 is treated as 32 and therefore 31 is an + * invalid co-efficient. + */ + if (coeff >= SZ_COEF - 1) + coeff--; + else if (coeff == 1) + coeff++; + coeff = coeff << BC_NXT; + + if (nxt < SZ_NXT) { + coeff |= nxt; + coeff <<= 1; + coeff |= 1; + } else { + /* + * src inc field is 2 bit wide, for 4+, use special + * code 0:0:1 to prevent dest inc + */ + coeff |= PP_SKIP; + coeff <<= 1; + coeff |= 1; + nxt -= PP_SKIP; + do { + pr_debug("tbl = %03X\n", coeff); + scale_tbl[k++] = coeff; + coeff = (nxt > PP_SKIP) ? PP_SKIP : nxt; + coeff <<= 1; + } while ((nxt -= PP_SKIP) > 0); + } + pr_debug("tbl = %03X\n", coeff); + scale_tbl[k++] = coeff; + + return k; +} + +/* + * @brief Build PP coefficient table + * Build PP coefficient table for one dimension (width or height) + * based on given input and output resolution + * + * @param inv input resolution + * @param outv output resolution + * @param k index of free table entry + * + * @return The index of the next free coefficient entry on success + * -1 on failure + */ +static int scale_1d(int inv, int outv, int k) +{ + int v; /* overflow counter */ + int coeff, nxt; /* table output */ + + if (inv == outv) + return scale_0d(k, 1, 1, 1); /* force scaling */ + + if (inv * 4 < outv) { + pr_debug("upscale err: ratio should be in range 1:1 to 1:4\n"); + return -1; + } + + v = 0; + if (inv < outv) { + /* upscale: mix <= 2 input pixels per output pixel */ + do { + coeff = outv - v; + v += inv; + if (v >= outv) { + v -= outv; + nxt = 1; + } else + nxt = 0; + pr_debug("upscale: coeff = %d/%d nxt = %d\n", coeff, + outv, nxt); + k = scale_0d(k, coeff, outv, nxt); + if (k < 0) + return -1; + } while (v); + } else if (inv >= 2 * outv) { + /* PP doesn't support resize ratio > 2:1 except 4:1. */ + if ((inv != 2 * outv) && (inv != 4 * outv)) + return -1; + /* downscale: >=2:1 bilinear approximation */ + coeff = inv - 2 * outv; + v = 0; + nxt = 0; + do { + v += coeff; + nxt = 2; + while (v >= outv) { + v -= outv; + nxt++; + } + pr_debug("downscale: coeff = 1/2 nxt = %d\n", nxt); + k = scale_0d(k, 1, 2, nxt); + if (k < 0) + return -1; + } while (v); + } else { + /* downscale: bilinear */ + int in_pos_inc = 2 * outv; + int out_pos = inv; + int out_pos_inc = 2 * inv; + int init_carry = inv - outv; + int carry = init_carry; + + v = outv + in_pos_inc; + do { + coeff = v - out_pos; + out_pos += out_pos_inc; + carry += out_pos_inc; + for (nxt = 0; v < out_pos; nxt++) { + v += in_pos_inc; + carry -= in_pos_inc; + } + pr_debug("downscale: coeff = %d/%d nxt = %d\n", coeff, + in_pos_inc, nxt); + k = scale_0d(k, coeff, in_pos_inc, nxt); + if (k < 0) + return -1; + } while (carry != init_carry); + } + return k; +} + +/* + * @brief Build PP coefficient table + * Build PP coefficient table for one dimension (width or height) + * based on given input and output resolution. The given input + * and output resolution might be not supported due to hardware + * limits. In this case this functin rounds the input and output + * to closest possible values and return them to caller. + * + * @param inv input resolution, might be modified after the call + * @param outv output resolution, might be modified after the call + * @param k index of free table entry + * + * @return The index of the next free coefficient entry on success + * -1 on failure + */ +static int scale_1d_smart(int *inv, int *outv, int index) +{ + int len, num, den, retry; + static int num1, den1; + + if (!inv || !outv) + return -1; + + /* Both should be non-zero */ + if (!(*inv) || !(*outv)) + return -1; + + retry = SCALE_RETRY; + + do { + num = ratio(*inv, *outv, &den); + pr_debug("num = %d, den = %d\n", num, den); + if (!num) + continue; + + if (index != 0) { + /* + * We are now resizing height. Check to see if the + * resize ratio for width can be reused by height + */ + if ((num == num1) && (den == den1)) + return index; + } + + if ((len = scale_1d(num, den, index)) < 0) + /* increase output dimension to try another ratio */ + (*outv)++; + else { + if (index == 0) { + /* + * We are now resizing width. The same resize + * ratio may be reused by height, so save the + * ratio. + */ + num1 = num; + den1 = den; + } + return len; + } + } while (retry--); + + pr_debug("pp scale err\n"); + return -1; +} + +/* + * @brief Build PP coefficient table for both width and height + * Build PP coefficient table for both width and height based on + * given resizing ratios. + * + * @param sz Structure contains resizing ratio informations + * + * @return 0 on success, others on failure + */ +static int scale_2d(emma_pp_scale * sz) +{ + int inv, outv; + + /* horizontal resizing. parameter check - must provide in size */ + if (!sz->in.width) + return -1; + + /* Resizing based on num:den */ + inv = sz->num.width; + outv = sz->den.width; + + if ((g_hlen = scale_1d_smart(&inv, &outv, 0)) > 0) { + /* Resizing succeeded */ + sz->den.width = outv; + sz->out.width = (sz->in.width * outv) / inv; + } else { + /* Resizing based on in:out */ + inv = sz->in.width; + outv = sz->out.width; + + if ((g_hlen = scale_1d_smart(&inv, &outv, 0)) > 0) { + /* Resizing succeeded */ + sz->out.width = outv; + sz->num.width = ratio(sz->in.width, sz->out.width, + &sz->den.width); + } else + return -1; + } + + sz->out.width &= ~1; + + /* vertical resizing. parameter check - must provide in size */ + if (!sz->in.height) + return -1; + + /* Resizing based on num:den */ + inv = sz->num.height; + outv = sz->den.height; + + if ((g_vlen = scale_1d_smart(&inv, &outv, g_hlen)) > 0) { + /* Resizing succeeded */ + sz->den.height = outv; + sz->out.height = (sz->in.height * outv) / inv; + } else { + /* Resizing based on in:out */ + inv = sz->in.height; + outv = sz->out.height; + + if ((g_vlen = scale_1d_smart(&inv, &outv, g_hlen)) > 0) { + /* Resizing succeeded */ + sz->out.height = outv; + sz->num.height = ratio(sz->in.height, sz->out.height, + &sz->den.height); + } else + return -1; + } + + return 0; +} + +/*! + * @brief Set PP resizing registers. + * @param sz Pointer to pp scaling structure + * @return Zero on success, others on failure + */ +static int pphw_scale(emma_pp_scale * sz) +{ + __raw_writel((sz->out.width << 16) | sz->out.height, + PP_DEST_IMAGE_SIZE); + __raw_writel(((g_hlen - 1) << 16) | (g_vlen == + g_hlen ? 0 : (g_hlen << 8)) | + (g_vlen - 1), PP_RESIZE_INDEX); + for (g_hlen = 0; g_hlen < g_vlen; g_hlen++) + __raw_writel(scale_tbl[g_hlen], + PP_RESIZE_COEF_TBL + g_hlen * 4); + + return 0; +} + +/*! + * @brief Reset PP. + * @return Zero on success, others on failure + */ +static int pphw_reset(void) +{ + int i; + + __raw_writel(0x100, PP_CNTL); + + /* timeout */ + for (i = 0; i < 1000; i++) { + if (!(__raw_readl(PP_CNTL) & 0x100)) { + pr_debug("pp reset over\n"); + break; + } + } + + /* check reset value */ + if (__raw_readl(PP_CNTL) != 0x876) { + pr_debug("pp reset value err = 0x%08X\n", __raw_readl(PP_CNTL)); + return -1; + } + + return 0; +} + +/*! + * @brief Enable or disable PP. + * @param flag Zero to disable PP, others to enable PP + * @return Zero on success, others on failure + */ +static int pphw_enable(int flag) +{ + int ret = 0; + + if (flag) + __raw_writel(__raw_readl(PP_CNTL) | 1, PP_CNTL); + else + ret = pphw_reset(); + + return ret; +} + +/*! + * @brief Set PP input address. + * @param cfg The pointer to PP configuration parameter + * @return Zero on success, others on failure + */ +static int pphw_ptr(emma_pp_cfg * cfg) +{ + if (!cfg->ptr.u) { + int size; + + /* yuv - packed */ + size = PP_CALC_Y_SIZE(cfg); + cfg->ptr.u = cfg->ptr.y + size; + cfg->ptr.v = cfg->ptr.u + (size >> 2); + + /* yuv packed with qp appended */ + if (!cfg->ptr.qp) + cfg->ptr.qp = cfg->ptr.v + (size >> 2); + } + __raw_writel(cfg->ptr.y, PP_SOURCE_Y_PTR); + __raw_writel(cfg->ptr.u, PP_SOURCE_CB_PTR); + __raw_writel(cfg->ptr.v, PP_SOURCE_CR_PTR); + __raw_writel(cfg->ptr.qp, PP_QUANTIZER_PTR); + + return 0; +} + +/*! + * @brief Set PP output address. + * @param cfg The pointer to PP configuration parameter + * @return Zero on success, others on failure + */ +static int pphw_outptr(emma_pp_cfg * cfg) +{ + __raw_writel(cfg->outptr, PP_DEST_RGB_PTR); + return 0; +} + +/*! + * @brief Configuration PP. + * @param cfg The pointer to PP configuration parameter + * @return Zero on success, others on failure + */ +static int pphw_cfg(emma_pp_cfg * cfg) +{ + int rt; + register int r; + + pphw_scale(&cfg->dim); + + if (!cfg->in_y_stride) + cfg->in_y_stride = cfg->dim.in.width; + + if (!cfg->out_stride) + cfg->out_stride = cfg->dim.out.width; + + r = __raw_readl(PP_CNTL) & ~EN_MASK; + + /* config parms */ + r |= cfg->operation & EN_MASK; + if (cfg->operation & EN_MACROBLOCK) { + /* Macroblock Mode */ + r |= 0x0200; + __raw_writel(0x06, PP_INTRCNTL); + } else { + /* Frame mode */ + __raw_writel(0x05, PP_INTRCNTL); + } + + if (cfg->red_width | cfg->green_width | cfg->blue_width) { + /* color conversion to be performed */ + r |= EN_CSC; + if (!(cfg->red_offset | cfg->green_offset)) { + /* auto offset B:G:R LSb to Msb */ + cfg->green_offset = cfg->blue_offset + cfg->blue_width; + cfg->red_offset = cfg->green_offset + cfg->green_width; + } + if (!cfg->rgb_resolution) { + /* derive minimum resolution required */ + int w, w2; + + w = cfg->red_offset + cfg->red_width; + w2 = cfg->blue_offset + cfg->blue_width; + if (w < w2) + w = w2; + w2 = cfg->green_offset + cfg->green_width; + if (w < w2) + w = w2; + if (w > 16) + w = 24; + else if (w > 8) + w = 16; + else + w = 8; + cfg->rgb_resolution = w; + } + /* 00,11 - 32 bpp, 10 - 16 bpp, 01 - 8 bpp */ + r &= ~0xC00; + if (cfg->rgb_resolution < 32) + r |= (cfg->rgb_resolution << 7); + __raw_writel((cfg->red_offset << 26) | + (cfg->green_offset << 21) | + (cfg->blue_offset << 16) | + (cfg->red_width << 8) | + (cfg->green_width << 4) | + cfg->blue_width, PP_DEST_FRAME_FMT_CNTL); + } else { + /* add YUV422 formatting */ + static const unsigned int _422[] = { + 0x62000888, + 0x60100888, + 0x43080888, + 0x41180888 + }; + + __raw_writel(_422[(cfg->blue_offset >> 3) & 3], + PP_DEST_FRAME_FMT_CNTL); + cfg->rgb_resolution = 16; + r &= ~0xC00; + r |= (cfg->rgb_resolution << 7); + } + + /* add csc formatting */ + if (!cfg->csc_table[1]) { + static const unsigned short _csc[][6] = { + {0x80, 0xb4, 0x2c, 0x5b, 0x0e4, 0}, + {0x95, 0xcc, 0x32, 0x68, 0x104, 1}, + {0x80, 0xca, 0x18, 0x3c, 0x0ec, 0}, + {0x95, 0xe5, 0x1b, 0x44, 0x10e, 1}, + }; + memcpy(cfg->csc_table, _csc[cfg->csc_table[0]], + sizeof(_csc[0])); + } + __raw_writel((cfg->csc_table[0] << 24) | + (cfg->csc_table[1] << 16) | + (cfg->csc_table[2] << 8) | + cfg->csc_table[3], PP_CSC_COEF_0123); + __raw_writel((cfg->csc_table[5] ? (1 << 9) : 0) | cfg->csc_table[4], + PP_CSC_COEF_4); + + __raw_writel(r, PP_CNTL); + + pphw_ptr(cfg); + pphw_outptr(cfg); + + /* + * #MB in a row = input_width / 16pix + * 1 byte per QP per MB + * QP must be formatted to be 4-byte aligned + * YUV lines are to be 4-byte aligned as well + * So Y is 8 byte aligned, as U = V = Y/2 for 420 + * MPEG MBs are 16x16 anyway + */ + __raw_writel((cfg->dim.in.width << 16) | cfg->dim.in.height, + PP_PROCESS_FRAME_PARA); + __raw_writel(cfg->in_y_stride | (PP_CALC_QP_WIDTH(cfg) << 16), + PP_SOURCE_FRAME_WIDTH); + + /* in bytes */ + rt = cfg->rgb_resolution >> 3; + if (rt == 3) + rt = 4; + __raw_writel(cfg->out_stride * rt, PP_DEST_DISPLAY_WIDTH); + + pp_dump(); + return 0; +} + +/*! + * @brief Check PP interrupt status. + * @return PP interrupt status + */ +static int pphw_isr(void) +{ + unsigned long status; + + pr_debug("pp: in isr.\n"); + status = __raw_readl(PP_INTRSTATUS) & 7; + if (!status) { + pr_debug("pp: not my isr err.\n"); + return status; + } + + if (status & 4) + pr_debug("pp: isr state error.\n"); + + /* clear interrupt status */ + __raw_writel(status, PP_INTRSTATUS); + + return status; +} + +static struct clk *emma_clk; + +/*! + * @brief PP module clock enable + */ +static void pphw_init(void) +{ + emma_clk = clk_get(NULL, "emma_clk"); + clk_enable(emma_clk); +} + +/*! + * @brief PP module clock disable + */ +static void pphw_exit(void) +{ + clk_disable(emma_clk); + clk_put(emma_clk); +} diff --git a/drivers/media/video/mxc/output/mx27_pp.h b/drivers/media/video/mxc/output/mx27_pp.h new file mode 100644 index 000000000000..7bc65ddda3a1 --- /dev/null +++ b/drivers/media/video/mxc/output/mx27_pp.h @@ -0,0 +1,180 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_pp.h + * + * @brief Header file for MX27 V4L2 Video Output Driver + * + * @ingroup MXC_V4L2_OUTPUT + */ +#ifndef __MX27_PP_H__ +#define __MX27_PP_H__ + +#include "mxc_v4l2_output.h" + +/* PP register definitions */ +#define PP_REG(ofs) (IO_ADDRESS(EMMA_BASE_ADDR) - 0x400 + ofs) + +/* Register offsets */ +#define PP_CNTL PP_REG(0x00) +#define PP_INTRCNTL PP_REG(0x04) +#define PP_INTRSTATUS PP_REG(0x08) +#define PP_SOURCE_Y_PTR PP_REG(0x0C) +#define PP_SOURCE_CB_PTR PP_REG(0x10) +#define PP_SOURCE_CR_PTR PP_REG(0x14) +#define PP_DEST_RGB_PTR PP_REG(0x18) +#define PP_QUANTIZER_PTR PP_REG(0x1C) +#define PP_PROCESS_FRAME_PARA PP_REG(0x20) +#define PP_SOURCE_FRAME_WIDTH PP_REG(0x24) +#define PP_DEST_DISPLAY_WIDTH PP_REG(0x28) +#define PP_DEST_IMAGE_SIZE PP_REG(0x2C) +#define PP_DEST_FRAME_FMT_CNTL PP_REG(0x30) +#define PP_RESIZE_INDEX PP_REG(0x34) +#define PP_CSC_COEF_0123 PP_REG(0x38) +#define PP_CSC_COEF_4 PP_REG(0x3C) +#define PP_RESIZE_COEF_TBL PP_REG(0x100) + +/* resize table dimensions + dest pixel index left/32 right/32 #src pixels to read + 0 [BC_COEF] [BC_COEF] [BC_NXT] + : + pp_tbl_max-1 +*/ +#define BC_NXT 2 +#define BC_COEF 5 +#define SZ_COEF (1 << BC_COEF) +#define SZ_NXT (1 << BC_NXT) + +/* PP operations */ +#define EN_DEBLOCK 0x02 +#define EN_DERING 0x04 +#define EN_CSC 0x10 +#define EN_MACROBLOCK 0x20 +#define EN_DEF 0x16 +#define EN_MASK 0x36 +#define EN_BIGDATA 0x1000 +#define EN_BIGQP 0x2000 + +/* PP CSC tables */ +#define CSC_TBL_NONE 0x80 +#define CSC_TBL_REUSE 0x81 +#define CSC_TBL_A1 0x00 +#define CSC_TBL_A0 0x20 +#define CSC_TBL_B1 0x40 +#define CSC_TBL_B0 0x60 +/* converts from 4 decimal fixed point to hw setting & vice versa */ +#define PP_CSC_FP4_2_HW(coeff) ((((coeff) << 7) + 5000) / 10000) +#define PP_CSC_HW_2_FP4(coeff) ((((coeff) * 10000) + 64) >> 7) + +#define PP_PIX_YUYV 0 +#define PP_PIX_YVYU 8 +#define PP_PIX_UYVY 16 +#define PP_PIX_VYUY 24 + +/* PP size & width calculation macros */ +#define PP_CALC_QP_WIDTH(cfg) \ + (!((cfg)->operation & (EN_DEBLOCK | EN_DERING)) ? 0 : \ + (((((cfg)->dim.in.width + 15) >> 4) + 3) & ~3)) +#define PP_CALC_Y_SIZE(cfg) \ + ((cfg)->in_y_stride * (cfg)->dim.in.height) +#define PP_CALC_CH_SIZE(cfg) (PP_CALC_Y_SIZE(cfg) >> 2) +#define PP_CALC_BPP(cfg) \ + ((cfg)->rgb_resolution > 16 ? 4 : ((cfg)->rgb_resolution >> 3)) +#define PP_CALC_YUV_SIZE(cfg) \ + ((PP_CALC_Y_SIZE(cfg) * 3) >> 1) +#define PP_CALC_QP_SIZE(cfg) \ + (PP_CALC_QP_WIDTH(cfg) * (((cfg)->dim.in.height + 15) >> 4)) +#define PP_CALC_DEST_WIDTH(cfg) \ + (((cfg)->out_stride & ~1) * PP_CALC_BPP(cfg)) +#define PP_CALC_DEST_SIZE(cfg) \ + ((cfg)->dim.out.height * PP_CALC_DEST_WIDTH(cfg)) + +/* + * physical addresses for bus mastering + * v=0 -> yuv packed + * v=0 & qp=0 -> yuv packed with qp appended + */ +typedef struct _emma_pp_ptr { + unsigned int y; /* Y data (line align8) */ + unsigned int u; /* U data (line align4) */ + unsigned int v; /* V data (line align4) */ + unsigned int qp; /* Quantization (line align4) */ +} emma_pp_ptr; + +typedef struct _emma_pp_size { + int width; + int height; +} emma_pp_size; + +/* + * if num.width != 0 + * resize ratio = num.width : den.width + * else + * resize ratio = in.width : out.width + * same for height + */ +typedef struct _emma_pp_scale { + emma_pp_size num; + emma_pp_size den; + emma_pp_size in; /* clip */ + emma_pp_size out; /* 0 -> same as in */ +} emma_pp_scale; + +typedef struct _emma_pp_cfg { + unsigned char operation; /* OR of EN_xx defines */ + + /* + * input color coeff + * fixed pt 8 bits, steps of 1/128 + * csc[5] is 1 or 0 to indicate Y + 16 + * csc[0] is matrix id 0-3 while csc[1-5]=0 + */ + unsigned short csc_table[6]; + + /* + * Output color (shade width, shade offset, pixel resolution) + * Eg. 16bpp RGB565 resolution, the values could be: + * red_width = 5, green_width = 6, blue_width = 6 + * red_offset = 11, green_offset = 5, blue_offset = 0 (defaults) + * rgb_resolution = 16 (default) + * For YUV422: xxx_width=0, blue_offset=PP_PIX_xxx + */ + unsigned short red_width; + unsigned short green_width; + unsigned short blue_width; + /* if offsets are 0, the offsets are by width LSb to MSb B:G:R */ + unsigned short red_offset; + unsigned short blue_offset; + unsigned short green_offset; + /* if resolution is 0, the minimum for the sum of widths is chosen */ + short rgb_resolution; /* 8,16,24 bpp only */ + + emma_pp_ptr ptr; /* dma buffer pointers */ + unsigned int outptr; /* RGB/YUV output */ + emma_pp_scale dim; /* in/out dimensions */ + + /* pixels between two adjacent input Y rows */ + unsigned short in_y_stride; /* 0 = in_width */ + /* PIXELS between two adjacent output rows */ + unsigned short out_stride; /* 0 = out_width */ +} emma_pp_cfg; + +int pp_ptr(unsigned long ptr); +int pp_enable(int flag); +int pp_cfg(vout_data * vout); +int pp_init(vout_data * vout); +int pp_num_last(void); +void pp_exit(vout_data * vout); + +#endif /* __MX27_PP_H__ */ diff --git a/drivers/media/video/mxc/output/mx27_v4l2_output.c b/drivers/media/video/mxc/output/mx27_v4l2_output.c new file mode 100644 index 000000000000..a00f92d8e979 --- /dev/null +++ b/drivers/media/video/mxc/output/mx27_v4l2_output.c @@ -0,0 +1,1442 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_v4l2_output.c + * + * @brief MX27 V4L2 Video Output Driver + * + * Video4Linux2 Output Device using MX27 eMMA Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/semaphore.h> +#include <linux/poll.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> + +#include "mxc_v4l2_output.h" +#include "mx27_pp.h" +#include "../drivers/video/mxc/mx2fb.h" + +#define SDC_FG_FB_FORMAT V4L2_PIX_FMT_RGB565 + +struct v4l2_output mxc_outputs[1] = { + { + .index = 0, + .name = "DISP0 Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN}, +}; + +static int video_nr = 16; +static spinlock_t g_lock = SPIN_LOCK_UNLOCKED; +vout_data *g_vout; + +/* debug counters */ +uint32_t g_irq_cnt; +uint32_t g_buf_output_cnt; +uint32_t g_buf_q_cnt; +uint32_t g_buf_dq_cnt; + +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC +static uint32_t g_output_fb = -1; +static uint32_t g_fb_enabled = 0; +static uint32_t g_pp_ready = 0; + +static int fb_event_notify(struct notifier_block *self, + unsigned long action, void *data) +{ + struct fb_event *event = data; + struct fb_info *info = event->info; + unsigned long lock_flags; + int blank, i; + + for (i = 0; i < num_registered_fb; i++) + if (registered_fb[i] == info) + break; + + /* + * Check if the event is sent by the framebuffer in which + * the video is displayed. + */ + if (i != g_output_fb) + return 0; + + switch (action) { + case FB_EVENT_BLANK: + blank = *(int *)event->data; + spin_lock_irqsave(&g_lock, lock_flags); + g_fb_enabled = !blank; + if (blank && g_pp_ready) { + if (pp_enable(1)) + pr_debug("unable to enable PP\n"); + g_pp_ready = 0; + } + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + case FB_EVENT_MXC_EOF: + spin_lock_irqsave(&g_lock, lock_flags); + g_fb_enabled = 1; + if (g_pp_ready) { + if (pp_enable(1)) + pr_debug("unable to enable PP\n"); + g_pp_ready = 0; + } + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + + return 0; +} + +static struct notifier_block fb_event_notifier = { + .notifier_call = fb_event_notify, +}; + +static struct notifier_block mx2fb_event_notifier = { + .notifier_call = fb_event_notify, +}; +#endif + +#define QUEUE_SIZE (MAX_FRAME_NUM + 1) +static __inline int queue_size(v4l_queue * q) +{ + if (q->tail >= q->head) + return (q->tail - q->head); + else + return ((q->tail + QUEUE_SIZE) - q->head); +} + +static __inline int queue_buf(v4l_queue * q, int idx) +{ + if (((q->tail + 1) % QUEUE_SIZE) == q->head) + return -1; /* queue full */ + q->list[q->tail] = idx; + q->tail = (q->tail + 1) % QUEUE_SIZE; + return 0; +} + +static __inline int dequeue_buf(v4l_queue * q) +{ + int ret; + if (q->tail == q->head) + return -1; /* queue empty */ + ret = q->list[q->head]; + q->head = (q->head + 1) % QUEUE_SIZE; + return ret; +} + +static __inline int peek_next_buf(v4l_queue * q) +{ + if (q->tail == q->head) + return -1; /* queue empty */ + return q->list[q->head]; +} + +static __inline unsigned long get_jiffies(struct timeval *t) +{ + struct timeval cur; + + if (t->tv_usec >= 1000000) { + t->tv_sec += t->tv_usec / 1000000; + t->tv_usec = t->tv_usec % 1000000; + } + + do_gettimeofday(&cur); + if ((t->tv_sec < cur.tv_sec) + || ((t->tv_sec == cur.tv_sec) && (t->tv_usec < cur.tv_usec))) + return jiffies; + + if (t->tv_usec < cur.tv_usec) { + cur.tv_sec = t->tv_sec - cur.tv_sec - 1; + cur.tv_usec = t->tv_usec + 1000000 - cur.tv_usec; + } else { + cur.tv_sec = t->tv_sec - cur.tv_sec; + cur.tv_usec = t->tv_usec - cur.tv_usec; + } + + return jiffies + timeval_to_jiffies(&cur); +} + +/*! + * Private function to free buffers + * + * @param bufs_paddr Array of physical address of buffers to be freed + * + * @param bufs_vaddr Array of virtual address of buffers to be freed + * + * @param num_buf Number of buffers to be freed + * + * @param size Size for each buffer to be free + * + * @return status 0 success. + */ +static int mxc_free_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + if (bufs_vaddr[i] != 0) { + dma_free_coherent(0, size, bufs_vaddr[i], + bufs_paddr[i]); + pr_debug("freed @ paddr=0x%08X\n", (u32) bufs_paddr[i]); + bufs_paddr[i] = 0; + bufs_vaddr[i] = NULL; + } + } + return 0; +} + +/*! + * Private function to allocate buffers + * + * @param bufs_paddr Output array of physical address of buffers allocated + * + * @param bufs_vaddr Output array of virtual address of buffers allocated + * + * @param num_buf Input number of buffers to allocate + * + * @param size Input size for each buffer to allocate + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + bufs_vaddr[i] = dma_alloc_coherent(0, size, + &bufs_paddr[i], + GFP_DMA | GFP_KERNEL); + + if (bufs_vaddr[i] == 0) { + mxc_free_buffers(bufs_paddr, bufs_vaddr, i, size); + pr_debug("dma_alloc_coherent failed.\n"); + return -ENOBUFS; + } + pr_debug("allocated @ paddr=0x%08X, size=%d.\n", + (u32) bufs_paddr[i], size); + } + + return 0; +} + +static void mxc_v4l2out_timer_handler(unsigned long arg) +{ + int index; + unsigned long timeout; + unsigned long lock_flags; + vout_data *vout = (vout_data *) arg; + + pr_debug("timer handler: %lu\n", jiffies); + + spin_lock_irqsave(&g_lock, lock_flags); + + if ((vout->state == STATE_STREAM_OFF) + || (vout->state == STATE_STREAM_STOPPING)) { + pr_debug("stream has stopped\n"); + goto exit0; + } + + /* + * If timer occurs before PP h/w is ready, then set the state to + * paused and the timer will be set again when next buffer is queued + * or PP completes. + */ + if (vout->ipu_buf[0] != -1) { + pr_debug("buffer is busy\n"); + vout->state = STATE_STREAM_PAUSED; + goto exit0; + } + + /* Dequeue buffer and pass to PP */ + index = dequeue_buf(&vout->ready_q); + if (index == -1) { /* no buffers ready, should never occur */ + pr_debug("mxc_v4l2out: timer - no queued buffers ready\n"); + goto exit0; + } + + g_buf_dq_cnt++; + vout->frame_count++; + vout->ipu_buf[0] = index; + + if (pp_ptr((unsigned int)vout->queue_buf_paddr[index])) { + pr_debug("unable to update buffer\n"); + goto exit0; + } +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC + if (g_fb_enabled && (vout->v4l2_fb.flags != V4L2_FBUF_FLAG_OVERLAY)) + g_pp_ready = 1; + else if (pp_enable(1)) { + pr_debug("unable to enable PP\n"); + goto exit0; + } +#else + if (pp_enable(1)) { + pr_debug("unable to enable PP\n"); + goto exit0; + } +#endif + pr_debug("enabled index %d\n", index); + + /* Setup timer for next buffer */ + index = peek_next_buf(&vout->ready_q); + pr_debug("next index %d\n", index); + if (index != -1) { + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + pr_debug("warning: timer timeout already expired.\n"); + } + + if (mod_timer(&vout->output_timer, timeout)) + pr_debug("warning: timer was already set\n"); + + pr_debug("timer handler next schedule: %lu\n", timeout); + } else { + vout->state = STATE_STREAM_PAUSED; + pr_debug("timer handler paused\n"); + } + + exit0: + spin_unlock_irqrestore(&g_lock, lock_flags); +} + +irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id) +{ + int last_buf; + int index; + unsigned long timeout; + unsigned long lock_flags; + vout_data *vout = dev_id; + + spin_lock_irqsave(&g_lock, lock_flags); + + g_irq_cnt++; + + if ((vout->state == STATE_STREAM_OFF) + || (vout->state == STATE_STREAM_STOPPING)) { + spin_unlock_irqrestore(&g_lock, lock_flags); + return IRQ_HANDLED; + } + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + gwinfo.enabled = 1; + gwinfo.alpha_value = 255; + gwinfo.ck_enabled = 0; + gwinfo.xpos = vout->crop_current.left; + gwinfo.ypos = vout->crop_current.top; + gwinfo.base = (unsigned long)vout->display_bufs[pp_num_last()]; + gwinfo.xres = vout->crop_current.width; + gwinfo.yres = vout->crop_current.height; + gwinfo.xres_virtual = vout->crop_current.width; + gwinfo.vs_reversed = 0; + + mx2_gw_set(&gwinfo); + } + + /* Process previous buffer */ + last_buf = vout->ipu_buf[0]; + pr_debug("last_buf %d g_irq_cnt %d\n", last_buf, g_irq_cnt); + if (last_buf != -1) { + g_buf_output_cnt++; + vout->v4l2_bufs[last_buf].flags = V4L2_BUF_FLAG_DONE; + queue_buf(&vout->done_q, last_buf); + vout->ipu_buf[0] = -1; + wake_up_interruptible(&vout->v4l_bufq); + } + + /* Setup timer for next buffer, when stream has been paused */ + if ((vout->state == STATE_STREAM_PAUSED) + && ((index = peek_next_buf(&vout->ready_q)) != -1)) { + pr_debug("next index %d\n", index); + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + pr_debug("warning: timer timeout already expired.\n"); + } + + vout->state = STATE_STREAM_ON; + + if (mod_timer(&vout->output_timer, timeout)) + pr_debug("warning: timer was already set\n"); + + pr_debug("timer handler next schedule: %lu\n", timeout); + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return IRQ_HANDLED; +} + +/*! + * Start the output stream + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamon(vout_data * vout) +{ + unsigned long timeout; + int index; + + if (!vout) + return -EINVAL; + + if (vout->state != STATE_STREAM_OFF) + return -EBUSY; + + if (queue_size(&vout->ready_q) < 1) { + pr_debug("no buffers queued yet!\n"); + return -EINVAL; + } + + vout->ipu_buf[0] = -1; + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + /* Free previously allocated buffer */ + mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr, + 2, vout->display_buf_size); + /* Allocate buffers for foreground */ + if (mxc_allocate_buffers(vout->display_bufs, + vout->display_bufs_vaddr, 2, + vout->display_buf_size) < 0) { + pr_debug("unable to allocate SDC FG buffers\n"); + return -ENOMEM; + } + } + + /* Configure PP */ + if (pp_cfg(vout)) { + /* Free previously allocated buffer */ + mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr, + 2, vout->display_buf_size); + pr_debug("failed to config PP.\n"); + return -EINVAL; + } +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC + g_output_fb = vout->output_fb_num[vout->cur_disp_output]; + g_fb_enabled = 0; + g_pp_ready = 0; + fb_register_client(&fb_event_notifier); + mx2fb_register_client(&mx2fb_event_notifier); +#endif + vout->frame_count = 0; + vout->state = STATE_STREAM_ON; + index = peek_next_buf(&vout->ready_q); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = jiffies; + else + timeout = get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + pr_debug("warning: timer timeout already expired.\n"); + } + + vout->start_jiffies = vout->output_timer.expires = timeout; + pr_debug("STREAMON:Add timer %d timeout @ %lu jiffies, current = %lu\n", + index, timeout, jiffies); + add_timer(&vout->output_timer); + + return 0; +} + +/*! + * Shut down the voutera + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamoff(vout_data * vout) +{ + int i, retval = 0; + unsigned long lock_flag = 0; + + if (!vout) + return -EINVAL; + + if (vout->state == STATE_STREAM_OFF) { + return 0; + } + + spin_lock_irqsave(&g_lock, lock_flag); + + del_timer(&vout->output_timer); + pp_enable(0); /* Disable PP */ + + if (vout->state == STATE_STREAM_ON) { + vout->state = STATE_STREAM_STOPPING; + } + + spin_unlock_irqrestore(&g_lock, lock_flag); + + vout->ready_q.head = vout->ready_q.tail = 0; + vout->done_q.head = vout->done_q.tail = 0; + for (i = 0; i < vout->buffer_cnt; i++) { + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + + vout->state = STATE_STREAM_OFF; + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + /* Disable graphic window */ + gwinfo.enabled = 0; + mx2_gw_set(&gwinfo); + } +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC + g_output_fb = -1; + g_fb_enabled = 0; + g_pp_ready = 0; + fb_unregister_client(&fb_event_notifier); + mx2fb_unregister_client(&mx2fb_event_notifier); +#endif + + mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr, + 2, vout->display_buf_size); + + return retval; +} + +/* + * Valid whether the palette is supported + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 1 if supported, 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return (palette == V4L2_PIX_FMT_YUV420); +} + +/* + * Returns bits per pixel for given pixel format + * + * @param pixelformat V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return bits per pixel of pixelformat + */ +static u32 fmt_to_bpp(u32 pixelformat) +{ + u32 bpp; + + switch (pixelformat) { + case V4L2_PIX_FMT_RGB565: + bpp = 16; + break; + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_RGB24: + bpp = 24; + break; + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_RGB32: + bpp = 32; + break; + default: + bpp = 8; + break; + } + return bpp; +} + +/* + * V4L2 - Handles VIDIOC_G_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_g_fmt(vout_data * vout, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + return -EINVAL; + } + *f = vout->v2f; + return 0; +} + +/* + * V4L2 - Handles VIDIOC_S_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_s_fmt(vout_data * vout, struct v4l2_format *f) +{ + int retval = 0; + u32 size = 0; + u32 bytesperline; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + goto err0; + } + if (!valid_mode(f->fmt.pix.pixelformat)) { + pr_debug("pixel format not supported\n"); + retval = -EINVAL; + goto err0; + } + + bytesperline = (f->fmt.pix.width * fmt_to_bpp(f->fmt.pix.pixelformat)) / + 8; + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV422P: + /* byteperline for YUV planar formats is for + Y plane only */ + size = bytesperline * f->fmt.pix.height * 2; + break; + case V4L2_PIX_FMT_YUV420: + size = (bytesperline * f->fmt.pix.height * 3) / 2; + break; + default: + size = bytesperline * f->fmt.pix.height; + break; + } + + /* Return the actual size of the image to the app */ + f->fmt.pix.sizeimage = size; + + vout->v2f.fmt.pix.sizeimage = size; + vout->v2f.fmt.pix.width = f->fmt.pix.width; + vout->v2f.fmt.pix.height = f->fmt.pix.height; + vout->v2f.fmt.pix.pixelformat = f->fmt.pix.pixelformat; + vout->v2f.fmt.pix.bytesperline = f->fmt.pix.bytesperline; + + retval = 0; + err0: + return retval; +} + +/* + * V4L2 - Handles VIDIOC_G_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42lout_control(vout_data * vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + return (vout->rotate & IPU_ROTATE_HORIZ_FLIP) ? 1 : 0; + case V4L2_CID_VFLIP: + return (vout->rotate & IPU_ROTATE_VERT_FLIP) ? 1 : 0; + case (V4L2_CID_PRIVATE_BASE + 1): + return vout->rotate; + default: + return -EINVAL; + } +} + +/* + * V4L2 - Handles VIDIOC_S_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42lout_control(vout_data * vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + case V4L2_CID_MXC_ROT: + return 0; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 interface - open function + * + * @param inode structure inode * + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l2out_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + int err; + + if (!vout) { + pr_info("Internal error, vout_data not found!\n"); + return -ENODEV; + } + + down(&vout->busy_lock); + + err = -EINTR; + if (signal_pending(current)) + goto oops; + + if (vout->open_count++ == 0) { + pp_init(vout); + + init_waitqueue_head(&vout->v4l_bufq); + + init_timer(&vout->output_timer); + vout->output_timer.function = mxc_v4l2out_timer_handler; + vout->output_timer.data = (unsigned long)vout; + + vout->state = STATE_STREAM_OFF; + g_irq_cnt = g_buf_output_cnt = g_buf_q_cnt = g_buf_dq_cnt = 0; + + } + + file->private_data = dev; + up(&vout->busy_lock); + return 0; + + oops: + up(&vout->busy_lock); + return err; +} + +/*! + * V4L2 interface - close function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l2out_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = file->private_data; + vout_data *vout = video_get_drvdata(dev); + + if (--vout->open_count == 0) { + pr_debug("release resource\n"); + + pp_exit(vout); + if (vout->state != STATE_STREAM_OFF) + mxc_v4l2out_streamoff(vout); + + file->private_data = NULL; + + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, vout->queue_buf_size); + vout->buffer_cnt = 0; + mxc_free_buffers(vout->display_bufs, + vout->display_bufs_vaddr, + 2, vout->display_buf_size); + + /* capture off */ + wake_up_interruptible(&vout->v4l_bufq); + } + + return 0; +} + +/*! + * V4L2 interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int +mxc_v4l2out_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = file->private_data; + vout_data *vout = video_get_drvdata(dev); + int retval = 0; + int i = 0; + + if (!vout) + return -EBADF; + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2_output"); + cap->version = 0; + cap->capabilities = + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + case VIDIOC_G_FMT: + { + struct v4l2_format *gf = arg; + retval = mxc_v4l2out_g_fmt(vout, gf); + break; + } + case VIDIOC_S_FMT: + { + struct v4l2_format *sf = arg; + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + retval = mxc_v4l2out_s_fmt(vout, sf); + break; + } + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *req = arg; + if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (req->memory != V4L2_MEMORY_MMAP)) { + pr_debug + ("VIDIOC_REQBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + + if (req->count == 0) + mxc_v4l2out_streamoff(vout); + + if (vout->state == STATE_STREAM_OFF) { + if (vout->queue_buf_paddr[0] != 0) { + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + pr_debug + ("VIDIOC_REQBUFS: freed buffers\n"); + } + vout->buffer_cnt = 0; + } else { + pr_debug("VIDIOC_REQBUFS: Buffer is in use\n"); + retval = -EBUSY; + break; + } + + if (req->count == 0) + break; + + if (req->count < MIN_FRAME_NUM) { + req->count = MIN_FRAME_NUM; + } else if (req->count > MAX_FRAME_NUM) { + req->count = MAX_FRAME_NUM; + } + vout->buffer_cnt = req->count; + vout->queue_buf_size = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + + retval = mxc_allocate_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + if (retval < 0) + break; + + /* Init buffer queues */ + vout->done_q.head = 0; + vout->done_q.tail = 0; + vout->ready_q.head = 0; + vout->ready_q.tail = 0; + + for (i = 0; i < vout->buffer_cnt; i++) { + memset(&(vout->v4l2_bufs[i]), 0, + sizeof(vout->v4l2_bufs[i])); + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].memory = V4L2_MEMORY_MMAP; + vout->v4l2_bufs[i].index = i; + vout->v4l2_bufs[i].type = + V4L2_BUF_TYPE_VIDEO_OUTPUT; + vout->v4l2_bufs[i].length = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + vout->v4l2_bufs[i].m.offset = + (unsigned long)vout->queue_buf_paddr[i]; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + break; + } + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *buf = arg; + u32 type = buf->type; + int index = buf->index; + + if ((type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + pr_debug + ("VIDIOC_QUERYBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + down(&vout->param_lock); + memcpy(buf, &(vout->v4l2_bufs[index]), sizeof(*buf)); + up(&vout->param_lock); + break; + } + case VIDIOC_QBUF: + { + struct v4l2_buffer *buf = arg; + int index = buf->index; + unsigned long lock_flags; + unsigned long timeout; + + if ((buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt) || (buf->flags != 0)) { + retval = -EINVAL; + break; + } + + pr_debug("VIDIOC_QBUF: %d\n", buf->index); + + spin_lock_irqsave(&g_lock, lock_flags); + + memcpy(&(vout->v4l2_bufs[index]), buf, sizeof(*buf)); + vout->v4l2_bufs[index].flags |= V4L2_BUF_FLAG_QUEUED; + + g_buf_q_cnt++; + queue_buf(&vout->ready_q, index); + + if (vout->state == STATE_STREAM_PAUSED) { + index = peek_next_buf(&vout->ready_q); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == + 0) + && (vout->v4l2_bufs[index].timestamp. + tv_usec == 0)) + timeout = + vout->start_jiffies + + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index]. + timestamp); + + if (jiffies >= timeout) { + pr_debug + ("warning: timer timeout already expired.\n"); + } + + vout->output_timer.expires = timeout; + pr_debug + ("QBUF:Add timer %d timeout @ %lu jiffies, " + "current = %lu\n", index, timeout, + jiffies); + add_timer(&vout->output_timer); + vout->state = STATE_STREAM_ON; + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + case VIDIOC_DQBUF: + { + struct v4l2_buffer *buf = arg; + int idx; + + pr_debug("VIDIOC_DQBUF: q size = %d\n", + queue_size(&vout->done_q)); + + if ((queue_size(&vout->done_q) == 0) && + (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + break; + } + + if (!wait_event_interruptible_timeout(vout->v4l_bufq, + queue_size(&vout-> + done_q) + != 0, 10 * HZ)) { + pr_debug("VIDIOC_DQBUF: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + pr_debug("VIDIOC_DQBUF: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + idx = dequeue_buf(&vout->done_q); + if (idx == -1) { /* No frame free */ + pr_debug + ("VIDIOC_DQBUF: no free buffers, returning\n"); + retval = -EAGAIN; + break; + } + if ((vout->v4l2_bufs[idx].flags & V4L2_BUF_FLAG_DONE) == + 0) + pr_debug + ("VIDIOC_DQBUF: buffer in done q, but not " + "flagged as done\n"); + + vout->v4l2_bufs[idx].flags = 0; + memcpy(buf, &(vout->v4l2_bufs[idx]), sizeof(*buf)); + pr_debug("VIDIOC_DQBUF: %d\n", buf->index); + break; + } + case VIDIOC_STREAMON: + { + retval = mxc_v4l2out_streamon(vout); + break; + } + case VIDIOC_STREAMOFF: + { + retval = mxc_v4l2out_streamoff(vout); + break; + } + case VIDIOC_G_CTRL: + { + retval = mxc_get_v42lout_control(vout, arg); + break; + } + case VIDIOC_S_CTRL: + { + retval = mxc_set_v42lout_control(vout, arg); + break; + } + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + cap->bounds = vout->crop_bounds[vout->cur_disp_output]; + cap->defrect = vout->crop_bounds[vout->cur_disp_output]; + retval = 0; + break; + } + case VIDIOC_G_CROP: + { + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + crop->c = vout->crop_current; + break; + } + case VIDIOC_S_CROP: + { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = + &(vout->crop_bounds[vout->cur_disp_output]); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + if (crop->c.height < 0) { + retval = -EINVAL; + break; + } + if (crop->c.width < 0) { + retval = -EINVAL; + break; + } + + if (crop->c.top < b->top) + crop->c.top = b->top; + if (crop->c.top > b->top + b->height) + crop->c.top = b->top + b->height; + if (crop->c.height > b->top - crop->c.top + b->height) + crop->c.height = + b->top - crop->c.top + b->height; + + if (crop->c.left < b->left) + crop->c.top = b->left; + if (crop->c.left > b->left + b->width) + crop->c.top = b->left + b->width; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + /* stride line limitation */ + crop->c.height -= crop->c.height % 8; + crop->c.width -= crop->c.width % 8; + + vout->crop_current = crop->c; + + vout->display_buf_size = vout->crop_current.width * + vout->crop_current.height; + vout->display_buf_size *= + fmt_to_bpp(SDC_FG_FB_FORMAT) / 8; + break; + } + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if ((output->index >= 2) || + (vout->output_enabled[output->index] == false)) { + retval = -EINVAL; + break; + } + + *output = mxc_outputs[0]; + output->name[4] = '0' + output->index; + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = vout->cur_disp_output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + + if ((*p_output_num >= 2) || + (vout->output_enabled[*p_output_num] == false)) { + retval = -EINVAL; + break; + } + + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + + vout->cur_disp_output = *p_output_num; + break; + } + case VIDIOC_G_FBUF: + { + struct v4l2_framebuffer *fb = arg; + *fb = vout->v4l2_fb; + break; + } + case VIDIOC_S_FBUF: + { + struct v4l2_framebuffer *fb = arg; + vout->v4l2_fb = *fb; + vout->v4l2_fb.capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + break; + } + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_PARM: + case VIDIOC_ENUMSTD: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&vout->busy_lock); + return retval; +} + +/* + * V4L2 interface - ioctl function + * + * @return None + */ +static int +mxc_v4l2out_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, mxc_v4l2out_do_ioctl); +} + +/*! + * V4L2 interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error + */ +static int mxc_v4l2out_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = file->private_data; + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + vout_data *vout = video_get_drvdata(dev); + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + /* make buffers write-thru cacheable */ + vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) & + ~L_PTE_BUFFERABLE); + + if (remap_pfn_range(vma, start, vma->vm_pgoff, size, vma->vm_page_prot)) { + pr_debug("mxc_mmap(V4L)i - remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + mxc_mmap_exit: + up(&vout->busy_lock); + return res; +} + +/*! + * V4L2 interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_v4l2out_poll(struct file *file, poll_table * wait) +{ + struct video_device *dev = file->private_data; + vout_data *vout = video_get_drvdata(dev); + + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + queue = &vout->v4l_bufq; + poll_wait(file, queue, wait); + + up(&vout->busy_lock); + return res; +} + +static struct file_operations mxc_v4l2out_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l2out_open, + .release = mxc_v4l2out_close, + .ioctl = mxc_v4l2out_ioctl, + .mmap = mxc_v4l2out_mmap, + .poll = mxc_v4l2out_poll, +}; + +static struct video_device mxc_v4l2out_template = { + .name = "MXC Video Output", + .vfl_type = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING, + .fops = &mxc_v4l2out_fops, + .release = video_device_release, +}; + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxc_v4l2out_probe(struct platform_device *pdev) +{ + int i; + vout_data *vout; + + /* + * Allocate sufficient memory for the fb structure + */ + g_vout = vout = kmalloc(sizeof(vout_data), GFP_KERNEL); + + if (!vout) + return 0; + + memset(vout, 0, sizeof(vout_data)); + + vout->video_dev = video_device_alloc(); + if (vout->video_dev == NULL) + return -1; + vout->video_dev->minor = -1; + + *(vout->video_dev) = mxc_v4l2out_template; + + /* register v4l device */ + if (video_register_device(vout->video_dev, + VFL_TYPE_GRABBER, video_nr) == -1) { + pr_debug("video_register_device failed\n"); + return 0; + } + pr_debug("mxc_v4l2out: registered device video%d\n", + vout->video_dev->minor & 0x1f); + + video_set_drvdata(vout->video_dev, vout); + + init_MUTEX(&vout->param_lock); + init_MUTEX(&vout->busy_lock); + + /* setup outputs and cropping */ + vout->cur_disp_output = -1; + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strncmp(idstr, "DISP", 4) == 0) { + int disp_num = i; + vout->crop_bounds[disp_num].left = 0; + vout->crop_bounds[disp_num].top = 0; + vout->crop_bounds[disp_num].width = + registered_fb[i]->var.xres; + vout->crop_bounds[disp_num].height = + registered_fb[i]->var.yres; + vout->output_enabled[disp_num] = true; + vout->output_fb_num[disp_num] = i; + if (vout->cur_disp_output == -1) + vout->cur_disp_output = disp_num; + } + + } + vout->crop_current = vout->crop_bounds[vout->cur_disp_output]; + + /* Setup framebuffer parameters */ + vout->v4l2_fb.capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + vout->v4l2_fb.flags = V4L2_FBUF_FLAG_PRIMARY; + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2out_driver = { + .driver = { + .name = "MXC Video Output", + .owner = THIS_MODULE, + .bus = &platform_bus_type, + }, + .probe = mxc_v4l2out_probe, + .remove = NULL, +}; + +static void camera_platform_release(struct device *device) +{ +} + +static struct platform_device mxc_v4l2out_device = { + .name = "MXC Video Output", + .dev = { + .release = camera_platform_release, + }, + .id = 0, +}; + +/*! + * mxc v4l2 init function + * + */ +static int mxc_v4l2out_init(void) +{ + u8 err = 0; + + err = platform_driver_register(&mxc_v4l2out_driver); + if (err == 0) { + platform_device_register(&mxc_v4l2out_device); + } + return err; +} + +/*! + * mxc v4l2 cleanup function + * + */ +static void mxc_v4l2out_clean(void) +{ + pr_debug("unregistering video\n"); + + video_unregister_device(g_vout->video_dev); + + platform_driver_unregister(&mxc_v4l2out_driver); + platform_device_unregister(&mxc_v4l2out_device); + kfree(g_vout); + g_vout = NULL; +} + +module_init(mxc_v4l2out_init); +module_exit(mxc_v4l2out_clean); + +module_param(video_nr, int, 0444); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2-driver for MXC video output"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c b/drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c new file mode 100644 index 000000000000..4a82da2d7d00 --- /dev/null +++ b/drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c @@ -0,0 +1,1926 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/media/video/mxc/output/mxc_v4l2_output.c + * + * @brief MXC V4L2 Video Output Driver + * + * Video4Linux2 Output Device using MXC IPU Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <asm/cacheflush.h> +#include <asm/io.h> +#include <asm/semaphore.h> +#include <linux/dma-mapping.h> + +#include <mach/mxcfb.h> +#include <mach/ipu.h> + +#include "mxc_v4l2_output.h" + +vout_data *g_vout; +#define SDC_FG_FB_FORMAT IPU_PIX_FMT_RGB565 + +struct v4l2_output mxc_outputs[2] = { + { + .index = MXC_V4L2_OUT_2_SDC, + .name = "DISP3 Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN}, + { + .index = MXC_V4L2_OUT_2_ADC, + .name = "DISPx Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN} +}; + +static int video_nr = 16; +static DEFINE_SPINLOCK(g_lock); +static unsigned int g_pp_out_number; +static unsigned int g_pp_in_number; + +/* debug counters */ +uint32_t g_irq_cnt; +uint32_t g_buf_output_cnt; +uint32_t g_buf_q_cnt; +uint32_t g_buf_dq_cnt; + +static inline uint32_t channel_2_dma(ipu_channel_t ch, ipu_buffer_t type) +{ + return ((type == IPU_INPUT_BUFFER) ? ((uint32_t) ch & 0xFF) : + ((type == IPU_OUTPUT_BUFFER) ? (((uint32_t) ch >> 8) & 0xFF) + : (((uint32_t) ch >> 16) & 0xFF))); +}; + +static inline uint32_t DMAParamAddr(uint32_t dma_ch) +{ + return (0x10000 | (dma_ch << 4)); +}; + +#define QUEUE_SIZE (MAX_FRAME_NUM + 1) +static inline int queue_size(v4l_queue * q) +{ + if (q->tail >= q->head) + return (q->tail - q->head); + else + return ((q->tail + QUEUE_SIZE) - q->head); +} + +static inline int queue_buf(v4l_queue * q, int idx) +{ + if (((q->tail + 1) % QUEUE_SIZE) == q->head) + return -1; /* queue full */ + q->list[q->tail] = idx; + q->tail = (q->tail + 1) % QUEUE_SIZE; + return 0; +} + +static inline int dequeue_buf(v4l_queue * q) +{ + int ret; + if (q->tail == q->head) + return -1; /* queue empty */ + ret = q->list[q->head]; + q->head = (q->head + 1) % QUEUE_SIZE; + return ret; +} + +static inline int peek_next_buf(v4l_queue * q) +{ + if (q->tail == q->head) + return -1; /* queue empty */ + return q->list[q->head]; +} + +static inline unsigned long get_jiffies(struct timeval *t) +{ + struct timeval cur; + + if (t->tv_usec >= 1000000) { + t->tv_sec += t->tv_usec / 1000000; + t->tv_usec = t->tv_usec % 1000000; + } + + do_gettimeofday(&cur); + if ((t->tv_sec < cur.tv_sec) + || ((t->tv_sec == cur.tv_sec) && (t->tv_usec < cur.tv_usec))) + return jiffies; + + if (t->tv_usec < cur.tv_usec) { + cur.tv_sec = t->tv_sec - cur.tv_sec - 1; + cur.tv_usec = t->tv_usec + 1000000 - cur.tv_usec; + } else { + cur.tv_sec = t->tv_sec - cur.tv_sec; + cur.tv_usec = t->tv_usec - cur.tv_usec; + } + + return jiffies + timeval_to_jiffies(&cur); +} + +/*! + * Private function to free buffers + * + * @param bufs_paddr Array of physical address of buffers to be freed + * + * @param bufs_vaddr Array of virtual address of buffers to be freed + * + * @param num_buf Number of buffers to be freed + * + * @param size Size for each buffer to be free + * + * @return status 0 success. + */ +static int mxc_free_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + if (bufs_vaddr[i] != 0) { + dma_free_coherent(0, size, bufs_vaddr[i], + bufs_paddr[i]); + pr_debug("freed @ paddr=0x%08X\n", (u32) bufs_paddr[i]); + bufs_paddr[i] = 0; + bufs_vaddr[i] = NULL; + } + } + return 0; +} + +/*! + * Private function to allocate buffers + * + * @param bufs_paddr Output array of physical address of buffers allocated + * + * @param bufs_vaddr Output array of virtual address of buffers allocated + * + * @param num_buf Input number of buffers to allocate + * + * @param size Input size for each buffer to allocate + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + bufs_vaddr[i] = dma_alloc_coherent(0, size, + &bufs_paddr[i], + GFP_DMA | GFP_KERNEL); + + if (bufs_vaddr[i] == 0) { + mxc_free_buffers(bufs_paddr, bufs_vaddr, i, size); + printk(KERN_ERR "dma_alloc_coherent failed.\n"); + return -ENOBUFS; + } + pr_debug("allocated @ paddr=0x%08X, size=%d.\n", + (u32) bufs_paddr[i], size); + } + + return 0; +} + +/* + * Returns bits per pixel for given pixel format + * + * @param pixelformat V4L2_PIX_FMT_RGB565, + * V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return bits per pixel of pixelformat + */ +static u32 fmt_to_bpp(u32 pixelformat) +{ + u32 bpp; + + switch (pixelformat) { + case V4L2_PIX_FMT_RGB565: + bpp = 16; + break; + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_RGB24: + bpp = 24; + break; + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_RGB32: + bpp = 32; + break; + default: + bpp = 8; + break; + } + return bpp; +} + +static u32 bpp_to_fmt(struct fb_info *fbi) +{ + if (fbi->var.nonstd) + return fbi->var.nonstd; + + if (fbi->var.bits_per_pixel == 24) + return V4L2_PIX_FMT_BGR24; + else if (fbi->var.bits_per_pixel == 32) + return V4L2_PIX_FMT_BGR32; + else if (fbi->var.bits_per_pixel == 16) + return V4L2_PIX_FMT_RGB565; + + return 0; +} + +static void mxc_v4l2out_timer_handler(unsigned long arg) +{ + int index; + unsigned long timeout; + unsigned long lock_flags = 0; + vout_data *vout = (vout_data *) arg; + + dev_dbg(vout->video_dev->dev, "timer handler: %lu\n", jiffies); + + spin_lock_irqsave(&g_lock, lock_flags); + + if ((vout->state == STATE_STREAM_STOPPING) + || (vout->state == STATE_STREAM_OFF)) + goto exit0; + /* + * If timer occurs before IPU h/w is ready, then set the state to + * paused and the timer will be set again when next buffer is queued + * or PP comletes + */ + if (vout->ipu_buf[0] != -1) { + dev_dbg(vout->video_dev->dev, "IPU buffer busy\n"); + vout->state = STATE_STREAM_PAUSED; + goto exit0; + } + + /* One frame buffer should be ready here */ + if (vout->frame_count % 2 == 1) { + /* set BUF0 rdy */ + if (ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 0) < + 0) + pr_debug("error selecting display buf 0"); + } else { + if (ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 1) < + 0) + pr_debug("error selecting display buf 1"); + } + + /* Dequeue buffer and pass to IPU */ + index = dequeue_buf(&vout->ready_q); + if (index == -1) { /* no buffers ready, should never occur */ + dev_err(vout->video_dev->dev, + "mxc_v4l2out: timer - no queued buffers ready\n"); + goto exit0; + } + + g_buf_dq_cnt++; + vout->frame_count++; + vout->ipu_buf[1] = vout->ipu_buf[0] = index; + + if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + 0, + vout->v4l2_bufs[vout->ipu_buf[0]].m. + offset) < 0) + goto exit0; + + if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + 1, + vout->v4l2_bufs[vout->ipu_buf[0]].m. + offset + vout->v2f.fmt.pix.width / 2) < 0) + goto exit0; + + /* All buffer should now ready in IPU out, tranfer to display buf */ + if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, + 0, + vout-> + display_bufs[(vout->frame_count - + 1) % 2]) < 0) { + dev_err(vout->video_dev->dev, + "unable to update buffer %d address\n", + vout->next_rdy_ipu_buf); + goto exit0; + } + if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, + 1, + vout-> + display_bufs[(vout->frame_count - + 1) % 2] + + vout->crop_current.width / 2 * + bytes_per_pixel(SDC_FG_FB_FORMAT)) < 0) { + dev_err(vout->video_dev->dev, + "unable to update buffer %d address\n", + vout->next_rdy_ipu_buf); + goto exit0; + } + + if (ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0) < 0) { + dev_err(vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + goto exit0; + } + + if (ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 0) < 0) { + dev_err(vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + goto exit0; + } + + /* Setup timer for next buffer */ + index = peek_next_buf(&vout->ready_q); + if (index != -1) { + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + dev_dbg(vout->video_dev->dev, + "warning: timer timeout already expired.\n"); + } + if (mod_timer(&vout->output_timer, timeout)) + dev_dbg(vout->video_dev->dev, + "warning: timer was already set\n"); + + dev_dbg(vout->video_dev->dev, + "timer handler next schedule: %lu\n", timeout); + } else { + vout->state = STATE_STREAM_PAUSED; + } + +exit0: + spin_unlock_irqrestore(&g_lock, lock_flags); +} + +extern void _ipu_write_param_mem(uint32_t addr, uint32_t *data, + uint32_t numWords); + +static irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id) +{ + unsigned long lock_flags = 0; + vout_data *vout = dev_id; + uint32_t u_offset; + uint32_t v_offset; + uint32_t local_params[4]; + uint32_t width, height; + uint32_t dma_chan; + + spin_lock_irqsave(&g_lock, lock_flags); + g_irq_cnt++; + + dma_chan = channel_2_dma(vout->post_proc_ch, IPU_INPUT_BUFFER); + memset(&local_params, 0, sizeof(local_params)); + + if (g_pp_in_number % 2 == 1) { + u_offset = vout->offset.u_offset - vout->v2f.fmt.pix.width / 4; + v_offset = vout->offset.v_offset - vout->v2f.fmt.pix.width / 4; + width = vout->v2f.fmt.pix.width / 2; + height = vout->v2f.fmt.pix.height; + local_params[3] = + (uint32_t) ((width - 1) << 12) | ((uint32_t) (height - + 1) << 24); + local_params[1] = (1UL << (46 - 32)) | (u_offset << (53 - 32)); + local_params[2] = u_offset >> (64 - 53); + local_params[2] |= v_offset << (79 - 64); + local_params[3] |= v_offset >> (96 - 79); + _ipu_write_param_mem(DMAParamAddr(dma_chan), local_params, 4); + + if (ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 1) < + 0) { + dev_err(vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + } + } else { + u_offset = vout->offset.u_offset; + v_offset = vout->offset.v_offset; + width = vout->v2f.fmt.pix.width / 2; + height = vout->v2f.fmt.pix.height; + local_params[3] = + (uint32_t) ((width - 1) << 12) | ((uint32_t) (height - + 1) << 24); + local_params[1] = (1UL << (46 - 32)) | (u_offset << (53 - 32)); + local_params[2] = u_offset >> (64 - 53); + local_params[2] |= v_offset << (79 - 64); + local_params[3] |= v_offset >> (96 - 79); + _ipu_write_param_mem(DMAParamAddr(dma_chan), local_params, 4); + } + g_pp_in_number++; + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return IRQ_HANDLED; +} + +static irqreturn_t mxc_v4l2out_pp_out_irq_handler(int irq, void *dev_id) +{ + vout_data *vout = dev_id; + int index; + unsigned long timeout; + u32 lock_flags = 0; + + spin_lock_irqsave(&g_lock, lock_flags); + + if (g_pp_out_number % 2 == 1) { + if (ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 1) + < 0) { + dev_err(vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + } + } else { + if (vout->ipu_buf[0] != -1) { + vout->v4l2_bufs[vout->ipu_buf[0]].flags = + V4L2_BUF_FLAG_DONE; + queue_buf(&vout->done_q, vout->ipu_buf[0]); + wake_up_interruptible(&vout->v4l_bufq); + vout->ipu_buf[0] = -1; + } + if (vout->state == STATE_STREAM_STOPPING) { + if ((vout->ipu_buf[0] == -1) + && (vout->ipu_buf[1] == -1)) + vout->state = STATE_STREAM_OFF; + } else if ((vout->state == STATE_STREAM_PAUSED) + && ((index = peek_next_buf(&vout->ready_q)) != -1)) { + /*! + * Setup timer for next buffer, + * when stream has been paused + */ + pr_debug("next index %d\n", index); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index]. + timestamp); + + if (jiffies >= timeout) { + pr_debug + ("warning: timer timeout" + "already expired.\n"); + } + + vout->state = STATE_STREAM_ON; + + if (mod_timer(&vout->output_timer, timeout)) + pr_debug("warning: timer was already set\n"); + + pr_debug("timer handler next schedule: %lu\n", timeout); + } + } + g_pp_out_number++; + + spin_unlock_irqrestore(&g_lock, lock_flags); + return IRQ_HANDLED; +} + +/*! + * Start the output stream + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamon(vout_data *vout) +{ + struct device *dev = vout->video_dev->dev; + ipu_channel_params_t params; + struct mxcfb_pos fb_pos; + struct fb_var_screeninfo fbvar; + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + int pp_in_buf[2]; + u16 out_width; + u16 out_height; + ipu_channel_t display_input_ch = MEM_PP_MEM; + bool use_direct_adc = false; + mm_segment_t old_fs; + + if (!vout) + return -EINVAL; + + if (vout->state != STATE_STREAM_OFF) + return -EBUSY; + + if (queue_size(&vout->ready_q) < 2) { + dev_err(dev, "2 buffers not been queued yet!\n"); + return -EINVAL; + } + + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + + vout->next_done_ipu_buf = vout->next_rdy_ipu_buf = 0; + vout->ipu_buf[0] = pp_in_buf[0] = dequeue_buf(&vout->ready_q); + vout->ipu_buf[1] = pp_in_buf[1] = vout->ipu_buf[0]; + vout->frame_count = 1; + g_pp_out_number = 1; + g_pp_in_number = 1; + + ipu_enable_irq(IPU_IRQ_PP_IN_EOF); + ipu_enable_irq(IPU_IRQ_PP_OUT_EOF); + + /* Init Display Channel */ +#ifdef CONFIG_FB_MXC_ASYNC_PANEL + if (vout->cur_disp_output < DISP3) { + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, 0); + fbi = NULL; + if (ipu_can_rotate_in_place(vout->rotate)) { + dev_dbg(dev, "Using PP direct to ADC channel\n"); + use_direct_adc = true; + vout->display_ch = MEM_PP_ADC; + vout->post_proc_ch = MEM_PP_ADC; + + memset(¶ms, 0, sizeof(params)); + params.mem_pp_adc.in_width = vout->v2f.fmt.pix.width; + params.mem_pp_adc.in_height = vout->v2f.fmt.pix.height; + params.mem_pp_adc.in_pixel_fmt = + vout->v2f.fmt.pix.pixelformat; + params.mem_pp_adc.out_width = out_width; + params.mem_pp_adc.out_height = out_height; + params.mem_pp_adc.out_pixel_fmt = SDC_FG_FB_FORMAT; +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.mem_pp_adc.out_left = + 2 + vout->crop_current.left; +#else + params.mem_pp_adc.out_left = + 12 + vout->crop_current.left; +#endif + params.mem_pp_adc.out_top = vout->crop_current.top; + if (ipu_init_channel( + vout->post_proc_ch, ¶ms) != 0) { + dev_err(dev, "Error initializing PP chan\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + params.mem_pp_adc. + in_pixel_fmt, + params.mem_pp_adc.in_width, + params.mem_pp_adc.in_height, + vout->v2f.fmt.pix. + bytesperline / + bytes_per_pixel(params. + mem_pp_adc. + in_pixel_fmt), + vout->rotate, + vout-> + v4l2_bufs[pp_in_buf[0]].m. + offset, + vout-> + v4l2_bufs[pp_in_buf[1]].m. + offset, + vout->offset.u_offset, + vout->offset.v_offset) != + 0) { + dev_err(dev, "Error initializing PP in buf\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_adc. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, 0, 0, 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP" + "output buffer\n"); + return -EINVAL; + } + + } else { + dev_dbg(dev, "Using ADC SYS2 channel\n"); + vout->display_ch = ADC_SYS2; + vout->post_proc_ch = MEM_PP_MEM; + + if (vout->display_bufs[0]) { + mxc_free_buffers(vout->display_bufs, + vout->display_bufs_vaddr, + 2, vout->display_buf_size); + } + + vout->display_buf_size = vout->crop_current.width * + vout->crop_current.height * + fmt_to_bpp(SDC_FG_FB_FORMAT) / 8; + mxc_allocate_buffers(vout->display_bufs, + vout->display_bufs_vaddr, + 2, vout->display_buf_size); + + memset(¶ms, 0, sizeof(params)); + params.adc_sys2.disp = vout->cur_disp_output; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.adc_sys2.out_left = 2 + vout->crop_current.left; +#else + params.adc_sys2.out_left = 12 + vout->crop_current.left; +#endif + params.adc_sys2.out_top = vout->crop_current.top; + if (ipu_init_channel(ADC_SYS2, ¶ms) < 0) + return -EINVAL; + + if (ipu_init_channel_buffer(vout->display_ch, + IPU_INPUT_BUFFER, + SDC_FG_FB_FORMAT, + out_width, out_height, + out_width, IPU_ROTATE_NONE, + vout->display_bufs[0], + vout->display_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing SDC FG buffer\n"); + return -EINVAL; + } + } + } else +#endif + { /* Use SDC */ + dev_dbg(dev, "Using SDC channel\n"); + + fbvar = fbi->var; + if (vout->cur_disp_output == 3) { + vout->display_ch = MEM_FG_SYNC; + fbvar.bits_per_pixel = 16; + fbvar.nonstd = IPU_PIX_FMT_UYVY; + + fbvar.xres = fbvar.xres_virtual = out_width; + fbvar.yres = out_height; + fbvar.yres_virtual = out_height * 2; + } else if (vout->cur_disp_output == 5) { + vout->display_ch = MEM_DC_SYNC; + fbvar.bits_per_pixel = 16; + fbvar.nonstd = IPU_PIX_FMT_UYVY; + + fbvar.xres = fbvar.xres_virtual = out_width; + fbvar.yres = out_height; + fbvar.yres_virtual = out_height * 2; + } else { + vout->display_ch = MEM_BG_SYNC; + } + + fbvar.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbvar); + + fb_pos.x = vout->crop_current.left; + fb_pos.y = vout->crop_current.top; + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS, + (unsigned long)&fb_pos); + set_fs(old_fs); + } + + vout->display_bufs[1] = fbi->fix.smem_start; + vout->display_bufs[0] = fbi->fix.smem_start + + (fbi->fix.line_length * fbi->var.yres); + vout->display_buf_size = vout->crop_current.width * + vout->crop_current.height * fbi->var.bits_per_pixel / 8; + + vout->post_proc_ch = MEM_PP_MEM; + } + + /* Init PP */ + if (use_direct_adc == false) { + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.height; + out_height = vout->crop_current.width; + } + memset(¶ms, 0, sizeof(params)); + params.mem_pp_mem.in_width = vout->v2f.fmt.pix.width / 2; + params.mem_pp_mem.in_height = vout->v2f.fmt.pix.height; + params.mem_pp_mem.in_pixel_fmt = vout->v2f.fmt.pix.pixelformat; + params.mem_pp_mem.out_width = out_width / 2; + params.mem_pp_mem.out_height = out_height; + if (vout->display_ch == ADC_SYS2) + params.mem_pp_mem.out_pixel_fmt = SDC_FG_FB_FORMAT; + else + params.mem_pp_mem.out_pixel_fmt = bpp_to_fmt(fbi); + if (ipu_init_channel(vout->post_proc_ch, ¶ms) != 0) { + dev_err(dev, "Error initializing PP channel\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + params.mem_pp_mem.in_pixel_fmt, + params.mem_pp_mem.in_width, + params.mem_pp_mem.in_height, + vout->v2f.fmt.pix.bytesperline / + bytes_per_pixel(params.mem_pp_mem. + in_pixel_fmt), + IPU_ROTATE_NONE, + vout->v4l2_bufs[pp_in_buf[0]].m. + offset, + vout->v4l2_bufs[pp_in_buf[0]].m. + offset + params.mem_pp_mem.in_width, + vout->offset.u_offset, + vout->offset.v_offset) != 0) { + dev_err(dev, "Error initializing PP input buffer\n"); + return -EINVAL; + } + + if (!ipu_can_rotate_in_place(vout->rotate)) { + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + if (mxc_allocate_buffers + (vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size) < 0) + return -ENOBUFS; + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing" + "PP output buffer\n"); + return -EINVAL; + } + + if (ipu_init_channel(MEM_ROT_PP_MEM, NULL) != 0) { + dev_err(dev, + "Error initializing PP ROT channel\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(MEM_ROT_PP_MEM, + IPU_INPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP ROT" + "input buffer\n"); + return -EINVAL; + } + + /* swap width and height */ + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + } + + if (ipu_init_channel_buffer(MEM_ROT_PP_MEM, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->display_bufs[0], + vout->display_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP" + "output buffer\n"); + return -EINVAL; + } + + if (ipu_link_channels(vout->post_proc_ch, + MEM_ROT_PP_MEM) < 0) + return -EINVAL; + + ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 1); + + ipu_enable_channel(MEM_ROT_PP_MEM); + + display_input_ch = MEM_ROT_PP_MEM; + } else { + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, + out_width / 2, + out_height, + out_width, + vout->rotate, + vout->display_bufs[0], + vout->display_bufs[0] + + + out_width / 2 * + bytes_per_pixel + (SDC_FG_FB_FORMAT), 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP" + "output buffer\n"); + return -EINVAL; + } + } + if (ipu_unlink_channels( + display_input_ch, vout->display_ch) < 0) { + dev_err(dev, "Error linking ipu channels\n"); + return -EINVAL; + } + } + + vout->state = STATE_STREAM_PAUSED; + + ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0); + + if (use_direct_adc == false) { + ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 0); + ipu_enable_channel(vout->post_proc_ch); + + if (fbi) { + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_UNBLANK); + release_console_sem(); + } else { + ipu_enable_channel(vout->display_ch); + } + } else { + ipu_enable_channel(vout->post_proc_ch); + } + + vout->start_jiffies = jiffies; + dev_dbg(dev, + "streamon: start time = %lu jiffies\n", vout->start_jiffies); + + return 0; +} + +/*! + * Shut down the voutera + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamoff(vout_data *vout) +{ + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + int i, retval = 0; + unsigned long lockflag = 0; + + if (!vout) + return -EINVAL; + + if (vout->state == STATE_STREAM_OFF) + return 0; + + spin_lock_irqsave(&g_lock, lockflag); + + del_timer(&vout->output_timer); + + if (vout->state == STATE_STREAM_ON) + vout->state = STATE_STREAM_STOPPING; + + ipu_disable_irq(IPU_IRQ_PP_IN_EOF); + ipu_disable_irq(IPU_IRQ_PP_OUT_EOF); + + spin_unlock_irqrestore(&g_lock, lockflag); + + if (vout->post_proc_ch == MEM_PP_MEM) { /* SDC or ADC with Rotation */ + if (!ipu_can_rotate_in_place(vout->rotate)) { + ipu_unlink_channels(MEM_PP_MEM, MEM_ROT_PP_MEM); + ipu_unlink_channels(MEM_ROT_PP_MEM, vout->display_ch); + ipu_disable_channel(MEM_ROT_PP_MEM, true); + + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + } else { + ipu_unlink_channels(MEM_PP_MEM, vout->display_ch); + } + ipu_disable_channel(MEM_PP_MEM, true); + + if (vout->display_ch == ADC_SYS2) { + ipu_disable_channel(vout->display_ch, true); + ipu_uninit_channel(vout->display_ch); + } else { + fbi->var.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbi->var); + + if (vout->display_ch == MEM_FG_SYNC) { + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_POWERDOWN); + release_console_sem(); + } + + vout->display_bufs[0] = 0; + vout->display_bufs[1] = 0; + } + + ipu_uninit_channel(MEM_PP_MEM); + if (!ipu_can_rotate_in_place(vout->rotate)) + ipu_uninit_channel(MEM_ROT_PP_MEM); + } else { /* ADC Direct */ + ipu_disable_channel(MEM_PP_ADC, true); + ipu_uninit_channel(MEM_PP_ADC); + } + vout->ready_q.head = vout->ready_q.tail = 0; + vout->done_q.head = vout->done_q.tail = 0; + for (i = 0; i < vout->buffer_cnt; i++) { + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + + vout->state = STATE_STREAM_OFF; + +#ifdef CONFIG_FB_MXC_ASYNC_PANEL + if (vout->cur_disp_output < DISP3) { + if (vout->display_bufs[0] != 0) { + mxc_free_buffers(vout->display_bufs, + vout->display_bufs_vaddr, 2, + vout->display_buf_size); + } + + mxcfb_set_refresh_mode(registered_fb + [vout-> + output_fb_num[vout->cur_disp_output]], + MXCFB_REFRESH_PARTIAL, 0); + } +#endif + + return retval; +} + +/* + * Valid whether the palette is supported + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 1 if supported, 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return ((palette == V4L2_PIX_FMT_RGB565) || + (palette == V4L2_PIX_FMT_BGR24) || + (palette == V4L2_PIX_FMT_RGB24) || + (palette == V4L2_PIX_FMT_BGR32) || + (palette == V4L2_PIX_FMT_RGB32) || + (palette == V4L2_PIX_FMT_NV12) || + (palette == V4L2_PIX_FMT_YUV422P) || + (palette == V4L2_PIX_FMT_YUV420)); +} + +/* + * V4L2 - Handles VIDIOC_G_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_g_fmt(vout_data *vout, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + *f = vout->v2f; + return 0; +} + +/* + * V4L2 - Handles VIDIOC_S_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_s_fmt(vout_data *vout, struct v4l2_format *f) +{ + int retval = 0; + u32 size = 0; + u32 bytesperline; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + goto err0; + } + if (!valid_mode(f->fmt.pix.pixelformat)) { + dev_err(vout->video_dev->dev, "pixel format not supported\n"); + retval = -EINVAL; + goto err0; + } + + bytesperline = (f->fmt.pix.width * fmt_to_bpp(f->fmt.pix.pixelformat)) / + 8; + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV422P: + /* byteperline for YUV planar formats is for + Y plane only */ + size = bytesperline * f->fmt.pix.height * 2; + break; + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_NV12: + size = (bytesperline * f->fmt.pix.height * 3) / 2; + break; + default: + size = bytesperline * f->fmt.pix.height; + break; + } + + /* Return the actual size of the image to the app */ + if (f->fmt.pix.sizeimage < size) + f->fmt.pix.sizeimage = size; + else + size = f->fmt.pix.sizeimage; + + vout->v2f.fmt.pix = f->fmt.pix; + if (vout->v2f.fmt.pix.priv != 0) { + if (copy_from_user(&vout->offset, + (void *)vout->v2f.fmt.pix.priv, + sizeof(vout->offset))) { + retval = -EFAULT; + goto err0; + } + } else { + vout->offset.u_offset = 0; + vout->offset.v_offset = 0; + } + + retval = 0; +err0: + return retval; +} + +/* + * V4L2 - Handles VIDIOC_G_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42lout_control(vout_data *vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + return (vout->rotate & IPU_ROTATE_HORIZ_FLIP) ? 1 : 0; + case V4L2_CID_VFLIP: + return (vout->rotate & IPU_ROTATE_VERT_FLIP) ? 1 : 0; + case (V4L2_CID_PRIVATE_BASE + 1): + return vout->rotate; + default: + return -EINVAL; + } +} + +/* + * V4L2 - Handles VIDIOC_S_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42lout_control(vout_data *vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + vout->rotate |= c->value ? IPU_ROTATE_HORIZ_FLIP : + IPU_ROTATE_NONE; + break; + case V4L2_CID_VFLIP: + vout->rotate |= c->value ? IPU_ROTATE_VERT_FLIP : + IPU_ROTATE_NONE; + break; + case V4L2_CID_MXC_ROT: + vout->rotate = c->value; + break; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 interface - open function + * + * @param inode structure inode * + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l2out_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + int err; + + if (!vout) + return -ENODEV; + + down(&vout->busy_lock); + + err = -EINTR; + if (signal_pending(current)) + goto oops; + + if (vout->open_count++ == 0) { + ipu_request_irq(IPU_IRQ_PP_IN_EOF, + mxc_v4l2out_pp_in_irq_handler, + 0, dev->name, vout); + ipu_request_irq(IPU_IRQ_PP_OUT_EOF, + mxc_v4l2out_pp_out_irq_handler, + 0, dev->name, vout); + + init_waitqueue_head(&vout->v4l_bufq); + + init_timer(&vout->output_timer); + vout->output_timer.function = mxc_v4l2out_timer_handler; + vout->output_timer.data = (unsigned long)vout; + + vout->state = STATE_STREAM_OFF; + vout->rotate = IPU_ROTATE_NONE; + g_irq_cnt = g_buf_output_cnt = g_buf_q_cnt = g_buf_dq_cnt = 0; + + } + + file->private_data = dev; + + up(&vout->busy_lock); + + return 0; + +oops: + up(&vout->busy_lock); + return err; +} + +/*! + * V4L2 interface - close function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l2out_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + + if (--vout->open_count == 0) { + if (vout->state != STATE_STREAM_OFF) + mxc_v4l2out_streamoff(vout); + + ipu_free_irq(IPU_IRQ_PP_IN_EOF, vout); + ipu_free_irq(IPU_IRQ_PP_OUT_EOF, vout); + + file->private_data = NULL; + + mxc_free_buffers(vout->queue_buf_paddr, vout->queue_buf_vaddr, + vout->buffer_cnt, vout->queue_buf_size); + vout->buffer_cnt = 0; + mxc_free_buffers(vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + + /* capture off */ + wake_up_interruptible(&vout->v4l_bufq); + } + + return 0; +} + +/*! + * V4L2 interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int +mxc_v4l2out_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *vdev = file->private_data; + vout_data *vout = video_get_drvdata(vdev); + int retval = 0; + int i = 0; + + if (!vout) + return -EBADF; + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2_output"); + cap->version = 0; + cap->capabilities = + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + case VIDIOC_G_FMT: + { + struct v4l2_format *gf = arg; + retval = mxc_v4l2out_g_fmt(vout, gf); + break; + } + case VIDIOC_S_FMT: + { + struct v4l2_format *sf = arg; + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + retval = mxc_v4l2out_s_fmt(vout, sf); + break; + } + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *req = arg; + if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (req->memory != V4L2_MEMORY_MMAP)) { + dev_dbg(vdev->dev, + "VIDIOC_REQBUFS: incorrect" + "buffer type\n"); + retval = -EINVAL; + break; + } + + if (req->count == 0) + mxc_v4l2out_streamoff(vout); + + if (vout->state == STATE_STREAM_OFF) { + if (vout->queue_buf_paddr[0] != 0) { + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + dev_dbg(vdev->dev, + "VIDIOC_REQBUFS:" + "freed buffers\n"); + } + vout->buffer_cnt = 0; + } else { + dev_dbg(vdev->dev, + "VIDIOC_REQBUFS: Buffer is in use\n"); + retval = -EBUSY; + break; + } + + if (req->count == 0) + break; + + if (req->count < MIN_FRAME_NUM) + req->count = MIN_FRAME_NUM; + else if (req->count > MAX_FRAME_NUM) + req->count = MAX_FRAME_NUM; + vout->buffer_cnt = req->count; + vout->queue_buf_size = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + + retval = mxc_allocate_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + if (retval < 0) + break; + + /* Init buffer queues */ + vout->done_q.head = 0; + vout->done_q.tail = 0; + vout->ready_q.head = 0; + vout->ready_q.tail = 0; + + for (i = 0; i < vout->buffer_cnt; i++) { + memset(&(vout->v4l2_bufs[i]), 0, + sizeof(vout->v4l2_bufs[i])); + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].memory = V4L2_MEMORY_MMAP; + vout->v4l2_bufs[i].index = i; + vout->v4l2_bufs[i].type = + V4L2_BUF_TYPE_VIDEO_OUTPUT; + vout->v4l2_bufs[i].length = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + vout->v4l2_bufs[i].m.offset = + (unsigned long)vout->queue_buf_paddr[i]; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + break; + } + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *buf = arg; + u32 type = buf->type; + int index = buf->index; + + if ((type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + dev_dbg(vdev->dev, + "VIDIOC_QUERYBUFS: incorrect" + "buffer type\n"); + retval = -EINVAL; + break; + } + down(&vout->param_lock); + memcpy(buf, &(vout->v4l2_bufs[index]), sizeof(*buf)); + up(&vout->param_lock); + break; + } + case VIDIOC_QBUF: + { + struct v4l2_buffer *buf = arg; + int index = buf->index; + unsigned long lock_flags; + + if ((buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + retval = -EINVAL; + break; + } + + dev_dbg(vdev->dev, "VIDIOC_QBUF: %d\n", buf->index); + + /* mmapped buffers are L1 WB cached, + * so we need to clean them */ + if (buf->flags & V4L2_BUF_FLAG_MAPPED) + flush_cache_all(); + + spin_lock_irqsave(&g_lock, lock_flags); + + memcpy(&(vout->v4l2_bufs[index]), buf, sizeof(*buf)); + vout->v4l2_bufs[index].flags |= V4L2_BUF_FLAG_QUEUED; + + g_buf_q_cnt++; + queue_buf(&vout->ready_q, index); + if (vout->state == STATE_STREAM_PAUSED) { + unsigned long timeout; + + index = peek_next_buf(&vout->ready_q); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == + 0) + && (vout->v4l2_bufs[index].timestamp. + tv_usec == 0)) + timeout = + vout->start_jiffies + + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index]. + timestamp); + + if (jiffies >= timeout) { + dev_dbg(vout->video_dev->dev, + "warning: timer timeout" + "already expired.\n"); + } + vout->output_timer.expires = timeout; + dev_dbg(vdev->dev, + "QBUF: frame #%u timeout @" + " %lu jiffies, current = %lu\n", + vout->frame_count, timeout, jiffies); + add_timer(&vout->output_timer); + vout->state = STATE_STREAM_ON; + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + case VIDIOC_DQBUF: + { + struct v4l2_buffer *buf = arg; + int idx; + + if ((queue_size(&vout->done_q) == 0) && + (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + break; + } + + if (!wait_event_interruptible_timeout(vout->v4l_bufq, + queue_size(&vout-> + done_q) + != 0, 10 * HZ)) { + dev_dbg(vdev->dev, "VIDIOC_DQBUF: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + dev_dbg(vdev->dev, + "VIDIOC_DQBUF: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + idx = dequeue_buf(&vout->done_q); + if (idx == -1) { /* No frame free */ + dev_dbg(vdev->dev, + "VIDIOC_DQBUF: no free buffers\n"); + retval = -EAGAIN; + break; + } + if ((vout->v4l2_bufs[idx].flags & V4L2_BUF_FLAG_DONE) == + 0) + dev_dbg(vdev->dev, + "VIDIOC_DQBUF: buffer in done q, " + "but not flagged as done\n"); + + vout->v4l2_bufs[idx].flags = 0; + memcpy(buf, &(vout->v4l2_bufs[idx]), sizeof(*buf)); + dev_dbg(vdev->dev, "VIDIOC_DQBUF: %d\n", buf->index); + break; + } + case VIDIOC_STREAMON: + { + retval = mxc_v4l2out_streamon(vout); + break; + } + case VIDIOC_STREAMOFF: + { + retval = mxc_v4l2out_streamoff(vout); + break; + } + case VIDIOC_G_CTRL: + { + retval = mxc_get_v42lout_control(vout, arg); + break; + } + case VIDIOC_S_CTRL: + { + retval = mxc_set_v42lout_control(vout, arg); + break; + } + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + + cap->bounds = vout->crop_bounds[vout->cur_disp_output]; + cap->defrect = vout->crop_bounds[vout->cur_disp_output]; + retval = 0; + break; + } + case VIDIOC_G_CROP: + { + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + crop->c = vout->crop_current; + break; + } + case VIDIOC_S_CROP: + { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = + &(vout->crop_bounds[vout->cur_disp_output]); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + if (crop->c.height < 0) { + retval = -EINVAL; + break; + } + if (crop->c.width < 0) { + retval = -EINVAL; + break; + } + + /* only full screen supported for SDC BG */ + if (vout->cur_disp_output == 4) { + crop->c = vout->crop_current; + break; + } + + if (crop->c.top < b->top) + crop->c.top = b->top; + if (crop->c.top >= b->top + b->height) + crop->c.top = b->top + b->height - 1; + if (crop->c.height > b->top - crop->c.top + b->height) + crop->c.height = + b->top - crop->c.top + b->height; + + if (crop->c.left < b->left) + crop->c.left = b->left; + if (crop->c.left >= b->left + b->width) + crop->c.left = b->left + b->width - 1; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + /* stride line limitation */ + crop->c.height -= crop->c.height % 8; + crop->c.width -= crop->c.width % 8; + + vout->crop_current = crop->c; + break; + } + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if ((output->index >= 5) || + (vout->output_enabled[output->index] == false)) { + retval = -EINVAL; + break; + } + + if (output->index < 3) { + *output = mxc_outputs[MXC_V4L2_OUT_2_ADC]; + output->name[4] = '0' + output->index; + } else { + *output = mxc_outputs[MXC_V4L2_OUT_2_SDC]; + } + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = vout->cur_disp_output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + int fbnum; + struct v4l2_rect *b; + + if ((*p_output_num >= MXC_V4L2_OUT_NUM_OUTPUTS) || + (vout->output_enabled[*p_output_num] == false)) { + retval = -EINVAL; + break; + } + + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + + vout->cur_disp_output = *p_output_num; + + /* Update bounds in case they have changed */ + b = &vout->crop_bounds[vout->cur_disp_output]; + + fbnum = vout->output_fb_num[vout->cur_disp_output]; + if (vout->cur_disp_output == 3) + fbnum = vout->output_fb_num[4]; + + b->width = registered_fb[fbnum]->var.xres; + b->height = registered_fb[fbnum]->var.yres; + + vout->crop_current = *b; + break; + } + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_PARM: + case VIDIOC_ENUMSTD: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&vout->busy_lock); + return retval; +} + +/* + * V4L2 interface - ioctl function + * + * @return None + */ +static int +mxc_v4l2out_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, mxc_v4l2out_do_ioctl); +} + +/*! + * V4L2 interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error + */ +static int mxc_v4l2out_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *vdev = video_devdata(file); + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + int i; + vout_data *vout = video_get_drvdata(vdev); + + dev_dbg(vdev->dev, "pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + for (i = 0; i < vout->buffer_cnt; i++) { + if ((vout->v4l2_bufs[i].m.offset == + (vma->vm_pgoff << PAGE_SHIFT)) && + (vout->v4l2_bufs[i].length >= size)) { + vout->v4l2_bufs[i].flags |= V4L2_BUF_FLAG_MAPPED; + break; + } + } + if (i == vout->buffer_cnt) { + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + /* make buffers inner write-back, outer write-thru cacheable */ + vma->vm_page_prot = pgprot_outer_wrthru(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + dev_dbg(vdev->dev, "mmap remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + +mxc_mmap_exit: + up(&vout->busy_lock); + return res; +} + +/*! + * V4L2 interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_v4l2out_poll(struct file *file, poll_table * wait) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + queue = &vout->v4l_bufq; + poll_wait(file, queue, wait); + + up(&vout->busy_lock); + return res; +} + +static struct +file_operations mxc_v4l2out_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l2out_open, + .release = mxc_v4l2out_close, + .ioctl = mxc_v4l2out_ioctl, + .mmap = mxc_v4l2out_mmap, + .poll = mxc_v4l2out_poll, +}; + +static struct video_device mxc_v4l2out_template = { + .owner = THIS_MODULE, + .name = "MXC Video Output", + .type = 0, + .type2 = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING, + .fops = &mxc_v4l2out_fops, + .release = video_device_release, +}; + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxc_v4l2out_probe(struct platform_device *pdev) +{ + int i; + vout_data *vout; + + /* + * Allocate sufficient memory for the fb structure + */ + g_vout = vout = kmalloc(sizeof(vout_data), GFP_KERNEL); + + if (!vout) + return 0; + + memset(vout, 0, sizeof(vout_data)); + + vout->video_dev = video_device_alloc(); + if (vout->video_dev == NULL) + return -1; + vout->video_dev->dev = &pdev->dev; + vout->video_dev->minor = -1; + + *(vout->video_dev) = mxc_v4l2out_template; + + /* register v4l device */ + if (video_register_device(vout->video_dev, + VFL_TYPE_GRABBER, video_nr) == -1) { + dev_dbg(&pdev->dev, "video_register_device failed\n"); + return 0; + } + dev_info(&pdev->dev, "Registered device video%d\n", + vout->video_dev->minor & 0x1f); + vout->video_dev->dev = &pdev->dev; + + video_set_drvdata(vout->video_dev, vout); + + init_MUTEX(&vout->param_lock); + init_MUTEX(&vout->busy_lock); + + /* setup outputs and cropping */ + vout->cur_disp_output = -1; + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strncmp(idstr, "DISP", 4) == 0) { + int disp_num = idstr[4] - '0'; + if (disp_num == 3) { + if (strcmp(idstr, "DISP3 BG - DI1") == 0) + disp_num = 5; + else if (strncmp(idstr, "DISP3 BG", 8) == 0) + disp_num = 4; + } + vout->crop_bounds[disp_num].left = 0; + vout->crop_bounds[disp_num].top = 0; + vout->crop_bounds[disp_num].width = + registered_fb[i]->var.xres; + vout->crop_bounds[disp_num].height = + registered_fb[i]->var.yres; + vout->output_enabled[disp_num] = true; + vout->output_fb_num[disp_num] = i; + if (vout->cur_disp_output == -1) + vout->cur_disp_output = disp_num; + } + + } + vout->crop_current = vout->crop_bounds[vout->cur_disp_output]; + + platform_set_drvdata(pdev, vout); + + return 0; +} + +static int mxc_v4l2out_remove(struct platform_device *pdev) +{ + vout_data *vout = platform_get_drvdata(pdev); + + if (vout->video_dev) { + if (-1 != vout->video_dev->minor) + video_unregister_device(vout->video_dev); + else + video_device_release(vout->video_dev); + vout->video_dev = NULL; + } + + platform_set_drvdata(pdev, NULL); + + kfree(vout); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2out_driver = { + .driver = { + .name = "MXC Video Output", + }, + .probe = mxc_v4l2out_probe, + .remove = mxc_v4l2out_remove, +}; + +static struct platform_device mxc_v4l2out_device = { + .name = "MXC Video Output", + .id = 0, +}; + +/*! + * mxc v4l2 init function + * + */ +static int mxc_v4l2out_init(void) +{ + u8 err = 0; + + err = platform_driver_register(&mxc_v4l2out_driver); + if (err == 0) + platform_device_register(&mxc_v4l2out_device); + return err; +} + +/*! + * mxc v4l2 cleanup function + * + */ +static void mxc_v4l2out_clean(void) +{ + video_unregister_device(g_vout->video_dev); + + platform_driver_unregister(&mxc_v4l2out_driver); + platform_device_unregister(&mxc_v4l2out_device); + kfree(g_vout); + g_vout = NULL; +} + +module_init(mxc_v4l2out_init); +module_exit(mxc_v4l2out_clean); + +module_param(video_nr, int, 0444); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2-driver for MXC video output"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/output/mxc_v4l2_output.c b/drivers/media/video/mxc/output/mxc_v4l2_output.c new file mode 100644 index 000000000000..4fa4a641ab62 --- /dev/null +++ b/drivers/media/video/mxc/output/mxc_v4l2_output.c @@ -0,0 +1,2405 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/media/video/mxc/output/mxc_v4l2_output.c + * + * @brief MXC V4L2 Video Output Driver + * + * Video4Linux2 Output Device using MXC IPU Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/semaphore.h> +#include <linux/console.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/mxcfb.h> +#include <media/v4l2-ioctl.h> +#include <asm/cacheflush.h> + +#include "mxc_v4l2_output.h" + +vout_data *g_vout; +#define INTERLACED_CONTENT(vout) ((cpu_is_mx51_rev(CHIP_REV_2_0) >= 1) && \ + (((vout)->field_fmt == V4L2_FIELD_INTERLACED_TB) || \ + ((vout)->field_fmt == V4L2_FIELD_INTERLACED_BT))) +#define LOAD_3FIELDS(vout) ((INTERLACED_CONTENT(vout)) && \ + ((vout)->motion_sel != HIGH_MOTION)) + +#define SDC_FG_FB_FORMAT IPU_PIX_FMT_RGB565 + +struct v4l2_output mxc_outputs[2] = { + { + .index = MXC_V4L2_OUT_2_SDC, + .name = "DISP3 Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN}, + { + .index = MXC_V4L2_OUT_2_ADC, + .name = "DISPx Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN} +}; + +static int video_nr = 16; +static int pending_buffer; +static int pp_eof; +static spinlock_t g_lock = SPIN_LOCK_UNLOCKED; +static int last_index_n; +static int last_index_c; + +/* debug counters */ +uint32_t g_irq_cnt; +uint32_t g_buf_output_cnt; +uint32_t g_buf_q_cnt; +uint32_t g_buf_dq_cnt; + +#define QUEUE_SIZE (MAX_FRAME_NUM + 1) +static __inline int queue_size(v4l_queue * q) +{ + if (q->tail >= q->head) + return (q->tail - q->head); + else + return ((q->tail + QUEUE_SIZE) - q->head); +} + +static __inline int queue_buf(v4l_queue * q, int idx) +{ + if (((q->tail + 1) % QUEUE_SIZE) == q->head) + return -1; /* queue full */ + q->list[q->tail] = idx; + q->tail = (q->tail + 1) % QUEUE_SIZE; + return 0; +} + +static __inline int dequeue_buf(v4l_queue * q) +{ + int ret; + if (q->tail == q->head) + return -1; /* queue empty */ + ret = q->list[q->head]; + q->head = (q->head + 1) % QUEUE_SIZE; + return ret; +} + +static __inline int peek_next_buf(v4l_queue * q) +{ + if (q->tail == q->head) + return -1; /* queue empty */ + return q->list[q->head]; +} + +static __inline unsigned long get_jiffies(struct timeval *t) +{ + struct timeval cur; + + if (t->tv_usec >= 1000000) { + t->tv_sec += t->tv_usec / 1000000; + t->tv_usec = t->tv_usec % 1000000; + } + + do_gettimeofday(&cur); + if ((t->tv_sec < cur.tv_sec) + || ((t->tv_sec == cur.tv_sec) && (t->tv_usec < cur.tv_usec))) + return jiffies; + + if (t->tv_usec < cur.tv_usec) { + cur.tv_sec = t->tv_sec - cur.tv_sec - 1; + cur.tv_usec = t->tv_usec + 1000000 - cur.tv_usec; + } else { + cur.tv_sec = t->tv_sec - cur.tv_sec; + cur.tv_usec = t->tv_usec - cur.tv_usec; + } + + return jiffies + timeval_to_jiffies(&cur); +} + +/*! + * Private function to free buffers + * + * @param bufs_paddr Array of physical address of buffers to be freed + * + * @param bufs_vaddr Array of virtual address of buffers to be freed + * + * @param num_buf Number of buffers to be freed + * + * @param size Size for each buffer to be free + * + * @return status 0 success. + */ +static int mxc_free_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + if (bufs_vaddr[i] != 0) { + dma_free_coherent(0, size, bufs_vaddr[i], + bufs_paddr[i]); + pr_debug("freed @ paddr=0x%08X\n", (u32) bufs_paddr[i]); + bufs_paddr[i] = 0; + bufs_vaddr[i] = NULL; + } + } + return 0; +} + +/*! + * Private function to allocate buffers + * + * @param bufs_paddr Output array of physical address of buffers allocated + * + * @param bufs_vaddr Output array of virtual address of buffers allocated + * + * @param num_buf Input number of buffers to allocate + * + * @param size Input size for each buffer to allocate + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + bufs_vaddr[i] = dma_alloc_coherent(0, size, + &bufs_paddr[i], + GFP_DMA | GFP_KERNEL); + + if (bufs_vaddr[i] == 0) { + mxc_free_buffers(bufs_paddr, bufs_vaddr, i, size); + printk(KERN_ERR "dma_alloc_coherent failed.\n"); + return -ENOBUFS; + } + pr_debug("allocated @ paddr=0x%08X, size=%d.\n", + (u32) bufs_paddr[i], size); + } + + return 0; +} + +/* + * Returns bits per pixel for given pixel format + * + * @param pixelformat V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return bits per pixel of pixelformat + */ +static u32 fmt_to_bpp(u32 pixelformat) +{ + u32 bpp; + + bpp = 8*bytes_per_pixel(pixelformat); + return bpp; +} + +static bool format_is_yuv(u32 pixelformat) +{ + switch (pixelformat) { + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_NV12: + return true; + break; + } + return false; +} + +static u32 bpp_to_fmt(struct fb_info *fbi) +{ + if (fbi->var.nonstd) + return fbi->var.nonstd; + + if (fbi->var.bits_per_pixel == 24) + return V4L2_PIX_FMT_BGR24; + else if (fbi->var.bits_per_pixel == 32) + return V4L2_PIX_FMT_BGR32; + else if (fbi->var.bits_per_pixel == 16) + return V4L2_PIX_FMT_RGB565; + + return 0; +} + +static irqreturn_t mxc_v4l2out_disp_refresh_irq_handler(int irq, void *dev_id) +{ + vout_data *vout = dev_id; + int index, last_buf, ret; + unsigned long timeout; + unsigned long lock_flags = 0; + + spin_lock_irqsave(&g_lock, lock_flags); + + g_irq_cnt++; + + if (vout->ic_bypass && (pending_buffer || vout->frame_count < 3)) { + last_buf = vout->ipu_buf[vout->next_done_ipu_buf]; + if (last_buf != -1) { + g_buf_output_cnt++; + vout->v4l2_bufs[last_buf].flags = V4L2_BUF_FLAG_DONE; + queue_buf(&vout->done_q, last_buf); + vout->ipu_buf[vout->next_done_ipu_buf] = -1; + wake_up_interruptible(&vout->v4l_bufq); + vout->next_done_ipu_buf = !vout->next_done_ipu_buf; + } + } + + if ((pending_buffer) && (pp_eof || vout->ic_bypass)) { + pp_eof = 0; + if (vout->ic_bypass) { + ret = ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf); + } else { + if (LOAD_3FIELDS(vout)) { + ret = ipu_select_multi_vdi_buffer(vout->next_rdy_ipu_buf); + } else { + ret = ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf); + } + } + if (ret < 0) { + dev_err(&vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + } + vout->next_rdy_ipu_buf = !vout->next_rdy_ipu_buf; + + pending_buffer = 0; + + /* Setup timer for next buffer */ + index = peek_next_buf(&vout->ready_q); + if (index != -1) { + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0) + && vout->start_jiffies) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + dev_dbg(&vout->video_dev->dev, + "warning: timer timeout already expired.\n"); + } + if (mod_timer(&vout->output_timer, timeout)) + dev_dbg(&vout->video_dev->dev, + "warning: timer was already set\n"); + + dev_dbg(&vout->video_dev->dev, + "timer handler next schedule: %lu\n", timeout); + } else { + vout->state = STATE_STREAM_PAUSED; + } + } + + if (vout->state == STATE_STREAM_STOPPING) { + if ((vout->ipu_buf[0] == -1) && (vout->ipu_buf[1] == -1)) { + vout->state = STATE_STREAM_OFF; + } + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return IRQ_HANDLED; +} + +static int get_display_irq(vout_data *vout) +{ + + int disp_irq = 0; + + switch (vout->display_ch) { + case MEM_FG_SYNC: + case MEM_BG_SYNC: + disp_irq = IPU_IRQ_BG_SF_END; + break; + case MEM_DC_SYNC: + disp_irq = IPU_IRQ_DC_FC_1; + break; + default: + dev_err(&vout->video_dev->dev, + "not support display channel\n"); + } + + return disp_irq; +} + +static void mxc_v4l2out_timer_handler(unsigned long arg) +{ + int index, ret; + unsigned long lock_flags = 0; + vout_data *vout = (vout_data *) arg; + + spin_lock_irqsave(&g_lock, lock_flags); + + if ((vout->state == STATE_STREAM_STOPPING) + || (vout->state == STATE_STREAM_OFF)) + goto exit0; + /* + * If timer occurs before IPU h/w is ready, then set the state to + * paused and the timer will be set again when next buffer is queued + * or PP comletes + */ + if (vout->ipu_buf[vout->next_rdy_ipu_buf] != -1) { + dev_dbg(&vout->video_dev->dev, "IPU buffer busy\n"); + vout->state = STATE_STREAM_PAUSED; + goto exit0; + } + + /* Dequeue buffer and pass to IPU */ + unsigned int aid_field_offset, current_field_offset; + if (INTERLACED_CONTENT(vout)) { + if (((LOAD_3FIELDS(vout)) && (vout->next_rdy_ipu_buf)) || + ((!LOAD_3FIELDS(vout)) && !(vout->next_rdy_ipu_buf))) { + aid_field_offset = vout->bytesperline; + current_field_offset = 0; + index = last_index_n; + } else { + aid_field_offset = 0; + current_field_offset = vout->bytesperline; + index = dequeue_buf(&vout->ready_q); + if (index == -1) { /* no buffers ready, should never occur */ + dev_err(&vout->video_dev->dev, + "mxc_v4l2out: timer - no queued buffers ready\n"); + goto exit0; + } + g_buf_dq_cnt++; + vout->frame_count++; + last_index_n = index; + } + } else { + current_field_offset = 0; + index = dequeue_buf(&vout->ready_q); + if (index == -1) { /* no buffers ready, should never occur */ + dev_err(&vout->video_dev->dev, + "mxc_v4l2out: timer - no queued buffers ready\n"); + goto exit0; + } + g_buf_dq_cnt++; + vout->frame_count++; + } + + if (vout->ic_bypass) { + vout->ipu_buf[vout->next_rdy_ipu_buf] = index; + ret = ipu_update_channel_buffer(vout->display_ch, IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index].m.offset); + } else { + if (LOAD_3FIELDS(vout)) { + int index_n = index; + index = last_index_n; + int index_p = last_index_c; + vout->ipu_buf_p[vout->next_rdy_ipu_buf] = index_p; + vout->ipu_buf[vout->next_rdy_ipu_buf] = last_index_c = index; + vout->ipu_buf_n[vout->next_rdy_ipu_buf] = last_index_n = index_n; + last_index_n = vout->ipu_buf_n[vout->next_rdy_ipu_buf]; + last_index_c = vout->ipu_buf[vout->next_rdy_ipu_buf]; + ret = ipu_update_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index].m.offset+current_field_offset); + ret += ipu_update_channel_buffer(MEM_VDI_PRP_VF_MEM_P, + IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index_p].m.offset+aid_field_offset); + ret += ipu_update_channel_buffer(MEM_VDI_PRP_VF_MEM_N, + IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index_n].m.offset+aid_field_offset); + } else { + vout->ipu_buf[vout->next_rdy_ipu_buf] = index; + ret = ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index].m.offset+current_field_offset); + } + } + if (ret < 0) { + dev_err(&vout->video_dev->dev, + "unable to update buffer %d address rc=%d\n", + vout->next_rdy_ipu_buf, ret); + goto exit0; + } + + pending_buffer = 1; + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return; + + exit0: + spin_unlock_irqrestore(&g_lock, lock_flags); +} + +static irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id) +{ + int last_buf; + int index; + unsigned long timeout; + unsigned long lock_flags = 0; + vout_data *vout = dev_id; + + spin_lock_irqsave(&g_lock, lock_flags); + + g_irq_cnt++; + + /* Process previous buffer */ + if (LOAD_3FIELDS(vout)) + last_buf = vout->ipu_buf_p[vout->next_done_ipu_buf]; + else + last_buf = vout->ipu_buf[vout->next_done_ipu_buf]; + + if (last_buf != -1) { + if ((!INTERLACED_CONTENT(vout)) || (vout->next_done_ipu_buf)) { + g_buf_output_cnt++; + vout->v4l2_bufs[last_buf].flags = V4L2_BUF_FLAG_DONE; + queue_buf(&vout->done_q, last_buf); + wake_up_interruptible(&vout->v4l_bufq); + } + vout->ipu_buf[vout->next_done_ipu_buf] = -1; + if (LOAD_3FIELDS(vout)) { + vout->ipu_buf_p[vout->next_done_ipu_buf] = -1; + vout->ipu_buf_n[vout->next_done_ipu_buf] = -1; + } + vout->next_done_ipu_buf = !vout->next_done_ipu_buf; + } + pp_eof = 1; + + if (vout->state == STATE_STREAM_STOPPING) { + if ((vout->ipu_buf[0] == -1) && (vout->ipu_buf[1] == -1)) { + vout->state = STATE_STREAM_OFF; + } + } else if ((vout->state == STATE_STREAM_PAUSED) + && ((index = peek_next_buf(&vout->ready_q)) != -1)) { + /* Setup timer for next buffer, when stream has been paused */ + pr_debug("next index %d\n", index); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + pr_debug("warning: timer timeout already expired.\n"); + } + + vout->state = STATE_STREAM_ON; + + if (mod_timer(&vout->output_timer, timeout)) + pr_debug("warning: timer was already set\n"); + + pr_debug("timer handler next schedule: %lu\n", timeout); + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return IRQ_HANDLED; +} + +/*! + * Initialize VDI channels + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int init_VDI_channel(vout_data *vout, ipu_channel_params_t params) +{ + struct device *dev = &vout->video_dev->dev; + + if (ipu_init_channel(MEM_VDI_PRP_VF_MEM, ¶ms) != 0) { + dev_dbg(dev, "Error initializing VDI current channel\n"); + return -EINVAL; + } + if (LOAD_3FIELDS(vout)) { + if (ipu_init_channel(MEM_VDI_PRP_VF_MEM_P, ¶ms) != 0) { + dev_err(dev, "Error initializing VDI previous channel\n"); + return -EINVAL; + } + if (ipu_init_channel(MEM_VDI_PRP_VF_MEM_N, ¶ms) != 0) { + dev_err(dev, "Error initializing VDI next channel\n"); + return -EINVAL; + } + } + return 0; +} + +/*! + * Initialize VDI channel buffers + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int init_VDI_in_channel_buffer(vout_data *vout, uint32_t in_pixel_fmt, + uint16_t in_width, uint16_t in_height, + uint32_t stride, + dma_addr_t phyaddr_0, dma_addr_t phyaddr_1, + uint32_t u_offset, uint32_t v_offset) +{ + struct device *dev = &vout->video_dev->dev; + + if (ipu_init_channel_buffer(MEM_VDI_PRP_VF_MEM, IPU_INPUT_BUFFER, + in_pixel_fmt, in_width, in_height, stride, + IPU_ROTATE_NONE, + vout->v4l2_bufs[vout->ipu_buf[0]].m.offset+vout->bytesperline, + vout->v4l2_bufs[vout->ipu_buf[0]].m.offset, + u_offset, v_offset) != 0) { + dev_err(dev, "Error initializing VDI current input buffer\n"); + return -EINVAL; + } + if (LOAD_3FIELDS(vout)) { + if (ipu_init_channel_buffer(MEM_VDI_PRP_VF_MEM_P, + IPU_INPUT_BUFFER, + in_pixel_fmt, in_width, in_height, + stride, IPU_ROTATE_NONE, + vout->v4l2_bufs[vout->ipu_buf_p[0]].m.offset, + vout->v4l2_bufs[vout->ipu_buf_p[0]].m.offset+vout->bytesperline, + u_offset, v_offset) != 0) { + dev_err(dev, "Error initializing VDI previous input buffer\n"); + return -EINVAL; + } + if (ipu_init_channel_buffer(MEM_VDI_PRP_VF_MEM_N, + IPU_INPUT_BUFFER, + in_pixel_fmt, in_width, in_height, + stride, IPU_ROTATE_NONE, + vout->v4l2_bufs[vout->ipu_buf_n[0]].m.offset, + vout->v4l2_bufs[vout->ipu_buf_n[0]].m.offset+vout->bytesperline, + u_offset, v_offset) != 0) { + dev_err(dev, "Error initializing VDI next input buffer\n"); + return -EINVAL; + } + } + return 0; +} + +/*! + * Initialize VDI path + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int init_VDI(ipu_channel_params_t params, vout_data *vout, + struct device *dev, struct fb_info *fbi, + ipu_channel_t *display_input_ch, u16 out_width, + u16 out_height) +{ + params.mem_prp_vf_mem.in_width = vout->v2f.fmt.pix.width; + params.mem_prp_vf_mem.in_height = vout->v2f.fmt.pix.height; + params.mem_prp_vf_mem.motion_sel = vout->motion_sel; + params.mem_prp_vf_mem.field_fmt = vout->field_fmt; + params.mem_prp_vf_mem.in_pixel_fmt = vout->v2f.fmt.pix.pixelformat; + params.mem_prp_vf_mem.out_width = out_width; + params.mem_prp_vf_mem.out_height = out_height; + if (vout->display_ch == ADC_SYS2) + params.mem_prp_vf_mem.out_pixel_fmt = SDC_FG_FB_FORMAT; + else + params.mem_prp_vf_mem.out_pixel_fmt = bpp_to_fmt(fbi); + + if (init_VDI_channel(vout, params) != 0) { + dev_err(dev, "Error init_VDI_channel channel\n"); + return -EINVAL; + } + + + if (init_VDI_in_channel_buffer(vout, + params.mem_prp_vf_mem.in_pixel_fmt, + params.mem_prp_vf_mem.in_width, + params.mem_prp_vf_mem.in_height, + bytes_per_pixel(params.mem_prp_vf_mem. + in_pixel_fmt), + vout->v4l2_bufs[vout->ipu_buf[0]].m.offset, + vout->v4l2_bufs[vout->ipu_buf[1]].m.offset, + vout->offset.u_offset, + vout->offset.v_offset) != 0) { + return -EINVAL; + } + + if (!ipu_can_rotate_in_place(vout->rotate)) { + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + if (mxc_allocate_buffers + (vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size) < 0) { + return -ENOBUFS; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_prp_vf_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, 0) != 0) { + dev_err(dev, "Error initializing PRP output buffer\n"); + return -EINVAL; + } + + if (ipu_init_channel(MEM_ROT_VF_MEM, NULL) != 0) { + dev_err(dev, "Error initializing PP ROT channel\n"); + return -EINVAL; + } + if (ipu_init_channel_buffer(MEM_ROT_VF_MEM, + IPU_INPUT_BUFFER, + params.mem_prp_vf_mem. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, 0) != 0) { + dev_err(dev, + "Error initializing PP ROT input buffer\n"); + return -EINVAL; + } + + /* swap width and height */ + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + } + + if (ipu_init_channel_buffer(MEM_ROT_VF_MEM, + IPU_OUTPUT_BUFFER, + params.mem_prp_vf_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->display_bufs[0], + vout->display_bufs[1], 0, 0) != 0) { + dev_err(dev, + "Error initializing PP-VDI output buffer\n"); + return -EINVAL; + } + + if (ipu_link_channels(vout->post_proc_ch, MEM_ROT_VF_MEM) < 0) + return -EINVAL; + + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 1); + + ipu_enable_channel(MEM_ROT_VF_MEM); + *display_input_ch = MEM_ROT_VF_MEM; + + } else { + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_prp_vf_mem. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, + vout->display_bufs[0], + vout->display_bufs[1], 0, 0) != 0) { + dev_err(dev, + "Error initializing PP-VDI output buffer\n"); + return -EINVAL; + } + } + return 0; +} + +/*! + * Initialize PP path + * + * @param params structure ipu_channel_params_t + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int init_PP(ipu_channel_params_t params, vout_data *vout, + struct device *dev, struct fb_info *fbi, + ipu_channel_t *display_input_ch, u16 out_width, + u16 out_height) +{ + params.mem_pp_mem.in_width = vout->v2f.fmt.pix.width; + params.mem_pp_mem.in_height = vout->v2f.fmt.pix.height; + params.mem_pp_mem.in_pixel_fmt = vout->v2f.fmt.pix.pixelformat; + params.mem_pp_mem.out_width = out_width; + params.mem_pp_mem.out_height = out_height; + if (vout->display_ch == ADC_SYS2) + params.mem_pp_mem.out_pixel_fmt = SDC_FG_FB_FORMAT; + else + params.mem_pp_mem.out_pixel_fmt = bpp_to_fmt(fbi); + if (ipu_init_channel(vout->post_proc_ch, ¶ms) != 0) { + dev_err(dev, "Error initializing PP channel\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + params.mem_pp_mem.in_pixel_fmt, + params.mem_pp_mem.in_width, + params.mem_pp_mem.in_height, + vout->v2f.fmt.pix.bytesperline / + bytes_per_pixel(params.mem_pp_mem. + in_pixel_fmt), + IPU_ROTATE_NONE, + vout->v4l2_bufs[vout->ipu_buf[0]].m.offset, + vout->v4l2_bufs[vout->ipu_buf[1]].m.offset, + vout->offset.u_offset, + vout->offset.v_offset) != 0) { + dev_err(dev, "Error initializing PP input buffer\n"); + return -EINVAL; + } + + if (!ipu_can_rotate_in_place(vout->rotate)) { + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + if (mxc_allocate_buffers + (vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size) < 0) { + return -ENOBUFS; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, 0) != 0) { + dev_err(dev, "Error initializing PP output buffer\n"); + return -EINVAL; + } + + if (ipu_init_channel(MEM_ROT_PP_MEM, NULL) != 0) { + dev_err(dev, "Error initializing PP ROT channel\n"); + return -EINVAL; + } + if (ipu_init_channel_buffer(MEM_ROT_PP_MEM, + IPU_INPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, 0) != 0) { + dev_err(dev, + "Error initializing PP ROT input buffer\n"); + return -EINVAL; + } + + /* swap width and height */ + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + } + + if (ipu_init_channel_buffer(MEM_ROT_PP_MEM, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->display_bufs[0], + vout->display_bufs[1], 0, 0) != 0) { + dev_err(dev, "Error initializing PP output buffer\n"); + return -EINVAL; + } + + if (ipu_link_channels(vout->post_proc_ch, MEM_ROT_PP_MEM) < 0) + return -EINVAL; + + ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 1); + + ipu_enable_channel(MEM_ROT_PP_MEM); + *display_input_ch = MEM_ROT_PP_MEM; + + } else { + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, + vout->display_bufs[0], + vout->display_bufs[1], 0, 0) != 0) { + dev_err(dev, "Error initializing PP output buffer\n"); + return -EINVAL; + } + } + return 0; +} + +/*! + * Start the output stream + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamon(vout_data * vout) +{ + struct device *dev = &vout->video_dev->dev; + ipu_channel_params_t params; + struct mxcfb_pos fb_pos; + struct fb_var_screeninfo fbvar; + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + u16 out_width; + u16 out_height; + int disp_irq = 0; + ipu_channel_t display_input_ch; + bool use_direct_adc = false; + mm_segment_t old_fs; + + dev_dbg(dev, "mxc_v4l2out_streamon: field format=%d\n", + vout->field_fmt); + if (INTERLACED_CONTENT(vout)) { + ipu_request_irq(IPU_IRQ_PRP_VF_OUT_EOF, + mxc_v4l2out_pp_in_irq_handler, + 0, &vout->video_dev->name, vout); + display_input_ch = MEM_VDI_PRP_VF_MEM; + } else { + ipu_request_irq(IPU_IRQ_PP_IN_EOF, + mxc_v4l2out_pp_in_irq_handler, + 0, &vout->video_dev->name, vout); + display_input_ch = MEM_PP_MEM; + } + + if (!vout) + return -EINVAL; + + if (vout->state != STATE_STREAM_OFF) + return -EBUSY; + + if (queue_size(&vout->ready_q) < 2) { + dev_err(dev, "2 buffers not been queued yet!\n"); + return -EINVAL; + } + + if ((vout->field_fmt == V4L2_FIELD_BOTTOM) || (vout->field_fmt == V4L2_FIELD_TOP)) { + dev_err(dev, "4 queued buffers need, not supported yet!\n"); + return -EINVAL; + } + + pending_buffer = 0; + + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + vout->next_done_ipu_buf = 0; + vout->next_rdy_ipu_buf = 1; + + if (!INTERLACED_CONTENT(vout)) { + vout->next_done_ipu_buf = vout->next_rdy_ipu_buf = 0; + vout->ipu_buf[0] = dequeue_buf(&vout->ready_q); + vout->ipu_buf[1] = dequeue_buf(&vout->ready_q); + vout->frame_count = 2; + } else if (!LOAD_3FIELDS(vout)) { + vout->ipu_buf[0] = dequeue_buf(&vout->ready_q); + vout->ipu_buf[1] = -1; + vout->frame_count = 1; + last_index_n = vout->ipu_buf[0]; + } else { + vout->ipu_buf_p[0] = dequeue_buf(&vout->ready_q); + vout->ipu_buf[0] = vout->ipu_buf_p[0]; + vout->ipu_buf_n[0] = dequeue_buf(&vout->ready_q); + vout->ipu_buf_p[1] = -1; + vout->ipu_buf[1] = -1; + vout->ipu_buf_n[1] = -1; + last_index_c = vout->ipu_buf[0]; + last_index_n = vout->ipu_buf_n[0]; + vout->frame_count = 2; + } + + /* Init Display Channel */ +#ifdef CONFIG_FB_MXC_ASYNC_PANEL + if (INTERLACED_CONTENT(vout)) + ipu_enable_irq(IPU_IRQ_PRP_VF_OUT_EOF); + else + ipu_enable_irq(IPU_IRQ_PP_IN_EOF); + + if (vout->cur_disp_output < DISP3) { + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, 0); + fbi = NULL; + if (ipu_can_rotate_in_place(vout->rotate)) { + dev_dbg(dev, "Using PP direct to ADC channel\n"); + use_direct_adc = true; + vout->display_ch = MEM_PP_ADC; + vout->post_proc_ch = MEM_PP_ADC; + + memset(¶ms, 0, sizeof(params)); + params.mem_pp_adc.in_width = vout->v2f.fmt.pix.width; + params.mem_pp_adc.in_height = vout->v2f.fmt.pix.height; + params.mem_pp_adc.in_pixel_fmt = + vout->v2f.fmt.pix.pixelformat; + params.mem_pp_adc.out_width = out_width; + params.mem_pp_adc.out_height = out_height; + params.mem_pp_adc.out_pixel_fmt = SDC_FG_FB_FORMAT; +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.mem_pp_adc.out_left = + 2 + vout->crop_current.left; +#else + params.mem_pp_adc.out_left = + 12 + vout->crop_current.left; +#endif + params.mem_pp_adc.out_top = vout->crop_current.top; + if (ipu_init_channel(vout->post_proc_ch, ¶ms) != 0) { + dev_err(dev, "Error initializing PP chan\n"); + return -EINVAL; + } + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + params.mem_pp_adc. + in_pixel_fmt, + params.mem_pp_adc.in_width, + params.mem_pp_adc.in_height, + vout->v2f.fmt.pix. + bytesperline / + bytes_per_pixel(params. + mem_pp_adc. + in_pixel_fmt), + vout->rotate, + vout->v4l2_bufs[vout->ipu_buf[0]].m.offset, + vout->v4l2_bufs[vout->ipu_buf[1]].m.offset, + vout->offset.u_offset, + vout->offset.v_offset) != + 0) { + dev_err(dev, "Error initializing PP in buf\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_adc. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, 0, 0, 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP output buffer\n"); + return -EINVAL; + } + + } else { + dev_dbg(dev, "Using ADC SYS2 channel\n"); + vout->display_ch = ADC_SYS2; + vout->post_proc_ch = MEM_PP_MEM; + + if (vout->display_bufs[0]) { + mxc_free_buffers(vout->display_bufs, + vout->display_bufs_vaddr, + 2, vout->display_buf_size); + } + + vout->display_buf_size = vout->crop_current.width * + vout->crop_current.height * + fmt_to_bpp(SDC_FG_FB_FORMAT) / 8; + mxc_allocate_buffers(vout->display_bufs, + vout->display_bufs_vaddr, + 2, vout->display_buf_size); + + memset(¶ms, 0, sizeof(params)); + params.adc_sys2.disp = vout->cur_disp_output; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.adc_sys2.out_left = 2 + vout->crop_current.left; +#else + params.adc_sys2.out_left = 12 + vout->crop_current.left; +#endif + params.adc_sys2.out_top = vout->crop_current.top; + if (ipu_init_channel(ADC_SYS2, ¶ms) < 0) + return -EINVAL; + + if (ipu_init_channel_buffer(vout->display_ch, + IPU_INPUT_BUFFER, + SDC_FG_FB_FORMAT, + out_width, out_height, + out_width, IPU_ROTATE_NONE, + vout->display_bufs[0], + vout->display_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing SDC FG buffer\n"); + return -EINVAL; + } + } + } else +#endif + { /* Use SDC */ + unsigned int ipu_ch = CHAN_NONE; + + dev_dbg(dev, "Using SDC channel\n"); + + /* Bypass IC if resizing and rotation not needed + Always do CSC in DP + Meanwhile, apply IC bypass to SDC only + */ + if (out_width == vout->v2f.fmt.pix.width && + out_height == vout->v2f.fmt.pix.height && + ipu_can_rotate_in_place(vout->rotate)) { + pr_debug("Bypassing IC\n"); + vout->ic_bypass = 1; + ipu_disable_irq(IPU_IRQ_PP_IN_EOF); + } else { + vout->ic_bypass = 0; + } + +#ifdef CONFIG_MXC_IPU_V1 + /* IPUv1 needs IC to do CSC */ + if (format_is_yuv(vout->v2f.fmt.pix.pixelformat) != + format_is_yuv(bpp_to_fmt(fbi))) + vout->ic_bypass = 0; +#endif + + fbvar = fbi->var; + + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_GET_FB_IPU_CHAN, + (unsigned long)&ipu_ch); + set_fs(old_fs); + } + + if (ipu_ch == CHAN_NONE) { + dev_err(dev, + "Can not get display ipu channel\n"); + return -EINVAL; + } + + vout->display_ch = ipu_ch; + + if (vout->cur_disp_output == 3 || vout->cur_disp_output == 5) { + fbvar.bits_per_pixel = 16; + if (format_is_yuv(vout->v2f.fmt.pix.pixelformat)) + fbvar.nonstd = IPU_PIX_FMT_UYVY; + else + fbvar.nonstd = 0; + + fbvar.xres = fbvar.xres_virtual = out_width; + fbvar.yres = out_height; + fbvar.yres_virtual = out_height * 2; + } + + if (vout->ic_bypass) { + fbvar.bits_per_pixel = 8* + bytes_per_pixel(vout->v2f.fmt.pix.pixelformat); + fbvar.nonstd = vout->v2f.fmt.pix.pixelformat; + } + + fbvar.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbvar); + + fb_pos.x = vout->crop_current.left; + fb_pos.y = vout->crop_current.top; + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS, + (unsigned long)&fb_pos); + set_fs(old_fs); + } + + vout->display_bufs[1] = fbi->fix.smem_start; + vout->display_bufs[0] = fbi->fix.smem_start + + (fbi->fix.line_length * fbi->var.yres); + vout->display_buf_size = vout->crop_current.width * + vout->crop_current.height * fbi->var.bits_per_pixel / 8; + if (INTERLACED_CONTENT(vout)) + vout->post_proc_ch = MEM_VDI_PRP_VF_MEM; + else + vout->post_proc_ch = MEM_PP_MEM; + } + + /* Init PP */ + if (use_direct_adc == false && !vout->ic_bypass) { + if (INTERLACED_CONTENT(vout)) { + vout->post_proc_ch = MEM_VDI_PRP_VF_MEM; + ipu_enable_irq(IPU_IRQ_PRP_VF_OUT_EOF); + } else { + vout->post_proc_ch = MEM_PP_MEM; + ipu_enable_irq(IPU_IRQ_PP_IN_EOF); + } + + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.height; + out_height = vout->crop_current.width; + } + memset(¶ms, 0, sizeof(params)); + int rc; + if (INTERLACED_CONTENT(vout)) { + rc = init_VDI(params, vout, dev, fbi, &display_input_ch, + out_width, out_height); + } else { + rc = init_PP(params, vout, dev, fbi, &display_input_ch, + out_width, out_height); + } + if (rc < 0) + return rc; + if (ipu_link_channels(display_input_ch, vout->display_ch) < 0) { + dev_err(dev, "Error linking ipu channels\n"); + return -EINVAL; + } + } + + vout->state = STATE_STREAM_PAUSED; + + if (use_direct_adc == false) { + if (!vout->ic_bypass) { +#ifndef CONFIG_MXC_IPU_V1 + ipu_enable_channel(vout->post_proc_ch); +#endif + if (LOAD_3FIELDS(vout)) { + ipu_enable_channel(MEM_VDI_PRP_VF_MEM_P); + ipu_enable_channel(MEM_VDI_PRP_VF_MEM_N); + ipu_select_multi_vdi_buffer(0); + } else if (INTERLACED_CONTENT(vout)) { + ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0); + } else { + ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 1); + } + ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 1); +#ifdef CONFIG_MXC_IPU_V1 + ipu_enable_channel(vout->post_proc_ch); +#endif + } else { + ipu_update_channel_buffer(vout->display_ch, + IPU_INPUT_BUFFER, + 0, vout->v4l2_bufs[vout->ipu_buf[0]].m.offset); + ipu_update_channel_buffer(vout->display_ch, + IPU_INPUT_BUFFER, + 1, vout->v4l2_bufs[vout->ipu_buf[1]].m.offset); + ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 1); + } + disp_irq = get_display_irq(vout); + ipu_request_irq(disp_irq, mxc_v4l2out_disp_refresh_irq_handler, + 0, NULL, vout); + + if (fbi) { + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_UNBLANK); + release_console_sem(); + } else { + ipu_enable_channel(vout->display_ch); + } + } else { + ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 1); + ipu_enable_channel(vout->post_proc_ch); + } + vout->start_jiffies = jiffies; + + msleep(1); + + dev_dbg(dev, + "streamon: start time = %lu jiffies\n", vout->start_jiffies); + + return 0; +} + +/*! + * Shut down the voutera + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamoff(vout_data * vout) +{ + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + int i, retval = 0, disp_irq = 0; + unsigned long lockflag = 0; + + if (!vout) + return -EINVAL; + + if (vout->state == STATE_STREAM_OFF) { + return 0; + } + + if (INTERLACED_CONTENT(vout)) + ipu_free_irq(IPU_IRQ_PRP_VF_OUT_EOF, vout); + else + ipu_free_irq(IPU_IRQ_PP_IN_EOF, vout); + + spin_lock_irqsave(&g_lock, lockflag); + + del_timer(&vout->output_timer); + + if (vout->state == STATE_STREAM_ON) { + vout->state = STATE_STREAM_STOPPING; + } + + if (!vout->ic_bypass) { + if (INTERLACED_CONTENT(vout)) + ipu_disable_irq(IPU_IRQ_PRP_VF_OUT_EOF); + else + ipu_disable_irq(IPU_IRQ_PP_IN_EOF); + } + + spin_unlock_irqrestore(&g_lock, lockflag); + + pending_buffer = 0; + disp_irq = get_display_irq(vout); + ipu_free_irq(disp_irq, vout); + + if (vout->display_ch == MEM_FG_SYNC) { + struct mxcfb_pos fb_pos; + mm_segment_t old_fs; + + fb_pos.x = 0; + fb_pos.y = 0; + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS, + (unsigned long)&fb_pos); + set_fs(old_fs); + } + } + + if (vout->post_proc_ch == MEM_PP_MEM || + vout->post_proc_ch == MEM_PRP_VF_MEM) { + /* SDC or ADC with Rotation */ + if (!ipu_can_rotate_in_place(vout->rotate)) { + ipu_unlink_channels(MEM_PP_MEM, MEM_ROT_PP_MEM); + ipu_unlink_channels(MEM_ROT_PP_MEM, + vout->display_ch); + ipu_disable_channel(MEM_ROT_PP_MEM, true); + + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + } else { + ipu_unlink_channels(MEM_PP_MEM, vout->display_ch); + } + ipu_disable_channel(MEM_PP_MEM, true); + + if (vout->display_ch == ADC_SYS2 || + vout->display_ch == MEM_FG_SYNC) { + ipu_disable_channel(vout->display_ch, true); + ipu_uninit_channel(vout->display_ch); + } else { + fbi->var.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbi->var); + + if (vout->display_ch == MEM_FG_SYNC) { + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_POWERDOWN); + release_console_sem(); + } + + vout->display_bufs[0] = 0; + vout->display_bufs[1] = 0; + } + + ipu_uninit_channel(MEM_PP_MEM); + if (!ipu_can_rotate_in_place(vout->rotate)) + ipu_uninit_channel(MEM_ROT_PP_MEM); + } else if (INTERLACED_CONTENT(vout) && (vout->post_proc_ch == MEM_VDI_PRP_VF_MEM)) { + if (!ipu_can_rotate_in_place(vout->rotate)) { + ipu_unlink_channels(MEM_VDI_PRP_VF_MEM, + MEM_ROT_VF_MEM); + ipu_unlink_channels(MEM_ROT_VF_MEM, + vout->display_ch); + ipu_disable_channel(MEM_ROT_VF_MEM, true); + + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + } else { + ipu_unlink_channels(MEM_VDI_PRP_VF_MEM, + vout->display_ch); + } + + ipu_disable_channel(MEM_VDI_PRP_VF_MEM, true); + + if (vout->display_ch == ADC_SYS2 || + vout->display_ch == MEM_FG_SYNC) { + ipu_disable_channel(vout->display_ch, true); + ipu_uninit_channel(vout->display_ch); + } else { + fbi->var.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbi->var); + + if (vout->display_ch == MEM_FG_SYNC) { + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_POWERDOWN); + release_console_sem(); + } + + vout->display_bufs[0] = 0; + vout->display_bufs[1] = 0; + } + + ipu_uninit_channel(MEM_VDI_PRP_VF_MEM); + if (!ipu_can_rotate_in_place(vout->rotate)) + ipu_uninit_channel(MEM_ROT_VF_MEM); + } else { /* ADC Direct */ + ipu_disable_channel(MEM_PP_ADC, true); + ipu_uninit_channel(MEM_PP_ADC); + } + vout->ready_q.head = vout->ready_q.tail = 0; + vout->done_q.head = vout->done_q.tail = 0; + for (i = 0; i < vout->buffer_cnt; i++) { + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + + vout->state = STATE_STREAM_OFF; + +#ifdef CONFIG_FB_MXC_ASYNC_PANEL + if (vout->cur_disp_output < DISP3) { + if (vout->display_bufs[0] != 0) { + mxc_free_buffers(vout->display_bufs, + vout->display_bufs_vaddr, 2, + vout->display_buf_size); + } + + mxcfb_set_refresh_mode(registered_fb + [vout-> + output_fb_num[vout->cur_disp_output]], + MXCFB_REFRESH_PARTIAL, 0); + } +#endif + + return retval; +} + +/* + * Valid whether the palette is supported + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 1 if supported, 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return ((palette == V4L2_PIX_FMT_RGB565) || + (palette == V4L2_PIX_FMT_BGR24) || + (palette == V4L2_PIX_FMT_RGB24) || + (palette == V4L2_PIX_FMT_BGR32) || + (palette == V4L2_PIX_FMT_RGB32) || + (palette == V4L2_PIX_FMT_NV12) || + (palette == V4L2_PIX_FMT_YUV422P) || + (palette == V4L2_PIX_FMT_YUV420)); +} + +/* + * V4L2 - Handles VIDIOC_G_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_g_fmt(vout_data * vout, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + return -EINVAL; + } + *f = vout->v2f; + return 0; +} + +/* + * V4L2 - Handles VIDIOC_S_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_s_fmt(vout_data * vout, struct v4l2_format *f) +{ + int retval = 0; + u32 size = 0; + u32 bytesperline; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + goto err0; + } + if (!valid_mode(f->fmt.pix.pixelformat)) { + dev_err(&vout->video_dev->dev, "pixel format not supported\n"); + retval = -EINVAL; + goto err0; + } + + bytesperline = (f->fmt.pix.width * fmt_to_bpp(f->fmt.pix.pixelformat)) / + 8; + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + vout->bytesperline = bytesperline; + + /* Based on http://v4l2spec.bytesex.org/spec/x6386.htm#V4L2-FIELD */ + vout->field_fmt = f->fmt.pix.field; + switch (vout->field_fmt) { + /* Images are in progressive format, not interlaced */ + case V4L2_FIELD_NONE: + break; + /* The two fields of a frame are passed in separate buffers, + in temporal order, i. e. the older one first. */ + case V4L2_FIELD_ALTERNATE: + dev_err(&vout->video_dev->dev, + "V4L2_FIELD_ALTERNATE field format not supported yet!\n"); + break; + case V4L2_FIELD_INTERLACED_TB: + if (cpu_is_mx51()) + break; + case V4L2_FIELD_INTERLACED_BT: + dev_err(&vout->video_dev->dev, + "V4L2_FIELD_INTERLACED_BT field format not supported yet!\n"); + default: + vout->field_fmt = V4L2_FIELD_NONE; + break; + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV422P: + /* byteperline for YUV planar formats is for + Y plane only */ + size = bytesperline * f->fmt.pix.height * 2; + break; + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_NV12: + size = (bytesperline * f->fmt.pix.height * 3) / 2; + break; + default: + size = bytesperline * f->fmt.pix.height; + break; + } + + /* Return the actual size of the image to the app */ + if (f->fmt.pix.sizeimage < size) { + f->fmt.pix.sizeimage = size; + } else { + size = f->fmt.pix.sizeimage; + } + + vout->v2f.fmt.pix = f->fmt.pix; + if (vout->v2f.fmt.pix.priv != 0) { + if (copy_from_user(&vout->offset, + (void *)vout->v2f.fmt.pix.priv, + sizeof(vout->offset))) { + retval = -EFAULT; + goto err0; + } + } else { + vout->offset.u_offset = 0; + vout->offset.v_offset = 0; + } + + retval = 0; + err0: + return retval; +} + +/* + * V4L2 - Handles VIDIOC_G_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42lout_control(vout_data * vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + return (vout->rotate & IPU_ROTATE_HORIZ_FLIP) ? 1 : 0; + case V4L2_CID_VFLIP: + return (vout->rotate & IPU_ROTATE_VERT_FLIP) ? 1 : 0; + case (V4L2_CID_PRIVATE_BASE + 1): + return vout->rotate; + default: + return -EINVAL; + } +} + +/* + * V4L2 - Handles VIDIOC_S_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42lout_control(vout_data * vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + vout->rotate |= c->value ? IPU_ROTATE_HORIZ_FLIP : + IPU_ROTATE_NONE; + break; + case V4L2_CID_VFLIP: + vout->rotate |= c->value ? IPU_ROTATE_VERT_FLIP : + IPU_ROTATE_NONE; + break; + case V4L2_CID_MXC_ROT: + vout->rotate = c->value; + break; + case V4L2_CID_MXC_MOTION: + vout->motion_sel = c->value; + break; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 interface - open function + * + * @param inode structure inode * + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l2out_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + int err; + + if (!vout) { + return -ENODEV; + } + + down(&vout->busy_lock); + + err = -EINTR; + if (signal_pending(current)) + goto oops; + + + if (vout->open_count++ == 0) { + init_waitqueue_head(&vout->v4l_bufq); + + init_timer(&vout->output_timer); + vout->output_timer.function = mxc_v4l2out_timer_handler; + vout->output_timer.data = (unsigned long)vout; + + vout->state = STATE_STREAM_OFF; + vout->rotate = IPU_ROTATE_NONE; + g_irq_cnt = g_buf_output_cnt = g_buf_q_cnt = g_buf_dq_cnt = 0; + + } + + file->private_data = dev; + + up(&vout->busy_lock); + + return 0; + + oops: + up(&vout->busy_lock); + return err; +} + +/*! + * V4L2 interface - close function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l2out_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + + if (--vout->open_count == 0) { + if (vout->state != STATE_STREAM_OFF) + mxc_v4l2out_streamoff(vout); + + file->private_data = NULL; + + mxc_free_buffers(vout->queue_buf_paddr, vout->queue_buf_vaddr, + vout->buffer_cnt, vout->queue_buf_size); + vout->buffer_cnt = 0; + mxc_free_buffers(vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + + /* capture off */ + wake_up_interruptible(&vout->v4l_bufq); + + } + + return 0; +} + +/*! + * V4L2 interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int +mxc_v4l2out_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *vdev = file->private_data; + vout_data *vout = video_get_drvdata(vdev); + int retval = 0; + int i = 0; + + if (!vout) + return -EBADF; + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2_output"); + cap->version = 0; + cap->capabilities = + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + case VIDIOC_G_FMT: + { + struct v4l2_format *gf = arg; + retval = mxc_v4l2out_g_fmt(vout, gf); + break; + } + case VIDIOC_S_FMT: + { + struct v4l2_format *sf = arg; + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + retval = mxc_v4l2out_s_fmt(vout, sf); + break; + } + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *req = arg; + if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (req->memory != V4L2_MEMORY_MMAP)) { + dev_dbg(&vdev->dev, + "VIDIOC_REQBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + + if (req->count == 0) + mxc_v4l2out_streamoff(vout); + + if (vout->state == STATE_STREAM_OFF) { + if (vout->queue_buf_paddr[0] != 0) { + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + dev_dbg(&vdev->dev, + "VIDIOC_REQBUFS: freed buffers\n"); + } + vout->buffer_cnt = 0; + } else { + dev_dbg(&vdev->dev, + "VIDIOC_REQBUFS: Buffer is in use\n"); + retval = -EBUSY; + break; + } + + if (req->count == 0) + break; + + if (req->count < MIN_FRAME_NUM) { + req->count = MIN_FRAME_NUM; + } else if (req->count > MAX_FRAME_NUM) { + req->count = MAX_FRAME_NUM; + } + vout->buffer_cnt = req->count; + vout->queue_buf_size = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + + retval = mxc_allocate_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + if (retval < 0) + break; + + /* Init buffer queues */ + vout->done_q.head = 0; + vout->done_q.tail = 0; + vout->ready_q.head = 0; + vout->ready_q.tail = 0; + + for (i = 0; i < vout->buffer_cnt; i++) { + memset(&(vout->v4l2_bufs[i]), 0, + sizeof(vout->v4l2_bufs[i])); + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].memory = V4L2_MEMORY_MMAP; + vout->v4l2_bufs[i].index = i; + vout->v4l2_bufs[i].type = + V4L2_BUF_TYPE_VIDEO_OUTPUT; + vout->v4l2_bufs[i].length = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + vout->v4l2_bufs[i].m.offset = + (unsigned long)vout->queue_buf_paddr[i]; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + break; + } + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *buf = arg; + u32 type = buf->type; + int index = buf->index; + + if ((type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + dev_dbg(&vdev->dev, + "VIDIOC_QUERYBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + down(&vout->param_lock); + memcpy(buf, &(vout->v4l2_bufs[index]), sizeof(*buf)); + up(&vout->param_lock); + break; + } + case VIDIOC_QBUF: + { + struct v4l2_buffer *buf = arg; + int index = buf->index; + unsigned long lock_flags; + int param[5][3]; + + if ((buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + retval = -EINVAL; + break; + } + + dev_dbg(&vdev->dev, "VIDIOC_QBUF: %d field = %d\n", buf->index, buf->field); + + /* mmapped buffers are L1 WB cached, + * so we need to clean them */ + if (buf->memory & V4L2_MEMORY_MMAP) { + flush_cache_all(); + } + + spin_lock_irqsave(&g_lock, lock_flags); + + memcpy(&(vout->v4l2_bufs[index]), buf, sizeof(*buf)); + vout->v4l2_bufs[index].flags |= V4L2_BUF_FLAG_QUEUED; + + g_buf_q_cnt++; + if (vout->v4l2_bufs[index].reserved) + if (!copy_from_user(¶m[0][0], + (void *)vout-> + v4l2_bufs[index] + .reserved, sizeof(param))) + ipu_set_csc_coefficients(vout-> + display_ch, + param); + queue_buf(&vout->ready_q, index); + if (vout->state == STATE_STREAM_PAUSED) { + unsigned long timeout; + + index = peek_next_buf(&vout->ready_q); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == + 0) + && (vout->v4l2_bufs[index].timestamp. + tv_usec == 0)) + timeout = + vout->start_jiffies + + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index]. + timestamp); + + if (jiffies >= timeout) { + dev_dbg(&vout->video_dev->dev, + "warning: timer timeout already expired.\n"); + } + vout->output_timer.expires = timeout; + dev_dbg(&vdev->dev, + "QBUF: frame #%u timeout @ %lu jiffies, current = %lu\n", + vout->frame_count, timeout, jiffies); + add_timer(&vout->output_timer); + vout->state = STATE_STREAM_ON; + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + case VIDIOC_DQBUF: + { + struct v4l2_buffer *buf = arg; + int idx; + + if ((queue_size(&vout->done_q) == 0) && + (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + break; + } + + if (!wait_event_interruptible_timeout(vout->v4l_bufq, + queue_size(&vout-> + done_q) + != 0, 10 * HZ)) { + dev_dbg(&vdev->dev, "VIDIOC_DQBUF: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + dev_dbg(&vdev->dev, + "VIDIOC_DQBUF: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + idx = dequeue_buf(&vout->done_q); + if (idx == -1) { /* No frame free */ + dev_dbg(&vdev->dev, + "VIDIOC_DQBUF: no free buffers, returning\n"); + retval = -EAGAIN; + break; + } + if ((vout->v4l2_bufs[idx].flags & V4L2_BUF_FLAG_DONE) == + 0) + dev_dbg(&vdev->dev, + "VIDIOC_DQBUF: buffer in done q, but not " + "flagged as done\n"); + + vout->v4l2_bufs[idx].flags = 0; + memcpy(buf, &(vout->v4l2_bufs[idx]), sizeof(*buf)); + dev_dbg(&vdev->dev, "VIDIOC_DQBUF: %d\n", buf->index); + break; + } + case VIDIOC_STREAMON: + { + retval = mxc_v4l2out_streamon(vout); + break; + } + case VIDIOC_STREAMOFF: + { + retval = mxc_v4l2out_streamoff(vout); + break; + } + case VIDIOC_G_CTRL: + { + retval = mxc_get_v42lout_control(vout, arg); + break; + } + case VIDIOC_S_CTRL: + { + retval = mxc_set_v42lout_control(vout, arg); + break; + } + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + + cap->bounds = vout->crop_bounds[vout->cur_disp_output]; + cap->defrect = vout->crop_bounds[vout->cur_disp_output]; + retval = 0; + break; + } + case VIDIOC_G_CROP: + { + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + crop->c = vout->crop_current; + break; + } + case VIDIOC_S_CROP: + { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = + &(vout->crop_bounds[vout->cur_disp_output]); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + if (crop->c.height < 0) { + retval = -EINVAL; + break; + } + if (crop->c.width < 0) { + retval = -EINVAL; + break; + } + + /* only full screen supported for SDC BG and SDC DC */ + if (vout->cur_disp_output == 4 || + vout->cur_disp_output == 5) { + crop->c = vout->crop_current; + break; + } + + if (crop->c.top < b->top) + crop->c.top = b->top; + if (crop->c.top >= b->top + b->height) + crop->c.top = b->top + b->height - 1; + if (crop->c.height > b->top - crop->c.top + b->height) + crop->c.height = + b->top - crop->c.top + b->height; + + if (crop->c.left < b->left) + crop->c.left = b->left; + if (crop->c.left >= b->left + b->width) + crop->c.left = b->left + b->width - 1; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + /* stride line limitation */ + crop->c.height -= crop->c.height % 8; + crop->c.width -= crop->c.width % 8; + + vout->crop_current = crop->c; + break; + } + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if ((output->index >= 5) || + (vout->output_enabled[output->index] == false)) { + retval = -EINVAL; + break; + } + + if (output->index < 3) { + *output = mxc_outputs[MXC_V4L2_OUT_2_ADC]; + output->name[4] = '0' + output->index; + } else { + *output = mxc_outputs[MXC_V4L2_OUT_2_SDC]; + } + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = vout->cur_disp_output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + int fbnum; + struct v4l2_rect *b; + + if ((*p_output_num >= MXC_V4L2_OUT_NUM_OUTPUTS) || + (vout->output_enabled[*p_output_num] == false)) { + retval = -EINVAL; + break; + } + + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + + vout->cur_disp_output = *p_output_num; + + /* Update bounds in case they have changed */ + b = &vout->crop_bounds[vout->cur_disp_output]; + + fbnum = vout->output_fb_num[vout->cur_disp_output]; + + /* + * For FG overlay, it uses BG window parameter as + * limitation reference; and BG must be enabled to + * support FG. + */ + if (vout->cur_disp_output == 3) { + unsigned int i, ipu_ch = CHAN_NONE; + struct fb_info *fbi; + mm_segment_t old_fs; + + for (i = 0; i < num_registered_fb; i++) { + fbi = registered_fb[i]; + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, + MXCFB_GET_FB_IPU_CHAN, + (unsigned long)&ipu_ch); + set_fs(old_fs); + } + if (ipu_ch == CHAN_NONE) { + dev_err(&vdev->dev, + "Can't get disp ipu channel\n"); + retval = -EINVAL; + break; + } + + if (ipu_ch == MEM_BG_SYNC) { + fbnum = i; + break; + } + } + } + + b->width = registered_fb[fbnum]->var.xres; + b->height = registered_fb[fbnum]->var.yres; + + vout->crop_current = *b; + break; + } + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_PARM: + case VIDIOC_ENUMSTD: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&vout->busy_lock); + return retval; +} + +/* + * V4L2 interface - ioctl function + * + * @return None + */ +static int +mxc_v4l2out_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, mxc_v4l2out_do_ioctl); +} + +/*! + * V4L2 interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error + */ +static int mxc_v4l2out_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *vdev = video_devdata(file); + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + int i; + vout_data *vout = video_get_drvdata(vdev); + + dev_dbg(&vdev->dev, "pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + for (i = 0; i < vout->buffer_cnt; i++) { + if ((vout->v4l2_bufs[i].m.offset == + (vma->vm_pgoff << PAGE_SHIFT)) && + (vout->v4l2_bufs[i].length >= size)) { + vout->v4l2_bufs[i].flags |= V4L2_BUF_FLAG_MAPPED; + break; + } + } + if (i == vout->buffer_cnt) { + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + /* make buffers inner write-back, outer write-thru cacheable */ + /* vma->vm_page_prot = pgprot_outer_wrthru(vma->vm_page_prot);*/ + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + dev_dbg(&vdev->dev, "mmap remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mxc_mmap_exit: + up(&vout->busy_lock); + return res; +} + +/*! + * V4L2 interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_v4l2out_poll(struct file *file, poll_table * wait) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + queue = &vout->v4l_bufq; + poll_wait(file, queue, wait); + + up(&vout->busy_lock); + return res; +} + +static struct +file_operations mxc_v4l2out_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l2out_open, + .release = mxc_v4l2out_close, + .ioctl = mxc_v4l2out_ioctl, + .mmap = mxc_v4l2out_mmap, + .poll = mxc_v4l2out_poll, +}; + +static struct video_device mxc_v4l2out_template = { + .name = "MXC Video Output", + .vfl_type = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING, + .fops = &mxc_v4l2out_fops, + .release = video_device_release, +}; + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxc_v4l2out_probe(struct platform_device *pdev) +{ + int i; + vout_data *vout; + + /* + * Allocate sufficient memory for the fb structure + */ + g_vout = vout = kmalloc(sizeof(vout_data), GFP_KERNEL); + + if (!vout) + return 0; + + memset(vout, 0, sizeof(vout_data)); + + vout->video_dev = video_device_alloc(); + if (vout->video_dev == NULL) + return -1; + vout->video_dev->minor = -1; + + *(vout->video_dev) = mxc_v4l2out_template; + + /* register v4l device */ + if (video_register_device(vout->video_dev, + VFL_TYPE_GRABBER, video_nr) == -1) { + dev_dbg(&pdev->dev, "video_register_device failed\n"); + return 0; + } + dev_info(&pdev->dev, "Registered device video%d\n", + vout->video_dev->minor & 0x1f); + /*vout->video_dev->dev = &pdev->dev;*/ + + video_set_drvdata(vout->video_dev, vout); + + init_MUTEX(&vout->param_lock); + init_MUTEX(&vout->busy_lock); + + /* setup outputs and cropping */ + vout->cur_disp_output = -1; + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strncmp(idstr, "DISP", 4) == 0) { + int disp_num = idstr[4] - '0'; + if (disp_num == 3) { + if (strcmp(idstr, "DISP3 BG - DI1") == 0) + disp_num = 5; + else if (strncmp(idstr, "DISP3 BG", 8) == 0) + disp_num = 4; + } + vout->crop_bounds[disp_num].left = 0; + vout->crop_bounds[disp_num].top = 0; + vout->crop_bounds[disp_num].width = + registered_fb[i]->var.xres; + vout->crop_bounds[disp_num].height = + registered_fb[i]->var.yres; + vout->output_enabled[disp_num] = true; + vout->output_fb_num[disp_num] = i; + if (vout->cur_disp_output == -1) { + vout->cur_disp_output = disp_num; + } + } + + } + vout->crop_current = vout->crop_bounds[vout->cur_disp_output]; + + platform_set_drvdata(pdev, vout); + + return 0; +} + +static int mxc_v4l2out_remove(struct platform_device *pdev) +{ + vout_data *vout = platform_get_drvdata(pdev); + + if (vout->video_dev) { + if (-1 != vout->video_dev->minor) + video_unregister_device(vout->video_dev); + else + video_device_release(vout->video_dev); + vout->video_dev = NULL; + } + + platform_set_drvdata(pdev, NULL); + + kfree(vout); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2out_driver = { + .driver = { + .name = "MXC Video Output", + }, + .probe = mxc_v4l2out_probe, + .remove = mxc_v4l2out_remove, +}; + +static struct platform_device mxc_v4l2out_device = { + .name = "MXC Video Output", + .id = 0, +}; + +/*! + * mxc v4l2 init function + * + */ +static int mxc_v4l2out_init(void) +{ + u8 err = 0; + + err = platform_driver_register(&mxc_v4l2out_driver); + if (err == 0) { + platform_device_register(&mxc_v4l2out_device); + } + return err; +} + +/*! + * mxc v4l2 cleanup function + * + */ +static void mxc_v4l2out_clean(void) +{ + video_unregister_device(g_vout->video_dev); + + platform_driver_unregister(&mxc_v4l2out_driver); + platform_device_unregister(&mxc_v4l2out_device); + kfree(g_vout); + g_vout = NULL; +} + +module_init(mxc_v4l2out_init); +module_exit(mxc_v4l2out_clean); + +module_param(video_nr, int, 0444); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2-driver for MXC video output"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/output/mxc_v4l2_output.h b/drivers/media/video/mxc/output/mxc_v4l2_output.h new file mode 100644 index 000000000000..069edde1e850 --- /dev/null +++ b/drivers/media/video/mxc/output/mxc_v4l2_output.h @@ -0,0 +1,138 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup MXC_V4L2_OUTPUT MXC V4L2 Video Output Driver + */ +/*! + * @file mxc_v4l2_output.h + * + * @brief MXC V4L2 Video Output Driver Header file + * + * Video4Linux2 Output Device using MXC IPU Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#ifndef __MXC_V4L2_OUTPUT_H__ +#define __MXC_V4L2_OUTPUT_H__ + +#include <media/v4l2-dev.h> + +#ifdef __KERNEL__ + +#include <linux/ipu.h> +#include <linux/mxc_v4l2.h> +#include <linux/videodev2.h> + +#define MIN_FRAME_NUM 2 +#define MAX_FRAME_NUM 30 + +#define MXC_V4L2_OUT_NUM_OUTPUTS 6 +#define MXC_V4L2_OUT_2_SDC 0 +#define MXC_V4L2_OUT_2_ADC 1 + + +typedef struct { + int list[MAX_FRAME_NUM + 1]; + int head; + int tail; +} v4l_queue; + +/*! + * States for the video stream + */ +typedef enum { + STATE_STREAM_OFF, + STATE_STREAM_ON, + STATE_STREAM_PAUSED, + STATE_STREAM_STOPPING, +} v4lout_state; + +/*! + * common v4l2 driver structure. + */ +typedef struct _vout_data { + struct video_device *video_dev; + /*! + * semaphore guard against SMP multithreading + */ + struct semaphore busy_lock; + + /*! + * number of process that have device open + */ + int open_count; + + /*! + * params lock for this camera + */ + struct semaphore param_lock; + + struct timer_list output_timer; + unsigned long start_jiffies; + u32 frame_count; + + v4l_queue ready_q; + v4l_queue done_q; + + s8 next_rdy_ipu_buf; + s8 next_done_ipu_buf; + s8 ipu_buf[2]; + s8 ipu_buf_p[2]; + s8 ipu_buf_n[2]; + volatile v4lout_state state; + + int cur_disp_output; + int output_fb_num[MXC_V4L2_OUT_NUM_OUTPUTS]; + int output_enabled[MXC_V4L2_OUT_NUM_OUTPUTS]; + struct v4l2_framebuffer v4l2_fb; + int ic_bypass; + ipu_channel_t display_ch; + ipu_channel_t post_proc_ch; + + /*! + * FRAME_NUM-buffering, so we need a array + */ + int buffer_cnt; + dma_addr_t queue_buf_paddr[MAX_FRAME_NUM]; + void *queue_buf_vaddr[MAX_FRAME_NUM]; + u32 queue_buf_size; + struct v4l2_buffer v4l2_bufs[MAX_FRAME_NUM]; + u32 display_buf_size; + dma_addr_t display_bufs[2]; + void *display_bufs_vaddr[2]; + dma_addr_t rot_pp_bufs[2]; + void *rot_pp_bufs_vaddr[2]; + + /*! + * Poll wait queue + */ + wait_queue_head_t v4l_bufq; + + /*! + * v4l2 format + */ + struct v4l2_format v2f; + struct v4l2_mxc_offset offset; + ipu_rotate_mode_t rotate; + + /* crop */ + struct v4l2_rect crop_bounds[MXC_V4L2_OUT_NUM_OUTPUTS]; + struct v4l2_rect crop_current; + u32 bytesperline; + enum v4l2_field field_fmt; + ipu_motion_sel motion_sel; +} vout_data; + +#endif +#endif /* __MXC_V4L2_OUTPUT_H__ */ diff --git a/drivers/media/video/pxp.c b/drivers/media/video/pxp.c new file mode 100644 index 000000000000..0c9d858630be --- /dev/null +++ b/drivers/media/video/pxp.c @@ -0,0 +1,1231 @@ +/* + * Freescale STMP378X PxP driver + * + * Author: Matt Porter <mporter@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/dma-mapping.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/vmalloc.h> +#include <linux/videodev.h> + +#include <media/videobuf-dma-contig.h> +#include <media/v4l2-common.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> + +#include <mach/platform.h> +#include <mach/regs-pxp.h> + +#include "pxp.h" + +#define PXP_DRIVER_NAME "stmp3xxx-pxp" +#define PXP_DRIVER_MAJOR 1 +#define PXP_DRIVER_MINOR 0 + +#define PXP_DEF_BUFS 2 +#define PXP_MIN_PIX 8 + +#define V4L2_OUTPUT_TYPE_INTERNAL 4 + +static struct pxp_data_format pxp_s0_formats[] = { + { + .name = "24-bit RGB", + .bpp = 4, + .fourcc = V4L2_PIX_FMT_RGB24, + .colorspace = V4L2_COLORSPACE_SRGB, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__RGB888, + }, { + .name = "16-bit RGB 5:6:5", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_RGB565, + .colorspace = V4L2_COLORSPACE_SRGB, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__RGB565, + }, { + .name = "16-bit RGB 5:5:5", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_RGB555, + .colorspace = V4L2_COLORSPACE_SRGB, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__RGB555, + }, { + .name = "YUV 4:2:0 Planar", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_YUV420, + .colorspace = V4L2_COLORSPACE_JPEG, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__YUV420, + }, { + .name = "YUV 4:2:2 Planar", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_YUV422P, + .colorspace = V4L2_COLORSPACE_JPEG, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__YUV422, + }, +}; + +struct v4l2_queryctrl pxp_controls[] = { + { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Horizontal Flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Vertical Flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, { + .id = V4L2_CID_PRIVATE_BASE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Rotation", + .minimum = 0, + .maximum = 270, + .step = 90, + .default_value = 0, + .flags = 0, + }, { + .id = V4L2_CID_PRIVATE_BASE + 1, + .name = "Background Color", + .minimum = 0, + .maximum = 0xFFFFFF, + .step = 1, + .default_value = 0, + .flags = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, { + .id = V4L2_CID_PRIVATE_BASE + 2, + .name = "YUV Colorspace", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, +}; + +static void pxp_set_ctrl(struct pxps *pxp) +{ + u32 ctrl; + + ctrl = BF(pxp->s0_fmt->ctrl_s0_fmt, PXP_CTRL_S0_FORMAT); + ctrl |= + BF(BV_PXP_CTRL_OUTPUT_RGB_FORMAT__RGB888, PXP_CTRL_OUTPUT_RGB_FORMAT); + ctrl |= BM_PXP_CTRL_CROP; + + if (pxp->scaling) + ctrl |= BM_PXP_CTRL_SCALE; + if (pxp->vflip) + ctrl |= BM_PXP_CTRL_VFLIP; + if (pxp->hflip) + ctrl |= BM_PXP_CTRL_HFLIP; + if (pxp->rotate) + ctrl |= BF(pxp->rotate/90, PXP_CTRL_ROTATE); + + ctrl |= BM_PXP_CTRL_IRQ_ENABLE; + if (pxp->active) + ctrl |= BM_PXP_CTRL_ENABLE; + + __raw_writel(ctrl, REGS_PXP_BASE + HW_PXP_CTRL); +} + +static void pxp_set_rgbbuf(struct pxps *pxp) +{ + __raw_writel(pxp->outb_phys, REGS_PXP_BASE + HW_PXP_RGBBUF); + /* Always equal to the FB size */ + __raw_writel(BF(pxp->fb.fmt.width, PXP_RGBSIZE_WIDTH) | + BF(pxp->fb.fmt.height, PXP_RGBSIZE_HEIGHT), + REGS_PXP_BASE + HW_PXP_RGBSIZE); +} + +static void pxp_set_colorkey(struct pxps *pxp) +{ + /* Low and high are set equal. V4L does not allow a chromakey range */ + __raw_writel(pxp->chromakey, REGS_PXP_BASE + HW_PXP_S0COLORKEYLOW); + __raw_writel(pxp->chromakey, REGS_PXP_BASE + HW_PXP_S0COLORKEYHIGH); +} + +static void pxp_set_oln(struct pxps *pxp) +{ + __raw_writel((u32)pxp->fb.base, REGS_PXP_BASE + HW_PXP_OL0); + __raw_writel(BF(pxp->fb.fmt.width >> 3, PXP_OLnSIZE_WIDTH) | + BF(pxp->fb.fmt.height >> 3, PXP_OLnSIZE_HEIGHT), + REGS_PXP_BASE + HW_PXP_OL0SIZE); +} + +static void pxp_set_olparam(struct pxps *pxp) +{ + u32 olparam; + struct v4l2_pix_format *fmt = &pxp->fb.fmt; + + olparam = BF(pxp->global_alpha, PXP_OLnPARAM_ALPHA); + if (fmt->pixelformat == V4L2_PIX_FMT_RGB24) + olparam |= + BF(BV_PXP_OLnPARAM_FORMAT__RGB888, PXP_OLnPARAM_FORMAT); + else + olparam |= + BF(BV_PXP_OLnPARAM_FORMAT__RGB565, PXP_OLnPARAM_FORMAT); + if (pxp->global_alpha_state) + olparam |= BF(BV_PXP_OLnPARAM_ALPHA_CNTL__Override, + PXP_OLnPARAM_ALPHA_CNTL); + if (pxp->chromakey_state) + olparam |= BM_PXP_OLnPARAM_ENABLE_COLORKEY; + if (pxp->overlay_state) + olparam |= BM_PXP_OLnPARAM_ENABLE; + __raw_writel(olparam, REGS_PXP_BASE + HW_PXP_OL0PARAM); +} + +static void pxp_set_s0param(struct pxps *pxp) +{ + u32 s0param; + + s0param = BF(pxp->drect.left >> 3, PXP_S0PARAM_XBASE); + s0param |= BF(pxp->drect.top >> 3, PXP_S0PARAM_YBASE); + s0param |= BF(pxp->s0_width >> 3, PXP_S0PARAM_WIDTH); + s0param |= BF(pxp->s0_height >> 3, PXP_S0PARAM_HEIGHT); + __raw_writel(s0param, REGS_PXP_BASE + HW_PXP_S0PARAM); +} + +static void pxp_set_s0crop(struct pxps *pxp) +{ + u32 s0crop; + + s0crop = BF(pxp->srect.left >> 3, PXP_S0CROP_XBASE); + s0crop |= BF(pxp->srect.top >> 3, PXP_S0CROP_YBASE); + s0crop |= BF(pxp->drect.width >> 3, PXP_S0CROP_WIDTH); + s0crop |= BF(pxp->drect.height >> 3, PXP_S0CROP_HEIGHT); + __raw_writel(s0crop, REGS_PXP_BASE + HW_PXP_S0CROP); +} + +static int pxp_set_scaling(struct pxps *pxp) +{ + int ret = 0; + u32 xscale, yscale, s0scale; + + if ((pxp->s0_fmt->fourcc != V4L2_PIX_FMT_YUV420) && + (pxp->s0_fmt->fourcc != V4L2_PIX_FMT_YUV422P)) { + pxp->scaling = 0; + ret = -EINVAL; + goto out; + } + + if ((pxp->srect.width == pxp->drect.width) && + (pxp->srect.height == pxp->drect.height)) { + pxp->scaling = 0; + goto out; + } + + pxp->scaling = 1; + xscale = pxp->srect.width * 0x1000 / pxp->drect.width; + yscale = pxp->srect.height * 0x1000 / pxp->drect.height; + s0scale = BF(yscale, PXP_S0SCALE_YSCALE) | + BF(xscale, PXP_S0SCALE_XSCALE); + __raw_writel(s0scale, REGS_PXP_BASE + HW_PXP_S0SCALE); + +out: + pxp_set_ctrl(pxp); + + return ret; +} + +static int pxp_set_fbinfo(struct pxps *pxp) +{ + struct fb_var_screeninfo var; + struct fb_fix_screeninfo fix; + struct v4l2_framebuffer *fb = &pxp->fb; + int err; + + err = stmp3xxxfb_get_info(&var, &fix); + + fb->fmt.width = var.xres; + fb->fmt.height = var.yres; + if (var.bits_per_pixel == 16) + fb->fmt.pixelformat = V4L2_PIX_FMT_RGB565; + else + fb->fmt.pixelformat = V4L2_PIX_FMT_RGB24; + fb->base = (void *)fix.smem_start; + return err; +} + +static void pxp_set_s0bg(struct pxps *pxp) +{ + __raw_writel(pxp->s0_bgcolor, REGS_PXP_BASE + HW_PXP_S0BACKGROUND); +} + +static void pxp_set_csc(struct pxps *pxp) +{ + if (pxp->yuv) { + /* YUV colorspace */ + __raw_writel(0x04030000, REGS_PXP_BASE + HW_PXP_CSCCOEFF0); + __raw_writel(0x01230208, REGS_PXP_BASE + HW_PXP_CSCCOEFF1); + __raw_writel(0x076b079c, REGS_PXP_BASE + HW_PXP_CSCCOEFF2); + } else { + /* YCrCb colorspace */ + __raw_writel(0x84ab01f0, REGS_PXP_BASE + HW_PXP_CSCCOEFF0); + __raw_writel(0x01230204, REGS_PXP_BASE + HW_PXP_CSCCOEFF1); + __raw_writel(0x0730079c, REGS_PXP_BASE + HW_PXP_CSCCOEFF2); + } +} + +static int pxp_set_cstate(struct pxps *pxp, struct v4l2_control *vc) +{ + + if (vc->id == V4L2_CID_HFLIP) + pxp->hflip = vc->value; + else if (vc->id == V4L2_CID_VFLIP) + pxp->vflip = vc->value; + else if (vc->id == V4L2_CID_PRIVATE_BASE) { + if (vc->value % 90) + return -ERANGE; + pxp->rotate = vc->value; + } else if (vc->id == V4L2_CID_PRIVATE_BASE + 1) { + pxp->s0_bgcolor = vc->value; + pxp_set_s0bg(pxp); + } else if (vc->id == V4L2_CID_PRIVATE_BASE + 2) { + pxp->yuv = vc->value; + pxp_set_csc(pxp); + } + + pxp_set_ctrl(pxp); + + return 0; +} + +static int pxp_get_cstate(struct pxps *pxp, struct v4l2_control *vc) +{ + if (vc->id == V4L2_CID_HFLIP) + vc->value = pxp->hflip; + else if (vc->id == V4L2_CID_VFLIP) + vc->value = pxp->vflip; + else if (vc->id == V4L2_CID_PRIVATE_BASE) + vc->value = pxp->rotate; + else if (vc->id == V4L2_CID_PRIVATE_BASE + 1) + vc->value = pxp->s0_bgcolor; + else if (vc->id == V4L2_CID_PRIVATE_BASE + 2) + vc->value = pxp->yuv; + + return 0; +} + +static int pxp_enumoutput(struct file *file, void *fh, + struct v4l2_output *o) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + if ((o->index < 0) || (o->index > 1)) + return -EINVAL; + + memset(o, 0, sizeof(struct v4l2_output)); + if (o->index == 0) { + strcpy(o->name, "PxP Display Output"); + pxp->output = 0; + } else { + strcpy(o->name, "PxP Virtual Output"); + pxp->output = 1; + } + o->type = V4L2_OUTPUT_TYPE_INTERNAL; + o->std = 0; + o->reserved[0] = pxp->outb_phys; + + return 0; +} + +static int pxp_g_output(struct file *file, void *fh, + unsigned int *i) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + *i = pxp->output; + + return 0; +} + +static int pxp_s_output(struct file *file, void *fh, + unsigned int i) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_pix_format *fmt = &pxp->fb.fmt; + int bpp; + + if ((i < 0) || (i > 1)) + return -EINVAL; + + if (pxp->outb) + goto out; + + /* Output buffer is same format as fbdev */ + if (fmt->pixelformat == V4L2_PIX_FMT_RGB24) + bpp = 4; + else + bpp = 2; + + pxp->outb = kmalloc(fmt->width * fmt->height * bpp, GFP_KERNEL); + pxp->outb_phys = virt_to_phys(pxp->outb); + dma_map_single(NULL, pxp->outb, + fmt->width * fmt->height * bpp, DMA_TO_DEVICE); + +out: + pxp_set_rgbbuf(pxp); + + return 0; +} + +static int pxp_enum_fmt_video_output(struct file *file, void *fh, + struct v4l2_fmtdesc *fmt) +{ + enum v4l2_buf_type type = fmt->type; + int index = fmt->index; + + if ((fmt->index < 0) || (fmt->index >= ARRAY_SIZE(pxp_s0_formats))) + return -EINVAL; + + memset(fmt, 0, sizeof(struct v4l2_fmtdesc)); + fmt->index = index; + fmt->type = type; + fmt->pixelformat = pxp_s0_formats[index].fourcc; + strcpy(fmt->description, pxp_s0_formats[index].name); + + return 0; +} + +static int pxp_g_fmt_video_output(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct v4l2_pix_format *pf = &f->fmt.pix; + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct pxp_data_format *fmt = pxp->s0_fmt; + + pf->width = pxp->s0_width; + pf->height = pxp->s0_height; + pf->pixelformat = fmt->fourcc; + pf->field = V4L2_FIELD_NONE; + pf->bytesperline = fmt->bpp * pf->width; + pf->sizeimage = pf->bytesperline * pf->height; + pf->colorspace = fmt->colorspace; + pf->priv = 0; + + return 0; +} + +static struct pxp_data_format *pxp_get_format(struct v4l2_format *f) +{ + struct pxp_data_format *fmt; + int i; + + for (i = 0; i < ARRAY_SIZE(pxp_s0_formats); i++) { + fmt = &pxp_s0_formats[i]; + if (fmt->fourcc == f->fmt.pix.pixelformat) + break; + } + + if (i == ARRAY_SIZE(pxp_s0_formats)) + return NULL; + + return &pxp_s0_formats[i]; +} + +static int pxp_try_fmt_video_output(struct file *file, void *fh, + struct v4l2_format *f) +{ + int w = f->fmt.pix.width; + int h = f->fmt.pix.height; + struct pxp_data_format *fmt = pxp_get_format(f); + + if (!fmt) + return -EINVAL; + + w = min(w, 2040); + w = max(w, 8); + h = min(h, 2040); + h = max(h, 8); + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.width = w; + f->fmt.pix.height = h; + f->fmt.pix.pixelformat = fmt->fourcc; + + return 0; +} + +static int pxp_s_fmt_video_output(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_pix_format *pf = &f->fmt.pix; + int ret = pxp_try_fmt_video_output(file, fh, f); + + if (ret == 0) { + pxp->s0_fmt = pxp_get_format(f); + pxp->s0_width = pf->width; + pxp->s0_height = pf->height; + pxp_set_ctrl(pxp); + pxp_set_s0param(pxp); + } + + return ret; +} + +static int pxp_g_fmt_output_overlay(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_window *wf = &f->fmt.win; + + memset(wf, 0, sizeof(struct v4l2_window)); + wf->chromakey = pxp->chromakey; + wf->global_alpha = pxp->global_alpha; + wf->field = V4L2_FIELD_NONE; + wf->clips = NULL; + wf->clipcount = 0; + wf->bitmap = NULL; + wf->w.left = pxp->srect.left; + wf->w.top = pxp->srect.top; + wf->w.width = pxp->srect.width; + wf->w.height = pxp->srect.height; + + return 0; +} + +static int pxp_try_fmt_output_overlay(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_window *wf = &f->fmt.win; + struct v4l2_rect srect; + u32 chromakey = wf->chromakey; + u8 global_alpha = wf->global_alpha; + + memcpy(&srect, &(wf->w), sizeof(struct v4l2_rect)); + + pxp_g_fmt_output_overlay(file, fh, f); + + wf->chromakey = chromakey; + wf->global_alpha = global_alpha; + + /* Constrain parameters to the input buffer */ + wf->w.left = srect.left; + wf->w.top = srect.top; + wf->w.width = min(srect.width, ((__s32)pxp->s0_width - wf->w.left)); + wf->w.height = min(srect.height, ((__s32)pxp->s0_height - wf->w.top)); + + return 0; +} + +static int pxp_s_fmt_output_overlay(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_window *wf = &f->fmt.win; + int ret = pxp_try_fmt_output_overlay(file, fh, f); + + if (ret == 0) { + pxp->srect.left = wf->w.left; + pxp->srect.top = wf->w.top; + pxp->srect.width = wf->w.width; + pxp->srect.height = wf->w.height; + pxp->global_alpha = wf->global_alpha; + pxp->chromakey = wf->chromakey; + pxp_set_s0param(pxp); + pxp_set_s0crop(pxp); + pxp_set_scaling(pxp); + pxp_set_olparam(pxp); + pxp_set_colorkey(pxp); + } + + return ret; +} + +static int pxp_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *r) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_reqbufs(&pxp->s0_vbq, r); +} + +static int pxp_querybuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_querybuf(&pxp->s0_vbq, b); +} + +static int pxp_qbuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_qbuf(&pxp->s0_vbq, b); +} + +static int pxp_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_dqbuf(&pxp->s0_vbq, b, file->f_flags & O_NONBLOCK); +} + +static int pxp_streamon(struct file *file, void *priv, + enum v4l2_buf_type t) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret = 0; + + if ((t != V4L2_BUF_TYPE_VIDEO_OUTPUT)) + return -EINVAL; + + ret = videobuf_streamon(&pxp->s0_vbq); + + if (!ret && (pxp->output == 0)) + stmp3xxxfb_cfg_pxp(1, pxp->outb_phys); + + return ret; +} + +static int pxp_streamoff(struct file *file, void *priv, + enum v4l2_buf_type t) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret = 0; + + if ((t != V4L2_BUF_TYPE_VIDEO_OUTPUT)) + return -EINVAL; + + ret = videobuf_streamoff(&pxp->s0_vbq); + + if (!ret) + stmp3xxxfb_cfg_pxp(0, 0); + + return ret; +} + +static int pxp_buf_setup(struct videobuf_queue *q, + unsigned int *count, unsigned *size) +{ + struct pxps *pxp = q->priv_data; + + *size = pxp->s0_width * pxp->s0_height * pxp->s0_fmt->bpp; + + if (0 == *count) + *count = PXP_DEF_BUFS; + + return 0; +} + +static void pxp_buf_free(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + if (in_interrupt()) + BUG(); + + videobuf_dma_contig_free(q, vb); + + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static int pxp_buf_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct pxps *pxp = q->priv_data; + int ret = 0; + + vb->width = pxp->s0_width; + vb->height = pxp->s0_height; + vb->size = vb->width * vb->height * pxp->s0_fmt->bpp; + vb->field = V4L2_FIELD_NONE; + vb->state = VIDEOBUF_NEEDS_INIT; + + + ret = videobuf_iolock(q, vb, NULL); + if (ret) + goto fail; + vb->state = VIDEOBUF_PREPARED; + + return 0; + +fail: + pxp_buf_free(q, vb); + return ret; +} + +static void pxp_buf_output(struct pxps *pxp) +{ + dma_addr_t Y, U, V; + + if (pxp->active) { + pxp->active->state = VIDEOBUF_ACTIVE; + Y = videobuf_to_dma_contig(pxp->active); + __raw_writel(Y, REGS_PXP_BASE + HW_PXP_S0BUF); + if ((pxp->s0_fmt->fourcc == V4L2_PIX_FMT_YUV420) || + (pxp->s0_fmt->fourcc == V4L2_PIX_FMT_YUV422P)) { + int s = 1; /* default to YUV 4:2:2 */ + if (pxp->s0_fmt->fourcc == V4L2_PIX_FMT_YUV420) + s = 2; + U = Y + (pxp->s0_width * pxp->s0_height); + V = U + ((pxp->s0_width * pxp->s0_height) >> s); + __raw_writel(U, REGS_PXP_BASE + HW_PXP_S0UBUF); + __raw_writel(V, REGS_PXP_BASE + HW_PXP_S0VBUF); + } + stmp3xxx_setl(BM_PXP_CTRL_ENABLE, REGS_PXP_BASE + HW_PXP_CTRL); + } +} + +static void pxp_buf_queue(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + struct pxps *pxp = q->priv_data; + unsigned long flags; + + spin_lock_irqsave(&pxp->lock, flags); + + list_add_tail(&vb->queue, &pxp->outq); + vb->state = VIDEOBUF_QUEUED; + + if (!pxp->active) { + pxp->active = vb; + pxp_buf_output(pxp); + } + + spin_unlock_irqrestore(&pxp->lock, flags); +} + +static void pxp_buf_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + pxp_buf_free(q, vb); +} + +static struct videobuf_queue_ops pxp_vbq_ops = { + .buf_setup = pxp_buf_setup, + .buf_prepare = pxp_buf_prepare, + .buf_queue = pxp_buf_queue, + .buf_release = pxp_buf_release, +}; + +static int pxp_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + memset(cap, 0, sizeof(*cap)); + strcpy(cap->driver, "pxp"); + strcpy(cap->card, "pxp"); + strlcpy(cap->bus_info, dev_name(&pxp->pdev->dev), sizeof(cap->bus_info)); + + cap->version = (PXP_DRIVER_MAJOR << 8) + PXP_DRIVER_MINOR; + + cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_VIDEO_OUTPUT_OVERLAY | + V4L2_CAP_STREAMING; + + return 0; +} + +static int pxp_g_fbuf(struct file *file, void *priv, + struct v4l2_framebuffer *fb) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + memset(fb, 0, sizeof(*fb)); + + fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY | + V4L2_FBUF_CAP_CHROMAKEY | + V4L2_FBUF_CAP_LOCAL_ALPHA | + V4L2_FBUF_CAP_GLOBAL_ALPHA; + + if (pxp->global_alpha_state) + fb->flags |= V4L2_FBUF_FLAG_GLOBAL_ALPHA; + if (pxp->local_alpha_state) + fb->flags |= V4L2_FBUF_FLAG_LOCAL_ALPHA; + if (pxp->chromakey_state) + fb->flags |= V4L2_FBUF_FLAG_CHROMAKEY; + + return 0; +} + +static int pxp_s_fbuf(struct file *file, void *priv, + struct v4l2_framebuffer *fb) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + pxp->overlay_state = + (fb->flags & V4L2_FBUF_FLAG_OVERLAY) != 0; + pxp->global_alpha_state = + (fb->flags & V4L2_FBUF_FLAG_GLOBAL_ALPHA) != 0; + pxp->local_alpha_state = + (fb->flags & V4L2_FBUF_FLAG_LOCAL_ALPHA) != 0; + /* Global alpha overrides local alpha if both are requested */ + if (pxp->global_alpha_state && pxp->local_alpha_state) + pxp->local_alpha_state = 0; + pxp->chromakey_state = + (fb->flags & V4L2_FBUF_FLAG_CHROMAKEY) != 0; + + pxp_set_olparam(pxp); + pxp_set_s0crop(pxp); + pxp_set_scaling(pxp); + + return 0; +} + +static int pxp_g_crop(struct file *file, void *fh, + struct v4l2_crop *c) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + if (c->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY) + return -EINVAL; + + c->c.left = pxp->drect.left; + c->c.top = pxp->drect.top; + c->c.width = pxp->drect.width; + c->c.height = pxp->drect.height; + + return 0; +} + +static int pxp_s_crop(struct file *file, void *fh, + struct v4l2_crop *c) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int l = c->c.left; + int t = c->c.top; + int w = c->c.width; + int h = c->c.height; + int fbw = pxp->fb.fmt.width; + int fbh = pxp->fb.fmt.height; + + if (c->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY) + return -EINVAL; + + /* Constrain parameters to FB limits */ + w = min(w, fbw); + w = max(w, PXP_MIN_PIX); + h = min(h, fbh); + h = max(h, PXP_MIN_PIX); + if ((l + w) > fbw) + l = 0; + if ((t + h) > fbh) + t = 0; + + /* Round up values to PxP pixel block */ + l = roundup(l, PXP_MIN_PIX); + t = roundup(t, PXP_MIN_PIX); + w = roundup(w, PXP_MIN_PIX); + h = roundup(h, PXP_MIN_PIX); + + pxp->drect.left = l; + pxp->drect.top = t; + pxp->drect.width = w; + pxp->drect.height = h; + + pxp_set_s0param(pxp); + pxp_set_s0crop(pxp); + pxp_set_scaling(pxp); + + return 0; +} + +static int pxp_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) + if (qc->id && qc->id == pxp_controls[i].id) { + memcpy(qc, &(pxp_controls[i]), sizeof(*qc)); + return 0; + } + + return -EINVAL; +} + +static int pxp_g_ctrl(struct file *file, void *priv, + struct v4l2_control *vc) +{ + int i; + + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) + if (vc->id == pxp_controls[i].id) + return pxp_get_cstate(pxp, vc); + + return -EINVAL; +} + +static int pxp_s_ctrl(struct file *file, void *priv, + struct v4l2_control *vc) +{ + int i; + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) + if (vc->id == pxp_controls[i].id) { + if (vc->value < pxp_controls[i].minimum || + vc->value > pxp_controls[i].maximum) + return -ERANGE; + return pxp_set_cstate(pxp, vc); + } + + return -EINVAL; +} + +void pxp_release(struct video_device *vfd) +{ + struct pxps *pxp = video_get_drvdata(vfd); + + spin_lock(&pxp->lock); + video_device_release(vfd); + spin_unlock(&pxp->lock); +} + +static int pxp_hw_init(struct pxps *pxp) +{ + struct fb_var_screeninfo var; + struct fb_fix_screeninfo fix; + int err; + + err = stmp3xxxfb_get_info(&var, &fix); + if (err) + return err; + + /* Pull PxP out of reset */ + __raw_writel(0, REGS_PXP_BASE + HW_PXP_CTRL); + + /* Config defaults */ + pxp->active = NULL; + + pxp->s0_fmt = &pxp_s0_formats[0]; + pxp->drect.left = pxp->srect.left = 0; + pxp->drect.top = pxp->srect.top = 0; + pxp->drect.width = pxp->srect.width = pxp->s0_width = var.xres; + pxp->drect.height = pxp->srect.height = pxp->s0_height = var.yres; + pxp->s0_bgcolor = 0; + + pxp->output = 0; + err = pxp_set_fbinfo(pxp); + if (err) + return err; + + pxp->scaling = 0; + pxp->hflip = 0; + pxp->vflip = 0; + pxp->rotate = 0; + pxp->yuv = 0; + + pxp->overlay_state = 0; + pxp->global_alpha_state = 0; + pxp->global_alpha = 0; + pxp->local_alpha_state = 0; + pxp->chromakey_state = 0; + pxp->chromakey = 0; + + /* Write default h/w config */ + pxp_set_ctrl(pxp); + pxp_set_s0param(pxp); + pxp_set_s0crop(pxp); + pxp_set_oln(pxp); + pxp_set_olparam(pxp); + pxp_set_colorkey(pxp); + pxp_set_csc(pxp); + + return 0; +} + +static int pxp_open(struct file *file) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret = 0; + + mutex_lock(&pxp->mutex); + pxp->users++; + + if (pxp->users > 1) { + pxp->users--; + ret = -EBUSY; + goto out; + } + +out: + mutex_unlock(&pxp->mutex); + if (ret) + return ret; + + videobuf_queue_dma_contig_init(&pxp->s0_vbq, + &pxp_vbq_ops, + &pxp->pdev->dev, + &pxp->lock, + V4L2_BUF_TYPE_VIDEO_OUTPUT, + V4L2_FIELD_NONE, + sizeof(struct videobuf_buffer), + pxp); + + return 0; +} + +static int pxp_close(struct file *file) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + videobuf_stop(&pxp->s0_vbq); + videobuf_mmap_free(&pxp->s0_vbq); + + mutex_lock(&pxp->mutex); + pxp->users--; + mutex_unlock(&pxp->mutex); + + return 0; +} + +static int pxp_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret; + + ret = videobuf_mmap_mapper(&pxp->s0_vbq, vma); + + return ret; +} + +static const struct v4l2_file_operations pxp_fops = { + .owner = THIS_MODULE, + .open = pxp_open, + .release = pxp_close, + .ioctl = video_ioctl2, + .mmap = pxp_mmap, +}; + +static const struct v4l2_ioctl_ops pxp_ioctl_ops = { + .vidioc_querycap = pxp_querycap, + + .vidioc_reqbufs = pxp_reqbufs, + .vidioc_querybuf = pxp_querybuf, + .vidioc_qbuf = pxp_qbuf, + .vidioc_dqbuf = pxp_dqbuf, + + .vidioc_streamon = pxp_streamon, + .vidioc_streamoff = pxp_streamoff, + + .vidioc_enum_output = pxp_enumoutput, + .vidioc_g_output = pxp_g_output, + .vidioc_s_output = pxp_s_output, + + .vidioc_enum_fmt_vid_out = pxp_enum_fmt_video_output, + .vidioc_try_fmt_vid_out = pxp_try_fmt_video_output, + .vidioc_g_fmt_vid_out = pxp_g_fmt_video_output, + .vidioc_s_fmt_vid_out = pxp_s_fmt_video_output, + + .vidioc_try_fmt_vid_out_overlay = pxp_try_fmt_output_overlay, + .vidioc_g_fmt_vid_out_overlay = pxp_g_fmt_output_overlay, + .vidioc_s_fmt_vid_out_overlay = pxp_s_fmt_output_overlay, + + .vidioc_g_fbuf = pxp_g_fbuf, + .vidioc_s_fbuf = pxp_s_fbuf, + + .vidioc_g_crop = pxp_g_crop, + .vidioc_s_crop = pxp_s_crop, + + .vidioc_queryctrl = pxp_queryctrl, + .vidioc_g_ctrl = pxp_g_ctrl, + .vidioc_s_ctrl = pxp_s_ctrl, +}; + +static const struct video_device pxp_template = { + .name = "PxP", + .vfl_type = VID_TYPE_OVERLAY | + VID_TYPE_CLIPPING | + VID_TYPE_SCALES, + .fops = &pxp_fops, + .release = pxp_release, + .minor = -1, + .ioctl_ops = &pxp_ioctl_ops, +}; + +static irqreturn_t pxp_irq(int irq, void *dev_id) +{ + struct pxps *pxp = (struct pxps *)dev_id; + struct videobuf_buffer *vb; + unsigned long flags; + + spin_lock_irqsave(&pxp->lock, flags); + + stmp3xxx_clearl(BM_PXP_STAT_IRQ, REGS_PXP_BASE + HW_PXP_STAT); + + vb = pxp->active; + vb->state = VIDEOBUF_DONE; + do_gettimeofday(&vb->ts); + vb->field_count++; + + list_del_init(&vb->queue); + + if (list_empty(&pxp->outq)) { + pxp->active = NULL; + goto out; + } + + pxp->active = list_entry(pxp->outq.next, + struct videobuf_buffer, + queue); + + pxp_buf_output(pxp); + +out: + wake_up(&vb->done); + + spin_unlock_irqrestore(&pxp->lock, flags); + + return IRQ_HANDLED; +} + +static int pxp_probe(struct platform_device *pdev) +{ + struct pxps *pxp; + struct resource *res; + int irq; + int err = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!res || irq < 0) { + err = -ENODEV; + goto exit; + } + + pxp = kzalloc(sizeof(*pxp), GFP_KERNEL); + if (!pxp) { + dev_err(&pdev->dev, "failed to allocate control object\n"); + err = -ENOMEM; + goto exit; + } + + dev_set_drvdata(&pdev->dev, pxp); + pxp->res = res; + pxp->irq = irq; + + INIT_LIST_HEAD(&pxp->outq); + spin_lock_init(&pxp->lock); + mutex_init(&pxp->mutex); + + if (!request_mem_region(res->start, res->end - res->start + 1, + PXP_DRIVER_NAME)) { + err = -EBUSY; + goto freepxp; + } + + pxp->regs = (void __iomem *)res->start; /* it is already ioremapped */ + pxp->pdev = pdev; + + err = request_irq(pxp->irq, pxp_irq, 0, PXP_DRIVER_NAME, pxp); + + if (err) { + dev_err(&pdev->dev, "interrupt register failed\n"); + goto release; + } + + pxp->vdev = video_device_alloc(); + if (!pxp->vdev) { + dev_err(&pdev->dev, "video_device_alloc() failed\n"); + err = -ENOMEM; + goto freeirq; + } + + memcpy(pxp->vdev, &pxp_template, sizeof(pxp_template)); + video_set_drvdata(pxp->vdev, pxp); + + err = video_register_device(pxp->vdev, VFL_TYPE_GRABBER, 0); + if (err) { + dev_err(&pdev->dev, "failed to register video device\n"); + goto freevdev; + } + + err = pxp_hw_init(pxp); + if (err) { + dev_err(&pdev->dev, "failed to initialize hardware\n"); + goto freevdev; + } + + dev_info(&pdev->dev, "initialized\n"); + +exit: + return err; + +freevdev: + video_device_release(pxp->vdev); + +freeirq: + free_irq(pxp->irq, pxp); + +release: + release_mem_region(res->start, res->end - res->start + 1); + +freepxp: + kfree(pxp); + + return err; +} + +static int __devexit pxp_remove(struct platform_device *pdev) +{ + struct pxps *pxp = platform_get_drvdata(pdev); + + video_unregister_device(pxp->vdev); + video_device_release(pxp->vdev); + + kfree(pxp->outb); + kfree(pxp); + + return 0; +} + +static struct platform_driver pxp_driver = { + .driver = { + .name = PXP_DRIVER_NAME, + }, + .probe = pxp_probe, + .remove = __exit_p(pxp_remove), +}; + + +static int __devinit pxp_init(void) +{ + return platform_driver_register(&pxp_driver); +} + +static void __exit pxp_exit(void) +{ + platform_driver_unregister(&pxp_driver); +} + +module_init(pxp_init); +module_exit(pxp_exit); + +MODULE_DESCRIPTION("STMP37xx PxP driver"); +MODULE_AUTHOR("Matt Porter <mporter@embeddedalley.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/pxp.h b/drivers/media/video/pxp.h new file mode 100644 index 000000000000..0e2cd62969ca --- /dev/null +++ b/drivers/media/video/pxp.h @@ -0,0 +1,75 @@ +/* + * Freescale STMP378X PxP driver + * + * Author: Matt Porter <mporter@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +struct pxps { + struct platform_device *pdev; + struct resource *res; + int irq; + void __iomem *regs; + + spinlock_t lock; + struct mutex mutex; + int users; + + struct video_device *vdev; + + struct videobuf_queue s0_vbq; + struct videobuf_buffer *active; + struct list_head outq; + + int output; + u32 *outb; + dma_addr_t outb_phys; + + /* Current S0 configuration */ + struct pxp_data_format *s0_fmt; + u32 s0_width; + u32 s0_height; + u32 s0_bgcolor; + + struct v4l2_framebuffer fb; + struct v4l2_rect drect; + struct v4l2_rect srect; + + /* Transformation support */ + int scaling; + int hflip; + int vflip; + int rotate; + int yuv; + + /* Output overlay support */ + int overlay_state; + int global_alpha_state; + u8 global_alpha; + int local_alpha_state; + int chromakey_state; + u32 chromakey; +}; + +struct pxp_data_format { + char *name; + unsigned int bpp; + u32 fourcc; + enum v4l2_colorspace colorspace; + u32 ctrl_s0_fmt; +}; + +extern int stmp3xxxfb_get_info(struct fb_var_screeninfo *var, + struct fb_fix_screeninfo *fix); +extern void stmp3xxxfb_cfg_pxp(int enable, dma_addr_t pxp_phys); diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index 3f2a912659af..10ba9a035a58 100644 --- a/drivers/mmc/card/Kconfig +++ b/drivers/mmc/card/Kconfig @@ -50,3 +50,15 @@ config MMC_TEST This driver is only of interest to those developing or testing a host driver. Most people should say N here. + +config SDIO_UNIFI_FS + tristate "UniFi SDIO glue for Freescale MMC/SDIO" + depends on (MMC_MXC || MMC_IMX_ESDHCI) + depends on (MACH_MX31_3DS || MACH_MX35_3DS || MACH_MX37_3DS || MACH_MX51_3DS) + help + This provides an interface between the CSR UniFi WiFi + driver and the Freescale MMC/SDIO interface. + If you have a MXC platform with a UniFi WiFi chip, + say M here. + + If unsure, say N. diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile index 0d407514f67d..bbcf742d0ea1 100644 --- a/drivers/mmc/card/Makefile +++ b/drivers/mmc/card/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_MMC_TEST) += mmc_test.o obj-$(CONFIG_SDIO_UART) += sdio_uart.o +obj-$(CONFIG_SDIO_UNIFI_FS) += unifi_fs/ diff --git a/drivers/mmc/card/unifi_fs/Makefile b/drivers/mmc/card/unifi_fs/Makefile new file mode 100644 index 000000000000..381d4a2d1fd5 --- /dev/null +++ b/drivers/mmc/card/unifi_fs/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_SDIO_UNIFI_FS) = unifi_fs.o +unifi_fs-objs = fs_lx.o diff --git a/drivers/mmc/card/unifi_fs/fs_lx.c b/drivers/mmc/card/unifi_fs/fs_lx.c new file mode 100644 index 000000000000..09972e94ea27 --- /dev/null +++ b/drivers/mmc/card/unifi_fs/fs_lx.c @@ -0,0 +1,681 @@ +/* + * fs_lx.c - Freescale SDIO glue module for UniFi. + * + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * Important: + * This module does not support more than one device driver instances. + * + */ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/version.h> +#include <linux/module.h> +#include <linux/delay.h> + +#include <linux/module.h> +#include <linux/init.h> + +#include <linux/scatterlist.h> + +#include <linux/mmc/core.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/sdio_ids.h> + +#include <linux/clk.h> +#include <linux/err.h> + +#include <mach/mmc.h> +#include <mach/gpio.h> + +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include "fs_sdio_api.h" + +struct regulator_unifi { + struct regulator *reg_gpo1; + struct regulator *reg_gpo2; + struct regulator *reg_1v5_ana_bb; + struct regulator *reg_vdd_vpa; + struct regulator *reg_1v5_dd; +}; + +static struct sdio_driver sdio_unifi_driver; + +static int fs_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id); +static void fs_sdio_remove(struct sdio_func *func); +static void fs_sdio_irq(struct sdio_func *func); +static int fs_sdio_suspend(struct device *dev, pm_message_t state); +static int fs_sdio_resume(struct device *dev); + +/* Globals to store the context to this module and the device driver */ +static struct sdio_dev *available_sdio_dev; +static struct fs_driver *available_driver; +struct mxc_unifi_platform_data *plat_data; + +extern void mxc_mmc_force_detect(int id); + +enum sdio_cmd_direction { + CMD_READ, + CMD_WRITE, +}; + +static int fsl_io_rw_direct(struct mmc_card *card, int write, unsigned fn, + unsigned addr, u8 in, u8 *out) +{ + struct mmc_command cmd; + int err; + + BUG_ON(!card); + BUG_ON(fn > 7); + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = SD_IO_RW_DIRECT; + cmd.arg = write ? 0x80000000 : 0x00000000; + cmd.arg |= fn << 28; + cmd.arg |= (write && out) ? 0x08000000 : 0x00000000; + cmd.arg |= addr << 9; + cmd.arg |= in; + cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC; + + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (err) + return err; + + if (mmc_host_is_spi(card->host)) { + /* host driver already reported errors */ + } else { + if (cmd.resp[0] & R5_ERROR) + return -EIO; + if (cmd.resp[0] & R5_FUNCTION_NUMBER) + return -EINVAL; + if (cmd.resp[0] & R5_OUT_OF_RANGE) + return -ERANGE; + } + + if (out) { + if (mmc_host_is_spi(card->host)) + *out = (cmd.resp[0] >> 8) & 0xFF; + else + *out = cmd.resp[0] & 0xFF; + } + + return 0; +} + + +int fs_sdio_readb(struct sdio_dev *fdev, int funcnum, unsigned long addr, + unsigned char *pdata) +{ + int err; + char val; + + sdio_claim_host(fdev->func); + if (funcnum == 0) + val = sdio_f0_readb(fdev->func, (unsigned int)addr, &err); + else + val = sdio_readb(fdev->func, (unsigned int)addr, &err); + sdio_release_host(fdev->func); + if (!err) + *pdata = val; + else + printk(KERN_ERR "fs_lx: readb error,fun=%d,addr=%d,data=%d," + "err=%d\n", funcnum, (int)addr, *pdata, err); + + return err; +} +EXPORT_SYMBOL(fs_sdio_readb); + +int fs_sdio_writeb(struct sdio_dev *fdev, int funcnum, unsigned long addr, + unsigned char data) +{ + int err; + + sdio_claim_host(fdev->func); + if (funcnum == 0) + err = fsl_io_rw_direct(fdev->func->card, 1, 0, addr, + data, NULL); + else + sdio_writeb(fdev->func, data, (unsigned int)addr, &err); + sdio_release_host(fdev->func); + + if (err) + printk(KERN_ERR "fs_lx: writeb error,fun=%d,addr=%d,data=%d," + "err=%d\n", funcnum, (int)addr, data, err); + return err; +} +EXPORT_SYMBOL(fs_sdio_writeb); + +int fs_sdio_block_rw(struct sdio_dev *fdev, int funcnum, unsigned long addr, + unsigned char *pdata, unsigned int count, int direction) +{ + int err; + + sdio_claim_host(fdev->func); + if (direction == CMD_READ) + err = sdio_memcpy_fromio(fdev->func, pdata, addr, count); + else + err = sdio_memcpy_toio(fdev->func, addr, pdata, count); + sdio_release_host(fdev->func); + + return err; +} +EXPORT_SYMBOL(fs_sdio_block_rw); + +int fs_sdio_enable_interrupt(struct sdio_dev *fdev, int enable) +{ + struct mmc_host *host = fdev->func->card->host; + unsigned long flags; + + spin_lock_irqsave(&fdev->lock, flags); + if (enable) { + if (!fdev->int_enabled) { + fdev->int_enabled = 1; + host->ops->enable_sdio_irq(host, 1); + } + } else { + if (fdev->int_enabled) { + host->ops->enable_sdio_irq(host, 0); + fdev->int_enabled = 0; + } + } + spin_unlock_irqrestore(&fdev->lock, flags); + + return 0; +} +EXPORT_SYMBOL(fs_sdio_enable_interrupt); + +int fs_sdio_disable(struct sdio_dev *fdev) +{ + int err; + sdio_claim_host(fdev->func); + err = sdio_disable_func(fdev->func); + sdio_release_host(fdev->func); + if (err) + printk(KERN_ERR "fs_lx:fs_sdio_disable error,err=%d\n", err); + return err; +} +EXPORT_SYMBOL(fs_sdio_disable); + +int fs_sdio_enable(struct sdio_dev *fdev) +{ + int err = 0; + + sdio_claim_host(fdev->func); + err = sdio_disable_func(fdev->func); + err = sdio_enable_func(fdev->func); + sdio_release_host(fdev->func); + if (err) + printk(KERN_ERR "fs_lx:fs_sdio_enable error,err=%d\n", err); + return err; +} +EXPORT_SYMBOL(fs_sdio_enable); + +int fs_sdio_set_max_clock_speed(struct sdio_dev *fdev, int max_khz) +{ + struct mmc_card *card = fdev->func->card; + + /* Respect the host controller's min-max. */ + max_khz *= 1000; + if (max_khz < card->host->f_min) + max_khz = card->host->f_min; + if (max_khz > card->host->f_max) + max_khz = card->host->f_max; + + card->host->ios.clock = max_khz; + card->host->ops->set_ios(card->host, &card->host->ios); + + return max_khz / 1000; +} +EXPORT_SYMBOL(fs_sdio_set_max_clock_speed); + +int fs_sdio_set_block_size(struct sdio_dev *fdev, int blksz) +{ + return 0; +} +EXPORT_SYMBOL(fs_sdio_set_block_size); + +/* + * --------------------------------------------------------------------------- + * + * Turn on the power of WIFI card + * + * --------------------------------------------------------------------------- + */ +static void fs_unifi_power_on(void) +{ + struct regulator_unifi *reg_unifi; + unsigned int tmp; + + reg_unifi = plat_data->priv; + + if (reg_unifi->reg_gpo1) + regulator_enable(reg_unifi->reg_gpo1); + if (reg_unifi->reg_gpo2) + regulator_enable(reg_unifi->reg_gpo2); + + if (plat_data->enable) + plat_data->enable(1); + + if (reg_unifi->reg_1v5_ana_bb) { + regulator_set_voltage(reg_unifi->reg_1v5_ana_bb, + 1500000, 1500000); + regulator_enable(reg_unifi->reg_1v5_ana_bb); + } + if (reg_unifi->reg_vdd_vpa) { + tmp = regulator_get_voltage(reg_unifi->reg_vdd_vpa); + if (tmp < 3000000 || tmp > 3600000) + regulator_set_voltage(reg_unifi->reg_vdd_vpa, + 3000000, 3000000); + regulator_enable(reg_unifi->reg_vdd_vpa); + } + /* WL_1V5DD should come on last, 10ms after other supplies */ + msleep(10); + if (reg_unifi->reg_1v5_dd) { + regulator_set_voltage(reg_unifi->reg_1v5_dd, + 1500000, 1500000); + regulator_enable(reg_unifi->reg_1v5_dd); + } + msleep(10); +} + +/* + * --------------------------------------------------------------------------- + * + * Turn off the power of WIFI card + * + * --------------------------------------------------------------------------- + */ +static void fs_unifi_power_off(void) +{ + struct regulator_unifi *reg_unifi; + + reg_unifi = plat_data->priv; + if (reg_unifi->reg_1v5_dd) + regulator_disable(reg_unifi->reg_1v5_dd); + if (reg_unifi->reg_vdd_vpa) + regulator_disable(reg_unifi->reg_vdd_vpa); + + if (reg_unifi->reg_1v5_ana_bb) + regulator_disable(reg_unifi->reg_1v5_ana_bb); + + if (plat_data->enable) + plat_data->enable(0); + + if (reg_unifi->reg_gpo2) + regulator_disable(reg_unifi->reg_gpo2); + + if (reg_unifi->reg_gpo1) + regulator_disable(reg_unifi->reg_gpo1); +} + +/* This should be made conditional on being slot 2 too - so we can + * use a plug in card in slot 1 + */ +int fs_sdio_hard_reset(struct sdio_dev *fdev) +{ + return 0; +} +EXPORT_SYMBOL(fs_sdio_hard_reset); + +static const struct sdio_device_id fs_sdio_ids[] = { + {SDIO_DEVICE(0x032a, 0x0001)}, + { /* end: all zeroes */ }, +}; + +static struct sdio_driver sdio_unifi_driver = { + .name = "fs_unifi", + .probe = fs_sdio_probe, + .remove = fs_sdio_remove, + .id_table = fs_sdio_ids, + .drv = { + .suspend = fs_sdio_suspend, + .resume = fs_sdio_resume, + } +}; + +int fs_sdio_register_driver(struct fs_driver *driver) +{ + int ret, retry; + + /* Switch us on, sdio device may exist if power is on by default. */ + plat_data->hardreset(0); + if (available_sdio_dev) + mxc_mmc_force_detect(plat_data->host_id); + /* Wait for card removed */ + for (retry = 0; retry < 100; retry++) { + if (!available_sdio_dev) + break; + msleep(100); + } + if (retry == 100) + printk(KERN_ERR "fs_sdio_register_driver: sdio device exists, " + "timeout for card removed"); + fs_unifi_power_on(); + plat_data->hardreset(1); + msleep(500); + mxc_mmc_force_detect(plat_data->host_id); + for (retry = 0; retry < 100; retry++) { + if (available_sdio_dev) + break; + msleep(50); + } + if (retry == 100) + printk(KERN_ERR "fs_sdio_register_driver: Timeout waiting" + " for card added\n"); + /* Store the context to the device driver to the global */ + available_driver = driver; + + /* + * If available_sdio_dev is not NULL, probe has been called, + * so pass the probe to the registered driver + */ + if (available_sdio_dev) { + /* Store the context to the new device driver */ + available_sdio_dev->driver = driver; + + printk(KERN_INFO "fs_sdio_register_driver: Glue exists, add " + "device driver and register IRQ\n"); + driver->probe(available_sdio_dev); + + /* Register the IRQ handler to the SDIO IRQ. */ + sdio_claim_host(available_sdio_dev->func); + ret = sdio_claim_irq(available_sdio_dev->func, fs_sdio_irq); + sdio_release_host(available_sdio_dev->func); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL(fs_sdio_register_driver); + +void fs_sdio_unregister_driver(struct fs_driver *driver) +{ + /* + * If available_sdio_dev is not NULL, probe has been called, + * so pass the remove to the registered driver to clean up. + */ + if (available_sdio_dev) { + struct mmc_host *host = available_sdio_dev->func->card->host; + + printk(KERN_INFO "fs_sdio_unregister_driver: Glue exists, " + "unregister IRQ and remove device driver\n"); + + /* Unregister the IRQ handler first. */ + sdio_claim_host(available_sdio_dev->func); + sdio_release_irq(available_sdio_dev->func); + sdio_release_host(available_sdio_dev->func); + + driver->remove(available_sdio_dev); + + if (!available_sdio_dev->int_enabled) { + available_sdio_dev->int_enabled = 1; + host->ops->enable_sdio_irq(host, 1); + } + + /* Invalidate the context to the device driver */ + available_sdio_dev->driver = NULL; + } + + /* invalidate the context to the device driver to the global */ + available_driver = NULL; + /* Power down the UniFi */ + fs_unifi_power_off(); + +} +EXPORT_SYMBOL(fs_sdio_unregister_driver); + +static void fs_sdio_irq(struct sdio_func *func) +{ + struct sdio_dev *fdev = (struct sdio_dev *)sdio_get_drvdata(func); + if (fdev->driver) { + if (fdev->driver->card_int_handler) + fdev->driver->card_int_handler(fdev); + } +} + +#ifdef CONFIG_PM +static int fs_sdio_suspend(struct device *dev, pm_message_t state) +{ + struct sdio_dev *fdev = available_sdio_dev; + + /* Pass event to the registered driver. */ + if (fdev->driver) + if (fdev->driver->suspend) + fdev->driver->suspend(fdev, state); + + return 0; +} + +static int fs_sdio_resume(struct device *dev) +{ + struct sdio_dev *fdev = available_sdio_dev; + + /* Pass event to the registered driver. */ + if (fdev->driver) + if (fdev->driver->resume) + fdev->driver->resume(fdev); + + return 0; +} +#else +#define fs_sdio_suspend NULL +#define fs_sdio_resume NULL +#endif + +static int fs_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct sdio_dev *fdev; + + /* Allocate our private context */ + fdev = kmalloc(sizeof(struct sdio_dev), GFP_KERNEL); + if (!fdev) + return -ENOMEM; + available_sdio_dev = fdev; + memset(fdev, 0, sizeof(struct sdio_dev)); + fdev->func = func; + fdev->vendor_id = id->vendor; + fdev->device_id = id->device; + fdev->max_blocksize = func->max_blksize; + fdev->int_enabled = 1; + spin_lock_init(&fdev->lock); + + /* Store our context in the MMC driver */ + printk(KERN_INFO "fs_sdio_probe: Add glue driver\n"); + sdio_set_drvdata(func, fdev); + + return 0; +} + +static void fs_sdio_remove(struct sdio_func *func) +{ + struct sdio_dev *fdev = (struct sdio_dev *)sdio_get_drvdata(func); + struct mmc_host *host = func->card->host; + + /* If there is a registered device driver, pass on the remove */ + if (fdev->driver) { + printk(KERN_INFO "fs_sdio_remove: Free IRQ and remove device " + "driver\n"); + /* Unregister the IRQ handler first. */ + sdio_claim_host(fdev->func); + sdio_release_irq(func); + sdio_release_host(fdev->func); + + fdev->driver->remove(fdev); + + if (!fdev->int_enabled) { + fdev->int_enabled = 1; + host->ops->enable_sdio_irq(host, 1); + } + } + + /* Unregister the card context from the MMC driver. */ + sdio_set_drvdata(func, NULL); + + /* Invalidate the global to our context. */ + available_sdio_dev = NULL; + kfree(fdev); +} + +static int fs_unifi_init(void) +{ + struct regulator_unifi *reg_unifi; + struct regulator *reg; + int err = 0; + + plat_data = get_unifi_plat_data(); + + if (!plat_data) + return -ENOENT; + + reg_unifi = kzalloc(sizeof(struct regulator_unifi), GFP_KERNEL); + if (!reg_unifi) + return -ENOMEM; + + if (plat_data->reg_gpo1) { + reg = regulator_get(NULL, plat_data->reg_gpo1); + if (!IS_ERR(reg)) + reg_unifi->reg_gpo1 = reg; + else { + err = -EINVAL; + goto err_reg_gpo1; + } + } + + if (plat_data->reg_gpo2) { + reg = regulator_get(NULL, plat_data->reg_gpo2); + if (!IS_ERR(reg)) + reg_unifi->reg_gpo2 = reg; + else { + err = -EINVAL; + goto err_reg_gpo2; + } + } + + if (plat_data->reg_1v5_ana_bb) { + reg = regulator_get(NULL, plat_data->reg_1v5_ana_bb); + if (!IS_ERR(reg)) + reg_unifi->reg_1v5_ana_bb = reg; + else { + err = -EINVAL; + goto err_reg_1v5_ana_bb; + } + } + + if (plat_data->reg_vdd_vpa) { + reg = regulator_get(NULL, plat_data->reg_vdd_vpa); + if (!IS_ERR(reg)) + reg_unifi->reg_vdd_vpa = reg; + else { + err = -EINVAL; + goto err_reg_vdd_vpa; + } + } + + if (plat_data->reg_1v5_dd) { + reg = regulator_get(NULL, plat_data->reg_1v5_dd); + if (!IS_ERR(reg)) + reg_unifi->reg_1v5_dd = reg; + else { + err = -EINVAL; + goto err_reg_1v5_dd; + } + } + plat_data->priv = reg_unifi; + return 0; + +err_reg_1v5_dd: + if (reg_unifi->reg_vdd_vpa) + regulator_put(reg_unifi->reg_vdd_vpa); +err_reg_vdd_vpa: + if (reg_unifi->reg_1v5_ana_bb) + regulator_put(reg_unifi->reg_1v5_ana_bb); +err_reg_1v5_ana_bb: + if (reg_unifi->reg_gpo2) + regulator_put(reg_unifi->reg_gpo2); +err_reg_gpo2: + if (reg_unifi->reg_gpo1) + regulator_put(reg_unifi->reg_gpo1); +err_reg_gpo1: + kfree(reg_unifi); + return err; +} + +int fs_unifi_remove(void) +{ + struct regulator_unifi *reg_unifi; + + reg_unifi = plat_data->priv; + plat_data->priv = NULL; + + if (reg_unifi->reg_1v5_dd) + regulator_put(reg_unifi->reg_1v5_dd); + if (reg_unifi->reg_vdd_vpa) + regulator_put(reg_unifi->reg_vdd_vpa); + + if (reg_unifi->reg_1v5_ana_bb) + regulator_put(reg_unifi->reg_1v5_ana_bb); + + if (reg_unifi->reg_gpo2) + regulator_put(reg_unifi->reg_gpo2); + + if (reg_unifi->reg_gpo1) + regulator_put(reg_unifi->reg_gpo1); + + kfree(reg_unifi); + return 0; +} + +/* Module init and exit, register and unregister to the SDIO/MMC driver */ +static int __init fs_sdio_init(void) +{ + int err; + + printk(KERN_INFO "Freescale: Register to MMC/SDIO driver\n"); + /* Sleep a bit - otherwise if the mmc subsystem has just started, it + * will allow us to register, then immediatly remove us! + */ + msleep(10); + err = fs_unifi_init(); + if (err) { + printk(KERN_ERR "Error: fs_unifi_init failed!\n"); + return err; + } + err = sdio_register_driver(&sdio_unifi_driver); + if (err) { + printk(KERN_ERR "Error: register sdio_unifi_driver failed!\n"); + fs_unifi_remove(); + } + return err; +} + +module_init(fs_sdio_init); + +static void __exit fs_sdio_exit(void) +{ + printk(KERN_INFO "Freescale: Unregister from MMC/SDIO driver\n"); + sdio_unregister_driver(&sdio_unifi_driver); + fs_unifi_remove(); +} + +module_exit(fs_sdio_exit); + +MODULE_DESCRIPTION("Freescale SDIO glue driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/card/unifi_fs/fs_sdio_api.h b/drivers/mmc/card/unifi_fs/fs_sdio_api.h new file mode 100644 index 000000000000..ea6ebd765e28 --- /dev/null +++ b/drivers/mmc/card/unifi_fs/fs_sdio_api.h @@ -0,0 +1,68 @@ +/* + *fs_sdio_api.h - Freescale SDIO glue module API for UniFi. + * + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + */ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef _FS_SDIO_API_H +#define _FS_SDIO_API_H + +struct sdio_dev; + +struct fs_driver { + const char *name; + int (*probe)(struct sdio_dev *fdev); + void (*remove)(struct sdio_dev *fdev); + void (*card_int_handler)(struct sdio_dev *fdev); + void (*suspend)(struct sdio_dev *fdev, pm_message_t state); + void (*resume)(struct sdio_dev *fdev); +}; + +int fs_sdio_readb(struct sdio_dev *fdev, int funcnum, + unsigned long addr, unsigned char *pdata); +int fs_sdio_writeb(struct sdio_dev *fdev, int funcnum, + unsigned long addr, unsigned char data); +int fs_sdio_block_rw(struct sdio_dev *fdev, int funcnum, + unsigned long addr, unsigned char *pdata, + unsigned int count, int direction); + +int fs_sdio_register_driver(struct fs_driver *driver); +void fs_sdio_unregister_driver(struct fs_driver *driver); +int fs_sdio_set_block_size(struct sdio_dev *fdev, int blksz); +int fs_sdio_set_max_clock_speed(struct sdio_dev *fdev, int max_khz); +int fs_sdio_enable_interrupt(struct sdio_dev *fdev, int enable); +int fs_sdio_enable(struct sdio_dev *fdev); +int fs_sdio_hard_reset(struct sdio_dev *fdev); + +struct sdio_dev { + /**< Device driver for this module. */ + struct fs_driver *driver; + + struct sdio_func *func; + + /**< Data private to the device driver. */ + void *drv_data; + + int int_enabled; + spinlock_t lock; + + uint16_t vendor_id; /**< Vendor ID of the card. */ + uint16_t device_id; /**< Device ID of the card. */ + + /**< Maximum block size supported. */ + int max_blocksize; +}; + + +#endif /* #ifndef _FS_SDIO_API_H */ diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 891ef18bd77b..ae501274f940 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -250,6 +250,51 @@ config MMC_SPI If unsure, or if your system has no SPI master driver, say N. +config MMC_MXC + tristate "Freescale MXC Multimedia Card Interface support" + depends on ARCH_MXC && MMC + help + This selects the Freescale MXC Multimedia card Interface. + If you have a MXC platform with a Multimedia Card slot, + say Y or M here. + +config MMC_IMX_ESDHCI + tristate "Freescale i.MX Secure Digital Host Controller Interface support" + depends on ARCH_MXC && MMC + help + This selects the Freescale i.MX Multimedia card Interface. + If you have a i.MX platform with a Multimedia Card slot, + say Y or M here. + + If unsure, say N. + +config MMC_IMX_ESDHCI_SELECT2 + bool "Enable second ESDHCI port" + depends on MMC_IMX_ESDHCI && ARCH_MX25 + default n + help + Enable the second ESDHC port + +config MMC_IMX_ESDHCI_PIO_MODE + bool "Freescale i.MX Secure Digital Host Controller Interface PIO mode" + depends on MMC_IMX_ESDHC != n + default n + help + This set the Freescale i.MX Multimedia card Interface to PIO mode. + If you have a i.MX platform with a Multimedia Card slot, + and want test it with PIO mode. + say Y here. + + If unsure, say N. + +config MMC_STMP3XXX + tristate "STMP37xx/378x MMC support" + depends on MMC && ARCH_STMP3XXX + help + Select Y if you would like to access STMP37xx/378x MMC support. + + If unsure, say N. + config MMC_S3C tristate "Samsung S3C SD/MMC Card Interface support" depends on ARCH_S3C2410 @@ -267,6 +312,7 @@ config MMC_SDRICOH_CS help Say Y here if your Notebook reports a Ricoh Bay1Controller PCMCIA card whenever you insert a MMC or SD card into the card slot. + say Y or M here. To compile this driver as a module, choose M here: the module will be called sdricoh_cs. diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index cf153f628457..035175610a46 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -9,13 +9,15 @@ endif obj-$(CONFIG_MMC_ARMMMCI) += mmci.o obj-$(CONFIG_MMC_PXA) += pxamci.o obj-$(CONFIG_MMC_IMX) += imxmmc.o -obj-$(CONFIG_MMC_MXC) += mxcmmc.o +obj-$(CONFIG_MMC_MXC) += mxc_mmc.o obj-$(CONFIG_MMC_SDHCI) += sdhci.o obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mmc.o obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o obj-$(CONFIG_MMC_SDHCI_S3C) += sdhci-s3c.o +obj-$(CONFIG_MMC_IMX_ESDHCI) += mx_sdhci.o +obj-$(CONFIG_MMC_STMP3XXX) += stmp3xxx_mmc.o obj-$(CONFIG_MMC_WBSD) += wbsd.o obj-$(CONFIG_MMC_AU1X) += au1xmmc.o obj-$(CONFIG_MMC_OMAP) += omap.o diff --git a/drivers/mmc/host/mx_sdhci.c b/drivers/mmc/host/mx_sdhci.c new file mode 100644 index 000000000000..b0226abcf8fd --- /dev/null +++ b/drivers/mmc/host/mx_sdhci.c @@ -0,0 +1,2154 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx_sdhci.c + * + * @brief Driver for the Freescale Semiconductor MXC eSDHC modules. + * + * This driver code is based on sdhci.c, by Pierre Ossman <drzeus@drzeus.cx>"); + * This driver supports Enhanced Secure Digital Host Controller + * modules eSDHC of MXC. eSDHC is also referred as enhanced MMC/SD + * controller. + * + * @ingroup MMC_SD + */ + +#include <linux/delay.h> +#include <linux/highmem.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/scatterlist.h> + +#include <linux/leds.h> + +#include <linux/mmc/host.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/card.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> + +#include <asm/dma.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/mach/irq.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <mach/dma.h> +#include <mach/mmc.h> + +#include "mx_sdhci.h" + +#define DRIVER_NAME "mxsdhci" + +#define DBG(f, x...) \ + pr_debug(DRIVER_NAME " [%s()]: " f, __func__, ## x) + +static unsigned int debug_quirks; +static int last_op_dir; + +/* + * Different quirks to handle when the hardware deviates from a strict + * interpretation of the SDHCI specification. + */ + +/* Controller doesn't honor resets unless we touch the clock register */ +#define SDHCI_QUIRK_CLOCK_BEFORE_RESET (1<<0) +/* Controller has bad caps bits, but really supports DMA */ +#define SDHCI_QUIRK_FORCE_DMA (1<<1) +/* Controller doesn't like to be reset when there is no card inserted. */ +#define SDHCI_QUIRK_NO_CARD_NO_RESET (1<<2) +/* Controller doesn't like clearing the power reg before a change */ +#define SDHCI_QUIRK_SINGLE_POWER_WRITE (1<<3) +/* Controller has flaky internal state so reset it on each ios change */ +#define SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS (1<<4) +/* Controller has an unusable DMA engine */ +#define SDHCI_QUIRK_BROKEN_DMA (1<<5) +/* Controller can only DMA from 32-bit aligned addresses */ +#define SDHCI_QUIRK_32BIT_DMA_ADDR (1<<6) +/* Controller can only DMA chunk sizes that are a multiple of 32 bits */ +#define SDHCI_QUIRK_32BIT_DMA_SIZE (1<<7) +/* Controller needs to be reset after each request to stay stable */ +#define SDHCI_QUIRK_RESET_AFTER_REQUEST (1<<8) +/* Controller needs voltage and power writes to happen separately */ +#define SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER (1<<9) +/* Controller has an off-by-one issue with timeout value */ +#define SDHCI_QUIRK_INCR_TIMEOUT_CONTROL (1<<10) +/* Controller only support the PIO */ +#define SDHCI_QUIRK_ONLY_PIO (1<<16) +/* Controller support the External DMA */ +#define SDHCI_QUIRK_EXTERNAL_DMA_MODE (1<<17) +/* Controller support the Internal Simple DMA */ +#define SDHCI_QUIRK_INTERNAL_SIMPLE_DMA (1<<18) +/* Controller support the Internal Advanced DMA */ +#define SDHCI_QUIRK_INTERNAL_ADVANCED_DMA (1<<19) + +/* + * defines the mxc flags refer to the special hw pre-conditons and behavior + */ +static unsigned int mxc_quirks; +#ifdef CONFIG_MMC_IMX_ESDHCI_PIO_MODE +static unsigned int debug_quirks = SDHCI_QUIRK_ONLY_PIO; +#else +static unsigned int debug_quirks; +#endif +static unsigned int mxc_wml_value = 512; +static unsigned int *adma_des_table; + +#ifndef MXC_SDHCI_NUM +#define MXC_SDHCI_NUM 4 +#endif + +static struct sdhci_chip *mxc_fix_chips[MXC_SDHCI_NUM]; + +static void sdhci_prepare_data(struct sdhci_host *, struct mmc_data *); +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 *); + +/* Used to active the SD bus */ +extern void gpio_sdhc_active(int module); +extern void gpio_sdhc_inactive(int module); +static void sdhci_dma_irq(void *devid, int error, unsigned int cnt); + +void mxc_mmc_force_detect(int id) +{ + struct sdhci_host *host; + if ((id < 0) || (id >= MXC_SDHCI_NUM)) + return; + if (!mxc_fix_chips[id]) + return; + host = mxc_fix_chips[id]->hosts[0]; + if (host->detect_irq) + return; + + schedule_work(&host->cd_wq); + return; +} + +EXPORT_SYMBOL(mxc_mmc_force_detect); + +static void sdhci_dumpregs(struct sdhci_host *host) +{ + printk(KERN_DEBUG DRIVER_NAME + ": ============== REGISTER DUMP ==============\n"); + + printk(KERN_DEBUG DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n", + readl(host->ioaddr + SDHCI_DMA_ADDRESS), + readl(host->ioaddr + SDHCI_HOST_VERSION)); + printk(KERN_DEBUG DRIVER_NAME ": Blk size: 0x%08x | Blk cnt: 0x%08x\n", + (readl(host->ioaddr + SDHCI_BLOCK_SIZE) & 0xFFFF), + (readl(host->ioaddr + SDHCI_BLOCK_COUNT) >> 16)); + printk(KERN_DEBUG DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n", + readl(host->ioaddr + SDHCI_ARGUMENT), + readl(host->ioaddr + SDHCI_TRANSFER_MODE)); + printk(KERN_DEBUG DRIVER_NAME ": Present: 0x%08x | Host ctl: 0x%08x\n", + readl(host->ioaddr + SDHCI_PRESENT_STATE), + readl(host->ioaddr + SDHCI_HOST_CONTROL)); + printk(KERN_DEBUG DRIVER_NAME ": Clock: 0x%08x\n", + readl(host->ioaddr + SDHCI_CLOCK_CONTROL)); + printk(KERN_DEBUG DRIVER_NAME ": Int stat: 0x%08x\n", + readl(host->ioaddr + SDHCI_INT_STATUS)); + printk(KERN_DEBUG DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n", + readl(host->ioaddr + SDHCI_INT_ENABLE), + readl(host->ioaddr + SDHCI_SIGNAL_ENABLE)); + printk(KERN_DEBUG DRIVER_NAME ": Caps: 0x%08x\n", + readl(host->ioaddr + SDHCI_CAPABILITIES)); + + printk(KERN_DEBUG DRIVER_NAME + ": ===========================================\n"); +} + +/*****************************************************************************\ + * * + * Low level functions * + * * +\*****************************************************************************/ + +static void sdhci_reset(struct sdhci_host *host, u8 mask) +{ + unsigned long tmp; + unsigned long mask_u32; + unsigned long reg_save = 0; + + if (host->chip->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) { + if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & + SDHCI_CARD_PRESENT)) + return; + } + + if (mask & SDHCI_RESET_ALL) + host->clock = 0; + else if (host->flags & SDHCI_CD_PRESENT) + reg_save = readl(host->ioaddr + SDHCI_HOST_CONTROL); + + tmp = readl(host->ioaddr + SDHCI_CLOCK_CONTROL) | (mask << 24); + mask_u32 = readl(host->ioaddr + SDHCI_SIGNAL_ENABLE); + writel(tmp, host->ioaddr + SDHCI_CLOCK_CONTROL); + + /* Wait max 100 ms */ + tmp = 5000; + + /* hw clears the bit when it's done */ + while ((readl(host->ioaddr + SDHCI_CLOCK_CONTROL) >> 24) & mask) { + if (tmp == 0) { + printk(KERN_ERR "%s: Reset 0x%x never completed.\n", + mmc_hostname(host->mmc), (int)mask); + sdhci_dumpregs(host); + return; + } + tmp--; + udelay(20); + } + /* + * The INT_EN SIG_EN regs have been modified after reset. + * re-configure them ag. + */ + if (!(mask & SDHCI_RESET_ALL) && (host->flags & SDHCI_CD_PRESENT)) + writel(reg_save, host->ioaddr + SDHCI_HOST_CONTROL); + if (host->flags & SDHCI_USE_DMA) + mask_u32 &= ~(SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL); + if (mxc_wml_value == 512) + writel(SDHCI_WML_128_WORDS, host->ioaddr + SDHCI_WML); + else + writel(SDHCI_WML_16_WORDS, host->ioaddr + SDHCI_WML); + writel(mask_u32 | SDHCI_INT_CARD_INT, host->ioaddr + SDHCI_INT_ENABLE); + writel(mask_u32, host->ioaddr + SDHCI_SIGNAL_ENABLE); + last_op_dir = 0; +} + +static void sdhci_init(struct sdhci_host *host) +{ + u32 intmask; + + sdhci_reset(host, SDHCI_RESET_ALL); + + intmask = SDHCI_INT_ADMA_ERROR | + SDHCI_INT_DATA_END_BIT | SDHCI_INT_DATA_CRC | + SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_INDEX | + SDHCI_INT_END_BIT | SDHCI_INT_CRC | SDHCI_INT_TIMEOUT | + SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL | + SDHCI_INT_DMA_END | SDHCI_INT_DATA_END | SDHCI_INT_RESPONSE; + + if (host->flags & SDHCI_USE_DMA) + intmask &= ~(SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL); + /* Configure the WML rege */ + if (mxc_wml_value == 512) + writel(SDHCI_WML_128_WORDS, host->ioaddr + SDHCI_WML); + else + writel(SDHCI_WML_16_WORDS, host->ioaddr + SDHCI_WML); + writel(intmask | SDHCI_INT_CARD_INT, host->ioaddr + SDHCI_INT_ENABLE); + writel(intmask, host->ioaddr + SDHCI_SIGNAL_ENABLE); +} + +static void sdhci_activate_led(struct sdhci_host *host) +{ + u32 ctrl; + + ctrl = readl(host->ioaddr + SDHCI_HOST_CONTROL); + ctrl |= SDHCI_CTRL_LED; + writel(ctrl, host->ioaddr + SDHCI_HOST_CONTROL); +} + +static void sdhci_deactivate_led(struct sdhci_host *host) +{ + u32 ctrl; + + ctrl = readl(host->ioaddr + SDHCI_HOST_CONTROL); + ctrl &= ~SDHCI_CTRL_LED; + writel(ctrl, host->ioaddr + SDHCI_HOST_CONTROL); +} + +/*****************************************************************************\ + * * + * Core functions * + * * +\*****************************************************************************/ + +static inline char *sdhci_sg_to_buffer(struct sdhci_host *host) +{ + return sg_virt(host->cur_sg); +} + +static inline int sdhci_next_sg(struct sdhci_host *host) +{ + /* + * Skip to next SG entry. + */ + host->cur_sg++; + host->num_sg--; + + /* + * Any entries left? + */ + if (host->num_sg > 0) { + host->offset = 0; + host->remain = host->cur_sg->length; + } + + return host->num_sg; +} + +static void sdhci_read_block_pio(struct sdhci_host *host) +{ + int blksize, chunk_remain; + u32 data; + char *buffer; + int size; + + DBG("PIO reading\n"); + + blksize = host->data->blksz; + chunk_remain = 0; + data = 0; + + buffer = sdhci_sg_to_buffer(host) + host->offset; + + while (blksize) { + if (chunk_remain == 0) { + data = readl(host->ioaddr + SDHCI_BUFFER); + chunk_remain = min(blksize, 4); + } + + size = min(host->remain, chunk_remain); + + chunk_remain -= size; + blksize -= size; + host->offset += size; + host->remain -= size; + + while (size) { + *buffer = data & 0xFF; + buffer++; + data >>= 8; + size--; + } + + if (host->remain == 0) { + if (sdhci_next_sg(host) == 0) { + BUG_ON(blksize != 0); + return; + } + buffer = sdhci_sg_to_buffer(host); + } + } +} + +static void sdhci_write_block_pio(struct sdhci_host *host) +{ + int blksize, chunk_remain; + u32 data; + char *buffer; + int bytes, size; + + DBG("PIO writing\n"); + + blksize = host->data->blksz; + chunk_remain = 4; + data = 0; + + bytes = 0; + buffer = sdhci_sg_to_buffer(host) + host->offset; + + while (blksize) { + size = min(host->remain, chunk_remain); + + chunk_remain -= size; + blksize -= size; + host->offset += size; + host->remain -= size; + + while (size) { + data >>= 8; + data |= (u32) *buffer << 24; + buffer++; + size--; + } + + if (chunk_remain == 0) { + writel(data, host->ioaddr + SDHCI_BUFFER); + chunk_remain = min(blksize, 4); + } + + if (host->remain == 0) { + if (sdhci_next_sg(host) == 0) { + BUG_ON(blksize != 0); + return; + } + buffer = sdhci_sg_to_buffer(host); + } + } +} + +static void sdhci_transfer_pio(struct sdhci_host *host) +{ + u32 mask; + + BUG_ON(!host->data); + + if (host->num_sg == 0) + return; + + if (host->data->flags & MMC_DATA_READ) + mask = SDHCI_DATA_AVAILABLE; + else + mask = SDHCI_SPACE_AVAILABLE; + + while (readl(host->ioaddr + SDHCI_PRESENT_STATE) & mask) { + if (host->data->flags & MMC_DATA_READ) + sdhci_read_block_pio(host); + else + sdhci_write_block_pio(host); + + if (host->num_sg == 0) + break; + } + + DBG("PIO transfer complete.\n"); +} + +static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data) +{ + u32 count; + unsigned target_timeout, current_timeout; + + WARN_ON(host->data); + + if (data == NULL) + return; + + /* Sanity checks */ + BUG_ON(data->blksz * data->blocks > 524288); + BUG_ON(data->blksz > host->mmc->max_blk_size); + BUG_ON(data->blocks > 65535); + + host->data = data; + host->data_early = 0; + if (host->data->flags & MMC_DATA_READ) + writel(readl(host->ioaddr + SDHCI_CLOCK_CONTROL) | + SDHCI_CLOCK_HLK_EN, host->ioaddr + SDHCI_CLOCK_CONTROL); + + /* timeout in us */ + target_timeout = data->timeout_ns / 1000 + + data->timeout_clks / host->clock; + + /* + * Figure out needed cycles. + * We do this in steps in order to fit inside a 32 bit int. + * The first step is the minimum timeout, which will have a + * minimum resolution of 6 bits: + * (1) 2^13*1000 > 2^22, + * (2) host->timeout_clk < 2^16 + * => + * (1) / (2) > 2^6 + */ + count = 0; + current_timeout = (1 << 13) * 1000 / host->timeout_clk; + while (current_timeout < target_timeout) { + count++; + current_timeout <<= 1; + if (count >= 0xF) + break; + } + + /* + * Compensate for an off-by-one error in the CaFe hardware; otherwise, + * a too-small count gives us interrupt timeouts. + */ + if ((host->chip->quirks & SDHCI_QUIRK_INCR_TIMEOUT_CONTROL)) + count++; + + if (count >= 0xF) { + DBG(KERN_WARNING "%s: Too large timeout requested!\n", + mmc_hostname(host->mmc)); + count = 0xE; + } + + /* Set the max time-out value to level up the compatibility */ + count = 0xE; + + count = + (count << 16) | (readl(host->ioaddr + SDHCI_CLOCK_CONTROL) & + 0xFFF0FFFF); + writel(count, host->ioaddr + SDHCI_CLOCK_CONTROL); + + if (host->flags & SDHCI_USE_DMA) + host->flags |= SDHCI_REQ_USE_DMA; + + if (unlikely((host->flags & SDHCI_REQ_USE_DMA) && + (host->chip->quirks & SDHCI_QUIRK_32BIT_DMA_SIZE) && + ((data->blksz * data->blocks) & 0x3))) { + DBG("Reverting to PIO because of transfer size (%d)\n", + data->blksz * data->blocks); + host->flags &= ~SDHCI_REQ_USE_DMA; + } + + /* + * The assumption here being that alignment is the same after + * translation to device address space. + */ + if (unlikely((host->flags & SDHCI_REQ_USE_DMA) && + (host->chip->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) && + (data->sg->offset & 0x3))) { + DBG("Reverting to PIO because of bad alignment\n"); + host->flags &= ~SDHCI_REQ_USE_DMA; + } + + if (host->flags & SDHCI_REQ_USE_DMA) { + int i; + struct scatterlist *tsg; + + host->dma_size = data->blocks * data->blksz; + count = + dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + (data-> + flags & MMC_DATA_READ) ? DMA_FROM_DEVICE : + DMA_TO_DEVICE); + BUG_ON(count != data->sg_len); + DBG("Configure the sg DMA, %s, len is 0x%x, count is %d\n", + (data->flags & MMC_DATA_READ) + ? "DMA_FROM_DEIVCE" : "DMA_TO_DEVICE", host->dma_size, + count); + + /* Make sure the ADMA mode is selected. */ + i = readl(host->ioaddr + SDHCI_HOST_CONTROL); + i |= SDHCI_CTRL_ADMA; + writel(i, host->ioaddr + SDHCI_HOST_CONTROL); + + tsg = data->sg; + /* ADMA mode is used, create the descriptor table */ + for (i = 0; i < count; i++) { + if (tsg->dma_address & 0xFFF) { + DBG(KERN_ERR "ADMA addr isn't 4K aligned.\n"); + DBG(KERN_ERR "0x%x\n", tsg->dma_address); + DBG(KERN_ERR "Changed to Single DMA mode.\n"); + goto Single_DMA; + } + adma_des_table[2 * i] = tsg->length << 12; + adma_des_table[2 * i] |= FSL_ADMA_DES_ATTR_SET; + adma_des_table[2 * i] |= FSL_ADMA_DES_ATTR_VALID; + adma_des_table[2 * i + 1] = tsg->dma_address; + adma_des_table[2 * i + 1] |= FSL_ADMA_DES_ATTR_TRAN; + adma_des_table[2 * i + 1] |= FSL_ADMA_DES_ATTR_VALID; + if (count == (i + 1)) + adma_des_table[2 * i + 1] |= + FSL_ADMA_DES_ATTR_END; + tsg++; + } + + /* Write the physical address to ADMA address reg */ + writel(virt_to_phys(adma_des_table), + host->ioaddr + SDHCI_ADMA_ADDRESS); + Single_DMA: + /* Rollback to the Single DMA mode */ + i = readl(host->ioaddr + SDHCI_HOST_CONTROL); + i &= ~SDHCI_CTRL_ADMA; + writel(i, host->ioaddr + SDHCI_HOST_CONTROL); + /* Single DMA mode is used */ + writel(sg_dma_address(data->sg), + host->ioaddr + SDHCI_DMA_ADDRESS); + } else if ((host->flags & SDHCI_USE_EXTERNAL_DMA) && + (data->blocks * data->blksz >= mxc_wml_value)) { + host->dma_size = data->blocks * data->blksz; + DBG("Configure the External DMA, %s, len is 0x%x\n", + (data->flags & MMC_DATA_READ) + ? "DMA_FROM_DEIVCE" : "DMA_TO_DEVICE", host->dma_size); + + if (data->blksz & 0x3) { + printk(KERN_ERR + "mxc_mci: block size not multiple of 4 bytes\n"); + } + + if (data->flags & MMC_DATA_READ) + host->dma_dir = DMA_FROM_DEVICE; + else + host->dma_dir = DMA_TO_DEVICE; + + host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, + data->sg_len, host->dma_dir); + + if (data->flags & MMC_DATA_READ) { + mxc_dma_sg_config(host->dma, data->sg, data->sg_len, + host->dma_size, MXC_DMA_MODE_READ); + } else { + mxc_dma_sg_config(host->dma, data->sg, data->sg_len, + host->dma_size, MXC_DMA_MODE_WRITE); + } + } else { + host->cur_sg = data->sg; + host->num_sg = data->sg_len; + + host->offset = 0; + host->remain = host->cur_sg->length; + } + + /* We do not handle DMA boundaries, so set it to max (512 KiB) */ + writel((data->blocks << 16) | SDHCI_MAKE_BLKSZ(7, data->blksz), + host->ioaddr + SDHCI_BLOCK_SIZE); +} + +static void sdhci_finish_data(struct sdhci_host *host) +{ + struct mmc_data *data; + u16 blocks; + + BUG_ON(!host->data); + + data = host->data; + host->data = NULL; + + if (host->flags & SDHCI_REQ_USE_DMA) { + dma_unmap_sg(&(host->chip->pdev)->dev, data->sg, data->sg_len, + (data->flags & MMC_DATA_READ) ? DMA_FROM_DEVICE : + DMA_TO_DEVICE); + } + if ((host->flags & SDHCI_USE_EXTERNAL_DMA) && + (host->dma_size >= mxc_wml_value) && (data != NULL)) { + dma_unmap_sg(mmc_dev(host->mmc), data->sg, + host->dma_len, host->dma_dir); + host->dma_size = 0; + } + + /* + * Controller doesn't count down when in single block mode. + */ + if (data->blocks == 1) + blocks = (data->error == 0) ? 0 : 1; + else + blocks = readl(host->ioaddr + SDHCI_BLOCK_COUNT) >> 16; + data->bytes_xfered = data->blksz * data->blocks; + + if (data->stop) { + /* + * The controller needs a reset of internal state machines + * upon error conditions. + */ + if (data->error) { + sdhci_reset(host, SDHCI_RESET_CMD); + sdhci_reset(host, SDHCI_RESET_DATA); + } + + sdhci_send_command(host, data->stop); + } else + tasklet_schedule(&host->finish_tasklet); +} + +static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) +{ + int flags; + u32 mask; + u32 mode = 0; + unsigned long timeout; + + DBG("sdhci_send_command 0x%x is starting...\n", cmd->opcode); + WARN_ON(host->cmd); + + /* Wait max 10 ms */ + timeout = 5000; + + mask = SDHCI_CMD_INHIBIT; + if ((cmd->data != NULL) || (cmd->flags & MMC_RSP_BUSY)) + mask |= SDHCI_DATA_INHIBIT; + + /* We shouldn't wait for data inihibit for stop commands, even + though they might use busy signaling */ + if (host->mrq->data && (cmd == host->mrq->data->stop)) + mask &= ~SDHCI_DATA_INHIBIT; + + while (readl(host->ioaddr + SDHCI_PRESENT_STATE) & mask) { + if (timeout == 0) { + printk(KERN_ERR "%s: Controller never released " + "inhibit bit(s).\n", mmc_hostname(host->mmc)); + sdhci_dumpregs(host); + cmd->error = -EIO; + tasklet_schedule(&host->finish_tasklet); + return; + } + timeout--; + udelay(20); + } + + mod_timer(&host->timer, jiffies + 1 * HZ); + + host->cmd = cmd; + + sdhci_prepare_data(host, cmd->data); + + writel(cmd->arg, host->ioaddr + SDHCI_ARGUMENT); + + /* Set up the transfer mode */ + if (cmd->data != NULL) { + mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_DPSEL; + if (cmd->data->blocks > 1) + mode |= SDHCI_TRNS_MULTI; + if (cmd->data->flags & MMC_DATA_READ) + mode |= SDHCI_TRNS_READ; + else + mode &= ~SDHCI_TRNS_READ; + if (host->flags & SDHCI_USE_DMA) + mode |= SDHCI_TRNS_DMA; + if (host->flags & SDHCI_USE_EXTERNAL_DMA) + DBG("Prepare data completely in %s transfer mode.\n", + "EXTTERNAL DMA"); + } + + if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) { + printk(KERN_ERR "%s: Unsupported response type!\n", + mmc_hostname(host->mmc)); + cmd->error = -EINVAL; + tasklet_schedule(&host->finish_tasklet); + return; + } + + if (!(cmd->flags & MMC_RSP_PRESENT)) + flags = SDHCI_CMD_RESP_NONE; + else if (cmd->flags & MMC_RSP_136) + flags = SDHCI_CMD_RESP_LONG; + else if (cmd->flags & MMC_RSP_BUSY) + flags = SDHCI_CMD_RESP_SHORT_BUSY; + else + flags = SDHCI_CMD_RESP_SHORT; + + if (cmd->flags & MMC_RSP_CRC) + flags |= SDHCI_CMD_CRC; + if (cmd->flags & MMC_RSP_OPCODE) + flags |= SDHCI_CMD_INDEX; + if (cmd->data) + flags |= SDHCI_CMD_DATA; + + mode |= SDHCI_MAKE_CMD(cmd->opcode, flags); + DBG("Complete sending cmd, transfer mode would be 0x%x.\n", mode); + writel(mode, host->ioaddr + SDHCI_TRANSFER_MODE); +} + +static void sdhci_finish_command(struct sdhci_host *host) +{ + int i; + + BUG_ON(host->cmd == NULL); + + if (host->cmd->flags & MMC_RSP_PRESENT) { + if (host->cmd->flags & MMC_RSP_136) { + /* CRC is stripped so we need to do some shifting. */ + for (i = 0; i < 4; i++) { + host->cmd->resp[i] = readl(host->ioaddr + + SDHCI_RESPONSE + (3 - + i) + * 4) << 8; + if (i != 3) + host->cmd->resp[i] |= + readb(host->ioaddr + + SDHCI_RESPONSE + (3 - i) * 4 - + 1); + } + } else { + host->cmd->resp[0] = + readl(host->ioaddr + SDHCI_RESPONSE); + } + } + + host->cmd->error = 0; + + if (host->data && host->data_early) + sdhci_finish_data(host); + + if (!host->cmd->data) + tasklet_schedule(&host->finish_tasklet); + + host->cmd = NULL; +} + +static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) +{ + /*This variable holds the value of clock divider, prescaler */ + int div = 0, prescaler = 0; + int clk_rate; + u32 clk; + unsigned long timeout; + + if (clock == 0) { + goto out; + } else { + if (!host->plat_data->clk_flg) { + clk_enable(host->clk); + host->plat_data->clk_flg = 1; + } + } + if (clock == host->clock) + return; + + clk_rate = clk_get_rate(host->clk); + clk = readl(host->ioaddr + SDHCI_CLOCK_CONTROL) & ~SDHCI_CLOCK_MASK; + writel(clk, host->ioaddr + SDHCI_CLOCK_CONTROL); + + if (clock == host->min_clk) + prescaler = 16; + else + prescaler = 0; + while (prescaler <= 0x80) { + for (div = 0; div <= 0xF; div++) { + int x; + if (prescaler != 0) + x = (clk_rate / (div + 1)) / (prescaler * 2); + else + x = clk_rate / (div + 1); + + DBG("x=%d, clock=%d %d\n", x, clock, div); + if (x <= clock) + break; + } + if (div < 0x10) + break; + if (prescaler == 0) + prescaler = 1; + else + prescaler <<= 1; + } + DBG("prescaler = 0x%x, divider = 0x%x\n", prescaler, div); + clk |= (prescaler << 8) | (div << 4); + + /* Configure the clock control register */ + clk |= + (readl(host->ioaddr + SDHCI_CLOCK_CONTROL) & (~SDHCI_CLOCK_MASK)); + if (host->plat_data->vendor_ver < ESDHC_VENDOR_V22) + writel(clk, host->ioaddr + SDHCI_CLOCK_CONTROL); + else + writel(clk | SDHCI_CLOCK_SD_EN, + host->ioaddr + SDHCI_CLOCK_CONTROL); + + /* Wait max 10 ms */ + timeout = 5000; + while (timeout > 0) { + timeout--; + udelay(20); + } + + out: + host->clock = clock; +} + +static void sdhci_set_power(struct sdhci_host *host, unsigned short power) +{ + int voltage = 0; + + /* There is no PWR CTL REG */ + if (host->power == power) + return; + + if (host->regulator_mmc) { + if (power == (unsigned short)-1) { + regulator_disable(host->regulator_mmc); + DBG("mmc power off\n"); + } else { + if (power == 7) + voltage = 1800000; + else if (power >= 8) + voltage = 2000000 + (power - 8) * 100000; + regulator_set_voltage(host->regulator_mmc, + voltage, voltage); + + if (regulator_enable(host->regulator_mmc) == 0) { + DBG("mmc power on\n"); + msleep(1); + } + } + } + + host->power = power; +} + +/*****************************************************************************\ + * * + * MMC callbacks * + * * +\*****************************************************************************/ + +static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct sdhci_host *host; + unsigned long flags; + + host = mmc_priv(mmc); + + /* Enable the clock */ + if (!host->plat_data->clk_flg) { + clk_enable(host->clk); + host->plat_data->clk_flg = 1; + } + + spin_lock_irqsave(&host->lock, flags); + + WARN_ON(host->mrq != NULL); + + sdhci_activate_led(host); + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 0) { + if (mrq->cmd && mrq->data) { + if (mrq->data->flags & MMC_DATA_READ) + last_op_dir = 1; + else { + if (last_op_dir) + sdhci_reset(host, + SDHCI_RESET_CMD | + SDHCI_RESET_DATA); + } + } + } + + if (host->flags & SDHCI_USE_EXTERNAL_DMA) + spin_unlock_irqrestore(&host->lock, flags); + + host->mrq = mrq; + if (!(host->flags & SDHCI_CD_PRESENT)) { + host->mrq->cmd->error = -ENOMEDIUM; + tasklet_schedule(&host->finish_tasklet); + } else + sdhci_send_command(host, mrq->cmd); + + if (!(host->flags & SDHCI_USE_EXTERNAL_DMA)) + spin_unlock_irqrestore(&host->lock, flags); + + mmiowb(); +} + +static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct sdhci_host *host; + unsigned long flags; + u32 tmp; + mxc_dma_device_t dev_id = 0; + + DBG("%s: clock %u, bus %lu, power %u, vdd %u\n", DRIVER_NAME, + ios->clock, 1UL << ios->bus_width, ios->power_mode, ios->vdd); + + host = mmc_priv(mmc); + + /* Configure the External DMA mode */ + if (host->flags & SDHCI_USE_EXTERNAL_DMA) { + host->dma_dir = DMA_NONE; + if (mmc->ios.bus_width != host->mode) { + mxc_dma_free(host->dma); + if (mmc->ios.bus_width == MMC_BUS_WIDTH_4) { + if (host->id == 0) + dev_id = MXC_DMA_MMC1_WIDTH_4; + else + dev_id = MXC_DMA_MMC2_WIDTH_4; + } else { + if (host->id == 0) + dev_id = MXC_DMA_MMC1_WIDTH_1; + else + dev_id = MXC_DMA_MMC2_WIDTH_1; + } + host->dma = mxc_dma_request(dev_id, "MXC MMC"); + if (host->dma < 0) + DBG("Cannot allocate MMC DMA channel\n"); + mxc_dma_callback_set(host->dma, sdhci_dma_irq, + (void *)host); + /* Configure the WML rege */ + if (mxc_wml_value == 512) + writel(SDHCI_WML_128_WORDS, + host->ioaddr + SDHCI_WML); + else + writel(SDHCI_WML_16_WORDS, + host->ioaddr + SDHCI_WML); + } + } + + host->mode = mmc->ios.bus_width; + + spin_lock_irqsave(&host->lock, flags); + + /* + * Reset the chip on each power off. + * Should clear out any weird states. + */ + if (ios->power_mode == MMC_POWER_OFF) { + writel(0, host->ioaddr + SDHCI_SIGNAL_ENABLE); + sdhci_init(host); + } + + sdhci_set_clock(host, ios->clock); + + if (ios->power_mode == MMC_POWER_OFF) + sdhci_set_power(host, -1); + else { + sdhci_set_power(host, ios->vdd); + if (!readl(host->ioaddr + SDHCI_SIGNAL_ENABLE)) { + tmp = readl(host->ioaddr + SDHCI_INT_ENABLE); + if (host->sdio_enable) + writel(tmp, host->ioaddr + SDHCI_SIGNAL_ENABLE); + else + writel(tmp & ~SDHCI_INT_CARD_INT, + host->ioaddr + SDHCI_SIGNAL_ENABLE); + } + } + + tmp = readl(host->ioaddr + SDHCI_HOST_CONTROL); + + if (ios->bus_width == MMC_BUS_WIDTH_4) { + tmp &= ~SDHCI_CTRL_8BITBUS; + tmp |= SDHCI_CTRL_4BITBUS; + } else if (ios->bus_width == MMC_BUS_WIDTH_8) { + tmp &= ~SDHCI_CTRL_4BITBUS; + tmp |= SDHCI_CTRL_8BITBUS; + } else if (ios->bus_width == MMC_BUS_WIDTH_1) { + tmp &= ~SDHCI_CTRL_4BITBUS; + tmp &= ~SDHCI_CTRL_8BITBUS; + } + + if (host->flags & SDHCI_USE_DMA) + tmp |= SDHCI_CTRL_ADMA; + + writel(tmp, host->ioaddr + SDHCI_HOST_CONTROL); + + /* + * Some (ENE) controllers go apeshit on some ios operation, + * signalling timeout and CRC errors even on CMD0. Resetting + * it on each ios seems to solve the problem. + */ + if (host->chip->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS) + sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); + + mmiowb(); + spin_unlock_irqrestore(&host->lock, flags); +} + +static int sdhci_get_ro(struct mmc_host *mmc) +{ + struct sdhci_host *host; + + host = mmc_priv(mmc); + + if (host->plat_data->wp_status) + return host->plat_data->wp_status(mmc->parent); + else + return 0; +} + +static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct sdhci_host *host; + unsigned long flags; + u32 ier, prot, clk, present; + + host = mmc_priv(mmc); + + spin_lock_irqsave(&host->lock, flags); + + if (enable) { + if (host->sdio_enable++) + goto exit_unlock; + } else { + if (--(host->sdio_enable)) + goto exit_unlock; + } + /* Enable the clock */ + if (!host->plat_data->clk_flg) { + clk_enable(host->clk); + host->plat_data->clk_flg = 1; + } + ier = readl(host->ioaddr + SDHCI_SIGNAL_ENABLE); + prot = readl(host->ioaddr + SDHCI_HOST_CONTROL); + clk = readl(host->ioaddr + SDHCI_CLOCK_CONTROL); + + if (enable) { + ier |= SDHCI_INT_CARD_INT; + prot |= SDHCI_CTRL_D3CD; + clk |= SDHCI_CLOCK_PER_EN | SDHCI_CLOCK_IPG_EN; + present = readl(host->ioaddr + SDHCI_PRESENT_STATE); + if ((present & SDHCI_CARD_INT_MASK) != SDHCI_CARD_INT_ID) + writel(SDHCI_INT_CARD_INT, + host->ioaddr + SDHCI_INT_STATUS); + } else { + ier &= ~SDHCI_INT_CARD_INT; + prot &= ~SDHCI_CTRL_D3CD; + clk &= ~(SDHCI_CLOCK_PER_EN | SDHCI_CLOCK_IPG_EN); + } + + writel(prot, host->ioaddr + SDHCI_HOST_CONTROL); + writel(ier, host->ioaddr + SDHCI_SIGNAL_ENABLE); + writel(clk, host->ioaddr + SDHCI_CLOCK_CONTROL); + + mmiowb(); + exit_unlock: + spin_unlock_irqrestore(&host->lock, flags); +} + +static const struct mmc_host_ops sdhci_ops = { + .request = sdhci_request, + .set_ios = sdhci_set_ios, + .get_ro = sdhci_get_ro, + .enable_sdio_irq = sdhci_enable_sdio_irq, +}; + +/*****************************************************************************\ + * * + * Tasklets * + * * +\*****************************************************************************/ + +static void sdhci_tasklet_card(unsigned long param) +{ + struct sdhci_host *host; + unsigned long flags; + unsigned int cd_status = 0; + + host = (struct sdhci_host *)param; + + if (host->flags & SDHCI_CD_PRESENT) + host->flags &= ~SDHCI_CD_PRESENT; + else + host->flags |= SDHCI_CD_PRESENT; + /* Detect there is a card in slot or not */ + DBG("cd_status=%d %s\n", cd_status, + (host->flags & SDHCI_CD_PRESENT) ? "inserted" : "removed"); + + spin_lock_irqsave(&host->lock, flags); + + if (!(host->flags & SDHCI_CD_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); + + host->mrq->cmd->error = -ENOMEDIUM; + tasklet_schedule(&host->finish_tasklet); + } + } + + spin_unlock_irqrestore(&host->lock, flags); + + mmc_detect_change(host->mmc, msecs_to_jiffies(200)); +} + +static void sdhci_tasklet_finish(unsigned long param) +{ + struct sdhci_host *host; + unsigned long flags; + struct mmc_request *mrq; + + host = (struct sdhci_host *)param; + + spin_lock_irqsave(&host->lock, flags); + + del_timer(&host->timer); + + mrq = host->mrq; + + /* + * The controller needs a reset of internal state machines + * upon error conditions. + */ + if (mrq->cmd->error || + (mrq->data && (mrq->data->error || + (mrq->data->stop && mrq->data->stop->error))) || + (host->chip->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST)) { + + /* Some controllers need this kick or reset won't work here */ + if (host->chip->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) { + unsigned int clock; + + /* This is to force an update */ + clock = host->clock; + host->clock = 0; + sdhci_set_clock(host, clock); + } + + /* Spec says we should do both at the same time, but Ricoh + controllers do not like that. */ + sdhci_reset(host, SDHCI_RESET_CMD); + sdhci_reset(host, SDHCI_RESET_DATA); + } + + host->mrq = NULL; + host->cmd = NULL; + host->data = NULL; + + sdhci_deactivate_led(host); + + mmiowb(); + spin_unlock_irqrestore(&host->lock, flags); + + /* Stop the clock when the req is done */ + flags = SDHCI_DATA_ACTIVE | SDHCI_DOING_WRITE | SDHCI_DOING_READ; + if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & flags)) { + if (host->plat_data->clk_flg) { + clk_disable(host->clk); + host->plat_data->clk_flg = 0; + } + } + + mmc_request_done(host->mmc, mrq); +} + +static void sdhci_timeout_timer(unsigned long data) +{ + struct sdhci_host *host; + unsigned long tmp, flags; + + host = (struct sdhci_host *)data; + + spin_lock_irqsave(&host->lock, flags); + + if (host->mrq) { + printk(KERN_ERR "%s: Timeout waiting for hardware " + "interrupt.\n", mmc_hostname(host->mmc)); + sdhci_dumpregs(host); + + if (host->data) { + host->data->error = -ETIMEDOUT; + sdhci_finish_data(host); + } else { + if (host->cmd) + host->cmd->error = -ETIMEDOUT; + else + host->mrq->cmd->error = -ETIMEDOUT; + + tasklet_schedule(&host->finish_tasklet); + } + + if (!readl(host->ioaddr + SDHCI_SIGNAL_ENABLE)) { + printk(KERN_ERR "%s, ERROR SIG_INT is 0.\n", __func__); + tmp = readl(host->ioaddr + SDHCI_INT_ENABLE); + if (host->sdio_enable) + writel(tmp, host->ioaddr + SDHCI_SIGNAL_ENABLE); + else + writel(tmp & ~SDHCI_INT_CARD_INT, + host->ioaddr + SDHCI_SIGNAL_ENABLE); + if (!host->plat_data->status(host->mmc->parent)) + schedule_work(&host->cd_wq); + } + } + + mmiowb(); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void sdhci_cd_timer(unsigned long data) +{ + struct sdhci_host *host; + + host = (struct sdhci_host *)data; + schedule_work(&host->cd_wq); +} + +/*****************************************************************************\ + * * + * Interrupt handling * + * * +\*****************************************************************************/ + +static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask) +{ + BUG_ON(intmask == 0); + + if (!host->cmd) { + 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); + return; + } + + if (intmask & SDHCI_INT_TIMEOUT) + host->cmd->error = -ETIMEDOUT; + else if (intmask & (SDHCI_INT_CRC | SDHCI_INT_END_BIT | + SDHCI_INT_INDEX)) + host->cmd->error = -EILSEQ; + + if (host->cmd->error) + tasklet_schedule(&host->finish_tasklet); + else if (intmask & SDHCI_INT_RESPONSE) + sdhci_finish_command(host); +} + +static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) +{ + u32 intsave = 0; + + BUG_ON(intmask == 0); + + if (!host->data) { + /* + * A data end interrupt is sent together with the response + * for the stop command. + */ + if (intmask & SDHCI_INT_DATA_END) + return; + + 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); + sdhci_reset(host, SDHCI_RESET_CMD); + sdhci_reset(host, SDHCI_RESET_DATA); + return; + } + + /* Mask the INT */ + intsave = readl(host->ioaddr + SDHCI_INT_ENABLE); + writel(intsave & (~(intmask & SDHCI_INT_DATA_RE_MASK)), + host->ioaddr + SDHCI_INT_ENABLE); + + if (intmask & SDHCI_INT_DATA_TIMEOUT) + host->data->error = -ETIMEDOUT; + else if (intmask & (SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_END_BIT)) + host->data->error = -EILSEQ; + + if (host->data->error) + sdhci_finish_data(host); + else { + if ((host->flags & SDHCI_USE_EXTERNAL_DMA) && + (host->dma_size >= mxc_wml_value)) { + /* Use DMA if transfer size is greater than fifo size */ + if (intmask & (SDHCI_INT_DATA_AVAIL | + SDHCI_INT_SPACE_AVAIL)) { + intsave &= ~SDHCI_INT_DATA_RE_MASK; + if (mxc_dma_enable(host->dma) < 0) { + printk(KERN_ERR "ENABLE SDMA ERR.\n"); + intsave |= SDHCI_INT_DATA_RE_MASK; + } + } + } else { + if (intmask & (SDHCI_INT_DATA_AVAIL | + SDHCI_INT_SPACE_AVAIL)) + sdhci_transfer_pio(host); + } + + /* + * We currently don't do anything fancy with DMA + * boundaries, but as we can't disable the feature + * we need to at least restart the transfer. + */ + if ((intmask & SDHCI_INT_DMA_END) && + (!(intmask & SDHCI_INT_DATA_END))) + writel(readl(host->ioaddr + SDHCI_DMA_ADDRESS), + host->ioaddr + SDHCI_DMA_ADDRESS); + + if (intmask & SDHCI_INT_DATA_END) { + if (host->data->flags & MMC_DATA_READ) + writel(readl(host->ioaddr + SDHCI_CLOCK_CONTROL) + & ~SDHCI_CLOCK_HLK_EN, + host->ioaddr + SDHCI_CLOCK_CONTROL); + if (host->cmd) { + /* + * Data managed to finish before the + * command completed. Make sure we do + * things in the proper order. + */ + host->data_early = 1; + } else { + + if (host->plat_data->vendor_ver + < ESDHC_VENDOR_V22) { + /* + * There are the DATA END INT when + * writing is not complete. Double + * check on it. TO2 has been fixed it. + */ + intmask = readl(host->ioaddr + + SDHCI_PRESENT_STATE); + if (intmask & SDHCI_DATA_ACTIVE) + goto data_irq_out; + } + sdhci_finish_data(host); + } + } + } + data_irq_out: + /* Enable the INT */ + writel(intsave, host->ioaddr + SDHCI_INT_ENABLE); +} + +/*! +* This function is called by DMA Interrupt Service Routine to indicate +* requested DMA transfer is completed. +* +* @param devid pointer to device specific structure +* @param error any DMA error +* @param cnt amount of data that was transferred +*/ +static void sdhci_dma_irq(void *devid, int error, unsigned int cnt) +{ + u32 intsave = 0; + int ret; + struct sdhci_host *host = devid; + + DBG("%s: error: %d Transferred bytes:%d\n", DRIVER_NAME, error, cnt); + if (host->flags & SDHCI_USE_EXTERNAL_DMA) { + /* + * Stop the DMA transfer here, the data_irq would be called + * to process the others + */ + ret = mxc_dma_disable(host->dma); + if (ret < 0) + printk(KERN_ERR "Disable dma channel err %d\n", ret); + + if (error) { + DBG("Error in DMA transfer\n"); + return; + } + intsave = readl(host->ioaddr + SDHCI_INT_ENABLE); + intsave |= SDHCI_INT_DATA_RE_MASK; + writel(intsave, host->ioaddr + SDHCI_INT_ENABLE); + } +} + +/* woke queue handler func */ +static void esdhc_cd_callback(struct work_struct *work) +{ + unsigned long flags; + unsigned int cd_status = 0; + struct sdhci_host *host = container_of(work, struct sdhci_host, cd_wq); + + cd_status = host->plat_data->status(host->mmc->parent); + if (cd_status) + host->flags &= ~SDHCI_CD_PRESENT; + else + host->flags |= SDHCI_CD_PRESENT; + /* Detect there is a card in slot or not */ + DBG("cd_status=%d %s\n", cd_status, + (host->flags & SDHCI_CD_PRESENT) ? "inserted" : "removed"); + + spin_lock_irqsave(&host->lock, flags); + + if (!(host->flags & SDHCI_CD_PRESENT)) { + printk(KERN_INFO + "%s: Card removed and resetting controller.\n", + mmc_hostname(host->mmc)); + if (host->mrq) { + struct mmc_data *data; + data = host->data; + host->data = NULL; + + printk(KERN_ERR + "%s: Card removed during transfer!\n", + mmc_hostname(host->mmc)); + printk(KERN_ERR + "%s: Resetting controller.\n", + mmc_hostname(host->mmc)); + + if ((host->flags & SDHCI_USE_EXTERNAL_DMA) && + (data != NULL)) { + dma_unmap_sg(mmc_dev(host->mmc), data->sg, + host->dma_len, host->dma_dir); + host->dma_size = 0; + } + sdhci_reset(host, SDHCI_RESET_CMD); + sdhci_reset(host, SDHCI_RESET_DATA); + + host->mrq->cmd->error = -ENOMEDIUM; + tasklet_schedule(&host->finish_tasklet); + } + + if (host->init_flag > 0) + /* The initialization of sdhc controller has been + * done in the resume func */ + host->init_flag--; + else + sdhci_init(host); + } + + spin_unlock_irqrestore(&host->lock, flags); + + if (host->flags & SDHCI_CD_PRESENT) { + del_timer(&host->cd_timer); + mmc_detect_change(host->mmc, msecs_to_jiffies(100)); + } else + mmc_detect_change(host->mmc, 0); +} + +/*! +* Card detection interrupt service routine registered to handle +* the SDHC interrupts. This interrupt routine handles card +* insertion and card removal interrupts. +* +* @param irq the interrupt number +* @param devid driver private data +* +* @return The function returns \b IRQ_RETVAL(1) +*/ +static irqreturn_t sdhci_cd_irq(int irq, void *dev_id) +{ + unsigned int cd_status = 0; + struct sdhci_host *host = dev_id; + + do { + if (host->detect_irq == 0) + break; + cd_status = host->plat_data->status(host->mmc->parent); + if (cd_status) + set_irq_type(host->detect_irq, IRQF_TRIGGER_FALLING); + else + set_irq_type(host->detect_irq, IRQF_TRIGGER_RISING); + } while (cd_status != host->plat_data->status(host->mmc->parent)); + + DBG("cd_status=%d %s\n", cd_status, cd_status ? "removed" : "inserted"); + + cd_status = host->plat_data->status(host->mmc->parent); + if (!cd_status) + /* If there is a card in the slot, the timer is start + * to work. Then the card detection would be carried + * after the timer is timeout. + * */ + mod_timer(&host->cd_timer, jiffies + HZ / 2); + else + /* If there is no card, call the card detection func + * immediately. */ + schedule_work(&host->cd_wq); + + return IRQ_HANDLED; +} + +static irqreturn_t sdhci_irq(int irq, void *dev_id) +{ + irqreturn_t result; + struct sdhci_host *host = dev_id; + u32 intmask; + int cardint = 0; + + spin_lock(&host->lock); + + intmask = readl(host->ioaddr + SDHCI_INT_STATUS); + + if (!intmask || intmask == 0xffffffff) { + result = IRQ_NONE; + goto out; + } + + DBG("*** %s got interrupt: 0x%08x\n", mmc_hostname(host->mmc), intmask); + + if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) { + writel(intmask & + (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE), + host->ioaddr + SDHCI_INT_STATUS); + tasklet_schedule(&host->card_tasklet); + } + + intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE); + + if (intmask & SDHCI_INT_CMD_MASK) { + writel(intmask & SDHCI_INT_CMD_MASK, + host->ioaddr + SDHCI_INT_STATUS); + sdhci_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK); + } + + if (intmask & SDHCI_INT_DATA_MASK) { + writel(intmask & SDHCI_INT_DATA_MASK, + host->ioaddr + SDHCI_INT_STATUS); + if (cpu_is_mx35_rev(CHIP_REV_2_0) < 0) { + if (! + (readl(host->ioaddr + SDHCI_TRANSFER_MODE) & + SDHCI_TRNS_READ)) + intmask &= ~SDHCI_INT_DATA_END_BIT; + } + if (intmask & SDHCI_INT_DATA_MASK) + sdhci_data_irq(host, intmask & SDHCI_INT_DATA_MASK); + } + + intmask &= ~(SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK); + + intmask &= ~SDHCI_INT_ERROR; + + if (intmask & SDHCI_INT_BUS_POWER) { + printk(KERN_ERR "%s: Card is consuming too much power!\n", + mmc_hostname(host->mmc)); + writel(SDHCI_INT_BUS_POWER, host->ioaddr + SDHCI_INT_STATUS); + } + + intmask &= ~SDHCI_INT_BUS_POWER; + + if (intmask & SDHCI_INT_CARD_INT) + cardint = readl(host->ioaddr + SDHCI_SIGNAL_ENABLE) & + SDHCI_INT_CARD_INT; + + intmask &= ~SDHCI_INT_CARD_INT; + + if (intmask) { + printk(KERN_ERR "%s: Unexpected interrupt 0x%08x.\n", + mmc_hostname(host->mmc), intmask); + sdhci_dumpregs(host); + + writel(intmask, host->ioaddr + SDHCI_INT_STATUS); + } + + result = IRQ_HANDLED; + + mmiowb(); + out: + spin_unlock(&host->lock); + + /* + * We have to delay this as it calls back into the driver. + */ + if (cardint) + mmc_signal_sdio_irq(host->mmc); + + return result; +} + +/*****************************************************************************\ + * * + * Suspend/resume * + * * +\*****************************************************************************/ + +#ifdef CONFIG_PM + +static int sdhci_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct sdhci_chip *chip; + int i, ret; + + chip = dev_get_drvdata(&pdev->dev); + if (!chip) + return 0; + + DBG("Suspending...\n"); + + for (i = 0; i < chip->num_slots; i++) { + if (!chip->hosts[i]) + continue; + ret = mmc_suspend_host(chip->hosts[i]->mmc, state); + if (ret) { + for (i--; i >= 0; i--) + mmc_resume_host(chip->hosts[i]->mmc); + return ret; + } + } + + for (i = 0; i < chip->num_slots; i++) { + if (!chip->hosts[i]) + continue; + free_irq(chip->hosts[i]->irq, chip->hosts[i]); + } + + return 0; +} + +static int sdhci_resume(struct platform_device *pdev) +{ + struct sdhci_chip *chip; + int i, ret; + + chip = dev_get_drvdata(&pdev->dev); + if (!chip) + return 0; + + DBG("Resuming...\n"); + + for (i = 0; i < chip->num_slots; i++) { + if (!chip->hosts[i]) + continue; + ret = request_irq(chip->hosts[i]->irq, sdhci_irq, + IRQF_SHARED, + mmc_hostname(chip->hosts[i]->mmc), + chip->hosts[i]); + if (ret) + return ret; + sdhci_init(chip->hosts[i]); + chip->hosts[i]->init_flag = 2; + mmiowb(); + ret = mmc_resume_host(chip->hosts[i]->mmc); + if (ret) + return ret; + } + + return 0; +} + +#else /* CONFIG_PM */ + +#define sdhci_suspend NULL +#define sdhci_resume NULL + +#endif /* CONFIG_PM */ + +/*****************************************************************************\ + * * + * Device probing/removal * + * * +\*****************************************************************************/ + +static int __devinit sdhci_probe_slot(struct platform_device + *pdev, int slot) +{ + struct mxc_mmc_platform_data *mmc_plat = pdev->dev.platform_data; + int ret = 0; + unsigned int version, caps; + struct sdhci_chip *chip; + struct mmc_host *mmc; + struct sdhci_host *host; + mxc_dma_device_t dev_id = 0; + + if (!mmc_plat) + return -EINVAL; + + chip = dev_get_drvdata(&pdev->dev); + BUG_ON(!chip); + + mmc = mmc_alloc_host(sizeof(struct sdhci_host), &pdev->dev); + if (!mmc) + return -ENOMEM; + + host = mmc_priv(mmc); + host->mmc = mmc; + host->id = pdev->id; + host->dma = -1; + host->plat_data = mmc_plat; + if (!host->plat_data) { + ret = -EINVAL; + goto out0; + } + + host->chip = chip; + chip->hosts[slot] = host; + + /* Get pwr supply for eSDHC */ + if (NULL != mmc_plat->power_mmc) { + host->regulator_mmc = + regulator_get(&pdev->dev, mmc_plat->power_mmc); + if (IS_ERR(host->regulator_mmc)) { + ret = PTR_ERR(host->regulator_mmc); + goto out1; + } + if (regulator_enable(host->regulator_mmc) == 0) { + DBG("mmc power on\n"); + msleep(1); + } + } + + /* Active the eSDHC bus */ + gpio_sdhc_active(pdev->id); + + /* Get the SDHC clock from clock system APIs */ + host->clk = clk_get(&pdev->dev, mmc_plat->clock_mmc); + if (NULL == host->clk) + printk(KERN_ERR "MXC MMC can't get clock.\n"); + DBG("SDHC:%d clock:%lu\n", pdev->id, clk_get_rate(host->clk)); + + host->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!host->res) { + ret = -ENOMEM; + goto out2; + } + host->irq = platform_get_irq(pdev, 0); + if (!host->irq) { + ret = -ENOMEM; + goto out2; + } + host->detect_irq = platform_get_irq(pdev, 1); + if (!host->detect_irq) { + host->flags &= ~SDHCI_CD_PRESENT; + if ((pdev->id >= 0) && (pdev->id < MXC_SDHCI_NUM)) + mxc_fix_chips[pdev->id] = chip; + goto no_detect_irq; + } + + do { + ret = host->plat_data->status(host->mmc->parent); + if (ret) + set_irq_type(host->detect_irq, IRQF_TRIGGER_FALLING); + else + set_irq_type(host->detect_irq, IRQF_TRIGGER_RISING); + } while (ret != host->plat_data->status(host->mmc->parent)); + + ret = host->plat_data->status(host->mmc->parent); + if (ret) + host->flags &= ~SDHCI_CD_PRESENT; + else + host->flags |= SDHCI_CD_PRESENT; + + no_detect_irq: + DBG("slot %d at 0x%x, irq %d \n", slot, host->res->start, host->irq); + if (!request_mem_region(host->res->start, + host->res->end - + host->res->start + 1, pdev->name)) { + printk(KERN_ERR "request_mem_region failed\n"); + ret = -ENOMEM; + goto out2; + } + host->ioaddr = (void *)ioremap(host->res->start, host->res->end - + host->res->start + 1); + if (!host->ioaddr) { + ret = -ENOMEM; + goto out3; + } + + sdhci_reset(host, SDHCI_RESET_ALL); + + version = readl(host->ioaddr + SDHCI_HOST_VERSION); + host->plat_data->vendor_ver = (version & SDHCI_VENDOR_VER_MASK) >> + SDHCI_VENDOR_VER_SHIFT; + version = (version & SDHCI_SPEC_VER_MASK) >> SDHCI_SPEC_VER_SHIFT; + if (version != 1) { + printk(KERN_ERR "%s: Unknown controller version (%d). " + "You may experience problems.\n", mmc_hostname(mmc), + version); + } + + caps = readl(host->ioaddr + SDHCI_CAPABILITIES); + + if (chip->quirks & SDHCI_QUIRK_FORCE_DMA) + host->flags |= SDHCI_USE_DMA; + else if (!(caps & SDHCI_CAN_DO_DMA)) + DBG("Controller doesn't have DMA capability\n"); + else if (chip-> + quirks & (SDHCI_QUIRK_INTERNAL_ADVANCED_DMA | + SDHCI_QUIRK_INTERNAL_SIMPLE_DMA)) + host->flags |= SDHCI_USE_DMA; + else if (chip->quirks & (SDHCI_QUIRK_EXTERNAL_DMA_MODE)) + host->flags |= SDHCI_USE_EXTERNAL_DMA; + else + host->flags &= ~SDHCI_USE_DMA; + + /* + * These definitions of eSDHC are not compatible with the SD Host + * Controller Spec v2.0 + */ + host->min_clk = mmc_plat->min_clk; + host->max_clk = mmc_plat->max_clk; + host->timeout_clk = 1024 * 1000; /* Just set the value temply. */ + + /* + * Set host parameters. + */ + mmc->ops = &sdhci_ops; + mmc->f_min = host->min_clk; + mmc->f_max = host->max_clk; + mmc->caps = MMC_CAP_SDIO_IRQ; + mmc->caps |= mmc_plat->caps; + + if (caps & SDHCI_CAN_DO_HISPD) + mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED; + + mmc->ocr_avail = mmc_plat->ocr_mask; + if (caps & SDHCI_CAN_VDD_330) + mmc->ocr_avail |= MMC_VDD_32_33 | MMC_VDD_33_34; + if (caps & SDHCI_CAN_VDD_300) + mmc->ocr_avail |= MMC_VDD_29_30 | MMC_VDD_30_31; + if (caps & SDHCI_CAN_VDD_180) + mmc->ocr_avail |= MMC_VDD_165_195; + + if (mmc->ocr_avail == 0) { + printk(KERN_ERR "%s: Hardware doesn't report any " + "support voltages.\n", mmc_hostname(mmc)); + ret = -ENODEV; + goto out3; + } + + spin_lock_init(&host->lock); + + /* + * Maximum number of segments. Hardware cannot do scatter lists. + */ + if (host->flags & SDHCI_USE_DMA) + mmc->max_hw_segs = 1; + else + mmc->max_hw_segs = 16; + mmc->max_phys_segs = 16; + + /* + * Maximum number of sectors in one transfer. Limited by DMA boundary + * size (512KiB). + */ + if (host->flags & SDHCI_USE_EXTERNAL_DMA) + mmc->max_req_size = 32 * 1024; + else + mmc->max_req_size = 524288; + + /* + * Maximum segment size. Could be one segment with the maximum number + * of bytes. + */ + mmc->max_seg_size = mmc->max_req_size; + + /* + * Maximum block size. This varies from controller to controller and + * is specified in the capabilities register. + */ + mmc->max_blk_size = + (caps & SDHCI_MAX_BLOCK_MASK) >> SDHCI_MAX_BLOCK_SHIFT; + if (mmc->max_blk_size > 3) { + printk(KERN_WARNING "%s: Invalid maximum block size, " + "assuming 512 bytes\n", mmc_hostname(mmc)); + mmc->max_blk_size = 512; + } else + mmc->max_blk_size = 512 << mmc->max_blk_size; + + /* + * Maximum block count. + */ + mmc->max_blk_count = 65535; + + /* + * Apply a continous physical memory used for storing the ADMA + * descriptor table. + */ + if (host->flags & SDHCI_USE_DMA) { + adma_des_table = kcalloc((2 * (mmc->max_phys_segs) + 1), + sizeof(unsigned int), GFP_DMA); + if (adma_des_table == NULL) { + printk(KERN_ERR "Cannot allocate ADMA memory\n"); + ret = -ENOMEM; + goto out3; + } + } + + /* + * Init tasklets. + */ + tasklet_init(&host->card_tasklet, + sdhci_tasklet_card, (unsigned long)host); + tasklet_init(&host->finish_tasklet, + sdhci_tasklet_finish, (unsigned long)host); + + /* initialize the work queue */ + INIT_WORK(&host->cd_wq, esdhc_cd_callback); + + setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host); + setup_timer(&host->cd_timer, sdhci_cd_timer, (unsigned long)host); + + if (host->detect_irq) { + ret = request_irq(host->detect_irq, sdhci_cd_irq, 0, + pdev->name, host); + if (ret) + goto out4; + } + + ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED, pdev->name, host); + if (ret) + goto out5; + + sdhci_init(host); + + if (host->flags & SDHCI_USE_EXTERNAL_DMA) { + /* Apply the 1-bit SDMA channel. */ + if (host->id == 0) + dev_id = MXC_DMA_MMC1_WIDTH_1; + else + dev_id = MXC_DMA_MMC2_WIDTH_1; + host->dma = mxc_dma_request(dev_id, "MXC MMC"); + if (host->dma < 0) { + DBG("Cannot allocate MMC DMA channel\n"); + goto out6; + } + mxc_dma_callback_set(host->dma, sdhci_dma_irq, (void *)host); + } +#ifdef CONFIG_MMC_DEBUG + sdhci_dumpregs(host); +#endif + + mmiowb(); + + if (mmc_add_host(mmc) < 0) + goto out6; + if (host->flags & SDHCI_USE_EXTERNAL_DMA) + printk(KERN_INFO "%s: SDHCI detect irq %d irq %d %s\n", + mmc_hostname(mmc), host->detect_irq, host->irq, + "EXTERNAL DMA"); + else + printk(KERN_INFO "%s: SDHCI detect irq %d irq %d %s\n", + mmc_hostname(mmc), host->detect_irq, host->irq, + (host->flags & SDHCI_USE_DMA) ? "INTERNAL DMA" : "PIO"); + + return 0; + + out6: + free_irq(host->irq, host); + out5: + if (host->detect_irq) + free_irq(host->detect_irq, host); + else { + if ((pdev->id >= 0) && (pdev->id < MXC_SDHCI_NUM)) + mxc_fix_chips[pdev->id] = chip; + } + out4: + del_timer_sync(&host->timer); + del_timer_sync(&host->cd_timer); + tasklet_kill(&host->card_tasklet); + tasklet_kill(&host->finish_tasklet); + out3: + if (host->flags & SDHCI_USE_DMA) + kfree(adma_des_table); + release_mem_region(host->res->start, + host->res->end - host->res->start + 1); + out2: + clk_disable(host->clk); + host->plat_data->clk_flg = 0; + clk_put(host->clk); + out1: + gpio_sdhc_inactive(pdev->id); + out0: + mmc_free_host(mmc); + return ret; +} + +static void sdhci_remove_slot(struct platform_device *pdev, int slot) +{ + struct sdhci_chip *chip; + struct mmc_host *mmc; + struct sdhci_host *host; + + chip = dev_get_drvdata(&pdev->dev); + host = chip->hosts[slot]; + mmc = host->mmc; + + chip->hosts[slot] = NULL; + + mmc_remove_host(mmc); + + sdhci_reset(host, SDHCI_RESET_ALL); + + if (host->detect_irq) + free_irq(host->detect_irq, host); + else { + if ((pdev->id >= 0) && (pdev->id < MXC_SDHCI_NUM)) + mxc_fix_chips[pdev->id] = NULL; + } + free_irq(host->irq, host); + if (chip->quirks & SDHCI_QUIRK_EXTERNAL_DMA_MODE) { + host->flags &= ~SDHCI_USE_EXTERNAL_DMA; + mxc_dma_free(host->dma); + } + + del_timer_sync(&host->timer); + + tasklet_kill(&host->card_tasklet); + tasklet_kill(&host->finish_tasklet); + + if (host->flags & SDHCI_USE_DMA) + kfree(adma_des_table); + release_mem_region(host->res->start, + host->res->end - host->res->start + 1); + clk_disable(host->clk); + host->plat_data->clk_flg = 0; + clk_put(host->clk); + mmc_free_host(mmc); + gpio_sdhc_inactive(pdev->id); +} + +static int sdhci_probe(struct platform_device *pdev) +{ + int ret = 0, i; + u8 slots = 1; + struct sdhci_chip *chip; + + printk(KERN_INFO DRIVER_NAME ": MXC SDHCI Controller Driver. \n"); + BUG_ON(pdev == NULL); + + chip = kzalloc(sizeof(struct sdhci_chip) + + sizeof(struct sdhci_host *) * slots, GFP_KERNEL); + if (!chip) { + ret = -ENOMEM; + goto err; + } + + /* Distinguish different platform */ + if (machine_is_mx37_3ds()) { + mxc_quirks = SDHCI_QUIRK_EXTERNAL_DMA_MODE; + } else { + mxc_quirks = SDHCI_QUIRK_INTERNAL_ADVANCED_DMA | + SDHCI_QUIRK_INTERNAL_SIMPLE_DMA; + } + chip->pdev = pdev; + chip->quirks = mxc_quirks; + + if (debug_quirks) + chip->quirks = debug_quirks; + + chip->num_slots = slots; + dev_set_drvdata(&pdev->dev, chip); + + for (i = 0; i < slots; i++) { + ret = sdhci_probe_slot(pdev, i); + if (ret) { + for (i--; i >= 0; i--) + sdhci_remove_slot(pdev, i); + goto free; + } + } + + return 0; + + free: + dev_set_drvdata(&pdev->dev, NULL); + kfree(chip); + + err: + return ret; +} + +static int sdhci_remove(struct platform_device *pdev) +{ + int i; + struct sdhci_chip *chip; + + chip = dev_get_drvdata(&pdev->dev); + + if (chip) { + for (i = 0; i < chip->num_slots; i++) + sdhci_remove_slot(pdev, i); + + dev_set_drvdata(&pdev->dev, NULL); + + kfree(chip); + } + + return 0; +} + +static struct platform_driver sdhci_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = sdhci_probe, + .remove = sdhci_remove, + .suspend = sdhci_suspend, + .resume = sdhci_resume, +}; + +/*****************************************************************************\ + * * + * Driver init/exit * + * * +\*****************************************************************************/ + +static int __init sdhci_drv_init(void) +{ + printk(KERN_INFO DRIVER_NAME + ": MXC Secure Digital Host Controller Interface driver\n"); + return platform_driver_register(&sdhci_driver); +} + +static void __exit sdhci_drv_exit(void) +{ + DBG("Exiting\n"); + + platform_driver_unregister(&sdhci_driver); +} + +module_init(sdhci_drv_init); +module_exit(sdhci_drv_exit); + +module_param(debug_quirks, uint, 0444); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Secure Digital Host Controller Interface driver"); +MODULE_LICENSE("GPL"); + +MODULE_PARM_DESC(debug_quirks, "Force certain quirks."); diff --git a/drivers/mmc/host/mx_sdhci.h b/drivers/mmc/host/mx_sdhci.h new file mode 100644 index 000000000000..509d444a6e81 --- /dev/null +++ b/drivers/mmc/host/mx_sdhci.h @@ -0,0 +1,275 @@ +/* + * linux/drivers/mmc/host/mx_sdhci.h - Secure Digital Host + * Controller Interface driver + * + * Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved. + * Copyright 2008-2009 Freescale Semiconductor, Inc. All 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. + */ + +/* + * Controller registers + */ + +#define SDHCI_DMA_ADDRESS 0x00 + +#define SDHCI_BLOCK_SIZE 0x04 +#define SDHCI_MAKE_BLKSZ(dma, blksz) (((dma & 0x7) << 13) | (blksz & 0x1FFF)) + +#define SDHCI_BLOCK_COUNT 0x04 + +#define SDHCI_ARGUMENT 0x08 + +#define SDHCI_TRANSFER_MODE 0x0C +#define SDHCI_TRNS_DMA 0x00000001 +#define SDHCI_TRNS_BLK_CNT_EN 0x00000002 +#define SDHCI_TRNS_ACMD12 0x00000004 +#define SDHCI_TRNS_READ 0x00000010 +#define SDHCI_TRNS_MULTI 0x00000020 +#define SDHCI_TRNS_DPSEL 0x00200000 +#define SDHCI_TRNS_MASK 0xFFFF0000 + +#define SDHCI_COMMAND 0x0E +#define SDHCI_CMD_RESP_MASK 0x03 +#define SDHCI_CMD_CRC 0x08 +#define SDHCI_CMD_INDEX 0x10 +#define SDHCI_CMD_DATA 0x20 + +#define SDHCI_CMD_RESP_NONE 0x00 +#define SDHCI_CMD_RESP_LONG 0x01 +#define SDHCI_CMD_RESP_SHORT 0x02 +#define SDHCI_CMD_RESP_SHORT_BUSY 0x03 + +#define SDHCI_MAKE_CMD(c, f) (((c & 0xff) << 8) | (f & 0xff)) << 16 + +#define SDHCI_RESPONSE 0x10 + +#define SDHCI_BUFFER 0x20 + +#define SDHCI_PRESENT_STATE 0x24 +#define SDHCI_CMD_INHIBIT 0x00000001 +#define SDHCI_DATA_INHIBIT 0x00000002 +#define SDHCI_DATA_ACTIVE 0x00000004 +#define SDHCI_DOING_WRITE 0x00000100 +#define SDHCI_DOING_READ 0x00000200 +#define SDHCI_SPACE_AVAILABLE 0x00000400 +#define SDHCI_DATA_AVAILABLE 0x00000800 +#define SDHCI_CARD_PRESENT 0x00010000 +#define SDHCI_WRITE_PROTECT 0x00080000 +#define SDHCI_DAT0_IDLE 0x01000000 +#define SDHCI_CARD_INT_MASK 0x0E000000 +#define SDHCI_CARD_INT_ID 0x0C000000 + +#define SDHCI_HOST_CONTROL 0x28 +#define SDHCI_CTRL_LED 0x00000001 +#define SDHCI_CTRL_4BITBUS 0x00000002 +#define SDHCI_CTRL_8BITBUS 0x00000004 +#define SDHCI_CTRL_HISPD 0x00000004 +#define SDHCI_CTRL_DMA_MASK 0x18 +#define SDHCI_CTRL_SDMA 0x00 +#define SDHCI_CTRL_ADMA1 0x08 +#define SDHCI_CTRL_ADMA32 0x10 +#define SDHCI_CTRL_ADMA64 0x18 +#define SDHCI_CTRL_D3CD 0x00000008 +#define SDHCI_CTRL_ADMA 0x00000100 +/* wake up control */ +#define SDHCI_CTRL_WECINS 0x04000000 + +#define SDHCI_POWER_CONTROL 0x29 +#define SDHCI_POWER_ON 0x01 +#define SDHCI_POWER_180 0x0A +#define SDHCI_POWER_300 0x0C +#define SDHCI_POWER_330 0x0E + +#define SDHCI_BLOCK_GAP_CONTROL 0x2A + +#define SDHCI_WAKE_UP_CONTROL 0x2B + +#define SDHCI_CLOCK_CONTROL 0x2C +#define SDHCI_DIVIDER_SHIFT 8 +#define SDHCI_CLOCK_SD_EN 0x00000008 +#define SDHCI_CLOCK_PER_EN 0x00000004 +#define SDHCI_CLOCK_HLK_EN 0x00000002 +#define SDHCI_CLOCK_IPG_EN 0x00000001 +#define SDHCI_CLOCK_MASK 0x0000FFFF + +#define SDHCI_TIMEOUT_CONTROL 0x2E + +#define SDHCI_SOFTWARE_RESET 0x2F +#define SDHCI_RESET_ALL 0x01 +#define SDHCI_RESET_CMD 0x02 +#define SDHCI_RESET_DATA 0x04 + +#define SDHCI_INT_STATUS 0x30 +#define SDHCI_INT_ENABLE 0x34 +#define SDHCI_SIGNAL_ENABLE 0x38 +#define SDHCI_INT_RESPONSE 0x00000001 +#define SDHCI_INT_DATA_END 0x00000002 +#define SDHCI_INT_DMA_END 0x00000008 +#define SDHCI_INT_SPACE_AVAIL 0x00000010 +#define SDHCI_INT_DATA_AVAIL 0x00000020 +#define SDHCI_INT_CARD_INSERT 0x00000040 +#define SDHCI_INT_CARD_REMOVE 0x00000080 +#define SDHCI_INT_CARD_INT 0x00000100 +#define SDHCI_INT_ERROR 0x00008000 +#define SDHCI_INT_TIMEOUT 0x00010000 +#define SDHCI_INT_CRC 0x00020000 +#define SDHCI_INT_END_BIT 0x00040000 +#define SDHCI_INT_INDEX 0x00080000 +#define SDHCI_INT_DATA_TIMEOUT 0x00100000 +#define SDHCI_INT_DATA_CRC 0x00200000 +#define SDHCI_INT_DATA_END_BIT 0x00400000 +#define SDHCI_INT_BUS_POWER 0x00800000 +#define SDHCI_INT_ACMD12ERR 0x01000000 +#define SDHCI_INT_ADMA_ERROR 0x10000000 + +#define SDHCI_INT_NORMAL_MASK 0x00007FFF +#define SDHCI_INT_ERROR_MASK 0xFFFF8000 + +#define SDHCI_INT_CMD_MASK (SDHCI_INT_RESPONSE | SDHCI_INT_TIMEOUT | \ + SDHCI_INT_CRC | SDHCI_INT_END_BIT | SDHCI_INT_INDEX) +#define SDHCI_INT_DATA_MASK (SDHCI_INT_DATA_END | SDHCI_INT_DMA_END | \ + SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL | \ + SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_DATA_CRC | \ + SDHCI_INT_DATA_END_BIT | SDHCI_INT_ADMA_ERROR) +#define SDHCI_INT_DATA_RE_MASK (SDHCI_INT_DMA_END | \ + SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL) + +#define SDHCI_ACMD12_ERR 0x3C + +/* 3E-3F reserved */ + +#define SDHCI_CAPABILITIES 0x40 +#define SDHCI_TIMEOUT_CLK_MASK 0x0000003F +#define SDHCI_TIMEOUT_CLK_SHIFT 0 +#define SDHCI_TIMEOUT_CLK_UNIT 0x00000080 +#define SDHCI_CLOCK_BASE_MASK 0x00003F00 +#define SDHCI_CLOCK_BASE_SHIFT 8 +#define SDHCI_MAX_BLOCK_MASK 0x00030000 +#define SDHCI_MAX_BLOCK_SHIFT 16 +#define SDHCI_CAN_DO_ADMA2 0x00080000 +#define SDHCI_CAN_DO_ADMA1 0x00100000 +#define SDHCI_CAN_DO_HISPD 0x00200000 +#define SDHCI_CAN_DO_DMA 0x00400000 +#define SDHCI_CAN_VDD_330 0x01000000 +#define SDHCI_CAN_VDD_300 0x02000000 +#define SDHCI_CAN_VDD_180 0x04000000 +#define SDHCI_CAN_64BIT 0x10000000 + +/* 44-47 reserved for more caps */ +#define SDHCI_WML 0x44 +#define SDHCI_WML_4_WORDS 0x00040004 +#define SDHCI_WML_16_WORDS 0x00100010 +#define SDHCI_WML_64_WORDS 0x00400040 +#define SDHCI_WML_128_WORDS 0x00800080 + +#define SDHCI_MAX_CURRENT 0x48 + +/* 4C-4F reserved for more max current */ + +#define SDHCI_SET_ACMD12_ERROR 0x50 +#define SDHCI_SET_INT_ERROR 0x52 + +#define SDHCI_ADMA_ERROR 0x54 + +/* 55-57 reserved */ + +#define SDHCI_ADMA_ADDRESS 0x58 + +/* 60-FB reserved */ + +/* ADMA Addr Descriptor Attribute Filed */ +enum { + FSL_ADMA_DES_ATTR_VALID = 0x01, + FSL_ADMA_DES_ATTR_END = 0x02, + FSL_ADMA_DES_ATTR_INT = 0x04, + FSL_ADMA_DES_ATTR_SET = 0x10, + FSL_ADMA_DES_ATTR_TRAN = 0x20, + FSL_ADMA_DES_ATTR_LINK = 0x30, +}; + +#define SDHCI_HOST_VERSION 0xFC +#define SDHCI_VENDOR_VER_MASK 0xFF00 +#define SDHCI_VENDOR_VER_SHIFT 8 +#define SDHCI_SPEC_VER_MASK 0x00FF +#define SDHCI_SPEC_VER_SHIFT 0 +#define SDHCI_SPEC_100 0 +#define SDHCI_SPEC_200 1 +#define ESDHC_VENDOR_V22 0x12 + +struct sdhci_chip; + +struct sdhci_host { + struct sdhci_chip *chip; + struct mmc_host *mmc; /* MMC structure */ + +#ifdef CONFIG_LEDS_CLASS + struct led_classdev led; /* LED control */ +#endif + + spinlock_t lock; /* Mutex */ + + int init_flag; /* Host has been initialized */ + int flags; /* Host attributes */ +#define SDHCI_USE_DMA (1<<0) /* Host is DMA capable */ +#define SDHCI_REQ_USE_DMA (1<<1) /* Use DMA for this req. */ +#define SDHCI_USE_EXTERNAL_DMA (1<<2) /* Use the External DMA */ +#define SDHCI_CD_PRESENT (1<<8) /* CD present */ +#define SDHCI_WP_ENABLED (1<<9) /* Write protect */ + + unsigned int max_clk; /* Max possible freq (MHz) */ + unsigned int min_clk; /* Min possible freq (MHz) */ + unsigned int timeout_clk; /* Timeout freq (KHz) */ + + unsigned int clock; /* Current clock (MHz) */ + unsigned short power; /* Current voltage */ + struct regulator *regulator_mmc; /*! Regulator */ + + struct mmc_request *mrq; /* Current request */ + struct mmc_command *cmd; /* Current command */ + struct mmc_data *data; /* Current data request */ + unsigned int data_early:1; /* Data finished before cmd */ + + unsigned int id; /* Id for SD/MMC block */ + int mode; /* SD/MMC mode */ + int dma; /* DMA channel number. */ + unsigned int dma_size; /* Number of Bytes in DMA */ + unsigned int dma_len; /* Length of the s-g list */ + unsigned int dma_dir; /* DMA transfer direction */ + + struct scatterlist *cur_sg; /* We're working on this */ + int num_sg; /* Entries left */ + int offset; /* Offset into current sg */ + int remain; /* Bytes left in current */ + + struct resource *res; /* IO map memory */ + int irq; /* Device IRQ */ + int detect_irq; /* Card Detect IRQ number. */ + int sdio_enable; /* sdio interrupt enable number. */ + struct clk *clk; /* Clock id */ + int bar; /* PCI BAR index */ + unsigned long addr; /* Bus address */ + void __iomem *ioaddr; /* Mapped address */ + + struct tasklet_struct card_tasklet; /* Tasklet structures */ + struct tasklet_struct finish_tasklet; + struct work_struct cd_wq; /* card detection work queue */ + /* Platform specific data */ + struct mxc_mmc_platform_data *plat_data; + + struct timer_list timer; /* Timer for timeouts */ + struct timer_list cd_timer; /* Timer for cd */ +}; + +struct sdhci_chip { + struct platform_device *pdev; + + unsigned long quirks; + + int num_slots; /* Slots on controller */ + struct sdhci_host *hosts[0]; /* Pointers to hosts */ +}; diff --git a/drivers/mmc/host/mxc_mmc.c b/drivers/mmc/host/mxc_mmc.c new file mode 100644 index 000000000000..980dc98c9b5f --- /dev/null +++ b/drivers/mmc/host/mxc_mmc.c @@ -0,0 +1,1530 @@ +/* + * linux/drivers/mmc/host/mxc_mmc.c - Freescale MXC/i.MX MMC driver + * + * based on imxmmc.c + * Copyright (C) 2004 Sascha Hauer, Pengutronix <sascha@saschahauer.de> + * + * derived from pxamci.c by Russell King + * + * 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. + * + */ + +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_mmc.c + * + * @brief Driver for the Freescale Semiconductor MXC SDHC modules. + * + * This driver code is based on imxmmc.c, by Sascha Hauer, + * Pengutronix <sascha@saschahauer.de>. This driver supports both Secure Digital + * Host Controller modules (SDHC1 and SDHC2) of MXC. SDHC is also referred as + * MMC/SD controller. This code is not tested for SD cards. + * + * @ingroup MMC_SD + */ + +/* + * Include Files + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/dma-mapping.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sd.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> + +#include <mach/dma.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/sizes.h> +#include <asm/mach-types.h> +#include <asm/mach/irq.h> +#include <mach/mmc.h> + +#include "mxc_mmc.h" + +#define RSP_TYPE(x) ((x) & ~(MMC_RSP_BUSY|MMC_RSP_OPCODE)) + +/* + * This define is used to test the driver without using DMA + */ +#define MXC_MMC_DMA_ENABLE + +/*! + * Maxumum length of s/g list, only length of 1 is currently supported + */ +#define NR_SG 1 + +#ifdef CONFIG_MMC_DEBUG +static void dump_cmd(struct mmc_command *cmd) +{ + printk(KERN_INFO "%s: CMD: opcode: %d ", DRIVER_NAME, cmd->opcode); + printk(KERN_INFO "arg: 0x%08x ", cmd->arg); + printk(KERN_INFO "flags: 0x%08x\n", cmd->flags); +} + +static void dump_status(const char *func, int sts) +{ + unsigned int bitset; + printk(KERN_INFO "%s:status: ", func); + while (sts) { + /* Find the next bit set */ + bitset = sts & ~(sts - 1); + switch (bitset) { + case STATUS_CARD_INSERTION: + printk(KERN_INFO "CARD_INSERTION|"); + break; + case STATUS_CARD_REMOVAL: + printk(KERN_INFO "CARD_REMOVAL |"); + break; + case STATUS_YBUF_EMPTY: + printk(KERN_INFO "YBUF_EMPTY |"); + break; + case STATUS_XBUF_EMPTY: + printk(KERN_INFO "XBUF_EMPTY |"); + break; + case STATUS_YBUF_FULL: + printk(KERN_INFO "YBUF_FULL |"); + break; + case STATUS_XBUF_FULL: + printk(KERN_INFO "XBUF_FULL |"); + break; + case STATUS_BUF_UND_RUN: + printk(KERN_INFO "BUF_UND_RUN |"); + break; + case STATUS_BUF_OVFL: + printk(KERN_INFO "BUF_OVFL |"); + break; + case STATUS_READ_OP_DONE: + printk(KERN_INFO "READ_OP_DONE |"); + break; + case STATUS_WR_CRC_ERROR_CODE_MASK: + printk(KERN_INFO "WR_CRC_ERROR_CODE |"); + break; + case STATUS_READ_CRC_ERR: + printk(KERN_INFO "READ_CRC_ERR |"); + break; + case STATUS_WRITE_CRC_ERR: + printk(KERN_INFO "WRITE_CRC_ERR |"); + break; + case STATUS_SDIO_INT_ACTIVE: + printk(KERN_INFO "SDIO_INT_ACTIVE |"); + break; + case STATUS_END_CMD_RESP: + printk(KERN_INFO "END_CMD_RESP |"); + break; + case STATUS_WRITE_OP_DONE: + printk(KERN_INFO "WRITE_OP_DONE |"); + break; + case STATUS_CARD_BUS_CLK_RUN: + printk(KERN_INFO "CARD_BUS_CLK_RUN |"); + break; + case STATUS_BUF_READ_RDY: + printk(KERN_INFO "BUF_READ_RDY |"); + break; + case STATUS_BUF_WRITE_RDY: + printk(KERN_INFO "BUF_WRITE_RDY |"); + break; + case STATUS_RESP_CRC_ERR: + printk(KERN_INFO "RESP_CRC_ERR |"); + break; + case STATUS_TIME_OUT_RESP: + printk(KERN_INFO "TIME_OUT_RESP |"); + break; + case STATUS_TIME_OUT_READ: + printk(KERN_INFO "TIME_OUT_READ |"); + break; + default: + printk(KERN_INFO "Invalid Status Register value0x%x\n", + bitset); + break; + } + sts &= ~bitset; + } + printk(KERN_INFO "\n"); +} +#endif + +/*! + * This structure is a way for the low level driver to define their own + * \b mmc_host structure. This structure includes the core \b mmc_host + * structure that is provided by Linux MMC/SD Bus protocol driver as an + * element and has other elements that are specifically required by this + * low-level driver. + */ +struct mxcmci_host { + /*! + * The mmc structure holds all the information about the device + * structure, current SDHC io bus settings, the current OCR setting, + * devices attached to this host, and so on. + */ + struct mmc_host *mmc; + + /*! + * This variable is used for locking the host data structure from + * multiple access. + */ + spinlock_t lock; + + /*! + * Resource structure, which will maintain base addresses and IRQs. + */ + struct resource *res; + + /*! + * Base address of SDHC, used in readl and writel. + */ + void *base; + + /*! + * SDHC IRQ number. + */ + int irq; + + /*! + * Card Detect IRQ number. + */ + int detect_irq; + + /*! + * Clock id to hold ipg_perclk. + */ + struct clk *clk; + /*! + * MMC mode. + */ + int mode; + + /*! + * DMA channel number. + */ + int dma; + + /*! + * Pointer to hold MMC/SD request. + */ + struct mmc_request *req; + + /*! + * Pointer to hold MMC/SD command. + */ + struct mmc_command *cmd; + + /*! + * Pointer to hold MMC/SD data. + */ + struct mmc_data *data; + + /*! + * Holds the number of bytes to transfer using DMA. + */ + unsigned int dma_size; + + /*! + * Value to store in Command and Data Control Register + * - currently unused + */ + unsigned int cmdat; + + /*! + * Regulator + */ + struct regulator *regulator_mmc; + + /*! + * Current vdd settting + */ + int current_vdd; + + /*! + * Power mode - currently unused + */ + unsigned int power_mode; + + /*! + * DMA address for scatter-gather transfers + */ + dma_addr_t sg_dma; + + /*! + * Length of the scatter-gather list + */ + unsigned int dma_len; + + /*! + * Holds the direction of data transfer. + */ + unsigned int dma_dir; + + /*! + * Id for MMC block. + */ + unsigned int id; + + /*! + * Note whether this driver has been suspended. + */ + unsigned int mxc_mmc_suspend_flag; + + /*! + * sdio_irq enable/disable ref count + */ + int sdio_irq_cnt; + + /*! + * Platform specific data + */ + struct mxc_mmc_platform_data *plat_data; +}; + +extern void gpio_sdhc_active(int module); +extern void gpio_sdhc_inactive(int module); + +#ifdef MXC_MMC_DMA_ENABLE +static void mxcmci_dma_irq(void *devid, int error, unsigned int cnt); +#endif +static int mxcmci_data_done(struct mxcmci_host *host, unsigned int stat); + +/* Wait count to start the clock */ +#define CMD_WAIT_CNT 100 + +#define MAX_HOST 10 +static struct mmc_host *hosts[MAX_HOST]; + +void mxc_mmc_force_detect(int id) +{ + if (id < MAX_HOST) + mmc_detect_change(hosts[id], msecs_to_jiffies(100)); +} + +EXPORT_SYMBOL(mxc_mmc_force_detect); + +/*! + This function sets the SDHC register to stop the clock and waits for the + * clock stop indication. + */ +static void mxcmci_stop_clock(struct mxcmci_host *host, bool wait) +{ + int wait_cnt = 0; + while (1) { + __raw_writel(STR_STP_CLK_STOP_CLK, + host->base + MMC_STR_STP_CLK); + + if (!wait) + break; + + wait_cnt = CMD_WAIT_CNT; + while (wait_cnt--) { + if (!(__raw_readl(host->base + MMC_STATUS) & + STATUS_CARD_BUS_CLK_RUN)) + break; + } + + if (!(__raw_readl(host->base + MMC_STATUS) & + STATUS_CARD_BUS_CLK_RUN)) + break; + } +} + +/*! + * This function sets the SDHC register to start the clock and waits for the + * clock start indication. When the clock starts SDHC module starts processing + * the command in CMD Register with arguments in ARG Register. + * + * @param host Pointer to MMC/SD host structure + * @param wait Boolean value to indicate whether to wait for the clock to start or come out instantly + */ +static void mxcmci_start_clock(struct mxcmci_host *host, bool wait) +{ + int wait_cnt; + +#ifdef CONFIG_MMC_DEBUG + dump_status(__FUNCTION__, __raw_readl(host->base + MMC_STATUS)); +#endif + + while (1) { + __raw_writel(STR_STP_CLK_START_CLK, + host->base + MMC_STR_STP_CLK); + if (!wait) + break; + + wait_cnt = CMD_WAIT_CNT; + while (wait_cnt--) { + if (__raw_readl(host->base + MMC_STATUS) & + STATUS_CARD_BUS_CLK_RUN) { + break; + } + } + + if (__raw_readl(host->base + MMC_STATUS) & + STATUS_CARD_BUS_CLK_RUN) { + break; + } + } +#ifdef CONFIG_MMC_DEBUG + dump_status(__FUNCTION__, __raw_readl(host->base + MMC_STATUS)); +#endif + pr_debug("%s:CLK_RATE: 0x%08x\n", DRIVER_NAME, + __raw_readl(host->base + MMC_CLK_RATE)); +} + +/*! + * This function resets the SDHC host. + * + * @param host Pointer to MMC/SD host structure + */ +static void mxcmci_softreset(struct mxcmci_host *host) +{ + /* reset sequence */ + __raw_writel(0x8, host->base + MMC_STR_STP_CLK); + __raw_writel(0x9, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x3f, host->base + MMC_CLK_RATE); + + __raw_writel(0xff, host->base + MMC_RES_TO); + __raw_writel(512, host->base + MMC_BLK_LEN); + __raw_writel(1, host->base + MMC_NOB); +} + +/*! + * This function is called to setup SDHC register for data transfer. + * The function allocates DMA buffers, configures the DMA channel. + * Start the DMA channel to transfer data. When DMA is not enabled this + * function set ups only Number of Block and Block Length registers. + * + * @param host Pointer to MMC/SD host structure + * @param data Pointer to MMC/SD data structure + */ +static void mxcmci_setup_data(struct mxcmci_host *host, struct mmc_data *data) +{ + unsigned int nob = data->blocks; + + if (data->flags & MMC_DATA_STREAM) { + nob = 0xffff; + } + + host->data = data; + + __raw_writel(nob, host->base + MMC_NOB); + __raw_writel(data->blksz, host->base + MMC_BLK_LEN); + + host->dma_size = data->blocks * data->blksz; + pr_debug("%s:Request bytes to transfer:%d\n", DRIVER_NAME, + host->dma_size); + +#ifdef MXC_MMC_DMA_ENABLE + if (host->dma_size <= (16 << host->mmc->ios.bus_width)) { + return; + } + + if (data->blksz & 0x3) { + printk(KERN_ERR + "mxc_mci: block size not multiple of 4 bytes\n"); + } + + if (data->flags & MMC_DATA_READ) { + host->dma_dir = DMA_FROM_DEVICE; + } else { + host->dma_dir = DMA_TO_DEVICE; + } + host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + host->dma_dir); + + if (data->flags & MMC_DATA_READ) { + mxc_dma_sg_config(host->dma, data->sg, data->sg_len, + host->dma_size, MXC_DMA_MODE_READ); + } else { + mxc_dma_sg_config(host->dma, data->sg, data->sg_len, + host->dma_size, MXC_DMA_MODE_WRITE); + } +#endif +} + +/*! + * This function is called by \b mxcmci_request() function to setup the SDHC + * register to issue command. This function disables the card insertion and + * removal detection interrupt. + * + * @param host Pointer to MMC/SD host structure + * @param cmd Pointer to MMC/SD command structure + * @param cmdat Value to store in Command and Data Control Register + */ +static void mxcmci_start_cmd(struct mxcmci_host *host, struct mmc_command *cmd, + unsigned int cmdat) +{ + WARN_ON(host->cmd != NULL); + host->cmd = cmd; + + switch (RSP_TYPE(mmc_resp_type(cmd))) { + case RSP_TYPE(MMC_RSP_R1): /* r1, r1b, r6 */ + cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R1; + break; + case RSP_TYPE(MMC_RSP_R3): + cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R3; + break; + case RSP_TYPE(MMC_RSP_R2): + cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R2; + break; + default: + /* No Response required */ + break; + } + + if (cmd->opcode == MMC_GO_IDLE_STATE) { + cmdat |= CMD_DAT_CONT_INIT; /* This command needs init */ + } + + if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_4) { + cmdat |= CMD_DAT_CONT_BUS_WIDTH_4; + } + + __raw_writel(cmd->opcode, host->base + MMC_CMD); + __raw_writel(cmd->arg, host->base + MMC_ARG); + + __raw_writel(cmdat, host->base + MMC_CMD_DAT_CONT); + + if (!(__raw_readl(host->base + MMC_STATUS) & STATUS_CARD_BUS_CLK_RUN)) + mxcmci_start_clock(host, true); +} + +/*! + * This function is called to complete the command request. + * This function enables insertion or removal interrupt. + * + * @param host Pointer to MMC/SD host structure + * @param req Pointer to MMC/SD command request structure + */ +static void mxcmci_finish_request(struct mxcmci_host *host, + struct mmc_request *req) +{ + + host->req = NULL; + host->cmd = NULL; + host->data = NULL; + + mmc_request_done(host->mmc, req); +} + +/*! + * This function is called when the requested command is completed. + * This function reads the response from the card and data if the command is for + * data transfer. This function checks for CRC error in response FIFO or + * data FIFO. + * + * @param host Pointer to MMC/SD host structure + * @param stat Content of SDHC Status Register + * + * @return This function returns 0 if there is no pending command, otherwise 1 + * always. + */ +static int mxcmci_cmd_done(struct mxcmci_host *host, unsigned int stat) +{ + struct mmc_command *cmd = host->cmd; + struct mmc_data *data = host->data; + int i; + u32 a, b, c; + u32 temp_data; + unsigned int status; + unsigned long *buf; + u8 *buf8; + int no_of_bytes; + int no_of_words; + + if (!cmd) { + /* There is no command for completion */ + return 0; + } + + /* As this function finishes the command, initialize cmd to NULL */ + host->cmd = NULL; + + /* check for Time out errors */ + if (stat & STATUS_TIME_OUT_RESP) { + __raw_writel(STATUS_TIME_OUT_RESP, host->base + MMC_STATUS); + pr_debug("%s: CMD %d TIMEOUT\n", DRIVER_NAME, cmd->opcode); + cmd->error = -ETIMEDOUT; + /* + * Reinitialized the controller to clear the unknown + * error state. + */ + mxcmci_softreset(host); + __raw_writel(READ_TO_VALUE, host->base + MMC_READ_TO); + __raw_writel(INT_CNTR_END_CMD_RES, host->base + MMC_INT_CNTR); + } else if (stat & STATUS_RESP_CRC_ERR && cmd->flags & MMC_RSP_CRC) { + __raw_writel(STATUS_RESP_CRC_ERR, host->base + MMC_STATUS); + printk(KERN_ERR "%s: cmd %d CRC error\n", DRIVER_NAME, + cmd->opcode); + cmd->error = -EILSEQ; + /* + * Reinitialized the controller to clear the unknown + * error state. + */ + mxcmci_softreset(host); + __raw_writel(READ_TO_VALUE, host->base + MMC_READ_TO); + __raw_writel(INT_CNTR_END_CMD_RES, host->base + MMC_INT_CNTR); + } + + /* Read response from the card */ + switch (RSP_TYPE(mmc_resp_type(cmd))) { + case RSP_TYPE(MMC_RSP_R1): /* r1, r1b, r6 */ + a = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + b = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + c = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + cmd->resp[0] = a << 24 | b << 8 | c >> 8; + break; + case RSP_TYPE(MMC_RSP_R3): /* r3, r4 */ + a = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + b = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + c = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + cmd->resp[0] = a << 24 | b << 8 | c >> 8; + break; + case RSP_TYPE(MMC_RSP_R2): + for (i = 0; i < 4; i++) { + a = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + b = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + cmd->resp[i] = a << 16 | b; + } + break; + default: + break; + } + + pr_debug("%s: 0x%08x, 0x%08x, 0x%08x, 0x%08x\n", DRIVER_NAME, + cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]); + + if (!host->data || cmd->error) { + /* complete the command */ + mxcmci_finish_request(host, host->req); + return 1; + } + + /* The command has a data transfer */ +#ifdef MXC_MMC_DMA_ENABLE + /* Use DMA if transfer size is greater than fifo size */ + if (host->dma_size > (16 << host->mmc->ios.bus_width)) { + mxc_dma_enable(host->dma); + return 1; + } +#endif + /* Use PIO tranfer of data */ + buf = (unsigned long *)sg_virt(data->sg); + buf8 = (u8 *) buf; + + /* calculate the number of bytes requested for transfer */ + no_of_bytes = data->blocks * data->blksz; + no_of_words = (no_of_bytes + 3) / 4; + pr_debug("no_of_words=%d\n", no_of_words); + + if (data->flags & MMC_DATA_READ) { + for (i = 0; i < no_of_words; i++) { + /* wait for buffers to be ready for read */ + while (!(__raw_readl(host->base + MMC_STATUS) & + (STATUS_BUF_READ_RDY | STATUS_READ_OP_DONE))) ; + + pr_debug("status is 0x%x\n", + __raw_readl(host->base + MMC_STATUS)); + /* read 32 bit data */ + temp_data = __raw_readl(host->base + MMC_BUFFER_ACCESS); + if (SD_APP_SEND_SCR == cmd->opcode) { + pr_debug("CMD51 read out 0x%x\n", temp_data); + if (temp_data == 0xFFFFFFFF) + temp_data = 0; + } + if (no_of_bytes >= 4) { + *buf++ = temp_data; + no_of_bytes -= 4; + } else { + do { + *buf8++ = temp_data; + temp_data = temp_data >> 8; + } while (--no_of_bytes); + } + } + + /* wait for read operation completion bit */ + while (!(__raw_readl(host->base + MMC_STATUS) & + STATUS_READ_OP_DONE)) ; + + /* check for time out and CRC errors */ + status = __raw_readl(host->base + MMC_STATUS); + if (status & STATUS_TIME_OUT_READ) { + printk(KERN_ERR "%s: Read time out occurred\n", + DRIVER_NAME); + data->error = -ETIMEDOUT; + __raw_writel(STATUS_TIME_OUT_READ, + host->base + MMC_STATUS); + /* + * Reinitialized the controller to clear the unknown + * error state. + */ + mxcmci_softreset(host); + __raw_writel(READ_TO_VALUE, host->base + MMC_READ_TO); + __raw_writel(INT_CNTR_END_CMD_RES, + host->base + MMC_INT_CNTR); + } else if (status & STATUS_READ_CRC_ERR) { + printk(KERN_ERR "%s: Read CRC error occurred\n", + DRIVER_NAME); + if (SD_APP_SEND_SCR != cmd->opcode) + data->error = -EILSEQ; + __raw_writel(STATUS_READ_CRC_ERR, + host->base + MMC_STATUS); + /* + * Reinitialized the controller to clear the unknown + * error state. + */ + mxcmci_softreset(host); + __raw_writel(READ_TO_VALUE, host->base + MMC_READ_TO); + __raw_writel(INT_CNTR_END_CMD_RES, + host->base + MMC_INT_CNTR); + } + __raw_writel(STATUS_READ_OP_DONE, host->base + MMC_STATUS); + + pr_debug("%s: Read %u words\n", DRIVER_NAME, i); + } else { + for (i = 0; i < no_of_words; i++) { + + /* wait for buffers to be ready for write */ + while (!(__raw_readl(host->base + MMC_STATUS) & + STATUS_BUF_WRITE_RDY)) ; + + /* write 32 bit data */ + __raw_writel(*buf++, host->base + MMC_BUFFER_ACCESS); + if (__raw_readl(host->base + MMC_STATUS) & + STATUS_WRITE_OP_DONE) { + break; + } + } + + /* wait for write operation completion bit */ + while (!(__raw_readl(host->base + MMC_STATUS) & + STATUS_WRITE_OP_DONE)) ; + + /* check for CRC errors */ + status = __raw_readl(host->base + MMC_STATUS); + if (status & STATUS_WRITE_CRC_ERR) { + printk(KERN_ERR "%s: Write CRC error occurred\n", + DRIVER_NAME); + data->error = -EILSEQ; + __raw_writel(STATUS_WRITE_CRC_ERR, + host->base + MMC_STATUS); + } + __raw_writel(STATUS_WRITE_OP_DONE, host->base + MMC_STATUS); + pr_debug("%s: Written %u words\n", DRIVER_NAME, i); + } + + /* complete the data transfer request */ + mxcmci_data_done(host, status); + + return 1; +} + +/*! + * This function is called when the data transfer is completed either by DMA + * or by core. This function is called to clean up the DMA buffer and to send + * STOP transmission command for commands to transfer data. This function + * completes request issued by the MMC/SD core driver. + * + * @param host pointer to MMC/SD host structure. + * @param stat content of SDHC Status Register + * + * @return This function returns 0 if no data transfer otherwise return 1 + * always. + */ +static int mxcmci_data_done(struct mxcmci_host *host, unsigned int stat) +{ + struct mmc_data *data = host->data; + + if (!data) { + return 0; + } +#ifdef MXC_MMC_DMA_ENABLE + if (host->dma_size > (16 << host->mmc->ios.bus_width)) { + dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len, + host->dma_dir); + } +#endif + if (__raw_readl(host->base + MMC_STATUS) & STATUS_ERR_MASK) { + printk(KERN_ERR "%s: request failed. status: 0x%08x\n", + DRIVER_NAME, __raw_readl(host->base + MMC_STATUS)); + } + + host->data = NULL; + data->bytes_xfered = host->dma_size; + + if (host->req->stop && !(data->error)) { + mxcmci_stop_clock(host, true); + mxcmci_start_cmd(host, host->req->stop, 0); + } else { + mxcmci_finish_request(host, host->req); + } + + return 1; +} + +/*! + * GPIO interrupt service routine registered to handle the SDHC interrupts. + * This interrupt routine handles card insertion and card removal interrupts. + * + * @param irq the interrupt number + * @param devid driver private data + * @param regs holds a snapshot of the processor's context before the + * processor entered the interrupt code + * + * @return The function returns \b IRQ_RETVAL(1) + */ +static irqreturn_t mxcmci_gpio_irq(int irq, void *devid) +{ + struct mxcmci_host *host = devid; + int card_gpio_status = host->plat_data->status(host->mmc->parent); + + pr_debug("%s: MMC%d status=%d %s\n", DRIVER_NAME, host->id, + card_gpio_status, card_gpio_status ? "removed" : "inserted"); + + if (card_gpio_status == host->plat_data->card_inserted_state) { + /* + * Reinitialized the controller to clear the unknown + * error state when a card is inserted. + */ + mxcmci_softreset(host); + __raw_writel(READ_TO_VALUE, host->base + MMC_READ_TO); + __raw_writel(INT_CNTR_END_CMD_RES, host->base + MMC_INT_CNTR); + + mmc_detect_change(host->mmc, msecs_to_jiffies(100)); + } else { + mxcmci_cmd_done(host, STATUS_TIME_OUT_RESP); + mmc_detect_change(host->mmc, msecs_to_jiffies(50)); + } + + do { + card_gpio_status = host->plat_data->status(host->mmc->parent); + if (card_gpio_status) { + set_irq_type(host->detect_irq, IRQF_TRIGGER_FALLING); + } else { + set_irq_type(host->detect_irq, IRQF_TRIGGER_RISING); + } + } while (card_gpio_status != + host->plat_data->status(host->mmc->parent)); + + return IRQ_HANDLED; +} + +/*! + * Interrupt service routine registered to handle the SDHC interrupts. + * This interrupt routine handles end of command, card insertion and + * card removal interrupts. If the interrupt is card insertion or removal then + * inform the MMC/SD core driver to detect the change in physical connections. + * If the command is END_CMD_RESP read the Response FIFO. If DMA is not enabled + * and data transfer is associated with the command then read or write the data + * from or to the BUFFER_ACCESS FIFO. + * + * @param irq the interrupt number + * @param devid driver private data + * @param regs holds a snapshot of the processor's context before the + * processor entered the interrupt code + * + * @return The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + */ +static irqreturn_t mxcmci_irq(int irq, void *devid) +{ + struct mxcmci_host *host = devid; + struct mmc_data *data = host->data; + unsigned int status = 0; + u32 intctrl; + + if (host->mxc_mmc_suspend_flag == 1) { + clk_enable(host->clk); + } + + status = __raw_readl(host->base + MMC_STATUS); + pr_debug("MXC MMC IRQ status is 0x%x.\n", status); +#ifdef CONFIG_MMC_DEBUG + dump_status(__FUNCTION__, status); +#endif + if (status & STATUS_END_CMD_RESP) { + __raw_writel(STATUS_END_CMD_RESP, host->base + MMC_STATUS); + mxcmci_cmd_done(host, status); + } +#ifdef MXC_MMC_DMA_ENABLE + /* + * If read length < fifo length, STATUS_END_CMD_RESP and + * STATUS_READ_OP_DONE may come together. In this case, it's using PIO + * mode, we ignore STATUS_READ_OP_DONE. + */ + if ((status & (STATUS_WRITE_OP_DONE | STATUS_READ_OP_DONE)) && + !(status & STATUS_END_CMD_RESP)) { + pr_debug(KERN_INFO "MXC MMC IO OP DONE INT.\n"); + intctrl = __raw_readl(host->base + MMC_INT_CNTR); + __raw_writel((~(INT_CNTR_WRITE_OP_DONE | INT_CNTR_READ_OP_DONE) + & intctrl), host->base + MMC_INT_CNTR); + + pr_debug("%s:READ/WRITE OPERATION DONE\n", DRIVER_NAME); + /* check for time out and CRC errors */ + status = __raw_readl(host->base + MMC_STATUS); + if (status & STATUS_READ_OP_DONE) { + if (status & STATUS_TIME_OUT_READ) { + pr_debug("%s: Read time out occurred\n", + DRIVER_NAME); + data->error = -ETIMEDOUT; + __raw_writel(STATUS_TIME_OUT_READ, + host->base + MMC_STATUS); + } else if (status & STATUS_READ_CRC_ERR) { + pr_debug("%s: Read CRC error occurred\n", + DRIVER_NAME); + data->error = -EILSEQ; + __raw_writel(STATUS_READ_CRC_ERR, + host->base + MMC_STATUS); + } + __raw_writel(STATUS_READ_OP_DONE, + host->base + MMC_STATUS); + } + + /* check for CRC errors */ + if (status & STATUS_WRITE_OP_DONE) { + if (status & STATUS_WRITE_CRC_ERR) { + printk(KERN_ERR + "%s: Write CRC error occurred\n", + DRIVER_NAME); + data->error = -EILSEQ; + __raw_writel(STATUS_WRITE_CRC_ERR, + host->base + MMC_STATUS); + } + __raw_writel(STATUS_WRITE_OP_DONE, + host->base + MMC_STATUS); + } + + mxcmci_data_done(host, status); + } +#endif + status = __raw_readl(host->base + MMC_STATUS); + intctrl = __raw_readl(host->base + MMC_INT_CNTR); + if ((status & STATUS_SDIO_INT_ACTIVE) + && (intctrl & INT_CNTR_SDIO_IRQ_EN)) { + __raw_writel(STATUS_SDIO_INT_ACTIVE, host->base + MMC_STATUS); + + /*Here we do not handle the sdio interrupt to client driver + if the host is in suspend state */ + if (host->mxc_mmc_suspend_flag == 0) { + mmc_signal_sdio_irq(host->mmc); + } + } + return IRQ_HANDLED; +} + +/*! + * This function is called by MMC/SD Bus Protocol driver to issue a MMC + * and SD commands to the SDHC. + * + * @param mmc Pointer to MMC/SD host structure + * @param req Pointer to MMC/SD command request structure + */ +static void mxcmci_request(struct mmc_host *mmc, struct mmc_request *req) +{ + struct mxcmci_host *host = mmc_priv(mmc); + /* Holds the value of Command and Data Control Register */ + unsigned long cmdat; + + WARN_ON(host->req != NULL); + + host->req = req; +#ifdef CONFIG_MMC_DEBUG + dump_cmd(req->cmd); + dump_status(__FUNCTION__, __raw_readl(host->base + MMC_STATUS)); +#endif + + cmdat = 0; + if (req->data) { + mxcmci_setup_data(host, req->data); + + cmdat |= CMD_DAT_CONT_DATA_ENABLE; + + if (req->data->flags & MMC_DATA_WRITE) { + cmdat |= CMD_DAT_CONT_WRITE; + } + if (req->data->flags & MMC_DATA_STREAM) { + printk(KERN_ERR + "MXC MMC does not support stream mode\n"); + } + } + mxcmci_start_cmd(host, req->cmd, cmdat); +} + +/*! + * This function is called by MMC/SD Bus Protocol driver to change the clock + * speed of MMC or SD card + * + * @param mmc Pointer to MMC/SD host structure + * @param ios Pointer to MMC/SD I/O type structure + */ +static void mxcmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct mxcmci_host *host = mmc_priv(mmc); + /*This variable holds the value of clock prescaler */ + int prescaler; + int clk_rate = clk_get_rate(host->clk); + int voltage = 0; +#ifdef MXC_MMC_DMA_ENABLE + mxc_dma_device_t dev_id = 0; +#endif + + pr_debug("%s: clock %u, bus %lu, power %u, vdd %u\n", DRIVER_NAME, + ios->clock, 1UL << ios->bus_width, ios->power_mode, ios->vdd); + + host->dma_dir = DMA_NONE; + +#ifdef MXC_MMC_DMA_ENABLE + if (mmc->ios.bus_width != host->mode) { + mxc_dma_free(host->dma); + if (mmc->ios.bus_width == MMC_BUS_WIDTH_4) { + if (host->id == 0) { + dev_id = MXC_DMA_MMC1_WIDTH_4; + } else { + dev_id = MXC_DMA_MMC2_WIDTH_4; + } + } else { + if (host->id == 0) { + dev_id = MXC_DMA_MMC1_WIDTH_1; + } else { + dev_id = MXC_DMA_MMC2_WIDTH_1; + } + } + host->dma = mxc_dma_request(dev_id, "MXC MMC"); + if (host->dma < 0) { + printk(KERN_ERR "Cannot allocate MMC DMA channel\n"); + } + host->mode = mmc->ios.bus_width; + mxc_dma_callback_set(host->dma, mxcmci_dma_irq, (void *)host); + } +#endif + + if ((ios->vdd != host->current_vdd) && host->regulator_mmc) { + if (ios->vdd == 7) + voltage = 1800000; + else if (ios->vdd >= 8) + voltage = 2000000 + (ios->vdd - 8) * 100000; + regulator_set_voltage(host->regulator_mmc, voltage, voltage); + } + host->current_vdd = ios->vdd; + + if (ios->power_mode != host->power_mode && host->regulator_mmc) { + if (ios->power_mode == MMC_POWER_UP) { + if (regulator_enable(host->regulator_mmc) == 0) { + pr_debug("mmc power on\n"); + msleep(1); + } + } else if (ios->power_mode == MMC_POWER_OFF) { + regulator_disable(host->regulator_mmc); + pr_debug("mmc power off\n"); + } + } + host->power_mode = ios->power_mode; + + /* + * Vary divider first, then prescaler. + **/ + if (ios->clock) { + unsigned int clk_dev = 0; + + /* + * when prescaler = 16, CLK_20M = CLK_DIV / 2 + */ + if (ios->clock == mmc->f_min) + prescaler = 16; + else + prescaler = 0; + + /* clk_dev =1, CLK_DIV = ipg_perclk/2 */ + while (prescaler <= 0x800) { + for (clk_dev = 1; clk_dev <= 0xF; clk_dev++) { + int x; + if (prescaler != 0) { + x = (clk_rate / (clk_dev + 1)) / + (prescaler * 2); + } else { + x = clk_rate / (clk_dev + 1); + } + + pr_debug("x=%d, clock=%d %d\n", x, ios->clock, + clk_dev); + if (x <= ios->clock) { + break; + } + } + if (clk_dev < 0x10) { + break; + } + if (prescaler == 0) + prescaler = 1; + else + prescaler <<= 1; + } + + pr_debug("prescaler = 0x%x, divider = 0x%x\n", prescaler, + clk_dev); + mxcmci_stop_clock(host, true); + __raw_writel((prescaler << 4) | clk_dev, + host->base + MMC_CLK_RATE); + mxcmci_start_clock(host, false); + } else { + mxcmci_stop_clock(host, true); + } +} + +static void mxcmci_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct mxcmci_host *host = mmc_priv(mmc); + u32 intctrl; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + if (enable) + host->sdio_irq_cnt++; + else + host->sdio_irq_cnt--; + + if (host->sdio_irq_cnt == 1 || host->sdio_irq_cnt == 0) { + intctrl = __raw_readl(host->base + MMC_INT_CNTR); + intctrl &= ~INT_CNTR_SDIO_IRQ_EN; + if (host->sdio_irq_cnt) + intctrl |= INT_CNTR_SDIO_IRQ_EN; + __raw_writel(intctrl, host->base + MMC_INT_CNTR); + } + + spin_unlock_irqrestore(&host->lock, flags); +} + +static int mxcmci_get_ro(struct mmc_host *mmc) +{ + struct mxcmci_host *host = mmc_priv(mmc); + + if (host->plat_data->wp_status) + return host->plat_data->wp_status(mmc->parent); + else + return 0; +} + +/*! + * MMC/SD host operations structure. + * These functions are registered with MMC/SD Bus protocol driver. + */ +static struct mmc_host_ops mxcmci_ops = { + .request = mxcmci_request, + .set_ios = mxcmci_set_ios, + .get_ro = mxcmci_get_ro, + .enable_sdio_irq = mxcmci_enable_sdio_irq, +}; + +#ifdef MXC_MMC_DMA_ENABLE +/*! + * This function is called by DMA Interrupt Service Routine to indicate + * requested DMA transfer is completed. + * + * @param devid pointer to device specific structure + * @param error any DMA error + * @param cnt amount of data that was transferred + */ +static void mxcmci_dma_irq(void *devid, int error, unsigned int cnt) +{ + struct mxcmci_host *host = devid; + u32 status; + ulong nob, blk_size, i, blk_len; + + mxc_dma_disable(host->dma); + + if (error) { + printk(KERN_ERR "Error in DMA transfer\n"); + status = __raw_readl(host->base + MMC_STATUS); +#ifdef CONFIG_MMC_DEBUG + dump_status(__FUNCTION__, status); +#endif + mxcmci_data_done(host, status); + return; + } + pr_debug("%s: Transfered bytes:%d\n", DRIVER_NAME, cnt); + nob = __raw_readl(host->base + MMC_REM_NOB); + blk_size = __raw_readl(host->base + MMC_REM_BLK_SIZE); + blk_len = __raw_readl(host->base + MMC_BLK_LEN); + pr_debug("%s: REM_NOB:%lu REM_BLK_SIZE:%lu\n", DRIVER_NAME, nob, + blk_size); + i = 0; + + /* Enable the WRITE OP Done INT */ + status = __raw_readl(host->base + MMC_INT_CNTR); + __raw_writel((INT_CNTR_READ_OP_DONE | INT_CNTR_WRITE_OP_DONE | status), + host->base + MMC_INT_CNTR); +} +#endif + +/*! + * This function is called during the driver binding process. Based on the SDHC + * module that is being probed this function adds the appropriate SDHC module + * structure in the core driver. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions. + * + * @return The function returns 0 on successful registration and initialization + * of SDHC module. Otherwise returns specific error code. + */ +static int mxcmci_probe(struct platform_device *pdev) +{ + struct mxc_mmc_platform_data *mmc_plat = pdev->dev.platform_data; + struct mmc_host *mmc; + struct mxcmci_host *host = NULL; + int card_gpio_status; + int ret = -ENODEV; + + if (!mmc_plat) { + return -EINVAL; + } + + mmc = mmc_alloc_host(sizeof(struct mxcmci_host), &pdev->dev); + if (!mmc) { + return -ENOMEM; + } + host = mmc_priv(mmc); + platform_set_drvdata(pdev, mmc); + + mmc->ops = &mxcmci_ops; + mmc->ocr_avail = mmc_plat->ocr_mask; + + /* Hack to work with LP1070 */ + if (mmc->ocr_avail && ~(MMC_VDD_31_32 - 1) == 0) + mmc->ocr_avail |= MMC_VDD_31_32; + + mmc->max_phys_segs = NR_SG; + mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ; + + mmc->f_min = mmc_plat->min_clk; + mmc->f_max = mmc_plat->max_clk; + mmc->max_req_size = 32 * 1024; + mmc->max_seg_size = mmc->max_req_size; + mmc->max_blk_count = 65536; + + spin_lock_init(&host->lock); + host->mmc = mmc; + host->dma = -1; + host->dma_dir = DMA_NONE; + host->id = pdev->id; + host->mxc_mmc_suspend_flag = 0; + host->mode = -1; + host->plat_data = mmc_plat; + if (!host->plat_data) { + ret = -EINVAL; + goto out0; + } + + /* Get pwr supply for SDHC */ + if (NULL != mmc_plat->power_mmc) { + host->regulator_mmc = + regulator_get(&pdev->dev, mmc_plat->power_mmc); + if (IS_ERR(host->regulator_mmc)) { + ret = PTR_ERR(host->regulator_mmc); + goto out1; + } + if (!regulator_is_enabled(host->regulator_mmc)) { + if (regulator_enable(host->regulator_mmc) == 0) { + pr_debug("mmc power on\n"); + msleep(1); + } + } + } + + gpio_sdhc_active(pdev->id); + + host->clk = clk_get(&pdev->dev, "sdhc_clk"); + pr_debug("SDHC:%d clock:%lu\n", pdev->id, clk_get_rate(host->clk)); + clk_enable(host->clk); + + host->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!host->res) { + ret = -ENOMEM; + goto out2; + } + + if (!request_mem_region(host->res->start, + host->res->end - + host->res->start + 1, pdev->name)) { + printk(KERN_ERR "request_mem_region failed\n"); + ret = -ENOMEM; + goto out2; + } + host->base = (void *)IO_ADDRESS(host->res->start); + if (!host->base) { + ret = -ENOMEM; + goto out3; + } + + host->irq = platform_get_irq(pdev, 0); + if (!host->irq) { + ret = -ENOMEM; + goto out3; + } + + if (!host->plat_data->card_fixed) { + host->detect_irq = platform_get_irq(pdev, 1); + if (!host->detect_irq) + goto out3; + + do { + card_gpio_status = + host->plat_data->status(host->mmc->parent); + if (card_gpio_status) + set_irq_type(host->detect_irq, + IRQF_TRIGGER_FALLING); + else + set_irq_type(host->detect_irq, + IRQF_TRIGGER_RISING); + + } while (card_gpio_status != + host->plat_data->status(host->mmc->parent)); + + ret = request_irq(host->detect_irq, mxcmci_gpio_irq, 0, + pdev->name, host); + if (ret) + goto out3; + } + + mxcmci_softreset(host); + + if (__raw_readl(host->base + MMC_REV_NO) != SDHC_REV_NO) { + printk(KERN_ERR "%s: wrong rev.no. 0x%08x. aborting.\n", + pdev->name, MMC_REV_NO); + goto out3; + } + __raw_writel(READ_TO_VALUE, host->base + MMC_READ_TO); + + __raw_writel(INT_CNTR_END_CMD_RES, host->base + MMC_INT_CNTR); + + ret = request_irq(host->irq, mxcmci_irq, 0, pdev->name, host); + if (ret) { + goto out4; + } + + if ((ret = mmc_add_host(mmc)) < 0) { + goto out5; + } + + printk(KERN_INFO "%s-%d found\n", pdev->name, pdev->id); + if (host->id < MAX_HOST) + hosts[host->id] = host->mmc; + + return 0; + + out5: + free_irq(host->irq, host); + out4: + free_irq(host->detect_irq, host); + out3: + release_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start + 1); + out2: + clk_disable(host->clk); + regulator_disable(host->regulator_mmc); + regulator_put(host->regulator_mmc); + out1: + gpio_sdhc_inactive(pdev->id); + out0: + mmc_free_host(mmc); + platform_set_drvdata(pdev, NULL); + return ret; +} + +/*! + * Dissociates the driver from the SDHC device. Removes the appropriate SDHC + * module structure from the core driver. + * + * @param pdev the device structure used to give information on which SDHC + * to remove + * + * @return The function always returns 0. + */ +static int mxcmci_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + + if (mmc) { + struct mxcmci_host *host = mmc_priv(mmc); + + hosts[host->id] = NULL; + mmc_remove_host(mmc); + free_irq(host->irq, host); + free_irq(host->detect_irq, host); +#ifdef MXC_MMC_DMA_ENABLE + mxc_dma_free(host->dma); +#endif + release_mem_region(host->res->start, + host->res->end - host->res->start + 1); + mmc_free_host(mmc); + if (NULL != host->regulator_mmc) + regulator_put(host->regulator_mmc); + gpio_sdhc_inactive(pdev->id); + } + platform_set_drvdata(pdev, NULL); + return 0; +} + +#ifdef CONFIG_PM + +/*! + * This function is called to put the SDHC in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which SDHC + * to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxcmci_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + struct mxcmci_host *host = mmc_priv(mmc); + int ret = 0; + + if (mmc) { + host->mxc_mmc_suspend_flag = 1; + ret = mmc_suspend_host(mmc, state); + } + + clk_disable(host->clk); + /* + * The CD INT should be disabled in the suspend + * and enabled in resumed. + * Otherwise, the system would be halt when wake + * up with the situation that there is a card + * insertion during the system is in suspend mode. + */ + disable_irq(host->detect_irq); + + gpio_sdhc_inactive(pdev->id); + + if (host->regulator_mmc) + regulator_disable(host->regulator_mmc); + + return ret; +} + +/*! + * This function is called to bring the SDHC back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which SDHC + * to resume + * + * @return The function always returns 0. + */ +static int mxcmci_resume(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + struct mxcmci_host *host = mmc_priv(mmc); + int ret = 0; + + /* + * Note that a card insertion interrupt will cause this + * driver to resume automatically. In that case we won't + * actually have to do any work here. Return success. + */ + if (!host->mxc_mmc_suspend_flag) { + return 0; + } + + /* enable pwr supply for SDHC */ + if (host->regulator_mmc && !regulator_is_enabled(host->regulator_mmc)) { + regulator_enable(host->regulator_mmc); + msleep(1); + } + + gpio_sdhc_active(pdev->id); + + clk_enable(host->clk); + + if (mmc) { + ret = mmc_resume_host(mmc); + host->mxc_mmc_suspend_flag = 0; + } + + enable_irq(host->detect_irq); + + return ret; +} +#else +#define mxcmci_suspend NULL +#define mxcmci_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcmci_driver = { + .driver = { + .name = "mxcmci", + }, + .probe = mxcmci_probe, + .remove = mxcmci_remove, + .suspend = mxcmci_suspend, + .resume = mxcmci_resume, +}; + +/*! + * This function is used to initialize the MMC/SD driver module. The function + * registers the power management callback functions with the kernel and also + * registers the MMC/SD callback functions with the core MMC/SD driver. + * + * @return The function returns 0 on success and a non-zero value on failure. + */ +static int __init mxcmci_init(void) +{ + printk(KERN_INFO "MXC MMC/SD driver\n"); + return platform_driver_register(&mxcmci_driver); +} + +/*! + * This function is used to cleanup all resources before the driver exits. + */ +static void __exit mxcmci_exit(void) +{ + platform_driver_unregister(&mxcmci_driver); +} + +module_init(mxcmci_init); +module_exit(mxcmci_exit); + +MODULE_DESCRIPTION("MXC Multimedia Card Interface Driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/host/mxc_mmc.h b/drivers/mmc/host/mxc_mmc.h new file mode 100644 index 000000000000..3ad45377dde6 --- /dev/null +++ b/drivers/mmc/host/mxc_mmc.h @@ -0,0 +1,126 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __MXC_MMC_REG_H__ +#define __MXC_MMC_REG_H__ + +#include <mach/hardware.h> + +/*! + * @defgroup MMC_SD MMC/SD Driver + */ + +/*! + * @file mxc_mmc.h + * + * @brief Driver for the Freescale Semiconductor MXC SDHC modules. + * + * This file defines offsets and bits of SDHC registers. SDHC is also referred as + * MMC/SD controller + * + * @ingroup MMC_SD + */ + +/*! + * Number of SDHC modules + */ + +#define SDHC_MMC_WML 16 +#define SDHC_SD_WML 64 +#define DRIVER_NAME "MXCMMC" +#define SDHC_MEM_SIZE 16384 +#define SDHC_REV_NO 0x400 +#define READ_TO_VALUE 0x2db4 + +/* Address offsets of the SDHC registers */ +#define MMC_STR_STP_CLK 0x00 /* Clock Control Reg */ +#define MMC_STATUS 0x04 /* Status Reg */ +#define MMC_CLK_RATE 0x08 /* Clock Rate Reg */ +#define MMC_CMD_DAT_CONT 0x0C /* Command and Data Control Reg */ +#define MMC_RES_TO 0x10 /* Response Time-out Reg */ +#define MMC_READ_TO 0x14 /* Read Time-out Reg */ +#define MMC_BLK_LEN 0x18 /* Block Length Reg */ +#define MMC_NOB 0x1C /* Number of Blocks Reg */ +#define MMC_REV_NO 0x20 /* Revision Number Reg */ +#define MMC_INT_CNTR 0x24 /* Interrupt Control Reg */ +#define MMC_CMD 0x28 /* Command Number Reg */ +#define MMC_ARG 0x2C /* Command Argument Reg */ +#define MMC_RES_FIFO 0x34 /* Command Response Reg */ +#define MMC_BUFFER_ACCESS 0x38 /* Data Buffer Access Reg */ +#define MMC_REM_NOB 0x40 /* Remaining NOB Reg */ +#define MMC_REM_BLK_SIZE 0x44 /* Remaining Block Size Reg */ + +/* Bit definitions for STR_STP_CLK */ +#define STR_STP_CLK_RESET (1<<3) +#define STR_STP_CLK_START_CLK (1<<1) +#define STR_STP_CLK_STOP_CLK (1<<0) + +/* Bit definitions for STATUS */ +#define STATUS_CARD_INSERTION (1<<31) +#define STATUS_CARD_REMOVAL (1<<30) +#define STATUS_YBUF_EMPTY (1<<29) +#define STATUS_XBUF_EMPTY (1<<28) +#define STATUS_YBUF_FULL (1<<27) +#define STATUS_XBUF_FULL (1<<26) +#define STATUS_BUF_UND_RUN (1<<25) +#define STATUS_BUF_OVFL (1<<24) +#define STATUS_SDIO_INT_ACTIVE (1<<14) +#define STATUS_END_CMD_RESP (1<<13) +#define STATUS_WRITE_OP_DONE (1<<12) +#define STATUS_READ_OP_DONE (1<<11) +#define STATUS_WR_CRC_ERROR_CODE_MASK (3<<9) +#define STATUS_CARD_BUS_CLK_RUN (1<<8) +#define STATUS_BUF_READ_RDY (1<<7) +#define STATUS_BUF_WRITE_RDY (1<<6) +#define STATUS_RESP_CRC_ERR (1<<5) +#define STATUS_READ_CRC_ERR (1<<3) +#define STATUS_WRITE_CRC_ERR (1<<2) +#define STATUS_TIME_OUT_RESP (1<<1) +#define STATUS_TIME_OUT_READ (1<<0) +#define STATUS_ERR_MASK 0x3f + +/* Clock rate definitions */ +#define CLK_RATE_PRESCALER(x) ((x) & 0xF) +#define CLK_RATE_CLK_DIVIDER(x) (((x) & 0xF) << 4) + +/* Bit definitions for CMD_DAT_CONT */ +#define CMD_DAT_CONT_CMD_RESP_LONG_OFF (1<<12) +#define CMD_DAT_CONT_STOP_READWAIT (1<<11) +#define CMD_DAT_CONT_START_READWAIT (1<<10) +#define CMD_DAT_CONT_BUS_WIDTH_1 (0<<8) +#define CMD_DAT_CONT_BUS_WIDTH_4 (2<<8) +#define CMD_DAT_CONT_INIT (1<<7) +#define CMD_DAT_CONT_WRITE (1<<4) +#define CMD_DAT_CONT_DATA_ENABLE (1<<3) +#define CMD_DAT_CONT_RESPONSE_FORMAT_R1 (1) +#define CMD_DAT_CONT_RESPONSE_FORMAT_R2 (2) +#define CMD_DAT_CONT_RESPONSE_FORMAT_R3 (3) +#define CMD_DAT_CONT_RESPONSE_FORMAT_R4 (4) +#define CMD_DAT_CONT_RESPONSE_FORMAT_R5 (5) +#define CMD_DAT_CONT_RESPONSE_FORMAT_R6 (6) + +/* Bit definitions for INT_CNTR */ +#define INT_CNTR_SDIO_INT_WKP_EN (1<<18) +#define INT_CNTR_CARD_INSERTION_WKP_EN (1<<17) +#define INT_CNTR_CARD_REMOVAL_WKP_EN (1<<16) +#define INT_CNTR_CARD_INSERTION_EN (1<<15) +#define INT_CNTR_CARD_REMOVAL_EN (1<<14) +#define INT_CNTR_SDIO_IRQ_EN (1<<13) +#define INT_CNTR_DAT0_EN (1<<12) +#define INT_CNTR_BUF_READ_EN (1<<4) +#define INT_CNTR_BUF_WRITE_EN (1<<3) +#define INT_CNTR_END_CMD_RES (1<<2) +#define INT_CNTR_WRITE_OP_DONE (1<<1) +#define INT_CNTR_READ_OP_DONE (1<<0) + +#endif /* __MXC_MMC_REG_H__ */ diff --git a/drivers/mmc/host/stmp3xxx_mmc.c b/drivers/mmc/host/stmp3xxx_mmc.c new file mode 100644 index 000000000000..2061cc120472 --- /dev/null +++ b/drivers/mmc/host/stmp3xxx_mmc.c @@ -0,0 +1,1095 @@ +/* + * Copyright (C) 2007 SigmaTel, Inc., Ioannis Kappas <ikappas@sigmatel.com> + * + * Portions copyright (C) 2003 Russell King, PXA MMCI Driver + * Portions copyright (C) 2004-2005 Pierre Ossman, W83L51xD SD/MMC driver + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/highmem.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/completion.h> +#include <linux/mmc/host.h> +#include <linux/gpio.h> +#include <linux/regulator/consumer.h> +#include <mach/hardware.h> +#include <mach/dma.h> +#include <mach/regs-apbh.h> +#include <mach/regs-ssp.h> +#include <mach/regs-clkctrl.h> +#include <mach/stmp3xxx.h> +#include <mach/mmc.h> +#include <mach/platform.h> + +#define DRIVER_NAME "stmp3xxx-mmc" + +#define CLOCKRATE_MIN 400000 +#define CLOCKRATE_MAX 48000000 + +/* + * Card detect polling timeout + */ +#define STMP37XX_MMC_DETECT_TIMEOUT (HZ/2) + +/* Max value supported for XFER_COUNT */ +#define SSP_BUFFER_SIZE (65536 - 512) + +struct stmp3xxx_mmc_host { + struct device *dev; + struct mmc_host *mmc; + + struct clk *clk; + unsigned int clkrt; + + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + + /* Whether the card is capable of 4-bit data */ + int bus_width_4:1; + + /* Whether SD card is present */ + unsigned present:1; + + /* Polling timer */ + struct timer_list timer; + + /* SSP interface which MMC/SD card slot is attached to */ + void __iomem *ssp_base; + + /* DMA channel used for this host */ + unsigned int dmach; + + /* IRQs */ + int dmairq, errirq; + + /* DMA descriptor to transfer data over SSP interface */ + struct stmp3xxx_dma_descriptor dma_desc; + + /* DMA buffer */ + dma_addr_t dma_buf_phys; + char *dma_buf; + + struct completion dma_done; + /* status on last interrupt */ + u32 status; + int read_uA, write_uA; + struct regulator *regulator; +}; + +/* Return read only state of card */ +static int stmp3xxx_mmc_get_ro(struct mmc_host *mmc) +{ + struct stmp3xxx_mmc_host *host = mmc_priv(mmc); + struct stmp3xxxmmc_platform_data *pdata = host->dev->platform_data; + + if (pdata && pdata->get_wp) + return pdata->get_wp(); + + return 0; +} + +/* Detect if card is plugged */ +static inline int stmp3xxx_mmc_is_plugged(struct stmp3xxx_mmc_host *host) +{ + u32 status = __raw_readl(host->ssp_base + HW_SSP_STATUS); + return !(status & BM_SSP_STATUS_CARD_DETECT); +} + +/* Card detection polling function */ +static void stmp3xxx_mmc_detect_poll(unsigned long arg) +{ + struct stmp3xxx_mmc_host *host = (struct stmp3xxx_mmc_host *)arg; + int card_status; + + card_status = stmp3xxx_mmc_is_plugged(host); + if (card_status != host->present) { + host->present = card_status; + mmc_detect_change(host->mmc, 0); + } + + mod_timer(&host->timer, jiffies + STMP37XX_MMC_DETECT_TIMEOUT); +} + +#define STMP3XXX_MMC_IRQ_BITS (BM_SSP_CTRL1_SDIO_IRQ | \ + BM_SSP_CTRL1_RESP_ERR_IRQ | \ + BM_SSP_CTRL1_RESP_TIMEOUT_IRQ | \ + BM_SSP_CTRL1_DATA_TIMEOUT_IRQ | \ + BM_SSP_CTRL1_DATA_CRC_IRQ | \ + BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ | \ + BM_SSP_CTRL1_RECV_TIMEOUT_IRQ | \ + BM_SSP_CTRL1_FIFO_OVERRUN_IRQ) + +/* SSP DMA interrupt handler */ +static irqreturn_t mmc_irq_handler(int irq, void *dev_id) +{ + struct stmp3xxx_mmc_host *host = dev_id; + u32 c1; + + c1 = __raw_readl(host->ssp_base + HW_SSP_CTRL1); + stmp3xxx_clearl(c1 & STMP3XXX_MMC_IRQ_BITS, + host->ssp_base + HW_SSP_CTRL1); + if (irq == host->dmairq) + stmp3xxx_dma_clear_interrupt(host->dmach); + host->status = + __raw_readl(host->ssp_base + HW_SSP_STATUS); + + if (host->cmd) /* else it is a bogus interrupt */ + complete(&host->dma_done); + + return IRQ_HANDLED; +} + +/* + * Check for MMC command errors + * Returns error code or zerro if no errors + */ +static inline int stmp3xxx_mmc_cmd_error(u32 status) +{ + int err = 0; + + if (status & BM_SSP_STATUS_TIMEOUT) + err = -ETIMEDOUT; + else if (status & BM_SSP_STATUS_RESP_TIMEOUT) + err = -ETIMEDOUT; + else if (status & BM_SSP_STATUS_RESP_CRC_ERR) + err = -EILSEQ; + else if (status & BM_SSP_STATUS_RESP_ERR) + err = -EIO; + + return err; +} + +/* Send the BC command to the device */ +static void stmp3xxx_mmc_bc(struct stmp3xxx_mmc_host *host) +{ + struct mmc_command *cmd = host->cmd; + struct stmp3xxx_dma_descriptor *dma_desc = &host->dma_desc; + + dma_desc->command->cmd = BM_APBH_CHn_CMD_WAIT4ENDCMD | BM_APBH_CHn_CMD_SEMAPHORE | BM_APBH_CHn_CMD_IRQONCMPLT | BF(0, APBH_CHn_CMD_XFER_COUNT) | BF(3, APBH_CHn_CMD_CMDWORDS) | BF(0, APBH_CHn_CMD_COMMAND); /* NO_DMA_XFER */ + + dma_desc->command->pio_words[0] = BM_SSP_CTRL0_ENABLE | + BM_SSP_CTRL0_IGNORE_CRC; + dma_desc->command->pio_words[1] = BF(cmd->opcode, SSP_CMD0_CMD) | + BM_SSP_CMD0_APPEND_8CYC; + dma_desc->command->pio_words[2] = BF(cmd->arg, SSP_CMD1_CMD_ARG); + + init_completion(&host->dma_done); + stmp3xxx_dma_reset_channel(host->dmach); + stmp3xxx_dma_go(host->dmach, dma_desc, 1); + wait_for_completion(&host->dma_done); + + cmd->error = stmp3xxx_mmc_cmd_error(host->status); + + if (stmp3xxx_dma_running(host->dmach)) + dev_dbg(host->dev, "DMA command not finished\n"); + + if (cmd->error) { + dev_dbg(host->dev, "Command error 0x%x\n", cmd->error); + stmp3xxx_dma_reset_channel(host->dmach); + } +} + +/* Send the ac command to the device */ +static void stmp3xxx_mmc_ac(struct stmp3xxx_mmc_host *host) +{ + struct mmc_command *cmd = host->cmd; + struct stmp3xxx_dma_descriptor *dma_desc = &host->dma_desc; + u32 ignore_crc, resp, long_resp; + u32 ssp_ctrl0; + u32 ssp_cmd0; + u32 ssp_cmd1; + + ignore_crc = (mmc_resp_type(cmd) & MMC_RSP_CRC) ? + 0 : BM_SSP_CTRL0_IGNORE_CRC; + resp = (mmc_resp_type(cmd) & MMC_RSP_PRESENT) ? + BM_SSP_CTRL0_GET_RESP : 0; + long_resp = (mmc_resp_type(cmd) & MMC_RSP_136) ? + BM_SSP_CTRL0_LONG_RESP : 0; + + dma_desc->command->cmd = + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_SEMAPHORE | + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(0, APBH_CHn_CMD_XFER_COUNT) | + BF(3, APBH_CHn_CMD_CMDWORDS) | BF(0, APBH_CHn_CMD_COMMAND); + + ssp_ctrl0 = BM_SSP_CTRL0_ENABLE | ignore_crc | long_resp | resp; + ssp_cmd0 = BF(cmd->opcode, SSP_CMD0_CMD); + ssp_cmd1 = BF(cmd->arg, SSP_CMD1_CMD_ARG); + + dma_desc->command->pio_words[0] = ssp_ctrl0; + dma_desc->command->pio_words[1] = ssp_cmd0; + dma_desc->command->pio_words[2] = ssp_cmd1; + + stmp3xxx_dma_reset_channel(host->dmach); + init_completion(&host->dma_done); + stmp3xxx_dma_go(host->dmach, dma_desc, 1); + wait_for_completion(&host->dma_done); + + switch (mmc_resp_type(cmd)) { + case MMC_RSP_NONE: + while (__raw_readl(host->ssp_base + HW_SSP_CTRL0) + & BM_SSP_CTRL0_RUN) + continue; + break; + case MMC_RSP_R1: + case MMC_RSP_R1B: + case MMC_RSP_R3: + cmd->resp[0] = + __raw_readl(host->ssp_base + HW_SSP_SDRESP0); + break; + case MMC_RSP_R2: + cmd->resp[3] = + __raw_readl(host->ssp_base + HW_SSP_SDRESP0); + cmd->resp[2] = + __raw_readl(host->ssp_base + HW_SSP_SDRESP1); + cmd->resp[1] = + __raw_readl(host->ssp_base + HW_SSP_SDRESP2); + cmd->resp[0] = + __raw_readl(host->ssp_base + HW_SSP_SDRESP3); + break; + default: + dev_warn(host->dev, "Unsupported response type 0x%x\n", + mmc_resp_type(cmd)); + BUG(); + break; + } + + cmd->error = stmp3xxx_mmc_cmd_error(host->status); + + if (stmp3xxx_dma_running(host->dmach)) + dev_dbg(host->dev, "DMA command not finished\n"); + + if (cmd->error) { + dev_dbg(host->dev, "Command error 0x%x\n", cmd->error); + stmp3xxx_dma_reset_channel(host->dmach); + } +} + +/* Copy data between sg list and dma buffer */ +static unsigned int stmp3xxx_sg_dma_copy(struct stmp3xxx_mmc_host *host, + unsigned int size, int to_dma) +{ + struct mmc_data *data = host->cmd->data; + unsigned int copy_size, bytes_copied = 0; + struct scatterlist *sg; + char *dmabuf = host->dma_buf; + char *sgbuf; + int len, i; + + sg = data->sg; + len = data->sg_len; + + /* + * Just loop through all entries. Size might not + * be the entire list though so make sure that + * we do not transfer too much. + */ + for (i = 0; i < len; i++) { + sgbuf = kmap_atomic(sg_page(&sg[i]), KM_BIO_SRC_IRQ) + + sg[i].offset; + copy_size = size < sg[i].length ? size : sg[i].length; + if (to_dma) + memcpy(dmabuf, sgbuf, copy_size); + else + memcpy(sgbuf, dmabuf, copy_size); + kunmap_atomic(sgbuf, KM_BIO_SRC_IRQ); + + dmabuf += sg[i].length; + + bytes_copied += copy_size; + size -= copy_size; + + if (size == 0) + break; + } + + return bytes_copied; +} + +/* Convert ns to tick count according to the current sclk speed */ +static unsigned short stmp3xxx_ns_to_ssp_ticks(unsigned clock_rate, unsigned ns) +{ + const unsigned int ssp_timeout_mul = 4096; + /* + * Calculate ticks in ms since ns are large numbers + * and might overflow + */ + const unsigned int clock_per_ms = clock_rate / 1000; + const unsigned int ms = ns / 1000; + const unsigned int ticks = ms * clock_per_ms; + const unsigned int ssp_ticks = ticks / ssp_timeout_mul; + + BUG_ON(ssp_ticks == 0); + return ssp_ticks; +} + +static void __init_reg(struct device *dev, struct regulator **pp_reg) +{ + struct regulator *reg = *pp_reg; + + if (!reg) { + reg = regulator_get(NULL, "mmc_ssp-1"); + if (reg && !IS_ERR(reg)) + regulator_set_mode(reg, REGULATOR_MODE_NORMAL); + else + reg = NULL; + *pp_reg = reg; + } +} + +/* Send adtc command to the card */ +static void stmp3xxx_mmc_adtc(struct stmp3xxx_mmc_host *host) +{ + struct mmc_command *cmd = host->cmd; + struct stmp3xxx_dma_descriptor *dma_desc = &host->dma_desc; + int ignore_crc, resp, long_resp; + int is_reading = 0; + unsigned int copy_size; + + u32 ssp_ctrl0; + u32 ssp_cmd0; + u32 ssp_cmd1; + u32 timeout; + u32 val; + + u32 data_size = cmd->data->blksz * cmd->data->blocks; + u32 log2_block_size; + + ignore_crc = mmc_resp_type(cmd) & MMC_RSP_CRC ? 0 : 1; + resp = mmc_resp_type(cmd) & MMC_RSP_PRESENT ? 1 : 0; + long_resp = mmc_resp_type(cmd) & MMC_RSP_136 ? 1 : 0; + + dev_dbg(host->dev, "ADTC command:\n" + "response: %d, ignore crc: %d\n" + "data list: %u, blocksz: %u, blocks: %u, timeout: %uns %uclks, " + "flags: 0x%x\n", resp, ignore_crc, cmd->data->sg_len, + cmd->data->blksz, cmd->data->blocks, cmd->data->timeout_ns, + cmd->data->timeout_clks, cmd->data->flags); + + if (cmd->data->flags & MMC_DATA_WRITE) { + dev_dbg(host->dev, "Data Write\n"); + copy_size = stmp3xxx_sg_dma_copy(host, data_size, 1); + BUG_ON(copy_size < data_size); + is_reading = 0; + if (!host->regulator) + __init_reg(host->dev, &host->regulator); + if (host->regulator) + regulator_set_current_limit(host->regulator, + host->write_uA, + host->write_uA); + } else if (cmd->data->flags & MMC_DATA_READ) { + dev_dbg(host->dev, "Data Read\n"); + is_reading = 1; + if (!host->regulator) + __init_reg(host->dev, &host->regulator); + if (host->regulator) + regulator_set_current_limit(host->regulator, + host->read_uA, + host->read_uA); + } else { + dev_warn(host->dev, "Unsuspported data mode, 0x%x\n", + cmd->data->flags); + BUG(); + } + + BUG_ON(cmd->data->flags & MMC_DATA_STREAM); + BUG_ON((data_size % 8) > 0); + + dma_desc->command->cmd = + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_SEMAPHORE | + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(data_size, APBH_CHn_CMD_XFER_COUNT) | + BF(3, APBH_CHn_CMD_CMDWORDS); + + /* when is_reading is set, DMA controller performs WRITE operation. */ + dma_desc->command->cmd |= + BF(is_reading ? BV_APBH_CHn_CMD_COMMAND__DMA_WRITE : + BV_APBH_CHn_CMD_COMMAND__DMA_READ, + APBH_CHn_CMD_COMMAND); + ssp_ctrl0 = + (ignore_crc ? BM_SSP_CTRL0_IGNORE_CRC : 0) | (resp ? + BM_SSP_CTRL0_GET_RESP + : 0) | (long_resp ? + BM_SSP_CTRL0_LONG_RESP + : 0) | + (is_reading ? BM_SSP_CTRL0_READ : 0) | BM_SSP_CTRL0_DATA_XFER | + BM_SSP_CTRL0_WAIT_FOR_IRQ | BM_SSP_CTRL0_ENABLE | BF(data_size, + SSP_CTRL0_XFER_COUNT) + | BF(host->bus_width_4 ? BV_SSP_CTRL0_BUS_WIDTH__FOUR_BIT : + BV_SSP_CTRL0_BUS_WIDTH__ONE_BIT, + SSP_CTRL0_BUS_WIDTH); + + /* + * We need to set the hardware register to the logarithm to base 2 of + * the block size. + */ + log2_block_size = ilog2(cmd->data->blksz); + + ssp_cmd0 = + BF(log2_block_size, SSP_CMD0_BLOCK_SIZE) | + BF(cmd->opcode, SSP_CMD0_CMD) | + BF(cmd->data->blocks - 1, SSP_CMD0_BLOCK_COUNT); + + if (cmd->opcode == 12) + ssp_cmd0 |= BM_SSP_CMD0_APPEND_8CYC; + + ssp_cmd1 = BF(cmd->arg, SSP_CMD1_CMD_ARG); + + dma_desc->command->pio_words[0] = ssp_ctrl0; + dma_desc->command->pio_words[1] = ssp_cmd0; + dma_desc->command->pio_words[2] = ssp_cmd1; + + /* Set the timeout count */ + timeout = stmp3xxx_ns_to_ssp_ticks(host->clkrt, cmd->data->timeout_ns); + val = __raw_readl(host->ssp_base + HW_SSP_TIMING); + val &= ~(BM_SSP_TIMING_TIMEOUT); + val |= BF(timeout, SSP_TIMING_TIMEOUT); + __raw_writel(val, host->ssp_base + HW_SSP_TIMING); + + init_completion(&host->dma_done); + stmp3xxx_dma_reset_channel(host->dmach); + stmp3xxx_dma_go(host->dmach, dma_desc, 1); + wait_for_completion(&host->dma_done); + if (host->regulator) + regulator_set_current_limit(host->regulator, 0, 0); + + switch (mmc_resp_type(cmd)) { + case MMC_RSP_NONE: + break; + case MMC_RSP_R1: + case MMC_RSP_R3: + cmd->resp[0] = + __raw_readl(host->ssp_base + HW_SSP_SDRESP0); + break; + case MMC_RSP_R2: + cmd->resp[3] = + __raw_readl(host->ssp_base + HW_SSP_SDRESP0); + cmd->resp[2] = + __raw_readl(host->ssp_base + HW_SSP_SDRESP1); + cmd->resp[1] = + __raw_readl(host->ssp_base + HW_SSP_SDRESP2); + cmd->resp[0] = + __raw_readl(host->ssp_base + HW_SSP_SDRESP3); + break; + default: + dev_warn(host->dev, "Unsupported response type 0x%x\n", + mmc_resp_type(cmd)); + BUG(); + break; + } + + cmd->error = stmp3xxx_mmc_cmd_error(host->status); + + if (stmp3xxx_dma_running(host->dmach)) + dev_dbg(host->dev, "DMA command not finished\n"); + + if (cmd->error) { + dev_dbg(host->dev, "Command error 0x%x\n", cmd->error); + stmp3xxx_dma_reset_channel(host->dmach); + } else { + if (is_reading) + cmd->data->bytes_xfered = + stmp3xxx_sg_dma_copy(host, data_size, 0); + else + cmd->data->bytes_xfered = data_size; + + dev_dbg(host->dev, "Transferred %u bytes\n", + cmd->data->bytes_xfered); + } +} + +/* Begin sedning a command to the card */ +static void stmp3xxx_mmc_start_cmd(struct stmp3xxx_mmc_host *host, + struct mmc_command *cmd) +{ + dev_dbg(host->dev, "MMC command:\n" + "type: 0x%x opcode: %u, arg: %u, flags 0x%x retries: %u\n", + mmc_cmd_type(cmd), cmd->opcode, cmd->arg, cmd->flags, + cmd->retries); + + host->cmd = cmd; + + switch (mmc_cmd_type(cmd)) { + case MMC_CMD_BC: + stmp3xxx_mmc_bc(host); + break; + case MMC_CMD_BCR: + stmp3xxx_mmc_ac(host); + break; + case MMC_CMD_AC: + stmp3xxx_mmc_ac(host); + break; + case MMC_CMD_ADTC: + stmp3xxx_mmc_adtc(host); + break; + default: + dev_warn(host->dev, "Unknown MMC command\n"); + BUG(); + break; + } + + dev_dbg(host->dev, "response: %u %u %u %u errors: %u\n", + cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3], + cmd->error); +} + +/* Handle MMC request */ +static void stmp3xxx_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct stmp3xxx_mmc_host *host = mmc_priv(mmc); + + dev_dbg(host->dev, "MMC request\n"); + + host->mrq = mrq; + + stmp3xxx_mmc_start_cmd(host, mrq->cmd); + + if (mrq->data && mrq->data->stop) { + dev_dbg(host->dev, "Stop opcode is %u\n", + mrq->data->stop->opcode); + stmp3xxx_mmc_start_cmd(host, mrq->data->stop); + } + + host->mrq = NULL; + mmc_request_done(mmc, mrq); +} + +/* + * Change divisors to reflect the rate of 'hz'. Note that we should not + * play with clock rate, because the same source is used to clock both + * SSP ports. + */ +static void +stmp3xxx_set_sclk_speed(struct stmp3xxx_mmc_host *host, unsigned int hz) +{ + unsigned long ssp; + u32 div1, div2; + u32 val; + struct stmp3xxxmmc_platform_data *pdata = host->dev->platform_data; + + if (get_evk_board_version() == 1) { + /*EVK Ver1 max clock is 12M */ + if (hz > 12000000) + hz = 12000000; + } + + if (pdata && pdata->setclock) { + /* + if the SSP is buggy and platform provides callback... + well, let it be. + */ + host->clkrt = pdata->setclock(hz); + return; + } + + /* + ...but the RightIdea(tm) is to set divisors to match + the requested clock. + */ + hz /= 1000; + + ssp = clk_get_rate(host->clk); + + for (div1 = 2; div1 < 254; div1 += 2) { + div2 = ssp / hz / div1; + if (div2 < 0x100) + break; + } + if (div1 >= 254) { + dev_err(host->dev, "Cannot set clock to %dkHz\n", hz); + return; + } + + dev_dbg(host->dev, "Setting clock rate to %ld kHz [%x+%x] " + "(requested %d), source %ldk\n", + ssp / div1 / div2, div1, div2, hz, ssp); + + val = __raw_readl(host->ssp_base + HW_SSP_TIMING); + val &= ~(BM_SSP_TIMING_CLOCK_DIVIDE | BM_SSP_TIMING_CLOCK_RATE); + val |= BF(div1, SSP_TIMING_CLOCK_DIVIDE) | + BF(div2 - 1, SSP_TIMING_CLOCK_RATE); + __raw_writel(val, host->ssp_base + HW_SSP_TIMING); + + host->clkrt = ssp / div1 / div2 * 1000; +} + +/* Configure card */ +static void stmp3xxx_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct stmp3xxx_mmc_host *host = mmc_priv(mmc); + struct stmp3xxxmmc_platform_data *pdata; + + dev_dbg(host->dev, "MMC set ios:\n" + "Clock %u, vdd %u, bus_mode %u, chip_select %u, " + "power mode %u, bus_width %u\n", ios->clock, ios->vdd, + ios->bus_mode, ios->chip_select, ios->power_mode, + ios->bus_width); + + pdata = host->dev->platform_data; + + if (pdata->cmd_pullup) { + if (ios->bus_mode == MMC_BUSMODE_PUSHPULL) + pdata->cmd_pullup(0); + else + pdata->cmd_pullup(1); + } else + dev_warn(host->dev, + "Platform does not support CMD pin pullup control\n"); + + if (ios->bus_width == MMC_BUS_WIDTH_4) + host->bus_width_4 = 1; + else + host->bus_width_4 = 0; + + if (ios->clock > 0) + stmp3xxx_set_sclk_speed(host, ios->clock); +} + +static const struct mmc_host_ops stmp3xxx_mmc_ops = { + .request = stmp3xxx_mmc_request, + .get_ro = stmp3xxx_mmc_get_ro, + .set_ios = stmp3xxx_mmc_set_ios, +}; + +/* + * STMP37XX MMC/SD driver initialization + */ + +/* Reset ssp peripheral to default values */ +static void stmp3xxx_mmc_reset(struct stmp3xxx_mmc_host *host) +{ + u32 ssp_ctrl0; + u32 ssp_ctrl1; + + stmp3xxx_reset_block(host->ssp_base, 0); + + /* Configure SSP Control Register 0 */ + ssp_ctrl0 = + BM_SSP_CTRL0_IGNORE_CRC | + BF(BV_SSP_CTRL0_BUS_WIDTH__ONE_BIT, SSP_CTRL0_BUS_WIDTH); + + /* Configure SSP Control Register 1 */ + ssp_ctrl1 = + BM_SSP_CTRL1_DMA_ENABLE | + BM_SSP_CTRL1_POLARITY | + BM_SSP_CTRL1_RECV_TIMEOUT_IRQ_EN | + BM_SSP_CTRL1_DATA_CRC_IRQ_EN | + BM_SSP_CTRL1_DATA_TIMEOUT_IRQ_EN | + BM_SSP_CTRL1_RESP_TIMEOUT_IRQ_EN | + BM_SSP_CTRL1_RESP_ERR_IRQ_EN | + BF(BV_SSP_CTRL1_WORD_LENGTH__EIGHT_BITS, SSP_CTRL1_WORD_LENGTH) | + BF(BV_SSP_CTRL1_SSP_MODE__SD_MMC, SSP_CTRL1_SSP_MODE); + + __raw_writel(BF(0xFFFF, SSP_TIMING_TIMEOUT) | + BF(2, SSP_TIMING_CLOCK_DIVIDE) | + BF(0, SSP_TIMING_CLOCK_RATE), + host->ssp_base + HW_SSP_TIMING); + + /* Write the SSP Control Register 0 and 1 values out to the interface */ + __raw_writel(ssp_ctrl0, host->ssp_base + HW_SSP_CTRL0); + __raw_writel(ssp_ctrl1, host->ssp_base + HW_SSP_CTRL1); +} + +static void stmp3xxx_mmc_irq_release(struct stmp3xxx_mmc_host *host) +{ + free_irq(host->dmairq, host); + free_irq(host->errirq, host); +} + +static int __init stmp3xxx_mmc_irq_init(struct stmp3xxx_mmc_host *host) +{ + int ret; + + ret = request_irq(host->dmairq, mmc_irq_handler, 0, + DRIVER_NAME " dma", host); + if (ret) { + dev_err(host->dev, "Unable to set up DMA irq handler\n"); + goto out0; + } + + ret = request_irq(host->errirq, mmc_irq_handler, IRQF_SHARED, + DRIVER_NAME " error", host); + if (ret) { + dev_err(host->dev, "Unable to set up SSP error irq handler\n"); + goto out1; + } + return 0; + +out1: + free_irq(host->dmairq, host); +out0: + return ret; +} + +/* Allocate and initialise the DMA chains */ +static int stmp3xxx_mmc_dma_init(struct stmp3xxx_mmc_host *host, int reset) +{ + int ret; + + if (!reset) { + /* Allocate DMA channel */ + ret = stmp3xxx_dma_request(host->dmach, + host->dev, "STMP37XX MMC/SD"); + if (ret) { + dev_err(host->dev, "Unable to request DMA channel\n"); + return ret; + } + + host->dma_buf = dma_alloc_coherent(host->dev, SSP_BUFFER_SIZE, + &host->dma_buf_phys, + GFP_DMA); + if (host->dma_buf == NULL) { + dev_err(host->dev, "Unable to allocate DMA memory\n"); + ret = -ENOMEM; + goto out_mem; + } + + ret = stmp3xxx_dma_allocate_command(host->dmach, + &host->dma_desc); + if (ret) { + dev_err(host->dev, + "Unable to allocate DMA descriptor\n"); + goto out_cmd; + } + + host->dma_desc.command->next = (u32) host->dma_desc.handle; + host->dma_desc.command->buf_ptr = (u32) host->dma_buf_phys; + host->dma_desc.virtual_buf_ptr = host->dma_buf; + } + + /* Reset DMA channel */ + stmp3xxx_dma_reset_channel(host->dmach); + + /* Enable DMA interrupt */ + stmp3xxx_dma_clear_interrupt(host->dmach); + stmp3xxx_dma_enable_interrupt(host->dmach); + + return 0; + +out_cmd: + dma_free_coherent(host->dev, SSP_BUFFER_SIZE, host->dma_buf, + host->dma_buf_phys); +out_mem: + stmp3xxx_dma_release(host->dmach); + + return ret; +} + +static void stmp3xxx_mmc_dma_release(struct stmp3xxx_mmc_host *host) +{ + stmp3xxx_dma_reset_channel(host->dmach); + + dma_free_coherent(host->dev, SSP_BUFFER_SIZE, host->dma_buf, + host->dma_buf_phys); + + stmp3xxx_dma_free_command(host->dmach, &host->dma_desc); + stmp3xxx_dma_release(host->dmach); +} + +/* Probe peripheral for connected cards */ +static int __init stmp3xxx_mmc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct stmp3xxxmmc_platform_data *mmc_data; + struct stmp3xxx_mmc_host *host; + struct mmc_host *mmc; + struct resource *r; + int err = 0; + + mmc_data = dev->platform_data; + if (mmc_data == NULL) { + err = -EINVAL; + dev_err(dev, "Missing platform data\n"); + goto out; + } + + /* Allocate main MMC host structure */ + mmc = mmc_alloc_host(sizeof(struct stmp3xxx_mmc_host), dev); + if (!mmc) { + dev_err(dev, "Unable to allocate MMC host\n"); + err = -ENOMEM; + goto out; + } + host = mmc_priv(mmc); + + host->read_uA = mmc_data->read_uA; + host->write_uA = mmc_data->write_uA; + host->regulator = regulator_get(NULL, "mmc_ssp-1"); + if (host->regulator && !IS_ERR(host->regulator)) + regulator_set_mode(host->regulator, REGULATOR_MODE_NORMAL); + else + host->regulator = NULL; + + /* get resources: */ + + /* + * 1. io memory + */ + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "failed to get IORESOURCE_MEM\n"); + err = -ENXIO; + goto out_res; + } + host->ssp_base = + r->start - STMP3XXX_REGS_PHBASE + STMP3XXX_REGS_BASE; + + /* + * 2. DMA channel + */ + r = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!r) { + dev_err(&pdev->dev, "failed to get IORESOURCE_DMA\n"); + err = -ENXIO; + goto out_res; + } + host->dmach = r->start; + + /* + * 3. two IRQs + */ + host->dmairq = platform_get_irq(pdev, 0); + if (host->dmairq < 0) { + dev_err(&pdev->dev, "failed to get IORESOURCE_IRQ/0\n"); + err = host->dmairq; + goto out_res; + } + + host->errirq = platform_get_irq(pdev, 1); + if (host->errirq < 0) { + dev_err(&pdev->dev, "failed to get IORESOURCE_IRQ/1\n"); + err = host->errirq; + goto out_res; + } + + /* Set up MMC pins */ + if (mmc_data->hw_init) { + err = mmc_data->hw_init(); + if (err) { + dev_err(dev, "MMC HW configuration failed\n"); + goto out_res; + } + } + + host->mmc = mmc; + host->dev = dev; + + /* Set minimal clock rate */ + host->clk = clk_get(dev, "ssp"); + if (IS_ERR(host->clk)) { + err = PTR_ERR(host->clk); + dev_err(dev, "Clocks initialization failed\n"); + goto out_clk; + } + + clk_enable(host->clk); + stmp3xxx_set_sclk_speed(host, CLOCKRATE_MIN); + + /* Reset MMC block */ + stmp3xxx_mmc_reset(host); + + /* Enable DMA */ + err = stmp3xxx_mmc_dma_init(host, 0); + if (err) { + dev_err(dev, "DMA init failed\n"); + goto out_dma; + } + + /* Set up interrupt handlers */ + err = stmp3xxx_mmc_irq_init(host); + if (err) { + dev_err(dev, "IRQ initialization failed\n"); + goto out_irq; + } + + /* Get current card status for further cnanges tracking */ + host->present = stmp3xxx_mmc_is_plugged(host); + + /* Add a card detection polling timer */ + init_timer(&host->timer); + host->timer.function = stmp3xxx_mmc_detect_poll; + host->timer.data = (unsigned long)host; + host->timer.expires = jiffies + STMP37XX_MMC_DETECT_TIMEOUT; + add_timer(&host->timer); + + mmc->ops = &stmp3xxx_mmc_ops; + mmc->f_min = CLOCKRATE_MIN; + mmc->f_max = CLOCKRATE_MAX; + mmc->caps = MMC_CAP_4_BIT_DATA; + + /* Maximum block count requests. */ + mmc->max_blk_size = 512; + mmc->max_blk_count = SSP_BUFFER_SIZE / 512; + mmc->max_hw_segs = SSP_BUFFER_SIZE / 512; + mmc->max_phys_segs = SSP_BUFFER_SIZE / 512; + mmc->max_req_size = SSP_BUFFER_SIZE; + mmc->max_seg_size = SSP_BUFFER_SIZE; + + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + + platform_set_drvdata(pdev, mmc); + + err = mmc_add_host(mmc); + if (err) { + dev_err(dev, "Oh God. mmc_add_host failed\n"); + goto out_all; + } + + return err; + +out_all: + +out_irq: + stmp3xxx_mmc_dma_release(host); +out_dma: + clk_disable(host->clk); +out_clk: + if (mmc_data->hw_release) + mmc_data->hw_release(); +out_res: + mmc_free_host(mmc); +out: + return err; +} + +static int __exit stmp3xxx_mmc_remove(struct platform_device *pdev) +{ + struct stmp3xxx_mmc_host *host; + struct stmp3xxxmmc_platform_data *mmc_data; + struct mmc_host *mmc; + + dev_info(&pdev->dev, "Removing\n"); + + mmc_data = pdev->dev.platform_data; + mmc = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); + + host = mmc_priv(mmc); + mmc_remove_host(mmc); + + /* Disable SSP clock */ + clk_disable(host->clk); + clk_put(host->clk); + + /* Release IRQs */ + stmp3xxx_mmc_irq_release(host); + + /* Delete card detection timer */ + del_timer(&host->timer); + + /* Release DMA */ + stmp3xxx_mmc_dma_release(host); + if (host->regulator) + regulator_put(host->regulator); + + mmc_free_host(mmc); + + if (mmc_data->hw_release) + mmc_data->hw_release(); + + return 0; +} + +#ifdef CONFIG_PM +static int stmp3xxx_mmc_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct stmp3xxx_mmc_host *host; + struct stmp3xxxmmc_platform_data *mmc_data; + struct mmc_host *mmc; + int ret = 0; + + dev_dbg(&pdev->dev, "Suspending\n"); + + mmc_data = pdev->dev.platform_data; + mmc = platform_get_drvdata(pdev); + host = mmc_priv(mmc); + + ret = mmc_suspend_host(mmc, state); + if (!ret) { + if (mmc_data && mmc_data->hw_release) + mmc_data->hw_release(); + clk_disable(host->clk); + } + return ret; +} + +static int stmp3xxx_mmc_resume(struct platform_device *pdev) +{ + struct stmp3xxx_mmc_host *host; + struct stmp3xxxmmc_platform_data *mmc_data; + struct mmc_host *mmc; + + dev_dbg(&pdev->dev, "Resuming\n"); + + mmc_data = pdev->dev.platform_data; + mmc = platform_get_drvdata(pdev); + host = mmc_priv(mmc); + + clk_enable(host->clk); + + if (mmc_data->hw_init) + mmc_data->hw_init(); + stmp3xxx_mmc_reset(host); + stmp3xxx_mmc_dma_init(host, 1); + + return mmc_resume_host(mmc); +} +#else +#define stmp3xxx_mmc_suspend NULL +#define stmp3xxx_mmc_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_driver stmp3xxx_mmc_driver = { + .probe = stmp3xxx_mmc_probe, + .remove = __exit_p(stmp3xxx_mmc_remove), + .suspend = stmp3xxx_mmc_suspend, + .resume = stmp3xxx_mmc_resume, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init stmp3xxx_mmc_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&stmp3xxx_mmc_driver); + if (ret < 0) + return ret; + + return ret; +} + +static void __exit stmp3xxx_mmc_exit(void) +{ + platform_driver_unregister(&stmp3xxx_mmc_driver); +} + +module_init(stmp3xxx_mmc_init); +module_exit(stmp3xxx_mmc_exit); + +MODULE_DESCRIPTION("STMP37xx/378x MMC peripheral"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile index 0993d5cf3923..f008baccd796 100644 --- a/drivers/mtd/devices/Makefile +++ b/drivers/mtd/devices/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_MTD_LART) += lart.o obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o obj-$(CONFIG_MTD_M25P80) += m25p80.o + diff --git a/drivers/mtd/devices/mxc_dataflash.c b/drivers/mtd/devices/mxc_dataflash.c new file mode 100644 index 000000000000..88c5788cb445 --- /dev/null +++ b/drivers/mtd/devices/mxc_dataflash.c @@ -0,0 +1,1031 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * (c) 2005 MontaVista Software, Inc. + * + * This code is based on mtd_dataflash.c by adding FSL spi access. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/err.h> + +#include <linux/spi/spi.h> +#include <linux/spi/flash.h> + +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> + +/* + * DataFlash is a kind of SPI flash. Most AT45 chips have two buffers in + * each chip, which may be used for double buffered I/O; but this driver + * doesn't (yet) use these for any kind of i/o overlap or prefetching. + * + * Sometimes DataFlash is packaged in MMC-format cards, although the + * MMC stack can't (yet?) distinguish between MMC and DataFlash + * protocols during enumeration. + */ + +/* reads can bypass the buffers */ +#define OP_READ_CONTINUOUS 0xE8 +#define OP_READ_PAGE 0xD2 + +/* group B requests can run even while status reports "busy" */ +#define OP_READ_STATUS 0xD7 /* group B */ + +/* move data between host and buffer */ +#define OP_READ_BUFFER1 0xD4 /* group B */ +#define OP_READ_BUFFER2 0xD6 /* group B */ +#define OP_WRITE_BUFFER1 0x84 /* group B */ +#define OP_WRITE_BUFFER2 0x87 /* group B */ + +/* erasing flash */ +#define OP_ERASE_PAGE 0x81 +#define OP_ERASE_BLOCK 0x50 + +/* move data between buffer and flash */ +#define OP_TRANSFER_BUF1 0x53 +#define OP_TRANSFER_BUF2 0x55 +#define OP_MREAD_BUFFER1 0xD4 +#define OP_MREAD_BUFFER2 0xD6 +#define OP_MWERASE_BUFFER1 0x83 +#define OP_MWERASE_BUFFER2 0x86 +#define OP_MWRITE_BUFFER1 0x88 /* sector must be pre-erased */ +#define OP_MWRITE_BUFFER2 0x89 /* sector must be pre-erased */ + +/* write to buffer, then write-erase to flash */ +#define OP_PROGRAM_VIA_BUF1 0x82 +#define OP_PROGRAM_VIA_BUF2 0x85 + +/* compare buffer to flash */ +#define OP_COMPARE_BUF1 0x60 +#define OP_COMPARE_BUF2 0x61 + +/* read flash to buffer, then write-erase to flash */ +#define OP_REWRITE_VIA_BUF1 0x58 +#define OP_REWRITE_VIA_BUF2 0x59 + +/* newer chips report JEDEC manufacturer and device IDs; chip + * serial number and OTP bits; and per-sector writeprotect. + */ +#define OP_READ_ID 0x9F +#define OP_READ_SECURITY 0x77 +#define OP_WRITE_SECURITY_REVC 0x9A +#define OP_WRITE_SECURITY 0x9B /* revision D */ + +#define SPI_FIFOSIZE 24 /* Bust size in bytes */ +#define CMD_SIZE 4 +#define DUMY_SIZE 4 + +struct dataflash { + uint8_t command[4]; + char name[24]; + + unsigned partitioned:1; + + unsigned short page_offset; /* offset in flash address */ + unsigned int page_size; /* of bytes per page */ + + struct mutex lock; + struct spi_device *spi; + + struct mtd_info mtd; +}; + +#ifdef CONFIG_MTD_PARTITIONS +#define mtd_has_partitions() (1) +#else +#define mtd_has_partitions() (0) +#endif + +/* ......................................................................... */ + +/* + * This function initializes the SPI device parameters. + */ +static inline int spi_nor_setup(struct spi_device *spi, u8 bst_len) +{ + spi->bits_per_word = bst_len << 3; + + return spi_setup(spi); +} + +/* + * This function perform spi read/write transfer. + */ +static int spi_read_write(struct spi_device *spi, u8 * buf, u32 len) +{ + struct spi_message m; + struct spi_transfer t; + + if (len > SPI_FIFOSIZE || len <= 0) + return -1; + + spi_nor_setup(spi, len); + + spi_message_init(&m); + memset(&t, 0, sizeof t); + + t.tx_buf = buf; + t.rx_buf = buf; + t.len = ((len - 1) >> 2) + 1; + + spi_message_add_tail(&t, &m); + + if (spi_sync(spi, &m) != 0 || m.status != 0) { + printk(KERN_ERR "%s: error\n", __func__); + return -1; + } + + DEBUG(MTD_DEBUG_LEVEL2, "%s: len: 0x%x success\n", __func__, len); + + return 0; + +} + +/* + * Return the status of the DataFlash device. + */ +static inline int dataflash_status(struct spi_device *spi) +{ + /* NOTE: at45db321c over 25 MHz wants to write + * a dummy byte after the opcode... + */ + ssize_t retval; + + u16 val = OP_READ_STATUS << 8; + + retval = spi_read_write(spi, (u8 *)&val, 2); + + if (retval < 0) + return retval; + + DEBUG(MTD_DEBUG_LEVEL2, "%s: status: 0x%x\n", __func__, val & 0xff); + + return val & 0xff; +} + +/* + * Poll the DataFlash device until it is READY. + * This usually takes 5-20 msec or so; more for sector erase. + */ +static int dataflash_waitready(struct spi_device *spi) +{ + int status; + + for (;;) { + status = dataflash_status(spi); + if (status < 0) { + DEBUG(MTD_DEBUG_LEVEL1, "%s: status %d?\n", + dev_name(&spi->dev), status); + status = 0; + } + + if (status & (1 << 7)) /* RDY/nBSY */ + return status; + + msleep(3); + } +} + +/* ......................................................................... */ + +/* + * Erase pages of flash. + */ +static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct dataflash *priv = (struct dataflash *)mtd->priv; + struct spi_device *spi = priv->spi; + unsigned blocksize = priv->page_size << 3; + uint8_t *command; + + DEBUG(MTD_DEBUG_LEVEL2, "%s: erase addr=0x%x len 0x%x\n", + dev_name(&spi->dev), instr->addr, instr->len); + + /* Sanity checks */ + if ((instr->addr + instr->len) > mtd->size + || (instr->len % priv->page_size) != 0 + || (instr->addr % priv->page_size) != 0) + return -EINVAL; + + command = priv->command; + + mutex_lock(&priv->lock); + while (instr->len > 0) { + unsigned int pageaddr; + int status; + int do_block; + + /* Calculate flash page address; use block erase (for speed) if + * we're at a block boundary and need to erase the whole block. + */ + pageaddr = instr->addr / priv->page_size; + do_block = (pageaddr & 0x7) == 0 && instr->len >= blocksize; + pageaddr = pageaddr << priv->page_offset; + + command[3] = do_block ? OP_ERASE_BLOCK : OP_ERASE_PAGE; + command[2] = (uint8_t) (pageaddr >> 16); + command[1] = (uint8_t) (pageaddr >> 8); + command[0] = 0; + + DEBUG(MTD_DEBUG_LEVEL3, "ERASE %s: (%x) %x %x %x [%i]\n", + do_block ? "block" : "page", + command[0], command[1], command[2], command[3], pageaddr); + + status = spi_read_write(spi, command, 4); + (void)dataflash_waitready(spi); + + if (status < 0) { + printk(KERN_ERR "%s: erase %x, err %d\n", + dev_name(&spi->dev), pageaddr, status); + /* REVISIT: can retry instr->retries times; or + * giveup and instr->fail_addr = instr->addr; + */ + continue; + } + + if (do_block) { + instr->addr += blocksize; + instr->len -= blocksize; + } else { + instr->addr += priv->page_size; + instr->len -= priv->page_size; + } + } + mutex_unlock(&priv->lock); + + /* Inform MTD subsystem that erase is complete */ + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + + return 0; +} + +/* + * Read from the DataFlash device. + * from : Start offset in flash device + * len : Amount to read + * retlen : About of data actually read + * buf : Buffer containing the data + */ +static int dataflash_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct dataflash *priv = mtd->priv; + struct spi_device *spi = priv->spi; + u32 addr; + int rx_len = 0, count = 0, i = 0; + u_char txer[SPI_FIFOSIZE]; + u_char *s = txer; + u_char *d = buf; + int cmd_len = CMD_SIZE + DUMY_SIZE; + int status = 0; + + DEBUG(MTD_DEBUG_LEVEL2, "%s: read 0x%x..0x%x\n", + dev_name(&priv->spi->dev), (unsigned)from, (unsigned)(from + len)); + + *retlen = 0; + + /* Sanity checks */ + if (!len) + return 0; + + if (from + len > mtd->size) + return -EINVAL; + + /* Calculate flash page/byte address */ + addr = (((unsigned)from / priv->page_size) << priv->page_offset) + + ((unsigned)from % priv->page_size); + + mutex_unlock(&priv->lock); + + while (len > 0) { + + rx_len = len > (SPI_FIFOSIZE - cmd_len) ? + SPI_FIFOSIZE - cmd_len : len; + + txer[3] = OP_READ_CONTINUOUS; + txer[2] = (addr >> 16) & 0xff; + txer[1] = (addr >> 8) & 0xff; + txer[0] = addr & 0xff; + + status = spi_read_write(spi, txer, + roundup(rx_len, 4) + cmd_len); + if (status) { + mutex_unlock(&priv->lock); + return status; + } + + s = txer + cmd_len; + + for (i = rx_len; i >= 0; i -= 4, s += 4) { + if (i < 4) { + if (i == 1) { + *d = s[3]; + } else if (i == 2) { + *d++ = s[3]; + *d++ = s[2]; + } else if (i == 3) { + *d++ = s[3]; + *d++ = s[2]; + *d++ = s[1]; + } + + break; + } + + *d++ = s[3]; + *d++ = s[2]; + *d++ = s[1]; + *d++ = s[0]; + } + + /* updaate */ + len -= rx_len; + addr += rx_len; + count += rx_len; + + DEBUG(MTD_DEBUG_LEVEL2, + "%s: left:0x%x, from:0x%08x, to:0x%p, done: 0x%x\n", + __func__, len, (u32) addr, d, count); + } + + *retlen = count; + + DEBUG(MTD_DEBUG_LEVEL2, "%s: %d bytes read\n", __func__, count); + + mutex_unlock(&priv->lock); + + return status; +} + +/* + * Write to the DataFlash device. + * to : Start offset in flash device + * len : Amount to write + * retlen : Amount of data actually written + * buf : Buffer containing the data + */ +static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct dataflash *priv = mtd->priv; + struct spi_device *spi = priv->spi; + u32 pageaddr, addr, offset, writelen; + size_t remaining = len; + u_char *writebuf = (u_char *) buf; + int status = -EINVAL; + u_char txer[SPI_FIFOSIZE] = { 0 }; + uint8_t *command = priv->command; + u_char *d = txer; + u_char *s = (u_char *) buf; + int delta = 0, l = 0, i = 0, count = 0; + + DEBUG(MTD_DEBUG_LEVEL2, "%s: write 0x%x..0x%x\n", + dev_name(&spi->dev), (unsigned)to, (unsigned)(to + len)); + + *retlen = 0; + + /* Sanity checks */ + if (!len) + return 0; + + if ((to + len) > mtd->size) + return -EINVAL; + + pageaddr = ((unsigned)to / priv->page_size); + offset = ((unsigned)to % priv->page_size); + if (offset + len > priv->page_size) + writelen = priv->page_size - offset; + else + writelen = len; + + mutex_lock(&priv->lock); + + while (remaining > 0) { + DEBUG(MTD_DEBUG_LEVEL3, "write @ %i:%i len=%i\n", + pageaddr, offset, writelen); + + addr = pageaddr << priv->page_offset; + + /* (1) Maybe transfer partial page to Buffer1 */ + if (writelen != priv->page_size) { + command[3] = OP_TRANSFER_BUF1; + command[2] = (addr & 0x00FF0000) >> 16; + command[1] = (addr & 0x0000FF00) >> 8; + command[0] = 0; + + DEBUG(MTD_DEBUG_LEVEL3, "TRANSFER: (%x) %x %x %x\n", + command[3], command[2], command[1], command[0]); + + status = spi_read_write(spi, command, CMD_SIZE); + if (status) { + mutex_unlock(&priv->lock); + return status; + + } + + (void)dataflash_waitready(spi); + } + + count = writelen; + while (count) { + d = txer; + l = count > (SPI_FIFOSIZE - CMD_SIZE) ? + SPI_FIFOSIZE - CMD_SIZE : count; + + delta = l % 4; + if (delta) { + switch (delta) { + case 1: + d[0] = OP_WRITE_BUFFER1; + d[6] = (offset >> 8) & 0xff; + d[5] = offset & 0xff; + d[4] = *s++; + break; + case 2: + d[1] = OP_WRITE_BUFFER1; + d[7] = (offset >> 8) & 0xff; + d[6] = offset & 0xff; + d[5] = *s++; + d[4] = *s++; + break; + case 3: + d[2] = OP_WRITE_BUFFER1; + d[0] = (offset >> 8) & 0xff; + d[7] = offset & 0xff; + d[6] = *s++; + d[5] = *s++; + d[4] = *s++; + break; + default: + break; + } + + DEBUG(MTD_DEBUG_LEVEL3, + "WRITEBUF: (%x) %x %x %x\n", + txer[3], txer[2], txer[1], txer[0]); + + status = spi_read_write(spi, txer, + delta + CMD_SIZE); + if (status) { + mutex_unlock(&priv->lock); + return status; + } + + /* update */ + count -= delta; + offset += delta; + l -= delta; + } + + d[3] = OP_WRITE_BUFFER1; + d[1] = (offset >> 8) & 0xff; + d[0] = offset & 0xff; + + for (i = 0, d += 4; i < l / 4; i++, d += 4) { + d[3] = *s++; + d[2] = *s++; + d[1] = *s++; + d[0] = *s++; + } + + DEBUG(MTD_DEBUG_LEVEL3, "WRITEBUF: (%x) %x %x %x\n", + txer[3], txer[2], txer[1], txer[0]); + + status = spi_read_write(spi, txer, l + CMD_SIZE); + if (status) { + mutex_unlock(&priv->lock); + return status; + } + + /* update */ + count -= l; + offset += l; + } + + /* (2) Program full page via Buffer1 */ + command[3] = OP_MWERASE_BUFFER1; + command[2] = (addr >> 16) & 0xff; + command[1] = (addr >> 8) & 0xff; + + DEBUG(MTD_DEBUG_LEVEL3, "PROGRAM: (%x) %x %x %x\n", + command[3], command[2], command[1], command[0]); + + status = spi_read_write(spi, command, CMD_SIZE); + if (status) { + mutex_unlock(&priv->lock); + return status; + } + + (void)dataflash_waitready(spi); + + remaining -= writelen; + pageaddr++; + offset = 0; + writebuf += writelen; + *retlen += writelen; + + if (remaining > priv->page_size) + writelen = priv->page_size; + else + writelen = remaining; + } + mutex_unlock(&priv->lock); + + return status; +} + +/* ......................................................................... */ + +#ifdef CONFIG_MTD_DATAFLASH_OTP + +static int dataflash_get_otp_info(struct mtd_info *mtd, + struct otp_info *info, size_t len) +{ + /* Report both blocks as identical: bytes 0..64, locked. + * Unless the user block changed from all-ones, we can't + * tell whether it's still writable; so we assume it isn't. + */ + info->start = 0; + info->length = 64; + info->locked = 1; + return sizeof(*info); +} + +static ssize_t otp_read(struct spi_device *spi, unsigned base, + uint8_t *buf, loff_t off, size_t len) +{ + struct dataflash *priv = mtd->priv; + struct spi_device *spi = priv->spi; + int rx_len = 0, count = 0, i = 0; + u_char txer[SPI_FIFOSIZE]; + u_char *s = txer; + u_char *d = NULL; + int cmd_len = CMD_SIZE; + int status; + + if (off > 64) + return -EINVAL; + + if ((off + len) > 64) + len = 64 - off; + if (len == 0) + return len; + + /* to make simple, we read 64 out */ + l = base + 64; + + d = kzalloc(l, GFP_KERNEL); + if (!d) + return -ENOMEM; + + while (l > 0) { + + rx_len = l > (SPI_FIFOSIZE - cmd_len) ? + SPI_FIFOSIZE - cmd_len : l; + + txer[3] = OP_READ_SECURITY; + + status = spi_read_write(spi, txer, rx_len + cmd_len); + if (status) { + mutex_unlock(&priv->lock); + return status; + } + + s = txer + cmd_len; + for (i = rx_len; i >= 0; i -= 4, s += 4) { + + *d++ = s[3]; + *d++ = s[2]; + *d++ = s[1]; + *d++ = s[0]; + } + + /* updaate */ + l -= rx_len; + addr += rx_len; + count += rx_len; + + DEBUG(MTD_DEBUG_LEVEL2, + "%s: left:0x%x, from:0x%08x, to:0x%p, done: 0x%x\n", + __func__, len, (u32) addr, d, count); + } + + d -= count; + memcpy(buf, d + base + off, len); + + mutex_unlock(&priv->lock); + + return len; +} + +static int dataflash_read_fact_otp(struct mtd_info *mtd, + loff_t from, size_t len, size_t *retlen, + u_char *buf) +{ + struct dataflash *priv = (struct dataflash *)mtd->priv; + int status; + + /* 64 bytes, from 0..63 ... start at 64 on-chip */ + mutex_lock(&priv->lock); + status = otp_read(priv->spi, 64, buf, from, len); + mutex_unlock(&priv->lock); + + if (status < 0) + return status; + *retlen = status; + return 0; +} + +static int dataflash_read_user_otp(struct mtd_info *mtd, + loff_t from, size_t len, size_t *retlen, + u_char *buf) +{ + struct dataflash *priv = (struct dataflash *)mtd->priv; + int status; + + /* 64 bytes, from 0..63 ... start at 0 on-chip */ + mutex_lock(&priv->lock); + status = otp_read(priv->spi, 0, buf, from, len); + mutex_unlock(&priv->lock); + + if (status < 0) + return status; + *retlen = status; + return 0; +} + +static int dataflash_write_user_otp(struct mtd_info *mtd, + loff_t from, size_t len, size_t *retlen, + u_char *buf) +{ + printk(KERN_ERR "%s not support!!\n", __func__); + return 0; +} + +static char *otp_setup(struct mtd_info *device, char revision) +{ + device->get_fact_prot_info = dataflash_get_otp_info; + device->read_fact_prot_reg = dataflash_read_fact_otp; + device->get_user_prot_info = dataflash_get_otp_info; + device->read_user_prot_reg = dataflash_read_user_otp; + + /* rev c parts (at45db321c and at45db1281 only!) use a + * different write procedure; not (yet?) implemented. + */ + if (revision > 'c') + device->write_user_prot_reg = dataflash_write_user_otp; + + return ", OTP"; +} + +#else + +static char *otp_setup(struct mtd_info *device, char revision) +{ + return " (OTP)"; +} + +#endif + +/* ......................................................................... */ + +/* + * Register DataFlash device with MTD subsystem. + */ +static int __devinit +add_dataflash_otp(struct spi_device *spi, char *name, + int nr_pages, int pagesize, int pageoffset, char revision) +{ + struct dataflash *priv; + struct mtd_info *device; + struct flash_platform_data *pdata = spi->dev.platform_data; + char *otp_tag = ""; + + priv = kzalloc(sizeof *priv, GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->lock); + priv->spi = spi; + priv->page_size = pagesize; + priv->page_offset = pageoffset; + + /* name must be usable with cmdlinepart */ + sprintf(priv->name, "spi%d.%d-%s", + spi->master->bus_num, spi->chip_select, name); + + device = &priv->mtd; + device->name = (pdata && pdata->name) ? pdata->name : priv->name; + device->size = nr_pages * pagesize; + device->erasesize = pagesize; + device->writesize = pagesize; + device->owner = THIS_MODULE; + device->type = MTD_DATAFLASH; + device->flags = MTD_CAP_NORFLASH; + device->erase = dataflash_erase; + device->read = dataflash_read; + device->write = dataflash_write; + device->priv = priv; + + if (revision >= 'c') + otp_tag = otp_setup(device, revision); + + dev_info(&spi->dev, "%s (%d KBytes) pagesize %d bytes%s\n", + name, DIV_ROUND_UP(device->size, 1024), pagesize, otp_tag); + dev_set_drvdata(&spi->dev, priv); + + if (mtd_has_partitions()) { + struct mtd_partition *parts; + int nr_parts = 0; + +#ifdef CONFIG_MTD_CMDLINE_PARTS + static const char *part_probes[] = { "cmdlinepart", NULL, }; + + nr_parts = parse_mtd_partitions(device, part_probes, &parts, 0); +#endif + + if (nr_parts <= 0 && pdata && pdata->parts) { + parts = pdata->parts; + nr_parts = pdata->nr_parts; + } + + if (nr_parts > 0) { + priv->partitioned = 1; + return add_mtd_partitions(device, parts, nr_parts); + } + } else if (pdata && pdata->nr_parts) + dev_warn(&spi->dev, "ignoring %d default partitions on %s\n", + pdata->nr_parts, device->name); + + return add_mtd_device(device) == 1 ? -ENODEV : 0; +} + +static inline int __devinit +add_dataflash(struct spi_device *spi, char *name, + int nr_pages, int pagesize, int pageoffset) +{ + return add_dataflash_otp(spi, name, nr_pages, pagesize, pageoffset, 0); +} + +struct flash_info { + char *name; + + /* JEDEC id has a high byte of zero plus three data bytes: + * the manufacturer id, then a two byte device id. + */ + uint32_t jedec_id; + + /* The size listed here is what works with OP_ERASE_PAGE. */ + unsigned nr_pages; + uint16_t pagesize; + uint16_t pageoffset; + + uint16_t flags; +#define SUP_POW2PS 0x0002 /* supports 2^N byte pages */ +#define IS_POW2PS 0x0001 /* uses 2^N byte pages */ +}; + +static struct flash_info __devinitdata dataflash_data[] = { + + /* + * NOTE: chips with SUP_POW2PS (rev D and up) need two entries, + * one with IS_POW2PS and the other without. The entry with the + * non-2^N byte page size can't name exact chip revisions without + * losing backwards compatibility for cmdlinepart. + * + * These newer chips also support 128-byte security registers (with + * 64 bytes one-time-programmable) and software write-protection. + */ + {"AT45DB011B", 0x1f2200, 512, 264, 9, SUP_POW2PS}, + {"at45db011d", 0x1f2200, 512, 256, 8, SUP_POW2PS | IS_POW2PS}, + + {"AT45DB021B", 0x1f2300, 1024, 264, 9, SUP_POW2PS}, + {"at45db021d", 0x1f2300, 1024, 256, 8, SUP_POW2PS | IS_POW2PS}, + + {"AT45DB041x", 0x1f2400, 2048, 264, 9, SUP_POW2PS}, + {"at45db041d", 0x1f2400, 2048, 256, 8, SUP_POW2PS | IS_POW2PS}, + + {"AT45DB081B", 0x1f2500, 4096, 264, 9, SUP_POW2PS}, + {"at45db081d", 0x1f2500, 4096, 256, 8, SUP_POW2PS | IS_POW2PS}, + + {"AT45DB161x", 0x1f2600, 4096, 528, 10, SUP_POW2PS}, + {"at45db161d", 0x1f2600, 4096, 512, 9, SUP_POW2PS | IS_POW2PS}, + + {"AT45DB321x", 0x1f2700, 8192, 528, 10, 0}, /* rev C */ + + {"AT45DB321x", 0x1f2701, 8192, 528, 10, SUP_POW2PS}, + {"at45db321d", 0x1f2701, 8192, 512, 9, SUP_POW2PS | IS_POW2PS}, + + {"AT45DB642x", 0x1f2800, 8192, 1056, 11, SUP_POW2PS}, + {"at45db642d", 0x1f2800, 8192, 1024, 10, SUP_POW2PS | IS_POW2PS}, +}; + +static struct flash_info *__devinit jedec_probe(struct spi_device *spi) +{ + int tmp; + u32 code = OP_READ_ID << 24; + u32 jedec; + struct flash_info *info; + int status; + + /* JEDEC also defines an optional "extended device information" + * string for after vendor-specific data, after the three bytes + * we use here. Supporting some chips might require using it. + * + * If the vendor ID isn't Atmel's (0x1f), assume this call failed. + * That's not an error; only rev C and newer chips handle it, and + * only Atmel sells these chips. + */ + + tmp = spi_read_write(spi, (u8 *)&code, 4); + if (tmp < 0) { + DEBUG(MTD_DEBUG_LEVEL0, "%s: error %d reading JEDEC ID\n", + dev_name(&spi->dev), tmp); + return NULL; + } + + jedec = code & 0xFFFFFF; + + for (tmp = 0, info = dataflash_data; + tmp < ARRAY_SIZE(dataflash_data); tmp++, info++) { + if (info->jedec_id == jedec) { + DEBUG(MTD_DEBUG_LEVEL1, "%s: OTP, sector protect%s\n", + dev_name(&spi->dev), (info->flags & SUP_POW2PS) + ? ", binary pagesize" : ""); + if (info->flags & SUP_POW2PS) { + status = dataflash_status(spi); + if (status < 0) { + DEBUG(MTD_DEBUG_LEVEL1, + "%s: status error %d\n", + dev_name(&spi->dev), status); + return ERR_PTR(status); + } + if (status & 0x1) { + if (info->flags & IS_POW2PS) + return info; + } else { + if (!(info->flags & IS_POW2PS)) + return info; + } + } + } + } + + /* + * Treat other chips as errors ... we won't know the right page + * size (it might be binary) even when we can tell which density + * class is involved (legacy chip id scheme). + */ + dev_warn(&spi->dev, "JEDEC id %06x not handled\n", jedec); + return ERR_PTR(-ENODEV); +} + +/* + * Detect and initialize DataFlash device, using JEDEC IDs on newer chips + * or else the ID code embedded in the status bits: + * + * Device Density ID code #Pages PageSize Offset + * AT45DB011B 1Mbit (128K) xx0011xx (0x0c) 512 264 9 + * AT45DB021B 2Mbit (256K) xx0101xx (0x14) 1024 264 9 + * AT45DB041B 4Mbit (512K) xx0111xx (0x1c) 2048 264 9 + * AT45DB081B 8Mbit (1M) xx1001xx (0x24) 4096 264 9 + * AT45DB0161B 16Mbit (2M) xx1011xx (0x2c) 4096 528 10 + * AT45DB0321B 32Mbit (4M) xx1101xx (0x34) 8192 528 10 + * AT45DB0642 64Mbit (8M) xx111xxx (0x3c) 8192 1056 11 + * AT45DB1282 128Mbit (16M) xx0100xx (0x10) 16384 1056 11 + */ +static int __devinit dataflash_probe(struct spi_device *spi) +{ + int status; + struct flash_info *info; + + /* + * Try to detect dataflash by JEDEC ID. + * If it succeeds we know we have either a C or D part. + * D will support power of 2 pagesize option. + * Both support the security register, though with different + * write procedures. + */ + info = jedec_probe(spi); + if (IS_ERR(info)) + return PTR_ERR(info); + if (info != NULL) + return add_dataflash_otp(spi, info->name, info->nr_pages, + info->pagesize, info->pageoffset, + (info->flags & SUP_POW2PS) ? 'd' : + 'c'); + + /* + * Older chips support only legacy commands, identifing + * capacity using bits in the status byte. + */ + status = dataflash_status(spi); + if (status <= 0 || status == 0xff) { + DEBUG(MTD_DEBUG_LEVEL1, "%s: status error %d\n", + dev_name(&spi->dev), status); + if (status == 0 || status == 0xff) + status = -ENODEV; + return status; + } + + /* if there's a device there, assume it's dataflash. + * board setup should have set spi->max_speed_max to + * match f(car) for continuous reads, mode 0 or 3. + */ + switch (status & 0x3c) { + case 0x0c: /* 0 0 1 1 x x */ + status = add_dataflash(spi, "AT45DB011B", 512, 264, 9); + break; + case 0x14: /* 0 1 0 1 x x */ + status = add_dataflash(spi, "AT45DB021B", 1024, 264, 9); + break; + case 0x1c: /* 0 1 1 1 x x */ + status = add_dataflash(spi, "AT45DB041x", 2048, 264, 9); + break; + case 0x24: /* 1 0 0 1 x x */ + status = add_dataflash(spi, "AT45DB081B", 4096, 264, 9); + break; + case 0x2c: /* 1 0 1 1 x x */ + status = add_dataflash(spi, "AT45DB161x", 4096, 528, 10); + break; + case 0x34: /* 1 1 0 1 x x */ + status = add_dataflash(spi, "AT45DB321x", 8192, 528, 10); + break; + case 0x38: /* 1 1 1 x x x */ + case 0x3c: + status = add_dataflash(spi, "AT45DB642x", 8192, 1056, 11); + break; + /* obsolete AT45DB1282 not (yet?) supported */ + default: + DEBUG(MTD_DEBUG_LEVEL1, "%s: unsupported device (%x)\n", + dev_name(&spi->dev), status & 0x3c); + status = -ENODEV; + } + + if (status < 0) + DEBUG(MTD_DEBUG_LEVEL1, "%s: add_dataflash --> %d\n", + dev_name(&spi->dev), status); + + return status; +} + +static int __devexit dataflash_remove(struct spi_device *spi) +{ + struct dataflash *flash = dev_get_drvdata(&spi->dev); + int status; + + DEBUG(MTD_DEBUG_LEVEL1, "%s: remove\n", dev_name(&spi->dev)); + + if (mtd_has_partitions() && flash->partitioned) + status = del_mtd_partitions(&flash->mtd); + else + status = del_mtd_device(&flash->mtd); + if (status == 0) + kfree(flash); + return status; +} + +static struct spi_driver dataflash_driver = { + .driver = { + .name = "mxc_dataflash", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + + .probe = dataflash_probe, + .remove = __devexit_p(dataflash_remove), + + /* FIXME: investigate suspend and resume... */ +}; + +static int __init dataflash_init(void) +{ + return spi_register_driver(&dataflash_driver); +} + +module_init(dataflash_init); + +static void __exit dataflash_exit(void) +{ + spi_unregister_driver(&dataflash_driver); +} + +module_exit(dataflash_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MTD DataFlash driver"); diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 7a58bd5522fd..c90fcb79af03 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -546,4 +546,14 @@ config MTD_VMU To build this as a module select M here, the module will be called vmu-flash. +config MTD_MXC + bool "Map driver for Freescale MXC boards" + depends on MTD && ARCH_MXC + default y + select MTD_CFI + select MTD_PARTITIONS + help + This enables access to the flash chips on Freescale MXC based + platforms. If you have such a board, say 'Y'. + endmenu diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index 5beb0662d724..01bf210825c9 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -60,3 +60,4 @@ obj-$(CONFIG_MTD_INTEL_VR_NOR) += intel_vr_nor.o obj-$(CONFIG_MTD_BFIN_ASYNC) += bfin-async-flash.o obj-$(CONFIG_MTD_RBTX4939) += rbtx4939-flash.o obj-$(CONFIG_MTD_VMU) += vmu-flash.o +obj-$(CONFIG_MTD_MXC) += mxc_nor.o diff --git a/drivers/mtd/maps/mxc_nor.c b/drivers/mtd/maps/mxc_nor.c new file mode 100644 index 000000000000..69b55bcac847 --- /dev/null +++ b/drivers/mtd/maps/mxc_nor.c @@ -0,0 +1,184 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + * (c) 2005 MontaVista Software, Inc. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/ioport.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> +#include <linux/clocksource.h> +#include <asm/mach-types.h> +#include <asm/mach/flash.h> + +#define DVR_VER "2.0" + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "RedBoot", "cmdlinepart", NULL }; +#endif + +struct clocksource *mtd_xip_clksrc; + +struct mxcflash_info { + struct mtd_partition *parts; + struct mtd_info *mtd; + struct map_info map; +}; + +/*! + * @defgroup NOR_MTD NOR Flash MTD Driver + */ + +/*! + * @file mxc_nor.c + * + * @brief This file contains the MTD Mapping information on the MXC. + * + * @ingroup NOR_MTD + */ + +static int __devinit mxcflash_probe(struct platform_device *pdev) +{ + int err, nr_parts = 0; + struct mxcflash_info *info; + struct flash_platform_data *flash = pdev->dev.platform_data; + struct resource *res = pdev->resource; + unsigned long size = res->end - res->start + 1; + + info = kzalloc(sizeof(struct mxcflash_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (!request_mem_region(res->start, size, "flash")) { + err = -EBUSY; + goto out_free_info; + } + info->map.virt = ioremap(res->start, size); + if (!info->map.virt) { + err = -ENOMEM; + goto out_release_mem_region; + } + info->map.name = dev_name(&pdev->dev); + info->map.phys = res->start; + info->map.size = size; + info->map.bankwidth = flash->width; + + mtd_xip_clksrc = clocksource_get_next(); + + simple_map_init(&info->map); + info->mtd = do_map_probe(flash->map_name, &info->map); + if (!info->mtd) { + err = -EIO; + goto out_iounmap; + } + info->mtd->owner = THIS_MODULE; + +#ifdef CONFIG_MTD_PARTITIONS + nr_parts = + parse_mtd_partitions(info->mtd, part_probes, &info->parts, 0); + if (nr_parts > 0) { + add_mtd_partitions(info->mtd, info->parts, nr_parts); + } else if (flash->parts) { + add_mtd_partitions(info->mtd, flash->parts, flash->nr_parts); + } else +#endif + { + printk(KERN_NOTICE "MXC flash: no partition info " + "available, registering whole flash\n"); + add_mtd_device(info->mtd); + } + + platform_set_drvdata(pdev, info); + return 0; + + out_iounmap: + iounmap(info->map.virt); + out_release_mem_region: + release_mem_region(res->start, size); + out_free_info: + kfree(info); + + return err; +} + +static int __devexit mxcflash_remove(struct platform_device *pdev) +{ + + struct mxcflash_info *info = platform_get_drvdata(pdev); + struct flash_platform_data *flash = pdev->dev.platform_data; + + platform_set_drvdata(pdev, NULL); + + if (info) { + if (info->parts) { + del_mtd_partitions(info->mtd); + kfree(info->parts); + } else if (flash->parts) + del_mtd_partitions(info->mtd); + else + del_mtd_device(info->mtd); + + map_destroy(info->mtd); + release_mem_region(info->map.phys, info->map.size); + iounmap((void __iomem *)info->map.virt); + kfree(info); + } + return 0; +} + +static struct platform_driver mxcflash_driver = { + .driver = { + .name = "mxc_nor_flash", + }, + .probe = mxcflash_probe, + .remove = __devexit_p(mxcflash_remove), +}; + +/*! + * This is the module's entry function. It passes board specific + * config details into the MTD physmap driver which then does the + * real work for us. After this function runs, our job is done. + * + * @return 0 if successful; non-zero otherwise + */ +static int __init mxc_mtd_init(void) +{ + pr_info("MXC MTD nor Driver %s\n", DVR_VER); + if (platform_driver_register(&mxcflash_driver) != 0) { + printk(KERN_ERR "Driver register failed for mxcflash_driver\n"); + return -ENODEV; + } + return 0; +} + +/*! + * This function is the module's exit function. It's empty because the + * MTD physmap driver is doing the real work and our job was done after + * mxc_mtd_init() runs. + */ +static void __exit mxc_mtd_exit(void) +{ + platform_driver_unregister(&mxcflash_driver); +} + +module_init(mxc_mtd_init); +module_exit(mxc_mtd_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MTD map and partitions for Freescale MXC boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index ce96c091f01b..9653aa5e8628 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -378,6 +378,102 @@ config MTD_NAND_NANDSIM The simulator may simulate various NAND flash chips for the MTD nand layer. +config MTD_NAND_IMX_NFC + tristate "i.MX NAND Flash Controller driver" + depends on MTD_NAND + help + Enables the i.MX NAND Flash controller driver. + +config MTD_NAND_MXC + tristate "MXC NAND support" + depends on MTD_NAND && ARCH_MXC_HAS_NFC_V1 + help + This enables the driver for the NAND flash controller on the + MXC processors. + +config MTD_NAND_MXC_V2 + tristate "MXC NAND Version 2 support" + depends on MTD_NAND && ARCH_MXC_HAS_NFC_V2 + help + This enables the driver for the version 2 of NAND flash controller + on the MXC processors. + +config MTD_NAND_MXC_V3 + tristate "MXC NAND Version 3 support" + depends on MTD_NAND && ARCH_MXC_HAS_NFC_V3 + help + This enables the driver for the version 3 of NAND flash controller + on the MXC processors. + +config MTD_NAND_MXC_SWECC + bool "Software ECC support " + depends on MTD_NAND_MXC || MTD_NAND_MXC_V2 || MTD_NAND_MXC_V3 + help + This enables the support for Software ECC handling. By + default MXC NAND controller Hardware ECC is supported. + + +config MTD_NAND_MXC_FORCE_CE + bool "NAND chip select operation support" + depends on MTD_NAND_MXC || MTD_NAND_MXC_V2|| MTD_NAND_MXC_V3 + help + This enables the NAND chip select by using CE control line. By + default CE operation is disabled. + +config MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + bool "ECC correction in S/W" + depends on MTD_NAND_MXC + help + This enables the Option2 NFC ECC correction in software. By + default Option 1 is selected. Enable if you need option2 ECC correction. + +config MXC_NAND_LOW_LEVEL_ERASE + bool "Low level NAND erase" + depends on MTD_NAND_MXC || MTD_NAND_MXC_V2 || MTD_NAND_MXC_V3 + help + This enables the erase of whole NAND flash. By + default low level erase operation is disabled. + +config MTD_NAND_GPMI_LBA + tristate "GPMI LBA NAND driver" + depends on MTD_NAND && ARCH_STMP3XXX + help + Enables support of LBA devices on GPMI on 37xx/378x SigmaTel + boards + +config MTD_NAND_GPMI + tristate "GPMI NAND driver" + depends on MTD_NAND && ARCH_STMP3XXX && !MTD_NAND_GPMI_LBA + help + Enables support of NAND devices on GPMI on 37xx/378x SigmaTel + boards + +config MTD_NAND_GPMI_SYSFS_ENTRIES + bool "Create /sys entries for GPMI device" + depends on MTD_NAND_GPMI + help + Check this to enable /sys entries for GPMI devices + +config MTD_NAND_GPMI_BCH + bool "Enable BCH HWECC" + depends on MTD_NAND_GPMI + depends on ARCH_STMP378X + default y + help + Check this to enable /sys entries for GPMI devices + +config MTD_NAND_GPMI_TA1 + bool "Support for TA1 NCB format (Hamming code 22,16)" + depends on MTD_NAND_GPMI + depends on ARCH_STMP378X + default y + +config MTD_NAND_GPMI_TA3 + bool "Support for TA3 NCB format (Hamming code 13,8)" + depends on MTD_NAND_GPMI + depends on ARCH_STMP378X + default y + config MTD_NAND_PLATFORM tristate "Support for generic platform NAND driver" depends on MTD_NAND diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index f3a786b3cff3..f18e71f4a1eb 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -37,7 +37,12 @@ obj-$(CONFIG_MTD_NAND_ORION) += orion_nand.o obj-$(CONFIG_MTD_NAND_FSL_ELBC) += fsl_elbc_nand.o obj-$(CONFIG_MTD_NAND_FSL_UPM) += fsl_upm.o obj-$(CONFIG_MTD_NAND_SH_FLCTL) += sh_flctl.o +obj-$(CONFIG_MTD_NAND_IMX_NFC) += imx_nfc.o obj-$(CONFIG_MTD_NAND_MXC) += mxc_nand.o +obj-$(CONFIG_MTD_NAND_MXC_V2) += mxc_nd2.o +obj-$(CONFIG_MTD_NAND_MXC_V3) += mxc_nd2.o +obj-$(CONFIG_MTD_NAND_GPMI) += gpmi/ +obj-$(CONFIG_MTD_NAND_GPMI_LBA) += lba/ obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o diff --git a/drivers/mtd/nand/gpmi/Makefile b/drivers/mtd/nand/gpmi/Makefile new file mode 100644 index 000000000000..4a4b50d294fa --- /dev/null +++ b/drivers/mtd/nand/gpmi/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_MTD_NAND_GPMI) += gpmi.o +gpmi-objs += gpmi-base.o gpmi-bbt.o +gpmi-objs += gpmi-hamming-22-16.o +gpmi-objs += gpmi-hamming-13-8.o +gpmi-objs += gpmi-bch.o +gpmi-objs += gpmi-ecc8.o diff --git a/drivers/mtd/nand/gpmi/gpmi-base.c b/drivers/mtd/nand/gpmi/gpmi-base.c new file mode 100644 index 000000000000..8bfb1c677a4e --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-base.c @@ -0,0 +1,1976 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/concat.h> +#include <linux/dma-mapping.h> +#include <linux/ctype.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/regulator/consumer.h> +#include <asm/div64.h> + +#include <mach/stmp3xxx.h> +#include <mach/platform.h> +#include <mach/regs-ecc8.h> +#include <mach/regs-gpmi.h> +#include <mach/dma.h> +#include "gpmi.h" + +static int debug; +static int copies; +static int map_buffers = true; +static int ff_writes; +static int raw_mode; +static int add_mtd_entire; +static int add_mtd_chip; +static int ignorebad; +static int max_chips = 4; +static long clk = -1; +static int bch /* = 0 */ ; + +static int gpmi_nand_init_hw(struct platform_device *pdev, int request_pins); +static void gpmi_nand_release_hw(struct platform_device *pdev); +static int gpmi_dma_exchange(struct gpmi_nand_data *g, + struct stmp3xxx_dma_descriptor *dma); +static void gpmi_read_buf(struct mtd_info *mtd, uint8_t * buf, int len); + +struct gpmi_nand_timing gpmi_safe_timing = { + .address_setup = 25, + .data_setup = 80, + .data_hold = 60, + .dsample_time = 6, +}; + +/* + * define OOB placement schemes for 4k and 2k page devices + */ +static struct nand_ecclayout gpmi_oob_128 = { + .oobfree = { + { + .offset = 2, + .length = 56, + }, { + .length = 0, + }, + }, +}; + +static struct nand_ecclayout gpmi_oob_64 = { + .oobfree = { + { + .offset = 2, + .length = 16, + }, { + .length = 0, + }, + }, +}; + +static inline u32 gpmi_cycles_ceil(u32 ntime, u32 period) +{ + int k; + + k = (ntime + period - 1) / period; + if (k == 0) + k++; + return k; +} + +/** + * gpmi_timer_expiry - timer expiry handling + */ +static void gpmi_timer_expiry(unsigned long d) +{ +#ifdef CONFIG_PM + struct gpmi_nand_data *g = (struct gpmi_nand_data *)d; + + pr_debug("%s: timer expired\n", __func__); + del_timer_sync(&g->timer); + + if (g->use_count || + __raw_readl(REGS_GPMI_BASE + HW_GPMI_CTRL0) & BM_GPMI_CTRL0_RUN) { + g->timer.expires = jiffies + 4 * HZ; + add_timer(&g->timer); + } else { + stmp3xxx_setl(BM_GPMI_CTRL0_CLKGATE, + REGS_GPMI_BASE + HW_GPMI_CTRL0); + clk_disable(g->clk); + g->self_suspended = 1; + } +#endif +} + +/** + * gpmi_self_wakeup - wakeup from self-pm light suspend + */ +static void gpmi_self_wakeup(struct gpmi_nand_data *g) +{ +#ifdef CONFIG_PM + int i = 1000; + clk_enable(g->clk); + stmp3xxx_clearl(BM_GPMI_CTRL0_CLKGATE, REGS_GPMI_BASE + HW_GPMI_CTRL0); + while (i-- + && __raw_readl(REGS_GPMI_BASE + + HW_GPMI_CTRL0) & BM_GPMI_CTRL0_CLKGATE) ; + + pr_debug("%s: i stopped at %d, data %p\n", __func__, i, g); + g->self_suspended = 0; + g->timer.expires = jiffies + 4 * HZ; + add_timer(&g->timer); +#endif +} + +/** + * gpmi_set_timings - set GPMI timings + * @pdev: pointer to GPMI platform device + * @tm: pointer to structure &gpmi_nand_timing with new timings + * + * During initialization, GPMI uses safe sub-optimal timings, which + * can be changed after reading boot control blocks + */ +void gpmi_set_timings(struct platform_device *pdev, struct gpmi_nand_timing *tm) +{ + struct gpmi_nand_data *g = platform_get_drvdata(pdev); + u32 period_ns = 1000000 / clk_get_rate(g->clk) + 1; + u32 address_cycles, data_setup_cycles; + u32 data_hold_cycles, data_sample_cycles; + u32 busy_timeout; + u32 t0; + + if (g->self_suspended) + gpmi_self_wakeup(g); + g->use_count++; + + g->timing = *tm; + + address_cycles = gpmi_cycles_ceil(tm->address_setup, period_ns); + data_setup_cycles = gpmi_cycles_ceil(tm->data_setup, period_ns); + data_hold_cycles = gpmi_cycles_ceil(tm->data_hold, period_ns); + data_sample_cycles = gpmi_cycles_ceil(tm->dsample_time + period_ns / 4, + period_ns / 2); + busy_timeout = gpmi_cycles_ceil(10000000 / 4096, period_ns); + + dev_dbg(&pdev->dev, + "%s: ADDR %u, DSETUP %u, DH %u, DSAMPLE %u, BTO %u\n", + __func__, + address_cycles, data_setup_cycles, data_hold_cycles, + data_sample_cycles, busy_timeout); + + t0 = BF(address_cycles, GPMI_TIMING0_ADDRESS_SETUP) | + BF(data_setup_cycles, GPMI_TIMING0_DATA_SETUP) | + BF(data_hold_cycles, GPMI_TIMING0_DATA_HOLD); + __raw_writel(t0, REGS_GPMI_BASE + HW_GPMI_TIMING0); + + __raw_writel(BF(busy_timeout, GPMI_TIMING1_DEVICE_BUSY_TIMEOUT), + REGS_GPMI_BASE + HW_GPMI_TIMING1); + +#ifdef CONFIG_ARCH_STMP378X + stmp3xxx_clearl(BM_GPMI_CTRL1_RDN_DELAY, + REGS_GPMI_BASE + HW_GPMI_CTRL1); + stmp3xxx_setl(BF(data_sample_cycles, GPMI_CTRL1_RDN_DELAY), + REGS_GPMI_BASE + HW_GPMI_CTRL1); +#else + stmp3xxx_clearl(BM_GPMI_CTRL1_DSAMPLE_TIME, + REGS_GPMI_BASE + HW_GPMI_CTRL1); + stmp3xxx_setl(BF(data_sample_cycles, GPMI_CTRL1_DSAMPLE_TIME), + REGS_GPMI_BASE + HW_GPMI_CTRL1); +#endif + + g->use_count--; +} + +static inline u32 bch_mode(void) +{ + u32 c1 = 0; + +#ifdef CONFIG_MTD_NAND_GPMI_BCH + if (bch) + c1 |= BM_GPMI_CTRL1_BCH_MODE; +#endif + return c1; +} + +/** + * gpmi_nand_init_hw - initialize the hardware + * @pdev: pointer to platform device + * + * Initialize GPMI hardware and set default (safe) timings for NAND access. + * Returns error code or 0 on success + */ +static int gpmi_nand_init_hw(struct platform_device *pdev, int request_pins) +{ + struct gpmi_nand_data *g = platform_get_drvdata(pdev); + struct gpmi_platform_data *gpd = + (struct gpmi_platform_data *)pdev->dev.platform_data; + int err = 0; + + g->clk = clk_get(NULL, "gpmi"); + if (IS_ERR(g->clk)) { + err = PTR_ERR(g->clk); + dev_err(&pdev->dev, "cannot set failsafe clockrate\n"); + goto out; + } + clk_enable(g->clk); + if (clk <= 0) + clk = 24000; /* safe setting, some chips do not work on + speeds >= 24kHz */ + clk_set_rate(g->clk, clk); + + clk = clk_get_rate(g->clk); + + if (request_pins) + gpd->pinmux(1); + + stmp3xxx_reset_block(HW_GPMI_CTRL0 + REGS_GPMI_BASE, 1); + + /* this CLEARS reset, despite of its name */ + stmp3xxx_setl(BM_GPMI_CTRL1_DEV_RESET, REGS_GPMI_BASE + HW_GPMI_CTRL1); + + /* IRQ polarity */ + stmp3xxx_setl(BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY, + REGS_GPMI_BASE + HW_GPMI_CTRL1); + + /* ...and ECC module */ + stmp3xxx_setl(bch_mode(), REGS_GPMI_BASE + HW_GPMI_CTRL1); + + /* choose NAND mode (1 means ATA, 0 - NAND */ + stmp3xxx_clearl(BM_GPMI_CTRL1_GPMI_MODE, + REGS_GPMI_BASE + HW_GPMI_CTRL1); + +out: + return err; +} + +/** + * gpmi_nand_release_hw - free the hardware + * @pdev: pointer to platform device + * + * In opposite to gpmi_nand_init_hw, release all acquired resources + */ +static void gpmi_nand_release_hw(struct platform_device *pdev) +{ + struct gpmi_nand_data *g = platform_get_drvdata(pdev); + struct gpmi_platform_data *gpd = + (struct gpmi_platform_data *)pdev->dev.platform_data; + + stmp3xxx_setl(BM_GPMI_CTRL0_SFTRST, REGS_GPMI_BASE + HW_GPMI_CTRL0); + + clk_disable(g->clk); + clk_put(g->clk); + gpd->pinmux(0); +} + +static int gpmi_dma_is_error(struct gpmi_nand_data *g) +{ + /* u32 n = __raw_readl(g->dma_ch); */ + + /* CURrent DMA command */ + u32 c = __raw_readl(REGS_APBH_BASE + + HW_APBH_CHn_NXTCMDAR(g->cchip->dma_ch)); + + if (c == g->cchip->error.handle) { + pr_debug("%s: dma chain has reached error terminator\n", + __func__); + return -EIO; + } + return 0; +} + +/** + * gpmi_dma_exchange - run DMA to exchange with NAND chip + * + * @g: structure associated with NAND chip + * + * Run DMA and wait for completion + */ +static int gpmi_dma_exchange(struct gpmi_nand_data *g, + struct stmp3xxx_dma_descriptor *d) +{ + struct platform_device *pdev = g->dev; + unsigned long timeout; + int err; + + if (g->self_suspended) + gpmi_self_wakeup(g); + g->use_count++; + + if (!g->regulator) { + g->regulator = regulator_get(&pdev->dev, "mmc_ssp-2"); + if (g->regulator && !IS_ERR(g->regulator)) + regulator_set_mode(g->regulator, REGULATOR_MODE_NORMAL); + else + g->regulator = NULL; + } + + if (g->regulator) + regulator_set_current_limit(g->regulator, g->reg_uA, g->reg_uA); + + init_completion(&g->done); + stmp3xxx_dma_enable_interrupt(g->cchip->dma_ch); + stmp3xxx_dma_go(g->cchip->dma_ch, d ? d : g->cchip->d, 1); + + timeout = wait_for_completion_timeout(&g->done, msecs_to_jiffies(1000)); + err = (timeout <= 0) ? -ETIMEDOUT : gpmi_dma_is_error(g); + + if (err) + printk(KERN_ERR "%s: error %d, CS = %d, channel %d\n", + __func__, err, g->cchip->cs, g->cchip->dma_ch); + + stmp3xxx_dma_reset_channel(g->cchip->dma_ch); + stmp3xxx_dma_clear_interrupt(g->cchip->dma_ch); + + if (g->regulator) + regulator_set_current_limit(g->regulator, 0, 0); + + mod_timer(&g->timer, jiffies + 4 * HZ); + g->use_count--; + + return err; +} + +/** + * gpmi_ecc_read_page - replacement for nand_read_page + * + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + */ +static int gpmi_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t * buf) +{ + struct gpmi_nand_data *g = chip->priv; + struct mtd_ecc_stats stats; + dma_addr_t bufphys, oobphys; + int err; + + bufphys = oobphys = ~0; + + if (map_buffers && virt_addr_valid(buf)) + bufphys = dma_map_single(&g->dev->dev, buf, + mtd->writesize, DMA_FROM_DEVICE); + if (dma_mapping_error(&g->dev->dev, bufphys)) + bufphys = g->data_buffer_handle; + + if (map_buffers) + oobphys = dma_map_single(&g->dev->dev, chip->oob_poi, + mtd->oobsize, DMA_FROM_DEVICE); + if (dma_mapping_error(&g->dev->dev, oobphys)) + oobphys = g->oob_buffer_handle; + + /* ECC read */ + (void)g->hc->read(g->hc, g->selected_chip, g->cchip->d, + g->cchip->error.handle, bufphys, oobphys); + + err = gpmi_dma_exchange(g, NULL); + + g->hc->stat(g->hc, g->selected_chip, &stats); + + if (stats.failed || stats.corrected) { + + pr_debug("%s: ECC failed=%d, corrected=%d\n", + __func__, stats.failed, stats.corrected); + + g->mtd.ecc_stats.failed += stats.failed; + g->mtd.ecc_stats.corrected += stats.corrected; + } + + if (!dma_mapping_error(&g->dev->dev, oobphys)) { + if (oobphys != g->oob_buffer_handle) + dma_unmap_single(&g->dev->dev, oobphys, + mtd->oobsize, DMA_FROM_DEVICE); + else { + memcpy(chip->oob_poi, g->oob_buffer, mtd->oobsize); + copies++; + } + } + + if (!dma_mapping_error(&g->dev->dev, bufphys)) { + if (bufphys != g->data_buffer_handle) + dma_unmap_single(&g->dev->dev, bufphys, + mtd->writesize, DMA_FROM_DEVICE); + else { + memcpy(buf, g->data_buffer, mtd->writesize); + copies++; + } + } + + /* always fill the (possible ECC bytes with FF) */ + memset(chip->oob_poi + g->oob_free, 0xff, mtd->oobsize - g->oob_free); + + return err; +} + +static inline int is_ff(const u8 * buffer, size_t size) +{ + while (size--) { + if (*buffer++ != 0xff) + return 0; + } + return 1; +} + +/** + * gpmi_ecc_write_page - replacement for nand_write_page + * + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: data buffer + */ +static void gpmi_ecc_write_page(struct mtd_info *mtd, + struct nand_chip *chip, const uint8_t * buf) +{ + struct gpmi_nand_data *g = chip->priv; + dma_addr_t bufphys, oobphys; + int err; + + /* if we can't map it, copy it */ + bufphys = oobphys = ~0; + + if (map_buffers && virt_addr_valid(buf)) + bufphys = dma_map_single(&g->dev->dev, + (void *)buf, mtd->writesize, + DMA_TO_DEVICE); + if (dma_mapping_error(&g->dev->dev, bufphys)) { + bufphys = g->data_buffer_handle; + memcpy(g->data_buffer, buf, mtd->writesize); + copies++; + } + + /* if OOB is all FF, leave it as such */ + if (!is_ff(chip->oob_poi, mtd->oobsize)) { + if (map_buffers) + oobphys = dma_map_single(&g->dev->dev, chip->oob_poi, + mtd->oobsize, DMA_TO_DEVICE); + if (dma_mapping_error(&g->dev->dev, oobphys)) { + oobphys = g->oob_buffer_handle; + memcpy(g->oob_buffer, chip->oob_poi, mtd->oobsize); + copies++; + } + } else + ff_writes++; + + /* call ECC */ + g->hc->write(g->hc, g->selected_chip, g->cchip->d, + g->cchip->error.handle, bufphys, oobphys); + + err = gpmi_dma_exchange(g, NULL); + if (err < 0) + printk(KERN_ERR "%s: dma error\n", __func__); + + if (!dma_mapping_error(&g->dev->dev, oobphys)) { + if (oobphys != g->oob_buffer_handle) + dma_unmap_single(&g->dev->dev, oobphys, mtd->oobsize, + DMA_TO_DEVICE); + } + + if (bufphys != g->data_buffer_handle) + dma_unmap_single(&g->dev->dev, bufphys, mtd->writesize, + DMA_TO_DEVICE); +} + +/** + * gpmi_write_buf - replacement for nand_write_buf + * + * @mtd: MTD device + * @buf: data buffer + * @len: length of the data buffer + */ +static void gpmi_write_buf(struct mtd_info *mtd, const uint8_t * buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + struct stmp3xxx_dma_descriptor *chain = g->cchip->d; + dma_addr_t phys; + int err; + + BUG_ON(len > mtd->writesize); + + phys = ~0; + + if (map_buffers && virt_addr_valid(buf)) + phys = dma_map_single(&g->dev->dev, + (void *)buf, len, DMA_TO_DEVICE); + if (dma_mapping_error(&g->dev->dev, phys)) { + phys = g->write_buffer_handle; + memcpy(g->write_buffer, buf, len); + copies++; + } + + /* write plain data */ + chain->command->cmd = + BF(len, APBH_CHn_CMD_XFER_COUNT) | + BF(4, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(BV_APBH_CHn_CMD_COMMAND__DMA_READ, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WRITE, GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BM_GPMI_CTRL0_LOCK_CS | + BF(g->selected_chip, GPMI_CTRL0_CS) | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_DATA, GPMI_CTRL0_ADDRESS) | + BF(len, GPMI_CTRL0_XFER_COUNT); + + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->pio_words[3] = 0; + chain->command->buf_ptr = phys; + + err = gpmi_dma_exchange(g, NULL); + if (err) + printk(KERN_ERR "%s: dma error\n", __func__); + + if (phys != g->write_buffer_handle) + dma_unmap_single(&g->dev->dev, phys, len, DMA_TO_DEVICE); + + if (debug >= 2) + print_hex_dump_bytes("WBUF ", DUMP_PREFIX_OFFSET, buf, len); +} + +/** + * gpmi_read_buf - replacement for nand_read_buf + * + * @mtd: MTD device + * @buf: pointer to the buffer + * @len: size of the buffer + */ +static void gpmi_read_buf(struct mtd_info *mtd, uint8_t * buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + struct stmp3xxx_dma_descriptor *chain; + dma_addr_t phys; + int err; + + phys = ~0; + + if (map_buffers && virt_addr_valid(buf)) + phys = dma_map_single(&g->dev->dev, buf, len, DMA_FROM_DEVICE); + if (dma_mapping_error(&g->dev->dev, phys)) + phys = g->read_buffer_handle; + + chain = g->cchip->d; + + /* read data */ + chain->command->cmd = + BF(len, APBH_CHn_CMD_XFER_COUNT) | + BF(1, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__DMA_WRITE, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__READ, GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BM_GPMI_CTRL0_LOCK_CS | + BF(g->selected_chip, GPMI_CTRL0_CS) | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_DATA, GPMI_CTRL0_ADDRESS) | + BF(len, GPMI_CTRL0_XFER_COUNT); + chain->command->buf_ptr = phys; + chain++; + + chain->command->cmd = + BF(4, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY, + GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_DATA, GPMI_CTRL0_ADDRESS) | + BM_GPMI_CTRL0_LOCK_CS | BF(g->selected_chip, GPMI_CTRL0_CS); + chain->command->pio_words[1] = + chain->command->pio_words[2] = chain->command->pio_words[3] = 0; + chain->command->buf_ptr = 0; + + err = gpmi_dma_exchange(g, NULL); + if (err) + printk(KERN_ERR "%s: dma error\n", __func__); + + if (phys != g->read_buffer_handle) + dma_unmap_single(&g->dev->dev, phys, len, DMA_FROM_DEVICE); + else { + memcpy(buf, g->read_buffer, len); + copies++; + } + + if (debug >= 2) + print_hex_dump_bytes("RBUF ", DUMP_PREFIX_OFFSET, buf, len); +} + +/** + * gpmi_read_byte - replacement for nand_read_byte + * @mtd: MTD device + * + * Uses gpmi_read_buf to read 1 byte from device + */ +static u8 gpmi_read_byte(struct mtd_info *mtd) +{ + u8 b; + + gpmi_read_buf(mtd, (uint8_t *) & b, 1); + return b; +} + +/** + * gpmi_read_word - replacement for nand_read_word + * @mtd: MTD device + * + * Uses gpmi_read_buf to read 2 bytes from device + */ +static u16 gpmi_read_word(struct mtd_info *mtd) +{ + u16 w; + + gpmi_read_buf(mtd, (uint8_t *) & w, sizeof(u16)); + return w; +} + +/** + * gpmi_erase - erase a block and update BBT table + * + * @mtd: MTD device + * @instr: erase instruction + */ +int gpmi_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + int rc; + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + struct gpmi_nand_data *data = platform_get_drvdata(g->dev); + + if (g->self_suspended) + gpmi_self_wakeup(data); + g->use_count++; + + rc = nand_erase_nand(mtd, instr, 0); + + if (rc == -EIO) /* block cannot be erased */ + gpmi_block_mark_as(chip, + (instr->addr >> chip->bbt_erase_shift), + 0x01); + + mod_timer(&g->timer, jiffies + 4 * HZ); + g->use_count--; + return rc; +} + +/** + * gpmi_dev_ready - poll the RDY pin + * + * @mtd: MTD device + */ +static int gpmi_dev_ready(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + struct stmp3xxx_dma_descriptor *chain = g->cchip->d; + int ret; + + /* wait for ready */ + chain->command->cmd = + BF(4, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY, + GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_DATA, + GPMI_CTRL0_ADDRESS) | BF(g->selected_chip, GPMI_CTRL0_CS); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->pio_words[3] = 0; + chain->command->buf_ptr = 0; + chain++; + + ret = gpmi_dma_exchange(g, NULL); + if (ret != 0) + printk(KERN_ERR "gpmi: gpmi_dma_exchange() timeout!\n"); + return ret == 0; +} + +/** + * gpmi_hwcontrol - set command/address byte to the device + * + * @mtd: MTD device + * @cmd: command byte + * @ctrl: control flags + */ +static void gpmi_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + struct stmp3xxx_dma_descriptor *chain = g->cchip->d; + int ret; + + if ((ctrl & (NAND_ALE | NAND_CLE))) { + if (cmd != NAND_CMD_NONE) + g->cmd_buffer[g->cmd_buffer_sz++] = cmd; + return; + } + + if (g->cmd_buffer_sz == 0) + return; + + /* output command */ + chain->command->cmd = + BF(g->cmd_buffer_sz, APBH_CHn_CMD_XFER_COUNT) | + BF(3, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__DMA_READ, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WRITE, GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BM_GPMI_CTRL0_LOCK_CS | + BF(g->selected_chip, GPMI_CTRL0_CS) | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_CLE, GPMI_CTRL0_ADDRESS) | + BF(g->cmd_buffer_sz, GPMI_CTRL0_XFER_COUNT); + if (g->cmd_buffer_sz > 0) + chain->command->pio_words[0] |= BM_GPMI_CTRL0_ADDRESS_INCREMENT; + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->buf_ptr = g->cmd_buffer_handle; + chain++; + + /* emit IRQ */ + chain->command->cmd = + BF(0, APBH_CHn_CMD_CMDWORDS) | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND) | + BM_APBH_CHn_CMD_WAIT4ENDCMD; + chain++; + + /* last in chain get the irq bit set */ + chain[-1].command->cmd |= BM_APBH_CHn_CMD_IRQONCMPLT; + + if (debug >= 3) + print_hex_dump(KERN_INFO, "CMD ", DUMP_PREFIX_OFFSET, 16, 1, + g->cmd_buffer, g->cmd_buffer_sz, 1); + + ret = gpmi_dma_exchange(g, NULL); + if (ret != 0) { + printk(KERN_ERR "%s: chip %d, dma error %d on the command:\n", + __func__, g->selected_chip, ret); + print_hex_dump(KERN_INFO, "CMD ", DUMP_PREFIX_OFFSET, 16, 1, + g->cmd_buffer, g->cmd_buffer_sz, 1); + } + + gpmi_dev_ready(mtd); + + g->cmd_buffer_sz = 0; +} + +/** + * gpmi_alloc_buffers - allocate DMA buffers for one chip + * + * @pdev: GPMI platform device + * @g: pointer to structure associated with NAND chip + * + * Allocate buffer using dma_alloc_coherent + */ +static int gpmi_alloc_buffers(struct platform_device *pdev, + struct gpmi_nand_data *g) +{ + g->cmd_buffer = dma_alloc_coherent(&pdev->dev, + g->cmd_buffer_size, + &g->cmd_buffer_handle, GFP_DMA); + if (!g->cmd_buffer) + goto out1; + + g->write_buffer = dma_alloc_coherent(&pdev->dev, + g->write_buffer_size * 2, + &g->write_buffer_handle, GFP_DMA); + if (!g->write_buffer) + goto out2; + + g->read_buffer = g->write_buffer + g->write_buffer_size; + g->read_buffer_handle = g->write_buffer_handle + g->write_buffer_size; + + g->data_buffer = dma_alloc_coherent(&pdev->dev, + g->data_buffer_size, + &g->data_buffer_handle, GFP_DMA); + if (!g->data_buffer) + goto out3; + + g->oob_buffer = dma_alloc_coherent(&pdev->dev, + g->oob_buffer_size, + &g->oob_buffer_handle, GFP_DMA); + if (!g->oob_buffer) + goto out4; + + g->verify_buffer = kzalloc(2 * (g->data_buffer_size + + g->oob_buffer_size), GFP_KERNEL); + if (!g->verify_buffer) + goto out5; + + return 0; + +out5: + dma_free_coherent(&pdev->dev, g->oob_buffer_size, + g->oob_buffer, g->oob_buffer_handle); +out4: + dma_free_coherent(&pdev->dev, g->data_buffer_size, + g->data_buffer, g->data_buffer_handle); +out3: + dma_free_coherent(&pdev->dev, g->write_buffer_size * 2, + g->write_buffer, g->write_buffer_handle); +out2: + dma_free_coherent(&pdev->dev, g->cmd_buffer_size, + g->cmd_buffer, g->cmd_buffer_handle); +out1: + return -ENOMEM; +} + +/** + * gpmi_free_buffers - free buffers allocated by gpmi_alloc_buffers + * + * @pdev: platform device + * @g: pointer to structure associated with NAND chip + * + * Deallocate buffers on exit + */ +static void gpmi_free_buffers(struct platform_device *pdev, + struct gpmi_nand_data *g) +{ + kfree(g->verify_buffer); + dma_free_coherent(&pdev->dev, g->oob_buffer_size, + g->oob_buffer, g->oob_buffer_handle); + dma_free_coherent(&pdev->dev, g->write_buffer_size * 2, + g->write_buffer, g->write_buffer_handle); + dma_free_coherent(&pdev->dev, g->cmd_buffer_size, + g->cmd_buffer, g->cmd_buffer_handle); + dma_free_coherent(&pdev->dev, g->data_buffer_size, + g->data_buffer, g->data_buffer_handle); +} + +/* only used in SW-ECC or NO-ECC cases */ +static int gpmi_verify_buf(struct mtd_info *mtd, const uint8_t * buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + + chip->read_buf(mtd, g->verify_buffer, len); + + if (memcmp(buf, g->verify_buffer, len)) + return -EFAULT; + + return 0; +} + +/** + * gpmi_ecc_read_oob - replacement for nand_read_oob + * + * @mtd: MTD device + * @chip: mtd->priv + * @page: page address + * @sndcmd: flag indicates that command should be sent + */ +int gpmi_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page, int sndcmd) +{ + struct gpmi_nand_data *g = chip->priv; + loff_t oob_offset; + struct mtd_ecc_stats stats; + dma_addr_t oobphys; + int ecc; + int ret; + + ecc = g->raw_oob_mode == 0 && raw_mode == 0; + + if (sndcmd) { + oob_offset = mtd->writesize; + if (likely(ecc)) + oob_offset += chip->ecc.bytes * chip->ecc.steps; + chip->cmdfunc(mtd, NAND_CMD_READ0, oob_offset, page); + sndcmd = 0; + } + + if (unlikely(!ecc)) { + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + return 1; + } + + oobphys = ~0; + + if (map_buffers) + oobphys = dma_map_single(&g->dev->dev, chip->oob_poi, + mtd->oobsize, DMA_FROM_DEVICE); + if (dma_mapping_error(&g->dev->dev, oobphys)) + oobphys = g->oob_buffer_handle; + + /* ECC read */ + (void)g->hc->read(g->hc, g->selected_chip, g->cchip->d, + g->cchip->error.handle, ~0, oobphys); + + ret = gpmi_dma_exchange(g, NULL); + + g->hc->stat(g->hc, g->selected_chip, &stats); + + if (stats.failed || stats.corrected) { + + printk(KERN_DEBUG "%s: ECC failed=%d, corrected=%d\n", + __func__, stats.failed, stats.corrected); + + g->mtd.ecc_stats.failed += stats.failed; + g->mtd.ecc_stats.corrected += stats.corrected; + } + + if (oobphys != g->oob_buffer_handle) + dma_unmap_single(&g->dev->dev, oobphys, mtd->oobsize, + DMA_FROM_DEVICE); + else { + memcpy(chip->oob_poi, g->oob_buffer, mtd->oobsize); + copies++; + } + + /* fill rest with ff */ + memset(chip->oob_poi + g->oob_free, 0xff, mtd->oobsize - g->oob_free); + + return ret ? ret : 1; +} + +/** + * gpmi_ecc_write_oob - replacement for nand_write_oob + * + * @mtd: MTD device + * @chip: mtd->priv + * @page: page address + */ +static int gpmi_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + int status = 0; + struct gpmi_nand_data *g = chip->priv; + loff_t oob_offset; + dma_addr_t oobphys; + int ecc; + int err = 0; + + /* if OOB is all FF, leave it as such */ + if (is_ff(chip->oob_poi, mtd->oobsize)) { + ff_writes++; + + pr_debug("%s: Skipping an empty page 0x%x (0x%x)\n", + __func__, page, page << chip->page_shift); + return 0; + } + + ecc = g->raw_oob_mode == 0 && raw_mode == 0; + + /* Send command to start input data */ + oob_offset = mtd->writesize; + if (likely(ecc)) { + oob_offset += chip->ecc.bytes * chip->ecc.steps; + memset(chip->oob_poi + g->oob_free, 0xff, + mtd->oobsize - g->oob_free); + } + chip->cmdfunc(mtd, NAND_CMD_SEQIN, oob_offset, page); + + /* call ECC */ + if (likely(ecc)) { + + oobphys = ~0; + + if (map_buffers) + oobphys = dma_map_single(&g->dev->dev, chip->oob_poi, + mtd->oobsize, DMA_TO_DEVICE); + if (dma_mapping_error(&g->dev->dev, oobphys)) { + oobphys = g->oob_buffer_handle; + memcpy(g->oob_buffer, chip->oob_poi, mtd->oobsize); + copies++; + } + + g->hc->write(g->hc, g->selected_chip, g->cchip->d, + g->cchip->error.handle, ~0, oobphys); + + err = gpmi_dma_exchange(g, NULL); + } else + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + + /* Send command to program the OOB data */ + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + /* ..and wait for result */ + status = chip->waitfunc(mtd, chip); + + if (likely(ecc)) { + if (oobphys != g->oob_buffer_handle) + dma_unmap_single(&g->dev->dev, oobphys, mtd->oobsize, + DMA_TO_DEVICE); + } + + if (status & NAND_STATUS_FAIL) { + pr_debug("%s: NAND_STATUS_FAIL\n", __func__); + return -EIO; + } + + return err; +} + +/** + * gpmi_irq - IRQ handler + * + * @irq: irq no + * @context: IRQ context, pointer to gpmi_nand_data + */ +static irqreturn_t gpmi_irq(int irq, void *context) +{ + struct gpmi_nand_data *g = context; + + if (stmp3xxx_dma_is_interrupt(g->cchip->dma_ch)) { + stmp3xxx_dma_clear_interrupt(g->cchip->dma_ch); + complete(&g->done); + } + stmp3xxx_clearl(BM_GPMI_CTRL1_DEV_IRQ | BM_GPMI_CTRL1_TIMEOUT_IRQ, + REGS_GPMI_BASE + HW_GPMI_CTRL1); + return IRQ_HANDLED; +} + +static void gpmi_select_chip(struct mtd_info *mtd, int chipnr) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + + if (chipnr == g->selected_chip) + return; + + g->selected_chip = chipnr; + g->cchip = NULL; + + if (chipnr == -1) + return; + + g->cchip = g->chips + chipnr; +} + +static void gpmi_command(struct mtd_info *mtd, unsigned int command, + int column, int page_addr) +{ + register struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + + g->saved_command(mtd, command, column, page_addr); +} + +static int gpmi_read_oob(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + register struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + int ret; + + g->raw_oob_mode = ops->mode == MTD_OOB_RAW; + ret = g->saved_read_oob(mtd, from, ops); + g->raw_oob_mode = 0; + return ret; +} + +static int gpmi_write_oob(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + register struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + int ret; + + g->raw_oob_mode = ops->mode == MTD_OOB_RAW; + ret = g->saved_write_oob(mtd, to, ops); + g->raw_oob_mode = 0; + return ret; +} + +/** + * perform the needed steps between nand_scan_ident and nand_scan_tail + */ +static int gpmi_scan_middle(struct gpmi_nand_data *g) +{ + int oobsize = 0; + + /* Limit to 2G size due to Kernel larger 4G space support */ + if (g->mtd.size == 0) { + g->mtd.size = 1 << 31; + g->chip.chipsize = do_div(g->mtd.size, g->chip.numchips); + } + + g->ecc_oob_bytes = 9; + switch (g->mtd.writesize) { + case 2048: /* 2K page */ + g->chip.ecc.layout = &gpmi_oob_64; + g->chip.ecc.bytes = 9; + g->oob_free = 19; + g->hwecc_type_read = GPMI_ECC4_RD; + g->hwecc_type_write = GPMI_ECC4_WR; + oobsize = 64; + break; + case 4096: + g->chip.ecc.layout = &gpmi_oob_128; + g->chip.ecc.bytes = 18; + g->oob_free = 65; + g->hwecc_type_read = GPMI_ECC8_RD; + g->hwecc_type_write = GPMI_ECC8_WR; + oobsize = 218; + break; + default: + printk(KERN_ERR "Unsupported write_size %d.", g->mtd.writesize); + break; + } + + g->mtd.ecclayout = g->chip.ecc.layout; + /* sanity check */ + if (oobsize > NAND_MAX_OOBSIZE || g->mtd.writesize > NAND_MAX_PAGESIZE) { + printk(KERN_ERR "Internal error. Either page size " + "(%d) > max (%d) " + "or oob size (%d) > max(%d). Sorry.\n", + oobsize, NAND_MAX_OOBSIZE, + g->mtd.writesize, NAND_MAX_PAGESIZE); + return -ERANGE; + } + + g->saved_command = g->chip.cmdfunc; + g->chip.cmdfunc = gpmi_command; + + if (oobsize > 0) { + g->mtd.oobsize = oobsize; + /* otherwise error; oobsize should be set + in valid cases */ + g->hc = gpmi_hwecc_chip_find("ecc8"); + g->hc->setup(g->hc, 0, g->mtd.writesize, g->mtd.oobsize); + return 0; + } + + return -ENXIO; +} + +/** + * gpmi_write_page - [REPLACEABLE] write one page + * @mtd: MTD device structure + * @chip: NAND chip descriptor + * @buf: the data to write + * @page: page number to write + * @cached: cached programming + * @raw: use _raw version of write_page + */ +static int gpmi_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t * buf, int page, int cached, int raw) +{ + struct gpmi_nand_data *g = chip->priv; + int status, empty_data, empty_oob; + int oobsz; +#if defined(CONFIG_MTD_NAND_VERIFY_WRITE) + void *vbuf, *obuf; +#if 0 + void *voob, *ooob; +#endif +#endif + + oobsz = likely(g->raw_oob_mode == 0 && raw_mode == 0) ? + g->oob_free : mtd->oobsize; + + empty_data = is_ff(buf, mtd->writesize); + empty_oob = is_ff(buf, oobsz); + + if (empty_data && empty_oob) { + ff_writes++; + + pr_debug("%s: Skipping an empty page 0x%x (0x%x)\n", + __func__, page, page << chip->page_shift); + return 0; + } + + chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); + + if (likely(raw == 0)) + chip->ecc.write_page(mtd, chip, buf); + else + chip->ecc.write_page_raw(mtd, chip, buf); + + /* + * Cached progamming disabled for now, Not sure if its worth the + * trouble. The speed gain is not very impressive. (2.3->2.6Mib/s) + */ + cached = 0; + + if (!cached || !(chip->options & NAND_CACHEPRG)) { + + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + status = chip->waitfunc(mtd, chip); + + /* + * See if operation failed and additional status checks are + * available + */ + if ((status & NAND_STATUS_FAIL) && (chip->errstat)) + status = chip->errstat(mtd, chip, FL_WRITING, status, + page); + + if (status & NAND_STATUS_FAIL) { + pr_debug("%s: NAND_STATUS_FAIL\n", __func__); + return -EIO; + } + } else { + chip->cmdfunc(mtd, NAND_CMD_CACHEDPROG, -1, -1); + status = chip->waitfunc(mtd, chip); + } + +#if defined(CONFIG_MTD_NAND_VERIFY_WRITE) + if (empty_data) + return 0; + + obuf = g->verify_buffer; +#if 1 /* make vbuf aligned by mtd->writesize */ + vbuf = obuf + mtd->writesize; +#else + ooob = obuf + mtd->writesize; + vbuf = ooob + mtd->oobsize; + voob = vbuf + mtd->writesize; +#endif + + /* keep data around */ + memcpy(obuf, buf, mtd->writesize); +#if 0 + memcpy(ooob, chip->oob_poi, oobsz); +#endif + /* Send command to read back the data */ + chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page); + + if (likely(raw == 0)) + chip->ecc.read_page(mtd, chip, vbuf); + else + chip->ecc.read_page_raw(mtd, chip, vbuf); + +#if 0 + memcpy(voob, chip->oob_poi, oobsz); +#endif + + if (!empty_data && memcmp(obuf, vbuf, mtd->writesize) != 0) + return -EIO; +#endif + + return 0; +} + +/** + * gpmi_read_page_raw - [Intern] read raw page data without ecc + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + */ +static int gpmi_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t * buf) +{ + chip->read_buf(mtd, buf, mtd->writesize); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + return 0; +} + +/** + * gpmi_write_page_raw - [Intern] raw page write function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: data buffer + */ +static void gpmi_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t * buf) +{ + chip->write_buf(mtd, buf, mtd->writesize); + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); +} + +static int gpmi_init_chip(struct platform_device *pdev, + struct gpmi_nand_data *g, int n, unsigned dma_ch) +{ + int err; + + g->chips[n].dma_ch = dma_ch; + g->chips[n].cs = n; + + err = stmp3xxx_dma_request(dma_ch, NULL, dev_name(&pdev->dev)); + if (err) { + dev_err(&pdev->dev, "can't request DMA channel 0x%x\n", dma_ch); + goto out_all; + } + + err = stmp3xxx_dma_make_chain(dma_ch, + &g->chips[n].chain, + g->chips[n].d, ARRAY_SIZE(g->chips[n].d)); + if (err) { + dev_err(&pdev->dev, "can't setup DMA chain\n"); + goto out_all; + } + + err = stmp3xxx_dma_allocate_command(dma_ch, &g->chips[n].error); + if (err) { + dev_err(&pdev->dev, "can't setup DMA chain\n"); + goto out_all; + } + + g->chips[n].error.command->cmd = + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); +out_all: + return err; +} + +static void gpmi_deinit_chip(struct platform_device *pdev, + struct gpmi_nand_data *g, int n) +{ + int dma_ch; + + if (n < 0) { + for (n = 0; n < ARRAY_SIZE(g->chips); n++) + gpmi_deinit_chip(pdev, g, n); + return; + } + + if (g->chips[n].dma_ch <= 0) + return; + + dma_ch = g->chips[n].dma_ch; + + stmp3xxx_dma_free_command(dma_ch, &g->chips[n].error); + stmp3xxx_dma_free_chain(&g->chips[n].chain); + stmp3xxx_dma_release(dma_ch); +} + +#if 0 +static int gpmi_to_concat(struct mtd_partition *part, char **list) +{ + while (list && *list) { + if (strcmp(part->name, *list) == 0) { + pr_debug("Partition '%s' will be concatenated\n", + part->name); + return true; + } + list++; + } + pr_debug("Partition '%s' is left as-is", part->name); + return false; +} +#endif + +static void gpmi_create_partitions(struct gpmi_nand_data *g, + struct gpmi_platform_data *gpd, + uint64_t chipsize) +{ +#ifdef CONFIG_MTD_PARTITIONS + int chip, p; + char chipname[20]; + + if (g->numchips == 1) + g->masters[0] = &g->mtd; + else { + for (chip = 0; chip < g->numchips; chip++) { + memset(g->chip_partitions + chip, + 0, sizeof(g->chip_partitions[chip])); + snprintf(chipname, sizeof(chipname), + "gpmi-chip-%d", chip); + g->chip_partitions[chip].name = + kstrdup(chipname, GFP_KERNEL); + g->chip_partitions[chip].size = chipsize; + g->chip_partitions[chip].offset = chipsize * chip; + g->chip_partitions[chip].mask_flags = 0; + } + add_mtd_partitions(&g->mtd, g->chip_partitions, g->numchips); + } + g->n_concat = 0; + memset(g->concat, 0, sizeof(g->concat)); + for (chip = 0; chip < g->numchips; chip++) { + if (add_mtd_chip) { + printk(KERN_NOTICE "Adding MTD for the chip %d\n", + chip); + add_mtd_device(g->masters[chip]); + } + if (chip >= gpd->items) + continue; + add_mtd_partitions(g->masters[chip], + gpd->parts[chip].partitions, + gpd->parts[chip].nr_partitions); + } + if (g->n_concat > 0) { +#ifdef CONFIG_MTD_CONCAT + if (g->n_concat == 1) +#endif + for (p = 0; p < g->n_concat; p++) + add_mtd_device(g->concat[p]); +#ifdef CONFIG_MTD_CONCAT + if (g->n_concat > 1) { + g->concat_mtd = mtd_concat_create(g->concat, + g->n_concat, + gpd->concat_name); + if (g->concat_mtd) + add_mtd_device(g->concat_mtd); + } +#endif + } + g->custom_partitions = true; +#endif +} + +static void gpmi_delete_partitions(struct gpmi_nand_data *g) +{ +#ifdef CONFIG_MTD_PARTITIONS + int chip, p; + + if (!g->custom_partitions) + return; +#ifdef CONFIG_MTD_CONCAT + if (g->concat_mtd) + del_mtd_device(g->concat_mtd); + if (g->n_concat == 1) +#endif + for (p = 0; p < g->n_concat; p++) + del_mtd_device(g->concat[p]); + + for (chip = 0; chip < g->numchips; chip++) { + del_mtd_partitions(g->masters[chip]); + if (add_mtd_chip) + del_mtd_device(g->masters[chip]); + kfree(g->chip_partitions[chip].name); + } +#endif +} + +/** + * gpmi_nand_probe - probe for the GPMI device + * + * Probe for GPMI device and discover NAND chips + */ +static int __init gpmi_nand_probe(struct platform_device *pdev) +{ + struct gpmi_nand_data *g; + struct gpmi_platform_data *gpd; + const char *part_type = 0; + int err = 0; + struct resource *r; + int dma; + unsigned long long chipsize; + + /* Allocate memory for the device structure (and zero it) */ + g = kzalloc(sizeof(*g), GFP_KERNEL); + if (!g) { + dev_err(&pdev->dev, "failed to allocate gpmi_nand_data\n"); + err = -ENOMEM; + goto out1; + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "failed to get resource\n"); + err = -ENXIO; + goto out2; + } + g->io_base = ioremap(r->start, r->end - r->start + 1); + if (!g->io_base) { + dev_err(&pdev->dev, "ioremap failed\n"); + err = -EIO; + goto out2; + } + + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!r) { + err = -EIO; + dev_err(&pdev->dev, "can't get IRQ resource\n"); + goto out3; + } + + gpd = (struct gpmi_platform_data *)pdev->dev.platform_data; + platform_set_drvdata(pdev, g); + err = gpmi_nand_init_hw(pdev, 1); + if (err) + goto out3; + + init_timer(&g->timer); + g->timer.data = (unsigned long)g; + g->timer.function = gpmi_timer_expiry; + g->timer.expires = jiffies + 4 * HZ; + add_timer(&g->timer); + dev_dbg(&pdev->dev, "%s: timer set to %ld\n", + __func__, jiffies + 4 * HZ); + + g->reg_uA = gpd->io_uA; + g->regulator = regulator_get(&pdev->dev, "mmc_ssp-2"); + if (g->regulator && !IS_ERR(g->regulator)) { + regulator_set_mode(g->regulator, REGULATOR_MODE_NORMAL); + } else + g->regulator = NULL; + + gpmi_set_timings(pdev, &gpmi_safe_timing); + + g->irq = r->start; + err = request_irq(g->irq, gpmi_irq, 0, dev_name(&pdev->dev), g); + if (err) { + dev_err(&pdev->dev, "can't request GPMI IRQ\n"); + goto out4; + } + + r = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!r) { + dev_err(&pdev->dev, "can't get DMA resource\n"); + goto out5; + } + + if (r->end - r->start > GPMI_MAX_CHIPS) + dev_info(&pdev->dev, "too spread resource: max %d chips\n", + GPMI_MAX_CHIPS); + + for (dma = r->start; + dma < min_t(int, r->end, r->start + GPMI_MAX_CHIPS); dma++) { + err = gpmi_init_chip(pdev, g, dma - r->start, dma); + if (err) + goto out6; + } + + g->cmd_buffer_size = GPMI_CMD_BUF_SZ; + g->write_buffer_size = GPMI_WRITE_BUF_SZ; + g->data_buffer_size = GPMI_DATA_BUF_SZ; + g->oob_buffer_size = GPMI_OOB_BUF_SZ; + + err = gpmi_alloc_buffers(pdev, g); + if (err) { + dev_err(&pdev->dev, "can't setup buffers\n"); + goto out6; + } + + g->dev = pdev; + g->chip.priv = g; + g->timing = gpmi_safe_timing; + g->selected_chip = -1; + g->ignorebad = ignorebad; /* copy global setting */ + + g->mtd.priv = &g->chip; + g->mtd.name = dev_name(&pdev->dev); + g->mtd.owner = THIS_MODULE; + + g->chip.cmd_ctrl = gpmi_hwcontrol; + g->chip.read_word = gpmi_read_word; + g->chip.read_byte = gpmi_read_byte; + g->chip.read_buf = gpmi_read_buf; + g->chip.write_buf = gpmi_write_buf; + g->chip.select_chip = gpmi_select_chip; + g->chip.verify_buf = gpmi_verify_buf; + g->chip.dev_ready = gpmi_dev_ready; + + g->chip.ecc.mode = NAND_ECC_HW_SYNDROME; + g->chip.ecc.write_oob = gpmi_ecc_write_oob; + g->chip.ecc.read_oob = gpmi_ecc_read_oob; + g->chip.ecc.write_page = gpmi_ecc_write_page; + g->chip.ecc.read_page = gpmi_ecc_read_page; + g->chip.ecc.read_page_raw = gpmi_read_page_raw; + g->chip.ecc.write_page_raw = gpmi_write_page_raw; + g->chip.ecc.size = 512; + + g->chip.write_page = gpmi_write_page; + + g->chip.scan_bbt = gpmi_scan_bbt; + g->chip.block_bad = gpmi_block_bad; + + g->cmd_buffer_sz = 0; + + /* first scan to find the device and get the page size */ + if (nand_scan_ident(&g->mtd, max_chips) + || gpmi_scan_middle(g) + || nand_scan_tail(&g->mtd)) { + dev_err(&pdev->dev, "No NAND found\n"); + /* errors found on some step */ + goto out7; + } + + g->chip.options |= NAND_NO_SUBPAGE_WRITE; + g->chip.subpagesize = g->mtd.writesize; + g->mtd.subpage_sft = 0; + + g->mtd.erase = gpmi_erase; + + g->saved_read_oob = g->mtd.read_oob; + g->saved_write_oob = g->mtd.write_oob; + g->mtd.read_oob = gpmi_read_oob; + g->mtd.write_oob = gpmi_write_oob; + +#ifdef CONFIG_MTD_PARTITIONS + if (gpd == NULL) + goto out_all; + + if (gpd->parts[0].part_probe_types) { + g->nr_parts = parse_mtd_partitions(&g->mtd, + gpd->parts[0]. + part_probe_types, &g->parts, + 0); + if (g->nr_parts > 0) + part_type = "command line"; + else + g->nr_parts = 0; + } + + if (g->nr_parts == 0 && gpd->parts[0].partitions) { + g->parts = gpd->parts[0].partitions; + g->nr_parts = gpd->parts[0].nr_partitions; + part_type = "static"; + } + + if (g->nr_parts == 0) { + dev_err(&pdev->dev, "Neither part_probe_types nor " + "partitions was specified in platform_data"); + goto out_all; + } + + dev_info(&pdev->dev, "Using %s partition definition\n", part_type); + + g->numchips = g->chip.numchips; + chipsize = g->mtd.size; + do_div(chipsize, (unsigned long)g->numchips); + + if (!strcmp(part_type, "command line")) + add_mtd_partitions(&g->mtd, g->parts, g->nr_parts); + else + gpmi_create_partitions(g, gpd, chipsize); + + if (add_mtd_entire) { + printk(KERN_NOTICE "Adding MTD covering the whole flash\n"); + add_mtd_device(&g->mtd); + } +#else + add_mtd_device(&g->mtd); +#endif + gpmi_uid_init("nand", &g->mtd, gpd->uid_offset, gpd->uid_size); + +#ifdef CONFIG_MTD_NAND_GPMI_SYSFS_ENTRIES + gpmi_sysfs(pdev, true); +#endif + return 0; + +out_all: + ecc8_exit(); + bch_exit(); +out7: + nand_release(&g->mtd); + gpmi_free_buffers(pdev, g); +out6: + gpmi_deinit_chip(pdev, g, -1); +out5: + free_irq(g->irq, g); +out4: + del_timer_sync(&g->timer); + gpmi_nand_release_hw(pdev); +out3: + platform_set_drvdata(pdev, NULL); + iounmap(g->io_base); +out2: + kfree(g); +out1: + return err; +} + +/** + * gpmi_nand_remove - remove a GPMI device + * + */ +static int __devexit gpmi_nand_remove(struct platform_device *pdev) +{ + struct gpmi_nand_data *g = platform_get_drvdata(pdev); + int i = 0; +#ifdef CONFIG_MTD_PARTITIONS + struct gpmi_platform_data *gpd = pdev->dev.platform_data; + struct mtd_partition *platf_parts; +#endif + + gpmi_delete_partitions(g); + del_timer_sync(&g->timer); + gpmi_uid_remove("nand"); +#ifdef CONFIG_MTD_NAND_GPMI_SYSFS_ENTRIES + gpmi_sysfs(pdev, false); +#endif + nand_release(&g->mtd); + gpmi_free_buffers(pdev, g); + gpmi_deinit_chip(pdev, g, -1); + gpmi_nand_release_hw(pdev); + free_irq(g->irq, g); + if (g->regulator) + regulator_put(g->regulator); + +#ifdef CONFIG_MTD_PARTITIONS + if (i < gpd->items && gpd->parts[i].partitions) + platf_parts = gpd->parts[i].partitions; + else + platf_parts = NULL; + if (g->parts && g->parts != platf_parts) + kfree(g->parts); +#endif + iounmap(g->io_base); + kfree(g); + + return 0; +} + +#ifdef CONFIG_PM +static int gpmi_nand_suspend(struct platform_device *pdev, pm_message_t pm) +{ + struct gpmi_nand_data *g = platform_get_drvdata(pdev); + int r = 0; + + if (g->self_suspended) + gpmi_self_wakeup(g); + del_timer_sync(&g->timer); + + r = g->mtd.suspend(&g->mtd); + if (r == 0) + gpmi_nand_release_hw(pdev); + + return r; +} + +static int gpmi_nand_resume(struct platform_device *pdev) +{ + struct gpmi_nand_data *g = platform_get_drvdata(pdev); + int r; + + r = gpmi_nand_init_hw(pdev, 1); + gpmi_set_timings(pdev, &g->timing); + g->mtd.resume(&g->mtd); + g->timer.expires = jiffies + 4 * HZ; + add_timer(&g->timer); + return r; +} +#else +#define gpmi_nand_suspend NULL +#define gpmi_nand_resume NULL +#endif + +#ifdef CONFIG_MTD_NAND_GPMI_SYSFS_ENTRIES +static ssize_t show_timings(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct gpmi_nand_timing *ptm; + struct gpmi_nand_data *g = dev_get_drvdata(d); + + ptm = &g->timing; + return sprintf(buf, "DATA_SETUP %d, DATA_HOLD %d, " + "ADDR_SETUP %d, DSAMPLE_TIME %d\n", + ptm->data_setup, ptm->data_hold, + ptm->address_setup, ptm->dsample_time); +} + +static ssize_t store_timings(struct device *d, struct device_attribute *attr, + const char *buf, size_t size) +{ + const char *p, *end; + struct gpmi_nand_timing t; + struct gpmi_nand_data *g = dev_get_drvdata(d); + char tmps[20]; + u8 *timings[] = { + &t.data_setup, + &t.data_hold, + &t.address_setup, + &t.dsample_time, + NULL, + }; + u8 **timing = timings; + + p = buf; + + /* parse values */ + while (*timing != NULL) { + unsigned long t_long; + + end = strchr(p, ','); + memset(tmps, 0, sizeof(tmps)); + if (end) + strncpy(tmps, p, min_t(int, sizeof(tmps) - 1, end - p)); + else + strncpy(tmps, p, sizeof(tmps) - 1); + + if (strict_strtoul(tmps, 0, &t_long) < 0) + return -EINVAL; + + if (t_long > 255) + return -EINVAL; + + **timing = (u8) t_long; + timing++; + + if (!end && *timing) + return -EINVAL; + p = end + 1; + } + + gpmi_set_timings(g->dev, &t); + + return size; +} + +static ssize_t show_stat(struct device *d, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "copies\t\t%dff pages\t%d\n", copies, ff_writes); +} + +static ssize_t show_chips(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct gpmi_nand_data *g = dev_get_drvdata(d); + return sprintf(buf, "%d\n", g->numchips); +} + +static ssize_t show_ignorebad(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct gpmi_nand_data *g = dev_get_drvdata(d); + + return sprintf(buf, "%d\n", g->ignorebad); +} + +static ssize_t store_ignorebad(struct device *d, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct gpmi_nand_data *g = dev_get_drvdata(d); + const char *p = buf; + unsigned long v; + + if (strict_strtoul(p, 0, &v) < 0) + return size; + if (v > 0) + v = 1; + if (v != g->ignorebad) { + if (v) { + g->bbt = g->chip.bbt; + g->chip.bbt = NULL; + g->ignorebad = 1; + } else { + g->chip.bbt = g->bbt; + g->ignorebad = 0; + } + } + return size; +} + +static DEVICE_ATTR(timings, 0644, show_timings, store_timings); +static DEVICE_ATTR(stat, 0444, show_stat, NULL); +static DEVICE_ATTR(ignorebad, 0644, show_ignorebad, store_ignorebad); +static DEVICE_ATTR(numchips, 0444, show_chips, NULL); + +static struct device_attribute *gpmi_attrs[] = { + &dev_attr_timings, + &dev_attr_stat, + &dev_attr_ignorebad, + &dev_attr_numchips, + NULL, +}; + +int gpmi_sysfs(struct platform_device *pdev, int create) +{ + int err = 0; + int i; + + if (create) { + for (i = 0; gpmi_attrs[i]; i++) { + err = device_create_file(&pdev->dev, gpmi_attrs[i]); + if (err) + break; + } + if (err) + while (--i >= 0) + device_remove_file(&pdev->dev, gpmi_attrs[i]); + } else { + for (i = 0; gpmi_attrs[i]; i++) + device_remove_file(&pdev->dev, gpmi_attrs[i]); + } + return err; +} +#endif + +static struct platform_driver gpmi_nand_driver = { + .probe = gpmi_nand_probe, + .remove = __devexit_p(gpmi_nand_remove), + .driver = { + .name = "gpmi", + .owner = THIS_MODULE, + }, + .suspend = gpmi_nand_suspend, + .resume = gpmi_nand_resume, +}; + +static LIST_HEAD(gpmi_hwecc_chips); + +void gpmi_hwecc_chip_add(struct gpmi_hwecc_chip *chip) +{ + list_add(&chip->list, &gpmi_hwecc_chips); +} + +EXPORT_SYMBOL_GPL(gpmi_hwecc_chip_add); + +void gpmi_hwecc_chip_remove(struct gpmi_hwecc_chip *chip) +{ + list_del(&chip->list); +} + +EXPORT_SYMBOL_GPL(gpmi_hwecc_chip_remove); + +struct gpmi_hwecc_chip *gpmi_hwecc_chip_find(char *name) +{ + struct gpmi_hwecc_chip *c; + + list_for_each_entry(c, &gpmi_hwecc_chips, list) + if (strncmp(c->name, name, sizeof(c->name)) == 0) + return c; + return NULL; +} + +EXPORT_SYMBOL_GPL(gpmi_hwecc_chip_find); + +static int __init gpmi_nand_init(void) +{ + bch_init(); + ecc8_init(); + return platform_driver_register(&gpmi_nand_driver); +} + +static void __exit gpmi_nand_exit(void) +{ + platform_driver_unregister(&gpmi_nand_driver); +} + +module_init(gpmi_nand_init); +module_exit(gpmi_nand_exit); +MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPMI NAND driver"); +module_param(max_chips, int, 0400); +module_param(clk, long, 0400); +module_param(bch, int, 0600); +module_param(map_buffers, int, 0600); +module_param(raw_mode, int, 0600); +module_param(debug, int, 0600); +module_param(add_mtd_entire, int, 0400); +module_param(add_mtd_chip, int, 0400); +module_param(ignorebad, int, 0400); diff --git a/drivers/mtd/nand/gpmi/gpmi-bbt.c b/drivers/mtd/nand/gpmi/gpmi-bbt.c new file mode 100644 index 000000000000..54755f870440 --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-bbt.c @@ -0,0 +1,565 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/dma-mapping.h> +#include <linux/ctype.h> +#include <mach/dma.h> +#include <mach/unique-id.h> +#include "gpmi.h" + +static int boot_search_count; +static int stride = 64; +static int ncb_version = 3; +module_param(boot_search_count, int, 0400); +module_param(ncb_version, int, 0400); + +void *gpmi_read_page(struct mtd_info *mtd, loff_t start, void *data, int raw) +{ + int ret; + struct mtd_oob_ops ops; + + if (!data) + data = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL); + if (!data) + return NULL; + + if (raw) + ops.mode = MTD_OOB_RAW; + else + ops.mode = MTD_OOB_PLACE; + ops.datbuf = data; + ops.len = mtd->writesize; + ops.oobbuf = data + mtd->writesize; + ops.ooblen = mtd->oobsize; + ops.ooboffs = 0; + ret = nand_do_read_ops(mtd, start, &ops); + + if (ret) + return NULL; + return data; +} + +int gpmi_write_ncb(struct mtd_info *mtd, struct gpmi_bcb_info *b) +{ + struct gpmi_ncb *ncb = NULL, *unencoded_ncb = NULL; + struct nand_chip *chip = mtd->priv; + int err; + loff_t start = 0; + struct mtd_oob_ops ops; + struct erase_info instr; + int ncb_count; + + ncb = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL); + if (!ncb) { + err = -ENOMEM; + goto out; + } + unencoded_ncb = kzalloc(mtd->writesize, GFP_KERNEL); + if (!unencoded_ncb) { + err = -ENOMEM; + goto out; + } + ops.mode = -1; /* if the value is not set in switch below, + this will cause BUG. Take care. */ + if (b && b->pre_ncb) + memcpy(unencoded_ncb, b->pre_ncb, b->pre_ncb_size); + else { + memcpy(&unencoded_ncb->fingerprint1, SIG1, sizeof(u32)); + memcpy(&unencoded_ncb->fingerprint2, SIG_NCB, sizeof(u32)); + if (b) + unencoded_ncb->timing = b->timing; + } + + switch (ncb_version) { + case 0: + ops.mode = MTD_OOB_AUTO; + memcpy(ncb, unencoded_ncb, sizeof(*unencoded_ncb)); + break; +#ifdef CONFIG_MTD_NAND_GPMI_TA1 + case 1: + ops.mode = MTD_OOB_RAW; + gpmi_encode_hamming_ncb_22_16(unencoded_ncb, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + ncb, mtd->writesize + mtd->oobsize); + break; +#endif +#ifdef CONFIG_MTD_NAND_GPMI_TA3 + case 3: + ops.mode = MTD_OOB_RAW; + gpmi_encode_hamming_ncb_13_8(unencoded_ncb, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + ncb, mtd->writesize + mtd->oobsize); + break; +#endif + + default: + printk(KERN_ERR"Incorrect ncb_version = %d\n", ncb_version); + err = -EINVAL; + goto out; + } + + ops.datbuf = (u8 *)ncb; + ops.len = mtd->writesize; + ops.oobbuf = (u8 *)ncb + mtd->writesize; + ops.ooblen = mtd->oobsize; + ops.ooboffs = 0; + + ncb_count = 0; + do { + printk(KERN_NOTICE"GPMI: Trying to store NCB at addr %lx\n", + (unsigned long)start); + memset(&instr, 0, sizeof(instr)); + instr.mtd = mtd; + instr.addr = start; + instr.len = (1 << chip->phys_erase_shift); + err = nand_erase_nand(mtd, &instr, 0); + if (err == 0) { + printk(KERN_NOTICE"GPMI: Erased, storing\n"); + err = nand_do_write_ops(mtd, start, &ops); + printk(KERN_NOTICE"GPMI: NCB update %s (%d).\n", + err ? "failed" : "succeeded", err); + } + start += (1 << chip->phys_erase_shift); + ncb_count++; + } while (err != 0 && ncb_count < 100); + + if (b) + b->ncbblock = start >> chip->bbt_erase_shift; + +out: + kfree(ncb); + kfree(unencoded_ncb); + + return 0; +} + +static int gpmi_redundancy_check_one(u8 *pg, int dsize, int esize, int offset, + int o1, int o2) +{ + int r; + + if (o1 == o2) + return 0; + + r = memcmp(pg + o1 * dsize, pg + o2 * dsize, dsize); + if (r) { + pr_debug("DATA copies %d and %d are different: %d\n", + o1, o2, r); + return r; + } + + r = memcmp(pg + o1 * esize + offset, + pg + o2 * esize + offset, esize); + if (r) { + pr_debug("ECC copies %d and %d are different: %d\n", o1, o2, r); + return r; + } + pr_debug("Both DATA and ECC copies %d and %d are identical\n", o1, o2); + return r; +} + +static int gpmi_redundancy_check(u8 *pg, int dsize, int esize, int ecc_offset) +{ + if (gpmi_redundancy_check_one(pg, dsize, esize, ecc_offset, 0, 1) == 0) + return 0; + if (gpmi_redundancy_check_one(pg, dsize, esize, ecc_offset, 0, 2) == 0) + return 0; + if (gpmi_redundancy_check_one(pg, dsize, esize, ecc_offset, 1, 2) == 0) + return 1; + return -1; +} + +static inline int gpmi_ncb1_redundancy_check(u8 *pg) +{ + return gpmi_redundancy_check(pg, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES, + NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY); +} + +static int gpmi_scan_sigmatel_bbt( + struct mtd_info *mtd, struct gpmi_bcb_info *nfo) +{ + int page, r; + u8 *pg; + struct gpmi_ncb *result = NULL; + + if (boot_search_count == 0) + boot_search_count = 1; + if (nfo == NULL) + return -EINVAL; + + pg = NULL; + printk(KERN_NOTICE"Scanning for NCB...\n"); + for (page = 0; page < (1<<boot_search_count); page += stride) { + pg = gpmi_read_page(mtd, page * mtd->writesize, + pg, ncb_version != 0); + + printk(KERN_NOTICE"GPMI: Checking page 0x%08X\n", page); + + if (ncb_version == 0) { + if (memcmp(pg, SIG1, SIG_SIZE) != 0) + continue; + printk(KERN_NOTICE"GPMI: Signature found at 0x%08X\n", + page); + result = (struct gpmi_ncb *)pg; + } + +#ifdef CONFIG_MTD_NAND_GPMI_TA1 + if (ncb_version == 1) { + void *dptr, *eccptr; + + if (memcmp(pg, SIG1, SIG_SIZE) != 0) + continue; + printk(KERN_NOTICE"GPMI: Signature found at 0x%08X\n", + page); + + r = gpmi_ncb1_redundancy_check(pg); + + if (r < 0) { + printk(KERN_ERR"GPMI: Oops. All three " + "copies of NCB are differrent!\n"); + continue; + } + + dptr = pg + r * NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES; + eccptr = pg + NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY + + r * NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES; + + if (gpmi_verify_hamming_22_16(dptr, eccptr, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) < 0) { + printk(KERN_ERR"Verification failed.\n"); + continue; + } + result = (struct gpmi_ncb *)pg; + } +#endif + +#ifdef CONFIG_MTD_NAND_GPMI_TA3 + if (ncb_version == 3) { + + if (memcmp(pg + 12, SIG1, SIG_SIZE) != 0) + continue; + + printk(KERN_NOTICE"GPMI: Signature found at 0x%08X\n", + page); + + if (gpmi_verify_hamming_13_8( + pg + 12, + pg + 524, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) < 0) { + printk(KERN_ERR"Verification failed.\n"); + continue; + } + result = (struct gpmi_ncb *)(pg + 12); + } +#endif + if (result) { + printk(KERN_NOTICE"GPMI: Valid NCB found " + "at 0x%08x\n", page); + nfo->timing = result->timing; + nfo->ncbblock = page * mtd->writesize; + break; + } + } + kfree(pg); + + return result != NULL; +} + +int gpmi_scan_bbt(struct mtd_info *mtd) +{ + struct gpmi_bcb_info stmp_bbt; + struct nand_chip *this = mtd->priv; + struct gpmi_nand_data *g = this->priv; + int r; + int numblocks, from, i, ign; + + memset(&stmp_bbt, 0, sizeof(stmp_bbt)); + g->transcribe_bbmark = 0; + + /* + Since NCB uses the full page, including BB pattern bits, + driver has to ignore result of gpmi_block_bad when reading + these pages. + */ + ign = g->ignorebad; + + g->ignorebad = true; /* strictly speaking, I'd have to hide + * the BBT too. + * But we still scanning it :) */ + r = gpmi_scan_sigmatel_bbt(mtd, &stmp_bbt); + + /* and then, driver has to restore the setting */ + g->ignorebad = ign; + + if (r) { + printk(KERN_NOTICE"Setting discovered timings: %d:%d:%d:%d\n", + stmp_bbt.timing.data_setup, + stmp_bbt.timing.data_hold, + stmp_bbt.timing.address_setup, + stmp_bbt.timing.dsample_time); + + gpmi_set_timings(g->dev, &stmp_bbt.timing); + g->timing = stmp_bbt.timing; + + } else { + g->transcribe_bbmark = !0; + numblocks = this->chipsize >> this->bbt_erase_shift; + from = 0; + printk(KERN_NOTICE"Checking BB on common-formatted flash\n"); + for (i = stmp_bbt.ncbblock + 1; i < numblocks; i++) { + /* check the block and transcribe the bb if needed */ + gpmi_block_bad(mtd, from, 0); + from += (1 << this->bbt_erase_shift); + } + } + + r = nand_default_bbt(mtd); + + if (g->transcribe_bbmark) { + /* NCB has been not found, so create NCB now */ + g->transcribe_bbmark = 0; + + stmp_bbt.timing = gpmi_safe_timing; + r = gpmi_write_ncb(mtd, &stmp_bbt); + } else { + /* NCB found, and its block should be marked as "good" */ + gpmi_block_mark_as(this, stmp_bbt.ncbblock, 0x00); + } + + return r; +} + +int gpmi_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip) + +{ + int page, res = 0; + struct nand_chip *chip = mtd->priv; + u16 bad; + struct gpmi_nand_data *g = chip->priv; + int chipnr; + + /* badblockpos is an offset in OOB area */ + int badblockpos = chip->ecc.steps * chip->ecc.bytes; + + if (g->ignorebad) + return 0; + + page = (int)(ofs >> chip->page_shift) & chip->pagemask; + + chipnr = (int)(ofs >> chip->chip_shift); + chip->select_chip(mtd, chipnr); + + if (g->transcribe_bbmark) + /* bad block marks still are on first byte of OOB */ + badblockpos = 0; + + if (chip->options & NAND_BUSWIDTH_16) { + chip->cmdfunc(mtd, NAND_CMD_READOOB, badblockpos & 0xFE, + page); + bad = cpu_to_le16(chip->read_word(mtd)); + if (badblockpos & 0x1) + bad >>= 8; + if ((bad & 0xFF) != 0xff) + res = 1; + } else { + chip->cmdfunc(mtd, NAND_CMD_READOOB, badblockpos, page); + if (chip->read_byte(mtd) != 0xff) + res = 1; + } + + if (g->transcribe_bbmark && res) + chip->block_markbad(mtd, ofs); + + chip->select_chip(mtd, -1); + + return res; +} + +#if defined(CONFIG_STMP3XXX_UNIQUE_ID) +/* + * UID on NAND support + */ +const int uid_size = 256; + +struct gpmi_uid_context { + struct mtd_info *mtd; + struct nand_chip *nand; + u_int32_t start; + u_int32_t size; +}; + +static int gpmi_read_uid(struct gpmi_uid_context *ctx, void *result) +{ + void *pg = NULL; + int page, o; + int status = -ENOENT; + int h_size = gpmi_hamming_ecc_size_22_16(uid_size); + + for (page = ctx->start >> ctx->nand->page_shift; + page < (ctx->start + ctx->size) >> ctx->nand->page_shift;) { + pr_debug("%s: reading page 0x%x\n", __func__, page); + if (gpmi_block_bad(ctx->mtd, page * ctx->mtd->writesize, 0)) { + pr_debug("%s: bad block %x, skipping it\n", + __func__, page * ctx->mtd->writesize); + page += (1 << ctx->nand->phys_erase_shift) + >> ctx->nand->page_shift; + continue; + } + pg = gpmi_read_page(ctx->mtd, page * ctx->mtd->writesize, + pg, 0); + if (pg) + break; + page++; + } + + if (!pg) + return status; + + o = gpmi_redundancy_check(pg, uid_size, h_size, 3 * uid_size); + if (o >= 0) { + if (gpmi_verify_hamming_22_16( + pg + o * uid_size, + pg + 3 * uid_size + h_size, uid_size) >= 0) { + memcpy(result, pg + o * uid_size, uid_size); + status = 0; + } + } + kfree(pg); + return status; +} + +static int gpmi_write_uid(struct gpmi_uid_context *ctx, void *src) +{ + struct mtd_oob_ops ops; + struct erase_info instr; + u8 *data = kzalloc(ctx->mtd->writesize + ctx->mtd->oobsize, GFP_KERNEL); + int h_size = gpmi_hamming_ecc_size_22_16(uid_size); + char ecc[h_size]; + u_int32_t start; + int i; + int err; + + if (!data) + return -ENOMEM; + + gpmi_encode_hamming_22_16(src, uid_size, ecc, h_size); + for (i = 0; i < 3; i++) { + memcpy(data + i * uid_size, src, uid_size); + memcpy(data + 3 * uid_size + i * h_size, ecc, h_size); + } + + ops.mode = MTD_OOB_AUTO; + ops.datbuf = data; + ops.len = ctx->mtd->writesize; + ops.oobbuf = NULL; + ops.ooblen = ctx->mtd->oobsize; + ops.ooboffs = 0; + + start = ctx->start; + + do { + memset(&instr, 0, sizeof(instr)); + instr.mtd = ctx->mtd; + instr.addr = start; + instr.len = (1 << ctx->nand->phys_erase_shift); + err = nand_erase_nand(ctx->mtd, &instr, 0); + if (err == 0) + err = nand_do_write_ops(ctx->mtd, start, &ops); + start += (1 << ctx->nand->phys_erase_shift); + if (start > ctx->start + ctx->size) + break; + } while (err != 0); + + return err; +} + +static ssize_t gpmi_uid_store(void *context, const char *page, + size_t count, int ascii) +{ + u8 data[uid_size]; + + memset(data, 0, sizeof(data)); + memcpy(data, page, uid_size < count ? uid_size : count); + gpmi_write_uid(context, data); + return count; +} + +static ssize_t gpmi_uid_show(void *context, char *page, int ascii) +{ + u8 result[uid_size]; + int i; + char *p = page; + int r; + + r = gpmi_read_uid(context, result); + if (r < 0) + return r; + + if (ascii) { + for (i = 0; i < uid_size; i++) { + if (i % 16 == 0) { + if (i) + *p++ = '\n'; + sprintf(p, "%04X: ", i); + p += strlen(p); + } + sprintf(p, "%02X ", result[i]); + p += strlen(p); + } + *p++ = '\n'; + return p - page; + + } else { + memcpy(page, result, uid_size); + return uid_size; + } +} + +static struct uid_ops gpmi_uid_ops = { + .id_show = gpmi_uid_show, + .id_store = gpmi_uid_store, +}; + +static struct gpmi_uid_context gpmi_uid_context; + +int __init gpmi_uid_init(const char *name, struct mtd_info *mtd, + u_int32_t start, u_int32_t size) +{ + gpmi_uid_context.mtd = mtd; + gpmi_uid_context.nand = mtd->priv; + gpmi_uid_context.start = start; + gpmi_uid_context.size = size; + return uid_provider_init(name, &gpmi_uid_ops, &gpmi_uid_context) ? + 0 : -EFAULT; +} + +void gpmi_uid_remove(const char *name) +{ + uid_provider_remove(name); +} +#endif diff --git a/drivers/mtd/nand/gpmi/gpmi-bch.c b/drivers/mtd/nand/gpmi/gpmi-bch.c new file mode 100644 index 000000000000..d7b261778dcf --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-bch.c @@ -0,0 +1,293 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * STMP378X BCH hardware ECC engine + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/dma-mapping.h> + +#include <asm/dma.h> +#include <mach/stmp3xxx.h> +#include <mach/platform.h> +#include <mach/irqs.h> +#include <mach/regs-gpmi.h> +#include "gpmi.h" + +#define BCH_MAX_NANDS 4 + +static int bch_available(void *context); +static int bch_setup(void *context, int index, int writesize, int oobsize); +static int bch_read(void *context, + int index, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob); +static int bch_stat(void *ctx, int index, struct mtd_ecc_stats *r); +static int bch_reset(void *context, int index); + +struct bch_state_t { + struct gpmi_hwecc_chip chip; + struct { + struct mtd_ecc_stats stat; + struct completion done; + u32 writesize, oobsize; + } nands[BCH_MAX_NANDS]; +}; + +static struct bch_state_t state = { + .chip = { + .name = "bch", + .setup = bch_setup, + .stat = bch_stat, + .read = bch_read, + .reset = bch_reset, + }, +}; + +static int bch_reset(void *context, int index) +{ + stmp3xxx_reset_block(REGS_BCH_BASE, true); + stmp3xxx_setl(BM_BCH_CTRL_COMPLETE_IRQ_EN, REGS_BCH_BASE + HW_BCH_CTRL); + return 0; +} + +static int bch_stat(void *context, int index, struct mtd_ecc_stats *r) +{ + struct bch_state_t *state = context; + + *r = state->nands[index].stat; + state->nands[index].stat.failed = 0; + state->nands[index].stat.corrected = 0; + return 0; +} + +static irqreturn_t bch_irq(int irq, void *context) +{ + u32 b0, s0; + struct mtd_ecc_stats stat; + int r; + struct bch_state_t *state = context; + + s0 = __raw_readl(REGS_BCH_BASE + HW_BCH_STATUS0); + r = (s0 & BM_BCH_STATUS0_COMPLETED_CE) >> 16; + + stat.corrected = stat.failed = 0; + + b0 = (s0 & BM_BCH_STATUS0_STATUS_BLK0) >> 8; + if (b0 <= 4) + stat.corrected += b0; + if (b0 == 0xFE) + stat.failed++; + + if (s0 & BM_BCH_STATUS0_CORRECTED) + stat.corrected += (s0 & BM_BCH_STATUS0_CORRECTED); + if (s0 & BM_BCH_STATUS0_UNCORRECTABLE) + stat.failed++; + + stmp3xxx_clearl(BM_BCH_CTRL_COMPLETE_IRQ, REGS_BCH_BASE + HW_BCH_CTRL); + + pr_debug("%s: chip %d, failed %d, corrected %d\n", + __func__, r, + state->nands[r].stat.failed, + state->nands[r].stat.corrected); + state->nands[r].stat.corrected += stat.corrected; + state->nands[r].stat.failed += stat.failed; + complete(&state->nands[r].done); + + return IRQ_HANDLED; +} + +static int bch_available(void *context) +{ + stmp3xxx_reset_block(REGS_BCH_BASE, 0); + return __raw_readl(REGS_BCH_BASE + HW_BCH_BLOCKNAME) == 0x20484342; +} + +static int bch_setup(void *context, int index, int writesize, int oobsize) +{ + struct bch_state_t *state = context; + u32 layout = (u32)REGS_BCH_BASE + 0x80 + index * 0x20; + u32 ecc0, eccN; + u32 reg; + int meta; + + switch (writesize) { + case 2048: + ecc0 = 4; + eccN = 4; + meta = 5; + break; + case 4096: + ecc0 = 16; + eccN = 14; + meta = 10; + break; + default: + printk(KERN_ERR"%s: cannot tune BCH for page size %d\n", + __func__, writesize); + return -EINVAL; + } + + state->nands[index].oobsize = oobsize; + state->nands[index].writesize = writesize; + + __raw_writel(BF(writesize/512, BCH_FLASH0LAYOUT0_NBLOCKS) | + BF(oobsize, BCH_FLASH0LAYOUT0_META_SIZE) | + BF(ecc0 >> 1, BCH_FLASH0LAYOUT0_ECC0) | /* for oob */ + BF(0x00, BCH_FLASH0LAYOUT0_DATA0_SIZE), layout); + __raw_writel(BF(writesize + oobsize, BCH_FLASH0LAYOUT1_PAGE_SIZE) | + BF(eccN >> 1, BCH_FLASH0LAYOUT1_ECCN) | /* for dblock */ + BF(512, BCH_FLASH0LAYOUT1_DATAN_SIZE), layout + 0x10); + + /* + * since driver only supports CS 0..3, layouts are mapped 1:1 : + * FLASHnLAYOUT[1,2] => LAYOUTSELECT[n*2:n2*+1] + */ + reg = __raw_readl(REGS_BCH_BASE + HW_BCH_LAYOUTSELECT); + reg &= ~(0x03 << (index * 2)); + reg |= index << (index * 2); + __raw_writel(reg, REGS_BCH_BASE + HW_BCH_LAYOUTSELECT); + + bch_reset(context, index); + + printk(KERN_DEBUG"%s: CS = %d, LAYOUT = 0x%08X, layout_reg = " + "0x%08x+0x%08x: 0x%08x+0x%08x\n", + __func__, + index, __raw_readl(REGS_BCH_BASE + HW_BCH_LAYOUTSELECT), + layout, layout + 0x10, + __raw_readl(layout), + __raw_readl(layout+0x10)); + return 0; +} + +static int bch_read(void *context, + int index, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob) +{ + unsigned long readsize = 0; + u32 bufmask = 0; + struct bch_state_t *state = context; + + if (!dma_mapping_error(NULL, oob)) { + bufmask |= BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY; + readsize += state->nands[index].oobsize; + } + if (!dma_mapping_error(NULL, page)) { + bufmask |= (BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE + & ~BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY); + readsize += state->nands[index].writesize; + } + + printk(KERN_DEBUG"readsize = %ld, bufmask = 0x%X\n", readsize, bufmask); + bch_reset(context, index); + + /* wait for ready */ + chain->command->cmd = + BF(1, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY, + GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_DATA, GPMI_CTRL0_ADDRESS) | + BF(index, GPMI_CTRL0_CS); + chain->command->alternate = 0; + chain++; + + /* enable BCH and read NAND data */ + chain->command->cmd = + BF(6, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__READ, GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(index, GPMI_CTRL0_CS) | + BF(readsize, GPMI_CTRL0_XFER_COUNT); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = + BM_GPMI_ECCCTRL_ENABLE_ECC | + BF(0x02, GPMI_ECCCTRL_ECC_CMD) | + BF(bufmask, GPMI_ECCCTRL_BUFFER_MASK); + chain->command->pio_words[3] = readsize; + chain->command->pio_words[4] = !dma_mapping_error(NULL, page) ? page : 0; + chain->command->pio_words[5] = !dma_mapping_error(NULL, oob) ? oob : 0; + chain->command->alternate = 0; + chain++; + + /* disable BCH block */ + chain->command->cmd = + BF(3, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY, + GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(index, GPMI_CTRL0_CS) | + BF(readsize, GPMI_CTRL0_XFER_COUNT); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->alternate = 0; + chain++; + + /* and deassert nand lock */ + chain->command->cmd = + BF(0, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->alternate = 0; + + return 0; +} + +int __init bch_init(void) +{ + int err; + + if (!bch_available(&state.chip)) + return -ENXIO; + gpmi_hwecc_chip_add(&state.chip); + err = request_irq(IRQ_BCH, bch_irq, 0, state.chip.name, &state); + if (err) + return err; + + printk(KERN_DEBUG"%s: initialized\n", __func__); + return 0; +} + +void bch_exit(void) +{ + free_irq(IRQ_BCH, &state); + gpmi_hwecc_chip_remove(&state.chip); +} diff --git a/drivers/mtd/nand/gpmi/gpmi-ecc8.c b/drivers/mtd/nand/gpmi/gpmi-ecc8.c new file mode 100644 index 000000000000..ead809cf0d54 --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-ecc8.c @@ -0,0 +1,387 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * STMP37XX/STMP378X ECC8 hardware ECC engine + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/dma-mapping.h> + +#include <asm/dma.h> +#include <mach/stmp3xxx.h> +#include <mach/irqs.h> +#include <mach/regs-gpmi.h> +#include <mach/regs-apbx.h> +#include <mach/regs-apbh.h> +#include <mach/platform.h> +#include "gpmi.h" + +#define ECC8_MAX_NANDS 4 + +static int ecc8_available(void *context); +static int ecc8_setup(void *context, int index, int writesize, int oobsize); +static int ecc8_read(void *context, int index, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob); +static int ecc8_write(void *context, int index, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob); +static int ecc8_stat(void *ctx, int index, struct mtd_ecc_stats *r); +static int ecc8_reset(void *context, int index); + +struct ecc8_nand { +}; + +struct ecc8_state_t { + struct gpmi_hwecc_chip chip; + struct completion done; + struct mtd_ecc_stats stat; + u32 writesize, oobsize; + u32 ecc_page, ecc_oob, oob_free; + u32 r, w; + int bits; + u32 s0_mask, s1_mask; +}; + +static struct ecc8_state_t state = { + .chip = { + .name = "ecc8", + .setup = ecc8_setup, + .stat = ecc8_stat, + .read = ecc8_read, + .write = ecc8_write, + .reset = ecc8_reset, + }, +}; + +static int ecc8_reset(void *context, int index) +{ + stmp3xxx_reset_block(REGS_ECC8_BASE, false); + while (__raw_readl(REGS_ECC8_BASE + HW_ECC8_CTRL) & BM_ECC8_CTRL_AHBM_SFTRST) + stmp3xxx_clearl(BM_ECC8_CTRL_AHBM_SFTRST, REGS_ECC8_BASE + HW_ECC8_CTRL); + stmp3xxx_setl(BM_ECC8_CTRL_COMPLETE_IRQ_EN, REGS_ECC8_BASE + HW_ECC8_CTRL); + return 0; +} + +static int ecc8_stat(void *context, int index, struct mtd_ecc_stats *r) +{ + struct ecc8_state_t *state = context; + + wait_for_completion(&state->done); + + *r = state->stat; + state->stat.failed = 0; + state->stat.corrected = 0; + return 0; +} + +static irqreturn_t ecc8_irq(int irq, void *context) +{ + int r; + struct mtd_ecc_stats ecc_stats; + struct ecc8_state_t *state = context; + u32 corr; + u32 s0 = __raw_readl(REGS_ECC8_BASE + HW_ECC8_STATUS0), + s1 = __raw_readl(REGS_ECC8_BASE + HW_ECC8_STATUS1); + + r = (s0 & BM_ECC8_STATUS0_COMPLETED_CE) >> 16; + + if (s0 & BM_ECC8_STATUS0_CORRECTED || + s0 & BM_ECC8_STATUS0_UNCORRECTABLE) { + + ecc_stats.failed = 0; + ecc_stats.corrected = 0; + + s0 = (s0 & BM_ECC8_STATUS0_STATUS_AUX) >> 8; + if (s0 <= 4) + ecc_stats.corrected += s0; + if (s0 == 0xE) + ecc_stats.failed++; + + for ( ; s1 != 0; s1 >>= 4) { + corr = s1 & 0xF; + if (corr == 0x0C) + continue; + if (corr == 0xE) + ecc_stats.failed++; + if (corr <= 8) + ecc_stats.corrected += corr; + s1 >>= 4; + } + state->stat.corrected += ecc_stats.corrected; + state->stat.failed += ecc_stats.failed; + } + + complete(&state->done); + + stmp3xxx_clearl(BM_ECC8_CTRL_COMPLETE_IRQ, REGS_ECC8_BASE + HW_ECC8_CTRL); + return IRQ_HANDLED; +} + +static int ecc8_available(void *context) +{ + return 1; +} + +static int ecc8_setup(void *context, int index, int writesize, int oobsize) +{ + struct ecc8_state_t *state = context; + + switch (writesize) { + case 2048: + state->ecc_page = 9; + state->ecc_oob = 9; + state->bits = 4; + state->r = BF(BV_GPMI_ECCCTRL_ECC_CMD__DECODE_4_BIT, + GPMI_ECCCTRL_ECC_CMD); + state->w = BF(BV_GPMI_ECCCTRL_ECC_CMD__ENCODE_4_BIT, + GPMI_ECCCTRL_ECC_CMD); + break; + case 4096: + state->ecc_page = 18; + state->ecc_oob = 9; + state->bits = 8; + state->r = BF(BV_GPMI_ECCCTRL_ECC_CMD__DECODE_8_BIT, + GPMI_ECCCTRL_ECC_CMD); + state->w = BF(BV_GPMI_ECCCTRL_ECC_CMD__ENCODE_8_BIT, + GPMI_ECCCTRL_ECC_CMD); + break; + default: + return -ENOTSUPP; + } + + state->oob_free = oobsize - + (state->bits * state->ecc_page) - state->ecc_oob; + state->oobsize = oobsize; + state->writesize = writesize; + + ecc8_reset(context, index); + + return 0; +} + +/** + * ecc8_read - create dma chain to read nand page + * + * @context: context data, pointer to ecc8_state + * @index: CS of nand to read + * @chain: dma chain to be filled + * @page: (physical) address of page data to be read to + * @oob: (physical) address of OOB data to be read to + * + * Return: status of operation -- 0 on success + */ +static int ecc8_read(void *context, int cs, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob) +{ + unsigned long readsize = 0; + u32 bufmask = 0; + struct ecc8_state_t *state = context; + + ecc8_reset(context, cs); + + state->s0_mask = state->s1_mask = 0; + + if (!dma_mapping_error(NULL, page)) { + bufmask |= (1 << state->bits) - 1; + readsize += (state->bits * state->ecc_page); + readsize += state->writesize; + } + if (!dma_mapping_error(NULL, oob)) { + bufmask |= BV_GPMI_ECCCTRL_BUFFER_MASK__AUXILIARY; + readsize += state->oob_free + state->ecc_oob; + state->s0_mask = BM_ECC8_STATUS0_STATUS_AUX; + if (dma_mapping_error(NULL, page)) + page = oob; + } + + + /* wait for ready */ + chain->command->cmd = + BF(1, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY, + GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_DATA, GPMI_CTRL0_ADDRESS) | + BF(cs, GPMI_CTRL0_CS); + chain->command->buf_ptr = 0; + chain++; + + /* enable ECC and read NAND data */ + chain->command->cmd = + BF(6, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__READ, GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(cs, GPMI_CTRL0_CS) | + BF(readsize, GPMI_CTRL0_XFER_COUNT); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = + BM_GPMI_ECCCTRL_ENABLE_ECC | + state->r | + BF(bufmask, GPMI_ECCCTRL_BUFFER_MASK); + chain->command->pio_words[3] = readsize; + chain->command->pio_words[4] = !dma_mapping_error(NULL, page) ? page : 0; + chain->command->pio_words[5] = !dma_mapping_error(NULL, oob) ? oob : 0; + chain->command->buf_ptr = 0; + chain++; + + /* disable ECC block */ + chain->command->cmd = + BF(3, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY, + GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(cs, GPMI_CTRL0_CS); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->buf_ptr = 0; + chain++; + + /* and deassert nand lock */ + chain->command->cmd = + BF(0, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->buf_ptr = 0; + + init_completion(&state->done); + + return 0; +} + +static int ecc8_write(void *context, int cs, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob) +{ + u32 bufmask = 0; + struct ecc8_state_t *state = context; + u32 data_w, data_w_ecc, + oob_w, oob_w_ecc; + + ecc8_reset(context, cs); + + data_w = data_w_ecc = oob_w = oob_w_ecc = 0; + + if (!dma_mapping_error(NULL, oob)) { + bufmask |= BV_GPMI_ECCCTRL_BUFFER_MASK__AUXILIARY; + oob_w = state->oob_free; + oob_w_ecc = oob_w + state->ecc_oob; + } + if (!dma_mapping_error(NULL, page)) { + bufmask |= (1 << state->bits) - 1; + data_w = state->bits * 512; + data_w_ecc = data_w + state->bits * state->ecc_page; + } + + /* enable ECC and send NAND data (page only) */ + chain->command->cmd = + BF(data_w ? data_w : oob_w, APBH_CHn_CMD_XFER_COUNT) | + BF(4, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__DMA_READ, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WRITE, GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BM_GPMI_CTRL0_LOCK_CS | + BF(cs, GPMI_CTRL0_CS) | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_DATA, GPMI_CTRL0_ADDRESS) | + BF(data_w + oob_w, GPMI_CTRL0_XFER_COUNT); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = + BM_GPMI_ECCCTRL_ENABLE_ECC | + state->w | + BF(bufmask, GPMI_ECCCTRL_BUFFER_MASK); + chain->command->pio_words[3] = + data_w_ecc + oob_w_ecc; + if (!dma_mapping_error(NULL, page)) + chain->command->buf_ptr = page; + else + chain->command->buf_ptr = oob; + chain++; + + if (!dma_mapping_error(NULL, page) && !dma_mapping_error(NULL, oob)) { + /* send NAND data (OOB only) */ + chain->command->cmd = + BM_APBH_CHn_CMD_CHAIN | + BF(oob_w, APBH_CHn_CMD_XFER_COUNT) | + BF(0, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BF(BV_APBH_CHn_CMD_COMMAND__DMA_READ, + APBH_CHn_CMD_COMMAND); + chain->command->buf_ptr = oob; /* never dma_mapping_error() */ + chain++; + } + /* emit IRQ */ + chain->command->cmd = + BF(0, APBH_CHn_CMD_CMDWORDS) | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, + APBH_CHn_CMD_COMMAND) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_IRQONCMPLT; + + return 0; +} + +int __init ecc8_init(void) +{ + int err; + + if (!ecc8_available(&state)) + return -ENXIO; + + gpmi_hwecc_chip_add(&state.chip); + err = request_irq(IRQ_ECC8_IRQ, ecc8_irq, 0, state.chip.name, &state); + if (err) + return err; + + printk(KERN_INFO"%s: initialized\n", __func__); + return 0; +} + +void ecc8_exit(void) +{ + free_irq(IRQ_ECC8_IRQ, &state); + gpmi_hwecc_chip_remove(&state.chip); +} diff --git a/drivers/mtd/nand/gpmi/gpmi-hamming-13-8.c b/drivers/mtd/nand/gpmi/gpmi-hamming-13-8.c new file mode 100644 index 000000000000..256911050782 --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-hamming-13-8.c @@ -0,0 +1,130 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * NCB software ECC Hamming code + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/platform_device.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <mach/dma.h> +#include "gpmi.h" + +#define BIT_VAL(v, n) (((v) >> (n)) & 0x1) +#define B(n) (BIT_VAL(d, n)) + +static u8 calculate_parity(u8 d) +{ + u8 p = 0; + + if (d == 0 || d == 0xFF) + return 0; /* optimization :) */ + + p |= (B(6) ^ B(5) ^ B(3) ^ B(2)) << 0; + p |= (B(7) ^ B(5) ^ B(4) ^ B(2) ^ B(1)) << 1; + p |= (B(7) ^ B(6) ^ B(5) ^ B(1) ^ B(0)) << 2; + p |= (B(7) ^ B(4) ^ B(3) ^ B(0)) << 3; + p |= (B(6) ^ B(4) ^ B(3) ^ B(2) ^ B(1) ^ B(0)) << 4; + return p; +} + +static inline int even_number_of_1s(u8 byte) +{ + int even = 1; + + while (byte > 0) { + even ^= (byte & 0x1); + byte >>= 1; + } + return even; +} + +static int lookup_single_error(u8 syndrome) +{ + int i; + u8 syndrome_table[] = { + 0x1C, 0x16, 0x13, 0x19, + 0x1A, 0x07, 0x15, 0x0E, + 0x01, 0x02, 0x04, 0x08, + 0x10, + }; + + for (i = 0; i < ARRAY_SIZE(syndrome_table); i++) + if (syndrome_table[i] == syndrome) + return i; + return -ENOENT; +} + +int gpmi_verify_hamming_13_8(void *data, u8 *parity, size_t size) +{ + int i; + u8 *pdata = data; + int bit_to_flip; + u8 np, syndrome; + int errors = 0; + + for (i = 0; i < size; i ++, pdata++) { + np = calculate_parity(*pdata); + syndrome = np ^ parity[i]; + if (syndrome == 0) /* cool */ { + continue; + } + + if (even_number_of_1s(syndrome)) + return -i; /* can't recover */ + + bit_to_flip = lookup_single_error(syndrome); + if (bit_to_flip < 0) + return -i; /* can't fix the error */ + + if (bit_to_flip < 8) { + *pdata ^= (1 << bit_to_flip); + errors++; + } + } + return errors; +} + +void gpmi_encode_hamming_13_8(void *source_block, size_t src_size, + void *source_ecc, size_t ecc_size) +{ + int i; + u8 *src = source_block; + u8 *ecc = source_ecc; + + for (i = 0; i < src_size && i < ecc_size; i++) + ecc[i] = calculate_parity(src[i]); +} + +void gpmi_encode_hamming_ncb_13_8(void *source_block, size_t source_size, + void *target_block, size_t target_size) +{ + if (target_size < 12 + 2 * NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) + return; + memset(target_block, 0xFF, target_size); + memcpy((u8 *)target_block + 12, source_block, source_size); + gpmi_encode_hamming_13_8(source_block, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + (u8 *)target_block + 12 + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES); +} + +unsigned gpmi_hamming_ecc_size_13_8(int block_size) +{ + return block_size; +} diff --git a/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.c b/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.c new file mode 100644 index 000000000000..cceb35feab2e --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.c @@ -0,0 +1,195 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * NCB software ECC Hamming code + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/platform_device.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <mach/dma.h> +#include "gpmi.h" +#include "gpmi-hamming-22-16.h" + +#define BIT_VAL(v, n) (((v) >> (n)) & 0x1) +#define B(n) (BIT_VAL(d, n)) +#define BSEQ(a1, a2, a3, a4, a5, a6, a7, a8) \ + (B(a1) ^ B(a2) ^ B(a3) ^ B(a4) ^ B(a5) ^ B(a6) ^ B(a7) ^ B(a8)) +static u8 calculate_parity(u16 d) +{ + u8 p = 0; + + if (d == 0 || d == 0xFFFF) + return 0; /* optimization :) */ + + p |= BSEQ(15, 12, 11, 8, 5, 4, 3, 2) << 0; + p |= BSEQ(13, 12, 11, 10, 9, 7, 3, 1) << 1; + p |= BSEQ(15, 14, 13, 11, 10, 9, 6, 5) << 2; + p |= BSEQ(15, 14, 13, 8, 7, 6, 4, 0) << 3; + p |= BSEQ(12, 9, 8, 7, 6, 2, 1, 0) << 4; + p |= BSEQ(14, 10, 5, 4, 3, 2, 1, 0) << 5; + return p; +} + +static inline int even_number_of_1s(u8 byte) +{ + int even = 1; + + while (byte > 0) { + even ^= (byte & 0x1); + byte >>= 1; + } + return even; +} + +static int lookup_single_error(u8 syndrome) +{ + int i; + u8 syndrome_table[] = { + 0x38, 0x32, 0x31, 0x23, 0x29, 0x25, 0x1C, 0x1A, + 0x19, 0x16, 0x26, 0x07, 0x13, 0x0E, 0x2C, 0x0D, + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, + }; + + for (i = 0; i < ARRAY_SIZE(syndrome_table); i++) + if (syndrome_table[i] == syndrome) + return i; + return -ENOENT; +} + +int gpmi_verify_hamming_22_16(void *data, u8 *parity, size_t size) +{ + int i, j; + int bit_index, bit_to_flip; + u16 *pdata = data; + u8 p = 0, np, syndrome; + int errors = 0; + + for (bit_index = i = j = 0; + i < size / sizeof(u16); + i ++, pdata++) { + + switch (bit_index) { + + case 0: + p = parity[j] & 0x3F; + break; + case 2: + p = (parity[j++] & 0xC0) >> 6; + p |= (parity[j] & 0x0F) << 2; + break; + case 4: + p = (parity[j++] & 0xF0) >> 4; + p |= (parity[j] & 0x03) << 4; + break; + case 6: + p = (parity[j++] & 0xFC) >> 2; + break; + default: + BUG(); /* how did you get this ?! */ + break; + } + bit_index = (bit_index + 2) % 8; + + np = calculate_parity(*pdata); + syndrome = np ^ p; + if (syndrome == 0) /* cool */ { + continue; + } + + if (even_number_of_1s(syndrome)) + return -i; /* can't recover */ + + bit_to_flip = lookup_single_error(syndrome); + if (bit_to_flip < 0) + return -i; /* can't fix the error */ + + if (bit_to_flip < 16) { + *pdata ^= (1 << bit_to_flip); + errors++; + } + } + return errors; +} + +void gpmi_encode_hamming_22_16(void *source_block, size_t src_size, + void *source_ecc, size_t ecc_size) +{ + int i, j, bit_index; + u16 *src = source_block; + u8 *ecc = source_ecc; + u8 np; + + for (bit_index = j = i = 0; + j < src_size/sizeof(u16) && i < ecc_size; + j++) { + + np = calculate_parity(src[j]); + + switch (bit_index) { + + case 0: + ecc[i] = np & 0x3F; + break; + case 2: + ecc[i++] |= (np & 0x03) << 6; + ecc[i] = (np & 0x3C) >> 2; + break; + case 4: + ecc[i++] |= (np & 0x0F) << 4; + ecc[i] = (np & 0x30) >> 4; + break; + case 6: + ecc[i++] |= (np & 0x3F) << 2; + break; + } + bit_index = (bit_index + 2) % 8; + } + +} + +void gpmi_encode_hamming_ncb_22_16(void *source_block, size_t source_size, + void *target_block, size_t target_size) +{ + u8 *dst = target_block; + u8 ecc[NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES]; + + gpmi_encode_hamming_22_16(source_block, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + ecc, + NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES); + /* create THREE copies of source block */ + memcpy(dst + NAND_HC_ECC_OFFSET_FIRST_DATA_COPY, + source_block, NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES); + memcpy(dst + NAND_HC_ECC_OFFSET_SECOND_DATA_COPY, + source_block, NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES); + memcpy(dst + NAND_HC_ECC_OFFSET_THIRD_DATA_COPY, + source_block, NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES); + /* ..and three copies of ECC block */ + memcpy(dst + NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY, + ecc, NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES); + memcpy(dst + NAND_HC_ECC_OFFSET_SECOND_PARITY_COPY, + ecc, NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES); + memcpy(dst + NAND_HC_ECC_OFFSET_THIRD_PARITY_COPY, + ecc, NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES); +} + +unsigned gpmi_hamming_ecc_size_22_16(int block_size) +{ + return (((block_size * 8) / 16) * 6) / 8; +} diff --git a/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.h b/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.h new file mode 100644 index 000000000000..6959bf3c599e --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.h @@ -0,0 +1,43 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * NCB software ECC Hamming code + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __LINUX_GPMI_H +#define __LINUX_GPMI_H + +#define NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES (512) +#define NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES \ + ((((NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES*8)/16)*6)/8) +#define NAND_HC_ECC_OFFSET_FIRST_DATA_COPY (0) +#define NAND_HC_ECC_OFFSET_SECOND_DATA_COPY \ + (NAND_HC_ECC_OFFSET_FIRST_DATA_COPY + \ + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) +#define NAND_HC_ECC_OFFSET_THIRD_DATA_COPY \ + (NAND_HC_ECC_OFFSET_SECOND_DATA_COPY + \ + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) +#define NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY \ + (NAND_HC_ECC_OFFSET_THIRD_DATA_COPY + \ + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) +#define NAND_HC_ECC_OFFSET_SECOND_PARITY_COPY \ + (NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY + \ + NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES) +#define NAND_HC_ECC_OFFSET_THIRD_PARITY_COPY \ + (NAND_HC_ECC_OFFSET_SECOND_PARITY_COPY + \ + NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES) + +#endif diff --git a/drivers/mtd/nand/gpmi/gpmi.h b/drivers/mtd/nand/gpmi/gpmi.h new file mode 100644 index 000000000000..65de2e1a1c78 --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi.h @@ -0,0 +1,293 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __DRIVERS_GPMI_H +#define __DRIVERS_GPMI_H + +#include <linux/mtd/partitions.h> +#include <linux/timer.h> +#include <mach/gpmi.h> +#include <mach/regs-gpmi.h> +#include <mach/regs-apbh.h> +#include <mach/dma.h> +#ifdef CONFIG_MTD_NAND_GPMI_BCH +#include <mach/regs-bch.h> +#endif +#include <mach/regs-ecc8.h> + +#include "gpmi-hamming-22-16.h" + +#define GPMI_ECC4_WR \ + (BM_GPMI_ECCCTRL_ENABLE_ECC | \ + BF(BV_GPMI_ECCCTRL_ECC_CMD__ENCODE_4_BIT, GPMI_ECCCTRL_ECC_CMD)) +#define GPMI_ECC4_RD \ + (BM_GPMI_ECCCTRL_ENABLE_ECC | \ + BF(BV_GPMI_ECCCTRL_ECC_CMD__DECODE_4_BIT, GPMI_ECCCTRL_ECC_CMD)) +#define GPMI_ECC8_WR \ + (BM_GPMI_ECCCTRL_ENABLE_ECC | \ + BF(BV_GPMI_ECCCTRL_ECC_CMD__ENCODE_8_BIT, GPMI_ECCCTRL_ECC_CMD)) +#define GPMI_ECC8_RD \ + (BM_GPMI_ECCCTRL_ENABLE_ECC | \ + BF(BV_GPMI_ECCCTRL_ECC_CMD__DECODE_8_BIT, GPMI_ECCCTRL_ECC_CMD)) + +/* fingerprints of BCB that can be found on STMP-formatted flash */ +#define SIG1 "STMP" +#define SIG_NCB "NCB " +#define SIG_LDLB "LDLB" +#define SIG_DBBT "DBBT" +#define SIG_SIZE 4 + +struct gpmi_nand_timing { + u8 data_setup; + u8 data_hold; + u8 address_setup; + u8 dsample_time; +}; + +struct gpmi_bcb_info { + struct gpmi_nand_timing timing; + loff_t ncbblock; + const void *pre_ncb; + size_t pre_ncb_size; +}; + +struct gpmi_ncb; + +int gpmi_erase(struct mtd_info *mtd, struct erase_info *instr); +int gpmi_block_markbad(struct mtd_info *mtd, loff_t ofs); +int gpmi_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip); +int gpmi_scan_bbt(struct mtd_info *mtd); +int gpmi_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip); +#ifdef CONFIG_MTD_NAND_GPMI_SYSFS_ENTRIES +int gpmi_sysfs(struct platform_device *p, int create); +#endif +int gpmi_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page, int sndcmd); +void gpmi_set_timings(struct platform_device *pdev, + struct gpmi_nand_timing *tm); +int gpmi_write_ncb(struct mtd_info *mtd, struct gpmi_bcb_info *b); + +unsigned gpmi_hamming_ecc_size_22_16(int block_size); +void gpmi_encode_hamming_ncb_22_16(void *source_block, size_t source_size, + void *target_block, size_t target_size); +void gpmi_encode_hamming_22_16(void *source_block, size_t src_size, + void *source_ecc, size_t ecc_size); +int gpmi_verify_hamming_22_16(void *data, u8 *parity, size_t size); + +unsigned gpmi_hamming_ecc_size_13_8(int block_size); +void gpmi_encode_hamming_ncb_13_8(void *source_block, size_t source_size, + void *target_block, size_t target_size); +void gpmi_encode_hamming_13_8(void *source_block, size_t src_size, + void *source_ecc, size_t ecc_size); +int gpmi_verify_hamming_13_8(void *data, u8 *parity, size_t size); + +#define GPMI_DMA_MAX_CHAIN 20 /* max DMA commands in chain */ + +/* + * Sizes of data buffers to exchange commands/data with NAND chip + * Default values cover 4K NAND page (4096 data bytes + 218 bytes OOB) + */ +#define GPMI_CMD_BUF_SZ 10 +#define GPMI_DATA_BUF_SZ NAND_MAX_PAGESIZE +#define GPMI_WRITE_BUF_SZ NAND_MAX_PAGESIZE +#define GPMI_OOB_BUF_SZ NAND_MAX_OOBSIZE + +#define GPMI_MAX_CHIPS 10 + +struct gpmi_hwecc_chip { + char name[40]; + struct list_head list; + int (*setup)(void *ctx, int index, int writesize, int oobsize); + int (*reset)(void *ctx, int index); + int (*read)(void *ctx, int index, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob); + int (*write)(void *ctx, int index, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob); + int (*stat)(void *ctx, int index, struct mtd_ecc_stats *r); +}; + +/* HWECC chips */ +struct gpmi_hwecc_chip *gpmi_hwecc_chip_find(char *name); +void gpmi_hwecc_chip_add(struct gpmi_hwecc_chip *chip); +void gpmi_hwecc_chip_remove(struct gpmi_hwecc_chip *chip); +int bch_init(void); +int ecc8_init(void); +void bch_exit(void); +void ecc8_exit(void); + + +struct gpmi_nand_data { + void __iomem *io_base; + struct clk *clk; + int irq; + struct timer_list timer; + int self_suspended; + int use_count; + struct regulator *regulator; + int reg_uA; + + int ignorebad; + void *bbt; + + struct nand_chip chip; + struct mtd_info mtd; + struct platform_device *dev; + +#ifdef CONFIG_MTD_PARTITIONS + int nr_parts; + struct mtd_partition + *parts; +#endif + + struct completion done; + + u8 *cmd_buffer; + dma_addr_t cmd_buffer_handle; + int cmd_buffer_size, cmd_buffer_sz; + + u8 *write_buffer; + dma_addr_t write_buffer_handle; + int write_buffer_size; + u8 *read_buffer; /* point in write_buffer */ + dma_addr_t read_buffer_handle; + + u8 *data_buffer; + dma_addr_t data_buffer_handle; + int data_buffer_size; + + u8 *oob_buffer; + dma_addr_t oob_buffer_handle; + int oob_buffer_size; + + void *verify_buffer; + + struct nchip { + unsigned dma_ch; + struct stmp37xx_circ_dma_chain chain; + struct stmp3xxx_dma_descriptor d[GPMI_DMA_MAX_CHAIN]; + struct stmp3xxx_dma_descriptor error; + int cs; + } chips[GPMI_MAX_CHIPS]; + struct nchip *cchip; + int selected_chip; + + unsigned hwecc_type_read, hwecc_type_write; + int hwecc; + + int ecc_oob_bytes, oob_free; + + int transcribe_bbmark; + struct gpmi_nand_timing timing; + + void (*saved_command)(struct mtd_info *mtd, unsigned int command, + int column, int page_addr); + + int raw_oob_mode; + int (*saved_read_oob)(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops); + int (*saved_write_oob)(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops); + + struct gpmi_hwecc_chip *hc; + + int numchips; + int custom_partitions; + struct mtd_info *masters[GPMI_MAX_CHIPS]; + struct mtd_partition chip_partitions[GPMI_MAX_CHIPS]; + int n_concat; + struct mtd_info *concat[GPMI_MAX_CHIPS]; + struct mtd_info *concat_mtd; +}; + +extern struct gpmi_nand_timing gpmi_safe_timing; + +struct gpmi_ncb { + u32 fingerprint1; + struct gpmi_nand_timing timing; + u32 pagesize; + u32 page_plus_oob_size; + u32 sectors_per_block; + u32 sector_in_page_mask; + u32 sector_to_page_shift; + u32 num_nands; + u32 reserved[3]; + u32 fingerprint2; /* offset 0x2C */ +}; + +struct gpmi_ldlb { + u32 fingerprint1; + u16 major, minor, sub, reserved; + u32 nand_bitmap; + u32 reserved1[7]; + u32 fingerprint2; + struct { + u32 fw_starting_nand; + u32 fw_starting_sector; + u32 fw_sector_stride; + u32 fw_sectors_total; + } fw[2]; + u16 fw_major, fw_minor, fw_sub, fw_reserved; + u32 bbt_blk; + u32 bbt_blk_backup; +}; + +static inline void gpmi_block_mark_as(struct nand_chip *chip, + int block, int mark) +{ + u32 o; + int shift = (block & 0x03) << 1, + index = block >> 2; + + if (chip->bbt) { + mark &= 0x03; + + o = chip->bbt[index]; + o &= ~(0x03 << shift); + o |= (mark << shift); + chip->bbt[index] = o; + } +} + +static inline int gpmi_block_badness(struct nand_chip *chip, + int block) +{ + u32 o; + int shift = (block & 0x03) << 1, + index = block >> 2; + + if (chip->bbt) { + o = (chip->bbt[index] >> shift) & 0x03; + pr_debug("%s: block = %d, o = %d\n", __func__, block, o); + return o; + } + return -1; +} + +#ifdef CONFIG_STMP3XXX_UNIQUE_ID +int __init gpmi_uid_init(const char *name, struct mtd_info *mtd, + u_int32_t start, u_int32_t size); +void gpmi_uid_remove(const char *name); +#else +#define gpmi_uid_init(name, mtd, start, size) +#define gpmi_uid_remove(name) +#endif + +#endif diff --git a/drivers/mtd/nand/imx_nfc.c b/drivers/mtd/nand/imx_nfc.c new file mode 100644 index 000000000000..65f72b4e468a --- /dev/null +++ b/drivers/mtd/nand/imx_nfc.c @@ -0,0 +1,8286 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mtd/partitions.h> +#include <linux/io.h> + +#define DRIVER_VERSION "1.0" + +/* Define this macro to enable event reporting. */ + +#define EVENT_REPORTING + +/* + * For detailed information that will be helpful in understanding this driver, + * see: + * + * Documentation/imx_nfc.txt + */ + +/* + * Macros that describe NFC hardware have names of the form: + * + * NFC_* + * + * Macros that apply only to specific versions of the NFC have names of the + * following form: + * + * NFC_<M>_<N>_* + * + * where: + * + * <M> is the major version of the NFC hardware. + * <N> is the minor version of the NFC hardware. + * + * The minor version can be 'X', which means that the macro applies to *all* + * NFCs of the same major version. + * + * For NFC versions with only one set of registers, macros that give offsets + * against the base address have names of the form: + * + * *<RegisterName>_REG_OFF + * + * Macros that give the position of a field's LSB within a given register have + * names of the form: + * + * *<RegisterName>_<FieldName>_POS + * + * Macros that mask a field within a given register have names of the form: + * + * *<RegisterName>_<FieldName>_MSK + */ + +/* + * Macro definitions for ALL NFC versions. + */ + +#define NFC_MAIN_BUF_SIZE (512) + +/* + * Macro definitions for version 1.0 NFCs. + */ + +#define NFC_1_0_BUF_SIZE_REG_OFF (0x00) +#define NFC_1_0_BUF_ADDR_REG_OFF (0x04) +#define NFC_1_0_FLASH_ADDR_REG_OFF (0x06) +#define NFC_1_0_FLASH_CMD_REG_OFF (0x08) +#define NFC_1_0_CONFIG_REG_OFF (0x0A) +#define NFC_1_0_ECC_STATUS_RESULT_REG_OFF (0x0C) +#define NFC_1_0_RSLTMAIN_AREA_REG_OFF (0x0E) +#define NFC_1_0_RSLTSPARE_AREA_REG_OFF (0x10) +#define NFC_1_0_WRPROT_REG_OFF (0x12) +#define NFC_1_0_UNLOCKSTART_BLKADDR_REG_OFF (0x14) +#define NFC_1_0_UNLOCKEND_BLKADDR_REG_OFF (0x16) +#define NFC_1_0_NF_WRPRST_REG_OFF (0x18) + +#define NFC_1_0_CONFIG1_REG_OFF (0x1A) +#define NFC_1_0_CONFIG1_NF_CE_POS (7) +#define NFC_1_0_CONFIG1_NF_CE_MSK (0x1 << 7) + +#define NFC_1_0_CONFIG2_REG_OFF (0x1C) + +/* +* Macro definitions for version 2.X NFCs. +*/ + +#define NFC_2_X_FLASH_ADDR_REG_OFF (0x06) +#define NFC_2_X_FLASH_CMD_REG_OFF (0x08) +#define NFC_2_X_CONFIG_REG_OFF (0x0A) + +#define NFC_2_X_WR_PROT_REG_OFF (0x12) + +#define NFC_2_X_NF_WR_PR_ST_REG_OFF (0x18) + +#define NFC_2_X_CONFIG2_REG_OFF (0x1C) +#define NFC_2_X_CONFIG2_FCMD_POS (0) +#define NFC_2_X_CONFIG2_FCMD_MSK (0x1 << 0) +#define NFC_2_X_CONFIG2_FADD_POS (1) +#define NFC_2_X_CONFIG2_FADD_MSK (0x1 << 1) +#define NFC_2_X_CONFIG2_FDI_POS (2) +#define NFC_2_X_CONFIG2_FDI_MSK (0x1 << 2) +#define NFC_2_X_CONFIG2_FDO_POS (3) +#define NFC_2_X_CONFIG2_FDO_MSK (0x7 << 3) +#define NFC_2_X_CONFIG2_INT_POS (15) +#define NFC_2_X_CONFIG2_INT_MSK (0x1 << 15) + +/* +* Macro definitions for version 2.0 NFCs. +*/ + +#define NFC_2_0_BUF_ADDR_REG_OFF (0x04) +#define NFC_2_0_BUF_ADDR_RBA_POS (0) +#define NFC_2_0_BUF_ADDR_RBA_MSK (0xf << 0) + +#define NFC_2_0_ECC_STATUS_REG_OFF (0x0C) +#define NFC_2_0_ECC_STATUS_NOSER1_POS (0) +#define NFC_2_0_ECC_STATUS_NOSER1_MSK (0xF << 0) +#define NFC_2_0_ECC_STATUS_NOSER2_POS (4) +#define NFC_2_0_ECC_STATUS_NOSER2_MSK (0xF << 4) +#define NFC_2_0_ECC_STATUS_NOSER3_POS (8) +#define NFC_2_0_ECC_STATUS_NOSER3_MSK (0xF << 8) +#define NFC_2_0_ECC_STATUS_NOSER4_POS (12) +#define NFC_2_0_ECC_STATUS_NOSER4_MSK (0xF << 12) + +#define NFC_2_0_CONFIG1_REG_OFF (0x1A) +#define NFC_2_0_CONFIG1_SP_EN_POS (2) +#define NFC_2_0_CONFIG1_SP_EN_MSK (0x1 << 2) +#define NFC_2_0_CONFIG1_ECC_EN_POS (3) +#define NFC_2_0_CONFIG1_ECC_EN_MSK (0x1 << 3) +#define NFC_2_0_CONFIG1_INT_MSK_POS (4) +#define NFC_2_0_CONFIG1_INT_MSK_MSK (0x1 << 4) +#define NFC_2_0_CONFIG1_NF_BIG_POS (5) +#define NFC_2_0_CONFIG1_NF_BIG_MSK (0x1 << 5) +#define NFC_2_0_CONFIG1_NFC_RST_POS (6) +#define NFC_2_0_CONFIG1_NFC_RST_MSK (0x1 << 6) +#define NFC_2_0_CONFIG1_NF_CE_POS (7) +#define NFC_2_0_CONFIG1_NF_CE_MSK (0x1 << 7) +#define NFC_2_0_CONFIG1_ONE_CYLE_POS (8) +#define NFC_2_0_CONFIG1_ONE_CYLE_MSK (0x1 << 8) +#define NFC_2_0_CONFIG1_MLC_POS (9) +#define NFC_2_0_CONFIG1_MLC_MSK (0x1 << 9) + +#define NFC_2_0_UNLOCK_START_REG_OFF (0x14) +#define NFC_2_0_UNLOCK_END_REG_OFF (0x16) + +/* +* Macro definitions for version 2.1 NFCs. +*/ + +#define NFC_2_1_BUF_ADDR_REG_OFF (0x04) +#define NFC_2_1_BUF_ADDR_RBA_POS (0) +#define NFC_2_1_BUF_ADDR_RBA_MSK (0x7 << 0) +#define NFC_2_1_BUF_ADDR_CS_POS (4) +#define NFC_2_1_BUF_ADDR_CS_MSK (0x3 << 4) + +#define NFC_2_1_ECC_STATUS_REG_OFF (0x0C) +#define NFC_2_1_ECC_STATUS_NOSER1_POS (0) +#define NFC_2_1_ECC_STATUS_NOSER1_MSK (0xF << 0) +#define NFC_2_1_ECC_STATUS_NOSER2_POS (4) +#define NFC_2_1_ECC_STATUS_NOSER2_MSK (0xF << 4) +#define NFC_2_1_ECC_STATUS_NOSER3_POS (8) +#define NFC_2_1_ECC_STATUS_NOSER3_MSK (0xF << 8) +#define NFC_2_1_ECC_STATUS_NOSER4_POS (12) +#define NFC_2_1_ECC_STATUS_NOSER4_MSK (0xF << 12) + +#define NFC_2_1_CONFIG1_REG_OFF (0x1A) +#define NFC_2_1_CONFIG1_ECC_MODE_POS (0) +#define NFC_2_1_CONFIG1_ECC_MODE_MSK (0x1 << 0) +#define NFC_2_1_CONFIG1_DMA_MODE_POS (1) +#define NFC_2_1_CONFIG1_DMA_MODE_MSK (0x1 << 1) +#define NFC_2_1_CONFIG1_SP_EN_POS (2) +#define NFC_2_1_CONFIG1_SP_EN_MSK (0x1 << 2) +#define NFC_2_1_CONFIG1_ECC_EN_POS (3) +#define NFC_2_1_CONFIG1_ECC_EN_MSK (0x1 << 3) +#define NFC_2_1_CONFIG1_INT_MSK_POS (4) +#define NFC_2_1_CONFIG1_INT_MSK_MSK (0x1 << 4) +#define NFC_2_1_CONFIG1_NF_BIG_POS (5) +#define NFC_2_1_CONFIG1_NF_BIG_MSK (0x1 << 5) +#define NFC_2_1_CONFIG1_NFC_RST_POS (6) +#define NFC_2_1_CONFIG1_NFC_RST_MSK (0x1 << 6) +#define NFC_2_1_CONFIG1_NF_CE_POS (7) +#define NFC_2_1_CONFIG1_NF_CE_MSK (0x1 << 7) +#define NFC_2_1_CONFIG1_SYM_POS (8) +#define NFC_2_1_CONFIG1_SYM_MSK (0x1 << 8) +#define NFC_2_1_CONFIG1_PPB_POS (9) +#define NFC_2_1_CONFIG1_PPB_MSK (0x3 << 9) +#define NFC_2_1_CONFIG1_FP_INT_POS (11) +#define NFC_2_1_CONFIG1_FP_INT_MSK (0x1 << 11) + +#define NFC_2_1_UNLOCK_START_0_REG_OFF (0x20) +#define NFC_2_1_UNLOCK_END_0_REG_OFF (0x22) +#define NFC_2_1_UNLOCK_START_1_REG_OFF (0x24) +#define NFC_2_1_UNLOCK_END_1_REG_OFF (0x26) +#define NFC_2_1_UNLOCK_START_2_REG_OFF (0x28) +#define NFC_2_1_UNLOCK_END_2_REG_OFF (0x2A) +#define NFC_2_1_UNLOCK_START_3_REG_OFF (0x2C) +#define NFC_2_1_UNLOCK_END_3_REG_OFF (0x2E) + +/* +* Macro definitions for version 3.X NFCs. +*/ + +/* +* Macro definitions for version 3.1 NFCs. +*/ + +#define NFC_3_1_FLASH_ADDR_CMD_REG_OFF (0x00) +#define NFC_3_1_CONFIG1_REG_OFF (0x04) +#define NFC_3_1_ECC_STATUS_RESULT_REG_OFF (0x08) +#define NFC_3_1_LAUNCH_NFC_REG_OFF (0x0C) + +#define NFC_3_1_WRPROT_REG_OFF (0x00) +#define NFC_3_1_WRPROT_UNLOCK_BLK_ADD0_REG_OFF (0x04) +#define NFC_3_1_CONFIG2_REG_OFF (0x14) +#define NFC_3_1_IPC_REG_OFF (0x18) + +/* +* Macro definitions for version 3.2 NFCs. +*/ + +#define NFC_3_2_CMD_REG_OFF (0x00) + +#define NFC_3_2_ADD0_REG_OFF (0x04) +#define NFC_3_2_ADD1_REG_OFF (0x08) +#define NFC_3_2_ADD2_REG_OFF (0x0C) +#define NFC_3_2_ADD3_REG_OFF (0x10) +#define NFC_3_2_ADD4_REG_OFF (0x14) +#define NFC_3_2_ADD5_REG_OFF (0x18) +#define NFC_3_2_ADD6_REG_OFF (0x1C) +#define NFC_3_2_ADD7_REG_OFF (0x20) +#define NFC_3_2_ADD8_REG_OFF (0x24) +#define NFC_3_2_ADD9_REG_OFF (0x28) +#define NFC_3_2_ADD10_REG_OFF (0x2C) +#define NFC_3_2_ADD11_REG_OFF (0x30) + +#define NFC_3_2_CONFIG1_REG_OFF (0x34) +#define NFC_3_2_CONFIG1_SP_EN_POS (0) +#define NFC_3_2_CONFIG1_SP_EN_MSK (0x1 << 0) +#define NFC_3_2_CONFIG1_NF_CE_POS (1) +#define NFC_3_2_CONFIG1_NF_CE_MSK (0x1 << 1) +#define NFC_3_2_CONFIG1_RST_POS (2) +#define NFC_3_2_CONFIG1_RST_MSK (0x1 << 2) +#define NFC_3_2_CONFIG1_RBA_POS (4) +#define NFC_3_2_CONFIG1_RBA_MSK (0x7 << 4) +#define NFC_3_2_CONFIG1_ITER_POS (8) +#define NFC_3_2_CONFIG1_ITER_MSK (0xf << 8) +#define NFC_3_2_CONFIG1_CS_POS (12) +#define NFC_3_2_CONFIG1_CS_MSK (0x7 << 12) +#define NFC_3_2_CONFIG1_STATUS_POS (16) +#define NFC_3_2_CONFIG1_STATUS_MSK (0xFFFF<<16) + +#define NFC_3_2_ECC_STATUS_REG_OFF (0x38) +#define NFC_3_2_ECC_STATUS_NOBER1_POS (0) +#define NFC_3_2_ECC_STATUS_NOBER1_MSK (0xF << 0) +#define NFC_3_2_ECC_STATUS_NOBER2_POS (4) +#define NFC_3_2_ECC_STATUS_NOBER2_MSK (0xF << 4) +#define NFC_3_2_ECC_STATUS_NOBER3_POS (8) +#define NFC_3_2_ECC_STATUS_NOBER3_MSK (0xF << 8) +#define NFC_3_2_ECC_STATUS_NOBER4_POS (12) +#define NFC_3_2_ECC_STATUS_NOBER4_MSK (0xF << 12) +#define NFC_3_2_ECC_STATUS_NOBER5_POS (16) +#define NFC_3_2_ECC_STATUS_NOBER5_MSK (0xF << 16) +#define NFC_3_2_ECC_STATUS_NOBER6_POS (20) +#define NFC_3_2_ECC_STATUS_NOBER6_MSK (0xF << 20) +#define NFC_3_2_ECC_STATUS_NOBER7_POS (24) +#define NFC_3_2_ECC_STATUS_NOBER7_MSK (0xF << 24) +#define NFC_3_2_ECC_STATUS_NOBER8_POS (28) +#define NFC_3_2_ECC_STATUS_NOBER8_MSK (0xF << 28) + + +#define NFC_3_2_STATUS_SUM_REG_OFF (0x3C) +#define NFC_3_2_STATUS_SUM_NAND_SUM_POS (0x0) +#define NFC_3_2_STATUS_SUM_NAND_SUM_MSK (0xFF << 0) +#define NFC_3_2_STATUS_SUM_ECC_SUM_POS (8) +#define NFC_3_2_STATUS_SUM_ECC_SUM_MSK (0xFF << 8) + +#define NFC_3_2_LAUNCH_REG_OFF (0x40) +#define NFC_3_2_LAUNCH_FCMD_POS (0) +#define NFC_3_2_LAUNCH_FCMD_MSK (0x1 << 0) +#define NFC_3_2_LAUNCH_FADD_POS (1) +#define NFC_3_2_LAUNCH_FADD_MSK (0x1 << 1) +#define NFC_3_2_LAUNCH_FDI_POS (2) +#define NFC_3_2_LAUNCH_FDI_MSK (0x1 << 2) +#define NFC_3_2_LAUNCH_FDO_POS (3) +#define NFC_3_2_LAUNCH_FDO_MSK (0x7 << 3) +#define NFC_3_2_LAUNCH_AUTO_PROG_POS (6) +#define NFC_3_2_LAUNCH_AUTO_PROG_MSK (0x1 << 6) +#define NFC_3_2_LAUNCH_AUTO_READ_POS (7) +#define NFC_3_2_LAUNCH_AUTO_READ_MSK (0x1 << 7) +#define NFC_3_2_LAUNCH_AUTO_ERASE_POS (9) +#define NFC_3_2_LAUNCH_AUTO_ERASE_MSK (0x1 << 9) +#define NFC_3_2_LAUNCH_COPY_BACK0_POS (10) +#define NFC_3_2_LAUNCH_COPY_BACK0_MSK (0x1 << 10) +#define NFC_3_2_LAUNCH_COPY_BACK1_POS (11) +#define NFC_3_2_LAUNCH_COPY_BACK1_MSK (0x1 << 11) +#define NFC_3_2_LAUNCH_AUTO_STATUS_POS (12) +#define NFC_3_2_LAUNCH_AUTO_STATUS_MSK (0x1 << 12) + +#define NFC_3_2_WRPROT_REG_OFF (0x00) +#define NFC_3_2_WRPROT_WPC_POS (0) +#define NFC_3_2_WRPROT_WPC_MSK (0x7 << 0) +#define NFC_3_2_WRPROT_CS2L_POS (3) +#define NFC_3_2_WRPROT_CS2L_MSK (0x7 << 3) +#define NFC_3_2_WRPROT_BLS_POS (6) +#define NFC_3_2_WRPROT_BLS_MSK (0x3 << 6) +#define NFC_3_2_WRPROT_LTS0_POS (8) +#define NFC_3_2_WRPROT_LTS0_MSK (0x1 << 8) +#define NFC_3_2_WRPROT_LS0_POS (9) +#define NFC_3_2_WRPROT_LS0_MSK (0x1 << 9) +#define NFC_3_2_WRPROT_US0_POS (10) +#define NFC_3_2_WRPROT_US0_MSK (0x1 << 10) +#define NFC_3_2_WRPROT_LTS1_POS (11) +#define NFC_3_2_WRPROT_LTS1_MSK (0x1 << 11) +#define NFC_3_2_WRPROT_LS1_POS (12) +#define NFC_3_2_WRPROT_LS1_MSK (0x1 << 12) +#define NFC_3_2_WRPROT_US1_POS (13) +#define NFC_3_2_WRPROT_US1_MSK (0x1 << 13) +#define NFC_3_2_WRPROT_LTS2_POS (14) +#define NFC_3_2_WRPROT_LTS2_MSK (0x1 << 14) +#define NFC_3_2_WRPROT_LS2_POS (15) +#define NFC_3_2_WRPROT_LS2_MSK (0x1 << 15) +#define NFC_3_2_WRPROT_US2_POS (16) +#define NFC_3_2_WRPROT_US2_MSK (0x1 << 16) +#define NFC_3_2_WRPROT_LTS3_POS (17) +#define NFC_3_2_WRPROT_LTS3_MSK (0x1 << 17) +#define NFC_3_2_WRPROT_LS3_POS (18) +#define NFC_3_2_WRPROT_LS3_MSK (0x1 << 18) +#define NFC_3_2_WRPROT_US3_POS (19) +#define NFC_3_2_WRPROT_US3_MSK (0x1 << 19) +#define NFC_3_2_WRPROT_LTS4_POS (20) +#define NFC_3_2_WRPROT_LTS4_MSK (0x1 << 20) +#define NFC_3_2_WRPROT_LS4_POS (21) +#define NFC_3_2_WRPROT_LS4_MSK (0x1 << 21) +#define NFC_3_2_WRPROT_US4_POS (22) +#define NFC_3_2_WRPROT_US4_MSK (0x1 << 22) +#define NFC_3_2_WRPROT_LTS5_POS (23) +#define NFC_3_2_WRPROT_LTS5_MSK (0x1 << 23) +#define NFC_3_2_WRPROT_LS5_POS (24) +#define NFC_3_2_WRPROT_LS5_MSK (0x1 << 24) +#define NFC_3_2_WRPROT_US5_POS (25) +#define NFC_3_2_WRPROT_US5_MSK (0x1 << 25) +#define NFC_3_2_WRPROT_LTS6_POS (26) +#define NFC_3_2_WRPROT_LTS6_MSK (0x1 << 26) +#define NFC_3_2_WRPROT_LS6_POS (27) +#define NFC_3_2_WRPROT_LS6_MSK (0x1 << 27) +#define NFC_3_2_WRPROT_US6_POS (28) +#define NFC_3_2_WRPROT_US6_MSK (0x1 << 28) +#define NFC_3_2_WRPROT_LTS7_POS (29) +#define NFC_3_2_WRPROT_LTS7_MSK (0x1 << 29) +#define NFC_3_2_WRPROT_LS7_POS (30) +#define NFC_3_2_WRPROT_LS7_MSK (0x1 << 30) +#define NFC_3_2_WRPROT_US7_POS (31) +#define NFC_3_2_WRPROT_US7_MSK (0x1 << 31) + +#define NFC_3_2_UNLOCK_BLK_ADD0_REG_OFF (0x04) +#define NFC_3_2_UNLOCK_BLK_ADD0_USBA0_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD0_USBA0_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD0_UEBA0_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD0_UEBA0_MSK (0xFFFF<<16) + +#define NFC_3_2_UNLOCK_BLK_ADD1_REG_OFF (0x08) +#define NFC_3_2_UNLOCK_BLK_ADD1_USBA1_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD1_USBA1_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD1_UEBA1_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD1_UEBA1_MSK (0xFFFF<<16) + +#define NFC_3_2_UNLOCK_BLK_ADD2_REG_OFF (0x0C) +#define NFC_3_2_UNLOCK_BLK_ADD2_USBA2_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD2_USBA2_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD2_UEBA2_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD2_UEBA2_MSK (0xFFFF<<16) + +#define NFC_3_2_UNLOCK_BLK_ADD3_REG_OFF (0x10) +#define NFC_3_2_UNLOCK_BLK_ADD3_USBA3_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD3_USBA3_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD3_UEBA3_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD3_UEBA3_MSK (0xFFFF<<16) + +#define NFC_3_2_UNLOCK_BLK_ADD4_REG_OFF (0x14) +#define NFC_3_2_UNLOCK_BLK_ADD4_USBA4_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD4_USBA4_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD4_UEBA4_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD4_UEBA4_MSK (0xFFFF<<16) + +#define NFC_3_2_UNLOCK_BLK_ADD5_REG_OFF (0x18) +#define NFC_3_2_UNLOCK_BLK_ADD5_USBA5_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD5_USBA5_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD5_UEBA5_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD5_UEBA5_MSK (0xFFFF<<16) + +#define NFC_3_2_UNLOCK_BLK_ADD6_REG_OFF (0x1C) +#define NFC_3_2_UNLOCK_BLK_ADD6_USBA6_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD6_USBA6_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD6_UEBA6_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD6_UEBA6_MSK (0xFFFF<<16) + +#define NFC_3_2_UNLOCK_BLK_ADD7_REG_OFF (0x20) +#define NFC_3_2_UNLOCK_BLK_ADD7_USBA7_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD7_USBA7_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD7_UEBA7_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD7_UEBA7_MSK (0xFFFF<<16) + +#define NFC_3_2_CONFIG2_REG_OFF (0x24) +#define NFC_3_2_CONFIG2_PS_POS (0) +#define NFC_3_2_CONFIG2_PS_MSK (0x3 << 0) +#define NFC_3_2_CONFIG2_SYM_POS (2) +#define NFC_3_2_CONFIG2_SYM_MSK (0x1 << 2) +#define NFC_3_2_CONFIG2_ECC_EN_POS (3) +#define NFC_3_2_CONFIG2_ECC_EN_MSK (0x1 << 3) +#define NFC_3_2_CONFIG2_CMD_PHASES_POS (4) +#define NFC_3_2_CONFIG2_CMD_PHASES_MSK (0x1 << 4) +#define NFC_3_2_CONFIG2_ADDR_PHASES0_POS (5) +#define NFC_3_2_CONFIG2_ADDR_PHASES0_MSK (0x1 << 5) +#define NFC_3_2_CONFIG2_ECC_MODE_POS (6) +#define NFC_3_2_CONFIG2_ECC_MODE_MSK (0x1 << 6) +#define NFC_3_2_CONFIG2_PPB_POS (7) +#define NFC_3_2_CONFIG2_PPB_MSK (0x3 << 7) +#define NFC_3_2_CONFIG2_EDC_POS (9) +#define NFC_3_2_CONFIG2_EDC_MSK (0x7 << 9) +#define NFC_3_2_CONFIG2_ADDR_PHASES1_POS (12) +#define NFC_3_2_CONFIG2_ADDR_PHASES1_MSK (0x3 << 12) +#define NFC_3_2_CONFIG2_AUTO_DONE_MSK_POS (14) +#define NFC_3_2_CONFIG2_AUTO_DONE_MSK_MSK (0x1 << 14) +#define NFC_3_2_CONFIG2_INT_MSK_POS (15) +#define NFC_3_2_CONFIG2_INT_MSK_MSK (0x1 << 15) +#define NFC_3_2_CONFIG2_SPAS_POS (16) +#define NFC_3_2_CONFIG2_SPAS_MSK (0xFF << 16) +#define NFC_3_2_CONFIG2_ST_CMD_POS (24) +#define NFC_3_2_CONFIG2_ST_CMD_MSK (0xFF << 24) + +#define NFC_3_2_CONFIG3_REG_OFF (0x28) +#define NFC_3_2_CONFIG3_ADD_OP_POS (0) +#define NFC_3_2_CONFIG3_ADD_OP_MSK (0x3 << 0) +#define NFC_3_2_CONFIG3_TOO_POS (2) +#define NFC_3_2_CONFIG3_TOO_MSK (0x1 << 2) +#define NFC_3_2_CONFIG3_FW_POS (3) +#define NFC_3_2_CONFIG3_FW_MSK (0x1 << 3) +#define NFC_3_2_CONFIG3_SB2R_POS (4) +#define NFC_3_2_CONFIG3_SB2R_MSK (0x7 << 4) +#define NFC_3_2_CONFIG3_NF_BIG_POS (7) +#define NFC_3_2_CONFIG3_NF_BIG_MSK (0x1 << 7) +#define NFC_3_2_CONFIG3_SBB_POS (8) +#define NFC_3_2_CONFIG3_SBB_MSK (0x7 << 8) +#define NFC_3_2_CONFIG3_DMA_MODE_POS (11) +#define NFC_3_2_CONFIG3_DMA_MODE_MSK (0x1 << 11) +#define NFC_3_2_CONFIG3_NUM_OF_DEVICES_POS (12) +#define NFC_3_2_CONFIG3_NUM_OF_DEVICES_MSK (0x7 << 12) +#define NFC_3_2_CONFIG3_RBB_MODE_POS (15) +#define NFC_3_2_CONFIG3_RBB_MODE_MSK (0x1 << 15) +#define NFC_3_2_CONFIG3_FMP_POS (16) +#define NFC_3_2_CONFIG3_FMP_MSK (0xF << 16) +#define NFC_3_2_CONFIG3_NO_SDMA_POS (20) +#define NFC_3_2_CONFIG3_NO_SDMA_MSK (0x1 << 20) + +#define NFC_3_2_IPC_REG_OFF (0x2C) +#define NFC_3_2_IPC_CREQ_POS (0) +#define NFC_3_2_IPC_CREQ_MSK (0x1 << 0) +#define NFC_3_2_IPC_CACK_POS (1) +#define NFC_3_2_IPC_CACK_MSK (0x1 << 1) +#define NFC_3_2_IPC_DMA_STATUS_POS (26) +#define NFC_3_2_IPC_DMA_STATUS_MSK (0x3 << 26) +#define NFC_3_2_IPC_RB_B_POS (28) +#define NFC_3_2_IPC_RB_B_MSK (0x1 << 28) +#define NFC_3_2_IPC_LPS_POS (29) +#define NFC_3_2_IPC_LPS_MSK (0x1 << 29) +#define NFC_3_2_IPC_AUTO_PROG_DONE_POS (30) +#define NFC_3_2_IPC_AUTO_PROG_DONE_MSK (0x1 << 30) +#define NFC_3_2_IPC_INT_POS (31) +#define NFC_3_2_IPC_INT_MSK (0x1 << 31) + +#define NFC_3_2_AXI_ERR_ADD_REG_OFF (0x30) + +/** + * enum override - Choices for overrides. + * + * Some functions of this driver can be overriden at run time. This is a + * convenient enumerated type for all such options. + */ + +enum override { + NEVER = -1, + DRIVER_CHOICE = 0, + ALWAYS = 1, +}; + +/** + * struct physical_geometry - Physical geometry description. + * + * This structure describes the physical geometry of the medium. + * + * @chip_count: The number of chips in the medium. + * @chip_size: The size, in bytes, of a single chip (excluding the + * out-of-band bytes). + * @block_size: The size, in bytes, of a single block (excluding the + * out-of-band bytes). + * @page_data_size: The size, in bytes, of the data area in a page (excluding + * the out-of-band bytes). + * @page_oob_size: The size, in bytes, of the out-of-band area in a page. + */ + +struct physical_geometry { + unsigned int chip_count; + uint64_t chip_size; + unsigned int block_size; + unsigned int page_data_size; + unsigned int page_oob_size; +}; + +/** + * struct nfc_geometry - NFC geometry description. + * + * This structure describes the NFC's view of the medium geometry, which may be + * different from the physical geometry (for example, we treat pages that are + * physically 2K+112 as if they are 2K+64). + * + * @page_data_size: The size of the data area in a page (excluding the + * out-of-band bytes). This is almost certain to be the same + * as the physical data size. + * @page_oob_size: The size of the out-of-band area in a page. This may be + * different from the physical OOB size. + * @ecc_algorithm: The name of the ECC algorithm (e.g., "Reed-Solomon" or + * "BCH"). + * @ecc_strength: A number that describes the strength of the ECC algorithm. + * For example, various i.MX SoC's support ECC-1, ECC-4 or + * ECC-8 of the Reed-Solomon ECC algorithm. + * @buffer_count: The number of main/spare buffers used with this geometry. + * @spare_buf_size: The number of bytes held in each spare buffer. + * @spare_buf_spill: The number of extra bytes held in the last spare buffer. + * @mtd_layout: The MTD layout that best matches the geometry described by + * the rest of this structure. The logical layer might not use + * this structure, especially when interleaving. + */ + +struct nfc_geometry { + const unsigned int page_data_size; + const unsigned int page_oob_size; + const char *ecc_algorithm; + const int ecc_strength; + const unsigned int buffer_count; + const unsigned int spare_buf_size; + const unsigned int spare_buf_spill; + struct nand_ecclayout mtd_layout; +}; + +/** + * struct logical_geometry - Logical geometry description. + * + * This structure describes the logical geometry we expose to MTD. This geometry + * may be different from the physical or NFC geometries, especially when + * interleaving. + * + * @chip_count: The number of chips in the medium. + * @chip_size: The size, in bytes, of a single chip (excluding the + * out-of-band bytes). + * @usable_size: The size, in bytes, of the medium that MTD can actually + * use. This may be less than the chip size multiplied by the + * number of chips. + * @block_size: The size, in bytes, of a single block (excluding the + * out-of-band bytes). + * @page_data_size: The size of the data area in a page (excluding the + * out-of-band bytes). + * @page_oob_size: The size of the out-of-band area in a page. + */ + +struct logical_geometry { + unsigned int chip_count; + uint32_t chip_size; + uint32_t usable_size; + unsigned int block_size; + unsigned int page_data_size; + unsigned int page_oob_size; + struct nand_ecclayout *mtd_layout; +}; + +/** + * struct imx_nfc_data - i.MX NFC per-device data. + * + * Note that the "device" managed by this driver represents the NAND Flash + * controller *and* the NAND Flash medium behind it. Thus, the per-device data + * structure has information about the controller, the chips to which it is + * connected, and properties of the medium as a whole. + * + * @dev: A pointer to the owning struct device. + * @pdev: A pointer to the owning struct platform_device. + * @pdata: A pointer to the device's platform data. + * @buffers: A pointer to the NFC buffers. + * @primary_regs: A pointer to the NFC primary registers. + * @secondary_regs: A pointer to the NFC secondary registers. + * @clock: A pointer to the NFC struct clk. + * @interrupt: The NFC interrupt number. + * @physical_geometry: A description of the medium's physical geometry. + * @nfc: A pointer to the NFC HAL. + * @nfc_geometry: A description of the medium geometry as viewed by the + * NFC. + * @done: The struct completion we use to handle interrupts. + * @logical_geometry: A description of the logical geometry exposed to MTD. + * @interrupt_override: Can override how the driver uses interrupts when + * waiting for the NFC. + * @auto_op_override: Can override whether the driver uses automatic NFC + * operations. + * @inject_ecc_error: Indicates that the driver should inject an ECC error in + * the next read operation that uses ECC. User space + * programs can set this value through the sysfs node of + * the same name. If this value is less than zero, the + * driver will inject an uncorrectable ECC error. If this + * value is greater than zero, the driver will inject that + * number of correctable errors, capped at whatever + * possible maximum currently applies. + * @current_chip: The chip currently selected by the NAND Fash MTD HIL. + * A negative value indicates that no chip is selected. + * We use this field to detect when the HIL begins and + * ends essential transactions. This helps us to know when + * we should turn the NFC clock on or off. + * @command: The last command the HIL tried to send by calling + * cmdfunc(). Later functions use this information to + * adjust their behavior. The sentinel value ~0 indicates + * no command. + * @command_is_new: Indicates the command has just come down to cmdfunc() + * from the HIL and hasn't yet been handled. Other + * functions use this to adjust their behavior. + * @page_address: The current page address of interest. For reads, this + * information arrives in calls to cmdfunc(), but we don't + * actually use it until later. + * @nand: The data structure that represents this NAND Flash + * medium to the MTD NAND Flash system. + * @mtd: The data structure that represents this NAND Flash + * medium to MTD. + * @partitions: A pointer to a set of partitions collected from one of + * several possible sources (e.g., the boot loader, the + * kernel command line, etc.). See the global variable + * partition_source_types for the list of partition + * sources we examine. If this member is NULL, then no + * partitions were discovered. + * @partition_count: The number of discovered partitions. + */ + +struct imx_nfc_data { + + /* System Interface */ + struct device *dev; + struct platform_device *pdev; + + /* Platform Configuration */ + struct imx_nfc_platform_data *pdata; + void *buffers; + void *primary_regs; + void *secondary_regs; + struct clk *clock; + unsigned int interrupt; + + /* Flash Hardware */ + struct physical_geometry physical_geometry; + + /* NFC HAL and NFC Utilities */ + struct nfc_hal *nfc; + struct nfc_geometry *nfc_geometry; + struct completion done; + + /* Medium Abstraction Layer */ + struct logical_geometry logical_geometry; + enum override interrupt_override; + enum override auto_op_override; + int inject_ecc_error; + + /* MTD Interface Layer */ + int current_chip; + unsigned int command; + int command_is_new; + int page_address; + + /* NAND Flash MTD */ + struct nand_chip nand; + + /* MTD */ + struct mtd_info mtd; + struct mtd_partition *partitions; + unsigned int partition_count; + +}; + +/** + * struct nfc_hal - i.MX NFC HAL + * + * This structure embodies an abstract interface to the underlying NFC hardware. + * + * @major_version: The major version number of the NFC to which this + * structure applies. + * @minor_version: The minor version number of the NFC to which this + * structure applies. + * @max_chip_count: The maximum number of chips the NFC can possibly + * support. This may *not* be the actual number of chips + * currently connected. This value is constant for NFC's + * of a given version. + * @max_buffer_count: The number of main/spare buffers available in the NFC's + * memory. This value is constant for NFC's of a given + * version. + * @spare_buf_stride: The stride, in bytes, from the beginning of one spare + * buffer to the beginning of the next one. This value is + * constant for NFC's of a given version. + * @has_secondary_regs: Indicates if the NFC has a secondary register set that + * must be mapped in. + * @can_be_symmetric: Indicates if the NFC supports a "symmetric" clock. When + * the clock is "symmetric," the hardware waits one NFC + * clock for every read/write cycle. When the clock is + * "asymmetric," the hardware waits two NFC clocks for + * every read/write cycle. + * @init: Initializes the NFC and any version-specific data + * structures. This function will be called after + * everything has been set up for communication with the + * NFC itself, but before the platform has set up off-chip + * communication. Thus, this function must not attempt to + * communicate with the NAND Flash hardware. A non-zero + * return value indicates failure. + * @set_geometry: Based on the physical geometry, this function selects + * an NFC geometry structure and configures the NFC + * hardware to match. A non-zero return value indicates + * failure. + * @exit: Shuts down the NFC and cleans up version-specific data + * structures. This function will be called after the + * platform has shut down off-chip communication but while + * communication with the NFC still works. + * @set_closest_cycle: Configures the hardware to make the NAND Flash bus + * cycle period as close as possible to the given cycle + * period. This function is called during boot up and may + * assume that, at the time it's called, the parent clock + * is running at the highest rate it will ever run. Thus, + * this function need never worry that the NAND Flash bus + * will run faster and potentially make it impossible to + * communicate with the NAND Flash device -- it will only + * run slower. + * @mask_interrupt: Masks the NFC's interrupt. + * @unmask_interrupt: Unmasks the NFC's interrupt. + * @clear_interrupt: Clears the NFC's interrupt. + * @is_interrupting: Returns true if the NFC is interrupting. + * @is_ready: Returns true if all the chips in the medium are ready. + * This member may be set to NULL, which indicates that + * the underlying NFC hardware doesn't expose ready/busy + * signals. + * @set_force_ce: If passed true, forces the hardware chip enable signal + * for the current chip to be asserted always. If passed + * false, causes the chip enable signal to be asserted + * only during I/O. + * @set_ecc: Sets ECC on or off. + * @get_ecc_status: Examines the hardware ECC status and returns: + * == 0 => No errors. + * > 0 => The number of corrected errors. + * < 0 => There were uncorrectable errors. + * @get_symmetric: Gets the current symmetric clock setting. For versions + * that don't support symmetric clocks, this function + * always returns false. + * @set_symmetric: For versions that support symmetric clocks, sets + * whether or not the clock is symmetric. + * @select_chip: Selects the current chip. + * @command_cycle: Sends a command code and then returns immediately + * *without* waiting for the NFC to finish. + * @write_cycle: Applies a single write cycle to the current chip, + * sending the given byte, and waiting for the NFC to + * finish. + * @read_cycle: Applies a single read cycle to the current chip and + * returns the result, necessarily waiting for the NFC to + * finish. The width of the result is the same as the + * width of the Flash bus. + * @read_page: Applies read cycles to the current chip to read an + * entire page into the NFC. Note that ECC is enabled or + * disabled with the set_ecc function pointer (see above). + * This function waits for the NFC to finish before + * returning. + * @send_page: Applies write cycles to send an entire page from the + * NFC to the current chip. Note that ECC is enabled or + * disabled with the set_ecc function pointer (see above). + * This function waits for the NFC to finish before + * returning. + * @start_auto_read: Starts an automatic read operation. A NULL pointer + * indicates automatic read operations aren't available + * with this NFC version. + * @wait_for_auto_read: Blocks until an automatic read operation is ready for + * the CPU to copy a page out of the NFC. + * @resume_auto_read: Resumes an automatic read operation after the CPU has + * copied a page out. + * @start_auto_write: Starts an automatic write operation. A NULL pointer + * indicates automatic write operations aren't available + * with this NFC version. + * @wait_for_auto_write: Blocks until an automatic write operation is ready for + * the CPU to copy a page into the NFC. + * @start_auto_erase: Starts an automatic erase operation. A NULL pointer + * indicates automatic erase operations aren't available + * with this NFC version. + */ + +struct nfc_hal { + const unsigned int major_version; + const unsigned int minor_version; + const unsigned int max_chip_count; + const unsigned int max_buffer_count; + const unsigned int spare_buf_stride; + const int has_secondary_regs; + const int can_be_symmetric; + int (*init) (struct imx_nfc_data *); + int (*set_geometry) (struct imx_nfc_data *); + void (*exit) (struct imx_nfc_data *); + int (*set_closest_cycle) (struct imx_nfc_data *, unsigned ns); + void (*mask_interrupt) (struct imx_nfc_data *); + void (*unmask_interrupt) (struct imx_nfc_data *); + void (*clear_interrupt) (struct imx_nfc_data *); + int (*is_interrupting) (struct imx_nfc_data *); + int (*is_ready) (struct imx_nfc_data *); + void (*set_force_ce) (struct imx_nfc_data *, int on); + void (*set_ecc) (struct imx_nfc_data *, int on); + int (*get_ecc_status) (struct imx_nfc_data *); + int (*get_symmetric) (struct imx_nfc_data *); + void (*set_symmetric) (struct imx_nfc_data *, int on); + void (*select_chip) (struct imx_nfc_data *, int chip); + void (*command_cycle) (struct imx_nfc_data *, unsigned cmd); + void (*write_cycle) (struct imx_nfc_data *, unsigned byte); + unsigned (*read_cycle) (struct imx_nfc_data *); + void (*read_page) (struct imx_nfc_data *); + void (*send_page) (struct imx_nfc_data *); + int (*start_auto_read) (struct imx_nfc_data *, + unsigned start, unsigned count, unsigned column, unsigned page); + int (*wait_for_auto_read) (struct imx_nfc_data *); + int (*resume_auto_read) (struct imx_nfc_data *); + int (*start_auto_write) (struct imx_nfc_data *, + unsigned start, unsigned count, unsigned column, unsigned page); + int (*wait_for_auto_write)(struct imx_nfc_data *); + int (*start_auto_erase) (struct imx_nfc_data *, + unsigned start, unsigned count, unsigned page); +}; + +/* + * This variable controls whether or not probing is enabled. If false, then + * the driver will refuse to probe. The "enable" module parameter controls the + * value of this variable. + */ + +static int imx_nfc_module_enable = true; + +#ifdef EVENT_REPORTING + +/* + * This variable controls whether the driver reports event information by + * printing to the console. The "report_events" module parameter controls the + * value of this variable. + */ + +static int imx_nfc_module_report_events; /* implicitly initialized false*/ + +#endif + +/* + * This variable potentially overrides the driver's choice to interleave. The + * "interleave_override" module parameter controls the value of this variable. + */ + +static enum override imx_nfc_module_interleave_override = DRIVER_CHOICE; + +/* + * When set, this variable forces the driver to use the bytewise copy functions + * to get data in and out of the NFC. This is intended for testing, not typical + * use. + */ + +static int imx_nfc_module_force_bytewise_copy; /* implicitly initialized false*/ + +/* + * The list of algorithms we use to get partition information from the + * environment. + */ + +#ifdef CONFIG_MTD_PARTITIONS +static const char *partition_source_types[] = { "cmdlinepart", NULL }; +#endif + +/* + * The following structures describe the NFC geometries we support. + * + * Notice that pieces of some structure definitions are commented out and edited + * because various parts of the MTD system can't handle the way our hardware + * formats the out-of-band area. + * + * Here are the problems: + * + * - struct nand_ecclayout expects no more than 64 ECC bytes. + * + * The eccpos member of struct nand_ecclayout can't hold more than 64 ECC + * byte positions. Some of our formats have more so, unedited, they won't + * even compile. We comment out all ECC byte positions after the 64th one. + * + * - struct nand_ecclayout expects no more than 8 free spans. + * + * The oobfree member of struct nand_ecclayout can't hold more than 8 free + * spans. Some of our formats have more so, unedited, they won't even + * compile. We comment out all free spans after the eighth one. + * + * - The MEMGETOOBSEL command in the mtdchar driver. + * + * The mtdchar ioctl command MEMGETOOBSEL checks the number of ECC bytes + * against the length of the eccpos array in struct nand_oobinfo + * (see include/mtd/mtd-abi.h). This array can handle up to 32 ECC bytes, + * but some of our formats have more. + * + * To make this work, we cap the value assigned to eccbytes at 32. + * + * Notice that struct nand_oobinfo, used by mtdchar, is *different* from the + * struct nand_ecclayout that MTD uses internally. The latter structure + * can accomodate up to 64 ECC byte positions. Thus, we declare up to 64 + * ECC byte positions here, even if the advertised number of ECC bytes is + * less. + * + * This command is obsolete and, if no one used it, we wouldn't care about + * this problem. Unfortunately The nandwrite program uses it, and everyone + * expects nandwrite to work (it's how everyone usually lays down their + * JFFS2 file systems). + */ + +static struct nfc_geometry nfc_geometry_512_16_RS_ECC1 = { + .page_data_size = 512, + .page_oob_size = 16, + .ecc_algorithm = "Reed-Solomon", + .ecc_strength = 1, + .buffer_count = 1, + .spare_buf_size = 16, + .spare_buf_spill = 0, + .mtd_layout = { + .eccbytes = 5, + .eccpos = {6, 7, 8, 9, 10}, + .oobavail = 11, + .oobfree = { {0, 6}, {11, 5} }, + } +}; + +static struct nfc_geometry nfc_geometry_512_16_RS_ECC4 = { + .page_data_size = 512, + .page_oob_size = 16, + .ecc_algorithm = "Reed-Solomon", + .ecc_strength = 4, + .buffer_count = 1, + .spare_buf_size = 16, + .spare_buf_spill = 0, + .mtd_layout = { + .eccbytes = 9, + .eccpos = {7, 8, 9, 10, 11, 12, 13, 14, 15}, + .oobavail = 7, + .oobfree = { {0, 7} }, + } +}; + +static struct nfc_geometry nfc_geometry_512_16_BCH_ECC4 = { + .page_data_size = 512, + .page_oob_size = 16, + .ecc_algorithm = "BCH", + .ecc_strength = 4, + .buffer_count = 1, + .spare_buf_size = 16, + .spare_buf_spill = 0, + .mtd_layout = { + .eccbytes = 8, + .eccpos = {8, 9, 10, 11, 12, 13, 14, 15}, + .oobavail = 8, + .oobfree = { {0, 8} }, + } +}; + +static struct nfc_geometry nfc_geometry_2K_64_RS_ECC4 = { + .page_data_size = 2048, + .page_oob_size = 64, + .ecc_algorithm = "Reed-Solomon", + .ecc_strength = 4, + .buffer_count = 4, + .spare_buf_size = 16, + .spare_buf_spill = 0, + .mtd_layout = { + .eccbytes = 32 /*9 * 4*/, /* See notes above. */ + .eccpos = { + + (0*16)+7 , (0*16)+8 , (0*16)+9 , (0*16)+10, (0*16)+11, + (0*16)+12, (0*16)+13, (0*16)+14, (0*16)+15, + + (1*16)+7 , (1*16)+8 , (1*16)+9 , (1*16)+10, (1*16)+11, + (1*16)+12, (1*16)+13, (1*16)+14, (1*16)+15, + + (2*16)+7 , (2*16)+8 , (2*16)+9 , (2*16)+10, (2*16)+11, + (2*16)+12, (2*16)+13, (2*16)+14, (2*16)+15, + + (3*16)+7 , (3*16)+8 , (3*16)+9 , (3*16)+10, (3*16)+11, + (3*16)+12, (3*16)+13, (3*16)+14, (3*16)+15, + + }, + .oobavail = 7 * 4, + .oobfree = { + {(0*16)+0, 7}, + {(1*16)+0, 7}, + {(2*16)+0, 7}, + {(3*16)+0, 7}, + }, + } +}; + +static struct nfc_geometry nfc_geometry_2K_64_BCH_ECC4 = { + .page_data_size = 2048, + .page_oob_size = 64, + .ecc_algorithm = "BCH", + .ecc_strength = 4, + .buffer_count = 4, + .spare_buf_size = 16, + .spare_buf_spill = 0, + .mtd_layout = { + .eccbytes = 8 * 4, + .eccpos = { + + (0*16)+8 , (0*16)+9 , (0*16)+10, (0*16)+11, + (0*16)+12, (0*16)+13, (0*16)+14, (0*16)+15, + + (1*16)+8 , (1*16)+9 , (1*16)+10, (1*16)+11, + (1*16)+12, (1*16)+13, (1*16)+14, (1*16)+15, + + (2*16)+8 , (2*16)+9 , (2*16)+10, (2*16)+11, + (2*16)+12, (2*16)+13, (2*16)+14, (2*16)+15, + + (3*16)+8 , (3*16)+9 , (3*16)+10, (3*16)+11, + (3*16)+12, (3*16)+13, (3*16)+14, (3*16)+15, + + }, + .oobavail = 8 * 4, + .oobfree = { + {(0*16)+0, 8}, + {(1*16)+0, 8}, + {(2*16)+0, 8}, + {(3*16)+0, 8}, + }, + } +}; + +static struct nfc_geometry nfc_geometry_4K_128_BCH_ECC4 = { + .page_data_size = 4096, + .page_oob_size = 128, + .ecc_algorithm = "BCH", + .ecc_strength = 4, + .buffer_count = 8, + .spare_buf_size = 16, + .spare_buf_spill = 0, + .mtd_layout = { + .eccbytes = 8 * 8, + .eccpos = { + + (0*16)+8 , (0*16)+9 , (0*16)+10, (0*16)+11, + (0*16)+12, (0*16)+13, (0*16)+14, (0*16)+15, + + (1*16)+8 , (1*16)+9 , (1*16)+10, (1*16)+11, + (1*16)+12, (1*16)+13, (1*16)+14, (1*16)+15, + + (2*16)+8 , (2*16)+9 , (2*16)+10, (2*16)+11, + (2*16)+12, (2*16)+13, (2*16)+14, (2*16)+15, + + (3*16)+8 , (3*16)+9 , (3*16)+10, (3*16)+11, + (3*16)+12, (3*16)+13, (3*16)+14, (3*16)+15, + + (4*16)+8 , (4*16)+9 , (4*16)+10, (4*16)+11, + (4*16)+12, (4*16)+13, (4*16)+14, (4*16)+15, + + (5*16)+8 , (5*16)+9 , (5*16)+10, (5*16)+11, + (5*16)+12, (5*16)+13, (5*16)+14, (5*16)+15, + + (6*16)+8 , (6*16)+9 , (6*16)+10, (6*16)+11, + (6*16)+12, (6*16)+13, (6*16)+14, (6*16)+15, + + (7*16)+8 , (7*16)+9 , (7*16)+10, (7*16)+11, + (7*16)+12, (7*16)+13, (7*16)+14, (7*16)+15, + + }, + .oobavail = 8 * 8, + .oobfree = { + {(0*16)+0, 8}, + {(1*16)+0, 8}, + {(2*16)+0, 8}, + {(3*16)+0, 8}, + {(4*16)+0, 8}, + {(5*16)+0, 8}, + {(6*16)+0, 8}, + {(7*16)+0, 8}, + }, + } +}; + +static struct nfc_geometry nfc_geometry_4K_218_BCH_ECC8 = { + .page_data_size = 4096, + .page_oob_size = 218, + .ecc_algorithm = "BCH", + .ecc_strength = 8, + .buffer_count = 8, + .spare_buf_size = 26, + .spare_buf_spill = 10, + .mtd_layout = { + .eccbytes = 32 /*10 * 8*/, /* See notes above. */ + .eccpos = { + + (0*26)+12, (0*26)+13, (0*26)+14, (0*26)+15, (0*26)+16, + (0*26)+17, (0*26)+18, (0*26)+19, (0*26)+20, (0*26)+21, + + (1*26)+12, (1*26)+13, (1*26)+14, (1*26)+15, (1*26)+16, + (1*26)+17, (1*26)+18, (1*26)+19, (1*26)+20, (1*26)+21, + + (2*26)+12, (2*26)+13, (2*26)+14, (2*26)+15, (2*26)+16, + (2*26)+17, (2*26)+18, (2*26)+19, (2*26)+20, (2*26)+21, + + (3*26)+12, (3*26)+13, (3*26)+14, (3*26)+15, (3*26)+16, + (3*26)+17, (3*26)+18, (3*26)+19, (3*26)+20, (3*26)+21, + + (4*26)+12, (4*26)+13, (4*26)+14, (4*26)+15, (4*26)+16, + (4*26)+17, (4*26)+18, (4*26)+19, (4*26)+20, (4*26)+21, + + (5*26)+12, (5*26)+13, (5*26)+14, (5*26)+15, (5*26)+16, + (5*26)+17, (5*26)+18, (5*26)+19, (5*26)+20, (5*26)+21, + + (6*26)+12, (6*26)+13, (6*26)+14, (6*26)+15, + /* See notes above. + (6*26)+16, + (6*26)+17, (6*26)+18, (6*26)+19, (6*26)+20, (6*26)+21, + + (7*26)+12, (7*26)+13, (7*26)+14, (7*26)+15, (7*26)+16, + (7*26)+17, (7*26)+18, (7*26)+19, (7*26)+20, (7*26)+21, + */ + + }, + .oobavail = 96 /*(16 * 8) + 10*/, /* See notes above. */ + .oobfree = { + {(0*26)+0, 12}, {(0*26)+22, 4}, + {(1*26)+0, 12}, {(1*26)+22, 4}, + {(2*26)+0, 12}, {(2*26)+22, 4}, + {(3*26)+0, 48}, /* See notes above. */ + /* See notes above. + {(3*26)+0, 12}, {(3*26)+22, 4}, + {(4*26)+0, 12}, {(4*26)+22, 4}, + {(5*26)+0, 12}, {(5*26)+22, 4}, + {(6*26)+0, 12}, {(6*26)+22, 4}, + {(7*26)+0, 12}, {(7*26)+22, 4 + 10}, + */ + }, + } +}; + +/* + * When the logical geometry differs from the NFC geometry (e.g., + * interleaving), we synthesize a layout rather than use the one that comes with + * the NFC geometry. See mal_set_logical_geometry(). + */ + +static struct nand_ecclayout synthetic_layout; + +/* + * These structures describe how the BBT code will find block marks in the OOB + * area of a page. Don't be confused by the fact that this is the same type used + * to describe bad block tables. Some of the same information is needed, so the + * designers use the same structure for two conceptually distinct functions. + */ + +static uint8_t block_mark_pattern[] = { 0xff, 0xff }; + +static struct nand_bbt_descr small_page_block_mark_descriptor = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 5, + .len = 1, + .pattern = block_mark_pattern, +}; + +static struct nand_bbt_descr large_page_block_mark_descriptor = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 0, + .len = 2, + .pattern = block_mark_pattern, +}; + +/* + * Bad block table descriptors for the main and mirror tables. + */ + +static uint8_t bbt_main_pattern[] = { 'B', 'b', 't', '0' }; +static uint8_t bbt_mirror_pattern[] = { '1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descriptor = { + .options = + NAND_BBT_LASTBLOCK | + NAND_BBT_CREATE | + NAND_BBT_WRITE | + NAND_BBT_2BIT | + NAND_BBT_VERSION | + NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = bbt_main_pattern, +}; + +static struct nand_bbt_descr bbt_mirror_descriptor = { + .options = + NAND_BBT_LASTBLOCK | + NAND_BBT_CREATE | + NAND_BBT_WRITE | + NAND_BBT_2BIT | + NAND_BBT_VERSION | + NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = bbt_mirror_pattern, +}; + +#ifdef EVENT_REPORTING + +/** + * struct event - A single record in the event trace. + * + * @time: The time at which the event occurred. + * @nesting: Indicates function call nesting. + * @description: A description of the event. + */ + +struct event { + ktime_t time; + unsigned int nesting; + char *description; +}; + +/** + * The event trace. + * + * @overhead: The delay to take a time stamp and nothing else. + * @nesting: The current nesting level. + * @overflow: Indicates the trace overflowed. + * @next: Index of the next event to write. + * @events: The array of events. + */ + +#define MAX_EVENT_COUNT (200) + +static struct { + ktime_t overhead; + int nesting; + int overflow; + unsigned int next; + struct event events[MAX_EVENT_COUNT]; +} event_trace; + +/** + * reset_event_trace() - Resets the event trace. + */ +static void reset_event_trace(void) +{ + event_trace.nesting = 0; + event_trace.overflow = false; + event_trace.next = 0; +} + +/** + * add_event() - Adds an event to the event trace. + * + * @description: A description of the event. + * @delta: A delta to the nesting level for this event [-1, 0, 1]. + */ +static inline void add_event(char *description, int delta) +{ + struct event *event; + + if (!imx_nfc_module_report_events) + return; + + if (event_trace.overflow) + return; + + if (event_trace.next >= MAX_EVENT_COUNT) { + event_trace.overflow = true; + return; + } + + event = event_trace.events + event_trace.next; + + event->time = ktime_get(); + + event->description = description; + + if (!delta) + event->nesting = event_trace.nesting; + else if (delta < 0) { + event->nesting = event_trace.nesting - 1; + event_trace.nesting -= 2; + } else { + event->nesting = event_trace.nesting + 1; + event_trace.nesting += 2; + } + + if (event_trace.nesting < 0) + event_trace.nesting = 0; + + event_trace.next++; + +} + +/** + * add_state_event_l() - Adds an event to display some state. + * + * @address: The address to examine. + * @mask: A mask to apply to the contents of the given address. + * @clear: An event message to add if the result is zero. + * @not_zero: An event message to add if the result is not zero. + */ +static void add_state_event_l(void *address, uint32_t mask, + char *zero, char *not_zero) +{ + int state; + state = !!(__raw_readl(address) & mask); + if (state) + add_event(not_zero, 0); + else + add_event(zero, 0); +} + +/** + * start_event_trace() - Starts an event trace and adds the first event. + * + * @description: A description of the first event. + */ +static void start_event_trace(char *description) +{ + + ktime_t t0; + ktime_t t1; + + if (!imx_nfc_module_report_events) + return; + + reset_event_trace(); + + t0 = ktime_get(); + t1 = ktime_get(); + + event_trace.overhead = ktime_sub(t1, t0); + + add_event(description, 1); + +} + +/** + * dump_event_trace() - Dumps the event trace. + */ +static void dump_event_trace(void) +{ + unsigned int i; + time_t seconds; + long nanoseconds; + char line[100]; + int o; + struct event *first_event; + struct event *last_event; + struct event *matching_event; + struct event *event; + ktime_t delta; + + /* Check if event reporting is turned off. */ + + if (!imx_nfc_module_report_events) + return; + + /* Print important facts about this event trace. */ + + printk(KERN_DEBUG "\n+--------------\n"); + + printk(KERN_DEBUG "| Overhead : [%d:%d]\n", + event_trace.overhead.tv.sec, + event_trace.overhead.tv.nsec); + + if (!event_trace.next) { + printk(KERN_DEBUG "| No Events\n"); + return; + } + + first_event = event_trace.events; + last_event = event_trace.events + (event_trace.next - 1); + + delta = ktime_sub(last_event->time, first_event->time); + printk(KERN_DEBUG "| Elapsed Time: [%d:%d]\n", + delta.tv.sec, delta.tv.nsec); + + if (event_trace.overflow) + printk(KERN_DEBUG "| Overflow!\n"); + + /* Print the events in this history. */ + + for (i = 0, event = event_trace.events; + i < event_trace.next; i++, event++) { + + /* Get the delta between this event and the previous event. */ + + if (!i) { + seconds = 0; + nanoseconds = 0; + } else { + delta = ktime_sub(event[0].time, event[-1].time); + seconds = delta.tv.sec; + nanoseconds = delta.tv.nsec; + } + + /* Print the current event. */ + + o = 0; + + o = snprintf(line, sizeof(line) - o, "| [%ld:% 10ld]%*s %s", + seconds, nanoseconds, + event->nesting, "", + event->description); + /* Check if this is the last event in a nested series. */ + + if (i && (event[0].nesting < event[-1].nesting)) { + + for (matching_event = event - 1;; matching_event--) { + + if (matching_event < event_trace.events) { + matching_event = 0; + break; + } + + if (matching_event->nesting == event->nesting) + break; + + } + + if (matching_event) { + delta = ktime_sub(event->time, + matching_event->time); + o += snprintf(line + o, sizeof(line) - o, + " <%d:%d]", delta.tv.sec, + delta.tv.nsec); + } + + } + + /* Check if this is the first event in a nested series. */ + + if ((i < event_trace.next - 1) && + (event[0].nesting < event[1].nesting)) { + + for (matching_event = event + 1;; matching_event++) { + + if (matching_event >= + (event_trace.events+event_trace.next)) { + matching_event = 0; + break; + } + + if (matching_event->nesting == event->nesting) + break; + + } + + if (matching_event) { + delta = ktime_sub(matching_event->time, + event->time); + o += snprintf(line + o, sizeof(line) - o, + " [%d:%d>", delta.tv.sec, + delta.tv.nsec); + } + + } + + printk(KERN_DEBUG "%s\n", line); + + } + + printk(KERN_DEBUG "+--------------\n"); + +} + +/** + * stop_event_trace() - Stops an event trace. + * + * @description: A description of the last event. + */ +static void stop_event_trace(char *description) +{ + struct event *event; + + if (!imx_nfc_module_report_events) + return; + + /* + * We want the end of the trace, no matter what happens. If the trace + * has already overflowed, or is about to, just jam this event into the + * last spot. Otherwise, add this event like any other. + */ + + if (event_trace.overflow || (event_trace.next >= MAX_EVENT_COUNT)) { + event = event_trace.events + (MAX_EVENT_COUNT - 1); + event->time = ktime_get(); + event->description = description; + event->nesting = 0; + } else { + add_event(description, -1); + } + + dump_event_trace(); + reset_event_trace(); + +} + +#else /* EVENT_REPORTING */ + +#define start_event_trace(description) do {} while (0) +#define add_event(description, delta) do {} while (0) +#define add_state_event_l(address, mask, zero, not_zero) do {} while (0) +#define stop_event_trace(description) do {} while (0) +#define dump_event_trace() do {} while (0) + +#endif /* EVENT_REPORTING */ + +/** + * unimplemented() - Announces intentionally unimplemented features. + * + * @this: Per-device data. + * @msg: A message about the unimplemented feature. + */ +static inline void unimplemented(struct imx_nfc_data *this, const char * msg) +{ + dev_err(this->dev, "Intentionally unimplemented: %s", msg); +} + +/** + * raw_read_mask_w() - Reads masked bits in a 16-bit hardware register. + */ +static inline uint16_t raw_read_mask_w(uint16_t mask, void *address) +{ + return __raw_readw(address) & mask; +} + +/** + * raw_set_mask_w() - Sets bits in a 16-bit hardware register. + */ +static inline void raw_set_mask_w(uint16_t mask, void *address) +{ + __raw_writew(__raw_readw(address) | mask, address); +} + +/** + * raw_clr_mask_w() - Clears bits in a 16-bit hardware register. + */ +static inline void raw_clr_mask_w(uint16_t mask, void *address) +{ + __raw_writew(__raw_readw(address) & (~mask), address); +} + +/** + * raw_read_mask_l() - Reads masked bits in a 32-bit hardware register. + */ +static inline uint32_t raw_read_mask_l(uint32_t mask, void *address) +{ + return __raw_readl(address) & mask; +} + +/** + * raw_set_mask_l() - Sets bits in a 32-bit hardware register. + */ +static inline void raw_set_mask_l(uint32_t mask, void *address) +{ + __raw_writel(__raw_readl(address) | mask, address); +} + +/** + * raw_clr_mask_l() - Clears bits in a 32-bit hardware register. + */ +static inline void raw_clr_mask_l(uint32_t mask, void *address) +{ + __raw_writel(__raw_readl(address) & (~mask), address); +} + +/** + * is_large_page_chip() - Returns true for large page media. + * + * @this: Per-device data. + */ +static inline int is_large_page_chip(struct imx_nfc_data *this) +{ + return (this->physical_geometry.page_data_size > 512); +} + +/** + * is_small_page_chip() - Returns true for small page media. + * + * @this: Per-device data. + */ +static inline int is_small_page_chip(struct imx_nfc_data *this) +{ + return !is_large_page_chip(this); +} + +/** + * get_cycle_in_ns() - Returns the given device's cycle period, in ns. + * + * @this: Per-device data. + */ +static inline unsigned get_cycle_in_ns(struct imx_nfc_data *this) +{ + unsigned long cycle_in_ns; + + cycle_in_ns = 1000000000 / clk_get_rate(this->clock); + + if (!this->nfc->get_symmetric(this)) + cycle_in_ns *= 2; + + return cycle_in_ns; + +} + +/** + * nfc_util_set_best_cycle() - Sets the closest possible NAND Flash bus cycle. + * + * This function computes the clock setup that will best approximate the given + * target Flash bus cycle period. + * + * For some NFC versions, we can make the clock "symmetric." When the clock + * is "symmetric," the hardware waits one NFC clock for every read/write cycle. + * When the clock is "asymmetric," the hardware waits two NFC clocks for every + * read/write cycle. Thus, making the clock asymmetric essentially divides the + * NFC clock by two. + * + * We compute the target frequency that matches the given target period. We then + * discover the closest available match with that frequency and the closest + * available match with double that frequency (for use with an asymmetric + * clock). We implement the best choice of original clock and symmetric or + * asymmetric setting, preferring symmetric clocks. + * + * @this: Per-device data. + * @ns: The target cycle period, in nanoseconds. + * @no_asym: Disallow making the clock asymmetric. + * @no_sym: Disallow making the clock symmetric. + */ +static int nfc_util_set_best_cycle(struct imx_nfc_data *this, + unsigned int ns, int no_asym, int no_sym) +{ + unsigned long target_hz; + long symmetric_hz; + long symmetric_delta_hz; + long asymmetric_hz; + long asymmetric_delta_hz; + unsigned long best_hz; + int best_symmetry_setting; + struct device *dev = this->dev; + + /* The target cycle period must be greater than zero. */ + + if (!ns) + return -EINVAL; + + /* Compute the target frequency. */ + + target_hz = 1000000000 / ns; + + /* Find out how close we can get with a symmetric clock. */ + + if (!no_sym && this->nfc->can_be_symmetric) + symmetric_hz = clk_round_rate(this->clock, target_hz); + else + symmetric_hz = -EINVAL; + + /* Find out how close we can get with an asymmetric clock. */ + + if (!no_asym) + asymmetric_hz = clk_round_rate(this->clock, target_hz * 2); + else + asymmetric_hz = -EINVAL; + + /* Does anything work at all? */ + + if ((symmetric_hz == -EINVAL) && (asymmetric_hz == -EINVAL)) { + dev_err(dev, "Can't support Flash bus cycle of %uns\n", ns); + return -EINVAL; + } + + /* Discover the best match. */ + + if ((symmetric_hz != -EINVAL) && (asymmetric_hz != -EINVAL)) { + + symmetric_delta_hz = target_hz - symmetric_hz; + asymmetric_delta_hz = target_hz - (asymmetric_hz / 2); + + if (symmetric_delta_hz <= asymmetric_delta_hz) + best_symmetry_setting = true; + else + best_symmetry_setting = false; + + } else if (symmetric_hz != -EINVAL) { + best_symmetry_setting = true; + } else { + best_symmetry_setting = false; + } + + best_hz = best_symmetry_setting ? symmetric_hz : asymmetric_hz; + + /* Implement the best match. */ + + this->nfc->set_symmetric(this, best_symmetry_setting); + + return clk_set_rate(this->clock, best_hz); + +} + +/** + * nfc_util_wait_for_the_nfc() - Waits for the NFC to finish an operation. + * + * @this: Per-device data. + * @use_irq: Indicates that we should wait for an interrupt rather than polling + * and delaying. + */ +static void nfc_util_wait_for_the_nfc(struct imx_nfc_data *this, int use_irq) +{ + unsigned spin_count; + struct device *dev = this->dev; + + /* Apply the override, if any. */ + + switch (this->interrupt_override) { + + case NEVER: + use_irq = false; + break; + + case DRIVER_CHOICE: + break; + + case ALWAYS: + use_irq = true; + break; + + } + + /* Check if we're using interrupts. */ + + if (use_irq) { + + /* + * If control arrives here, the caller wants to use interrupts. + * Presumably, this operation is known to take a very long time. + */ + + if (this->nfc->is_interrupting(this)) { + add_event("Waiting for the NFC (early interrupt)", 1); + this->nfc->clear_interrupt(this); + } else { + add_event("Waiting for the NFC (interrupt)", 1); + this->nfc->unmask_interrupt(this); + wait_for_completion(&this->done); + } + + add_event("NFC done", -1); + + } else { + + /* + * If control arrives here, the caller doesn't want to use + * interrupts. Presumably, this operation is too quick to + * justify the overhead. Leave the interrupt masked, and loop + * until the interrupt bit lights up, or we time out. + * + * We spin for a maximum of about 2ms before declaring a time + * out. No operation we could possibly spin on should take that + * long. + */ + + spin_count = 2000; + + add_event("Waiting for the NFC (polling)", 1); + + for (; spin_count > 0; spin_count--) { + + if (this->nfc->is_interrupting(this)) { + this->nfc->clear_interrupt(this); + add_event("NFC done", -1); + return; + } + + udelay(1); + + } + + /* Timed out. */ + + add_event("Timed out", -1); + + dev_err(dev, "[wait_for_the_nfc] ===== Time Out =====\n"); + dump_event_trace(); + + } + +} + +/** + * nfc_util_bytewise_copy_from_nfc_mem() - Copies bytes from the NFC memory. + * + * @from: A pointer to the source memory. + * @to: A pointer to the destination memory. + * @size: The number of bytes to copy. + */ +static void nfc_util_bytewise_copy_from_nfc_mem(const void *from, + void *to, size_t n) +{ + unsigned int i; + const uint8_t *f = from; + uint8_t *t = to; + uint16_t *p; + uint16_t x; + + for (i = 0; i < n; i++, f++, t++) { + + p = (uint16_t *) (((unsigned long) f) & ~((unsigned long) 1)); + + x = __raw_readw(p); + + if (((unsigned long) f) & 0x1) + *t = (x >> 8) & 0xff; + else + *t = (x >> 0) & 0xff; + + } + +} + +/** + * nfc_util_bytewise_copy_to_nfc_mem() - Copies bytes to the NFC memory. + * + * @from: A pointer to the source memory. + * @to: A pointer to the destination memory. + * @size: The number of bytes to copy. + */ +static void nfc_util_bytewise_copy_to_nfc_mem(const void *from, + void *to, size_t n) +{ + unsigned int i; + const uint8_t *f = from; + uint8_t *t = to; + uint16_t *p; + uint16_t x; + + for (i = 0; i < n; i++, f++, t++) { + + p = (uint16_t *) (((unsigned long) t) & ~((unsigned long) 1)); + + x = __raw_readw(p); + + if (((unsigned long) t) & 0x1) + ((uint8_t *)(&x))[1] = *f; + else + ((uint8_t *)(&x))[0] = *f; + + __raw_writew(x, p); + + } + +} + +/** + * nfc_util_copy_from_nfc_mem() - Copies from the NFC memory to main memory. + * + * @from: A pointer to the source memory. + * @to: A pointer to the destination memory. + * @size: The number of bytes to copy. + */ +static void nfc_util_copy_from_nfc_mem(const void *from, void *to, size_t n) +{ + unsigned int chunk_count; + + /* + * Check if we're testing bytewise copies. + */ + + if (imx_nfc_module_force_bytewise_copy) + goto force_bytewise_copy; + + /* + * We'd like to use memcpy to get data out of the NFC but, because that + * memory responds only to 16- and 32-byte reads, we can only do so + * safely if both the start and end of both the source and destination + * are perfectly aligned on 4-byte boundaries. + */ + + if (!(((unsigned long) from) & 0x3) && !(((unsigned long) to) & 0x3)) { + + /* + * If control arrives here, both the source and destination are + * aligned on 4-byte boundaries. Compute the number of whole, + * 4-byte chunks we can move. + */ + + chunk_count = n / 4; + + /* + * Move all the chunks we can, and then update the pointers and + * byte count to show what's left. + */ + + if (chunk_count) { + memcpy(to, from, chunk_count * 4); + from += chunk_count * 4; + to += chunk_count * 4; + n -= chunk_count * 4; + } + + } + + /* + * Move what's left. + */ + +force_bytewise_copy: + + nfc_util_bytewise_copy_from_nfc_mem(from, to, n); + +} + +/** + * nfc_util_copy_to_nfc_mem() - Copies from main memory to the NFC memory. + * + * @from: A pointer to the source memory. + * @to: A pointer to the destination memory. + * @size: The number of bytes to copy. + */ +static void nfc_util_copy_to_nfc_mem(const void *from, void *to, size_t n) +{ + unsigned int chunk_count; + + /* + * Check if we're testing bytewise copies. + */ + + if (imx_nfc_module_force_bytewise_copy) + goto force_bytewise_copy; + + /* + * We'd like to use memcpy to get data into the NFC but, because that + * memory responds only to 16- and 32-byte writes, we can only do so + * safely if both the start and end of both the source and destination + * are perfectly aligned on 4-byte boundaries. + */ + + if (!(((unsigned long) from) & 0x3) && !(((unsigned long) to) & 0x3)) { + + /* + * If control arrives here, both the source and destination are + * aligned on 4-byte boundaries. Compute the number of whole, + * 4-byte chunks we can move. + */ + + chunk_count = n / 4; + + /* + * Move all the chunks we can, and then update the pointers and + * byte count to show what's left. + */ + + if (chunk_count) { + memcpy(to, from, chunk_count * 4); + from += chunk_count * 4; + to += chunk_count * 4; + n -= chunk_count * 4; + } + + } + + /* + * Move what's left. + */ + +force_bytewise_copy: + + nfc_util_bytewise_copy_to_nfc_mem(from, to, n); + +} + +/** + * nfc_util_copy_from_the_nfc() - Copies bytes out of the NFC. + * + * This function makes the data in the NFC look like a contiguous, model page. + * + * @this: Per-device data. + * @start: The index of the starting byte in the NFC. + * @buf: A pointer to the destination buffer. + * @len: The number of bytes to copy out. + */ +static void nfc_util_copy_from_the_nfc(struct imx_nfc_data *this, + unsigned int start, uint8_t *buf, unsigned int len) +{ + unsigned int i; + unsigned int count; + unsigned int offset; + unsigned int data_size; + unsigned int oob_size; + unsigned int total_size; + void *spare_base; + unsigned int first_spare; + void *from; + struct nfc_geometry *geometry = this->nfc_geometry; + + /* + * During initialization, the HIL will attempt to read ID bytes. For + * some NFC hardware versions, the ID bytes are deposited in the NFC + * memory, so this function will be called to deliver them. At that + * point, we won't know the NFC geometry. That's OK because we're only + * going to be reading a byte at a time. + * + * If we don't yet know the NFC geometry, just plug in some values that + * make things work for now. + */ + + if (unlikely(!geometry)) { + data_size = NFC_MAIN_BUF_SIZE; + oob_size = 0; + } else { + data_size = geometry->page_data_size; + oob_size = geometry->page_oob_size; + } + + total_size = data_size + oob_size; + + /* Validate. */ + + if ((start >= total_size) || ((start + len) > total_size)) { + dev_err(this->dev, "Bad copy from NFC memory: [%u, %u]\n", + start, len); + return; + } + + /* Check if we're copying anything at all. */ + + if (!len) + return; + + /* Check if anything comes from the main area. */ + + if (start < data_size) { + + /* Compute the bytes to copy from the main area. */ + + count = min(len, data_size - start); + + /* Copy. */ + + nfc_util_copy_from_nfc_mem(this->buffers + start, buf, count); + + buf += count; + start += count; + len -= count; + + } + + /* Check if we're done. */ + + if (!len) + return; + + /* Compute the base address of the spare buffers. */ + + spare_base = this->buffers + + (this->nfc->max_buffer_count * NFC_MAIN_BUF_SIZE); + + /* Discover in which spare buffer the copying begins. */ + + first_spare = (start - data_size) / geometry->spare_buf_size; + + /* Check if anything comes from the regular spare buffer area. */ + + if (first_spare < geometry->buffer_count) { + + /* Start copying from spare buffers. */ + + for (i = first_spare; i < geometry->buffer_count; i++) { + + /* Compute the offset into this spare area. */ + + offset = start - + (data_size + (geometry->spare_buf_size * i)); + + /* Compute the address of that offset. */ + + from = spare_base + offset + + (this->nfc->spare_buf_stride * i); + + /* Compute the bytes to copy from this spare area. */ + + count = min(len, geometry->spare_buf_size - offset); + + /* Copy. */ + + nfc_util_copy_from_nfc_mem(from, buf, count); + + buf += count; + start += count; + len -= count; + + } + + } + + /* Check if we're done. */ + + if (!len) + return; + + /* Compute the offset into the extra spare area. */ + + offset = start - + (data_size + (geometry->spare_buf_size*geometry->buffer_count)); + + /* Compute the address of that offset. */ + + from = spare_base + offset + + (this->nfc->spare_buf_stride * geometry->buffer_count); + + /* Compute the bytes to copy from the extra spare area. */ + + count = min(len, geometry->spare_buf_spill - offset); + + /* Copy. */ + + nfc_util_copy_from_nfc_mem(from, buf, count); + +} + +/** + * nfc_util_copy_to_the_nfc() - Copies bytes into the NFC memory. + * + * This function makes the data in the NFC look like a contiguous, model page. + * + * @this: Per-device data. + * @buf: A pointer to the source buffer. + * @start: The index of the starting byte in the NFC memory. + * @len: The number of bytes to copy in. + */ +static void nfc_util_copy_to_the_nfc(struct imx_nfc_data *this, + const uint8_t *buf, unsigned int start, unsigned int len) +{ + unsigned int i; + unsigned int count; + unsigned int offset; + unsigned int data_size; + unsigned int oob_size; + unsigned int total_size; + void *spare_base; + unsigned int first_spare; + void *to; + struct nfc_geometry *geometry = this->nfc_geometry; + + /* Establish some important facts. */ + + data_size = geometry->page_data_size; + oob_size = geometry->page_oob_size; + total_size = data_size + oob_size; + + /* Validate. */ + + if ((start >= total_size) || ((start + len) > total_size)) { + dev_err(this->dev, "Bad copy to NFC memory: [%u, %u]\n", + start, len); + return; + } + + /* Check if we're copying anything at all. */ + + if (!len) + return; + + /* Check if anything goes to the main area. */ + + if (start < data_size) { + + /* Compute the bytes to copy to the main area. */ + + count = min(len, data_size - start); + + /* Copy. */ + + nfc_util_copy_to_nfc_mem(buf, this->buffers + start, count); + + buf += count; + start += count; + len -= count; + + } + + /* Check if we're done. */ + + if (!len) + return; + + /* Compute the base address of the spare buffers. */ + + spare_base = this->buffers + + (this->nfc->max_buffer_count * NFC_MAIN_BUF_SIZE); + + /* Discover in which spare buffer the copying begins. */ + + first_spare = (start - data_size) / geometry->spare_buf_size; + + /* Check if anything goes to the regular spare buffer area. */ + + if (first_spare < geometry->buffer_count) { + + /* Start copying to spare buffers. */ + + for (i = first_spare; i < geometry->buffer_count; i++) { + + /* Compute the offset into this spare area. */ + + offset = start - + (data_size + (geometry->spare_buf_size * i)); + + /* Compute the address of that offset. */ + + to = spare_base + offset + + (this->nfc->spare_buf_stride * i); + + /* Compute the bytes to copy to this spare area. */ + + count = min(len, geometry->spare_buf_size - offset); + + /* Copy. */ + + nfc_util_copy_to_nfc_mem(buf, to, count); + + buf += count; + start += count; + len -= count; + + } + + } + + /* Check if we're done. */ + + if (!len) + return; + + /* Compute the offset into the extra spare area. */ + + offset = start - + (data_size + (geometry->spare_buf_size*geometry->buffer_count)); + + /* Compute the address of that offset. */ + + to = spare_base + offset + + (this->nfc->spare_buf_stride * geometry->buffer_count); + + /* Compute the bytes to copy to the extra spare area. */ + + count = min(len, geometry->spare_buf_spill - offset); + + /* Copy. */ + + nfc_util_copy_to_nfc_mem(buf, to, count); + +} + +/** + * nfc_util_isr() - i.MX NFC ISR. + * + * @irq: The arriving interrupt number. + * @context: A cookie for this ISR. + */ +static irqreturn_t nfc_util_isr(int irq, void *cookie) +{ + struct imx_nfc_data *this = cookie; + this->nfc->mask_interrupt(this); + this->nfc->clear_interrupt(this); + complete(&this->done); + return IRQ_HANDLED; +} + +/** + * nfc_util_send_cmd() - Sends a command to the current chip, without waiting. + * + * @this: Per-device data. + * @command: The command code. + */ + +static void nfc_util_send_cmd(struct imx_nfc_data *this, unsigned int command) +{ + + add_event("Entering nfc_util_send_cmd", 1); + + this->nfc->command_cycle(this, command); + + add_event("Exiting nfc_util_send_cmd", -1); + +} + +/** + * nfc_util_send_cmd_and_addrs() - Sends a cmd and addrs to the current chip. + * + * This function conveniently combines sending a command, and then sending + * optional addresses, waiting for the NFC to finish will all steps. + * + * @this: Per-device data. + * @command: The command code. + * @column: The column address to send, or -1 if no column address applies. + * @page: The page address to send, or -1 if no page address applies. + */ + +static void nfc_util_send_cmd_and_addrs(struct imx_nfc_data *this, + unsigned command, int column, int page) +{ + uint32_t page_mask; + + add_event("Entering nfc_util_send_cmd_and_addrs", 1); + + /* Send the command.*/ + + add_event("Sending the command...", 0); + + nfc_util_send_cmd(this, command); + + nfc_util_wait_for_the_nfc(this, false); + + /* Send the addresses. */ + + add_event("Sending the addresses...", 0); + + if (column != -1) { + + this->nfc->write_cycle(this, (column >> 0) & 0xff); + + if (is_large_page_chip(this)) + this->nfc->write_cycle(this, (column >> 8) & 0xff); + + } + + if (page != -1) { + + page_mask = this->nand.pagemask; + + do { + this->nfc->write_cycle(this, page & 0xff); + page_mask >>= 8; + page >>= 8; + } while (page_mask != 0); + + } + + add_event("Exiting nfc_util_send_cmd_and_addrs", -1); + +} + +/** + * nfc_2_x_exit() - Version-specific shut down. + * + * @this: Per-device data. + */ +static void nfc_2_x_exit(struct imx_nfc_data *this) +{ +} + +/** + * nfc_2_x_clear_interrupt() - Clears an interrupt. + * + * @this: Per-device data. + */ +static void nfc_2_x_clear_interrupt(struct imx_nfc_data *this) +{ + void *base = this->primary_regs; + raw_clr_mask_w(NFC_2_X_CONFIG2_INT_MSK, base + NFC_2_X_CONFIG2_REG_OFF); +} + +/** + * nfc_2_x_is_interrupting() - Returns the interrupt bit status. + * + * @this: Per-device data. + */ +static int nfc_2_x_is_interrupting(struct imx_nfc_data *this) +{ + void *base = this->primary_regs; + return raw_read_mask_w(NFC_2_X_CONFIG2_INT_MSK, + base + NFC_2_X_CONFIG2_REG_OFF); +} + +/** + * nfc_2_x_command_cycle() - Sends a command. + * + * @this: Per-device data. + * @command: The command code. + */ +static void nfc_2_x_command_cycle(struct imx_nfc_data *this, unsigned command) +{ + void *base = this->primary_regs; + + /* Write the command we want to send. */ + + __raw_writew(command, base + NFC_2_X_FLASH_CMD_REG_OFF); + + /* Launch a command cycle. */ + + __raw_writew(NFC_2_X_CONFIG2_FCMD_MSK, base + NFC_2_X_CONFIG2_REG_OFF); + +} + +/** + * nfc_2_x_write_cycle() - Writes a single byte. + * + * @this: Per-device data. + * @byte: The byte. + */ +static void nfc_2_x_write_cycle(struct imx_nfc_data *this, unsigned int byte) +{ + void *base = this->primary_regs; + + /* Give the NFC the byte we want to write. */ + + __raw_writew(byte, base + NFC_2_X_FLASH_ADDR_REG_OFF); + + /* Launch an address cycle. + * + * This is *sort* of a hack, but not really. The intent of the NFC + * design is for this operation to send an address byte. In fact, the + * NFC neither knows nor cares what we're sending. It justs runs a write + * cycle. + */ + + __raw_writew(NFC_2_X_CONFIG2_FADD_MSK, base + NFC_2_X_CONFIG2_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, false); + +} + +/** + * nfc_2_0_init() - Version-specific hardware initialization. + * + * @this: Per-device data. + */ +static int nfc_2_0_init(struct imx_nfc_data *this) +{ + void *base = this->primary_regs; + + /* Initialize the interrupt machinery. */ + + this->nfc->mask_interrupt(this); + this->nfc->clear_interrupt(this); + + /* Unlock the NFC memory. */ + + __raw_writew(0x2, base + NFC_2_X_CONFIG_REG_OFF); + + /* Set the unlocked block range to cover the entire medium. */ + + __raw_writew(0 , base + NFC_2_0_UNLOCK_START_REG_OFF); + __raw_writew(~0, base + NFC_2_0_UNLOCK_END_REG_OFF); + + /* Unlock all blocks. */ + + __raw_writew(0x4, base + NFC_2_X_WR_PROT_REG_OFF); + + /* Return success. */ + + return 0; + +} + +/** + * nfc_2_0_mask_interrupt() - Masks interrupts. + * + * @this: Per-device data. + */ +static void nfc_2_0_mask_interrupt(struct imx_nfc_data *this) +{ + void *base = this->primary_regs; + raw_set_mask_w(NFC_2_0_CONFIG1_INT_MSK_MSK, + base + NFC_2_0_CONFIG1_REG_OFF); +} + +/** + * nfc_2_0_unmask_interrupt() - Unmasks interrupts. + * + * @this: Per-device data. + */ +static void nfc_2_0_unmask_interrupt(struct imx_nfc_data *this) +{ + void *base = this->primary_regs; + raw_clr_mask_w(NFC_2_0_CONFIG1_INT_MSK_MSK, + base + NFC_2_0_CONFIG1_REG_OFF); +} + +/** + * nfc_2_0_set_ecc() - Turns ECC on or off. + * + * @this: Per-device data. + * @on: Indicates if ECC should be on or off. + */ +static void nfc_2_0_set_ecc(struct imx_nfc_data *this, int on) +{ + void *base = this->primary_regs; + + if (on) + raw_set_mask_w(NFC_2_0_CONFIG1_ECC_EN_MSK, + base + NFC_2_0_CONFIG1_REG_OFF); + else + raw_clr_mask_w(NFC_2_0_CONFIG1_ECC_EN_MSK, + base + NFC_2_0_CONFIG1_REG_OFF); + +} + +/** + * nfc_2_0_get_ecc_status() - Reports ECC errors. + * + * @this: Per-device data. + */ +static int nfc_2_0_get_ecc_status(struct imx_nfc_data *this) +{ + unsigned int i; + void *base = this->primary_regs; + uint16_t status_reg; + unsigned int buffer_status[4]; + int status; + + /* Get the entire status register. */ + + status_reg = __raw_readw(base + NFC_2_0_ECC_STATUS_REG_OFF); + + /* Pick out the status for each buffer. */ + + buffer_status[0] = (status_reg & NFC_2_0_ECC_STATUS_NOSER1_MSK) + >> NFC_2_0_ECC_STATUS_NOSER1_POS; + + buffer_status[1] = (status_reg & NFC_2_0_ECC_STATUS_NOSER2_MSK) + >> NFC_2_0_ECC_STATUS_NOSER2_POS; + + buffer_status[2] = (status_reg & NFC_2_0_ECC_STATUS_NOSER3_MSK) + >> NFC_2_0_ECC_STATUS_NOSER3_POS; + + buffer_status[3] = (status_reg & NFC_2_0_ECC_STATUS_NOSER4_MSK) + >> NFC_2_0_ECC_STATUS_NOSER4_POS; + + /* Loop through the buffers we're actually using. */ + + status = 0; + + for (i = 0; i < this->nfc_geometry->buffer_count; i++) { + + if (buffer_status[i] > this->nfc_geometry->ecc_strength) { + status = -1; + break; + } + + status += buffer_status[i]; + + } + + /* Return the final result. */ + + return status; + +} + +/** + * nfc_2_0_get_symmetric() - Indicates if the clock is symmetric. + * + * @this: Per-device data. + */ +static int nfc_2_0_get_symmetric(struct imx_nfc_data *this) +{ + void *base = this->primary_regs; + + return !!raw_read_mask_w(NFC_2_0_CONFIG1_ONE_CYLE_MSK, + base + NFC_2_0_CONFIG1_REG_OFF); + +} + +/** + * nfc_2_0_set_symmetric() - Turns symmetric clock mode on or off. + * + * @this: Per-device data. + */ +static void nfc_2_0_set_symmetric(struct imx_nfc_data *this, int on) +{ + void *base = this->primary_regs; + + if (on) + raw_set_mask_w(NFC_2_0_CONFIG1_ONE_CYLE_MSK, + base + NFC_2_0_CONFIG1_REG_OFF); + else + raw_clr_mask_w(NFC_2_0_CONFIG1_ONE_CYLE_MSK, + base + NFC_2_0_CONFIG1_REG_OFF); + +} + +/** + * nfc_2_0_set_geometry() - Configures for the medium geometry. + * + * @this: Per-device data. + */ +static int nfc_2_0_set_geometry(struct imx_nfc_data *this) +{ + struct physical_geometry *physical = &this->physical_geometry; + + /* Select an NFC geometry. */ + + switch (physical->page_data_size) { + + case 512: + this->nfc_geometry = &nfc_geometry_512_16_RS_ECC4; + break; + + case 2048: + this->nfc_geometry = &nfc_geometry_2K_64_RS_ECC4; + break; + + default: + dev_err(this->dev, "NFC can't handle page size: %u", + physical->page_data_size); + return !0; + break; + + } + + /* + * This NFC version receives page size information from a register + * that's external to the NFC. We must rely on platform-specific code + * to set this register for us. + */ + + return this->pdata->set_page_size(physical->page_data_size); + +} + +/** + * nfc_2_0_select_chip() - Selects the current chip. + * + * @this: Per-device data. + * @chip: The chip number to select, or -1 to select no chip. + */ +static void nfc_2_0_select_chip(struct imx_nfc_data *this, int chip) +{ +} + +/** + * nfc_2_0_read_cycle() - Applies a single read cycle to the current chip. + * + * @this: Per-device data. + */ +static unsigned int nfc_2_0_read_cycle(struct imx_nfc_data *this) +{ + uint8_t byte; + unsigned int result; + void *base = this->primary_regs; + + /* Read into main buffer 0. */ + + __raw_writew(0x0, base + NFC_2_0_BUF_ADDR_REG_OFF); + + /* Launch a "Data Out" operation. */ + + __raw_writew(0x4 << NFC_2_X_CONFIG2_FDO_POS, + base + NFC_2_X_CONFIG2_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, false); + + /* Get the result from the NFC memory. */ + + nfc_util_copy_from_the_nfc(this, 0, &byte, 1); + result = byte; + + /* Return the results. */ + + return result; + +} + +/** + * nfc_2_0_read_page() - Reads a page from the current chip into the NFC. + * + * @this: Per-device data. + */ +static void nfc_2_0_read_page(struct imx_nfc_data *this) +{ + unsigned int i; + void *base = this->primary_regs; + + /* Loop over the number of buffers in use. */ + + for (i = 0; i < this->nfc_geometry->buffer_count; i++) { + + /* Make the NFC read into the current buffer. */ + + __raw_writew(i << NFC_2_0_BUF_ADDR_RBA_POS, + base + NFC_2_0_BUF_ADDR_REG_OFF); + + /* Launch a page data out operation. */ + + __raw_writew(0x1 << NFC_2_X_CONFIG2_FDO_POS, + base + NFC_2_X_CONFIG2_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, true); + + } + +} + +/** + * nfc_2_0_send_page() - Sends a page from the NFC to the current chip. + * + * @this: Per-device data. + */ +static void nfc_2_0_send_page(struct imx_nfc_data *this) +{ + unsigned int i; + void *base = this->primary_regs; + + /* Loop over the number of buffers in use. */ + + for (i = 0; i < this->nfc_geometry->buffer_count; i++) { + + /* Make the NFC send from the current buffer. */ + + __raw_writew(i << NFC_2_0_BUF_ADDR_RBA_POS, + base + NFC_2_0_BUF_ADDR_REG_OFF); + + /* Launch a page data in operation. */ + + __raw_writew(0x1 << NFC_2_X_CONFIG2_FDI_POS, + base + NFC_2_X_CONFIG2_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, true); + + } + +} + +/** + * nfc_3_2_init() - Hardware initialization. + * + * @this: Per-device data. + */ +static int nfc_3_2_init(struct imx_nfc_data *this) +{ + int error; + unsigned int no_sdma; + unsigned int fmp; + unsigned int rbb_mode; + unsigned int num_of_devices; + unsigned int dma_mode; + unsigned int sbb; + unsigned int nf_big; + unsigned int sb2r; + unsigned int fw; + unsigned int too; + unsigned int add_op; + uint32_t config3; + void *primary_base = this->primary_regs; + void *secondary_base = this->secondary_regs; + + /* Initialize the interrupt machinery. */ + + this->nfc->mask_interrupt(this); + this->nfc->clear_interrupt(this); + + /* Set up the clock. */ + + error = this->nfc->set_closest_cycle(this, + this->pdata->target_cycle_in_ns); + + if (error) + return !0; + + /* We never read the spare area alone. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_SP_EN_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Tell the NFC the "Read Status" command code. */ + + raw_clr_mask_l(NFC_3_2_CONFIG2_ST_CMD_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + + raw_set_mask_l(NAND_CMD_STATUS << NFC_3_2_CONFIG2_ST_CMD_POS, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + + /* + * According to erratum ENGcm09051, the CONFIG3 register doesn't reset + * correctly, so we need to re-build the entire register just in case. + */ + + /* + * Set the NO_SDMA bit to tell the NFC that we are NOT using SDMA. If + * you clear this bit (to indicates you *are* using SDMA), but you + * don't actually set up SDMA, the NFC has been observed to crash the + * hardware when it asserts its DMA request signals. In the future, we + * *may* use SDMA, but it's not worth the effort at this writing. + */ + + no_sdma = 0x1; + + /* + * Set the default FIFO Mode Protection (128 bytes). FMP doesn't work if + * the NO_SDMA bit is set. + */ + + fmp = 0x2; + + /* + * The rbb_mode bit determines how the NFC figures out whether chips are + * ready during automatic operations only (this has no effect on atomic + * operations). The two choices are either to monitor the ready/busy + * signals, or to read the status register. We monitor the ready/busy + * signals. + */ + + rbb_mode = 0x1; + + /* + * We don't yet know how many devices are connected. We'll find out in + * out in nfc_3_2_set_geometry(). + */ + + num_of_devices = 0; + + /* Set the default DMA mode. */ + + dma_mode = 0x0; + + /* Set the default status busy bit. */ + + sbb = 0x6; + + /* Little-endian (the default). */ + + nf_big = 0x0; + + /* Set the default (standard) status bit to record. */ + + sb2r = 0x0; + + /* We support only 8-bit Flash bus width. */ + + fw = 0x1; + + /* We don't support "two-on-one." */ + + too = 0x0; + + /* Set the addressing option. */ + + add_op = 0x3; + + /* Set the CONFIG3 register. */ + + config3 = 0; + + config3 |= no_sdma << NFC_3_2_CONFIG3_NO_SDMA_POS; + config3 |= fmp << NFC_3_2_CONFIG3_FMP_POS; + config3 |= rbb_mode << NFC_3_2_CONFIG3_RBB_MODE_POS; + config3 |= num_of_devices << NFC_3_2_CONFIG3_NUM_OF_DEVICES_POS; + config3 |= dma_mode << NFC_3_2_CONFIG3_DMA_MODE_POS; + config3 |= sbb << NFC_3_2_CONFIG3_SBB_POS; + config3 |= nf_big << NFC_3_2_CONFIG3_NF_BIG_POS; + config3 |= sb2r << NFC_3_2_CONFIG3_SB2R_POS; + config3 |= fw << NFC_3_2_CONFIG3_FW_POS; + config3 |= too << NFC_3_2_CONFIG3_TOO_POS; + config3 |= add_op << NFC_3_2_CONFIG3_ADD_OP_POS; + + __raw_writel(config3, secondary_base + NFC_3_2_CONFIG3_REG_OFF); + + /* Return success. */ + + return 0; + +} + +/** + * nfc_3_2_set_geometry() - Configures for the medium geometry. + * + * @this: Per-device data. + */ +static int nfc_3_2_set_geometry(struct imx_nfc_data *this) +{ + unsigned int ps; + unsigned int cmd_phases; + unsigned int pages_per_chip; + unsigned int addr_phases0; + unsigned int addr_phases1; + unsigned int pages_per_block; + unsigned int ecc_mode; + unsigned int ppb; + unsigned int spas; + unsigned int mask; + uint32_t config2; + unsigned int num_of_devices; + uint32_t config3; + unsigned int x; + unsigned int chip; + struct physical_geometry *physical = &this->physical_geometry; + void *secondary_base = this->secondary_regs; + + /* + * Select an NFC geometry based on the physical geometry and the + * capabilities of this NFC. + */ + + switch (physical->page_data_size) { + + case 512: + this->nfc_geometry = &nfc_geometry_512_16_BCH_ECC4; + ps = 0; + break; + + case 2048: + this->nfc_geometry = &nfc_geometry_2K_64_BCH_ECC4; + ps = 1; + break; + + case 4096: + + switch (this->physical_geometry.page_oob_size) { + + case 128: + this->nfc_geometry = &nfc_geometry_4K_128_BCH_ECC4; + break; + + case 218: + this->nfc_geometry = &nfc_geometry_4K_218_BCH_ECC8; + break; + + default: + dev_err(this->dev, + "NFC can't handle page geometry: %u+%u", + physical->page_data_size, + physical->page_oob_size); + return !0; + break; + + } + + ps = 2; + + break; + + default: + dev_err(this->dev, "NFC can't handle page size: %u", + physical->page_data_size); + return !0; + break; + + } + + /* Compute the ECC mode. */ + + switch (this->nfc_geometry->ecc_strength) { + + case 4: + ecc_mode = 0; + break; + + case 8: + ecc_mode = 1; + break; + + default: + dev_err(this->dev, "NFC can't handle ECC strength: %u", + this->nfc_geometry->ecc_strength); + return !0; + break; + + } + + /* Compute the pages per block. */ + + pages_per_block = physical->block_size / physical->page_data_size; + + switch (pages_per_block) { + case 32: + ppb = 0; + break; + case 64: + ppb = 1; + break; + case 128: + ppb = 2; + break; + case 256: + ppb = 3; + break; + default: + dev_err(this->dev, "NFC can't handle pages per block: %d", + pages_per_block); + return !0; + break; + } + + /* + * The hardware needs to know the physical size of the spare area, in + * units of half-words (16 bits). This may be different from the amount + * of the spare area we actually expose to MTD. For example, for for + * 2K+112, we only expose 64 spare bytes, but the hardware needs to know + * the real facts. + */ + + spas = this->physical_geometry.page_oob_size >> 1; + + /* + * The number of command phases needed to read a page is directly + * dependent on whether this is a small page or large page device. Large + * page devices need more address phases, terminated by a second command + * phase. + */ + + cmd_phases = is_large_page_chip(this) ? 1 : 0; + + /* + * The num_adr_phases1 field contains the number of phases needed to + * transmit addresses for read and program operations. This is the sum + * of the number of phases for a page address and the number of phases + * for a column address. + * + * The number of phases for a page address is the number of bytes needed + * to contain a page address. + * + * The number of phases for a column address is the number of bytes + * needed to contain a column address. + * + * After computing the sum, we subtract three because a value of zero in + * this field indicates three address phases, and this is the minimum + * number of phases the hardware can comprehend. + * + * We compute the number of phases based on the *physical* geometry, not + * the NFC geometry. For example, even if we are treating a very large + * device as if it contains fewer pages than it actually does, the + * hardware still needs the additional address phases. + */ + + pages_per_chip = + physical->chip_size >> (fls(physical->page_data_size) - 1); + + addr_phases1 = (fls(pages_per_chip) >> 3) + 1; + + addr_phases1 += (fls(physical->page_data_size) >> 3) + 1; + + addr_phases1 -= 3; + + /* + * The num_adr_phases0 field contains the number of phases needed to + * transmit a page address for an erase operation. That is, this is + * the value of addr_phases1, less the number of phases for the column + * address. + * + * The hardware expresses this phase count as one or two cycles less + * than the count indicated by add_phases1 (see the reference manual). + */ + + addr_phases0 = is_large_page_chip(this) ? 1 : 0; + + /* Set the CONFIG2 register. */ + + mask = + NFC_3_2_CONFIG2_PS_MSK | + NFC_3_2_CONFIG2_CMD_PHASES_MSK | + NFC_3_2_CONFIG2_ADDR_PHASES0_MSK | + NFC_3_2_CONFIG2_ECC_MODE_MSK | + NFC_3_2_CONFIG2_PPB_MSK | + NFC_3_2_CONFIG2_ADDR_PHASES1_MSK | + NFC_3_2_CONFIG2_SPAS_MSK ; + + config2 = __raw_readl(secondary_base + NFC_3_2_CONFIG2_REG_OFF); + + config2 &= ~mask; + + config2 |= ps << NFC_3_2_CONFIG2_PS_POS; + config2 |= cmd_phases << NFC_3_2_CONFIG2_CMD_PHASES_POS; + config2 |= addr_phases0 << NFC_3_2_CONFIG2_ADDR_PHASES0_POS; + config2 |= ecc_mode << NFC_3_2_CONFIG2_ECC_MODE_POS; + config2 |= ppb << NFC_3_2_CONFIG2_PPB_POS; + config2 |= addr_phases1 << NFC_3_2_CONFIG2_ADDR_PHASES1_POS; + config2 |= spas << NFC_3_2_CONFIG2_SPAS_POS; + + config2 = __raw_writel(config2, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + + /* + * Compute the num_of_devices field. + * + * It's very important to set this field correctly. This controls the + * set of ready/busy lines to which the NFC listens with automatic + * transactions. If this number is too large, the NFC will listen to + * ready/busy signals that are electrically floating, or it will try to + * read the status registers of chips that don't exist. Conversely, if + * the number is too small, the NFC could believe an operation is + * finished when some chips are still busy. + */ + + num_of_devices = physical->chip_count - 1; + + /* Set the CONFIG3 register. */ + + mask = NFC_3_2_CONFIG3_NUM_OF_DEVICES_MSK; + + config3 = __raw_readl(secondary_base + NFC_3_2_CONFIG3_REG_OFF); + + config3 &= ~mask; + + config3 |= num_of_devices << NFC_3_2_CONFIG3_NUM_OF_DEVICES_POS; + + __raw_writel(config3, secondary_base + NFC_3_2_CONFIG3_REG_OFF); + + /* + * Check if the physical chip count is a power of 2. If not, then + * automatic operations aren't available. This is because we use an + * addressing option (see the ADD_OP field of CONFIG3) that requires + * a number of chips that is a power of 2. + */ + + if (ffs(physical->chip_count) != fls(physical->chip_count)) { + this->nfc->start_auto_read = 0; + this->nfc->start_auto_write = 0; + this->nfc->start_auto_erase = 0; + } + + /* Unlock the NFC RAM. */ + + x = __raw_readl(secondary_base + NFC_3_2_WRPROT_REG_OFF); + x &= ~NFC_3_2_WRPROT_BLS_MSK; + x |= 0x2 << NFC_3_2_WRPROT_BLS_POS; + __raw_writel(x, secondary_base + NFC_3_2_WRPROT_REG_OFF); + + /* Loop over chip selects, setting the unlocked ranges. */ + + for (chip = 0; chip < this->nfc->max_chip_count; chip++) { + + /* Set the unlocked range to cover the entire chip.*/ + + __raw_writel(0xffff0000, secondary_base + + NFC_3_2_UNLOCK_BLK_ADD0_REG_OFF + (chip * 4)); + + /* Unlock. */ + + x = __raw_readl(secondary_base + NFC_3_2_WRPROT_REG_OFF); + x &= ~(NFC_3_2_WRPROT_CS2L_MSK | NFC_3_2_WRPROT_WPC_MSK); + x |= chip << NFC_3_2_WRPROT_CS2L_POS; + x |= 0x4 << NFC_3_2_WRPROT_WPC_POS ; + __raw_writel(x, secondary_base + NFC_3_2_WRPROT_REG_OFF); + + } + + /* Return success. */ + + return 0; + +} + +/** + * nfc_3_2_exit() - Hardware cleanup. + * + * @this: Per-device data. + */ +static void nfc_3_2_exit(struct imx_nfc_data *this) +{ +} + +/** + * nfc_3_2_set_closest_cycle() - Version-specific hardware cleanup. + * + * @this: Per-device data. + */ +static int nfc_3_2_set_closest_cycle(struct imx_nfc_data *this, unsigned ns) +{ + struct clk *parent_clock; + unsigned long parent_clock_rate_in_hz; + unsigned long sym_low_clock_rate_in_hz; + unsigned long asym_low_clock_rate_in_hz; + unsigned int sym_high_cycle_in_ns; + unsigned int asym_high_cycle_in_ns; + + /* + * According to ENGcm09121: + * + * - If the NFC is set to SYMMETRIC mode, the NFC clock divider must + * divide the EMI Slow Clock by NO MORE THAN 4. + * + * - If the NFC is set for ASYMMETRIC mode, the NFC clock divider must + * divide the EMI Slow Clock by NO MORE THAN 3. + * + * We need to compute the corresponding cycle time constraints. Start + * by getting information about the parent clock. + */ + + parent_clock = clk_get_parent(this->clock); + parent_clock_rate_in_hz = clk_get_rate(parent_clock); + + /* Compute the limit frequencies. */ + + sym_low_clock_rate_in_hz = parent_clock_rate_in_hz / 4; + asym_low_clock_rate_in_hz = parent_clock_rate_in_hz / 3; + + /* Compute the corresponding limit cycle periods. */ + + sym_high_cycle_in_ns = 1000000000 / sym_low_clock_rate_in_hz; + asym_high_cycle_in_ns = (1000000000 / asym_low_clock_rate_in_hz) * 2; + + /* Attempt to implement the given cycle. */ + + return nfc_util_set_best_cycle(this, ns, + ns > asym_high_cycle_in_ns, ns > sym_high_cycle_in_ns); + +} + +/** + * nfc_3_2_mask_interrupt() - Masks interrupts. + * + * @this: Per-device data. + */ +static void nfc_3_2_mask_interrupt(struct imx_nfc_data *this) +{ + void *secondary_base = this->secondary_regs; + raw_set_mask_l(NFC_3_2_CONFIG2_INT_MSK_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); +} + +/** + * nfc_3_2_unmask_interrupt() - Unmasks interrupts. + * + * @this: Per-device data. + */ +static void nfc_3_2_unmask_interrupt(struct imx_nfc_data *this) +{ + void *secondary_base = this->secondary_regs; + raw_clr_mask_l(NFC_3_2_CONFIG2_INT_MSK_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); +} + +/** + * nfc_3_2_clear_interrupt() - Clears an interrupt. + * + * @this: Per-device data. + */ +static void nfc_3_2_clear_interrupt(struct imx_nfc_data *this) +{ + int done; + void *secondary_base = this->secondary_regs; + + /* Request IP bus interface access. */ + + raw_set_mask_l(NFC_3_2_IPC_CREQ_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + + /* Wait for access. */ + + do + done = !!raw_read_mask_l(NFC_3_2_IPC_CACK_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + while (!done); + + /* Clear the interrupt. */ + + raw_clr_mask_l(NFC_3_2_IPC_INT_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + + /* Release the IP bus interface. */ + + raw_clr_mask_l(NFC_3_2_IPC_CREQ_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + +} + +/** + * nfc_3_2_is_interrupting() - Returns the interrupt bit status. + * + * @this: Per-device data. + */ +static int nfc_3_2_is_interrupting(struct imx_nfc_data *this) +{ + void *secondary_base = this->secondary_regs; + return !!raw_read_mask_l(NFC_3_2_IPC_INT_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); +} + +/** + * nfc_3_2_is_ready() - Returns the ready/busy status. + * + * @this: Per-device data. + */ +static int nfc_3_2_is_ready(struct imx_nfc_data *this) +{ + void *secondary_base = this->secondary_regs; + return !!raw_read_mask_l(NFC_3_2_IPC_RB_B_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); +} + +/** + * nfc_3_2_set_force_ce() - Can force CE to be asserted always. + * + * @this: Per-device data. + * @on: Indicates if the hardware CE signal should be asserted always. + */ +static void nfc_3_2_set_force_ce(struct imx_nfc_data *this, int on) +{ + void *primary_base = this->primary_regs; + + if (on) + raw_set_mask_l(NFC_3_2_CONFIG1_NF_CE_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + else + raw_clr_mask_l(NFC_3_2_CONFIG1_NF_CE_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + +} + +/** + * nfc_3_2_set_ecc() - Turns ECC on or off. + * + * @this: Per-device data. + * @on: Indicates if ECC should be on or off. + */ +static void nfc_3_2_set_ecc(struct imx_nfc_data *this, int on) +{ + void *secondary_base = this->secondary_regs; + + if (on) + raw_set_mask_l(NFC_3_2_CONFIG2_ECC_EN_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + else + raw_clr_mask_l(NFC_3_2_CONFIG2_ECC_EN_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + +} + +/** + * nfc_3_2_get_ecc_status() - Reports ECC errors. + * + * @this: Per-device data. + */ +static int nfc_3_2_get_ecc_status(struct imx_nfc_data *this) +{ + unsigned int i; + void *base = this->primary_regs; + uint16_t status_reg; + unsigned int buffer_status[8]; + int status; + + /* Get the entire status register. */ + + status_reg = __raw_readw(base + NFC_3_2_ECC_STATUS_REG_OFF); + + /* Pick out the status for each buffer. */ + + buffer_status[0] = (status_reg & NFC_3_2_ECC_STATUS_NOBER1_MSK) + >> NFC_3_2_ECC_STATUS_NOBER1_POS; + + buffer_status[1] = (status_reg & NFC_3_2_ECC_STATUS_NOBER2_MSK) + >> NFC_3_2_ECC_STATUS_NOBER2_POS; + + buffer_status[2] = (status_reg & NFC_3_2_ECC_STATUS_NOBER3_MSK) + >> NFC_3_2_ECC_STATUS_NOBER3_POS; + + buffer_status[3] = (status_reg & NFC_3_2_ECC_STATUS_NOBER4_MSK) + >> NFC_3_2_ECC_STATUS_NOBER4_POS; + + buffer_status[4] = (status_reg & NFC_3_2_ECC_STATUS_NOBER5_MSK) + >> NFC_3_2_ECC_STATUS_NOBER5_POS; + + buffer_status[5] = (status_reg & NFC_3_2_ECC_STATUS_NOBER6_MSK) + >> NFC_3_2_ECC_STATUS_NOBER6_POS; + + buffer_status[6] = (status_reg & NFC_3_2_ECC_STATUS_NOBER7_MSK) + >> NFC_3_2_ECC_STATUS_NOBER7_POS; + + buffer_status[7] = (status_reg & NFC_3_2_ECC_STATUS_NOBER8_MSK) + >> NFC_3_2_ECC_STATUS_NOBER8_POS; + + /* Loop through the buffers we're actually using. */ + + status = 0; + + for (i = 0; i < this->nfc_geometry->buffer_count; i++) { + + if (buffer_status[i] > this->nfc_geometry->ecc_strength) { + status = -1; + break; + } + + status += buffer_status[i]; + + } + + /* Return the final result. */ + + return status; + +} + +/** + * nfc_3_2_get_symmetric() - Indicates if the clock is symmetric. + * + * @this: Per-device data. + */ +static int nfc_3_2_get_symmetric(struct imx_nfc_data *this) +{ + void *secondary_base = this->secondary_regs; + + return !!raw_read_mask_w(NFC_3_2_CONFIG2_SYM_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + +} + +/** + * nfc_3_2_set_symmetric() - Turns symmetric clock mode on or off. + * + * @this: Per-device data. + */ +static void nfc_3_2_set_symmetric(struct imx_nfc_data *this, int on) +{ + void *secondary_base = this->secondary_regs; + + if (on) + raw_set_mask_l(NFC_3_2_CONFIG2_SYM_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + else + raw_clr_mask_l(NFC_3_2_CONFIG2_SYM_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + +} + +/** + * nfc_3_2_select_chip() - Selects the current chip. + * + * @this: Per-device data. + * @chip: The chip number to select, or -1 to select no chip. + */ +static void nfc_3_2_select_chip(struct imx_nfc_data *this, int chip) +{ + unsigned long x; + void *primary_base = this->primary_regs; + + if (chip < 0) + return; + + x = __raw_readl(primary_base + NFC_3_2_CONFIG1_REG_OFF); + + x &= ~NFC_3_2_CONFIG1_CS_MSK; + + x |= (chip << NFC_3_2_CONFIG1_CS_POS) & NFC_3_2_CONFIG1_CS_MSK; + + __raw_writel(x, primary_base + NFC_3_2_CONFIG1_REG_OFF); + +} + +/** + * nfc_3_2_command_cycle() - Sends a command. + * + * @this: Per-device data. + * @command: The command code. + */ +static void nfc_3_2_command_cycle(struct imx_nfc_data *this, unsigned command) +{ + void *primary_base = this->primary_regs; + + /* Write the command we want to send. */ + + __raw_writel(command, primary_base + NFC_3_2_CMD_REG_OFF); + + /* Launch a command cycle. */ + + __raw_writel(NFC_3_2_LAUNCH_FCMD_MSK, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + +} + +/** + * nfc_3_2_write_cycle() - writes a single byte. + * + * @this: Per-device data. + * @byte: The byte. + */ +static void nfc_3_2_write_cycle(struct imx_nfc_data *this, unsigned int byte) +{ + void *primary_base = this->primary_regs; + + /* Give the NFC the byte we want to write. */ + + __raw_writel(byte, primary_base + NFC_3_2_ADD0_REG_OFF); + + /* Launch an address cycle. + * + * This is *sort* of a hack, but not really. The intent of the NFC + * design is for this operation to send an address byte. In fact, the + * NFC neither knows nor cares what we're sending. It justs runs a write + * cycle. + */ + + __raw_writel(NFC_3_2_LAUNCH_FADD_MSK, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, false); + +} + +/** + * nfc_3_2_read_cycle() - Applies a single read cycle to the current chip. + * + * @this: Per-device data. + */ +static unsigned int nfc_3_2_read_cycle(struct imx_nfc_data *this) +{ + unsigned int result; + void *primary_base = this->primary_regs; + + /* Launch a "Data Out" operation. */ + + __raw_writel(0x4 << NFC_3_2_LAUNCH_FDO_POS, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, false); + + /* Get the result. */ + + result = __raw_readl(primary_base + NFC_3_2_CONFIG1_REG_OFF) + >> NFC_3_2_CONFIG1_STATUS_POS; + result &= 0xff; + + /* Return the result. */ + + return result; + +} + +/** + * nfc_3_2_read_page() - Reads a page into the NFC memory. + * + * @this: Per-device data. + */ +static void nfc_3_2_read_page(struct imx_nfc_data *this) +{ + void *primary_base = this->primary_regs; + + /* Start reading into buffer 0. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_RBA_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Launch a page data out operation. */ + + __raw_writel(0x1 << NFC_3_2_LAUNCH_FDO_POS, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, true); + +} + +/** + * nfc_3_2_send_page() - Sends a page from the NFC to the current chip. + * + * @this: Per-device data. + */ +static void nfc_3_2_send_page(struct imx_nfc_data *this) +{ + void *primary_base = this->primary_regs; + + /* Start sending from buffer 0. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_RBA_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Launch a page data in operation. */ + + __raw_writel(NFC_3_2_LAUNCH_FDI_MSK, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, true); + +} + +/** + * nfc_3_2_add_state_events() - Adds events to display important state. + * + * @this: Per-device data. + */ +static void nfc_3_2_add_state_events(struct imx_nfc_data *this) +{ +#ifdef EVENT_REPORTING + void *secondary_base = this->secondary_regs; + + add_state_event_l + ( + secondary_base + NFC_3_2_IPC_REG_OFF, + NFC_3_2_IPC_INT_MSK, + " Interrupt : 0", + " Interrupt : X" + ); + + add_state_event_l + ( + secondary_base + NFC_3_2_IPC_REG_OFF, + NFC_3_2_IPC_AUTO_PROG_DONE_MSK, + " auto_prog_done: 0", + " auto_prog_done: X" + ); + + add_state_event_l + ( + secondary_base + NFC_3_2_IPC_REG_OFF, + NFC_3_2_IPC_RB_B_MSK, + " Medium : Busy", + " Medium : Ready" + ); +#endif +} + +/** + * nfc_3_2_get_auto_loop_params() - Gets automatic operation loop parameters. + * + * This function and the corresponding "setter" enable the automatic operations + * to keep some state as they iterate over chips. + * + * The most "obvious" way to save state would be to allocate a private data + * structure and hang it off the owning struct nfc_hal. On the other hand, + * writing the code to allocate the memory and then release it when the NFC + * shuts down is annoying - and we have some perfectly good memory in the NFC + * hardware that we can use. Since we only use two commands at a time, we can + * stash our loop limits and loop index in the top 16 bits of the NAND_CMD + * register. To paraphrase the reference manual: + * + * + * NAND_CMD + * + * |<-- 4 bits -->|<-- 4 bits -->|<-- 8 bits -->| + * +----------------+---------------+--------------------------------+ + * | First | Last | Loop Index | + * +----------------+---------------+--------------------------------+ + * | NAND COMMAND1 | NAND COMMAND0 | + * +--------------------------------+--------------------------------+ + * |<-- 16 bits -->|<-- 16 bits -->| + * + * + * @this: Per-device data. + * @first: A pointer to a variable that will receive the first chip number. + * @last: A pointer to a variable that will receive the last chip number. + * @index: A pointer to a variable that will receive the current chip number. + */ +static void nfc_3_2_get_auto_loop_params(struct imx_nfc_data *this, + unsigned *first, unsigned *last, unsigned *index) +{ + uint32_t x; + void *primary_base = this->primary_regs; + + x = __raw_readl(primary_base + NFC_3_2_CMD_REG_OFF); + + *first = (x >> 28) & 0x0f; + *last = (x >> 24) & 0x0f; + *index = (x >> 16) & 0xff; + +} + +/** + * nfc_3_2_set_auto_loop_params() - Sets automatic operation loop parameters. + * + * See nfc_3_2_get_auto_loop_params() for detailed information about these + * functions. + * + * @this: Per-device data. + * @first: The first chip number. + * @last: The last chip number. + * @index: The current chip number. + */ +static void nfc_3_2_set_auto_loop_params(struct imx_nfc_data *this, + unsigned first, unsigned last, unsigned index) +{ + uint32_t x; + void *primary_base = this->primary_regs; + + x = __raw_readl(primary_base + NFC_3_2_CMD_REG_OFF); + + x &= 0x0000ffff; + x |= (first & 0x0f) << 28; + x |= (last & 0x0f) << 24; + x |= (index & 0xff) << 16; + + __raw_writel(x, primary_base + NFC_3_2_CMD_REG_OFF); + +} + +/** + * nfc_3_2_get_auto_addresses() - Gets automatic operation addresses. + * + * @this: Per-device data. + * @group: The address group number. + * @chip: A pointer to a variable that will receive the chip number. + * @column: A pointer to a variable that will receive the column address. + * A NULL pointer indicates there is no column address. + * @page: A pointer to a variable that will receive the page address. + */ +static void nfc_3_2_get_auto_addresses(struct imx_nfc_data *this, + unsigned group, unsigned *chip, unsigned *column, unsigned *page) +{ + uint32_t x; + unsigned int chip_count; + unsigned int cs_width; + unsigned int cs_mask; + unsigned int page_lsbs; + unsigned int page_msbs; + uint32_t *low; + uint16_t *high; + void *primary_base = this->primary_regs; + void *secondary_base = this->secondary_regs; + + /* + * The width of the chip select field depends on the number of connected + * chips. + * + * Notice that these computations work only if the number of chips is a + * power of 2. In fact, that is a fundamental limitation for using + * automatic operations. + */ + + x = __raw_readl(secondary_base + NFC_3_2_CONFIG3_REG_OFF); + + chip_count = + (x & NFC_3_2_CONFIG3_NUM_OF_DEVICES_MSK) >> + NFC_3_2_CONFIG3_NUM_OF_DEVICES_POS; + chip_count++; + + cs_width = ffs(chip_count) - 1; + cs_mask = chip_count - 1; + + /* Construct pointers to the pieces of the given address group. */ + + low = primary_base + NFC_3_2_ADD0_REG_OFF; + low += group; + + high = primary_base + NFC_3_2_ADD8_REG_OFF; + high += group; + + /* Check if there's a column address. */ + + if (column) { + + /* + * The low 32 bits of the address group look like this: + * + * 16 - n n + * | <- bits ->|<->|<- 16 bits ->| + * +-------------+---+----------------+ + * | Page LSBs |CS | Column | + * +-------------+---+----------------+ + */ + + x = __raw_readl(low); + + *column = x & 0xffff; + *chip = (x >> 16) & cs_mask; + page_lsbs = x >> (16 + cs_width); + + /* The high 16 bits contain the MSB's of the page address. */ + + page_msbs = __raw_readw(high); + + *page = (page_msbs << (16 - cs_width)) | page_lsbs; + + } else { + + /* + * The low 32 bits of the address group look like this: + * + * n + * | <- (32 - n) bits ->|<->| + * +-----------------------------+---+ + * | Page LSBs |CS | + * +-----------------------------+---+ + */ + + x = __raw_readl(low); + + *chip = x & cs_mask; + page_lsbs = x >> cs_width; + + /* The high 16 bits contain the MSB's of the page address. */ + + page_msbs = __raw_readw(high); + + *page = (page_msbs << (32 - cs_width)) | page_lsbs; + + } + +} + +/** + * nfc_3_2_set_auto_addresses() - Sets automatic operation addresses. + * + * @this: Per-device data. + * @group: The address group number. + * @chip: The chip number. + * @column: The column address. The sentinel value ~0 indicates that there is + * no column address. + * @page: The page address. + */ +static void nfc_3_2_set_auto_addresses(struct imx_nfc_data *this, + unsigned group, unsigned chip, unsigned column, unsigned page) +{ + uint32_t x; + unsigned chip_count; + unsigned int cs_width; + unsigned int cs_mask; + uint32_t *low; + uint16_t *high; + void *primary_base = this->primary_regs; + void *secondary_base = this->secondary_regs; + + /* + * The width of the chip select field depends on the number of connected + * chips. + * + * Notice that these computations work only if the number of chips is a + * power of 2. In fact, that is a fundamental limitation for using + * automatic operations. + */ + + x = __raw_readl(secondary_base + NFC_3_2_CONFIG3_REG_OFF); + + chip_count = + (x & NFC_3_2_CONFIG3_NUM_OF_DEVICES_MSK) >> + NFC_3_2_CONFIG3_NUM_OF_DEVICES_POS; + chip_count++; + + cs_width = ffs(chip_count) - 1; + cs_mask = chip_count - 1; + + /* Construct pointers to the pieces of the given address group. */ + + low = primary_base + NFC_3_2_ADD0_REG_OFF; + low += group; + + high = primary_base + NFC_3_2_ADD8_REG_OFF; + high += group; + + /* Check if we have a column address. */ + + if (column != ~0) { + + /* + * The low 32 bits of the address group look like this: + * + * 16 - n n + * | <- bits ->|<->|<- 16 bits ->| + * +-------------+---+----------------+ + * | Page LSBs |CS | Column | + * +-------------+---+----------------+ + */ + + x = 0; + x |= column & 0xffff; + x |= (chip & cs_mask) << 16; + x |= page << (16 + cs_width); + + __raw_writel(x, low); + + /* The high 16 bits contain the MSB's of the page address. */ + + x = (page >> (16 - cs_width)) & 0xffff; + + __raw_writew(x, high); + + } else { + + /* + * The low 32 bits of the address group look like this: + * + * n + * | <- (32 - n) bits ->|<->| + * +-----------------------------+---+ + * | Page LSBs |CS | + * +-----------------------------+---+ + */ + + x = 0; + x |= chip & cs_mask; + x |= page << cs_width; + + __raw_writel(x, low); + + /* The high 16 bits contain the MSB's of the page address. */ + + x = (page >> (32 - cs_width)) & 0xffff; + + __raw_writew(x, high); + + } + +} + +/** + * nfc_3_2_start_auto_read() - Starts an automatic read. + * + * This function returns 0 if everything went well. + * + * @this: Per-device data. + * @start: The first physical chip number on which to operate. + * @count: The number of physical chips on which to operate. + * @column: The column address. + * @page: The page address. + */ +static int nfc_3_2_start_auto_read(struct imx_nfc_data *this, + unsigned start, unsigned count, unsigned column, unsigned page) +{ + uint32_t x; + int return_value = 0; + void *primary_base = this->primary_regs; + + add_event("Entering nfc_3_2_start_auto_read", 1); + + /* Check for nonsense. */ + + if ((start > 7) || (!count) || (count > 8)) { + return_value = !0; + goto exit; + } + + /* Set state. */ + + nfc_3_2_set_auto_loop_params(this, start, start + count - 1, start); + nfc_3_2_set_auto_addresses(this, 0, start, column, page); + + /* Set up for ONE iteration at a time. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_ITER_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Reset to buffer 0. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_RBA_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* + * Set up the commands. Note that the number of command phases was + * configured in the set_geometry() function so, even though we're + * giving both commands here, they won't necessarily both be used. + */ + + x = __raw_readl(primary_base + NFC_3_2_CMD_REG_OFF); + + x &= 0xffff0000; + x |= NAND_CMD_READ0 << 0; + x |= NAND_CMD_READSTART << 8; + + __raw_writel(x, primary_base + NFC_3_2_CMD_REG_OFF); + + /* Launch the operation. */ + + add_event("Launching", 0); + + __raw_writel(NFC_3_2_LAUNCH_AUTO_READ_MSK, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + +exit: /* Return. */ + + add_event("Exiting nfc_3_2_start_auto_read", -1); + + return return_value; + +} + +/** + * nfc_3_2_wait_for_auto_read() - Waits until auto read is ready for the CPU. + * + * This function returns 0 if everything went well. + * + * @this: Per-device data. + */ +static int nfc_3_2_wait_for_auto_read(struct imx_nfc_data *this) +{ + unsigned int first; + unsigned int last; + unsigned int index; + int return_value = 0; + + add_event("Entering nfc_3_2_wait_for_auto_read", 1); + + /* Get state. */ + + nfc_3_2_get_auto_loop_params(this, &first, &last, &index); + + /* This function should be called for every chip. */ + + if ((index < first) || (index > last)) { + return_value = !0; + goto exit; + } + + /* Wait for the NFC to completely finish and interrupt. */ + + nfc_util_wait_for_the_nfc(this, true); + +exit: /* Return. */ + + add_event("Exiting nfc_3_2_wait_for_auto_read", -1); + + return return_value; + +} + +/** + * nfc_3_2_resume_auto_read() - Resumes auto read after CPU intervention. + * + * This function returns 0 if everything went well. + * + * @this: Per-device data. + */ +static int nfc_3_2_resume_auto_read(struct imx_nfc_data *this) +{ + unsigned int first; + unsigned int last; + unsigned int index; + unsigned int chip; + unsigned int column; + unsigned int page; + int return_value = 0; + void *primary_base = this->primary_regs; + + add_event("Entering nfc_3_2_resume_auto_read", 1); + + /* Get state. */ + + nfc_3_2_get_auto_loop_params(this, &first, &last, &index); + nfc_3_2_get_auto_addresses(this, 0, &chip, &column, &page); + + /* This function should be called for every chip, except the last. */ + + if ((index < first) || (index >= last)) { + return_value = !0; + goto exit; + } + + /* Move to the next chip. */ + + index++; + + /* Update state. */ + + nfc_3_2_set_auto_loop_params(this, first, last, index); + nfc_3_2_set_auto_addresses(this, 0, index, column, page); + + /* Reset to buffer 0. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_RBA_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Launch the operation. */ + + add_event("Launching", 0); + + __raw_writel(NFC_3_2_LAUNCH_AUTO_READ_MSK, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + +exit: /* Return. */ + + add_event("Exiting nfc_3_2_resume_auto_read", -1); + + return return_value; + +} + +/** + * nfc_3_2_start_auto_write() - Starts an automatic write. + * + * This function returns 0 if everything went well. + * + * @this: Per-device data. + * @start: The first physical chip number on which to operate. + * @count: The number of physical chips on which to operate. + * @column: The column address. + * @page: The page address. + */ +static int nfc_3_2_start_auto_write(struct imx_nfc_data *this, + unsigned start, unsigned count, unsigned column, unsigned page) +{ + uint32_t x; + int return_value = 0; + void *primary_base = this->primary_regs; + void *secondary_base = this->secondary_regs; + + add_event("Entering nfc_3_2_start_auto_write", 1); + + /* Check for nonsense. */ + + if ((start > 7) || (!count) || (count > 8)) { + return_value = !0; + goto exit; + } + + /* Set state. */ + + nfc_3_2_set_auto_loop_params(this, start, start + count - 1, start); + nfc_3_2_set_auto_addresses(this, 0, start, column, page); + + /* Set up for ONE iteration at a time. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_ITER_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Set up the commands. */ + + x = __raw_readl(primary_base + NFC_3_2_CMD_REG_OFF); + + x &= 0xffff0000; + x |= NAND_CMD_SEQIN << 0; + x |= NAND_CMD_PAGEPROG << 8; + + __raw_writel(x, primary_base + NFC_3_2_CMD_REG_OFF); + + /* Clear the auto_prog_done bit. */ + + raw_clr_mask_l(NFC_3_2_IPC_AUTO_PROG_DONE_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + +exit: /* Return. */ + + add_event("Exiting nfc_3_2_start_auto_write", -1); + + return return_value; + +} + +/** + * nfc_3_2_wait_for_auto_write() - Waits for auto write to be writey for the CPU. + * + * This function returns 0 if everything went well. + * + * @this: Per-device data. + */ +static int nfc_3_2_wait_for_auto_write(struct imx_nfc_data *this) +{ + unsigned int first; + unsigned int last; + unsigned int index; + unsigned int chip; + unsigned int column; + unsigned int page; + uint32_t x; + int interrupt; + int transmitted; + int ready; + int return_value = 0; + void *primary_base = this->primary_regs; + void *secondary_base = this->secondary_regs; + + add_event("Entering nfc_3_2_wait_for_auto_write", 1); + + /* Get state. */ + + nfc_3_2_get_auto_loop_params(this, &first, &last, &index); + nfc_3_2_get_auto_addresses(this, 0, &chip, &column, &page); + + /* This function should be called for every chip. */ + + if ((index < first) || (index > last)) { + return_value = !0; + goto exit; + } + + /* Reset to buffer 0. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_RBA_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Launch the operation. */ + + nfc_3_2_add_state_events(this); + + add_event("Launching", 0); + + __raw_writel(NFC_3_2_LAUNCH_AUTO_PROG_MSK, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + + nfc_3_2_add_state_events(this); + + /* Wait for the NFC to transmit the page. */ + + add_event("Spinning while the NFC transmits the page...", 0); + + do + transmitted = !!raw_read_mask_l(NFC_3_2_IPC_AUTO_PROG_DONE_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + while (!transmitted); + + /* + * When control arrives here, the auto_prog_done bit is set. This + * indicates the NFC has finished transmitting the current page. The CPU + * is now free to write the next page into the NFC's memory. The Flash + * hardware is still busy programming the page into its storage array. + * + * Clear the auto_prog_done bit. This is analogous to acknowledging an + * interrupt. + */ + + nfc_3_2_add_state_events(this); + + add_event("Acknowledging the page...", 0); + + raw_clr_mask_l(NFC_3_2_IPC_AUTO_PROG_DONE_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + + nfc_3_2_add_state_events(this); + + /* + * If this is *not* the last iteration, move to the next chip and return + * to the caller so he can put the next page in the NFC buffer. + */ + + if (index < last) { + + add_event("Moving to the next chip...", 0); + + index++; + + nfc_3_2_set_auto_loop_params(this, first, last, index); + nfc_3_2_set_auto_addresses(this, 0, index, column, page); + + goto exit; + + } + + /* + * If control arrives here, this is the last iteration, so it's time to + * close out the entire operation. We need to wait for the medium to be + * ready and then acknowledge the final interrupt. + * + * Because of the way the NFC hardware works, the code here requires a + * bit of explanation. The most important rule is: + * + * During automatic operations, the NFC sets its + * interrupt bit *whenever* it sees the ready/busy + * signal transition from "Busy" to "Ready". + * + * Recall that the ready/busy signals from all the chips in the medium + * are "wire-anded." Thus, the NFC will only see that the medium is + * ready if *all* chips are ready. + * + * Because of variability in NAND Flash timing, the medium *may* have + * become ready during previous iterations, which means the interrupt + * bit *may* be set at this moment. This is a "left-over" interrupt, and + * can complicate our logic. + * + * The two bits of state that interest us here are the interrupt bit + * and the ready/busy bit. It boils down to the following truth table: + * + * | Interrupt | Ready/Busy | Description + * +------------+------------+--------------- + * | | | Busy medium and no left-over interrupt. + * | 0 | 0 | The final interrupt will arrive in the + * | | | future. + * +------------+------------+--------------- + * | | | Ready medium and no left-over interrupt. + * | 0 | 1 | There will be no final interrupt. This + * | | | case should be impossible. + * +------------+------------+--------------- + * | | | Busy medium and left-over interrupt. + * | 1 | 0 | The final interrupt will arrive in the + * | | | future. This is the hard case. + * +------------+------------+--------------- + * | | | Ready medium and left-over interrupt. + * | 1 | 1 | The final interrupt has already + * | | | arrived. Acknowledge it and exit. + * +------------+------------+--------------- + * + * Case #3 is a small problem. If we clear the interrupt, we may or may + * not have another interrupt following. + */ + + /* Sample the IPC register. */ + + x = __raw_readl(secondary_base + NFC_3_2_IPC_REG_OFF); + + interrupt = !!(x & NFC_3_2_IPC_INT_MSK); + ready = !!(x & NFC_3_2_IPC_RB_B_MSK); + + /* Check for the easy cases. */ + + if (!interrupt && !ready) { + add_event("Waiting for the final interrupt..." , 0); + nfc_util_wait_for_the_nfc(this, true); + goto exit; + } else if (!interrupt && ready) { + add_event("Done." , 0); + goto exit; + } else if (interrupt && ready) { + add_event("Acknowledging the final interrupt..." , 0); + nfc_util_wait_for_the_nfc(this, false); + goto exit; + + } + + /* + * If control arrives here, we hit case #3. Begin by acknowledging the + * interrupt we have right now. + */ + + add_event("Clearing the left-over interrupt..." , 0); + nfc_util_wait_for_the_nfc(this, false); + + /* + * Check the ready/busy bit again. If the medium is still busy, then + * we're going to get one more interrupt. + */ + + ready = !!raw_read_mask_l(NFC_3_2_IPC_RB_B_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + + if (!ready) { + add_event("Waiting for the final interrupt..." , 0); + nfc_util_wait_for_the_nfc(this, true); + } + +exit: /* Return. */ + + add_event("Exiting nfc_3_2_wait_for_auto_write", -1); + + return return_value; + +} + +/** + * nfc_3_2_start_auto_erase() - Starts an automatic erase. + * + * This function returns 0 if everything went well. + * + * @this: Per-device data. + * @start: The first physical chip number on which to operate. + * @count: The number of physical chips on which to operate. + * @page: The page address. + */ +static int nfc_3_2_start_auto_erase(struct imx_nfc_data *this, + unsigned start, unsigned count, unsigned page) +{ + uint32_t x; + unsigned i; + int return_value = 0; + void *primary_base = this->primary_regs; + + add_event("Entering nfc_3_2_start_auto_erase", 1); + + /* Check for nonsense. */ + + if ((start > 7) || (!count) || (count > 8)) { + return_value = !0; + goto exit; + } + + /* Set up the commands. */ + + x = __raw_readl(primary_base + NFC_3_2_CMD_REG_OFF); + + x &= 0xffff0000; + x |= NAND_CMD_ERASE1 << 0; + x |= NAND_CMD_ERASE2 << 8; + + __raw_writel(x, primary_base + NFC_3_2_CMD_REG_OFF); + + /* Set the iterations. */ + + x = __raw_readl(primary_base + NFC_3_2_CONFIG1_REG_OFF); + + x &= ~NFC_3_2_CONFIG1_ITER_MSK; + x |= ((count - 1) << NFC_3_2_CONFIG1_ITER_POS) & + NFC_3_2_CONFIG1_ITER_MSK; + + __raw_writel(x, primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Loop over chips, setting up the address groups. */ + + for (i = 0; i < count; i++) + nfc_3_2_set_auto_addresses(this, i, start + i, ~0, page); + + /* Launch the operation. */ + + add_event("Launching", 0); + + __raw_writel(NFC_3_2_LAUNCH_AUTO_ERASE_MSK, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + +exit: /* Return. */ + + add_event("Exiting nfc_3_2_start_auto_erase", -1); + + return return_value; + +} + +/* + * At this point, we've defined all the version-specific primitives. We're now + * ready to construct the NFC HAL structures for every version. + */ + +struct nfc_hal nfc_1_0_hal = { + .major_version = 1, + .minor_version = 0, + .max_chip_count = 1, + .max_buffer_count = 4, + .spare_buf_stride = 16, + .has_secondary_regs = 0, + .can_be_symmetric = 0, + }; + +struct nfc_hal nfc_2_0_hal = { + .major_version = 2, + .minor_version = 0, + .max_chip_count = 1, + .max_buffer_count = 4, + .spare_buf_stride = 16, + .has_secondary_regs = false, + .can_be_symmetric = true, + .init = nfc_2_0_init, + .set_geometry = nfc_2_0_set_geometry, + .exit = nfc_2_x_exit, + .mask_interrupt = nfc_2_0_mask_interrupt, + .unmask_interrupt = nfc_2_0_unmask_interrupt, + .clear_interrupt = nfc_2_x_clear_interrupt, + .is_interrupting = nfc_2_x_is_interrupting, + .is_ready = 0, /* Ready/Busy not exposed. */ + .set_ecc = nfc_2_0_set_ecc, + .get_ecc_status = nfc_2_0_get_ecc_status, + .get_symmetric = nfc_2_0_get_symmetric, + .set_symmetric = nfc_2_0_set_symmetric, + .select_chip = nfc_2_0_select_chip, + .command_cycle = nfc_2_x_command_cycle, + .write_cycle = nfc_2_x_write_cycle, + .read_cycle = nfc_2_0_read_cycle, + .read_page = nfc_2_0_read_page, + .send_page = nfc_2_0_send_page, + .start_auto_read = 0, /* Not supported. */ + .wait_for_auto_read = 0, /* Not supported. */ + .resume_auto_read = 0, /* Not supported. */ + .start_auto_write = 0, /* Not supported. */ + .wait_for_auto_write = 0, /* Not supported. */ + .start_auto_erase = 0, /* Not supported. */ + }; + +struct nfc_hal nfc_2_1_hal = { + .major_version = 2, + .minor_version = 1, + .max_chip_count = 4, + .max_buffer_count = 8, + .spare_buf_stride = 64, + .has_secondary_regs = 0, + .can_be_symmetric = !0, + }; + +struct nfc_hal nfc_3_1_hal = { + .major_version = 3, + .minor_version = 1, + .max_chip_count = 4, + .max_buffer_count = 8, + .spare_buf_stride = 64, + .has_secondary_regs = !0, + .can_be_symmetric = !0, + }; + +struct nfc_hal nfc_3_2_hal = { + .major_version = 3, + .minor_version = 2, + .max_chip_count = 8, + .max_buffer_count = 8, + .spare_buf_stride = 64, + .has_secondary_regs = true, + .can_be_symmetric = true, + .init = nfc_3_2_init, + .set_geometry = nfc_3_2_set_geometry, + .exit = nfc_3_2_exit, + .set_closest_cycle = nfc_3_2_set_closest_cycle, + .mask_interrupt = nfc_3_2_mask_interrupt, + .unmask_interrupt = nfc_3_2_unmask_interrupt, + .clear_interrupt = nfc_3_2_clear_interrupt, + .is_interrupting = nfc_3_2_is_interrupting, + .is_ready = nfc_3_2_is_ready, + .set_force_ce = nfc_3_2_set_force_ce, + .set_ecc = nfc_3_2_set_ecc, + .get_ecc_status = nfc_3_2_get_ecc_status, + .get_symmetric = nfc_3_2_get_symmetric, + .set_symmetric = nfc_3_2_set_symmetric, + .select_chip = nfc_3_2_select_chip, + .command_cycle = nfc_3_2_command_cycle, + .write_cycle = nfc_3_2_write_cycle, + .read_cycle = nfc_3_2_read_cycle, + .read_page = nfc_3_2_read_page, + .send_page = nfc_3_2_send_page, + .start_auto_read = nfc_3_2_start_auto_read, + .wait_for_auto_read = nfc_3_2_wait_for_auto_read, + .resume_auto_read = nfc_3_2_resume_auto_read, + .start_auto_write = nfc_3_2_start_auto_write, + .wait_for_auto_write = nfc_3_2_wait_for_auto_write, + .start_auto_erase = nfc_3_2_start_auto_erase, + }; + +/* + * This array has a pointer to every NFC HAL structure. The probing process will + * find the one that matches the version given by the platform. + */ + +struct nfc_hal *(nfc_hals[]) = { + &nfc_1_0_hal, + &nfc_2_0_hal, + &nfc_2_1_hal, + &nfc_3_1_hal, + &nfc_3_2_hal, +}; + +/** + * mal_init() - Initialize the Medium Abstraction Layer. + * + * @this: Per-device data. + */ +static void mal_init(struct imx_nfc_data *this) +{ + this->interrupt_override = DRIVER_CHOICE; + this->auto_op_override = DRIVER_CHOICE; + this->inject_ecc_error = 0; +} + +/** + * mal_set_physical_geometry() - Set up the physical medium geometry. + * + * This function retrieves the physical geometry information discovered by + * nand_scan(), corrects it, and records it in the per-device data structure. + * + * @this: Per-device data. + */ +static int mal_set_physical_geometry(struct imx_nfc_data *this) +{ + struct mtd_info *mtd = &this->mtd; + struct nand_chip *nand = &this->nand; + struct device *dev = this->dev; + uint8_t manufacturer_id; + uint8_t device_id; + unsigned int block_size_in_pages; + unsigned int chip_size_in_blocks; + unsigned int chip_size_in_pages; + uint64_t medium_size_in_bytes; + struct physical_geometry *physical = &this->physical_geometry; + + /* + * Begin by transcribing exactly what the MTD code discovered. If there + * are any mistakes, we'll fix them in a moment. + */ + + physical->chip_count = nand->numchips; + physical->chip_size = nand->chipsize; + physical->block_size = mtd->erasesize; + physical->page_data_size = mtd->writesize; + physical->page_oob_size = mtd->oobsize; + + /* Read some of the ID bytes from the first NAND Flash chip. */ + + nand->select_chip(mtd, 0); + + nfc_util_send_cmd_and_addrs(this, NAND_CMD_READID, 0x00, -1); + + manufacturer_id = nand->read_byte(mtd); + device_id = nand->read_byte(mtd); + + /* + * Most manufacturers sell 4K page devices with 218 out-of-band bytes + * per page to accomodate ECC-8. + * + * Samsung and Hynix claim their parts have better reliability, so they + * only need ECC-4 and they have only 128 out-of-band bytes. + * + * The MTD code pays no attention to the manufacturer ID (something that + * eventually will have to change), so it believes that all 4K pages + * have 218 out-of-band bytes. + * + * We correct that mistake here. + */ + + if (physical->page_data_size == 4096) { + if ((manufacturer_id == NAND_MFR_SAMSUNG) || + (manufacturer_id == NAND_MFR_HYNIX)) { + physical->page_oob_size = 128; + } + } + + /* Compute some interesting facts. */ + + block_size_in_pages = + physical->block_size / physical->page_data_size; + chip_size_in_pages = + physical->chip_size >> (fls(physical->page_data_size) - 1); + chip_size_in_blocks = + physical->chip_size >> (fls(physical->block_size) - 1); + medium_size_in_bytes = + physical->chip_size * physical->chip_count; + + /* Report. */ + + dev_dbg(dev, "-----------------\n"); + dev_dbg(dev, "Physical Geometry\n"); + dev_dbg(dev, "-----------------\n"); + dev_dbg(dev, "Chip Count : %d\n", physical->chip_count); + dev_dbg(dev, "Page Data Size in Bytes: %u (0x%x)\n", + physical->page_data_size, physical->page_data_size); + dev_dbg(dev, "Page OOB Size in Bytes : %u\n", + physical->page_oob_size); + dev_dbg(dev, "Block Size in Bytes : %u (0x%x)\n", + physical->block_size, physical->block_size); + dev_dbg(dev, "Block Size in Pages : %u (0x%x)\n", + block_size_in_pages, block_size_in_pages); + dev_dbg(dev, "Chip Size in Bytes : %llu (0x%llx)\n", + physical->chip_size, physical->chip_size); + dev_dbg(dev, "Chip Size in Pages : %u (0x%x)\n", + chip_size_in_pages, chip_size_in_pages); + dev_dbg(dev, "Chip Size in Blocks : %u (0x%x)\n", + chip_size_in_blocks, chip_size_in_blocks); + dev_dbg(dev, "Medium Size in Bytes : %llu (0x%llx)\n", + medium_size_in_bytes, medium_size_in_bytes); + + /* Return success. */ + + return 0; + +} + +/** + * mal_set_nfc_geometry() - Set up the NFC geometry. + * + * This function calls the NFC HAL to select an NFC geometry that is compatible + * with the medium's physical geometry. + * + * @this: Per-device data. + */ +static int mal_set_nfc_geometry(struct imx_nfc_data *this) +{ + struct device *dev = this->dev; + struct nfc_geometry *nfc; + + /* Set the NFC geometry. */ + + if (this->nfc->set_geometry(this)) + return !0; + + /* Get a pointer to the new NFC geometry information. */ + + nfc = this->nfc_geometry; + + /* Report. */ + + dev_dbg(dev, "------------\n"); + dev_dbg(dev, "NFC Geometry\n"); + dev_dbg(dev, "------------\n"); + dev_dbg(dev, "Page Data Size in Bytes: %u (0x%x)\n", + nfc->page_data_size, nfc->page_data_size); + dev_dbg(dev, "Page OOB Size in Bytes : %u\n", nfc->page_oob_size); + dev_dbg(dev, "ECC Algorithm : %s\n", nfc->ecc_algorithm); + dev_dbg(dev, "ECC Strength : %d\n", nfc->ecc_strength); + dev_dbg(dev, "Buffer Count : %u\n", nfc->buffer_count); + dev_dbg(dev, "Spare Buffer Size : %u\n", nfc->spare_buf_size); + dev_dbg(dev, "Spare Buffer Spillover : %u\n", nfc->spare_buf_spill); + dev_dbg(dev, "Auto Read Available : %s\n", + this->nfc->start_auto_read ? "Yes" : "No"); + dev_dbg(dev, "Auto Write Available : %s\n", + this->nfc->start_auto_write ? "Yes" : "No"); + dev_dbg(dev, "Auto Erase Available : %s\n", + this->nfc->start_auto_erase ? "Yes" : "No"); + + /* Return success. */ + + return 0; + +} + +/** + * mal_set_logical_geometry() - Set up the logical medium geometry. + * + * This function constructs the logical geometry that we will expose to MTD, + * based on the physical and NFC geometries, and whether or not interleaving is + * on. + * + * @this: Per-device data. + */ +static int mal_set_logical_geometry(struct imx_nfc_data *this) +{ + const uint32_t max_medium_size_in_bytes = ~0; + int we_are_interleaving; + uint64_t physical_medium_size_in_bytes; + unsigned int usable_blocks; + unsigned int block_size_in_pages; + unsigned int chip_size_in_blocks; + unsigned int chip_size_in_pages; + unsigned int usable_medium_size_in_pages; + unsigned int usable_medium_size_in_blocks; + struct physical_geometry *physical = &this->physical_geometry; + struct nfc_geometry *nfc = this->nfc_geometry; + struct logical_geometry *logical = &this->logical_geometry; + struct device *dev = this->dev; + + /* Figure out if we're interleaving. */ + + we_are_interleaving = this->pdata->interleave; + + switch (imx_nfc_module_interleave_override) { + + case NEVER: + we_are_interleaving = false; + break; + + case DRIVER_CHOICE: + break; + + case ALWAYS: + we_are_interleaving = true; + break; + + } + + /* Compute the physical size of the medium. */ + + physical_medium_size_in_bytes = + physical->chip_count * physical->chip_size; + + /* Compute the logical geometry. */ + + if (!we_are_interleaving) { + + /* + * At this writing, MTD uses unsigned 32-bit variables to + * represent the size of the medium. If the physical medium is + * larger than that, the logical medium must be smaller. Here, + * we compute the total number of physical blocks in the medium + * that we can actually use. + */ + + if (physical_medium_size_in_bytes <= max_medium_size_in_bytes) { + usable_blocks = + physical_medium_size_in_bytes >> + (ffs(physical->block_size) - 1); + } else { + usable_blocks = + max_medium_size_in_bytes / physical->block_size; + } + + /* Set up the logical geometry. + * + * Notice that the usable medium size is not necessarily the + * same as the chip size multiplied by the number of physical + * chips. We can't afford to touch the physical chip size + * because the NAND Flash MTD code *requires* it to be a power + * of 2. + */ + + logical->chip_count = physical->chip_count; + logical->chip_size = physical->chip_size; + logical->usable_size = usable_blocks * physical->block_size; + logical->block_size = physical->block_size; + logical->page_data_size = nfc->page_data_size; + + /* Use the MTD layout that best matches the NFC geometry. */ + + logical->mtd_layout = &nfc->mtd_layout; + logical->page_oob_size = nfc->mtd_layout.eccbytes + + nfc->mtd_layout.oobavail; + + } else { + + /* + * If control arrives here, we are interleaving. Specifically, + * we are "horizontally concatenating" the pages in all the + * physical chips. + * + * - A logical page will be the size of a physical page + * multiplied by the number of physical chips. + * + * - A logical block will have the same number of pages as a + * physical block but, since the logical page size is larger, + * the logical block size is larger. + * + * - The entire medium will appear to be a single chip. + * + * At this writing, MTD uses unsigned 32-bit variables to + * represent the size of the medium. If the physical medium is + * larger than that, the logical medium must be smaller. + * + * The NAND Flash MTD code represents the size of a single chip + * as an unsigned 32-bit value. It also *requires* that the size + * of a chip be a power of two. Thus, the largest possible chip + * size is 2GiB. + * + * When interleaving, the entire medium appears to be one chip. + * Thus, when interleaving, the largest possible medium size is + * 2GiB. + */ + + if (physical_medium_size_in_bytes <= max_medium_size_in_bytes) { + logical->chip_size = + 0x1 << (fls(physical_medium_size_in_bytes) - 1); + } else { + logical->chip_size = + 0x1 << (fls(max_medium_size_in_bytes) - 1); + } + + /* + * If control arrives here, we're interleaving. The logical + * geometry is very different from the physical geometry. + */ + + logical->chip_count = 1; + logical->usable_size = logical->chip_size; + logical->block_size = + physical->block_size * physical->chip_count; + logical->page_data_size = + nfc->page_data_size * physical->chip_count; + + /* + * Since the logical geometry doesn't match the physical + * geometry, we can't use the MTD layout that matches the + * NFC geometry. We synthesize one here. + * + * Our "logical" OOB will be the concatenation of the first 5 + * bytes of the "physical" OOB of every chip. This has some + * important properties: + * + * - This will make the block mark of every physical chip + * visible (even for small page chips, which put their block + * mark in the 5th OOB byte). + * + * - None of the NFC controllers put ECC in the first 5 OOB + * bytes, so this layout exposes no ECC. + */ + + logical->page_oob_size = 5 * physical->chip_count; + + synthetic_layout.eccbytes = 0; + synthetic_layout.oobavail = 5 * physical->chip_count; + synthetic_layout.oobfree[0].offset = 0; + synthetic_layout.oobfree[0].length = synthetic_layout.oobavail; + + /* Install the synthetic layout. */ + + logical->mtd_layout = &synthetic_layout; + + } + + /* Compute some interesting facts. */ + + block_size_in_pages = logical->block_size / logical->page_data_size; + chip_size_in_pages = logical->chip_size / logical->page_data_size; + chip_size_in_blocks = logical->chip_size / logical->block_size; + usable_medium_size_in_pages = + logical->usable_size / logical->page_data_size; + usable_medium_size_in_blocks = + logical->usable_size / logical->block_size; + + /* Report. */ + + dev_dbg(dev, "----------------\n"); + dev_dbg(dev, "Logical Geometry\n"); + dev_dbg(dev, "----------------\n"); + dev_dbg(dev, "Chip Count : %d\n", logical->chip_count); + dev_dbg(dev, "Page Data Size in Bytes: %u (0x%x)\n", + logical->page_data_size, logical->page_data_size); + dev_dbg(dev, "Page OOB Size in Bytes : %u\n", + logical->page_oob_size); + dev_dbg(dev, "Block Size in Bytes : %u (0x%x)\n", + logical->block_size, logical->block_size); + dev_dbg(dev, "Block Size in Pages : %u (0x%x)\n", + block_size_in_pages, block_size_in_pages); + dev_dbg(dev, "Chip Size in Bytes : %u (0x%x)\n", + logical->chip_size, logical->chip_size); + dev_dbg(dev, "Chip Size in Pages : %u (0x%x)\n", + chip_size_in_pages, chip_size_in_pages); + dev_dbg(dev, "Chip Size in Blocks : %u (0x%x)\n", + chip_size_in_blocks, chip_size_in_blocks); + dev_dbg(dev, "Physical Size in Bytes : %llu (0x%llx)\n", + physical_medium_size_in_bytes, physical_medium_size_in_bytes); + dev_dbg(dev, "Usable Size in Bytes : %u (0x%x)\n", + logical->usable_size, logical->usable_size); + dev_dbg(dev, "Usable Size in Pages : %u (0x%x)\n", + usable_medium_size_in_pages, usable_medium_size_in_pages); + dev_dbg(dev, "Usable Size in Blocks : %u (0x%x)\n", + usable_medium_size_in_blocks, usable_medium_size_in_blocks); + + /* Return success. */ + + return 0; + +} + +/** + * mal_set_mtd_geometry() - Set up the MTD geometry. + * + * This function adjusts the owning MTD data structures to match the logical + * geometry we've chosen. + * + * @this: Per-device data. + */ +static int mal_set_mtd_geometry(struct imx_nfc_data *this) +{ + struct logical_geometry *logical = &this->logical_geometry; + struct mtd_info *mtd = &this->mtd; + struct nand_chip *nand = &this->nand; + + /* Configure the struct mtd_info. */ + + mtd->size = logical->usable_size; + mtd->erasesize = logical->block_size; + mtd->writesize = logical->page_data_size; + mtd->ecclayout = logical->mtd_layout; + mtd->oobavail = mtd->ecclayout->oobavail; + mtd->oobsize = mtd->ecclayout->oobavail + mtd->ecclayout->eccbytes; + mtd->subpage_sft = 0; /* We don't support sub-page writing. */ + + /* Configure the struct nand_chip. */ + + nand->numchips = logical->chip_count; + nand->chipsize = logical->chip_size; + nand->page_shift = ffs(logical->page_data_size) - 1; + nand->pagemask = (nand->chipsize >> nand->page_shift) - 1; + nand->subpagesize = mtd->writesize >> mtd->subpage_sft; + nand->phys_erase_shift = ffs(logical->block_size) - 1; + nand->bbt_erase_shift = nand->phys_erase_shift; + nand->chip_shift = ffs(logical->chip_size) - 1; + nand->oob_poi = nand->buffers->databuf+logical->page_data_size; + nand->ecc.layout = logical->mtd_layout; + + /* Set up the pattern that describes block marks. */ + + if (is_small_page_chip(this)) + nand->badblock_pattern = &small_page_block_mark_descriptor; + else + nand->badblock_pattern = &large_page_block_mark_descriptor; + + /* Return success. */ + + return 0; +} + +/** + * mal_set_geometry() - Set up the medium geometry. + * + * @this: Per-device data. + */ +static int mal_set_geometry(struct imx_nfc_data *this) +{ + + /* Set up the various layers of geometry, in this specific order. */ + + if (mal_set_physical_geometry(this)) + return !0; + + if (mal_set_nfc_geometry(this)) + return !0; + + if (mal_set_logical_geometry(this)) + return !0; + + if (mal_set_mtd_geometry(this)) + return !0; + + /* Return success. */ + + return 0; + +} + +/** + * mal_reset() - Resets the given chip. + * + * This is the fully-generalized reset operation, including support for + * interleaving. All reset operations funnel through here. + * + * @this: Per-device data. + * @chip: The logical chip of interest. + */ +static void mal_reset(struct imx_nfc_data *this, unsigned chip) +{ + int we_are_interleaving; + unsigned int start; + unsigned int end; + unsigned int i; + struct physical_geometry *physical = &this->physical_geometry; + struct logical_geometry *logical = &this->logical_geometry; + + add_event("Entering mal_get_status", 1); + + /* Establish some important facts. */ + + we_are_interleaving = logical->chip_count != physical->chip_count; + + /* Choose the loop bounds. */ + + if (we_are_interleaving) { + start = 0; + end = physical->chip_count; + } else { + start = chip; + end = start + 1; + } + + /* Loop over physical chips. */ + + add_event("Looping over physical chips...", 0); + + for (i = start; i < end; i++) { + + /* Select the current chip. */ + + this->nfc->select_chip(this, i); + + /* Reset the current chip. */ + + add_event("Resetting...", 0); + + nfc_util_send_cmd(this, NAND_CMD_RESET); + nfc_util_wait_for_the_nfc(this, false); + + } + + add_event("Exiting mal_get_status", -1); + +} + +/** + * mal_get_status() - Abstracted status retrieval. + * + * For media with a single chip, or concatenated chips, the HIL explicitly + * addresses a single chip at a time and wants the status from that chip only. + * + * For interleaved media, we must combine the individual chip states. At this + * writing, the NAND MTD system knows about the following bits in status + * registers: + * + * +------------------------+-------+---------+ + * | | | Combine | + * | Macro | Value | With | + * +------------------------+-------+---------+ + * | NAND_STATUS_FAIL | 0x01 | OR | + * | NAND_STATUS_FAIL_N1 | 0x02 | OR | + * | NAND_STATUS_TRUE_READY | 0x20 | AND | + * | NAND_STATUS_READY | 0x40 | AND | + * | NAND_STATUS_WP | 0x80 | AND | + * +------------------------+-------+---------+ + * + * @this: Per-device data. + * @chip: The logical chip of interest. + */ +static uint8_t mal_get_status(struct imx_nfc_data *this, unsigned chip) +{ + int we_are_interleaving; + unsigned int start; + unsigned int end; + unsigned int i; + unsigned int x; + unsigned int or_mask; + unsigned int and_mask; + uint8_t status; + struct physical_geometry *physical = &this->physical_geometry; + struct logical_geometry *logical = &this->logical_geometry; + + add_event("Entering mal_get_status", 1); + + /* Establish some important facts. */ + + we_are_interleaving = logical->chip_count != physical->chip_count; + + /* Compute the masks we need. */ + + or_mask = NAND_STATUS_FAIL | NAND_STATUS_FAIL_N1; + and_mask = NAND_STATUS_TRUE_READY | NAND_STATUS_READY | NAND_STATUS_WP; + + /* Assume the chip is successful, ready and writeable. */ + + status = and_mask & ~or_mask; + + /* Choose the loop bounds. */ + + if (we_are_interleaving) { + start = 0; + end = physical->chip_count; + } else { + start = chip; + end = start + 1; + } + + /* Loop over physical chips. */ + + add_event("Looping over physical chips...", 0); + + for (i = start; i < end; i++) { + + /* Select the current chip. */ + + this->nfc->select_chip(this, i); + + /* Get the current chip's status. */ + + add_event("Sending the command...", 0); + + nfc_util_send_cmd(this, NAND_CMD_STATUS); + nfc_util_wait_for_the_nfc(this, false); + + add_event("Reading the status...", 0); + + x = this->nfc->read_cycle(this); + + /* Fold this chip's status into the combined status. */ + + status |= (x & or_mask); + status &= (x & and_mask) | or_mask; + + } + + add_event("Exiting mal_get_status", -1); + + return status; + +} + +/** + * mal_read_a_page() - Abstracted page read. + * + * This function returns the ECC status for the entire read operation. A + * positive return value indicates the number of errors that were corrected + * (symbol errors for Reed-Solomon hardware engines, bit errors for BCH hardware + * engines). A negative return value indicates that the ECC engine failed to + * correct all errors and the data is corrupted. A zero return value indicates + * there were no errors at all. + * + * @this: Per-device data. + * @use_ecc: Indicates if we're to use ECC. + * @chip: The logical chip of interest. + * @page: The logical page number to read. + * @data: A pointer to the destination data buffer. If this pointer is null, + * that indicates the caller doesn't want the data. + * @oob: A pointer to the destination OOB buffer. If this pointer is null, + * that indicates the caller doesn't want the OOB. + */ +static int mal_read_a_page(struct imx_nfc_data *this, int use_ecc, + unsigned chip, unsigned page, uint8_t *data, uint8_t *oob) +{ + int we_are_interleaving; + int use_automatic_op; + unsigned int start; + unsigned int end; + unsigned int current_chip; + unsigned int oob_bytes_to_copy; + unsigned int data_bytes_to_copy; + int status; + unsigned int worst_case_ecc_status; + int return_value = 0; + struct physical_geometry *physical = &this->physical_geometry; + struct nfc_geometry *nfc = this->nfc_geometry; + struct logical_geometry *logical = &this->logical_geometry; + + add_event("Entering mal_read_a_page", 1); + + /* Establish some important facts. */ + + we_are_interleaving = logical->chip_count != physical->chip_count; + use_automatic_op = !!this->nfc->start_auto_read; + + /* Apply the automatic operation override, if any. */ + + switch (this->auto_op_override) { + + case NEVER: + use_automatic_op = false; + break; + + case DRIVER_CHOICE: + break; + + case ALWAYS: + if (this->nfc->start_auto_read) + use_automatic_op = true; + break; + + } + + /* Set up ECC. */ + + this->nfc->set_ecc(this, use_ecc); + + /* Check if we're interleaving and set up the loop iterations. */ + + if (we_are_interleaving) { + + start = 0; + end = physical->chip_count; + + data_bytes_to_copy = + this->logical_geometry.page_data_size / + this->physical_geometry.chip_count; + oob_bytes_to_copy = + this->logical_geometry.page_oob_size / + this->physical_geometry.chip_count; + + } else { + + start = chip; + end = start + 1; + + data_bytes_to_copy = this->logical_geometry.page_data_size; + oob_bytes_to_copy = this->logical_geometry.page_oob_size; + + } + + /* If we're using the automatic operation, start it now. */ + + if (use_automatic_op) { + add_event("Starting the automatic operation...", 0); + this->nfc->start_auto_read(this, start, end - start, 0, page); + } + + /* Loop over physical chips. */ + + add_event("Looping over physical chips...", 0); + + for (current_chip = start; current_chip < end; current_chip++) { + + /* Check if we're using the automatic operation. */ + + if (use_automatic_op) { + + add_event("Waiting...", 0); + this->nfc->wait_for_auto_read(this); + + } else { + + /* Select the current chip. */ + + this->nfc->select_chip(this, current_chip); + + /* Set up the chip. */ + + add_event("Sending the command and addresses...", 0); + + nfc_util_send_cmd_and_addrs(this, + NAND_CMD_READ0, 0, page); + + if (is_large_page_chip(this)) { + add_event("Sending the final command...", 0); + nfc_util_send_cmd(this, NAND_CMD_READSTART); + } + + /* Wait till the page is ready. */ + + add_event("Waiting for the page to arrive...", 0); + + nfc_util_wait_for_the_nfc(this, true); + + /* Read the page. */ + + add_event("Reading the page...", 0); + + this->nfc->read_page(this); + + } + + /* Copy a page out of the NFC. */ + + add_event("Copying from the NFC...", 0); + + if (oob) { + nfc_util_copy_from_the_nfc(this, + nfc->page_data_size, oob, oob_bytes_to_copy); + oob += oob_bytes_to_copy; + } + + if (data) { + nfc_util_copy_from_the_nfc(this, + 0, data, data_bytes_to_copy); + data += data_bytes_to_copy; + } + + /* + * If we're using ECC, and we haven't already seen an ECC + * failure, continue to gather ECC status. Note that, if we + * *do* see an ECC failure, we continue to read because the + * client might want the data for forensic purposes. + */ + + if (use_ecc && (return_value >= 0)) { + + add_event("Getting ECC status...", 0); + + status = this->nfc->get_ecc_status(this); + + if (status >= 0) + return_value += status; + else + return_value = -1; + + } + + /* Check if we're using the automatic operation. */ + + if (use_automatic_op) { + + /* + * If this is not the last iteration, resume the + * automatic operation. + */ + + if (current_chip < (end - 1)) { + add_event("Resuming...", 0); + this->nfc->resume_auto_read(this); + } + + } + + } + + /* Check if we're supposed to inject an ECC error. */ + + if (use_ecc && this->inject_ecc_error) { + + /* Inject the appropriate error. */ + + if (this->inject_ecc_error < 0) { + + add_event("Injecting an uncorrectable error...", 0); + + return_value = -1; + + } else { + + add_event("Injecting correctable errors...", 0); + + worst_case_ecc_status = + physical->chip_count * + nfc->buffer_count * + nfc->ecc_strength; + + if (this->inject_ecc_error > worst_case_ecc_status) + return_value = worst_case_ecc_status; + else + return_value = this->inject_ecc_error; + + } + + /* Stop injecting further errors. */ + + this->inject_ecc_error = 0; + + } + + /* Return. */ + + add_event("Exiting mal_read_a_page", -1); + + return return_value; + +} + +/** + * mal_write_a_page() - Abstracted page write. + * + * This function returns zero if the operation succeeded, or -EIO if the + * operation failed. + * + * @this: Per-device data. + * @use_ecc: Indicates if we're to use ECC. + * @chip: The logical chip of interest. + * @page: The logical page number to write. + * @data: A pointer to the source data buffer. + * @oob: A pointer to the source OOB buffer. + */ +static int mal_write_a_page(struct imx_nfc_data *this, int use_ecc, + unsigned chip, unsigned page, const uint8_t *data, const uint8_t *oob) +{ + int we_are_interleaving; + int use_automatic_op; + unsigned int start; + unsigned int end; + unsigned int current_chip; + unsigned int oob_bytes_to_copy; + unsigned int data_bytes_to_copy; + int return_value = 0; + struct physical_geometry *physical = &this->physical_geometry; + struct nfc_geometry *nfc = this->nfc_geometry; + struct logical_geometry *logical = &this->logical_geometry; + + add_event("Entering mal_write_a_page", 1); + + /* Establish some important facts. */ + + we_are_interleaving = logical->chip_count != physical->chip_count; + use_automatic_op = !!this->nfc->start_auto_write; + + /* Apply the automatic operation override, if any. */ + + switch (this->auto_op_override) { + + case NEVER: + use_automatic_op = false; + break; + + case DRIVER_CHOICE: + break; + + case ALWAYS: + if (this->nfc->start_auto_write) + use_automatic_op = true; + break; + + } + + /* Set up ECC. */ + + this->nfc->set_ecc(this, use_ecc); + + /* Check if we're interleaving and set up the loop iterations. */ + + if (we_are_interleaving) { + + start = 0; + end = physical->chip_count; + + data_bytes_to_copy = + this->logical_geometry.page_data_size / + this->physical_geometry.chip_count; + oob_bytes_to_copy = + this->logical_geometry.page_oob_size / + this->physical_geometry.chip_count; + + } else { + + start = chip; + end = start + 1; + + data_bytes_to_copy = this->logical_geometry.page_data_size; + oob_bytes_to_copy = this->logical_geometry.page_oob_size; + + } + + /* If we're using the automatic operation, start the hardware now. */ + + if (use_automatic_op) { + add_event("Starting the automatic operation...", 0); + this->nfc->start_auto_write(this, start, end - start, 0, page); + } + + /* Loop over physical chips. */ + + add_event("Looping over physical chips...", 0); + + for (current_chip = start; current_chip < end; current_chip++) { + + /* Copy a page into the NFC. */ + + add_event("Copying to the NFC...", 0); + + nfc_util_copy_to_the_nfc(this, oob, nfc->page_data_size, + oob_bytes_to_copy); + oob += oob_bytes_to_copy; + + nfc_util_copy_to_the_nfc(this, data, 0, data_bytes_to_copy); + + data += data_bytes_to_copy; + + /* Check if we're using the automatic operation. */ + + if (use_automatic_op) { + + /* Wait for the write operation to finish. */ + + add_event("Waiting...", 0); + + this->nfc->wait_for_auto_write(this); + + } else { + + /* Select the current chip. */ + + this->nfc->select_chip(this, current_chip); + + /* Set up the chip. */ + + add_event("Sending the command and addresses...", 0); + + nfc_util_send_cmd_and_addrs(this, + NAND_CMD_SEQIN, 0, page); + + /* Send the page. */ + + add_event("Sending the page...", 0); + + this->nfc->send_page(this); + + /* Start programming the page. */ + + add_event("Programming the page...", 0); + + nfc_util_send_cmd(this, NAND_CMD_PAGEPROG); + + /* Wait until the page is finished. */ + + add_event("Waiting...", 0); + + nfc_util_wait_for_the_nfc(this, true); + + } + + } + + /* Get status. */ + + add_event("Gathering status...", 0); + + if (mal_get_status(this, chip) & NAND_STATUS_FAIL) { + add_event("Bad status", 0); + return_value = -EIO; + } else { + add_event("Good status", 0); + } + + /* Return. */ + + add_event("Exiting mal_write_a_page", -1); + + return return_value; + +} + +/** + * mal_erase_a_block() - Abstract block erase operation. + * + * Note that this function does *not* wait for the operation to finish. The + * caller is expected to call waitfunc() at some later time. + * + * @this: Per-device data. + * @chip: The logical chip of interest. + * @page: A logical page address that identifies the block to erase. + */ +static void mal_erase_a_block(struct imx_nfc_data *this, + unsigned chip, unsigned page) +{ + int we_are_interleaving; + int use_automatic_op; + unsigned int start; + unsigned int end; + unsigned int i; + struct physical_geometry *physical = &this->physical_geometry; + struct logical_geometry *logical = &this->logical_geometry; + + add_event("Entering mal_erase_a_block", 1); + + /* Establish some important facts. */ + + we_are_interleaving = logical->chip_count != physical->chip_count; + use_automatic_op = !!this->nfc->start_auto_erase; + + /* Apply the automatic operation override, if any. */ + + switch (this->auto_op_override) { + + case NEVER: + use_automatic_op = false; + break; + + case DRIVER_CHOICE: + break; + + case ALWAYS: + if (this->nfc->start_auto_erase) + use_automatic_op = true; + break; + + } + + /* Choose the loop bounds. */ + + if (we_are_interleaving) { + start = 0; + end = physical->chip_count; + } else { + start = chip; + end = start + 1; + } + + /* Check if we're using the automatic operation. */ + + if (use_automatic_op) { + + /* + * Start the operation. Note that we don't wait for it to + * finish because the HIL will call our waitfunc(). + */ + + add_event("Starting the automatic operation...", 0); + + this->nfc->start_auto_erase(this, start, end - start, page); + + } else { + + /* Loop over physical chips. */ + + add_event("Looping over physical chips...", 0); + + for (i = start; i < end; i++) { + + /* Select the current chip. */ + + this->nfc->select_chip(this, i); + + /* Set up the chip. */ + + nfc_util_send_cmd_and_addrs(this, + NAND_CMD_ERASE1, -1, page); + + /* Start the erase. */ + + nfc_util_send_cmd(this, NAND_CMD_ERASE2); + + /* + * If this is the last time through the loop, break out + * now so we don't try to wait (the HIL will call our + * waitfunc() for the final wait). + */ + + if (i >= (end - 1)) + break; + + /* Wait for the erase on the current chip to finish. */ + + nfc_util_wait_for_the_nfc(this, true); + + } + + } + + add_event("Exiting mal_erase_a_block", -1); + +} + +/** + * mal_is_block_bad() - Abstract bad block check. + * + * @this: Per-device data. + * @chip: The logical chip of interest. + * @page: The logical page number to read. + */ + #if 0 + +/* TODO: Finish this function and plug it in. */ + +static int mal_is_block_bad(struct imx_nfc_data *this, + unsigned chip, unsigned page) +{ + int we_are_interleaving; + unsigned int start; + unsigned int end; + unsigned int i; + uint8_t *p; + int return_value = 0; + struct nand_chip *nand = &this->nand; + struct physical_geometry *physical = &this->physical_geometry; + struct logical_geometry *logical = &this->logical_geometry; + + /* Figure out if we're interleaving. */ + + we_are_interleaving = logical->chip_count != physical->chip_count; + + /* + * We're about to use the NAND Flash MTD layer's buffer, so invalidate + * the page cache. + */ + + this->nand.pagebuf = -1; + + /* + * Read the OOB of the given page, using the NAND Flash MTD's buffer. + * + * Notice that ECC is off, which it *must* be when scanning block marks. + */ + + mal_read_a_page(this, false, + this->current_chip, this->page_address, 0, nand->oob_poi); + + /* Choose the loop bounds. */ + + if (we_are_interleaving) { + start = 0; + end = physical->chip_count; + } else { + start = chip; + end = start + 1; + } + + /* Start scanning at the beginning of the OOB data. */ + + p = nand->oob_poi; + + /* Loop over physical chips. */ + + add_event("Looping over physical chips...", 0); + + for (i = start; i < end; i++, p += 5) { + + /* Examine the OOB for this chip. */ + + if (p[nand->badblockpos] != 0xff) { + return_value = !0; + break; + } + + } + + /* Return. */ + + return return_value; + +} +#endif + +/** + * mil_init() - Initializes the MTD Interface Layer. + * + * @this: Per-device data. + */ +static void mil_init(struct imx_nfc_data *this) +{ + this->current_chip = -1; /* No chip is selected yet. */ + this->command_is_new = false; /* No command yet. */ +} + +/** + * mil_cmd_ctrl() - MTD Interface cmd_ctrl() + * + * @mtd: A pointer to the owning MTD. + * @dat: The data signals to present to the chip. + * @ctrl: The control signals to present to the chip. + */ +static void mil_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); +} + +/** + * mil_dev_ready() - MTD Interface dev_ready() + * + * @mtd: A pointer to the owning MTD. + */ +static int mil_dev_ready(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc dev_ready]\n"); + + add_event("Entering mil_dev_ready", 1); + + if (this->nfc->is_ready(this)) { + add_event("Exiting mil_dev_ready - Returning ready", -1); + return !0; + } else { + add_event("Exiting mil_dev_ready - Returning busy", -1); + return 0; + } + +} + +/** + * mil_select_chip() - MTD Interface select_chip() + * + * @mtd: A pointer to the owning MTD. + * @chip: The chip number to select, or -1 to select no chip. + */ +static void mil_select_chip(struct mtd_info *mtd, int chip) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc select_chip] chip: %d\n", chip); + + /* Figure out what kind of transition this is. */ + + if ((this->current_chip < 0) && (chip >= 0)) { + start_event_trace("Entering mil_select_chip"); + if (this->pdata->force_ce) + this->nfc->set_force_ce(this, true); + clk_enable(this->clock); + add_event("Exiting mil_select_chip", -1); + } else if ((this->current_chip >= 0) && (chip < 0)) { + add_event("Entering mil_select_chip", 1); + if (this->pdata->force_ce) + this->nfc->set_force_ce(this, false); + clk_disable(this->clock); + stop_event_trace("Exiting mil_select_chip"); + } else { + add_event("Entering mil_select_chip", 1); + add_event("Exiting mil_select_chip", -1); + } + + this->current_chip = chip; + +} + +/** + * mil_cmdfunc() - MTD Interface cmdfunc() + * + * This function handles NAND Flash command codes from the HIL. Since only the + * HIL calls this function (none of the reference implementations we use do), it + * needs to handle very few command codes. + * + * @mtd: A pointer to the owning MTD. + * @command: The command code. + * @column: The column address associated with this command code, or -1 if no + * column address applies. + * @page: The page address associated with this command code, or -1 if no + * page address applies. + */ +static void mil_cmdfunc(struct mtd_info *mtd, + unsigned command, int column, int page) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc cmdfunc] command: 0x%02x, " + "column: 0x%04x, page: 0x%06x\n", command, column, page); + + add_event("Entering mil_cmdfunc", 1); + + /* Record the command and the fact that it hasn't yet been sent. */ + + this->command = command; + this->command_is_new = true; + + /* + * Process the command code. + * + * Note the default case to trap unrecognized codes. Thus, every command + * we support must have a case here, even if we don't have to do any + * pre-processing work. If the HIL changes and starts sending commands + * we haven't explicitly implemented, this will warn us. + */ + + switch (command) { + + case NAND_CMD_READ0: + add_event("NAND_CMD_READ0", 0); + /* + * After calling this function to send the command and + * addresses, the HIL will call ecc.read_page() or + * ecc.read_page_raw() to collect the data. + * + * The column address from the HIL is always zero. The only + * information we need to keep from this call is the page + * address. + */ + this->page_address = page; + break; + + case NAND_CMD_STATUS: + add_event("NAND_CMD_STATUS", 0); + /* + * After calling this function to send the command, the HIL + * will call read_byte() once to collect the status. + */ + break; + + case NAND_CMD_READID: + add_event("NAND_CMD_READID", 0); + /* + * After calling this function to send the command, the HIL + * will call read_byte() repeatedly to collect ID bytes. + */ + break; + + case NAND_CMD_RESET: + add_event("NAND_CMD_RESET", 0); + mal_reset(this, this->current_chip); + break; + + default: + dev_emerg(this->dev, "Unsupported NAND Flash command code: " + "0x%02x\n", command); + BUG(); + break; + + } + + add_event("Exiting mil_cmdfunc", -1); + +} + +/** + * mil_waitfunc() - MTD Interface waifunc() + * + * This function blocks until the current chip is ready and then returns the + * contents of the chip's status register. The HIL only calls this function + * after starting an erase operation. + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + */ +static int mil_waitfunc(struct mtd_info *mtd, struct nand_chip *nand) +{ + int status; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc waitfunc]\n"); + + add_event("Entering mil_waitfunc", 1); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, true); + + /* Get the status. */ + + status = mal_get_status(this, this->current_chip); + + add_event("Exiting mil_waitfunc", -1); + + return status; + +} + +/** + * mil_read_byte() - MTD Interface read_byte(). + * + * @mtd: A pointer to the owning MTD. + */ +static uint8_t mil_read_byte(struct mtd_info *mtd) +{ + uint8_t byte = 0; + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + + add_event("Entering mil_read_byte", 1); + + /* + * The command sent by the HIL before it called this function determines + * how we get the byte we're going to return. + */ + + switch (this->command) { + + case NAND_CMD_STATUS: + add_event("NAND_CMD_STATUS", 0); + byte = mal_get_status(this, this->current_chip); + break; + + case NAND_CMD_READID: + add_event("NAND_CMD_READID", 0); + + /* + * Check if the command is new. If so, then the HIL just + * recently called cmdfunc(), so the current chip isn't selected + * and the command hasn't been sent to the chip. + */ + + if (this->command_is_new) { + add_event("Sending the \"Read ID\" command...", 0); + this->nfc->select_chip(this, this->current_chip); + nfc_util_send_cmd_and_addrs(this, + NAND_CMD_READID, 0, -1); + this->command_is_new = false; + } + + /* Read the ID byte. */ + + add_event("Reading the ID byte...", 0); + + byte = this->nfc->read_cycle(this); + + break; + + default: + dev_emerg(this->dev, "Unsupported NAND Flash command code: " + "0x%02x\n", this->command); + BUG(); + break; + + } + + DEBUG(MTD_DEBUG_LEVEL2, + "[imx_nfc read_byte] Returning: 0x%02x\n", byte); + + add_event("Exiting mil_read_byte", -1); + + return byte; + +} + +/** + * mil_read_word() - MTD Interface read_word(). + * + * @mtd: A pointer to the owning MTD. + */ +static uint16_t mil_read_word(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); + return 0; +} + +/** + * mil_read_buf() - MTD Interface read_buf(). + * + * @mtd: A pointer to the owning MTD. + * @buf: The destination buffer. + * @len: The number of bytes to read. + */ +static void mil_read_buf(struct mtd_info *mtd, uint8_t * buf, int len) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); +} + +/** + * mil_write_buf() - MTD Interface write_buf(). + * + * @mtd: A pointer to the owning MTD. + * @buf: The source buffer. + * @len: The number of bytes to read. + */ +static void mil_write_buf(struct mtd_info *mtd, const uint8_t * buf, int len) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); +} + +/** + * mil_verify_buf() - MTD Interface verify_buf(). + * + * @mtd: A pointer to the owning MTD. + * @buf: The destination buffer. + * @len: The number of bytes to read. + */ +static int mil_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); + return 0; +} + +/** + * mil_ecc_hwctl() - MTD Interface ecc.hwctl(). + * + * @mtd: A pointer to the owning MTD. + * @mode: The ECC mode. + */ +static void mil_ecc_hwctl(struct mtd_info *mtd, int mode) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); +} + +/** + * mil_ecc_calculate() - MTD Interface ecc.calculate(). + * + * @mtd: A pointer to the owning MTD. + * @dat: A pointer to the source data. + * @ecc_code: A pointer to a buffer that will receive the resulting ECC. + */ +static int mil_ecc_calculate(struct mtd_info *mtd, + const uint8_t *dat, uint8_t *ecc_code) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); + return 0; +} + +/** + * mil_ecc_correct() - MTD Interface ecc.correct(). + * + * @mtd: A pointer to the owning MTD. + * @dat: A pointer to the source data. + * @read_ecc: A pointer to the ECC that was read from the medium. + * @calc_ecc: A pointer to the ECC that was calculated for the source data. + */ +static int mil_ecc_correct(struct mtd_info *mtd, + uint8_t *dat, uint8_t *read_ecc, uint8_t *calc_ecc) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); + return 0; +} + +/** + * mil_ecc_read_page() - MTD Interface ecc.read_page(). + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @buf: A pointer to the destination buffer. + */ +static int mil_ecc_read_page(struct mtd_info *mtd, + struct nand_chip *nand, uint8_t *buf) +{ + int ecc_status; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc ecc_read_page]\n"); + + add_event("Entering mil_ecc_read_page", 1); + + /* Read the page. */ + + ecc_status = + mal_read_a_page(this, true, this->current_chip, + this->page_address, buf, nand->oob_poi); + + /* Propagate ECC information. */ + + if (ecc_status < 0) { + add_event("ECC Failure", 0); + mtd->ecc_stats.failed++; + } else if (ecc_status > 0) { + add_event("ECC Corrections", 0); + mtd->ecc_stats.corrected += ecc_status; + } + + add_event("Exiting mil_ecc_read_page", -1); + + return 0; + +} + +/** + * mil_ecc_read_page_raw() - MTD Interface ecc.read_page_raw(). + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @buf: A pointer to the destination buffer. + */ +static int mil_ecc_read_page_raw(struct mtd_info *mtd, + struct nand_chip *nand, uint8_t *buf) +{ + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc ecc_read_page_raw]\n"); + + add_event("Entering mil_ecc_read_page_raw", 1); + + mal_read_a_page(this, false, this->current_chip, + this->page_address, buf, nand->oob_poi); + + add_event("Exiting mil_ecc_read_page_raw", -1); + + return 0; + +} + +/** + * mil_ecc_write_page() - MTD Interface ecc.write_page(). + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @buf: A pointer to the source buffer. + */ +static void mil_ecc_write_page(struct mtd_info *mtd, + struct nand_chip *nand, const uint8_t *buf) +{ + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); +} + +/** + * mil_ecc_write_page_raw() - MTD Interface ecc.write_page_raw(). + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @buf: A pointer to the source buffer. + */ +static void mil_ecc_write_page_raw(struct mtd_info *mtd, + struct nand_chip *nand, const uint8_t *buf) +{ + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); +} + +/** + * mil_write_page() - MTD Interface ecc.write_page(). + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @buf: A pointer to the source buffer. + * @page: The page number to write. + * @cached: Indicates cached programming (ignored). + * @raw: Indicates not to use ECC. + */ +static int mil_write_page(struct mtd_info *mtd, + struct nand_chip *nand, const uint8_t *buf, + int page, int cached, int raw) +{ + int return_value; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc write_page]\n"); + + add_event("Entering mil_write_page", 1); + + return_value = mal_write_a_page(this, !raw, + this->current_chip, page, buf, nand->oob_poi); + + add_event("Exiting mil_write_page", -1); + + return return_value; + +} + +/** + * mil_ecc_read_oob() - MTD Interface read_oob(). + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @page: The page number to read. + * @sndcmd: Indicates this function should send a command to the chip before + * reading the out-of-band bytes. This is only false for small page + * chips that support auto-increment. + */ +static int mil_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *nand, + int page, int sndcmd) +{ + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc ecc_read_oob] " + "page: 0x%06x, sndcmd: %s\n", page, sndcmd ? "Yes" : "No"); + + add_event("Entering mil_ecc_read_oob", 1); + + mal_read_a_page(this, false, + this->current_chip, page, 0, nand->oob_poi); + + add_event("Exiting mil_ecc_read_oob", -1); + + /* + * Return true, indicating that the next call to this function must send + * a command. + */ + + return true; + +} + +/** + * mil_ecc_write_oob() - MTD Interface write_oob(). + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @page: The page number to write. + */ +static int mil_ecc_write_oob(struct mtd_info *mtd, + struct nand_chip *nand, int page) +{ + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc ecc_write_oob] page: 0x%06x\n", page); + + /* + * There are fundamental incompatibilities between the i.MX NFC and the + * NAND Flash MTD model that make it essentially impossible to write the + * out-of-band bytes. + */ + + dev_emerg(this->dev, "This driver doesn't support writing the OOB\n"); + WARN_ON(1); + + /* Return status. */ + + return -EIO; + +} + +/** + * mil_erase_cmd() - MTD Interface erase_cmd(). + * + * We set the erase_cmd pointer in struct nand_chip to point to this function. + * Thus, the HIL will call here for all erase operations. + * + * Strictly speaking, since the erase_cmd pointer is marked "Internal," we + * shouldn't be doing this. However, the only reason the HIL uses that pointer + * is to install a different function for erasing conventional NAND Flash or AND + * Flash. Since AND Flash is obsolete and we don't support it, this isn't + * important. + * + * Furthermore, to cleanly implement interleaving (which is critical to speeding + * up erase operations), we want to "hook into" the operation at the highest + * semantic level possible. If we don't hook this function, then the only way + * we'll know that an erase is happening is when the HIL calls cmdfunc() with + * an erase command. Implementing interleaving at that level is roughly a + * billion times less desirable. + * + * This function does *not* wait for the operation to finish. The HIL will call + * waitfunc() later to wait for the operation to finish. + * + * @mtd: A pointer to the owning MTD. + * @page: A page address that identifies the block to erase. + */ +static void mil_erase_cmd(struct mtd_info *mtd, int page) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc erase_cmd] page: 0x%06x\n", page); + + add_event("Entering mil_erase_cmd", 1); + + mal_erase_a_block(this, this->current_chip, page); + + add_event("Exiting mil_erase_cmd", -1); + +} + +/** + * mil_block_bad() - MTD Interface block_bad(). + * + * @mtd: A pointer to the owning MTD. + * @ofs: The offset of the block of interest, from the start of the medium. + * @getchip: Indicates this function must acquire the MTD before using it. + */ +#if 0 + +/* TODO: Finish this function and plug it in. */ + +static int mil_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip) +{ + unsigned int chip; + unsigned int page; + int return_value; + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc block_bad] page: 0x%06x\n", page); + + add_event("Entering mil_block_bad", 1); + + /* Compute the logical chip number that contains the given offset. */ + + chip = (unsigned int) (ofs >> nand->chip_shift); + + /* Compute the logical page address within the logical chip. */ + + page = ((unsigned int) (ofs >> nand->page_shift)) & nand->pagemask; + + /* Check if the block is bad. */ + + return_value = mal_is_block_bad(this, chip, page); + + if (return_value) + add_event("Bad block", 0); + + /* Return. */ + + add_event("Exiting mil_block_bad", -1); + + return return_value; + +} +#endif + +/** + * mil_scan_bbt() - MTD Interface scan_bbt(). + * + * The HIL calls this function once, when it initializes the NAND Flash MTD. + * + * Nominally, the purpose of this function is to look for or create the bad + * block table. In fact, since the HIL calls this function at the very end of + * the initialization process started by nand_scan(), and the HIL doesn't have a + * more formal mechanism, everyone "hooks" this function to continue the + * initialization process. + * + * At this point, the physical NAND Flash chips have been identified and + * counted, so we know the physical geometry. This enables us to make some + * important configuration decisions. + * + * The return value of this function propogates directly back to this driver's + * call to nand_scan(). Anything other than zero will cause this driver to + * tear everything down and declare failure. + * + * @mtd: A pointer to the owning MTD. + */ +static int mil_scan_bbt(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc scan_bbt] \n"); + + add_event("Entering mil_scan_bbt", 1); + + /* + * We replace the erase_cmd() function that the MTD NAND Flash system + * has installed with our own. See mil_erase_cmd() for the reasons. + */ + + nand->erase_cmd = mil_erase_cmd; + + /* + * Tell MTD users that the out-of-band area can't be written. + * + * This flag is not part of the standard kernel source tree. It comes + * from a patch that touches both MTD and JFFS2. + * + * The problem is that, without this patch, JFFS2 believes it can write + * the data area and the out-of-band area separately. This is wrong for + * two reasons: + * + * 1) Our NFC distributes out-of-band bytes throughout the page, + * intermingled with the data, and covered by the same ECC. + * Thus, it's not possible to write the out-of-band bytes and + * data bytes separately. + * + * 2) Large page (MLC) Flash chips don't support partial page + * writes. You must write the entire page at a time. Thus, even + * if our NFC didn't force you to write out-of-band and data + * bytes together, it would *still* be a bad idea to do + * otherwise. + */ + + mtd->flags &= ~MTD_OOB_WRITEABLE; + + /* Set up geometry. */ + + mal_set_geometry(this); + + /* We use the reference implementation for bad block management. */ + + add_event("Exiting mil_scan_bbt", -1); + + return nand_scan_bbt(mtd, nand->badblock_pattern); + +} + +/** + * parse_bool_param() - Parses the value of a boolean parameter string. + * + * @s: The string to parse. + */ +static int parse_bool_param(const char *s) +{ + + if (!strcmp(s, "1") || !strcmp(s, "on") || + !strcmp(s, "yes") || !strcmp(s, "true")) { + return 1; + } else if (!strcmp(s, "0") || !strcmp(s, "off") || + !strcmp(s, "no") || !strcmp(s, "false")) { + return 0; + } else { + return -1; + } + +} + +/** + * set_module_enable() - Controls whether this driver is enabled. + * + * Note that this state can be controlled from the command line. Disabling this + * driver is sometimes useful for debugging. + * + * @s: The new value of the parameter. + * @kp: The owning kernel parameter. + */ +static int set_module_enable(const char *s, struct kernel_param *kp) +{ + + switch (parse_bool_param(s)) { + + case 1: + imx_nfc_module_enable = true; + break; + + case 0: + imx_nfc_module_enable = false; + break; + + default: + return -EINVAL; + break; + + } + + return 0; + +} + +/** + * get_module_enable() - Indicates whether this driver is enabled. + * + * @p: A pointer to a (small) buffer that will receive the response. + * @kp: The owning kernel parameter. + */ +static int get_module_enable(char *p, struct kernel_param *kp) +{ + p[0] = imx_nfc_module_enable ? '1' : '0'; + p[1] = 0; + return 1; +} + +#ifdef EVENT_REPORTING + +/** + * set_module_report_events() - Controls whether this driver reports events. + * + * @s: The new value of the parameter. + * @kp: The owning kernel parameter. + */ +static int set_module_report_events(const char *s, struct kernel_param *kp) +{ + + switch (parse_bool_param(s)) { + + case 1: + imx_nfc_module_report_events = true; + break; + + case 0: + imx_nfc_module_report_events = false; + reset_event_trace(); + break; + + default: + return -EINVAL; + break; + + } + + return 0; + +} + +/** + * get_module_report_events() - Indicates whether the driver reports events. + * + * @p: A pointer to a (small) buffer that will receive the response. + * @kp: The owning kernel parameter. + */ +static int get_module_report_events(char *p, struct kernel_param *kp) +{ + p[0] = imx_nfc_module_report_events ? '1' : '0'; + p[1] = 0; + return 1; +} + +/** + * set_module_dump_events() - Forces the driver to dump current events. + * + * @s: The new value of the parameter. + * @kp: The owning kernel parameter. + */ +static int set_module_dump_events(const char *s, struct kernel_param *kp) +{ + dump_event_trace(); + return 0; +} + +#endif /*EVENT_REPORTING*/ + +/** + * set_module_interleave_override() - Controls the interleave override. + * + * @s: The new value of the parameter. + * @kp: The owning kernel parameter. + */ +static int set_module_interleave_override(const char *s, + struct kernel_param *kp) +{ + + if (!strcmp(s, "-1")) + imx_nfc_module_interleave_override = NEVER; + else if (!strcmp(s, "0")) + imx_nfc_module_interleave_override = DRIVER_CHOICE; + else if (!strcmp(s, "1")) + imx_nfc_module_interleave_override = ALWAYS; + else + return -EINVAL; + + return 0; + +} + +/** + * get_module_interleave_override() - Indicates the interleave override state. + * + * @p: A pointer to a (small) buffer that will receive the response. + * @kp: The owning kernel parameter. + */ +static int get_module_interleave_override(char *p, struct kernel_param *kp) +{ + return sprintf(p, "%d", imx_nfc_module_interleave_override); +} + +/** + * set_force_bytewise_copy() - Controls forced bytewise copy from/to the NFC. + * + * @s: The new value of the parameter. + * @kp: The owning kernel parameter. + */ +static int set_module_force_bytewise_copy(const char *s, + struct kernel_param *kp) +{ + + switch (parse_bool_param(s)) { + + case 1: + imx_nfc_module_force_bytewise_copy = true; + break; + + case 0: + imx_nfc_module_force_bytewise_copy = false; + break; + + default: + return -EINVAL; + break; + + } + + return 0; + +} + +/** + * get_force_bytewise_copy() - Indicates whether bytewise copy is being forced. + * + * @p: A pointer to a (small) buffer that will receive the response. + * @kp: The owning kernel parameter. + */ +static int get_module_force_bytewise_copy(char *p, struct kernel_param *kp) +{ + p[0] = imx_nfc_module_force_bytewise_copy ? '1' : '0'; + p[1] = 0; + return 1; +} + +/* Module attributes that appear in sysfs. */ + +module_param_call(enable, set_module_enable, get_module_enable, 0, 0444); +MODULE_PARM_DESC(enable, "enables/disables probing"); + +#ifdef EVENT_REPORTING +module_param_call(report_events, + set_module_report_events, get_module_report_events, 0, 0644); +MODULE_PARM_DESC(report_events, "enables/disables event reporting"); + +module_param_call(dump_events, set_module_dump_events, 0, 0, 0644); +MODULE_PARM_DESC(dump_events, "forces current event dump"); +#endif + +module_param_call(interleave_override, set_module_interleave_override, + get_module_interleave_override, 0, 0444); +MODULE_PARM_DESC(interleave_override, "overrides interleaving choice"); + +module_param_call(force_bytewise_copy, set_module_force_bytewise_copy, + get_module_force_bytewise_copy, 0, 0644); +MODULE_PARM_DESC(force_bytewise_copy, "forces bytewise copy from/to NFC"); + +/** + * show_device_platform_info() - Shows the device's platform information. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_platform_info(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int o = 0; + unsigned int i; + void *buffer_base; + void *primary_base; + void *secondary_base; + unsigned int interrupt_number; + struct resource *r; + struct imx_nfc_data *this = dev_get_drvdata(dev); + struct platform_device *pdev = this->pdev; + struct imx_nfc_platform_data *pdata = this->pdata; + struct mtd_partition *partition; + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, + IMX_NFC_BUFFERS_ADDR_RES_NAME); + + buffer_base = (void *) r->start; + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, + IMX_NFC_PRIMARY_REGS_ADDR_RES_NAME); + + primary_base = (void *) r->start; + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, + IMX_NFC_SECONDARY_REGS_ADDR_RES_NAME); + + secondary_base = (void *) r->start; + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + IMX_NFC_INTERRUPT_RES_NAME); + + interrupt_number = r->start; + + o += sprintf(buf, + "NFC Major Version : %u\n" + "NFC Minor Version : %u\n" + "Force CE : %s\n" + "Target Cycle in ns : %u\n" + "Clock Name : %s\n" + "Interleave : %s\n" + "Buffer Base : 0x%p\n" + "Primary Registers Base : 0x%p\n" + "Secondary Registers Base: 0x%p\n" + "Interrupt Number : %u\n" + , + pdata->nfc_major_version, + pdata->nfc_minor_version, + pdata->force_ce ? "Yes" : "No", + pdata->target_cycle_in_ns, + pdata->clock_name, + pdata->interleave ? "Yes" : "No", + buffer_base, + primary_base, + secondary_base, + interrupt_number + ); + + #ifdef CONFIG_MTD_PARTITIONS + + o += sprintf(buf + o, + "Partition Count : %u\n" + , + pdata->partition_count + ); + + /* Loop over partitions. */ + + for (i = 0; i < pdata->partition_count; i++) { + + partition = pdata->partitions + i; + + o += sprintf(buf+o, " [%d]\n", i); + o += sprintf(buf+o, " Name : %s\n", partition->name); + + switch (partition->offset) { + + case MTDPART_OFS_NXTBLK: + o += sprintf(buf+o, " Offset: " + "MTDPART_OFS_NXTBLK\n"); + break; + case MTDPART_OFS_APPEND: + o += sprintf(buf+o, " Offset: " + "MTDPART_OFS_APPEND\n"); + break; + default: + o += sprintf(buf+o, " Offset: %u (%u MiB)\n", + partition->offset, + partition->offset / (1024 * 1024)); + break; + + } + + if (partition->size == MTDPART_SIZ_FULL) { + o += sprintf(buf+o, " Size : " + "MTDPART_SIZ_FULL\n"); + } else { + o += sprintf(buf+o, " Size : %u (%u MiB)\n", + partition->size, + partition->size / (1024 * 1024)); + } + + } + + #endif + + return o; + +} + +/** + * show_device_physical_geometry() - Shows the physical Flash device geometry. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_physical_geometry(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + struct physical_geometry *physical = &this->physical_geometry; + + return sprintf(buf, + "Chip Count : %u\n" + "Chip Size in Bytes : %llu\n" + "Block Size in Bytes : %u\n" + "Page Data Size in Bytes: %u\n" + "Page OOB Size in Bytes : %u\n" + , + physical->chip_count, + physical->chip_size, + physical->block_size, + physical->page_data_size, + physical->page_oob_size + ); + +} + +/** + * show_device_nfc_info() - Shows the NFC-specific information. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_nfc_info(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long parent_clock_rate_in_hz; + unsigned long clock_rate_in_hz; + struct clk *parent_clock; + struct imx_nfc_data *this = dev_get_drvdata(dev); + struct nfc_hal *nfc = this->nfc; + + parent_clock = clk_get_parent(this->clock); + parent_clock_rate_in_hz = clk_get_rate(parent_clock); + clock_rate_in_hz = clk_get_rate(this->clock); + + return sprintf(buf, + "Major Version : %u\n" + "Minor Version : %u\n" + "Max Chip Count : %u\n" + "Max Buffer Count : %u\n" + "Spare Buffer Stride : %u\n" + "Has Secondary Registers : %s\n" + "Can Be Symmetric : %s\n" + "Exposes Ready/Busy : %s\n" + "Parent Clock Rate in Hz : %lu\n" + "Clock Rate in Hz : %lu\n" + "Symmetric Clock : %s\n" + , + nfc->major_version, + nfc->minor_version, + nfc->max_chip_count, + nfc->max_buffer_count, + nfc->spare_buf_stride, + nfc->has_secondary_regs ? "Yes" : "No", + nfc->can_be_symmetric ? "Yes" : "No", + nfc->is_ready ? "Yes" : "No", + parent_clock_rate_in_hz, + clock_rate_in_hz, + this->nfc->get_symmetric(this) ? "Yes" : "No" + ); + +} + +/** + * show_device_nfc_geometry() - Shows the NFC view of the device geometry. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_nfc_geometry(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + + return sprintf(buf, + "Page Data Size in Bytes : %u\n" + "Page OOB Size in Bytes : %u\n" + "ECC Algorithm : %s\n" + "ECC Strength : %u\n" + "Buffers in Use : %u\n" + "Spare Buffer Size in Use: %u\n" + "Spare Buffer Spillover : %u\n" + , + this->nfc_geometry->page_data_size, + this->nfc_geometry->page_oob_size, + this->nfc_geometry->ecc_algorithm, + this->nfc_geometry->ecc_strength, + this->nfc_geometry->buffer_count, + this->nfc_geometry->spare_buf_size, + this->nfc_geometry->spare_buf_spill + ); + +} + +/** + * show_device_logical_geometry() - Shows the logical device geometry. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_logical_geometry(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + struct logical_geometry *logical = &this->logical_geometry; + + return sprintf(buf, + "Chip Count : %u\n" + "Chip Size in Bytes : %u\n" + "Usable Size in Bytes : %u\n" + "Block Size in Bytes : %u\n" + "Page Data Size in Bytes: %u\n" + "Page OOB Size in Bytes : %u\n" + , + logical->chip_count, + logical->chip_size, + logical->usable_size, + logical->block_size, + logical->page_data_size, + logical->page_oob_size + ); + +} + +/** + * show_device_mtd_nand_info() - Shows the device's MTD NAND-specific info. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_mtd_nand_info(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int o = 0; + unsigned int i; + unsigned int j; + static const unsigned int columns = 8; + struct imx_nfc_data *this = dev_get_drvdata(dev); + struct nand_chip *nand = &this->nand; + + o += sprintf(buf + o, + "Options : 0x%08x\n" + "Chip Count : %u\n" + "Chip Size : %lu\n" + "Minimum Writable Size: %u\n" + "Page Shift : %u\n" + "Page Mask : 0x%x\n" + "Block Shift : %u\n" + "BBT Block Shift : %u\n" + "Chip Shift : %u\n" + "Block Mark Offset : %u\n" + "Cached Page Number : %d\n" + , + nand->options, + nand->numchips, + nand->chipsize, + nand->subpagesize, + nand->page_shift, + nand->pagemask, + nand->phys_erase_shift, + nand->bbt_erase_shift, + nand->chip_shift, + nand->badblockpos, + nand->pagebuf + ); + + o += sprintf(buf + o, + "ECC Byte Count : %u\n" + , + nand->ecc.layout->eccbytes + ); + + /* Loop over rows. */ + + for (i = 0; (i * columns) < nand->ecc.layout->eccbytes; i++) { + + /* Loop over columns within rows. */ + + for (j = 0; j < columns; j++) { + + if (((i * columns) + j) >= nand->ecc.layout->eccbytes) + break; + + o += sprintf(buf + o, " %3u", + nand->ecc.layout->eccpos[(i * columns) + j]); + + } + + o += sprintf(buf + o, "\n"); + + } + + o += sprintf(buf + o, + "OOB Available Bytes : %u\n" + , + nand->ecc.layout->oobavail + ); + + j = 0; + + for (i = 0; j < nand->ecc.layout->oobavail; i++) { + + j += nand->ecc.layout->oobfree[i].length; + + o += sprintf(buf + o, + " [%3u, %2u]\n" + , + nand->ecc.layout->oobfree[i].offset, + nand->ecc.layout->oobfree[i].length + ); + + } + + return o; + +} + +/** + * show_device_mtd_info() - Shows the device's MTD-specific information. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_mtd_info(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int o = 0; + unsigned int i; + unsigned int j; + static const unsigned int columns = 8; + struct imx_nfc_data *this = dev_get_drvdata(dev); + struct mtd_info *mtd = &this->mtd; + + o += sprintf(buf + o, + "Name : %s\n" + "Type : %u\n" + "Flags : 0x%08x\n" + "Size in Bytes : %u\n" + "Erase Region Count : %d\n" + "Erase Size in Bytes: %u\n" + "Write Size in Bytes: %u\n" + "OOB Size in Bytes : %u\n" + "Errors Corrected : %u\n" + "Failed Reads : %u\n" + "Bad Block Count : %u\n" + "BBT Block Count : %u\n" + , + mtd->name, + mtd->type, + mtd->flags, + mtd->size, + mtd->numeraseregions, + mtd->erasesize, + mtd->writesize, + mtd->oobsize, + mtd->ecc_stats.corrected, + mtd->ecc_stats.failed, + mtd->ecc_stats.badblocks, + mtd->ecc_stats.bbtblocks + ); + + o += sprintf(buf + o, + "ECC Byte Count : %u\n" + , + mtd->ecclayout->eccbytes + ); + + /* Loop over rows. */ + + for (i = 0; (i * columns) < mtd->ecclayout->eccbytes; i++) { + + /* Loop over columns within rows. */ + + for (j = 0; j < columns; j++) { + + if (((i * columns) + j) >= mtd->ecclayout->eccbytes) + break; + + o += sprintf(buf + o, " %3u", + mtd->ecclayout->eccpos[(i * columns) + j]); + + } + + o += sprintf(buf + o, "\n"); + + } + + o += sprintf(buf + o, + "OOB Available Bytes: %u\n" + , + mtd->ecclayout->oobavail + ); + + j = 0; + + for (i = 0; j < mtd->ecclayout->oobavail; i++) { + + j += mtd->ecclayout->oobfree[i].length; + + o += sprintf(buf + o, + " [%3u, %2u]\n" + , + mtd->ecclayout->oobfree[i].offset, + mtd->ecclayout->oobfree[i].length + ); + + } + + return o; + +} + +/** + * show_device_bbt_pages() - Shows the pages in which BBT's appear. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_bbt_pages(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int o = 0; + unsigned int i; + struct imx_nfc_data *this = dev_get_drvdata(dev); + struct nand_chip *nand = &this->nand; + + /* Loop over main BBT pages. */ + + if (nand->bbt_td) + for (i = 0; i < NAND_MAX_CHIPS; i++) + o += sprintf(buf + o, "%d: 0x%08x\n", + i, nand->bbt_td->pages[i]); + + /* Loop over mirror BBT pages. */ + + if (nand->bbt_md) + for (i = 0; i < NAND_MAX_CHIPS; i++) + o += sprintf(buf + o, "%d: 0x%08x\n", + i, nand->bbt_md->pages[i]); + + return o; + +} + +/** + * show_device_cycle_in_ns() - Shows the device's cycle in ns. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_cycle_in_ns(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + return sprintf(buf, "%u\n", get_cycle_in_ns(this)); +} + +/** + * store_device_cycle_in_ns() - Sets the device's cycle in ns. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer containing a new attribute value. + * @size: The size of the buffer. + */ +static ssize_t store_device_cycle_in_ns(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int error; + unsigned long new_cycle_in_ns; + struct imx_nfc_data *this = dev_get_drvdata(dev); + + /* Look for nonsense. */ + + if (!size) + return -EINVAL; + + /* Try to understand the new cycle period. */ + + if (strict_strtoul(buf, 0, &new_cycle_in_ns)) + return -EINVAL; + + /* Try to implement the new cycle period. */ + + error = this->nfc->set_closest_cycle(this, new_cycle_in_ns); + + if (error) + return -EINVAL; + + /* Return success. */ + + return size; + +} + +/** + * show_device_interrupt_override() - Shows the device's interrupt override. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_interrupt_override(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + + switch (this->interrupt_override) { + + case NEVER: + return sprintf(buf, "-1\n"); + break; + + case DRIVER_CHOICE: + return sprintf(buf, "0\n"); + break; + + case ALWAYS: + return sprintf(buf, "1\n"); + break; + + default: + return sprintf(buf, "?\n"); + break; + + } + +} + +/** + * store_device_interrupt_override() - Sets the device's interrupt override. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer containing a new attribute value. + * @size: The size of the buffer. + */ +static ssize_t store_device_interrupt_override(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + + if (!strcmp(buf, "-1")) + this->interrupt_override = NEVER; + else if (!strcmp(buf, "0")) + this->interrupt_override = DRIVER_CHOICE; + else if (!strcmp(buf, "1")) + this->interrupt_override = ALWAYS; + else + return -EINVAL; + + return size; + +} + +/** + * show_device_auto_op_override() - Shows the device's automatic op override. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_auto_op_override(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + + switch (this->auto_op_override) { + + case NEVER: + return sprintf(buf, "-1\n"); + break; + + case DRIVER_CHOICE: + return sprintf(buf, "0\n"); + break; + + case ALWAYS: + return sprintf(buf, "1\n"); + break; + + default: + return sprintf(buf, "?\n"); + break; + + } + +} + +/** + * store_device_auto_op_override() - Sets the device's automatic op override. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer containing a new attribute value. + * @size: The size of the buffer. + */ +static ssize_t store_device_auto_op_override(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + + if (!strcmp(buf, "-1")) + this->auto_op_override = NEVER; + else if (!strcmp(buf, "0")) + this->auto_op_override = DRIVER_CHOICE; + else if (!strcmp(buf, "1")) + this->auto_op_override = ALWAYS; + else + return -EINVAL; + + return size; + +} + +/** + * show_device_inject_ecc_error() - Shows the device's error injection flag. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_inject_ecc_error(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", this->inject_ecc_error); + +} + +/** + * store_device_inject_ecc_error() - Sets the device's error injection flag. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer containing a new attribute value. + * @size: The size of the buffer. + */ +static ssize_t store_device_inject_ecc_error(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + unsigned long new_inject_ecc_error; + struct imx_nfc_data *this = dev_get_drvdata(dev); + + /* Look for nonsense. */ + + if (!size) + return -EINVAL; + + /* Try to understand the new cycle period. */ + + if (strict_strtol(buf, 0, &new_inject_ecc_error)) + return -EINVAL; + + /* Store the value. */ + + this->inject_ecc_error = new_inject_ecc_error; + + /* Return success. */ + + return size; + +} + +/** + * store_device_invalidate_page_cache() - Invalidates the device's page cache. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer containing a new attribute value. + * @size: The size of the buffer. + */ +static ssize_t store_device_invalidate_page_cache(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + + /* Invalidate the page cache. */ + + this->nand.pagebuf = -1; + + /* Return success. */ + + return size; + +} + +/* Device attributes that appear in sysfs. */ + +static DEVICE_ATTR(platform_info , 0444, show_device_platform_info , 0); +static DEVICE_ATTR(physical_geometry, 0444, show_device_physical_geometry, 0); +static DEVICE_ATTR(nfc_info , 0444, show_device_nfc_info , 0); +static DEVICE_ATTR(nfc_geometry , 0444, show_device_nfc_geometry , 0); +static DEVICE_ATTR(logical_geometry , 0444, show_device_logical_geometry , 0); +static DEVICE_ATTR(mtd_nand_info , 0444, show_device_mtd_nand_info , 0); +static DEVICE_ATTR(mtd_info , 0444, show_device_mtd_info , 0); +static DEVICE_ATTR(bbt_pages , 0444, show_device_bbt_pages , 0); + +static DEVICE_ATTR(cycle_in_ns, 0644, + show_device_cycle_in_ns, store_device_cycle_in_ns); + +static DEVICE_ATTR(interrupt_override, 0644, + show_device_interrupt_override, store_device_interrupt_override); + +static DEVICE_ATTR(auto_op_override, 0644, + show_device_auto_op_override, store_device_auto_op_override); + +static DEVICE_ATTR(inject_ecc_error, 0644, + show_device_inject_ecc_error, store_device_inject_ecc_error); + +static DEVICE_ATTR(invalidate_page_cache, 0644, + 0, store_device_invalidate_page_cache); + +static struct device_attribute *device_attributes[] = { + &dev_attr_platform_info, + &dev_attr_physical_geometry, + &dev_attr_nfc_info, + &dev_attr_nfc_geometry, + &dev_attr_logical_geometry, + &dev_attr_mtd_nand_info, + &dev_attr_mtd_info, + &dev_attr_bbt_pages, + &dev_attr_cycle_in_ns, + &dev_attr_interrupt_override, + &dev_attr_auto_op_override, + &dev_attr_inject_ecc_error, + &dev_attr_invalidate_page_cache, +}; + +/** + * validate_the_platform() - Validates information about the platform. + * + * Note that this function doesn't validate the NFC version. That's done when + * the probing process attempts to configure for the specific hardware version. + * + * @pdev: A pointer to the platform device. + */ +static int validate_the_platform(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx_nfc_platform_data *pdata = pdev->dev.platform_data; + + /* Validate the clock name. */ + + if (!pdata->clock_name) { + dev_err(dev, "No clock name\n"); + return -ENXIO; + } + + /* Validate the partitions. */ + + if ((pdata->partitions && (!pdata->partition_count)) || + (!pdata->partitions && (pdata->partition_count))) { + dev_err(dev, "Bad partition data\n"); + return -ENXIO; + } + + /* Return success */ + + return 0; + +} + +/** + * set_up_the_nfc_hal() - Sets up for the specific NFC hardware HAL. + * + * @this: Per-device data. + */ +static int set_up_the_nfc_hal(struct imx_nfc_data *this) +{ + unsigned int i; + struct nfc_hal *p; + struct imx_nfc_platform_data *pdata = this->pdata; + + for (i = 0; i < ARRAY_SIZE(nfc_hals); i++) { + + p = nfc_hals[i]; + + /* + * Restrict to 3.2 until others are fully implemented. + * + * TODO: Remove this. + */ + + if ((p->major_version != 3) && (p->minor_version != 2)) + continue; + + if ((p->major_version == pdata->nfc_major_version) && + (p->minor_version == pdata->nfc_minor_version)) { + this->nfc = p; + return 0; + break; + } + + } + + dev_err(this->dev, "Unkown NFC version %d.%d\n", + pdata->nfc_major_version, pdata->nfc_minor_version); + + return !0; + +} + +/** + * acquire_resources() - Tries to acquire resources. + * + * @this: Per-device data. + */ +static int acquire_resources(struct imx_nfc_data *this) +{ + + int error = 0; + struct platform_device *pdev = this->pdev; + struct device *dev = this->dev; + struct imx_nfc_platform_data *pdata = this->pdata; + struct resource *r; + + /* Find the buffers and map them. */ + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, + IMX_NFC_BUFFERS_ADDR_RES_NAME); + + if (!r) { + dev_err(dev, "Can't get '%s'\n", IMX_NFC_BUFFERS_ADDR_RES_NAME); + error = -ENXIO; + goto exit_buffers; + } + + this->buffers = ioremap(r->start, r->end - r->start + 1); + + if (!this->buffers) { + dev_err(dev, "Can't remap buffers\n"); + error = -EIO; + goto exit_buffers; + } + + /* Find the primary registers and map them. */ + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, + IMX_NFC_PRIMARY_REGS_ADDR_RES_NAME); + + if (!r) { + dev_err(dev, "Can't get '%s'\n", + IMX_NFC_PRIMARY_REGS_ADDR_RES_NAME); + error = -ENXIO; + goto exit_primary_registers; + } + + this->primary_regs = ioremap(r->start, r->end - r->start + 1); + + if (!this->primary_regs) { + dev_err(dev, "Can't remap the primary registers\n"); + error = -EIO; + goto exit_primary_registers; + } + + /* Check for secondary registers. */ + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, + IMX_NFC_SECONDARY_REGS_ADDR_RES_NAME); + + if (r && !this->nfc->has_secondary_regs) { + + dev_err(dev, "Resource '%s' should not be present\n", + IMX_NFC_SECONDARY_REGS_ADDR_RES_NAME); + error = -ENXIO; + goto exit_secondary_registers; + + } + + if (this->nfc->has_secondary_regs) { + + if (!r) { + dev_err(dev, "Can't get '%s'\n", + IMX_NFC_SECONDARY_REGS_ADDR_RES_NAME); + error = -ENXIO; + goto exit_secondary_registers; + } + + this->secondary_regs = ioremap(r->start, r->end - r->start + 1); + + if (!this->secondary_regs) { + dev_err(dev, + "Can't remap the secondary registers\n"); + error = -EIO; + goto exit_secondary_registers; + } + + } + + /* Find out what our interrupt is and try to own it. */ + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + IMX_NFC_INTERRUPT_RES_NAME); + + if (!r) { + dev_err(dev, "Can't get '%s'\n", IMX_NFC_INTERRUPT_RES_NAME); + error = -ENXIO; + goto exit_irq; + } + + this->interrupt = r->start; + + error = request_irq(this->interrupt, + nfc_util_isr, 0, this->dev->bus_id, this); + + if (error) { + dev_err(dev, "Can't own interrupt %d\n", this->interrupt); + goto exit_irq; + } + + /* Find out the name of our clock and try to own it. */ + + this->clock = clk_get(dev, pdata->clock_name); + + if (this->clock == ERR_PTR(-ENOENT)) { + dev_err(dev, "Can't get clock '%s'\n", pdata->clock_name); + error = -ENXIO; + goto exit_clock; + } + + /* Return success. */ + + return 0; + + /* Error return paths begin here. */ + +exit_clock: + free_irq(this->interrupt, this); +exit_irq: + if (this->secondary_regs) + iounmap(this->secondary_regs); +exit_secondary_registers: + iounmap(this->primary_regs); +exit_primary_registers: + iounmap(this->buffers); +exit_buffers: + return error; + +} + +/** + * release_resources() - Releases resources. + * + * @this: Per-device data. + */ +static void release_resources(struct imx_nfc_data *this) +{ + + /* Release our clock. */ + + clk_disable(this->clock); + clk_put(this->clock); + + /* Release our interrupt. */ + + free_irq(this->interrupt, this); + + /* Release mapped memory. */ + + iounmap(this->buffers); + iounmap(this->primary_regs); + if (this->secondary_regs) + iounmap(this->secondary_regs); + +} + +/** + * register_with_mtd() - Registers this medium with MTD. + * + * @this: Per-device data. + */ +static int register_with_mtd(struct imx_nfc_data *this) +{ + int error = 0; + struct mtd_info *mtd = &this->mtd; + struct nand_chip *nand = &this->nand; + struct device *dev = this->dev; + struct imx_nfc_platform_data *pdata = this->pdata; + + /* Link the MTD structures together, along with our own data. */ + + mtd->priv = nand; + nand->priv = this; + + /* Prepare the MTD structure. */ + + mtd->owner = THIS_MODULE; + + /* + * Signal Control Functions + */ + + nand->cmd_ctrl = mil_cmd_ctrl; + + /* + * Chip Control Functions + * + * Not all of our NFC hardware versions expose Ready/Busy signals. For + * versions that don't, the is_ready function pointer will be NULL. In + * those cases, we leave the dev_ready member unassigned, which will + * cause the HIL to use a reference implementation's algorithm to + * discover when the hardware is ready. + */ + + nand->select_chip = mil_select_chip; + nand->cmdfunc = mil_cmdfunc; + nand->waitfunc = mil_waitfunc; + + if (this->nfc->is_ready) + nand->dev_ready = mil_dev_ready; + + /* + * Low-level I/O Functions + */ + + nand->read_byte = mil_read_byte; + nand->read_word = mil_read_word; + nand->read_buf = mil_read_buf; + nand->write_buf = mil_write_buf; + nand->verify_buf = mil_verify_buf; + + /* + * ECC Control Functions + */ + + nand->ecc.hwctl = mil_ecc_hwctl; + nand->ecc.calculate = mil_ecc_calculate; + nand->ecc.correct = mil_ecc_correct; + + /* + * ECC-Aware I/O Functions + */ + + nand->ecc.read_page = mil_ecc_read_page; + nand->ecc.read_page_raw = mil_ecc_read_page_raw; + nand->ecc.write_page = mil_ecc_write_page; + nand->ecc.write_page_raw = mil_ecc_write_page_raw; + + /* + * High-level I/O Functions + */ + + nand->write_page = mil_write_page; + nand->ecc.read_oob = mil_ecc_read_oob; + nand->ecc.write_oob = mil_ecc_write_oob; + + /* + * Bad Block Marking Functions + * + * We want to use the reference block_bad and block_markbad + * implementations, so we don't assign those members. + */ + + nand->scan_bbt = mil_scan_bbt; + + /* + * Error Recovery Functions + * + * We don't fill in the errstat function pointer because it's optional + * and we don't have a need for it. + */ + + /* + * Device Attributes + * + * We don't fill in the chip_delay member because we don't have a need + * for it. + * + * We support only 8-bit Flash bus width. + */ + + /* + * ECC Attributes + * + * Members that aren't set here are configured by a version-specific + * set_geometry() function. + */ + + nand->ecc.mode = NAND_ECC_HW; + nand->ecc.size = NFC_MAIN_BUF_SIZE; + nand->ecc.prepad = 0; + nand->ecc.postpad = 0; + + /* + * Bad Block Management Attributes + * + * We don't fill in the following attributes: + * + * badblockpos + * bbt + * badblock_pattern + * bbt_erase_shift + * + * These attributes aren't hardware-specific, and the HIL makes fine + * choices without our help. + */ + + nand->options |= NAND_USE_FLASH_BBT; + nand->bbt_td = &bbt_main_descriptor; + nand->bbt_md = &bbt_mirror_descriptor; + + /* + * Device Control + * + * We don't fill in the controller attribute. In principle, we could set + * up a structure to represent the controller. However, since it's + * vanishingly improbable that we'll have more than one medium behind + * the controller, it's not worth the effort. We let the HIL handle it. + */ + + /* + * Memory-mapped I/O + * + * We don't fill in the following attributes: + * + * IO_ADDR_R + * IO_ADDR_W + * + * None of these are necessary because we don't have a memory-mapped + * implementation. + */ + + /* + * Install a "fake" ECC layout. + * + * We'll be calling nand_scan() to do the final MTD setup. If we haven't + * already chosen an ECC layout, then nand_scan() will choose one based + * on the part geometry it discovers. Unfortunately, it doesn't make + * good choices. It would be best if we could install the correct ECC + * layout now, before we call nand_scan(). We can't do that because we + * don't know the medium geometry yet. Here, we install a "fake" ECC + * layout just to stop nand_scan() from trying to pick on for itself. + * Later, in imx_nfc_scan_bbt(), when we know the medium geometry, we'll + * install the correct choice. + * + * Of course, this tactic depends critically on nand_scan() not using + * the fake layout before we can install a good one. This is in fact the + * case. + */ + + nand->ecc.layout = &nfc_geometry_512_16_RS_ECC1.mtd_layout; + + /* + * Ask the NAND Flash system to scan for chips. + * + * This will fill in reference implementations for all the members of + * the MTD structures that we didn't set, and will make the medium fully + * usable. + */ + + error = nand_scan(mtd, this->nfc->max_chip_count); + + if (error) { + dev_err(dev, "Chip scan failed\n"); + error = -ENXIO; + goto exit_scan; + } + + /* Register the MTD that represents the entire medium. */ + + mtd->name = "NAND Flash Medium"; + + add_mtd_device(mtd); + + /* Check if we're doing partitions and register MTD's accordingly. */ + + #ifdef CONFIG_MTD_PARTITIONS + + /* + * Look for partition information. If we find some, install + * them. Otherwise, use the partitions handed to us by the + * platform. + */ + + this->partition_count = + parse_mtd_partitions(mtd, partition_source_types, + &this->partitions, 0); + + if ((this->partition_count <= 0) && (pdata->partitions)) { + this->partition_count = pdata->partition_count; + this->partitions = pdata->partitions; + } + + if (this->partitions) + add_mtd_partitions(mtd, this->partitions, + this->partition_count); + + #endif + + /* Return success. */ + + return 0; + + /* Error return paths begin here. */ + +exit_scan: + return error; + +} + +/** + * unregister_with_mtd() - Unregisters this medium with MTD. + * + * @this: Per-device data. + */ +static void unregister_with_mtd(struct imx_nfc_data *this) +{ + + /* Get MTD to let go. */ + + nand_release(&this->mtd); + +} + +/** + * manage_sysfs_files() - Creates/removes sysfs files for this device. + * + * @this: Per-device data. + */ +static void manage_sysfs_files(struct imx_nfc_data *this, int create) +{ + int error; + unsigned int i; + struct device_attribute **attr; + + for (i = 0, attr = device_attributes; + i < ARRAY_SIZE(device_attributes); i++, attr++) { + + if (create) { + error = device_create_file(this->dev, *attr); + if (error) { + while (--attr >= device_attributes) + device_remove_file(this->dev, *attr); + return; + } + } else { + device_remove_file(this->dev, *attr); + } + + } + +} + +/** + * imx_nfc_probe() - Probes for a device and, if possible, takes ownership. + * + * @pdev: A pointer to the platform device. + */ +static int imx_nfc_probe(struct platform_device *pdev) +{ + int error = 0; + char *symmetric_clock; + struct clk *parent_clock; + unsigned long parent_clock_rate_in_hz; + unsigned long parent_clock_rate_in_mhz; + unsigned long nfc_clock_rate_in_hz; + unsigned long nfc_clock_rate_in_mhz; + unsigned long clock_divisor; + unsigned long cycle_in_ns; + struct device *dev = &pdev->dev; + struct imx_nfc_platform_data *pdata = pdev->dev.platform_data; + struct imx_nfc_data *this = 0; + + /* Say hello. */ + + dev_info(dev, "Probing...\n"); + + /* Check if we're enabled. */ + + if (!imx_nfc_module_enable) { + dev_info(dev, "Disabled\n"); + return -ENXIO; + } + + /* Validate the platform device data. */ + + error = validate_the_platform(pdev); + + if (error) + goto exit_validate_platform; + + /* Allocate memory for the per-device data. */ + + this = kzalloc(sizeof(*this), GFP_KERNEL); + + if (!this) { + dev_err(dev, "Failed to allocate per-device memory\n"); + error = -ENOMEM; + goto exit_allocate_this; + } + + /* Link our per-device data to the owning device. */ + + platform_set_drvdata(pdev, this); + + /* Fill in the convenience pointers in our per-device data. */ + + this->pdev = pdev; + this->dev = &pdev->dev; + this->pdata = pdata; + + /* Initialize the interrupt service pathway. */ + + init_completion(&this->done); + + /* Set up the NFC HAL. */ + + error = set_up_the_nfc_hal(this); + + if (error) + goto exit_set_up_nfc_hal; + + /* Attempt to acquire the resources we need. */ + + error = acquire_resources(this); + + if (error) + goto exit_acquire_resources; + + /* Initialize the NFC HAL. */ + + if (this->nfc->init(this)) { + error = -ENXIO; + goto exit_nfc_init; + } + + /* Tell the platform we're bringing this device up. */ + + if (pdata->init) + error = pdata->init(); + + if (error) + goto exit_platform_init; + + /* Report. */ + + parent_clock = clk_get_parent(this->clock); + parent_clock_rate_in_hz = clk_get_rate(parent_clock); + parent_clock_rate_in_mhz = parent_clock_rate_in_hz / 1000000; + nfc_clock_rate_in_hz = clk_get_rate(this->clock); + nfc_clock_rate_in_mhz = nfc_clock_rate_in_hz / 1000000; + + clock_divisor = parent_clock_rate_in_hz / nfc_clock_rate_in_hz; + symmetric_clock = this->nfc->get_symmetric(this) ? "Yes" : "No"; + cycle_in_ns = get_cycle_in_ns(this); + + dev_dbg(dev, "-------------\n"); + dev_dbg(dev, "Configuration\n"); + dev_dbg(dev, "-------------\n"); + dev_dbg(dev, "NFC Version : %d.%d\n" , this->nfc->major_version, + this->nfc->minor_version); + dev_dbg(dev, "Buffers : 0x%p\n" , this->buffers); + dev_dbg(dev, "Primary Regs : 0x%p\n" , this->primary_regs); + dev_dbg(dev, "Secondary Regs : 0x%p\n" , this->secondary_regs); + dev_dbg(dev, "Interrupt : %u\n" , this->interrupt); + dev_dbg(dev, "Clock Name : %s\n" , pdata->clock_name); + dev_dbg(dev, "Parent Clock Rate: %lu Hz (%lu MHz)\n", + parent_clock_rate_in_hz, + parent_clock_rate_in_mhz); + dev_dbg(dev, "Clock Divisor : %lu\n", clock_divisor); + dev_dbg(dev, "NFC Clock Rate : %lu Hz (%lu MHz)\n", + nfc_clock_rate_in_hz, + nfc_clock_rate_in_mhz); + dev_dbg(dev, "Symmetric Clock : %s\n" , symmetric_clock); + dev_dbg(dev, "Actual Cycle : %lu ns\n" , cycle_in_ns); + dev_dbg(dev, "Target Cycle : %u ns\n" , pdata->target_cycle_in_ns); + + /* Initialize the Medium Abstraction Layer. */ + + mal_init(this); + + /* Initialize the MTD Interface Layer. */ + + mil_init(this); + + /* Register this medium with MTD. */ + + error = register_with_mtd(this); + + if (error) + goto exit_mtd_registration; + + /* Create sysfs entries for this device. */ + + manage_sysfs_files(this, true); + + /* Return success. */ + + return 0; + + /* Error return paths begin here. */ + +exit_mtd_registration: + if (pdata->exit) + pdata->exit(); +exit_platform_init: + this->nfc->exit(this); +exit_nfc_init: +exit_acquire_resources: +exit_set_up_nfc_hal: + platform_set_drvdata(pdev, NULL); + kfree(this); +exit_allocate_this: +exit_validate_platform: + return error; + +} + +/** + * imx_nfc_remove() - Dissociates this driver from the given device. + * + * @pdev: A pointer to the device. + */ +static int __exit imx_nfc_remove(struct platform_device *pdev) +{ + struct imx_nfc_data *this = platform_get_drvdata(pdev); + + /* Remove sysfs entries for this device. */ + + manage_sysfs_files(this, false); + + /* Unregister with the NAND Flash MTD system. */ + + unregister_with_mtd(this); + + /* Tell the platform we're shutting down this device. */ + + if (this->pdata->exit) + this->pdata->exit(); + + /* Shut down the NFC. */ + + this->nfc->exit(this); + + /* Release our resources. */ + + release_resources(this); + + /* Unlink our per-device data from the platform device. */ + + platform_set_drvdata(pdev, NULL); + + /* Free our per-device data. */ + + kfree(this); + + /* Return success. */ + + return 0; +} + +#ifdef CONFIG_PM + +/** + * suspend() - Puts the NFC in a low power state. + * + * Refer to Documentation/driver-model/driver.txt for more information. + * + * @pdev: A pointer to the device. + * @state: The new power state. + */ + +static int imx_nfc_suspend(struct platform_device *pdev, pm_message_t state) +{ + int error = 0; + struct imx_nfc_data *this = platform_get_drvdata(pdev); + struct mtd_info *mtd = &this->mtd; + struct device *dev = &this->pdev->dev; + + dev_dbg(dev, "Suspending...\n"); + + /* Suspend MTD's use of this device. */ + + error = mtd->suspend(mtd); + + /* Suspend the actual hardware. */ + + clk_disable(this->clock); + + return error; + +} + +/** + * resume() - Brings the NFC back from a low power state. + * + * Refer to Documentation/driver-model/driver.txt for more information. + * + * @pdev: A pointer to the device. + */ +static int imx_nfc_resume(struct platform_device *pdev) +{ + struct imx_nfc_data *this = platform_get_drvdata(pdev); + struct mtd_info *mtd = &this->mtd; + struct device *dev = &this->pdev->dev; + + dev_dbg(dev, "Resuming...\n"); + + /* Resume MTD's use of this device. */ + + mtd->resume(mtd); + + return 0; + +} + +#else + +#define suspend NULL +#define resume NULL + +#endif /* CONFIG_PM */ + +/* + * This structure represents this driver to the platform management system. + */ +static struct platform_driver imx_nfc_driver = { + .driver = { + .name = IMX_NFC_DRIVER_NAME, + }, + .probe = imx_nfc_probe, + .remove = __exit_p(imx_nfc_remove), + .suspend = imx_nfc_suspend, + .resume = imx_nfc_resume, +}; + +/** + * imx_nfc_init() - Initializes this module. + */ +static int __init imx_nfc_init(void) +{ + + pr_info("i.MX NFC driver %s\n", DRIVER_VERSION); + + /* Register this driver with the platform management system. */ + + if (platform_driver_register(&imx_nfc_driver) != 0) { + pr_err("i.MX NFC driver registration failed\n"); + return -ENODEV; + } + + return 0; + +} + +/** + * imx_nfc_exit() - Deactivates this module. + */ +static void __exit imx_nfc_exit(void) +{ + pr_debug("i.MX NFC driver exiting...\n"); + platform_driver_unregister(&imx_nfc_driver); +} + +module_init(imx_nfc_init); +module_exit(imx_nfc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX NAND Flash Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/lba/Makefile b/drivers/mtd/nand/lba/Makefile new file mode 100644 index 000000000000..0e576bfa856d --- /dev/null +++ b/drivers/mtd/nand/lba/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_MTD_NAND_GPMI_LBA) += gpmi_lba.o +gpmi_lba-objs += gpmi-transport.o lba-core.o lba-blk.o diff --git a/drivers/mtd/nand/lba/gpmi-transport.c b/drivers/mtd/nand/lba/gpmi-transport.c new file mode 100644 index 000000000000..0073842db6b9 --- /dev/null +++ b/drivers/mtd/nand/lba/gpmi-transport.c @@ -0,0 +1,832 @@ +/* + * Freescale STMP37XX/STMP378X GPMI transport layer for LBA driver + * + * Author: Dmitrij Frasenyak <sed@embeddedalley.com> + * Clock settings and hw init comes from + * gpmi driver by Dmitry Pervushin. + * + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/ctype.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <asm/div64.h> +#include <mach/platform.h> +#include <mach/regs-apbh.h> +#include <mach/regs-gpmi.h> +#include <mach/stmp3xxx.h> +#include <mach/dma.h> +#include "gpmi.h" +#include "lba.h" + +struct lba_data *g_data; +static int max_chips = 1; +static long clk = -1; + +struct gpmi_nand_timing gpmi_safe_timing = { + .address_setup = 25, + .data_setup = 80, + .data_hold = 60, + .dsample_time = 6, +}; + +/****************************************************************************** + * HW init part + ******************************************************************************/ + +/** + * gpmi_irq - IRQ handler + * + * @irq: irq no + * @context: IRQ context, pointer to gpmi_nand_data + */ +static irqreturn_t gpmi_irq(int irq, void *context) +{ + struct lba_data *data = context; + int i; + + for (i = 0; i < max_chips; i++) { + if (stmp3xxx_dma_is_interrupt(data->nand[i].dma_ch)) { + stmp3xxx_dma_clear_interrupt(data->nand[i].dma_ch); + complete(&data->nand[i].done); + } + + } + stmp3xxx_clearl(BM_GPMI_CTRL1_DEV_IRQ | BM_GPMI_CTRL1_TIMEOUT_IRQ, + REGS_GPMI_BASE + HW_GPMI_CTRL1); + return IRQ_HANDLED; +} + +static inline u32 gpmi_cycles_ceil(u32 ntime, u32 period) +{ + int k; + + k = (ntime + period - 1) / period; + if (k == 0) + k++; + return k ; +} + + +/** + * gpmi_set_timings - set GPMI timings + * @pdev: pointer to GPMI platform device + * @tm: pointer to structure &gpmi_nand_timing with new timings + * + * During initialization, GPMI uses safe sub-optimal timings, which + * can be changed after reading boot control blocks + */ +void gpmi_set_timings(struct lba_data *data, struct gpmi_nand_timing *tm) +{ + u32 period_ns = 1000000 / clk_get_rate(data->clk) + 1; + u32 address_cycles, data_setup_cycles; + u32 data_hold_cycles, data_sample_cycles; + u32 busy_timeout; + u32 t0, reg; + + address_cycles = gpmi_cycles_ceil(tm->address_setup, period_ns); + data_setup_cycles = gpmi_cycles_ceil(tm->data_setup, period_ns); + data_hold_cycles = gpmi_cycles_ceil(tm->data_hold, period_ns); + data_sample_cycles = gpmi_cycles_ceil(tm->dsample_time + period_ns / 4, + period_ns / 2); + busy_timeout = gpmi_cycles_ceil(10000000 / 4096, period_ns); + + t0 = address_cycles << BP_GPMI_TIMING0_ADDRESS_SETUP; + t0 |= data_setup_cycles << BP_GPMI_TIMING0_DATA_SETUP; + t0 |= data_hold_cycles << BP_GPMI_TIMING0_DATA_HOLD; + __raw_writel(t0, REGS_GPMI_BASE + HW_GPMI_TIMING0); + + __raw_writel(busy_timeout, REGS_GPMI_BASE + HW_GPMI_TIMING1); + + reg = __raw_readl(REGS_GPMI_BASE + HW_GPMI_CTRL1); +#ifdef CONFIG_ARCH_STMP378X + reg &= ~BM_GPMI_CTRL1_RDN_DELAY; + reg |= data_sample_cycles << BP_GPMI_CTRL1_RDN_DELAY; +#else + reg &= ~BM_GPMI_CTRL1_DSAMPLE_TIME; + reg |= data_sample_cycles << BP_GPMI_CTRL1_DSAMPLE_TIME; +#endif + __raw_writel(reg, REGS_GPMI_BASE + HW_GPMI_CTRL1); +} + +void queue_plug(struct lba_data *data) +{ + u32 ctrl1; + clk_enable(data->clk); + if (clk <= 0) + clk = 24000; /* safe setting, some chips do not work on + speeds >= 24kHz */ + clk_set_rate(data->clk, clk); + + clk = clk_get_rate(data->clk); + + + stmp3xxx_reset_block(HW_GPMI_CTRL0 + REGS_GPMI_BASE, 1); + + ctrl1 = __raw_readl(REGS_GPMI_BASE + HW_GPMI_CTRL1); + + /* write protection OFF */ + ctrl1 |= BM_GPMI_CTRL1_DEV_RESET; + + /* IRQ polarity */ + ctrl1 |= BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY; + + /* ...and ECC module */ + /*HW_GPMI_CTRL1_SET(bch_mode());*/ + + /* choose NAND mode (1 means ATA, 0 - NAND */ + ctrl1 &= ~BM_GPMI_CTRL1_GPMI_MODE; + + __raw_writel(ctrl1, REGS_GPMI_BASE + HW_GPMI_CTRL1); + + gpmi_set_timings(data, &gpmi_safe_timing); +} + +void queue_release(struct lba_data *data) +{ + stmp3xxx_setl(BM_GPMI_CTRL0_SFTRST, REGS_GPMI_BASE + HW_GPMI_CTRL0); + + clk_disable(data->clk); +} + + +/** + * gpmi_init_hw - initialize the hardware + * @pdev: pointer to platform device + * + * Initialize GPMI hardware and set default (safe) timings for NAND access. + * Returns error code or 0 on success + */ +static int gpmi_init_hw(struct platform_device *pdev, int request_pins) +{ + struct lba_data *data = platform_get_drvdata(pdev); + struct gpmi_platform_data *gpd = + (struct gpmi_platform_data *)pdev->dev.platform_data; + int err = 0; + + data->clk = clk_get(NULL, "gpmi"); + if (IS_ERR(data->clk)) { + err = PTR_ERR(data->clk); + dev_err(&pdev->dev, "cannot set failsafe clockrate\n"); + goto out; + } + if (request_pins) + gpd->pinmux(1); + + queue_plug(data); + +out: + return err; +} +/** + * gpmi_release_hw - free the hardware + * @pdev: pointer to platform device + * + * In opposite to gpmi_init_hw, release all acquired resources + */ +static void gpmi_release_hw(struct platform_device *pdev) +{ + struct gpmi_platform_data *gpd = + (struct gpmi_platform_data *)pdev->dev.platform_data; + struct lba_data *data = platform_get_drvdata(pdev); + + queue_release(data); + clk_put(data->clk); + gpd->pinmux(0); +} + + +/** + * gpmi_alloc_buffers - allocate DMA buffers for one chip + * + * @pdev: GPMI platform device + * @g: pointer to structure associated with NAND chip + * + * Allocate buffer using dma_alloc_coherent + */ +static int gpmi_alloc_buffers(struct platform_device *pdev, + struct gpmi_perchip_data *g) +{ + g->cmd_buffer = dma_alloc_coherent(&pdev->dev, + g->cmd_buffer_size, + &g->cmd_buffer_handle, GFP_DMA); + if (!g->cmd_buffer) + goto out1; + + g->write_buffer = dma_alloc_coherent(&pdev->dev, + g->write_buffer_size, + &g->write_buffer_handle, GFP_DMA); + if (!g->write_buffer) + goto out2; + + g->data_buffer = dma_alloc_coherent(&pdev->dev, + g->data_buffer_size, + &g->data_buffer_handle, GFP_DMA); + if (!g->data_buffer) + goto out3; + + g->oob_buffer = dma_alloc_coherent(&pdev->dev, + g->oob_buffer_size, + &g->oob_buffer_handle, GFP_DMA); + if (!g->oob_buffer) + goto out4; + + g->cmdtail_buffer = dma_alloc_coherent(&pdev->dev, + g->cmdtail_buffer_size, + &g->cmdtail_buffer_handle, GFP_DMA); + if (!g->oob_buffer) + goto out5; + + return 0; + +out5: + dma_free_coherent(&pdev->dev, g->oob_buffer_size, + g->oob_buffer, g->oob_buffer_handle); + +out4: + dma_free_coherent(&pdev->dev, g->data_buffer_size, + g->data_buffer, g->data_buffer_handle); +out3: + dma_free_coherent(&pdev->dev, g->write_buffer_size, + g->write_buffer, g->write_buffer_handle); +out2: + dma_free_coherent(&pdev->dev, g->cmd_buffer_size, + g->cmd_buffer, g->cmd_buffer_handle); +out1: + return -ENOMEM; +} + +/** + * gpmi_free_buffers - free buffers allocated by gpmi_alloc_buffers + * + * @pdev: platform device + * @g: pointer to structure associated with NAND chip + * + * Deallocate buffers on exit + */ +static void gpmi_free_buffers(struct platform_device *pdev, + struct gpmi_perchip_data *g) +{ + dma_free_coherent(&pdev->dev, g->oob_buffer_size, + g->oob_buffer, g->oob_buffer_handle); + dma_free_coherent(&pdev->dev, g->write_buffer_size, + g->write_buffer, g->write_buffer_handle); + dma_free_coherent(&pdev->dev, g->cmd_buffer_size, + g->cmd_buffer, g->cmd_buffer_handle); + dma_free_coherent(&pdev->dev, g->cmdtail_buffer_size, + g->cmdtail_buffer, g->cmdtail_buffer_handle); + dma_free_coherent(&pdev->dev, g->data_buffer_size, + g->data_buffer, g->data_buffer_handle); +} + + +/****************************************************************************** + * Arch specific chain_* callbaks + ******************************************************************************/ + +/** + * chain_w4r - Initialize descriptor to perform W4R operation + * + * @chain: Descriptor to use + * @cs: CS for this operation + * + * Due to HW bug we have to put W4R into separate desc. + */ +static void chain_w4r(struct stmp3xxx_dma_descriptor *chain, int cs) +{ + chain->command->cmd = + (4 << BP_APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + (BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER << BP_APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + (BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY << BP_GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + (BV_GPMI_CTRL0_ADDRESS__NAND_DATA << BP_GPMI_CTRL0_ADDRESS) | + (cs << BP_GPMI_CTRL0_CS); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->pio_words[3] = 0; + chain->command->buf_ptr = 0; +} + +/** + * chain_cmd - Initialize descriptor to push CMD to the bus + * + * @chain: Descriptor to use + * @cmd_handle: dma_addr_t pointer that holds the command + * @lba_cmd: flags and lenghth of this command. + * @cs: CS for this operation + * + * CLE || CLE+ALE + */ +static void chain_cmd(struct stmp3xxx_dma_descriptor *chain, + dma_addr_t cmd_handle, + struct lba_cmd *lba_cmd, + int cs) +{ + /* output command */ + chain->command->cmd = + (lba_cmd->len << BP_APBH_CHn_CMD_XFER_COUNT) | + (3 << BP_APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + (BV_APBH_CHn_CMD_COMMAND__DMA_READ << BP_APBH_CHn_CMD_COMMAND); + chain->command->cmd |= BM_APBH_CHn_CMD_CHAIN; + chain->command->pio_words[0] = + (BV_GPMI_CTRL0_COMMAND_MODE__WRITE << BP_GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BM_GPMI_CTRL0_LOCK_CS | + (cs << BP_GPMI_CTRL0_CS) | + (BV_GPMI_CTRL0_ADDRESS__NAND_CLE << BP_GPMI_CTRL0_ADDRESS) | + (lba_cmd->len << BP_GPMI_CTRL0_XFER_COUNT); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->buf_ptr = cmd_handle; + + if (lba_cmd->flag & FE_CMD_INC) + chain->command->pio_words[0] |= BM_GPMI_CTRL0_ADDRESS_INCREMENT; +/*BUG if (lba_cmd->flag & FE_W4R) */ +/* chain->command->cmd |= BM_APBH_CHn_CMD_NANDWAIT4READY; */ +} + +/** + * chain_cmd - Initialize descriptor to read data from the bus + * + * @chain: Descriptor to use + * @data_handle: dma_addr_t pointer to buffer to store data + * @data_len: the size of the data buffer to read + * @cmd_handle: dma_addr_t pointer that holds the command + * @lba_cmd: flags and lenghth of this command. + * @cs: CS for this operation + */ +static void chain_read_data(struct stmp3xxx_dma_descriptor *chain, + dma_addr_t data_handle, + dma_addr_t data_len, + struct lba_cmd *lba_cmd, + int cs) +{ + chain->command->cmd = + (data_len << BP_APBH_CHn_CMD_XFER_COUNT) | + (4 << BP_APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + (BV_APBH_CHn_CMD_COMMAND__DMA_WRITE << BP_APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + (BV_GPMI_CTRL0_COMMAND_MODE__READ << BP_GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BM_GPMI_CTRL0_LOCK_CS | + (cs << BP_GPMI_CTRL0_CS) | + (BV_GPMI_CTRL0_ADDRESS__NAND_DATA << BP_GPMI_CTRL0_ADDRESS) | + (data_len << BP_GPMI_CTRL0_XFER_COUNT); + + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->pio_words[3] = 0; + chain->command->buf_ptr = data_handle; + + if (lba_cmd->flag & FE_CMD_INC) + chain->command->pio_words[0] |= BM_GPMI_CTRL0_ADDRESS_INCREMENT; +/*BUG if (lba_cmd->flag & FE_W4R) */ +/* chain->command->cmd |= BM_APBH_CHn_CMD_NANDWAIT4READY; */ + +} + +/** + * chain_cmd - Initialize descriptor to read data from the bus + * + * @chain: Descriptor to use + * @data_handle: dma_addr_t pointer to buffer to read data from + * @data_len: the size of the data buffer to write + * @cmd_handle: dma_addr_t pointer that holds the command + * @lba_cmd: flags and lenghth of this command. + * @cs: CS for this operation + */ +static void chain_write_data(struct stmp3xxx_dma_descriptor *chain, + dma_addr_t data_handle, + int data_len, + struct lba_cmd *lba_cmd, + int cs) +{ + + chain->command->cmd = + (data_len << BP_APBH_CHn_CMD_XFER_COUNT) | + (4 << BP_APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + (BV_APBH_CHn_CMD_COMMAND__DMA_READ << BP_APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + (BV_GPMI_CTRL0_COMMAND_MODE__WRITE << BP_GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BM_GPMI_CTRL0_LOCK_CS | + (cs << BP_GPMI_CTRL0_CS) | + (BV_GPMI_CTRL0_ADDRESS__NAND_DATA << BP_GPMI_CTRL0_ADDRESS) | + (data_len << BP_GPMI_CTRL0_XFER_COUNT); + + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->pio_words[3] = 0; + chain->command->buf_ptr = data_handle; + + if (lba_cmd->flag & FE_CMD_INC) + chain->command->pio_words[0] |= BM_GPMI_CTRL0_ADDRESS_INCREMENT; +/*BUG if (lba_cmd->flag & FE_W4R) */ +/* chain->command->cmd |= BM_APBH_CHn_CMD_NANDWAIT4READY; */ + +} + + +/****************************************************************************** + * Interface to arch independent part + ******************************************************************************/ +/** + * queue_cmd - Setup a chain of descriptors + * + * @priv: private data passed + * @cmd_buf: pointer to command buffer (to be removed) + * @cmd_handle: dma_addr_t pointer that holds the command + * @cmd_len: the size of the command buffer (to be removed) + * @data_handle: dma_addr_t pointer to a data buffer + * @data_len: the size of the data buffer + * @cmd_flags: commands flags + */ +int queue_cmd(void *priv, + uint8_t *cmd_buf, dma_addr_t cmd_handle, int cmd_len, + dma_addr_t data, int data_len, + struct lba_cmd *cmd_flags) +{ + + struct gpmi_perchip_data *g = priv; + unsigned long flags; + int idx; + int ret = 0; + struct stmp3xxx_dma_descriptor *chain ; + int i; + + if (!g || !(cmd_buf || cmd_handle)) + BUG(); + + spin_lock_irqsave(&g->lock, flags); + + /* Keep it for debug purpose */ + chain = g->d; + for (i = g->d_tail; i < GPMI_DMA_MAX_CHAIN; i++) { + chain[i].command->cmd = 0; + chain[i].command->buf_ptr = 0; + } + /* End */ + + if (!cmd_handle) { + if (!cmd_buf) + BUG(); + memcpy(g->cmd_buffer, cmd_buf, cmd_len); + cmd_handle = g->cmd_buffer_handle; + } + + idx = g->d_tail; + chain = &g->d[idx]; + + do { + if (!cmd_flags) + BUG(); + + if (cmd_flags->flag & FE_W4R) { + /* there seems to be HW BUG with W4R flag. + * IRQ controller hangs forever when it's combined + * with real operation. + */ + chain_w4r(chain, g->cs); + chain++; idx++; + } + + + switch (cmd_flags->flag & F_MASK) { + + case F_CMD: + chain_cmd(chain, cmd_handle, cmd_flags, g->cs); + break; + case F_DATA_READ: + chain_read_data(chain, data, data_len, + cmd_flags, g->cs); + break; + case F_DATA_WRITE: + chain_write_data(chain, data, data_len, + cmd_flags, g->cs); + break; + default:{ + if (cmd_flags->flag & FE_END) + goto out; + else{ + printk(KERN_ERR "uknown cmd\n"); + BUG(); + } + } + } + + + chain++; idx++; + cmd_handle += cmd_flags->len; + + if (idx >= GPMI_DMA_MAX_CHAIN) { + printk(KERN_ERR "to many chains; idx is 0x%x\n", idx); + BUG(); + } + + } while (!((cmd_flags++)->flag & FE_END)); + +out: + if (idx < GPMI_DMA_MAX_CHAIN) { + ret = idx; + g->d_tail = idx; + } + spin_unlock_irqrestore(g->lock, flags); + + return ret; + +} + +dma_addr_t queue_get_cmd_handle(void *priv) +{ + struct gpmi_perchip_data *g = priv; + return g->cmd_buffer_handle; +} + +uint8_t *queue_get_cmd_ptr(void *priv) +{ + struct gpmi_perchip_data *g = priv; + return g->cmd_buffer; +} + +dma_addr_t queue_get_data_handle(void *priv) +{ + struct gpmi_perchip_data *g = priv; + return g->data_buffer_handle; +} + +uint8_t *queue_get_data_ptr(void *priv) +{ + struct gpmi_perchip_data *g = priv; + return g->data_buffer; +} + + +/** + * queue_run - run the chain + * + * @priv: private data. + */ +int queue_run(void *priv) +{ + struct gpmi_perchip_data *g = priv; + + if (!g->d_tail) + return 0; + stmp3xxx_dma_reset_channel(g->dma_ch); + stmp3xxx_dma_clear_interrupt(g->dma_ch); + stmp3xxx_dma_enable_interrupt(g->dma_ch); + + g->d[g->d_tail-1].command->cmd &= ~(BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN); + g->d[g->d_tail-1].command->cmd |= BM_APBH_CHn_CMD_IRQONCMPLT ; + + g->d[g->d_tail-1].command->pio_words[0] &= ~BM_GPMI_CTRL0_LOCK_CS; + +#ifdef DEBUG + /*stmp37cc_dma_print_chain(&g->chain);*/ +#endif + + init_completion(&g->done); + stmp3xxx_dma_go(g->dma_ch, g->d, 1); + wait_for_completion(&g->done); + + g->d_tail = 0; + + return 0; + +} + +/****************************************************************************** + * Platform specific part / chard driver and misc functions + ******************************************************************************/ + + + +static int __init lba_probe(struct platform_device *pdev) +{ + struct lba_data *data; + struct resource *r; + struct gpmi_perchip_data *g; + int err; + + /* Allocate memory for the device structure (and zero it) */ + data = kzalloc(sizeof(*data) + sizeof(struct gpmi_perchip_data), + GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, "failed to allocate gpmi_nand_data\n"); + err = -ENOMEM; + goto out1; + } + g_data = data; + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "failed to get resource\n"); + err = -ENXIO; + goto out2; + } + data->io_base = ioremap(r->start, r->end - r->start + 1); + if (!data->io_base) { + dev_err(&pdev->dev, "ioremap failed\n"); + err = -EIO; + goto out2; + } + + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!r) { + err = -EIO; + dev_err(&pdev->dev, "can't get IRQ resource\n"); + goto out3; + } + data->irq = r->start; + + platform_set_drvdata(pdev, data); + err = gpmi_init_hw(pdev, 1); + if (err) + goto out3; + + + err = request_irq(data->irq, + gpmi_irq, 0, dev_name(&pdev->dev), data); + if (err) { + dev_err(&pdev->dev, "can't request GPMI IRQ\n"); + goto out4; + } + + g = data->nand; + + r = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!r) { + dev_err(&pdev->dev, "can't get DMA resource\n"); + goto out_res; + } + g->cs = 0; + g->dma_ch = r->start; + + err = stmp3xxx_dma_request(g->dma_ch, NULL, dev_name(&pdev->dev)); + if (err) { + dev_err(&pdev->dev, "can't request DMA channel 0x%x\n", + g->dma_ch); + goto out_res; + } + + err = stmp3xxx_dma_make_chain(g->dma_ch, &g->chain, + g->d, ARRAY_SIZE(g->d)); + if (err) { + dev_err(&pdev->dev, "can't setup DMA chain\n"); + stmp3xxx_dma_release(g->dma_ch); + goto out_res; + } + + g->cmd_buffer_size = GPMI_CMD_BUF_SZ; + g->cmdtail_buffer_size = GPMI_CMD_BUF_SZ; + g->write_buffer_size = GPMI_WRITE_BUF_SZ; + g->data_buffer_size = GPMI_DATA_BUF_SZ; + g->oob_buffer_size = GPMI_OOB_BUF_SZ; + + err = gpmi_alloc_buffers(pdev, g); + if (err) { + dev_err(&pdev->dev, "can't setup buffers\n"); + stmp3xxx_dma_free_chain(&g->chain); + stmp3xxx_dma_release(g->dma_ch); + goto out_res; + } + + g->dev = pdev; + g->chip.priv = g; + g->index = 0; + g->timing = gpmi_safe_timing; + + g->cmd_buffer_sz = + g->write_buffer_sz = + g->data_buffer_sz = + 0; + g->valid = !0; /* mark the data as valid */ + + + lba_core_init(data); + + return 0; + +out_res: + free_irq(data->irq, data); +out4: + gpmi_release_hw(pdev); +out3: + platform_set_drvdata(pdev, NULL); + iounmap(data->io_base); +out2: + kfree(data); +out1: + return err; +} + +static int gpmi_suspend(struct platform_device *pdev, pm_message_t pm) +{ + struct lba_data *data = platform_get_drvdata(pdev); + int err; + + printk(KERN_INFO "%s: %d\n", __func__, __LINE__); + err = lba_core_suspend(pdev, data); + if (!err) + gpmi_release_hw(pdev); + + return err; +} + +static int gpmi_resume(struct platform_device *pdev) +{ + struct lba_data *data = platform_get_drvdata(pdev); + int r; + + printk(KERN_INFO "%s: %d\n", __func__, __LINE__); + r = gpmi_init_hw(pdev, 1); + lba_core_resume(pdev, data); + return r; +} + +/** + * gpmi_nand_remove - remove a GPMI device + * + */ +static int __devexit lba_remove(struct platform_device *pdev) +{ + struct lba_data *data = platform_get_drvdata(pdev); + int i; + + lba_core_remove(data); + gpmi_release_hw(pdev); + free_irq(data->irq, data); + + for (i = 0; i < max_chips; i++) { + if (!data->nand[i].valid) + continue; + gpmi_free_buffers(pdev, &data->nand[i]); + stmp3xxx_dma_free_chain(&data->nand[i].chain); + stmp3xxx_dma_release(data->nand[i].dma_ch); + } + iounmap(data->io_base); + kfree(data); + + return 0; +} + +static struct platform_driver lba_driver = { + .probe = lba_probe, + .remove = __devexit_p(lba_remove), + .driver = { + .name = "gpmi", + .owner = THIS_MODULE, + }, + .suspend = gpmi_suspend, + .resume = gpmi_resume, + +}; + + +static int __init lba_init(void) +{ + return platform_driver_register(&lba_driver); +} + +static void lba_exit(void) +{ + platform_driver_unregister(&lba_driver); +} + +module_init(lba_init); +module_exit(lba_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/lba/gpmi.h b/drivers/mtd/nand/lba/gpmi.h new file mode 100644 index 000000000000..a647c948040f --- /dev/null +++ b/drivers/mtd/nand/lba/gpmi.h @@ -0,0 +1,103 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __DRIVERS_GPMI_H +#define __DRIVERS_GPMI_H + +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <mach/stmp3xxx.h> +#include <mach/dma.h> + +#include <mach/gpmi.h> +#include <mach/regs-gpmi.h> +#include <mach/regs-apbh.h> +#ifdef CONFIG_MTD_NAND_GPMI_BCH +#include <mach/regs-bch.h> +#endif + + +struct gpmi_nand_timing { + u8 data_setup; + u8 data_hold; + u8 address_setup; + u8 dsample_time; +}; + +#define GPMI_DMA_MAX_CHAIN 20 /* max DMA commands in chain */ + +#define GPMI_CMD_BUF_SZ 10 +#define GPMI_DATA_BUF_SZ 4096 +#define GPMI_WRITE_BUF_SZ 4096 +#define GPMI_OOB_BUF_SZ 218 + + +struct gpmi_perchip_data { + int valid; + struct nand_chip chip; + struct platform_device *dev; + int index; + + spinlock_t lock; /* protect chain operations */ + struct stmp37xx_circ_dma_chain chain; + struct stmp3xxx_dma_descriptor d[GPMI_DMA_MAX_CHAIN]; + int d_tail; + + struct completion done; + + u8 *cmd_buffer; + dma_addr_t cmd_buffer_handle; + int cmd_buffer_size, cmd_buffer_sz; + + u8 *cmdtail_buffer; + dma_addr_t cmdtail_buffer_handle; + int cmdtail_buffer_size, cmdtail_buffer_sz; + + u8 *write_buffer; + dma_addr_t write_buffer_handle; + int write_buffer_size, write_buffer_sz; + + u8 *data_buffer; + dma_addr_t data_buffer_handle; + u8 *data_buffer_cptr; + int data_buffer_size, data_buffer_sz, bytes2read; + + u8 *oob_buffer; + dma_addr_t oob_buffer_handle; + int oob_buffer_size; + + int cs; + unsigned dma_ch; + + int ecc_oob_bytes, oob_free; + + struct gpmi_nand_timing timing; + + void *p2w, *oob2w, *p2r, *oob2r; + size_t p2w_size, oob2w_size, p2r_size, oob2r_size; + dma_addr_t p2w_dma, oob2w_dma, p2r_dma, oob2r_dma; + unsigned read_memcpy:1, write_memcpy:1, + read_oob_memcpy:1, write_oob_memcpy:1; +}; + + +extern struct gpmi_nand_timing gpmi_safe_timing; + + +#endif diff --git a/drivers/mtd/nand/lba/lba-blk.c b/drivers/mtd/nand/lba/lba-blk.c new file mode 100644 index 000000000000..228aa9d27760 --- /dev/null +++ b/drivers/mtd/nand/lba/lba-blk.c @@ -0,0 +1,345 @@ +/* + * Freescale STMP37XX/STMP378X LBA/block driver + * + * Author: Dmitrij Frasenyak <sed@embeddedalley.com> + * + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +#include <linux/sched.h> +#include <linux/kernel.h> /* printk() */ +#include <linux/slab.h> /* kmalloc() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/kthread.h> +#include <linux/timer.h> +#include <linux/types.h> /* size_t */ +#include <linux/fcntl.h> /* O_ACCMODE */ +#include <linux/hdreg.h> /* HDIO_GETGEO */ +#include <linux/kdev_t.h> +#include <linux/vmalloc.h> +#include <linux/genhd.h> +#include <linux/blkdev.h> +#include <linux/buffer_head.h> /* invalidate_bdev */ +#include <linux/bio.h> +#include <linux/dma-mapping.h> +#include <linux/hdreg.h> +#include <linux/blkdev.h> +#include "lba.h" + +static int lba_major; + +#define LBA_NAME "lba" + +#if 0 +#define TAG() printk(KERNE_ERR "%s: %d\n", __func__, __LINE__) +#else +#define TAG() +#endif + +/* + * The internal representation of our device. + */ +struct lba_blk_dev { + int size; /* Device size in sectors */ + spinlock_t lock; /* For mutual exclusion */ + int users; + struct request_queue *queue; /* The device request queue */ + struct gendisk *gd; /* The gendisk structure */ + struct lba_data *data; /* pointer from lba core */ + + struct task_struct *thread; + struct bio *bio_head; + struct bio *bio_tail; + wait_queue_head_t wait_q; + struct semaphore busy; + +}; + +static struct lba_blk_dev *g_lba_blk; + +static void blk_add_bio(struct lba_blk_dev *dev, struct bio *bio); + + +/* + * Transfer a single BIO. + */ +static int lba_blk_xfer_bio(struct lba_blk_dev *dev, struct bio *bio) +{ + int i; + struct bio_vec *bvec; + sector_t sector = bio->bi_sector; + enum dma_data_direction dir; + int status = 0; + int (*lba_xfer)(void *priv, + unsigned int sector, + unsigned int count, + void *buffer, + dma_addr_t handle); + + if (bio_data_dir(bio) == WRITE) { + lba_xfer = lba_write_sectors; + dir = DMA_TO_DEVICE; + } else { + lba_xfer = lba_read_sectors; + dir = DMA_FROM_DEVICE; + } + + /* Fixme: merge segments */ + bio_for_each_segment(bvec, bio, i) { + void *buffer = page_address(bvec->bv_page); + dma_addr_t handle ; + if (!buffer) + BUG(); + buffer += bvec->bv_offset; + handle = dma_map_single(&dev->data->nand->dev->dev, + buffer, + bvec->bv_len, + dir); + status = lba_xfer(dev->data->nand, sector, + bvec->bv_len >> 9, + buffer, + handle); + + dma_unmap_single(&dev->data->nand->dev->dev, + handle, + bvec->bv_len, + dir); + if (status) + break; + + sector += bio_cur_bytes(bio) >> 9; + } + + return status; +} + + +/* + * The direct make request version. + */ +static int lba_make_request(struct request_queue *q, struct bio *bio) +{ + struct lba_blk_dev *dev = q->queuedata; + + blk_add_bio(dev, bio); + return 0; +} + +/* + * Open and close. + */ + +static int lba_blk_open(struct block_device *bdev, fmode_t mode) +{ + struct lba_blk_dev *dev = bdev->bd_disk->private_data; + + TAG(); + + spin_lock_irq(&dev->lock); + dev->users++; + spin_unlock_irq(&dev->lock); + return 0; +} + + +static int lba_blk_release(struct gendisk *gd, fmode_t mode) +{ + struct lba_blk_dev *dev = gd->private_data; + + spin_lock(&dev->lock); + dev->users--; + spin_unlock(&dev->lock); + + return 0; +} + +static int lba_getgeo(struct block_device *bdev, struct hd_geometry *geo) +{ + /* + * get geometry: we have to fake one... trim the size to a + * multiple of 2048 (1M): tell we have 32 sectors, 64 heads, + * whatever cylinders. + */ + geo->heads = 1 << 6; + geo->sectors = 1 << 5; + geo->cylinders = get_capacity(bdev->bd_disk) >> 11; + return 0; +} + +/* + * Add bio to back of pending list + */ +static void blk_add_bio(struct lba_blk_dev *dev, struct bio *bio) +{ + unsigned long flags; + spin_lock_irqsave(&dev->lock, flags); + if (dev->bio_tail) { + dev->bio_tail->bi_next = bio; + dev->bio_tail = bio; + } else + dev->bio_head = dev->bio_tail = bio; + wake_up(&dev->wait_q); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* + * Grab first pending buffer + */ +static struct bio *blk_get_bio(struct lba_blk_dev *dev) +{ + struct bio *bio; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + bio = dev->bio_head; + if (bio) { + if (bio == dev->bio_tail) { + dev->bio_tail = NULL; + dev->bio_head = NULL; + } + dev->bio_head = bio->bi_next; + bio->bi_next = NULL; + } + spin_unlock_irqrestore(&dev->lock, flags); + + return bio; +} + +static int lba_thread(void *data) +{ + struct lba_blk_dev *dev = data; + struct bio *bio; + int status; + + set_user_nice(current, -20); + + while (!kthread_should_stop() || dev->bio_head) { + + wait_event_interruptible(dev->wait_q, + dev->bio_head || kthread_should_stop()); + + if (!dev->bio_head) + continue; + + if (lba_core_lock_mode(dev->data, LBA_MODE_MDP)) + continue; + + bio = blk_get_bio(dev); + status = lba_blk_xfer_bio(dev, bio); + bio_endio(bio, status); + + lba_core_unlock_mode(dev->data); + } + + return 0; +} + + + +/* + * The device operations structure. + */ +static struct block_device_operations lba_blk_ops = { + .owner = THIS_MODULE, + .open = lba_blk_open, + .release = lba_blk_release, + .getgeo = lba_getgeo, +}; + + +int lba_blk_init(struct lba_data *data) +{ + + struct lba_blk_dev *dev; + int err; + if (!data) + BUG(); + + printk(KERN_INFO "LBA block driver v0.1\n"); + lba_major = LBA_MAJOR; + dev = g_lba_blk = kzalloc(sizeof(struct lba_blk_dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->data = data; + register_blkdev(lba_major, "lba"); + + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->wait_q); + sema_init(&dev->busy, 1); + + dev->queue = blk_alloc_queue(GFP_KERNEL); + if (!dev->queue) + goto out2; + blk_queue_make_request(dev->queue, lba_make_request); + /*dev->queue->unplug_fn = lba_unplug_device;*/ + blk_queue_logical_block_size(dev->queue, 512); + + dev->queue->queuedata = dev; + dev->gd = alloc_disk(32); + if (!dev->gd) { + printk(KERN_ERR "failed to alloc disk\n"); + goto out3; + } + dev->size = data->mdp_size ; + printk(KERN_INFO "%s: set capacity of the device to 0x%x\n", + __func__, dev->size); + dev->gd->major = lba_major; + dev->gd->first_minor = 0; + dev->gd->fops = &lba_blk_ops; + dev->gd->queue = dev->queue; + dev->gd->private_data = dev; + snprintf(dev->gd->disk_name, 8, LBA_NAME); + set_capacity(dev->gd, dev->size); + + dev->thread = kthread_create(lba_thread, dev, "lba-%d", 1); + if (IS_ERR(dev->thread)) { + err = PTR_ERR(dev->thread); + goto out3; + } + wake_up_process(dev->thread); + + add_disk(dev->gd); + + + TAG(); + + return 0; +out3: +out2: + unregister_blkdev(lba_major, "lba"); + return -ENOMEM; +} + +int lba_blk_remove(struct lba_data *data) +{ + + struct lba_blk_dev *dev = g_lba_blk; + + del_gendisk(dev->gd); + kthread_stop(dev->thread); + blk_cleanup_queue(dev->queue); + put_disk(dev->gd); + + unregister_blkdev(lba_major, LBA_NAME); + kfree(dev); + return 0; +} + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_BLOCKDEV_MAJOR(254); diff --git a/drivers/mtd/nand/lba/lba-core.c b/drivers/mtd/nand/lba/lba-core.c new file mode 100644 index 000000000000..cdf28ca42b2a --- /dev/null +++ b/drivers/mtd/nand/lba/lba-core.c @@ -0,0 +1,619 @@ +/* + * Freescale STMP37XX/STMP378X LBA/core driver + * + * Author: Dmitrij Frasenyak <sed@embeddedalley.com> + * + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/dma-mapping.h> +#include <linux/ctype.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <mach/stmp3xxx.h> +#include <mach/dma.h> +#include "gpmi.h" +#include "lba.h" + +#define LBA_SELFPM_TIMEOUT 2000 /* msecs */ +dma_addr_t g_cmd_handle; +dma_addr_t g_data_handle; +uint8_t *g_data_buffer; +uint8_t *g_cmd_buffer; + +uint8_t lba_get_status1(void *priv) +{ + uint8_t cmd_buf[] = { 0x70 } ; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_W4R}, + {0, F_DATA_READ | FE_END}, + }; + *g_data_buffer = 0; + queue_cmd(priv, cmd_buf, 0, 1, g_data_handle, 1, lba_flags); + queue_run(priv); + return *g_data_buffer; +} + + +int lba_wait_for_ready(void *priv) +{ + int stat; + unsigned long j_start = jiffies; + + stat = lba_get_status1(priv); + if ((stat & 0x60) != 0x60) { + while (((stat & 0x60) != 0x60) && + (jiffies - j_start < msecs_to_jiffies(2000))) { + schedule(); + stat = lba_get_status1(priv); + } + } + if (stat != 0x60) + return stat; + + return 0; +} + +int lba_write_sectors(void *priv, unsigned int sector, unsigned int count, + void *buffer, dma_addr_t handle) +{ + uint8_t cmd_buf[] = { + 0x80, + count & 0xff, (count >> 8) & 0xff, /* Count */ + (sector & 0xff), (sector >> 8) & 0xff, /* Address */ + (sector >> 16) & 0xff, (sector >> 24) & 0xff, /* Addres */ + /* Data goes here */ + 0x10 + }; + + struct lba_cmd flags_t1[] = { /* Transmition mode 1/A */ + {7 , F_CMD | FE_CMD_INC | FE_W4R}, + {0, F_DATA_WRITE}, + {1 , F_CMD | FE_END} + }; + + if (count > 8) + return -EINVAL; + + if (lba_wait_for_ready(priv)) + return -EIO; + + while (count) { + int cnt = (count < 8) ? count : 8; + int data_len = cnt * 512; + + queue_cmd(priv, cmd_buf, 0, 8, + handle, data_len, flags_t1); + + handle += data_len; + count -= cnt; + + } + + queue_run(priv); + + return count; + +} + +int lba_read_sectors(void *priv, unsigned int sector, unsigned int count, + void *buffer, dma_addr_t handle) +{ + + int data_len; + int cnt; + uint8_t cmd_buf[] = { + 0x00, + count & 0xff, (count >> 8) & 0xff, /* Count */ + (sector & 0xff), (sector >> 8) & 0xff, /* Addr */ + (sector >> 16) & 0xff, (sector >> 24) & 0xff, /* Addr */ + 0x30 + /* Data goes here <data> */ + + }; + + struct lba_cmd flags_r3[] = { /* Read mode 3/A */ + {7 , F_CMD | FE_CMD_INC | FE_W4R}, + {1 , F_CMD }, + {0 , F_DATA_READ | FE_W4R | FE_END }, + }; + struct lba_cmd flags_r3c[] = { /* Read mode 3/A */ + {0 , F_DATA_READ | FE_W4R | FE_END }, + }; + struct lba_cmd *flags = flags_r3; + int flags_len = 8; + + if (count > 8) + return -EINVAL; + + if (lba_wait_for_ready(priv)) + return -EIO; + + while (count) { + cnt = (count < 8) ? count : 8; + data_len = cnt * 512; + queue_cmd(priv, cmd_buf, 0, flags_len, handle, data_len, flags); + handle += data_len; + count -= cnt; + flags = flags_r3c; + flags_len = 0; + } + + queue_run(priv); + + return count; + +} + + +uint8_t lba_get_id1(void *priv, uint8_t *ret_buffer) +{ + uint8_t cmd_buf[] = { 0x90 , 0x00, /* Data read 5bytes*/ }; + struct lba_cmd lba_flags[] = { + {2 , F_CMD | FE_CMD_INC | FE_W4R}, + {0, F_DATA_READ | FE_END}, + }; + + queue_cmd(priv, cmd_buf, 0, 2, g_data_handle, 5, lba_flags); + queue_run(priv); + memcpy(ret_buffer, g_data_buffer, 5); + + return 0; +} + +uint8_t lba_get_id2(void *priv, uint8_t *ret_buffer) +{ + uint8_t cmd_buf[] = { 0x92 , 0x00, /* Data read 5bytes*/ }; + struct lba_cmd lba_flags[] = { + {2 , F_CMD | FE_CMD_INC | FE_W4R}, + {0, F_DATA_READ | FE_END}, + }; + + queue_cmd(priv, cmd_buf, 0, 2, g_data_handle, 5, lba_flags); + queue_run(priv); + memcpy(ret_buffer, g_data_buffer, 5); + return 0; +} + +uint8_t lba_get_status2(void *priv) +{ + uint8_t cmd_buf[] = { 0x71 }; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_CMD_INC | FE_W4R}, + {0 , F_DATA_READ | FE_END}, + }; + *g_data_buffer = 0; + queue_cmd(priv, cmd_buf, 0, 1, g_data_handle, 1, lba_flags); + queue_run(priv); + return *g_data_buffer; +} + +static uint8_t lba_parse_status2(void *priv) +{ + uint8_t stat; + + stat = lba_get_status2(priv); + printk(KERN_INFO "Status2:|"); + if (stat & 0x40) + printk(" C.PAR.ERR |"); /* no KERN_ here */ + if (stat & 0x20) + printk(" NO spare |"); + if (stat & 0x10) + printk(" ADDR OoRange |"); + if (stat & 0x8) + printk(" high speed |"); + if ((stat & 0x6) == 6) + printk(" MDP |"); + if ((stat & 0x6) == 4) + printk(" VFP |"); + if ((stat & 0x6) == 2) + printk(" PNP |"); + if (stat & 1) + printk(" PSW |"); + + printk("\n"); + return 0; +} + + +int lba_2mdp(void *priv) +{ + uint8_t cmd_buf[] = { 0xFC }; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_W4R | FE_END} + }; + + queue_cmd(priv, cmd_buf, 0, 1, 0, 0, lba_flags); + queue_run(priv); + return 0; +} + +void _lba_misc_cmd_set(void *priv, uint8_t *cmd_buf) +{ + struct lba_cmd lba_flags[] = { + {6 , F_CMD | FE_CMD_INC | FE_W4R }, + {1 , F_CMD | FE_END }, + }; + + queue_cmd(priv, cmd_buf, 0, 7, 0, 0, lba_flags); + queue_run(priv); +} + +uint8_t _lba_misc_cmd_get(void *priv, uint8_t *cmd_buf) +{ + struct lba_cmd lba_flags[] = { + {6 , F_CMD | FE_CMD_INC | FE_W4R }, + {1 , F_CMD }, + {0 , F_DATA_READ | FE_W4R | FE_END} + }; + + queue_cmd(priv, cmd_buf, 0, 7, g_data_handle, 1, lba_flags); + queue_run(priv); + return *g_data_buffer; +} +void lba_mdp2vfp(void *priv, uint8_t pass[2]) +{ + uint8_t cmd_buf[] = { 0x0, 0xbe, pass[0], pass[1], 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_bcm2vfp(void *priv, uint8_t pass[2]) +{ + lba_mdp2vfp(priv, pass); +} + +void lba_powersave_enable(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xba, 0, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} +void lba_powersave_disable(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xbb, 0, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_highspeed_enable(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xbc, 0, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_highspeed_disable(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xbd, 0, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_prot1_set(void *priv, uint8_t mode) +{ + uint8_t cmd_buf[] = { 0x0, 0xa2, mode, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_prot2_set(void *priv, uint8_t mode) +{ + uint8_t cmd_buf[] = { 0x0, 0xa3, mode, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +uint8_t lba_prot1_get(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xb2, 0, 0, 0, 0, 0x57 }; + return _lba_misc_cmd_get(priv, cmd_buf); +} + +uint8_t lba_prot2_get(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xb3, 0, 0, 0, 0, 0x57 }; + return _lba_misc_cmd_get(priv, cmd_buf); +} + +uint64_t lba_mdp_size_get(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xb0, 0, 0, 0, 0, 0x57 }; + struct lba_cmd lba_flags[] = { + {6 , F_CMD | FE_CMD_INC | FE_W4R }, + {1 , F_CMD }, + {0 , F_DATA_READ | FE_W4R | FE_END} + }; + + memset((void *)g_data_buffer, 0, 8); + queue_cmd(priv, cmd_buf, 0, 7, g_data_handle, 5, lba_flags); + queue_run(priv); + return le64_to_cpu(*(long long *)g_data_buffer); +} + +void lba_cache_flush(void *priv) +{ + uint8_t cmd_buf[] = { 0xF9 }; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_W4R }, + {0 , FE_W4R | FE_END} + }; + + queue_cmd(priv, cmd_buf, 0, 7, g_data_handle, 5, lba_flags); + queue_run(priv); +} + +void lba_reboot(void *priv) +{ + uint8_t cmd_buf[] = { 0xFD }; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_W4R }, + {0 , FE_W4R | FE_END} + }; + + queue_cmd(priv, cmd_buf, 0, 7, g_data_handle, 5, lba_flags); + queue_run(priv); +} + +void lba_def_state(void *priv) +{ + lba_wait_for_ready(priv); + lba_reboot(priv); + + lba_wait_for_ready(priv); + lba_parse_status2(priv); + + lba_wait_for_ready(priv); + lba_2mdp(priv); + + lba_wait_for_ready(priv); + lba_prot1_set(priv, LBA_T_SIZE8); /* 512 * 8 */ + + lba_wait_for_ready(priv); +/* Type C read; Type A write; */ + lba_prot2_set(priv, LBA_P_WRITE_A | LBA_P_READ_C); +} + +/* + * Should be called with mode locked + */ +void lba_core_setvfp_passwd(struct lba_data *data, uint8_t pass[2]) +{ + memcpy(data->pass, pass, 2); +} + +int lba_core_lock_mode(struct lba_data *data, int mode) +{ + void *priv = &data->nand; + + if (down_interruptible(&data->mode_lock)) + return -EAGAIN; + /* + * MDP and VFP are the only supported + * modes for now. + */ + if ((mode != LBA_MODE_MDP) && + (mode != LBA_MODE_VFP)) { + up(&data->mode_lock); + return -EINVAL; + } + + while ((data->mode & LBA_MODE_MASK) == LBA_MODE_SUSP) { + up(&data->mode_lock); + + if (wait_event_interruptible( + data->suspend_q, + (data->mode & LBA_MODE_MASK) != LBA_MODE_SUSP)) + return -EAGAIN; + + if (down_interruptible(&data->mode_lock)) + return -EAGAIN; + + data->last_access = jiffies; + } + + if (data->mode & LBA_MODE_SELFPM) { + queue_plug(data); + data->mode &= ~LBA_MODE_SELFPM; + } + + if (mode == data->mode) + return 0; + + /* + * mode = VFP || MDP only + * Revisit when more modes are added + */ + switch (data->mode) { + case LBA_MODE_RST: + case LBA_MODE_PNR: + case LBA_MODE_BCM: + lba_def_state(priv); + if (mode == LBA_MODE_MDP) { + data->mode = LBA_MODE_MDP; + break; + } + /*no break -> fall down to set VFP mode*/ + case LBA_MODE_MDP: + lba_wait_for_ready(priv); + lba_mdp2vfp(priv, data->pass); + data->mode = LBA_MODE_VFP; + break; + case LBA_MODE_VFP: + lba_wait_for_ready(priv); + lba_2mdp(priv); + data->mode = LBA_MODE_MDP; + break; + default: + up(&data->mode_lock); + return -EINVAL; + } + + return 0; +} + +int lba_core_unlock_mode(struct lba_data *data) +{ + data->last_access = jiffies; + up(&data->mode_lock); + wake_up(&data->selfpm_q); + return 0; +} + +static int selfpm_timeout_expired(struct lba_data *data) +{ + return jiffies_to_msecs(jiffies - data->last_access) > 2000; +} + +static int lba_selfpm_thread(void *d) +{ + struct lba_data *data = d; + + set_user_nice(current, -5); + + while (!kthread_should_stop()) { + + if (wait_event_interruptible(data->selfpm_q, + kthread_should_stop() || + !(data->mode & LBA_MODE_SELFPM))) + continue; + + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(LBA_SELFPM_TIMEOUT)); + + if (down_trylock(&data->mode_lock)) + continue; + + if (!selfpm_timeout_expired(data)) { + up(&data->mode_lock); + continue; + } + data->mode |= LBA_MODE_SELFPM; + lba_wait_for_ready((void *)data->nand); + lba_cache_flush((void *)data->nand); + queue_release(data); + up(&data->mode_lock); + + } + + return 0; +} + +int lba_core_init(struct lba_data *data) +{ + uint8_t id_buf[5]; + uint8_t capacity; + uint8_t id1_template[5] = {0x98, 0xDC, 0x00, 0x15, 0x00}; + uint8_t id2_template[5] = {0x98, 0x21, 0x00, 0x55, 0xAA}; + void *priv = (void *)data->nand; + + + g_data = data; + g_cmd_handle = queue_get_cmd_handle(priv); + g_data_handle = queue_get_data_handle(priv); + g_data_buffer = queue_get_data_ptr(priv); + g_cmd_buffer = queue_get_cmd_ptr(priv); + + spin_lock_init(&data->lock); + sema_init(&data->mode_lock, 1); + init_waitqueue_head(&data->suspend_q); + init_waitqueue_head(&data->selfpm_q); + + + lba_get_id1(data->nand, id_buf); + if (!memcmp(id_buf, id1_template, 5)) + printk(KERN_INFO + "LBA: Found LBA/SLC NAND emulated ID\n"); + else + return -ENODEV; + + lba_get_id2(data->nand, id_buf); + capacity = id_buf[2]; + id_buf[2] = 0; + + if (memcmp(id_buf, id2_template, 5)) { + printk(KERN_INFO + "LBA: Uknown LBA device\n"); + return -ENODEV; + } + printk(KERN_INFO + "LBA: Found %dGbytes LBA NAND device\n", + 1 << capacity); + + lba_wait_for_ready(priv); + lba_parse_status2(priv); + + lba_def_state(priv); + data->mode = LBA_MODE_MDP; + + g_data->pnp_size = 0xff; + g_data->vfp_size = 16384; + + lba_wait_for_ready(priv); + g_data->mdp_size = lba_mdp_size_get(priv); + + lba_wait_for_ready(priv); + /*lba_powersave_enable(priv);*/ + /*lba_highspeed_enable(priv);*/ + + lba_wait_for_ready(priv); + lba_parse_status2(priv); + + data->thread = kthread_create(lba_selfpm_thread, + data, "lba-selfpm-%d", 1); + if (IS_ERR(data->thread)) + return PTR_ERR(data->thread); + + lba_blk_init(g_data); + + wake_up_process(data->thread); + return 0; + +}; + +int lba_core_remove(struct lba_data *data) +{ + kthread_stop(data->thread); + lba_blk_remove(data); + lba_wait_for_ready((void *)data->nand); + lba_cache_flush((void *)data->nand); + return 0; +} + +int lba_core_suspend(struct platform_device *pdev, struct lba_data *data) +{ + BUG_ON((data->mode & 0xffff) == LBA_MODE_SUSP); + if (down_interruptible(&data->mode_lock)) + return -EAGAIN; + if (data->mode & LBA_MODE_SELFPM) + queue_plug(data); + + data->mode = LBA_MODE_SUSP | LBA_MODE_SELFPM; + up(&data->mode_lock); + lba_wait_for_ready((void *)data->nand); + lba_cache_flush((void *)data->nand); + return 0; +} + +int lba_core_resume(struct platform_device *pdev, struct lba_data *data) +{ + BUG_ON((data->mode & 0xffff) != LBA_MODE_SUSP); + lba_def_state((void *)data->nand); + data->last_access = jiffies; + data->mode = LBA_MODE_MDP; + wake_up(&data->suspend_q); + return 0; +} diff --git a/drivers/mtd/nand/lba/lba.h b/drivers/mtd/nand/lba/lba.h new file mode 100644 index 000000000000..3466f96881eb --- /dev/null +++ b/drivers/mtd/nand/lba/lba.h @@ -0,0 +1,140 @@ +/* + * Freescale STMP37XX/STMP378X LBA interface + * + * Author: Dmitrij Frasenyak <sed@embeddedalley.com> + * + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __INCLUDE_LBA_H__ +#define __INCLUDE_LBA_H__ + + +#include <linux/spinlock.h> +#include <linux/kthread.h> +#include "gpmi.h" + +struct lba_cmd { + uint8_t len; +#define F_MASK 0x0f +#define F_ALE 0x01 +#define F_CMD 0x02 +#define F_DATA_READ 0x04 +#define F_DATA_WRITE 0x08 + +#define FE_W4R 0x10 +#define FE_CMD_INC 0x20 +#define FE_END 0x40 + + uint8_t flag; +}; + +#define LBA_P_READ_A 0 +#define LBA_P_READ_B 2 +#define LBA_P_READ_C 3 +#define LBA_P_WRITE_A 0 +#define LBA_P_WRITE_B 4 +#define LBA_T_SIZE1 1 +#define LBA_T_SIZE4 2 +#define LBA_T_SIZE8 4 +#define LBA_T_CRC (1 << 6) +#define LBA_T_ECC_CHECK (2 << 6) +#define LBA_T_ECC_CORRECT (3 << 6) + +struct lba_data { + void __iomem *io_base; + struct clk *clk; + int irq; + + spinlock_t lock; + int use_count; + int mode; + struct semaphore mode_lock; +#define LBA_MODE_MASK 0x0000ffff +#define LBA_FLAG_MASK 0xffff0000 +#define LBA_MODE_RST 0 +#define LBA_MODE_PNR 1 +#define LBA_MODE_BCM 2 +#define LBA_MODE_MDP 3 +#define LBA_MODE_VFP 4 +#define LBA_MODE_SUSP 5 +#define LBA_MODE_SELFPM 0x80000000 + wait_queue_head_t suspend_q; + wait_queue_head_t selfpm_q; + struct task_struct *thread; + long long last_access; + /* PNR specific */ + /* BCM specific */ + /* VFP specific */ + uint8_t pass[2]; + + /* Size of the partiotions: pages for PNP; sectors for others */ + unsigned int pnp_size; + unsigned int vfp_size; + long long mdp_size; + void *priv; + /*should be last*/ + struct gpmi_perchip_data nand[0]; + +}; + +extern struct lba_data *g_data; + +void stmp37cc_dma_print_chain(struct stmp37xx_circ_dma_chain *chain); + +int lba_blk_init(struct lba_data *data); +int lba_blk_remove(struct lba_data *data); +int lba_blk_suspend(struct platform_device *pdev, struct lba_data *data); +int lba_blk_resume(struct platform_device *pdev, struct lba_data *data); + + +int lba_core_init(struct lba_data *data); +int lba_core_remove(struct lba_data *data); +int lba_core_suspend(struct platform_device *pdev, struct lba_data *data); +int lba_core_resume(struct platform_device *pdev, struct lba_data *data); +int lba_core_lock_mode(struct lba_data *data, int mode); +int lba_core_unlock_mode(struct lba_data *data); + +int lba_write_sectors(void *priv, unsigned int sector, unsigned int count, + void *buffer, dma_addr_t handle); +int lba_read_sectors(void *priv, unsigned int sector, unsigned int count, + void *buffer, dma_addr_t handle); +void lba_protocol1_set(void *priv, uint8_t param); +uint8_t lba_protocol1_get(void *priv); +uint8_t lba_get_status1(void *priv); +uint8_t lba_get_status2(void *priv); + +uint8_t lba_get_id1(void *priv, uint8_t *ret_buffer); +uint8_t lba_get_id2(void *priv, uint8_t *); + + +int queue_cmd(void *priv, + uint8_t *cmd_buf, dma_addr_t cmd_handle, int cmd_len, + dma_addr_t data, int data_len, + struct lba_cmd *cmd_flags); + +int queue_run(void *priv); + +dma_addr_t queue_get_cmd_handle(void *priv); + +uint8_t *queue_get_cmd_ptr(void *priv); + +dma_addr_t queue_get_data_handle(void *priv); + +uint8_t *queue_get_data_ptr(void *priv); + +void queue_plug(struct lba_data *data); +void queue_release(struct lba_data *data); + + +#endif diff --git a/drivers/mtd/nand/mxc_nd.c b/drivers/mtd/nand/mxc_nd.c new file mode 100644 index 000000000000..fc95af3bd442 --- /dev/null +++ b/drivers/mtd/nand/mxc_nd.c @@ -0,0 +1,1413 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <asm/io.h> +#include <asm/mach/flash.h> + +#include "mxc_nd.h" + +#define DVR_VER "2.1" + +struct mxc_mtd_s { + struct mtd_info mtd; + struct nand_chip nand; + struct mtd_partition *parts; + struct device *dev; +}; + +static struct mxc_mtd_s *mxc_nand_data; + +/* + * Define delays in microsec for NAND device operations + */ +#define TROP_US_DELAY 2000 +/* + * Macros to get half word and bit positions of ECC + */ +#define COLPOS(x) ((x) >> 4) +#define BITPOS(x) ((x) & 0xf) + +/* Define single bit Error positions in Main & Spare area */ +#define MAIN_SINGLEBIT_ERROR 0x4 +#define SPARE_SINGLEBIT_ERROR 0x1 + +struct nand_info { + bool bSpareOnly; + bool bStatusRequest; + u16 colAddr; +}; + +static struct nand_info g_nandfc_info; + +#ifdef CONFIG_MTD_NAND_MXC_SWECC +static int hardware_ecc = 0; +#else +static int hardware_ecc = 1; +#endif + +#ifndef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 +static int Ecc_disabled; +#endif + +static int is2k_Pagesize = 0; + +static struct clk *nfc_clk; + +/* + * OOB placement block for use with hardware ecc generation + */ +static struct nand_ecclayout nand_hw_eccoob_8 = { + .eccbytes = 5, + .eccpos = {6, 7, 8, 9, 10}, + .oobfree = {{0, 5}, {11, 5}} +}; + +static struct nand_ecclayout nand_hw_eccoob_16 = { + .eccbytes = 5, + .eccpos = {6, 7, 8, 9, 10}, + .oobfree = {{0, 6}, {12, 4}} +}; + +static struct nand_ecclayout nand_hw_eccoob_2k = { + .eccbytes = 20, + .eccpos = {6, 7, 8, 9, 10, 22, 23, 24, 25, 26, + 38, 39, 40, 41, 42, 54, 55, 56, 57, 58}, + .oobfree = { + {.offset = 0, + .length = 5}, + + {.offset = 11, + .length = 10}, + + {.offset = 27, + .length = 10}, + + {.offset = 43, + .length = 10}, + + {.offset = 59, + .length = 5} + } +}; + +/*! + * @defgroup NAND_MTD NAND Flash MTD Driver for MXC processors + */ + +/*! + * @file mxc_nd.c + * + * @brief This file contains the hardware specific layer for NAND Flash on + * MXC processor + * + * @ingroup NAND_MTD + */ + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "RedBoot", "cmdlinepart", NULL }; +#endif + +static wait_queue_head_t irq_waitq; + +static irqreturn_t mxc_nfc_irq(int irq, void *dev_id) +{ + NFC_CONFIG1 |= NFC_INT_MSK; /* Disable interrupt */ + wake_up(&irq_waitq); + + return IRQ_RETVAL(1); +} + +/*! + * This function polls the NANDFC to wait for the basic operation to complete by + * checking the INT bit of config2 register. + * + * @param maxRetries number of retry attempts (separated by 1 us) + * @param param parameter for debug + * @param useirq True if IRQ should be used rather than polling + */ +static void wait_op_done(int maxRetries, u16 param, bool useirq) +{ + if (useirq) { + if ((NFC_CONFIG2 & NFC_INT) == 0) { + NFC_CONFIG1 &= ~NFC_INT_MSK; /* Enable interrupt */ + wait_event(irq_waitq, NFC_CONFIG2 & NFC_INT); + } + NFC_CONFIG2 &= ~NFC_INT; + } else { + while (maxRetries-- > 0) { + if (NFC_CONFIG2 & NFC_INT) { + NFC_CONFIG2 &= ~NFC_INT; + break; + } + udelay(1); + } + if (maxRetries <= 0) + DEBUG(MTD_DEBUG_LEVEL0, "%s(%d): INT not set\n", + __FUNCTION__, param); + } +} + +/*! + * This function issues the specified command to the NAND device and + * waits for completion. + * + * @param cmd command for NAND Flash + * @param useirq True if IRQ should be used rather than polling + */ +static void send_cmd(u16 cmd, bool useirq) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_cmd(0x%x, %d)\n", cmd, useirq); + + NFC_FLASH_CMD = (u16) cmd; + NFC_CONFIG2 = NFC_CMD; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, cmd, useirq); +} + +/*! + * This function sends an address (or partial address) to the + * NAND device. The address is used to select the source/destination for + * a NAND command. + * + * @param addr address to be written to NFC. + * @param islast True if this is the last address cycle for command + */ +static void send_addr(u16 addr, bool islast) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_addr(0x%x %d)\n", addr, islast); + + NFC_FLASH_ADDR = addr; + NFC_CONFIG2 = NFC_ADDR; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, addr, islast); +} + +/*! + * This function requests the NANDFC to initate the transfer + * of data currently in the NANDFC RAM buffer to the NAND device. + * + * @param buf_id Specify Internal RAM Buffer number (0-3) + * @param bSpareOnly set true if only the spare area is transferred + */ +static void send_prog_page(u8 buf_id, bool bSpareOnly) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_prog_page (%d)\n", bSpareOnly); + + /* NANDFC buffer 0 is used for page read/write */ + + NFC_BUF_ADDR = buf_id; + + /* Configure spare or page+spare access */ + if (!is2k_Pagesize) { + if (bSpareOnly) { + NFC_CONFIG1 |= NFC_SP_EN; + } else { + NFC_CONFIG1 &= ~(NFC_SP_EN); + } + } + NFC_CONFIG2 = NFC_INPUT; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, bSpareOnly, true); +} + +/*! + * This function will correct the single bit ECC error + * + * @param buf_id Specify Internal RAM Buffer number (0-3) + * @param eccpos Ecc byte and bit position + * @param bSpareOnly set to true if only spare area needs correction + */ + +static void mxc_nd_correct_error(u8 buf_id, u16 eccpos, bool bSpareOnly) +{ + u16 col; + u8 pos; + volatile u16 *buf; + + /* Get col & bit position of error + these macros works for both 8 & 16 bits */ + col = COLPOS(eccpos); /* Get half-word position */ + pos = BITPOS(eccpos); /* Get bit position */ + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nd_correct_error (col=%d pos=%d)\n", col, pos); + + /* Set the pointer for main / spare area */ + if (!bSpareOnly) { + buf = (volatile u16 *)(MAIN_AREA0 + col + (256 * buf_id)); + } else { + buf = (volatile u16 *)(SPARE_AREA0 + col + (8 * buf_id)); + } + + /* Fix the data */ + *buf ^= (1 << pos); +} + +/*! + * This function will maintains state of single bit Error + * in Main & spare area + * + * @param buf_id Specify Internal RAM Buffer number (0-3) + * @param spare set to true if only spare area needs correction + */ +static void mxc_nd_correct_ecc(u8 buf_id, bool spare) +{ +#ifdef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + static int lastErrMain = 0, lastErrSpare = 0; /* To maintain single bit + error in previous page */ +#endif + u16 value, ecc_status; + /* Read the ECC result */ + ecc_status = NFC_ECC_STATUS_RESULT; + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nd_correct_ecc (Ecc status=%x)\n", ecc_status); + +#ifdef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + /* Check for Error in Mainarea */ + if ((ecc_status & 0xC) == MAIN_SINGLEBIT_ERROR) { + /* Check for error in previous page */ + if (lastErrMain && !spare) { + value = NFC_RSLTMAIN_AREA; + /* Correct single bit error in Mainarea + NFC will not correct the error in + current page */ + mxc_nd_correct_error(buf_id, value, false); + } else { + /* Set if single bit error in current page */ + lastErrMain = 1; + } + } else { + /* Reset if no single bit error in current page */ + lastErrMain = 0; + } + + /* Check for Error in Sparearea */ + if ((ecc_status & 0x3) == SPARE_SINGLEBIT_ERROR) { + /* Check for error in previous page */ + if (lastErrSpare) { + value = NFC_RSLTSPARE_AREA; + /* Correct single bit error in Mainarea + NFC will not correct the error in + current page */ + mxc_nd_correct_error(buf_id, value, true); + } else { + /* Set if single bit error in current page */ + lastErrSpare = 1; + } + } else { + /* Reset if no single bit error in current page */ + lastErrSpare = 0; + } +#else + if (((ecc_status & 0xC) == MAIN_SINGLEBIT_ERROR) + || ((ecc_status & 0x3) == SPARE_SINGLEBIT_ERROR)) { + if (Ecc_disabled) { + if ((ecc_status & 0xC) == MAIN_SINGLEBIT_ERROR) { + value = NFC_RSLTMAIN_AREA; + /* Correct single bit error in Mainarea + NFC will not correct the error in + current page */ + mxc_nd_correct_error(buf_id, value, false); + } + if ((ecc_status & 0x3) == SPARE_SINGLEBIT_ERROR) { + value = NFC_RSLTSPARE_AREA; + /* Correct single bit error in Mainarea + NFC will not correct the error in + current page */ + mxc_nd_correct_error(buf_id, value, true); + } + + } else { + /* Disable ECC */ + NFC_CONFIG1 &= ~(NFC_ECC_EN); + Ecc_disabled = 1; + } + } else if (ecc_status == 0) { + if (Ecc_disabled) { + /* Enable ECC */ + NFC_CONFIG1 |= NFC_ECC_EN; + Ecc_disabled = 0; + } + } else { + /* 2-bit Error Do nothing */ + } +#endif /* CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 */ + +} + +/*! + * This function requests the NANDFC to initated the transfer + * of data from the NAND device into in the NANDFC ram buffer. + * + * @param buf_id Specify Internal RAM Buffer number (0-3) + * @param bSpareOnly set true if only the spare area is transferred + */ +static void send_read_page(u8 buf_id, bool bSpareOnly) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_read_page (%d)\n", bSpareOnly); + + /* NANDFC buffer 0 is used for page read/write */ + NFC_BUF_ADDR = buf_id; + + /* Configure spare or page+spare access */ + if (!is2k_Pagesize) { + if (bSpareOnly) { + NFC_CONFIG1 |= NFC_SP_EN; + } else { + NFC_CONFIG1 &= ~(NFC_SP_EN); + } + } + + NFC_CONFIG2 = NFC_OUTPUT; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, bSpareOnly, true); + + /* If there are single bit errors in + two consecutive page reads then + the error is not corrected by the + NFC for the second page. + Correct single bit error in driver */ + + mxc_nd_correct_ecc(buf_id, bSpareOnly); +} + +/*! + * This function requests the NANDFC to perform a read of the + * NAND device ID. + */ +static void send_read_id(void) +{ + struct nand_chip *this = &mxc_nand_data->nand; + + /* NANDFC buffer 0 is used for device ID output */ + NFC_BUF_ADDR = 0x0; + + /* Read ID into main buffer */ + NFC_CONFIG1 &= (~(NFC_SP_EN)); + NFC_CONFIG2 = NFC_ID; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, 0, true); + + if (this->options & NAND_BUSWIDTH_16) { + volatile u16 *mainBuf = MAIN_AREA0; + + /* + * Pack the every-other-byte result for 16-bit ID reads + * into every-byte as the generic code expects and various + * chips implement. + */ + + mainBuf[0] = (mainBuf[0] & 0xff) | ((mainBuf[1] & 0xff) << 8); + mainBuf[1] = (mainBuf[2] & 0xff) | ((mainBuf[3] & 0xff) << 8); + mainBuf[2] = (mainBuf[4] & 0xff) | ((mainBuf[5] & 0xff) << 8); + } +} + +/*! + * This function requests the NANDFC to perform a read of the + * NAND device status and returns the current status. + * + * @return device status + */ +static u16 get_dev_status(void) +{ + volatile u16 *mainBuf = MAIN_AREA1; + u32 store; + u16 ret; + /* Issue status request to NAND device */ + + /* store the main area1 first word, later do recovery */ + store = *((u32 *) mainBuf); + /* + * NANDFC buffer 1 is used for device status to prevent + * corruption of read/write buffer on status requests. + */ + NFC_BUF_ADDR = 1; + + /* Read status into main buffer */ + NFC_CONFIG1 &= (~(NFC_SP_EN)); + NFC_CONFIG2 = NFC_STATUS; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, 0, true); + + /* Status is placed in first word of main buffer */ + /* get status, then recovery area 1 data */ + ret = mainBuf[0]; + *((u32 *) mainBuf) = store; + + return ret; +} + +/*! + * This functions is used by upper layer to checks if device is ready + * + * @param mtd MTD structure for the NAND Flash + * + * @return 0 if device is busy else 1 + */ +static int mxc_nand_dev_ready(struct mtd_info *mtd) +{ + /* + * NFC handles R/B internally.Therefore,this function + * always returns status as ready. + */ + return 1; +} + +static void mxc_nand_enable_hwecc(struct mtd_info *mtd, int mode) +{ + /* + * If HW ECC is enabled, we turn it on during init. There is + * no need to enable again here. + */ +} + +static int mxc_nand_correct_data(struct mtd_info *mtd, u_char * dat, + u_char * read_ecc, u_char * calc_ecc) +{ + /* + * 1-Bit errors are automatically corrected in HW. No need for + * additional correction. 2-Bit errors cannot be corrected by + * HW ECC, so we need to return failure + */ + u16 ecc_status = NFC_ECC_STATUS_RESULT; + + if (((ecc_status & 0x3) == 2) || ((ecc_status >> 2) == 2)) { + DEBUG(MTD_DEBUG_LEVEL0, + "MXC_NAND: HWECC uncorrectable 2-bit ECC error\n"); + return -1; + } + + return 0; +} + +static int mxc_nand_calculate_ecc(struct mtd_info *mtd, const u_char * dat, + u_char * ecc_code) +{ + /* + * Just return success. HW ECC does not read/write the NFC spare + * buffer. Only the FLASH spare area contains the calcuated ECC. + */ + return 0; +} + +/*! + * This function reads byte from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static u_char mxc_nand_read_byte(struct mtd_info *mtd) +{ + u_char retVal = 0; + u16 col, rdWord; + volatile u16 *mainBuf = MAIN_AREA0; + volatile u16 *spareBuf = SPARE_AREA0; + + /* Check for status request */ + if (g_nandfc_info.bStatusRequest) { + return (get_dev_status() & 0xFF); + } + + /* Get column for 16-bit access */ + col = g_nandfc_info.colAddr >> 1; + + /* If we are accessing the spare region */ + if (g_nandfc_info.bSpareOnly) { + rdWord = spareBuf[col]; + } else { + rdWord = mainBuf[col]; + } + + /* Pick upper/lower byte of word from RAM buffer */ + if (g_nandfc_info.colAddr & 0x1) { + retVal = (rdWord >> 8) & 0xFF; + } else { + retVal = rdWord & 0xFF; + } + + /* Update saved column address */ + g_nandfc_info.colAddr++; + + return retVal; +} + +/*! + * This function reads word from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static u16 mxc_nand_read_word(struct mtd_info *mtd) +{ + u16 col; + u16 rdWord, retVal; + volatile u16 *p; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_read_word(col = %d)\n", g_nandfc_info.colAddr); + + col = g_nandfc_info.colAddr; + /* Adjust saved column address */ + if (col < mtd->writesize && g_nandfc_info.bSpareOnly) + col += mtd->writesize; + + if (col < mtd->writesize) + p = (MAIN_AREA0) + (col >> 1); + else + p = (SPARE_AREA0) + ((col - mtd->writesize) >> 1); + + if (col & 1) { + rdWord = *p; + retVal = (rdWord >> 8) & 0xff; + rdWord = *(p + 1); + retVal |= (rdWord << 8) & 0xff00; + + } else { + retVal = *p; + + } + + /* Update saved column address */ + g_nandfc_info.colAddr = col + 2; + + return retVal; +} + +/*! + * This function writes data of length \b len to buffer \b buf. The data to be + * written on NAND Flash is first copied to RAMbuffer. After the Data Input + * Operation by the NFC, the data is written to NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be written to NAND Flash + * @param len number of bytes to be written + */ +static void mxc_nand_write_buf(struct mtd_info *mtd, + const u_char * buf, int len) +{ + int n; + int col; + int i = 0; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_write_buf(col = %d, len = %d)\n", g_nandfc_info.colAddr, + len); + + col = g_nandfc_info.colAddr; + + /* Adjust saved column address */ + if (col < mtd->writesize && g_nandfc_info.bSpareOnly) + col += mtd->writesize; + + n = mtd->writesize + mtd->oobsize - col; + n = min(len, n); + + DEBUG(MTD_DEBUG_LEVEL3, + "%s:%d: col = %d, n = %d\n", __FUNCTION__, __LINE__, col, n); + + while (n) { + volatile u32 *p; + if (col < mtd->writesize) + p = (volatile u32 *)((ulong) (MAIN_AREA0) + (col & ~3)); + else + p = (volatile u32 *)((ulong) (SPARE_AREA0) - + mtd->writesize + (col & ~3)); + + DEBUG(MTD_DEBUG_LEVEL3, "%s:%d: p = %p\n", __FUNCTION__, + __LINE__, p); + + if (((col | (int)&buf[i]) & 3) || n < 16) { + u32 data = 0; + + if (col & 3 || n < 4) + data = *p; + + switch (col & 3) { + case 0: + if (n) { + data = (data & 0xffffff00) | + (buf[i++] << 0); + n--; + col++; + } + case 1: + if (n) { + data = (data & 0xffff00ff) | + (buf[i++] << 8); + n--; + col++; + } + case 2: + if (n) { + data = (data & 0xff00ffff) | + (buf[i++] << 16); + n--; + col++; + } + case 3: + if (n) { + data = (data & 0x00ffffff) | + (buf[i++] << 24); + n--; + col++; + } + } + + *p = data; + } else { + int m = mtd->writesize - col; + + if (col >= mtd->writesize) + m += mtd->oobsize; + + m = min(n, m) & ~3; + + DEBUG(MTD_DEBUG_LEVEL3, + "%s:%d: n = %d, m = %d, i = %d, col = %d\n", + __FUNCTION__, __LINE__, n, m, i, col); + + memcpy((void *)(p), &buf[i], m); + col += m; + i += m; + n -= m; + } + } + /* Update saved column address */ + g_nandfc_info.colAddr = col; + +} + +/*! + * This function id is used to read the data buffer from the NAND Flash. To + * read the data from NAND Flash first the data output cycle is initiated by + * the NFC, which copies the data to RAMbuffer. This data of length \b len is + * then copied to buffer \b buf. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be read from NAND Flash + * @param len number of bytes to be read + */ +static void mxc_nand_read_buf(struct mtd_info *mtd, u_char * buf, int len) +{ + + int n; + int col; + int i = 0; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_read_buf(col = %d, len = %d)\n", g_nandfc_info.colAddr, + len); + + col = g_nandfc_info.colAddr; + /* Adjust saved column address */ + if (col < mtd->writesize && g_nandfc_info.bSpareOnly) + col += mtd->writesize; + + n = mtd->writesize + mtd->oobsize - col; + n = min(len, n); + + while (n) { + volatile u32 *p; + + if (col < mtd->writesize) + p = (volatile u32 *)((ulong) (MAIN_AREA0) + (col & ~3)); + else + p = (volatile u32 *)((ulong) (SPARE_AREA0) - + mtd->writesize + (col & ~3)); + + if (((col | (int)&buf[i]) & 3) || n < 16) { + u32 data; + + data = *p; + switch (col & 3) { + case 0: + if (n) { + buf[i++] = (u8) (data); + n--; + col++; + } + case 1: + if (n) { + buf[i++] = (u8) (data >> 8); + n--; + col++; + } + case 2: + if (n) { + buf[i++] = (u8) (data >> 16); + n--; + col++; + } + case 3: + if (n) { + buf[i++] = (u8) (data >> 24); + n--; + col++; + } + } + } else { + int m = mtd->writesize - col; + + if (col >= mtd->writesize) + m += mtd->oobsize; + + m = min(n, m) & ~3; + memcpy(&buf[i], (void *)(p), m); + col += m; + i += m; + n -= m; + } + } + /* Update saved column address */ + g_nandfc_info.colAddr = col; + +} + +/*! + * This function is used by the upper layer to verify the data in NAND Flash + * with the data in the \b buf. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be verified + * @param len length of the data to be verified + * + * @return -EFAULT if error else 0 + * + */ +static int +mxc_nand_verify_buf(struct mtd_info *mtd, const u_char * buf, int len) +{ + return -EFAULT; +} + +/*! + * This function is used by upper layer for select and deselect of the NAND + * chip + * + * @param mtd MTD structure for the NAND Flash + * @param chip val indicating select or deselect + */ +static void mxc_nand_select_chip(struct mtd_info *mtd, int chip) +{ +#ifdef CONFIG_MTD_NAND_MXC_FORCE_CE + if (chip > 0) { + DEBUG(MTD_DEBUG_LEVEL0, + "ERROR: Illegal chip select (chip = %d)\n", chip); + return; + } + + if (chip == -1) { + NFC_CONFIG1 &= (~(NFC_CE)); + return; + } + + NFC_CONFIG1 |= NFC_CE; +#endif + + switch (chip) { + case -1: + /* Disable the NFC clock */ + clk_disable(nfc_clk); + break; + case 0: + /* Enable the NFC clock */ + clk_enable(nfc_clk); + break; + + default: + break; + } +} + +/*! + * This function is used by the upper layer to write command to NAND Flash for + * different operations to be carried out on NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * @param command command for NAND Flash + * @param column column offset for the page read + * @param page_addr page to be read from NAND Flash + */ +static void mxc_nand_command(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + bool useirq = true; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_command (cmd = 0x%x, col = 0x%x, page = 0x%x)\n", + command, column, page_addr); + + /* + * Reset command state information + */ + g_nandfc_info.bStatusRequest = false; + + /* Reset column address to 0 */ + g_nandfc_info.colAddr = 0; + + /* + * Command pre-processing step + */ + switch (command) { + + case NAND_CMD_STATUS: + g_nandfc_info.bStatusRequest = true; + break; + + case NAND_CMD_READ0: + g_nandfc_info.colAddr = column; + g_nandfc_info.bSpareOnly = false; + useirq = false; + break; + + case NAND_CMD_READOOB: + g_nandfc_info.colAddr = column; + g_nandfc_info.bSpareOnly = true; + useirq = false; + if (is2k_Pagesize) + command = NAND_CMD_READ0; /* only READ0 is valid */ + break; + + case NAND_CMD_SEQIN: + if (column >= mtd->writesize) { + if (is2k_Pagesize) { + /** + * FIXME: before send SEQIN command for write OOB, + * We must read one page out. + * For K9F1GXX has no READ1 command to set current HW + * pointer to spare area, we must write the whole page including OOB together. + */ + /* call itself to read a page */ + mxc_nand_command(mtd, NAND_CMD_READ0, 0, + page_addr); + } + g_nandfc_info.colAddr = column - mtd->writesize; + g_nandfc_info.bSpareOnly = true; + /* Set program pointer to spare region */ + if (!is2k_Pagesize) + send_cmd(NAND_CMD_READOOB, false); + } else { + g_nandfc_info.bSpareOnly = false; + g_nandfc_info.colAddr = column; + /* Set program pointer to page start */ + if (!is2k_Pagesize) + send_cmd(NAND_CMD_READ0, false); + } + useirq = false; + break; + + case NAND_CMD_PAGEPROG: +#ifndef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + if (Ecc_disabled) { + /* Enable Ecc for page writes */ + NFC_CONFIG1 |= NFC_ECC_EN; + } +#endif + + send_prog_page(0, g_nandfc_info.bSpareOnly); + + if (is2k_Pagesize) { + /* data in 4 areas datas */ + send_prog_page(1, g_nandfc_info.bSpareOnly); + send_prog_page(2, g_nandfc_info.bSpareOnly); + send_prog_page(3, g_nandfc_info.bSpareOnly); + } + + break; + + case NAND_CMD_ERASE1: + useirq = false; + break; + } + + /* + * Write out the command to the device. + */ + send_cmd(command, useirq); + + /* + * Write out column address, if necessary + */ + if (column != -1) { + /* + * MXC NANDFC can only perform full page+spare or + * spare-only read/write. When the upper layers + * layers perform a read/write buf operation, + * we will used the saved column adress to index into + * the full page. + */ + send_addr(0, page_addr == -1); + if (is2k_Pagesize) + /* another col addr cycle for 2k page */ + send_addr(0, false); + } + + /* + * Write out page address, if necessary + */ + if (page_addr != -1) { + send_addr((page_addr & 0xff), false); /* paddr_0 - p_addr_7 */ + + if (is2k_Pagesize) { + /* One more address cycle for higher density devices */ + if (mtd->size >= 0x10000000) { + /* paddr_8 - paddr_15 */ + send_addr((page_addr >> 8) & 0xff, false); + send_addr((page_addr >> 16) & 0xff, true); + } else + /* paddr_8 - paddr_15 */ + send_addr((page_addr >> 8) & 0xff, true); + } else { + /* One more address cycle for higher density devices */ + if (mtd->size >= 0x4000000) { + /* paddr_8 - paddr_15 */ + send_addr((page_addr >> 8) & 0xff, false); + send_addr((page_addr >> 16) & 0xff, true); + } else + /* paddr_8 - paddr_15 */ + send_addr((page_addr >> 8) & 0xff, true); + } + } + + /* + * Command post-processing step + */ + switch (command) { + + case NAND_CMD_RESET: + break; + + case NAND_CMD_READOOB: + case NAND_CMD_READ0: + if (is2k_Pagesize) { + /* send read confirm command */ + send_cmd(NAND_CMD_READSTART, true); + /* read for each AREA */ + send_read_page(0, g_nandfc_info.bSpareOnly); + send_read_page(1, g_nandfc_info.bSpareOnly); + send_read_page(2, g_nandfc_info.bSpareOnly); + send_read_page(3, g_nandfc_info.bSpareOnly); + } else { + send_read_page(0, g_nandfc_info.bSpareOnly); + } + break; + + case NAND_CMD_READID: + send_read_id(); + break; + + case NAND_CMD_PAGEPROG: +#ifndef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + if (Ecc_disabled) { + /* Disble Ecc after page writes */ + NFC_CONFIG1 &= ~(NFC_ECC_EN); + } +#endif + break; + + case NAND_CMD_STATUS: + break; + + case NAND_CMD_ERASE2: + break; + } +} + +/* Define some generic bad / good block scan pattern which are used + * while scanning a device for factory marked good / bad blocks. */ +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; + +static struct nand_bbt_descr smallpage_memorybased = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 5, + .len = 1, + .pattern = scan_ff_pattern +}; + +static struct nand_bbt_descr largepage_memorybased = { + .options = 0, + .offs = 0, + .len = 2, + .pattern = scan_ff_pattern +}; + +/* Generic flash bbt decriptors +*/ +static uint8_t bbt_pattern[] = { 'B', 'b', 't', '0' }; +static uint8_t mirror_pattern[] = { '1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = mirror_pattern +}; + +static int mxc_nand_scan_bbt(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + + /* Config before scanning */ + /* Do not rely on NFMS_BIT, set/clear NFMS bit based on mtd->writesize */ + if (mtd->writesize == 2048) { + NFMS |= (1 << NFMS_BIT); + is2k_Pagesize = 1; + } else { + if ((NFMS >> NFMS_BIT) & 0x1) { /* This case strangly happened on MXC91321 P1.2.2 */ + printk(KERN_INFO + "Oops... NFMS Bit set for 512B Page, resetting it. [RCSR: 0x%08x]\n", + NFMS); + NFMS &= ~(1 << NFMS_BIT); + } + is2k_Pagesize = 0; + } + + if (is2k_Pagesize) + this->ecc.layout = &nand_hw_eccoob_2k; + + /* jffs2 not write oob */ + mtd->flags &= ~MTD_OOB_WRITEABLE; + + /* use flash based bbt */ + this->bbt_td = &bbt_main_descr; + this->bbt_md = &bbt_mirror_descr; + + /* update flash based bbt */ + this->options |= NAND_USE_FLASH_BBT; + + if (!this->badblock_pattern) { + if (mtd->writesize == 2048) + this->badblock_pattern = &smallpage_memorybased; + else + this->badblock_pattern = (mtd->writesize > 512) ? + &largepage_memorybased : &smallpage_memorybased; + } + /* Build bad block table */ + return nand_scan_bbt(mtd, this->badblock_pattern); +} + +#ifdef CONFIG_MXC_NAND_LOW_LEVEL_ERASE +static void mxc_low_erase(struct mtd_info *mtd) +{ + + struct nand_chip *this = mtd->priv; + unsigned int page_addr, addr; + u_char status; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND : mxc_low_erase:Erasing NAND\n"); + for (addr = 0; addr < this->chipsize; addr += mtd->erasesize) { + page_addr = addr / mtd->writesize; + mxc_nand_command(mtd, NAND_CMD_ERASE1, -1, page_addr); + mxc_nand_command(mtd, NAND_CMD_ERASE2, -1, -1); + mxc_nand_command(mtd, NAND_CMD_STATUS, -1, -1); + status = mxc_nand_read_byte(mtd); + if (status & NAND_STATUS_FAIL) { + printk(KERN_ERR + "ERASE FAILED(block = %d,status = 0x%x)\n", + addr / mtd->erasesize, status); + } + } + +} +#endif +/*! + * This function is called during the driver binding process. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and + * remove functions + * + * @return The function always returns 0. + */ +static int __init mxcnd_probe(struct platform_device *pdev) +{ + struct nand_chip *this; + struct mtd_info *mtd; + struct flash_platform_data *flash = pdev->dev.platform_data; + int nr_parts = 0; + + int err = 0; + /* Allocate memory for MTD device structure and private data */ + mxc_nand_data = kzalloc(sizeof(struct mxc_mtd_s), GFP_KERNEL); + if (!mxc_nand_data) { + printk(KERN_ERR "%s: failed to allocate mtd_info\n", + __FUNCTION__); + err = -ENOMEM; + goto out; + } + memset((char *)&g_nandfc_info, 0, sizeof(g_nandfc_info)); + + mxc_nand_data->dev = &pdev->dev; + /* structures must be linked */ + this = &mxc_nand_data->nand; + mtd = &mxc_nand_data->mtd; + mtd->priv = this; + mtd->owner = THIS_MODULE; + + /* 50 us command delay time */ + this->chip_delay = 5; + + this->priv = mxc_nand_data; + this->dev_ready = mxc_nand_dev_ready; + this->cmdfunc = mxc_nand_command; + this->select_chip = mxc_nand_select_chip; + this->read_byte = mxc_nand_read_byte; + this->read_word = mxc_nand_read_word; + this->write_buf = mxc_nand_write_buf; + this->read_buf = mxc_nand_read_buf; + this->verify_buf = mxc_nand_verify_buf; + this->scan_bbt = mxc_nand_scan_bbt; + + nfc_clk = clk_get(&pdev->dev, "nfc_clk"); + clk_enable(nfc_clk); + + NFC_CONFIG1 |= NFC_INT_MSK; + init_waitqueue_head(&irq_waitq); + err = request_irq(MXC_INT_NANDFC, mxc_nfc_irq, 0, "mxc_nd", NULL); + if (err) { + goto out_1; + } + + if (hardware_ecc) { + this->ecc.calculate = mxc_nand_calculate_ecc; + this->ecc.hwctl = mxc_nand_enable_hwecc; + this->ecc.correct = mxc_nand_correct_data; + this->ecc.mode = NAND_ECC_HW; + this->ecc.size = 512; + this->ecc.bytes = 3; + this->ecc.layout = &nand_hw_eccoob_8; + NFC_CONFIG1 |= NFC_ECC_EN; + } else { + this->ecc.mode = NAND_ECC_SOFT; + } + + /* Reset NAND */ + this->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + + /* preset operation */ + /* Unlock the internal RAM Buffer */ + NFC_CONFIG = 0x2; + + /* Blocks to be unlocked */ + NFC_UNLOCKSTART_BLKADDR = 0x0; + NFC_UNLOCKEND_BLKADDR = 0x4000; + + /* Unlock Block Command for given address range */ + NFC_WRPROT = 0x4; + + /* NAND bus width determines access funtions used by upper layer */ + if (flash->width == 2) { + this->options |= NAND_BUSWIDTH_16; + this->ecc.layout = &nand_hw_eccoob_16; + } else { + this->options |= 0; + } + + is2k_Pagesize = 0; + + /* Scan to find existence of the device */ + if (nand_scan(mtd, 1)) { + DEBUG(MTD_DEBUG_LEVEL0, + "MXC_ND: Unable to find any NAND device.\n"); + err = -ENXIO; + goto out_1; + } + + /* Register the partitions */ +#ifdef CONFIG_MTD_PARTITIONS + nr_parts = + parse_mtd_partitions(mtd, part_probes, &mxc_nand_data->parts, 0); + if (nr_parts > 0) + add_mtd_partitions(mtd, mxc_nand_data->parts, nr_parts); + else if (flash->parts) + add_mtd_partitions(mtd, flash->parts, flash->nr_parts); + else +#endif + { + pr_info("Registering %s as whole device\n", mtd->name); + add_mtd_device(mtd); + } +#ifdef CONFIG_MXC_NAND_LOW_LEVEL_ERASE + /* Erase all the blocks of a NAND */ + mxc_low_erase(mtd); +#endif + + platform_set_drvdata(pdev, mtd); + return 0; + + out_1: + kfree(mxc_nand_data); + out: + return err; + +} + + /*! + * Dissociates the driver from the device. + * + * @param pdev the device structure used to give information on which + * + * @return The function always returns 0. + */ + +static int __exit mxcnd_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + + clk_put(nfc_clk); + platform_set_drvdata(pdev, NULL); + + if (mxc_nand_data) { + nand_release(mtd); + free_irq(MXC_INT_NANDFC, NULL); + kfree(mxc_nand_data); + } + + return 0; +} + +#ifdef CONFIG_PM +/*! + * This function is called to put the NAND in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device information structure + * + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure + */ + +static int mxcnd_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mtd_info *info = platform_get_drvdata(pdev); + int ret = 0; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND : NAND suspend\n"); + if (info) + ret = info->suspend(info); + + /* Disable the NFC clock */ + clk_disable(nfc_clk); + + return ret; +} + +/*! + * This function is called to bring the NAND back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device information structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxcnd_resume(struct platform_device *pdev) +{ + struct mtd_info *info = platform_get_drvdata(pdev); + int ret = 0; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND : NAND resume\n"); + /* Enable the NFC clock */ + clk_enable(nfc_clk); + + if (info) { + info->resume(info); + } + + return ret; +} + +#else +#define mxcnd_suspend NULL +#define mxcnd_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcnd_driver = { + .driver = { + .name = "mxc_nand_flash", + }, + .probe = mxcnd_probe, + .remove = __exit_p(mxcnd_remove), + .suspend = mxcnd_suspend, + .resume = mxcnd_resume, +}; + +/*! + * Main initialization routine + * @return 0 if successful; non-zero otherwise + */ +static int __init mxc_nd_init(void) +{ + /* Register the device driver structure. */ + pr_info("MXC MTD nand Driver %s\n", DVR_VER); + if (platform_driver_register(&mxcnd_driver) != 0) { + printk(KERN_ERR "Driver register failed for mxcnd_driver\n"); + return -ENODEV; + } + return 0; +} + +/*! + * Clean up routine + */ +static void __exit mxc_nd_cleanup(void) +{ + /* Unregister the device structure */ + platform_driver_unregister(&mxcnd_driver); +} + +module_init(mxc_nd_init); +module_exit(mxc_nd_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC NAND MTD driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/mxc_nd.h b/drivers/mtd/nand/mxc_nd.h new file mode 100644 index 000000000000..e38a9a637c1d --- /dev/null +++ b/drivers/mtd/nand/mxc_nd.h @@ -0,0 +1,112 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_nd.h + * + * @brief This file contains the NAND Flash Controller register information. + * + * + * @ingroup NAND_MTD + */ + +#ifndef __MXC_ND_H__ +#define __MXC_ND_H__ + +#include <mach/hardware.h> + +/* + * Addresses for NFC registers + */ +#define NFC_BUF_SIZE (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE00))) +#define NFC_BUF_ADDR (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE04))) +#define NFC_FLASH_ADDR (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE06))) +#define NFC_FLASH_CMD (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE08))) +#define NFC_CONFIG (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE0A))) +#define NFC_ECC_STATUS_RESULT (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE0C))) +#define NFC_RSLTMAIN_AREA (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE0E))) +#define NFC_RSLTSPARE_AREA (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE10))) +#define NFC_WRPROT (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE12))) +#define NFC_UNLOCKSTART_BLKADDR (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE14))) +#define NFC_UNLOCKEND_BLKADDR (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE16))) +#define NFC_NF_WRPRST (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE18))) +#define NFC_CONFIG1 (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE1A))) +#define NFC_CONFIG2 (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE1C))) + +/*! + * Addresses for NFC RAM BUFFER Main area 0 + */ +#define MAIN_AREA0 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x000) +#define MAIN_AREA1 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x200) +#define MAIN_AREA2 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x400) +#define MAIN_AREA3 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x600) + +/*! + * Addresses for NFC SPARE BUFFER Spare area 0 + */ +#define SPARE_AREA0 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x800) +#define SPARE_AREA1 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x810) +#define SPARE_AREA2 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x820) +#define SPARE_AREA3 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x830) + +/*! + * Set INT to 0, FCMD to 1, rest to 0 in NFC_CONFIG2 Register for Command + * operation + */ +#define NFC_CMD 0x1 + +/*! + * Set INT to 0, FADD to 1, rest to 0 in NFC_CONFIG2 Register for Address + * operation + */ +#define NFC_ADDR 0x2 + +/*! + * Set INT to 0, FDI to 1, rest to 0 in NFC_CONFIG2 Register for Input + * operation + */ +#define NFC_INPUT 0x4 + +/*! + * Set INT to 0, FDO to 001, rest to 0 in NFC_CONFIG2 Register for Data Output + * operation + */ +#define NFC_OUTPUT 0x8 + +/*! + * Set INT to 0, FD0 to 010, rest to 0 in NFC_CONFIG2 Register for Read ID + * operation + */ +#define NFC_ID 0x10 + +/*! + * Set INT to 0, FDO to 100, rest to 0 in NFC_CONFIG2 Register for Read Status + * operation + */ +#define NFC_STATUS 0x20 + +/*! + * Set INT to 1, rest to 0 in NFC_CONFIG2 Register for Read Status + * operation + */ +#define NFC_INT 0x8000 + +#define NFC_SP_EN (1 << 2) +#define NFC_ECC_EN (1 << 3) +#define NFC_INT_MSK (1 << 4) +#define NFC_BIG (1 << 5) +#define NFC_RST (1 << 6) +#define NFC_CE (1 << 7) +#define NFC_ONE_CYCLE (1 << 8) + +#endif /* MXCND_H */ diff --git a/drivers/mtd/nand/mxc_nd2.c b/drivers/mtd/nand/mxc_nd2.c new file mode 100644 index 000000000000..65b0fd6cfaff --- /dev/null +++ b/drivers/mtd/nand/mxc_nd2.c @@ -0,0 +1,1448 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mtd/partitions.h> +#include <asm/mach/flash.h> +#include <asm/io.h> +#include "mxc_nd2.h" + +#define DVR_VER "2.5" + +/* Global address Variables */ +static void __iomem *nfc_axi_base, *nfc_ip_base; + +struct mxc_mtd_s { + struct mtd_info mtd; + struct nand_chip nand; + struct mtd_partition *parts; + struct device *dev; +}; + +static struct mxc_mtd_s *mxc_nand_data; + +/* + * Define delays in microsec for NAND device operations + */ +#define TROP_US_DELAY 2000 + +struct nand_info { + bool bStatusRequest; + u16 colAddr; +}; + +static struct nand_info g_nandfc_info; + +#ifdef CONFIG_MTD_NAND_MXC_SWECC +static int hardware_ecc = 0; +#else +static int hardware_ecc = 1; +#endif + +static u8 num_of_interleave = 1; + +static u8 *data_buf; +static u8 *oob_buf; + +static int g_page_mask; + +static struct clk *nfc_clk; + +/* + * OOB placement block for use with hardware ecc generation + */ +static struct nand_ecclayout nand_hw_eccoob_512 = { + .eccbytes = 9, + .eccpos = {7, 8, 9, 10, 11, 12, 13, 14, 15}, + .oobavail = 4, + .oobfree = {{0, 4}} +}; + +static struct nand_ecclayout nand_hw_eccoob_2k = { + .eccbytes = 9, + .eccpos = {7, 8, 9, 10, 11, 12, 13, 14, 15}, + .oobavail = 4, + .oobfree = {{2, 4}} +}; + +static struct nand_ecclayout nand_hw_eccoob_4k = { + .eccbytes = 9, + .eccpos = {7, 8, 9, 10, 11, 12, 13, 14, 15}, + .oobavail = 4, + .oobfree = {{2, 4}} +}; + +/*! + * @defgroup NAND_MTD NAND Flash MTD Driver for MXC processors + */ + +/*! + * @file mxc_nd2.c + * + * @brief This file contains the hardware specific layer for NAND Flash on + * MXC processor + * + * @ingroup NAND_MTD + */ + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "RedBoot", "cmdlinepart", NULL }; +#endif + +static wait_queue_head_t irq_waitq; + +static irqreturn_t mxc_nfc_irq(int irq, void *dev_id) +{ + /* Disable Interuupt */ + raw_write(raw_read(REG_NFC_INTRRUPT) | NFC_INT_MSK, REG_NFC_INTRRUPT); + wake_up(&irq_waitq); + + return IRQ_HANDLED; +} + +static void nfc_memcpy(void *dest, void *src, int len) +{ + u8 *d = dest; + u8 *s = src; + + while (len > 0) { + if (len >= 4) { + *(u32 *)d = *(u32 *)s; + d += 4; + s += 4; + len -= 4; + } else { + *(u16 *)d = *(u16 *)s; + len -= 2; + break; + } + } + + if (len) + BUG(); +} + +/* + * Functions to transfer data to/from spare erea. + */ +static void +copy_spare(struct mtd_info *mtd, void *pbuf, void *pspare, int len, bool bfrom) +{ + u16 i, j; + u16 m = mtd->oobsize; + u16 n = mtd->writesize >> 9; + u8 *d = (u8 *) pbuf; + u8 *s = (u8 *) pspare; + u16 t = SPARE_LEN; + + m /= num_of_interleave; + n /= num_of_interleave; + + j = (m / n >> 1) << 1; + + if (bfrom) { + for (i = 0; i < n - 1; i++) + nfc_memcpy(&d[i * j], &s[i * t], j); + + /* the last section */ + nfc_memcpy(&d[i * j], &s[i * t], len - i * j); + } else { + for (i = 0; i < n - 1; i++) + nfc_memcpy(&s[i * t], &d[i * j], j); + + /* the last section */ + nfc_memcpy(&s[i * t], &d[i * j], len - i * j); + } +} + +/*! + * This function polls the NFC to wait for the basic operation to complete by + * checking the INT bit of config2 register. + * + * @param maxRetries number of retry attempts (separated by 1 us) + * @param useirq True if IRQ should be used rather than polling + */ +static void wait_op_done(int maxRetries, bool useirq) +{ + if (useirq) { + if ((raw_read(REG_NFC_OPS_STAT) & NFC_OPS_STAT) == 0) { + /* Enable Interuupt */ + raw_write(raw_read(REG_NFC_INTRRUPT) & ~NFC_INT_MSK, + REG_NFC_INTRRUPT); + wait_event(irq_waitq, + (raw_read(REG_NFC_OPS_STAT) & NFC_OPS_STAT)); + } + WRITE_NFC_IP_REG((raw_read(REG_NFC_OPS_STAT) & + ~NFC_OPS_STAT), REG_NFC_OPS_STAT); + } else { + while (1) { + maxRetries--; + if (raw_read(REG_NFC_OPS_STAT) & NFC_OPS_STAT) { + WRITE_NFC_IP_REG((raw_read(REG_NFC_OPS_STAT) & + ~NFC_OPS_STAT), + REG_NFC_OPS_STAT); + break; + } + udelay(1); + if (maxRetries <= 0) { + DEBUG(MTD_DEBUG_LEVEL0, "%s(%d): INT not set\n", + __func__, __LINE__); + } + } + } +} + +static inline void send_atomic_cmd(u16 cmd, bool useirq) +{ + /* fill command */ + raw_write(cmd, REG_NFC_FLASH_CMD); + + /* clear status */ + ACK_OPS; + + /* send out command */ + raw_write(NFC_CMD, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, useirq); +} + +static void mxc_do_addr_cycle(struct mtd_info *mtd, int column, int page_addr); +static int mxc_check_ecc_status(struct mtd_info *mtd); + +#ifdef NFC_AUTO_MODE_ENABLE +/*! + * This function handle the interleave related work + * @param mtd mtd info + * @param cmd command + */ +static void auto_cmd_interleave(struct mtd_info *mtd, u16 cmd) +{ + u32 i, page_addr, ncs; + u32 j = num_of_interleave; + struct nand_chip *this = mtd->priv; + u32 addr_low = raw_read(NFC_FLASH_ADDR0); + u32 addr_high = raw_read(NFC_FLASH_ADDR8); + u8 *dbuf = data_buf; + u8 *obuf = oob_buf; + u32 dlen = mtd->writesize / j; + u32 olen = mtd->oobsize / j; + + /* adjust the addr value + * since ADD_OP mode is 01 + */ + if (cmd == NAND_CMD_ERASE2) + page_addr = addr_low; + else + page_addr = addr_low >> 16 | addr_high << 16; + + ncs = page_addr >> (this->chip_shift - this->page_shift); + + if (j > 1) { + page_addr *= j; + } else { + page_addr *= this->numchips; + page_addr += ncs; + } + + switch (cmd) { + case NAND_CMD_PAGEPROG: + for (i = 0; i < j; i++) { + /* reset addr cycle */ + mxc_do_addr_cycle(mtd, 0, page_addr++); + + /* data transfer */ + memcpy(MAIN_AREA0, dbuf, dlen); + copy_spare(mtd, obuf, SPARE_AREA0, olen, false); + + /* update the value */ + dbuf += dlen; + obuf += olen; + + NFC_SET_RBA(0); + ACK_OPS; + raw_write(NFC_AUTO_PROG, REG_NFC_OPS); + + /* wait auto_prog_done bit set */ + while (!(raw_read(REG_NFC_OPS_STAT) & NFC_OP_DONE)) ; + } + + wait_op_done(TROP_US_DELAY, false); + while (!(raw_read(REG_NFC_OPS_STAT) & NFC_RB)) ; + + break; + case NAND_CMD_READSTART: + for (i = 0; i < j; i++) { + /* reset addr cycle */ + mxc_do_addr_cycle(mtd, 0, page_addr++); + + NFC_SET_RBA(0); + ACK_OPS; + raw_write(NFC_AUTO_READ, REG_NFC_OPS); + wait_op_done(TROP_US_DELAY, false); + + /* check ecc error */ + mxc_check_ecc_status(mtd); + + /* data transfer */ + memcpy(dbuf, MAIN_AREA0, dlen); + copy_spare(mtd, obuf, SPARE_AREA0, olen, true); + + /* update the value */ + dbuf += dlen; + obuf += olen; + } + break; + case NAND_CMD_ERASE2: + for (i = 0; i < j; i++) { + mxc_do_addr_cycle(mtd, -1, page_addr++); + ACK_OPS; + raw_write(NFC_AUTO_ERASE, REG_NFC_OPS); + wait_op_done(TROP_US_DELAY, true); + } + break; + case NAND_CMD_RESET: + for (i = 0; i < j; i++) { + if (j > 1) + NFC_SET_NFC_ACTIVE_CS(i); + send_atomic_cmd(cmd, false); + } + break; + default: + break; + } +} +#endif + +static void send_addr(u16 addr, bool useirq); + +/*! + * This function issues the specified command to the NAND device and + * waits for completion. + * + * @param cmd command for NAND Flash + * @param useirq True if IRQ should be used rather than polling + */ +static void send_cmd(struct mtd_info *mtd, u16 cmd, bool useirq) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_cmd(0x%x, %d)\n", cmd, useirq); + +#ifdef NFC_AUTO_MODE_ENABLE + switch (cmd) { + case NAND_CMD_READ0: + case NAND_CMD_READOOB: + raw_write(NAND_CMD_READ0, REG_NFC_FLASH_CMD); + break; + case NAND_CMD_SEQIN: + case NAND_CMD_ERASE1: + raw_write(cmd, REG_NFC_FLASH_CMD); + break; + case NAND_CMD_PAGEPROG: + case NAND_CMD_ERASE2: + case NAND_CMD_READSTART: + raw_write(raw_read(REG_NFC_FLASH_CMD) | cmd << NFC_CMD_1_SHIFT, + REG_NFC_FLASH_CMD); + auto_cmd_interleave(mtd, cmd); + break; + case NAND_CMD_READID: + send_atomic_cmd(cmd, useirq); + send_addr(0, false); + break; + case NAND_CMD_RESET: + auto_cmd_interleave(mtd, cmd); + case NAND_CMD_STATUS: + break; + default: + break; + } +#else + send_atomic_cmd(cmd, useirq); +#endif +} + +/*! + * This function sends an address (or partial address) to the + * NAND device. The address is used to select the source/destination for + * a NAND command. + * + * @param addr address to be written to NFC. + * @param useirq True if IRQ should be used rather than polling + */ +static void send_addr(u16 addr, bool useirq) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_addr(0x%x %d)\n", addr, useirq); + + /* fill address */ + raw_write((addr << NFC_FLASH_ADDR_SHIFT), REG_NFC_FLASH_ADDR); + + /* clear status */ + ACK_OPS; + + /* send out address */ + raw_write(NFC_ADDR, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, useirq); +} + +/*! + * This function requests the NFC to initate the transfer + * of data currently in the NFC RAM buffer to the NAND device. + * + * @param buf_id Specify Internal RAM Buffer number + */ +static void send_prog_page(u8 buf_id) +{ +#ifndef NFC_AUTO_MODE_ENABLE + DEBUG(MTD_DEBUG_LEVEL3, "%s\n", __FUNCTION__); + + /* set ram buffer id */ + NFC_SET_RBA(buf_id); + + /* clear status */ + ACK_OPS; + + /* transfer data from NFC ram to nand */ + raw_write(NFC_INPUT, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, false); +#endif +} + +/*! + * This function requests the NFC to initated the transfer + * of data from the NAND device into in the NFC ram buffer. + * + * @param buf_id Specify Internal RAM Buffer number + */ +static void send_read_page(u8 buf_id) +{ +#ifndef NFC_AUTO_MODE_ENABLE + DEBUG(MTD_DEBUG_LEVEL3, "%s(%d)\n", __FUNCTION__, buf_id); + + /* set ram buffer id */ + NFC_SET_RBA(buf_id); + + /* clear status */ + ACK_OPS; + + /* transfer data from nand to NFC ram */ + raw_write(NFC_OUTPUT, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, false); +#endif +} + +/*! + * This function requests the NFC to perform a read of the + * NAND device ID. + */ +static void send_read_id(void) +{ + /* Set RBA bits for BUFFER0 */ + NFC_SET_RBA(0); + + /* clear status */ + ACK_OPS; + + /* Read ID into main buffer */ + raw_write(NFC_ID, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, false); + +} + +#ifdef NFC_AUTO_MODE_ENABLE +static inline void read_dev_status(u16 *status) +{ + u32 mask = 0xFF << 16; + + /* clear status */ + ACK_OPS; + + do { + /* send auto read status command */ + raw_write(NFC_AUTO_STATE, REG_NFC_OPS); + if (cpu_is_mx51_rev(CHIP_REV_2_0) == 1) + wait_op_done(TROP_US_DELAY, false); + *status = (raw_read(NFC_CONFIG1) & mask) >> 16; + } while ((*status & NAND_STATUS_READY) == 0); +} +#endif + +/*! + * This function requests the NFC to perform a read of the + * NAND device status and returns the current status. + * + * @return device status + */ +static u16 get_dev_status(void) +{ +#ifdef NFC_AUTO_MODE_ENABLE + int i; + u16 status = 0; + for (i = 0; i < num_of_interleave; i++) { + + /* set ative cs */ + NFC_SET_NFC_ACTIVE_CS(i); + + /* FIXME, NFC Auto erase may have + * problem, have to pollingit until + * the nand get idle, otherwise + * it may get error + */ + read_dev_status(&status); + if (status & NAND_STATUS_FAIL) + break; + } + + return status; +#else + volatile u16 *mainBuf = MAIN_AREA1; + u8 val = 1; + u16 ret; + + /* Set ram buffer id */ + NFC_SET_RBA(val); + + /* clear status */ + ACK_OPS; + + /* Read status into main buffer */ + raw_write(NFC_STATUS, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, false); + + /* Status is placed in first word of main buffer */ + /* get status, then recovery area 1 data */ + ret = *mainBuf; + + return ret; +#endif +} + +static void mxc_nand_enable_hwecc(struct mtd_info *mtd, int mode) +{ + raw_write((raw_read(REG_NFC_ECC_EN) | NFC_ECC_EN), REG_NFC_ECC_EN); + return; +} + +/* + * Function to record the ECC corrected/uncorrected errors resulted + * after a page read. This NFC detects and corrects upto to 4 symbols + * of 9-bits each. + */ +static int mxc_check_ecc_status(struct mtd_info *mtd) +{ + u32 ecc_stat, err; + int no_subpages = 1; + int ret = 0; + u8 ecc_bit_mask, err_limit; + + ecc_bit_mask = (IS_4BIT_ECC ? 0x7 : 0xf); + err_limit = (IS_4BIT_ECC ? 0x4 : 0x8); + + no_subpages = mtd->writesize >> 9; + + no_subpages /= num_of_interleave; + + ecc_stat = GET_NFC_ECC_STATUS(); + do { + err = ecc_stat & ecc_bit_mask; + if (err > err_limit) { + mtd->ecc_stats.failed++; + printk(KERN_WARNING "UnCorrectable RS-ECC Error\n"); + return -1; + } else { + ret += err; + } + ecc_stat >>= 4; + } while (--no_subpages); + + mtd->ecc_stats.corrected += ret; + pr_debug("%d Symbol Correctable RS-ECC Error\n", ret); + + return ret; +} + +/* + * Function to correct the detected errors. This NFC corrects all the errors + * detected. So this function just return 0. + */ +static int mxc_nand_correct_data(struct mtd_info *mtd, u_char * dat, + u_char * read_ecc, u_char * calc_ecc) +{ + return 0; +} + +/* + * Function to calculate the ECC for the data to be stored in the Nand device. + * This NFC has a hardware RS(511,503) ECC engine together with the RS ECC + * CONTROL blocks are responsible for detection and correction of up to + * 8 symbols of 9 bits each in 528 byte page. + * So this function is just return 0. + */ + +static int mxc_nand_calculate_ecc(struct mtd_info *mtd, const u_char * dat, + u_char * ecc_code) +{ + return 0; +} + +/*! + * This function id is used to read the data buffer from the NAND Flash. To + * read the data from NAND Flash first the data output cycle is initiated by + * the NFC, which copies the data to RAMbuffer. This data of length \b len is + * then copied to buffer \b buf. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be read from NAND Flash + * @param len number of bytes to be read + */ +static void mxc_nand_read_buf(struct mtd_info *mtd, u_char * buf, int len) +{ + u16 col = g_nandfc_info.colAddr; + + if (mtd->writesize) { + + int j = mtd->writesize - col; + int n = mtd->oobsize + j; + + n = min(n, len); + + if (j > 0) { + if (n > j) { + memcpy(buf, &data_buf[col], j); + memcpy(buf + j, &oob_buf[0], n - j); + } else { + memcpy(buf, &data_buf[col], n); + } + } else { + col -= mtd->writesize; + memcpy(buf, &oob_buf[col], len); + } + + /* update */ + g_nandfc_info.colAddr += n; + + } else { + /* At flash identify phase, + * mtd->writesize has not been + * set correctly, it should + * be zero.And len will less 2 + */ + memcpy(buf, &data_buf[col], len); + + /* update */ + g_nandfc_info.colAddr += len; + } + +} + +/*! + * This function reads byte from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static uint8_t mxc_nand_read_byte(struct mtd_info *mtd) +{ + uint8_t ret; + + /* Check for status request */ + if (g_nandfc_info.bStatusRequest) { + return (get_dev_status() & 0xFF); + } + + mxc_nand_read_buf(mtd, &ret, 1); + + return ret; +} + +/*! + * This function reads word from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static u16 mxc_nand_read_word(struct mtd_info *mtd) +{ + u16 ret; + + mxc_nand_read_buf(mtd, (uint8_t *) &ret, sizeof(u16)); + + return ret; +} + +/*! + * This function reads byte from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static u_char mxc_nand_read_byte16(struct mtd_info *mtd) +{ + /* Check for status request */ + if (g_nandfc_info.bStatusRequest) { + return (get_dev_status() & 0xFF); + } + + return mxc_nand_read_word(mtd) & 0xFF; +} + +/*! + * This function writes data of length \b len from buffer \b buf to the NAND + * internal RAM buffer's MAIN area 0. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be written to NAND Flash + * @param len number of bytes to be written + */ +static void mxc_nand_write_buf(struct mtd_info *mtd, + const u_char * buf, int len) +{ + u16 col = g_nandfc_info.colAddr; + int j = mtd->writesize - col; + int n = mtd->oobsize + j; + + n = min(n, len); + + if (j > 0) { + if (n > j) { + memcpy(&data_buf[col], buf, j); + memcpy(&oob_buf[0], buf + j, n - j); + } else { + memcpy(&data_buf[col], buf, n); + } + } else { + col -= mtd->writesize; + memcpy(&oob_buf[col], buf, len); + } + + /* update */ + g_nandfc_info.colAddr += n; +} + +/*! + * This function is used by the upper layer to verify the data in NAND Flash + * with the data in the \b buf. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be verified + * @param len length of the data to be verified + * + * @return -EFAULT if error else 0 + * + */ +static int mxc_nand_verify_buf(struct mtd_info *mtd, const u_char * buf, + int len) +{ + u_char *s = data_buf; + + const u_char *p = buf; + + for (; len > 0; len--) { + if (*p++ != *s++) + return -EFAULT; + } + + return 0; +} + +/*! + * This function is used by upper layer for select and deselect of the NAND + * chip + * + * @param mtd MTD structure for the NAND Flash + * @param chip val indicating select or deselect + */ +static void mxc_nand_select_chip(struct mtd_info *mtd, int chip) +{ + + switch (chip) { + case -1: + /* Disable the NFC clock */ + clk_disable(nfc_clk); + break; + case 0 ... 7: + /* Enable the NFC clock */ + clk_enable(nfc_clk); + + NFC_SET_NFC_ACTIVE_CS(chip); + break; + + default: + break; + } +} + +/* + * Function to perform the address cycles. + */ +static void mxc_do_addr_cycle(struct mtd_info *mtd, int column, int page_addr) +{ +#ifdef NFC_AUTO_MODE_ENABLE + + if (page_addr != -1 && column != -1) { + u32 mask = 0xFFFF; + /* the column address */ + raw_write(column & mask, NFC_FLASH_ADDR0); + raw_write((raw_read(NFC_FLASH_ADDR0) | + ((page_addr & mask) << 16)), NFC_FLASH_ADDR0); + /* the row address */ + raw_write(((raw_read(NFC_FLASH_ADDR8) & (mask << 16)) | + ((page_addr & (mask << 16)) >> 16)), + NFC_FLASH_ADDR8); + } else if (page_addr != -1) { + raw_write(page_addr, NFC_FLASH_ADDR0); + raw_write(0, NFC_FLASH_ADDR8); + } + + DEBUG(MTD_DEBUG_LEVEL3, + "AutoMode:the ADDR REGS value is (0x%x, 0x%x)\n", + raw_read(NFC_FLASH_ADDR0), raw_read(NFC_FLASH_ADDR8)); +#else + + u32 page_mask = g_page_mask; + + if (column != -1) { + send_addr(column & 0xFF, false); + if (IS_2K_PAGE_NAND) { + /* another col addr cycle for 2k page */ + send_addr((column >> 8) & 0xF, false); + } else if (IS_4K_PAGE_NAND) { + /* another col addr cycle for 4k page */ + send_addr((column >> 8) & 0x1F, false); + } + } + if (page_addr != -1) { + do { + send_addr((page_addr & 0xff), false); + page_mask >>= 8; + page_addr >>= 8; + } while (page_mask != 0); + } +#endif +} + +/*! + * This function is used by the upper layer to write command to NAND Flash for + * different operations to be carried out on NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * @param command command for NAND Flash + * @param column column offset for the page read + * @param page_addr page to be read from NAND Flash + */ +static void mxc_nand_command(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + bool useirq = false; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_command (cmd = 0x%x, col = 0x%x, page = 0x%x)\n", + command, column, page_addr); + /* + * Reset command state information + */ + g_nandfc_info.bStatusRequest = false; + + /* + * Command pre-processing step + */ + switch (command) { + case NAND_CMD_STATUS: + g_nandfc_info.colAddr = 0; + g_nandfc_info.bStatusRequest = true; + break; + + case NAND_CMD_READ0: + g_nandfc_info.colAddr = column; + break; + + case NAND_CMD_READOOB: + g_nandfc_info.colAddr = column; + command = NAND_CMD_READ0; + break; + + case NAND_CMD_SEQIN: + if (column != 0) { + + /* FIXME: before send SEQIN command for + * partial write,We need read one page out. + * FSL NFC does not support partial write + * It alway send out 512+ecc+512+ecc ... + * for large page nand flash. But for small + * page nand flash, it did support SPARE + * ONLY operation. But to make driver + * simple. We take the same as large page,read + * whole page out and update. As for MLC nand + * NOP(num of operation) = 1. Partial written + * on one programed page is not allowed! We + * can't limit it on the driver, it need the + * upper layer applicaiton take care it + */ + + mxc_nand_command(mtd, NAND_CMD_READ0, 0, page_addr); + } + + g_nandfc_info.colAddr = column; + column = 0; + + break; + + case NAND_CMD_PAGEPROG: +#ifndef NFC_AUTO_MODE_ENABLE + /* FIXME:the NFC interal buffer + * access has some limitation, it + * does not allow byte access. To + * make the code simple and ease use + * not every time check the address + * alignment.Use the temp buffer + * to accomadate the data.since We + * know data_buf will be at leat 4 + * byte alignment, so we can use + * memcpy safely + */ + nfc_memcpy(MAIN_AREA0, data_buf, mtd->writesize); + copy_spare(mtd, oob_buf, SPARE_AREA0, mtd->oobsize, false); +#endif + + if (IS_LARGE_PAGE_NAND) + PROG_PAGE(); + else + send_prog_page(0); + + useirq = true; + + break; + + case NAND_CMD_ERASE1: + break; + case NAND_CMD_ERASE2: + useirq = true; + + break; + } + + /* + * Write out the command to the device. + */ + send_cmd(mtd, command, useirq); + + mxc_do_addr_cycle(mtd, column, page_addr); + + /* + * Command post-processing step + */ + switch (command) { + + case NAND_CMD_READOOB: + case NAND_CMD_READ0: + if (IS_LARGE_PAGE_NAND) { + /* send read confirm command */ + send_cmd(mtd, NAND_CMD_READSTART, false); + /* read for each AREA */ + READ_PAGE(); + } else { + send_read_page(0); + } + +#ifndef NFC_AUTO_MODE_ENABLE + /* FIXME, the NFC interal buffer + * access has some limitation, it + * does not allow byte access. To + * make the code simple and ease use + * not every time check the address + * alignment.Use the temp buffer + * to accomadate the data.since We + * know data_buf will be at leat 4 + * byte alignment, so we can use + * memcpy safely + */ + nfc_memcpy(data_buf, MAIN_AREA0, mtd->writesize); + copy_spare(mtd, oob_buf, SPARE_AREA0, mtd->oobsize, true); +#endif + + break; + + case NAND_CMD_READID: + send_read_id(); + g_nandfc_info.colAddr = column; + nfc_memcpy(data_buf, MAIN_AREA0, 2048); + + break; + } +} + +static int mxc_nand_read_oob(struct mtd_info *mtd, + struct nand_chip *chip, int page, int sndcmd) +{ + if (sndcmd) { + + chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page); + sndcmd = 0; + } + + memcpy(chip->oob_poi, oob_buf, mtd->oobsize); + + return sndcmd; +} + +static int mxc_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t * buf) +{ + +#ifndef NFC_AUTO_MODE_ENABLE + mxc_check_ecc_status(mtd); +#endif + + memcpy(buf, data_buf, mtd->writesize); + memcpy(chip->oob_poi, oob_buf, mtd->oobsize); + + return 0; +} + +static void mxc_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t * buf) +{ + memcpy(data_buf, buf, mtd->writesize); + memcpy(oob_buf, chip->oob_poi, mtd->oobsize); + +} + +/* Define some generic bad / good block scan pattern which are used + * while scanning a device for factory marked good / bad blocks. */ +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; + +static struct nand_bbt_descr smallpage_memorybased = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 5, + .len = 1, + .pattern = scan_ff_pattern +}; + +static struct nand_bbt_descr largepage_memorybased = { + .options = 0, + .offs = 0, + .len = 2, + .pattern = scan_ff_pattern +}; + +/* Generic flash bbt decriptors +*/ +static uint8_t bbt_pattern[] = { 'B', 'b', 't', '0' }; +static uint8_t mirror_pattern[] = { '1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = mirror_pattern +}; + +static int mxc_nand_scan_bbt(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + + g_page_mask = this->pagemask; + + /* limit to 2G size due to Kernel + * larger 4G space support,need fix + * it later + */ + if (mtd->size == 0) { + mtd->size = 1 << 31; + this->numchips = 1; + this->chipsize = mtd->size; + } + + if (IS_2K_PAGE_NAND) { + NFC_SET_NFMS(1 << NFMS_NF_PG_SZ); + this->ecc.layout = &nand_hw_eccoob_2k; + } else if (IS_4K_PAGE_NAND) { + NFC_SET_NFMS(1 << NFMS_NF_PG_SZ); + this->ecc.layout = &nand_hw_eccoob_4k; + } else { + this->ecc.layout = &nand_hw_eccoob_512; + } + + /* propagate ecc.layout to mtd_info */ + mtd->ecclayout = this->ecc.layout; + + /* jffs2 not write oob */ + mtd->flags &= ~MTD_OOB_WRITEABLE; + + /* use flash based bbt */ + this->bbt_td = &bbt_main_descr; + this->bbt_md = &bbt_mirror_descr; + + /* update flash based bbt */ + this->options |= NAND_USE_FLASH_BBT; + + if (!this->badblock_pattern) { + this->badblock_pattern = (mtd->writesize > 512) ? + &largepage_memorybased : &smallpage_memorybased; + } + + /* Build bad block table */ + return nand_scan_bbt(mtd, this->badblock_pattern); +} + +static void mxc_nfc_init(void) +{ + /* Disable interrupt */ + raw_write((raw_read(REG_NFC_INTRRUPT) | NFC_INT_MSK), REG_NFC_INTRRUPT); + + /* disable spare enable */ + raw_write(raw_read(REG_NFC_SP_EN) & ~NFC_SP_EN, REG_NFC_SP_EN); + + /* Unlock the internal RAM Buffer */ + raw_write(NFC_SET_BLS(NFC_BLS_UNLCOKED), REG_NFC_BLS); + + /* Blocks to be unlocked */ + UNLOCK_ADDR(0x0, 0xFFFF); + + /* Unlock Block Command for given address range */ + raw_write(NFC_SET_WPC(NFC_WPC_UNLOCK), REG_NFC_WPC); + + /* Enable symetric mode by default except mx37TO1.0 */ + if (!(cpu_is_mx37_rev(CHIP_REV_1_0) == 1)) + raw_write(raw_read(REG_NFC_ONE_CYCLE) | + NFC_ONE_CYCLE, REG_NFC_ONE_CYCLE); +} + +static int mxc_alloc_buf(void) +{ + int err = 0; + + data_buf = kzalloc(NAND_MAX_PAGESIZE, GFP_KERNEL); + if (!data_buf) { + printk(KERN_ERR "%s: failed to allocate data_buf\n", __func__); + err = -ENOMEM; + goto out; + } + oob_buf = kzalloc(NAND_MAX_OOBSIZE, GFP_KERNEL); + if (!oob_buf) { + printk(KERN_ERR "%s: failed to allocate oob_buf\n", __func__); + err = -ENOMEM; + goto out; + } + + out: + return err; +} + +static void mxc_free_buf(void) +{ + kfree(data_buf); + kfree(oob_buf); +} + +/*! + * This function is called during the driver binding process. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and + * remove functions + * + * @return The function always returns 0. + */ +static int __init mxcnd_probe(struct platform_device *pdev) +{ + struct nand_chip *this; + struct mtd_info *mtd; + struct flash_platform_data *flash = pdev->dev.platform_data; + int nr_parts = 0, err = 0; + + nfc_axi_base = IO_ADDRESS(NFC_AXI_BASE_ADDR); + nfc_ip_base = IO_ADDRESS(NFC_BASE_ADDR); + + /* init the nfc */ + mxc_nfc_init(); + + /* init data buf */ + if (mxc_alloc_buf()) + goto out; + + /* Allocate memory for MTD device structure and private data */ + mxc_nand_data = kzalloc(sizeof(struct mxc_mtd_s), GFP_KERNEL); + if (!mxc_nand_data) { + printk(KERN_ERR "%s: failed to allocate mtd_info\n", + __FUNCTION__); + err = -ENOMEM; + goto out; + } + + memset((char *)&g_nandfc_info, 0, sizeof(g_nandfc_info)); + + mxc_nand_data->dev = &pdev->dev; + /* structures must be linked */ + this = &mxc_nand_data->nand; + mtd = &mxc_nand_data->mtd; + mtd->priv = this; + mtd->owner = THIS_MODULE; + + this->priv = mxc_nand_data; + this->cmdfunc = mxc_nand_command; + this->select_chip = mxc_nand_select_chip; + this->read_byte = mxc_nand_read_byte; + this->read_word = mxc_nand_read_word; + this->write_buf = mxc_nand_write_buf; + this->read_buf = mxc_nand_read_buf; + this->verify_buf = mxc_nand_verify_buf; + this->scan_bbt = mxc_nand_scan_bbt; + + /* NAND bus width determines access funtions used by upper layer */ + if (flash->width == 2) { + this->read_byte = mxc_nand_read_byte16; + this->options |= NAND_BUSWIDTH_16; + NFC_SET_NFMS(1 << NFMS_NF_DWIDTH); + } else { + NFC_SET_NFMS(0); + } + + nfc_clk = clk_get(&pdev->dev, "nfc_clk"); + clk_enable(nfc_clk); + + init_waitqueue_head(&irq_waitq); + err = request_irq(MXC_INT_NANDFC, mxc_nfc_irq, 0, "mxc_nd", NULL); + if (err) { + goto out_1; + } + + if (hardware_ecc) { + this->ecc.read_page = mxc_nand_read_page; + this->ecc.write_page = mxc_nand_write_page; + this->ecc.read_oob = mxc_nand_read_oob; + this->ecc.layout = &nand_hw_eccoob_512; + this->ecc.calculate = mxc_nand_calculate_ecc; + this->ecc.hwctl = mxc_nand_enable_hwecc; + this->ecc.correct = mxc_nand_correct_data; + this->ecc.mode = NAND_ECC_HW; + this->ecc.size = 512; + this->ecc.bytes = 9; + raw_write((raw_read(REG_NFC_ECC_EN) | NFC_ECC_EN), + REG_NFC_ECC_EN); + } else { + this->ecc.mode = NAND_ECC_SOFT; + raw_write((raw_read(REG_NFC_ECC_EN) & ~NFC_ECC_EN), + REG_NFC_ECC_EN); + } + + /* config the gpio */ + if (flash->init) + flash->init(); + + /* Reset NAND */ + this->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + + /* Scan to find existence of the device */ + if (nand_scan(mtd, NFC_GET_MAXCHIP_SP())) { + DEBUG(MTD_DEBUG_LEVEL0, + "MXC_ND2: Unable to find any NAND device.\n"); + err = -ENXIO; + goto out_1; + } + + /* Register the partitions */ +#ifdef CONFIG_MTD_PARTITIONS + nr_parts = + parse_mtd_partitions(mtd, part_probes, &mxc_nand_data->parts, 0); + if (nr_parts > 0) + add_mtd_partitions(mtd, mxc_nand_data->parts, nr_parts); + else if (flash->parts) + add_mtd_partitions(mtd, flash->parts, flash->nr_parts); + else +#endif + { + pr_info("Registering %s as whole device\n", mtd->name); + add_mtd_device(mtd); + } + + platform_set_drvdata(pdev, mtd); + + return 0; + + out_1: + kfree(mxc_nand_data); + out: + return err; + +} + + /*! + * Dissociates the driver from the device. + * + * @param pdev the device structure used to give information on which + * + * @return The function always returns 0. + */ + +static int __exit mxcnd_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + struct flash_platform_data *flash = pdev->dev.platform_data; + + if (flash->exit) + flash->exit(); + + mxc_free_buf(); + + clk_disable(nfc_clk); + clk_put(nfc_clk); + platform_set_drvdata(pdev, NULL); + + if (mxc_nand_data) { + nand_release(mtd); + free_irq(MXC_INT_NANDFC, NULL); + kfree(mxc_nand_data); + } + + return 0; +} + +#ifdef CONFIG_PM +/*! + * This function is called to put the NAND in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device information structure + * + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure + */ + +static int mxcnd_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mtd_info *info = platform_get_drvdata(pdev); + int ret = 0; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND2 : NAND suspend\n"); + if (info) + ret = info->suspend(info); + + /* Disable the NFC clock */ + clk_disable(nfc_clk); + + /* Disable the NFC clock */ + clk_disable(nfc_clk); + + return ret; +} + +/*! + * This function is called to bring the NAND back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device information structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxcnd_resume(struct platform_device *pdev) +{ + struct mtd_info *info = platform_get_drvdata(pdev); + int ret = 0; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND2 : NAND resume\n"); + /* Enable the NFC clock */ + clk_enable(nfc_clk); + + if (info) { + info->resume(info); + } + + return ret; +} + +#else +#define mxcnd_suspend NULL +#define mxcnd_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcnd_driver = { + .driver = { + .name = "mxc_nandv2_flash", + }, + .probe = mxcnd_probe, + .remove = __exit_p(mxcnd_remove), + .suspend = mxcnd_suspend, + .resume = mxcnd_resume, +}; + +/*! + * Main initialization routine + * @return 0 if successful; non-zero otherwise + */ +static int __init mxc_nd_init(void) +{ + /* Register the device driver structure. */ + pr_info("MXC MTD nand Driver %s\n", DVR_VER); + if (platform_driver_register(&mxcnd_driver) != 0) { + printk(KERN_ERR "Driver register failed for mxcnd_driver\n"); + return -ENODEV; + } + return 0; +} + +/*! + * Clean up routine + */ +static void __exit mxc_nd_cleanup(void) +{ + /* Unregister the device structure */ + platform_driver_unregister(&mxcnd_driver); +} + +module_init(mxc_nd_init); +module_exit(mxc_nd_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC NAND MTD driver Version 2-5"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/mxc_nd2.h b/drivers/mtd/nand/mxc_nd2.h new file mode 100644 index 000000000000..9cb92100b5a4 --- /dev/null +++ b/drivers/mtd/nand/mxc_nd2.h @@ -0,0 +1,679 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_nd2.h + * + * @brief This file contains the NAND Flash Controller register information. + * + * + * @ingroup NAND_MTD + */ + +#ifndef __MXC_ND2_H__ +#define __MXC_ND2_H__ + +#include <mach/hardware.h> + +#define IS_2K_PAGE_NAND ((mtd->writesize / num_of_interleave) \ + == NAND_PAGESIZE_2KB) +#define IS_4K_PAGE_NAND ((mtd->writesize / num_of_interleave) \ + == NAND_PAGESIZE_4KB) +#define IS_LARGE_PAGE_NAND ((mtd->writesize / num_of_interleave) > 512) + +#define GET_NAND_OOB_SIZE (mtd->oobsize / num_of_interleave) + +#define NAND_PAGESIZE_2KB 2048 +#define NAND_PAGESIZE_4KB 4096 + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3 +/* + * For V3 NFC registers Definition + */ +/* AXI Bus Mapped */ +#define NFC_AXI_BASE_ADDR NFC_BASE_ADDR_AXI + +#if defined(CONFIG_ARCH_MXC_HAS_NFC_V3_1) /* mx37 */ +#define MXC_INT_NANDFC MXC_INT_EMI +#define NFC_FLASH_ADDR_CMD (nfc_axi_base + 0x1E00) +#define NFC_CONFIG1 (nfc_axi_base + 0x1E04) +#define NFC_ECC_STATUS_RESULT (nfc_axi_base + 0x1E08) +#define LAUNCH_NFC (nfc_axi_base + 0x1E0c) +#define NFC_WRPROT (nfc_ip_base + 0x00) +#define NFC_WRPROT_UNLOCK_BLK_ADD0 (nfc_ip_base + 0x04) +#define NFC_CONFIG2 (nfc_ip_base + 0x14) +#define NFC_IPC (nfc_ip_base + 0x18) +#elif defined(CONFIG_ARCH_MXC_HAS_NFC_V3_2) /* mx51 */ +#define MXC_INT_NANDFC MXC_INT_NFC +#define NFC_AUTO_MODE_ENABLE +#define NFC_FLASH_CMD (nfc_axi_base + 0x1E00) +#define NFC_FLASH_ADDR0 (nfc_axi_base + 0x1E04) +#define NFC_FLASH_ADDR8 (nfc_axi_base + 0x1E24) +#define NFC_CONFIG1 (nfc_axi_base + 0x1E34) +#define NFC_ECC_STATUS_RESULT (nfc_axi_base + 0x1E38) +#define NFC_ECC_STATUS_SUM (nfc_axi_base + 0x1E3C) +#define LAUNCH_NFC (nfc_axi_base + 0x1E40) +#define NFC_WRPROT (nfc_ip_base + 0x00) +#define NFC_WRPROT_UNLOCK_BLK_ADD0 (nfc_ip_base + 0x04) +#define NFC_CONFIG2 (nfc_ip_base + 0x24) +#define NFC_CONFIG3 (nfc_ip_base + 0x28) +#define NFC_IPC (nfc_ip_base + 0x2C) +#define NFC_DELAY_LINE (nfc_ip_base + 0x34) +#else /* skye */ +#define NFC_FLASH_ADDR_CMD (nfc_axi_base + 0xE00) +#define NFC_CONFIG1 (nfc_axi_base + 0xE04) +#define NFC_ECC_STATUS_RESULT (nfc_axi_base + 0xE08) +#define LAUNCH_NFC (nfc_axi_base + 0xE0C) +#define NFC_WRPROT (nfc_ip_base + 0x00) +#define NFC_WRPROT_UNLOCK_BLK_ADD0 (nfc_ip_base + 0x04) +#define NFC_CONFIG2 (nfc_ip_base + 0x14) +#define NFC_IPC (nfc_ip_base + 0x18) +#endif +/*! + * Addresses for NFC RAM BUFFER Main area 0 + */ +#define MAIN_AREA0 ((u16 *)(nfc_axi_base + 0x000)) +#define MAIN_AREA1 ((u16 *)(nfc_axi_base + 0x200)) + +/*! + * Addresses for NFC SPARE BUFFER Spare area 0 + */ +#if defined(CONFIG_ARCH_MXC_HAS_NFC_V3_1) || \ + defined(CONFIG_ARCH_MXC_HAS_NFC_V3_2) +#define SPARE_AREA0 ((u16 *)(nfc_axi_base + 0x1000)) +#define SPARE_LEN 64 +#define SPARE_COUNT 8 +#define SPARE_SIZE (SPARE_LEN * SPARE_COUNT) +#else +#define SPARE_AREA0 ((u16 *)(nfc_axi_base + 0x800)) +#define SPARE_LEN 16 +#define SPARE_COUNT 4 +#define SPARE_SIZE (SPARE_LEN * SPARE_COUNT) +#endif + +#if defined(CONFIG_ARCH_MXC_HAS_NFC_V3_1) || \ + defined(CONFIG_ARCH_MXC_HAS_NFC_V3_2) +#define NFC_SPAS_WIDTH 8 +#define NFC_SPAS_SHIFT 16 + +#define IS_4BIT_ECC \ +( \ + cpu_is_mx51_rev(CHIP_REV_2_0) > 0 ? \ + !((raw_read(NFC_CONFIG2) & NFC_ECC_MODE_4) >> 6) : \ + ((raw_read(NFC_CONFIG2) & NFC_ECC_MODE_4) >> 6) \ +) + +#define NFC_SET_SPAS(v) \ + raw_write((((raw_read(NFC_CONFIG2) & \ + NFC_FIELD_RESET(NFC_SPAS_WIDTH, NFC_SPAS_SHIFT)) | ((v) << 16))), \ + NFC_CONFIG2) + +#define NFC_SET_ECC_MODE(v) \ +do { \ + if (cpu_is_mx51_rev(CHIP_REV_2_0) > 0) { \ + if ((v) == NFC_SPAS_218 || (v) == NFC_SPAS_112) \ + raw_write(((raw_read(NFC_CONFIG2) & \ + NFC_ECC_MODE_MASK) | \ + NFC_ECC_MODE_4), NFC_CONFIG2); \ + else \ + raw_write(((raw_read(NFC_CONFIG2) & \ + NFC_ECC_MODE_MASK) & \ + NFC_ECC_MODE_8), NFC_CONFIG2); \ + } else { \ + if ((v) == NFC_SPAS_218 || (v) == NFC_SPAS_112) \ + raw_write(((raw_read(NFC_CONFIG2) & \ + NFC_ECC_MODE_MASK) & \ + NFC_ECC_MODE_8), NFC_CONFIG2); \ + else \ + raw_write(((raw_read(NFC_CONFIG2) & \ + NFC_ECC_MODE_MASK) | \ + NFC_ECC_MODE_4), NFC_CONFIG2); \ + } \ +} while (0) + +#define WRITE_NFC_IP_REG(val,reg) \ + do { \ + raw_write(NFC_IPC_CREQ, NFC_IPC); \ + while (!((raw_read(NFC_IPC) & NFC_IPC_ACK)>>1));\ + raw_write(val, reg); \ + raw_write(0, NFC_IPC); \ + } while(0) + +#else +#define IS_4BIT_ECC 1 +#define NFC_SET_SPAS(v) +#define NFC_SET_ECC_MODE(v) +#define NFC_SET_NFMS(v) (NFMS |= (v)) + +#define WRITE_NFC_IP_REG(val,reg) \ + raw_write((raw_read(REG_NFC_OPS_STAT) & ~NFC_OPS_STAT), \ + REG_NFC_OPS_STAT) +#endif + +#define GET_NFC_ECC_STATUS() raw_read(REG_NFC_ECC_STATUS_RESULT); + +/*! + * Set 1 to specific operation bit, rest to 0 in LAUNCH_NFC Register for + * Specific operation + */ +#define NFC_CMD 0x1 +#define NFC_ADDR 0x2 +#define NFC_INPUT 0x4 +#define NFC_OUTPUT 0x8 +#define NFC_ID 0x10 +#define NFC_STATUS 0x20 + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3_2 /* mx51 */ +#define NFC_AUTO_PROG 0x40 +#define NFC_AUTO_READ 0x80 +#define NFC_AUTO_ERASE 0x200 +#define NFC_COPY_BACK_0 0x400 +#define NFC_COPY_BACK_1 0x800 +#define NFC_AUTO_STATE 0x1000 +#endif + +/* Bit Definitions for NFC_IPC*/ +#define NFC_OPS_STAT (1 << 31) + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3_2 /* mx51 */ +#define NFC_OP_DONE (1 << 30) +#define NFC_RB (1 << 28) +#define NFC_PS_WIDTH 2 +#define NFC_PS_SHIFT 0 +#define NFC_PS_512 0 +#define NFC_PS_2K 1 +#define NFC_PS_4K 2 +#else +#define NFC_RB (1 << 29) +#endif + +#define NFC_ONE_CYCLE (1 << 2) + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3_2 /* mx51 */ +#define NFC_INT_MSK (1 << 15) +#define NFC_AUTO_PROG_DONE_MSK (1 << 14) +#define NFC_NUM_ADDR_PHASE1_WIDTH 2 +#define NFC_NUM_ADDR_PHASE1_SHIFT 12 + +#define NFC_NUM_ADDR_PHASE0_WIDTH 1 +#define NFC_NUM_ADDR_PHASE0_SHIFT 5 + +#define NFC_ONE_LESS_PHASE1 0 +#define NFC_TWO_LESS_PHASE1 1 + +#define NFC_FLASH_ADDR_SHIFT 0 +#else +#define NFC_INT_MSK (1 << 4) +#define NFC_BIG (1 << 5) +#define NFC_FLASH_ADDR_SHIFT 16 +#endif + +#define NFC_UNLOCK_END_ADDR_SHIFT 16 + +/* Bit definition for NFC_CONFIGRATION_1 */ +#define NFC_SP_EN (1 << 0) +#define NFC_CE (1 << 1) +#define NFC_RST (1 << 2) +#define NFC_ECC_EN (1 << 3) + +#define NFC_FIELD_RESET(width, shift) ~(((1 << (width)) - 1) << (shift)) + +#define NFC_RBA_SHIFT 4 + +#if defined(CONFIG_ARCH_MXC_HAS_NFC_V3_1) || \ + defined(CONFIG_ARCH_MXC_HAS_NFC_V3_2) /* mx51 */ +#define NFC_RBA_WIDTH 3 +#else +#define NFC_RBA_WIDTH 2 +#endif + +#if defined(CONFIG_ARCH_MXC_HAS_NFC_V3_2) /* mx51 */ +#define NFC_ITERATION_SHIFT 8 +#define NFC_ITERATION_WIDTH 4 +#define NFC_ACTIVE_CS_SHIFT 12 +#define NFC_ACTIVE_CS_WIDTH 3 +/* bit definition for CONFIGRATION3 */ +#define NFC_NO_SDMA (1 << 20) +#define NFC_FMP_SHIFT 16 +#define NFC_FMP_WIDTH 4 +#define NFC_RBB_MODE (1 << 15) +#define NFC_NUM_OF_DEVICES_SHIFT 12 +#define NFC_NUM_OF_DEVICES_WIDTH 4 +#define NFC_DMA_MODE_SHIFT 11 +#define NFC_DMA_MODE_WIDTH 1 +#define NFC_SBB_SHIFT 8 +#define NFC_SBB_WIDTH 3 +#define NFC_BIG (1 << 7) +#define NFC_SB2R_SHIFT 4 +#define NFC_SB2R_WIDTH 3 +#define NFC_FW_SHIFT 3 +#define NFC_FW_WIDTH 1 +#define NFC_TOO (1 << 2) +#define NFC_ADD_OP_SHIFT 0 +#define NFC_ADD_OP_WIDTH 2 +#define NFC_FW_8 1 +#define NFC_FW_16 0 +#define NFC_ST_CMD_SHITF 24 +#define NFC_ST_CMD_WIDTH 8 +#endif + +#define NFC_PPB_32 (0 << 7) +#define NFC_PPB_64 (1 << 7) +#define NFC_PPB_128 (2 << 7) +#define NFC_PPB_256 (3 << 7) +#define NFC_PPB_RESET ~(3 << 7) + +#define NFC_BLS_LOCKED (0 << 16) +#define NFC_BLS_LOCKED_DEFAULT (1 << 16) +#define NFC_BLS_UNLCOKED (2 << 16) +#define NFC_BLS_RESET ~(3 << 16) +#define NFC_WPC_LOCK_TIGHT 1 +#define NFC_WPC_LOCK (1 << 1) +#define NFC_WPC_UNLOCK (1 << 2) +#define NFC_WPC_RESET ~(7) +#if defined(CONFIG_ARCH_MXC_HAS_NFC_V3_1) || \ + defined(CONFIG_ARCH_MXC_HAS_NFC_V3_2) +#define NFC_ECC_MODE_4 (1 << 6) +#define NFC_ECC_MODE_8 ~(1 << 6) +#define NFC_ECC_MODE_MASK ~(1 << 6) +#define NFC_SPAS_16 8 +#define NFC_SPAS_64 32 +#define NFC_SPAS_128 64 +#define NFC_SPAS_112 56 +#define NFC_SPAS_218 109 +#define NFC_IPC_CREQ (1 << 0) +#define NFC_IPC_ACK (1 << 1) +#endif + +#define REG_NFC_OPS_STAT NFC_IPC +#define REG_NFC_INTRRUPT NFC_CONFIG2 +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3_2 +#define REG_NFC_FLASH_ADDR NFC_FLASH_ADDR0 +#define REG_NFC_FLASH_CMD NFC_FLASH_CMD +#else +#define REG_NFC_FLASH_ADDR NFC_FLASH_ADDR_CMD +#define REG_NFC_FLASH_CMD NFC_FLASH_ADDR_CMD +#endif +#define REG_NFC_OPS LAUNCH_NFC +#define REG_NFC_SET_RBA NFC_CONFIG1 +#define REG_NFC_RB NFC_IPC +#define REG_NFC_ECC_EN NFC_CONFIG2 +#define REG_NFC_ECC_STATUS_RESULT NFC_ECC_STATUS_RESULT +#define REG_NFC_CE NFC_CONFIG1 +#define REG_NFC_RST NFC_CONFIG1 +#define REG_NFC_PPB NFC_CONFIG2 +#define REG_NFC_SP_EN NFC_CONFIG1 +#define REG_NFC_BLS NFC_WRPROT +#define REG_UNLOCK_BLK_ADD0 NFC_WRPROT_UNLOCK_BLK_ADD0 +#define REG_UNLOCK_BLK_ADD1 NFC_WRPROT_UNLOCK_BLK_ADD1 +#define REG_UNLOCK_BLK_ADD2 NFC_WRPROT_UNLOCK_BLK_ADD2 +#define REG_UNLOCK_BLK_ADD3 NFC_WRPROT_UNLOCK_BLK_ADD3 +#define REG_NFC_WPC NFC_WRPROT +#define REG_NFC_ONE_CYCLE NFC_CONFIG2 + +/* NFC V3 Specific MACRO functions definitions */ +#define raw_write(v,a) __raw_writel(v,a) +#define raw_read(a) __raw_readl(a) + +/* Explcit ack ops status (if any), before issue of any command */ +#define ACK_OPS \ + raw_write((raw_read(REG_NFC_OPS_STAT) & ~NFC_OPS_STAT), \ + REG_NFC_OPS_STAT); + +/* Set RBA buffer id*/ +#define NFC_SET_RBA(val) \ + raw_write((raw_read(REG_NFC_SET_RBA) & \ + (NFC_FIELD_RESET(NFC_RBA_WIDTH, NFC_RBA_SHIFT))) | \ + ((val) << NFC_RBA_SHIFT), REG_NFC_SET_RBA); + +#define NFC_SET_PS(val) \ + raw_write((raw_read(NFC_CONFIG2) & \ + (NFC_FIELD_RESET(NFC_PS_WIDTH, NFC_PS_SHIFT))) | \ + ((val) << NFC_PS_SHIFT), NFC_CONFIG2); + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3_2 +#define UNLOCK_ADDR(start_addr,end_addr) \ +{ \ + int i = 0; \ + for (; i < NAND_MAX_CHIPS; i++) \ + raw_write(start_addr | \ + (end_addr << NFC_UNLOCK_END_ADDR_SHIFT), \ + REG_UNLOCK_BLK_ADD0 + (i << 2)); \ +} +#define NFC_SET_NFC_ACTIVE_CS(val) \ + raw_write((raw_read(NFC_CONFIG1) & \ + (NFC_FIELD_RESET(NFC_ACTIVE_CS_WIDTH, NFC_ACTIVE_CS_SHIFT))) | \ + ((val) << NFC_ACTIVE_CS_SHIFT), NFC_CONFIG1); + +#define NFC_GET_MAXCHIP_SP() 8 + +#else +#define UNLOCK_ADDR(start_addr,end_addr) \ + raw_write(start_addr | \ + (end_addr << NFC_UNLOCK_END_ADDR_SHIFT), REG_UNLOCK_BLK_ADD0); + +#define NFC_SET_NFC_ACTIVE_CS(val) +#define NFC_GET_MAXCHIP_SP() 1 +#endif + +#define NFC_SET_BLS(val) ((raw_read(REG_NFC_BLS) & NFC_BLS_RESET) | val ) +#define NFC_SET_WPC(val) ((raw_read(REG_NFC_WPC) & NFC_WPC_RESET) | val ) +#define CHECK_NFC_RB raw_read(REG_NFC_RB) & NFC_RB + +#if defined(CONFIG_ARCH_MXC_HAS_NFC_V3_2) +#define NFC_SET_NFC_NUM_ADDR_PHASE1(val) \ + raw_write((raw_read(NFC_CONFIG2) & \ + (NFC_FIELD_RESET(NFC_NUM_ADDR_PHASE1_WIDTH, \ + NFC_NUM_ADDR_PHASE1_SHIFT))) | \ + ((val) << NFC_NUM_ADDR_PHASE1_SHIFT), NFC_CONFIG2); + +#define NFC_SET_NFC_NUM_ADDR_PHASE0(val) \ + raw_write((raw_read(NFC_CONFIG2) & \ + (NFC_FIELD_RESET(NFC_NUM_ADDR_PHASE0_WIDTH, \ + NFC_NUM_ADDR_PHASE0_SHIFT))) | \ + ((val) << NFC_NUM_ADDR_PHASE0_SHIFT), NFC_CONFIG2); + +#define NFC_SET_NFC_ITERATION(val) \ + raw_write((raw_read(NFC_CONFIG1) & \ + (NFC_FIELD_RESET(NFC_ITERATION_WIDTH, NFC_ITERATION_SHIFT))) | \ + ((val) << NFC_ITERATION_SHIFT), NFC_CONFIG1); + +#define NFC_SET_FW(val) \ + raw_write((raw_read(NFC_CONFIG3) & \ + (NFC_FIELD_RESET(NFC_FW_WIDTH, NFC_FW_SHIFT))) | \ + ((val) << NFC_FW_SHIFT), NFC_CONFIG3); + +#define NFC_SET_NUM_OF_DEVICE(val) \ + raw_write((raw_read(NFC_CONFIG3) & \ + (NFC_FIELD_RESET(NFC_NUM_OF_DEVICES_WIDTH, \ + NFC_NUM_OF_DEVICES_SHIFT))) | \ + ((val) << NFC_NUM_OF_DEVICES_SHIFT), NFC_CONFIG3); + +#define NFC_SET_ADD_OP_MODE(val) \ + raw_write((raw_read(NFC_CONFIG3) & \ + (NFC_FIELD_RESET(NFC_ADD_OP_WIDTH, NFC_ADD_OP_SHIFT))) | \ + ((val) << NFC_ADD_OP_SHIFT), NFC_CONFIG3); + +#define NFC_SET_ADD_CS_MODE(val) \ +{ \ + NFC_SET_ADD_OP_MODE(val); \ + NFC_SET_NUM_OF_DEVICE(this->numchips - 1); \ +} + +#define NFC_SET_ST_CMD(val) \ + raw_write((raw_read(NFC_CONFIG2) & \ + (NFC_FIELD_RESET(NFC_ST_CMD_WIDTH, \ + NFC_ST_CMD_SHITF))) | \ + ((val) << NFC_ST_CMD_SHITF), NFC_CONFIG2); + +#define NFMS_NF_DWIDTH 0 +#define NFMS_NF_PG_SZ 1 +#define NFC_CMD_1_SHIFT 8 + +#define NUM_OF_ADDR_CYCLE (fls(g_page_mask) >> 3) +#define SET_NFC_DELAY_LINE(val) raw_write((val), NFC_DELAY_LINE) + +/*should set the fw,ps,spas,ppb*/ +#define NFC_SET_NFMS(v) \ +do { \ + if (!(v)) \ + NFC_SET_FW(NFC_FW_8); \ + if (((v) & (1 << NFMS_NF_DWIDTH))) \ + NFC_SET_FW(NFC_FW_16); \ + if (((v) & (1 << NFMS_NF_PG_SZ))) { \ + if (IS_2K_PAGE_NAND) { \ + NFC_SET_PS(NFC_PS_2K); \ + NFC_SET_NFC_NUM_ADDR_PHASE1(NUM_OF_ADDR_CYCLE); \ + NFC_SET_NFC_NUM_ADDR_PHASE0(NFC_TWO_LESS_PHASE1); \ + } else if (IS_4K_PAGE_NAND) { \ + NFC_SET_PS(NFC_PS_4K); \ + NFC_SET_NFC_NUM_ADDR_PHASE1(NUM_OF_ADDR_CYCLE); \ + NFC_SET_NFC_NUM_ADDR_PHASE0(NFC_TWO_LESS_PHASE1); \ + } else { \ + NFC_SET_PS(NFC_PS_512); \ + NFC_SET_NFC_NUM_ADDR_PHASE1(NUM_OF_ADDR_CYCLE - 1); \ + NFC_SET_NFC_NUM_ADDR_PHASE0(NFC_ONE_LESS_PHASE1); \ + } \ + NFC_SET_ADD_CS_MODE(1); \ + NFC_SET_SPAS(GET_NAND_OOB_SIZE >> 1); \ + NFC_SET_ECC_MODE(GET_NAND_OOB_SIZE >> 1); \ + NFC_SET_ST_CMD(0x70); \ + raw_write(raw_read(NFC_CONFIG3) | 1 << 20, NFC_CONFIG3); \ + SET_NFC_DELAY_LINE(0); \ + } \ +} while (0) +#endif + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3_1 +#define NFC_SET_NFMS(v) +#endif + +#define READ_PAGE() send_read_page(0) +#define PROG_PAGE() send_prog_page(0) + +#elif CONFIG_ARCH_MXC_HAS_NFC_V2 + +/* + * For V1/V2 NFC registers Definition + */ + +#define NFC_AXI_BASE_ADDR 0x00 +/* + * Addresses for NFC registers + */ +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V2_1 +#define NFC_REG_BASE (nfc_ip_base + 0x1000) +#else +#define NFC_REG_BASE nfc_ip_base +#endif +#define NFC_BUF_SIZE (NFC_REG_BASE + 0xE00) +#define NFC_BUF_ADDR (NFC_REG_BASE + 0xE04) +#define NFC_FLASH_ADDR (NFC_REG_BASE + 0xE06) +#define NFC_FLASH_CMD (NFC_REG_BASE + 0xE08) +#define NFC_CONFIG (NFC_REG_BASE + 0xE0A) +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V2_1 +#define NFC_ECC_STATUS_RESULT (NFC_REG_BASE + 0xE0C) +#define NFC_ECC_STATUS_RESULT_1 (NFC_REG_BASE + 0xE0C) +#define NFC_ECC_STATUS_RESULT_2 (NFC_REG_BASE + 0xE0E) +#define NFC_SPAS (NFC_REG_BASE + 0xE10) +#else +#define NFC_ECC_STATUS_RESULT (NFC_REG_BASE + 0xE0C) +#define NFC_RSLTMAIN_AREA (NFC_REG_BASE + 0xE0E) +#define NFC_RSLTSPARE_AREA (NFC_REG_BASE + 0xE10) +#endif +#define NFC_WRPROT (NFC_REG_BASE + 0xE12) +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V2_1 +#define NFC_UNLOCKSTART_BLKADDR (NFC_REG_BASE + 0xE20) +#define NFC_UNLOCKEND_BLKADDR (NFC_REG_BASE + 0xE22) +#define NFC_UNLOCKSTART_BLKADDR1 (NFC_REG_BASE + 0xE24) +#define NFC_UNLOCKEND_BLKADDR1 (NFC_REG_BASE + 0xE26) +#define NFC_UNLOCKSTART_BLKADDR2 (NFC_REG_BASE + 0xE28) +#define NFC_UNLOCKEND_BLKADDR2 (NFC_REG_BASE + 0xE2A) +#define NFC_UNLOCKSTART_BLKADDR3 (NFC_REG_BASE + 0xE2C) +#define NFC_UNLOCKEND_BLKADDR3 (NFC_REG_BASE + 0xE2E) +#else +#define NFC_UNLOCKSTART_BLKADDR (NFC_REG_BASE + 0xE14) +#define NFC_UNLOCKEND_BLKADDR (NFC_REG_BASE + 0xE16) +#endif +#define NFC_NF_WRPRST (NFC_REG_BASE + 0xE18) +#define NFC_CONFIG1 (NFC_REG_BASE + 0xE1A) +#define NFC_CONFIG2 (NFC_REG_BASE + 0xE1C) + +/*! + * Addresses for NFC RAM BUFFER Main area 0 + */ +#define MAIN_AREA0 (u16 *)(nfc_ip_base + 0x000) +#define MAIN_AREA1 (u16 *)(nfc_ip_base + 0x200) + +/*! + * Addresses for NFC SPARE BUFFER Spare area 0 + */ +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V2_1 +#define SPARE_AREA0 (u16 *)(nfc_ip_base + 0x1000) +#define SPARE_LEN 64 +#define SPARE_COUNT 8 +#else +#define SPARE_AREA0 (u16 *)(nfc_ip_base + 0x800) +#define SPARE_LEN 16 +#define SPARE_COUNT 4 +#endif +#define SPARE_SIZE (SPARE_LEN * SPARE_COUNT) + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V2_1 +#define REG_NFC_ECC_MODE NFC_CONFIG1 +#define SPAS_SHIFT (0) +#define REG_NFC_SPAS NFC_SPAS +#define SPAS_MASK (0xFF00) +#define IS_4BIT_ECC \ + ((raw_read(REG_NFC_ECC_MODE) & NFC_ECC_MODE_4) >> 0) + +#define NFC_SET_SPAS(v) \ + raw_write(((raw_read(REG_NFC_SPAS) & SPAS_MASK) | ((v<<SPAS_SHIFT))), \ + REG_NFC_SPAS) + +#define NFC_SET_ECC_MODE(v) \ +do { \ + if ((v) == NFC_SPAS_218 || (v) == NFC_SPAS_112) { \ + raw_write((raw_read(REG_NFC_ECC_MODE) & NFC_ECC_MODE_8), \ + REG_NFC_ECC_MODE); \ + } else { \ + raw_write((raw_read(REG_NFC_ECC_MODE) | NFC_ECC_MODE_4), \ + REG_NFC_ECC_MODE); \ + } \ +} while (0) + +#define GET_ECC_STATUS() __raw_readl(REG_NFC_ECC_STATUS_RESULT); +#define NFC_SET_NFMS(v) \ +do { \ + (NFMS |= (v)); \ + if (((v) & (1 << NFMS_NF_PG_SZ))) { \ + NFC_SET_SPAS(GET_NAND_OOB_SIZE >> 1); \ + NFC_SET_ECC_MODE(GET_NAND_OOB_SIZE >> 1); \ + } \ +} while (0) +#else +#define IS_4BIT_ECC (1) +#define NFC_SET_SPAS(v) +#define NFC_SET_ECC_MODE(v) +#define GET_ECC_STATUS() raw_read(REG_NFC_ECC_STATUS_RESULT); +#define NFC_SET_NFMS(v) (NFMS |= (v)) +#endif + +#define WRITE_NFC_IP_REG(val,reg) \ + raw_write((raw_read(REG_NFC_OPS_STAT) & ~NFC_OPS_STAT), \ + REG_NFC_OPS_STAT) + +#define GET_NFC_ECC_STATUS() raw_read(REG_NFC_ECC_STATUS_RESULT); + +/*! + * Set INT to 0, Set 1 to specific operation bit, rest to 0 in LAUNCH_NFC Register for + * Specific operation + */ +#define NFC_CMD 0x1 +#define NFC_ADDR 0x2 +#define NFC_INPUT 0x4 +#define NFC_OUTPUT 0x8 +#define NFC_ID 0x10 +#define NFC_STATUS 0x20 + +/* Bit Definitions */ +#define NFC_OPS_STAT (1 << 15) +#define NFC_SP_EN (1 << 2) +#define NFC_ECC_EN (1 << 3) +#define NFC_INT_MSK (1 << 4) +#define NFC_BIG (1 << 5) +#define NFC_RST (1 << 6) +#define NFC_CE (1 << 7) +#define NFC_ONE_CYCLE (1 << 8) +#define NFC_BLS_LOCKED 0 +#define NFC_BLS_LOCKED_DEFAULT 1 +#define NFC_BLS_UNLCOKED 2 +#define NFC_WPC_LOCK_TIGHT 1 +#define NFC_WPC_LOCK (1 << 1) +#define NFC_WPC_UNLOCK (1 << 2) +#define NFC_FLASH_ADDR_SHIFT 0 +#define NFC_UNLOCK_END_ADDR_SHIFT 0 + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V2_1 +#define NFC_ECC_MODE_4 (1<<0) +#define NFC_ECC_MODE_8 ~(1<<0) +#define NFC_SPAS_16 8 +#define NFC_SPAS_64 32 +#define NFC_SPAS_112 56 +#define NFC_SPAS_128 64 +#define NFC_SPAS_218 109 +#endif +/* NFC Register Mapping */ +#define REG_NFC_OPS_STAT NFC_CONFIG2 +#define REG_NFC_INTRRUPT NFC_CONFIG1 +#define REG_NFC_FLASH_ADDR NFC_FLASH_ADDR +#define REG_NFC_FLASH_CMD NFC_FLASH_CMD +#define REG_NFC_OPS NFC_CONFIG2 +#define REG_NFC_SET_RBA NFC_BUF_ADDR +#define REG_NFC_ECC_EN NFC_CONFIG1 +#define REG_NFC_ECC_STATUS_RESULT NFC_ECC_STATUS_RESULT +#define REG_NFC_CE NFC_CONFIG1 +#define REG_NFC_SP_EN NFC_CONFIG1 +#define REG_NFC_BLS NFC_CONFIG +#define REG_NFC_WPC NFC_WRPROT +#define REG_START_BLKADDR NFC_UNLOCKSTART_BLKADDR +#define REG_END_BLKADDR NFC_UNLOCKEND_BLKADDR +#define REG_NFC_RST NFC_CONFIG1 +#define REG_NFC_ONE_CYCLE NFC_CONFIG1 + +/* NFC V1/V2 Specific MACRO functions definitions */ + +#define raw_write(v,a) __raw_writew(v,a) +#define raw_read(a) __raw_readw(a) + +#define NFC_SET_BLS(val) val + +#define UNLOCK_ADDR(start_addr,end_addr) \ +{ \ + raw_write(start_addr,REG_START_BLKADDR); \ + raw_write(end_addr,REG_END_BLKADDR); \ +} + +#define NFC_SET_NFC_ACTIVE_CS(val) +#define NFC_GET_MAXCHIP_SP() 1 +#define NFC_SET_WPC(val) val + +/* NULL Definitions */ +#define ACK_OPS +#define NFC_SET_RBA(val) raw_write(val, REG_NFC_SET_RBA); + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V2_1 +#define READ_PAGE() send_read_page(0) +#define PROG_PAGE() send_prog_page(0) +#else +#define READ_PAGE() \ +do { \ + send_read_page(0); \ + send_read_page(1); \ + send_read_page(2); \ + send_read_page(3); \ +} while (0) + +#define PROG_PAGE() \ +do { \ + send_prog_page(0); \ + send_prog_page(1); \ + send_prog_page(2); \ + send_prog_page(3); \ +} while (0) +#endif +#define CHECK_NFC_RB 1 + +#endif + +#endif /* __MXC_ND2_H__ */ diff --git a/drivers/mxc/Kconfig b/drivers/mxc/Kconfig new file mode 100644 index 000000000000..72225bbe91de --- /dev/null +++ b/drivers/mxc/Kconfig @@ -0,0 +1,39 @@ +# drivers/video/mxc/Kconfig + +if ARCH_MXC + +menu "MXC support drivers" + +config MXC_IPU + bool "Image Processing Unit Driver" + depends on !ARCH_MX21 + depends on !ARCH_MX27 + depends on !ARCH_MX25 + select MXC_IPU_V1 if !ARCH_MX37 && !ARCH_MX51 + select MXC_IPU_V3 if ARCH_MX37 || ARCH_MX51 + select MXC_IPU_V3D if ARCH_MX37 + select MXC_IPU_V3EX if ARCH_MX51 + help + If you plan to use the Image Processing unit, say + Y here. IPU is needed by Framebuffer and V4L2 drivers. + +source "drivers/mxc/ipu/Kconfig" +source "drivers/mxc/ipu3/Kconfig" + +source "drivers/mxc/ssi/Kconfig" +source "drivers/mxc/dam/Kconfig" +source "drivers/mxc/pmic/Kconfig" +source "drivers/mxc/mcu_pmic/Kconfig" +source "drivers/mxc/security/Kconfig" +source "drivers/mxc/hmp4e/Kconfig" +source "drivers/mxc/hw_event/Kconfig" +source "drivers/mxc/vpu/Kconfig" +source "drivers/mxc/asrc/Kconfig" +source "drivers/mxc/bt/Kconfig" +source "drivers/mxc/gps_ioctrl/Kconfig" +source "drivers/mxc/mlb/Kconfig" +source "drivers/mxc/adc/Kconfig" + +endmenu + +endif diff --git a/drivers/mxc/Makefile b/drivers/mxc/Makefile new file mode 100644 index 000000000000..6416bc429888 --- /dev/null +++ b/drivers/mxc/Makefile @@ -0,0 +1,17 @@ +obj-$(CONFIG_MXC_IPU_V1) += ipu/ +obj-$(CONFIG_MXC_IPU_V3) += ipu3/ +obj-$(CONFIG_MXC_SSI) += ssi/ +obj-$(CONFIG_MXC_DAM) += dam/ + +obj-$(CONFIG_MXC_PMIC_MC9SDZ60) += mcu_pmic/ +obj-$(CONFIG_MXC_PMIC) += pmic/ + +obj-$(CONFIG_MXC_HMP4E) += hmp4e/ +obj-y += security/ +obj-$(CONFIG_MXC_VPU) += vpu/ +obj-$(CONFIG_MXC_HWEVENT) += hw_event/ +obj-$(CONFIG_MXC_ASRC) += asrc/ +obj-$(CONFIG_MXC_BLUETOOTH) += bt/ +obj-$(CONFIG_GPS_IOCTRL) += gps_ioctrl/ +obj-$(CONFIG_MXC_MLB) += mlb/ +obj-$(CONFIG_IMX_ADC) += adc/ diff --git a/drivers/mxc/adc/Kconfig b/drivers/mxc/adc/Kconfig new file mode 100644 index 000000000000..91ad23bc7cbe --- /dev/null +++ b/drivers/mxc/adc/Kconfig @@ -0,0 +1,14 @@ +# +# i.MX ADC devices +# + +menu "i.MX ADC support" + +config IMX_ADC + tristate "i.MX ADC" + depends on ARCH_MXC + default n + help + This selects the Freescale i.MX on-chip ADC driver. + +endmenu diff --git a/drivers/mxc/adc/Makefile b/drivers/mxc/adc/Makefile new file mode 100644 index 000000000000..e21e48ee1185 --- /dev/null +++ b/drivers/mxc/adc/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for i.MX adc devices. +# +obj-$(CONFIG_IMX_ADC) += imx_adc.o diff --git a/drivers/mxc/adc/imx_adc.c b/drivers/mxc/adc/imx_adc.c new file mode 100644 index 000000000000..11f0a66ee4bb --- /dev/null +++ b/drivers/mxc/adc/imx_adc.c @@ -0,0 +1,1045 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file adc/imx_adc.c + * @brief This is the main file of i.MX ADC driver. + * + * @ingroup IMX_ADC + */ + +/* + * Includes + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/imx_adc.h> +#include "imx_adc_reg.h" + +static int imx_adc_major; + +/*! + * Number of users waiting in suspendq + */ +static int swait; + +/*! + * To indicate whether any of the adc devices are suspending + */ +static int suspend_flag; + +/*! + * The suspendq is used by blocking application calls + */ +static wait_queue_head_t suspendq; +static wait_queue_head_t tsq; + +static bool imx_adc_ready; +static bool ts_data_ready; +static int tsi_data = TSI_DATA; +static unsigned short ts_data_buf[16]; + +static struct class *imx_adc_class; +static struct imx_adc_data *adc_data; + +static DECLARE_MUTEX(general_convert_mutex); +static DECLARE_MUTEX(ts_convert_mutex); + +unsigned long tsc_base; + +int is_imx_adc_ready(void) +{ + return imx_adc_ready; +} +EXPORT_SYMBOL(is_imx_adc_ready); + +void tsc_clk_enable(void) +{ + unsigned long reg; + + clk_enable(adc_data->adc_clk); + + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_IPG_CLK_EN; + __raw_writel(reg, tsc_base + TGCR); +} + +void tsc_clk_disable(void) +{ + unsigned long reg; + + clk_disable(adc_data->adc_clk); + + reg = __raw_readl(tsc_base + TGCR); + reg &= ~TGCR_IPG_CLK_EN; + __raw_writel(reg, tsc_base + TGCR); +} + +void tsc_self_reset(void) +{ + unsigned long reg; + + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_TSC_RST; + __raw_writel(reg, tsc_base + TGCR); + + while (__raw_readl(tsc_base + TGCR) & TGCR_TSC_RST) + continue; +} + +/* Internal reference */ +void tsc_intref_enable(void) +{ + unsigned long reg; + + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_INTREFEN; + __raw_writel(reg, tsc_base + TGCR); +} + +/* initialize touchscreen */ +void imx_tsc_init(void) +{ + unsigned long reg; + int lastitemid; + int dbtime; + + /* Level sense */ + reg = __raw_readl(tsc_base + TCQCR); + reg |= CQCR_PD_CFG; + reg |= (0xf << CQCR_FIFOWATERMARK_SHIFT); /* watermark */ + __raw_writel(reg, tsc_base + TCQCR); + + /* Configure 4-wire */ + reg = TSC_4WIRE_PRECHARGE; + reg |= CC_IGS; + __raw_writel(reg, tsc_base + TCC0); + + reg = TSC_4WIRE_TOUCH_DETECT; + reg |= 3 << CC_NOS_SHIFT; /* 4 samples */ + reg |= 32 << CC_SETTLING_TIME_SHIFT; /* it's important! */ + __raw_writel(reg, tsc_base + TCC1); + + reg = TSC_4WIRE_X_MEASUMENT; + reg |= 3 << CC_NOS_SHIFT; /* 4 samples */ + reg |= 16 << CC_SETTLING_TIME_SHIFT; /* settling time */ + __raw_writel(reg, tsc_base + TCC2); + + reg = TSC_4WIRE_Y_MEASUMENT; + reg |= 3 << CC_NOS_SHIFT; /* 4 samples */ + reg |= 16 << CC_SETTLING_TIME_SHIFT; /* settling time */ + __raw_writel(reg, tsc_base + TCC3); + + reg = (TCQ_ITEM_TCC0 << TCQ_ITEM7_SHIFT) | + (TCQ_ITEM_TCC0 << TCQ_ITEM6_SHIFT) | + (TCQ_ITEM_TCC1 << TCQ_ITEM5_SHIFT) | + (TCQ_ITEM_TCC0 << TCQ_ITEM4_SHIFT) | + (TCQ_ITEM_TCC3 << TCQ_ITEM3_SHIFT) | + (TCQ_ITEM_TCC2 << TCQ_ITEM2_SHIFT) | + (TCQ_ITEM_TCC1 << TCQ_ITEM1_SHIFT) | + (TCQ_ITEM_TCC0 << TCQ_ITEM0_SHIFT); + __raw_writel(reg, tsc_base + TCQ_ITEM_7_0); + + lastitemid = 5; + reg = __raw_readl(tsc_base + TCQCR); + reg = (reg & ~CQCR_LAST_ITEM_ID_MASK) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT); + __raw_writel(reg, tsc_base + TCQCR); + + /* pen down enable */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_PD_MSK; + __raw_writel(reg, tsc_base + TCQCR); + reg = __raw_readl(tsc_base + TCQMR); + reg &= ~TCQMR_PD_IRQ_MSK; + __raw_writel(reg, tsc_base + TCQMR); + + /* Debounce time = dbtime*8 adc clock cycles */ + reg = __raw_readl(tsc_base + TGCR); + dbtime = 3; + reg &= ~TGCR_PDBTIME_MASK; + reg |= dbtime << TGCR_PDBTIME_SHIFT; + reg |= TGCR_SLPC; + __raw_writel(reg, tsc_base + TGCR); + +} + +static irqreturn_t imx_adc_interrupt(int irq, void *dev_id) +{ + int reg; + + /* disable pen down detect */ + reg = __raw_readl(tsc_base + TGCR); + reg &= ~TGCR_PD_EN; + __raw_writel(reg, tsc_base + TGCR); + + ts_data_ready = 1; + wake_up_interruptible(&tsq); + return IRQ_HANDLED; +} + +enum IMX_ADC_STATUS imx_adc_read_general(unsigned short *result) +{ + unsigned long reg; + unsigned int data_num = 0; + + reg = __raw_readl(tsc_base + GCQCR); + reg |= CQCR_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + while (!(__raw_readl(tsc_base + GCQSR) & CQSR_EOQ)) + continue; + reg = __raw_readl(tsc_base + GCQCR); + reg &= ~CQCR_FQS; + __raw_writel(reg, tsc_base + GCQCR); + reg = __raw_readl(tsc_base + GCQSR); + reg |= CQSR_EOQ; + __raw_writel(reg, tsc_base + GCQSR); + + while (!(__raw_readl(tsc_base + GCQSR) & CQSR_EMPT)) { + result[data_num] = __raw_readl(tsc_base + GCQFIFO) >> + GCQFIFO_ADCOUT_SHIFT; + data_num++; + } + return IMX_ADC_SUCCESS; +} + +/*! + * This function will get raw (X,Y) value by converting the voltage + * @param touch_sample Pointer to touch sample + * + * return This funciton returns 0 if successful. + * + * + */ +enum IMX_ADC_STATUS imx_adc_read_ts(struct t_touch_screen *touch_sample, + int wait_tsi) +{ + int reg; + int data_num = 0; + int detect_sample1, detect_sample2; + + memset(ts_data_buf, 0, sizeof ts_data_buf); + touch_sample->valid_flag = 1; + + if (wait_tsi) { + /* Config idle for 4-wire */ + reg = TSC_4WIRE_TOUCH_DETECT; + __raw_writel(reg, tsc_base + TICR); + + /* Pen interrupt starts new conversion queue */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + reg |= CQCR_QSM_PEN; + __raw_writel(reg, tsc_base + TCQCR); + + /* PDEN and PDBEN */ + reg = __raw_readl(tsc_base + TGCR); + reg |= (TGCR_PDB_EN | TGCR_PD_EN); + __raw_writel(reg, tsc_base + TGCR); + + wait_event_interruptible(tsq, ts_data_ready); + while (!(__raw_readl(tsc_base + TCQSR) & CQSR_EOQ)) + continue; + /* stop the conversion */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + __raw_writel(reg, tsc_base + TCQCR); + reg = CQSR_PD | CQSR_EOQ; + __raw_writel(reg, tsc_base + TCQSR); + + /* change configuration for FQS mode */ + tsi_data = TSI_DATA; + reg = (0x1 << CC_YPLLSW_SHIFT) | (0x1 << CC_XNURSW_SHIFT) | + CC_XPULSW; + __raw_writel(reg, tsc_base + TICR); + } else { + /* FQS semaphore */ + down(&ts_convert_mutex); + + reg = (0x1 << CC_YPLLSW_SHIFT) | (0x1 << CC_XNURSW_SHIFT) | + CC_XPULSW; + __raw_writel(reg, tsc_base + TICR); + + /* FQS */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + reg |= CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + TCQCR); + reg = __raw_readl(tsc_base + TCQCR); + reg |= CQCR_FQS; + __raw_writel(reg, tsc_base + TCQCR); + while (!(__raw_readl(tsc_base + TCQSR) & CQSR_EOQ)) + continue; + + /* stop FQS */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + __raw_writel(reg, tsc_base + TCQCR); + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_FQS; + __raw_writel(reg, tsc_base + TCQCR); + + /* clear status bit */ + reg = __raw_readl(tsc_base + TCQSR); + reg |= CQSR_EOQ; + __raw_writel(reg, tsc_base + TCQSR); + tsi_data = FQS_DATA; + } + + while (!(__raw_readl(tsc_base + TCQSR) & CQSR_EMPT)) { + reg = __raw_readl(tsc_base + TCQFIFO); + ts_data_buf[data_num] = reg; + data_num++; + } + + touch_sample->x_position1 = ts_data_buf[4] >> 4; + touch_sample->x_position2 = ts_data_buf[5] >> 4; + touch_sample->x_position3 = ts_data_buf[6] >> 4; + touch_sample->y_position1 = ts_data_buf[9] >> 4; + touch_sample->y_position2 = ts_data_buf[10] >> 4; + touch_sample->y_position3 = ts_data_buf[11] >> 4; + + detect_sample1 = ts_data_buf[0]; + detect_sample2 = ts_data_buf[12]; + + if ((detect_sample1 > 0x6000) || (detect_sample2 > 0x6000)) + touch_sample->valid_flag = 0; + + ts_data_ready = 0; + + if (!(touch_sample->x_position1 || + touch_sample->x_position2 || touch_sample->x_position3)) + touch_sample->contact_resistance = 0; + else + touch_sample->contact_resistance = 1; + + if (tsi_data == FQS_DATA) + up(&ts_convert_mutex); + return IMX_ADC_SUCCESS; +} + +/*! + * This function performs filtering and rejection of excessive noise prone + * sampl. + * + * @param ts_curr Touch screen value + * + * @return This function returns 0 on success, -1 otherwise. + */ +static int imx_adc_filter(struct t_touch_screen *ts_curr) +{ + + unsigned int ydiff1, ydiff2, ydiff3, xdiff1, xdiff2, xdiff3; + unsigned int sample_sumx, sample_sumy; + static unsigned int prev_x[FILTLEN], prev_y[FILTLEN]; + int index = 0; + unsigned int y_curr, x_curr; + static int filt_count; + /* Added a variable filt_type to decide filtering at run-time */ + unsigned int filt_type = 0; + + /* ignore the data converted when pen down and up */ + if ((ts_curr->contact_resistance == 0) || tsi_data == TSI_DATA) { + ts_curr->x_position = 0; + ts_curr->y_position = 0; + filt_count = 0; + return 0; + } + /* ignore the data valid */ + if (ts_curr->valid_flag == 0) + return -1; + + ydiff1 = abs(ts_curr->y_position1 - ts_curr->y_position2); + ydiff2 = abs(ts_curr->y_position2 - ts_curr->y_position3); + ydiff3 = abs(ts_curr->y_position1 - ts_curr->y_position3); + if ((ydiff1 > DELTA_Y_MAX) || + (ydiff2 > DELTA_Y_MAX) || (ydiff3 > DELTA_Y_MAX)) { + pr_debug("imx_adc_filter: Ret pos 1\n"); + return -1; + } + + xdiff1 = abs(ts_curr->x_position1 - ts_curr->x_position2); + xdiff2 = abs(ts_curr->x_position2 - ts_curr->x_position3); + xdiff3 = abs(ts_curr->x_position1 - ts_curr->x_position3); + + if ((xdiff1 > DELTA_X_MAX) || + (xdiff2 > DELTA_X_MAX) || (xdiff3 > DELTA_X_MAX)) { + pr_debug("imx_adc_filter: Ret pos 2\n"); + return -1; + } + /* Compute two closer values among the three available Y readouts */ + + if (ydiff1 < ydiff2) { + if (ydiff1 < ydiff3) { + /* Sample 0 & 1 closest together */ + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position2; + } else { + /* Sample 0 & 2 closest together */ + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position3; + } + } else { + if (ydiff2 < ydiff3) { + /* Sample 1 & 2 closest together */ + sample_sumy = ts_curr->y_position2 + + ts_curr->y_position3; + } else { + /* Sample 0 & 2 closest together */ + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position3; + } + } + + /* + * Compute two closer values among the three available X + * readouts + */ + if (xdiff1 < xdiff2) { + if (xdiff1 < xdiff3) { + /* Sample 0 & 1 closest together */ + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position2; + } else { + /* Sample 0 & 2 closest together */ + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position3; + } + } else { + if (xdiff2 < xdiff3) { + /* Sample 1 & 2 closest together */ + sample_sumx = ts_curr->x_position2 + + ts_curr->x_position3; + } else { + /* Sample 0 & 2 closest together */ + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position3; + } + } + + /* + * Wait FILTER_MIN_DELAY number of samples to restart + * filtering + */ + if (filt_count < FILTER_MIN_DELAY) { + /* + * Current output is the average of the two closer + * values and no filtering is used + */ + y_curr = (sample_sumy / 2); + x_curr = (sample_sumx / 2); + ts_curr->y_position = y_curr; + ts_curr->x_position = x_curr; + filt_count++; + + } else { + if (abs(sample_sumx - (prev_x[0] + prev_x[1])) > + (DELTA_X_MAX * 16)) { + pr_debug("imx_adc_filter: : Ret pos 3\n"); + return -1; + } + if (abs(sample_sumy - (prev_y[0] + prev_y[1])) > + (DELTA_Y_MAX * 16)) { + pr_debug("imx_adc_filter: : Ret pos 4\n"); + return -1; + } + sample_sumy /= 2; + sample_sumx /= 2; + /* Use hard filtering if the sample difference < 10 */ + if ((abs(sample_sumy - prev_y[0]) > 10) || + (abs(sample_sumx - prev_x[0]) > 10)) + filt_type = 1; + + /* + * Current outputs are the average of three previous + * values and the present readout + */ + y_curr = sample_sumy; + for (index = 0; index < FILTLEN; index++) { + if (filt_type == 0) + y_curr = y_curr + (prev_y[index]); + else + y_curr = y_curr + (prev_y[index] / 3); + } + if (filt_type == 0) + y_curr = y_curr >> 2; + else + y_curr = y_curr >> 1; + ts_curr->y_position = y_curr; + + x_curr = sample_sumx; + for (index = 0; index < FILTLEN; index++) { + if (filt_type == 0) + x_curr = x_curr + (prev_x[index]); + else + x_curr = x_curr + (prev_x[index] / 3); + } + if (filt_type == 0) + x_curr = x_curr >> 2; + else + x_curr = x_curr >> 1; + ts_curr->x_position = x_curr; + + } + + /* Update previous X and Y values */ + for (index = (FILTLEN - 1); index > 0; index--) { + prev_x[index] = prev_x[index - 1]; + prev_y[index] = prev_y[index - 1]; + } + + /* + * Current output will be the most recent past for the + * next sample + */ + prev_y[0] = y_curr; + prev_x[0] = x_curr; + + return 0; + +} + +/*! + * This function retrieves the current touch screen (X,Y) coordinates. + * + * @param touch_sample Pointer to touch sample. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_get_touch_sample(struct t_touch_screen + *touch_sample, int wait_tsi) +{ + if (imx_adc_read_ts(touch_sample, wait_tsi)) + return IMX_ADC_ERROR; + if (!imx_adc_filter(touch_sample)) + return IMX_ADC_SUCCESS; + else + return IMX_ADC_ERROR; +} +EXPORT_SYMBOL(imx_adc_get_touch_sample); + +/*! + * This is the suspend of power management for the i.MX ADC API. + * It supports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int imx_adc_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (device_may_wakeup(&pdev->dev)) { + enable_irq_wake(adc_data->irq); + } else { + suspend_flag = 1; + tsc_clk_disable(); + } + return 0; +}; + +/*! + * This is the resume of power management for the i.MX adc API. + * It supports RESTORE state. + * + * @param pdev the device + * + * @return This function returns 0 if successful. + */ +static int imx_adc_resume(struct platform_device *pdev) +{ + if (device_may_wakeup(&pdev->dev)) { + disable_irq_wake(adc_data->irq); + } else { + suspend_flag = 0; + tsc_clk_enable(); + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + } + return 0; +} + +/*! + * This function implements the open method on an i.MX ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int imx_adc_open(struct inode *inode, struct file *file) +{ + while (suspend_flag) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, !suspend_flag)) + return -ERESTARTSYS; + } + pr_debug("imx_adc : imx_adc_open()\n"); + return 0; +} + +/*! + * This function implements the release method on an i.MX ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int imx_adc_free(struct inode *inode, struct file *file) +{ + pr_debug("imx_adc : imx_adc_free()\n"); + return 0; +} + +/*! + * This function initializes all ADC registers with default values. This + * function also registers the interrupt events. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +int imx_adc_init(void) +{ + int reg; + + pr_debug("imx_adc_init()\n"); + + if (suspend_flag) + return -EBUSY; + + tsc_clk_enable(); + + /* Reset */ + tsc_self_reset(); + + /* Internal reference */ + tsc_intref_enable(); + + /* Set power mode */ + reg = __raw_readl(tsc_base + TGCR) & ~TGCR_POWER_MASK; + reg |= TGCR_POWER_SAVE; + __raw_writel(reg, tsc_base + TGCR); + + imx_tsc_init(); + + return IMX_ADC_SUCCESS; +} +EXPORT_SYMBOL(imx_adc_init); + +/*! + * This function disables the ADC, de-registers the interrupt events. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_deinit(void) +{ + pr_debug("imx_adc_deinit()\n"); + + return IMX_ADC_SUCCESS; +} +EXPORT_SYMBOL(imx_adc_deinit); + +/*! + * This function triggers a conversion and returns one sampling result of one + * channel. + * + * @param channel The channel to be sampled + * @param result The pointer to the conversion result. The memory + * should be allocated by the caller of this function. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_convert(enum t_channel channel, + unsigned short *result) +{ + int reg; + int lastitemid; + struct t_touch_screen touch_sample; + + switch (channel) { + + case TS_X_POS: + imx_adc_get_touch_sample(&touch_sample, 0); + result[0] = touch_sample.x_position; + + /* if no pen down ,recover the register configuration */ + if (touch_sample.contact_resistance == 0) { + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + reg |= CQCR_QSM_PEN; + __raw_writel(reg, tsc_base + TCQCR); + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_PDB_EN | TGCR_PD_EN; + __raw_writel(reg, tsc_base + TGCR); + } + break; + + case TS_Y_POS: + imx_adc_get_touch_sample(&touch_sample, 0); + result[1] = touch_sample.y_position; + + /* if no pen down ,recover the register configuration */ + if (touch_sample.contact_resistance == 0) { + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + reg |= CQCR_QSM_PEN; + __raw_writel(reg, tsc_base + TCQCR); + + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_PDB_EN | TGCR_PD_EN; + __raw_writel(reg, tsc_base + TGCR); + } + break; + + case GER_PURPOSE_ADC0: + down(&general_convert_mutex); + + lastitemid = 0; + reg = (0xf << CQCR_FIFOWATERMARK_SHIFT) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + reg = TSC_GENERAL_ADC_GCC0; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC0); + + imx_adc_read_general(result); + up(&general_convert_mutex); + break; + + case GER_PURPOSE_ADC1: + down(&general_convert_mutex); + + lastitemid = 0; + reg = (0xf << CQCR_FIFOWATERMARK_SHIFT) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + reg = TSC_GENERAL_ADC_GCC1; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC0); + + imx_adc_read_general(result); + up(&general_convert_mutex); + break; + + case GER_PURPOSE_ADC2: + down(&general_convert_mutex); + + lastitemid = 0; + reg = (0xf << CQCR_FIFOWATERMARK_SHIFT) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + reg = TSC_GENERAL_ADC_GCC2; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC0); + + imx_adc_read_general(result); + up(&general_convert_mutex); + break; + + case GER_PURPOSE_MULTICHNNEL: + down(&general_convert_mutex); + + reg = TSC_GENERAL_ADC_GCC0; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC0); + + reg = TSC_GENERAL_ADC_GCC1; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC1); + + reg = TSC_GENERAL_ADC_GCC2; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC2); + + reg = (GCQ_ITEM_GCC2 << GCQ_ITEM2_SHIFT) | + (GCQ_ITEM_GCC1 << GCQ_ITEM1_SHIFT) | + (GCQ_ITEM_GCC0 << GCQ_ITEM0_SHIFT); + __raw_writel(reg, tsc_base + GCQ_ITEM_7_0); + + lastitemid = 2; + reg = (0xf << CQCR_FIFOWATERMARK_SHIFT) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + imx_adc_read_general(result); + up(&general_convert_mutex); + break; + default: + pr_debug("%s: bad channel number\n", __func__); + return IMX_ADC_ERROR; + } + + return IMX_ADC_SUCCESS; +} +EXPORT_SYMBOL(imx_adc_convert); + +/*! + * This function triggers a conversion and returns sampling results of each + * specified channel. + * + * @param channels This input parameter is bitmap to specify channels + * to be sampled. + * @param result The pointer to array to store sampling results. + * The memory should be allocated by the caller of this + * function. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_convert_multichnnel(enum t_channel channels, + unsigned short *result) +{ + imx_adc_convert(GER_PURPOSE_MULTICHNNEL, result); + return IMX_ADC_SUCCESS; +} +EXPORT_SYMBOL(imx_adc_convert_multichnnel); + +/*! + * This function implements IOCTL controls on an i.MX ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int imx_adc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct t_adc_convert_param *convert_param; + + if ((_IOC_TYPE(cmd) != 'p') && (_IOC_TYPE(cmd) != 'D')) + return -ENOTTY; + + while (suspend_flag) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, !suspend_flag)) + return -ERESTARTSYS; + } + + switch (cmd) { + case IMX_ADC_INIT: + pr_debug("init adc\n"); + CHECK_ERROR(imx_adc_init()); + break; + + case IMX_ADC_DEINIT: + pr_debug("deinit adc\n"); + CHECK_ERROR(imx_adc_deinit()); + break; + + case IMX_ADC_CONVERT: + convert_param = kmalloc(sizeof(*convert_param), GFP_KERNEL); + if (convert_param == NULL) + return -ENOMEM; + if (copy_from_user(convert_param, + (struct t_adc_convert_param *)arg, + sizeof(*convert_param))) { + kfree(convert_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(imx_adc_convert(convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((struct t_adc_convert_param *)arg, + convert_param, sizeof(*convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case IMX_ADC_CONVERT_MULTICHANNEL: + convert_param = kmalloc(sizeof(*convert_param), GFP_KERNEL); + if (convert_param == NULL) + return -ENOMEM; + if (copy_from_user(convert_param, + (struct t_adc_convert_param *)arg, + sizeof(*convert_param))) { + kfree(convert_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(imx_adc_convert_multichnnel + (convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((struct t_adc_convert_param *)arg, + convert_param, sizeof(*convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + default: + pr_debug("imx_adc_ioctl: unsupported ioctl command 0x%x\n", + cmd); + return -EINVAL; + } + return 0; +} + +static struct file_operations imx_adc_fops = { + .owner = THIS_MODULE, + .ioctl = imx_adc_ioctl, + .open = imx_adc_open, + .release = imx_adc_free, +}; + +static int imx_adc_module_probe(struct platform_device *pdev) +{ + int ret = 0; + int retval; + struct device *temp_class; + struct resource *res; + void __iomem *base; + + /* ioremap the base address */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No TSC base address provided\n"); + goto err_out0; + } + base = ioremap(res->start, res->end - res->start); + if (base == NULL) { + dev_err(&pdev->dev, "failed to rebase TSC base address\n"); + goto err_out0; + } + tsc_base = (unsigned long)base; + + /* create the chrdev */ + imx_adc_major = register_chrdev(0, "imx_adc", &imx_adc_fops); + + if (imx_adc_major < 0) { + dev_err(&pdev->dev, "Unable to get a major for imx_adc\n"); + return imx_adc_major; + } + init_waitqueue_head(&suspendq); + init_waitqueue_head(&tsq); + + imx_adc_class = class_create(THIS_MODULE, "imx_adc"); + if (IS_ERR(imx_adc_class)) { + dev_err(&pdev->dev, "Error creating imx_adc class.\n"); + ret = PTR_ERR(imx_adc_class); + goto err_out1; + } + + temp_class = device_create(imx_adc_class, NULL, + MKDEV(imx_adc_major, 0), NULL, "imx_adc"); + if (IS_ERR(temp_class)) { + dev_err(&pdev->dev, "Error creating imx_adc class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + adc_data = kmalloc(sizeof(struct imx_adc_data), GFP_KERNEL); + if (adc_data == NULL) + return -ENOMEM; + adc_data->irq = platform_get_irq(pdev, 0); + retval = request_irq(adc_data->irq, imx_adc_interrupt, + 0, MOD_NAME, MOD_NAME); + if (retval) { + return retval; + } + adc_data->adc_clk = clk_get(&pdev->dev, "tchscrn_clk"); + + ret = imx_adc_init(); + + if (ret != IMX_ADC_SUCCESS) { + dev_err(&pdev->dev, "Error in imx_adc_init.\n"); + goto err_out4; + } + imx_adc_ready = 1; + + /* By default, devices should wakeup if they can */ + /* So TouchScreen is set as "should wakeup" as it can */ + device_init_wakeup(&pdev->dev, 1); + + pr_info("i.MX ADC at 0x%x irq %d\n", (unsigned int)res->start, + adc_data->irq); + return ret; + +err_out4: + device_destroy(imx_adc_class, MKDEV(imx_adc_major, 0)); +err_out2: + class_destroy(imx_adc_class); +err_out1: + unregister_chrdev(imx_adc_major, "imx_adc"); +err_out0: + return ret; +} + +static int imx_adc_module_remove(struct platform_device *pdev) +{ + imx_adc_ready = 0; + imx_adc_deinit(); + device_destroy(imx_adc_class, MKDEV(imx_adc_major, 0)); + class_destroy(imx_adc_class); + unregister_chrdev(imx_adc_major, "imx_adc"); + free_irq(adc_data->irq, MOD_NAME); + kfree(adc_data); + pr_debug("i.MX ADC successfully removed\n"); + return 0; +} + +static struct platform_driver imx_adc_driver = { + .driver = { + .name = "imx_adc", + }, + .suspend = imx_adc_suspend, + .resume = imx_adc_resume, + .probe = imx_adc_module_probe, + .remove = imx_adc_module_remove, +}; + +/* + * Initialization and Exit + */ +static int __init imx_adc_module_init(void) +{ + pr_debug("i.MX ADC driver loading...\n"); + return platform_driver_register(&imx_adc_driver); +} + +static void __exit imx_adc_module_exit(void) +{ + platform_driver_unregister(&imx_adc_driver); + pr_debug("i.MX ADC driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(imx_adc_module_init); +module_exit(imx_adc_module_exit); + +MODULE_DESCRIPTION("i.MX ADC device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/adc/imx_adc_reg.h b/drivers/mxc/adc/imx_adc_reg.h new file mode 100644 index 000000000000..8ae776038fbc --- /dev/null +++ b/drivers/mxc/adc/imx_adc_reg.h @@ -0,0 +1,242 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef __IMX_ADC_H__ +#define __IMX_ADC_H__ + +/* TSC General Config Register */ +#define TGCR 0x000 +#define TGCR_IPG_CLK_EN (1 << 0) +#define TGCR_TSC_RST (1 << 1) +#define TGCR_FUNC_RST (1 << 2) +#define TGCR_SLPC (1 << 4) +#define TGCR_STLC (1 << 5) +#define TGCR_HSYNC_EN (1 << 6) +#define TGCR_HSYNC_POL (1 << 7) +#define TGCR_POWERMODE_SHIFT 8 +#define TGCR_POWER_OFF (0x0 << TGCR_POWERMODE_SHIFT) +#define TGCR_POWER_SAVE (0x1 << TGCR_POWERMODE_SHIFT) +#define TGCR_POWER_ON (0x3 << TGCR_POWERMODE_SHIFT) +#define TGCR_POWER_MASK (0x3 << TGCR_POWERMODE_SHIFT) +#define TGCR_INTREFEN (1 << 10) +#define TGCR_ADCCLKCFG_SHIFT 16 +#define TGCR_PD_EN (1 << 23) +#define TGCR_PDB_EN (1 << 24) +#define TGCR_PDBTIME_SHIFT 25 +#define TGCR_PDBTIME128 (0x3f << TGCR_PDBTIME_SHIFT) +#define TGCR_PDBTIME_MASK (0x7f << TGCR_PDBTIME_SHIFT) + +/* TSC General Status Register */ +#define TGSR 0x004 +#define TCQ_INT (1 << 0) +#define GCQ_INT (1 << 1) +#define SLP_INT (1 << 2) +#define TCQ_DMA (1 << 16) +#define GCQ_DMA (1 << 17) + +/* TSC IDLE Config Register */ +#define TICR 0x008 + +/* TouchScreen Convert Queue FIFO Register */ +#define TCQFIFO 0x400 +/* TouchScreen Convert Queue Control Register */ +#define TCQCR 0x404 +#define CQCR_QSM_SHIFT 0 +#define CQCR_QSM_STOP (0x0 << CQCR_QSM_SHIFT) +#define CQCR_QSM_PEN (0x1 << CQCR_QSM_SHIFT) +#define CQCR_QSM_FQS (0x2 << CQCR_QSM_SHIFT) +#define CQCR_QSM_FQS_PEN (0x3 << CQCR_QSM_SHIFT) +#define CQCR_QSM_MASK (0x3 << CQCR_QSM_SHIFT) +#define CQCR_FQS (1 << 2) +#define CQCR_RPT (1 << 3) +#define CQCR_LAST_ITEM_ID_SHIFT 4 +#define CQCR_LAST_ITEM_ID_MASK (0xf << CQCR_LAST_ITEM_ID_SHIFT) +#define CQCR_FIFOWATERMARK_SHIFT 8 +#define CQCR_FIFOWATERMARK_MASK (0xf << CQCR_FIFOWATERMARK_SHIFT) +#define CQCR_REPEATWAIT_SHIFT 12 +#define CQCR_REPEATWAIT_MASK (0xf << CQCR_REPEATWAIT_SHIFT) +#define CQCR_QRST (1 << 16) +#define CQCR_FRST (1 << 17) +#define CQCR_PD_MSK (1 << 18) +#define CQCR_PD_CFG (1 << 19) + +/* TouchScreen Convert Queue Status Register */ +#define TCQSR 0x408 +#define CQSR_PD (1 << 0) +#define CQSR_EOQ (1 << 1) +#define CQSR_FOR (1 << 4) +#define CQSR_FUR (1 << 5) +#define CQSR_FER (1 << 6) +#define CQSR_EMPT (1 << 13) +#define CQSR_FULL (1 << 14) +#define CQSR_FDRY (1 << 15) + +/* TouchScreen Convert Queue Mask Register */ +#define TCQMR 0x40c +#define TCQMR_PD_IRQ_MSK (1 << 0) +#define TCQMR_EOQ_IRQ_MSK (1 << 1) +#define TCQMR_FOR_IRQ_MSK (1 << 4) +#define TCQMR_FUR_IRQ_MSK (1 << 5) +#define TCQMR_FER_IRQ_MSK (1 << 6) +#define TCQMR_PD_DMA_MSK (1 << 16) +#define TCQMR_EOQ_DMA_MSK (1 << 17) +#define TCQMR_FOR_DMA_MSK (1 << 20) +#define TCQMR_FUR_DMA_MSK (1 << 21) +#define TCQMR_FER_DMA_MSK (1 << 22) +#define TCQMR_FDRY_DMA_MSK (1 << 31) + +/* TouchScreen Convert Queue ITEM 7~0 */ +#define TCQ_ITEM_7_0 0x420 + +/* TouchScreen Convert Queue ITEM 15~8 */ +#define TCQ_ITEM_15_8 0x424 + +#define TCQ_ITEM7_SHIFT 28 +#define TCQ_ITEM6_SHIFT 24 +#define TCQ_ITEM5_SHIFT 20 +#define TCQ_ITEM4_SHIFT 16 +#define TCQ_ITEM3_SHIFT 12 +#define TCQ_ITEM2_SHIFT 8 +#define TCQ_ITEM1_SHIFT 4 +#define TCQ_ITEM0_SHIFT 0 + +#define TCQ_ITEM_TCC0 0x0 +#define TCQ_ITEM_TCC1 0x1 +#define TCQ_ITEM_TCC2 0x2 +#define TCQ_ITEM_TCC3 0x3 +#define TCQ_ITEM_TCC4 0x4 +#define TCQ_ITEM_TCC5 0x5 +#define TCQ_ITEM_TCC6 0x6 +#define TCQ_ITEM_TCC7 0x7 +#define TCQ_ITEM_GCC7 0x8 +#define TCQ_ITEM_GCC6 0x9 +#define TCQ_ITEM_GCC5 0xa +#define TCQ_ITEM_GCC4 0xb +#define TCQ_ITEM_GCC3 0xc +#define TCQ_ITEM_GCC2 0xd +#define TCQ_ITEM_GCC1 0xe +#define TCQ_ITEM_GCC0 0xf + +/* TouchScreen Convert Config 0-7 */ +#define TCC0 0x440 +#define TCC1 0x444 +#define TCC2 0x448 +#define TCC3 0x44c +#define TCC4 0x450 +#define TCC5 0x454 +#define TCC6 0x458 +#define TCC7 0x45c +#define CC_PEN_IACK (1 << 1) +#define CC_SEL_REFN_SHIFT 2 +#define CC_SEL_REFN_YNLR (0x1 << CC_SEL_REFN_SHIFT) +#define CC_SEL_REFN_AGND (0x2 << CC_SEL_REFN_SHIFT) +#define CC_SEL_REFN_MASK (0x3 << CC_SEL_REFN_SHIFT) +#define CC_SELIN_SHIFT 4 +#define CC_SELIN_XPUL (0x0 << CC_SELIN_SHIFT) +#define CC_SELIN_YPLL (0x1 << CC_SELIN_SHIFT) +#define CC_SELIN_XNUR (0x2 << CC_SELIN_SHIFT) +#define CC_SELIN_YNLR (0x3 << CC_SELIN_SHIFT) +#define CC_SELIN_WIPER (0x4 << CC_SELIN_SHIFT) +#define CC_SELIN_INAUX0 (0x5 << CC_SELIN_SHIFT) +#define CC_SELIN_INAUX1 (0x6 << CC_SELIN_SHIFT) +#define CC_SELIN_INAUX2 (0x7 << CC_SELIN_SHIFT) +#define CC_SELIN_MASK (0x7 << CC_SELIN_SHIFT) +#define CC_SELREFP_SHIFT 7 +#define CC_SELREFP_YPLL (0x0 << CC_SELREFP_SHIFT) +#define CC_SELREFP_XPUL (0x1 << CC_SELREFP_SHIFT) +#define CC_SELREFP_EXT (0x2 << CC_SELREFP_SHIFT) +#define CC_SELREFP_INT (0x3 << CC_SELREFP_SHIFT) +#define CC_SELREFP_MASK (0x3 << CC_SELREFP_SHIFT) +#define CC_XPULSW (1 << 9) +#define CC_XNURSW_SHIFT 10 +#define CC_XNURSW_HIGH (0x0 << CC_XNURSW_SHIFT) +#define CC_XNURSW_OFF (0x1 << CC_XNURSW_SHIFT) +#define CC_XNURSW_LOW (0x3 << CC_XNURSW_SHIFT) +#define CC_XNURSW_MASK (0x3 << CC_XNURSW_SHIFT) +#define CC_YPLLSW_SHIFT 12 +#define CC_YPLLSW_MASK (0x3 << CC_YPLLSW_SHIFT) +#define CC_YNLRSW (1 << 14) +#define CC_WIPERSW (1 << 15) +#define CC_NOS_SHIFT 16 +#define CC_YPLLSW_HIGH (0x0 << CC_NOS_SHIFT) +#define CC_YPLLSW_OFF (0x1 << CC_NOS_SHIFT) +#define CC_YPLLSW_LOW (0x3 << CC_NOS_SHIFT +#define CC_NOS_MASK (0xf << CC_NOS_SHIFT) +#define CC_IGS (1 << 20) +#define CC_SETTLING_TIME_SHIFT 24 +#define CC_SETTLING_TIME_MASK (0xff << CC_SETTLING_TIME_SHIFT) + +#define TSC_4WIRE_PRECHARGE 0x158c +#define TSC_4WIRE_TOUCH_DETECT 0x578e + +#define TSC_4WIRE_X_MEASUMENT 0x1c90 +#define TSC_4WIRE_Y_MEASUMENT 0x4604 + +#define TSC_GENERAL_ADC_GCC0 0x17dc +#define TSC_GENERAL_ADC_GCC1 0x17ec +#define TSC_GENERAL_ADC_GCC2 0x17fc + +/* GeneralADC Convert Queue FIFO Register */ +#define GCQFIFO 0x800 +#define GCQFIFO_ADCOUT_SHIFT 4 +#define GCQFIFO_ADCOUT_MASK (0xfff << GCQFIFO_ADCOUT_SHIFT) +/* GeneralADC Convert Queue Control Register */ +#define GCQCR 0x804 +/* GeneralADC Convert Queue Status Register */ +#define GCQSR 0x808 +/* GeneralADC Convert Queue Mask Register */ +#define GCQMR 0x80c + +/* GeneralADC Convert Queue ITEM 7~0 */ +#define GCQ_ITEM_7_0 0x820 +/* GeneralADC Convert Queue ITEM 15~8 */ +#define GCQ_ITEM_15_8 0x824 + +#define GCQ_ITEM7_SHIFT 28 +#define GCQ_ITEM6_SHIFT 24 +#define GCQ_ITEM5_SHIFT 20 +#define GCQ_ITEM4_SHIFT 16 +#define GCQ_ITEM3_SHIFT 12 +#define GCQ_ITEM2_SHIFT 8 +#define GCQ_ITEM1_SHIFT 4 +#define GCQ_ITEM0_SHIFT 0 + +#define GCQ_ITEM_GCC0 0x0 +#define GCQ_ITEM_GCC1 0x1 +#define GCQ_ITEM_GCC2 0x2 +#define GCQ_ITEM_GCC3 0x3 + +/* GeneralADC Convert Config 0-7 */ +#define GCC0 0x840 +#define GCC1 0x844 +#define GCC2 0x848 +#define GCC3 0x84c +#define GCC4 0x850 +#define GCC5 0x854 +#define GCC6 0x858 +#define GCC7 0x85c + +/* TSC Test Register R/W */ +#define TTR 0xc00 +/* TSC Monitor Register 1, 2 */ +#define MNT1 0xc04 +#define MNT2 0xc04 + +#define DETECT_ITEM_ID_1 1 +#define DETECT_ITEM_ID_2 5 +#define TS_X_ITEM_ID 2 +#define TS_Y_ITEM_ID 3 +#define TSI_DATA 1 +#define FQS_DATA 0 + +#endif /* __IMX_ADC_H__ */ diff --git a/drivers/mxc/asrc/Kconfig b/drivers/mxc/asrc/Kconfig new file mode 100644 index 000000000000..a4c66b19f067 --- /dev/null +++ b/drivers/mxc/asrc/Kconfig @@ -0,0 +1,13 @@ +# +# ASRC configuration +# + +menu "MXC Asynchronous Sample Rate Converter support" + +config MXC_ASRC + tristate "ASRC support" + depends on ARCH_MX35 + ---help--- + Say Y to get the ASRC service. + +endmenu diff --git a/drivers/mxc/asrc/Makefile b/drivers/mxc/asrc/Makefile new file mode 100644 index 000000000000..0d2487d389c7 --- /dev/null +++ b/drivers/mxc/asrc/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the kernel Asynchronous Sample Rate Converter driver +# + +ifeq ($(CONFIG_ARCH_MX35),y) + obj-$(CONFIG_MXC_ASRC) += mxc_asrc.o +endif diff --git a/drivers/mxc/asrc/mxc_asrc.c b/drivers/mxc/asrc/mxc_asrc.c new file mode 100644 index 000000000000..62b8472433b6 --- /dev/null +++ b/drivers/mxc/asrc/mxc_asrc.c @@ -0,0 +1,1686 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_asrc.c + * + * @brief MXC Asynchronous Sample Rate Converter + * + * @ingroup SOUND + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/pagemap.h> +#include <linux/vmalloc.h> +#include <linux/types.h> +#include <linux/version.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/dma-mapping.h> +#include <linux/mxc_asrc.h> +#include <asm/irq.h> +#include <asm/memory.h> +#include <mach/dma.h> + +static int asrc_major; +static struct class *asrc_class; +#define ASRC_PROC_PATH "driver/asrc" + +#define ASRC_RATIO_DECIMAL_DEPTH 26 + +DEFINE_SPINLOCK(data_lock); +DEFINE_SPINLOCK(input_int_lock); +DEFINE_SPINLOCK(output_int_lock); + +#define AICPA 0 /* Input Clock Divider A Offset */ +#define AICDA 3 /* Input Clock Prescaler A Offset */ +#define AICPB 6 /* Input Clock Divider B Offset */ +#define AICDB 9 /* Input Clock Prescaler B Offset */ +#define AOCPA 12 /* Output Clock Divider A Offset */ +#define AOCDA 15 /* Output Clock Prescaler A Offset */ +#define AOCPB 18 /* Output Clock Divider B Offset */ +#define AOCDB 21 /* Output Clock Prescaler B Offset */ +#define AICPC 0 /* Input Clock Divider C Offset */ +#define AICDC 3 /* Input Clock Prescaler C Offset */ +#define AOCDC 6 /* Output Clock Prescaler C Offset */ +#define AOCPC 9 /* Output Clock Divider C Offset */ + +char *asrc_pair_id[] = { + [0] = "ASRC RX PAIR A", + [1] = "ASRC TX PAIR A", + [2] = "ASRC RX PAIR B", + [3] = "ASRC TX PAIR B", + [4] = "ASRC RX PAIR C", + [5] = "ASRC TX PAIR C", +}; + +enum asrc_status { + ASRC_ASRSTR_AIDEA = 0x01, + ASRC_ASRSTR_AIDEB = 0x02, + ASRC_ASRSTR_AIDEC = 0x04, + ASRC_ASRSTR_AODFA = 0x08, + ASRC_ASRSTR_AODFB = 0x10, + ASRC_ASRSTR_AODFC = 0x20, + ASRC_ASRSTR_AOLE = 0x40, + ASRC_ASRSTR_FPWT = 0x80, + ASRC_ASRSTR_AIDUA = 0x100, + ASRC_ASRSTR_AIDUB = 0x200, + ASRC_ASRSTR_AIDUC = 0x400, + ASRC_ASRSTR_AODOA = 0x800, + ASRC_ASRSTR_AODOB = 0x1000, + ASRC_ASRSTR_AODOC = 0x2000, + ASRC_ASRSTR_AIOLA = 0x4000, + ASRC_ASRSTR_AIOLB = 0x8000, + ASRC_ASRSTR_AIOLC = 0x10000, + ASRC_ASRSTR_AOOLA = 0x20000, + ASRC_ASRSTR_AOOLB = 0x40000, + ASRC_ASRSTR_AOOLC = 0x80000, + ASRC_ASRSTR_ATQOL = 0x100000, + ASRC_ASRSTR_DSLCNT = 0x200000, +}; + +/* Sample rates are aligned with that defined in pcm.h file */ +static const unsigned char asrc_process_table[][8][2] = { + /* 32kHz 44.1kHz 48kHz 64kHz 88.2kHz 96kHz 176kHz 192kHz */ +/*5512Hz*/ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*8kHz*/ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*11025Hz*/ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*16kHz*/ + {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*22050Hz*/ + {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*32kHz*/ + {{0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0},}, +/*44.1kHz*/ + {{0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, +/*48kHz*/ + {{0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, +/*64kHz*/ + {{1, 2}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0},}, +/*88.2kHz*/ + {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, +/*96kHz*/ + {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, +/*176kHz*/ + {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, +/*192kHz*/ + {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, +}; + +static const unsigned char asrc_divider_table[] = { +/*5500Hz 8kHz 11025Hz 16kHz 22050kHz 32kHz 44.1kHz 48kHz 64kHz 88.2kHz 96kHz 176400Hz 192kHz*/ + 0x07, 0x15, 0x06, 0x14, 0x05, 0x13, 0x04, 0x04, 0x12, 0x03, 0x03, 0x02, + 0x02, +}; + +static struct asrc_data *g_asrc_data; +static struct proc_dir_entry *proc_asrc; +static unsigned long asrc_vrt_base_addr; +static struct mxc_asrc_platform_data *mxc_asrc_data; + +static int asrc_set_clock_ratio(enum asrc_pair_index index, + int input_sample_rate, int output_sample_rate) +{ + int i; + int integ = 0; + unsigned long reg_val = 0; + + if (output_sample_rate == 0) + return -1; + while (input_sample_rate >= output_sample_rate) { + input_sample_rate -= output_sample_rate; + integ++; + } + reg_val |= (integ << 26); + + for (i = 1; i <= ASRC_RATIO_DECIMAL_DEPTH; i++) { + if ((input_sample_rate * 2) >= output_sample_rate) { + reg_val |= (1 << (ASRC_RATIO_DECIMAL_DEPTH - i)); + input_sample_rate = + input_sample_rate * 2 - output_sample_rate; + } else + input_sample_rate = input_sample_rate << 1; + + if (input_sample_rate == 0) + break; + } + + __raw_writel(reg_val, + (asrc_vrt_base_addr + ASRC_ASRIDRLA_REG + (index << 3))); + __raw_writel((reg_val >> 24), + (asrc_vrt_base_addr + ASRC_ASRIDRHA_REG + (index << 3))); + return 0; +} + +static int asrc_set_process_configuration(enum asrc_pair_index index, + int input_sample_rate, + int output_sample_rate) +{ + int i = 0, j = 0; + unsigned long reg; + switch (input_sample_rate) { + case 5512: + i = 0; + case 8000: + i = 1; + break; + case 11025: + i = 2; + break; + case 16000: + i = 3; + break; + case 22050: + i = 4; + break; + case 32000: + i = 5; + break; + case 44100: + i = 6; + break; + case 48000: + i = 7; + break; + case 64000: + i = 8; + break; + case 88200: + i = 9; + break; + case 96000: + i = 10; + break; + case 176400: + i = 11; + break; + case 192000: + i = 12; + break; + default: + return -1; + } + + switch (output_sample_rate) { + case 32000: + j = 0; + break; + case 44100: + j = 1; + break; + case 48000: + j = 2; + break; + case 64000: + j = 3; + break; + case 88200: + j = 4; + break; + case 96000: + j = 5; + break; + case 176400: + j = 6; + break; + case 192000: + j = 7; + break; + default: + return -1; + } + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCFG_REG); + reg &= ~(0x0f << (6 + (index << 2))); + reg |= + ((asrc_process_table[i][j][0] << (6 + (index << 2))) | + (asrc_process_table[i][j][1] << (8 + (index << 2)))); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCFG_REG); + + return 0; +} + +static int asrc_get_asrck_clock_divider(int sample_rate) +{ + int i = 0; + switch (sample_rate) { + case 5500: + i = 0; + break; + case 8000: + i = 1; + break; + case 11025: + i = 2; + break; + case 16000: + i = 3; + break; + case 22050: + i = 4; + break; + case 32000: + i = 5; + break; + case 44100: + i = 6; + break; + case 48000: + i = 7; + break; + case 64000: + i = 8; + break; + case 88200: + i = 9; + break; + case 96000: + i = 10; + break; + case 176400: + i = 11; + break; + case 192000: + i = 12; + break; + default: + return -1; + } + + return asrc_divider_table[i]; +} + +int asrc_req_pair(int chn_num, enum asrc_pair_index *index) +{ + int err = 0; + unsigned long lock_flags; + spin_lock_irqsave(&data_lock, lock_flags); + + if (chn_num > 2) { + if (g_asrc_data->asrc_pair[ASRC_PAIR_C].active + || (chn_num > g_asrc_data->asrc_pair[ASRC_PAIR_C].chn_max)) + err = -EBUSY; + else { + *index = ASRC_PAIR_C; + g_asrc_data->asrc_pair[ASRC_PAIR_C].chn_num = chn_num; + g_asrc_data->asrc_pair[ASRC_PAIR_C].active = 1; + } + } else { + if (g_asrc_data->asrc_pair[ASRC_PAIR_A].active || + (g_asrc_data->asrc_pair[ASRC_PAIR_A].chn_max == 0)) { + if (g_asrc_data->asrc_pair[ASRC_PAIR_B]. + active + || (g_asrc_data->asrc_pair[ASRC_PAIR_B]. + chn_max == 0)) + err = -EBUSY; + else { + *index = ASRC_PAIR_B; + g_asrc_data->asrc_pair[ASRC_PAIR_B].chn_num = 2; + g_asrc_data->asrc_pair[ASRC_PAIR_B].active = 1; + } + } else { + *index = ASRC_PAIR_A; + g_asrc_data->asrc_pair[ASRC_PAIR_A].chn_num = 2; + g_asrc_data->asrc_pair[ASRC_PAIR_A].active = 1; + } + } + spin_unlock_irqrestore(&data_lock, lock_flags); + return err; +} + +EXPORT_SYMBOL(asrc_req_pair); + +void asrc_release_pair(enum asrc_pair_index index) +{ + unsigned long reg; + unsigned long lock_flags; + + spin_lock_irqsave(&data_lock, lock_flags); + g_asrc_data->asrc_pair[index].active = 0; + g_asrc_data->asrc_pair[index].overload_error = 0; + /********Disable PAIR*************/ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg &= ~(1 << (index + 1)); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + spin_unlock_irqrestore(&data_lock, lock_flags); +} + +EXPORT_SYMBOL(asrc_release_pair); + +int asrc_config_pair(struct asrc_config *config) +{ + int err = 0; + int reg, tmp, channel_num; + unsigned long lock_flags; + /* Set the channel number */ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + spin_lock_irqsave(&data_lock, lock_flags); + g_asrc_data->asrc_pair[config->pair].chn_num = config->channel_num; + spin_unlock_irqrestore(&data_lock, lock_flags); + reg &= + ~((0xFFFFFFFF >> (32 - mxc_asrc_data->channel_bits)) << + (mxc_asrc_data->channel_bits * config->pair)); + if (mxc_asrc_data->channel_bits > 3) + channel_num = config->channel_num; + else + channel_num = (config->channel_num + 1) / 2; + tmp = channel_num << (mxc_asrc_data->channel_bits * config->pair); + reg |= tmp; + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + + /* Set the clock source */ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCSR_REG); + tmp = ~(0x0f << (config->pair << 2)); + reg &= tmp; + tmp = ~(0x0f << (12 + (config->pair << 2))); + reg &= tmp; + reg |= + ((config->inclk << (config->pair << 2)) | (config-> + outclk << (12 + + (config-> + pair << + 2)))); + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCSR_REG); + + /* default setting */ + /* automatic selection for processing mode */ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg |= (1 << (20 + config->pair)); + reg &= ~(1 << (14 + (config->pair << 1))); + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRRA_REG); + reg &= 0xffbfffff; + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRRA_REG); + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg = reg & (~(1 << 23)); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + + /* Default Clock Divider Setting */ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + if (config->pair == ASRC_PAIR_A) { + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + reg &= 0xfc0fc0; + /* Input Part */ + if ((config->inclk & 0x0f) == INCLK_SPDIF_RX + || (config->inclk & 0x0f) == INCLK_SPDIF_TX) { + reg |= 7 << AICPA; + } else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + input_sample_rate); + reg |= tmp << AICPA; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AICPA; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AICPA; + else + err = -EFAULT; + } + /* Output Part */ + if ((config->outclk & 0x0f) == OUTCLK_SPDIF_RX + || (config->outclk & 0x0f) == OUTCLK_SPDIF_TX) { + reg |= 7 << AOCPA; + } else if ((config->outclk & 0x0f) == OUTCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + output_sample_rate); + reg |= tmp << AOCPA; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AOCPA; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AOCPA; + else + err = -EFAULT; + } + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + + } else if (config->pair == ASRC_PAIR_B) { + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + reg &= 0x03f03f; + /* Input Part */ + if ((config->inclk & 0x0f) == INCLK_SPDIF_RX + || (config->inclk & 0x0f) == INCLK_SPDIF_TX) { + reg |= 7 << AICPB; + } else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + input_sample_rate); + reg |= tmp << AICPB; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AICPB; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AICPB; + else + err = -EFAULT; + } + /* Output Part */ + if ((config->outclk & 0x0f) == INCLK_SPDIF_RX + || (config->outclk & 0x0f) == INCLK_SPDIF_TX) { + reg |= 7 << AOCPB; + } else if ((config->outclk & 0x0f) == INCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + output_sample_rate); + reg |= tmp << AOCPB; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AOCPB; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AOCPB; + else + err = -EFAULT; + } + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + + } else { + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR2_REG); + reg &= 0; + /* Input Part */ + if ((config->inclk & 0x0f) == INCLK_SPDIF_RX + || (config->inclk & 0x0f) == INCLK_SPDIF_TX) { + reg |= 7 << AICPC; + } else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + input_sample_rate); + reg |= tmp << AICPC; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AICPC; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AICPC; + else + err = -EFAULT; + } + /* Output Part */ + if ((config->outclk & 0x0f) == INCLK_SPDIF_RX + || (config->outclk & 0x0f) == INCLK_SPDIF_TX) { + reg |= 7 << AOCPC; + } else if ((config->outclk & 0x0f) == INCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + output_sample_rate); + reg |= tmp << AOCPC; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AOCPC; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AOCPC; + else + err = -EFAULT; + } + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCDR2_REG); + + } + + /* check whether ideal ratio is a must */ + if ((config->inclk & 0x0f) == INCLK_NONE) { + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg &= ~(1 << (20 + config->pair)); + reg |= (0x03 << (13 + (config->pair << 1))); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + err = asrc_set_clock_ratio(config->pair, + config->input_sample_rate, + config->output_sample_rate); + if (err < 0) + return err; + + err = asrc_set_process_configuration(config->pair, + config->input_sample_rate, + config-> + output_sample_rate); + if (err < 0) + return err; + } else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) { + if (config->input_sample_rate == 44100 + || config->input_sample_rate == 88200) { + pr_info + ("ASRC core clock cann't support sample rate %d\n", + config->input_sample_rate); + err = -EFAULT; + } + } else if ((config->outclk & 0x0f) == OUTCLK_ASRCK1_CLK) { + if (config->output_sample_rate == 44100 + || config->output_sample_rate == 88200) { + pr_info + ("ASRC core clock cann't support sample rate %d\n", + config->input_sample_rate); + err = -EFAULT; + } + } + + return err; +} + +EXPORT_SYMBOL(asrc_config_pair); + +void asrc_start_conv(enum asrc_pair_index index) +{ + int reg, reg_1; + unsigned long lock_flags; + int i; + + spin_lock_irqsave(&data_lock, lock_flags); + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + if ((reg & 0x0E) == 0) + clk_enable(mxc_asrc_data->asrc_audio_clk); + reg |= (1 << (1 + index)); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCFG_REG); + while (!(reg & (1 << (index + 21)))) + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCFG_REG); + reg_1 = __raw_readl(asrc_vrt_base_addr + ASRC_ASRSTR_REG); + + reg = 0; + for (i = 0; i < 20; i++) { + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + } + + __raw_writel(0x40, asrc_vrt_base_addr + ASRC_ASRIER_REG); + spin_unlock_irqrestore(&data_lock, lock_flags); + return; +} + +EXPORT_SYMBOL(asrc_start_conv); + +void asrc_stop_conv(enum asrc_pair_index index) +{ + int reg; + unsigned long lock_flags; + spin_lock_irqsave(&data_lock, lock_flags); + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg &= ~(1 << (1 + index)); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + if ((reg & 0x0E) == 0) + clk_disable(mxc_asrc_data->asrc_audio_clk); + spin_unlock_irqrestore(&data_lock, lock_flags); + return; +} + +EXPORT_SYMBOL(asrc_stop_conv); + +/*! + * @brief asrc interrupt handler + */ +static irqreturn_t asrc_isr(int irq, void *dev_id) +{ + unsigned long status; + int reg = 0x40; + + status = __raw_readl(asrc_vrt_base_addr + ASRC_ASRSTR_REG); + if (g_asrc_data->asrc_pair[ASRC_PAIR_A].active == 1) { + if (status & ASRC_ASRSTR_ATQOL) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_TASK_Q_OVERLOAD; + if (status & ASRC_ASRSTR_AOOLA) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_OUTPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AIOLA) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_INPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AODOA) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_OUTPUT_BUFFER_OVERFLOW; + if (status & ASRC_ASRSTR_AIDUA) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_INPUT_BUFFER_UNDERRUN; + } else if (g_asrc_data->asrc_pair[ASRC_PAIR_B].active == 1) { + if (status & ASRC_ASRSTR_ATQOL) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_TASK_Q_OVERLOAD; + if (status & ASRC_ASRSTR_AOOLB) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_OUTPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AIOLB) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_INPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AODOB) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_OUTPUT_BUFFER_OVERFLOW; + if (status & ASRC_ASRSTR_AIDUB) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_INPUT_BUFFER_UNDERRUN; + } else if (g_asrc_data->asrc_pair[ASRC_PAIR_C].active == 1) { + if (status & ASRC_ASRSTR_ATQOL) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_TASK_Q_OVERLOAD; + if (status & ASRC_ASRSTR_AOOLC) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_OUTPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AIOLC) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_INPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AODOC) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_OUTPUT_BUFFER_OVERFLOW; + if (status & ASRC_ASRSTR_AIDUC) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_INPUT_BUFFER_UNDERRUN; + } + + /* try to clean the overload error */ + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRSTR_REG); + + return IRQ_HANDLED; +} + +void asrc_get_status(struct asrc_status_flags *flags) +{ + unsigned long lock_flags; + enum asrc_pair_index index; + + spin_lock_irqsave(&data_lock, lock_flags); + index = flags->index; + flags->overload_error = g_asrc_data->asrc_pair[index].overload_error; + + spin_unlock_irqrestore(&data_lock, lock_flags); + return; +} + +EXPORT_SYMBOL(asrc_get_status); + +static int mxc_init_asrc(void) +{ + /* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */ + __raw_writel(0x0001, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + + /* Enable overflow interrupt */ + __raw_writel(0x00, asrc_vrt_base_addr + ASRC_ASRIER_REG); + + /* Default 6: 2: 2 channel assignment */ + __raw_writel((0x06 << mxc_asrc_data->channel_bits * + 2) | (0x02 << mxc_asrc_data->channel_bits) | 0x02, + asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + + /* Parameter Registers recommended settings */ + __raw_writel(0x7fffff, asrc_vrt_base_addr + ASRC_ASRPM1_REG); + __raw_writel(0x255555, asrc_vrt_base_addr + ASRC_ASRPM2_REG); + __raw_writel(0xff7280, asrc_vrt_base_addr + ASRC_ASRPM3_REG); + __raw_writel(0xff7280, asrc_vrt_base_addr + ASRC_ASRPM4_REG); + __raw_writel(0xff7280, asrc_vrt_base_addr + ASRC_ASRPM5_REG); + + __raw_writel(0x001f00, asrc_vrt_base_addr + ASRC_ASRTFR1); + + /* Set the processing clock for 76KHz, 133M */ + __raw_writel(0x06D6, asrc_vrt_base_addr + ASRC_ASR76K_REG); + + /* Set the processing clock for 56KHz, 133M */ + __raw_writel(0x0947, asrc_vrt_base_addr + ASRC_ASR56K_REG); + + if (request_irq(MXC_INT_ASRC, asrc_isr, 0, "asrc", NULL)) + return -1; + + return 0; +} + +static int asrc_get_output_buffer_size(int input_buffer_size, + int input_sample_rate, + int output_sample_rate) +{ + int i = 0; + int outbuffer_size = 0; + int outsample = output_sample_rate; + while (outsample >= input_sample_rate) { + ++i; + outsample -= input_sample_rate; + } + outbuffer_size = i * input_buffer_size; + i = 1; + while (((input_buffer_size >> i) > 2) && (outsample != 0)) { + if (((outsample << 1) - input_sample_rate) >= 0) { + outsample = (outsample << 1) - input_sample_rate; + outbuffer_size += (input_buffer_size >> i); + } else { + outsample = outsample << 1; + } + i++; + } + outbuffer_size = (outbuffer_size >> 3) << 3; + return outbuffer_size; +} + +static void asrc_input_dma_callback(void *data, int error, unsigned int count) +{ + struct asrc_pair_params *params; + struct dma_block *block; + mxc_dma_requestbuf_t dma_request; + unsigned long lock_flags; + + params = data; + + spin_lock_irqsave(&input_int_lock, lock_flags); + params->input_queue_empty--; + if (!list_empty(¶ms->input_queue)) { + block = + list_entry(params->input_queue.next, + struct dma_block, queue); + dma_request.src_addr = (dma_addr_t) block->dma_paddr; + dma_request.dst_addr = + (ASRC_BASE_ADDR + ASRC_ASRDIA_REG + (params->index << 3)); + dma_request.num_of_bytes = block->length; + mxc_dma_config(params->input_dma_channel, &dma_request, + 1, MXC_DMA_MODE_WRITE); + list_del(params->input_queue.next); + list_add_tail(&block->queue, ¶ms->input_done_queue); + params->input_queue_empty++; + } + params->input_counter++; + wake_up_interruptible(¶ms->input_wait_queue); + spin_unlock_irqrestore(&input_int_lock, lock_flags); + return; +} + +static void asrc_output_dma_callback(void *data, int error, unsigned int count) +{ + struct asrc_pair_params *params; + struct dma_block *block; + mxc_dma_requestbuf_t dma_request; + unsigned long lock_flags; + + params = data; + + spin_lock_irqsave(&output_int_lock, lock_flags); + params->output_queue_empty--; + + if (!list_empty(¶ms->output_queue)) { + block = + list_entry(params->output_queue.next, + struct dma_block, queue); + dma_request.src_addr = + (ASRC_BASE_ADDR + ASRC_ASRDOA_REG + (params->index << 3)); + dma_request.dst_addr = (dma_addr_t) block->dma_paddr; + dma_request.num_of_bytes = block->length; + mxc_dma_config(params->output_dma_channel, &dma_request, + 1, MXC_DMA_MODE_READ); + list_del(params->output_queue.next); + list_add_tail(&block->queue, ¶ms->output_done_queue); + params->output_queue_empty++; + } + params->output_counter++; + wake_up_interruptible(¶ms->output_wait_queue); + spin_unlock_irqrestore(&output_int_lock, lock_flags); + return; +} + +static void mxc_free_dma_buf(struct asrc_pair_params *params) +{ + int i; + for (i = 0; i < ASRC_DMA_BUFFER_NUM; i++) { + if (params->input_dma[i].dma_vaddr != NULL) { + dma_free_coherent(0, + params->input_buffer_size, + params->input_dma[i]. + dma_vaddr, + params->input_dma[i].dma_paddr); + params->input_dma[i].dma_vaddr = NULL; + } + if (params->output_dma[i].dma_vaddr != NULL) { + dma_free_coherent(0, + params->output_buffer_size, + params->output_dma[i]. + dma_vaddr, + params->output_dma[i].dma_paddr); + params->output_dma[i].dma_vaddr = NULL; + } + } + + return; +} + +static int mxc_allocate_dma_buf(struct asrc_pair_params *params) +{ + int i; + for (i = 0; i < ASRC_DMA_BUFFER_NUM; i++) { + params->input_dma[i].dma_vaddr = + dma_alloc_coherent(0, params->input_buffer_size, + ¶ms->input_dma[i].dma_paddr, + GFP_DMA | GFP_KERNEL); + if (params->input_dma[i].dma_vaddr == NULL) { + mxc_free_dma_buf(params); + pr_info("can't allocate buff\n"); + return -ENOBUFS; + } + } + for (i = 0; i < ASRC_DMA_BUFFER_NUM; i++) { + params->output_dma[i].dma_vaddr = + dma_alloc_coherent(0, + params->output_buffer_size, + ¶ms->output_dma[i].dma_paddr, + GFP_DMA | GFP_KERNEL); + if (params->output_dma[i].dma_vaddr == NULL) { + mxc_free_dma_buf(params); + return -ENOBUFS; + } + } + + return 0; +} + +/*! + * asrc interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param cmd unsigned int + * + * @param arg unsigned long + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int asrc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int err = 0; + struct asrc_pair_params *params; + params = file->private_data; + + if (down_interruptible(¶ms->busy_lock)) + return -EBUSY; + switch (cmd) { + case ASRC_REQ_PAIR: + { + struct asrc_req req; + if (copy_from_user(&req, (void __user *)arg, + sizeof(struct asrc_req))) { + err = -EFAULT; + break; + } + err = asrc_req_pair(req.chn_num, &req.index); + if (err < 0) + break; + params->pair_hold = 1; + params->index = req.index; + if (copy_to_user + ((void __user *)arg, &req, sizeof(struct asrc_req))) + err = -EFAULT; + + break; + } + case ASRC_CONFIG_PAIR: + { + struct asrc_config config; + mxc_dma_device_t rx_id, tx_id; + char *rx_name, *tx_name; + int channel = -1; + if (copy_from_user + (&config, (void __user *)arg, + sizeof(struct asrc_config))) { + err = -EFAULT; + break; + } + err = asrc_config_pair(&config); + if (err < 0) + break; + params->output_buffer_size = + asrc_get_output_buffer_size(config. + dma_buffer_size, + config. + input_sample_rate, + config. + output_sample_rate); + params->input_buffer_size = config.dma_buffer_size; + if (config.buffer_num > ASRC_DMA_BUFFER_NUM) + params->buffer_num = ASRC_DMA_BUFFER_NUM; + else + params->buffer_num = config.buffer_num; + err = mxc_allocate_dma_buf(params); + if (err < 0) + break; + + /* TBD - need to update when new SDMA interface ready */ + if (config.pair == ASRC_PAIR_A) { + rx_id = MXC_DMA_ASRC_A_RX; + tx_id = MXC_DMA_ASRC_A_TX; + rx_name = asrc_pair_id[0]; + tx_name = asrc_pair_id[1]; + } else if (config.pair == ASRC_PAIR_B) { + rx_id = MXC_DMA_ASRC_B_RX; + tx_id = MXC_DMA_ASRC_B_TX; + rx_name = asrc_pair_id[2]; + tx_name = asrc_pair_id[3]; + } else { + rx_id = MXC_DMA_ASRC_C_RX; + tx_id = MXC_DMA_ASRC_C_TX; + rx_name = asrc_pair_id[4]; + tx_name = asrc_pair_id[5]; + } + channel = mxc_dma_request(rx_id, rx_name); + params->input_dma_channel = channel; + err = mxc_dma_callback_set(channel, (mxc_dma_callback_t) + asrc_input_dma_callback, + (void *)params); + channel = mxc_dma_request(tx_id, tx_name); + params->output_dma_channel = channel; + err = mxc_dma_callback_set(channel, (mxc_dma_callback_t) + asrc_output_dma_callback, + (void *)params); + /* TBD - need to update when new SDMA interface ready */ + params->input_queue_empty = 0; + params->output_queue_empty = 0; + INIT_LIST_HEAD(¶ms->input_queue); + INIT_LIST_HEAD(¶ms->input_done_queue); + INIT_LIST_HEAD(¶ms->output_queue); + INIT_LIST_HEAD(¶ms->output_done_queue); + init_waitqueue_head(¶ms->input_wait_queue); + init_waitqueue_head(¶ms->output_wait_queue); + + if (copy_to_user + ((void __user *)arg, &config, + sizeof(struct asrc_config))) + err = -EFAULT; + break; + } + case ASRC_QUERYBUF: + { + struct asrc_querybuf buffer; + if (copy_from_user + (&buffer, (void __user *)arg, + sizeof(struct asrc_querybuf))) { + err = -EFAULT; + break; + } + buffer.input_offset = + (unsigned long)params->input_dma[buffer. + buffer_index]. + dma_paddr; + buffer.input_length = params->input_buffer_size; + buffer.output_offset = + (unsigned long)params->output_dma[buffer. + buffer_index]. + dma_paddr; + buffer.output_length = params->output_buffer_size; + if (copy_to_user + ((void __user *)arg, &buffer, + sizeof(struct asrc_querybuf))) + err = -EFAULT; + break; + } + case ASRC_RELEASE_PAIR: + { + enum asrc_pair_index index; + if (copy_from_user + (&index, (void __user *)arg, + sizeof(enum asrc_pair_index))) { + err = -EFAULT; + break; + } + + mxc_dma_free(params->input_dma_channel); + mxc_dma_free(params->output_dma_channel); + mxc_free_dma_buf(params); + asrc_release_pair(index); + params->pair_hold = 0; + break; + } + case ASRC_Q_INBUF: + { + struct asrc_buffer buf; + struct dma_block *block; + mxc_dma_requestbuf_t dma_request; + unsigned long lock_flags; + if (copy_from_user + (&buf, (void __user *)arg, + sizeof(struct asrc_buffer))) { + err = -EFAULT; + break; + } + spin_lock_irqsave(&input_int_lock, lock_flags); + params->input_dma[buf.index].index = buf.index; + params->input_dma[buf.index].length = buf.length; + list_add_tail(¶ms->input_dma[buf.index]. + queue, ¶ms->input_queue); + if (params->asrc_active == 0 + || params->input_queue_empty == 0) { + block = + list_entry(params->input_queue.next, + struct dma_block, queue); + dma_request.src_addr = + (dma_addr_t) block->dma_paddr; + dma_request.dst_addr = + (ASRC_BASE_ADDR + ASRC_ASRDIA_REG + + (params->index << 3)); + dma_request.num_of_bytes = block->length; + mxc_dma_config(params-> + input_dma_channel, + &dma_request, 1, + MXC_DMA_MODE_WRITE); + params->input_queue_empty++; + list_del(params->input_queue.next); + list_add_tail(&block->queue, + ¶ms->input_done_queue); + } + + spin_unlock_irqrestore(&input_int_lock, lock_flags); + break; + } + case ASRC_DQ_INBUF:{ + struct asrc_buffer buf; + struct dma_block *block; + unsigned long lock_flags; + if (copy_from_user + (&buf, (void __user *)arg, + sizeof(struct asrc_buffer))) { + err = -EFAULT; + break; + } + /* if ASRC is inactive, nonsense to DQ buffer */ + if (params->asrc_active == 0) { + err = -EFAULT; + buf.buf_valid = ASRC_BUF_NA; + if (copy_to_user + ((void __user *)arg, &buf, + sizeof(struct asrc_buffer))) + err = -EFAULT; + break; + } + + if (!wait_event_interruptible_timeout + (params->input_wait_queue, + params->input_counter != 0, 10 * HZ)) { + pr_info + ("ASRC_DQ_INBUF timeout counter %x\n", + params->input_counter); + err = -ETIME; + break; + } else if (signal_pending(current)) { + pr_info("ASRC_DQ_INBUF interrupt received\n"); + err = -ERESTARTSYS; + break; + } + spin_lock_irqsave(&input_int_lock, lock_flags); + params->input_counter--; + block = + list_entry(params->input_done_queue.next, + struct dma_block, queue); + list_del(params->input_done_queue.next); + spin_unlock_irqrestore(&input_int_lock, lock_flags); + buf.index = block->index; + buf.length = block->length; + buf.buf_valid = ASRC_BUF_AV; + if (copy_to_user + ((void __user *)arg, &buf, + sizeof(struct asrc_buffer))) + err = -EFAULT; + + break; + } + case ASRC_Q_OUTBUF:{ + struct asrc_buffer buf; + struct dma_block *block; + mxc_dma_requestbuf_t dma_request; + unsigned long lock_flags; + if (copy_from_user + (&buf, (void __user *)arg, + sizeof(struct asrc_buffer))) { + err = -EFAULT; + break; + } + spin_lock_irqsave(&output_int_lock, lock_flags); + params->output_dma[buf.index].index = buf.index; + params->output_dma[buf.index].length = buf.length; + list_add_tail(¶ms->output_dma[buf.index]. + queue, ¶ms->output_queue); + if (params->asrc_active == 0 + || params->output_queue_empty == 0) { + block = + list_entry(params->output_queue. + next, struct dma_block, queue); + dma_request.src_addr = + (ASRC_BASE_ADDR + ASRC_ASRDOA_REG + + (params->index << 3)); + dma_request.dst_addr = + (dma_addr_t) block->dma_paddr; + dma_request.num_of_bytes = block->length; + mxc_dma_config(params-> + output_dma_channel, + &dma_request, 1, + MXC_DMA_MODE_READ); + list_del(params->output_queue.next); + list_add_tail(&block->queue, + ¶ms->output_done_queue); + params->output_queue_empty++; + } + + spin_unlock_irqrestore(&output_int_lock, lock_flags); + break; + } + case ASRC_DQ_OUTBUF:{ + struct asrc_buffer buf; + struct dma_block *block; + unsigned long lock_flags; + if (copy_from_user + (&buf, (void __user *)arg, + sizeof(struct asrc_buffer))) { + err = -EFAULT; + break; + } + /* if ASRC is inactive, nonsense to DQ buffer */ + if (params->asrc_active == 0) { + buf.buf_valid = ASRC_BUF_NA; + err = -EFAULT; + if (copy_to_user + ((void __user *)arg, &buf, + sizeof(struct asrc_buffer))) + err = -EFAULT; + break; + } + + if (!wait_event_interruptible_timeout + (params->output_wait_queue, + params->output_counter != 0, 10 * HZ)) { + pr_info + ("ASRC_DQ_OUTBUF timeout counter %x\n", + params->output_counter); + err = -ETIME; + break; + } else if (signal_pending(current)) { + pr_info("ASRC_DQ_INBUF interrupt received\n"); + err = -ERESTARTSYS; + break; + } + spin_lock_irqsave(&output_int_lock, lock_flags); + params->output_counter--; + block = + list_entry(params->output_done_queue.next, + struct dma_block, queue); + list_del(params->output_done_queue.next); + spin_unlock_irqrestore(&output_int_lock, lock_flags); + buf.index = block->index; + buf.length = block->length; + buf.buf_valid = ASRC_BUF_AV; + if (copy_to_user + ((void __user *)arg, &buf, + sizeof(struct asrc_buffer))) + err = -EFAULT; + + break; + } + case ASRC_START_CONV:{ + enum asrc_pair_index index; + unsigned long lock_flags; + if (copy_from_user + (&index, (void __user *)arg, + sizeof(enum asrc_pair_index))) { + err = -EFAULT; + break; + } + + spin_lock_irqsave(&input_int_lock, lock_flags); + if (params->input_queue_empty == 0) { + err = -EFAULT; + pr_info + ("ASRC_START_CONV - no block available\n"); + break; + } + spin_unlock_irqrestore(&input_int_lock, lock_flags); + params->asrc_active = 1; + + asrc_start_conv(index); + mxc_dma_enable(params->input_dma_channel); + mxc_dma_enable(params->output_dma_channel); + break; + } + case ASRC_STOP_CONV:{ + enum asrc_pair_index index; + if (copy_from_user + (&index, (void __user *)arg, + sizeof(enum asrc_pair_index))) { + err = -EFAULT; + break; + } + mxc_dma_disable(params->input_dma_channel); + mxc_dma_disable(params->output_dma_channel); + asrc_stop_conv(index); + params->asrc_active = 0; + break; + } + case ASRC_STATUS:{ + struct asrc_status_flags flags; + if (copy_from_user + (&flags, (void __user *)arg, + sizeof(struct asrc_status_flags))) { + err = -EFAULT; + break; + } + asrc_get_status(&flags); + if (copy_to_user + ((void __user *)arg, &flags, + sizeof(struct asrc_status_flags))) + err = -EFAULT; + break; + } + case ASRC_FLUSH:{ + /* flush input dma buffer */ + unsigned long lock_flags; + mxc_dma_device_t rx_id, tx_id; + char *rx_name, *tx_name; + int channel = -1; + spin_lock_irqsave(&input_int_lock, lock_flags); + while (!list_empty(¶ms->input_queue)) + list_del(params->input_queue.next); + while (!list_empty(¶ms->input_done_queue)) + list_del(params->input_done_queue.next); + params->input_counter = 0; + params->input_queue_empty = 0; + spin_unlock_irqrestore(&input_int_lock, lock_flags); + + /* flush output dma buffer */ + spin_lock_irqsave(&output_int_lock, lock_flags); + while (!list_empty(¶ms->output_queue)) + list_del(params->output_queue.next); + while (!list_empty(¶ms->output_done_queue)) + list_del(params->output_done_queue.next); + params->output_counter = 0; + params->output_queue_empty = 0; + spin_unlock_irqrestore(&output_int_lock, lock_flags); + + /* release DMA and request again */ + mxc_dma_free(params->input_dma_channel); + mxc_dma_free(params->output_dma_channel); + if (params->index == ASRC_PAIR_A) { + rx_id = MXC_DMA_ASRC_A_RX; + tx_id = MXC_DMA_ASRC_A_TX; + rx_name = asrc_pair_id[0]; + tx_name = asrc_pair_id[1]; + } else if (params->index == ASRC_PAIR_B) { + rx_id = MXC_DMA_ASRC_B_RX; + tx_id = MXC_DMA_ASRC_B_TX; + rx_name = asrc_pair_id[2]; + tx_name = asrc_pair_id[3]; + } else { + rx_id = MXC_DMA_ASRC_C_RX; + tx_id = MXC_DMA_ASRC_C_TX; + rx_name = asrc_pair_id[4]; + tx_name = asrc_pair_id[5]; + } + channel = mxc_dma_request(rx_id, rx_name); + params->input_dma_channel = channel; + err = mxc_dma_callback_set(channel, (mxc_dma_callback_t) + asrc_input_dma_callback, + (void *)params); + channel = mxc_dma_request(tx_id, tx_name); + params->output_dma_channel = channel; + err = mxc_dma_callback_set(channel, (mxc_dma_callback_t) + asrc_output_dma_callback, + (void *)params); + + break; + } + default: + break; + } + + up(¶ms->busy_lock); + return err; +} + +/*! + * asrc interface - open function + * + * @param inode structure inode * + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENOBUFS failed to allocate buffer, ERESTARTSYS interrupted by user + */ +static int mxc_asrc_open(struct inode *inode, struct file *file) +{ + int err = 0; + struct asrc_pair_params *pair_params; + if (signal_pending(current)) + return -EINTR; + pair_params = kzalloc(sizeof(struct asrc_pair_params), GFP_KERNEL); + if (pair_params == NULL) { + pr_debug("Failed to allocate pair_params\n"); + err = -ENOBUFS; + } + + init_MUTEX(&pair_params->busy_lock); + file->private_data = pair_params; + return err; +} + +/*! + * asrc interface - close function + * + * @param inode struct inode * + * @param file structure file * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int mxc_asrc_close(struct inode *inode, struct file *file) +{ + struct asrc_pair_params *pair_params; + pair_params = file->private_data; + if (pair_params->asrc_active == 1) { + mxc_dma_disable(pair_params->input_dma_channel); + mxc_dma_disable(pair_params->output_dma_channel); + asrc_stop_conv(pair_params->index); + wake_up_interruptible(&pair_params->input_wait_queue); + wake_up_interruptible(&pair_params->output_wait_queue); + } + if (pair_params->pair_hold == 1) { + mxc_dma_free(pair_params->input_dma_channel); + mxc_dma_free(pair_params->output_dma_channel); + mxc_free_dma_buf(pair_params); + asrc_release_pair(pair_params->index); + } + kfree(pair_params); + file->private_data = NULL; + return 0; +} + +/*! + * asrc interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int mxc_asrc_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long size; + int res = 0; + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) + return -ENOBUFS; + + vma->vm_flags &= ~VM_IO; + return res; +} + +static struct file_operations asrc_fops = { + .owner = THIS_MODULE, + .ioctl = asrc_ioctl, + .mmap = mxc_asrc_mmap, + .open = mxc_asrc_open, + .release = mxc_asrc_close, +}; + +static int asrc_read_proc_attr(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + unsigned long reg; + int len = 0; + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + + len += sprintf(page, "ANCA: %d\n", + (int)(reg & + (0xFFFFFFFF >> + (32 - mxc_asrc_data->channel_bits)))); + len += + sprintf(page + len, "ANCB: %d\n", + (int)((reg >> mxc_asrc_data-> + channel_bits) & (0xFFFFFFFF >> (32 - + mxc_asrc_data-> + channel_bits)))); + len += + sprintf(page + len, "ANCC: %d\n", + (int)((reg >> (mxc_asrc_data->channel_bits * 2)) & + (0xFFFFFFFF >> (32 - mxc_asrc_data->channel_bits)))); + + if (off > len) + return 0; + + *eof = (len <= count) ? 1 : 0; + *start = page + off; + + return min(count, len - (int)off); +} + +static int asrc_write_proc_attr(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + char buf[50]; + unsigned long reg; + int na, nb, nc; + int total; + if (count > 48) + return -EINVAL; + if (copy_from_user(buf, buffer, count)) { + pr_debug("Attr proc write, Failed to copy buffer from user\n"); + return -EFAULT; + } + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + sscanf(buf, "ANCA: %d\nANCB: %d\nANCC: %d", &na, &nb, &nc); + if (mxc_asrc_data->channel_bits > 3) + total = 10; + else + total = 5; + if ((na + nb + nc) != total) { + pr_info("Wrong ASRCNR settings\n"); + return -EFAULT; + } + reg = na | (nb << mxc_asrc_data-> + channel_bits) | (nc << (mxc_asrc_data->channel_bits * 2)); + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + + return count; +} + +static void asrc_proc_create(void) +{ + struct proc_dir_entry *proc_attr; + proc_asrc = proc_mkdir(ASRC_PROC_PATH, NULL); + if (proc_asrc) { + proc_attr = create_proc_entry("ChSettings", + S_IFREG | S_IRUGO | + S_IWUSR, proc_asrc); + if (proc_attr) { + proc_attr->read_proc = asrc_read_proc_attr; + proc_attr->write_proc = asrc_write_proc_attr; + proc_attr->size = 48; + proc_attr->uid = proc_attr->gid = 0; + } else { + remove_proc_entry(ASRC_PROC_PATH, NULL); + pr_info("Failed to create proc attribute entry \n"); + } + } else { + pr_info("ASRC: Failed to create proc entry %s\n", + ASRC_PROC_PATH); + } +} + +/*! + * Entry point for the asrc device + * + * @param pdev Pionter to the registered platform device + * @return Error code indicating success or failure + */ +static int mxc_asrc_probe(struct platform_device *pdev) +{ + int err = 0; + struct resource *res; + struct device *temp_class; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + g_asrc_data = kzalloc(sizeof(struct asrc_data), GFP_KERNEL); + + if (g_asrc_data == NULL) { + pr_info("Failed to allocate g_asrc_data\n"); + return -ENOMEM; + } + + g_asrc_data->asrc_pair[0].chn_max = 2; + g_asrc_data->asrc_pair[1].chn_max = 2; + g_asrc_data->asrc_pair[2].chn_max = 6; + g_asrc_data->asrc_pair[0].overload_error = 0; + g_asrc_data->asrc_pair[1].overload_error = 0; + g_asrc_data->asrc_pair[2].overload_error = 0; + + asrc_major = register_chrdev(asrc_major, "mxc_asrc", &asrc_fops); + if (asrc_major < 0) { + pr_info("Unable to register asrc device\n"); + err = -EBUSY; + goto error; + } + + asrc_class = class_create(THIS_MODULE, "mxc_asrc"); + if (IS_ERR(asrc_class)) { + err = PTR_ERR(asrc_class); + goto err_out_chrdev; + } + + temp_class = device_create(asrc_class, NULL, MKDEV(asrc_major, 0), + NULL, "mxc_asrc"); + if (IS_ERR(temp_class)) { + err = PTR_ERR(temp_class); + goto err_out_class; + } + + asrc_vrt_base_addr = + (unsigned long)ioremap(res->start, res->end - res->start + 1); + + mxc_asrc_data = + (struct mxc_asrc_platform_data *)pdev->dev.platform_data; + clk_enable(mxc_asrc_data->asrc_core_clk); + + asrc_proc_create(); + err = mxc_init_asrc(); + if (err < 0) + goto err_out_class; + + goto out; + + err_out_class: + clk_disable(mxc_asrc_data->asrc_core_clk); + device_destroy(asrc_class, MKDEV(asrc_major, 0)); + class_destroy(asrc_class); + err_out_chrdev: + unregister_chrdev(asrc_major, "mxc_asrc"); + error: + kfree(g_asrc_data); + out: + pr_info("mxc_asrc registered\n"); + return err; +} + +/*! + * Exit asrc device + * + * @param pdev Pionter to the registered platform device + * @return Error code indicating success or failure + */ +static int mxc_asrc_remove(struct platform_device *pdev) +{ + free_irq(MXC_INT_ASRC, NULL); + kfree(g_asrc_data); + clk_disable(mxc_asrc_data->asrc_core_clk); + mxc_asrc_data = NULL; + iounmap((unsigned long __iomem *)asrc_vrt_base_addr); + remove_proc_entry("ChSettings", proc_asrc); + remove_proc_entry(ASRC_PROC_PATH, NULL); + device_destroy(asrc_class, MKDEV(asrc_major, 0)); + class_destroy(asrc_class); + unregister_chrdev(asrc_major, "mxc_asrc"); + return 0; +} + +/*! mxc asrc driver definition + * + */ +static struct platform_driver mxc_asrc_driver = { + .driver = { + .name = "mxc_asrc", + }, + .probe = mxc_asrc_probe, + .remove = mxc_asrc_remove, +}; + +/*! + * Register asrc driver + * + */ +static __init int asrc_init(void) +{ + int ret; + ret = platform_driver_register(&mxc_asrc_driver); + return ret; +} + +/*! + * Exit and free the asrc data + * + */ static void __exit asrc_exit(void) +{ + platform_driver_unregister(&mxc_asrc_driver); + return; +} + +module_init(asrc_init); +module_exit(asrc_exit); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Asynchronous Sample Rate Converter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/bt/Kconfig b/drivers/mxc/bt/Kconfig new file mode 100644 index 000000000000..9dbfbe57a14f --- /dev/null +++ b/drivers/mxc/bt/Kconfig @@ -0,0 +1,13 @@ +# +# Bluetooth configuration +# + +menu "MXC Bluetooth support" + +config MXC_BLUETOOTH + tristate "MXC Bluetooth support" + depends on MACH_MX31_3DS || MACH_MX35_3DS || MACH_MX37_3DS || MACH_MX51_3DS + ---help--- + Say Y to get the third party Bluetooth service. + +endmenu diff --git a/drivers/mxc/bt/Makefile b/drivers/mxc/bt/Makefile new file mode 100644 index 000000000000..91bc4cff380e --- /dev/null +++ b/drivers/mxc/bt/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for the kernel Bluetooth power-on/reset +# +obj-$(CONFIG_MXC_BLUETOOTH) += mxc_bt.o diff --git a/drivers/mxc/bt/mxc_bt.c b/drivers/mxc/bt/mxc_bt.c new file mode 100644 index 000000000000..ae08b379ef70 --- /dev/null +++ b/drivers/mxc/bt/mxc_bt.c @@ -0,0 +1,128 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_bt.c + * + * @brief MXC Thirty party Bluetooth + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/regulator/consumer.h> +#include <mach/hardware.h> + +static struct regulator *bt_vdd; +static struct regulator *bt_vdd_parent; +static struct regulator *bt_vusb; +static struct regulator *bt_vusb_parent; + +/*! + * This function poweron the bluetooth hardware module + * + * @param pdev Pointer to the platform device + * @return 0 on success, -1 otherwise. + */ +static int mxc_bt_probe(struct platform_device *pdev) +{ + struct mxc_bt_platform_data *platform_data; + platform_data = (struct mxc_bt_platform_data *)pdev->dev.platform_data; + if (platform_data->bt_vdd) { + bt_vdd = regulator_get(&pdev->dev, platform_data->bt_vdd); + regulator_enable(bt_vdd); + } + if (platform_data->bt_vdd_parent) { + bt_vdd_parent = + regulator_get(&pdev->dev, platform_data->bt_vdd_parent); + regulator_enable(bt_vdd_parent); + } + if (platform_data->bt_vusb) { + bt_vusb = regulator_get(&pdev->dev, platform_data->bt_vusb); + regulator_enable(bt_vusb); + } + if (platform_data->bt_vusb_parent) { + bt_vusb_parent = + regulator_get(&pdev->dev, platform_data->bt_vusb_parent); + regulator_enable(bt_vusb_parent); + } + + if (platform_data->bt_reset != NULL) + platform_data->bt_reset(); + return 0; + +} + +/*! + * This function poweroff the bluetooth hardware module + * + * @param pdev Pointer to the platform device + * @return 0 on success, -1 otherwise. + */ +static int mxc_bt_remove(struct platform_device *pdev) +{ + struct mxc_bt_platform_data *platform_data; + platform_data = (struct mxc_bt_platform_data *)pdev->dev.platform_data; + if (bt_vdd) { + regulator_disable(bt_vdd); + regulator_put(bt_vdd); + } + if (bt_vdd_parent) { + regulator_disable(bt_vdd_parent); + regulator_put(bt_vdd_parent); + } + if (bt_vusb) { + regulator_disable(bt_vusb); + regulator_put(bt_vusb); + } + if (bt_vusb_parent) { + regulator_disable(bt_vusb_parent); + regulator_put(bt_vusb_parent); + } + return 0; + +} + +static struct platform_driver bluetooth_driver = { + .driver = { + .name = "mxc_bt", + }, + .probe = mxc_bt_probe, + .remove = mxc_bt_remove, +}; + +/*! + * Register bluetooth driver module + * + */ +static __init int bluetooth_init(void) +{ + return platform_driver_register(&bluetooth_driver); +} + +/*! + * Exit and free the bluetooth module + * + */ +static void __exit bluetooth_exit(void) +{ + platform_driver_unregister(&bluetooth_driver); +} + +module_init(bluetooth_init); +module_exit(bluetooth_exit); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Thirty party Bluetooth"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/dam/Kconfig b/drivers/mxc/dam/Kconfig new file mode 100644 index 000000000000..7b4bee92f648 --- /dev/null +++ b/drivers/mxc/dam/Kconfig @@ -0,0 +1,13 @@ +# +# DAM API configuration +# + +menu "MXC Digital Audio Multiplexer support" + +config MXC_DAM + tristate "DAM support" + depends on ARCH_MXC + ---help--- + Say Y to get the Digital Audio Multiplexer services API available on MXC platform. + +endmenu diff --git a/drivers/mxc/dam/Makefile b/drivers/mxc/dam/Makefile new file mode 100644 index 000000000000..b5afdc143dfa --- /dev/null +++ b/drivers/mxc/dam/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the kernel Digital Audio MUX (DAM) device driver. +# + +ifeq ($(CONFIG_ARCH_MX27),y) + obj-$(CONFIG_MXC_DAM) += dam_v1.o +else + obj-$(CONFIG_MXC_DAM) += dam.o +endif diff --git a/drivers/mxc/dam/dam.c b/drivers/mxc/dam/dam.c new file mode 100644 index 000000000000..532b02f65bb2 --- /dev/null +++ b/drivers/mxc/dam/dam.c @@ -0,0 +1,427 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dam.c + * @brief This is the brief documentation for this dam.c file. + * + * This file contains the implementation of the DAM driver main services + * + * @ingroup DAM + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <asm/uaccess.h> +#include "dam.h" + +/*! + * This include to define bool type, false and true definitions. + */ +#include <mach/hardware.h> + +#define ModifyRegister32(a,b,c) (c = ( ( (c)&(~(a)) ) | (b) )) + +#define DAM_VIRT_BASE_ADDR IO_ADDRESS(AUDMUX_BASE_ADDR) + +#ifndef _reg_DAM_PTCR1 +#define _reg_DAM_PTCR1 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x00))) +#endif + +#ifndef _reg_DAM_PDCR1 +#define _reg_DAM_PDCR1 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x04))) +#endif + +#ifndef _reg_DAM_PTCR2 +#define _reg_DAM_PTCR2 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x08))) +#endif + +#ifndef _reg_DAM_PDCR2 +#define _reg_DAM_PDCR2 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x0C))) +#endif + +#ifndef _reg_DAM_PTCR3 +#define _reg_DAM_PTCR3 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x10))) +#endif + +#ifndef _reg_DAM_PDCR3 +#define _reg_DAM_PDCR3 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x14))) +#endif + +#ifndef _reg_DAM_PTCR4 +#define _reg_DAM_PTCR4 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x18))) +#endif + +#ifndef _reg_DAM_PDCR4 +#define _reg_DAM_PDCR4 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x1C))) +#endif + +#ifndef _reg_DAM_PTCR5 +#define _reg_DAM_PTCR5 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x20))) +#endif + +#ifndef _reg_DAM_PDCR5 +#define _reg_DAM_PDCR5 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x24))) +#endif + +#ifndef _reg_DAM_PTCR6 +#define _reg_DAM_PTCR6 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x28))) +#endif + +#ifndef _reg_DAM_PDCR6 +#define _reg_DAM_PDCR6 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x2C))) +#endif + +#ifndef _reg_DAM_PTCR7 +#define _reg_DAM_PTCR7 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x30))) +#endif + +#ifndef _reg_DAM_PDCR7 +#define _reg_DAM_PDCR7 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x34))) +#endif + +#ifndef _reg_DAM_CNMCR +#define _reg_DAM_CNMCR (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x38))) +#endif + +#ifndef _reg_DAM_PTCR +#define _reg_DAM_PTCR(a) (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + a*8))) +#endif + +#ifndef _reg_DAM_PDCR +#define _reg_DAM_PDCR(a) (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 4 + a*8))) +#endif + +/*! + * PTCR Registers bit shift definitions + */ +#define dam_synchronous_mode_shift 11 +#define dam_receive_clock_select_shift 12 +#define dam_receive_clock_direction_shift 16 +#define dam_receive_frame_sync_select_shift 17 +#define dam_receive_frame_sync_direction_shift 21 +#define dam_transmit_clock_select_shift 22 +#define dam_transmit_clock_direction_shift 26 +#define dam_transmit_frame_sync_select_shift 27 +#define dam_transmit_frame_sync_direction_shift 31 +#define dam_selection_mask 0xF + +/*! + * HPDCR Register bit shift definitions + */ +#define dam_internal_network_mode_shift 0 +#define dam_mode_shift 8 +#define dam_transmit_receive_switch_shift 12 +#define dam_receive_data_select_shift 13 + +/*! + * HPDCR Register bit masq definitions + */ +#define dam_mode_masq 0x03 +#define dam_internal_network_mode_mask 0xFF + +/*! + * CNMCR Register bit shift definitions + */ +#define dam_ce_bus_port_cntlow_shift 0 +#define dam_ce_bus_port_cnthigh_shift 8 +#define dam_ce_bus_port_clkpol_shift 16 +#define dam_ce_bus_port_fspol_shift 17 +#define dam_ce_bus_port_enable_shift 18 + +#define DAM_NAME "dam" + +EXPORT_SYMBOL(dam_select_mode); +EXPORT_SYMBOL(dam_select_RxClk_direction); +EXPORT_SYMBOL(dam_select_RxClk_source); +EXPORT_SYMBOL(dam_select_RxD_source); +EXPORT_SYMBOL(dam_select_RxFS_direction); +EXPORT_SYMBOL(dam_select_RxFS_source); +EXPORT_SYMBOL(dam_select_TxClk_direction); +EXPORT_SYMBOL(dam_select_TxClk_source); +EXPORT_SYMBOL(dam_select_TxFS_direction); +EXPORT_SYMBOL(dam_select_TxFS_source); +EXPORT_SYMBOL(dam_set_internal_network_mode_mask); +EXPORT_SYMBOL(dam_set_synchronous); +EXPORT_SYMBOL(dam_switch_Tx_Rx); +EXPORT_SYMBOL(dam_reset_register); + +/*! + * This function selects the operation mode of the port. + * + * @param port the DAM port to configure + * @param the_mode the operation mode of the port + * + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_select_mode(dam_port port, dam_mode the_mode) +{ + int result; + result = 0; + + ModifyRegister32(dam_mode_masq << dam_mode_shift, + the_mode << dam_mode_shift, _reg_DAM_PDCR(port)); + + return result; +} + +/*! + * This function controls Receive clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx clock signal direction + */ +void dam_select_RxClk_direction(dam_port port, signal_direction direction) +{ + ModifyRegister32(1 << dam_receive_clock_direction_shift, + direction << dam_receive_clock_direction_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function controls Receive clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_RxClk_source(dam_port p_config, + bool from_RxClk, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << dam_receive_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_receive_clock_select_shift, + _reg_DAM_PTCR(p_config)); +} + +/*! + * This function selects the source port for the RxD data. + * + * @param p_config the DAM port to configure + * @param p_source the source port + */ +void dam_select_RxD_source(dam_port p_config, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << dam_receive_data_select_shift, + p_source << dam_receive_data_select_shift, + _reg_DAM_PDCR(p_config)); +} + +/*! + * This function controls Receive Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx Frame Sync signal direction + */ +void dam_select_RxFS_direction(dam_port port, signal_direction direction) +{ + ModifyRegister32(1 << dam_receive_frame_sync_direction_shift, + direction << dam_receive_frame_sync_direction_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function controls Receive Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_RxFS_source(dam_port p_config, + bool from_RxFS, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << + dam_receive_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_receive_frame_sync_select_shift, + _reg_DAM_PTCR(p_config)); +} + +/*! + * This function controls Transmit clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx clock signal direction + */ +void dam_select_TxClk_direction(dam_port port, signal_direction direction) +{ + ModifyRegister32(1 << dam_transmit_clock_direction_shift, + direction << dam_transmit_clock_direction_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function controls Transmit clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_TxClk_source(dam_port p_config, + bool from_RxClk, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << dam_transmit_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_transmit_clock_select_shift, + _reg_DAM_PTCR(p_config)); +} + +/*! + * This function controls Transmit Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx Frame Sync signal direction + */ +void dam_select_TxFS_direction(dam_port port, signal_direction direction) +{ + ModifyRegister32(1 << dam_transmit_frame_sync_direction_shift, + direction << dam_transmit_frame_sync_direction_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function controls Transmit Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_TxFS_source(dam_port p_config, + bool from_RxFS, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << + dam_transmit_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_transmit_frame_sync_select_shift, + _reg_DAM_PTCR(p_config)); +} + +/*! + * This function sets a bit mask that selects the port from which of the RxD + * signals are to be ANDed together for internal network mode. + * Bit 6 represents RxD from Port7 and bit0 represents RxD from Port1. + * 1 excludes RxDn from ANDing. 0 includes RxDn for ANDing. + * + * @param port the DAM port to configure + * @param bit_mask the bit mask + * + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_set_internal_network_mode_mask(dam_port port, unsigned char bit_mask) +{ + int result; + result = 0; + + ModifyRegister32(dam_internal_network_mode_mask << + dam_internal_network_mode_shift, + bit_mask << dam_internal_network_mode_shift, + _reg_DAM_PDCR(port)); + + return result; +} + +/*! + * This function controls whether or not the port is in synchronous mode. + * When the synchronous mode is selected, the receive and the transmit sections + * use common clock and frame sync signals. + * When the synchronous mode is not selected, separate clock and frame sync + * signals are used for the transmit and the receive sections. + * The defaut value is the synchronous mode selected. + * + * @param port the DAM port to configure + * @param synchronous the state to assign + */ +void dam_set_synchronous(dam_port port, bool synchronous) +{ + ModifyRegister32(1 << dam_synchronous_mode_shift, + synchronous << dam_synchronous_mode_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function swaps the transmit and receive signals from (Da-TxD, Db-RxD) + * to (Da-RxD, Db-TxD). + * This default signal configuration is Da-TxD, Db-RxD. + * + * @param port the DAM port to configure + * @param value the switch state + */ +void dam_switch_Tx_Rx(dam_port port, bool value) +{ + ModifyRegister32(1 << dam_transmit_receive_switch_shift, + value << dam_transmit_receive_switch_shift, + _reg_DAM_PDCR(port)); +} + +/*! + * This function resets the two registers of the selected port. + * + * @param port the DAM port to reset + */ +void dam_reset_register(dam_port port) +{ + ModifyRegister32(0xFFFFFFFF, 0x00000000, _reg_DAM_PTCR(port)); + ModifyRegister32(0xFFFFFFFF, 0x00000000, _reg_DAM_PDCR(port)); +} + +/*! + * This function implements the init function of the DAM device. + * This function is called when the module is loaded. + * + * @return This function returns 0. + */ +static int __init dam_init(void) +{ + return 0; +} + +/*! + * This function implements the exit function of the SPI device. + * This function is called when the module is unloaded. + * + */ +static void __exit dam_exit(void) +{ +} + +module_init(dam_init); +module_exit(dam_exit); + +MODULE_DESCRIPTION("DAM char device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/dam/dam.h b/drivers/mxc/dam/dam.h new file mode 100644 index 000000000000..cb9ead53f689 --- /dev/null +++ b/drivers/mxc/dam/dam.h @@ -0,0 +1,258 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @defgroup DAM Digital Audio Multiplexer (AUDMUX) Driver + */ + + /*! + * @file dam.h + * @brief This is the brief documentation for this dam.h file. + * + * This header file contains DAM driver functions prototypes. + * + * @ingroup DAM + */ + +#ifndef __MXC_DAM_H__ +#define __MXC_DAM_H__ + +/*! + * This enumeration describes the Digital Audio Multiplexer mode. + */ +typedef enum { + + /*! + * Normal mode + */ + normal_mode = 0, + + /*! + * Internal network mode + */ + internal_network_mode = 1, + + /*! + * CE bus network mode + */ + CE_bus_network_mode = 2 +} dam_mode; + +/*! + * This enumeration describes the port. + */ +typedef enum { + + /*! + * The port 1 + */ + port_1 = 0, + + /*! + * The port 2 + */ + port_2 = 1, + + /*! + * The port 3 + */ + port_3 = 2, + + /*! + * The port 4 + */ + port_4 = 3, + + /*! + * The port 5 + */ + port_5 = 4, + + /*! + * The port 6 + */ + port_6 = 5, + + /*! + * The port 7 + */ + port_7 = 6 +} dam_port; + +/*! + * This enumeration describes the signal direction. + */ +typedef enum { + + /*! + * Signal In + */ + signal_in = 0, + + /*! + * Signal Out + */ + signal_out = 1 +} signal_direction; + +/*! + * Test purpose definition + */ +#define TEST_DAM 1 + +#ifdef TEST_DAM + +#define DAM_IOCTL 0x55 +#define DAM_CONFIG_SSI1_MC13783 _IOWR(DAM_IOCTL, 1, int) +#define DAM_CONFIG_SSI2_MC13783 _IOWR(DAM_IOCTL, 2, int) +#define DAM_CONFIG_SSI_NETWORK_MODE_MC13783 _IOWR(DAM_IOCTL, 3, int) +#endif + +/*! + * This function selects the operation mode of the port. + * + * @param port the DAM port to configure + * @param the_mode the operation mode of the port + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_select_mode(dam_port port, dam_mode the_mode); + +/*! + * This function controls Receive clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx clock signal direction + */ +void dam_select_RxClk_direction(dam_port port, signal_direction direction); + +/*! + * This function controls Receive clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_RxClk_source(dam_port p_config, bool from_RxClk, + dam_port p_source); + +/*! + * This function selects the source port for the RxD data. + * + * @param p_config the DAM port to configure + * @param p_source the source port + */ +void dam_select_RxD_source(dam_port p_config, dam_port p_source); + +/*! + * This function controls Receive Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx Frame Sync signal direction + */ +void dam_select_RxFS_direction(dam_port port, signal_direction direction); + +/*! + * This function controls Receive Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_RxFS_source(dam_port p_config, bool from_RxFS, + dam_port p_source); + +/*! + * This function controls Transmit clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx clock signal direction + */ +void dam_select_TxClk_direction(dam_port port, signal_direction direction); + +/*! + * This function controls Transmit clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_TxClk_source(dam_port p_config, bool from_RxClk, + dam_port p_source); + +/*! + * This function controls Transmit Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx Frame Sync signal direction + */ +void dam_select_TxFS_direction(dam_port port, signal_direction direction); + +/*! + * This function controls Transmit Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_TxFS_source(dam_port p_config, bool from_RxFS, + dam_port p_source); + +/*! + * This function sets a bit mask that selects the port from which of + * the RxD signals are to be ANDed together for internal network mode. + * Bit 6 represents RxD from Port7 and bit0 represents RxD from Port1. + * 1 excludes RxDn from ANDing. 0 includes RxDn for ANDing. + * + * @param port the DAM port to configure + * @param bit_mask the bit mask + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_set_internal_network_mode_mask(dam_port port, unsigned char bit_mask); + +/*! + * This function controls whether or not the port is in synchronous mode. + * When the synchronous mode is selected, the receive and the transmit sections + * use common clock and frame sync signals. + * When the synchronous mode is not selected, separate clock and frame sync + * signals are used for the transmit and the receive sections. + * The defaut value is the synchronous mode selected. + * + * @param port the DAM port to configure + * @param synchronous the state to assign + */ +void dam_set_synchronous(dam_port port, bool synchronous); + +/*! + * This function swaps the transmit and receive signals from (Da-TxD, Db-RxD) to + * (Da-RxD, Db-TxD). + * This default signal configuration is Da-TxD, Db-RxD. + * + * @param port the DAM port to configure + * @param value the switch state + */ +void dam_switch_Tx_Rx(dam_port port, bool value); + +/*! + * This function resets the two registers of the selected port. + * + * @param port the DAM port to reset + */ +void dam_reset_register(dam_port port); + +#endif diff --git a/drivers/mxc/dam/dam_v1.c b/drivers/mxc/dam/dam_v1.c new file mode 100644 index 000000000000..0fc88bb98a38 --- /dev/null +++ b/drivers/mxc/dam/dam_v1.c @@ -0,0 +1,617 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dam_v1.c + * @brief This is the brief documentation for this dam_v1.c file. + * + * This file contains the implementation of the DAM driver main services + * + * @ingroup DAM + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <asm/uaccess.h> +#include "dam.h" + +/*! + * This include to define bool type, false and true definitions. + */ +#include <mach/hardware.h> + +#define DAM_VIRT_BASE_ADDR IO_ADDRESS(AUDMUX_BASE_ADDR) + +#define ModifyRegister32(a,b,c) do{\ + __raw_writel( ((__raw_readl(c)) & (~(a))) | (b),(c) );\ +}while(0) + +#ifndef _reg_DAM_HPCR1 +#define _reg_DAM_HPCR1 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x00))) +#endif + +#ifndef _reg_DAM_HPCR2 +#define _reg_DAM_HPCR2 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x04))) +#endif + +#ifndef _reg_DAM_HPCR3 +#define _reg_DAM_HPCR3 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x08))) +#endif + +#ifndef _reg_DAM_PPCR1 +#define _reg_DAM_PPCR1 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x10))) +#endif + +#ifndef _reg_DAM_PPCR2 +#define _reg_DAM_PPCR2 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x14))) +#endif + +#ifndef _reg_DAM_PPCR3 +#define _reg_DAM_PPCR3 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x1c))) +#endif + +#ifndef _reg_DAM_HPCR +#define _reg_DAM_HPCR(a) ((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + (a)*4)) +#endif + +#ifndef _reg_DAM_PPCR +#define _reg_DAM_PPCR(a) ((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x0c + (0x04 << (a-3)) )) +#endif + +/*! + * HPCR/PPCR Registers bit shift definitions + */ +#define dam_transmit_frame_sync_direction_shift 31 +#define dam_transmit_clock_direction_shift 30 +#define dam_transmit_frame_sync_select_shift 26 +#define dam_transmit_clock_select_shift 26 +#define dam_receive_frame_sync_direction_shift 25 +#define dam_receive_clock_direction_shift 24 +#define dam_receive_clock_select_shift 20 +#define dam_receive_frame_sync_select_shift 20 + +#define dam_receive_data_select_shift 13 +#define dam_synchronous_mode_shift 12 + +#define dam_transmit_receive_switch_shift 10 + +#define dam_mode_shift 8 +#define dam_internal_network_mode_shift 0 + +/*! + * HPCR/PPCR Register bit masq definitions + */ +//#define dam_selection_mask 0xF +#define dam_fs_selection_mask 0xF +#define dam_clk_selection_mask 0xF +#define dam_dat_selection_mask 0x7 +//#define dam_mode_masq 0x03 +#define dam_internal_network_mode_mask 0xFF + +/*! + * HPCR/PPCR Register reset value definitions + */ +#define dam_hpcr_default_value 0x00001000 +#define dam_ppcr_default_value 0x00001000 + +#define DAM_NAME "dam" +static struct class *mxc_dam_class; + +EXPORT_SYMBOL(dam_select_mode); +EXPORT_SYMBOL(dam_select_RxClk_direction); +EXPORT_SYMBOL(dam_select_RxClk_source); +EXPORT_SYMBOL(dam_select_RxD_source); +EXPORT_SYMBOL(dam_select_RxFS_direction); +EXPORT_SYMBOL(dam_select_RxFS_source); +EXPORT_SYMBOL(dam_select_TxClk_direction); +EXPORT_SYMBOL(dam_select_TxClk_source); +EXPORT_SYMBOL(dam_select_TxFS_direction); +EXPORT_SYMBOL(dam_select_TxFS_source); +EXPORT_SYMBOL(dam_set_internal_network_mode_mask); +EXPORT_SYMBOL(dam_set_synchronous); +EXPORT_SYMBOL(dam_switch_Tx_Rx); +EXPORT_SYMBOL(dam_reset_register); + +/*! + * DAM major + */ +#ifdef TEST_DAM +static int major_dam; + +typedef struct _mxc_cfg { + int reg; + int val; +} mxc_cfg; + +#endif + +/*! + * This function selects the operation mode of the port. + * + * @param port the DAM port to configure + * @param the_mode the operation mode of the port + * + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_select_mode(dam_port port, dam_mode the_mode) +{ + int result; + result = 0; + + if (port >= 3) + the_mode = normal_mode; + ModifyRegister32(1 << dam_mode_shift, + the_mode << dam_mode_shift, _reg_DAM_HPCR(port)); + + return result; +} + +/*! + * This function controls Receive clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx clock signal direction + */ +void dam_select_RxClk_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_receive_clock_direction_shift, + direction << dam_receive_clock_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_receive_clock_direction_shift, + direction << dam_receive_clock_direction_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function controls Receive clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_RxClk_source(dam_port p_config, + bool from_RxClk, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_clk_selection_mask << + dam_receive_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_receive_clock_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_clk_selection_mask << + dam_receive_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_receive_clock_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function selects the source port for the RxD data. + * + * @param p_config the DAM port to configure + * @param p_source the source port + */ +void dam_select_RxD_source(dam_port p_config, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_dat_selection_mask << + dam_receive_data_select_shift, + p_source << dam_receive_data_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_dat_selection_mask << + dam_receive_data_select_shift, + p_source << dam_receive_data_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function controls Receive Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx Frame Sync signal direction + */ +void dam_select_RxFS_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_receive_frame_sync_direction_shift, + direction << + dam_receive_frame_sync_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_receive_frame_sync_direction_shift, + direction << + dam_receive_frame_sync_direction_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function controls Receive Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_RxFS_source(dam_port p_config, + bool from_RxFS, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_fs_selection_mask << + dam_receive_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_receive_frame_sync_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_fs_selection_mask << + dam_receive_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_receive_frame_sync_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function controls Transmit clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx clock signal direction + */ +void dam_select_TxClk_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_transmit_clock_direction_shift, + direction << + dam_transmit_clock_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_transmit_clock_direction_shift, + direction << + dam_transmit_clock_direction_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function controls Transmit clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_TxClk_source(dam_port p_config, + bool from_RxClk, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_clk_selection_mask << + dam_transmit_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_transmit_clock_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_clk_selection_mask << + dam_transmit_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_transmit_clock_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function controls Transmit Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx Frame Sync signal direction + */ +void dam_select_TxFS_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_transmit_frame_sync_direction_shift, + direction << + dam_transmit_frame_sync_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_transmit_frame_sync_direction_shift, + direction << + dam_transmit_frame_sync_direction_shift, + _reg_DAM_HPCR(port)); + } + return; +} + +/*! + * This function controls Transmit Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_TxFS_source(dam_port p_config, + bool from_RxFS, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_fs_selection_mask << + dam_transmit_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_transmit_frame_sync_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_fs_selection_mask << + dam_transmit_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_transmit_frame_sync_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function sets a bit mask that selects the port from which of the RxD + * signals are to be ANDed together for internal network mode. + * Bit 6 represents RxD from Port7 and bit0 represents RxD from Port1. + * 1 excludes RxDn from ANDing. 0 includes RxDn for ANDing. + * + * @param port the DAM port to configure + * @param bit_mask the bit mask + * + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_set_internal_network_mode_mask(dam_port port, unsigned char bit_mask) +{ + int result; + result = 0; + + ModifyRegister32(dam_internal_network_mode_mask << + dam_internal_network_mode_shift, + bit_mask << dam_internal_network_mode_shift, + _reg_DAM_HPCR(port)); + return result; +} + +/*! + * This function controls whether or not the port is in synchronous mode. + * When the synchronous mode is selected, the receive and the transmit sections + * use common clock and frame sync signals. + * When the synchronous mode is not selected, separate clock and frame sync + * signals are used for the transmit and the receive sections. + * The defaut value is the synchronous mode selected. + * + * @param port the DAM port to configure + * @param synchronous the state to assign + */ +void dam_set_synchronous(dam_port port, bool synchronous) +{ + if (port < 3) { + ModifyRegister32(1 << dam_synchronous_mode_shift, + synchronous << dam_synchronous_mode_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_synchronous_mode_shift, + synchronous << dam_synchronous_mode_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function swaps the transmit and receive signals from (Da-TxD, Db-RxD) + * to (Da-RxD, Db-TxD). + * This default signal configuration is Da-TxD, Db-RxD. + * + * @param port the DAM port to configure + * @param value the switch state + */ +void dam_switch_Tx_Rx(dam_port port, bool value) +{ + if (port < 3) { + ModifyRegister32(1 << dam_transmit_receive_switch_shift, + value << dam_transmit_receive_switch_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_transmit_receive_switch_shift, + value << dam_transmit_receive_switch_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function resets the two registers of the selected port. + * + * @param port the DAM port to reset + */ +void dam_reset_register(dam_port port) +{ + if (port < 3) { + ModifyRegister32(0xFFFFFFFF, dam_hpcr_default_value, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(0xFFFFFFFF, dam_ppcr_default_value, + _reg_DAM_PPCR(port)); + } + return; +} + +#ifdef TEST_DAM + +/*! + * This function implements IOCTL controls on a DAM device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter :\n + * DAM_CONFIG_SSI1:\n + * data from port 1 to port 4, clock and FS from port 1 (SSI1)\n + * DAM_CONFIG_SSI2:\n + * data from port 2 to port 5, clock and FS from port 2 (SSI2)\n + * DAM_CONFIG_SSI_NETWORK_MODE:\n + * network mode for mix digital with data from port 1 to port4,\n + * data from port 2 to port 4, clock and FS from port 1 (SSI1) + * + * @return This function returns 0 if successful. + */ +static int dam_ioctl(struct inode *inode, + struct file *file, unsigned int cmd, unsigned long arg) +{ + return 0; +} + +/*! + * This function implements the open method on a DAM device. + * + * @param inode pointer on the node + * @param file pointer on the file + * + * @return This function returns 0. + */ +static int dam_open(struct inode *inode, struct file *file) +{ + /* DBG_PRINTK("ssi : dam_open()\n"); */ + return 0; +} + +/*! + * This function implements the release method on a DAM device. + * + * @param inode pointer on the node + * @param file pointer on the file + * + * @return This function returns 0. + */ +static int dam_free(struct inode *inode, struct file *file) +{ + /* DBG_PRINTK("ssi : dam_free()\n"); */ + return 0; +} + +/*! + * This structure defines file operations for a DAM device. + */ +static struct file_operations dam_fops = { + + /*! + * the owner + */ + .owner = THIS_MODULE, + + /*! + * the ioctl operation + */ + .ioctl = dam_ioctl, + + /*! + * the open operation + */ + .open = dam_open, + + /*! + * the release operation + */ + .release = dam_free, +}; + +#endif + +/*! + * This function implements the init function of the DAM device. + * This function is called when the module is loaded. + * + * @return This function returns 0. + */ +static int __init dam_init(void) +{ +#ifdef TEST_DAM + struct device *temp_class; + printk(KERN_DEBUG "dam : dam_init(void) \n"); + + major_dam = register_chrdev(0, DAM_NAME, &dam_fops); + if (major_dam < 0) { + printk(KERN_WARNING "Unable to get a major for dam"); + return major_dam; + } + + mxc_dam_class = class_create(THIS_MODULE, DAM_NAME); + if (IS_ERR(mxc_dam_class)) { + goto err_out; + } + + temp_class = device_create(mxc_dam_class, NULL, + MKDEV(major_dam, 0), NULL, DAM_NAME); + if (IS_ERR(temp_class)) { + goto err_out; + } +#endif + return 0; + + err_out: + printk(KERN_ERR "Error creating dam class device.\n"); + device_destroy(mxc_dam_class, MKDEV(major_dam, 0)); + class_destroy(mxc_dam_class); + unregister_chrdev(major_dam, DAM_NAME); + return -1; +} + +/*! + * This function implements the exit function of the SPI device. + * This function is called when the module is unloaded. + * + */ +static void __exit dam_exit(void) +{ +#ifdef TEST_DAM + device_destroy(mxc_dam_class, MKDEV(major_dam, 0)); + class_destroy(mxc_dam_class); + unregister_chrdev(major_dam, DAM_NAME); + printk(KERN_DEBUG "dam : successfully unloaded\n"); +#endif +} + +module_init(dam_init); +module_exit(dam_exit); + +MODULE_DESCRIPTION("DAM char device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/gps_ioctrl/Kconfig b/drivers/mxc/gps_ioctrl/Kconfig new file mode 100644 index 000000000000..0a85d1636dd7 --- /dev/null +++ b/drivers/mxc/gps_ioctrl/Kconfig @@ -0,0 +1,13 @@ +# +# BROADCOM GPS configuration +# + +menu "Broadcom GPS ioctrl support" + +config GPS_IOCTRL + tristate "GPS ioctrl support" + depends on MACH_MX31_3DS || MACH_MX35_3DS || MACH_MX37_3DS || MACH_MX51_3DS + ---help--- + Say Y to enable Broadcom GPS ioctrl on MXC platform. + +endmenu diff --git a/drivers/mxc/gps_ioctrl/Makefile b/drivers/mxc/gps_ioctrl/Makefile new file mode 100644 index 000000000000..c52d7c9f151c --- /dev/null +++ b/drivers/mxc/gps_ioctrl/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the GPIO device driver module. +# +obj-$(CONFIG_GPS_IOCTRL) += gps_gpiodrv.o +gps_gpiodrv-objs := agpsgpiodev.o diff --git a/drivers/mxc/gps_ioctrl/agpsgpiodev.c b/drivers/mxc/gps_ioctrl/agpsgpiodev.c new file mode 100644 index 000000000000..2f64274bf37c --- /dev/null +++ b/drivers/mxc/gps_ioctrl/agpsgpiodev.c @@ -0,0 +1,331 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file agpsgpiodev.c + * + * @brief Main file for GPIO kernel module. Contains driver entry/exit + * + */ + +#include <linux/module.h> +#include <linux/fs.h> /* Async notification */ +#include <linux/uaccess.h> /* for get_user, put_user, access_ok */ +#include <linux/sched.h> /* jiffies */ +#include <linux/poll.h> +#include <linux/device.h> +#include <linux/regulator/consumer.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/cdev.h> +#include <mach/hardware.h> +#include "agpsgpiodev.h" + +extern void gpio_gps_active(void); +extern void gpio_gps_inactive(void); +extern int gpio_gps_access(int para); + +struct mxc_gps_platform_data *mxc_gps_ioctrl_data; +static int Device_Open; /* Only allow a single user of this device */ +static struct cdev mxc_gps_cdev; +static dev_t agps_gpio_dev; +static struct class *gps_class; +static struct device *gps_class_dev; + +/* Write GPIO from user space */ +static int ioctl_writegpio(int arg) +{ + + /* Bit 0 of arg identifies the GPIO pin to write: + 0 = GPS_RESET_GPIO, 1 = GPS_POWER_GPIO. + Bit 1 of arg identifies the value to write (0 or 1). */ + + /* Bit 2 should be 0 to show this access is write */ + return gpio_gps_access(arg & (~0x4)); +} + +/* Read GPIO from user space */ +static int ioctl_readgpio(int arg) +{ + /* Bit 0 of arg identifies the GPIO pin to read: + 0 = GPS_RESET_GPIO. 1 = GPS_POWER_GPIO + Bit 2 should be 1 to show this access is read */ + return gpio_gps_access(arg | 0x4); +} + +static int device_open(struct inode *inode, struct file *fp) +{ + /* We don't want to talk to two processes at the same time. */ + if (Device_Open) { + printk(KERN_DEBUG "device_open() - Returning EBUSY. \ + Device already open... \n"); + return -EBUSY; + } + Device_Open++; /* BUGBUG : Not protected! */ + try_module_get(THIS_MODULE); + + return 0; +} + +static int device_release(struct inode *inode, struct file *fp) +{ + /* We're now ready for our next caller */ + Device_Open--; + module_put(THIS_MODULE); + + return 0; +} + +static int device_ioctl(struct inode *inode, struct file *fp, + unsigned int cmd, unsigned long arg) +{ + int err = 0; + + /* Extract the type and number bitfields, and don't decode wrong cmds. + Return ENOTTY (inappropriate ioctl) before access_ok() */ + if (_IOC_TYPE(cmd) != MAJOR_NUM) { + printk(KERN_ERR + "device_ioctl() - Error! IOC_TYPE = %d. Expected %d\n", + _IOC_TYPE(cmd), MAJOR_NUM); + return -ENOTTY; + } + if (_IOC_NR(cmd) > IOCTL_MAXNUMBER) { + printk(KERN_ERR + "device_ioctl() - Error!" + "IOC_NR = %d greater than max supported(%d)\n", + _IOC_NR(cmd), IOCTL_MAXNUMBER); + return -ENOTTY; + } + + /* 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. I think this is primarily + for good coding practice. You can easily do any kind of R/W access + without these checks and IOCTL code can be implemented "randomly"! */ + 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) { + printk(KERN_ERR + "device_ioctl() - Error! User arg not valid" + "for selected access (R/W/RW). Cmd %d\n", + _IOC_TYPE(cmd)); + return -EFAULT; + } + + /* Note: Read and writing data to user buffer can be done using regular + pointer stuff but we may also use get_user() or put_user() */ + + /* Cmd and arg has been verified... */ + switch (cmd) { + case IOCTL_WRITEGPIO: + return ioctl_writegpio((int)arg); + case IOCTL_READGPIO: + return ioctl_readgpio((int)arg); + default: + printk(KERN_ERR "device_ioctl() - Invalid IOCTL (0x%x)\n", cmd); + return EINVAL; + } + return 0; +} + +struct file_operations Fops = { + .ioctl = device_ioctl, + .open = device_open, + .release = device_release, +}; + +/* Initialize the module - Register the character device */ +int init_chrdev(struct device *dev) +{ + int ret, gps_major; + + ret = alloc_chrdev_region(&agps_gpio_dev, 1, 1, "agps_gpio"); + gps_major = MAJOR(agps_gpio_dev); + if (ret < 0) { + dev_err(dev, "can't get major %d\n", gps_major); + goto err3; + } + + cdev_init(&mxc_gps_cdev, &Fops); + mxc_gps_cdev.owner = THIS_MODULE; + + ret = cdev_add(&mxc_gps_cdev, agps_gpio_dev, 1); + if (ret) { + dev_err(dev, "can't add cdev\n"); + goto err2; + } + + /* create class and device for udev information */ + gps_class = class_create(THIS_MODULE, "gps"); + if (IS_ERR(gps_class)) { + dev_err(dev, "failed to create gps class\n"); + ret = -ENOMEM; + goto err1; + } + + gps_class_dev = device_create(gps_class, NULL, MKDEV(gps_major, 1), NULL, + AGPSGPIO_DEVICE_FILE_NAME); + if (IS_ERR(gps_class_dev)) { + dev_err(dev, "failed to create gps gpio class device\n"); + ret = -ENOMEM; + goto err0; + } + + return 0; +err0: + class_destroy(gps_class); +err1: + cdev_del(&mxc_gps_cdev); +err2: + unregister_chrdev_region(agps_gpio_dev, 1); +err3: + return ret; +} + +/* Cleanup - unregister the appropriate file from /proc. */ +void cleanup_chrdev(void) +{ + /* destroy gps device class */ + device_destroy(gps_class, MKDEV(MAJOR(agps_gpio_dev), 1)); + class_destroy(gps_class); + + /* Unregister the device */ + cdev_del(&mxc_gps_cdev); + unregister_chrdev_region(agps_gpio_dev, 1); +} + +/*! + * This function initializes the driver in terms of memory of the soundcard + * and some basic HW clock settings. + * + * @return 0 on success, -1 otherwise. + */ +static int __init gps_ioctrl_probe(struct platform_device *pdev) +{ + struct regulator *gps_regu; + + mxc_gps_ioctrl_data = + (struct mxc_gps_platform_data *)pdev->dev.platform_data; + + /* open GPS GPO3 1v8 for GL gps support */ + if (mxc_gps_ioctrl_data->core_reg != NULL) { + mxc_gps_ioctrl_data->gps_regu_core = + regulator_get(&(pdev->dev), mxc_gps_ioctrl_data->core_reg); + gps_regu = mxc_gps_ioctrl_data->gps_regu_core; + if (!IS_ERR_VALUE((u32)gps_regu)) { + regulator_set_voltage(gps_regu, 1800000, 1800000); + regulator_enable(gps_regu); + } else { + return -1; + } + } + /* open GPS GPO1 2v8 for GL gps support */ + if (mxc_gps_ioctrl_data->analog_reg != NULL) { + mxc_gps_ioctrl_data->gps_regu_analog = + regulator_get(&(pdev->dev), + mxc_gps_ioctrl_data->analog_reg); + gps_regu = mxc_gps_ioctrl_data->gps_regu_analog; + if (!IS_ERR_VALUE((u32)gps_regu)) { + regulator_set_voltage(gps_regu, 2800000, 2800000); + regulator_enable(gps_regu); + } else { + return -1; + } + } + gpio_gps_active(); + + /* Register character device */ + init_chrdev(&(pdev->dev)); + return 0; +} + +static int gps_ioctrl_remove(struct platform_device *pdev) +{ + struct regulator *gps_regu; + + mxc_gps_ioctrl_data = + (struct mxc_gps_platform_data *)pdev->dev.platform_data; + + /* Character device cleanup.. */ + cleanup_chrdev(); + gpio_gps_inactive(); + + /* close GPS GPO3 1v8 for GL gps */ + gps_regu = mxc_gps_ioctrl_data->gps_regu_core; + if (mxc_gps_ioctrl_data->core_reg != NULL) { + regulator_disable(gps_regu); + regulator_put(gps_regu); + } + /* close GPS GPO1 2v8 for GL gps */ + gps_regu = mxc_gps_ioctrl_data->gps_regu_analog; + if (mxc_gps_ioctrl_data->analog_reg != NULL) { + regulator_disable(gps_regu); + regulator_put(gps_regu); + } + + return 0; +} + +static int gps_ioctrl_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* PowerEn toggle off */ + ioctl_writegpio(0x1); + return 0; +} + +static int gps_ioctrl_resume(struct platform_device *pdev) +{ + /* PowerEn pull up */ + ioctl_writegpio(0x3); + return 0; +} + +static struct platform_driver gps_ioctrl_driver = { + .probe = gps_ioctrl_probe, + .remove = gps_ioctrl_remove, + .suspend = gps_ioctrl_suspend, + .resume = gps_ioctrl_resume, + .driver = { + .name = "gps_ioctrl", + }, +}; + +/*! + * Entry point for GPS ioctrl module. + * + */ +static int __init gps_ioctrl_init(void) +{ + return platform_driver_register(&gps_ioctrl_driver); +} + +/*! + * unloading module. + * + */ +static void __exit gps_ioctrl_exit(void) +{ + platform_driver_unregister(&gps_ioctrl_driver); +} + +module_init(gps_ioctrl_init); +module_exit(gps_ioctrl_exit); +MODULE_DESCRIPTION("GPIO DEVICE DRIVER"); +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/gps_ioctrl/agpsgpiodev.h b/drivers/mxc/gps_ioctrl/agpsgpiodev.h new file mode 100644 index 000000000000..815890578f4a --- /dev/null +++ b/drivers/mxc/gps_ioctrl/agpsgpiodev.h @@ -0,0 +1,46 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file agpsgpiodev.h + * + * @brief head file of Simple character device interface for AGPS kernel module. + * + * @ingroup + */ + +#ifndef AGPSGPIODEV_H +#define AGPSGPIODEV_H + +#include <linux/ioctl.h> + +#define USE_BLOCKING /* Test driver with blocking calls */ +#undef USE_FASYNC /* Test driver with async notification */ + +/* The major device number. We can't rely on dynamic registration any more + because ioctls need to know it */ +#define MAJOR_NUM 100 + +#define IOCTL_WRITEGPIO _IOWR(MAJOR_NUM, 1, char *) +#define IOCTL_READGPIO _IOR(MAJOR_NUM, 2, char *) +#define IOCTL_MAXNUMBER 2 + +/* The name of the device file */ +#define AGPSGPIO_DEVICE_FILE_NAME "agpsgpio" + +/* Exported prototypes */ +int init_chrdev(struct device *dev); +void cleanup_chrdev(void); +void wakeup(void); + +#endif diff --git a/drivers/mxc/hmp4e/Kconfig b/drivers/mxc/hmp4e/Kconfig new file mode 100644 index 000000000000..fdd7dbc041ba --- /dev/null +++ b/drivers/mxc/hmp4e/Kconfig @@ -0,0 +1,24 @@ +# +# MPEG4 Encoder kernel module configuration +# + +menu "MXC MPEG4 Encoder Kernel module support" + +config MXC_HMP4E + tristate "MPEG4 Encoder support" + depends on ARCH_MXC + depends on !ARCH_MX27 + default y + ---help--- + Say Y to get the MPEG4 Encoder kernel module available on + MXC platform. + +config MXC_HMP4E_DEBUG + bool "MXC MPEG4 Debug messages" + depends on MXC_HMP4E != n + default n + ---help--- + Say Y here if you need the Encoder driver to print debug messages. + This is an option for developers, most people should say N here. + +endmenu diff --git a/drivers/mxc/hmp4e/Makefile b/drivers/mxc/hmp4e/Makefile new file mode 100644 index 000000000000..0efe11fbdbc8 --- /dev/null +++ b/drivers/mxc/hmp4e/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the MPEG4 Encoder kernel module. + +obj-$(CONFIG_MXC_HMP4E) += mxc_hmp4e.o + +ifeq ($(CONFIG_MXC_HMP4E_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/mxc/hmp4e/mxc_hmp4e.c b/drivers/mxc/hmp4e/mxc_hmp4e.c new file mode 100644 index 000000000000..38b84329d459 --- /dev/null +++ b/drivers/mxc/hmp4e/mxc_hmp4e.c @@ -0,0 +1,812 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * Encoder device driver (kernel module) + * + * Copyright (C) 2005 Hantro Products Oy. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> /* __init,__exit directives */ +#include <linux/mm.h> /* remap_page_range / remap_pfn_range */ +#include <linux/fs.h> /* for struct file_operations */ +#include <linux/errno.h> /* standard error codes */ +#include <linux/platform_device.h> /* for device registeration for PM */ +#include <linux/delay.h> /* for msleep_interruptible */ +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> /* for dma_alloc_consistent */ +#include <linux/clk.h> +#include <asm/uaccess.h> /* for ioctl __get_user, __put_user */ +#include <mach/hardware.h> +#include "mxc_hmp4e.h" /* MPEG4 encoder specific */ + +/* here's all the must remember stuff */ +typedef struct { + ulong buffaddr; + u32 buffsize; + ulong iobaseaddr; + u32 iosize; + volatile u32 *hwregs; + u32 irq; + u16 hwid_offset; + u16 intr_offset; + u16 busy_offset; + u16 type; /* Encoder type, CIF = 0, VGA = 1 */ + u16 clk_gate; + u16 busy_val; + struct fasync_struct *async_queue; +#ifdef CONFIG_PM + s32 suspend_state; + wait_queue_head_t power_queue; +#endif +} hmp4e_t; + +/* and this is our MAJOR; use 0 for dynamic allocation (recommended)*/ +static s32 hmp4e_major; + +static u32 hmp4e_phys; +static struct class *hmp4e_class; +static hmp4e_t hmp4e_data; + +/*! MPEG4 enc clock handle. */ +static struct clk *hmp4e_clk; + +/* + * avoid "enable_irq(x) unbalanced from ..." + * error messages from the kernel, since {ena,dis}able_irq() + * calls are stacked in kernel. + */ +static bool irq_enable = false; + +ulong base_port = MPEG4_ENC_BASE_ADDR; +u32 irq = MXC_INT_MPEG4_ENCODER; + +module_param(base_port, long, 000); +module_param(irq, int, 000); + +/*! + * These variables store the register values when HMP4E is in suspend mode. + */ +#ifdef CONFIG_PM +u32 io_regs[64]; +#endif + +static s32 hmp4e_map_buffer(struct file *filp, struct vm_area_struct *vma); +static s32 hmp4e_map_hwregs(struct file *filp, struct vm_area_struct *vma); +static void hmp4e_reset(hmp4e_t * dev); +irqreturn_t hmp4e_isr(s32 irq, void *dev_id); + +/*! + * This funtion is called to write h/w register. + * + * @param val value to be written into the register + * @param offset register offset + * + */ +static inline void hmp4e_write(u32 val, u32 offset) +{ + hmp4e_t *dev = &hmp4e_data; + __raw_writel(val, (dev->hwregs + offset)); +} + +/*! + * This funtion is called to read h/w register. + * + * @param offset register offset + * + * @return This function returns the value read from the register. + * + */ +static inline u32 hmp4e_read(u32 offset) +{ + hmp4e_t *dev = &hmp4e_data; + u32 val; + + val = __raw_readl(dev->hwregs + offset); + + return val; +} + +/*! + * The device's mmap method. The VFS has kindly prepared the process's + * vm_area_struct for us, so we examine this to see what was requested. + * + * @param filp pointer to struct file + * @param vma pointer to struct vma_area_struct + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_mmap(struct file *filp, struct vm_area_struct *vma) +{ + s32 result; + ulong offset = vma->vm_pgoff << PAGE_SHIFT; + + pr_debug("hmp4e_mmap: size = %lu off = 0x%08lx\n", + (unsigned long)(vma->vm_end - vma->vm_start), offset); + + if (offset == 0) { + result = hmp4e_map_buffer(filp, vma); + } else if (offset == hmp4e_data.iobaseaddr) { + result = hmp4e_map_hwregs(filp, vma); + } else { + pr_debug("hmp4e: mmap invalid value\n"); + result = -EINVAL; + } + + return result; +} + +/*! + * This funtion is called to handle ioctls. + * + * @param inode pointer to struct inode + * @param filp pointer to struct file + * @param cmd ioctl command + * @param arg user data + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_ioctl(struct inode *inode, struct file *filp, + u32 cmd, ulong arg) +{ + s32 err = 0, retval = 0; + ulong offset = 0; + hmp4e_t *dev = &hmp4e_data; + write_t bwrite; + +#ifdef CONFIG_PM + wait_event_interruptible(hmp4e_data.power_queue, + hmp4e_data.suspend_state == 0); +#endif + + /* + * extract the type and number bitfields, and don't decode + * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() + */ + if (_IOC_TYPE(cmd) != HMP4E_IOC_MAGIC) { + pr_debug("hmp4e: ioctl invalid magic\n"); + return -ENOTTY; + } + + if (_IOC_NR(cmd) > HMP4E_IOC_MAXNR) { + pr_debug("hmp4e: ioctl exceeds max ioctl\n"); + return -ENOTTY; + } + + /* + * 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 *)arg, _IOC_SIZE(cmd)); + } else if (_IOC_DIR(cmd) & _IOC_WRITE) { + err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); + } + + if (err) { + pr_debug("hmp4e: ioctl invalid direction\n"); + return -EFAULT; + } + + switch (cmd) { + case HMP4E_IOCHARDRESET: + break; + + case HMP4E_IOCGBUFBUSADDRESS: + retval = __put_user((ulong) hmp4e_phys, (u32 *) arg); + break; + + case HMP4E_IOCGBUFSIZE: + retval = __put_user(hmp4e_data.buffsize, (u32 *) arg); + break; + + case HMP4E_IOCSREGWRITE: + if (dev->type != 1) { /* This ioctl only for VGA */ + pr_debug("hmp4e: HMP4E_IOCSREGWRITE invalid\n"); + retval = -EINVAL; + break; + } + + retval = __copy_from_user(&bwrite, (u32 *) arg, + sizeof(write_t)); + + if (bwrite.offset <= hmp4e_data.iosize - 4) { + hmp4e_write(bwrite.data, (bwrite.offset / 4)); + } else { + pr_debug("hmp4e: HMP4E_IOCSREGWRITE failed\n"); + retval = -EFAULT; + } + break; + + case HMP4E_IOCXREGREAD: + if (dev->type != 1) { /* This ioctl only for VGA */ + pr_debug("hmp4e: HMP4E_IOCSREGWRITE invalid\n"); + retval = -EINVAL; + break; + } + + retval = __get_user(offset, (ulong *) arg); + if (offset <= hmp4e_data.iosize - 4) { + __put_user(hmp4e_read((offset / 4)), (ulong *) arg); + } else { + pr_debug("hmp4e: HMP4E_IOCXREGREAD failed\n"); + retval = -EFAULT; + } + break; + + case HMP4E_IOCGHWOFFSET: + __put_user(hmp4e_data.iobaseaddr, (ulong *) arg); + break; + + case HMP4E_IOCGHWIOSIZE: + __put_user(hmp4e_data.iosize, (u32 *) arg); + break; + + case HMP4E_IOC_CLI: + if (irq_enable == true) { + disable_irq(hmp4e_data.irq); + irq_enable = false; + } + break; + + case HMP4E_IOC_STI: + if (irq_enable == false) { + enable_irq(hmp4e_data.irq); + irq_enable = true; + } + break; + + default: + pr_debug("unknown case %x\n", cmd); + } + + return retval; +} + +/*! + * This funtion is called when the device is opened. + * + * @param inode pointer to struct inode + * @param filp pointer to struct file + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_open(struct inode *inode, struct file *filp) +{ + hmp4e_t *dev = &hmp4e_data; + + filp->private_data = (void *)dev; + + if (request_irq(dev->irq, hmp4e_isr, 0, "mxc_hmp4e", dev) != 0) { + pr_debug("hmp4e: request irq failed\n"); + return -EBUSY; + } + + if (irq_enable == false) { + irq_enable = true; + } + clk_enable(hmp4e_clk); + return 0; +} + +static s32 hmp4e_fasync(s32 fd, struct file *filp, s32 mode) +{ + hmp4e_t *dev = (hmp4e_t *) filp->private_data; + return fasync_helper(fd, filp, mode, &dev->async_queue); +} + +/*! + * This funtion is called when the device is closed. + * + * @param inode pointer to struct inode + * @param filp pointer to struct file + * + * @return This function returns 0. + * + */ +static s32 hmp4e_release(struct inode *inode, struct file *filp) +{ + hmp4e_t *dev = (hmp4e_t *) filp->private_data; + + /* this is necessary if user process exited asynchronously */ + if (irq_enable == true) { + disable_irq(dev->irq); + irq_enable = false; + } + + /* reset hardware */ + hmp4e_reset(&hmp4e_data); + + /* free the encoder IRQ */ + free_irq(dev->irq, (void *)dev); + + /* remove this filp from the asynchronusly notified filp's */ + hmp4e_fasync(-1, filp, 0); + clk_disable(hmp4e_clk); + return 0; +} + +/* VFS methods */ +static struct file_operations hmp4e_fops = { + .owner = THIS_MODULE, + .open = hmp4e_open, + .release = hmp4e_release, + .ioctl = hmp4e_ioctl, + .mmap = hmp4e_mmap, + .fasync = hmp4e_fasync, +}; + +/*! + * This funtion allocates physical contigous memory. + * + * @param size size of memory to be allocated + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_alloc(u32 size) +{ + hmp4e_data.buffsize = PAGE_ALIGN(size); + hmp4e_data.buffaddr = + (ulong) dma_alloc_coherent(NULL, hmp4e_data.buffsize, + (dma_addr_t *) & hmp4e_phys, + GFP_DMA | GFP_KERNEL); + + if (hmp4e_data.buffaddr == 0) { + printk(KERN_ERR "hmp4e: couldn't allocate data buffer\n"); + return -ENOMEM; + } + + memset((s8 *) hmp4e_data.buffaddr, 0, hmp4e_data.buffsize); + return 0; +} + +/*! + * This funtion frees the DMAed memory. + */ +static void hmp4e_free(void) +{ + if (hmp4e_data.buffaddr != 0) { + dma_free_coherent(NULL, hmp4e_data.buffsize, + (void *)hmp4e_data.buffaddr, hmp4e_phys); + hmp4e_data.buffaddr = 0; + } +} + +/*! + * This funtion maps the shared buffer in memory. + * + * @param filp pointer to struct file + * @param vma pointer to struct vm_area_struct + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_map_buffer(struct file *filp, struct vm_area_struct *vma) +{ + ulong phys; + ulong start = (u32) vma->vm_start; + ulong size = (u32) (vma->vm_end - vma->vm_start); + + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > hmp4e_data.buffsize) { + pr_debug("hmp4e: hmp4e_map_buffer, invalid size\n"); + return -EINVAL; + } + + vma->vm_flags |= VM_RESERVED | VM_IO; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + phys = hmp4e_phys; + + if (remap_pfn_range(vma, start, phys >> PAGE_SHIFT, size, + vma->vm_page_prot)) { + pr_debug("hmp4e: failed mmapping shared buffer\n"); + return -EAGAIN; + } + + return 0; +} + +/*! + * This funtion maps the h/w register space in memory. + * + * @param filp pointer to struct file + * @param vma pointer to struct vm_area_struct + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_map_hwregs(struct file *filp, struct vm_area_struct *vma) +{ + ulong phys; + ulong start = (unsigned long)vma->vm_start; + ulong size = (unsigned long)(vma->vm_end - vma->vm_start); + + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > PAGE_SIZE) { + pr_debug("hmp4e: hmp4e_map_hwregs, invalid size\n"); + return -EINVAL; + } + + vma->vm_flags |= VM_RESERVED | VM_IO; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Remember this won't work for vmalloc()d memory ! */ + phys = hmp4e_data.iobaseaddr; + + if (remap_pfn_range(vma, start, phys >> PAGE_SHIFT, hmp4e_data.iosize, + vma->vm_page_prot)) { + pr_debug("hmp4e: failed mmapping HW registers\n"); + return -EAGAIN; + } + + return 0; +} + +/*! + * This function is the interrupt service routine. + * + * @param irq the irq number + * @param dev_id driver data when ISR was regiatered + * + * @return The return value is IRQ_HANDLED. + * + */ +irqreturn_t hmp4e_isr(s32 irq, void *dev_id) +{ + hmp4e_t *dev = (hmp4e_t *) dev_id; + u32 offset = dev->intr_offset; + + u32 irq_status = hmp4e_read(offset); + + /* clear enc IRQ */ + hmp4e_write(irq_status & (~0x01), offset); + + if (dev->async_queue) + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + + return IRQ_HANDLED; +} + +/*! + * This function is called to reset the encoder. + * + * @param dev pointer to struct hmp4e_data + * + */ +static void hmp4e_reset(hmp4e_t * dev) +{ + s32 i; + + /* enable HCLK for register reset */ + hmp4e_write(dev->clk_gate, 0); + + /* Reset registers, except ECR0 (0x00) and ID (read-only) */ + for (i = 1; i < (dev->iosize / 4); i += 1) { + if (i == dev->hwid_offset) /* ID is read only */ + continue; + + /* Only for CIF, not used */ + if ((dev->type == 0) && (i == 14)) + continue; + + hmp4e_write(0, i); + } + + /* disable HCLK */ + hmp4e_write(0, 0); + return; +} + +/*! + * This function is called during the driver binding process. This function + * does the hardware initialization. + * + * @param dev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions + * + * @return The function returns 0 if successful. + */ +static s32 hmp4e_probe(struct platform_device *pdev) +{ + s32 result; + u32 hwid; + struct device *temp_class; + + hmp4e_data.iobaseaddr = base_port; + hmp4e_data.irq = irq; + hmp4e_data.buffaddr = 0; + + /* map hw i/o registers into kernel space */ + hmp4e_data.hwregs = (volatile void *)IO_ADDRESS(hmp4e_data.iobaseaddr); + + hmp4e_clk = clk_get(&pdev->dev, "mpeg4_clk"); + if (IS_ERR(hmp4e_clk)) { + printk(KERN_INFO "hmp4e: Unable to get clock\n"); + return -EIO; + } + + clk_enable(hmp4e_clk); + + /* check hw id for encoder signature */ + hwid = hmp4e_read(7); + if ((hwid & 0xffff) == 0x1882) { /* CIF first */ + hmp4e_data.type = 0; + hmp4e_data.iosize = (16 * 4); + hmp4e_data.hwid_offset = 7; + hmp4e_data.intr_offset = 5; + hmp4e_data.clk_gate = (1 << 1); + hmp4e_data.buffsize = 512000; + hmp4e_data.busy_offset = 0; + hmp4e_data.busy_val = 1; + } else { + hwid = hmp4e_read((0x88 / 4)); + if ((hwid & 0xffff0000) == 0x52510000) { /* VGA */ + hmp4e_data.type = 1; + hmp4e_data.iosize = (35 * 4); + hmp4e_data.hwid_offset = (0x88 / 4); + hmp4e_data.intr_offset = (0x10 / 4); + hmp4e_data.clk_gate = (1 << 12); + hmp4e_data.buffsize = 1048576; + hmp4e_data.busy_offset = (0x10 / 4); + hmp4e_data.busy_val = (1 << 1); + } else { + printk(KERN_INFO "hmp4e: HW ID not found\n"); + goto error1; + } + } + + /* Reset hardware */ + hmp4e_reset(&hmp4e_data); + + /* allocate memory shared with ewl */ + result = hmp4e_alloc(hmp4e_data.buffsize); + if (result < 0) + goto error1; + + result = register_chrdev(hmp4e_major, "hmp4e", &hmp4e_fops); + if (result <= 0) { + pr_debug("hmp4e: unable to get major %d\n", hmp4e_major); + goto error2; + } + + hmp4e_major = result; + + hmp4e_class = class_create(THIS_MODULE, "hmp4e"); + if (IS_ERR(hmp4e_class)) { + pr_debug("Error creating hmp4e class.\n"); + goto error3; + } + + temp_class = device_create(hmp4e_class, NULL, MKDEV(hmp4e_major, 0), NULL, + "hmp4e"); + if (IS_ERR(temp_class)) { + pr_debug("Error creating hmp4e class device.\n"); + goto error4; + } + + platform_set_drvdata(pdev, &hmp4e_data); + +#ifdef CONFIG_PM + hmp4e_data.async_queue = NULL; + hmp4e_data.suspend_state = 0; + init_waitqueue_head(&hmp4e_data.power_queue); +#endif + + printk(KERN_INFO "hmp4e: %s encoder initialized\n", + hmp4e_data.type ? "VGA" : "CIF"); + clk_disable(hmp4e_clk); + return 0; + + error4: + class_destroy(hmp4e_class); + error3: + unregister_chrdev(hmp4e_major, "hmp4e"); + error2: + hmp4e_free(); + error1: + clk_disable(hmp4e_clk); + clk_put(hmp4e_clk); + printk(KERN_INFO "hmp4e: module not inserted\n"); + return -EIO; +} + +/*! + * Dissociates the driver. + * + * @param dev the device structure + * + * @return The function always returns 0. + */ +static s32 hmp4e_remove(struct platform_device *pdev) +{ + device_destroy(hmp4e_class, MKDEV(hmp4e_major, 0)); + class_destroy(hmp4e_class); + unregister_chrdev(hmp4e_major, "hmp4e"); + hmp4e_free(); + clk_disable(hmp4e_clk); + clk_put(hmp4e_clk); + platform_set_drvdata(pdev, NULL); + return 0; +} + +#ifdef CONFIG_PM +/*! + * This is the suspend of power management for the Hantro MPEG4 module + * + * @param dev the device + * @param state the state + * + * @return This function always returns 0. + */ +static s32 hmp4e_suspend(struct platform_device *pdev, pm_message_t state) +{ + s32 i; + hmp4e_t *pdata = &hmp4e_data; + + /* + * how many times msleep_interruptible will be called before + * giving up + */ + s32 timeout = 10; + + pr_debug("hmp4e: Suspend\n"); + hmp4e_data.suspend_state = 1; + + /* check if encoder is currently running */ + while ((hmp4e_read(pdata->busy_offset) & (pdata->busy_val)) && + --timeout) { + pr_debug("hmp4e: encoder is running, going to sleep\n"); + msleep_interruptible((unsigned int)30); + } + + if (!timeout) { + pr_debug("hmp4e: timeout suspending, resetting encoder\n"); + hmp4e_write(hmp4e_read(pdata->busy_offset) & + (~pdata->busy_val), pdata->busy_offset); + } + + /* first read register 0 */ + io_regs[0] = hmp4e_read(0); + + /* then override HCLK to make sure other registers can be read */ + hmp4e_write(pdata->clk_gate, 0); + + /* read other registers */ + for (i = 1; i < (pdata->iosize / 4); i += 1) { + + /* Only for CIF, not used */ + if ((pdata->type == 0) && (i == 14)) + continue; + + io_regs[i] = hmp4e_read(i); + } + + /* restore value of register 0 */ + hmp4e_write(io_regs[0], 0); + + /* stop HCLK */ + hmp4e_write(0, 0); + clk_disable(hmp4e_clk); + return 0; +}; + +/*! + * This is the resume of power management for the Hantro MPEG4 module + * It suports RESTORE state. + * + * @param pdev the platform device + * + * @return This function always returns 0 + */ +static s32 hmp4e_resume(struct platform_device *pdev) +{ + s32 i; + u32 status; + hmp4e_t *pdata = &hmp4e_data; + + pr_debug("hmp4e: Resume\n"); + clk_enable(hmp4e_clk); + + /* override HCLK to make sure registers can be written */ + hmp4e_write(pdata->clk_gate, 0x00); + + for (i = 1; i < (pdata->iosize / 4); i += 1) { + if (i == pdata->hwid_offset) /* Read only */ + continue; + + /* Only for CIF, not used */ + if ((pdata->type == 0) && (i == 14)) + continue; + + hmp4e_write(io_regs[i], i); + } + + /* write register 0 last */ + hmp4e_write(io_regs[0], 0x00); + + /* Clear the suspend flag */ + hmp4e_data.suspend_state = 0; + + /* Unblock the wait queue */ + wake_up_interruptible(&hmp4e_data.power_queue); + + /* Continue operations */ + status = hmp4e_read(pdata->intr_offset); + if (status & 0x1) { + hmp4e_write(status & (~0x01), pdata->intr_offset); + if (hmp4e_data.async_queue) + kill_fasync(&hmp4e_data.async_queue, SIGIO, POLL_IN); + } + + return 0; +}; + +#endif + +static struct platform_driver hmp4e_driver = { + .driver = { + .name = "mxc_hmp4e", + }, + .probe = hmp4e_probe, + .remove = hmp4e_remove, +#ifdef CONFIG_PM + .suspend = hmp4e_suspend, + .resume = hmp4e_resume, +#endif +}; + +static s32 __init hmp4e_init(void) +{ + printk(KERN_INFO "hmp4e: init\n"); + platform_driver_register(&hmp4e_driver); + return 0; +} + +static void __exit hmp4e_cleanup(void) +{ + platform_driver_unregister(&hmp4e_driver); + printk(KERN_INFO "hmp4e: module removed\n"); +} + +module_init(hmp4e_init); +module_exit(hmp4e_cleanup); + +/* module description */ +MODULE_AUTHOR("Hantro Products Oy"); +MODULE_DESCRIPTION("Device driver for Hantro's hardware based MPEG4 encoder"); +MODULE_SUPPORTED_DEVICE("5251/4251 MPEG4 Encoder"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/hmp4e/mxc_hmp4e.h b/drivers/mxc/hmp4e/mxc_hmp4e.h new file mode 100644 index 000000000000..f58831716346 --- /dev/null +++ b/drivers/mxc/hmp4e/mxc_hmp4e.h @@ -0,0 +1,70 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * Encoder device driver (kernel module headers) + * + * Copyright (C) 2005 Hantro Products Oy. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef _HMP4ENC_H_ +#define _HMP4ENC_H_ +#include <linux/ioctl.h> /* needed for the _IOW etc stuff used later */ + +/* this is for writing data through ioctl to registers*/ +typedef struct { + unsigned long data; + unsigned long offset; +} write_t; + +/* + * Ioctl definitions + */ + +/* Use 'k' as magic number */ +#define HMP4E_IOC_MAGIC 'k' +/* + * S means "Set" through a ptr, + * T means "Tell" directly with the argument value + * G means "Get": reply by setting through a pointer + * Q means "Query": response is on the return value + * X means "eXchange": G and S atomically + * H means "sHift": T and Q atomically + */ +#define HMP4E_IOCGBUFBUSADDRESS _IOR(HMP4E_IOC_MAGIC, 1, unsigned long *) +#define HMP4E_IOCGBUFSIZE _IOR(HMP4E_IOC_MAGIC, 2, unsigned int *) +#define HMP4E_IOCGHWOFFSET _IOR(HMP4E_IOC_MAGIC, 3, unsigned long *) +#define HMP4E_IOCGHWIOSIZE _IOR(HMP4E_IOC_MAGIC, 4, unsigned int *) +#define HMP4E_IOC_CLI _IO(HMP4E_IOC_MAGIC, 5) +#define HMP4E_IOC_STI _IO(HMP4E_IOC_MAGIC, 6) +#define HMP4E_IOCHARDRESET _IO(HMP4E_IOC_MAGIC, 7) +#define HMP4E_IOCSREGWRITE _IOW(HMP4E_IOC_MAGIC, 8, write_t) +#define HMP4E_IOCXREGREAD _IOWR(HMP4E_IOC_MAGIC, 9, unsigned long) + +#define HMP4E_IOC_MAXNR 9 + +#endif /* !_HMP4ENC_H_ */ diff --git a/drivers/mxc/hw_event/Kconfig b/drivers/mxc/hw_event/Kconfig new file mode 100644 index 000000000000..bcf479689501 --- /dev/null +++ b/drivers/mxc/hw_event/Kconfig @@ -0,0 +1,11 @@ +menu "MXC HARDWARE EVENT" + +config MXC_HWEVENT + bool "MXC Hardware Event Handler" + default y + depends on ARCH_MXC + help + If you plan to use the Hardware Event Handler in the MXC, say + Y here. If unsure, select Y. + +endmenu diff --git a/drivers/mxc/hw_event/Makefile b/drivers/mxc/hw_event/Makefile new file mode 100644 index 000000000000..a53fe2b45e04 --- /dev/null +++ b/drivers/mxc/hw_event/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MXC_HWEVENT) += mxc_hw_event.o diff --git a/drivers/mxc/hw_event/mxc_hw_event.c b/drivers/mxc/hw_event/mxc_hw_event.c new file mode 100644 index 000000000000..5451c1c68859 --- /dev/null +++ b/drivers/mxc/hw_event/mxc_hw_event.c @@ -0,0 +1,265 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * mxc_hw_event.c + * Collect the hardware events, send to user by netlink + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/netlink.h> +#include <linux/sched.h> +#include <linux/list.h> +#include <linux/signal.h> +#include <linux/freezer.h> +#include <linux/kthread.h> +#include <net/sock.h> + +#include <mach/hw_events.h> + +#define EVENT_POOL_SIZE 10 + +struct hw_event_elem { + struct mxc_hw_event event; + struct list_head list; +}; + +static struct sock *nl_event_sock; /* netlink socket */ +static struct list_head event_head; +static struct list_head free_head; +static struct hw_event_elem events_pool[EVENT_POOL_SIZE]; /* event pool */ +static DEFINE_SPINLOCK(list_lock); +static DECLARE_WAIT_QUEUE_HEAD(event_wq); +static unsigned int seq; /* send seq */ +static int initialized; +static struct task_struct *hwevent_kthread; + +/*! + * main HW event handler thread + */ +static int hw_event_thread(void *data) +{ + struct sk_buff *skb = NULL; + struct nlmsghdr *nlh = NULL; + unsigned int size; + struct hw_event_elem *event, *n; + LIST_HEAD(tmp_head); + DEFINE_WAIT(wait); + + while (1) { + + prepare_to_wait(&event_wq, &wait, TASK_INTERRUPTIBLE); + /* wait for event coming */ + if (!freezing(current) && !kthread_should_stop() && + list_empty(&event_head)) + schedule(); + finish_wait(&event_wq, &wait); + + try_to_freeze(); + + if (kthread_should_stop()) + break; + + /* fetch event from list */ + spin_lock_irq(&list_lock); + tmp_head = event_head; + tmp_head.prev->next = &tmp_head; + tmp_head.next->prev = &tmp_head; + /* clear the event list head */ + INIT_LIST_HEAD(&event_head); + spin_unlock_irq(&list_lock); + + list_for_each_entry_safe(event, n, &tmp_head, list) { + + size = NLMSG_SPACE(sizeof(struct mxc_hw_event)); + skb = alloc_skb(size, GFP_KERNEL); + if (!skb) { + /* if failed alloc skb, we drop this event */ + printk(KERN_WARNING + "mxc_hw_event: skb_alloc() failed\n"); + goto alloc_failure; + } + + /* put the netlink header struct to skb */ + nlh = + NLMSG_PUT(skb, 0, seq++, NLMSG_DONE, + size - sizeof(*nlh)); + + /* fill the netlink data */ + memcpy((struct mxc_hw_event *)NLMSG_DATA(nlh), + &event->event, sizeof(struct mxc_hw_event)); + + /* free the event node, set to unused */ + spin_lock_irq(&list_lock); + list_move(&event->list, &free_head); + spin_unlock_irq(&list_lock); + + /* send to all process that create this socket */ + NETLINK_CB(skb).pid = 0; /* sender pid */ + NETLINK_CB(skb).dst_group = HW_EVENT_GROUP; + /* broadcast the event */ + netlink_broadcast(nl_event_sock, skb, 0, HW_EVENT_GROUP, + GFP_KERNEL); + + continue; + nlmsg_failure: + printk(KERN_WARNING + "mxc_hw_event: No tailroom for NLMSG in skb\n"); + alloc_failure: + /* free the event node, set to unused */ + spin_lock_irq(&list_lock); + list_del(&event->list); + list_add_tail(&event->list, &free_head); + spin_unlock_irq(&list_lock); + } + } + + return 0; +} + +/*! + * + * @priority the event priority, REALTIME, EMERENCY, NORMAL + * @new_event event id to be send + */ +int hw_event_send(int priority, struct mxc_hw_event *new_event) +{ + unsigned int size; + struct sk_buff *skb = NULL; + struct nlmsghdr *nlh = NULL; + struct mxc_hw_event *event; + struct hw_event_elem *event_elem; + int ret; + unsigned long flag; + struct list_head *list_node; + + if (!initialized) { + pr_info("HW Event module has not been initialized\n"); + return -1; + } + + if (priority == HWE_HIGH_PRIORITY) { + /** + * the most high priority event, + * we send it immediatly. + */ + + size = NLMSG_SPACE(sizeof(struct mxc_hw_event)); + + /* alloc skb */ + if (in_interrupt()) { + skb = alloc_skb(size, GFP_ATOMIC); + } else { + skb = alloc_skb(size, GFP_KERNEL); + } + if (!skb) { + /* if failed alloc skb, we drop this event */ + printk(KERN_WARNING + "hw_event send: skb_alloc() failed\n"); + goto send_later; + } + + /* put the netlink header struct to skb */ + nlh = NLMSG_PUT(skb, 0, seq++, NLMSG_DONE, size - sizeof(*nlh)); + + /* fill the netlink data */ + event = (struct mxc_hw_event *)NLMSG_DATA(nlh); + memcpy(event, new_event, sizeof(struct mxc_hw_event)); + + /* send to all process that create this socket */ + NETLINK_CB(skb).pid = 0; /* sender pid */ + NETLINK_CB(skb).dst_group = HW_EVENT_GROUP; + /* broadcast the event */ + ret = netlink_broadcast(nl_event_sock, skb, 0, HW_EVENT_GROUP, + in_interrupt()? GFP_ATOMIC : + GFP_KERNEL); + if (ret) { + + nlmsg_failure: + /* send failed */ + kfree_skb(skb); + goto send_later; + } + + return 0; + } + + send_later: + spin_lock_irqsave(&list_lock, flag); + if (list_empty(&free_head)) { + spin_unlock_irqrestore(&list_lock, flag); + /* no more free event node */ + printk(KERN_WARNING "mxc_event send: no more free node\n"); + return -1; + } + + /* get a free node from free list, and added to event list */ + list_node = free_head.next; + /* fill event */ + event_elem = list_entry(list_node, struct hw_event_elem, list); + event_elem->event = *new_event; + list_move(list_node, &event_head); + spin_unlock_irqrestore(&list_lock, flag); + + wake_up(&event_wq); + + return 0; +} + +static int __init mxc_hw_event_init(void) +{ + int i; + + /* initial the list head for event and free */ + INIT_LIST_HEAD(&free_head); + INIT_LIST_HEAD(&event_head); + + /* initial the free list */ + for (i = 0; i < EVENT_POOL_SIZE; i++) + list_add_tail(&events_pool[i].list, &free_head); + + /* create netlink kernel sock */ + nl_event_sock = + netlink_kernel_create(&init_net, NETLINK_USERSOCK, 0, NULL, NULL, + THIS_MODULE); + if (!nl_event_sock) { + printk(KERN_WARNING + "mxc_hw_event: Fail to create netlink socket.\n"); + return 1; + } + + hwevent_kthread = kthread_run(hw_event_thread, NULL, "hwevent"); + if (IS_ERR(hwevent_kthread)) { + printk(KERN_WARNING + "mxc_hw_event: Fail to create hwevent thread.\n"); + return 1; + } + + initialized = 1; + + return 0; +} + +static void __exit mxc_hw_event_exit(void) +{ + kthread_stop(hwevent_kthread); + /* wait for thread completion */ + sock_release(nl_event_sock->sk_socket); +} + +module_init(mxc_hw_event_init); +module_exit(mxc_hw_event_exit); + +EXPORT_SYMBOL(hw_event_send); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/ipu/Kconfig b/drivers/mxc/ipu/Kconfig new file mode 100644 index 000000000000..919cb598c294 --- /dev/null +++ b/drivers/mxc/ipu/Kconfig @@ -0,0 +1,4 @@ +config MXC_IPU_V1 + bool + +source "drivers/mxc/ipu/pf/Kconfig" diff --git a/drivers/mxc/ipu/Makefile b/drivers/mxc/ipu/Makefile new file mode 100644 index 000000000000..dc6d42ad75d6 --- /dev/null +++ b/drivers/mxc/ipu/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_MXC_IPU_V1) = mxc_ipu.o + +mxc_ipu-objs := ipu_common.o ipu_sdc.o ipu_adc.o ipu_ic.o ipu_csi.o ipu_device.o + +obj-$(CONFIG_MXC_IPU_PF) += pf/ diff --git a/drivers/mxc/ipu/ipu_adc.c b/drivers/mxc/ipu/ipu_adc.c new file mode 100644 index 000000000000..78bfa0d19381 --- /dev/null +++ b/drivers/mxc/ipu/ipu_adc.c @@ -0,0 +1,689 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * @file ipu_adc.c + * + * @brief IPU ADC functions + * + * @ingroup IPU + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +/*#define ADC_CHAN1_SA_MASK 0xFF800000 */ + +static void _ipu_set_cmd_data_mappings(display_port_t disp, + uint32_t pixel_fmt, int ifc_width); + +int32_t _ipu_adc_init_channel(ipu_channel_t chan, display_port_t disp, + mcu_mode_t cmd, int16_t x_pos, int16_t y_pos) +{ + uint32_t reg; + uint32_t start_addr, stride; + unsigned long lock_flags; + uint32_t size; + + size = 0; + + switch (disp) { + case DISP0: + reg = __raw_readl(ADC_DISP0_CONF); + stride = reg & ADC_DISP_CONF_SL_MASK; + break; + case DISP1: + reg = __raw_readl(ADC_DISP1_CONF); + stride = reg & ADC_DISP_CONF_SL_MASK; + break; + case DISP2: + reg = __raw_readl(ADC_DISP2_CONF); + stride = reg & ADC_DISP_CONF_SL_MASK; + break; + default: + return -EINVAL; + } + + if (stride == 0) + return -EINVAL; + + stride++; + start_addr = (y_pos * stride) + x_pos; + + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(ADC_CONF); + + switch (chan) { + case ADC_SYS1: + reg &= ~0x00FF4000; + reg |= + ((uint32_t) size << 21 | (uint32_t) disp << 19 | (uint32_t) + cmd << 16); + + __raw_writel(start_addr, ADC_SYSCHA1_SA); + break; + + case ADC_SYS2: + reg &= ~0xFF008000; + reg |= + ((uint32_t) size << 29 | (uint32_t) disp << 27 | (uint32_t) + cmd << 24); + + __raw_writel(start_addr, ADC_SYSCHA2_SA); + break; + + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + reg &= ~0x000000F9; + reg |= + ((uint32_t) size << 5 | (uint32_t) disp << 3 | + ADC_CONF_PRP_EN); + + __raw_writel(start_addr, ADC_PRPCHAN_SA); + break; + + case MEM_PP_ADC: + reg &= ~0x00003F02; + reg |= + ((uint32_t) size << 10 | (uint32_t) disp << 8 | + ADC_CONF_PP_EN); + + __raw_writel(start_addr, ADC_PPCHAN_SA); + break; + default: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -1; + break; + } + __raw_writel(reg, ADC_CONF); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +int32_t _ipu_adc_uninit_channel(ipu_channel_t chan) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(ADC_CONF); + + switch (chan) { + case ADC_SYS1: + reg &= ~0x00FF4000; + break; + case ADC_SYS2: + reg &= ~0xFF008000; + break; + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + reg &= ~0x000000F9; + break; + case MEM_PP_ADC: + reg &= ~0x00003F02; + break; + default: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -1; + break; + } + __raw_writel(reg, ADC_CONF); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +int32_t ipu_adc_write_template(display_port_t disp, uint32_t * pCmd, bool write) +{ + uint32_t ima_addr = 0; + uint32_t row_nu; + int i; + + /* Set IPU_IMA_ADDR (IPU Internal Memory Access Address) */ + /* MEM_NU = 0x0001 (CPM) */ + /* ROW_NU = 2*N ( N is channel number) */ + /* WORD_NU = 0 */ + if (write) { + row_nu = (uint32_t) disp *2 * ATM_ADDR_RANGE; + } else { + row_nu = ((uint32_t) disp * 2 + 1) * ATM_ADDR_RANGE; + } + + /* form template addr for IPU_IMA_ADDR */ + ima_addr = (0x3 << 16 /*Template memory */ | row_nu << 3); + + __raw_writel(ima_addr, IPU_IMA_ADDR); + + /* write template data for IPU_IMA_DATA */ + for (i = 0; i < TEMPLATE_BUF_SIZE; i++) + /* only DATA field are needed */ + __raw_writel(pCmd[i], IPU_IMA_DATA); + + return 0; +} + +int32_t +ipu_adc_write_cmd(display_port_t disp, cmddata_t type, + uint32_t cmd, const uint32_t * params, uint16_t numParams) +{ + uint16_t i; + int disable_di = 0; + u32 reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(IPU_CONF); + if ((reg & IPU_CONF_DI_EN) == 0) { + disable_di = 1; + reg |= IPU_CONF_DI_EN; + __raw_writel(reg, IPU_CONF); + } + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + __raw_writel((uint32_t) ((type ? 0x0 : 0x1) | disp << 1 | 0x10), + DI_DISP_LLA_CONF); + __raw_writel(cmd, DI_DISP_LLA_DATA); + udelay(3); + + __raw_writel((uint32_t) (0x10 | disp << 1 | 0x11), DI_DISP_LLA_CONF); + for (i = 0; i < numParams; i++) { + __raw_writel(params[i], DI_DISP_LLA_DATA); + udelay(3); + } + + if (disable_di) { + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(IPU_CONF); + reg &= ~IPU_CONF_DI_EN; + __raw_writel(reg, IPU_CONF); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + } + + return 0; +} + +int32_t ipu_adc_set_update_mode(ipu_channel_t channel, + ipu_adc_update_mode_t mode, + uint32_t refresh_rate, unsigned long addr, + uint32_t * size) +{ + int32_t err = 0; + uint32_t ref_per, reg, src = 0; + unsigned long lock_flags; + uint32_t ipu_freq; + + ipu_freq = clk_get_rate(g_ipu_clk); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPU_FS_DISP_FLOW); + reg &= ~FS_AUTO_REF_PER_MASK; + switch (mode) { + case IPU_ADC_REFRESH_NONE: + src = 0; + break; + case IPU_ADC_AUTO_REFRESH: + if (refresh_rate == 0) { + err = -EINVAL; + goto err0; + } + ref_per = ipu_freq / ((1UL << 17) * refresh_rate); + ref_per--; + reg |= ref_per << FS_AUTO_REF_PER_OFFSET; + + src = FS_SRC_AUTOREF; + break; + case IPU_ADC_AUTO_REFRESH_SNOOP: + if (refresh_rate == 0) { + err = -EINVAL; + goto err0; + } + ref_per = ipu_freq / ((1UL << 17) * refresh_rate); + ref_per--; + reg |= ref_per << FS_AUTO_REF_PER_OFFSET; + + src = FS_SRC_AUTOREF_SNOOP; + break; + case IPU_ADC_SNOOPING: + src = FS_SRC_SNOOP; + break; + } + + switch (channel) { + case ADC_SYS1: + reg &= ~FS_ADC1_SRC_SEL_MASK; + reg |= src << FS_ADC1_SRC_SEL_OFFSET; + break; + case ADC_SYS2: + reg &= ~FS_ADC2_SRC_SEL_MASK; + reg |= src << FS_ADC2_SRC_SEL_OFFSET; + break; + default: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + __raw_writel(reg, IPU_FS_DISP_FLOW); + + /* Setup bus snooping */ + if ((mode == IPU_ADC_AUTO_REFRESH_SNOOP) || (mode == IPU_ADC_SNOOPING)) { + err = mxc_snoop_set_config(0, addr, *size); + if (err > 0) { + *size = err; + err = 0; + } + } else { + mxc_snoop_set_config(0, 0, 0); + } + + err0: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return err; +} + +int32_t ipu_adc_get_snooping_status(uint32_t * statl, uint32_t * stath) +{ + return mxc_snoop_get_status(0, statl, stath); +} + +int32_t ipu_adc_init_panel(display_port_t disp, + uint16_t width, uint16_t height, + uint32_t pixel_fmt, + uint32_t stride, + ipu_adc_sig_cfg_t sig, + display_addressing_t addr, + uint32_t vsync_width, vsync_t mode) +{ + uint32_t temp; + unsigned long lock_flags; + uint32_t ser_conf; + uint32_t disp_conf; + uint32_t adc_disp_conf; + uint32_t adc_disp_vsync; + uint32_t old_pol; + + if ((disp != DISP1) && (disp != DISP2) && + (sig.ifc_mode >= IPU_ADC_IFC_MODE_3WIRE_SERIAL)) { + return -EINVAL; + } +/* adc_disp_conf = ((uint32_t)((((size == 3)||(size == 2))?1:0)<<14) | */ +/* (uint32_t)addr<<12 | (stride-1)); */ + adc_disp_conf = (uint32_t) addr << 12 | (stride - 1); + + _ipu_set_cmd_data_mappings(disp, pixel_fmt, sig.ifc_width); + + spin_lock_irqsave(&ipu_lock, lock_flags); + disp_conf = __raw_readl(DI_DISP_IF_CONF); + old_pol = __raw_readl(DI_DISP_SIG_POL); + adc_disp_vsync = __raw_readl(ADC_DISP_VSYNC); + + switch (disp) { + case DISP0: + __raw_writel(adc_disp_conf, ADC_DISP0_CONF); + __raw_writel((((height - 1) << 16) | (width - 1)), + ADC_DISP0_SS); + + adc_disp_vsync &= ~(ADC_DISP_VSYNC_D0_MODE_MASK | + ADC_DISP_VSYNC_D0_WIDTH_MASK); + adc_disp_vsync |= (vsync_width << 16) | (uint32_t) mode; + + old_pol &= ~0x2000003FL; + old_pol |= sig.data_pol | sig.cs_pol << 1 | + sig.addr_pol << 2 | sig.read_pol << 3 | + sig.write_pol << 4 | sig.Vsync_pol << 5 | + sig.burst_pol << 29; + __raw_writel(old_pol, DI_DISP_SIG_POL); + + disp_conf &= ~0x0000001FL; + disp_conf |= (sig.burst_mode << 3) | (sig.ifc_mode << 1) | + DI_CONF_DISP0_EN; + __raw_writel(disp_conf, DI_DISP_IF_CONF); + break; + case DISP1: + __raw_writel(adc_disp_conf, ADC_DISP1_CONF); + __raw_writel((((height - 1) << 16) | (width - 1)), + ADC_DISP12_SS); + + adc_disp_vsync &= ~(ADC_DISP_VSYNC_D12_MODE_MASK | + ADC_DISP_VSYNC_D12_WIDTH_MASK); + adc_disp_vsync |= (vsync_width << 16) | (uint32_t) mode; + + old_pol &= ~0x4000FF00L; + old_pol |= (sig.Vsync_pol << 6 | sig.data_pol << 8 | + sig.cs_pol << 9 | sig.addr_pol << 10 | + sig.read_pol << 11 | sig.write_pol << 12 | + sig.clk_pol << 14 | sig.burst_pol << 30); + __raw_writel(old_pol, DI_DISP_SIG_POL); + + disp_conf &= ~0x00003F00L; + if (sig.ifc_mode >= IPU_ADC_IFC_MODE_3WIRE_SERIAL) { + ser_conf = (sig.ifc_width - 1) << + DI_SER_DISPx_CONF_SER_BIT_NUM_OFFSET; + if (sig.ser_preamble_len) { + ser_conf |= DI_SER_DISPx_CONF_PREAMBLE_EN; + ser_conf |= sig.ser_preamble << + DI_SER_DISPx_CONF_PREAMBLE_OFFSET; + ser_conf |= (sig.ser_preamble_len - 1) << + DI_SER_DISPx_CONF_PREAMBLE_LEN_OFFSET; + } + + ser_conf |= + sig.ser_rw_mode << DI_SER_DISPx_CONF_RW_CFG_OFFSET; + + if (sig.burst_mode == IPU_ADC_BURST_SERIAL) + ser_conf |= DI_SER_DISPx_CONF_BURST_MODE_EN; + __raw_writel(ser_conf, DI_SER_DISP1_CONF); + } else { /* parallel interface */ + disp_conf |= (uint32_t) (sig.burst_mode << 12); + } + disp_conf |= (sig.ifc_mode << 9) | DI_CONF_DISP1_EN; + __raw_writel(disp_conf, DI_DISP_IF_CONF); + break; + case DISP2: + __raw_writel(adc_disp_conf, ADC_DISP2_CONF); + __raw_writel((((height - 1) << 16) | (width - 1)), + ADC_DISP12_SS); + + adc_disp_vsync &= ~(ADC_DISP_VSYNC_D12_MODE_MASK | + ADC_DISP_VSYNC_D12_WIDTH_MASK); + adc_disp_vsync |= (vsync_width << 16) | (uint32_t) mode; + + old_pol &= ~0x80FF0000L; + temp = (uint32_t) (sig.data_pol << 16 | sig.cs_pol << 17 | + sig.addr_pol << 18 | sig.read_pol << 19 | + sig.write_pol << 20 | sig.Vsync_pol << 6 | + sig.burst_pol << 31 | sig.clk_pol << 22); + __raw_writel(temp | old_pol, DI_DISP_SIG_POL); + + disp_conf &= ~0x003F0000L; + if (sig.ifc_mode >= IPU_ADC_IFC_MODE_3WIRE_SERIAL) { + ser_conf = (sig.ifc_width - 1) << + DI_SER_DISPx_CONF_SER_BIT_NUM_OFFSET; + if (sig.ser_preamble_len) { + ser_conf |= DI_SER_DISPx_CONF_PREAMBLE_EN; + ser_conf |= sig.ser_preamble << + DI_SER_DISPx_CONF_PREAMBLE_OFFSET; + ser_conf |= (sig.ser_preamble_len - 1) << + DI_SER_DISPx_CONF_PREAMBLE_LEN_OFFSET; + + } + + ser_conf |= + sig.ser_rw_mode << DI_SER_DISPx_CONF_RW_CFG_OFFSET; + + if (sig.burst_mode == IPU_ADC_BURST_SERIAL) + ser_conf |= DI_SER_DISPx_CONF_BURST_MODE_EN; + __raw_writel(ser_conf, DI_SER_DISP2_CONF); + } else { /* parallel interface */ + disp_conf |= (uint32_t) (sig.burst_mode << 20); + } + disp_conf |= (sig.ifc_mode << 17) | DI_CONF_DISP2_EN; + __raw_writel(disp_conf, DI_DISP_IF_CONF); + break; + default: + break; + } + + __raw_writel(adc_disp_vsync, ADC_DISP_VSYNC); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +int32_t ipu_adc_init_ifc_timing(display_port_t disp, bool read, + uint32_t cycle_time, + uint32_t up_time, + uint32_t down_time, + uint32_t read_latch_time, uint32_t pixel_clk) +{ + uint32_t reg; + uint32_t time_conf3 = 0; + uint32_t clk_per; + uint32_t up_per; + uint32_t down_per; + uint32_t read_per; + uint32_t pixclk_per = 0; + uint32_t ipu_freq; + + ipu_freq = clk_get_rate(g_ipu_clk); + + clk_per = (cycle_time * (ipu_freq / 1000L) * 16L) / 1000000L; + up_per = (up_time * (ipu_freq / 1000L) * 4L) / 1000000L; + down_per = (down_time * (ipu_freq / 1000L) * 4L) / 1000000L; + + reg = (clk_per << DISPx_IF_CLK_PER_OFFSET) | + (up_per << DISPx_IF_CLK_UP_OFFSET) | + (down_per << DISPx_IF_CLK_DOWN_OFFSET); + + if (read) { + read_per = + (read_latch_time * (ipu_freq / 1000L) * 4L) / 1000000L; + if (pixel_clk) + pixclk_per = (ipu_freq * 16L) / pixel_clk; + time_conf3 = (read_per << DISPx_IF_CLK_READ_EN_OFFSET) | + (pixclk_per << DISPx_PIX_CLK_PER_OFFSET); + } + + dev_dbg(g_ipu_dev, "DI_DISPx_TIME_CONF_1/2 = 0x%08X\n", reg); + dev_dbg(g_ipu_dev, "DI_DISPx_TIME_CONF_3 = 0x%08X\n", time_conf3); + + switch (disp) { + case DISP0: + if (read) { + __raw_writel(reg, DI_DISP0_TIME_CONF_2); + __raw_writel(time_conf3, DI_DISP0_TIME_CONF_3); + } else { + __raw_writel(reg, DI_DISP0_TIME_CONF_1); + } + break; + case DISP1: + if (read) { + __raw_writel(reg, DI_DISP1_TIME_CONF_2); + __raw_writel(time_conf3, DI_DISP1_TIME_CONF_3); + } else { + __raw_writel(reg, DI_DISP1_TIME_CONF_1); + } + break; + case DISP2: + if (read) { + __raw_writel(reg, DI_DISP2_TIME_CONF_2); + __raw_writel(time_conf3, DI_DISP2_TIME_CONF_3); + } else { + __raw_writel(reg, DI_DISP2_TIME_CONF_1); + } + break; + default: + return -EINVAL; + break; + } + + return 0; +} + +struct ipu_adc_di_map { + uint32_t map_byte1; + uint32_t map_byte2; + uint32_t map_byte3; + uint32_t cycle_cnt; +}; + +static const struct ipu_adc_di_map di_mappings[] = { + [0] = { + /* RGB888, 8-bit bus */ + .map_byte1 = 0x1600AAAA, + .map_byte2 = 0x00E05555, + .map_byte2 = 0x00070000, + .cycle_cnt = 3, + }, + [1] = { + /* RGB666, 8-bit bus */ + .map_byte1 = 0x1C00AAAF, + .map_byte2 = 0x00E0555F, + .map_byte3 = 0x0007000F, + .cycle_cnt = 3, + }, + [2] = { + /* RGB565, 8-bit bus */ + .map_byte1 = 0x008055BF, + .map_byte2 = 0x0142015F, + .map_byte3 = 0x0007003F, + .cycle_cnt = 2, + }, + [3] = { + /* RGB888, 24-bit bus */ + .map_byte1 = 0x0007000F, + .map_byte2 = 0x000F000F, + .map_byte3 = 0x0017000F, + .cycle_cnt = 1, + }, + [4] = { + /* RGB666, 18-bit bus */ + .map_byte1 = 0x0005000F, + .map_byte2 = 0x000B000F, + .map_byte3 = 0x0011000F, + .cycle_cnt = 1, + }, + [5] = { + /* RGB565, 16-bit bus */ + .map_byte1 = 0x0004003F, + .map_byte2 = 0x000A000F, + .map_byte3 = 0x000F003F, + .cycle_cnt = 1, + }, +}; + +/* Private methods */ +static void _ipu_set_cmd_data_mappings(display_port_t disp, + uint32_t pixel_fmt, int ifc_width) +{ + uint32_t reg; + u32 map = 0; + + if (ifc_width == 8) { + switch (pixel_fmt) { + case IPU_PIX_FMT_BGR24: + map = 0; + break; + case IPU_PIX_FMT_RGB666: + map = 1; + break; + case IPU_PIX_FMT_RGB565: + map = 2; + break; + default: + break; + } + } else if (ifc_width >= 16) { + switch (pixel_fmt) { + case IPU_PIX_FMT_BGR24: + map = 3; + break; + case IPU_PIX_FMT_RGB666: + map = 4; + break; + case IPU_PIX_FMT_RGB565: + map = 5; + break; + default: + break; + } + } + + switch (disp) { + case DISP0: + if (ifc_width == 8) { + __raw_writel(0x00070000, DI_DISP0_CB0_MAP); + __raw_writel(0x0000FFFF, DI_DISP0_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP0_CB2_MAP); + } else { + __raw_writel(0x00070000, DI_DISP0_CB0_MAP); + __raw_writel(0x000F0000, DI_DISP0_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP0_CB2_MAP); + } + __raw_writel(di_mappings[map].map_byte1, DI_DISP0_DB0_MAP); + __raw_writel(di_mappings[map].map_byte2, DI_DISP0_DB1_MAP); + __raw_writel(di_mappings[map].map_byte3, DI_DISP0_DB2_MAP); + reg = __raw_readl(DI_DISP_ACC_CC); + reg &= ~DISP0_IF_CLK_CNT_D_MASK; + reg |= (di_mappings[map].cycle_cnt - 1) << + DISP0_IF_CLK_CNT_D_OFFSET; + __raw_writel(reg, DI_DISP_ACC_CC); + break; + case DISP1: + if (ifc_width == 8) { + __raw_writel(0x00070000, DI_DISP1_CB0_MAP); + __raw_writel(0x0000FFFF, DI_DISP1_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP1_CB2_MAP); + } else { + __raw_writel(0x00070000, DI_DISP1_CB0_MAP); + __raw_writel(0x000F0000, DI_DISP1_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP1_CB2_MAP); + } + __raw_writel(di_mappings[map].map_byte1, DI_DISP1_DB0_MAP); + __raw_writel(di_mappings[map].map_byte2, DI_DISP1_DB1_MAP); + __raw_writel(di_mappings[map].map_byte3, DI_DISP1_DB2_MAP); + reg = __raw_readl(DI_DISP_ACC_CC); + reg &= ~DISP1_IF_CLK_CNT_D_MASK; + reg |= (di_mappings[map].cycle_cnt - 1) << + DISP1_IF_CLK_CNT_D_OFFSET; + __raw_writel(reg, DI_DISP_ACC_CC); + break; + case DISP2: + if (ifc_width == 8) { + __raw_writel(0x00070000, DI_DISP2_CB0_MAP); + __raw_writel(0x0000FFFF, DI_DISP2_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP2_CB2_MAP); + } else { + __raw_writel(0x00070000, DI_DISP2_CB0_MAP); + __raw_writel(0x000F0000, DI_DISP2_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP2_CB2_MAP); + } + __raw_writel(di_mappings[map].map_byte1, DI_DISP2_DB0_MAP); + __raw_writel(di_mappings[map].map_byte2, DI_DISP2_DB1_MAP); + __raw_writel(di_mappings[map].map_byte3, DI_DISP2_DB2_MAP); + reg = __raw_readl(DI_DISP_ACC_CC); + reg &= ~DISP2_IF_CLK_CNT_D_MASK; + reg |= (di_mappings[map].cycle_cnt - 1) << + DISP2_IF_CLK_CNT_D_OFFSET; + __raw_writel(reg, DI_DISP_ACC_CC); + break; + default: + break; + } +} + +void ipu_disp_direct_write(ipu_channel_t channel, u32 value, u32 offset) +{ + /*TODO*/ +} + +int ipu_init_async_panel(int disp, int type, uint32_t cycle_time, + uint32_t pixel_fmt, ipu_adc_sig_cfg_t sig) +{ + /*TODO:uniform interface for ipu async panel init*/ + return -1; +} + +EXPORT_SYMBOL(ipu_adc_write_template); +EXPORT_SYMBOL(ipu_adc_write_cmd); +EXPORT_SYMBOL(ipu_adc_set_update_mode); +EXPORT_SYMBOL(ipu_adc_init_panel); +EXPORT_SYMBOL(ipu_adc_init_ifc_timing); diff --git a/drivers/mxc/ipu/ipu_common.c b/drivers/mxc/ipu/ipu_common.c new file mode 100644 index 000000000000..b2795fe83f0d --- /dev/null +++ b/drivers/mxc/ipu/ipu_common.c @@ -0,0 +1,1835 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_common.c + * + * @brief This file contains the IPU driver common API functions. + * + * @ingroup IPU + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +/* + * This type definition is used to define a node in the GPIO interrupt queue for + * registered interrupts for GPIO pins. Each node contains the GPIO signal number + * associated with the ISR and the actual ISR function pointer. + */ +struct ipu_irq_node { + irqreturn_t(*handler) (int, void *); /*!< the ISR */ + const char *name; /*!< device associated with the interrupt */ + void *dev_id; /*!< some unique information for the ISR */ + __u32 flags; /*!< not used */ +}; + +/* Globals */ +struct clk *g_ipu_clk; +struct clk *g_ipu_csi_clk; +static struct clk *dfm_clk; +int g_ipu_irq[2]; +int g_ipu_hw_rev; +bool g_sec_chan_en[21]; +uint32_t g_channel_init_mask; +DEFINE_SPINLOCK(ipu_lock); +struct device *g_ipu_dev; + +static struct ipu_irq_node ipu_irq_list[IPU_IRQ_COUNT]; +static const char driver_name[] = "mxc_ipu"; + +static uint32_t g_ipu_config = 0; +static uint32_t g_channel_init_mask_backup = 0; +static bool g_csi_used = false; + +/* Static functions */ +static irqreturn_t ipu_irq_handler(int irq, void *desc); +static void _ipu_pf_init(ipu_channel_params_t * params); +static void _ipu_pf_uninit(void); + +inline static uint32_t channel_2_dma(ipu_channel_t ch, ipu_buffer_t type) +{ + return ((type == IPU_INPUT_BUFFER) ? ((uint32_t) ch & 0xFF) : + ((type == IPU_OUTPUT_BUFFER) ? (((uint32_t) ch >> 8) & 0xFF) + : (((uint32_t) ch >> 16) & 0xFF))); +}; + +inline static uint32_t DMAParamAddr(uint32_t dma_ch) +{ + return (0x10000 | (dma_ch << 4)); +}; + +/*! + * This function is called by the driver framework to initialize the IPU + * hardware. + * + * @param dev The device structure for the IPU passed in by the framework. + * + * @return This function returns 0 on success or negative error code on error + */ +static +int ipu_probe(struct platform_device *pdev) +{ +// struct platform_device *pdev = to_platform_device(dev); + struct mxc_ipu_config *ipu_conf = pdev->dev.platform_data; + + spin_lock_init(&ipu_lock); + + g_ipu_dev = &pdev->dev; + g_ipu_hw_rev = ipu_conf->rev; + + /* Register IPU interrupts */ + g_ipu_irq[0] = platform_get_irq(pdev, 0); + if (g_ipu_irq[0] < 0) + return -EINVAL; + + if (request_irq(g_ipu_irq[0], ipu_irq_handler, 0, driver_name, 0) != 0) { + dev_err(g_ipu_dev, "request SYNC interrupt failed\n"); + return -EBUSY; + } + /* Some platforms have 2 IPU interrupts */ + g_ipu_irq[1] = platform_get_irq(pdev, 1); + if (g_ipu_irq[1] >= 0) { + if (request_irq + (g_ipu_irq[1], ipu_irq_handler, 0, driver_name, 0) != 0) { + dev_err(g_ipu_dev, "request ERR interrupt failed\n"); + return -EBUSY; + } + } + + /* Enable IPU and CSI clocks */ + /* Get IPU clock freq */ + g_ipu_clk = clk_get(&pdev->dev, "ipu_clk"); + dev_dbg(g_ipu_dev, "ipu_clk = %lu\n", clk_get_rate(g_ipu_clk)); + + g_ipu_csi_clk = clk_get(&pdev->dev, "csi_clk"); + + dfm_clk = clk_get(NULL, "dfm_clk"); + + clk_enable(g_ipu_clk); + + /* resetting the CONF register of the IPU */ + __raw_writel(0x00000000, IPU_CONF); + + __raw_writel(0x00100010L, DI_HSP_CLK_PER); + + /* Set SDC refresh channels as high priority */ + __raw_writel(0x0000C000L, IDMAC_CHA_PRI); + + /* Set to max back to back burst requests */ + __raw_writel(0x00000000L, IDMAC_CONF); + + register_ipu_device(); + + return 0; +} + +/*! + * This function is called to initialize a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID to initalize. + * + * @param params Input parameter containing union of channel initialization + * parameters. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_init_channel(ipu_channel_t channel, ipu_channel_params_t * params) +{ + uint32_t ipu_conf; + uint32_t reg; + unsigned long lock_flags; + + dev_dbg(g_ipu_dev, "init channel = %d\n", IPU_CHAN_ID(channel)); + + if ((channel != MEM_SDC_BG) && (channel != MEM_SDC_FG) && + (channel != MEM_ROT_ENC_MEM) && (channel != MEM_ROT_VF_MEM) && + (channel != MEM_ROT_PP_MEM) && (channel != CSI_MEM) + && (params == NULL)) { + return -EINVAL; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + ipu_conf = __raw_readl(IPU_CONF); + if (ipu_conf == 0) { + clk_enable(g_ipu_clk); + } + + if (g_channel_init_mask & (1L << IPU_CHAN_ID(channel))) { + dev_err(g_ipu_dev, "Warning: channel already initialized %d\n", + IPU_CHAN_ID(channel)); + } + + switch (channel) { + case CSI_PRP_VF_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_VF_IN_VALID, IPU_FS_PROC_FLOW); + + if (params->mem_prp_vf_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_prpvf(params, true); + break; + case CSI_PRP_VF_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | (FS_DEST_ADC << FS_PRPVF_DEST_SEL_OFFSET), + IPU_FS_PROC_FLOW); + + _ipu_adc_init_channel(CSI_PRP_VF_ADC, + params->csi_prp_vf_adc.disp, + WriteTemplateNonSeq, + params->csi_prp_vf_adc.out_left, + params->csi_prp_vf_adc.out_top); + + _ipu_ic_init_prpvf(params, true); + break; + case MEM_PRP_VF_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | FS_VF_IN_VALID, IPU_FS_PROC_FLOW); + + if (params->mem_prp_vf_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_prpvf(params, false); + break; + case MEM_ROT_VF_MEM: + _ipu_ic_init_rotate_vf(params); + break; + case CSI_PRP_ENC_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_ENC_IN_VALID, IPU_FS_PROC_FLOW); + _ipu_ic_init_prpenc(params, true); + break; + case MEM_PRP_ENC_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | FS_ENC_IN_VALID, IPU_FS_PROC_FLOW); + _ipu_ic_init_prpenc(params, false); + break; + case MEM_ROT_ENC_MEM: + _ipu_ic_init_rotate_enc(params); + break; + case MEM_PP_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | (FS_DEST_ADC << FS_PP_DEST_SEL_OFFSET), + IPU_FS_PROC_FLOW); + + _ipu_adc_init_channel(MEM_PP_ADC, params->mem_pp_adc.disp, + WriteTemplateNonSeq, + params->mem_pp_adc.out_left, + params->mem_pp_adc.out_top); + + if (params->mem_pp_adc.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_pp(params); + break; + case MEM_PP_MEM: + if (params->mem_pp_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_pp(params); + break; + case MEM_ROT_PP_MEM: + _ipu_ic_init_rotate_pp(params); + break; + case CSI_MEM: + _ipu_ic_init_csi(params); + break; + + case MEM_PF_Y_MEM: + case MEM_PF_U_MEM: + case MEM_PF_V_MEM: + /* Enable PF block */ + _ipu_pf_init(params); + break; + + case MEM_SDC_BG: + break; + case MEM_SDC_FG: + break; + case ADC_SYS1: + _ipu_adc_init_channel(ADC_SYS1, params->adc_sys1.disp, + params->adc_sys1.ch_mode, + params->adc_sys1.out_left, + params->adc_sys1.out_top); + break; + case ADC_SYS2: + _ipu_adc_init_channel(ADC_SYS2, params->adc_sys2.disp, + params->adc_sys2.ch_mode, + params->adc_sys2.out_left, + params->adc_sys2.out_top); + break; + default: + dev_err(g_ipu_dev, "Missing channel initialization\n"); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + + /* Enable IPU sub module */ + g_channel_init_mask |= 1L << IPU_CHAN_ID(channel); + + if (g_channel_init_mask & 0x00000066L) { /*CSI */ + ipu_conf |= IPU_CONF_CSI_EN; + if (cpu_is_mx31() || cpu_is_mx32()) { + g_csi_used = true; + } + } + if (g_channel_init_mask & 0x00001FFFL) { /*IC */ + ipu_conf |= IPU_CONF_IC_EN; + } + if (g_channel_init_mask & 0x00000A10L) { /*ROT */ + ipu_conf |= IPU_CONF_ROT_EN; + } + if (g_channel_init_mask & 0x0001C000L) { /*SDC */ + ipu_conf |= IPU_CONF_SDC_EN | IPU_CONF_DI_EN; + } + if (g_channel_init_mask & 0x00061140L) { /*ADC */ + ipu_conf |= IPU_CONF_ADC_EN | IPU_CONF_DI_EN; + } + if (g_channel_init_mask & 0x00380000L) { /*PF */ + ipu_conf |= IPU_CONF_PF_EN; + } + __raw_writel(ipu_conf, IPU_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +/*! + * This function is called to uninitialize a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID to uninitalize. + */ +void ipu_uninit_channel(ipu_channel_t channel) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t dma, mask = 0; + uint32_t ipu_conf; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if ((g_channel_init_mask & (1L << IPU_CHAN_ID(channel))) == 0) { + dev_err(g_ipu_dev, "Channel already uninitialized %d\n", + IPU_CHAN_ID(channel)); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return; + } + + /* Make sure channel is disabled */ + /* Get input and output dma channels */ + dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + if (dma != IDMA_CHAN_INVALID) + mask |= 1UL << dma; + dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + if (dma != IDMA_CHAN_INVALID) + mask |= 1UL << dma; + /* Get secondary input dma channel */ + if (g_sec_chan_en[IPU_CHAN_ID(channel)]) { + dma = channel_2_dma(channel, IPU_SEC_INPUT_BUFFER); + if (dma != IDMA_CHAN_INVALID) { + mask |= 1UL << dma; + } + } + if (mask & __raw_readl(IDMAC_CHA_EN)) { + dev_err(g_ipu_dev, + "Channel %d is not disabled, disable first\n", + IPU_CHAN_ID(channel)); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return; + } + + /* Reset the double buffer */ + reg = __raw_readl(IPU_CHA_DB_MODE_SEL); + __raw_writel(reg & ~mask, IPU_CHA_DB_MODE_SEL); + + g_sec_chan_en[IPU_CHAN_ID(channel)] = false; + + switch (channel) { + case CSI_MEM: + _ipu_ic_uninit_csi(); + break; + case CSI_PRP_VF_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_PRPVF_DEST_SEL_MASK, IPU_FS_PROC_FLOW); + + _ipu_adc_uninit_channel(CSI_PRP_VF_ADC); + + /* Fall thru */ + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + _ipu_ic_uninit_prpvf(); + break; + case MEM_PRP_VF_ADC: + break; + case MEM_ROT_VF_MEM: + _ipu_ic_uninit_rotate_vf(); + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + _ipu_ic_uninit_prpenc(); + break; + case MEM_ROT_ENC_MEM: + _ipu_ic_uninit_rotate_enc(); + break; + case MEM_PP_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_PP_DEST_SEL_MASK, IPU_FS_PROC_FLOW); + + _ipu_adc_uninit_channel(MEM_PP_ADC); + + /* Fall thru */ + case MEM_PP_MEM: + _ipu_ic_uninit_pp(); + break; + case MEM_ROT_PP_MEM: + _ipu_ic_uninit_rotate_pp(); + break; + + case MEM_PF_Y_MEM: + _ipu_pf_uninit(); + break; + case MEM_PF_U_MEM: + case MEM_PF_V_MEM: + break; + + case MEM_SDC_BG: + break; + case MEM_SDC_FG: + break; + case ADC_SYS1: + _ipu_adc_uninit_channel(ADC_SYS1); + break; + case ADC_SYS2: + _ipu_adc_uninit_channel(ADC_SYS2); + break; + case MEM_SDC_MASK: + case CHAN_NONE: + break; + } + + g_channel_init_mask &= ~(1L << IPU_CHAN_ID(channel)); + + ipu_conf = __raw_readl(IPU_CONF); + if ((g_channel_init_mask & 0x00000066L) == 0) { /*CSI */ + ipu_conf &= ~IPU_CONF_CSI_EN; + } + if ((g_channel_init_mask & 0x00001FFFL) == 0) { /*IC */ + ipu_conf &= ~IPU_CONF_IC_EN; + } + if ((g_channel_init_mask & 0x00000A10L) == 0) { /*ROT */ + ipu_conf &= ~IPU_CONF_ROT_EN; + } + if ((g_channel_init_mask & 0x0001C000L) == 0) { /*SDC */ + ipu_conf &= ~IPU_CONF_SDC_EN; + } + if ((g_channel_init_mask & 0x00061140L) == 0) { /*ADC */ + ipu_conf &= ~IPU_CONF_ADC_EN; + } + if ((g_channel_init_mask & 0x0007D140L) == 0) { /*DI */ + ipu_conf &= ~IPU_CONF_DI_EN; + } + if ((g_channel_init_mask & 0x00380000L) == 0) { /*PF */ + ipu_conf &= ~IPU_CONF_PF_EN; + } + __raw_writel(ipu_conf, IPU_CONF); + if (ipu_conf == 0) { + clk_disable(g_ipu_clk); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * This function is called to initialize a buffer for logical IPU channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param pixel_fmt Input parameter for pixel format of buffer. Pixel + * format is a FOURCC ASCII code. + * + * @param width Input parameter for width of buffer in pixels. + * + * @param height Input parameter for height of buffer in pixels. + * + * @param stride Input parameter for stride length of buffer + * in pixels. + * + * @param rot_mode Input parameter for rotation setting of buffer. + * A rotation setting other than \b IPU_ROTATE_VERT_FLIP + * should only be used for input buffers of rotation + * channels. + * + * @param phyaddr_0 Input parameter buffer 0 physical address. + * + * @param phyaddr_1 Input parameter buffer 1 physical address. + * Setting this to a value other than NULL enables + * double buffering mode. + * + * @param u private u offset for additional cropping, + * zero if not used. + * + * @param v private v offset for additional cropping, + * zero if not used. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_init_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t pixel_fmt, + uint16_t width, uint16_t height, + uint32_t stride, + ipu_rotate_mode_t rot_mode, + dma_addr_t phyaddr_0, dma_addr_t phyaddr_1, + uint32_t u, uint32_t v) +{ + uint32_t params[10]; + unsigned long lock_flags; + uint32_t reg; + uint32_t dma_chan; + + dma_chan = channel_2_dma(channel, type); + + if (stride < width * bytes_per_pixel(pixel_fmt)) + stride = width * bytes_per_pixel(pixel_fmt); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + if (stride % 4) { + dev_err(g_ipu_dev, + "Stride must be 32-bit aligned, stride = %d\n", stride); + return -EINVAL; + } + /* IC channels' width must be multiple of 8 pixels */ + if ((dma_chan <= 13) && (width % 8)) { + dev_err(g_ipu_dev, "width must be 8 pixel multiple\n"); + return -EINVAL; + } + /* Build parameter memory data for DMA channel */ + _ipu_ch_param_set_size(params, pixel_fmt, width, height, stride, u, v); + _ipu_ch_param_set_buffer(params, phyaddr_0, phyaddr_1); + _ipu_ch_param_set_rotation(params, rot_mode); + /* Some channels (rotation) have restriction on burst length */ + if ((dma_chan == 10) || (dma_chan == 11) || (dma_chan == 13)) { + _ipu_ch_param_set_burst_size(params, 8); + } else if (dma_chan == 24) { /* PF QP channel */ + _ipu_ch_param_set_burst_size(params, 4); + } else if (dma_chan == 25) { /* PF H264 BS channel */ + _ipu_ch_param_set_burst_size(params, 16); + } else if (((dma_chan == 14) || (dma_chan == 15)) && + pixel_fmt == IPU_PIX_FMT_RGB565) { + _ipu_ch_param_set_burst_size(params, 16); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + _ipu_write_param_mem(DMAParamAddr(dma_chan), params, 10); + + reg = __raw_readl(IPU_CHA_DB_MODE_SEL); + if (phyaddr_1) { + reg |= 1UL << dma_chan; + } else { + reg &= ~(1UL << dma_chan); + } + __raw_writel(reg, IPU_CHA_DB_MODE_SEL); + + /* Reset to buffer 0 */ + __raw_writel(1UL << dma_chan, IPU_CHA_CUR_BUF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +/*! + * This function is called to update the physical address of a buffer for + * a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param bufNum Input parameter for which buffer number to update. + * 0 or 1 are the only valid values. + * + * @param phyaddr Input parameter buffer physical address. + * + * @return This function returns 0 on success or negative error code on + * fail. This function will fail if the buffer is set to ready. + */ +int32_t ipu_update_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum, dma_addr_t phyaddr) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t dma_chan = channel_2_dma(channel, type); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (bufNum == 0) { + reg = __raw_readl(IPU_CHA_BUF0_RDY); + if (reg & (1UL << dma_chan)) { + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EACCES; + } + __raw_writel(DMAParamAddr(dma_chan) + 0x0008UL, IPU_IMA_ADDR); + __raw_writel(phyaddr, IPU_IMA_DATA); + } else { + reg = __raw_readl(IPU_CHA_BUF1_RDY); + if (reg & (1UL << dma_chan)) { + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EACCES; + } + __raw_writel(DMAParamAddr(dma_chan) + 0x0009UL, IPU_IMA_ADDR); + __raw_writel(phyaddr, IPU_IMA_DATA); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + dev_dbg(g_ipu_dev, "IPU: update IDMA ch %d buf %d = 0x%08X\n", + dma_chan, bufNum, phyaddr); + return 0; +} + +/*! + * This function is called to set a channel's buffer as ready. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param bufNum Input parameter for which buffer number set to + * ready state. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_select_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum) +{ + uint32_t dma_chan = channel_2_dma(channel, type); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + if (bufNum == 0) { + /*Mark buffer 0 as ready. */ + __raw_writel(1UL << dma_chan, IPU_CHA_BUF0_RDY); + } else { + /*Mark buffer 1 as ready. */ + __raw_writel(1UL << dma_chan, IPU_CHA_BUF1_RDY); + } + return 0; +} + +/*! + * This function links 2 channels together for automatic frame + * synchronization. The output of the source channel is linked to the input of + * the destination channel. + * + * @param src_ch Input parameter for the logical channel ID of + * the source channel. + * + * @param dest_ch Input parameter for the logical channel ID of + * the destination channel. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_link_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch) +{ + unsigned long lock_flags; + uint32_t out_dma; + uint32_t in_dma; + bool isProc; + uint32_t value; + uint32_t mask; + uint32_t offset; + uint32_t fs_proc_flow; + uint32_t fs_disp_flow; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + fs_proc_flow = __raw_readl(IPU_FS_PROC_FLOW); + fs_disp_flow = __raw_readl(IPU_FS_DISP_FLOW); + + out_dma = (1UL << channel_2_dma(src_ch, IPU_OUTPUT_BUFFER)); + in_dma = (1UL << channel_2_dma(dest_ch, IPU_INPUT_BUFFER)); + + /* PROCESS THE OUTPUT DMA CH */ + switch (out_dma) { + /*VF-> */ + case IDMA_IC_1: + pr_debug("Link VF->"); + isProc = true; + mask = FS_PRPVF_DEST_SEL_MASK; + offset = FS_PRPVF_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_11) ? FS_DEST_ROT : /*->VF_ROT */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /* ->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /*->ADC1 */ + /* ->ADCDirect */ + 0; + break; + + /*VF_ROT-> */ + case IDMA_IC_9: + pr_debug("Link VF_ROT->"); + isProc = true; + mask = FS_PRPVF_ROT_DEST_SEL_MASK; + offset = FS_PRPVF_ROT_DEST_SEL_OFFSET; + value = (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /*->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + 0; + break; + + /*ENC-> */ + case IDMA_IC_0: + pr_debug("Link ENC->"); + isProc = true; + mask = 0; + offset = 0; + value = (in_dma == IDMA_IC_10) ? FS_PRPENC_DEST_SEL : /*->ENC_ROT */ + 0; + break; + + /*PP-> */ + case IDMA_IC_2: + pr_debug("Link PP->"); + isProc = true; + mask = FS_PP_DEST_SEL_MASK; + offset = FS_PP_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_13) ? FS_DEST_ROT : /*->PP_ROT */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /* ->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + /* ->ADCDirect */ + 0; + break; + + /*PP_ROT-> */ + case IDMA_IC_12: + pr_debug("Link PP_ROT->"); + isProc = true; + mask = FS_PP_ROT_DEST_SEL_MASK; + offset = FS_PP_ROT_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_5) ? FS_DEST_ROT : /*->PP */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /* ->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + 0; + break; + + /*PF-> */ + case IDMA_PF_Y_OUT: + case IDMA_PF_U_OUT: + case IDMA_PF_V_OUT: + pr_debug("Link PF->"); + isProc = true; + mask = FS_PF_DEST_SEL_MASK; + offset = FS_PF_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_5) ? FS_PF_DEST_PP : + (in_dma == IDMA_IC_13) ? FS_PF_DEST_ROT : 0; + break; + + /* Invalid Chainings: ENC_ROT-> */ + default: + pr_debug("Link Invalid->"); + value = 0; + break; + + } + + if (value) { + if (isProc) { + fs_proc_flow &= ~mask; + fs_proc_flow |= (value << offset); + } else { + fs_disp_flow &= ~mask; + fs_disp_flow |= (value << offset); + } + } else { + dev_err(g_ipu_dev, "Invalid channel chaining %d -> %d\n", + out_dma, in_dma); + return -EINVAL; + } + + /* PROCESS THE INPUT DMA CH */ + switch (in_dma) { + /* ->VF_ROT */ + case IDMA_IC_11: + pr_debug("VF_ROT\n"); + isProc = true; + mask = 0; + offset = 0; + value = (out_dma == IDMA_IC_1) ? FS_PRPVF_ROT_SRC_SEL : /*VF-> */ + 0; + break; + + /* ->ENC_ROT */ + case IDMA_IC_10: + pr_debug("ENC_ROT\n"); + isProc = true; + mask = 0; + offset = 0; + value = (out_dma == IDMA_IC_0) ? FS_PRPENC_ROT_SRC_SEL : /*ENC-> */ + 0; + break; + + /* ->PP */ + case IDMA_IC_5: + pr_debug("PP\n"); + isProc = true; + mask = FS_PP_SRC_SEL_MASK; + offset = FS_PP_SRC_SEL_OFFSET; + value = (out_dma == IDMA_PF_Y_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_U_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_V_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_IC_12) ? FS_PP_SRC_ROT : /*PP_ROT-> */ + 0; + break; + + /* ->PP_ROT */ + case IDMA_IC_13: + pr_debug("PP_ROT\n"); + isProc = true; + mask = FS_PP_ROT_SRC_SEL_MASK; + offset = FS_PP_ROT_SRC_SEL_OFFSET; + value = (out_dma == IDMA_PF_Y_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_U_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_V_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_IC_2) ? FS_ROT_SRC_PP : /*PP-> */ + 0; + break; + + /* ->SDC_BG */ + case IDMA_SDC_BG: + pr_debug("SDC_BG\n"); + isProc = false; + mask = FS_SDC_BG_SRC_SEL_MASK; + offset = FS_SDC_BG_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /* ->SDC_FG */ + case IDMA_SDC_FG: + pr_debug("SDC_FG\n"); + isProc = false; + mask = FS_SDC_FG_SRC_SEL_MASK; + offset = FS_SDC_FG_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /* ->ADC1 */ + case IDMA_ADC_SYS1_WR: + pr_debug("ADC_SYS1\n"); + isProc = false; + mask = FS_ADC1_SRC_SEL_MASK; + offset = FS_ADC1_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /* ->ADC2 */ + case IDMA_ADC_SYS2_WR: + pr_debug("ADC_SYS2\n"); + isProc = false; + mask = FS_ADC2_SRC_SEL_MASK; + offset = FS_ADC2_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /*Invalid chains: */ + /* ->ENC, ->VF, ->PF, ->VF_COMBINE, ->PP_COMBINE */ + default: + pr_debug("Invalid\n"); + value = 0; + break; + + } + + if (value) { + if (isProc) { + fs_proc_flow &= ~mask; + fs_proc_flow |= (value << offset); + } else { + fs_disp_flow &= ~mask; + fs_disp_flow |= (value << offset); + } + } else { + dev_err(g_ipu_dev, "Invalid channel chaining %d -> %d\n", + out_dma, in_dma); + return -EINVAL; + } + + __raw_writel(fs_proc_flow, IPU_FS_PROC_FLOW); + __raw_writel(fs_disp_flow, IPU_FS_DISP_FLOW); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +/*! + * This function unlinks 2 channels and disables automatic frame + * synchronization. + * + * @param src_ch Input parameter for the logical channel ID of + * the source channel. + * + * @param dest_ch Input parameter for the logical channel ID of + * the destination channel. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_unlink_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch) +{ + unsigned long lock_flags; + uint32_t out_dma; + uint32_t in_dma; + uint32_t fs_proc_flow; + uint32_t fs_disp_flow; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + fs_proc_flow = __raw_readl(IPU_FS_PROC_FLOW); + fs_disp_flow = __raw_readl(IPU_FS_DISP_FLOW); + + out_dma = (1UL << channel_2_dma(src_ch, IPU_OUTPUT_BUFFER)); + in_dma = (1UL << channel_2_dma(dest_ch, IPU_INPUT_BUFFER)); + + /*clear the src_ch's output destination */ + switch (out_dma) { + /*VF-> */ + case IDMA_IC_1: + pr_debug("Unlink VF->"); + fs_proc_flow &= ~FS_PRPVF_DEST_SEL_MASK; + break; + + /*VF_ROT-> */ + case IDMA_IC_9: + pr_debug("Unlink VF_Rot->"); + fs_proc_flow &= ~FS_PRPVF_ROT_DEST_SEL_MASK; + break; + + /*ENC-> */ + case IDMA_IC_0: + pr_debug("Unlink ENC->"); + fs_proc_flow &= ~FS_PRPENC_DEST_SEL; + break; + + /*PP-> */ + case IDMA_IC_2: + pr_debug("Unlink PP->"); + fs_proc_flow &= ~FS_PP_DEST_SEL_MASK; + break; + + /*PP_ROT-> */ + case IDMA_IC_12: + pr_debug("Unlink PP_ROT->"); + fs_proc_flow &= ~FS_PP_ROT_DEST_SEL_MASK; + break; + + /*PF-> */ + case IDMA_PF_Y_OUT: + case IDMA_PF_U_OUT: + case IDMA_PF_V_OUT: + pr_debug("Unlink PF->"); + fs_proc_flow &= ~FS_PF_DEST_SEL_MASK; + break; + + default: /*ENC_ROT-> */ + pr_debug("Unlink Invalid->"); + break; + } + + /*clear the dest_ch's input source */ + switch (in_dma) { + /*->VF_ROT*/ + case IDMA_IC_11: + pr_debug("VF_ROT\n"); + fs_proc_flow &= ~FS_PRPVF_ROT_SRC_SEL; + break; + + /*->Enc_ROT*/ + case IDMA_IC_10: + pr_debug("ENC_ROT\n"); + fs_proc_flow &= ~FS_PRPENC_ROT_SRC_SEL; + break; + + /*->PP*/ + case IDMA_IC_5: + pr_debug("PP\n"); + fs_proc_flow &= ~FS_PP_SRC_SEL_MASK; + break; + + /*->PP_ROT*/ + case IDMA_IC_13: + pr_debug("PP_ROT\n"); + fs_proc_flow &= ~FS_PP_ROT_SRC_SEL_MASK; + break; + + /*->SDC_FG*/ + case IDMA_SDC_FG: + pr_debug("SDC_FG\n"); + fs_disp_flow &= ~FS_SDC_FG_SRC_SEL_MASK; + break; + + /*->SDC_BG*/ + case IDMA_SDC_BG: + pr_debug("SDC_BG\n"); + fs_disp_flow &= ~FS_SDC_BG_SRC_SEL_MASK; + break; + + /*->ADC1*/ + case IDMA_ADC_SYS1_WR: + pr_debug("ADC_SYS1\n"); + fs_disp_flow &= ~FS_ADC1_SRC_SEL_MASK; + break; + + /*->ADC2*/ + case IDMA_ADC_SYS2_WR: + pr_debug("ADC_SYS2\n"); + fs_disp_flow &= ~FS_ADC2_SRC_SEL_MASK; + break; + + default: /*->VF, ->ENC, ->VF_COMBINE, ->PP_COMBINE, ->PF*/ + pr_debug("Invalid\n"); + break; + } + + __raw_writel(fs_proc_flow, IPU_FS_PROC_FLOW); + __raw_writel(fs_disp_flow, IPU_FS_DISP_FLOW); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +/*! + * This function check whether a logical channel was enabled. + * + * @param channel Input parameter for the logical channel ID. + * + * @return This function returns 1 while request channel is enabled or + * 0 for not enabled. + */ +int32_t ipu_is_channel_busy(ipu_channel_t channel) +{ + uint32_t reg; + uint32_t in_dma; + uint32_t out_dma; + + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + in_dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + + reg = __raw_readl(IDMAC_CHA_EN); + + if (reg & ((1UL << out_dma) | (1UL << in_dma))) + return 1; + return 0; +} +EXPORT_SYMBOL(ipu_is_channel_busy); + +/*! + * This function enables a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_enable_channel(ipu_channel_t channel) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t in_dma; + uint32_t sec_dma; + uint32_t out_dma; + uint32_t chan_mask = 0; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IDMAC_CHA_EN); + + /* Get input and output dma channels */ + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + if (out_dma != IDMA_CHAN_INVALID) + reg |= 1UL << out_dma; + in_dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + if (in_dma != IDMA_CHAN_INVALID) + reg |= 1UL << in_dma; + + /* Get secondary input dma channel */ + if (g_sec_chan_en[IPU_CHAN_ID(channel)]) { + sec_dma = channel_2_dma(channel, IPU_SEC_INPUT_BUFFER); + if (sec_dma != IDMA_CHAN_INVALID) { + reg |= 1UL << sec_dma; + } + } + + __raw_writel(reg | chan_mask, IDMAC_CHA_EN); + + if (IPU_CHAN_ID(channel) <= IPU_CHAN_ID(MEM_PP_ADC)) { + _ipu_ic_enable_task(channel); + } else if (channel == MEM_SDC_BG) { + dev_dbg(g_ipu_dev, "Initializing SDC BG\n"); + _ipu_sdc_bg_init(NULL); + } else if (channel == MEM_SDC_FG) { + dev_dbg(g_ipu_dev, "Initializing SDC FG\n"); + _ipu_sdc_fg_init(NULL); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +/*! + * This function disables a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param wait_for_stop Flag to set whether to wait for channel end + * of frame or return immediately. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_disable_channel(ipu_channel_t channel, bool wait_for_stop) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t sec_dma; + uint32_t in_dma; + uint32_t out_dma; + uint32_t chan_mask = 0; + uint32_t timeout; + uint32_t eof_intr; + uint32_t enabled; + + /* Get input and output dma channels */ + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + if (out_dma != IDMA_CHAN_INVALID) + chan_mask = 1UL << out_dma; + in_dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + if (in_dma != IDMA_CHAN_INVALID) + chan_mask |= 1UL << in_dma; + sec_dma = channel_2_dma(channel, IPU_SEC_INPUT_BUFFER); + if (sec_dma != IDMA_CHAN_INVALID) + chan_mask |= 1UL << sec_dma; + + if (wait_for_stop && channel != MEM_SDC_FG && channel != MEM_SDC_BG) { + timeout = 40; + while ((__raw_readl(IDMAC_CHA_BUSY) & chan_mask) || + (_ipu_channel_status(channel) == TASK_STAT_ACTIVE)) { + timeout--; + msleep(10); + if (timeout == 0) { + printk + (KERN_INFO + "MXC IPU: Warning - timeout waiting for channel to stop,\n" + "\tbuf0_rdy = 0x%08X, buf1_rdy = 0x%08X\n" + "\tbusy = 0x%08X, tstat = 0x%08X\n\tmask = 0x%08X\n", + __raw_readl(IPU_CHA_BUF0_RDY), + __raw_readl(IPU_CHA_BUF1_RDY), + __raw_readl(IDMAC_CHA_BUSY), + __raw_readl(IPU_TASKS_STAT), chan_mask); + break; + } + } + dev_dbg(g_ipu_dev, "timeout = %d * 10ms\n", 40 - timeout); + } + /* SDC BG and FG must be disabled before DMA is disabled */ + if ((channel == MEM_SDC_BG) || (channel == MEM_SDC_FG)) { + + if (channel == MEM_SDC_BG) + eof_intr = IPU_IRQ_SDC_BG_EOF; + else + eof_intr = IPU_IRQ_SDC_FG_EOF; + + /* Wait for any buffer flips to finsh */ + timeout = 4; + while (timeout && + ((__raw_readl(IPU_CHA_BUF0_RDY) & chan_mask) || + (__raw_readl(IPU_CHA_BUF1_RDY) & chan_mask))) { + msleep(10); + timeout--; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + ipu_clear_irq(eof_intr); + if (channel == MEM_SDC_BG) + enabled = _ipu_sdc_bg_uninit(); + else + enabled = _ipu_sdc_fg_uninit(); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + if (enabled && wait_for_stop) { + timeout = 5; + } else { + timeout = 0; + } + while (timeout && !ipu_get_irq_status(eof_intr)) { + msleep(5); + timeout--; + } + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + /* Disable IC task */ + if (IPU_CHAN_ID(channel) <= IPU_CHAN_ID(MEM_PP_ADC)) { + _ipu_ic_disable_task(channel); + } + + /* Disable DMA channel(s) */ + reg = __raw_readl(IDMAC_CHA_EN); + __raw_writel(reg & ~chan_mask, IDMAC_CHA_EN); + + /* Clear DMA related interrupts */ + __raw_writel(chan_mask, IPU_INT_STAT_1); + __raw_writel(chan_mask, IPU_INT_STAT_2); + __raw_writel(chan_mask, IPU_INT_STAT_4); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +static +irqreturn_t ipu_irq_handler(int irq, void *desc) +{ + uint32_t line_base = 0; + uint32_t line; + irqreturn_t result = IRQ_NONE; + uint32_t int_stat; + + int_stat = __raw_readl(IPU_INT_STAT_1); + int_stat &= __raw_readl(IPU_INT_CTRL_1); + __raw_writel(int_stat, IPU_INT_STAT_1); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 32; + int_stat = __raw_readl(IPU_INT_STAT_2); + int_stat &= __raw_readl(IPU_INT_CTRL_2); + __raw_writel(int_stat, IPU_INT_STAT_2); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 64; + int_stat = __raw_readl(IPU_INT_STAT_3); + int_stat &= __raw_readl(IPU_INT_CTRL_3); + __raw_writel(int_stat, IPU_INT_STAT_3); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 96; + int_stat = __raw_readl(IPU_INT_STAT_4); + int_stat &= __raw_readl(IPU_INT_CTRL_4); + __raw_writel(int_stat, IPU_INT_STAT_4); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 128; + int_stat = __raw_readl(IPU_INT_STAT_5); + int_stat &= __raw_readl(IPU_INT_CTRL_5); + __raw_writel(int_stat, IPU_INT_STAT_5); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + return result; +} + +/*! + * This function enables the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to enable interrupt for. + * + */ +void ipu_enable_irq(uint32_t irq) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPUIRQ_2_CTRLREG(irq)); + reg |= IPUIRQ_2_MASK(irq); + __raw_writel(reg, IPUIRQ_2_CTRLREG(irq)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * This function disables the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to disable interrupt for. + * + */ +void ipu_disable_irq(uint32_t irq) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPUIRQ_2_CTRLREG(irq)); + reg &= ~IPUIRQ_2_MASK(irq); + __raw_writel(reg, IPUIRQ_2_CTRLREG(irq)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * This function clears the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to clear interrupt for. + * + */ +void ipu_clear_irq(uint32_t irq) +{ + __raw_writel(IPUIRQ_2_MASK(irq), IPUIRQ_2_STATREG(irq)); +} + +/*! + * This function returns the current interrupt status for the specified interrupt + * line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @return Returns true if the interrupt is pending/asserted or false if + * the interrupt is not pending. + */ +bool ipu_get_irq_status(uint32_t irq) +{ + uint32_t reg = __raw_readl(IPUIRQ_2_STATREG(irq)); + + if (reg & IPUIRQ_2_MASK(irq)) + return true; + else + return false; +} + +/*! + * This function registers an interrupt handler function for the specified + * interrupt line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @param handler Input parameter for address of the handler + * function. + * + * @param irq_flags Flags for interrupt mode. Currently not used. + * + * @param devname Input parameter for string name of driver + * registering the handler. + * + * @param dev_id Input parameter for pointer of data to be passed + * to the handler. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int ipu_request_irq(uint32_t irq, + irqreturn_t(*handler) (int, void *), + uint32_t irq_flags, const char *devname, void *dev_id) +{ + unsigned long lock_flags; + + BUG_ON(irq >= IPU_IRQ_COUNT); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (ipu_irq_list[irq].handler != NULL) { + dev_err(g_ipu_dev, + "ipu_request_irq - handler already installed on irq %d\n", + irq); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + + ipu_irq_list[irq].handler = handler; + ipu_irq_list[irq].flags = irq_flags; + ipu_irq_list[irq].dev_id = dev_id; + ipu_irq_list[irq].name = devname; + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + ipu_enable_irq(irq); /* enable the interrupt */ + + return 0; +} + +/*! + * This function unregisters an interrupt handler for the specified interrupt + * line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @param dev_id Input parameter for pointer of data to be passed + * to the handler. This must match value passed to + * ipu_request_irq(). + * + */ +void ipu_free_irq(uint32_t irq, void *dev_id) +{ + ipu_disable_irq(irq); /* disable the interrupt */ + + if (ipu_irq_list[irq].dev_id == dev_id) { + ipu_irq_list[irq].handler = NULL; + } +} + +/*! + * This function sets the post-filter pause row for h.264 mode. + * + * @param pause_row The last row to process before pausing. + * + * @return This function returns 0 on success or negative error code on + * fail. + * + */ +int32_t ipu_pf_set_pause_row(uint32_t pause_row) +{ + int32_t retval = 0; + uint32_t timeout = 5; + unsigned long lock_flags; + uint32_t reg; + + reg = __raw_readl(IPU_TASKS_STAT); + while ((reg & TSTAT_PF_MASK) && ((reg & TSTAT_PF_H264_PAUSE) == 0)) { + timeout--; + msleep(5); + if (timeout == 0) { + dev_err(g_ipu_dev, "PF Timeout - tstat = 0x%08X\n", + __raw_readl(IPU_TASKS_STAT)); + retval = -ETIMEDOUT; + goto err0; + } + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(PF_CONF); + + /* Set the pause row */ + if (pause_row) { + reg &= ~PF_CONF_PAUSE_ROW_MASK; + reg |= PF_CONF_PAUSE_EN | pause_row << PF_CONF_PAUSE_ROW_SHIFT; + } else { + reg &= ~(PF_CONF_PAUSE_EN | PF_CONF_PAUSE_ROW_MASK); + } + __raw_writel(reg, PF_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + err0: + return retval; +} + +/* Private functions */ +void _ipu_write_param_mem(uint32_t addr, uint32_t * data, uint32_t numWords) +{ + for (; numWords > 0; numWords--) { + dev_dbg(g_ipu_dev, + "write param mem - addr = 0x%08X, data = 0x%08X\n", + addr, *data); + __raw_writel(addr, IPU_IMA_ADDR); + __raw_writel(*data++, IPU_IMA_DATA); + addr++; + if ((addr & 0x7) == 5) { + addr &= ~0x7; /* set to word 0 */ + addr += 8; /* increment to next row */ + } + } +} + +static void _ipu_pf_init(ipu_channel_params_t * params) +{ + uint32_t reg; + + /*Setup the type of filtering required */ + switch (params->mem_pf_mem.operation) { + case PF_MPEG4_DEBLOCK: + case PF_MPEG4_DERING: + case PF_MPEG4_DEBLOCK_DERING: + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = true; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = false; + break; + case PF_H264_DEBLOCK: + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = true; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = true; + break; + default: + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = false; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = false; + return; + break; + } + reg = params->mem_pf_mem.operation; + __raw_writel(reg, PF_CONF); +} + +static void _ipu_pf_uninit(void) +{ + __raw_writel(0x0L, PF_CONF); + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = false; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = false; +} + +uint32_t _ipu_channel_status(ipu_channel_t channel) +{ + uint32_t stat = 0; + uint32_t task_stat_reg = __raw_readl(IPU_TASKS_STAT); + + switch (channel) { + case CSI_MEM: + stat = + (task_stat_reg & TSTAT_CSI2MEM_MASK) >> + TSTAT_CSI2MEM_OFFSET; + break; + case CSI_PRP_VF_ADC: + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_ADC: + case MEM_PRP_VF_MEM: + stat = (task_stat_reg & TSTAT_VF_MASK) >> TSTAT_VF_OFFSET; + break; + case MEM_ROT_VF_MEM: + stat = + (task_stat_reg & TSTAT_VF_ROT_MASK) >> TSTAT_VF_ROT_OFFSET; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + stat = (task_stat_reg & TSTAT_ENC_MASK) >> TSTAT_ENC_OFFSET; + break; + case MEM_ROT_ENC_MEM: + stat = + (task_stat_reg & TSTAT_ENC_ROT_MASK) >> + TSTAT_ENC_ROT_OFFSET; + break; + case MEM_PP_ADC: + case MEM_PP_MEM: + stat = (task_stat_reg & TSTAT_PP_MASK) >> TSTAT_PP_OFFSET; + break; + case MEM_ROT_PP_MEM: + stat = + (task_stat_reg & TSTAT_PP_ROT_MASK) >> TSTAT_PP_ROT_OFFSET; + break; + + case MEM_PF_Y_MEM: + case MEM_PF_U_MEM: + case MEM_PF_V_MEM: + stat = (task_stat_reg & TSTAT_PF_MASK) >> TSTAT_PF_OFFSET; + break; + case MEM_SDC_BG: + break; + case MEM_SDC_FG: + break; + case ADC_SYS1: + stat = + (task_stat_reg & TSTAT_ADCSYS1_MASK) >> + TSTAT_ADCSYS1_OFFSET; + break; + case ADC_SYS2: + stat = + (task_stat_reg & TSTAT_ADCSYS2_MASK) >> + TSTAT_ADCSYS2_OFFSET; + break; + case MEM_SDC_MASK: + default: + stat = TASK_STAT_IDLE; + break; + } + return stat; +} + +uint32_t bytes_per_pixel(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_GENERIC: /*generic data */ + case IPU_PIX_FMT_RGB332: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YUV422P: + return 1; + break; + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_YUYV: + case IPU_PIX_FMT_UYVY: + return 2; + break; + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + return 3; + break; + case IPU_PIX_FMT_GENERIC_32: /*generic data */ + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_RGB32: + case IPU_PIX_FMT_ABGR32: + return 4; + break; + default: + return 1; + break; + } + return 0; +} + +void ipu_set_csc_coefficients(ipu_channel_t channel, int32_t param[][3]) +{ + /* TODO */ +} +EXPORT_SYMBOL(ipu_set_csc_coefficients); + +ipu_color_space_t format_to_colorspace(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_RGB32: + return RGB; + break; + + default: + return YCbCr; + break; + } + return RGB; + +} + +static u32 saved_disp3_time_conf; +static bool in_lpdr_mode; +static struct clk *default_ipu_parent_clk; + +int ipu_lowpwr_display_enable(void) +{ + unsigned long rate, div; + struct clk *parent_clk = g_ipu_clk; + + if (in_lpdr_mode || IS_ERR(dfm_clk)) { + return -EINVAL; + } + + if (g_channel_init_mask != (1L << IPU_CHAN_ID(MEM_SDC_BG))) { + dev_err(g_ipu_dev, "LPDR mode requires only SDC BG active.\n"); + return -EINVAL; + } + + default_ipu_parent_clk = clk_get_parent(g_ipu_clk); + in_lpdr_mode = true; + + /* Calculate current pixel clock rate */ + rate = clk_get_rate(g_ipu_clk) * 16; + saved_disp3_time_conf = __raw_readl(DI_DISP3_TIME_CONF); + div = saved_disp3_time_conf & 0xFFF; + rate /= div; + rate *= 4; /* min hsp clk is 4x pixel clk */ + + /* Initialize DFM clock */ + rate = clk_round_rate(dfm_clk, rate); + clk_set_rate(dfm_clk, rate); + clk_enable(dfm_clk); + + /* Wait for next VSYNC */ + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC), + IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)); + while ((__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)) & + IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC)) == 0) + msleep_interruptible(1); + + /* Set display clock divider to divide by 4 */ + __raw_writel(((0x8) << 22) | 0x40, DI_DISP3_TIME_CONF); + + clk_set_parent(parent_clk, dfm_clk); + + return 0; +} + +int ipu_lowpwr_display_disable(void) +{ + struct clk *parent_clk = g_ipu_clk; + + if (!in_lpdr_mode || IS_ERR(dfm_clk)) { + return -EINVAL; + } + + if (g_channel_init_mask != (1L << IPU_CHAN_ID(MEM_SDC_BG))) { + dev_err(g_ipu_dev, "LPDR mode requires only SDC BG active.\n"); + return -EINVAL; + } + + in_lpdr_mode = false; + + /* Wait for next VSYNC */ + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC), + IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)); + while ((__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)) & + IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC)) == 0) + msleep_interruptible(1); + + __raw_writel(saved_disp3_time_conf, DI_DISP3_TIME_CONF); + clk_set_parent(parent_clk, default_ipu_parent_clk); + clk_disable(dfm_clk); + + return 0; +} + +static int ipu_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (cpu_is_mx31() || cpu_is_mx32()) { + /* work-around for i.Mx31 SR mode after camera related test */ + if (g_csi_used) { + g_ipu_config = __raw_readl(IPU_CONF); + clk_enable(g_ipu_csi_clk); + __raw_writel(0x51, IPU_CONF); + g_channel_init_mask_backup = g_channel_init_mask; + g_channel_init_mask |= 2; + } + } else if (cpu_is_mx35()) { + g_ipu_config = __raw_readl(IPU_CONF); + /* Disable the clock of display otherwise the backlight cannot + * be close after camera/tvin related test */ + __raw_writel(g_ipu_config & 0xfbf, IPU_CONF); + } + + return 0; +} + +static int ipu_resume(struct platform_device *pdev) +{ + if (cpu_is_mx31() || cpu_is_mx32()) { + /* work-around for i.Mx31 SR mode after camera related test */ + if (g_csi_used) { + __raw_writel(g_ipu_config, IPU_CONF); + clk_disable(g_ipu_csi_clk); + g_channel_init_mask = g_channel_init_mask_backup; + } + } else if (cpu_is_mx35()) { + __raw_writel(g_ipu_config, IPU_CONF); + } + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcipu_driver = { + .driver = { + .name = "mxc_ipu", + }, + .probe = ipu_probe, + .suspend = ipu_suspend, + .resume = ipu_resume, +}; + +int32_t __init ipu_gen_init(void) +{ + int32_t ret; + + ret = platform_driver_register(&mxcipu_driver); + return 0; +} + +subsys_initcall(ipu_gen_init); + +static void __exit ipu_gen_uninit(void) +{ + if (g_ipu_irq[0]) + free_irq(g_ipu_irq[0], 0); + if (g_ipu_irq[1]) + free_irq(g_ipu_irq[1], 0); + + platform_driver_unregister(&mxcipu_driver); +} + +module_exit(ipu_gen_uninit); + +EXPORT_SYMBOL(ipu_init_channel); +EXPORT_SYMBOL(ipu_uninit_channel); +EXPORT_SYMBOL(ipu_init_channel_buffer); +EXPORT_SYMBOL(ipu_unlink_channels); +EXPORT_SYMBOL(ipu_update_channel_buffer); +EXPORT_SYMBOL(ipu_select_buffer); +EXPORT_SYMBOL(ipu_link_channels); +EXPORT_SYMBOL(ipu_enable_channel); +EXPORT_SYMBOL(ipu_disable_channel); +EXPORT_SYMBOL(ipu_enable_irq); +EXPORT_SYMBOL(ipu_disable_irq); +EXPORT_SYMBOL(ipu_clear_irq); +EXPORT_SYMBOL(ipu_get_irq_status); +EXPORT_SYMBOL(ipu_request_irq); +EXPORT_SYMBOL(ipu_free_irq); +EXPORT_SYMBOL(ipu_pf_set_pause_row); +EXPORT_SYMBOL(bytes_per_pixel); diff --git a/drivers/mxc/ipu/ipu_csi.c b/drivers/mxc/ipu/ipu_csi.c new file mode 100644 index 000000000000..10708cf3ba54 --- /dev/null +++ b/drivers/mxc/ipu/ipu_csi.c @@ -0,0 +1,222 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_csi.c + * + * @brief IPU CMOS Sensor interface functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" + +static bool gipu_csi_get_mclk_flag = false; +static int csi_mclk_flag = 0; + +extern void gpio_sensor_suspend(bool flag); + +/*! + * ipu_csi_init_interface + * Sets initial values for the CSI registers. + * The width and height of the sensor and the actual frame size will be + * set to the same values. + * @param width Sensor width + * @param height Sensor height + * @param pixel_fmt pixel format + * @param sig ipu_csi_signal_cfg_t structure + * + * @return 0 for success, -EINVAL for error + */ +int32_t +ipu_csi_init_interface(uint16_t width, uint16_t height, uint32_t pixel_fmt, + ipu_csi_signal_cfg_t sig) +{ + uint32_t data = 0; + + /* Set SENS_DATA_FORMAT bits (8 and 9) + RGB or YUV444 is 0 which is current value in data so not set explicitly + This is also the default value if attempts are made to set it to + something invalid. */ + switch (pixel_fmt) { + case IPU_PIX_FMT_UYVY: + data = CSI_SENS_CONF_DATA_FMT_YUV422; + break; + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_BGR24: + data = CSI_SENS_CONF_DATA_FMT_RGB_YUV444; + break; + case IPU_PIX_FMT_GENERIC: + data = CSI_SENS_CONF_DATA_FMT_BAYER; + break; + default: + return -EINVAL; + } + + /* Set the CSI_SENS_CONF register remaining fields */ + data |= sig.data_width << CSI_SENS_CONF_DATA_WIDTH_SHIFT | + sig.data_pol << CSI_SENS_CONF_DATA_POL_SHIFT | + sig.Vsync_pol << CSI_SENS_CONF_VSYNC_POL_SHIFT | + sig.Hsync_pol << CSI_SENS_CONF_HSYNC_POL_SHIFT | + sig.pixclk_pol << CSI_SENS_CONF_PIX_CLK_POL_SHIFT | + sig.ext_vsync << CSI_SENS_CONF_EXT_VSYNC_SHIFT | + sig.clk_mode << CSI_SENS_CONF_SENS_PRTCL_SHIFT | + sig.sens_clksrc << CSI_SENS_CONF_SENS_CLKSRC_SHIFT; + + __raw_writel(data, CSI_SENS_CONF); + + /* Setup frame size */ + __raw_writel(width | height << 16, CSI_SENS_FRM_SIZE); + + __raw_writel(width << 16, CSI_FLASH_STROBE_1); + __raw_writel(height << 16 | 0x22, CSI_FLASH_STROBE_2); + + /* Set CCIR registers */ + if ((sig.clk_mode == IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE) || + (sig.clk_mode == IPU_CSI_CLK_MODE_CCIR656_INTERLACED)) { + __raw_writel(0x40030, CSI_CCIR_CODE_1); + __raw_writel(0xFF0000, CSI_CCIR_CODE_3); + } + + dev_dbg(g_ipu_dev, "CSI_SENS_CONF = 0x%08X\n", + __raw_readl(CSI_SENS_CONF)); + dev_dbg(g_ipu_dev, "CSI_ACT_FRM_SIZE = 0x%08X\n", + __raw_readl(CSI_ACT_FRM_SIZE)); + + return 0; +} + +/*! + * ipu_csi_flash_strobe + * + * @param flag true to turn on flash strobe + * + * @return 0 for success + */ +void ipu_csi_flash_strobe(bool flag) +{ + if (flag == true) { + __raw_writel(__raw_readl(CSI_FLASH_STROBE_2) | 0x1, + CSI_FLASH_STROBE_2); + } +} + +/*! + * ipu_csi_enable_mclk + * + * @param src enum define which source to control the clk + * CSI_MCLK_VF CSI_MCLK_ENC CSI_MCLK_RAW CSI_MCLK_I2C + * @param flag true to enable mclk, false to disable mclk + * @param wait true to wait 100ms make clock stable, false not wait + * + * @return 0 for success + */ +int32_t ipu_csi_enable_mclk(int src, bool flag, bool wait) +{ + if (flag == true) { + csi_mclk_flag |= src; + } else { + csi_mclk_flag &= ~src; + } + + if (gipu_csi_get_mclk_flag == flag) + return 0; + + if (flag == true) { + clk_enable(g_ipu_csi_clk); + if (wait == true) + msleep(10); + /*printk("enable csi clock from source %d\n", src); */ + gipu_csi_get_mclk_flag = true; + } else if (csi_mclk_flag == 0) { + clk_disable(g_ipu_csi_clk); + /*printk("disable csi clock from source %d\n", src); */ + gipu_csi_get_mclk_flag = flag; + } + + return 0; +} + +/*! + * ipu_csi_read_mclk_flag + * + * @return csi_mclk_flag + */ +int ipu_csi_read_mclk_flag(void) +{ + return csi_mclk_flag; +} + +/*! + * ipu_csi_get_window_size + * + * @param width pointer to window width + * @param height pointer to window height + * @param dummy dummy for IPUv1 to keep the same interface with IPUv3 + * + */ +void ipu_csi_get_window_size(uint32_t *width, uint32_t *height, + uint32_t dummy) +{ + uint32_t reg; + + reg = __raw_readl(CSI_ACT_FRM_SIZE); + *width = (reg & 0xFFFF) + 1; + *height = (reg >> 16 & 0xFFFF) + 1; +} + +/*! + * ipu_csi_set_window_size + * + * @param width window width + * @param height window height + * @param dummy dummy for IPUv1 to keep the same interface with IPUv3 + * + */ +void ipu_csi_set_window_size(uint32_t width, uint32_t height, uint32_t dummy) +{ + __raw_writel((width - 1) | (height - 1) << 16, CSI_ACT_FRM_SIZE); +} + +/*! + * ipu_csi_set_window_pos + * + * @param left uint32 window x start + * @param top uint32 window y start + * @param dummy dummy for IPUv1 to keep the same interface with IPUv3 + * + */ +void ipu_csi_set_window_pos(uint32_t left, uint32_t top, uint32_t dummy) +{ + uint32_t temp = __raw_readl(CSI_OUT_FRM_CTRL); + temp &= 0xffff0000; + temp = top | (left << 8) | temp; + __raw_writel(temp, CSI_OUT_FRM_CTRL); +} + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(ipu_csi_set_window_pos); +EXPORT_SYMBOL(ipu_csi_set_window_size); +EXPORT_SYMBOL(ipu_csi_get_window_size); +EXPORT_SYMBOL(ipu_csi_read_mclk_flag); +EXPORT_SYMBOL(ipu_csi_enable_mclk); +EXPORT_SYMBOL(ipu_csi_flash_strobe); +EXPORT_SYMBOL(ipu_csi_init_interface); diff --git a/drivers/mxc/ipu/ipu_device.c b/drivers/mxc/ipu/ipu_device.c new file mode 100644 index 000000000000..5fd1c51ec9b1 --- /dev/null +++ b/drivers/mxc/ipu/ipu_device.c @@ -0,0 +1,696 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_device.c + * + * @brief This file contains the IPU driver device interface and fops functions. + * + * @ingroup IPU + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +/* Strucutures and variables for exporting MXC IPU as device*/ + +#define MAX_Q_SIZE 10 + +static int mxc_ipu_major; +static struct class *mxc_ipu_class; + +DEFINE_SPINLOCK(queue_lock); +static DECLARE_MUTEX(user_mutex); + +static wait_queue_head_t waitq; +static int pending_events = 0; +int read_ptr = 0; +int write_ptr = 0; + +ipu_event_info events[MAX_Q_SIZE]; + +int register_ipu_device(void); + +/* Static functions */ + +int get_events(ipu_event_info *p) +{ + unsigned long flags; + int ret = 0, i, cnt, found = 0; + spin_lock_irqsave(&queue_lock, flags); + if (pending_events != 0) { + if (write_ptr > read_ptr) + cnt = write_ptr - read_ptr; + else + cnt = MAX_Q_SIZE - read_ptr + write_ptr; + for (i = 0; i < cnt; i++) { + if (p->irq == events[read_ptr].irq) { + *p = events[read_ptr]; + events[read_ptr].irq = 0; + read_ptr++; + if (read_ptr >= MAX_Q_SIZE) + read_ptr = 0; + found = 1; + break; + } + + if (events[read_ptr].irq) { + events[write_ptr] = events[read_ptr]; + events[read_ptr].irq = 0; + write_ptr++; + if (write_ptr >= MAX_Q_SIZE) + write_ptr = 0; + } else + pending_events--; + + read_ptr++; + if (read_ptr >= MAX_Q_SIZE) + read_ptr = 0; + } + if (found) + pending_events--; + else + ret = -1; + } else { + ret = -1; + } + + spin_unlock_irqrestore(&queue_lock, flags); + return ret; +} + +static irqreturn_t mxc_ipu_generic_handler(int irq, void *dev_id) +{ + ipu_event_info e; + + e.irq = irq; + e.dev = dev_id; + events[write_ptr] = e; + write_ptr++; + if (write_ptr >= MAX_Q_SIZE) + write_ptr = 0; + pending_events++; + /* Wakeup any blocking user context */ + wake_up_interruptible(&waitq); + return IRQ_HANDLED; +} + +static int mxc_ipu_open(struct inode *inode, struct file *file) +{ + int ret = 0; + return ret; +} +static int mxc_ipu_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + switch (cmd) { + + case IPU_INIT_CHANNEL: + { + ipu_channel_parm parm; + if (copy_from_user + (&parm, (ipu_channel_parm *) arg, + sizeof(ipu_channel_parm))) { + return -EFAULT; + } + if (!parm.flag) { + ret = + ipu_init_channel(parm.channel, + &parm.params); + } else { + ret = ipu_init_channel(parm.channel, NULL); + } + } + break; + + case IPU_UNINIT_CHANNEL: + { + ipu_channel_t ch; + int __user *argp = (void __user *)arg; + if (get_user(ch, argp)) + return -EFAULT; + ipu_uninit_channel(ch); + } + break; + + case IPU_INIT_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) { + return -EFAULT; + } + ret = + ipu_init_channel_buffer(parm.channel, parm.type, + parm.pixel_fmt, + parm.width, parm.height, + parm.stride, + parm.rot_mode, + parm.phyaddr_0, + parm.phyaddr_1, + parm.u_offset, + parm.v_offset); + + } + break; + + case IPU_UPDATE_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) { + return -EFAULT; + } + if ((parm.phyaddr_0 != (dma_addr_t) NULL) + && (parm.phyaddr_1 == (dma_addr_t) NULL)) { + ret = + ipu_update_channel_buffer(parm.channel, + parm.type, + parm.bufNum, + parm.phyaddr_0); + } else if ((parm.phyaddr_0 == (dma_addr_t) NULL) + && (parm.phyaddr_1 != (dma_addr_t) NULL)) { + ret = + ipu_update_channel_buffer(parm.channel, + parm.type, + parm.bufNum, + parm.phyaddr_1); + } else { + ret = -1; + } + + } + break; + case IPU_SELECT_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) { + return -EFAULT; + } + ret = + ipu_select_buffer(parm.channel, parm.type, + parm.bufNum); + + } + break; + case IPU_LINK_CHANNELS: + { + ipu_channel_link link; + if (copy_from_user + (&link, (ipu_channel_link *) arg, + sizeof(ipu_channel_link))) { + return -EFAULT; + } + ret = ipu_link_channels(link.src_ch, link.dest_ch); + + } + break; + case IPU_UNLINK_CHANNELS: + { + ipu_channel_link link; + if (copy_from_user + (&link, (ipu_channel_link *) arg, + sizeof(ipu_channel_link))) { + return -EFAULT; + } + ret = ipu_unlink_channels(link.src_ch, link.dest_ch); + + } + break; + case IPU_ENABLE_CHANNEL: + { + ipu_channel_t ch; + int __user *argp = (void __user *)arg; + if (get_user(ch, argp)) + return -EFAULT; + ipu_enable_channel(ch); + } + break; + case IPU_DISABLE_CHANNEL: + { + ipu_channel_info info; + if (copy_from_user + (&info, (ipu_channel_info *) arg, + sizeof(ipu_channel_info))) { + return -EFAULT; + } + ret = ipu_disable_channel(info.channel, info.stop); + } + break; + case IPU_ENABLE_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_enable_irq(irq); + } + break; + case IPU_DISABLE_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_disable_irq(irq); + } + break; + case IPU_CLEAR_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_clear_irq(irq); + } + break; + case IPU_FREE_IRQ: + { + ipu_irq_info info; + if (copy_from_user + (&info, (ipu_irq_info *) arg, + sizeof(ipu_irq_info))) { + return -EFAULT; + } + ipu_free_irq(info.irq, info.dev_id); + } + break; + case IPU_REQUEST_IRQ_STATUS: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ret = ipu_get_irq_status(irq); + } + break; + case IPU_SDC_INIT_PANEL: + { + ipu_sdc_panel_info sinfo; + if (copy_from_user + (&sinfo, (ipu_sdc_panel_info *) arg, + sizeof(ipu_sdc_panel_info))) { + return -EFAULT; + } + ret = + ipu_sdc_init_panel(sinfo.panel, sinfo.pixel_clk, + sinfo.width, sinfo.height, + sinfo.pixel_fmt, + sinfo.hStartWidth, + sinfo.hSyncWidth, + sinfo.hEndWidth, + sinfo.vStartWidth, + sinfo.vSyncWidth, + sinfo.vEndWidth, sinfo.signal); + } + break; + case IPU_SDC_SET_WIN_POS: + { + ipu_sdc_window_pos pos; + if (copy_from_user + (&pos, (ipu_sdc_window_pos *) arg, + sizeof(ipu_sdc_window_pos))) { + return -EFAULT; + } + ret = + ipu_disp_set_window_pos(pos.channel, pos.x_pos, + pos.y_pos); + + } + break; + case IPU_SDC_SET_GLOBAL_ALPHA: + { + ipu_sdc_global_alpha g; + if (copy_from_user + (&g, (ipu_sdc_global_alpha *) arg, + sizeof(ipu_sdc_global_alpha))) { + return -EFAULT; + } + ret = ipu_sdc_set_global_alpha(g.enable, g.alpha); + } + break; + case IPU_SDC_SET_COLOR_KEY: + { + ipu_sdc_color_key c; + if (copy_from_user + (&c, (ipu_sdc_color_key *) arg, + sizeof(ipu_sdc_color_key))) { + return -EFAULT; + } + ret = + ipu_sdc_set_color_key(c.channel, c.enable, + c.colorKey); + } + break; + case IPU_SDC_SET_BRIGHTNESS: + { + uint8_t b; + int __user *argp = (void __user *)arg; + if (get_user(b, argp)) + return -EFAULT; + ret = ipu_sdc_set_brightness(b); + + } + break; + case IPU_REGISTER_GENERIC_ISR: + { + ipu_event_info info; + if (copy_from_user + (&info, (ipu_event_info *) arg, + sizeof(ipu_event_info))) { + return -EFAULT; + } + ret = + ipu_request_irq(info.irq, mxc_ipu_generic_handler, + 0, "video_sink", info.dev); + } + break; + case IPU_GET_EVENT: + /* User will have to allocate event_type structure and pass the pointer in arg */ + { + ipu_event_info info; + int r = -1; + + if (copy_from_user + (&info, (ipu_event_info *) arg, + sizeof(ipu_event_info))) + return -EFAULT; + + r = get_events(&info); + if (r == -1) { + wait_event_interruptible_timeout(waitq, + (pending_events != 0), 2 * HZ); + r = get_events(&info); + } + ret = -1; + if (r == 0) { + if (!copy_to_user((ipu_event_info *) arg, + &info, sizeof(ipu_event_info))) + ret = 0; + } + } + break; + case IPU_ADC_WRITE_TEMPLATE: + { + ipu_adc_template temp; + if (copy_from_user + (&temp, (ipu_adc_template *) arg, sizeof(temp))) { + return -EFAULT; + } + ret = + ipu_adc_write_template(temp.disp, temp.pCmd, + temp.write); + } + break; + case IPU_ADC_UPDATE: + { + ipu_adc_update update; + if (copy_from_user + (&update, (ipu_adc_update *) arg, sizeof(update))) { + return -EFAULT; + } + ret = + ipu_adc_set_update_mode(update.channel, update.mode, + update.refresh_rate, + update.addr, update.size); + } + break; + case IPU_ADC_SNOOP: + { + ipu_adc_snoop snoop; + if (copy_from_user + (&snoop, (ipu_adc_snoop *) arg, sizeof(snoop))) { + return -EFAULT; + } + ret = + ipu_adc_get_snooping_status(snoop.statl, + snoop.stath); + } + break; + case IPU_ADC_CMD: + { + ipu_adc_cmd cmd; + if (copy_from_user + (&cmd, (ipu_adc_cmd *) arg, sizeof(cmd))) { + return -EFAULT; + } + ret = + ipu_adc_write_cmd(cmd.disp, cmd.type, cmd.cmd, + cmd.params, cmd.numParams); + } + break; + case IPU_ADC_INIT_PANEL: + { + ipu_adc_panel panel; + if (copy_from_user + (&panel, (ipu_adc_panel *) arg, sizeof(panel))) { + return -EFAULT; + } + ret = + ipu_adc_init_panel(panel.disp, panel.width, + panel.height, panel.pixel_fmt, + panel.stride, panel.signal, + panel.addr, panel.vsync_width, + panel.mode); + } + break; + case IPU_ADC_IFC_TIMING: + { + ipu_adc_ifc_timing t; + if (copy_from_user + (&t, (ipu_adc_ifc_timing *) arg, sizeof(t))) { + return -EFAULT; + } + ret = + ipu_adc_init_ifc_timing(t.disp, t.read, + t.cycle_time, t.up_time, + t.down_time, + t.read_latch_time, + t.pixel_clk); + } + break; + case IPU_CSI_INIT_INTERFACE: + { + ipu_csi_interface c; + if (copy_from_user + (&c, (ipu_csi_interface *) arg, sizeof(c))) + return -EFAULT; + ret = + ipu_csi_init_interface(c.width, c.height, + c.pixel_fmt, c.signal); + } + break; + case IPU_CSI_ENABLE_MCLK: + { + ipu_csi_mclk m; + if (copy_from_user(&m, (ipu_csi_mclk *) arg, sizeof(m))) + return -EFAULT; + ret = ipu_csi_enable_mclk(m.src, m.flag, m.wait); + } + break; + case IPU_CSI_READ_MCLK_FLAG: + { + ret = ipu_csi_read_mclk_flag(); + } + break; + case IPU_CSI_FLASH_STROBE: + { + bool strobe; + int __user *argp = (void __user *)arg; + if (get_user(strobe, argp)) + return -EFAULT; + ipu_csi_flash_strobe(strobe); + } + break; + case IPU_CSI_GET_WIN_SIZE: + { + ipu_csi_window_size w; + int dummy = 0; + ipu_csi_get_window_size(&w.width, &w.height, dummy); + if (copy_to_user + ((ipu_csi_window_size *) arg, &w, sizeof(w))) + return -EFAULT; + } + break; + case IPU_CSI_SET_WIN_SIZE: + { + ipu_csi_window_size w; + int dummy = 0; + if (copy_from_user + (&w, (ipu_csi_window_size *) arg, sizeof(w))) + return -EFAULT; + ipu_csi_set_window_size(w.width, w.height, dummy); + } + break; + case IPU_CSI_SET_WINDOW: + { + ipu_csi_window p; + int dummy = 0; + if (copy_from_user + (&p, (ipu_csi_window *) arg, sizeof(p))) + return -EFAULT; + ipu_csi_set_window_pos(p.left, p.top, dummy); + } + break; + case IPU_PF_SET_PAUSE_ROW: + { + uint32_t p; + int __user *argp = (void __user *)arg; + if (get_user(p, argp)) + return -EFAULT; + ret = ipu_pf_set_pause_row(p); + } + break; + case IPU_ALOC_MEM: + { + ipu_mem_info info; + if (copy_from_user + (&info, (ipu_mem_info *) arg, + sizeof(ipu_mem_info))) + return -EFAULT; + + info.vaddr = dma_alloc_coherent(0, + PAGE_ALIGN(info.size), + &info.paddr, + GFP_DMA | GFP_KERNEL); + if (info.vaddr == 0) { + printk(KERN_ERR "dma alloc failed!\n"); + return -ENOBUFS; + } + if (copy_to_user((ipu_mem_info *) arg, &info, + sizeof(ipu_mem_info)) > 0) + return -EFAULT; + } + break; + case IPU_FREE_MEM: + { + ipu_mem_info info; + if (copy_from_user + (&info, (ipu_mem_info *) arg, + sizeof(ipu_mem_info))) + return -EFAULT; + + if (info.vaddr != 0) + dma_free_coherent(0, PAGE_ALIGN(info.size), + info.vaddr, info.paddr); + else + return -EFAULT; + } + break; + case IPU_IS_CHAN_BUSY: + { + ipu_channel_t chan; + if (copy_from_user + (&chan, (ipu_channel_t *)arg, + sizeof(ipu_channel_t))) + return -EFAULT; + + if (ipu_is_channel_busy(chan)) + ret = 1; + else + ret = 0; + } + break; + default: + break; + + } + return ret; +} + +static int mxc_ipu_mmap(struct file *file, struct vm_area_struct *vma) +{ + vma->vm_page_prot = pgprot_writethru(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)) { + printk(KERN_ERR + "mmap failed!\n"); + return -ENOBUFS; + } + return 0; +} + +static int mxc_ipu_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static struct file_operations mxc_ipu_fops = { + .owner = THIS_MODULE, + .open = mxc_ipu_open, + .mmap = mxc_ipu_mmap, + .release = mxc_ipu_release, + .ioctl = mxc_ipu_ioctl +}; + +int register_ipu_device() +{ + int ret = 0; + struct device *temp; + mxc_ipu_major = register_chrdev(0, "mxc_ipu", &mxc_ipu_fops); + if (mxc_ipu_major < 0) { + printk(KERN_ERR + "Unable to register Mxc Ipu as a char device\n"); + return mxc_ipu_major; + } + + mxc_ipu_class = class_create(THIS_MODULE, "mxc_ipu"); + if (IS_ERR(mxc_ipu_class)) { + printk(KERN_ERR "Unable to create class for Mxc Ipu\n"); + ret = PTR_ERR(mxc_ipu_class); + goto err1; + } + + temp = device_create(mxc_ipu_class, NULL, MKDEV(mxc_ipu_major, 0), NULL, + "mxc_ipu"); + + if (IS_ERR(temp)) { + printk(KERN_ERR "Unable to create class device for Mxc Ipu\n"); + ret = PTR_ERR(temp); + goto err2; + } + spin_lock_init(&queue_lock); + init_waitqueue_head(&waitq); + return ret; + + err2: + class_destroy(mxc_ipu_class); + err1: + unregister_chrdev(mxc_ipu_major, "mxc_ipu"); + return ret; + +} diff --git a/drivers/mxc/ipu/ipu_ic.c b/drivers/mxc/ipu/ipu_ic.c new file mode 100644 index 000000000000..cdf823a2760b --- /dev/null +++ b/drivers/mxc/ipu/ipu_ic.c @@ -0,0 +1,592 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * @file ipu_ic.c + * + * @brief IPU IC functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +enum { + IC_TASK_VIEWFINDER, + IC_TASK_ENCODER, + IC_TASK_POST_PROCESSOR +}; + +extern int g_ipu_hw_rev; +static void _init_csc(uint8_t ic_task, ipu_color_space_t in_format, + ipu_color_space_t out_format); +static bool _calc_resize_coeffs(uint32_t inSize, uint32_t outSize, + uint32_t * resizeCoeff, + uint32_t * downsizeCoeff); + +void _ipu_ic_enable_task(ipu_channel_t channel) +{ + uint32_t ic_conf; + + ic_conf = __raw_readl(IC_CONF); + switch (channel) { + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + ic_conf |= IC_CONF_PRPVF_EN; + break; + case MEM_ROT_VF_MEM: + ic_conf |= IC_CONF_PRPVF_ROT_EN; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + ic_conf |= IC_CONF_PRPENC_EN; + break; + case MEM_ROT_ENC_MEM: + ic_conf |= IC_CONF_PRPENC_ROT_EN; + break; + case MEM_PP_ADC: + case MEM_PP_MEM: + ic_conf |= IC_CONF_PP_EN; + break; + case MEM_ROT_PP_MEM: + ic_conf |= IC_CONF_PP_ROT_EN; + break; + case CSI_MEM: + // ??? + ic_conf |= IC_CONF_RWS_EN | IC_CONF_PRPENC_EN; + break; + default: + break; + } + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_disable_task(ipu_channel_t channel) +{ + uint32_t ic_conf; + + ic_conf = __raw_readl(IC_CONF); + switch (channel) { + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_EN; + break; + case MEM_ROT_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_ROT_EN; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + ic_conf &= ~IC_CONF_PRPENC_EN; + break; + case MEM_ROT_ENC_MEM: + ic_conf &= ~IC_CONF_PRPENC_ROT_EN; + break; + case MEM_PP_ADC: + case MEM_PP_MEM: + ic_conf &= ~IC_CONF_PP_EN; + break; + case MEM_ROT_PP_MEM: + ic_conf &= ~IC_CONF_PP_ROT_EN; + break; + case CSI_MEM: + // ??? + ic_conf &= ~(IC_CONF_RWS_EN | IC_CONF_PRPENC_EN); + break; + default: + break; + } + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_init_prpvf(ipu_channel_params_t * params, bool src_is_csi) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_prp_vf_mem.in_height, + params->mem_prp_vf_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_prp_vf_mem.in_width, + params->mem_prp_vf_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PRP_VF_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_prp_vf_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_vf_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + _init_csc(IC_TASK_VIEWFINDER, RGB, out_fmt); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable RGB->YCBCR CSC */ + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + _init_csc(IC_TASK_VIEWFINDER, YCbCr, RGB); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable YCBCR->RGB CSC */ + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_prp_vf_mem.graphics_combine_en) { + ic_conf |= IC_CONF_PRPVF_CMB; + + /* need transparent CSC1 conversion */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, RGB); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable RGB->RGB CSC */ + + if (params->mem_prp_vf_mem.global_alpha_en) { + ic_conf |= IC_CONF_IC_GLB_LOC_A; + } else { + ic_conf &= ~IC_CONF_IC_GLB_LOC_A; + } + + if (params->mem_prp_vf_mem.key_color_en) { + ic_conf |= IC_CONF_KEY_COLOR_EN; + } else { + ic_conf &= ~IC_CONF_KEY_COLOR_EN; + } + } else { + ic_conf &= ~IC_CONF_PP_CMB; + } + +#ifndef CONFIG_VIRTIO_SUPPORT /* Setting RWS_EN doesn't work in Virtio */ + if (src_is_csi) { + ic_conf &= ~IC_CONF_RWS_EN; + } else { + ic_conf |= IC_CONF_RWS_EN; + } +#endif + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_prpvf(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPVF_EN | IC_CONF_PRPVF_CMB | + IC_CONF_PRPVF_CSC2 | IC_CONF_PRPVF_CSC1); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_vf(ipu_channel_params_t * params) +{ +} + +void _ipu_ic_uninit_rotate_vf(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_PRPVF_ROT_EN; + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_csi(ipu_channel_params_t * params) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_CSI_MEM_WR_EN; + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_uninit_csi(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_RWS_EN | IC_CONF_PRPENC_EN); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_prpenc(ipu_channel_params_t * params, bool src_is_csi) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_prp_enc_mem.in_height, + params->mem_prp_enc_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_prp_enc_mem.in_width, + params->mem_prp_enc_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PRP_ENC_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_prp_enc_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_enc_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* TODO: ERROR! */ + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + _init_csc(IC_TASK_ENCODER, YCbCr, RGB); + ic_conf |= IC_CONF_PRPENC_CSC1; /* Enable YCBCR->RGB CSC */ + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (src_is_csi) { + ic_conf &= ~IC_CONF_RWS_EN; + } else { + ic_conf |= IC_CONF_RWS_EN; + } + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_prpenc(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPENC_EN | IC_CONF_PRPENC_CSC1); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_enc(ipu_channel_params_t * params) +{ +} + +void _ipu_ic_uninit_rotate_enc(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPENC_ROT_EN); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_pp(ipu_channel_params_t * params) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_pp_mem.in_height, + params->mem_pp_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_pp_mem.in_width, + params->mem_pp_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PP_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_pp_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_pp_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + _init_csc(IC_TASK_POST_PROCESSOR, RGB, out_fmt); + ic_conf |= IC_CONF_PP_CSC2; /* Enable RGB->YCBCR CSC */ + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + _init_csc(IC_TASK_POST_PROCESSOR, YCbCr, RGB); + ic_conf |= IC_CONF_PP_CSC1; /* Enable YCBCR->RGB CSC */ + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_pp_mem.graphics_combine_en) { + ic_conf |= IC_CONF_PP_CMB; + + /* need transparent CSC1 conversion */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, RGB); + ic_conf |= IC_CONF_PP_CSC1; /* Enable RGB->RGB CSC */ + + if (params->mem_pp_mem.global_alpha_en) { + ic_conf |= IC_CONF_IC_GLB_LOC_A; + } else { + ic_conf &= ~IC_CONF_IC_GLB_LOC_A; + } + + if (params->mem_pp_mem.key_color_en) { + ic_conf |= IC_CONF_KEY_COLOR_EN; + } else { + ic_conf &= ~IC_CONF_KEY_COLOR_EN; + } + } else { + ic_conf &= ~IC_CONF_PP_CMB; + } + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_pp(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PP_EN | IC_CONF_PP_CSC1 | IC_CONF_PP_CSC2 | + IC_CONF_PP_CMB); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_pp(ipu_channel_params_t * params) +{ +} + +void _ipu_ic_uninit_rotate_pp(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_PP_ROT_EN; + __raw_writel(reg, IC_CONF); +} + +static void _init_csc(uint8_t ic_task, ipu_color_space_t in_format, + ipu_color_space_t out_format) +{ +/* Y = R * .299 + G * .587 + B * .114; + U = R * -.169 + G * -.332 + B * .500 + 128.; + V = R * .500 + G * -.419 + B * -.0813 + 128.;*/ + static const uint32_t rgb2ycbcr_coeff[4][3] = { + {0x004D, 0x0096, 0x001D}, + {0x01D5, 0x01AB, 0x0080}, + {0x0080, 0x0195, 0x01EB}, + {0x0000, 0x0200, 0x0200}, /* A0, A1, A2 */ + }; + + /* transparent RGB->RGB matrix for combining + */ + static const uint32_t rgb2rgb_coeff[4][3] = { + {0x0080, 0x0000, 0x0000}, + {0x0000, 0x0080, 0x0000}, + {0x0000, 0x0000, 0x0080}, + {0x0000, 0x0000, 0x0000}, /* A0, A1, A2 */ + }; + +/* R = (1.164 * (Y - 16)) + (1.596 * (Cr - 128)); + G = (1.164 * (Y - 16)) - (0.392 * (Cb - 128)) - (0.813 * (Cr - 128)); + B = (1.164 * (Y - 16)) + (2.017 * (Cb - 128); */ + static const uint32_t ycbcr2rgb_coeff[4][3] = { + {149, 0, 204}, + {149, 462, 408}, + {149, 255, 0}, + {8192 - 446, 266, 8192 - 554}, /* A0, A1, A2 */ + }; + + uint32_t param[2]; + uint32_t address = 0; + + if (g_ipu_hw_rev > 1) { + if (ic_task == IC_TASK_VIEWFINDER) { + address = 0x645 << 3; + } else if (ic_task == IC_TASK_ENCODER) { + address = 0x321 << 3; + } else if (ic_task == IC_TASK_POST_PROCESSOR) { + address = 0x96C << 3; + } else { + BUG(); + } + } else { + if (ic_task == IC_TASK_VIEWFINDER) { + address = 0x5a5 << 3; + } else if (ic_task == IC_TASK_ENCODER) { + address = 0x2d1 << 3; + } else if (ic_task == IC_TASK_POST_PROCESSOR) { + address = 0x87c << 3; + } else { + BUG(); + } + } + + if ((in_format == YCbCr) && (out_format == RGB)) { + /* Init CSC1 (YCbCr->RGB) */ + param[0] = + (ycbcr2rgb_coeff[3][0] << 27) | (ycbcr2rgb_coeff[0][0] << + 18) | + (ycbcr2rgb_coeff[1][1] << 9) | ycbcr2rgb_coeff[2][2]; + /* scale = 2, sat = 0 */ + param[1] = (ycbcr2rgb_coeff[3][0] >> 5) | (2L << (40 - 32)); + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (ycbcr2rgb_coeff[3][1] << 27) | (ycbcr2rgb_coeff[0][1] << + 18) | + (ycbcr2rgb_coeff[1][0] << 9) | ycbcr2rgb_coeff[2][0]; + param[1] = (ycbcr2rgb_coeff[3][1] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (ycbcr2rgb_coeff[3][2] << 27) | (ycbcr2rgb_coeff[0][2] << + 18) | + (ycbcr2rgb_coeff[1][2] << 9) | ycbcr2rgb_coeff[2][1]; + param[1] = (ycbcr2rgb_coeff[3][2] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + } else if ((in_format == RGB) && (out_format == YCbCr)) { + /* Init CSC1 (RGB->YCbCr) */ + param[0] = + (rgb2ycbcr_coeff[3][0] << 27) | (rgb2ycbcr_coeff[0][0] << + 18) | + (rgb2ycbcr_coeff[1][1] << 9) | rgb2ycbcr_coeff[2][2]; + /* scale = 1, sat = 0 */ + param[1] = (rgb2ycbcr_coeff[3][0] >> 5) | (1UL << 8); + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2ycbcr_coeff[3][1] << 27) | (rgb2ycbcr_coeff[0][1] << + 18) | + (rgb2ycbcr_coeff[1][0] << 9) | rgb2ycbcr_coeff[2][0]; + param[1] = (rgb2ycbcr_coeff[3][1] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2ycbcr_coeff[3][2] << 27) | (rgb2ycbcr_coeff[0][2] << + 18) | + (rgb2ycbcr_coeff[1][2] << 9) | rgb2ycbcr_coeff[2][1]; + param[1] = (rgb2ycbcr_coeff[3][2] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + } else if ((in_format == RGB) && (out_format == RGB)) { + /* Init CSC1 */ + param[0] = + (rgb2rgb_coeff[3][0] << 27) | (rgb2rgb_coeff[0][0] << 18) | + (rgb2rgb_coeff[1][1] << 9) | rgb2rgb_coeff[2][2]; + /* scale = 2, sat = 0 */ + param[1] = (rgb2rgb_coeff[3][0] >> 5) | (2UL << 8); + + _ipu_write_param_mem(address, param, 2); + + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2rgb_coeff[3][1] << 27) | (rgb2rgb_coeff[0][1] << 18) | + (rgb2rgb_coeff[1][0] << 9) | rgb2rgb_coeff[2][0]; + param[1] = (rgb2rgb_coeff[3][1] >> 5); + + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2rgb_coeff[3][2] << 27) | (rgb2rgb_coeff[0][2] << 18) | + (rgb2rgb_coeff[1][2] << 9) | rgb2rgb_coeff[2][1]; + param[1] = (rgb2rgb_coeff[3][2] >> 5); + + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + } else { + dev_err(g_ipu_dev, "Unsupported color space conversion\n"); + } +} + +static bool _calc_resize_coeffs(uint32_t inSize, uint32_t outSize, + uint32_t * resizeCoeff, + uint32_t * downsizeCoeff) +{ + uint32_t tempSize; + uint32_t tempDownsize; + + /* Cannot downsize more than 8:1 */ + if ((outSize << 3) < inSize) { + return false; + } + /* compute downsizing coefficient */ + tempDownsize = 0; + tempSize = inSize; + while ((tempSize >= outSize * 2) && (tempDownsize < 2)) { + tempSize >>= 1; + tempDownsize++; + } + *downsizeCoeff = tempDownsize; + + /* compute resizing coefficient using the following equation: + resizeCoeff = M*(SI -1)/(SO - 1) + where M = 2^13, SI - input size, SO - output size */ + *resizeCoeff = (8192L * (tempSize - 1)) / (outSize - 1); + if (*resizeCoeff >= 16384L) { + dev_err(g_ipu_dev, "Warning! Overflow on resize coeff.\n"); + *resizeCoeff = 0x3FFF; + } + + dev_dbg(g_ipu_dev, "resizing from %u -> %u pixels, " + "downsize=%u, resize=%u.%lu (reg=%u)\n", inSize, outSize, + *downsizeCoeff, (*resizeCoeff >= 8192L) ? 1 : 0, + ((*resizeCoeff & 0x1FFF) * 10000L) / 8192L, *resizeCoeff); + + return true; +} diff --git a/drivers/mxc/ipu/ipu_param_mem.h b/drivers/mxc/ipu/ipu_param_mem.h new file mode 100644 index 000000000000..07bd03a81e3f --- /dev/null +++ b/drivers/mxc/ipu/ipu_param_mem.h @@ -0,0 +1,176 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __INCLUDE_IPU_PARAM_MEM_H__ +#define __INCLUDE_IPU_PARAM_MEM_H__ + +#include <linux/types.h> + +static __inline void _ipu_ch_param_set_size(uint32_t * params, + uint32_t pixel_fmt, uint16_t width, + uint16_t height, uint16_t stride, + uint32_t u, uint32_t v) +{ + uint32_t u_offset = 0; + uint32_t v_offset = 0; + memset(params, 0, 40); + + params[3] = + (uint32_t) ((width - 1) << 12) | ((uint32_t) (height - 1) << 24); + params[4] = (uint32_t) (height - 1) >> 8; + params[7] = (uint32_t) (stride - 1) << 3; + + switch (pixel_fmt) { + case IPU_PIX_FMT_GENERIC: + /*Represents 8-bit Generic data */ + params[7] |= 3 | (7UL << (81 - 64)) | (31L << (89 - 64)); /* BPP & PFS */ + params[8] = 2; /* SAT = use 32-bit access */ + break; + case IPU_PIX_FMT_GENERIC_32: + /*Represents 32-bit Generic data */ + params[7] |= (7UL << (81 - 64)) | (7L << (89 - 64)); /* BPP & PFS */ + params[8] = 2; /* SAT = use 32-bit access */ + break; + case IPU_PIX_FMT_RGB565: + params[7] |= 2L | (4UL << (81 - 64)) | (7L << (89 - 64)); /* BPP & PFS */ + params[8] = 2 | /* SAT = 32-bit access */ + (0UL << (99 - 96)) | /* Red bit offset */ + (5UL << (104 - 96)) | /* Green bit offset */ + (11UL << (109 - 96)) | /* Blue bit offset */ + (16UL << (114 - 96)) | /* Alpha bit offset */ + (4UL << (119 - 96)) | /* Red bit width - 1 */ + (5UL << (122 - 96)) | /* Green bit width - 1 */ + (4UL << (125 - 96)); /* Blue bit width - 1 */ + break; + case IPU_PIX_FMT_BGR24: /* 24 BPP & RGB PFS */ + params[7] |= 1 | (4UL << (81 - 64)) | (7L << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (8UL << (104 - 96)) | /* Green bit offset */ + (16UL << (109 - 96)) | /* Blue bit offset */ + (24UL << (114 - 96)) | /* Alpha bit offset */ + (7UL << (119 - 96)) | /* Red bit width - 1 */ + (7UL << (122 - 96)) | /* Green bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */ + break; + case IPU_PIX_FMT_RGB24: /* 24 BPP & RGB PFS */ + params[7] |= 1 | (4UL << (81 - 64)) | (7L << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (16UL << (99 - 96)) | /* Red bit offset */ + (8UL << (104 - 96)) | /* Green bit offset */ + (0UL << (109 - 96)) | /* Blue bit offset */ + (24UL << (114 - 96)) | /* Alpha bit offset */ + (7UL << (119 - 96)) | /* Red bit width - 1 */ + (7UL << (122 - 96)) | /* Green bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */ + break; + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_BGR32: + /* BPP & pixel fmt */ + params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (8UL << (99 - 96)) | /* Red bit offset */ + (16UL << (104 - 96)) | /* Green bit offset */ + (24UL << (109 - 96)) | /* Blue bit offset */ + (0UL << (114 - 96)) | /* Alpha bit offset */ + (7UL << (119 - 96)) | /* Red bit width - 1 */ + (7UL << (122 - 96)) | /* Green bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */ + params[9] = 7; /* Alpha bit width - 1 */ + break; + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_RGB32: + /* BPP & pixel fmt */ + params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (24UL << (99 - 96)) | /* Red bit offset */ + (16UL << (104 - 96)) | /* Green bit offset */ + (8UL << (109 - 96)) | /* Blue bit offset */ + (0UL << (114 - 96)) | /* Alpha bit offset */ + (7UL << (119 - 96)) | /* Red bit width - 1 */ + (7UL << (122 - 96)) | /* Green bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */ + params[9] = 7; /* Alpha bit width - 1 */ + break; + case IPU_PIX_FMT_ABGR32: + /* BPP & pixel fmt */ + params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (0UL << (99 - 96)) | /* Alpha bit offset */ + (8UL << (104 - 96)) | /* Blue bit offset */ + (16UL << (109 - 96)) | /* Green bit offset */ + (24UL << (114 - 96)) | /* Red bit offset */ + (7UL << (119 - 96)) | /* Alpha bit width - 1 */ + (7UL << (122 - 96)) | /* Blue bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Green bit width - 1 */ + params[9] = 7; /* Red bit width - 1 */ + break; + case IPU_PIX_FMT_UYVY: + /* BPP & pixel format */ + params[7] |= 2 | (6UL << 17) | (7 << (89 - 64)); + params[8] = 2; /* SAT = 32-bit access */ + break; + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YUV420P: + /* BPP & pixel format */ + params[7] |= 3 | (3UL << 17) | (7 << (89 - 64)); + params[8] = 2; /* SAT = 32-bit access */ + u_offset = (u == 0) ? stride * height : u; + v_offset = (v == 0) ? u_offset + u_offset / 4 : v; + break; + case IPU_PIX_FMT_YVU422P: + /* BPP & pixel format */ + params[7] |= 3 | (2UL << 17) | (7 << (89 - 64)); + params[8] = 2; /* SAT = 32-bit access */ + v_offset = (v == 0) ? stride * height : v; + u_offset = (u == 0) ? v_offset + v_offset / 2 : u; + break; + case IPU_PIX_FMT_YUV422P: + /* BPP & pixel format */ + params[7] |= 3 | (2UL << 17) | (7 << (89 - 64)); + params[8] = 2; /* SAT = 32-bit access */ + u_offset = (u == 0) ? stride * height : u; + v_offset = (v == 0) ? u_offset + u_offset / 2 : v; + break; + default: + dev_err(g_ipu_dev, "mxc ipu: unimplemented pixel format\n"); + break; + } + + params[1] = (1UL << (46 - 32)) | (u_offset << (53 - 32)); + params[2] = u_offset >> (64 - 53); + params[2] |= v_offset << (79 - 64); + params[3] |= v_offset >> (96 - 79); +} + +static __inline void _ipu_ch_param_set_burst_size(uint32_t * params, + uint16_t burst_pixels) +{ + params[7] &= ~(0x3FL << (89 - 64)); + params[7] |= (uint32_t) (burst_pixels - 1) << (89 - 64); +}; + +static __inline void _ipu_ch_param_set_buffer(uint32_t * params, + dma_addr_t buf0, dma_addr_t buf1) +{ + params[5] = buf0; + params[6] = buf1; +}; + +static __inline void _ipu_ch_param_set_rotation(uint32_t * params, + ipu_rotate_mode_t rot) +{ + params[7] |= (uint32_t) rot << (84 - 64); +}; + +void _ipu_write_param_mem(uint32_t addr, uint32_t * data, uint32_t numWords); + +#endif diff --git a/drivers/mxc/ipu/ipu_prv.h b/drivers/mxc/ipu/ipu_prv.h new file mode 100644 index 000000000000..b76a2f2357fd --- /dev/null +++ b/drivers/mxc/ipu/ipu_prv.h @@ -0,0 +1,59 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __INCLUDE_IPU_PRV_H__ +#define __INCLUDE_IPU_PRV_H__ + +#include <linux/types.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <mach/hardware.h> + +/* Globals */ +extern struct device *g_ipu_dev; +extern spinlock_t ipu_lock; +extern struct clk *g_ipu_clk; +extern struct clk *g_ipu_csi_clk; + +int register_ipu_device(void); +ipu_color_space_t format_to_colorspace(uint32_t fmt); + +uint32_t _ipu_channel_status(ipu_channel_t channel); + +void _ipu_sdc_fg_init(ipu_channel_params_t * params); +uint32_t _ipu_sdc_fg_uninit(void); +void _ipu_sdc_bg_init(ipu_channel_params_t * params); +uint32_t _ipu_sdc_bg_uninit(void); + +void _ipu_ic_enable_task(ipu_channel_t channel); +void _ipu_ic_disable_task(ipu_channel_t channel); +void _ipu_ic_init_prpvf(ipu_channel_params_t * params, bool src_is_csi); +void _ipu_ic_uninit_prpvf(void); +void _ipu_ic_init_rotate_vf(ipu_channel_params_t * params); +void _ipu_ic_uninit_rotate_vf(void); +void _ipu_ic_init_csi(ipu_channel_params_t * params); +void _ipu_ic_uninit_csi(void); +void _ipu_ic_init_prpenc(ipu_channel_params_t * params, bool src_is_csi); +void _ipu_ic_uninit_prpenc(void); +void _ipu_ic_init_rotate_enc(ipu_channel_params_t * params); +void _ipu_ic_uninit_rotate_enc(void); +void _ipu_ic_init_pp(ipu_channel_params_t * params); +void _ipu_ic_uninit_pp(void); +void _ipu_ic_init_rotate_pp(ipu_channel_params_t * params); +void _ipu_ic_uninit_rotate_pp(void); + +int32_t _ipu_adc_init_channel(ipu_channel_t chan, display_port_t disp, + mcu_mode_t cmd, int16_t x_pos, int16_t y_pos); +int32_t _ipu_adc_uninit_channel(ipu_channel_t chan); + +#endif /* __INCLUDE_IPU_PRV_H__ */ diff --git a/drivers/mxc/ipu/ipu_regs.h b/drivers/mxc/ipu/ipu_regs.h new file mode 100644 index 000000000000..d3ac76ea7834 --- /dev/null +++ b/drivers/mxc/ipu/ipu_regs.h @@ -0,0 +1,396 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * @file ipu_regs.h + * + * @brief IPU Register definitions + * + * @ingroup IPU + */ +#ifndef __IPU_REGS_INCLUDED__ +#define __IPU_REGS_INCLUDED__ + +#define IPU_REG_BASE IO_ADDRESS(IPU_CTRL_BASE_ADDR) + +/* Register addresses */ +/* IPU Common registers */ +#define IPU_CONF (IPU_REG_BASE + 0x0000) +#define IPU_CHA_BUF0_RDY (IPU_REG_BASE + 0x0004) +#define IPU_CHA_BUF1_RDY (IPU_REG_BASE + 0x0008) +#define IPU_CHA_DB_MODE_SEL (IPU_REG_BASE + 0x000C) +#define IPU_CHA_CUR_BUF (IPU_REG_BASE + 0x0010) +#define IPU_FS_PROC_FLOW (IPU_REG_BASE + 0x0014) +#define IPU_FS_DISP_FLOW (IPU_REG_BASE + 0x0018) +#define IPU_TASKS_STAT (IPU_REG_BASE + 0x001C) +#define IPU_IMA_ADDR (IPU_REG_BASE + 0x0020) +#define IPU_IMA_DATA (IPU_REG_BASE + 0x0024) +#define IPU_INT_CTRL_1 (IPU_REG_BASE + 0x0028) +#define IPU_INT_CTRL_2 (IPU_REG_BASE + 0x002C) +#define IPU_INT_CTRL_3 (IPU_REG_BASE + 0x0030) +#define IPU_INT_CTRL_4 (IPU_REG_BASE + 0x0034) +#define IPU_INT_CTRL_5 (IPU_REG_BASE + 0x0038) +#define IPU_INT_STAT_1 (IPU_REG_BASE + 0x003C) +#define IPU_INT_STAT_2 (IPU_REG_BASE + 0x0040) +#define IPU_INT_STAT_3 (IPU_REG_BASE + 0x0044) +#define IPU_INT_STAT_4 (IPU_REG_BASE + 0x0048) +#define IPU_INT_STAT_5 (IPU_REG_BASE + 0x004C) +#define IPU_BRK_CTRL_1 (IPU_REG_BASE + 0x0050) +#define IPU_BRK_CTRL_2 (IPU_REG_BASE + 0x0054) +#define IPU_BRK_STAT (IPU_REG_BASE + 0x0058) +#define IPU_DIAGB_CTRL (IPU_REG_BASE + 0x005C) +/* CMOS Sensor Interface Registers */ +#define CSI_SENS_CONF (IPU_REG_BASE + 0x0060) +#define CSI_SENS_FRM_SIZE (IPU_REG_BASE + 0x0064) +#define CSI_ACT_FRM_SIZE (IPU_REG_BASE + 0x0068) +#define CSI_OUT_FRM_CTRL (IPU_REG_BASE + 0x006C) +#define CSI_TST_CTRL (IPU_REG_BASE + 0x0070) +#define CSI_CCIR_CODE_1 (IPU_REG_BASE + 0x0074) +#define CSI_CCIR_CODE_2 (IPU_REG_BASE + 0x0078) +#define CSI_CCIR_CODE_3 (IPU_REG_BASE + 0x007C) +#define CSI_FLASH_STROBE_1 (IPU_REG_BASE + 0x0080) +#define CSI_FLASH_STROBE_2 (IPU_REG_BASE + 0x0084) +/* Image Converter Registers */ +#define IC_CONF (IPU_REG_BASE + 0x0088) +#define IC_PRP_ENC_RSC (IPU_REG_BASE + 0x008C) +#define IC_PRP_VF_RSC (IPU_REG_BASE + 0x0090) +#define IC_PP_RSC (IPU_REG_BASE + 0x0094) +#define IC_CMBP_1 (IPU_REG_BASE + 0x0098) +#define IC_CMBP_2 (IPU_REG_BASE + 0x009C) +#define PF_CONF (IPU_REG_BASE + 0x00A0) +#define IDMAC_CONF (IPU_REG_BASE + 0x00A4) +#define IDMAC_CHA_EN (IPU_REG_BASE + 0x00A8) +#define IDMAC_CHA_PRI (IPU_REG_BASE + 0x00AC) +#define IDMAC_CHA_BUSY (IPU_REG_BASE + 0x00B0) +/* SDC Registers */ +#define SDC_COM_CONF (IPU_REG_BASE + 0x00B4) +#define SDC_GW_CTRL (IPU_REG_BASE + 0x00B8) +#define SDC_FG_POS (IPU_REG_BASE + 0x00BC) +#define SDC_BG_POS (IPU_REG_BASE + 0x00C0) +#define SDC_CUR_POS (IPU_REG_BASE + 0x00C4) +#define SDC_PWM_CTRL (IPU_REG_BASE + 0x00C8) +#define SDC_CUR_MAP (IPU_REG_BASE + 0x00CC) +#define SDC_HOR_CONF (IPU_REG_BASE + 0x00D0) +#define SDC_VER_CONF (IPU_REG_BASE + 0x00D4) +#define SDC_SHARP_CONF_1 (IPU_REG_BASE + 0x00D8) +#define SDC_SHARP_CONF_2 (IPU_REG_BASE + 0x00DC) +/* ADC Registers */ +#define ADC_CONF (IPU_REG_BASE + 0x00E0) +#define ADC_SYSCHA1_SA (IPU_REG_BASE + 0x00E4) +#define ADC_SYSCHA2_SA (IPU_REG_BASE + 0x00E8) +#define ADC_PRPCHAN_SA (IPU_REG_BASE + 0x00EC) +#define ADC_PPCHAN_SA (IPU_REG_BASE + 0x00F0) +#define ADC_DISP0_CONF (IPU_REG_BASE + 0x00F4) +#define ADC_DISP0_RD_AP (IPU_REG_BASE + 0x00F8) +#define ADC_DISP0_RDM (IPU_REG_BASE + 0x00FC) +#define ADC_DISP0_SS (IPU_REG_BASE + 0x0100) +#define ADC_DISP1_CONF (IPU_REG_BASE + 0x0104) +#define ADC_DISP1_RD_AP (IPU_REG_BASE + 0x0108) +#define ADC_DISP1_RDM (IPU_REG_BASE + 0x010C) +#define ADC_DISP12_SS (IPU_REG_BASE + 0x0110) +#define ADC_DISP2_CONF (IPU_REG_BASE + 0x0114) +#define ADC_DISP2_RD_AP (IPU_REG_BASE + 0x0118) +#define ADC_DISP2_RDM (IPU_REG_BASE + 0x011C) +#define ADC_DISP_VSYNC (IPU_REG_BASE + 0x0120) +/* Display Interface re(sters */ +#define DI_DISP_IF_CONF (IPU_REG_BASE + 0x0124) +#define DI_DISP_SIG_POL (IPU_REG_BASE + 0x0128) +#define DI_SER_DISP1_CONF (IPU_REG_BASE + 0x012C) +#define DI_SER_DISP2_CONF (IPU_REG_BASE + 0x0130) +#define DI_HSP_CLK_PER (IPU_REG_BASE + 0x0134) +#define DI_DISP0_TIME_CONF_1 (IPU_REG_BASE + 0x0138) +#define DI_DISP0_TIME_CONF_2 (IPU_REG_BASE + 0x013C) +#define DI_DISP0_TIME_CONF_3 (IPU_REG_BASE + 0x0140) +#define DI_DISP1_TIME_CONF_1 (IPU_REG_BASE + 0x0144) +#define DI_DISP1_TIME_CONF_2 (IPU_REG_BASE + 0x0148) +#define DI_DISP1_TIME_CONF_3 (IPU_REG_BASE + 0x014C) +#define DI_DISP2_TIME_CONF_1 (IPU_REG_BASE + 0x0150) +#define DI_DISP2_TIME_CONF_2 (IPU_REG_BASE + 0x0154) +#define DI_DISP2_TIME_CONF_3 (IPU_REG_BASE + 0x0158) +#define DI_DISP3_TIME_CONF (IPU_REG_BASE + 0x015C) +#define DI_DISP0_DB0_MAP (IPU_REG_BASE + 0x0160) +#define DI_DISP0_DB1_MAP (IPU_REG_BASE + 0x0164) +#define DI_DISP0_DB2_MAP (IPU_REG_BASE + 0x0168) +#define DI_DISP0_CB0_MAP (IPU_REG_BASE + 0x016C) +#define DI_DISP0_CB1_MAP (IPU_REG_BASE + 0x0170) +#define DI_DISP0_CB2_MAP (IPU_REG_BASE + 0x0174) +#define DI_DISP1_DB0_MAP (IPU_REG_BASE + 0x0178) +#define DI_DISP1_DB1_MAP (IPU_REG_BASE + 0x017C) +#define DI_DISP1_DB2_MAP (IPU_REG_BASE + 0x0180) +#define DI_DISP1_CB0_MAP (IPU_REG_BASE + 0x0184) +#define DI_DISP1_CB1_MAP (IPU_REG_BASE + 0x0188) +#define DI_DISP1_CB2_MAP (IPU_REG_BASE + 0x018C) +#define DI_DISP2_DB0_MAP (IPU_REG_BASE + 0x0190) +#define DI_DISP2_DB1_MAP (IPU_REG_BASE + 0x0194) +#define DI_DISP2_DB2_MAP (IPU_REG_BASE + 0x0198) +#define DI_DISP2_CB0_MAP (IPU_REG_BASE + 0x019C) +#define DI_DISP2_CB1_MAP (IPU_REG_BASE + 0x01A0) +#define DI_DISP2_CB2_MAP (IPU_REG_BASE + 0x01A4) +#define DI_DISP3_B0_MAP (IPU_REG_BASE + 0x01A8) +#define DI_DISP3_B1_MAP (IPU_REG_BASE + 0x01AC) +#define DI_DISP3_B2_MAP (IPU_REG_BASE + 0x01B0) +#define DI_DISP_ACC_CC (IPU_REG_BASE + 0x01B4) +#define DI_DISP_LLA_CONF (IPU_REG_BASE + 0x01B8) +#define DI_DISP_LLA_DATA (IPU_REG_BASE + 0x01BC) + +#define IPUIRQ_2_STATREG(int) (IPU_INT_STAT_1 + 4*(int>>5)) +#define IPUIRQ_2_CTRLREG(int) (IPU_INT_CTRL_1 + 4*(int>>5)) +#define IPUIRQ_2_MASK(int) (1UL << (int & 0x1F)) + +enum { + IPU_CONF_CSI_EN = 0x00000001, + IPU_CONF_IC_EN = 0x00000002, + IPU_CONF_ROT_EN = 0x00000004, + IPU_CONF_PF_EN = 0x00000008, + IPU_CONF_SDC_EN = 0x00000010, + IPU_CONF_ADC_EN = 0x00000020, + IPU_CONF_DI_EN = 0x00000040, + IPU_CONF_DU_EN = 0x00000080, + IPU_CONF_PXL_ENDIAN = 0x00000100, + + FS_PRPVF_ROT_SRC_SEL = 0x00000040, + FS_PRPENC_ROT_SRC_SEL = 0x00000020, + FS_PRPENC_DEST_SEL = 0x00000010, + FS_PP_SRC_SEL_MASK = 0x00000300, + FS_PP_SRC_SEL_OFFSET = 8, + FS_PP_ROT_SRC_SEL_MASK = 0x00000C00, + FS_PP_ROT_SRC_SEL_OFFSET = 10, + FS_PF_DEST_SEL_MASK = 0x00003000, + FS_PF_DEST_SEL_OFFSET = 12, + FS_PRPVF_DEST_SEL_MASK = 0x00070000, + FS_PRPVF_DEST_SEL_OFFSET = 16, + FS_PRPVF_ROT_DEST_SEL_MASK = 0x00700000, + FS_PRPVF_ROT_DEST_SEL_OFFSET = 20, + FS_PP_DEST_SEL_MASK = 0x07000000, + FS_PP_DEST_SEL_OFFSET = 24, + FS_PP_ROT_DEST_SEL_MASK = 0x70000000, + FS_PP_ROT_DEST_SEL_OFFSET = 28, + FS_VF_IN_VALID = 0x00000002, + FS_ENC_IN_VALID = 0x00000001, + + FS_SDC_BG_SRC_SEL_MASK = 0x00000007, + FS_SDC_BG_SRC_SEL_OFFSET = 0, + FS_SDC_FG_SRC_SEL_MASK = 0x00000070, + FS_SDC_FG_SRC_SEL_OFFSET = 4, + FS_ADC1_SRC_SEL_MASK = 0x00000700, + FS_ADC1_SRC_SEL_OFFSET = 8, + FS_ADC2_SRC_SEL_MASK = 0x00007000, + FS_ADC2_SRC_SEL_OFFSET = 12, + FS_AUTO_REF_PER_MASK = 0x03FF0000, + FS_AUTO_REF_PER_OFFSET = 16, + + FS_DEST_ARM = 0, + FS_DEST_ROT = 1, + FS_DEST_PP = 1, + FS_DEST_ADC1 = 2, + FS_DEST_ADC2 = 3, + FS_DEST_SDC_BG = 4, + FS_DEST_SDC_FG = 5, + FS_DEST_ADC = 6, + + FS_SRC_ARM = 0, + FS_PP_SRC_PF = 1, + FS_PP_SRC_ROT = 2, + + FS_ROT_SRC_PP = 1, + FS_ROT_SRC_PF = 2, + + FS_PF_DEST_PP = 1, + FS_PF_DEST_ROT = 2, + + FS_SRC_ROT_VF = 1, + FS_SRC_ROT_PP = 2, + FS_SRC_VF = 3, + FS_SRC_PP = 4, + FS_SRC_SNOOP = 5, + FS_SRC_AUTOREF = 6, + FS_SRC_AUTOREF_SNOOP = 7, + + TSTAT_PF_H264_PAUSE = 0x00000001, + TSTAT_CSI2MEM_MASK = 0x0000000C, + TSTAT_CSI2MEM_OFFSET = 2, + TSTAT_VF_MASK = 0x00000600, + TSTAT_VF_OFFSET = 9, + TSTAT_VF_ROT_MASK = 0x000C0000, + TSTAT_VF_ROT_OFFSET = 18, + TSTAT_ENC_MASK = 0x00000180, + TSTAT_ENC_OFFSET = 7, + TSTAT_ENC_ROT_MASK = 0x00030000, + TSTAT_ENC_ROT_OFFSET = 16, + TSTAT_PP_MASK = 0x00001800, + TSTAT_PP_OFFSET = 11, + TSTAT_PP_ROT_MASK = 0x00300000, + TSTAT_PP_ROT_OFFSET = 20, + TSTAT_PF_MASK = 0x00C00000, + TSTAT_PF_OFFSET = 22, + TSTAT_ADCSYS1_MASK = 0x03000000, + TSTAT_ADCSYS1_OFFSET = 24, + TSTAT_ADCSYS2_MASK = 0x0C000000, + TSTAT_ADCSYS2_OFFSET = 26, + + TASK_STAT_IDLE = 0, + TASK_STAT_ACTIVE = 1, + TASK_STAT_WAIT4READY = 2, + + /* Register bits */ + SDC_COM_TFT_COLOR = 0x00000001UL, + SDC_COM_FG_EN = 0x00000010UL, + SDC_COM_GWSEL = 0x00000020UL, + SDC_COM_GLB_A = 0x00000040UL, + SDC_COM_KEY_COLOR_G = 0x00000080UL, + SDC_COM_BG_EN = 0x00000200UL, + SDC_COM_SHARP = 0x00001000UL, + + SDC_V_SYNC_WIDTH_L = 0x00000001UL, + + ADC_CONF_PRP_EN = 0x00000001L, + ADC_CONF_PP_EN = 0x00000002L, + ADC_CONF_MCU_EN = 0x00000004L, + + ADC_DISP_CONF_SL_MASK = 0x00000FFFL, + ADC_DISP_CONF_TYPE_MASK = 0x00003000L, + ADC_DISP_CONF_TYPE_XY = 0x00002000L, + + ADC_DISP_VSYNC_D0_MODE_MASK = 0x00000003L, + ADC_DISP_VSYNC_D0_WIDTH_MASK = 0x003F0000L, + ADC_DISP_VSYNC_D12_MODE_MASK = 0x0000000CL, + ADC_DISP_VSYNC_D12_WIDTH_MASK = 0x3F000000L, + + /* Image Converter Register bits */ + IC_CONF_PRPENC_EN = 0x00000001, + IC_CONF_PRPENC_CSC1 = 0x00000002, + IC_CONF_PRPENC_ROT_EN = 0x00000004, + IC_CONF_PRPVF_EN = 0x00000100, + IC_CONF_PRPVF_CSC1 = 0x00000200, + IC_CONF_PRPVF_CSC2 = 0x00000400, + IC_CONF_PRPVF_CMB = 0x00000800, + IC_CONF_PRPVF_ROT_EN = 0x00001000, + IC_CONF_PP_EN = 0x00010000, + IC_CONF_PP_CSC1 = 0x00020000, + IC_CONF_PP_CSC2 = 0x00040000, + IC_CONF_PP_CMB = 0x00080000, + IC_CONF_PP_ROT_EN = 0x00100000, + IC_CONF_IC_GLB_LOC_A = 0x10000000, + IC_CONF_KEY_COLOR_EN = 0x20000000, + IC_CONF_RWS_EN = 0x40000000, + IC_CONF_CSI_MEM_WR_EN = 0x80000000, + + IDMA_CHAN_INVALID = 0x000000FF, + IDMA_IC_0 = 0x00000001, + IDMA_IC_1 = 0x00000002, + IDMA_IC_2 = 0x00000004, + IDMA_IC_3 = 0x00000008, + IDMA_IC_4 = 0x00000010, + IDMA_IC_5 = 0x00000020, + IDMA_IC_6 = 0x00000040, + IDMA_IC_7 = 0x00000080, + IDMA_IC_8 = 0x00000100, + IDMA_IC_9 = 0x00000200, + IDMA_IC_10 = 0x00000400, + IDMA_IC_11 = 0x00000800, + IDMA_IC_12 = 0x00001000, + IDMA_IC_13 = 0x00002000, + IDMA_SDC_BG = 0x00004000, + IDMA_SDC_FG = 0x00008000, + IDMA_SDC_MASK = 0x00010000, + IDMA_SDC_PARTIAL = 0x00020000, + IDMA_ADC_SYS1_WR = 0x00040000, + IDMA_ADC_SYS2_WR = 0x00080000, + IDMA_ADC_SYS1_CMD = 0x00100000, + IDMA_ADC_SYS2_CMD = 0x00200000, + IDMA_ADC_SYS1_RD = 0x00400000, + IDMA_ADC_SYS2_RD = 0x00800000, + IDMA_PF_QP = 0x01000000, + IDMA_PF_BSP = 0x02000000, + IDMA_PF_Y_IN = 0x04000000, + IDMA_PF_U_IN = 0x08000000, + IDMA_PF_V_IN = 0x10000000, + IDMA_PF_Y_OUT = 0x20000000, + IDMA_PF_U_OUT = 0x40000000, + IDMA_PF_V_OUT = 0x80000000, + + CSI_SENS_CONF_DATA_FMT_SHIFT = 8, + CSI_SENS_CONF_DATA_FMT_RGB_YUV444 = 0x00000000L, + CSI_SENS_CONF_DATA_FMT_YUV422 = 0x00000200L, + CSI_SENS_CONF_DATA_FMT_BAYER = 0x00000300L, + + CSI_SENS_CONF_VSYNC_POL_SHIFT = 0, + CSI_SENS_CONF_HSYNC_POL_SHIFT = 1, + CSI_SENS_CONF_DATA_POL_SHIFT = 2, + CSI_SENS_CONF_PIX_CLK_POL_SHIFT = 3, + CSI_SENS_CONF_SENS_PRTCL_SHIFT = 4, + CSI_SENS_CONF_SENS_CLKSRC_SHIFT = 7, + CSI_SENS_CONF_DATA_WIDTH_SHIFT = 10, + CSI_SENS_CONF_EXT_VSYNC_SHIFT = 15, + CSI_SENS_CONF_DIVRATIO_SHIFT = 16, + + PF_CONF_TYPE_MASK = 0x00000007, + PF_CONF_TYPE_SHIFT = 0, + PF_CONF_PAUSE_EN = 0x00000010, + PF_CONF_RESET = 0x00008000, + PF_CONF_PAUSE_ROW_MASK = 0x00FF0000, + PF_CONF_PAUSE_ROW_SHIFT = 16, + + /* DI_DISP_SIG_POL bits */ + DI_D3_VSYNC_POL_SHIFT = 28, + DI_D3_HSYNC_POL_SHIFT = 27, + DI_D3_DRDY_SHARP_POL_SHIFT = 26, + DI_D3_CLK_POL_SHIFT = 25, + DI_D3_DATA_POL_SHIFT = 24, + + /* DI_DISP_IF_CONF bits */ + DI_D3_CLK_IDLE_SHIFT = 26, + DI_D3_CLK_SEL_SHIFT = 25, + DI_D3_DATAMSK_SHIFT = 24, + + DISPx_IF_CLK_DOWN_OFFSET = 22, + DISPx_IF_CLK_UP_OFFSET = 12, + DISPx_IF_CLK_PER_OFFSET = 0, + DISPx_IF_CLK_READ_EN_OFFSET = 16, + DISPx_PIX_CLK_PER_OFFSET = 0, + + DI_CONF_DISP0_EN = 0x00000001L, + DI_CONF_DISP0_IF_MODE_OFFSET = 1, + DI_CONF_DISP0_BURST_MODE_OFFSET = 3, + DI_CONF_DISP1_EN = 0x00000100L, + DI_CONF_DISP1_IF_MODE_OFFSET = 9, + DI_CONF_DISP1_BURST_MODE_OFFSET = 12, + DI_CONF_DISP2_EN = 0x00010000L, + DI_CONF_DISP2_IF_MODE_OFFSET = 17, + DI_CONF_DISP2_BURST_MODE_OFFSET = 20, + + DI_SER_DISPx_CONF_SER_BIT_NUM_OFFSET = 16, + DI_SER_DISPx_CONF_PREAMBLE_OFFSET = 8, + DI_SER_DISPx_CONF_PREAMBLE_LEN_OFFSET = 4, + DI_SER_DISPx_CONF_RW_CFG_OFFSET = 1, + DI_SER_DISPx_CONF_BURST_MODE_EN = 0x01000000L, + DI_SER_DISPx_CONF_PREAMBLE_EN = 0x00000001L, + + /* DI_DISP_ACC_CC */ + DISP0_IF_CLK_CNT_D_MASK = 0x00000003L, + DISP0_IF_CLK_CNT_D_OFFSET = 0, + DISP0_IF_CLK_CNT_C_MASK = 0x0000000CL, + DISP0_IF_CLK_CNT_C_OFFSET = 2, + DISP1_IF_CLK_CNT_D_MASK = 0x00000030L, + DISP1_IF_CLK_CNT_D_OFFSET = 4, + DISP1_IF_CLK_CNT_C_MASK = 0x000000C0L, + DISP1_IF_CLK_CNT_C_OFFSET = 6, + DISP2_IF_CLK_CNT_D_MASK = 0x00000300L, + DISP2_IF_CLK_CNT_D_OFFSET = 8, + DISP2_IF_CLK_CNT_C_MASK = 0x00000C00L, + DISP2_IF_CLK_CNT_C_OFFSET = 10, + DISP3_IF_CLK_CNT_MASK = 0x00003000L, + DISP3_IF_CLK_CNT_OFFSET = 12, +}; + +#endif diff --git a/drivers/mxc/ipu/ipu_sdc.c b/drivers/mxc/ipu/ipu_sdc.c new file mode 100644 index 000000000000..a36eb6dc6a4e --- /dev/null +++ b/drivers/mxc/ipu/ipu_sdc.c @@ -0,0 +1,357 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_sdc.c + * + * @brief IPU SDC submodule API functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +static uint32_t g_h_start_width; +static uint32_t g_v_start_width; + +static const uint32_t di_mappings[] = { + 0x1600AAAA, 0x00E05555, 0x00070000, 3, /* RGB888 */ + 0x0005000F, 0x000B000F, 0x0011000F, 1, /* RGB666 */ + 0x0011000F, 0x000B000F, 0x0005000F, 1, /* BGR666 */ + 0x0004003F, 0x000A000F, 0x000F003F, 1 /* RGB565 */ +}; + +/*! + * This function is called to initialize a synchronous LCD panel. + * + * @param panel The type of panel. + * + * @param pixel_clk Desired pixel clock frequency in Hz. + * + * @param pixel_fmt Input parameter for pixel format of buffer. Pixel + * format is a FOURCC ASCII code. + * + * @param width The width of panel in pixels. + * + * @param height The height of panel in pixels. + * + * @param hStartWidth The number of pixel clocks between the HSYNC + * signal pulse and the start of valid data. + * + * @param hSyncWidth The width of the HSYNC signal in units of pixel + * clocks. + * + * @param hEndWidth The number of pixel clocks between the end of + * valid data and the HSYNC signal for next line. + * + * @param vStartWidth The number of lines between the VSYNC + * signal pulse and the start of valid data. + * + * @param vSyncWidth The width of the VSYNC signal in units of lines + * + * @param vEndWidth The number of lines between the end of valid + * data and the VSYNC signal for next frame. + * + * @param sig Bitfield of signal polarities for LCD interface. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_sdc_init_panel(ipu_panel_t panel, + uint32_t pixel_clk, + uint16_t width, uint16_t height, + uint32_t pixel_fmt, + uint16_t h_start_width, uint16_t h_sync_width, + uint16_t h_end_width, uint16_t v_start_width, + uint16_t v_sync_width, uint16_t v_end_width, + ipu_di_signal_cfg_t sig) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t old_conf; + uint32_t div; + + dev_dbg(g_ipu_dev, "panel size = %d x %d\n", width, height); + + if ((v_sync_width == 0) || (h_sync_width == 0)) + return EINVAL; + + /* Init panel size and blanking periods */ + reg = + ((uint32_t) (h_sync_width - 1) << 26) | + ((uint32_t) (width + h_sync_width + h_start_width + h_end_width - 1) + << 16); + __raw_writel(reg, SDC_HOR_CONF); + + reg = ((uint32_t) (v_sync_width - 1) << 26) | SDC_V_SYNC_WIDTH_L | + ((uint32_t) + (height + v_sync_width + v_start_width + v_end_width - 1) << 16); + __raw_writel(reg, SDC_VER_CONF); + + g_h_start_width = h_start_width + h_sync_width; + g_v_start_width = v_start_width + v_sync_width; + + switch (panel) { + case IPU_PANEL_SHARP_TFT: + __raw_writel(0x00FD0102L, SDC_SHARP_CONF_1); + __raw_writel(0x00F500F4L, SDC_SHARP_CONF_2); + __raw_writel(SDC_COM_SHARP | SDC_COM_TFT_COLOR, SDC_COM_CONF); + break; + case IPU_PANEL_TFT: + __raw_writel(SDC_COM_TFT_COLOR, SDC_COM_CONF); + break; + default: + return EINVAL; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + /* Init clocking */ + + /* Calculate divider */ + /* fractional part is 4 bits so simply multiple by 2^4 to get fractional part */ + dev_dbg(g_ipu_dev, "pixel clk = %d\n", pixel_clk); + div = (clk_get_rate(g_ipu_clk) * 16) / pixel_clk; + if (div < 0x40) { /* Divider less than 4 */ + dev_dbg(g_ipu_dev, + "InitPanel() - Pixel clock divider less than 1\n"); + div = 0x40; + } + /* DISP3_IF_CLK_DOWN_WR is half the divider value and 2 less fraction bits */ + /* Subtract 1 extra from DISP3_IF_CLK_DOWN_WR based on timing debug */ + /* DISP3_IF_CLK_UP_WR is 0 */ + __raw_writel((((div / 8) - 1) << 22) | div, DI_DISP3_TIME_CONF); + + /* DI settings */ + old_conf = __raw_readl(DI_DISP_IF_CONF) & 0x78FFFFFF; + old_conf |= sig.datamask_en << DI_D3_DATAMSK_SHIFT | + sig.clksel_en << DI_D3_CLK_SEL_SHIFT | + sig.clkidle_en << DI_D3_CLK_IDLE_SHIFT; + __raw_writel(old_conf, DI_DISP_IF_CONF); + + old_conf = __raw_readl(DI_DISP_SIG_POL) & 0xE0FFFFFF; + old_conf |= sig.data_pol << DI_D3_DATA_POL_SHIFT | + sig.clk_pol << DI_D3_CLK_POL_SHIFT | + sig.enable_pol << DI_D3_DRDY_SHARP_POL_SHIFT | + sig.Hsync_pol << DI_D3_HSYNC_POL_SHIFT | + sig.Vsync_pol << DI_D3_VSYNC_POL_SHIFT; + __raw_writel(old_conf, DI_DISP_SIG_POL); + + switch (pixel_fmt) { + case IPU_PIX_FMT_RGB24: + __raw_writel(di_mappings[0], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[1], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[2], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[3] - 1) << 12), DI_DISP_ACC_CC); + break; + case IPU_PIX_FMT_RGB666: + __raw_writel(di_mappings[4], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[5], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[6], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[7] - 1) << 12), DI_DISP_ACC_CC); + break; + case IPU_PIX_FMT_BGR666: + __raw_writel(di_mappings[8], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[9], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[10], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[11] - 1) << 12), DI_DISP_ACC_CC); + break; + default: + __raw_writel(di_mappings[12], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[13], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[14], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[15] - 1) << 12), DI_DISP_ACC_CC); + break; + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + dev_dbg(g_ipu_dev, "DI_DISP_IF_CONF = 0x%08X\n", + __raw_readl(DI_DISP_IF_CONF)); + dev_dbg(g_ipu_dev, "DI_DISP_SIG_POL = 0x%08X\n", + __raw_readl(DI_DISP_SIG_POL)); + dev_dbg(g_ipu_dev, "DI_DISP3_TIME_CONF = 0x%08X\n", + __raw_readl(DI_DISP3_TIME_CONF)); + + return 0; +} + +/*! + * This function sets the foreground and background plane global alpha blending + * modes. + * + * @param enable Boolean to enable or disable global alpha + * blending. If disabled, per pixel blending is used. + * + * @param alpha Global alpha value. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_sdc_set_global_alpha(bool enable, uint8_t alpha) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (enable) { + reg = __raw_readl(SDC_GW_CTRL) & 0x00FFFFFFL; + __raw_writel(reg | ((uint32_t) alpha << 24), SDC_GW_CTRL); + + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg | SDC_COM_GLB_A, SDC_COM_CONF); + } else { + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg & ~SDC_COM_GLB_A, SDC_COM_CONF); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +/*! + * This function sets the transparent color key for SDC graphic plane. + * + * @param channel Input parameter for the logical channel ID. + * + * @param enable Boolean to enable or disable color key + * + * @param colorKey 24-bit RGB color to use as transparent color key. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_sdc_set_color_key(ipu_channel_t channel, bool enable, + uint32_t color_key) +{ + uint32_t reg, sdc_conf; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + sdc_conf = __raw_readl(SDC_COM_CONF); + if (channel == MEM_SDC_BG) { + sdc_conf &= ~SDC_COM_GWSEL; + } else { + sdc_conf |= SDC_COM_GWSEL; + } + + if (enable) { + reg = __raw_readl(SDC_GW_CTRL) & 0xFF000000L; + __raw_writel(reg | (color_key & 0x00FFFFFFL), SDC_GW_CTRL); + + sdc_conf |= SDC_COM_KEY_COLOR_G; + } else { + sdc_conf &= ~SDC_COM_KEY_COLOR_G; + } + __raw_writel(sdc_conf, SDC_COM_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +int32_t ipu_sdc_set_brightness(uint8_t value) +{ + __raw_writel(0x03000000UL | value << 16, SDC_PWM_CTRL); + return 0; +} + +/*! + * This function sets the window position of the foreground or background plane. + * modes. + * + * @param channel Input parameter for the logical channel ID. + * + * @param x_pos The X coordinate position to place window at. + * The position is relative to the top left corner. + * + * @param y_pos The Y coordinate position to place window at. + * The position is relative to the top left corner. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_disp_set_window_pos(ipu_channel_t channel, int16_t x_pos, + int16_t y_pos) +{ + x_pos += g_h_start_width; + y_pos += g_v_start_width; + + if (channel == MEM_SDC_BG) { + __raw_writel((x_pos << 16) | y_pos, SDC_BG_POS); + } else if (channel == MEM_SDC_FG) { + __raw_writel((x_pos << 16) | y_pos, SDC_FG_POS); + } else { + return EINVAL; + } + return 0; +} + +void _ipu_sdc_fg_init(ipu_channel_params_t * params) +{ + uint32_t reg; + (void)params; + + /* Enable FG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg | SDC_COM_FG_EN | SDC_COM_BG_EN, SDC_COM_CONF); +} + +uint32_t _ipu_sdc_fg_uninit(void) +{ + uint32_t reg; + + /* Disable FG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg & ~SDC_COM_FG_EN, SDC_COM_CONF); + + return (reg & SDC_COM_FG_EN); +} + +void _ipu_sdc_bg_init(ipu_channel_params_t * params) +{ + uint32_t reg; + (void)params; + + /* Enable FG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg | SDC_COM_BG_EN, SDC_COM_CONF); +} + +uint32_t _ipu_sdc_bg_uninit(void) +{ + uint32_t reg; + + /* Disable BG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg & ~SDC_COM_BG_EN, SDC_COM_CONF); + + return (reg & SDC_COM_BG_EN); +} + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(ipu_sdc_init_panel); +EXPORT_SYMBOL(ipu_sdc_set_global_alpha); +EXPORT_SYMBOL(ipu_sdc_set_color_key); +EXPORT_SYMBOL(ipu_sdc_set_brightness); +EXPORT_SYMBOL(ipu_disp_set_window_pos); diff --git a/drivers/mxc/ipu/pf/Kconfig b/drivers/mxc/ipu/pf/Kconfig new file mode 100644 index 000000000000..fa5a777cf727 --- /dev/null +++ b/drivers/mxc/ipu/pf/Kconfig @@ -0,0 +1,7 @@ +config MXC_IPU_PF + tristate "MXC MPEG4/H.264 Post Filter Driver" + depends on MXC_IPU_V1 + default y + help + Driver for MPEG4 dering and deblock and H.264 deblock + using MXC IPU h/w diff --git a/drivers/mxc/ipu/pf/Makefile b/drivers/mxc/ipu/pf/Makefile new file mode 100644 index 000000000000..641adf4be4bd --- /dev/null +++ b/drivers/mxc/ipu/pf/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MXC_IPU_PF) += mxc_pf.o diff --git a/drivers/mxc/ipu/pf/mxc_pf.c b/drivers/mxc/ipu/pf/mxc_pf.c new file mode 100644 index 000000000000..744152415e3a --- /dev/null +++ b/drivers/mxc/ipu/pf/mxc_pf.c @@ -0,0 +1,993 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_pf.c + * + * @brief MXC IPU MPEG4/H.264 Post-filtering driver + * + * User-level API for IPU Hardware MPEG4/H.264 Post-filtering. + * + * @ingroup MXC_PF + */ + +#include <linux/pagemap.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/ipu.h> +#include <linux/mxc_pf.h> + +struct mxc_pf_data { + pf_operation_t mode; + u32 pf_enabled; + u32 width; + u32 height; + u32 stride; + uint32_t qp_size; + dma_addr_t qp_paddr; + void *qp_vaddr; + pf_buf buf[PF_MAX_BUFFER_CNT]; + void *buf_vaddr[PF_MAX_BUFFER_CNT]; + wait_queue_head_t pf_wait; + volatile int done_mask; + volatile int wait_mask; + volatile int busy_flag; + struct semaphore busy_lock; +}; + +static struct mxc_pf_data pf_data; +static u8 open_count = 0; +static struct class *mxc_pf_class; + +/* + * Function definitions + */ + +static irqreturn_t mxc_pf_irq_handler(int irq, void *dev_id) +{ + struct mxc_pf_data *pf = dev_id; + + if (irq == IPU_IRQ_PF_Y_OUT_EOF) { + pf->done_mask |= PF_WAIT_Y; + } else if (irq == IPU_IRQ_PF_U_OUT_EOF) { + pf->done_mask |= PF_WAIT_U; + } else if (irq == IPU_IRQ_PF_V_OUT_EOF) { + pf->done_mask |= PF_WAIT_V; + } else { + return IRQ_NONE; + } + + if (pf->wait_mask && ((pf->done_mask & pf->wait_mask) == pf->wait_mask)) { + wake_up_interruptible(&pf->pf_wait); + } + return IRQ_HANDLED; +} + +/*! + * This function handles PF_IOCTL_INIT calls. It initializes the PF channels, + * interrupt handlers, and channel buffers. + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_init(pf_init_params * pf_init) +{ + int err; + ipu_channel_params_t params; + u32 w; + u32 stride; + u32 h; + u32 qp_size = 0; + u32 qp_stride; + + if ((pf_init->pf_mode > 4) || (pf_init->width > 1024) || + (pf_init->height > 1024) || (pf_init->stride < pf_init->width)) { + return -EINVAL; + } + + pf_data.mode = pf_init->pf_mode; + w = pf_data.width = pf_init->width; + h = pf_data.height = pf_init->height; + stride = pf_data.stride = pf_init->stride; + pf_data.qp_size = pf_init->qp_size; + + memset(¶ms, 0, sizeof(params)); + params.mem_pf_mem.operation = pf_data.mode; + err = ipu_init_channel(MEM_PF_Y_MEM, ¶ms); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error initializing channel\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, IPU_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error initializing Y input buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, IPU_OUTPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error initializing Y output buffer\n"); + goto err0; + } + + w = w / 2; + h = h / 2; + stride = stride / 2; + + if (pf_data.mode != PF_MPEG4_DERING) { + err = ipu_init_channel_buffer(MEM_PF_U_MEM, IPU_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing U input buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_U_MEM, IPU_OUTPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing U output buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_V_MEM, IPU_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing V input buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_V_MEM, IPU_OUTPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing V output buffer\n"); + goto err0; + } + } + + /*Setup Channel QF and BSC Params */ + if (pf_data.mode == PF_H264_DEBLOCK) { + w = ((pf_data.width + 15) / 16); + h = (pf_data.height + 15) / 16; + qp_stride = w; + qp_size = 4 * qp_stride * h; + pr_debug("H264 QP width = %d, height = %d\n", w, h); + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, + IPU_SEC_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC_32, w, h, + qp_stride, IPU_ROTATE_NONE, 0, 0, + 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing H264 QP buffer\n"); + goto err0; + } +/* w = (pf_data.width + 3) / 4; */ + w *= 4; + h *= 4; + qp_stride = w; + err = ipu_init_channel_buffer(MEM_PF_U_MEM, + IPU_SEC_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, + qp_stride, IPU_ROTATE_NONE, 0, 0, + 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing H264 BSB buffer\n"); + goto err0; + } + qp_size += qp_stride * h; + } else { /* MPEG4 mode */ + + w = (pf_data.width + 15) / 16; + h = (pf_data.height + 15) / 16; + qp_stride = (w + 3) & ~0x3UL; + pr_debug("MPEG4 QP width = %d, height = %d, stride = %d\n", + w, h, qp_stride); + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, + IPU_SEC_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, + qp_stride, IPU_ROTATE_NONE, 0, 0, + 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing MPEG4 QP buffer\n"); + goto err0; + } + qp_size = qp_stride * h; + } + + /* Support 2 QP buffers */ + qp_size *= 2; + + if (pf_data.qp_size > qp_size) + qp_size = pf_data.qp_size; + else + pf_data.qp_size = qp_size; + + pf_data.qp_vaddr = dma_alloc_coherent(NULL, pf_data.qp_size, + &pf_data.qp_paddr, + GFP_KERNEL | GFP_DMA); + if (!pf_data.qp_vaddr) + return -ENOMEM; + + pf_init->qp_paddr = pf_data.qp_paddr; + pf_init->qp_size = pf_data.qp_size; + + return 0; + + err0: + return err; +} + +/*! + * This function handles PF_IOCTL_UNINIT calls. It uninitializes the PF + * channels and interrupt handlers. + * + * @return This function returns 0 on success or negative error code + * on error. + */ +static int mxc_pf_uninit(void) +{ + pf_data.pf_enabled = 0; + ipu_disable_irq(IPU_IRQ_PF_Y_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_U_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_V_OUT_EOF); + + ipu_disable_channel(MEM_PF_Y_MEM, true); + ipu_disable_channel(MEM_PF_U_MEM, true); + ipu_disable_channel(MEM_PF_V_MEM, true); + ipu_uninit_channel(MEM_PF_Y_MEM); + ipu_uninit_channel(MEM_PF_U_MEM); + ipu_uninit_channel(MEM_PF_V_MEM); + + if (pf_data.qp_vaddr) { + dma_free_coherent(NULL, pf_data.qp_size, pf_data.qp_vaddr, + pf_data.qp_paddr); + pf_data.qp_vaddr = NULL; + } + + return 0; +} + +/*! + * This function handles PF_IOCTL_REQBUFS calls. It initializes the PF channels + * and channel buffers. + * + * @param reqbufs Input/Output Structure containing buffer mode, + * type, offset, and size. The offset and size of + * the buffer are returned for PF_MEMORY_MMAP mode. + * + * @return This function returns 0 on success or negative error code + * on error. + */ +static int mxc_pf_reqbufs(pf_reqbufs_params * reqbufs) +{ + int err; + uint32_t buf_size; + int i; + int alloc_cnt = 0; + pf_buf *buf = pf_data.buf; + if (reqbufs->count > PF_MAX_BUFFER_CNT) { + reqbufs->count = PF_MAX_BUFFER_CNT; + } + /* Deallocate mmapped buffers */ + if (reqbufs->count == 0) { + for (i = 0; i < PF_MAX_BUFFER_CNT; i++) { + if (buf[i].index != -1) { + dma_free_coherent(NULL, buf[i].size, + pf_data.buf_vaddr[i], + buf[i].offset); + pf_data.buf_vaddr[i] = NULL; + buf[i].index = -1; + buf[i].size = 0; + } + } + return 0; + } + + buf_size = (pf_data.stride * pf_data.height * 3) / 2; + if (reqbufs->req_size > buf_size) { + buf_size = reqbufs->req_size; + pr_debug("using requested buffer size of %d\n", buf_size); + } else { + reqbufs->req_size = buf_size; + pr_debug("using default buffer size of %d\n", buf_size); + } + + for (i = 0; alloc_cnt < reqbufs->count; i++) { + buf[i].index = i; + buf[i].size = buf_size; + pf_data.buf_vaddr[i] = dma_alloc_coherent(NULL, buf[i].size, + &buf[i].offset, + GFP_KERNEL | GFP_DMA); + if (!pf_data.buf_vaddr[i] || !buf[i].offset) { + printk(KERN_ERR + "mxc_pf: unable to allocate IPU buffers.\n"); + err = -ENOMEM; + goto err0; + } + pr_debug("Allocated buffer %d at paddr 0x%08X, vaddr %p\n", + i, buf[i].offset, pf_data.buf_vaddr[i]); + + alloc_cnt++; + } + + return 0; + err0: + for (i = 0; i < alloc_cnt; i++) { + dma_free_coherent(NULL, buf[i].size, pf_data.buf_vaddr[i], + buf[i].offset); + pf_data.buf_vaddr[i] = NULL; + buf[i].index = -1; + buf[i].size = 0; + } + return err; +} + +/*! + * This function handles PF_IOCTL_START calls. It sets the PF channel buffers + * addresses and starts the channels + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_start(pf_buf * in, pf_buf * out, int qp_buf) +{ + int err; + dma_addr_t y_in_paddr; + dma_addr_t u_in_paddr; + dma_addr_t v_in_paddr; + dma_addr_t p1_in_paddr; + dma_addr_t p2_in_paddr; + dma_addr_t y_out_paddr; + dma_addr_t u_out_paddr; + dma_addr_t v_out_paddr; + + /* H.264 requires output buffer equal to input */ + if (pf_data.mode == PF_H264_DEBLOCK) + out = in; + + y_in_paddr = in->offset + in->y_offset; + if (in->u_offset) + u_in_paddr = in->offset + in->u_offset; + else + u_in_paddr = y_in_paddr + (pf_data.stride * pf_data.height); + if (in->v_offset) + v_in_paddr = in->offset + in->v_offset; + else + v_in_paddr = u_in_paddr + (pf_data.stride * pf_data.height) / 4; + p1_in_paddr = pf_data.qp_paddr; + if (qp_buf) + p1_in_paddr += pf_data.qp_size / 2; + + if (pf_data.mode == PF_H264_DEBLOCK) { + p2_in_paddr = p1_in_paddr + + ((pf_data.width + 15) / 16) * + ((pf_data.height + 15) / 16) * 4; + } else { + p2_in_paddr = 0; + } + + pr_debug("y_in_paddr = 0x%08X\nu_in_paddr = 0x%08X\n" + "v_in_paddr = 0x%08X\n" + "qp_paddr = 0x%08X\nbsb_paddr = 0x%08X\n", + y_in_paddr, u_in_paddr, v_in_paddr, p1_in_paddr, p2_in_paddr); + + y_out_paddr = out->offset + out->y_offset; + if (out->u_offset) + u_out_paddr = out->offset + out->u_offset; + else + u_out_paddr = y_out_paddr + (pf_data.stride * pf_data.height); + if (out->v_offset) + v_out_paddr = out->offset + out->v_offset; + else + v_out_paddr = + u_out_paddr + (pf_data.stride * pf_data.height) / 4; + + pr_debug("y_out_paddr = 0x%08X\nu_out_paddr = 0x%08X\n" + "v_out_paddr = 0x%08X\n", + y_out_paddr, u_out_paddr, v_out_paddr); + + pf_data.done_mask = 0; + + ipu_enable_irq(IPU_IRQ_PF_Y_OUT_EOF); + if (pf_data.mode != PF_MPEG4_DERING) { + ipu_enable_irq(IPU_IRQ_PF_U_OUT_EOF); + ipu_enable_irq(IPU_IRQ_PF_V_OUT_EOF); + } + + err = ipu_update_channel_buffer(MEM_PF_Y_MEM, IPU_INPUT_BUFFER, 0, + y_in_paddr); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error setting Y input buffer\n"); + goto err0; + } + + err = ipu_update_channel_buffer(MEM_PF_Y_MEM, IPU_OUTPUT_BUFFER, 0, + y_out_paddr); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error setting Y output buffer\n"); + goto err0; + } + + if (pf_data.mode != PF_MPEG4_DERING) { + err = + ipu_update_channel_buffer(MEM_PF_U_MEM, IPU_INPUT_BUFFER, 0, + u_in_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting U input buffer\n"); + goto err0; + } + + err = + ipu_update_channel_buffer(MEM_PF_U_MEM, IPU_OUTPUT_BUFFER, + 0, u_out_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting U output buffer\n"); + goto err0; + } + + err = + ipu_update_channel_buffer(MEM_PF_V_MEM, IPU_INPUT_BUFFER, 0, + v_in_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting V input buffer\n"); + goto err0; + } + + err = + ipu_update_channel_buffer(MEM_PF_V_MEM, IPU_OUTPUT_BUFFER, + 0, v_out_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting V output buffer\n"); + goto err0; + } + } + + err = ipu_update_channel_buffer(MEM_PF_Y_MEM, IPU_SEC_INPUT_BUFFER, 0, + p1_in_paddr); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error setting QP buffer\n"); + goto err0; + } + + if (pf_data.mode == PF_H264_DEBLOCK) { + + err = ipu_update_channel_buffer(MEM_PF_U_MEM, + IPU_SEC_INPUT_BUFFER, 0, + p2_in_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting H264 BSB buffer\n"); + goto err0; + } + ipu_select_buffer(MEM_PF_U_MEM, IPU_SEC_INPUT_BUFFER, 0); + } + + ipu_select_buffer(MEM_PF_Y_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_Y_MEM, IPU_SEC_INPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_Y_MEM, IPU_INPUT_BUFFER, 0); + if (pf_data.mode != PF_MPEG4_DERING) { + ipu_select_buffer(MEM_PF_U_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_V_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_U_MEM, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_V_MEM, IPU_INPUT_BUFFER, 0); + } + + if (!pf_data.pf_enabled) { + pf_data.pf_enabled = 1; + if (pf_data.mode != PF_MPEG4_DERING) { + ipu_enable_channel(MEM_PF_V_MEM); + ipu_enable_channel(MEM_PF_U_MEM); + } + ipu_enable_channel(MEM_PF_Y_MEM); + } + + return 0; + err0: + return err; +} + +/*! + * Post Filter driver open function. This function implements the Linux + * file_operations.open() API function. + * + * @param inode struct inode * + * + * @param filp struct file * + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_open(struct inode *inode, struct file *filp) +{ + int i; + + if (open_count) { + return -EBUSY; + } + + open_count++; + + memset(&pf_data, 0, sizeof(pf_data)); + for (i = 0; i < PF_MAX_BUFFER_CNT; i++) { + pf_data.buf[i].index = -1; + } + init_waitqueue_head(&pf_data.pf_wait); + init_MUTEX(&pf_data.busy_lock); + + pf_data.busy_flag = 1; + + ipu_request_irq(IPU_IRQ_PF_Y_OUT_EOF, mxc_pf_irq_handler, + 0, "mxc_ipu_pf", &pf_data); + + ipu_request_irq(IPU_IRQ_PF_U_OUT_EOF, mxc_pf_irq_handler, + 0, "mxc_ipu_pf", &pf_data); + + ipu_request_irq(IPU_IRQ_PF_V_OUT_EOF, mxc_pf_irq_handler, + 0, "mxc_ipu_pf", &pf_data); + + ipu_disable_irq(IPU_IRQ_PF_Y_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_U_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_V_OUT_EOF); + + return 0; +} + +/*! + * Post Filter driver release function. This function implements the Linux + * file_operations.release() API function. + * + * @param inode struct inode * + * + * @param filp struct file * + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_release(struct inode *inode, struct file *filp) +{ + pf_reqbufs_params req_buf; + + if (open_count) { + mxc_pf_uninit(); + + /* Free any allocated buffers */ + req_buf.count = 0; + mxc_pf_reqbufs(&req_buf); + + ipu_free_irq(IPU_IRQ_PF_V_OUT_EOF, &pf_data); + ipu_free_irq(IPU_IRQ_PF_U_OUT_EOF, &pf_data); + ipu_free_irq(IPU_IRQ_PF_Y_OUT_EOF, &pf_data); + open_count--; + } + return 0; +} + +/*! + * Post Filter driver ioctl function. This function implements the Linux + * file_operations.ioctl() API function. + * + * @param inode struct inode * + * + * @param filp struct file * + * + * @param cmd IOCTL command to handle + * + * @param arg Pointer to arguments for IOCTL + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int retval = 0; + + switch (cmd) { + case PF_IOCTL_INIT: + { + pf_init_params pf_init; + + pr_debug("PF_IOCTL_INIT\n"); + if (copy_from_user(&pf_init, (void *)arg, + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + retval = mxc_pf_init(&pf_init); + if (retval < 0) + break; + pf_init.qp_paddr = pf_data.qp_paddr; + pf_init.qp_size = pf_data.qp_size; + + /* Return size of memory allocated */ + if (copy_to_user((void *)arg, &pf_init, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + pf_data.busy_flag = 0; + break; + } + case PF_IOCTL_UNINIT: + pr_debug("PF_IOCTL_UNINIT\n"); + retval = mxc_pf_uninit(); + break; + case PF_IOCTL_REQBUFS: + { + pf_reqbufs_params reqbufs; + pr_debug("PF_IOCTL_REQBUFS\n"); + + if (copy_from_user + (&reqbufs, (void *)arg, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + retval = mxc_pf_reqbufs(&reqbufs); + + /* Return size of memory allocated */ + if (copy_to_user((void *)arg, &reqbufs, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + break; + } + case PF_IOCTL_QUERYBUF: + { + pf_buf buf; + pr_debug("PF_IOCTL_QUERYBUF\n"); + + if (copy_from_user(&buf, (void *)arg, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + if ((buf.index < 0) || + (buf.index >= PF_MAX_BUFFER_CNT) || + (pf_data.buf[buf.index].index != buf.index)) { + retval = -EINVAL; + break; + } + /* Return size of memory allocated */ + if (copy_to_user((void *)arg, &pf_data.buf[buf.index], + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + break; + } + case PF_IOCTL_START: + { + int index; + pf_start_params start_params; + pr_debug("PF_IOCTL_START\n"); + + if (pf_data.busy_flag) { + retval = -EBUSY; + break; + } + + if (copy_from_user(&start_params, (void *)arg, + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + if (start_params.h264_pause_row >= + ((pf_data.height + 15) / 16)) { + retval = -EINVAL; + break; + } + + pf_data.busy_flag = 1; + + index = start_params.in.index; + if ((index >= 0) && (index < PF_MAX_BUFFER_CNT)) { + if (pf_data.buf[index].offset != + start_params.in.offset) { + retval = -EINVAL; + break; + } + } + + index = start_params.out.index; + if ((index >= 0) && (index < PF_MAX_BUFFER_CNT)) { + if (pf_data.buf[index].offset != + start_params.out.offset) { + retval = -EINVAL; + break; + } + } + + ipu_pf_set_pause_row(start_params.h264_pause_row); + + /*Update y, u, v buffers in DMA Channels */ + if ((retval = + mxc_pf_start(&start_params.in, &start_params.out, + start_params.qp_buf)) + < 0) { + break; + } + + pr_debug("PF_IOCTL_START - processing started\n"); + + if (!start_params.wait) { + break; + } + + pr_debug("PF_IOCTL_START - waiting for completion\n"); + + pf_data.wait_mask = PF_WAIT_ALL; + /* Fall thru to wait */ + } + case PF_IOCTL_WAIT: + { + if (!pf_data.wait_mask) + pf_data.wait_mask = (u32) arg; + + if (pf_data.mode == PF_MPEG4_DERING) + pf_data.wait_mask &= PF_WAIT_Y; + + if (!pf_data.wait_mask) { + retval = -EINVAL; + break; + } + + if (!wait_event_interruptible_timeout(pf_data.pf_wait, + ((pf_data. + done_mask & + pf_data. + wait_mask) == + pf_data. + wait_mask), + 1 * HZ)) { + pr_debug + ("PF_IOCTL_WAIT: timeout, done_mask = %d\n", + pf_data.done_mask); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + pr_debug("PF_IOCTL_WAIT: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + pf_data.busy_flag = 0; + pf_data.wait_mask = 0; + + pr_debug("PF_IOCTL_WAIT - finished\n"); + break; + } + case PF_IOCTL_RESUME: + { + int pause_row; + pr_debug("PF_IOCTL_RESUME\n"); + + if (pf_data.busy_flag == 0) { + retval = -EFAULT; + break; + } + + if (copy_from_user(&pause_row, (void *)arg, + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + if (pause_row >= ((pf_data.height + 15) / 16)) { + retval = -EINVAL; + break; + } + + ipu_pf_set_pause_row(pause_row); + break; + } + + default: + printk(KERN_ERR "ipu_pf_ioctl not supported ioctl\n"); + retval = -1; + } + + if (retval < 0) + pr_debug("return = %d\n", retval); + return retval; +} + +/*! + * Post Filter driver mmap function. This function implements the Linux + * file_operations.mmap() API function for mapping driver buffers to user space. + * + * @param file struct file * + * + * @param vma structure vm_area_struct * + * + * @return 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error. + */ +static int mxc_pf_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + + pr_debug("pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&pf_data.busy_lock)) + return -EINTR; + + /* make buffers write-thru cacheable */ + vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) & + ~L_PTE_BUFFERABLE); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + printk(KERN_ERR "mxc_pf: remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mmap_exit: + up(&pf_data.busy_lock); + return res; +} + +/*! + * Post Filter driver fsync function. This function implements the Linux + * file_operations.fsync() API function. + * + * The user must call fsync() before reading an output buffer. This + * call flushes the L1 and L2 caches + * + * @param filp structure file * + * + * @param dentry struct dentry * + * + * @param datasync unused + * + * @return status POLLIN | POLLRDNORM + */ +int mxc_pf_fsync(struct file *filp, struct dentry *dentry, int datasync) +{ + flush_cache_all(); + outer_flush_all(); + return 0; +} + +/*! + * Post Filter driver poll function. This function implements the Linux + * file_operations.poll() API function. + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_pf_poll(struct file *file, poll_table * wait) +{ + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&pf_data.busy_lock)) + return -EINTR; + + queue = &pf_data.pf_wait; + poll_wait(file, queue, wait); + + up(&pf_data.busy_lock); + + return res; +} + +/*! + * File operation structure functions pointers. + */ +static struct file_operations mxc_pf_fops = { + .owner = THIS_MODULE, + .open = mxc_pf_open, + .release = mxc_pf_release, + .ioctl = mxc_pf_ioctl, + .poll = mxc_pf_poll, + .mmap = mxc_pf_mmap, + .fsync = mxc_pf_fsync, +}; + +static int mxc_pf_major = 0; + +/*! + * Post Filter driver module initialization function. + */ +int mxc_pf_dev_init(void) +{ + int ret = 0; + struct device *temp_class; + + mxc_pf_major = register_chrdev(0, "mxc_ipu_pf", &mxc_pf_fops); + + if (mxc_pf_major < 0) { + printk(KERN_INFO "Unable to get a major for mxc_ipu_pf"); + return mxc_pf_major; + } + + mxc_pf_class = class_create(THIS_MODULE, "mxc_ipu_pf"); + if (IS_ERR(mxc_pf_class)) { + printk(KERN_ERR "Error creating mxc_ipu_pf class.\n"); + ret = PTR_ERR(mxc_pf_class); + goto err_out1; + } + + temp_class = device_create(mxc_pf_class, NULL, MKDEV(mxc_pf_major, 0), NULL, + "mxc_ipu_pf"); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating mxc_ipu_pf class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + printk(KERN_INFO "IPU Post-filter loading\n"); + + return 0; + + err_out2: + class_destroy(mxc_pf_class); + err_out1: + unregister_chrdev(mxc_pf_major, "mxc_ipu_pf"); + return ret; +} + +/*! + * Post Filter driver module exit function. + */ +static void mxc_pf_exit(void) +{ + if (mxc_pf_major > 0) { + device_destroy(mxc_pf_class, MKDEV(mxc_pf_major, 0)); + class_destroy(mxc_pf_class); + unregister_chrdev(mxc_pf_major, "mxc_ipu_pf"); + } +} + +module_init(mxc_pf_dev_init); +module_exit(mxc_pf_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC MPEG4/H.264 Postfilter Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/ipu3/Kconfig b/drivers/mxc/ipu3/Kconfig new file mode 100644 index 000000000000..0ae0ffa9e19d --- /dev/null +++ b/drivers/mxc/ipu3/Kconfig @@ -0,0 +1,5 @@ +config MXC_IPU_V3 + bool + +config MXC_IPU_V3D + bool diff --git a/drivers/mxc/ipu3/Makefile b/drivers/mxc/ipu3/Makefile new file mode 100644 index 000000000000..2d9bc3c8b0c3 --- /dev/null +++ b/drivers/mxc/ipu3/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_MXC_IPU_V3) = mxc_ipu.o + +mxc_ipu-objs := ipu_common.o ipu_ic.o ipu_disp.o ipu_capture.o ipu_device.o diff --git a/drivers/mxc/ipu3/ipu_capture.c b/drivers/mxc/ipu3/ipu_capture.c new file mode 100644 index 000000000000..256fb9aa17e5 --- /dev/null +++ b/drivers/mxc/ipu3/ipu_capture.c @@ -0,0 +1,711 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_capture.c + * + * @brief IPU capture dase functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/ipu.h> +#include <linux/clk.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" + +/*! + * ipu_csi_init_interface + * Sets initial values for the CSI registers. + * The width and height of the sensor and the actual frame size will be + * set to the same values. + * @param width Sensor width + * @param height Sensor height + * @param pixel_fmt pixel format + * @param cfg_param ipu_csi_signal_cfg_t structure + * @param csi csi 0 or csi 1 + * + * @return 0 for success, -EINVAL for error + */ +int32_t +ipu_csi_init_interface(uint16_t width, uint16_t height, uint32_t pixel_fmt, + ipu_csi_signal_cfg_t cfg_param) +{ + uint32_t data = 0; + uint32_t csi = cfg_param.csi; + unsigned long lock_flags; + + /* Set SENS_DATA_FORMAT bits (8, 9 and 10) + RGB or YUV444 is 0 which is current value in data so not set + explicitly + This is also the default value if attempts are made to set it to + something invalid. */ + switch (pixel_fmt) { + case IPU_PIX_FMT_YUYV: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_YUYV; + break; + case IPU_PIX_FMT_UYVY: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_UYVY; + break; + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_BGR24: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB_YUV444; + break; + case IPU_PIX_FMT_GENERIC: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; + break; + case IPU_PIX_FMT_RGB565: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB565; + break; + case IPU_PIX_FMT_RGB555: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB555; + break; + default: + return -EINVAL; + } + + /* Set the CSI_SENS_CONF register remaining fields */ + data |= cfg_param.data_width << CSI_SENS_CONF_DATA_WIDTH_SHIFT | + cfg_param.data_fmt << CSI_SENS_CONF_DATA_FMT_SHIFT | + cfg_param.data_pol << CSI_SENS_CONF_DATA_POL_SHIFT | + cfg_param.Vsync_pol << CSI_SENS_CONF_VSYNC_POL_SHIFT | + cfg_param.Hsync_pol << CSI_SENS_CONF_HSYNC_POL_SHIFT | + cfg_param.pixclk_pol << CSI_SENS_CONF_PIX_CLK_POL_SHIFT | + cfg_param.ext_vsync << CSI_SENS_CONF_EXT_VSYNC_SHIFT | + cfg_param.clk_mode << CSI_SENS_CONF_SENS_PRTCL_SHIFT | + cfg_param.pack_tight << CSI_SENS_CONF_PACK_TIGHT_SHIFT | + cfg_param.force_eof << CSI_SENS_CONF_FORCE_EOF_SHIFT | + cfg_param.data_en_pol << CSI_SENS_CONF_DATA_EN_POL_SHIFT; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + __raw_writel(data, CSI_SENS_CONF(csi)); + + /* Setup sensor frame size */ + __raw_writel((width - 1) | (height - 1) << 16, CSI_SENS_FRM_SIZE(csi)); + + /* Set CCIR registers */ + if ((cfg_param.clk_mode == IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE) || + (cfg_param.clk_mode == IPU_CSI_CLK_MODE_CCIR656_INTERLACED)) { + _ipu_csi_ccir_err_detection_enable(csi); + __raw_writel(0x40030, CSI_CCIR_CODE_1(csi)); + __raw_writel(0xFF0000, CSI_CCIR_CODE_3(csi)); + } else if ((cfg_param.clk_mode == + IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR) || + (cfg_param.clk_mode == + IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR) || + (cfg_param.clk_mode == + IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR) || + (cfg_param.clk_mode == + IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR)) { + _ipu_csi_ccir_err_detection_enable(csi); + __raw_writel(0x40030, CSI_CCIR_CODE_1(csi)); + __raw_writel(0xFF0000, CSI_CCIR_CODE_3(csi)); + } else if ((cfg_param.clk_mode == IPU_CSI_CLK_MODE_GATED_CLK) || + (cfg_param.clk_mode == IPU_CSI_CLK_MODE_NONGATED_CLK)) { + _ipu_csi_ccir_err_detection_disable(csi); + } + + dev_dbg(g_ipu_dev, "CSI_SENS_CONF = 0x%08X\n", + __raw_readl(CSI_SENS_CONF(csi))); + dev_dbg(g_ipu_dev, "CSI_ACT_FRM_SIZE = 0x%08X\n", + __raw_readl(CSI_ACT_FRM_SIZE(csi))); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_csi_init_interface); + +/*! _ipu_csi_mclk_set + * + * @param pixel_clk desired pixel clock frequency in Hz + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_mclk_set(uint32_t pixel_clk, uint32_t csi) +{ + uint32_t temp; + uint32_t div_ratio; + + div_ratio = (clk_get_rate(g_ipu_clk) / pixel_clk) - 1; + + if (div_ratio > 0xFF || div_ratio < 0) { + dev_dbg(g_ipu_dev, "The value of pixel_clk extends normal range\n"); + return -EINVAL; + } + + temp = __raw_readl(CSI_SENS_CONF(csi)); + temp &= ~CSI_SENS_CONF_DIVRATIO_MASK; + __raw_writel(temp | (div_ratio << CSI_SENS_CONF_DIVRATIO_SHIFT), + CSI_SENS_CONF(csi)); + + return 0; +} + +/*! + * ipu_csi_enable_mclk + * + * @param csi csi 0 or csi 1 + * @param flag true to enable mclk, false to disable mclk + * @param wait true to wait 100ms make clock stable, false not wait + * + * @return Returns 0 on success + */ +int ipu_csi_enable_mclk(int csi, bool flag, bool wait) +{ + if (flag) { + clk_enable(g_csi_clk[csi]); + if (wait == true) + msleep(10); + } else + clk_disable(g_csi_clk[csi]); + + return 0; +} +EXPORT_SYMBOL(ipu_csi_enable_mclk); + +/*! + * ipu_csi_get_window_size + * + * @param width pointer to window width + * @param height pointer to window height + * @param csi csi 0 or csi 1 + */ +void ipu_csi_get_window_size(uint32_t *width, uint32_t *height, uint32_t csi) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(CSI_ACT_FRM_SIZE(csi)); + *width = (reg & 0xFFFF) + 1; + *height = (reg >> 16 & 0xFFFF) + 1; + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} +EXPORT_SYMBOL(ipu_csi_get_window_size); + +/*! + * ipu_csi_set_window_size + * + * @param width window width + * @param height window height + * @param csi csi 0 or csi 1 + */ +void ipu_csi_set_window_size(uint32_t width, uint32_t height, uint32_t csi) +{ + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + __raw_writel((width - 1) | (height - 1) << 16, CSI_ACT_FRM_SIZE(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} +EXPORT_SYMBOL(ipu_csi_set_window_size); + +/*! + * ipu_csi_set_window_pos + * + * @param left uint32 window x start + * @param top uint32 window y start + * @param csi csi 0 or csi 1 + */ +void ipu_csi_set_window_pos(uint32_t left, uint32_t top, uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp &= ~(CSI_HSC_MASK | CSI_VSC_MASK); + temp |= ((top << CSI_VSC_SHIFT) | (left << CSI_HSC_SHIFT)); + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} +EXPORT_SYMBOL(ipu_csi_set_window_pos); + +/*! + * _ipu_csi_horizontal_downsize_enable + * Enable horizontal downsizing(decimation) by 2. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_horizontal_downsize_enable(uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp |= CSI_HORI_DOWNSIZE_EN; + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_csi_horizontal_downsize_disable + * Disable horizontal downsizing(decimation) by 2. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_horizontal_downsize_disable(uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp &= ~CSI_HORI_DOWNSIZE_EN; + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_csi_vertical_downsize_enable + * Enable vertical downsizing(decimation) by 2. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_vertical_downsize_enable(uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp |= CSI_VERT_DOWNSIZE_EN; + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_csi_vertical_downsize_disable + * Disable vertical downsizing(decimation) by 2. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_vertical_downsize_disable(uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp &= ~CSI_VERT_DOWNSIZE_EN; + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * ipu_csi_set_test_generator + * + * @param active 1 for active and 0 for inactive + * @param r_value red value for the generated pattern of even pixel + * @param g_value green value for the generated pattern of even + * pixel + * @param b_value blue value for the generated pattern of even pixel + * @param pixel_clk desired pixel clock frequency in Hz + * @param csi csi 0 or csi 1 + */ +void ipu_csi_set_test_generator(bool active, uint32_t r_value, + uint32_t g_value, uint32_t b_value, uint32_t pix_clk, uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_TST_CTRL(csi)); + + if (active == false) { + temp &= ~CSI_TEST_GEN_MODE_EN; + __raw_writel(temp, CSI_TST_CTRL(csi)); + } else { + /* Set sensb_mclk div_ratio*/ + _ipu_csi_mclk_set(pix_clk, csi); + + temp &= ~(CSI_TEST_GEN_R_MASK | CSI_TEST_GEN_G_MASK | + CSI_TEST_GEN_B_MASK); + temp |= CSI_TEST_GEN_MODE_EN; + temp |= (r_value << CSI_TEST_GEN_R_SHIFT) | + (g_value << CSI_TEST_GEN_G_SHIFT) | + (b_value << CSI_TEST_GEN_B_SHIFT); + __raw_writel(temp, CSI_TST_CTRL(csi)); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} +EXPORT_SYMBOL(ipu_csi_set_test_generator); + +/*! + * _ipu_csi_ccir_err_detection_en + * Enable error detection and correction for + * CCIR interlaced mode with protection bit. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_ccir_err_detection_enable(uint32_t csi) +{ + uint32_t temp; + + temp = __raw_readl(CSI_CCIR_CODE_1(csi)); + temp |= CSI_CCIR_ERR_DET_EN; + __raw_writel(temp, CSI_CCIR_CODE_1(csi)); +} + +/*! + * _ipu_csi_ccir_err_detection_disable + * Disable error detection and correction for + * CCIR interlaced mode with protection bit. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_ccir_err_detection_disable(uint32_t csi) +{ + uint32_t temp; + + temp = __raw_readl(CSI_CCIR_CODE_1(csi)); + temp &= ~CSI_CCIR_ERR_DET_EN; + __raw_writel(temp, CSI_CCIR_CODE_1(csi)); +} + +/*! + * _ipu_csi_set_mipi_di + * + * @param num MIPI data identifier 0-3 handled by CSI + * @param di_val data identifier value + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_set_mipi_di(uint32_t num, uint32_t di_val, uint32_t csi) +{ + uint32_t temp; + int retval = 0; + unsigned long lock_flags; + + if (di_val > 0xFFL) { + retval = -EINVAL; + goto err; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_MIPI_DI(csi)); + + switch (num) { + case IPU_CSI_MIPI_DI0: + temp &= ~CSI_MIPI_DI0_MASK; + temp |= (di_val << CSI_MIPI_DI0_SHIFT); + __raw_writel(temp, CSI_MIPI_DI(csi)); + break; + case IPU_CSI_MIPI_DI1: + temp &= ~CSI_MIPI_DI1_MASK; + temp |= (di_val << CSI_MIPI_DI1_SHIFT); + __raw_writel(temp, CSI_MIPI_DI(csi)); + break; + case IPU_CSI_MIPI_DI2: + temp &= ~CSI_MIPI_DI2_MASK; + temp |= (di_val << CSI_MIPI_DI2_SHIFT); + __raw_writel(temp, CSI_MIPI_DI(csi)); + break; + case IPU_CSI_MIPI_DI3: + temp &= ~CSI_MIPI_DI3_MASK; + temp |= (di_val << CSI_MIPI_DI3_SHIFT); + __raw_writel(temp, CSI_MIPI_DI(csi)); + break; + default: + retval = -EINVAL; + goto err; + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +err: + return retval; +} + +/*! + * _ipu_csi_set_skip_isp + * + * @param skip select frames to be skipped and set the + * correspond bits to 1 + * @param max_ratio number of frames in a skipping set and the + * maximum value of max_ratio is 5 + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_set_skip_isp(uint32_t skip, uint32_t max_ratio, uint32_t csi) +{ + uint32_t temp; + int retval = 0; + unsigned long lock_flags; + + if (max_ratio > 5) { + retval = -EINVAL; + goto err; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_SKIP(csi)); + temp &= ~(CSI_MAX_RATIO_SKIP_ISP_MASK | CSI_SKIP_ISP_MASK); + temp |= (max_ratio << CSI_MAX_RATIO_SKIP_ISP_SHIFT) | + (skip << CSI_SKIP_ISP_SHIFT); + __raw_writel(temp, CSI_SKIP(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +err: + return retval; +} + +/*! + * _ipu_csi_set_skip_smfc + * + * @param skip select frames to be skipped and set the + * correspond bits to 1 + * @param max_ratio number of frames in a skipping set and the + * maximum value of max_ratio is 5 + * @param id csi to smfc skipping id + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_set_skip_smfc(uint32_t skip, uint32_t max_ratio, + uint32_t id, uint32_t csi) +{ + uint32_t temp; + int retval = 0; + unsigned long lock_flags; + + if (max_ratio > 5 || id > 3) { + retval = -EINVAL; + goto err; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_SKIP(csi)); + temp &= ~(CSI_MAX_RATIO_SKIP_SMFC_MASK | CSI_ID_2_SKIP_MASK | + CSI_SKIP_SMFC_MASK); + temp |= (max_ratio << CSI_MAX_RATIO_SKIP_SMFC_SHIFT) | + (id << CSI_ID_2_SKIP_SHIFT) | + (skip << CSI_SKIP_SMFC_SHIFT); + __raw_writel(temp, CSI_SKIP(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +err: + return retval; +} + +/*! + * _ipu_smfc_init + * Map CSI frames to IDMAC channels. + * + * @param channel IDMAC channel 0-3 + * @param mipi_id mipi id number 0-3 + * @param csi csi0 or csi1 + */ +void _ipu_smfc_init(ipu_channel_t channel, uint32_t mipi_id, uint32_t csi) +{ + uint32_t temp; + + temp = __raw_readl(SMFC_MAP); + + switch (channel) { + case CSI_MEM0: + temp &= ~SMFC_MAP_CH0_MASK; + temp |= ((csi << 2) | mipi_id) << SMFC_MAP_CH0_SHIFT; + break; + case CSI_MEM1: + temp &= ~SMFC_MAP_CH1_MASK; + temp |= ((csi << 2) | mipi_id) << SMFC_MAP_CH1_SHIFT; + break; + case CSI_MEM2: + temp &= ~SMFC_MAP_CH2_MASK; + temp |= ((csi << 2) | mipi_id) << SMFC_MAP_CH2_SHIFT; + break; + case CSI_MEM3: + temp &= ~SMFC_MAP_CH3_MASK; + temp |= ((csi << 2) | mipi_id) << SMFC_MAP_CH3_SHIFT; + break; + default: + return; + } + + __raw_writel(temp, SMFC_MAP); +} + +/*! + * _ipu_smfc_set_wmc + * Caution: The number of required channels, the enabled channels + * and the FIFO size per channel are configured restrictedly. + * + * @param channel IDMAC channel 0-3 + * @param set set 1 or clear 0 + * @param level water mark level when FIFO is on the + * relative size + */ +void _ipu_smfc_set_wmc(ipu_channel_t channel, bool set, uint32_t level) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(SMFC_WMC); + + switch (channel) { + case CSI_MEM0: + if (set == true) { + temp &= ~SMFC_WM0_SET_MASK; + temp |= level << SMFC_WM0_SET_SHIFT; + } else { + temp &= ~SMFC_WM0_CLR_MASK; + temp |= level << SMFC_WM0_CLR_SHIFT; + } + break; + case CSI_MEM1: + if (set == true) { + temp &= ~SMFC_WM1_SET_MASK; + temp |= level << SMFC_WM1_SET_SHIFT; + } else { + temp &= ~SMFC_WM1_CLR_MASK; + temp |= level << SMFC_WM1_CLR_SHIFT; + } + break; + case CSI_MEM2: + if (set == true) { + temp &= ~SMFC_WM2_SET_MASK; + temp |= level << SMFC_WM2_SET_SHIFT; + } else { + temp &= ~SMFC_WM2_CLR_MASK; + temp |= level << SMFC_WM2_CLR_SHIFT; + } + break; + case CSI_MEM3: + if (set == true) { + temp &= ~SMFC_WM3_SET_MASK; + temp |= level << SMFC_WM3_SET_SHIFT; + } else { + temp &= ~SMFC_WM3_CLR_MASK; + temp |= level << SMFC_WM3_CLR_SHIFT; + } + break; + default: + return; + } + + __raw_writel(temp, SMFC_WMC); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_smfc_set_burst_size + * + * @param channel IDMAC channel 0-3 + * @param bs burst size of IDMAC channel, + * the value programmed here shoud be BURST_SIZE-1 + */ +void _ipu_smfc_set_burst_size(ipu_channel_t channel, uint32_t bs) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(SMFC_BS); + + switch (channel) { + case CSI_MEM0: + temp &= ~SMFC_BS0_MASK; + temp |= bs << SMFC_BS0_SHIFT; + break; + case CSI_MEM1: + temp &= ~SMFC_BS1_MASK; + temp |= bs << SMFC_BS1_SHIFT; + break; + case CSI_MEM2: + temp &= ~SMFC_BS2_MASK; + temp |= bs << SMFC_BS2_SHIFT; + break; + case CSI_MEM3: + temp &= ~SMFC_BS3_MASK; + temp |= bs << SMFC_BS3_SHIFT; + break; + default: + return; + } + + __raw_writel(temp, SMFC_BS); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_csi_init + * + * @param channel IDMAC channel + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_init(ipu_channel_t channel, uint32_t csi) +{ + uint32_t csi_sens_conf, csi_dest; + int retval = 0; + + switch (channel) { + case CSI_MEM0: + case CSI_MEM1: + case CSI_MEM2: + case CSI_MEM3: + csi_dest = CSI_DATA_DEST_IDMAC; + break; + case CSI_PRP_ENC_MEM: + case CSI_PRP_VF_MEM: + csi_dest = CSI_DATA_DEST_IC; + break; + default: + retval = -EINVAL; + goto err; + } + + csi_sens_conf = __raw_readl(CSI_SENS_CONF(csi)); + csi_sens_conf &= ~CSI_SENS_CONF_DATA_DEST_MASK; + __raw_writel(csi_sens_conf | (csi_dest << + CSI_SENS_CONF_DATA_DEST_SHIFT), CSI_SENS_CONF(csi)); +err: + return retval; +} diff --git a/drivers/mxc/ipu3/ipu_common.c b/drivers/mxc/ipu3/ipu_common.c new file mode 100644 index 000000000000..0ad39e46351d --- /dev/null +++ b/drivers/mxc/ipu3/ipu_common.c @@ -0,0 +1,2225 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_common.c + * + * @brief This file contains the IPU driver common API functions. + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include <linux/clk.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +struct ipu_irq_node { + irqreturn_t(*handler) (int, void *); /*!< the ISR */ + const char *name; /*!< device associated with the interrupt */ + void *dev_id; /*!< some unique information for the ISR */ + __u32 flags; /*!< not used */ +}; + +/* Globals */ +struct clk *g_ipu_clk; +bool g_ipu_clk_enabled; +struct clk *g_di_clk[2]; +struct clk *g_csi_clk[2]; +unsigned char g_dc_di_assignment[10]; +ipu_channel_t g_ipu_csi_channel[2]; +int g_ipu_irq[2]; +int g_ipu_hw_rev; +bool g_sec_chan_en[22]; +bool g_thrd_chan_en[21]; +uint32_t g_channel_init_mask; +uint32_t g_channel_enable_mask; +DEFINE_SPINLOCK(ipu_lock); +struct device *g_ipu_dev; + +static struct ipu_irq_node ipu_irq_list[IPU_IRQ_COUNT]; +static const char driver_name[] = "mxc_ipu"; + +static int ipu_dc_use_count; +static int ipu_dp_use_count; +static int ipu_dmfc_use_count; +static int ipu_smfc_use_count; +static int ipu_ic_use_count; +static int ipu_rot_use_count; +static int ipu_vdi_use_count; +static int ipu_di_use_count[2]; +static int ipu_csi_use_count[2]; +/* Set to the follow using IC direct channel, default non */ +static ipu_channel_t using_ic_dirct_ch; + +/* for power gating */ +static uint32_t ipu_conf_reg; +static uint32_t ic_conf_reg; +static uint32_t ipu_cha_db_mode_reg[4]; +static uint32_t ipu_cha_cur_buf_reg[4]; +static uint32_t idma_enable_reg[2]; +static uint32_t buf_ready_reg[8]; + +u32 *ipu_cm_reg; +u32 *ipu_idmac_reg; +u32 *ipu_dp_reg; +u32 *ipu_ic_reg; +u32 *ipu_dc_reg; +u32 *ipu_dc_tmpl_reg; +u32 *ipu_dmfc_reg; +u32 *ipu_di_reg[2]; +u32 *ipu_smfc_reg; +u32 *ipu_csi_reg[2]; +u32 *ipu_cpmem_base; +u32 *ipu_tpmem_base; +u32 *ipu_disp_base[2]; +u32 *ipu_vdi_reg; + +/* Static functions */ +static irqreturn_t ipu_irq_handler(int irq, void *desc); + +static inline uint32_t channel_2_dma(ipu_channel_t ch, ipu_buffer_t type) +{ + return ((uint32_t) ch >> (6 * type)) & 0x3F; +}; + +static inline int _ipu_is_ic_chan(uint32_t dma_chan) +{ + return ((dma_chan >= 11) && (dma_chan <= 22) && (dma_chan != 17) && (dma_chan != 18)); +} + +static inline int _ipu_is_ic_graphic_chan(uint32_t dma_chan) +{ + return (dma_chan == 14 || dma_chan == 15); +} + +/* Either DP BG or DP FG can be graphic window */ +static inline int _ipu_is_dp_graphic_chan(uint32_t dma_chan) +{ + return (dma_chan == 23 || dma_chan == 27); +} + +static inline int _ipu_is_irt_chan(uint32_t dma_chan) +{ + return ((dma_chan >= 45) && (dma_chan <= 50)); +} + +static inline int _ipu_is_dmfc_chan(uint32_t dma_chan) +{ + return ((dma_chan >= 23) && (dma_chan <= 29)); +} + +static inline int _ipu_is_smfc_chan(uint32_t dma_chan) +{ + return ((dma_chan >= 0) && (dma_chan <= 3)); +} + +#define idma_is_valid(ch) (ch != NO_DMA) +#define idma_mask(ch) (idma_is_valid(ch) ? (1UL << (ch & 0x1F)) : 0) +#define idma_is_set(reg, dma) (__raw_readl(reg(dma)) & idma_mask(dma)) + +/*! + * This function is called by the driver framework to initialize the IPU + * hardware. + * + * @param dev The device structure for the IPU passed in by the + * driver framework. + * + * @return Returns 0 on success or negative error code on error + */ +static int ipu_probe(struct platform_device *pdev) +{ + struct resource *res; + struct mxc_ipu_config *plat_data = pdev->dev.platform_data; + unsigned long ipu_base; + + spin_lock_init(&ipu_lock); + + g_ipu_hw_rev = plat_data->rev; + + g_ipu_dev = &pdev->dev; + + /* Register IPU interrupts */ + g_ipu_irq[0] = platform_get_irq(pdev, 0); + if (g_ipu_irq[0] < 0) + return -EINVAL; + + if (request_irq(g_ipu_irq[0], ipu_irq_handler, 0, pdev->name, 0) != 0) { + dev_err(g_ipu_dev, "request SYNC interrupt failed\n"); + return -EBUSY; + } + /* Some platforms have 2 IPU interrupts */ + g_ipu_irq[1] = platform_get_irq(pdev, 1); + if (g_ipu_irq[1] >= 0) { + if (request_irq + (g_ipu_irq[1], ipu_irq_handler, 0, pdev->name, 0) != 0) { + dev_err(g_ipu_dev, "request ERR interrupt failed\n"); + return -EBUSY; + } + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (IS_ERR(res)) + return -ENODEV; + + ipu_base = res->start; + ipu_cm_reg = ioremap(ipu_base + IPU_CM_REG_BASE, PAGE_SIZE); + ipu_ic_reg = ioremap(ipu_base + IPU_IC_REG_BASE, PAGE_SIZE); + ipu_idmac_reg = ioremap(ipu_base + IPU_IDMAC_REG_BASE, PAGE_SIZE); + /* DP Registers are accessed thru the SRM */ + ipu_dp_reg = ioremap(ipu_base + IPU_SRM_REG_BASE, PAGE_SIZE); + ipu_dc_reg = ioremap(ipu_base + IPU_DC_REG_BASE, PAGE_SIZE); + ipu_dmfc_reg = ioremap(ipu_base + IPU_DMFC_REG_BASE, PAGE_SIZE); + ipu_di_reg[0] = ioremap(ipu_base + IPU_DI0_REG_BASE, PAGE_SIZE); + ipu_di_reg[1] = ioremap(ipu_base + IPU_DI1_REG_BASE, PAGE_SIZE); + ipu_smfc_reg = ioremap(ipu_base + IPU_SMFC_REG_BASE, PAGE_SIZE); + ipu_csi_reg[0] = ioremap(ipu_base + IPU_CSI0_REG_BASE, PAGE_SIZE); + ipu_csi_reg[1] = ioremap(ipu_base + IPU_CSI1_REG_BASE, PAGE_SIZE); + ipu_cpmem_base = ioremap(ipu_base + IPU_CPMEM_REG_BASE, PAGE_SIZE); + ipu_tpmem_base = ioremap(ipu_base + IPU_TPM_REG_BASE, SZ_64K); + ipu_dc_tmpl_reg = ioremap(ipu_base + IPU_DC_TMPL_REG_BASE, SZ_128K); + ipu_disp_base[1] = ioremap(ipu_base + IPU_DISP1_BASE, SZ_4K); + ipu_vdi_reg = ioremap(ipu_base + IPU_VDI_REG_BASE, PAGE_SIZE); + + dev_dbg(g_ipu_dev, "IPU VDI Regs = %p\n", ipu_vdi_reg); + dev_dbg(g_ipu_dev, "IPU CM Regs = %p\n", ipu_cm_reg); + dev_dbg(g_ipu_dev, "IPU IC Regs = %p\n", ipu_ic_reg); + dev_dbg(g_ipu_dev, "IPU IDMAC Regs = %p\n", ipu_idmac_reg); + dev_dbg(g_ipu_dev, "IPU DP Regs = %p\n", ipu_dp_reg); + dev_dbg(g_ipu_dev, "IPU DC Regs = %p\n", ipu_dc_reg); + dev_dbg(g_ipu_dev, "IPU DMFC Regs = %p\n", ipu_dmfc_reg); + dev_dbg(g_ipu_dev, "IPU DI0 Regs = %p\n", ipu_di_reg[0]); + dev_dbg(g_ipu_dev, "IPU DI1 Regs = %p\n", ipu_di_reg[1]); + dev_dbg(g_ipu_dev, "IPU SMFC Regs = %p\n", ipu_smfc_reg); + dev_dbg(g_ipu_dev, "IPU CSI0 Regs = %p\n", ipu_csi_reg[0]); + dev_dbg(g_ipu_dev, "IPU CSI1 Regs = %p\n", ipu_csi_reg[1]); + dev_dbg(g_ipu_dev, "IPU CPMem = %p\n", ipu_cpmem_base); + dev_dbg(g_ipu_dev, "IPU TPMem = %p\n", ipu_tpmem_base); + dev_dbg(g_ipu_dev, "IPU DC Template Mem = %p\n", ipu_dc_tmpl_reg); + dev_dbg(g_ipu_dev, "IPU Display Region 1 Mem = %p\n", ipu_disp_base[1]); + + /* Enable IPU and CSI clocks */ + /* Get IPU clock freq */ + g_ipu_clk = clk_get(&pdev->dev, "ipu_clk"); + dev_dbg(g_ipu_dev, "ipu_clk = %lu\n", clk_get_rate(g_ipu_clk)); + + clk_enable(g_ipu_clk); + + g_di_clk[0] = plat_data->di_clk[0]; + g_di_clk[1] = plat_data->di_clk[1]; + + g_csi_clk[0] = clk_get(&pdev->dev, "csi_mclk1"); + g_csi_clk[1] = clk_get(&pdev->dev, "csi_mclk2"); + + __raw_writel(0x807FFFFF, IPU_MEM_RST); + while (__raw_readl(IPU_MEM_RST) & 0x80000000) ; + + _ipu_init_dc_mappings(); + + /* Enable error interrupts by default */ + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(5)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(6)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(9)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(10)); + + /* DMFC Init */ + _ipu_dmfc_init(); + + /* Set sync refresh channels as high priority */ + __raw_writel(0x18800000L, IDMAC_CHA_PRI(0)); + + /* Set MCU_T to divide MCU access window into 2 */ + __raw_writel(0x00400000L | (IPU_MCU_T_DEFAULT << 18), IPU_DISP_GEN); + + clk_disable(g_ipu_clk); + + register_ipu_device(); + + return 0; +} + +int ipu_remove(struct platform_device *pdev) +{ + if (g_ipu_irq[0]) + free_irq(g_ipu_irq[0], 0); + if (g_ipu_irq[1]) + free_irq(g_ipu_irq[1], 0); + + clk_put(g_ipu_clk); + + iounmap(ipu_cm_reg); + iounmap(ipu_ic_reg); + iounmap(ipu_idmac_reg); + iounmap(ipu_dc_reg); + iounmap(ipu_dp_reg); + iounmap(ipu_dmfc_reg); + iounmap(ipu_di_reg[0]); + iounmap(ipu_di_reg[1]); + iounmap(ipu_smfc_reg); + iounmap(ipu_csi_reg[0]); + iounmap(ipu_csi_reg[1]); + iounmap(ipu_cpmem_base); + iounmap(ipu_tpmem_base); + iounmap(ipu_dc_tmpl_reg); + iounmap(ipu_disp_base[1]); + iounmap(ipu_vdi_reg); + + return 0; +} + +void ipu_dump_registers(void) +{ + printk(KERN_DEBUG "IPU_CONF = \t0x%08X\n", __raw_readl(IPU_CONF)); + printk(KERN_DEBUG "IDMAC_CONF = \t0x%08X\n", __raw_readl(IDMAC_CONF)); + printk(KERN_DEBUG "IDMAC_CHA_EN1 = \t0x%08X\n", + __raw_readl(IDMAC_CHA_EN(0))); + printk(KERN_DEBUG "IDMAC_CHA_EN2 = \t0x%08X\n", + __raw_readl(IDMAC_CHA_EN(32))); + printk(KERN_DEBUG "IDMAC_CHA_PRI1 = \t0x%08X\n", + __raw_readl(IDMAC_CHA_PRI(0))); + printk(KERN_DEBUG "IDMAC_CHA_PRI2 = \t0x%08X\n", + __raw_readl(IDMAC_CHA_PRI(32))); + printk(KERN_DEBUG "IDMAC_BAND_EN1 = \t0x%08X\n", + __raw_readl(IDMAC_BAND_EN(0))); + printk(KERN_DEBUG "IDMAC_BAND_EN2 = \t0x%08X\n", + __raw_readl(IDMAC_BAND_EN(32))); + printk(KERN_DEBUG "IPU_CHA_DB_MODE_SEL0 = \t0x%08X\n", + __raw_readl(IPU_CHA_DB_MODE_SEL(0))); + printk(KERN_DEBUG "IPU_CHA_DB_MODE_SEL1 = \t0x%08X\n", + __raw_readl(IPU_CHA_DB_MODE_SEL(32))); + printk(KERN_DEBUG "DMFC_WR_CHAN = \t0x%08X\n", + __raw_readl(DMFC_WR_CHAN)); + printk(KERN_DEBUG "DMFC_WR_CHAN_DEF = \t0x%08X\n", + __raw_readl(DMFC_WR_CHAN_DEF)); + printk(KERN_DEBUG "DMFC_DP_CHAN = \t0x%08X\n", + __raw_readl(DMFC_DP_CHAN)); + printk(KERN_DEBUG "DMFC_DP_CHAN_DEF = \t0x%08X\n", + __raw_readl(DMFC_DP_CHAN_DEF)); + printk(KERN_DEBUG "DMFC_IC_CTRL = \t0x%08X\n", + __raw_readl(DMFC_IC_CTRL)); + printk(KERN_DEBUG "IPU_FS_PROC_FLOW1 = \t0x%08X\n", + __raw_readl(IPU_FS_PROC_FLOW1)); + printk(KERN_DEBUG "IPU_FS_PROC_FLOW2 = \t0x%08X\n", + __raw_readl(IPU_FS_PROC_FLOW2)); + printk(KERN_DEBUG "IPU_FS_PROC_FLOW3 = \t0x%08X\n", + __raw_readl(IPU_FS_PROC_FLOW3)); + printk(KERN_DEBUG "IPU_FS_DISP_FLOW1 = \t0x%08X\n", + __raw_readl(IPU_FS_DISP_FLOW1)); +} + +/*! + * This function is called to initialize a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID to init. + * + * @param params Input parameter containing union of channel + * initialization parameters. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_init_channel(ipu_channel_t channel, ipu_channel_params_t *params) +{ + int ret = 0; + uint32_t ipu_conf; + uint32_t reg; + unsigned long lock_flags; + + dev_dbg(g_ipu_dev, "init channel = %d\n", IPU_CHAN_ID(channel)); + + /* re-enable error interrupts every time a channel is initialized */ + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(5)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(6)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(9)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(10)); + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (g_channel_init_mask & (1L << IPU_CHAN_ID(channel))) { + dev_err(g_ipu_dev, "Warning: channel already initialized %d\n", + IPU_CHAN_ID(channel)); + } + + ipu_conf = __raw_readl(IPU_CONF); + + switch (channel) { + case CSI_MEM0: + case CSI_MEM1: + case CSI_MEM2: + case CSI_MEM3: + if (params->csi_mem.csi > 1) { + ret = -EINVAL; + goto err; + } + + ipu_smfc_use_count++; + ipu_csi_use_count[params->csi_mem.csi]++; + g_ipu_csi_channel[params->csi_mem.csi] = channel; + + /*SMFC setting*/ + if (params->csi_mem.mipi_en) { + ipu_conf |= (1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET + + params->csi_mem.csi)); + _ipu_smfc_init(channel, params->csi_mem.mipi_id, + params->csi_mem.csi); + } else { + ipu_conf &= ~(1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET + + params->csi_mem.csi)); + _ipu_smfc_init(channel, 0, params->csi_mem.csi); + } + + /*CSI data (include compander) dest*/ + _ipu_csi_init(channel, params->csi_mem.csi); + break; + case CSI_PRP_ENC_MEM: + if (params->csi_prp_enc_mem.csi > 1) { + ret = -EINVAL; + goto err; + } + if ((using_ic_dirct_ch != 0) && + (using_ic_dirct_ch != MEM_PRP_ENC_MEM)) { + ret = -EINVAL; + goto err; + } + using_ic_dirct_ch = CSI_PRP_ENC_MEM; + + ipu_ic_use_count++; + ipu_csi_use_count[params->csi_prp_enc_mem.csi]++; + g_ipu_csi_channel[params->csi_prp_enc_mem.csi] = channel; + + /*Without SMFC, CSI only support parallel data source*/ + ipu_conf &= ~(1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET + + params->csi_prp_enc_mem.csi)); + + /*CSI0/1 feed into IC*/ + ipu_conf &= ~IPU_CONF_IC_INPUT; + if (params->csi_prp_enc_mem.csi) + ipu_conf |= IPU_CONF_CSI_SEL; + else + ipu_conf &= ~IPU_CONF_CSI_SEL; + + /*PRP skip buffer in memory, only valid when RWS_EN is true*/ + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_ENC_IN_VALID, IPU_FS_PROC_FLOW1); + + /*CSI data (include compander) dest*/ + _ipu_csi_init(channel, params->csi_prp_enc_mem.csi); + _ipu_ic_init_prpenc(params, true); + break; + case CSI_PRP_VF_MEM: + if (params->csi_prp_vf_mem.csi > 1) { + ret = -EINVAL; + goto err; + } + if ((using_ic_dirct_ch != 0) && + (using_ic_dirct_ch != MEM_PRP_VF_MEM)) { + ret = -EINVAL; + goto err; + } + using_ic_dirct_ch = CSI_PRP_VF_MEM; + + ipu_ic_use_count++; + ipu_csi_use_count[params->csi_prp_vf_mem.csi]++; + g_ipu_csi_channel[params->csi_prp_vf_mem.csi] = channel; + + /*Without SMFC, CSI only support parallel data source*/ + ipu_conf &= ~(1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET + + params->csi_prp_vf_mem.csi)); + + /*CSI0/1 feed into IC*/ + ipu_conf &= ~IPU_CONF_IC_INPUT; + if (params->csi_prp_vf_mem.csi) + ipu_conf |= IPU_CONF_CSI_SEL; + else + ipu_conf &= ~IPU_CONF_CSI_SEL; + + /*PRP skip buffer in memory, only valid when RWS_EN is true*/ + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_VF_IN_VALID, IPU_FS_PROC_FLOW1); + + /*CSI data (include compander) dest*/ + _ipu_csi_init(channel, params->csi_prp_vf_mem.csi); + _ipu_ic_init_prpvf(params, true); + break; + case MEM_PRP_VF_MEM: + ipu_ic_use_count++; + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg | FS_VF_IN_VALID, IPU_FS_PROC_FLOW1); + + if (params->mem_prp_vf_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + if (params->mem_prp_vf_mem.alpha_chan_en) + g_thrd_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_prpvf(params, false); + break; + case MEM_VDI_PRP_VF_MEM: + if ((using_ic_dirct_ch != 0) && + (using_ic_dirct_ch != MEM_VDI_PRP_VF_MEM)) { + ret = -EINVAL; + goto err; + } + using_ic_dirct_ch = MEM_VDI_PRP_VF_MEM; + ipu_ic_use_count++; + ipu_vdi_use_count++; + reg = __raw_readl(IPU_FS_PROC_FLOW1); + reg &= ~FS_VDI_SRC_SEL_MASK; + __raw_writel(reg , IPU_FS_PROC_FLOW1); + + if (params->mem_prp_vf_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + _ipu_ic_init_prpvf(params, false); + _ipu_vdi_init(channel, params); + break; + case MEM_VDI_PRP_VF_MEM_P: + _ipu_vdi_init(channel, params); + break; + case MEM_VDI_PRP_VF_MEM_N: + _ipu_vdi_init(channel, params); + break; + case MEM_ROT_VF_MEM: + ipu_ic_use_count++; + ipu_rot_use_count++; + _ipu_ic_init_rotate_vf(params); + break; + case MEM_PRP_ENC_MEM: + ipu_ic_use_count++; + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg | FS_ENC_IN_VALID, IPU_FS_PROC_FLOW1); + _ipu_ic_init_prpenc(params, false); + break; + case MEM_ROT_ENC_MEM: + ipu_ic_use_count++; + ipu_rot_use_count++; + _ipu_ic_init_rotate_enc(params); + break; + case MEM_PP_MEM: + if (params->mem_pp_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + if (params->mem_pp_mem.alpha_chan_en) + g_thrd_chan_en[IPU_CHAN_ID(channel)] = true; + _ipu_ic_init_pp(params); + ipu_ic_use_count++; + break; + case MEM_ROT_PP_MEM: + _ipu_ic_init_rotate_pp(params); + ipu_ic_use_count++; + ipu_rot_use_count++; + break; + case MEM_DC_SYNC: + if (params->mem_dc_sync.di > 1) { + ret = -EINVAL; + goto err; + } + + g_dc_di_assignment[1] = params->mem_dc_sync.di; + _ipu_dc_init(1, params->mem_dc_sync.di, + params->mem_dc_sync.interlaced); + ipu_di_use_count[params->mem_dc_sync.di]++; + ipu_dc_use_count++; + ipu_dmfc_use_count++; + break; + case MEM_BG_SYNC: + if (params->mem_dp_bg_sync.di > 1) { + ret = -EINVAL; + goto err; + } + + if (params->mem_dp_bg_sync.alpha_chan_en) + g_thrd_chan_en[IPU_CHAN_ID(channel)] = true; + + g_dc_di_assignment[5] = params->mem_dp_bg_sync.di; + _ipu_dp_init(channel, params->mem_dp_bg_sync.in_pixel_fmt, + params->mem_dp_bg_sync.out_pixel_fmt); + _ipu_dc_init(5, params->mem_dp_bg_sync.di, + params->mem_dp_bg_sync.interlaced); + ipu_di_use_count[params->mem_dp_bg_sync.di]++; + ipu_dc_use_count++; + ipu_dp_use_count++; + ipu_dmfc_use_count++; + break; + case MEM_FG_SYNC: + _ipu_dp_init(channel, params->mem_dp_fg_sync.in_pixel_fmt, + params->mem_dp_fg_sync.out_pixel_fmt); + + if (params->mem_dp_fg_sync.alpha_chan_en) + g_thrd_chan_en[IPU_CHAN_ID(channel)] = true; + + ipu_dc_use_count++; + ipu_dp_use_count++; + ipu_dmfc_use_count++; + break; + case DIRECT_ASYNC0: + if (params->direct_async.di > 1) { + ret = -EINVAL; + goto err; + } + + g_dc_di_assignment[8] = params->direct_async.di; + _ipu_dc_init(8, params->direct_async.di, false); + ipu_di_use_count[params->direct_async.di]++; + ipu_dc_use_count++; + break; + case DIRECT_ASYNC1: + if (params->direct_async.di > 1) { + ret = -EINVAL; + goto err; + } + + g_dc_di_assignment[9] = params->direct_async.di; + _ipu_dc_init(9, params->direct_async.di, false); + ipu_di_use_count[params->direct_async.di]++; + ipu_dc_use_count++; + break; + default: + dev_err(g_ipu_dev, "Missing channel initialization\n"); + break; + } + + /* Enable IPU sub module */ + g_channel_init_mask |= 1L << IPU_CHAN_ID(channel); + if (ipu_ic_use_count == 1) + ipu_conf |= IPU_CONF_IC_EN; + if (ipu_vdi_use_count == 1) { + ipu_conf |= IPU_CONF_VDI_EN; + ipu_conf |= IPU_CONF_IC_INPUT; + } + if (ipu_rot_use_count == 1) + ipu_conf |= IPU_CONF_ROT_EN; + if (ipu_dc_use_count == 1) + ipu_conf |= IPU_CONF_DC_EN; + if (ipu_dp_use_count == 1) + ipu_conf |= IPU_CONF_DP_EN; + if (ipu_dmfc_use_count == 1) + ipu_conf |= IPU_CONF_DMFC_EN; + if (ipu_di_use_count[0] == 1) { + ipu_conf |= IPU_CONF_DI0_EN; + clk_enable(g_di_clk[0]); + } + if (ipu_di_use_count[1] == 1) { + ipu_conf |= IPU_CONF_DI1_EN; + clk_enable(g_di_clk[1]); + } + if (ipu_smfc_use_count == 1) + ipu_conf |= IPU_CONF_SMFC_EN; + if (ipu_csi_use_count[0] == 1) + ipu_conf |= IPU_CONF_CSI0_EN; + if (ipu_csi_use_count[1] == 1) + ipu_conf |= IPU_CONF_CSI1_EN; + + __raw_writel(ipu_conf, IPU_CONF); + +err: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return ret; +} +EXPORT_SYMBOL(ipu_init_channel); + +/*! + * This function is called to uninitialize a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID to uninit. + */ +void ipu_uninit_channel(ipu_channel_t channel) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t in_dma, out_dma = 0; + uint32_t ipu_conf; + + if ((g_channel_init_mask & (1L << IPU_CHAN_ID(channel))) == 0) { + dev_err(g_ipu_dev, "Channel already uninitialized %d\n", + IPU_CHAN_ID(channel)); + return; + } + + /* Make sure channel is disabled */ + /* Get input and output dma channels */ + in_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + out_dma = channel_2_dma(channel, IPU_VIDEO_IN_BUFFER); + + if (idma_is_set(IDMAC_CHA_EN, in_dma) || + idma_is_set(IDMAC_CHA_EN, out_dma)) { + dev_err(g_ipu_dev, + "Channel %d is not disabled, disable first\n", + IPU_CHAN_ID(channel)); + return; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + ipu_conf = __raw_readl(IPU_CONF); + + /* Reset the double buffer */ + reg = __raw_readl(IPU_CHA_DB_MODE_SEL(in_dma)); + __raw_writel(reg & ~idma_mask(in_dma), IPU_CHA_DB_MODE_SEL(in_dma)); + reg = __raw_readl(IPU_CHA_DB_MODE_SEL(out_dma)); + __raw_writel(reg & ~idma_mask(out_dma), IPU_CHA_DB_MODE_SEL(out_dma)); + + g_sec_chan_en[IPU_CHAN_ID(channel)] = false; + g_thrd_chan_en[IPU_CHAN_ID(channel)] = false; + + switch (channel) { + case CSI_MEM0: + case CSI_MEM1: + case CSI_MEM2: + case CSI_MEM3: + ipu_smfc_use_count--; + if (g_ipu_csi_channel[0] == channel) { + g_ipu_csi_channel[0] = CHAN_NONE; + ipu_csi_use_count[0]--; + } else if (g_ipu_csi_channel[1] == channel) { + g_ipu_csi_channel[1] = CHAN_NONE; + ipu_csi_use_count[1]--; + } + break; + case CSI_PRP_ENC_MEM: + ipu_ic_use_count--; + if (using_ic_dirct_ch == CSI_PRP_ENC_MEM) + using_ic_dirct_ch = 0; + _ipu_ic_uninit_prpenc(); + if (g_ipu_csi_channel[0] == channel) { + g_ipu_csi_channel[0] = CHAN_NONE; + ipu_csi_use_count[0]--; + } else if (g_ipu_csi_channel[1] == channel) { + g_ipu_csi_channel[1] = CHAN_NONE; + ipu_csi_use_count[1]--; + } + break; + case CSI_PRP_VF_MEM: + ipu_ic_use_count--; + if (using_ic_dirct_ch == CSI_PRP_VF_MEM) + using_ic_dirct_ch = 0; + _ipu_ic_uninit_prpvf(); + if (g_ipu_csi_channel[0] == channel) { + g_ipu_csi_channel[0] = CHAN_NONE; + ipu_csi_use_count[0]--; + } else if (g_ipu_csi_channel[1] == channel) { + g_ipu_csi_channel[1] = CHAN_NONE; + ipu_csi_use_count[1]--; + } + break; + case MEM_PRP_VF_MEM: + ipu_ic_use_count--; + _ipu_ic_uninit_prpvf(); + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_VF_IN_VALID, IPU_FS_PROC_FLOW1); + break; + case MEM_VDI_PRP_VF_MEM: + ipu_ic_use_count--; + ipu_vdi_use_count--; + if (using_ic_dirct_ch == MEM_VDI_PRP_VF_MEM) + using_ic_dirct_ch = 0; + _ipu_ic_uninit_prpvf(); + _ipu_vdi_uninit(); + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_VF_IN_VALID, IPU_FS_PROC_FLOW1); + break; + case MEM_ROT_VF_MEM: + ipu_rot_use_count--; + ipu_ic_use_count--; + _ipu_ic_uninit_rotate_vf(); + break; + case MEM_PRP_ENC_MEM: + ipu_ic_use_count--; + _ipu_ic_uninit_prpenc(); + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_ENC_IN_VALID, IPU_FS_PROC_FLOW1); + break; + case MEM_ROT_ENC_MEM: + ipu_rot_use_count--; + ipu_ic_use_count--; + _ipu_ic_uninit_rotate_enc(); + break; + case MEM_PP_MEM: + ipu_ic_use_count--; + _ipu_ic_uninit_pp(); + break; + case MEM_ROT_PP_MEM: + ipu_rot_use_count--; + ipu_ic_use_count--; + _ipu_ic_uninit_rotate_pp(); + break; + case MEM_DC_SYNC: + _ipu_dc_uninit(1); + ipu_di_use_count[g_dc_di_assignment[1]]--; + ipu_dc_use_count--; + ipu_dmfc_use_count--; + break; + case MEM_BG_SYNC: + _ipu_dp_uninit(channel); + _ipu_dc_uninit(5); + ipu_di_use_count[g_dc_di_assignment[5]]--; + ipu_dc_use_count--; + ipu_dp_use_count--; + ipu_dmfc_use_count--; + break; + case MEM_FG_SYNC: + _ipu_dp_uninit(channel); + ipu_dc_use_count--; + ipu_dp_use_count--; + ipu_dmfc_use_count--; + break; + case DIRECT_ASYNC0: + _ipu_dc_uninit(8); + ipu_di_use_count[g_dc_di_assignment[8]]--; + ipu_dc_use_count--; + break; + case DIRECT_ASYNC1: + _ipu_dc_uninit(9); + ipu_di_use_count[g_dc_di_assignment[9]]--; + ipu_dc_use_count--; + break; + default: + break; + } + + g_channel_init_mask &= ~(1L << IPU_CHAN_ID(channel)); + + if (ipu_ic_use_count == 0) + ipu_conf &= ~IPU_CONF_IC_EN; + if (ipu_vdi_use_count == 0) { + ipu_conf &= ~IPU_CONF_VDI_EN; + ipu_conf &= ~IPU_CONF_IC_INPUT; + } + if (ipu_rot_use_count == 0) + ipu_conf &= ~IPU_CONF_ROT_EN; + if (ipu_dc_use_count == 0) + ipu_conf &= ~IPU_CONF_DC_EN; + if (ipu_dp_use_count == 0) + ipu_conf &= ~IPU_CONF_DP_EN; + if (ipu_dmfc_use_count == 0) + ipu_conf &= ~IPU_CONF_DMFC_EN; + if (ipu_di_use_count[0] == 0) { + ipu_conf &= ~IPU_CONF_DI0_EN; + clk_disable(g_di_clk[0]); + } + if (ipu_di_use_count[1] == 0) { + ipu_conf &= ~IPU_CONF_DI1_EN; + clk_disable(g_di_clk[1]); + } + if (ipu_smfc_use_count == 0) + ipu_conf &= ~IPU_CONF_SMFC_EN; + if (ipu_csi_use_count[0] == 0) + ipu_conf &= ~IPU_CONF_CSI0_EN; + if (ipu_csi_use_count[1] == 0) + ipu_conf &= ~IPU_CONF_CSI1_EN; + + __raw_writel(ipu_conf, IPU_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + if (ipu_conf == 0) { + clk_disable(g_ipu_clk); + g_ipu_clk_enabled = false; + } + + WARN_ON(ipu_ic_use_count < 0); + WARN_ON(ipu_vdi_use_count < 0); + WARN_ON(ipu_rot_use_count < 0); + WARN_ON(ipu_dc_use_count < 0); + WARN_ON(ipu_dp_use_count < 0); + WARN_ON(ipu_dmfc_use_count < 0); + WARN_ON(ipu_smfc_use_count < 0); +} +EXPORT_SYMBOL(ipu_uninit_channel); + +/*! + * This function is called to initialize a buffer for logical IPU channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param pixel_fmt Input parameter for pixel format of buffer. + * Pixel format is a FOURCC ASCII code. + * + * @param width Input parameter for width of buffer in pixels. + * + * @param height Input parameter for height of buffer in pixels. + * + * @param stride Input parameter for stride length of buffer + * in pixels. + * + * @param rot_mode Input parameter for rotation setting of buffer. + * A rotation setting other than + * IPU_ROTATE_VERT_FLIP + * should only be used for input buffers of + * rotation channels. + * + * @param phyaddr_0 Input parameter buffer 0 physical address. + * + * @param phyaddr_1 Input parameter buffer 1 physical address. + * Setting this to a value other than NULL enables + * double buffering mode. + * + * @param u private u offset for additional cropping, + * zero if not used. + * + * @param v private v offset for additional cropping, + * zero if not used. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_init_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t pixel_fmt, + uint16_t width, uint16_t height, + uint32_t stride, + ipu_rotate_mode_t rot_mode, + dma_addr_t phyaddr_0, dma_addr_t phyaddr_1, + uint32_t u, uint32_t v) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t dma_chan; + uint32_t burst_size; + + dma_chan = channel_2_dma(channel, type); + if (!idma_is_valid(dma_chan)) + return -EINVAL; + + if (stride < width * bytes_per_pixel(pixel_fmt)) + stride = width * bytes_per_pixel(pixel_fmt); + + if (stride % 4) { + dev_err(g_ipu_dev, + "Stride not 32-bit aligned, stride = %d\n", stride); + return -EINVAL; + } + /* IC & IRT channels' width must be multiple of 8 pixels */ + if ((_ipu_is_ic_chan(dma_chan) || _ipu_is_irt_chan(dma_chan)) + && (width % 8)) { + dev_err(g_ipu_dev, "Width must be 8 pixel multiple\n"); + return -EINVAL; + } + + /* Build parameter memory data for DMA channel */ + _ipu_ch_param_init(dma_chan, pixel_fmt, width, height, stride, u, v, 0, + phyaddr_0, phyaddr_1); + + /* Set correlative channel parameter of local alpha channel */ + if ((_ipu_is_ic_graphic_chan(dma_chan) || + _ipu_is_dp_graphic_chan(dma_chan)) && + (g_thrd_chan_en[IPU_CHAN_ID(channel)] == true)) { + _ipu_ch_param_set_alpha_use_separate_channel(dma_chan, true); + _ipu_ch_param_set_alpha_buffer_memory(dma_chan); + _ipu_ch_param_set_alpha_condition_read(dma_chan); + /* fix alpha width as 8 and burst size as 16*/ + _ipu_ch_params_set_alpha_width(dma_chan, 8); + _ipu_ch_param_set_burst_size(dma_chan, 16); + } else if (_ipu_is_ic_graphic_chan(dma_chan) && + ipu_pixel_format_has_alpha(pixel_fmt)) + _ipu_ch_param_set_alpha_use_separate_channel(dma_chan, false); + + if (rot_mode) + _ipu_ch_param_set_rotation(dma_chan, rot_mode); + + /* IC and ROT channels have restriction of 8 or 16 pix burst length */ + if (_ipu_is_ic_chan(dma_chan)) { + if ((width % 16) == 0) + _ipu_ch_param_set_burst_size(dma_chan, 16); + else + _ipu_ch_param_set_burst_size(dma_chan, 8); + } else if (_ipu_is_irt_chan(dma_chan)) { + _ipu_ch_param_set_burst_size(dma_chan, 8); + _ipu_ch_param_set_block_mode(dma_chan); + } else if (_ipu_is_dmfc_chan(dma_chan)) + _ipu_dmfc_set_wait4eot(dma_chan, width); + + if (_ipu_chan_is_interlaced(channel)) + _ipu_ch_param_set_interlaced_scan(dma_chan); + + if (_ipu_is_ic_chan(dma_chan) || _ipu_is_irt_chan(dma_chan)) { + burst_size = _ipu_ch_param_get_burst_size(dma_chan); + _ipu_ic_idma_init(dma_chan, width, height, burst_size, + rot_mode); + } else if (_ipu_is_smfc_chan(dma_chan)) { + burst_size = _ipu_ch_param_get_burst_size(dma_chan); + if ((pixel_fmt == IPU_PIX_FMT_GENERIC) && + ((_ipu_ch_param_get_bpp(dma_chan) == 5) || + (_ipu_ch_param_get_bpp(dma_chan) == 3))) + burst_size = burst_size >> 4; + else + burst_size = burst_size >> 2; + _ipu_smfc_set_burst_size(channel, burst_size-1); + } + + if (idma_is_set(IDMAC_CHA_PRI, dma_chan)) + _ipu_ch_param_set_high_priority(dma_chan); + + _ipu_ch_param_dump(dma_chan); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPU_CHA_DB_MODE_SEL(dma_chan)); + if (phyaddr_1) + reg |= idma_mask(dma_chan); + else + reg &= ~idma_mask(dma_chan); + __raw_writel(reg, IPU_CHA_DB_MODE_SEL(dma_chan)); + + /* Reset to buffer 0 */ + __raw_writel(idma_mask(dma_chan), IPU_CHA_CUR_BUF(dma_chan)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_init_channel_buffer); + +/*! + * This function is called to update the physical address of a buffer for + * a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param bufNum Input parameter for buffer number to update. + * 0 or 1 are the only valid values. + * + * @param phyaddr Input parameter buffer physical address. + * + * @return This function returns 0 on success or negative error code on + * fail. This function will fail if the buffer is set to ready. + */ +int32_t ipu_update_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum, dma_addr_t phyaddr) +{ + uint32_t reg; + int ret = 0; + unsigned long lock_flags; + uint32_t dma_chan = channel_2_dma(channel, type); + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (bufNum == 0) + reg = __raw_readl(IPU_CHA_BUF0_RDY(dma_chan)); + else + reg = __raw_readl(IPU_CHA_BUF1_RDY(dma_chan)); + + if ((reg & idma_mask(dma_chan)) == 0) + _ipu_ch_param_set_buffer(dma_chan, bufNum, phyaddr); + else + ret = -EACCES; + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return ret; +} +EXPORT_SYMBOL(ipu_update_channel_buffer); + +/*! + * This function is called to set a channel's buffer as ready. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param bufNum Input parameter for which buffer number set to + * ready state. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_select_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum) +{ + uint32_t dma_chan = channel_2_dma(channel, type); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + if (bufNum == 0) { + /*Mark buffer 0 as ready. */ + __raw_writel(idma_mask(dma_chan), IPU_CHA_BUF0_RDY(dma_chan)); + } else { + /*Mark buffer 1 as ready. */ + __raw_writel(idma_mask(dma_chan), IPU_CHA_BUF1_RDY(dma_chan)); + } + if (channel == MEM_VDI_PRP_VF_MEM) + _ipu_vdi_toggle_top_field_man(); + return 0; +} +EXPORT_SYMBOL(ipu_select_buffer); + +/*! + * This function is called to set a channel's buffer as ready. + * + * @param bufNum Input parameter for which buffer number set to + * ready state. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_select_multi_vdi_buffer(uint32_t bufNum) +{ + + uint32_t dma_chan = channel_2_dma(MEM_VDI_PRP_VF_MEM, IPU_INPUT_BUFFER); + uint32_t mask_bit = + idma_mask(channel_2_dma(MEM_VDI_PRP_VF_MEM_P, IPU_INPUT_BUFFER))| + idma_mask(dma_chan)| + idma_mask(channel_2_dma(MEM_VDI_PRP_VF_MEM_N, IPU_INPUT_BUFFER)); + + if (bufNum == 0) { + /*Mark buffer 0 as ready. */ + __raw_writel(mask_bit, IPU_CHA_BUF0_RDY(dma_chan)); + } else { + /*Mark buffer 1 as ready. */ + __raw_writel(mask_bit, IPU_CHA_BUF1_RDY(dma_chan)); + } + _ipu_vdi_toggle_top_field_man(); + return 0; +} +EXPORT_SYMBOL(ipu_select_multi_vdi_buffer); + +#define NA -1 +static int proc_dest_sel[] = + { 0, 1, 1, 3, 5, 5, 4, 7, 8, 9, 10, 11, 12, 14, 15, 16, + 0, 1, 1, 5, 5, 5, 5, 5, 7, 8, 9, 10, 11, 12, 14, 31 }; +static int proc_src_sel[] = { 0, 6, 7, 6, 7, 8, 5, NA, NA, NA, + NA, NA, NA, NA, NA, 1, 2, 3, 4, 7, 8, NA, NA, NA }; +static int disp_src_sel[] = { 0, 6, 7, 8, 3, 4, 5, NA, NA, NA, + NA, NA, NA, NA, NA, 1, NA, 2, NA, 3, 4, 4, 4, 4 }; + + +/*! + * This function links 2 channels together for automatic frame + * synchronization. The output of the source channel is linked to the input of + * the destination channel. + * + * @param src_ch Input parameter for the logical channel ID of + * the source channel. + * + * @param dest_ch Input parameter for the logical channel ID of + * the destination channel. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_link_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch) +{ + int retval = 0; + unsigned long lock_flags; + uint32_t fs_proc_flow1; + uint32_t fs_proc_flow2; + uint32_t fs_proc_flow3; + uint32_t fs_disp_flow1; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + fs_proc_flow1 = __raw_readl(IPU_FS_PROC_FLOW1); + fs_proc_flow2 = __raw_readl(IPU_FS_PROC_FLOW2); + fs_proc_flow3 = __raw_readl(IPU_FS_PROC_FLOW3); + fs_disp_flow1 = __raw_readl(IPU_FS_DISP_FLOW1); + + switch (src_ch) { + case CSI_MEM0: + fs_proc_flow3 &= ~FS_SMFC0_DEST_SEL_MASK; + fs_proc_flow3 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_SMFC0_DEST_SEL_OFFSET; + break; + case CSI_MEM1: + fs_proc_flow3 &= ~FS_SMFC1_DEST_SEL_MASK; + fs_proc_flow3 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_SMFC1_DEST_SEL_OFFSET; + break; + case CSI_MEM2: + fs_proc_flow3 &= ~FS_SMFC2_DEST_SEL_MASK; + fs_proc_flow3 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_SMFC2_DEST_SEL_OFFSET; + break; + case CSI_MEM3: + fs_proc_flow3 &= ~FS_SMFC3_DEST_SEL_MASK; + fs_proc_flow3 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_SMFC3_DEST_SEL_OFFSET; + break; + case CSI_PRP_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPENC_DEST_SEL_OFFSET; + break; + case CSI_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPVF_DEST_SEL_OFFSET; + break; + case MEM_PP_MEM: + fs_proc_flow2 &= ~FS_PP_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PP_DEST_SEL_OFFSET; + break; + case MEM_ROT_PP_MEM: + fs_proc_flow2 &= ~FS_PP_ROT_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PP_ROT_DEST_SEL_OFFSET; + break; + case MEM_PRP_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPENC_DEST_SEL_OFFSET; + break; + case MEM_ROT_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_ROT_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPENC_ROT_DEST_SEL_OFFSET; + break; + case MEM_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPVF_DEST_SEL_OFFSET; + break; + case MEM_VDI_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPVF_DEST_SEL_OFFSET; + break; + case MEM_ROT_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_ROT_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPVF_ROT_DEST_SEL_OFFSET; + break; + default: + retval = -EINVAL; + goto err; + } + + switch (dest_ch) { + case MEM_PP_MEM: + fs_proc_flow1 &= ~FS_PP_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << FS_PP_SRC_SEL_OFFSET; + break; + case MEM_ROT_PP_MEM: + fs_proc_flow1 &= ~FS_PP_ROT_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << + FS_PP_ROT_SRC_SEL_OFFSET; + break; + case MEM_PRP_ENC_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << FS_PRP_SRC_SEL_OFFSET; + break; + case MEM_ROT_ENC_MEM: + fs_proc_flow1 &= ~FS_PRPENC_ROT_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << + FS_PRPENC_ROT_SRC_SEL_OFFSET; + break; + case MEM_PRP_VF_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << FS_PRP_SRC_SEL_OFFSET; + break; + case MEM_VDI_PRP_VF_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << FS_PRP_SRC_SEL_OFFSET; + break; + case MEM_ROT_VF_MEM: + fs_proc_flow1 &= ~FS_PRPVF_ROT_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << + FS_PRPVF_ROT_SRC_SEL_OFFSET; + break; + case MEM_DC_SYNC: + fs_disp_flow1 &= ~FS_DC1_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << FS_DC1_SRC_SEL_OFFSET; + break; + case MEM_BG_SYNC: + fs_disp_flow1 &= ~FS_DP_SYNC0_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << + FS_DP_SYNC0_SRC_SEL_OFFSET; + break; + case MEM_FG_SYNC: + fs_disp_flow1 &= ~FS_DP_SYNC1_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << + FS_DP_SYNC1_SRC_SEL_OFFSET; + break; + case MEM_DC_ASYNC: + fs_disp_flow1 &= ~FS_DC2_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << FS_DC2_SRC_SEL_OFFSET; + break; + case MEM_BG_ASYNC0: + fs_disp_flow1 &= ~FS_DP_ASYNC0_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << + FS_DP_ASYNC0_SRC_SEL_OFFSET; + break; + case MEM_FG_ASYNC0: + fs_disp_flow1 &= ~FS_DP_ASYNC1_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << + FS_DP_ASYNC1_SRC_SEL_OFFSET; + break; + default: + retval = -EINVAL; + goto err; + } + + __raw_writel(fs_proc_flow1, IPU_FS_PROC_FLOW1); + __raw_writel(fs_proc_flow2, IPU_FS_PROC_FLOW2); + __raw_writel(fs_proc_flow3, IPU_FS_PROC_FLOW3); + __raw_writel(fs_disp_flow1, IPU_FS_DISP_FLOW1); + +err: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return retval; +} +EXPORT_SYMBOL(ipu_link_channels); + +/*! + * This function unlinks 2 channels and disables automatic frame + * synchronization. + * + * @param src_ch Input parameter for the logical channel ID of + * the source channel. + * + * @param dest_ch Input parameter for the logical channel ID of + * the destination channel. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_unlink_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch) +{ + int retval = 0; + unsigned long lock_flags; + uint32_t fs_proc_flow1; + uint32_t fs_proc_flow2; + uint32_t fs_proc_flow3; + uint32_t fs_disp_flow1; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + fs_proc_flow1 = __raw_readl(IPU_FS_PROC_FLOW1); + fs_proc_flow2 = __raw_readl(IPU_FS_PROC_FLOW2); + fs_proc_flow3 = __raw_readl(IPU_FS_PROC_FLOW3); + fs_disp_flow1 = __raw_readl(IPU_FS_DISP_FLOW1); + + switch (src_ch) { + case CSI_MEM0: + fs_proc_flow3 &= ~FS_SMFC0_DEST_SEL_MASK; + break; + case CSI_MEM1: + fs_proc_flow3 &= ~FS_SMFC1_DEST_SEL_MASK; + break; + case CSI_MEM2: + fs_proc_flow3 &= ~FS_SMFC2_DEST_SEL_MASK; + break; + case CSI_MEM3: + fs_proc_flow3 &= ~FS_SMFC3_DEST_SEL_MASK; + break; + case CSI_PRP_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_DEST_SEL_MASK; + break; + case CSI_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + break; + case MEM_PP_MEM: + fs_proc_flow2 &= ~FS_PP_DEST_SEL_MASK; + break; + case MEM_ROT_PP_MEM: + fs_proc_flow2 &= ~FS_PP_ROT_DEST_SEL_MASK; + break; + case MEM_PRP_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_DEST_SEL_MASK; + break; + case MEM_ROT_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_ROT_DEST_SEL_MASK; + break; + case MEM_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + break; + case MEM_VDI_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + break; + case MEM_ROT_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_ROT_DEST_SEL_MASK; + break; + default: + retval = -EINVAL; + goto err; + } + + switch (dest_ch) { + case MEM_PP_MEM: + fs_proc_flow1 &= ~FS_PP_SRC_SEL_MASK; + break; + case MEM_ROT_PP_MEM: + fs_proc_flow1 &= ~FS_PP_ROT_SRC_SEL_MASK; + break; + case MEM_PRP_ENC_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + break; + case MEM_ROT_ENC_MEM: + fs_proc_flow1 &= ~FS_PRPENC_ROT_SRC_SEL_MASK; + break; + case MEM_PRP_VF_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + break; + case MEM_VDI_PRP_VF_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + break; + case MEM_ROT_VF_MEM: + fs_proc_flow1 &= ~FS_PRPVF_ROT_SRC_SEL_MASK; + break; + case MEM_DC_SYNC: + fs_disp_flow1 &= ~FS_DC1_SRC_SEL_MASK; + break; + case MEM_BG_SYNC: + fs_disp_flow1 &= ~FS_DP_SYNC0_SRC_SEL_MASK; + break; + case MEM_FG_SYNC: + fs_disp_flow1 &= ~FS_DP_SYNC1_SRC_SEL_MASK; + break; + case MEM_DC_ASYNC: + fs_disp_flow1 &= ~FS_DC2_SRC_SEL_MASK; + break; + case MEM_BG_ASYNC0: + fs_disp_flow1 &= ~FS_DP_ASYNC0_SRC_SEL_MASK; + break; + case MEM_FG_ASYNC0: + fs_disp_flow1 &= ~FS_DP_ASYNC1_SRC_SEL_MASK; + break; + default: + retval = -EINVAL; + goto err; + } + + __raw_writel(fs_proc_flow1, IPU_FS_PROC_FLOW1); + __raw_writel(fs_proc_flow2, IPU_FS_PROC_FLOW2); + __raw_writel(fs_proc_flow3, IPU_FS_PROC_FLOW3); + __raw_writel(fs_disp_flow1, IPU_FS_DISP_FLOW1); + +err: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return retval; +} +EXPORT_SYMBOL(ipu_unlink_channels); + +/*! + * This function check whether a logical channel was enabled. + * + * @param channel Input parameter for the logical channel ID. + * + * @return This function returns 1 while request channel is enabled or + * 0 for not enabled. + */ +int32_t ipu_is_channel_busy(ipu_channel_t channel) +{ + uint32_t reg; + uint32_t in_dma; + uint32_t out_dma; + + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + in_dma = channel_2_dma(channel, IPU_VIDEO_IN_BUFFER); + + reg = __raw_readl(IDMAC_CHA_EN(in_dma)); + if (reg & idma_mask(in_dma)) + return 1; + reg = __raw_readl(IDMAC_CHA_EN(out_dma)); + if (reg & idma_mask(out_dma)) + return 1; + return 0; +} +EXPORT_SYMBOL(ipu_is_channel_busy); + +/*! + * This function enables a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_enable_channel(ipu_channel_t channel) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t in_dma; + uint32_t out_dma; + uint32_t sec_dma; + uint32_t thrd_dma; + + if (g_channel_enable_mask & (1L << IPU_CHAN_ID(channel))) { + dev_err(g_ipu_dev, "Warning: channel already enabled %d\n", + IPU_CHAN_ID(channel)); + } + + /* Get input and output dma channels */ + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + in_dma = channel_2_dma(channel, IPU_VIDEO_IN_BUFFER); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (idma_is_valid(in_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(in_dma)); + __raw_writel(reg | idma_mask(in_dma), IDMAC_CHA_EN(in_dma)); + } + if (idma_is_valid(out_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(out_dma)); + __raw_writel(reg | idma_mask(out_dma), IDMAC_CHA_EN(out_dma)); + } + + if ((g_sec_chan_en[IPU_CHAN_ID(channel)]) && + ((channel == MEM_PP_MEM) || (channel == MEM_PRP_VF_MEM) || + (channel == MEM_VDI_PRP_VF_MEM))) { + sec_dma = channel_2_dma(channel, IPU_GRAPH_IN_BUFFER); + reg = __raw_readl(IDMAC_CHA_EN(sec_dma)); + __raw_writel(reg | idma_mask(sec_dma), IDMAC_CHA_EN(sec_dma)); + } + if ((g_thrd_chan_en[IPU_CHAN_ID(channel)]) && + ((channel == MEM_PP_MEM) || (channel == MEM_PRP_VF_MEM))) { + thrd_dma = channel_2_dma(channel, IPU_ALPHA_IN_BUFFER); + reg = __raw_readl(IDMAC_CHA_EN(thrd_dma)); + __raw_writel(reg | idma_mask(thrd_dma), IDMAC_CHA_EN(thrd_dma)); + + sec_dma = channel_2_dma(channel, IPU_GRAPH_IN_BUFFER); + reg = __raw_readl(IDMAC_SEP_ALPHA); + __raw_writel(reg | idma_mask(sec_dma), IDMAC_SEP_ALPHA); + } else if ((g_thrd_chan_en[IPU_CHAN_ID(channel)]) && + ((channel == MEM_BG_SYNC) || (channel == MEM_FG_SYNC))) { + thrd_dma = channel_2_dma(channel, IPU_ALPHA_IN_BUFFER); + reg = __raw_readl(IDMAC_CHA_EN(thrd_dma)); + __raw_writel(reg | idma_mask(thrd_dma), IDMAC_CHA_EN(thrd_dma)); + reg = __raw_readl(IDMAC_SEP_ALPHA); + __raw_writel(reg | idma_mask(in_dma), IDMAC_SEP_ALPHA); + } + + if ((channel == MEM_DC_SYNC) || (channel == MEM_BG_SYNC) || + (channel == MEM_FG_SYNC)) + _ipu_dp_dc_enable(channel); + + if (_ipu_is_ic_chan(in_dma) || _ipu_is_ic_chan(out_dma) || + _ipu_is_irt_chan(in_dma) || _ipu_is_irt_chan(out_dma)) + _ipu_ic_enable_task(channel); + + g_channel_enable_mask |= 1L << IPU_CHAN_ID(channel); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_enable_channel); + +static irqreturn_t disable_chan_irq_handler(int irq, void *dev_id) +{ + struct completion *comp = dev_id; + + complete(comp); + return IRQ_HANDLED; +} + +/*! + * This function disables a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param wait_for_stop Flag to set whether to wait for channel end + * of frame or return immediately. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_disable_channel(ipu_channel_t channel, bool wait_for_stop) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t in_dma; + uint32_t out_dma; + uint32_t sec_dma = NO_DMA; + uint32_t thrd_dma = NO_DMA; + + if ((g_channel_enable_mask & (1L << IPU_CHAN_ID(channel))) == 0) { + dev_err(g_ipu_dev, "Channel already disabled %d\n", + IPU_CHAN_ID(channel)); + return 0; + } + + /* Get input and output dma channels */ + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + in_dma = channel_2_dma(channel, IPU_VIDEO_IN_BUFFER); + + if ((idma_is_valid(in_dma) && + !idma_is_set(IDMAC_CHA_EN, in_dma)) + && (idma_is_valid(out_dma) && + !idma_is_set(IDMAC_CHA_EN, out_dma))) + return -EINVAL; + + if (g_sec_chan_en[IPU_CHAN_ID(channel)]) + sec_dma = channel_2_dma(channel, IPU_GRAPH_IN_BUFFER); + if (g_thrd_chan_en[IPU_CHAN_ID(channel)]) { + sec_dma = channel_2_dma(channel, IPU_GRAPH_IN_BUFFER); + thrd_dma = channel_2_dma(channel, IPU_ALPHA_IN_BUFFER); + } + + if ((channel == MEM_BG_SYNC) || (channel == MEM_FG_SYNC) || + (channel == MEM_DC_SYNC)) { + _ipu_dp_dc_disable(channel, false); + } else if (wait_for_stop) { + if (idma_is_set(IDMAC_CHA_BUSY, in_dma) || + idma_is_set(IDMAC_CHA_BUSY, out_dma) || + (g_sec_chan_en[IPU_CHAN_ID(channel)] && + idma_is_set(IDMAC_CHA_BUSY, sec_dma)) || + (g_thrd_chan_en[IPU_CHAN_ID(channel)] && + idma_is_set(IDMAC_CHA_BUSY, thrd_dma)) || + (_ipu_channel_status(channel) == TASK_STAT_ACTIVE)) { + uint32_t ret, irq = out_dma; + DECLARE_COMPLETION_ONSTACK(disable_comp); + + ret = ipu_request_irq(irq, disable_chan_irq_handler, 0, NULL, &disable_comp); + if (ret < 0) { + dev_err(g_ipu_dev, "irq %d in use\n", irq); + } else { + ret = wait_for_completion_timeout(&disable_comp, msecs_to_jiffies(50)); + ipu_free_irq(irq, &disable_comp); + if (ret == msecs_to_jiffies(50)) + ipu_dump_registers(); + } + } + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + /* Disable IC task */ + if (_ipu_is_ic_chan(in_dma) || _ipu_is_ic_chan(out_dma) || + _ipu_is_irt_chan(in_dma) || _ipu_is_irt_chan(out_dma)) + _ipu_ic_disable_task(channel); + + /* Disable DMA channel(s) */ + if (idma_is_valid(in_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(in_dma)); + __raw_writel(reg & ~idma_mask(in_dma), IDMAC_CHA_EN(in_dma)); + __raw_writel(idma_mask(in_dma), IPU_CHA_CUR_BUF(in_dma)); + } + if (idma_is_valid(out_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(out_dma)); + __raw_writel(reg & ~idma_mask(out_dma), IDMAC_CHA_EN(out_dma)); + __raw_writel(idma_mask(out_dma), IPU_CHA_CUR_BUF(out_dma)); + } + if (g_sec_chan_en[IPU_CHAN_ID(channel)] && idma_is_valid(sec_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(sec_dma)); + __raw_writel(reg & ~idma_mask(sec_dma), IDMAC_CHA_EN(sec_dma)); + __raw_writel(idma_mask(sec_dma), IPU_CHA_CUR_BUF(sec_dma)); + } + if (g_thrd_chan_en[IPU_CHAN_ID(channel)] && idma_is_valid(thrd_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(thrd_dma)); + __raw_writel(reg & ~idma_mask(thrd_dma), IDMAC_CHA_EN(thrd_dma)); + if (channel == MEM_BG_SYNC || channel == MEM_FG_SYNC) { + reg = __raw_readl(IDMAC_SEP_ALPHA); + __raw_writel(reg & ~idma_mask(in_dma), IDMAC_SEP_ALPHA); + } else { + reg = __raw_readl(IDMAC_SEP_ALPHA); + __raw_writel(reg & ~idma_mask(sec_dma), IDMAC_SEP_ALPHA); + } + __raw_writel(idma_mask(thrd_dma), IPU_CHA_CUR_BUF(thrd_dma)); + } + + /* Set channel buffers NOT to be ready */ + __raw_writel(0xF0000000, IPU_GPR); /* write one to clear */ + if (idma_is_valid(in_dma)) { + if (idma_is_set(IPU_CHA_BUF0_RDY, in_dma)) { + __raw_writel(idma_mask(in_dma), + IPU_CHA_BUF0_RDY(in_dma)); + } + if (idma_is_set(IPU_CHA_BUF1_RDY, in_dma)) { + __raw_writel(idma_mask(in_dma), + IPU_CHA_BUF1_RDY(in_dma)); + } + } + if (idma_is_valid(out_dma)) { + if (idma_is_set(IPU_CHA_BUF0_RDY, out_dma)) { + __raw_writel(idma_mask(out_dma), + IPU_CHA_BUF0_RDY(out_dma)); + } + if (idma_is_set(IPU_CHA_BUF1_RDY, out_dma)) { + __raw_writel(idma_mask(out_dma), + IPU_CHA_BUF1_RDY(out_dma)); + } + } + if (g_sec_chan_en[IPU_CHAN_ID(channel)] && idma_is_valid(sec_dma)) { + if (idma_is_set(IPU_CHA_BUF0_RDY, sec_dma)) { + __raw_writel(idma_mask(sec_dma), + IPU_CHA_BUF0_RDY(sec_dma)); + } + if (idma_is_set(IPU_CHA_BUF1_RDY, sec_dma)) { + __raw_writel(idma_mask(sec_dma), + IPU_CHA_BUF1_RDY(sec_dma)); + } + } + if (g_thrd_chan_en[IPU_CHAN_ID(channel)] && idma_is_valid(thrd_dma)) { + if (idma_is_set(IPU_CHA_BUF0_RDY, thrd_dma)) { + __raw_writel(idma_mask(thrd_dma), + IPU_CHA_BUF0_RDY(thrd_dma)); + } + if (idma_is_set(IPU_CHA_BUF1_RDY, thrd_dma)) { + __raw_writel(idma_mask(thrd_dma), + IPU_CHA_BUF1_RDY(thrd_dma)); + } + } + __raw_writel(0x0, IPU_GPR); /* write one to set */ + + g_channel_enable_mask &= ~(1L << IPU_CHAN_ID(channel)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_disable_channel); + +static irqreturn_t ipu_irq_handler(int irq, void *desc) +{ + int i; + uint32_t line; + irqreturn_t result = IRQ_NONE; + uint32_t int_stat; + const int err_reg[] = { 5, 6, 9, 10, 0 }; + const int int_reg[] = { 1, 2, 3, 4, 11, 12, 13, 14, 15, 0 }; + + for (i = 0;; i++) { + if (err_reg[i] == 0) + break; + int_stat = __raw_readl(IPU_INT_STAT(err_reg[i])); + int_stat &= __raw_readl(IPU_INT_CTRL(err_reg[i])); + if (int_stat) { + __raw_writel(int_stat, IPU_INT_STAT(err_reg[i])); + dev_err(g_ipu_dev, + "IPU Error - IPU_INT_STAT_%d = 0x%08X\n", + err_reg[i], int_stat); + /* Disable interrupts so we only get error once */ + int_stat = + __raw_readl(IPU_INT_CTRL(err_reg[i])) & ~int_stat; + __raw_writel(int_stat, IPU_INT_CTRL(err_reg[i])); + } + } + + for (i = 0;; i++) { + if (int_reg[i] == 0) + break; + int_stat = __raw_readl(IPU_INT_STAT(int_reg[i])); + int_stat &= __raw_readl(IPU_INT_CTRL(int_reg[i])); + __raw_writel(int_stat, IPU_INT_STAT(int_reg[i])); + while ((line = ffs(int_stat)) != 0) { + line--; + int_stat &= ~(1UL << line); + line += (int_reg[i] - 1) * 32; + result |= + ipu_irq_list[line].handler(line, + ipu_irq_list[line]. + dev_id); + } + } + + return result; +} + +/*! + * This function enables the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to enable interrupt for. + * + */ +void ipu_enable_irq(uint32_t irq) +{ + uint32_t reg; + unsigned long lock_flags; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPUIRQ_2_CTRLREG(irq)); + reg |= IPUIRQ_2_MASK(irq); + __raw_writel(reg, IPUIRQ_2_CTRLREG(irq)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); +} +EXPORT_SYMBOL(ipu_enable_irq); + +/*! + * This function disables the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to disable interrupt for. + * + */ +void ipu_disable_irq(uint32_t irq) +{ + uint32_t reg; + unsigned long lock_flags; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPUIRQ_2_CTRLREG(irq)); + reg &= ~IPUIRQ_2_MASK(irq); + __raw_writel(reg, IPUIRQ_2_CTRLREG(irq)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); +} +EXPORT_SYMBOL(ipu_disable_irq); + +/*! + * This function clears the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to clear interrupt for. + * + */ +void ipu_clear_irq(uint32_t irq) +{ + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + __raw_writel(IPUIRQ_2_MASK(irq), IPUIRQ_2_STATREG(irq)); + + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); +} +EXPORT_SYMBOL(ipu_clear_irq); + +/*! + * This function returns the current interrupt status for the specified + * interrupt line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @return Returns true if the interrupt is pending/asserted or false if + * the interrupt is not pending. + */ +bool ipu_get_irq_status(uint32_t irq) +{ + uint32_t reg; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + reg = __raw_readl(IPUIRQ_2_STATREG(irq)); + + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); + + if (reg & IPUIRQ_2_MASK(irq)) + return true; + else + return false; +} +EXPORT_SYMBOL(ipu_get_irq_status); + +/*! + * This function registers an interrupt handler function for the specified + * interrupt line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @param handler Input parameter for address of the handler + * function. + * + * @param irq_flags Flags for interrupt mode. Currently not used. + * + * @param devname Input parameter for string name of driver + * registering the handler. + * + * @param dev_id Input parameter for pointer of data to be + * passed to the handler. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int ipu_request_irq(uint32_t irq, + irqreturn_t(*handler) (int, void *), + uint32_t irq_flags, const char *devname, void *dev_id) +{ + unsigned long lock_flags; + + BUG_ON(irq >= IPU_IRQ_COUNT); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (ipu_irq_list[irq].handler != NULL) { + dev_err(g_ipu_dev, + "handler already installed on irq %d\n", irq); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + + ipu_irq_list[irq].handler = handler; + ipu_irq_list[irq].flags = irq_flags; + ipu_irq_list[irq].dev_id = dev_id; + ipu_irq_list[irq].name = devname; + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + ipu_enable_irq(irq); /* enable the interrupt */ + + return 0; +} +EXPORT_SYMBOL(ipu_request_irq); + +/*! + * This function unregisters an interrupt handler for the specified interrupt + * line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @param dev_id Input parameter for pointer of data to be passed + * to the handler. This must match value passed to + * ipu_request_irq(). + * + */ +void ipu_free_irq(uint32_t irq, void *dev_id) +{ + ipu_disable_irq(irq); /* disable the interrupt */ + + if (ipu_irq_list[irq].dev_id == dev_id) + ipu_irq_list[irq].handler = NULL; +} +EXPORT_SYMBOL(ipu_free_irq); + +uint32_t _ipu_channel_status(ipu_channel_t channel) +{ + uint32_t stat = 0; + uint32_t task_stat_reg = __raw_readl(IPU_PROC_TASK_STAT); + + switch (channel) { + case MEM_PRP_VF_MEM: + stat = (task_stat_reg & TSTAT_VF_MASK) >> TSTAT_VF_OFFSET; + break; + case MEM_VDI_PRP_VF_MEM: + stat = (task_stat_reg & TSTAT_VF_MASK) >> TSTAT_VF_OFFSET; + break; + case MEM_ROT_VF_MEM: + stat = + (task_stat_reg & TSTAT_VF_ROT_MASK) >> TSTAT_VF_ROT_OFFSET; + break; + case MEM_PRP_ENC_MEM: + stat = (task_stat_reg & TSTAT_ENC_MASK) >> TSTAT_ENC_OFFSET; + break; + case MEM_ROT_ENC_MEM: + stat = + (task_stat_reg & TSTAT_ENC_ROT_MASK) >> + TSTAT_ENC_ROT_OFFSET; + break; + case MEM_PP_MEM: + stat = (task_stat_reg & TSTAT_PP_MASK) >> TSTAT_PP_OFFSET; + break; + case MEM_ROT_PP_MEM: + stat = + (task_stat_reg & TSTAT_PP_ROT_MASK) >> TSTAT_PP_ROT_OFFSET; + break; + + default: + stat = TASK_STAT_IDLE; + break; + } + return stat; +} + +int32_t ipu_swap_channel(ipu_channel_t from_ch, ipu_channel_t to_ch) +{ + uint32_t reg; + unsigned long lock_flags; + + int from_dma = channel_2_dma(from_ch, IPU_INPUT_BUFFER); + int to_dma = channel_2_dma(to_ch, IPU_INPUT_BUFFER); + + /* enable target channel */ + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IDMAC_CHA_EN(to_dma)); + __raw_writel(reg | idma_mask(to_dma), IDMAC_CHA_EN(to_dma)); + + g_channel_enable_mask |= 1L << IPU_CHAN_ID(to_ch); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + /* switch dp dc */ + _ipu_dp_dc_disable(from_ch, true); + + /* disable source channel */ + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IDMAC_CHA_EN(from_dma)); + __raw_writel(reg & ~idma_mask(from_dma), IDMAC_CHA_EN(from_dma)); + __raw_writel(idma_mask(from_dma), IPU_CHA_CUR_BUF(from_dma)); + + g_channel_enable_mask &= ~(1L << IPU_CHAN_ID(from_ch)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_swap_channel); + +uint32_t bytes_per_pixel(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_GENERIC: /*generic data */ + case IPU_PIX_FMT_RGB332: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YUV422P: + return 1; + break; + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_YUYV: + case IPU_PIX_FMT_UYVY: + return 2; + break; + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + return 3; + break; + case IPU_PIX_FMT_GENERIC_32: /*generic data */ + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_RGB32: + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_ABGR32: + return 4; + break; + default: + return 1; + break; + } + return 0; +} +EXPORT_SYMBOL(bytes_per_pixel); + +ipu_color_space_t format_to_colorspace(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_RGB666: + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_RGB32: + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_ABGR32: + case IPU_PIX_FMT_LVDS666: + case IPU_PIX_FMT_LVDS888: + return RGB; + break; + + default: + return YCbCr; + break; + } + return RGB; +} + +bool ipu_pixel_format_has_alpha(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_ABGR32: + return true; + break; + default: + return false; + break; + } + return false; +} + +void ipu_set_csc_coefficients(ipu_channel_t channel, int32_t param[][3]) +{ + _ipu_dp_set_csc_coefficients(channel, param); +} +EXPORT_SYMBOL(ipu_set_csc_coefficients); + +static int ipu_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (g_ipu_clk_enabled) { + /* save and disable enabled channels*/ + idma_enable_reg[0] = __raw_readl(IDMAC_CHA_EN(0)); + idma_enable_reg[1] = __raw_readl(IDMAC_CHA_EN(32)); + while ((__raw_readl(IDMAC_CHA_BUSY(0)) & idma_enable_reg[0]) + || (__raw_readl(IDMAC_CHA_BUSY(32)) & + idma_enable_reg[1])) { + /* disable channel not busy already */ + uint32_t chan_should_disable, timeout = 1000, time = 0; + + chan_should_disable = + __raw_readl(IDMAC_CHA_BUSY(0)) + ^ idma_enable_reg[0]; + __raw_writel((~chan_should_disable) & + idma_enable_reg[0], IDMAC_CHA_EN(0)); + chan_should_disable = + __raw_readl(IDMAC_CHA_BUSY(1)) + ^ idma_enable_reg[1]; + __raw_writel((~chan_should_disable) & + idma_enable_reg[1], IDMAC_CHA_EN(32)); + msleep(2); + time += 2; + if (time >= timeout) + return -1; + } + __raw_writel(0, IDMAC_CHA_EN(0)); + __raw_writel(0, IDMAC_CHA_EN(32)); + + /* save double buffer select regs */ + ipu_cha_db_mode_reg[0] = __raw_readl(IPU_CHA_DB_MODE_SEL(0)); + ipu_cha_db_mode_reg[1] = __raw_readl(IPU_CHA_DB_MODE_SEL(32)); + ipu_cha_db_mode_reg[2] = + __raw_readl(IPU_ALT_CHA_DB_MODE_SEL(0)); + ipu_cha_db_mode_reg[3] = + __raw_readl(IPU_ALT_CHA_DB_MODE_SEL(32)); + + /* save current buffer regs */ + ipu_cha_cur_buf_reg[0] = __raw_readl(IPU_CHA_CUR_BUF(0)); + ipu_cha_cur_buf_reg[1] = __raw_readl(IPU_CHA_CUR_BUF(32)); + ipu_cha_cur_buf_reg[2] = __raw_readl(IPU_ALT_CUR_BUF0); + ipu_cha_cur_buf_reg[3] = __raw_readl(IPU_ALT_CUR_BUF1); + + /* save sub-modules status and disable all */ + ic_conf_reg = __raw_readl(IC_CONF); + __raw_writel(0, IC_CONF); + ipu_conf_reg = __raw_readl(IPU_CONF); + __raw_writel(0, IPU_CONF); + + /* save buf ready regs */ + buf_ready_reg[0] = __raw_readl(IPU_CHA_BUF0_RDY(0)); + buf_ready_reg[1] = __raw_readl(IPU_CHA_BUF0_RDY(32)); + buf_ready_reg[2] = __raw_readl(IPU_CHA_BUF1_RDY(0)); + buf_ready_reg[3] = __raw_readl(IPU_CHA_BUF1_RDY(32)); + buf_ready_reg[4] = __raw_readl(IPU_ALT_CHA_BUF0_RDY(0)); + buf_ready_reg[5] = __raw_readl(IPU_ALT_CHA_BUF0_RDY(32)); + buf_ready_reg[6] = __raw_readl(IPU_ALT_CHA_BUF1_RDY(0)); + buf_ready_reg[7] = __raw_readl(IPU_ALT_CHA_BUF1_RDY(32)); + } + + mxc_pg_enable(pdev); + + return 0; +} + +static int ipu_resume(struct platform_device *pdev) +{ + mxc_pg_disable(pdev); + + if (g_ipu_clk_enabled) { + + /* restore buf ready regs */ + __raw_writel(buf_ready_reg[0], IPU_CHA_BUF0_RDY(0)); + __raw_writel(buf_ready_reg[1], IPU_CHA_BUF0_RDY(32)); + __raw_writel(buf_ready_reg[2], IPU_CHA_BUF1_RDY(0)); + __raw_writel(buf_ready_reg[3], IPU_CHA_BUF1_RDY(32)); + __raw_writel(buf_ready_reg[4], IPU_ALT_CHA_BUF0_RDY(0)); + __raw_writel(buf_ready_reg[5], IPU_ALT_CHA_BUF0_RDY(32)); + __raw_writel(buf_ready_reg[6], IPU_ALT_CHA_BUF1_RDY(0)); + __raw_writel(buf_ready_reg[7], IPU_ALT_CHA_BUF1_RDY(32)); + + /* re-enable sub-modules*/ + __raw_writel(ipu_conf_reg, IPU_CONF); + __raw_writel(ic_conf_reg, IC_CONF); + + /* restore double buffer select regs */ + __raw_writel(ipu_cha_db_mode_reg[0], IPU_CHA_DB_MODE_SEL(0)); + __raw_writel(ipu_cha_db_mode_reg[1], IPU_CHA_DB_MODE_SEL(32)); + __raw_writel(ipu_cha_db_mode_reg[2], + IPU_ALT_CHA_DB_MODE_SEL(0)); + __raw_writel(ipu_cha_db_mode_reg[3], + IPU_ALT_CHA_DB_MODE_SEL(32)); + + /* restore current buffer select regs */ + __raw_writel(~(ipu_cha_cur_buf_reg[0]), IPU_CHA_CUR_BUF(0)); + __raw_writel(~(ipu_cha_cur_buf_reg[1]), IPU_CHA_CUR_BUF(32)); + __raw_writel(~(ipu_cha_cur_buf_reg[2]), IPU_ALT_CUR_BUF0); + __raw_writel(~(ipu_cha_cur_buf_reg[3]), IPU_ALT_CUR_BUF1); + + /* restart idma channel*/ + __raw_writel(idma_enable_reg[0], IDMAC_CHA_EN(0)); + __raw_writel(idma_enable_reg[1], IDMAC_CHA_EN(32)); + } else { + clk_enable(g_ipu_clk); + _ipu_dmfc_init(); + _ipu_init_dc_mappings(); + + /* Set sync refresh channels as high priority */ + __raw_writel(0x18800000L, IDMAC_CHA_PRI(0)); + clk_disable(g_ipu_clk); + } + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcipu_driver = { + .driver = { + .name = "mxc_ipu", + }, + .probe = ipu_probe, + .remove = ipu_remove, + .suspend_late = ipu_suspend, + .resume_early = ipu_resume, +}; + +int32_t __init ipu_gen_init(void) +{ + int32_t ret; + + ret = platform_driver_register(&mxcipu_driver); + return 0; +} + +subsys_initcall(ipu_gen_init); + +static void __exit ipu_gen_uninit(void) +{ + platform_driver_unregister(&mxcipu_driver); +} + +module_exit(ipu_gen_uninit); diff --git a/drivers/mxc/ipu3/ipu_device.c b/drivers/mxc/ipu3/ipu_device.c new file mode 100644 index 000000000000..4c98d4c7edd3 --- /dev/null +++ b/drivers/mxc/ipu3/ipu_device.c @@ -0,0 +1,446 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_device.c + * + * @brief This file contains the IPUv3 driver device interface and fops functions. + * + * @ingroup IPU + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include <asm/cacheflush.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +/* Strucutures and variables for exporting MXC IPU as device*/ + +static int mxc_ipu_major; +static struct class *mxc_ipu_class; + +DEFINE_SPINLOCK(event_lock); + +struct ipu_dev_irq_info { + wait_queue_head_t waitq; + int irq_pending; +} irq_info[480]; + +int register_ipu_device(void); + +/* Static functions */ + +int get_events(ipu_event_info *p) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&event_lock, flags); + if (irq_info[p->irq].irq_pending > 0) + irq_info[p->irq].irq_pending--; + else + ret = -1; + spin_unlock_irqrestore(&event_lock, flags); + + return ret; +} + +static irqreturn_t mxc_ipu_generic_handler(int irq, void *dev_id) +{ + irq_info[irq].irq_pending++; + + /* Wakeup any blocking user context */ + wake_up_interruptible(&(irq_info[irq].waitq)); + return IRQ_HANDLED; +} + +static int mxc_ipu_open(struct inode *inode, struct file *file) +{ + int ret = 0; + return ret; +} +static int mxc_ipu_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + switch (cmd) { + case IPU_INIT_CHANNEL: + { + ipu_channel_parm parm; + + if (copy_from_user + (&parm, (ipu_channel_parm *) arg, + sizeof(ipu_channel_parm))) + return -EFAULT; + + if (!parm.flag) { + ret = + ipu_init_channel(parm.channel, + &parm.params); + } else { + ret = ipu_init_channel(parm.channel, NULL); + } + } + break; + case IPU_UNINIT_CHANNEL: + { + ipu_channel_t ch; + int __user *argp = (void __user *)arg; + if (get_user(ch, argp)) + return -EFAULT; + ipu_uninit_channel(ch); + } + break; + case IPU_INIT_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) + return -EFAULT; + + ret = + ipu_init_channel_buffer( + parm.channel, parm.type, + parm.pixel_fmt, + parm.width, parm.height, + parm.stride, + parm.rot_mode, + parm.phyaddr_0, + parm.phyaddr_1, + parm.u_offset, + parm.v_offset); + + } + break; + case IPU_UPDATE_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) + return -EFAULT; + + if ((parm.phyaddr_0 != (dma_addr_t) NULL) + && (parm.phyaddr_1 == (dma_addr_t) NULL)) { + ret = + ipu_update_channel_buffer( + parm.channel, + parm.type, + parm.bufNum, + parm.phyaddr_0); + } else if ((parm.phyaddr_0 == (dma_addr_t) NULL) + && (parm.phyaddr_1 != (dma_addr_t) NULL)) { + ret = + ipu_update_channel_buffer( + parm.channel, + parm.type, + parm.bufNum, + parm.phyaddr_1); + } else { + ret = -1; + } + + } + break; + case IPU_SELECT_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) + return -EFAULT; + + ret = + ipu_select_buffer(parm.channel, + parm.type, parm.bufNum); + + } + break; + case IPU_LINK_CHANNELS: + { + ipu_channel_link link; + if (copy_from_user + (&link, (ipu_channel_link *) arg, + sizeof(ipu_channel_link))) + return -EFAULT; + + ret = ipu_link_channels(link.src_ch, + link.dest_ch); + + } + break; + case IPU_UNLINK_CHANNELS: + { + ipu_channel_link link; + if (copy_from_user + (&link, (ipu_channel_link *) arg, + sizeof(ipu_channel_link))) + return -EFAULT; + + ret = ipu_unlink_channels(link.src_ch, + link.dest_ch); + + } + break; + case IPU_ENABLE_CHANNEL: + { + ipu_channel_t ch; + int __user *argp = (void __user *)arg; + if (get_user(ch, argp)) + return -EFAULT; + ipu_enable_channel(ch); + } + break; + case IPU_DISABLE_CHANNEL: + { + ipu_channel_info info; + if (copy_from_user + (&info, (ipu_channel_info *) arg, + sizeof(ipu_channel_info))) + return -EFAULT; + + ret = ipu_disable_channel(info.channel, + info.stop); + } + break; + case IPU_ENABLE_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_enable_irq(irq); + } + break; + case IPU_DISABLE_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_disable_irq(irq); + } + break; + case IPU_CLEAR_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_clear_irq(irq); + } + break; + case IPU_FREE_IRQ: + { + ipu_irq_info info; + + if (copy_from_user + (&info, (ipu_irq_info *) arg, + sizeof(ipu_irq_info))) + return -EFAULT; + + ipu_free_irq(info.irq, info.dev_id); + irq_info[info.irq].irq_pending = 0; + } + break; + case IPU_REQUEST_IRQ_STATUS: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ret = ipu_get_irq_status(irq); + } + break; + case IPU_REGISTER_GENERIC_ISR: + { + ipu_event_info info; + if (copy_from_user + (&info, (ipu_event_info *) arg, + sizeof(ipu_event_info))) + return -EFAULT; + + ret = + ipu_request_irq(info.irq, + mxc_ipu_generic_handler, + 0, "video_sink", info.dev); + if (ret == 0) + init_waitqueue_head(&(irq_info[info.irq].waitq)); + } + break; + case IPU_GET_EVENT: + /* User will have to allocate event_type + structure and pass the pointer in arg */ + { + ipu_event_info info; + int r = -1; + + if (copy_from_user + (&info, (ipu_event_info *) arg, + sizeof(ipu_event_info))) + return -EFAULT; + + r = get_events(&info); + if (r == -1) { + if ((file->f_flags & O_NONBLOCK) && + (irq_info[info.irq].irq_pending == 0)) + return -EAGAIN; + wait_event_interruptible_timeout(irq_info[info.irq].waitq, + (irq_info[info.irq].irq_pending != 0), 2 * HZ); + r = get_events(&info); + } + ret = -1; + if (r == 0) { + if (!copy_to_user((ipu_event_info *) arg, + &info, sizeof(ipu_event_info))) + ret = 0; + } + } + break; + case IPU_ALOC_MEM: + { + ipu_mem_info info; + if (copy_from_user + (&info, (ipu_mem_info *) arg, + sizeof(ipu_mem_info))) + return -EFAULT; + + info.vaddr = dma_alloc_coherent(0, + PAGE_ALIGN(info.size), + &info.paddr, + GFP_DMA | GFP_KERNEL); + if (info.vaddr == 0) { + printk(KERN_ERR "dma alloc failed!\n"); + return -ENOBUFS; + } + if (copy_to_user((ipu_mem_info *) arg, &info, + sizeof(ipu_mem_info)) > 0) + return -EFAULT; + } + break; + case IPU_FREE_MEM: + { + ipu_mem_info info; + if (copy_from_user + (&info, (ipu_mem_info *) arg, + sizeof(ipu_mem_info))) + return -EFAULT; + + if (info.vaddr) + dma_free_coherent(0, PAGE_ALIGN(info.size), + info.vaddr, info.paddr); + else + return -EFAULT; + } + break; + case IPU_IS_CHAN_BUSY: + { + ipu_channel_t chan; + if (copy_from_user + (&chan, (ipu_channel_t *)arg, + sizeof(ipu_channel_t))) + return -EFAULT; + + if (ipu_is_channel_busy(chan)) + ret = 1; + else + ret = 0; + } + break; + default: + break; + } + return ret; +} + +static int mxc_ipu_mmap(struct file *file, struct vm_area_struct *vma) +{ +// vma->vm_page_prot = pgprot_writethru(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)) { + printk(KERN_ERR + "mmap failed!\n"); + return -ENOBUFS; + } + return 0; +} + +static int mxc_ipu_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static struct file_operations mxc_ipu_fops = { + .owner = THIS_MODULE, + .open = mxc_ipu_open, + .mmap = mxc_ipu_mmap, + .release = mxc_ipu_release, + .ioctl = mxc_ipu_ioctl +}; + +int register_ipu_device() +{ + int ret = 0; + struct device *temp; + mxc_ipu_major = register_chrdev(0, "mxc_ipu", &mxc_ipu_fops); + if (mxc_ipu_major < 0) { + printk(KERN_ERR + "Unable to register Mxc Ipu as a char device\n"); + return mxc_ipu_major; + } + + mxc_ipu_class = class_create(THIS_MODULE, "mxc_ipu"); + if (IS_ERR(mxc_ipu_class)) { + printk(KERN_ERR "Unable to create class for Mxc Ipu\n"); + ret = PTR_ERR(mxc_ipu_class); + goto err1; + } + + temp = device_create(mxc_ipu_class, NULL, MKDEV(mxc_ipu_major, 0), + NULL, "mxc_ipu"); + + if (IS_ERR(temp)) { + printk(KERN_ERR "Unable to create class device for Mxc Ipu\n"); + ret = PTR_ERR(temp); + goto err2; + } + spin_lock_init(&event_lock); + + return ret; + +err2: + class_destroy(mxc_ipu_class); +err1: + unregister_chrdev(mxc_ipu_major, "mxc_ipu"); + return ret; + +} diff --git a/drivers/mxc/ipu3/ipu_disp.c b/drivers/mxc/ipu3/ipu_disp.c new file mode 100644 index 000000000000..79b0c2123c22 --- /dev/null +++ b/drivers/mxc/ipu3/ipu_disp.c @@ -0,0 +1,1515 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_disp.c + * + * @brief IPU display submodule API functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include <asm/atomic.h> +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +enum csc_type_t { + RGB2YUV = 0, + YUV2RGB, + RGB2RGB, + YUV2YUV, + CSC_NONE, + CSC_NUM +}; + +struct dp_csc_param_t { + int mode; + void *coeff; +}; + +#define SYNC_WAVE 0 +#define ASYNC_SER_WAVE 6 + +/* DC display ID assignments */ +#define DC_DISP_ID_SYNC(di) (di) +#define DC_DISP_ID_SERIAL 2 +#define DC_DISP_ID_ASYNC 3 + + +/* all value below is determined by fix reg setting in _ipu_dmfc_init*/ +#define DMFC_FIFO_SIZE_28 (128*4) +#define DMFC_FIFO_SIZE_29 (64*4) +#define DMFC_FIFO_SIZE_24 (64*4) +#define DMFC_FIFO_SIZE_27 (128*4) +#define DMFC_FIFO_SIZE_23 (128*4) + +void _ipu_dmfc_init(void) +{ + /* disable DMFC-IC channel*/ + __raw_writel(0x2, DMFC_IC_CTRL); + /* 1 - segment 0 and 1; 2, 1C and 2C unused */ + __raw_writel(0x00000088, DMFC_WR_CHAN); + __raw_writel(0x202020F6, DMFC_WR_CHAN_DEF); + /* 5B - segment 2 and 3; 5F - segment 4 and 5; */ + /* 6B - segment 6; 6F - segment 7 */ + __raw_writel(0x1F1E9694, DMFC_DP_CHAN); + /* Enable chan 5 watermark set at 5 bursts and clear at 7 bursts */ + __raw_writel(0x2020F6F6, DMFC_DP_CHAN_DEF); +} + +void _ipu_dmfc_set_wait4eot(int dma_chan, int width) +{ + u32 dmfc_gen1 = __raw_readl(DMFC_GENERAL1); + + if (dma_chan == 23) { /*5B*/ + if (DMFC_FIFO_SIZE_23/width > 3) + dmfc_gen1 |= 1UL << 20; + else + dmfc_gen1 &= ~(1UL << 20); + } else if (dma_chan == 24) { /*6B*/ + if (DMFC_FIFO_SIZE_24/width > 1) + dmfc_gen1 |= 1UL << 22; + else + dmfc_gen1 &= ~(1UL << 22); + } else if (dma_chan == 27) { /*5F*/ + if (DMFC_FIFO_SIZE_27/width > 2) + dmfc_gen1 |= 1UL << 21; + else + dmfc_gen1 &= ~(1UL << 21); + } else if (dma_chan == 28) { /*1*/ + if (DMFC_FIFO_SIZE_28/width > 2) + dmfc_gen1 |= 1UL << 16; + else + dmfc_gen1 &= ~(1UL << 16); + } else if (dma_chan == 29) { /*6F*/ + if (DMFC_FIFO_SIZE_29/width > 1) + dmfc_gen1 |= 1UL << 23; + else + dmfc_gen1 &= ~(1UL << 23); + } + + __raw_writel(dmfc_gen1, DMFC_GENERAL1); +} + +static void _ipu_di_data_wave_config(int di, + int wave_gen, + int access_size, int component_size) +{ + u32 reg; + reg = (access_size << DI_DW_GEN_ACCESS_SIZE_OFFSET) | + (component_size << DI_DW_GEN_COMPONENT_SIZE_OFFSET); + __raw_writel(reg, DI_DW_GEN(di, wave_gen)); +} + +static void _ipu_di_data_pin_config(int di, int wave_gen, int di_pin, int set, + int up, int down) +{ + u32 reg; + + reg = __raw_readl(DI_DW_GEN(di, wave_gen)); + reg &= ~(0x3 << (di_pin * 2)); + reg |= set << (di_pin * 2); + __raw_writel(reg, DI_DW_GEN(di, wave_gen)); + + __raw_writel((down << 16) | up, DI_DW_SET(di, wave_gen, set)); +} + +static void _ipu_di_sync_config(int di, int wave_gen, + int run_count, int run_src, + int offset_count, int offset_src, + int repeat_count, int cnt_clr_src, + int cnt_polarity_gen_en, + int cnt_polarity_clr_src, + int cnt_polarity_trigger_src, + int cnt_up, int cnt_down) +{ + u32 reg; + + if ((run_count >= 0x1000) || (offset_count >= 0x1000) || (repeat_count >= 0x1000) || + (cnt_up >= 0x400) || (cnt_down >= 0x400)) { + dev_err(g_ipu_dev, "DI%d counters out of range.\n", di); + return; + } + + reg = (run_count << 19) | (++run_src << 16) | + (offset_count << 3) | ++offset_src; + __raw_writel(reg, DI_SW_GEN0(di, wave_gen)); + reg = (cnt_polarity_gen_en << 29) | (++cnt_clr_src << 25) | + (++cnt_polarity_trigger_src << 12) | (++cnt_polarity_clr_src << 9); + reg |= (cnt_down << 16) | cnt_up; + if (repeat_count == 0) { + /* Enable auto reload */ + reg |= 0x10000000; + } + __raw_writel(reg, DI_SW_GEN1(di, wave_gen)); + reg = __raw_readl(DI_STP_REP(di, wave_gen)); + reg &= ~(0xFFFF << (16 * ((wave_gen - 1) & 0x1))); + reg |= repeat_count << (16 * ((wave_gen - 1) & 0x1)); + __raw_writel(reg, DI_STP_REP(di, wave_gen)); +} + +static void _ipu_dc_map_config(int map, int byte_num, int offset, int mask) +{ + int ptr = map * 3 + byte_num; + u32 reg; + + reg = __raw_readl(DC_MAP_CONF_VAL(ptr)); + reg &= ~(0xFFFF << (16 * (ptr & 0x1))); + reg |= ((offset << 8) | mask) << (16 * (ptr & 0x1)); + __raw_writel(reg, DC_MAP_CONF_VAL(ptr)); + + reg = __raw_readl(DC_MAP_CONF_PTR(map)); + reg &= ~(0x1F << ((16 * (map & 0x1)) + (5 * byte_num))); + reg |= ptr << ((16 * (map & 0x1)) + (5 * byte_num)); + __raw_writel(reg, DC_MAP_CONF_PTR(map)); +} + +static void _ipu_dc_map_clear(int map) +{ + u32 reg = __raw_readl(DC_MAP_CONF_PTR(map)); + __raw_writel(reg & ~(0xFFFF << (16 * (map & 0x1))), + DC_MAP_CONF_PTR(map)); +} + +static void _ipu_dc_write_tmpl(int word, u32 opcode, u32 operand, int map, + int wave, int glue, int sync) +{ + u32 reg; + int stop = 1; + + reg = sync; + reg |= (glue << 4); + reg |= (++wave << 11); + reg |= (++map << 15); + reg |= (operand << 20) & 0xFFF00000; + __raw_writel(reg, ipu_dc_tmpl_reg + word * 2); + + reg = (operand >> 12); + reg |= opcode << 4; + reg |= (stop << 9); + __raw_writel(reg, ipu_dc_tmpl_reg + word * 2 + 1); +} + +static void _ipu_dc_link_event(int chan, int event, int addr, int priority) +{ + u32 reg; + + reg = __raw_readl(DC_RL_CH(chan, event)); + reg &= ~(0xFFFF << (16 * (event & 0x1))); + reg |= ((addr << 8) | priority) << (16 * (event & 0x1)); + __raw_writel(reg, DC_RL_CH(chan, event)); +} + +/* Y = R * .299 + G * .587 + B * .114; + U = R * -.169 + G * -.332 + B * .500 + 128.; + V = R * .500 + G * -.419 + B * -.0813 + 128.;*/ +static const int rgb2ycbcr_coeff[5][3] = { + {153, 301, 58}, + {-87, -170, 0x0100}, + {0x100, -215, -42}, + {0x0000, 0x0200, 0x0200}, /* B0, B1, B2 */ + {0x2, 0x2, 0x2}, /* S0, S1, S2 */ +}; + +/* R = (1.164 * (Y - 16)) + (1.596 * (Cr - 128)); + G = (1.164 * (Y - 16)) - (0.392 * (Cb - 128)) - (0.813 * (Cr - 128)); + B = (1.164 * (Y - 16)) + (2.017 * (Cb - 128); */ +static const int ycbcr2rgb_coeff[5][3] = { + {0x095, 0x000, 0x0CC}, + {0x095, 0x3CE, 0x398}, + {0x095, 0x0FF, 0x000}, + {0x3E42, 0x010A, 0x3DD6}, /*B0,B1,B2 */ + {0x1, 0x1, 0x1}, /*S0,S1,S2 */ +}; + +#define mask_a(a) ((u32)(a) & 0x3FF) +#define mask_b(b) ((u32)(b) & 0x3FFF) + +/* Pls keep S0, S1 and S2 as 0x2 by using this convertion */ +static int _rgb_to_yuv(int n, int red, int green, int blue) +{ + int c; + c = red * rgb2ycbcr_coeff[n][0]; + c += green * rgb2ycbcr_coeff[n][1]; + c += blue * rgb2ycbcr_coeff[n][2]; + c /= 16; + c += rgb2ycbcr_coeff[3][n] * 4; + c += 8; + c /= 16; + if (c < 0) + c = 0; + if (c > 255) + c = 255; + return c; +} + +/* + * Row is for BG: RGB2YUV YUV2RGB RGB2RGB YUV2YUV CSC_NONE + * Column is for FG: RGB2YUV YUV2RGB RGB2RGB YUV2YUV CSC_NONE + */ +static struct dp_csc_param_t dp_csc_array[CSC_NUM][CSC_NUM] = { +{{DP_COM_CONF_CSC_DEF_BOTH, &rgb2ycbcr_coeff}, {0, 0}, {0, 0}, {DP_COM_CONF_CSC_DEF_BG, &rgb2ycbcr_coeff}, {DP_COM_CONF_CSC_DEF_BG, &rgb2ycbcr_coeff} }, +{{0, 0}, {DP_COM_CONF_CSC_DEF_BOTH, &ycbcr2rgb_coeff}, {DP_COM_CONF_CSC_DEF_BG, &ycbcr2rgb_coeff}, {0, 0}, {DP_COM_CONF_CSC_DEF_BG, &ycbcr2rgb_coeff} }, +{{0, 0}, {DP_COM_CONF_CSC_DEF_FG, &ycbcr2rgb_coeff}, {0, 0}, {0, 0}, {0, 0} }, +{{DP_COM_CONF_CSC_DEF_FG, &rgb2ycbcr_coeff}, {0, 0}, {0, 0}, {0, 0}, {0, 0} }, +{{DP_COM_CONF_CSC_DEF_FG, &rgb2ycbcr_coeff}, {DP_COM_CONF_CSC_DEF_FG, &ycbcr2rgb_coeff}, {0, 0}, {0, 0}, {0, 0} } +}; + +static enum csc_type_t fg_csc_type = CSC_NONE, bg_csc_type = CSC_NONE; +static int color_key_4rgb = 1; + +void __ipu_dp_csc_setup(int dp, struct dp_csc_param_t dp_csc_param, + bool srm_mode_update) +{ + u32 reg; + const int (*coeff)[5][3]; + + if (dp_csc_param.mode >= 0) { + reg = __raw_readl(DP_COM_CONF(dp)); + reg &= ~DP_COM_CONF_CSC_DEF_MASK; + reg |= dp_csc_param.mode; + __raw_writel(reg, DP_COM_CONF(dp)); + } + + coeff = dp_csc_param.coeff; + + if (coeff) { + __raw_writel(mask_a((*coeff)[0][0]) | + (mask_a((*coeff)[0][1]) << 16), DP_CSC_A_0(dp)); + __raw_writel(mask_a((*coeff)[0][2]) | + (mask_a((*coeff)[1][0]) << 16), DP_CSC_A_1(dp)); + __raw_writel(mask_a((*coeff)[1][1]) | + (mask_a((*coeff)[1][2]) << 16), DP_CSC_A_2(dp)); + __raw_writel(mask_a((*coeff)[2][0]) | + (mask_a((*coeff)[2][1]) << 16), DP_CSC_A_3(dp)); + __raw_writel(mask_a((*coeff)[2][2]) | + (mask_b((*coeff)[3][0]) << 16) | + ((*coeff)[4][0] << 30), DP_CSC_0(dp)); + __raw_writel(mask_b((*coeff)[3][1]) | ((*coeff)[4][1] << 14) | + (mask_b((*coeff)[3][2]) << 16) | + ((*coeff)[4][2] << 30), DP_CSC_1(dp)); + } + + if (srm_mode_update) { + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + } +} + +int _ipu_dp_init(ipu_channel_t channel, uint32_t in_pixel_fmt, + uint32_t out_pixel_fmt) +{ + int in_fmt, out_fmt; + int dp; + int partial = false; + uint32_t reg; + + if (channel == MEM_FG_SYNC) { + dp = DP_SYNC; + partial = true; + } else if (channel == MEM_BG_SYNC) { + dp = DP_SYNC; + partial = false; + } else if (channel == MEM_BG_ASYNC0) { + dp = DP_ASYNC0; + partial = false; + } else { + return -EINVAL; + } + + in_fmt = format_to_colorspace(in_pixel_fmt); + out_fmt = format_to_colorspace(out_pixel_fmt); + + if (partial) { + if (in_fmt == RGB) { + if (out_fmt == RGB) + fg_csc_type = RGB2RGB; + else + fg_csc_type = RGB2YUV; + } else { + if (out_fmt == RGB) + fg_csc_type = YUV2RGB; + else + fg_csc_type = YUV2YUV; + } + } else { + if (in_fmt == RGB) { + if (out_fmt == RGB) + bg_csc_type = RGB2RGB; + else + bg_csc_type = RGB2YUV; + } else { + if (out_fmt == RGB) + bg_csc_type = YUV2RGB; + else + bg_csc_type = YUV2YUV; + } + } + + /* Transform color key from rgb to yuv if CSC is enabled */ + reg = __raw_readl(DP_COM_CONF(dp)); + if (color_key_4rgb && (reg & DP_COM_CONF_GWCKE) && + (((fg_csc_type == RGB2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == RGB2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2RGB) && (bg_csc_type == YUV2RGB)))) { + int red, green, blue; + int y, u, v; + uint32_t color_key = __raw_readl(DP_GRAPH_WIND_CTRL(dp)) & 0xFFFFFFL; + + dev_dbg(g_ipu_dev, "_ipu_dp_init color key 0x%x need change to yuv fmt!\n", color_key); + + red = (color_key >> 16) & 0xFF; + green = (color_key >> 8) & 0xFF; + blue = color_key & 0xFF; + + y = _rgb_to_yuv(0, red, green, blue); + u = _rgb_to_yuv(1, red, green, blue); + v = _rgb_to_yuv(2, red, green, blue); + color_key = (y << 16) | (u << 8) | v; + + reg = __raw_readl(DP_GRAPH_WIND_CTRL(dp)) & 0xFF000000L; + __raw_writel(reg | color_key, DP_GRAPH_WIND_CTRL(dp)); + color_key_4rgb = 0; + + dev_dbg(g_ipu_dev, "_ipu_dp_init color key change to yuv fmt 0x%x!\n", color_key); + } + + __ipu_dp_csc_setup(dp, dp_csc_array[bg_csc_type][fg_csc_type], true); + + return 0; +} + +void _ipu_dp_uninit(ipu_channel_t channel) +{ + int dp; + int partial = false; + + if (channel == MEM_FG_SYNC) { + dp = DP_SYNC; + partial = true; + } else if (channel == MEM_BG_SYNC) { + dp = DP_SYNC; + partial = false; + } else if (channel == MEM_BG_ASYNC0) { + dp = DP_ASYNC0; + partial = false; + } else { + return; + } + + if (partial) + fg_csc_type = CSC_NONE; + else + bg_csc_type = CSC_NONE; + + __ipu_dp_csc_setup(dp, dp_csc_array[bg_csc_type][fg_csc_type], false); +} + +void _ipu_dc_init(int dc_chan, int di, bool interlaced) +{ + u32 reg = 0; + + if ((dc_chan == 1) || (dc_chan == 5)) { + if (interlaced) { + _ipu_dc_link_event(dc_chan, DC_EVT_NL, 0, 3); + _ipu_dc_link_event(dc_chan, DC_EVT_EOL, 0, 2); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 0, 1); + } else { + if (di) { + _ipu_dc_link_event(dc_chan, DC_EVT_NL, 2, 3); + _ipu_dc_link_event(dc_chan, DC_EVT_EOL, 3, 2); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 4, 1); + } else { + _ipu_dc_link_event(dc_chan, DC_EVT_NL, 5, 3); + _ipu_dc_link_event(dc_chan, DC_EVT_EOL, 6, 2); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 7, 1); + } + } + _ipu_dc_link_event(dc_chan, DC_EVT_NF, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NFIELD, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOF, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOFIELD, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR, 0, 0); + + reg = 0x2; + reg |= DC_DISP_ID_SYNC(di) << DC_WR_CH_CONF_PROG_DISP_ID_OFFSET; + reg |= di << 2; + if (interlaced) + reg |= DC_WR_CH_CONF_FIELD_MODE; + } else if ((dc_chan == 8) || (dc_chan == 9)) { + /* async channels */ + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_W_0, 0x64, 1); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_W_1, 0x64, 1); + + reg = 0x3; + reg |= DC_DISP_ID_SERIAL << DC_WR_CH_CONF_PROG_DISP_ID_OFFSET; + } + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); + + __raw_writel(0x00000000, DC_WR_CH_ADDR(dc_chan)); + + __raw_writel(0x00000084, DC_GEN); +} + +void _ipu_dc_uninit(int dc_chan) +{ + if ((dc_chan == 1) || (dc_chan == 5)) { + _ipu_dc_link_event(dc_chan, DC_EVT_NL, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOL, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NF, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NFIELD, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOF, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOFIELD, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR, 0, 0); + } else if ((dc_chan == 8) || (dc_chan == 9)) { + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR_W_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR_W_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN_W_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN_W_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_W_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_W_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR_R_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR_R_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN_R_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN_R_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_R_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_R_1, 0, 0); + } +} + +int _ipu_chan_is_interlaced(ipu_channel_t channel) +{ + if (channel == MEM_DC_SYNC) + return !!(__raw_readl(DC_WR_CH_CONF_1) & + DC_WR_CH_CONF_FIELD_MODE); + else if ((channel == MEM_BG_SYNC) || (channel == MEM_FG_SYNC)) + return !!(__raw_readl(DC_WR_CH_CONF_5) & + DC_WR_CH_CONF_FIELD_MODE); + return 0; +} + +void _ipu_dp_dc_enable(ipu_channel_t channel) +{ + int di; + uint32_t reg; + uint32_t dc_chan; + int irq = 0; + + if (channel == MEM_FG_SYNC) + irq = IPU_IRQ_DP_SF_END; + else if (channel == MEM_DC_SYNC) + dc_chan = 1; + else if (channel == MEM_BG_SYNC) + dc_chan = 5; + else + return; + + if (channel == MEM_FG_SYNC) { + /* Enable FG channel */ + reg = __raw_readl(DP_COM_CONF(DP_SYNC)); + __raw_writel(reg | DP_COM_CONF_FG_EN, DP_COM_CONF(DP_SYNC)); + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + return; + } + + di = g_dc_di_assignment[dc_chan]; + + /* Make sure other DC sync channel is not assigned same DI */ + reg = __raw_readl(DC_WR_CH_CONF(6 - dc_chan)); + if ((di << 2) == (reg & DC_WR_CH_CONF_PROG_DI_ID)) { + reg &= ~DC_WR_CH_CONF_PROG_DI_ID; + reg |= di ? 0 : DC_WR_CH_CONF_PROG_DI_ID; + __raw_writel(reg, DC_WR_CH_CONF(6 - dc_chan)); + } + + reg = __raw_readl(DC_WR_CH_CONF(dc_chan)); + reg |= 4 << DC_WR_CH_CONF_PROG_TYPE_OFFSET; + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); + + reg = __raw_readl(IPU_DISP_GEN); + if (di) + reg |= DI1_COUNTER_RELEASE; + else + reg |= DI0_COUNTER_RELEASE; + __raw_writel(reg, IPU_DISP_GEN); +} + +static bool dc_swap; + +static irqreturn_t dc_irq_handler(int irq, void *dev_id) +{ + struct completion *comp = dev_id; + + complete(comp); + return IRQ_HANDLED; +} + +void _ipu_dp_dc_disable(ipu_channel_t channel, bool swap) +{ + int ret; + unsigned long lock_flags; + uint32_t reg; + uint32_t csc; + uint32_t dc_chan; + int irq = 0; + int timeout = 50; + DECLARE_COMPLETION_ONSTACK(dc_comp); + + dc_swap = swap; + + if (channel == MEM_DC_SYNC) { + dc_chan = 1; + irq = IPU_IRQ_DC_FC_1; + } else if (channel == MEM_BG_SYNC) { + dc_chan = 5; + irq = IPU_IRQ_DP_SF_END; + } else if (channel == MEM_FG_SYNC) { + /* Disable FG channel */ + dc_chan = 5; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(DP_COM_CONF(DP_SYNC)); + csc = reg & DP_COM_CONF_CSC_DEF_MASK; + if (csc == DP_COM_CONF_CSC_DEF_FG) + reg &= ~DP_COM_CONF_CSC_DEF_MASK; + + reg &= ~DP_COM_CONF_FG_EN; + __raw_writel(reg, DP_COM_CONF(DP_SYNC)); + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_DP_SF_END), + IPUIRQ_2_STATREG(IPU_IRQ_DP_SF_END)); + while ((__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_DP_SF_END)) & + IPUIRQ_2_MASK(IPU_IRQ_DP_SF_END)) == 0) { + msleep(2); + timeout -= 2; + if (timeout <= 0) + break; + } + + timeout = 50; + + /* + * Wait for DC triple buffer to empty, + * this check is useful for tv overlay. + */ + if (g_dc_di_assignment[dc_chan] == 0) + while ((__raw_readl(DC_STAT) & 0x00000002) + != 0x00000002) { + msleep(2); + timeout -= 2; + if (timeout <= 0) + break; + } + else if (g_dc_di_assignment[dc_chan] == 1) + while ((__raw_readl(DC_STAT) & 0x00000020) + != 0x00000020) { + msleep(2); + timeout -= 2; + if (timeout <= 0) + break; + } + return; + } else { + return; + } + + if (!dc_swap) + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_VSYNC_PRE_0 + + g_dc_di_assignment[dc_chan]), + IPUIRQ_2_STATREG(IPU_IRQ_VSYNC_PRE_0 + + g_dc_di_assignment[dc_chan])); + ipu_clear_irq(irq); + ret = ipu_request_irq(irq, dc_irq_handler, 0, NULL, &dc_comp); + if (ret < 0) { + dev_err(g_ipu_dev, "DC irq %d in use\n", irq); + return; + } + ret = wait_for_completion_timeout(&dc_comp, msecs_to_jiffies(50)); + + dev_dbg(g_ipu_dev, "DC stop timeout - %d * 10ms\n", 5 - ret); + ipu_free_irq(irq, &dc_comp); + + if (dc_swap) { + spin_lock_irqsave(&ipu_lock, lock_flags); + /* Swap DC channel 1 and 5 settings, and disable old dc chan */ + reg = __raw_readl(DC_WR_CH_CONF(dc_chan)); + __raw_writel(reg, DC_WR_CH_CONF(6 - dc_chan)); + reg &= ~DC_WR_CH_CONF_PROG_TYPE_MASK; + reg ^= DC_WR_CH_CONF_PROG_DI_ID; + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + } else { + timeout = 50; + + /* Wait for DC triple buffer to empty */ + if (g_dc_di_assignment[dc_chan] == 0) + while ((__raw_readl(DC_STAT) & 0x00000002) + != 0x00000002) { + msleep(2); + timeout -= 2; + if (timeout <= 0) + break; + } + else if (g_dc_di_assignment[dc_chan] == 1) + while ((__raw_readl(DC_STAT) & 0x00000020) + != 0x00000020) { + msleep(2); + timeout -= 2; + if (timeout <= 0) + break; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(DC_WR_CH_CONF(dc_chan)); + reg &= ~DC_WR_CH_CONF_PROG_TYPE_MASK; + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); + + reg = __raw_readl(IPU_DISP_GEN); + if (g_dc_di_assignment[dc_chan]) + reg &= ~DI1_COUNTER_RELEASE; + else + reg &= ~DI0_COUNTER_RELEASE; + __raw_writel(reg, IPU_DISP_GEN); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + if (__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_VSYNC_PRE_0 + + g_dc_di_assignment[dc_chan])) & + IPUIRQ_2_MASK(IPU_IRQ_VSYNC_PRE_0 + + g_dc_di_assignment[dc_chan])) + dev_dbg(g_ipu_dev, + "VSyncPre occurred before DI%d disable\n", + g_dc_di_assignment[dc_chan]); + } +} + +void _ipu_init_dc_mappings(void) +{ + /* IPU_PIX_FMT_RGB24 */ + _ipu_dc_map_clear(0); + _ipu_dc_map_config(0, 0, 7, 0xFF); + _ipu_dc_map_config(0, 1, 15, 0xFF); + _ipu_dc_map_config(0, 2, 23, 0xFF); + + /* IPU_PIX_FMT_RGB666 */ + _ipu_dc_map_clear(1); + _ipu_dc_map_config(1, 0, 5, 0xFC); + _ipu_dc_map_config(1, 1, 11, 0xFC); + _ipu_dc_map_config(1, 2, 17, 0xFC); + + /* IPU_PIX_FMT_YUV444 */ + _ipu_dc_map_clear(2); + _ipu_dc_map_config(2, 0, 15, 0xFF); + _ipu_dc_map_config(2, 1, 23, 0xFF); + _ipu_dc_map_config(2, 2, 7, 0xFF); + + /* IPU_PIX_FMT_RGB565 */ + _ipu_dc_map_clear(3); + _ipu_dc_map_config(3, 0, 4, 0xF8); + _ipu_dc_map_config(3, 1, 10, 0xFC); + _ipu_dc_map_config(3, 2, 15, 0xF8); + + /* IPU_PIX_FMT_LVDS666 */ + _ipu_dc_map_clear(4); + _ipu_dc_map_config(4, 0, 5, 0xFC); + _ipu_dc_map_config(4, 1, 13, 0xFC); + _ipu_dc_map_config(4, 2, 21, 0xFC); +} + +int _ipu_pixfmt_to_map(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_GENERIC: + case IPU_PIX_FMT_RGB24: + return 0; + case IPU_PIX_FMT_RGB666: + return 1; + case IPU_PIX_FMT_YUV444: + return 2; + case IPU_PIX_FMT_RGB565: + return 3; + case IPU_PIX_FMT_LVDS666: + return 4; + } + + return -1; +} + +/*! + * This function sets the colorspace for of dp. + * modes. + * + * @param channel Input parameter for the logical channel ID. + * + * @param param If it's not NULL, update the csc table + * with this parameter. + * + * @return N/A + */ +void _ipu_dp_set_csc_coefficients(ipu_channel_t channel, int32_t param[][3]) +{ + int dp; + struct dp_csc_param_t dp_csc_param; + + if (channel == MEM_FG_SYNC) + dp = DP_SYNC; + else if (channel == MEM_BG_SYNC) + dp = DP_SYNC; + else if (channel == MEM_BG_ASYNC0) + dp = DP_ASYNC0; + else + return; + + dp_csc_param.mode = -1; + dp_csc_param.coeff = param; + __ipu_dp_csc_setup(dp, dp_csc_param, true); +} + +/*! + * This function is called to initialize a synchronous LCD panel. + * + * @param disp The DI the panel is attached to. + * + * @param pixel_clk Desired pixel clock frequency in Hz. + * + * @param pixel_fmt Input parameter for pixel format of buffer. + * Pixel format is a FOURCC ASCII code. + * + * @param width The width of panel in pixels. + * + * @param height The height of panel in pixels. + * + * @param hStartWidth The number of pixel clocks between the HSYNC + * signal pulse and the start of valid data. + * + * @param hSyncWidth The width of the HSYNC signal in units of pixel + * clocks. + * + * @param hEndWidth The number of pixel clocks between the end of + * valid data and the HSYNC signal for next line. + * + * @param vStartWidth The number of lines between the VSYNC + * signal pulse and the start of valid data. + * + * @param vSyncWidth The width of the VSYNC signal in units of lines + * + * @param vEndWidth The number of lines between the end of valid + * data and the VSYNC signal for next frame. + * + * @param sig Bitfield of signal polarities for LCD interface. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_init_sync_panel(int disp, uint32_t pixel_clk, + uint16_t width, uint16_t height, + uint32_t pixel_fmt, + uint16_t h_start_width, uint16_t h_sync_width, + uint16_t h_end_width, uint16_t v_start_width, + uint16_t v_sync_width, uint16_t v_end_width, + uint32_t v_to_h_sync, ipu_di_signal_cfg_t sig) +{ + unsigned long lock_flags; + uint32_t field0_offset = 0; + uint32_t field1_offset; + uint32_t reg; + uint32_t disp_gen, di_gen, vsync_cnt; + uint32_t div; + uint32_t h_total, v_total; + int map; + struct clk *di_clk; + + dev_dbg(g_ipu_dev, "panel size = %d x %d\n", width, height); + + if ((v_sync_width == 0) || (h_sync_width == 0)) + return EINVAL; + + h_total = width + h_sync_width + h_start_width + h_end_width; + v_total = height + v_sync_width + v_start_width + v_end_width; + + /* Init clocking */ + dev_dbg(g_ipu_dev, "pixel clk = %d\n", pixel_clk); + if (sig.ext_clk) + di_clk = g_di_clk[disp]; + else + di_clk = g_ipu_clk; + + /* + * Calculate divider + * Fractional part is 4 bits, + * so simply multiply by 2^4 to get fractional part. + */ + div = (clk_get_rate(di_clk) * 16) / pixel_clk; + if (div < 0x10) /* Min DI disp clock divider is 1 */ + div = 0x10; + /* + * DI disp clock offset is zero, + * and fractional part is rounded off to 0.5. + */ + div &= 0xFF8; + + reg = __raw_readl(DI_GENERAL(disp)); + if (sig.ext_clk) + __raw_writel(reg | DI_GEN_DI_CLK_EXT, DI_GENERAL(disp)); + else + __raw_writel(reg & ~DI_GEN_DI_CLK_EXT, DI_GENERAL(disp)); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + disp_gen = __raw_readl(IPU_DISP_GEN); + disp_gen &= disp ? ~DI1_COUNTER_RELEASE : ~DI0_COUNTER_RELEASE; + __raw_writel(disp_gen, IPU_DISP_GEN); + + __raw_writel(div, DI_BS_CLKGEN0(disp)); + + /* Setup pixel clock timing */ + /* FIXME: needs to be more flexible */ + /* Down time is half of period */ + __raw_writel((div / 16) << 16, DI_BS_CLKGEN1(disp)); + + _ipu_di_data_wave_config(disp, SYNC_WAVE, div / 16 - 1, div / 16 - 1); + _ipu_di_data_pin_config(disp, SYNC_WAVE, DI_PIN15, 3, 0, div / 16 * 2); + + div = div / 16; /* Now divider is integer portion */ + + map = _ipu_pixfmt_to_map(pixel_fmt); + if (map < 0) { + dev_dbg(g_ipu_dev, "IPU_DISP: No MAP\n"); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + + di_gen = 0; + if (sig.ext_clk) + di_gen |= DI_GEN_DI_CLK_EXT; + + if (sig.interlaced) { + if (cpu_is_mx51_rev(CHIP_REV_2_0)) { + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config( + disp, /* display */ + 1, /* counter */ + h_total/2 - 1, /* run count */ + DI_SYNC_CLK, /* run_resolution */ + 0, /* offset */ + DI_SYNC_NONE, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_NONE, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* Field 1 VSYNC waveform */ + _ipu_di_sync_config( + disp, /* display */ + 2, /* counter */ + h_total - 1, /* run count */ + DI_SYNC_CLK, /* run_resolution */ + 0, /* offset */ + DI_SYNC_NONE, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_NONE, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 4 /* COUNT DOWN */ + ); + + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config( + disp, /* display */ + 3, /* counter */ + v_total*2 - 1, /* run count */ + DI_SYNC_INT_HSYNC, /* run_resolution */ + 1, /* offset */ + DI_SYNC_INT_HSYNC, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_NONE, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 4 /* COUNT DOWN */ + ); + + /* Active Field ? */ + _ipu_di_sync_config( + disp, /* display */ + 4, /* counter */ + v_total/2 - 1, /* run count */ + DI_SYNC_HSYNC, /* run_resolution */ + v_start_width, /* offset */ + DI_SYNC_HSYNC, /* offset resolution */ + 2, /* repeat count */ + DI_SYNC_VSYNC, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* Active Line */ + _ipu_di_sync_config( + disp, /* display */ + 5, /* counter */ + 0, /* run count */ + DI_SYNC_HSYNC, /* run_resolution */ + 0, /* offset */ + DI_SYNC_NONE, /* offset resolution */ + height/2, /* repeat count */ + 4, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* Field 0 VSYNC waveform */ + _ipu_di_sync_config( + disp, /* display */ + 6, /* counter */ + v_total - 1, /* run count */ + DI_SYNC_HSYNC, /* run_resolution */ + 0, /* offset */ + DI_SYNC_NONE, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_NONE, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* DC VSYNC waveform */ + vsync_cnt = 7; + _ipu_di_sync_config( + disp, /* display */ + 7, /* counter */ + v_total/2 - 1, /* run count */ + DI_SYNC_HSYNC, /* run_resolution */ + 9, /* offset */ + DI_SYNC_HSYNC, /* offset resolution */ + 2, /* repeat count */ + DI_SYNC_VSYNC, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* active pixel waveform */ + _ipu_di_sync_config( + disp, /* display */ + 8, /* counter */ + 0, /* run count */ + DI_SYNC_CLK, /* run_resolution */ + h_start_width, /* offset */ + DI_SYNC_CLK, /* offset resolution */ + width, /* repeat count */ + 5, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* ??? */ + _ipu_di_sync_config( + disp, /* display */ + 9, /* counter */ + v_total - 1, /* run count */ + DI_SYNC_INT_HSYNC, /* run_resolution */ + v_total/2, /* offset */ + DI_SYNC_INT_HSYNC, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_HSYNC, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 4 /* COUNT DOWN */ + ); + + /* set gentime select and tag sel */ + reg = __raw_readl(DI_SW_GEN1(disp, 9)); + reg &= 0x1FFFFFFF; + reg |= (3-1)<<29 | 0x00008000; + __raw_writel(reg, DI_SW_GEN1(disp, 9)); + + __raw_writel(v_total / 2 - 1, DI_SCR_CONF(disp)); + + /* set y_sel = 1 */ + di_gen |= 0x10000000; + di_gen |= DI_GEN_POLARITY_5; + di_gen |= DI_GEN_POLARITY_8; + } else { + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config(disp, 1, h_total - 1, DI_SYNC_CLK, + 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + field1_offset = v_sync_width + v_start_width + height / 2 + + v_end_width; + if (sig.odd_field_first) { + field0_offset = field1_offset - 1; + field1_offset = 0; + } + v_total += v_start_width + v_end_width; + + /* Field 1 VSYNC waveform */ + _ipu_di_sync_config(disp, 2, v_total - 1, 1, + field0_offset, + field0_offset ? 1 : DI_SYNC_NONE, + 0, DI_SYNC_NONE, 0, + DI_SYNC_NONE, DI_SYNC_NONE, 0, 4); + + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config(disp, 3, h_total - 1, DI_SYNC_CLK, + 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, 0, + DI_SYNC_NONE, DI_SYNC_NONE, 0, 4); + + /* Active Field ? */ + _ipu_di_sync_config(disp, 4, + field0_offset ? + field0_offset : field1_offset - 2, + 1, v_start_width + v_sync_width, 1, 2, 2, + 0, DI_SYNC_NONE, DI_SYNC_NONE, 0, 0); + + /* Active Line */ + _ipu_di_sync_config(disp, 5, 0, 1, + 0, DI_SYNC_NONE, + height / 2, 4, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + /* Field 0 VSYNC waveform */ + _ipu_di_sync_config(disp, 6, v_total - 1, 1, + 0, DI_SYNC_NONE, + 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + /* DC VSYNC waveform */ + vsync_cnt = 7; + _ipu_di_sync_config(disp, 7, 0, 1, + field1_offset, + field1_offset ? 1 : DI_SYNC_NONE, + 1, 2, 0, DI_SYNC_NONE, DI_SYNC_NONE, 0, 0); + + /* active pixel waveform */ + _ipu_di_sync_config(disp, 8, 0, DI_SYNC_CLK, + h_sync_width + h_start_width, DI_SYNC_CLK, + width, 5, 0, DI_SYNC_NONE, DI_SYNC_NONE, + 0, 0); + + /* ??? */ + _ipu_di_sync_config(disp, 9, v_total - 1, 2, + 0, DI_SYNC_NONE, + 0, DI_SYNC_NONE, 6, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + reg = __raw_readl(DI_SW_GEN1(disp, 9)); + reg |= 0x8000; + __raw_writel(reg, DI_SW_GEN1(disp, 9)); + + __raw_writel(v_sync_width + v_start_width + + v_end_width + height / 2 - 1, DI_SCR_CONF(disp)); + } + + /* Init template microcode */ + _ipu_dc_write_tmpl(0, WROD(0), 0, map, SYNC_WAVE, 0, 8); + + if (sig.Hsync_pol) + di_gen |= DI_GEN_POLARITY_3; + if (sig.Vsync_pol) + di_gen |= DI_GEN_POLARITY_2; + } else { + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config(disp, 1, h_total - 1, DI_SYNC_CLK, + 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + /* Setup external (delayed) HSYNC waveform */ + _ipu_di_sync_config(disp, DI_SYNC_HSYNC, h_total - 1, + DI_SYNC_CLK, div * v_to_h_sync, DI_SYNC_CLK, + 0, DI_SYNC_NONE, 1, DI_SYNC_NONE, + DI_SYNC_CLK, 0, h_sync_width * 2); + /* Setup VSYNC waveform */ + vsync_cnt = DI_SYNC_VSYNC; + _ipu_di_sync_config(disp, DI_SYNC_VSYNC, v_total - 1, + DI_SYNC_INT_HSYNC, 0, DI_SYNC_NONE, 0, + DI_SYNC_NONE, 1, DI_SYNC_NONE, + DI_SYNC_INT_HSYNC, 0, v_sync_width * 2); + __raw_writel(v_total - 1, DI_SCR_CONF(disp)); + + /* Setup active data waveform to sync with DC */ + _ipu_di_sync_config(disp, 4, 0, DI_SYNC_HSYNC, + v_sync_width + v_start_width, DI_SYNC_HSYNC, height, + DI_SYNC_VSYNC, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + _ipu_di_sync_config(disp, 5, 0, DI_SYNC_CLK, + h_sync_width + h_start_width, DI_SYNC_CLK, + width, 4, 0, DI_SYNC_NONE, DI_SYNC_NONE, 0, + 0); + + /* reset all unused counters */ + __raw_writel(0, DI_SW_GEN0(disp, 6)); + __raw_writel(0, DI_SW_GEN1(disp, 6)); + __raw_writel(0, DI_SW_GEN0(disp, 7)); + __raw_writel(0, DI_SW_GEN1(disp, 7)); + __raw_writel(0, DI_SW_GEN0(disp, 8)); + __raw_writel(0, DI_SW_GEN1(disp, 8)); + __raw_writel(0, DI_SW_GEN0(disp, 9)); + __raw_writel(0, DI_SW_GEN1(disp, 9)); + + reg = __raw_readl(DI_STP_REP(disp, 6)); + reg &= 0x0000FFFF; + __raw_writel(reg, DI_STP_REP(disp, 6)); + __raw_writel(0, DI_STP_REP(disp, 7)); + __raw_writel(0, DI_STP_REP(disp, 9)); + + /* Init template microcode */ + if (disp) { + _ipu_dc_write_tmpl(2, WROD(0), 0, map, SYNC_WAVE, 8, 5); + _ipu_dc_write_tmpl(3, WROD(0), 0, map, SYNC_WAVE, 4, 5); + _ipu_dc_write_tmpl(4, WROD(0), 0, map, SYNC_WAVE, 0, 5); + } else { + _ipu_dc_write_tmpl(5, WROD(0), 0, map, SYNC_WAVE, 8, 5); + _ipu_dc_write_tmpl(6, WROD(0), 0, map, SYNC_WAVE, 4, 5); + _ipu_dc_write_tmpl(7, WROD(0), 0, map, SYNC_WAVE, 0, 5); + } + + if (sig.Hsync_pol) + di_gen |= DI_GEN_POLARITY_2; + if (sig.Vsync_pol) + di_gen |= DI_GEN_POLARITY_3; + } + + __raw_writel(di_gen, DI_GENERAL(disp)); + __raw_writel((--vsync_cnt << DI_VSYNC_SEL_OFFSET) | 0x00000002, + DI_SYNC_AS_GEN(disp)); + + reg = __raw_readl(DI_POL(disp)); + reg &= ~(DI_POL_DRDY_DATA_POLARITY | DI_POL_DRDY_POLARITY_15); + if (sig.enable_pol) + reg |= DI_POL_DRDY_POLARITY_15; + if (sig.data_pol) + reg |= DI_POL_DRDY_DATA_POLARITY; + __raw_writel(reg, DI_POL(disp)); + + __raw_writel(width, DC_DISP_CONF2(DC_DISP_ID_SYNC(disp))); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_init_sync_panel); + + +int ipu_init_async_panel(int disp, int type, uint32_t cycle_time, + uint32_t pixel_fmt, ipu_adc_sig_cfg_t sig) +{ + unsigned long lock_flags; + int map; + u32 ser_conf = 0; + u32 div; + u32 di_clk = clk_get_rate(g_ipu_clk); + + /* round up cycle_time, then calcalate the divider using scaled math */ + cycle_time += (1000000000UL / di_clk) - 1; + div = (cycle_time * (di_clk / 256UL)) / (1000000000UL / 256UL); + + map = _ipu_pixfmt_to_map(pixel_fmt); + if (map < 0) + return -EINVAL; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (type == IPU_PANEL_SERIAL) { + __raw_writel((div << 24) | ((sig.ifc_width - 1) << 4), + DI_DW_GEN(disp, ASYNC_SER_WAVE)); + + _ipu_di_data_pin_config(disp, ASYNC_SER_WAVE, DI_PIN_CS, + 0, 0, (div * 2) + 1); + _ipu_di_data_pin_config(disp, ASYNC_SER_WAVE, DI_PIN_SER_CLK, + 1, div, div * 2); + _ipu_di_data_pin_config(disp, ASYNC_SER_WAVE, DI_PIN_SER_RS, + 2, 0, 0); + + _ipu_dc_write_tmpl(0x64, WROD(0), 0, map, ASYNC_SER_WAVE, 0, 0); + + /* Configure DC for serial panel */ + __raw_writel(0x14, DC_DISP_CONF1(DC_DISP_ID_SERIAL)); + + if (sig.clk_pol) + ser_conf |= DI_SER_CONF_SERIAL_CLK_POL; + if (sig.data_pol) + ser_conf |= DI_SER_CONF_SERIAL_DATA_POL; + if (sig.rs_pol) + ser_conf |= DI_SER_CONF_SERIAL_RS_POL; + if (sig.cs_pol) + ser_conf |= DI_SER_CONF_SERIAL_CS_POL; + __raw_writel(ser_conf, DI_SER_CONF(disp)); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} +EXPORT_SYMBOL(ipu_init_async_panel); + +/*! + * This function sets the foreground and background plane global alpha blending + * modes. This function also sets the DP graphic plane according to the + * parameter of IPUv3 DP channel. + * + * @param channel IPUv3 DP channel + * + * @param enable Boolean to enable or disable global alpha + * blending. If disabled, local blending is used. + * + * @param alpha Global alpha value. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_disp_set_global_alpha(ipu_channel_t channel, bool enable, + uint8_t alpha) +{ + uint32_t reg; + uint32_t flow; + unsigned long lock_flags; + bool bg_chan; + + if (channel == MEM_BG_SYNC || channel == MEM_FG_SYNC) + flow = DP_SYNC; + else if (channel == MEM_BG_ASYNC0 || channel == MEM_FG_ASYNC0) + flow = DP_ASYNC0; + else if (channel == MEM_BG_ASYNC1 || channel == MEM_FG_ASYNC1) + flow = DP_ASYNC1; + else + return -EINVAL; + + if (channel == MEM_BG_SYNC || channel == MEM_BG_ASYNC0 || + channel == MEM_BG_ASYNC1) + bg_chan = true; + else + bg_chan = false; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (bg_chan) { + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg & ~DP_COM_CONF_GWSEL, DP_COM_CONF(flow)); + } else { + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg | DP_COM_CONF_GWSEL, DP_COM_CONF(flow)); + } + + if (enable) { + reg = __raw_readl(DP_GRAPH_WIND_CTRL(flow)) & 0x00FFFFFFL; + __raw_writel(reg | ((uint32_t) alpha << 24), + DP_GRAPH_WIND_CTRL(flow)); + + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg | DP_COM_CONF_GWAM, DP_COM_CONF(flow)); + } else { + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg & ~DP_COM_CONF_GWAM, DP_COM_CONF(flow)); + } + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); + + return 0; +} +EXPORT_SYMBOL(ipu_disp_set_global_alpha); + +/*! + * This function sets the transparent color key for SDC graphic plane. + * + * @param channel Input parameter for the logical channel ID. + * + * @param enable Boolean to enable or disable color key + * + * @param colorKey 24-bit RGB color for transparent color key. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_disp_set_color_key(ipu_channel_t channel, bool enable, + uint32_t color_key) +{ + uint32_t reg, flow; + int y, u, v; + int red, green, blue; + unsigned long lock_flags; + + if (channel == MEM_BG_SYNC || channel == MEM_FG_SYNC) + flow = DP_SYNC; + else if (channel == MEM_BG_ASYNC0 || channel == MEM_FG_ASYNC0) + flow = DP_ASYNC0; + else if (channel == MEM_BG_ASYNC1 || channel == MEM_FG_ASYNC1) + flow = DP_ASYNC1; + else + return -EINVAL; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + color_key_4rgb = 1; + /* Transform color key from rgb to yuv if CSC is enabled */ + if (((fg_csc_type == RGB2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == RGB2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2RGB) && (bg_csc_type == YUV2RGB))) { + + dev_dbg(g_ipu_dev, "color key 0x%x need change to yuv fmt\n", color_key); + + red = (color_key >> 16) & 0xFF; + green = (color_key >> 8) & 0xFF; + blue = color_key & 0xFF; + + y = _rgb_to_yuv(0, red, green, blue); + u = _rgb_to_yuv(1, red, green, blue); + v = _rgb_to_yuv(2, red, green, blue); + color_key = (y << 16) | (u << 8) | v; + + color_key_4rgb = 0; + + dev_dbg(g_ipu_dev, "color key change to yuv fmt 0x%x\n", color_key); + } + + if (enable) { + reg = __raw_readl(DP_GRAPH_WIND_CTRL(flow)) & 0xFF000000L; + __raw_writel(reg | color_key, DP_GRAPH_WIND_CTRL(flow)); + + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg | DP_COM_CONF_GWCKE, DP_COM_CONF(flow)); + } else { + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg & ~DP_COM_CONF_GWCKE, DP_COM_CONF(flow)); + } + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); + + return 0; +} +EXPORT_SYMBOL(ipu_disp_set_color_key); + +/*! + * This function sets the window position of the foreground or background plane. + * modes. + * + * @param channel Input parameter for the logical channel ID. + * + * @param x_pos The X coordinate position to place window at. + * The position is relative to the top left corner. + * + * @param y_pos The Y coordinate position to place window at. + * The position is relative to the top left corner. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_disp_set_window_pos(ipu_channel_t channel, int16_t x_pos, + int16_t y_pos) +{ + u32 reg; + unsigned long lock_flags; + uint32_t flow = 0; + + if (channel == MEM_FG_SYNC) + flow = DP_SYNC; + else if (channel == MEM_FG_ASYNC0) + flow = DP_ASYNC0; + else if (channel == MEM_FG_ASYNC1) + flow = DP_ASYNC1; + else + return -EINVAL; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + __raw_writel((x_pos << 16) | y_pos, DP_FG_POS(flow)); + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); + + return 0; +} +EXPORT_SYMBOL(ipu_disp_set_window_pos); + +void ipu_disp_direct_write(ipu_channel_t channel, u32 value, u32 offset) +{ + if (channel == DIRECT_ASYNC0) + __raw_writel(value, ipu_disp_base[0] + offset); + else if (channel == DIRECT_ASYNC1) + __raw_writel(value, ipu_disp_base[1] + offset); +} +EXPORT_SYMBOL(ipu_disp_direct_write); + +void ipu_reset_disp_panel(void) +{ + uint32_t tmp; + + tmp = __raw_readl(DI_GENERAL(1)); + __raw_writel(tmp | 0x08, DI_GENERAL(1)); + msleep(10); /* tRES >= 100us */ + tmp = __raw_readl(DI_GENERAL(1)); + __raw_writel(tmp & ~0x08, DI_GENERAL(1)); + msleep(60); + + return; +} +EXPORT_SYMBOL(ipu_reset_disp_panel); diff --git a/drivers/mxc/ipu3/ipu_ic.c b/drivers/mxc/ipu3/ipu_ic.c new file mode 100644 index 000000000000..45894265a630 --- /dev/null +++ b/drivers/mxc/ipu3/ipu_ic.c @@ -0,0 +1,822 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * @file ipu_ic.c + * + * @brief IPU IC functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/videodev2.h> +#include <linux/io.h> +#include <linux/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +enum { + IC_TASK_VIEWFINDER, + IC_TASK_ENCODER, + IC_TASK_POST_PROCESSOR +}; + +static void _init_csc(uint8_t ic_task, ipu_color_space_t in_format, + ipu_color_space_t out_format, int csc_index); +static bool _calc_resize_coeffs(uint32_t inSize, uint32_t outSize, + uint32_t *resizeCoeff, + uint32_t *downsizeCoeff); + +void _ipu_vdi_set_top_field_man(bool top_field_0) +{ + uint32_t reg; + + reg = __raw_readl(VDI_C); + if (top_field_0) + reg &= ~VDI_C_TOP_FIELD_MAN_1; + else + reg |= VDI_C_TOP_FIELD_MAN_1; + __raw_writel(reg, VDI_C); +} + +void _ipu_vdi_set_motion(ipu_motion_sel motion_sel) +{ + uint32_t reg; + + reg = __raw_readl(VDI_C); + reg &= ~(VDI_C_MOT_SEL_FULL | VDI_C_MOT_SEL_MED | VDI_C_MOT_SEL_LOW); + if (motion_sel == HIGH_MOTION) + reg |= VDI_C_MOT_SEL_FULL; + else if (motion_sel == MED_MOTION) + reg |= VDI_C_MOT_SEL_MED; + else + reg |= VDI_C_MOT_SEL_LOW; + + __raw_writel(reg, VDI_C); +} + +void ic_dump_register(void) +{ + printk(KERN_DEBUG "IC_CONF = \t0x%08X\n", __raw_readl(IC_CONF)); + printk(KERN_DEBUG "IC_PRP_ENC_RSC = \t0x%08X\n", + __raw_readl(IC_PRP_ENC_RSC)); + printk(KERN_DEBUG "IC_PRP_VF_RSC = \t0x%08X\n", + __raw_readl(IC_PRP_VF_RSC)); + printk(KERN_DEBUG "IC_PP_RSC = \t0x%08X\n", __raw_readl(IC_PP_RSC)); + printk(KERN_DEBUG "IC_IDMAC_1 = \t0x%08X\n", __raw_readl(IC_IDMAC_1)); + printk(KERN_DEBUG "IC_IDMAC_2 = \t0x%08X\n", __raw_readl(IC_IDMAC_2)); + printk(KERN_DEBUG "IC_IDMAC_3 = \t0x%08X\n", __raw_readl(IC_IDMAC_3)); +} + +void _ipu_ic_enable_task(ipu_channel_t channel) +{ + uint32_t ic_conf; + + ic_conf = __raw_readl(IC_CONF); + switch (channel) { + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + ic_conf |= IC_CONF_PRPVF_EN; + break; + case MEM_VDI_PRP_VF_MEM: + ic_conf |= IC_CONF_PRPVF_EN; + break; + case MEM_ROT_VF_MEM: + ic_conf |= IC_CONF_PRPVF_ROT_EN; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + ic_conf |= IC_CONF_PRPENC_EN; + break; + case MEM_ROT_ENC_MEM: + ic_conf |= IC_CONF_PRPENC_ROT_EN; + break; + case MEM_PP_MEM: + ic_conf |= IC_CONF_PP_EN; + break; + case MEM_ROT_PP_MEM: + ic_conf |= IC_CONF_PP_ROT_EN; + break; + default: + break; + } + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_disable_task(ipu_channel_t channel) +{ + uint32_t ic_conf; + + ic_conf = __raw_readl(IC_CONF); + switch (channel) { + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_EN; + break; + case MEM_VDI_PRP_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_EN; + break; + case MEM_ROT_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_ROT_EN; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + ic_conf &= ~IC_CONF_PRPENC_EN; + break; + case MEM_ROT_ENC_MEM: + ic_conf &= ~IC_CONF_PRPENC_ROT_EN; + break; + case MEM_PP_MEM: + ic_conf &= ~IC_CONF_PP_EN; + break; + case MEM_ROT_PP_MEM: + ic_conf &= ~IC_CONF_PP_ROT_EN; + break; + default: + break; + } + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_vdi_init(ipu_channel_t channel, ipu_channel_params_t *params) +{ + uint32_t reg; + uint32_t pixel_fmt; + bool top_field_0; + + reg = ((params->mem_prp_vf_mem.in_height-1) << 16) | + (params->mem_prp_vf_mem.in_width-1); + __raw_writel(reg, VDI_FSIZE); + + /* Full motion, only vertical filter is used + Burst size is 4 accesses */ + pixel_fmt = + (params->mem_prp_vf_mem.in_pixel_fmt == + V4L2_PIX_FMT_YUV422P) ? VDI_C_CH_422 : VDI_C_CH_420; + + reg = __raw_readl(VDI_C); + reg |= pixel_fmt; + switch (channel) { + case MEM_VDI_PRP_VF_MEM: + reg |= VDI_C_BURST_SIZE2_4; + break; + case MEM_VDI_PRP_VF_MEM_P: + reg |= VDI_C_BURST_SIZE1_4 | VDI_C_VWM1_SET_1 | VDI_C_VWM1_CLR_2; + break; + case MEM_VDI_PRP_VF_MEM_N: + reg |= VDI_C_BURST_SIZE3_4 | VDI_C_VWM3_SET_1 | VDI_C_VWM3_CLR_2; + break; + default: + break; + } + __raw_writel(reg, VDI_C); + + /* MED_MOTION and LOW_MOTION algorithm that are using 3 fields + * should start presenting using the 2nd field. + */ + if (((params->mem_prp_vf_mem.field_fmt == V4L2_FIELD_INTERLACED_TB) && + (params->mem_prp_vf_mem.motion_sel != HIGH_MOTION)) || + ((params->mem_prp_vf_mem.field_fmt == V4L2_FIELD_INTERLACED_BT) && + (params->mem_prp_vf_mem.motion_sel == HIGH_MOTION))) + top_field_0 = false; + else + top_field_0 = true; + + /* Buffer selection toggle the value therefore init val is inverted. */ + _ipu_vdi_set_top_field_man(!top_field_0); + + _ipu_vdi_set_motion(params->mem_prp_vf_mem.motion_sel); + + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_RWS_EN; + __raw_writel(reg, IC_CONF); +} + +void _ipu_vdi_uninit(void) +{ + __raw_writel(0, VDI_FSIZE); + __raw_writel(0, VDI_C); +} + +void _ipu_ic_init_prpvf(ipu_channel_params_t *params, bool src_is_csi) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_prp_vf_mem.in_height, + params->mem_prp_vf_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_prp_vf_mem.in_width, + params->mem_prp_vf_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PRP_VF_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_prp_vf_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_vf_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC1 */ + _init_csc(IC_TASK_VIEWFINDER, RGB, out_fmt, 1); + ic_conf |= IC_CONF_PRPVF_CSC1; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC1 */ + _init_csc(IC_TASK_VIEWFINDER, YCbCr, RGB, 1); + ic_conf |= IC_CONF_PRPVF_CSC1; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_prp_vf_mem.graphics_combine_en) { + ic_conf |= IC_CONF_PRPVF_CMB; + + if (!(ic_conf & IC_CONF_PRPVF_CSC1)) { + /* need transparent CSC1 conversion */ + _init_csc(IC_TASK_VIEWFINDER, RGB, RGB, 1); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable RGB->RGB CSC */ + } + in_fmt = format_to_colorspace(params->mem_prp_vf_mem.in_g_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_vf_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC2 */ + _init_csc(IC_TASK_VIEWFINDER, RGB, out_fmt, 2); + ic_conf |= IC_CONF_PRPVF_CSC2; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC2 */ + _init_csc(IC_TASK_VIEWFINDER, YCbCr, RGB, 2); + ic_conf |= IC_CONF_PRPVF_CSC2; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_prp_vf_mem.global_alpha_en) { + ic_conf |= IC_CONF_IC_GLB_LOC_A; + reg = __raw_readl(IC_CMBP_1); + reg &= ~(0xff); + reg |= params->mem_prp_vf_mem.alpha; + __raw_writel(reg, IC_CMBP_1); + } else + ic_conf &= ~IC_CONF_IC_GLB_LOC_A; + + if (params->mem_prp_vf_mem.key_color_en) { + ic_conf |= IC_CONF_KEY_COLOR_EN; + __raw_writel(params->mem_prp_vf_mem.key_color, + IC_CMBP_2); + } else + ic_conf &= ~IC_CONF_KEY_COLOR_EN; + } else { + ic_conf &= ~IC_CONF_PRPVF_CMB; + } + + if (src_is_csi) + ic_conf &= ~IC_CONF_RWS_EN; + else + ic_conf |= IC_CONF_RWS_EN; + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_prpvf(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPVF_EN | IC_CONF_PRPVF_CMB | + IC_CONF_PRPVF_CSC2 | IC_CONF_PRPVF_CSC1); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_vf(ipu_channel_params_t *params) +{ +} + +void _ipu_ic_uninit_rotate_vf(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_PRPVF_ROT_EN; + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_prpenc(ipu_channel_params_t *params, bool src_is_csi) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_prp_enc_mem.in_height, + params->mem_prp_enc_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_prp_enc_mem.in_width, + params->mem_prp_enc_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PRP_ENC_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_prp_enc_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_enc_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC1 */ + _init_csc(IC_TASK_ENCODER, RGB, out_fmt, 1); + ic_conf |= IC_CONF_PRPENC_CSC1; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC1 */ + _init_csc(IC_TASK_ENCODER, YCbCr, RGB, 1); + ic_conf |= IC_CONF_PRPENC_CSC1; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (src_is_csi) + ic_conf &= ~IC_CONF_RWS_EN; + else + ic_conf |= IC_CONF_RWS_EN; + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_prpenc(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPENC_EN | IC_CONF_PRPENC_CSC1); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_enc(ipu_channel_params_t *params) +{ +} + +void _ipu_ic_uninit_rotate_enc(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPENC_ROT_EN); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_pp(ipu_channel_params_t *params) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_pp_mem.in_height, + params->mem_pp_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_pp_mem.in_width, + params->mem_pp_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PP_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_pp_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_pp_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC1 */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, out_fmt, 1); + ic_conf |= IC_CONF_PP_CSC1; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC1 */ + _init_csc(IC_TASK_POST_PROCESSOR, YCbCr, RGB, 1); + ic_conf |= IC_CONF_PP_CSC1; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_pp_mem.graphics_combine_en) { + ic_conf |= IC_CONF_PP_CMB; + + if (!(ic_conf & IC_CONF_PP_CSC1)) { + /* need transparent CSC1 conversion */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, RGB, 1); + ic_conf |= IC_CONF_PP_CSC1; /* Enable RGB->RGB CSC */ + } + + in_fmt = format_to_colorspace(params->mem_pp_mem.in_g_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_pp_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC2 */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, out_fmt, 2); + ic_conf |= IC_CONF_PP_CSC2; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC2 */ + _init_csc(IC_TASK_POST_PROCESSOR, YCbCr, RGB, 2); + ic_conf |= IC_CONF_PP_CSC2; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_pp_mem.global_alpha_en) { + ic_conf |= IC_CONF_IC_GLB_LOC_A; + reg = __raw_readl(IC_CMBP_1); + reg &= ~(0xff00); + reg |= (params->mem_pp_mem.alpha << 8); + __raw_writel(reg, IC_CMBP_1); + } else + ic_conf &= ~IC_CONF_IC_GLB_LOC_A; + + if (params->mem_pp_mem.key_color_en) { + ic_conf |= IC_CONF_KEY_COLOR_EN; + __raw_writel(params->mem_pp_mem.key_color, + IC_CMBP_2); + } else + ic_conf &= ~IC_CONF_KEY_COLOR_EN; + } else { + ic_conf &= ~IC_CONF_PP_CMB; + } + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_pp(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PP_EN | IC_CONF_PP_CSC1 | IC_CONF_PP_CSC2 | + IC_CONF_PP_CMB); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_pp(ipu_channel_params_t *params) +{ +} + +void _ipu_ic_uninit_rotate_pp(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_PP_ROT_EN; + __raw_writel(reg, IC_CONF); +} + +int _ipu_ic_idma_init(int dma_chan, uint16_t width, uint16_t height, + int burst_size, ipu_rotate_mode_t rot) +{ + u32 ic_idmac_1, ic_idmac_2, ic_idmac_3; + u32 temp_rot = bitrev8(rot) >> 5; + bool need_hor_flip = false; + + if ((burst_size != 8) && (burst_size != 16)) { + dev_dbg(g_ipu_dev, "Illegal burst length for IC\n"); + return -EINVAL; + } + + width--; + height--; + + if (temp_rot & 0x2) /* Need horizontal flip */ + need_hor_flip = true; + + ic_idmac_1 = __raw_readl(IC_IDMAC_1); + ic_idmac_2 = __raw_readl(IC_IDMAC_2); + ic_idmac_3 = __raw_readl(IC_IDMAC_3); + if (dma_chan == 22) { /* PP output - CB2 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB2_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB2_BURST_16; + + if (need_hor_flip) + ic_idmac_1 |= IC_IDMAC_1_PP_FLIP_RS; + else + ic_idmac_1 &= ~IC_IDMAC_1_PP_FLIP_RS; + + ic_idmac_2 &= ~IC_IDMAC_2_PP_HEIGHT_MASK; + ic_idmac_2 |= height << IC_IDMAC_2_PP_HEIGHT_OFFSET; + + ic_idmac_3 &= ~IC_IDMAC_3_PP_WIDTH_MASK; + ic_idmac_3 |= width << IC_IDMAC_3_PP_WIDTH_OFFSET; + + } else if (dma_chan == 11) { /* PP Input - CB5 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB5_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB5_BURST_16; + } else if (dma_chan == 47) { /* PP Rot input */ + ic_idmac_1 &= ~IC_IDMAC_1_PP_ROT_MASK; + ic_idmac_1 |= temp_rot << IC_IDMAC_1_PP_ROT_OFFSET; + } + + if (dma_chan == 12) { /* PRP Input - CB6 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB6_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB6_BURST_16; + } + + if (dma_chan == 20) { /* PRP ENC output - CB0 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB0_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB0_BURST_16; + + if (need_hor_flip) + ic_idmac_1 |= IC_IDMAC_1_PRPENC_FLIP_RS; + else + ic_idmac_1 &= ~IC_IDMAC_1_PRPENC_FLIP_RS; + + ic_idmac_2 &= ~IC_IDMAC_2_PRPENC_HEIGHT_MASK; + ic_idmac_2 |= height << IC_IDMAC_2_PRPENC_HEIGHT_OFFSET; + + ic_idmac_3 &= ~IC_IDMAC_3_PRPENC_WIDTH_MASK; + ic_idmac_3 |= width << IC_IDMAC_3_PRPENC_WIDTH_OFFSET; + + } else if (dma_chan == 45) { /* PRP ENC Rot input */ + ic_idmac_1 &= ~IC_IDMAC_1_PRPENC_ROT_MASK; + ic_idmac_1 |= temp_rot << IC_IDMAC_1_PRPENC_ROT_OFFSET; + } + + if (dma_chan == 21) { /* PRP VF output - CB1 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB1_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB1_BURST_16; + + if (need_hor_flip) + ic_idmac_1 |= IC_IDMAC_1_PRPVF_FLIP_RS; + else + ic_idmac_1 &= ~IC_IDMAC_1_PRPVF_FLIP_RS; + + ic_idmac_2 &= ~IC_IDMAC_2_PRPVF_HEIGHT_MASK; + ic_idmac_2 |= height << IC_IDMAC_2_PRPVF_HEIGHT_OFFSET; + + ic_idmac_3 &= ~IC_IDMAC_3_PRPVF_WIDTH_MASK; + ic_idmac_3 |= width << IC_IDMAC_3_PRPVF_WIDTH_OFFSET; + + } else if (dma_chan == 46) { /* PRP VF Rot input */ + ic_idmac_1 &= ~IC_IDMAC_1_PRPVF_ROT_MASK; + ic_idmac_1 |= temp_rot << IC_IDMAC_1_PRPVF_ROT_OFFSET; + } + + if (dma_chan == 14) { /* PRP VF graphics combining input - CB3 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB3_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB3_BURST_16; + } else if (dma_chan == 15) { /* PP graphics combining input - CB4 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB4_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB4_BURST_16; + } + + __raw_writel(ic_idmac_1, IC_IDMAC_1); + __raw_writel(ic_idmac_2, IC_IDMAC_2); + __raw_writel(ic_idmac_3, IC_IDMAC_3); + + return 0; +} + +static void _init_csc(uint8_t ic_task, ipu_color_space_t in_format, + ipu_color_space_t out_format, int csc_index) +{ + +/* Y = R * .299 + G * .587 + B * .114; + U = R * -.169 + G * -.332 + B * .500 + 128.; + V = R * .500 + G * -.419 + B * -.0813 + 128.;*/ + static const uint32_t rgb2ycbcr_coeff[4][3] = { + {0x004D, 0x0096, 0x001D}, + {0x01D5, 0x01AB, 0x0080}, + {0x0080, 0x0195, 0x01EB}, + {0x0000, 0x0200, 0x0200}, /* A0, A1, A2 */ + }; + + /* transparent RGB->RGB matrix for combining + */ + static const uint32_t rgb2rgb_coeff[4][3] = { + {0x0080, 0x0000, 0x0000}, + {0x0000, 0x0080, 0x0000}, + {0x0000, 0x0000, 0x0080}, + {0x0000, 0x0000, 0x0000}, /* A0, A1, A2 */ + }; + +/* R = (1.164 * (Y - 16)) + (1.596 * (Cr - 128)); + G = (1.164 * (Y - 16)) - (0.392 * (Cb - 128)) - (0.813 * (Cr - 128)); + B = (1.164 * (Y - 16)) + (2.017 * (Cb - 128); */ + static const uint32_t ycbcr2rgb_coeff[4][3] = { + {149, 0, 204}, + {149, 462, 408}, + {149, 255, 0}, + {8192 - 446, 266, 8192 - 554}, /* A0, A1, A2 */ + }; + + uint32_t param; + uint32_t *base = NULL; + + if (ic_task == IC_TASK_ENCODER) { + base = ipu_tpmem_base + 0x2008 / 4; + } else if (ic_task == IC_TASK_VIEWFINDER) { + if (csc_index == 1) + base = ipu_tpmem_base + 0x4028 / 4; + else + base = ipu_tpmem_base + 0x4040 / 4; + } else if (ic_task == IC_TASK_POST_PROCESSOR) { + if (csc_index == 1) + base = ipu_tpmem_base + 0x6060 / 4; + else + base = ipu_tpmem_base + 0x6078 / 4; + } else { + BUG(); + } + + if ((in_format == YCbCr) && (out_format == RGB)) { + /* Init CSC (YCbCr->RGB) */ + param = (ycbcr2rgb_coeff[3][0] << 27) | + (ycbcr2rgb_coeff[0][0] << 18) | + (ycbcr2rgb_coeff[1][1] << 9) | ycbcr2rgb_coeff[2][2]; + __raw_writel(param, base++); + /* scale = 2, sat = 0 */ + param = (ycbcr2rgb_coeff[3][0] >> 5) | (2L << (40 - 32)); + __raw_writel(param, base++); + + param = (ycbcr2rgb_coeff[3][1] << 27) | + (ycbcr2rgb_coeff[0][1] << 18) | + (ycbcr2rgb_coeff[1][0] << 9) | ycbcr2rgb_coeff[2][0]; + __raw_writel(param, base++); + param = (ycbcr2rgb_coeff[3][1] >> 5); + __raw_writel(param, base++); + + param = (ycbcr2rgb_coeff[3][2] << 27) | + (ycbcr2rgb_coeff[0][2] << 18) | + (ycbcr2rgb_coeff[1][2] << 9) | ycbcr2rgb_coeff[2][1]; + __raw_writel(param, base++); + param = (ycbcr2rgb_coeff[3][2] >> 5); + __raw_writel(param, base++); + } else if ((in_format == RGB) && (out_format == YCbCr)) { + /* Init CSC (RGB->YCbCr) */ + param = (rgb2ycbcr_coeff[3][0] << 27) | + (rgb2ycbcr_coeff[0][0] << 18) | + (rgb2ycbcr_coeff[1][1] << 9) | rgb2ycbcr_coeff[2][2]; + __raw_writel(param, base++); + /* scale = 1, sat = 0 */ + param = (rgb2ycbcr_coeff[3][0] >> 5) | (1UL << 8); + __raw_writel(param, base++); + + param = (rgb2ycbcr_coeff[3][1] << 27) | + (rgb2ycbcr_coeff[0][1] << 18) | + (rgb2ycbcr_coeff[1][0] << 9) | rgb2ycbcr_coeff[2][0]; + __raw_writel(param, base++); + param = (rgb2ycbcr_coeff[3][1] >> 5); + __raw_writel(param, base++); + + param = (rgb2ycbcr_coeff[3][2] << 27) | + (rgb2ycbcr_coeff[0][2] << 18) | + (rgb2ycbcr_coeff[1][2] << 9) | rgb2ycbcr_coeff[2][1]; + __raw_writel(param, base++); + param = (rgb2ycbcr_coeff[3][2] >> 5); + __raw_writel(param, base++); + } else if ((in_format == RGB) && (out_format == RGB)) { + /* Init CSC */ + param = + (rgb2rgb_coeff[3][0] << 27) | (rgb2rgb_coeff[0][0] << 18) | + (rgb2rgb_coeff[1][1] << 9) | rgb2rgb_coeff[2][2]; + __raw_writel(param, base++); + /* scale = 2, sat = 0 */ + param = (rgb2rgb_coeff[3][0] >> 5) | (2UL << 8); + __raw_writel(param, base++); + + param = + (rgb2rgb_coeff[3][1] << 27) | (rgb2rgb_coeff[0][1] << 18) | + (rgb2rgb_coeff[1][0] << 9) | rgb2rgb_coeff[2][0]; + __raw_writel(param, base++); + param = (rgb2rgb_coeff[3][1] >> 5); + __raw_writel(param, base++); + + param = + (rgb2rgb_coeff[3][2] << 27) | (rgb2rgb_coeff[0][2] << 18) | + (rgb2rgb_coeff[1][2] << 9) | rgb2rgb_coeff[2][1]; + __raw_writel(param, base++); + param = (rgb2rgb_coeff[3][2] >> 5); + __raw_writel(param, base++); + } else { + dev_err(g_ipu_dev, "Unsupported color space conversion\n"); + } +} + +static bool _calc_resize_coeffs(uint32_t inSize, uint32_t outSize, + uint32_t *resizeCoeff, + uint32_t *downsizeCoeff) +{ + uint32_t tempSize; + uint32_t tempDownsize; + + /* Input size cannot be more than 4096 */ + /* Output size cannot be more than 1024 */ + if ((inSize > 4096) || (outSize > 1024)) + return false; + + /* Cannot downsize more than 8:1 */ + if ((outSize << 3) < inSize) + return false; + + /* Compute downsizing coefficient */ + /* Output of downsizing unit cannot be more than 1024 */ + tempDownsize = 0; + tempSize = inSize; + while (((tempSize > 1024) || (tempSize >= outSize * 2)) && + (tempDownsize < 2)) { + tempSize >>= 1; + tempDownsize++; + } + *downsizeCoeff = tempDownsize; + + /* compute resizing coefficient using the following equation: + resizeCoeff = M*(SI -1)/(SO - 1) + where M = 2^13, SI - input size, SO - output size */ + *resizeCoeff = (8192L * (tempSize - 1)) / (outSize - 1); + if (*resizeCoeff >= 16384L) { + dev_err(g_ipu_dev, "Warning! Overflow on resize coeff.\n"); + *resizeCoeff = 0x3FFF; + } + + dev_dbg(g_ipu_dev, "resizing from %u -> %u pixels, " + "downsize=%u, resize=%u.%lu (reg=%u)\n", inSize, outSize, + *downsizeCoeff, (*resizeCoeff >= 8192L) ? 1 : 0, + ((*resizeCoeff & 0x1FFF) * 10000L) / 8192L, *resizeCoeff); + + return true; +} + +void _ipu_vdi_toggle_top_field_man() +{ + uint32_t reg; + uint32_t mask_reg; + + reg = __raw_readl(VDI_C); + mask_reg = reg & VDI_C_TOP_FIELD_MAN_1; + if (mask_reg == VDI_C_TOP_FIELD_MAN_1) + reg &= ~VDI_C_TOP_FIELD_MAN_1; + else + reg |= VDI_C_TOP_FIELD_MAN_1; + + __raw_writel(reg, VDI_C); +} + diff --git a/drivers/mxc/ipu3/ipu_param_mem.h b/drivers/mxc/ipu3/ipu_param_mem.h new file mode 100644 index 000000000000..067ace19563c --- /dev/null +++ b/drivers/mxc/ipu3/ipu_param_mem.h @@ -0,0 +1,391 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __INCLUDE_IPU_PARAM_MEM_H__ +#define __INCLUDE_IPU_PARAM_MEM_H__ + +#include <linux/types.h> +#include <linux/bitrev.h> + +extern u32 *ipu_cpmem_base; + +struct ipu_ch_param_word { + uint32_t data[5]; + uint32_t res[3]; +}; + +struct ipu_ch_param { + struct ipu_ch_param_word word[2]; +}; + +#define ipu_ch_param_addr(ch) (((struct ipu_ch_param *)ipu_cpmem_base) + (ch)) + +#define _param_word(base, w) \ + (((struct ipu_ch_param *)(base))->word[(w)].data) + +#define ipu_ch_param_set_field(base, w, bit, size, v) { \ + int i = (bit) / 32; \ + int off = (bit) % 32; \ + _param_word(base, w)[i] |= (v) << off; \ + if (((bit)+(size)-1)/32 > i) { \ + _param_word(base, w)[i + 1] |= (v) >> (off ? (32 - off) : 0); \ + } \ +} + +#define ipu_ch_param_mod_field(base, w, bit, size, v) { \ + int i = (bit) / 32; \ + int off = (bit) % 32; \ + u32 mask = (1UL << size) - 1; \ + u32 temp = _param_word(base, w)[i]; \ + temp &= ~(mask << off); \ + _param_word(base, w)[i] = temp | (v) << off; \ + if (((bit)+(size)-1)/32 > i) { \ + temp = _param_word(base, w)[i + 1]; \ + temp &= ~(mask >> (32 - off)); \ + _param_word(base, w)[i + 1] = \ + temp | ((v) >> (off ? (32 - off) : 0)); \ + } \ +} + +#define ipu_ch_param_read_field(base, w, bit, size) ({ \ + u32 temp2; \ + int i = (bit) / 32; \ + int off = (bit) % 32; \ + u32 mask = (1UL << size) - 1; \ + u32 temp1 = _param_word(base, w)[i]; \ + temp1 = mask & (temp1 >> off); \ + if (((bit)+(size)-1)/32 > i) { \ + temp2 = _param_word(base, w)[i + 1]; \ + temp2 &= mask >> (off ? (32 - off) : 0); \ + temp1 |= temp2 << (off ? (32 - off) : 0); \ + } \ + temp1; \ +}) + +static inline void _ipu_ch_params_set_packing(struct ipu_ch_param *p, + int red_width, int red_offset, + int green_width, int green_offset, + int blue_width, int blue_offset, + int alpha_width, int alpha_offset) +{ + /* Setup red width and offset */ + ipu_ch_param_set_field(p, 1, 116, 3, red_width - 1); + ipu_ch_param_set_field(p, 1, 128, 5, red_offset); + /* Setup green width and offset */ + ipu_ch_param_set_field(p, 1, 119, 3, green_width - 1); + ipu_ch_param_set_field(p, 1, 133, 5, green_offset); + /* Setup blue width and offset */ + ipu_ch_param_set_field(p, 1, 122, 3, blue_width - 1); + ipu_ch_param_set_field(p, 1, 138, 5, blue_offset); + /* Setup alpha width and offset */ + ipu_ch_param_set_field(p, 1, 125, 3, alpha_width - 1); + ipu_ch_param_set_field(p, 1, 143, 5, alpha_offset); +} + +static inline void _ipu_ch_param_dump(int ch) +{ + struct ipu_ch_param *p = ipu_ch_param_addr(ch); + pr_debug("ch %d word 0 - %08X %08X %08X %08X %08X\n", ch, + p->word[0].data[0], p->word[0].data[1], p->word[0].data[2], + p->word[0].data[3], p->word[0].data[4]); + pr_debug("ch %d word 1 - %08X %08X %08X %08X %08X\n", ch, + p->word[1].data[0], p->word[1].data[1], p->word[1].data[2], + p->word[1].data[3], p->word[1].data[4]); + pr_debug("PFS 0x%x, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 85, 4)); + pr_debug("BPP 0x%x, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 0, 107, 3)); + pr_debug("NPB 0x%x\n", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 78, 7)); + + pr_debug("FW %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 0, 125, 13)); + pr_debug("FH %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 0, 138, 12)); + pr_debug("Stride %d\n", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 102, 14)); + + pr_debug("Width0 %d+1, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 116, 3)); + pr_debug("Width1 %d+1, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 119, 3)); + pr_debug("Width2 %d+1, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 122, 3)); + pr_debug("Width3 %d+1, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 125, 3)); + pr_debug("Offset0 %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 128, 5)); + pr_debug("Offset1 %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 133, 5)); + pr_debug("Offset2 %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 138, 5)); + pr_debug("Offset3 %d\n", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 143, 5)); +} + +static inline void _ipu_ch_param_init(int ch, + uint32_t pixel_fmt, uint32_t width, + uint32_t height, uint32_t stride, + uint32_t u, uint32_t v, + uint32_t uv_stride, dma_addr_t addr0, + dma_addr_t addr1) +{ + uint32_t u_offset = 0; + uint32_t v_offset = 0; + struct ipu_ch_param params; + + memset(¶ms, 0, sizeof(params)); + + ipu_ch_param_set_field(¶ms, 0, 125, 13, width - 1); + + if ((ch == 8) || (ch == 9) || (ch == 10)) { + ipu_ch_param_set_field(¶ms, 0, 138, 12, (height / 2) - 1); + ipu_ch_param_set_field(¶ms, 1, 102, 14, (stride * 2) - 1); + } else { + ipu_ch_param_set_field(¶ms, 0, 138, 12, height - 1); + ipu_ch_param_set_field(¶ms, 1, 102, 14, stride - 1); + } + + ipu_ch_param_set_field(¶ms, 1, 0, 29, addr0 >> 3); + ipu_ch_param_set_field(¶ms, 1, 29, 29, addr1 >> 3); + + switch (pixel_fmt) { + case IPU_PIX_FMT_GENERIC: + /*Represents 8-bit Generic data */ + ipu_ch_param_set_field(¶ms, 0, 107, 3, 5); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 6); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 63); /* burst size */ + + break; + case IPU_PIX_FMT_GENERIC_32: + /*Represents 32-bit Generic data */ + break; + case IPU_PIX_FMT_RGB565: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 3); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 5, 0, 6, 5, 5, 11, 8, 16); + break; + case IPU_PIX_FMT_BGR24: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 1); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 19); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 8, 0, 8, 8, 8, 16, 8, 24); + break; + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_YUV444: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 1); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 19); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 8, 16, 8, 8, 8, 0, 8, 24); + break; + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_BGR32: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 0); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 8, 8, 8, 16, 8, 24, 8, 0); + break; + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_RGB32: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 0); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 8, 24, 8, 16, 8, 8, 8, 0); + break; + case IPU_PIX_FMT_ABGR32: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 0); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + + _ipu_ch_params_set_packing(¶ms, 8, 0, 8, 8, 8, 16, 8, 24); + break; + case IPU_PIX_FMT_UYVY: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 3); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 0xA); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + break; + case IPU_PIX_FMT_YUYV: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 3); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 0x8); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + break; + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YUV420P: + ipu_ch_param_set_field(¶ms, 1, 85, 4, 2); /* pix format */ + + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + u_offset = stride * height; + v_offset = u_offset + (uv_stride * height / 2); + if ((ch == 8) || (ch == 9) || (ch == 10)) { + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + uv_stride = uv_stride*2; + } else { + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + } + break; + case IPU_PIX_FMT_YVU422P: + /* BPP & pixel format */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 1); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + v_offset = (v == 0) ? stride * height : v; + u_offset = (u == 0) ? v_offset + v_offset / 2 : u; + break; + case IPU_PIX_FMT_YUV422P: + /* BPP & pixel format */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 1); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + u_offset = (u == 0) ? stride * height : u; + v_offset = (v == 0) ? u_offset + u_offset / 2 : v; + break; + case IPU_PIX_FMT_NV12: + /* BPP & pixel format */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 4); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + uv_stride = stride; + u_offset = (u == 0) ? stride * height : u; + break; + default: + dev_err(g_ipu_dev, "mxc ipu: unimplemented pixel format\n"); + break; + } + /*set burst size to 16*/ + + + if (uv_stride) + ipu_ch_param_set_field(¶ms, 1, 128, 14, uv_stride - 1); + + if (u > u_offset) + u_offset = u; + + if (v > v_offset) + v_offset = v; + + ipu_ch_param_set_field(¶ms, 0, 46, 22, u_offset / 8); + ipu_ch_param_set_field(¶ms, 0, 68, 22, v_offset / 8); + + pr_debug("initializing idma ch %d @ %p\n", ch, ipu_ch_param_addr(ch)); + memcpy(ipu_ch_param_addr(ch), ¶ms, sizeof(params)); +}; + +static inline void _ipu_ch_param_set_burst_size(uint32_t ch, + uint16_t burst_pixels) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 78, 7, + burst_pixels - 1); +}; + +static inline int _ipu_ch_param_get_burst_size(uint32_t ch) +{ + return ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 78, 7) + 1; +}; + +static inline int _ipu_ch_param_get_bpp(uint32_t ch) +{ + return ipu_ch_param_read_field(ipu_ch_param_addr(ch), 0, 107, 3); +}; + +static inline void _ipu_ch_param_set_buffer(uint32_t ch, int bufNum, + dma_addr_t phyaddr) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 29 * bufNum, 29, + phyaddr / 8); +}; + +static inline void _ipu_ch_param_set_rotation(uint32_t ch, + ipu_rotate_mode_t rot) +{ + u32 temp_rot = bitrev8(rot) >> 5; + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 0, 119, 3, temp_rot); +}; + +static inline void _ipu_ch_param_set_block_mode(uint32_t ch) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 0, 117, 2, 1); +}; + +static inline void _ipu_ch_param_set_alpha_use_separate_channel(uint32_t ch, + bool option) +{ + if (option) { + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 89, 1, 1); + } else { + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 89, 1, 0); + } +}; + +static inline void _ipu_ch_param_set_alpha_condition_read(uint32_t ch) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 149, 1, 1); +}; + +static inline void _ipu_ch_param_set_alpha_buffer_memory(uint32_t ch) +{ + int alp_mem_idx; + + switch (ch) { + case 14: /* PRP graphic */ + alp_mem_idx = 0; + break; + case 15: /* PP graphic */ + alp_mem_idx = 1; + break; + case 23: /* DP BG SYNC graphic */ + alp_mem_idx = 4; + break; + case 27: /* DP FG SYNC graphic */ + alp_mem_idx = 2; + break; + default: + dev_err(g_ipu_dev, "unsupported correlative channel of local " + "alpha channel\n"); + return; + } + + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 90, 3, alp_mem_idx); +}; + +static inline void _ipu_ch_param_set_interlaced_scan(uint32_t ch) +{ + u32 stride; + ipu_ch_param_set_field(ipu_ch_param_addr(ch), 0, 113, 1, 1); + stride = ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 102, 14) + 1; + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 58, 20, stride / 8); + stride *= 2; + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 102, 14, stride - 1); +}; + +static inline void _ipu_ch_param_set_high_priority(uint32_t ch) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 93, 2, 1); +}; + +static inline void _ipu_ch_params_set_alpha_width(uint32_t ch, int alpha_width) +{ + ipu_ch_param_set_field(ipu_ch_param_addr(ch), 1, 125, 3, alpha_width - 1); +} + +#endif diff --git a/drivers/mxc/ipu3/ipu_prv.h b/drivers/mxc/ipu3/ipu_prv.h new file mode 100644 index 000000000000..c9884068283f --- /dev/null +++ b/drivers/mxc/ipu3/ipu_prv.h @@ -0,0 +1,92 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __INCLUDE_IPU_PRV_H__ +#define __INCLUDE_IPU_PRV_H__ + +#include <linux/types.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <mach/hardware.h> + +/* Globals */ +extern struct device *g_ipu_dev; +extern spinlock_t ipu_lock; +extern bool g_ipu_clk_enabled; +extern struct clk *g_ipu_clk; +extern struct clk *g_di_clk[2]; +extern struct clk *g_csi_clk[2]; +extern unsigned char g_dc_di_assignment[]; +extern int g_ipu_hw_rev; + +#define IDMA_CHAN_INVALID 0xFF + +struct ipu_channel { + u8 video_in_dma; + u8 alpha_in_dma; + u8 graph_in_dma; + u8 out_dma; +}; + +int register_ipu_device(void); +ipu_color_space_t format_to_colorspace(uint32_t fmt); +bool ipu_pixel_format_has_alpha(uint32_t fmt); + +void ipu_dump_registers(void); + +uint32_t _ipu_channel_status(ipu_channel_t channel); + +void _ipu_init_dc_mappings(void); +int _ipu_dp_init(ipu_channel_t channel, uint32_t in_pixel_fmt, + uint32_t out_pixel_fmt); +void _ipu_dp_uninit(ipu_channel_t channel); +void _ipu_dc_init(int dc_chan, int di, bool interlaced); +void _ipu_dc_uninit(int dc_chan); +void _ipu_dp_dc_enable(ipu_channel_t channel); +void _ipu_dp_dc_disable(ipu_channel_t channel, bool swap); +void _ipu_dmfc_init(void); +void _ipu_dmfc_set_wait4eot(int dma_chan, int width); +int _ipu_chan_is_interlaced(ipu_channel_t channel); + +void _ipu_ic_enable_task(ipu_channel_t channel); +void _ipu_ic_disable_task(ipu_channel_t channel); +void _ipu_ic_init_prpvf(ipu_channel_params_t *params, bool src_is_csi); +void _ipu_vdi_init(ipu_channel_t channel, ipu_channel_params_t *params); +void _ipu_vdi_uninit(void); +void _ipu_ic_uninit_prpvf(void); +void _ipu_ic_init_rotate_vf(ipu_channel_params_t *params); +void _ipu_ic_uninit_rotate_vf(void); +void _ipu_ic_init_csi(ipu_channel_params_t *params); +void _ipu_ic_uninit_csi(void); +void _ipu_ic_init_prpenc(ipu_channel_params_t *params, bool src_is_csi); +void _ipu_ic_uninit_prpenc(void); +void _ipu_ic_init_rotate_enc(ipu_channel_params_t *params); +void _ipu_ic_uninit_rotate_enc(void); +void _ipu_ic_init_pp(ipu_channel_params_t *params); +void _ipu_ic_uninit_pp(void); +void _ipu_ic_init_rotate_pp(ipu_channel_params_t *params); +void _ipu_ic_uninit_rotate_pp(void); +int _ipu_ic_idma_init(int dma_chan, uint16_t width, uint16_t height, + int burst_size, ipu_rotate_mode_t rot); +void _ipu_vdi_toggle_top_field_man(void); +int _ipu_csi_init(ipu_channel_t channel, uint32_t csi); +void ipu_csi_set_test_generator(bool active, uint32_t r_value, + uint32_t g_value, uint32_t b_value, + uint32_t pix_clk, uint32_t csi); +void _ipu_csi_ccir_err_detection_enable(uint32_t csi); +void _ipu_csi_ccir_err_detection_disable(uint32_t csi); +void _ipu_smfc_init(ipu_channel_t channel, uint32_t mipi_id, uint32_t csi); +void _ipu_smfc_set_burst_size(ipu_channel_t channel, uint32_t bs); +void _ipu_dp_set_csc_coefficients(ipu_channel_t channel, int32_t param[][3]); + +#endif /* __INCLUDE_IPU_PRV_H__ */ diff --git a/drivers/mxc/ipu3/ipu_regs.h b/drivers/mxc/ipu3/ipu_regs.h new file mode 100644 index 000000000000..f4b266aef794 --- /dev/null +++ b/drivers/mxc/ipu3/ipu_regs.h @@ -0,0 +1,651 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * @file ipu_regs.h + * + * @brief IPU Register definitions + * + * @ingroup IPU + */ +#ifndef __IPU_REGS_INCLUDED__ +#define __IPU_REGS_INCLUDED__ + +#define IPU_DISP0_BASE 0x00000000 +#define IPU_MCU_T_DEFAULT 8 +#define IPU_DISP1_BASE (IPU_MCU_T_DEFAULT << 25) +#define IPU_CM_REG_BASE 0x1E000000 +#define IPU_IDMAC_REG_BASE 0x1E008000 +#define IPU_ISP_REG_BASE 0x1E010000 +#define IPU_DP_REG_BASE 0x1E018000 +#define IPU_IC_REG_BASE 0x1E020000 +#define IPU_IRT_REG_BASE 0x1E028000 +#define IPU_CSI0_REG_BASE 0x1E030000 +#define IPU_CSI1_REG_BASE 0x1E038000 +#define IPU_DI0_REG_BASE 0x1E040000 +#define IPU_DI1_REG_BASE 0x1E048000 +#define IPU_SMFC_REG_BASE 0x1E050000 +#define IPU_DC_REG_BASE 0x1E058000 +#define IPU_DMFC_REG_BASE 0x1E060000 +#define IPU_CPMEM_REG_BASE 0x1F000000 +#define IPU_LUT_REG_BASE 0x1F020000 +#define IPU_SRM_REG_BASE 0x1F040000 +#define IPU_TPM_REG_BASE 0x1F060000 +#define IPU_DC_TMPL_REG_BASE 0x1F080000 +#define IPU_ISP_TBPR_REG_BASE 0x1F0C0000 +#define IPU_VDI_REG_BASE 0x1E068000 + + +extern u32 *ipu_cm_reg; +extern u32 *ipu_idmac_reg; +extern u32 *ipu_dp_reg; +extern u32 *ipu_ic_reg; +extern u32 *ipu_dc_reg; +extern u32 *ipu_dc_tmpl_reg; +extern u32 *ipu_dmfc_reg; +extern u32 *ipu_di_reg[]; +extern u32 *ipu_smfc_reg; +extern u32 *ipu_csi_reg[]; +extern u32 *ipu_tpmem_base; +extern u32 *ipu_disp_base[]; +extern u32 *ipu_vdi_reg; + +/* Register addresses */ +/* IPU Common registers */ +#define IPU_CONF (ipu_cm_reg) + +#define IPU_SRM_PRI1 (ipu_cm_reg + 0x00A0/4) +#define IPU_SRM_PRI2 (ipu_cm_reg + 0x00A4/4) +#define IPU_FS_PROC_FLOW1 (ipu_cm_reg + 0x00A8/4) +#define IPU_FS_PROC_FLOW2 (ipu_cm_reg + 0x00AC/4) +#define IPU_FS_PROC_FLOW3 (ipu_cm_reg + 0x00B0/4) +#define IPU_FS_DISP_FLOW1 (ipu_cm_reg + 0x00B4/4) +#define IPU_FS_DISP_FLOW2 (ipu_cm_reg + 0x00B8/4) +#define IPU_SKIP (ipu_cm_reg + 0x00BC/4) +#define IPU_DISP_ALT_CONF (ipu_cm_reg + 0x00C0/4) +#define IPU_DISP_GEN (ipu_cm_reg + 0x00C4/4) +#define IPU_DISP_ALT1 (ipu_cm_reg + 0x00C8/4) +#define IPU_DISP_ALT2 (ipu_cm_reg + 0x00CC/4) +#define IPU_DISP_ALT3 (ipu_cm_reg + 0x00D0/4) +#define IPU_DISP_ALT4 (ipu_cm_reg + 0x00D4/4) +#define IPU_SNOOP (ipu_cm_reg + 0x00D8/4) +#define IPU_MEM_RST (ipu_cm_reg + 0x00DC/4) +#define IPU_PM (ipu_cm_reg + 0x00E0/4) +#define IPU_GPR (ipu_cm_reg + 0x00E4/4) +#define IPU_CHA_DB_MODE_SEL(ch) (ipu_cm_reg + 0x0150/4 + (ch / 32)) +#define IPU_ALT_CHA_DB_MODE_SEL(ch) (ipu_cm_reg + 0x0168/4 + (ch / 32)) +#define IPU_CHA_CUR_BUF(ch) ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x023C/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0124/4 + (ch / 32)); }) +#define IPU_ALT_CUR_BUF0 ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0244/4) : \ + (ipu_cm_reg + 0x012C/4); }) +#define IPU_ALT_CUR_BUF1 ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0248/4) : \ + (ipu_cm_reg + 0x0130/4); }) +#define IPU_SRM_STAT ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x024C/4) : \ + (ipu_cm_reg + 0x0134/4); }) +#define IPU_PROC_TASK_STAT ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0250/4) : \ + (ipu_cm_reg + 0x0138/4); }) +#define IPU_DISP_TASK_STAT ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0254/4) : \ + (ipu_cm_reg + 0x013C/4); }) +#define IPU_CHA_BUF0_RDY(ch) ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0268/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0140/4 + (ch / 32)); }) +#define IPU_CHA_BUF1_RDY(ch) ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0270/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0148/4 + (ch / 32)); }) +#define IPU_ALT_CHA_BUF0_RDY(ch) ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0278/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0158/4 + (ch / 32)); }) +#define IPU_ALT_CHA_BUF1_RDY(ch) ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0280/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0160/4 + (ch / 32)); }) + +#define IPU_INT_CTRL(n) (ipu_cm_reg + 0x003C/4 + ((n) - 1)) +#define IPU_INT_CTRL_IRQ(irq) IPU_INT_CTRL(((irq) / 32)) +#define IPU_INT_STAT_IRQ(irq) IPU_INT_STAT(((irq) / 32)) +#define IPU_INT_STAT(n) ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0200/4 + ((n) - 1)) : \ + (ipu_cm_reg + 0x00E8/4 + ((n) - 1)); }) + +#define IPUIRQ_2_STATREG(irq) (IPU_INT_STAT(1) + ((irq) / 32)) +#define IPUIRQ_2_CTRLREG(irq) (IPU_INT_CTRL(1) + ((irq) / 32)) +#define IPUIRQ_2_MASK(irq) (1UL << ((irq) & 0x1F)) + +#define VDI_FSIZE (ipu_vdi_reg) +#define VDI_C (ipu_vdi_reg + 0x0004/4) + +/* CMOS Sensor Interface Registers */ +#define CSI_SENS_CONF(csi) (ipu_csi_reg[csi]) +#define CSI_SENS_FRM_SIZE(csi) (ipu_csi_reg[csi] + 0x0004/4) +#define CSI_ACT_FRM_SIZE(csi) (ipu_csi_reg[csi] + 0x0008/4) +#define CSI_OUT_FRM_CTRL(csi) (ipu_csi_reg[csi] + 0x000C/4) +#define CSI_TST_CTRL(csi) (ipu_csi_reg[csi] + 0x0010/4) +#define CSI_CCIR_CODE_1(csi) (ipu_csi_reg[csi] + 0x0014/4) +#define CSI_CCIR_CODE_2(csi) (ipu_csi_reg[csi] + 0x0018/4) +#define CSI_CCIR_CODE_3(csi) (ipu_csi_reg[csi] + 0x001C/4) +#define CSI_MIPI_DI(csi) (ipu_csi_reg[csi] + 0x0020/4) +#define CSI_SKIP(csi) (ipu_csi_reg[csi] + 0x0024/4) +#define CSI_CPD_CTRL(csi) (ipu_csi_reg[csi] + 0x0028/4) +#define CSI_CPD_RC(csi, n) (ipu_csi_reg[csi] + 0x002C/4 + n) +#define CSI_CPD_RS(csi, n) (ipu_csi_reg[csi] + 0x004C/4 + n) +#define CSI_CPD_GRC(csi, n) (ipu_csi_reg[csi] + 0x005C/4 + n) +#define CSI_CPD_GRS(csi, n) (ipu_csi_reg[csi] + 0x007C/4 + n) +#define CSI_CPD_GBC(csi, n) (ipu_csi_reg[csi] + 0x008C/4 + n) +#define CSI_CPD_GBS(csi, n) (ipu_csi_reg[csi] + 0x00AC/4 + n) +#define CSI_CPD_BC(csi, n) (ipu_csi_reg[csi] + 0x00BC/4 + n) +#define CSI_CPD_BS(csi, n) (ipu_csi_reg[csi] + 0x00DC/4 + n) +#define CSI_CPD_OFFSET1(csi) (ipu_csi_reg[csi] + 0x00EC/4) +#define CSI_CPD_OFFSET2(csi) (ipu_csi_reg[csi] + 0x00F0/4) + +/*SMFC Registers */ +#define SMFC_MAP (ipu_smfc_reg) +#define SMFC_WMC (ipu_smfc_reg + 0x0004/4) +#define SMFC_BS (ipu_smfc_reg + 0x0008/4) + +/* Image Converter Registers */ +#define IC_CONF (ipu_ic_reg) +#define IC_PRP_ENC_RSC (ipu_ic_reg + 0x0004/4) +#define IC_PRP_VF_RSC (ipu_ic_reg + 0x0008/4) +#define IC_PP_RSC (ipu_ic_reg + 0x000C/4) +#define IC_CMBP_1 (ipu_ic_reg + 0x0010/4) +#define IC_CMBP_2 (ipu_ic_reg + 0x0014/4) +#define IC_IDMAC_1 (ipu_ic_reg + 0x0018/4) +#define IC_IDMAC_2 (ipu_ic_reg + 0x001C/4) +#define IC_IDMAC_3 (ipu_ic_reg + 0x0020/4) +#define IC_IDMAC_4 (ipu_ic_reg + 0x0024/4) + +#define IDMAC_CONF (ipu_idmac_reg + 0x0000) +#define IDMAC_CHA_EN(ch) (ipu_idmac_reg + 0x0004/4 + (ch/32)) +#define IDMAC_SEP_ALPHA (ipu_idmac_reg + 0x000C/4) +#define IDMAC_ALT_SEP_ALPHA (ipu_idmac_reg + 0x0010/4) +#define IDMAC_CHA_PRI(ch) (ipu_idmac_reg + 0x0014/4 + (ch/32)) +#define IDMAC_WM_EN(ch) (ipu_idmac_reg + 0x001C/4 + (ch/32)) +#define IDMAC_CH_LOCK_EN_1 ({g_ipu_hw_rev == 2 ? \ + (ipu_idmac_reg + 0x0024/4) : 0; }) +#define IDMAC_CH_LOCK_EN_2 ({g_ipu_hw_rev == 2 ? \ + (ipu_idmac_reg + 0x0028/4) : \ + (ipu_idmac_reg + 0x0024/4); }) +#define IDMAC_SUB_ADDR_0 ({g_ipu_hw_rev == 2 ? \ + (ipu_idmac_reg + 0x002C/4) : \ + (ipu_idmac_reg + 0x0028/4); }) +#define IDMAC_SUB_ADDR_1 ({g_ipu_hw_rev == 2 ? \ + (ipu_idmac_reg + 0x0030/4) : \ + (ipu_idmac_reg + 0x002C/4); }) +#define IDMAC_SUB_ADDR_2 ({g_ipu_hw_rev == 2 ? \ + (ipu_idmac_reg + 0x0034/4) : \ + (ipu_idmac_reg + 0x0030/4); }) +#define IDMAC_BAND_EN(ch) ({g_ipu_hw_rev == 2 ? \ + (ipu_idmac_reg + 0x0040/4 + (ch/32)) : \ + (ipu_idmac_reg + 0x0034/4 + (ch/32)); }) +#define IDMAC_CHA_BUSY(ch) ({g_ipu_hw_rev == 2 ? \ + (ipu_idmac_reg + 0x0100/4 + (ch/32)) : \ + (ipu_idmac_reg + 0x0040/4 + (ch/32)); }) + +#define DI_GENERAL(di) (ipu_di_reg[di]) +#define DI_BS_CLKGEN0(di) (ipu_di_reg[di] + 0x0004/4) +#define DI_BS_CLKGEN1(di) (ipu_di_reg[di] + 0x0008/4) + +#define DI_SW_GEN0(di, gen) (ipu_di_reg[di] + 0x000C/4 + (gen - 1)) +#define DI_SW_GEN1(di, gen) (ipu_di_reg[di] + 0x0030/4 + (gen - 1)) +#define DI_STP_REP(di, gen) (ipu_di_reg[di] + 0x0148/4 + (gen - 1)/2) +#define DI_SYNC_AS_GEN(di) (ipu_di_reg[di] + 0x0054/4) +#define DI_DW_GEN(di, gen) (ipu_di_reg[di] + 0x0058/4 + gen) +#define DI_DW_SET(di, gen, set) (ipu_di_reg[di] + 0x0088/4 + gen + 0xC*set) +#define DI_SER_CONF(di) (ipu_di_reg[di] + 0x015C/4) +#define DI_SSC(di) (ipu_di_reg[di] + 0x0160/4) +#define DI_POL(di) (ipu_di_reg[di] + 0x0164/4) +#define DI_AW0(di) (ipu_di_reg[di] + 0x0168/4) +#define DI_AW1(di) (ipu_di_reg[di] + 0x016C/4) +#define DI_SCR_CONF(di) (ipu_di_reg[di] + 0x0170/4) +#define DI_STAT(di) (ipu_di_reg[di] + 0x0174/4) + +#define DMFC_RD_CHAN (ipu_dmfc_reg) +#define DMFC_WR_CHAN (ipu_dmfc_reg + 0x0004/4) +#define DMFC_WR_CHAN_DEF (ipu_dmfc_reg + 0x0008/4) +#define DMFC_DP_CHAN (ipu_dmfc_reg + 0x000C/4) +#define DMFC_DP_CHAN_DEF (ipu_dmfc_reg + 0x0010/4) +#define DMFC_GENERAL1 (ipu_dmfc_reg + 0x0014/4) +#define DMFC_GENERAL2 (ipu_dmfc_reg + 0x0018/4) +#define DMFC_IC_CTRL (ipu_dmfc_reg + 0x001C/4) +#define DMFC_STAT (ipu_dmfc_reg + 0x0020/4) + +#define DC_MAP_CONF_PTR(n) (ipu_dc_reg + 0x0108/4 + n/2) +#define DC_MAP_CONF_VAL(n) (ipu_dc_reg + 0x0144/4 + n/2) + +#define _RL_CH_2_OFFSET(ch) ((ch == 0) ? 8 : ( \ + (ch == 1) ? 0x24 : ( \ + (ch == 2) ? 0x40 : ( \ + (ch == 5) ? 0x64 : ( \ + (ch == 6) ? 0x80 : ( \ + (ch == 8) ? 0x9C : ( \ + (ch == 9) ? 0xBC : (-1)))))))) +#define DC_RL_CH(ch, evt) (ipu_dc_reg + _RL_CH_2_OFFSET(ch)/4 + evt/2) + +#define DC_EVT_NF 0 +#define DC_EVT_NL 1 +#define DC_EVT_EOF 2 +#define DC_EVT_NFIELD 3 +#define DC_EVT_EOL 4 +#define DC_EVT_EOFIELD 5 +#define DC_EVT_NEW_ADDR 6 +#define DC_EVT_NEW_CHAN 7 +#define DC_EVT_NEW_DATA 8 + +#define DC_EVT_NEW_ADDR_W_0 0 +#define DC_EVT_NEW_ADDR_W_1 1 +#define DC_EVT_NEW_CHAN_W_0 2 +#define DC_EVT_NEW_CHAN_W_1 3 +#define DC_EVT_NEW_DATA_W_0 4 +#define DC_EVT_NEW_DATA_W_1 5 +#define DC_EVT_NEW_ADDR_R_0 6 +#define DC_EVT_NEW_ADDR_R_1 7 +#define DC_EVT_NEW_CHAN_R_0 8 +#define DC_EVT_NEW_CHAN_R_1 9 +#define DC_EVT_NEW_DATA_R_0 10 +#define DC_EVT_NEW_DATA_R_1 11 + +#define dc_ch_offset(ch) \ +({ \ + const u8 _offset[] = { \ + 0, 0x1C, 0x38, 0x54, 0x58, 0x5C, 0x78, 0, 0x94, 0xB4}; \ + _offset[ch]; \ +}) +#define DC_WR_CH_CONF(ch) (ipu_dc_reg + dc_ch_offset(ch)/4) +#define DC_WR_CH_ADDR(ch) (ipu_dc_reg + dc_ch_offset(ch)/4 + 4/4) + +#define DC_WR_CH_CONF_1 (ipu_dc_reg + 0x001C/4) +#define DC_WR_CH_ADDR_1 (ipu_dc_reg + 0x0020/4) +#define DC_WR_CH_CONF_5 (ipu_dc_reg + 0x005C/4) +#define DC_WR_CH_ADDR_5 (ipu_dc_reg + 0x0060/4) +#define DC_GEN (ipu_dc_reg + 0x00D4/4) +#define DC_DISP_CONF1(disp) (ipu_dc_reg + 0x00D8/4 + disp) +#define DC_DISP_CONF2(disp) (ipu_dc_reg + 0x00E8/4 + disp) +#define DC_STAT (ipu_dc_reg + 0x01C8/4) +#define DC_UGDE_0(evt) (ipu_dc_reg + 0x0174/4 + evt*4) +#define DC_UGDE_1(evt) (ipu_dc_reg + 0x0178/4 + evt*4) +#define DC_UGDE_2(evt) (ipu_dc_reg + 0x017C/4 + evt*4) +#define DC_UGDE_3(evt) (ipu_dc_reg + 0x0180/4 + evt*4) + +#define DP_SYNC 0 +#define DP_ASYNC0 0x60 +#define DP_ASYNC1 0xBC +#define DP_COM_CONF(flow) (ipu_dp_reg + flow/4) +#define DP_GRAPH_WIND_CTRL(flow) (ipu_dp_reg + 0x0004/4 + flow/4) +#define DP_FG_POS(flow) (ipu_dp_reg + 0x0008/4 + flow/4) +#define DP_CSC_A_0(flow) (ipu_dp_reg + 0x0044/4 + flow/4) +#define DP_CSC_A_1(flow) (ipu_dp_reg + 0x0048/4 + flow/4) +#define DP_CSC_A_2(flow) (ipu_dp_reg + 0x004C/4 + flow/4) +#define DP_CSC_A_3(flow) (ipu_dp_reg + 0x0050/4 + flow/4) +#define DP_CSC_0(flow) (ipu_dp_reg + 0x0054/4 + flow/4) +#define DP_CSC_1(flow) (ipu_dp_reg + 0x0058/4 + flow/4) + +enum { + IPU_CONF_CSI0_EN = 0x00000001, + IPU_CONF_CSI1_EN = 0x00000002, + IPU_CONF_IC_EN = 0x00000004, + IPU_CONF_ROT_EN = 0x00000008, + IPU_CONF_ISP_EN = 0x00000010, + IPU_CONF_DP_EN = 0x00000020, + IPU_CONF_DI0_EN = 0x00000040, + IPU_CONF_DI1_EN = 0x00000080, + IPU_CONF_DMFC_EN = 0x00000400, + IPU_CONF_SMFC_EN = 0x00000100, + IPU_CONF_DC_EN = 0x00000200, + IPU_CONF_VDI_EN = 0x00001000, + IPU_CONF_IDMAC_DIS = 0x00400000, + IPU_CONF_IC_DMFC_SEL = 0x02000000, + IPU_CONF_IC_DMFC_SYNC = 0x04000000, + IPU_CONF_VDI_DMFC_SYNC = 0x08000000, + IPU_CONF_CSI0_DATA_SOURCE = 0x10000000, + IPU_CONF_CSI0_DATA_SOURCE_OFFSET = 28, + IPU_CONF_CSI1_DATA_SOURCE = 0x20000000, + IPU_CONF_IC_INPUT = 0x40000000, + IPU_CONF_CSI_SEL = 0x80000000, + + DI0_COUNTER_RELEASE = 0x01000000, + DI1_COUNTER_RELEASE = 0x02000000, + + FS_PRPVF_ROT_SRC_SEL_MASK = 0x00000F00, + FS_PRPVF_ROT_SRC_SEL_OFFSET = 8, + FS_PRPENC_ROT_SRC_SEL_MASK = 0x0000000F, + FS_PRPENC_ROT_SRC_SEL_OFFSET = 0, + FS_PP_ROT_SRC_SEL_MASK = 0x000F0000, + FS_PP_ROT_SRC_SEL_OFFSET = 16, + FS_PP_SRC_SEL_MASK = 0x0000F000, + FS_PP_SRC_SEL_OFFSET = 12, + FS_PRP_SRC_SEL_MASK = 0x0F000000, + FS_PRP_SRC_SEL_OFFSET = 24, + FS_VF_IN_VALID = 0x80000000, + FS_ENC_IN_VALID = 0x40000000, + FS_VDI_SRC_SEL_MASK = 0x30000000, + FS_VDI_SRC_SEL_OFFSET = 28, + + + FS_PRPENC_DEST_SEL_MASK = 0x0000000F, + FS_PRPENC_DEST_SEL_OFFSET = 0, + FS_PRPVF_DEST_SEL_MASK = 0x000000F0, + FS_PRPVF_DEST_SEL_OFFSET = 4, + FS_PRPVF_ROT_DEST_SEL_MASK = 0x00000F00, + FS_PRPVF_ROT_DEST_SEL_OFFSET = 8, + FS_PP_DEST_SEL_MASK = 0x0000F000, + FS_PP_DEST_SEL_OFFSET = 12, + FS_PP_ROT_DEST_SEL_MASK = 0x000F0000, + FS_PP_ROT_DEST_SEL_OFFSET = 16, + FS_PRPENC_ROT_DEST_SEL_MASK = 0x00F00000, + FS_PRPENC_ROT_DEST_SEL_OFFSET = 20, + + FS_SMFC0_DEST_SEL_MASK = 0x0000000F, + FS_SMFC0_DEST_SEL_OFFSET = 0, + FS_SMFC1_DEST_SEL_MASK = 0x00000070, + FS_SMFC1_DEST_SEL_OFFSET = 4, + FS_SMFC2_DEST_SEL_MASK = 0x00000780, + FS_SMFC2_DEST_SEL_OFFSET = 7, + FS_SMFC3_DEST_SEL_MASK = 0x00003800, + FS_SMFC3_DEST_SEL_OFFSET = 11, + + FS_DC1_SRC_SEL_MASK = 0x00F00000, + FS_DC1_SRC_SEL_OFFSET = 20, + FS_DC2_SRC_SEL_MASK = 0x000F0000, + FS_DC2_SRC_SEL_OFFSET = 16, + FS_DP_SYNC0_SRC_SEL_MASK = 0x0000000F, + FS_DP_SYNC0_SRC_SEL_OFFSET = 0, + FS_DP_SYNC1_SRC_SEL_MASK = 0x000000F0, + FS_DP_SYNC1_SRC_SEL_OFFSET = 4, + FS_DP_ASYNC0_SRC_SEL_MASK = 0x00000F00, + FS_DP_ASYNC0_SRC_SEL_OFFSET = 8, + FS_DP_ASYNC1_SRC_SEL_MASK = 0x0000F000, + FS_DP_ASYNC1_SRC_SEL_OFFSET = 12, + + FS_AUTO_REF_PER_MASK = 0, + FS_AUTO_REF_PER_OFFSET = 16, + + TSTAT_VF_MASK = 0x0000000C, + TSTAT_VF_OFFSET = 2, + TSTAT_VF_ROT_MASK = 0x00000300, + TSTAT_VF_ROT_OFFSET = 8, + TSTAT_ENC_MASK = 0x00000003, + TSTAT_ENC_OFFSET = 0, + TSTAT_ENC_ROT_MASK = 0x000000C0, + TSTAT_ENC_ROT_OFFSET = 6, + TSTAT_PP_MASK = 0x00000030, + TSTAT_PP_OFFSET = 4, + TSTAT_PP_ROT_MASK = 0x00000C00, + TSTAT_PP_ROT_OFFSET = 10, + + TASK_STAT_IDLE = 0, + TASK_STAT_ACTIVE = 1, + TASK_STAT_WAIT4READY = 2, + + /* Image Converter Register bits */ + IC_CONF_PRPENC_EN = 0x00000001, + IC_CONF_PRPENC_CSC1 = 0x00000002, + IC_CONF_PRPENC_ROT_EN = 0x00000004, + IC_CONF_PRPVF_EN = 0x00000100, + IC_CONF_PRPVF_CSC1 = 0x00000200, + IC_CONF_PRPVF_CSC2 = 0x00000400, + IC_CONF_PRPVF_CMB = 0x00000800, + IC_CONF_PRPVF_ROT_EN = 0x00001000, + IC_CONF_PP_EN = 0x00010000, + IC_CONF_PP_CSC1 = 0x00020000, + IC_CONF_PP_CSC2 = 0x00040000, + IC_CONF_PP_CMB = 0x00080000, + IC_CONF_PP_ROT_EN = 0x00100000, + IC_CONF_IC_GLB_LOC_A = 0x10000000, + IC_CONF_KEY_COLOR_EN = 0x20000000, + IC_CONF_RWS_EN = 0x40000000, + IC_CONF_CSI_MEM_WR_EN = 0x80000000, + + IC_IDMAC_1_CB0_BURST_16 = 0x00000001, + IC_IDMAC_1_CB1_BURST_16 = 0x00000002, + IC_IDMAC_1_CB2_BURST_16 = 0x00000004, + IC_IDMAC_1_CB3_BURST_16 = 0x00000008, + IC_IDMAC_1_CB4_BURST_16 = 0x00000010, + IC_IDMAC_1_CB5_BURST_16 = 0x00000020, + IC_IDMAC_1_CB6_BURST_16 = 0x00000040, + IC_IDMAC_1_CB7_BURST_16 = 0x00000080, + IC_IDMAC_1_PRPENC_ROT_MASK = 0x00003800, + IC_IDMAC_1_PRPENC_ROT_OFFSET = 11, + IC_IDMAC_1_PRPVF_ROT_MASK = 0x0001C000, + IC_IDMAC_1_PRPVF_ROT_OFFSET = 14, + IC_IDMAC_1_PP_ROT_MASK = 0x000E0000, + IC_IDMAC_1_PP_ROT_OFFSET = 17, + IC_IDMAC_1_PP_FLIP_RS = 0x00400000, + IC_IDMAC_1_PRPVF_FLIP_RS = 0x00200000, + IC_IDMAC_1_PRPENC_FLIP_RS = 0x00100000, + + IC_IDMAC_2_PRPENC_HEIGHT_MASK = 0x000003FF, + IC_IDMAC_2_PRPENC_HEIGHT_OFFSET = 0, + IC_IDMAC_2_PRPVF_HEIGHT_MASK = 0x000FFC00, + IC_IDMAC_2_PRPVF_HEIGHT_OFFSET = 10, + IC_IDMAC_2_PP_HEIGHT_MASK = 0x3FF00000, + IC_IDMAC_2_PP_HEIGHT_OFFSET = 20, + + IC_IDMAC_3_PRPENC_WIDTH_MASK = 0x000003FF, + IC_IDMAC_3_PRPENC_WIDTH_OFFSET = 0, + IC_IDMAC_3_PRPVF_WIDTH_MASK = 0x000FFC00, + IC_IDMAC_3_PRPVF_WIDTH_OFFSET = 10, + IC_IDMAC_3_PP_WIDTH_MASK = 0x3FF00000, + IC_IDMAC_3_PP_WIDTH_OFFSET = 20, + + CSI_SENS_CONF_DATA_FMT_SHIFT = 8, + CSI_SENS_CONF_DATA_FMT_MASK = 0x00000700, + CSI_SENS_CONF_DATA_FMT_RGB_YUV444 = 0L, + CSI_SENS_CONF_DATA_FMT_YUV422_YUYV = 1L, + CSI_SENS_CONF_DATA_FMT_YUV422_UYVY = 2L, + CSI_SENS_CONF_DATA_FMT_BAYER = 3L, + CSI_SENS_CONF_DATA_FMT_RGB565 = 4L, + CSI_SENS_CONF_DATA_FMT_RGB555 = 5L, + CSI_SENS_CONF_DATA_FMT_RGB444 = 6L, + CSI_SENS_CONF_DATA_FMT_JPEG = 7L, + + CSI_SENS_CONF_VSYNC_POL_SHIFT = 0, + CSI_SENS_CONF_HSYNC_POL_SHIFT = 1, + CSI_SENS_CONF_DATA_POL_SHIFT = 2, + CSI_SENS_CONF_PIX_CLK_POL_SHIFT = 3, + CSI_SENS_CONF_SENS_PRTCL_SHIFT = 4, + CSI_SENS_CONF_PACK_TIGHT_SHIFT = 7, + CSI_SENS_CONF_DATA_WIDTH_SHIFT = 11, + CSI_SENS_CONF_EXT_VSYNC_SHIFT = 15, + CSI_SENS_CONF_DIVRATIO_SHIFT = 16, + + CSI_SENS_CONF_DIVRATIO_MASK = 0x00FF0000L, + CSI_SENS_CONF_DATA_DEST_SHIFT = 24, + CSI_SENS_CONF_DATA_DEST_MASK = 0x07000000L, + CSI_SENS_CONF_JPEG8_EN_SHIFT = 27, + CSI_SENS_CONF_JPEG_EN_SHIFT = 28, + CSI_SENS_CONF_FORCE_EOF_SHIFT = 29, + CSI_SENS_CONF_DATA_EN_POL_SHIFT = 31, + + CSI_DATA_DEST_ISP = 1L, + CSI_DATA_DEST_IC = 2L, + CSI_DATA_DEST_IDMAC = 4L, + + CSI_CCIR_ERR_DET_EN = 0x01000000L, + CSI_HORI_DOWNSIZE_EN = 0x80000000L, + CSI_VERT_DOWNSIZE_EN = 0x40000000L, + CSI_TEST_GEN_MODE_EN = 0x01000000L, + + CSI_HSC_MASK = 0x1FFF0000, + CSI_HSC_SHIFT = 16, + CSI_VSC_MASK = 0x00000FFF, + CSI_VSC_SHIFT = 0, + + CSI_TEST_GEN_R_MASK = 0x000000FFL, + CSI_TEST_GEN_R_SHIFT = 0, + CSI_TEST_GEN_G_MASK = 0x0000FF00L, + CSI_TEST_GEN_G_SHIFT = 8, + CSI_TEST_GEN_B_MASK = 0x00FF0000L, + CSI_TEST_GEN_B_SHIFT = 16, + + CSI_MIPI_DI0_MASK = 0x000000FFL, + CSI_MIPI_DI0_SHIFT = 0, + CSI_MIPI_DI1_MASK = 0x0000FF00L, + CSI_MIPI_DI1_SHIFT = 8, + CSI_MIPI_DI2_MASK = 0x00FF0000L, + CSI_MIPI_DI2_SHIFT = 16, + CSI_MIPI_DI3_MASK = 0xFF000000L, + CSI_MIPI_DI3_SHIFT = 24, + + CSI_MAX_RATIO_SKIP_ISP_MASK = 0x00070000L, + CSI_MAX_RATIO_SKIP_ISP_SHIFT = 16, + CSI_SKIP_ISP_MASK = 0x00F80000L, + CSI_SKIP_ISP_SHIFT = 19, + CSI_MAX_RATIO_SKIP_SMFC_MASK = 0x00000007L, + CSI_MAX_RATIO_SKIP_SMFC_SHIFT = 0, + CSI_SKIP_SMFC_MASK = 0x000000F8L, + CSI_SKIP_SMFC_SHIFT = 3, + CSI_ID_2_SKIP_MASK = 0x00000300L, + CSI_ID_2_SKIP_SHIFT = 8, + + CSI_COLOR_FIRST_ROW_MASK = 0x00000002L, + CSI_COLOR_FIRST_COMP_MASK = 0x00000001L, + + SMFC_MAP_CH0_MASK = 0x00000007L, + SMFC_MAP_CH0_SHIFT = 0, + SMFC_MAP_CH1_MASK = 0x00000038L, + SMFC_MAP_CH1_SHIFT = 3, + SMFC_MAP_CH2_MASK = 0x000001C0L, + SMFC_MAP_CH2_SHIFT = 6, + SMFC_MAP_CH3_MASK = 0x00000E00L, + SMFC_MAP_CH3_SHIFT = 9, + + SMFC_WM0_SET_MASK = 0x00000007L, + SMFC_WM0_SET_SHIFT = 0, + SMFC_WM1_SET_MASK = 0x000001C0L, + SMFC_WM1_SET_SHIFT = 6, + SMFC_WM2_SET_MASK = 0x00070000L, + SMFC_WM2_SET_SHIFT = 16, + SMFC_WM3_SET_MASK = 0x01C00000L, + SMFC_WM3_SET_SHIFT = 22, + + SMFC_WM0_CLR_MASK = 0x00000038L, + SMFC_WM0_CLR_SHIFT = 3, + SMFC_WM1_CLR_MASK = 0x00000E00L, + SMFC_WM1_CLR_SHIFT = 9, + SMFC_WM2_CLR_MASK = 0x00380000L, + SMFC_WM2_CLR_SHIFT = 19, + SMFC_WM3_CLR_MASK = 0x0E000000L, + SMFC_WM3_CLR_SHIFT = 25, + + SMFC_BS0_MASK = 0x0000000FL, + SMFC_BS0_SHIFT = 0, + SMFC_BS1_MASK = 0x000000F0L, + SMFC_BS1_SHIFT = 4, + SMFC_BS2_MASK = 0x00000F00L, + SMFC_BS2_SHIFT = 8, + SMFC_BS3_MASK = 0x0000F000L, + SMFC_BS3_SHIFT = 12, + + PF_CONF_TYPE_MASK = 0x00000007, + PF_CONF_TYPE_SHIFT = 0, + PF_CONF_PAUSE_EN = 0x00000010, + PF_CONF_RESET = 0x00008000, + PF_CONF_PAUSE_ROW_MASK = 0x00FF0000, + PF_CONF_PAUSE_ROW_SHIFT = 16, + + DI_DW_GEN_ACCESS_SIZE_OFFSET = 24, + DI_DW_GEN_COMPONENT_SIZE_OFFSET = 16, + + DI_GEN_DI_CLK_EXT = 0x100000, + DI_GEN_POLARITY_1 = 0x00000001, + DI_GEN_POLARITY_2 = 0x00000002, + DI_GEN_POLARITY_3 = 0x00000004, + DI_GEN_POLARITY_4 = 0x00000008, + DI_GEN_POLARITY_5 = 0x00000010, + DI_GEN_POLARITY_6 = 0x00000020, + DI_GEN_POLARITY_7 = 0x00000040, + DI_GEN_POLARITY_8 = 0x00000080, + + DI_POL_DRDY_DATA_POLARITY = 0x00000080, + DI_POL_DRDY_POLARITY_15 = 0x00000010, + + DI_VSYNC_SEL_OFFSET = 13, + + DC_WR_CH_CONF_FIELD_MODE = 0x00000200, + DC_WR_CH_CONF_PROG_TYPE_OFFSET = 5, + DC_WR_CH_CONF_PROG_TYPE_MASK = 0x000000E0, + DC_WR_CH_CONF_PROG_DI_ID = 0x00000004, + DC_WR_CH_CONF_PROG_DISP_ID_OFFSET = 3, + DC_WR_CH_CONF_PROG_DISP_ID_MASK = 0x00000018, + + DC_UGDE_0_ODD_EN = 0x02000000, + DC_UGDE_0_ID_CODED_MASK = 0x00000007, + DC_UGDE_0_ID_CODED_OFFSET = 0, + DC_UGDE_0_EV_PRIORITY_MASK = 0x00000078, + DC_UGDE_0_EV_PRIORITY_OFFSET = 3, + + DP_COM_CONF_FG_EN = 0x00000001, + DP_COM_CONF_GWSEL = 0x00000002, + DP_COM_CONF_GWAM = 0x00000004, + DP_COM_CONF_GWCKE = 0x00000008, + DP_COM_CONF_CSC_DEF_MASK = 0x00000300, + DP_COM_CONF_CSC_DEF_OFFSET = 8, + DP_COM_CONF_CSC_DEF_FG = 0x00000300, + DP_COM_CONF_CSC_DEF_BG = 0x00000200, + DP_COM_CONF_CSC_DEF_BOTH = 0x00000100, + + DI_SER_CONF_LLA_SER_ACCESS = 0x00000020, + DI_SER_CONF_SERIAL_CLK_POL = 0x00000010, + DI_SER_CONF_SERIAL_DATA_POL = 0x00000008, + DI_SER_CONF_SERIAL_RS_POL = 0x00000004, + DI_SER_CONF_SERIAL_CS_POL = 0x00000002, + DI_SER_CONF_WAIT4SERIAL = 0x00000001, + + VDI_C_CH_420 = 0x00000000, + VDI_C_CH_422 = 0x00000002, + VDI_C_MOT_SEL_FULL = 0x00000008, + VDI_C_MOT_SEL_LOW = 0x00000004, + VDI_C_MOT_SEL_MED = 0x00000000, + VDI_C_BURST_SIZE1_4 = 0x00000030, + VDI_C_BURST_SIZE2_4 = 0x00000300, + VDI_C_BURST_SIZE3_4 = 0x00003000, + VDI_C_VWM1_SET_1 = 0x00000000, + VDI_C_VWM1_CLR_2 = 0x00080000, + VDI_C_VWM3_SET_1 = 0x00000000, + VDI_C_VWM3_CLR_2 = 0x02000000, + VDI_C_TOP_FIELD_MAN_1 = 0x40000000, + VDI_C_TOP_FIELD_AUTO_1 = 0x80000000, +}; + +enum di_pins { + DI_PIN11 = 0, + DI_PIN12 = 1, + DI_PIN13 = 2, + DI_PIN14 = 3, + DI_PIN15 = 4, + DI_PIN16 = 5, + DI_PIN17 = 6, + DI_PIN_CS = 7, + + DI_PIN_SER_CLK = 0, + DI_PIN_SER_RS = 1, +}; + +enum di_sync_wave { + DI_SYNC_NONE = -1, + DI_SYNC_CLK = 0, + DI_SYNC_INT_HSYNC = 1, + DI_SYNC_HSYNC = 2, + DI_SYNC_VSYNC = 3, + DI_SYNC_DE = 5, +}; + +/* DC template opcodes */ +#define WROD(lf) (0x18 | (lf << 1)) + +#endif diff --git a/drivers/mxc/mcu_pmic/Kconfig b/drivers/mxc/mcu_pmic/Kconfig new file mode 100644 index 000000000000..cb6815e92d86 --- /dev/null +++ b/drivers/mxc/mcu_pmic/Kconfig @@ -0,0 +1,17 @@ +# +# PMIC Modules configuration +# + +config MXC_PMIC_MC9S08DZ60 + tristate "MC9S08DZ60 PMIC" + depends on ARCH_MXC && I2C + ---help--- + This is the MXC MC9S08DZ60(MCU) PMIC support. + +config MXC_MC9SDZ60_RTC + tristate "MC9SDZ60 Real Time Clock (RTC) support" + depends on MXC_PMIC_MC9SDZ60 + ---help--- + This is the MC9SDZ60 RTC module driver. This module provides kernel API + for RTC part of MC9SDZ60. + If you want MC9SDZ60 RTC support, you should say Y here diff --git a/drivers/mxc/mcu_pmic/Makefile b/drivers/mxc/mcu_pmic/Makefile new file mode 100644 index 000000000000..96aae94d5290 --- /dev/null +++ b/drivers/mxc/mcu_pmic/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the mc9sdz60 pmic drivers. +# + +obj-$(CONFIG_MXC_PMIC_MC9SDZ60) += pmic_mc9sdz60_mod.o +pmic_mc9sdz60_mod-objs := mcu_pmic_core.o max8660.o mc9s08dz60.o mcu_pmic_gpio.o diff --git a/drivers/mxc/mcu_pmic/max8660.c b/drivers/mxc/mcu_pmic/max8660.c new file mode 100644 index 000000000000..f48899f212f9 --- /dev/null +++ b/drivers/mxc/mcu_pmic/max8660.c @@ -0,0 +1,154 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file max8660.c + * @brief Driver for max8660 + * + * @ingroup pmic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/proc_fs.h> +#include <linux/i2c.h> +#include <linux/mfd/mc9s08dz60/pmic.h> +#include <asm/uaccess.h> +#include "mcu_pmic_core.h" +#include "max8660.h" + +/* I2C bus id and device address of mcu */ +#define I2C1_BUS 0 +#define MAX8660_I2C_ADDR 0x68 + +static struct i2c_client *max8660_i2c_client; + + /* reg names for max8660 + REG_MAX8660_OUTPUT_ENABLE_1, + REG_MAX8660_OUTPUT_ENABLE_2, + REG_MAX8660_VOLT__CHANGE_1, + REG_MAX8660_V3_TARGET_VOLT_1, + REG_MAX8660_V3_TARGET_VOLT_2, + REG_MAX8660_V4_TARGET_VOLT_1, + REG_MAX8660_V4_TARGET_VOLT_2, + REG_MAX8660_V5_TARGET_VOLT_1, + REG_MAX8660_V5_TARGET_VOLT_2, + REG_MAX8660_V6V7_TARGET_VOLT, + REG_MAX8660_FORCE_PWM + */ + + /* save down the reg values for the device is write only */ +static u8 max8660_reg_value_table[] = + { 0x0, 0x0, 0x0, 0x17, 0x17, 0x1F, 0x1F, 0x04, 0x04, 0x0, 0x0 +}; +static int max8660_dev_present; + +int is_max8660_present(void) +{ + return max8660_dev_present; +} + +int max8660_get_buffered_reg_val(int reg_name, u8 *value) +{ + if (!max8660_dev_present) + return -1; + /* outof range */ + if (reg_name < REG_MAX8660_OUTPUT_ENABLE_1 + || reg_name > REG_MAX8660_FORCE_PWM) + return -1; + *value = + max8660_reg_value_table[reg_name - REG_MAX8660_OUTPUT_ENABLE_1]; + return 0; +} +int max8660_save_buffered_reg_val(int reg_name, u8 value) +{ + + /* outof range */ + if (reg_name < REG_MAX8660_OUTPUT_ENABLE_1 + || reg_name > REG_MAX8660_FORCE_PWM) + return -1; + max8660_reg_value_table[reg_name - REG_MAX8660_OUTPUT_ENABLE_1] = value; + return 0; +} + +int max8660_write_reg(u8 reg, u8 value) +{ + if (max8660_dev_present && (i2c_smbus_write_byte_data( + max8660_i2c_client, reg, value) >= 0)) + return 0; + return -1; +} + +/*! + * max8660 I2C attach function + * + * @param adapter struct i2c_client * + * @return 0 for max8660 successfully detected + */ +static int max8660_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + max8660_i2c_client = client; + retval = i2c_smbus_write_byte_data(max8660_i2c_client, + MAX8660_OUTPUT_ENABLE_1, 0); + if (retval == 0) { + max8660_dev_present = 1; + pr_info("max8660 probed !\n"); + } else { + max8660_dev_present = 0; + pr_info("max8660 not detected!\n"); + } + return retval; +} + +/*! + * max8660 I2C detach function + * + * @param client struct i2c_client * + * @return 0 + */ +static int max8660_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id max8660_id[] = { + { "max8660", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, max8660_id); + +static struct i2c_driver max8660_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "max8660",}, + .probe = max8660_probe, + .remove = max8660_remove, + .id_table = max8660_id, +}; + +/* called by pmic core when init*/ +int max8660_init(void) +{ + int err; + err = i2c_add_driver(&max8660_i2c_driver); + return err; +} +void max8660_exit(void) +{ + i2c_del_driver(&max8660_i2c_driver); +} diff --git a/drivers/mxc/mcu_pmic/max8660.h b/drivers/mxc/mcu_pmic/max8660.h new file mode 100644 index 000000000000..567784611d80 --- /dev/null +++ b/drivers/mxc/mcu_pmic/max8660.h @@ -0,0 +1,49 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file max8660.h + * @brief Driver for max8660 + * + * @ingroup pmic + */ +#ifndef _MAX8660_H_ +#define _MAX8660_H_ + +#ifdef __KERNEL__ + +#define MAX8660_OUTPUT_ENABLE_1 0x10 +#define MAX8660_OUTPUT_ENABLE_2 0x12 +#define MAX8660_VOLT_CHANGE_CONTROL 0x20 +#define MAX8660_V3_TARGET_VOLT_1 0x23 +#define MAX8660_V3_TARGET_VOLT_2 0x24 +#define MAX8660_V4_TARGET_VOLT_1 0x29 +#define MAX8660_V4_TARGET_VOLT_2 0x2A +#define MAX8660_V5_TARGET_VOLT_1 0x32 +#define MAX8660_V5_TARGET_VOLT_2 0x33 +#define MAX8660_V6V7_TARGET_VOLT 0x39 +#define MAX8660_FORCE_PWM 0x80 + +int is_max8660_present(void); +int max8660_write_reg(u8 reg, u8 value); +int max8660_save_buffered_reg_val(int reg_name, u8 value); +int max8660_get_buffered_reg_val(int reg_name, u8 *value); +int max8660_init(void); +void max8660_exit(void); + +extern int reg_max8660_probe(void); +extern int reg_max8660_remove(void); + +#endif /* __KERNEL__ */ + +#endif /* _MAX8660_H_ */ diff --git a/drivers/mxc/mcu_pmic/mc9s08dz60.c b/drivers/mxc/mcu_pmic/mc9s08dz60.c new file mode 100644 index 000000000000..1e5da6319b12 --- /dev/null +++ b/drivers/mxc/mcu_pmic/mc9s08dz60.c @@ -0,0 +1,197 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file mc9s08dz60.c + * @brief Driver for MC9sdz60 + * + * @ingroup pmic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/proc_fs.h> +#include <linux/i2c.h> +#include <linux/mfd/mc9s08dz60/core.h> + +#include <mach/clock.h> +#include <linux/uaccess.h> +#include "mc9s08dz60.h" + +/* I2C bus id and device address of mcu */ +#define I2C1_BUS 0 +#define MC9S08DZ60_I2C_ADDR 0xD2 /* 7bits I2C address */ +static struct i2c_client *mc9s08dz60_i2c_client; + +int mc9s08dz60_read_reg(u8 reg, u8 *value) +{ + *value = (u8) i2c_smbus_read_byte_data(mc9s08dz60_i2c_client, reg); + return 0; +} + +int mc9s08dz60_write_reg(u8 reg, u8 value) +{ + if (i2c_smbus_write_byte_data(mc9s08dz60_i2c_client, reg, value) < 0) + return -1; + return 0; +} + +static ssize_t mc9s08dz60_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int i; + u8 value; + int offset = 7; + + for (i = 0; i < 7; i++) { + mc9s08dz60_read_reg(i, &value); + pr_info("reg%02x: %02x\t", i, value); + mc9s08dz60_read_reg(i + offset, &value); + pr_info("reg%02x: %02x\t", i + offset, value); + mc9s08dz60_read_reg(i + offset * 2, &value); + pr_info("reg%02x: %02x\t", i + offset * 2, value); + mc9s08dz60_read_reg(i + offset * 3, &value); + pr_info("reg%02x: %02x\t", i + offset * 3, value); + mc9s08dz60_read_reg(i + offset * 4, &value); + pr_info("reg%02x: %02x\t", i + offset * 4, value); + mc9s08dz60_read_reg(i + offset * 5, &value); + pr_info("reg%02x: %02x\n", i + offset * 5, value); + } + + return 0; +} + +static ssize_t mc9s08dz60_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int ret; + unsigned long reg, new_value; + u8 value; + char *p; + + strict_strtoul(buf, 16, ®); + + p = NULL; + p = memchr(buf, ' ', count); + + if (p == NULL) { + mc9s08dz60_read_reg(reg, &value); + pr_info("reg%02lu: %06x\n", reg, value); + return count; + } + + p += 1; + + strict_strtoul(p, 16, &new_value); + value = new_value; + + ret = mc9s08dz60_write_reg((u8)reg, value); + if (ret == 0) + pr_info("write reg%02lx: %06x\n", reg, value); + else + pr_info("register update failed\n"); + + return count; +} + +static struct device_attribute mc9s08dz60_dev_attr = { + .attr = { + .name = "mc9s08dz60_ctl", + .mode = S_IRUSR | S_IWUSR, + }, + .show = mc9s08dz60_show, + .store = mc9s08dz60_store, +}; + + +/*! + * mc9s08dz60 I2C attach function + * + * @param adapter struct i2c_adapter * + * @return 0 + */ +static int mc9s08dz60_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct mc9s08dz60 *mc9s08dz60 = NULL; + struct mc9s08dz60_platform_data *plat_data = client->dev.platform_data; + pr_info("mc9s08dz60 probing .... \n"); + + mc9s08dz60 = kzalloc(sizeof(struct mc9s08dz60), GFP_KERNEL); + if (mc9s08dz60 == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, mc9s08dz60); + mc9s08dz60->dev = &client->dev; + mc9s08dz60->i2c_client = client; + + if (plat_data && plat_data->init) { + ret = plat_data->init(mc9s08dz60); + if (ret != 0) + return -1; + } + + ret = device_create_file(&client->dev, &mc9s08dz60_dev_attr); + if (ret) + dev_err(&client->dev, "create device file failed!\n"); + + + mc9s08dz60_i2c_client = client; + + return 0; +} + +/*! + * mc9s08dz60 I2C detach function + * + * @param client struct i2c_client * + * @return 0 + */ +static int mc9s08dz60_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id mc9s08dz60_id[] = { + { "mc9s08dz60", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, mc9s08dz60_id); + +static struct i2c_driver mc9s08dz60_i2c_driver = { + .driver = {.owner = THIS_MODULE, + .name = "mc9s08dz60", + }, + .probe = mc9s08dz60_probe, + .remove = mc9s08dz60_remove, + .id_table = mc9s08dz60_id, +}; + +#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos)) +#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos)) + +int mc9s08dz60_init(void) +{ + int err; + err = i2c_add_driver(&mc9s08dz60_i2c_driver); + return err; +} +void mc9s08dz60_exit(void) +{ + i2c_del_driver(&mc9s08dz60_i2c_driver); +} diff --git a/drivers/mxc/mcu_pmic/mc9s08dz60.h b/drivers/mxc/mcu_pmic/mc9s08dz60.h new file mode 100644 index 000000000000..28b8746eeb12 --- /dev/null +++ b/drivers/mxc/mcu_pmic/mc9s08dz60.h @@ -0,0 +1,73 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9s08dz60.h + * @brief Driver for mc9s08dz60 + * + * @ingroup pmic + */ +#ifndef _MC9SDZ60_H_ +#define _MC9SDZ60_H_ + +#define MCU_VERSION 0x00 +/*#define Reserved 0x01*/ +#define MCU_SECS 0x02 +#define MCU_MINS 0x03 +#define MCU_HRS 0x04 +#define MCU_DAY 0x05 +#define MCU_DATE 0x06 +#define MCU_MONTH 0x07 +#define MCU_YEAR 0x08 + +#define MCU_ALARM_SECS 0x09 +#define MCU_ALARM_MINS 0x0A +#define MCU_ALARM_HRS 0x0B +/* #define Reserved 0x0C*/ +/* #define Reserved 0x0D*/ +#define MCU_TS_CONTROL 0x0E +#define MCU_X_LOW 0x0F +#define MCU_Y_LOW 0x10 +#define MCU_XY_HIGH 0x11 +#define MCU_X_LEFT_LOW 0x12 +#define MCU_X_LEFT_HIGH 0x13 +#define MCU_X_RIGHT 0x14 +#define MCU_Y_TOP_LOW 0x15 +#define MCU_Y_TOP_HIGH 0x16 +#define MCU_Y_BOTTOM 0x17 +/* #define Reserved 0x18*/ +/* #define Reserved 0x19*/ +#define MCU_RESET_1 0x1A +#define MCU_RESET_2 0x1B +#define MCU_POWER_CTL 0x1C +#define MCU_DELAY_CONFIG 0x1D +/* #define Reserved 0x1E */ +/* #define Reserved 0x1F */ +#define MCU_GPIO_1 0x20 +#define MCU_GPIO_2 0x21 +#define MCU_KPD_1 0x22 +#define MCU_KPD_2 0x23 +#define MCU_KPD_CONTROL 0x24 +#define MCU_INT_ENABLE_1 0x25 +#define MCU_INT_ENABLE_2 0x26 +#define MCU_INT_FLAG_1 0x27 +#define MCU_INT_FLAG_2 0x28 +#define MCU_DES_FLAG 0x29 +int mc9s08dz60_read_reg(u8 reg, u8 *value); +int mc9s08dz60_write_reg(u8 reg, u8 value); +int mc9s08dz60_init(void); +void mc9s08dz60_exit(void); + +extern int reg_mc9s08dz60_probe(void); +extern int reg_mc9s08dz60_remove(void); + +#endif /* _MC9SDZ60_H_ */ diff --git a/drivers/mxc/mcu_pmic/mcu_pmic_core.c b/drivers/mxc/mcu_pmic/mcu_pmic_core.c new file mode 100644 index 000000000000..55b2f5fe6e3e --- /dev/null +++ b/drivers/mxc/mcu_pmic/mcu_pmic_core.c @@ -0,0 +1,226 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9s08dz60/mcu_pmic_core.c + * @brief This is the main file of mc9s08dz60 Power Control driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mfd/mc9s08dz60/pmic.h> +#include <asm/ioctl.h> +#include <asm/uaccess.h> +#include <mach/gpio.h> + +#include "mcu_pmic_core.h" +#include "mc9s08dz60.h" +#include "max8660.h" + +/* bitfield macros for mcu pmic*/ +#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos)) +#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos)) + + +/* map reg names (enum pmic_reg in pmic_external.h) to real addr*/ +const static u8 mcu_pmic_reg_addr_table[] = { + MCU_VERSION, + MCU_SECS, + MCU_MINS, + MCU_HRS, + MCU_DAY, + MCU_DATE, + MCU_MONTH, + MCU_YEAR, + MCU_ALARM_SECS, + MCU_ALARM_MINS, + MCU_ALARM_HRS, + MCU_TS_CONTROL, + MCU_X_LOW, + MCU_Y_LOW, + MCU_XY_HIGH, + MCU_X_LEFT_LOW, + MCU_X_LEFT_HIGH, + MCU_X_RIGHT, + MCU_Y_TOP_LOW, + MCU_Y_TOP_HIGH, + MCU_Y_BOTTOM, + MCU_RESET_1, + MCU_RESET_2, + MCU_POWER_CTL, + MCU_DELAY_CONFIG, + MCU_GPIO_1, + MCU_GPIO_2, + MCU_KPD_1, + MCU_KPD_2, + MCU_KPD_CONTROL, + MCU_INT_ENABLE_1, + MCU_INT_ENABLE_2, + MCU_INT_FLAG_1, + MCU_INT_FLAG_2, + MCU_DES_FLAG, + MAX8660_OUTPUT_ENABLE_1, + MAX8660_OUTPUT_ENABLE_2, + MAX8660_VOLT_CHANGE_CONTROL, + MAX8660_V3_TARGET_VOLT_1, + MAX8660_V3_TARGET_VOLT_2, + MAX8660_V4_TARGET_VOLT_1, + MAX8660_V4_TARGET_VOLT_2, + MAX8660_V5_TARGET_VOLT_1, + MAX8660_V5_TARGET_VOLT_2, + MAX8660_V6V7_TARGET_VOLT, + MAX8660_FORCE_PWM +}; + +static int mcu_pmic_read(int reg_num, unsigned int *reg_val) +{ + int ret; + u8 value = 0; + /* mcu ops */ + if (reg_num >= REG_MCU_VERSION && reg_num <= REG_MCU_DES_FLAG) + ret = mc9s08dz60_read_reg(mcu_pmic_reg_addr_table[reg_num], + &value); + else if (reg_num >= REG_MAX8660_OUTPUT_ENABLE_1 + && reg_num <= REG_MAX8660_FORCE_PWM) + ret = max8660_get_buffered_reg_val(reg_num, &value); + else + return -1; + + if (ret < 0) + return -1; + *reg_val = value; + + return 0; +} + +static int mcu_pmic_write(int reg_num, const unsigned int reg_val) +{ + int ret; + u8 value = reg_val; + /* mcu ops */ + if (reg_num >= REG_MCU_VERSION && reg_num <= REG_MCU_DES_FLAG) { + + ret = + mc9s08dz60_write_reg( + mcu_pmic_reg_addr_table[reg_num], value); + if (ret < 0) + return -1; + } else if (reg_num >= REG_MAX8660_OUTPUT_ENABLE_1 + && reg_num <= REG_MAX8660_FORCE_PWM) { + ret = + max8660_write_reg(mcu_pmic_reg_addr_table[reg_num], value); + + if (ret < 0) + return -1; + + ret = max8660_save_buffered_reg_val(reg_num, value); + } else + return -1; + + return 0; +} + +int mcu_pmic_read_reg(int reg, unsigned int *reg_value, + unsigned int reg_mask) +{ + int ret = 0; + unsigned int temp = 0; + + ret = mcu_pmic_read(reg, &temp); + if (ret != 0) + return -1; + *reg_value = (temp & reg_mask); + + pr_debug("Read REG[ %d ] = 0x%x\n", reg, *reg_value); + + return ret; +} + + +int mcu_pmic_write_reg(int reg, unsigned int reg_value, + unsigned int reg_mask) +{ + int ret = 0; + unsigned int temp = 0; + + ret = mcu_pmic_read(reg, &temp); + if (ret != 0) + return -1; + temp = (temp & (~reg_mask)) | reg_value; + + ret = mcu_pmic_write(reg, temp); + if (ret != 0) + return -1; + + pr_debug("Write REG[ %d ] = 0x%x\n", reg, reg_value); + + return ret; +} + +/*! + * make max8660 - mc9s08dz60 enter low-power mode + */ +static void pmic_power_off(void) +{ + mcu_pmic_write_reg(REG_MCU_POWER_CTL, 0x10, 0x10); +} + +static int __init mcu_pmic_init(void) +{ + int err; + + /* init chips */ + err = max8660_init(); + if (err) + goto fail1; + + err = mc9s08dz60_init(); + if (err) + goto fail1; + + if (is_max8660_present()) { + pr_info("max8660 is present \n"); + pm_power_off = pmic_power_off; + } else + pr_debug("max8660 is not present\n"); + pr_info("mcu_pmic_init completed!\n"); + return 0; + +fail1: + pr_err("mcu_pmic_init failed!\n"); + return err; +} + +static void __exit mcu_pmic_exit(void) +{ + reg_max8660_remove(); + mc9s08dz60_exit(); + max8660_exit(); +} + +subsys_initcall_sync(mcu_pmic_init); +module_exit(mcu_pmic_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("mcu pmic driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/mcu_pmic/mcu_pmic_core.h b/drivers/mxc/mcu_pmic/mcu_pmic_core.h new file mode 100644 index 000000000000..9ab07356f8a8 --- /dev/null +++ b/drivers/mxc/mcu_pmic/mcu_pmic_core.h @@ -0,0 +1,43 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mcu_pmic_core.h + * @brief Driver for max8660 + * + * @ingroup pmic + */ +#ifndef _MCU_PMIC_CORE_H_ +#define _MCU_PMIC_CORE_H_ + +#include <linux/mfd/mc9s08dz60/pmic.h> + +#define MAX8660_REG_START (REG_MCU_DES_FLAG + 1) +enum { + + /* reg names for max8660 */ + REG_MAX8660_OUTPUT_ENABLE_1 = MAX8660_REG_START, + REG_MAX8660_OUTPUT_ENABLE_2, + REG_MAX8660_VOLT_CHANGE_CONTROL_1, + REG_MAX8660_V3_TARGET_VOLT_1, + REG_MAX8660_V3_TARGET_VOLT_2, + REG_MAX8660_V4_TARGET_VOLT_1, + REG_MAX8660_V4_TARGET_VOLT_2, + REG_MAX8660_V5_TARGET_VOLT_1, + REG_MAX8660_V5_TARGET_VOLT_2, + REG_MAX8660_V6V7_TARGET_VOLT, + REG_MAX8660_FORCE_PWM +}; + + +#endif /* _MCU_PMIC_CORE_H_ */ diff --git a/drivers/mxc/mcu_pmic/mcu_pmic_gpio.c b/drivers/mxc/mcu_pmic/mcu_pmic_gpio.c new file mode 100644 index 000000000000..dae00495e2b2 --- /dev/null +++ b/drivers/mxc/mcu_pmic/mcu_pmic_gpio.c @@ -0,0 +1,131 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9s08dz60/mcu_pmic_gpio.c + * @brief This is the main file of mc9s08dz60 Power Control driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <linux/mfd/mc9s08dz60/pmic.h> +#include <linux/pmic_status.h> +#include <linux/ioctl.h> + +#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos)) +#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos)) + +int pmic_gpio_set_bit_val(int reg, unsigned int bit, + unsigned int val) +{ + int reg_name; + u8 reg_mask = 0; + + if (bit > 7) + return -1; + + switch (reg) { + case MCU_GPIO_REG_RESET_1: + reg_name = REG_MCU_RESET_1; + break; + case MCU_GPIO_REG_RESET_2: + reg_name = REG_MCU_RESET_2; + break; + case MCU_GPIO_REG_POWER_CONTROL: + reg_name = REG_MCU_POWER_CTL; + break; + case MCU_GPIO_REG_GPIO_CONTROL_1: + reg_name = REG_MCU_GPIO_1; + break; + case MCU_GPIO_REG_GPIO_CONTROL_2: + reg_name = REG_MCU_GPIO_2; + break; + default: + return -1; + } + + SET_BIT_IN_BYTE(reg_mask, bit); + if (0 == val) + CHECK_ERROR(mcu_pmic_write_reg(reg_name, 0, reg_mask)); + else + CHECK_ERROR(mcu_pmic_write_reg(reg_name, reg_mask, reg_mask)); + + return 0; +} +EXPORT_SYMBOL(pmic_gpio_set_bit_val); + +int pmic_gpio_get_bit_val(int reg, unsigned int bit, + unsigned int *val) +{ + int reg_name; + unsigned int reg_read_val; + u8 reg_mask = 0; + + if (bit > 7) + return -1; + + switch (reg) { + case MCU_GPIO_REG_RESET_1: + reg_name = REG_MCU_RESET_1; + break; + case MCU_GPIO_REG_RESET_2: + reg_name = REG_MCU_RESET_2; + break; + case MCU_GPIO_REG_POWER_CONTROL: + reg_name = REG_MCU_POWER_CTL; + break; + case MCU_GPIO_REG_GPIO_CONTROL_1: + reg_name = REG_MCU_GPIO_1; + break; + case MCU_GPIO_REG_GPIO_CONTROL_2: + reg_name = REG_MCU_GPIO_2; + break; + default: + return -1; + } + + SET_BIT_IN_BYTE(reg_mask, bit); + CHECK_ERROR(mcu_pmic_read_reg(reg_name, ®_read_val, reg_mask)); + if (0 == reg_read_val) + *val = 0; + else + *val = 1; + + return 0; +} +EXPORT_SYMBOL(pmic_gpio_get_bit_val); + +int pmic_gpio_get_designation_bit_val(unsigned int bit, + unsigned int *val) +{ + unsigned int reg_read_val; + u8 reg_mask = 0; + + if (bit > 7) + return -1; + + SET_BIT_IN_BYTE(reg_mask, bit); + CHECK_ERROR( + mcu_pmic_read_reg(REG_MCU_DES_FLAG, ®_read_val, reg_mask)); + if (0 == reg_read_val) + *val = 0; + else + *val = 1; + + return 0; +} +EXPORT_SYMBOL(pmic_gpio_get_designation_bit_val); diff --git a/drivers/mxc/mlb/Kconfig b/drivers/mxc/mlb/Kconfig new file mode 100644 index 000000000000..294c9776fb4d --- /dev/null +++ b/drivers/mxc/mlb/Kconfig @@ -0,0 +1,13 @@ +# +# MLB configuration +# + +menu "MXC Media Local Bus Driver" + +config MXC_MLB + tristate "MLB support" + depends on ARCH_MX35 + ---help--- + Say Y to get the MLB support. + +endmenu diff --git a/drivers/mxc/mlb/Makefile b/drivers/mxc/mlb/Makefile new file mode 100644 index 000000000000..60662eb1c031 --- /dev/null +++ b/drivers/mxc/mlb/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the kernel MLB driver +# + +obj-$(CONFIG_MXC_MLB) += mxc_mlb.o diff --git a/drivers/mxc/mlb/mxc_mlb.c b/drivers/mxc/mlb/mxc_mlb.c new file mode 100644 index 000000000000..233c9d5e731a --- /dev/null +++ b/drivers/mxc/mlb/mxc_mlb.c @@ -0,0 +1,1050 @@ +/* + * linux/drivers/mxc/mlb/mxc_mlb.c + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/cdev.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/mxc_mlb.h> +#include <linux/uaccess.h> + +#include <mach/hardware.h> +//#include <asm/irq.h> + +/*! + * MLB module memory map registers define + */ +#define MLB_REG_DCCR 0x0 +#define MLB_REG_SSCR 0x4 +#define MLB_REG_SDCR 0x8 +#define MLB_REG_SMCR 0xC +#define MLB_REG_VCCR 0x1C +#define MLB_REG_SBCR 0x20 +#define MLB_REG_ABCR 0x24 +#define MLB_REG_CBCR 0x28 +#define MLB_REG_IBCR 0x2C +#define MLB_REG_CICR 0x30 +#define MLB_REG_CECRn 0x40 +#define MLB_REG_CSCRn 0x44 +#define MLB_REG_CCBCRn 0x48 +#define MLB_REG_CNBCRn 0x4C +#define MLB_REG_LCBCRn 0x280 + +#define MLB_DCCR_FS_OFFSET 28 +#define MLB_DCCR_EN (1 << 31) +#define MLB_DCCR_LBM_OFFSET 30 +#define MLB_DCCR_RESET (1 << 23) +#define MLB_CECR_CE (1 << 31) +#define MLB_CECR_TR (1 << 30) +#define MLB_CECR_CT_OFFSET 28 +#define MLB_CECR_MBS (1 << 19) +#define MLB_CSCR_CBPE (1 << 0) +#define MLB_CSCR_CBDB (1 << 1) +#define MLB_CSCR_CBD (1 << 2) +#define MLB_CSCR_CBS (1 << 3) +#define MLB_CSCR_BE (1 << 4) +#define MLB_CSCR_ABE (1 << 5) +#define MLB_CSCR_LFS (1 << 6) +#define MLB_CSCR_PBPE (1 << 8) +#define MLB_CSCR_PBDB (1 << 9) +#define MLB_CSCR_PBD (1 << 10) +#define MLB_CSCR_PBS (1 << 11) +#define MLB_CSCR_RDY (1 << 16) +#define MLB_CSCR_BM (1 << 31) +#define MLB_CSCR_BF (1 << 30) +#define MLB_SSCR_SDML (1 << 5) + +#define MLB_CONTROL_TX_CHANN (0 << 4) +#define MLB_CONTROL_RX_CHANN (1 << 4) +#define MLB_ASYNC_TX_CHANN (2 << 4) +#define MLB_ASYNC_RX_CHANN (3 << 4) + +#define MLB_MINOR_DEVICES 2 +#define MLB_CONTROL_DEV_NAME "ctrl" +#define MLB_ASYNC_DEV_NAME "async" + +#define TX_CHANNEL 0 +#define RX_CHANNEL 1 +#define TX_CHANNEL_BUF_SIZE 1024 +#define RX_CHANNEL_BUF_SIZE 2*1024 +/* max package data size */ +#define ASYNC_PACKET_SIZE 1024 +#define CTRL_PACKET_SIZE 64 +#define RX_RING_NODES 10 + +#define _get_txchan(dev) mlb_devinfo[dev].channels[TX_CHANNEL] +#define _get_rxchan(dev) mlb_devinfo[dev].channels[RX_CHANNEL] + +enum { + MLB_CTYPE_SYNC, + MLB_CTYPE_ISOC, + MLB_CTYPE_ASYNC, + MLB_CTYPE_CTRL, +}; + +/*! + * Rx ring buffer + */ +struct mlb_rx_ringnode { + int size; + char *data; +}; + +struct mlb_channel_info { + + /* channel offset in memmap */ + const unsigned int reg_offset; + /* channel address */ + int address; + /*! + * channel buffer start address + * for Rx, buf_head pointer to a loop ring buffer + */ + unsigned long buf_head; + /* physical buffer head address */ + unsigned long phy_head; + /* channel buffer size */ + unsigned int buf_size; + /* channel buffer current ptr */ + unsigned long buf_ptr; + /* buffer spin lock */ + rwlock_t buf_lock; +}; + +struct mlb_dev_info { + + /* device node name */ + const char dev_name[20]; + /* channel type */ + const unsigned int channel_type; + /* channel info for tx/rx */ + struct mlb_channel_info channels[2]; + /* rx ring buffer */ + struct mlb_rx_ringnode rx_bufs[RX_RING_NODES]; + /* rx ring buffer read/write ptr */ + unsigned int rdpos, wtpos; + /* exception event */ + unsigned long ex_event; + /* channel started up or not */ + atomic_t on; + /* device open count */ + atomic_t opencnt; + /* wait queue head for channel */ + wait_queue_head_t rd_wq; + wait_queue_head_t wt_wq; + /* spinlock for event access */ + spinlock_t event_lock; +}; + +static struct mlb_dev_info mlb_devinfo[MLB_MINOR_DEVICES] = { + { + .dev_name = MLB_CONTROL_DEV_NAME, + .channel_type = MLB_CTYPE_CTRL, + .channels = { + [0] = { + .reg_offset = MLB_CONTROL_TX_CHANN, + .buf_size = TX_CHANNEL_BUF_SIZE, + .buf_lock = + __RW_LOCK_UNLOCKED(mlb_devinfo[0].channels[0]. + buf_lock), + }, + [1] = { + .reg_offset = MLB_CONTROL_RX_CHANN, + .buf_size = RX_CHANNEL_BUF_SIZE, + .buf_lock = + __RW_LOCK_UNLOCKED(mlb_devinfo[0].channels[1]. + buf_lock), + }, + }, + .on = ATOMIC_INIT(0), + .opencnt = ATOMIC_INIT(0), + .rd_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[0].rd_wq), + .wt_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[0].wt_wq), + .event_lock = __SPIN_LOCK_UNLOCKED(mlb_devinfo[0].event_lock), + }, + { + .dev_name = MLB_ASYNC_DEV_NAME, + .channel_type = MLB_CTYPE_ASYNC, + .channels = { + [0] = { + .reg_offset = MLB_ASYNC_TX_CHANN, + .buf_size = TX_CHANNEL_BUF_SIZE, + .buf_lock = + __RW_LOCK_UNLOCKED(mlb_devinfo[1].channels[0]. + buf_lock), + }, + [1] = { + .reg_offset = MLB_ASYNC_RX_CHANN, + .buf_size = RX_CHANNEL_BUF_SIZE, + .buf_lock = + __RW_LOCK_UNLOCKED(mlb_devinfo[1].channels[1]. + buf_lock), + }, + }, + .on = ATOMIC_INIT(0), + .opencnt = ATOMIC_INIT(0), + .rd_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[1].rd_wq), + .wt_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[1].wt_wq), + .event_lock = __SPIN_LOCK_UNLOCKED(mlb_devinfo[1].event_lock), + }, +}; + +static struct regulator *reg_nvcc; /* NVCC_MLB regulator */ +static struct clk *mlb_clk; +static struct cdev mxc_mlb_dev; /* chareset device */ +static dev_t dev; +static struct class *mlb_class; /* device class */ +static struct device *class_dev; +static unsigned long mlb_base; /* mlb module base address */ +static unsigned int irq; + +/*! + * Initial the MLB module device + */ +static void mlb_dev_init(void) +{ + unsigned long dccr_val; + unsigned long phyaddr; + + /* reset the MLB module */ + __raw_writel(MLB_DCCR_RESET, mlb_base + MLB_REG_DCCR); + while (__raw_readl(mlb_base + MLB_REG_DCCR) + & MLB_DCCR_RESET) ; + + /*! + * Enable MLB device, disable loopback mode, + * set default fps to 512, set mlb device address to 0 + */ + dccr_val = MLB_DCCR_EN; + __raw_writel(dccr_val, mlb_base + MLB_REG_DCCR); + + /* disable all the system interrupt */ + __raw_writel(0x5F, mlb_base + MLB_REG_SMCR); + + /* write async, control tx/rx base address */ + phyaddr = _get_txchan(0).phy_head >> 16; + __raw_writel(phyaddr << 16 | phyaddr, mlb_base + MLB_REG_CBCR); + phyaddr = _get_txchan(1).phy_head >> 16; + __raw_writel(phyaddr << 16 | phyaddr, mlb_base + MLB_REG_ABCR); + +} + +static void mlb_dev_exit(void) +{ + __raw_writel(0, mlb_base + MLB_REG_DCCR); +} + +/*! + * MLB receive start function + * + * load phy_head to next buf register to start next rx + * here use single-packet buffer, set start=end + */ +static void mlb_start_rx(int cdev_id) +{ + struct mlb_channel_info *chinfo = &_get_rxchan(cdev_id); + unsigned long next; + + next = chinfo->phy_head & 0xFFFC; + /* load next buf */ + __raw_writel((next << 16) | next, mlb_base + + MLB_REG_CNBCRn + chinfo->reg_offset); + /* set ready bit to start next rx */ + __raw_writel(MLB_CSCR_RDY, mlb_base + MLB_REG_CSCRn + + chinfo->reg_offset); +} + +/*! + * MLB transmit start function + * make sure aquiring the rw buf_lock, when calling this + */ +static void mlb_start_tx(int cdev_id) +{ + struct mlb_channel_info *chinfo = &_get_txchan(cdev_id); + unsigned long begin, end; + + begin = chinfo->phy_head; + end = (chinfo->phy_head + chinfo->buf_ptr - chinfo->buf_head) & 0xFFFC; + /* load next buf */ + __raw_writel((begin << 16) | end, mlb_base + + MLB_REG_CNBCRn + chinfo->reg_offset); + /* set ready bit to start next tx */ + __raw_writel(MLB_CSCR_RDY, mlb_base + MLB_REG_CSCRn + + chinfo->reg_offset); +} + +/*! + * Enable the MLB channel + */ +static void mlb_channel_enable(int chan_dev_id, int on) +{ + unsigned long tx_regval = 0, rx_regval = 0; + /*! + * setup the direction, enable, channel type, + * mode select, channel address and mask buf start + */ + if (on) { + unsigned int ctype = mlb_devinfo[chan_dev_id].channel_type; + tx_regval = MLB_CECR_CE | MLB_CECR_TR | MLB_CECR_MBS | + (ctype << MLB_CECR_CT_OFFSET) | + _get_txchan(chan_dev_id).address; + rx_regval = MLB_CECR_CE | MLB_CECR_MBS | + (ctype << MLB_CECR_CT_OFFSET) | + _get_rxchan(chan_dev_id).address; + + atomic_set(&mlb_devinfo[chan_dev_id].on, 1); + } else { + atomic_set(&mlb_devinfo[chan_dev_id].on, 0); + } + + /* update the rx/tx channel entry config */ + __raw_writel(tx_regval, mlb_base + MLB_REG_CECRn + + _get_txchan(chan_dev_id).reg_offset); + __raw_writel(rx_regval, mlb_base + MLB_REG_CECRn + + _get_rxchan(chan_dev_id).reg_offset); + + if (on) + mlb_start_rx(chan_dev_id); +} + +/*! + * MLB interrupt handler + */ +void mlb_tx_isr(int minor, unsigned int cis) +{ + struct mlb_channel_info *chinfo = &_get_txchan(minor); + + if (cis & MLB_CSCR_CBD) { + /* buffer done, reset the buf_ptr */ + write_lock(&chinfo->buf_lock); + chinfo->buf_ptr = chinfo->buf_head; + write_unlock(&chinfo->buf_lock); + /* wake up the writer */ + wake_up_interruptible(&mlb_devinfo[minor].wt_wq); + } +} + +void mlb_rx_isr(int minor, unsigned int cis) +{ + struct mlb_channel_info *chinfo = &_get_rxchan(minor); + unsigned long end; + unsigned int len; + + if (cis & MLB_CSCR_CBD) { + + int wpos, rpos; + + rpos = mlb_devinfo[minor].rdpos; + wpos = mlb_devinfo[minor].wtpos; + + /* buffer done, get current buffer ptr */ + end = + __raw_readl(mlb_base + MLB_REG_CCBCRn + chinfo->reg_offset); + end >>= 16; /* end here is phy */ + len = end - (chinfo->phy_head & 0xFFFC); + + /*! + * copy packet from IRAM buf to ring buf. + * if the wpos++ == rpos, drop this packet + */ + if (((wpos + 1) % RX_RING_NODES) != rpos) { + +#ifdef DEBUG + if (mlb_devinfo[minor].channel_type == MLB_CTYPE_CTRL) { + if (len > CTRL_PACKET_SIZE) + pr_debug + ("mxc_mlb: ctrl packet" + "overflow\n"); + } else { + if (len > ASYNC_PACKET_SIZE) + pr_debug + ("mxc_mlb: async packet" + "overflow\n"); + } +#endif + memcpy(mlb_devinfo[minor].rx_bufs[wpos].data, + (const void *)chinfo->buf_head, len); + mlb_devinfo[minor].rx_bufs[wpos].size = len; + + /* update the ring wpos */ + mlb_devinfo[minor].wtpos = (wpos + 1) % RX_RING_NODES; + + /* wake up the reader */ + wake_up_interruptible(&mlb_devinfo[minor].rd_wq); + + pr_debug("recv package, len:%d, rdpos: %d, wtpos: %d\n", + len, rpos, mlb_devinfo[minor].wtpos); + } else { + pr_debug + ("drop package, due to no space, (%d,%d)\n", + rpos, mlb_devinfo[minor].wtpos); + } + + /* start next rx */ + mlb_start_rx(minor); + } +} + +static irqreturn_t mlb_isr(int irq, void *dev_id) +{ + unsigned long int_status, sscr, tx_cis, rx_cis; + struct mlb_dev_info *pdev; + int minor; + + sscr = __raw_readl(mlb_base + MLB_REG_SSCR); + pr_debug("mxc_mlb: system interrupt:%lx\n", sscr); + __raw_writel(0x7F, mlb_base + MLB_REG_SSCR); + + int_status = __raw_readl(mlb_base + MLB_REG_CICR) & 0xFFFF; + pr_debug("mxc_mlb: channel interrupt ids: %lx\n", int_status); + + for (minor = 0; minor < MLB_MINOR_DEVICES; minor++) { + + pdev = &mlb_devinfo[minor]; + tx_cis = rx_cis = 0; + + /* get tx channel interrupt status */ + if (int_status & (1 << (_get_txchan(minor).reg_offset >> 4))) + tx_cis = __raw_readl(mlb_base + MLB_REG_CSCRn + + _get_txchan(minor).reg_offset); + /* get rx channel interrupt status */ + if (int_status & (1 << (_get_rxchan(minor).reg_offset >> 4))) + rx_cis = __raw_readl(mlb_base + MLB_REG_CSCRn + + _get_rxchan(minor).reg_offset); + + if (!tx_cis && !rx_cis) + continue; + + pr_debug("tx/rx int status: 0x%08lx/0x%08lx\n", tx_cis, rx_cis); + /* fill exception event */ + spin_lock(&pdev->event_lock); + pdev->ex_event |= tx_cis & 0x303; + pdev->ex_event |= (rx_cis & 0x303) << 16; + spin_unlock(&pdev->event_lock); + + /* clear the interrupt status */ + __raw_writel(tx_cis & 0xFFFF, mlb_base + MLB_REG_CSCRn + + _get_txchan(minor).reg_offset); + __raw_writel(rx_cis & 0xFFFF, mlb_base + MLB_REG_CSCRn + + _get_rxchan(minor).reg_offset); + + /* handel tx channel */ + if (tx_cis) + mlb_tx_isr(minor, tx_cis); + /* handle rx channel */ + if (rx_cis) + mlb_rx_isr(minor, rx_cis); + + } + + return IRQ_HANDLED; +} + +static int mxc_mlb_open(struct inode *inode, struct file *filp) +{ + int minor; + + minor = MINOR(inode->i_rdev); + + if (minor < 0 || minor >= MLB_MINOR_DEVICES) + return -ENODEV; + + /* open for each channel device */ + if (atomic_cmpxchg(&mlb_devinfo[minor].opencnt, 0, 1) != 0) + return -EBUSY; + + /* reset the buffer read/write ptr */ + _get_txchan(minor).buf_ptr = _get_txchan(minor).buf_head; + _get_rxchan(minor).buf_ptr = _get_rxchan(minor).buf_head; + mlb_devinfo[minor].rdpos = mlb_devinfo[minor].wtpos = 0; + mlb_devinfo[minor].ex_event = 0; + + return 0; +} + +static int mxc_mlb_release(struct inode *inode, struct file *filp) +{ + int minor; + + minor = MINOR(inode->i_rdev); + + /* clear channel settings and info */ + mlb_channel_enable(minor, 0); + + /* decrease the open count */ + atomic_set(&mlb_devinfo[minor].opencnt, 0); + + return 0; +} + +static int mxc_mlb_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + unsigned long flags, event; + int minor; + + minor = MINOR(inode->i_rdev); + + switch (cmd) { + + case MLB_CHAN_SETADDR: + { + unsigned int caddr; + /* get channel address from user space */ + if (copy_from_user(&caddr, argp, sizeof(caddr))) { + pr_err("mxc_mlb: copy from user failed\n"); + return -EFAULT; + } + _get_txchan(minor).address = (caddr >> 16) & 0xFFFF; + _get_rxchan(minor).address = caddr & 0xFFFF; + break; + } + + case MLB_CHAN_STARTUP: + if (atomic_read(&mlb_devinfo[minor].on)) { + pr_debug("mxc_mlb: channel areadly startup\n"); + break; + } + mlb_channel_enable(minor, 1); + break; + case MLB_CHAN_SHUTDOWN: + if (atomic_read(&mlb_devinfo[minor].on) == 0) { + pr_debug("mxc_mlb: channel areadly shutdown\n"); + break; + } + mlb_channel_enable(minor, 0); + break; + case MLB_CHAN_GETEVENT: + /* get and clear the ex_event */ + spin_lock_irqsave(&mlb_devinfo[minor].event_lock, flags); + event = mlb_devinfo[minor].ex_event; + mlb_devinfo[minor].ex_event = 0; + spin_unlock_irqrestore(&mlb_devinfo[minor].event_lock, flags); + + if (event) { + if (copy_to_user(argp, &event, sizeof(event))) { + pr_err("mxc_mlb: copy to user failed\n"); + return -EFAULT; + } + } else { + pr_debug("mxc_mlb: no exception event now\n"); + return -EAGAIN; + } + break; + case MLB_SET_FPS: + { + unsigned int fps; + unsigned long dccr_val; + + /* get fps from user space */ + if (copy_from_user(&fps, argp, sizeof(fps))) { + pr_err("mxc_mlb: copy from user failed\n"); + return -EFAULT; + } + + /* check fps value */ + if (fps != 256 && fps != 512 && fps != 1024) { + pr_debug("mxc_mlb: invalid fps argument\n"); + return -EINVAL; + } + + dccr_val = __raw_readl(mlb_base + MLB_REG_DCCR); + dccr_val &= ~(0x3 << MLB_DCCR_FS_OFFSET); + dccr_val |= (fps >> 9) << MLB_DCCR_FS_OFFSET; + __raw_writel(dccr_val, mlb_base + MLB_REG_DCCR); + break; + } + + case MLB_GET_VER: + { + unsigned long version; + + /* get MLB device module version */ + version = __raw_readl(mlb_base + MLB_REG_VCCR); + + if (copy_to_user(argp, &version, sizeof(version))) { + pr_err("mxc_mlb: copy to user failed\n"); + return -EFAULT; + } + break; + } + + case MLB_SET_DEVADDR: + { + unsigned long dccr_val; + unsigned char devaddr; + + /* get MLB device address from user space */ + if (copy_from_user + (&devaddr, argp, sizeof(unsigned char))) { + pr_err("mxc_mlb: copy from user failed\n"); + return -EFAULT; + } + + dccr_val = __raw_readl(mlb_base + MLB_REG_DCCR); + dccr_val &= ~0xFF; + dccr_val |= devaddr; + __raw_writel(dccr_val, mlb_base + MLB_REG_DCCR); + + break; + } + default: + pr_info("mxc_mlb: Invalid ioctl command\n"); + return -EINVAL; + } + + return 0; +} + +/*! + * MLB read routine + * + * Read the current received data from queued buffer, + * and free this buffer for hw to fill ingress data. + */ +static ssize_t mxc_mlb_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + int minor, ret; + int size, rdpos; + struct mlb_rx_ringnode *rxbuf; + + minor = MINOR(filp->f_dentry->d_inode->i_rdev); + + rdpos = mlb_devinfo[minor].rdpos; + rxbuf = mlb_devinfo[minor].rx_bufs; + + /* check the current rx buffer is available or not */ + if (rdpos == mlb_devinfo[minor].wtpos) { + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + /* if !O_NONBLOCK, we wait for recv packet */ + ret = wait_event_interruptible(mlb_devinfo[minor].rd_wq, + (mlb_devinfo[minor].wtpos != + rdpos)); + if (ret < 0) + return ret; + } + + size = rxbuf[rdpos].size; + if (size > count) { + /* the user buffer is too small */ + pr_warning + ("mxc_mlb: received data size is bigger than count\n"); + return -EINVAL; + } + + /* copy rx buffer data to user buffer */ + if (copy_to_user(buf, rxbuf[rdpos].data, size)) { + pr_err("mxc_mlb: copy from user failed\n"); + return -EFAULT; + } + + /* update the read ptr */ + mlb_devinfo[minor].rdpos = (rdpos + 1) % RX_RING_NODES; + + *f_pos = 0; + + return size; +} + +/*! + * MLB write routine + * + * Copy the user data to tx channel buffer, + * and prepare the channel current/next buffer ptr. + */ +static ssize_t mxc_mlb_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + int minor; + unsigned long flags; + DEFINE_WAIT(__wait); + int ret; + + minor = MINOR(filp->f_dentry->d_inode->i_rdev); + + if (count > _get_txchan(minor).buf_size) { + /* too many data to write */ + pr_warning("mxc_mlb: overflow write data\n"); + return -EFBIG; + } + + *f_pos = 0; + + /* check the current tx buffer is used or not */ + write_lock_irqsave(&_get_txchan(minor).buf_lock, flags); + if (_get_txchan(minor).buf_ptr != _get_txchan(minor).buf_head) { + write_unlock_irqrestore(&_get_txchan(minor).buf_lock, flags); + + /* there's already some datas being transmit now */ + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + + /* if !O_NONBLOCK, we wait for transmit finish */ + for (;;) { + prepare_to_wait(&mlb_devinfo[minor].wt_wq, + &__wait, TASK_INTERRUPTIBLE); + + write_lock_irqsave(&_get_txchan(minor).buf_lock, flags); + if (_get_txchan(minor).buf_ptr == + _get_txchan(minor).buf_head) + break; + + write_unlock_irqrestore(&_get_txchan(minor).buf_lock, + flags); + if (!signal_pending(current)) { + schedule(); + continue; + } + return -ERESTARTSYS; + } + finish_wait(&mlb_devinfo[minor].wt_wq, &__wait); + } + + /* copy user buffer to tx buffer */ + if (copy_from_user((void *)_get_txchan(minor).buf_ptr, buf, count)) { + pr_err("mxc_mlb: copy from user failed\n"); + ret = -EFAULT; + goto out; + } + _get_txchan(minor).buf_ptr += count; + + /* set current/next buffer start/end */ + mlb_start_tx(minor); + + ret = count; + +out: + write_unlock_irqrestore(&_get_txchan(minor).buf_lock, flags); + return ret; +} + +static unsigned int mxc_mlb_poll(struct file *filp, + struct poll_table_struct *wait) +{ + int minor; + unsigned int ret = 0; + unsigned long flags; + + minor = MINOR(filp->f_dentry->d_inode->i_rdev); + + poll_wait(filp, &mlb_devinfo[minor].rd_wq, wait); + poll_wait(filp, &mlb_devinfo[minor].wt_wq, wait); + + /* check the tx buffer is avaiable or not */ + read_lock_irqsave(&_get_txchan(minor).buf_lock, flags); + if (_get_txchan(minor).buf_ptr == _get_txchan(minor).buf_head) + ret |= POLLOUT | POLLWRNORM; + read_unlock_irqrestore(&_get_txchan(minor).buf_lock, flags); + + /* check the rx buffer filled or not */ + if (mlb_devinfo[minor].rdpos != mlb_devinfo[minor].wtpos) + ret |= POLLIN | POLLRDNORM; + + /* check the exception event */ + if (mlb_devinfo[minor].ex_event) + ret |= POLLIN | POLLRDNORM; + + return ret; +} + +/*! + * char dev file operations structure + */ +static struct file_operations mxc_mlb_fops = { + + .owner = THIS_MODULE, + .open = mxc_mlb_open, + .release = mxc_mlb_release, + .ioctl = mxc_mlb_ioctl, + .poll = mxc_mlb_poll, + .read = mxc_mlb_read, + .write = mxc_mlb_write, +}; + +/*! + * This function is called whenever the MLB device is detected. + */ +static int __devinit mxc_mlb_probe(struct platform_device *pdev) +{ + int ret, mlb_major, i, j; + struct mxc_mlb_platform_data *plat_data; + struct resource *res; + void __iomem *base; + unsigned long bufaddr, phyaddr; + + /* malloc the Rx ring buffer firstly */ + for (i = 0; i < MLB_MINOR_DEVICES; i++) { + char *buf; + int bufsize; + + if (mlb_devinfo[i].channel_type == MLB_CTYPE_ASYNC) + bufsize = ASYNC_PACKET_SIZE; + else + bufsize = CTRL_PACKET_SIZE; + + buf = kmalloc(bufsize * RX_RING_NODES, GFP_KERNEL); + if (buf == NULL) { + ret = -ENOMEM; + dev_err(&pdev->dev, "can not alloc rx buffers\n"); + goto err4; + } + for (j = 0; j < RX_RING_NODES; j++) { + mlb_devinfo[i].rx_bufs[j].data = buf; + buf += bufsize; + } + } + + /** + * Register MLB lld as two character devices + * One for Packet date channel, the other for control data channel + */ + ret = alloc_chrdev_region(&dev, 0, MLB_MINOR_DEVICES, "mxc_mlb"); + mlb_major = MAJOR(dev); + + if (ret < 0) { + dev_err(&pdev->dev, "can't get major %d\n", mlb_major); + goto err3; + } + + cdev_init(&mxc_mlb_dev, &mxc_mlb_fops); + mxc_mlb_dev.owner = THIS_MODULE; + + ret = cdev_add(&mxc_mlb_dev, dev, MLB_MINOR_DEVICES); + if (ret) { + dev_err(&pdev->dev, "can't add cdev\n"); + goto err2; + } + + /* create class and device for udev information */ + mlb_class = class_create(THIS_MODULE, "mlb"); + if (IS_ERR(mlb_class)) { + dev_err(&pdev->dev, "failed to create mlb class\n"); + ret = -ENOMEM; + goto err2; + } + + for (i = 0; i < MLB_MINOR_DEVICES; i++) { + + class_dev = device_create(mlb_class, NULL, MKDEV(mlb_major, i), + NULL, mlb_devinfo[i].dev_name); + if (IS_ERR(class_dev)) { + dev_err(&pdev->dev, "failed to create mlb %s" + " class device\n", mlb_devinfo[i].dev_name); + ret = -ENOMEM; + goto err1; + } + } + + /* get irq line */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No mlb irq line provided\n"); + goto err1; + } + + irq = res->start; + /* request irq */ + if (request_irq(irq, mlb_isr, 0, "mlb", NULL)) { + dev_err(&pdev->dev, "failed to request irq\n"); + ret = -EBUSY; + goto err1; + } + + /* ioremap from phy mlb to kernel space */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No mlb base address provided\n"); + goto err0; + } + + base = ioremap(res->start, res->end - res->start); + dev_dbg(&pdev->dev, "mapped mlb base address: 0x%08x\n", + (unsigned int)base); + + if (base == NULL) { + dev_err(&pdev->dev, "failed to do ioremap with mlb base\n"); + goto err0; + } + mlb_base = (unsigned long)base; + + /*! + * get rx/tx buffer address from platform data + * make sure the buf_address is 4bytes aligned + * + * ------------------- <-- plat_data->buf_address + * | minor 0 tx buf | + * ----------------- + * | minor 0 rx buf | + * ----------------- + * | .... | + * ----------------- + * | minor n tx buf | + * ----------------- + * | minor n rx buf | + * ------------------- + */ + + plat_data = (struct mxc_mlb_platform_data *)pdev->dev.platform_data; + + bufaddr = plat_data->buf_address & ~0x3; + phyaddr = plat_data->phy_address & ~0x3; + + for (i = 0; i < MLB_MINOR_DEVICES; i++) { + /* set the virtual and physical buf head address */ + _get_txchan(i).buf_head = bufaddr; + _get_txchan(i).phy_head = phyaddr; + + bufaddr += TX_CHANNEL_BUF_SIZE; + phyaddr += TX_CHANNEL_BUF_SIZE; + + _get_rxchan(i).buf_head = bufaddr; + _get_rxchan(i).phy_head = phyaddr; + + bufaddr += RX_CHANNEL_BUF_SIZE; + phyaddr += RX_CHANNEL_BUF_SIZE; + + dev_dbg(&pdev->dev, "phy_head: tx(%lx), rx(%lx)\n", + _get_txchan(i).phy_head, _get_rxchan(i).phy_head); + dev_dbg(&pdev->dev, "buf_head: tx(%lx), rx(%lx)\n", + _get_txchan(i).buf_head, _get_rxchan(i).buf_head); + } + + /* enable GPIO */ + gpio_mlb_active(); + + /* power on MLB */ + reg_nvcc = regulator_get(&pdev->dev, plat_data->reg_nvcc); + /* set MAX LDO6 for NVCC to 2.5V */ + regulator_set_voltage(reg_nvcc, 2500000, 2500000); + regulator_enable(reg_nvcc); + + /* enable clock */ + mlb_clk = clk_get(&pdev->dev, plat_data->mlb_clk); + clk_enable(mlb_clk); + + /* initial MLB module */ + mlb_dev_init(); + + return 0; + +err0: + free_irq(irq, NULL); +err1: + for (--i; i >= 0; i--) + device_destroy(mlb_class, MKDEV(mlb_major, i)); + + class_destroy(mlb_class); +err2: + cdev_del(&mxc_mlb_dev); +err3: + unregister_chrdev_region(dev, MLB_MINOR_DEVICES); +err4: + for (i = 0; i < MLB_MINOR_DEVICES; i++) + kfree(mlb_devinfo[i].rx_bufs[0].data); + + return ret; +} + +static int __devexit mxc_mlb_remove(struct platform_device *pdev) +{ + int i; + + mlb_dev_exit(); + + /* disable mlb clock */ + clk_disable(mlb_clk); + clk_put(mlb_clk); + + /* disable mlb power */ + regulator_disable(reg_nvcc); + regulator_put(reg_nvcc); + + /* inactive GPIO */ + gpio_mlb_inactive(); + + /* iounmap */ + iounmap((void *)mlb_base); + + free_irq(irq, NULL); + + /* destroy mlb device class */ + for (i = MLB_MINOR_DEVICES - 1; i >= 0; i--) + device_destroy(mlb_class, MKDEV(MAJOR(dev), i)); + class_destroy(mlb_class); + + /* Unregister the two MLB devices */ + cdev_del(&mxc_mlb_dev); + unregister_chrdev_region(dev, MLB_MINOR_DEVICES); + + for (i = 0; i < MLB_MINOR_DEVICES; i++) + kfree(mlb_devinfo[i].rx_bufs[0].data); + + return 0; +} + +#ifdef CONFIG_PM +static int mxc_mlb_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int mxc_mlb_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define mxc_mlb_suspend NULL +#define mxc_mlb_resume NULL +#endif + +/*! + * platform driver structure for MLB + */ +static struct platform_driver mxc_mlb_driver = { + .driver = { + .name = "mxc_mlb"}, + .probe = mxc_mlb_probe, + .remove = __devexit_p(mxc_mlb_remove), + .suspend = mxc_mlb_suspend, + .resume = mxc_mlb_resume, +}; + +static int __init mxc_mlb_init(void) +{ + return platform_driver_register(&mxc_mlb_driver); +} + +static void __exit mxc_mlb_exit(void) +{ + platform_driver_unregister(&mxc_mlb_driver); +} + +module_init(mxc_mlb_init); +module_exit(mxc_mlb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MLB low level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/Kconfig b/drivers/mxc/pmic/Kconfig new file mode 100644 index 000000000000..4ee91110fc03 --- /dev/null +++ b/drivers/mxc/pmic/Kconfig @@ -0,0 +1,64 @@ +# +# PMIC device driver configuration +# + +menu "MXC PMIC support" + +config MXC_PMIC + boolean + +config MXC_PMIC_MC13783 + tristate "MC13783 PMIC" + depends on ARCH_MXC && SPI + select MXC_PMIC + ---help--- + This is the MXC MC13783(PMIC) support. It include + ADC, Audio, Battery, Connectivity, Light, Power and RTC. + +config MXC_PMIC_MC13892 + tristate "MC13892 PMIC" + depends on ARCH_MXC && (I2C || SPI) + select MXC_PMIC + ---help--- + This is the MXC MC13892(PMIC) support. It include + ADC, Battery, Connectivity, Light, Power and RTC. + +config MXC_PMIC_I2C + bool "Support PMIC I2C Interface" + depends on MXC_PMIC_MC13892 && I2C + +config MXC_PMIC_SPI + bool "Support PMIC SPI Interface" + depends on (MXC_PMIC_MC13892 || MXC_PMIC_MC13783) && SPI + +config MXC_PMIC_MC34704 + tristate "MC34704 PMIC" + depends on ARCH_MXC && I2C + select MXC_PMIC + ---help--- + This is the MXC MC34704 PMIC support. + +config MXC_PMIC_MC9SDZ60 + tristate "MC9sDZ60 PMIC" + depends on ARCH_MXC && I2C + select MXC_PMIC + ---help--- + This is the MXC MC9sDZ60(MCU) PMIC support. + +config MXC_PMIC_CHARDEV + tristate "MXC PMIC device interface" + depends on MXC_PMIC + help + Say Y here to use "pmic" device files, found in the /dev directory + on the system. They make it possible to have user-space programs + use or controll PMIC. Mainly its useful for notifying PMIC events + to user-space programs. + +comment "MXC PMIC Client Drivers" + depends on MXC_PMIC + +source "drivers/mxc/pmic/mc13783/Kconfig" + +source "drivers/mxc/pmic/mc13892/Kconfig" + +endmenu diff --git a/drivers/mxc/pmic/Makefile b/drivers/mxc/pmic/Makefile new file mode 100644 index 000000000000..385c07e8509f --- /dev/null +++ b/drivers/mxc/pmic/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the MXC PMIC drivers. +# + +obj-y += core/ +obj-$(CONFIG_MXC_PMIC_MC13783) += mc13783/ +obj-$(CONFIG_MXC_PMIC_MC13892) += mc13892/ diff --git a/drivers/mxc/pmic/core/Makefile b/drivers/mxc/pmic/core/Makefile new file mode 100644 index 000000000000..bb42231e3aa8 --- /dev/null +++ b/drivers/mxc/pmic/core/Makefile @@ -0,0 +1,21 @@ +# +# Makefile for the PMIC core drivers. +# +obj-$(CONFIG_MXC_PMIC_MC13783) += pmic_mc13783_mod.o +pmic_mc13783_mod-objs := pmic_external.o pmic_event.o pmic_common.o pmic_core_spi.o mc13783.o + +obj-$(CONFIG_MXC_PMIC_MC13892) += pmic_mc13892_mod.o +pmic_mc13892_mod-objs := pmic_external.o pmic_event.o pmic_common.o mc13892.o + +ifneq ($(CONFIG_MXC_PMIC_SPI),) +pmic_mc13892_mod-objs += pmic_core_spi.o +endif + +ifneq ($(CONFIG_MXC_PMIC_I2C),) +pmic_mc13892_mod-objs += pmic_core_i2c.o +endif + +obj-$(CONFIG_MXC_PMIC_MC34704) += pmic_mc34704_mod.o +pmic_mc34704_mod-objs := pmic_external.o pmic_event.o mc34704.o + +obj-$(CONFIG_MXC_PMIC_CHARDEV) += pmic-dev.o diff --git a/drivers/mxc/pmic/core/mc13783.c b/drivers/mxc/pmic/core/mc13783.c new file mode 100644 index 000000000000..179cad815ebf --- /dev/null +++ b/drivers/mxc/pmic/core/mc13783.c @@ -0,0 +1,380 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic/core/mc13783.c + * @brief This file contains MC13783 specific PMIC code. This implementaion + * may differ for each PMIC chip. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> +#include <linux/spi/spi.h> +#include <linux/mfd/mc13783/core.h> + +#include <asm/uaccess.h> + +#include "pmic.h" + +/* + * Defines + */ +#define EVENT_MASK_0 0x697fdf +#define EVENT_MASK_1 0x3efffb +#define MXC_PMIC_FRAME_MASK 0x00FFFFFF +#define MXC_PMIC_MAX_REG_NUM 0x3F +#define MXC_PMIC_REG_NUM_SHIFT 0x19 +#define MXC_PMIC_WRITE_BIT_SHIFT 31 + +static unsigned int events_enabled0 = 0; +static unsigned int events_enabled1 = 0; +struct mxc_pmic pmic_drv_data; + +/*! + * This function is called to read a register on PMIC. + * + * @param reg_num number of the pmic register to be read + * @param reg_val return value of register + * + * @return Returns 0 on success -1 on failure. + */ +int pmic_read(unsigned int reg_num, unsigned int *reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (reg_num > MXC_PMIC_MAX_REG_NUM) + return PMIC_ERROR; + + frame |= reg_num << MXC_PMIC_REG_NUM_SHIFT; + + ret = spi_rw(pmic_drv_data.spi, (u8 *) & frame, 1); + + *reg_val = frame & MXC_PMIC_FRAME_MASK; + + return ret; +} + +/*! + * This function is called to write a value to the register on PMIC. + * + * @param reg_num number of the pmic register to be written + * @param reg_val value to be written + * + * @return Returns 0 on success -1 on failure. + */ +int pmic_write(int reg_num, const unsigned int reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (reg_num > MXC_PMIC_MAX_REG_NUM) + return PMIC_ERROR; + + frame |= (1 << MXC_PMIC_WRITE_BIT_SHIFT); + + frame |= reg_num << MXC_PMIC_REG_NUM_SHIFT; + + frame |= reg_val & MXC_PMIC_FRAME_MASK; + + ret = spi_rw(pmic_drv_data.spi, (u8 *) & frame, 1); + + return ret; +} + +void *pmic_alloc_data(struct device *dev) +{ + struct mc13783 *mc13783; + + mc13783 = kzalloc(sizeof(struct mc13783), GFP_KERNEL); + if (mc13783 == NULL) + return NULL; + + mc13783->dev = dev; + + return (void *)mc13783; +} + +/*! + * This function initializes the SPI device parameters for this PMIC. + * + * @param spi the SPI slave device(PMIC) + * + * @return None + */ +int pmic_spi_setup(struct spi_device *spi) +{ + /* Setup the SPI slave i.e.PMIC */ + pmic_drv_data.spi = spi; + + spi->mode = SPI_MODE_2 | SPI_CS_HIGH; + spi->bits_per_word = 32; + + return spi_setup(spi); +} + +/*! + * This function initializes the PMIC registers. + * + * @return None + */ +int pmic_init_registers(void) +{ + CHECK_ERROR(pmic_write(REG_INTERRUPT_MASK_0, MXC_PMIC_FRAME_MASK)); + CHECK_ERROR(pmic_write(REG_INTERRUPT_MASK_1, MXC_PMIC_FRAME_MASK)); + CHECK_ERROR(pmic_write(REG_INTERRUPT_STATUS_0, MXC_PMIC_FRAME_MASK)); + CHECK_ERROR(pmic_write(REG_INTERRUPT_STATUS_1, MXC_PMIC_FRAME_MASK)); + return PMIC_SUCCESS; +} + +/*! + * This function returns the PMIC version in system. + * + * @param ver pointer to the pmic_version_t structure + * + * @return This function returns PMIC version. + */ +void pmic_get_revision(pmic_version_t * ver) +{ + int rev_id = 0; + int rev1 = 0; + int rev2 = 0; + int finid = 0; + int icid = 0; + + ver->id = PMIC_MC13783; + pmic_read(REG_REVISION, &rev_id); + + rev1 = (rev_id & 0x018) >> 3; + rev2 = (rev_id & 0x007); + icid = (rev_id & 0x01C0) >> 6; + finid = (rev_id & 0x01E00) >> 9; + + /* Ver 0.2 is actually 3.2a. Report as 3.2 */ + if ((rev1 == 0) && (rev2 == 2)) { + rev1 = 3; + } + + if (rev1 == 0 || icid != 2) { + ver->revision = -1; + printk(KERN_NOTICE + "mc13783: Not detected.\tAccess failed\t!!!\n"); + } else { + ver->revision = ((rev1 * 10) + rev2); + printk(KERN_INFO "mc13783 Rev %d.%d FinVer %x detected\n", rev1, + rev2, finid); + } + + return; + +} + +/*! + * This function reads the interrupt status registers of PMIC + * and determine the current active events. + * + * @param active_events array pointer to be used to return active + * event numbers. + * + * @return This function returns PMIC version. + */ +unsigned int pmic_get_active_events(unsigned int *active_events) +{ + unsigned int count = 0; + unsigned int status0, status1; + int bit_set; + + pmic_read(REG_INTERRUPT_STATUS_0, &status0); + pmic_read(REG_INTERRUPT_STATUS_1, &status1); + pmic_write(REG_INTERRUPT_STATUS_0, status0); + pmic_write(REG_INTERRUPT_STATUS_1, status1); + status0 &= events_enabled0; + status1 &= events_enabled1; + + while (status0) { + bit_set = ffs(status0) - 1; + *(active_events + count) = bit_set; + count++; + status0 ^= (1 << bit_set); + } + while (status1) { + bit_set = ffs(status1) - 1; + *(active_events + count) = bit_set + 24; + count++; + status1 ^= (1 << bit_set); + } + + return count; +} + +/*! + * This function unsets a bit in mask register of pmic to unmask an event IT. + * + * @param event the event to be unmasked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_unmask(type_event event) +{ + unsigned int event_mask = 0; + unsigned int mask_reg = 0; + unsigned int event_bit = 0; + int ret; + + if (event < EVENT_E1HZI) { + mask_reg = REG_INTERRUPT_MASK_0; + event_mask = EVENT_MASK_0; + event_bit = (1 << event); + events_enabled0 |= event_bit; + } else { + event -= 24; + mask_reg = REG_INTERRUPT_MASK_1; + event_mask = EVENT_MASK_1; + event_bit = (1 << event); + events_enabled1 |= event_bit; + } + + if ((event_bit & event_mask) == 0) { + pr_debug("Error: unmasking a reserved/unused event\n"); + return PMIC_ERROR; + } + + ret = pmic_write_reg(mask_reg, 0, event_bit); + + pr_debug("Enable Event : %d\n", event); + + return ret; +} + +/*! + * This function sets a bit in mask register of pmic to disable an event IT. + * + * @param event the event to be masked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_mask(type_event event) +{ + unsigned int event_mask = 0; + unsigned int mask_reg = 0; + unsigned int event_bit = 0; + int ret; + + if (event < EVENT_E1HZI) { + mask_reg = REG_INTERRUPT_MASK_0; + event_mask = EVENT_MASK_0; + event_bit = (1 << event); + events_enabled0 &= ~event_bit; + } else { + event -= 24; + mask_reg = REG_INTERRUPT_MASK_1; + event_mask = EVENT_MASK_1; + event_bit = (1 << event); + events_enabled1 &= ~event_bit; + } + + if ((event_bit & event_mask) == 0) { + pr_debug("Error: masking a reserved/unused event\n"); + return PMIC_ERROR; + } + + ret = pmic_write_reg(mask_reg, event_bit, event_bit); + + pr_debug("Disable Event : %d\n", event); + + return ret; +} + +/*! + * This function is called to read all sensor bits of PMIC. + * + * @param sensor Sensor to be checked. + * + * @return This function returns true if the sensor bit is high; + * or returns false if the sensor bit is low. + */ +bool pmic_check_sensor(t_sensor sensor) +{ + unsigned int reg_val = 0; + + CHECK_ERROR(pmic_read_reg + (REG_INTERRUPT_SENSE_0, ®_val, PMIC_ALL_BITS)); + + if ((1 << sensor) & reg_val) + return true; + else + return false; +} + +/*! + * This function checks one sensor of PMIC. + * + * @param sensor_bits structure of all sensor bits. + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ + +PMIC_STATUS pmic_get_sensors(t_sensor_bits * sensor_bits) +{ + int sense_0 = 0; + int sense_1 = 0; + + memset(sensor_bits, 0, sizeof(t_sensor_bits)); + + pmic_read_reg(REG_INTERRUPT_SENSE_0, &sense_0, 0xffffff); + pmic_read_reg(REG_INTERRUPT_SENSE_1, &sense_1, 0xffffff); + + sensor_bits->sense_chgdets = (sense_0 & (1 << 6)) ? true : false; + sensor_bits->sense_chgovs = (sense_0 & (1 << 7)) ? true : false; + sensor_bits->sense_chgrevs = (sense_0 & (1 << 8)) ? true : false; + sensor_bits->sense_chgshorts = (sense_0 & (1 << 9)) ? true : false; + sensor_bits->sense_cccvs = (sense_0 & (1 << 10)) ? true : false; + sensor_bits->sense_chgcurrs = (sense_0 & (1 << 11)) ? true : false; + sensor_bits->sense_bpons = (sense_0 & (1 << 12)) ? true : false; + sensor_bits->sense_lobatls = (sense_0 & (1 << 13)) ? true : false; + sensor_bits->sense_lobaths = (sense_0 & (1 << 14)) ? true : false; + sensor_bits->sense_usb4v4s = (sense_0 & (1 << 16)) ? true : false; + sensor_bits->sense_usb2v0s = (sense_0 & (1 << 17)) ? true : false; + sensor_bits->sense_usb0v8s = (sense_0 & (1 << 18)) ? true : false; + sensor_bits->sense_id_floats = (sense_0 & (1 << 19)) ? true : false; + sensor_bits->sense_id_gnds = (sense_0 & (1 << 20)) ? true : false; + sensor_bits->sense_se1s = (sense_0 & (1 << 21)) ? true : false; + sensor_bits->sense_ckdets = (sense_0 & (1 << 22)) ? true : false; + + sensor_bits->sense_onofd1s = (sense_1 & (1 << 3)) ? true : false; + sensor_bits->sense_onofd2s = (sense_1 & (1 << 4)) ? true : false; + sensor_bits->sense_onofd3s = (sense_1 & (1 << 5)) ? true : false; + sensor_bits->sense_pwrrdys = (sense_1 & (1 << 11)) ? true : false; + sensor_bits->sense_thwarnhs = (sense_1 & (1 << 12)) ? true : false; + sensor_bits->sense_thwarnls = (sense_1 & (1 << 13)) ? true : false; + sensor_bits->sense_clks = (sense_1 & (1 << 14)) ? true : false; + sensor_bits->sense_mc2bs = (sense_1 & (1 << 17)) ? true : false; + sensor_bits->sense_hsdets = (sense_1 & (1 << 18)) ? true : false; + sensor_bits->sense_hsls = (sense_1 & (1 << 19)) ? true : false; + sensor_bits->sense_alspths = (sense_1 & (1 << 20)) ? true : false; + sensor_bits->sense_ahsshorts = (sense_1 & (1 << 21)) ? true : false; + return PMIC_SUCCESS; +} + +EXPORT_SYMBOL(pmic_check_sensor); +EXPORT_SYMBOL(pmic_get_sensors); diff --git a/drivers/mxc/pmic/core/mc13892.c b/drivers/mxc/pmic/core/mc13892.c new file mode 100644 index 000000000000..34ceec59221e --- /dev/null +++ b/drivers/mxc/pmic/core/mc13892.c @@ -0,0 +1,333 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic/core/mc13892.c + * @brief This file contains MC13892 specific PMIC code. This implementaion + * may differ for each PMIC chip. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> +#include <linux/mfd/mc13892/core.h> + +#include <asm/mach-types.h> +#include <asm/uaccess.h> + +#include "pmic.h" + +/* + * Defines + */ +#define MC13892_I2C_RETRY_TIMES 10 +#define MXC_PMIC_FRAME_MASK 0x00FFFFFF +#define MXC_PMIC_MAX_REG_NUM 0x3F +#define MXC_PMIC_REG_NUM_SHIFT 0x19 +#define MXC_PMIC_WRITE_BIT_SHIFT 31 + +static unsigned int events_enabled0; +static unsigned int events_enabled1; +static struct mxc_pmic pmic_drv_data; +#ifndef CONFIG_MXC_PMIC_I2C +struct i2c_client *mc13892_client; +#endif + +int pmic_i2c_24bit_read(struct i2c_client *client, unsigned int reg_num, + unsigned int *value) +{ + unsigned char buf[3]; + int ret; + int i; + + memset(buf, 0, 3); + for (i = 0; i < MC13892_I2C_RETRY_TIMES; i++) { + ret = i2c_smbus_read_i2c_block_data(client, reg_num, 3, buf); + if (ret == 3) + break; + msleep(1); + } + + if (ret == 3) { + *value = buf[0] << 16 | buf[1] << 8 | buf[2]; + return ret; + } else { + pr_debug("24bit read error, ret = %d\n", ret); + return -1; /* return -1 on failure */ + } +} + +int pmic_i2c_24bit_write(struct i2c_client *client, + unsigned int reg_num, unsigned int reg_val) +{ + char buf[3]; + int ret; + int i; + + buf[0] = (reg_val >> 16) & 0xff; + buf[1] = (reg_val >> 8) & 0xff; + buf[2] = (reg_val) & 0xff; + + for (i = 0; i < MC13892_I2C_RETRY_TIMES; i++) { + ret = i2c_smbus_write_i2c_block_data(client, reg_num, 3, buf); + if (ret == 0) + break; + msleep(1); + } + + return ret; +} + +int pmic_read(int reg_num, unsigned int *reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (pmic_drv_data.spi != NULL) { + if (reg_num > MXC_PMIC_MAX_REG_NUM) + return PMIC_ERROR; + + frame |= reg_num << MXC_PMIC_REG_NUM_SHIFT; + + ret = spi_rw(pmic_drv_data.spi, (u8 *) &frame, 1); + + *reg_val = frame & MXC_PMIC_FRAME_MASK; + } else { + if (mc13892_client == NULL) + return PMIC_ERROR; + + if (pmic_i2c_24bit_read(mc13892_client, reg_num, reg_val) == -1) + return PMIC_ERROR; + } + + return PMIC_SUCCESS; +} + +int pmic_write(int reg_num, const unsigned int reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (pmic_drv_data.spi != NULL) { + if (reg_num > MXC_PMIC_MAX_REG_NUM) + return PMIC_ERROR; + + frame |= (1 << MXC_PMIC_WRITE_BIT_SHIFT); + + frame |= reg_num << MXC_PMIC_REG_NUM_SHIFT; + + frame |= reg_val & MXC_PMIC_FRAME_MASK; + + ret = spi_rw(pmic_drv_data.spi, (u8 *) &frame, 1); + + return ret; + } else { + if (mc13892_client == NULL) + return PMIC_ERROR; + + return pmic_i2c_24bit_write(mc13892_client, reg_num, reg_val); + } +} + +void *pmic_alloc_data(struct device *dev) +{ + struct mc13892 *mc13892; + + mc13892 = kzalloc(sizeof(struct mc13892), GFP_KERNEL); + if (mc13892 == NULL) + return NULL; + + mc13892->dev = dev; + + return (void *)mc13892; +} + +/*! + * This function initializes the SPI device parameters for this PMIC. + * + * @param spi the SPI slave device(PMIC) + * + * @return None + */ +int pmic_spi_setup(struct spi_device *spi) +{ + /* Setup the SPI slave i.e.PMIC */ + pmic_drv_data.spi = spi; + + spi->mode = SPI_MODE_0 | SPI_CS_HIGH; + spi->bits_per_word = 32; + + return spi_setup(spi); +} + +int pmic_init_registers(void) +{ + CHECK_ERROR(pmic_write(REG_INT_MASK0, 0xFFFFFF)); + CHECK_ERROR(pmic_write(REG_INT_MASK0, 0xFFFFFF)); + CHECK_ERROR(pmic_write(REG_INT_STATUS0, 0xFFFFFF)); + CHECK_ERROR(pmic_write(REG_INT_STATUS1, 0xFFFFFF)); + /* disable auto charge */ + if (machine_is_mx51_3ds()) + CHECK_ERROR(pmic_write(REG_CHARGE, 0xB40003)); + + pm_power_off = mc13892_power_off; + + return PMIC_SUCCESS; +} + +unsigned int pmic_get_active_events(unsigned int *active_events) +{ + unsigned int count = 0; + unsigned int status0, status1; + int bit_set; + + pmic_read(REG_INT_STATUS0, &status0); + pmic_read(REG_INT_STATUS1, &status1); + pmic_write(REG_INT_STATUS0, status0); + pmic_write(REG_INT_STATUS1, status1); + status0 &= events_enabled0; + status1 &= events_enabled1; + + while (status0) { + bit_set = ffs(status0) - 1; + *(active_events + count) = bit_set; + count++; + status0 ^= (1 << bit_set); + } + while (status1) { + bit_set = ffs(status1) - 1; + *(active_events + count) = bit_set + 24; + count++; + status1 ^= (1 << bit_set); + } + + return count; +} + +#define EVENT_MASK_0 0x387fff +#define EVENT_MASK_1 0x1177eb + +int pmic_event_unmask(type_event event) +{ + unsigned int event_mask = 0; + unsigned int mask_reg = 0; + unsigned int event_bit = 0; + int ret; + + if (event < EVENT_1HZI) { + mask_reg = REG_INT_MASK0; + event_mask = EVENT_MASK_0; + event_bit = (1 << event); + events_enabled0 |= event_bit; + } else { + event -= 24; + mask_reg = REG_INT_MASK1; + event_mask = EVENT_MASK_1; + event_bit = (1 << event); + events_enabled1 |= event_bit; + } + + if ((event_bit & event_mask) == 0) { + pr_debug("Error: unmasking a reserved/unused event\n"); + return PMIC_ERROR; + } + + ret = pmic_write_reg(mask_reg, 0, event_bit); + + pr_debug("Enable Event : %d\n", event); + + return ret; +} + +int pmic_event_mask(type_event event) +{ + unsigned int event_mask = 0; + unsigned int mask_reg = 0; + unsigned int event_bit = 0; + int ret; + + if (event < EVENT_1HZI) { + mask_reg = REG_INT_MASK0; + event_mask = EVENT_MASK_0; + event_bit = (1 << event); + events_enabled0 &= ~event_bit; + } else { + event -= 24; + mask_reg = REG_INT_MASK1; + event_mask = EVENT_MASK_1; + event_bit = (1 << event); + events_enabled1 &= ~event_bit; + } + + if ((event_bit & event_mask) == 0) { + pr_debug("Error: masking a reserved/unused event\n"); + return PMIC_ERROR; + } + + ret = pmic_write_reg(mask_reg, event_bit, event_bit); + + pr_debug("Disable Event : %d\n", event); + + return ret; +} + +/*! + * This function returns the PMIC version in system. + * + * @param ver pointer to the pmic_version_t structure + * + * @return This function returns PMIC version. + */ +void pmic_get_revision(pmic_version_t *ver) +{ + int rev_id = 0; + int rev1 = 0; + int rev2 = 0; + int finid = 0; + int icid = 0; + + ver->id = PMIC_MC13892; + pmic_read(REG_IDENTIFICATION, &rev_id); + + rev1 = (rev_id & 0x018) >> 3; + rev2 = (rev_id & 0x007); + icid = (rev_id & 0x01C0) >> 6; + finid = (rev_id & 0x01E00) >> 9; + + ver->revision = ((rev1 * 10) + rev2); + printk(KERN_INFO "mc13892 Rev %d.%d FinVer %x detected\n", rev1, + rev2, finid); +} + +void mc13892_power_off(void) +{ + unsigned int value; + + pmic_read_reg(REG_POWER_CTL0, &value, 0xffffff); + + value |= 0x000008; + + pmic_write_reg(REG_POWER_CTL0, value, 0xffffff); +} diff --git a/drivers/mxc/pmic/core/mc34704.c b/drivers/mxc/pmic/core/mc34704.c new file mode 100644 index 000000000000..f0ec05afe0ab --- /dev/null +++ b/drivers/mxc/pmic/core/mc34704.c @@ -0,0 +1,329 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic/core/mc34704.c + * @brief This file contains MC34704 specific PMIC code. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/uaccess.h> +#include <linux/mfd/mc34704/core.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> + +#include "pmic.h" + +/* + * Globals + */ +static pmic_version_t mxc_pmic_version = { + .id = PMIC_MC34704, + .revision = 0, +}; +static unsigned int events_enabled; +unsigned int active_events[MAX_ACTIVE_EVENTS]; +struct i2c_client *mc34704_client; +static void pmic_trigger_poll(void); + +#define MAX_MC34704_REG 0x59 +static unsigned int mc34704_reg_readonly[MAX_MC34704_REG / 32 + 1] = { + (1 << 0x03) || (1 << 0x05) || (1 << 0x07) || (1 << 0x09) || + (1 << 0x0B) || (1 << 0x0E) || (1 << 0x11) || (1 << 0x14) || + (1 << 0x17) || (1 << 0x18), + 0, +}; +static unsigned int mc34704_reg_written[MAX_MC34704_REG / 32 + 1]; +static unsigned char mc34704_shadow_regs[MAX_MC34704_REG - 1]; +#define IS_READONLY(r) ((1 << ((r) % 32)) & mc34704_reg_readonly[(r) / 32]) +#define WAS_WRITTEN(r) ((1 << ((r) % 32)) & mc34704_reg_written[(r) / 32]) +#define MARK_WRITTEN(r) do { \ + mc34704_reg_written[(r) / 32] |= (1 << ((r) % 32)); \ +} while (0) + +int pmic_read(int reg_nr, unsigned int *reg_val) +{ + int c; + + /* + * Use the shadow register if we've written to it + */ + if (WAS_WRITTEN(reg_nr)) { + *reg_val = mc34704_shadow_regs[reg_nr]; + return PMIC_SUCCESS; + } + + /* + * Otherwise, actually read the real register. + * Write-only registers will read as zero. + */ + c = i2c_smbus_read_byte_data(mc34704_client, reg_nr); + if (c == -1) { + pr_debug("mc34704: error reading register 0x%02x\n", reg_nr); + return PMIC_ERROR; + } else { + *reg_val = c; + return PMIC_SUCCESS; + } +} + +int pmic_write(int reg_nr, const unsigned int reg_val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(mc34704_client, reg_nr, reg_val); + if (ret == -1) { + return PMIC_ERROR; + } else { + /* + * Update our software copy of the register since you + * can't read what you wrote. + */ + if (!IS_READONLY(reg_nr)) { + mc34704_shadow_regs[reg_nr] = reg_val; + MARK_WRITTEN(reg_nr); + } + return PMIC_SUCCESS; + } +} + +unsigned int pmic_get_active_events(unsigned int *active_events) +{ + unsigned int count = 0; + unsigned int faults; + int bit_set; + + /* Check for any relevant PMIC faults */ + pmic_read(REG_MC34704_FAULTS, &faults); + faults &= events_enabled; + + /* + * Mask all active events, because there is no way to acknowledge + * or dismiss them in the PMIC -- they're sticky. + */ + events_enabled &= ~faults; + + /* Account for all unmasked faults */ + while (faults) { + bit_set = ffs(faults) - 1; + *(active_events + count) = bit_set; + count++; + faults ^= (1 << bit_set); + } + return count; +} + +int pmic_event_unmask(type_event event) +{ + unsigned int event_bit = 0; + unsigned int prior_events = events_enabled; + + event_bit = (1 << event); + events_enabled |= event_bit; + + pr_debug("Enable Event : %d\n", event); + + /* start the polling task as needed */ + if (events_enabled && prior_events == 0) + pmic_trigger_poll(); + + return 0; +} + +int pmic_event_mask(type_event event) +{ + unsigned int event_bit = 0; + + event_bit = (1 << event); + events_enabled &= ~event_bit; + + pr_debug("Disable Event : %d\n", event); + + return 0; +} + +/*! + * PMIC event polling task. This task is called periodically to poll + * for possible MC34704 events (No interrupt supplied by the hardware). + */ +static void pmic_event_task(struct work_struct *work); +DECLARE_DELAYED_WORK(pmic_ws, pmic_event_task); + +static void pmic_trigger_poll(void) +{ + schedule_delayed_work(&pmic_ws, HZ / 10); +} + +static void pmic_event_task(struct work_struct *work) +{ + unsigned int count = 0; + int i; + + count = pmic_get_active_events(active_events); + pr_debug("active events number %d\n", count); + + /* call handlers for all active events */ + for (i = 0; i < count; i++) + pmic_event_callback(active_events[i]); + + /* re-trigger this task, but only if somebody is watching */ + if (events_enabled) + pmic_trigger_poll(); + + return; +} + +pmic_version_t pmic_get_version(void) +{ + return mxc_pmic_version; +} +EXPORT_SYMBOL(pmic_get_version); + +int __devinit pmic_init_registers(void) +{ + /* + * Set some registers to what they should be, + * if for no other reason than to initialize our + * software register copies. + */ + CHECK_ERROR(pmic_write(REG_MC34704_GENERAL2, 0x09)); + CHECK_ERROR(pmic_write(REG_MC34704_VGSET1, 0)); + CHECK_ERROR(pmic_write(REG_MC34704_REG2SET1, 0)); + CHECK_ERROR(pmic_write(REG_MC34704_REG3SET1, 0)); + CHECK_ERROR(pmic_write(REG_MC34704_REG4SET1, 0)); + CHECK_ERROR(pmic_write(REG_MC34704_REG5SET1, 0)); + + return PMIC_SUCCESS; +} + +static int __devinit is_chip_onboard(struct i2c_client *client) +{ + int val; + + /* + * This PMIC has no version or ID register, so just see + * if it ACK's and returns 0 on some write-only register as + * evidence of its presence. + */ + val = i2c_smbus_read_byte_data(client, REG_MC34704_GENERAL2); + if (val != 0) + return -1; + + return 0; +} + +static int __devinit pmic_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct mc34704 *mc34704; + struct mc34704_platform_data *plat_data = client->dev.platform_data; + + if (!plat_data || !plat_data->init) + return -ENODEV; + + ret = is_chip_onboard(client); + + if (ret == -1) + return -ENODEV; + + mc34704 = kzalloc(sizeof(struct mc34704), GFP_KERNEL); + if (mc34704 == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, mc34704); + mc34704->dev = &client->dev; + mc34704->i2c_client = client; + + mc34704_client = client; + + /* Initialize the PMIC event handling */ + pmic_event_list_init(); + + /* Initialize PMI registers */ + if (pmic_init_registers() != PMIC_SUCCESS) + return PMIC_ERROR; + + ret = plat_data->init(mc34704); + if (ret != 0) + return PMIC_ERROR; + + dev_info(&client->dev, "Loaded\n"); + + return PMIC_SUCCESS; +} + +static int pmic_remove(struct i2c_client *client) +{ + return 0; +} + +static int pmic_suspend(struct i2c_client *client, pm_message_t state) +{ + return 0; +} + +static int pmic_resume(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id mc34704_id[] = { + {"mc34704", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, mc34704_id); + +static struct i2c_driver pmic_driver = { + .driver = { + .name = "mc34704", + .bus = NULL, + }, + .probe = pmic_probe, + .remove = pmic_remove, + .suspend = pmic_suspend, + .resume = pmic_resume, + .id_table = mc34704_id, +}; + +static int __init pmic_init(void) +{ + return i2c_add_driver(&pmic_driver); +} + +static void __exit pmic_exit(void) +{ + i2c_del_driver(&pmic_driver); +} + +/* + * Module entry points + */ +subsys_initcall_sync(pmic_init); +module_exit(pmic_exit); + +MODULE_DESCRIPTION("MC34704 PMIC driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/core/pmic-dev.c b/drivers/mxc/pmic/core/pmic-dev.c new file mode 100644 index 000000000000..1cb7ce311338 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic-dev.c @@ -0,0 +1,319 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All rights reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic-dev.c + * @brief This provides /dev interface to the user program. They make it + * possible to have user-space programs use or control PMIC. Mainly its + * useful for notifying PMIC events to user-space programs. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/kdev_t.h> +#include <linux/circ_buf.h> +#include <linux/major.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/signal.h> +#include <linux/pmic_external.h> + +#include <asm/uaccess.h> + +#define PMIC_NAME "pmic" +#define CIRC_BUF_MAX 16 +#define CIRC_ADD(elem,cir_buf,size) \ + down(&event_mutex); \ + if(CIRC_SPACE(cir_buf.head, cir_buf.tail, size)){ \ + cir_buf.buf[cir_buf.head] = (char)elem; \ + cir_buf.head = (cir_buf.head + 1) & (size - 1); \ + } else { \ + pr_info("Failed to notify event to the user\n");\ + } \ + up(&event_mutex); + +#define CIRC_REMOVE(elem,cir_buf,size) \ + down(&event_mutex); \ + if(CIRC_CNT(cir_buf.head, cir_buf.tail, size)){ \ + elem = (int)cir_buf.buf[cir_buf.tail]; \ + cir_buf.tail = (cir_buf.tail + 1) & (size - 1); \ + } else { \ + elem = -1; \ + pr_info("No valid notified event\n"); \ + } \ + up(&event_mutex); + +static int pmic_major; +static struct class *pmic_class; +static struct fasync_struct *pmic_dev_queue; + +static DECLARE_MUTEX(event_mutex); +static struct circ_buf pmic_events; + +static void callbackfn(void *event) +{ + printk(KERN_INFO "\n\n DETECTED PMIC EVENT : %d\n\n", + (unsigned int)event); +} + +static void user_notify_callback(void *event) +{ + CIRC_ADD((int)event, pmic_events, CIRC_BUF_MAX); + kill_fasync(&pmic_dev_queue, SIGIO, POLL_IN); +} + +/*! + * This function implements IOCTL controls on a PMIC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_dev_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + register_info reg_info; + pmic_event_callback_t event_sub; + type_event event; + int ret = 0; + + if (_IOC_TYPE(cmd) != 'P') + return -ENOTTY; + + switch (cmd) { + case PMIC_READ_REG: + if (copy_from_user(®_info, (register_info *) arg, + sizeof(register_info))) { + return -EFAULT; + } + ret = + pmic_read_reg(reg_info.reg, &(reg_info.reg_value), + 0x00ffffff); + pr_debug("read reg %d %x\n", reg_info.reg, reg_info.reg_value); + if (copy_to_user((register_info *) arg, ®_info, + sizeof(register_info))) { + return -EFAULT; + } + break; + + case PMIC_WRITE_REG: + if (copy_from_user(®_info, (register_info *) arg, + sizeof(register_info))) { + return -EFAULT; + } + ret = + pmic_write_reg(reg_info.reg, reg_info.reg_value, + 0x00ffffff); + pr_debug("write reg %d %x\n", reg_info.reg, reg_info.reg_value); + if (copy_to_user((register_info *) arg, ®_info, + sizeof(register_info))) { + return -EFAULT; + } + break; + + case PMIC_SUBSCRIBE: + if (get_user(event, (int __user *)arg)) { + return -EFAULT; + } + event_sub.func = callbackfn; + event_sub.param = (void *)event; + ret = pmic_event_subscribe(event, event_sub); + pr_debug("subscribe done\n"); + break; + + case PMIC_UNSUBSCRIBE: + if (get_user(event, (int __user *)arg)) { + return -EFAULT; + } + event_sub.func = callbackfn; + event_sub.param = (void *)event; + ret = pmic_event_unsubscribe(event, event_sub); + pr_debug("unsubscribe done\n"); + break; + + case PMIC_NOTIFY_USER: + if (get_user(event, (int __user *)arg)) { + return -EFAULT; + } + event_sub.func = user_notify_callback; + event_sub.param = (void *)event; + ret = pmic_event_subscribe(event, event_sub); + break; + + case PMIC_GET_NOTIFY: + CIRC_REMOVE(event, pmic_events, CIRC_BUF_MAX); + if (put_user(event, (int __user *)arg)) { + return -EFAULT; + } + break; + + default: + printk(KERN_ERR "%d unsupported ioctl command\n", (int)cmd); + return -EINVAL; + } + + return ret; +} + +/*! + * This function implements the open method on a PMIC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_dev_open(struct inode *inode, struct file *file) +{ + pr_debug("open\n"); + return PMIC_SUCCESS; +} + +/*! + * This function implements the release method on a PMIC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * + * @return This function returns 0. + */ +static int pmic_dev_free(struct inode *inode, struct file *file) +{ + pr_debug("free\n"); + return PMIC_SUCCESS; +} + +static int pmic_dev_fasync(int fd, struct file *filp, int mode) +{ + return fasync_helper(fd, filp, mode, &pmic_dev_queue); +} + +/*! + * This structure defines file operations for a PMIC device. + */ +static struct file_operations pmic_fops = { + /*! + * the owner + */ + .owner = THIS_MODULE, + /*! + * the ioctl operation + */ + .ioctl = pmic_dev_ioctl, + /*! + * the open operation + */ + .open = pmic_dev_open, + /*! + * the release operation + */ + .release = pmic_dev_free, + /*! + * the release operation + */ + .fasync = pmic_dev_fasync, +}; + +/*! + * This function implements the init function of the PMIC char device. + * This function is called when the module is loaded. It registers + * the character device for PMIC to be used by user-space programs. + * + * @return This function returns 0. + */ +static int __init pmic_dev_init(void) +{ + int ret = 0; + struct device *pmic_device; + pmic_version_t pmic_ver; + + pmic_ver = pmic_get_version(); + if (pmic_ver.revision < 0) { + printk(KERN_ERR "No PMIC device found\n"); + return -ENODEV; + } + + pmic_major = register_chrdev(0, PMIC_NAME, &pmic_fops); + if (pmic_major < 0) { + printk(KERN_ERR "unable to get a major for pmic\n"); + return pmic_major; + } + + pmic_class = class_create(THIS_MODULE, PMIC_NAME); + if (IS_ERR(pmic_class)) { + printk(KERN_ERR "Error creating pmic class.\n"); + ret = PMIC_ERROR; + goto err; + } + + pmic_device = device_create(pmic_class, NULL, MKDEV(pmic_major, 0), NULL, + PMIC_NAME); + if (IS_ERR(pmic_device)) { + printk(KERN_ERR "Error creating pmic class device.\n"); + ret = PMIC_ERROR; + goto err1; + } + + pmic_events.buf = kmalloc(CIRC_BUF_MAX * sizeof(char), GFP_KERNEL); + if (NULL == pmic_events.buf) { + ret = -ENOMEM; + goto err2; + } + pmic_events.head = pmic_events.tail = 0; + + printk(KERN_INFO "PMIC Character device: successfully loaded\n"); + return ret; + err2: + device_destroy(pmic_class, MKDEV(pmic_major, 0)); + err1: + class_destroy(pmic_class); + err: + unregister_chrdev(pmic_major, PMIC_NAME); + return ret; + +} + +/*! + * This function implements the exit function of the PMIC character device. + * This function is called when the module is unloaded. It unregisters + * the PMIC character device. + * + */ +static void __exit pmic_dev_exit(void) +{ + device_destroy(pmic_class, MKDEV(pmic_major, 0)); + class_destroy(pmic_class); + + unregister_chrdev(pmic_major, PMIC_NAME); + + printk(KERN_INFO "PMIC Character device: successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(pmic_dev_init); +module_exit(pmic_dev_exit); + +MODULE_DESCRIPTION("PMIC Protocol /dev entries driver"); +MODULE_AUTHOR("FreeScale"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/core/pmic.h b/drivers/mxc/pmic/core/pmic.h new file mode 100644 index 000000000000..b1382a34e850 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic.h @@ -0,0 +1,134 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __PMIC_H__ +#define __PMIC_H__ + + /*! + * @file pmic.h + * @brief This file contains prototypes of all the functions to be + * defined for each PMIC chip. The implementation of these may differ + * from PMIC chip to PMIC chip. + * + * @ingroup PMIC_CORE + */ + +#include <linux/spi/spi.h> + +#define MAX_ACTIVE_EVENTS 10 + +/*! + * This structure is a way for the PMIC core driver to define their own + * \b spi_device structure. This structure includes the core \b spi_device + * structure that is provided by Linux SPI Framework/driver as an + * element and may contain other elements that are required by core driver. + */ +struct mxc_pmic { + /*! + * Master side proxy for an SPI slave device(PMIC) + */ + struct spi_device *spi; +}; + +/*! + * This function is called to transfer data to PMIC on SPI. + * + * @param spi the SPI slave device(PMIC) + * @param buf the pointer to the data buffer + * @param len the length of the data to be transferred + * + * @return Returns 0 on success -1 on failure. + */ +static inline int spi_rw(struct spi_device *spi, u8 * buf, size_t len) +{ + struct spi_transfer t = { + .tx_buf = (const void *)buf, + .rx_buf = buf, + .len = len, + .cs_change = 0, + .delay_usecs = 0, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + if (spi_sync(spi, &m) != 0 || m.status != 0) + return PMIC_ERROR; + return (len - m.actual_length); +} + +/*! + * This function returns the PMIC version in system. + * + * @param ver pointer to the pmic_version_t structure + * + * @return This function returns PMIC version. + */ +void pmic_get_revision(pmic_version_t * ver); + +/*! + * This function initializes the SPI device parameters for this PMIC. + * + * @param spi the SPI slave device(PMIC) + * + * @return None + */ +int pmic_spi_setup(struct spi_device *spi); + +/*! + * This function initializes the PMIC registers. + * + * @return None + */ +int pmic_init_registers(void); + +/*! + * This function reads the interrupt status registers of PMIC + * and determine the current active events. + * + * @param active_events array pointer to be used to return active + * event numbers. + * + * @return This function returns PMIC version. + */ +unsigned int pmic_get_active_events(unsigned int *active_events); + +/*! + * This function sets a bit in mask register of pmic to disable an event IT. + * + * @param event the event to be masked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_mask(type_event event); + +/*! + * This function unsets a bit in mask register of pmic to unmask an event IT. + * + * @param event the event to be unmasked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_unmask(type_event event); + +#ifdef CONFIG_MXC_PMIC_FIXARB +extern PMIC_STATUS pmic_fix_arbitration(struct spi_device *spi); +#else +static inline PMIC_STATUS pmic_fix_arbitration(struct spi_device *spi) +{ + return PMIC_SUCCESS; +} +#endif + +void *pmic_alloc_data(struct device *dev); + +#endif /* __PMIC_H__ */ diff --git a/drivers/mxc/pmic/core/pmic_common.c b/drivers/mxc/pmic/core/pmic_common.c new file mode 100644 index 000000000000..55f34b29cd93 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_common.c @@ -0,0 +1,98 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic_common.c + * @brief This is the common file for the PMIC Core/Protocol driver. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> + +#include <asm/uaccess.h> + +#include "pmic.h" + +/* + * Global variables + */ +pmic_version_t mxc_pmic_version; +unsigned int active_events[MAX_ACTIVE_EVENTS]; +struct workqueue_struct *pmic_event_wq; + +void pmic_bh_handler(struct work_struct *work); +/*! + * Bottom half handler of PMIC event handling. + */ +DECLARE_WORK(pmic_ws, pmic_bh_handler); + +/*! + * This function is the bottom half handler of the PMIC interrupt. + * It checks for active events and launches callback for the + * active events. + */ +void pmic_bh_handler(struct work_struct *work) +{ + unsigned int loop; + unsigned int count = 0; + + count = pmic_get_active_events(active_events); + pr_debug("active events number %d\n", count); + + for (loop = 0; loop < count; loop++) + pmic_event_callback(active_events[loop]); + + return; +} + +/*! + * This function is called when pmic interrupt occurs on the processor. + * It is the interrupt handler for the pmic module. + * + * @param irq the irq number + * @param dev_id the pointer on the device + * + * @return The function returns IRQ_HANDLED when handled. + */ +irqreturn_t pmic_irq_handler(int irq, void *dev_id) +{ + /* prepare a task */ + queue_work(pmic_event_wq, &pmic_ws); + + return IRQ_HANDLED; +} + +/*! + * This function is used to determine the PMIC type and its revision. + * + * @return Returns the PMIC type and its revision. + */ + +pmic_version_t pmic_get_version(void) +{ + return mxc_pmic_version; +} +EXPORT_SYMBOL(pmic_get_version); diff --git a/drivers/mxc/pmic/core/pmic_core_i2c.c b/drivers/mxc/pmic/core/pmic_core_i2c.c new file mode 100644 index 000000000000..d529891973cd --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_core_i2c.c @@ -0,0 +1,346 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic_core_i2c.c + * @brief This is the main file for the PMIC Core/Protocol driver. i2c + * should be providing the interface between the PMIC and the MCU. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/i2c.h> +#include <linux/mfd/mc13892/core.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> + +#include <asm/uaccess.h> + +#include "pmic.h" + +#define MC13892_GENERATION_ID_LSH 6 +#define MC13892_IC_ID_LSH 13 + +#define MC13892_GENERATION_ID_WID 3 +#define MC13892_IC_ID_WID 6 + +#define MC13892_GEN_ID_VALUE 0x7 +#define MC13892_IC_ID_VALUE 1 + +/* + * Global variables + */ +struct i2c_client *mc13892_client; + +extern struct workqueue_struct *pmic_event_wq; +extern pmic_version_t mxc_pmic_version; +extern irqreturn_t pmic_irq_handler(int irq, void *dev_id); + +/* + * Platform device structure for PMIC client drivers + */ +static struct platform_device adc_ldm = { + .name = "pmic_adc", + .id = 1, +}; +static struct platform_device battery_ldm = { + .name = "pmic_battery", + .id = 1, +}; +static struct platform_device power_ldm = { + .name = "pmic_power", + .id = 1, +}; +static struct platform_device rtc_ldm = { + .name = "pmic_rtc", + .id = 1, +}; +static struct platform_device light_ldm = { + .name = "pmic_light", + .id = 1, +}; +static struct platform_device rleds_ldm = { + .name = "pmic_leds", + .id = 'r', +}; +static struct platform_device gleds_ldm = { + .name = "pmic_leds", + .id = 'g', +}; +static struct platform_device bleds_ldm = { + .name = "pmic_leds", + .id = 'b', +}; + +static void pmic_pdev_register(struct device *dev) +{ + platform_device_register(&adc_ldm); + platform_device_register(&battery_ldm); + platform_device_register(&rtc_ldm); + platform_device_register(&power_ldm); + platform_device_register(&light_ldm); + platform_device_register(&rleds_ldm); + platform_device_register(&gleds_ldm); + platform_device_register(&bleds_ldm); +} + +/*! + * This function unregisters platform device structures for + * PMIC client drivers. + */ +static void pmic_pdev_unregister(void) +{ + platform_device_unregister(&adc_ldm); + platform_device_unregister(&battery_ldm); + platform_device_unregister(&rtc_ldm); + platform_device_unregister(&power_ldm); + platform_device_unregister(&light_ldm); +} + +static int __devinit is_chip_onboard(struct i2c_client *client) +{ + unsigned int ret = 0; + + /*bind the right device to the driver */ + if (pmic_i2c_24bit_read(client, REG_IDENTIFICATION, &ret) == -1) + return -1; + + if (MC13892_GEN_ID_VALUE != BITFEXT(ret, MC13892_GENERATION_ID)) { + /*compare the address value */ + dev_err(&client->dev, + "read generation ID 0x%x is not equal to 0x%x!\n", + BITFEXT(ret, MC13892_GENERATION_ID), + MC13892_GEN_ID_VALUE); + return -1; + } + + return 0; +} + +static ssize_t mc13892_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, value; + int offset = (REG_TEST4 + 1) / 4; + + for (i = 0; i < offset; i++) { + pmic_read(i, &value); + pr_info("reg%02d: %06x\t\t", i, value); + pmic_read(i + offset, &value); + pr_info("reg%02d: %06x\t\t", i + offset, value); + pmic_read(i + offset * 2, &value); + pr_info("reg%02d: %06x\t\t", i + offset * 2, value); + pmic_read(i + offset * 3, &value); + pr_info("reg%02d: %06x\n", i + offset * 3, value); + } + + return 0; +} + +static ssize_t mc13892_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int reg, value, ret; + char *p; + + reg = simple_strtoul(buf, NULL, 10); + + p = NULL; + p = memchr(buf, ' ', count); + + if (p == NULL) { + pmic_read(reg, &value); + pr_debug("reg%02d: %06x\n", reg, value); + return count; + } + + p += 1; + + value = simple_strtoul(p, NULL, 16); + + ret = pmic_write(reg, value); + if (ret == 0) + pr_debug("write reg%02d: %06x\n", reg, value); + else + pr_debug("register update failed\n"); + + return count; +} + +static struct device_attribute mc13892_dev_attr = { + .attr = { + .name = "mc13892_ctl", + .mode = S_IRUSR | S_IWUSR, + }, + .show = mc13892_show, + .store = mc13892_store, +}; + +static int __devinit pmic_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + int pmic_irq; + struct mc13892 *mc13892; + struct mc13892_platform_data *plat_data = client->dev.platform_data; + + ret = is_chip_onboard(client); + if (ret == -1) + return -ENODEV; + + mc13892 = kzalloc(sizeof(struct mc13892), GFP_KERNEL); + if (mc13892 == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, mc13892); + mc13892->dev = &client->dev; + mc13892->i2c_client = client; + + /* so far, we got matched chip on board */ + + mc13892_client = client; + + /* Initialize the PMIC event handling */ + pmic_event_list_init(); + + /* Initialize GPIO for PMIC Interrupt */ + gpio_pmic_active(); + + /* Get the PMIC Version */ + pmic_get_revision(&mxc_pmic_version); + if (mxc_pmic_version.revision < 0) { + dev_err((struct device *)client, + "PMIC not detected!!! Access Failed\n"); + return -ENODEV; + } else { + dev_dbg((struct device *)client, + "Detected pmic core IC version number is %d\n", + mxc_pmic_version.revision); + } + + /* Initialize the PMIC parameters */ + ret = pmic_init_registers(); + if (ret != PMIC_SUCCESS) + return PMIC_ERROR; + + pmic_event_wq = create_workqueue("mc13892"); + if (!pmic_event_wq) { + pr_err("mc13892 pmic driver init: fail to create work queue"); + return -EFAULT; + } + + /* Set and install PMIC IRQ handler */ + pmic_irq = (int)(client->irq); + if (pmic_irq == 0) + return PMIC_ERROR; + + set_irq_type(pmic_irq, IRQF_TRIGGER_RISING); + ret = + request_irq(pmic_irq, pmic_irq_handler, 0, "PMIC_IRQ", + 0); + + if (ret) { + dev_err(&client->dev, "request irq %d error!\n", pmic_irq); + return ret; + } + enable_irq_wake(pmic_irq); + + if (plat_data && plat_data->init) { + ret = plat_data->init(mc13892); + if (ret != 0) + return PMIC_ERROR; + } + + ret = device_create_file(&client->dev, &mc13892_dev_attr); + if (ret) + dev_err(&client->dev, "create device file failed!\n"); + + pmic_pdev_register(&client->dev); + + dev_info(&client->dev, "Loaded\n"); + + return PMIC_SUCCESS; +} + +static int pmic_remove(struct i2c_client *client) +{ + int pmic_irq = (int)(client->irq); + + if (pmic_event_wq) + destroy_workqueue(pmic_event_wq); + + free_irq(pmic_irq, 0); + pmic_pdev_unregister(); + return 0; +} + +static int pmic_suspend(struct i2c_client *client, pm_message_t state) +{ + return 0; +} + +static int pmic_resume(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id mc13892_id[] = { + {"mc13892", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, mc13892_id); + +static struct i2c_driver pmic_driver = { + .driver = { + .name = "mc13892", + .bus = NULL, + }, + .probe = pmic_probe, + .remove = pmic_remove, + .suspend = pmic_suspend, + .resume = pmic_resume, + .id_table = mc13892_id, +}; + +static int __init pmic_init(void) +{ + return i2c_add_driver(&pmic_driver); +} + +static void __exit pmic_exit(void) +{ + i2c_del_driver(&pmic_driver); +} + +/* + * Module entry points + */ +subsys_initcall_sync(pmic_init); +module_exit(pmic_exit); + +MODULE_DESCRIPTION("Core/Protocol driver for PMIC"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/core/pmic_core_spi.c b/drivers/mxc/pmic/core/pmic_core_spi.c new file mode 100644 index 000000000000..4cc9eedec9de --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_core_spi.c @@ -0,0 +1,305 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic_core_spi.c + * @brief This is the main file for the PMIC Core/Protocol driver. SPI + * should be providing the interface between the PMIC and the MCU. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/spi/spi.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> + +#include <asm/uaccess.h> + +#include "pmic.h" + +/* + * Static functions + */ +static void pmic_pdev_register(void); +static void pmic_pdev_unregister(void); + +/* + * Platform device structure for PMIC client drivers + */ +static struct platform_device adc_ldm = { + .name = "pmic_adc", + .id = 1, +}; +static struct platform_device battery_ldm = { + .name = "pmic_battery", + .id = 1, +}; +static struct platform_device power_ldm = { + .name = "pmic_power", + .id = 1, +}; +static struct platform_device rtc_ldm = { + .name = "pmic_rtc", + .id = 1, +}; +static struct platform_device light_ldm = { + .name = "pmic_light", + .id = 1, +}; +static struct platform_device rleds_ldm = { + .name = "pmic_leds", + .id = 'r', +}; +static struct platform_device gleds_ldm = { + .name = "pmic_leds", + .id = 'g', +}; +static struct platform_device bleds_ldm = { + .name = "pmic_leds", + .id = 'b', +}; + +/* + * External functions + */ +extern void pmic_event_list_init(void); +extern void pmic_event_callback(type_event event); +extern void gpio_pmic_active(void); +extern irqreturn_t pmic_irq_handler(int irq, void *dev_id); +extern pmic_version_t mxc_pmic_version; +extern struct workqueue_struct *pmic_event_wq; + +/*! + * This function registers platform device structures for + * PMIC client drivers. + */ +static void pmic_pdev_register(void) +{ + platform_device_register(&adc_ldm); + platform_device_register(&battery_ldm); + platform_device_register(&rtc_ldm); + platform_device_register(&power_ldm); + platform_device_register(&light_ldm); + platform_device_register(&rleds_ldm); + platform_device_register(&gleds_ldm); + platform_device_register(&bleds_ldm); +} + +/*! + * This function unregisters platform device structures for + * PMIC client drivers. + */ +static void pmic_pdev_unregister(void) +{ + platform_device_unregister(&adc_ldm); + platform_device_unregister(&battery_ldm); + platform_device_unregister(&rtc_ldm); + platform_device_unregister(&power_ldm); + platform_device_unregister(&light_ldm); +} + +/*! + * This function puts the SPI slave device in low-power mode/state. + * + * @param spi the SPI slave device + * @param message the power state to enter + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int pmic_suspend(struct spi_device *spi, pm_message_t message) +{ + return PMIC_SUCCESS; +} + +/*! + * This function brings the SPI slave device back from low-power mode/state. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int pmic_resume(struct spi_device *spi) +{ + return PMIC_SUCCESS; +} + +static struct spi_driver pmic_driver; + +/*! + * This function is called whenever the SPI slave device is detected. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit pmic_probe(struct spi_device *spi) +{ + int ret = 0; + struct pmic_platform_data *plat_data = spi->dev.platform_data; + + /* Initialize the PMIC parameters */ + ret = pmic_spi_setup(spi); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + + /* Initialize the PMIC event handling */ + pmic_event_list_init(); + + /* Initialize GPIO for PMIC Interrupt */ + gpio_pmic_active(); + + /* Get the PMIC Version */ + pmic_get_revision(&mxc_pmic_version); + if (mxc_pmic_version.revision < 0) { + dev_err((struct device *)spi, + "PMIC not detected!!! Access Failed\n"); + return -ENODEV; + } else { + dev_dbg((struct device *)spi, + "Detected pmic core IC version number is %d\n", + mxc_pmic_version.revision); + } + + spi_set_drvdata(spi, pmic_alloc_data(&(spi->dev))); + + /* Initialize the PMIC parameters */ + ret = pmic_init_registers(); + if (ret != PMIC_SUCCESS) { + kfree(spi_get_drvdata(spi)); + spi_set_drvdata(spi, NULL); + return PMIC_ERROR; + } + + pmic_event_wq = create_workqueue("pmic_spi"); + if (!pmic_event_wq) { + pr_err("pmic driver init: fail to create work queue"); + kfree(spi_get_drvdata(spi)); + spi_set_drvdata(spi, NULL); + return -EFAULT; + } + + /* Set and install PMIC IRQ handler */ + set_irq_type(spi->irq, IRQF_TRIGGER_RISING); + ret = request_irq(spi->irq, pmic_irq_handler, 0, "PMIC_IRQ", 0); + if (ret) { + kfree(spi_get_drvdata(spi)); + spi_set_drvdata(spi, NULL); + dev_err((struct device *)spi, "gpio1: irq%d error.", spi->irq); + return ret; + } + + enable_irq_wake(spi->irq); + + if (plat_data && plat_data->init) { + ret = plat_data->init(spi_get_drvdata(spi)); + if (ret != 0) { + kfree(spi_get_drvdata(spi)); + spi_set_drvdata(spi, NULL); + return PMIC_ERROR; + } + } + + power_ldm.dev.platform_data = spi->dev.platform_data; + + pmic_pdev_register(); + + printk(KERN_INFO "Device %s probed\n", dev_name(&spi->dev)); + + return PMIC_SUCCESS; +} + +/*! + * This function is called whenever the SPI slave device is removed. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devexit pmic_remove(struct spi_device *spi) +{ + if (pmic_event_wq) + destroy_workqueue(pmic_event_wq); + + free_irq(spi->irq, 0); + + pmic_pdev_unregister(); + + printk(KERN_INFO "Device %s removed\n", dev_name(&spi->dev)); + + return PMIC_SUCCESS; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct spi_driver pmic_driver = { + .driver = { + .name = "pmic_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = pmic_probe, + .remove = __devexit_p(pmic_remove), + .suspend = pmic_suspend, + .resume = pmic_resume, +}; + +/* + * Initialization and Exit + */ + +/*! + * This function implements the init function of the PMIC device. + * This function is called when the module is loaded. It registers + * the PMIC Protocol driver. + * + * @return This function returns 0. + */ +static int __init pmic_init(void) +{ + return spi_register_driver(&pmic_driver); +} + +/*! + * This function implements the exit function of the PMIC device. + * This function is called when the module is unloaded. It unregisters + * the PMIC Protocol driver. + * + */ +static void __exit pmic_exit(void) +{ + pr_debug("Unregistering the PMIC Protocol Driver\n"); + spi_unregister_driver(&pmic_driver); +} + +/* + * Module entry points + */ +subsys_initcall_sync(pmic_init); +module_exit(pmic_exit); + +MODULE_DESCRIPTION("Core/Protocol driver for PMIC"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/core/pmic_event.c b/drivers/mxc/pmic/core/pmic_event.c new file mode 100644 index 000000000000..2d1bebfef448 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_event.c @@ -0,0 +1,235 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic_event.c + * @brief This file manage all event of PMIC component. + * + * It contains event subscription, unsubscription and callback + * launch methods implemeted. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> +#include "pmic.h" + +/*! + * This structure is used to keep a list of subscribed + * callbacks for an event. + */ +typedef struct { + /*! + * Keeps a list of subscribed clients to an event. + */ + struct list_head list; + + /*! + * Callback function with parameter, called when event occurs + */ + pmic_event_callback_t callback; +} pmic_event_callback_list_t; + +/* Create a mutex to be used to prevent concurrent access to the event list */ +static DECLARE_MUTEX(event_mutex); + +/* This is a pointer to the event handler array. It defines the currently + * active set of events and user-defined callback functions. + */ +static struct list_head pmic_events[PMIC_MAX_EVENTS]; + +/*! + * This function initializes event list for PMIC event handling. + * + */ +void pmic_event_list_init(void) +{ + int i; + + for (i = 0; i < PMIC_MAX_EVENTS; i++) { + INIT_LIST_HEAD(&pmic_events[i]); + } + + sema_init(&event_mutex, 1); + return; +} + +/*! + * This function is used to subscribe on an event. + * + * @param event the event number to be subscribed + * @param callback the callback funtion to be subscribed + * + * @return This function returns 0 on SUCCESS, error on FAILURE. + */ +PMIC_STATUS pmic_event_subscribe(type_event event, + pmic_event_callback_t callback) +{ + pmic_event_callback_list_t *new = NULL; + + pr_debug("Event:%d Subscribe\n", event); + + /* Check whether the event & callback are valid? */ + if (event >= PMIC_MAX_EVENTS) { + pr_debug("Invalid Event:%d\n", event); + return -EINVAL; + } + if (NULL == callback.func) { + pr_debug("Null or Invalid Callback\n"); + return -EINVAL; + } + + /* Create a new linked list entry */ + new = kmalloc(sizeof(pmic_event_callback_list_t), GFP_KERNEL); + if (NULL == new) { + return -ENOMEM; + } + /* Initialize the list node fields */ + new->callback.func = callback.func; + new->callback.param = callback.param; + INIT_LIST_HEAD(&new->list); + + /* Obtain the lock to access the list */ + if (down_interruptible(&event_mutex)) { + kfree(new); + return PMIC_SYSTEM_ERROR_EINTR; + } + + /* Unmask the requested event */ + if (list_empty(&pmic_events[event])) { + if (pmic_event_unmask(event) != PMIC_SUCCESS) { + kfree(new); + up(&event_mutex); + return PMIC_ERROR; + } + } + + /* Add this entry to the event list */ + list_add_tail(&new->list, &pmic_events[event]); + + /* Release the lock */ + up(&event_mutex); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to unsubscribe on an event. + * + * @param event the event number to be unsubscribed + * @param callback the callback funtion to be unsubscribed + * + * @return This function returns 0 on SUCCESS, error on FAILURE. + */ +PMIC_STATUS pmic_event_unsubscribe(type_event event, + pmic_event_callback_t callback) +{ + struct list_head *p; + struct list_head *n; + pmic_event_callback_list_t *temp = NULL; + int ret = PMIC_EVENT_NOT_SUBSCRIBED; + + pr_debug("Event:%d Unsubscribe\n", event); + + /* Check whether the event & callback are valid? */ + if (event >= PMIC_MAX_EVENTS) { + pr_debug("Invalid Event:%d\n", event); + return -EINVAL; + } + + if (NULL == callback.func) { + pr_debug("Null or Invalid Callback\n"); + return -EINVAL; + } + + /* Obtain the lock to access the list */ + if (down_interruptible(&event_mutex)) { + return PMIC_SYSTEM_ERROR_EINTR; + } + + /* Find the entry in the list */ + list_for_each_safe(p, n, &pmic_events[event]) { + temp = list_entry(p, pmic_event_callback_list_t, list); + if (temp->callback.func == callback.func + && temp->callback.param == callback.param) { + /* Remove the entry from the list */ + list_del(p); + kfree(temp); + ret = PMIC_SUCCESS; + break; + } + } + + /* Unmask the requested event */ + if (list_empty(&pmic_events[event])) { + if (pmic_event_mask(event) != PMIC_SUCCESS) { + ret = PMIC_UNSUBSCRIBE_ERROR; + } + } + + /* Release the lock */ + up(&event_mutex); + + return ret; +} + +/*! + * This function calls all callback of a specific event. + * + * @param event the active event number + * + * @return None + */ +void pmic_event_callback(type_event event) +{ + struct list_head *p; + pmic_event_callback_list_t *temp = NULL; + + /* Obtain the lock to access the list */ + if (down_interruptible(&event_mutex)) { + return; + } + + if (list_empty(&pmic_events[event])) { + pr_debug("PMIC Event:%d detected. No callback subscribed\n", + event); + up(&event_mutex); + return; + } + + list_for_each(p, &pmic_events[event]) { + temp = list_entry(p, pmic_event_callback_list_t, list); + temp->callback.func(temp->callback.param); + } + + /* Release the lock */ + up(&event_mutex); + + return; + +} + +EXPORT_SYMBOL(pmic_event_subscribe); +EXPORT_SYMBOL(pmic_event_unsubscribe); diff --git a/drivers/mxc/pmic/core/pmic_external.c b/drivers/mxc/pmic/core/pmic_external.c new file mode 100644 index 000000000000..bea23af3207d --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_external.c @@ -0,0 +1,100 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic_external.c + * @brief This file contains all external functions of PMIC drivers. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/init.h> +#include <linux/errno.h> + +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> + +/* + * External Functions + */ +extern int pmic_read(int reg_num, unsigned int *reg_val); +extern int pmic_write(int reg_num, const unsigned int reg_val); + +/*! + * This function is called by PMIC clients to read a register on PMIC. + * + * @param reg number of register + * @param reg_value return value of register + * @param reg_mask Bitmap mask indicating which bits to modify + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_read_reg(int reg, unsigned int *reg_value, + unsigned int reg_mask) +{ + int ret = 0; + unsigned int temp = 0; + + ret = pmic_read(reg, &temp); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + *reg_value = (temp & reg_mask); + + pr_debug("Read REG[ %d ] = 0x%x\n", reg, *reg_value); + + return ret; +} + +/*! + * This function is called by PMIC clients to write a register on PMIC. + * + * @param reg number of register + * @param reg_value New value of register + * @param reg_mask Bitmap mask indicating which bits to modify + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_write_reg(int reg, unsigned int reg_value, + unsigned int reg_mask) +{ + int ret = 0; + unsigned int temp = 0; + + ret = pmic_read(reg, &temp); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + temp = (temp & (~reg_mask)) | reg_value; +#ifdef CONFIG_MXC_PMIC_MC13783 + if (reg == REG_POWER_MISCELLANEOUS) + temp &= 0xFFFE7FFF; +#endif + ret = pmic_write(reg, temp); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + + pr_debug("Write REG[ %d ] = 0x%x\n", reg, reg_value); + + return ret; +} + +EXPORT_SYMBOL(pmic_read_reg); +EXPORT_SYMBOL(pmic_write_reg); diff --git a/drivers/mxc/pmic/mc13783/Kconfig b/drivers/mxc/pmic/mc13783/Kconfig new file mode 100644 index 000000000000..02496c624e2e --- /dev/null +++ b/drivers/mxc/pmic/mc13783/Kconfig @@ -0,0 +1,55 @@ +# +# PMIC Modules configuration +# + +config MXC_MC13783_ADC + tristate "MC13783 ADC support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 ADC module driver. This module provides kernel API + for the ADC system of MC13783. + It controls also the touch screen interface. + If you want MC13783 ADC support, you should say Y here + +config MXC_MC13783_AUDIO + tristate "MC13783 Audio support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 audio module driver. This module provides kernel API + for audio part of MC13783. + If you want MC13783 audio support, you should say Y here +config MXC_MC13783_RTC + tristate "MC13783 Real Time Clock (RTC) support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 RTC module driver. This module provides kernel API + for RTC part of MC13783. + If you want MC13783 RTC support, you should say Y here +config MXC_MC13783_LIGHT + tristate "MC13783 Light and Backlight support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 Light module driver. This module provides kernel API + for led and backlight control part of MC13783. + If you want MC13783 Light support, you should say Y here +config MXC_MC13783_BATTERY + tristate "MC13783 Battery API support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 battery module driver. This module provides kernel API + for battery control part of MC13783. + If you want MC13783 battery support, you should say Y here +config MXC_MC13783_CONNECTIVITY + tristate "MC13783 Connectivity API support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 connectivity module driver. This module provides kernel API + for USB/RS232 connectivity control part of MC13783. + If you want MC13783 connectivity support, you should say Y here +config MXC_MC13783_POWER + tristate "MC13783 Power API support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 power and supplies module driver. This module provides kernel API + for power and regulator control part of MC13783. + If you want MC13783 power support, you should say Y here diff --git a/drivers/mxc/pmic/mc13783/Makefile b/drivers/mxc/pmic/mc13783/Makefile new file mode 100644 index 000000000000..7bbba23f5ab9 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for the mc13783 pmic drivers. +# + +obj-$(CONFIG_MXC_MC13783_ADC) += pmic_adc-mod.o +obj-$(CONFIG_MXC_MC13783_AUDIO) += pmic_audio-mod.o +obj-$(CONFIG_MXC_MC13783_RTC) += pmic_rtc-mod.o +obj-$(CONFIG_MXC_MC13783_LIGHT) += pmic_light-mod.o +obj-$(CONFIG_MXC_MC13783_BATTERY) += pmic_battery-mod.o +obj-$(CONFIG_MXC_MC13783_CONNECTIVITY) += pmic_convity-mod.o +obj-$(CONFIG_MXC_MC13783_POWER) += pmic_power-mod.o +pmic_adc-mod-objs := pmic_adc.o +pmic_audio-mod-objs := pmic_audio.o +pmic_rtc-mod-objs := pmic_rtc.o +pmic_light-mod-objs := pmic_light.o +pmic_battery-mod-objs := pmic_battery.o +pmic_convity-mod-objs := pmic_convity.o +pmic_power-mod-objs := pmic_power.o diff --git a/drivers/mxc/pmic/mc13783/pmic_adc.c b/drivers/mxc/pmic/mc13783/pmic_adc.c new file mode 100644 index 000000000000..1554faffd288 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_adc.c @@ -0,0 +1,1542 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_adc.c + * @brief This is the main file of PMIC(mc13783) ADC driver. + * + * @ingroup PMIC_ADC + */ + +/* + * Includes + */ + +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/pmic_adc.h> +#include <linux/pmic_status.h> + +#include "../core/pmic.h" +#include "pmic_adc_defs.h" + +#define NB_ADC_REG 5 + +static int pmic_adc_major; + +/* internal function */ +static void callback_tsi(void *); +static void callback_adcdone(void *); +static void callback_adcbisdone(void *); +static void callback_adc_comp_high(void *); + +/*! + * Number of users waiting in suspendq + */ +static int swait = 0; + +/*! + * To indicate whether any of the adc devices are suspending + */ +static int suspend_flag = 0; + +/*! + * The suspendq is used by blocking application calls + */ +static wait_queue_head_t suspendq; + +static struct class *pmic_adc_class; + +/* + * ADC mc13783 API + */ +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_adc_init); +EXPORT_SYMBOL(pmic_adc_deinit); +EXPORT_SYMBOL(pmic_adc_convert); +EXPORT_SYMBOL(pmic_adc_convert_8x); +EXPORT_SYMBOL(pmic_adc_convert_multichnnel); +EXPORT_SYMBOL(pmic_adc_set_touch_mode); +EXPORT_SYMBOL(pmic_adc_get_touch_mode); +EXPORT_SYMBOL(pmic_adc_get_touch_sample); +EXPORT_SYMBOL(pmic_adc_get_battery_current); +EXPORT_SYMBOL(pmic_adc_active_comparator); +EXPORT_SYMBOL(pmic_adc_deactive_comparator); + +static DECLARE_COMPLETION(adcdone_it); +static DECLARE_COMPLETION(adcbisdone_it); +static DECLARE_COMPLETION(adc_tsi); +static pmic_event_callback_t tsi_event; +static pmic_event_callback_t event_adc; +static pmic_event_callback_t event_adc_bis; +static pmic_event_callback_t adc_comp_h; +static bool data_ready_adc_1; +static bool data_ready_adc_2; +static bool adc_ts; +static bool wait_ts; +static bool monitor_en; +static bool monitor_adc; +static t_check_mode wcomp_mode; +static DECLARE_MUTEX(convert_mutex); + +void (*monitoring_cb) (void); /*call back to be called when event is detected. */ + +static DECLARE_WAIT_QUEUE_HEAD(queue_adc_busy); +static t_adc_state adc_dev[2]; + +static unsigned channel_num[] = { + 0, + 1, + 3, + 4, + 2, + 12, + 13, + 14, + 15, + -1, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 7, + 6, + -1, + -1, + -1, + -1, + 5, + 7 +}; + +static bool pmic_adc_ready; + +int is_pmic_adc_ready() +{ + return pmic_adc_ready; +} +EXPORT_SYMBOL(is_pmic_adc_ready); + + +/*! + * This is the suspend of power management for the mc13783 ADC API. + * It supports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int pmic_adc_suspend(struct platform_device *pdev, pm_message_t state) +{ + unsigned int reg_value = 0; + suspend_flag = 1; + CHECK_ERROR(pmic_write_reg(REG_ADC_0, DEF_ADC_0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_2, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, DEF_ADC_3, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_4, reg_value, PMIC_ALL_BITS)); + + return 0; +}; + +/*! + * This is the resume of power management for the mc13783 adc API. + * It supports RESTORE state. + * + * @param pdev the device + * + * @return This function returns 0 if successful. + */ +static int pmic_adc_resume(struct platform_device *pdev) +{ + /* nothing for mc13783 adc */ + unsigned int adc_0_reg, adc_1_reg; + suspend_flag = 0; + + /* let interrupt of TSI again */ + adc_0_reg = ADC_WAIT_TSI_0; + CHECK_ERROR(pmic_write_reg(REG_ADC_0, adc_0_reg, PMIC_ALL_BITS)); + adc_1_reg = ADC_WAIT_TSI_1 | (ADC_BIS * adc_ts); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, PMIC_ALL_BITS)); + + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +/* + * Call back functions + */ + +/*! + * This is the callback function called on TSI mc13783 event, used in synchronous call. + */ +static void callback_tsi(void *unused) +{ + pr_debug("*** TSI IT mc13783 PMIC_ADC_GET_TOUCH_SAMPLE ***\n"); + if (wait_ts) { + complete(&adc_tsi); + pmic_event_mask(EVENT_TSI); + } +} + +/*! + * This is the callback function called on ADCDone mc13783 event. + */ +static void callback_adcdone(void *unused) +{ + if (data_ready_adc_1) { + complete(&adcdone_it); + } +} + +/*! + * This is the callback function called on ADCDone mc13783 event. + */ +static void callback_adcbisdone(void *unused) +{ + pr_debug("* adcdone bis it callback *\n"); + if (data_ready_adc_2) { + complete(&adcbisdone_it); + } +} + +/*! + * This is the callback function called on mc13783 event. + */ +static void callback_adc_comp_high(void *unused) +{ + pr_debug("* adc comp it high *\n"); + if (wcomp_mode == CHECK_HIGH || wcomp_mode == CHECK_LOW_OR_HIGH) { + /* launch callback */ + if (monitoring_cb != NULL) { + monitoring_cb(); + } + } +} + +/*! + * This function performs filtering and rejection of excessive noise prone + * samples. + * + * @param ts_curr Touch screen value + * + * @return This function returns 0 on success, -1 otherwise. + */ +static int pmic_adc_filter(t_touch_screen * ts_curr) +{ + unsigned int ydiff1, ydiff2, ydiff3, xdiff1, xdiff2, xdiff3; + unsigned int sample_sumx, sample_sumy; + static unsigned int prev_x[FILTLEN], prev_y[FILTLEN]; + int index = 0; + unsigned int y_curr, x_curr; + static int filt_count = 0; + /* Added a variable filt_type to decide filtering at run-time */ + unsigned int filt_type = 0; + + if (ts_curr->contact_resistance == 0) { + ts_curr->x_position = 0; + ts_curr->y_position = 0; + filt_count = 0; + return 0; + } + + ydiff1 = abs(ts_curr->y_position1 - ts_curr->y_position2); + ydiff2 = abs(ts_curr->y_position2 - ts_curr->y_position3); + ydiff3 = abs(ts_curr->y_position1 - ts_curr->y_position3); + if ((ydiff1 > DELTA_Y_MAX) || + (ydiff2 > DELTA_Y_MAX) || (ydiff3 > DELTA_Y_MAX)) { + pr_debug("pmic_adc_filter: Ret pos 1\n"); + return -1; + } + + xdiff1 = abs(ts_curr->x_position1 - ts_curr->x_position2); + xdiff2 = abs(ts_curr->x_position2 - ts_curr->x_position3); + xdiff3 = abs(ts_curr->x_position1 - ts_curr->x_position3); + + if ((xdiff1 > DELTA_X_MAX) || + (xdiff2 > DELTA_X_MAX) || (xdiff3 > DELTA_X_MAX)) { + pr_debug("mc13783_adc_filter: Ret pos 2\n"); + return -1; + } + /* Compute two closer values among the three available Y readouts */ + + if (ydiff1 < ydiff2) { + if (ydiff1 < ydiff3) { + // Sample 0 & 1 closest together + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position2; + } else { + // Sample 0 & 2 closest together + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position3; + } + } else { + if (ydiff2 < ydiff3) { + // Sample 1 & 2 closest together + sample_sumy = ts_curr->y_position2 + + ts_curr->y_position3; + } else { + // Sample 0 & 2 closest together + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position3; + } + } + + /* + * Compute two closer values among the three available X + * readouts + */ + if (xdiff1 < xdiff2) { + if (xdiff1 < xdiff3) { + // Sample 0 & 1 closest together + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position2; + } else { + // Sample 0 & 2 closest together + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position3; + } + } else { + if (xdiff2 < xdiff3) { + // Sample 1 & 2 closest together + sample_sumx = ts_curr->x_position2 + + ts_curr->x_position3; + } else { + // Sample 0 & 2 closest together + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position3; + } + } + /* + * Wait FILTER_MIN_DELAY number of samples to restart + * filtering + */ + if (filt_count < FILTER_MIN_DELAY) { + /* + * Current output is the average of the two closer + * values and no filtering is used + */ + y_curr = (sample_sumy / 2); + x_curr = (sample_sumx / 2); + ts_curr->y_position = y_curr; + ts_curr->x_position = x_curr; + filt_count++; + } else { + if (abs(sample_sumx - (prev_x[0] + prev_x[1])) > + (DELTA_X_MAX * 16)) { + pr_debug("pmic_adc_filter: : Ret pos 3\n"); + return -1; + } + if (abs(sample_sumy - (prev_y[0] + prev_y[1])) > + (DELTA_Y_MAX * 16)) { + return -1; + } + sample_sumy /= 2; + sample_sumx /= 2; + /* Use hard filtering if the sample difference < 10 */ + if ((abs(sample_sumy - prev_y[0]) > 10) || + (abs(sample_sumx - prev_x[0]) > 10)) { + filt_type = 1; + } + + /* + * Current outputs are the average of three previous + * values and the present readout + */ + y_curr = sample_sumy; + for (index = 0; index < FILTLEN; index++) { + if (filt_type == 0) { + y_curr = y_curr + (prev_y[index]); + } else { + y_curr = y_curr + (prev_y[index] / 3); + } + } + if (filt_type == 0) { + y_curr = y_curr >> 2; + } else { + y_curr = y_curr >> 1; + } + ts_curr->y_position = y_curr; + + x_curr = sample_sumx; + for (index = 0; index < FILTLEN; index++) { + if (filt_type == 0) { + x_curr = x_curr + (prev_x[index]); + } else { + x_curr = x_curr + (prev_x[index] / 3); + } + } + if (filt_type == 0) { + x_curr = x_curr >> 2; + } else { + x_curr = x_curr >> 1; + } + ts_curr->x_position = x_curr; + + } + + /* Update previous X and Y values */ + for (index = (FILTLEN - 1); index > 0; index--) { + prev_x[index] = prev_x[index - 1]; + prev_y[index] = prev_y[index - 1]; + } + + /* + * Current output will be the most recent past for the + * next sample + */ + prev_y[0] = y_curr; + prev_x[0] = x_curr; + + return 0; +} + +/*! + * This function implements the open method on a MC13783 ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_adc_open(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + pr_debug("mc13783_adc : mc13783_adc_open()\n"); + return 0; +} + +/*! + * This function implements the release method on a MC13783 ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_adc_free(struct inode *inode, struct file *file) +{ + pr_debug("mc13783_adc : mc13783_adc_free()\n"); + return 0; +} + +/*! + * This function initializes all ADC registers with default values. This + * function also registers the interrupt events. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +int pmic_adc_init(void) +{ + unsigned int reg_value = 0, i = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + for (i = 0; i < ADC_NB_AVAILABLE; i++) { + adc_dev[i] = ADC_FREE; + } + CHECK_ERROR(pmic_write_reg(REG_ADC_0, DEF_ADC_0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_2, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, DEF_ADC_3, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_4, reg_value, PMIC_ALL_BITS)); + reg_value = 0x001000; + CHECK_ERROR(pmic_write_reg(REG_ARBITRATION_PERIPHERAL_AUDIO, reg_value, + 0xFFFFFF)); + + data_ready_adc_1 = false; + data_ready_adc_2 = false; + adc_ts = false; + wait_ts = false; + monitor_en = false; + monitor_adc = false; + wcomp_mode = CHECK_LOW; + monitoring_cb = NULL; + /* sub to ADCDone IT */ + event_adc.param = NULL; + event_adc.func = callback_adcdone; + CHECK_ERROR(pmic_event_subscribe(EVENT_ADCDONEI, event_adc)); + + /* sub to ADCDoneBis IT */ + event_adc_bis.param = NULL; + event_adc_bis.func = callback_adcbisdone; + CHECK_ERROR(pmic_event_subscribe(EVENT_ADCBISDONEI, event_adc_bis)); + + /* sub to Touch Screen IT */ + tsi_event.param = NULL; + tsi_event.func = callback_tsi; + CHECK_ERROR(pmic_event_subscribe(EVENT_TSI, tsi_event)); + + /* ADC reading above high limit */ + adc_comp_h.param = NULL; + adc_comp_h.func = callback_adc_comp_high; + CHECK_ERROR(pmic_event_subscribe(EVENT_WHIGHI, adc_comp_h)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables the ADC, de-registers the interrupt events. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_deinit(void) +{ + CHECK_ERROR(pmic_event_unsubscribe(EVENT_ADCDONEI, event_adc)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_ADCBISDONEI, event_adc_bis)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_TSI, tsi_event)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_WHIGHI, adc_comp_h)); + + return PMIC_SUCCESS; +} + +/*! + * This function initializes adc_param structure. + * + * @param adc_param Structure to be initialized. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_init_param(t_adc_param * adc_param) +{ + int i = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + adc_param->delay = 0; + adc_param->conv_delay = false; + adc_param->single_channel = false; + adc_param->group = false; + adc_param->channel_0 = BATTERY_VOLTAGE; + adc_param->channel_1 = BATTERY_VOLTAGE; + adc_param->read_mode = 0; + adc_param->wait_tsi = 0; + adc_param->chrgraw_devide_5 = true; + adc_param->read_ts = false; + adc_param->ts_value.x_position = 0; + adc_param->ts_value.y_position = 0; + adc_param->ts_value.contact_resistance = 0; + for (i = 0; i <= MAX_CHANNEL; i++) { + adc_param->value[i] = 0; + } + return 0; +} + +/*! + * This function starts the convert. + * + * @param adc_param contains all adc configuration and return value. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS mc13783_adc_convert(t_adc_param * adc_param) +{ + bool use_bis = false; + unsigned int adc_0_reg = 0, adc_1_reg = 0, reg_1 = 0, result_reg = + 0, i = 0; + unsigned int result = 0, temp = 0; + pmic_version_t mc13783_ver; + pr_debug("mc13783 ADC - mc13783_adc_convert ....\n"); + if (suspend_flag == 1) { + return -EBUSY; + } + + if (adc_param->wait_tsi) { + /* we need to set ADCEN 1 for TSI interrupt on mc13783 1.x */ + /* configure adc to wait tsi interrupt */ + INIT_COMPLETION(adc_tsi); + pr_debug("mc13783 ADC - pmic_write_reg ....\n"); + /*for ts don't use bis */ + adc_0_reg = 0x001c00 | (ADC_BIS * 0); + pmic_event_unmask(EVENT_TSI); + CHECK_ERROR(pmic_write_reg + (REG_ADC_0, adc_0_reg, PMIC_ALL_BITS)); + /*for ts don't use bis */ + adc_1_reg = 0x200001 | (ADC_BIS * 0); + CHECK_ERROR(pmic_write_reg + (REG_ADC_1, adc_1_reg, PMIC_ALL_BITS)); + pr_debug("wait tsi ....\n"); + wait_ts = true; + wait_for_completion_interruptible(&adc_tsi); + wait_ts = false; + } + if (adc_param->read_ts == false) + down(&convert_mutex); + use_bis = mc13783_adc_request(adc_param->read_ts); + if (use_bis < 0) { + pr_debug("process has received a signal and got interrupted\n"); + return -EINTR; + } + + /* CONFIGURE ADC REG 0 */ + adc_0_reg = 0; + adc_1_reg = 0; + if (adc_param->read_ts == false) { + adc_0_reg = adc_param->read_mode & 0x00003F; + /* add auto inc */ + adc_0_reg |= ADC_INC; + if (use_bis) { + /* add adc bis */ + adc_0_reg |= ADC_BIS; + } + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + if (adc_param->chrgraw_devide_5) { + adc_0_reg |= ADC_CHRGRAW_D5; + } + } + if (adc_param->single_channel) { + adc_1_reg |= ADC_SGL_CH; + } + + if (adc_param->conv_delay) { + adc_1_reg |= ADC_ATO; + } + + if (adc_param->group) { + adc_1_reg |= ADC_ADSEL; + } + + if (adc_param->single_channel) { + adc_1_reg |= ADC_SGL_CH; + } + + adc_1_reg |= (adc_param->channel_0 << ADC_CH_0_POS) & + ADC_CH_0_MASK; + adc_1_reg |= (adc_param->channel_1 << ADC_CH_1_POS) & + ADC_CH_1_MASK; + } else { + adc_0_reg = 0x003c00 | (ADC_BIS * use_bis) | ADC_INC; + } + pr_debug("Write Reg %i = %x\n", REG_ADC_0, adc_0_reg); + /*Change has been made here */ + CHECK_ERROR(pmic_write_reg(REG_ADC_0, adc_0_reg, + ADC_INC | ADC_BIS | ADC_CHRGRAW_D5 | + 0xfff00ff)); + /* CONFIGURE ADC REG 1 */ + if (adc_param->read_ts == false) { + adc_1_reg |= ADC_NO_ADTRIG; + adc_1_reg |= ADC_EN; + adc_1_reg |= (adc_param->delay << ADC_DELAY_POS) & + ADC_DELAY_MASK; + if (use_bis) { + adc_1_reg |= ADC_BIS; + } + } else { + /* configure and start convert to read x and y position */ + /* configure to read 2 value in channel selection 1 & 2 */ + adc_1_reg = 0x100409 | (ADC_BIS * use_bis) | ADC_NO_ADTRIG; + } + reg_1 = adc_1_reg; + if (use_bis == 0) { + data_ready_adc_1 = false; + adc_1_reg |= ASC_ADC; + data_ready_adc_1 = true; + pr_debug("Write Reg %i = %x\n", REG_ADC_1, adc_1_reg); + INIT_COMPLETION(adcdone_it); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, + ADC_SGL_CH | ADC_ATO | ADC_ADSEL + | ADC_CH_0_MASK | ADC_CH_1_MASK | + ADC_NO_ADTRIG | ADC_EN | + ADC_DELAY_MASK | ASC_ADC | ADC_BIS)); + pr_debug("wait adc done \n"); + wait_for_completion_interruptible(&adcdone_it); + data_ready_adc_1 = false; + } else { + data_ready_adc_2 = false; + adc_1_reg |= ASC_ADC; + data_ready_adc_2 = true; + INIT_COMPLETION(adcbisdone_it); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, 0xFFFFFF)); + temp = 0x800000; + CHECK_ERROR(pmic_write_reg(REG_ADC_3, temp, 0xFFFFFF)); + temp = 0x001000; + pmic_write_reg(REG_ARBITRATION_PERIPHERAL_AUDIO, temp, + 0xFFFFFF); + pr_debug("wait adc done bis\n"); + wait_for_completion_interruptible(&adcbisdone_it); + data_ready_adc_2 = false; + } + /* read result and store in adc_param */ + result = 0; + if (use_bis == 0) { + result_reg = REG_ADC_2; + } else { + result_reg = REG_ADC_4; + } + CHECK_ERROR(pmic_write_reg(REG_ADC_1, 4 << ADC_CH_1_POS, + ADC_CH_0_MASK | ADC_CH_1_MASK)); + + for (i = 0; i <= 3; i++) { + CHECK_ERROR(pmic_read_reg(result_reg, &result, PMIC_ALL_BITS)); + pr_debug("result %i = %x\n", result_reg, result); + adc_param->value[i] = ((result & ADD1_RESULT_MASK) >> 2); + adc_param->value[i + 4] = ((result & ADD2_RESULT_MASK) >> 14); + } + if (adc_param->read_ts) { + adc_param->ts_value.x_position = adc_param->value[2]; + adc_param->ts_value.x_position1 = adc_param->value[0]; + adc_param->ts_value.x_position2 = adc_param->value[1]; + adc_param->ts_value.x_position3 = adc_param->value[2]; + adc_param->ts_value.y_position1 = adc_param->value[3]; + adc_param->ts_value.y_position2 = adc_param->value[4]; + adc_param->ts_value.y_position3 = adc_param->value[5]; + adc_param->ts_value.y_position = adc_param->value[5]; + adc_param->ts_value.contact_resistance = adc_param->value[6]; + + } + + /*if (adc_param->read_ts) { + adc_param->ts_value.x_position = adc_param->value[2]; + adc_param->ts_value.y_position = adc_param->value[5]; + adc_param->ts_value.contact_resistance = adc_param->value[6]; + } */ + mc13783_adc_release(use_bis); + if (adc_param->read_ts == false) + up(&convert_mutex); + + return PMIC_SUCCESS; +} + +/*! + * This function select the required read_mode for a specific channel. + * + * @param channel The channel to be sampled + * + * @return This function returns the requires read_mode + */ +t_reading_mode mc13783_set_read_mode(t_channel channel) +{ + t_reading_mode read_mode = 0; + + switch (channel) { + case LICELL: + read_mode = M_LITHIUM_CELL; + break; + case CHARGE_CURRENT: + read_mode = M_CHARGE_CURRENT; + break; + case BATTERY_CURRENT: + read_mode = M_BATTERY_CURRENT; + break; + case THERMISTOR: + read_mode = M_THERMISTOR; + break; + case DIE_TEMP: + read_mode = M_DIE_TEMPERATURE; + break; + case USB_ID: + read_mode = M_UID; + break; + default: + read_mode = 0; + } + + return read_mode; +} + +/*! + * This function triggers a conversion and returns one sampling result of one + * channel. + * + * @param channel The channel to be sampled + * @param result The pointer to the conversion result. The memory + * should be allocated by the caller of this function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_convert(t_channel channel, unsigned short *result) +{ + t_adc_param adc_param; + PMIC_STATUS ret; + + if (suspend_flag == 1) { + return -EBUSY; + } + + channel = channel_num[channel]; + if (channel == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + mc13783_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert\n"); + adc_param.read_ts = false; + adc_param.read_mode = mc13783_set_read_mode(channel); + + adc_param.single_channel = true; + /* Find the group */ + if ((channel >= 0) && (channel <= 7)) { + adc_param.channel_0 = channel; + adc_param.group = false; + } else if ((channel >= 8) && (channel <= 15)) { + adc_param.channel_0 = channel & 0x07; + adc_param.group = true; + } else { + return PMIC_PARAMETER_ERROR; + } + ret = mc13783_adc_convert(&adc_param); + *result = adc_param.value[0]; + return ret; +} + +/*! + * This function triggers a conversion and returns eight sampling results of + * one channel. + * + * @param channel The channel to be sampled + * @param result The pointer to array to store eight sampling results. + * The memory should be allocated by the caller of this + * function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_convert_8x(t_channel channel, unsigned short *result) +{ + t_adc_param adc_param; + int i; + PMIC_STATUS ret; + if (suspend_flag == 1) { + return -EBUSY; + } + + channel = channel_num[channel]; + + if (channel == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + mc13783_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert_8x\n"); + adc_param.read_ts = false; + adc_param.single_channel = true; + adc_param.read_mode = mc13783_set_read_mode(channel); + if ((channel >= 0) && (channel <= 7)) { + adc_param.channel_0 = channel; + adc_param.channel_1 = channel; + adc_param.group = false; + } else if ((channel >= 8) && (channel <= 15)) { + adc_param.channel_0 = channel & 0x07; + adc_param.channel_1 = channel & 0x07; + adc_param.group = true; + } else { + return PMIC_PARAMETER_ERROR; + } + + ret = mc13783_adc_convert(&adc_param); + for (i = 0; i <= 7; i++) { + result[i] = adc_param.value[i]; + } + return ret; +} + +/*! + * This function triggers a conversion and returns sampling results of each + * specified channel. + * + * @param channels This input parameter is bitmap to specify channels + * to be sampled. + * @param result The pointer to array to store sampling results. + * The memory should be allocated by the caller of this + * function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_convert_multichnnel(t_channel channels, + unsigned short *result) +{ + t_adc_param adc_param; + int i; + PMIC_STATUS ret; + if (suspend_flag == 1) { + return -EBUSY; + } + mc13783_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert_multichnnel\n"); + + channels = channel_num[channels]; + + if (channels == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + + adc_param.read_ts = false; + adc_param.single_channel = false; + if ((channels >= 0) && (channels <= 7)) { + adc_param.channel_0 = channels; + adc_param.channel_1 = ((channels + 4) % 4) + 4; + adc_param.group = false; + } else if ((channels >= 8) && (channels <= 15)) { + channels = channels & 0x07; + adc_param.channel_0 = channels; + adc_param.channel_1 = ((channels + 4) % 4) + 4; + adc_param.group = true; + } else { + return PMIC_PARAMETER_ERROR; + } + adc_param.read_mode = 0x00003f; + adc_param.read_ts = false; + ret = mc13783_adc_convert(&adc_param); + + for (i = 0; i <= 7; i++) { + result[i] = adc_param.value[i]; + } + return ret; +} + +/*! + * This function sets touch screen operation mode. + * + * @param touch_mode Touch screen operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_set_touch_mode(t_touch_mode touch_mode) +{ + if (suspend_flag == 1) { + return -EBUSY; + } + CHECK_ERROR(pmic_write_reg(REG_ADC_0, + BITFVAL(MC13783_ADC0_TS_M, touch_mode), + BITFMASK(MC13783_ADC0_TS_M))); + return PMIC_SUCCESS; +} + +/*! + * This function retrieves the current touch screen operation mode. + * + * @param touch_mode Pointer to the retrieved touch screen operation + * mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_touch_mode(t_touch_mode * touch_mode) +{ + unsigned int value; + if (suspend_flag == 1) { + return -EBUSY; + } + CHECK_ERROR(pmic_read_reg(REG_ADC_0, &value, PMIC_ALL_BITS)); + + *touch_mode = BITFEXT(value, MC13783_ADC0_TS_M); + + return PMIC_SUCCESS; +} + +/*! + * This function retrieves the current touch screen (X,Y) coordinates. + * + * @param touch_sample Pointer to touch sample. + * @param wait indicates whether this call must block or not. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_touch_sample(t_touch_screen * touch_sample, int wait) +{ + if (mc13783_adc_read_ts(touch_sample, wait) != 0) + return PMIC_ERROR; + if (0 == pmic_adc_filter(touch_sample)) + return PMIC_SUCCESS; + else + return PMIC_ERROR; +} + +/*! + * This function read the touch screen value. + * + * @param ts_value return value of touch screen + * @param wait_tsi if true, this function is synchronous (wait in TSI event). + * + * @return This function returns 0. + */ +PMIC_STATUS mc13783_adc_read_ts(t_touch_screen * ts_value, int wait_tsi) +{ + t_adc_param param; + pr_debug("mc13783_adc : mc13783_adc_read_ts\n"); + if (suspend_flag == 1) { + return -EBUSY; + } + if (wait_ts) { + pr_debug("mc13783_adc : error TS busy \n"); + return PMIC_ERROR; + } + mc13783_adc_init_param(¶m); + param.wait_tsi = wait_tsi; + param.read_ts = true; + if (mc13783_adc_convert(¶m) != 0) + return PMIC_ERROR; + /* check if x-y is ok */ + if ((param.ts_value.x_position1 < TS_X_MAX) && + (param.ts_value.x_position1 >= TS_X_MIN) && + (param.ts_value.y_position1 < TS_Y_MAX) && + (param.ts_value.y_position1 >= TS_Y_MIN) && + (param.ts_value.x_position2 < TS_X_MAX) && + (param.ts_value.x_position2 >= TS_X_MIN) && + (param.ts_value.y_position2 < TS_Y_MAX) && + (param.ts_value.y_position2 >= TS_Y_MIN) && + (param.ts_value.x_position3 < TS_X_MAX) && + (param.ts_value.x_position3 >= TS_X_MIN) && + (param.ts_value.y_position3 < TS_Y_MAX) && + (param.ts_value.y_position3 >= TS_Y_MIN)) { + ts_value->x_position = param.ts_value.x_position; + ts_value->x_position1 = param.ts_value.x_position1; + ts_value->x_position2 = param.ts_value.x_position2; + ts_value->x_position3 = param.ts_value.x_position3; + ts_value->y_position = param.ts_value.y_position; + ts_value->y_position1 = param.ts_value.y_position1; + ts_value->y_position2 = param.ts_value.y_position2; + ts_value->y_position3 = param.ts_value.y_position3; + ts_value->contact_resistance = + param.ts_value.contact_resistance + 1; + + } else { + ts_value->x_position = 0; + ts_value->y_position = 0; + ts_value->contact_resistance = 0; + + } + return PMIC_SUCCESS; +} + +/*! + * This function starts a Battery Current mode conversion. + * + * @param mode Conversion mode. + * @param result Battery Current measurement result. + * if \a mode = ADC_8CHAN_1X, the result is \n + * result[0] = (BATTP - BATT_I) \n + * if \a mode = ADC_1CHAN_8X, the result is \n + * result[0] = BATTP \n + * result[1] = BATT_I \n + * result[2] = BATTP \n + * result[3] = BATT_I \n + * result[4] = BATTP \n + * result[5] = BATT_I \n + * result[6] = BATTP \n + * result[7] = BATT_I + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_battery_current(t_conversion_mode mode, + unsigned short *result) +{ + PMIC_STATUS ret; + t_channel channel; + if (suspend_flag == 1) { + return -EBUSY; + } + channel = BATTERY_CURRENT; + if (mode == ADC_8CHAN_1X) { + ret = pmic_adc_convert(channel, result); + } else { + ret = pmic_adc_convert_8x(channel, result); + } + return ret; +} + +/*! + * This function request a ADC. + * + * @return This function returns index of ADC to be used (0 or 1) if successful. + return -1 if error. + */ +int mc13783_adc_request(bool read_ts) +{ + int adc_index = -1; + if (read_ts != 0) { + /*for ts we use bis=0 */ + if (adc_dev[0] == ADC_USED) + return -1; + /*no wait here */ + adc_dev[0] = ADC_USED; + adc_index = 0; + } else { + /*for other adc use bis = 1 */ + if (adc_dev[1] == ADC_USED) { + return -1; + /*no wait here */ + } + adc_dev[1] = ADC_USED; + adc_index = 1; + } + pr_debug("mc13783_adc : request ADC %d\n", adc_index); + return adc_index; +} + +/*! + * This function release an ADC. + * + * @param adc_index index of ADC to be released. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_release(int adc_index) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + + pr_debug("mc13783_adc : release ADC %d\n", adc_index); + if ((adc_dev[adc_index] == ADC_MONITORING) || + (adc_dev[adc_index] == ADC_USED)) { + adc_dev[adc_index] = ADC_FREE; + wake_up(&queue_adc_busy); + return 0; + } + return -1; +} + +/*! + * This function initializes monitoring structure. + * + * @param monitor Structure to be initialized. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_init_monitor_param(t_monitoring_param * monitor) +{ + pr_debug("mc13783_adc : init monitor\n"); + monitor->delay = 0; + monitor->conv_delay = false; + monitor->channel = BATTERY_VOLTAGE; + monitor->read_mode = 0; + monitor->comp_low = 0; + monitor->comp_high = 0; + monitor->group = 0; + monitor->check_mode = CHECK_LOW_OR_HIGH; + monitor->callback = NULL; + return 0; +} + +/*! + * This function actives the comparator. When comparator is active and ADC + * is enabled, the 8th converted value will be digitally compared against the + * window defined by WLOW and WHIGH registers. + * + * @param low Comparison window low threshold (WLOW). + * @param high Comparison window high threshold (WHIGH). + * @param channel The channel to be sampled + * @param callback Callback function to be called when the converted + * value is beyond the comparison window. The callback + * function will pass a parameter of type + * \b t_comp_expection to indicate the reason of + * comparator exception. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_active_comparator(unsigned char low, + unsigned char high, + t_channel channel, + t_comparator_cb callback) +{ + bool use_bis = false; + unsigned int adc_0_reg = 0, adc_1_reg = 0, adc_3_reg = 0; + t_monitoring_param monitoring; + + if (suspend_flag == 1) { + return -EBUSY; + } + if (monitor_en) { + pr_debug("mc13783_adc : monitoring already configured\n"); + return PMIC_ERROR; + } + monitor_en = true; + mc13783_adc_init_monitor_param(&monitoring); + monitoring.comp_low = low; + monitoring.comp_high = high; + monitoring.channel = channel; + monitoring.callback = (void *)callback; + + use_bis = mc13783_adc_request(false); + if (use_bis < 0) { + pr_debug("mc13783_adc : request error\n"); + return PMIC_ERROR; + } + monitor_adc = use_bis; + + adc_0_reg = 0; + + /* TO DO ADOUT CONFIGURE */ + adc_0_reg = monitoring.read_mode & ADC_MODE_MASK; + if (use_bis) { + /* add adc bis */ + adc_0_reg |= ADC_BIS; + } + adc_0_reg |= ADC_WCOMP; + + /* CONFIGURE ADC REG 1 */ + adc_1_reg = 0; + adc_1_reg |= ADC_EN; + if (monitoring.conv_delay) { + adc_1_reg |= ADC_ATO; + } + if (monitoring.group) { + adc_1_reg |= ADC_ADSEL; + } + adc_1_reg |= (monitoring.channel << ADC_CH_0_POS) & ADC_CH_0_MASK; + adc_1_reg |= (monitoring.delay << ADC_DELAY_POS) & ADC_DELAY_MASK; + if (use_bis) { + adc_1_reg |= ADC_BIS; + } + + adc_3_reg |= (monitoring.comp_high << ADC_WCOMP_H_POS) & + ADC_WCOMP_H_MASK; + adc_3_reg |= (monitoring.comp_low << ADC_WCOMP_L_POS) & + ADC_WCOMP_L_MASK; + if (use_bis) { + adc_3_reg |= ADC_BIS; + } + + wcomp_mode = monitoring.check_mode; + /* call back to be called when event is detected. */ + monitoring_cb = monitoring.callback; + + CHECK_ERROR(pmic_write_reg(REG_ADC_0, adc_0_reg, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, adc_3_reg, PMIC_ALL_BITS)); + return PMIC_SUCCESS; +} + +/*! + * This function deactivates the comparator. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_deactive_comparator(void) +{ + unsigned int reg_value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + if (!monitor_en) { + pr_debug("mc13783_adc : adc monitoring free\n"); + return PMIC_ERROR; + } + + if (monitor_en) { + reg_value = ADC_BIS; + } + + /* clear all reg value */ + CHECK_ERROR(pmic_write_reg(REG_ADC_0, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, reg_value, PMIC_ALL_BITS)); + + reg_value = 0; + + if (monitor_adc) { + CHECK_ERROR(pmic_write_reg + (REG_ADC_4, reg_value, PMIC_ALL_BITS)); + } else { + CHECK_ERROR(pmic_write_reg + (REG_ADC_2, reg_value, PMIC_ALL_BITS)); + } + + mc13783_adc_release(monitor_adc); + monitor_en = false; + return PMIC_SUCCESS; +} + +/*! + * This function implements IOCTL controls on a MC13783 ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_adc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + t_adc_convert_param *convert_param; + t_touch_mode touch_mode; + t_touch_screen touch_sample; + unsigned short b_current; + t_adc_comp_param *comp_param; + if ((_IOC_TYPE(cmd) != 'p') && (_IOC_TYPE(cmd) != 'D')) + return -ENOTTY; + + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + + switch (cmd) { + case PMIC_ADC_INIT: + pr_debug("init adc\n"); + CHECK_ERROR(pmic_adc_init()); + break; + + case PMIC_ADC_DEINIT: + pr_debug("deinit adc\n"); + CHECK_ERROR(pmic_adc_deinit()); + break; + + case PMIC_ADC_CONVERT: + if ((convert_param = kmalloc(sizeof(t_adc_convert_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(convert_param, (t_adc_convert_param *) arg, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_adc_convert(convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((t_adc_convert_param *) arg, convert_param, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case PMIC_ADC_CONVERT_8X: + if ((convert_param = kmalloc(sizeof(t_adc_convert_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(convert_param, (t_adc_convert_param *) arg, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_adc_convert_8x(convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((t_adc_convert_param *) arg, convert_param, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case PMIC_ADC_CONVERT_MULTICHANNEL: + if ((convert_param = kmalloc(sizeof(t_adc_convert_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(convert_param, (t_adc_convert_param *) arg, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_adc_convert_multichnnel + (convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((t_adc_convert_param *) arg, convert_param, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case PMIC_ADC_SET_TOUCH_MODE: + CHECK_ERROR(pmic_adc_set_touch_mode((t_touch_mode) arg)); + break; + + case PMIC_ADC_GET_TOUCH_MODE: + CHECK_ERROR(pmic_adc_get_touch_mode(&touch_mode)); + if (copy_to_user((t_touch_mode *) arg, &touch_mode, + sizeof(t_touch_mode))) { + return -EFAULT; + } + break; + + case PMIC_ADC_GET_TOUCH_SAMPLE: + pr_debug("pmic_adc_ioctl: " "PMIC_ADC_GET_TOUCH_SAMPLE\n"); + CHECK_ERROR(pmic_adc_get_touch_sample(&touch_sample, 1)); + if (copy_to_user((t_touch_screen *) arg, &touch_sample, + sizeof(t_touch_screen))) { + return -EFAULT; + } + break; + + case PMIC_ADC_GET_BATTERY_CURRENT: + CHECK_ERROR(pmic_adc_get_battery_current(ADC_8CHAN_1X, + &b_current)); + if (copy_to_user((unsigned short *)arg, &b_current, + sizeof(unsigned short))) { + + return -EFAULT; + } + break; + + case PMIC_ADC_ACTIVATE_COMPARATOR: + if ((comp_param = kmalloc(sizeof(t_adc_comp_param), GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + if (copy_from_user(comp_param, (t_adc_comp_param *) arg, + sizeof(t_adc_comp_param))) { + kfree(comp_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_adc_active_comparator(comp_param->wlow, + comp_param->whigh, + comp_param-> + channel, + comp_param-> + callback), + (kfree(comp_param))); + break; + + case PMIC_ADC_DEACTIVE_COMPARATOR: + CHECK_ERROR(pmic_adc_deactive_comparator()); + break; + + default: + pr_debug("pmic_adc_ioctl: unsupported ioctl command 0x%x\n", + cmd); + return -EINVAL; + } + return 0; +} + +static struct file_operations mc13783_adc_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_adc_ioctl, + .open = pmic_adc_open, + .release = pmic_adc_free, +}; + +static int pmic_adc_module_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *temp_class; + + pmic_adc_major = register_chrdev(0, "pmic_adc", &mc13783_adc_fops); + + if (pmic_adc_major < 0) { + pr_debug(KERN_ERR "Unable to get a major for pmic_adc\n"); + return pmic_adc_major; + } + init_waitqueue_head(&suspendq); + + pmic_adc_class = class_create(THIS_MODULE, "pmic_adc"); + if (IS_ERR(pmic_adc_class)) { + pr_debug(KERN_ERR "Error creating pmic_adc class.\n"); + ret = PTR_ERR(pmic_adc_class); + goto err_out1; + } + + temp_class = device_create(pmic_adc_class, NULL, + MKDEV(pmic_adc_major, 0), NULL, "pmic_adc"); + if (IS_ERR(temp_class)) { + pr_debug(KERN_ERR "Error creating pmic_adc class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + ret = pmic_adc_init(); + if (ret != PMIC_SUCCESS) { + pr_debug(KERN_ERR "Error in pmic_adc_init.\n"); + goto err_out4; + } + + pmic_adc_ready = 1; + pr_debug(KERN_INFO "PMIC ADC successfully probed\n"); + return ret; + + err_out4: + device_destroy(pmic_adc_class, MKDEV(pmic_adc_major, 0)); + err_out2: + class_destroy(pmic_adc_class); + err_out1: + unregister_chrdev(pmic_adc_major, "pmic_adc"); + return ret; +} + +static int pmic_adc_module_remove(struct platform_device *pdev) +{ + pmic_adc_ready = 0; + pmic_adc_deinit(); + device_destroy(pmic_adc_class, MKDEV(pmic_adc_major, 0)); + class_destroy(pmic_adc_class); + unregister_chrdev(pmic_adc_major, "pmic_adc"); + pr_debug(KERN_INFO "PMIC ADC successfully removed\n"); + return 0; +} + +static struct platform_driver pmic_adc_driver_ldm = { + .driver = { + .name = "pmic_adc", + }, + .suspend = pmic_adc_suspend, + .resume = pmic_adc_resume, + .probe = pmic_adc_module_probe, + .remove = pmic_adc_module_remove, +}; + +/* + * Initialization and Exit + */ +static int __init pmic_adc_module_init(void) +{ + pr_debug("PMIC ADC driver loading...\n"); + return platform_driver_register(&pmic_adc_driver_ldm); +} + +static void __exit pmic_adc_module_exit(void) +{ + platform_driver_unregister(&pmic_adc_driver_ldm); + pr_debug("PMIC ADC driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(pmic_adc_module_init); +module_exit(pmic_adc_module_exit); + +MODULE_DESCRIPTION("PMIC ADC device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_adc_defs.h b/drivers/mxc/pmic/mc13783/pmic_adc_defs.h new file mode 100644 index 000000000000..db1b08232c77 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_adc_defs.h @@ -0,0 +1,321 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_adc_defs.h + * @brief This header contains all defines for PMIC(mc13783) ADC driver. + * + * @ingroup PMIC_ADC + */ + +#ifndef __MC13783_ADC__DEFS_H__ +#define __MC13783_ADC__DEFS_H__ + +#define MC13783_ADC_DEVICE "/dev/mc13783_adc" + +#define DEF_ADC_0 0x008000 +#define DEF_ADC_3 0x000080 + +#define ADC_NB_AVAILABLE 2 + +#define MAX_CHANNEL 7 + +/* + * Maximun allowed variation in the three X/Y co-ordinates acquired from + * touch-screen + */ +#define DELTA_Y_MAX 50 +#define DELTA_X_MAX 50 + +/* Upon clearing the filter, this is the delay in restarting the filter */ +#define FILTER_MIN_DELAY 4 + +/* Length of X and Y Touch screen filters */ +#define FILTLEN 3 + +#define TS_X_MAX 1000 +#define TS_Y_MAX 1000 + +#define TS_X_MIN 80 +#define TS_Y_MIN 80 + +#define MC13783_ADC0_TS_M_LSH 14 +#define MC13783_ADC0_TS_M_WID 3 +/* + * ADC 0 + */ +#define ADC_WAIT_TSI_0 0x001C00 + +/* + * ADC 1 + */ + +#define ADC_EN 0x000001 +#define ADC_SGL_CH 0x000002 +#define ADC_ADSEL 0x000008 +#define ADC_CH_0_POS 5 +#define ADC_CH_0_MASK 0x0000E0 +#define ADC_CH_1_POS 8 +#define ADC_CH_1_MASK 0x000700 +#define ADC_DELAY_POS 11 +#define ADC_DELAY_MASK 0x07F800 +#define ADC_ATO 0x080000 +#define ASC_ADC 0x100000 +#define ADC_WAIT_TSI_1 0x300001 +#define ADC_CHRGRAW_D5 0x008000 + +/* + * ADC 2 - 4 + */ +#define ADD1_RESULT_MASK 0x00000FFC +#define ADD2_RESULT_MASK 0x00FFC000 +#define ADC_TS_MASK 0x00FFCFFC + +/* + * ADC 3 + */ +#define ADC_INC 0x030000 +#define ADC_BIS 0x800000 + +/* + * ADC 3 + */ +#define ADC_NO_ADTRIG 0x200000 +#define ADC_WCOMP 0x040000 +#define ADC_WCOMP_H_POS 0 +#define ADC_WCOMP_L_POS 9 +#define ADC_WCOMP_H_MASK 0x00003F +#define ADC_WCOMP_L_MASK 0x007E00 + +#define ADC_MODE_MASK 0x00003F + +/* + * Interrupt Status 0 + */ +#define ADC_INT_BISDONEI 0x02 + +/*! + * Define state mode of ADC. + */ +typedef enum adc_state { + /*! + * Free. + */ + ADC_FREE, + /*! + * Used. + */ + ADC_USED, + /*! + * Monitoring + */ + ADC_MONITORING, +} t_adc_state; + +/*! + * This enumeration, is used to configure the mode of ADC. + */ +typedef enum reading_mode { + /*! + * Enables lithium cell reading + */ + M_LITHIUM_CELL = 0x000001, + /*! + * Enables charge current reading + */ + M_CHARGE_CURRENT = 0x000002, + /*! + * Enables battery current reading + */ + M_BATTERY_CURRENT = 0x000004, + /*! + * Enables thermistor reading + */ + M_THERMISTOR = 0x000008, + /*! + * Enables die temperature reading + */ + M_DIE_TEMPERATURE = 0x000010, + /*! + * Enables UID reading + */ + M_UID = 0x000020, +} t_reading_mode; + +/*! + * This enumeration, is used to configure the monitoring mode. + */ +typedef enum check_mode { + /*! + * Comparator low level + */ + CHECK_LOW, + /*! + * Comparator high level + */ + CHECK_HIGH, + /*! + * Comparator low or high level + */ + CHECK_LOW_OR_HIGH, +} t_check_mode; + +/*! + * This structure is used to configure and report adc value. + */ +typedef struct { + /*! + * Delay before first conversion + */ + unsigned int delay; + /*! + * sets the ATX bit for delay on all conversion + */ + bool conv_delay; + /*! + * Sets the single channel mode + */ + bool single_channel; + /*! + * Selects the set of inputs + */ + bool group; + /*! + * Channel selection 1 + */ + t_channel channel_0; + /*! + * Channel selection 2 + */ + t_channel channel_1; + /*! + * Used to configure ADC mode with t_reading_mode + */ + t_reading_mode read_mode; + /*! + * Sets the Touch screen mode + */ + bool read_ts; + /*! + * Wait TSI event before touch screen reading + */ + bool wait_tsi; + /*! + * Sets CHRGRAW scaling to divide by 5 + * Only supported on 2.0 and higher + */ + bool chrgraw_devide_5; + /*! + * Return ADC values + */ + unsigned int value[8]; + /*! + * Return touch screen values + */ + t_touch_screen ts_value; +} t_adc_param; + +/*! + * This structure is used to configure the monitoring mode of ADC. + */ +typedef struct { + /*! + * Delay before first conversion + */ + unsigned int delay; + /*! + * sets the ATX bit for delay on all conversion + */ + bool conv_delay; + /*! + * Channel selection 1 + */ + t_channel channel; + /*! + * Selects the set of inputs + */ + bool group; + /*! + * Used to configure ADC mode with t_reading_mode + */ + unsigned int read_mode; + /*! + * Comparator low level in WCOMP mode + */ + unsigned int comp_low; + /*! + * Comparator high level in WCOMP mode + */ + unsigned int comp_high; + /*! + * Sets type of monitoring (low, high or both) + */ + t_check_mode check_mode; + /*! + * Callback to be launched when event is detected + */ + void (*callback) (void); +} t_monitoring_param; + +/*! + * This function performs filtering and rejection of excessive noise prone + * samples. + * + * @param ts_curr Touch screen value + * + * @return This function returns 0 on success, -1 otherwise. + */ +static int pmic_adc_filter(t_touch_screen * ts_curr); + +/*! + * This function request a ADC. + * + * @return This function returns index of ADC to be used (0 or 1) if successful. + return -1 if error. + */ +int mc13783_adc_request(bool read_ts); + +/*! + * This function is used to update buffer of touch screen value in read mode. + */ +void update_buffer(void); + +/*! + * This function release an ADC. + * + * @param adc_index index of ADC to be released. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_release(int adc_index); + +/*! + * This function select the required read_mode for a specific channel. + * + * @param channel The channel to be sampled + * + * @return This function returns the requires read_mode + */ +t_reading_mode mc13783_set_read_mode(t_channel channel); + +/*! + * This function read the touch screen value. + * + * @param touch_sample return value of touch screen + * @param wait_tsi if true, this function is synchronous (wait in TSI event). + * + * @return This function returns 0. + */ +PMIC_STATUS mc13783_adc_read_ts(t_touch_screen * touch_sample, int wait_tsi); + +#endif /* __MC13783_ADC__DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_audio.c b/drivers/mxc/pmic/mc13783/pmic_audio.c new file mode 100644 index 000000000000..6aab4b83f6ea --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_audio.c @@ -0,0 +1,5876 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_audio.c + * @brief Implementation of the PMIC(mc13783) Audio driver APIs. + * + * The PMIC Audio driver and this API were developed to support the + * audio playback, recording, and mixing capabilities of the power + * management ICs that are available from Freescale Semiconductor, Inc. + * + * The following operating modes are supported: + * + * @verbatim + Operating Mode mc13783 + ---------------------------- ------- + Stereo DAC Playback Yes + Stereo DAC Input Mixing Yes + Voice CODEC Playback Yes + Voice CODEC Input Mixing Yes + Voice CODEC Mono Recording Yes + Voice CODEC Stereo Recording Yes + Microphone Bias Control Yes + Output Amplifier Control Yes + Output Mixing Control Yes + Input Amplifier Control Yes + Master/Slave Mode Select Yes + Anti Pop Bias Circuit Control Yes + @endverbatim + * + * Note that the Voice CODEC may also be referred to as the Telephone + * CODEC in the PMIC DTS documentation. Also note that, while the power + * management ICs do provide similar audio capabilities, each PMIC may + * support additional configuration settings and features. Therefore, it + * is highly recommended that the appropriate power management IC DTS + * documents be used in conjunction with this API interface. + * + * @ingroup PMIC_AUDIO + */ + +#include <linux/module.h> +#include <linux/interrupt.h> /* For tasklet interface. */ +#include <linux/platform_device.h> /* For kernel module interface. */ +#include <linux/init.h> +#include <linux/spinlock.h> /* For spinlock interface. */ +#include <linux/pmic_adc.h> /* For PMIC ADC driver interface. */ +#include <linux/pmic_status.h> +#include <mach/pmic_audio.h> /* For PMIC Audio driver interface. */ + +/* + * mc13783 PMIC Audio API + */ + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(MIN_STDAC_SAMPLING_RATE_HZ); +EXPORT_SYMBOL(MAX_STDAC_SAMPLING_RATE_HZ); +EXPORT_SYMBOL(pmic_audio_open); +EXPORT_SYMBOL(pmic_audio_close); +EXPORT_SYMBOL(pmic_audio_set_protocol); +EXPORT_SYMBOL(pmic_audio_get_protocol); +EXPORT_SYMBOL(pmic_audio_enable); +EXPORT_SYMBOL(pmic_audio_disable); +EXPORT_SYMBOL(pmic_audio_reset); +EXPORT_SYMBOL(pmic_audio_reset_all); +EXPORT_SYMBOL(pmic_audio_set_callback); +EXPORT_SYMBOL(pmic_audio_clear_callback); +EXPORT_SYMBOL(pmic_audio_get_callback); +EXPORT_SYMBOL(pmic_audio_antipop_enable); +EXPORT_SYMBOL(pmic_audio_antipop_disable); +EXPORT_SYMBOL(pmic_audio_digital_filter_reset); +EXPORT_SYMBOL(pmic_audio_vcodec_set_clock); +EXPORT_SYMBOL(pmic_audio_vcodec_get_clock); +EXPORT_SYMBOL(pmic_audio_vcodec_set_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_vcodec_get_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_vcodec_set_secondary_txslot); +EXPORT_SYMBOL(pmic_audio_vcodec_get_secondary_txslot); +EXPORT_SYMBOL(pmic_audio_vcodec_set_config); +EXPORT_SYMBOL(pmic_audio_vcodec_clear_config); +EXPORT_SYMBOL(pmic_audio_vcodec_get_config); +EXPORT_SYMBOL(pmic_audio_vcodec_enable_bypass); +EXPORT_SYMBOL(pmic_audio_vcodec_disable_bypass); +EXPORT_SYMBOL(pmic_audio_stdac_set_clock); +EXPORT_SYMBOL(pmic_audio_stdac_get_clock); +EXPORT_SYMBOL(pmic_audio_stdac_set_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_stdac_get_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_stdac_set_config); +EXPORT_SYMBOL(pmic_audio_stdac_clear_config); +EXPORT_SYMBOL(pmic_audio_stdac_get_config); +EXPORT_SYMBOL(pmic_audio_input_set_config); +EXPORT_SYMBOL(pmic_audio_input_clear_config); +EXPORT_SYMBOL(pmic_audio_input_get_config); +EXPORT_SYMBOL(pmic_audio_vcodec_set_mic); +EXPORT_SYMBOL(pmic_audio_vcodec_get_mic); +EXPORT_SYMBOL(pmic_audio_vcodec_set_mic_on_off); +EXPORT_SYMBOL(pmic_audio_vcodec_get_mic_on_off); +EXPORT_SYMBOL(pmic_audio_vcodec_set_record_gain); +EXPORT_SYMBOL(pmic_audio_vcodec_get_record_gain); +EXPORT_SYMBOL(pmic_audio_vcodec_enable_micbias); +EXPORT_SYMBOL(pmic_audio_vcodec_disable_micbias); +EXPORT_SYMBOL(pmic_audio_vcodec_enable_mixer); +EXPORT_SYMBOL(pmic_audio_vcodec_disable_mixer); +EXPORT_SYMBOL(pmic_audio_stdac_enable_mixer); +EXPORT_SYMBOL(pmic_audio_stdac_disable_mixer); +EXPORT_SYMBOL(pmic_audio_output_set_port); +EXPORT_SYMBOL(pmic_audio_output_get_port); +EXPORT_SYMBOL(pmic_audio_output_clear_port); +EXPORT_SYMBOL(pmic_audio_output_set_stereo_in_gain); +EXPORT_SYMBOL(pmic_audio_output_get_stereo_in_gain); +EXPORT_SYMBOL(pmic_audio_output_set_pgaGain); +EXPORT_SYMBOL(pmic_audio_output_get_pgaGain); +EXPORT_SYMBOL(pmic_audio_output_enable_mixer); +EXPORT_SYMBOL(pmic_audio_output_disable_mixer); +EXPORT_SYMBOL(pmic_audio_output_set_balance); +EXPORT_SYMBOL(pmic_audio_output_get_balance); +EXPORT_SYMBOL(pmic_audio_output_enable_mono_adder); +EXPORT_SYMBOL(pmic_audio_output_disable_mono_adder); +EXPORT_SYMBOL(pmic_audio_output_set_mono_adder_gain); +EXPORT_SYMBOL(pmic_audio_output_get_mono_adder_gain); +EXPORT_SYMBOL(pmic_audio_output_set_config); +EXPORT_SYMBOL(pmic_audio_output_clear_config); +EXPORT_SYMBOL(pmic_audio_output_get_config); +EXPORT_SYMBOL(pmic_audio_output_enable_phantom_ground); +EXPORT_SYMBOL(pmic_audio_output_disable_phantom_ground); +EXPORT_SYMBOL(pmic_audio_set_autodetect); +#ifdef DEBUG_AUDIO +EXPORT_SYMBOL(pmic_audio_dump_registers); +#endif /* DEBUG_AUDIO */ +/*! + * Define the minimum sampling rate (in Hz) that is supported by the + * Stereo DAC. + */ +const unsigned MIN_STDAC_SAMPLING_RATE_HZ = 8000; + +/*! + * Define the maximum sampling rate (in Hz) that is supported by the + * Stereo DAC. + */ +const unsigned MAX_STDAC_SAMPLING_RATE_HZ = 96000; + +/*! @def SET_BITS + * Set a register field to a given value. + */ +#define SET_BITS(reg, field, value) (((value) << reg.field.offset) & \ + reg.field.mask) +/*! @def GET_BITS + * Get the current value of a given register field. + */ +#define GET_BITS(reg, field, value) (((value) & reg.field.mask) >> \ + reg.field.offset) + +/*! + * @brief Define the possible states for a device handle. + * + * This enumeration is used to track the current state of each device handle. + */ +typedef enum { + HANDLE_FREE, /*!< Handle is available for use. */ + HANDLE_IN_USE /*!< Handle is currently in use. */ +} HANDLE_STATE; + +/*! + * @brief Identifies the hardware interrupt source. + * + * This enumeration identifies which of the possible hardware interrupt + * sources actually caused the current interrupt handler to be called. + */ +typedef enum { + CORE_EVENT_MC2BI, /*!< Microphone Bias 2 detect. */ + CORE_EVENT_HSDETI, /*!< Detect Headset attach */ + CORE_EVENT_HSLI, /*!< Detect Stereo Headset */ + CORE_EVENT_ALSPTHI, /*!< Detect Thermal shutdown of ALSP */ + CORE_EVENT_AHSSHORTI /*!< Detect Short circuit on AHS outputs */ +} PMIC_CORE_EVENT; + +/*! + * @brief This structure is used to track the state of a microphone input. + */ +typedef struct { + PMIC_AUDIO_INPUT_PORT mic; /*!< Microphone input port. */ + PMIC_AUDIO_INPUT_MIC_STATE micOnOff; /*!< Microphone On/Off state. */ + PMIC_AUDIO_MIC_AMP_MODE ampMode; /*!< Input amplifier mode. */ + PMIC_AUDIO_MIC_GAIN gain; /*!< Input amplifier gain level. */ +} PMIC_MICROPHONE_STATE; + +/*! + * @brief Tracks whether a headset is currently attached or not. + */ +typedef enum { + NO_HEADSET, /*!< No headset currently attached. */ + HEADSET_ON /*!< Headset has been attached. */ +} HEADSET_STATUS; + +/*! + * @brief mc13783 only enum that indicates the path to output taken + * by the voice codec output + */ +typedef enum { + VCODEC_DIRECT_OUT, /*!< Vcodec signal out direct */ + VCODEC_MIXER_OUT /*!< Output via the mixer */ +} PMIC_AUDIO_VCODEC_OUTPUT_PATH; + +/*! + * @brief This structure is used to define a specific hardware register field. + * + * All hardware register fields are defined using an offset to the LSB + * and a mask. The offset is used to right shift a register value before + * applying the mask to actually obtain the value of the field. + */ +typedef struct { + const unsigned char offset; /*!< Offset of LSB of register field. */ + const unsigned int mask; /*!< Mask value used to isolate register field. */ +} REGFIELD; + +/*! + * @brief This structure lists all fields of the AUD_CODEC hardware register. + */ +typedef struct { + REGFIELD CDCSSISEL; /*!< codec SSI bus select */ + REGFIELD CDCCLKSEL; /*!< Codec clock input select */ + REGFIELD CDCSM; /*!< Codec slave / master select */ + REGFIELD CDCBCLINV; /*!< Codec bitclock inversion */ + REGFIELD CDCFSINV; /*!< Codec framesync inversion */ + REGFIELD CDCFS; /*!< Bus protocol selection - 2 bits */ + REGFIELD CDCCLK; /*!< Codec clock setting - 3 bits */ + REGFIELD CDCFS8K16K; /*!< Codec framesync select */ + REGFIELD CDCEN; /*!< Codec enable */ + REGFIELD CDCCLKEN; /*!< Codec clocking enable */ + REGFIELD CDCTS; /*!< Codec SSI tristate */ + REGFIELD CDCDITH; /*!< Codec dithering */ + REGFIELD CDCRESET; /*!< Codec filter reset */ + REGFIELD CDCBYP; /*!< Codec bypass */ + REGFIELD CDCALM; /*!< Codec analog loopback */ + REGFIELD CDCDLM; /*!< Codec digital loopback */ + REGFIELD AUDIHPF; /*!< Transmit high pass filter enable */ + REGFIELD AUDOHPF; /*!< Receive high pass filter enable */ +} REGISTER_AUD_CODEC; + +/*! + * @brief This variable is used to access the AUD_CODEC hardware register. + * + * This variable defines how to access all of the fields within the + * AUD_CODEC hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUD_CODEC regAUD_CODEC = { + {0, 0x000001}, /* CDCSSISEL */ + {1, 0x000002}, /* CDCCLKSEL */ + {2, 0x000004}, /* CDCSM */ + {3, 0x000008}, /* CDCBCLINV */ + {4, 0x000010}, /* CDCFSINV */ + {5, 0x000060}, /* CDCFS */ + {7, 0x000380}, /* CDCCLK */ + {10, 0x000400}, /* CDCFS8K16K */ + {11, 0x000800}, /* CDCEN */ + {12, 0x001000}, /* CDCCLKEN */ + {13, 0x002000}, /* CDCTS */ + {14, 0x004000}, /* CDCDITH */ + {15, 0x008000}, /* CDCRESET */ + {16, 0x010000}, /* CDCBYP */ + {17, 0x020000}, /* CDCALM */ + {18, 0x040000}, /* CDCDLM */ + {19, 0x080000}, /* AUDIHPF */ + {20, 0x100000} /* AUDOHPF */ + /* Unused */ + /* Unused */ + /* Unused */ + +}; + +/*! + * @brief This structure lists all fields of the ST_DAC hardware register. + */ + /* VVV */ +typedef struct { + REGFIELD STDCSSISEL; /*!< Stereo DAC SSI bus select */ + REGFIELD STDCCLKSEL; /*!< Stereo DAC clock input select */ + REGFIELD STDCSM; /*!< Stereo DAC slave / master select */ + REGFIELD STDCBCLINV; /*!< Stereo DAC bitclock inversion */ + REGFIELD STDCFSINV; /*!< Stereo DAC framesync inversion */ + REGFIELD STDCFS; /*!< Bus protocol selection - 2 bits */ + REGFIELD STDCCLK; /*!< Stereo DAC clock setting - 3 bits */ + REGFIELD STDCFSDLYB; /*!< Stereo DAC framesync delay bar */ + REGFIELD STDCEN; /*!< Stereo DAC enable */ + REGFIELD STDCCLKEN; /*!< Stereo DAC clocking enable */ + REGFIELD STDCRESET; /*!< Stereo DAC filter reset */ + REGFIELD SPDIF; /*!< Stereo DAC SSI SPDIF mode. Mode no longer available. */ + REGFIELD SR; /*!< Stereo DAC sample rate - 4 bits */ +} REGISTER_ST_DAC; + +/*! + * @brief This variable is used to access the ST_DAC hardware register. + * + * This variable defines how to access all of the fields within the + * ST_DAC hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_ST_DAC regST_DAC = { + {0, 0x000001}, /* STDCSSISEL */ + {1, 0x000002}, /* STDCCLKSEL */ + {2, 0x000004}, /* STDCSM */ + {3, 0x000008}, /* STDCBCLINV */ + {4, 0x000010}, /* STDCFSINV */ + {5, 0x000060}, /* STDCFS */ + {7, 0x000380}, /* STDCCLK */ + {10, 0x000400}, /* STDCFSDLYB */ + {11, 0x000800}, /* STDCEN */ + {12, 0x001000}, /* STDCCLKEN */ + {15, 0x008000}, /* STDCRESET */ + {16, 0x010000}, /* SPDIF */ + {17, 0x1E0000} /* SR */ +}; + +/*! + * @brief This structure lists all of the fields in the SSI_NETWORK hardware register. + */ +typedef struct { + REGFIELD CDCTXRXSLOT; /*!< Codec timeslot assignment - 2 bits */ + REGFIELD CDCTXSECSLOT; /*!< Codec secondary transmit timeslot - 2 bits */ + REGFIELD CDCRXSECSLOT; /*!< Codec secondary receive timeslot - 2 bits */ + REGFIELD CDCRXSECGAIN; /*!< Codec secondary receive channel gain setting - 2 bits */ + REGFIELD CDCSUMGAIN; /*!< Codec summed receive signal gain setting */ + REGFIELD CDCFSDLY; /*!< Codec framesync delay */ + REGFIELD STDCSLOTS; /*!< Stereo DAC number of timeslots select - 2 bits */ + REGFIELD STDCRXSLOT; /*!< Stereo DAC timeslot assignment - 2 bits */ + REGFIELD STDCRXSECSLOT; /*!< Stereo DAC secondary receive timeslot - 2 bits */ + REGFIELD STDCRXSECGAIN; /*!< Stereo DAC secondary receive channel gain setting - 2 bits */ + REGFIELD STDCSUMGAIN; /*!< Stereo DAC summed receive signal gain setting */ +} REGISTER_SSI_NETWORK; + +/*! + * @brief This variable is used to access the SSI_NETWORK hardware register. + * + * This variable defines how to access all of the fields within the + * SSI_NETWORK hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_SSI_NETWORK regSSI_NETWORK = { + {2, 0x00000c}, /* CDCTXRXSLOT */ + {4, 0x000030}, /* CDCTXSECSLOT */ + {6, 0x0000c0}, /* CDCRXSECSLOT */ + {8, 0x000300}, /* CDCRXSECGAIN */ + {10, 0x000400}, /* CDCSUMGAIN */ + {11, 0x000800}, /* CDCFSDLY */ + {12, 0x003000}, /* STDCSLOTS */ + {14, 0x00c000}, /* STDCRXSLOT */ + {16, 0x030000}, /* STDCRXSECSLOT */ + {18, 0x0c0000}, /* STDCRXSECGAIN */ + {20, 0x100000} /* STDCSUMGAIN */ +}; + +/*! + * @brief This structure lists all fields of the AUDIO_TX hardware register. + * + * + */ +typedef struct { + REGFIELD MC1BEN; /*!< Microphone bias 1 enable */ + REGFIELD MC2BEN; /*!< Microphone bias 2 enable */ + REGFIELD MC2BDETDBNC; /*!< Microphone bias detect debounce setting */ + REGFIELD MC2BDETEN; /*!< Microphone bias 2 detect enable */ + REGFIELD AMC1REN; /*!< Amplifier Amc1R enable */ + REGFIELD AMC1RITOV; /*!< Amplifier Amc1R current to voltage mode enable */ + REGFIELD AMC1LEN; /*!< Amplifier Amc1L enable */ + REGFIELD AMC1LITOV; /*!< Amplifier Amc1L current to voltage mode enable */ + REGFIELD AMC2EN; /*!< Amplifier Amc2 enable */ + REGFIELD AMC2ITOV; /*!< Amplifier Amc2 current to voltage mode enable */ + REGFIELD ATXINEN; /*!< Amplifier Atxin enable */ + REGFIELD ATXOUTEN; /*!< Reserved for output TXOUT enable, currently not used */ + REGFIELD RXINREC; /*!< RXINR/RXINL to voice CODEC ADC routing enable */ + REGFIELD PGATXR; /*!< Transmit gain setting right - 5 bits */ + REGFIELD PGATXL; /*!< Transmit gain setting left - 5 bits */ +} REGISTER_AUDIO_TX; + +/*! + * @brief This variable is used to access the AUDIO_TX hardware register. + * + * This variable defines how to access all of the fields within the + * AUDIO_TX hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUDIO_TX regAUDIO_TX = { + {0, 0x000001}, /* MC1BEN */ + {1, 0x000002}, /* MC2BEN */ + {2, 0x000004}, /* MC2BDETDBNC */ + {3, 0x000008}, /* MC2BDETEN */ + {5, 0x000020}, /* AMC1REN */ + {6, 0x000040}, /* AMC1RITOV */ + {7, 0x000080}, /* AMC1LEN */ + {8, 0x000100}, /* AMC1LITOV */ + {9, 0x000200}, /* AMC2EN */ + {10, 0x000400}, /* AMC2ITOV */ + {11, 0x000800}, /* ATXINEN */ + {12, 0x001000}, /* ATXOUTEN */ + {13, 0x002000}, /* RXINREC */ + {14, 0x07c000}, /* PGATXR */ + {19, 0xf80000} /* PGATXL */ +}; + +/*! + * @brief This structure lists all fields of the AUDIO_RX_0 hardware register. + */ +typedef struct { + REGFIELD VAUDIOON; /*!< Forces VAUDIO in active on mode */ + REGFIELD BIASEN; /*!< Audio bias enable */ + REGFIELD BIASSPEED; /*!< Turn on ramp speed of the audio bias */ + REGFIELD ASPEN; /*!< Amplifier Asp enable */ + REGFIELD ASPSEL; /*!< Asp input selector */ + REGFIELD ALSPEN; /*!< Amplifier Alsp enable */ + REGFIELD ALSPREF; /*!< Bias Alsp at common audio reference */ + REGFIELD ALSPSEL; /*!< Alsp input selector */ + REGFIELD LSPLEN; /*!< Output LSPL enable */ + REGFIELD AHSREN; /*!< Amplifier AhsR enable */ + REGFIELD AHSLEN; /*!< Amplifier AhsL enable */ + REGFIELD AHSSEL; /*!< Ahsr and Ahsl input selector */ + REGFIELD HSPGDIS; /*!< Phantom ground disable */ + REGFIELD HSDETEN; /*!< Headset detect enable */ + REGFIELD HSDETAUTOB; /*!< Amplifier state determined by headset detect */ + REGFIELD ARXOUTREN; /*!< Output RXOUTR enable */ + REGFIELD ARXOUTLEN; /*!< Output RXOUTL enable */ + REGFIELD ARXOUTSEL; /*!< Arxout input selector */ + REGFIELD CDCOUTEN; /*!< Output CDCOUT enable */ + REGFIELD HSLDETEN; /*!< Headset left channel detect enable */ + REGFIELD ADDCDC; /*!< Adder channel codec selection */ + REGFIELD ADDSTDC; /*!< Adder channel stereo DAC selection */ + REGFIELD ADDRXIN; /*!< Adder channel line in selection */ +} REGISTER_AUDIO_RX_0; + +/*! + * @brief This variable is used to access the AUDIO_RX_0 hardware register. + * + * This variable defines how to access all of the fields within the + * AUDIO_RX_0 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUDIO_RX_0 regAUDIO_RX_0 = { + {0, 0x000001}, /* VAUDIOON */ + {1, 0x000002}, /* BIASEN */ + {2, 0x000004}, /* BIASSPEED */ + {3, 0x000008}, /* ASPEN */ + {4, 0x000010}, /* ASPSEL */ + {5, 0x000020}, /* ALSPEN */ + {6, 0x000040}, /* ALSPREF */ + {7, 0x000080}, /* ALSPSEL */ + {8, 0x000100}, /* LSPLEN */ + {9, 0x000200}, /* AHSREN */ + {10, 0x000400}, /* AHSLEN */ + {11, 0x000800}, /* AHSSEL */ + {12, 0x001000}, /* HSPGDIS */ + {13, 0x002000}, /* HSDETEN */ + {14, 0x004000}, /* HSDETAUTOB */ + {15, 0x008000}, /* ARXOUTREN */ + {16, 0x010000}, /* ARXOUTLEN */ + {17, 0x020000}, /* ARXOUTSEL */ + {18, 0x040000}, /* CDCOUTEN */ + {19, 0x080000}, /* HSLDETEN */ + {21, 0x200000}, /* ADDCDC */ + {22, 0x400000}, /* ADDSTDC */ + {23, 0x800000} /* ADDRXIN */ +}; + +/*! + * @brief This structure lists all fields of the AUDIO_RX_1 hardware register. + */ +typedef struct { + REGFIELD PGARXEN; /*!< Codec receive PGA enable */ + REGFIELD PGARX; /*!< Codec receive gain setting - 4 bits */ + REGFIELD PGASTEN; /*!< Stereo DAC PGA enable */ + REGFIELD PGAST; /*!< Stereo DAC gain setting - 4 bits */ + REGFIELD ARXINEN; /*!< Amplifier Arx enable */ + REGFIELD ARXIN; /*!< Amplifier Arx additional gain setting */ + REGFIELD PGARXIN; /*!< PGArxin gain setting - 4 bits */ + REGFIELD MONO; /*!< Mono adder setting - 2 bits */ + REGFIELD BAL; /*!< Balance control - 3 bits */ + REGFIELD BALLR; /*!< Left / right balance */ +} REGISTER_AUDIO_RX_1; + +/*! + * @brief This variable is used to access the AUDIO_RX_1 hardware register. + * + * This variable defines how to access all of the fields within the + * AUDIO_RX_1 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUDIO_RX_1 regAUDIO_RX_1 = { + {0, 0x000001}, /* PGARXEN */ + {1, 0x00001e}, /* PGARX */ + {5, 0x000020}, /* PGASTEN */ + {6, 0x0003c0}, /* PGAST */ + {10, 0x000400}, /* ARXINEN */ + {11, 0x000800}, /* ARXIN */ + {12, 0x00f000}, /* PGARXIN */ + {16, 0x030000}, /* MONO */ + {18, 0x1c0000}, /* BAL */ + {21, 0x200000} /* BALLR */ +}; + +/*! Define a mask to access the entire hardware register. */ +static const unsigned int REG_FULLMASK = 0xffffff; + +/*! Reset value for the AUD_CODEC register. */ +static const unsigned int RESET_AUD_CODEC = 0x180027; + +/*! Reset value for the ST_DAC register. + * + * Note that we avoid resetting any of the arbitration bits. + */ +static const unsigned int RESET_ST_DAC = 0x0E0004; + +/*! Reset value for the SSI_NETWORK register. */ +static const unsigned int RESET_SSI_NETWORK = 0x013060; + +/*! Reset value for the AUDIO_TX register. + * + * Note that we avoid resetting any of the arbitration bits. + */ +static const unsigned int RESET_AUDIO_TX = 0x420000; + +/*! Reset value for the AUDIO_RX_0 register. */ +static const unsigned int RESET_AUDIO_RX_0 = 0x001000; + +/*! Reset value for the AUDIO_RX_1 register. */ +static const unsigned int RESET_AUDIO_RX_1 = 0x00D35A; + +/*! Reset mask for the SSI network Vcodec part. first 12 bits + * 0 - 11 */ +static const unsigned int REG_SSI_VCODEC_MASK = 0x000fff; + +/*! Reset mask for the SSI network STDAC part. last 12 bits + * 12 - 24 */ +static const unsigned int REG_SSI_STDAC_MASK = 0xfff000; + +/*! Constant NULL value for initializing/reseting the audio handles. */ +static const PMIC_AUDIO_HANDLE AUDIO_HANDLE_NULL = (PMIC_AUDIO_HANDLE) NULL; + +/*! + * @brief This structure maintains the current state of the Stereo DAC. + */ +typedef struct { + PMIC_AUDIO_HANDLE handle; /*!< Handle used to access + the Stereo DAC. */ + HANDLE_STATE handleState; /*!< Current handle state. */ + PMIC_AUDIO_DATA_BUS busID; /*!< Data bus used to access + the Stereo DAC. */ + bool protocol_set; + PMIC_AUDIO_BUS_PROTOCOL protocol; /*!< Data bus protocol. */ + PMIC_AUDIO_BUS_MODE masterSlave; /*!< Master/Slave mode + select. */ + PMIC_AUDIO_NUMSLOTS numSlots; /*!< Number of timeslots + used. */ + PMIC_AUDIO_CALLBACK callback; /*!< Event notification + callback function + pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification mask. */ + PMIC_AUDIO_CLOCK_IN_SOURCE clockIn; /*!< Stereo DAC clock input + source select. */ + PMIC_AUDIO_STDAC_SAMPLING_RATE samplingRate; /*!< Stereo DAC sampling rate + select. */ + PMIC_AUDIO_STDAC_CLOCK_IN_FREQ clockFreq; /*!< Stereo DAC clock input + frequency. */ + PMIC_AUDIO_CLOCK_INVERT invert; /*!< Stereo DAC clock signal + invert select. */ + PMIC_AUDIO_STDAC_TIMESLOTS timeslot; /*!< Stereo DAC data + timeslots select. */ + PMIC_AUDIO_STDAC_CONFIG config; /*!< Stereo DAC configuration + options. */ +} PMIC_AUDIO_STDAC_STATE; + +/*! + * @brief This variable maintains the current state of the Stereo DAC. + * + * This variable tracks the current state of the Stereo DAC audio hardware + * along with any information that is required by the device driver to + * manage the hardware (e.g., callback functions and event notification + * masks). + * + * The initial values represent the reset/power on state of the Stereo DAC. + */ +static PMIC_AUDIO_STDAC_STATE stDAC = { + (PMIC_AUDIO_HANDLE) NULL, /* handle */ + HANDLE_FREE, /* handleState */ + AUDIO_DATA_BUS_1, /* busID */ + false, + NORMAL_MSB_JUSTIFIED_MODE, /* protocol */ + BUS_MASTER_MODE, /* masterSlave */ + USE_2_TIMESLOTS, /* numSlots */ + (PMIC_AUDIO_CALLBACK) NULL, /* callback */ + (PMIC_AUDIO_EVENTS) NULL, /* eventMask */ + CLOCK_IN_CLIA, /* clockIn */ + STDAC_RATE_44_1_KHZ, /* samplingRate */ + STDAC_CLI_13MHZ, /* clockFreq */ + NO_INVERT, /* invert */ + USE_TS0_TS1, /* timeslot */ + (PMIC_AUDIO_STDAC_CONFIG) 0 /* config */ +}; + +/*! + * @brief This structure maintains the current state of the Voice CODEC. + */ +typedef struct { + PMIC_AUDIO_HANDLE handle; /*!< Handle used to access + the Voice CODEC. */ + HANDLE_STATE handleState; /*!< Current handle state. */ + PMIC_AUDIO_DATA_BUS busID; /*!< Data bus used to access + the Voice CODEC. */ + bool protocol_set; + PMIC_AUDIO_BUS_PROTOCOL protocol; /*!< Data bus protocol. */ + PMIC_AUDIO_BUS_MODE masterSlave; /*!< Master/Slave mode + select. */ + PMIC_AUDIO_NUMSLOTS numSlots; /*!< Number of timeslots + used. */ + PMIC_AUDIO_CALLBACK callback; /*!< Event notification + callback function + pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification + mask. */ + PMIC_AUDIO_CLOCK_IN_SOURCE clockIn; /*!< Voice CODEC clock input + source select. */ + PMIC_AUDIO_VCODEC_SAMPLING_RATE samplingRate; /*!< Voice CODEC sampling + rate select. */ + PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ clockFreq; /*!< Voice CODEC clock input + frequency. */ + PMIC_AUDIO_CLOCK_INVERT invert; /*!< Voice CODEC clock + signal invert select. */ + PMIC_AUDIO_VCODEC_TIMESLOT timeslot; /*!< Voice CODEC data + timeslot select. */ + PMIC_AUDIO_VCODEC_TIMESLOT secondaryTXtimeslot; + + PMIC_AUDIO_VCODEC_CONFIG config; /*!< Voice CODEC + configuration + options. */ + PMIC_MICROPHONE_STATE leftChannelMic; /*!< Left channel + microphone + configuration. */ + PMIC_MICROPHONE_STATE rightChannelMic; /*!< Right channel + microphone + configuration. */ +} PMIC_AUDIO_VCODEC_STATE; + +/*! + * @brief This variable maintains the current state of the Voice CODEC. + * + * This variable tracks the current state of the Voice CODEC audio hardware + * along with any information that is required by the device driver to + * manage the hardware (e.g., callback functions and event notification + * masks). + * + * The initial values represent the reset/power on state of the Voice CODEC. + */ +static PMIC_AUDIO_VCODEC_STATE vCodec = { + (PMIC_AUDIO_HANDLE) NULL, /* handle */ + HANDLE_FREE, /* handleState */ + AUDIO_DATA_BUS_2, /* busID */ + false, + NETWORK_MODE, /* protocol */ + BUS_SLAVE_MODE, /* masterSlave */ + USE_4_TIMESLOTS, /* numSlots */ + (PMIC_AUDIO_CALLBACK) NULL, /* callback */ + (PMIC_AUDIO_EVENTS) NULL, /* eventMask */ + CLOCK_IN_CLIB, /* clockIn */ + VCODEC_RATE_8_KHZ, /* samplingRate */ + VCODEC_CLI_13MHZ, /* clockFreq */ + NO_INVERT, /* invert */ + USE_TS0, /* timeslot pri */ + USE_TS2, /* timeslot sec TX */ + INPUT_HIGHPASS_FILTER | OUTPUT_HIGHPASS_FILTER, /* config */ + /* leftChannelMic */ + {NO_MIC, /* mic */ + MICROPHONE_OFF, /* micOnOff */ + AMP_OFF, /* ampMode */ + MIC_GAIN_0DB /* gain */ + }, + /* rightChannelMic */ + {NO_MIC, /* mic */ + MICROPHONE_OFF, /* micOnOff */ + AMP_OFF, /* ampMode */ + MIC_GAIN_0DB /* gain */ + } +}; + +/*! + * @brief This maintains the current state of the External Stereo Input. + */ +typedef struct { + PMIC_AUDIO_HANDLE handle; /*!< Handle used to access the + External Stereo Inputs. */ + HANDLE_STATE handleState; /*!< Current handle state. */ + PMIC_AUDIO_CALLBACK callback; /*!< Event notification callback + function pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification mask. */ + PMIC_AUDIO_STEREO_IN_GAIN inputGain; /*!< External Stereo Input + amplifier gain level. */ +} PMIC_AUDIO_EXT_STEREO_IN_STATE; + +/*! + * @brief This maintains the current state of the External Stereo Input. + * + * This variable tracks the current state of the External Stereo Input audio + * hardware along with any information that is required by the device driver + * to manage the hardware (e.g., callback functions and event notification + * masks). + * + * The initial values represent the reset/power on state of the External + * Stereo Input. + */ +static PMIC_AUDIO_EXT_STEREO_IN_STATE extStereoIn = { + (PMIC_AUDIO_HANDLE) NULL, /* handle */ + HANDLE_FREE, /* handleState */ + (PMIC_AUDIO_CALLBACK) NULL, /* callback */ + (PMIC_AUDIO_EVENTS) NULL, /* eventMask */ + STEREO_IN_GAIN_0DB /* inputGain */ +}; + +/*! + * @brief This maintains the current state of the callback & Eventmask. + */ +typedef struct { + PMIC_AUDIO_CALLBACK callback; /*!< Event notification callback + function pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification mask. */ +} PMIC_AUDIO_EVENT_STATE; + +static PMIC_AUDIO_EVENT_STATE event_state = { + (PMIC_AUDIO_CALLBACK) NULL, /*Callback */ + (PMIC_AUDIO_EVENTS) NULL, /* EventMask */ + +}; + +/*! + * @brief This maintains the current state of the Audio Output Section. + */ +typedef struct { + PMIC_AUDIO_OUTPUT_PORT outputPort; /*!< Current audio + output port. */ + PMIC_AUDIO_OUTPUT_PGA_GAIN vCodecoutputPGAGain; /*!< Output PGA gain + level codec */ + PMIC_AUDIO_OUTPUT_PGA_GAIN stDacoutputPGAGain; /*!< Output PGA gain + level stDAC */ + PMIC_AUDIO_OUTPUT_PGA_GAIN extStereooutputPGAGain; /*!< Output PGA gain + level stereo ext */ + PMIC_AUDIO_OUTPUT_BALANCE_GAIN balanceLeftGain; /*!< Left channel + balance gain + level. */ + PMIC_AUDIO_OUTPUT_BALANCE_GAIN balanceRightGain; /*!< Right channel + balance gain + level. */ + PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN monoAdderGain; /*!< Mono adder gain + level. */ + PMIC_AUDIO_OUTPUT_CONFIG config; /*!< Audio output + section config + options. */ + PMIC_AUDIO_VCODEC_OUTPUT_PATH vCodecOut; + +} PMIC_AUDIO_AUDIO_OUTPUT_STATE; + +/*! + * @brief This variable maintains the current state of the Audio Output Section. + * + * This variable tracks the current state of the Audio Output Section. + * + * The initial values represent the reset/power on state of the Audio + * Output Section. + */ +static PMIC_AUDIO_AUDIO_OUTPUT_STATE audioOutput = { + (PMIC_AUDIO_OUTPUT_PORT) NULL, /* outputPort */ + OUTPGA_GAIN_0DB, /* outputPGAGain */ + OUTPGA_GAIN_0DB, /* outputPGAGain */ + OUTPGA_GAIN_0DB, /* outputPGAGain */ + BAL_GAIN_0DB, /* balanceLeftGain */ + BAL_GAIN_0DB, /* balanceRightGain */ + MONOADD_GAIN_0DB, /* monoAdderGain */ + (PMIC_AUDIO_OUTPUT_CONFIG) 0, /* config */ + VCODEC_DIRECT_OUT +}; + +/*! The current headset status. */ +static HEADSET_STATUS headsetState = NO_HEADSET; + +/* Removed PTT variable */ +/*! Define a 1 ms wait interval that is needed to ensure that certain + * hardware operations are successfully completed. + */ +static const unsigned long delay_1ms = (HZ / 1000); + +/*! + * @brief This spinlock is used to provide mutual exclusion. + * + * Create a spinlock that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * that were defined above. Mutually exclusive access is required to + * ensure that the audio data structures are consistent at all times + * when possibly accessed by multiple threads of execution (for example, + * while simultaneously handling a user request and an interrupt event). + * + * We need to use a spinlock whenever we do need to provide mutual + * exclusion while possibly executing in a hardware interrupt context. + * Spinlocks should be held for the minimum time that is necessary + * because hardware interrupts are disabled while a spinlock is held. + * + */ + +static spinlock_t lock = SPIN_LOCK_UNLOCKED; +/*! + * @brief This mutex is used to provide mutual exclusion. + * + * Create a mutex that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * that were defined above. Mutually exclusive access is required to + * ensure that the audio data structures are consistent at all times + * when possibly accessed by multiple threads of execution. + * + * Note that we use a mutex instead of the spinlock whenever disabling + * interrupts while in the critical section is not required. This helps + * to minimize kernel interrupt handling latency. + */ +static DECLARE_MUTEX(mutex); + +/*! + * @brief Global variable to track currently active interrupt events. + * + * This global variable is used to keep track of all of the currently + * active interrupt events for the audio driver. Note that access to this + * variable may occur while within an interrupt context and, therefore, + * must be guarded by using a spinlock. + */ +/* static PMIC_CORE_EVENT eventID = 0; */ + +/* Prototypes for all static audio driver functions. */ +/* +static PMIC_STATUS pmic_audio_mic_boost_enable(void); +static PMIC_STATUS pmic_audio_mic_boost_disable(void);*/ +static PMIC_STATUS pmic_audio_close_handle(const PMIC_AUDIO_HANDLE handle); +static PMIC_STATUS pmic_audio_reset_device(const PMIC_AUDIO_HANDLE handle); + +static PMIC_STATUS pmic_audio_deregister(void *callback, + PMIC_AUDIO_EVENTS * const eventMask); + +/************************************************************************* + * Audio device access APIs. + ************************************************************************* + */ + +/*! + * @name General Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Audio + * hardware. + */ +/*@{*/ + +PMIC_STATUS pmic_audio_set_autodetect(int val) +{ + PMIC_STATUS status; + unsigned int reg_mask = 0, reg_write = 0; + reg_mask = SET_BITS(regAUDIO_RX_0, VAUDIOON, 1); + status = pmic_write_reg(REG_AUDIO_RX_0, reg_mask, reg_mask); + if (status != PMIC_SUCCESS) + return status; + reg_mask = 0; + if (val == 1) { + reg_write = SET_BITS(regAUDIO_RX_0, HSDETEN, 1) | + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + } else { + reg_write = 0; + } + reg_mask = + SET_BITS(regAUDIO_RX_0, HSDETEN, 1) | SET_BITS(regAUDIO_RX_0, + HSDETAUTOB, 1); + status = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + + return status; +} + +/*! + * @brief Request exclusive access to the PMIC Audio hardware. + * + * Attempt to open and gain exclusive access to a key PMIC audio hardware + * component (e.g., the Stereo DAC or the Voice CODEC). Depending upon the + * type of audio operation that is desired and the nature of the audio data + * stream, the Stereo DAC and/or the Voice CODEC will be a required hardware + * component and needs to be acquired by calling this function. + * + * If the open request is successful, then a numeric handle is returned + * and this handle must be used in all subsequent function calls to complete + * the configuration of either the Stereo DAC or the Voice CODEC and along + * with any other associated audio hardware components that will be needed. + * + * The same handle must also be used in the close call when use of the PMIC + * audio hardware is no longer required. + * + * The open request will fail if the requested audio hardware component has + * already been acquired by a previous open call but not yet closed. + * + * @param handle Device handle to be used for subsequent PMIC + * audio API calls. + * @param device The required PMIC audio hardware component. + * + * @retval PMIC_SUCCESS If the open request was successful + * @retval PMIC_PARAMETER_ERROR If the handle argument is NULL. + * @retval PMIC_ERROR If the audio hardware component is + * unavailable. + */ +PMIC_STATUS pmic_audio_open(PMIC_AUDIO_HANDLE * const handle, + const PMIC_AUDIO_SOURCE device) +{ + PMIC_STATUS rc = PMIC_ERROR; + + if (handle == (PMIC_AUDIO_HANDLE *) NULL) { + /* Do not dereference a NULL pointer. */ + return PMIC_PARAMETER_ERROR; + } + + /* We only need to acquire a mutex here because the interrupt handler + * never modifies the device handle or device handle state. Therefore, + * we don't need to worry about conflicts with the interrupt handler + * or the need to execute in an interrupt context. + * + * But we do need a critical section here to avoid problems in case + * multiple calls to pmic_audio_open() are made since we can only allow + * one of them to succeed. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Check the current device handle state and acquire the handle if + * it is available. + */ + + if ((device == STEREO_DAC) && (stDAC.handleState == HANDLE_FREE)) { + stDAC.handle = (PMIC_AUDIO_HANDLE) (&stDAC); + stDAC.handleState = HANDLE_IN_USE; + *handle = stDAC.handle; + rc = PMIC_SUCCESS; + } else if ((device == VOICE_CODEC) + && (vCodec.handleState == HANDLE_FREE)) { + vCodec.handle = (PMIC_AUDIO_HANDLE) (&vCodec); + vCodec.handleState = HANDLE_IN_USE; + *handle = vCodec.handle; + rc = PMIC_SUCCESS; + } else if ((device == EXTERNAL_STEREO_IN) && + (extStereoIn.handleState == HANDLE_FREE)) { + extStereoIn.handle = (PMIC_AUDIO_HANDLE) (&extStereoIn); + extStereoIn.handleState = HANDLE_IN_USE; + *handle = extStereoIn.handle; + rc = PMIC_SUCCESS; + } else { + *handle = AUDIO_HANDLE_NULL; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Terminate further access to the PMIC audio hardware. + * + * Terminate further access to the PMIC audio hardware that was previously + * acquired by calling pmic_audio_open(). This now allows another thread to + * successfully call pmic_audio_open() to gain access. + * + * Note that we will shutdown/reset the Voice CODEC or Stereo DAC as well as + * any associated audio input/output components that are no longer required. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the close request was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_close(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* We need a critical section here to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* We can now call pmic_audio_close_handle() to actually do the work. */ + rc = pmic_audio_close_handle(handle); + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Configure the data bus protocol to be used. + * + * Provide the parameters needed to properly configure the audio data bus + * protocol so that data can be read/written to either the Stereo DAC or + * the Voice CODEC. + * + * @param handle Device handle from pmic_audio_open() call. + * @param busID Select data bus to be used. + * @param protocol Select the data bus protocol. + * @param masterSlave Select the data bus timing mode. + * @param numSlots Define the number of timeslots (only if in + * master mode). + * + * @retval PMIC_SUCCESS If the protocol was successful configured. + * @retval PMIC_PARAMETER_ERROR If the handle or the protocol parameters + * are invalid. + */ +PMIC_STATUS pmic_audio_set_protocol(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_DATA_BUS busID, + const PMIC_AUDIO_BUS_PROTOCOL protocol, + const PMIC_AUDIO_BUS_MODE masterSlave, + const PMIC_AUDIO_NUMSLOTS numSlots) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int ST_DAC_MASK = SET_BITS(regST_DAC, STDCSSISEL, 1) | + SET_BITS(regST_DAC, STDCFS, 3) | SET_BITS(regST_DAC, STDCSM, 1); + + unsigned int reg_mask; + /*unsigned int VCODEC_MASK = SET_BITS(regAUD_CODEC, CDCSSISEL, 1) | + SET_BITS(regAUD_CODEC, CDCFS, 3) | SET_BITS(regAUD_CODEC, CDCSM, 1); */ + + unsigned int SSI_NW_MASK = SET_BITS(regSSI_NETWORK, STDCSLOTS, 1); + unsigned int reg_value = 0; + unsigned int ssi_nw_value = 0; + + /* Enter a critical section so that we can ensure only one + * state change request is completed at a time. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (handle == (PMIC_AUDIO_HANDLE) NULL) { + rc = PMIC_PARAMETER_ERROR; + } else { + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + if ((stDAC.handleState == HANDLE_IN_USE) && + (stDAC.busID == busID) && (stDAC.protocol_set)) { + pr_debug("The requested bus already in USE\n"); + rc = PMIC_PARAMETER_ERROR; + } else if ((masterSlave == BUS_MASTER_MODE) + && (numSlots != USE_4_TIMESLOTS)) { + pr_debug + ("mc13783 supports only 4 slots in Master mode\n"); + rc = PMIC_NOT_SUPPORTED; + } else if ((masterSlave == BUS_SLAVE_MODE) + && (numSlots != USE_4_TIMESLOTS)) { + pr_debug + ("Driver currently supports only 4 slots in Slave mode\n"); + rc = PMIC_NOT_SUPPORTED; + } else if (!((protocol == NETWORK_MODE) || + (protocol == I2S_MODE))) { + pr_debug + ("mc13783 Voice codec works only in Network and I2S modes\n"); + rc = PMIC_NOT_SUPPORTED; + } else { + pr_debug + ("Proceeding to configure Voice Codec\n"); + if (busID == AUDIO_DATA_BUS_1) { + reg_value = + SET_BITS(regAUD_CODEC, CDCSSISEL, + 0); + } else { + reg_value = + SET_BITS(regAUD_CODEC, CDCSSISEL, + 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCSSISEL, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + if (masterSlave == BUS_MASTER_MODE) { + reg_value = + SET_BITS(regAUD_CODEC, CDCSM, 0); + } else { + reg_value = + SET_BITS(regAUD_CODEC, CDCSM, 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCSM, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + if (protocol == NETWORK_MODE) { + reg_value = + SET_BITS(regAUD_CODEC, CDCFS, 1); + } else { /* protocol == I2S, other options have been already eliminated */ + reg_value = + SET_BITS(regAUD_CODEC, CDCFS, 2); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCFS, 3); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + ssi_nw_value = + SET_BITS(regSSI_NETWORK, CDCFSDLY, 1); + /*if (pmic_write_reg + (REG_AUDIO_CODEC, reg_value, + VCODEC_MASK) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { */ + vCodec.busID = busID; + vCodec.protocol = protocol; + vCodec.masterSlave = masterSlave; + vCodec.numSlots = numSlots; + vCodec.protocol_set = true; + //pmic_write_reg(REG_AUDIO_SSI_NETWORK, ssi_nw_value, ssi_nw_value); + + pr_debug + ("mc13783 Voice codec successfully configured\n"); + rc = PMIC_SUCCESS; + //} + + } + + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + if ((vCodec.handleState == HANDLE_IN_USE) && + (vCodec.busID == busID) && (vCodec.protocol_set)) { + pr_debug("The requested bus already in USE\n"); + rc = PMIC_PARAMETER_ERROR; + } else if (((protocol == NORMAL_MSB_JUSTIFIED_MODE) || + (protocol == I2S_MODE)) + && (numSlots != USE_2_TIMESLOTS)) { + pr_debug + ("STDAC uses only 2 slots in Normal and I2S modes\n"); + rc = PMIC_PARAMETER_ERROR; + } else if ((protocol == NETWORK_MODE) && + !((numSlots == USE_2_TIMESLOTS) || + (numSlots == USE_4_TIMESLOTS) || + (numSlots == USE_8_TIMESLOTS))) { + pr_debug + ("STDAC uses only 2,4 or 8 slots in Network mode\n"); + rc = PMIC_PARAMETER_ERROR; + } else if (protocol == SPD_IF_MODE) { + pr_debug + ("STDAC driver currently does not support SPD IF mode\n"); + rc = PMIC_NOT_SUPPORTED; + } else { + pr_debug + ("Proceeding to configure Stereo DAC\n"); + if (busID == AUDIO_DATA_BUS_1) { + reg_value = + SET_BITS(regST_DAC, STDCSSISEL, 0); + } else { + reg_value = + SET_BITS(regST_DAC, STDCSSISEL, 1); + } + if (masterSlave == BUS_MASTER_MODE) { + reg_value |= + SET_BITS(regST_DAC, STDCSM, 0); + } else { + reg_value |= + SET_BITS(regST_DAC, STDCSM, 1); + } + if (protocol == NETWORK_MODE) { + reg_value |= + SET_BITS(regST_DAC, STDCFS, 1); + } else if (protocol == + NORMAL_MSB_JUSTIFIED_MODE) { + reg_value |= + SET_BITS(regST_DAC, STDCFS, 0); + } else { /* I2S mode as the other option has already been eliminated */ + reg_value |= + SET_BITS(regST_DAC, STDCFS, 2); + } + + if (pmic_write_reg + (REG_AUDIO_STEREO_DAC, + reg_value, ST_DAC_MASK) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + if (numSlots == USE_2_TIMESLOTS) { + reg_value = + SET_BITS(regSSI_NETWORK, + STDCSLOTS, 3); + } else if (numSlots == USE_4_TIMESLOTS) { + reg_value = + SET_BITS(regSSI_NETWORK, + STDCSLOTS, 2); + } else { /* Use 8 timeslots - L , R and 6 other */ + reg_value = + SET_BITS(regSSI_NETWORK, + STDCSLOTS, 1); + } + if (pmic_write_reg + (REG_AUDIO_SSI_NETWORK, + reg_value, + SSI_NW_MASK) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + stDAC.busID = busID; + stDAC.protocol = protocol; + stDAC.protocol_set = true; + stDAC.masterSlave = masterSlave; + stDAC.numSlots = numSlots; + pr_debug + ("mc13783 Stereo DAC successfully configured\n"); + rc = PMIC_SUCCESS; + } + } + + } + } else { + rc = PMIC_PARAMETER_ERROR; + /* Handle can only be Voice Codec or Stereo DAC */ + pr_debug("Handles only STDAC and VCODEC\n"); + } + + } + /* Exit critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Retrieve the current data bus protocol configuration. + * + * Retrieve the parameters that define the current audio data bus protocol. + * + * @param handle Device handle from pmic_audio_open() call. + * @param busID The data bus being used. + * @param protocol The data bus protocol being used. + * @param masterSlave The data bus timing mode being used. + * @param numSlots The number of timeslots being used (if in + * master mode). + * + * @retval PMIC_SUCCESS If the protocol was successful retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_get_protocol(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_DATA_BUS * const busID, + PMIC_AUDIO_BUS_PROTOCOL * const protocol, + PMIC_AUDIO_BUS_MODE * const masterSlave, + PMIC_AUDIO_NUMSLOTS * const numSlots) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + if ((busID != (PMIC_AUDIO_DATA_BUS *) NULL) && + (protocol != (PMIC_AUDIO_BUS_PROTOCOL *) NULL) && + (masterSlave != (PMIC_AUDIO_BUS_MODE *) NULL) && + (numSlots != (PMIC_AUDIO_NUMSLOTS *) NULL)) { + /* Enter a critical section so that we return a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + *busID = stDAC.busID; + *protocol = stDAC.protocol; + *masterSlave = stDAC.masterSlave; + *numSlots = stDAC.numSlots; + rc = PMIC_SUCCESS; + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + *busID = vCodec.busID; + *protocol = vCodec.protocol; + *masterSlave = vCodec.masterSlave; + *numSlots = vCodec.numSlots; + rc = PMIC_SUCCESS; + } + + /* Exit critical section. */ + up(&mutex); + } + + return rc; +} + +/*! + * @brief Enable the Stereo DAC or the Voice CODEC. + * + * Explicitly enable the Stereo DAC or the Voice CODEC to begin audio + * playback or recording as required. This should only be done after + * successfully configuring all of the associated audio components (e.g., + * microphones, amplifiers, etc.). + * + * Note that the timed delays used in this function are necessary to + * ensure reliable operation of the Voice CODEC and Stereo DAC. The + * Stereo DAC seems to be particularly sensitive and it has been observed + * to fail to generate the required master mode clock signals if it is + * not allowed enough time to initialize properly. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the device was successful enabled. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the device could not be enabled. + */ +PMIC_STATUS pmic_audio_enable(const PMIC_AUDIO_HANDLE handle) +{ + const unsigned int AUDIO_BIAS_ENABLE = SET_BITS(regAUDIO_RX_0, + VAUDIOON, 1); + const unsigned int STDAC_ENABLE = SET_BITS(regST_DAC, STDCEN, 1); + const unsigned int VCODEC_ENABLE = SET_BITS(regAUD_CODEC, CDCEN, 1); + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + pmic_write_reg(REG_AUDIO_RX_0, AUDIO_BIAS_ENABLE, + AUDIO_BIAS_ENABLE); + reg_mask = + SET_BITS(regAUDIO_RX_0, HSDETEN, + 1) | SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + reg_write = + SET_BITS(regAUDIO_RX_0, HSDETEN, + 1) | SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + if (rc == PMIC_SUCCESS) + pr_debug("pmic_audio_enable\n"); + /* We can enable the Stereo DAC. */ + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, + STDAC_ENABLE, STDAC_ENABLE); + /*pmic_read_reg(REG_AUDIO_STEREO_DAC, ®_value); */ + if (rc != PMIC_SUCCESS) { + pr_debug("Failed to enable the Stereo DAC\n"); + rc = PMIC_ERROR; + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE)) { + /* Must first set the audio bias bit to power up the audio circuits. */ + pmic_write_reg(REG_AUDIO_RX_0, AUDIO_BIAS_ENABLE, + AUDIO_BIAS_ENABLE); + reg_mask = SET_BITS(regAUDIO_RX_0, HSDETEN, 1) | + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + reg_write = SET_BITS(regAUDIO_RX_0, HSDETEN, 1) | + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + + /* Then we can enable the Voice CODEC. */ + rc = pmic_write_reg(REG_AUDIO_CODEC, VCODEC_ENABLE, + VCODEC_ENABLE); + + /* pmic_read_reg(REG_AUDIO_CODEC, ®_value); */ + if (rc != PMIC_SUCCESS) { + pr_debug("Failed to enable the Voice codec\n"); + rc = PMIC_ERROR; + } + } + /* Exit critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Disable the Stereo DAC or the Voice CODEC. + * + * Explicitly disable the Stereo DAC or the Voice CODEC to end audio + * playback or recording as required. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the device was successful disabled. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the device could not be disabled. + */ +PMIC_STATUS pmic_audio_disable(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int STDAC_DISABLE = SET_BITS(regST_DAC, STDCEN, 1); + const unsigned int VCODEC_DISABLE = SET_BITS(regAUD_CODEC, CDCEN, 1); + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, 0, STDAC_DISABLE); + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_CODEC, 0, VCODEC_DISABLE); + } + if (rc == PMIC_SUCCESS) { + pr_debug("Disabled successfully\n"); + } + /* Exit critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Reset the selected audio hardware control registers to their + * power on state. + * + * This resets all of the audio hardware control registers currently + * associated with the device handle back to their power on states. For + * example, if the handle is associated with the Stereo DAC and a + * specific output port and output amplifiers, then this function will + * reset all of those components to their initial power on state. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the reset operation was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the reset was unsuccessful. + */ +PMIC_STATUS pmic_audio_reset(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + rc = pmic_audio_reset_device(handle); + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Reset all audio hardware control registers to their power on state. + * + * This resets all of the audio hardware control registers back to their + * power on states. Use this function with care since it also invalidates + * (i.e., automatically closes) all currently opened device handles. + * + * @retval PMIC_SUCCESS If the reset operation was successful. + * @retval PMIC_ERROR If the reset was unsuccessful. + */ +PMIC_STATUS pmic_audio_reset_all(void) +{ + PMIC_STATUS rc = PMIC_SUCCESS; + unsigned int audio_ssi_reset = 0; + unsigned int audio_rx1_reset = 0; + /* We need a critical section here to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* First close all opened device handles, also deregisters callbacks. */ + pmic_audio_close_handle(stDAC.handle); + pmic_audio_close_handle(vCodec.handle); + pmic_audio_close_handle(extStereoIn.handle); + + if (pmic_write_reg(REG_AUDIO_RX_1, RESET_AUDIO_RX_1, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + audio_rx1_reset = 1; + } + if (pmic_write_reg(REG_AUDIO_SSI_NETWORK, RESET_SSI_NETWORK, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + audio_ssi_reset = 1; + } + if (pmic_write_reg + (REG_AUDIO_STEREO_DAC, RESET_ST_DAC, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + if (audio_ssi_reset) { + /* better to check if SSI is also reset as some fields are represennted in SSI reg */ + stDAC.busID = AUDIO_DATA_BUS_1; + stDAC.protocol = NORMAL_MSB_JUSTIFIED_MODE; + stDAC.masterSlave = BUS_MASTER_MODE; + stDAC.protocol_set = false; + stDAC.numSlots = USE_2_TIMESLOTS; + stDAC.clockIn = CLOCK_IN_CLIA; + stDAC.samplingRate = STDAC_RATE_44_1_KHZ; + stDAC.clockFreq = STDAC_CLI_13MHZ; + stDAC.invert = NO_INVERT; + stDAC.timeslot = USE_TS0_TS1; + stDAC.config = (PMIC_AUDIO_STDAC_CONFIG) 0; + } + } + + if (pmic_write_reg(REG_AUDIO_CODEC, RESET_AUD_CODEC, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + if (audio_ssi_reset) { + vCodec.busID = AUDIO_DATA_BUS_2; + vCodec.protocol = NETWORK_MODE; + vCodec.masterSlave = BUS_SLAVE_MODE; + vCodec.protocol_set = false; + vCodec.numSlots = USE_4_TIMESLOTS; + vCodec.clockIn = CLOCK_IN_CLIB; + vCodec.samplingRate = VCODEC_RATE_8_KHZ; + vCodec.clockFreq = VCODEC_CLI_13MHZ; + vCodec.invert = NO_INVERT; + vCodec.timeslot = USE_TS0; + vCodec.config = + INPUT_HIGHPASS_FILTER | OUTPUT_HIGHPASS_FILTER; + } + } + + if (pmic_write_reg(REG_AUDIO_RX_0, RESET_AUDIO_RX_0, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. */ + audioOutput.outputPort = (PMIC_AUDIO_OUTPUT_PORT) NULL; + audioOutput.vCodecoutputPGAGain = OUTPGA_GAIN_0DB; + audioOutput.stDacoutputPGAGain = OUTPGA_GAIN_0DB; + audioOutput.extStereooutputPGAGain = OUTPGA_GAIN_0DB; + audioOutput.balanceLeftGain = BAL_GAIN_0DB; + audioOutput.balanceRightGain = BAL_GAIN_0DB; + audioOutput.monoAdderGain = MONOADD_GAIN_0DB; + audioOutput.config = (PMIC_AUDIO_OUTPUT_CONFIG) 0; + audioOutput.vCodecOut = VCODEC_DIRECT_OUT; + } + + if (pmic_write_reg(REG_AUDIO_TX, RESET_AUDIO_TX, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. Note that we + * reset the vCodec fields since all of the input/recording + * devices are only connected to the Voice CODEC and are managed + * as part of the Voice CODEC state. + */ + if (audio_rx1_reset) { + vCodec.leftChannelMic.mic = NO_MIC; + vCodec.leftChannelMic.micOnOff = MICROPHONE_OFF; + vCodec.leftChannelMic.ampMode = CURRENT_TO_VOLTAGE; + vCodec.leftChannelMic.gain = MIC_GAIN_0DB; + vCodec.rightChannelMic.mic = NO_MIC; + vCodec.rightChannelMic.micOnOff = MICROPHONE_OFF; + vCodec.rightChannelMic.ampMode = AMP_OFF; + vCodec.rightChannelMic.gain = MIC_GAIN_0DB; + } + } + /* Finally, also reset any global state variables. */ + headsetState = NO_HEADSET; + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Set the Audio callback function. + * + * Register a callback function that will be used to signal PMIC audio + * events. For example, the OSS audio driver should register a callback + * function in order to be notified of headset connect/disconnect events. + * + * @param func A pointer to the callback function. + * @param eventMask A mask selecting events to be notified. + * @param hs_state To know the headset state. + * + * + * + * @retval PMIC_SUCCESS If the callback was successfully + * registered. + * @retval PMIC_PARAMETER_ERROR If the handle or the eventMask is invalid. + */ +PMIC_STATUS pmic_audio_set_callback(void *func, + const PMIC_AUDIO_EVENTS eventMask, + PMIC_HS_STATE * hs_state) +{ + unsigned long flags; + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + pmic_event_callback_t eventNotify; + + /* We need to start a critical section here to ensure a consistent state + * in case simultaneous calls to pmic_audio_set_callback() are made. In + * that case, we must serialize the calls to ensure that the "callback" + * and "eventMask" state variables are always consistent. + * + * Note that we don't actually need to acquire the spinlock until later + * when we are finally ready to update the "callback" and "eventMask" + * state variables which are shared with the interrupt handler. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + rc = PMIC_ERROR; + /* Register for PMIC events from the core protocol driver. */ + if (eventMask & MICROPHONE_DETECTED) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_MC2BI); + rc = pmic_event_subscribe(EVENT_MC2BI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_HSDETI " + "failed\n", __FILE__); + goto End; + } + } + + if (eventMask & HEADSET_DETECTED) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSDETI); + rc = pmic_event_subscribe(EVENT_HSDETI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_HSDETI " + "failed\n", __FILE__); + goto Cleanup_HDT; + } + + } + if (eventMask & HEADSET_STEREO) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSLI); + rc = pmic_event_subscribe(EVENT_HSLI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_HSLI " + "failed\n", __FILE__); + goto Cleanup_HST; + } + } + if (eventMask & HEADSET_THERMAL_SHUTDOWN) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_ALSPTHI); + rc = pmic_event_subscribe(EVENT_ALSPTHI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_ALSPTHI " + "failed\n", __FILE__); + goto Cleanup_TSD; + } + pr_debug("Registered for EVENT_ALSPTHI\n"); + } + if (eventMask & HEADSET_SHORT_CIRCUIT) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_AHSSHORTI); + rc = pmic_event_subscribe(EVENT_AHSSHORTI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_AHSSHORTI " + "failed\n", __FILE__); + goto Cleanup_HShort; + } + pr_debug("Registered for EVENT_AHSSHORTI\n"); + } + + /* We also need the spinlock here to avoid possible problems + * with the interrupt handler when we update the + * "callback" and "eventMask" state variables. + */ + spin_lock_irqsave(&lock, flags); + + /* Successfully registered for all events. */ + event_state.callback = func; + event_state.eventMask = eventMask; + + /* The spinlock is no longer needed now that we've finished + * updating the "callback" and "eventMask" state variables. + */ + spin_unlock_irqrestore(&lock, flags); + + goto End; + + /* This section unregisters any already registered events if we should + * encounter an error partway through the registration process. Note + * that we don't check the return status here since it is already set + * to PMIC_ERROR before we get here. + */ + Cleanup_HShort: + + if (eventMask & HEADSET_SHORT_CIRCUIT) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_AHSSHORTI); + pmic_event_unsubscribe(EVENT_AHSSHORTI, eventNotify); + } + + Cleanup_TSD: + + if (eventMask & HEADSET_THERMAL_SHUTDOWN) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_ALSPTHI); + pmic_event_unsubscribe(EVENT_ALSPTHI, eventNotify); + } + + Cleanup_HST: + + if (eventMask & HEADSET_STEREO) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSLI); + pmic_event_unsubscribe(EVENT_HSLI, eventNotify); + } + + Cleanup_HDT: + + if (eventMask & HEADSET_DETECTED) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSDETI); + pmic_event_unsubscribe(EVENT_HSDETI, eventNotify); + } + + End: + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Deregisters the existing audio callback function. + * + * Deregister the callback function that was previously registered by calling + * pmic_audio_set_callback(). + * + * + * @retval PMIC_SUCCESS If the callback was successfully + * deregistered. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_clear_callback(void) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* We need a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (event_state.callback != (PMIC_AUDIO_CALLBACK) NULL) { + rc = pmic_audio_deregister(&(event_state.callback), + &(event_state.eventMask)); + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Get the current audio callback function settings. + * + * Get the current callback function and event mask. + * + * @param func The current callback function. + * @param eventMask The current event selection mask. + * + * @retval PMIC_SUCCESS If the callback information was + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_get_callback(PMIC_AUDIO_CALLBACK * const func, + PMIC_AUDIO_EVENTS * const eventMask) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* We only need to acquire the mutex here because we will not be updating + * anything that may affect the interrupt handler. We just need to ensure + * that the callback fields are not changed while we are in the critical + * section by calling either pmic_audio_set_callback() or + * pmic_audio_clear_callback(). + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((func != (PMIC_AUDIO_CALLBACK *) NULL) && + (eventMask != (PMIC_AUDIO_EVENTS *) NULL)) { + + *func = event_state.callback; + *eventMask = event_state.eventMask; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Enable the anti-pop circuitry to avoid extra noise when inserting + * or removing a external device (e.g., a headset). + * + * Enable the use of the built-in anti-pop circuitry to prevent noise from + * being generated when an external audio device is inserted or removed + * from an audio plug. A slow ramp speed may be needed to avoid extra noise. + * + * @param rampSpeed The desired anti-pop circuitry ramp speed. + * + * @retval PMIC_SUCCESS If the anti-pop circuitry was successfully + * enabled. + * @retval PMIC_ERROR If the anti-pop circuitry could not be + * enabled. + */ +PMIC_STATUS pmic_audio_antipop_enable(const PMIC_AUDIO_ANTI_POP_RAMP_SPEED + rampSpeed) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, BIASEN, 1) | + SET_BITS(regAUDIO_RX_0, BIASSPEED, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + /* + * Antipop is enabled by enabling the BIAS (BIASEN) and setting the + * BIASSPEED . + * BIASEN is just to make sure that BIAS is enabled + */ + reg_value = SET_BITS(regAUDIO_RX_0, BIASEN, 1) + | SET_BITS(regAUDIO_RX_0, BIASSPEED, 0) | SET_BITS(regAUDIO_RX_0, + HSLDETEN, 1); + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_value, reg_mask); + return rc; +} + +/*! + * @brief Disable the anti-pop circuitry. + * + * Disable the use of the built-in anti-pop circuitry to prevent noise from + * being generated when an external audio device is inserted or removed + * from an audio plug. + * + * @retval PMIC_SUCCESS If the anti-pop circuitry was successfully + * disabled. + * @retval PMIC_ERROR If the anti-pop circuitry could not be + * disabled. + */ +PMIC_STATUS pmic_audio_antipop_disable(void) +{ + PMIC_STATUS rc = PMIC_ERROR; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, BIASSPEED, 1) | + SET_BITS(regAUDIO_RX_0, BIASEN, 1); + const unsigned int reg_write = SET_BITS(regAUDIO_RX_0, BIASSPEED, 1) | + SET_BITS(regAUDIO_RX_0, BIASEN, 0); + + /* No critical section required here since we are not updating any + * global data. + */ + + /* + * Antipop is disabled by setting BIASSPEED = 0. BIASEN bit remains set + * as only antipop needs to be disabled + */ + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + + return rc; +} + +/*! + * @brief Performs a reset of the Voice CODEC/Stereo DAC digital filter. + * + * The digital filter should be reset whenever the clock or sampling rate + * configuration has been changed. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the digital filter was successfully + * reset. + * @retval PMIC_ERROR If the digital filter could not be reset. + */ +PMIC_STATUS pmic_audio_digital_filter_reset(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regST_DAC, STDCRESET, 1); + if (pmic_write_reg(REG_AUDIO_STEREO_DAC, reg_mask, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("STDAC filter reset\n"); + } + + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUD_CODEC, CDCRESET, 1); + if (pmic_write_reg(REG_AUDIO_CODEC, reg_mask, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("CODEC filter reset\n"); + } + } + return rc; +} + +/*! + * @brief Get the most recent PTT button voltage reading. + * + * This feature is not supported by mc13783 + * @param level PTT button level. + * + * @retval PMIC_SUCCESS If the most recent PTT button voltage was + * returned. + * @retval PMIC_PARAMETER_ERROR If a NULL pointer argument was given. + */ +PMIC_STATUS pmic_audio_get_ptt_button_level(unsigned int *const level) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +#ifdef DEBUG_AUDIO + +/*! + * @brief Provide a hexadecimal dump of all PMIC audio registers (DEBUG only) + * + * This function is intended strictly for debugging purposes only and will + * print the current values of the following PMIC registers: + * + * - AUD_CODEC + * - ST_DAC + * - AUDIO_RX_0 + * - AUDIO_RX_1 + * - AUDIO_TX + * - AUDIO_SSI_NW + * + * The register fields will not be decoded. + * + * Note that we don't dump any of the arbitration bits because we cannot + * access the true arbitration bit settings when reading the registers + * from the secondary SPI bus. + * + * Also note that we must not call this function with interrupts disabled, + * for example, while holding a spinlock, because calls to pmic_read_reg() + * eventually end up in the SPI driver which will want to perform a + * schedule() operation. If schedule() is called with interrupts disabled, + * then you will see messages like the following: + * + * BUG: scheduling while atomic: ... + * + */ +void pmic_audio_dump_registers(void) +{ + unsigned int reg_value = 0; + + /* Dump the AUD_CODEC (Voice CODEC) register. */ + if (pmic_read_reg(REG_AUDIO_CODEC, ®_value, REG_FULLMASK) + == PMIC_SUCCESS) { + pr_debug("Audio Codec = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio codec\n"); + } + + /* Dump the ST DAC (Stereo DAC) register. */ + if (pmic_read_reg + (REG_AUDIO_STEREO_DAC, ®_value, REG_FULLMASK) == PMIC_SUCCESS) { + pr_debug("Stereo DAC = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read Stereo DAC\n"); + } + + /* Dump the SSI NW register. */ + if (pmic_read_reg + (REG_AUDIO_SSI_NETWORK, ®_value, REG_FULLMASK) == PMIC_SUCCESS) { + pr_debug("SSI Network = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read SSI network\n"); + } + + /* Dump the Audio RX 0 register. */ + if (pmic_read_reg(REG_AUDIO_RX_0, ®_value, REG_FULLMASK) + == PMIC_SUCCESS) { + pr_debug("Audio RX 0 = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio RX 0\n"); + } + + /* Dump the Audio RX 1 register. */ + if (pmic_read_reg(REG_AUDIO_RX_1, ®_value, REG_FULLMASK) + == PMIC_SUCCESS) { + pr_debug("Audio RX 1 = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio RX 1\n"); + } + /* Dump the Audio TX register. */ + if (pmic_read_reg(REG_AUDIO_TX, ®_value, REG_FULLMASK) == + PMIC_SUCCESS) { + pr_debug("Audio Tx = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio TX\n"); + } + +} + +#endif /* DEBUG_AUDIO */ + +/*@}*/ + +/************************************************************************* + * General Voice CODEC configuration. + ************************************************************************* + */ + +/*! + * @name General Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice + * CODEC hardware. + */ +/*@{*/ + +/*! + * @brief Set the Voice CODEC clock source and operating characteristics. + * + * Define the Voice CODEC clock source and operating characteristics. This + * must be done before the Voice CODEC is enabled. + * + * + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn Select the clock signal source. + * @param clockFreq Select the clock signal frequency. + * @param samplingRate Select the audio data sampling rate. + * @param invert Enable inversion of the frame sync and/or + * bit clock inputs. + * + * @retval PMIC_SUCCESS If the Voice CODEC clock settings were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or clock configuration was + * invalid. + * @retval PMIC_ERROR If the Voice CODEC clock configuration + * could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_clock(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_CLOCK_IN_SOURCE + clockIn, + const PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ + clockFreq, + const PMIC_AUDIO_VCODEC_SAMPLING_RATE + samplingRate, + const PMIC_AUDIO_CLOCK_INVERT invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Validate all of the calling parameters. */ + if (handle == (PMIC_AUDIO_HANDLE) NULL) { + rc = PMIC_PARAMETER_ERROR; + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + if ((clockIn != CLOCK_IN_CLIA) && (clockIn != CLOCK_IN_CLIB)) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((clockFreq >= VCODEC_CLI_13MHZ) + && (clockFreq <= VCODEC_CLI_33_6MHZ))) { + rc = PMIC_PARAMETER_ERROR; + } else if ((samplingRate != VCODEC_RATE_8_KHZ) + && (samplingRate != VCODEC_RATE_16_KHZ)) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((invert >= NO_INVERT) + && (invert <= INVERT_FRAMESYNC))) { + rc = PMIC_PARAMETER_ERROR; + } else { + /*reg_mask = SET_BITS(regAUD_CODEC, CDCCLK, 7) | + SET_BITS(regAUD_CODEC, CDCCLKSEL, 1) | + SET_BITS(regAUD_CODEC, CDCFS8K16K, 1) | + SET_BITS(regAUD_CODEC, CDCBCLINV, 1) | + SET_BITS(regAUD_CODEC, CDCFSINV, 1); */ + if (clockIn == CLOCK_IN_CLIA) { + reg_value = + SET_BITS(regAUD_CODEC, CDCCLKSEL, 0); + } else { + reg_value = + SET_BITS(regAUD_CODEC, CDCCLKSEL, 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCCLKSEL, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + reg_value = 0; + if (clockFreq == VCODEC_CLI_13MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 0); + } else if (clockFreq == VCODEC_CLI_15_36MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 1); + } else if (clockFreq == VCODEC_CLI_16_8MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 2); + } else if (clockFreq == VCODEC_CLI_26MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 4); + } else { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 7); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCCLK, 7); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + reg_value = 0; + reg_mask = 0; + + if (samplingRate == VCODEC_RATE_8_KHZ) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCFS8K16K, 0); + } else { + reg_value |= + SET_BITS(regAUD_CODEC, CDCFS8K16K, 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCFS8K16K, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + reg_value = 0; + reg_mask = + SET_BITS(regAUD_CODEC, CDCBCLINV, + 1) | SET_BITS(regAUD_CODEC, CDCFSINV, 1); + + if (invert & INVERT_BITCLOCK) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCBCLINV, 1); + } + if (invert & INVERT_FRAMESYNC) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCFSINV, 1); + } + if (invert & NO_INVERT) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCBCLINV, 0); + reg_value |= + SET_BITS(regAUD_CODEC, CDCFSINV, 0); + } + if (pmic_write_reg + (REG_AUDIO_CODEC, reg_value, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("CODEC clock set\n"); + vCodec.clockIn = clockIn; + vCodec.clockFreq = clockFreq; + vCodec.samplingRate = samplingRate; + vCodec.invert = invert; + } + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the Voice CODEC clock source and operating characteristics. + * + * Get the current Voice CODEC clock source and operating characteristics. + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn The clock signal source. + * @param clockFreq The clock signal frequency. + * @param samplingRate The audio data sampling rate. + * @param invert Inversion of the frame sync and/or + * bit clock inputs is enabled/disabled. + * + * @retval PMIC_SUCCESS If the Voice CODEC clock settings were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle invalid. + * @retval PMIC_ERROR If the Voice CODEC clock configuration + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_clock(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_CLOCK_IN_SOURCE * + const clockIn, + PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ * + const clockFreq, + PMIC_AUDIO_VCODEC_SAMPLING_RATE * + const samplingRate, + PMIC_AUDIO_CLOCK_INVERT * const invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure that we return a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (clockIn != (PMIC_AUDIO_CLOCK_IN_SOURCE *) NULL) && + (clockFreq != (PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ *) NULL) && + (samplingRate != (PMIC_AUDIO_VCODEC_SAMPLING_RATE *) NULL) && + (invert != (PMIC_AUDIO_CLOCK_INVERT *) NULL)) { + *clockIn = vCodec.clockIn; + *clockFreq = vCodec.clockFreq; + *samplingRate = vCodec.samplingRate; + *invert = vCodec.invert; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the Voice CODEC primary audio channel timeslot. + * + * Set the Voice CODEC primary audio channel timeslot. This function must be + * used if the default timeslot for the primary audio channel is to be changed. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot Select the primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC primary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC primary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_TIMESLOT + timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + const unsigned int reg_mask = SET_BITS(regSSI_NETWORK, CDCTXRXSLOT, 3); + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + ((timeslot == USE_TS0) || (timeslot == USE_TS1) || + (timeslot == USE_TS2) || (timeslot == USE_TS3))) { + reg_write = SET_BITS(regSSI_NETWORK, CDCTXRXSLOT, timeslot); + + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + vCodec.timeslot = timeslot; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Voice CODEC primary audio channel timeslot. + * + * Get the current Voice CODEC primary audio channel timeslot. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot The primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC primary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC primary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_VCODEC_TIMESLOT * + const timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (timeslot != (PMIC_AUDIO_VCODEC_TIMESLOT *) NULL)) { + *timeslot = vCodec.timeslot; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the Voice CODEC secondary recording audio channel timeslot. + * + * Set the Voice CODEC secondary audio channel timeslot. This function must be + * used if the default timeslot for the secondary audio channel is to be + * changed. The secondary audio channel timeslot is used to transmit the audio + * data that was recorded by the Voice CODEC from the secondary audio input + * channel. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot Select the secondary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC secondary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC secondary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_secondary_txslot(const PMIC_AUDIO_HANDLE + handle, + const + PMIC_AUDIO_VCODEC_TIMESLOT + timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = SET_BITS(regSSI_NETWORK, CDCTXSECSLOT, 3); + unsigned int reg_write = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + /* How to handle primary slot and secondary slot being the same */ + if ((timeslot >= USE_TS0) && (timeslot <= USE_TS3) + && (timeslot != vCodec.timeslot)) { + reg_write = + SET_BITS(regSSI_NETWORK, CDCTXSECSLOT, timeslot); + + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + vCodec.secondaryTXtimeslot = timeslot; + } + } + + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the Voice CODEC secondary recording audio channel timeslot. + * + * Get the Voice CODEC secondary audio channel timeslot. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot The secondary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC secondary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC secondary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_secondary_txslot(const PMIC_AUDIO_HANDLE + handle, + PMIC_AUDIO_VCODEC_TIMESLOT * + const timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (timeslot != (PMIC_AUDIO_VCODEC_TIMESLOT *) NULL)) { + rc = PMIC_SUCCESS; + *timeslot = vCodec.secondaryTXtimeslot; + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Set/Enable the Voice CODEC options. + * + * Set or enable various Voice CODEC options. The available options include + * the use of dithering, highpass digital filters, and loopback modes. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Voice CODEC options to enable. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or Voice CODEC options + * were invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * successfully set/enabled. + */ +PMIC_STATUS pmic_audio_vcodec_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (config & DITHERING) { + reg_write = SET_BITS(regAUD_CODEC, CDCDITH, 0); + reg_mask = SET_BITS(regAUD_CODEC, CDCDITH, 1); + } + + if (config & INPUT_HIGHPASS_FILTER) { + reg_write |= SET_BITS(regAUD_CODEC, AUDIHPF, 1); + reg_mask |= SET_BITS(regAUD_CODEC, AUDIHPF, 1); + } + + if (config & OUTPUT_HIGHPASS_FILTER) { + reg_write |= SET_BITS(regAUD_CODEC, AUDOHPF, 1); + reg_mask |= SET_BITS(regAUD_CODEC, AUDOHPF, 1); + } + + if (config & DIGITAL_LOOPBACK) { + reg_write |= SET_BITS(regAUD_CODEC, CDCDLM, 1); + reg_mask |= SET_BITS(regAUD_CODEC, CDCDLM, 1); + } + + if (config & ANALOG_LOOPBACK) { + reg_write |= SET_BITS(regAUD_CODEC, CDCALM, 1); + reg_mask |= SET_BITS(regAUD_CODEC, CDCALM, 1); + } + + if (config & VCODEC_MASTER_CLOCK_OUTPUTS) { + reg_write |= SET_BITS(regAUD_CODEC, CDCCLKEN, 1) | + SET_BITS(regAUD_CODEC, CDCTS, 0); + reg_mask |= SET_BITS(regAUD_CODEC, CDCCLKEN, 1) | + SET_BITS(regAUD_CODEC, CDCTS, 1); + + } + + if (config & TRISTATE_TS) { + reg_write |= SET_BITS(regAUD_CODEC, CDCTS, 1); + reg_mask |= SET_BITS(regAUD_CODEC, CDCTS, 1); + } + + if (reg_mask == 0) { + /* We should not reach this point without having to configure + * anything so we flag it as an error. + */ + rc = PMIC_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_CODEC, + reg_write, reg_mask); + } + + if (rc == PMIC_SUCCESS) { + vCodec.config |= config; + } + } + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Clear/Disable the Voice CODEC options. + * + * Clear or disable various Voice CODEC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Voice CODEC options to be cleared/disabled. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the Voice CODEC options + * were invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * cleared/disabled. + */ +PMIC_STATUS pmic_audio_vcodec_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_CONFIG + config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (config & DITHERING) { + reg_mask = SET_BITS(regAUD_CODEC, CDCDITH, 1); + reg_write = SET_BITS(regAUD_CODEC, CDCDITH, 1); + } + + if (config & INPUT_HIGHPASS_FILTER) { + reg_mask |= SET_BITS(regAUD_CODEC, AUDIHPF, 1); + } + + if (config & OUTPUT_HIGHPASS_FILTER) { + reg_mask |= SET_BITS(regAUD_CODEC, AUDOHPF, 1); + } + + if (config & DIGITAL_LOOPBACK) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCDLM, 1); + } + + if (config & ANALOG_LOOPBACK) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCALM, 1); + } + + if (config & VCODEC_MASTER_CLOCK_OUTPUTS) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCCLKEN, 1); + } + + if (config & TRISTATE_TS) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCTS, 1); + } + + if (reg_mask == 0) { + /* We should not reach this point without having to configure + * anything so we flag it as an error. + */ + rc = PMIC_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_CODEC, + reg_write, reg_mask); + } + + if (rc == PMIC_SUCCESS) { + vCodec.config |= config; + } + + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Voice CODEC options. + * + * Get the current Voice CODEC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The current set of Voice CODEC options. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_VCODEC_CONFIG * + const config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (config != (PMIC_AUDIO_VCODEC_CONFIG *) NULL)) { + *config = vCodec.config; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable the Voice CODEC bypass audio pathway. + * + * Enables the Voice CODEC bypass pathway for audio data. This allows direct + * output of the voltages on the TX data bus line to the output amplifiers + * (bypassing the digital-to-analog converters within the Voice CODEC). + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC bypass was successfully + * enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC bypass could not be + * enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_bypass(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = SET_BITS(regAUD_CODEC, CDCBYP, 1); + const unsigned int reg_mask = SET_BITS(regAUD_CODEC, CDCBYP, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_CODEC, reg_write, reg_mask); + } + + return rc; +} + +/*! + * @brief Disable the Voice CODEC bypass audio pathway. + * + * Disables the Voice CODEC bypass pathway for audio data. This means that + * the TX data bus line will deliver digital data to the digital-to-analog + * converters within the Voice CODEC. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC bypass was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC bypass could not be + * disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_bypass(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = 0; + const unsigned int reg_mask = SET_BITS(regAUD_CODEC, CDCBYP, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_CODEC, reg_write, reg_mask); + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * General Stereo DAC configuration. + ************************************************************************* + */ + +/*! + * @name General Stereo DAC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Stereo + * DAC hardware. + */ +/*@{*/ + +/*! + * @brief Set the Stereo DAC clock source and operating characteristics. + * + * Define the Stereo DAC clock source and operating characteristics. This + * must be done before the Stereo DAC is enabled. + * + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn Select the clock signal source. + * @param clockFreq Select the clock signal frequency. + * @param samplingRate Select the audio data sampling rate. + * @param invert Enable inversion of the frame sync and/or + * bit clock inputs. + * + * @retval PMIC_SUCCESS If the Stereo DAC clock settings were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or clock configuration was + * invalid. + * @retval PMIC_ERROR If the Stereo DAC clock configuration + * could not be set. + */ +PMIC_STATUS pmic_audio_stdac_set_clock(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_CLOCK_IN_SOURCE clockIn, + const PMIC_AUDIO_STDAC_CLOCK_IN_FREQ + clockFreq, + const PMIC_AUDIO_STDAC_SAMPLING_RATE + samplingRate, + const PMIC_AUDIO_CLOCK_INVERT invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + /* Validate all of the calling parameters. */ + if (handle == (PMIC_AUDIO_HANDLE) NULL) { + rc = PMIC_PARAMETER_ERROR; + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + if ((clockIn != CLOCK_IN_CLIA) && (clockIn != CLOCK_IN_CLIB)) { + rc = PMIC_PARAMETER_ERROR; + } else if ((stDAC.masterSlave == BUS_MASTER_MODE) + && !((clockFreq >= STDAC_CLI_3_36864MHZ) + && (clockFreq <= STDAC_CLI_33_6MHZ))) { + rc = PMIC_PARAMETER_ERROR; + } else if ((stDAC.masterSlave == BUS_SLAVE_MODE) + && !((clockFreq >= STDAC_MCLK_PLL_DISABLED) + && (clockFreq <= STDAC_BCLK_IN_PLL))) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((samplingRate >= STDAC_RATE_8_KHZ) + && (samplingRate <= STDAC_RATE_96_KHZ))) { + rc = PMIC_PARAMETER_ERROR; + } + /* + else if(!((invert >= NO_INVERT) && (invert <= INVERT_FRAMESYNC))) + { + rc = PMIC_PARAMETER_ERROR; + } */ + else { + reg_mask = SET_BITS(regST_DAC, STDCCLK, 7) | + SET_BITS(regST_DAC, STDCCLKSEL, 1) | + SET_BITS(regST_DAC, SR, 15) | + SET_BITS(regST_DAC, STDCBCLINV, 1) | + SET_BITS(regST_DAC, STDCFSINV, 1); + if (clockIn == CLOCK_IN_CLIA) { + reg_value = SET_BITS(regST_DAC, STDCCLKSEL, 0); + } else { + reg_value = SET_BITS(regST_DAC, STDCCLKSEL, 1); + } + /* How to take care of sample rates in SLAVE mode */ + if ((clockFreq == STDAC_CLI_3_36864MHZ) + || ((clockFreq == STDAC_FSYNC_IN_PLL))) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 6); + } else if ((clockFreq == STDAC_CLI_12MHZ) + || (clockFreq == STDAC_MCLK_PLL_DISABLED)) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 5); + } else if (clockFreq == STDAC_CLI_13MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 0); + } else if (clockFreq == STDAC_CLI_15_36MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 1); + } else if (clockFreq == STDAC_CLI_16_8MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 2); + } else if (clockFreq == STDAC_CLI_26MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 4); + } else if ((clockFreq == STDAC_CLI_33_6MHZ) + || (clockFreq == STDAC_BCLK_IN_PLL)) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 7); + } + + reg_value |= SET_BITS(regST_DAC, SR, samplingRate); + + if (invert & INVERT_BITCLOCK) { + reg_value |= SET_BITS(regST_DAC, STDCBCLINV, 1); + } + if (invert & INVERT_FRAMESYNC) { + reg_value |= SET_BITS(regST_DAC, STDCFSINV, 1); + } + if (invert & NO_INVERT) { + reg_value |= SET_BITS(regST_DAC, STDCBCLINV, 0); + reg_value |= SET_BITS(regST_DAC, STDCFSINV, 0); + } + if (pmic_write_reg + (REG_AUDIO_STEREO_DAC, reg_value, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("STDAC clock set\n"); + rc = PMIC_SUCCESS; + stDAC.clockIn = clockIn; + stDAC.clockFreq = clockFreq; + stDAC.samplingRate = samplingRate; + stDAC.invert = invert; + } + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the Stereo DAC clock source and operating characteristics. + * + * Get the current Stereo DAC clock source and operating characteristics. + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn The clock signal source. + * @param clockFreq The clock signal frequency. + * @param samplingRate The audio data sampling rate. + * @param invert Inversion of the frame sync and/or + * bit clock inputs is enabled/disabled. + * + * @retval PMIC_SUCCESS If the Stereo DAC clock settings were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle invalid. + * @retval PMIC_ERROR If the Stereo DAC clock configuration + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_clock(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_CLOCK_IN_SOURCE * + const clockIn, + PMIC_AUDIO_STDAC_SAMPLING_RATE * + const samplingRate, + PMIC_AUDIO_STDAC_CLOCK_IN_FREQ * + const clockFreq, + PMIC_AUDIO_CLOCK_INVERT * const invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE) && + (clockIn != (PMIC_AUDIO_CLOCK_IN_SOURCE *) NULL) && + (samplingRate != (PMIC_AUDIO_STDAC_SAMPLING_RATE *) NULL) && + (clockFreq != (PMIC_AUDIO_STDAC_CLOCK_IN_FREQ *) NULL) && + (invert != (PMIC_AUDIO_CLOCK_INVERT *) NULL)) { + *clockIn = stDAC.clockIn; + *samplingRate = stDAC.samplingRate; + *clockFreq = stDAC.clockFreq; + *invert = stDAC.invert; + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the Stereo DAC primary audio channel timeslot. + * + * Set the Stereo DAC primary audio channel timeslot. This function must be + * used if the default timeslot for the primary audio channel is to be changed. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot Select the primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Stereo DAC primary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Stereo DAC primary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_stdac_set_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_TIMESLOTS + timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = SET_BITS(regSSI_NETWORK, STDCRXSLOT, 3); + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + if ((timeslot == USE_TS0_TS1) || (timeslot == USE_TS2_TS3) + || (timeslot == USE_TS4_TS5) || (timeslot == USE_TS6_TS7)) { + if (pmic_write_reg + (REG_AUDIO_SSI_NETWORK, timeslot, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("STDAC primary timeslot set\n"); + stDAC.timeslot = timeslot; + rc = PMIC_SUCCESS; + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Stereo DAC primary audio channel timeslot. + * + * Get the current Stereo DAC primary audio channel timeslot. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot The primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Stereo DAC primary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC primary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STDAC_TIMESLOTS * + const timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE) && + (timeslot != (PMIC_AUDIO_STDAC_TIMESLOTS *) NULL)) { + *timeslot = stDAC.timeslot; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set/Enable the Stereo DAC options. + * + * Set or enable various Stereo DAC options. The available options include + * resetting the digital filter and enabling the bus master clock outputs. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Stereo DAC options to enable. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or Stereo DAC options + * were invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * successfully set/enabled. + */ +PMIC_STATUS pmic_audio_stdac_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + if (config & STDAC_MASTER_CLOCK_OUTPUTS) { + reg_write |= SET_BITS(regST_DAC, STDCCLKEN, 1); + reg_mask |= SET_BITS(regST_DAC, STDCCLKEN, 1); + } + + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + stDAC.config |= config; + pr_debug("STDAC config set\n"); + + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Clear/Disable the Stereo DAC options. + * + * Clear or disable various Stereo DAC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Stereo DAC options to be cleared/disabled. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the Stereo DAC options + * were invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * cleared/disabled. + */ +PMIC_STATUS pmic_audio_stdac_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + + if (config & STDAC_MASTER_CLOCK_OUTPUTS) { + reg_mask |= SET_BITS(regST_DAC, STDCCLKEN, 1); + } + + if (reg_mask != 0) { + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + stDAC.config &= ~config; + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Stereo DAC options. + * + * Get the current Stereo DAC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The current set of Stereo DAC options. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STDAC_CONFIG * const config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE) && + (config != (PMIC_AUDIO_STDAC_CONFIG *) NULL)) { + *config = stDAC.config; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio input section configuration. + ************************************************************************* + */ + +/*! + * @name Audio Input Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC audio + * input hardware. + */ +/*@{*/ + +/*! + * @brief Set/Enable the audio input section options. + * + * Set or enable various audio input section options. The only available + * option right now is to enable the automatic disabling of the microphone + * input amplifiers when a microphone/headset is inserted or removed. + * NOT SUPPORTED BY MC13783 + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The audio input section options to enable. + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio input section + * options were invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be successfully set/enabled. + */ +PMIC_STATUS pmic_audio_input_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_CONFIG config) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*! + * @brief Clear/Disable the audio input section options. + * + * Clear or disable various audio input section options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The audio input section options to be + * cleared/disabled. + * NOT SUPPORTED BY MC13783 + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the audio input section + * options were invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be cleared/disabled. + */ +PMIC_STATUS pmic_audio_input_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_CONFIG config) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; + +} + +/*! + * @brief Get the current audio input section options. + * + * Get the current audio input section options. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] config The current set of audio input section options. + * NOT SUPPORTED BY MC13783 + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be retrieved. + */ +PMIC_STATUS pmic_audio_input_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_CONFIG * const config) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio recording using the Voice CODEC. + ************************************************************************* + */ + +/*! + * @name Audio Recording Using the Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice CODEC + * to perform audio recording. + */ +/*@{*/ + +/*! + * @brief Select the microphone inputs to be used for Voice CODEC recording. + * + * Select left (mc13783-only) and right microphone inputs for Voice CODEC + * recording. It is possible to disable or not use a particular microphone + * input channel by specifying NO_MIC as a parameter. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel Select the left microphone input channel. + * @param rightChannel Select the right microphone input channel. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input ports + * were invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be successfully enabled. + */ +PMIC_STATUS pmic_audio_vcodec_set_mic(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_PORT leftChannel, + const PMIC_AUDIO_INPUT_PORT rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (!((leftChannel == NO_MIC) || (leftChannel == MIC1_LEFT))) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((rightChannel == NO_MIC) + || (rightChannel == MIC1_RIGHT_MIC_MONO) + || (rightChannel == TXIN_EXT) + || (rightChannel == MIC2_AUX))) { + rc = PMIC_PARAMETER_ERROR; + } else { + if (leftChannel == NO_MIC) { + } else { /* Left channel MIC enable */ + reg_mask = SET_BITS(regAUDIO_TX, AMC1LEN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1); + reg_write = SET_BITS(regAUDIO_TX, AMC1LEN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 0); + } + /*For right channel enable one and clear the other two as well as RXINREC */ + if (rightChannel == NO_MIC) { + } else if (rightChannel == MIC1_RIGHT_MIC_MONO) { + reg_mask |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + reg_write |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 0) | + SET_BITS(regAUDIO_TX, AMC2EN, 0) | + SET_BITS(regAUDIO_TX, ATXINEN, 0); + } else if (rightChannel == MIC2_AUX) { + reg_mask |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + reg_write |= SET_BITS(regAUDIO_TX, AMC1REN, 0) | + SET_BITS(regAUDIO_TX, RXINREC, 0) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 0); + } else { /* TX line in */ + reg_mask |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + reg_write |= SET_BITS(regAUDIO_TX, AMC1REN, 0) | + SET_BITS(regAUDIO_TX, RXINREC, 0) | + SET_BITS(regAUDIO_TX, AMC2EN, 0) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + } + + if (reg_mask == 0) { + rc = PMIC_PARAMETER_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug + ("MIC inputs configured successfully\n"); + vCodec.leftChannelMic.mic = leftChannel; + vCodec.rightChannelMic.mic = + rightChannel; + + } + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current microphone inputs being used for Voice CODEC + * recording. + * + * Get the left (mc13783-only) and right microphone inputs currently being + * used for Voice CODEC recording. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel The left microphone input channel. + * @param rightChannel The right microphone input channel. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_mic(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_PORT * const leftChannel, + PMIC_AUDIO_INPUT_PORT * + const rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (leftChannel != (PMIC_AUDIO_INPUT_PORT *) NULL) && + (rightChannel != (PMIC_AUDIO_INPUT_PORT *) NULL)) { + *leftChannel = vCodec.leftChannelMic.mic; + *rightChannel = vCodec.rightChannelMic.mic; + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Enable/disable the microphone input. + * + * This function enables/disables the current microphone input channel. The + * input amplifier is automatically turned off when the microphone input is + * disabled. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel The left microphone input channel state. + * @param rightChannel the right microphone input channel state. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully reconfigured. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input states + * were invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be reconfigured. + */ +PMIC_STATUS pmic_audio_vcodec_set_mic_on_off(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_MIC_STATE + leftChannel, + const PMIC_AUDIO_INPUT_MIC_STATE + rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + unsigned int curr_left = 0; + unsigned int curr_right = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + curr_left = vCodec.leftChannelMic.mic; + curr_right = vCodec.rightChannelMic.mic; + if ((curr_left == NO_MIC) && (curr_right == NO_MIC)) { + rc = PMIC_PARAMETER_ERROR; + } else { + if (curr_left == MIC1_LEFT) { + if ((leftChannel == MICROPHONE_ON) && + (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, 0); + + } else if ((leftChannel == MICROPHONE_OFF) && + (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, 0); + + } else { + /* Both are in same state . Nothing to be done */ + } + + } + if (curr_right == MIC1_RIGHT_MIC_MONO) { + if ((rightChannel == MICROPHONE_ON) && + (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else if ((rightChannel == MICROPHONE_OFF) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else { + /* Both are in same state . Nothing to be done */ + } + } else if (curr_right == MIC2_AUX) { + if ((rightChannel == MICROPHONE_ON) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else if ((rightChannel == MICROPHONE_OFF) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else { + /* Both are in same state . Nothing to be done */ + } + } else if (curr_right == TXIN_EXT) { + if ((rightChannel == MICROPHONE_ON) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + } else if ((rightChannel == MICROPHONE_OFF) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else { + /* Both are in same state . Nothing to be done */ + } + } + if (reg_mask == 0) { + } else { + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug + ("MIC states configured successfully\n"); + vCodec.leftChannelMic.micOnOff = + leftChannel; + vCodec.rightChannelMic.micOnOff = + rightChannel; + } + } + } + + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Return the current state of the microphone inputs. + * + * This function returns the current state (on/off) of the microphone + * input channels. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel The current left microphone input channel + * state. + * @param rightChannel the current right microphone input channel + * state. + * + * @retval PMIC_SUCCESS If the microphone input channel states + * were successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input channel states + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_mic_on_off(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_MIC_STATE * + const leftChannel, + PMIC_AUDIO_INPUT_MIC_STATE * + const rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (leftChannel != (PMIC_AUDIO_INPUT_MIC_STATE *) NULL) && + (rightChannel != (PMIC_AUDIO_INPUT_MIC_STATE *) NULL)) { + *leftChannel = vCodec.leftChannelMic.micOnOff; + *rightChannel = vCodec.rightChannelMic.micOnOff; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the microphone input amplifier mode and gain level. + * + * This function sets the current microphone input amplifier operating mode + * and gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannelMode The left microphone input amplifier mode. + * @param leftChannelGain The left microphone input amplifier gain level. + * @param rightChannelMode The right microphone input amplifier mode. + * @param rightChannelGain The right microphone input amplifier gain + * level. + * + * @retval PMIC_SUCCESS If the microphone input amplifiers were + * successfully reconfigured. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input amplifier + * modes or gain levels were invalid. + * @retval PMIC_ERROR If the microphone input amplifiers could + * not be reconfigured. + */ +PMIC_STATUS pmic_audio_vcodec_set_record_gain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_AMP_MODE + leftChannelMode, + const PMIC_AUDIO_MIC_GAIN + leftChannelGain, + const PMIC_AUDIO_MIC_AMP_MODE + rightChannelMode, + const PMIC_AUDIO_MIC_GAIN + rightChannelGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (!(((leftChannelGain >= MIC_GAIN_MINUS_8DB) + && (leftChannelGain <= MIC_GAIN_PLUS_23DB)) + && ((rightChannelGain >= MIC_GAIN_MINUS_8DB) + && (rightChannelGain <= MIC_GAIN_PLUS_23DB)))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("VCODEC set record gain - wrong gain value\n"); + } else if (((leftChannelMode != AMP_OFF) + && (leftChannelMode != VOLTAGE_TO_VOLTAGE) + && (leftChannelMode != CURRENT_TO_VOLTAGE)) + || ((rightChannelMode != VOLTAGE_TO_VOLTAGE) + && (rightChannelMode != CURRENT_TO_VOLTAGE) + && (rightChannelMode != AMP_OFF))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("VCODEC set record gain - wrong amp mode\n"); + } else { + if (vCodec.leftChannelMic.mic == MIC1_LEFT) { + reg_mask = SET_BITS(regAUDIO_TX, AMC1LITOV, 1) | + SET_BITS(regAUDIO_TX, PGATXL, 31); + if (leftChannelMode == VOLTAGE_TO_VOLTAGE) { + reg_write = + SET_BITS(regAUDIO_TX, AMC1LITOV, 0); + } else { + reg_write = + SET_BITS(regAUDIO_TX, AMC1LITOV, 1); + } + reg_write |= + SET_BITS(regAUDIO_TX, PGATXL, + leftChannelGain); + } + if (vCodec.rightChannelMic.mic == MIC1_RIGHT_MIC_MONO) { + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1RITOV, + 1) | SET_BITS(regAUDIO_TX, PGATXR, + 31); + if (rightChannelMode == VOLTAGE_TO_VOLTAGE) { + reg_write |= + SET_BITS(regAUDIO_TX, AMC1RITOV, 0); + } else { + reg_write |= + SET_BITS(regAUDIO_TX, AMC1RITOV, 1); + } + reg_write |= + SET_BITS(regAUDIO_TX, PGATXR, + rightChannelGain); + } else if (vCodec.rightChannelMic.mic == MIC2_AUX) { + reg_mask |= SET_BITS(regAUDIO_TX, AMC2ITOV, 1); + reg_mask |= SET_BITS(regAUDIO_TX, PGATXR, 31); + if (rightChannelMode == VOLTAGE_TO_VOLTAGE) { + reg_write |= + SET_BITS(regAUDIO_TX, AMC2ITOV, 0); + } else { + reg_write |= + SET_BITS(regAUDIO_TX, AMC2ITOV, 1); + } + reg_write |= + SET_BITS(regAUDIO_TX, PGATXR, + rightChannelGain); + } else if (vCodec.rightChannelMic.mic == TXIN_EXT) { + reg_mask |= SET_BITS(regAUDIO_TX, PGATXR, 31); + /* No current to voltage option for TX IN amplifier */ + reg_write |= + SET_BITS(regAUDIO_TX, PGATXR, + rightChannelGain); + } + + if (reg_mask == 0) { + } else { + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + reg_write = + SET_BITS(regAUDIO_TX, PGATXL, + leftChannelGain); + reg_mask = SET_BITS(regAUDIO_TX, PGATXL, 31); + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("MIC amp mode and gain set\n"); + vCodec.leftChannelMic.ampMode = + leftChannelMode; + vCodec.leftChannelMic.gain = + leftChannelGain; + vCodec.rightChannelMic.ampMode = + rightChannelMode; + vCodec.rightChannelMic.gain = + rightChannelGain; + + } + } + } + } + + /* Exit critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current microphone input amplifier mode and gain level. + * + * This function gets the current microphone input amplifier operating mode + * and gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannelMode The left microphone input amplifier mode. + * @param leftChannelGain The left microphone input amplifier gain level. + * @param rightChannelMode The right microphone input amplifier mode. + * @param rightChannelGain The right microphone input amplifier gain + * level. + * + * @retval PMIC_SUCCESS If the microphone input amplifier modes + * and gain levels were successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input amplifier modes + * and gain levels could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_record_gain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_MIC_AMP_MODE * + const leftChannelMode, + PMIC_AUDIO_MIC_GAIN * + const leftChannelGain, + PMIC_AUDIO_MIC_AMP_MODE * + const rightChannelMode, + PMIC_AUDIO_MIC_GAIN * + const rightChannelGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (leftChannelMode != (PMIC_AUDIO_MIC_AMP_MODE *) NULL) && + (leftChannelGain != (PMIC_AUDIO_MIC_GAIN *) NULL) && + (rightChannelMode != (PMIC_AUDIO_MIC_AMP_MODE *) NULL) && + (rightChannelGain != (PMIC_AUDIO_MIC_GAIN *) NULL)) { + *leftChannelMode = vCodec.leftChannelMic.ampMode; + *leftChannelGain = vCodec.leftChannelMic.gain; + *rightChannelMode = vCodec.rightChannelMic.ampMode; + *rightChannelGain = vCodec.rightChannelMic.gain; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable a microphone bias circuit. + * + * This function enables one of the available microphone bias circuits. + * + * @param handle Device handle from pmic_audio_open() call. + * @param biasCircuit The microphone bias circuit to be enabled. + * + * @retval PMIC_SUCCESS If the microphone bias circuit was + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or selected microphone bias + * circuit was invalid. + * @retval PMIC_ERROR If the microphone bias circuit could not + * be enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_micbias(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_BIAS + biasCircuit) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (biasCircuit & MIC_BIAS1) { + reg_write = SET_BITS(regAUDIO_TX, MC1BEN, 1); + reg_mask = SET_BITS(regAUDIO_TX, MC1BEN, 1); + } + if (biasCircuit & MIC_BIAS2) { + reg_write |= SET_BITS(regAUDIO_TX, MC2BEN, 1); + reg_mask |= SET_BITS(regAUDIO_TX, MC2BEN, 1); + } + if (reg_mask != 0) { + rc = pmic_write_reg(REG_AUDIO_TX, reg_write, reg_mask); + } + } + + return rc; +} + +/*! + * @brief Disable a microphone bias circuit. + * + * This function disables one of the available microphone bias circuits. + * + * @param handle Device handle from pmic_audio_open() call. + * @param biasCircuit The microphone bias circuit to be disabled. + * + * @retval PMIC_SUCCESS If the microphone bias circuit was + * successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or selected microphone bias + * circuit was invalid. + * @retval PMIC_ERROR If the microphone bias circuit could not + * be disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_micbias(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_BIAS + biasCircuit) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (biasCircuit & MIC_BIAS1) { + reg_mask = SET_BITS(regAUDIO_TX, MC1BEN, 1); + } + + if (biasCircuit & MIC_BIAS2) { + reg_mask |= SET_BITS(regAUDIO_TX, MC2BEN, 1); + } + + if (reg_mask != 0) { + rc = pmic_write_reg(REG_AUDIO_TX, reg_write, reg_mask); + } + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio Playback Using the Voice CODEC. + ************************************************************************* + */ + +/*! + * @name Audio Playback Using the Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice CODEC + * to perform audio playback. + */ +/*@{*/ + +/*! + * @brief Configure and enable the Voice CODEC mixer. + * + * This function configures and enables the Voice CODEC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * @param rxSecondaryTimeslot The timeslot used for the secondary audio + * channel. + * @param gainIn The secondary audio channel gain level. + * @param gainOut The mixer output gain level. + * + * @retval PMIC_SUCCESS If the Voice CODEC mixer was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mixer configuration + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC mixer could not be + * reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_mixer(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_TIMESLOT + rxSecondaryTimeslot, + const PMIC_AUDIO_VCODEC_MIX_IN_GAIN + gainIn, + const PMIC_AUDIO_VCODEC_MIX_OUT_GAIN + gainOut) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (!((rxSecondaryTimeslot >= USE_TS0) + && (rxSecondaryTimeslot <= USE_TS3))) { + pr_debug + ("VCODEC enable mixer - wrong sec rx timeslot\n"); + } else if (!((gainIn >= VCODEC_NO_MIX) + && (gainIn <= VCODEC_MIX_IN_MINUS_12DB))) { + pr_debug("VCODEC enable mixer - wrong mix in gain\n"); + + } else if (!((gainOut >= VCODEC_MIX_OUT_0DB) + && (gainOut <= VCODEC_MIX_OUT_MINUS_6DB))) { + pr_debug("VCODEC enable mixer - wrong mix out gain\n"); + } else { + + reg_mask = SET_BITS(regSSI_NETWORK, CDCRXSECSLOT, 3) | + SET_BITS(regSSI_NETWORK, CDCRXSECGAIN, 3) | + SET_BITS(regSSI_NETWORK, CDCSUMGAIN, 1); + reg_write = + SET_BITS(regSSI_NETWORK, CDCRXSECSLOT, + rxSecondaryTimeslot) | + SET_BITS(regSSI_NETWORK, CDCRXSECGAIN, + gainIn) | SET_BITS(regSSI_NETWORK, + CDCSUMGAIN, gainOut); + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + reg_write, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("Vcodec mixer enabled\n"); + } + } + } + + return rc; +} + +/*! + * @brief Disable the Voice CODEC mixer. + * + * This function disables the Voice CODEC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC mixer was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC mixer could not be + * disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regSSI_NETWORK, CDCRXSECGAIN, 1); + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + VCODEC_NO_MIX, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Vcodec mixer disabled\n"); + } + + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio Playback Using the Stereo DAC. + ************************************************************************* + */ + +/*! + * @name Audio Playback Using the Stereo DAC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Stereo DAC + * to perform audio playback. + */ +/*@{*/ + +/*! + * @brief Configure and enable the Stereo DAC mixer. + * + * This function configures and enables the Stereo DAC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * @param rxSecondaryTimeslot The timeslot used for the secondary audio + * channel. + * @param gainIn The secondary audio channel gain level. + * @param gainOut The mixer output gain level. + * + * @retval PMIC_SUCCESS If the Stereo DAC mixer was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mixer configuration + * was invalid. + * @retval PMIC_ERROR If the Stereo DAC mixer could not be + * reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_stdac_enable_mixer(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_TIMESLOTS + rxSecondaryTimeslot, + const PMIC_AUDIO_STDAC_MIX_IN_GAIN + gainIn, + const PMIC_AUDIO_STDAC_MIX_OUT_GAIN + gainOut) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + if (!((rxSecondaryTimeslot >= USE_TS0_TS1) + && (rxSecondaryTimeslot <= USE_TS6_TS7))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("STDAC enable mixer - wrong sec timeslot\n"); + } else if (!((gainIn >= STDAC_NO_MIX) + && (gainIn <= STDAC_MIX_IN_MINUS_12DB))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("STDAC enable mixer - wrong mix in gain\n"); + } else if (!((gainOut >= STDAC_MIX_OUT_0DB) + && (gainOut <= STDAC_MIX_OUT_MINUS_6DB))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("STDAC enable mixer - wrong mix out gain\n"); + } else { + + reg_mask = SET_BITS(regSSI_NETWORK, STDCRXSECSLOT, 3) | + SET_BITS(regSSI_NETWORK, STDCRXSECGAIN, 3) | + SET_BITS(regSSI_NETWORK, STDCSUMGAIN, 1); + reg_write = + SET_BITS(regSSI_NETWORK, STDCRXSECSLOT, + rxSecondaryTimeslot) | + SET_BITS(regSSI_NETWORK, STDCRXSECGAIN, + gainIn) | SET_BITS(regSSI_NETWORK, + STDCSUMGAIN, gainOut); + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + reg_write, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("STDAC mixer enabled\n"); + } + } + + } + + return rc; +} + +/*! + * @brief Disable the Stereo DAC mixer. + * + * This function disables the Stereo DAC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Stereo DAC mixer was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC mixer could not be + * disabled. + */ +PMIC_STATUS pmic_audio_stdac_disable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = 0; + const unsigned int reg_mask = + SET_BITS(regSSI_NETWORK, STDCRXSECGAIN, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, reg_write, reg_mask); + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio Output Control + ************************************************************************* + */ + +/*! + * @name Audio Output Section Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC audio output + * section to support playback. + */ +/*@{*/ + +/*! + * @brief Select the audio output ports. + * + * This function selects the audio output ports to be used. This also enables + * the appropriate output amplifiers. + * + * @param handle Device handle from pmic_audio_open() call. + * @param port The audio output ports to be used. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * acquired. + * @retval PMIC_PARAMETER_ERROR If the handle or output ports were + * invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * acquired. + */ +PMIC_STATUS pmic_audio_output_set_port(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PORT port) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((port == MONO_ALERT) || (port == MONO_EXTOUT)) { + rc = PMIC_NOT_SUPPORTED; + } else { + if (((handle == stDAC.handle) + && (stDAC.handleState == HANDLE_IN_USE)) + || ((handle == extStereoIn.handle) + && (extStereoIn.handleState == HANDLE_IN_USE)) + || ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut == VCODEC_MIXER_OUT))) { + /* Stereo signal and MIXER source needs to be routed to the port + / Avoid Codec direct out */ + + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 1); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 1) | SET_BITS(regAUDIO_RX_0, + ALSPREF, + 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 1); + } + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSLEN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 1); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSREN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 1); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + } + if (port & STEREO_LEFT_LOW_POWER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, LSPLEN, 1); + + reg_write |= SET_BITS(regAUDIO_RX_0, LSPLEN, 1); + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut = VCODEC_DIRECT_OUT)) { + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 0); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 1) | SET_BITS(regAUDIO_RX_0, + ALSPREF, + 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 0); + } + + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSLEN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSREN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 0); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 0); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 0); + } + if (port & MONO_CDCOUT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 1); + + reg_write |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 1); + } + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("output ports enabled\n"); + audioOutput.outputPort = port; + + } + } + } + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Deselect/disable the audio output ports. + * + * This function disables the audio output ports that were previously enabled + * by calling pmic_audio_output_set_port(). + * + * @param handle Device handle from pmic_audio_open() call. + * @param port The audio output ports to be disabled. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or output ports were + * invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * disabled. + */ +PMIC_STATUS pmic_audio_output_clear_port(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PORT port) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((port == MONO_ALERT) || (port == MONO_EXTOUT)) { + rc = PMIC_NOT_SUPPORTED; + } else { + if (((handle == stDAC.handle) + && (stDAC.handleState == HANDLE_IN_USE)) + || ((handle == extStereoIn.handle) + && (extStereoIn.handleState == HANDLE_IN_USE)) + || ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut = VCODEC_MIXER_OUT))) { + /* Stereo signal and MIXER source needs to be routed to the port / + Avoid Codec direct out */ + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 0); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 0) | SET_BITS(regAUDIO_RX_0, + ALSPREF, 0); + + } + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 0); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 0); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 0); + } + if (port & STEREO_LEFT_LOW_POWER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, LSPLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, LSPLEN, 0); + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut = VCODEC_DIRECT_OUT)) { + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 0); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 0) | SET_BITS(regAUDIO_RX_0, + ALSPREF, 0); + } + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 0); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 0); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 0); + } + if (port & MONO_CDCOUT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 0); + } + } +#ifdef CONFIG_HEADSET_DETECT_ENABLE + + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 0); + } +#endif + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("output ports disabled\n"); + audioOutput.outputPort &= ~port; + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current audio output ports. + * + * This function retrieves the audio output ports that are currently being + * used. + * + * @param handle Device handle from pmic_audio_open() call. + * @param port The audio output ports currently being used. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_output_get_port(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_PORT * const port) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) && + (port != (PMIC_AUDIO_OUTPUT_PORT *) NULL)) { + *port = audioOutput.outputPort; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the gain level for the external stereo inputs. + * + * This function sets the gain levels for the external stereo inputs. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The external stereo input gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the gain level could not be set. + */ +PMIC_STATUS pmic_audio_output_set_stereo_in_gain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STEREO_IN_GAIN + gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1) | + SET_BITS(regAUDIO_RX_1, ARXIN, 1); + unsigned int reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + /* The ARX amplifier for stereo is also enabled over here */ + + if ((gain == STEREO_IN_GAIN_0DB) || (gain == STEREO_IN_GAIN_PLUS_18DB)) { + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + + if (gain == STEREO_IN_GAIN_0DB) { + reg_write |= SET_BITS(regAUDIO_RX_1, ARXIN, 1); + } else { + reg_write |= SET_BITS(regAUDIO_RX_1, ARXIN, 0); + } + + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Ext stereo gain set\n"); + extStereoIn.inputGain = gain; + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + } + + return rc; +} + +/*! + * @brief Get the current gain level for the external stereo inputs. + * + * This function retrieves the current gain levels for the external stereo + * inputs. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The current external stereo input gain + * level. + * + * @retval PMIC_SUCCESS If the gain level was successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the gain level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_stereo_in_gain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STEREO_IN_GAIN * + const gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE) && + (gain != (PMIC_AUDIO_STEREO_IN_GAIN *) NULL)) { + *gain = extStereoIn.inputGain; + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the output PGA gain level. + * + * This function sets the audio output PGA gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The output PGA gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the gain level could not be set. + */ +PMIC_STATUS pmic_audio_output_set_pgaGain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PGA_GAIN gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + unsigned int reg_gain; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (!((gain >= OUTPGA_GAIN_MINUS_33DB) + && (gain <= OUTPGA_GAIN_PLUS_6DB))) { + rc = PMIC_NOT_SUPPORTED; + pr_debug("output set PGA gain - wrong gain value\n"); + } else { + reg_gain = gain + 2; + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, ARXIN, 15) | + SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXIN, reg_gain) | + SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGARX, 15); + reg_write = SET_BITS(regAUDIO_RX_1, PGARX, reg_gain); + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGAST, 15); + reg_write = SET_BITS(regAUDIO_RX_1, PGAST, reg_gain); + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output PGA gains set\n"); + + if (handle == stDAC.handle) { + audioOutput.stDacoutputPGAGain = gain; + } else if (handle == vCodec.handle) { + audioOutput.vCodecoutputPGAGain = gain; + } else { + audioOutput.extStereooutputPGAGain = + gain; + } + } else { + pr_debug + ("Error writing PGA gains to register\n"); + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the output PGA gain level. + * + * This function retrieves the current audio output PGA gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The current output PGA gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the gain level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_pgaGain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_PGA_GAIN * + const gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (gain != (PMIC_AUDIO_OUTPUT_PGA_GAIN *) NULL) { + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + *gain = audioOutput.extStereooutputPGAGain; + rc = PMIC_SUCCESS; + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + *gain = audioOutput.vCodecoutputPGAGain; + rc = PMIC_SUCCESS; + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + *gain = audioOutput.stDacoutputPGAGain; + rc = PMIC_SUCCESS; + } else { + rc = PMIC_PARAMETER_ERROR; + } + } else { + rc = PMIC_PARAMETER_ERROR; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable the output mixer. + * + * This function enables the output mixer for the audio stream that + * corresponds to the current handle (i.e., the Voice CODEC, Stereo DAC, or + * the external stereo inputs). + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mixer was successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mixer could not be enabled. + */ +PMIC_STATUS pmic_audio_output_enable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + unsigned int reg_mask_mix = 0; + unsigned int reg_write_mix = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if (((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE))) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGASTEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGASTEN, 1); + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 1); + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGARXEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGARXEN, 1); + audioOutput.vCodecOut = VCODEC_MIXER_OUT; + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 1); + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write_mix, reg_mask_mix); + if (rc == PMIC_SUCCESS) { + pr_debug("Output PGA mixers enabled\n"); + rc = PMIC_SUCCESS; + } + + } else { + pr_debug("Error writing mixer enable to register\n"); + } + + } + + return rc; +} + +/*! + * @brief Disable the output mixer. + * + * This function disables the output mixer for the audio stream that + * corresponds to the current handle (i.e., the Voice CODEC, Stereo DAC, or + * the external stereo inputs). + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mixer was successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mixer could not be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + + unsigned int reg_mask_mix = 0; + unsigned int reg_write_mix = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + if (((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE))) { + /*reg_mask = SET_BITS(regAUDIO_RX_1, PGASTEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGASTEN, 0); */ + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 0); + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGARXEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGARXEN, 0); + audioOutput.vCodecOut = VCODEC_DIRECT_OUT; + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 0); + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + /*reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 0); */ + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write_mix, reg_mask_mix); + if (rc == PMIC_SUCCESS) { + pr_debug("Output PGA mixers disabled\n"); + } + } + } + return rc; +} + +/*! + * @brief Configure and enable the output balance amplifiers. + * + * This function configures and enables the output balance amplifiers. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftGain The desired left channel gain level. + * @param rightGain The desired right channel gain level. + * + * @retval PMIC_SUCCESS If the output balance amplifiers were + * successfully configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or gain levels were invalid. + * @retval PMIC_ERROR If the output balance amplifiers could not + * be reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_output_set_balance(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_BALANCE_GAIN + leftGain, + const PMIC_AUDIO_OUTPUT_BALANCE_GAIN + rightGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + unsigned int reg_mask_ch = 0; + unsigned int reg_write_ch = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if (!((leftGain >= BAL_GAIN_MINUS_21DB) && (leftGain <= BAL_GAIN_0DB))) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((rightGain >= BAL_GAIN_MINUS_21DB) + && (rightGain <= BAL_GAIN_0DB))) { + rc = PMIC_PARAMETER_ERROR; + } else { + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + /* In mc13783 only one channel can be attenuated wrt the other. + * It is not possible to specify attenuation for both + * This function will return an error if both channels + * are required to be attenuated + * The BALLR bit is set/reset depending on whether leftGain + * or rightGain is specified*/ + if ((rightGain == BAL_GAIN_0DB) + && (leftGain == BAL_GAIN_0DB)) { + /* Nothing to be done */ + } else if ((rightGain != BAL_GAIN_0DB) + && (leftGain == BAL_GAIN_0DB)) { + /* Attenuate right channel */ + reg_mask = SET_BITS(regAUDIO_RX_1, BAL, 7); + reg_mask_ch = SET_BITS(regAUDIO_RX_1, BALLR, 1); + reg_write = + SET_BITS(regAUDIO_RX_1, BAL, + (BAL_GAIN_0DB - rightGain)); + /* The enum and the register values are reversed in order .. */ + reg_write_ch = + SET_BITS(regAUDIO_RX_1, BALLR, 0); + /* BALLR = 0 selects right channel for atten */ + } else if ((rightGain == BAL_GAIN_0DB) + && (leftGain != BAL_GAIN_0DB)) { + /* Attenuate left channel */ + + reg_mask = SET_BITS(regAUDIO_RX_1, BAL, 7); + reg_mask_ch = SET_BITS(regAUDIO_RX_1, BALLR, 1); + reg_write = + SET_BITS(regAUDIO_RX_1, BAL, + (BAL_GAIN_0DB - leftGain)); + reg_write_ch = + SET_BITS(regAUDIO_RX_1, BALLR, 1); + /* BALLR = 1 selects left channel for atten */ + } else { + rc = PMIC_PARAMETER_ERROR; + } + + if ((reg_mask == 0) || (reg_mask_ch == 0)) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write_ch, reg_mask_ch); + + if (rc == PMIC_SUCCESS) { + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, + reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug + ("Output balance attenuation set\n"); + audioOutput.balanceLeftGain = + leftGain; + audioOutput.balanceRightGain = + rightGain; + } + } + } + } + } + return rc; +} + +/*! + * @brief Get the current output balance amplifier gain levels. + * + * This function retrieves the current output balance amplifier gain levels. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftGain The current left channel gain level. + * @param rightGain The current right channel gain level. + * + * @retval PMIC_SUCCESS If the output balance amplifier gain levels + * were successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the output balance amplifier gain levels + * could be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_balance(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_BALANCE_GAIN * + const leftGain, + PMIC_AUDIO_OUTPUT_BALANCE_GAIN * + const rightGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) && + ((leftGain != (PMIC_AUDIO_OUTPUT_BALANCE_GAIN *) NULL) && + (rightGain != (PMIC_AUDIO_OUTPUT_BALANCE_GAIN *) NULL))) { + *leftGain = audioOutput.balanceLeftGain; + *rightGain = audioOutput.balanceRightGain; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Configure and enable the output mono adder. + * + * This function configures and enables the output mono adder. + * + * @param handle Device handle from pmic_audio_open() call. + * @param mode The desired mono adder operating mode. + * + * @retval PMIC_SUCCESS If the mono adder was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mono adder mode was + * invalid. + * @retval PMIC_ERROR If the mono adder could not be reconfigured + * or enabled. + */ +PMIC_STATUS pmic_audio_output_enable_mono_adder(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MONO_ADDER_MODE + mode) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = SET_BITS(regAUDIO_RX_1, MONO, 3); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((mode >= MONO_ADDER_OFF) && (mode <= STEREO_OPPOSITE_PHASE)) { + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + if (mode == MONO_ADDER_OFF) { + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 0); + } else if (mode == MONO_ADD_LEFT_RIGHT) { + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 2); + } else if (mode == MONO_ADD_OPPOSITE_PHASE) { + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 3); + } else { /* stereo opposite */ + + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 1); + } + + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output mono adder mode set\n"); + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + } else { + rc = PMIC_PARAMETER_ERROR; + } + return rc; +} + +/*! + * @brief Disable the output mono adder. + * + * This function disables the output mono adder. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mono adder was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mono adder could not be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_mono_adder(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = 0; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_1, MONO, 3); + + /* No critical section required here since we are not updating any + * global data. + */ + + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + } + + return rc; +} + +/*! + * @brief Configure the mono adder output gain level. + * + * This function configures the mono adder output amplifier gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The desired output gain level. + * + * @retval PMIC_SUCCESS If the mono adder output amplifier gain + * level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the mono adder output amplifier gain + * level could not be reconfigured. + */ +PMIC_STATUS pmic_audio_output_set_mono_adder_gain(const PMIC_AUDIO_HANDLE + handle, + const + PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN + gain) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*! + * @brief Get the current mono adder output gain level. + * + * This function retrieves the current mono adder output amplifier gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The current output gain level. + * + * @retval PMIC_SUCCESS If the mono adder output amplifier gain + * level was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mono adder output amplifier gain + * level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_mono_adder_gain(const PMIC_AUDIO_HANDLE + handle, + PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN + * const gain) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*! + * @brief Set various audio output section options. + * + * This function sets one or more audio output section configuration + * options. The currently supported options include whether to disable + * the non-inverting mono speaker output, enabling the loudspeaker common + * bias circuit, enabling detection of headset insertion/removal, and + * whether to automatically disable the headset amplifiers when a headset + * insertion/removal has been detected. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The desired audio output section + * configuration options to be set. + * + * @retval PMIC_SUCCESS If the desired configuration options were + * all successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or configuration options + * were invalid. + * @retval PMIC_ERROR If the desired configuration options + * could not be set. + */ +PMIC_STATUS pmic_audio_output_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + if (config & MONO_SPEAKER_INVERT_OUT_ONLY) { + /* If this is one of the parameters */ + rc = PMIC_NOT_SUPPORTED; + } else { + if (config & MONO_LOUDSPEAKER_COMMON_BIAS) { + reg_mask = SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + } + if (config & HEADSET_DETECT_ENABLE) { + reg_mask |= SET_BITS(regAUDIO_RX_0, HSDETEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETEN, 1); + } + if (config & STEREO_HEADSET_AMP_AUTO_DISABLE) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + } + + if (reg_mask == 0) { + rc = PMIC_PARAMETER_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output config set\n"); + audioOutput.config |= config; + + } + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Clear various audio output section options. + * + * This function clears one or more audio output section configuration + * options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The desired audio output section + * configuration options to be cleared. + * + * @retval PMIC_SUCCESS If the desired configuration options were + * all successfully cleared. + * @retval PMIC_PARAMETER_ERROR If the handle or configuration options + * were invalid. + * @retval PMIC_ERROR If the desired configuration options + * could not be cleared. + */ +PMIC_STATUS pmic_audio_output_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_CONFIG + config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + /*unsigned int reg_write_RX = 0; + unsigned int reg_mask_RX = 0; + unsigned int reg_write_TX = 0; + unsigned int reg_mask_TX = 0; */ + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + if (config & MONO_SPEAKER_INVERT_OUT_ONLY) { + /* If this is one of the parameters */ + rc = PMIC_NOT_SUPPORTED; + } else { + if (config & MONO_LOUDSPEAKER_COMMON_BIAS) { + reg_mask = SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ALSPREF, 0); + } + + if (config & HEADSET_DETECT_ENABLE) { + reg_mask |= SET_BITS(regAUDIO_RX_0, HSDETEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETEN, 0); + } + + if (config & STEREO_HEADSET_AMP_AUTO_DISABLE) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 0); + } + + if (reg_mask == 0) { + rc = PMIC_PARAMETER_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output config cleared\n"); + audioOutput.config &= ~config; + + } + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current audio output section options. + * + * This function retrieves the current audio output section configuration + * option settings. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The current audio output section + * configuration option settings. + * + * @retval PMIC_SUCCESS If the current configuration options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the current configuration options + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_CONFIG * + const config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) && + (config != (PMIC_AUDIO_OUTPUT_CONFIG *) NULL)) { + *config = audioOutput.config; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable the phantom ground circuit that is used to help identify + * the type of headset that has been inserted. + * + * This function enables the phantom ground circuit that is used to help + * identify the type of headset (e.g., stereo or mono) that has been inserted. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the phantom ground circuit was + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the phantom ground circuit could not + * be enabled. + */ +PMIC_STATUS pmic_audio_output_enable_phantom_ground() +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, HSPGDIS, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + rc = pmic_write_reg(REG_AUDIO_RX_0, 0, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("Phantom ground enabled\n"); + + } + return rc; +} + +/*! + * @brief Disable the phantom ground circuit that is used to help identify + * the type of headset that has been inserted. + * + * This function disables the phantom ground circuit that is used to help + * identify the type of headset (e.g., stereo or mono) that has been inserted. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the phantom ground circuit was + * successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the phantom ground circuit could not + * be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_phantom_ground() +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, HSPGDIS, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + rc = pmic_write_reg(REG_AUDIO_RX_0, 1, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("Phantom ground disabled\n"); + + } + return rc; +} + +/*@}*/ + +/************************************************************************** + * Static functions. + ************************************************************************** + */ + +/*! + * @name Audio Driver Internal Support Functions + * These non-exported internal functions are used to support the functionality + * of the exported audio APIs. + */ +/*@{*/ + +/*! + * @brief Enables the 5.6V boost for the microphone bias 2 circuit. + * + * This function enables the switching regulator SW3 and configures it to + * provide the 5.6V boost that is required for driving the microphone bias 2 + * circuit when using a 5-pole jack configuration (which is the case for the + * Sphinx board). + * + * @retval PMIC_SUCCESS The 5.6V boost was successfully enabled. + * @retval PMIC_ERROR Failed to enable the 5.6V boost. + */ +/* +static PMIC_STATUS pmic_audio_mic_boost_enable(void) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + return rc; +} +*/ +/*! + * @brief Disables the 5.6V boost for the microphone bias 2 circuit. + * + * This function disables the switching regulator SW3 to turn off the 5.6V + * boost for the microphone bias 2 circuit. + * + * @retval PMIC_SUCCESS The 5.6V boost was successfully disabled. + * @retval PMIC_ERROR Failed to disable the 5.6V boost. + */ +/* +static PMIC_STATUS pmic_audio_mic_boost_disable(void) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + return rc; +} +*/ + +/*! + * @brief Free a device handle previously acquired by calling pmic_audio_open(). + * + * Terminate further access to the PMIC audio hardware that was previously + * acquired by calling pmic_audio_open(). This now allows another thread to + * successfully call pmic_audio_open() to gain access. + * + * Note that we will shutdown/reset the Voice CODEC or Stereo DAC as well as + * any associated audio input/output components that are no longer required. + * + * Also note that this function should only be called with the mutex already + * acquired. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the close request was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +static PMIC_STATUS pmic_audio_close_handle(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Match up the handle to the audio device and then close it. */ + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + /* Also shutdown the Stereo DAC hardware. The simplest way to + * do this is to simply call pmic_audio_reset_device() which will + * restore the ST_DAC register to it's initial power-on state. + * + * This will also shutdown the audio output section if no one + * else is still using it. + */ + rc = pmic_audio_reset_device(stDAC.handle); + + if (rc == PMIC_SUCCESS) { + stDAC.handle = AUDIO_HANDLE_NULL; + stDAC.handleState = HANDLE_FREE; + } + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + /* Also shutdown the Voice CODEC and audio input hardware. The + * simplest way to do this is to simply call pmic_audio_reset_device() + * which will restore the AUD_CODEC register to it's initial + * power-on state. + * + * This will also shutdown the audio output section if no one + * else is still using it. + */ + rc = pmic_audio_reset_device(vCodec.handle); + if (rc == PMIC_SUCCESS) { + vCodec.handle = AUDIO_HANDLE_NULL; + vCodec.handleState = HANDLE_FREE; + } + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + + /* Call pmic_audio_reset_device() here to shutdown the audio output + * section if no one else is still using it. + */ + rc = pmic_audio_reset_device(extStereoIn.handle); + + if (rc == PMIC_SUCCESS) { + extStereoIn.handle = AUDIO_HANDLE_NULL; + extStereoIn.handleState = HANDLE_FREE; + } + } + + return rc; +} + +/*! + * @brief Reset the selected audio hardware control registers to their + * power on state. + * + * This resets all of the audio hardware control registers currently + * associated with the device handle back to their power on states. For + * example, if the handle is associated with the Stereo DAC and a + * specific output port and output amplifiers, then this function will + * reset all of those components to their initial power on state. + * + * This function can only be called if the mutex has already been acquired. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the reset operation was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the reset was unsuccessful. + */ +static PMIC_STATUS pmic_audio_reset_device(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + /* Also shutdown the audio output section if nobody else is using it. + if ((vCodec.handleState == HANDLE_FREE) && + (extStereoIn.handleState == HANDLE_FREE)) + { + pmic_write_reg(REG_RX_AUD_AMPS, RESET_RX_AUD_AMPS, + REG_FULLMASK); + } */ + + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, + RESET_ST_DAC, REG_FULLMASK); + + if (rc == PMIC_SUCCESS) { + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + RESET_SSI_NETWORK, + REG_SSI_STDAC_MASK); + if (rc == PMIC_SUCCESS) { + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + stDAC.busID = AUDIO_DATA_BUS_1; + stDAC.protocol = NORMAL_MSB_JUSTIFIED_MODE; + stDAC.protocol_set = false; + stDAC.masterSlave = BUS_MASTER_MODE; + stDAC.numSlots = USE_2_TIMESLOTS; + stDAC.clockIn = CLOCK_IN_CLIA; + stDAC.samplingRate = STDAC_RATE_44_1_KHZ; + stDAC.clockFreq = STDAC_CLI_13MHZ; + stDAC.invert = NO_INVERT; + stDAC.timeslot = USE_TS0_TS1; + stDAC.config = (PMIC_AUDIO_STDAC_CONFIG) 0; + + } + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE)) { + /* Disable the audio input section when disabling the Voice CODEC. */ + pmic_write_reg(REG_AUDIO_TX, RESET_AUDIO_TX, REG_FULLMASK); + + rc = pmic_write_reg(REG_AUDIO_CODEC, + RESET_AUD_CODEC, REG_FULLMASK); + + if (rc == PMIC_SUCCESS) { + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + RESET_SSI_NETWORK, + REG_SSI_VCODEC_MASK); + if (rc == PMIC_SUCCESS) { + + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + vCodec.busID = AUDIO_DATA_BUS_2; + vCodec.protocol = NETWORK_MODE; + vCodec.protocol_set = false; + vCodec.masterSlave = BUS_SLAVE_MODE; + vCodec.numSlots = USE_4_TIMESLOTS; + vCodec.clockIn = CLOCK_IN_CLIB; + vCodec.samplingRate = VCODEC_RATE_8_KHZ; + vCodec.clockFreq = VCODEC_CLI_13MHZ; + vCodec.invert = NO_INVERT; + vCodec.timeslot = USE_TS0; + vCodec.config = + INPUT_HIGHPASS_FILTER | + OUTPUT_HIGHPASS_FILTER; + + } + } + + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + /* Disable the Ext stereo Amplifier and disable it as analog mixer input */ + reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + pmic_write_reg(REG_AUDIO_RX_1, 0, reg_mask); + + reg_mask = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + pmic_write_reg(REG_AUDIO_RX_0, 0, reg_mask); + + /* We don't need to reset any other registers for this case. */ + rc = PMIC_SUCCESS; + } + + return rc; +} + +/*! + * @brief Deregister the callback function and event mask currently associated + * with an audio device handle. + * + * This function deregisters any existing callback function and event mask for + * the given audio device handle. This is done by either calling the + * pmic_audio_clear_callback() API or by closing the device handle. + * + * Note that this function should only be called with the mutex already + * acquired. We will also acquire the spinlock here to prevent possible + * race conditions with the interrupt handler. + * + * @param[in] callback The current event callback function pointer. + * @param[in] eventMask The current audio event mask. + * + * @retval PMIC_SUCCESS If the callback function and event mask + * were both successfully deregistered. + * @retval PMIC_ERROR If either the callback function or the + * event mask was not successfully + * deregistered. + */ + +static PMIC_STATUS pmic_audio_deregister(void *callback, + PMIC_AUDIO_EVENTS * const eventMask) +{ + unsigned long flags; + pmic_event_callback_t eventNotify; + PMIC_STATUS rc = PMIC_SUCCESS; + + /* Deregister each of the PMIC events that we had previously + * registered for by calling pmic_event_subscribe(). + */ + if (*eventMask & (HEADSET_DETECTED)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_HSDETI); + if (pmic_event_unsubscribe(EVENT_HSDETI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_DETECTED); + pr_debug("Deregistered for EVENT_HSDETI\n"); + } else { + rc = PMIC_ERROR; + } + } + + if (*eventMask & (HEADSET_STEREO)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_HSLI); + if (pmic_event_unsubscribe(EVENT_HSLI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_STEREO); + pr_debug("Deregistered for EVENT_HSLI\n"); + } else { + rc = PMIC_ERROR; + } + } + if (*eventMask & (HEADSET_THERMAL_SHUTDOWN)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_ALSPTHI); + if (pmic_event_unsubscribe(EVENT_ALSPTHI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_THERMAL_SHUTDOWN); + pr_debug("Deregistered for EVENT_ALSPTHI\n"); + } else { + rc = PMIC_ERROR; + } + } + if (*eventMask & (HEADSET_SHORT_CIRCUIT)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_AHSSHORTI); + if (pmic_event_unsubscribe(EVENT_AHSSHORTI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_SHORT_CIRCUIT); + pr_debug("Deregistered for EVENT_AHSSHORTI\n"); + } else { + rc = PMIC_ERROR; + } + } + + if (rc == PMIC_SUCCESS) { + /* We need to grab the spinlock here to create a critical section to + * avoid any possible race conditions with the interrupt handler + */ + spin_lock_irqsave(&lock, flags); + + /* Restore the initial reset values for the callback function + * and event mask parameters. This should be NULL and zero, + * respectively. + */ + callback = NULL; + *eventMask = 0; + + /* Exit the critical section. */ + spin_unlock_irqrestore(&lock, flags); + } + + return rc; +} + +/*! + * @brief enable/disable fm output. + * + * @param[in] enable true to enable false to disable + */ +PMIC_STATUS pmic_audio_fm_output_enable(bool enable) +{ + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + if (enable) { + pmic_audio_antipop_enable(ANTI_POP_RAMP_FAST); + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + + reg_mask |= SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + + reg_mask |= SET_BITS(regAUDIO_RX_0, HSPGDIS, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, HSPGDIS, 0); + } else { + reg_mask |= SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, ADDRXIN, 0); + } + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + if (rc != PMIC_SUCCESS) + return rc; + if (enable) { + reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + } else { + reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 0); + } + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + return rc; +} + +/*@}*/ + +/************************************************************************** + * Module initialization and termination functions. + * + * Note that if this code is compiled into the kernel, then the + * module_init() function will be called within the device_initcall() + * group. + ************************************************************************** + */ + +/*! + * @name Audio Driver Loading/Unloading Functions + * These non-exported internal functions are used to support the audio + * device driver initialization and de-initialization operations. + */ +/*@{*/ + +/*! + * @brief This is the audio device driver initialization function. + * + * This function is called by the kernel when this device driver is first + * loaded. + */ +static int __init mc13783_pmic_audio_init(void) +{ + printk(KERN_INFO "PMIC Audio driver loading...\n"); + + return 0; +} + +/*! + * @brief This is the audio device driver de-initialization function. + * + * This function is called by the kernel when this device driver is about + * to be unloaded. + */ +static void __exit mc13783_pmic_audio_exit(void) +{ + printk(KERN_INFO "PMIC Audio driver unloading...\n"); + + /* Close all device handles that are still open. This will also + * deregister any callbacks that may still be active. + */ + if (stDAC.handleState == HANDLE_IN_USE) { + pmic_audio_close(stDAC.handle); + } + if (vCodec.handleState == HANDLE_IN_USE) { + pmic_audio_close(vCodec.handle); + } + if (extStereoIn.handleState == HANDLE_IN_USE) { + pmic_audio_close(extStereoIn.handle); + } + + /* Explicitly reset all of the audio registers so that there is no + * possibility of leaving the audio hardware in a state + * where it can cause problems if there is no device driver loaded. + */ + pmic_write_reg(REG_AUDIO_STEREO_DAC, RESET_ST_DAC, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_CODEC, RESET_AUD_CODEC, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_TX, RESET_AUDIO_TX, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_SSI_NETWORK, RESET_SSI_NETWORK, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_RX_0, RESET_AUDIO_RX_0, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_RX_1, RESET_AUDIO_RX_1, REG_FULLMASK); +} + +/*@}*/ + +/* + * Module entry points and description information. + */ + +module_init(mc13783_pmic_audio_init); +module_exit(mc13783_pmic_audio_exit); + +MODULE_DESCRIPTION("PMIC - mc13783 ADC driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_battery.c b/drivers/mxc/pmic/mc13783/pmic_battery.c new file mode 100644 index 000000000000..775ebe77a5c0 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_battery.c @@ -0,0 +1,1221 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_battery.c + * @brief This is the main file of PMIC(mc13783) Battery driver. + * + * @ingroup PMIC_BATTERY + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/wait.h> + +#include <linux/pmic_battery.h> +#include <linux/pmic_adc.h> +#include <linux/pmic_status.h> + +#include "pmic_battery_defs.h" + +#include <mach/pmic_power.h> +#ifdef CONFIG_MXC_HWEVENT +#include <mach/hw_events.h> +#endif + +static int pmic_battery_major; + +/*! + * Number of users waiting in suspendq + */ +static int swait = 0; + +/*! + * To indicate whether any of the battery devices are suspending + */ +static int suspend_flag = 0; + +/*! + * The suspendq is used to block application calls + */ +static wait_queue_head_t suspendq; + +static struct class *pmic_battery_class; + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_batt_enable_charger); +EXPORT_SYMBOL(pmic_batt_disable_charger); +EXPORT_SYMBOL(pmic_batt_set_charger); +EXPORT_SYMBOL(pmic_batt_get_charger_setting); +EXPORT_SYMBOL(pmic_batt_get_charge_current); +EXPORT_SYMBOL(pmic_batt_enable_eol); +EXPORT_SYMBOL(pmic_batt_bp_enable_eol); +EXPORT_SYMBOL(pmic_batt_disable_eol); +EXPORT_SYMBOL(pmic_batt_set_out_control); +EXPORT_SYMBOL(pmic_batt_set_threshold); +EXPORT_SYMBOL(pmic_batt_led_control); +EXPORT_SYMBOL(pmic_batt_set_reverse_supply); +EXPORT_SYMBOL(pmic_batt_set_unregulated); +EXPORT_SYMBOL(pmic_batt_set_5k_pull); +EXPORT_SYMBOL(pmic_batt_event_subscribe); +EXPORT_SYMBOL(pmic_batt_event_unsubscribe); + +static DECLARE_MUTEX(count_mutex); /* open count mutex */ +static int open_count; /* open count for device file */ + +/*! + * Callback function for events, we want on MGN board + */ +static void callback_chg_detect(void) +{ +#ifdef CONFIG_MXC_HWEVENT + t_sensor_bits sensor; + struct mxc_hw_event event = { HWE_BAT_CHARGER_PLUG, 0 }; + + pr_debug("In callback_chg_detect\n"); + + /* get sensor values */ + pmic_get_sensors(&sensor); + + pr_debug("Callback, charger detect:%d\n", sensor.sense_chgdets); + + if (sensor.sense_chgdets) + event.args = 1; + else + event.args = 0; + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static void callback_low_battery(void) +{ +#ifdef CONFIG_MXC_HWEVENT + struct mxc_hw_event event = { HWE_BAT_BATTERY_LOW, 0 }; + + pr_debug("In callback_low_battery\n"); + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static void callback_power_fail(void) +{ +#ifdef CONFIG_MXC_HWEVENT + struct mxc_hw_event event = { HWE_BAT_POWER_FAILED, 0 }; + + pr_debug("In callback_power_fail\n"); + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static void callback_chg_overvoltage(void) +{ +#ifdef CONFIG_MXC_HWEVENT + struct mxc_hw_event event = { HWE_BAT_CHARGER_OVERVOLTAGE, 0 }; + + pr_debug("In callback_chg_overvoltage\n"); + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static void callback_chg_full(void) +{ +#ifdef CONFIG_MXC_HWEVENT + t_sensor_bits sensor; + struct mxc_hw_event event = { HWE_BAT_CHARGER_FULL, 0 }; + + pr_debug("In callback_chg_full\n"); + + /* disable charge function */ + pmic_batt_disable_charger(BATT_MAIN_CHGR); + + /* get charger sensor */ + pmic_get_sensors(&sensor); + + /* if did not detect the charger */ + if (sensor.sense_chgdets) + return; + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +/*! + * This is the suspend of power management for the pmic battery API. + * It suports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int pmic_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + unsigned int reg_value = 0; + + suspend_flag = 1; + CHECK_ERROR(pmic_write_reg(REG_CHARGER, reg_value, PMIC_ALL_BITS)); + + return 0; +}; + +/*! + * This is the resume of power management for the pmic battery API. + * It suports RESTORE state. + * + * @param pdev the device + * + * @return This function returns 0 if successful. + */ +static int pmic_battery_resume(struct platform_device *pdev) +{ + suspend_flag = 0; + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +/*! + * This function is used to start charging a battery. For different charger, + * different voltage and current range are supported. \n + * + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Charging voltage. + * @param c_current Charging current. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_enable_charger(t_batt_charger chgr, + unsigned char c_voltage, + unsigned char c_current) +{ + unsigned int val, mask, reg; + + val = 0; + mask = 0; + reg = 0; + + if (suspend_flag == 1) + return PMIC_ERROR; + + switch (chgr) { + case BATT_MAIN_CHGR: + val = BITFVAL(MC13783_BATT_DAC_DAC, c_current) | + BITFVAL(MC13783_BATT_DAC_V_DAC, c_voltage); + mask = BITFMASK(MC13783_BATT_DAC_DAC) | + BITFMASK(MC13783_BATT_DAC_V_DAC); + reg = REG_CHARGER; + break; + + case BATT_CELL_CHGR: + val = BITFVAL(MC13783_BATT_DAC_V_COIN, c_voltage) | + BITFVAL(MC13783_BATT_DAC_COIN_CH_EN, + MC13783_BATT_DAC_COIN_CH_EN_ENABLED); + mask = BITFMASK(MC13783_BATT_DAC_V_COIN) | + BITFMASK(MC13783_BATT_DAC_COIN_CH_EN); + reg = REG_POWER_CONTROL_0; + break; + + case BATT_TRCKLE_CHGR: + val = BITFVAL(MC13783_BATT_DAC_TRCKLE, c_current); + mask = BITFMASK(MC13783_BATT_DAC_TRCKLE); + reg = REG_CHARGER; + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function turns off a charger. + * + * @param chgr Charger as defined in \b t_batt_charger. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_disable_charger(t_batt_charger chgr) +{ + unsigned int val, mask, reg; + + val = 0; + mask = 0; + reg = 0; + + if (suspend_flag == 1) + return PMIC_ERROR; + switch (chgr) { + case BATT_MAIN_CHGR: + val = BITFVAL(MC13783_BATT_DAC_DAC, 0) | + BITFVAL(MC13783_BATT_DAC_V_DAC, 0); + mask = BITFMASK(MC13783_BATT_DAC_DAC) | + BITFMASK(MC13783_BATT_DAC_V_DAC); + reg = REG_CHARGER; + break; + + case BATT_CELL_CHGR: + val = BITFVAL(MC13783_BATT_DAC_COIN_CH_EN, + MC13783_BATT_DAC_COIN_CH_EN_DISABLED); + mask = BITFMASK(MC13783_BATT_DAC_COIN_CH_EN); + reg = REG_POWER_CONTROL_0; + break; + + case BATT_TRCKLE_CHGR: + val = BITFVAL(MC13783_BATT_DAC_TRCKLE, 0); + mask = BITFMASK(MC13783_BATT_DAC_TRCKLE); + reg = REG_CHARGER; + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to change the charger setting. + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Charging voltage. + * @param c_current Charging current. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_charger(t_batt_charger chgr, + unsigned char c_voltage, + unsigned char c_current) +{ + unsigned int val, mask, reg; + + val = 0; + mask = 0; + reg = 0; + + if (suspend_flag == 1) + return PMIC_ERROR; + + switch (chgr) { + case BATT_MAIN_CHGR: + val = BITFVAL(MC13783_BATT_DAC_DAC, c_current) | + BITFVAL(MC13783_BATT_DAC_V_DAC, c_voltage); + mask = BITFMASK(MC13783_BATT_DAC_DAC) | + BITFMASK(MC13783_BATT_DAC_V_DAC); + reg = REG_CHARGER; + break; + + case BATT_CELL_CHGR: + val = BITFVAL(MC13783_BATT_DAC_V_COIN, c_voltage); + mask = BITFMASK(MC13783_BATT_DAC_V_COIN); + reg = REG_POWER_CONTROL_0; + break; + + case BATT_TRCKLE_CHGR: + val = BITFVAL(MC13783_BATT_DAC_TRCKLE, c_current); + mask = BITFMASK(MC13783_BATT_DAC_TRCKLE); + reg = REG_CHARGER; + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, val, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function is used to retrive the charger setting. + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Output parameter for charging voltage setting. + * @param c_current Output parameter for charging current setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_charger_setting(t_batt_charger chgr, + unsigned char *c_voltage, + unsigned char *c_current) +{ + unsigned int val, reg; + + reg = 0; + + if (suspend_flag == 1) + return PMIC_ERROR; + + switch (chgr) { + case BATT_MAIN_CHGR: + case BATT_TRCKLE_CHGR: + reg = REG_CHARGER; + break; + case BATT_CELL_CHGR: + reg = REG_POWER_CONTROL_0; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, &val, PMIC_ALL_BITS)); + + switch (chgr) { + case BATT_MAIN_CHGR: + *c_voltage = BITFEXT(val, MC13783_BATT_DAC_V_DAC);; + *c_current = BITFEXT(val, MC13783_BATT_DAC_DAC); + break; + + case BATT_CELL_CHGR: + *c_voltage = BITFEXT(val, MC13783_BATT_DAC_V_COIN); + *c_current = 0; + break; + + case BATT_TRCKLE_CHGR: + *c_voltage = 0; + *c_current = BITFEXT(val, MC13783_BATT_DAC_TRCKLE); + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery voltage. + * + * @param b_voltage Output parameter for voltage setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_batt_voltage(unsigned short *b_voltage) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + channel = BATTERY_VOLTAGE; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *b_voltage = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery current. + * + * @param b_current Output parameter for current setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_batt_current(unsigned short *b_current) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + + channel = BATTERY_CURRENT; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *b_current = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery temperature. + * + * @param b_temper Output parameter for temperature setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_batt_temperature(unsigned short *b_temper) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + + channel = GEN_PURPOSE_AD5; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *b_temper = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery charging voltage. + * + * @param c_voltage Output parameter for charging voltage setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_charge_voltage(unsigned short *c_voltage) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + + channel = CHARGE_VOLTAGE; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *c_voltage = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery charging current. + * + * @param c_current Output parameter for charging current setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_charge_current(unsigned short *c_current) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + + channel = CHARGE_CURRENT; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *c_current = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function enables End-of-Life comparator. Not supported on + * mc13783. Use pmic_batt_bp_enable_eol function. + * + * @param threshold End-of-Life threshold. + * + * @return This function returns PMIC_UNSUPPORTED + */ +PMIC_STATUS pmic_batt_enable_eol(unsigned char threshold) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function enables End-of-Life comparator. + * + * @param typical Falling Edge Threshold threshold. + * @verbatim + BPDET UVDET LOBATL + ____ _____ ___________ + 0 2.6 UVDET + 0.2 + 1 2.6 UVDET + 0.3 + 2 2.6 UVDET + 0.4 + 3 2.6 UVDET + 0.5 + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_bp_enable_eol(t_bp_threshold typical) +{ + unsigned int val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_EOL_CMP_EN, + MC13783_BATT_DAC_EOL_CMP_EN_ENABLE) | + BITFVAL(MC13783_BATT_DAC_EOL_SEL, typical); + mask = BITFMASK(MC13783_BATT_DAC_EOL_CMP_EN) | + BITFMASK(MC13783_BATT_DAC_EOL_SEL); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables End-of-Life comparator. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_disable_eol(void) +{ + unsigned int val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_EOL_CMP_EN, + MC13783_BATT_DAC_EOL_CMP_EN_DISABLE); + mask = BITFMASK(MC13783_BATT_DAC_EOL_CMP_EN); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets the output controls. + * It sets the FETOVRD and FETCTRL bits of mc13783 + * + * @param control type of control. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_set_out_control(t_control control) +{ + unsigned int val, mask; + if (suspend_flag == 1) + return PMIC_ERROR; + + switch (control) { + case CONTROL_HARDWARE: + val = BITFVAL(MC13783_BATT_DAC_FETOVRD_EN, 0) | + BITFVAL(MC13783_BATT_DAC_FETCTRL_EN, 0); + mask = BITFMASK(MC13783_BATT_DAC_FETOVRD_EN) | + BITFMASK(MC13783_BATT_DAC_FETCTRL_EN); + break; + case CONTROL_BPFET_LOW: + val = BITFVAL(MC13783_BATT_DAC_FETOVRD_EN, 1) | + BITFVAL(MC13783_BATT_DAC_FETCTRL_EN, 0); + mask = BITFMASK(MC13783_BATT_DAC_FETOVRD_EN) | + BITFMASK(MC13783_BATT_DAC_FETCTRL_EN); + break; + case CONTROL_BPFET_HIGH: + val = BITFVAL(MC13783_BATT_DAC_FETOVRD_EN, 1) | + BITFVAL(MC13783_BATT_DAC_FETCTRL_EN, 1); + mask = BITFMASK(MC13783_BATT_DAC_FETOVRD_EN) | + BITFMASK(MC13783_BATT_DAC_FETCTRL_EN); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function sets over voltage threshold. + * + * @param threshold value of over voltage threshold. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_set_threshold(int threshold) +{ + unsigned int val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + if (threshold > BAT_THRESHOLD_MAX) + return PMIC_PARAMETER_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_OVCTRL, threshold); + mask = BITFMASK(MC13783_BATT_DAC_OVCTRL); + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function controls charge LED. + * + * @param on If on is ture, LED will be turned on, + * or otherwise, LED will be turned off. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_led_control(bool on) +{ + unsigned val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_LED_EN, on); + mask = BITFMASK(MC13783_BATT_DAC_LED_EN); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets reverse supply mode. + * + * @param enable If enable is ture, reverse supply mode is enable, + * or otherwise, reverse supply mode is disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_reverse_supply(bool enable) +{ + unsigned val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_REVERSE_SUPPLY, enable); + mask = BITFMASK(MC13783_BATT_DAC_REVERSE_SUPPLY); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets unregulatored charging mode on main battery. + * + * @param enable If enable is ture, unregulated charging mode is + * enable, or otherwise, disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_unregulated(bool enable) +{ + unsigned val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_UNREGULATED, enable); + mask = BITFMASK(MC13783_BATT_DAC_UNREGULATED); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets a 5K pull down at CHRGRAW. + * To be used in the dual path charging configuration. + * + * @param enable If enable is true, 5k pull down is + * enable, or otherwise, disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_5k_pull(bool enable) +{ + unsigned val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_5K, enable); + mask = BITFMASK(MC13783_BATT_DAC_5K); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to un/subscribe on battery event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS mc13783_battery_event(t_batt_event event, void *callback, bool sub) +{ + pmic_event_callback_t bat_callback; + type_event bat_event; + + bat_callback.func = callback; + bat_callback.param = NULL; + switch (event) { + case BAT_IT_CHG_DET: + bat_event = EVENT_CHGDETI; + break; + case BAT_IT_CHG_OVERVOLT: + bat_event = EVENT_CHGOVI; + break; + case BAT_IT_CHG_REVERSE: + bat_event = EVENT_CHGREVI; + break; + case BAT_IT_CHG_SHORT_CIRCUIT: + bat_event = EVENT_CHGSHORTI; + break; + case BAT_IT_CCCV: + bat_event = EVENT_CCCVI; + break; + case BAT_IT_BELOW_THRESHOLD: + bat_event = EVENT_CHRGCURRI; + break; + default: + return PMIC_PARAMETER_ERROR; + } + if (sub == true) { + CHECK_ERROR(pmic_event_subscribe(bat_event, bat_callback)); + } else { + CHECK_ERROR(pmic_event_unsubscribe(bat_event, bat_callback)); + } + return 0; +} + +/*! + * This function is used to subscribe on battery event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_event_subscribe(t_batt_event event, void *callback) +{ + if (suspend_flag == 1) + return PMIC_ERROR; + + return mc13783_battery_event(event, callback, true); +} + +/*! + * This function is used to un subscribe on battery event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_event_unsubscribe(t_batt_event event, void *callback) +{ + if (suspend_flag == 1) + return PMIC_ERROR; + + return mc13783_battery_event(event, callback, false); +} + +/*! + * This function implements IOCTL controls on a PMIC Battery device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_battery_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + t_charger_setting *chgr_setting = NULL; + unsigned short c_current; + unsigned int bc_info; + t_eol_setting *eol_setting; + + if (_IOC_TYPE(cmd) != 'p') + return -ENOTTY; + + switch (cmd) { + case PMIC_BATT_CHARGER_CONTROL: + if ((chgr_setting = kmalloc(sizeof(t_charger_setting), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(chgr_setting, (t_charger_setting *) arg, + sizeof(t_charger_setting))) { + kfree(chgr_setting); + return -EFAULT; + } + + if (chgr_setting->on != false) { + CHECK_ERROR_KFREE(pmic_batt_enable_charger + (chgr_setting->chgr, + chgr_setting->c_voltage, + chgr_setting->c_current), + (kfree(chgr_setting))); + } else { + CHECK_ERROR(pmic_batt_disable_charger + (chgr_setting->chgr)); + } + + kfree(chgr_setting); + break; + + case PMIC_BATT_SET_CHARGER: + if ((chgr_setting = kmalloc(sizeof(t_charger_setting), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(chgr_setting, (t_charger_setting *) arg, + sizeof(t_charger_setting))) { + kfree(chgr_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_batt_set_charger(chgr_setting->chgr, + chgr_setting->c_voltage, + chgr_setting-> + c_current), + (kfree(chgr_setting))); + + kfree(chgr_setting); + break; + + case PMIC_BATT_GET_CHARGER: + if ((chgr_setting = kmalloc(sizeof(t_charger_setting), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(chgr_setting, (t_charger_setting *) arg, + sizeof(t_charger_setting))) { + kfree(chgr_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_batt_get_charger_setting + (chgr_setting->chgr, &chgr_setting->c_voltage, + &chgr_setting->c_current), + (kfree(chgr_setting))); + if (copy_to_user + ((t_charger_setting *) arg, chgr_setting, + sizeof(t_charger_setting))) { + return -EFAULT; + } + + kfree(chgr_setting); + break; + + case PMIC_BATT_GET_CHARGER_SENSOR: + { + t_sensor_bits sensor; + pmic_get_sensors(&sensor); + if (copy_to_user + ((unsigned int *)arg, &sensor.sense_chgdets, + sizeof(unsigned int))) + return -EFAULT; + + break; + } + case PMIC_BATT_GET_BATTERY_VOLTAGE: + CHECK_ERROR(pmic_batt_get_batt_voltage(&c_current)); + bc_info = (unsigned int)c_current * 2300 / 1023 + 2400; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + + break; + + case PMIC_BATT_GET_BATTERY_CURRENT: + CHECK_ERROR(pmic_batt_get_batt_current(&c_current)); + bc_info = (unsigned int)c_current * 5750 / 1023; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + break; + + case PMIC_BATT_GET_BATTERY_TEMPERATURE: + CHECK_ERROR(pmic_batt_get_batt_temperature(&c_current)); + bc_info = (unsigned int)c_current; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + + break; + + case PMIC_BATT_GET_CHARGER_VOLTAGE: + CHECK_ERROR(pmic_batt_get_charge_voltage(&c_current)); + bc_info = (unsigned int)c_current * 23000 / 1023; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + + break; + + case PMIC_BATT_GET_CHARGER_CURRENT: + CHECK_ERROR(pmic_batt_get_charge_current(&c_current)); + bc_info = (unsigned int)c_current * 5750 / 1023; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + + break; + + case PMIC_BATT_EOL_CONTROL: + if ((eol_setting = kmalloc(sizeof(t_eol_setting), GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + if (copy_from_user(eol_setting, (t_eol_setting *) arg, + sizeof(t_eol_setting))) { + kfree(eol_setting); + return -EFAULT; + } + + if (eol_setting->enable != false) { + CHECK_ERROR_KFREE(pmic_batt_bp_enable_eol + (eol_setting->typical), + (kfree(chgr_setting))); + } else { + CHECK_ERROR_KFREE(pmic_batt_disable_eol(), + (kfree(chgr_setting))); + } + + kfree(eol_setting); + break; + + case PMIC_BATT_SET_OUT_CONTROL: + CHECK_ERROR(pmic_batt_set_out_control((t_control) arg)); + break; + + case PMIC_BATT_SET_THRESHOLD: + CHECK_ERROR(pmic_batt_set_threshold((int)arg)); + break; + + case PMIC_BATT_LED_CONTROL: + CHECK_ERROR(pmic_batt_led_control((bool) arg)); + break; + + case PMIC_BATT_REV_SUPP_CONTROL: + CHECK_ERROR(pmic_batt_set_reverse_supply((bool) arg)); + break; + + case PMIC_BATT_UNREG_CONTROL: + CHECK_ERROR(pmic_batt_set_unregulated((bool) arg)); + break; + + default: + return -EINVAL; + } + return 0; +} + +/*! + * This function implements the open method on a Pmic battery device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_battery_open(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + + /* check open count, if open firstly, register callbacks */ + down(&count_mutex); + if (open_count++ > 0) { + up(&count_mutex); + return 0; + } + + pr_debug("Subscribe the callbacks\n"); + /* register battery event callback */ + if (pmic_batt_event_subscribe(BAT_IT_CHG_DET, callback_chg_detect)) { + pr_debug("Failed to subscribe the charger detect callback\n"); + goto event_err1; + } + if (pmic_power_event_sub(PWR_IT_LOBATLI, callback_power_fail)) { + pr_debug("Failed to subscribe the power failed callback\n"); + goto event_err2; + } + if (pmic_power_event_sub(PWR_IT_LOBATHI, callback_low_battery)) { + pr_debug("Failed to subscribe the low battery callback\n"); + goto event_err3; + } + if (pmic_batt_event_subscribe + (BAT_IT_CHG_OVERVOLT, callback_chg_overvoltage)) { + pr_debug("Failed to subscribe the low battery callback\n"); + goto event_err4; + } + if (pmic_batt_event_subscribe + (BAT_IT_BELOW_THRESHOLD, callback_chg_full)) { + pr_debug("Failed to subscribe the charge full callback\n"); + goto event_err5; + } + + up(&count_mutex); + + return 0; + + /* un-subscribe the event callbacks */ +event_err5: + pmic_batt_event_unsubscribe(BAT_IT_CHG_OVERVOLT, + callback_chg_overvoltage); +event_err4: + pmic_power_event_unsub(PWR_IT_LOBATHI, callback_low_battery); +event_err3: + pmic_power_event_unsub(PWR_IT_LOBATLI, callback_power_fail); +event_err2: + pmic_batt_event_unsubscribe(BAT_IT_CHG_DET, callback_chg_detect); +event_err1: + + open_count--; + up(&count_mutex); + + return -EFAULT; + +} + +/*! + * This function implements the release method on a Pmic battery device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_battery_release(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + + /* check open count, if open firstly, register callbacks */ + down(&count_mutex); + if (--open_count == 0) { + /* unregister these event callback */ + pr_debug("Unsubscribe the callbacks\n"); + pmic_batt_event_unsubscribe(BAT_IT_BELOW_THRESHOLD, + callback_chg_full); + pmic_batt_event_unsubscribe(BAT_IT_CHG_OVERVOLT, + callback_chg_overvoltage); + pmic_power_event_unsub(PWR_IT_LOBATHI, callback_low_battery); + pmic_power_event_unsub(PWR_IT_LOBATLI, callback_power_fail); + pmic_batt_event_unsubscribe(BAT_IT_CHG_DET, + callback_chg_detect); + } + up(&count_mutex); + + return 0; +} + +static struct file_operations pmic_battery_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_battery_ioctl, + .open = pmic_battery_open, + .release = pmic_battery_release, +}; + +static int pmic_battery_remove(struct platform_device *pdev) +{ + device_destroy(pmic_battery_class, MKDEV(pmic_battery_major, 0)); + class_destroy(pmic_battery_class); + unregister_chrdev(pmic_battery_major, PMIC_BATTERY_STRING); + return 0; +} + +static int pmic_battery_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *temp_class; + + pmic_battery_major = register_chrdev(0, PMIC_BATTERY_STRING, + &pmic_battery_fops); + + if (pmic_battery_major < 0) { + printk(KERN_ERR "Unable to get a major for pmic_battery\n"); + return pmic_battery_major; + } + init_waitqueue_head(&suspendq); + + pmic_battery_class = class_create(THIS_MODULE, PMIC_BATTERY_STRING); + if (IS_ERR(pmic_battery_class)) { + printk(KERN_ERR "Error creating PMIC battery class.\n"); + ret = PTR_ERR(pmic_battery_class); + goto err_out1; + } + + temp_class = device_create(pmic_battery_class, NULL, + MKDEV(pmic_battery_major, 0), NULL, + PMIC_BATTERY_STRING); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating PMIC battery class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + pmic_batt_led_control(true); + pmic_batt_set_5k_pull(true); + + printk(KERN_INFO "PMIC Battery successfully probed\n"); + + return ret; + + err_out2: + class_destroy(pmic_battery_class); + err_out1: + unregister_chrdev(pmic_battery_major, PMIC_BATTERY_STRING); + return ret; +} + +static struct platform_driver pmic_battery_driver_ldm = { + .driver = { + .name = "pmic_battery", + .bus = &platform_bus_type, + }, + .suspend = pmic_battery_suspend, + .resume = pmic_battery_resume, + .probe = pmic_battery_probe, + .remove = pmic_battery_remove, +}; + +/* + * Init and Exit + */ + +static int __init pmic_battery_init(void) +{ + pr_debug("PMIC Battery driver loading...\n"); + return platform_driver_register(&pmic_battery_driver_ldm); +} + +static void __exit pmic_battery_exit(void) +{ + platform_driver_unregister(&pmic_battery_driver_ldm); + pr_debug("PMIC Battery driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(pmic_battery_init); +module_exit(pmic_battery_exit); + +MODULE_DESCRIPTION("pmic_battery driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_battery_defs.h b/drivers/mxc/pmic/mc13783/pmic_battery_defs.h new file mode 100644 index 000000000000..9f66a185cd2f --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_battery_defs.h @@ -0,0 +1,81 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_battery_defs.h + * @brief This is the internal header for PMIC(mc13783) Battery driver. + * + * @ingroup PMIC_BATTERY + */ + +#ifndef __PMIC_BATTERY_DEFS_H__ +#define __PMIC_BATTERY_DEFS_H__ + +#define PMIC_BATTERY_STRING "pmic_battery" + +/* REG_CHARGE */ +#define MC13783_BATT_DAC_V_DAC_LSH 0 +#define MC13783_BATT_DAC_V_DAC_WID 3 +#define MC13783_BATT_DAC_DAC_LSH 3 +#define MC13783_BATT_DAC_DAC_WID 4 +#define MC13783_BATT_DAC_TRCKLE_LSH 7 +#define MC13783_BATT_DAC_TRCKLE_WID 3 +#define MC13783_BATT_DAC_FETOVRD_EN_LSH 10 +#define MC13783_BATT_DAC_FETOVRD_EN_WID 1 +#define MC13783_BATT_DAC_FETCTRL_EN_LSH 11 +#define MC13783_BATT_DAC_FETCTRL_EN_WID 1 +#define MC13783_BATT_DAC_REVERSE_SUPPLY_LSH 13 +#define MC13783_BATT_DAC_REVERSE_SUPPLY_WID 1 +#define MC13783_BATT_DAC_OVCTRL_LSH 15 +#define MC13783_BATT_DAC_OVCTRL_WID 2 +#define MC13783_BATT_DAC_UNREGULATED_LSH 17 +#define MC13783_BATT_DAC_UNREGULATED_WID 1 +#define MC13783_BATT_DAC_LED_EN_LSH 18 +#define MC13783_BATT_DAC_LED_EN_WID 1 +#define MC13783_BATT_DAC_5K_LSH 19 +#define MC13783_BATT_DAC_5K_WID 1 + +#define BITS_OUT_VOLTAGE 0 +#define LONG_OUT_VOLTAGE 3 +#define BITS_CURRENT_MAIN 3 +#define LONG_CURRENT_MAIN 4 +#define BITS_CURRENT_TRICKLE 7 +#define LONG_CURRENT_TRICKLE 3 +#define BIT_FETOVRD 10 +#define BIT_FETCTRL 11 +#define BIT_RVRSMODE 13 +#define BITS_OVERVOLTAGE 15 +#define LONG_OVERVOLTAGE 2 +#define BIT_UNREGULATED 17 +#define BIT_CHRG_LED 18 +#define BIT_CHRGRAWPDEN 19 + +/* REG_POWXER_CONTROL_0 */ +#define MC13783_BATT_DAC_V_COIN_LSH 20 +#define MC13783_BATT_DAC_V_COIN_WID 3 +#define MC13783_BATT_DAC_COIN_CH_EN_LSH 23 +#define MC13783_BATT_DAC_COIN_CH_EN_WID 1 +#define MC13783_BATT_DAC_COIN_CH_EN_ENABLED 1 +#define MC13783_BATT_DAC_COIN_CH_EN_DISABLED 0 +#define MC13783_BATT_DAC_EOL_CMP_EN_LSH 18 +#define MC13783_BATT_DAC_EOL_CMP_EN_WID 1 +#define MC13783_BATT_DAC_EOL_CMP_EN_ENABLE 1 +#define MC13783_BATT_DAC_EOL_CMP_EN_DISABLE 0 +#define MC13783_BATT_DAC_EOL_SEL_LSH 16 +#define MC13783_BATT_DAC_EOL_SEL_WID 2 + +#define DEF_VALUE 0 + +#define BAT_THRESHOLD_MAX 3 + +#endif /* __PMIC_BATTERY_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_convity.c b/drivers/mxc/pmic/mc13783/pmic_convity.c new file mode 100644 index 000000000000..5d634ebebacf --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_convity.c @@ -0,0 +1,2482 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_convity.c + * @brief Implementation of the PMIC Connectivity driver APIs. + * + * The PMIC connectivity device driver and this API were developed to support + * the external connectivity capabilities of several power management ICs that + * are available from Freescale Semiconductor, Inc. + * + * The following operating modes, in terms of external connectivity, are + * supported: + * + * @verbatim + Operating Mode mc13783 + --------------- ------- + USB (incl. OTG) Yes + RS-232 Yes + CEA-936 Yes + + @endverbatim + * + * @ingroup PMIC_CONNECTIVITY + */ + +#include <linux/interrupt.h> /* For tasklet interface. */ +#include <linux/platform_device.h> /* For kernel module interface. */ +#include <linux/spinlock.h> /* For spinlock interface. */ +#include <linux/pmic_adc.h> /* For PMIC ADC driver interface. */ +#include <linux/pmic_status.h> +#include <mach/pmic_convity.h> /* For PMIC Connectivity driver interface. */ + +/* + * mc13783 Connectivity API + */ +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_convity_open); +EXPORT_SYMBOL(pmic_convity_close); +EXPORT_SYMBOL(pmic_convity_set_mode); +EXPORT_SYMBOL(pmic_convity_get_mode); +EXPORT_SYMBOL(pmic_convity_reset); +EXPORT_SYMBOL(pmic_convity_set_callback); +EXPORT_SYMBOL(pmic_convity_clear_callback); +EXPORT_SYMBOL(pmic_convity_get_callback); +EXPORT_SYMBOL(pmic_convity_usb_set_speed); +EXPORT_SYMBOL(pmic_convity_usb_get_speed); +EXPORT_SYMBOL(pmic_convity_usb_set_power_source); +EXPORT_SYMBOL(pmic_convity_usb_get_power_source); +EXPORT_SYMBOL(pmic_convity_usb_set_xcvr); +EXPORT_SYMBOL(pmic_convity_usb_get_xcvr); +EXPORT_SYMBOL(pmic_convity_usb_otg_set_dlp_duration); +EXPORT_SYMBOL(pmic_convity_usb_otg_get_dlp_duration); +EXPORT_SYMBOL(pmic_convity_usb_otg_set_config); +EXPORT_SYMBOL(pmic_convity_usb_otg_clear_config); +EXPORT_SYMBOL(pmic_convity_usb_otg_get_config); +EXPORT_SYMBOL(pmic_convity_set_output); +EXPORT_SYMBOL(pmic_convity_rs232_set_config); +EXPORT_SYMBOL(pmic_convity_rs232_get_config); +EXPORT_SYMBOL(pmic_convity_cea936_exit_signal); + +/*! @def SET_BITS + * Set a register field to a given value. + */ + +#define SET_BITS(reg, field, value) (((value) << reg.field.offset) & \ + reg.field.mask) + +/*! @def GET_BITS + * Get the current value of a given register field. + */ +#define GET_BITS(reg, value) (((value) & reg.mask) >> \ + reg.offset) + +/*! + * @brief Define the possible states for a device handle. + * + * This enumeration is used to track the current state of each device handle. + */ +typedef enum { + HANDLE_FREE, /*!< Handle is available for use. */ + HANDLE_IN_USE /*!< Handle is currently in use. */ +} HANDLE_STATE; + +/* + * This structure is used to define a specific hardware register field. + * + * All hardware register fields are defined using an offset to the LSB + * and a mask. The offset is used to right shift a register value before + * applying the mask to actually obtain the value of the field. + */ +typedef struct { + const unsigned char offset; /* Offset of LSB of register field. */ + const unsigned int mask; /* Mask value used to isolate register field. */ +} REGFIELD; + +/*! + * @brief This structure is used to identify the fields in the USBCNTRL_REG_0 hardware register. + * + * This structure lists all of the fields within the USBCNTRL_REG_0 hardware + * register. + */ +typedef struct { + REGFIELD FSENB; /*!< USB Full Speed Enable */ + REGFIELD USB_SUSPEND; /*!< USB Suspend Mode Enable */ + REGFIELD USB_PU; /*!< USB Pullup Enable */ + REGFIELD UDP_PD; /*!< USB Data Plus Pulldown Enable */ + REGFIELD UDM_PD; /*!< USB 150K UDP Pullup Enable */ + REGFIELD DP150K_PU; /*!< USB Pullup/Pulldown Override Enable */ + REGFIELD VBUSPDENB; /*!< USB VBUS Pulldown NMOS Switch Enable */ + REGFIELD CURRENT_LIMIT; /*!< USB Regulator Current Limit Setting-3 bits */ + REGFIELD DLP_SRP; /*!< USB Data Line Pulsing Timer Enable */ + REGFIELD SE0_CONN; /*!< USB Pullup Connect When SE0 Detected */ + REGFIELD USBXCVREN; /*!< USB Transceiver Enabled When INTERFACE_MODE[2:0]=000 and RESETB=high */ + REGFIELD PULLOVR; /*!< 1K5 Pullup and UDP/UDM Pulldown Disable When UTXENB=Low */ + REGFIELD INTERFACE_MODE; /*!< Connectivity Interface Mode Select-3 Bits */ + REGFIELD DATSE0; /*!< USB Single or Differential Mode Select */ + REGFIELD BIDIR; /*!< USB Unidirectional/Bidirectional Transmission */ + REGFIELD USBCNTRL; /*!< USB Mode of Operation controlled By USBEN/SPI Pin */ + REGFIELD IDPD; /*!< USB UID Pulldown Enable */ + REGFIELD IDPULSE; /*!< USB Pulse to Gnd on UID Line Generated */ + REGFIELD IDPUCNTRL; /*!< USB UID Pin pulled high By 5ua Curr Source */ + REGFIELD DMPULSE; /*!< USB Positive pulse on the UDM Line Generated */ +} USBCNTRL_REG_0; + +/*! + * @brief This variable is used to access the USBCNTRL_REG_0 hardware register. + * + * This variable defines how to access all of the fields within the + * USBCNTRL_REG_0 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const USBCNTRL_REG_0 regUSB0 = { + {0, 0x000001}, /*!< FSENB */ + {1, 0x000002}, /*!< USB_SUSPEND */ + {2, 0x000004}, /*!< USB_PU */ + {3, 0x000008}, /*!< UDP_PD */ + {4, 0x000010}, /*!< UDM_PD */ + {5, 0x000020}, /*!< DP150K_PU */ + {6, 0x000040}, /*!< VBUSPDENB */ + {7, 0x000380}, /*!< CURRENT_LIMIT */ + {10, 0x000400}, /*!< DLP_SRP */ + {11, 0x000800}, /*!< SE0_CONN */ + {12, 0x001000}, /*!< USBXCVREN */ + {13, 0x002000}, /*!< PULLOVR */ + {14, 0x01c000}, /*!< INTERFACE_MODE */ + {17, 0x020000}, /*!< DATSE0 */ + {18, 0x040000}, /*!< BIDIR */ + {19, 0x080000}, /*!< USBCNTRL */ + {20, 0x100000}, /*!< IDPD */ + {21, 0x200000}, /*!< IDPULSE */ + {22, 0x400000}, /*!< IDPUCNTRL */ + {23, 0x800000} /*!< DMPULSE */ + +}; + +/*! + * @brief This structure is used to identify the fields in the USBCNTRL_REG_1 hardware register. + * + * This structure lists all of the fields within the USBCNTRL_REG_1 hardware + * register. + */ +typedef struct { + REGFIELD VUSBIN; /*!< Controls The Input Source For VUSB */ + REGFIELD VUSB; /*!< VUSB Output Voltage Select-High=3.3V Low=2.775V */ + REGFIELD VUSBEN; /*!< VUSB Output Enable- */ + REGFIELD VBUSEN; /*!< VBUS Output Enable- */ + REGFIELD RSPOL; /*!< Low=RS232 TX on UDM, RX on UDP + High= RS232 TX on UDP, RX on UDM */ + REGFIELD RSTRI; /*!< TX Forced To Tristate in RS232 Mode Only */ + REGFIELD ID100kPU; /*!< 100k UID Pullup Enabled */ +} USBCNTRL_REG_1; + +/*! + * @brief This variable is used to access the USBCNTRL_REG_1 hardware register. + * + * This variable defines how to access all of the fields within the + * USBCNTRL_REG_1 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const USBCNTRL_REG_1 regUSB1 = { + {0, 0x000003}, /*!< VUSBIN-2 Bits */ + {2, 0x000004}, /*!< VUSB */ + {3, 0x000008}, /*!< VUSBEN */ + /*{4, 0x000010} *//*!< Reserved */ + {5, 0x000020}, /*!< VBUSEN */ + {6, 0x000040}, /*!< RSPOL */ + {7, 0x000080}, /*!< RSTRI */ + {8, 0x000100} /*!< ID100kPU */ + /*!< 9-23 Unused */ +}; + +/*! Define a mask to access the entire hardware register. */ +static const unsigned int REG_FULLMASK = 0xffffff; + +/*! Define the mc13783 USBCNTRL_REG_0 register power on reset state. */ +static const unsigned int RESET_USBCNTRL_REG_0 = 0x080060; + +/*! Define the mc13783 USBCNTRL_REG_1 register power on reset state. */ +static const unsigned int RESET_USBCNTRL_REG_1 = 0x000006; + +static pmic_event_callback_t eventNotify; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the connectivity driver. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + PMIC_CONVITY_USB_SPEED usbSpeed; /*!< USB connection + speed. */ + PMIC_CONVITY_USB_MODE usbMode; /*!< USB connection + mode. */ + PMIC_CONVITY_USB_POWER_IN usbPowerIn; /*!< USB transceiver + power source. */ + PMIC_CONVITY_USB_POWER_OUT usbPowerOut; /*!< USB transceiver + power output + level. */ + PMIC_CONVITY_USB_TRANSCEIVER_MODE usbXcvrMode; /*!< USB transceiver + mode. */ + unsigned int usbDlpDuration; /*!< USB Data Line + Pulsing duration. */ + PMIC_CONVITY_USB_OTG_CONFIG usbOtgCfg; /*!< USB OTG + configuration + options. */ + PMIC_CONVITY_RS232_INTERNAL rs232CfgInternal; /*!< RS-232 internal + connections. */ + PMIC_CONVITY_RS232_EXTERNAL rs232CfgExternal; /*!< RS-232 external + connections. */ +} pmic_convity_state_struct; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the driver in USB mode. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + PMIC_CONVITY_USB_SPEED usbSpeed; /*!< USB connection + speed. */ + PMIC_CONVITY_USB_MODE usbMode; /*!< USB connection + mode. */ + PMIC_CONVITY_USB_POWER_IN usbPowerIn; /*!< USB transceiver + power source. */ + PMIC_CONVITY_USB_POWER_OUT usbPowerOut; /*!< USB transceiver + power output + level. */ + PMIC_CONVITY_USB_TRANSCEIVER_MODE usbXcvrMode; /*!< USB transceiver + mode. */ + unsigned int usbDlpDuration; /*!< USB Data Line + Pulsing duration. */ + PMIC_CONVITY_USB_OTG_CONFIG usbOtgCfg; /*!< USB OTG + configuration + options. */ +} pmic_convity_usb_state; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the driver in RS_232 mode. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + PMIC_CONVITY_RS232_INTERNAL rs232CfgInternal; /*!< RS-232 internal + connections. */ + PMIC_CONVITY_RS232_EXTERNAL rs232CfgExternal; /*!< RS-232 external + connections. */ +} pmic_convity_rs232_state; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the driver in cea-936 mode. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + +} pmic_convity_cea936_state; + +/*! + * @brief Identifies the hardware interrupt source. + * + * This enumeration identifies which of the possible hardware interrupt + * sources actually caused the current interrupt handler to be called. + */ +typedef enum { + CORE_EVENT_4V4 = 1, /*!< Detected USB 4.4 V event. */ + CORE_EVENT_2V0 = 2, /*!< Detected USB 2.0 V event. */ + CORE_EVENT_0V8 = 4, /*!< Detected USB 0.8 V event. */ + CORE_EVENT_ABDET = 8 /*!< Detected USB mini A-B connector event. */ +} PMIC_CORE_EVENT; + +/*! + * @brief This structure defines the reset/power on state for the Connectivity driver. + */ +static const pmic_convity_state_struct reset = { + 0, + HANDLE_FREE, + USB, + NULL, + 0, + USB_FULL_SPEED, + USB_PERIPHERAL, + USB_POWER_INTERNAL, + USB_POWER_3V3, + USB_TRANSCEIVER_OFF, + 0, + USB_PULL_OVERRIDE | USB_VBUS_CURRENT_LIMIT_HIGH, + RS232_TX_USE0VM_RX_UDATVP, + RS232_TX_UDM_RX_UDP +}; + +/*! + * @brief This structure maintains the current state of the Connectivity driver. + * + * The initial values must be identical to the reset state defined by the + * #reset variable. + */ +static pmic_convity_usb_state usb = { + 0, + HANDLE_FREE, + USB, + NULL, + 0, + USB_FULL_SPEED, + USB_PERIPHERAL, + USB_POWER_INTERNAL, + USB_POWER_3V3, + USB_TRANSCEIVER_OFF, + 0, + USB_PULL_OVERRIDE | USB_VBUS_CURRENT_LIMIT_HIGH, +}; + +/*! + * @brief This structure maintains the current state of the Connectivity driver. + * + * The initial values must be identical to the reset state defined by the + * #reset variable. + */ +static pmic_convity_rs232_state rs_232 = { + 0, + HANDLE_FREE, + RS232_1, + NULL, + 0, + RS232_TX_USE0VM_RX_UDATVP, + RS232_TX_UDM_RX_UDP +}; + +/*! + * @brief This structure maintains the current state of the Connectivity driver. + * + * The initial values must be identical to the reset state defined by the + * #reset variable. + */ +static pmic_convity_cea936_state cea_936 = { + 0, + HANDLE_FREE, + CEA936_MONO, + NULL, + 0, +}; + +/*! + * @brief This spinlock is used to provide mutual exclusion. + * + * Create a spinlock that can be used to provide mutually exclusive + * read/write access to the globally accessible "convity" data structure + * that was defined above. Mutually exclusive access is required to + * ensure that the convity data structure is consistent at all times + * when possibly accessed by multiple threads of execution (for example, + * while simultaneously handling a user request and an interrupt event). + * + * We need to use a spinlock sometimes because we need to provide mutual + * exclusion while handling a hardware interrupt. + */ +static spinlock_t lock = SPIN_LOCK_UNLOCKED; + +/*! + * @brief This mutex is used to provide mutual exclusion. + * + * Create a mutex that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * that were defined above. Mutually exclusive access is required to + * ensure that the Connectivity data structures are consistent at all + * times when possibly accessed by multiple threads of execution. + * + * Note that we use a mutex instead of the spinlock whenever disabling + * interrupts while in the critical section is not required. This helps + * to minimize kernel interrupt handling latency. + */ +static DECLARE_MUTEX(mutex); + +/* Prototype for the connectivity driver tasklet function. */ +static void pmic_convity_tasklet(struct work_struct *work); + +/*! + * @brief Tasklet handler for the connectivity driver. + * + * Declare a tasklet that will do most of the processing for all of the + * connectivity-related interrupt events (USB4.4VI, USB2.0VI, USB0.8VI, + * and AB_DETI). Note that we cannot do all of the required processing + * within the interrupt handler itself because we may need to call the + * ADC driver to measure voltages as well as calling any user-registered + * callback functions. + */ +DECLARE_WORK(convityTasklet, pmic_convity_tasklet); + +/*! + * @brief Global variable to track currently active interrupt events. + * + * This global variable is used to keep track of all of the currently + * active interrupt events for the connectivity driver. Note that access + * to this variable may occur while within an interrupt context and, + * therefore, must be guarded by using a spinlock. + */ +static PMIC_CORE_EVENT eventID = 0; + +/* Prototypes for all static connectivity driver functions. */ +static PMIC_STATUS pmic_convity_set_mode_internal(const PMIC_CONVITY_MODE mode); +static PMIC_STATUS pmic_convity_deregister_all(void); +static void pmic_convity_event_handler(void *param); + +/************************************************************************** + * General setup and configuration functions. + ************************************************************************** + */ + +/*! + * @name General Setup and Configuration Connectivity APIs + * Functions for setting up and configuring the connectivity hardware. + */ +/*@{*/ + +/*! + * Attempt to open and gain exclusive access to the PMIC connectivity + * hardware. An initial operating mode must also be specified. + * + * If the open request is successful, then a numeric handle is returned + * and this handle must be used in all subsequent function calls. The + * same handle must also be used in the pmic_convity_close() call when use + * of the PMIC connectivity hardware is no longer required. + * + * @param handle device handle from open() call + * @param mode initial connectivity operating mode + * + * @return PMIC_SUCCESS if the open request was successful + */ +PMIC_STATUS pmic_convity_open(PMIC_CONVITY_HANDLE * const handle, + const PMIC_CONVITY_MODE mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + if (handle == (PMIC_CONVITY_HANDLE *) NULL) { + /* Do not dereference a NULL pointer. */ + return PMIC_ERROR; + } + + /* We only need to acquire a mutex here because the interrupt handler + * never modifies the device handle or device handle state. Therefore, + * we don't need to worry about conflicts with the interrupt handler + * or the need to execute in an interrupt context. + * + * But we do need a critical section here to avoid problems in case + * multiple calls to pmic_convity_open() are made since we can only + * allow one of them to succeed. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Check the current device handle state and acquire the handle if + * it is available. + */ + if ((usb.handle_state != HANDLE_FREE) + && (rs_232.handle_state != HANDLE_FREE) + && (cea_936.handle_state != HANDLE_FREE)) { + + /* Cannot open the PMIC connectivity hardware at this time or an invalid + * mode was requested. + */ + *handle = reset.handle; + } else { + + if (mode == USB) { + usb.handle = (PMIC_CONVITY_HANDLE) (&usb); + usb.handle_state = HANDLE_IN_USE; + } else if ((mode == RS232_1) || (mode == RS232_2)) { + rs_232.handle = (PMIC_CONVITY_HANDLE) (&rs_232); + rs_232.handle_state = HANDLE_IN_USE; + } else if ((mode == CEA936_STEREO) || (mode == CEA936_MONO) + || (mode == CEA936_TEST_LEFT) + || (mode == CEA936_TEST_RIGHT)) { + cea_936.handle = (PMIC_CONVITY_HANDLE) (&cea_936); + cea_936.handle_state = HANDLE_IN_USE; + + } + /* Let's begin by acquiring the connectivity device handle. */ + /* Then we can try to set the desired operating mode. */ + rc = pmic_convity_set_mode_internal(mode); + + if (rc == PMIC_SUCCESS) { + /* Successfully set the desired operating mode, now return the + * handle to the caller. + */ + if (mode == USB) { + *handle = usb.handle; + } else if ((mode == RS232_1) || (mode == RS232_2)) { + *handle = rs_232.handle; + } else if ((mode == CEA936_STEREO) + || (mode == CEA936_MONO) + || (mode == CEA936_TEST_LEFT) + || (mode == CEA936_TEST_RIGHT)) { + *handle = cea_936.handle; + } + } else { + /* Failed to set the desired mode, return the handle to an unused + * state. + */ + if (mode == USB) { + usb.handle = reset.handle; + usb.handle_state = reset.handle_state; + } else if ((mode == RS232_1) || (mode == RS232_2)) { + rs_232.handle = reset.handle; + rs_232.handle_state = reset.handle_state; + } else if ((mode == CEA936_STEREO) + || (mode == CEA936_MONO) + || (mode == CEA936_TEST_LEFT) + || (mode == CEA936_TEST_RIGHT)) { + cea_936.handle = reset.handle; + cea_936.handle_state = reset.handle_state; + } + + *handle = reset.handle; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Terminate further access to the PMIC connectivity hardware. Also allows + * another process to call pmic_convity_open() to gain access. + * + * @param handle device handle from open() call + * + * @return PMIC_SUCCESS if the close request was successful + */ +PMIC_STATUS pmic_convity_close(const PMIC_CONVITY_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Begin a critical section here to avoid the possibility of race + * conditions if multiple threads happen to call this function and + * pmic_convity_open() at the same time. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Confirm that the device handle matches the one assigned in the + * pmic_convity_open() call and then close the connection. + */ + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + rc = PMIC_SUCCESS; + + /* Deregister for all existing callbacks if necessary and make sure + * that the event handling settings are consistent following the + * close operation. + */ + if ((usb.callback != reset.callback) + || (rs_232.callback != reset.callback) + || (cea_936.callback != reset.callback)) { + /* Deregister the existing callback function and all registered + * events before we completely close the handle. + */ + rc = pmic_convity_deregister_all(); + if (rc == PMIC_SUCCESS) { + + } else if (usb.eventMask != reset.eventMask) { + /* Having a non-zero eventMask without a callback function being + * defined should never occur but let's just make sure here that + * we keep things consistent. + */ + usb.eventMask = reset.eventMask; + /* Mark the connectivity device handle as being closed. */ + usb.handle = reset.handle; + usb.handle_state = reset.handle_state; + + } else if (rs_232.eventMask != reset.eventMask) { + + rs_232.eventMask = reset.eventMask; + /* Mark the connectivity device handle as being closed. */ + rs_232.handle = reset.handle; + rs_232.handle_state = reset.handle_state; + + } else if (cea_936.eventMask != reset.eventMask) { + cea_936.eventMask = reset.eventMask; + /* Mark the connectivity device handle as being closed. */ + cea_936.handle = reset.handle; + cea_936.handle_state = reset.handle_state; + + } + + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Change the current operating mode of the PMIC connectivity hardware. + * The available connectivity operating modes is hardware dependent and + * consists of one or more of the following: USB (including USB On-the-Go), + * RS-232, and CEA-936. Requesting an operating mode that is not supported + * by the PMIC hardware will return PMIC_NOT_SUPPORTED. + * + * @param handle device handle from + open() call + * @param mode desired operating mode + * + * @return PMIC_SUCCESS if the requested mode was successfully set + */ +PMIC_STATUS pmic_convity_set_mode(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_MODE mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + rc = pmic_convity_set_mode_internal(mode); + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the current operating mode for the PMIC connectivity hardware. + * + * @param handle device handle from open() call + * @param mode the current PMIC connectivity operating mode + * + * @return PMIC_SUCCESS if the requested mode was successfully set + */ +PMIC_STATUS pmic_convity_get_mode(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_MODE * const mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232. + handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) + && (mode != (PMIC_CONVITY_MODE *) NULL)) { + + *mode = usb.mode; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Restore all registers to the initial power-on/reset state. + * + * @param handle device handle from open() call + * + * @return PMIC_SUCCESS if the reset was successful + */ +PMIC_STATUS pmic_convity_reset(const PMIC_CONVITY_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + + /* Reset the PMIC Connectivity register to it's power on state. */ + rc = pmic_write_reg(REG_USB, RESET_USBCNTRL_REG_0, + REG_FULLMASK); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + RESET_USBCNTRL_REG_1, REG_FULLMASK); + + if (rc == PMIC_SUCCESS) { + /* Also reset the device driver state data structure. */ + + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Register a callback function that will be used to signal PMIC connectivity + * events. For example, the USB subsystem should register a callback function + * in order to be notified of device connect/disconnect events. Note, however, + * that non-USB events may also be signalled depending upon the PMIC hardware + * capabilities. Therefore, the callback function must be able to properly + * handle all of the possible events if support for non-USB peripherals is + * also to be included. + * + * @param handle device handle from open() call + * @param func a pointer to the callback function + * @param eventMask a mask selecting events to be notified + * + * @return PMIC_SUCCESS if the callback was successful registered + */ +PMIC_STATUS pmic_convity_set_callback(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_CALLBACK func, + const PMIC_CONVITY_EVENTS eventMask) +{ + unsigned long flags; + PMIC_STATUS rc = PMIC_ERROR; + + /* We need to start a critical section here to ensure a consistent state + * in case simultaneous calls to pmic_convity_set_callback() are made. In + * that case, we must serialize the calls to ensure that the "callback" + * and "eventMask" state variables are always consistent. + * + * Note that we don't actually need to acquire the spinlock until later + * when we are finally ready to update the "callback" and "eventMask" + * state variables which are shared with the interrupt handler. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + /* Return an error if either the callback function or event mask + * is not properly defined. + * + * It is also considered an error if a callback function has already + * been defined. If you wish to register for a new set of events, + * then you must first call pmic_convity_clear_callback() to + * deregister the existing callback function and list of events + * before trying to register a new callback function. + */ + if ((func == NULL) || (eventMask == 0) + || (usb.callback != NULL)) { + rc = PMIC_ERROR; + + /* Register for PMIC events from the core protocol driver. */ + } else { + + if ((eventMask & USB_DETECT_4V4_RISE) || + (eventMask & USB_DETECT_4V4_FALL)) { + /* We need to register for the 4.4V interrupt. */ + //EVENT_USBI or EVENT_USB_44VI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_4V4); + rc = pmic_event_subscribe(EVENT_USBI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + return rc; + } + } + + if ((eventMask & USB_DETECT_2V0_RISE) || + (eventMask & USB_DETECT_2V0_FALL)) { + /* We need to register for the 2.0V interrupt. */ + //EVENT_USB_20VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_2V0); + rc = pmic_event_subscribe(EVENT_USBI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + goto Cleanup_4V4; + } + } + + if ((eventMask & USB_DETECT_0V8_RISE) || + (eventMask & USB_DETECT_0V8_FALL)) { + /* We need to register for the 0.8V interrupt. */ + //EVENT_USB_08VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_0V8); + rc = pmic_event_subscribe(EVENT_USBI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + goto Cleanup_2V0; + } + } + + if ((eventMask & USB_DETECT_MINI_A) || + (eventMask & USB_DETECT_MINI_B) + || (eventMask & USB_DETECT_NON_USB_ACCESSORY) + || (eventMask & USB_DETECT_FACTORY_MODE)) { + /* We need to register for the AB_DET interrupt. */ + //EVENT_AB_DETI or EVENT_IDI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_ABDET); + rc = pmic_event_subscribe(EVENT_IDI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + goto Cleanup_0V8; + } + } + + /* Use a critical section to maintain a consistent state. */ + spin_lock_irqsave(&lock, flags); + + /* Successfully registered for all events. */ + usb.callback = func; + usb.eventMask = eventMask; + spin_unlock_irqrestore(&lock, flags); + + goto End; + + /* This section unregisters any already registered events if we should + * encounter an error partway through the registration process. Note + * that we don't check the return status here since it is already set + * to PMIC_ERROR before we get here. + */ + Cleanup_0V8: + + if ((eventMask & USB_DETECT_0V8_RISE) || + (eventMask & USB_DETECT_0V8_FALL)) { + //EVENT_USB_08VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_0V8); + pmic_event_unsubscribe(EVENT_USBI, eventNotify); + goto End; + } + + Cleanup_2V0: + + if ((eventMask & USB_DETECT_2V0_RISE) || + (eventMask & USB_DETECT_2V0_FALL)) { + //EVENT_USB_20VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_2V0); + pmic_event_unsubscribe(EVENT_USBI, eventNotify); + goto End; + } + + Cleanup_4V4: + + if ((eventMask & USB_DETECT_4V4_RISE) || + (eventMask & USB_DETECT_4V4_FALL)) { + //EVENT_USB_44VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_4V4); + pmic_event_unsubscribe(EVENT_USBI, eventNotify); + } + } + /* Exit the critical section. */ + + } + End:up(&mutex); + return rc; + +} + +/*! + * Clears the current callback function. If this function returns successfully + * then all future Connectivity events will only be handled by the default + * handler within the Connectivity driver. + * + * @param handle device handle from open() call + * + * @return PMIC_SUCCESS if the callback was successful cleared + */ +PMIC_STATUS pmic_convity_clear_callback(const PMIC_CONVITY_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + + rc = pmic_convity_deregister_all(); + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the current callback function and event mask. + * + * @param handle device handle from open() call + * @param func the current callback function + * @param eventMask the current event selection mask + * + * @return PMIC_SUCCESS if the callback information was successful + * retrieved + */ +PMIC_STATUS pmic_convity_get_callback(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_CALLBACK * const func, + PMIC_CONVITY_EVENTS * const eventMask) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if ((((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232. + handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) + && (func != (PMIC_CONVITY_CALLBACK *) NULL) + && (eventMask != (PMIC_CONVITY_EVENTS *) NULL)) { + *func = usb.callback; + *eventMask = usb.eventMask; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + + up(&mutex); + + return rc; +} + +/*@*/ + +/************************************************************************** + * USB-specific configuration and setup functions. + ************************************************************************** + */ + +/*! + * @name USB and USB-OTG Connectivity APIs + * Functions for controlling USB and USB-OTG connectivity. + */ +/*@{*/ + +/*! + * Set the USB transceiver speed. + * + * @param handle device handle from open() call + * @param speed the desired USB transceiver speed + * + * @return PMIC_SUCCESS if the transceiver speed was successfully set + */ +PMIC_STATUS pmic_convity_usb_set_speed(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_SPEED speed) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = SET_BITS(regUSB0, FSENB, 1); + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (handle == (rs_232.handle || cea_936.handle)) { + return PMIC_PARAMETER_ERROR; + } else { + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) { + /* Validate the function parameters and if they are valid, then + * configure the pull-up and pull-down resistors as required for + * the desired operating mode. + */ + if ((speed == USB_HIGH_SPEED)) { + /* + * The USB transceiver also does not support the high speed mode + * (which is also optional under the USB OTG specification). + */ + rc = PMIC_NOT_SUPPORTED; + } else if ((speed != USB_LOW_SPEED) + && (speed != USB_FULL_SPEED)) { + /* Final validity check on the speed parameter. */ + rc = PMIC_ERROR;; + } else { + /* First configure the D+ and D- pull-up/pull-down resistors as + * per the USB OTG specification. + */ + if (speed == USB_FULL_SPEED) { + /* Activate pull-up on D+ and pull-down on D-. */ + reg_value = + SET_BITS(regUSB0, UDM_PD, 1); + } else if (speed == USB_LOW_SPEED) { + /* Activate pull-up on D+ and pull-down on D-. */ + reg_value = SET_BITS(regUSB0, FSENB, 1); + } + + /* Now set the desired USB transceiver speed. Note that + * USB_FULL_SPEED simply requires FSENB=0 (which it + * already is). + */ + + rc = pmic_write_reg(REG_USB, reg_value, + reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbSpeed = speed; + } + } + } + } + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the USB transceiver speed. + * + * @param handle device handle from open() call + * @param speed the current USB transceiver speed + * @param mode the current USB transceiver mode + * + * @return PMIC_SUCCESS if the transceiver speed was successfully + * obtained + */ +PMIC_STATUS pmic_convity_usb_get_speed(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_SPEED * const speed, + PMIC_CONVITY_USB_MODE * const mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (speed != (PMIC_CONVITY_USB_SPEED *) NULL) && + (mode != (PMIC_CONVITY_USB_MODE *) NULL)) { + *speed = usb.usbSpeed; + *mode = usb.usbMode; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * This function enables/disables VUSB and VBUS output. + * This API configures the VUSBEN and VBUSEN bits of USB register + * + * @param handle device handle from open() call + * @param out_type true, for VBUS + * false, for VUSB + * @param out if true, output is enabled + * if false, output is disabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_convity_set_output(const PMIC_CONVITY_HANDLE handle, + bool out_type, bool out) +{ + + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + if ((out_type == 0) && (out == 1)) { + + reg_value = SET_BITS(regUSB1, VUSBEN, 1); + reg_mask = SET_BITS(regUSB1, VUSBEN, 1); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } else if (out_type == 0 && out == 0) { + reg_mask = SET_BITS(regUSB1, VBUSEN, 1); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } else if (out_type == 1 && out == 1) { + + reg_value = SET_BITS(regUSB1, VBUSEN, 1); + reg_mask = SET_BITS(regUSB1, VBUSEN, 1); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } + + else if (out_type == 1 && out == 0) { + + reg_mask = SET_BITS(regUSB1, VBUSEN, 1); + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } + + /*else { + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } */ + } + + up(&mutex); + + return rc; +} + +/*! + * Set the USB transceiver's power supply configuration. + * + * @param handle device handle from open() call + * @param pwrin USB transceiver regulator input power source + * @param pwrout USB transceiver regulator output power level + * + * @return PMIC_SUCCESS if the USB transceiver's power supply + * configuration was successfully set + */ +PMIC_STATUS pmic_convity_usb_set_power_source(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_POWER_IN + pwrin, + const PMIC_CONVITY_USB_POWER_OUT + pwrout) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + /* SET_BITS(regUSB1, VUSBEN, 1) | SET_BITS(regUSB1, VBUSEN, + 1) | SET_BITS(regUSB1, + VUSBIN, + 2) | */ + // SET_BITS(regUSB1, VUSB, 1); +/* SET_BITS(regUSB1, VUSBIN, 1) | SET_BITS(regUSB1, VUSBEN, + 1) | SET_BITS(regUSB1, + VBUSEN, 1);*/ + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + if (pwrin == USB_POWER_INTERNAL_BOOST) { + reg_value |= SET_BITS(regUSB1, VUSBIN, 0); + reg_mask = SET_BITS(regUSB1, VUSBIN, 1); + } else if (pwrin == USB_POWER_VBUS) { + reg_value |= SET_BITS(regUSB1, VUSBIN, 1); + reg_mask = SET_BITS(regUSB1, VUSBIN, 1); + } + + else if (pwrin == USB_POWER_INTERNAL) { + reg_value |= SET_BITS(regUSB1, VUSBIN, 2); + reg_mask = SET_BITS(regUSB1, VUSBIN, 1); + } + + if (pwrout == USB_POWER_3V3) { + reg_value |= SET_BITS(regUSB1, VUSB, 1); + reg_mask |= SET_BITS(regUSB1, VUSB, 1); + } + + else if (pwrout == USB_POWER_2V775) { + reg_value |= SET_BITS(regUSB1, VUSB, 0); + reg_mask |= SET_BITS(regUSB1, VUSB, 1); + } + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbPowerIn = pwrin; + usb.usbPowerOut = pwrout; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the USB transceiver's current power supply configuration. + * + * @param handle device handle from open() call + * @param pwrin USB transceiver regulator input power source + * @param pwrout USB transceiver regulator output power level + * + * @return PMIC_SUCCESS if the USB transceiver's power supply + * configuration was successfully retrieved + */ +PMIC_STATUS pmic_convity_usb_get_power_source(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_POWER_IN * + const pwrin, + PMIC_CONVITY_USB_POWER_OUT * + const pwrout) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (pwrin != (PMIC_CONVITY_USB_POWER_IN *) NULL) && + (pwrout != (PMIC_CONVITY_USB_POWER_OUT *) NULL)) { + *pwrin = usb.usbPowerIn; + *pwrout = usb.usbPowerOut; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Set the USB transceiver's operating mode. + * + * @param handle device handle from open() call + * @param mode desired operating mode + * + * @return PMIC_SUCCESS if the USB transceiver's operating mode + * was successfully configured + */ +PMIC_STATUS pmic_convity_usb_set_xcvr(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_TRANSCEIVER_MODE + mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + if (mode == USB_TRANSCEIVER_OFF) { + reg_value = SET_BITS(regUSB0, USBXCVREN, 0); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + } + + if (mode == USB_SINGLE_ENDED_UNIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 1) | SET_BITS(regUSB0, + BIDIR, 0); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } else if (mode == USB_SINGLE_ENDED_BIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 1) | SET_BITS(regUSB0, + BIDIR, 1); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } else if (mode == USB_DIFFERENTIAL_UNIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 0) | SET_BITS(regUSB0, + BIDIR, 0); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } else if (mode == USB_DIFFERENTIAL_BIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 0) | SET_BITS(regUSB0, + BIDIR, 1); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } + + if (mode == USB_SUSPEND_ON) { + reg_value |= SET_BITS(regUSB0, USB_SUSPEND, 1); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + } else if (mode == USB_SUSPEND_OFF) { + reg_value |= SET_BITS(regUSB0, USB_SUSPEND, 0); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + } + + if (mode == USB_OTG_SRP_DLP_START) { + reg_value |= SET_BITS(regUSB0, USB_PU, 0); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + } else if (mode == USB_OTG_SRP_DLP_STOP) { + reg_value &= SET_BITS(regUSB0, USB_PU, 1); + reg_mask |= SET_BITS(regUSB0, USB_PU, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbXcvrMode = mode; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the USB transceiver's current operating mode. + * + * @param handle device handle from open() call + * @param mode current operating mode + * + * @return PMIC_SUCCESS if the USB transceiver's operating mode + * was successfully retrieved + */ +PMIC_STATUS pmic_convity_usb_get_xcvr(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_TRANSCEIVER_MODE * + const mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (mode != (PMIC_CONVITY_USB_TRANSCEIVER_MODE *) NULL)) { + *mode = usb.usbXcvrMode; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Set the Data Line Pulse duration (in milliseconds) for the USB OTG + * Session Request Protocol. + * + * For mc13783, this feature is not supported.So return PMIC_NOT_SUPPORTED + * + * @param handle device handle from open() call + * @param duration the data line pulse duration (ms) + * + * @return PMIC_SUCCESS if the pulse duration was successfully set + */ +PMIC_STATUS pmic_convity_usb_otg_set_dlp_duration(const PMIC_CONVITY_HANDLE + handle, + const unsigned int duration) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + /* The setting of the dlp duration is not supported by the mc13783 PMIC hardware. */ + + /* No critical section is required. */ + + if ((handle != usb.handle) + || (usb.handle_state != HANDLE_IN_USE)) { + /* Must return error indication for invalid handle parameter to be + * consistent with other APIs. + */ + rc = PMIC_ERROR; + } + + return rc; +} + +/*! + * Get the current Data Line Pulse duration (in milliseconds) for the USB + * OTG Session Request Protocol. + * + * @param handle device handle from open() call + * @param duration the data line pulse duration (ms) + * + * @return PMIC_SUCCESS if the pulse duration was successfully obtained + */ +PMIC_STATUS pmic_convity_usb_otg_get_dlp_duration(const PMIC_CONVITY_HANDLE + handle, + unsigned int *const duration) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + /* The setting of dlp duration is not supported by the mc13783 PMIC hardware. */ + + /* No critical section is required. */ + + if ((handle != usb.handle) + || (usb.handle_state != HANDLE_IN_USE)) { + /* Must return error indication for invalid handle parameter to be + * consistent with other APIs. + */ + rc = PMIC_ERROR; + } + + return rc; +} + +/*! + * Set the USB On-The-Go (OTG) configuration. + * + * @param handle device handle from open() call + * @param cfg desired USB OTG configuration + * + * @return PMIC_SUCCESS if the OTG configuration was successfully set + */ +PMIC_STATUS pmic_convity_usb_otg_set_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_OTG_CONFIG + cfg) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + if (cfg & USB_OTG_SE0CONN) { + reg_value = SET_BITS(regUSB0, SE0_CONN, 1); + reg_mask = SET_BITS(regUSB0, SE0_CONN, 1); + } + if (cfg & USBXCVREN) { + reg_value |= SET_BITS(regUSB0, USBXCVREN, 1); + reg_mask |= SET_BITS(regUSB0, USBXCVREN, 1); + } + + if (cfg & USB_OTG_DLP_SRP) { + reg_value |= SET_BITS(regUSB0, DLP_SRP, 1); + reg_mask |= SET_BITS(regUSB0, DLP_SRP, 1); + } + + if (cfg & USB_PULL_OVERRIDE) { + reg_value |= SET_BITS(regUSB0, PULLOVR, 1); + reg_mask |= SET_BITS(regUSB0, PULLOVR, 1); + } + + if (cfg & USB_PU) { + reg_value |= SET_BITS(regUSB0, USB_PU, 1); + reg_mask |= SET_BITS(regUSB0, USB_PU, 1); + } + + if (cfg & USB_UDM_PD) { + reg_value |= SET_BITS(regUSB0, UDM_PD, 1); + reg_mask |= SET_BITS(regUSB0, UDM_PD, 1); + } + + if (cfg & USB_UDP_PD) { + reg_value |= SET_BITS(regUSB0, UDP_PD, 1); + reg_mask |= SET_BITS(regUSB0, UDP_PD, 1); + } + + if (cfg & USB_DP150K_PU) { + reg_value |= SET_BITS(regUSB0, DP150K_PU, 1); + reg_mask |= SET_BITS(regUSB0, DP150K_PU, 1); + } + + if (cfg & USB_USBCNTRL) { + reg_value |= SET_BITS(regUSB0, USBCNTRL, 1); + reg_mask |= SET_BITS(regUSB0, USBCNTRL, 1); + } + + if (cfg & USB_VBUS_CURRENT_LIMIT_HIGH) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 0); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 1); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 2); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 3); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 4); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 5); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 6); + } + if (cfg & USB_VBUS_CURRENT_LIMIT_LOW) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 7); + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 7); + } + + if (cfg & USB_VBUS_PULLDOWN) { + reg_value |= SET_BITS(regUSB0, VBUSPDENB, 1); + reg_mask |= SET_BITS(regUSB0, VBUSPDENB, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + if ((cfg & USB_VBUS_CURRENT_LIMIT_HIGH) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS)) { + /* Make sure that the VBUS current limit state is + * correctly set to either USB_VBUS_CURRENT_LIMIT_HIGH + * or USB_VBUS_CURRENT_LIMIT_LOW but never both at the + * same time. + * + * We guarantee this by first clearing both of the + * status bits and then resetting the correct one. + */ + usb.usbOtgCfg &= + ~(USB_VBUS_CURRENT_LIMIT_HIGH | + USB_VBUS_CURRENT_LIMIT_LOW | + USB_VBUS_CURRENT_LIMIT_LOW_10MS | + USB_VBUS_CURRENT_LIMIT_LOW_20MS | + USB_VBUS_CURRENT_LIMIT_LOW_30MS | + USB_VBUS_CURRENT_LIMIT_LOW_40MS | + USB_VBUS_CURRENT_LIMIT_LOW_50MS | + USB_VBUS_CURRENT_LIMIT_LOW_60MS); + } + + usb.usbOtgCfg |= cfg; + } + } + //} + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Clears the USB On-The-Go (OTG) configuration. Multiple configuration settings + * may be OR'd together in a single call. However, selecting conflicting + * settings (e.g., multiple VBUS current limits) will result in undefined + * behavior. + * + * @param handle Device handle from open() call. + * @param cfg USB OTG configuration settings to be cleared. + * + * @retval PMIC_SUCCESS If the OTG configuration was successfully + * cleared. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_NOT_SUPPORTED If the desired USB OTG configuration is + * not supported by the PMIC hardware. + */ +PMIC_STATUS pmic_convity_usb_otg_clear_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_OTG_CONFIG + cfg) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + /* if ((cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS)) + { + rc = PMIC_NOT_SUPPORTED; + } */ + //else + + if (cfg & USB_OTG_SE0CONN) { + reg_mask = SET_BITS(regUSB0, SE0_CONN, 1); + } + + if (cfg & USB_OTG_DLP_SRP) { + reg_mask |= SET_BITS(regUSB0, DLP_SRP, 1); + } + + if (cfg & USB_DP150K_PU) { + reg_mask |= SET_BITS(regUSB0, DP150K_PU, 1); + } + + if (cfg & USB_PULL_OVERRIDE) { + reg_mask |= SET_BITS(regUSB0, PULLOVR, 1); + } + + if (cfg & USB_PU) { + + reg_mask |= SET_BITS(regUSB0, USB_PU, 1); + } + + if (cfg & USB_UDM_PD) { + + reg_mask |= SET_BITS(regUSB0, UDM_PD, 1); + } + + if (cfg & USB_UDP_PD) { + + reg_mask |= SET_BITS(regUSB0, UDP_PD, 1); + } + + if (cfg & USB_USBCNTRL) { + reg_mask |= SET_BITS(regUSB0, USBCNTRL, 1); + } + + if (cfg & USB_VBUS_CURRENT_LIMIT_HIGH) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 0); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 1); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 2); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 3); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 4); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 5); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 6); + } + + if (cfg & USB_VBUS_PULLDOWN) { + // reg_value |= SET_BITS(regUSB0, VBUSPDENB, 1); + reg_mask |= SET_BITS(regUSB0, VBUSPDENB, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbOtgCfg &= ~cfg; + } + } + //} + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the current USB On-The-Go (OTG) configuration. + * + * @param handle device handle from open() call + * @param cfg the current USB OTG configuration + * + * @return PMIC_SUCCESS if the OTG configuration was successfully + * retrieved + */ +PMIC_STATUS pmic_convity_usb_otg_get_config(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_OTG_CONFIG * + const cfg) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (cfg != (PMIC_CONVITY_USB_OTG_CONFIG *) NULL)) { + *cfg = usb.usbOtgCfg; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************** + * RS-232-specific configuration and setup functions. + ************************************************************************** + */ + +/*! + * @name RS-232 Serial Connectivity APIs + * Functions for controlling RS-232 serial connectivity. + */ +/*@{*/ + +/*! + * Set the connectivity interface to the selected RS-232 operating + * configuration. Note that the RS-232 operating mode will be automatically + * overridden if the USB_EN is asserted at any time (e.g., when a USB device + * is attached). However, we will also automatically return to the RS-232 + * mode once the USB device is detached. + * + * @param handle device handle from open() call + * @param cfgInternal RS-232 transceiver internal connections + * @param cfgExternal RS-232 transceiver external connections + * + * @return PMIC_SUCCESS if the requested mode was set + */ +PMIC_STATUS pmic_convity_rs232_set_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_RS232_INTERNAL + cfgInternal, + const PMIC_CONVITY_RS232_EXTERNAL + cfgExternal) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value0 = 0, reg_value1 = 0; + unsigned int reg_mask = SET_BITS(regUSB1, RSPOL, 1); + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == rs_232.handle) && (rs_232.handle_state == HANDLE_IN_USE)) { + rc = PMIC_SUCCESS; + + /* Validate the calling parameters. */ + /*if ((cfgInternal != RS232_TX_USE0VM_RX_UDATVP) && + (cfgInternal != RS232_TX_RX_INTERNAL_DEFAULT) && (cfgInternal != RS232_TX_UDATVP_RX_URXVM)) + { + + rc = PMIC_NOT_SUPPORTED; + } */ + if (cfgInternal == RS232_TX_USE0VM_RX_UDATVP) { + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 1); + + } else if (cfgInternal == RS232_TX_RX_INTERNAL_DEFAULT) { + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 1); + reg_mask |= SET_BITS(regUSB1, RSPOL, 1); + + } else if (cfgInternal == RS232_TX_UDATVP_RX_URXVM) { + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 2); + reg_value1 |= SET_BITS(regUSB1, RSPOL, 1); + + } else if ((cfgExternal == RS232_TX_UDM_RX_UDP) || + (cfgExternal == RS232_TX_RX_EXTERNAL_DEFAULT)) { + /* Configure for TX on D+ and RX on D-. */ + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 1); + reg_value1 |= SET_BITS(regUSB1, RSPOL, 0); + } else if (cfgExternal != RS232_TX_UDM_RX_UDP) { + /* Any other RS-232 configuration is an error. */ + rc = PMIC_ERROR; + } + + if (rc == PMIC_SUCCESS) { + /* Configure for TX on D- and RX on D+. */ + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value1, reg_mask); + + if (rc == PMIC_SUCCESS) { + rs_232.rs232CfgInternal = cfgInternal; + rs_232.rs232CfgExternal = cfgExternal; + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the connectivity interface's current RS-232 operating configuration. + * + * @param handle device handle from open() call + * @param cfgInternal RS-232 transceiver internal connections + * @param cfgExternal RS-232 transceiver external connections + * + * @return PMIC_SUCCESS if the requested mode was retrieved + */ +PMIC_STATUS pmic_convity_rs232_get_config(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_RS232_INTERNAL * + const cfgInternal, + PMIC_CONVITY_RS232_EXTERNAL * + const cfgExternal) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == rs_232.handle) && + (rs_232.handle_state == HANDLE_IN_USE) && + (cfgInternal != (PMIC_CONVITY_RS232_INTERNAL *) NULL) && + (cfgExternal != (PMIC_CONVITY_RS232_EXTERNAL *) NULL)) { + *cfgInternal = rs_232.rs232CfgInternal; + *cfgExternal = rs_232.rs232CfgExternal; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************** + * CEA-936-specific configuration and setup functions. + ************************************************************************** + */ + +/*! + * @name CEA-936 Connectivity APIs + * Functions for controlling CEA-936 connectivity. + */ +/*@{*/ + +/*! + * Signal the attached device to exit the current CEA-936 operating mode. + * Returns an error if the current operating mode is not CEA-936. + * + * @param handle device handle from open() call + * @param signal type of exit signal to be sent + * + * @return PMIC_SUCCESS if exit signal was sent + */ +PMIC_STATUS pmic_convity_cea936_exit_signal(const PMIC_CONVITY_HANDLE handle, + const + PMIC_CONVITY_CEA936_EXIT_SIGNAL + signal) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = + SET_BITS(regUSB0, IDPD, 1) | SET_BITS(regUSB0, IDPULSE, 1); + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle != cea_936.handle) + || (cea_936.handle_state != HANDLE_IN_USE)) { + /* Must return error indication for invalid handle parameter to be + * consistent with other APIs. + */ + rc = PMIC_ERROR; + } else if (signal == CEA936_UID_PULLDOWN_6MS) { + reg_value = + SET_BITS(regUSB0, IDPULSE, 0) | SET_BITS(regUSB0, IDPD, 0); + } else if (signal == CEA936_UID_PULLDOWN_6MS) { + reg_value = SET_BITS(regUSB0, IDPULSE, 1); + } else if (signal == CEA936_UID_PULLDOWN) { + reg_value = SET_BITS(regUSB0, IDPD, 1); + } else if (signal == CEA936_UDMPULSE) { + reg_value = SET_BITS(regUSB0, DMPULSE, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************** + * Static functions. + ************************************************************************** + */ + +/*! + * @name Connectivity Driver Internal Support Functions + * These non-exported internal functions are used to support the functionality + * of the exported connectivity APIs. + */ +/*@{*/ + +/*! + * This internal helper function sets the desired operating mode (either USB + * OTG or RS-232). It must be called with the mutex already acquired. + * + * @param mode the desired operating mode (USB or RS232) + * + * @return PMIC_SUCCESS if the desired operating mode was set + * @return PMIC_NOT_SUPPORTED if the desired operating mode is invalid + */ +static PMIC_STATUS pmic_convity_set_mode_internal(const PMIC_CONVITY_MODE mode) +{ + unsigned int reg_value0 = 0, reg_value1 = 0; + unsigned int reg_mask0 = 0, reg_mask1 = 0; + + //unsigned int reg_mask1 = SET_BITS(regUSB1, VBUSEN, 1); + + PMIC_STATUS rc = PMIC_SUCCESS; + + switch (mode) { + case USB: + /* For the USB mode, we start by tri-stating the USB bus (by + * setting VBUSEN = 0) until a device is connected (i.e., + * until we receive a 4.4V rising edge event). All pull-up + * and pull-down resistors are also disabled until a USB + * device is actually connected and we have determined which + * device is the host and the desired USB bus speed. + * + * Also tri-state the RS-232 buffers (by setting RSTRI = 1). + * This prevents the hardware from automatically returning to + * the RS-232 mode when the USB device is detached. + */ + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 0); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + /*reg_value1 = SET_BITS(regUSB1, RSTRI, 1); */ + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + /* if (rc == PMIC_SUCCESS) { + CHECK_ERROR(pmic_write_reg + (REG_CHARGE_USB_SPARE, + reg_value1, reg_mask1)); + } */ + + break; + + case RS232_1: + /* For the RS-232 mode, we tri-state the USB bus (by setting + * VBUSEN = 0) and enable the RS-232 transceiver (by setting + * RS232ENB = 0). + * + * Note that even in the RS-232 mode, if a USB device is + * plugged in, we will receive a 4.4V rising edge event which + * will automatically disable the RS-232 transceiver and + * tri-state the RS-232 buffers. This allows us to temporarily + * switch over to USB mode while the USB device is attached. + * The RS-232 transceiver and buffers will be automatically + * re-enabled when the USB device is detached. + */ + + /* Explicitly disconnect all of the USB pull-down resistors + * and the VUSB power regulator here just to be safe. + * + * But we do connect the internal pull-up resistor on USB_D+ + * to avoid having an extra load on the USB_D+ line when in + * RS-232 mode. + */ + + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 1) | + SET_BITS(regUSB0, VBUSPDENB, 1) | + SET_BITS(regUSB0, USB_PU, 1); + reg_mask0 = + SET_BITS(regUSB0, INTERFACE_MODE, 7) | SET_BITS(regUSB0, + VBUSPDENB, + 1) | + SET_BITS(regUSB0, USB_PU, 1); + + reg_value1 = SET_BITS(regUSB1, RSPOL, 0); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + + if (rc == PMIC_SUCCESS) { + CHECK_ERROR(pmic_write_reg + (REG_CHARGE_USB_SPARE, + reg_value1, reg_mask1)); + } + break; + + case RS232_2: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 2) | + SET_BITS(regUSB0, VBUSPDENB, 1) | + SET_BITS(regUSB0, USB_PU, 1); + reg_mask0 = + SET_BITS(regUSB0, INTERFACE_MODE, 7) | SET_BITS(regUSB0, + VBUSPDENB, + 1) | + SET_BITS(regUSB0, USB_PU, 1); + + reg_value1 = SET_BITS(regUSB1, RSPOL, 1); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + + if (rc == PMIC_SUCCESS) { + CHECK_ERROR(pmic_write_reg + (REG_CHARGE_USB_SPARE, + reg_value1, reg_mask1)); + } + break; + + case CEA936_MONO: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 4); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + case CEA936_STEREO: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 5); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + case CEA936_TEST_RIGHT: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 6); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + case CEA936_TEST_LEFT: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 7); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + default: + rc = PMIC_NOT_SUPPORTED; + } + + if (rc == PMIC_SUCCESS) { + if (mode == USB) { + usb.mode = mode; + } else if ((mode == RS232_1) || (mode == RS232_1)) { + rs_232.mode = mode; + } else if ((mode == CEA936_MONO) || (mode == CEA936_STEREO) || + (mode == CEA936_TEST_RIGHT) + || (mode == CEA936_TEST_LEFT)) { + cea_936.mode = mode; + } + } + + return rc; +} + +/*! + * This internal helper function deregisters all of the currently registered + * callback events. It must be called with the mutual exclusion spinlock + * already acquired. + * + * We've defined the event and callback deregistration code here as a separate + * function because it can be called by either the pmic_convity_close() or the + * pmic_convity_clear_callback() APIs. We also wanted to avoid any possible + * issues with having the same thread calling spin_lock_irq() twice. + * + * Note that the mutex must have already been acquired. We will also acquire + * the spinlock here to avoid any possible race conditions with the interrupt + * handler. + * + * @return PMIC_SUCCESS if all of the callback events were cleared + */ +static PMIC_STATUS pmic_convity_deregister_all(void) +{ + unsigned long flags; + PMIC_STATUS rc = PMIC_SUCCESS; + + /* Deregister each of the PMIC events that we had previously + * registered for by using pmic_event_subscribe(). + */ + + if ((usb.eventMask & USB_DETECT_MINI_A) || + (usb.eventMask & USB_DETECT_MINI_B) || + (usb.eventMask & USB_DETECT_NON_USB_ACCESSORY) || + (usb.eventMask & USB_DETECT_FACTORY_MODE)) { + //EVENT_AB_DETI or EVENT_IDI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_ABDET); + + if (pmic_event_unsubscribe(EVENT_IDI, eventNotify) == + PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_MINI_A | + USB_DETECT_MINI_B | + USB_DETECT_NON_USB_ACCESSORY | + USB_DETECT_FACTORY_MODE); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_AB_DETI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + } + + else if ((usb.eventMask & USB_DETECT_0V8_RISE) || + (usb.eventMask & USB_DETECT_0V8_FALL)) { + //EVENT_USB_08VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_0V8); + if (pmic_event_unsubscribe(EVENT_USBI, eventNotify) == + PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_0V8_RISE | + USB_DETECT_0V8_FALL); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_USB_08VI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + + } + + else if ((usb.eventMask & USB_DETECT_2V0_RISE) || + (usb.eventMask & USB_DETECT_2V0_FALL)) { + //EVENT_USB_20VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_2V0); + if (pmic_event_unsubscribe(EVENT_USBI, eventNotify) == + PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_2V0_RISE | + USB_DETECT_2V0_FALL); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_USB_20VI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + } + + else if ((usb.eventMask & USB_DETECT_4V4_RISE) || + (usb.eventMask & USB_DETECT_4V4_FALL)) { + + //EVENT_USB_44VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_4V4); + + if (pmic_event_unsubscribe(EVENT_USBI, eventNotify) == + PMIC_SUCCESS) { + + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_4V4_RISE | + USB_DETECT_4V4_FALL); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_USB_44VI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + } + + if (rc == PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + /* Restore the initial reset values for the callback function + * and event mask parameters. This should be NULL and zero, + * respectively. + * + * Note that we wait until the end here to fully reset everything + * just in case some of the pmic_event_unsubscribe() calls above + * failed for some reason (which normally shouldn't happen). + */ + usb.callback = reset.callback; + usb.eventMask = reset.eventMask; + + spin_unlock_irqrestore(&lock, flags); + } + return rc; +} + +/*! + * This is the default event handler for all connectivity-related events + * and hardware interrupts. + * + * @param param event ID + */ +static void pmic_convity_event_handler(void *param) +{ + unsigned long flags; + + /* Update the global list of active interrupt events. */ + spin_lock_irqsave(&lock, flags); + eventID |= (PMIC_CORE_EVENT) (param); + spin_unlock_irqrestore(&lock, flags); + + /* Schedule the tasklet to be run as soon as it is convenient to do so. */ + schedule_work(&convityTasklet); +} + +/*! + * @brief This is the connectivity driver tasklet that handles interrupt events. + * + * This function is scheduled by the connectivity driver interrupt handler + * pmic_convity_event_handler() to complete the processing of all of the + * connectivity-related interrupt events. + * + * Since this tasklet runs with interrupts enabled, we can safely call + * the ADC driver, if necessary, to properly detect the type of USB connection + * that is being made and to call any user-registered callback functions. + * + * @param arg The parameter that was provided above in + * the DECLARE_TASKLET() macro (unused). + */ +static void pmic_convity_tasklet(struct work_struct *work) +{ + + PMIC_CONVITY_EVENTS activeEvents = 0; + unsigned long flags = 0; + + /* Check the interrupt sense bits to determine exactly what + * event just occurred. + */ + if (eventID & CORE_EVENT_4V4) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_4V4; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_USB4V4S) ? + USB_DETECT_4V4_RISE : USB_DETECT_4V4_FALL; + + if (activeEvents & ~usb.eventMask) { + /* The default handler for 4.4 V rising/falling edge detection + * is to simply ignore the event. + */ + ; + } + } + if (eventID & CORE_EVENT_2V0) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_2V0; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_USB2V0S) ? + USB_DETECT_2V0_RISE : USB_DETECT_2V0_FALL; + if (activeEvents & ~usb.eventMask) { + /* The default handler for 2.0 V rising/falling edge detection + * is to simply ignore the event. + */ + ; + } + } + if (eventID & CORE_EVENT_0V8) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_0V8; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_USB0V8S) ? + USB_DETECT_0V8_RISE : USB_DETECT_0V8_FALL; + + if (activeEvents & ~usb.eventMask) { + /* The default handler for 0.8 V rising/falling edge detection + * is to simply ignore the event. + */ + ; + } + } + if (eventID & CORE_EVENT_ABDET) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_ABDET; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_ID_GNDS) ? + USB_DETECT_MINI_A : 0; + + activeEvents |= pmic_check_sensor(SENSE_ID_FLOATS) ? + USB_DETECT_MINI_B : 0; + } + + /* Begin a critical section here so that we don't register/deregister + * for events or open/close the connectivity driver while the existing + * event handler (if it is currently defined) is in the middle of handling + * the current event. + */ + spin_lock_irqsave(&lock, flags); + + /* Finally, call the user-defined callback function if required. */ + if ((usb.handle_state == HANDLE_IN_USE) && + (usb.callback != NULL) && (activeEvents & usb.eventMask)) { + (*usb.callback) (activeEvents); + } + + spin_unlock_irqrestore(&lock, flags); +} + +/*@}*/ + +/************************************************************************** + * Module initialization and termination functions. + * + * Note that if this code is compiled into the kernel, then the + * module_init() function will be called within the device_initcall() + * group. + ************************************************************************** + */ + +/*! + * @name Connectivity Driver Loading/Unloading Functions + * These non-exported internal functions are used to support the connectivity + * device driver initialization and de-initialization operations. + */ +/*@{*/ + +/*! + * @brief This is the connectivity device driver initialization function. + * + * This function is called by the kernel when this device driver is first + * loaded. + */ +static int __init mc13783_pmic_convity_init(void) +{ + printk(KERN_INFO "PMIC Connectivity driver loading..\n"); + + return 0; +} + +/*! + * @brief This is the Connectivity device driver de-initialization function. + * + * This function is called by the kernel when this device driver is about + * to be unloaded. + */ +static void __exit mc13783_pmic_convity_exit(void) +{ + printk(KERN_INFO "PMIC Connectivity driver unloading\n"); + + /* Close the device handle if it is still open. This will also + * deregister any callbacks that may still be active. + */ + if (usb.handle_state == HANDLE_IN_USE) { + pmic_convity_close(usb.handle); + } else if (usb.handle_state == HANDLE_IN_USE) { + pmic_convity_close(rs_232.handle); + } else if (usb.handle_state == HANDLE_IN_USE) { + pmic_convity_close(cea_936.handle); + } + + /* Reset the PMIC Connectivity register to it's power on state. + * We should do this when unloading the module so that we don't + * leave the hardware in a state which could cause problems when + * no device driver is loaded. + */ + pmic_write_reg(REG_USB, RESET_USBCNTRL_REG_0, REG_FULLMASK); + pmic_write_reg(REG_CHARGE_USB_SPARE, RESET_USBCNTRL_REG_1, + REG_FULLMASK); + /* Note that there is no need to reset the "convity" device driver + * state structure to the reset state since we are in the final + * stage of unloading the device driver. The device driver state + * structure will be automatically and properly reinitialized if + * this device driver is reloaded. + */ +} + +/*@}*/ + +/* + * Module entry points and description information. + */ + +module_init(mc13783_pmic_convity_init); +module_exit(mc13783_pmic_convity_exit); + +MODULE_DESCRIPTION("mc13783 Connectivity device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_light.c b/drivers/mxc/pmic/mc13783/pmic_light.c new file mode 100644 index 000000000000..c28cbc3386c3 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_light.c @@ -0,0 +1,2769 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_light.c + * @brief This is the main file of PMIC(mc13783) Light and Backlight driver. + * + * @ingroup PMIC_LIGHT + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include <linux/pmic_light.h> +#include <linux/pmic_status.h> +#include "pmic_light_defs.h" + +#define NB_LIGHT_REG 6 + +static int pmic_light_major; + +/*! + * Number of users waiting in suspendq + */ +static int swait = 0; + +/*! + * To indicate whether any of the light devices are suspending + */ +static int suspend_flag = 0; + +/*! + * The suspendq is used to block application calls + */ +static wait_queue_head_t suspendq; + +static struct class *pmic_light_class; + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_bklit_tcled_master_enable); +EXPORT_SYMBOL(pmic_bklit_tcled_master_disable); +EXPORT_SYMBOL(pmic_bklit_master_enable); +EXPORT_SYMBOL(pmic_bklit_master_disable); +EXPORT_SYMBOL(pmic_bklit_set_current); +EXPORT_SYMBOL(pmic_bklit_get_current); +EXPORT_SYMBOL(pmic_bklit_set_dutycycle); +EXPORT_SYMBOL(pmic_bklit_get_dutycycle); +EXPORT_SYMBOL(pmic_bklit_set_cycle_time); +EXPORT_SYMBOL(pmic_bklit_get_cycle_time); +EXPORT_SYMBOL(pmic_bklit_set_mode); +EXPORT_SYMBOL(pmic_bklit_get_mode); +EXPORT_SYMBOL(pmic_bklit_rampup); +EXPORT_SYMBOL(pmic_bklit_off_rampup); +EXPORT_SYMBOL(pmic_bklit_rampdown); +EXPORT_SYMBOL(pmic_bklit_off_rampdown); +EXPORT_SYMBOL(pmic_bklit_enable_edge_slow); +EXPORT_SYMBOL(pmic_bklit_disable_edge_slow); +EXPORT_SYMBOL(pmic_bklit_get_edge_slow); +EXPORT_SYMBOL(pmic_bklit_set_strobemode); +EXPORT_SYMBOL(pmic_tcled_enable); +EXPORT_SYMBOL(pmic_tcled_disable); +EXPORT_SYMBOL(pmic_tcled_get_mode); +EXPORT_SYMBOL(pmic_tcled_ind_set_current); +EXPORT_SYMBOL(pmic_tcled_ind_get_current); +EXPORT_SYMBOL(pmic_tcled_ind_set_blink_pattern); +EXPORT_SYMBOL(pmic_tcled_ind_get_blink_pattern); +EXPORT_SYMBOL(pmic_tcled_fun_set_current); +EXPORT_SYMBOL(pmic_tcled_fun_get_current); +EXPORT_SYMBOL(pmic_tcled_fun_set_cycletime); +EXPORT_SYMBOL(pmic_tcled_fun_get_cycletime); +EXPORT_SYMBOL(pmic_tcled_fun_set_dutycycle); +EXPORT_SYMBOL(pmic_tcled_fun_get_dutycycle); +EXPORT_SYMBOL(pmic_tcled_fun_blendedramps); +EXPORT_SYMBOL(pmic_tcled_fun_sawramps); +EXPORT_SYMBOL(pmic_tcled_fun_blendedbowtie); +EXPORT_SYMBOL(pmic_tcled_fun_chasinglightspattern); +EXPORT_SYMBOL(pmic_tcled_fun_strobe); +EXPORT_SYMBOL(pmic_tcled_fun_rampup); +EXPORT_SYMBOL(pmic_tcled_get_fun_rampup); +EXPORT_SYMBOL(pmic_tcled_fun_rampdown); +EXPORT_SYMBOL(pmic_tcled_get_fun_rampdown); +EXPORT_SYMBOL(pmic_tcled_fun_triode_on); +EXPORT_SYMBOL(pmic_tcled_fun_triode_off); +EXPORT_SYMBOL(pmic_tcled_enable_edge_slow); +EXPORT_SYMBOL(pmic_tcled_disable_edge_slow); +EXPORT_SYMBOL(pmic_tcled_enable_half_current); +EXPORT_SYMBOL(pmic_tcled_disable_half_current); +EXPORT_SYMBOL(pmic_tcled_enable_audio_modulation); +EXPORT_SYMBOL(pmic_tcled_disable_audio_modulation); +EXPORT_SYMBOL(pmic_bklit_set_boost_mode); +EXPORT_SYMBOL(pmic_bklit_get_boost_mode); +EXPORT_SYMBOL(pmic_bklit_config_boost_mode); +EXPORT_SYMBOL(pmic_bklit_gets_boost_mode); + +/*! + * This is the suspend of power management for the pmic light API. + * It suports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int pmic_light_suspend(struct platform_device *dev, pm_message_t state) +{ + suspend_flag = 1; + /* switch off all leds and backlights */ + CHECK_ERROR(pmic_light_init_reg()); + + return 0; +}; + +/*! + * This is the resume of power management for the pmic light API. + * It suports RESTORE state. + * + * @param dev the device + * + * @return This function returns 0 if successful. + */ +static int pmic_light_resume(struct platform_device *pdev) +{ + suspend_flag = 0; + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +/*! + * This function enables backlight & tcled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_tcled_master_enable(void) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + reg_value = BITFVAL(BIT_LEDEN, 1); + mask = BITFMASK(BIT_LEDEN); + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables backlight & tcled. + * + * @return This function returns PMIC_SUCCESS if successful + */ +PMIC_STATUS pmic_bklit_tcled_master_disable(void) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + reg_value = BITFVAL(BIT_LEDEN, 0); + mask = BITFMASK(BIT_LEDEN); + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables backlight. Not supported on mc13783 + * Use pmic_bklit_tcled_master_enable. + * + * @return This function returns PMIC_NOT_SUPPORTED + */ +PMIC_STATUS pmic_bklit_master_enable(void) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function disables backlight. Not supported on mc13783 + * Use pmic_bklit_tcled_master_enable. + * + * @return This function returns PMIC_NOT_SUPPORTED + */ +PMIC_STATUS pmic_bklit_master_disable(void) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function sets backlight current level. + * + * @param channel Backlight channel + * @param level Backlight current level, as the following table. + * @verbatim + level main & aux keyboard + ------ ----------- -------- + 0 0 mA 0 mA + 1 3 mA 12 mA + 2 6 mA 24 mA + 3 9 mA 36 mA + 4 12 mA 48 mA + 5 15 mA 60 mA + 6 18 mA 72 mA + 7 21 mA 84 mA + @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_current(t_bklit_channel channel, unsigned char level) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + value = BITFVAL(BIT_CL_MAIN, level); + mask = BITFMASK(BIT_CL_MAIN); + break; + case BACKLIGHT_LED2: + value = BITFVAL(BIT_CL_AUX, level); + mask = BITFMASK(BIT_CL_AUX); + break; + case BACKLIGHT_LED3: + value = BITFVAL(BIT_CL_KEY, level); + mask = BITFMASK(BIT_CL_KEY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(LREG_2, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives backlight current level. + * The channels are not individually adjustable, hence + * the channel parameter is ignored. + * + * @param channel Backlight channel (Ignored because the + * channels are not individually adjustable) + * @param level Pointer to store backlight current level result. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_current(t_bklit_channel channel, + unsigned char *level) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_CL_MAIN); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_CL_AUX); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_CL_KEY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_2, ®_value, mask)); + + switch (channel) { + case BACKLIGHT_LED1: + *level = BITFEXT(reg_value, BIT_CL_MAIN); + break; + case BACKLIGHT_LED2: + *level = BITFEXT(reg_value, BIT_CL_AUX); + break; + case BACKLIGHT_LED3: + *level = BITFEXT(reg_value, BIT_CL_KEY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a backlight channel duty cycle. + * LED perceived brightness for each zone may be individually set by setting + * duty cycle. The default setting is for 0% duty cycle; this keeps all zone + * drivers turned off even after the master enable command. Each LED current + * sink can be turned on and adjusted for brightness with an independent 4 bit + * word for a duty cycle ranging from 0% to 100% in approximately 6.7% steps. + * + * @param channel Backlight channel. + * @param dc Backlight duty cycle, as the following table. + * @verbatim + dc Duty Cycle (% On-time over Cycle Time) + ------ --------------------------------------- + 0 0% + 1 6.7% + 2 13.3% + 3 20% + 4 26.7% + 5 33.3% + 6 40% + 7 46.7% + 8 53.3% + 9 60% + 10 66.7% + 11 73.3% + 12 80% + 13 86.7% + 14 93.3% + 15 100% + @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_dutycycle(t_bklit_channel channel, unsigned char dc) +{ + unsigned int reg_value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (dc > 15) { + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_2, ®_value, PMIC_ALL_BITS)); + + switch (channel) { + case BACKLIGHT_LED1: + reg_value = reg_value & (~MASK_DUTY_CYCLE); + reg_value = reg_value | (dc << BIT_DUTY_CYCLE); + break; + case BACKLIGHT_LED2: + reg_value = reg_value & (~(MASK_DUTY_CYCLE << INDEX_AUX)); + reg_value = reg_value | (dc << (BIT_DUTY_CYCLE + INDEX_AUX)); + break; + case BACKLIGHT_LED3: + reg_value = reg_value & (~(MASK_DUTY_CYCLE << INDEX_KYD)); + reg_value = reg_value | (dc << (BIT_DUTY_CYCLE + INDEX_KYD)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(LREG_2, reg_value, PMIC_ALL_BITS)); + return PMIC_SUCCESS; + +} + +/*! + * This function retrives a backlight channel duty cycle. + * + * @param channel Backlight channel. + * @param dc Pointer to backlight duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_dutycycle(t_bklit_channel channel, unsigned char *dc) +{ + unsigned int reg_value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + CHECK_ERROR(pmic_read_reg(LREG_2, ®_value, PMIC_ALL_BITS)); + + switch (channel) { + case BACKLIGHT_LED1: + *dc = (int)((reg_value & (MASK_DUTY_CYCLE)) + >> BIT_DUTY_CYCLE); + + break; + case BACKLIGHT_LED2: + *dc = (int)((reg_value & (MASK_DUTY_CYCLE << INDEX_AUX)) + >> (BIT_DUTY_CYCLE + INDEX_AUX)); + break; + case BACKLIGHT_LED3: + *dc = (int)((reg_value & (MASK_DUTY_CYCLE << + INDEX_KYD)) >> (BIT_DUTY_CYCLE + + INDEX_KYD)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a backlight channel cycle time. + * Cycle Time is defined as the period of a complete cycle of + * Time_on + Time_off. The default Cycle Time is set to 0.01 seconds such that + * the 100 Hz on-off cycling is averaged out by the eye to eliminate + * flickering. Additionally, the Cycle Time can be programmed to intentionally + * extend the period of on-off cycles for a visual pulsating or blinking effect. + * + * @param period Backlight cycle time, as the following table. + * @verbatim + period Cycle Time + -------- ------------ + 0 0.01 seconds + 1 0.1 seconds + 2 0.5 seconds + 3 2 seconds + @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_cycle_time(unsigned char period) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + if (period > 3) { + return PMIC_PARAMETER_ERROR; + } + mask = BITFMASK(BIT_PERIOD); + value = BITFVAL(BIT_PERIOD, period); + CHECK_ERROR(pmic_write_reg(LREG_2, value, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function retrives a backlight channel cycle time setting. + * + * @param period Pointer to save backlight cycle time setting result. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_cycle_time(unsigned char *period) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_PERIOD); + CHECK_ERROR(pmic_read_reg(LREG_2, &value, mask)); + *period = BITFEXT(value, BIT_PERIOD); + return PMIC_SUCCESS; +} + +/*! + * This function sets backlight operation mode. There are two modes of + * operations: current control and triode mode. + * The Duty Cycle/Cycle Time control is retained in Triode Mode. Audio + * coupling is not available in Triode Mode. + * + * @param channel Backlight channel. + * @param mode Backlight operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_mode(t_bklit_channel channel, t_bklit_mode mode) +{ + unsigned int reg_value = 0; + unsigned int clear_val = 0; + unsigned int triode_val = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + CHECK_ERROR(pmic_read_reg(LREG_0, ®_value, PMIC_ALL_BITS)); + + switch (channel) { + case BACKLIGHT_LED1: + clear_val = ~(MASK_TRIODE_MAIN_BL); + triode_val = MASK_TRIODE_MAIN_BL; + break; + case BACKLIGHT_LED2: + clear_val = ~(MASK_TRIODE_MAIN_BL << INDEX_AUXILIARY); + triode_val = (MASK_TRIODE_MAIN_BL << INDEX_AUXILIARY); + break; + case BACKLIGHT_LED3: + clear_val = ~(MASK_TRIODE_MAIN_BL << INDEX_KEYPAD); + triode_val = (MASK_TRIODE_MAIN_BL << INDEX_KEYPAD); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + reg_value = (reg_value & clear_val); + + if (mode == BACKLIGHT_TRIODE_MODE) { + reg_value = (reg_value | triode_val); + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, PMIC_ALL_BITS)); + return PMIC_SUCCESS; +} + +/*! + * This function gets backlight operation mode. There are two modes of + * operations: current control and triode mode. + * The Duty Cycle/Cycle Time control is retained in Triode Mode. Audio + * coupling is not available in Triode Mode. + * + * @param channel Backlight channel. + * @param mode Backlight operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_mode(t_bklit_channel channel, t_bklit_mode * mode) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_TRIODE_MAIN_BL); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_TRIODE_AUX_BL); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_TRIODE_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_0, ®_value, mask)); + + switch (channel) { + case BACKLIGHT_LED1: + *mode = BITFEXT(reg_value, BIT_TRIODE_MAIN_BL); + break; + case BACKLIGHT_LED2: + *mode = BITFEXT(reg_value, BIT_TRIODE_AUX_BL); + break; + case BACKLIGHT_LED3: + *mode = BITFEXT(reg_value, BIT_TRIODE_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function starts backlight brightness ramp up function; ramp time is + * fixed at 0.5 seconds. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_rampup(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_UP_MAIN_BL); + reg_value = BITFVAL(BIT_UP_MAIN_BL, 1); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_UP_AUX_BL); + reg_value = BITFVAL(BIT_UP_AUX_BL, 1); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_UP_KEY_BL); + reg_value = BITFVAL(BIT_UP_KEY_BL, 1); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function stops backlight brightness ramp up function; + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_off_rampup(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_UP_MAIN_BL); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_UP_AUX_BL); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_UP_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function starts backlight brightness ramp down function; ramp time is + * fixed at 0.5 seconds. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_rampdown(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_DOWN_MAIN_BL); + reg_value = BITFVAL(BIT_DOWN_MAIN_BL, 1); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_DOWN_AUX_BL); + reg_value = BITFVAL(BIT_DOWN_AUX_BL, 1); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_DOWN_KEY_BL); + reg_value = BITFVAL(BIT_DOWN_KEY_BL, 1); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function stops backlight brightness ramp down function. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_off_rampdown(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_DOWN_MAIN_BL); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_DOWN_AUX_BL); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_DOWN_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables backlight analog edge slowing mode. Analog Edge + * Slowing slows down the transient edges to reduce the chance of coupling LED + * modulation activity into other circuits. Rise and fall times will be targeted + * for approximately 50usec. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_enable_edge_slow(void) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_SLEWLIMBL); + value = BITFVAL(BIT_SLEWLIMBL, 1); + CHECK_ERROR(pmic_write_reg(LREG_2, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables backlight analog edge slowing mode. The backlight + * drivers will default to an <93>Instant On<94> mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_disable_edge_slow(void) +{ + unsigned int mask; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_SLEWLIMBL); + CHECK_ERROR(pmic_write_reg(LREG_2, 0, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets backlight analog edge slowing mode. DThe backlight + * + * @param edge Edge slowing mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_edge_slow(bool * edge) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_SLEWLIMBL); + CHECK_ERROR(pmic_read_reg(LREG_2, &value, mask)); + *edge = (bool) BITFEXT(value, BIT_SLEWLIMBL); + + return PMIC_SUCCESS; +} + +/*! + * This function sets backlight Strobe Light Pulsing mode. + * + * @param channel Backlight channel. + * @param mode Strobe Light Pulsing mode. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_bklit_set_strobemode(t_bklit_channel channel, + t_bklit_strobe_mode mode) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function enables tri-color LED. + * + * @param mode Tri-color LED operation mode. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_enable(t_tcled_mode mode, t_funlight_bank bank) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (mode) { + case TCLED_FUN_MODE: + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + value = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + value = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + value = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + break; + case TCLED_IND_MODE: + mask = MASK_BK1_FL | MASK_BK2_FL | MASK_BK3_FL; + break; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables tri-color LED. + * + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + * + */ +PMIC_STATUS pmic_tcled_disable(t_funlight_bank bank) +{ + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, 0, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives tri-color LED operation mode. + * + * @param mode Pointer to Tri-color LED operation mode. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_mode(t_tcled_mode * mode, t_funlight_bank bank) +{ + unsigned int val; + unsigned int mask; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_0, &val, mask)); + + if (val) { + *mode = TCLED_FUN_MODE; + } else { + *mode = TCLED_IND_MODE; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel current level in indicator mode. + * + * @param channel Tri-color LED channel. + * @param level Current level. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_set_current(t_ind_channel channel, + t_tcled_cur_level level, + t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (level > TCLED_CUR_LEVEL_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + value = BITFVAL(BITS_CL_RED, level); + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_IND_GREEN: + value = BITFVAL(BITS_CL_GREEN, level); + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_IND_BLUE: + value = BITFVAL(BITS_CL_BLUE, level); + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel current level + * in indicator mode. + * + * @param channel Tri-color LED channel. + * @param level Pointer to current level. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_get_current(t_ind_channel channel, + t_tcled_cur_level * level, + t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_IND_GREEN: + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_IND_BLUE: + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_IND_RED: + *level = BITFEXT(value, BITS_CL_RED); + break; + case TCLED_IND_GREEN: + *level = BITFEXT(value, BITS_CL_GREEN); + break; + case TCLED_IND_BLUE: + *level = BITFEXT(value, BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel blinking pattern in indication + * mode. + * + * @param channel Tri-color LED channel. + * @param pattern Blinking pattern. + * @param skip If true, skip a cycle after each cycle. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_set_blink_pattern(t_ind_channel channel, + t_tcled_ind_blink_pattern pattern, + bool skip, t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (skip == true) { + return PMIC_NOT_SUPPORTED; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + value = BITFVAL(BITS_DC_RED, pattern); + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_IND_GREEN: + value = BITFVAL(BITS_DC_GREEN, pattern); + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_IND_BLUE: + value = BITFVAL(BITS_DC_BLUE, pattern); + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel blinking pattern in + * indication mode. + * + * @param channel Tri-color LED channel. + * @param pattern Pointer to Blinking pattern. + * @param skip Pointer to a boolean varible indicating if skip + * @param bank Selected tri-color bank + * a cycle after each cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_get_blink_pattern(t_ind_channel channel, + t_tcled_ind_blink_pattern * + pattern, bool * skip, + t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_IND_GREEN: + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_IND_BLUE: + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_IND_RED: + *pattern = BITFEXT(value, BITS_DC_RED); + break; + case TCLED_IND_GREEN: + *pattern = BITFEXT(value, BITS_DC_GREEN); + break; + case TCLED_IND_BLUE: + *pattern = BITFEXT(value, BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel current level in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param level Current level. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_current(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_cur_level level) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (level > TCLED_CUR_LEVEL_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + value = BITFVAL(BITS_CL_RED, level); + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_FUN_CHANNEL2: + value = BITFVAL(BITS_CL_GREEN, level); + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_FUN_CHANNEL3: + value = BITFVAL(BITS_CL_BLUE, level); + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel current level + * in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param level Pointer to current level. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_current(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_cur_level * level) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_FUN_CHANNEL2: + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_FUN_CHANNEL3: + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_FUN_CHANNEL1: + *level = BITFEXT(value, BITS_CL_RED); + break; + case TCLED_FUN_CHANNEL2: + *level = BITFEXT(value, BITS_CL_GREEN); + break; + case TCLED_FUN_CHANNEL3: + *level = BITFEXT(value, BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets tri-color LED cycle time. + * + * @param bank Tri-color LED bank + * @param ct Cycle time. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_cycletime(t_funlight_bank bank, + t_tcled_fun_cycle_time ct) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (ct > TC_CYCLE_TIME_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + value = BITFVAL(BIT_PERIOD, ct); + mask = BITFMASK(BIT_PERIOD); + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function retrives tri-color LED cycle time in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param ct Pointer to cycle time. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_cycletime(t_funlight_bank bank, + t_tcled_fun_cycle_time * ct) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (*ct > TC_CYCLE_TIME_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BIT_PERIOD); + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + *ct = BITFVAL(BIT_PERIOD, value); + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel duty cycle in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param dc Duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_dutycycle(t_funlight_bank bank, + t_funlight_channel channel, + unsigned char dc) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + value = BITFVAL(BITS_DC_RED, dc); + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_FUN_CHANNEL2: + value = BITFVAL(BITS_DC_GREEN, dc); + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_FUN_CHANNEL3: + value = BITFVAL(BITS_DC_BLUE, dc); + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel duty cycle in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param dc Pointer to duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_dutycycle(t_funlight_bank bank, + t_funlight_channel channel, + unsigned char *dc) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_FUN_CHANNEL2: + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_FUN_CHANNEL3: + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_FUN_CHANNEL1: + *dc = BITFEXT(value, BITS_DC_RED); + break; + case TCLED_FUN_CHANNEL2: + *dc = BITFEXT(value, BITS_DC_GREEN); + break; + case TCLED_FUN_CHANNEL3: + *dc = BITFEXT(value, BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Blended Ramp fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_blendedramps(t_funlight_bank bank, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_RAMPS_SLOW); + break; + case TC_FAST: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_RAMPS_FAST); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Saw Ramp fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_sawramps(t_funlight_bank bank, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + value = BITFVAL(BITS_FUN_LIGHT, SAW_RAMPS_SLOW); + break; + case TC_FAST: + value = BITFVAL(BITS_FUN_LIGHT, SAW_RAMPS_FAST); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Blended Bowtie fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_blendedbowtie(t_funlight_bank bank, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_INVERSE_RAMPS_SLOW); + break; + case TC_FAST: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_INVERSE_RAMPS_FAST); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Chasing Lights fun light pattern. + * + * @param bank Tri-color LED bank + * @param pattern Chasing light pattern mode. + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_chasinglightspattern(t_funlight_bank bank, + t_chaselight_pattern pattern, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (pattern > BGR) { + return PMIC_PARAMETER_ERROR; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + if (pattern == PMIC_RGB) { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_RGB_SLOW); + } else { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_BGR_SLOW); + } + break; + case TC_FAST: + if (pattern == PMIC_RGB) { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_RGB_FAST); + } else { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_BGR_FAST); + } + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Strobe Mode fun light pattern. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_strobe(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_fun_strobe_speed speed) +{ + /* not supported on mc13783 */ + + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function initiates Tri-color LED brightness Ramp Up function; Ramp time + * is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampup Ramp-up configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_rampup(t_funlight_bank bank, + t_funlight_channel channel, bool rampup) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPUP; + value = LEDR1RAMPUP; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPUP; + value = LEDR2RAMPUP; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPUP; + value = LEDR3RAMPUP; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + value = value; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + value = value * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + value = value * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (!rampup) { + value = 0; + } + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets Tri-color LED brightness Ramp Up function; Ramp time + * is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampup Ramp-up configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_fun_rampup(t_funlight_bank bank, + t_funlight_channel channel, bool * rampup) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPUP; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPUP; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPUP; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_1, &value, mask)); + if (value) { + *rampup = true; + } else { + *rampup = false; + } + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Tri-color LED brightness Ramp Down function; Ramp + * time is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampdown Ramp-down configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_rampdown(t_funlight_bank bank, + t_funlight_channel channel, bool rampdown) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPDOWN; + value = LEDR1RAMPDOWN; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPDOWN; + value = LEDR2RAMPDOWN; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPDOWN; + value = LEDR3RAMPDOWN; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + value = value; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + value = value * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + value = value * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (!rampdown) { + value = 0; + } + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function initiates Tri-color LED brightness Ramp Down function; Ramp + * time is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampdown Ramp-down configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_fun_rampdown(t_funlight_bank bank, + t_funlight_channel channel, + bool * rampdown) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPDOWN; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPDOWN; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPDOWN; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_1, &value, mask)); + if (value) { + *rampdown = true; + } else { + *rampdown = false; + } + return PMIC_SUCCESS; +} + +/*! + * This function enables a Tri-color channel triode mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_triode_on(t_funlight_bank bank, + t_funlight_channel channel) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + value = ENABLE_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + value = ENABLE_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + value = ENABLE_BK2_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables a Tri-color LED channel triode mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_triode_off(t_funlight_bank bank, + t_funlight_channel channel) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables Tri-color LED edge slowing. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_enable_edge_slow(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_SLEWLIMTC, 1); + mask = BITFMASK(BIT_SLEWLIMTC); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables Tri-color LED edge slowing. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_disable_edge_slow(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_SLEWLIMTC, 0); + mask = BITFMASK(BIT_SLEWLIMTC); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables Tri-color LED half current mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_enable_half_current(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_TC1HALF, 1); + mask = BITFMASK(BIT_TC1HALF); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables Tri-color LED half current mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_disable_half_current(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_TC1HALF, 0); + mask = BITFMASK(BIT_TC1HALF); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables backlight or Tri-color LED audio modulation. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_tcled_enable_audio_modulation(t_led_channel channel, + t_aud_path path, + t_aud_gain gain, bool lpf_bypass) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function disables backlight or Tri-color LED audio modulation. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_tcled_disable_audio_modulation(void) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function enables the boost mode. + * Only on mc13783 2.0 or higher + * + * @param en_dis Enable or disable the boost mode + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_set_boost_mode(bool en_dis) +{ + + pmic_version_t mc13783_ver; + unsigned int mask; + unsigned int value; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_BOOSTEN, en_dis); + mask = BITFMASK(BIT_BOOSTEN); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets the boost mode. + * Only on mc13783 2.0 or higher + * + * @param en_dis Enable or disable the boost mode + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_get_boost_mode(bool * en_dis) +{ + pmic_version_t mc13783_ver; + unsigned int mask; + unsigned int value; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_BOOSTEN); + CHECK_ERROR(pmic_read_reg(LREG_0, &value, mask)); + *en_dis = BITFEXT(value, BIT_BOOSTEN); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function sets boost mode configuration + * Only on mc13783 2.0 or higher + * + * @param abms Define adaptive boost mode selection + * @param abr Define adaptive boost reference + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_config_boost_mode(unsigned int abms, unsigned int abr) +{ + unsigned int conf_boost = 0; + unsigned int mask; + unsigned int value; + pmic_version_t mc13783_ver; + + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + if (suspend_flag == 1) { + return -EBUSY; + } + + if (abms > MAX_BOOST_ABMS) { + return PMIC_PARAMETER_ERROR; + } + + if (abr > MAX_BOOST_ABR) { + return PMIC_PARAMETER_ERROR; + } + + conf_boost = abms | (abr << 3); + + value = BITFVAL(BITS_BOOST, conf_boost); + mask = BITFMASK(BITS_BOOST); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets boost mode configuration + * Only on mc13783 2.0 or higher + * + * @param abms Define adaptive boost mode selection + * @param abr Define adaptive boost reference + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_gets_boost_mode(unsigned int *abms, unsigned int *abr) +{ + unsigned int mask; + unsigned int value; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + if (suspend_flag == 1) { + return -EBUSY; + } + + mask = BITFMASK(BITS_BOOST_ABMS); + CHECK_ERROR(pmic_read_reg(LREG_0, &value, mask)); + *abms = BITFEXT(value, BITS_BOOST_ABMS); + + mask = BITFMASK(BITS_BOOST_ABR); + CHECK_ERROR(pmic_read_reg(LREG_0, &value, mask)); + *abr = BITFEXT(value, BITS_BOOST_ABR); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function implements IOCTL controls on a PMIC Light device. + * + + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_light_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + t_bklit_setting_param *bklit_setting = NULL; + t_tcled_enable_param *tcled_setting; + t_fun_param *fun_param; + t_tcled_ind_param *tcled_ind; + + if (_IOC_TYPE(cmd) != 'p') + return -ENOTTY; + + switch (cmd) { + case PMIC_BKLIT_TCLED_ENABLE: + pmic_bklit_tcled_master_enable(); + break; + + case PMIC_BKLIT_TCLED_DISABLE: + pmic_bklit_tcled_master_disable(); + break; + + case PMIC_BKLIT_ENABLE: + pmic_bklit_master_enable(); + break; + + case PMIC_BKLIT_DISABLE: + pmic_bklit_master_disable(); + break; + + case PMIC_SET_BKLIT: + if ((bklit_setting = kmalloc(sizeof(t_bklit_setting_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(bklit_setting, (t_bklit_setting_param *) arg, + sizeof(t_bklit_setting_param))) { + kfree(bklit_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_bklit_set_mode(bklit_setting->channel, + bklit_setting->mode), + (kfree(bklit_setting))); + + CHECK_ERROR_KFREE(pmic_bklit_set_current(bklit_setting->channel, + bklit_setting-> + current_level), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_set_dutycycle + (bklit_setting->channel, + bklit_setting->duty_cycle), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_set_cycle_time + (bklit_setting->cycle_time), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_set_boost_mode + (bklit_setting->en_dis), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_config_boost_mode + (bklit_setting->abms, bklit_setting->abr), + (kfree(bklit_setting))); + if (bklit_setting->edge_slow != false) { + CHECK_ERROR_KFREE(pmic_bklit_enable_edge_slow(), + (kfree(bklit_setting))); + } else { + CHECK_ERROR_KFREE(pmic_bklit_disable_edge_slow(), + (kfree(bklit_setting))); + } + + kfree(bklit_setting); + break; + + case PMIC_GET_BKLIT: + if ((bklit_setting = kmalloc(sizeof(t_bklit_setting_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + + if (copy_from_user(bklit_setting, (t_bklit_setting_param *) arg, + sizeof(t_bklit_setting_param))) { + kfree(bklit_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_bklit_get_current(bklit_setting->channel, + &bklit_setting-> + current_level), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_cycle_time + (&bklit_setting->cycle_time), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_dutycycle + (bklit_setting->channel, + &bklit_setting->duty_cycle), + (kfree(bklit_setting))); + bklit_setting->strobe = BACKLIGHT_STROBE_NONE; + CHECK_ERROR_KFREE(pmic_bklit_get_mode(bklit_setting->channel, + &bklit_setting->mode), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_edge_slow + (&bklit_setting->edge_slow), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_boost_mode + (&bklit_setting->en_dis), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_gets_boost_mode + (&bklit_setting->abms, &bklit_setting->abr), + (kfree(bklit_setting))); + + if (copy_to_user((t_bklit_setting_param *) arg, bklit_setting, + sizeof(t_bklit_setting_param))) { + kfree(bklit_setting); + return -EFAULT; + } + kfree(bklit_setting); + break; + + case PMIC_RAMPUP_BKLIT: + CHECK_ERROR(pmic_bklit_rampup((t_bklit_channel) arg)); + break; + + case PMIC_RAMPDOWN_BKLIT: + CHECK_ERROR(pmic_bklit_rampdown((t_bklit_channel) arg)); + break; + + case PMIC_OFF_RAMPUP_BKLIT: + CHECK_ERROR(pmic_bklit_off_rampup((t_bklit_channel) arg)); + break; + + case PMIC_OFF_RAMPDOWN_BKLIT: + CHECK_ERROR(pmic_bklit_off_rampdown((t_bklit_channel) arg)); + break; + + case PMIC_TCLED_ENABLE: + if ((tcled_setting = kmalloc(sizeof(t_tcled_enable_param), + GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + + if (copy_from_user(tcled_setting, (t_tcled_enable_param *) arg, + sizeof(t_tcled_enable_param))) { + kfree(tcled_setting); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_tcled_enable(tcled_setting->mode, + tcled_setting->bank), + (kfree(bklit_setting))); + break; + + case PMIC_TCLED_DISABLE: + CHECK_ERROR(pmic_tcled_disable((t_funlight_bank) arg)); + break; + + case PMIC_TCLED_PATTERN: + if ((fun_param = kmalloc(sizeof(t_fun_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(fun_param, + (t_fun_param *) arg, sizeof(t_fun_param))) { + kfree(fun_param); + return -EFAULT; + } + + switch (fun_param->pattern) { + case BLENDED_RAMPS_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedramps + (fun_param->bank, TC_SLOW), + (kfree(fun_param))); + break; + + case BLENDED_RAMPS_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedramps + (fun_param->bank, TC_FAST), + (kfree(fun_param))); + break; + + case SAW_RAMPS_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_sawramps + (fun_param->bank, TC_SLOW), + (kfree(fun_param))); + break; + + case SAW_RAMPS_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_sawramps + (fun_param->bank, TC_FAST), + (kfree(fun_param))); + break; + + case BLENDED_BOWTIE_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedbowtie + (fun_param->bank, TC_SLOW), + (kfree(fun_param))); + break; + + case BLENDED_BOWTIE_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedbowtie + (fun_param->bank, TC_FAST), + (kfree(fun_param))); + break; + + case STROBE_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_strobe + (fun_param->bank, fun_param->channel, + TC_STROBE_SLOW), (kfree(fun_param))); + break; + + case STROBE_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_strobe + (fun_param->bank, + fun_param->channel, TC_STROBE_SLOW), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_RGB_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, PMIC_RGB, TC_SLOW), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_RGB_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, PMIC_RGB, TC_FAST), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_BGR_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, BGR, TC_SLOW), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_BGR_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, BGR, TC_FAST), + (kfree(fun_param))); + break; + } + + kfree(fun_param); + break; + + case PMIC_SET_TCLED: + if ((tcled_ind = kmalloc(sizeof(t_tcled_ind_param), GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + + if (copy_from_user(tcled_ind, (t_tcled_ind_param *) arg, + sizeof(t_tcled_ind_param))) { + kfree(tcled_ind); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_tcled_ind_set_current(tcled_ind->channel, + tcled_ind->level, + tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_ind_set_blink_pattern + (tcled_ind->channel, tcled_ind->pattern, + tcled_ind->skip, tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_fun_rampup + (tcled_ind->bank, tcled_ind->channel, + tcled_ind->rampup), (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_fun_rampdown + (tcled_ind->bank, tcled_ind->channel, + tcled_ind->rampdown), (kfree(tcled_ind))); + if (tcled_ind->half_current) { + CHECK_ERROR_KFREE(pmic_tcled_enable_half_current(), + (kfree(tcled_ind))); + } else { + CHECK_ERROR_KFREE(pmic_tcled_disable_half_current(), + (kfree(tcled_ind))); + } + + kfree(tcled_ind); + break; + + case PMIC_GET_TCLED: + if ((tcled_ind = kmalloc(sizeof(t_tcled_ind_param), GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + if (copy_from_user(tcled_ind, (t_tcled_ind_param *) arg, + sizeof(t_tcled_ind_param))) { + kfree(tcled_ind); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_tcled_ind_get_current(tcled_ind->channel, + &tcled_ind->level, + tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_ind_get_blink_pattern + (tcled_ind->channel, &tcled_ind->pattern, + &tcled_ind->skip, tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_get_fun_rampup + (tcled_ind->bank, tcled_ind->channel, + &tcled_ind->rampup), (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_get_fun_rampdown + (tcled_ind->bank, tcled_ind->channel, + &tcled_ind->rampdown), (kfree(tcled_ind))); + if (copy_to_user + ((t_tcled_ind_param *) arg, tcled_ind, + sizeof(t_tcled_ind_param))) { + return -EFAULT; + } + kfree(tcled_ind); + + break; + + default: + return -EINVAL; + } + return 0; +} + +/*! + * This function initialize Light registers of mc13783 with 0. + * + * @return This function returns 0 if successful. + */ +int pmic_light_init_reg(void) +{ + CHECK_ERROR(pmic_write_reg(LREG_0, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_1, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_2, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_3, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_4, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_5, 0, PMIC_ALL_BITS)); + return 0; +} + +/*! + * This function implements the open method on a mc13783 light device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_light_open(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + return 0; +} + +/*! + * This function implements the release method on a mc13783 light device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_light_release(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + return 0; +} + +static struct file_operations pmic_light_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_light_ioctl, + .open = pmic_light_open, + .release = pmic_light_release, +}; + +static int pmic_light_remove(struct platform_device *pdev) +{ + device_destroy(pmic_light_class, MKDEV(pmic_light_major, 0)); + class_destroy(pmic_light_class); + unregister_chrdev(pmic_light_major, "pmic_light"); + return 0; +} + +static int pmic_light_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *temp_class; + + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + pmic_light_major = register_chrdev(0, "pmic_light", &pmic_light_fops); + + if (pmic_light_major < 0) { + printk(KERN_ERR "Unable to get a major for pmic_light\n"); + return pmic_light_major; + } + init_waitqueue_head(&suspendq); + + pmic_light_class = class_create(THIS_MODULE, "pmic_light"); + if (IS_ERR(pmic_light_class)) { + printk(KERN_ERR "Error creating pmic_light class.\n"); + ret = PTR_ERR(pmic_light_class); + goto err_out1; + } + + temp_class = device_create(pmic_light_class, NULL, + MKDEV(pmic_light_major, 0), NULL, + "pmic_light"); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating pmic_light class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + ret = pmic_light_init_reg(); + if (ret != PMIC_SUCCESS) { + goto err_out3; + } + + printk(KERN_INFO "PMIC Light successfully loaded\n"); + return ret; + + err_out3: + device_destroy(pmic_light_class, MKDEV(pmic_light_major, 0)); + err_out2: + class_destroy(pmic_light_class); + err_out1: + unregister_chrdev(pmic_light_major, "pmic_light"); + return ret; +} + +static struct platform_driver pmic_light_driver_ldm = { + .driver = { + .name = "pmic_light", + }, + .suspend = pmic_light_suspend, + .resume = pmic_light_resume, + .probe = pmic_light_probe, + .remove = pmic_light_remove, +}; + +/* + * Initialization and Exit + */ + +static int __init pmic_light_init(void) +{ + pr_debug("PMIC Light driver loading...\n"); + return platform_driver_register(&pmic_light_driver_ldm); +} +static void __exit pmic_light_exit(void) +{ + platform_driver_unregister(&pmic_light_driver_ldm); + pr_debug("PMIC Light driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_light_init); +module_exit(pmic_light_exit); + +MODULE_DESCRIPTION("PMIC_LIGHT"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_light_defs.h b/drivers/mxc/pmic/mc13783/pmic_light_defs.h new file mode 100644 index 000000000000..80082363d9ed --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_light_defs.h @@ -0,0 +1,144 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_light_defs.h + * @brief This is the internal header PMIC(mc13783) Light and Backlight driver. + * + * @ingroup PMIC_LIGHT + */ + +#ifndef __MC13783_LIGHT_DEFS_H__ +#define __MC13783_LIGHT_DEFS_H__ + +#define LREG_0 REG_LED_CONTROL_0 +#define LREG_1 REG_LED_CONTROL_1 +#define LREG_2 REG_LED_CONTROL_2 +#define LREG_3 REG_LED_CONTROL_3 +#define LREG_4 REG_LED_CONTROL_4 +#define LREG_5 REG_LED_CONTROL_5 + +/* REG_LED_CONTROL_0 */ + +#define BIT_LEDEN_LSH 0 +#define BIT_LEDEN_WID 1 +#define MASK_TRIODE_MAIN_BL 0x080 +#define INDEX_AUXILIARY 1 +#define INDEX_KEYPAD 2 +#define BITS_FUN_LIGHT_LSH 17 +#define BITS_FUN_LIGHT_WID 4 +#define MASK_FUN_LIGHT 0x1E0000 +#define MASK_BK1_FL 0x200000 +#define ENABLE_BK1_FL 0x200000 +#define MASK_BK2_FL 0x400000 +#define ENABLE_BK2_FL 0x400000 +#define MASK_BK3_FL 0x800000 +#define ENABLE_BK3_FL 0x800000 +#define BIT_UP_MAIN_BL_LSH 1 +#define BIT_UP_MAIN_BL_WID 1 +#define BIT_UP_AUX_BL_LSH 2 +#define BIT_UP_AUX_BL_WID 1 +#define BIT_UP_KEY_BL_LSH 3 +#define BIT_UP_KEY_BL_WID 1 +#define BIT_DOWN_MAIN_BL_LSH 4 +#define BIT_DOWN_MAIN_BL_WID 1 +#define BIT_DOWN_AUX_BL_LSH 5 +#define BIT_DOWN_AUX_BL_WID 1 +#define BIT_DOWN_KEY_BL_LSH 6 +#define BIT_DOWN_KEY_BL_WID 1 +#define BIT_TRIODE_MAIN_BL_LSH 7 +#define BIT_TRIODE_MAIN_BL_WID 1 +#define BIT_TRIODE_AUX_BL_LSH 8 +#define BIT_TRIODE_AUX_BL_WID 1 +#define BIT_TRIODE_KEY_BL_LSH 9 +#define BIT_TRIODE_KEY_BL_WID 1 + +#define BIT_BOOSTEN_LSH 10 +#define BIT_BOOSTEN_WID 1 +#define BITS_BOOST_LSH 11 +#define BITS_BOOST_WID 5 +#define BITS_BOOST_ABMS_LSH 11 +#define BITS_BOOST_ABMS_WID 3 +#define BITS_BOOST_ABR_LSH 14 +#define BITS_BOOST_ABR_WID 2 + +#define MAX_BOOST_ABMS 7 +#define MAX_BOOST_ABR 3 + +/* REG_LED_CONTROL_1 */ + +#define BIT_SLEWLIMTC_LSH 23 +#define BIT_SLEWLIMTC_WID 1 +#define BIT_TC1HALF_LSH 18 +#define BIT_TC1HALF_WID 1 +#define LEDR1RAMPUP 0x000001 +#define LEDR2RAMPUP 0x000040 +#define LEDR3RAMPUP 0x001000 +#define LEDR1RAMPDOWN 0x000008 +#define LEDR2RAMPDOWN 0x000200 +#define LEDR3RAMPDOWN 0x008000 + +/* REG_LED_CONTROL_2 */ + +#define BIT_SLEWLIMBL_LSH 23 +#define BIT_SLEWLIMBL_WID 1 +#define BIT_DUTY_CYCLE 9 +#define MASK_DUTY_CYCLE 0x001E00 +#define INDEX_AUX 4 +#define INDEX_KYD 8 +#define BIT_CL_MAIN_LSH 0 +#define BIT_CL_MAIN_WID 3 +#define BIT_CL_AUX_LSH 3 +#define BIT_CL_AUX_WID 3 +#define BIT_CL_KEY_LSH 6 +#define BIT_CL_KEY_WID 3 + +/* REG_LED_CONTROL_3 4 5 */ +#define BITS_CL_RED_LSH 0 +#define BITS_CL_RED_WID 2 +#define BITS_CL_GREEN_LSH 2 +#define BITS_CL_GREEN_WID 2 +#define BITS_CL_BLUE_LSH 4 +#define BITS_CL_BLUE_WID 2 +#define BITS_DC_RED_LSH 6 +#define BITS_DC_RED_WID 5 +#define BITS_DC_GREEN_LSH 11 +#define BITS_DC_GREEN_WID 5 +#define BITS_DC_BLUE_LSH 16 +#define BITS_DC_BLUE_WID 5 +#define BIT_PERIOD_LSH 21 +#define BIT_PERIOD_WID 2 + +#define DUTY_CYCLE_MAX 31 + +/* Fun light pattern */ +#define BLENDED_RAMPS_SLOW 0 +#define BLENDED_RAMPS_FAST 1 +#define SAW_RAMPS_SLOW 2 +#define SAW_RAMPS_FAST 3 +#define BLENDED_INVERSE_RAMPS_SLOW 4 +#define BLENDED_INVERSE_RAMPS_FAST 5 +#define CHASING_LIGHTS_RGB_SLOW 6 +#define CHASING_LIGHTS_RGB_FAST 7 +#define CHASING_LIGHTS_BGR_SLOW 8 +#define CHASING_LIGHTS_BGR_FAST 9 +#define FUN_LIGHTS_OFF 15 + +/*! + * This function initialize Light registers of mc13783 with 0. + * + * @return This function returns 0 if successful. + */ +int pmic_light_init_reg(void); + +#endif /* __MC13783_LIGHT_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_power.c b/drivers/mxc/pmic/mc13783/pmic_power.c new file mode 100644 index 000000000000..8f877b887e16 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_power.c @@ -0,0 +1,3146 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_power.c + * @brief This is the main file of PMIC(mc13783) Power driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ + +#include <linux/platform_device.h> +#include <linux/ioctl.h> +#include <linux/pmic_status.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <mach/pmic_power.h> + +#include "pmic_power_defs.h" + +#ifdef CONFIG_MXC_HWEVENT +#include <mach/hw_events.h> +#endif + +#include <asm/mach-types.h> + +#define MC13783_REGCTRL_GPOx_MASK 0x18000 + +static bool VBKUP1_EN = false; +static bool VBKUP2_EN = false; + +/* + * Power Pmic API + */ + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_power_off); +EXPORT_SYMBOL(pmic_power_set_pc_config); +EXPORT_SYMBOL(pmic_power_get_pc_config); +EXPORT_SYMBOL(pmic_power_regulator_on); +EXPORT_SYMBOL(pmic_power_regulator_off); +EXPORT_SYMBOL(pmic_power_regulator_set_voltage); +EXPORT_SYMBOL(pmic_power_regulator_get_voltage); +EXPORT_SYMBOL(pmic_power_regulator_set_config); +EXPORT_SYMBOL(pmic_power_regulator_get_config); +EXPORT_SYMBOL(pmic_power_vbkup2_auto_en); +EXPORT_SYMBOL(pmic_power_get_vbkup2_auto_state); +EXPORT_SYMBOL(pmic_power_bat_det_en); +EXPORT_SYMBOL(pmic_power_get_bat_det_state); +EXPORT_SYMBOL(pmic_power_vib_pin_en); +EXPORT_SYMBOL(pmic_power_gets_vib_pin_state); +EXPORT_SYMBOL(pmic_power_get_power_mode_sense); +EXPORT_SYMBOL(pmic_power_set_regen_assig); +EXPORT_SYMBOL(pmic_power_get_regen_assig); +EXPORT_SYMBOL(pmic_power_set_regen_inv); +EXPORT_SYMBOL(pmic_power_get_regen_inv); +EXPORT_SYMBOL(pmic_power_esim_v_en); +EXPORT_SYMBOL(pmic_power_gets_esim_v_state); +EXPORT_SYMBOL(pmic_power_set_auto_reset_en); +EXPORT_SYMBOL(pmic_power_get_auto_reset_en); +EXPORT_SYMBOL(pmic_power_set_conf_button); +EXPORT_SYMBOL(pmic_power_get_conf_button); +EXPORT_SYMBOL(pmic_power_event_sub); +EXPORT_SYMBOL(pmic_power_event_unsub); + +/*! + * This function is called to put the power in a low power state. + * Switching off the platform cannot be decided by + * the power module. It has to be handled by the + * client application. + * + * @param pdev the device structure used to give information on which power + * device (0 through 3 channels) to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int pmic_power_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +}; + +/*! + * This function is called to resume the power from a low power state. + * + * @param pdev the device structure used to give information on which power + * device (0 through 3 channels) to suspend + * + * @return The function always returns 0. + */ +static int pmic_power_resume(struct platform_device *pdev) +{ + return 0; +}; + +/*! + * This function sets user power off in power control register and thus powers + * off the phone. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +void pmic_power_off(void) +{ + unsigned int mask, value; + + mask = BITFMASK(MC13783_PWRCTRL_USER_OFF_SPI); + value = BITFVAL(MC13783_PWRCTRL_USER_OFF_SPI, + MC13783_PWRCTRL_USER_OFF_SPI_ENABLE); + + pmic_write_reg(REG_POWER_CONTROL_0, value, mask); +} + +/*! + * This function sets the power control configuration. + * + * @param pc_config power control configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_set_pc_config(t_pc_config * pc_config) +{ + unsigned int pwrctrl_val_reg0 = 0; + unsigned int pwrctrl_val_reg1 = 0; + unsigned int pwrctrl_mask_reg0 = 0; + unsigned int pwrctrl_mask_reg1 = 0; + + if (pc_config == NULL) { + return PMIC_PARAMETER_ERROR; + } + + if (pc_config->pc_enable != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PCEN, + MC13783_PWRCTRL_PCEN_ENABLE); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_PCT, + pc_config->pc_timer); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PCEN, + MC13783_PWRCTRL_PCEN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_PCEN); + pwrctrl_mask_reg1 |= BITFMASK(MC13783_PWRCTRL_PCT); + + if (pc_config->pc_count_enable != false) { + + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PC_COUNT_EN, + MC13783_PWRCTRL_PC_COUNT_EN_ENABLE); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_PC_COUNT, + pc_config->pc_count); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_PC_MAX_CNT, + pc_config->pc_max_count); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PC_COUNT_EN, + MC13783_PWRCTRL_PC_COUNT_EN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_PC_COUNT_EN); + pwrctrl_mask_reg1 |= BITFMASK(MC13783_PWRCTRL_PC_MAX_CNT) | + BITFMASK(MC13783_PWRCTRL_PC_COUNT); + + if (pc_config->warm_enable != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_WARM_EN, + MC13783_PWRCTRL_WARM_EN_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_WARM_EN, + MC13783_PWRCTRL_WARM_EN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_WARM_EN); + + if (pc_config->user_off_pc != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_USER_OFF_PC, + MC13783_PWRCTRL_USER_OFF_PC_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_WARM_EN, + MC13783_PWRCTRL_USER_OFF_PC_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_USER_OFF_PC); + + if (pc_config->clk_32k_user_off != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_USER_OFF, + MC13783_PWRCTRL_32OUT_USER_OFF_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_USER_OFF, + MC13783_PWRCTRL_32OUT_USER_OFF_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_32OUT_USER_OFF); + + if (pc_config->clk_32k_enable != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_EN, + MC13783_PWRCTRL_32OUT_EN_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_EN, + MC13783_PWRCTRL_32OUT_EN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_32OUT_EN); + + if (pc_config->en_vbkup1 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + VBKUP1_EN = true; + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + VBKUP1_EN = false; + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP1_EN); + + if (pc_config->en_vbkup2 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + VBKUP2_EN = true; + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + VBKUP2_EN = false; + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP2_EN); + + if (pc_config->auto_en_vbkup1 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_AUTO_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_AUTO_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP1_AUTO_EN); + + if (pc_config->auto_en_vbkup2 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_AUTO_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_AUTO_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP2_AUTO_EN); + + if (VBKUP1_EN != false) { + if (pc_config->vhold_voltage > 3 + || pc_config->vhold_voltage < 0) { + return PMIC_PARAMETER_ERROR; + } else { + + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1, + pc_config->vhold_voltage); + } + } + if (VBKUP2_EN != false) { + if (pc_config->vhold_voltage > 3 + || pc_config->vhold_voltage < 0) { + return PMIC_PARAMETER_ERROR; + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2, + pc_config->vhold_voltage2); + } + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP1) | + BITFMASK(MC13783_PWRCTRL_VBKUP2); + + if (pc_config->mem_allon != false) { + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_MEM_ALLON, + MC13783_PWRCTRL_MEM_ALLON_ENABLE); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_MEM_TMR, + pc_config->mem_timer); + } else { + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_MEM_ALLON, + MC13783_PWRCTRL_MEM_ALLON_DISABLE); + } + pwrctrl_mask_reg1 |= BITFMASK(MC13783_PWRCTRL_MEM_ALLON) | + BITFMASK(MC13783_PWRCTRL_MEM_TMR); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, + pwrctrl_val_reg0, pwrctrl_mask_reg0)); + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_1, + pwrctrl_val_reg1, pwrctrl_mask_reg1)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives the power control configuration. + * + * @param pc_config pointer to power control configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_pc_config(t_pc_config * pc_config) +{ + unsigned int pwrctrl_val_reg0 = 0; + unsigned int pwrctrl_val_reg1 = 0; + + if (pc_config == NULL) { + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_0, + &pwrctrl_val_reg0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_1, + &pwrctrl_val_reg1, PMIC_ALL_BITS)); + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_PCEN) + == MC13783_PWRCTRL_PCEN_ENABLE) { + pc_config->pc_enable = true; + pc_config->pc_timer = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_PCT); + + } else { + pc_config->pc_enable = false; + pc_config->pc_timer = 0; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_PC_COUNT_EN) + == MC13783_PWRCTRL_PCEN_ENABLE) { + pc_config->pc_count_enable = true; + pc_config->pc_count = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_PC_COUNT); + pc_config->pc_max_count = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_PC_MAX_CNT); + } else { + pc_config->pc_count_enable = false; + pc_config->pc_count = 0; + pc_config->pc_max_count = 0; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_WARM_EN) + == MC13783_PWRCTRL_WARM_EN_ENABLE) { + pc_config->warm_enable = true; + } else { + pc_config->warm_enable = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_USER_OFF_PC) + == MC13783_PWRCTRL_USER_OFF_PC_ENABLE) { + pc_config->user_off_pc = true; + } else { + pc_config->user_off_pc = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_32OUT_USER_OFF) + == MC13783_PWRCTRL_32OUT_USER_OFF_ENABLE) { + pc_config->clk_32k_user_off = true; + } else { + pc_config->clk_32k_user_off = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_32OUT_EN) + == MC13783_PWRCTRL_32OUT_EN_ENABLE) { + pc_config->clk_32k_enable = true; + } else { + pc_config->clk_32k_enable = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP1_AUTO_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->auto_en_vbkup1 = true; + } else { + pc_config->auto_en_vbkup1 = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP2_AUTO_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->auto_en_vbkup2 = true; + } else { + pc_config->auto_en_vbkup2 = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP1_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->en_vbkup1 = true; + pc_config->vhold_voltage = BITFEXT(pwrctrl_val_reg0, + MC13783_PWRCTRL_VBKUP1); + } else { + pc_config->en_vbkup1 = false; + pc_config->vhold_voltage = 0; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP2_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->en_vbkup2 = true; + pc_config->vhold_voltage2 = BITFEXT(pwrctrl_val_reg0, + MC13783_PWRCTRL_VBKUP2); + } else { + pc_config->en_vbkup2 = false; + pc_config->vhold_voltage2 = 0; + } + + if (BITFEXT(pwrctrl_val_reg1, MC13783_PWRCTRL_MEM_ALLON) == + MC13783_PWRCTRL_MEM_ALLON_ENABLE) { + pc_config->mem_allon = true; + pc_config->mem_timer = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_MEM_TMR); + } else { + pc_config->mem_allon = false; + pc_config->mem_timer = 0; + } + + return PMIC_SUCCESS; +} + +/*! + * This function turns on a regulator. + * + * @param regulator The regulator to be truned on. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_on(t_pmic_regulator regulator) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_PLL: + reg_val = BITFVAL(MC13783_SWCTRL_PLL_EN, + MC13783_SWCTRL_PLL_EN_ENABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_EN); + reg = REG_SWITCHERS_4; + break; + case SW_SW3: + reg_val = BITFVAL(MC13783_SWCTRL_SW3_EN, + MC13783_SWCTRL_SW3_EN_ENABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_EN); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGCTRL_VAUDIO_EN, + MC13783_REGCTRL_VAUDIO_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGCTRL_VIOHI_EN, + MC13783_REGCTRL_VIOHI_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGCTRL_VIOLO_EN, + MC13783_REGCTRL_VIOLO_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VDIG_EN, + MC13783_REGCTRL_VDIG_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGCTRL_VGEN_EN, + MC13783_REGCTRL_VGEN_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFDIG_EN, + MC13783_REGCTRL_VRFDIG_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGCTRL_VRFREF_EN, + MC13783_REGCTRL_VRFREF_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGCTRL_VRFCP_EN, + MC13783_REGCTRL_VRFCP_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_val = BITFVAL(MC13783_REGCTRL_VSIM_EN, + MC13783_REGCTRL_VSIM_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_val = BITFVAL(MC13783_REGCTRL_VESIM_EN, + MC13783_REGCTRL_VESIM_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGCTRL_VCAM_EN, + MC13783_REGCTRL_VCAM_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFBG_EN, + MC13783_REGCTRL_VRFBG_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VVIB: + reg_val = BITFVAL(MC13783_REGCTRL_VVIB_EN, + MC13783_REGCTRL_VVIB_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VVIB_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGCTRL_VRF1_EN, + MC13783_REGCTRL_VRF1_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGCTRL_VRF2_EN, + MC13783_REGCTRL_VRF2_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC1_EN, + MC13783_REGCTRL_VMMC1_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC2_EN, + MC13783_REGCTRL_VMMC2_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_GPO1: + reg_val = BITFVAL(MC13783_REGCTRL_GPO1_EN, + MC13783_REGCTRL_GPO1_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO1_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO2: + reg_val = BITFVAL(MC13783_REGCTRL_GPO2_EN, + MC13783_REGCTRL_GPO2_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO2_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO3: + reg_val = BITFVAL(MC13783_REGCTRL_GPO3_EN, + MC13783_REGCTRL_GPO3_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO3_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO4: + reg_val = BITFVAL(MC13783_REGCTRL_GPO4_EN, + MC13783_REGCTRL_GPO4_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO4_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function turns off a regulator. + * + * @param regulator The regulator to be truned off. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_off(t_pmic_regulator regulator) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_PLL: + reg_val = BITFVAL(MC13783_SWCTRL_PLL_EN, + MC13783_SWCTRL_PLL_EN_DISABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_EN); + reg = REG_SWITCHERS_4; + break; + case SW_SW3: + reg_val = BITFVAL(MC13783_SWCTRL_SW3_EN, + MC13783_SWCTRL_SW3_EN_DISABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_EN); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGCTRL_VAUDIO_EN, + MC13783_REGCTRL_VAUDIO_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGCTRL_VIOHI_EN, + MC13783_REGCTRL_VIOHI_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGCTRL_VIOLO_EN, + MC13783_REGCTRL_VIOLO_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VDIG_EN, + MC13783_REGCTRL_VDIG_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGCTRL_VGEN_EN, + MC13783_REGCTRL_VGEN_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFDIG_EN, + MC13783_REGCTRL_VRFDIG_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGCTRL_VRFREF_EN, + MC13783_REGCTRL_VRFREF_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGCTRL_VRFCP_EN, + MC13783_REGCTRL_VRFCP_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_val = BITFVAL(MC13783_REGCTRL_VSIM_EN, + MC13783_REGCTRL_VSIM_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_val = BITFVAL(MC13783_REGCTRL_VESIM_EN, + MC13783_REGCTRL_VESIM_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGCTRL_VCAM_EN, + MC13783_REGCTRL_VCAM_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFBG_EN, + MC13783_REGCTRL_VRFBG_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VVIB: + reg_val = BITFVAL(MC13783_REGCTRL_VVIB_EN, + MC13783_REGCTRL_VVIB_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VVIB_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGCTRL_VRF1_EN, + MC13783_REGCTRL_VRF1_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGCTRL_VRF2_EN, + MC13783_REGCTRL_VRF2_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC1_EN, + MC13783_REGCTRL_VMMC1_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC2_EN, + MC13783_REGCTRL_VMMC2_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_GPO1: + reg_val = BITFVAL(MC13783_REGCTRL_GPO1_EN, + MC13783_REGCTRL_GPO1_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO1_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO2: + reg_val = BITFVAL(MC13783_REGCTRL_GPO2_EN, + MC13783_REGCTRL_GPO2_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO2_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO3: + reg_val = BITFVAL(MC13783_REGCTRL_GPO3_EN, + MC13783_REGCTRL_GPO3_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO3_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO4: + reg_val = BITFVAL(MC13783_REGCTRL_GPO4_EN, + MC13783_REGCTRL_GPO4_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO4_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets the regulator output voltage. + * + * @param regulator The regulator to be configured. + * @param voltage The regulator output voltage. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_voltage(t_pmic_regulator regulator, + t_regulator_voltage voltage) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + if ((voltage.sw1a < SW1A_0_9V) || (voltage.sw1a > SW1A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1A, voltage.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW1A); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + if ((voltage.sw1b < SW1B_0_9V) || (voltage.sw1b > SW1B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1B, voltage.sw1b); + reg_mask = BITFMASK(MC13783_SWSET_SW1B); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + if ((voltage.sw2a < SW2A_0_9V) || (voltage.sw2a > SW2A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2A, voltage.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW2A); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + if ((voltage.sw2b < SW2B_0_9V) || (voltage.sw2b > SW2B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2B, voltage.sw2b); + reg_mask = BITFMASK(MC13783_SWSET_SW1A); + reg = REG_SWITCHERS_3; + break; + case SW_SW3: + if (voltage.sw3 != SW3_5V) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW3, voltage.sw3); + reg_mask = BITFMASK(MC13783_SWSET_SW3); + reg = REG_SWITCHERS_5; + break; + case REGU_VIOLO: + if ((voltage.violo < VIOLO_1_2V) || + (voltage.violo > VIOLO_1_8V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VIOLO, voltage.violo); + reg_mask = BITFMASK(MC13783_REGSET_VIOLO); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VDIG: + if ((voltage.vdig < VDIG_1_2V) || (voltage.vdig > VDIG_1_8V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VDIG, voltage.vdig); + reg_mask = BITFMASK(MC13783_REGSET_VDIG); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VGEN: + if ((voltage.vgen < VGEN_1_2V) || (voltage.vgen > VGEN_2_4V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VGEN, voltage.vgen); + reg_mask = BITFMASK(MC13783_REGSET_VGEN); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VRFDIG: + if ((voltage.vrfdig < VRFDIG_1_2V) || + (voltage.vrfdig > VRFDIG_1_875V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRFDIG, voltage.vrfdig); + reg_mask = BITFMASK(MC13783_REGSET_VRFDIG); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VRFREF: + if ((voltage.vrfref < VRFREF_2_475V) || + (voltage.vrfref > VRFREF_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRFREF, voltage.vrfref); + reg_mask = BITFMASK(MC13783_REGSET_VRFREF); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VRFCP: + if ((voltage.vrfcp < VRFCP_2_7V) || + (voltage.vrfcp > VRFCP_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRFCP, voltage.vrfcp); + reg_mask = BITFMASK(MC13783_REGSET_VRFCP); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VSIM: + if ((voltage.vsim < VSIM_1_8V) || (voltage.vsim > VSIM_2_9V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VSIM, voltage.vsim); + reg_mask = BITFMASK(MC13783_REGSET_VSIM); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VESIM: + if ((voltage.vesim < VESIM_1_8V) || + (voltage.vesim > VESIM_2_9V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VESIM, voltage.vesim); + reg_mask = BITFMASK(MC13783_REGSET_VESIM); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VCAM: + if ((voltage.vcam < VCAM_1_5V) || (voltage.vcam > VCAM_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VCAM, voltage.vcam); + reg_mask = BITFMASK(MC13783_REGSET_VCAM); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VVIB: + if ((voltage.vvib < VVIB_1_3V) || (voltage.vvib > VVIB_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VVIB, voltage.vvib); + reg_mask = BITFMASK(MC13783_REGSET_VVIB); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VRF1: + if ((voltage.vrf1 < VRF1_1_5V) || (voltage.vrf1 > VRF1_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRF1, voltage.vrf1); + reg_mask = BITFMASK(MC13783_REGSET_VRF1); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VRF2: + if ((voltage.vrf2 < VRF2_1_5V) || (voltage.vrf2 > VRF2_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRF2, voltage.vrf2); + reg_mask = BITFMASK(MC13783_REGSET_VRF2); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VMMC1: + if ((voltage.vmmc1 < VMMC1_1_6V) || (voltage.vmmc1 > VMMC1_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VMMC1, voltage.vmmc1); + reg_mask = BITFMASK(MC13783_REGSET_VMMC1); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VMMC2: + if ((voltage.vmmc2 < VMMC2_1_6V) || (voltage.vmmc2 > VMMC2_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VMMC2, voltage.vmmc2); + reg_mask = BITFMASK(MC13783_REGSET_VMMC2); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VAUDIO: + case REGU_VIOHI: + case REGU_VRFBG: + case REGU_GPO1: + case REGU_GPO2: + case REGU_GPO3: + case REGU_GPO4: + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives the regulator output voltage. + * + * @param regulator The regulator to be truned off. + * @param voltage Pointer to regulator output voltage. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_voltage(t_pmic_regulator regulator, + t_regulator_voltage * voltage) +{ + unsigned int reg_val = 0; + + if (regulator == SW_SW1A) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_0, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW1B) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_1, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW2A) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_2, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW2B) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_3, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW3) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_5, + ®_val, PMIC_ALL_BITS)); + } else if ((regulator == REGU_VIOLO) || (regulator == REGU_VDIG) || + (regulator == REGU_VGEN) || + (regulator == REGU_VRFDIG) || + (regulator == REGU_VRFREF) || + (regulator == REGU_VRFCP) || + (regulator == REGU_VSIM) || + (regulator == REGU_VESIM) || (regulator == REGU_VCAM)) { + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_0, + ®_val, PMIC_ALL_BITS)); + } else if ((regulator == REGU_VVIB) || (regulator == REGU_VRF1) || + (regulator == REGU_VRF2) || + (regulator == REGU_VMMC1) || (regulator == REGU_VMMC2)) { + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_1, + ®_val, PMIC_ALL_BITS)); + } + + switch (regulator) { + case SW_SW1A: + voltage->sw1a = BITFEXT(reg_val, MC13783_SWSET_SW1A); + break; + case SW_SW1B: + voltage->sw1b = BITFEXT(reg_val, MC13783_SWSET_SW1B); + break; + case SW_SW2A: + voltage->sw2a = BITFEXT(reg_val, MC13783_SWSET_SW2A); + break; + case SW_SW2B: + voltage->sw2b = BITFEXT(reg_val, MC13783_SWSET_SW2B); + break; + case SW_SW3: + voltage->sw3 = BITFEXT(reg_val, MC13783_SWSET_SW3); + break; + case REGU_VIOLO: + voltage->violo = BITFEXT(reg_val, MC13783_REGSET_VIOLO); + break; + case REGU_VDIG: + voltage->vdig = BITFEXT(reg_val, MC13783_REGSET_VDIG); + break; + case REGU_VGEN: + voltage->vgen = BITFEXT(reg_val, MC13783_REGSET_VGEN); + break; + case REGU_VRFDIG: + voltage->vrfdig = BITFEXT(reg_val, MC13783_REGSET_VRFDIG); + break; + case REGU_VRFREF: + voltage->vrfref = BITFEXT(reg_val, MC13783_REGSET_VRFREF); + break; + case REGU_VRFCP: + voltage->vrfcp = BITFEXT(reg_val, MC13783_REGSET_VRFCP); + break; + case REGU_VSIM: + voltage->vsim = BITFEXT(reg_val, MC13783_REGSET_VSIM); + break; + case REGU_VESIM: + voltage->vesim = BITFEXT(reg_val, MC13783_REGSET_VESIM); + break; + case REGU_VCAM: + voltage->vcam = BITFEXT(reg_val, MC13783_REGSET_VCAM); + break; + case REGU_VVIB: + voltage->vvib = BITFEXT(reg_val, MC13783_REGSET_VVIB); + break; + case REGU_VRF1: + voltage->vrf1 = BITFEXT(reg_val, MC13783_REGSET_VRF1); + break; + case REGU_VRF2: + voltage->vrf2 = BITFEXT(reg_val, MC13783_REGSET_VRF2); + break; + case REGU_VMMC1: + voltage->vmmc1 = BITFEXT(reg_val, MC13783_REGSET_VMMC1); + break; + case REGU_VMMC2: + voltage->vmmc2 = BITFEXT(reg_val, MC13783_REGSET_VMMC2); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the DVS voltage + * + * @param regulator The regulator to be configured. + * @param dvs The switch Dynamic Voltage Scaling + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_dvs(t_pmic_regulator regulator, + t_regulator_voltage dvs) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + if ((dvs.sw1a < SW1A_0_9V) || (dvs.sw1a > SW1A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1A_DVS, dvs.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW1A_DVS); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + if ((dvs.sw1b < SW1B_0_9V) || (dvs.sw1b > SW1B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1B_DVS, dvs.sw1b); + reg_mask = BITFMASK(MC13783_SWSET_SW1B_DVS); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + if ((dvs.sw2a < SW2A_0_9V) || (dvs.sw2a > SW2A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2A_DVS, dvs.sw2a); + reg_mask = BITFMASK(MC13783_SWSET_SW2A_DVS); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + if ((dvs.sw2b < SW2B_0_9V) || (dvs.sw2b > SW2B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2B_DVS, dvs.sw2b); + reg_mask = BITFMASK(MC13783_SWSET_SW2B_DVS); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the DVS voltage + * + * @param regulator The regulator to be handled. + * @param dvs The switch Dynamic Voltage Scaling + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_dvs(t_pmic_regulator regulator, + t_regulator_voltage * dvs) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWSET_SW1A_DVS); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWSET_SW1B_DVS); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWSET_SW2A_DVS); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWSET_SW2B_DVS); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1A_DVS); + break; + case SW_SW1B: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1B_DVS); + break; + case SW_SW2A: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2A_DVS); + break; + case SW_SW2B: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2B_DVS); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the standiby voltage + * + * @param regulator The regulator to be configured. + * @param stby The switch standby voltage + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_stby(t_pmic_regulator regulator, + t_regulator_voltage stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + if ((stby.sw1a < SW1A_0_9V) || (stby.sw1a > SW1A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1A_STDBY, stby.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW1A_STDBY); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + if ((stby.sw1b < SW1B_0_9V) || (stby.sw1b > SW1B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1B_STDBY, stby.sw1b); + reg_mask = BITFMASK(MC13783_SWSET_SW1B_STDBY); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + if ((stby.sw2a < SW2A_0_9V) || (stby.sw2a > SW2A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2A_STDBY, stby.sw2a); + reg_mask = BITFMASK(MC13783_SWSET_SW2A_STDBY); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + if ((stby.sw2b < SW2B_0_9V) || (stby.sw2b > SW2B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2B_STDBY, stby.sw2b); + reg_mask = BITFMASK(MC13783_SWSET_SW2B_STDBY); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the standiby voltage + * + * @param regulator The regulator to be handled. + * @param stby The switch standby voltage + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_stby(t_pmic_regulator regulator, + t_regulator_voltage * stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWSET_SW1A_STDBY); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWSET_SW1B_STDBY); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWSET_SW2A_STDBY); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWSET_SW2B_STDBY); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1A_STDBY); + break; + case SW_SW1B: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1B_STDBY); + break; + case SW_SW2A: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2A_STDBY); + break; + case SW_SW2B: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2B_STDBY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switchers mode. + * + * @param regulator The regulator to be configured. + * @param mode The switcher mode + * @param stby Switch between main and standby. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_mode(t_pmic_regulator regulator, + t_regulator_sw_mode mode, bool stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + unsigned int l_mode; + + if (mode == SYNC_RECT) { + l_mode = MC13783_SWCTRL_SW_MODE_SYNC_RECT_EN; + } else if (mode == NO_PULSE_SKIP) { + l_mode = MC13783_SWCTRL_SW_MODE_PULSE_NO_SKIP_EN; + } else if (mode == PULSE_SKIP) { + l_mode = MC13783_SWCTRL_SW_MODE_PULSE_SKIP_EN; + } else if (mode == LOW_POWER) { + l_mode = MC13783_SWCTRL_SW_MODE_LOW_POWER_EN; + } else { + return PMIC_PARAMETER_ERROR; + } + + switch (regulator) { + case SW_SW1A: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW1A_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW1B_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW1B_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW2A_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_MODE); + } + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW2B_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_MODE); + } + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switchers mode. + * + * @param regulator The regulator to be handled. + * @param mode The switcher mode. + * @param stby Switch between main and standby. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_mode(t_pmic_regulator regulator, + t_regulator_sw_mode * mode, bool stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg = 0; + unsigned int l_mode = 0; + + switch (regulator) { + case SW_SW1A: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_MODE); + } + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_MODE); + } + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW1A_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_MODE); + } + break; + case SW_SW1B: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW1B_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1B_MODE); + } + break; + case SW_SW2A: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW2A_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_MODE); + } + break; + case SW_SW2B: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW2B_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_MODE); + } + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (l_mode == MC13783_SWCTRL_SW_MODE_SYNC_RECT_EN) { + *mode = SYNC_RECT; + } else if (l_mode == MC13783_SWCTRL_SW_MODE_PULSE_NO_SKIP_EN) { + *mode = NO_PULSE_SKIP; + } else if (l_mode == MC13783_SWCTRL_SW_MODE_PULSE_SKIP_EN) { + *mode = PULSE_SKIP; + } else if (l_mode == MC13783_SWCTRL_SW_MODE_LOW_POWER_EN) { + *mode = LOW_POWER; + } else { + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switch dvs speed + * + * @param regulator The regulator to be configured. + * @param speed The dvs speed. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_dvs_speed(t_pmic_regulator regulator, + t_switcher_dvs_speed speed) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + if (speed > 3 || speed < 0) { + return PMIC_PARAMETER_ERROR; + } + + switch (regulator) { + case SW_SW1A: + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switch dvs speed + * + * @param regulator The regulator to be handled. + * @param speed The dvs speed. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_dvs_speed(t_pmic_regulator regulator, + t_switcher_dvs_speed * speed) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_DVS_SPEED); + break; + case SW_SW1B: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW1B_DVS_SPEED); + break; + case SW_SW2A: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_DVS_SPEED); + break; + case SW_SW2B: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_DVS_SPEED); + break; + default: + break; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switch panic mode + * + * @param regulator The regulator to be configured. + * @param panic_mode Enable or disable panic mode + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_panic_mode(t_pmic_regulator regulator, + bool panic_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switch panic mode + * + * @param regulator The regulator to be handled + * @param panic_mode Enable or disable panic mode + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_panic_mode(t_pmic_regulator regulator, + bool * panic_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_PANIC_MODE); + break; + case SW_SW1B: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1B_PANIC_MODE); + break; + case SW_SW2A: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_PANIC_MODE); + break; + case SW_SW2B: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_PANIC_MODE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switch softstart mode + * + * @param regulator The regulator to be configured. + * @param softstart Enable or disable softstart. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_softstart(t_pmic_regulator regulator, + bool softstart) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switch softstart mode + * + * @param regulator The regulator to be handled + * @param softstart Enable or disable softstart. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_softstart(t_pmic_regulator regulator, + bool * softstart) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_SOFTSTART); + break; + case SW_SW1B: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_SOFTSTART); + break; + case SW_SW2A: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_SOFTSTART); + break; + case SW_SW2B: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_SOFTSTART); + break; + default: + break; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the PLL multiplication factor + * + * @param regulator The regulator to be configured. + * @param factor The multiplication factor. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_factor(t_pmic_regulator regulator, + t_switcher_factor factor) +{ + unsigned int reg_val = 0, reg_mask = 0; + + if (regulator != SW_PLL) { + return PMIC_PARAMETER_ERROR; + } + if (factor < FACTOR_28 || factor > FACTOR_35) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWCTRL_PLL_FACTOR, factor); + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_FACTOR); + + CHECK_ERROR(pmic_write_reg(REG_SWITCHERS_4, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the PLL multiplication factor + * + * @param regulator The regulator to be handled + * @param factor The multiplication factor. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_factor(t_pmic_regulator regulator, + t_switcher_factor * factor) +{ + unsigned int reg_val = 0, reg_mask = 0; + + if (regulator != SW_PLL) { + return PMIC_PARAMETER_ERROR; + } + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_FACTOR); + + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_4, ®_val, reg_mask)); + + *factor = BITFEXT(reg_val, MC13783_SWCTRL_PLL_FACTOR); + + return PMIC_SUCCESS; +} + +/*! + * This function enables or disables low power mode. + * + * @param regulator The regulator to be configured. + * @param lp_mode Select nominal or low power mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_lp_mode(t_pmic_regulator regulator, + t_regulator_lp_mode lp_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + unsigned int l_mode, l_stby; + + if (lp_mode == LOW_POWER_DISABLED) { + l_mode = MC13783_REGTRL_LP_MODE_DISABLE; + l_stby = MC13783_REGTRL_STBY_MODE_DISABLE; + } else if (lp_mode == LOW_POWER_CTRL_BY_PIN) { + l_mode = MC13783_REGTRL_LP_MODE_DISABLE; + l_stby = MC13783_REGTRL_STBY_MODE_ENABLE; + } else if (lp_mode == LOW_POWER_EN) { + l_mode = MC13783_REGTRL_LP_MODE_ENABLE; + l_stby = MC13783_REGTRL_STBY_MODE_DISABLE; + } else if (lp_mode == LOW_POWER_AND_LOW_POWER_CTRL_BY_PIN) { + l_mode = MC13783_REGTRL_LP_MODE_ENABLE; + l_stby = MC13783_REGTRL_STBY_MODE_ENABLE; + } else { + return PMIC_PARAMETER_ERROR; + } + + switch (regulator) { + case SW_SW3: + reg_val = BITFVAL(MC13783_SWCTRL_SW3_MODE, l_mode) | + BITFVAL(MC13783_SWCTRL_SW3_STBY, l_stby); + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_MODE) | + BITFMASK(MC13783_SWCTRL_SW3_STBY); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGCTRL_VAUDIO_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VAUDIO_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_MODE) | + BITFMASK(MC13783_REGCTRL_VAUDIO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGCTRL_VIOHI_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VIOHI_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_MODE) | + BITFMASK(MC13783_REGCTRL_VIOHI_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGCTRL_VIOLO_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VIOLO_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_MODE) | + BITFMASK(MC13783_REGCTRL_VIOLO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VDIG_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VDIG_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGCTRL_VGEN_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VGEN_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_MODE) | + BITFMASK(MC13783_REGCTRL_VGEN_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFDIG_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRFDIG_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VRFDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGCTRL_VRFREF_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRFREF_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_MODE) | + BITFMASK(MC13783_REGCTRL_VRFREF_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGCTRL_VRFCP_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRFCP_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_MODE) | + BITFMASK(MC13783_REGCTRL_VRFCP_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_val = BITFVAL(MC13783_REGCTRL_VSIM_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VSIM_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_MODE) | + BITFMASK(MC13783_REGCTRL_VSIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_val = BITFVAL(MC13783_REGCTRL_VESIM_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VESIM_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_MODE) | + BITFMASK(MC13783_REGCTRL_VESIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGCTRL_VCAM_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VCAM_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_MODE) | + BITFMASK(MC13783_REGCTRL_VCAM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + if ((lp_mode == LOW_POWER) || + (lp_mode == LOW_POWER_AND_LOW_POWER_CTRL_BY_PIN)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGCTRL_VRFBG_STBY, l_mode); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGCTRL_VRF1_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRF1_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_MODE) | + BITFMASK(MC13783_REGCTRL_VRF1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGCTRL_VRF2_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRF2_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_MODE) | + BITFMASK(MC13783_REGCTRL_VRF2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC1_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VMMC1_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC2_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VMMC2_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets low power mode. + * + * @param regulator The regulator to be handled + * @param lp_mode Select nominal or low power mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_lp_mode(t_pmic_regulator regulator, + t_regulator_lp_mode * lp_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + unsigned int l_mode, l_stby; + + switch (regulator) { + case SW_SW3: + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_MODE) | + BITFMASK(MC13783_SWCTRL_SW3_STBY); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_MODE) | + BITFMASK(MC13783_REGCTRL_VAUDIO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_MODE) | + BITFMASK(MC13783_REGCTRL_VIOHI_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_MODE) | + BITFMASK(MC13783_REGCTRL_VIOLO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_MODE) | + BITFMASK(MC13783_REGCTRL_VGEN_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VRFDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_MODE) | + BITFMASK(MC13783_REGCTRL_VRFREF_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_MODE) | + BITFMASK(MC13783_REGCTRL_VRFCP_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_MODE) | + BITFMASK(MC13783_REGCTRL_VSIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_MODE) | + BITFMASK(MC13783_REGCTRL_VESIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_MODE) | + BITFMASK(MC13783_REGCTRL_VCAM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_MODE) | + BITFMASK(MC13783_REGCTRL_VRF1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_MODE) | + BITFMASK(MC13783_REGCTRL_VRF2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW3: + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW3_MODE); + l_stby = BITFEXT(reg_val, MC13783_SWCTRL_SW3_STBY); + break; + case REGU_VAUDIO: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VAUDIO_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VAUDIO_STBY); + break; + case REGU_VIOHI: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VIOHI_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VIOHI_STBY); + break; + case REGU_VIOLO: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VIOLO_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VIOLO_STBY); + break; + case REGU_VDIG: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VDIG_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VDIG_STBY); + break; + case REGU_VGEN: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VGEN_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VGEN_STBY); + break; + case REGU_VRFDIG: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRFDIG_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFDIG_STBY); + break; + case REGU_VRFREF: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRFREF_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFREF_STBY); + break; + case REGU_VRFCP: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRFCP_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFCP_STBY); + break; + case REGU_VSIM: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VSIM_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VSIM_STBY); + break; + case REGU_VESIM: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VESIM_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VESIM_STBY); + break; + case REGU_VCAM: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VCAM_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VCAM_STBY); + break; + case REGU_VRFBG: + l_mode = MC13783_REGTRL_LP_MODE_DISABLE; + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFBG_STBY); + break; + case REGU_VRF1: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRF1_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRF1_STBY); + break; + case REGU_VRF2: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRF2_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRF2_STBY); + break; + case REGU_VMMC1: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VMMC1_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VMMC1_STBY); + break; + case REGU_VMMC2: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VMMC2_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VMMC2_STBY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if ((l_mode == MC13783_REGTRL_LP_MODE_DISABLE) && + (l_stby == MC13783_REGTRL_STBY_MODE_DISABLE)) { + *lp_mode = LOW_POWER_DISABLED; + } else if ((l_mode == MC13783_REGTRL_LP_MODE_DISABLE) && + (l_stby == MC13783_REGTRL_STBY_MODE_ENABLE)) { + *lp_mode = LOW_POWER_CTRL_BY_PIN; + } else if ((l_mode == MC13783_REGTRL_LP_MODE_ENABLE) && + (l_stby == MC13783_REGTRL_STBY_MODE_DISABLE)) { + *lp_mode = LOW_POWER_EN; + } else { + *lp_mode = LOW_POWER_AND_LOW_POWER_CTRL_BY_PIN; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the regulator configuration. + * + * @param regulator The regulator to be configured. + * @param config The regulator output configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_config(t_pmic_regulator regulator, + t_regulator_config * config) +{ + if (config == NULL) { + return PMIC_ERROR; + } + + switch (regulator) { + case SW_SW1A: + case SW_SW1B: + case SW_SW2A: + case SW_SW2B: + CHECK_ERROR(pmic_power_regulator_set_voltage + (regulator, config->voltage)); + CHECK_ERROR(pmic_power_switcher_set_dvs + (regulator, config->voltage_lvs)); + CHECK_ERROR(pmic_power_switcher_set_stby + (regulator, config->voltage_stby)); + CHECK_ERROR(pmic_power_switcher_set_mode + (regulator, config->mode, false)); + CHECK_ERROR(pmic_power_switcher_set_mode + (regulator, config->stby_mode, true)); + CHECK_ERROR(pmic_power_switcher_set_dvs_speed + (regulator, config->dvs_speed)); + CHECK_ERROR(pmic_power_switcher_set_panic_mode + (regulator, config->panic_mode)); + CHECK_ERROR(pmic_power_switcher_set_softstart + (regulator, config->softstart)); + break; + case SW_PLL: + CHECK_ERROR(pmic_power_switcher_set_factor + (regulator, config->factor)); + break; + case SW_SW3: + case REGU_VIOLO: + case REGU_VDIG: + case REGU_VGEN: + case REGU_VRFDIG: + case REGU_VRFREF: + case REGU_VRFCP: + case REGU_VSIM: + case REGU_VESIM: + case REGU_VCAM: + case REGU_VRF1: + case REGU_VRF2: + case REGU_VMMC1: + case REGU_VMMC2: + CHECK_ERROR(pmic_power_regulator_set_voltage + (regulator, config->voltage)); + CHECK_ERROR(pmic_power_regulator_set_lp_mode + (regulator, config->lp_mode)); + break; + case REGU_VVIB: + CHECK_ERROR(pmic_power_regulator_set_voltage + (regulator, config->voltage)); + break; + case REGU_VAUDIO: + case REGU_VIOHI: + case REGU_VRFBG: + CHECK_ERROR(pmic_power_regulator_set_lp_mode + (regulator, config->lp_mode)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function retrives the regulator output configuration. + * + * @param regulator The regulator to be truned off. + * @param config Pointer to regulator configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_config(t_pmic_regulator regulator, + t_regulator_config * config) +{ + if (config == NULL) { + return PMIC_ERROR; + } + + switch (regulator) { + case SW_SW1A: + case SW_SW1B: + case SW_SW2A: + case SW_SW2B: + CHECK_ERROR(pmic_power_regulator_get_voltage + (regulator, &config->voltage)); + CHECK_ERROR(pmic_power_switcher_get_dvs + (regulator, &config->voltage_lvs)); + CHECK_ERROR(pmic_power_switcher_get_stby + (regulator, &config->voltage_stby)); + CHECK_ERROR(pmic_power_switcher_get_mode + (regulator, &config->mode, false)); + CHECK_ERROR(pmic_power_switcher_get_mode + (regulator, &config->stby_mode, true)); + CHECK_ERROR(pmic_power_switcher_get_dvs_speed + (regulator, &config->dvs_speed)); + CHECK_ERROR(pmic_power_switcher_get_panic_mode + (regulator, &config->panic_mode)); + CHECK_ERROR(pmic_power_switcher_get_softstart + (regulator, &config->softstart)); + break; + case SW_PLL: + CHECK_ERROR(pmic_power_switcher_get_factor + (regulator, &config->factor)); + break; + case SW_SW3: + case REGU_VIOLO: + case REGU_VDIG: + case REGU_VGEN: + case REGU_VRFDIG: + case REGU_VRFREF: + case REGU_VRFCP: + case REGU_VSIM: + case REGU_VESIM: + case REGU_VCAM: + case REGU_VRF1: + case REGU_VRF2: + case REGU_VMMC1: + case REGU_VMMC2: + CHECK_ERROR(pmic_power_regulator_get_voltage + (regulator, &config->voltage)); + CHECK_ERROR(pmic_power_regulator_get_lp_mode + (regulator, &config->lp_mode)); + break; + case REGU_VVIB: + CHECK_ERROR(pmic_power_regulator_get_voltage + (regulator, &config->voltage)); + break; + case REGU_VAUDIO: + case REGU_VIOHI: + case REGU_VRFBG: + CHECK_ERROR(pmic_power_regulator_get_lp_mode + (regulator, &config->lp_mode)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function enables automatically VBKUP2 in the memory hold modes. + * Only on mc13783 2.0 or higher + * + * @param en if true, enable VBKUP2AUTOMH + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_vbkup2_auto_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGCTRL_VBKUP2AUTOMH, en); + reg_mask = BITFMASK(MC13783_REGCTRL_VBKUP2AUTOMH); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets state of automatically VBKUP2. + * Only on mc13783 2.0 or higher + * + * @param en if true, VBKUP2AUTOMH is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_vbkup2_auto_state(bool * en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGCTRL_VBKUP2AUTOMH); + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_0, + ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_REGCTRL_VBKUP2AUTOMH); + + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function enables battery detect function. + * Only on mc13783 2.0 or higher + * + * @param en if true, enable BATTDETEN + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_bat_det_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGCTRL_BATTDETEN, en); + reg_mask = BITFMASK(MC13783_REGCTRL_BATTDETEN); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets state of battery detect function. + * Only on mc13783 2.0 or higher + * + * @param en if true, BATTDETEN is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_bat_det_state(bool * en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGCTRL_BATTDETEN); + + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_0, + ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_REGCTRL_BATTDETEN); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function enables control of VVIB by VIBEN pin. + * Only on mc13783 2.0 or higher + * + * @param en if true, enable VIBPINCTRL + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_vib_pin_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGCTRL_VIBPINCTRL, en); + reg_mask = BITFMASK(MC13783_REGCTRL_VIBPINCTRL); + + CHECK_ERROR(pmic_write_reg(REG_POWER_MISCELLANEOUS, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets state of control of VVIB by VIBEN pin. + * Only on mc13783 2.0 or higher + * @param en if true, VIBPINCTRL is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_gets_vib_pin_state(bool * en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGCTRL_VIBPINCTRL); + CHECK_ERROR(pmic_read_reg(REG_POWER_MISCELLANEOUS, + ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_REGCTRL_VIBPINCTRL); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function returns power up sense value + * + * @param p_up_sense value of power up sense + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_power_mode_sense(struct t_p_up_sense * p_up_sense) +{ + unsigned int reg_value = 0; + CHECK_ERROR(pmic_read_reg(REG_POWER_UP_MODE_SENSE, + ®_value, PMIC_ALL_BITS)); + p_up_sense->state_ictest = (STATE_ICTEST_MASK & reg_value); + p_up_sense->state_clksel = ((STATE_CLKSEL_MASK & reg_value) + >> STATE_CLKSEL_BIT); + p_up_sense->state_pums1 = ((STATE_PUMS1_MASK & reg_value) + >> STATE_PUMS1_BITS); + p_up_sense->state_pums2 = ((STATE_PUMS2_MASK & reg_value) + >> STATE_PUMS2_BITS); + p_up_sense->state_pums3 = ((STATE_PUMS3_MASK & reg_value) + >> STATE_PUMS3_BITS); + p_up_sense->state_chrgmode0 = ((STATE_CHRGM1_MASK & reg_value) + >> STATE_CHRGM1_BITS); + p_up_sense->state_chrgmode1 = ((STATE_CHRGM2_MASK & reg_value) + >> STATE_CHRGM2_BITS); + p_up_sense->state_umod = ((STATE_UMOD_MASK & reg_value) + >> STATE_UMOD_BITS); + p_up_sense->state_usben = ((STATE_USBEN_MASK & reg_value) + >> STATE_USBEN_BIT); + p_up_sense->state_sw_1a1b_joined = ((STATE_SW1A_J_B_MASK & reg_value) + >> STATE_SW1A_J_B_BIT); + p_up_sense->state_sw_2a2b_joined = ((STATE_SW2A_J_B_MASK & reg_value) + >> STATE_SW2A_J_B_BIT); + return PMIC_SUCCESS; +} + +/*! + * This function configures the Regen assignment for all regulator + * + * @param regulator type of regulator + * @param en_dis if true, the regulator is enabled by regen. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_regen_assig(t_pmic_regulator regulator, bool en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + switch (regulator) { + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGGEN_VAUDIO, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VAUDIO); + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGGEN_VIOHI, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VIOHI); + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGGEN_VIOLO, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VIOLO); + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGGEN_VDIG, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VDIG); + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGGEN_VGEN, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VGEN); + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGGEN_VRFDIG, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFDIG); + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGGEN_VRFREF, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFREF); + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGGEN_VRFCP, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFCP); + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGGEN_VCAM, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VCAM); + break; + case REGU_VRFBG: + reg_val = BITFVAL(MC13783_REGGEN_VRFBG, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFBG); + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGGEN_VRF1, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRF1); + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGGEN_VRF2, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRF2); + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGGEN_VMMC1, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VMMC1); + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGGEN_VMMC2, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VMMC2); + break; + case REGU_GPO1: + reg_val = BITFVAL(MC13783_REGGEN_GPO1, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO1); + break; + case REGU_GPO2: + reg_val = BITFVAL(MC13783_REGGEN_GPO2, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO2); + break; + case REGU_GPO3: + reg_val = BITFVAL(MC13783_REGGEN_GPO3, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO3); + break; + case REGU_GPO4: + reg_val = BITFVAL(MC13783_REGGEN_GPO4, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO4); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_REGEN_ASSIGNMENT, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the Regen assignment for all regulator + * + * @param regulator type of regulator + * @param en_dis return value, if true : + * the regulator is enabled by regen. + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_regen_assig(t_pmic_regulator regulator, + bool * en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + switch (regulator) { + case REGU_VAUDIO: + reg_mask = BITFMASK(MC13783_REGGEN_VAUDIO); + break; + case REGU_VIOHI: + reg_mask = BITFMASK(MC13783_REGGEN_VIOHI); + break; + case REGU_VIOLO: + reg_mask = BITFMASK(MC13783_REGGEN_VIOLO); + break; + case REGU_VDIG: + reg_mask = BITFMASK(MC13783_REGGEN_VDIG); + break; + case REGU_VGEN: + reg_mask = BITFMASK(MC13783_REGGEN_VGEN); + break; + case REGU_VRFDIG: + reg_mask = BITFMASK(MC13783_REGGEN_VRFDIG); + break; + case REGU_VRFREF: + reg_mask = BITFMASK(MC13783_REGGEN_VRFREF); + break; + case REGU_VRFCP: + reg_mask = BITFMASK(MC13783_REGGEN_VRFCP); + break; + case REGU_VCAM: + reg_mask = BITFMASK(MC13783_REGGEN_VCAM); + break; + case REGU_VRFBG: + reg_mask = BITFMASK(MC13783_REGGEN_VRFBG); + break; + case REGU_VRF1: + reg_mask = BITFMASK(MC13783_REGGEN_VRF1); + break; + case REGU_VRF2: + reg_mask = BITFMASK(MC13783_REGGEN_VRF2); + break; + case REGU_VMMC1: + reg_mask = BITFMASK(MC13783_REGGEN_VMMC1); + break; + case REGU_VMMC2: + reg_mask = BITFMASK(MC13783_REGGEN_VMMC2); + break; + case REGU_GPO1: + reg_mask = BITFMASK(MC13783_REGGEN_GPO1); + break; + case REGU_GPO2: + reg_mask = BITFMASK(MC13783_REGGEN_GPO2); + break; + case REGU_GPO3: + reg_mask = BITFMASK(MC13783_REGGEN_GPO3); + break; + case REGU_GPO4: + reg_mask = BITFMASK(MC13783_REGGEN_GPO4); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(REG_REGEN_ASSIGNMENT, ®_val, reg_mask)); + + switch (regulator) { + case REGU_VAUDIO: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VAUDIO); + break; + case REGU_VIOHI: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VIOHI); + break; + case REGU_VIOLO: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VIOLO); + break; + case REGU_VDIG: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VDIG); + break; + case REGU_VGEN: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VGEN); + break; + case REGU_VRFDIG: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFDIG); + break; + case REGU_VRFREF: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFREF); + break; + case REGU_VRFCP: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFCP); + break; + case REGU_VCAM: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VCAM); + break; + case REGU_VRFBG: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFBG); + break; + case REGU_VRF1: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRF1); + break; + case REGU_VRF2: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRF2); + break; + case REGU_VMMC1: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VMMC1); + break; + case REGU_VMMC2: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VMMC2); + break; + case REGU_GPO1: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO1); + break; + case REGU_GPO2: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO2); + break; + case REGU_GPO3: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO3); + break; + case REGU_GPO4: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO4); + break; + default: + break; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the Regen polarity. + * + * @param en_dis If true regen is inverted. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_regen_inv(bool en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_val = BITFVAL(MC13783_REGGEN_INV, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_INV); + + CHECK_ERROR(pmic_write_reg(REG_REGEN_ASSIGNMENT, reg_val, reg_mask)); + return PMIC_SUCCESS; +} + +/*! + * This function gets the Regen polarity. + * + * @param en_dis If true regen is inverted. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_regen_inv(bool * en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_mask = BITFMASK(MC13783_REGGEN_INV); + CHECK_ERROR(pmic_read_reg(REG_REGEN_ASSIGNMENT, ®_val, reg_mask)); + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_INV); + return PMIC_SUCCESS; +} + +/*! + * This function enables esim control voltage. + * Only on mc13783 2.0 or higher + * + * @param vesim if true, enable VESIMESIMEN + * @param vmmc1 if true, enable VMMC1ESIMEN + * @param vmmc2 if true, enable VMMC2ESIMEN + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_esim_v_en(bool vesim, bool vmmc1, bool vmmc2) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGGEN_VESIMESIM, vesim) | + BITFVAL(MC13783_REGGEN_VMMC1ESIM, vesim) | + BITFVAL(MC13783_REGGEN_VMMC2ESIM, vesim); + reg_mask = BITFMASK(MC13783_REGGEN_VESIMESIM) | + BITFMASK(MC13783_REGGEN_VMMC1ESIM) | + BITFMASK(MC13783_REGGEN_VMMC2ESIM); + CHECK_ERROR(pmic_write_reg(REG_REGEN_ASSIGNMENT, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets esim control voltage values. + * Only on mc13783 2.0 or higher + * + * @param vesim if true, enable VESIMESIMEN + * @param vmmc1 if true, enable VMMC1ESIMEN + * @param vmmc2 if true, enable VMMC2ESIMEN + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_gets_esim_v_state(bool * vesim, bool * vmmc1, + bool * vmmc2) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGGEN_VESIMESIM) | + BITFMASK(MC13783_REGGEN_VMMC1ESIM) | + BITFMASK(MC13783_REGGEN_VMMC2ESIM); + CHECK_ERROR(pmic_read_reg(REG_REGEN_ASSIGNMENT, + ®_val, reg_mask)); + *vesim = BITFEXT(reg_val, MC13783_REGGEN_VESIMESIM); + *vmmc1 = BITFEXT(reg_val, MC13783_REGGEN_VMMC1ESIM); + *vmmc2 = BITFEXT(reg_val, MC13783_REGGEN_VMMC2ESIM); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function enables auto reset after a system reset. + * + * @param en if true, the auto reset is enabled + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_auto_reset_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_val = BITFVAL(MC13783_AUTO_RESTART, en); + reg_mask = BITFMASK(MC13783_AUTO_RESTART); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_2, reg_val, reg_mask)); + return PMIC_SUCCESS; +} + +/*! + * This function gets auto reset configuration. + * + * @param en if true, the auto reset is enabled + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_auto_reset_en(bool * en) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_mask = BITFMASK(MC13783_AUTO_RESTART); + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_2, ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_AUTO_RESTART); + return PMIC_SUCCESS; +} + +/*! + * This function configures a system reset on a button. + * + * @param bt type of button. + * @param sys_rst if true, enable the system reset on this button + * @param deb_time sets the debounce time on this button pin + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_conf_button(t_button bt, bool sys_rst, int deb_time) +{ + int max_val = 0; + unsigned int reg_val = 0, reg_mask = 0; + + max_val = (1 << MC13783_DEB_BT_ON1B_WID) - 1; + if (deb_time > max_val) { + return PMIC_PARAMETER_ERROR; + } + + switch (bt) { + case BT_ON1B: + reg_val = BITFVAL(MC13783_EN_BT_ON1B, sys_rst) | + BITFVAL(MC13783_DEB_BT_ON1B, deb_time); + reg_mask = BITFMASK(MC13783_EN_BT_ON1B) | + BITFMASK(MC13783_DEB_BT_ON1B); + break; + case BT_ON2B: + reg_val = BITFVAL(MC13783_EN_BT_ON2B, sys_rst) | + BITFVAL(MC13783_DEB_BT_ON2B, deb_time); + reg_mask = BITFMASK(MC13783_EN_BT_ON2B) | + BITFMASK(MC13783_DEB_BT_ON2B); + break; + case BT_ON3B: + reg_val = BITFVAL(MC13783_EN_BT_ON3B, sys_rst) | + BITFVAL(MC13783_DEB_BT_ON3B, deb_time); + reg_mask = BITFMASK(MC13783_EN_BT_ON3B) | + BITFMASK(MC13783_DEB_BT_ON3B); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_2, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets configuration of a button. + * + * @param bt type of button. + * @param sys_rst if true, the system reset is enabled on this button + * @param deb_time gets the debounce time on this button pin + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_conf_button(t_button bt, + bool * sys_rst, int *deb_time) +{ + unsigned int reg_val = 0, reg_mask = 0; + + switch (bt) { + case BT_ON1B: + reg_mask = BITFMASK(MC13783_EN_BT_ON1B) | + BITFMASK(MC13783_DEB_BT_ON1B); + break; + case BT_ON2B: + reg_mask = BITFMASK(MC13783_EN_BT_ON2B) | + BITFMASK(MC13783_DEB_BT_ON2B); + break; + case BT_ON3B: + reg_mask = BITFMASK(MC13783_EN_BT_ON3B) | + BITFMASK(MC13783_DEB_BT_ON3B); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_2, ®_val, reg_mask)); + + switch (bt) { + case BT_ON1B: + *sys_rst = BITFEXT(reg_val, MC13783_EN_BT_ON1B); + *deb_time = BITFEXT(reg_val, MC13783_DEB_BT_ON1B); + break; + case BT_ON2B: + *sys_rst = BITFEXT(reg_val, MC13783_EN_BT_ON2B); + *deb_time = BITFEXT(reg_val, MC13783_DEB_BT_ON2B); + break; + case BT_ON3B: + *sys_rst = BITFEXT(reg_val, MC13783_EN_BT_ON3B); + *deb_time = BITFEXT(reg_val, MC13783_DEB_BT_ON3B); + break; + default: + return PMIC_PARAMETER_ERROR; + } + return PMIC_SUCCESS; +} + +/*! + * This function is used to un/subscribe on power event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_event(t_pwr_int event, void *callback, bool sub) +{ + pmic_event_callback_t power_callback; + type_event power_event; + + power_callback.func = callback; + power_callback.param = NULL; + switch (event) { + case PWR_IT_BPONI: + power_event = EVENT_BPONI; + break; + case PWR_IT_LOBATLI: + power_event = EVENT_LOBATLI; + break; + case PWR_IT_LOBATHI: + power_event = EVENT_LOBATHI; + break; + case PWR_IT_ONOFD1I: + power_event = EVENT_ONOFD1I; + break; + case PWR_IT_ONOFD2I: + power_event = EVENT_ONOFD2I; + break; + case PWR_IT_ONOFD3I: + power_event = EVENT_ONOFD3I; + break; + case PWR_IT_SYSRSTI: + power_event = EVENT_SYSRSTI; + break; + case PWR_IT_PWRRDYI: + power_event = EVENT_PWRRDYI; + break; + case PWR_IT_PCI: + power_event = EVENT_PCI; + break; + case PWR_IT_WARMI: + power_event = EVENT_WARMI; + break; + default: + return PMIC_PARAMETER_ERROR; + } + if (sub == true) { + CHECK_ERROR(pmic_event_subscribe(power_event, power_callback)); + } else { + CHECK_ERROR(pmic_event_unsubscribe + (power_event, power_callback)); + } + return PMIC_SUCCESS; +} + +/*! + * This function is used to subscribe on power event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_event_sub(t_pwr_int event, void *callback) +{ + return pmic_power_event(event, callback, true); +} + +/*! + * This function is used to un subscribe on power event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_event_unsub(t_pwr_int event, void *callback) +{ + return pmic_power_event(event, callback, false); +} + +void pmic_power_key_callback(void) +{ +#ifdef CONFIG_MXC_HWEVENT + /*read the power key is pressed or up */ + t_sensor_bits sense; + struct mxc_hw_event event = { HWE_POWER_KEY, 0 }; + + pmic_get_sensors(&sense); + if (sense.sense_onofd1s) { + pr_debug("PMIC Power key up\n"); + event.args = PWRK_UNPRESS; + } else { + pr_debug("PMIC Power key pressed\n"); + event.args = PWRK_PRESS; + } + /* send hw event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static irqreturn_t power_key_int(int irq, void *dev_id) +{ + pr_info(KERN_INFO "on-off key pressed\n"); + + return 0; +} + +extern void gpio_power_key_active(void); + +/* + * Init and Exit + */ + +static int pmic_power_probe(struct platform_device *pdev) +{ + int irq, ret; + struct pmic_platform_data *ppd; + + /* configure on/off button */ + gpio_power_key_active(); + + ppd = pdev->dev.platform_data; + if (ppd) + irq = ppd->power_key_irq; + else + goto done; + + if (irq == 0) { + pr_info(KERN_INFO "PMIC Power has no platform data\n"); + goto done; + } + set_irq_type(irq, IRQF_TRIGGER_RISING); + + ret = request_irq(irq, power_key_int, 0, "power_key", 0); + if (ret) + pr_info(KERN_ERR "register on-off key interrupt failed\n"); + + set_irq_wake(irq, 1); + + done: + pr_info(KERN_INFO "PMIC Power successfully probed\n"); + return 0; +} + +static struct platform_driver pmic_power_driver_ldm = { + .driver = { + .name = "pmic_power", + }, + .suspend = pmic_power_suspend, + .resume = pmic_power_resume, + .probe = pmic_power_probe, + .remove = NULL, +}; + +static int __init pmic_power_init(void) +{ + pr_debug("PMIC Power driver loading..\n"); + pmic_power_event_sub(PWR_IT_ONOFD1I, pmic_power_key_callback); + /* set power off hook to mc13783 power off */ + pm_power_off = pmic_power_off; + return platform_driver_register(&pmic_power_driver_ldm); +} +static void __exit pmic_power_exit(void) +{ + pmic_power_event_unsub(PWR_IT_ONOFD1I, pmic_power_key_callback); + platform_driver_unregister(&pmic_power_driver_ldm); + pr_debug("PMIC Power driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall_sync(pmic_power_init); +module_exit(pmic_power_exit); + +MODULE_DESCRIPTION("pmic_power driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_power_defs.h b/drivers/mxc/pmic/mc13783/pmic_power_defs.h new file mode 100644 index 000000000000..38e554146a70 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_power_defs.h @@ -0,0 +1,509 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_power_defs.h + * @brief This is the internal header define of PMIC(mc13783) Power driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ + +#ifndef __MC13783_POWER_DEFS_H__ +#define __MC13783_POWER_DEFS_H__ + +/* + * Power Up Mode Sense bits + */ + +#define STATE_ICTEST_MASK 0x000001 + +#define STATE_CLKSEL_BIT 1 +#define STATE_CLKSEL_MASK 0x000002 + +#define STATE_PUMS1_BITS 2 +#define STATE_PUMS1_MASK 0x00000C + +#define STATE_PUMS2_BITS 4 +#define STATE_PUMS2_MASK 0x000030 + +#define STATE_PUMS3_BITS 6 +#define STATE_PUMS3_MASK 0x0000C0 + +#define STATE_CHRGM1_BITS 8 +#define STATE_CHRGM1_MASK 0x000300 + +#define STATE_CHRGM2_BITS 10 +#define STATE_CHRGM2_MASK 0x000C00 + +#define STATE_UMOD_BITS 12 +#define STATE_UMOD_MASK 0x003000 + +#define STATE_USBEN_BIT 14 +#define STATE_USBEN_MASK 0x004000 + +#define STATE_SW1A_J_B_BIT 15 +#define STATE_SW1A_J_B_MASK 0x008000 + +#define STATE_SW2A_J_B_BIT 16 +#define STATE_SW2A_J_B_MASK 0x010000 + +#define PC_COUNT_MAX 3 +#define PC_COUNT_MIN 0 +/* + * Reg Regen + */ +#define MC13783_REGGEN_VAUDIO_LSH 0 +#define MC13783_REGGEN_VAUDIO_WID 1 +#define MC13783_REGGEN_VIOHI_LSH 1 +#define MC13783_REGGEN_VIOHI_WID 1 +#define MC13783_REGGEN_VIOLO_LSH 2 +#define MC13783_REGGEN_VIOLO_WID 1 +#define MC13783_REGGEN_VDIG_LSH 3 +#define MC13783_REGGEN_VDIG_WID 1 +#define MC13783_REGGEN_VGEN_LSH 4 +#define MC13783_REGGEN_VGEN_WID 1 +#define MC13783_REGGEN_VRFDIG_LSH 5 +#define MC13783_REGGEN_VRFDIG_WID 1 +#define MC13783_REGGEN_VRFREF_LSH 6 +#define MC13783_REGGEN_VRFREF_WID 1 +#define MC13783_REGGEN_VRFCP_LSH 7 +#define MC13783_REGGEN_VRFCP_WID 1 +#define MC13783_REGGEN_VCAM_LSH 8 +#define MC13783_REGGEN_VCAM_WID 1 +#define MC13783_REGGEN_VRFBG_LSH 9 +#define MC13783_REGGEN_VRFBG_WID 1 +#define MC13783_REGGEN_VRF1_LSH 10 +#define MC13783_REGGEN_VRF1_WID 1 +#define MC13783_REGGEN_VRF2_LSH 11 +#define MC13783_REGGEN_VRF2_WID 1 +#define MC13783_REGGEN_VMMC1_LSH 12 +#define MC13783_REGGEN_VMMC1_WID 1 +#define MC13783_REGGEN_VMMC2_LSH 13 +#define MC13783_REGGEN_VMMC2_WID 1 +#define MC13783_REGGEN_GPO1_LSH 16 +#define MC13783_REGGEN_GPO1_WID 1 +#define MC13783_REGGEN_GPO2_LSH 17 +#define MC13783_REGGEN_GPO2_WID 1 +#define MC13783_REGGEN_GPO3_LSH 18 +#define MC13783_REGGEN_GPO3_WID 1 +#define MC13783_REGGEN_GPO4_LSH 19 +#define MC13783_REGGEN_GPO4_WID 1 +#define MC13783_REGGEN_INV_LSH 20 +#define MC13783_REGGEN_INV_WID 1 +#define MC13783_REGGEN_VESIMESIM_LSH 21 +#define MC13783_REGGEN_VESIMESIM_WID 1 +#define MC13783_REGGEN_VMMC1ESIM_LSH 22 +#define MC13783_REGGEN_VMMC1ESIM_WID 1 +#define MC13783_REGGEN_VMMC2ESIM_LSH 23 +#define MC13783_REGGEN_VMMC2ESIM_WID 1 + +/* + * Reg Power Control 0 + */ +#define MC13783_PWRCTRL_PCEN_LSH 0 +#define MC13783_PWRCTRL_PCEN_WID 1 +#define MC13783_PWRCTRL_PCEN_ENABLE 1 +#define MC13783_PWRCTRL_PCEN_DISABLE 0 +#define MC13783_PWRCTRL_PC_COUNT_EN_LSH 1 +#define MC13783_PWRCTRL_PC_COUNT_EN_WID 1 +#define MC13783_PWRCTRL_PC_COUNT_EN_ENABLE 1 +#define MC13783_PWRCTRL_PC_COUNT_EN_DISABLE 0 +#define MC13783_PWRCTRL_WARM_EN_LSH 2 +#define MC13783_PWRCTRL_WARM_EN_WID 1 +#define MC13783_PWRCTRL_WARM_EN_ENABLE 1 +#define MC13783_PWRCTRL_WARM_EN_DISABLE 0 +#define MC13783_PWRCTRL_USER_OFF_SPI_LSH 3 +#define MC13783_PWRCTRL_USER_OFF_SPI_WID 1 +#define MC13783_PWRCTRL_USER_OFF_SPI_ENABLE 1 +#define MC13783_PWRCTRL_USER_OFF_PC_LSH 4 +#define MC13783_PWRCTRL_USER_OFF_PC_WID 1 +#define MC13783_PWRCTRL_USER_OFF_PC_ENABLE 1 +#define MC13783_PWRCTRL_USER_OFF_PC_DISABLE 0 +#define MC13783_PWRCTRL_32OUT_USER_OFF_LSH 5 +#define MC13783_PWRCTRL_32OUT_USER_OFF_WID 1 +#define MC13783_PWRCTRL_32OUT_USER_OFF_ENABLE 1 +#define MC13783_PWRCTRL_32OUT_USER_OFF_DISABLE 0 +#define MC13783_PWRCTRL_32OUT_EN_LSH 6 +#define MC13783_PWRCTRL_32OUT_EN_WID 1 +#define MC13783_PWRCTRL_32OUT_EN_ENABLE 1 +#define MC13783_PWRCTRL_32OUT_EN_DISABLE 0 +#define MC13783_REGCTRL_VBKUP2AUTOMH_LSH 7 +#define MC13783_REGCTRL_VBKUP2AUTOMH_WID 1 +#define MC13783_PWRCTRL_VBKUP1_EN_LSH 8 +#define MC13783_PWRCTRL_VBKUP1_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP_ENABLE 1 +#define MC13783_PWRCTRL_VBKUP_DISABLE 0 +#define MC13783_PWRCTRL_VBKUP1_AUTO_EN_LSH 9 +#define MC13783_PWRCTRL_VBKUP1_AUTO_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP1_LSH 10 +#define MC13783_PWRCTRL_VBKUP1_WID 2 +#define MC13783_PWRCTRL_VBKUP2_EN_LSH 12 +#define MC13783_PWRCTRL_VBKUP2_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP2_AUTO_EN_LSH 13 +#define MC13783_PWRCTRL_VBKUP2_AUTO_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP2_LSH 14 +#define MC13783_PWRCTRL_VBKUP2_WID 2 +#define MC13783_REGCTRL_BATTDETEN_LSH 19 +#define MC13783_REGCTRL_BATTDETEN_WID 1 + +/* + * Reg Power Control 1 + */ +#define MC13783_PWRCTRL_PCT_LSH 0 +#define MC13783_PWRCTRL_PCT_WID 8 +#define MC13783_PWRCTRL_PC_COUNT_LSH 8 +#define MC13783_PWRCTRL_PC_COUNT_WID 4 +#define MC13783_PWRCTRL_PC_MAX_CNT_LSH 12 +#define MC13783_PWRCTRL_PC_MAX_CNT_WID 4 +#define MC13783_PWRCTRL_MEM_TMR_LSH 16 +#define MC13783_PWRCTRL_MEM_TMR_WID 4 +#define MC13783_PWRCTRL_MEM_ALLON_LSH 20 +#define MC13783_PWRCTRL_MEM_ALLON_WID 1 +#define MC13783_PWRCTRL_MEM_ALLON_ENABLE 1 +#define MC13783_PWRCTRL_MEM_ALLON_DISABLE 0 + +/* + * Reg Power Control 2 + */ +#define MC13783_AUTO_RESTART_LSH 0 +#define MC13783_AUTO_RESTART_WID 1 +#define MC13783_EN_BT_ON1B_LSH 1 +#define MC13783_EN_BT_ON1B_WID 1 +#define MC13783_EN_BT_ON2B_LSH 2 +#define MC13783_EN_BT_ON2B_WID 1 +#define MC13783_EN_BT_ON3B_LSH 3 +#define MC13783_EN_BT_ON3B_WID 1 +#define MC13783_DEB_BT_ON1B_LSH 4 +#define MC13783_DEB_BT_ON1B_WID 2 +#define MC13783_DEB_BT_ON2B_LSH 6 +#define MC13783_DEB_BT_ON2B_WID 2 +#define MC13783_DEB_BT_ON3B_LSH 8 +#define MC13783_DEB_BT_ON3B_WID 2 + +/* + * Reg Regulator Mode 0 + */ +#define MC13783_REGCTRL_VAUDIO_EN_LSH 0 +#define MC13783_REGCTRL_VAUDIO_EN_WID 1 +#define MC13783_REGCTRL_VAUDIO_EN_ENABLE 1 +#define MC13783_REGCTRL_VAUDIO_EN_DISABLE 0 +#define MC13783_REGCTRL_VAUDIO_STBY_LSH 1 +#define MC13783_REGCTRL_VAUDIO_STBY_WID 1 +#define MC13783_REGCTRL_VAUDIO_MODE_LSH 2 +#define MC13783_REGCTRL_VAUDIO_MODE_WID 1 +#define MC13783_REGCTRL_VIOHI_EN_LSH 3 +#define MC13783_REGCTRL_VIOHI_EN_WID 1 +#define MC13783_REGCTRL_VIOHI_EN_ENABLE 1 +#define MC13783_REGCTRL_VIOHI_EN_DISABLE 0 +#define MC13783_REGCTRL_VIOHI_STBY_LSH 4 +#define MC13783_REGCTRL_VIOHI_STBY_WID 1 +#define MC13783_REGCTRL_VIOHI_MODE_LSH 5 +#define MC13783_REGCTRL_VIOHI_MODE_WID 1 +#define MC13783_REGCTRL_VIOLO_EN_LSH 6 +#define MC13783_REGCTRL_VIOLO_EN_WID 1 +#define MC13783_REGCTRL_VIOLO_EN_ENABLE 1 +#define MC13783_REGCTRL_VIOLO_EN_DISABLE 0 +#define MC13783_REGCTRL_VIOLO_STBY_LSH 7 +#define MC13783_REGCTRL_VIOLO_STBY_WID 1 +#define MC13783_REGCTRL_VIOLO_MODE_LSH 8 +#define MC13783_REGCTRL_VIOLO_MODE_WID 1 +#define MC13783_REGCTRL_VDIG_EN_LSH 9 +#define MC13783_REGCTRL_VDIG_EN_WID 1 +#define MC13783_REGCTRL_VDIG_EN_ENABLE 1 +#define MC13783_REGCTRL_VDIG_EN_DISABLE 0 +#define MC13783_REGCTRL_VDIG_STBY_LSH 10 +#define MC13783_REGCTRL_VDIG_STBY_WID 1 +#define MC13783_REGCTRL_VDIG_MODE_LSH 11 +#define MC13783_REGCTRL_VDIG_MODE_WID 1 +#define MC13783_REGCTRL_VGEN_EN_LSH 12 +#define MC13783_REGCTRL_VGEN_EN_WID 1 +#define MC13783_REGCTRL_VGEN_EN_ENABLE 1 +#define MC13783_REGCTRL_VGEN_EN_DISABLE 0 +#define MC13783_REGCTRL_VGEN_STBY_LSH 13 +#define MC13783_REGCTRL_VGEN_STBY_WID 1 +#define MC13783_REGCTRL_VGEN_MODE_LSH 14 +#define MC13783_REGCTRL_VGEN_MODE_WID 1 +#define MC13783_REGCTRL_VRFDIG_EN_LSH 15 +#define MC13783_REGCTRL_VRFDIG_EN_WID 1 +#define MC13783_REGCTRL_VRFDIG_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFDIG_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFDIG_STBY_LSH 16 +#define MC13783_REGCTRL_VRFDIG_STBY_WID 1 +#define MC13783_REGCTRL_VRFDIG_MODE_LSH 17 +#define MC13783_REGCTRL_VRFDIG_MODE_WID 1 +#define MC13783_REGCTRL_VRFREF_EN_LSH 18 +#define MC13783_REGCTRL_VRFREF_EN_WID 1 +#define MC13783_REGCTRL_VRFREF_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFREF_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFREF_STBY_LSH 19 +#define MC13783_REGCTRL_VRFREF_STBY_WID 1 +#define MC13783_REGCTRL_VRFREF_MODE_LSH 20 +#define MC13783_REGCTRL_VRFREF_MODE_WID 1 +#define MC13783_REGCTRL_VRFCP_EN_LSH 21 +#define MC13783_REGCTRL_VRFCP_EN_WID 1 +#define MC13783_REGCTRL_VRFCP_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFCP_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFCP_STBY_LSH 22 +#define MC13783_REGCTRL_VRFCP_STBY_WID 1 +#define MC13783_REGCTRL_VRFCP_MODE_LSH 23 +#define MC13783_REGCTRL_VRFCP_MODE_WID 1 + +/* + * Reg Regulator Mode 1 + */ +#define MC13783_REGCTRL_VSIM_EN_LSH 0 +#define MC13783_REGCTRL_VSIM_EN_WID 1 +#define MC13783_REGCTRL_VSIM_EN_ENABLE 1 +#define MC13783_REGCTRL_VSIM_EN_DISABLE 0 +#define MC13783_REGCTRL_VSIM_STBY_LSH 1 +#define MC13783_REGCTRL_VSIM_STBY_WID 1 +#define MC13783_REGCTRL_VSIM_MODE_LSH 2 +#define MC13783_REGCTRL_VSIM_MODE_WID 1 +#define MC13783_REGCTRL_VESIM_EN_LSH 3 +#define MC13783_REGCTRL_VESIM_EN_WID 1 +#define MC13783_REGCTRL_VESIM_EN_ENABLE 1 +#define MC13783_REGCTRL_VESIM_EN_DISABLE 0 +#define MC13783_REGCTRL_VESIM_STBY_LSH 4 +#define MC13783_REGCTRL_VESIM_STBY_WID 1 +#define MC13783_REGCTRL_VESIM_MODE_LSH 5 +#define MC13783_REGCTRL_VESIM_MODE_WID 1 +#define MC13783_REGCTRL_VCAM_EN_LSH 6 +#define MC13783_REGCTRL_VCAM_EN_WID 1 +#define MC13783_REGCTRL_VCAM_EN_ENABLE 1 +#define MC13783_REGCTRL_VCAM_EN_DISABLE 0 +#define MC13783_REGCTRL_VCAM_STBY_LSH 7 +#define MC13783_REGCTRL_VCAM_STBY_WID 1 +#define MC13783_REGCTRL_VCAM_MODE_LSH 8 +#define MC13783_REGCTRL_VCAM_MODE_WID 1 +#define MC13783_REGCTRL_VRFBG_EN_LSH 9 +#define MC13783_REGCTRL_VRFBG_EN_WID 1 +#define MC13783_REGCTRL_VRFBG_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFBG_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFBG_STBY_LSH 10 +#define MC13783_REGCTRL_VRFBG_STBY_WID 1 +#define MC13783_REGCTRL_VVIB_EN_LSH 11 +#define MC13783_REGCTRL_VVIB_EN_WID 1 +#define MC13783_REGCTRL_VVIB_EN_ENABLE 1 +#define MC13783_REGCTRL_VVIB_EN_DISABLE 0 +#define MC13783_REGCTRL_VRF1_EN_LSH 12 +#define MC13783_REGCTRL_VRF1_EN_WID 1 +#define MC13783_REGCTRL_VRF1_EN_ENABLE 1 +#define MC13783_REGCTRL_VRF1_EN_DISABLE 0 +#define MC13783_REGCTRL_VRF1_STBY_LSH 13 +#define MC13783_REGCTRL_VRF1_STBY_WID 1 +#define MC13783_REGCTRL_VRF1_MODE_LSH 14 +#define MC13783_REGCTRL_VRF1_MODE_WID 1 +#define MC13783_REGCTRL_VRF2_EN_LSH 15 +#define MC13783_REGCTRL_VRF2_EN_WID 1 +#define MC13783_REGCTRL_VRF2_EN_ENABLE 1 +#define MC13783_REGCTRL_VRF2_EN_DISABLE 0 +#define MC13783_REGCTRL_VRF2_STBY_LSH 16 +#define MC13783_REGCTRL_VRF2_STBY_WID 1 +#define MC13783_REGCTRL_VRF2_MODE_LSH 17 +#define MC13783_REGCTRL_VRF2_MODE_WID 1 +#define MC13783_REGCTRL_VMMC1_EN_LSH 18 +#define MC13783_REGCTRL_VMMC1_EN_WID 1 +#define MC13783_REGCTRL_VMMC1_EN_ENABLE 1 +#define MC13783_REGCTRL_VMMC1_EN_DISABLE 0 +#define MC13783_REGCTRL_VMMC1_STBY_LSH 19 +#define MC13783_REGCTRL_VMMC1_STBY_WID 1 +#define MC13783_REGCTRL_VMMC1_MODE_LSH 20 +#define MC13783_REGCTRL_VMMC1_MODE_WID 1 +#define MC13783_REGCTRL_VMMC2_EN_LSH 21 +#define MC13783_REGCTRL_VMMC2_EN_WID 1 +#define MC13783_REGCTRL_VMMC2_EN_ENABLE 1 +#define MC13783_REGCTRL_VMMC2_EN_DISABLE 0 +#define MC13783_REGCTRL_VMMC2_STBY_LSH 22 +#define MC13783_REGCTRL_VMMC2_STBY_WID 1 +#define MC13783_REGCTRL_VMMC2_MODE_LSH 23 +#define MC13783_REGCTRL_VMMC2_MODE_WID 1 + +/* + * Reg Regulator Misc. + */ +#define MC13783_REGCTRL_GPO1_EN_LSH 6 +#define MC13783_REGCTRL_GPO1_EN_WID 1 +#define MC13783_REGCTRL_GPO1_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO1_EN_DISABLE 0 +#define MC13783_REGCTRL_GPO2_EN_LSH 8 +#define MC13783_REGCTRL_GPO2_EN_WID 1 +#define MC13783_REGCTRL_GPO2_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO2_EN_DISABLE 0 +#define MC13783_REGCTRL_GPO3_EN_LSH 10 +#define MC13783_REGCTRL_GPO3_EN_WID 1 +#define MC13783_REGCTRL_GPO3_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO3_EN_DISABLE 0 +#define MC13783_REGCTRL_GPO4_EN_LSH 12 +#define MC13783_REGCTRL_GPO4_EN_WID 1 +#define MC13783_REGCTRL_GPO4_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO4_EN_DISABLE 0 +#define MC13783_REGCTRL_VIBPINCTRL_LSH 14 +#define MC13783_REGCTRL_VIBPINCTRL_WID 1 + +/* + * Reg Regulator Setting 0 + */ +#define MC13783_REGSET_VIOLO_LSH 2 +#define MC13783_REGSET_VIOLO_WID 2 +#define MC13783_REGSET_VDIG_LSH 4 +#define MC13783_REGSET_VDIG_WID 2 +#define MC13783_REGSET_VGEN_LSH 6 +#define MC13783_REGSET_VGEN_WID 3 +#define MC13783_REGSET_VRFDIG_LSH 9 +#define MC13783_REGSET_VRFDIG_WID 2 +#define MC13783_REGSET_VRFREF_LSH 11 +#define MC13783_REGSET_VRFREF_WID 2 +#define MC13783_REGSET_VRFCP_LSH 13 +#define MC13783_REGSET_VRFCP_WID 1 +#define MC13783_REGSET_VSIM_LSH 14 +#define MC13783_REGSET_VSIM_WID 1 +#define MC13783_REGSET_VESIM_LSH 15 +#define MC13783_REGSET_VESIM_WID 1 +#define MC13783_REGSET_VCAM_LSH 16 +#define MC13783_REGSET_VCAM_WID 3 + +/* + * Reg Regulator Setting 1 + */ +#define MC13783_REGSET_VVIB_LSH 0 +#define MC13783_REGSET_VVIB_WID 2 +#define MC13783_REGSET_VRF1_LSH 2 +#define MC13783_REGSET_VRF1_WID 2 +#define MC13783_REGSET_VRF2_LSH 4 +#define MC13783_REGSET_VRF2_WID 2 +#define MC13783_REGSET_VMMC1_LSH 6 +#define MC13783_REGSET_VMMC1_WID 3 +#define MC13783_REGSET_VMMC2_LSH 9 +#define MC13783_REGSET_VMMC2_WID 3 + +/* + * Reg Switcher 0 + */ +#define MC13783_SWSET_SW1A_LSH 0 +#define MC13783_SWSET_SW1A_WID 6 +#define MC13783_SWSET_SW1A_DVS_LSH 6 +#define MC13783_SWSET_SW1A_DVS_WID 6 +#define MC13783_SWSET_SW1A_STDBY_LSH 12 +#define MC13783_SWSET_SW1A_STDBY_WID 6 + +/* + * Reg Switcher 1 + */ +#define MC13783_SWSET_SW1B_LSH 0 +#define MC13783_SWSET_SW1B_WID 6 +#define MC13783_SWSET_SW1B_DVS_LSH 6 +#define MC13783_SWSET_SW1B_DVS_WID 6 +#define MC13783_SWSET_SW1B_STDBY_LSH 12 +#define MC13783_SWSET_SW1B_STDBY_WID 6 + +/* + * Reg Switcher 2 + */ +#define MC13783_SWSET_SW2A_LSH 0 +#define MC13783_SWSET_SW2A_WID 6 +#define MC13783_SWSET_SW2A_DVS_LSH 6 +#define MC13783_SWSET_SW2A_DVS_WID 6 +#define MC13783_SWSET_SW2A_STDBY_LSH 12 +#define MC13783_SWSET_SW2A_STDBY_WID 6 + +/* + * Reg Switcher 3 + */ +#define MC13783_SWSET_SW2B_LSH 0 +#define MC13783_SWSET_SW2B_WID 6 +#define MC13783_SWSET_SW2B_DVS_LSH 6 +#define MC13783_SWSET_SW2B_DVS_WID 6 +#define MC13783_SWSET_SW2B_STDBY_LSH 12 +#define MC13783_SWSET_SW2B_STDBY_WID 6 + +/* + * Reg Switcher 4 + */ +#define MC13783_SWCTRL_SW1A_MODE_LSH 0 +#define MC13783_SWCTRL_SW1A_MODE_WID 2 +#define MC13783_SWCTRL_SW1A_STBY_MODE_LSH 2 +#define MC13783_SWCTRL_SW1A_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW1A_DVS_SPEED_LSH 6 +#define MC13783_SWCTRL_SW1A_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW1A_PANIC_MODE_LSH 8 +#define MC13783_SWCTRL_SW1A_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW1A_SOFTSTART_LSH 9 +#define MC13783_SWCTRL_SW1A_SOFTSTART_WID 1 +#define MC13783_SWCTRL_SW1B_MODE_LSH 10 +#define MC13783_SWCTRL_SW1B_MODE_WID 2 +#define MC13783_SWCTRL_SW1B_STBY_MODE_LSH 12 +#define MC13783_SWCTRL_SW1B_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW1B_DVS_SPEED_LSH 14 +#define MC13783_SWCTRL_SW1B_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW1B_PANIC_MODE_LSH 16 +#define MC13783_SWCTRL_SW1B_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW1B_SOFTSTART_LSH 17 +#define MC13783_SWCTRL_SW1B_SOFTSTART_WID 1 +#define MC13783_SWCTRL_PLL_EN_LSH 18 +#define MC13783_SWCTRL_PLL_EN_WID 1 +#define MC13783_SWCTRL_PLL_EN_ENABLE 1 +#define MC13783_SWCTRL_PLL_EN_DISABLE 0 +#define MC13783_SWCTRL_PLL_FACTOR_LSH 19 +#define MC13783_SWCTRL_PLL_FACTOR_WID 3 + +/* + * Reg Switcher 5 + */ +#define MC13783_SWCTRL_SW2A_MODE_LSH 0 +#define MC13783_SWCTRL_SW2A_MODE_WID 2 +#define MC13783_SWCTRL_SW2A_STBY_MODE_LSH 2 +#define MC13783_SWCTRL_SW2A_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW2A_DVS_SPEED_LSH 6 +#define MC13783_SWCTRL_SW2A_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW2A_PANIC_MODE_LSH 8 +#define MC13783_SWCTRL_SW2A_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW2A_SOFTSTART_LSH 9 +#define MC13783_SWCTRL_SW2A_SOFTSTART_WID 1 +#define MC13783_SWCTRL_SW2B_MODE_LSH 10 +#define MC13783_SWCTRL_SW2B_MODE_WID 2 +#define MC13783_SWCTRL_SW2B_STBY_MODE_LSH 12 +#define MC13783_SWCTRL_SW2B_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW2B_DVS_SPEED_LSH 14 +#define MC13783_SWCTRL_SW2B_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW2B_PANIC_MODE_LSH 16 +#define MC13783_SWCTRL_SW2B_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW2B_SOFTSTART_LSH 17 +#define MC13783_SWCTRL_SW2B_SOFTSTART_WID 1 +#define MC13783_SWSET_SW3_LSH 18 +#define MC13783_SWSET_SW3_WID 2 +#define MC13783_SWCTRL_SW3_EN_LSH 20 +#define MC13783_SWCTRL_SW3_EN_WID 2 +#define MC13783_SWCTRL_SW3_EN_ENABLE 1 +#define MC13783_SWCTRL_SW3_EN_DISABLE 0 +#define MC13783_SWCTRL_SW3_STBY_LSH 21 +#define MC13783_SWCTRL_SW3_STBY_WID 1 +#define MC13783_SWCTRL_SW3_MODE_LSH 22 +#define MC13783_SWCTRL_SW3_MODE_WID 1 + +/* + * Switcher configuration + */ +#define MC13783_SWCTRL_SW_MODE_SYNC_RECT_EN 0 +#define MC13783_SWCTRL_SW_MODE_PULSE_NO_SKIP_EN 1 +#define MC13783_SWCTRL_SW_MODE_PULSE_SKIP_EN 2 +#define MC13783_SWCTRL_SW_MODE_LOW_POWER_EN 3 +#define MC13783_REGTRL_LP_MODE_ENABLE 1 +#define MC13783_REGTRL_LP_MODE_DISABLE 0 +#define MC13783_REGTRL_STBY_MODE_ENABLE 1 +#define MC13783_REGTRL_STBY_MODE_DISABLE 0 + +#endif /* __MC13783_POWER_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_rtc.c b/drivers/mxc/pmic/mc13783/pmic_rtc.c new file mode 100644 index 000000000000..50f086f87022 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_rtc.c @@ -0,0 +1,552 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_rtc.c + * @brief This is the main file of PMIC(mc13783) RTC driver. + * + * @ingroup PMIC_RTC + */ + +/* + * Includes + */ +#include <linux/wait.h> +#include <linux/poll.h> +#include <linux/platform_device.h> +#include <linux/pmic_rtc.h> +#include <linux/pmic_status.h> + +#include "pmic_rtc_defs.h" + +#define PMIC_LOAD_ERROR_MSG \ +"PMIC card was not correctly detected. Stop loading PMIC RTC driver\n" + +/* + * Global variables + */ +static int pmic_rtc_major; +static void callback_alarm_asynchronous(void *); +static void callback_alarm_synchronous(void *); +static unsigned int pmic_rtc_poll(struct file *file, poll_table * wait); +static DECLARE_WAIT_QUEUE_HEAD(queue_alarm); +static DECLARE_WAIT_QUEUE_HEAD(pmic_rtc_wait); +static pmic_event_callback_t alarm_callback; +static pmic_event_callback_t rtc_callback; +static int pmic_rtc_detected = 0; +static bool pmic_rtc_done = 0; +static struct class *pmic_rtc_class; + +static DECLARE_MUTEX(mutex); + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_rtc_set_time); +EXPORT_SYMBOL(pmic_rtc_get_time); +EXPORT_SYMBOL(pmic_rtc_set_time_alarm); +EXPORT_SYMBOL(pmic_rtc_get_time_alarm); +EXPORT_SYMBOL(pmic_rtc_wait_alarm); +EXPORT_SYMBOL(pmic_rtc_event_sub); +EXPORT_SYMBOL(pmic_rtc_event_unsub); +EXPORT_SYMBOL(pmic_rtc_loaded); + +/* + * Real Time Clock Pmic API + */ + +/*! + * This is the callback function called on TSI Pmic event, used in asynchronous + * call. + */ +static void callback_alarm_asynchronous(void *unused) +{ + pmic_rtc_done = true; +} + +/*! + * This is the callback function is used in test code for (un)sub. + */ +static void callback_test_sub(void) +{ + printk(KERN_INFO "*****************************************\n"); + printk(KERN_INFO "***** PMIC RTC 'Alarm IT CallBack' ******\n"); + printk(KERN_INFO "*****************************************\n"); +} + +/*! + * This is the callback function called on TSI Pmic event, used in synchronous + * call. + */ +static void callback_alarm_synchronous(void *unused) +{ + printk(KERN_INFO "*** Alarm IT Pmic ***\n"); + wake_up(&queue_alarm); +} + +/*! + * This function wait the Alarm event + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_wait_alarm(void) +{ + DEFINE_WAIT(wait); + alarm_callback.func = callback_alarm_synchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_subscribe(EVENT_TODAI, alarm_callback)); + prepare_to_wait(&queue_alarm, &wait, TASK_UNINTERRUPTIBLE); + schedule(); + finish_wait(&queue_alarm, &wait); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_TODAI, alarm_callback)); + return PMIC_SUCCESS; +} + +/*! + * This function set the real time clock of PMIC + * + * @param pmic_time value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_set_time(struct timeval * pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + + tod_reg_val = pmic_time->tv_sec % 86400; + day_reg_val = pmic_time->tv_sec / 86400; + + mask = BITFMASK(MC13783_RTCTIME_TIME); + value = BITFVAL(MC13783_RTCTIME_TIME, tod_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_TIME, value, mask)); + + mask = BITFMASK(MC13783_RTCDAY_DAY); + value = BITFVAL(MC13783_RTCDAY_DAY, day_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_DAY, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function get the real time clock of PMIC + * + * @param pmic_time return value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_get_time(struct timeval * pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + + mask = BITFMASK(MC13783_RTCTIME_TIME); + CHECK_ERROR(pmic_read_reg(REG_RTC_TIME, &value, mask)); + tod_reg_val = BITFEXT(value, MC13783_RTCTIME_TIME); + + mask = BITFMASK(MC13783_RTCDAY_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY, &value, mask)); + day_reg_val = BITFEXT(value, MC13783_RTCDAY_DAY); + + pmic_time->tv_sec = (unsigned long)((unsigned long)(tod_reg_val & + 0x0001FFFF) + + (unsigned long)(day_reg_val * + 86400)); + return PMIC_SUCCESS; +} + +/*! + * This function set the real time clock alarm of PMIC + * + * @param pmic_time value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_set_time_alarm(struct timeval * pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + int ret; + + if ((ret = down_interruptible(&mutex)) < 0) + return ret; + + tod_reg_val = pmic_time->tv_sec % 86400; + day_reg_val = pmic_time->tv_sec / 86400; + + mask = BITFMASK(MC13783_RTCALARM_TIME); + value = BITFVAL(MC13783_RTCALARM_TIME, tod_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_ALARM, value, mask)); + + mask = BITFMASK(MC13783_RTCALARM_DAY); + value = BITFVAL(MC13783_RTCALARM_DAY, day_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_DAY_ALARM, value, mask)); + up(&mutex); + return PMIC_SUCCESS; +} + +/*! + * This function get the real time clock alarm of PMIC + * + * @param pmic_time return value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_get_time_alarm(struct timeval * pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + + mask = BITFMASK(MC13783_RTCALARM_TIME); + CHECK_ERROR(pmic_read_reg(REG_RTC_ALARM, &value, mask)); + tod_reg_val = BITFEXT(value, MC13783_RTCALARM_TIME); + + mask = BITFMASK(MC13783_RTCALARM_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY_ALARM, &value, mask)); + day_reg_val = BITFEXT(value, MC13783_RTCALARM_DAY); + + pmic_time->tv_sec = (unsigned long)((unsigned long)(tod_reg_val & + 0x0001FFFF) + + (unsigned long)(day_reg_val * + 86400)); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to un/subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event(t_rtc_int event, void *callback, bool sub) +{ + type_event rtc_event; + if (callback == NULL) { + return PMIC_ERROR; + } else { + rtc_callback.func = callback; + rtc_callback.param = NULL; + } + switch (event) { + case RTC_IT_ALARM: + rtc_event = EVENT_TODAI; + break; + case RTC_IT_1HZ: + rtc_event = EVENT_E1HZI; + break; + case RTC_IT_RST: + rtc_event = EVENT_RTCRSTI; + break; + default: + return PMIC_PARAMETER_ERROR; + } + if (sub == true) { + CHECK_ERROR(pmic_event_subscribe(rtc_event, rtc_callback)); + } else { + CHECK_ERROR(pmic_event_unsubscribe(rtc_event, rtc_callback)); + } + return PMIC_SUCCESS; +} + +/*! + * This function is used to subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event_sub(t_rtc_int event, void *callback) +{ + CHECK_ERROR(pmic_rtc_event(event, callback, true)); + return PMIC_SUCCESS; +} + +/*! + * This function is used to un subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event_unsub(t_rtc_int event, void *callback) +{ + CHECK_ERROR(pmic_rtc_event(event, callback, false)); + return PMIC_SUCCESS; +} + +/* Called without the kernel lock - fine */ +static unsigned int pmic_rtc_poll(struct file *file, poll_table * wait) +{ + /*poll_wait(file, &pmic_rtc_wait, wait); */ + + if (pmic_rtc_done) + return POLLIN | POLLRDNORM; + return 0; +} + +/*! + * This function implements IOCTL controls on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_rtc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct timeval *pmic_time = NULL; + + if (_IOC_TYPE(cmd) != 'p') + return -ENOTTY; + + if (arg) { + if ((pmic_time = kmalloc(sizeof(struct timeval), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + /* if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) { + return -EFAULT; + } */ + } + + switch (cmd) { + case PMIC_RTC_SET_TIME: + if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("SET RTC\n"); + CHECK_ERROR(pmic_rtc_set_time(pmic_time)); + break; + case PMIC_RTC_GET_TIME: + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("GET RTC\n"); + CHECK_ERROR(pmic_rtc_get_time(pmic_time)); + break; + case PMIC_RTC_SET_ALARM: + if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("SET RTC ALARM\n"); + CHECK_ERROR(pmic_rtc_set_time_alarm(pmic_time)); + break; + case PMIC_RTC_GET_ALARM: + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("GET RTC ALARM\n"); + CHECK_ERROR(pmic_rtc_get_time_alarm(pmic_time)); + break; + case PMIC_RTC_WAIT_ALARM: + printk(KERN_INFO "WAIT ALARM...\n"); + CHECK_ERROR(pmic_rtc_event_sub(RTC_IT_ALARM, + callback_test_sub)); + CHECK_ERROR(pmic_rtc_wait_alarm()); + printk(KERN_INFO "ALARM DONE\n"); + CHECK_ERROR(pmic_rtc_event_unsub(RTC_IT_ALARM, + callback_test_sub)); + break; + case PMIC_RTC_ALARM_REGISTER: + printk(KERN_INFO "PMIC RTC ALARM REGISTER\n"); + alarm_callback.func = callback_alarm_asynchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_subscribe(EVENT_TODAI, alarm_callback)); + break; + case PMIC_RTC_ALARM_UNREGISTER: + printk(KERN_INFO "PMIC RTC ALARM UNREGISTER\n"); + alarm_callback.func = callback_alarm_asynchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_unsubscribe + (EVENT_TODAI, alarm_callback)); + pmic_rtc_done = false; + break; + default: + pr_debug("%d unsupported ioctl command\n", (int)cmd); + return -EINVAL; + } + + if (arg) { + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) { + return -EFAULT; + } + kfree(pmic_time); + } + + return 0; +} + +/*! + * This function implements the open method on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_rtc_open(struct inode *inode, struct file *file) +{ + return 0; +} + +/*! + * This function implements the release method on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_rtc_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/*! + * This function is called to put the RTC in a low power state. + * There is no need for power handlers for the RTC device. + * The RTC cannot be suspended. + * + * @param pdev the device structure used to give information on which RTC + * device (0 through 3 channels) to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int pmic_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +/*! + * This function is called to resume the RTC from a low power state. + * + * @param pdev the device structure used to give information on which RTC + * device (0 through 3 channels) to suspend + * + * @return The function always returns 0. + */ +static int pmic_rtc_resume(struct platform_device *pdev) +{ + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ + +static struct file_operations pmic_rtc_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_rtc_ioctl, + .poll = pmic_rtc_poll, + .open = pmic_rtc_open, + .release = pmic_rtc_release, +}; + +int pmic_rtc_loaded(void) +{ + return pmic_rtc_detected; +} + +static int pmic_rtc_remove(struct platform_device *pdev) +{ + device_destroy(pmic_rtc_class, MKDEV(pmic_rtc_major, 0)); + class_destroy(pmic_rtc_class); + unregister_chrdev(pmic_rtc_major, "pmic_rtc"); + return 0; +} + +static int pmic_rtc_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *temp_class; + + pmic_rtc_major = register_chrdev(0, "pmic_rtc", &pmic_rtc_fops); + if (pmic_rtc_major < 0) { + printk(KERN_ERR "Unable to get a major for pmic_rtc\n"); + return pmic_rtc_major; + } + + pmic_rtc_class = class_create(THIS_MODULE, "pmic_rtc"); + if (IS_ERR(pmic_rtc_class)) { + printk(KERN_ERR "Error creating pmic rtc class.\n"); + ret = PTR_ERR(pmic_rtc_class); + goto err_out1; + } + + temp_class = device_create(pmic_rtc_class, NULL, + MKDEV(pmic_rtc_major, 0), NULL, + "pmic_rtc"); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating pmic rtc class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + pmic_rtc_detected = 1; + printk(KERN_INFO "PMIC RTC successfully probed\n"); + return ret; + + err_out2: + class_destroy(pmic_rtc_class); + err_out1: + unregister_chrdev(pmic_rtc_major, "pmic_rtc"); + return ret; +} + +static struct platform_driver pmic_rtc_driver_ldm = { + .driver = { + .name = "pmic_rtc", + .owner = THIS_MODULE, + }, + .suspend = pmic_rtc_suspend, + .resume = pmic_rtc_resume, + .probe = pmic_rtc_probe, + .remove = pmic_rtc_remove, +}; + +static int __init pmic_rtc_init(void) +{ + pr_debug("PMIC RTC driver loading...\n"); + return platform_driver_register(&pmic_rtc_driver_ldm); +} +static void __exit pmic_rtc_exit(void) +{ + platform_driver_unregister(&pmic_rtc_driver_ldm); + pr_debug("PMIC RTC driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_rtc_init); +module_exit(pmic_rtc_exit); + +MODULE_DESCRIPTION("Pmic_rtc driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_rtc_defs.h b/drivers/mxc/pmic/mc13783/pmic_rtc_defs.h new file mode 100644 index 000000000000..16e968dd9977 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_rtc_defs.h @@ -0,0 +1,47 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MC13783_RTC_DEFS_H__ +#define __MC13783_RTC_DEFS_H__ + +/*! + * @file mc13783/pmic_rtc_defs.h + * @brief This is the internal header of PMIC(mc13783) RTC driver. + * + * @ingroup PMIC_RTC + */ + +/* + * RTC Time + */ +#define MC13783_RTCTIME_TIME_LSH 0 +#define MC13783_RTCTIME_TIME_WID 17 + +/* + * RTC Alarm + */ +#define MC13783_RTCALARM_TIME_LSH 0 +#define MC13783_RTCALARM_TIME_WID 17 + +/* + * RTC Day + */ +#define MC13783_RTCDAY_DAY_LSH 0 +#define MC13783_RTCDAY_DAY_WID 15 + +/* + * RTC Day alarm + */ +#define MC13783_RTCALARM_DAY_LSH 0 +#define MC13783_RTCALARM_DAY_WID 15 + +#endif /* __MC13783_RTC_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13892/Kconfig b/drivers/mxc/pmic/mc13892/Kconfig new file mode 100644 index 000000000000..930e06ab2282 --- /dev/null +++ b/drivers/mxc/pmic/mc13892/Kconfig @@ -0,0 +1,48 @@ +# +# PMIC Modules configuration +# + +config MXC_MC13892_ADC + tristate "MC13892 ADC support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 ADC module driver. This module provides kernel API + for the ADC system of MC13892. + It controls also the touch screen interface. + If you want MC13892 ADC support, you should say Y here + +config MXC_MC13892_RTC + tristate "MC13892 Real Time Clock (RTC) support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 RTC module driver. This module provides kernel API + for RTC part of MC13892. + If you want MC13892 RTC support, you should say Y here +config MXC_MC13892_LIGHT + tristate "MC13892 Light and Backlight support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 Light module driver. This module provides kernel API + for led and backlight control part of MC13892. + If you want MC13892 Light support, you should say Y here +config MXC_MC13892_BATTERY + tristate "MC13892 Battery API support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 battery module driver. This module provides kernel API + for battery control part of MC13892. + If you want MC13892 battery support, you should say Y here +config MXC_MC13892_CONNECTIVITY + tristate "MC13892 Connectivity API support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 connectivity module driver. This module provides kernel API + for USB/RS232 connectivity control part of MC13892. + If you want MC13892 connectivity support, you should say Y here +config MXC_MC13892_POWER + tristate "MC13892 Power API support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 power and supplies module driver. This module provides kernel API + for power and regulator control part of MC13892. + If you want MC13892 power support, you should say Y here diff --git a/drivers/mxc/pmic/mc13892/Makefile b/drivers/mxc/pmic/mc13892/Makefile new file mode 100644 index 000000000000..0ed2b7eb4c11 --- /dev/null +++ b/drivers/mxc/pmic/mc13892/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the mc13783 pmic drivers. +# + +obj-$(CONFIG_MXC_MC13892_ADC) += pmic_adc.o +#obj-$(CONFIG_MXC_MC13892_RTC) += pmic_rtc.o +obj-$(CONFIG_MXC_MC13892_LIGHT) += pmic_light.o +obj-$(CONFIG_MXC_MC13892_BATTERY) += pmic_battery.o +#obj-$(CONFIG_MXC_MC13892_CONNECTIVITY) += pmic_convity.o +#obj-$(CONFIG_MXC_MC13892_POWER) += pmic_power.o diff --git a/drivers/mxc/pmic/mc13892/pmic_adc.c b/drivers/mxc/pmic/mc13892/pmic_adc.c new file mode 100644 index 000000000000..cec4d6045974 --- /dev/null +++ b/drivers/mxc/pmic/mc13892/pmic_adc.c @@ -0,0 +1,984 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/device.h> + +#include <linux/pmic_adc.h> +#include <linux/pmic_status.h> + +#include "../core/pmic.h" + +#define DEF_ADC_0 0x008000 +#define DEF_ADC_3 0x0001c0 + +#define ADC_NB_AVAILABLE 2 + +#define MAX_CHANNEL 7 + +#define MC13892_ADC0_TS_M_LSH 14 +#define MC13892_ADC0_TS_M_WID 3 + +/* + * Maximun allowed variation in the three X/Y co-ordinates acquired from + * touch-screen + */ +#define DELTA_Y_MAX 50 +#define DELTA_X_MAX 50 + +/* + * ADC 0 + */ +#define ADC_WAIT_TSI_0 0x001400 + +#define ADC_INC 0x030000 +#define ADC_BIS 0x800000 +#define ADC_CHRGRAW_D5 0x008000 + +/* + * ADC 1 + */ + +#define ADC_EN 0x000001 +#define ADC_SGL_CH 0x000002 +#define ADC_ADSEL 0x000008 +#define ADC_CH_0_POS 5 +#define ADC_CH_0_MASK 0x0000E0 +#define ADC_CH_1_POS 8 +#define ADC_CH_1_MASK 0x000700 +#define ADC_DELAY_POS 11 +#define ADC_DELAY_MASK 0x07F800 +#define ADC_ATO 0x080000 +#define ASC_ADC 0x100000 +#define ADC_WAIT_TSI_1 0x200001 +#define ADC_NO_ADTRIG 0x200000 + +/* + * ADC 2 - 4 + */ +#define ADD1_RESULT_MASK 0x00000FFC +#define ADD2_RESULT_MASK 0x00FFC000 +#define ADC_TS_MASK 0x00FFCFFC + +#define ADC_WCOMP 0x040000 +#define ADC_WCOMP_H_POS 0 +#define ADC_WCOMP_L_POS 9 +#define ADC_WCOMP_H_MASK 0x00003F +#define ADC_WCOMP_L_MASK 0x007E00 + +#define ADC_MODE_MASK 0x00003F + +#define ADC_INT_BISDONEI 0x02 +#define ADC_TSMODE_MASK 0x007000 + +typedef enum adc_state { + ADC_FREE, + ADC_USED, + ADC_MONITORING, +} t_adc_state; + +typedef enum reading_mode { + /*! + * Enables lithium cell reading + */ + M_LITHIUM_CELL = 0x000001, + /*! + * Enables charge current reading + */ + M_CHARGE_CURRENT = 0x000002, + /*! + * Enables battery current reading + */ + M_BATTERY_CURRENT = 0x000004, +} t_reading_mode; + +typedef struct { + /*! + * Delay before first conversion + */ + unsigned int delay; + /*! + * sets the ATX bit for delay on all conversion + */ + bool conv_delay; + /*! + * Sets the single channel mode + */ + bool single_channel; + /*! + * Channel selection 1 + */ + t_channel channel_0; + /*! + * Channel selection 2 + */ + t_channel channel_1; + /*! + * Used to configure ADC mode with t_reading_mode + */ + t_reading_mode read_mode; + /*! + * Sets the Touch screen mode + */ + bool read_ts; + /*! + * Wait TSI event before touch screen reading + */ + bool wait_tsi; + /*! + * Sets CHRGRAW scaling to divide by 5 + * Only supported on 2.0 and higher + */ + bool chrgraw_devide_5; + /*! + * Return ADC values + */ + unsigned int value[8]; + /*! + * Return touch screen values + */ + t_touch_screen ts_value; +} t_adc_param; + +static int pmic_adc_filter(t_touch_screen *ts_curr); +int mc13892_adc_request(bool read_ts); +int mc13892_adc_release(int adc_index); +t_reading_mode mc13892_set_read_mode(t_channel channel); +PMIC_STATUS mc13892_adc_read_ts(t_touch_screen *touch_sample, int wait_tsi); + +/* internal function */ +static void callback_tsi(void *); +static void callback_adcdone(void *); +static void callback_adcbisdone(void *); + +static int swait; + +static int suspend_flag; + +static wait_queue_head_t suspendq; + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_adc_init); +EXPORT_SYMBOL(pmic_adc_deinit); +EXPORT_SYMBOL(pmic_adc_convert); +EXPORT_SYMBOL(pmic_adc_convert_8x); +EXPORT_SYMBOL(pmic_adc_set_touch_mode); +EXPORT_SYMBOL(pmic_adc_get_touch_mode); +EXPORT_SYMBOL(pmic_adc_get_touch_sample); + +static DECLARE_COMPLETION(adcdone_it); +static DECLARE_COMPLETION(adcbisdone_it); +static DECLARE_COMPLETION(adc_tsi); +static pmic_event_callback_t tsi_event; +static pmic_event_callback_t event_adc; +static pmic_event_callback_t event_adc_bis; +static bool data_ready_adc_1; +static bool data_ready_adc_2; +static bool adc_ts; +static bool wait_ts; +static bool monitor_en; +static bool monitor_adc; +static DECLARE_MUTEX(convert_mutex); + +static DECLARE_WAIT_QUEUE_HEAD(queue_adc_busy); +static t_adc_state adc_dev[2]; + +static unsigned channel_num[] = { + 0, + 1, + 3, + 4, + 2, + 0, + 1, + 3, + 4, + -1, + 5, + 6, + 7, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 +}; + +static bool pmic_adc_ready; + +int is_pmic_adc_ready() +{ + return pmic_adc_ready; +} +EXPORT_SYMBOL(is_pmic_adc_ready); + + +static int pmic_adc_suspend(struct platform_device *pdev, pm_message_t state) +{ + suspend_flag = 1; + CHECK_ERROR(pmic_write_reg(REG_ADC0, DEF_ADC_0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC1, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC2, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC3, DEF_ADC_3, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC4, 0, PMIC_ALL_BITS)); + + return 0; +}; + +static int pmic_adc_resume(struct platform_device *pdev) +{ + /* nothing for mc13892 adc */ + unsigned int adc_0_reg, adc_1_reg, reg_mask; + suspend_flag = 0; + + /* let interrupt of TSI again */ + adc_0_reg = ADC_WAIT_TSI_0; + reg_mask = ADC_WAIT_TSI_0; + CHECK_ERROR(pmic_write_reg(REG_ADC0, adc_0_reg, reg_mask)); + adc_1_reg = ADC_WAIT_TSI_1 | (ADC_BIS * adc_ts); + CHECK_ERROR(pmic_write_reg(REG_ADC1, adc_1_reg, PMIC_ALL_BITS)); + + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +static void callback_tsi(void *unused) +{ + pr_debug("*** TSI IT mc13892 PMIC_ADC_GET_TOUCH_SAMPLE ***\n"); + if (wait_ts) { + complete(&adc_tsi); + pmic_event_mask(EVENT_TSI); + } +} + +static void callback_adcdone(void *unused) +{ + if (data_ready_adc_1) + complete(&adcdone_it); +} + +static void callback_adcbisdone(void *unused) +{ + pr_debug("* adcdone bis it callback *\n"); + if (data_ready_adc_2) + complete(&adcbisdone_it); +} + +static int pmic_adc_filter(t_touch_screen *ts_curr) +{ + unsigned int ydiff, xdiff; + unsigned int sample_sumx, sample_sumy; + + if (ts_curr->contact_resistance == 0) { + ts_curr->x_position = 0; + ts_curr->y_position = 0; + return 0; + } + + ydiff = abs(ts_curr->y_position1 - ts_curr->y_position2); + if (ydiff > DELTA_Y_MAX) { + pr_debug("pmic_adc_filter: Ret pos y\n"); + return -1; + } + + xdiff = abs(ts_curr->x_position1 - ts_curr->x_position2); + if (xdiff > DELTA_X_MAX) { + pr_debug("mc13892_adc_filter: Ret pos x\n"); + return -1; + } + + sample_sumx = ts_curr->x_position1 + ts_curr->x_position2; + sample_sumy = ts_curr->y_position1 + ts_curr->y_position2; + + ts_curr->y_position = sample_sumy / 2; + ts_curr->x_position = sample_sumx / 2; + + return 0; +} + +int pmic_adc_init(void) +{ + unsigned int reg_value = 0, i = 0; + + if (suspend_flag == 1) + return -EBUSY; + + for (i = 0; i < ADC_NB_AVAILABLE; i++) + adc_dev[i] = ADC_FREE; + + CHECK_ERROR(pmic_write_reg(REG_ADC0, DEF_ADC_0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC1, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC2, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC3, DEF_ADC_3, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC4, 0, PMIC_ALL_BITS)); + reg_value = 0x001000; + + data_ready_adc_1 = false; + data_ready_adc_2 = false; + adc_ts = false; + wait_ts = false; + monitor_en = false; + monitor_adc = false; + + /* sub to ADCDone IT */ + event_adc.param = NULL; + event_adc.func = callback_adcdone; + CHECK_ERROR(pmic_event_subscribe(EVENT_ADCDONEI, event_adc)); + + /* sub to ADCDoneBis IT */ + event_adc_bis.param = NULL; + event_adc_bis.func = callback_adcbisdone; + CHECK_ERROR(pmic_event_subscribe(EVENT_ADCBISDONEI, event_adc_bis)); + + /* sub to Touch Screen IT */ + tsi_event.param = NULL; + tsi_event.func = callback_tsi; + CHECK_ERROR(pmic_event_subscribe(EVENT_TSI, tsi_event)); + + return PMIC_SUCCESS; +} + +PMIC_STATUS pmic_adc_deinit(void) +{ + CHECK_ERROR(pmic_event_unsubscribe(EVENT_ADCDONEI, event_adc)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_ADCBISDONEI, event_adc_bis)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_TSI, tsi_event)); + + return PMIC_SUCCESS; +} + +int mc13892_adc_init_param(t_adc_param * adc_param) +{ + int i = 0; + + if (suspend_flag == 1) + return -EBUSY; + + adc_param->delay = 0; + adc_param->conv_delay = false; + adc_param->single_channel = false; + adc_param->channel_0 = BATTERY_VOLTAGE; + adc_param->channel_1 = BATTERY_VOLTAGE; + adc_param->read_mode = 0; + adc_param->wait_tsi = 0; + adc_param->chrgraw_devide_5 = true; + adc_param->read_ts = false; + adc_param->ts_value.x_position = 0; + adc_param->ts_value.y_position = 0; + adc_param->ts_value.contact_resistance = 0; + for (i = 0; i <= MAX_CHANNEL; i++) + adc_param->value[i] = 0; + + return 0; +} + +PMIC_STATUS mc13892_adc_convert(t_adc_param * adc_param) +{ + bool use_bis = false; + unsigned int adc_0_reg = 0, adc_1_reg = 0, reg_1 = 0, result_reg = + 0, i = 0; + unsigned int result = 0, temp = 0; + pmic_version_t mc13892_ver; + pr_debug("mc13892 ADC - mc13892_adc_convert ....\n"); + if (suspend_flag == 1) + return -EBUSY; + + if (adc_param->wait_tsi) { + /* configure adc to wait tsi interrupt */ + INIT_COMPLETION(adc_tsi); + + /*for ts don't use bis */ + /*put ts in interrupt mode */ + /* still kep reference? */ + adc_0_reg = 0x001400 | (ADC_BIS * 0); + pmic_event_unmask(EVENT_TSI); + CHECK_ERROR(pmic_write_reg(REG_ADC0, adc_0_reg, PMIC_ALL_BITS)); + /*for ts don't use bis */ + adc_1_reg = 0x200001 | (ADC_BIS * 0); + CHECK_ERROR(pmic_write_reg(REG_ADC1, adc_1_reg, PMIC_ALL_BITS)); + pr_debug("wait tsi ....\n"); + wait_ts = true; + wait_for_completion_interruptible(&adc_tsi); + wait_ts = false; + } + down(&convert_mutex); + use_bis = mc13892_adc_request(adc_param->read_ts); + if (use_bis < 0) { + pr_debug("process has received a signal and got interrupted\n"); + return -EINTR; + } + + /* CONFIGURE ADC REG 0 */ + adc_0_reg = 0; + adc_1_reg = 0; + if (adc_param->read_ts == false) { + adc_0_reg = adc_param->read_mode & 0x00003F; + /* add auto inc */ + adc_0_reg |= ADC_INC; + if (use_bis) { + /* add adc bis */ + adc_0_reg |= ADC_BIS; + } + mc13892_ver = pmic_get_version(); + if (mc13892_ver.revision >= 20) + if (adc_param->chrgraw_devide_5) + adc_0_reg |= ADC_CHRGRAW_D5; + + if (adc_param->single_channel) + adc_1_reg |= ADC_SGL_CH; + + if (adc_param->conv_delay) + adc_1_reg |= ADC_ATO; + + if (adc_param->single_channel) + adc_1_reg |= ADC_SGL_CH; + + adc_1_reg |= (adc_param->channel_0 << ADC_CH_0_POS) & + ADC_CH_0_MASK; + adc_1_reg |= (adc_param->channel_1 << ADC_CH_1_POS) & + ADC_CH_1_MASK; + } else { + adc_0_reg = 0x002400 | (ADC_BIS * use_bis) | ADC_INC; + } + pr_debug("Write Reg %i = %x\n", REG_ADC0, adc_0_reg); + /*Change has been made here */ + CHECK_ERROR(pmic_write_reg(REG_ADC0, adc_0_reg, + ADC_INC | ADC_BIS | ADC_CHRGRAW_D5 | + 0xfff00ff)); + /* CONFIGURE ADC REG 1 */ + if (adc_param->read_ts == false) { + adc_1_reg |= ADC_NO_ADTRIG; + adc_1_reg |= ADC_EN; + adc_1_reg |= (adc_param->delay << ADC_DELAY_POS) & + ADC_DELAY_MASK; + if (use_bis) + adc_1_reg |= ADC_BIS; + } else { + /* configure and start convert to read x and y position */ + /* configure to read 2 value in channel selection 1 & 2 */ + adc_1_reg = 0x100409 | (ADC_BIS * use_bis) | ADC_NO_ADTRIG; + /* set ATOx = 5, it could be better for ts ADC */ + adc_1_reg |= 0x002800; + } + reg_1 = adc_1_reg; + if (use_bis == 0) { + data_ready_adc_1 = false; + adc_1_reg |= ASC_ADC; + data_ready_adc_1 = true; + pr_debug("Write Reg %i = %x\n", REG_ADC1, adc_1_reg); + INIT_COMPLETION(adcdone_it); + CHECK_ERROR(pmic_write_reg(REG_ADC1, adc_1_reg, + ADC_SGL_CH | ADC_ATO | ADC_ADSEL + | ADC_CH_0_MASK | ADC_CH_1_MASK | + ADC_NO_ADTRIG | ADC_EN | + ADC_DELAY_MASK | ASC_ADC | ADC_BIS)); + pr_debug("wait adc done \n"); + wait_for_completion_interruptible(&adcdone_it); + data_ready_adc_1 = false; + } else { + data_ready_adc_2 = false; + adc_1_reg |= ASC_ADC; + data_ready_adc_2 = true; + INIT_COMPLETION(adcbisdone_it); + CHECK_ERROR(pmic_write_reg(REG_ADC1, adc_1_reg, 0xFFFFFF)); + temp = 0x800000; + CHECK_ERROR(pmic_write_reg(REG_ADC3, temp, 0xFFFFFF)); + pr_debug("wait adc done bis\n"); + wait_for_completion_interruptible(&adcbisdone_it); + data_ready_adc_2 = false; + } + /* read result and store in adc_param */ + result = 0; + if (use_bis == 0) + result_reg = REG_ADC2; + else + result_reg = REG_ADC4; + + CHECK_ERROR(pmic_write_reg(REG_ADC1, 4 << ADC_CH_1_POS, + ADC_CH_0_MASK | ADC_CH_1_MASK)); + + for (i = 0; i <= 3; i++) { + CHECK_ERROR(pmic_read_reg(result_reg, &result, PMIC_ALL_BITS)); + adc_param->value[i] = ((result & ADD1_RESULT_MASK) >> 2); + adc_param->value[i + 4] = ((result & ADD2_RESULT_MASK) >> 14); + pr_debug("value[%d] = %d, value[%d] = %d\n", + i, adc_param->value[i], + i + 4, adc_param->value[i + 4]); + } + if (adc_param->read_ts) { + adc_param->ts_value.x_position = adc_param->value[0]; + adc_param->ts_value.x_position1 = adc_param->value[0]; + adc_param->ts_value.x_position2 = adc_param->value[1]; + adc_param->ts_value.y_position = adc_param->value[3]; + adc_param->ts_value.y_position1 = adc_param->value[3]; + adc_param->ts_value.y_position2 = adc_param->value[4]; + adc_param->ts_value.contact_resistance = adc_param->value[6]; + CHECK_ERROR(pmic_write_reg(REG_ADC0, 0x0, + ADC_TSMODE_MASK)); + } + + /*if (adc_param->read_ts) { + adc_param->ts_value.x_position = adc_param->value[2]; + adc_param->ts_value.y_position = adc_param->value[5]; + adc_param->ts_value.contact_resistance = adc_param->value[6]; + } */ + mc13892_adc_release(use_bis); + up(&convert_mutex); + + return PMIC_SUCCESS; +} + +t_reading_mode mc13892_set_read_mode(t_channel channel) +{ + t_reading_mode read_mode = 0; + + switch (channel) { + case CHARGE_CURRENT: + read_mode = M_CHARGE_CURRENT; + break; + case BATTERY_CURRENT: + read_mode = M_BATTERY_CURRENT; + break; + default: + read_mode = 0; + } + + return read_mode; +} + +PMIC_STATUS pmic_adc_convert(t_channel channel, unsigned short *result) +{ + t_adc_param adc_param; + PMIC_STATUS ret; + + if (suspend_flag == 1) + return -EBUSY; + + channel = channel_num[channel]; + if (channel == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + mc13892_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert\n"); + adc_param.read_ts = false; + adc_param.single_channel = true; + adc_param.read_mode = mc13892_set_read_mode(channel); + + /* Find the group */ + if (channel <= 7) + adc_param.channel_0 = channel; + else + return PMIC_PARAMETER_ERROR; + + ret = mc13892_adc_convert(&adc_param); + *result = adc_param.value[0]; + return ret; +} + +PMIC_STATUS pmic_adc_convert_8x(t_channel channel, unsigned short *result) +{ + t_adc_param adc_param; + int i; + PMIC_STATUS ret; + if (suspend_flag == 1) + return -EBUSY; + + channel = channel_num[channel]; + + if (channel == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + mc13892_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert_8x\n"); + adc_param.read_ts = false; + adc_param.single_channel = true; + adc_param.read_mode = mc13892_set_read_mode(channel); + + if (channel <= 7) { + adc_param.channel_0 = channel; + adc_param.channel_1 = channel; + } else + return PMIC_PARAMETER_ERROR; + + ret = mc13892_adc_convert(&adc_param); + for (i = 0; i <= 7; i++) + result[i] = adc_param.value[i]; + + return ret; +} + +PMIC_STATUS pmic_adc_set_touch_mode(t_touch_mode touch_mode) +{ + if (suspend_flag == 1) + return -EBUSY; + + CHECK_ERROR(pmic_write_reg(REG_ADC0, + BITFVAL(MC13892_ADC0_TS_M, touch_mode), + BITFMASK(MC13892_ADC0_TS_M))); + return PMIC_SUCCESS; +} + +PMIC_STATUS pmic_adc_get_touch_mode(t_touch_mode * touch_mode) +{ + unsigned int value; + if (suspend_flag == 1) + return -EBUSY; + + CHECK_ERROR(pmic_read_reg(REG_ADC0, &value, PMIC_ALL_BITS)); + + *touch_mode = BITFEXT(value, MC13892_ADC0_TS_M); + + return PMIC_SUCCESS; +} + +PMIC_STATUS pmic_adc_get_touch_sample(t_touch_screen *touch_sample, int wait) +{ + if (mc13892_adc_read_ts(touch_sample, wait) != 0) + return PMIC_ERROR; + if (0 == pmic_adc_filter(touch_sample)) + return PMIC_SUCCESS; + else + return PMIC_ERROR; +} + +PMIC_STATUS mc13892_adc_read_ts(t_touch_screen *ts_value, int wait_tsi) +{ + t_adc_param param; + pr_debug("mc13892_adc : mc13892_adc_read_ts\n"); + if (suspend_flag == 1) + return -EBUSY; + + if (wait_ts) { + pr_debug("mc13892_adc : error TS busy \n"); + return PMIC_ERROR; + } + mc13892_adc_init_param(¶m); + param.wait_tsi = wait_tsi; + param.read_ts = true; + if (mc13892_adc_convert(¶m) != 0) + return PMIC_ERROR; + /* check if x-y is ok */ + if (param.ts_value.contact_resistance < 1000) { + ts_value->x_position = param.ts_value.x_position; + ts_value->x_position1 = param.ts_value.x_position1; + ts_value->x_position2 = param.ts_value.x_position2; + + ts_value->y_position = param.ts_value.y_position; + ts_value->y_position1 = param.ts_value.y_position1; + ts_value->y_position2 = param.ts_value.y_position2; + + ts_value->contact_resistance = + param.ts_value.contact_resistance + 1; + + } else { + ts_value->x_position = 0; + ts_value->y_position = 0; + ts_value->contact_resistance = 0; + + } + return PMIC_SUCCESS; +} + +int mc13892_adc_request(bool read_ts) +{ + int adc_index = -1; + if (read_ts != 0) { + /*for ts we use bis=0 */ + if (adc_dev[0] == ADC_USED) + return -1; + /*no wait here */ + adc_dev[0] = ADC_USED; + adc_index = 0; + } else { + /*for other adc use bis = 1 */ + if (adc_dev[1] == ADC_USED) { + return -1; + /*no wait here */ + } + adc_dev[1] = ADC_USED; + adc_index = 1; + } + pr_debug("mc13892_adc : request ADC %d\n", adc_index); + return adc_index; +} + +int mc13892_adc_release(int adc_index) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) + return -ERESTARTSYS; + } + + pr_debug("mc13892_adc : release ADC %d\n", adc_index); + if ((adc_dev[adc_index] == ADC_MONITORING) || + (adc_dev[adc_index] == ADC_USED)) { + adc_dev[adc_index] = ADC_FREE; + wake_up(&queue_adc_busy); + return 0; + } + return -1; +} + +#ifdef DEBUG +static t_adc_param adc_param_db; + +static ssize_t adc_info(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int *value = adc_param_db.value; + + pr_debug("adc_info\n"); + + pr_debug("ch0\t\t%d\n", adc_param_db.channel_0); + pr_debug("ch1\t\t%d\n", adc_param_db.channel_1); + pr_debug("d5\t\t%d\n", adc_param_db.chrgraw_devide_5); + pr_debug("conv delay\t%d\n", adc_param_db.conv_delay); + pr_debug("delay\t\t%d\n", adc_param_db.delay); + pr_debug("read mode\t%d\n", adc_param_db.read_mode); + pr_debug("read ts\t\t%d\n", adc_param_db.read_ts); + pr_debug("single ch\t%d\n", adc_param_db.single_channel); + pr_debug("wait ts int\t%d\n", adc_param_db.wait_tsi); + pr_debug("value0-3:\t%d\t%d\t%d\t%d\n", value[0], value[1], + value[2], value[3]); + pr_debug("value4-7:\t%d\t%d\t%d\t%d\n", value[4], value[5], + value[6], value[7]); + + return 0; +} + +enum { + ADC_SET_CH0 = 0, + ADC_SET_CH1, + ADC_SET_DV5, + ADC_SET_CON_DELAY, + ADC_SET_DELAY, + ADC_SET_RM, + ADC_SET_RT, + ADC_SET_S_CH, + ADC_SET_WAIT_TS, + ADC_INIT_P, + ADC_START, + ADC_TS, + ADC_TS_READ, + ADC_TS_CAL, + ADC_CMD_MAX +}; + +static const char *const adc_cmd[ADC_CMD_MAX] = { + [ADC_SET_CH0] = "ch0", + [ADC_SET_CH1] = "ch1", + [ADC_SET_DV5] = "dv5", + [ADC_SET_CON_DELAY] = "cd", + [ADC_SET_DELAY] = "dl", + [ADC_SET_RM] = "rm", + [ADC_SET_RT] = "rt", + [ADC_SET_S_CH] = "sch", + [ADC_SET_WAIT_TS] = "wt", + [ADC_INIT_P] = "init", + [ADC_START] = "start", + [ADC_TS] = "touch", + [ADC_TS_READ] = "touchr", + [ADC_TS_CAL] = "cal" +}; + +static int cmd(unsigned int index, int value) +{ + t_touch_screen ts; + + switch (index) { + case ADC_SET_CH0: + adc_param_db.channel_0 = value; + break; + case ADC_SET_CH1: + adc_param_db.channel_1 = value; + break; + case ADC_SET_DV5: + adc_param_db.chrgraw_devide_5 = value; + break; + case ADC_SET_CON_DELAY: + adc_param_db.conv_delay = value; + break; + case ADC_SET_RM: + adc_param_db.read_mode = value; + break; + case ADC_SET_RT: + adc_param_db.read_ts = value; + break; + case ADC_SET_S_CH: + adc_param_db.single_channel = value; + break; + case ADC_SET_WAIT_TS: + adc_param_db.wait_tsi = value; + break; + case ADC_INIT_P: + mc13892_adc_init_param(&adc_param_db); + break; + case ADC_START: + mc13892_adc_convert(&adc_param_db); + break; + case ADC_TS: + pmic_adc_get_touch_sample(&ts, 1); + pr_debug("x = %d\n", ts.x_position); + pr_debug("y = %d\n", ts.y_position); + pr_debug("p = %d\n", ts.contact_resistance); + break; + case ADC_TS_READ: + pmic_adc_get_touch_sample(&ts, 0); + pr_debug("x = %d\n", ts.x_position); + pr_debug("y = %d\n", ts.y_position); + pr_debug("p = %d\n", ts.contact_resistance); + break; + case ADC_TS_CAL: + break; + default: + pr_debug("error command\n"); + break; + } + return 0; +} + +static ssize_t adc_ctl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int state = 0; + const char *const *s; + char *p, *q; + int error; + int len, value = 0; + + pr_debug("adc_ctl\n"); + + q = NULL; + q = memchr(buf, ' ', count); + + if (q != NULL) { + len = q - buf; + q += 1; + value = simple_strtoul(q, NULL, 10); + } else { + p = memchr(buf, '\n', count); + len = p ? p - buf : count; + } + + for (s = &adc_cmd[state]; state < ADC_CMD_MAX; s++, state++) { + if (*s && !strncmp(buf, *s, len)) + break; + } + if (state < ADC_CMD_MAX && *s) + error = cmd(state, value); + else + error = -EINVAL; + + return count; +} + +#else +static ssize_t adc_info(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static ssize_t adc_ctl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return count; +} + +#endif + +static DEVICE_ATTR(adc, 0644, adc_info, adc_ctl); + +static int pmic_adc_module_probe(struct platform_device *pdev) +{ + int ret = 0; + + pr_debug("PMIC ADC start probe\n"); + ret = device_create_file(&(pdev->dev), &dev_attr_adc); + if (ret) { + pr_debug("Can't create device file!\n"); + return -ENODEV; + } + + init_waitqueue_head(&suspendq); + + ret = pmic_adc_init(); + if (ret != PMIC_SUCCESS) { + pr_debug("Error in pmic_adc_init.\n"); + goto rm_dev_file; + } + + pmic_adc_ready = 1; + pr_debug("PMIC ADC successfully probed\n"); + return 0; + + rm_dev_file: + device_remove_file(&(pdev->dev), &dev_attr_adc); + return ret; +} + +static int pmic_adc_module_remove(struct platform_device *pdev) +{ + pmic_adc_deinit(); + pmic_adc_ready = 0; + pr_debug("PMIC ADC successfully removed\n"); + return 0; +} + +static struct platform_driver pmic_adc_driver_ldm = { + .driver = { + .name = "pmic_adc", + }, + .suspend = pmic_adc_suspend, + .resume = pmic_adc_resume, + .probe = pmic_adc_module_probe, + .remove = pmic_adc_module_remove, +}; + +static int __init pmic_adc_module_init(void) +{ + pr_debug("PMIC ADC driver loading...\n"); + return platform_driver_register(&pmic_adc_driver_ldm); +} + +static void __exit pmic_adc_module_exit(void) +{ + platform_driver_unregister(&pmic_adc_driver_ldm); + pr_debug("PMIC ADC driver successfully unloaded\n"); +} + +module_init(pmic_adc_module_init); +module_exit(pmic_adc_module_exit); + +MODULE_DESCRIPTION("PMIC ADC device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13892/pmic_battery.c b/drivers/mxc/pmic/mc13892/pmic_battery.c new file mode 100644 index 000000000000..8535eb0a34e4 --- /dev/null +++ b/drivers/mxc/pmic/mc13892/pmic_battery.c @@ -0,0 +1,634 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <linux/power_supply.h> + +#include <linux/delay.h> +#include <asm/mach-types.h> +#include <linux/pmic_battery.h> +#include <linux/pmic_adc.h> +#include <linux/pmic_status.h> + +#define BIT_CHG_VOL_LSH 0 +#define BIT_CHG_VOL_WID 3 + +#define BIT_CHG_CURR_LSH 3 +#define BIT_CHG_CURR_WID 4 + +#define BIT_CHG_PLIM_LSH 15 +#define BIT_CHG_PLIM_WID 2 + +#define BIT_CHG_DETS_LSH 6 +#define BIT_CHG_DETS_WID 1 +#define BIT_CHG_CURRS_LSH 11 +#define BIT_CHG_CURRS_WID 1 + +#define TRICKLE_CHG_EN_LSH 7 +#define LOW_POWER_BOOT_ACK_LSH 8 +#define BAT_TH_CHECK_DIS_LSH 9 +#define BATTFET_CTL_EN_LSH 10 +#define BATTFET_CTL_LSH 11 +#define REV_MOD_EN_LSH 13 +#define PLIM_DIS_LSH 17 +#define CHG_LED_EN_LSH 18 +#define RESTART_CHG_STAT_LSH 20 +#define AUTO_CHG_DIS_LSH 21 +#define CYCLING_DIS_LSH 22 +#define VI_PROGRAM_EN_LSH 23 + +#define TRICKLE_CHG_EN_WID 1 +#define LOW_POWER_BOOT_ACK_WID 1 +#define BAT_TH_CHECK_DIS_WID 1 +#define BATTFET_CTL_EN_WID 1 +#define BATTFET_CTL_WID 1 +#define REV_MOD_EN_WID 1 +#define PLIM_DIS_WID 1 +#define CHG_LED_EN_WID 1 +#define RESTART_CHG_STAT_WID 1 +#define AUTO_CHG_DIS_WID 1 +#define CYCLING_DIS_WID 1 +#define VI_PROGRAM_EN_WID 1 + +#define ACC_STARTCC_LSH 0 +#define ACC_STARTCC_WID 1 +#define ACC_RSTCC_LSH 1 +#define ACC_RSTCC_WID 1 +#define ACC_CCFAULT_LSH 7 +#define ACC_CCFAULT_WID 7 +#define ACC_CCOUT_LSH 8 +#define ACC_CCOUT_WID 16 +#define ACC1_ONEC_LSH 0 +#define ACC1_ONEC_WID 15 + +#define ACC_CALIBRATION 0x17 +#define ACC_START_COUNTER 0x07 +#define ACC_STOP_COUNTER 0x2 +#define ACC_CONTROL_BIT_MASK 0x1f +#define ACC_ONEC_VALUE 2621 +#define ACC_COULOMB_PER_LSB 1 +#define ACC_CALIBRATION_DURATION_MSECS 20 + +#define BAT_VOLTAGE_UNIT_UV 4692 +#define BAT_CURRENT_UNIT_UA 5870 +#define CHG_VOLTAGE_UINT_UV 23474 +#define CHG_MIN_CURRENT_UA 3500 + +#define COULOMB_TO_UAH(c) (10000 * c / 36) + +enum chg_setting { + TRICKLE_CHG_EN, + LOW_POWER_BOOT_ACK, + BAT_TH_CHECK_DIS, + BATTFET_CTL_EN, + BATTFET_CTL, + REV_MOD_EN, + PLIM_DIS, + CHG_LED_EN, + RESTART_CHG_STAT, + AUTO_CHG_DIS, + CYCLING_DIS, + VI_PROGRAM_EN +}; + +static int pmic_set_chg_current(unsigned short curr) +{ + unsigned int mask; + unsigned int value; + + value = BITFVAL(BIT_CHG_CURR, curr); + mask = BITFMASK(BIT_CHG_CURR); + CHECK_ERROR(pmic_write_reg(REG_CHARGE, value, mask)); + + return 0; +} + +static int pmic_set_chg_misc(enum chg_setting type, unsigned short flag) +{ + + unsigned int reg_value = 0; + unsigned int mask = 0; + + switch (type) { + case TRICKLE_CHG_EN: + reg_value = BITFVAL(TRICKLE_CHG_EN, flag); + mask = BITFMASK(TRICKLE_CHG_EN); + break; + case LOW_POWER_BOOT_ACK: + reg_value = BITFVAL(LOW_POWER_BOOT_ACK, flag); + mask = BITFMASK(LOW_POWER_BOOT_ACK); + break; + case BAT_TH_CHECK_DIS: + reg_value = BITFVAL(BAT_TH_CHECK_DIS, flag); + mask = BITFMASK(BAT_TH_CHECK_DIS); + break; + case BATTFET_CTL_EN: + reg_value = BITFVAL(BATTFET_CTL_EN, flag); + mask = BITFMASK(BATTFET_CTL_EN); + break; + case BATTFET_CTL: + reg_value = BITFVAL(BATTFET_CTL, flag); + mask = BITFMASK(BATTFET_CTL); + break; + case REV_MOD_EN: + reg_value = BITFVAL(REV_MOD_EN, flag); + mask = BITFMASK(REV_MOD_EN); + break; + case PLIM_DIS: + reg_value = BITFVAL(PLIM_DIS, flag); + mask = BITFMASK(PLIM_DIS); + break; + case CHG_LED_EN: + reg_value = BITFVAL(CHG_LED_EN, flag); + mask = BITFMASK(CHG_LED_EN); + break; + case RESTART_CHG_STAT: + reg_value = BITFVAL(RESTART_CHG_STAT, flag); + mask = BITFMASK(RESTART_CHG_STAT); + break; + case AUTO_CHG_DIS: + reg_value = BITFVAL(AUTO_CHG_DIS, flag); + mask = BITFMASK(AUTO_CHG_DIS); + break; + case CYCLING_DIS: + reg_value = BITFVAL(CYCLING_DIS, flag); + mask = BITFMASK(CYCLING_DIS); + break; + case VI_PROGRAM_EN: + reg_value = BITFVAL(VI_PROGRAM_EN, flag); + mask = BITFMASK(VI_PROGRAM_EN); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_CHARGE, reg_value, mask)); + + return 0; +} + +static int pmic_get_batt_voltage(unsigned short *voltage) +{ + t_channel channel; + unsigned short result[8]; + + channel = BATTERY_VOLTAGE; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *voltage = result[0]; + + return 0; +} + +static int pmic_get_batt_current(unsigned short *curr) +{ + t_channel channel; + unsigned short result[8]; + + channel = BATTERY_CURRENT; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *curr = result[0]; + + return 0; +} + +static int coulomb_counter_calibration; +static unsigned int coulomb_counter_start_time_msecs; + +static int pmic_start_coulomb_counter(void) +{ + /* set scaler */ + CHECK_ERROR(pmic_write_reg(REG_ACC1, + ACC_COULOMB_PER_LSB * ACC_ONEC_VALUE, BITFMASK(ACC1_ONEC))); + + CHECK_ERROR(pmic_write_reg( + REG_ACC0, ACC_START_COUNTER, ACC_CONTROL_BIT_MASK)); + coulomb_counter_start_time_msecs = jiffies_to_msecs(jiffies); + pr_debug("coulomb counter start time %u\n", + coulomb_counter_start_time_msecs); + return 0; +} + +static int pmic_stop_coulomb_counter(void) +{ + CHECK_ERROR(pmic_write_reg( + REG_ACC0, ACC_STOP_COUNTER, ACC_CONTROL_BIT_MASK)); + return 0; +} + +static int pmic_calibrate_coulomb_counter(void) +{ + int ret; + unsigned int value; + + /* set scaler */ + CHECK_ERROR(pmic_write_reg(REG_ACC1, + 0x1, BITFMASK(ACC1_ONEC))); + + CHECK_ERROR(pmic_write_reg( + REG_ACC0, ACC_CALIBRATION, ACC_CONTROL_BIT_MASK)); + msleep(ACC_CALIBRATION_DURATION_MSECS); + + ret = pmic_read_reg(REG_ACC0, &value, BITFMASK(ACC_CCOUT)); + if (ret != 0) + return -1; + value = BITFEXT(value, ACC_CCOUT); + pr_debug("calibrate value = %x\n", value); + coulomb_counter_calibration = (int)((s16)((u16) value)); + pr_debug("coulomb_counter_calibration = %d\n", + coulomb_counter_calibration); + + return 0; + +} + +static int pmic_get_charger_coulomb(int *coulomb) +{ + int ret; + unsigned int value; + int calibration; + unsigned int time_diff_msec; + + ret = pmic_read_reg(REG_ACC0, &value, BITFMASK(ACC_CCOUT)); + if (ret != 0) + return -1; + value = BITFEXT(value, ACC_CCOUT); + pr_debug("counter value = %x\n", value); + *coulomb = ((s16)((u16)value)) * ACC_COULOMB_PER_LSB; + + if (abs(*coulomb) >= ACC_COULOMB_PER_LSB) { + /* calibrate */ + time_diff_msec = jiffies_to_msecs(jiffies); + time_diff_msec = + (time_diff_msec > coulomb_counter_start_time_msecs) ? + (time_diff_msec - coulomb_counter_start_time_msecs) : + (0xffffffff - coulomb_counter_start_time_msecs + + time_diff_msec); + calibration = coulomb_counter_calibration * (int)time_diff_msec + / (ACC_ONEC_VALUE * ACC_CALIBRATION_DURATION_MSECS); + *coulomb -= calibration; + } + + return 0; +} + +static int pmic_restart_charging(void) +{ + pmic_set_chg_misc(BAT_TH_CHECK_DIS, 1); + pmic_set_chg_misc(AUTO_CHG_DIS, 0); + pmic_set_chg_misc(VI_PROGRAM_EN, 1); + pmic_set_chg_current(0x8); + pmic_set_chg_misc(RESTART_CHG_STAT, 1); + return 0; +} + +struct mc13892_dev_info { + struct device *dev; + + unsigned short voltage_raw; + int voltage_uV; + unsigned short current_raw; + int current_uA; + int battery_status; + int full_counter; + int charger_online; + int charger_voltage_uV; + int accum_current_uAh; + + struct power_supply bat; + struct power_supply charger; + + struct workqueue_struct *monitor_wqueue; + struct delayed_work monitor_work; +}; + +#define mc13892_SENSER 25 +#define to_mc13892_dev_info(x) container_of((x), struct mc13892_dev_info, \ + bat); + +static enum power_supply_property mc13892_battery_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_STATUS, +}; + +static enum power_supply_property mc13892_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int mc13892_charger_update_status(struct mc13892_dev_info *di) +{ + int ret; + unsigned int value; + int online; + + ret = pmic_read_reg(REG_INT_SENSE0, &value, BITFMASK(BIT_CHG_DETS)); + + if (ret == 0) { + online = BITFEXT(value, BIT_CHG_DETS); + if (online != di->charger_online) { + di->charger_online = online; + dev_info(di->charger.dev, "charger status: %s\n", + online ? "online" : "offline"); + power_supply_changed(&di->charger); + + cancel_delayed_work(&di->monitor_work); + queue_delayed_work(di->monitor_wqueue, + &di->monitor_work, HZ / 10); + if (online) { + pmic_start_coulomb_counter(); + pmic_restart_charging(); + } else + pmic_stop_coulomb_counter(); + } + } + + return ret; +} + +static int mc13892_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mc13892_dev_info *di = + container_of((psy), struct mc13892_dev_info, charger); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->charger_online; + return 0; + default: + break; + } + return -EINVAL; +} + +static int mc13892_battery_read_status(struct mc13892_dev_info *di) +{ + int retval; + int coulomb; + retval = pmic_get_batt_voltage(&(di->voltage_raw)); + if (retval == 0) + di->voltage_uV = di->voltage_raw * BAT_VOLTAGE_UNIT_UV; + + retval = pmic_get_batt_current(&(di->current_raw)); + if (retval == 0) { + if (di->current_raw & 0x200) + di->current_uA = + (0x1FF - (di->current_raw & 0x1FF)) * + BAT_CURRENT_UNIT_UA * (-1); + else + di->current_uA = + (di->current_raw & 0x1FF) * BAT_CURRENT_UNIT_UA; + } + retval = pmic_get_charger_coulomb(&coulomb); + if (retval == 0) + di->accum_current_uAh = COULOMB_TO_UAH(coulomb); + + return retval; +} + +static void mc13892_battery_update_status(struct mc13892_dev_info *di) +{ + unsigned int value; + int retval; + int old_battery_status = di->battery_status; + + if (di->battery_status == POWER_SUPPLY_STATUS_UNKNOWN) + di->full_counter = 0; + + if (di->charger_online) { + retval = pmic_read_reg(REG_INT_SENSE0, + &value, BITFMASK(BIT_CHG_CURRS)); + + if (retval == 0) { + value = BITFEXT(value, BIT_CHG_CURRS); + if (value) + di->battery_status = + POWER_SUPPLY_STATUS_CHARGING; + else + di->battery_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + } + + if (di->battery_status == POWER_SUPPLY_STATUS_NOT_CHARGING) + di->full_counter++; + else + di->full_counter = 0; + } else { + di->battery_status = POWER_SUPPLY_STATUS_DISCHARGING; + di->full_counter = 0; + } + + dev_dbg(di->bat.dev, "bat status: %d\n", + di->battery_status); + + if (di->battery_status != old_battery_status) + power_supply_changed(&di->bat); +} + +static void mc13892_battery_work(struct work_struct *work) +{ + struct mc13892_dev_info *di = container_of(work, + struct mc13892_dev_info, + monitor_work.work); + const int interval = HZ * 60; + + dev_dbg(di->dev, "%s\n", __func__); + + mc13892_battery_update_status(di); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval); +} + +static void charger_online_event_callback(void *para) +{ + struct mc13892_dev_info *di = (struct mc13892_dev_info *) para; + pr_info("\n\n DETECTED charger plug/unplug event\n"); + mc13892_charger_update_status(di); +} + + +static int mc13892_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mc13892_dev_info *di = to_mc13892_dev_info(psy); + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (di->battery_status == POWER_SUPPLY_STATUS_UNKNOWN) { + mc13892_charger_update_status(di); + mc13892_battery_update_status(di); + } + val->intval = di->battery_status; + return 0; + default: + break; + } + + mc13892_battery_read_status(di); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->voltage_uV; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->current_uA; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = di->accum_current_uAh; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = 3800000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = 3300000; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pmic_battery_remove(struct platform_device *pdev) +{ + pmic_event_callback_t bat_event_callback; + struct mc13892_dev_info *di = platform_get_drvdata(pdev); + + bat_event_callback.func = charger_online_event_callback; + bat_event_callback.param = (void *) di; + pmic_event_unsubscribe(EVENT_CHGDETI, bat_event_callback); + + cancel_rearming_delayed_workqueue(di->monitor_wqueue, + &di->monitor_work); + destroy_workqueue(di->monitor_wqueue); + power_supply_unregister(&di->bat); + power_supply_unregister(&di->charger); + + kfree(di); + + return 0; +} + +static int pmic_battery_probe(struct platform_device *pdev) +{ + int retval = 0; + struct mc13892_dev_info *di; + pmic_event_callback_t bat_event_callback; + pmic_version_t pmic_version; + + /* Only apply battery driver for MC13892 V2.0 due to ENGR108085 */ + pmic_version = pmic_get_version(); + if (pmic_version.revision < 20) { + pr_debug("Battery driver is only applied for MC13892 V2.0\n"); + return -1; + } + if (machine_is_mx51_babbage()) { + pr_debug("mc13892 charger is not used for this platform\n"); + return -1; + } + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + retval = -ENOMEM; + goto di_alloc_failed; + } + + platform_set_drvdata(pdev, di); + + di->dev = &pdev->dev; + di->bat.name = "mc13892_bat"; + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = mc13892_battery_props; + di->bat.num_properties = ARRAY_SIZE(mc13892_battery_props); + di->bat.get_property = mc13892_battery_get_property; + di->bat.use_for_apm = 1; + + di->battery_status = POWER_SUPPLY_STATUS_UNKNOWN; + + retval = power_supply_register(&pdev->dev, &di->bat); + if (retval) { + dev_err(di->dev, "failed to register battery\n"); + goto batt_failed; + } + di->charger.name = "mc13892_charger"; + di->charger.type = POWER_SUPPLY_TYPE_MAINS; + di->charger.properties = mc13892_charger_props; + di->charger.num_properties = ARRAY_SIZE(mc13892_charger_props); + di->charger.get_property = mc13892_charger_get_property; + retval = power_supply_register(&pdev->dev, &di->charger); + if (retval) { + dev_err(di->dev, "failed to register charger\n"); + goto charger_failed; + } + INIT_DELAYED_WORK(&di->monitor_work, mc13892_battery_work); + di->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); + if (!di->monitor_wqueue) { + retval = -ESRCH; + goto workqueue_failed; + } + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ * 10); + + bat_event_callback.func = charger_online_event_callback; + bat_event_callback.param = (void *) di; + pmic_event_subscribe(EVENT_CHGDETI, bat_event_callback); + + pmic_stop_coulomb_counter(); + pmic_calibrate_coulomb_counter(); + goto success; + +workqueue_failed: + power_supply_unregister(&di->charger); +charger_failed: + power_supply_unregister(&di->bat); +batt_failed: + kfree(di); +di_alloc_failed: +success: + dev_dbg(di->dev, "%s battery probed!\n", __func__); + return retval; + + + return 0; +} + +static struct platform_driver pmic_battery_driver_ldm = { + .driver = { + .name = "pmic_battery", + .bus = &platform_bus_type, + }, + .probe = pmic_battery_probe, + .remove = pmic_battery_remove, +}; + +static int __init pmic_battery_init(void) +{ + pr_debug("PMIC Battery driver loading...\n"); + return platform_driver_register(&pmic_battery_driver_ldm); +} + +static void __exit pmic_battery_exit(void) +{ + platform_driver_unregister(&pmic_battery_driver_ldm); + pr_debug("PMIC Battery driver successfully unloaded\n"); +} + +module_init(pmic_battery_init); +module_exit(pmic_battery_exit); + +MODULE_DESCRIPTION("pmic_battery driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13892/pmic_light.c b/drivers/mxc/pmic/mc13892/pmic_light.c new file mode 100644 index 000000000000..ae02430a1981 --- /dev/null +++ b/drivers/mxc/pmic/mc13892/pmic_light.c @@ -0,0 +1,685 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13892/pmic_light.c + * @brief This is the main file of PMIC(mc13783) Light and Backlight driver. + * + * @ingroup PMIC_LIGHT + */ + +/* + * Includes + */ +#define DEBUG +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/pmic_light.h> +#include <linux/pmic_status.h> + +#define BIT_CL_MAIN_LSH 9 +#define BIT_CL_AUX_LSH 21 +#define BIT_CL_KEY_LSH 9 +#define BIT_CL_RED_LSH 9 +#define BIT_CL_GREEN_LSH 21 +#define BIT_CL_BLUE_LSH 9 + +#define BIT_CL_MAIN_WID 3 +#define BIT_CL_AUX_WID 3 +#define BIT_CL_KEY_WID 3 +#define BIT_CL_RED_WID 3 +#define BIT_CL_GREEN_WID 3 +#define BIT_CL_BLUE_WID 3 + +#define BIT_DC_MAIN_LSH 3 +#define BIT_DC_AUX_LSH 15 +#define BIT_DC_KEY_LSH 3 +#define BIT_DC_RED_LSH 3 +#define BIT_DC_GREEN_LSH 15 +#define BIT_DC_BLUE_LSH 3 + +#define BIT_DC_MAIN_WID 6 +#define BIT_DC_AUX_WID 6 +#define BIT_DC_KEY_WID 6 +#define BIT_DC_RED_WID 6 +#define BIT_DC_GREEN_WID 6 +#define BIT_DC_BLUE_WID 6 + +#define BIT_RP_MAIN_LSH 2 +#define BIT_RP_AUX_LSH 14 +#define BIT_RP_KEY_LSH 2 +#define BIT_RP_RED_LSH 2 +#define BIT_RP_GREEN_LSH 14 +#define BIT_RP_BLUE_LSH 2 + +#define BIT_RP_MAIN_WID 1 +#define BIT_RP_AUX_WID 1 +#define BIT_RP_KEY_WID 1 +#define BIT_RP_RED_WID 1 +#define BIT_RP_GREEN_WID 1 +#define BIT_RP_BLUE_WID 1 + +#define BIT_HC_MAIN_LSH 1 +#define BIT_HC_AUX_LSH 13 +#define BIT_HC_KEY_LSH 1 + +#define BIT_HC_MAIN_WID 1 +#define BIT_HC_AUX_WID 1 +#define BIT_HC_KEY_WID 1 + +#define BIT_BP_RED_LSH 0 +#define BIT_BP_GREEN_LSH 12 +#define BIT_BP_BLUE_LSH 0 + +#define BIT_BP_RED_WID 2 +#define BIT_BP_GREEN_WID 2 +#define BIT_BP_BLUE_WID 2 + +int pmic_light_init_reg(void) +{ + CHECK_ERROR(pmic_write_reg(REG_LED_CTL0, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_LED_CTL1, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_LED_CTL2, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_LED_CTL3, 0, PMIC_ALL_BITS)); + + return 0; +} + +static int pmic_light_suspend(struct platform_device *dev, pm_message_t state) +{ + return 0; +}; + +static int pmic_light_resume(struct platform_device *pdev) +{ + return 0; +}; + +PMIC_STATUS mc13892_bklit_set_hi_current(enum lit_channel channel, int mode) +{ + unsigned int mask; + unsigned int value; + int reg; + + switch (channel) { + case LIT_MAIN: + value = BITFVAL(BIT_HC_MAIN, mode); + mask = BITFMASK(BIT_HC_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + value = BITFVAL(BIT_HC_AUX, mode); + mask = BITFMASK(BIT_HC_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + value = BITFVAL(BIT_HC_KEY, mode); + mask = BITFMASK(BIT_HC_KEY); + reg = REG_LED_CTL1; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_hi_current(enum lit_channel channel, int *mode) +{ + unsigned int mask; + int reg; + + switch (channel) { + case LIT_MAIN: + mask = BITFMASK(BIT_HC_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + mask = BITFMASK(BIT_HC_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + mask = BITFMASK(BIT_HC_KEY); + reg = REG_LED_CTL1; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, mode, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_set_current(enum lit_channel channel, + unsigned char level) +{ + unsigned int mask; + unsigned int value; + int reg; + + if (level > LIT_CURR_HI_42) + return PMIC_PARAMETER_ERROR; + else if (level >= LIT_CURR_HI_0) { + CHECK_ERROR(mc13892_bklit_set_hi_current(channel, 1)); + level -= LIT_CURR_HI_0; + } + + switch (channel) { + case LIT_MAIN: + value = BITFVAL(BIT_CL_MAIN, level); + mask = BITFMASK(BIT_CL_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + value = BITFVAL(BIT_CL_AUX, level); + mask = BITFMASK(BIT_CL_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + value = BITFVAL(BIT_CL_KEY, level); + mask = BITFMASK(BIT_CL_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + value = BITFVAL(BIT_CL_RED, level); + mask = BITFMASK(BIT_CL_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + value = BITFVAL(BIT_CL_GREEN, level); + mask = BITFMASK(BIT_CL_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + value = BITFVAL(BIT_CL_BLUE, level); + mask = BITFMASK(BIT_CL_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_current(enum lit_channel channel, + unsigned char *level) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + int reg, mode; + + CHECK_ERROR(mc13892_bklit_get_hi_current(channel, &mode)); + + switch (channel) { + case LIT_MAIN: + mask = BITFMASK(BIT_CL_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + mask = BITFMASK(BIT_CL_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + mask = BITFMASK(BIT_CL_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + mask = BITFMASK(BIT_CL_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + mask = BITFMASK(BIT_CL_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + mask = BITFMASK(BIT_CL_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_value, mask)); + + switch (channel) { + case LIT_MAIN: + *level = BITFEXT(reg_value, BIT_CL_MAIN); + break; + case LIT_AUX: + *level = BITFEXT(reg_value, BIT_CL_AUX); + break; + case LIT_KEY: + *level = BITFEXT(reg_value, BIT_CL_KEY); + break; + case LIT_RED: + *level = BITFEXT(reg_value, BIT_CL_RED); + break; + case LIT_GREEN: + *level = BITFEXT(reg_value, BIT_CL_GREEN); + break; + case LIT_BLUE: + *level = BITFEXT(reg_value, BIT_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (mode == 1) + *level += LIT_CURR_HI_0; + + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_set_dutycycle(enum lit_channel channel, + unsigned char dc) +{ + unsigned int mask; + unsigned int value; + int reg; + + switch (channel) { + case LIT_MAIN: + value = BITFVAL(BIT_DC_MAIN, dc); + mask = BITFMASK(BIT_DC_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + value = BITFVAL(BIT_DC_AUX, dc); + mask = BITFMASK(BIT_DC_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + value = BITFVAL(BIT_DC_KEY, dc); + mask = BITFMASK(BIT_DC_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + value = BITFVAL(BIT_DC_RED, dc); + mask = BITFMASK(BIT_DC_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + value = BITFVAL(BIT_DC_GREEN, dc); + mask = BITFMASK(BIT_DC_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + value = BITFVAL(BIT_DC_BLUE, dc); + mask = BITFMASK(BIT_DC_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_dutycycle(enum lit_channel channel, + unsigned char *dc) +{ + unsigned int mask; + int reg; + unsigned int reg_value = 0; + + switch (channel) { + case LIT_MAIN: + mask = BITFMASK(BIT_DC_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + mask = BITFMASK(BIT_DC_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + mask = BITFMASK(BIT_DC_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + mask = BITFMASK(BIT_DC_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + mask = BITFMASK(BIT_DC_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + mask = BITFMASK(BIT_DC_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_set_ramp(enum lit_channel channel, int flag) +{ + unsigned int mask; + unsigned int value; + int reg; + + switch (channel) { + case LIT_MAIN: + value = BITFVAL(BIT_RP_MAIN, flag); + mask = BITFMASK(BIT_RP_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + value = BITFVAL(BIT_RP_AUX, flag); + mask = BITFMASK(BIT_RP_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + value = BITFVAL(BIT_RP_KEY, flag); + mask = BITFMASK(BIT_RP_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + value = BITFVAL(BIT_RP_RED, flag); + mask = BITFMASK(BIT_RP_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + value = BITFVAL(BIT_RP_GREEN, flag); + mask = BITFMASK(BIT_RP_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + value = BITFVAL(BIT_RP_BLUE, flag); + mask = BITFMASK(BIT_RP_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_ramp(enum lit_channel channel, int *flag) +{ + unsigned int mask; + int reg; + + switch (channel) { + case LIT_MAIN: + mask = BITFMASK(BIT_RP_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + mask = BITFMASK(BIT_RP_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + mask = BITFMASK(BIT_RP_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + mask = BITFMASK(BIT_RP_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + mask = BITFMASK(BIT_RP_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + mask = BITFMASK(BIT_RP_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, flag, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_set_blink_p(enum lit_channel channel, int period) +{ + unsigned int mask; + unsigned int value; + int reg; + + switch (channel) { + case LIT_RED: + value = BITFVAL(BIT_BP_RED, period); + mask = BITFMASK(BIT_BP_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + value = BITFVAL(BIT_BP_GREEN, period); + mask = BITFMASK(BIT_BP_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + value = BITFVAL(BIT_BP_BLUE, period); + mask = BITFMASK(BIT_BP_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_blink_p(enum lit_channel channel, int *period) +{ + unsigned int mask; + int reg; + + switch (channel) { + case LIT_RED: + mask = BITFMASK(BIT_BP_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + mask = BITFMASK(BIT_BP_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + mask = BITFMASK(BIT_BP_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, period, mask)); + return PMIC_SUCCESS; +} + +EXPORT_SYMBOL(mc13892_bklit_set_current); +EXPORT_SYMBOL(mc13892_bklit_get_current); +EXPORT_SYMBOL(mc13892_bklit_set_dutycycle); +EXPORT_SYMBOL(mc13892_bklit_get_dutycycle); +EXPORT_SYMBOL(mc13892_bklit_set_ramp); +EXPORT_SYMBOL(mc13892_bklit_get_ramp); +EXPORT_SYMBOL(mc13892_bklit_set_blink_p); +EXPORT_SYMBOL(mc13892_bklit_get_blink_p); + +static int pmic_light_remove(struct platform_device *pdev) +{ + return 0; +} + +#ifdef DEBUG +static ssize_t lit_info(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return 0; +} + +enum { + SET_CURR = 0, + SET_DC, + SET_RAMP, + SET_BP, + SET_CH, + LIT_CMD_MAX +}; + +static const char *const lit_cmd[LIT_CMD_MAX] = { + [SET_CURR] = "cur", + [SET_DC] = "dc", + [SET_RAMP] = "ra", + [SET_BP] = "bp", + [SET_CH] = "ch" +}; + +static int cmd(unsigned int index, int value) +{ + static int ch = LIT_MAIN; + int ret = 0; + + switch (index) { + case SET_CH: + ch = value; + break; + case SET_CURR: + pr_debug("set %d cur %d\n", ch, value); + ret = mc13892_bklit_set_current(ch, value); + break; + case SET_DC: + pr_debug("set %d dc %d\n", ch, value); + ret = mc13892_bklit_set_dutycycle(ch, value); + break; + case SET_RAMP: + pr_debug("set %d ramp %d\n", ch, value); + ret = mc13892_bklit_set_ramp(ch, value); + break; + case SET_BP: + pr_debug("set %d bp %d\n", ch, value); + ret = mc13892_bklit_set_blink_p(ch, value); + break; + default: + pr_debug("error command\n"); + break; + } + + if (ret == PMIC_SUCCESS) + pr_debug("command exec successfully!\n"); + + return 0; +} + +static ssize_t lit_ctl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int state = 0; + const char *const *s; + char *p, *q; + int error; + int len, value = 0; + + pr_debug("lit_ctl\n"); + + q = NULL; + q = memchr(buf, ' ', count); + + if (q != NULL) { + len = q - buf; + q += 1; + value = simple_strtoul(q, NULL, 10); + } else { + p = memchr(buf, '\n', count); + len = p ? p - buf : count; + } + + for (s = &lit_cmd[state]; state < LIT_CMD_MAX; s++, state++) { + if (*s && !strncmp(buf, *s, len)) + break; + } + if (state < LIT_CMD_MAX && *s) + error = cmd(state, value); + else + error = -EINVAL; + + return count; +} + +#else +static ssize_t lit_info(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static ssize_t lit_ctl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return count; +} + +#endif + +static DEVICE_ATTR(lit, 0644, lit_info, lit_ctl); + +static int pmic_light_probe(struct platform_device *pdev) +{ + int ret = 0; + + pr_debug("PMIC ADC start probe\n"); + ret = device_create_file(&(pdev->dev), &dev_attr_lit); + if (ret) { + pr_debug("Can't create device file!\n"); + return -ENODEV; + } + + pmic_light_init_reg(); + + pr_debug("PMIC Light successfully loaded\n"); + return 0; +} + +static struct platform_driver pmic_light_driver_ldm = { + .driver = { + .name = "pmic_light", + }, + .suspend = pmic_light_suspend, + .resume = pmic_light_resume, + .probe = pmic_light_probe, + .remove = pmic_light_remove, +}; + +/* + * Initialization and Exit + */ + +static int __init pmic_light_init(void) +{ + pr_debug("PMIC Light driver loading...\n"); + return platform_driver_register(&pmic_light_driver_ldm); +} +static void __exit pmic_light_exit(void) +{ + platform_driver_unregister(&pmic_light_driver_ldm); + pr_debug("PMIC Light driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_light_init); +module_exit(pmic_light_exit); + +MODULE_DESCRIPTION("PMIC_LIGHT"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/security/Kconfig b/drivers/mxc/security/Kconfig new file mode 100644 index 000000000000..83d856d85dc0 --- /dev/null +++ b/drivers/mxc/security/Kconfig @@ -0,0 +1,64 @@ +menu "MXC Security Drivers" + +config MXC_SECURITY_SCC + tristate "MXC SCC Driver" + default n + ---help--- + This module contains the core API's for accessing the SCC module. + If you are unsure about this, say N here. + +config MXC_SECURITY_SCC2 + tristate "MXC SCC2 Driver" + depends on ARCH_MX37 || ARCH_MX51 + default n + ---help--- + This module contains the core API's for accessing the SCC2 module. + If you are unsure about this, say N here. + +config SCC_DEBUG + bool "MXC SCC Module debugging" + depends on MXC_SECURITY_SCC || MXC_SECURITY_SCC2 + ---help--- + This is an option for use by developers; most people should + say N here. This enables SCC module debugging. + +config MXC_SECURITY_RNG + tristate "MXC RNG Driver" + depends on ARCH_MXC + depends on !ARCH_MXC91321 + depends on !ARCH_MX27 + default n + select MXC_SECURITY_CORE + ---help--- + This module contains the core API's for accessing the RNG module. + If you are unsure about this, say N here. + +config MXC_RNG_TEST_DRIVER + bool "MXC RNG debug register" + depends on MXC_SECURITY_RNG + default n + ---help--- + This option enables the RNG kcore driver to provide peek-poke facility + into the RNG device registers. Enable this, only for development and + testing purposes. +config MXC_RNG_DEBUG + bool "MXC RNG Module Dubugging" + depends on MXC_SECURITY_RNG + default n + ---help--- + This is an option for use by developers; most people should + say N here. This enables RNG module debugging. + +config MXC_DRYICE + tristate "MXC DryIce Driver" + depends on ARCH_MX25 + default n + ---help--- + This module contains the core API's for accessing the DryIce module. + If you are unsure about this, say N here. + +if (ARCH_MX37 || ARCH_MX51 || ARCH_MX27) +source "drivers/mxc/security/sahara2/Kconfig" +endif + +endmenu diff --git a/drivers/mxc/security/Makefile b/drivers/mxc/security/Makefile new file mode 100644 index 000000000000..f643a09a423d --- /dev/null +++ b/drivers/mxc/security/Makefile @@ -0,0 +1,11 @@ +# Makefile for the Linux MXC Security API +ifeq ($( SCC_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif +EXTRA_CFLAGS += -DMXC -DLINUX_KERNEL + +obj-$(CONFIG_MXC_SECURITY_SCC2) += scc2_driver.o +obj-$(CONFIG_MXC_SECURITY_SCC) += mxc_scc.o +obj-$(CONFIG_MXC_SECURITY_RNG) += rng/ +obj-$(CONFIG_MXC_SAHARA) += sahara2/ +obj-$(CONFIG_MXC_DRYICE) += dryice.o diff --git a/drivers/mxc/security/dryice-regs.h b/drivers/mxc/security/dryice-regs.h new file mode 100644 index 000000000000..b8d3858bdb7d --- /dev/null +++ b/drivers/mxc/security/dryice-regs.h @@ -0,0 +1,207 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +#ifndef __DRYICE_REGS_H__ +#define __DRYICE_REGS_H__ + +/*********************************************************************** + * DryIce Register Definitions + ***********************************************************************/ + +/* DryIce Time Counter MSB Reg */ +#define DTCMR 0x00 + +/* DryIce Time Counter LSB Reg */ +#define DTCLR 0x04 + +/* DryIce Clock Alarm MSB Reg */ +#define DCAMR 0x08 + +/* DryIce Clock Alarm LSB Reg */ +#define DCALR 0x0c + +/* DryIce Control Reg */ +#define DCR 0x10 +#define DCR_TDCHL (1 << 30) /* Tamper Detect Config Hard Lock */ +#define DCR_TDCSL (1 << 29) /* Tamper Detect COnfig Soft Lock */ +#define DCR_KSHL (1 << 28) /* Key Select Hard Lock */ +#define DCR_KSSL (1 << 27) /* Key Select Soft Lock */ +#define DCR_RKHL (1 << 26) /* Random Key Hard Lock */ +#define DCR_RKSL (1 << 25) /* Random Key Soft Lock */ +#define DCR_PKRHL (1 << 24) /* Programmed Key Read Hard Lock */ +#define DCR_PKRSL (1 << 23) /* Programmed Key Read Soft Lock */ +#define DCR_PKWHL (1 << 22) /* Programmed Key Write Hard Lock */ +#define DCR_PKWSL (1 << 21) /* Programmed Key Write Soft Lock */ +#define DCR_MCHL (1 << 20) /* Monotonic Counter Hard Lock */ +#define DCR_MCSL (1 << 19) /* Monotonic Counter Soft Lock */ +#define DCR_TCHL (1 << 18) /* Time Counter Hard Lock */ +#define DCR_TCSL (1 << 17) /* Time Counter Soft Lock */ +#define DCR_FSHL (1 << 16) /* Failure State Hard Lock */ +#define DCR_NSA (1 << 15) /* Non-Secure Access */ +#define DCR_OSCB (1 << 14) /* Oscillator Bypass */ +#define DCR_APE (1 << 4) /* Alarm Pin Enable */ +#define DCR_TCE (1 << 3) /* Time Counter Enable */ +#define DCR_MCE (1 << 2) /* Monotonic Counter Enable */ +#define DCR_SWR (1 << 0) /* Software Reset (w/o) */ + +/* DryIce Status Reg */ +#define DSR 0x14 +#define DSR_WTD (1 << 23) /* Wire-mesh Tampering Detected */ +#define DSR_ETBD (1 << 22) /* External Tampering B Detected */ +#define DSR_ETAD (1 << 21) /* External Tampering A Detected */ +#define DSR_EBD (1 << 20) /* External Boot Detected */ +#define DSR_SAD (1 << 19) /* Security Alarm Detected */ +#define DSR_TTD (1 << 18) /* Temperature Tampering Detected */ +#define DSR_CTD (1 << 17) /* Clock Tampering Detected */ +#define DSR_VTD (1 << 16) /* Voltage Tampering Detected */ +#define DSR_KBF (1 << 11) /* Key Busy Flag */ +#define DSR_WBF (1 << 10) /* Write Busy Flag */ +#define DSR_WNF (1 << 9) /* Write Next Flag */ +#define DSR_WCF (1 << 8) /* Write Complete Flag */ +#define DSR_WEF (1 << 7) /* Write Error Flag */ +#define DSR_RKE (1 << 6) /* Random Key Error */ +#define DSR_RKV (1 << 5) /* Random Key Valid */ +#define DSR_CAF (1 << 4) /* Clock Alarm Flag */ +#define DSR_MCO (1 << 3) /* Monotonic Counter Overflow */ +#define DSR_TCO (1 << 2) /* Time Counter Overflow */ +#define DSR_NVF (1 << 1) /* Non-Valid Flag */ +#define DSR_SVF (1 << 0) /* Security Violation Flag */ + +#define DSR_TAMPER_BITS (DSR_WTD | DSR_ETBD | DSR_ETAD | DSR_EBD | DSR_SAD | \ + DSR_TTD | DSR_CTD | DSR_VTD | DSR_MCO | DSR_TCO) + +/* ensure that external tamper defs match register bits */ +#if DSR_WTD != DI_TAMPER_EVENT_WTD +#error "Mismatch between DSR_WTD and DI_TAMPER_EVENT_WTD" +#endif +#if DSR_ETBD != DI_TAMPER_EVENT_ETBD +#error "Mismatch between DSR_ETBD and DI_TAMPER_EVENT_ETBD" +#endif +#if DSR_ETAD != DI_TAMPER_EVENT_ETAD +#error "Mismatch between DSR_ETAD and DI_TAMPER_EVENT_ETAD" +#endif +#if DSR_EBD != DI_TAMPER_EVENT_EBD +#error "Mismatch between DSR_EBD and DI_TAMPER_EVENT_EBD" +#endif +#if DSR_SAD != DI_TAMPER_EVENT_SAD +#error "Mismatch between DSR_SAD and DI_TAMPER_EVENT_SAD" +#endif +#if DSR_TTD != DI_TAMPER_EVENT_TTD +#error "Mismatch between DSR_TTD and DI_TAMPER_EVENT_TTD" +#endif +#if DSR_CTD != DI_TAMPER_EVENT_CTD +#error "Mismatch between DSR_CTD and DI_TAMPER_EVENT_CTD" +#endif +#if DSR_VTD != DI_TAMPER_EVENT_VTD +#error "Mismatch between DSR_VTD and DI_TAMPER_EVENT_VTD" +#endif +#if DSR_MCO != DI_TAMPER_EVENT_MCO +#error "Mismatch between DSR_MCO and DI_TAMPER_EVENT_MCO" +#endif +#if DSR_TCO != DI_TAMPER_EVENT_TCO +#error "Mismatch between DSR_TCO and DI_TAMPER_EVENT_TCO" +#endif + +/* DryIce Interrupt Enable Reg */ +#define DIER 0x18 +#define DIER_WNIE (1 << 9) /* Write Next Interrupt Enable */ +#define DIER_WCIE (1 << 8) /* Write Complete Interrupt Enable */ +#define DIER_WEIE (1 << 7) /* Write Error Interrupt Enable */ +#define DIER_RKIE (1 << 5) /* Random Key Interrupt Enable */ +#define DIER_CAIE (1 << 4) /* Clock Alarm Interrupt Enable */ +#define DIER_MOIE (1 << 3) /* Monotonic Overflow Interrupt En */ +#define DIER_TOIE (1 << 2) /* Time Overflow Interrupt Enable */ +#define DIER_SVIE (1 << 0) /* Security Violation Interrupt En */ + +/* DryIce Monotonic Counter Reg */ +#define DMCR 0x1c + +/* DryIce Key Select Reg */ +#define DKSR 0x20 +#define DKSR_IIM_KEY 0x0 +#define DKSR_PROG_KEY 0x4 +#define DKSR_RAND_KEY 0x5 +#define DKSR_PROG_XOR_IIM_KEY 0x6 +#define DKSR_RAND_XOR_IIM_KEY 0x7 + +/* DryIce Key Control Reg */ +#define DKCR 0x24 +#define DKCR_LRK (1 << 0) /* Load Random Key */ + +/* DryIce Tamper Configuration Reg */ +#define DTCR 0x28 +#define DTCR_ETGFB_SHIFT 27 /* Ext Tamper Glitch Filter B */ +#define DTCR_ETGFB_MASK 0xf8000000 +#define DTCR_ETGFA_SHIFT 22 /* Ext Tamper Glitch Filter A */ +#define DTCR_ETGFA_MASK 0x07c00000 +#define DTCR_WTGF_SHIFT 17 /* Wire-mesh Tamper Glitch Filter */ +#define DTCR_WTGF_MASK 0x003e0000 +#define DTCR_WGFE (1 << 16) /* Wire-mesh Glitch Filter Enable */ +#define DTCR_SAOE (1 << 15) /* Security Alarm Output Enable */ +#define DTCR_MOE (1 << 9) /* Monotonic Overflow Enable */ +#define DTCR_TOE (1 << 8) /* Time Overflow Enable */ +#define DTCR_WTE (1 << 7) /* Wire-mesh Tampering Enable */ +#define DTCR_ETBE (1 << 6) /* External Tampering B Enable */ +#define DTCR_ETAE (1 << 5) /* External Tampering A Enable */ +#define DTCR_EBE (1 << 4) /* External Boot Enable */ +#define DTCR_SAIE (1 << 3) /* Security Alarm Input Enable */ +#define DTCR_TTE (1 << 2) /* Temperature Tamper Enable */ +#define DTCR_CTE (1 << 1) /* Clock Tamper Enable */ +#define DTCR_VTE (1 << 0) /* Voltage Tamper Enable */ + +/* DryIce Analog Configuration Reg */ +#define DACR 0x2c +#define DACR_VRC_SHIFT 6 /* Voltage Reference Configuration */ +#define DACR_VRC_MASK 0x000001c0 +#define DACR_HTDC_SHIFT 3 /* High Temperature Detect Configuration */ +#define DACR_HTDC_MASK 0x00000038 +#define DACR_LTDC_SHIFT 0 /* Low Temperature Detect Configuration */ +#define DACR_LTDC_MASK 0x00000007 + +/* DryIce General Purpose Reg */ +#define DGPR 0x3c + +/* DryIce Programmed Key0-7 Regs */ +#define DPKR0 0x40 +#define DPKR1 0x44 +#define DPKR2 0x48 +#define DPKR3 0x4c +#define DPKR4 0x50 +#define DPKR5 0x54 +#define DPKR6 0x58 +#define DPKR7 0x5c + +/* DryIce Random Key0-7 Regs */ +#define DRKR0 0x60 +#define DRKR1 0x64 +#define DRKR2 0x68 +#define DRKR3 0x6c +#define DRKR4 0x70 +#define DRKR5 0x74 +#define DRKR6 0x78 +#define DRKR7 0x7c + +#define DI_ADDRESS_RANGE (DRKR7 + 4) + +/* + * this doesn't really belong here but the + * portability layer doesn't include it + */ +#ifdef LINUX_KERNEL +#define EXTERN_SYMBOL(symbol) EXPORT_SYMBOL(symbol) +#else +#define EXTERN_SYMBOL(symbol) do {} while (0) +#endif + +#endif /* __DRYICE_REGS_H__ */ diff --git a/drivers/mxc/security/dryice.c b/drivers/mxc/security/dryice.c new file mode 100644 index 000000000000..f7cefb6ae599 --- /dev/null +++ b/drivers/mxc/security/dryice.c @@ -0,0 +1,1123 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +#undef DI_DEBUG /* enable debug messages */ +#undef DI_DEBUG_REGIO /* show register read/write */ +#undef DI_TESTING /* include test code */ + +#ifdef DI_DEBUG +#define di_debug(fmt, arg...) os_printk(KERN_INFO fmt, ##arg) +#else +#define di_debug(fmt, arg...) do {} while (0) +#endif + +#define di_info(fmt, arg...) os_printk(KERN_INFO fmt, ##arg) +#define di_warn(fmt, arg...) os_printk(KERN_WARNING fmt, ##arg) + +#include "sahara2/include/portable_os.h" +#include "dryice.h" +#include "dryice-regs.h" + +/* mask of the lock-related function flags */ +#define DI_FUNC_LOCK_FLAGS (DI_FUNC_FLAG_READ_LOCK | \ + DI_FUNC_FLAG_WRITE_LOCK | \ + DI_FUNC_FLAG_HARD_LOCK) + +/* + * dryice hardware states + */ +enum di_states { + DI_STATE_VALID = 0, + DI_STATE_NON_VALID, + DI_STATE_FAILURE, +}; + +/* + * todo list actions + */ +enum todo_actions { + TODO_ACT_WRITE_VAL, + TODO_ACT_WRITE_PTR, + TODO_ACT_WRITE_PTR32, + TODO_ACT_ASSIGN, + TODO_ACT_WAIT_RKG, +}; + +/* + * todo list status + */ +enum todo_status { + TODO_ST_LOADING, + TODO_ST_READY, + TODO_ST_PEND_WCF, + TODO_ST_PEND_RKG, + TODO_ST_DONE, +}; + +OS_DEV_INIT_DCL(dryice_init) +OS_DEV_SHUTDOWN_DCL(dryice_exit) +OS_DEV_ISR_DCL(dryice_norm_irq) +OS_WAIT_OBJECT(done_queue); +OS_WAIT_OBJECT(exit_queue); + +struct dryice_data { + int busy; /* enforce exclusive access */ + os_lock_t busy_lock; + int exit_flag; /* don't start new operations */ + + uint32_t baseaddr; /* physical base address */ + void *ioaddr; /* virtual base address */ + + /* interrupt handling */ + struct irq_struct { + os_interrupt_id_t irq; + int set; + } irq_norm, irq_sec; + + struct clk *clk; /* clock control */ + + int key_programmed; /* key has been programmed */ + int key_selected; /* key has been selected */ + + /* callback function and cookie */ + void (*cb_func)(di_return_t rc, unsigned long cookie); + unsigned long cb_cookie; +} *di = NULL; + +#define TODO_LIST_LEN 12 +static struct { + struct td { + enum todo_actions action; + uint32_t src; + uint32_t dst; + int num; + } list[TODO_LIST_LEN]; + int cur; /* current todo pointer */ + int num; /* number of todo's on the list */ + int async; /* non-zero if list is async */ + int status; /* current status of the list */ + di_return_t rc; /* return code generated by the list */ +} todo; + +/* + * dryice register read/write functions + */ +#ifdef DI_DEBUG_REGIO +static uint32_t di_read(int reg) +{ + uint32_t val = os_read32(di->ioaddr + (reg)); + di_info("di_read(0x%02x) = 0x%08x\n", reg, val); + + return val; +} + +static void di_write(uint32_t val, int reg) +{ + di_info("dryice_write_reg(0x%08x, 0x%02x)\n", val, reg); + os_write32(di->ioaddr + (reg), val); +} +#else +#define di_read(reg) os_read32(di->ioaddr + (reg)) +#define di_write(val, reg) os_write32(di->ioaddr + (reg), val); +#endif + +/* + * set the dryice busy flag atomically, allowing + * for case where the driver is trying to exit. + */ +static int di_busy_set(void) +{ + os_lock_context_t context; + int rc = 0; + + os_lock_save_context(di->busy_lock, context); + if (di->exit_flag || di->busy) + rc = 1; + else + di->busy = 1; + os_unlock_restore_context(di->busy_lock, context); + + return rc; +} + +/* + * clear the dryice busy flag + */ +static inline void di_busy_clear(void) +{ + /* don't acquire the lock because the race is benign */ + di->busy = 0; + + if (di->exit_flag) + os_wake_sleepers(exit_queue); +} + +/* + * return the current state of dryice + * (valid, non-valid, or failure) + */ +static enum di_states di_state(void) +{ + enum di_states state = DI_STATE_VALID; + uint32_t dsr = di_read(DSR); + + if (dsr & DSR_NVF) + state = DI_STATE_NON_VALID; + else if (dsr & DSR_SVF) + state = DI_STATE_FAILURE; + + return state; +} + +#define DI_WRITE_LOOP_CNT 0x1000 +/* + * the write-error flag is something that shouldn't get set + * during normal operation. if it's set something is terribly + * wrong. the best we can do is try to clear the bit and hope + * that dryice will recover. this situation is similar to an + * unexpected bus fault in terms of severity. + */ +static void try_to_clear_wef(void) +{ + int cnt; + + while (1) { + di_write(DSR_WEF, DSR); + for (cnt = 0; cnt < DI_WRITE_LOOP_CNT; cnt++) { + if ((di_read(DSR) & DSR_WEF) == 0) + break; + } + di_warn("WARNING: DryIce cannot clear DSR_WEF " + "(Write Error Flag)!\n"); + } +} + +/* + * write a dryice register and loop, waiting for it + * to complete. use only during driver initialization. + * returns 0 on success or 1 on write failure. + */ +static int di_write_loop(uint32_t val, int reg) +{ + int rc = 0; + int cnt; + + di_debug("FUNC: %s\n", __func__); + di_write(val, reg); + + for (cnt = 0; cnt < DI_WRITE_LOOP_CNT; cnt++) { + uint32_t dsr = di_read(DSR); + if (dsr & DSR_WEF) { + try_to_clear_wef(); + rc = 1; + } + if (dsr & DSR_WCF) + break; + } + di_debug("wait_write_loop looped %d times\n", cnt); + if (cnt == DI_WRITE_LOOP_CNT) + rc = 1; + + if (rc) + di_warn("DryIce wait_write_done: WRITE ERROR!\n"); + return rc; +} + +/* + * initialize the todo list. must be called + * before adding items to the list. + */ +static void todo_init(int async_flag) +{ + di_debug("FUNC: %s\n", __func__); + todo.cur = 0; + todo.num = 0; + todo.async = async_flag; + todo.rc = 0; + todo.status = TODO_ST_LOADING; +} + +/* + * perform the current action on the todo list + */ +#define TC todo.list[todo.cur] +void todo_cur(void) +{ + di_debug("FUNC: %s[%d]\n", __func__, todo.cur); + switch (TC.action) { + case TODO_ACT_WRITE_VAL: + di_debug(" TODO_ACT_WRITE_VAL\n"); + /* enable the write-completion interrupt */ + todo.status = TODO_ST_PEND_WCF; + di_write(di_read(DIER) | DIER_WCIE, DIER); + + di_write(TC.src, TC.dst); + break; + + case TODO_ACT_WRITE_PTR32: + di_debug(" TODO_ACT_WRITE_PTR32\n"); + /* enable the write-completion interrupt */ + todo.status = TODO_ST_PEND_WCF; + di_write(di_read(DIER) | DIER_WCIE, DIER); + + di_write(*(uint32_t *)TC.src, TC.dst); + break; + + case TODO_ACT_WRITE_PTR: + { + uint8_t *p = (uint8_t *)TC.src; + uint32_t val = 0; + int num = TC.num; + + di_debug(" TODO_ACT_WRITE_PTR\n"); + while (num--) + val = (val << 8) | *p++; + + /* enable the write-completion interrupt */ + todo.status = TODO_ST_PEND_WCF; + di_write(di_read(DIER) | DIER_WCIE, DIER); + + di_write(val, TC.dst); + } + break; + + case TODO_ACT_ASSIGN: + di_debug(" TODO_ACT_ASSIGN\n"); + switch (TC.num) { + case 1: + *(uint8_t *)TC.dst = TC.src; + break; + case 2: + *(uint16_t *)TC.dst = TC.src; + break; + case 4: + *(uint32_t *)TC.dst = TC.src; + break; + default: + di_warn("Unexpected size in TODO_ACT_ASSIGN\n"); + break; + } + break; + + case TODO_ACT_WAIT_RKG: + di_debug(" TODO_ACT_WAIT_RKG\n"); + /* enable the random-key interrupt */ + todo.status = TODO_ST_PEND_RKG; + di_write(di_read(DIER) | DIER_RKIE, DIER); + break; + + default: + di_debug(" TODO_ACT_NOOP\n"); + break; + } +} + +/* + * called when done with the todo list. + * if async, it does the callback. + * if blocking, it wakes up the caller. + */ +static void todo_done(di_return_t rc) +{ + todo.rc = rc; + todo.status = TODO_ST_DONE; + if (todo.async) { + di_busy_clear(); + if (di->cb_func) + di->cb_func(rc, di->cb_cookie); + } else + os_wake_sleepers(done_queue); +} + +/* + * performs the actions sequentially from the todo list + * until it encounters an item that isn't ready. + */ +static void todo_run(void) +{ + di_debug("FUNC: %s\n", __func__); + while (todo.status == TODO_ST_READY) { + if (todo.cur == todo.num) { + todo_done(0); + break; + } + todo_cur(); + if (todo.status != TODO_ST_READY) + break; + todo.cur++; + } +} + +/* + * kick off the todo list by making it ready + */ +static void todo_start(void) +{ + di_debug("FUNC: %s\n", __func__); + todo.status = TODO_ST_READY; + todo_run(); +} + +/* + * blocking callers sleep here until the todo list is done + */ +static int todo_wait_done(void) +{ + di_debug("FUNC: %s\n", __func__); + os_sleep(done_queue, todo.status == TODO_ST_DONE, 0); + + return todo.rc; +} + +/* + * add a dryice register write to the todo list. + * the value to be written is supplied. + */ +#define todo_write_val(val, reg) \ + todo_add(TODO_ACT_WRITE_VAL, val, reg, 0) + +/* + * add a dryice register write to the todo list. + * "size" bytes pointed to by addr will be written. + */ +#define todo_write_ptr(addr, reg, size) \ + todo_add(TODO_ACT_WRITE_PTR, (uint32_t)addr, reg, size) + +/* + * add a dryice register write to the todo list. + * the word pointed to by addr will be written. + */ +#define todo_write_ptr32(addr, reg) \ + todo_add(TODO_ACT_WRITE_PTR32, (uint32_t)addr, reg, 0) + +/* + * add a dryice memory write to the todo list. + * object can only have a size of 1, 2, or 4 bytes. + */ +#define todo_assign(var, val) \ + todo_add(TODO_ACT_ASSIGN, val, (uint32_t)&(var), sizeof(var)) + +#define todo_wait_rkg() \ + todo_add(TODO_ACT_WAIT_RKG, 0, 0, 0) + +static void todo_add(int action, uint32_t src, uint32_t dst, int num) +{ + struct td *p = &todo.list[todo.num]; + + di_debug("FUNC: %s\n", __func__); + if (todo.num == TODO_LIST_LEN) { + di_warn("WARNING: DryIce todo-list overflow!\n"); + return; + } + p->action = action; + p->src = src; + p->dst = dst; + p->num = num; + todo.num++; +} + +#if defined(DI_DEBUG) || defined(DI_TESTING) +/* + * print out the contents of the dryice status register + * with all the bits decoded + */ +static void show_dsr(const char *heading) +{ + uint32_t dsr = di_read(DSR); + + di_info("%s\n", heading); + if (dsr & DSR_TAMPER_BITS) { + if (dsr & DSR_WTD) + di_info("Wire-mesh Tampering Detected\n"); + if (dsr & DSR_ETBD) + di_info("External Tampering B Detected\n"); + if (dsr & DSR_ETAD) + di_info("External Tampering A Detected\n"); + if (dsr & DSR_EBD) + di_info("External Boot Detected\n"); + if (dsr & DSR_SAD) + di_info("Security Alarm Detected\n"); + if (dsr & DSR_TTD) + di_info("Temperature Tampering Detected\n"); + if (dsr & DSR_CTD) + di_info("Clock Tampering Detected\n"); + if (dsr & DSR_VTD) + di_info("Voltage Tampering Detected\n"); + if (dsr & DSR_MCO) + di_info("Monotonic Counter Overflow\n"); + if (dsr & DSR_TCO) + di_info("Time Counter Overflow\n"); + } else + di_info("No Tamper Events Detected\n"); + + di_info("%d Key Busy Flag\n", !!(dsr & DSR_KBF)); + di_info("%d Write Busy Flag\n", !!(dsr & DSR_WBF)); + di_info("%d Write Next Flag\n", !!(dsr & DSR_WNF)); + di_info("%d Write Complete Flag\n", !!(dsr & DSR_WCF)); + di_info("%d Write Error Flag\n", !!(dsr & DSR_WEF)); + di_info("%d Random Key Error\n", !!(dsr & DSR_RKE)); + di_info("%d Random Key Valid\n", !!(dsr & DSR_RKV)); + di_info("%d Clock Alarm Flag\n", !!(dsr & DSR_CAF)); + di_info("%d Non-Valid Flag\n", !!(dsr & DSR_NVF)); + di_info("%d Security Violation Flag\n", !!(dsr & DSR_SVF)); +} + +/* + * print out a key in hex + */ +static void print_key(const char *tag, uint8_t *key, int bits) +{ + int bytes = (bits + 7) / 8; + + di_info("%s", tag); + while (bytes--) + os_printk("%02x", *key++); + os_printk("\n"); +} +#endif /* defined(DI_DEBUG) || defined(DI_TESTING) */ + +/* + * dryice normal interrupt service routine + */ +OS_DEV_ISR(dryice_norm_irq) +{ + /* save dryice status register */ + uint32_t dsr = di_read(DSR); + + if (dsr & DSR_WCF) { + /* disable the write-completion interrupt */ + di_write(di_read(DIER) & ~DIER_WCIE, DIER); + + if (todo.status == TODO_ST_PEND_WCF) { + if (dsr & DSR_WEF) { + try_to_clear_wef(); + todo_done(DI_ERR_WRITE); + } else { + todo.cur++; + todo.status = TODO_ST_READY; + todo_run(); + } + } + } else if (dsr & (DSR_RKV | DSR_RKE)) { + /* disable the random-key-gen interrupt */ + di_write(di_read(DIER) & ~DIER_RKIE, DIER); + + if (todo.status == TODO_ST_PEND_RKG) { + if (dsr & DSR_RKE) + todo_done(DI_ERR_FAIL); + else { + todo.cur++; + todo.status = TODO_ST_READY; + todo_run(); + } + } + } + + os_dev_isr_return(1); +} + +/* write loop with error handling -- for init only */ +#define di_write_loop_goto(val, reg, rc, label) \ + do {if (di_write_loop(val, reg)) \ + {rc = OS_ERROR_FAIL_S; goto label; } } while (0) + +/* + * dryice driver initialization + */ +OS_DEV_INIT(dryice_init) +{ + di_return_t rc = 0; + + di_info("MXC DryIce driver\n"); + + /* allocate memory */ + di = os_alloc_memory(sizeof(*di), GFP_KERNEL); + if (di == NULL) { + rc = OS_ERROR_NO_MEMORY_S; + goto err_alloc; + } + memset(di, 0, sizeof(*di)); + di->baseaddr = DRYICE_BASE_ADDR; + di->irq_norm.irq = MXC_INT_DRYICE_NORM; + di->irq_sec.irq = MXC_INT_DRYICE_SEC; + + /* map i/o registers */ + di->ioaddr = os_map_device(di->baseaddr, DI_ADDRESS_RANGE); + if (di->ioaddr == NULL) { + rc = OS_ERROR_FAIL_S; + goto err_iomap; + } + + /* allocate locks */ + di->busy_lock = os_lock_alloc_init(); + if (di->busy_lock == NULL) { + rc = OS_ERROR_NO_MEMORY_S; + goto err_locks; + } + + /* enable clocks (is there a portable way to do this?) */ + di->clk = clk_get(NULL, "dryice_clk"); + clk_enable(di->clk); + + /* register for interrupts */ + /* os_register_interrupt() dosen't support an option to make the + interrupt as shared. Replaced it with request_irq().*/ + rc = request_irq(di->irq_norm.irq, dryice_norm_irq, IRQF_SHARED, + "dry_ice", di); + if (rc) + goto err_irqs; + else + di->irq_norm.set = 1; + + /* + * DRYICE HARDWARE INIT + */ + +#ifdef DI_DEBUG + show_dsr("DSR Pre-Initialization State"); +#endif + + if (di_state() == DI_STATE_NON_VALID) { + uint32_t dsr = di_read(DSR); + + di_debug("initializing from non-valid state\n"); + + /* clear security violation flag */ + if (dsr & DSR_SVF) + di_write_loop_goto(DSR_SVF, DSR, rc, err_write); + + /* clear tamper detect flags */ + if (dsr & DSR_TAMPER_BITS) + di_write_loop_goto(DSR_TAMPER_BITS, DSR, rc, err_write); + + /* initialize timers */ + di_write_loop_goto(0, DTCLR, rc, err_write); + di_write_loop_goto(0, DTCMR, rc, err_write); + di_write_loop_goto(0, DMCR, rc, err_write); + + /* clear non-valid flag */ + di_write_loop_goto(DSR_NVF, DSR, rc, err_write); + } + + /* set tamper events we are interested in watching */ + di_write_loop_goto(DTCR_WTE | DTCR_ETBE | DTCR_ETAE, DTCR, rc, + err_write); +#ifdef DI_DEBUG + show_dsr("DSR Post-Initialization State"); +#endif + os_dev_init_return(OS_ERROR_OK_S); + +err_write: + /* unregister interrupts */ + if (di->irq_norm.set) + os_deregister_interrupt(di->irq_norm.irq); + if (di->irq_sec.set) + os_deregister_interrupt(di->irq_sec.irq); + + /* turn off clocks (is there a portable way to do this?) */ + clk_disable(di->clk); + clk_put(di->clk); + +err_irqs: + /* unallocate locks */ + os_lock_deallocate(di->busy_lock); + +err_locks: + /* unmap i/o registers */ + os_unmap_device(di->ioaddr, DI_ADDRESS_RANGE); + +err_iomap: + /* free the dryice struct */ + os_free_memory(di); + +err_alloc: + os_dev_init_return(rc); +} + +/* + * dryice driver exit routine + */ +OS_DEV_SHUTDOWN(dryice_exit) +{ + /* don't allow new operations */ + di->exit_flag = 1; + + /* wait for the current operation to complete */ + os_sleep(exit_queue, di->busy == 0, 0); + + /* unregister interrupts */ + if (di->irq_norm.set) + os_deregister_interrupt(di->irq_norm.irq); + if (di->irq_sec.set) + os_deregister_interrupt(di->irq_sec.irq); + + /* turn off clocks (is there a portable way to do this?) */ + clk_disable(di->clk); + clk_put(di->clk); + + /* unallocate locks */ + os_lock_deallocate(di->busy_lock); + + /* unmap i/o registers */ + os_unmap_device(di->ioaddr, DI_ADDRESS_RANGE); + + /* free the dryice struct */ + os_free_memory(di); + + os_dev_shutdown_return(OS_ERROR_OK_S); +} + +di_return_t dryice_set_programmed_key(const void *key_data, int key_bits, + int flags) +{ + uint32_t dcr; + int key_bytes, reg; + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + if (key_data == NULL) { + rc = DI_ERR_INVAL; + goto err; + } + if (key_bits < 0 || key_bits > MAX_KEY_LEN || key_bits % 8) { + rc = DI_ERR_INVAL; + goto err; + } + if (flags & DI_FUNC_FLAG_WORD_KEY) { + if (key_bits % 32 || (uint32_t)key_data & 0x3) { + rc = DI_ERR_INVAL; + goto err; + } + } + if (di->key_programmed) { + rc = DI_ERR_INUSE; + goto err; + } + if (di_state() == DI_STATE_FAILURE) { + rc = DI_ERR_STATE; + goto err; + } + dcr = di_read(DCR); + if (dcr & DCR_PKWHL) { + rc = DI_ERR_HLOCK; + goto err; + } + if (dcr & DCR_PKWSL) { + rc = DI_ERR_SLOCK; + goto err; + } + key_bytes = key_bits / 8; + + todo_init((flags & DI_FUNC_FLAG_ASYNC) != 0); + + /* accomodate busses that can only do 32-bit transfers */ + if (flags & DI_FUNC_FLAG_WORD_KEY) { + uint32_t *keyp = (void *)key_data; + + for (reg = 0; reg < MAX_KEY_WORDS; reg++) { + if (reg < MAX_KEY_WORDS - key_bytes / 4) + todo_write_val(0, DPKR7 - reg * 4); + else { + todo_write_ptr32(keyp, DPKR7 - reg * 4); + keyp++; + } + } + } else { + uint8_t *keyp = (void *)key_data; + + for (reg = 0; reg < MAX_KEY_WORDS; reg++) { + int size = key_bytes - (MAX_KEY_WORDS - reg - 1) * 4; + if (size <= 0) + todo_write_val(0, DPKR7 - reg * 4); + else { + if (size > 4) + size = 4; + todo_write_ptr(keyp, DPKR7 - reg * 4, size); + keyp += size; + } + } + } + todo_assign(di->key_programmed, 1); + + if (flags & DI_FUNC_LOCK_FLAGS) { + dcr = di_read(DCR); + if (flags & DI_FUNC_FLAG_READ_LOCK) { + if (flags & DI_FUNC_FLAG_HARD_LOCK) + dcr |= DCR_PKRHL; + else + dcr |= DCR_PKRSL; + } + if (flags & DI_FUNC_FLAG_WRITE_LOCK) { + if (flags & DI_FUNC_FLAG_HARD_LOCK) + dcr |= DCR_PKWHL; + else + dcr |= DCR_PKWSL; + } + todo_write_val(dcr, DCR); + } + todo_start(); + + if (flags & DI_FUNC_FLAG_ASYNC) + return 0; + + rc = todo_wait_done(); +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_set_programmed_key); + +di_return_t dryice_get_programmed_key(uint8_t *key_data, int key_bits) +{ + int reg, byte, key_bytes; + uint32_t dcr, dpkr; + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + if (key_data == NULL) { + rc = DI_ERR_INVAL; + goto err; + } + if (key_bits < 0 || key_bits > MAX_KEY_LEN || key_bits % 8) { + rc = DI_ERR_INVAL; + goto err; + } + #if 0 + if (!di->key_programmed) { + rc = DI_ERR_UNSET; + goto err; + } + #endif + if (di_state() == DI_STATE_FAILURE) { + rc = DI_ERR_STATE; + goto err; + } + dcr = di_read(DCR); + if (dcr & DCR_PKRHL) { + rc = DI_ERR_HLOCK; + goto err; + } + if (dcr & DCR_PKRSL) { + rc = DI_ERR_SLOCK; + goto err; + } + key_bytes = key_bits / 8; + + /* read key */ + for (reg = 0; reg < MAX_KEY_WORDS; reg++) { + if (reg < (MAX_KEY_BYTES - key_bytes) / 4) + continue; + dpkr = di_read(DPKR7 - reg * 4); + + for (byte = 0; byte < 4; byte++) { + if (reg * 4 + byte >= MAX_KEY_BYTES - key_bytes) { + int shift = 24 - byte * 8; + *key_data++ = (dpkr >> shift) & 0xff; + } + } + dpkr = 0; /* cleared for security */ + } +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_get_programmed_key); + +di_return_t dryice_release_programmed_key(void) +{ + uint32_t dcr; + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + if (!di->key_programmed) { + rc = DI_ERR_UNSET; + goto err; + } + dcr = di_read(DCR); + if (dcr & DCR_PKWHL) { + rc = DI_ERR_HLOCK; + goto err; + } + if (dcr & DCR_PKWSL) { + rc = DI_ERR_SLOCK; + goto err; + } + di->key_programmed = 0; + +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_release_programmed_key); + +di_return_t dryice_set_random_key(int flags) +{ + uint32_t dcr; + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + if (di_state() == DI_STATE_FAILURE) { + rc = DI_ERR_STATE; + goto err; + } + dcr = di_read(DCR); + if (dcr & DCR_RKHL) { + rc = DI_ERR_HLOCK; + goto err; + } + if (dcr & DCR_RKSL) { + rc = DI_ERR_SLOCK; + goto err; + } + todo_init((flags & DI_FUNC_FLAG_ASYNC) != 0); + + /* clear Random Key Error bit, if set */ + if (di_read(DSR) & DSR_RKE) + todo_write_val(DSR_RKE, DCR); + + /* load random key */ + todo_write_val(DKCR_LRK, DKCR); + + /* wait for RKV (valid) or RKE (error) */ + todo_wait_rkg(); + + if (flags & DI_FUNC_LOCK_FLAGS) { + dcr = di_read(DCR); + if (flags & DI_FUNC_FLAG_WRITE_LOCK) { + if (flags & DI_FUNC_FLAG_HARD_LOCK) + dcr |= DCR_RKHL; + else + dcr |= DCR_RKSL; + } + todo_write_val(dcr, DCR); + } + todo_start(); + + if (flags & DI_FUNC_FLAG_ASYNC) + return 0; + + rc = todo_wait_done(); +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_set_random_key); + +di_return_t dryice_select_key(di_key_t key, int flags) +{ + uint32_t dcr, dksr; + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + switch (key) { + case DI_KEY_FK: + dksr = DKSR_IIM_KEY; + break; + case DI_KEY_PK: + dksr = DKSR_PROG_KEY; + break; + case DI_KEY_RK: + dksr = DKSR_RAND_KEY; + break; + case DI_KEY_FPK: + dksr = DKSR_PROG_XOR_IIM_KEY; + break; + case DI_KEY_FRK: + dksr = DKSR_RAND_XOR_IIM_KEY; + break; + default: + rc = DI_ERR_INVAL; + goto err; + } + if (di->key_selected) { + rc = DI_ERR_INUSE; + goto err; + } + if (di_state() != DI_STATE_VALID) { + rc = DI_ERR_STATE; + goto err; + } + dcr = di_read(DCR); + if (dcr & DCR_KSHL) { + rc = DI_ERR_HLOCK; + goto err; + } + if (dcr & DCR_KSSL) { + rc = DI_ERR_SLOCK; + goto err; + } + todo_init((flags & DI_FUNC_FLAG_ASYNC) != 0); + + /* select key */ + todo_write_val(dksr, DKSR); + + todo_assign(di->key_selected, 1); + + if (flags & DI_FUNC_LOCK_FLAGS) { + dcr = di_read(DCR); + if (flags & DI_FUNC_FLAG_WRITE_LOCK) { + if (flags & DI_FUNC_FLAG_HARD_LOCK) + dcr |= DCR_KSHL; + else + dcr |= DCR_KSSL; + } + todo_write_val(dcr, DCR); + } + todo_start(); + + if (flags & DI_FUNC_FLAG_ASYNC) + return 0; + + rc = todo_wait_done(); +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_select_key); + +di_return_t dryice_check_key(di_key_t *key) +{ + uint32_t dksr; + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + if (key == NULL) { + rc = DI_ERR_INVAL; + goto err; + } + + dksr = di_read(DKSR); + + if (di_state() != DI_STATE_VALID) { + dksr = DKSR_IIM_KEY; + rc = DI_ERR_STATE; + } else if (dksr == DI_KEY_RK || dksr == DI_KEY_FRK) { + if (!(di_read(DSR) & DSR_RKV)) { + dksr = DKSR_IIM_KEY; + rc = DI_ERR_UNSET; + } + } + switch (dksr) { + case DKSR_IIM_KEY: + *key = DI_KEY_FK; + break; + case DKSR_PROG_KEY: + *key = DI_KEY_PK; + break; + case DKSR_RAND_KEY: + *key = DI_KEY_RK; + break; + case DKSR_PROG_XOR_IIM_KEY: + *key = DI_KEY_FPK; + break; + case DKSR_RAND_XOR_IIM_KEY: + *key = DI_KEY_FRK; + break; + } +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_check_key); + +di_return_t dryice_release_key_selection(void) +{ + uint32_t dcr; + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + if (!di->key_selected) { + rc = DI_ERR_UNSET; + goto err; + } + dcr = di_read(DCR); + if (dcr & DCR_KSHL) { + rc = DI_ERR_HLOCK; + goto err; + } + if (dcr & DCR_KSSL) { + rc = DI_ERR_SLOCK; + goto err; + } + di->key_selected = 0; + +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_release_key_selection); + +di_return_t dryice_get_tamper_event(uint32_t *events, uint32_t *timestamp, + int flags) +{ + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + if (di_state() == DI_STATE_VALID) { + rc = DI_ERR_STATE; + goto err; + } + if (events == NULL) { + rc = DI_ERR_INVAL; + goto err; + } + *events = di_read(DSR) & DSR_TAMPER_BITS; + if (timestamp) { + if (di_state() == DI_STATE_NON_VALID) + *timestamp = di_read(DTCMR); + else + *timestamp = 0; + } +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_get_tamper_event); + +di_return_t dryice_register_callback(void (*func)(di_return_t, + unsigned long cookie), + unsigned long cookie) +{ + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + di->cb_func = func; + di->cb_cookie = cookie; + + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_register_callback); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("DryIce"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/security/dryice.h b/drivers/mxc/security/dryice.h new file mode 100644 index 000000000000..8334b5098d31 --- /dev/null +++ b/drivers/mxc/security/dryice.h @@ -0,0 +1,287 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +#ifndef __DRYICE_H__ +#define __DRYICE_H__ + + +/*! + * @file dryice.h + * @brief Definition of DryIce API. + */ + +/*! @page dryice_api DryIce API + * + * Definition of the DryIce API. + * + * The DryIce API implements a software interface to the DryIce hardware + * block. Methods are provided to store, retrieve, generate, and manage + * cryptographic keys and to monitor security tamper events. + * + * See @ref dryice_api for the DryIce API. + */ + +/*! + * This defines the SCC key length (in bits) + */ +#define SCC_KEY_LEN 168 + +/*! + * This defines the maximum key length (in bits) + */ +#define MAX_KEY_LEN 256 +#define MAX_KEY_BYTES ((MAX_KEY_LEN) / 8) +#define MAX_KEY_WORDS ((MAX_KEY_LEN) / 32) + +/*! + * @name DryIce Function Flags + */ +/*@{*/ +#define DI_FUNC_FLAG_ASYNC 0x01 /*!< do not block */ +#define DI_FUNC_FLAG_READ_LOCK 0x02 /*!< set read lock for this resource */ +#define DI_FUNC_FLAG_WRITE_LOCK 0x04 /*!< set write lock for resource */ +#define DI_FUNC_FLAG_HARD_LOCK 0x08 /*!< locks will be hard (default soft) */ +#define DI_FUNC_FLAG_WORD_KEY 0x10 /*!< key provided as 32-bit words */ +/*@}*/ + +/*! + * @name DryIce Tamper Events + */ +/*@{*/ +#define DI_TAMPER_EVENT_WTD (1 << 23) /*!< wire-mesh tampering det */ +#define DI_TAMPER_EVENT_ETBD (1 << 22) /*!< ext tampering det: input B */ +#define DI_TAMPER_EVENT_ETAD (1 << 21) /*!< ext tampering det: input A */ +#define DI_TAMPER_EVENT_EBD (1 << 20) /*!< external boot detected */ +#define DI_TAMPER_EVENT_SAD (1 << 19) /*!< security alarm detected */ +#define DI_TAMPER_EVENT_TTD (1 << 18) /*!< temperature tampering det */ +#define DI_TAMPER_EVENT_CTD (1 << 17) /*!< clock tampering det */ +#define DI_TAMPER_EVENT_VTD (1 << 16) /*!< voltage tampering det */ +#define DI_TAMPER_EVENT_MCO (1 << 3) /*!< monotonic counter overflow */ +#define DI_TAMPER_EVENT_TCO (1 << 2) /*!< time counter overflow */ +/*@}*/ + +/*! + * DryIce Key Sources + */ +typedef enum di_key { + DI_KEY_FK, /*!< the fused (IIM) key */ + DI_KEY_PK, /*!< the programmed key */ + DI_KEY_RK, /*!< the random key */ + DI_KEY_FPK, /*!< the programmed key XORed with the fused key */ + DI_KEY_FRK, /*!< the random key XORed with the fused key */ +} di_key_t; + +/*! + * DryIce Error Codes + */ +typedef enum dryice_return { + DI_SUCCESS = 0, /*!< operation was successful */ + DI_ERR_BUSY, /*!< device or resource busy */ + DI_ERR_STATE, /*!< dryice is in incompatible state */ + DI_ERR_INUSE, /*!< resource is already in use */ + DI_ERR_UNSET, /*!< resource has not been initialized */ + DI_ERR_WRITE, /*!< error occurred during register write */ + DI_ERR_INVAL, /*!< invalid argument */ + DI_ERR_FAIL, /*!< operation failed */ + DI_ERR_HLOCK, /*!< resource is hard locked */ + DI_ERR_SLOCK, /*!< resource is soft locked */ + DI_ERR_NOMEM, /*!< out of memory */ +} di_return_t; + +/*! + * These functions define the DryIce API. + */ + +/*! + * Write a given key to the Programmed Key registers in DryIce, and + * optionally lock the Programmed Key against either reading or further + * writing. The value is held until a call to the release_programmed_key + * interface is made, or until the appropriate HW reset if the write-lock + * flags are used. Unused key bits will be zeroed. + * + * @param[in] key_data A pointer to the key data to be programmed, with + * the most significant byte or word first. This + * will be interpreted as a byte pointer unless the + * WORD_KEY flag is set, in which case it will be + * treated as a word pointer and the key data will be + * read a word at a time, starting with the MSW. + * When called asynchronously, the data pointed to by + * key_data must persist until the operation completes. + * + * @param[in] key_bits The number of bits in the key to be stored. + * This must be a multiple of 8 and within the + * range of 0 and MAX_KEY_LEN. + * + * @param[in] flags This is a bit-wise OR of the flags to be passed + * to the function. Flags can include: + * ASYNC, READ_LOCK, WRITE_LOCK, HARD_LOCK, and + * WORD_KEY. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, INVAL + * on invalid arguments, INUSE if key has already been + * programmed, STATE if DryIce is in the wrong state, + * HLOCK or SLOCK if the key registers are locked for + * writing, and WRITE if a write error occurs + * (See #di_return_t). + */ +extern di_return_t dryice_set_programmed_key(const void *key_data, int key_bits, + int flags); + +/*! + * Read the Programmed Key registers and write the contents into a buffer. + * + * @param[out] key_data A byte pointer to where the key data will be written, + * with the most significant byte being written first. + * + * @param[in] key_bits The number of bits of the key to be retrieved. + * This must be a multiple of 8 and within the + * range of 0 and MAX_KEY_LEN. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, INVAL + * on invalid arguments, UNSET if key has not been + * programmed, STATE if DryIce is in the wrong state, + * and HLOCK or SLOCK if the key registers are locked for + * reading (See #di_return_t). + */ +extern di_return_t dryice_get_programmed_key(uint8_t *key_data, int key_bits); + +/*! + * Allow the set_programmed_key interface to be used to write a new + * Programmed Key to DryIce. Note that this interface does not overwrite + * the value in the Programmed Key registers. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, + * UNSET if the key has not been previously set, and + * HLOCK or SLOCK if the key registers are locked for + * writing (See #di_return_t). + */ +extern di_return_t dryice_release_programmed_key(void); + +/*! + * Generate and load a new Random Key in DryIce, and optionally lock the + * Random Key against further change. + * + * @param[in] flags This is a bit-wise OR of the flags to be passed + * to the function. Flags can include: + * ASYNC, READ_LOCK, WRITE_LOCK, and HARD_LOCK. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, STATE + * if DryIce is in the wrong state, FAIL if the key gen + * failed, HLOCK or SLOCK if the key registers are + * locked, and WRITE if a write error occurs + * (See #di_return_t). + */ +extern di_return_t dryice_set_random_key(int flags); + +/*! + * Set the key selection in DryIce to determine the key used by an + * encryption module such as SCC. The selection is held until a call to the + * Release Selected Key interface is made, or until the appropriate HW + * reset if the LOCK flags are used. + * + * @param[in] key The source of the key to be used by the SCC + * (See #di_key_t). + * + * @param[in] flags This is a bit-wise OR of the flags to be passed + * to the function. Flags can include: + * ASYNC, WRITE_LOCK, and HARD_LOCK. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, INVAL + * on invalid arguments, INUSE if a selection has already + * been made, STATE if DryIce is in the wrong state, + * HLOCK or SLOCK if the selection register is locked, + * and WRITE if a write error occurs + */ +extern di_return_t dryice_select_key(di_key_t key, int flags); + +/*! + * Check which key will be used in the SCC. This is needed because in some + * DryIce states, the Key Select Register is overridden by a default value + * (the Fused/IIM key). + * + * @param[out] key The source of the key that is currently selected for + * use by the SCC. This may be different from the key + * specified by the dryice_select_key function + * (See #di_key_t). This value is set even if an error + * code (except for BUSY) is returned. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, STATE if + * DryIce is in the wrong state, INVAL on invalid + * arguments, or UNSET if no key has been selected + * (See #di_return_t). + */ +extern di_return_t dryice_check_key(di_key_t *key); + +/*! + * Allow the dryice_select_key interface to be used to set a new key selection + * in DryIce. Note that this interface does not overwrite the value in DryIce. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, UNSET + * if the no selection has been made previously, and + * HLOCK or SLOCK if the selection register is locked + * (See #di_return_t). + */ +extern di_return_t dryice_release_key_selection(void); + +/*! + * Returns tamper-detection status bits. Also an optional timestamp when + * DryIce is in the Non-valid state. If DryIce is not in Failure or Non-valid + * state, this interface returns a failure code. + * + * @param[out] events This is a bit-wise OR of the following events: + * WTD (Wire Mesh), ETBD (External Tamper B), + * ETAD (External Tamper A), EBD (External Boot), + * SAD (Security Alarm), TTD (Temperature Tamper), + * CTD (Clock Tamper), VTD (Voltage Tamper), + * MCO (Monolithic Counter Overflow), and + * TCO (Time Counter Overflow). + * + * @param[out] timestamp This is the value of the time counter in seconds + * when the tamper occurred. A timestamp will not be + * returned if a NULL pointer is specified. If DryIce + * is not in the Non-valid state the time cannot be + * read, so a timestamp of 0 will be returned. + * + * @param[in] flags This is a bit-wise OR of the flags to be passed + * to the function. Flags is ignored currently by + * this function. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, and + * INVAL on invalid arguments (See #di_return_t). + */ +extern di_return_t +dryice_get_tamper_event(uint32_t *events, uint32_t *timestamp, int flags); + +/*! + * Provide a callback function to be called upon the completion of DryIce calls + * that are executed asynchronously. + * + * @param[in] func This is a pointer to a function of type: + * void callback(di_return_t rc, unsigned long cookie) + * The return code of the async function is passed + * back in "rc" along with the cookie provided when + * registering the callback. + * + * @param[in] cookie This is an "opaque" cookie of type unsigned long that + * is returned on subsequent callbacks. It may be of any + * value. + * + * @return Returns SUCCESS (0), or BUSY if DryIce is busy + * (See #di_return_t). + */ +extern di_return_t dryice_register_callback(void (*func)(di_return_t rc, + unsigned long cookie), + unsigned long cookie); + +#endif /* __DRYICE_H__ */ diff --git a/drivers/mxc/security/mxc_scc.c b/drivers/mxc/security/mxc_scc.c new file mode 100644 index 000000000000..8a6b0c2419b5 --- /dev/null +++ b/drivers/mxc/security/mxc_scc.c @@ -0,0 +1,2386 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_scc.c + * + * This is the driver code for the Security Controller (SCC). It has no device + * driver interface, so no user programs may access it. Its interaction with + * the Linux kernel is from calls to #scc_init() when the driver is loaded, and + * #scc_cleanup() should the driver be unloaded. The driver uses locking and + * (task-sleep/task-wakeup) functions of the kernel. It also registers itself + * to handle the interrupt line(s) from the SCC. + * + * Other drivers in the kernel may use the remaining API functions to get at + * the services of the SCC. The main service provided is the Secure Memory, + * which allows encoding and decoding of secrets with a per-chip secret key. + * + * The SCC is single-threaded, and so is this module. When the scc_crypt() + * routine is called, it will lock out other accesses to the function. If + * another task is already in the module, the subsequent caller will spin on a + * lock waiting for the other access to finish. + * + * Note that long crypto operations could cause a task to spin for a while, + * preventing other kernel work (other than interrupt processing) to get done. + * + * The external (kernel module) interface is through the following functions: + * @li scc_get_configuration() + * @li scc_crypt() + * @li scc_zeroize_memories() + * @li scc_monitor_security_failure() + * @li scc_stop_monitoring_security_failure() + * @li scc_set_sw_alarm() + * @li scc_read_register() + * @li scc_write_register() + * + * All other functions are internal to the driver. + * + * @ingroup MXCSCC +*/ +#include "sahara2/include/fsl_platform.h" +#include "sahara2/include/portable_os.h" +#include "mxc_scc_internals.h" + +#include <linux/delay.h> + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + +#include <linux/device.h> +#include <mach/clock.h> +#include <linux/device.h> + +#else +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> + +#endif + +/*! + * This is the set of errors which signal that access to the SCM RAM has + * failed or will fail. + */ +#define SCM_ACCESS_ERRORS \ + (SCM_ERR_USER_ACCESS | SCM_ERR_ILLEGAL_ADDRESS | \ + SCM_ERR_ILLEGAL_MASTER | SCM_ERR_CACHEABLE_ACCESS | \ + SCM_ERR_UNALIGNED_ACCESS | SCM_ERR_BYTE_ACCESS | \ + SCM_ERR_INTERNAL_ERROR | SCM_ERR_SMN_BLOCKING_ACCESS | \ + SCM_ERR_CIPHERING | SCM_ERR_ZEROIZING | SCM_ERR_BUSY) +/****************************************************************************** + * + * Global / Static Variables + * + *****************************************************************************/ + +/*! + * This is type void* so that a) it cannot directly be dereferenced, + * and b) pointer arithmetic on it will function in a 'normal way' for + * the offsets in scc_defines.h + * + * scc_base is the location in the iomap where the SCC's registers + * (and memory) start. + * + * The referenced data is declared volatile so that the compiler will + * not make any assumptions about the value of registers in the SCC, + * and thus will always reload the register into CPU memory before + * using it (i.e. wherever it is referenced in the driver). + * + * This value should only be referenced by the #SCC_READ_REGISTER and + * #SCC_WRITE_REGISTER macros and their ilk. All dereferences must be + * 32 bits wide. + */ +static volatile void *scc_base; + +/*! Array to hold function pointers registered by + #scc_monitor_security_failure() and processed by + #scc_perform_callbacks() */ +static void (*scc_callbacks[SCC_CALLBACK_SIZE]) (void); + +/*! Structure returned by #scc_get_configuration() */ +static scc_config_t scc_configuration = { + .driver_major_version = SCC_DRIVER_MAJOR_VERSION_1, + .driver_minor_version = SCC_DRIVER_MINOR_VERSION_8, + .scm_version = -1, + .smn_version = -1, + .block_size_bytes = -1, + .black_ram_size_blocks = -1, + .red_ram_size_blocks = -1 +}; + +/*! Key Control Information. Integrity is controlled by use of + #scc_crypto_lock. */ +static struct scc_key_slot scc_key_info[SCC_KEY_SLOTS]; + +/*! Internal flag to know whether SCC is in Failed state (and thus many + * registers are unavailable). Once it goes failed, it never leaves it. */ +static volatile enum scc_status scc_availability = SCC_STATUS_INITIAL; + +/*! Flag to say whether interrupt handler has been registered for + * SMN interrupt */ +static int smn_irq_set = 0; + +/*! Flag to say whether interrupt handler has been registered for + * SCM interrupt */ +static int scm_irq_set = 0; + +/*! This lock protects the #scc_callbacks list as well as the @c + * callbacks_performed flag in #scc_perform_callbacks. Since the data this + * protects may be read or written from either interrupt or base level, all + * operations should use the irqsave/irqrestore or similar to make sure that + * interrupts are inhibited when locking from base level. + */ +static spinlock_t scc_callbacks_lock = SPIN_LOCK_UNLOCKED; + +/*! + * Ownership of this lock prevents conflicts on the crypto operation in the SCC + * and the integrity of the #scc_key_info. + */ +static spinlock_t scc_crypto_lock = SPIN_LOCK_UNLOCKED; + +/*! Calculated once for quick reference to size of the unreserved space in one + * RAM in SCM. + */ +static uint32_t scc_memory_size_bytes; + +/*! Calculated once for quick reference to size of SCM address space */ +static uint32_t scm_highest_memory_address; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)) +#ifndef SCC_CLOCK_NOT_GATED +/*! Pointer to SCC's clock information. Initialized during scc_init(). */ +static struct clk *scc_clk = NULL; +#endif +#endif + +/*! The lookup table for an 8-bit value. Calculated once + * by #scc_init_ccitt_crc(). + */ +static uint16_t scc_crc_lookup_table[256]; + +/*! Fixed padding for appending to plaintext to fill out a block */ +static uint8_t scc_block_padding[8] = + { SCC_DRIVER_PAD_CHAR, 0, 0, 0, 0, 0, 0, 0 }; + +/****************************************************************************** + * + * Function Implementations - Externally Accessible + * + *****************************************************************************/ + +/*****************************************************************************/ +/* fn scc_init() */ +/*****************************************************************************/ +/*! + * Initialize the driver at boot time or module load time. + * + * Register with the kernel as the interrupt handler for the SCC interrupt + * line(s). + * + * Map the SCC's register space into the driver's memory space. + * + * Query the SCC for its configuration and status. Save the configuration in + * #scc_configuration and save the status in #scc_availability. Called by the + * kernel. + * + * Do any locking/wait queue initialization which may be necessary. + * + * The availability fuse may be checked, depending on platform. + */ +static int scc_init(void) +{ + uint32_t smn_status; + int i; + int return_value = -EIO; /* assume error */ + if (scc_availability == SCC_STATUS_INITIAL) { + + /* Set this until we get an initial reading */ + scc_availability = SCC_STATUS_CHECKING; + + /* Initialize the constant for the CRC function */ + scc_init_ccitt_crc(); + + /* initialize the callback table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + scc_callbacks[i] = 0; + } + + /* Initialize key slots */ + for (i = 0; i < SCC_KEY_SLOTS; i++) { + scc_key_info[i].offset = i * SCC_KEY_SLOT_SIZE; + scc_key_info[i].status = 0; /* unassigned */ + } + + /* Enable the SCC clock on platforms where it is gated */ +#ifndef SCC_CLOCK_NOT_GATED + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + mxc_clks_enable(SCC_CLK); +#else + + scc_clk = clk_get(NULL, "scc_clk"); + if (scc_clk != ERR_PTR(ENOENT)) { + clk_enable(scc_clk); + } +#endif /* LINUX_VERSION_CODE */ + +#endif /* SCC_CLOCK_NOT_GATED */ + /* See whether there is an SCC available */ + if (0 && !SCC_ENABLED()) { + os_printk(KERN_ERR + "SCC: Fuse for SCC is set to disabled. Exiting.\n"); + } else { + /* Map the SCC (SCM and SMN) memory on the internal bus into + kernel address space */ + scc_base = (void *)IO_ADDRESS(SCC_BASE); + + /* If that worked, we can try to use the SCC */ + if (scc_base == NULL) { + os_printk(KERN_ERR + "SCC: Register mapping failed. Exiting.\n"); + } else { + /* Get SCM into 'clean' condition w/interrupts cleared & + disabled */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | + SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + + /* Clear error status register (any write will do it) */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, 0); + + /* + * There is an SCC. Determine its current state. Side effect + * is to populate scc_config and scc_availability + */ + smn_status = scc_grab_config_values(); + + /* Try to set up interrupt handler(s) */ + if (scc_availability == SCC_STATUS_OK) { + if (setup_interrupt_handling() != 0) { + unsigned condition; + /*! + * The error could be only that the SCM interrupt was + * not set up. This interrupt is always masked, so + * that is not an issue. + * + * The SMN's interrupt may be shared on that line, it + * may be separate, or it may not be wired. Do what + * is necessary to check its status. + * + * Although the driver is coded for possibility of not + * having SMN interrupt, the fact that there is one + * means it should be available and used. + */ +#ifdef USE_SMN_INTERRUPT + condition = !smn_irq_set; /* Separate. Check SMN binding */ +#elif !defined(NO_SMN_INTERRUPT) + condition = !scm_irq_set; /* Shared. Check SCM binding */ +#else + condition = FALSE; /* SMN not wired at all. Ignore. */ +#endif + /* setup was not able to set up SMN interrupt */ + scc_availability = + SCC_STATUS_UNIMPLEMENTED; + } /* interrupt handling returned non-zero */ + } /* availability is OK */ + if (scc_availability == SCC_STATUS_OK) { + /* Get SMN into 'clean' condition w/interrupts cleared & + enabled */ + SCC_WRITE_REGISTER(SMN_COMMAND, + SMN_COMMAND_CLEAR_INTERRUPT + | + SMN_COMMAND_ENABLE_INTERRUPT); + } + /* availability is still OK */ + } /* if scc_base != NULL */ + + } /* if SCC_ENABLED() */ + + /* + * If status is SCC_STATUS_UNIMPLEMENTED or is still + * SCC_STATUS_CHECKING, could be leaving here with the driver partially + * initialized. In either case, cleanup (which will mark the SCC as + * UNIMPLEMENTED). + */ + if (scc_availability == SCC_STATUS_CHECKING || + scc_availability == SCC_STATUS_UNIMPLEMENTED) { + scc_cleanup(); + } else { + return_value = 0; /* All is well */ + } + } + /* ! STATUS_INITIAL */ + pr_debug("SCC: Driver Status is %s\n", + (scc_availability == SCC_STATUS_INITIAL) ? "INITIAL" : + (scc_availability == SCC_STATUS_CHECKING) ? "CHECKING" : + (scc_availability == + SCC_STATUS_UNIMPLEMENTED) ? "UNIMPLEMENTED" + : (scc_availability == + SCC_STATUS_OK) ? "OK" : (scc_availability == + SCC_STATUS_FAILED) ? "FAILED" : + "UNKNOWN"); + + return return_value; +} /* scc_init */ + +/*****************************************************************************/ +/* fn scc_cleanup() */ +/*****************************************************************************/ +/*! + * Perform cleanup before driver/module is unloaded by setting the machine + * state close to what it was when the driver was loaded. This function is + * called when the kernel is shutting down or when this driver is being + * unloaded. + * + * A driver like this should probably never be unloaded, especially if there + * are other module relying upon the callback feature for monitoring the SCC + * status. + * + * In any case, cleanup the callback table (by clearing out all of the + * pointers). Deregister the interrupt handler(s). Unmap SCC registers. + * + */ +static void scc_cleanup(void) +{ + int i; + + /* Mark the driver / SCC as unusable. */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; + + /* Clear out callback table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + scc_callbacks[i] = 0; + } + + /* If SCC has been mapped in, clean it up and unmap it */ + if (scc_base) { + /* For the SCM, disable interrupts, zeroize RAMs. Interrupt + * status will appear because zeroize will complete. */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_MASK_INTERRUPTS | + SCM_INTERRUPT_CTRL_ZEROIZE_MEMORY); + + /* For the SMN, clear and disable interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND, SMN_COMMAND_CLEAR_INTERRUPT); + + /* remove virtual mapping */ + iounmap((void *)scc_base); + } + + /* Now that interrupts cannot occur, disassociate driver from the interrupt + * lines. + */ + + /* Deregister SCM interrupt handler */ + if (scm_irq_set) { + os_deregister_interrupt(INT_SCC_SCM); + } + + /* Deregister SMN interrupt handler */ + if (smn_irq_set) { +#ifdef USE_SMN_INTERRUPT + os_deregister_interrupt(INT_SCC_SMN); +#endif + } + pr_debug("SCC driver cleaned up.\n"); + +} /* scc_cleanup */ + +/*****************************************************************************/ +/* fn scc_get_configuration() */ +/*****************************************************************************/ +scc_config_t *scc_get_configuration(void) +{ + /* + * If some other driver calls scc before the kernel does, make sure that + * this driver's initialization is performed. + */ + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /*! + * If there is no SCC, yet the driver exists, the value -1 will be in + * the #scc_config_t fields for other than the driver versions. + */ + return &scc_configuration; +} /* scc_get_configuration */ + +/*****************************************************************************/ +/* fn scc_zeroize_memories() */ +/*****************************************************************************/ +scc_return_t scc_zeroize_memories(void) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t status; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + if (scc_availability == SCC_STATUS_OK) { + unsigned long irq_flags; /* for IRQ save/restore */ + + /* Lock access to crypto memory of the SCC */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + /* Start the Zeroize by setting a bit in the SCM_INTERRUPT_CTRL + * register */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_MASK_INTERRUPTS + | SCM_INTERRUPT_CTRL_ZEROIZE_MEMORY); + + scc_wait_completion(); + + /* Get any error info */ + status = SCC_READ_REGISTER(SCM_ERROR_STATUS); + + /* unlock the SCC */ + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + if (!(status & SCM_ERR_ZEROIZE_FAILED)) { + return_status = SCC_RET_OK; + } else { + pr_debug + ("SCC: Zeroize failed. SCM Error Status is 0x%08x\n", + status); + } + + /* Clear out any status. */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + + /* and any error status */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, 0); + } + + return return_status; +} /* scc_zeroize_memories */ + +/*****************************************************************************/ +/* fn scc_crypt() */ +/*****************************************************************************/ +scc_return_t +scc_crypt(unsigned long count_in_bytes, const uint8_t * data_in, + const uint8_t * init_vector, + scc_enc_dec_t direction, scc_crypto_mode_t crypto_mode, + scc_verify_t check_mode, uint8_t * data_out, + unsigned long *count_out_bytes) + +{ + scc_return_t return_code = SCC_RET_FAIL; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + (void)scc_update_state(); /* in case no interrupt line from SMN */ + + /* make initial error checks */ + if (scc_availability != SCC_STATUS_OK + || count_in_bytes == 0 + || data_in == 0 + || data_out == 0 + || (crypto_mode != SCC_CBC_MODE && crypto_mode != SCC_ECB_MODE) + || (crypto_mode == SCC_CBC_MODE && init_vector == NULL) + || (direction != SCC_ENCRYPT && direction != SCC_DECRYPT) + || (check_mode == SCC_VERIFY_MODE_NONE && + count_in_bytes % SCC_BLOCK_SIZE_BYTES() != 0) + || (direction == SCC_DECRYPT && + count_in_bytes % SCC_BLOCK_SIZE_BYTES() != 0) + || (check_mode != SCC_VERIFY_MODE_NONE && + check_mode != SCC_VERIFY_MODE_CCITT_CRC)) { + pr_debug + ("SCC: scc_crypt() count_in_bytes_ok = %d; data_in_ok = %d;" + " data_out_ok = %d; iv_ok = %d\n", !(count_in_bytes == 0), + !(data_in == 0), !(data_out == 0), + !(crypto_mode == SCC_CBC_MODE && init_vector == NULL)); + pr_debug("SCC: scc_crypt() mode_ok=%d; direction_ok=%d;" + " size_ok=%d, check_mode_ok=%d\n", + !(crypto_mode != SCC_CBC_MODE + && crypto_mode != SCC_ECB_MODE), + !(direction != SCC_ENCRYPT + && direction != SCC_DECRYPT), + !((check_mode == SCC_VERIFY_MODE_NONE + && count_in_bytes % SCC_BLOCK_SIZE_BYTES() != 0) + || (direction == SCC_DECRYPT + && count_in_bytes % SCC_BLOCK_SIZE_BYTES() != + 0)), !(check_mode != SCC_VERIFY_MODE_NONE + && check_mode != + SCC_VERIFY_MODE_CCITT_CRC)); + pr_debug("SCC: scc_crypt() detected bad argument\n"); + } else { + /* Start settings for write to SCM_CONTROL register */ + uint32_t scc_control = SCM_CONTROL_START_CIPHER; + unsigned long irq_flags; /* for IRQ save/restore */ + + /* Lock access to crypto memory of the SCC */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + /* Special needs for CBC Mode */ + if (crypto_mode == SCC_CBC_MODE) { + scc_control |= SCM_CBC_MODE; /* change default of ECB */ + /* Put in Initial Context. Vector registers are contiguous */ + copy_to_scc(init_vector, SCM_INIT_VECTOR_0, + SCC_BLOCK_SIZE_BYTES(), NULL); + } + + /* Fill the RED_START register */ + SCC_WRITE_REGISTER(SCM_RED_START, + SCM_NON_RESERVED_OFFSET / + SCC_BLOCK_SIZE_BYTES()); + + /* Fill the BLACK_START register */ + SCC_WRITE_REGISTER(SCM_BLACK_START, + SCM_NON_RESERVED_OFFSET / + SCC_BLOCK_SIZE_BYTES()); + + if (direction == SCC_ENCRYPT) { + /* Check for sufficient space in data_out */ + if (check_mode == SCC_VERIFY_MODE_NONE) { + if (*count_out_bytes < count_in_bytes) { + return_code = + SCC_RET_INSUFFICIENT_SPACE; + } + } else { /* SCC_VERIFY_MODE_CCITT_CRC */ + /* Calculate extra bytes needed for crc (2) and block + padding */ + int padding_needed = + CRC_SIZE_BYTES + SCC_BLOCK_SIZE_BYTES() - + ((count_in_bytes + CRC_SIZE_BYTES) + % SCC_BLOCK_SIZE_BYTES()); + + /* Verify space is available */ + if (*count_out_bytes < + count_in_bytes + padding_needed) { + return_code = + SCC_RET_INSUFFICIENT_SPACE; + } + } + /* If did not detect space error, do the encryption */ + if (return_code != SCC_RET_INSUFFICIENT_SPACE) { + return_code = + scc_encrypt(count_in_bytes, data_in, + scc_control, data_out, + check_mode == + SCC_VERIFY_MODE_CCITT_CRC, + count_out_bytes); + } + + } + /* direction == SCC_ENCRYPT */ + else { /* SCC_DECRYPT */ + /* Check for sufficient space in data_out */ + if (check_mode == SCC_VERIFY_MODE_NONE) { + if (*count_out_bytes < count_in_bytes) { + return_code = + SCC_RET_INSUFFICIENT_SPACE; + } + } else { /* SCC_VERIFY_MODE_CCITT_CRC */ + /* Do initial check. Assume last block (of padding) and CRC + * will get stripped. After decipher is done and padding is + * removed, will know exact value. + */ + int possible_size = + (int)count_in_bytes - CRC_SIZE_BYTES - + SCC_BLOCK_SIZE_BYTES(); + if ((int)*count_out_bytes < possible_size) { + pr_debug + ("SCC: insufficient decrypt space %ld/%d.\n", + *count_out_bytes, possible_size); + return_code = + SCC_RET_INSUFFICIENT_SPACE; + } + } + + /* If did not detect space error, do the decryption */ + if (return_code != SCC_RET_INSUFFICIENT_SPACE) { + return_code = + scc_decrypt(count_in_bytes, data_in, + scc_control, data_out, + check_mode == + SCC_VERIFY_MODE_CCITT_CRC, + count_out_bytes); + } + + } /* SCC_DECRYPT */ + + /* unlock the SCC */ + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + } /* else no initial error */ + + return return_code; +} /* scc_crypt */ + +/*****************************************************************************/ +/* fn scc_set_sw_alarm() */ +/*****************************************************************************/ +void scc_set_sw_alarm(void) +{ + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Update scc_availability based on current SMN status. This might + * perform callbacks. + */ + (void)scc_update_state(); + + /* if everything is OK, make it fail */ + if (scc_availability == SCC_STATUS_OK) { + + /* sound the alarm (and disable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND, SMN_COMMAND_SET_SOFTWARE_ALARM); + + scc_availability = SCC_STATUS_FAILED; /* Remember what we've done */ + + /* In case SMN interrupt is not available, tell the world */ + scc_perform_callbacks(); + } + + return; +} /* scc_set_sw_alarm */ + +/*****************************************************************************/ +/* fn scc_monitor_security_failure() */ +/*****************************************************************************/ +scc_return_t scc_monitor_security_failure(void callback_func(void)) +{ + int i; + unsigned long irq_flags; /* for IRQ save/restore */ + scc_return_t return_status = SCC_RET_TOO_MANY_FUNCTIONS; + int function_stored = FALSE; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Acquire lock of callbacks table. Could be spin_lock_irq() if this + * routine were just called from base (not interrupt) level + */ + spin_lock_irqsave(&scc_callbacks_lock, irq_flags); + + /* Search through table looking for empty slot */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + if (scc_callbacks[i] == callback_func) { + if (function_stored) { + /* Saved duplicate earlier. Clear this later one. */ + scc_callbacks[i] = NULL; + } + /* Exactly one copy is now stored */ + return_status = SCC_RET_OK; + break; + } else if (scc_callbacks[i] == NULL && !function_stored) { + /* Found open slot. Save it and remember */ + scc_callbacks[i] = callback_func; + return_status = SCC_RET_OK; + function_stored = TRUE; + } + } + + /* Free the lock */ + spin_unlock_irqrestore(&scc_callbacks_lock, irq_flags); + + return return_status; +} /* scc_monitor_security_failure */ + +/*****************************************************************************/ +/* fn scc_stop_monitoring_security_failure() */ +/*****************************************************************************/ +void scc_stop_monitoring_security_failure(void callback_func(void)) +{ + unsigned long irq_flags; /* for IRQ save/restore */ + int i; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Acquire lock of callbacks table. Could be spin_lock_irq() if this + * routine were just called from base (not interrupt) level + */ + spin_lock_irqsave(&scc_callbacks_lock, irq_flags); + + /* Search every entry of the table for this function */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + if (scc_callbacks[i] == callback_func) { + scc_callbacks[i] = NULL; /* found instance - clear it out */ + break; + } + } + + /* Free the lock */ + spin_unlock_irqrestore(&scc_callbacks_lock, irq_flags); + + return; +} /* scc_stop_monitoring_security_failure */ + +/*****************************************************************************/ +/* fn scc_read_register() */ +/*****************************************************************************/ +scc_return_t scc_read_register(int register_offset, uint32_t * value) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t smn_status; + uint32_t scm_status; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* First layer of protection -- completely unaccessible SCC */ + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + + /* Second layer -- that offset is valid */ + if (register_offset != SMN_BITBANK_DECREMENT && /* write only! */ + check_register_offset(register_offset) == SCC_RET_OK) { + + /* Get current status / update local state */ + smn_status = scc_update_state(); + scm_status = SCC_READ_REGISTER(SCM_STATUS); + + /* + * Third layer - verify that the register being requested is + * available in the current state of the SCC. + */ + if ((return_status = + check_register_accessible(register_offset, + smn_status, + scm_status)) == + SCC_RET_OK) { + *value = SCC_READ_REGISTER(register_offset); + } + } + } + + return return_status; +} /* scc_read_register */ + +/*****************************************************************************/ +/* fn scc_write_register() */ +/*****************************************************************************/ +scc_return_t scc_write_register(int register_offset, uint32_t value) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t smn_status; + uint32_t scm_status; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* First layer of protection -- completely unaccessible SCC */ + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + + /* Second layer -- that offset is valid */ + if (!(register_offset == SCM_STATUS || /* These registers are */ + register_offset == SCM_CONFIGURATION || /* Read Only */ + register_offset == SMN_BIT_COUNT || + register_offset == SMN_TIMER) && + check_register_offset(register_offset) == SCC_RET_OK) { + + /* Get current status / update local state */ + smn_status = scc_update_state(); + scm_status = SCC_READ_REGISTER(SCM_STATUS); + + /* + * Third layer - verify that the register being requested is + * available in the current state of the SCC. + */ + if (check_register_accessible + (register_offset, smn_status, scm_status) == 0) { + SCC_WRITE_REGISTER(register_offset, value); + return_status = SCC_RET_OK; + } + } + } + + return return_status; +} /* scc_write_register() */ + +/****************************************************************************** + * + * Function Implementations - Internal + * + *****************************************************************************/ + +/*****************************************************************************/ +/* fn scc_irq() */ +/*****************************************************************************/ +/*! + * This is the interrupt handler for the SCC. + * + * This function checks the SMN Status register to see whether it + * generated the interrupt, then it checks the SCM Status register to + * see whether it needs attention. + * + * If an SMN Interrupt is active, then the SCC state set to failure, and + * #scc_perform_callbacks() is invoked to notify any interested parties. + * + * The SCM Interrupt should be masked, as this driver uses polling to determine + * when the SCM has completed a crypto or zeroing operation. Therefore, if the + * interrupt is active, the driver will just clear the interrupt and (re)mask. + * + */ +OS_DEV_ISR(scc_irq) +{ + uint32_t smn_status; + uint32_t scm_status; + int handled = 0; /* assume interrupt isn't from SMN */ +#if defined(USE_SMN_INTERRUPT) + int smn_irq = INT_SCC_SMN; /* SMN interrupt is on a line by itself */ +#elif defined (NO_SMN_INTERRUPT) + int smn_irq = -1; /* not wired to CPU at all */ +#else + int smn_irq = INT_SCC_SCM; /* SMN interrupt shares a line with SCM */ +#endif + + /* Update current state... This will perform callbacks... */ + smn_status = scc_update_state(); + + /* SMN is on its own interrupt line. Verify the IRQ was triggered + * before clearing the interrupt and marking it handled. */ + if ((os_dev_get_irq() == smn_irq) && + (smn_status & SMN_STATUS_SMN_STATUS_IRQ)) { + SCC_WRITE_REGISTER(SMN_COMMAND, SMN_COMMAND_CLEAR_INTERRUPT); + handled++; /* tell kernel that interrupt was handled */ + } + + /* Check on the health of the SCM */ + scm_status = SCC_READ_REGISTER(SCM_STATUS); + + /* The driver masks interrupts, so this should never happen. */ + if (os_dev_get_irq() == INT_SCC_SCM) { + /* but if it does, try to prevent it in the future */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + handled++; + } + + /* Any non-zero value of handled lets kernel know we got something */ + return IRQ_RETVAL(handled); +} + +/*****************************************************************************/ +/* fn scc_perform_callbacks() */ +/*****************************************************************************/ +/*! Perform callbacks registered by #scc_monitor_security_failure(). + * + * Make sure callbacks only happen once... Since there may be some reason why + * the interrupt isn't generated, this routine could be called from base(task) + * level. + * + * One at a time, go through #scc_callbacks[] and call any non-null pointers. + */ +static void scc_perform_callbacks(void) +{ + static int callbacks_performed = 0; + unsigned long irq_flags; /* for IRQ save/restore */ + int i; + + /* Acquire lock of callbacks table and callbacks_performed flag */ + spin_lock_irqsave(&scc_callbacks_lock, irq_flags); + + if (!callbacks_performed) { + callbacks_performed = 1; + + /* Loop over all of the entries in the table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + /* If not null, ... */ + if (scc_callbacks[i]) { + scc_callbacks[i] (); /* invoke the callback routine */ + } + } + } + + spin_unlock_irqrestore(&scc_callbacks_lock, irq_flags); + + return; +} + +/*****************************************************************************/ +/* fn copy_to_scc() */ +/*****************************************************************************/ +/*! + * Move data from possibly unaligned source and realign for SCC, possibly + * while calculating CRC. + * + * Multiple calls can be made to this routine (without intervening calls to + * #copy_from_scc(), as long as the sum total of bytes copied is a multiple of + * four (SCC native word size). + * + * @param[in] from Location in memory + * @param[out] to Location in SCC + * @param[in] count_bytes Number of bytes to copy + * @param[in,out] crc Pointer to CRC. Initial value must be + * #CRC_CCITT_START if this is the start of + * message. Output is the resulting (maybe + * partial) CRC. If NULL, no crc is calculated. + * + * @return Zero - success. Non-zero - SCM status bits defining failure. + */ +static uint32_t +copy_to_scc(const uint8_t * from, uint32_t to, unsigned long count_bytes, + uint16_t * crc) +{ + int i; + uint32_t scm_word; + uint16_t current_crc = 0; /* local copy for fast access */ + uint32_t status; + + pr_debug("SCC: copying %ld bytes to 0x%0x.\n", count_bytes, to); + + status = SCC_READ_REGISTER(SCM_ERROR_STATUS) & SCM_ACCESS_ERRORS; + if (status != 0) { + pr_debug + ("SCC copy_to_scc(): Error status detected (before copy):" + " %08x\n", status); + /* clear out errors left behind by somebody else */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, status); + } + + if (crc) { + current_crc = *crc; + } + + /* Initialize value being built for SCM. If we are starting 'clean', + * set it to zero. Otherwise pick up partial value which had been saved + * earlier. */ + if (SCC_BYTE_OFFSET(to) == 0) { + scm_word = 0; + } else { + scm_word = SCC_READ_REGISTER(SCC_WORD_PTR(to)); /* recover */ + } + + /* Now build up SCM words and write them out when each is full */ + for (i = 0; i < count_bytes; i++) { + uint8_t byte = *from++; /* value from plaintext */ + +#if defined(__BIG_ENDIAN) || defined(FSL_HAVE_DRYICE) + scm_word = (scm_word << 8) | byte; /* add byte to SCM word */ +#else + scm_word = (byte << 24) | (scm_word >> 8); +#endif + /* now calculate CCITT CRC */ + if (crc) { + CALC_CRC(byte, current_crc); + } + + to++; /* bump location in SCM */ + + /* check for full word */ + if (SCC_BYTE_OFFSET(to) == 0) { + SCC_WRITE_REGISTER((uint32_t) (to - 4), scm_word); /* write it out */ + } + } + + /* If at partial word after previous loop, save it in SCM memory for + next time. */ + if (SCC_BYTE_OFFSET(to) != 0) { + SCC_WRITE_REGISTER(SCC_WORD_PTR(to), scm_word); /* save */ + } + + /* Copy CRC back */ + if (crc) { + *crc = current_crc; + } + + status = SCC_READ_REGISTER(SCM_ERROR_STATUS) & SCM_ACCESS_ERRORS; + if (status != 0) { + pr_debug("SCC copy_to_scc(): Error status detected: %08x\n", + status); + /* Clear any/all bits. */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, status); + } + return status; +} + +/*****************************************************************************/ +/* fn copy_from_scc() */ +/*****************************************************************************/ +/*! + * Move data from aligned 32-bit source and place in (possibly unaligned) + * target, and maybe calculate CRC at the same time. + * + * Multiple calls can be made to this routine (without intervening calls to + * #copy_to_scc(), as long as the sum total of bytes copied is be a multiple + * of four. + * + * @param[in] from Location in SCC + * @param[out] to Location in memory + * @param[in] count_bytes Number of bytes to copy + * @param[in,out] crc Pointer to CRC. Initial value must be + * #CRC_CCITT_START if this is the start of + * message. Output is the resulting (maybe + * partial) CRC. If NULL, crc is not calculated. + * + * @return Zero - success. Non-zero - SCM status bits defining failure. + */ +static uint32_t +copy_from_scc(const uint32_t from, uint8_t * to, unsigned long count_bytes, + uint16_t * crc) +{ + uint32_t running_from = from; + uint32_t scm_word; + uint16_t current_crc = 0; /* local copy for fast access */ + uint32_t status; + pr_debug("SCC: copying %ld bytes from 0x%x.\n", count_bytes, from); + status = SCC_READ_REGISTER(SCM_ERROR_STATUS) & SCM_ACCESS_ERRORS; + if (status != 0) { + pr_debug + ("SCC copy_from_scc(): Error status detected (before copy):" + " %08x\n", status); + /* clear out errors left behind by somebody else */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, status); + } + + if (crc) { + current_crc = *crc; + } + + /* Read word which is sitting in SCM memory. Ignore byte offset */ + scm_word = SCC_READ_REGISTER(SCC_WORD_PTR(running_from)); + + /* If necessary, move the 'first' byte into place */ + if (SCC_BYTE_OFFSET(running_from) != 0) { +#if defined(__BIG_ENDIAN) || defined(FSL_HAVE_DRYICE) + scm_word <<= 8 * SCC_BYTE_OFFSET(running_from); +#else + scm_word >>= 8 * SCC_BYTE_OFFSET(running_from); +#endif + } + + /* Now build up SCM words and write them out when each is full */ + while (count_bytes--) { + uint8_t byte; /* value from plaintext */ + +#if defined(__BIG_ENDIAN) || defined(FSL_HAVE_DRYICE) + byte = (scm_word & 0xff000000) >> 24; /* pull byte out of SCM word */ + scm_word <<= 8; /* shift over to remove the just-pulled byte */ +#else + byte = (scm_word & 0xff); + scm_word >>= 8; /* shift over to remove the just-pulled byte */ +#endif + *to++ = byte; /* send byte to memory */ + + /* now calculate CRC */ + if (crc) { + CALC_CRC(byte, current_crc); + } + + running_from++; + /* check for empty word */ + if (count_bytes && SCC_BYTE_OFFSET(running_from) == 0) { + /* read one in */ + scm_word = SCC_READ_REGISTER((uint32_t) running_from); + } + } + + if (crc) { + *crc = current_crc; + } + + status = SCC_READ_REGISTER(SCM_ERROR_STATUS) & SCM_ACCESS_ERRORS; + if (status != 0) { + pr_debug("SCC copy_from_scc(): Error status detected: %08x\n", + status); + /* Clear any/all bits. */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, status); + } + + return status; +} + +/*****************************************************************************/ +/* fn scc_strip_padding() */ +/*****************************************************************************/ +/*! + * Remove padding from plaintext. Search backwards for #SCC_DRIVER_PAD_CHAR, + * verifying that each byte passed over is zero (0). Maximum number of bytes + * to examine is 8. + * + * @param[in] from Pointer to byte after end of message + * @param[out] count_bytes_stripped Number of padding bytes removed by this + * function. + * + * @return #SCC_RET_OK if all goes, well, #SCC_RET_FAIL if padding was + * not present. +*/ +static scc_return_t +scc_strip_padding(uint8_t * from, unsigned *count_bytes_stripped) +{ + int i = SCC_BLOCK_SIZE_BYTES(); + scc_return_t return_code = SCC_RET_VERIFICATION_FAILED; + + /* + * Search backwards looking for the magic marker. If it isn't found, + * make sure that a 0 byte is there in its place. Stop after the maximum + * amount of padding (8 bytes) has been searched); + */ + while (i-- > 0) { + if (*--from == SCC_DRIVER_PAD_CHAR) { + *count_bytes_stripped = SCC_BLOCK_SIZE_BYTES() - i; + return_code = SCC_RET_OK; + break; + } else if (*from != 0) { /* if not marker, check for 0 */ + pr_debug("SCC: Found non-zero interim pad: 0x%x\n", + *from); + break; + } + } + + return return_code; +} + +/*****************************************************************************/ +/* fn scc_update_state() */ +/*****************************************************************************/ +/*! + * Make certain SCC is still running. + * + * Side effect is to update #scc_availability and, if the state goes to failed, + * run #scc_perform_callbacks(). + * + * (If #SCC_BRINGUP is defined, bring SCC to secure state if it is found to be + * in health check state) + * + * @return Current value of #SMN_STATUS register. + */ +static uint32_t scc_update_state(void) +{ + uint32_t smn_status_register = SMN_STATE_FAIL; + int smn_state; + + /* if FAIL or UNIMPLEMENTED, don't bother */ + if (scc_availability == SCC_STATUS_CHECKING || + scc_availability == SCC_STATUS_OK) { + + smn_status_register = SCC_READ_REGISTER(SMN_STATUS); + smn_state = smn_status_register & SMN_STATUS_STATE_MASK; + +#ifdef SCC_BRINGUP + /* If in Health Check while booting, try to 'bringup' to Secure mode */ + if (scc_availability == SCC_STATUS_CHECKING && + smn_state == SMN_STATE_HEALTH_CHECK) { + /* Code up a simple algorithm for the ASC */ + SCC_WRITE_REGISTER(SMN_SEQUENCE_START, 0xaaaa); + SCC_WRITE_REGISTER(SMN_SEQUENCE_END, 0x5555); + SCC_WRITE_REGISTER(SMN_SEQUENCE_CHECK, 0x5555); + /* State should be SECURE now */ + smn_status_register = SCC_READ_REGISTER(SMN_STATUS); + smn_state = smn_status_register & SMN_STATUS_STATE_MASK; + } +#endif + + /* + * State should be SECURE or NON_SECURE for operation of the part. If + * FAIL, mark failed (i.e. limited access to registers). Any other + * state, mark unimplemented, as the SCC is unuseable. + */ + if (smn_state == SMN_STATE_SECURE + || smn_state == SMN_STATE_NON_SECURE) { + /* Healthy */ + scc_availability = SCC_STATUS_OK; + } else if (smn_state == SMN_STATE_FAIL) { + scc_availability = SCC_STATUS_FAILED; /* uh oh - unhealthy */ + scc_perform_callbacks(); + os_printk(KERN_ERR "SCC: SCC went into FAILED mode\n"); + } else { + /* START, ZEROIZE RAM, HEALTH CHECK, or unknown */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; /* unuseable */ + os_printk(KERN_ERR "SCC: SCC declared UNIMPLEMENTED\n"); + } + } + /* if availability is initial or ok */ + return smn_status_register; +} + +/*****************************************************************************/ +/* fn scc_init_ccitt_crc() */ +/*****************************************************************************/ +/*! + * Populate the partial CRC lookup table. + * + * @return none + * + */ +static void scc_init_ccitt_crc(void) +{ + int dividend; /* index for lookup table */ + uint16_t remainder; /* partial value for a given dividend */ + int bit; /* index into bits of a byte */ + + /* + * Compute the remainder of each possible dividend. + */ + for (dividend = 0; dividend < 256; ++dividend) { + /* + * Start with the dividend followed by zeros. + */ + remainder = dividend << (8); + + /* + * Perform modulo-2 division, a bit at a time. + */ + for (bit = 8; bit > 0; --bit) { + /* + * Try to divide the current data bit. + */ + if (remainder & 0x8000) { + remainder = (remainder << 1) ^ CRC_POLYNOMIAL; + } else { + remainder = (remainder << 1); + } + } + + /* + * Store the result into the table. + */ + scc_crc_lookup_table[dividend] = remainder; + } + +} /* scc_init_ccitt_crc() */ + +/*****************************************************************************/ +/* fn grab_config_values() */ +/*****************************************************************************/ +/*! + * grab_config_values() will read the SCM Configuration and SMN Status + * registers and store away version and size information for later use. + * + * @return The current value of the SMN Status register. + */ +static uint32_t scc_grab_config_values(void) +{ + uint32_t config_register; + uint32_t smn_status_register = SMN_STATE_FAIL; + + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + /* access the SCC - these are 'safe' registers */ + config_register = SCC_READ_REGISTER(SCM_CONFIGURATION); + pr_debug("SCC Driver: SCM config is 0x%08x\n", config_register); + + /* Get SMN status and update scc_availability */ + smn_status_register = scc_update_state(); + pr_debug("SCC Driver: SMN status is 0x%08x\n", + smn_status_register); + + /* save sizes and versions information for later use */ + scc_configuration.block_size_bytes = (config_register & + SCM_CFG_BLOCK_SIZE_MASK) + >> SCM_CFG_BLOCK_SIZE_SHIFT; + + scc_configuration.red_ram_size_blocks = (config_register & + SCM_CFG_RED_SIZE_MASK) + >> SCM_CFG_RED_SIZE_SHIFT; + + scc_configuration.black_ram_size_blocks = (config_register & + SCM_CFG_BLACK_SIZE_MASK) + >> SCM_CFG_BLACK_SIZE_SHIFT; + + scc_configuration.scm_version = (config_register + & SCM_CFG_VERSION_ID_MASK) + >> SCM_CFG_VERSION_ID_SHIFT; + + scc_configuration.smn_version = (smn_status_register & + SMN_STATUS_VERSION_ID_MASK) + >> SMN_STATUS_VERSION_ID_SHIFT; + + if (scc_configuration.scm_version != SCM_VERSION_1) { + scc_availability = SCC_STATUS_UNIMPLEMENTED; /* Unknown version */ + } + + scc_memory_size_bytes = (SCC_BLOCK_SIZE_BYTES() * + scc_configuration. + black_ram_size_blocks) + - SCM_NON_RESERVED_OFFSET; + + /* This last is for driver consumption only */ + scm_highest_memory_address = SCM_BLACK_MEMORY + + (SCC_BLOCK_SIZE_BYTES() * + scc_configuration.black_ram_size_blocks); + } + + return smn_status_register; +} /* grab_config_values */ + +/*****************************************************************************/ +/* fn setup_interrupt_handling() */ +/*****************************************************************************/ +/*! + * Register the SCM and SMN interrupt handlers. + * + * Called from #scc_init() + * + * @return 0 on success + */ +static int setup_interrupt_handling(void) +{ + int smn_error_code = -1; + int scm_error_code = -1; + + /* Disnable SCM interrupts */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + +#ifdef USE_SMN_INTERRUPT + /* Install interrupt service routine for SMN. */ + smn_error_code = os_register_interrupt(SCC_DRIVER_NAME, + INT_SCC_SMN, scc_irq); + if (smn_error_code != 0) { + os_printk + ("SCC Driver: Error installing SMN Interrupt Handler: %d\n", + smn_error_code); + } else { + smn_irq_set = 1; /* remember this for cleanup */ + /* Enable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND, + SMN_COMMAND_CLEAR_INTERRUPT | + SMN_COMMAND_ENABLE_INTERRUPT); + } +#else + smn_error_code = 0; /* no problems... will handle later */ +#endif + + /* + * Install interrupt service routine for SCM (or both together). + */ + scm_error_code = os_register_interrupt(SCC_DRIVER_NAME, + INT_SCC_SCM, scc_irq); + if (scm_error_code != 0) { +#ifndef MXC + os_printk + ("SCC Driver: Error installing SCM Interrupt Handler: %d\n", + scm_error_code); +#else + os_printk + ("SCC Driver: Error installing SCC Interrupt Handler: %d\n", + scm_error_code); +#endif + } else { + scm_irq_set = 1; /* remember this for cleanup */ +#if defined(USE_SMN_INTERRUPT) && !defined(NO_SMN_INTERRUPT) + /* Enable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND, + SMN_COMMAND_CLEAR_INTERRUPT | + SMN_COMMAND_ENABLE_INTERRUPT); +#endif + } + + /* Return an error if one was encountered */ + return scm_error_code ? scm_error_code : smn_error_code; +} /* setup_interrupt_handling */ + +/*****************************************************************************/ +/* fn scc_do_crypto() */ +/*****************************************************************************/ +/*! Have the SCM perform the crypto function. + * + * Set up length register, and the store @c scm_control into control register + * to kick off the operation. Wait for completion, gather status, clear + * interrupt / status. + * + * @param byte_count number of bytes to perform in this operation + * @param scm_control Bit values to be set in @c SCM_CONTROL register + * + * @return 0 on success, value of #SCM_ERROR_STATUS on failure + */ +static uint32_t scc_do_crypto(int byte_count, uint32_t scm_control) +{ + int block_count = byte_count / SCC_BLOCK_SIZE_BYTES(); + uint32_t crypto_status; + + /* clear any outstanding flags */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + + /* In length register, 0 means 1, etc. */ + SCC_WRITE_REGISTER(SCM_LENGTH, block_count - 1); + + /* set modes and kick off the operation */ + SCC_WRITE_REGISTER(SCM_CONTROL, scm_control); + + scc_wait_completion(); + + /* Mask for done and error bits */ + crypto_status = SCC_READ_REGISTER(SCM_STATUS) + & (SCM_STATUS_CIPHERING_DONE + | SCM_STATUS_LENGTH_ERROR | SCM_STATUS_INTERNAL_ERROR); + + /* Only done bit should be on */ + if (crypto_status != SCM_STATUS_CIPHERING_DONE) { + /* Replace with error status instead */ + crypto_status = SCC_READ_REGISTER(SCM_ERROR_STATUS); + pr_debug("SCM Failure: 0x%x\n", crypto_status); + if (crypto_status == 0) { + /* That came up 0. Turn on arbitrary bit to signal error. */ + crypto_status = SCM_ERR_INTERNAL_ERROR; + } + } else { + crypto_status = 0; + } + + pr_debug("SCC: Done waiting.\n"); + + /* Clear out any status. */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + + /* And clear any error status */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, 0); + + return crypto_status; +} + +/*****************************************************************************/ +/* fn scc_encrypt() */ +/*****************************************************************************/ +/*! + * Perform an encryption on the input. If @c verify_crc is true, a CRC must be + * calculated on the plaintext, and appended, with padding, before computing + * the ciphertext. + * + * @param[in] count_in_bytes Count of bytes of plaintext + * @param[in] data_in Pointer to the plaintext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing ciphertext + * @param[in] add_crc Flag for computing CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + */ +static scc_return_t +scc_encrypt(uint32_t count_in_bytes, const uint8_t * data_in, + uint32_t scm_control, + uint8_t * data_out, int add_crc, unsigned long *count_out_bytes) + +{ + scc_return_t return_code = SCC_RET_FAIL; /* initialised for failure */ + uint32_t input_bytes_left = count_in_bytes; /* local copy */ + uint32_t output_bytes_copied = 0; /* running total */ + uint32_t bytes_to_process; /* multi-purpose byte counter */ + uint16_t crc = CRC_CCITT_START; /* running CRC value */ + crc_t *crc_ptr = NULL; /* Reset if CRC required */ + /* byte address into SCM RAM */ + uint32_t scm_location = SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET; + /* free RED RAM */ + uint32_t scm_bytes_remaining = scc_memory_size_bytes; + /* CRC+padding holder */ + uint8_t padding_buffer[PADDING_BUFFER_MAX_BYTES]; + unsigned padding_byte_count = 0; /* Reset if padding required */ + uint32_t scm_error_status = 0; /* No known SCM error initially */ + uint32_t i; /* Counter for clear data loop */ + uint32_t dirty_bytes; /* Number of bytes of memory used + temporarily during encryption, + which need to be wiped after + completion of the operation. */ + + /* Set location of CRC and prepare padding bytes if required */ + if (add_crc != 0) { + crc_ptr = &crc; + padding_byte_count = SCC_BLOCK_SIZE_BYTES() + - (count_in_bytes + + CRC_SIZE_BYTES) % SCC_BLOCK_SIZE_BYTES(); + memcpy(padding_buffer + CRC_SIZE_BYTES, scc_block_padding, + padding_byte_count); + } + + /* Process remaining input or padding data */ + while (input_bytes_left > 0) { + + /* Determine how much work to do this pass */ + bytes_to_process = (input_bytes_left > scm_bytes_remaining) ? + scm_bytes_remaining : input_bytes_left; + + /* Copy plaintext into SCM RAM, calculating CRC if required */ + copy_to_scc(data_in, scm_location, bytes_to_process, crc_ptr); + + /* Adjust pointers & counters */ + input_bytes_left -= bytes_to_process; + data_in += bytes_to_process; + scm_location += bytes_to_process; + scm_bytes_remaining -= bytes_to_process; + + /* Add CRC and padding after the last byte is copied if required */ + if ((input_bytes_left == 0) && (crc_ptr != NULL)) { + + /* Copy CRC into padding buffer MSB first */ + padding_buffer[0] = (crc >> 8) & 0xFF; + padding_buffer[1] = crc & 0xFF; + + /* Reset pointers and counter */ + data_in = padding_buffer; + input_bytes_left = CRC_SIZE_BYTES + padding_byte_count; + crc_ptr = NULL; /* CRC no longer required */ + + /* Go round loop again to copy CRC and padding to SCM */ + continue; + } + + /* if no input and crc_ptr */ + /* Now have block-sized plaintext in SCM to encrypt */ + /* Encrypt plaintext; exit loop on error */ + bytes_to_process = scm_location - + (SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET); + + if (output_bytes_copied + bytes_to_process > *count_out_bytes) { + return_code = SCC_RET_INSUFFICIENT_SPACE; + scm_error_status = -1; /* error signal */ + pr_debug + ("SCC: too many ciphertext bytes for space available\n"); + break; + } + pr_debug("SCC: Starting encryption. %x for %d bytes (%p/%p)\n", + scm_control, bytes_to_process, + (void *)SCC_READ_REGISTER(SCM_RED_START), + (void *)SCC_READ_REGISTER(SCM_BLACK_START)); + scm_error_status = scc_do_crypto(bytes_to_process, scm_control); + if (scm_error_status != 0) { + break; + } + + /* Copy out ciphertext */ + copy_from_scc(SCM_BLACK_MEMORY + SCM_NON_RESERVED_OFFSET, + data_out, bytes_to_process, NULL); + + /* Adjust pointers and counters for next loop */ + output_bytes_copied += bytes_to_process; + data_out += bytes_to_process; + scm_location = SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET; + scm_bytes_remaining = scc_memory_size_bytes; + + } /* input_bytes_left > 0 */ + /* Clear all red and black memory used during ephemeral encryption */ + dirty_bytes = (count_in_bytes > scc_memory_size_bytes) ? + scc_memory_size_bytes : count_in_bytes; + + for (i = 0; i < dirty_bytes; i += 4) { + SCC_WRITE_REGISTER(SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET + i, + 0); + SCC_WRITE_REGISTER(SCM_BLACK_MEMORY + SCM_NON_RESERVED_OFFSET + + i, 0); + } + + /* If no SCM error, set OK status and save ouput byte count */ + if (scm_error_status == 0) { + return_code = SCC_RET_OK; + *count_out_bytes = output_bytes_copied; + } + + return return_code; +} /* scc_encrypt */ + +/*****************************************************************************/ +/* fn scc_decrypt() */ +/*****************************************************************************/ +/*! + * Perform a decryption on the input. If @c verify_crc is true, the last block + * (maybe the two last blocks) is special - it should contain a CRC and + * padding. These must be stripped and verified. + * + * @param[in] count_in_bytes Count of bytes of ciphertext + * @param[in] data_in Pointer to the ciphertext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing plaintext + * @param[in] verify_crc Flag for running CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + + */ +static scc_return_t +scc_decrypt(uint32_t count_in_bytes, const uint8_t * data_in, + uint32_t scm_control, + uint8_t * data_out, int verify_crc, unsigned long *count_out_bytes) +{ + scc_return_t return_code = SCC_RET_FAIL; + uint32_t bytes_left = count_in_bytes; /* local copy */ + uint32_t bytes_copied = 0; /* running total of bytes going to user */ + uint32_t bytes_to_copy = 0; /* Number in this encryption 'chunk' */ + uint16_t crc = CRC_CCITT_START; /* running CRC value */ + /* next target for ctext */ + uint32_t scm_location = SCM_BLACK_MEMORY + SCM_NON_RESERVED_OFFSET; + unsigned padding_byte_count; /* number of bytes of padding stripped */ + uint8_t last_two_blocks[2 * SCC_BLOCK_SIZE_BYTES()]; /* temp */ + uint32_t scm_error_status = 0; /* register value */ + uint32_t i; /* Counter for clear data loop */ + uint32_t dirty_bytes; /* Number of bytes of memory used + temporarily during decryption, + which need to be wiped after + completion of the operation. */ + + scm_control |= SCM_DECRYPT_MODE; + + if (verify_crc) { + /* Save last two blocks (if there are at least two) of ciphertext for + special treatment. */ + bytes_left -= SCC_BLOCK_SIZE_BYTES(); + if (bytes_left >= SCC_BLOCK_SIZE_BYTES()) { + bytes_left -= SCC_BLOCK_SIZE_BYTES(); + } + } + + /* Copy ciphertext into SCM BLACK memory */ + while (bytes_left && scm_error_status == 0) { + + /* Determine how much work to do this pass */ + if (bytes_left > (scc_memory_size_bytes)) { + bytes_to_copy = scc_memory_size_bytes; + } else { + bytes_to_copy = bytes_left; + } + + if (bytes_copied + bytes_to_copy > *count_out_bytes) { + scm_error_status = -1; + break; + } + copy_to_scc(data_in, scm_location, bytes_to_copy, NULL); + data_in += bytes_to_copy; /* move pointer */ + + pr_debug("SCC: Starting decryption of %d bytes.\n", + bytes_to_copy); + + /* Do the work, wait for completion */ + scm_error_status = scc_do_crypto(bytes_to_copy, scm_control); + + copy_from_scc(SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET, + data_out, bytes_to_copy, &crc); + bytes_copied += bytes_to_copy; + data_out += bytes_to_copy; + scm_location = SCM_BLACK_MEMORY + SCM_NON_RESERVED_OFFSET; + + /* Do housekeeping */ + bytes_left -= bytes_to_copy; + + } /* while bytes_left */ + + /* At this point, either the process is finished, or this is verify mode */ + + if (scm_error_status == 0) { + if (!verify_crc) { + *count_out_bytes = bytes_copied; + return_code = SCC_RET_OK; + } else { + /* Verify mode. There are one or two blocks of unprocessed + * ciphertext sitting at data_in. They need to be moved to the + * SCM, decrypted, searched to remove padding, then the plaintext + * copied back to the user (while calculating CRC, of course). + */ + + /* Calculate ciphertext still left */ + bytes_to_copy = count_in_bytes - bytes_copied; + + copy_to_scc(data_in, scm_location, bytes_to_copy, NULL); + data_in += bytes_to_copy; /* move pointer */ + + pr_debug("SCC: Finishing decryption (%d bytes).\n", + bytes_to_copy); + + /* Do the work, wait for completion */ + scm_error_status = + scc_do_crypto(bytes_to_copy, scm_control); + + if (scm_error_status == 0) { + /* Copy decrypted data back from SCM RED memory */ + copy_from_scc(SCM_RED_MEMORY + + SCM_NON_RESERVED_OFFSET, + last_two_blocks, bytes_to_copy, + NULL); + + /* (Plaintext) + crc + padding should be in temp buffer */ + if (scc_strip_padding + (last_two_blocks + bytes_to_copy, + &padding_byte_count) == SCC_RET_OK) { + bytes_to_copy -= + padding_byte_count + CRC_SIZE_BYTES; + + /* verify enough space in user buffer */ + if (bytes_copied + bytes_to_copy <= + *count_out_bytes) { + int i = 0; + + /* Move out last plaintext and calc CRC */ + while (i < bytes_to_copy) { + CALC_CRC(last_two_blocks + [i], crc); + *data_out++ = + last_two_blocks + [i++]; + bytes_copied++; + } + + /* Verify the CRC by running over itself */ + CALC_CRC(last_two_blocks + [bytes_to_copy], crc); + CALC_CRC(last_two_blocks + [bytes_to_copy + 1], + crc); + if (crc == 0) { + /* Just fine ! */ + *count_out_bytes = + bytes_copied; + return_code = + SCC_RET_OK; + } else { + return_code = + SCC_RET_VERIFICATION_FAILED; + pr_debug + ("SCC: CRC values are %04x, %02x%02x\n", + crc, + last_two_blocks + [bytes_to_copy], + last_two_blocks + [bytes_to_copy + + 1]); + } + } /* if space available */ + } /* if scc_strip_padding... */ + else { + /* bad padding means bad verification */ + return_code = + SCC_RET_VERIFICATION_FAILED; + } + } + /* scm_error_status == 0 */ + } /* verify_crc */ + } + + /* scm_error_status == 0 */ + /* Clear all red and black memory used during ephemeral decryption */ + dirty_bytes = (count_in_bytes > scc_memory_size_bytes) ? + scc_memory_size_bytes : count_in_bytes; + + for (i = 0; i < dirty_bytes; i += 4) { + SCC_WRITE_REGISTER(SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET + i, + 0); + SCC_WRITE_REGISTER(SCM_BLACK_MEMORY + SCM_NON_RESERVED_OFFSET + + i, 0); + } + return return_code; +} /* scc_decrypt */ + +/*****************************************************************************/ +/* fn scc_alloc_slot() */ +/*****************************************************************************/ +/*! + * Allocate a key slot to fit the requested size. + * + * @param value_size_bytes Size of the key or other secure data + * @param owner_id Value to tie owner to slot + * @param[out] slot Handle to access or deallocate slot + * + * @return SCC_RET_OK on success, SCC_RET_INSUFFICIENT_SPACE if not slots of + * requested size are available. + */ +scc_return_t +scc_alloc_slot(uint32_t value_size_bytes, uint64_t owner_id, uint32_t * slot) +{ + scc_return_t status = SCC_RET_FAIL; + unsigned long irq_flags; + + if (scc_availability != SCC_STATUS_OK) { + goto out; + } + /* ACQUIRE LOCK to prevent others from using SCC crypto */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + pr_debug("SCC: Allocating %d-byte slot for 0x%Lx\n", + value_size_bytes, owner_id); + + if ((value_size_bytes != 0) && (value_size_bytes <= SCC_MAX_KEY_SIZE)) { + int i; + + for (i = 0; i < SCC_KEY_SLOTS; i++) { + if (scc_key_info[i].status == 0) { + scc_key_info[i].owner_id = owner_id; + scc_key_info[i].length = value_size_bytes; + scc_key_info[i].status = 1; /* assigned! */ + *slot = i; + status = SCC_RET_OK; + break; /* exit 'for' loop */ + } + } + + if (status != SCC_RET_OK) { + status = SCC_RET_INSUFFICIENT_SPACE; + } else { + pr_debug("SCC: Allocated slot %d (0x%Lx)\n", i, + owner_id); + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + out: + return status; +} + +/*****************************************************************************/ +/* fn verify_slot_access() */ +/*****************************************************************************/ +inline static scc_return_t +verify_slot_access(uint64_t owner_id, uint32_t slot, uint32_t access_len) +{ + scc_return_t status = SCC_RET_FAIL; + if (scc_availability != SCC_STATUS_OK) { + goto out; + } + + if ((slot < SCC_KEY_SLOTS) && scc_key_info[slot].status + && (scc_key_info[slot].owner_id == owner_id) + && (access_len <= SCC_KEY_SLOT_SIZE)) { + status = SCC_RET_OK; + pr_debug("SCC: Verify on slot %d succeeded\n", slot); + } else { + if (slot >= SCC_KEY_SLOTS) { + pr_debug("SCC: Verify on bad slot (%d) failed\n", slot); + } else if (scc_key_info[slot].status) { + pr_debug("SCC: Verify on slot %d failed (%Lx) \n", slot, + owner_id); + } else { + pr_debug + ("SCC: Verify on slot %d failed: not allocated\n", + slot); + } + } + + out: + return status; +} + +scc_return_t +scc_verify_slot_access(uint64_t owner_id, uint32_t slot, uint32_t access_len) +{ + return verify_slot_access(owner_id, slot, access_len); +} + +/*****************************************************************************/ +/* fn scc_dealloc_slot() */ +/*****************************************************************************/ +scc_return_t scc_dealloc_slot(uint64_t owner_id, uint32_t slot) +{ + scc_return_t status; + unsigned long irq_flags; + int i; + + /* ACQUIRE LOCK to prevent others from using SCC crypto */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, 0); + + if (status == SCC_RET_OK) { + scc_key_info[slot].owner_id = 0; + scc_key_info[slot].status = 0; /* unassign */ + + /* clear old info */ + for (i = 0; i < SCC_KEY_SLOT_SIZE; i += 4) { + SCC_WRITE_REGISTER(SCM_RED_MEMORY + + scc_key_info[slot].offset + i, 0); + SCC_WRITE_REGISTER(SCM_BLACK_MEMORY + + scc_key_info[slot].offset + i, 0); + } + pr_debug("SCC: Deallocated slot %d\n", slot); + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} + +/*****************************************************************************/ +/* fn scc_load_slot() */ +/*****************************************************************************/ +/*! + * Load a value into a slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param key_data Data to load into the slot + * @param key_length Length, in bytes, of @c key_data to copy to SCC. + * + * @return SCC_RET_OK on success. SCC_RET_FAIL will be returned if slot + * specified cannot be accessed for any reason, or SCC_RET_INSUFFICIENT_SPACE + * if @c key_length exceeds the size of the slot. + */ +scc_return_t +scc_load_slot(uint64_t owner_id, uint32_t slot, const uint8_t * key_data, + uint32_t key_length) +{ + scc_return_t status; + unsigned long irq_flags; + + /* ACQUIRE LOCK to prevent others from using SCC crypto */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, key_length); + if ((status == SCC_RET_OK) && (key_data != NULL)) { + status = SCC_RET_FAIL; /* reset expectations */ + + if (key_length > SCC_KEY_SLOT_SIZE) { + pr_debug + ("SCC: scc_load_slot() rejecting key of %d bytes.\n", + key_length); + status = SCC_RET_INSUFFICIENT_SPACE; + } else { + if (copy_to_scc(key_data, + SCM_RED_MEMORY + + scc_key_info[slot].offset, key_length, + NULL)) { + pr_debug("SCC: RED copy_to_scc() failed for" + " scc_load_slot()\n"); + } else { + if ((key_length % 4) != 0) { + uint32_t zeros = 0; + + /* zero-pad to get remainder bytes in correct place */ + copy_to_scc((uint8_t *) & zeros, + SCM_RED_MEMORY + + + scc_key_info[slot].offset + + key_length, + 4 - (key_length % 4), NULL); + } + status = SCC_RET_OK; + } + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} /* scc_load_slot */ + +scc_return_t +scc_read_slot(uint64_t owner_id, uint32_t slot, uint32_t key_length, + uint8_t * key_data) +{ + scc_return_t status; + unsigned long irq_flags; + + /* ACQUIRE LOCK to prevent others from using SCC crypto */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, key_length); + if ((status == SCC_RET_OK) && (key_data != NULL)) { + status = SCC_RET_FAIL; /* reset expectations */ + + if (key_length > SCC_KEY_SLOT_SIZE) { + pr_debug + ("SCC: scc_read_slot() rejecting key of %d bytes.\n", + key_length); + status = SCC_RET_INSUFFICIENT_SPACE; + } else { + if (copy_from_scc + (SCM_RED_MEMORY + scc_key_info[slot].offset, + key_data, key_length, NULL)) { + pr_debug("SCC: RED copy_from_scc() failed for" + " scc_read_slot()\n"); + } else { + status = SCC_RET_OK; + } + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} /* scc_read_slot */ + +/*****************************************************************************/ +/* fn scc_encrypt_slot() */ +/*****************************************************************************/ +/*! + * Encrypt the key data stored in a slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param length Length, in bytes, of @c black_data + * @param black_data Location to store result of encrypting RED data in slot + * + * @return SCC_RET_OK on success, SCC_RET_FAIL if slot specified cannot be + * accessed for any reason. + */ +scc_return_t scc_encrypt_slot(uint64_t owner_id, uint32_t slot, + uint32_t length, uint8_t * black_data) +{ + unsigned long irq_flags; + scc_return_t status; + uint32_t crypto_status; + uint32_t slot_offset = + scc_key_info[slot].offset / SCC_BLOCK_SIZE_BYTES(); + + /* ACQUIRE LOCK to prevent others from using crypto or releasing slot */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, length); + if (status == SCC_RET_OK) { + SCC_WRITE_REGISTER(SCM_BLACK_START, slot_offset); + SCC_WRITE_REGISTER(SCM_RED_START, slot_offset); + + /* Use OwnerID as CBC IV to tie Owner to data */ + SCC_WRITE_REGISTER(SCM_INIT_VECTOR_0, *(uint32_t *) & owner_id); + SCC_WRITE_REGISTER(SCM_INIT_VECTOR_1, + *(((uint32_t *) & owner_id) + 1)); + + /* Set modes and kick off the encryption */ + crypto_status = scc_do_crypto(length, + SCM_CONTROL_START_CIPHER | + SCM_CBC_MODE); + + if (crypto_status != 0) { + pr_debug("SCM encrypt red crypto failure: 0x%x\n", + crypto_status); + } else { + + /* Give blob back to caller */ + if (!copy_from_scc + (SCM_BLACK_MEMORY + scc_key_info[slot].offset, + black_data, length, NULL)) { + status = SCC_RET_OK; + pr_debug("SCC: Encrypted slot %d\n", slot); + } + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} + +/*****************************************************************************/ +/* fn scc_decrypt_slot() */ +/*****************************************************************************/ +/*! + * Decrypt some black data and leave result in the slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param length Length, in bytes, of @c black_data + * @param black_data Location of data to dencrypt and store in slot + * + * @return SCC_RET_OK on success, SCC_RET_FAIL if slot specified cannot be + * accessed for any reason. + */ +scc_return_t scc_decrypt_slot(uint64_t owner_id, uint32_t slot, + uint32_t length, const uint8_t * black_data) +{ + unsigned long irq_flags; + scc_return_t status; + uint32_t crypto_status; + uint32_t slot_offset = + scc_key_info[slot].offset / SCC_BLOCK_SIZE_BYTES(); + + /* ACQUIRE LOCK to prevent others from using crypto or releasing slot */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, length); + if (status == SCC_RET_OK) { + status = SCC_RET_FAIL; /* reset expectations */ + + /* Place black key in to BLACK RAM and set up the SCC */ + copy_to_scc(black_data, + SCM_BLACK_MEMORY + scc_key_info[slot].offset, + length, NULL); + + SCC_WRITE_REGISTER(SCM_BLACK_START, slot_offset); + SCC_WRITE_REGISTER(SCM_RED_START, slot_offset); + + /* Use OwnerID as CBC IV to tie Owner to data */ + SCC_WRITE_REGISTER(SCM_INIT_VECTOR_0, *(uint32_t *) & owner_id); + SCC_WRITE_REGISTER(SCM_INIT_VECTOR_1, + *(((uint32_t *) & owner_id) + 1)); + + /* Set modes and kick off the decryption */ + crypto_status = scc_do_crypto(length, + SCM_CONTROL_START_CIPHER + | SCM_CBC_MODE | + SCM_DECRYPT_MODE); + + if (crypto_status != 0) { + pr_debug("SCM decrypt black crypto failure: 0x%x\n", + crypto_status); + } else { + status = SCC_RET_OK; + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} + +/*****************************************************************************/ +/* fn scc_get_slot_info() */ +/*****************************************************************************/ +/*! + * Determine address and value length for a give slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param address Location to store kernel address of slot data + * @param value_size_bytes Location to store allocated length of data in slot. + * May be NULL if value is not needed by caller. + * @param slot_size_bytes Location to store max length data in slot + * May be NULL if value is not needed by caller. + * + * @return SCC_RET_OK or error indication + */ +scc_return_t +scc_get_slot_info(uint64_t owner_id, uint32_t slot, uint32_t * address, + uint32_t * value_size_bytes, uint32_t * slot_size_bytes) +{ + scc_return_t status = verify_slot_access(owner_id, slot, 0); + + if (status == SCC_RET_OK) { + *address = + SCC_BASE + SCM_RED_MEMORY + scc_key_info[slot].offset; + if (value_size_bytes != NULL) { + *value_size_bytes = scc_key_info[slot].length; + } + if (slot_size_bytes != NULL) { + *slot_size_bytes = SCC_KEY_SLOT_SIZE; + } + } + + return status; +} + +/*****************************************************************************/ +/* fn scc_wait_completion() */ +/*****************************************************************************/ +/*! + * Poll looking for end-of-cipher indication. Only used + * if @c SCC_SCM_SLEEP is not defined. + * + * @internal + * + * On a Tahiti, crypto under 230 or so bytes is done after the first loop, all + * the way up to five sets of spins for 1024 bytes. (8- and 16-byte functions + * are done when we first look. Zeroizing takes one pass around. + */ +static void scc_wait_completion(void) +{ + int i = 0; + + /* check for completion by polling */ + while (!is_cipher_done() && (i++ < SCC_CIPHER_MAX_POLL_COUNT)) { + udelay(10); + } + pr_debug("SCC: Polled DONE %d times\n", i); +} /* scc_wait_completion() */ + +/*****************************************************************************/ +/* fn is_cipher_done() */ +/*****************************************************************************/ +/*! + * This function returns non-zero if SCM Status register indicates + * that a cipher has terminated or some other interrupt-generating + * condition has occurred. + */ +static int is_cipher_done(void) +{ + register uint32_t scm_status; + register int cipher_done; + + scm_status = SCC_READ_REGISTER(SCM_STATUS); + + /* + * Done when 'SCM is currently performing a function' bits are zero + */ + cipher_done = !(scm_status & (SCM_STATUS_ZEROIZING | + SCM_STATUS_CIPHERING)); + + return cipher_done; +} /* is_cipher_done() */ + +/*****************************************************************************/ +/* fn offset_within_smn() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SMN register set. + * + * @param[in] register_offset register offset of SMN. + * + * @return 1 if true, 0 if false (not within SMN) + */ +static inline int offset_within_smn(uint32_t register_offset) +{ + return register_offset >= SMN_STATUS && register_offset <= SMN_TIMER; +} + +/*****************************************************************************/ +/* fn offset_within_scm() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SCM register set. + * + * @param[in] register_offset Register offset of SCM + * + * @return 1 if true, 0 if false (not within SCM) + */ +static inline int offset_within_scm(uint32_t register_offset) +{ + return (register_offset >= SCM_RED_START) + && (register_offset < scm_highest_memory_address); + /* Although this would cause trouble for zeroize testing, this change would + * close a security whole which currently allows any kernel program to access + * any location in RED RAM. Perhaps enforce in non-SCC_DEBUG compiles? + && (register_offset <= SCM_INIT_VECTOR_1); */ +} + +/*****************************************************************************/ +/* fn check_register_accessible() */ +/*****************************************************************************/ +/*! + * Given the current SCM and SMN status, verify that access to the requested + * register should be OK. + * + * @param[in] register_offset register offset within SCC + * @param[in] smn_status recent value from #SMN_STATUS + * @param[in] scm_status recent value from #SCM_STATUS + * + * @return #SCC_RET_OK if ok, #SCC_RET_FAIL if not + */ +static scc_return_t +check_register_accessible(uint32_t register_offset, uint32_t smn_status, + uint32_t scm_status) +{ + int error_code = SCC_RET_FAIL; + + /* Verify that the register offset passed in is not among the verboten set + * if the SMN is in Fail mode. + */ + if (offset_within_smn(register_offset)) { + if ((smn_status & SMN_STATUS_STATE_MASK) == SMN_STATE_FAIL) { + if (!((register_offset == SMN_STATUS) || + (register_offset == SMN_COMMAND) || + (register_offset == SMN_DEBUG_DETECT_STAT))) { + pr_debug + ("SCC Driver: Note: Security State is in FAIL state.\n"); + } /* register not a safe one */ + else { + /* SMN is in FAIL, but register is a safe one */ + error_code = SCC_RET_OK; + } + } /* State is FAIL */ + else { + /* State is not fail. All registers accessible. */ + error_code = SCC_RET_OK; + } + } + /* offset within SMN */ + /* Not SCM register. Check for SCM busy. */ + else if (offset_within_scm(register_offset)) { + /* This is the 'cannot access' condition in the SCM */ + if ((scm_status & SCM_STATUS_BUSY) + /* these are always available - rest fail on busy */ + && !((register_offset == SCM_STATUS) || + (register_offset == SCM_ERROR_STATUS) || + (register_offset == SCM_INTERRUPT_CTRL) || + (register_offset == SCM_CONFIGURATION))) { + pr_debug + ("SCC Driver: Note: Secure Memory is in BUSY state.\n"); + } /* status is busy & register inaccessible */ + else { + error_code = SCC_RET_OK; + } + } + /* offset within SCM */ + return error_code; + +} /* check_register_accessible() */ + +/*****************************************************************************/ +/* fn check_register_offset() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SCC register set. + * + * @param[in] register_offset register offset of SMN. + * + * #SCC_RET_OK if ok, #SCC_RET_FAIL if not + */ +static scc_return_t check_register_offset(uint32_t register_offset) +{ + int return_value = SCC_RET_FAIL; + + /* Is it valid word offset ? */ + if (SCC_BYTE_OFFSET(register_offset) == 0) { + /* Yes. Is register within SCM? */ + if (offset_within_scm(register_offset)) { + return_value = SCC_RET_OK; /* yes, all ok */ + } + /* Not in SCM. Now look within the SMN */ + else if (offset_within_smn(register_offset)) { + return_value = SCC_RET_OK; /* yes, all ok */ + } + } + + return return_value; +} + +#ifdef SCC_REGISTER_DEBUG + +/*****************************************************************************/ +/* fn dbg_scc_read_register() */ +/*****************************************************************************/ +/*! + * Noisily read a 32-bit value to an SCC register. + * @param offset The address of the register to read. + * + * @return The register value + * */ +static uint32_t dbg_scc_read_register(uint32_t offset) +{ + uint32_t value; + + value = readl(scc_base + offset); + +#ifndef SCC_RAM_DEBUG /* print no RAM references */ + if ((offset < SCM_RED_MEMORY) || (offset >= scm_highest_memory_address)) { +#endif + pr_debug("SCC RD: 0x%4x : 0x%08x\n", offset, value); +#ifndef SCC_RAM_DEBUG + } +#endif + + return value; +} + +/*****************************************************************************/ +/* fn dbg_scc_write_register() */ +/*****************************************************************************/ +/* + * Noisily read a 32-bit value to an SCC register. + * @param offset The address of the register to written. + * + * @param value The new register value + */ +static void dbg_scc_write_register(uint32_t offset, uint32_t value) +{ + +#ifndef SCC_RAM_DEBUG /* print no RAM references */ + if ((offset < SCM_RED_MEMORY) || (offset >= scm_highest_memory_address)) { +#endif + pr_debug("SCC WR: 0x%4x : 0x%08x\n", offset, value); +#ifndef SCC_RAM_DEBUG + } +#endif + + (void)writel(value, scc_base + offset); +} + +#endif /* SCC_REGISTER_DEBUG */ diff --git a/drivers/mxc/security/mxc_scc_internals.h b/drivers/mxc/security/mxc_scc_internals.h new file mode 100644 index 000000000000..ffdbcf62514a --- /dev/null +++ b/drivers/mxc/security/mxc_scc_internals.h @@ -0,0 +1,498 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MXC_SCC_INTERNALS_H__ +#define __MXC_SCC_INTERNALS_H__ + +/*! + * @file mxc_scc_internals.h + * + * @brief This is intended to be the file which contains most or all of the code or + * changes need to port the driver. It also includes other definitions needed + * by the driver. + * + * This header file should only ever be included by scc_driver.c + * + * Compile-time flags minimally needed: + * + * @li Some sort of platform flag. + * @li Some start-of-SCC consideration, such as SCC_BASE_ADDR + * + * Some changes which could be made when porting this driver: + * #SCC_SPIN_COUNT + * + * @ingroup MXCSCC + */ +#if 0 +#include <linux/version.h> /* Current version Linux kernel */ +#include <linux/module.h> /* Basic support for loadable modules, + printk */ +#include <linux/init.h> /* module_init, module_exit */ +#include <linux/kernel.h> /* General kernel system calls */ +#include <linux/sched.h> /* for interrupt.h */ +#include <linux/spinlock.h> +#include <linux/interrupt.h> /* IRQ / interrupt definitions */ +#include <linux/io.h> /* ioremap() */ +#endif +#include <linux/mxc_scc_driver.h> + +/* Get handle on certain per-platform symbols */ +#ifdef TAHITI +#include <asm/arch/mx2.h> + +/* + * Mark the SCC as always there... as Tahiti is not officially supported by + * driver. Porting opportunity. + */ +#define SCC_ENABLED() (1) + +#elif defined(MXC) + +#include <mach/iim.h> +#include <mach/mxc_scc.h> + +#ifdef SCC_FUSE + +/* + * This macro is used to determine whether the SCC is enabled/available + * on the platform. This macro may need to be ported. + */ +#define SCC_ENABLED() ((SCC_FUSE & MXC_IIMHWV1_SCC_DISABLE) == 0) + +#else + +#define SCC_ENABLED() (1) + +#endif + +#else /* neither TAHITI nor MXC */ + +#error Do not understand target architecture + +#endif /* TAHITI */ + +/* Temporarily define compile-time flags to make Doxygen happy. */ +#ifdef DOXYGEN_HACK +/*! @addtogroup scccompileflags */ +/*! @{ */ + +/*! @def NO_SMN_INTERRUPT + * The SMN interrupt is not wired to the CPU at all. + */ +#define NO_SMN_INTERRUPT + +/*! + * Register an interrupt handler for the SMN as well as + * the SCM. In some implementations, the SMN is not connected at all (see + * #NO_SMN_INTERRUPT), and in others, it is on the same interrupt line as the + * SCM. When defining this flag, the SMN interrupt should be on a separate + * line from the SCM interrupt. + */ + +#define USE_SMN_INTERRUPT + +/*! + * Turn on generation of run-time operational, debug, and error messages + */ +#define SCC_DEBUG + +/*! + * Turn on generation of run-time logging of access to the SCM and SMN + * registers. + */ +#define SCC_REGISTER_DEBUG + +/*! + * Turn on generation of run-time logging of access to the SCM Red and + * Black memories. Will only work if #SCC_REGISTER_DEBUG is also defined. + */ +#define SCC_RAM_DEBUG + +/*! + * If the driver finds the SCC in HEALTH_CHECK state, go ahead and + * run a quick ASC to bring it to SECURE state. + */ +#define SCC_BRINGUP + +/*! + * Expected to come from platform header files or compile command line. + * This symbol must be the address of the SCC + */ +#define SCC_BASE + +/*! + * This must be the interrupt line number of the SCM interrupt. + */ +#define INT_SCM + +/*! + * if #USE_SMN_INTERRUPT is defined, this must be the interrupt line number of + * the SMN interrupt. + */ +#define INT_SMN + +/*! + * Define the number of Stored Keys which the SCC driver will make available. + * Value shall be from 0 to 20. Default is zero (0). + */ +#define SCC_KEY_SLOTS + +/*! + * Make sure that this flag is defined if compiling for a Little-Endian + * platform. Linux Kernel builds provide this flag. + */ +#define __LITTLE_ENDIAN + +/*! + * Make sure that this flag is defined if compiling for a Big-Endian platform. + * Linux Kernel builds provide this flag. + */ +#define __BIG_ENDIAN + +/*! + * Read a 32-bit register value from a 'peripheral'. Standard Linux/Unix + * macro. + * + * @param offset Bus address of register to be read + * + * @return The value of the register + */ +#define readl(offset) + +/*! + * Write a 32-bit value to a register in a 'peripheral'. Standard Linux/Unix + * macro. + * + * @param value The 32-bit value to store + * @param offset Bus address of register to be written + * + * return (none) + */ +#define writel(value,offset) + + /*! @} *//* end group scccompileflags */ + +#endif /* DOXYGEN_HACK */ + +/*! + * Define the number of Stored Keys which the SCC driver will make available. + * Value shall be from 0 to 20. Default is zero (0). + */ +#define SCC_KEY_SLOTS 20 + +#ifndef SCC_KEY_SLOTS +#define SCC_KEY_SLOTS 0 + +#else + +#if (SCC_KEY_SLOTS < 0) || (SCC_KEY_SLOTS > 20) +#error Bad value for SCC_KEY_SLOTS +#endif + +/*! + * Maximum length of key/secret value which can be stored in SCC. + */ +#define SCC_MAX_KEY_SIZE 32 + +/*! + * This is the size, in bytes, of each key slot, and therefore the maximum size + * of the wrapped key. + */ +#define SCC_KEY_SLOT_SIZE 32 + +/*! + * This is the offset into each RAM of the base of the area which is + * not used for Stored Keys. + */ +#define SCM_NON_RESERVED_OFFSET (SCC_KEY_SLOTS * SCC_KEY_SLOT_SIZE) + +#endif + +/* These come for free with Linux, but may need to be set in a port. */ +#ifndef __BIG_ENDIAN +#ifndef __LITTLE_ENDIAN +#error One of __LITTLE_ENDIAN or __BIG_ENDIAN must be #defined +#endif +#else +#ifdef __LITTLE_ENDIAN +#error Exactly one of __LITTLE_ENDIAN or __BIG_ENDIAN must be #defined +#endif +#endif + +#ifndef SCC_CALLBACK_SIZE +/*! The number of function pointers which can be stored in #scc_callbacks. + * Defaults to 4, can be overridden with compile-line argument. + */ +#define SCC_CALLBACK_SIZE 4 +#endif + +/*! Initial CRC value for CCITT-CRC calculation. */ +#define CRC_CCITT_START 0xFFFF + +#ifdef TAHITI + +/*! + * The SCC_BASE has to be SMN_BASE_ADDR on TAHITI, as the banks of + * registers are swapped in place. + */ +#define SCC_BASE SMN_BASE_ADDR + +/*! The interrupt number for the SCC (SCM only!) on Tahiti */ +#define INT_SCC_SCM 62 + +/*! Tahiti does not have the SMN interrupt wired to the CPU. */ +#define NO_SMN_INTERRUPT + +#endif /* TAHITI */ + +/*! Number of times to spin between polling of SCC while waiting for cipher + * or zeroizing function to complete. See also #SCC_CIPHER_MAX_POLL_COUNT. */ +#define SCC_SPIN_COUNT 1000 + +/*! Number of times to polling SCC while waiting for cipher + * or zeroizing function to complete. See also #SCC_SPIN_COUNT. */ +#define SCC_CIPHER_MAX_POLL_COUNT 100 + +/*! + * @def SCC_READ_REGISTER + * Read a 32-bit value from an SCC register. Macro which depends upon + * #scc_base. Linux readl()/writel() macros operate on 32-bit quantities, as + * do SCC register reads/writes. + * + * @param offset Register offset within SCC. + * + * @return The value from the SCC's register. + */ +#ifndef SCC_REGISTER_DEBUG +#define SCC_READ_REGISTER(offset) __raw_readl(scc_base+(offset)) +#else +#define SCC_READ_REGISTER(offset) dbg_scc_read_register(offset) +#endif + +/*! + * Write a 32-bit value to an SCC register. Macro depends upon #scc_base. + * Linux readl()/writel() macros operate on 32-bit quantities, as do SCC + * register reads/writes. + * + * @param offset Register offset within SCC. + * @param value 32-bit value to store into the register + * + * @return (void) + */ +#ifndef SCC_REGISTER_DEBUG +#define SCC_WRITE_REGISTER(offset,value) (void)__raw_writel(value, scc_base+(offset)) +#else +#define SCC_WRITE_REGISTER(offset,value) dbg_scc_write_register(offset, value) +#endif + +/*! + * Calculates the byte offset into a word + * @param bp The byte (char*) pointer + * @return The offset (0, 1, 2, or 3) + */ +#define SCC_BYTE_OFFSET(bp) ((uint32_t)(bp) % sizeof(uint32_t)) + +/*! + * Converts (by rounding down) a byte pointer into a word pointer + * @param bp The byte (char*) pointer + * @return The word (uint32_t) as though it were an aligned (uint32_t*) + */ +#define SCC_WORD_PTR(bp) (((uint32_t)(bp)) & ~(sizeof(uint32_t)-1)) + +/*! + * Determine number of bytes in an SCC block + * + * @return Bytes / block + */ +#define SCC_BLOCK_SIZE_BYTES() scc_configuration.block_size_bytes + +/*! + * Maximum number of additional bytes which may be added in CRC+padding mode. + */ +#define PADDING_BUFFER_MAX_BYTES (CRC_SIZE_BYTES + sizeof(scc_block_padding)) + +/*! + * Shorthand (clearer, anyway) for number of bytes in a CRC. + */ +#define CRC_SIZE_BYTES (sizeof(crc_t)) + +/*! + * The polynomial used in CCITT-CRC calculation + */ +#define CRC_POLYNOMIAL 0x1021 + +/*! + * Calculate CRC on one byte of data + * + * @param[in,out] running_crc A value of type crc_t where CRC is kept. This + * must be an rvalue and an lvalue. + * @param[in] byte_value The byte (uint8_t, char) to be put in the CRC + * + * @return none + */ +#define CALC_CRC(byte_value,running_crc) { \ + uint8_t data; \ + data = (0xff&(byte_value)) ^ (running_crc >> 8); \ + running_crc = scc_crc_lookup_table[data] ^ (running_crc << 8); \ +} + +/*! Value of 'beginning of padding' marker in driver-provided padding */ +#define SCC_DRIVER_PAD_CHAR 0x80 + +/*! Name of the driver. Used (on Linux, anyway) when registering interrupts */ +#define SCC_DRIVER_NAME "scc" + +/* Port -- these symbols are defined in Linux 2.6 and later. They are defined + * here for backwards compatibility because this started life as a 2.4 + * driver, and as a guide to portation to other platforms. + */ + +#if !defined(LINUX_VERSION_CODE) || LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + +#define irqreturn_t void /* Return type of an interrupt handler */ + +#define IRQ_HANDLED /* Would be '1' for handled -- as in return IRQ_HANDLED; */ + +#define IRQ_NONE /* would be '0' for not handled -- as in return IRQ_NONE; */ + +#define IRQ_RETVAL(x) /* Return x==0 (not handled) or non-zero (handled) */ + +#endif /* LINUX earlier than 2.5 */ + +/* These are nice to have around */ +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +/*! Provide a typedef for the CRC which can be used in encrypt/decrypt */ +typedef uint16_t crc_t; + +/*! Gives high-level view of state of the SCC */ +enum scc_status { + SCC_STATUS_INITIAL, /*!< State of driver before ever checking */ + SCC_STATUS_CHECKING, /*!< Transient state while driver loading */ + SCC_STATUS_UNIMPLEMENTED, /*!< SCC is non-existent or unuseable */ + SCC_STATUS_OK, /*!< SCC is in Secure or Default state */ + SCC_STATUS_FAILED /*!< In Failed state */ +}; + +/*! + * Information about a key slot. + */ +struct scc_key_slot { + uint64_t owner_id; /*!< Access control value. */ + uint32_t length; /*!< Length of value in slot. */ + uint32_t offset; /*!< Offset of value from start of each RAM. */ + uint32_t status; /*!< 0 = unassigned, 1 = assigned. */ +}; + +/* Forward-declare a number routines which are not part of user api */ +static int scc_init(void); +static void scc_cleanup(void); + +/* Forward defines of internal functions */ +OS_DEV_ISR(scc_irq); +/*! Perform callbacks registered by #scc_monitor_security_failure(). + * + * Make sure callbacks only happen once... Since there may be some reason why + * the interrupt isn't generated, this routine could be called from base(task) + * level. + * + * One at a time, go through #scc_callbacks[] and call any non-null pointers. + */ +static void scc_perform_callbacks(void); +static uint32_t copy_to_scc(const uint8_t * from, uint32_t to, + unsigned long count_bytes, uint16_t * crc); +static uint32_t copy_from_scc(const uint32_t from, uint8_t * to, + unsigned long count_bytes, uint16_t * crc); +static scc_return_t scc_strip_padding(uint8_t * from, + unsigned *count_bytes_stripped); +static uint32_t scc_update_state(void); +static void scc_init_ccitt_crc(void); +static uint32_t scc_grab_config_values(void); +static int setup_interrupt_handling(void); +/*! + * Perform an encryption on the input. If @c verify_crc is true, a CRC must be + * calculated on the plaintext, and appended, with padding, before computing + * the ciphertext. + * + * @param[in] count_in_bytes Count of bytes of plaintext + * @param[in] data_in Pointer to the plaintext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing ciphertext + * @param[in] add_crc Flag for computing CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + */ +static scc_return_t scc_encrypt(uint32_t count_in_bytes, + const uint8_t * data_in, + uint32_t scm_control, uint8_t * data_out, + int add_crc, unsigned long *count_out_bytes); + +/*! + * Perform a decryption on the input. If @c verify_crc is true, the last block + * (maybe the two last blocks) is special - it should contain a CRC and + * padding. These must be stripped and verified. + * + * @param[in] count_in_bytes Count of bytes of ciphertext + * @param[in] data_in Pointer to the ciphertext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing plaintext + * @param[in] verify_crc Flag for running CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + + */ +static scc_return_t scc_decrypt(uint32_t count_in_bytes, + const uint8_t * data_in, + uint32_t scm_control, uint8_t * data_out, + int verify_crc, unsigned long *count_out_bytes); + +static void scc_wait_completion(void); +static int is_cipher_done(void); +static scc_return_t check_register_accessible(uint32_t offset, + uint32_t smn_status, + uint32_t scm_status); +static scc_return_t check_register_offset(uint32_t offset); + +#ifdef SCC_REGISTER_DEBUG +static uint32_t dbg_scc_read_register(uint32_t offset); +static void dbg_scc_write_register(uint32_t offset, uint32_t value); +#endif + +/* For Linux kernel, export the API functions to other kernel modules */ +EXPORT_SYMBOL(scc_get_configuration); +EXPORT_SYMBOL(scc_zeroize_memories); +EXPORT_SYMBOL(scc_crypt); +EXPORT_SYMBOL(scc_set_sw_alarm); +EXPORT_SYMBOL(scc_monitor_security_failure); +EXPORT_SYMBOL(scc_stop_monitoring_security_failure); +EXPORT_SYMBOL(scc_read_register); +EXPORT_SYMBOL(scc_write_register); +EXPORT_SYMBOL(scc_alloc_slot); +EXPORT_SYMBOL(scc_dealloc_slot); +EXPORT_SYMBOL(scc_load_slot); +EXPORT_SYMBOL(scc_encrypt_slot); +EXPORT_SYMBOL(scc_decrypt_slot); +EXPORT_SYMBOL(scc_get_slot_info); + +/* Tell Linux where to invoke driver at boot/module load time */ +module_init(scc_init); +/* Tell Linux where to invoke driver on module unload */ +module_exit(scc_cleanup); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Device Driver for SCC (SMN/SCM)"); + +#endif /* __MXC_SCC_INTERNALS_H__ */ diff --git a/drivers/mxc/security/rng/Makefile b/drivers/mxc/security/rng/Makefile new file mode 100644 index 000000000000..7d3332e2e675 --- /dev/null +++ b/drivers/mxc/security/rng/Makefile @@ -0,0 +1,35 @@ +# Makefile for the Linux RNG Driver +# +# This makefile works within a kernel driver tree + + # Makefile for rng_driver + + +# Possible configurable paramters +CFG_RNG += -DRNGA_MAX_REQUEST_SIZE=32 + +#DBG_RNGA = -DRNGA_DEBUG +#DBG_RNGA += -DRNGA_REGISTER_DEBUG +#DBG_RNGA += -DRNGA_ENTROPY_DEBUG + +EXTRA_CFLAGS = -DLINUX_KERNEL $(CFG_RNG) $(DBG_RNG) + + +ifeq ($(CONFIG_MXC_RNG_TEST_DRIVER),y) +EXTRA_CFLAGS += -DRNG_REGISTER_PEEK_POKE +endif +ifeq ($(CONFIG_RNG_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif + + +EXTRA_CFLAGS += -Idrivers/mxc/security/rng/include -Idrivers/mxc/security/sahara2/include + +obj-$(CONFIG_MXC_SECURITY_RNG) += shw.o +#shw-objs := shw_driver.o shw_memory_mapper.o ../sahara2/fsl_shw_keystore.o +shw-objs := shw_driver.o shw_memory_mapper.o ../sahara2/fsl_shw_keystore.o \ + fsl_shw_sym.o fsl_shw_wrap.o shw_dryice.o des_key.o \ + shw_hash.o shw_hmac.o + +obj-$(CONFIG_MXC_SECURITY_RNG) += rng.o +rng-objs := rng_driver.o diff --git a/drivers/mxc/security/rng/des_key.c b/drivers/mxc/security/rng/des_key.c new file mode 100644 index 000000000000..62ff9b89eb3e --- /dev/null +++ b/drivers/mxc/security/rng/des_key.c @@ -0,0 +1,385 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +/*! + * @file des_key.c + * + * This file implements the function #fsl_shw_permute1_bytes(). + * + * The code was lifted from crypto++ v5.5.2, which is public domain code. The + * code to handle words instead of bytes was extensively modified from the byte + * version and then converted to handle one to three keys at once. + * + */ + +#include "shw_driver.h" +#ifdef DIAG_SECURITY_FUNC +#include "apihelp.h" +#endif + +#ifndef __KERNEL__ +#include <asm/types.h> +#include <linux/byteorder/little_endian.h> /* or whichever is proper for target arch */ +#endif + +#ifdef DEBUG +#undef DEBUG /* TEMPORARY */ +#endif + +#if defined(DEBUG) || defined(SELF_TEST) +static void DUMP_BYTES(const char *label, const uint8_t * data, int len) +{ + int i; + + printf("%s: ", label); + for (i = 0; i < len; i++) { + printf("%02X", data[i]); + if ((i % 8 == 0) && (i != 0)) { + printf("_"); /* key separator */ + } + } + printf("\n"); +} + +static void DUMP_WORDS(const char *label, const uint32_t * data, int len) +{ + int i, j; + + printf("%s: ", label); + /* Dump the words in reverse order, so that they are intelligible */ + for (i = len - 1; i >= 0; i--) { + for (j = 3; j >= 0; j--) { + uint32_t word = data[i]; + printf("%02X", (word >> ((j * 8)) & 0xff)); + if ((i != 0) && ((((i) * 4 + 5 + j) % 7) == 5)) + printf("_"); /* key separator */ + } + printf("|"); /* word separator */ + } + printf("\n"); +} +#else +#define DUMP_BYTES(label, data,len) +#define DUMP_WORDS(label, data,len) +#endif + +/*! + * permuted choice table (key) + * + * Note that this table has had one subtracted from each element so that the + * code doesn't have to do it. + */ +static const uint8_t pc1[] = { + 56, 48, 40, 32, 24, 16, 8, + 0, 57, 49, 41, 33, 25, 17, + 9, 1, 58, 50, 42, 34, 26, + 18, 10, 2, 59, 51, 43, 35, + 62, 54, 46, 38, 30, 22, 14, + 6, 61, 53, 45, 37, 29, 21, + 13, 5, 60, 52, 44, 36, 28, + 20, 12, 4, 27, 19, 11, 3, +}; + +/*! bit 0 is left-most in byte */ +static const int bytebit[] = { + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +/*! + * Convert a 3-key 3DES key into the first-permutation 168-bit version. + * + * This is the format of the input key: + * + * @verbatim + BIT: |191 128|127 64|63 0| + BYTE: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | + KEY: | 0 | 1 | 2 | + @endverbatim + * + * This is the format of the output key: + * + * @verbatim + BIT: |167 112|111 56|55 0| + BYTE: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | + KEY: | 1 | 2 | 3 | + @endverbatim + * + * @param[in] key bytes of 3DES key + * @param[out] permuted_key 21 bytes of permuted key + * @param[in] key_count How many DES keys (2 or 3) + */ +void fsl_shw_permute1_bytes(const uint8_t * key, uint8_t * permuted_key, + int key_count) +{ + int i; + int j; + int l; + int m; + + DUMP_BYTES("Input key", key, 8 * key_count); + + /* For each individual sub-key */ + for (i = 0; i < 3; i++) { + DUMP_BYTES("(key)", key, 8); + memset(permuted_key, 0, 7); + /* For each bit of key */ + for (j = 0; j < 56; j++) { /* convert pc1 to bits of key */ + l = pc1[j]; /* integer bit location */ + m = l & 07; /* find bit */ + permuted_key[j >> 3] |= (((key[l >> 3] & /* find which key byte l is in */ + bytebit[m]) /* and which bit of that byte */ + ? 0x80 : 0) >> (j % 8)); /* and store 1-bit result */ + } + switch (i) { + case 0: + if (key_count != 1) + key += 8; /* move on to second key */ + break; + case 1: + if (key_count == 2) + key -= 8; /* go back to first key */ + else if (key_count == 3) + key += 8; /* move on to third key */ + break; + default: + break; + } + permuted_key += 7; + } + DUMP_BYTES("Output key (bytes)", permuted_key - 21, 21); +} + +#ifdef SELF_TEST +const uint8_t key1_in[] = { + /* FE01FE01FE01FE01_01FE01FE01FE01FE_FEFE0101FEFE0101 */ + 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, + 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, + 0xFE, 0xFE, 0x01, 0x01, 0xFE, 0xFE, 0x01, 0x01 +}; + +const uint32_t key1_word_in[] = { + 0xFE01FE01, 0xFE01FE01, + 0x01FE01FE, 0x01FE01FE, + 0xFEFE0101, 0xFEFE0101 +}; + +uint8_t exp_key1_out[] = { + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33 +}; + +uint32_t exp_word_key1_out[] = { + 0x33333333, 0xAA333333, 0xAAAAAAAA, 0x5555AAAA, + 0x55555555, 0x00000055, +}; + +const uint8_t key2_in[] = { + 0xEF, 0x10, 0xBB, 0xA4, 0x23, 0x49, 0x42, 0x58, + 0x01, 0x28, 0x01, 0x4A, 0x10, 0xE4, 0x03, 0x59, + 0xFE, 0x84, 0x30, 0x29, 0x8E, 0xF1, 0x10, 0x5A +}; + +const uint32_t key2_word_in[] = { + 0xEF10BBA4, 0x23494258, + 0x0128014A, 0x10E40359, + 0xFE843029, 0x8EF1105A +}; + +uint8_t exp_key2_out[] = { + 0x0D, 0xE1, 0x1D, 0x85, 0x50, 0x9A, 0x56, 0x20, + 0xA8, 0x22, 0x94, 0x82, 0x08, 0xA0, 0x33, 0xA1, + 0x2D, 0xE9, 0x11, 0x39, 0x95 +}; + +uint32_t exp_word_key2_out[] = { + 0xE9113995, 0xA033A12D, 0x22948208, 0x9A5620A8, + 0xE11D8550, 0x0000000D +}; + +const uint8_t key3_in[] = { + 0x3F, 0xE9, 0x49, 0x4B, 0x67, 0x57, 0x07, 0x3C, + 0x89, 0x77, 0x73, 0x0C, 0xA0, 0x05, 0x41, 0x69, + 0xB3, 0x7C, 0x98, 0xD8, 0xC9, 0x35, 0x57, 0x19 +}; + +const uint32_t key3_word_in[] = { + 0xEF10BBA4, 0x23494258, + 0x0128014A, 0x10E40359, + 0xFE843029, 0x8EF1105A +}; + +uint8_t exp_key3_out[] = { + 0x02, 0x3E, 0x93, 0xA7, 0x9F, 0x18, 0xF1, 0x11, + 0xC6, 0x96, 0x00, 0x62, 0xA8, 0x96, 0x02, 0x3E, + 0x93, 0xA7, 0x9F, 0x18, 0xF1 +}; + +uint32_t exp_word_key3_out[] = { + 0xE9113995, 0xA033A12D, 0x22948208, 0x9A5620A8, + 0xE11D8550, 0x0000000D +}; + +const uint8_t key4_in[] = { + 0x3F, 0xE9, 0x49, 0x4B, 0x67, 0x57, 0x07, 0x3C, + 0x89, 0x77, 0x73, 0x0C, 0xA0, 0x05, 0x41, 0x69, +}; + +const uint32_t key4_word_in[] = { + 0xEF10BBA4, 0x23494258, + 0x0128014A, 0x10E40359, + 0xFE843029, 0x8EF1105A +}; + +const uint8_t key5_in[] = { + 0x3F, 0xE9, 0x49, 0x4B, 0x67, 0x57, 0x07, 0x3C, + 0x89, 0x77, 0x73, 0x0C, 0xA0, 0x05, 0x41, 0x69, + 0x3F, 0xE9, 0x49, 0x4B, 0x67, 0x57, 0x07, 0x3C, +}; + +uint8_t exp_key4_out[] = { + 0x0D, 0xE1, 0x1D, 0x85, 0x50, 0x9A, 0x56, 0x20, + 0xA8, 0x22, 0x94, 0x82, 0x08, 0xA0, 0x33, 0xA1, + 0x2D, 0xE9, 0x11, 0x39, 0x95 +}; + +uint32_t exp_word_key4_out[] = { + 0xE9113995, 0xA033A12D, 0x22948208, 0x9A5620A8, + 0xE11D8550, 0x0000000D +}; + +const uint8_t key6_in[] = { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 +}; + +uint8_t exp_key6_out[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +uint32_t exp_word_key6_out[] = { + 0x00000000, 0x0000000, 0x0000000, 0x00000000, + 0x00000000, 0x0000000 +}; + +const uint8_t key7_in[] = { + /* 01FE01FE01FE01FE_FE01FE01FE01FE01_0101FEFE0101FEFE */ + /* 0101FEFE0101FEFE_FE01FE01FE01FE01_01FE01FE01FE01FE */ + 0x01, 0x01, 0xFE, 0xFE, 0x01, 0x01, 0xFE, 0xFE, + 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, + 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, +}; + +uint8_t exp_key7_out[] = { + 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa +}; + +uint32_t exp_word_key7_out[] = { + 0xcccccccc, 0x55cccccc, 0x55555555, 0xaaaa5555, + 0xaaaaaaaa, 0x000000aa +}; + +int run_test(const uint8_t * key_in, + const int key_count, + const uint32_t * key_word_in, + const uint8_t * exp_bytes_key_out, + const uint32_t * exp_word_key_out) +{ + uint8_t key_out[22]; + uint32_t word_key_out[6]; + int failed = 0; + + memset(key_out, 0x42, 22); + fsl_shw_permute1_bytes(key_in, key_out, key_count); + if (memcmp(key_out, exp_bytes_key_out, 21) != 0) { + printf("bytes_to_bytes: ERROR: \n"); + DUMP_BYTES("key_in", key_in, 8 * key_count); + DUMP_BYTES("key_out", key_out, 21); + DUMP_BYTES("exp_out", exp_bytes_key_out, 21); + failed |= 1; + } else if (key_out[21] != 0x42) { + printf("bytes_to_bytes: ERROR: Buffer overflow 0x%02x\n", + (int)key_out[21]); + } else { + printf("bytes_to_bytes: OK\n"); + } +#if 0 + memset(word_key_out, 0x42, 21); + fsl_shw_permute1_bytes_to_words(key_in, word_key_out, key_count); + if (memcmp(word_key_out, exp_word_key_out, 21) != 0) { + printf("bytes_to_words: ERROR: \n"); + DUMP_BYTES("key_in", key_in, 8 * key_count); + DUMP_WORDS("key_out", word_key_out, 6); + DUMP_WORDS("exp_out", exp_word_key_out, 6); + failed |= 1; + } else { + printf("bytes_to_words: OK\n"); + } + + if (key_word_in != NULL) { + memset(word_key_out, 0x42, 21); + fsl_shw_permute1_words_to_words(key_word_in, word_key_out); + if (memcmp(word_key_out, exp_word_key_out, 21) != 0) { + printf("words_to_words: ERROR: \n"); + DUMP_BYTES("key_in", key_in, 24); + DUMP_WORDS("key_out", word_key_out, 6); + DUMP_WORDS("exp_out", exp_word_key_out, 6); + failed |= 1; + } else { + printf("words_to_words: OK\n"); + } + } +#endif + + return failed; +} /* end fn run_test */ + +int main() +{ + int failed = 0; + + printf("key1\n"); + failed |= + run_test(key1_in, 3, key1_word_in, exp_key1_out, exp_word_key1_out); + printf("\nkey2\n"); + failed |= + run_test(key2_in, 3, key2_word_in, exp_key2_out, exp_word_key2_out); + printf("\nkey3\n"); + failed |= run_test(key3_in, 3, NULL, exp_key3_out, exp_word_key3_out); + printf("\nkey4\n"); + failed |= run_test(key4_in, 2, NULL, exp_key4_out, exp_word_key4_out); + printf("\nkey5\n"); + failed |= run_test(key5_in, 3, NULL, exp_key4_out, exp_word_key4_out); + printf("\nkey6 - 3\n"); + failed |= run_test(key6_in, 3, NULL, exp_key6_out, exp_word_key6_out); + printf("\nkey6 - 2\n"); + failed |= run_test(key6_in, 2, NULL, exp_key6_out, exp_word_key6_out); + printf("\nkey6 - 1\n"); + failed |= run_test(key6_in, 1, NULL, exp_key6_out, exp_word_key6_out); + printf("\nkey7\n"); + failed |= run_test(key7_in, 3, NULL, exp_key7_out, exp_word_key7_out); + printf("\n"); + + if (failed != 0) { + printf("TEST FAILED\n"); + } + return failed; +} + +#endif /* SELF_TEST */ diff --git a/drivers/mxc/security/rng/fsl_shw_hash.c b/drivers/mxc/security/rng/fsl_shw_hash.c new file mode 100644 index 000000000000..60572862a0fa --- /dev/null +++ b/drivers/mxc/security/rng/fsl_shw_hash.c @@ -0,0 +1,84 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +/*! + * @file fsl_shw_hash.c + * + * This file implements Cryptographic Hashing functions of the FSL SHW API + * for Sahara. This does not include HMAC. + */ + +#include "shw_driver.h" + +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-005 */ +/*! + * Hash a stream of data with a cryptographic hash algorithm. + * + * The flags in the @a hash_ctx control the operation of this function. + * + * Hashing functions work on 64 octets of message at a time. Therefore, when + * any partial hashing of a long message is performed, the message @a length of + * each segment must be a multiple of 64. When ready to + * #FSL_HASH_FLAGS_FINALIZE the hash, the @a length may be any value. + * + * With the #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_FINALIZE flags on, a + * one-shot complete hash, including padding, will be performed. The @a length + * may be any value. + * + * The first octets of a data stream can be hashed by setting the + * #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_SAVE flags. The @a length must be + * a multiple of 64. + * + * The flag #FSL_HASH_FLAGS_LOAD is used to load a context previously saved by + * #FSL_HASH_FLAGS_SAVE. The two in combination will allow a (multiple-of-64 + * octets) 'middle sequence' of the data stream to be hashed with the + * beginning. The @a length must again be a multiple of 64. + * + * Since the flag #FSL_HASH_FLAGS_LOAD is used to load a context previously + * saved by #FSL_HASH_FLAGS_SAVE, the #FSL_HASH_FLAGS_LOAD and + * #FSL_HASH_FLAGS_FINALIZE flags, used together, can be used to finish the + * stream. The @a length may be any value. + * + * If the user program wants to do the padding for the hash, it can leave off + * the #FSL_HASH_FLAGS_FINALIZE flag. The @a length must then be a multiple of + * 64 octets. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] hash_ctx Hashing algorithm and state of the cipher. + * @param msg Pointer to the data to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result If not null, pointer to where to store the hash + * digest. + * @param result_len Number of octets to store in @a result. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)hash_ctx; + (void)msg; + (void)length; + (void)result; + (void)result_len; + + return ret; +} diff --git a/drivers/mxc/security/rng/fsl_shw_hmac.c b/drivers/mxc/security/rng/fsl_shw_hmac.c new file mode 100644 index 000000000000..4d2a104afcd8 --- /dev/null +++ b/drivers/mxc/security/rng/fsl_shw_hmac.c @@ -0,0 +1,83 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +/*! + * @file fsl_shw_hmac.c + * + * This file implements Hashed Message Authentication Code functions of the FSL + * SHW API. + */ + +#include "shw_driver.h" + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-001 */ +/*! + * Get the precompute information + * + * + * @param user_ctx + * @param key_info + * @param hmac_ctx + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx) +{ + fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)hmac_ctx; + + return status; +} + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-002 */ +/*! + * Get the hmac + * + * + * @param user_ctx Info for acquiring memory + * @param key_info + * @param hmac_ctx + * @param msg + * @param length + * @param result + * @param result_len + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)hmac_ctx; + (void)msg; + (void)length; + (void)result; + (void)result_len; + + return status; +} diff --git a/drivers/mxc/security/rng/fsl_shw_rand.c b/drivers/mxc/security/rng/fsl_shw_rand.c new file mode 100644 index 000000000000..aa4d426c70fe --- /dev/null +++ b/drivers/mxc/security/rng/fsl_shw_rand.c @@ -0,0 +1,122 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +/*! + * @file fsl_shw_rand.c + * + * This file implements Random Number Generation functions of the FSL SHW API + * in USER MODE for talking to a standalone RNGA/RNGC device driver. + * + * It contains the fsl_shw_get_random() and fsl_shw_add_entropy() functions. + * + * These routines will build a request block and pass it to the SHW driver. + */ + +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/fcntl.h> +#include <sys/ioctl.h> +#include <signal.h> + +#ifdef FSL_DEBUG +#include <stdio.h> +#include <errno.h> +#include <string.h> +#endif /* FSL_DEBUG */ + +#include "shw_driver.h" + +extern fsl_shw_return_t validate_uco(fsl_shw_uco_t * uco); + +#if defined(FSL_HAVE_RNGA) || defined(FSL_HAVE_RNGB) || defined(FSL_HAVE_RNGC) + +/* REQ-S2LRD-PINTFC-API-BASIC-RNG-002 */ +fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + /* perform a sanity check / update uco */ + ret = validate_uco(user_ctx); + if (ret == FSL_RETURN_OK_S) { + struct get_random_req *req = malloc(sizeof(*req)); + + if (req == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + } else { + + init_req(&req->hdr, user_ctx); + req->size = length; + req->random = data; + + ret = + send_req(SHW_USER_REQ_GET_RANDOM, &req->hdr, + user_ctx); + } + } + + return ret; +} + +fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + /* perform a sanity check on the uco */ + ret = validate_uco(user_ctx); + if (ret == FSL_RETURN_OK_S) { + struct add_entropy_req *req = malloc(sizeof(*req)); + + if (req == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + } else { + init_req(&req->hdr, user_ctx); + req->size = length; + req->entropy = data; + + ret = + send_req(SHW_USER_REQ_ADD_ENTROPY, &req->hdr, + user_ctx); + } + } + + return ret; +} + +#else /* no H/W RNG block */ + +fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + + (void)user_ctx; + (void)length; + (void)data; + + return FSL_RETURN_ERROR_S; +} + +fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + + (void)user_ctx; + (void)length; + (void)data; + + return FSL_RETURN_ERROR_S; +} +#endif diff --git a/drivers/mxc/security/rng/fsl_shw_sym.c b/drivers/mxc/security/rng/fsl_shw_sym.c new file mode 100644 index 000000000000..bbeb1e24bc48 --- /dev/null +++ b/drivers/mxc/security/rng/fsl_shw_sym.c @@ -0,0 +1,317 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +/*! + * @file fsl_shw_sym.c + * + * This file implements the Symmetric Cipher functions of the FSL SHW API. Its + * features are limited to what can be done with the combination of SCC and + * DryIce. + */ +#include "fsl_platform.h" +#include "shw_driver.h" + +#if defined(__KERNEL__) && defined(FSL_HAVE_DRYICE) + +#include "../dryice.h" +#include <linux/mxc_scc_driver.h> +#ifdef DIAG_SECURITY_FUNC +#include "apihelp.h" +#endif + +#include <diagnostic.h> + +#define SYM_DECRYPT 0 +#define SYM_ENCRYPT 1 + +extern fsl_shw_return_t shw_convert_pf_key(fsl_shw_pf_key_t shw_pf_key, + di_key_t * di_keyp); + +/*! 'Initial' IV for presence of FSL_SYM_CTX_LOAD flag */ +static uint8_t zeros[8] = { + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/*! + * Common function for encryption and decryption + * + * This is for a device with DryIce. + * + * A key must either refer to a 'pure' HW key, or, if PRG or PRG_IIM, + * established, then that key will be programmed. Then, the HW_key in the + * object will be selected. After this setup, the ciphering will be performed + * by calling the SCC driver.. + * + * The function 'releases' the reservations before it completes. + */ +fsl_shw_return_t do_symmetric(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + int encrypt, + uint32_t length, + const uint8_t * in, uint8_t * out) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + int key_selected = 0; + uint8_t *iv = NULL; + unsigned long count_out = length; + di_key_t di_key = DI_KEY_PK; /* default for user key */ + di_key_t di_key_orig; /* currently selected key */ + di_key_t selected_key = -1; + di_return_t di_code; + scc_return_t scc_code; + + /* For now, only blocking mode calls are supported */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* No software keys allowed */ + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + ret = FSL_RETURN_BAD_FLAG_S; + } + + /* The only algorithm the SCC supports */ + if (key_info->algorithm != FSL_KEY_ALG_TDES) { + ret = FSL_RETURN_BAD_ALGORITHM_S; + goto out; + } + + /* Validate key length */ + if ((key_info->key_length != 16) + && (key_info->key_length != 21) + && (key_info->key_length != 24)) { + ret = FSL_RETURN_BAD_KEY_LENGTH_S; + goto out; + } + + /* Validate data is multiple of DES/TDES block */ + if ((length & 7) != 0) { + ret = FSL_RETURN_BAD_DATA_LENGTH_S; + goto out; + } + + /* Do some setup according to where the key lives */ + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + if ((key_info->pf_key != FSL_SHW_PF_KEY_PRG) + && (key_info->pf_key != FSL_SHW_PF_KEY_IIM_PRG)) { + ret = FSL_RETURN_ERROR_S; + } + } else if (key_info->flags & FSL_SKO_KEY_PRESENT) { + ret = FSL_RETURN_BAD_FLAG_S; + } else if (key_info->flags & FSL_SKO_KEY_SELECT_PF_KEY) { + /* + * No key present or established, just refer to HW + * as programmed. + */ + } else { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* Now make proper selection */ + ret = shw_convert_pf_key(key_info->pf_key, &di_key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Determine the current DI key selection */ + di_code = dryice_check_key(&di_key_orig); + if (di_code != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Could not save current DI key state: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + + /* If the requested DI key is already selected, don't re-select it. */ + if (di_key != di_key_orig) { + di_code = dryice_select_key(di_key, 0); + if (di_code != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Error from select_key: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_INTERNAL_ERROR_S; + goto out; + } + } + key_selected = 1; + + /* Verify that we are using the key we want */ + di_code = dryice_check_key(&selected_key); + if (di_code != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Error from check_key: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_INTERNAL_ERROR_S; + goto out; + } + + if (di_key != selected_key) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Wrong key in use: %d instead of %d\n\n", + selected_key, di_key); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + + if (sym_ctx->mode == FSL_SYM_MODE_CBC) { + if ((sym_ctx->flags & FSL_SYM_CTX_LOAD) + && !(sym_ctx->flags & FSL_SYM_CTX_INIT)) { + iv = sym_ctx->context; + } else if ((sym_ctx->flags & FSL_SYM_CTX_INIT) + && !(sym_ctx->flags & FSL_SYM_CTX_LOAD)) { + iv = zeros; + } else { + /* Exactly one must be set! */ + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + } + + /* Now run the data through the SCC */ + scc_code = scc_crypt(length, in, iv, + encrypt ? SCC_ENCRYPT : SCC_DECRYPT, + (sym_ctx->mode == FSL_SYM_MODE_ECB) + ? SCC_ECB_MODE : SCC_CBC_MODE, + SCC_VERIFY_MODE_NONE, out, &count_out); + if (scc_code != SCC_RET_OK) { + ret = FSL_RETURN_INTERNAL_ERROR_S; +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("scc_code from scc_crypt() is %d\n", scc_code); +#endif + goto out; + } + + if ((sym_ctx->mode == FSL_SYM_MODE_CBC) + && (sym_ctx->flags & FSL_SYM_CTX_SAVE)) { + /* Save the context for the caller */ + if (encrypt) { + /* Last ciphertext block ... */ + memcpy(sym_ctx->context, out + length - 8, 8); + } else { + /* Last ciphertext block ... */ + memcpy(sym_ctx->context, in + length - 8, 8); + } + } + + ret = FSL_RETURN_OK_S; + + out: + if (key_selected) { + (void)dryice_release_key_selection(); + } + + return ret; +} + +EXPORT_SYMBOL(fsl_shw_symmetric_encrypt); +/*! + * Compute symmetric encryption + * + * + * @param user_ctx + * @param key_info + * @param sym_ctx + * @param length + * @param pt + * @param ct + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, uint8_t * ct) +{ + fsl_shw_return_t ret; + + ret = do_symmetric(user_ctx, key_info, sym_ctx, SYM_ENCRYPT, + length, pt, ct); + + return ret; +} + +EXPORT_SYMBOL(fsl_shw_symmetric_decrypt); +/*! + * Compute symmetric decryption + * + * + * @param user_ctx + * @param key_info + * @param sym_ctx + * @param length + * @param pt + * @param ct + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, uint8_t * pt) +{ + fsl_shw_return_t ret; + + ret = do_symmetric(user_ctx, key_info, sym_ctx, SYM_DECRYPT, + length, ct, pt); + + return ret; +} + +#else /* __KERNEL__ && DRYICE */ + +fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, uint8_t * ct) +{ + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)sym_ctx; + (void)length; + (void)pt; + (void)ct; + + return FSL_RETURN_ERROR_S; +} + +fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, uint8_t * pt) +{ + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)sym_ctx; + (void)length; + (void)ct; + (void)pt; + + return FSL_RETURN_ERROR_S; +} + +#endif /* __KERNEL__ and DRYICE */ diff --git a/drivers/mxc/security/rng/fsl_shw_wrap.c b/drivers/mxc/security/rng/fsl_shw_wrap.c new file mode 100644 index 000000000000..05f812c534e0 --- /dev/null +++ b/drivers/mxc/security/rng/fsl_shw_wrap.c @@ -0,0 +1,1301 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_shw_wrap.c + * + * This file implements Key-Wrap (Black Key) and Key Establishment functions of + * the FSL SHW API for the SHW (non-SAHARA) driver. + * + * This is the Black Key information: + * + * <ul> + * <li> Ownerid is an 8-byte, user-supplied, value to keep KEY + * confidential.</li> + * <li> KEY is a 1-32 byte value which starts in SCC RED RAM before + * wrapping, and ends up there on unwrap. Length is limited because of + * size of SCC1 RAM.</li> + * <li> KEY' is the encrypted KEY</li> + * <li> LEN is a 1-byte (for now) byte-length of KEY</li> + * <li> ALG is a 1-byte value for the algorithm which which the key is + * associated. Values are defined by the FSL SHW API</li> + * <li> FLAGS is a 1-byte value contain information like "this key is for + * software" (TBD)</li> + * <li> Ownerid, LEN, and ALG come from the user's "key_info" object, as does + * the slot number where KEY already is/will be.</li> + * <li> T is a Nonce</li> + * <li> T' is the encrypted T</li> + * <li> KEK is a Key-Encryption Key for the user's Key</li> + * <li> ICV is the "Integrity Check Value" for the wrapped key</li> + * <li> Black Key is the string of bytes returned as the wrapped key</li> + * <li> Wrap Key is the user's choice for encrypting the nonce. One of + * the Fused Key, the Random Key, or the XOR of the two. + * </ul> +<table border="0"> +<tr><TD align="right">BLACK_KEY <TD width="3">=<TD>ICV | T' | LEN | ALG | + FLAGS | KEY'</td></tr> +<tr><td> </td></tr> + +<tr><th>To Wrap</th></tr> +<tr><TD align="right">T</td> <TD width="3">=</td> <TD>RND()<sub>16</sub> + </td></tr> +<tr><TD align="right">KEK</td><TD width="3">=</td><TD>HASH<sub>sha256</sub>(T | + Ownerid)<sub>16</sub></td></tr> +<tr><TD align="right">KEY'<TD width="3">=</td><TD> + TDES<sub>cbc-enc</sub>(Key=KEK, Data=KEY, IV=Ownerid)</td></tr> +<tr><TD align="right">ICV</td><TD width="3">=</td><td>HMAC<sub>sha256</sub> + (Key=T, Data=Ownerid | LEN | ALG | FLAGS | KEY')<sub>16</sub></td></tr> +<tr><TD align="right">T'</td><TD width="3">=</td><TD>TDES<sub>ecb-enc</sub> + (Key=Wrap_Key, IV=Ownerid, Data=T)</td></tr> + +<tr><td> </td></tr> + +<tr><th>To Unwrap</th></tr> +<tr><TD align="right">T</td><TD width="3">=</td><TD>TDES<sub>ecb-dec</sub> + (Key=Wrap_Key, IV=Ownerid, Data=T')</td></tr> +<tr><TD align="right">ICV</td><TD width="3">=</td><td>HMAC<sub>sha256</sub> + (Key=T, Data=Ownerid | LEN | ALG | FLAGS | KEY')<sub>16</sub></td></tr> +<tr><TD align="right">KEK</td><TD width="3">=</td><td>HASH<sub>sha256</sub> + (T | Ownerid)<sub>16</sub></td></tr> +<tr><TD align="right">KEY<TD width="3">=</td><TD>TDES<sub>cbc-dec</sub> + (Key=KEK, Data=KEY', IV=Ownerid)</td></tr> +</table> + + * This code supports two types of keys: Software Keys and keys destined for + * (or residing in) the DryIce Programmed Key Register. + * + * Software Keys go to / from the keystore. + * + * PK keys go to / from the DryIce Programmed Key Register. + * + * This code only works on a platform with DryIce. "software" keys go into + * the keystore. "Program" keys go to the DryIce Programmed Key Register. + * As far as this code is concerned, the size of that register is 21 bytes, + * the size of a 3DES key with parity stripped. + * + * The maximum key size supported for wrapped/unwrapped keys depends upon + * LENGTH_LENGTH. Currently, it is one byte, so the maximum key size is + * 255 bytes. However, key objects cannot currently hold a key of this + * length, so a smaller key size is the max. + */ + +#include "fsl_platform.h" + +/* This code only works in kernel mode */ + +#include "shw_driver.h" +#ifdef DIAG_SECURITY_FUNC +#include "apihelp.h" +#endif + +#if defined(__KERNEL__) && defined(FSL_HAVE_DRYICE) + +#include "../dryice.h" +#include <linux/mxc_scc_driver.h> + +#include "portable_os.h" +#include "fsl_shw_keystore.h" + +#include <diagnostic.h> + +#include "shw_hmac.h" +#include "shw_hash.h" + +#define ICV_LENGTH 16 +#define T_LENGTH 16 +#define KEK_LENGTH 21 +#define LENGTH_LENGTH 1 +#define ALGORITHM_LENGTH 1 +#define FLAGS_LENGTH 1 + +/* ICV | T' | LEN | ALG | FLAGS | KEY' */ +#define ICV_OFFSET 0 +#define T_PRIME_OFFSET (ICV_OFFSET + ICV_LENGTH) +#define LENGTH_OFFSET (T_PRIME_OFFSET + T_LENGTH) +#define ALGORITHM_OFFSET (LENGTH_OFFSET + LENGTH_LENGTH) +#define FLAGS_OFFSET (ALGORITHM_OFFSET + ALGORITHM_LENGTH) +#define KEY_PRIME_OFFSET (FLAGS_OFFSET + FLAGS_LENGTH) + +#define FLAGS_SW_KEY 0x01 + +#define LENGTH_PATCH 8 +#define LENGTH_PATCH_MASK (LENGTH_PATCH - 1) + +/*! rounded up from 168 bits to the next word size */ +#define HW_KEY_LEN_WORDS_BITS 192 + +/*! + * Round a key length up to the TDES block size + * + * @param len Length of key, in bytes + * + * @return Length rounded up, if necessary + */ +#define ROUND_LENGTH(len) \ +({ \ + uint32_t orig_len = len; \ + uint32_t new_len; \ + \ + if ((orig_len & LENGTH_PATCH_MASK) != 0) { \ + new_len = (orig_len + LENGTH_PATCH \ + - (orig_len & LENGTH_PATCH_MASK)); \ + } \ + else { \ + new_len = orig_len; \ + } \ + \ + new_len; \ +}) + +/* This is the system keystore object */ +extern fsl_shw_kso_t system_keystore; + +#ifdef DIAG_SECURITY_FUNC +static void dump(const char *name, const uint8_t * data, unsigned int len) +{ + os_printk("%s: ", name); + while (len > 0) { + os_printk("%02x ", (unsigned)*data++); + len--; + } + os_printk("\n"); +} +#endif + +/* + * For testing of the algorithm implementation,, the DO_REPEATABLE_WRAP flag + * causes the T_block to go into the T field during a wrap operation. This + * will make the black key value repeatable (for a given SCC secret key, or + * always if the default key is in use). + * + * Normally, a random sequence is used. + */ +#ifdef DO_REPEATABLE_WRAP +/*! + * Block of zeroes which is maximum Symmetric block size, used for + * initializing context register, etc. + */ +static uint8_t T_block[16] = { + 0x42, 0, 0, 0x42, 0x42, 0, 0, 0x42, + 0x42, 0, 0, 0x42, 0x42, 0, 0, 0x42 +}; +#endif + +EXPORT_SYMBOL(fsl_shw_establish_key); +EXPORT_SYMBOL(fsl_shw_read_key); +EXPORT_SYMBOL(fsl_shw_extract_key); +EXPORT_SYMBOL(fsl_shw_release_key); + +extern fsl_shw_return_t alloc_slot(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info); + +extern fsl_shw_return_t load_slot(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + const uint8_t * key); + +extern fsl_shw_return_t dealloc_slot(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info); + +/*! + * Initalialize SKO and SCCO used for T <==> T' cipher operation + * + * @param wrap_key Which wrapping key user wants + * @param key_info Key object for selecting wrap key + * @param wrap_ctx Sym Context object for doing the cipher op + */ +static inline void init_wrap_key(fsl_shw_pf_key_t wrap_key, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * wrap_ctx) +{ + fsl_shw_sko_init_pf_key(key_info, FSL_KEY_ALG_TDES, wrap_key); + fsl_shw_scco_init(wrap_ctx, FSL_KEY_ALG_TDES, FSL_SYM_MODE_ECB); +} + +/*! + * Insert descriptors to calculate ICV = HMAC(key=T, data=LEN|ALG|KEY') + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param T Location of nonce (length is T_LENGTH bytes) + * @param userid Location of userid/ownerid + * @param userid_len Length, in bytes of @c userid + * @param black_key Beginning of Black Key region + * @param key_length Number of bytes of key' there are in @c black_key + * @param[out] hmac Location to store ICV. Will be tagged "USES" so + * sf routines will not try to free it. + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t calc_icv(const uint8_t * T, + const uint8_t * userid, + unsigned int userid_len, + const uint8_t * black_key, + uint32_t key_length, uint8_t * hmac) +{ + fsl_shw_return_t code; + shw_hmac_state_t hmac_state; + + /* Load up T as key for the HMAC */ + code = shw_hmac_init(&hmac_state, T, T_LENGTH); + if (code != FSL_RETURN_OK_S) { + goto out; + } + + /* Previous step loaded key; Now set up to hash the data */ + + /* Input - start with ownerid */ + code = shw_hmac_update(&hmac_state, userid, userid_len); + if (code != FSL_RETURN_OK_S) { + goto out; + } + + /* Still input - Append black-key fields len, alg, key' */ + code = shw_hmac_update(&hmac_state, + (void *)black_key + LENGTH_OFFSET, + (LENGTH_LENGTH + + ALGORITHM_LENGTH + + FLAGS_LENGTH + key_length)); + if (code != FSL_RETURN_OK_S) { + goto out; + } + + /* Output - computed ICV/HMAC */ + code = shw_hmac_final(&hmac_state, hmac, ICV_LENGTH); + if (code != FSL_RETURN_OK_S) { + goto out; + } + + out: + + return code; +} /* calc_icv */ + +/*! + * Compute and return the KEK (Key Encryption Key) from the inputs + * + * @param userid The user's 'secret' for the key + * @param userid_len Length, in bytes of @c userid + * @param T The nonce + * @param[out] kek Location to store the computed KEK. It will + * be 21 bytes long. + * + * @return the usual error code + */ +static fsl_shw_return_t calc_kek(const uint8_t * userid, + unsigned int userid_len, + const uint8_t * T, uint8_t * kek) +{ + fsl_shw_return_t code = FSL_RETURN_INTERNAL_ERROR_S; + shw_hash_state_t hash_state; + + code = shw_hash_init(&hash_state, FSL_HASH_ALG_SHA256); + if (code != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Hash init failed: %s\n", fsl_error_string(code)); +#endif + goto out; + } + + code = shw_hash_update(&hash_state, T, T_LENGTH); + if (code != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Hash for T failed: %s\n", + fsl_error_string(code)); +#endif + goto out; + } + + code = shw_hash_update(&hash_state, userid, userid_len); + if (code != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Hash for userid failed: %s\n", + fsl_error_string(code)); +#endif + goto out; + } + + code = shw_hash_final(&hash_state, kek, KEK_LENGTH); + if (code != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Could not extract kek: %s\n", + fsl_error_string(code)); +#endif + goto out; + } + +#if KEK_LENGTH != 21 + { + uint8_t permuted_kek[21]; + + fsl_shw_permute1_bytes(kek, permuted_kek, KEK_LENGTH / 8); + memcpy(kek, permuted_kek, 21); + memset(permuted_kek, 0, sizeof(permuted_kek)); + } +#endif + +#ifdef DIAG_SECURITY_FUNC + dump("kek", kek, 21); +#endif + + out: + + return code; +} /* end fn calc_kek */ + +/*! + * Validate user's wrap key selection + * + * @param wrap_key The user's desired wrapping key + */ +static fsl_shw_return_t check_wrap_key(fsl_shw_pf_key_t wrap_key) +{ + /* unable to use desired key */ + fsl_shw_return_t ret = FSL_RETURN_NO_RESOURCE_S; + + if ((wrap_key != FSL_SHW_PF_KEY_IIM) && + (wrap_key != FSL_SHW_PF_KEY_RND) && + (wrap_key != FSL_SHW_PF_KEY_IIM_RND)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Invalid wrap_key in key wrap/unwrap attempt"); +#endif + goto out; + } + ret = FSL_RETURN_OK_S; + + out: + return ret; +} /* end fn check_wrap_key */ + +/*! + * Perform unwrapping of a black key into a RED slot + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be unwrapped... key length, slot info, etc. + * @param black_key Encrypted key + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t unwrap(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + const uint8_t * black_key) +{ + fsl_shw_return_t ret; + uint8_t hmac[ICV_LENGTH]; + uint8_t T[T_LENGTH]; + uint8_t kek[KEK_LENGTH + 20]; + int key_length = black_key[LENGTH_OFFSET]; + int rounded_key_length = ROUND_LENGTH(key_length); + uint8_t key[rounded_key_length]; + fsl_shw_sko_t t_key_info; + fsl_shw_scco_t t_key_ctx; + fsl_shw_sko_t kek_key_info; + fsl_shw_scco_t kek_ctx; + int unwrapping_sw_key = key_info->flags & FSL_SKO_KEY_SW_KEY; + int pk_needs_restoration = 0; /* bool */ + unsigned original_key_length = key_info->key_length; + int pk_was_held = 0; + uint8_t current_pk[21]; + di_return_t di_code; + + ret = check_wrap_key(user_ctx->wrap_key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + if (black_key == NULL) { + ret = FSL_RETURN_ERROR_S; + goto out; + } +#ifdef DIAG_SECURITY_FUNC + dump("black", black_key, KEY_PRIME_OFFSET + key_length); +#endif + /* Validate SW flags to prevent misuse */ + if ((key_info->flags & FSL_SKO_KEY_SW_KEY) + && !(black_key[FLAGS_OFFSET] & FLAGS_SW_KEY)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* Compute T = 3des-dec-ecb(wrap_key, T') */ + init_wrap_key(user_ctx->wrap_key, &t_key_info, &t_key_ctx); + ret = fsl_shw_symmetric_decrypt(user_ctx, &t_key_info, &t_key_ctx, + T_LENGTH, + black_key + T_PRIME_OFFSET, T); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Recovery of nonce (T) failed"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } + + /* Compute ICV = HMAC(T, ownerid | len | alg | flags | key' */ + ret = calc_icv(T, (uint8_t *) & key_info->userid, + sizeof(key_info->userid), + black_key, original_key_length, hmac); + + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Calculation of ICV failed"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Validating MAC of wrapped key"); +#endif + + /* Check computed ICV against value in Black Key */ + if (memcmp(black_key + ICV_OFFSET, hmac, ICV_LENGTH) != 0) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Computed ICV fails validation\n"); +#endif + ret = FSL_RETURN_AUTH_FAILED_S; + goto out; + } + + /* Compute KEK = SHA256(T | ownerid). */ + ret = calc_kek((uint8_t *) & key_info->userid, sizeof(key_info->userid), + T, kek); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + if (unwrapping_sw_key) { + di_code = dryice_get_programmed_key(current_pk, 8 * 21); + if (di_code != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Could not save current PK: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + } + + /* + * "Establish" the KEK in the PK. If the PK was held and unwrapping a + * software key, then release it and try again, but remember that we need + * to leave it 'held' if we are unwrapping a software key. + * + * If the PK is held while we are unwrapping a key for the PK, then + * the user didn't call release, so gets an error. + */ + di_code = dryice_set_programmed_key(kek, 8 * 21, 0); + if ((di_code == DI_ERR_INUSE) && unwrapping_sw_key) { + /* Temporarily reprogram the PK out from under the user */ + pk_was_held = 1; + dryice_release_programmed_key(); + di_code = dryice_set_programmed_key(kek, 8 * 21, 0); + } + if (di_code != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Could not program KEK: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + + if (unwrapping_sw_key) { + pk_needs_restoration = 1; + } + dryice_release_programmed_key(); /* Because of previous 'set' */ + + /* Compute KEY = TDES-decrypt(KEK, KEY') */ + fsl_shw_sko_init_pf_key(&kek_key_info, FSL_KEY_ALG_TDES, + FSL_SHW_PF_KEY_PRG); + fsl_shw_sko_set_key_length(&kek_key_info, KEK_LENGTH); + + fsl_shw_scco_init(&kek_ctx, FSL_KEY_ALG_TDES, FSL_SYM_MODE_CBC); + fsl_shw_scco_set_flags(&kek_ctx, FSL_SYM_CTX_LOAD); + fsl_shw_scco_set_context(&kek_ctx, (uint8_t *) & key_info->userid); +#ifdef DIAG_SECURITY_FUNC + dump("KEY'", black_key + KEY_PRIME_OFFSET, rounded_key_length); +#endif + ret = fsl_shw_symmetric_decrypt(user_ctx, &kek_key_info, &kek_ctx, + rounded_key_length, + black_key + KEY_PRIME_OFFSET, key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } +#ifdef DIAG_SECURITY_FUNC + dump("KEY", key, original_key_length); +#endif + /* Now either put key into PK or into a slot */ + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + ret = load_slot(user_ctx, key_info, key); + } else { + /* + * Since we have just unwrapped a program key, it had + * to have been wrapped as a program key, so it must + * be 168 bytes long and permuted ... + */ + ret = dryice_set_programmed_key(key, 8 * key_length, 0); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + } + + out: + key_info->key_length = original_key_length; + + if (pk_needs_restoration) { + di_code = dryice_set_programmed_key(current_pk, 8 * 21, 0); + } + + if (!pk_was_held) { + dryice_release_programmed_key(); + } + + /* Erase tracks of confidential data */ + memset(T, 0, T_LENGTH); + memset(key, 0, rounded_key_length); + memset(current_pk, 0, sizeof(current_pk)); + memset(&t_key_info, 0, sizeof(t_key_info)); + memset(&t_key_ctx, 0, sizeof(t_key_ctx)); + memset(&kek_key_info, 0, sizeof(kek_key_info)); + memset(&kek_ctx, 0, sizeof(kek_ctx)); + memset(kek, 0, KEK_LENGTH); + + return ret; +} /* unwrap */ + +/*! + * Perform wrapping of a black key from a RED slot (or the PK register) + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be wrapped... key length, slot info, etc. + * @param black_key Place to store encrypted key + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t wrap(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, uint8_t * black_key) +{ + fsl_shw_return_t ret = FSL_RETURN_OK_S; + fsl_shw_sko_t t_key_info; /* for holding T */ + fsl_shw_scco_t t_key_ctx; + fsl_shw_sko_t kek_key_info; + fsl_shw_scco_t kek_ctx; + unsigned original_key_length = key_info->key_length; + unsigned rounded_key_length; + uint8_t T[T_LENGTH]; + uint8_t kek[KEK_LENGTH + 20]; + uint8_t *red_key = 0; + int red_key_malloced = 0; /* bool */ + int pk_was_held = 0; /* bool */ + uint8_t saved_pk[21]; + uint8_t pk_needs_restoration; /* bool */ + di_return_t di_code; + + ret = check_wrap_key(user_ctx->wrap_key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + if (black_key == NULL) { + ret = FSL_RETURN_ERROR_S; + goto out; + } + + if (key_info->flags & FSL_SKO_KEY_SELECT_PF_KEY) { + if ((key_info->pf_key != FSL_SHW_PF_KEY_PRG) + && (key_info->pf_key != FSL_SHW_PF_KEY_IIM_PRG)) { + ret = FSL_RETURN_ERROR_S; + goto out; + } + } else { + if (!(key_info->flags & FSL_SKO_KEY_ESTABLISHED)) { + ret = FSL_RETURN_BAD_FLAG_S; /* not established! */ + goto out; + } + } + + black_key[ALGORITHM_OFFSET] = key_info->algorithm; + +#ifndef DO_REPEATABLE_WRAP + /* Compute T = RND() */ + ret = fsl_shw_get_random(user_ctx, T_LENGTH, T); + if (ret != FSL_RETURN_OK_S) { + goto out; + } +#else + memcpy(T, T_block, T_LENGTH); +#endif + + /* Compute KEK = SHA256(T | ownerid). */ + ret = calc_kek((uint8_t *) & key_info->userid, sizeof(key_info->userid), + T, kek); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Calculation of KEK failed\n"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } + + rounded_key_length = ROUND_LENGTH(original_key_length); + + di_code = dryice_get_programmed_key(saved_pk, 8 * 21); + if (di_code != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Could not save current PK: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + + /* + * Load KEK into DI PKR. Note that we are NOT permuting it before loading, + * so we are using it as though it is a 168-bit key ready for the SCC. + */ + di_code = dryice_set_programmed_key(kek, 8 * 21, 0); + if (di_code == DI_ERR_INUSE) { + /* Temporarily reprogram the PK out from under the user */ + pk_was_held = 1; + dryice_release_programmed_key(); + di_code = dryice_set_programmed_key(kek, 8 * 21, 0); + } + if (di_code != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Could not program KEK: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + pk_needs_restoration = 1; + dryice_release_programmed_key(); + + /* Find red key */ + if (key_info->flags & FSL_SKO_KEY_SELECT_PF_KEY) { + black_key[LENGTH_OFFSET] = 21; + rounded_key_length = 24; + + red_key = saved_pk; + } else { + black_key[LENGTH_OFFSET] = key_info->key_length; + + red_key = os_alloc_memory(key_info->key_length, 0); + if (red_key == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + goto out; + } + red_key_malloced = 1; + + ret = fsl_shw_read_key(user_ctx, key_info, red_key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + } + +#ifdef DIAG_SECURITY_FUNC + dump("KEY", red_key, black_key[LENGTH_OFFSET]); +#endif + /* Compute KEY' = TDES-encrypt(KEK, KEY) */ + fsl_shw_sko_init_pf_key(&kek_key_info, FSL_KEY_ALG_TDES, + FSL_SHW_PF_KEY_PRG); + fsl_shw_sko_set_key_length(&kek_key_info, KEK_LENGTH); + + fsl_shw_scco_init(&kek_ctx, FSL_KEY_ALG_TDES, FSL_SYM_MODE_CBC); + fsl_shw_scco_set_flags(&kek_ctx, FSL_SYM_CTX_LOAD); + fsl_shw_scco_set_context(&kek_ctx, (uint8_t *) & key_info->userid); + ret = fsl_shw_symmetric_encrypt(user_ctx, &kek_key_info, &kek_ctx, + rounded_key_length, + red_key, black_key + KEY_PRIME_OFFSET); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Encryption of KEY failed\n"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } + + /* Set up flags info */ + black_key[FLAGS_OFFSET] = 0; + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + black_key[FLAGS_OFFSET] |= FLAGS_SW_KEY; + } +#ifdef DIAG_SECURITY_FUNC + dump("KEY'", black_key + KEY_PRIME_OFFSET, rounded_key_length); +#endif + /* Compute and store ICV into Black Key */ + ret = calc_icv(T, + (uint8_t *) & key_info->userid, + sizeof(key_info->userid), + black_key, original_key_length, black_key + ICV_OFFSET); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Calculation of ICV failed\n"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } + + /* Compute T' = 3des-enc-ecb(wrap_key, T); Result goes to Black Key */ + init_wrap_key(user_ctx->wrap_key, &t_key_info, &t_key_ctx); + ret = fsl_shw_symmetric_encrypt(user_ctx, &t_key_info, &t_key_ctx, + T_LENGTH, + T, black_key + T_PRIME_OFFSET); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Encryption of nonce failed"); +#endif + goto out; + } +#ifdef DIAG_SECURITY_FUNC + dump("black", black_key, KEY_PRIME_OFFSET + black_key[LENGTH_OFFSET]); +#endif + + out: + if (pk_needs_restoration) { + dryice_set_programmed_key(saved_pk, 8 * 21, 0); + } + + if (!pk_was_held) { + dryice_release_programmed_key(); + } + + if (red_key_malloced) { + memset(red_key, 0, key_info->key_length); + os_free_memory(red_key); + } + + key_info->key_length = original_key_length; + + /* Erase tracks of confidential data */ + memset(T, 0, T_LENGTH); + memset(&t_key_info, 0, sizeof(t_key_info)); + memset(&t_key_ctx, 0, sizeof(t_key_ctx)); + memset(&kek_key_info, 0, sizeof(kek_key_info)); + memset(&kek_ctx, 0, sizeof(kek_ctx)); + memset(kek, 0, sizeof(kek)); + memset(saved_pk, 0, sizeof(saved_pk)); + + return ret; +} /* wrap */ + +static fsl_shw_return_t create(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + unsigned key_length = key_info->key_length; + di_return_t di_code; + + if (!(key_info->flags & FSL_SKO_KEY_SW_KEY)) { + /* Must be creating key for PK */ + if ((key_info->algorithm != FSL_KEY_ALG_TDES) || + ((key_info->key_length != 16) + && (key_info->key_length != 21) /* permuted 168-bit key */ + &&(key_info->key_length != 24))) { + ret = FSL_RETURN_ERROR_S; + goto out; + } + + key_length = 21; /* 168-bit PK */ + } + + /* operational block */ + { + uint8_t key_value[key_length]; + +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creating random key\n"); +#endif + ret = fsl_shw_get_random(user_ctx, key_length, key_value); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("get_random for CREATE KEY failed\n"); +#endif + goto out; + } + + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + ret = load_slot(user_ctx, key_info, key_value); + } else { + di_code = + dryice_set_programmed_key(key_value, 8 * key_length, + 0); + if (di_code != 0) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("di set_pk failed: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + ret = FSL_RETURN_OK_S; + } + memset(key_value, 0, key_length); + } /* end operational block */ + +#ifdef DIAG_SECURITY_FUNC + if (ret != FSL_RETURN_OK_S) { + LOG_DIAG("Loading random key failed"); + } +#endif + + out: + + return ret; +} /* end fn create */ + +static fsl_shw_return_t accept(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, const uint8_t * key) +{ + uint8_t permuted_key[21]; + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + if (key == NULL) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("ACCEPT: Red Key is NULL"); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } +#ifdef DIAG_SECURITY_FUNC + dump("red", key, key_info->key_length); +#endif + /* Only SW keys go into the keystore */ + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + + /* Copy in safe number of bytes of Red key */ + ret = load_slot(user_ctx, key_info, key); + } else { /* not SW key */ + di_return_t di_ret; + + /* Only 3DES PGM key types can be established */ + if (((key_info->pf_key != FSL_SHW_PF_KEY_PRG) + && (key_info->pf_key != FSL_SHW_PF_KEY_IIM_PRG)) + || (key_info->algorithm != FSL_KEY_ALG_TDES)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS + ("ACCEPT: Failed trying to establish non-PRG" + " or invalid 3DES Key: iim%d, iim_prg%d, alg%d\n", + (key_info->pf_key != FSL_SHW_PF_KEY_PRG), + (key_info->pf_key != FSL_SHW_PF_KEY_IIM_PRG), + (key_info->algorithm != FSL_KEY_ALG_TDES)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + if ((key_info->key_length != 16) + && (key_info->key_length != 21) + && (key_info->key_length != 24)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("ACCEPT: Failed trying to establish" + " invalid 3DES Key: len=%d (%d)\n", + key_info->key_length, + ((key_info->key_length != 16) + && (key_info->key_length != 21) + && (key_info->key_length != 24))); +#endif + ret = FSL_RETURN_BAD_KEY_LENGTH_S; + goto out; + } + + /* Convert key into 168-bit value and put it into PK */ + if (key_info->key_length != 21) { + fsl_shw_permute1_bytes(key, permuted_key, + key_info->key_length / 8); + di_ret = + dryice_set_programmed_key(permuted_key, 168, 0); + } else { + /* Already permuted ! */ + di_ret = dryice_set_programmed_key(key, 168, 0); + } + if (di_ret != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS + ("ACCEPT: DryIce error setting Program Key: %s", + di_error_string(di_ret)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + } + + ret = FSL_RETURN_OK_S; + + out: + memset(permuted_key, 0, 21); + + return ret; +} /* end fn accept */ + +/*! + * Place a key into a protected location for use only by cryptographic + * algorithms. + * + * This only needs to be used to a) unwrap a key, or b) set up a key which + * could be wrapped by calling #fsl_shw_extract_key() at some later time). + * + * The protected key will not be available for use until this operation + * successfully completes. + * + * @bug This whole discussion needs review. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be established. In the create case, the key + * length must be set. + * @param establish_type How @a key will be interpreted to establish a + * key for use. + * @param key If @a establish_type is #FSL_KEY_WRAP_UNWRAP, + * this is the location of a wrapped key. If + * @a establish_type is #FSL_KEY_WRAP_CREATE, this + * parameter can be @a NULL. If @a establish_type + * is #FSL_KEY_WRAP_ACCEPT, this is the location + * of a plaintext key. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + unsigned original_key_length = key_info->key_length; + unsigned rounded_key_length; + unsigned slot_allocated = 0; + + /* For now, only blocking mode calls are supported */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("%s: Non-blocking call not supported\n", + __FUNCTION__); +#endif + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* + HW keys are always 'established', but otherwise do not allow user + * to establish over the top of an established key. + */ + if ((key_info->flags & FSL_SKO_KEY_ESTABLISHED) + && !(key_info->flags & FSL_SKO_KEY_SELECT_PF_KEY)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("%s: Key already established\n", __FUNCTION__); +#endif + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* @bug VALIDATE KEY flags here -- SW or PRG/IIM_PRG */ + + /* Write operations into SCC memory require word-multiple number of + * bytes. For ACCEPT and CREATE functions, the key length may need + * to be rounded up. Calculate. */ + if (LENGTH_PATCH && (original_key_length & LENGTH_PATCH_MASK) != 0) { + rounded_key_length = original_key_length + LENGTH_PATCH + - (original_key_length & LENGTH_PATCH_MASK); + } else { + rounded_key_length = original_key_length; + } + + /* SW keys need a place to live */ + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + ret = alloc_slot(user_ctx, key_info); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Slot allocation failed\n"); +#endif + goto out; + } + slot_allocated = 1; + } + + switch (establish_type) { + case FSL_KEY_WRAP_CREATE: +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creating random key\n"); +#endif + ret = create(user_ctx, key_info); + break; + + case FSL_KEY_WRAP_ACCEPT: +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Accepting plaintext key\n"); +#endif + ret = accept(user_ctx, key_info, key); + break; + + case FSL_KEY_WRAP_UNWRAP: +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Unwrapping wrapped key\n"); +#endif + ret = unwrap(user_ctx, key_info, key); + break; + + default: + ret = FSL_RETURN_BAD_FLAG_S; + break; + } /* switch */ + + out: + if (ret != FSL_RETURN_OK_S) { + if (slot_allocated) { + (void)dealloc_slot(user_ctx, key_info); + } + key_info->flags &= ~FSL_SKO_KEY_ESTABLISHED; + } else { + key_info->flags |= FSL_SKO_KEY_ESTABLISHED; + } + + return ret; +} /* end fn fsl_shw_establish_key */ + +/*! + * Wrap a key and retrieve the wrapped value. + * + * A wrapped key is a key that has been cryptographically obscured. It is + * only able to be used with #fsl_shw_establish_key(). + * + * This function will also release a software key (see #fsl_shw_release_key()) + * so it must be re-established before reuse. This is not true of PGM keys. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * @param[out] covered_key The location to store the 48-octet wrapped key. + * (This size is based upon the maximum key size + * of 32 octets). + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + /* For now, only blocking mode calls are supported */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Wrapping a key\n"); +#endif + + if (!(key_info->flags & FSL_SKO_KEY_ESTABLISHED)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("%s: Key not established\n", __FUNCTION__); +#endif + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + /* Verify that a SW key info really belongs to a SW key */ + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + /* ret = FSL_RETURN_BAD_FLAG_S; + goto out;*/ + } + + ret = wrap(user_ctx, key_info, covered_key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + /* Need to deallocate on successful extraction */ + (void)dealloc_slot(user_ctx, key_info); + /* Mark key not available in the flags */ + key_info->flags &= + ~(FSL_SKO_KEY_ESTABLISHED | FSL_SKO_KEY_PRESENT); + memset(key_info->key, 0, sizeof(key_info->key)); + } + + out: + return ret; +} /* end fn fsl_shw_extract_key */ + +/*! + * De-establish a key so that it can no longer be accessed. + * + * The key will need to be re-established before it can again be used. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + /* For now, only blocking mode calls are supported */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Not in blocking mode\n"); +#endif + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Releasing a key\n"); +#endif + + if (!(key_info->flags & FSL_SKO_KEY_ESTABLISHED)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Key not established\n"); +#endif + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + (void)dealloc_slot(user_ctx, key_info); + /* Turn off 'established' flag */ +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("dealloc_slot() called\n"); +#endif + key_info->flags &= ~FSL_SKO_KEY_ESTABLISHED; + ret = FSL_RETURN_OK_S; + goto out; + } + + if ((key_info->pf_key == FSL_SHW_PF_KEY_PRG) + || (key_info->pf_key == FSL_SHW_PF_KEY_IIM_PRG)) { + di_return_t di_ret; + + di_ret = dryice_release_programmed_key(); + if (di_ret != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS + ("dryice_release_programmed_key() failed: %d\n", + di_ret); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + } else { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Neither SW nor HW key\n"); +#endif + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + ret = FSL_RETURN_OK_S; + + out: + return ret; +} /* end fn fsl_shw_release_key */ + +fsl_shw_return_t fsl_shw_read_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, uint8_t * key) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + + /* Only blocking mode calls are supported */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + printk("Reading a key\n"); +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Reading a key"); +#endif + if (key_info->flags & FSL_SKO_KEY_PRESENT) { + memcpy(key_info->key, key, key_info->key_length); + ret = FSL_RETURN_OK_S; + } else if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + printk("key established\n"); + if (key_info->keystore == NULL) { + printk("keystore is null\n"); + /* First verify that the key access is valid */ + ret = + system_keystore.slot_verify_access(system_keystore. + user_data, + key_info->userid, + key_info-> + handle); + + printk("key in system keystore\n"); + + /* Key is in system keystore */ + ret = keystore_slot_read(&system_keystore, + key_info->userid, + key_info->handle, + key_info->key_length, key); + } else { + printk("key goes in user keystore.\n"); + /* Key goes in user keystore */ + ret = keystore_slot_read(key_info->keystore, + key_info->userid, + key_info->handle, + key_info->key_length, key); + } + } + + out: + return ret; +} /* end fn fsl_shw_read_key */ + +#else /* __KERNEL__ && DRYICE */ + +/* User mode -- these functions are unsupported */ + +fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key) +{ + (void)user_ctx; + (void)key_info; + (void)establish_type; + (void)key; + + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key) +{ + (void)user_ctx; + (void)key_info; + (void)covered_key; + + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info) +{ + (void)user_ctx; + (void)key_info; + + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t fsl_shw_read_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, uint8_t * key) +{ + (void)user_ctx; + (void)key_info; + (void)key; + + return FSL_RETURN_NO_RESOURCE_S; +} + +#endif /* __KERNEL__ && DRYICE */ diff --git a/drivers/mxc/security/rng/include/rng_driver.h b/drivers/mxc/security/rng/include/rng_driver.h new file mode 100644 index 000000000000..7d6d24dde915 --- /dev/null +++ b/drivers/mxc/security/rng/include/rng_driver.h @@ -0,0 +1,134 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef RNG_DRIVER_H +#define RNG_DRIVER_H + +#include "shw_driver.h" + +/* This is a Linux flag meaning 'compiling kernel code'... */ +#ifndef __KERNEL__ +#include <inttypes.h> +#include <stdlib.h> +#include <memory.h> +#else +#include "../../sahara2/include/portable_os.h" +#endif + +#include "../../sahara2/include/fsl_platform.h" + +/*! @file rng_driver.h + * + * @brief Header file to use the RNG driver. + * + * @ingroup RNG + */ + +#if defined(FSL_HAVE_RNGA) + +#include "rng_rnga.h" + +#elif defined(FSL_HAVE_RNGB) || defined(FSL_HAVE_RNGC) + +#include "rng_rngc.h" + +#else /* neither RNGA, RNGB, nor RNGC */ + +#error NO_RNG_TYPE_IDENTIFIED + +#endif + +/***************************************************************************** + * Enumerations + *****************************************************************************/ + +/*! Values from Version ID register */ +enum rng_type { + /*! Type RNGA. */ + RNG_TYPE_RNGA = 0, + /*! Type RNGB. */ + RNG_TYPE_RNGB = 1, + /*! Type RNGC */ + RNG_TYPE_RNGC = 2 +}; + +/*! + * Return values (error codes) for kernel register interface functions + */ +typedef enum rng_return { + RNG_RET_OK = 0, /*!< Function succeeded */ + RNG_RET_FAIL /*!< Non-specific failure */ +} rng_return_t; + +/***************************************************************************** + * Data Structures + *****************************************************************************/ +/*! + * An entry in the RNG Work Queue. Based upon standard SHW queue entry. + * + * This entry also gets saved (for non-blocking requests) in the user's result + * pool. When the user picks up the request, the final processing (copy from + * data_local to data_user) will get made if status was good. + */ +typedef struct rng_work_entry { + struct shw_queue_entry_t hdr; /*!< Standards SHW queue info. */ + uint32_t length; /*!< Number of bytes still needed to satisfy request. */ + uint32_t *data_local; /*!< Where data from RNG FIFO gets placed. */ + uint8_t *data_user; /*!< Ultimate target of data. */ + unsigned completed; /*!< Non-zero if job is done. */ +} rng_work_entry_t; + +/***************************************************************************** + * Function Prototypes + *****************************************************************************/ + +#ifdef RNG_REGISTER_PEEK_POKE +/*! + * Read value from an RNG register. + * The offset will be checked for validity as well as whether it is + * accessible at the time of the call. + * + * This routine cannot be used to read the RNG's Output FIFO if the RNG is in + * High Assurance mode. + * + * @param[in] register_offset The (byte) offset within the RNG block + * of the register to be queried. See + * RNG(A, C) registers for meanings. + * @param[out] value Pointer to where value from the register + * should be placed. + * + * @return See #rng_return_t. + */ +/* REQ-FSLSHW-PINTFC-API-LLF-001 */ +extern rng_return_t rng_read_register(uint32_t register_offset, + uint32_t * value); + +/*! + * Write a new value into an RNG register. + * + * The offset will be checked for validity as well as whether it is + * accessible at the time of the call. + * + * @param[in] register_offset The (byte) offset within the RNG block + * of the register to be modified. See + * RNG(A, C) registers for meanings. + * @param[in] value The value to store into the register. + * + * @return See #rng_return_t. + */ +/* REQ-FSLSHW-PINTFC-API-LLF-002 */ +extern rng_return_t rng_write_register(uint32_t register_offset, + uint32_t value); +#endif /* RNG_REGISTER_PEEK_POKE */ + +#endif /* RNG_DRIVER_H */ diff --git a/drivers/mxc/security/rng/include/rng_internals.h b/drivers/mxc/security/rng/include/rng_internals.h new file mode 100644 index 000000000000..62d195bf4df7 --- /dev/null +++ b/drivers/mxc/security/rng/include/rng_internals.h @@ -0,0 +1,680 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef RNG_INTERNALS_H +#define RNG_INTERNALS_H + +/*! @file rng_internals.h + * + * This file contains definitions which are internal to the RNG driver. + * + * This header file should only ever be needed by rng_driver.c + * + * Compile-time flags minimally needed: + * + * @li Some sort of platform flag. (FSL_HAVE_RNGA or FSL_HAVE_RNGC) + * + * @ingroup RNG + */ + +#include "portable_os.h" +#include "shw_driver.h" +#include "rng_driver.h" + +/*! @defgroup rngcompileflags RNG Compile Flags + * + * These are flags which are used to configure the RNG driver at compilation + * time. + * + * Most of them default to good values for normal operation, but some + * (#INT_RNG and #RNG_BASE_ADDR) need to be provided. + * + * The terms 'defined' and 'undefined' refer to whether a @c \#define (or -D on + * a compile command) has defined a given preprocessor symbol. If a given + * symbol is defined, then @c \#ifdef \<symbol\> will succeed. Some symbols + * described below default to not having a definition, i.e. they are undefined. + * + */ + +/*! @addtogroup rngcompileflags */ +/*! @{ */ + +/*! + * This is the maximum number of times the driver will loop waiting for the + * RNG hardware to say that it has generated random data. It prevents the + * driver from stalling forever should there be a hardware problem. + * + * Default value is 100. It should be revisited as CPU clocks speed up. + */ +#ifndef RNG_MAX_TRIES +#define RNG_MAX_TRIES 100 +#endif + +/* Temporarily define compile-time flags to make Doxygen happy and allow them + to get into the documentation. */ +#ifdef DOXYGEN_HACK + +/*! + * This symbol is the base address of the RNG in the CPU memory map. It may + * come from some included header file, or it may come from the compile command + * line. This symbol has no default, and the driver will not compile without + * it. + */ +#define RNG_BASE_ADDR +#undef RNG_BASE_ADDR + +/*! + * This symbol is the Interrupt Number of the RNG in the CPU. It may come + * from some included header file, or it may come from the compile command + * line. This symbol has no default, and the driver will not compile without + * it. + */ +#define INT_RNG +#undef INT_RNG + +/*! + * Defining this symbol will allow other kernel programs to call the + * #rng_read_register() and #rng_write_register() functions. If this symbol is + * not defined, those functions will not be present in the driver. + */ +#define RNG_REGISTER_PEEK_POKE +#undef RNG_REGISTER_PEEK_POKE + +/*! + * Turn on compilation of run-time operational, debug, and error messages. + * + * This flag is undefined by default. + */ +/* REQ-FSLSHW-DEBUG-001 */ + +/*! + * Turn on compilation of run-time logging of access to the RNG registers, + * except for the RNG's Output FIFO register. See #RNG_ENTROPY_DEBUG. + * + * This flag is undefined by default + */ +#define RNG_REGISTER_DEBUG +#undef RNG_REGISTER_DEBUG + +/*! + * Turn on compilation of run-time logging of reading of the RNG's Output FIFO + * register. This flag does nothing if #RNG_REGISTER_DEBUG is not defined. + * + * This flag is undefined by default + */ +#define RNG_ENTROPY_DEBUG +#undef RNG_ENTROPY_DEBUG + +/*! + * If this flag is defined, the driver will not attempt to put the RNG into + * High Assurance mode. + + * If it is undefined, the driver will attempt to put the RNG into High + * Assurance mode. If RNG fails to go into High Assurance mode, the driver + * will fail to initialize. + + * In either case, if the RNG is already in this mode, the driver will operate + * normally. + * + * This flag is undefined by default. + */ +#define RNG_NO_FORCE_HIGH_ASSURANCE +#undef RNG_NO_FORCE_HIGH_ASSURANCE + +/*! + * If this flag is defined, the driver will put the RNG into low power mode + * every opportunity. + * + * This flag is undefined by default. + */ +#define RNG_USE_LOW_POWER_MODE +#undef RNG_USE_LOW_POWER_MODE + +/*! @} */ +#endif /* end DOXYGEN_HACK */ + +/*! + * If this flag is defined, the driver will not attempt to put the RNG into + * High Assurance mode. + + * If it is undefined, the driver will attempt to put the RNG into High + * Assurance mode. If RNG fails to go into High Assurance mode, the driver + * will fail to initialize. + + * In either case, if the RNG is already in this mode, the driver will operate + * normally. + * + */ +#define RNG_NO_FORCE_HIGH_ASSURANCE + +/*! + * Read a 32-bit value from an RNG register. This macro depends upon + * #rng_base. The os_read32() macro operates on 32-bit quantities, as do + * all RNG register reads. + * + * @param offset Register byte offset within RNG. + * + * @return The value from the RNG's register. + */ +#ifndef RNG_REGISTER_DEBUG +#define RNG_READ_REGISTER(offset) os_read32(rng_base+(offset)) +#else +#define RNG_READ_REGISTER(offset) dbg_rng_read_register(offset) +#endif + +/*! + * Write a 32-bit value to an RNG register. This macro depends upon + * #rng_base. The os_write32() macro operates on 32-bit quantities, as do + * all RNG register writes. + * + * @param offset Register byte offset within RNG. + * @param value 32-bit value to store into the register + * + * @return (void) + */ +#ifndef RNG_REGISTER_DEBUG +#define RNG_WRITE_REGISTER(offset,value) \ + (void)os_write32(rng_base+(offset), value) +#else +#define RNG_WRITE_REGISTER(offset,value) dbg_rng_write_register(offset,value) +#endif + +#ifndef RNG_DRIVER_NAME +/*! @addtogroup rngcompileflags */ +/*! @{ */ +/*! Name the driver will use to register itself to the kernel as the driver. */ +#define RNG_DRIVER_NAME "rng" +/*! @} */ +#endif + +/*! + * Calculate number of words needed to hold the given number of bytes. + * + * @param byte_count Number of bytes + * + * @return Number of words + */ +#define BYTES_TO_WORDS(byte_count) \ + (((byte_count)+sizeof(uint32_t)-1)/sizeof(uint32_t)) + +/*! Gives high-level view of state of the RNG */ +typedef enum rng_status { + RNG_STATUS_INITIAL, /*!< Driver status before ever starting. */ + RNG_STATUS_CHECKING, /*!< During driver initialization. */ + RNG_STATUS_UNIMPLEMENTED, /*!< Hardware is non-existent / unreachable. */ + RNG_STATUS_OK, /*!< Hardware is In Secure or Default state. */ + RNG_STATUS_FAILED /*!< Hardware is In Failed state / other fatal + problem. Driver is still able to read/write + some registers, but cannot get Random + data. */ +} rng_status_t; + +static shw_queue_t rng_work_queue; + +/***************************************************************************** + * + * Function Declarations + * + *****************************************************************************/ + +/* kernel interface functions */ +OS_DEV_INIT_DCL(rng_init); +OS_DEV_TASK_DCL(rng_entropy_task); +OS_DEV_SHUTDOWN_DCL(rng_shutdown); +OS_DEV_ISR_DCL(rng_irq); + +#define RNG_ADD_QUEUE_ENTRY(pool, entry) \ + SHW_ADD_QUEUE_ENTRY(pool, (shw_queue_entry_t*)entry) + +#define RNG_REMOVE_QUEUE_ENTRY(pool, entry) \ + SHW_REMOVE_QUEUE_ENTRY(pool, (shw_queue_entry_t*)entry) +#define RNG_GET_WORK_ENTRY() \ + (rng_work_entry_t*)SHW_POP_FIRST_ENTRY(&rng_work_queue) + +/*! + * Add an work item to a work list. Item will be marked incomplete. + * + * @param work Work entry to place at tail of list. + * + * @return none + */ +inline static void RNG_ADD_WORK_ENTRY(rng_work_entry_t * work) +{ + work->completed = FALSE; + + SHW_ADD_QUEUE_ENTRY(&rng_work_queue, (shw_queue_entry_t *) work); + + os_dev_schedule_task(rng_entropy_task); +} + +/*! + * For #rng_check_register_accessible(), check read permission on given + * register. + */ +#define RNG_CHECK_READ 0 + +/*! + * For #rng_check_register_accessible(), check write permission on given + * register. + */ +#define RNG_CHECK_WRITE 1 + +/* Define different helper symbols based on RNG type */ +#ifdef FSL_HAVE_RNGA + +/****************************************************************************** + * + * RNGA support + * + *****************************************************************************/ + +/*! Interrupt number for driver. */ +#if defined(MXC_INT_RNG) +/* Most modern definition */ +#define INT_RNG MXC_INT_RNG +#elif defined(MXC_INT_RNGA) +#define INT_RNG MXC_INT_RNGA +#else +#define INT_RNG INT_RNGA +#endif + +/*! Base (bus?) address of RNG component. */ +#define RNG_BASE_ADDR RNGA_BASE_ADDR + +/*! Read and return the status register. */ +#define RNG_GET_STATUS() \ + RNG_READ_REGISTER(RNGA_STATUS) +/*! Configure RNG for Auto seeding */ +#define RNG_AUTO_SEED() +/* Put RNG for Seed Generation */ +#define RNG_SEED_GEN() +/*! + * Return RNG Type value. Should be RNG_TYPE_RNGA, RNG_TYPE_RNGB, + * or RNG_TYPE_RNGC. + */ +#define RNG_GET_RNG_TYPE() \ + ((RNG_READ_REGISTER(RNGA_CONTROL) & RNGA_CONTROL_RNG_TYPE_MASK) \ + >> RNGA_CONTROL_RNG_TYPE_SHIFT) + +/*! + * Verify Type value of RNG. + * + * Returns true of OK, false if not. + */ +#define RNG_VERIFY_TYPE(type) \ + ((type) == RNG_TYPE_RNGA) + +/*! Returns non-zero if RNG device is reporting an error. */ +#define RNG_HAS_ERROR() \ + (RNG_READ_REGISTER(RNGA_STATUS) & RNGA_STATUS_ERROR_INTERRUPT) +/*! Returns non-zero if Bad Key is selected */ +#define RNG_HAS_BAD_KEY() 0 +/*! Return non-zero if Self Test Done */ +#define RNG_SELF_TEST_DONE() 0 +/*! Returns non-zero if RNG ring oscillators have failed. */ +#define RNG_OSCILLATOR_FAILED() \ + (RNG_READ_REGISTER(RNGA_STATUS) & RNGA_STATUS_OSCILLATOR_DEAD) + +/*! Returns maximum number of 32-bit words in the RNG's output fifo. */ +#define RNG_GET_FIFO_SIZE() \ + ((RNG_READ_REGISTER(RNGA_STATUS) & RNGA_STATUS_OUTPUT_FIFO_SIZE_MASK) \ + >> RNGA_STATUS_OUTPUT_FIFO_SIZE_SHIFT) + +/*! Returns number of 32-bit words currently in the RNG's output fifo. */ +#define RNG_GET_WORDS_IN_FIFO() \ + ((RNG_READ_REGISTER(RNGA_STATUS) & RNGA_STATUS_OUTPUT_FIFO_LEVEL_MASK) \ + >> RNGA_STATUS_OUTPUT_FIFO_LEVEL_SHIFT) +/* Configuring RNG for Self Test */ +#define RNG_SELF_TEST() +/*! Get a random value from the RNG's output FIFO. */ +#define RNG_READ_FIFO() \ + RNG_READ_REGISTER(RNGA_OUTPUT_FIFO) + +/*! Put entropy into the RNG's algorithm. + * @param value 32-bit value to add to RNG's entropy. + **/ +#define RNG_ADD_ENTROPY(value) \ + RNG_WRITE_REGISTER(RNGA_ENTROPY, (value)) +/*! Return non-zero in case of Error during Self Test */ +#define RNG_CHECK_SELF_ERR() 0 +/*! Return non-zero in case of Error during Seed Generation */ +#define RNG_CHECK_SEED_ERR() 0 +/*! Get the RNG started at generating output. */ +#define RNG_GO() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_GO); \ +} +/*! To clear all Error Bits in Error Status Register */ +#define RNG_CLEAR_ERR() +/*! Put RNG into High Assurance mode */ +#define RNG_SET_HIGH_ASSURANCE() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_HIGH_ASSURANCE); \ +} + +/*! Return non-zero if the RNG is in High Assurance mode. */ +#define RNG_GET_HIGH_ASSURANCE() \ + (RNG_READ_REGISTER(RNGA_CONTROL) & RNGA_CONTROL_HIGH_ASSURANCE) + +/*! Clear all status, error and otherwise. */ +#define RNG_CLEAR_ALL_STATUS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_CLEAR_INTERRUPT); \ +} +/* Return non-zero if RESEED Required */ +#define RNG_RESEED() 1 + +/*! Return non-zero if Seeding is done */ +#define RNG_SEED_DONE() 1 + +/*! Return non-zero if everything seems OK with the RNG. */ +#define RNG_WORKING() \ + ((RNG_READ_REGISTER(RNGA_STATUS) \ + & (RNGA_STATUS_SLEEP | RNGA_STATUS_SECURITY_VIOLATION \ + | RNGA_STATUS_ERROR_INTERRUPT | RNGA_STATUS_FIFO_UNDERFLOW \ + | RNGA_STATUS_LAST_READ_STATUS )) == 0) + +/*! Put the RNG into sleep (low-power) mode. */ +#define RNG_SLEEP() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_SLEEP); \ +} + +/*! Wake the RNG from sleep (low-power) mode. */ +#define RNG_WAKE() \ +{ \ + uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control & ~RNGA_CONTROL_SLEEP); \ +} + +/*! Mask interrupts so that the driver/OS will not see them. */ +#define RNG_MASK_ALL_INTERRUPTS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_MASK_INTERRUPTS); \ +} + +/*! Unmask interrupts so that the driver/OS will see them. */ +#define RNG_UNMASK_ALL_INTERRUPTS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control & ~RNGA_CONTROL_MASK_INTERRUPTS);\ +} + +/*! + * @def RNG_PUT_RNG_TO_SLEEP() + * + * If compiled with #RNG_USE_LOW_POWER_MODE, this routine will put the RNG + * to sleep (low power mode). + * + * @return none + */ +/*! + * @def RNG_WAKE_RNG_FROM_SLEEP() + * + * If compiled with #RNG_USE_LOW_POWER_MODE, this routine will wake the RNG + * from sleep (low power mode). + * + * @return none + */ +#ifdef RNG_USE_LOW_POWER_MODE + +#define RNG_PUT_RNG_TO_SLEEP() \ + RNG_SLEEP() + +#define RNG_WAKE_FROM_SLEEP() \ + RNG_WAKE() 1 + +#else /* not low power mode */ + +#define RNG_PUT_RNG_TO_SLEEP() + +#define RNG_WAKE_FROM_SLEEP() + +#endif /* Use low-power mode */ + +#else /* FSL_HAVE_RNGB or FSL_HAVE_RNGC */ + +/****************************************************************************** + * + * RNGB and RNGC support + * + *****************************************************************************/ +/* + * The operational interfaces for RNGB and RNGC are almost identical, so + * the defines for RNGC work fine for both. There are minor differences + * which will be treated within this conditional block. + */ + +/*! Interrupt number for driver. */ +#if defined(MXC_INT_RNG) +/* Most modern definition */ +#define INT_RNG MXC_INT_RNG +#elif defined(MXC_INT_RNGC) +#define INT_RNG MXC_INT_RNGC +#elif defined(MXC_INT_RNGB) +#define INT_RNG MXC_INT_RNGB +#elif defined(INT_RNGC) +#define INT_RNG INT_RNGC +#else +#error NO_INTERRUPT_DEFINED +#endif + +/*! Base address of RNG component. */ +#ifdef FSL_HAVE_RNGB +#define RNG_BASE_ADDR RNGB_BASE_ADDR +#else +#define RNG_BASE_ADDR RNGC_BASE_ADDR +#endif + +/*! Read and return the status register. */ +#define RNG_GET_STATUS() \ + RNG_READ_REGISTER(RNGC_ERROR) + +/*! + * Return RNG Type value. Should be RNG_TYPE_RNGA or RNG_TYPE_RNGC. + */ +#define RNG_GET_RNG_TYPE() \ + ((RNG_READ_REGISTER(RNGC_VERSION_ID) & RNGC_VERID_RNG_TYPE_MASK) \ + >> RNGC_VERID_RNG_TYPE_SHIFT) + +/*! + * Verify Type value of RNG. + * + * Returns true of OK, false if not. + */ +#ifdef FSL_HAVE_RNGB +#define RNG_VERIFY_TYPE(type) \ + ((type) == RNG_TYPE_RNGB) +#else /* RNGC */ +#define RNG_VERIFY_TYPE(type) \ + ((type) == RNG_TYPE_RNGC) +#endif + +/*! Returns non-zero if RNG device is reporting an error. */ +#define RNG_HAS_ERROR() \ + (RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_ERROR) +/*! Returns non-zero if Bad Key is selected */ +#define RNG_HAS_BAD_KEY() \ + (RNG_READ_REGISTER(RNGC_ERROR) & RNGC_ERROR_STATUS_BAD_KEY) +/*! Returns non-zero if RNG ring oscillators have failed. */ +#define RNG_OSCILLATOR_FAILED() \ + (RNG_READ_REGISTER(RNGC_ERROR) & RNGC_ERROR_STATUS_OSC_ERR) + +/*! Returns maximum number of 32-bit words in the RNG's output fifo. */ +#define RNG_GET_FIFO_SIZE() \ + ((RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_FIFO_SIZE_MASK) \ + >> RNGC_STATUS_FIFO_SIZE_SHIFT) + +/*! Returns number of 32-bit words currently in the RNG's output fifo. */ +#define RNG_GET_WORDS_IN_FIFO() \ + ((RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_FIFO_LEVEL_MASK) \ + >> RNGC_STATUS_FIFO_LEVEL_SHIFT) + +/*! Get a random value from the RNG's output FIFO. */ +#define RNG_READ_FIFO() \ + RNG_READ_REGISTER(RNGC_FIFO) + +/*! Put entropy into the RNG's algorithm. + * @param value 32-bit value to add to RNG's entropy. + **/ +#ifdef FSL_HAVE_RNGB +#define RNG_ADD_ENTROPY(value) \ + RNG_WRITE_REGISTER(RNGB_ENTROPY, value) +#else /* RNGC does not have Entropy register */ +#define RNG_ADD_ENTROPY(value) +#endif +/*! Wake the RNG from sleep (low-power) mode. */ +#define RNG_WAKE() 1 +/*! Get the RNG started at generating output. */ +#define RNG_GO() +/*! Put RNG into High Assurance mode. */ +#define RNG_SET_HIGH_ASSURANCE() +/*! Returns non-zero in case of Error during Self Test */ +#define RNG_CHECK_SELF_ERR() \ + (RNG_READ_REGISTER(RNGC_ERROR) & RNGC_ERROR_STATUS_ST_ERR) +/*! Return non-zero in case of Error during Seed Generation */ +#define RNG_CHECK_SEED_ERR() \ + (RNG_READ_REGISTER(RNGC_ERROR) & RNGC_ERROR_STATUS_STAT_ERR) + +/*! Configure RNG for Self Test */ +#define RNG_SELF_TEST() \ +{ \ + register uint32_t command = RNG_READ_REGISTER(RNGC_COMMAND); \ + RNG_WRITE_REGISTER(RNGC_COMMAND, command \ + | RNGC_COMMAND_SELF_TEST); \ +} +/*! Clearing the Error bits in Error Status Register */ +#define RNG_CLEAR_ERR() \ +{ \ + register uint32_t command = RNG_READ_REGISTER(RNGC_COMMAND); \ + RNG_WRITE_REGISTER(RNGC_COMMAND, command \ + | RNGC_COMMAND_CLEAR_ERROR); \ +} + +/*! Return non-zero if Self Test Done */ +#define RNG_SELF_TEST_DONE() \ + (RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_ST_DONE) +/* Put RNG for SEED Generation */ +#define RNG_SEED_GEN() \ +{ \ + register uint32_t command = RNG_READ_REGISTER(RNGC_COMMAND); \ + RNG_WRITE_REGISTER(RNGC_COMMAND, command \ + | RNGC_COMMAND_SEED); \ +} +/* Return non-zero if RESEED Required */ +#define RNG_RESEED() \ + (RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_RESEED) + +/*! Return non-zero if the RNG is in High Assurance mode. */ +#define RNG_GET_HIGH_ASSURANCE() (RNG_READ_REGISTER(RNGC_STATUS) & \ + RNGC_STATUS_SEC_STATE) + +/*! Clear all status, error and otherwise. */ +#define RNG_CLEAR_ALL_STATUS() \ + RNG_WRITE_REGISTER(RNGC_COMMAND, \ + RNGC_COMMAND_CLEAR_INTERRUPT \ + | RNGC_COMMAND_CLEAR_ERROR) + +/*! Return non-zero if everything seems OK with the RNG. */ +#define RNG_WORKING() \ + ((RNG_READ_REGISTER(RNGC_ERROR) \ + & (RNGC_ERROR_STATUS_STAT_ERR | RNGC_ERROR_STATUS_RAND_ERR \ + | RNGC_ERROR_STATUS_FIFO_ERR | RNGC_ERROR_STATUS_ST_ERR | \ + RNGC_ERROR_STATUS_OSC_ERR | RNGC_ERROR_STATUS_LFSR_ERR )) == 0) +/*! Return Non zero if SEEDING is DONE */ +#define RNG_SEED_DONE() \ + ((RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_SEED_DONE) != 0) + +/*! Put the RNG into sleep (low-power) mode. */ +#define RNG_SLEEP() + +/*! Wake the RNG from sleep (low-power) mode. */ + +/*! Mask interrupts so that the driver/OS will not see them. */ +#define RNG_MASK_ALL_INTERRUPTS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGC_CONTROL); \ + RNG_WRITE_REGISTER(RNGC_CONTROL, control \ + | RNGC_CONTROL_MASK_DONE \ + | RNGC_CONTROL_MASK_ERROR); \ +} +/*! Configuring RNGC for self Test. */ + +#define RNG_AUTO_SEED() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGC_CONTROL); \ + RNG_WRITE_REGISTER(RNGC_CONTROL, control \ + | RNGC_CONTROL_AUTO_SEED); \ +} + +/*! Unmask interrupts so that the driver/OS will see them. */ +#define RNG_UNMASK_ALL_INTERRUPTS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGC_CONTROL); \ + RNG_WRITE_REGISTER(RNGC_CONTROL, \ + control & ~(RNGC_CONTROL_MASK_DONE|RNGC_CONTROL_MASK_ERROR)); \ +} + +/*! Put RNG to sleep if appropriate. */ +#define RNG_PUT_RNG_TO_SLEEP() + +/*! Wake RNG from sleep if necessary. */ +#define RNG_WAKE_FROM_SLEEP() + +#endif /* RNG TYPE */ + +/* internal functions */ +static os_error_code rng_map_RNG_memory(void); +static os_error_code rng_setup_interrupt_handling(void); +#ifdef RNG_REGISTER_PEEK_POKE +inline static int rng_check_register_offset(uint32_t offset); +inline static int rng_check_register_accessible(uint32_t offset, + int access_write); +#endif /* DEBUG_RNG_REGISTERS */ +static fsl_shw_return_t rng_drain_fifo(uint32_t * random_p, int count_words); +static os_error_code rng_grab_config_values(void); +static void rng_cleanup(void); + +#ifdef FSL_HAVE_RNGA +static void rng_sec_failure(void); +#endif + +#ifdef RNG_REGISTER_DEBUG +static uint32_t dbg_rng_read_register(uint32_t offset); +static void dbg_rng_write_register(uint32_t offset, uint32_t value); +#endif + +#if defined(LINUX_VERSION_CODE) + +EXPORT_SYMBOL(fsl_shw_add_entropy); +EXPORT_SYMBOL(fsl_shw_get_random); + +#ifdef RNG_REGISTER_PEEK_POKE +/* For Linux kernel, export the API functions to other kernel modules */ +EXPORT_SYMBOL(rng_read_register); +EXPORT_SYMBOL(rng_write_register); +#endif /* DEBUG_RNG_REGISTERS */ + + + +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Device Driver for RNG"); + +#endif /* LINUX_VERSION_CODE */ + +#endif /* RNG_INTERNALS_H */ diff --git a/drivers/mxc/security/rng/include/rng_rnga.h b/drivers/mxc/security/rng/include/rng_rnga.h new file mode 100644 index 000000000000..971064d51522 --- /dev/null +++ b/drivers/mxc/security/rng/include/rng_rnga.h @@ -0,0 +1,181 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef RNG_RNGA_H +#define RNG_RNGA_H + +/*! @defgroup rngaregs RNGA Registers + * @ingroup RNG + * These are the definitions for the RNG registers and their offsets + * within the RNG. They are used in the @c register_offset parameter of + * #rng_read_register() and #rng_write_register(). + */ +/*! @addtogroup rngaregs */ +/*! @{ */ + +/*! Control Register. See @ref rngacontrolreg. */ +#define RNGA_CONTROL 0x00 +/*! Status Register. See @ref rngastatusreg. */ +#define RNGA_STATUS 0x04 +/*! Register for adding to the Entropy of the RNG */ +#define RNGA_ENTROPY 0x08 +/*! Register containing latest 32 bits of random value */ +#define RNGA_OUTPUT_FIFO 0x0c +/*! Mode Register. Non-secure mode access only. See @ref rngmodereg. */ +#define RNGA_MODE 0x10 +/*! Verification Control Register. Non-secure mode access only. See + * @ref rngvfctlreg. */ +#define RNGA_VERIFICATION_CONTROL 0x14 +/*! Oscillator Control Counter Register. Non-secure mode access only. + * See @ref rngosccntctlreg. */ +#define RNGA_OSCILLATOR_CONTROL_COUNTER 0x18 +/*! Oscillator 1 Counter Register. Non-secure mode access only. See + * @ref rngosccntreg. */ +#define RNGA_OSCILLATOR1_COUNTER 0x1c +/*! Oscillator 2 Counter Register. Non-secure mode access only. See + * @ref rngosccntreg. */ +#define RNGA_OSCILLATOR2_COUNTER 0x20 +/*! Oscillator Counter Status Register. Non-secure mode access only. See + * @ref rngosccntstatreg. */ +#define RNGA_OSCILLATOR_COUNTER_STATUS 0x24 +/*! @} */ + +/*! Total address space of the RNGA, in bytes */ +#define RNG_ADDRESS_RANGE 0x28 + +/*! @defgroup rngacontrolreg RNGA Control Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngacontrolreg */ +/*! @{ */ +/*! These bits are unimplemented or reserved */ +#define RNGA_CONTROL_ZEROS_MASK 0x0fffffe0 +/*! 'RNG type' - should be 0 for RNGA */ +#define RNGA_CONTROL_RNG_TYPE_MASK 0xf0000000 +/*! Number of bits to shift the type to get it to LSB */ +#define RNGA_CONTROL_RNG_TYPE_SHIFT 28 +/*! Put RNG to sleep */ +#define RNGA_CONTROL_SLEEP 0x00000010 +/*! Clear interrupt & status */ +#define RNGA_CONTROL_CLEAR_INTERRUPT 0x00000008 +/*! Mask interrupt generation */ +#define RNGA_CONTROL_MASK_INTERRUPTS 0x00000004 +/*! Enter into Secure Mode. Notify SCC of security violation should FIFO + * underflow occur. */ +#define RNGA_CONTROL_HIGH_ASSURANCE 0x00000002 +/*! Load data into FIFO */ +#define RNGA_CONTROL_GO 0x00000001 +/*! @} */ + +/*! @defgroup rngastatusreg RNGA Status Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngastatusreg */ +/*! @{ */ +/*! RNG Oscillator not working */ +#define RNGA_STATUS_OSCILLATOR_DEAD 0x80000000 +/*! These bits are undefined or reserved */ +#define RNGA_STATUS_ZEROS1_MASK 0x7f000000 +/*! How big FIFO is, in bytes */ +#define RNGA_STATUS_OUTPUT_FIFO_SIZE_MASK 0x00ff0000 +/*! How many bits right to shift fifo size to make it LSB */ +#define RNGA_STATUS_OUTPUT_FIFO_SIZE_SHIFT 16 +/*! How many bytes are currently in the FIFO */ +#define RNGA_STATUS_OUTPUT_FIFO_LEVEL_MASK 0x0000ff00 +/*! How many bits right to shift fifo level to make it LSB */ +#define RNGA_STATUS_OUTPUT_FIFO_LEVEL_SHIFT 8 +/*! These bits are undefined or reserved. */ +#define RNGA_STATUS_ZEROS2_MASK 0x000000e0 +/*! RNG is sleeping. */ +#define RNGA_STATUS_SLEEP 0x00000010 +/*! Error detected. */ +#define RNGA_STATUS_ERROR_INTERRUPT 0x00000008 +/*! FIFO was empty on some read since last status read. */ +#define RNGA_STATUS_FIFO_UNDERFLOW 0x00000004 +/*! FIFO was empty on most recent read. */ +#define RNGA_STATUS_LAST_READ_STATUS 0x00000002 +/*! Security violation occurred. Will only happen in High Assurance mode. */ +#define RNGA_STATUS_SECURITY_VIOLATION 0x00000001 +/*! @} */ + +/*! @defgroup rngmodereg RNG Mode Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngmodereg */ +/*! @{ */ +/*! These bits are undefined or reserved */ +#define RNGA_MODE_ZEROS_MASK 0xfffffffc +/*! RNG is in / put RNG in Oscillator Frequency Test Mode. */ +#define RNGA_MODE_OSCILLATOR_FREQ_TEST 0x00000002 +/*! Put RNG in verification mode / RNG is in verification mode. */ +#define RNGA_MODE_VERIFICATION 0x00000001 +/*! @} */ + +/*! @defgroup rngvfctlreg RNG Verification Control Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngvfctlreg */ +/*! @{ */ +/*! These bits are undefined or reserved. */ +#define RNGA_VFCTL_ZEROS_MASK 0xfffffff8 +/*! Reset the shift registers. */ +#define RNGA_VFCTL_RESET_SHIFT_REGISTERS 0x00000004 +/*! Drive shift registers from system clock. */ +#define RNGA_VFCTL_FORCE_SYSTEM_CLOCK 0x00000002 +/*! Turn off shift register clocks. */ +#define RNGA_VFCTL_SHIFT_CLOCK_OFF 0x00000001 +/*! @} */ + +/*! + * @defgroup rngosccntctlreg RNG Oscillator Counter Control Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngosccntctlreg */ +/*! @{ */ +/*! These bits are undefined or reserved. */ +#define RNGA_OSCCR_ZEROS_MASK 0xfffc0000 +/*! Bits containing clock cycle counter */ +#define RNGA_OSCCR_CLOCK_CYCLES_MASK 0x0003ffff +/*! Bits to shift right RNG_OSCCR_CLOCK_CYCLES_MASK */ +#define RNGA_OSCCR_CLOCK_CYCLES_SHIFT 0 +/*! @} */ + +/*! + * @defgroup rngosccntreg RNG Oscillator (1 and 2) Counter Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngosccntreg */ +/*! @{ */ +/*! These bits are undefined or reserved. */ +#define RNGA_COUNTER_ZEROS_MASK 0xfff00000 +/*! Bits containing number of clock pulses received from the oscillator. */ +#define RNGA_COUNTER_PULSES_MASK 0x000fffff +/*! Bits right to shift RNG_COUNTER_PULSES_MASK to make it LSB. */ +#define RNGA_COUNTER_PULSES_SHIFT 0 +/*! @} */ + +/*! + * @defgroup rngosccntstatreg RNG Oscillator Counter Status Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngosccntstatreg */ +/*! @{ */ +/*! These bits are undefined or reserved. */ +#define RNGA_COUNTER_STATUS_ZEROS_MASK 0xfffffffc +/*! Oscillator 2 has toggled 0x400 times */ +#define RNGA_COUNTER_STATUS_OSCILLATOR2 0x00000002 +/*! Oscillator 1 has toggled 0x400 times */ +#define RNGA_COUNTER_STATUS_OSCILLATOR1 0x00000001 +/*! @} */ + +#endif /* RNG_RNGA_H */ diff --git a/drivers/mxc/security/rng/include/rng_rngc.h b/drivers/mxc/security/rng/include/rng_rngc.h new file mode 100644 index 000000000000..68effa622b87 --- /dev/null +++ b/drivers/mxc/security/rng/include/rng_rngc.h @@ -0,0 +1,235 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @file rng_rngc.h + * + * Definition of the registers for the RNGB and RNGC. The names start with + * RNGC where they are in common or relate only to the RNGC; the RNGB-only + * definitions begin with RNGB. + * + */ + +#ifndef RNG_RNGC_H +#define RNG_RNGC_H + +#define RNGC_VERSION_MAJOR3 3 + +/*! @defgroup rngcregs RNGB/RNGC Registers + * These are the definitions for the RNG registers and their offsets + * within the RNG. They are used in the @c register_offset parameter of + * #rng_read_register() and #rng_write_register(). + * + * @ingroup RNG + */ +/*! @addtogroup rngcregs */ +/*! @{ */ + +/*! RNGC Version ID Register R/W */ +#define RNGC_VERSION_ID 0x0000 +/*! RNGC Command Register R/W */ +#define RNGC_COMMAND 0x0004 +/*! RNGC Control Register R/W */ +#define RNGC_CONTROL 0x0008 +/*! RNGC Status Register R */ +#define RNGC_STATUS 0x000C +/*! RNGC Error Status Register R */ +#define RNGC_ERROR 0x0010 +/*! RNGC FIFO Register W */ +#define RNGC_FIFO 0x0014 +/*! Undefined */ +#define RNGC_UNDEF_18 0x0018 +/*! RNGB Entropy Register W */ +#define RNGB_ENTROPY 0x0018 +/*! Undefined */ +#define RNGC_UNDEF_1C 0x001C +/*! RNGC Verification Control Register1 R/W */ +#define RNGC_VERIFICATION_CONTROL 0x0020 +/*! Undefined */ +#define RNGC_UNDEF_24 0x0024 +/*! RNGB XKEY Data Register R */ +#define RNGB_XKEY 0x0024 +/*! RNGC Oscillator Counter Control Register1 R/W */ +#define RNGC_OSC_COUNTER_CONTROL 0x0028 +/*! RNGC Oscillator Counter Register1 R */ +#define RNGC_OSC_COUNTER 0x002C +/*! RNGC Oscillator Counter Status Register1 R */ +#define RNGC_OSC_COUNTER_STATUS 0x0030 +/*! @} */ + +/*! @defgroup rngcveridreg RNGB/RNGC Version ID Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngcveridreg */ +/*! @{ */ +/*! These bits are unimplemented or reserved */ +#define RNGC_VERID_ZEROS_MASK 0x0f000000 +/*! Mask for RNG TYPE */ +#define RNGC_VERID_RNG_TYPE_MASK 0xf0000000 +/*! Shift to make RNG TYPE be LSB */ +#define RNGC_VERID_RNG_TYPE_SHIFT 28 +/*! Mask for RNG Chip Version */ +#define RNGC_VERID_CHIP_VERSION_MASK 0x00ff0000 +/*! Shift to make RNG Chip version be LSB */ +#define RNGC_VERID_CHIP_VERSION_SHIFT 16 +/*! Mask for RNG Major Version */ +#define RNGC_VERID_VERSION_MAJOR_MASK 0x0000ff00 +/*! Shift to make RNG Major version be LSB */ +#define RNGC_VERID_VERSION_MAJOR_SHIFT 8 +/*! Mask for RNG Minor Version */ +#define RNGC_VERID_VERSION_MINOR_MASK 0x000000ff +/*! Shift to make RNG Minor version be LSB */ +#define RNGC_VERID_VERSION_MINOR_SHIFT 0 +/*! @} */ + +/*! @defgroup rngccommandreg RNGB/RNGC Command Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngccommandreg */ +/*! @{ */ +/*! These bits are unimplemented or reserved. */ +#define RNGC_COMMAND_ZEROS_MASK 0xffffff8c +/*! Perform a software reset of the RNGC. */ +#define RNGC_COMMAND_SOFTWARE_RESET 0x00000040 +/*! Clear error from Error Status register (and interrupt). */ +#define RNGC_COMMAND_CLEAR_ERROR 0x00000020 +/*! Clear interrupt & status. */ +#define RNGC_COMMAND_CLEAR_INTERRUPT 0x00000010 +/*! Start RNGC seed generation. */ +#define RNGC_COMMAND_SEED 0x00000002 +/*! Perform a self test of (and reset) the RNGC. */ +#define RNGC_COMMAND_SELF_TEST 0x00000001 +/*! @} */ + +/*! @defgroup rngccontrolreg RNGB/RNGC Control Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngccontrolreg */ +/*! @{ */ +/*! These bits are unimplemented or reserved */ +#define RNGC_CONTROL_ZEROS_MASK 0xfffffc8c +/*! Allow access to verification registers. */ +#define RNGC_CONTROL_CTL_ACC 0x00000200 +/*! Put RNGC into deterministic verifcation mode. */ +#define RNGC_CONTROL_VERIF_MODE 0x00000100 +/*! Prevent RNGC from generating interrupts caused by errors. */ +#define RNGC_CONTROL_MASK_ERROR 0x00000040 + +/*! + * Prevent RNGB/RNGC from generating interrupts after Seed Done or Self Test + * Mode completion. + */ +#define RNGC_CONTROL_MASK_DONE 0x00000020 +/*! Allow RNGC to generate a new seed whenever it is needed. */ +#define RNGC_CONTROL_AUTO_SEED 0x00000010 +/*! Set FIFO Underflow Response.*/ +#define RNGC_CONTROL_FIFO_UFLOW_MASK 0x00000003 +/*! Shift value to make FIFO Underflow Response be LSB. */ +#define RNGC_CONTROL_FIFO_UFLOW_SHIFT 0 + +/*! @} */ + +/*! @{ */ +/*! FIFO Underflow should cause ... */ +#define RNGC_CONTROL_FIFO_UFLOW_ZEROS_ERROR 0 +/*! FIFO Underflow should cause ... */ +#define RNGC_CONTROL_FIFO_UFLOW_ZEROS_ERROR2 1 +/*! FIFO Underflow should cause ... */ +#define RNGC_CONTROL_FIFO_UFLOW_BUS_XFR 2 +/*! FIFO Underflow should cause ... */ +#define RNGC_CONTROL_FIFO_UFLOW_ZEROS_INTR 3 +/*! @} */ + +/*! @defgroup rngcstatusreg RNGB/RNGC Status Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngcstatusreg */ +/*! @{ */ +/*! Unused or MBZ. */ +#define RNGC_STATUS_ZEROS_MASK 0x003e0080 +/*! + * Statistical tests pass-fail. Individual bits on indicate failure of a + * particular test. + */ +#define RNGC_STATUS_STAT_TEST_PF_MASK 0xff000000 +/*! Mask to get Statistical PF to be LSB. */ +#define RNGC_STATUS_STAT_TEST_PF_SHIFT 24 +/*! + * Self tests pass-fail. Individual bits on indicate failure of a + * particular test. + */ +#define RNGC_STATUS_ST_PF_MASK 0x00c00000 +/*! Shift value to get Self Test PF field to be LSB. */ +#define RNGC_STATUS_ST_PF_SHIFT 22 +/* TRNG Self test pass-fail */ +#define RNGC_STATUS_ST_PF_TRNG 0x00800000 +/* PRNG Self test pass-fail */ +#define RNGC_STATUS_ST_PF_PRNG 0x00400000 +/*! Error detected in RNGC. See Error Status register. */ +#define RNGC_STATUS_ERROR 0x00010000 +/*! Size of the internal FIFO in 32-bit words. */ +#define RNGC_STATUS_FIFO_SIZE_MASK 0x0000f000 +/*! Shift value to get FIFO Size to be LSB. */ +#define RNGC_STATUS_FIFO_SIZE_SHIFT 12 +/*! The level (available data) of the internal FIFO in 32-bit words. */ +#define RNGC_STATUS_FIFO_LEVEL_MASK 0x00000f00 +/*! Shift value to get FIFO Level to be LSB. */ +#define RNGC_STATUS_FIFO_LEVEL_SHIFT 8 +/*! A new seed is ready for use. */ +#define RNGC_STATUS_NEXT_SEED_DONE 0x00000040 +/*! The first seed has been generated. */ +#define RNGC_STATUS_SEED_DONE 0x00000020 +/*! Self Test has been completed. */ +#define RNGC_STATUS_ST_DONE 0x00000010 +/*! Reseed is necessary. */ +#define RNGC_STATUS_RESEED 0x00000008 +/*! RNGC is sleeping. */ +#define RNGC_STATUS_SLEEP 0x00000004 +/*! RNGC is currently generating numbers, seeding, generating next seed, or + performing a self test. */ +#define RNGC_STATUS_BUSY 0x00000002 +/*! RNGC is in secure state. */ +#define RNGC_STATUS_SEC_STATE 0x00000001 + +/*! @} */ + +/*! @defgroup rngcerrstatusreg RNGB/RNGC Error Status Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngcerrstatusreg */ +/*! @{ */ +/*! Unused or MBZ. */ +#define RNGC_ERROR_STATUS_ZEROS_MASK 0xffffff80 +/*! Bad Key Error Status */ +#define RNGC_ERROR_STATUS_BAD_KEY 0x00000040 +/*! Random Compare Error. Previous number matched the current number. */ +#define RNGC_ERROR_STATUS_RAND_ERR 0x00000020 +/*! FIFO Underflow. FIFO was read while empty. */ +#define RNGC_ERROR_STATUS_FIFO_ERR 0x00000010 +/*! Statistic Error Statistic Test failed for the last seed. */ +#define RNGC_ERROR_STATUS_STAT_ERR 0x00000008 +/*! Self-test error. Some self test has failed. */ +#define RNGC_ERROR_STATUS_ST_ERR 0x00000004 +/*! + * Oscillator Error. The oscillator may be broken. Clear by hard or soft + * reset. + */ +#define RNGC_ERROR_STATUS_OSC_ERR 0x00000002 +/*! LFSR Error. Clear by hard or soft reset. */ +#define RNGC_ERROR_STATUS_LFSR_ERR 0x00000001 + +/*! @} */ + +/*! Total address space of the RNGB/RNGC registers, in bytes */ +#define RNG_ADDRESS_RANGE 0x34 + +#endif /* RNG_RNGC_H */ diff --git a/drivers/mxc/security/rng/include/shw_driver.h b/drivers/mxc/security/rng/include/shw_driver.h new file mode 100644 index 000000000000..cdf0cb3b1b0f --- /dev/null +++ b/drivers/mxc/security/rng/include/shw_driver.h @@ -0,0 +1,2971 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef SHW_DRIVER_H +#define SHW_DRIVER_H + +/* This is a Linux flag meaning 'compiling kernel code'... */ +#ifndef __KERNEL__ +#include <inttypes.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <memory.h> +#include <stdio.h> +#else +#include "../../sahara2/include/portable_os.h" +#endif /* __KERNEL__ */ + +#include "../../sahara2/include/fsl_platform.h" + +/*! @file shw_driver.h + * + * @brief Header file to use the SHW driver. + * + * The SHW driver is used in two modes: By a user, from the FSL SHW API in user + * space, which goes through /dev/fsl_shw to make open(), ioctl(), and close() + * calls; and by other kernel modules/drivers, which use the FSL SHW API, parts + * of which are supported directly by the SHW driver. + * + * Testing is performed by using the apitest and kernel api test routines + * developed for the Sahara2 driver. + */ +/*#define DIAG_SECURITY_FUNC*/ +/*! Perform a security function. */ +#define SHW_IOCTL_REQUEST 21 + +/* This definition may need a new name, and needs to go somewhere which + * can determine platform, kernel vs. user, os, etc. + */ +#define copy_bytes(out, in, len) memcpy(out, in, len) + + +/*! + * This is part of the IOCTL request type passed between kernel and user space. + * It is added to #SHW_IOCTL_REQUEST to generate the actual value. + */ +typedef enum shw_user_request_t { + SHW_USER_REQ_REGISTER_USER, /*!< Initialize user-kernel discussion. */ + SHW_USER_REQ_DEREGISTER_USER, /*!< Terminate user-kernel discussion. */ + SHW_USER_REQ_GET_RESULTS, /*!< Get information on outstanding + results. */ + SHW_USER_REQ_GET_CAPABILITIES, /*!< Get information on hardware support. */ + SHW_USER_REQ_GET_RANDOM, /*!< Get random data from RNG. */ + SHW_USER_REQ_ADD_ENTROPY, /*!< Add entropy to hardware RNG. */ + SHW_USER_REQ_DROP_PERMS, /*!< Diminish the permissions of a block of + secure memory */ + SHW_USER_REQ_SSTATUS, /*!< Check the status of a block of secure + memory */ + SHW_USER_REQ_SFREE, /*!< Free a block of secure memory */ + SHW_USER_REQ_SCC_ENCRYPT, /*!< Encrypt a region of user-owned secure + memory */ + SHW_USER_REQ_SCC_DECRYPT, /*!< Decrypt a region of user-owned secure + memory */ +} shw_user_request_t; + + +/*! + * @typedef scc_partition_status_t + */ +/** Partition status information. */ +typedef enum fsl_shw_partition_status_t { + FSL_PART_S_UNUSABLE, /*!< Partition not implemented */ + FSL_PART_S_UNAVAILABLE, /*!< Partition owned by other host */ + FSL_PART_S_AVAILABLE, /*!< Partition available */ + FSL_PART_S_ALLOCATED, /*!< Partition owned by host but not engaged + */ + FSL_PART_S_ENGAGED, /*!< Partition owned by host and engaged */ +} fsl_shw_partition_status_t; + + +/* + * Structure passed during user ioctl() calls to manage secure partitions. + */ +typedef struct scc_partition_info_t { + uint32_t user_base; /*!< Userspace pointer to base of partition */ + uint32_t permissions; /*!< Permissions to give the partition (only + used in call to _DROP_PERMS) */ + fsl_shw_partition_status_t status; /*!< Status of the partition */ +} scc_partition_info_t; + + +/****************************************************************************** + * Enumerations + *****************************************************************************/ +/*! + * Flags for the state of the User Context Object (#fsl_shw_uco_t). + */ +typedef enum fsl_shw_user_ctx_flags_t +{ + /*! + * API will block the caller until operation completes. The result will be + * available in the return code. If this is not set, user will have to get + * results using #fsl_shw_get_results(). + */ + FSL_UCO_BLOCKING_MODE = 0x01, + /*! + * User wants callback (at the function specified with + * #fsl_shw_uco_set_callback()) when the operation completes. This flag is + * valid only if #FSL_UCO_BLOCKING_MODE is not set. + */ + FSL_UCO_CALLBACK_MODE = 0x02, + /*! Do not free descriptor chain after driver (adaptor) finishes */ + FSL_UCO_SAVE_DESC_CHAIN = 0x04, + /*! + * User has made at least one request with callbacks requested, so API is + * ready to handle others. + */ + FSL_UCO_CALLBACK_SETUP_COMPLETE = 0x08, + /*! + * (virtual) pointer to descriptor chain is completely linked with physical + * (DMA) addresses, ready for the hardware. This flag should not be used + * by FSL SHW API programs. + */ + FSL_UCO_CHAIN_PREPHYSICALIZED = 0x10, + /*! + * The user has changed the context but the changes have not been copied to + * the kernel driver. + */ + FSL_UCO_CONTEXT_CHANGED = 0x20, + /*! Internal Use. This context belongs to a user-mode API user. */ + FSL_UCO_USERMODE_USER = 0x40, +} fsl_shw_user_ctx_flags_t; + + +/*! + * Return code for FSL_SHW library. + * + * These codes may be returned from a function call. In non-blocking mode, + * they will appear as the status in a Result Object. + */ +/* REQ-FSLSHW-ERR-001 */ +typedef enum fsl_shw_return_t +{ + /*! + * No error. As a function return code in Non-blocking mode, this may + * simply mean that the operation was accepted for eventual execution. + */ + FSL_RETURN_OK_S = 0, + /*! Failure for non-specific reason. */ + FSL_RETURN_ERROR_S, + /*! + * Operation failed because some resource was not able to be allocated. + */ + FSL_RETURN_NO_RESOURCE_S, + /*! Crypto algorithm unrecognized or improper. */ + FSL_RETURN_BAD_ALGORITHM_S, + /*! Crypto mode unrecognized or improper. */ + FSL_RETURN_BAD_MODE_S, + /*! Flag setting unrecognized or inconsistent. */ + FSL_RETURN_BAD_FLAG_S, + /*! Improper or unsupported key length for algorithm. */ + FSL_RETURN_BAD_KEY_LENGTH_S, + /*! Improper parity in a (DES, TDES) key. */ + FSL_RETURN_BAD_KEY_PARITY_S, + /*! + * Improper or unsupported data length for algorithm or internal buffer. + */ + FSL_RETURN_BAD_DATA_LENGTH_S, + /*! Authentication / Integrity Check code check failed. */ + FSL_RETURN_AUTH_FAILED_S, + /*! A memory error occurred. */ + FSL_RETURN_MEMORY_ERROR_S, + /*! An error internal to the hardware occurred. */ + FSL_RETURN_INTERNAL_ERROR_S, + /*! ECC detected Point at Infinity */ + FSL_RETURN_POINT_AT_INFINITY_S, + /*! ECC detected No Point at Infinity */ + FSL_RETURN_POINT_NOT_AT_INFINITY_S, + /*! GCD is One */ + FSL_RETURN_GCD_IS_ONE_S, + /*! GCD is not One */ + FSL_RETURN_GCD_IS_NOT_ONE_S, + /*! Candidate is Prime */ + FSL_RETURN_PRIME_S, + /*! Candidate is not Prime */ + FSL_RETURN_NOT_PRIME_S, + /*! N register loaded improperly with even value */ + FSL_RETURN_EVEN_MODULUS_ERROR_S, + /*! Divisor is zero. */ + FSL_RETURN_DIVIDE_BY_ZERO_ERROR_S, + /*! Bad Exponent or Scalar value for Point Multiply */ + FSL_RETURN_BAD_EXPONENT_ERROR_S, + /*! RNG hardware problem. */ + FSL_RETURN_OSCILLATOR_ERROR_S, + /*! RNG hardware problem. */ + FSL_RETURN_STATISTICS_ERROR_S, +} fsl_shw_return_t; + + +/*! + * Algorithm Identifier. + * + * Selection of algorithm will determine how large the block size of the + * algorithm is. Context size is the same length unless otherwise specified. + * Selection of algorithm also affects the allowable key length. + */ +typedef enum fsl_shw_key_alg_t +{ + /*! + * Key will be used to perform an HMAC. Key size is 1 to 64 octets. Block + * size is 64 octets. + */ + FSL_KEY_ALG_HMAC, + /*! + * Advanced Encryption Standard (Rijndael). Block size is 16 octets. Key + * size is 16 octets. (The single choice of key size is a Sahara platform + * limitation.) + */ + FSL_KEY_ALG_AES, + /*! + * Data Encryption Standard. Block size is 8 octets. Key size is 8 + * octets. + */ + FSL_KEY_ALG_DES, + /*! + * 2- or 3-key Triple DES. Block size is 8 octets. Key size is 16 octets + * for 2-key Triple DES, and 24 octets for 3-key. + */ + FSL_KEY_ALG_TDES, + /*! + * ARC4. No block size. Context size is 259 octets. Allowed key size is + * 1-16 octets. (The choices for key size are a Sahara platform + * limitation.) + */ + FSL_KEY_ALG_ARC4, +} fsl_shw_key_alg_t; + + +/*! + * Mode selector for Symmetric Ciphers. + * + * The selection of mode determines how a cryptographic algorithm will be + * used to process the plaintext or ciphertext. + * + * For all modes which are run block-by-block (that is, all but + * #FSL_SYM_MODE_STREAM), any partial operations must be performed on a text + * length which is multiple of the block size. Except for #FSL_SYM_MODE_CTR, + * these block-by-block algorithms must also be passed a total number of octets + * which is a multiple of the block size. + * + * In modes which require that the total number of octets of data be a multiple + * of the block size (#FSL_SYM_MODE_ECB and #FSL_SYM_MODE_CBC), and the user + * has a total number of octets which are not a multiple of the block size, the + * user must perform any necessary padding to get to the correct data length. + */ +typedef enum fsl_shw_sym_mode_t +{ + /*! + * Stream. There is no associated block size. Any request to process data + * may be of any length. This mode is only for ARC4 operations, and is + * also the only mode used for ARC4. + */ + FSL_SYM_MODE_STREAM, + + /*! + * Electronic Codebook. Each block of data is encrypted/decrypted. The + * length of the data stream must be a multiple of the block size. This + * mode may be used for DES, 3DES, and AES. The block size is determined + * by the algorithm. + */ + FSL_SYM_MODE_ECB, + /*! + * Cipher-Block Chaining. Each block of data is encrypted/decrypted and + * then "chained" with the previous block by an XOR function. Requires + * context to start the XOR (previous block). This mode may be used for + * DES, 3DES, and AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CBC, + /*! + * Counter. The counter is encrypted, then XORed with a block of data. + * The counter is then incremented (using modulus arithmetic) for the next + * block. The final operation may be non-multiple of block size. This mode + * may be used for AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CTR, +} fsl_shw_sym_mode_t; + + +/*! + * Algorithm selector for Cryptographic Hash functions. + * + * Selection of algorithm determines how large the context and digest will be. + * Context is the same size as the digest (resulting hash), unless otherwise + * specified. + */ +typedef enum fsl_shw_hash_alg_t +{ + FSL_HASH_ALG_MD5, /*!< MD5 algorithm. Digest is 16 octets. */ + FSL_HASH_ALG_SHA1, /*!< SHA-1 (aka SHA or SHA-160) algorithm. + Digest is 20 octets. */ + FSL_HASH_ALG_SHA224, /*!< SHA-224 algorithm. Digest is 28 octets, + though context is 32 octets. */ + FSL_HASH_ALG_SHA256 /*!< SHA-256 algorithm. Digest is 32 + octets. */ +} fsl_shw_hash_alg_t; + + +/*! + * The type of Authentication-Cipher function which will be performed. + */ +typedef enum fsl_shw_acc_mode_t +{ + /*! + * CBC-MAC for Counter. Requires context and modulus. Final operation may + * be non-multiple of block size. This mode may be used for AES. + */ + FSL_ACC_MODE_CCM, + /*! + * SSL mode. Not supported. Combines HMAC and encrypt (or decrypt). + * Needs one key object for encryption, another for the HMAC. The usual + * hashing and symmetric encryption algorithms are supported. + */ + FSL_ACC_MODE_SSL +} fsl_shw_acc_mode_t; + + +/* REQ-FSLSHW-PINTFC-COA-HCO-001 */ +/*! + * Flags which control a Hash operation. + */ +typedef enum fsl_shw_hash_ctx_flags_t +{ + FSL_HASH_FLAGS_INIT = 0x01, /*!< Context is empty. Hash is started + from scratch, with a message-processed + count of zero. */ + FSL_HASH_FLAGS_SAVE = 0x02, /*!< Retrieve context from hardware after + hashing. If used with the + #FSL_HASH_FLAGS_FINALIZE flag, the final + digest value will be saved in the + object. */ + FSL_HASH_FLAGS_LOAD = 0x04, /*!< Place context into hardware before + hashing. */ + FSL_HASH_FLAGS_FINALIZE = 0x08, /*!< PAD message and perform final digest + operation. If user message is + pre-padded, this flag should not be + used. */ +} fsl_shw_hash_ctx_flags_t; + + +/*! + * Flags which control an HMAC operation. + * + * These may be combined by ORing them together. See #fsl_shw_hmco_set_flags() + * and #fsl_shw_hmco_clear_flags(). + */ +typedef enum fsl_shw_hmac_ctx_flags_t +{ + FSL_HMAC_FLAGS_INIT = 1, /**< Message context is empty. HMAC is + started from scratch (with key) or from + precompute of inner hash, depending on + whether + #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT is + set. */ + FSL_HMAC_FLAGS_SAVE = 2, /**< Retrieve ongoing context from hardware + after hashing. If used with the + #FSL_HMAC_FLAGS_FINALIZE flag, the final + digest value (HMAC) will be saved in the + object. */ + FSL_HMAC_FLAGS_LOAD = 4, /**< Place ongoing context into hardware + before hashing. */ + FSL_HMAC_FLAGS_FINALIZE = 8, /**< PAD message and perform final HMAC + operations of inner and outer hashes. */ + FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT = 16 /**< This means that the context + contains precomputed inner and outer + hash values. */ +} fsl_shw_hmac_ctx_flags_t; + + +/** + * Flags to control use of the #fsl_shw_scco_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_scco_set_flags() and #fsl_shw_scco_clear_flags() + */ +typedef enum fsl_shw_sym_ctx_flags_t +{ + /** + * Context is empty. In ARC4, this means that the S-Box needs to be + * generated from the key. In #FSL_SYM_MODE_CBC mode, this allows an IV of + * zero to be specified. In #FSL_SYM_MODE_CTR mode, it means that an + * initial CTR value of zero is desired. + */ + FSL_SYM_CTX_INIT = 1, + /** + * Load context from object into hardware before running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_LOAD = 2, + /** + * Save context from hardware into object after running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_SAVE = 4, + /** + * Context (SBox) is to be unwrapped and wrapped on each use. + * This flag is unsupported. + * */ + FSL_SYM_CTX_PROTECT = 8, +} fsl_shw_sym_ctx_flags_t; + + +/** + * Flags which describe the state of the #fsl_shw_sko_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_sko_set_flags() and #fsl_shw_sko_clear_flags() + */ +typedef enum fsl_shw_key_flags_t +{ + FSL_SKO_KEY_IGNORE_PARITY = 1, /*!< If algorithm is DES or 3DES, do not + validate the key parity bits. */ + FSL_SKO_KEY_PRESENT = 2, /*!< Clear key is present in the object. */ + FSL_SKO_KEY_ESTABLISHED = 4, /*!< Key has been established for use. This + feature is not available for all + platforms, nor for all algorithms and + modes.*/ + FSL_SKO_USE_SECRET_KEY = 8, /*!< Use device-unique key. Not always + available. */ + FSL_SKO_KEY_SW_KEY = 16, /*!< Clear key can be provided to the user */ + FSL_SKO_KEY_SELECT_PF_KEY = 32, /*!< Internal flag to show that this key + references one of the hardware keys, and + its value is in pf_key. */ +} fsl_shw_key_flags_t; + + +/** + * Type of value which is associated with an established key. + */ +typedef uint64_t key_userid_t; + + +/** + * Flags which describe the state of the #fsl_shw_acco_t. + * + * The @a FSL_ACCO_CTX_INIT and @a FSL_ACCO_CTX_FINALIZE flags, when used + * together, provide for a one-shot operation. + */ +typedef enum fsl_shw_auth_ctx_flags_t +{ + FSL_ACCO_CTX_INIT = 1, /**< Initialize Context(s) */ + FSL_ACCO_CTX_LOAD = 2, /**< Load intermediate context(s). + This flag is unsupported. */ + FSL_ACCO_CTX_SAVE = 4, /**< Save intermediate context(s). + This flag is unsupported. */ + FSL_ACCO_CTX_FINALIZE = 8, /**< Create MAC during this operation. */ + FSL_ACCO_NIST_CCM = 0x10, /**< Formatting of CCM input data is + performed by calls to + #fsl_shw_ccm_nist_format_ctr_and_iv() and + #fsl_shw_ccm_nist_update_ctr_and_iv(). */ +} fsl_shw_auth_ctx_flags_t; + + +/** + * The operation which controls the behavior of #fsl_shw_establish_key(). + * + * These values are passed to #fsl_shw_establish_key(). + */ +typedef enum fsl_shw_key_wrap_t +{ + FSL_KEY_WRAP_CREATE, /**< Generate a key from random values. */ + FSL_KEY_WRAP_ACCEPT, /**< Use the provided clear key. */ + FSL_KEY_WRAP_UNWRAP /**< Unwrap a previously wrapped key. */ +} fsl_shw_key_wrap_t; + + +/** + * Modulus Selector for CTR modes. + * + * The incrementing of the Counter value may be modified by a modulus. If no + * modulus is needed or desired for AES, use #FSL_CTR_MOD_128. + */ +typedef enum fsl_shw_ctr_mod_t +{ + FSL_CTR_MOD_8, /**< Run counter with modulus of 2^8. */ + FSL_CTR_MOD_16, /**< Run counter with modulus of 2^16. */ + FSL_CTR_MOD_24, /**< Run counter with modulus of 2^24. */ + FSL_CTR_MOD_32, /**< Run counter with modulus of 2^32. */ + FSL_CTR_MOD_40, /**< Run counter with modulus of 2^40. */ + FSL_CTR_MOD_48, /**< Run counter with modulus of 2^48. */ + FSL_CTR_MOD_56, /**< Run counter with modulus of 2^56. */ + FSL_CTR_MOD_64, /**< Run counter with modulus of 2^64. */ + FSL_CTR_MOD_72, /**< Run counter with modulus of 2^72. */ + FSL_CTR_MOD_80, /**< Run counter with modulus of 2^80. */ + FSL_CTR_MOD_88, /**< Run counter with modulus of 2^88. */ + FSL_CTR_MOD_96, /**< Run counter with modulus of 2^96. */ + FSL_CTR_MOD_104, /**< Run counter with modulus of 2^104. */ + FSL_CTR_MOD_112, /**< Run counter with modulus of 2^112. */ + FSL_CTR_MOD_120, /**< Run counter with modulus of 2^120. */ + FSL_CTR_MOD_128 /**< Run counter with modulus of 2^128. */ +} fsl_shw_ctr_mod_t; + + +/** + * A work type associated with a work/result queue request. + */ +typedef enum shw_work_type_t +{ + SHW_WORK_GET_RANDOM = 1, /**< fsl_shw_get_random() request. */ + SHW_WORK_ADD_RANDOM, /**< fsl_shw_add_entropy() request. */ +} shw_work_type_t; + + +/** + * Permissions flags for Secure Partitions + */ +typedef enum fsl_shw_permission_t +{ +/** SCM Access Permission: Do not zeroize/deallocate partition on SMN Fail state */ + FSL_PERM_NO_ZEROIZE = 0x80000000, +/** SCM Access Permission: Enforce trusted key read in */ + FSL_PERM_TRUSTED_KEY_READ = 0x40000000, +/** SCM Access Permission: Ignore Supervisor/User mode in permission determination */ + FSL_PERM_HD_S = 0x00000800, +/** SCM Access Permission: Allow Read Access to Host Domain */ + FSL_PERM_HD_R = 0x00000400, +/** SCM Access Permission: Allow Write Access to Host Domain */ + FSL_PERM_HD_W = 0x00000200, +/** SCM Access Permission: Allow Execute Access to Host Domain */ + FSL_PERM_HD_X = 0x00000100, +/** SCM Access Permission: Allow Read Access to Trusted Host Domain */ + FSL_PERM_TH_R = 0x00000040, +/** SCM Access Permission: Allow Write Access to Trusted Host Domain */ + FSL_PERM_TH_W = 0x00000020, +/** SCM Access Permission: Allow Read Access to Other/World Domain */ + FSL_PERM_OT_R = 0x00000004, +/** SCM Access Permission: Allow Write Access to Other/World Domain */ + FSL_PERM_OT_W = 0x00000002, +/** SCM Access Permission: Allow Execute Access to Other/World Domain */ + FSL_PERM_OT_X = 0x00000001, +} fsl_shw_permission_t; + +/*! + * Select the cypher mode to use for partition cover/uncover operations. + * + * They currently map directly to the values used in the SCC2 driver, but this + * is not guarinteed behavior. + */ +typedef enum fsl_shw_cypher_mode_t +{ + FSL_SHW_CYPHER_MODE_ECB = 1, /*!< ECB mode */ + FSL_SHW_CYPHER_MODE_CBC = 2, /*!< CBC mode */ +} fsl_shw_cypher_mode_t; + +/*! + * Which platform key should be presented for cryptographic use. + */ +typedef enum fsl_shw_pf_key_t { + FSL_SHW_PF_KEY_IIM, /*!< Present fused IIM key */ + FSL_SHW_PF_KEY_PRG, /*!< Present Program key */ + FSL_SHW_PF_KEY_IIM_PRG, /*!< Present IIM ^ Program key */ + FSL_SHW_PF_KEY_IIM_RND, /*!< Present Random key */ + FSL_SHW_PF_KEY_RND, /*!< Present IIM ^ Random key */ +} fsl_shw_pf_key_t; + +/*! + * The various security tamper events + */ +typedef enum fsl_shw_tamper_t { + FSL_SHW_TAMPER_NONE, /*!< No error detected */ + FSL_SHW_TAMPER_WTD, /*!< wire-mesh tampering det */ + FSL_SHW_TAMPER_ETBD, /*!< ext tampering det: input B */ + FSL_SHW_TAMPER_ETAD, /*!< ext tampering det: input A */ + FSL_SHW_TAMPER_EBD, /*!< external boot detected */ + FSL_SHW_TAMPER_SAD, /*!< security alarm detected */ + FSL_SHW_TAMPER_TTD, /*!< temperature tampering det */ + FSL_SHW_TAMPER_CTD, /*!< clock tampering det */ + FSL_SHW_TAMPER_VTD, /*!< voltage tampering det */ + FSL_SHW_TAMPER_MCO, /*!< monotonic counter overflow */ + FSL_SHW_TAMPER_TCO, /*!< time counter overflow */ +} fsl_shw_tamper_t; + +/* + * Structure passed during user ioctl() calls to manage data stored in secure + * partitions. + */ + +typedef struct scc_region_t { + uint32_t partition_base; /*!< Base address of partition */ + uint32_t offset; /*!< Byte offset into partition */ + uint32_t length; /*!< Number of bytes in request */ + uint8_t *black_data; /*!< Address of cipher text */ + uint64_t owner_id; /*!< user's secret */ + fsl_shw_cypher_mode_t cypher_mode; /*!< ECB or CBC */ + uint32_t IV[4]; /*!< IV for CBC mode */ +} scc_region_t; + +/****************************************************************************** + * Data Structures + *****************************************************************************/ + +/** + * Initialization Object + */ +typedef struct fsl_sho_ibo +{ +} fsl_sho_ibo_t; + + +/** + * Common Entry structure for work queues, results queues. + */ +typedef struct shw_queue_entry_t { + struct shw_queue_entry_t* next; /**< Next entry in queue. */ + struct fsl_shw_uco_t* user_ctx; /**< Associated user context. */ + uint32_t flags; /**< User context flags at time of request. */ + void (*callback)(struct fsl_shw_uco_t* uco); /**< Any callback request. */ + uint32_t user_ref; /**< User's reference for this request. */ + fsl_shw_return_t code; /**< FSL SHW result of this operation. */ + uint32_t detail1; /**< Any extra error info. */ + uint32_t detail2; /**< More any extra error info. */ + void* user_mode_req; /**< Pointer into user space. */ + uint32_t (*postprocess)(struct shw_queue_entry_t* q); /**< (internal) + function to call + when this operation + completes. + */ +} shw_queue_entry_t; + + +/** + * A queue. Fields must be initialized to NULL before use. + */ +typedef struct shw_queue_t +{ + struct shw_queue_entry_t* head; /**< First entry in queue. */ + struct shw_queue_entry_t* tail; /**< Last entry. */ +} shw_queue_t; + + +/** + * Secure Partition information + */ +typedef struct fsl_shw_spo_t +{ + uint32_t user_base; + void* kernel_base; + struct fsl_shw_spo_t* next; +} fsl_shw_spo_t; + + +/* REQ-FSLSHW-PINTFC-COA-UCO-001 */ +/** + * User Context Object + */ +typedef struct fsl_shw_uco_t +{ + int openfd; /**< user-mode file descriptor */ + uint32_t user_ref; /**< User's reference */ + void (*callback)(struct fsl_shw_uco_t* uco); /**< User's callback fn */ + uint32_t flags; /**< from fsl_shw_user_ctx_flags_t */ + unsigned pool_size; /**< maximum size of user result pool */ +#ifdef __KERNEL__ + shw_queue_t result_pool; /**< where non-blocking results go */ + os_process_handle_t process; /**< remember for signalling User mode */ + fsl_shw_spo_t* partition; /**< chain of secure partitions owned by + the user */ +#endif + struct fsl_shw_uco_t* next; /**< To allow user-mode chaining of contexts, + for signalling and in kernel, to link user + contexts. */ + fsl_shw_pf_key_t wrap_key; /*!< What key for ciphering T */ +} fsl_shw_uco_t; + + +/* REQ-FSLSHW-PINTFC-API-GEN-006 ?? */ +/** + * Result object + */ +typedef struct fsl_shw_result_t +{ + uint32_t user_ref; /**< User's reference at time of request. */ + fsl_shw_return_t code; /**< Return code from request. */ + uint32_t detail1; /**< Extra error info. Unused in SHW driver. */ + uint32_t detail2; /**< Extra error info. Unused in SHW driver. */ + void* user_req; /**< Pointer to original user request. */ +} fsl_shw_result_t; + + +/** + * Keystore Object + */ +typedef struct fsl_shw_kso_t +{ +#ifdef __KERNEL__ + os_lock_t lock; /**< Pointer to lock that controls access to + the keystore. */ +#endif + void* user_data; /**< Pointer to user structure that handles + the internals of the keystore. */ + fsl_shw_return_t (*data_init) (fsl_shw_uco_t* user_ctx, + void** user_data); + void (*data_cleanup) (fsl_shw_uco_t* user_ctx, + void** user_data); + fsl_shw_return_t (*slot_verify_access)(void* user_data, uint64_t owner_id, + uint32_t slot); + fsl_shw_return_t (*slot_alloc) (void* user_data, uint32_t size_bytes, + uint64_t owner_id, uint32_t* slot); + fsl_shw_return_t (*slot_dealloc) (void* user_data, + uint64_t owner_id, uint32_t slot); + void* (*slot_get_address) (void* user_data, uint32_t slot); + uint32_t (*slot_get_base) (void* user_data, uint32_t slot); + uint32_t (*slot_get_offset) (void* user_data, uint32_t slot); + uint32_t (*slot_get_slot_size) (void* user_data, uint32_t slot); +} fsl_shw_kso_t; + + +/* REQ-FSLSHW-PINTFC-COA-SKO-001 */ +/** + * Secret Key Context Object + */ +typedef struct fsl_shw_sko_t +{ + uint32_t flags; /**< Flags from #fsl_shw_sym_ctx_flags_t. */ + fsl_shw_key_alg_t algorithm; /**< Algorithm for this key. */ + key_userid_t userid; /**< User's identifying value for Black key. */ + uint32_t handle; /**< Reference in SCC driver for Red key. */ + uint16_t key_length; /**< Length of stored key, in bytes. */ + uint8_t key[64]; /**< Bytes of stored key. */ + struct fsl_shw_kso_t* keystore; /**< If present, key is in keystore */ + fsl_shw_pf_key_t pf_key; /*!< What key to select for use when this key + is doing ciphering. If FSL_SHW_PF_KEY_PRG + or FSL_SHW_PF_KEY_PRG_IIM is the value, then + a 'present' or 'established' key will be + programed into the PK. */ +} fsl_shw_sko_t; + + +/* REQ-FSLSHW-PINTFC-COA-CO-001 */ +/** + * Platform Capability Object + * + * Pointer to this structure is returned by fsl_shw_get_capabilities() and + * queried with the various fsl_shw_pco_() functions. + */ +typedef struct fsl_shw_pco_t +{ + int api_major; /**< Major version number for API. */ + int api_minor; /**< Minor version number for API. */ + int driver_major; /**< Major version of some driver. */ + int driver_minor; /**< Minor version of some driver. */ + unsigned sym_algorithm_count; /**< Number of sym_algorithms. */ + fsl_shw_key_alg_t* sym_algorithms; /**< Pointer to array. */ + unsigned sym_mode_count; /**< Number of sym_modes. */ + fsl_shw_sym_mode_t* sym_modes; /**< Pointer to array. */ + unsigned hash_algorithm_count; /**< Number of hash_algorithms. */ + fsl_shw_hash_alg_t* hash_algorithms; /**< Pointer to array */ + uint8_t sym_support[5][4]; /**< indexed by key alg then mode */ + + int scc_driver_major; + int scc_driver_minor; + int scm_version; /**< Version from SCM Configuration register */ + int smn_version; /**< Version from SMN Status register */ + int block_size_bytes; /**< Number of bytes per block of RAM; also + block size of the crypto algorithm. */ + union { + struct scc_info { + int black_ram_size_blocks; /**< Number of blocks of Black RAM */ + int red_ram_size_blocks; /**< Number of blocks of Red RAM */ + } scc_info; + struct scc2_info { + int partition_size_bytes; /**< Number of bytes in each partition */ + int partition_count; /**< Number of partitions on this platform */ + } scc2_info; + } u; +} fsl_shw_pco_t; + + +/* REQ-FSLSHW-PINTFC-COA-HCO-001 */ +/** + * Hash Context Object + */ +typedef struct fsl_shw_hco_t /* fsl_shw_hash_context_object */ +{ + fsl_shw_hash_alg_t algorithm; /**< Algorithm for this context. */ + uint32_t flags; /**< Flags from + #fsl_shw_hash_ctx_flags_t. */ + uint8_t digest_length; /**< hash result length in bytes */ + uint8_t context_length; /**< Context length in bytes */ + uint8_t context_register_length; /**< in bytes */ + uint32_t context[9]; /**< largest digest + msg size */ +} fsl_shw_hco_t; + + +/* REQ-FSLSHW-PINTFC-COA-HCO-001 */ +/** + * HMAC Context Object + */ +typedef struct fsl_shw_hmco_t /* fsl_shw_hmac_context_object */ +{ + fsl_shw_hash_alg_t algorithm; /**< Hash algorithm for the HMAC. */ + uint32_t flags; /**< Flags from + #fsl_shw_hmac_ctx_flags_t. */ + uint8_t digest_length; /**< in bytes */ + uint8_t context_length; /**< in bytes */ + uint8_t context_register_length; /**< in bytes */ + uint32_t ongoing_context[9]; /**< largest digest + msg + size */ + uint32_t inner_precompute[9]; /**< largest digest + msg + size */ + uint32_t outer_precompute[9]; /**< largest digest + msg + size */ +} fsl_shw_hmco_t; + + +/* REQ-FSLSHW-PINTFC-COA-SCCO-001 */ +/** + * Symmetric Crypto Context Object Context Object + */ +typedef struct fsl_shw_scco_t +{ + uint32_t flags; /**< Flags from #fsl_shw_sym_ctx_flags_t. */ + unsigned block_size_bytes; /**< Both block and ctx size */ + fsl_shw_sym_mode_t mode; /**< Symmetric mode for this context. */ + /* Could put modulus plus 16-octet context in union with arc4 + sbox+ptrs... */ + fsl_shw_ctr_mod_t modulus_exp; /**< Exponent value for CTR modulus */ + uint8_t context[8]; /**< Stored context. Large enough + for 3DES. */ +} fsl_shw_scco_t; + + +/** + * Authenticate-Cipher Context Object + + * An object for controlling the function of, and holding information about, + * data for the authenticate-cipher functions, #fsl_shw_gen_encrypt() and + * #fsl_shw_auth_decrypt(). + */ +typedef struct fsl_shw_acco_t +{ + uint32_t flags; /**< See #fsl_shw_auth_ctx_flags_t for + meanings */ + fsl_shw_acc_mode_t mode; /**< CCM only */ + uint8_t mac_length; /**< User's value for length */ + unsigned q_length; /**< NIST parameter - */ + fsl_shw_scco_t cipher_ctx_info; /**< For running + encrypt/decrypt. */ + union { + fsl_shw_scco_t CCM_ctx_info; /**< For running the CBC in + AES-CCM. */ + fsl_shw_hco_t hash_ctx_info; /**< For running the hash */ + } auth_info; /**< "auth" info struct */ + uint8_t unencrypted_mac[16]; /**< max block size... */ +} fsl_shw_acco_t; + + +/** + * Common header in request structures between User-mode API and SHW driver. + */ +struct shw_req_header { + uint32_t flags; /**< Flags - from user-mode context. */ + uint32_t user_ref; /**< Reference - from user-mode context. */ + fsl_shw_return_t code; /**< Result code for operation. */ +}; + +/** + * Used by user-mode API to retrieve completed non-blocking results in + * SHW_USER_REQ_GET_RESULTS ioctl(). + */ +struct results_req { + struct shw_req_header hdr; /**< Boilerplate. */ + unsigned requested; /**< number of results requested, */ + unsigned actual; /**< number of results obtained. */ + fsl_shw_result_t *results; /**< pointer to memory to hold results. */ +}; + + +/** + * Used by user-mode API to retrieve hardware capabilities in + * SHW_USER_REQ_GET_CAPABILITIES ioctl(). + */ +struct capabilities_req { + struct shw_req_header hdr; /**< Boilerplate. */ + unsigned size; /**< Size, in bytes, capabilities. */ + fsl_shw_pco_t* capabilities; /**< Place to copy out the info. */ +}; + + +/** + * Used by user-mode API to get a random number + */ +struct get_random_req { + struct shw_req_header hdr; /**< Boilerplate. */ + unsigned size; /**< Size, in bytes, of random. */ + uint8_t* random; /**< Place to copy out the random number. */ +}; + + +/** + * Used by API to add entropy to a random number generator + */ +struct add_entropy_req { + struct shw_req_header hdr; /**< Boilerplate. */ + unsigned size; /**< Size, in bytes, of entropy. */ + uint8_t* entropy; /**< Location of the entropy to be added. */ +}; + + +/****************************************************************************** + * External variables + *****************************************************************************/ +#ifdef __KERNEL__ +extern os_lock_t shw_queue_lock; + +extern fsl_shw_uco_t* user_list; +#endif + + +/****************************************************************************** + * Access Macros for Objects + *****************************************************************************/ +/** + * Get FSL SHW API version + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcmajor A pointer to where the major version + * of the API is to be stored. + * @param[out] pcminor A pointer to where the minor version + * of the API is to be stored. + */ +#define fsl_shw_pco_get_version(pcobject, pcmajor, pcminor) \ +do { \ + *(pcmajor) = (pcobject)->api_major; \ + *(pcminor) = (pcobject)->api_minor; \ +} while (0) + + +/** + * Get underlying driver version. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcmajor A pointer to where the major version + * of the driver is to be stored. + * @param[out] pcminor A pointer to where the minor version + * of the driver is to be stored. + */ +#define fsl_shw_pco_get_driver_version(pcobject, pcmajor, pcminor) \ +do { \ + *(pcmajor) = (pcobject)->driver_major; \ + *(pcminor) = (pcobject)->driver_minor; \ +} while (0) + + +/** + * Get list of symmetric algorithms supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcalgorithms A pointer to where to store the location of + * the list of algorithms. + * @param[out] pcacount A pointer to where to store the number of + * algorithms in the list at @a algorithms. + */ +#define fsl_shw_pco_get_sym_algorithms(pcobject, pcalgorithms, pcacount) \ +do { \ + *(pcalgorithms) = (pcobject)->sym_algorithms; \ + *(pcacount) = (pcobject)->sym_algorithm_count; \ +} while (0) + + +/** + * Get list of symmetric modes supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] gsmodes A pointer to where to store the location of + * the list of modes. + * @param[out] gsacount A pointer to where to store the number of + * algorithms in the list at @a modes. + */ +#define fsl_shw_pco_get_sym_modes(pcobject, gsmodes, gsacount) \ +do { \ + *(gsmodes) = (pcobject)->sym_modes; \ + *(gsacount) = (pcobject)->sym_mode_count; \ +} while (0) + + +/** + * Get list of hash algorithms supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] gsalgorithms A pointer which will be set to the list of + * algorithms. + * @param[out] gsacount The number of algorithms in the list at @a + * algorithms. + */ +#define fsl_shw_pco_get_hash_algorithms(pcobject, gsalgorithms, gsacount) \ +do { \ + *(gsalgorithms) = (pcobject)->hash_algorithms; \ + *(gsacount) = (pcobject)->hash_algorithm_count; \ +} while (0) + + +/** + * Determine whether the combination of a given symmetric algorithm and a given + * mode is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcalg A Symmetric Cipher algorithm. + * @param pcmode A Symmetric Cipher mode. + * + * @return 0 if combination is not supported, non-zero if supported. + */ +#if defined(FSL_HAVE_DRYICE) && defined(__KERNEL__) +#define fsl_shw_pco_check_sym_supported(pcobject, pcalg, pcmode) \ + ((pcobject)->sym_support[pcalg][pcmode]) +#else +#define fsl_shw_pco_check_sym_supported(pcobject, pcalg, pcmode) \ + 0 +#endif + +/** + * Determine whether a given Encryption-Authentication mode is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcmode The Authentication mode. + * + * @return 0 if mode is not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_auth_supported(pcobject, pcmode) \ + 0 + + +/** + * Determine whether Black Keys (key establishment / wrapping) is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * + * @return 0 if wrapping is not supported, non-zero if supported. + */ +#if defined(FSL_HAVE_DRYICE) && defined(__KERNEL__) +#define fsl_shw_pco_check_black_key_supported(pcobject) \ + 1 +#else +#define fsl_shw_pco_check_black_key_supported(pcobject) \ + 0 + +#endif + +/*! + * Determine whether Programmed Key features are available + * + * @param pcobject The Platform Capabilities Object to query. + * + * @return 1 if Programmed Key features are available, otherwise zero. + */ +#if defined(FSL_HAVE_DRYICE) && defined(__KERNEL__) +#define fsl_shw_pco_check_pk_supported(pcobject) \ + 1 +#else +#define fsl_shw_pco_check_pk_supported(pcobject) \ + 0 +#endif + +/*! + * Determine whether Software Key features are available + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return 1 if Software key features are available, otherwise zero. + */ +#if defined(FSL_HAVE_DRYICE) && defined(__KERNEL__) +#define fsl_shw_pco_check_sw_keys_supported(pcobject) \ + 1 +#else +#define fsl_shw_pco_check_sw_keys_supported(pcobject) \ + 0 +#endif + +/*! + * Get FSL SHW SCC driver version + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcmajor A pointer to where the major version + * of the SCC driver is to be stored. + * @param[out] pcminor A pointer to where the minor version + * of the SCC driver is to be stored. + */ +#define fsl_shw_pco_get_scc_driver_version(pcobject, pcmajor, pcminor) \ +{ \ + *(pcmajor) = (pcobject)->scc_driver_major; \ + *(pcminor) = (pcobject)->scc_driver_minor; \ +} + + +/** + * Get SCM hardware version + * + * @param pcobject The Platform Capababilities Object to query. + * @return The SCM hardware version + */ +#define fsl_shw_pco_get_scm_version(pcobject) \ + ((pcobject)->scm_version) + + +/** + * Get SMN hardware version + * + * @param pcobject The Platform Capababilities Object to query. + * @return The SMN hardware version + */ +#define fsl_shw_pco_get_smn_version(pcobject) \ + ((pcobject)->smn_version) + + +/** + * Get the size of an SCM block, in bytes + * + * @param pcobject The Platform Capababilities Object to query. + * @return The size of an SCM block, in bytes. + */ +#define fsl_shw_pco_get_scm_block_size(pcobject) \ + ((pcobject)->block_size_bytes) + + +/** + * Get size of Black and Red RAM memory + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] black_size A pointer to where the size of the Black RAM, in + * blocks, is to be placed. + * @param[out] red_size A pointer to where the size of the Red RAM, in + * blocks, is to be placed. + */ +#define fsl_shw_pco_get_smn_size(pcobject, black_size, red_size) \ +{ \ + if ((pcobject)->scm_version == 1) { \ + *(black_size) = (pcobject)->u.scc_info.black_ram_size_blocks; \ + *(red_size) = (pcobject)->u.scc_info.red_ram_size_blocks; \ + } else { \ + *(black_size) = 0; \ + *(red_size) = 0; \ + } \ +} + + +/** + * Determine whether Secure Partitions are supported + * + * @param pcobject The Platform Capababilities Object to query. + * + * @return 0 if secure partitions are not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_spo_supported(pcobject) \ + ((pcobject)->scm_version == 2) + + +/** + * Get the size of a Secure Partitions + * + * @param pcobject The Platform Capababilities Object to query. + * + * @return Partition size, in bytes. 0 if Secure Partitions not supported. + */ +#define fsl_shw_pco_get_spo_size_bytes(pcobject) \ + (((pcobject)->scm_version == 2) ? \ + ((pcobject)->u.scc2_info.partition_size_bytes) : 0 ) \ + + +/** + * Get the number of Secure Partitions on this platform + * + * @param pcobject The Platform Capababilities Object to query. + * + * @return Number of partitions. 0 if Secure Paritions not supported. Note + * that this returns the total number of partitions, not all may be + * available to the user. + */ +#define fsl_shw_pco_get_spo_count(pcobject) \ + (((pcobject)->scm_version == 2) ? \ + ((pcobject)->u.scc2_info.partition_count) : 0 ) \ + + +/*! + * Initialize a Secret Key Object. + * + * This function must be called before performing any other operation with + * the Object. + * + * @param skobject The Secret Key Object to be initialized. + * @param skalgorithm DES, AES, etc. + * + */ +#define fsl_shw_sko_init(skobject,skalgorithm) \ +{ \ + fsl_shw_sko_t* skop = skobject; \ + \ + skop->algorithm = skalgorithm; \ + skop->flags = 0; \ + skop->keystore = NULL; \ + skop->pf_key = FSL_SHW_PF_KEY_PRG; \ +} + +/*! + * Initialize a Secret Key Object to use a Platform Key register. + * + * This function must be called before performing any other operation with + * the Object. + * + * @param skobject The Secret Key Object to be initialized. + * @param skalgorithm DES, AES, etc. + * @param skhwkey one of the fsl_shw_pf_key_t values. + * + */ +#define fsl_shw_sko_init_pf_key(skobject,skalgorithm,skhwkey) \ +{ \ + fsl_shw_sko_t* skop = skobject; \ + fsl_shw_key_alg_t alg = skalgorithm; \ + fsl_shw_pf_key_t key = skhwkey; \ + \ + skop->algorithm = alg; \ + if (alg == FSL_KEY_ALG_TDES) { \ + skop->key_length = 21; \ + } \ + skop->keystore = NULL; \ + skop->flags = FSL_SKO_KEY_SELECT_PF_KEY; \ + skop->pf_key = key; \ + if ((key == FSL_SHW_PF_KEY_IIM) || (key == FSL_SHW_PF_KEY_PRG) \ + || (key == FSL_SHW_PF_KEY_IIM_PRG) \ + || (key == FSL_SHW_PF_KEY_IIM_RND) \ + || (key == FSL_SHW_PF_KEY_RND)) { \ + skop->flags |= FSL_SKO_KEY_ESTABLISHED; \ + } \ +} + +/*! + * Store a cleartext key in the key object. + * + * This has the side effect of setting the #FSL_SKO_KEY_PRESENT flag and + * resetting the #FSL_SKO_KEY_ESTABLISHED flag. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skkey A pointer to the beginning of the key. + * @param skkeylen The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +#define fsl_shw_sko_set_key(skobject, skkey, skkeylen) \ +{ \ + (skobject)->key_length = skkeylen; \ + copy_bytes((skobject)->key, skkey, skkeylen); \ + (skobject)->flags |= FSL_SKO_KEY_PRESENT; \ + (skobject)->flags &= ~FSL_SKO_KEY_ESTABLISHED; \ +} + +/** + * Set a size for the key. + * + * This function would normally be used when the user wants the key to be + * generated from a random source. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skkeylen The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +#define fsl_shw_sko_set_key_length(skobject, skkeylen) \ + (skobject)->key_length = skkeylen; + + +/** + * Set the User ID associated with the key. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skuserid The User ID to identify authorized users of the key. + */ +#define fsl_shw_sko_set_user_id(skobject, skuserid) \ + (skobject)->userid = (skuserid) + +/** + * Establish a user Keystore to hold the key. + */ +#define fsl_shw_sko_set_keystore(skobject, user_keystore) \ + (skobject)->keystore = (user_keystore) + + + +/** + * Set the establish key handle into a key object. + * + * The @a userid field will be used to validate the access to the unwrapped + * key. This feature is not available for all platforms, nor for all + * algorithms and modes. + * + * The #FSL_SKO_KEY_ESTABLISHED will be set (and the #FSL_SKO_KEY_PRESENT flag + * will be cleared). + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skuserid The User ID to verify this user is an authorized user of + * the key. + * @param skhandle A @a handle from #fsl_shw_sko_get_established_info. + */ +#define fsl_shw_sko_set_established_info(skobject, skuserid, skhandle) \ +{ \ + (skobject)->userid = (skuserid); \ + (skobject)->handle = (skhandle); \ + (skobject)->flags |= FSL_SKO_KEY_ESTABLISHED; \ + (skobject)->flags &= \ + ~(FSL_SKO_KEY_PRESENT); \ +} + + +/** + * Retrieve the established-key handle from a key object. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skhandle The location to store the @a handle of the unwrapped + * key. + */ +#define fsl_shw_sko_get_established_info(skobject, skhandle) \ + *(skhandle) = (skobject)->handle + + +/** + * Extract the algorithm from a key object. + * + * @param skobject The Key Object to be queried. + * @param[out] skalgorithm A pointer to the location to store the algorithm. + */ +#define fsl_shw_sko_get_algorithm(skobject, skalgorithm) \ + *(skalgorithm) = (skobject)->algorithm + + +/** + * Retrieve the cleartext key from a key object that is stored in a user + * keystore. + * + * @param skobject The Key Object to be queried. + * @param[out] skkey A pointer to the location to store the key. NULL + * if the key is not stored in a user keystore. + */ +#define fsl_shw_sko_get_key(skobject, skkey) \ +{ \ + fsl_shw_kso_t* keystore = (skobject)->keystore; \ + if (keystore != NULL) { \ + *(skkey) = keystore->slot_get_address(keystore->user_data, \ + (skobject)->handle); \ + } else { \ + *(skkey) = NULL; \ + } \ +} + + +/*! + * Determine the size of a wrapped key based upon the cleartext key's length. + * + * This function can be used to calculate the number of octets that + * #fsl_shw_extract_key() will write into the location at @a covered_key. + * + * If zero is returned at @a length, this means that the key length in + * @a key_info is not supported. + * + * @param wkeyinfo Information about a key to be wrapped. + * @param wkeylen Location to store the length of a wrapped + * version of the key in @a key_info. + */ +#define fsl_shw_sko_calculate_wrapped_size(wkeyinfo, wkeylen) \ +{ \ + register fsl_shw_sko_t* kp = wkeyinfo; \ + register uint32_t kl = kp->key_length; \ + int key_blocks; \ + int base_size = 35; /* ICV + T' + ALG + LEN + FLAGS */ \ + \ + if (kp->flags & FSL_SKO_KEY_SELECT_PF_KEY) { \ + kl = 21; /* 168-bit 3DES key */ \ + } \ + key_blocks = (kl + 7) / 8; \ + /* Round length up to 3DES block size for CBC mode */ \ + *(wkeylen) = base_size + 8 * key_blocks; \ +} + +/*! + * Set some flags in the key object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skflags (One or more) ORed members of #fsl_shw_key_flags_t which + * are to be set. + */ +#define fsl_shw_sko_set_flags(skobject, skflags) \ + (skobject)->flags |= (skflags) + + +/** + * Clear some flags in the key object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skflags (One or more) ORed members of #fsl_shw_key_flags_t + * which are to be reset. + */ +#define fsl_shw_sko_clear_flags(skobject, skflags) \ + (skobject)->flags &= ~(skflags) + +/** + * Initialize a User Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the User Context Object to initial values, and set the size + * of the results pool. The mode will be set to a default of + * #FSL_UCO_BLOCKING_MODE. + * + * When using non-blocking operations, this sets the maximum number of + * operations which can be outstanding. This number includes the counts of + * operations waiting to start, operation(s) being performed, and results which + * have not been retrieved. + * + * Changes to this value are ignored once user registration has completed. It + * should be set to 1 if only blocking operations will ever be performed. + * + * @param ucontext The User Context object to operate on. + * @param usize The maximum number of operations which can be + * outstanding. + */ +#ifdef __KERNEL__ + +#define fsl_shw_uco_init(ucontext, usize) \ +do { \ + fsl_shw_uco_t* uco = ucontext; \ + \ + (uco)->pool_size = usize; \ + (uco)->flags = FSL_UCO_BLOCKING_MODE | FSL_UCO_CONTEXT_CHANGED; \ + (uco)->openfd = -1; \ + (uco)->callback = NULL; \ + (uco)->partition = NULL; \ + (uco)->wrap_key = FSL_SHW_PF_KEY_IIM; \ +} while (0) + +#else /* __KERNEL__ */ + +#define fsl_shw_uco_init(ucontext, usize) \ +do { \ + fsl_shw_uco_t* uco = ucontext; \ + \ + (uco)->pool_size = usize; \ + (uco)->flags = FSL_UCO_BLOCKING_MODE | FSL_UCO_CONTEXT_CHANGED; \ + (uco)->openfd = -1; \ + (uco)->callback = NULL; \ + (uco)->wrap_key = FSL_SHW_PF_KEY_IIM; \ +} while (0) + +#endif /* __KERNEL__ */ + + +/** + * Set the User Reference for the User Context. + * + * @param ucontext The User Context object to operate on. + * @param uref A value which will be passed back with a result. + */ +#define fsl_shw_uco_set_reference(ucontext, uref) \ +do { \ + fsl_shw_uco_t* uco = ucontext; \ + \ + (uco)->user_ref = uref; \ + (uco)->flags |= FSL_UCO_CONTEXT_CHANGED; \ +} while (0) + + +/** + * Set the User Reference for the User Context. + * + * @param ucontext The User Context object to operate on. + * @param ucallback The function the API will invoke when an operation + * completes. + */ +#define fsl_shw_uco_set_callback(ucontext, ucallback) \ +do { \ + fsl_shw_uco_t* uco = ucontext; \ + \ + (uco)->callback = ucallback; \ + (uco)->flags |= FSL_UCO_CONTEXT_CHANGED; \ +} while (0) + +/** + * Set flags in the User Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param ucontext The User Context object to operate on. + * @param uflags ORed values from #fsl_shw_user_ctx_flags_t. + */ +#define fsl_shw_uco_set_flags(ucontext, uflags) \ + (ucontext)->flags |= (uflags) | FSL_UCO_CONTEXT_CHANGED + + +/** + * Clear flags in the User Context. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param ucontext The User Context object to operate on. + * @param uflags ORed values from #fsl_shw_user_ctx_flags_t. + */ +#define fsl_shw_uco_clear_flags(ucontext, uflags) \ +do { \ + fsl_shw_uco_t* uco = ucontext; \ + \ + (uco)->flags &= ~(uflags); \ + (uco)->flags |= FSL_UCO_CONTEXT_CHANGED; \ +} while (0) + + +/** + * Retrieve the reference value from a Result Object. + * + * @param robject The result object to query. + * + * @return The reference associated with the request. + */ +#define fsl_shw_ro_get_reference(robject) \ + (robject)->user_ref + + +/** + * Retrieve the status code from a Result Object. + * + * @param robject The result object to query. + * + * @return The status of the request. + */ +#define fsl_shw_ro_get_status(robject) \ + (robject)->code + + + +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-004 */ +/** + * Initialize a Hash Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the hash + * context object. + * + * @param hcobject The hash context to operate upon. + * @param hcalgorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +#define fsl_shw_hco_init(hcobject, hcalgorithm) \ +do { \ + (hcobject)->algorithm = hcalgorithm; \ + (hcobject)->flags = 0; \ + switch (hcalgorithm) { \ + case FSL_HASH_ALG_MD5: \ + (hcobject)->digest_length = 16; \ + (hcobject)->context_length = 16; \ + (hcobject)->context_register_length = 24; \ + break; \ + case FSL_HASH_ALG_SHA1: \ + (hcobject)->digest_length = 20; \ + (hcobject)->context_length = 20; \ + (hcobject)->context_register_length = 24; \ + break; \ + case FSL_HASH_ALG_SHA224: \ + (hcobject)->digest_length = 28; \ + (hcobject)->context_length = 32; \ + (hcobject)->context_register_length = 36; \ + break; \ + case FSL_HASH_ALG_SHA256: \ + (hcobject)->digest_length = 32; \ + (hcobject)->context_length = 32; \ + (hcobject)->context_register_length = 36; \ + break; \ + default: \ + /* error ! */ \ + (hcobject)->digest_length = 1; \ + (hcobject)->context_length = 1; \ + (hcobject)->context_register_length = 1; \ + break; \ + } \ +} while (0) + + +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-001 */ +/** + * Get the current hash value and message length from the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hcobject The hash context to query. + * @param[out] hccontext Pointer to the location of @a length octets where to + * store a copy of the current value of the digest. + * @param hcclength Number of octets of hash value to copy. + * @param[out] hcmsglen Pointer to the location to store the number of octets + * already hashed. + */ +#define fsl_shw_hco_get_digest(hcobject, hccontext, hcclength, hcmsglen) \ +do { \ + memcpy(hccontext, (hcobject)->context, hcclength); \ + if ((hcobject)->algorithm == FSL_HASH_ALG_SHA224 \ + || (hcobject)->algorithm == FSL_HASH_ALG_SHA256) { \ + *(hcmsglen) = (hcobject)->context[8]; \ + } else { \ + *(hcmsglen) = (hcobject)->context[5]; \ + } \ +} while (0) + + +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-002 */ +/** + * Get the hash algorithm from the hash context object. + * + * @param hcobject The hash context to query. + * @param[out] hcalgorithm Pointer to where the algorithm is to be stored. + */ +#define fsl_shw_hco_get_info(hcobject, hcalgorithm) \ +do { \ + *(hcalgorithm) = (hcobject)->algorithm; \ +} while (0) + + +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-003 */ +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-004 */ +/** + * Set the current hash value and message length in the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hcobject The hash context to operate upon. + * @param hccontext Pointer to buffer of appropriate length to copy into + * the hash context object. + * @param hcmsglen The number of octets of the message which have + * already been hashed. + * + */ +#define fsl_shw_hco_set_digest(hcobject, hccontext, hcmsglen) \ +do { \ + memcpy((hcobject)->context, hccontext, (hcobject)->context_length); \ + if (((hcobject)->algorithm == FSL_HASH_ALG_SHA224) \ + || ((hcobject)->algorithm == FSL_HASH_ALG_SHA256)) { \ + (hcobject)->context[8] = hcmsglen; \ + } else { \ + (hcobject)->context[5] = hcmsglen; \ + } \ +} while (0) + + +/** + * Set flags in a Hash Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The hash context to be operated on. + * @param hcflags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +#define fsl_shw_hco_set_flags(hcobject, hcflags) \ + (hcobject)->flags |= (hcflags) + + +/** + * Clear flags in a Hash Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The hash context to be operated on. + * @param hcflags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +#define fsl_shw_hco_clear_flags(hcobject, hcflags) \ + (hcobject)->flags &= ~(hcflags) + + +/** + * Initialize an HMAC Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the HMAC + * context object. + * + * @param hcobject The HMAC context to operate upon. + * @param hcalgorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +#define fsl_shw_hmco_init(hcobject, hcalgorithm) \ + fsl_shw_hco_init(hcobject, hcalgorithm) + + +/** + * Set flags in an HMAC Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The HMAC context to be operated on. + * @param hcflags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +#define fsl_shw_hmco_set_flags(hcobject, hcflags) \ + (hcobject)->flags |= (hcflags) + + +/** + * Clear flags in an HMAC Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The HMAC context to be operated on. + * @param hcflags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +#define fsl_shw_hmco_clear_flags(hcobject, hcflags) \ + (hcobject)->flags &= ~(hcflags) + + +/** + * Initialize a Symmetric Cipher Context Object. + * + * This function must be called before performing any other operation with the + * Object. This will set the @a mode and @a algorithm and initialize the + * Object. + * + * @param scobject The context object to operate on. + * @param scalg The cipher algorithm this context will be used with. + * @param scmode #FSL_SYM_MODE_CBC, #FSL_SYM_MODE_ECB, etc. + * + */ +#define fsl_shw_scco_init(scobject, scalg, scmode) \ +do { \ + register uint32_t bsb; /* block-size bytes */ \ + \ + switch (scalg) { \ + case FSL_KEY_ALG_AES: \ + bsb = 16; \ + break; \ + case FSL_KEY_ALG_DES: \ + /* fall through */ \ + case FSL_KEY_ALG_TDES: \ + bsb = 8; \ + break; \ + case FSL_KEY_ALG_ARC4: \ + bsb = 259; \ + break; \ + case FSL_KEY_ALG_HMAC: \ + bsb = 1; /* meaningless */ \ + break; \ + default: \ + bsb = 00; \ + } \ + (scobject)->block_size_bytes = bsb; \ + (scobject)->mode = scmode; \ + (scobject)->flags = 0; \ +} while (0) + + +/** + * Set the flags for a Symmetric Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param scobject The context object to operate on. + * @param scflags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +#define fsl_shw_scco_set_flags(scobject, scflags) \ + (scobject)->flags |= (scflags) + + +/** + * Clear some flags in a Symmetric Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param scobject The context object to operate on. + * @param scflags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +#define fsl_shw_scco_clear_flags(scobject, scflags) \ + (scobject)->flags &= ~(scflags) + + +/** + * Set the Context (IV) for a Symmetric Cipher Context. + * + * This is to set the context/IV for #FSL_SYM_MODE_CBC mode, or to set the + * context (the S-Box and pointers) for ARC4. The full context size will + * be copied. + * + * @param scobject The context object to operate on. + * @param sccontext A pointer to the buffer which contains the context. + * + */ +#define fsl_shw_scco_set_context(scobject, sccontext) \ + memcpy((scobject)->context, sccontext, \ + (scobject)->block_size_bytes) + + +/** + * Get the Context for a Symmetric Cipher Context. + * + * This is to retrieve the context/IV for #FSL_SYM_MODE_CBC mode, or to + * retrieve context (the S-Box and pointers) for ARC4. The full context + * will be copied. + * + * @param scobject The context object to operate on. + * @param[out] sccontext Pointer to location where context will be stored. + */ +#define fsl_shw_scco_get_context(scobject, sccontext) \ + memcpy(sccontext, (scobject)->context, (scobject)->block_size_bytes) + + +/** + * Set the Counter Value for a Symmetric Cipher Context. + * + * This will set the Counter Value for CTR mode. + * + * @param scobject The context object to operate on. + * @param sccounter The starting counter value. The number of octets. + * copied will be the block size for the algorithm. + * @param scmodulus The modulus for controlling the incrementing of the + * counter. + * + */ +#define fsl_shw_scco_set_counter_info(scobject, sccounter, scmodulus) \ +do { \ + if ((sccounter) != NULL) { \ + memcpy((scobject)->context, sccounter, \ + (scobject)->block_size_bytes); \ + } \ + (scobject)->modulus_exp = scmodulus; \ +} while (0) + + +/** + * Get the Counter Value for a Symmetric Cipher Context. + * + * This will retrieve the Counter Value is for CTR mode. + * + * @param scobject The context object to query. + * @param[out] sccounter Pointer to location to store the current counter + * value. The number of octets copied will be the + * block size for the algorithm. + * @param[out] scmodulus Pointer to location to store the modulus. + * + */ +#define fsl_shw_scco_get_counter_info(scobject, sccounter, scmodulus) \ +do { \ + if ((sccounter) != NULL) { \ + memcpy(sccounter, (scobject)->context, \ + (scobject)->block_size_bytes); \ + } \ + if ((scmodulus) != NULL) { \ + *(scmodulus) = (scobject)->modulus_exp; \ + } \ +} while (0) + + +/** + * Initialize a Authentication-Cipher Context. + * + * @param acobject Pointer to object to operate on. + * @param acmode The mode for this object (only #FSL_ACC_MODE_CCM + * supported). + */ +#define fsl_shw_acco_init(acobject, acmode) \ +do { \ + (acobject)->flags = 0; \ + (acobject)->mode = (acmode); \ +} while (0) + + +/** + * Set the flags for a Authentication-Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param acobject Pointer to object to operate on. + * @param acflags The flags to set (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +#define fsl_shw_acco_set_flags(acobject, acflags) \ + (acobject)->flags |= (acflags) + + +/** + * Clear some flags in a Authentication-Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param acobject Pointer to object to operate on. + * @param acflags The flags to reset (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +#define fsl_shw_acco_clear_flags(acobject, acflags) \ + (acobject)->flags &= ~(acflags) + + +/** + * Set up the Authentication-Cipher Object for CCM mode. + * + * This will set the @a auth_object for CCM mode and save the @a ctr, + * and @a mac_length. This function can be called instead of + * #fsl_shw_acco_init(). + * + * The paramater @a ctr is Counter Block 0, (counter value 0), which is for the + * MAC. + * + * @param acobject Pointer to object to operate on. + * @param acalg Cipher algorithm. Only AES is supported. + * @param accounter The initial counter value. + * @param acmaclen The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_acco_set_ccm(acobject, acalg, accounter, acmaclen) \ + do { \ + (acobject)->flags = 0; \ + (acobject)->mode = FSL_ACC_MODE_CCM; \ + (acobject)->auth_info.CCM_ctx_info.block_size_bytes = 16; \ + (acobject)->cipher_ctx_info.block_size_bytes = 16; \ + (acobject)->mac_length = acmaclen; \ + fsl_shw_scco_set_counter_info(&(acobject)->cipher_ctx_info, accounter, \ + FSL_CTR_MOD_128); \ +} while (0) + + +/** + * Format the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will also set the IV and CTR values per Appendix A of NIST + * Special Publication 800-38C (May 2004). It will also perform the + * #fsl_shw_acco_set_ccm() operation with information derived from this set of + * parameters. + * + * Note this function assumes the algorithm is AES. It initializes the + * @a auth_object by setting the mode to #FSL_ACC_MODE_CCM and setting the + * flags to be #FSL_ACCO_NIST_CCM. + * + * @param acobject Pointer to object to operate on. + * @param act The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + * @param acad Number of octets of Associated Data (may be zero). + * @param acq A value for the size of the length of @a q field. Valid + * values are 1-8. + * @param acN The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param acQ The value of Q (size of the payload in octets). + * + */ +#define fsl_shw_ccm_nist_format_ctr_and_iv(acobject, act, acad, acq, acN, acQ)\ + do { \ + uint64_t Q = acQ; \ + uint8_t bflag = ((acad)?0x40:0) | ((((act)-2)/2)<<3) | ((acq)-1); \ + unsigned i; \ + uint8_t* qptr = (acobject)->auth_info.CCM_ctx_info.context + 15; \ + (acobject)->auth_info.CCM_ctx_info.block_size_bytes = 16; \ + (acobject)->cipher_ctx_info.block_size_bytes = 16; \ + (acobject)->mode = FSL_ACC_MODE_CCM; \ + (acobject)->flags = FSL_ACCO_NIST_CCM; \ + \ + /* Store away the MAC length (after calculating actual value */ \ + (acobject)->mac_length = (act); \ + /* Set Flag field in Block 0 */ \ + *((acobject)->auth_info.CCM_ctx_info.context) = bflag; \ + /* Set Nonce field in Block 0 */ \ + memcpy((acobject)->auth_info.CCM_ctx_info.context+1, acN, \ + 15-(acq)); \ + /* Set Flag field in ctr */ \ + *((acobject)->cipher_ctx_info.context) = (acq)-1; \ + /* Update the Q (payload length) field of Block0 */ \ + (acobject)->q_length = acq; \ + for (i = 0; i < (acq); i++) { \ + *qptr-- = Q & 0xFF; \ + Q >>= 8; \ + } \ + /* Set the Nonce field of the ctr */ \ + memcpy((acobject)->cipher_ctx_info.context+1, acN, 15-(acq)); \ + /* Clear the block counter field of the ctr */ \ + memset((acobject)->cipher_ctx_info.context+16-(acq), 0, (acq)+1); \ + } while (0) + + +/** + * Update the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will set the IV and CTR values per Appendix A of NIST Special + * Publication 800-38C (May 2004). + * + * Note this function assumes that #fsl_shw_ccm_nist_format_ctr_and_iv() has + * previously been called on the @a auth_object. + * + * @param acobject Pointer to object to operate on. + * @param acN The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param acQ The value of Q (size of the payload in octets). + * + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_ccm_nist_update_ctr_and_iv(acobject, acN, acQ) \ + do { \ + uint64_t Q = acQ; \ + unsigned i; \ + uint8_t* qptr = (acobject)->auth_info.CCM_ctx_info.context + 15; \ + \ + /* Update the Nonce field field of Block0 */ \ + memcpy((acobject)->auth_info.CCM_ctx_info.context+1, acN, \ + 15 - (acobject)->q_length); \ + /* Update the Q (payload length) field of Block0 */ \ + for (i = 0; i < (acobject)->q_length; i++) { \ + *qptr-- = Q & 0xFF; \ + Q >>= 8; \ + } \ + /* Update the Nonce field of the ctr */ \ + memcpy((acobject)->cipher_ctx_info.context+1, acN, \ + 15 - (acobject)->q_length); \ + } while (0) + + +/****************************************************************************** + * Library functions + *****************************************************************************/ +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-GEN-003 */ +/** + * Determine the hardware security capabilities of this platform. + * + * Though a user context object is passed into this function, it will always + * act in a non-blocking manner. + * + * @param user_ctx The user context which will be used for the query. + * + * @return A pointer to the capabilities object. + */ +extern fsl_shw_pco_t* fsl_shw_get_capabilities(fsl_shw_uco_t* user_ctx); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-GEN-004 */ +/** + * Create an association between the the user and the provider of the API. + * + * @param user_ctx The user context which will be used for this association. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t* user_ctx); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-GEN-005 */ +/** + * Destroy the association between the the user and the provider of the API. + * + * @param user_ctx The user context which is no longer needed. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t* user_ctx); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-GEN-006 */ +/** + * Retrieve results from earlier operations. + * + * @param user_ctx The user's context. + * @param result_size The number of array elements of @a results. + * @param[in,out] results Pointer to first of the (array of) locations to + * store results. + * @param[out] result_count Pointer to store the number of results which + * were returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t* user_ctx, + unsigned result_size, + fsl_shw_result_t results[], + unsigned* result_count); + +/** + * Place a key into a protected location for use only by cryptographic + * algorithms. + * + * This only needs to be used to a) unwrap a key, or b) set up a key which + * could be wrapped with a later call to #fsl_shw_extract_key(). Normal + * cleartext keys can simply be placed into #fsl_shw_sko_t key objects with + * #fsl_shw_sko_set_key() and used directly. + * + * The maximum key size supported for wrapped/unwrapped keys is 32 octets. + * (This is the maximum reasonable key length on Sahara - 32 octets for an HMAC + * key based on SHA-256.) The key size is determined by the @a key_info. The + * expected length of @a key can be determined by + * #fsl_shw_sko_calculate_wrapped_size() + * + * The protected key will not be available for use until this operation + * successfully completes. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be established. In the create case, the key + * length must be set. + * @param establish_type How @a key will be interpreted to establish a + * key for use. + * @param key If @a establish_type is #FSL_KEY_WRAP_UNWRAP, + * this is the location of a wrapped key. If + * @a establish_type is #FSL_KEY_WRAP_CREATE, this + * parameter can be @a NULL. If @a establish_type + * is #FSL_KEY_WRAP_ACCEPT, this is the location + * of a plaintext key. + */ +extern fsl_shw_return_t fsl_shw_establish_key( + fsl_shw_uco_t* user_ctx, + fsl_shw_sko_t* key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t* key); + + +/** + * Wrap a key and retrieve the wrapped value. + * + * A wrapped key is a key that has been cryptographically obscured. It is + * only able to be used with #fsl_shw_establish_key(). + * + * This function will also release the key (see #fsl_shw_release_key()) so + * that it must be re-established before reuse. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * @param[out] covered_key The location to store the wrapped key. + * (This size is based upon the maximum key size + * of 32 octets). + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t* user_ctx, + fsl_shw_sko_t* key_info, + uint8_t* covered_key); + +/*! + * Read the key value from a key object. + * + * Only a key marked as a software key (#FSL_SKO_KEY_SW_KEY) can be read with + * this call. It has no effect on the status of the key store. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The referenced key. + * @param[out] key The location to store the key value. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_read_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * key); + +/** + * De-establish a key so that it can no longer be accessed. + * + * The key will need to be re-established before it can again be used. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_release_key( + fsl_shw_uco_t* user_ctx, + fsl_shw_sko_t* key_info); + + +/* + * In userspace, partition assignments will be tracked using the user context. + * In kernel mode, partition assignments are based on address only. + */ + +/** + * Allocate a block of secure memory + * + * @param user_ctx User context + * @param size Memory size (octets). Note: currently only + * supports only single-partition sized blocks. + * @param UMID User Mode ID to use when registering the + * partition. + * @param permissions Permissions to initialize the partition with. + * Can be made by ORing flags from the + * #fsl_shw_permission_t. + * + * @return Address of the allocated memory. NULL if the + * call was not successful. + */ +extern void *fsl_shw_smalloc(fsl_shw_uco_t* user_ctx, + uint32_t size, + const uint8_t* UMID, + uint32_t permissions); + + +/** + * Free a block of secure memory that was allocated with #fsl_shw_smalloc + * + * @param user_ctx User context + * @param address Address of the block of secure memory to be + * released. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_sfree( + fsl_shw_uco_t* user_ctx, + void* address); + + +/** + * Check the status of a block of a secure memory that was allocated with + * #fsl_shw_smalloc + * + * @param user_ctx User context + * @param address Address of the block of secure memory to be + * released. + * @param status Status of the partition, of type + * #fsl_partition_status_t + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_sstatus(fsl_shw_uco_t* user_ctx, + void* address, + fsl_shw_partition_status_t* status); + + +/** + * Diminish the permissions of a block of secure memory. Note that permissions + * can only be revoked. + * + * @param user_ctx User context + * @param address Base address of the secure memory to work with + * @param permissions Permissions to initialize the partition with. + * Can be made by ORing flags from the + * #fsl_shw_permission_t. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_diminish_perms( + fsl_shw_uco_t* user_ctx, + void* address, + uint32_t permissions); + +extern fsl_shw_return_t do_scc_engage_partition( + fsl_shw_uco_t* user_ctx, + void* address, + const uint8_t* UMID, + uint32_t permissions); + +extern fsl_shw_return_t do_system_keystore_slot_alloc( + fsl_shw_uco_t* user_ctx, + uint32_t key_lenth, + uint64_t ownerid, + uint32_t *slot); + +extern fsl_shw_return_t do_system_keystore_slot_dealloc( + fsl_shw_uco_t* user_ctx, + uint64_t ownerid, + uint32_t slot); + +extern fsl_shw_return_t do_system_keystore_slot_load( + fsl_shw_uco_t* user_ctx, + uint64_t ownerid, + uint32_t slot, + const uint8_t *key, + uint32_t key_length); + +extern fsl_shw_return_t do_system_keystore_slot_encrypt( + fsl_shw_uco_t* user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + uint8_t* black_data); + +extern fsl_shw_return_t do_system_keystore_slot_decrypt( + fsl_shw_uco_t* user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + const uint8_t* black_data); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-SKO */ +/* REQ-FSL-SHW-PINTFC-COA-SCCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-SYM-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +/** + * Encrypt a stream of data with a symmetric-key algorithm. + * + * In ARC4, and also in #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_CTR modes, the + * flags of the @a sym_ctx object will control part of the operation of this + * function. The #FSL_SYM_CTX_INIT flag means that there is no context info in + * the object. The #FSL_SYM_CTX_LOAD means to use information in the + * @a sym_ctx at the start of the operation, and the #FSL_SYM_CTX_SAVE flag + * means to update the object's context information after the operation has + * been performed. + * + * All of the data for an operation can be run through at once using the + * #FSL_SYM_CTX_INIT or #FSL_SYM_CTX_LOAD flags, as appropriate, and then using + * a @a length for the whole of the data. + * + * If a #FSL_SYM_CTX_SAVE flag were added, an additional call to the function + * would "pick up" where the previous call left off, allowing the user to + * perform the larger function in smaller steps. + * + * In #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_ECB modes, the @a length must always + * be a multiple of the block size for the algorithm being used. For proper + * operation in #FSL_SYM_MODE_CTR mode, the @a length must be a multiple of the + * block size until the last operation on the total octet stream. + * + * Some users of ARC4 may want to compute the context (S-Box and pointers) from + * the key before any data is available. This may be done by running this + * function with a @a length of zero, with the init & save flags flags on in + * the @a sym_ctx. Subsequent operations would then run as normal with the + * load and save flags. Note that they key object is still required. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info Key and algorithm being used for this operation. + * @param[in,out] sym_ctx Info on cipher mode, state of the cipher. + * @param length Length, in octets, of the pt (and ct). + * @param pt pointer to plaintext to be encrypted. + * @param[out] ct pointer to where to store the resulting ciphertext. + * + * @return A return code of type #fsl_shw_return_t. + * + */ +extern fsl_shw_return_t fsl_shw_symmetric_encrypt( + fsl_shw_uco_t* user_ctx, + fsl_shw_sko_t* key_info, + fsl_shw_scco_t* sym_ctx, + uint32_t length, + const uint8_t* pt, + uint8_t* ct); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-SKO */ +/* REQ-FSL-SHW-PINTFC-COA-SCCO */ +/* PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +/** + * Decrypt a stream of data with a symmetric-key algorithm. + * + * In ARC4, and also in #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_CTR modes, the + * flags of the @a sym_ctx object will control part of the operation of this + * function. The #FSL_SYM_CTX_INIT flag means that there is no context info in + * the object. The #FSL_SYM_CTX_LOAD means to use information in the + * @a sym_ctx at the start of the operation, and the #FSL_SYM_CTX_SAVE flag + * means to update the object's context information after the operation has + * been performed. + * + * All of the data for an operation can be run through at once using the + * #FSL_SYM_CTX_INIT or #FSL_SYM_CTX_LOAD flags, as appropriate, and then using + * a @a length for the whole of the data. + * + * If a #FSL_SYM_CTX_SAVE flag were added, an additional call to the function + * would "pick up" where the previous call left off, allowing the user to + * perform the larger function in smaller steps. + * + * In #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_ECB modes, the @a length must always + * be a multiple of the block size for the algorithm being used. For proper + * operation in #FSL_SYM_MODE_CTR mode, the @a length must be a multiple of the + * block size until the last operation on the total octet stream. + * + * Some users of ARC4 may want to compute the context (S-Box and pointers) from + * the key before any data is available. This may be done by running this + * function with a @a length of zero, with the #FSL_SYM_CTX_INIT & + * #FSL_SYM_CTX_SAVE flags on in the @a sym_ctx. Subsequent operations would + * then run as normal with the load & save flags. Note that they key object is + * still required. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The key and algorithm being used in this operation. + * @param[in,out] sym_ctx Info on cipher mode, state of the cipher. + * @param length Length, in octets, of the ct (and pt). + * @param ct pointer to ciphertext to be decrypted. + * @param[out] pt pointer to where to store the resulting plaintext. + * + * @return A return code of type #fsl_shw_return_t + * + */ +extern fsl_shw_return_t fsl_shw_symmetric_decrypt( + fsl_shw_uco_t* user_ctx, + fsl_shw_sko_t* key_info, + fsl_shw_scco_t* sym_ctx, + uint32_t length, + const uint8_t* ct, + uint8_t* pt); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-HCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-HASH-005 */ +/** + * Hash a stream of data with a cryptographic hash algorithm. + * + * The flags in the @a hash_ctx control the operation of this function. + * + * Hashing functions work on 64 octets of message at a time. Therefore, when + * any partial hashing of a long message is performed, the message @a length of + * each segment must be a multiple of 64. When ready to + * #FSL_HASH_FLAGS_FINALIZE the hash, the @a length may be any value. + * + * With the #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_FINALIZE flags on, a + * one-shot complete hash, including padding, will be performed. The @a length + * may be any value. + * + * The first octets of a data stream can be hashed by setting the + * #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_SAVE flags. The @a length must be + * a multiple of 64. + * + * The flag #FSL_HASH_FLAGS_LOAD is used to load a context previously saved by + * #FSL_HASH_FLAGS_SAVE. The two in combination will allow a (multiple-of-64 + * octets) 'middle sequence' of the data stream to be hashed with the + * beginning. The @a length must again be a multiple of 64. + * + * Since the flag #FSL_HASH_FLAGS_LOAD is used to load a context previously + * saved by #FSL_HASH_FLAGS_SAVE, the #FSL_HASH_FLAGS_LOAD and + * #FSL_HASH_FLAGS_FINALIZE flags, used together, can be used to finish the + * stream. The @a length may be any value. + * + * If the user program wants to do the padding for the hash, it can leave off + * the #FSL_HASH_FLAGS_FINALIZE flag. The @a length must then be a multiple of + * 64 octets. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] hash_ctx Hashing algorithm and state of the cipher. + * @param msg Pointer to the data to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result If not null, pointer to where to store the hash + * digest. + * @param result_len Number of octets to store in @a result. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hash( + fsl_shw_uco_t* user_ctx, + fsl_shw_hco_t* hash_ctx, + const uint8_t* msg, + uint32_t length, + uint8_t* result, + uint32_t result_len); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-API-BASIC-HMAC-001 */ +/** + * Precompute the Key hashes for an HMAC operation. + * + * This function may be used to calculate the inner and outer precomputes, + * which are the hash contexts resulting from hashing the XORed key for the + * 'inner hash' and the 'outer hash', respectively, of the HMAC function. + * + * After execution of this function, the @a hmac_ctx will contain the + * precomputed inner and outer contexts, so that they may be used by + * #fsl_shw_hmac(). The flags of @a hmac_ctx will be updated with + * #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT to mark their presence. In addtion, the + * #FSL_HMAC_FLAGS_INIT flag will be set. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The key being used in this operation. Key must be + * 1 to 64 octets long. + * @param[in,out] hmac_ctx The context which controls, by its flags and + * algorithm, the operation of this function. + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hmac_precompute( + fsl_shw_uco_t* user_ctx, + fsl_shw_sko_t* key_info, + fsl_shw_hmco_t* hmac_ctx); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-HMAC-002 */ +/** + * Continue, finalize, or one-shot an HMAC operation. + * + * There are a number of ways to use this function. The flags in the + * @a hmac_ctx object will determine what operations occur. + * + * If #FSL_HMAC_FLAGS_INIT is set, then the hash will be started either from + * the @a key_info, or from the precomputed inner hash value in the + * @a hmac_ctx, depending on the value of #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT. + * + * If, instead, #FSL_HMAC_FLAGS_LOAD is set, then the hash will be continued + * from the ongoing inner hash computation in the @a hmac_ctx. + * + * If #FSL_HMAC_FLAGS_FINALIZE are set, then the @a msg will be padded, hashed, + * the outer hash will be performed, and the @a result will be generated. + * + * If the #FSL_HMAC_FLAGS_SAVE flag is set, then the (ongoing or final) digest + * value will be stored in the ongoing inner hash computation field of the @a + * hmac_ctx. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info If #FSL_HMAC_FLAGS_INIT is set in the @a hmac_ctx, + * this is the key being used in this operation, and the + * IPAD. If #FSL_HMAC_FLAGS_INIT is set in the @a + * hmac_ctx and @a key_info is NULL, then + * #fsl_shw_hmac_precompute() has been used to populate + * the @a inner_precompute and @a outer_precompute + * contexts. If #FSL_HMAC_FLAGS_INIT is not set, this + * parameter is ignored. + + * @param[in,out] hmac_ctx The context which controls, by its flags and + * algorithm, the operation of this function. + * @param msg Pointer to the message to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result Pointer, of @a result_len octets, to where to + * store the HMAC. + * @param result_len Length of @a result buffer. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hmac( + fsl_shw_uco_t* user_ctx, + fsl_shw_sko_t* key_info, + fsl_shw_hmco_t* hmac_ctx, + const uint8_t* msg, + uint32_t length, + uint8_t* result, + uint32_t result_len); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-RNG-002 */ +/** + * Get random data. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length The number of octets of @a data being requested. + * @param[out] data A pointer to a location of @a length octets to where + * random data will be returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_get_random( + fsl_shw_uco_t* user_ctx, + uint32_t length, + uint8_t* data); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-RNG-003 */ +/** + * Add entropy to random number generator. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length Number of bytes at @a data. + * @param data Entropy to add to random number generator. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_add_entropy( + fsl_shw_uco_t* user_ctx, + uint32_t length, + uint8_t* data); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-SKO */ +/** + * Perform Generation-Encryption by doing a Cipher and a Hash. + * + * Generate the authentication value @a auth_value as well as encrypt the @a + * payload into @a ct (the ciphertext). This is a one-shot function, so all of + * the @a auth_data and the total message @a payload must passed in one call. + * This also means that the flags in the @a auth_ctx must be #FSL_ACCO_CTX_INIT + * and #FSL_ACCO_CTX_FINALIZE. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param auth_ctx Controlling object for Authenticate-decrypt. + * @param cipher_key_info The key being used for the cipher part of this + * operation. In CCM mode, this key is used for + * both parts. + * @param auth_key_info The key being used for the authentication part + * of this operation. In CCM mode, this key is + * ignored and may be NULL. + * @param auth_data_length Length, in octets, of @a auth_data. + * @param auth_data Data to be authenticated but not encrypted. + * @param payload_length Length, in octets, of @a payload. + * @param payload Pointer to the plaintext to be encrypted. + * @param[out] ct Pointer to the where the encrypted @a payload + * will be stored. Must be @a payload_length + * octets long. + * @param[out] auth_value Pointer to where the generated authentication + * field will be stored. Must be as many octets as + * indicated by MAC length in the @a function_ctx. + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_gen_encrypt( + fsl_shw_uco_t* user_ctx, + fsl_shw_acco_t* auth_ctx, + fsl_shw_sko_t* cipher_key_info, + fsl_shw_sko_t* auth_key_info, + uint32_t auth_data_length, + const uint8_t* auth_data, + uint32_t payload_length, + const uint8_t* payload, + uint8_t* ct, + uint8_t* auth_value); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-SKO */ +/** + * Perform Authentication-Decryption in Cipher + Hash. + * + * This function will perform a one-shot decryption of a data stream as well as + * authenticate the authentication value. This is a one-shot function, so all + * of the @a auth_data and the total message @a payload must passed in one + * call. This also means that the flags in the @a auth_ctx must be + * #FSL_ACCO_CTX_INIT and #FSL_ACCO_CTX_FINALIZE. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param auth_ctx Controlling object for Authenticate-decrypt. + * @param cipher_key_info The key being used for the cipher part of this + * operation. In CCM mode, this key is used for + * both parts. + * @param auth_key_info The key being used for the authentication part + * of this operation. In CCM mode, this key is + * ignored and may be NULL. + * @param auth_data_length Length, in octets, of @a auth_data. + * @param auth_data Data to be authenticated but not decrypted. + * @param payload_length Length, in octets, of @a ct and @a pt. + * @param ct Pointer to the encrypted input stream. + * @param auth_value The (encrypted) authentication value which will + * be authenticated. This is the same data as the + * (output) @a auth_value argument to + * #fsl_shw_gen_encrypt(). + * @param[out] payload Pointer to where the plaintext resulting from + * the decryption will be stored. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_auth_decrypt( + fsl_shw_uco_t* user_ctx, + fsl_shw_acco_t* auth_ctx, + fsl_shw_sko_t* cipher_key_info, + fsl_shw_sko_t* auth_key_info, + uint32_t auth_data_length, + const uint8_t* auth_data, + uint32_t payload_length, + const uint8_t* ct, + const uint8_t* auth_value, + uint8_t* payload); + +/*! + * Cause the hardware to create a new random key for secure memory use. + * + * Have the hardware use the secure hardware random number generator to load a + * new secret key into the hardware random key register. It will not be made + * active without a call to #fsl_shw_select_pf_key(). + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * + * @return A return code of type #fsl_shw_return_t. + */ +#ifdef __KERNEL__ + +extern fsl_shw_return_t fsl_shw_gen_random_pf_key(fsl_shw_uco_t * user_ctx); + +#else + +#define fsl_shw_gen_random_pf_key(user_ctx) FSL_RETURN_NO_RESOURCE_S + +#endif /* __KERNEL__ */ + +/*! + * Retrieve the detected tamper event. + * + * Note that if more than one event was detected, this routine will only ever + * return one of them. + * + * @param[in] user_ctx A user context from #fsl_shw_register_user(). + * @param[out] tamperp Location to store the tamper information. + * @param[out] timestampp Locate to store timestamp from hardwhare when + * an event was detected. + * + * + * @return A return code of type #fsl_shw_return_t (for instance, if the platform + * is not in a fail state. + */ +#ifdef __KERNEL__ + +extern fsl_shw_return_t fsl_shw_read_tamper_event(fsl_shw_uco_t * user_ctx, + fsl_shw_tamper_t * tamperp, + uint64_t * timestampp); +#else + +#define fsl_shw_read_tamper_event(user_ctx,tamperp,timestampp) \ + FSL_RETURN_NO_RESOURCE_S + +#endif /* __KERNEL__ */ + +/***************************************************************************** + * + * Functions internal to SHW driver. + * +*****************************************************************************/ + +fsl_shw_return_t +do_scc_encrypt_region(fsl_shw_uco_t* user_ctx, + void* partition_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t* black_data, + uint32_t* IV, fsl_shw_cypher_mode_t cypher_mode); + +fsl_shw_return_t +do_scc_decrypt_region(fsl_shw_uco_t* user_ctx, + void* partition_base, uint32_t offset_bytes, + uint32_t byte_count, const uint8_t* black_data, + uint32_t* IV, fsl_shw_cypher_mode_t cypher_mode); + + +/***************************************************************************** + * + * Functions available to other SHW-family drivers. + * +*****************************************************************************/ + +#ifdef __KERNEL__ +/** + * Add an entry to a work/result queue. + * + * @param pool Pointer to list structure + * @param entry Entry to place at tail of list + * + * @return void + */ +inline static void SHW_ADD_QUEUE_ENTRY(shw_queue_t* pool, + shw_queue_entry_t* entry) +{ + os_lock_context_t lock_context; + + entry->next = NULL; + os_lock_save_context(shw_queue_lock, lock_context); + + if (pool->tail != NULL) { + pool->tail->next = entry; + } else { + /* Queue was empty, so this is also the head. */ + pool->head = entry; + } + pool->tail = entry; + + os_unlock_restore_context(shw_queue_lock, lock_context); + + return; + + +} + + +/** + * Get first entry on the queue and remove it from the queue. + * + * @return Pointer to first entry, or NULL if none. + */ +inline static shw_queue_entry_t* SHW_POP_FIRST_ENTRY(shw_queue_t* queue) +{ + shw_queue_entry_t* entry; + os_lock_context_t lock_context; + + os_lock_save_context(shw_queue_lock, lock_context); + + entry = queue->head; + + if (entry != NULL) { + queue->head = entry->next; + entry->next = NULL; + /* If this was only entry, clear the tail. */ + if (queue->tail == entry) { + queue->tail = NULL; + } + } + + os_unlock_restore_context(shw_queue_lock, lock_context); + + return entry; +} + + + +/** + * Remove an entry from the list. + * + * If the entry not on the queue, no error will be returned. + * + * @param pool Pointer to work queue + * @param entry Entry to remove from queue + * + * @return void + * + */ +inline static void SHW_QUEUE_REMOVE_ENTRY(shw_queue_t* pool, + shw_queue_entry_t* entry) +{ + os_lock_context_t lock_context; + + os_lock_save_context(shw_queue_lock, lock_context); + + /* Check for quick case.*/ + if (pool->head == entry) { + pool->head = entry->next; + entry->next = NULL; + if (pool->tail == entry) { + pool->tail = NULL; + } + } else { + register shw_queue_entry_t* prev = pool->head; + + /* We know it is not the head, so start looking at entry after head. */ + while (prev->next) { + if (prev->next != entry) { + prev = prev->next; /* Try another */ + continue; + } else { + /* Unlink from chain. */ + prev->next = entry->next; + entry->next = NULL; + /* If last in chain, update tail. */ + if (pool->tail == entry) { + pool->tail = prev; + } + break; + } + } /* while */ + } + + os_unlock_restore_context(shw_queue_lock, lock_context); + + return; +} +#endif /* __KERNEL__ */ + + +/***************************************************************************** + * + * Functions available to User-Mode API functions + * + ****************************************************************************/ +#ifndef __KERNEL__ + + + /** + * Sanity checks the user context object fields to ensure that they make some + * sense before passing the uco as a parameter. + * + * @brief Verify the user context object + * + * @param uco user context object + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t validate_uco(fsl_shw_uco_t *uco); + + +/** + * Initialize a request block to go to the driver. + * + * @param hdr Pointer to request block header + * @param user_ctx Pointer to user's context + * + * @return void + */ +inline static void init_req(struct shw_req_header* hdr, + fsl_shw_uco_t* user_ctx) +{ + hdr->flags = user_ctx->flags; + hdr->user_ref = user_ctx->user_ref; + hdr->code = FSL_RETURN_ERROR_S; + + return; +} + + +/** + * Send a request block off to the driver. + * + * If this is a non-blocking request, then req will be freed. + * + * @param type The type of request being sent + * @param req Pointer to the request block + * @param ctx Pointer to user's context + * + * @return code from driver if ioctl() succeeded, otherwise + * FSL_RETURN_INTERNAL_ERROR_S. + */ +inline static fsl_shw_return_t send_req(shw_user_request_t type, + struct shw_req_header* req, + fsl_shw_uco_t* ctx) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + unsigned blocking = ctx->flags & FSL_UCO_BLOCKING_MODE; + int code; + + code = ioctl(ctx->openfd, SHW_IOCTL_REQUEST + type, req); + + if (code == 0) { + if (blocking) { + ret = req->code; + } else { + ret = FSL_RETURN_OK_S; + } + } else { +#ifdef FSL_DEBUG + fprintf(stderr, "SHW: send_req failed with (%d), %s\n", errno, + strerror(errno)); +#endif + } + + if (blocking) { + free(req); + } + + return ret; +} + + +#endif /* no __KERNEL__ */ + +#if defined(FSL_HAVE_DRYICE) +/* Some kernel functions */ +void fsl_shw_permute1_bytes(const uint8_t * key, uint8_t * permuted_key, + int key_count); +void fsl_shw_permute1_bytes_to_words(const uint8_t * key, + uint32_t * permuted_key, int key_count); + +#define PFKEY_TO_STR(key_in) \ +({ \ + di_key_t key = key_in; \ + \ + ((key == DI_KEY_FK) ? "IIM" : \ + ((key == DI_KEY_PK) ? "PRG" : \ + ((key == DI_KEY_RK) ? "RND" : \ + ((key == DI_KEY_FPK) ? "IIM_PRG" : \ + ((key == DI_KEY_FRK) ? "IIM_RND" : "unk"))))); \ +}) + +#ifdef DIAG_SECURITY_FUNC +extern const char *di_error_string(int code); +#endif + +#endif /* HAVE DRYICE */ + +#endif /* SHW_DRIVER_H */ diff --git a/drivers/mxc/security/rng/include/shw_hash.h b/drivers/mxc/security/rng/include/shw_hash.h new file mode 100644 index 000000000000..d0e7eed0e1d2 --- /dev/null +++ b/drivers/mxc/security/rng/include/shw_hash.h @@ -0,0 +1,96 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +/*! + * @file shw_hash.h + * + * This file contains definitions for use of the (internal) SHW hash + * software computation. It defines the usual three steps: + * + * - #shw_hash_init() + * - #shw_hash_update() + * - #shw_hash_final() + * + * The only other item of note to callers is #SHW_HASH_LEN, which is the number + * of bytes calculated for the hash. + */ + +#ifndef SHW_HASH_H +#define SHW_HASH_H + +/*! Define which gives the number of bytes available in an hash result */ +#define SHW_HASH_LEN 32 + +/* Define which matches block length in bytes of the underlying hash */ +#define SHW_HASH_BLOCK_LEN 64 + +/* "Internal" define which matches SHA-256 state size (32-bit words) */ +#define SHW_HASH_STATE_WORDS 8 + +/* "Internal" define which matches word length in blocks of the underlying + hash. */ +#define SHW_HASH_BLOCK_WORD_SIZE 16 + +#define SHW_HASH_STATE_SIZE 32 + +/*! + * State for a SHA-1/SHA-2 Hash + * + * (Note to maintainers: state needs to be updated to uint64_t to handle + * SHA-384/SHA-512)... And bit_count to uint128_t (heh). + */ +typedef struct shw_hash_state { + unsigned int partial_count_bytes; /*!< Number of bytes of message sitting + * in @c partial_block */ + uint8_t partial_block[SHW_HASH_BLOCK_LEN]; /*!< Data waiting to be processed as a block */ + uint32_t state[SHW_HASH_STATE_WORDS]; /*!< Current hash state variables */ + uint64_t bit_count; /*!< Number of bits sent through the update function */ +} shw_hash_state_t; + +/*! + * Initialize the hash state structure + * + * @param state Address of hash state structure. + * @param algorithm Which hash algorithm to use (must be FSL_HASH_ALG_SHA256) + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hash_init(shw_hash_state_t * state, + fsl_shw_hash_alg_t algorithm); + +/*! + * Put data into the hash calculation + * + * @param state Address of hash state structure. + * @param msg Address of the message data for the hash. + * @param msg_len Number of bytes of @c msg. + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hash_update(shw_hash_state_t * state, + const uint8_t * msg, unsigned int msg_len); + +/*! + * Calculate the final hash value + * + * @param state Address of hash state structure. + * @param hash Address of location to store the hash. + * @param hash_len Number of bytes of @c hash to be stored. + * + * @return FSL_RETURN_OK_S if all went well, FSL_RETURN_BAD_DATA_LENGTH_S if + * hash_len is too long, otherwise an error code. + */ +fsl_shw_return_t shw_hash_final(shw_hash_state_t * state, + uint8_t * hash, unsigned int hash_len); + +#endif /* SHW_HASH_H */ diff --git a/drivers/mxc/security/rng/include/shw_hmac.h b/drivers/mxc/security/rng/include/shw_hmac.h new file mode 100644 index 000000000000..99d373149f51 --- /dev/null +++ b/drivers/mxc/security/rng/include/shw_hmac.h @@ -0,0 +1,82 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +/*! + * @file shw_hmac.h + * + * This file contains definitions for use of the (internal) SHW HMAC + * software computation. It defines the usual three steps: + * + * - #shw_hmac_init() + * - #shw_hmac_update() + * - #shw_hmac_final() + * + * The only other item of note to callers is #SHW_HASH_LEN, which is the number + * of bytes calculated for the HMAC. + */ + +#ifndef SHW_HMAC_H +#define SHW_HMAC_H + +#include "shw_hash.h" + +/*! + * State for an HMAC + * + * Note to callers: This structure contains key material and should be kept in + * a secure location, such as internal RAM. + */ +typedef struct shw_hmac_state { + shw_hash_state_t inner_hash; /*!< Current state of inner hash */ + shw_hash_state_t outer_hash; /*!< Current state of outer hash */ +} shw_hmac_state_t; + +/*! + * Initialize the HMAC state structure with the HMAC key + * + * @param state Address of HMAC state structure. + * @param key Address of the key to be used for the HMAC. + * @param key_len Number of bytes of @c key. This must not be greater than + * the block size of the underlying hash (#SHW_HASH_BLOCK_LEN). + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hmac_init(shw_hmac_state_t * state, + const uint8_t * key, unsigned int key_len); + +/*! + * Put data into the HMAC calculation + * + * @param state Address of HMAC state structure. + * @param msg Address of the message data for the HMAC. + * @param msg_len Number of bytes of @c msg. + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hmac_update(shw_hmac_state_t * state, + const uint8_t * msg, unsigned int msg_len); + +/*! + * Calculate the final HMAC + * + * @param state Address of HMAC state structure. + * @param hmac Address of location to store the HMAC. + * @param hmac_len Number of bytes of @c mac to be stored. Probably best if + * this value is no greater than #SHW_HASH_LEN. + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hmac_final(shw_hmac_state_t * state, + uint8_t * hmac, unsigned int hmac_len); + +#endif /* SHW_HMAC_H */ diff --git a/drivers/mxc/security/rng/include/shw_internals.h b/drivers/mxc/security/rng/include/shw_internals.h new file mode 100644 index 000000000000..c917ec813162 --- /dev/null +++ b/drivers/mxc/security/rng/include/shw_internals.h @@ -0,0 +1,162 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef SHW_INTERNALS_H +#define SHW_INTERNALS_H + +/*! @file shw_internals.h + * + * This file contains definitions which are internal to the SHW driver. + * + * This header file should only ever be included by shw_driver.c + * + * Compile-time flags minimally needed: + * + * @li Some sort of platform flag. + * + */ + +#include "portable_os.h" +#include "shw_driver.h" + +/*! @defgroup shwcompileflags SHW Compile Flags + * + * These are flags which are used to configure the SHW driver at compilation + * time. + * + * The terms 'defined' and 'undefined' refer to whether a @c \#define (or -D on + * a compile command) has defined a given preprocessor symbol. If a given + * symbol is defined, then @c \#ifdef \<symbol\> will succeed. Some symbols + * described below default to not having a definition, i.e. they are undefined. + * + */ + +/*! @addtogroup shwcompileflags */ +/*! @{ */ +#ifndef SHW_MAJOR_NODE +/*! + * This should be configured in a Makefile/compile command line. It is the + * value the driver will use to register itself as a device driver for a + * /dev/node file. Zero means allow (Linux) to assign a value. Any positive + * number will be attempted as the registration value, to allow for + * coordination with the creation/existence of a /dev/fsl_shw (for instance) + * file in the filesystem. + */ +#define SHW_MAJOR_NODE 0 +#endif + +/* Temporarily define compile-time flags to make Doxygen happy and allow them + to get into the documentation. */ +#ifdef DOXYGEN_HACK + +/*! + * Turn on compilation of run-time operational, debug, and error messages. + * + * This flag is undefined by default. + */ +/* REQ-FSLSHW-DEBUG-001 */ +#define SHW_DEBUG +#undef SHW_DEBUG + +/*! @} */ +#endif /* end DOXYGEN_HACK */ + +#ifndef SHW_DRIVER_NAME +/*! @addtogroup shwcompileflags */ +/*! @{ */ +/*! Name the driver will use to register itself to the kernel as the driver for + * the #shw_major_node and interrupt handling. */ +#define SHW_DRIVER_NAME "fsl_shw" +/*! @} */ +#endif +/*#define SHW_DEBUG*/ + +/*! + * Add a user context onto the list of registered users. + * + * Place it at the head of the #user_list queue. + * + * @param ctx A pointer to a user context + * + * @return void + */ +inline static void SHW_ADD_USER(fsl_shw_uco_t * ctx) +{ + os_lock_context_t lock_context; + + os_lock_save_context(shw_queue_lock, lock_context); + ctx->next = user_list; + user_list = ctx; + os_unlock_restore_context(shw_queue_lock, lock_context); + +} + +/*! + * Remove a user context from the list of registered users. + * + * @param ctx A pointer to a user context + * + * @return void + * + */ +inline static void SHW_REMOVE_USER(fsl_shw_uco_t * ctx) +{ + fsl_shw_uco_t *prev_ctx = user_list; + os_lock_context_t lock_context; + + os_lock_save_context(shw_queue_lock, lock_context); + + if (prev_ctx == ctx) { + /* Found at head, so just set new head */ + user_list = ctx->next; + } else { + for (; (prev_ctx != NULL); prev_ctx = prev_ctx->next) { + if (prev_ctx->next == ctx) { + prev_ctx->next = ctx->next; + break; + } + } + } + os_unlock_restore_context(shw_queue_lock, lock_context); +} + +static void shw_user_callback(fsl_shw_uco_t * uco); + +/* internal functions */ +static os_error_code shw_setup_user_driver_interaction(void); +static void shw_cleanup(void); + +static os_error_code init_uco(fsl_shw_uco_t * user_ctx, void *user_mode_uco); +static os_error_code get_capabilities(fsl_shw_uco_t * user_ctx, + void *user_mode_pco_request); +static os_error_code get_results(fsl_shw_uco_t * user_ctx, + void *user_mode_result_req); +static os_error_code get_random(fsl_shw_uco_t * user_ctx, + void *user_mode_get_random_req); +static os_error_code add_entropy(fsl_shw_uco_t * user_ctx, + void *user_mode_add_entropy_req); + +void* wire_user_memory(void* address, uint32_t length, void** page_ctx); +void unwire_user_memory(void** page_ctx); +os_error_code map_user_memory(struct vm_area_struct* vma, + uint32_t physical_addr, uint32_t size); +os_error_code unmap_user_memory(uint32_t user_addr, uint32_t size); + +#if defined(LINUX_VERSION_CODE) + +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Device Driver for FSL SHW API"); + +#endif /* LINUX_VERSION_CODE */ + +#endif /* SHW_INTERNALS_H */ diff --git a/drivers/mxc/security/rng/rng_driver.c b/drivers/mxc/security/rng/rng_driver.c new file mode 100644 index 000000000000..5664e11860fe --- /dev/null +++ b/drivers/mxc/security/rng/rng_driver.c @@ -0,0 +1,1150 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! @file rng_driver.c + * + * This is the driver code for the hardware Random Number Generator (RNG). + * + * It provides the following functions to callers: + * fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t* user_ctx, + * uint32_t length, + * uint8_t* data); + * + * fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t* user_ctx, + * uint32_t length, + * uint8_t* data); + * + * The life of the driver starts at boot (or module load) time, with a call by + * the kernel to #rng_init(). As part of initialization, a background task + * running #rng_entropy_task() will be created. + * + * The life of the driver ends when the kernel is shutting down (or the driver + * is being unloaded). At this time, #rng_shutdown() is called. No function + * will ever be called after that point. In the case that the driver is + * reloaded, a new copy of the driver, with fresh global values, etc., is + * loaded, and there will be a new call to #rng_init(). + * + * A call to fsl_shw_get_random() gets converted into a work entry which is + * queued and handed off to a background task for fulfilment. This provides + * for a single thread of control for reading the RNG's FIFO register, which + * might otherwise underflow if not carefully managed. + * + * A call to fsl_shw_add_entropy() will cause the additional entropy to + * be passed directly into the hardware. + * + * In a debug configuration, it provides the following kernel functions: + * rng_return_t rng_read_register(uint32_t byte_offset, uint32_t* valuep); + * rng_return_t rng_write_register(uint32_t byte_offset, uint32_t value); + * @ingroup RNG + */ + +#include "portable_os.h" +#include "fsl_shw.h" +#include "rng_internals.h" + +#ifdef FSL_HAVE_SCC2 +#include <linux/mxc_scc2_driver.h> +#else +#include <linux/mxc_scc_driver.h> +#endif + +#if defined(RNG_DEBUG) || defined(RNG_ENTROPY_DEBUG) || \ + defined(RNG_REGISTER_DEBUG) + +#include <diagnostic.h> + +#else + +#define LOG_KDIAG_ARGS(fmt, ...) +#define LOG_KDIAG(diag) + +#endif + +/* These are often handy */ +#ifndef FALSE +/*! Non-true value for arguments, return values. */ +#define FALSE 0 +#endif +#ifndef TRUE +/*! True value for arguments, return values. */ +#define TRUE 1 +#endif + +/****************************************************************************** + * + * Global / Static Variables + * + *****************************************************************************/ + +/*! + * This is type void* so that a) it cannot directly be dereferenced, and b) + * pointer arithmetic on it will function for the byte offsets in rng_rnga.h + * and rng_rngc.h + * + * rng_base is the location in the iomap where the RNG's registers + * (and memory) start. + * + * The referenced data is declared volatile so that the compiler will + * not make any assumptions about the value of registers in the RNG, + * and thus will always reload the register into CPU memory before + * using it (i.e. wherever it is referenced in the driver). + * + * This value should only be referenced by the #RNG_READ_REGISTER and + * #RNG_WRITE_REGISTER macros and their ilk. All dereferences must be + * 32 bits wide. + */ +static volatile void *rng_base; + +/*! + * Flag to say whether interrupt handler has been registered for RNG + * interrupt */ +static int rng_irq_set = FALSE; + +/*! + * Size of the RNG's OUTPUT_FIFO, in words. Retrieved with + * #RNG_GET_FIFO_SIZE() during driver initialization. + */ +static int rng_output_fifo_size; + +/*! Major number for device driver. */ +static int rng_major; + +/*! Registration handle for registering driver with OS. */ +os_driver_reg_t rng_reg_handle; + +/*! + * Internal flag to know whether RNG is in Failed state (and thus many + * registers are unavailable). If the value ever goes to #RNG_STATUS_FAILED, + * it will never change. + */ +static volatile rng_status_t rng_availability = RNG_STATUS_INITIAL; + +/*! + * Global lock for the RNG driver. Mainly used for entries on the RNG work + * queue. + */ +static os_lock_t rng_queue_lock = NULL; + +/*! + * Queue for the RNG task to process. + */ +static shw_queue_t rng_work_queue; + +/*! + * Flag to say whether task initialization succeeded. + */ +static unsigned task_started = FALSE; +/*! + * Waiting queue for RNG SELF TESTING + */ +static DECLARE_COMPLETION(rng_self_testing); +static DECLARE_COMPLETION(rng_seed_done); +/*! + * Object for blocking-mode callers of RNG driver to sleep. + */ +OS_WAIT_OBJECT(rng_wait_queue); + +/****************************************************************************** + * + * Function Implementations - Externally Accessible + * + *****************************************************************************/ + +/*****************************************************************************/ +/* fn rng_init() */ +/*****************************************************************************/ +/*! + * Initialize the driver. + * + * Set up the driver to have access to RNG device registers and verify that + * it appears to be a proper working device. + * + * Set up interrupt handling. Assure RNG is ready to go and (possibly) set it + * into High Assurance mode. Create a background task to run + * #rng_entropy_task(). Set up up a callback with the SCC driver should the + * security alarm go off. Tell the kernel that the driver is here. + * + * This routine is called during kernel init or module load (insmod). + * + * The function will fail in one of two ways: Returning OK to the caller so + * that kernel startup / driver initialization completes, or returning an + * error. In the success case, the function could set the rng_avaailability to + * RNG_STATUS_FAILED so that only minimal support (e.g. register peek / poke) + * is available in the driver. + * + * @return a call to os_dev_init_return() + */ +OS_DEV_INIT(rng_init) +{ + struct clk *clk; + os_error_code return_code = OS_ERROR_FAIL_S; + rng_availability = RNG_STATUS_CHECKING; + +#if !defined(FSL_HAVE_RNGA) + INIT_COMPLETION(rng_self_testing); + INIT_COMPLETION(rng_seed_done); +#endif + rng_work_queue.head = NULL; + rng_work_queue.tail = NULL; + + clk = clk_get(NULL, "rng_clk"); + + // Check that the clock was found + if (IS_ERR(clk)) { + LOG_KDIAG("RNG: Failed to find rng_clock."); + return_code = OS_ERROR_FAIL_S; + goto check_err; + } + + clk_enable(clk); + + os_printk(KERN_INFO "RNG Driver: Loading\n"); + + return_code = rng_map_RNG_memory(); + if (return_code != OS_ERROR_OK_S) { + rng_availability = RNG_STATUS_UNIMPLEMENTED; + LOG_KDIAG_ARGS("RNG: Driver failed to map RNG registers. %d", + return_code); + goto check_err; + } + LOG_KDIAG_ARGS("RNG Driver: rng_base is 0x%08x", (uint32_t) rng_base); + /*Check SCC keys are fused */ + if (RNG_HAS_ERROR()) { + if (RNG_HAS_BAD_KEY()) { +#ifdef RNG_DEBUG +#if !defined(FSL_HAVE_RNGA) + LOG_KDIAG("ERROR: BAD KEYS SELECTED"); + { + uint32_t rngc_status = + RNG_READ_REGISTER(RNGC_STATUS); + uint32_t rngc_error = + RNG_READ_REGISTER(RNGC_ERROR); + LOG_KDIAG_ARGS + ("status register: %08x, error status: %08x", + rngc_status, rngc_error); + } +#endif +#endif + rng_availability = RNG_STATUS_FAILED; + return_code = OS_ERROR_FAIL_S; + goto check_err; + } + } + + /* Check RNG configuration and status */ + return_code = rng_grab_config_values(); + if (return_code != OS_ERROR_OK_S) { + rng_availability = RNG_STATUS_UNIMPLEMENTED; + goto check_err; + } + + /* Masking All Interrupts */ + /* They are unmasked later in rng_setup_interrupt_handling() */ + RNG_MASK_ALL_INTERRUPTS(); + + RNG_WAKE(); + + /* Determine status of RNG */ + if (RNG_OSCILLATOR_FAILED()) { + LOG_KDIAG("RNG Driver: RNG Oscillator is dead"); + rng_availability = RNG_STATUS_FAILED; + goto check_err; + } + + /* Oscillator not dead. Setup interrupt code and start the RNG. */ + if ((return_code = rng_setup_interrupt_handling()) == OS_ERROR_OK_S) { +#if defined(FSL_HAVE_RNGA) + scc_return_t scc_code; +#endif + + RNG_GO(); + + /* Self Testing For RNG */ + do { + RNG_CLEAR_ERR(); + + /* wait for Clearing Erring finished */ + msleep(1); + + RNG_UNMASK_ALL_INTERRUPTS(); + RNG_SELF_TEST(); +#if !defined(FSL_HAVE_RNGA) + wait_for_completion(&rng_self_testing); +#endif + } while (RNG_CHECK_SELF_ERR()); + + RNG_CLEAR_ALL_STATUS(); + /* checking for RNG SEED done */ + do { + RNG_CLEAR_ERR(); + RNG_SEED_GEN(); +#if !defined(FSL_HAVE_RNGA) + wait_for_completion(&rng_seed_done); +#endif + } while (RNG_CHECK_SEED_ERR()); +#ifndef RNG_NO_FORCE_HIGH_ASSURANCE + RNG_SET_HIGH_ASSURANCE(); +#endif + if (RNG_GET_HIGH_ASSURANCE()) { + LOG_KDIAG("RNG Driver: RNG is in High Assurance mode"); + } else { +#ifndef RNG_NO_FORCE_HIGH_ASSURANCE + LOG_KDIAG + ("RNG Driver: RNG could not be put in High Assurance mode"); + rng_availability = RNG_STATUS_FAILED; + goto check_err; +#endif /* RNG_NO_FORCE_HIGH_ASSURANCE */ + } + + /* Check that RNG is OK */ + if (!RNG_WORKING()) { + LOG_KDIAG_ARGS + ("RNG determined to be inoperable. Status %08x", + RNG_GET_STATUS()); + /* Couldn't wake it up or other problem */ + rng_availability = RNG_STATUS_FAILED; + goto check_err; + } + + rng_queue_lock = os_lock_alloc_init(); + if (rng_queue_lock == NULL) { + LOG_KDIAG("RNG: lock initialization failed"); + rng_availability = RNG_STATUS_FAILED; + goto check_err; + } + + return_code = os_create_task(rng_entropy_task); + if (return_code != OS_ERROR_OK_S) { + LOG_KDIAG("RNG: task initialization failed"); + rng_availability = RNG_STATUS_FAILED; + goto check_err; + } else { + task_started = TRUE; + } +#ifdef FSL_HAVE_RNGA + scc_code = scc_monitor_security_failure(rng_sec_failure); + if (scc_code != SCC_RET_OK) { + LOG_KDIAG_ARGS("Failed to register SCC callback: %d", + scc_code); +#ifndef RNG_NO_FORCE_HIGH_ASSURANCE + return_code = OS_ERROR_FAIL_S; + goto check_err; +#endif + } +#endif /* FSL_HAVE_RNGA */ + return_code = os_driver_init_registration(rng_reg_handle); + if (return_code != OS_ERROR_OK_S) { + goto check_err; + } + /* add power suspend here */ + /* add power resume here */ + return_code = + os_driver_complete_registration(rng_reg_handle, + rng_major, RNG_DRIVER_NAME); + } + /* RNG is working */ + + check_err: + + /* If FIFO underflow or other error occurred during drain, this will fail, + * as system will have been put into fail mode by SCC. */ + if ((return_code == OS_ERROR_OK_S) + && (rng_availability == RNG_STATUS_CHECKING)) { + RNG_PUT_RNG_TO_SLEEP(); + rng_availability = RNG_STATUS_OK; /* RNG & driver are ready */ + } else if (return_code != OS_ERROR_OK_S) { + os_printk(KERN_ALERT "Driver initialization failed. %d", + return_code); + rng_cleanup(); + } + + os_dev_init_return(return_code); + +} /* rng_init */ + +/*****************************************************************************/ +/* fn rng_shutdown() */ +/*****************************************************************************/ +/*! + * Prepare driver for exit. + * + * This is called during @c rmmod when the driver is unloading. + * Try to undo whatever was done during #rng_init(), to make the machine be + * in the same state, if possible. + * + * Calls rng_cleanup() to do all work, and then unmap device's register space. + */ +OS_DEV_SHUTDOWN(rng_shutdown) +{ + LOG_KDIAG("shutdown called"); + + rng_cleanup(); + + os_driver_remove_registration(rng_reg_handle); + if (rng_base != NULL) { + /* release the virtual memory map to the RNG */ + os_unmap_device((void *)rng_base, RNG_ADDRESS_RANGE); + rng_base = NULL; + } + + os_dev_shutdown_return(OS_ERROR_OK_S); +} /* rng_shutdown */ + +/*****************************************************************************/ +/* fn rng_cleanup() */ +/*****************************************************************************/ +/*! + * Undo everything done by rng_init() and place driver in fail mode. + * + * Deregister from SCC, stop tasklet, shutdown the RNG. Leave the register + * map in place in case other drivers call rng_read/write_register() + * + * @return void + */ +static void rng_cleanup(void) +{ + struct clk *clk; + +#ifdef FSL_HAVE_RNGA + scc_stop_monitoring_security_failure(rng_sec_failure); +#endif + + clk = clk_get(NULL, "rng_clk"); + clk_disable(clk); + if (task_started) { + os_dev_stop_task(rng_entropy_task); + } + + if (rng_base != NULL) { + /* mask off RNG interrupts */ + RNG_MASK_ALL_INTERRUPTS(); + RNG_SLEEP(); + + if (rng_irq_set) { + /* unmap the interrupts from the IRQ lines */ + os_deregister_interrupt(INT_RNG); + rng_irq_set = FALSE; + } + LOG_KDIAG("Leaving rng driver status as failed"); + rng_availability = RNG_STATUS_FAILED; + } else { + LOG_KDIAG("Leaving rng driver status as unimplemented"); + rng_availability = RNG_STATUS_UNIMPLEMENTED; + } + LOG_KDIAG("Cleaned up"); +} /* rng_cleanup */ + +/*! + * Post-process routine for fsl_shw_get_random(). + * + * This function will copy the random data generated by the background task + * into the user's buffer and then free the local buffer. + * + * @param gen_entry The work request. + * + * @return 0 = meaning work completed, pass back result. + */ +static uint32_t finish_random(shw_queue_entry_t * gen_entry) +{ + rng_work_entry_t *work = (rng_work_entry_t *) gen_entry; + + if (work->hdr.flags & FSL_UCO_USERMODE_USER) { + os_copy_to_user(work->data_user, work->data_local, + work->length); + } else { + memcpy(work->data_user, work->data_local, work->length); + } + + os_free_memory(work->data_local); + work->data_local = NULL; + + return 0; /* means completed. */ +} + +/* REQ-FSLSHW-PINTFC-API-BASIC-RNG-002 */ +/*****************************************************************************/ +/* fn fsl_shw_get_random() */ +/*****************************************************************************/ +/*! + * Get random data. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length The number of octets of @a data being requested. + * @param data A pointer to a location of @a length octets to where + * random data will be returned. + * + * @return FSL_RETURN_NO_RESOURCE_S A return code of type #fsl_shw_return_t. + * FSL_RETURN_OK_S + */ +fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, uint32_t length, + uint8_t * data) +{ + fsl_shw_return_t return_code = FSL_RETURN_NO_RESOURCE_S; + /* Boost up length to cover any 'missing' bytes at end of a word */ + uint32_t *buf = os_alloc_memory(length + 3, 0); + volatile rng_work_entry_t *work = os_alloc_memory(sizeof(*work), 0); + + if ((rng_availability != RNG_STATUS_OK) || (buf == NULL) + || (work == NULL)) { + if (rng_availability != RNG_STATUS_OK) { + LOG_KDIAG_ARGS("rng not available: %d\n", + rng_availability); + } else { + LOG_KDIAG_ARGS + ("Resource allocation failure: %d or %d bytes", + length, sizeof(*work)); + } + /* Cannot perform function. Clean up and clear out. */ + if (buf != NULL) { + os_free_memory(buf); + } + if (work != NULL) { + os_free_memory((void *)work); + } + } else { + unsigned blocking = user_ctx->flags & FSL_UCO_BLOCKING_MODE; + + work->hdr.user_ctx = user_ctx; + work->hdr.flags = user_ctx->flags; + work->hdr.callback = user_ctx->callback; + work->hdr.user_ref = user_ctx->user_ref; + work->hdr.postprocess = finish_random; + work->length = length; + work->data_local = buf; + work->data_user = data; + + RNG_ADD_WORK_ENTRY((rng_work_entry_t *) work); + + if (blocking) { + os_sleep(rng_wait_queue, work->completed != FALSE, + FALSE); + finish_random((shw_queue_entry_t *) work); + return_code = work->hdr.code; + os_free_memory((void *)work); + } else { + return_code = FSL_RETURN_OK_S; + } + } + + return return_code; +} /* fsl_shw_get_entropy */ + +/*****************************************************************************/ +/* fn fsl_shw_add_entropy() */ +/*****************************************************************************/ +/*! + * Add entropy to random number generator. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length Number of bytes at @a data. + * @param data Entropy to add to random number generator. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, uint32_t length, + uint8_t * data) +{ + fsl_shw_return_t return_code = FSL_RETURN_NO_RESOURCE_S; +#if defined(FSL_HAVE_RNGC) + /* No Entropy Register in RNGC */ + return_code = FSL_RETURN_OK_S; +#else + uint32_t *local_data = NULL; + if (rng_availability == RNG_STATUS_OK) { + /* make 32-bit aligned place to hold data */ + local_data = os_alloc_memory(length + 3, 0); + if (local_data == NULL) { + return_code = FSL_RETURN_NO_RESOURCE_S; + } else { + memcpy(local_data, data, length); + + /* Copy one word at a time to hardware */ + while (TRUE) { + register uint32_t *ptr = local_data; + + RNG_ADD_ENTROPY(*ptr++); + if (length <= 4) { + break; + } + length -= 4; + } + return_code = FSL_RETURN_OK_S; + os_free_memory(local_data); + } /* else local_data not NULL */ + + } +#endif + /* rng_availability is OK */ + return return_code; +} /* fsl_shw_add_entropy */ + +#ifdef RNG_REGISTER_PEEK_POKE +/*****************************************************************************/ +/* fn rng_read_register() */ +/*****************************************************************************/ +/* + * Debug routines to allow reading of RNG registers. + * + * This routine is only for accesses by other than this driver. + * + * @param register_offset The byte offset of the register to be read. + * @param value Pointer to store the value of the register. + * + * @return RNG_RET_OK or an error return. + */ +rng_return_t rng_read_register(uint32_t register_offset, uint32_t * value) +{ + rng_return_t return_code = RNG_RET_FAIL; + + if ((rng_availability == RNG_STATUS_OK) + || (rng_availability == RNG_STATUS_FAILED)) { + if ((rng_check_register_offset(register_offset) + && rng_check_register_accessible(register_offset, + RNG_CHECK_READ))) { + /* The guards let the check through */ + *value = RNG_READ_REGISTER(register_offset); + return_code = RNG_RET_OK; + } + } + + return return_code; +} /* rng_read_register */ + +/*****************************************************************************/ +/* fn rng_write_register() */ +/*****************************************************************************/ +/* + * Debug routines to allow writing of RNG registers. + * + * This routine is only for accesses by other than this driver. + * + * @param register_offset The byte offset of the register to be written. + * @param value Value to store in the register. + * + * @return RNG_RET_OK or an error return. + */ +rng_return_t rng_write_register(uint32_t register_offset, uint32_t value) +{ + rng_return_t return_code = RNG_RET_FAIL; + + if ((rng_availability == RNG_STATUS_OK) + || (rng_availability == RNG_STATUS_FAILED)) { + if ((rng_check_register_offset(register_offset) + && rng_check_register_accessible(register_offset, + RNG_CHECK_WRITE))) { + RNG_WRITE_REGISTER(register_offset, value); + return_code = RNG_RET_OK; + } + } + + return return_code; +} /* rng_write_register */ +#endif /* RNG_REGISTER_PEEK_POKE */ + +/****************************************************************************** + * + * Function Implementations - Internal + * + *****************************************************************************/ + +#ifdef RNG_REGISTER_PEEK_POKE +/*****************************************************************************/ +/* fn check_register_offset() */ +/*****************************************************************************/ +/*! + * Verify that the @c offset is appropriate for the RNG's register set. + * + * @param[in] offset The (byte) offset within the RNG block + * of the register to be accessed. See + * RNG(A, C) register definitions for meanings. + * + * This routine is only for checking accesses by other than this driver. + * + * @return 0 if register offset out of bounds, 1 if ok to use + */ +inline int rng_check_register_offset(uint32_t offset) +{ + int return_code = FALSE; /* invalid */ + + /* Make sure offset isn't too high and also that it is aligned to + * aa 32-bit offset (multiple of four). + */ + if ((offset < RNG_ADDRESS_RANGE) && (offset % sizeof(uint32_t) == 0)) { + return_code = TRUE; /* OK */ + } else { + pr_debug("RNG: Denied access to offset %8x\n", offset); + } + + return return_code; + +} /* rng_check_register */ + +/*****************************************************************************/ +/* fn check_register_accessible() */ +/*****************************************************************************/ +/*! + * Make sure that register access is legal. + * + * Verify that, if in secure mode, only safe registers are used. + * For any register access, make sure that read-only registers are not written + * and that write-only registers are not read. This check also disallows any + * access to the RNG's Output FIFO, to prevent other drivers from draining the + * FIFO and causing an underflow condition. + * + * This routine is only for checking accesses by other than this driver. + * + * @param offset The (byte) offset within the RNG block + * of the register to be accessed. See + * @ref rngregs for meanings. + * @param access_write 0 for read, anything else for write + * + * @return 0 if invalid, 1 if OK. + */ +static int rng_check_register_accessible(uint32_t offset, int access_write) +{ + int return_code = FALSE; /* invalid */ + uint32_t secure = RNG_GET_HIGH_ASSURANCE(); + + /* First check for RNG in Secure Mode -- most registers inaccessible. + * Also disallowing access to RNG_OUTPUT_FIFO except by the driver. + */ + if (! +#ifdef FSL_HAVE_RNGA + (secure && + ((offset == RNGA_OUTPUT_FIFO) || + (offset == RNGA_MODE) || + (offset == RNGA_VERIFICATION_CONTROL) || + (offset == RNGA_OSCILLATOR_CONTROL_COUNTER) || + (offset == RNGA_OSCILLATOR1_COUNTER) || + (offset == RNGA_OSCILLATOR2_COUNTER) || + (offset == RNGA_OSCILLATOR_COUNTER_STATUS))) +#else /* RNGB or RNGC */ + (secure && + ((offset == RNGC_FIFO) || + (offset == RNGC_VERIFICATION_CONTROL) || + (offset == RNGC_OSC_COUNTER_CONTROL) || + (offset == RNGC_OSC_COUNTER) || + (offset == RNGC_OSC_COUNTER_STATUS))) +#endif + ) { + + /* Passed that test. Either not in high assurance, and/or are + checking register that is always available. Now check + R/W permissions. */ + if (access_write == RNG_CHECK_READ) { /* read request */ + /* Only the entropy register is write-only */ +#ifdef FSL_HAVE_RNGC + /* No registers are write-only */ + return_code = TRUE; +#else /* else RNGA or RNGB */ +#ifdef FSL_HAVE_RNGA + if (1) { +#else + if (!(offset == RNGB_ENTROPY)) { +#endif + return_code = TRUE; /* Let all others be read */ + } else { + pr_debug + ("RNG: Offset %04x denied read access\n", + offset); + } +#endif /* RNGA or RNGB */ + } /* read */ + else { /* access_write means write */ + /* Check against list of non-writable registers */ + if (! +#ifdef FSL_HAVE_RNGA + ((offset == RNGA_STATUS) || + (offset == RNGA_OUTPUT_FIFO) || + (offset == RNGA_OSCILLATOR1_COUNTER) || + (offset == RNGA_OSCILLATOR2_COUNTER) || + (offset == RNGA_OSCILLATOR_COUNTER_STATUS)) +#else /* FSL_HAVE_RNGB or FSL_HAVE_RNGC */ + ((offset == RNGC_STATUS) || + (offset == RNGC_FIFO) || + (offset == RNGC_OSC_COUNTER) || + (offset == RNGC_OSC_COUNTER_STATUS)) +#endif + ) { + return_code = TRUE; /* can be written */ + } else { + LOG_KDIAG_ARGS + ("Offset %04x denied write access", offset); + } + } /* write */ + } /* not high assurance and inaccessible register... */ + else { + LOG_KDIAG_ARGS("Offset %04x denied high-assurance access", + offset); + } + + return return_code; +} /* rng_check_register_accessible */ +#endif /* RNG_REGISTER_PEEK_POKE */ + +/*****************************************************************************/ +/* fn rng_irq() */ +/*****************************************************************************/ +/*! + * This is the interrupt handler for the RNG. It is only ever invoked if the + * RNG detects a FIFO Underflow error. + * + * If the error is a Security Violation, this routine will + * set the #rng_availability to #RNG_STATUS_FAILED, as the entropy pool may + * have been corrupted. The RNG will also be placed into low power mode. The + * SCC will have noticed the problem as well. + * + * The other possibility, if the RNG is not in High Assurance mode, would be + * simply a FIFO Underflow. No special action, other than to + * clear the interrupt, is taken. + */ +OS_DEV_ISR(rng_irq) +{ + int handled = FALSE; /* assume interrupt isn't from RNG */ + + LOG_KDIAG("rng irq!"); + + if (RNG_SEED_DONE()) { + complete(&rng_seed_done); + RNG_CLEAR_ALL_STATUS(); + handled = TRUE; + } + + if (RNG_SELF_TEST_DONE()) { + complete(&rng_self_testing); + RNG_CLEAR_ALL_STATUS(); + handled = TRUE; + } + /* Look to see whether RNG needs attention */ + if (RNG_HAS_ERROR()) { + if (RNG_GET_HIGH_ASSURANCE()) { + RNG_SLEEP(); + rng_availability = RNG_STATUS_FAILED; + RNG_MASK_ALL_INTERRUPTS(); + } + handled = TRUE; + /* Clear the interrupt */ + RNG_CLEAR_ALL_STATUS(); + + } + os_dev_isr_return(handled); +} /* rng_irq */ + +/*****************************************************************************/ +/* fn map_RNG_memory() */ +/*****************************************************************************/ +/*! + * Place the RNG's memory into kernel virtual space. + * + * @return OS_ERROR_OK_S on success, os_error_code on failure + */ +static os_error_code rng_map_RNG_memory(void) +{ + os_error_code error_code = OS_ERROR_FAIL_S; + + rng_base = os_map_device(RNG_BASE_ADDR, RNG_ADDRESS_RANGE); + if (rng_base == NULL) { + /* failure ! */ + LOG_KDIAG("RNG Driver: ioremap failed."); + } else { + error_code = OS_ERROR_OK_S; + } + + return error_code; +} /* rng_map_RNG_memory */ + +/*****************************************************************************/ +/* fn rng_setup_interrupt_handling() */ +/*****************************************************************************/ +/*! + * Register #rng_irq() as the interrupt handler for #INT_RNG. + * + * @return OS_ERROR_OK_S on success, os_error_code on failure + */ +static os_error_code rng_setup_interrupt_handling(void) +{ + os_error_code error_code; + + /* + * Install interrupt service routine for the RNG. Ignore the + * assigned IRQ number. + */ + error_code = os_register_interrupt(RNG_DRIVER_NAME, INT_RNG, + OS_DEV_ISR_REF(rng_irq)); + if (error_code != OS_ERROR_OK_S) { + LOG_KDIAG("RNG Driver: Error installing Interrupt Handler"); + } else { + rng_irq_set = TRUE; + RNG_UNMASK_ALL_INTERRUPTS(); + } + + return error_code; +} /* rng_setup_interrupt_handling */ + +/*****************************************************************************/ +/* fn rng_grab_config_values() */ +/*****************************************************************************/ +/*! + * Read configuration information from the RNG. + * + * Sets #rng_output_fifo_size. + * + * @return A error code indicating whether the part is the expected one. + */ +static os_error_code rng_grab_config_values(void) +{ + enum rng_type type; + os_error_code ret = OS_ERROR_FAIL_S; + + /* Go for type, versions... */ + type = RNG_GET_RNG_TYPE(); + + /* Make sure type is the one this code has been compiled for. */ + if (RNG_VERIFY_TYPE(type)) { + rng_output_fifo_size = RNG_GET_FIFO_SIZE(); + if (rng_output_fifo_size != 0) { + ret = OS_ERROR_OK_S; + } + } + if (ret != OS_ERROR_OK_S) { + LOG_KDIAG_ARGS + ("Unknown or unexpected RNG type %d (FIFO size %d)." + " Failing driver initialization", type, + rng_output_fifo_size); + } + + return ret; +} + + /* rng_grab_config_values */ + +/*****************************************************************************/ +/* fn rng_drain_fifo() */ +/*****************************************************************************/ +/*! + * This function copies words from the RNG FIFO into the caller's buffer. + * + * + * @param random_p Location to copy random data + * @param count_words Number of words to copy + * + * @return An error code. + */ +static fsl_shw_return_t rng_drain_fifo(uint32_t * random_p, int count_words) +{ + + int words_in_rng; /* Number of words available now in RNG */ + fsl_shw_return_t code = FSL_RETURN_ERROR_S; + int sequential_count = 0; /* times through big while w/empty FIFO */ + int fifo_empty_count = 0; /* number of times FIFO was empty */ + int max_sequential = 0; /* max times 0 seen in a row */ +#if !defined(FSL_HAVE_RNGA) + int count_for_reseed = 0; + INIT_COMPLETION(rng_seed_done); +#endif +#if !defined(FSL_HAVE_RNGA) + if (RNG_RESEED()) { + do { + LOG_KDIAG("Reseeding RNG"); + + RNG_CLEAR_ERR(); + RNG_SEED_GEN(); + wait_for_completion(&rng_seed_done); + if (count_for_reseed == 3) { + os_printk(KERN_ALERT + "Device was not able to enter RESEED Mode\n"); + code = FSL_RETURN_INTERNAL_ERROR_S; + } + count_for_reseed++; + } while (RNG_CHECK_SEED_ERR()); + } +#endif + /* Copy all of them in. Stop if pool fills. */ + while ((rng_availability == RNG_STATUS_OK) && (count_words > 0)) { + /* Ask RNG how many words currently in FIFO */ + words_in_rng = RNG_GET_WORDS_IN_FIFO(); + if (words_in_rng == 0) { + ++sequential_count; + fifo_empty_count++; + if (sequential_count > max_sequential) { + max_sequential = sequential_count; + } + if (sequential_count >= RNG_MAX_TRIES) { + LOG_KDIAG_ARGS("FIFO staying empty (%d)", + words_in_rng); + code = FSL_RETURN_NO_RESOURCE_S; + break; + } + } else { + /* Found at least one word */ + sequential_count = 0; + /* Now adjust: words_in_rng = MAX(count_words, words_in_rng) */ + words_in_rng = (count_words < words_in_rng) + ? count_words : words_in_rng; + } /* else found words */ + +#ifdef RNG_FORCE_FIFO_UNDERFLOW + /* + * For unit test, force occasional extraction of more words than + * available. This should cause FIFO Underflow, and IRQ invocation. + */ + words_in_rng = count_words; +#endif + + /* Copy out all available & neeeded data */ + while (words_in_rng-- > 0) { + *random_p++ = RNG_READ_FIFO(); + count_words--; + } + } /* while words still needed */ + + if (count_words == 0) { + code = FSL_RETURN_OK_S; + } + if (fifo_empty_count != 0) { + LOG_KDIAG_ARGS("FIFO empty %d times, max loop count %d", + fifo_empty_count, max_sequential); + } + + return code; +} /* rng_drain_fifo */ + +/*****************************************************************************/ +/* fn rng_entropy_task() */ +/*****************************************************************************/ +/*! + * This is the background task of the driver. It is scheduled by + * RNG_ADD_WORK_ENTRY(). + * + * This will process each entry on the #rng_work_queue. Blocking requests will + * cause sleepers to be awoken. Non-blocking requests will be placed on the + * results queue, and if appropriate, the callback function will be invoked. + */ +OS_DEV_TASK(rng_entropy_task) +{ + rng_work_entry_t *work; + + os_dev_task_begin(); + +#ifdef RNG_ENTROPY_DEBUG + LOG_KDIAG("entropy task starting"); +#endif + + while ((work = RNG_GET_WORK_ENTRY()) != NULL) { +#ifdef RNG_ENTROPY_DEBUG + LOG_KDIAG_ARGS("found %d bytes of work at %p (%p)", + work->length, work, work->data_local); +#endif + work->hdr.code = rng_drain_fifo(work->data_local, + BYTES_TO_WORDS(work->length)); + work->completed = TRUE; + + if (work->hdr.flags & FSL_UCO_BLOCKING_MODE) { +#ifdef RNG_ENTROPY_DEBUG + LOG_KDIAG("Waking queued processes"); +#endif + os_wake_sleepers(rng_wait_queue); + } else { + os_lock_context_t lock_context; + + os_lock_save_context(rng_queue_lock, lock_context); + RNG_ADD_QUEUE_ENTRY(&work->hdr.user_ctx->result_pool, + work); + os_unlock_restore_context(rng_queue_lock, lock_context); + + if (work->hdr.flags & FSL_UCO_CALLBACK_MODE) { + if (work->hdr.callback != NULL) { + work->hdr.callback(work->hdr.user_ctx); + } else { +#ifdef RNG_ENTROPY_DEBUG + LOG_KDIAG_ARGS + ("Callback ptr for %p is NULL", + work); +#endif + } + } + } + } /* while */ + +#ifdef RNG_ENTROPY_DEBUG + LOG_KDIAG("entropy task ending"); +#endif + + os_dev_task_return(OS_ERROR_OK_S); +} /* rng_entropy_task */ + +#ifdef FSL_HAVE_RNGA +/*****************************************************************************/ +/* fn rng_sec_failure() */ +/*****************************************************************************/ +/*! + * Function to handle "Security Alarm" indication from SCC. + * + * This function is registered with the Security Monitor ans the callback + * function for the RNG driver. Upon alarm, it will shut down the driver so + * that no more random data can be retrieved. + * + * @return void + */ +static void rng_sec_failure(void) +{ + os_printk(KERN_ALERT "RNG Driver: Security Failure Alarm received.\n"); + + rng_cleanup(); + + return; +} +#endif + +#ifdef RNG_REGISTER_DEBUG +/*****************************************************************************/ +/* fn dbg_rng_read_register() */ +/*****************************************************************************/ +/*! + * Noisily read a 32-bit value to an RNG register. + * @param offset The address of the register to read. + * + * @return The register value + * */ +static uint32_t dbg_rng_read_register(uint32_t offset) +{ + uint32_t value; + + value = os_read32(rng_base + offset); +#ifndef RNG_ENTROPY_DEBUG + if (offset != RNG_OUTPUT_FIFO) { +#endif + pr_debug("RNG RD: 0x%4x : 0x%08x\n", offset, value); +#ifndef RNG_ENTROPY_DEBUG + } +#endif + return value; +} + +/*****************************************************************************/ +/* fn dbg_rng_write_register() */ +/*****************************************************************************/ +/*! + * Noisily write a 32-bit value to an RNG register. + * @param offset The address of the register to written. + * + * @param value The new register value + */ +static void dbg_rng_write_register(uint32_t offset, uint32_t value) +{ + LOG_KDIAG_ARGS("WR: 0x%4x : 0x%08x", offset, value); + os_write32(value, rng_base + offset); + return; +} + +#endif /* RNG_REGISTER_DEBUG */ diff --git a/drivers/mxc/security/rng/shw_driver.c b/drivers/mxc/security/rng/shw_driver.c new file mode 100644 index 000000000000..24c912245099 --- /dev/null +++ b/drivers/mxc/security/rng/shw_driver.c @@ -0,0 +1,2335 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! @file shw_driver.c + * + * This is the user-mode driver code for the FSL Security Hardware (SHW) API. + * as well as the 'common' FSL SHW API code for kernel API users. + * + * Its interaction with the Linux kernel is from calls to shw_init() when the + * driver is loaded, and shw_shutdown() should the driver be unloaded. + * + * The User API (driver interface) is handled by the following functions: + * @li shw_open() - handles open() system call on FSL SHW device + * @li shw_release() - handles close() system call on FSL SHW device + * @li shw_ioctl() - handles ioctl() system call on FSL SHW device + * + * The driver also provides the following functions for kernel users of the FSL + * SHW API: + * @li fsl_shw_register_user() + * @li fsl_shw_deregister_user() + * @li fsl_shw_get_capabilities() + * @li fsl_shw_get_results() + * + * All other functions are internal to the driver. + * + * The life of the driver starts at boot (or module load) time, with a call by + * the kernel to shw_init(). + * + * The life of the driver ends when the kernel is shutting down (or the driver + * is being unloaded). At this time, shw_shutdown() is called. No function + * will ever be called after that point. + * + * In the case that the driver is reloaded, a new copy of the driver, with + * fresh global values, etc., is loaded, and there will be a new call to + * shw_init(). + * + * In user mode, the user's fsl_shw_register_user() call causes an open() event + * on the driver followed by a ioctl() with the registration information. Any + * subsequent API calls by the user are handled through the ioctl() function + * and shuffled off to the appropriate routine (or driver) for service. The + * fsl_shw_deregister_user() call by the user results in a close() function + * call on the driver. + * + * In kernel mode, the driver provides the functions fsl_shw_register_user(), + * fsl_shw_deregister_user(), fsl_shw_get_capabilities(), and + * fsl_shw_get_results(). Other parts of the API are provided by other + * drivers, if available, to support the cryptographic functions. + */ + +#include "portable_os.h" +#include "fsl_shw.h" +#include "fsl_shw_keystore.h" + +#include "shw_internals.h" + +#ifdef FSL_HAVE_SCC2 +#include <linux/mxc_scc2_driver.h> +#else +#include <linux/mxc_scc_driver.h> +#endif + +#ifdef SHW_DEBUG +#include <diagnostic.h> +#endif + +/****************************************************************************** + * + * Function Declarations + * + *****************************************************************************/ + +/* kernel interface functions */ +OS_DEV_INIT_DCL(shw_init); +OS_DEV_SHUTDOWN_DCL(shw_shutdown); +OS_DEV_IOCTL_DCL(shw_ioctl); +OS_DEV_MMAP_DCL(shw_mmap); + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_smalloc); +EXPORT_SYMBOL(fsl_shw_sfree); +EXPORT_SYMBOL(fsl_shw_sstatus); +EXPORT_SYMBOL(fsl_shw_diminish_perms); +EXPORT_SYMBOL(do_scc_encrypt_region); +EXPORT_SYMBOL(do_scc_decrypt_region); + +EXPORT_SYMBOL(do_system_keystore_slot_alloc); +EXPORT_SYMBOL(do_system_keystore_slot_dealloc); +EXPORT_SYMBOL(do_system_keystore_slot_load); +EXPORT_SYMBOL(do_system_keystore_slot_encrypt); +EXPORT_SYMBOL(do_system_keystore_slot_decrypt); +#endif + +static os_error_code +shw_handle_scc_sfree(fsl_shw_uco_t * user_ctx, uint32_t info); + +static os_error_code +shw_handle_scc_sstatus(fsl_shw_uco_t * user_ctx, uint32_t info); + +static os_error_code +shw_handle_scc_drop_perms(fsl_shw_uco_t * user_ctx, uint32_t info); + +static os_error_code +shw_handle_scc_encrypt(fsl_shw_uco_t * user_ctx, uint32_t info); + +static os_error_code +shw_handle_scc_decrypt(fsl_shw_uco_t * user_ctx, uint32_t info); + +#ifdef FSL_HAVE_SCC2 +static fsl_shw_return_t register_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base, + void *kernel_base); +static fsl_shw_return_t deregister_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base); +void *lookup_user_partition(fsl_shw_uco_t * user_ctx, uint32_t user_base); + +#endif /* FSL_HAVE_SCC2 */ + +/****************************************************************************** + * + * Global / Static Variables + * + *****************************************************************************/ + +/*! + * Major node (user/device interaction value) of this driver. + */ +static int shw_major_node = SHW_MAJOR_NODE; + +/*! + * Flag to know whether the driver has been associated with its user device + * node (e.g. /dev/shw). + */ +static int shw_device_registered = 0; + +/*! + * OS-dependent handle used for registering user interface of a driver. + */ +static os_driver_reg_t reg_handle; + +/*! + * Linked List of registered users of the API + */ +fsl_shw_uco_t *user_list; + +/*! + * This is the lock for all user request pools. H/W component drivers may also + * use it for their own work queues. + */ +os_lock_t shw_queue_lock = NULL; + +/* This is the system keystore object */ +fsl_shw_kso_t system_keystore; + +#ifndef FSL_HAVE_SAHARA +/*! Empty list of supported symmetric algorithms. */ +static fsl_shw_key_alg_t pf_syms[] = { +}; + +/*! Empty list of supported symmetric modes. */ +static fsl_shw_sym_mode_t pf_modes[] = { +}; + +/*! Empty list of supported hash algorithms. */ +static fsl_shw_hash_alg_t pf_hashes[] = { +}; +#endif /* no Sahara */ + +/*! This matches SHW capabilities... */ +static fsl_shw_pco_t cap = { + 1, 3, /* api version number - major & minor */ + 2, 3, /* driver version number - major & minor */ + sizeof(pf_syms) / sizeof(fsl_shw_key_alg_t), /* key alg count */ + pf_syms, /* key alg list ptr */ + sizeof(pf_modes) / sizeof(fsl_shw_sym_mode_t), /* sym mode count */ + pf_modes, /* modes list ptr */ + sizeof(pf_hashes) / sizeof(fsl_shw_hash_alg_t), /* hash alg count */ + pf_hashes, /* hash list ptr */ + /* + * The following table must be set to handle all values of key algorithm + * and sym mode, and be in the correct order.. + */ + { /* Stream, ECB, CBC, CTR */ + {0, 0, 0, 0} + , /* HMAC */ + {0, 0, 0, 0} + , /* AES */ + {0, 0, 0, 0} + , /* DES */ +#ifdef FSL_HAVE_DRYICE + {0, 1, 1, 0} + , /* 3DES - ECB and CBC */ +#else + {0, 0, 0, 0} + , /* 3DES */ +#endif + {0, 0, 0, 0} /* ARC4 */ + } + , + 0, 0, /* SCC driver version */ + 0, 0, 0, /* SCC version/capabilities */ + {{0, 0} + } + , /* (filled in during OS_INIT) */ +}; + +/* These are often handy */ +#ifndef FALSE +/*! Not true. Guaranteed to be zero. */ +#define FALSE 0 +#endif +#ifndef TRUE +/*! True. Guaranteed to be non-zero. */ +#define TRUE 1 +#endif + +/****************************************************************************** + * + * Function Implementations - Externally Accessible + * + *****************************************************************************/ + +/*****************************************************************************/ +/* fn shw_init() */ +/*****************************************************************************/ +/*! + * Initialize the driver. + * + * This routine is called during kernel init or module load (insmod). + * + * @return OS_ERROR_OK_S on success, errno on failure + */ +OS_DEV_INIT(shw_init) +{ + os_error_code error_code = OS_ERROR_NO_MEMORY_S; /* assume failure */ + scc_config_t *shw_capabilities; + +#ifdef SHW_DEBUG + LOG_KDIAG("SHW Driver: Loading"); +#endif + + user_list = NULL; + shw_queue_lock = os_lock_alloc_init(); + + if (shw_queue_lock != NULL) { + error_code = shw_setup_user_driver_interaction(); + if (error_code != OS_ERROR_OK_S) { +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS + ("SHW Driver: Failed to setup user i/f: %d", + error_code); +#endif + } + } + + /* queue_lock not NULL */ + /* Fill in the SCC portion of the capabilities object */ + shw_capabilities = scc_get_configuration(); + cap.scc_driver_major = shw_capabilities->driver_major_version; + cap.scc_driver_minor = shw_capabilities->driver_minor_version; + cap.scm_version = shw_capabilities->scm_version; + cap.smn_version = shw_capabilities->smn_version; + cap.block_size_bytes = shw_capabilities->block_size_bytes; + +#ifdef FSL_HAVE_SCC + cap.u.scc_info.black_ram_size_blocks = + shw_capabilities->black_ram_size_blocks; + cap.u.scc_info.red_ram_size_blocks = + shw_capabilities->red_ram_size_blocks; +#elif defined(FSL_HAVE_SCC2) + cap.u.scc2_info.partition_size_bytes = + shw_capabilities->partition_size_bytes; + cap.u.scc2_info.partition_count = shw_capabilities->partition_count; +#endif + +#if defined(FSL_HAVE_SCC2) || defined(FSL_HAVE_DRYICE) + if (error_code == OS_ERROR_OK_S) { + /* set up the system keystore, using the default keystore handler */ + fsl_shw_init_keystore_default(&system_keystore); + + if (fsl_shw_establish_keystore(NULL, &system_keystore) + == FSL_RETURN_OK_S) { + error_code = OS_ERROR_OK_S; + } else { + error_code = OS_ERROR_FAIL_S; + } + + if (error_code != OS_ERROR_OK_S) { +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS + ("Registering the system keystore failed with error" + " code: %d\n", error_code); +#endif + } + } +#endif /* FSL_HAVE_SCC2 */ + + if (error_code != OS_ERROR_OK_S) { +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW: Driver initialization failed. %d", + error_code); +#endif + shw_cleanup(); + } else { +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: Driver initialization complete."); +#endif + } + + os_dev_init_return(error_code); +} /* shw_init */ + +/*****************************************************************************/ +/* fn shw_shutdown() */ +/*****************************************************************************/ +/*! + * Prepare driver for exit. + * + * This is called during @c rmmod when the driver is unloading or when the + * kernel is shutting down. + * + * Calls shw_cleanup() to do all work to undo anything that happened during + * initialization or while driver was running. + */ +OS_DEV_SHUTDOWN(shw_shutdown) +{ + +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: shutdown called"); +#endif + shw_cleanup(); + + os_dev_shutdown_return(OS_ERROR_OK_S); +} /* shw_shutdown */ + +/*****************************************************************************/ +/* fn shw_cleanup() */ +/*****************************************************************************/ +/*! + * Prepare driver for shutdown. + * + * Remove the driver registration. + * + */ +static void shw_cleanup(void) +{ + if (shw_device_registered) { + + /* Turn off the all association with OS */ + os_driver_remove_registration(reg_handle); + shw_device_registered = 0; + } + + if (shw_queue_lock != NULL) { + os_lock_deallocate(shw_queue_lock); + } +#ifdef SHW_DEBUG + LOG_KDIAG("SHW Driver: Cleaned up"); +#endif +} /* shw_cleanup */ + +/*****************************************************************************/ +/* fn shw_open() */ +/*****************************************************************************/ +/*! + * Handle @c open() call from user. + * + * @return OS_ERROR_OK_S on success (always!) + */ +OS_DEV_OPEN(shw_open) +{ + os_error_code status = OS_ERROR_OK_S; + + os_dev_set_user_private(NULL); /* Make sure */ + + os_dev_open_return(status); +} /* shw_open */ + +/*****************************************************************************/ +/* fn shw_ioctl() */ +/*****************************************************************************/ +/*! + * Process an ioctl() request from user-mode API. + * + * This code determines which of the API requests the user has made and then + * sends the request off to the appropriate function. + * + * @return ioctl_return() + */ +OS_DEV_IOCTL(shw_ioctl) +{ + os_error_code code = OS_ERROR_FAIL_S; + + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW: IOCTL %d received", os_dev_get_ioctl_op()); +#endif + switch (os_dev_get_ioctl_op()) { + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_REGISTER_USER: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: register_user ioctl received"); +#endif + { + fsl_shw_uco_t *user_ctx = + os_alloc_memory(sizeof(*user_ctx), 0); + + if (user_ctx == NULL) { + code = OS_ERROR_NO_MEMORY_S; + } else { + code = + init_uco(user_ctx, + (fsl_shw_uco_t *) + os_dev_get_ioctl_arg()); + if (code == OS_ERROR_OK_S) { + os_dev_set_user_private(user_ctx); + } else { + os_free_memory(user_ctx); + } + } + } + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_DEREGISTER_USER: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: deregister_user ioctl received"); +#endif + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + SHW_REMOVE_USER(user_ctx); + } + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_GET_RESULTS: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: get_results ioctl received"); +#endif + code = get_results(user_ctx, + (struct results_req *) + os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_GET_CAPABILITIES: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: get_capabilities ioctl received"); +#endif + code = get_capabilities(user_ctx, + (fsl_shw_pco_t *) + os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_GET_RANDOM: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: get_random ioctl received"); +#endif + code = get_random(user_ctx, + (struct get_random_req *) + os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_ADD_ENTROPY: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: add_entropy ioctl received"); +#endif + code = add_entropy(user_ctx, + (struct add_entropy_req *) + os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_DROP_PERMS: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: drop permissions ioctl received"); +#endif + code = + shw_handle_scc_drop_perms(user_ctx, os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_SSTATUS: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: sstatus ioctl received"); +#endif + code = shw_handle_scc_sstatus(user_ctx, os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_SFREE: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: sfree ioctl received"); +#endif + code = shw_handle_scc_sfree(user_ctx, os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_SCC_ENCRYPT: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: scc encrypt ioctl received"); +#endif + code = shw_handle_scc_encrypt(user_ctx, os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_SCC_DECRYPT: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: scc decrypt ioctl received"); +#endif + code = shw_handle_scc_decrypt(user_ctx, os_dev_get_ioctl_arg()); + break; + + default: +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW: Unexpected ioctl %d", + os_dev_get_ioctl_op()); +#endif + break; + } + + os_dev_ioctl_return(code); +} + +#ifdef FSL_HAVE_SCC2 + +/*****************************************************************************/ +/* fn get_user_smid() */ +/*****************************************************************************/ +uint32_t get_user_smid(void *proc) +{ + /* + * A real implementation would have some way to handle signed applications + * which wouild be assigned distinct SMIDs. For the reference + * implementation, we show where this would be determined (here), but + * always provide a fixed answer, thus not separating users at all. + */ + + return 0x42eaae42; +} + +/* user_base: userspace base address of the partition + * kernel_base: kernel mode base address of the partition + */ +static fsl_shw_return_t register_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base, + void *kernel_base) +{ + fsl_shw_spo_t *partition_info; + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + if (user_ctx == NULL) { + goto out; + } + + partition_info = os_alloc_memory(sizeof(fsl_shw_spo_t), GFP_KERNEL); + + if (partition_info == NULL) { + goto out; + } + + /* stuff the partition info, then put it at the front of the chain */ + partition_info->user_base = user_base; + partition_info->kernel_base = kernel_base; + partition_info->next = user_ctx->partition; + + user_ctx->partition = (struct fsl_shw_spo_t *)partition_info; + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS + ("partition with user_base=%p, kernel_base=%p registered.", + (void *)user_base, kernel_base); +#endif + + ret = FSL_RETURN_OK_S; + + out: + + return ret; +} + +/* if the partition is in the users list, remove it */ +static fsl_shw_return_t deregister_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base) +{ + fsl_shw_spo_t *curr = (fsl_shw_spo_t *) user_ctx->partition; + fsl_shw_spo_t *last = (fsl_shw_spo_t *) user_ctx->partition; + + while (curr != NULL) { + if (curr->user_base == user_base) { + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS + ("deregister_user_partition: partition with " + "user_base=%p, kernel_base=%p deregistered.\n", + (void *)curr->user_base, curr->kernel_base); +#endif + + if (last == curr) { + user_ctx->partition = curr->next; + os_free_memory(curr); + return FSL_RETURN_OK_S; + } else { + last->next = curr->next; + os_free_memory(curr); + return FSL_RETURN_OK_S; + } + } + last = curr; + curr = (fsl_shw_spo_t *) curr->next; + } + + return FSL_RETURN_ERROR_S; +} + +/* Find the kernel-mode address of the partition. + * This can then be passed to the SCC functions. + */ +void *lookup_user_partition(fsl_shw_uco_t * user_ctx, uint32_t user_base) +{ + /* search through the partition chain to find one that matches the user base + * address. + */ + fsl_shw_spo_t *curr = (fsl_shw_spo_t *) user_ctx->partition; + + while (curr != NULL) { + if (curr->user_base == user_base) { + return curr->kernel_base; + } + curr = (fsl_shw_spo_t *) curr->next; + } + return NULL; +} + +#endif /* FSL_HAVE_SCC2 */ + +/*! +******************************************************************************* +* This function implements the smalloc() function for userspace programs, by +* making a call to the SCC2 mmap() function that acquires a region of secure +* memory on behalf of the user, and then maps it into the users memory space. +* Currently, the only memory size supported is that of a single SCC2 partition. +* Requests for other sized memory regions will fail. +*/ +OS_DEV_MMAP(shw_mmap) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; + +#ifdef FSL_HAVE_SCC2 + { + scc_return_t scc_ret; + fsl_shw_return_t fsl_ret; + uint32_t partition_registered = FALSE; + + uint32_t user_base; + void *partition_base; + uint32_t smid; + scc_config_t *scc_configuration; + + int part_no = -1; + uint32_t part_phys; + + fsl_shw_uco_t *user_ctx = + (fsl_shw_uco_t *) os_dev_get_user_private(); + + /* Make sure that the user context is valid */ + if (user_ctx == NULL) { + user_ctx = + os_alloc_memory(sizeof(*user_ctx), GFP_KERNEL); + + if (user_ctx == NULL) { + status = OS_ERROR_NO_MEMORY_S; + goto out; + } + fsl_shw_register_user(user_ctx); + os_dev_set_user_private(user_ctx); + } + + /* Determine the size of a secure partition */ + scc_configuration = scc_get_configuration(); + + /* Check that the memory size requested is equal to the partition + * size, and that the requested destination is on a page boundary. + */ + if (((os_mmap_user_base() % PAGE_SIZE) != 0) || + (os_mmap_memory_size() != + scc_configuration->partition_size_bytes)) { + status = OS_ERROR_BAD_ARG_S; + goto out; + } + + /* Retrieve the SMID associated with the user */ + smid = get_user_smid(user_ctx->process); + + /* Attempt to allocate a secure partition */ + scc_ret = + scc_allocate_partition(smid, &part_no, &partition_base, + &part_phys); + if (scc_ret != SCC_RET_OK) { + pr_debug + ("SCC mmap() request failed to allocate partition;" + " error %d\n", status); + status = OS_ERROR_FAIL_S; + goto out; + } + + pr_debug("scc_mmap() acquired partition %d at %08x\n", + part_no, part_phys); + + /* Record partition info in the user context */ + user_base = os_mmap_user_base(); + fsl_ret = + register_user_partition(user_ctx, user_base, + partition_base); + + if (fsl_ret != FSL_RETURN_OK_S) { + pr_debug + ("SCC mmap() request failed to register partition with user" + " context, error: %d\n", fsl_ret); + status = OS_ERROR_FAIL_S; + } + + partition_registered = TRUE; + + status = map_user_memory(os_mmap_memory_ctx(), part_phys, + os_mmap_memory_size()); + +#ifdef SHW_DEBUG + if (status == OS_ERROR_OK_S) { + LOG_KDIAG_ARGS + ("Partition allocated: user_base=%p, partition_base=%p.", + (void *)user_base, partition_base); + } +#endif + + out: + /* If there is an error it has to be handled here */ + if (status != OS_ERROR_OK_S) { + /* if the partition was registered with the user, unregister it. */ + if (partition_registered == TRUE) { + deregister_user_partition(user_ctx, user_base); + } + + /* if the partition was allocated, deallocate it */ + if (partition_base != NULL) { + scc_release_partition(partition_base); + } + } + } +#endif /* FSL_HAVE_SCC2 */ + + return status; +} + +/*****************************************************************************/ +/* fn shw_release() */ +/*****************************************************************************/ +/*! + * Handle @c close() call from user. + * This is a Linux device driver interface routine. + * + * @return OS_ERROR_OK_S on success (always!) + */ +OS_DEV_CLOSE(shw_release) +{ + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + os_error_code code = OS_ERROR_OK_S; + + if (user_ctx != NULL) { + + fsl_shw_deregister_user(user_ctx); + os_free_memory(user_ctx); + os_dev_set_user_private(NULL); + + } + + os_dev_close_return(code); +} /* shw_release */ + +/*****************************************************************************/ +/* fn shw_user_callback() */ +/*****************************************************************************/ +/*! + * FSL SHW User callback function. + * + * This function is set in the kernel version of the user context as the + * callback function when the user mode user wants a callback. Its job is to + * inform the user process that results (may) be available. It does this by + * sending a SIGUSR2 signal which is then caught by the user-mode FSL SHW + * library. + * + * @param user_ctx Kernel version of uco associated with the request. + * + * @return void + */ +static void shw_user_callback(fsl_shw_uco_t * user_ctx) +{ +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW: Signalling callback user process for context %p\n", + user_ctx); +#endif + os_send_signal(user_ctx->process, SIGUSR2); +} + +/*****************************************************************************/ +/* fn setup_user_driver_interaction() */ +/*****************************************************************************/ +/*! + * Register the driver with the kernel as the driver for shw_major_node. Note + * that this value may be zero, in which case the major number will be assigned + * by the OS. shw_major_node is never modified. + * + * The open(), ioctl(), and close() handles for the driver ned to be registered + * with the kernel. Upon success, shw_device_registered will be true; + * + * @return OS_ERROR_OK_S on success, or an os err code + */ +static os_error_code shw_setup_user_driver_interaction(void) +{ + os_error_code error_code; + + os_driver_init_registration(reg_handle); + os_driver_add_registration(reg_handle, OS_FN_OPEN, + OS_DEV_OPEN_REF(shw_open)); + os_driver_add_registration(reg_handle, OS_FN_IOCTL, + OS_DEV_IOCTL_REF(shw_ioctl)); + os_driver_add_registration(reg_handle, OS_FN_CLOSE, + OS_DEV_CLOSE_REF(shw_release)); + os_driver_add_registration(reg_handle, OS_FN_MMAP, + OS_DEV_MMAP_REF(shw_mmap)); + error_code = os_driver_complete_registration(reg_handle, shw_major_node, + SHW_DRIVER_NAME); + + if (error_code != OS_ERROR_OK_S) { + /* failure ! */ +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW Driver: register device driver failed: %d", + error_code); +#endif + } else { /* success */ + shw_device_registered = TRUE; +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW Driver: Major node is %d\n", + os_driver_get_major(reg_handle)); +#endif + } + + return error_code; +} /* shw_setup_user_driver_interaction */ + +/******************************************************************/ +/* User Mode Support */ +/******************************************************************/ + +/*! + * Initialze kernel User Context Object from User-space version. + * + * Copy user UCO into kernel UCO, set flags and fields for operation + * within kernel space. Add user to driver's list of users. + * + * @param user_ctx Pointer to kernel space UCO + * @param user_mode_uco User pointer to user space version + * + * @return os_error_code + */ +static os_error_code init_uco(fsl_shw_uco_t * user_ctx, void *user_mode_uco) +{ + os_error_code code; + + code = os_copy_from_user(user_ctx, user_mode_uco, sizeof(*user_ctx)); + if (code == OS_ERROR_OK_S) { + user_ctx->flags |= FSL_UCO_USERMODE_USER; + user_ctx->result_pool.head = NULL; + user_ctx->result_pool.tail = NULL; + user_ctx->process = os_get_process_handle(); + user_ctx->callback = shw_user_callback; + SHW_ADD_USER(user_ctx); + } +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW: init uco returning %d (flags %x)", + code, user_ctx->flags); +#endif + + return code; +} + +/*! + * Copy array from kernel to user space. + * + * This routine will check bounds before trying to copy, and return failure + * on bounds violation or error during the copy. + * + * @param userloc Location in userloc to place data. If NULL, the function + * will do nothing (except return NULL). + * @param userend Address beyond allowed copy region at @c userloc. + * @param data_start Location of data to be copied + * @param element_size sizeof() an element + * @param element_count Number of elements of size element_size to copy. + * @return New value of userloc, or NULL if there was an error. + */ +inline static void *copy_array(void *userloc, void *userend, void *data_start, + unsigned element_size, unsigned element_count) +{ + unsigned byte_count = element_size * element_count; + + if ((userloc == NULL) || (userend == NULL) + || ((userloc + byte_count) >= userend) || + (copy_to_user(userloc, data_start, byte_count) != OS_ERROR_OK_S)) { + userloc = NULL; + } else { + userloc += byte_count; + } + + return userloc; +} + +/*! + * Send an FSL SHW API return code up into the user-space request structure. + * + * @param user_header User address of request block / request header + * @param result_code The FSL SHW API code to be placed at header.code + * + * @return an os_error_code + * + * NOTE: This function assumes that the shw_req_header is at the beginning of + * each request structure. + */ +inline static os_error_code copy_fsl_code(void *user_header, + fsl_shw_return_t result_code) +{ + return os_copy_to_user(user_header + + offsetof(struct shw_req_header, code), + &result_code, sizeof(result_code)); +} + +static os_error_code shw_handle_scc_drop_perms(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; +#ifdef FSL_HAVE_SCC2 + scc_return_t scc_ret; + scc_partition_info_t partition_info; + void *kernel_base; + + status = + os_copy_from_user(&partition_info, (void *)info, + sizeof(partition_info)); + + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + kernel_base = lookup_user_partition(user_ctx, partition_info.user_base); + + if (kernel_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef SHW_DEBUG + LOG_KDIAG("_scc_drop_perms(): failed to find partition\n"); +#endif + goto out; + } + + /* call scc driver to perform the drop */ + scc_ret = scc_diminish_permissions(kernel_base, + partition_info.permissions); + if (scc_ret == SCC_RET_OK) { + status = OS_ERROR_OK_S; + } else { + status = OS_ERROR_FAIL_S; + } + + out: +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +static os_error_code shw_handle_scc_sstatus(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; +#ifdef FSL_HAVE_SCC2 + scc_partition_info_t partition_info; + void *kernel_base; + + status = os_copy_from_user(&partition_info, + (void *)info, sizeof(partition_info)); + + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + kernel_base = lookup_user_partition(user_ctx, partition_info.user_base); + + if (kernel_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef SHW_DEBUG + LOG_KDIAG("Failed to find partition\n"); +#endif + goto out; + } + + /* Call the SCC driver to ask about the partition status */ + partition_info.status = scc_partition_status(kernel_base); + + /* and copy the structure out */ + status = os_copy_to_user((void *)info, + &partition_info, sizeof(partition_info)); + + out: +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +static os_error_code shw_handle_scc_sfree(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; +#ifdef FSL_HAVE_SCC2 + { + scc_partition_info_t partition_info; + void *kernel_base; + int ret; + + status = os_copy_from_user(&partition_info, + (void *)info, + sizeof(partition_info)); + + /* check that the copy was successful */ + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + kernel_base = + lookup_user_partition(user_ctx, partition_info.user_base); + + if (kernel_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef SHW_DEBUG + LOG_KDIAG("failed to find partition\n"); +#endif /*SHW_DEBUG */ + goto out; + } + + /* Unmap the memory region (see sys_munmap in mmap.c) */ + ret = unmap_user_memory(partition_info.user_base, 8192); + + /* If the memory was successfully released */ + if (ret == OS_ERROR_OK_S) { + + /* release the partition */ + scc_release_partition(kernel_base); + + /* and remove it from the users context */ + deregister_user_partition(user_ctx, + partition_info.user_base); + + status = OS_ERROR_OK_S; + + } else { +#ifdef SHW_DEBUG + LOG_KDIAG("do_munmap not successful!"); +#endif + } + + } + out: +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +static os_error_code shw_handle_scc_encrypt(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_FAIL_S; +#ifdef FSL_HAVE_SCC2 + { + fsl_shw_return_t retval; + scc_region_t region_info; + void *page_ctx = NULL; + void *black_addr = NULL; + void *partition_base = NULL; + scc_config_t *scc_configuration; + + status = + os_copy_from_user(®ion_info, (void *)info, + sizeof(region_info)); + + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + partition_base = lookup_user_partition(user_ctx, + region_info. + partition_base); + + if (partition_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef SHW_DEBUG + LOG_KDIAG("failed to find secure partition\n"); +#endif + goto out; + } + + /* Check that the memory size requested is correct */ + scc_configuration = scc_get_configuration(); + if (region_info.offset + region_info.length > + scc_configuration->partition_size_bytes) { + status = OS_ERROR_FAIL_S; + goto out; + } + + /* wire down black_data */ + black_addr = wire_user_memory(region_info.black_data, + region_info.length, &page_ctx); + + if (black_addr == NULL) { + status = OS_ERROR_FAIL_S; + goto out; + } + + retval = + do_scc_encrypt_region(NULL, partition_base, + region_info.offset, + region_info.length, black_addr, + region_info.IV, + region_info.cypher_mode); + + if (retval == FSL_RETURN_OK_S) { + status = OS_ERROR_OK_S; + } else { + status = OS_ERROR_FAIL_S; + } + + /* release black data */ + unwire_user_memory(&page_ctx); + } + out: + +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +static os_error_code shw_handle_scc_decrypt(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_FAIL_S; +#ifdef FSL_HAVE_SCC2 + { + fsl_shw_return_t retval; + scc_region_t region_info; + void *page_ctx = NULL; + void *black_addr; + void *partition_base; + scc_config_t *scc_configuration; + + status = + os_copy_from_user(®ion_info, (void *)info, + sizeof(region_info)); + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS + ("partition_base: %p, offset: %i, length: %i, black data: %p", + (void *)region_info.partition_base, region_info.offset, + region_info.length, (void *)region_info.black_data); +#endif + + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + partition_base = lookup_user_partition(user_ctx, + region_info. + partition_base); + + if (partition_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef SHW_DEBUG + LOG_KDIAG("failed to find partition\n"); +#endif + goto out; + } + + /* Check that the memory size requested is correct */ + scc_configuration = scc_get_configuration(); + if (region_info.offset + region_info.length > + scc_configuration->partition_size_bytes) { + status = OS_ERROR_FAIL_S; + goto out; + } + + /* wire down black_data */ + black_addr = wire_user_memory(region_info.black_data, + region_info.length, &page_ctx); + + if (black_addr == NULL) { + status = OS_ERROR_FAIL_S; + goto out; + } + + retval = + do_scc_decrypt_region(NULL, partition_base, + region_info.offset, + region_info.length, black_addr, + region_info.IV, + region_info.cypher_mode); + + if (retval == FSL_RETURN_OK_S) { + status = OS_ERROR_OK_S; + } else { + status = OS_ERROR_FAIL_S; + } + + /* release black data */ + unwire_user_memory(&page_ctx); + } + out: + +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +fsl_shw_return_t do_system_keystore_slot_alloc(fsl_shw_uco_t * user_ctx, + uint32_t key_length, + uint64_t ownerid, + uint32_t * slot) +{ + (void)user_ctx; + return keystore_slot_alloc(&system_keystore, key_length, ownerid, slot); +} + +fsl_shw_return_t do_system_keystore_slot_dealloc(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot) +{ + (void)user_ctx; + return keystore_slot_dealloc(&system_keystore, ownerid, slot); +} + +fsl_shw_return_t do_system_keystore_slot_load(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + const uint8_t * key, + uint32_t key_length) +{ + (void)user_ctx; + return keystore_slot_load(&system_keystore, ownerid, slot, + (void *)key, key_length); +} + +fsl_shw_return_t do_system_keystore_slot_encrypt(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + uint8_t * black_data) +{ + (void)user_ctx; + return keystore_slot_encrypt(NULL, &system_keystore, ownerid, + slot, key_length, black_data); +} + +fsl_shw_return_t do_system_keystore_slot_decrypt(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + const uint8_t * black_data) +{ + (void)user_ctx; + return keystore_slot_decrypt(NULL, &system_keystore, ownerid, + slot, key_length, black_data); +} + +fsl_shw_return_t do_system_keystore_slot_read(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + uint8_t * key_data) +{ + (void)user_ctx; + + return keystore_slot_read(&system_keystore, ownerid, + slot, key_length, key_data); +} + +/*! + * Handle user-mode Get Capabilities request + * + * Right now, this function can only have a failure if the user has failed to + * provide a pointer to a location in user space with enough room to hold the + * fsl_shw_pco_t structure and any associated data. It will treat this failure + * as an ioctl failure and return an ioctl error code, instead of treating it + * as an API failure. + * + * @param user_ctx The kernel version of user's context + * @param user_mode_pco_request Pointer to user-space request + * + * @return an os_error_code + */ +static os_error_code get_capabilities(fsl_shw_uco_t * user_ctx, + void *user_mode_pco_request) +{ + os_error_code code; + struct capabilities_req req; + fsl_shw_pco_t local_cap; + + memcpy(&local_cap, &cap, sizeof(cap)); + /* Initialize pointers to out-of-struct arrays */ + local_cap.sym_algorithms = NULL; + local_cap.sym_modes = NULL; + local_cap.sym_modes = NULL; + + code = os_copy_from_user(&req, user_mode_pco_request, sizeof(req)); + if (code == OS_ERROR_OK_S) { + void *endcap; + void *user_bounds; +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHE: Received get_cap request: 0x%p/%u/0x%x", + req.capabilities, req.size, + sizeof(fsl_shw_pco_t)); +#endif + endcap = req.capabilities + 1; /* point to end of structure */ + user_bounds = (void *)req.capabilities + req.size; /* end of area */ + + /* First verify that request is big enough for the main structure */ + if (endcap >= user_bounds) { + endcap = NULL; /* No! */ + } + + /* Copy any Symmetric Algorithm suppport */ + if (cap.sym_algorithm_count != 0) { + local_cap.sym_algorithms = endcap; + endcap = + copy_array(endcap, user_bounds, cap.sym_algorithms, + sizeof(fsl_shw_key_alg_t), + cap.sym_algorithm_count); + } + + /* Copy any Symmetric Modes suppport */ + if (cap.sym_mode_count != 0) { + local_cap.sym_modes = endcap; + endcap = copy_array(endcap, user_bounds, cap.sym_modes, + sizeof(fsl_shw_sym_mode_t), + cap.sym_mode_count); + } + + /* Copy any Hash Algorithm suppport */ + if (cap.hash_algorithm_count != 0) { + local_cap.hash_algorithms = endcap; + endcap = + copy_array(endcap, user_bounds, cap.hash_algorithms, + sizeof(fsl_shw_hash_alg_t), + cap.hash_algorithm_count); + } + + /* Now copy up the (possibly modified) main structure */ + if (endcap != NULL) { + code = + os_copy_to_user(req.capabilities, &local_cap, + sizeof(cap)); + } + + if (endcap == NULL) { + code = OS_ERROR_BAD_ADDRESS_S; + } + + /* And return the FSL SHW code in the request structure. */ + if (code == OS_ERROR_OK_S) { + code = + copy_fsl_code(user_mode_pco_request, + FSL_RETURN_OK_S); + } + } + + /* code may already be set to an error. This is another error case. */ + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW: get capabilities returning %d", code); +#endif + + return code; +} + +/*! + * Handle user-mode Get Results request + * + * Get arguments from user space into kernel space, then call + * fsl_shw_get_results, and then copy its return code and any results from + * kernel space back to user space. + * + * @param user_ctx The kernel version of user's context + * @param user_mode_results_req Pointer to user-space request + * + * @return an os_error_code + */ +static os_error_code get_results(fsl_shw_uco_t * user_ctx, + void *user_mode_results_req) +{ + os_error_code code; + struct results_req req; + fsl_shw_result_t *results = NULL; + int loop; + + code = os_copy_from_user(&req, user_mode_results_req, sizeof(req)); + loop = 0; + + if (code == OS_ERROR_OK_S) { + results = os_alloc_memory(req.requested * sizeof(*results), 0); + if (results == NULL) { + code = OS_ERROR_NO_MEMORY_S; + } + } + + if (code == OS_ERROR_OK_S) { + fsl_shw_return_t err = + fsl_shw_get_results(user_ctx, req.requested, + results, &req.actual); + + /* Send API return code up to user. */ + code = copy_fsl_code(user_mode_results_req, err); + + if ((code == OS_ERROR_OK_S) && (err == FSL_RETURN_OK_S)) { + /* Now copy up the result count */ + code = os_copy_to_user(user_mode_results_req + + offsetof(struct results_req, + actual), &req.actual, + sizeof(req.actual)); + if ((code == OS_ERROR_OK_S) && (req.actual != 0)) { + /* now copy up the results... */ + code = os_copy_to_user(req.results, results, + req.actual * + sizeof(*results)); + } + } + } + + if (results != NULL) { + os_free_memory(results); + } + + return code; +} + +/*! + * Process header of user-mode request. + * + * Mark header as User Mode request. Update UCO's flags and reference fields + * with current versions from the header. + * + * @param user_ctx Pointer to kernel version of UCO. + * @param hdr Pointer to common part of user request. + * + * @return void + */ +inline static void process_hdr(fsl_shw_uco_t * user_ctx, + struct shw_req_header *hdr) +{ + hdr->flags |= FSL_UCO_USERMODE_USER; + user_ctx->flags = hdr->flags; + user_ctx->user_ref = hdr->user_ref; + + return; +} + +/*! + * Handle user-mode Get Random request + * + * @param user_ctx The kernel version of user's context + * @param user_mode_get_random_req Pointer to user-space request + * + * @return an os_error_code + */ +static os_error_code get_random(fsl_shw_uco_t * user_ctx, + void *user_mode_get_random_req) +{ + os_error_code code; + struct get_random_req req; + + code = os_copy_from_user(&req, user_mode_get_random_req, sizeof(req)); + if (code == OS_ERROR_OK_S) { + process_hdr(user_ctx, &req.hdr); +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS + ("SHW: get_random() for %d bytes in %sblocking mode", + req.size, + (req.hdr.flags & FSL_UCO_BLOCKING_MODE) ? "" : "non-"); +#endif + req.hdr.code = + fsl_shw_get_random(user_ctx, req.size, req.random); + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW: get_random() returning %d", req.hdr.code); +#endif + + /* Copy FSL function status back to user */ + code = copy_fsl_code(user_mode_get_random_req, req.hdr.code); + } + + return code; +} + +/*! + * Handle user-mode Add Entropy request + * + * @param user_ctx Pointer to the kernel version of user's context + * @param user_mode_add_entropy_req Address of user-space request + * + * @return an os_error_code + */ +static os_error_code add_entropy(fsl_shw_uco_t * user_ctx, + void *user_mode_add_entropy_req) +{ + os_error_code code; + struct add_entropy_req req; + uint8_t *local_buffer = NULL; + + code = os_copy_from_user(&req, user_mode_add_entropy_req, sizeof(req)); + if (code == OS_ERROR_OK_S) { + local_buffer = os_alloc_memory(req.size, 0); /* for random */ + if (local_buffer != NULL) { + code = + os_copy_from_user(local_buffer, req.entropy, + req.size); + } + if (code == OS_ERROR_OK_S) { + req.hdr.code = fsl_shw_add_entropy(user_ctx, req.size, + local_buffer); + + code = + copy_fsl_code(user_mode_add_entropy_req, + req.hdr.code); + } + } + + if (local_buffer != NULL) { + os_free_memory(local_buffer); + } + + return code; +} + +/******************************************************************/ +/* End User Mode Support */ +/******************************************************************/ + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_register_user); +#endif +/* REQ-S2LRD-PINTFC-API-GEN-004 */ +/* + * Handle user registration. + * + * @param user_ctx The user context for the registration. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t * user_ctx) +{ + fsl_shw_return_t code = FSL_RETURN_INTERNAL_ERROR_S; + + if ((user_ctx->flags & FSL_UCO_BLOCKING_MODE) && + (user_ctx->flags & FSL_UCO_CALLBACK_MODE)) { + code = FSL_RETURN_BAD_FLAG_S; + goto error_exit; + } else if (user_ctx->pool_size == 0) { + code = FSL_RETURN_NO_RESOURCE_S; + goto error_exit; + } else { + user_ctx->result_pool.head = NULL; + user_ctx->result_pool.tail = NULL; + SHW_ADD_USER(user_ctx); + code = FSL_RETURN_OK_S; + } + + error_exit: + return code; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_deregister_user); +#endif +/* REQ-S2LRD-PINTFC-API-GEN-005 */ +/*! + * Destroy the association between the the user and the provider of the API. + * + * @param user_ctx The user context which is no longer needed. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t * user_ctx) +{ + shw_queue_entry_t *finished_request; + fsl_shw_return_t ret = FSL_RETURN_OK_S; + + /* Clean up what we find in result pool. */ + do { + os_lock_context_t lock_context; + os_lock_save_context(shw_queue_lock, lock_context); + finished_request = user_ctx->result_pool.head; + + if (finished_request != NULL) { + SHW_QUEUE_REMOVE_ENTRY(&user_ctx->result_pool, + finished_request); + os_unlock_restore_context(shw_queue_lock, lock_context); + os_free_memory(finished_request); + } else { + os_unlock_restore_context(shw_queue_lock, lock_context); + } + } while (finished_request != NULL); + +#ifdef FSL_HAVE_SCC2 + { + fsl_shw_spo_t *partition; + struct mm_struct *mm = current->mm; + + while ((user_ctx->partition != NULL) + && (ret == FSL_RETURN_OK_S)) { + + partition = user_ctx->partition; + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS + ("Found an abandoned secure partition at %p, releasing", + partition); +#endif + + /* It appears that current->mm is not valid if this is called from a + * close routine (perhaps only if the program raised an exception that + * caused it to close?) If that is the case, then still free the + * partition, but do not remove it from the memory space (dangerous?) + */ + + if (mm == NULL) { +#ifdef SHW_DEBUG + LOG_KDIAG + ("Warning: no mm structure found, not unmapping " + "partition from user memory\n"); +#endif + } else { + /* Unmap the memory region (see sys_munmap in mmap.c) */ + /* Note that this assumes a single memory partition */ + unmap_user_memory(partition->user_base, 8192); + } + + /* If the memory was successfully released */ + if (ret == OS_ERROR_OK_S) { + /* release the partition */ + scc_release_partition(partition->kernel_base); + + /* and remove it from the users context */ + deregister_user_partition(user_ctx, + partition->user_base); + + ret = FSL_RETURN_OK_S; + } else { + ret = FSL_RETURN_ERROR_S; + + goto out; + } + } + } + out: +#endif /* FSL_HAVE_SCC2 */ + + SHW_REMOVE_USER(user_ctx); + + return ret; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_get_results); +#endif +/* REQ-S2LRD-PINTFC-API-GEN-006 */ +fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t * user_ctx, + unsigned result_size, + fsl_shw_result_t results[], + unsigned *result_count) +{ + shw_queue_entry_t *finished_request; + unsigned loop = 0; + + do { + os_lock_context_t lock_context; + + /* Protect state of user's result pool until we have retrieved and + * remove the first entry, or determined that the pool is empty. */ + os_lock_save_context(shw_queue_lock, lock_context); + finished_request = user_ctx->result_pool.head; + + if (finished_request != NULL) { + uint32_t code = 0; + + SHW_QUEUE_REMOVE_ENTRY(&user_ctx->result_pool, + finished_request); + os_unlock_restore_context(shw_queue_lock, lock_context); + + results[loop].user_ref = finished_request->user_ref; + results[loop].code = finished_request->code; + results[loop].detail1 = 0; + results[loop].detail2 = 0; + results[loop].user_req = + finished_request->user_mode_req; + if (finished_request->postprocess != NULL) { + code = + finished_request-> + postprocess(finished_request); + } + + results[loop].code = finished_request->code; + os_free_memory(finished_request); + if (code == 0) { + loop++; + } + } else { /* finished_request is NULL */ + /* pool is empty */ + os_unlock_restore_context(shw_queue_lock, lock_context); + } + + } while ((loop < result_size) && (finished_request != NULL)); + + *result_count = loop; + + return FSL_RETURN_OK_S; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_get_capabilities); +#endif +fsl_shw_pco_t *fsl_shw_get_capabilities(fsl_shw_uco_t * user_ctx) +{ + + /* Unused */ + (void)user_ctx; + + return ∩ +} + +#if !(defined(FSL_HAVE_SAHARA) || defined(FSL_HAVE_RNGA) \ + || defined(FSL_HAVE_RNGB) || defined(FSL_HAVE_RNGC)) + +#if defined(LINUX_VERSION_CODE) +EXPORT_SYMBOL(fsl_shw_get_random); +#endif +fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + + /* Unused */ + (void)user_ctx; + (void)length; + (void)data; + + return FSL_RETURN_ERROR_S; +} + +#if defined(LINUX_VERSION_CODE) +EXPORT_SYMBOL(fsl_shw_add_entropy); +#endif +fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + + /* Unused */ + (void)user_ctx; + (void)length; + (void)data; + + return FSL_RETURN_ERROR_S; +} +#endif + +#if !defined(FSL_HAVE_DRYICE) && !defined(FSL_HAVE_SAHARA2) +#if 0 +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_symmetric_decrypt); +#endif +fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, uint8_t * pt) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)sym_ctx; + (void)length; + (void)ct; + (void)pt; + + return FSL_RETURN_ERROR_S; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_symmetric_encrypt); +#endif +fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, uint8_t * ct) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)sym_ctx; + (void)length; + (void)pt; + (void)ct; + + return FSL_RETURN_ERROR_S; +} + +/* DryIce support provided in separate file */ + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_establish_key); +#endif +fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)establish_type; + (void)key; + + return FSL_RETURN_ERROR_S; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_extract_key); +#endif +fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)covered_key; + + return FSL_RETURN_ERROR_S; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_release_key); +#endif +fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + + return FSL_RETURN_ERROR_S; +} +#endif +#endif /* SAHARA or DRYICE */ + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_hash); +#endif +#if !defined(FSL_HAVE_SAHARA) +fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)hash_ctx; + (void)msg; + (void)length; + (void)result; + (void)result_len; + + return ret; +} +#endif + +#ifndef FSL_HAVE_SAHARA +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_hmac_precompute); +#endif + +fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx) +{ + fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)hmac_ctx; + + return status; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_hmac); +#endif + +fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)hmac_ctx; + (void)msg; + (void)length; + (void)result; + (void)result_len; + + return status; +} +#endif + +/*! + * Call the proper function to encrypt a region of encrypted secure memory + * + * @brief + * + * @param user_ctx User context of the partition owner (NULL in kernel) + * @param partition_base Base address (physical) of the partition + * @param offset_bytes Offset from base address of the data to be encrypted + * @param byte_count Length of the message (bytes) + * @param black_data Pointer to where the encrypted data is stored + * @param IV IV to use for encryption + * @param cypher_mode Cyphering mode to use, specified by type + * #fsl_shw_cypher_mode_t + * + * @return status + */ +fsl_shw_return_t +do_scc_encrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode) +{ + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; +#ifdef FSL_HAVE_SCC2 + + scc_return_t scc_ret; + +#ifdef SHW_DEBUG + uint32_t *owner_32 = (uint32_t *) & (owner_id); + + LOG_KDIAG_ARGS + ("partition base: %p, offset: %i, count: %i, black data: %p\n", + partition_base, offset_bytes, byte_count, (void *)black_data); + + LOG_KDIAG_ARGS("Owner ID: %08x%08x\n", owner_32[1], owner_32[0]); +#endif /* SHW_DEBUG */ + (void)user_ctx; + + os_cache_flush_range(black_data, byte_count); + + scc_ret = + scc_encrypt_region((uint32_t) partition_base, offset_bytes, + byte_count, __virt_to_phys(black_data), IV, + cypher_mode); + + if (scc_ret == SCC_RET_OK) { + retval = FSL_RETURN_OK_S; + } else { + retval = FSL_RETURN_ERROR_S; + } + + /* The SCC2 DMA engine should have written to the black ram, so we need to + * invalidate that region of memory. Note that the red ram is not an + * because it is mapped with the cache disabled. + */ + os_cache_inv_range(black_data, byte_count); + +#endif /* FSL_HAVE_SCC2 */ + return retval; +} + +/*! + * Call the proper function to decrypt a region of encrypted secure memory + * + * @brief + * + * @param user_ctx User context of the partition owner (NULL in kernel) + * @param partition_base Base address (physical) of the partition + * @param offset_bytes Offset from base address that the decrypted data + * shall be placed + * @param byte_count Length of the message (bytes) + * @param black_data Pointer to where the encrypted data is stored + * @param IV IV to use for decryption + * @param cypher_mode Cyphering mode to use, specified by type + * #fsl_shw_cypher_mode_t + * + * @return status + */ +fsl_shw_return_t +do_scc_decrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, const uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode) +{ + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + +#ifdef FSL_HAVE_SCC2 + + scc_return_t scc_ret; + +#ifdef SHW_DEBUG + uint32_t *owner_32 = (uint32_t *) & (owner_id); + + LOG_KDIAG_ARGS + ("partition base: %p, offset: %i, count: %i, black data: %p\n", + partition_base, offset_bytes, byte_count, (void *)black_data); + + LOG_KDIAG_ARGS("Owner ID: %08x%08x\n", owner_32[1], owner_32[0]); +#endif /* SHW_DEBUG */ + + (void)user_ctx; + + /* The SCC2 DMA engine will be reading from the black ram, so we need to + * make sure that the data is pushed out of the cache. Note that the red + * ram is not an issue because it is mapped with the cache disabled. + */ + os_cache_flush_range(black_data, byte_count); + + scc_ret = + scc_decrypt_region((uint32_t) partition_base, offset_bytes, + byte_count, + (uint8_t *) __virt_to_phys(black_data), IV, + cypher_mode); + + if (scc_ret == SCC_RET_OK) { + retval = FSL_RETURN_OK_S; + } else { + retval = FSL_RETURN_ERROR_S; + } + +#endif /* FSL_HAVE_SCC2 */ + + return retval; +} + +void *fsl_shw_smalloc(fsl_shw_uco_t * user_ctx, + uint32_t size, const uint8_t * UMID, uint32_t permissions) +{ +#ifdef FSL_HAVE_SCC2 + int part_no; + void *part_base; + uint32_t part_phys; + scc_config_t *scc_configuration; + + /* Check that the memory size requested is correct */ + scc_configuration = scc_get_configuration(); + if (size != scc_configuration->partition_size_bytes) { + return NULL; + } + + /* attempt to grab a partition. */ + if (scc_allocate_partition(0, &part_no, &part_base, &part_phys) + != SCC_RET_OK) { + return NULL; + } +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("Partition_base: %p, partition_base_phys: %p\n", + part_base, (void *)part_phys); +#endif + + if (scc_engage_partition(part_base, UMID, permissions) + != SCC_RET_OK) { + /* Engagement failed, so the partition needs to be de-allocated */ + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("Failed to engage partition %p, de-allocating", + part_base); +#endif + scc_release_partition(part_base); + + return NULL; + } + + return part_base; + +#else /* FSL_HAVE_SCC2 */ + + (void)user_ctx; + (void)size; + (void)UMID; + (void)permissions; + return NULL; + +#endif /* FSL_HAVE_SCC2 */ +} + +/* Release a block of secure memory */ +fsl_shw_return_t fsl_shw_sfree(fsl_shw_uco_t * user_ctx, void *address) +{ + (void)user_ctx; + +#ifdef FSL_HAVE_SCC2 + if (scc_release_partition(address) == SCC_RET_OK) { + return FSL_RETURN_OK_S; + } +#endif + + return FSL_RETURN_ERROR_S; +} + +/* Check the status of a block of secure memory */ +fsl_shw_return_t fsl_shw_sstatus(fsl_shw_uco_t * user_ctx, + void *address, + fsl_shw_partition_status_t * part_status) +{ + (void)user_ctx; + +#ifdef FSL_HAVE_SCC2 + *part_status = scc_partition_status(address); + + return FSL_RETURN_OK_S; +#endif + + return FSL_RETURN_ERROR_S; +} + +/* Diminish permissions on some secure memory */ +fsl_shw_return_t fsl_shw_diminish_perms(fsl_shw_uco_t * user_ctx, + void *address, uint32_t permissions) +{ + + (void)user_ctx; /* unused parameter warning */ + +#ifdef FSL_HAVE_SCC2 + if (scc_diminish_permissions(address, permissions) == SCC_RET_OK) { + return FSL_RETURN_OK_S; + } +#endif + return FSL_RETURN_ERROR_S; +} + +#ifndef FSL_HAVE_SAHARA +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_gen_encrypt); +#endif + +fsl_shw_return_t fsl_shw_gen_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value) +{ + volatile fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)auth_ctx; + (void)cipher_key_info; + (void)auth_key_info; /* save compilation warning */ + (void)auth_data_length; + (void)auth_data; + (void)payload_length; + (void)payload; + (void)ct; + (void)auth_value; + + return status; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_auth_decrypt); +#endif +/*! + * @brief Authenticate and decrypt a (CCM) stream. + * + * @param user_ctx The user's context + * @param auth_ctx Info on this Auth operation + * @param cipher_key_info Key to encrypt payload + * @param auth_key_info (unused - same key in CCM) + * @param auth_data_length Length in bytes of @a auth_data + * @param auth_data Any auth-only data + * @param payload_length Length in bytes of @a payload + * @param ct The encrypted data + * @param auth_value The authentication code to validate + * @param[out] payload The location to store decrypted data + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_auth_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * ct, + const uint8_t * auth_value, + uint8_t * payload) +{ + volatile fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)auth_ctx; + (void)cipher_key_info; + (void)auth_key_info; /* save compilation warning */ + (void)auth_data_length; + (void)auth_data; + (void)payload_length; + (void)ct; + (void)auth_value; + (void)payload; + + return status; +} + +#endif /* no SAHARA */ + +#ifndef FSL_HAVE_DRYICE + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_gen_random_pf_key); +#endif +/*! + * Cause the hardware to create a new random key for secure memory use. + * + * Have the hardware use the secure hardware random number generator to load a + * new secret key into the hardware random key register. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_gen_random_pf_key(fsl_shw_uco_t * user_ctx) +{ + volatile fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + return status; +} + +#endif /* not have DRYICE */ + +fsl_shw_return_t alloc_slot(fsl_shw_uco_t * user_ctx, fsl_shw_sko_t * key_info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_alloc(user_ctx, + key_info->key_length, + key_info->userid, + &(key_info->handle)); +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("key length: %i, handle: %i", + key_info->key_length, key_info->handle); +#endif + + } else { + /* Key goes in user keystore */ + ret = keystore_slot_alloc(key_info->keystore, + key_info->key_length, + key_info->userid, + &(key_info->handle)); + } + + return ret; +} /* end fn alloc_slot */ + +fsl_shw_return_t load_slot(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, const uint8_t * key) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_load(user_ctx, + key_info->userid, + key_info->handle, key, + key_info->key_length); + } else { + /* Key goes in user keystore */ + ret = keystore_slot_load(key_info->keystore, + key_info->userid, + key_info->handle, key, + key_info->key_length); + } + + return ret; +} /* end fn load_slot */ + +fsl_shw_return_t dealloc_slot(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + do_system_keystore_slot_dealloc(user_ctx, + key_info->userid, + key_info->handle); + } else { + /* Key goes in user keystore */ + keystore_slot_dealloc(key_info->keystore, + key_info->userid, key_info->handle); + } + + key_info->flags &= ~(FSL_SKO_KEY_ESTABLISHED | FSL_SKO_KEY_PRESENT); + + return ret; +} /* end fn slot_dealloc */ diff --git a/drivers/mxc/security/rng/shw_dryice.c b/drivers/mxc/security/rng/shw_dryice.c new file mode 100644 index 000000000000..1fbd4bfef986 --- /dev/null +++ b/drivers/mxc/security/rng/shw_dryice.c @@ -0,0 +1,204 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include "shw_driver.h" +#include "../dryice.h" + +#include <diagnostic.h> + +#ifdef FSL_HAVE_DRYICE + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_gen_random_pf_key); +#endif +/*! + * Cause the hardware to create a new random key for secure memory use. + * + * Have the hardware use the secure hardware random number generator to load a + * new secret key into the hardware random key register. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_gen_random_pf_key(fsl_shw_uco_t * user_ctx) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + di_return_t di_ret; + + /* For now, only blocking mode calls are supported */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + di_ret = dryice_set_random_key(0); + if (di_ret != DI_SUCCESS) { + printk("dryice_set_random_key returned %d\n", di_ret); + goto out; + } + + ret = FSL_RETURN_OK_S; + + out: + return ret; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_read_tamper_event); +#endif +fsl_shw_return_t fsl_shw_read_tamper_event(fsl_shw_uco_t * user_ctx, + fsl_shw_tamper_t * tamperp, + uint64_t * timestampp) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + di_return_t di_ret; + uint32_t di_events = 0; + uint32_t di_time_stamp; + + /* Only blocking mode calls are supported */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + di_ret = dryice_get_tamper_event(&di_events, &di_time_stamp, 0); + if ((di_ret != DI_SUCCESS) && (di_ret != DI_ERR_STATE)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("dryice_get_tamper_event returned %s\n", + di_error_string(di_ret)); +#endif + goto out; + } + + /* Pass time back to caller */ + *timestampp = (uint64_t) di_time_stamp; + + if (di_events & DI_TAMPER_EVENT_WTD) { + *tamperp = FSL_SHW_TAMPER_WTD; + } else if (di_events & DI_TAMPER_EVENT_ETBD) { + *tamperp = FSL_SHW_TAMPER_ETBD; + } else if (di_events & DI_TAMPER_EVENT_ETAD) { + *tamperp = FSL_SHW_TAMPER_ETAD; + } else if (di_events & DI_TAMPER_EVENT_EBD) { + *tamperp = FSL_SHW_TAMPER_EBD; + } else if (di_events & DI_TAMPER_EVENT_SAD) { + *tamperp = FSL_SHW_TAMPER_SAD; + } else if (di_events & DI_TAMPER_EVENT_TTD) { + *tamperp = FSL_SHW_TAMPER_TTD; + } else if (di_events & DI_TAMPER_EVENT_CTD) { + *tamperp = FSL_SHW_TAMPER_CTD; + } else if (di_events & DI_TAMPER_EVENT_VTD) { + *tamperp = FSL_SHW_TAMPER_VTD; + } else if (di_events & DI_TAMPER_EVENT_MCO) { + *tamperp = FSL_SHW_TAMPER_MCO; + } else if (di_events & DI_TAMPER_EVENT_TCO) { + *tamperp = FSL_SHW_TAMPER_TCO; + } else if (di_events != 0) { + /* Apparentliy a tamper type not known to this driver was detected */ + goto out; + } else { + *tamperp = FSL_SHW_TAMPER_NONE; + } + + ret = FSL_RETURN_OK_S; + + out: + return ret; +} /* end fn fsl_shw_read_tamper_event */ +#endif +/*! + * Convert an SHW HW key reference into a DI driver key reference + * + * @param shw_pf_key An SHW HW key value + * @param di_keyp Location to store the equivalent DI driver key + * + * @return FSL_RETURN_OK_S, or error if key is unknown or cannot translate. + */ +fsl_shw_return_t shw_convert_pf_key(fsl_shw_pf_key_t shw_pf_key, + di_key_t * di_keyp) +{ + fsl_shw_return_t ret = FSL_RETURN_BAD_FLAG_S; + + switch (shw_pf_key) { + case FSL_SHW_PF_KEY_IIM: + *di_keyp = DI_KEY_FK; + break; + case FSL_SHW_PF_KEY_RND: + *di_keyp = DI_KEY_RK; + break; + case FSL_SHW_PF_KEY_IIM_RND: + *di_keyp = DI_KEY_FRK; + break; + case FSL_SHW_PF_KEY_PRG: + *di_keyp = DI_KEY_PK; + break; + case FSL_SHW_PF_KEY_IIM_PRG: + *di_keyp = DI_KEY_FPK; + break; + default: + goto out; + } + + ret = FSL_RETURN_OK_S; + + out: + return ret; +} + +#ifdef DIAG_SECURITY_FUNC +const char *di_error_string(int code) +{ + char *str = "unknown"; + + switch (code) { + case DI_SUCCESS: + str = "operation was successful"; + break; + case DI_ERR_BUSY: + str = "device or resource busy"; + break; + case DI_ERR_STATE: + str = "dryice is in incompatible state"; + break; + case DI_ERR_INUSE: + str = "resource is already in use"; + break; + case DI_ERR_UNSET: + str = "resource has not been initialized"; + break; + case DI_ERR_WRITE: + str = "error occurred during register write"; + break; + case DI_ERR_INVAL: + str = "invalid argument"; + break; + case DI_ERR_FAIL: + str = "operation failed"; + break; + case DI_ERR_HLOCK: + str = "resource is hard locked"; + break; + case DI_ERR_SLOCK: + str = "resource is soft locked"; + break; + case DI_ERR_NOMEM: + str = "out of memory"; + break; + default: + break; + } + + return str; +} +#endif /* HAVE DRYICE */ diff --git a/drivers/mxc/security/rng/shw_hash.c b/drivers/mxc/security/rng/shw_hash.c new file mode 100644 index 000000000000..d87e1b75a7bc --- /dev/null +++ b/drivers/mxc/security/rng/shw_hash.c @@ -0,0 +1,328 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file shw_hash.c + * + * This file contains implementations for use of the (internal) SHW hash + * software computation. It defines the usual three steps: + * + * - #shw_hash_init() + * - #shw_hash_update() + * - #shw_hash_final() + * + * In support of the above functions, it also contains these functions: + * - #sha256_init() + * - #sha256_process_block() + * + * + * These functions depend upon the Linux Endian functions __be32_to_cpu(), + * __cpu_to_be32() to convert a 4-byte big-endian array to an integer and + * vice-versa. For those without Linux, it should be pretty obvious what they + * do. + * + * The #shw_hash_update() and #shw_hash_final() functions are generic enough to + * support SHA-1/SHA-224/SHA-256, as needed. Some extra tweaking would be + * necessary to get them to support SHA-384/SHA-512. + * + */ + +#include "shw_driver.h" +#include "shw_hash.h" + +#ifndef __KERNEL__ +#include <asm/types.h> +#include <linux/byteorder/little_endian.h> /* or whichever is proper for target arch */ +#define printk printf +#endif + +/*! + * Rotate a value right by a number of bits. + * + * @param x Word of data which needs rotating + * @param y Number of bits to rotate + * + * @return The new value + */ +inline uint32_t rotr32fixed(uint32_t x, unsigned int y) +{ + return (uint32_t) ((x >> y) | (x << (32 - y))); +} + +#define blk0(i) (W[i] = data[i]) +// Referencing parameters so many times is really poor practice. Do not imitate these macros +#define blk2(i) (W[i & 15] += s1(W[(i - 2) & 15]) + W[(i - 7) & 15] + s0(W[(i - 15) & 15])) + +#define Ch(x,y,z) (z ^ (x & (y ^ z))) +#define Maj(x,y,z) ((x & y) | (z & (x | y))) + +#define a(i) T[(0 - i) & 7] +#define b(i) T[(1 - i) & 7] +#define c(i) T[(2 - i) & 7] +#define d(i) T[(3 - i) & 7] +#define e(i) T[(4 - i) & 7] +#define f(i) T[(5 - i) & 7] +#define g(i) T[(6 - i) & 7] +#define h(i) T[(7 - i) & 7] + +// This is a bad way to write a multi-statement macro... and referencing 'i' so many +// times is really poor practice. Do not imitate. +#define R(i) h(i) += S1( e(i)) + Ch(e(i), f(i), g(i)) + K[i + j] +(j ? blk2(i) : blk0(i));\ + d(i) += h(i);h(i) += S0(a(i)) + Maj(a(i), b(i), c(i)) + +// for SHA256 +#define S0(x) (rotr32fixed(x, 2) ^ rotr32fixed(x, 13) ^ rotr32fixed(x, 22)) +#define S1(x) (rotr32fixed(x, 6) ^ rotr32fixed(x, 11) ^ rotr32fixed(x, 25)) +#define s0(x) (rotr32fixed(x, 7) ^ rotr32fixed(x, 18) ^ (x >> 3)) +#define s1(x) (rotr32fixed(x, 17) ^ rotr32fixed(x, 19) ^ (x >> 10)) + +/*! + * Initialize the Hash State + * + * Constructs the SHA256 hash engine. + * Specification: + * State Size = 32 bytes + * Block Size = 64 bytes + * Digest Size = 32 bytes + * + * @param state Address of hash state structure + * + */ +void sha256_init(shw_hash_state_t * state) +{ + state->bit_count = 0; + state->partial_count_bytes = 0; + + state->state[0] = 0x6a09e667; + state->state[1] = 0xbb67ae85; + state->state[2] = 0x3c6ef372; + state->state[3] = 0xa54ff53a; + state->state[4] = 0x510e527f; + state->state[5] = 0x9b05688c; + state->state[6] = 0x1f83d9ab; + state->state[7] = 0x5be0cd19; +} + +const uint32_t K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +/*! + * Hash a block of data into the SHA-256 hash state. + * + * This function hash the block of data in the @c partial_block + * element of the state structure into the state variables of the + * state structure. + * + * @param state Address of hash state structure + * + */ +static void sha256_process_block(shw_hash_state_t * state) +{ + uint32_t W[16]; + uint32_t T[8]; + uint32_t stack_buffer[SHW_HASH_BLOCK_WORD_SIZE]; + uint32_t *data = &stack_buffer[0]; + uint8_t *input = state->partial_block; + unsigned int i; + unsigned int j; + + /* Copy byte-oriented input block into word-oriented registers */ + for (i = 0; i < SHW_HASH_BLOCK_LEN / sizeof(uint32_t); + i++, input += sizeof(uint32_t)) { + stack_buffer[i] = __be32_to_cpu(*(uint32_t *) input); + } + + /* Copy context->state[] to working vars */ + memcpy(T, state->state, sizeof(T)); + + /* 64 operations, partially loop unrolled */ + for (j = 0; j < SHW_HASH_BLOCK_LEN; j += 16) { + R(0); + R(1); + R(2); + R(3); + R(4); + R(5); + R(6); + R(7); + R(8); + R(9); + R(10); + R(11); + R(12); + R(13); + R(14); + R(15); + } + /* Add the working vars back into context.state[] */ + state->state[0] += a(0); + state->state[1] += b(0); + state->state[2] += c(0); + state->state[3] += d(0); + state->state[4] += e(0); + state->state[5] += f(0); + state->state[6] += g(0); + state->state[7] += h(0); + + /* Wipe variables */ + memset(W, 0, sizeof(W)); + memset(T, 0, sizeof(T)); +} + +/*! + * Initialize the hash state structure + * + * @param state Address of hash state structure. + * @param alg Which hash algorithm to use (must be FSL_HASH_ALG_SHA1) + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hash_init(shw_hash_state_t * state, fsl_shw_hash_alg_t alg) +{ + if (alg != FSL_HASH_ALG_SHA256) { + return FSL_RETURN_BAD_ALGORITHM_S; + } + + sha256_init(state); + + return FSL_RETURN_OK_S; +} + +/*! + * Add input bytes to the hash + * + * The bytes are added to the partial_block element of the hash state, and as + * the partial block is filled, it is processed by sha1_process_block(). This + * function also updates the bit_count element of the hash state. + * + * @param state Address of hash state structure + * @param input Address of bytes to add to the hash + * @param input_len Numbef of bytes at @c input + * + */ +fsl_shw_return_t shw_hash_update(shw_hash_state_t * state, + const uint8_t * input, unsigned int input_len) +{ + unsigned int bytes_needed; /* Needed to fill a block */ + unsigned int bytes_to_copy; /* to copy into the block */ + + /* Account for new data */ + state->bit_count += 8 * input_len; + + /* + * Process input bytes into the ongoing block; process the block when it + * gets full. + */ + while (input_len > 0) { + bytes_needed = SHW_HASH_BLOCK_LEN - state->partial_count_bytes; + bytes_to_copy = ((input_len < bytes_needed) ? + input_len : bytes_needed); + + /* Add in the bytes and do the accounting */ + memcpy(state->partial_block + state->partial_count_bytes, + input, bytes_to_copy); + input += bytes_to_copy; + input_len -= bytes_to_copy; + state->partial_count_bytes += bytes_to_copy; + + /* Run a full block through the transform */ + if (state->partial_count_bytes == SHW_HASH_BLOCK_LEN) { + sha256_process_block(state); + state->partial_count_bytes = 0; + } + } + + return FSL_RETURN_OK_S; +} /* end fn shw_hash_update */ + +/*! + * Finalize the hash + * + * Performs the finalize operation on the previous input data & returns the + * resulting digest. The finalize operation performs the appropriate padding + * up to the block size. + * + * @param state Address of hash state structure + * @param result Location to store the hash result + * @param result_len Number of bytes of @c result to be stored. + * + * @return FSL_RETURN_OK_S if all went well, FSL_RETURN_BAD_DATA_LENGTH_S if + * hash_len is too long, otherwise an error code. + */ +fsl_shw_return_t shw_hash_final(shw_hash_state_t * state, uint8_t * result, + unsigned int result_len) +{ + static const uint8_t pad[SHW_HASH_BLOCK_LEN * 2] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + uint8_t data[sizeof(state->bit_count)]; + uint32_t pad_length; + uint64_t bit_count = state->bit_count; + uint8_t hash[SHW_HASH_LEN]; + int i; + + if (result_len > SHW_HASH_LEN) { + return FSL_RETURN_BAD_DATA_LENGTH_S; + } + + /* Save the length before padding. */ + for (i = sizeof(state->bit_count) - 1; i >= 0; i--) { + data[i] = bit_count & 0xFF; + bit_count >>= 8; + } + pad_length = ((state->partial_count_bytes < 56) ? + (56 - state->partial_count_bytes) : + (120 - state->partial_count_bytes)); + + /* Pad to 56 bytes mod 64 (BLOCK_SIZE). */ + shw_hash_update(state, pad, pad_length); + + /* + * Append the length. This should trigger transform of the final block. + */ + shw_hash_update(state, data, sizeof(state->bit_count)); + + /* Copy the result into a byte array */ + for (i = 0; i < SHW_HASH_STATE_WORDS; i++) { + *(uint32_t *) (hash + 4 * i) = __cpu_to_be32(state->state[i]); + } + + /* And copy the result out to caller */ + memcpy(result, hash, result_len); + + return FSL_RETURN_OK_S; +} /* end fn shw_hash_final */ diff --git a/drivers/mxc/security/rng/shw_hmac.c b/drivers/mxc/security/rng/shw_hmac.c new file mode 100644 index 000000000000..215f3d25055c --- /dev/null +++ b/drivers/mxc/security/rng/shw_hmac.c @@ -0,0 +1,145 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file shw_hmac.c + * + * This file contains implementations for use of the (internal) SHW HMAC + * software computation. It defines the usual three steps: + * + * - #shw_hmac_init() + * - #shw_hmac_update() + * - #shw_hmac_final() + * + * + */ + +#include "shw_driver.h" +#include "shw_hmac.h" + +#ifndef __KERNEL__ +#include <asm/types.h> +#include <linux/byteorder/little_endian.h> /* or whichever is proper for target arch */ +#define printk printf +#endif + +/*! XOR value for HMAC inner key */ +#define INNER_HASH_CONSTANT 0x36 + +/*! XOR value for HMAC outer key */ +#define OUTER_HASH_CONSTANT 0x5C + +/*! + * Initialize the HMAC state structure with the HMAC key + * + * @param state Address of HMAC state structure + * @param key Address of the key to be used for the HMAC. + * @param key_len Number of bytes of @c key. + * + * Convert the key into its equivalent inner and outer hash state objects. + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hmac_init(shw_hmac_state_t * state, + const uint8_t * key, unsigned int key_len) +{ + fsl_shw_return_t code = FSL_RETURN_ERROR_S; + uint8_t first_block[SHW_HASH_BLOCK_LEN]; + unsigned int i; + + /* Don't bother handling the pre-hash. */ + if (key_len > SHW_HASH_BLOCK_LEN) { + code = FSL_RETURN_BAD_KEY_LENGTH_S; + goto out; + } + + /* Prepare inner hash */ + for (i = 0; i < SHW_HASH_BLOCK_LEN; i++) { + if (i < key_len) { + first_block[i] = key[i] ^ INNER_HASH_CONSTANT; + } else { + first_block[i] = INNER_HASH_CONSTANT; + } + } + code = shw_hash_init(&state->inner_hash, FSL_HASH_ALG_SHA256); + if (code != FSL_RETURN_OK_S) { + goto out; + } + shw_hash_update(&state->inner_hash, first_block, SHW_HASH_BLOCK_LEN); + + /* Prepare outer hash */ + for (i = 0; i < SHW_HASH_BLOCK_LEN; i++) { + if (i < key_len) { + first_block[i] = key[i] ^ OUTER_HASH_CONSTANT; + } else { + first_block[i] = OUTER_HASH_CONSTANT; + } + } + code = shw_hash_init(&state->outer_hash, FSL_HASH_ALG_SHA256); + if (code != FSL_RETURN_OK_S) { + goto out; + } + shw_hash_update(&state->outer_hash, first_block, SHW_HASH_BLOCK_LEN); + + /* Wipe evidence of key */ + memset(first_block, 0, SHW_HASH_BLOCK_LEN); + + out: + return code; +} + +/*! + * Put data into the HMAC calculation + * + * Send the msg data inner inner hash's update function. + * + * @param state Address of HMAC state structure. + * @param msg Address of the message data for the HMAC. + * @param msg_len Number of bytes of @c msg. + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hmac_update(shw_hmac_state_t * state, + const uint8_t * msg, unsigned int msg_len) +{ + shw_hash_update(&state->inner_hash, msg, msg_len); + + return FSL_RETURN_OK_S; +} + +/*! + * Calculate the final HMAC + * + * @param state Address of HMAC state structure. + * @param hmac Address of location to store the HMAC. + * @param hmac_len Number of bytes of @c mac to be stored. Probably best if + * this value is no greater than #SHW_HASH_LEN. + * + * This function finalizes the internal hash, and uses that result as + * data for the outer hash. As many bytes of that result are passed + * to the user as desired. + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hmac_final(shw_hmac_state_t * state, + uint8_t * hmac, unsigned int hmac_len) +{ + uint8_t hash_result[SHW_HASH_LEN]; + + shw_hash_final(&state->inner_hash, hash_result, sizeof(hash_result)); + shw_hash_update(&state->outer_hash, hash_result, SHW_HASH_LEN); + + shw_hash_final(&state->outer_hash, hmac, hmac_len); + + return FSL_RETURN_OK_S; +} diff --git a/drivers/mxc/security/rng/shw_memory_mapper.c b/drivers/mxc/security/rng/shw_memory_mapper.c new file mode 100644 index 000000000000..71f1d301f02a --- /dev/null +++ b/drivers/mxc/security/rng/shw_memory_mapper.c @@ -0,0 +1,213 @@ +/* + * Copyright 2005-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + + +/** + * Memory management functions, from Sahara Crypto API + * + * This is a subset of the memory management functions from the Sahara Crypto + * API, and is intended to support user secure partitions. + */ + +#include "portable_os.h" +#include "fsl_shw.h" + +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/pagemap.h> + +#ifdef SHW_DEBUG +#include <diagnostic.h> +#endif + +/* Page context structure. Used by wire_user_memory and unwire_user_memory */ +typedef struct page_ctx_t { + uint32_t count; + struct page **local_pages; +} page_ctx_t; + +/** +******************************************************************************* +* Map and wire down a region of user memory. +* +* +* @param address Userspace address of the memory to wire +* @param length Length of the memory region to wire +* @param page_ctx Page context, to be passed to unwire_user_memory +* +* @return (if successful) Kernel virtual address of the wired pages +*/ +void* wire_user_memory(void* address, uint32_t length, void **page_ctx) +{ + void* kernel_black_addr = NULL; + int result = -1; + int page_index = 0; + page_ctx_t *page_context; + int nr_pages = 0; + unsigned long start_page; + fsl_shw_return_t status; + + /* Determine the number of pages being used for this link */ + nr_pages = (((unsigned long)(address) & ~PAGE_MASK) + + length + ~PAGE_MASK) >> PAGE_SHIFT; + + start_page = (unsigned long)(address) & PAGE_MASK; + + /* Allocate some memory to keep track of the wired user pages, so that + * they can be deallocated later. The block of memory will contain both + * the structure and the array of pages. + */ + page_context = kmalloc(sizeof(page_ctx_t) + + nr_pages * sizeof(struct page *), GFP_KERNEL); + + if (page_context == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; /* no memory! */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("kmalloc() failed."); +#endif + return NULL; + } + + /* Set the page pointer to point to the allocated region of memory */ + page_context->local_pages = (void*)page_context + sizeof(page_ctx_t); + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS("page_context at: %p, local_pages at: %p", + (void *)page_context, + (void *)(page_context->local_pages)); +#endif + + /* Wire down the pages from user space */ + down_read(¤t->mm->mmap_sem); + result = get_user_pages(current, current->mm, + start_page, nr_pages, + WRITE, 0 /* noforce */, + (page_context->local_pages), NULL); + up_read(¤t->mm->mmap_sem); + + if (result < nr_pages) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("get_user_pages() failed."); +#endif + if (result > 0) { + for (page_index = 0; page_index < result; page_index++) + page_cache_release((page_context->local_pages[page_index])); + + kfree(page_context); + } + return NULL; + } + + kernel_black_addr = page_address(page_context->local_pages[0]) + + ((unsigned long)address & ~PAGE_MASK); + + page_context->count = nr_pages; + *page_ctx = page_context; + + return kernel_black_addr; +} + + +/** +******************************************************************************* +* Release and unmap a region of user memory. +* +* @param page_ctx Page context from wire_user_memory +*/ +void unwire_user_memory(void** page_ctx) +{ + int page_index = 0; + struct page_ctx_t *page_context = *page_ctx; + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS("page_context at: %p, first page at:%p, count: %i", + (void *)page_context, + (void *)(page_context->local_pages), + page_context->count); +#endif + + if ((page_context != NULL) && (page_context->local_pages != NULL)) { + for (page_index = 0; page_index < page_context->count; page_index++) + page_cache_release(page_context->local_pages[page_index]); + + kfree(page_context); + *page_ctx = NULL; + } +} + + +/** +******************************************************************************* +* Map some physical memory into a users memory space +* +* @param vma Memory structure to map to +* @param physical_addr Physical address of the memory to be mapped in +* @param size Size of the memory to map (bytes) +* +* @return +*/ +os_error_code +map_user_memory(struct vm_area_struct *vma, uint32_t physical_addr, uint32_t size) +{ + os_error_code retval; + + /* Map the acquired partition into the user's memory space */ + vma->vm_end = vma->vm_start + size; + + /* set cache policy to uncached so that each write of the UMID and + * permissions get directly to the SCC2 in order to engage it + * properly. Once the permissions have been written, it may be + * useful to provide a service for the user to request a different + * cache policy + */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Make sure that the user cannot fork() a child which will inherit + * this mapping, as it creates a security hole. Likewise, do not + * allow the user to 'expand' his mapping beyond this partition. + */ + vma->vm_flags |= VM_IO | VM_RESERVED | VM_DONTCOPY | VM_DONTEXPAND; + + retval = remap_pfn_range(vma, + vma->vm_start, + __phys_to_pfn(physical_addr), + size, + vma->vm_page_prot); + + return retval; +} + + +/** +******************************************************************************* +* Remove some memory from a user's memory space +* +* @param user_addr Userspace address of the memory to be unmapped +* @param size Size of the memory to map (bytes) +* +* @return +*/ +os_error_code +unmap_user_memory(uint32_t user_addr, uint32_t size) +{ + os_error_code retval; + struct mm_struct *mm = current->mm; + + /* Unmap the memory region (see sys_munmap in mmap.c) */ + down_write(&mm->mmap_sem); + retval = do_munmap(mm, (unsigned long)user_addr, size); + up_write(&mm->mmap_sem); + + return retval; +} diff --git a/drivers/mxc/security/sahara2/Kconfig b/drivers/mxc/security/sahara2/Kconfig new file mode 100644 index 000000000000..ab4e6fdc2cfc --- /dev/null +++ b/drivers/mxc/security/sahara2/Kconfig @@ -0,0 +1,35 @@ +menu "SAHARA2 Security Hardware Support" + +config MXC_SAHARA + tristate "Security Hardware Support (FSL SHW)" + ---help--- + Provides driver and kernel mode API for using cryptographic + accelerators. + +config MXC_SAHARA_USER_MODE + tristate "User Mode API for FSL SHW" + depends on MXC_SAHARA + ---help--- + Provides kernel driver for User Mode API. + +config MXC_SAHARA_POLL_MODE + bool "Force driver to POLL for hardware completion." + depends on MXC_SAHARA + default n + ---help--- + When this flag is yes, the driver will not use interrupts to + determine when the hardware has completed a task, but instead + will hold onto the CPU and continually poll the hardware until + it completes. + +config MXC_SAHARA_POLL_MODE_TIMEOUT + hex "Poll loop timeout" + depends on MXC_SAHARA_POLL_MODE + default "0xFFFFFFFF" + help + To avoid infinite polling, a timeout is provided. Should the + timeout be reached, a fault is reported, indicating there must + be something wrong with SAHARA, and SAHARA is reset. The loop + will exit after the given number of iterations. + +endmenu diff --git a/drivers/mxc/security/sahara2/Makefile b/drivers/mxc/security/sahara2/Makefile new file mode 100644 index 000000000000..b515709be29b --- /dev/null +++ b/drivers/mxc/security/sahara2/Makefile @@ -0,0 +1,47 @@ +# Makefile for the Linux Sahara2 driver +# +# This makefile works within a kernel driver tree + +# Need to augment this to support optionally building user-mode support +API_SOURCES = fsl_shw_sym.c fsl_shw_user.c fsl_shw_hash.c fsl_shw_auth.c \ + fsl_shw_hmac.c fsl_shw_rand.c sf_util.c km_adaptor.c fsl_shw_keystore.c \ + fsl_shw_wrap.c \ + + +SOURCES = sah_driver_interface.c sah_hardware_interface.c \ + sah_interrupt_handler.c sah_queue.c sah_queue_manager.c \ + sah_status_manager.c sah_memory_mapper.c + + +# Turn on for mostly full debugging +# DIAGS = -DDIAG_DRV_STATUS -DDIAG_DRV_QUEUE -DDIAG_DRV_INTERRUPT -DDIAG_DRV_IF +# DIAGS += -DDIAG_DURING_INTERRUPT + +# Turn on for lint-type checking +#EXTRA_CFLAGS = -Wall -W -Wstrict-prototypes -Wmissing-prototypes +EXTRA_CFLAGS += -DLINUX_KERNEL $(DIAGS) + + +ifeq ($(CONFIG_MXC_SAHARA_POLL_MODE),y) +EXTRA_CFLAGS += -DSAHARA_POLL_MODE +EXTRA_CFLAGS += -DSAHARA_POLL_MODE_TIMEOUT=$(CONFIG_SAHARA_POLL_MODE_TIMEOUT) +endif + +ifeq ($(CONFIG_MXC_SAHARA_USER_MODE),y) +EXTRA_CFLAGS += -DSAHARA_USER_MODE +SOURCES += +endif + +ifeq ($(CONFIG_PM),y) +EXTRA_CFLAGS += -DSAHARA_POWER_MANAGMENT +endif + +EXTRA_CFLAGS += -Idrivers/mxc/security/sahara2/include + +# handle buggy BSP -- uncomment if these are undefined during build +#EXTRA_CFLAGS += -DSAHARA_BASE_ADDR=HAC_BASE_ADDR -DINT_SAHARA=INT_HAC_RTIC + + +obj-$(CONFIG_MXC_SAHARA) += sahara.o + +sahara-objs := $(SOURCES:.c=.o) $(API_SOURCES:.c=.o) diff --git a/drivers/mxc/security/sahara2/fsl_shw_auth.c b/drivers/mxc/security/sahara2/fsl_shw_auth.c new file mode 100644 index 000000000000..d3100f01380a --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_auth.c @@ -0,0 +1,706 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @file fsl_shw_auth.c + * + * This file contains the routines which do the combined encrypt+authentication + * functions. For now, only AES-CCM is supported. + */ + +#include "sahara.h" +#include "adaptor.h" +#include "sf_util.h" + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_gen_encrypt); +EXPORT_SYMBOL(fsl_shw_auth_decrypt); +#endif + + +/*! Size of buffer to repetively sink useless CBC output */ +#define CBC_BUF_LEN 4096 + +/*! + * Compute the size, in bytes, of the encoded auth length + * + * @param l The actual associated data length + * + * @return The encoded length + */ +#define COMPUTE_NIST_AUTH_LEN_SIZE(l) \ +({ \ + unsigned val; \ + uint32_t len = l; \ + if (len == 0) { \ + val = 0; \ + } else if (len < 65280) { \ + val = 2; \ + } else { /* cannot handle >= 2^32 */ \ + val = 6; \ + } \ + val; \ +}) + +/*! + * Store the encoded Auth Length into the Auth Data + * + * @param l The actual Auth Length + * @param p Location to store encoding (must be uint8_t*) + * + * @return void + */ +#define STORE_NIST_AUTH_LEN(l, p) \ +{ \ + register uint32_t L = l; \ + if ((uint32_t)(l) < 65280) { \ + (p)[1] = L & 0xff; \ + L >>= 8; \ + (p)[0] = L & 0xff; \ + } else { /* cannot handle >= 2^32 */ \ + int i; \ + for (i = 5; i > 1; i--) { \ + (p)[i] = L & 0xff; \ + L >>= 8; \ + } \ + (p)[1] = 0xfe; /* Markers */ \ + (p)[0] = 0xff; \ + } \ +} + +#if defined (FSL_HAVE_SAHARA2) || defined (USE_S2_CCM_DECRYPT_CHAIN) \ + || defined (USE_S2_CCM_ENCRYPT_CHAIN) +/*! Buffer to repetively sink useless CBC output */ +static uint8_t cbc_buffer[CBC_BUF_LEN]; +#endif + +/*! + * Place to store useless output (while bumping CTR0 to CTR1, for instance. + * Must be maximum Symmetric block size + */ +static uint8_t garbage_output[16]; + +/*! + * Block of zeroes which is maximum Symmetric block size, used for + * initializing context register, etc. + */ +static uint8_t block_zeros[16] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/*! + * Append a descriptor chain which will compute CBC over the + * formatted associated data blocks. + * + * @param[in,out] link1 Where to append the new link + * @param[in,out] data_len Location of current/updated auth-only data length + * @param user_ctx Info for acquiring memory + * @param auth_ctx Location of block0 value + * @param auth_data Unformatted associated data + * @param auth_data_length Length in octets of @a auth_data + * @param[in,out] temp_buf Location of in-process data. + * + * @return A return code of type #fsl_shw_return_t. + */ +static inline fsl_shw_return_t process_assoc_from_nist_params(sah_Link ** link1, + uint32_t * + data_len, + fsl_shw_uco_t * + user_ctx, + fsl_shw_acco_t * + auth_ctx, + const uint8_t * + auth_data, + uint32_t + auth_data_length, + uint8_t ** + temp_buf) +{ + fsl_shw_return_t status; + uint32_t auth_size_length = + COMPUTE_NIST_AUTH_LEN_SIZE(auth_data_length); + uint32_t auth_pad_length = + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes - + (auth_data_length + + auth_size_length) % + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes; + + if (auth_pad_length == + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes) { + auth_pad_length = 0; + } + + /* Put in Block0 */ + status = sah_Create_Link(user_ctx->mem_util, link1, + auth_ctx->auth_info.CCM_ctx_info.context, + auth_ctx->auth_info.CCM_ctx_info. + block_size_bytes, SAH_USES_LINK_DATA); + + if (auth_data_length != 0) { + if (status == FSL_RETURN_OK_S) { + /* Add on length preamble to auth data */ + STORE_NIST_AUTH_LEN(auth_data_length, *temp_buf); + status = sah_Append_Link(user_ctx->mem_util, *link1, + *temp_buf, auth_size_length, + SAH_OWNS_LINK_DATA); + *temp_buf += auth_size_length; /* 2, 6, or 10 bytes */ + } + + if (status == FSL_RETURN_OK_S) { + /* Add in auth data */ + status = sah_Append_Link(user_ctx->mem_util, *link1, + (uint8_t *) auth_data, + auth_data_length, + SAH_USES_LINK_DATA); + } + + if ((status == FSL_RETURN_OK_S) && (auth_pad_length > 0)) { + status = sah_Append_Link(user_ctx->mem_util, *link1, + block_zeros, auth_pad_length, + SAH_USES_LINK_DATA); + } + } + /* ... if auth_data_length != 0 */ + *data_len = auth_ctx->auth_info.CCM_ctx_info.block_size_bytes + + auth_data_length + auth_size_length + auth_pad_length; + + return status; +} /* end fn process_assoc_from_nist_params */ + +/*! + * Add a Descriptor which will process with CBC the NIST preamble data + * + * @param desc_chain Current chain + * @param user_ctx User's context + * @param auth_ctx Inf + * @pararm encrypt 0 => decrypt, non-zero => encrypt + * @param auth_data Additional auth data for this call + * @param auth_data_length Length in bytes of @a auth_data + * + * @return A return code of type #fsl_shw_return_t. + */ +static inline fsl_shw_return_t add_assoc_preamble(sah_Head_Desc ** desc_chain, + fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + int encrypt, + const uint8_t * auth_data, + uint32_t auth_data_length) +{ + uint8_t *temp_buf; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + fsl_shw_return_t status = FSL_RETURN_OK_S; + uint32_t cbc_data_length = 0; + /* Assume AES */ + uint32_t header = SAH_HDR_SKHA_ENC_DEC; + uint32_t temp_buf_flag; + unsigned chain_s2 = 1; + +#if defined (FSL_HAVE_SAHARA4) && !defined (USE_S2_CCM_DECRYPT_CHAIN) + if (!encrypt) { + chain_s2 = 0; + } +#endif +#if defined (FSL_HAVE_SAHARA4) && !defined (USE_S2_CCM_ENCRYPT_CHAIN) + if (encrypt) { + chain_s2 = 0; + } +#endif + /* Grab a block big enough for multiple uses so that only one allocate + * request needs to be made. + */ + temp_buf = + user_ctx->mem_util->mu_malloc(user_ctx->mem_util->mu_ref, + 3 * + auth_ctx->auth_info.CCM_ctx_info. + block_size_bytes); + + if (temp_buf == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; + goto out; + } + + if (auth_ctx->flags & FSL_ACCO_NIST_CCM) { + status = process_assoc_from_nist_params(&link1, + &cbc_data_length, + user_ctx, + auth_ctx, + auth_data, + auth_data_length, + &temp_buf); + if (status != FSL_RETURN_OK_S) { + goto out; + } + /* temp_buf has been referenced (and incremented). Only 'own' it + * once, at its first value. Since the nist routine called above + * bumps it... + */ + temp_buf_flag = SAH_USES_LINK_DATA; + } else { /* if NIST */ + status = sah_Create_Link(user_ctx->mem_util, &link1, + (uint8_t *) auth_data, + auth_data_length, SAH_USES_LINK_DATA); + if (status != FSL_RETURN_OK_S) { + goto out; + } + /* for next/first use of temp_buf */ + temp_buf_flag = SAH_OWNS_LINK_DATA; + cbc_data_length = auth_data_length; + } /* else not NIST */ + +#if defined (FSL_HAVE_SAHARA2) || defined (USE_S2_CCM_ENCRYPT_CHAIN) \ + || defined (USE_S2_CCM_DECRYPT_CHAIN) + + if (!chain_s2) { + header = SAH_HDR_SKHA_CBC_ICV + ^ sah_insert_skha_mode_cbc ^ sah_insert_skha_aux0 + ^ sah_insert_skha_encrypt; + } else { + /* + * Auth data links have been created. Now create link for the + * useless output of the CBC calculation. + */ + status = sah_Create_Link(user_ctx->mem_util, &link2, + temp_buf, + auth_ctx->auth_info.CCM_ctx_info. + block_size_bytes, + temp_buf_flag | SAH_OUTPUT_LINK); + if (status != FSL_RETURN_OK_S) { + goto out; + } + + temp_buf += auth_ctx->auth_info.CCM_ctx_info.block_size_bytes; + + cbc_data_length -= + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes; + if (cbc_data_length != 0) { + while ((status == FSL_RETURN_OK_S) + && (cbc_data_length != 0)) { + uint32_t linklen = + (cbc_data_length > + CBC_BUF_LEN) ? CBC_BUF_LEN : + cbc_data_length; + + status = + sah_Append_Link(user_ctx->mem_util, link2, + cbc_buffer, linklen, + SAH_USES_LINK_DATA | + SAH_OUTPUT_LINK); + if (status != FSL_RETURN_OK_S) { + goto out; + } + cbc_data_length -= linklen; + } + } + } +#else + header = SAH_HDR_SKHA_CBC_ICV + ^ sah_insert_skha_mode_cbc ^ sah_insert_skha_aux0 + ^ sah_insert_skha_encrypt; +#endif + /* Crank through auth data */ + status = sah_Append_Desc(user_ctx->mem_util, desc_chain, + header, link1, link2); + + out: + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(user_ctx->mem_util, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(user_ctx->mem_util, link2); + } + } + + (void)encrypt; + return status; +} /* add_assoc_preamble() */ + +#if SUPPORT_SSL +/*! + * Generate an SSL value + * + * @param user_ctx Info for acquiring memory + * @param auth_ctx Info for CTR0, size of MAC + * @param cipher_key_info + * @param auth_key_info + * @param auth_data_length + * @param auth_data + * @param payload_length + * @param payload + * @param ct + * @param auth_value + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t do_ssl_gen(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value) +{ + SAH_SF_DCLS; + uint8_t *ptr1 = NULL; + + /* Assume one-shot init-finalize... no precomputes */ + header = SAH_HDR_MDHA_SET_MODE_MD_KEY ^ + sah_insert_mdha_algorithm[auth_ctx->auth_info.hash_ctx_info. + algorithm] ^ sah_insert_mdha_init ^ + sah_insert_mdha_ssl ^ sah_insert_mdha_pdata ^ + sah_insert_mdha_mac_full; + + /* set up hmac */ + DESC_IN_KEY(header, 0, NULL, auth_key_info); + + /* This is wrong -- need to find 16 extra bytes of data from + * somewhere */ + DESC_IN_OUT(SAH_HDR_MDHA_HASH, payload_length, payload, 1, auth_value); + + /* set up encrypt */ + header = SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_mode[auth_ctx->cipher_ctx_info.mode] + ^ sah_insert_skha_encrypt + ^ sah_insert_skha_algorithm[cipher_key_info->algorithm]; + + /* Honor 'no key parity checking' for DES and TDES */ + if ((cipher_key_info->flags & FSL_SKO_KEY_IGNORE_PARITY) && + ((cipher_key_info->algorithm == FSL_KEY_ALG_DES) || + (cipher_key_info->algorithm == FSL_KEY_ALG_TDES))) { + header ^= sah_insert_skha_no_key_parity; + } + + if (auth_ctx->cipher_ctx_info.mode == FSL_SYM_MODE_CTR) { + header ^= + sah_insert_skha_modulus[auth_ctx->cipher_ctx_info. + modulus_exp]; + } + + if ((auth_ctx->cipher_ctx_info.mode == FSL_SYM_MODE_ECB) + || (auth_ctx->cipher_ctx_info.flags & FSL_SYM_CTX_INIT)) { + ptr1 = block_zeros; + } else { + ptr1 = auth_ctx->cipher_ctx_info.context; + } + + DESC_IN_KEY(header, auth_ctx->cipher_ctx_info.block_size_bytes, ptr1, + cipher_key_info); + + /* This is wrong -- need to find 16 extra bytes of data from + * somewhere... + */ + if (payload_length != 0) { + DESC_IN_OUT(SAH_HDR_SKHA_ENC_DEC, + payload_length, payload, payload_length, ct); + } + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + /* Eliminate compiler warnings until full implementation... */ + (void)auth_data; + (void)auth_data_length; + + return ret; +} /* do_ssl_gen() */ +#endif + +/*! + * @brief Generate a (CCM) auth code and encrypt the payload. + * + * This is a very complicated function. Seven (or eight) descriptors are + * required to perform a CCM calculation. + * + * First: Load CTR0 and key. + * + * Second: Run an octet of data through to bump to CTR1. (This could be + * done in software, but software will have to bump and later decrement - + * or copy and bump. + * + * Third: (in Virtio) Load a descriptor with data of zeros for CBC IV. + * + * Fourth: Run any (optional) "additional data" through the CBC-mode + * portion of the algorithm. + * + * Fifth: Run the payload through in CCM mode. + * + * Sixth: Extract the unencrypted MAC. + * + * Seventh: Load CTR0. + * + * Eighth: Encrypt the MAC. + * + * @param user_ctx The user's context + * @param auth_ctx Info on this Auth operation + * @param cipher_key_info Key to encrypt payload + * @param auth_key_info (unused - same key in CCM) + * @param auth_data_length Length in bytes of @a auth_data + * @param auth_data Any auth-only data + * @param payload_length Length in bytes of @a payload + * @param payload The data to encrypt + * @param[out] ct The location to store encrypted data + * @param[out] auth_value The location to store authentication code + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_gen_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + if (auth_ctx->mode == FSL_ACC_MODE_SSL) { +#if SUPPORT_SSL + ret = do_ssl_gen(user_ctx, auth_ctx, cipher_key_info, + auth_key_info, auth_data_length, auth_data, + payload_length, payload, ct, auth_value); +#else + ret = FSL_RETURN_BAD_MODE_S; +#endif + goto out; + } + + if (auth_ctx->mode != FSL_ACC_MODE_CCM) { + ret = FSL_RETURN_BAD_MODE_S; + goto out; + } + + /* Only support INIT and FINALIZE flags right now. */ + if ((auth_ctx->flags & (FSL_ACCO_CTX_INIT | FSL_ACCO_CTX_LOAD | + FSL_ACCO_CTX_SAVE | FSL_ACCO_CTX_FINALIZE)) + != (FSL_ACCO_CTX_INIT | FSL_ACCO_CTX_FINALIZE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* Load CTR0 and Key */ + header = (SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_mode_ctr + ^ sah_insert_skha_modulus_128 ^ sah_insert_skha_encrypt); + DESC_IN_KEY(header, + auth_ctx->cipher_ctx_info.block_size_bytes, + auth_ctx->cipher_ctx_info.context, cipher_key_info); + + /* Encrypt dummy data to bump to CTR1 */ + header = SAH_HDR_SKHA_ENC_DEC; + DESC_IN_OUT(header, auth_ctx->mac_length, garbage_output, + auth_ctx->mac_length, garbage_output); + +#if defined(FSL_HAVE_SAHARA2) || defined(USE_S2_CCM_ENCRYPT_CHAIN) +#ifndef NO_ZERO_IV_LOAD + header = (SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_encrypt ^ sah_insert_skha_mode_cbc); + DESC_IN_IN(header, + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes, + block_zeros, 0, NULL); +#endif +#endif + + ret = add_assoc_preamble(&desc_chain, user_ctx, + auth_ctx, 1, auth_data, auth_data_length); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Process the payload */ + header = (SAH_HDR_SKHA_SET_MODE_ENC_DEC + ^ sah_insert_skha_mode_ccm + ^ sah_insert_skha_modulus_128 ^ sah_insert_skha_encrypt); +#if defined (FSL_HAVE_SAHARA4) && !defined (USE_S2_CCM_ENCRYPT_CHAIN) + header ^= sah_insert_skha_aux0; +#endif + if (payload_length != 0) { + DESC_IN_OUT(header, payload_length, payload, payload_length, + ct); + } else { + DESC_IN_OUT(header, 0, NULL, 0, NULL); + } /* if payload_length */ + +#if defined (FSL_HAVE_SAHARA4) && !defined (USE_S2_CCM_ENCRYPT_CHAIN) + /* Pull out the CBC-MAC value. */ + DESC_OUT_OUT(SAH_HDR_SKHA_READ_CONTEXT_IV, 0, NULL, + auth_ctx->mac_length, auth_value); +#else + /* Pull out the unencrypted CBC-MAC value. */ + DESC_OUT_OUT(SAH_HDR_SKHA_READ_CONTEXT_IV, + 0, NULL, auth_ctx->mac_length, auth_ctx->unencrypted_mac); + + /* Now load CTR0 in, and encrypt the MAC */ + header = SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_encrypt + ^ sah_insert_skha_mode_ctr ^ sah_insert_skha_modulus_128; + DESC_IN_IN(header, + auth_ctx->cipher_ctx_info.block_size_bytes, + auth_ctx->cipher_ctx_info.context, 0, NULL); + + header = SAH_HDR_SKHA_ENC_DEC; /* Desc. #4 SKHA Enc/Dec */ + DESC_IN_OUT(header, + auth_ctx->mac_length, auth_ctx->unencrypted_mac, + auth_ctx->mac_length, auth_value); +#endif + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + (void)auth_key_info; + return ret; +} /* fsl_shw_gen_encrypt() */ + +/*! + * @brief Authenticate and decrypt a (CCM) stream. + * + * @param user_ctx The user's context + * @param auth_ctx Info on this Auth operation + * @param cipher_key_info Key to encrypt payload + * @param auth_key_info (unused - same key in CCM) + * @param auth_data_length Length in bytes of @a auth_data + * @param auth_data Any auth-only data + * @param payload_length Length in bytes of @a payload + * @param ct The encrypted data + * @param auth_value The authentication code to validate + * @param[out] payload The location to store decrypted data + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_auth_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * ct, + const uint8_t * auth_value, + uint8_t * payload) +{ + SAH_SF_DCLS; +#if defined(FSL_HAVE_SAHARA2) || defined(USE_S2_CCM_DECRYPT_CHAIN) + uint8_t *calced_auth = NULL; + unsigned blocking = user_ctx->flags & FSL_UCO_BLOCKING_MODE; +#endif + + SAH_SF_USER_CHECK(); + + /* Only support CCM */ + if (auth_ctx->mode != FSL_ACC_MODE_CCM) { + ret = FSL_RETURN_BAD_MODE_S; + goto out; + } + /* Only support INIT and FINALIZE flags right now. */ + if ((auth_ctx->flags & (FSL_ACCO_CTX_INIT | FSL_ACCO_CTX_LOAD | + FSL_ACCO_CTX_SAVE | FSL_ACCO_CTX_FINALIZE)) + != (FSL_ACCO_CTX_INIT | FSL_ACCO_CTX_FINALIZE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* Load CTR0 and Key */ + header = SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_mode_ctr ^ sah_insert_skha_modulus_128; +#if defined (FSL_HAVE_SAHARA4) && !defined (USE_S2_CCM_DECRYPT_CHAIN) + header ^= sah_insert_skha_aux0; +#endif + DESC_IN_KEY(header, + auth_ctx->cipher_ctx_info.block_size_bytes, + auth_ctx->cipher_ctx_info.context, cipher_key_info); + + /* Decrypt the MAC which the user passed in */ + header = SAH_HDR_SKHA_ENC_DEC; + DESC_IN_OUT(header, + auth_ctx->mac_length, auth_value, + auth_ctx->mac_length, auth_ctx->unencrypted_mac); + +#if defined(FSL_HAVE_SAHARA2) || defined(USE_S2_CCM_DECRYPT_CHAIN) +#ifndef NO_ZERO_IV_LOAD + header = (SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_encrypt ^ sah_insert_skha_mode_cbc); + DESC_IN_IN(header, + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes, + block_zeros, 0, NULL); +#endif +#endif + + ret = add_assoc_preamble(&desc_chain, user_ctx, + auth_ctx, 0, auth_data, auth_data_length); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Process the payload */ + header = (SAH_HDR_SKHA_SET_MODE_ENC_DEC + ^ sah_insert_skha_mode_ccm ^ sah_insert_skha_modulus_128); +#if defined (FSL_HAVE_SAHARA4) && !defined (USE_S2_CCM_DECRYPT_CHAIN) + header ^= sah_insert_skha_aux0; +#endif + if (payload_length != 0) { + DESC_IN_OUT(header, payload_length, ct, payload_length, + payload); + } else { + DESC_IN_OUT(header, 0, NULL, 0, NULL); + } + +#if defined (FSL_HAVE_SAHARA2) || defined (USE_S2_CCM_DECRYPT_CHAIN) + /* Now pull CBC context (unencrypted MAC) out for comparison. */ + /* Need to allocate a place for it, to handle non-blocking mode + * when this stack frame will disappear! + */ + calced_auth = DESC_TEMP_ALLOC(auth_ctx->mac_length); + header = SAH_HDR_SKHA_READ_CONTEXT_IV; + DESC_OUT_OUT(header, 0, NULL, auth_ctx->mac_length, calced_auth); + if (!blocking) { + /* get_results will need this for comparison */ + desc_chain->out1_ptr = calced_auth; + desc_chain->out2_ptr = auth_ctx->unencrypted_mac; + desc_chain->out_len = auth_ctx->mac_length; + } +#endif + + SAH_SF_EXECUTE(); + +#if defined (FSL_HAVE_SAHARA2) || defined (USE_S2_CCM_DECRYPT_CHAIN) + if (blocking && (ret == FSL_RETURN_OK_S)) { + unsigned i; + /* Validate the auth code */ + for (i = 0; i < auth_ctx->mac_length; i++) { + if (calced_auth[i] != auth_ctx->unencrypted_mac[i]) { + ret = FSL_RETURN_AUTH_FAILED_S; + break; + } + } + } +#endif + + out: + SAH_SF_DESC_CLEAN(); +#if defined (FSL_HAVE_SAHARA2) || defined (USE_S2_CCM_DECRYPT_CHAIN) + DESC_TEMP_FREE(calced_auth); +#endif + + (void)auth_key_info; + return ret; +} /* fsl_shw_gen_decrypt() */ diff --git a/drivers/mxc/security/sahara2/fsl_shw_hash.c b/drivers/mxc/security/sahara2/fsl_shw_hash.c new file mode 100644 index 000000000000..1551173c1c62 --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_hash.c @@ -0,0 +1,186 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_shw_hash.c + * + * This file implements Cryptographic Hashing functions of the FSL SHW API + * for Sahara. This does not include HMAC. + */ + +#include "sahara.h" +#include "sf_util.h" + +#ifdef LINUX_KERNEL +EXPORT_SYMBOL(fsl_shw_hash); +#endif + +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-005 */ +/*! + * Hash a stream of data with a cryptographic hash algorithm. + * + * The flags in the @a hash_ctx control the operation of this function. + * + * Hashing functions work on 64 octets of message at a time. Therefore, when + * any partial hashing of a long message is performed, the message @a length of + * each segment must be a multiple of 64. When ready to + * #FSL_HASH_FLAGS_FINALIZE the hash, the @a length may be any value. + * + * With the #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_FINALIZE flags on, a + * one-shot complete hash, including padding, will be performed. The @a length + * may be any value. + * + * The first octets of a data stream can be hashed by setting the + * #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_SAVE flags. The @a length must be + * a multiple of 64. + * + * The flag #FSL_HASH_FLAGS_LOAD is used to load a context previously saved by + * #FSL_HASH_FLAGS_SAVE. The two in combination will allow a (multiple-of-64 + * octets) 'middle sequence' of the data stream to be hashed with the + * beginning. The @a length must again be a multiple of 64. + * + * Since the flag #FSL_HASH_FLAGS_LOAD is used to load a context previously + * saved by #FSL_HASH_FLAGS_SAVE, the #FSL_HASH_FLAGS_LOAD and + * #FSL_HASH_FLAGS_FINALIZE flags, used together, can be used to finish the + * stream. The @a length may be any value. + * + * If the user program wants to do the padding for the hash, it can leave off + * the #FSL_HASH_FLAGS_FINALIZE flag. The @a length must then be a multiple of + * 64 octets. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] hash_ctx Hashing algorithm and state of the cipher. + * @param msg Pointer to the data to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result If not null, pointer to where to store the hash + * digest. + * @param result_len Number of octets to store in @a result. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + SAH_SF_DCLS; + unsigned ctx_flags = (hash_ctx->flags & (FSL_HASH_FLAGS_INIT + | FSL_HASH_FLAGS_LOAD + | FSL_HASH_FLAGS_SAVE + | FSL_HASH_FLAGS_FINALIZE)); + + SAH_SF_USER_CHECK(); + + /* Reset expectations if user gets overly zealous. */ + if (result_len > hash_ctx->digest_length) { + result_len = hash_ctx->digest_length; + } + + /* Validate hash ctx flags. + * Need INIT or LOAD but not both. + * Need SAVE or digest ptr (both is ok). + */ + if (((ctx_flags & (FSL_HASH_FLAGS_INIT | FSL_HASH_FLAGS_LOAD)) + == (FSL_HASH_FLAGS_INIT | FSL_HASH_FLAGS_LOAD)) + || ((ctx_flags & (FSL_HASH_FLAGS_INIT | FSL_HASH_FLAGS_LOAD)) == 0) + || (!(ctx_flags & FSL_HASH_FLAGS_SAVE) && (result == NULL))) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + if (ctx_flags & FSL_HASH_FLAGS_INIT) { + sah_Oct_Str out_ptr; + unsigned out_len; + + /* Create desc to perform the initial hashing operation */ + /* Desc. #8 w/INIT and algorithm */ + header = SAH_HDR_MDHA_SET_MODE_HASH + ^ sah_insert_mdha_init + ^ sah_insert_mdha_algorithm[hash_ctx->algorithm]; + + /* If user wants one-shot, set padding operation. */ + if (ctx_flags & FSL_HASH_FLAGS_FINALIZE) { + header ^= sah_insert_mdha_pdata; + } + + /* Determine where Digest will go - hash_ctx or result */ + if (ctx_flags & FSL_HASH_FLAGS_SAVE) { + out_ptr = (sah_Oct_Str) hash_ctx->context; + out_len = hash_ctx->context_register_length; + } else { + out_ptr = result; + out_len = (result_len > hash_ctx->digest_length) + ? hash_ctx->digest_length : result_len; + } + + DESC_IN_OUT(header, length, (sah_Oct_Str) msg, out_len, + out_ptr); + } else { /* not doing hash INIT */ + void *out_ptr; + unsigned out_len; + + /* + * Build two descriptors -- one to load in context/set mode, the + * other to compute & retrieve hash/context value. + * + * First up - Desc. #6 to load context. + */ + /* Desc. #8 w/algorithm */ + header = SAH_HDR_MDHA_SET_MODE_MD_KEY + ^ sah_insert_mdha_algorithm[hash_ctx->algorithm]; + + if (ctx_flags & FSL_HASH_FLAGS_FINALIZE) { + header ^= sah_insert_mdha_pdata; + } + + /* Message Digest (in) */ + DESC_IN_IN(header, + hash_ctx->context_register_length, + (sah_Oct_Str) hash_ctx->context, 0, NULL); + + if (ctx_flags & FSL_HASH_FLAGS_SAVE) { + out_ptr = hash_ctx->context; + out_len = hash_ctx->context_register_length; + } else { + out_ptr = result; + out_len = result_len; + } + + /* Second -- run data through and retrieve ctx regs */ + /* Desc. #10 - no mode register with this. */ + header = SAH_HDR_MDHA_HASH; + DESC_IN_OUT(header, length, (sah_Oct_Str) msg, out_len, + out_ptr); + } /* else not INIT */ + + /* Now that execution is rejoined, we can append another descriptor + to extract the digest/context a second time, into the result. */ + if ((ctx_flags & FSL_HASH_FLAGS_SAVE) + && (result != NULL) && (result_len != 0)) { + + header = SAH_HDR_MDHA_STORE_DIGEST; + + /* Message Digest (out) */ + DESC_IN_OUT(header, 0, NULL, + (result_len > hash_ctx->digest_length) + ? hash_ctx->digest_length : result_len, result); + } + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} /* fsl_shw_hash() */ diff --git a/drivers/mxc/security/sahara2/fsl_shw_hmac.c b/drivers/mxc/security/sahara2/fsl_shw_hmac.c new file mode 100644 index 000000000000..4184d9b280dd --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_hmac.c @@ -0,0 +1,266 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_shw_hmac.c + * + * This file implements Hashed Message Authentication Code functions of the FSL + * SHW API for Sahara. + */ + +#include "sahara.h" +#include "sf_util.h" + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_hmac_precompute); +EXPORT_SYMBOL(fsl_shw_hmac); +#endif + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-001 */ +/*! + * Get the precompute information + * + * + * @param user_ctx + * @param key_info + * @param hmac_ctx + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + if ((key_info->algorithm != FSL_KEY_ALG_HMAC) || + (key_info->key_length > 64)) { + return FSL_RETURN_BAD_ALGORITHM_S; + } else if (key_info->key_length == 0) { + return FSL_RETURN_BAD_KEY_LENGTH_S; + } + + /* Set up to start the Inner Calculation */ + /* Desc. #8 w/IPAD, & INIT */ + header = SAH_HDR_MDHA_SET_MODE_HASH + ^ sah_insert_mdha_ipad + ^ sah_insert_mdha_init + ^ sah_insert_mdha_algorithm[hmac_ctx->algorithm]; + + DESC_KEY_OUT(header, key_info, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->inner_precompute); + + /* Set up for starting Outer calculation */ + /* exchange IPAD bit for OPAD bit */ + header ^= (sah_insert_mdha_ipad ^ sah_insert_mdha_opad); + + /* Theoretically, we can leave this link out and use the key which is + * already in the register... however, if we do, the resulting opad + * hash does not have the correct value when using the model. */ + DESC_KEY_OUT(header, key_info, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->outer_precompute); + + SAH_SF_EXECUTE(); + if (ret == FSL_RETURN_OK_S) { + /* flag that precomputes have been entered in this hco + * assume it'll first be used for initilizing */ + hmac_ctx->flags |= (FSL_HMAC_FLAGS_INIT | + FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT); + } + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-002 */ +/*! + * Get the hmac + * + * + * @param user_ctx Info for acquiring memory + * @param key_info + * @param hmac_ctx + * @param msg + * @param length + * @param result + * @param result_len + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + /* check flag consistency */ + /* Note that Final, Init, and Save are an illegal combination when a key + * is being used. Because of the logic flow of this routine, that is + * taken care of without being explict */ + if ( + /* nothing to work on */ + (((hmac_ctx->flags & FSL_HMAC_FLAGS_INIT) == 0) && + ((hmac_ctx->flags & FSL_HMAC_FLAGS_LOAD) == 0)) || + /* can't do both */ + ((hmac_ctx->flags & FSL_HMAC_FLAGS_INIT) && + (hmac_ctx->flags & FSL_HMAC_FLAGS_LOAD)) || + /* must be some output */ + (((hmac_ctx->flags & FSL_HMAC_FLAGS_SAVE) == 0) && + ((hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE) == 0))) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* build descriptor #6 */ + + /* start building descriptor header */ + header = SAH_HDR_MDHA_SET_MODE_MD_KEY ^ + sah_insert_mdha_algorithm[hmac_ctx->algorithm] ^ + sah_insert_mdha_init; + + /* if this is to finalize the digest, mark to pad last block */ + if (hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE) { + header ^= sah_insert_mdha_pdata; + } + + /* Check if this is a one shot */ + if ((hmac_ctx->flags & FSL_HMAC_FLAGS_INIT) && + (hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE)) { + + header ^= sah_insert_mdha_hmac; + + /* See if this uses Precomputes */ + if (hmac_ctx->flags & FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT) { + DESC_IN_IN(header, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->inner_precompute, + hmac_ctx->context_length, + (uint8_t *) hmac_ctx->outer_precompute); + } else { /* Precomputes not requested, try using Key */ + if (key_info->key != NULL) { + /* first, validate the key fields and related flag */ + if ((key_info->key_length == 0) + || (key_info->key_length > 64)) { + ret = FSL_RETURN_BAD_KEY_LENGTH_S; + goto out; + } else { + if (key_info->algorithm != + FSL_KEY_ALG_HMAC) { + ret = + FSL_RETURN_BAD_ALGORITHM_S; + goto out; + } + } + + /* finish building descriptor header (Key specific) */ + header ^= sah_insert_mdha_mac_full; + DESC_IN_KEY(header, 0, NULL, key_info); + } else { /* not using Key or Precomputes, so die */ + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + } + } else { /* it's not a one shot, must be multi-step */ + /* this the last chunk? */ + if (hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE) { + header ^= sah_insert_mdha_hmac; + DESC_IN_IN(header, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->ongoing_context, + hmac_ctx->context_length, + (uint8_t *) hmac_ctx->outer_precompute); + } else { /* not last chunk */ + uint8_t *ptr1; + + if (hmac_ctx->flags & FSL_HMAC_FLAGS_INIT) { + /* must be using precomputes, cannot 'chunk' message + * starting with a key */ + if (hmac_ctx-> + flags & FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT) + { + ptr1 = + (uint8_t *) hmac_ctx-> + inner_precompute; + } else { + ret = FSL_RETURN_NO_RESOURCE_S; + goto out; + } + } else { + ptr1 = (uint8_t *) hmac_ctx->ongoing_context; + } + + if (ret != FSL_RETURN_OK_S) { + goto out; + } + DESC_IN_IN(header, + hmac_ctx->context_register_length, ptr1, + 0, NULL); + } + } /* multi-step */ + + /* build descriptor #10 & maybe 11 */ + header = SAH_HDR_MDHA_HASH; + + if (hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE) { + /* check that the results parameters seem reasonable */ + if ((result_len != 0) && (result != NULL)) { + if (result_len > hmac_ctx->context_register_length) { + result_len = hmac_ctx->context_register_length; + } + + /* message in / digest out (descriptor #10) */ + DESC_IN_OUT(header, length, msg, result_len, result); + + /* see if descriptor #11 needs to be built */ + if (hmac_ctx->flags & FSL_HMAC_FLAGS_SAVE) { + header = SAH_HDR_MDHA_STORE_DIGEST; + /* nothing in / context out */ + DESC_IN_IN(header, 0, NULL, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx-> + ongoing_context); + } + } else { + /* something wrong with result or its length */ + ret = FSL_RETURN_BAD_DATA_LENGTH_S; + } + } else { /* finalize not set, so store in ongoing context field */ + if ((length % 64) == 0) { /* this will change for 384/512 support */ + /* message in / context out */ + DESC_IN_OUT(header, length, msg, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->ongoing_context); + } else { + /* not final data, and not multiple of block length */ + ret = FSL_RETURN_BAD_DATA_LENGTH_S; + } + } + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} /* fsl_shw_hmac() */ diff --git a/drivers/mxc/security/sahara2/fsl_shw_keystore.c b/drivers/mxc/security/sahara2/fsl_shw_keystore.c new file mode 100644 index 000000000000..5585b5b252a0 --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_keystore.c @@ -0,0 +1,837 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** + * @file fsl_shw_keystore.c + * + * File which implements a default keystore policy, for use as the system + * keystore. + */ +#include "fsl_platform.h" +#include "fsl_shw.h" +#include "fsl_shw_keystore.h" + +#if defined(DIAG_DRV_IF) +#include <diagnostic.h> +#endif + +#if !defined(FSL_HAVE_SCC2) && defined(__KERNEL__) +#include <linux/mxc_scc_driver.h> +#endif + +/* Define a semaphore to protect the keystore data */ +#ifdef __KERNEL__ +#define LOCK_INCLUDES os_lock_context_t context +#define ACQUIRE_LOCK os_lock_save_context(keystore->lock, context) +#define RELEASE_LOCK os_unlock_restore_context(keystore->lock, context); +#else +#define LOCK_INCLUDES +#define ACQUIRE_LOCK +#define RELEASE_LOCK +#endif /* __KERNEL__ */ + +/*! + * Calculates the byte offset into a word + * @param bp The byte (char*) pointer + * @return The offset (0, 1, 2, or 3) + */ +#define SCC_BYTE_OFFSET(bp) ((uint32_t)(bp) % sizeof(uint32_t)) + +/*! + * Converts (by rounding down) a byte pointer into a word pointer + * @param bp The byte (char*) pointer + * @return The word (uint32_t) as though it were an aligned (uint32_t*) + */ +#define SCC_WORD_PTR(bp) (((uint32_t)(bp)) & ~(sizeof(uint32_t)-1)) + +/* Depending on the architecture, these functions should be defined + * differently. On Platforms with SCC2, the functions use the secure + * partition interface and should be available in both user and kernel space. + * On platforms with SCC, they use the SCC keystore interface. This is only + * available in kernel mode, so they should be stubbed out in user mode. + */ +#if defined(FSL_HAVE_SCC2) || (defined(FSL_HAVE_SCC) && defined(__KERNEL__)) +EXPORT_SYMBOL(fsl_shw_init_keystore); +void fsl_shw_init_keystore( + fsl_shw_kso_t *keystore, + fsl_shw_return_t(*data_init) (fsl_shw_uco_t *user_ctx, + void **user_data), + void (*data_cleanup) (fsl_shw_uco_t *user_ctx, + void **user_data), + fsl_shw_return_t(*slot_alloc) (void *user_data, + uint32_t size, + uint64_t owner_id, + uint32_t *slot), + fsl_shw_return_t(*slot_dealloc) (void *user_data, + uint64_t + owner_id, + uint32_t slot), + fsl_shw_return_t(*slot_verify_access) (void + *user_data, + uint64_t + owner_id, + uint32_t + slot), + void *(*slot_get_address) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_base) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_offset) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_slot_size) (void *user_data, + uint32_t handle)) +{ + keystore->data_init = data_init; + keystore->data_cleanup = data_cleanup; + keystore->slot_alloc = slot_alloc; + keystore->slot_dealloc = slot_dealloc; + keystore->slot_verify_access = slot_verify_access; + keystore->slot_get_address = slot_get_address; + keystore->slot_get_base = slot_get_base; + keystore->slot_get_offset = slot_get_offset; + keystore->slot_get_slot_size = slot_get_slot_size; +} + +EXPORT_SYMBOL(fsl_shw_init_keystore_default); +void fsl_shw_init_keystore_default(fsl_shw_kso_t *keystore) +{ + keystore->data_init = shw_kso_init_data; + keystore->data_cleanup = shw_kso_cleanup_data; + keystore->slot_alloc = shw_slot_alloc; + keystore->slot_dealloc = shw_slot_dealloc; + keystore->slot_verify_access = shw_slot_verify_access; + keystore->slot_get_address = shw_slot_get_address; + keystore->slot_get_base = shw_slot_get_base; + keystore->slot_get_offset = shw_slot_get_offset; + keystore->slot_get_slot_size = shw_slot_get_slot_size; +} + +/*! + * Do any keystore specific initializations + */ +EXPORT_SYMBOL(fsl_shw_establish_keystore); +fsl_shw_return_t fsl_shw_establish_keystore(fsl_shw_uco_t *user_ctx, + fsl_shw_kso_t *keystore) +{ + if (keystore->data_init == NULL) { + return FSL_RETURN_ERROR_S; + } + + /* Call the data_init function for any user setup */ + return keystore->data_init(user_ctx, &(keystore->user_data)); +} + +EXPORT_SYMBOL(fsl_shw_release_keystore); +void fsl_shw_release_keystore(fsl_shw_uco_t *user_ctx, + fsl_shw_kso_t *keystore) +{ + + /* Call the data_cleanup function for any keystore cleanup. + * NOTE: The keystore doesn't have any way of telling which keys are using + * it, so it is up to the user program to manage their key objects + * correctly. + */ + if ((keystore != NULL) && (keystore->data_cleanup != NULL)) { + keystore->data_cleanup(user_ctx, &(keystore->user_data)); + } + return; +} + +fsl_shw_return_t keystore_slot_alloc(fsl_shw_kso_t *keystore, uint32_t size, + uint64_t owner_id, uint32_t *slot) +{ + LOCK_INCLUDES; + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + +#ifdef DIAG_DRV_IF + LOG_DIAG("In keystore_slot_alloc."); + +#endif + ACQUIRE_LOCK; + if ((keystore->slot_alloc == NULL) || (keystore->user_data == NULL)) { + goto out; + } + +#ifdef DIAG_DRV_IF + LOG_DIAG_ARGS("key length: %i, handle: %i\n", size, *slot); + +#endif +retval = keystore->slot_alloc(keystore->user_data, size, owner_id, slot); +out:RELEASE_LOCK; + return retval; +} + +fsl_shw_return_t keystore_slot_dealloc(fsl_shw_kso_t *keystore, + uint64_t owner_id, uint32_t slot) +{ + LOCK_INCLUDES; + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + ACQUIRE_LOCK; + if ((keystore->slot_alloc == NULL) || (keystore->user_data == NULL)) { + goto out; + } + retval = + keystore->slot_dealloc(keystore->user_data, owner_id, slot); +out:RELEASE_LOCK; + return retval; +} + +fsl_shw_return_t +keystore_slot_load(fsl_shw_kso_t * keystore, uint64_t owner_id, uint32_t slot, + const uint8_t * key_data, uint32_t key_length) +{ + +#ifdef FSL_HAVE_SCC2 + LOCK_INCLUDES; + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + uint32_t slot_size; + uint32_t i; + uint8_t * slot_location; + ACQUIRE_LOCK; + if ((keystore->slot_verify_access == NULL) || + (keystore->user_data == NULL)) + goto out; + if (keystore-> + slot_verify_access(keystore->user_data, owner_id, + slot) !=FSL_RETURN_OK_S) { + retval = FSL_RETURN_AUTH_FAILED_S; + goto out; + } + slot_size = keystore->slot_get_slot_size(keystore->user_data, slot); + if (key_length > slot_size) { + retval = FSL_RETURN_BAD_DATA_LENGTH_S; + goto out; + } + slot_location = keystore->slot_get_address(keystore->user_data, slot); + for (i = 0; i < key_length; i++) { + slot_location[i] = key_data[i]; + } + retval = FSL_RETURN_OK_S; +out:RELEASE_LOCK; + return retval; + +#else /* FSL_HAVE_SCC2 */ + fsl_shw_return_t retval; + scc_return_t scc_ret; + scc_ret = + scc_load_slot(owner_id, slot, (uint8_t *) key_data, key_length); + switch (scc_ret) { + case SCC_RET_OK: + retval = FSL_RETURN_OK_S; + break; + case SCC_RET_VERIFICATION_FAILED: + retval = FSL_RETURN_AUTH_FAILED_S; + break; + case SCC_RET_INSUFFICIENT_SPACE: + retval = FSL_RETURN_BAD_DATA_LENGTH_S; + break; + default: + retval = FSL_RETURN_ERROR_S; + } + return retval; + +#endif /* FSL_HAVE_SCC2 */ +} + +fsl_shw_return_t +keystore_slot_read(fsl_shw_kso_t * keystore, uint64_t owner_id, uint32_t slot, + uint32_t key_length, uint8_t * key_data) +{ +#ifdef FSL_HAVE_SCC2 + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + uint8_t *slot_addr; + uint32_t slot_size; + + slot_addr = keystore->slot_get_address(keystore->user_data, slot); + slot_size = keystore->slot_get_slot_size(keystore->user_data, slot); + + if (key_length > slot_size) { + retval = FSL_RETURN_BAD_KEY_LENGTH_S; + goto out; + } + + memcpy(key_data, slot_addr, key_length); + retval = FSL_RETURN_OK_S; + + out: + return retval; + +#else /* Have SCC2 */ + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + scc_return_t scc_ret; + printk("keystore SCC \n"); + + scc_ret = + scc_read_slot(owner_id, slot, key_length, (uint8_t *) key_data); + printk("keystore SCC Ret value: %d \n", scc_ret); + switch (scc_ret) { + case SCC_RET_OK: + retval = FSL_RETURN_OK_S; + break; + case SCC_RET_VERIFICATION_FAILED: + retval = FSL_RETURN_AUTH_FAILED_S; + break; + case SCC_RET_INSUFFICIENT_SPACE: + retval = FSL_RETURN_BAD_DATA_LENGTH_S; + break; + default: + retval = FSL_RETURN_ERROR_S; + } + + return retval; + +#endif /* FSL_HAVE_SCC2 */ +}/* end fn keystore_slot_read */ + +fsl_shw_return_t +keystore_slot_encrypt(fsl_shw_uco_t *user_ctx, fsl_shw_kso_t *keystore, + uint64_t owner_id, uint32_t slot, uint32_t length, + uint8_t *destination) +{ + +#ifdef FSL_HAVE_SCC2 + LOCK_INCLUDES; + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + uint32_t slot_length; + uint32_t IV[4]; + uint32_t * iv_ptr = (uint32_t *) & (owner_id); + + /* Build the IV */ + IV[0] = iv_ptr[0]; + IV[1] = iv_ptr[1]; + IV[2] = 0; + IV[3] = 0; + ACQUIRE_LOCK; + + /* Ensure that the data will fit in the key slot */ + slot_length = + keystore->slot_get_slot_size(keystore->user_data, slot); + if (length > slot_length) { + goto out; + } + + /* Call scc encrypt function to encrypt the data. */ + retval = do_scc_encrypt_region(user_ctx, + (void *)keystore-> + slot_get_base(keystore->user_data, + slot), + keystore->slot_get_offset(keystore-> + user_data, + slot), + length, destination, IV, + FSL_SHW_CYPHER_MODE_CBC); + goto out; +out:RELEASE_LOCK; + return retval; + +#else + scc_return_t retval; + retval = scc_encrypt_slot(owner_id, slot, length, destination); + if (retval == SCC_RET_OK) + return FSL_RETURN_OK_S; + return FSL_RETURN_ERROR_S; + +#endif /* FSL_HAVE_SCC2 */ +} + +fsl_shw_return_t +keystore_slot_decrypt(fsl_shw_uco_t *user_ctx, fsl_shw_kso_t *keystore, + uint64_t owner_id, uint32_t slot, uint32_t length, + const uint8_t *source) +{ + +#ifdef FSL_HAVE_SCC2 + LOCK_INCLUDES; + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + uint32_t slot_length; + uint32_t IV[4]; + uint32_t *iv_ptr = (uint32_t *) & (owner_id); + + /* Build the IV */ + IV[0] = iv_ptr[0]; + IV[1] = iv_ptr[1]; + IV[2] = 0; + IV[3] = 0; + ACQUIRE_LOCK; + + /* Call scc decrypt function to decrypt the data. */ + + /* Ensure that the data will fit in the key slot */ + slot_length = + keystore->slot_get_slot_size(keystore->user_data, slot); + if (length > slot_length) + goto out; + + /* Call scc decrypt function to encrypt the data. */ + retval = do_scc_decrypt_region(user_ctx, + (void *)keystore-> + slot_get_base(keystore->user_data, + slot), + keystore->slot_get_offset(keystore-> + user_data, + slot), + length, source, IV, + FSL_SHW_CYPHER_MODE_CBC); + goto out; +out:RELEASE_LOCK; + return retval; + +#else + scc_return_t retval; + retval = scc_decrypt_slot(owner_id, slot, length, source); + if (retval == SCC_RET_OK) + return FSL_RETURN_OK_S; + return FSL_RETURN_ERROR_S; + +#endif /* FSL_HAVE_SCC2 */ +} + +#else /* SCC in userspace */ +void fsl_shw_init_keystore( + fsl_shw_kso_t *keystore, + fsl_shw_return_t(*data_init) (fsl_shw_uco_t *user_ctx, + void **user_data), + void (*data_cleanup) (fsl_shw_uco_t *user_ctx, + void **user_data), + fsl_shw_return_t(*slot_alloc) (void *user_data, + uint32_t size, + uint64_t owner_id, + uint32_t *slot), + fsl_shw_return_t(*slot_dealloc) (void *user_data, + uint64_t + owner_id, + uint32_t slot), + fsl_shw_return_t(*slot_verify_access) (void + *user_data, + uint64_t + owner_id, + uint32_t + slot), + void *(*slot_get_address) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_base) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_offset) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_slot_size) (void *user_data, + uint32_t handle)) +{ + (void)keystore; + (void)data_init; + (void)data_cleanup; + (void)slot_alloc; + (void)slot_dealloc; + (void)slot_verify_access; + (void)slot_get_address; + (void)slot_get_base; + (void)slot_get_offset; + (void)slot_get_slot_size; +} + +void fsl_shw_init_keystore_default(fsl_shw_kso_t * keystore) +{ + (void)keystore; +} +fsl_shw_return_t fsl_shw_establish_keystore(fsl_shw_uco_t *user_ctx, + fsl_shw_kso_t *keystore) +{ + (void)user_ctx; + (void)keystore; + return FSL_RETURN_NO_RESOURCE_S; +} +void fsl_shw_release_keystore(fsl_shw_uco_t *user_ctx, + fsl_shw_kso_t *keystore) +{ + (void)user_ctx; + (void)keystore; + return; +} + +fsl_shw_return_t keystore_slot_alloc(fsl_shw_kso_t *keystore, uint32_t size, + uint64_t owner_id, uint32_t *slot) +{ + (void)keystore; + (void)size; + (void)owner_id; + (void)slot; + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t keystore_slot_dealloc(fsl_shw_kso_t *keystore, + uint64_t owner_id, uint32_t slot) +{ + (void)keystore; + (void)owner_id; + (void)slot; + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t +keystore_slot_load(fsl_shw_kso_t *keystore, uint64_t owner_id, uint32_t slot, + const uint8_t *key_data, uint32_t key_length) +{ + (void)keystore; + (void)owner_id; + (void)slot; + (void)key_data; + (void)key_length; + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t +keystore_slot_read(fsl_shw_kso_t * keystore, uint64_t owner_id, uint32_t slot, + uint32_t key_length, uint8_t * key_data) +{ + (void)keystore; + (void)owner_id; + (void)slot; + (void)key_length; + (void)key_data; + + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t +keystore_slot_decrypt(fsl_shw_uco_t *user_ctx, fsl_shw_kso_t *keystore, + uint64_t owner_id, uint32_t slot, uint32_t length, + const uint8_t *source) +{ + (void)user_ctx; + (void)keystore; + (void)owner_id; + (void)slot; + (void)length; + (void)source; + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t +keystore_slot_encrypt(fsl_shw_uco_t *user_ctx, fsl_shw_kso_t *keystore, + uint64_t owner_id, uint32_t slot, uint32_t length, + uint8_t *destination) +{ + (void)user_ctx; + (void)keystore; + (void)owner_id; + (void)slot; + (void)length; + (void)destination; + return FSL_RETURN_NO_RESOURCE_S; +} + + +#endif /* FSL_HAVE_SCC2 */ + +/***** Default keystore implementation **************************************/ + +#ifdef FSL_HAVE_SCC2 + fsl_shw_return_t shw_kso_init_data(fsl_shw_uco_t *user_ctx, + void **user_data) +{ + int retval = FSL_RETURN_ERROR_S; + keystore_data_t *keystore_data = NULL; + fsl_shw_pco_t *capabilities = fsl_shw_get_capabilities(user_ctx); + uint32_t partition_size; + uint32_t slot_count; + uint32_t keystore_data_size; + uint8_t UMID[16] = { + 0x42, 0, 0, 0, 0x43, 0, 0, 0, 0x19, 0, 0, 0, 0x59, 0, 0, 0}; + uint32_t permissions = + FSL_PERM_TH_R | FSL_PERM_TH_W | FSL_PERM_HD_R | FSL_PERM_HD_W | + FSL_PERM_HD_X; + + /* Look up the size of a partition to see how big to make the keystore */ + partition_size = fsl_shw_pco_get_spo_size_bytes(capabilities); + + /* Calculate the required size of the keystore data structure, based on the + * number of keys that can fit in the partition. + */ + slot_count = partition_size / KEYSTORE_SLOT_SIZE; + keystore_data_size = + sizeof(keystore_data_t) + + slot_count * sizeof(keystore_data_slot_info_t); + +#ifdef __KERNEL__ + keystore_data = os_alloc_memory(keystore_data_size, GFP_KERNEL); + +#else + keystore_data = malloc(keystore_data_size); + +#endif + if (keystore_data == NULL) { + retval = FSL_RETURN_NO_RESOURCE_S; + goto out; + } + + /* Clear the memory (effectively clear all key assignments) */ + memset(keystore_data, 0, keystore_data_size); + + /* Place the slot information structure directly after the keystore data + * structure. + */ + keystore_data->slot = + (keystore_data_slot_info_t *) (keystore_data + 1); + keystore_data->slot_count = slot_count; + + /* Retrieve a secure partition to put the keystore in. */ + keystore_data->base_address = + fsl_shw_smalloc(user_ctx, partition_size, UMID, permissions); + if (keystore_data->base_address == NULL) { + retval = FSL_RETURN_NO_RESOURCE_S; + goto out; + } + *user_data = keystore_data; + retval = FSL_RETURN_OK_S; +out:if (retval != FSL_RETURN_OK_S) { + if (keystore_data != NULL) { + if (keystore_data->base_address != NULL) + fsl_shw_sfree(NULL, + keystore_data->base_address); + +#ifdef __KERNEL__ + os_free_memory(keystore_data); + +#else + free(keystore_data); + +#endif + } + } + return retval; +} +void shw_kso_cleanup_data(fsl_shw_uco_t *user_ctx, void **user_data) +{ + if (user_data != NULL) { + keystore_data_t * keystore_data = + (keystore_data_t *) (*user_data); + fsl_shw_sfree(user_ctx, keystore_data->base_address); + +#ifdef __KERNEL__ + os_free_memory(*user_data); + +#else + free(*user_data); + +#endif + } + return; +} + +fsl_shw_return_t shw_slot_verify_access(void *user_data, uint64_t owner_id, + uint32_t slot) +{ + keystore_data_t * data = user_data; + if (data->slot[slot].owner == owner_id) { + return FSL_RETURN_OK_S; + } else { + +#ifdef DIAG_DRV_IF + LOG_DIAG_ARGS("Access to slot %i fails.\n", slot); + +#endif + return FSL_RETURN_AUTH_FAILED_S; + } +} + +fsl_shw_return_t shw_slot_alloc(void *user_data, uint32_t size, + uint64_t owner_id, uint32_t *slot) +{ + keystore_data_t *data = user_data; + uint32_t i; + if (size > KEYSTORE_SLOT_SIZE) + return FSL_RETURN_BAD_KEY_LENGTH_S; + for (i = 0; i < data->slot_count; i++) { + if (data->slot[i].allocated == 0) { + data->slot[i].allocated = 1; + data->slot[i].owner = owner_id; + (*slot) = i; + +#ifdef DIAG_DRV_IF + LOG_DIAG_ARGS("Keystore: allocated slot %i. Slot " + "address: %p\n", + (*slot), + data->base_address + + (*slot) * KEYSTORE_SLOT_SIZE); + +#endif + return FSL_RETURN_OK_S; + } + } + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t shw_slot_dealloc(void *user_data, uint64_t owner_id, + uint32_t slot) +{ + keystore_data_t * data = user_data; + (void)owner_id; + (void)slot; + if (slot >= data->slot_count) + return FSL_RETURN_ERROR_S; + if (data->slot[slot].allocated == 1) { + /* Forcibly remove the data from the keystore */ + memset(shw_slot_get_address(user_data, slot), 0, + KEYSTORE_SLOT_SIZE); + data->slot[slot].allocated = 0; + return FSL_RETURN_OK_S; + } + return FSL_RETURN_ERROR_S; +} + +void *shw_slot_get_address(void *user_data, uint32_t slot) +{ + keystore_data_t * data = user_data; + if (slot >= data->slot_count) + return NULL; + return data->base_address + slot * KEYSTORE_SLOT_SIZE; +} + +uint32_t shw_slot_get_base(void *user_data, uint32_t slot) +{ + keystore_data_t * data = user_data; + + /* There could potentially be more than one secure partition object + * associated with this keystore. For now, there is just one. + */ + (void)slot; + return (uint32_t) (data->base_address); +} + +uint32_t shw_slot_get_offset(void *user_data, uint32_t slot) +{ + keystore_data_t *data = user_data; + if (slot >= data->slot_count) + return FSL_RETURN_ERROR_S; + return (slot * KEYSTORE_SLOT_SIZE); +} + +uint32_t shw_slot_get_slot_size(void *user_data, uint32_t slot) +{ + (void)user_data; + (void)slot; + + /* All slots are the same size in the default implementation */ + return KEYSTORE_SLOT_SIZE; +} + +#else /* FSL_HAVE_SCC2 */ + +#ifdef __KERNEL__ + fsl_shw_return_t shw_kso_init_data(fsl_shw_uco_t *user_ctx, + void **user_data) +{ + + /* The SCC does its own initialization. All that needs to be done here is + * make sure an SCC exists. + */ + *user_data = (void *)0xFEEDFEED; + return FSL_RETURN_OK_S; +} +void shw_kso_cleanup_data(fsl_shw_uco_t *user_ctx, void **user_data) +{ + + /* The SCC does its own cleanup. */ + *user_data = NULL; + return; +} + +fsl_shw_return_t shw_slot_verify_access(void *user_data, uint64_t owner_id, + uint32_t slot) +{ + + /* Zero is used for the size because the newer interface does bounds + * checking later. + */ + scc_return_t retval; + retval = scc_verify_slot_access(owner_id, slot, 0); + if (retval == SCC_RET_OK) { + return FSL_RETURN_OK_S; + } + return FSL_RETURN_AUTH_FAILED_S; +} + +fsl_shw_return_t shw_slot_alloc(void *user_data, uint32_t size, + uint64_t owner_id, uint32_t *slot) +{ + scc_return_t retval; + +#ifdef DIAG_DRV_IF + LOG_DIAG_ARGS("key length: %i, handle: %i\n", size, *slot); + +#endif + retval = scc_alloc_slot(size, owner_id, slot); + if (retval == SCC_RET_OK) + return FSL_RETURN_OK_S; + + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t shw_slot_dealloc(void *user_data, uint64_t owner_id, + uint32_t slot) +{ + scc_return_t retval; + retval = scc_dealloc_slot(owner_id, slot); + if (retval == SCC_RET_OK) + return FSL_RETURN_OK_S; + + return FSL_RETURN_ERROR_S; +} +void *shw_slot_get_address(void *user_data, uint32_t slot) +{ + uint64_t owner_id = *((uint64_t *) user_data); + uint32_t address; + uint32_t value_size_bytes; + uint32_t slot_size_bytes; + scc_return_t scc_ret; + scc_ret = + scc_get_slot_info(owner_id, slot, &address, &value_size_bytes, + &slot_size_bytes); + if (scc_ret == SCC_RET_OK) { + return (void *)address; + } + return NULL; +} + +uint32_t shw_slot_get_base(void *user_data, uint32_t slot) +{ + return 0; +} + +uint32_t shw_slot_get_offset(void *user_data, uint32_t slot) +{ + return 0; +} + + +/* Return the size of the key slot, in octets */ +uint32_t shw_slot_get_slot_size(void *user_data, uint32_t slot) +{ + uint64_t owner_id = *((uint64_t *) user_data); + uint32_t address; + uint32_t value_size_bytes; + uint32_t slot_size_bytes; + scc_return_t scc_ret; + scc_ret = + scc_get_slot_info(owner_id, slot, &address, &value_size_bytes, + &slot_size_bytes); + if (scc_ret == SCC_RET_OK) + return slot_size_bytes; + return 0; +} + + +#endif /* __KERNEL__ */ + +#endif /* FSL_HAVE_SCC2 */ + +/*****************************************************************************/ diff --git a/drivers/mxc/security/sahara2/fsl_shw_rand.c b/drivers/mxc/security/sahara2/fsl_shw_rand.c new file mode 100644 index 000000000000..566c9e5c1e0f --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_rand.c @@ -0,0 +1,96 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_shw_rand.c + * + * This file implements Random Number Generation functions of the FSL SHW API + * for Sahara. + */ + +#include "sahara.h" +#include "sf_util.h" + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_get_random); +#endif + +/* REQ-S2LRD-PINTFC-API-BASIC-RNG-002 */ +/*! + * Get a random number + * + * + * @param user_ctx + * @param length + * @param data + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + SAH_SF_DCLS; + + /* perform a sanity check on the uco */ + ret = sah_validate_uco(user_ctx); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + header = SAH_HDR_RNG_GENERATE; /* Desc. #18 */ + DESC_OUT_OUT(header, length, data, 0, NULL); + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_add_entropy); +#endif + +/*! + * Add entropy to a random number generator + + * @param user_ctx + * @param length + * @param data + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + SAH_SF_DCLS; + + /* perform a sanity check on the uco */ + ret = sah_validate_uco(user_ctx); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + header = SAH_HDR_RNG_GENERATE; /* Desc. #18 */ + + /* create descriptor #18. Generate random data */ + DESC_IN_IN(header, 0, NULL, length, data) + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} diff --git a/drivers/mxc/security/sahara2/fsl_shw_sym.c b/drivers/mxc/security/sahara2/fsl_shw_sym.c new file mode 100644 index 000000000000..454905bbcf68 --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_sym.c @@ -0,0 +1,281 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_shw_sym.c + * + * This file implements Symmetric Cipher functions of the FSL SHW API for + * Sahara. This does not include CCM. + */ + +#include "sahara.h" +#include "fsl_platform.h" + +#include "sf_util.h" +#include "adaptor.h" + +#ifdef LINUX_KERNEL +EXPORT_SYMBOL(fsl_shw_symmetric_encrypt); +EXPORT_SYMBOL(fsl_shw_symmetric_decrypt); +#endif + +#if defined(NEED_CTR_WORKAROUND) +/* CTR mode needs block-multiple data in/out */ +#define LENGTH_PATCH (sym_ctx->block_size_bytes) +#define LENGTH_PATCH_MASK (sym_ctx->block_size_bytes-1) +#else +#define LENGTH_PATCH 0 +#define LENGTH_PATCH_MASK 0 /* du not use! */ +#endif + +/*! + * Block of zeroes which is maximum Symmetric block size, used for + * initializing context register, etc. + */ +static uint32_t block_zeros[4] = { + 0, 0, 0, 0 +}; + +typedef enum cipher_direction { + SYM_DECRYPT, + SYM_ENCRYPT +} cipher_direction_t; + +/*! + * Create and run the chain for a symmetric-key operation. + * + * @param user_ctx Who the user is + * @param key_info What key is to be used + * @param sym_ctx Info details about algorithm + * @param encrypt 0 = decrypt, non-zero = encrypt + * @param length Number of octets at @a in and @a out + * @param in Pointer to input data + * @param out Location to store output data + * + * @return The status of handing chain to driver, + * or an earlier argument/flag or allocation + * error. + */ +static fsl_shw_return_t do_symmetric(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + cipher_direction_t encrypt, + uint32_t length, + const uint8_t * in, uint8_t * out) +{ + SAH_SF_DCLS; + uint8_t *sink = NULL; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + sah_Oct_Str ptr1; + uint32_t size1 = sym_ctx->block_size_bytes; + + SAH_SF_USER_CHECK(); + + /* Two different sets of chains, depending on algorithm */ + if (key_info->algorithm == FSL_KEY_ALG_ARC4) { + if (sym_ctx->flags & FSL_SYM_CTX_INIT) { + /* Desc. #35 w/ARC4 - start from key */ + header = SAH_HDR_ARC4_SET_MODE_KEY + ^ sah_insert_skha_algorithm_arc4; + + DESC_IN_KEY(header, 0, NULL, key_info); + } else { /* load SBox */ + /* Desc. #33 w/ARC4 and NO PERMUTE */ + header = SAH_HDR_ARC4_SET_MODE_SBOX + ^ sah_insert_skha_no_permute + ^ sah_insert_skha_algorithm_arc4; + DESC_IN_IN(header, 256, sym_ctx->context, + 3, sym_ctx->context + 256); + } /* load SBox */ + + /* Add in-out data descriptor to process the data */ + if (length != 0) { + DESC_IN_OUT(SAH_HDR_SKHA_ENC_DEC, length, in, length, + out); + } + + /* Operation is done ... save what came out? */ + if (sym_ctx->flags & FSL_SYM_CTX_SAVE) { + /* Desc. #34 - Read SBox, pointers */ + header = SAH_HDR_ARC4_READ_SBOX; + DESC_OUT_OUT(header, 256, sym_ctx->context, + 3, sym_ctx->context + 256); + } + } else { /* not ARC4 */ + /* Doing 1- or 2- descriptor chain. */ + /* Desc. #1 and algorithm and mode */ + header = SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_mode[sym_ctx->mode] + ^ sah_insert_skha_algorithm[key_info->algorithm]; + + /* Honor 'no key parity checking' for DES and TDES */ + if ((key_info->flags & FSL_SKO_KEY_IGNORE_PARITY) && + ((key_info->algorithm == FSL_KEY_ALG_DES) || + (key_info->algorithm == FSL_KEY_ALG_TDES))) { + header ^= sah_insert_skha_no_key_parity; + } + + /* Header by default is decrypting, so... */ + if (encrypt == SYM_ENCRYPT) { + header ^= sah_insert_skha_encrypt; + } + + if (sym_ctx->mode == FSL_SYM_MODE_CTR) { + header ^= sah_insert_skha_modulus[sym_ctx->modulus_exp]; + } + + if (sym_ctx->mode == FSL_SYM_MODE_ECB) { + ptr1 = NULL; + size1 = 0; + } else if (sym_ctx->flags & FSL_SYM_CTX_INIT) { + ptr1 = (uint8_t *) block_zeros; + } else { + ptr1 = sym_ctx->context; + } + + DESC_IN_KEY(header, sym_ctx->block_size_bytes, ptr1, key_info); + + /* Add in-out data descriptor */ + if (length != 0) { + header = SAH_HDR_SKHA_ENC_DEC; + if (LENGTH_PATCH && (sym_ctx->mode == FSL_SYM_MODE_CTR) + && ((length & LENGTH_PATCH_MASK) != 0)) { + sink = DESC_TEMP_ALLOC(LENGTH_PATCH); + ret = + sah_Create_Link(user_ctx->mem_util, &link1, + (uint8_t *) in, length, + SAH_USES_LINK_DATA); + ret = + sah_Append_Link(user_ctx->mem_util, link1, + (uint8_t *) sink, + LENGTH_PATCH - + (length & + LENGTH_PATCH_MASK), + SAH_USES_LINK_DATA); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + ret = + sah_Create_Link(user_ctx->mem_util, &link2, + out, length, + SAH_USES_LINK_DATA | + SAH_OUTPUT_LINK); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + ret = sah_Append_Link(user_ctx->mem_util, link2, + sink, + LENGTH_PATCH - + (length & + LENGTH_PATCH_MASK), + SAH_USES_LINK_DATA); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + ret = + sah_Append_Desc(user_ctx->mem_util, + &desc_chain, header, link1, + link2); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + link1 = link2 = NULL; + } else { + DESC_IN_OUT(header, length, in, length, out); + } + } + + /* Unload any desired context */ + if (sym_ctx->flags & FSL_SYM_CTX_SAVE) { + DESC_OUT_OUT(SAH_HDR_SKHA_READ_CONTEXT_IV, 0, NULL, + sym_ctx->block_size_bytes, + sym_ctx->context); + } + + } /* not ARC4 */ + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + DESC_TEMP_FREE(sink); + if (LENGTH_PATCH) { + sah_Destroy_Link(user_ctx->mem_util, link1); + sah_Destroy_Link(user_ctx->mem_util, link2); + } + + return ret; +} + +/* REQ-S2LRD-PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ + +/*! + * Compute symmetric encryption + * + * + * @param user_ctx + * @param key_info + * @param sym_ctx + * @param length + * @param pt + * @param ct + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, uint8_t * ct) +{ + fsl_shw_return_t ret; + + ret = do_symmetric(user_ctx, key_info, sym_ctx, SYM_ENCRYPT, + length, pt, ct); + + return ret; +} + +/* PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ + +/*! + * Compute symmetric decryption + * + * + * @param user_ctx + * @param key_info + * @param sym_ctx + * @param length + * @param pt + * @param ct + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, uint8_t * pt) +{ + fsl_shw_return_t ret; + + ret = do_symmetric(user_ctx, key_info, sym_ctx, SYM_DECRYPT, + length, ct, pt); + + return ret; +} diff --git a/drivers/mxc/security/sahara2/fsl_shw_user.c b/drivers/mxc/security/sahara2/fsl_shw_user.c new file mode 100644 index 000000000000..49ec97e7662a --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_user.c @@ -0,0 +1,137 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_shw_user.c + * + * This file implements user and platform capabilities functions of the FSL SHW + * API for Sahara + */ +#include "sahara.h" +#include <adaptor.h> +#include <sf_util.h> + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_get_capabilities); +EXPORT_SYMBOL(fsl_shw_register_user); +EXPORT_SYMBOL(fsl_shw_deregister_user); +EXPORT_SYMBOL(fsl_shw_get_results); +#endif /* __KERNEL__ */ + +struct cap_t { + unsigned populated; + union { + uint32_t buffer[sizeof(fsl_shw_pco_t)]; + fsl_shw_pco_t pco; + }; +}; + +static struct cap_t cap = { + 0, + {} +}; + +/* REQ-S2LRD-PINTFC-API-GEN-003 */ +/*! + * Determine the hardware security capabilities of this platform. + * + * Though a user context object is passed into this function, it will always + * act in a non-blocking manner. + * + * @param user_ctx The user context which will be used for the query. + * + * @return A pointer to the capabilities object. + */ +fsl_shw_pco_t *fsl_shw_get_capabilities(fsl_shw_uco_t * user_ctx) +{ + fsl_shw_pco_t *retval = NULL; + + if (cap.populated) { + retval = &cap.pco; + } else { + if (get_capabilities(user_ctx, &cap.pco) == FSL_RETURN_OK_S) { + cap.populated = 1; + retval = &cap.pco; + } + } + return retval; +} + +/* REQ-S2LRD-PINTFC-API-GEN-004 */ + +/*! + * Create an association between the the user and the provider of the API. + * + * @param user_ctx The user context which will be used for this association. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t * user_ctx) +{ + return sah_register(user_ctx); +} + +/* REQ-S2LRD-PINTFC-API-GEN-005 */ + +/*! + * Destroy the association between the the user and the provider of the API. + * + * @param user_ctx The user context which is no longer needed. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t * user_ctx) +{ + return sah_deregister(user_ctx); +} + +/* REQ-S2LRD-PINTFC-API-GEN-006 */ + +/*! + * Retrieve results from earlier operations. + * + * @param user_ctx The user's context. + * @param result_size The number of array elements of @a results. + * @param[in,out] results Pointer to first of the (array of) locations to + * store results. + * @param[out] result_count Pointer to store the number of results which + * were returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t * user_ctx, + unsigned result_size, + fsl_shw_result_t results[], + unsigned *result_count) +{ + fsl_shw_return_t status; + + /* perform a sanity check on the uco */ + status = sah_validate_uco(user_ctx); + + /* if uco appears ok, build structure and pass to get results */ + if (status == FSL_RETURN_OK_S) { + sah_results arg; + + /* if requested is zero, it's done before it started */ + if (result_size > 0) { + arg.requested = result_size; + arg.actual = result_count; + arg.results = results; + /* get the results */ + status = sah_get_results(&arg, user_ctx); + } + } + + return status; +} diff --git a/drivers/mxc/security/sahara2/fsl_shw_wrap.c b/drivers/mxc/security/sahara2/fsl_shw_wrap.c new file mode 100644 index 000000000000..ebcf9a6bd7c2 --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_wrap.c @@ -0,0 +1,967 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_shw_wrap.c + * + * This file implements Key-Wrap (Black Key) functions of the FSL SHW API for + * Sahara. + * + * - Ownerid is an 8-byte, user-supplied, value to keep KEY confidential. + * - KEY is a 1-32 byte value which starts in SCC RED RAM before + * wrapping, and ends up there on unwrap. Length is limited because of + * size of SCC1 RAM. + * - KEY' is the encrypted KEY + * - LEN is a 1-byte (for now) byte-length of KEY + * - ALG is a 1-byte value for the algorithm which which the key is + * associated. Values are defined by the FSL SHW API + * - Ownerid, LEN, and ALG come from the user's "key_info" object, as does the + * slot number where KEY already is/will be. + * - T is a Nonce + * - T' is the encrypted T + * - KEK is a Key-Encryption Key for the user's Key + * - ICV is the "Integrity Check Value" for the wrapped key + * - Black Key is the string of bytes returned as the wrapped key +<table> +<tr><TD align="right">BLACK_KEY <TD width="3">=<TD>ICV | T' | LEN | ALG | + KEY'</td></tr> +<tr><td> </td></tr> + +<tr><th>To Wrap</th></tr> +<tr><TD align="right">T</td> <TD width="3">=</td> <TD>RND()<sub>16</sub> + </td></tr> +<tr><TD align="right">KEK</td><TD width="3">=</td><TD>HASH<sub>sha256</sub>(T | + Ownerid)<sub>16</sub></td></tr> +<tr><TD align="right">KEY'<TD width="3">=</td><TD> + AES<sub>ctr-enc</sub>(Key=KEK, CTR=0, Data=KEY)</td></tr> +<tr><TD align="right">ICV</td><TD width="3">=</td><td>HMAC<sub>sha256</sub> + (Key=T, Data=Ownerid | LEN | ALG | KEY')<sub>16</sub></td></tr> +<tr><TD align="right">T'</td><TD width="3">=</td><TD>TDES<sub>cbc-enc</sub> + (Key=SLID, IV=Ownerid, Data=T)</td></tr> + +<tr><td> </td></tr> + +<tr><th>To Unwrap</th></tr> +<tr><TD align="right">T</td><TD width="3">=</td><TD>TDES<sub>ecb-dec</sub> + (Key=SLID, IV=Ownerid, Data=T')</sub></td></tr> +<tr><TD align="right">ICV</td><TD width="3">=</td><td>HMAC<sub>sha256</sub> + (Key=T, Data=Ownerid | LEN | ALG | KEY')<sub>16</sub></td></tr> +<tr><TD align="right">KEK</td><TD width="3">=</td><td>HASH<sub>sha256</sub> + (T | Ownerid)<sub>16</sub></td></tr> +<tr><TD align="right">KEY<TD width="3">=</td><TD>AES<sub>ctr-dec</sub> + (Key=KEK, CTR=0, Data=KEY')</td></tr> +</table> + + */ + +#include "sahara.h" +#include "fsl_platform.h" +#include "fsl_shw_keystore.h" + +#include "sf_util.h" +#include "adaptor.h" + +#if defined(DIAG_SECURITY_FUNC) +#include <diagnostic.h> +#endif + +#if defined(NEED_CTR_WORKAROUND) +/* CTR mode needs block-multiple data in/out */ +#define LENGTH_PATCH 16 +#define LENGTH_PATCH_MASK 0xF +#else +#define LENGTH_PATCH 4 +#define LENGTH_PATCH_MASK 3 +#endif + +#if LENGTH_PATCH +#define ROUND_LENGTH(len) \ +({ \ + uint32_t orig_len = len; \ + uint32_t new_len; \ + \ + if ((orig_len & LENGTH_PATCH_MASK) != 0) { \ + new_len = (orig_len + LENGTH_PATCH \ + - (orig_len & LENGTH_PATCH_MASK)); \ + } \ + else { \ + new_len = orig_len; \ + } \ + new_len; \ +}) +#else +#define ROUND_LENGTH(len) (len) +#endif + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_establish_key); +EXPORT_SYMBOL(fsl_shw_extract_key); +EXPORT_SYMBOL(fsl_shw_release_key); +EXPORT_SYMBOL(fsl_shw_read_key); +#endif + +#define ICV_LENGTH 16 +#define T_LENGTH 16 +#define KEK_LENGTH 16 +#define LENGTH_LENGTH 1 +#define ALGORITHM_LENGTH 1 +#define FLAGS_LENGTH 1 + +/* ICV | T' | LEN | ALG | KEY' */ +#define ICV_OFFSET 0 +#define T_PRIME_OFFSET (ICV_OFFSET + ICV_LENGTH) +#define LENGTH_OFFSET (T_PRIME_OFFSET + T_LENGTH) +#define ALGORITHM_OFFSET (LENGTH_OFFSET + LENGTH_LENGTH) +#define FLAGS_OFFSET (ALGORITHM_OFFSET + ALGORITHM_LENGTH) +#define KEY_PRIME_OFFSET (FLAGS_OFFSET + FLAGS_LENGTH) +#define FLAGS_SW_KEY 0x01 + +/* + * For testing of the algorithm implementation,, the DO_REPEATABLE_WRAP flag + * causes the T_block to go into the T field during a wrap operation. This + * will make the black key value repeatable (for a given SCC secret key, or + * always if the default key is in use). + * + * Normally, a random sequence is used. + */ +#ifdef DO_REPEATABLE_WRAP +/*! + * Block of zeroes which is maximum Symmetric block size, used for + * initializing context register, etc. + */ +static uint8_t T_block_[16] = { + 0x42, 0, 0, 0x42, 0x42, 0, 0, 0x42, + 0x42, 0, 0, 0x42, 0x42, 0, 0, 0x42 +}; +#endif + +/*! + * Insert descriptors to calculate ICV = HMAC(key=T, data=LEN|ALG|KEY') + * + * @param user_ctx User's context for this operation + * @param desc_chain Descriptor chain to append to + * @param t_key_info T's key object + * @param black_key Beginning of Black Key region + * @param key_length Number of bytes of key' there are in @c black_key + * @param[out] hmac Location to store ICV. Will be tagged "USES" so + * sf routines will not try to free it. + * + * @return A return code of type #fsl_shw_return_t. + */ +static inline fsl_shw_return_t create_icv_calc(fsl_shw_uco_t * user_ctx, + sah_Head_Desc ** desc_chain, + fsl_shw_sko_t * t_key_info, + const uint8_t * black_key, + uint32_t key_length, + uint8_t * hmac) +{ + fsl_shw_return_t sah_code; + uint32_t header; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + /* Load up T as key for the HMAC */ + header = (SAH_HDR_MDHA_SET_MODE_MD_KEY /* #6 */ + ^ sah_insert_mdha_algorithm_sha256 + ^ sah_insert_mdha_init ^ sah_insert_mdha_hmac ^ + sah_insert_mdha_pdata ^ sah_insert_mdha_mac_full); + sah_code = sah_add_in_key_desc(header, NULL, 0, t_key_info, /* Reference T in RED */ + user_ctx->mem_util, desc_chain); + if (sah_code != FSL_RETURN_OK_S) { + goto out; + } + + /* Previous step loaded key; Now set up to hash the data */ + header = SAH_HDR_MDHA_HASH; /* #10 */ + + /* Input - start with ownerid */ + sah_code = sah_Create_Link(user_ctx->mem_util, &link1, + (void *)&t_key_info->userid, + sizeof(t_key_info->userid), + SAH_USES_LINK_DATA); + if (sah_code != FSL_RETURN_OK_S) { + goto out; + } + + /* Still input - Append black-key fields len, alg, key' */ + sah_code = sah_Append_Link(user_ctx->mem_util, link1, + (void *)black_key + LENGTH_OFFSET, + (LENGTH_LENGTH + + ALGORITHM_LENGTH + + key_length), SAH_USES_LINK_DATA); + + if (sah_code != FSL_RETURN_OK_S) { + goto out; + } + /* Output - computed ICV/HMAC */ + sah_code = sah_Create_Link(user_ctx->mem_util, &link2, + hmac, ICV_LENGTH, + SAH_USES_LINK_DATA | SAH_OUTPUT_LINK); + if (sah_code != FSL_RETURN_OK_S) { + goto out; + } + + sah_code = sah_Append_Desc(user_ctx->mem_util, desc_chain, + header, link1, link2); + + out: + if (sah_code != FSL_RETURN_OK_S) { + (void)sah_Destroy_Link(user_ctx->mem_util, link1); + (void)sah_Destroy_Link(user_ctx->mem_util, link2); + } + + return sah_code; +} /* create_icv_calc */ + +/*! + * Perform unwrapping of a black key into a RED slot + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be unwrapped... key length, slot info, etc. + * @param black_key Encrypted key + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t unwrap(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + const uint8_t * black_key) +{ + SAH_SF_DCLS; + uint8_t *hmac = NULL; + fsl_shw_sko_t t_key_info; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + unsigned i; + unsigned rounded_key_length; + unsigned original_key_length = key_info->key_length; + + hmac = DESC_TEMP_ALLOC(ICV_LENGTH); + + /* Set up key_info for "T" - use same slot as eventual key */ + fsl_shw_sko_init(&t_key_info, FSL_KEY_ALG_AES); + t_key_info.userid = key_info->userid; + t_key_info.handle = key_info->handle; + t_key_info.flags = key_info->flags; + t_key_info.key_length = T_LENGTH; + t_key_info.keystore = key_info->keystore; + + /* Validate SW flags to prevent misuse */ + if ((key_info->flags & FSL_SKO_KEY_SW_KEY) + && !(black_key[FLAGS_OFFSET] & FLAGS_SW_KEY)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* Compute T = SLID_decrypt(T'); leave in RED slot */ + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_decrypt(user_ctx, + key_info->userid, + t_key_info.handle, + T_LENGTH, + black_key + T_PRIME_OFFSET); + + } else { + /* Key goes in user keystore */ + ret = keystore_slot_decrypt(user_ctx, + key_info->keystore, + key_info->userid, + t_key_info.handle, + T_LENGTH, + black_key + T_PRIME_OFFSET); + } + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Compute ICV = HMAC(T, ownerid | len | alg | key' */ + ret = create_icv_calc(user_ctx, &desc_chain, &t_key_info, + black_key, original_key_length, hmac); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creation of sah_Key_Link failed due to bad key" + " flag!\n"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Validating MAC of wrapped key"); +#endif + SAH_SF_EXECUTE(); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + SAH_SF_DESC_CLEAN(); + + /* Check computed ICV against value in Black Key */ + for (i = 0; i < ICV_LENGTH; i++) { + if (black_key[ICV_OFFSET + i] != hmac[i]) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("computed ICV fails at offset %i\n", i); + + { + char buff[300]; + int a; + for (a = 0; a < ICV_LENGTH; a++) + sprintf(&(buff[a * 2]), "%02x", + black_key[ICV_OFFSET + a]); + buff[a * 2 + 1] = 0; + LOG_DIAG_ARGS("black key: %s", buff); + + for (a = 0; a < ICV_LENGTH; a++) + sprintf(&(buff[a * 2]), "%02x", + hmac[a]); + buff[a * 2 + 1] = 0; + LOG_DIAG_ARGS("hmac: %s", buff); + } +#endif + ret = FSL_RETURN_AUTH_FAILED_S; + goto out; + } + } + + /* This is no longer needed. */ + DESC_TEMP_FREE(hmac); + + /* Compute KEK = SHA256(T | ownerid). Rewrite slot with value */ + header = (SAH_HDR_MDHA_SET_MODE_HASH /* #8 */ + ^ sah_insert_mdha_init + ^ sah_insert_mdha_algorithm_sha256 ^ sah_insert_mdha_pdata); + + /* Input - Start with T */ + ret = sah_Create_Key_Link(user_ctx->mem_util, &link1, &t_key_info); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Still input - append ownerid */ + ret = sah_Append_Link(user_ctx->mem_util, link1, + (void *)&key_info->userid, + sizeof(key_info->userid), SAH_USES_LINK_DATA); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Output - KEK goes into RED slot */ + ret = sah_Create_Key_Link(user_ctx->mem_util, &link2, &t_key_info); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Put the Hash calculation into the chain. */ + ret = sah_Append_Desc(user_ctx->mem_util, &desc_chain, + header, link1, link2); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Compute KEY = AES-decrypt(KEK, KEY') */ + header = (SAH_HDR_SKHA_SET_MODE_IV_KEY /* #1 */ + ^ sah_insert_skha_mode_ctr + ^ sah_insert_skha_algorithm_aes + ^ sah_insert_skha_modulus_128); + /* Load KEK in as the key to use */ + DESC_IN_KEY(header, 0, NULL, &t_key_info); + + rounded_key_length = ROUND_LENGTH(original_key_length); + key_info->key_length = rounded_key_length; + + /* Now set up for computation. Result in RED */ + header = SAH_HDR_SKHA_ENC_DEC; /* #4 */ + DESC_IN_KEY(header, rounded_key_length, black_key + KEY_PRIME_OFFSET, + key_info); + + /* Perform the operation */ +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Decrypting key with KEK"); +#endif + SAH_SF_EXECUTE(); + + out: + key_info->key_length = original_key_length; + SAH_SF_DESC_CLEAN(); + + DESC_TEMP_FREE(hmac); + + /* Erase tracks */ + t_key_info.userid = 0xdeadbeef; + t_key_info.handle = 0xdeadbeef; + + return ret; +} /* unwrap */ + +/*! + * Perform wrapping of a black key from a RED slot + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be wrapped... key length, slot info, etc. + * @param black_key Place to store encrypted key + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t wrap(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, uint8_t * black_key) +{ + SAH_SF_DCLS; + unsigned slots_allocated = 0; /* boolean */ + fsl_shw_sko_t T_key_info; /* for holding T */ + fsl_shw_sko_t KEK_key_info; /* for holding KEK */ + unsigned original_key_length = key_info->key_length; + unsigned rounded_key_length; + sah_Link *link1; + sah_Link *link2; + + black_key[LENGTH_OFFSET] = key_info->key_length; + black_key[ALGORITHM_OFFSET] = key_info->algorithm; + + memcpy(&T_key_info, key_info, sizeof(T_key_info)); + fsl_shw_sko_set_key_length(&T_key_info, T_LENGTH); + T_key_info.algorithm = FSL_KEY_ALG_HMAC; + + memcpy(&KEK_key_info, &T_key_info, sizeof(KEK_key_info)); + KEK_key_info.algorithm = FSL_KEY_ALG_AES; + + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_alloc(user_ctx, + T_LENGTH, key_info->userid, + &T_key_info.handle); + + } else { + /* Key goes in user keystore */ + ret = keystore_slot_alloc(key_info->keystore, + T_LENGTH, + key_info->userid, &T_key_info.handle); + } + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_alloc(user_ctx, + KEK_LENGTH, key_info->userid, + &KEK_key_info.handle); + + } else { + /* Key goes in user keystore */ + ret = keystore_slot_alloc(key_info->keystore, + KEK_LENGTH, key_info->userid, + &KEK_key_info.handle); + } + + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("do_scc_slot_alloc() failed"); +#endif + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + (void)do_system_keystore_slot_dealloc(user_ctx, + key_info->userid, T_key_info.handle); + + } else { + /* Key goes in user keystore */ + (void)keystore_slot_dealloc(key_info->keystore, + key_info->userid, T_key_info.handle); + } + } else { + slots_allocated = 1; + } + + /* Set up to compute everything except T' ... */ +#ifndef DO_REPEATABLE_WRAP + /* Compute T = RND() */ + header = SAH_HDR_RNG_GENERATE; /* Desc. #18 */ + DESC_KEY_OUT(header, &T_key_info, 0, NULL); +#else + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_load(user_ctx, + T_key_info.userid, + T_key_info.handle, T_block, + T_key_info.key_length); + } else { + /* Key goes in user keystore */ + ret = keystore_slot_load(key_info->keystore, + T_key_info.userid, + T_key_info.handle, + T_block, T_key_info.key_length); + } + + if (ret != FSL_RETURN_OK_S) { + goto out; + } +#endif + + /* Compute KEK = SHA256(T | Ownerid) */ + header = (SAH_HDR_MDHA_SET_MODE_HASH /* #8 */ + ^ sah_insert_mdha_init + ^ sah_insert_mdha_algorithm[FSL_HASH_ALG_SHA256] + ^ sah_insert_mdha_pdata); + /* Input - Start with T */ + ret = sah_Create_Key_Link(user_ctx->mem_util, &link1, &T_key_info); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + /* Still input - append ownerid */ + ret = sah_Append_Link(user_ctx->mem_util, link1, + (void *)&key_info->userid, + sizeof(key_info->userid), SAH_USES_LINK_DATA); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + /* Output - KEK goes into RED slot */ + ret = sah_Create_Key_Link(user_ctx->mem_util, &link2, &KEK_key_info); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + /* Put the Hash calculation into the chain. */ + ret = sah_Append_Desc(user_ctx->mem_util, &desc_chain, + header, link1, link2); + if (ret != FSL_RETURN_OK_S) { + goto out; + } +#if defined(NEED_CTR_WORKAROUND) + rounded_key_length = ROUND_LENGTH(original_key_length); + key_info->key_length = rounded_key_length; +#else + rounded_key_length = original_key_length; +#endif + /* Compute KEY' = AES-encrypt(KEK, KEY) */ + header = (SAH_HDR_SKHA_SET_MODE_IV_KEY /* #1 */ + ^ sah_insert_skha_mode[FSL_SYM_MODE_CTR] + ^ sah_insert_skha_algorithm[FSL_KEY_ALG_AES] + ^ sah_insert_skha_modulus[FSL_CTR_MOD_128]); + /* Set up KEK as key to use */ + DESC_IN_KEY(header, 0, NULL, &KEK_key_info); + header = SAH_HDR_SKHA_ENC_DEC; + DESC_KEY_OUT(header, key_info, + key_info->key_length, black_key + KEY_PRIME_OFFSET); + + /* Set up flags info */ + black_key[FLAGS_OFFSET] = 0; + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + black_key[FLAGS_OFFSET] |= FLAGS_SW_KEY; + } + + /* Compute and store ICV into Black Key */ + ret = create_icv_calc(user_ctx, &desc_chain, &T_key_info, + black_key, original_key_length, + black_key + ICV_OFFSET); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creation of sah_Key_Link failed due to bad key" + " flag!\n"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } + + /* Now get Sahara to do the work. */ +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Encrypting key with KEK"); +#endif + SAH_SF_EXECUTE(); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("sah_Descriptor_Chain_Execute() failed"); +#endif + goto out; + } + + /* Compute T' = SLID_encrypt(T); Result goes to Black Key */ + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_encrypt(user_ctx, + T_key_info.userid, T_key_info.handle, + T_LENGTH, black_key + T_PRIME_OFFSET); + } else { + /* Key goes in user keystore */ + ret = keystore_slot_encrypt(user_ctx, + key_info->keystore, + T_key_info.userid, + T_key_info.handle, + T_LENGTH, + black_key + T_PRIME_OFFSET); + } + + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("do_scc_slot_encrypt() failed"); +#endif + goto out; + } + + out: + key_info->key_length = original_key_length; + + SAH_SF_DESC_CLEAN(); + if (slots_allocated) { + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + (void)do_system_keystore_slot_dealloc(user_ctx, + key_info->userid, + T_key_info. + handle); + (void)do_system_keystore_slot_dealloc(user_ctx, + key_info->userid, + KEK_key_info. + handle); + } else { + /* Key goes in user keystore */ + (void)keystore_slot_dealloc(key_info->keystore, + key_info->userid, + T_key_info.handle); + (void)keystore_slot_dealloc(key_info->keystore, + key_info->userid, + KEK_key_info.handle); + } + } + + return ret; +} /* wrap */ + +/*! + * Place a key into a protected location for use only by cryptographic + * algorithms. + * + * This only needs to be used to a) unwrap a key, or b) set up a key which + * could be wrapped with a later call to #fsl_shw_extract_key(). Normal + * cleartext keys can simply be placed into #fsl_shw_sko_t key objects with + * #fsl_shw_sko_set_key() and used directly. + * + * The maximum key size supported for wrapped/unwrapped keys is 32 octets. + * (This is the maximum reasonable key length on Sahara - 32 octets for an HMAC + * key based on SHA-256.) The key size is determined by the @a key_info. The + * expected length of @a key can be determined by + * #fsl_shw_sko_calculate_wrapped_size() + * + * The protected key will not be available for use until this operation + * successfully completes. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be established. In the create case, the key + * length must be set. + * @param establish_type How @a key will be interpreted to establish a + * key for use. + * @param key If @a establish_type is #FSL_KEY_WRAP_UNWRAP, + * this is the location of a wrapped key. If + * @a establish_type is #FSL_KEY_WRAP_CREATE, this + * parameter can be @a NULL. If @a establish_type + * is #FSL_KEY_WRAP_ACCEPT, this is the location + * of a plaintext key. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key) +{ + SAH_SF_DCLS; + unsigned original_key_length = key_info->key_length; + unsigned rounded_key_length; + unsigned slot_allocated = 0; + uint32_t old_flags; + + header = SAH_HDR_RNG_GENERATE; /* Desc. #18 for rand */ + + /* TODO: THIS STILL NEEDS TO BE REFACTORED */ + + /* Write operations into SCC memory require word-multiple number of + * bytes. For ACCEPT and CREATE functions, the key length may need + * to be rounded up. Calculate. */ + if (LENGTH_PATCH && (original_key_length & LENGTH_PATCH_MASK) != 0) { + rounded_key_length = original_key_length + LENGTH_PATCH + - (original_key_length & LENGTH_PATCH_MASK); + } else { + rounded_key_length = original_key_length; + } + + SAH_SF_USER_CHECK(); + + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { +#ifdef DIAG_SECURITY_FUNC + ret = FSL_RETURN_BAD_FLAG_S; + LOG_DIAG("Key already established\n"); +#endif + } + + + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_alloc(user_ctx, + key_info->key_length, + key_info->userid, + &(key_info->handle)); +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS + ("key length: %i, handle: %i, rounded key length: %i", + key_info->key_length, key_info->handle, + rounded_key_length); +#endif + + } else { + /* Key goes in user keystore */ + ret = keystore_slot_alloc(key_info->keystore, + key_info->key_length, + key_info->userid, + &(key_info->handle)); + } + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Slot allocation failed\n"); +#endif + goto out; + } + slot_allocated = 1; + + key_info->flags |= FSL_SKO_KEY_ESTABLISHED; + switch (establish_type) { + case FSL_KEY_WRAP_CREATE: +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creating random key\n"); +#endif + /* Use safe version of key length */ + key_info->key_length = rounded_key_length; + /* Generate descriptor to put random value into */ + DESC_KEY_OUT(header, key_info, 0, NULL); + /* Restore actual, desired key length */ + key_info->key_length = original_key_length; + + old_flags = user_ctx->flags; + /* Now put random value into key */ + SAH_SF_EXECUTE(); + /* Restore user's old flag value */ + user_ctx->flags = old_flags; +#ifdef DIAG_SECURITY_FUNC + if (ret == FSL_RETURN_OK_S) { + LOG_DIAG("ret is ok"); + } else { + LOG_DIAG("ret is not ok"); + } +#endif + break; + + case FSL_KEY_WRAP_ACCEPT: +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Accepting plaintext key\n"); +#endif + if (key == NULL) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("ACCEPT: Red Key is NULL"); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + /* Copy in safe number of bytes of Red key */ + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_load(user_ctx, + key_info->userid, + key_info->handle, key, + rounded_key_length); + } else { + /* Key goes in user keystore */ + ret = keystore_slot_load(key_info->keystore, + key_info->userid, + key_info->handle, key, + key_info->key_length); + } + break; + + case FSL_KEY_WRAP_UNWRAP: +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Unwrapping wrapped key\n"); +#endif + /* For now, disallow non-blocking calls. */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { + ret = FSL_RETURN_BAD_FLAG_S; + } else if (key == NULL) { + ret = FSL_RETURN_ERROR_S; + } else { + ret = unwrap(user_ctx, key_info, key); + } + break; + + default: + ret = FSL_RETURN_BAD_FLAG_S; + break; + } /* switch */ + + out: + if (slot_allocated && (ret != FSL_RETURN_OK_S)) { + fsl_shw_return_t scc_err; + + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + scc_err = do_system_keystore_slot_dealloc(user_ctx, + key_info->userid, + key_info->handle); + } else { + /* Key goes in user keystore */ + scc_err = keystore_slot_dealloc(key_info->keystore, + key_info->userid, key_info->handle); + } + + key_info->flags &= ~FSL_SKO_KEY_ESTABLISHED; + } + + SAH_SF_DESC_CLEAN(); + + return ret; +} /* fsl_shw_establish_key() */ + +/*! + * Wrap a key and retrieve the wrapped value. + * + * A wrapped key is a key that has been cryptographically obscured. It is + * only able to be used with #fsl_shw_establish_key(). + * + * This function will also release the key (see #fsl_shw_release_key()) so + * that it must be re-established before reuse. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * @param[out] covered_key The location to store the 48-octet wrapped key. + * (This size is based upon the maximum key size + * of 32 octets). + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + /* For now, only blocking mode calls are supported */ + if (user_ctx->flags & FSL_UCO_BLOCKING_MODE) { + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + ret = wrap(user_ctx, key_info, covered_key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Verify that a SW key info really belongs to a SW key */ + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + /* ret = FSL_RETURN_BAD_FLAG_S; + goto out;*/ + } + + /* Need to deallocate on successful extraction */ + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_dealloc(user_ctx, + key_info->userid, key_info->handle); + } else { + /* Key goes in user keystore */ + ret = keystore_slot_dealloc(key_info->keystore, + key_info->userid, key_info->handle); + } + /* Mark key not available in the flags */ + key_info->flags &= + ~(FSL_SKO_KEY_ESTABLISHED | FSL_SKO_KEY_PRESENT); + } + } + +out: + SAH_SF_DESC_CLEAN(); + + return ret; +} + +/*! + * De-establish a key so that it can no longer be accessed. + * + * The key will need to be re-established before it can again be used. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + do_system_keystore_slot_dealloc(user_ctx, + key_info->userid, + key_info->handle); + } else { + /* Key goes in user keystore */ + keystore_slot_dealloc(key_info->keystore, + key_info->userid, + key_info->handle); + } + key_info->flags &= ~(FSL_SKO_KEY_ESTABLISHED | + FSL_SKO_KEY_PRESENT); + } + +out: + SAH_SF_DESC_CLEAN(); + + return ret; +} + +fsl_shw_return_t fsl_shw_read_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, uint8_t * key) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + if (!(key_info->flags & FSL_SKO_KEY_ESTABLISHED) + || !(key_info->flags & FSL_SKO_KEY_SW_KEY)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + if (key_info->keystore == NULL) { + /* Key lives in system keystore */ + ret = do_system_keystore_slot_read(user_ctx, + key_info->userid, + key_info->handle, + key_info->key_length, key); + } else { + /* Key lives in user keystore */ + ret = keystore_slot_read(key_info->keystore, + key_info->userid, + key_info->handle, + key_info->key_length, key); + } + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} diff --git a/drivers/mxc/security/sahara2/include/adaptor.h b/drivers/mxc/security/sahara2/include/adaptor.h new file mode 100644 index 000000000000..d2cb35b21e4c --- /dev/null +++ b/drivers/mxc/security/sahara2/include/adaptor.h @@ -0,0 +1,113 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file adaptor.h +* +* @brief The Adaptor component provides an interface to the device +* driver. +* +* Intended to be used by the FSL SHW API, this can also be called directly +*/ + +#ifndef ADAPTOR_H +#define ADAPTOR_H + +#include <sahara.h> + +/*! + * Structure passed during user ioctl() call to submit request. + */ +typedef struct sah_dar { + sah_Desc *desc_addr; /*!< head of descriptor chain */ + uint32_t uco_flags; /*!< copy of fsl_shw_uco flags field */ + uint32_t uco_user_ref; /*!< copy of fsl_shw_uco user_ref */ + uint32_t result; /*!< result of descriptor chain request */ + struct sah_dar *next; /*!< for driver use */ +} sah_dar_t; + +/*! + * Structure passed during user ioctl() call to Register a user + */ +typedef struct sah_register { + uint32_t pool_size; /*!< max number of outstanding requests possible */ + uint32_t result; /*!< result of registration request */ +} sah_register_t; + +/*! + * Structure passed during ioctl() call to request SCC operation + */ +typedef struct scc_data { + uint32_t length; /*!< length of data */ + uint8_t *in; /*!< input data */ + uint8_t *out; /*!< output data */ + unsigned direction; /*!< encrypt or decrypt */ + fsl_shw_sym_mode_t crypto_mode; /*!< CBC or EBC */ + uint8_t *init_vector; /*!< initialization vector or NULL */ +} scc_data_t; + +/*! + * Structure passed during user ioctl() calls to manage stored keys and + * stored-key slots. + */ +typedef struct scc_slot_t { + uint64_t ownerid; /*!< Owner's id to check/set permissions */ + uint32_t key_length; /*!< Length of key */ + uint32_t slot; /*!< Slot to operation on, or returned slot + number. */ + uint8_t *key; /*!< User-memory pointer to key value */ + fsl_shw_return_t code; /*!< API return code from operation */ +} scc_slot_t; + +/* + * Structure passed during user ioctl() calls to manage data stored in secure + * partitions. + */ +typedef struct scc_region_t { + uint32_t partition_base; /*!< User virtual address of the + partition base. */ + uint32_t offset; /*!< Offset from the start of the + partition where the cleartext data + is located. */ + uint32_t length; /*!< Length of the region to be + operated on */ + uint8_t *black_data; /*!< User virtual address of any black + (encrypted) data. */ + fsl_shw_cypher_mode_t cypher_mode; /*!< Cypher mode to use in an encryt/ + decrypt operation. */ + uint32_t IV[4]; /*!< Intialization vector to use in an + encrypt/decrypt operation. */ + fsl_shw_return_t code; /*!< API return code from operation */ +} scc_region_t; + +/* + * Structure passed during user ioctl() calls to manage secure partitions. + */ +typedef struct scc_partition_info_t { + uint32_t user_base; /**< Userspace pointer to base of partition */ + uint32_t permissions; /**< Permissions to give the partition (only + used in call to _DROP_PERMS) */ + fsl_shw_partition_status_t status; /*!< Status of the partition */ +} scc_partition_info_t; + +fsl_shw_return_t adaptor_Exec_Descriptor_Chain(sah_Head_Desc * dar, + fsl_shw_uco_t * uco); +fsl_shw_return_t sah_get_results(sah_results * arg, fsl_shw_uco_t * uco); +fsl_shw_return_t sah_register(fsl_shw_uco_t * user_ctx); +fsl_shw_return_t sah_deregister(fsl_shw_uco_t * user_ctx); +fsl_shw_return_t get_capabilities(fsl_shw_uco_t * user_ctx, + fsl_shw_pco_t *capabilities); + +#endif /* ADAPTOR_H */ + +/* End of adaptor.h */ diff --git a/drivers/mxc/security/sahara2/include/diagnostic.h b/drivers/mxc/security/sahara2/include/diagnostic.h new file mode 100644 index 000000000000..57f84d4cbb05 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/diagnostic.h @@ -0,0 +1,116 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file diagnostic.h +* +* @brief Macros for outputting kernel and user space diagnostics. +*/ + +#ifndef DIAGNOSTIC_H +#define DIAGNOSTIC_H + +#ifndef __KERNEL__ /* linux flag */ +#include <stdio.h> +#endif +#include "fsl_platform.h" + +#if defined(FSL_HAVE_SAHARA2) || defined(FSL_HAVE_SAHARA4) +#define DEV_NAME "sahara" +#elif defined(FSL_HAVE_RNGA) || defined(FSL_HAVE_RNGB) || \ + defined(FSL_HAVE_RNGC) +#define DEV_NAME "shw" +#endif + +/*! +******************************************************************** +* @brief This macro logs diagnostic messages to stderr. +* +* @param diag String that must be logged, char *. +* +* @return void +* +*/ +//#if defined DIAG_SECURITY_FUNC || defined DIAG_ADAPTOR +#define LOG_DIAG(diag) \ +({ \ + const char* fname = strrchr(__FILE__, '/'); \ + \ + sah_Log_Diag(fname ? fname+1 : __FILE__, __LINE__, diag); \ +}) + +#ifdef __KERNEL__ + +#define LOG_DIAG_ARGS(fmt, ...) \ +({ \ + const char* fname = strrchr(__FILE__, '/'); \ + os_printk(KERN_ALERT "%s:%i: " fmt "\n", \ + fname ? fname+1 : __FILE__, \ + __LINE__, \ + __VA_ARGS__); \ +}) + +#else + +#define LOG_DIAG_ARGS(fmt, ...) \ +({ \ + const char* fname = strrchr(__FILE__, '/'); \ + printf("%s:%i: " fmt "\n", \ + fname ? fname+1 : __FILE__, \ + __LINE__, \ + __VA_ARGS__); \ +}) + +#ifndef __KERNEL__ +void sah_Log_Diag(char *source_name, int source_line, char *diag); +#endif +#endif /* if define DIAG_SECURITY_FUNC ... */ + +#ifdef __KERNEL__ +/*! +******************************************************************** +* @brief This macro logs kernel diagnostic messages to the kernel +* log. +* +* @param diag String that must be logged, char *. +* +* @return As for printf() +*/ +#if 0 +#if defined(DIAG_DRV_IF) || defined(DIAG_DRV_QUEUE) || \ + defined(DIAG_DRV_STATUS) || defined(DIAG_DRV_INTERRUPT) || \ + defined(DIAG_MEM) || defined(DIAG_SECURITY_FUNC) || defined(DIAG_ADAPTOR) +#endif +#endif + +#define LOG_KDIAG_ARGS(fmt, ...) \ +({ \ + os_printk (KERN_ALERT "%s (%s:%i): " fmt "\n", \ + DEV_NAME, strrchr(__FILE__, '/')+1, __LINE__, __VA_ARGS__); \ +}) + +#define LOG_KDIAG(diag) \ + os_printk (KERN_ALERT "%s (%s:%i): %s\n", \ + DEV_NAME, strrchr(__FILE__, '/')+1, __LINE__, diag); + +#define sah_Log_Diag(n, l, d) \ + os_printk(KERN_ALERT "%s:%i: %s\n", n, l, d) + +#else /* not KERNEL */ + +#define sah_Log_Diag(n, l, d) \ + printf("%s:%i: %s\n", n, l, d) + +#endif /* __KERNEL__ */ + +#endif /* DIAGNOSTIC_H */ diff --git a/drivers/mxc/security/sahara2/include/fsl_platform.h b/drivers/mxc/security/sahara2/include/fsl_platform.h new file mode 100644 index 000000000000..e7a6fd1718b2 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/fsl_platform.h @@ -0,0 +1,161 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_platform.h + * + * Header file to isolate code which might be platform-dependent + */ + +#ifndef FSL_PLATFORM_H +#define FSL_PLATFORM_H + +#ifdef __KERNEL__ +#include "portable_os.h" +#endif + +#if defined(FSL_PLATFORM_OTHER) + +/* Have Makefile or other method of setting FSL_HAVE_* flags */ + +#elif defined(CONFIG_ARCH_MX3) /* i.MX31 */ + +#define FSL_HAVE_SCC +#define FSL_HAVE_RTIC +#define FSL_HAVE_RNGA + +#elif defined(CONFIG_ARCH_MX21) + +#define FSL_HAVE_HAC +#define FSL_HAVE_RNGA +#define FSL_HAVE_SCC + +#elif defined(CONFIG_ARCH_MX25) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGB +#define FSL_HAVE_RTIC3 +#define FSL_HAVE_DRYICE + +#elif defined(CONFIG_ARCH_MX27) + +#define FSL_HAVE_SAHARA2 +#define SUBMIT_MULTIPLE_DARS +#define FSL_HAVE_RTIC +#define FSL_HAVE_SCC +#define ALLOW_LLO_DESCRIPTORS + +#elif defined(CONFIG_ARCH_MX35) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGC +#define FSL_HAVE_RTIC + +#elif defined(CONFIG_ARCH_MX37) + +#define FSL_HAVE_SCC2 +#define FSL_HAVE_RNGC +#define FSL_HAVE_RTIC2 +#define FSL_HAVE_SRTC + +#elif defined(CONFIG_ARCH_MX51) + +#define FSL_HAVE_SCC2 +#define FSL_HAVE_SAHARA4 +#define FSL_HAVE_RTIC3 +#define FSL_HAVE_SRTC +#define NO_RESEED_WORKAROUND +#define NEED_CTR_WORKAROUND +#define USE_S2_CCM_ENCRYPT_CHAIN +#define USE_S2_CCM_DECRYPT_CHAIN +#define ALLOW_LLO_DESCRIPTORS + +#elif defined(CONFIG_ARCH_MXC91131) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGC +#define FSL_HAVE_HAC + +#elif defined(CONFIG_ARCH_MXC91221) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGC +#define FSL_HAVE_RTIC2 + +#elif defined(CONFIG_ARCH_MXC91231) + +#define FSL_HAVE_SAHARA2 +#define FSL_HAVE_RTIC +#define FSL_HAVE_SCC +#define NO_OUTPUT_1K_CROSSING + +#elif defined(CONFIG_ARCH_MXC91311) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGC + +#elif defined(CONFIG_ARCH_MXC91314) + +#define FSL_HAVE_SCC +#define FSL_HAVE_SAHAR4 +#define FSL_HAVE_RTIC3 +#define NO_RESEED_WORKAROUND +#define NEED_CTR_WORKAROUND +#define USE_S2_CCM_ENCRYPT_CHAIN +#define USE_S2_CCM_DECRYPT_CHAIN +#define ALLOW_LLO_DESCRIPTORS + +#elif defined(CONFIG_ARCH_MXC91321) + +#define FSL_HAVE_SAHARA2 +#define FSL_HAVE_RTIC +#define FSL_HAVE_SCC +#define SCC_CLOCK_NOT_GATED +#define NO_OUTPUT_1K_CROSSING + +#elif defined(CONFIG_ARCH_MXC92323) + +#define FSL_HAVE_SCC2 +#define FSL_HAVE_SAHARA4 +#define FSL_HAVE_PKHA +#define FSL_HAVE_RTIC2 +#define NO_1K_CROSSING +#define NO_RESEED_WORKAROUND +#define NEED_CTR_WORKAROUND +#define USE_S2_CCM_ENCRYPT_CHAIN +#define USE_S2_CCM_DECRYPT_CHAIN +#define ALLOW_LLO_DESCRIPTORS + + +#elif defined(CONFIG_ARCH_MXC91331) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGA +#define FSL_HAVE_HAC +#define FSL_HAVE_RTIC + +#elif defined(CONFIG_8548) + +#define FSL_HAVE_SEC2x + +#elif defined(CONFIG_MPC8374) + +#define FSL_HAVE_SEC3x + +#else + +#error UNKNOWN_PLATFORM + +#endif /* platform checks */ + +#endif /* FSL_PLATFORM_H */ diff --git a/drivers/mxc/security/sahara2/include/fsl_shw.h b/drivers/mxc/security/sahara2/include/fsl_shw.h new file mode 100644 index 000000000000..8f0159bef71a --- /dev/null +++ b/drivers/mxc/security/sahara2/include/fsl_shw.h @@ -0,0 +1,2515 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * NOTE TO MAINTAINERS: Although this header file is *the* header file to be + * #include'd by FSL SHW programs, it does not itself make any definitions for + * the API. Instead, it uses the fsl_platform.h file and / or compiler + * environment variables to determine which actual driver header file to + * include. This allows different implementations to contain different + * implementations of the various objects, macros, etc., or even to change + * which functions are macros and which are not. + */ + +/*! + * @file fsl_shw.h + * + * @brief Definition of the Freescale Security Hardware API. + * + * See @ref index for an overview of the API. + */ + +/*! + * @if USE_MAINPAGE + * @mainpage Common API for Freescale Security Hardware (FSL SHW API) + * @endif + * + * @section intro_sec Introduction + * + * This is the interface definition for the Freescale Security Hardware API + * (FSL SHW API) for User Mode and Kernel Mode to access Freescale Security + * Hardware components for cryptographic acceleration. The API is intended to + * provide cross-platform access to security hardware components of Freescale. + * + * This documentation has not been approved, and should not be taken to + * mean anything definite about future direction. + * + * Some example code is provided to give some idea of usage of this API. + * + * Note: This first version has been defined around the capabilities of the + * Sahara2 cryptographic accelerator, and may be expanded in the future to + * provide support for other platforms. The Platform Capabilities Object is + * intended as a way to allow programs to adapt to different platforms. + * + * The i.MX25 is an example of a platform without a SAHARA but yet has + * capabilities supported by this API. These include #fsl_shw_get_random() and + * #fsl_shw_add_entropy(), and the use of Triple-DES (TDEA) cipher algorithm + * (with no checking of key parity supported) in ECB and CBC modes with @ref + * sym_sec. See also the @ref di_sec for information on key handling, and @ref + * td_sec for detection of Tamper Events. Only the random functions are + * available from user space on this platform. + * + * @section usr_ctx The User Context + * + * The User Context Object (#fsl_shw_uco_t) controls the interaction between + * the user program and the API. It is initialized as part of user + * registration (#fsl_shw_register_user()), and is part of every interaction + * thereafter. + * + * @section pf_sec Platform Capabilities + * + * Since this API is not tied to one specific type of hardware or even one + * given version of a given type of hardware, the platform capabilities object + * could be used by a portable program to make choices about using software + * instead of hardware for certain operations. + * + * See the #fsl_shw_pco_t, returned by #fsl_shw_get_capabilities(). + * + * @ref pcoops are provided to query its contents. + * + * + * @section sym_sec Symmetric-Key Encryption and Decryption + * + * Symmetric-Key encryption support is provided for the block cipher algorithms + * AES, DES, and Triple DES. Modes supported are #FSL_SYM_MODE_ECB, + * #FSL_SYM_MODE_CBC, and #FSL_SYM_MODE_CTR, though not necessarily all modes + * for all algorithms. There is also support for the stream cipher algorithm + * commonly known as ARC4. + * + * Encryption and decryption are performed by using the functions + * #fsl_shw_symmetric_encrypt() and #fsl_shw_symmetric_decrypt(), respectively. + * There are two objects which provide information about the operation of these + * functions. They are the #fsl_shw_sko_t, to provide key and algorithm + * information; and the #fsl_shw_scco_t, to provide (and store) initial context + * or counter value information. + * + * CCM is not supported by these functions. For information CCM support, see + * @ref cmb_sec. + * + * + * @section hash_sec Cryptographic Hashing + * + * Hashing is performed by fsl_shw_hash(). Control of the function is through + * flags in the #fsl_shw_hco_t. The algorithms which are + * supported are listed in #fsl_shw_hash_alg_t. + * + * The hashing function works on octet streams. If a user application needs to + * hash a bitstream, it will need to do its own padding of the last block. + * + * + * @section hmac_sec Hashed Message Authentication Codes + * + * An HMAC is a method of combining a hash and a key so that a message cannot + * be faked by a third party. + * + * The #fsl_shw_hmac() can be used by itself for one-shot or multi-step + * operations, or in combination with #fsl_shw_hmac_precompute() to provide the + * ability to compute and save the beginning hashes from a key one time, and + * then use #fsl_shw_hmac() to calculate an HMAC on each message as it is + * processed. + * + * The maximum key length which is directly supported by this API is 64 octets. + * If a longer key size is needed for HMAC, the user will have to hash the key + * and present the digest value as the key to be used by the HMAC functions. + * + * + * @section rnd_sec Random Numbers + * + * Support is available for acquiring random values from a + * cryptographically-strong random number generator. See + * #fsl_shw_get_random(). The function #fsl_shw_add_entropy() may be used to + * add entropy to the random number generator. + * + * + * @section cmb_sec Combined Cipher and Authentication + * + * Some schemes require that messages be encrypted and that they also have an + * authentication code associated with the message. The function + * #fsl_shw_gen_encrypt() will generate the authentication code and encrypt the + * message. + * + * Upon receipt of such a message, the message must be decrypted and the + * authentication code validated. The function + * #fsl_shw_auth_decrypt() will perform these steps. + * + * Only AES-CCM is supported. + * + * + * @section wrap_sec Wrapped Keys + * + * On platforms with a Secure Memory, the function #fsl_shw_establish_key() can + * be used to place a key into the System Keystore. This key then can be used + * directly by the cryptographic hardware. It later then be wrapped + * (cryptographically obscured) by #fsl_shw_extract_key() and stored for later + * use. If a software key (#FSL_SKO_KEY_SW_KEY) was established, then its + * value can be retrieved with a call to #fsl_shw_read_key(). + * + * The wrapping and unwrapping functions provide security against unauthorized + * use and detection of tampering. + * + * The functions can also be used with a User Keystore. + * + * @section smalloc_sec Secure Memory Allocation + * + * On platforms with multiple partitions of Secure Memory, the function + * #fsl_shw_smalloc() can be used to acquire a partition for private use. The + * function #fsl_shw_diminish_perms() can then be used to revoke specific + * permissions on the partition, and #fsl_shw_sfree() can be used to release the + * partition. + * + * @section keystore_sec User Keystore + * + * User Keystore functionality is defined in fsl_shw_keystore.h. See @ref + * user_keystore for details. This is not supported on platforms without SCC2. + * + * @section di_sec Hardware key-select extensions - DryIce + * + * Some platforms have a component called DryIce which allows the software to + * control which key will be used by the secure memory encryption hardware. + * The choices are the secret per-chip Fused (IIM) Key, an unknown, hardware- + * generated Random Key, a software-written Programmed Key, or the IIM Key in + * combination with one of the others. #fsl_shw_pco_check_pk_supported() can + * be used to determine whether this feature is available on the platform. + * The rest of this section will explain the symmetric ciphering and key + * operations which are available on such a platform. + * + * The function #fsl_shw_sko_init_pf_key() will set up a Secret Key Object to + * refer to one of the system's platform keys. All keys which reference a + * platform key must use this initialization function, including a user- + * provided key value. Keys which are intended for software encryption must + * use #fsl_shw_sko_init(). + * + * To change the setting of the Programmed Key of the DryIce module, + * #fsl_shw_establish_key() must be called with a platform key object of type + * #FSL_SHW_PF_KEY_PRG or #FSL_SHW_PF_KEY_IIM_PRG. The key will be go + * into the PK register of DryIce and not to the keystore. Any symmetric + * operation which references either #FSL_SHW_PF_KEY_PRG or + * #FSL_SHW_PF_KEY_IIM_PRG will use the current PK value (possibly modified by + * the secret fused IIM key). Before the Flatform Key can be changed, a call to + * #fsl_shw_release_key() or #fsl_shw_extract_key() must be made. Neither + * function will change the value in the PK registers, and further ciphering + * can take place. + * + * When #fsl_shw_establish_key() is called to change the PK value, a plaintext + * key can be passed in with the #FSL_KEY_WRAP_ACCEPT argument or a previously + * wrapped key can be passed in with the #FSL_KEY_WRAP_UNWRAP argument. If + * #FSL_KEY_WRAP_CREATE is passed in, then a random value will be loaded into + * the PK register. The PK value can be wrapped by a call to + * #fsl_shw_extract_key() for later use with the #FSL_KEY_WRAP_UNWRAP argument. + * + * As an alternative to using only the fused key for @ref wrap_sec, + * #fsl_shw_uco_set_wrap_key() can be used to select either the random key or + * the random key with the fused key as the key which will be used to protect + * the one-time value used to wrap the key. This allows for these + * wrapped keys to be dependent upon and therefore unrecoverable after a tamper + * event causes the erasure of the DryIce Random Key register. + * + * The software can request that the hardware generate a (new) Random Key for + * DryIce by calling #fsl_shw_gen_random_pf_key(). + * + * + * @section td_sec Device Tamper-Detection + * + * Some platforms have a component which can detect certain types of tampering + * with the hardware. #fsl_shw_read_tamper_event() API will allow the + * retrieval of the type of event which caused a tamper-detection failure. + * + */ + +/*! @defgroup glossary Glossary + * + * @li @b AES - Advanced Encryption Standard - An NIST-created block cipher + * originally knowns as Rijndael. + * @li @b ARC4 - ARCFOUR - An S-Box-based OFB mode stream cipher. + * @li @b CBC - Cipher-Block Chaining - Each encrypted block is XORed with the + * result of the previous block's encryption. + * @li @b CCM - A way of combining CBC and CTR to perform cipher and + * authentication. + * @li @b ciphertext - @a plaintext which has been encrypted in some fashion. + * @li @b context - Information on the state of a cryptographic operation, + * excluding any key. This could include IV, Counter Value, or SBox. + * @li @b CTR - A mode where a counter value is encrypted and then XORed with + * the data. After each block, the counter value is incremented. + * @li @b DES - Data Encryption Standard - An 8-octet-block cipher. + * @li @b ECB - Electronic Codebook - A straight encryption/decryption of the + * data. + * @li @b hash - A cryptographically strong one-way function performed on data. + * @li @b HMAC - Hashed Message Authentication Code - A key-dependent one-way + * hash result, used to verify authenticity of a message. The equation + * for an HMAC is hash((K + A) || hash((K + B) || msg)), where K is the + * key, A is the constant for the outer hash, B is the constant for the + * inner hash, and hash is the hashing function (MD5, SHA256, etc). + * @li @b IPAD - In an HMAC operation, the context generated by XORing the key + * with a constant and then hashing that value as the first block of the + * inner hash. + * @li @b IV - An "Initial Vector" or @a context for modes like CBC. + * @li @b MAC - A Message Authentication Code. HMAC, hashing, and CCM all + * produce a MAC. + * @li @b mode - A way of using a cryptographic algorithm. See ECB, CBC, etc. + * @li @b MD5 - Message Digest 5 - A one-way hash function. + * @li @b plaintext - Data which has not been encrypted, or has been decrypted + * from @a ciphertext. + * @li @b OPAD - In an HMAC operation, the context generated by XORing the key + * with a constant and then hashing that value as the first block of the + * outer hash. + * @li @b SHA - Secure Hash Algorithm - A one-way hash function. + * @li @b TDES - AKA @b 3DES - Triple Data Encryption Standard - A method of + * using two or three keys and DES to perform three operations (encrypt + * decrypt encrypt) to create a new algorithm. + * @li @b XOR - Exclusive-OR. A Boolean arithmetic function. + * @li @b Wrapped value - A (key) which has been encrypted into an opaque datum + * which cannot be unwrapped (decrypted) for use except by an authorized + * user. Once created, the key is never visible, but may be used for + * other cryptographic operations. + */ + +#ifndef FSL_SHW_H +#define FSL_SHW_H + +/* Set FSL_HAVE_* flags */ + +#include "fsl_platform.h" + +#ifndef API_DOC + +#if defined(FSL_HAVE_SAHARA2) || defined(FSL_HAVE_SAHARA4) + +#include "sahara.h" + +#else + +#if defined(FSL_HAVE_RNGA) || defined(FSL_HAVE_RNGB) || defined(FSL_HAVE_RNGC) + +#include "rng_driver.h" + +#else + +#error FSL_SHW_API_platform_not_recognized + +#endif + +#endif /* HAVE SAHARA */ + +#else /* API_DOC */ + +#include <inttypes.h> /* for uint32_t, etc. */ +#include <stdio.h> /* Mainly for definition of NULL !! */ + +/* These groups will appear in the order in which they are defined. */ + +/*! + * @defgroup strgrp Objects + * + * These objects are used to pass information into and out of the API. Through + * flags and other settings, they control the behavior of the @ref opfuns. + * + * They are manipulated and queried by use of the various access functions. + * There are different sets defined for each object. See @ref objman. + */ + +/*! + * @defgroup consgrp Enumerations and other Constants + * + * This collection of symbols comprise the values which can be passed into + * various functions to control how the API will work. + */ + +/*! @defgroup opfuns Operational Functions + * + * These functions request that the underlying hardware perform cryptographic + * operations. They are the heart of the API. + */ + +/****** Organization the Object Operations under one group ! **********/ +/*! @defgroup objman Object-Manipulation Operations + * + */ +/*! @addtogroup objman + @{ */ +/*! + * @defgroup pcoops Platform Context Object Operations + * + * The Platform Context object is "read-only", so only query operations are + * provided for it. It is returned by the #fsl_shw_get_capabilities() + * function. + */ + +/*! @defgroup ucoops User Context Operations + * + * These operations should be the only access to the #fsl_shw_uco_t + * type/struct, as the internal members of the object are subject to change. + * The #fsl_shw_uco_init() function must be called before any other use of the + * object. + */ + +/*! + * @defgroup rops Result Object Operations + * + * As the Result Object contains the result of one of the @ref opfuns. The + * manipulations provided are query-only. No initialization is needed for this + * object. + */ + +/*! + * @defgroup skoops Secret Key Object Operations + * + * These operations should be the only access to the #fsl_shw_sko_t + * type/struct, as the internal members of that object are subject to change. + */ + +/*! + * @defgroup ksoops Keystore Object Operations + * + * These operations should be the only access to the #fsl_shw_kso_t + * type/struct, as the internal members of that object are subject to change. + */ + +/*! + * @defgroup hcops Hash Context Object Operations + * + * These operations should be the only access to the #fsl_shw_hco_t + * type/struct, as the internal members of that object are subject to change. + */ + +/*! + * @defgroup hmcops HMAC Context Object Operations + * + * These operations should be the only access to the #fsl_shw_hmco_t + * type/struct, as the internal members of that object are subject to change. + */ + +/*! + * @defgroup sccops Symmetric Cipher Context Operations + * + * These operations should be the only access to the #fsl_shw_scco_t + * type/struct, as the internal members of that object are subject to change + */ + +/*! @defgroup accoops Authentication-Cipher Context Object Operations + * + * These functions operate on a #fsl_shw_acco_t. Their purpose is to set + * flags, fields, etc., in order to control the operation of + * #fsl_shw_gen_encrypt() and #fsl_shw_auth_decrypt(). + */ + + /* @} *//************ END GROUPING of Object Manipulations *****************/ + +/*! @defgroup miscfuns Miscellaneous Functions + * + * These functions are neither @ref opfuns nor @ref objman. Their behavior + * does not depend upon the flags in the #fsl_shw_uco_t, yet they may involve + * more interaction with the library and the kernel than simply querying an + * object. + */ + +/****************************************************************************** + * Enumerations + *****************************************************************************/ +/*! @addtogroup consgrp + @{ */ + +/*! + * Flags for the state of the User Context Object (#fsl_shw_uco_t). + * + * These flags describe how the @ref opfuns will operate. + */ +typedef enum fsl_shw_user_ctx_flags_t { + /*! + * API will block the caller until operation completes. The result will be + * available in the return code. If this is not set, user will have to get + * results using #fsl_shw_get_results(). + */ + FSL_UCO_BLOCKING_MODE, + /*! + * User wants callback (at the function specified with + * #fsl_shw_uco_set_callback()) when the operation completes. This flag is + * valid only if #FSL_UCO_BLOCKING_MODE is not set. + */ + FSL_UCO_CALLBACK_MODE, + /*! Do not free descriptor chain after driver (adaptor) finishes */ + FSL_UCO_SAVE_DESC_CHAIN, + /*! + * User has made at least one request with callbacks requested, so API is + * ready to handle others. + */ + FSL_UCO_CALLBACK_SETUP_COMPLETE, + /*! + * (virtual) pointer to descriptor chain is completely linked with physical + * (DMA) addresses, ready for the hardware. This flag should not be used + * by FSL SHW API programs. + */ + FSL_UCO_CHAIN_PREPHYSICALIZED, + /*! + * The user has changed the context but the changes have not been copied to + * the kernel driver. + */ + FSL_UCO_CONTEXT_CHANGED, + /*! Internal Use. This context belongs to a user-mode API user. */ + FSL_UCO_USERMODE_USER, +} fsl_shw_user_ctx_flags_t; + +/*! + * Return code for FSL_SHW library. + * + * These codes may be returned from a function call. In non-blocking mode, + * they will appear as the status in a Result Object. + */ +typedef enum fsl_shw_return_t { + /*! + * No error. As a function return code in Non-blocking mode, this may + * simply mean that the operation was accepted for eventual execution. + */ + FSL_RETURN_OK_S = 0, + /*! Failure for non-specific reason. */ + FSL_RETURN_ERROR_S, + /*! + * Operation failed because some resource was not able to be allocated. + */ + FSL_RETURN_NO_RESOURCE_S, + /*! Crypto algorithm unrecognized or improper. */ + FSL_RETURN_BAD_ALGORITHM_S, + /*! Crypto mode unrecognized or improper. */ + FSL_RETURN_BAD_MODE_S, + /*! Flag setting unrecognized or inconsistent. */ + FSL_RETURN_BAD_FLAG_S, + /*! Improper or unsupported key length for algorithm. */ + FSL_RETURN_BAD_KEY_LENGTH_S, + /*! Improper parity in a (DES, TDES) key. */ + FSL_RETURN_BAD_KEY_PARITY_S, + /*! + * Improper or unsupported data length for algorithm or internal buffer. + */ + FSL_RETURN_BAD_DATA_LENGTH_S, + /*! Authentication / Integrity Check code check failed. */ + FSL_RETURN_AUTH_FAILED_S, + /*! A memory error occurred. */ + FSL_RETURN_MEMORY_ERROR_S, + /*! An error internal to the hardware occurred. */ + FSL_RETURN_INTERNAL_ERROR_S, + /*! ECC detected Point at Infinity */ + FSL_RETURN_POINT_AT_INFINITY_S, + /*! ECC detected No Point at Infinity */ + FSL_RETURN_POINT_NOT_AT_INFINITY_S, + /*! GCD is One */ + FSL_RETURN_GCD_IS_ONE_S, + /*! GCD is not One */ + FSL_RETURN_GCD_IS_NOT_ONE_S, + /*! Candidate is Prime */ + FSL_RETURN_PRIME_S, + /*! Candidate is not Prime */ + FSL_RETURN_NOT_PRIME_S, + /*! N register loaded improperly with even value */ + FSL_RETURN_EVEN_MODULUS_ERROR_S, + /*! Divisor is zero. */ + FSL_RETURN_DIVIDE_BY_ZERO_ERROR_S, + /*! Bad Exponent or Scalar value for Point Multiply */ + FSL_RETURN_BAD_EXPONENT_ERROR_S, + /*! RNG hardware problem. */ + FSL_RETURN_OSCILLATOR_ERROR_S, + /*! RNG hardware problem. */ + FSL_RETURN_STATISTICS_ERROR_S, +} fsl_shw_return_t; + +/*! + * Algorithm Identifier. + * + * Selection of algorithm will determine how large the block size of the + * algorithm is. Context size is the same length unless otherwise specified. + * Selection of algorithm also affects the allowable key length. + */ +typedef enum fsl_shw_key_alg_t { + FSL_KEY_ALG_HMAC, /*!< Key will be used to perform an HMAC. Key + size is 1 to 64 octets. Block size is 64 + octets. */ + FSL_KEY_ALG_AES, /*!< Advanced Encryption Standard (Rijndael). + Block size is 16 octets. Key size is 16 + octets. (The single choice of key size is a + Sahara platform limitation.) */ + FSL_KEY_ALG_DES, /*!< Data Encryption Standard. Block size is + 8 octets. Key size is 8 octets. */ + FSL_KEY_ALG_TDES, /*!< 2- or 3-key Triple DES. Block size is 8 + octets. Key size is 16 octets for 2-key + Triple DES, and 24 octets for 3-key. */ + FSL_KEY_ALG_ARC4 /*!< ARC4. No block size. Context size is 259 + octets. Allowed key size is 1-16 octets. + (The choices for key size are a Sahara + platform limitation.) */ +} fsl_shw_key_alg_t; + +/*! + * Mode selector for Symmetric Ciphers. + * + * The selection of mode determines how a cryptographic algorithm will be + * used to process the plaintext or ciphertext. + * + * For all modes which are run block-by-block (that is, all but + * #FSL_SYM_MODE_STREAM), any partial operations must be performed on a text + * length which is multiple of the block size. Except for #FSL_SYM_MODE_CTR, + * these block-by-block algorithms must also be passed a total number of octets + * which is a multiple of the block size. + * + * In modes which require that the total number of octets of data be a multiple + * of the block size (#FSL_SYM_MODE_ECB and #FSL_SYM_MODE_CBC), and the user + * has a total number of octets which are not a multiple of the block size, the + * user must perform any necessary padding to get to the correct data length. + */ +typedef enum fsl_shw_sym_mode_t { + /*! + * Stream. There is no associated block size. Any request to process data + * may be of any length. This mode is only for ARC4 operations, and is + * also the only mode used for ARC4. + */ + FSL_SYM_MODE_STREAM, + + /*! + * Electronic Codebook. Each block of data is encrypted/decrypted. The + * length of the data stream must be a multiple of the block size. This + * mode may be used for DES, 3DES, and AES. The block size is determined + * by the algorithm. + */ + FSL_SYM_MODE_ECB, + /*! + * Cipher-Block Chaining. Each block of data is encrypted/decrypted and + * then "chained" with the previous block by an XOR function. Requires + * context to start the XOR (previous block). This mode may be used for + * DES, 3DES, and AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CBC, + /*! + * Counter. The counter is encrypted, then XORed with a block of data. + * The counter is then incremented (using modulus arithmetic) for the next + * block. The final operation may be non-multiple of block size. This mode + * may be used for AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CTR, +} fsl_shw_sym_mode_t; + +/*! + * Algorithm selector for Cryptographic Hash functions. + * + * Selection of algorithm determines how large the context and digest will be. + * Context is the same size as the digest (resulting hash), unless otherwise + * specified. + */ +typedef enum fsl_shw_hash_alg_t { + FSL_HASH_ALG_MD5, /*!< MD5 algorithm. Digest is 16 octets. */ + FSL_HASH_ALG_SHA1, /*!< SHA-1 (aka SHA or SHA-160) algorithm. + Digest is 20 octets. */ + FSL_HASH_ALG_SHA224, /*!< SHA-224 algorithm. Digest is 28 octets, + though context is 32 octets. */ + FSL_HASH_ALG_SHA256 /*!< SHA-256 algorithm. Digest is 32 + octets. */ +} fsl_shw_hash_alg_t; + +/*! + * The type of Authentication-Cipher function which will be performed. + */ +typedef enum fsl_shw_acc_mode_t { + /*! + * CBC-MAC for Counter. Requires context and modulus. Final operation may + * be non-multiple of block size. This mode may be used for AES. + */ + FSL_ACC_MODE_CCM, + /*! + * SSL mode. Not supported. Combines HMAC and encrypt (or decrypt). + * Needs one key object for encryption, another for the HMAC. The usual + * hashing and symmetric encryption algorithms are supported. + */ + FSL_ACC_MODE_SSL, +} fsl_shw_acc_mode_t; + +/*! + * The operation which controls the behavior of #fsl_shw_establish_key(). + * + * These values are passed to #fsl_shw_establish_key(). + */ +typedef enum fsl_shw_key_wrap_t { + FSL_KEY_WRAP_CREATE, /*!< Generate a key from random values. */ + FSL_KEY_WRAP_ACCEPT, /*!< Use the provided clear key. */ + FSL_KEY_WRAP_UNWRAP /*!< Unwrap a previously wrapped key. */ +} fsl_shw_key_wrap_t; + +/* REQ-S2LRD-PINTFC-COA-HCO-001 */ +/*! + * Flags which control a Hash operation. + * + * These may be combined by ORing them together. See #fsl_shw_hco_set_flags() + * and #fsl_shw_hco_clear_flags(). + */ +typedef enum fsl_shw_hash_ctx_flags_t { + FSL_HASH_FLAGS_INIT = 1, /*!< Context is empty. Hash is started + from scratch, with a message-processed + count of zero. */ + FSL_HASH_FLAGS_SAVE = 2, /*!< Retrieve context from hardware after + hashing. If used with the + #FSL_HASH_FLAGS_FINALIZE flag, the final + digest value will be saved in the + object. */ + FSL_HASH_FLAGS_LOAD = 4, /*!< Place context into hardware before + hashing. */ + FSL_HASH_FLAGS_FINALIZE = 8, /*!< PAD message and perform final digest + operation. If user message is + pre-padded, this flag should not be + used. */ +} fsl_shw_hash_ctx_flags_t; + +/*! + * Flags which control an HMAC operation. + * + * These may be combined by ORing them together. See #fsl_shw_hmco_set_flags() + * and #fsl_shw_hmco_clear_flags(). + */ +typedef enum fsl_shw_hmac_ctx_flags_t { + FSL_HMAC_FLAGS_INIT = 1, /*!< Message context is empty. HMAC is + started from scratch (with key) or from + precompute of inner hash, depending on + whether + #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT is + set. */ + FSL_HMAC_FLAGS_SAVE = 2, /*!< Retrieve ongoing context from hardware + after hashing. If used with the + #FSL_HMAC_FLAGS_FINALIZE flag, the final + digest value (HMAC) will be saved in the + object. */ + FSL_HMAC_FLAGS_LOAD = 4, /*!< Place ongoing context into hardware + before hashing. */ + FSL_HMAC_FLAGS_FINALIZE = 8, /*!< PAD message and perform final HMAC + operations of inner and outer hashes. */ + FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT = 16 /*!< This means that the context + contains precomputed inner and outer + hash values. */ +} fsl_shw_hmac_ctx_flags_t; + +/*! + * Flags to control use of the #fsl_shw_scco_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_scco_set_flags() and #fsl_shw_scco_clear_flags() + */ +typedef enum fsl_shw_sym_ctx_flags_t { + /*! + * Context is empty. In ARC4, this means that the S-Box needs to be + * generated from the key. In #FSL_SYM_MODE_CBC mode, this allows an IV of + * zero to be specified. In #FSL_SYM_MODE_CTR mode, it means that an + * initial CTR value of zero is desired. + */ + FSL_SYM_CTX_INIT = 1, + /*! + * Load context from object into hardware before running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_LOAD = 2, + /*! + * Save context from hardware into object after running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_SAVE = 4, + /*! + * Context (SBox) is to be unwrapped and wrapped on each use. + * This flag is unsupported. + * */ + FSL_SYM_CTX_PROTECT = 8, +} fsl_shw_sym_ctx_flags_t; + +/*! + * Flags which describe the state of the #fsl_shw_sko_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_sko_set_flags() and #fsl_shw_sko_clear_flags() + */ +typedef enum fsl_shw_key_flags_t { + FSL_SKO_KEY_IGNORE_PARITY = 1, /*!< If algorithm is DES or 3DES, do not + validate the key parity bits. */ + FSL_SKO_KEY_PRESENT = 2, /*!< Clear key is present in the object. */ + FSL_SKO_KEY_ESTABLISHED = 4, /*!< Key has been established for use. This + feature is not available for all + platforms, nor for all algorithms and + modes. */ + FSL_SKO_KEY_SW_KEY = 8, /*!< This key is for software use, and can + be copied out of a keystore by its owner. + The default is that they key is available + only for hardware (or security driver) + use. */ +} fsl_shw_key_flags_t; + +/*! + * Type of value which is associated with an established key. + */ +typedef uint64_t key_userid_t; + +/*! + * Flags which describe the state of the #fsl_shw_acco_t. + * + * The @a FSL_ACCO_CTX_INIT and @a FSL_ACCO_CTX_FINALIZE flags, when used + * together, provide for a one-shot operation. + */ +typedef enum fsl_shw_auth_ctx_flags_t { + FSL_ACCO_CTX_INIT = 1, /*!< Initialize Context(s) */ + FSL_ACCO_CTX_LOAD = 2, /*!< Load intermediate context(s). + This flag is unsupported. */ + FSL_ACCO_CTX_SAVE = 4, /*!< Save intermediate context(s). + This flag is unsupported. */ + FSL_ACCO_CTX_FINALIZE = 8, /*!< Create MAC during this operation. */ + FSL_ACCO_NIST_CCM = 16, /*!< Formatting of CCM input data is + performed by calls to + #fsl_shw_ccm_nist_format_ctr_and_iv() and + #fsl_shw_ccm_nist_update_ctr_and_iv(). */ +} fsl_shw_auth_ctx_flags_t; + +/*! + * Modulus Selector for CTR modes. + * + * The incrementing of the Counter value may be modified by a modulus. If no + * modulus is needed or desired for AES, use #FSL_CTR_MOD_128. + */ +typedef enum fsl_shw_ctr_mod_t { + FSL_CTR_MOD_8, /*!< Run counter with modulus of 2^8. */ + FSL_CTR_MOD_16, /*!< Run counter with modulus of 2^16. */ + FSL_CTR_MOD_24, /*!< Run counter with modulus of 2^24. */ + FSL_CTR_MOD_32, /*!< Run counter with modulus of 2^32. */ + FSL_CTR_MOD_40, /*!< Run counter with modulus of 2^40. */ + FSL_CTR_MOD_48, /*!< Run counter with modulus of 2^48. */ + FSL_CTR_MOD_56, /*!< Run counter with modulus of 2^56. */ + FSL_CTR_MOD_64, /*!< Run counter with modulus of 2^64. */ + FSL_CTR_MOD_72, /*!< Run counter with modulus of 2^72. */ + FSL_CTR_MOD_80, /*!< Run counter with modulus of 2^80. */ + FSL_CTR_MOD_88, /*!< Run counter with modulus of 2^88. */ + FSL_CTR_MOD_96, /*!< Run counter with modulus of 2^96. */ + FSL_CTR_MOD_104, /*!< Run counter with modulus of 2^104. */ + FSL_CTR_MOD_112, /*!< Run counter with modulus of 2^112. */ + FSL_CTR_MOD_120, /*!< Run counter with modulus of 2^120. */ + FSL_CTR_MOD_128 /*!< Run counter with modulus of 2^128. */ +} fsl_shw_ctr_mod_t; + +/*! + * Permissions flags for Secure Partitions + * + * They currently map directly to the SCC2 hardware values, but this is not + * guarinteed behavior. + */ +typedef enum fsl_shw_permission_t { +/*! SCM Access Permission: Do not zeroize/deallocate partition on SMN Fail state */ + FSL_PERM_NO_ZEROIZE, +/*! SCM Access Permission: Enforce trusted key read in */ + FSL_PERM_TRUSTED_KEY_READ, +/*! SCM Access Permission: Ignore Supervisor/User mode in permission determination */ + FSL_PERM_HD_S, +/*! SCM Access Permission: Allow Read Access to Host Domain */ + FSL_PERM_HD_R, +/*! SCM Access Permission: Allow Write Access to Host Domain */ + FSL_PERM_HD_W, +/*! SCM Access Permission: Allow Execute Access to Host Domain */ + FSL_PERM_HD_X, +/*! SCM Access Permission: Allow Read Access to Trusted Host Domain */ + FSL_PERM_TH_R, +/*! SCM Access Permission: Allow Write Access to Trusted Host Domain */ + FSL_PERM_TH_W, +/*! SCM Access Permission: Allow Read Access to Other/World Domain */ + FSL_PERM_OT_R, +/*! SCM Access Permission: Allow Write Access to Other/World Domain */ + FSL_PERM_OT_W, +/*! SCM Access Permission: Allow Execute Access to Other/World Domain */ + FSL_PERM_OT_X, +} fsl_shw_permission_t; + +/*! + * Select the cypher mode to use for partition cover/uncover operations. + * + * They currently map directly to the values used in the SCC2 driver, but this + * is not guarinteed behavior. + */ +typedef enum fsl_shw_cypher_mode_t { + FSL_SHW_CYPHER_MODE_ECB, /*!< ECB mode */ + FSL_SHW_CYPHER_MODE_CBC, /*!< CBC mode */ +} fsl_shw_cypher_mode_t; + +/*! + * Which platform key should be presented for cryptographic use. + */ +typedef enum fsl_shw_pf_key_t { + FSL_SHW_PF_KEY_IIM, /*!< Present fused IIM key */ + FSL_SHW_PF_KEY_PRG, /*!< Present Program key */ + FSL_SHW_PF_KEY_IIM_PRG, /*!< Present IIM ^ Program key */ + FSL_SHW_PF_KEY_IIM_RND, /*!< Present Random key */ + FSL_SHW_PF_KEY_RND, /*!< Present IIM ^ Random key */ +} fsl_shw_pf_key_t; + +/*! + * The various security tamper events + */ +typedef enum fsl_shw_tamper_t { + FSL_SHW_TAMPER_NONE, /*!< No error detected */ + FSL_SHW_TAMPER_WTD, /*!< wire-mesh tampering det */ + FSL_SHW_TAMPER_ETBD, /*!< ext tampering det: input B */ + FSL_SHW_TAMPER_ETAD, /*!< ext tampering det: input A */ + FSL_SHW_TAMPER_EBD, /*!< external boot detected */ + FSL_SHW_TAMPER_SAD, /*!< security alarm detected */ + FSL_SHW_TAMPER_TTD, /*!< temperature tampering det */ + FSL_SHW_TAMPER_CTD, /*!< clock tampering det */ + FSL_SHW_TAMPER_VTD, /*!< voltage tampering det */ + FSL_SHW_TAMPER_MCO, /*!< monotonic counter overflow */ + FSL_SHW_TAMPER_TCO, /*!< time counter overflow */ +} fsl_shw_tamper_t; + +/*! @} *//* consgrp */ + +/****************************************************************************** + * Data Structures + *****************************************************************************/ +/*! @addtogroup strgrp + @{ */ + +/* REQ-S2LRD-PINTFC-COA-IBO-001 */ +/*! + * Application Initialization Object + * + * This object, the operations on it, and its interaction with the driver are + * TBD. + */ +typedef struct fsl_sho_ibo_t { +} fsl_sho_ibo_t; + +/* REQ-S2LRD-PINTFC-COA-UCO-001 */ +/*! + * User Context Object + * + * This object must be initialized by a call to #fsl_shw_uco_init(). It must + * then be passed to #fsl_shw_register_user() before it can be used in any + * calls besides those in @ref ucoops. + * + * It contains the user's configuration for the API, for instance whether an + * operation should block, or instead should call back the user upon completion + * of the operation. + * + * See @ref ucoops for further information. + */ +typedef struct fsl_shw_uco_t { /* fsl_shw_user_context_object */ +} fsl_shw_uco_t; + +/* REQ-S2LRD-PINTFC-API-GEN-006 ?? */ +/*! + * Result Object + * + * This object will contain success and failure information about a specific + * cryptographic request which has been made. + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref rops. + */ +typedef struct fsl_shw_result_t { /* fsl_shw_result */ +} fsl_shw_result_t; + +/*! + * Keystore Object + * + * This object holds the context of a user keystore, including the functions + * that define the interface and pointers to where the key data is stored. The + * user must supply a set of functions to handle keystore management, including + * slot allocation, deallocation, etc. A default keystore manager is provided + * as part of the API. + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref ksoops. + */ +typedef struct fsl_shw_kso_t { /* fsl_shw_keystore_object */ +} fsl_shw_kso_t; + +/* REQ-S2LRD-PINTFC-COA-SKO-001 */ +/*! + * Secret Key Object + * + * This object contains a key for a cryptographic operation, and information + * about its current state, its intended usage, etc. It may instead contain + * information about a protected key, or an indication to use a platform- + * specific secret key. + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref skoops. + */ +typedef struct fsl_shw_sko_t { /* fsl_shw_secret_key_object */ +} fsl_shw_sko_t; + +/* REQ-S2LRD-PINTFC-COA-CO-001 */ +/*! + * Platform Capabilities Object + * + * This object will contain information about the cryptographic features of the + * platform which the program is running on. + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. + * + * See @ref pcoops. + */ +typedef struct fsl_shw_pco_t { /* fsl_shw_platform_capabilities_object */ +} fsl_shw_pco_t; + +/* REQ-S2LRD-PINTFC-COA-HCO-001 */ +/*! + * Hash Context Object + * + * This object contains information to control hashing functions. + + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref hcops. + */ +typedef struct fsl_shw_hco_t { /* fsl_shw_hash_context_object */ +} fsl_shw_hco_t; + +/*! + * HMAC Context Object + * + * This object contains information to control HMAC functions. + + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref hmcops. + */ +typedef struct fsl_shw_hmco_t { /* fsl_shw_hmac_context_object */ +} fsl_shw_hmco_t; + +/* REQ-S2LRD-PINTFC-COA-SCCO-001 */ +/*! + * Symmetric Cipher Context Object + * + * This object contains information to control Symmetric Ciphering encrypt and + * decrypt functions in #FSL_SYM_MODE_STREAM (ARC4), #FSL_SYM_MODE_ECB, + * #FSL_SYM_MODE_CBC, and #FSL_SYM_MODE_CTR modes and the + * #fsl_shw_symmetric_encrypt() and #fsl_shw_symmetric_decrypt() functions. + * CCM mode is controlled with the #fsl_shw_acco_t object. + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref sccops. + */ +typedef struct fsl_shw_scco_t { /* fsl_shw_symmetric_cipher_context_object */ +} fsl_shw_scco_t; + +/*! + * Authenticate-Cipher Context Object + + * An object for controlling the function of, and holding information about, + * data for the authenticate-cipher functions, #fsl_shw_gen_encrypt() and + * #fsl_shw_auth_decrypt(). + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref + * accoops. + */ +typedef struct fsl_shw_acco_t { /* fsl_shw_authenticate_cipher_context_object */ +} fsl_shw_acco_t; + /*! @} *//* strgrp */ + +/****************************************************************************** + * Access Macros for Objects + *****************************************************************************/ +/*! @addtogroup pcoops + @{ */ + +/*! + * Get FSL SHW API version + * + * @param pc_info The Platform Capabilities Object to query. + * @param[out] major A pointer to where the major version + * of the API is to be stored. + * @param[out] minor A pointer to where the minor version + * of the API is to be stored. + */ +void fsl_shw_pco_get_version(const fsl_shw_pco_t * pc_info, + uint32_t * major, uint32_t * minor); + +/*! + * Get underlying driver version. + * + * @param pc_info The Platform Capabilities Object to query. + * @param[out] major A pointer to where the major version + * of the driver is to be stored. + * @param[out] minor A pointer to where the minor version + * of the driver is to be stored. + */ +void fsl_shw_pco_get_driver_version(const fsl_shw_pco_t * pc_info, + uint32_t * major, uint32_t * minor); + +/*! + * Get list of symmetric algorithms supported. + * + * @param pc_info The Platform Capabilities Object to query. + * @param[out] algorithms A pointer to where to store the location of + * the list of algorithms. + * @param[out] algorithm_count A pointer to where to store the number of + * algorithms in the list at @a algorithms. + */ +void fsl_shw_pco_get_sym_algorithms(const fsl_shw_pco_t * pc_info, + fsl_shw_key_alg_t * algorithms[], + uint8_t * algorithm_count); + +/*! + * Get list of symmetric modes supported. + * + * @param pc_info The Platform Capabilities Object to query. + * @param[out] modes A pointer to where to store the location of + * the list of modes. + * @param[out] mode_count A pointer to where to store the number of + * algorithms in the list at @a modes. + */ +void fsl_shw_pco_get_sym_modes(const fsl_shw_pco_t * pc_info, + fsl_shw_sym_mode_t * modes[], + uint8_t * mode_count); + +/*! + * Get list of hash algorithms supported. + * + * @param pc_info The Platform Capabilities Object to query. + * @param[out] algorithms A pointer which will be set to the list of + * algorithms. + * @param[out] algorithm_count The number of algorithms in the list at @a + * algorithms. + */ +void fsl_shw_pco_get_hash_algorithms(const fsl_shw_pco_t * pc_info, + fsl_shw_hash_alg_t * algorithms[], + uint8_t * algorithm_count); + +/*! + * Determine whether the combination of a given symmetric algorithm and a given + * mode is supported. + * + * @param pc_info The Platform Capabilities Object to query. + * @param algorithm A Symmetric Cipher algorithm. + * @param mode A Symmetric Cipher mode. + * + * @return 0 if combination is not supported, non-zero if supported. + */ +int fsl_shw_pco_check_sym_supported(const fsl_shw_pco_t * pc_info, + fsl_shw_key_alg_t algorithm, + fsl_shw_sym_mode_t mode); + +/*! + * Determine whether a given Encryption-Authentication mode is supported. + * + * @param pc_info The Platform Capabilities Object to query. + * @param mode The Authentication mode. + * + * @return 0 if mode is not supported, non-zero if supported. + */ +int fsl_shw_pco_check_auth_supported(const fsl_shw_pco_t * pc_info, + fsl_shw_acc_mode_t mode); + +/*! + * Determine whether Black Keys (key establishment / wrapping) is supported. + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return 0 if wrapping is not supported, non-zero if supported. + */ +int fsl_shw_pco_check_black_key_supported(const fsl_shw_pco_t * pc_info); + +/*! + * Get FSL SHW SCC driver version + * + * @param pc_info The Platform Capabilities Object to query. + * @param[out] major A pointer to where the major version + * of the SCC driver is to be stored. + * @param[out] minor A pointer to where the minor version + * of the SCC driver is to be stored. + */ +void fsl_shw_pco_get_scc_driver_version(const fsl_shw_pco_t * pc_info, + uint32_t * major, uint32_t * minor); + +/*! + * Get SCM hardware version + * + * @param pc_info The Platform Capabilities Object to query. + * @return The SCM hardware version + */ +uint32_t fsl_shw_pco_get_scm_version(const fsl_shw_pco_t * pc_info); + +/*! + * Get SMN hardware version + * + * @param pc_info The Platform Capabilities Object to query. + * @return The SMN hardware version + */ +uint32_t fsl_shw_pco_get_smn_version(const fsl_shw_pco_t * pc_info); + +/*! + * Get the size of an SCM block, in bytes + * + * @param pc_info The Platform Capabilities Object to query. + * @return The size of an SCM block, in bytes. + */ +uint32_t fsl_shw_pco_get_scm_block_size(const fsl_shw_pco_t * pc_info); + +/*! + * Get size of Black and Red RAM memory + * + * @param pc_info The Platform Capabilities Object to query. + * @param[out] black_size A pointer to where the size of the Black RAM, in + * blocks, is to be placed. + * @param[out] red_size A pointer to where the size of the Red RAM, in + * blocks, is to be placed. + */ +void fsl_shw_pco_get_smn_size(const fsl_shw_pco_t * pc_info, + uint32_t * black_size, uint32_t * red_size); + +/*! + * Determine whether Secure Partitions are supported + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return 0 if secure partitions are not supported, non-zero if supported. + */ +int fsl_shw_pco_check_spo_supported(const fsl_shw_pco_t * pc_info); + +/*! + * Get the size of a Secure Partitions + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return Partition size, in bytes. 0 if Secure Partitions not supported. + */ +uint32_t fsl_shw_pco_get_spo_size_bytes(const fsl_shw_pco_t * pc_info); + +/*! + * Get the number of Secure Partitions on this platform + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return Number of partitions. 0 if Secure Partitions not supported. Note + * that this returns the total number of partitions, though + * not all may be available to the user. + */ +uint32_t fsl_shw_pco_get_spo_count(const fsl_shw_pco_t * pc_info); + +/*! + * Determine whether Platform Key features are available + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return 1 if Programmed Key features are available, otherwise zero. + */ +int fsl_shw_pco_check_pk_supported(const fsl_shw_pco_t * pc_info); + +/*! + * Determine whether Software Key features are available + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return 1 if Software key features are available, otherwise zero. + */ +int fsl_shw_pco_check_sw_keys_supported(const fsl_shw_pco_t * pc_info); + +/*! @} *//* pcoops */ + +/*! @addtogroup ucoops + @{ */ + +/*! + * Initialize a User Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the User Context Object to initial values, and set the size + * of the results pool. The mode will be set to a default of + * #FSL_UCO_BLOCKING_MODE. + * + * When using non-blocking operations, this sets the maximum number of + * operations which can be outstanding. This number includes the counts of + * operations waiting to start, operation(s) being performed, and results which + * have not been retrieved. + * + * Changes to this value are ignored once user registration has completed. It + * should be set to 1 if only blocking operations will ever be performed. + * + * @param user_ctx The User Context object to operate on. + * @param pool_size The maximum number of operations which can be + * outstanding. + */ +void fsl_shw_uco_init(fsl_shw_uco_t * user_ctx, uint16_t pool_size); + +/*! + * Set the User Reference for the User Context. + * + * @param user_ctx The User Context object to operate on. + * @param reference A value which will be passed back with a result. + */ +void fsl_shw_uco_set_reference(fsl_shw_uco_t * user_ctx, uint32_t reference); + +/*! + * Set the callback routine for the User Context. + * + * Note that the callback routine may be called when no results are available, + * and possibly even when no requests are outstanding. + * + * + * @param user_ctx The User Context object to operate on. + * @param callback_fn The function the API will invoke when an operation + * completes. + */ +void fsl_shw_uco_set_callback(fsl_shw_uco_t * user_ctx, + void (*callback_fn) (fsl_shw_uco_t * uco)); + +/*! + * Set flags in the User Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param user_ctx The User Context object to operate on. + * @param flags ORed values from #fsl_shw_user_ctx_flags_t. + */ +void fsl_shw_uco_set_flags(fsl_shw_uco_t * user_ctx, uint32_t flags); + +/*! + * Clear flags in the User Context. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param user_ctx The User Context object to operate on. + * @param flags ORed values from #fsl_shw_user_ctx_flags_t. + */ +void fsl_shw_uco_clear_flags(fsl_shw_uco_t * user_ctx, uint32_t flags); + +/*! + * Select a key for the key-wrap key for key wrapping/unwrapping + * + * Without a call to this function, default is FSL_SHW_PF_KEY_IIM. The wrap + * key is used to encrypt and decrypt the per-key random secret which is used + * to calculate the key which will encrypt/decrypt the user's key. + * + * @param user_ctx The User Context object to operate on. + * @param pf_key Which key to use. Valid choices are + * #FSL_SHW_PF_KEY_IIM, #FSL_SHW_PF_KEY_RND, and + * #FSL_SHW_PF_KEY_IIM_RND. + */ +void fsl_shw_uco_set_wrap_key(fsl_shw_uco_t * user_ctx, + fsl_shw_pf_key_t pf_key); + + /*! @} *//* ucoops */ + +/*! @addtogroup rops + @{ */ + +/*! + * Retrieve the status code from a Result Object. + * + * @param result The result object to query. + * + * @return The status of the request. + */ +fsl_shw_return_t fsl_shw_ro_get_status(fsl_shw_result_t * result); + +/*! + * Retrieve the reference value from a Result Object. + * + * @param result The result object to query. + * + * @return The reference associated with the request. + */ +uint32_t fsl_shw_ro_get_reference(fsl_shw_result_t * result); + + /* @} *//* rops */ + +/*! @addtogroup skoops + @{ */ + +/*! + * Initialize a Secret Key Object. + * + * This function or #fsl_shw_sko_init_pf_key() must be called before performing + * any other operation with the Object. + * + * @param key_info The Secret Key Object to be initialized. + * @param algorithm DES, AES, etc. + * + */ +void fsl_shw_sko_init(fsl_shw_sko_t * key_info, fsl_shw_key_alg_t algorithm); + +/*! + * Initialize a Secret Key Object to use a Platform Key register. + * + * This function or #fsl_shw_sko_init() must be called before performing any + * other operation with the Object. #fsl_shw_sko_set_key() does not work on + * a key object initialized in this way. + * + * If this function is used to initialize the key object, but no key is + * established with the key object, then the object will refer strictly to the + * key value specified by the @c pf_key selection. + * + * If the pf key is #FSL_SHW_PF_KEY_PRG or #FSL_SHW_PF_KEY_IIM_PRG, then the + * key object may be used with #fsl_shw_establish_key() to change the Program + * Key value. When the pf key is neither #FSL_SHW_PF_KEY_PRG nor + * #FSL_SHW_PF_KEY_IIM_PRG, it is an error to call #fsl_shw_establish_key(). + * + * @param key_info The Secret Key Object to be initialized. + * @param algorithm DES, AES, etc. + * @param pf_key Which platform key is referenced. + */ +void fsl_shw_sko_init_pf_key(fsl_shw_sko_t * key_info, + fsl_shw_key_alg_t algorithm, + fsl_shw_pf_key_t pf_key); + +/*! + * Store a cleartext key in the key object. + * + * This has the side effect of setting the #FSL_SKO_KEY_PRESENT flag. It should + * not be used if there is a key established with the key object. If there is, + * a call to #fsl_shw_release_key() should be made first. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param key A pointer to the beginning of the key. + * @param key_length The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +void fsl_shw_sko_set_key(fsl_shw_sko_t * key_object, + const uint8_t * key, uint16_t key_length); + +/*! + * Set a size for the key. + * + * This function would normally be used when the user wants the key to be + * generated from a random source. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param key_length The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +void fsl_shw_sko_set_key_length(fsl_shw_sko_t * key_object, + uint16_t key_length); + +/*! + * Set the User ID associated with the key. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param userid The User ID to identify authorized users of the key. + */ +void fsl_shw_sko_set_user_id(fsl_shw_sko_t * key_object, key_userid_t userid); + +/*! + * Set the keystore that the key will be stored in. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param keystore The keystore to place the key in. This is a variable of + * type #fsl_shw_kso_t. + */ +void fsl_shw_sko_set_keystore(fsl_shw_sko_t * key_object, + fsl_shw_kso_t * keystore); + +/*! + * Set the establish key handle into a key object. + * + * The @a userid field will be used to validate the access to the unwrapped + * key. This feature is not available for all platforms, nor for all + * algorithms and modes. + * + * The #FSL_SKO_KEY_ESTABLISHED will be set (and the #FSL_SKO_KEY_PRESENT + * flag will be cleared). + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param userid The User ID to verify this user is an authorized user of + * the key. + * @param handle A @a handle from #fsl_shw_sko_get_established_info. + */ +void fsl_shw_sko_set_established_info(fsl_shw_sko_t * key_object, + key_userid_t userid, uint32_t handle); + +/*! + * Extract the algorithm from a key object. + * + * @param key_info The Key Object to be queried. + * @param[out] algorithm A pointer to the location to store the algorithm. + */ +void fsl_shw_sko_get_algorithm(const fsl_shw_sko_t * key_info, + fsl_shw_key_alg_t * algorithm); + +/*! + * Retrieve the cleartext key from a key object that is stored in a user + * keystore. + * + * @param skobject The Key Object to be queried. + * @param[out] skkey A pointer to the location to store the key. NULL + * if the key is not stored in a user keystore. + */ +void fsl_shw_sko_get_key(const fsl_shw_sko_t * skobject, void *skkey); + +/*! + * Retrieve the established-key handle from a key object. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param handle The location to store the @a handle of the unwrapped + * key. + */ +void fsl_shw_sko_get_established_info(fsl_shw_sko_t * key_object, + uint32_t * handle); + +/*! + * Determine the size of a wrapped key based upon the cleartext key's length. + * + * This function can be used to calculate the number of octets that + * #fsl_shw_extract_key() will write into the location at @a covered_key. + * + * If zero is returned at @a length, this means that the key length in + * @a key_info is not supported. + * + * @param key_info Information about a key to be wrapped. + * @param length Location to store the length of a wrapped + * version of the key in @a key_info. + */ +void fsl_shw_sko_calculate_wrapped_size(const fsl_shw_sko_t * key_info, + uint32_t * length); + +/*! + * Set some flags in the key object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param flags (One or more) ORed members of #fsl_shw_key_flags_t which + * are to be set. + */ +void fsl_shw_sko_set_flags(fsl_shw_sko_t * key_object, uint32_t flags); + +/*! + * Clear some flags in the key object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param flags (One or more) ORed members of #fsl_shw_key_flags_t which + * are to be reset. + */ +void fsl_shw_sko_clear_flags(fsl_shw_sko_t * key_object, uint32_t flags); + + /*! @} *//* end skoops */ + +/*****************************************************************************/ + +/*! @addtogroup hcops + @{ */ + +/*****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-004 - partially */ +/*! + * Initialize a Hash Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the hash + * context object. + * + * @param hash_ctx The hash context to operate upon. + * @param algorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +void fsl_shw_hco_init(fsl_shw_hco_t * hash_ctx, fsl_shw_hash_alg_t algorithm); + +/*****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-001 */ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-002 */ +/*! + * Get the current hash value and message length from the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hash_ctx The hash context to query. + * @param[out] digest Pointer to the location of @a length octets where to + * store a copy of the current value of the digest. + * @param length Number of octets of hash value to copy. + * @param[out] msg_length Pointer to the location to store the number of octets + * already hashed. + */ +void fsl_shw_hco_get_digest(const fsl_shw_hco_t * hash_ctx, uint8_t * digest, + uint8_t length, uint32_t * msg_length); + +/*****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-002 - partially */ +/*! + * Get the hash algorithm from the hash context object. + * + * @param hash_ctx The hash context to query. + * @param[out] algorithm Pointer to where the algorithm is to be stored. + */ +void fsl_shw_hco_get_info(const fsl_shw_hco_t * hash_ctx, + fsl_shw_hash_alg_t * algorithm); + +/*****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-003 */ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-004 */ +/*! + * Set the current hash value and message length in the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hash_ctx The hash context to operate upon. + * @param context Pointer to buffer of appropriate length to copy into + * the hash context object. + * @param msg_length The number of octets of the message which have + * already been hashed. + * + */ +void fsl_shw_hco_set_digest(fsl_shw_hco_t * hash_ctx, const uint8_t * context, + uint32_t msg_length); + +/*! + * Set flags in a Hash Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hash_ctx The hash context to be operated on. + * @param flags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +void fsl_shw_hco_set_flags(fsl_shw_hco_t * hash_ctx, uint32_t flags); + +/*! + * Clear flags in a Hash Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hash_ctx The hash context to be operated on. + * @param flags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +void fsl_shw_hco_clear_flags(fsl_shw_hco_t * hash_ctx, uint32_t flags); + + /*! @} *//* end hcops */ + +/*****************************************************************************/ + +/*! @addtogroup hmcops + @{ */ + +/*! + * Initialize an HMAC Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the HMAC + * context object. + * + * @param hmac_ctx The HMAC context to operate upon. + * @param algorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +void fsl_shw_hmco_init(fsl_shw_hmco_t * hmac_ctx, fsl_shw_hash_alg_t algorithm); + +/*! + * Set flags in an HMAC Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hmac_ctx The HMAC context to be operated on. + * @param flags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +void fsl_shw_hmco_set_flags(fsl_shw_hmco_t * hmac_ctx, uint32_t flags); + +/*! + * Clear flags in an HMAC Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hmac_ctx The HMAC context to be operated on. + * @param flags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +void fsl_shw_hmco_clear_flags(fsl_shw_hmco_t * hmac_ctx, uint32_t flags); + +/*! @} */ + +/*****************************************************************************/ + +/*! @addtogroup sccops + @{ */ + +/*! + * Initialize a Symmetric Cipher Context Object. + * + * This function must be called before performing any other operation with the + * Object. This will set the @a mode and @a algorithm and initialize the + * Object. + * + * @param sym_ctx The context object to operate on. + * @param algorithm The cipher algorithm this context will be used with. + * @param mode #FSL_SYM_MODE_CBC, #FSL_SYM_MODE_ECB, etc. + * + */ +void fsl_shw_scco_init(fsl_shw_scco_t * sym_ctx, + fsl_shw_key_alg_t algorithm, fsl_shw_sym_mode_t mode); + +/*! + * Set the flags for a Symmetric Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param sym_ctx The context object to operate on. + * @param flags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +void fsl_shw_scco_set_flags(fsl_shw_scco_t * sym_ctx, uint32_t flags); + +/*! + * Clear some flags in a Symmetric Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param sym_ctx The context object to operate on. + * @param flags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +void fsl_shw_scco_clear_flags(fsl_shw_scco_t * sym_ctx, uint32_t flags); + +/*! + * Set the Context (IV) for a Symmetric Cipher Context. + * + * This is to set the context/IV for #FSL_SYM_MODE_CBC mode, or to set the + * context (the S-Box and pointers) for ARC4. The full context size will + * be copied. + * + * @param sym_ctx The context object to operate on. + * @param context A pointer to the buffer which contains the context. + * + */ +void fsl_shw_scco_set_context(fsl_shw_scco_t * sym_ctx, uint8_t * context); + +/*! + * Get the Context for a Symmetric Cipher Context. + * + * This is to retrieve the context/IV for #FSL_SYM_MODE_CBC mode, or to + * retrieve context (the S-Box and pointers) for ARC4. The full context + * will be copied. + * + * @param sym_ctx The context object to operate on. + * @param[out] context Pointer to location where context will be stored. + */ +void fsl_shw_scco_get_context(const fsl_shw_scco_t * sym_ctx, + uint8_t * context); + +/*! + * Set the Counter Value for a Symmetric Cipher Context. + * + * This will set the Counter Value for CTR mode. + * + * @param sym_ctx The context object to operate on. + * @param counter The starting counter value. The number of octets. + * copied will be the block size for the algorithm. + * @param modulus The modulus for controlling the incrementing of the counter. + * + */ +void fsl_shw_scco_set_counter_info(fsl_shw_scco_t * sym_ctx, + const uint8_t * counter, + fsl_shw_ctr_mod_t modulus); + +/*! + * Get the Counter Value for a Symmetric Cipher Context. + * + * This will retrieve the Counter Value is for CTR mode. + * + * @param sym_ctx The context object to query. + * @param[out] counter Pointer to location to store the current counter + * value. The number of octets copied will be the + * block size for the algorithm. + * @param[out] modulus Pointer to location to store the modulus. + * + */ +void fsl_shw_scco_get_counter_info(const fsl_shw_scco_t * sym_ctx, + uint8_t * counter, + fsl_shw_ctr_mod_t * modulus); + + /*! @} *//* end sccops */ + +/*****************************************************************************/ + +/*! @addtogroup accoops + @{ */ + +/*! + * Initialize a Authentication-Cipher Context. + * + * @param auth_object Pointer to object to operate on. + * @param mode The mode for this object (only #FSL_ACC_MODE_CCM + * supported). + */ +void fsl_shw_acco_init(fsl_shw_acco_t * auth_object, fsl_shw_acc_mode_t mode); + +/*! + * Set the flags for a Authentication-Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param auth_object Pointer to object to operate on. + * @param flags The flags to set (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +void fsl_shw_acco_set_flags(fsl_shw_acco_t * auth_object, uint32_t flags); + +/*! + * Clear some flags in a Authentication-Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param auth_object Pointer to object to operate on. + * @param flags The flags to reset (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +void fsl_shw_acco_clear_flags(fsl_shw_acco_t * auth_object, uint32_t flags); + +/*! + * Set up the Authentication-Cipher Object for CCM mode. + * + * This will set the @a auth_object for CCM mode and save the @a ctr, + * and @a mac_length. This function can be called instead of + * #fsl_shw_acco_init(). + * + * The parameter @a ctr is Counter Block 0, (counter value 0), which is for the + * MAC. + * + * @param auth_object Pointer to object to operate on. + * @param algorithm Cipher algorithm. Only AES is supported. + * @param ctr The initial counter value. + * @param mac_length The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + */ +void fsl_shw_acco_set_ccm(fsl_shw_acco_t * auth_object, + fsl_shw_key_alg_t algorithm, + const uint8_t * ctr, uint8_t mac_length); + +/*! + * Format the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will also set the IV and CTR values per Appendix A of NIST + * Special Publication 800-38C (May 2004). It will also perform the + * #fsl_shw_acco_set_ccm() operation with information derived from this set of + * parameters. + * + * Note this function assumes the algorithm is AES. It initializes the + * @a auth_object by setting the mode to #FSL_ACC_MODE_CCM and setting the + * flags to be #FSL_ACCO_NIST_CCM. + * + * @param auth_object Pointer to object to operate on. + * @param t_length The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + * @param ad_length Number of octets of Associated Data (may be zero). + * @param q_length A value for the size of the length of @a q field. Valid + * values are 1-8. + * @param n The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param q The value of Q (size of the payload in octets). + * + */ +void fsl_shw_ccm_nist_format_ctr_and_iv(fsl_shw_acco_t * auth_object, + uint8_t t_length, + uint32_t ad_length, + uint8_t q_length, + const uint8_t * n, uint32_t q); + +/*! + * Update the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will set the IV and CTR values per Appendix A of NIST Special + * Publication 800-38C (May 2004). + * + * Note this function assumes that #fsl_shw_ccm_nist_format_ctr_and_iv() has + * previously been called on the @a auth_object. + * + * @param auth_object Pointer to object to operate on. + * @param n The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param q The value of Q (size of the payload in octets). + * + */ +void fsl_shw_ccm_nist_update_ctr_and_iv(fsl_shw_acco_t * auth_object, + const uint8_t * n, uint32_t q); + + /* @} *//* accoops */ + +/****************************************************************************** + * Library functions + *****************************************************************************/ + +/*! @addtogroup miscfuns + @{ */ + +/* REQ-S2LRD-PINTFC-API-GEN-003 */ +/*! + * Determine the hardware security capabilities of this platform. + * + * Though a user context object is passed into this function, it will always + * act in a non-blocking manner. + * + * @param user_ctx The user context which will be used for the query. + * + * @return A pointer to the capabilities object. + */ +extern fsl_shw_pco_t *fsl_shw_get_capabilities(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-004 */ +/*! + * Create an association between the user and the provider of the API. + * + * @param user_ctx The user context which will be used for this association. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-005 */ +/*! + * Destroy the association between the user and the provider of the API. + * + * @param user_ctx The user context which is no longer needed. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-006 */ +/*! + * Retrieve results from earlier operations. + * + * @param user_ctx The user's context. + * @param result_size The number of array elements of @a results. + * @param[in,out] results Pointer to first of the (array of) locations to + * store results. + * @param[out] result_count Pointer to store the number of results which + * were returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t * user_ctx, + uint16_t result_size, + fsl_shw_result_t results[], + uint16_t * result_count); + +/*! + * Allocate a block of secure memory + * + * @param user_ctx User context + * @param size Memory size (octets). Note: currently only + * supports only single-partition sized blocks. + * @param UMID User Mode ID to use when registering the + * partition. + * @param permissions Permissions to initialize the partition with. + * Can be made by ORing flags from the + * #fsl_shw_permission_t. + * + * @return Address of the allocated memory. NULL if the + * call was not successful. + */ +extern void *fsl_shw_smalloc(fsl_shw_uco_t * user_ctx, + uint32_t size, + const uint8_t * UMID, uint32_t permissions); + +/*! + * Free a block of secure memory that was allocated with #fsl_shw_smalloc + * + * @param user_ctx User context + * @param address Address of the block of secure memory to be + * released. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_sfree(fsl_shw_uco_t * user_ctx, void *address); + +/*! + * Diminish the permissions of a block of secure memory. Note that permissions + * can only be revoked. + * + * @param user_ctx User context + * @param address Base address of the secure memory to work with + * @param permissions Permissions to initialize the partition with. + * Can be made by ORing flags from the + * #fsl_shw_permission_t. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_diminish_perms(fsl_shw_uco_t * user_ctx, + void *address, + uint32_t permissions); + +/*! + * @brief Encrypt a region of secure memory using the hardware secret key + * + * @param user_ctx User context + * @param partition_base Base address of the partition + * @param offset_bytes Offset of data from the partition base + * @param byte_count Length of the data to encrypt + * @param black_data Location to store the encrypted data + * @param IV IV to use for the encryption routine + * @param cypher_mode Cyphering mode to use, specified by type + * #fsl_shw_cypher_mode_t + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t +do_scc_encrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode); + +/*! + * @brief Decrypt a region of secure memory using the hardware secret key + * + * @param user_ctx User context + * @param partition_base Base address of the partition + * @param offset_bytes Offset of data from the partition base + * @param byte_count Length of the data to encrypt + * @param black_data Location to store the encrypted data + * @param IV IV to use for the encryption routine + * @param cypher_mode Cyphering mode to use, specified by type + * #fsl_shw_cypher_mode_t + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t +do_scc_decrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, const uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode); + + /*! @} *//* miscfuns */ + +/*! @addtogroup opfuns + @{ */ + +/* REQ-S2LRD-PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +/*! + * Encrypt a stream of data with a symmetric-key algorithm. + * + * In ARC4, and also in #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_CTR modes, the + * flags of the @a sym_ctx object will control part of the operation of this + * function. The #FSL_SYM_CTX_INIT flag means that there is no context info in + * the object. The #FSL_SYM_CTX_LOAD means to use information in the + * @a sym_ctx at the start of the operation, and the #FSL_SYM_CTX_SAVE flag + * means to update the object's context information after the operation has + * been performed. + * + * All of the data for an operation can be run through at once using the + * #FSL_SYM_CTX_INIT or #FSL_SYM_CTX_LOAD flags, as appropriate, and then using + * a @a length for the whole of the data. + * + * If a #FSL_SYM_CTX_SAVE flag were added, an additional call to the function + * would "pick up" where the previous call left off, allowing the user to + * perform the larger function in smaller steps. + * + * In #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_ECB modes, the @a length must always + * be a multiple of the block size for the algorithm being used. For proper + * operation in #FSL_SYM_MODE_CTR mode, the @a length must be a multiple of the + * block size until the last operation on the total octet stream. + * + * Some users of ARC4 may want to compute the context (S-Box and pointers) from + * the key before any data is available. This may be done by running this + * function with a @a length of zero, with the init & save flags flags on in + * the @a sym_ctx. Subsequent operations would then run as normal with the + * load and save flags. Note that they key object is still required. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info Key and algorithm being used for this operation. + * @param[in,out] sym_ctx Info on cipher mode, state of the cipher. + * @param length Length, in octets, of the pt (and ct). + * @param pt pointer to plaintext to be encrypted. + * @param[out] ct pointer to where to store the resulting ciphertext. + * + * @return A return code of type #fsl_shw_return_t. + * + */ +extern fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, + uint8_t * ct); + +/* PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +/*! + * Decrypt a stream of data with a symmetric-key algorithm. + * + * In ARC4, and also in #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_CTR modes, the + * flags of the @a sym_ctx object will control part of the operation of this + * function. The #FSL_SYM_CTX_INIT flag means that there is no context info in + * the object. The #FSL_SYM_CTX_LOAD means to use information in the + * @a sym_ctx at the start of the operation, and the #FSL_SYM_CTX_SAVE flag + * means to update the object's context information after the operation has + * been performed. + * + * All of the data for an operation can be run through at once using the + * #FSL_SYM_CTX_INIT or #FSL_SYM_CTX_LOAD flags, as appropriate, and then using + * a @a length for the whole of the data. + * + * If a #FSL_SYM_CTX_SAVE flag were added, an additional call to the function + * would "pick up" where the previous call left off, allowing the user to + * perform the larger function in smaller steps. + * + * In #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_ECB modes, the @a length must always + * be a multiple of the block size for the algorithm being used. For proper + * operation in #FSL_SYM_MODE_CTR mode, the @a length must be a multiple of the + * block size until the last operation on the total octet stream. + * + * Some users of ARC4 may want to compute the context (S-Box and pointers) from + * the key before any data is available. This may be done by running this + * function with a @a length of zero, with the #FSL_SYM_CTX_INIT & + * #FSL_SYM_CTX_SAVE flags on in the @a sym_ctx. Subsequent operations would + * then run as normal with the load & save flags. Note that they key object is + * still required. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The key and algorithm being used in this operation. + * @param[in,out] sym_ctx Info on cipher mode, state of the cipher. + * @param length Length, in octets, of the ct (and pt). + * @param ct pointer to ciphertext to be decrypted. + * @param[out] pt pointer to where to store the resulting plaintext. + * + * @return A return code of type #fsl_shw_return_t + * + */ +extern fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, + uint8_t * pt); + +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-005 */ +/*! + * Hash a stream of data with a cryptographic hash algorithm. + * + * The flags in the @a hash_ctx control the operation of this function. + * + * Hashing functions work on 64 octets of message at a time. Therefore, when + * any partial hashing of a long message is performed, the message @a length of + * each segment must be a multiple of 64. When ready to + * #FSL_HASH_FLAGS_FINALIZE the hash, the @a length may be any value. + * + * With the #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_FINALIZE flags on, a + * one-shot complete hash, including padding, will be performed. The @a length + * may be any value. + * + * The first octets of a data stream can be hashed by setting the + * #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_SAVE flags. The @a length must be + * a multiple of 64. + * + * The flag #FSL_HASH_FLAGS_LOAD is used to load a context previously saved by + * #FSL_HASH_FLAGS_SAVE. The two in combination will allow a (multiple-of-64 + * octets) 'middle sequence' of the data stream to be hashed with the + * beginning. The @a length must again be a multiple of 64. + * + * Since the flag #FSL_HASH_FLAGS_LOAD is used to load a context previously + * saved by #FSL_HASH_FLAGS_SAVE, the #FSL_HASH_FLAGS_LOAD and + * #FSL_HASH_FLAGS_FINALIZE flags, used together, can be used to finish the + * stream. The @a length may be any value. + * + * If the user program wants to do the padding for the hash, it can leave off + * the #FSL_HASH_FLAGS_FINALIZE flag. The @a length must then be a multiple of + * 64 octets. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] hash_ctx Hashing algorithm and state of the cipher. + * @param msg Pointer to the data to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result If not null, pointer to where to store the hash + * digest. + * @param result_len Number of octets to store in @a result. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len); + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-001 */ +/*! + * Precompute the Key hashes for an HMAC operation. + * + * This function may be used to calculate the inner and outer precomputes, + * which are the hash contexts resulting from hashing the XORed key for the + * 'inner hash' and the 'outer hash', respectively, of the HMAC function. + * + * After execution of this function, the @a hmac_ctx will contain the + * precomputed inner and outer contexts, so that they may be used by + * #fsl_shw_hmac(). The flags of @a hmac_ctx will be updated with + * #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT to mark their presence. In addition, the + * #FSL_HMAC_FLAGS_INIT flag will be set. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The key being used in this operation. Key must be + * 1 to 64 octets long. + * @param[in,out] hmac_ctx The context which controls, by its flags and + * algorithm, the operation of this function. + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx); + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-002 */ +/*! + * Continue, finalize, or one-shot an HMAC operation. + * + * There are a number of ways to use this function. The flags in the + * @a hmac_ctx object will determine what operations occur. + * + * If #FSL_HMAC_FLAGS_INIT is set, then the hash will be started either from + * the @a key_info, or from the precomputed inner hash value in the + * @a hmac_ctx, depending on the value of #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT. + * + * If, instead, #FSL_HMAC_FLAGS_LOAD is set, then the hash will be continued + * from the ongoing inner hash computation in the @a hmac_ctx. + * + * If #FSL_HMAC_FLAGS_FINALIZE are set, then the @a msg will be padded, hashed, + * the outer hash will be performed, and the @a result will be generated. + * + * If the #FSL_HMAC_FLAGS_SAVE flag is set, then the (ongoing or final) digest + * value will be stored in the ongoing inner hash computation field of the @a + * hmac_ctx. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info If #FSL_HMAC_FLAGS_INIT is set in the @a hmac_ctx, + * this is the key being used in this operation, and the + * IPAD. If #FSL_HMAC_FLAGS_INIT is set in the @a + * hmac_ctx and @a key_info is NULL, then + * #fsl_shw_hmac_precompute() has been used to populate + * the @a inner_precompute and @a outer_precompute + * contexts. If #FSL_HMAC_FLAGS_INIT is not set, this + * parameter is ignored. + + * @param[in,out] hmac_ctx The context which controls, by its flags and + * algorithm, the operation of this function. + * @param msg Pointer to the message to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result Pointer, of @a result_len octets, to where to + * store the HMAC. + * @param result_len Length of @a result buffer. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len); + +/* REQ-S2LRD-PINTFC-API-BASIC-RNG-002 */ +/*! + * Get random data. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length The number of octets of @a data being requested. + * @param[out] data A pointer to a location of @a length octets to where + * random data will be returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data); + +/* REQ-S2LRD-PINTFC-API-BASIC-RNG-002 */ +/*! + * Add entropy to random number generator. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length Number of bytes at @a data. + * @param data Entropy to add to random number generator. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data); + +/*! + * Perform Generation-Encryption by doing a Cipher and a Hash. + * + * Generate the authentication value @a auth_value as well as encrypt the @a + * payload into @a ct (the ciphertext). This is a one-shot function, so all of + * the @a auth_data and the total message @a payload must passed in one call. + * This also means that the flags in the @a auth_ctx must be #FSL_ACCO_CTX_INIT + * and #FSL_ACCO_CTX_FINALIZE. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param auth_ctx Controlling object for Authenticate-decrypt. + * @param cipher_key_info The key being used for the cipher part of this + * operation. In CCM mode, this key is used for + * both parts. + * @param auth_key_info The key being used for the authentication part + * of this operation. In CCM mode, this key is + * ignored and may be NULL. + * @param auth_data_length Length, in octets, of @a auth_data. + * @param auth_data Data to be authenticated but not encrypted. + * @param payload_length Length, in octets, of @a payload. + * @param payload Pointer to the plaintext to be encrypted. + * @param[out] ct Pointer to the where the encrypted @a payload + * will be stored. Must be @a payload_length + * octets long. + * @param[out] auth_value Pointer to where the generated authentication + * field will be stored. Must be as many octets as + * indicated by MAC length in the @a function_ctx. + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_gen_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value); + +/*! + * Perform Authentication-Decryption in Cipher + Hash. + * + * This function will perform a one-shot decryption of a data stream as well as + * authenticate the authentication value. This is a one-shot function, so all + * of the @a auth_data and the total message @a payload must passed in one + * call. This also means that the flags in the @a auth_ctx must be + * #FSL_ACCO_CTX_INIT and #FSL_ACCO_CTX_FINALIZE. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param auth_ctx Controlling object for Authenticate-decrypt. + * @param cipher_key_info The key being used for the cipher part of this + * operation. In CCM mode, this key is used for + * both parts. + * @param auth_key_info The key being used for the authentication part + * of this operation. In CCM mode, this key is + * ignored and may be NULL. + * @param auth_data_length Length, in octets, of @a auth_data. + * @param auth_data Data to be authenticated but not decrypted. + * @param payload_length Length, in octets, of @a ct and @a pt. + * @param ct Pointer to the encrypted input stream. + * @param auth_value The (encrypted) authentication value which will + * be authenticated. This is the same data as the + * (output) @a auth_value argument to + * #fsl_shw_gen_encrypt(). + * @param[out] payload Pointer to where the plaintext resulting from + * the decryption will be stored. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_auth_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * ct, + const uint8_t * auth_value, + uint8_t * payload); + +/*! + * Establish the key in a protected location, which can be the system keystore, + * user keystore, or (on platforms that support it) as a Platform Key. + * + * By default, keys initialized with #fsl_shw_sko_init() will be placed into + * the system keystore. The user can cause the key to be established in a + * user keystore by first calling #fsl_shw_sko_set_keystore() on the key. + * Normally, keys in the system keystore can only be used for hardware + * encrypt or decrypt operations, however if the #FSL_SKO_KEY_SW_KEY flag is + * applied using #fsl_shw_sko_set_flags(), the key will be established as a + * software key, which can then be read out using #fsl_shw_read_key(). + * + * Keys initialized with #fsl_shw_sko_init_pf_key() are established as a + * Platform Key. Their use is covered in @ref di_sec. + * + * This function only needs to be used when unwrapping a key, setting up a key + * which could be wrapped with a later call to #fsl_shw_extract_key(), or + * setting up a key as a Platform Key. Normal cleartext keys can simply be + * placed into #fsl_shw_sko_t key objects with #fsl_shw_sko_set_key() and used + * directly. + * + * The maximum key size supported for wrapped/unwrapped keys is 32 octets. + * (This is the maximum reasonable key length on Sahara - 32 octets for an HMAC + * key based on SHA-256.) The key size is determined by the @a key_info. The + * expected length of @a key can be determined by + * #fsl_shw_sko_calculate_wrapped_size() + * + * The protected key will not be available for use until this operation + * successfully completes. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be established. In the create case, the key + * length must be set. + * @param establish_type How @a key will be interpreted to establish a + * key for use. + * @param key If @a establish_type is #FSL_KEY_WRAP_UNWRAP, + * this is the location of a wrapped key. If + * @a establish_type is #FSL_KEY_WRAP_CREATE, this + * parameter can be @a NULL. If @a establish_type + * is #FSL_KEY_WRAP_ACCEPT, this is the location + * of a plaintext key. + */ +extern fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key); + +/*! + * Read the key value from a key object. + * + * Only a key marked as a software key (#FSL_SKO_KEY_SW_KEY) can be read with + * this call. It has no effect on the status of the key store. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The referenced key. + * @param[out] key The location to store the key value. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_read_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * key); + +/*! + * Wrap a key and retrieve the wrapped value. + * + * A wrapped key is a key that has been cryptographically obscured. It is + * only able to be used with keys that have been established by + * #fsl_shw_establish_key(). + * + * For keys established in the system or user keystore, this function will + * also release the key (see #fsl_shw_release_key()) so that it must be re- + * established before reuse. This function will not release keys that are + * established as a Platform Key, so a call to #fsl_shw_release_key() is + * necessary to release those keys. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * @param[out] covered_key The location to store the wrapped key. + * (This size is based upon the maximum key size + * of 32 octets). + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key); + +/*! + * De-establish a key so that it can no longer be accessed. + * + * The key will need to be re-established before it can again be used. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info); + +/*! + * Cause the hardware to create a new random key for use by the secure memory + * encryption hardware. + * + * Have the hardware use the secure hardware random number generator to load a + * new secret key into the system's Random Key register. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_gen_random_pf_key(fsl_shw_uco_t * user_ctx); + +/*! + * Retrieve the detected tamper event. + * + * Note that if more than one event was detected, this routine will only ever + * return one of them. + * + * @param[in] user_ctx A user context from #fsl_shw_register_user(). + * @param[out] tamperp Location to store the tamper information. + * @param[out] timestampp Locate to store timestamp from hardwhare when + * an event was detected. + * + * + * @return A return code of type #fsl_shw_return_t (for instance, if the platform + * is not in a fail state. + */ +extern fsl_shw_return_t fsl_shw_read_tamper_event(fsl_shw_uco_t * user_ctx, + fsl_shw_tamper_t * tamperp, + uint64_t * timestampp); + +/*! @} *//* opfuns */ + +/* Insert example code into the API documentation. */ + +/*! + * @example apitest.c + */ + +/*! + * @example sym.c + */ + +/*! + * @example rand.c + */ + +/*! + * @example hash.c + */ + +/*! + * @example hmac1.c + */ + +/*! + * @example hmac2.c + */ + +/*! + * @example gen_encrypt.c + */ + +/*! + * @example auth_decrypt.c + */ + +/*! + * @example wrapped_key.c + */ + +/*! + * @example smalloc.c + */ + +/*! + * @example user_keystore.c + */ + +/*! + * @example dryice.c + */ + +#endif /* API_DOC */ + +#endif /* FSL_SHW_H */ diff --git a/drivers/mxc/security/sahara2/include/fsl_shw_keystore.h b/drivers/mxc/security/sahara2/include/fsl_shw_keystore.h new file mode 100644 index 000000000000..2a275da2dfa8 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/fsl_shw_keystore.h @@ -0,0 +1,475 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +#ifndef FSL_SHW_KEYSTORE_H +#define FSL_SHW_KEYSTORE_H + +/*! + * @file fsl_shw_keystore.h + * + * @brief Definition of the User Keystore API. + * + */ + +/*! \page user_keystore User Keystore API + * + * Definition of the User Keystore API. + * + * On platforms with multiple partitions of Secure Memory, the Keystore Object + * (#fsl_shw_kso_t) is provided to allow users to manage a private keystore for + * use in software cryptographic routines. The user can define a custom set of + * methods for managing their keystore, or use a default keystore handler. The + * keystore is established by #fsl_shw_establish_keystore(), and released by + * #fsl_shw_release_keystore(). The intent of this design is to make the + * keystore implementation as flexible as possible. + * + * See @ref keystore_api for the generic keystore API, and @ref + * default_keystore for the default keystore implementation. + * + */ + +/*! + * @defgroup keystore_api User Keystore API + * + * Keystore API + * + * These functions define the generic keystore API, which can be used in + * conjunction with a keystore implementation backend to support a user + * keystore. + */ + +/*! + * @defgroup default_keystore Default Keystore Implementation + * + * Default Keystore Implementation + * + * These functions define the default keystore implementation, which is used + * for the system keystore and for user keystores initialized by + * #fsl_shw_init_keystore_default(). They can be used as-is or as a reference + * for creating a custom keystore handler. It uses an entire Secure Memory + * partition, divided in to equal slots of length #KEYSTORE_SLOT_SIZE. These + * functions are not intended to be used directly- all user interaction with + * the keystore should be through the @ref keystore_api and the Wrapped Key + * interface. + * + * The current implementation is designed to work with both SCC and SCC2. + * Differences between the two versions are noted below. + */ + +/*! @addtogroup keystore_api + @{ */ + +#ifndef KEYSTORE_SLOT_SIZE +/*! Size of each key slot, in octets. This sets an upper bound on the size + * of a key that can placed in the keystore. + */ +#define KEYSTORE_SLOT_SIZE 32 +#endif + +/*! + * Initialize a Keystore Object. + * + * This function must be called before performing any other operation with the + * Object. It allows the user to associate a custom keystore interface by + * specifying the correct set of functions that will be used to perform actions + * on the keystore object. To use the default keystore handler, the function + * #fsl_shw_init_keystore_default() can be used instead. + * + * @param keystore The Keystore object to operate on. + * @param data_init Keystore initialization function. This function is + * responsible for initializing the keystore. A + * user-defined object can be assigned to the user_data + * pointer, and will be passed to any function acting on + * that keystore. It is called during + * #fsl_shw_establish_keystore(). + * @param data_cleanup Keystore cleanup function. This function cleans up + * any data structures associated with the keyboard. It + * is called by #fsl_shw_release_keystore(). + * @param slot_alloc Slot allocation function. This function allocates a + * key slot, potentially based on size and owner id. It + * is called by #fsl_shw_establish_key(). + * @param slot_dealloc Slot deallocation function. + * @param slot_verify_access Function to verify that a given Owner ID + * credential matches the given slot. + * @param slot_get_address For SCC2: Get the virtual address (kernel or + * userspace) of the data stored in the slot. + * For SCC: Get the physical address of the data + * stored in the slot. + * @param slot_get_base For SCC2: Get the (virtual) base address of the + * partition that the slot is located on. + * For SCC: Not implemented. + * @param slot_get_offset For SCC2: Get the offset from the start of the + * partition that the slot data is located at (in + * octets) + * For SCC: Not implemented. + * @param slot_get_slot_size Get the size of the key slot, in octets. + */ +extern void fsl_shw_init_keystore(fsl_shw_kso_t * keystore, + fsl_shw_return_t(*data_init) (fsl_shw_uco_t * + user_ctx, + void + **user_data), + void (*data_cleanup) (fsl_shw_uco_t * + user_ctx, + void **user_data), + fsl_shw_return_t(*slot_alloc) (void + *user_data, + uint32_t size, + uint64_t + owner_id, + uint32_t * + slot), + fsl_shw_return_t(*slot_dealloc) (void + *user_data, + uint64_t + owner_id, + uint32_t + slot), + fsl_shw_return_t(*slot_verify_access) (void + *user_data, + uint64_t + owner_id, + uint32_t + slot), + void *(*slot_get_address) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_base) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_offset) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_slot_size) (void + *user_data, + uint32_t + handle)); + +/*! + * Initialize a Keystore Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the user keystore object up to use the default keystore + * handler. If a custom keystore handler is desired, the function + * #fsl_shw_init_keystore() can be used instead. + * + * @param keystore The Keystore object to operate on. + */ +extern void fsl_shw_init_keystore_default(fsl_shw_kso_t * keystore); + +/*! + * Establish a Keystore Object. + * + * This function establishes a keystore object that has been set up by a call + * to #fsl_shw_init_keystore(). It is a wrapper for the user-defined + * data_init() function, which is specified during keystore initialization. + * + * @param user_ctx The user context that this keystore should be attached + * to + * @param keystore The Keystore object to operate on. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_establish_keystore(fsl_shw_uco_t * user_ctx, + fsl_shw_kso_t * keystore); + +/*! + * Release a Keystore Object. + * + * This function releases an established keystore object. It is a wrapper for + * the user-defined data_cleanup() function, which is specified during keystore + * initialization. + * + * @param user_ctx The user context that this keystore should be attached + * to. + * @param keystore The Keystore object to operate on. + */ +extern void fsl_shw_release_keystore(fsl_shw_uco_t * user_ctx, + fsl_shw_kso_t * keystore); + +/*! + * Allocate a slot in the Keystore. + * + * This function attempts to allocate a slot to hold a key in the keystore. It + * is called by #fsl_shw_establish_key() when establishing a Secure Key Object, + * if the key has been flagged to be stored in a user keystore by the + * #fsl_shw_sko_set_keystore() function. It is a wrapper for the + * implementation-specific function slot_alloc(). + * + * @param keystore The Keystore object to operate on. + * @param[in] size Size of the key to be stored (octets). + * @param[in] owner_id ID of the key owner. + * @param[out] slot If successful, assigned slot ID + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t keystore_slot_alloc(fsl_shw_kso_t * keystore, + uint32_t size, + uint64_t owner_id, uint32_t * slot); + +/*! + * Deallocate a slot in the Keystore. + * + * This function attempts to allocate a slot to hold a key in the keystore. + * It is called by #fsl_shw_extract_key() and #fsl_shw_release_key() when the + * key that it contains is to be released. It is a wrapper for the + * implmentation-specific function slot_dealloc(). + + * @param keystore The Keystore object to operate on. + * @param[in] owner_id ID of the key owner. + * @param[in] slot If successful, assigned slot ID. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t keystore_slot_dealloc(fsl_shw_kso_t * keystore, + uint64_t owner_id, uint32_t slot); + +/*! + * Load cleartext key data into a key slot + * + * This function loads a key slot with cleartext data. + * + * @param keystore The Keystore object to operate on. + * @param[in] owner_id ID of the key owner. + * @param[in] slot If successful, assigned slot ID. + * @param[in] key_data Pointer to the location of the cleartext key data. + * @param[in] key_length Length of the key data (octets). + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t +keystore_slot_load(fsl_shw_kso_t * keystore, uint64_t owner_id, uint32_t slot, + const uint8_t * key_data, uint32_t key_length); + +/*! + * Read cleartext key data from a key slot + * + * This function returns the key in a key slot. + * + * @param keystore The Keystore object to operate on. + * @param[in] owner_id ID of the key owner. + * @param[in] slot ID of slot where key resides. + * @param[in] key_length Length of the key data (octets). + * @param[out] key_data Pointer to the location of the cleartext key data. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t +keystore_slot_read(fsl_shw_kso_t * keystore, uint64_t owner_id, uint32_t slot, + uint32_t key_length, uint8_t * key_data); + +/*! + * Encrypt a keyslot + * + * This function encrypts a key using the hardware secret key. + * + * @param user_ctx User context + * @param keystore The Keystore object to operate on. + * @param[in] owner_id ID of the key owner. + * @param[in] slot Slot ID of the key to encrypt. + * @param[in] length Length of the key + * @param[out] destination Pointer to the location where the encrypted data + * is to be stored. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t +keystore_slot_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_kso_t * keystore, uint64_t owner_id, + uint32_t slot, uint32_t length, uint8_t * destination); + +/*! + * Decrypt a keyslot + * + * This function decrypts a key using the hardware secret key. + * + * @param user_ctx User context + * @param keystore The Keystore object to operate on. + * @param[in] owner_id ID of the key owner. + * @param[in] slot Slot ID of the key to encrypt. + * @param[in] length Length of the key + * @param[in] source Pointer to the location where the encrypted data + * is stored. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t +keystore_slot_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_kso_t * keystore, uint64_t owner_id, + uint32_t slot, uint32_t length, const uint8_t * source); + +/* @} */ + +/*! @addtogroup default_keystore + @{ */ + +/*! + * Data structure to hold per-slot information + */ +typedef struct keystore_data_slot_info_t { + uint8_t allocated; /*!< Track slot assignments */ + uint64_t owner; /*!< Owner IDs */ + uint32_t key_length; /*!< Size of the key */ +} keystore_data_slot_info_t; + +/*! + * Data structure to hold keystore information. + */ +typedef struct keystore_data_t { + void *base_address; /*!< Base of the Secure Partition */ + uint32_t slot_count; /*!< Number of slots in the keystore */ + struct keystore_data_slot_info_t *slot; /*!< Per-slot information */ +} keystore_data_t; + +/*! + * Default keystore initialization routine. + * + * This function acquires a Secure Partition Object to store the keystore, + * divides it into slots of length #KEYSTORE_SLOT_SIZE, and builds a data + * structure to hold key information. + * + * @param user_ctx User context + * @param[out] user_data Pointer to the location where the keystore data + * structure is to be stored. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t shw_kso_init_data(fsl_shw_uco_t * user_ctx, void **user_data); + +/*! + * Default keystore cleanup routine. + * + * This function releases the Secure Partition Object and the memory holding + * the keystore data structure, that obtained by the shw_kso_init_data + * function. + * + * @param user_ctx User context + * @param[in,out] user_data Pointer to the location where the keystore data + * structure is stored. + */ +void shw_kso_cleanup_data(fsl_shw_uco_t * user_ctx, void **user_data); + +/*! + * Default keystore slot access verification + * + * This function compares the supplied Owner ID to the registered owner of + * the key slot, to see if the supplied ID is correct. + * + * @param[in] user_data Pointer to the location where the keystore data + * structure stored. + * @param[in] owner_id Owner ID supplied as a credential. + * @param[in] slot Requested slot + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t shw_slot_verify_access(void *user_data, uint64_t owner_id, + uint32_t slot); + +/*! + * Default keystore slot allocation + * + * This function first checks that the requested size is equal to or less than + * the maximum keystore slot size. If so, it searches the keystore for a free + * key slot, and if found, marks it as used and returns a slot reference to the + * user. + * + * @param[in] user_data Pointer to the location where the keystore data + * structure stored. + * @param[in] size Size of the key data that will be stored in this slot + * (octets) + * @param[in] owner_id Owner ID supplied as a credential. + * @param[out] slot Requested slot + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t shw_slot_alloc(void *user_data, uint32_t size, + uint64_t owner_id, uint32_t * slot); + +/*! + * Default keystore slot deallocation + * + * This function releases the given key slot in the keystore, making it + * available to store a new key. + * + * @param[in] user_data Pointer to the location where the keystore data + * structure stored. + * @param[in] owner_id Owner ID supplied as a credential. + * @param[in] slot Requested slot + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t shw_slot_dealloc(void *user_data, + uint64_t owner_id, uint32_t slot); + +/*! + * Default keystore slot address lookup + * + * This function calculates the address where the key data is stored. + * + * @param[in] user_data Pointer to the location where the keystore data + * structure stored. + * @param[in] slot Requested slot + * + * @return SCC2: Virtual address (kernel or userspace) of the key data. + * SCC: Physical address of the key data. + */ +void *shw_slot_get_address(void *user_data, uint32_t slot); + +/*! + * Default keystore slot base address lookup + * + * This function calculates the base address of the Secure Partition on which + * the key data is located. For the reference design, only one Secure + * Partition is used per Keystore, however in general, any number may be used. + * + * @param[in] user_data Pointer to the location where the keystore data + * structure stored. + * @param[in] slot Requested slot + * + * @return SCC2: Secure Partition virtual (kernel or userspace) base address. + * SCC: Secure Partition physical base address. + */ +uint32_t shw_slot_get_base(void *user_data, uint32_t slot); + +/*! + * Default keystore slot offset lookup + * + * This function calculates the offset from the base of the Secure Partition + * where the key data is located. + * + * @param[in] user_data Pointer to the location where the keystore data + * structure stored. + * @param[in] slot Requested slot + * + * @return SCC2: Key data offset (octets) + * SCC: Not implemented + */ +uint32_t shw_slot_get_offset(void *user_data, uint32_t slot); + +/*! + * Default keystore slot offset lookup + * + * This function returns the size of the given key slot. In the reference + * implementation, all key slots are of the same size, however in general, + * the keystore slot sizes can be made variable. + * + * @param[in] user_data Pointer to the location where the keystore data + * structure stored. + * @param[in] slot Requested slot + * + * @return SCC2: Keystore slot size. + * SCC: Not implemented + */ +uint32_t shw_slot_get_slot_size(void *user_data, uint32_t slot); + +/* @} */ + +#endif /* FSL_SHW_KEYSTORE_H */ diff --git a/drivers/mxc/security/sahara2/include/linux_port.h b/drivers/mxc/security/sahara2/include/linux_port.h new file mode 100644 index 000000000000..492bbf4f893d --- /dev/null +++ b/drivers/mxc/security/sahara2/include/linux_port.h @@ -0,0 +1,1806 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file linux_port.h + * + * OS_PORT ported to Linux (2.6.9+ for now) + * + */ + + /*! + * @if USE_MAINPAGE + * @mainpage ==Linux version of== Generic OS API for STC Drivers + * @endif + * + * @section intro_sec Introduction + * + * This API / kernel programming environment blah blah. + * + * See @ref dkops "Driver-to-Kernel Operations" as a good place to start. + */ + +#ifndef LINUX_PORT_H +#define LINUX_PORT_H + +#define PORTABLE_OS_VERSION 101 + +/* Linux Kernel Includes */ +#include <linux/version.h> /* Current version Linux kernel */ + +#if defined(CONFIG_MODVERSIONS) && ! defined(MODVERSIONS) +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +#include <linux/modversions.h> +#endif +#define MODVERSIONS +#endif +/*! + * __NO_VERSION__ defined due to Kernel module possibly spanning multiple + * files. + */ +#define __NO_VERSION__ + +#include <linux/module.h> /* Basic support for loadable modules, + printk */ +#include <linux/init.h> /* module_init, module_exit */ +#include <linux/kernel.h> /* General kernel system calls */ +#include <linux/sched.h> /* for interrupt.h */ +#include <linux/fs.h> /* for inode */ +#include <linux/random.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> /* kmalloc */ + +#include <stdarg.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +#include <linux/device.h> /* used in dynamic power management */ +#else +#include <linux/platform_device.h> /* used in dynamic power management */ +#endif + +#include <linux/dmapool.h> +#include <linux/dma-mapping.h> + +#include <linux/clk.h> /* clock en/disable for DPM */ + +#include <linux/dmapool.h> +#include <linux/dma-mapping.h> + +#include <asm/uaccess.h> /* copy_to_user(), copy_from_user() */ +#include <asm/io.h> /* ioremap() */ +#include <asm/irq.h> +#include <asm/cacheflush.h> + +#include <mach/hardware.h> + +#ifndef TRUE +/*! Useful symbol for unsigned values used as flags. */ +#define TRUE 1 +#endif + +#ifndef FALSE +/*! Useful symbol for unsigned values used as flags. */ +#define FALSE 0 +#endif + +/* These symbols are defined in Linux 2.6 and later. Include here for minimal + * support of 2.4 kernel. + **/ +#if !defined(LINUX_VERSION_CODE) || LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +/*! + * Symbol defined somewhere in 2.5/2.6. It is the return signature of an ISR. + */ +#define irqreturn_t void +/*! Possible return value of 'modern' ISR routine. */ +#define IRQ_HANDLED +/*! Method of generating value of 'modern' ISR routine. */ +#define IRQ_RETVAL(x) +#endif + +/*! + * Type used for registering and deregistering interrupts. + */ +typedef int os_interrupt_id_t; + +/*! + * Type used as handle for a process + * + * See #os_get_process_handle() and #os_send_signal(). + */ +/* + * The following should be defined this way, but it gets compiler errors + * on the current tool chain. + * + * typedef task_t *os_process_handle_t; + */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +typedef task_t *os_process_handle_t; +#else +typedef struct task_struct *os_process_handle_t; +#endif + +/*! + * Generic return code for functions which need such a thing. + * + * No knowledge should be assumed of the value of any of these symbols except + * that @c OS_ERROR_OK_S is guaranteed to be zero. + */ +typedef enum { + OS_ERROR_OK_S = 0, /*!< Success */ + OS_ERROR_FAIL_S = -EIO, /*!< Generic driver failure */ + OS_ERROR_NO_MEMORY_S = -ENOMEM, /*!< Failure to acquire/use memory */ + OS_ERROR_BAD_ADDRESS_S = -EFAULT, /*!< Bad address */ + OS_ERROR_BAD_ARG_S = -EINVAL, /*!< Bad input argument */ +} os_error_code; + +/*! + * Handle to a lock. + */ +#ifdef CONFIG_PREEMPT_RT +typedef raw_spinlock_t *os_lock_t; +#else +typedef spinlock_t *os_lock_t; +#endif + +/*! + * Context while locking. + */ +typedef unsigned long os_lock_context_t; + +/*! + * Declare a wait object for sleeping/waking processes. + */ +#define OS_WAIT_OBJECT(name) \ + DECLARE_WAIT_QUEUE_HEAD(name##_qh) + +/*! + * Driver registration handle + * + * Used with #os_driver_init_registration(), #os_driver_add_registration(), + * and #os_driver_complete_registration(). + */ +typedef struct { + unsigned reg_complete; /*!< TRUE if next inits succeeded. */ + dev_t dev; /*!< dev_t for register_chrdev() */ + struct file_operations fops; /*!< struct for register_chrdev() */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) + struct class_simple *cs; /*!< results of class_simple_create() */ +#else + struct class *cs; /*!< results of class_create() */ +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26) + struct class_device *cd; /*!< Result of class_device_create() */ +#else + struct device *cd; /*!< Result of device_create() */ +#endif + unsigned power_complete; /*!< TRUE if next inits succeeded */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + struct device_driver dd; /*!< struct for register_driver() */ +#else + struct platform_driver dd; /*!< struct for register_driver() */ +#endif + struct platform_device pd; /*!< struct for platform_register_device() */ +} os_driver_reg_t; + +/* + * Function types which can be associated with driver entry points. + * + * Note that init and shutdown are absent. + */ +/*! @{ */ +/*! Keyword for registering open() operation handler. */ +#define OS_FN_OPEN open +/*! Keyword for registering close() operation handler. */ +#define OS_FN_CLOSE release +/*! Keyword for registering read() operation handler. */ +#define OS_FN_READ read +/*! Keyword for registering write() operation handler. */ +#define OS_FN_WRITE write +/*! Keyword for registering ioctl() operation handler. */ +#define OS_FN_IOCTL ioctl +/*! Keyword for registering mmap() operation handler. */ +#define OS_FN_MMAP mmap +/*! @} */ + +/*! + * Function signature for the portable interrupt handler + * + * While it would be nice to know which interrupt is being serviced, the + * Least Common Denominator rule says that no arguments get passed in. + * + * @return Zero if not handled, non-zero if handled. + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +typedef int (*os_interrupt_handler_t) (int, void *, struct pt_regs *); +#else +typedef int (*os_interrupt_handler_t) (int, void *); +#endif + +/*! + * @defgroup dkops Driver-to-Kernel Operations + * + * These are the operations which drivers should call to get the OS to perform + * services. + */ + +/*! @addtogroup dkops */ +/*! @{ */ + +/*! + * Register an interrupt handler. + * + * @param driver_name The name of the driver + * @param interrupt_id The interrupt line to monitor (type + * #os_interrupt_id_t) + * @param function The function to be called to handle an interrupt + * + * @return #os_error_code + */ +#define os_register_interrupt(driver_name, interrupt_id, function) \ + request_irq(interrupt_id, function, 0, driver_name, NULL) + +/*! + * Deregister an interrupt handler. + * + * @param interrupt_id The interrupt line to stop monitoring + * + * @return #os_error_code + */ +#define os_deregister_interrupt(interrupt_id) \ + free_irq(interrupt_id, NULL) + +/*! + * INTERNAL implementation of os_driver_init_registration() + * + * @return An os error code. + */ +inline static int os_drv_do_init_reg(os_driver_reg_t * handle) +{ + memset(handle, 0, sizeof(*handle)); + handle->fops.owner = THIS_MODULE; + handle->power_complete = FALSE; + handle->reg_complete = FALSE; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + handle->dd.name = NULL; +#else + handle->dd.driver.name = NULL; +#endif + + return OS_ERROR_OK_S; +} + +/*! + * Initialize driver registration. + * + * If the driver handles open(), close(), ioctl(), read(), write(), or mmap() + * calls, then it needs to register their location with the kernel so that they + * get associated with the device. + * + * @param handle The handle object to be used with this registration. The + * object must live (be in memory somewhere) at least until + * os_driver_remove_registration() is called. + * + * @return A handle for further driver registration, or NULL if failed. + */ +#define os_driver_init_registration(handle) \ + os_drv_do_init_reg(&handle) + +/*! + * Add a function registration to driver registration. + * + * @param handle A handle initialized by #os_driver_init_registration(). + * @param name Which function is being supported. + * @param function The result of a call to a @c _REF version of one of the + * driver function signature macros + * @return void + */ +#define os_driver_add_registration(handle, name, function) \ + do {handle.fops.name = (void*)(function); } while (0) + +/*! + * Record 'power suspend' function for the device. + * + * @param handle A handle initialized by #os_driver_init_registration(). + * @param function Name of function to call on power suspend request + * + * Status: Provisional + * + * @return void + */ +#define os_driver_register_power_suspend(handle, function) \ + handle.dd.suspend = function + +/*! + * Record 'power resume' function for the device. + * + * @param handle A handle initialized by #os_driver_init_registration(). + * @param function Name of function to call on power resume request + * + * Status: Provisional + * + * @return void + */ +#define os_driver_register_resume(handle, function) \ + handle.dd.resume = function + +/*! + * INTERNAL function of the Linux port of the OS API. Implements the + * os_driver_complete_registration() function. + * + * @param handle The handle used with #os_driver_init_registration(). + * @param major The major device number to be associated with the driver. + * If this value is zero, a major number may be assigned. + * See #os_driver_get_major() to determine final value. + * #os_driver_remove_registration(). + * @param driver_name The driver name. Can be used as part of 'device node' + * name on platforms which support such a feature. + * + * @return An error code + */ +inline static int os_drv_do_reg(os_driver_reg_t * handle, + unsigned major, char *driver_name) +{ + os_error_code code = OS_ERROR_NO_MEMORY_S; + char *name = kmalloc(strlen(driver_name) + 1, 0); + + if (name != NULL) { + memcpy(name, driver_name, strlen(driver_name) + 1); + code = OS_ERROR_OK_S; /* OK so far */ + /* If any chardev/POSIX routines were added, then do chrdev part */ + if (handle->fops.open || handle->fops.release + || handle->fops.read || handle->fops.write + || handle->fops.ioctl || handle->fops.mmap) { + + printk("ioctl pointer: %p. mmap pointer: %p\n", + handle->fops.ioctl, handle->fops.mmap); + + /* this method is depricated, see: + * http://lwn.net/Articles/126808/ + */ + code = + register_chrdev(major, driver_name, &handle->fops); + + /* instead something like this: */ +#if 0 + handle->dev = MKDEV(major, 0); + code = + register_chrdev_region(handle->dev, 1, driver_name); + if (code < 0) { + code = OS_ERROR_FAIL_S; + } else { + cdev_init(&handle->cdev, &handle->fops); + code = cdev_add(&handle->cdev, major, 1); + } +#endif + + if (code < 0) { + code = OS_ERROR_FAIL_S; + } else { + if (code != 0) { + /* Zero was passed in for major; code is actual value */ + handle->dev = MKDEV(code, 0); + } else { + handle->dev = MKDEV(major, 0); + } + code = OS_ERROR_OK_S; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) + handle->cs = + class_simple_create(THIS_MODULE, + driver_name); + if (IS_ERR(handle->cs)) { + code = (os_error_code) handle->cs; + handle->cs = NULL; + } else { + handle->cd = + class_simple_device_add(handle->cs, + handle->dev, + NULL, + driver_name); + if (IS_ERR(handle->cd)) { + class_simple_device_remove + (handle->dev); + unregister_chrdev(MAJOR + (handle->dev), + driver_name); + code = + (os_error_code) handle->cs; + handle->cs = NULL; + } else { + handle->reg_complete = TRUE; + } + } +#else + handle->cs = + class_create(THIS_MODULE, driver_name); + if (IS_ERR(handle->cs)) { + code = (os_error_code) handle->cs; + handle->cs = NULL; + } else { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26) + handle->cd = + class_device_create(handle->cs, + NULL, + handle->dev, + NULL, + driver_name); +#else + handle->cd = + device_create(handle->cs, NULL, + handle->dev, NULL, + driver_name); +#endif + if (IS_ERR(handle->cd)) { + class_destroy(handle->cs); + unregister_chrdev(MAJOR + (handle->dev), + driver_name); + code = + (os_error_code) handle->cs; + handle->cs = NULL; + } else { + handle->reg_complete = TRUE; + } + } +#endif + } + } + /* ... fops routine registered */ + /* Handle power management fns through separate interface */ + if ((code == OS_ERROR_OK_S) && + (handle->dd.suspend || handle->dd.resume)) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + handle->dd.name = name; + handle->dd.bus = &platform_bus_type; + code = driver_register(&handle->dd); +#else + handle->dd.driver.name = name; + handle->dd.driver.bus = &platform_bus_type; + code = driver_register(&handle->dd.driver); +#endif + if (code == OS_ERROR_OK_S) { + handle->pd.name = name; + handle->pd.id = 0; + code = platform_device_register(&handle->pd); + if (code != OS_ERROR_OK_S) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + driver_unregister(&handle->dd); +#else + driver_unregister(&handle->dd.driver); +#endif + } else { + handle->power_complete = TRUE; + } + } + } /* ... suspend or resume */ + } /* name != NULL */ + return code; +} + +/*! + * Finalize the driver registration with the kernel. + * + * Upon return from this call, the driver may begin receiving calls at the + * defined entry points. + * + * @param handle The handle used with #os_driver_init_registration(). + * @param major The major device number to be associated with the driver. + * If this value is zero, a major number may be assigned. + * See #os_driver_get_major() to determine final value. + * #os_driver_remove_registration(). + * @param driver_name The driver name. Can be used as part of 'device node' + * name on platforms which support such a feature. + * + * @return An error code + */ +#define os_driver_complete_registration(handle, major, driver_name) \ + os_drv_do_reg(&handle, major, driver_name) + +/*! + * Get driver Major Number from handle after a successful registration. + * + * @param handle A handle which has completed registration. + * + * @return The major number (if any) associated with the handle. + */ +#define os_driver_get_major(handle) \ + (handle.reg_complete ? MAJOR(handle.dev) : -1) + +/*! + * INTERNAL implemention of os_driver_remove_registration. + * + * @param handle A handle initialized by #os_driver_init_registration(). + * + * @return An error code. + */ +inline static int os_drv_rmv_reg(os_driver_reg_t * handle) +{ + if (handle->reg_complete) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) + if (handle->cd != NULL) { + class_simple_device_remove(handle->dev); + handle->cd = NULL; + } + if (handle->cs != NULL) { + class_simple_destroy(handle->cs); + handle->cs = NULL; + } + unregister_chrdev(MAJOR(handle->dev), handle->dd.name); +#else + if (handle->cd != NULL) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26) + class_device_destroy(handle->cs, handle->dev); +#else + device_destroy(handle->cs, handle->dev); +#endif + handle->cd = NULL; + } + if (handle->cs != NULL) { + class_destroy(handle->cs); + handle->cs = NULL; + } + unregister_chrdev(MAJOR(handle->dev), handle->dd.driver.name); +#endif + handle->reg_complete = FALSE; + } + if (handle->power_complete) { + platform_device_unregister(&handle->pd); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + driver_unregister(&handle->dd); +#else + driver_unregister(&handle->dd.driver); +#endif + handle->power_complete = FALSE; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + if (handle->dd.name != NULL) { + kfree(handle->dd.name); + handle->dd.name = NULL; + } +#else + if (handle->dd.driver.name != NULL) { + kfree(handle->dd.driver.name); + handle->dd.driver.name = NULL; + } +#endif + return OS_ERROR_OK_S; +} + +/*! + * Remove the driver's registration with the kernel. + * + * Upon return from this call, the driver not receive any more calls at the + * defined entry points (other than ISR and shutdown). + * + * @param handle A handle initialized by #os_driver_init_registration(). + * + * @return An error code. + */ +#define os_driver_remove_registration(handle) \ + os_drv_rmv_reg(&handle) + +/*! + * Register a driver with the Linux Device Model. + * + * @param driver_information The device_driver structure information + * + * @return An error code. + * + * Status: denigrated in favor of #os_driver_complete_registration() + */ +#define os_register_to_driver(driver_information) \ + driver_register(driver_information) + +/*! + * Unregister a driver from the Linux Device Model + * + * this routine unregisters from the Linux Device Model + * + * @param driver_information The device_driver structure information + * + * @return An error code. + * + * Status: Denigrated. See #os_register_to_driver(). + */ +#define os_unregister_from_driver(driver_information) \ + driver_unregister(driver_information) + +/*! + * register a device to a driver + * + * this routine registers a drivers devices to the Linux Device Model + * + * @param device_information The platform_device structure information + * + * @return An error code. + * + * Status: denigrated in favor of #os_driver_complete_registration() + */ +#define os_register_a_device(device_information) \ + platform_device_register(device_information) + +/*! + * unregister a device from a driver + * + * this routine unregisters a drivers devices from the Linux Device Model + * + * @param device_information The platform_device structure information + * + * @return An error code. + * + * Status: Denigrated. See #os_register_a_device(). + */ +#define os_unregister_a_device(device_information) \ + platform_device_unregister(device_information) + +/*! + * Print a message to console / into log file. After the @c msg argument a + * number of printf-style arguments may be added. Types should be limited to + * printf string, char, octal, decimal, and hexadecimal types. (This excludes + * pointers, and floating point). + * + * @param msg The main text of the message to be logged + * @param s The printf-style arguments which go with msg, if any + * + * @return (void) + */ +#define os_printk(...) \ + (void) printk(__VA_ARGS__) + +/*! + * Prepare a task to execute the given function. This should only be done once + * per function,, during the driver's initialization routine. + * + * @param task_fn Name of the OS_DEV_TASK() function to be created. + * + * @return an OS ERROR code. + */ +#define os_create_task(function_name) \ + OS_ERROR_OK_S + +/*! + * Schedule execution of a task. + * + * @param function_name The function associated with the task. + * + * @return (void) + */ +#define os_dev_schedule_task(function_name) \ + tasklet_schedule(&(function_name ## let)) + +/*! + * Make sure that task is no longer running and will no longer run. + * + * This function will not return until both are true. This is useful when + * shutting down a driver. + */ +#define os_dev_stop_task(function_name) \ +do { \ + tasklet_disable(&(function_name ## let)); \ + tasklet_kill(&(function_name ## let)); \ +} while (0) + +/*! + * Allocate some kernel memory + * + * @param amount Number of 8-bit bytes to allocate + * @param flags Some indication of purpose of memory (needs definition) + * + * @return Pointer to allocated memory, or NULL if failed. + */ +#define os_alloc_memory(amount, flags) \ + (void*)kmalloc(amount, flags) + +/*! + * Free some kernel memory + * + * @param location The beginning of the region to be freed. + * + * Do some OSes have separate free() functions which should be + * distinguished by passing in @c flags here, too? Don't some also require the + * size of the buffer being freed? + */ +#define os_free_memory(location) \ + kfree(location) + +/*! + * Allocate cache-coherent memory + * + * @param amount Number of bytes to allocate + * @param[out] dma_addrp Location to store physical address of allocated + * memory. + * @param flags Some indication of purpose of memory (needs + * definition). + * + * @return (virtual space) pointer to allocated memory, or NULL if failed. + * + */ +#define os_alloc_coherent(amount, dma_addrp, flags) \ + (void*)dma_alloc_coherent(NULL, amount, dma_addrp, flags) + +/*! + * Free cache-coherent memory + * + * @param size Number of bytes which were allocated. + * @param virt_addr Virtual(kernel) address of memory.to be freed, as + * returned by #os_alloc_coherent(). + * @param dma_addr Physical address of memory.to be freed, as returned + * by #os_alloc_coherent(). + * + * @return void + * + */ +#define os_free_coherent(size, virt_addr, dma_addr) \ + dma_free_coherent(NULL, size, virt_addr, dma_addr + +/*! + * Map an I/O space into kernel memory space + * + * @param start The starting address of the (physical / io space) region + * @param range_bytes The number of bytes to map + * + * @return A pointer to the mapped area, or NULL on failure + */ +#define os_map_device(start, range_bytes) \ + (void*)ioremap_nocache((start), range_bytes) + +/*! + * Unmap an I/O space from kernel memory space + * + * @param start The starting address of the (virtual) region + * @param range_bytes The number of bytes to unmap + * + * @return None + */ +#define os_unmap_device(start, range_bytes) \ + iounmap((void*)(start)) + +/*! + * Copy data from Kernel space to User space + * + * @param to The target location in user memory + * @param from The source location in kernel memory + * @param size The number of bytes to be copied + * + * @return #os_error_code + */ +#define os_copy_to_user(to, from, size) \ + ((copy_to_user(to, from, size) == 0) ? 0 : OS_ERROR_BAD_ADDRESS_S) + +/*! + * Copy data from User space to Kernel space + * + * @param to The target location in kernel memory + * @param from The source location in user memory + * @param size The number of bytes to be copied + * + * @return #os_error_code + */ +#define os_copy_from_user(to, from, size) \ + ((copy_from_user(to, from, size) == 0) ? 0 : OS_ERROR_BAD_ADDRESS_S) + +/*! + * Read a 8-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +#define os_read8(register_address) \ + __raw_readb(register_address) + +/*! + * Write a 8-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +#define os_write8(register_address, value) \ + __raw_writeb(value, register_address) + +/*! + * Read a 16-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +#define os_read16(register_address) \ + __raw_readw(register_address) + +/*! + * Write a 16-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +#define os_write16(register_address, value) \ + __raw_writew(value, (uint32_t*)(register_address)) + +/*! + * Read a 32-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +#define os_read32(register_address) \ + __raw_readl((uint32_t*)(register_address)) + +/*! + * Write a 32-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +#define os_write32(register_address, value) \ + __raw_writel(value, register_address) + +/*! + * Read a 64-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +#define os_read64(register_address) \ + ERROR_UNIMPLEMENTED + +/*! + * Write a 64-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +#define os_write64(register_address, value) \ + ERROR_UNIMPLEMENTED + +/*! + * Delay some number of microseconds + * + * Note that this is a busy-loop, not a suspension of the task/process. + * + * @param msecs The number of microseconds to delay + * + * @return void + */ +#define os_mdelay mdelay + +/*! + * Calculate virtual address from physical address + * + * @param pa Physical address + * + * @return virtual address + * + * @note this assumes that addresses are 32 bits wide + */ +#define os_va __va + +/*! + * Calculate physical address from virtual address + * + * + * @param va Virtual address + * + * @return physical address + * + * @note this assumes that addresses are 32 bits wide + */ +#define os_pa __pa + +#ifdef CONFIG_PREEMPT_RT +/*! + * Allocate and initialize a lock, returning a lock handle. + * + * The lock state will be initialized to 'unlocked'. + * + * @return A lock handle, or NULL if an error occurred. + */ +inline static os_lock_t os_lock_alloc_init(void) +{ + raw_spinlock_t *lockp; + lockp = (raw_spinlock_t *) kmalloc(sizeof(raw_spinlock_t), 0); + if (lockp) { + _raw_spin_lock_init(lockp); + } else { + printk("OS: lock init failed\n"); + } + + return lockp; +} +#else +/*! + * Allocate and initialize a lock, returning a lock handle. + * + * The lock state will be initialized to 'unlocked'. + * + * @return A lock handle, or NULL if an error occurred. + */ +inline static os_lock_t os_lock_alloc_init(void) +{ + spinlock_t *lockp; + lockp = (spinlock_t *) kmalloc(sizeof(spinlock_t), 0); + if (lockp) { + spin_lock_init(lockp); + } else { + printk("OS: lock init failed\n"); + } + + return lockp; +} +#endif /* CONFIG_PREEMPT_RT */ + +/*! + * Acquire a lock. + * + * This function should only be called from an interrupt service routine. + * + * @param lock_handle A handle to the lock to acquire. + * + * @return void + */ +#define os_lock(lock_handle) \ + spin_lock(lock_handle) + +/*! + * Unlock a lock. Lock must have been acquired by #os_lock(). + * + * @param lock_handle A handle to the lock to unlock. + * + * @return void + */ +#define os_unlock(lock_handle) \ + spin_unlock(lock_handle) + +/*! + * Acquire a lock in non-ISR context + * + * This function will spin until the lock is available. + * + * @param lock_handle A handle of the lock to acquire. + * @param context Place to save the before-lock context + * + * @return void + */ +#define os_lock_save_context(lock_handle, context) \ + spin_lock_irqsave(lock_handle, context) + +/*! + * Release a lock in non-ISR context + * + * @param lock_handle A handle of the lock to release. + * @param context Place where before-lock context was saved. + * + * @return void + */ +#define os_unlock_restore_context(lock_handle, context) \ + spin_unlock_irqrestore(lock_handle, context) + +/*! + * Deallocate a lock handle. + * + * @param lock_handle An #os_lock_t that has been allocated. + * + * @return void + */ +#define os_lock_deallocate(lock_handle) \ + kfree(lock_handle) + +/*! + * Determine process handle + * + * The process handle of the current user is returned. + * + * @return A handle on the current process. + */ +#define os_get_process_handle() \ + current + +/*! + * Send a signal to a process + * + * @param proc A handle to the target process. + * @param sig The POSIX signal to send to that process. + */ +#define os_send_signal(proc, sig) \ + send_sig(sig, proc, 0); + +/*! + * Get some random bytes + * + * @param buf The location to store the random data. + * @param count The number of bytes to store. + * + * @return void + */ +#define os_get_random_bytes(buf, count) \ + get_random_bytes(buf, count) + +/*! + * Go to sleep on an object. + * + * @param object The object on which to sleep + * @param condition An expression to check for sleep completion. Must be + * coded so that it can be referenced more than once inside + * macro, i.e., no ++ or other modifying expressions. + * @param atomic Non-zero if sleep must not return until condition. + * + * @return error code -- OK or sleep interrupted?? + */ +#define os_sleep(object, condition, atomic) \ +({ \ + DEFINE_WAIT(_waitentry_); \ + os_error_code code = OS_ERROR_OK_S; \ + \ + while (!(condition)) { \ + prepare_to_wait(&(object##_qh), &_waitentry_, \ + atomic ? 0 : TASK_INTERRUPTIBLE); \ + if (!(condition)) { \ + schedule(); \ + } \ + \ + finish_wait(&(object##_qh), &_waitentry_); \ + \ + if (!atomic && signal_pending(current)) { \ + code = OS_ERROR_FAIL_S; /* NEED SOMETHING BETTER */ \ + break; \ + } \ + }; \ + \ + code; \ +}) + +/*! + * Wake up whatever is sleeping on sleep object + * + * @param object The object on which things might be sleeping + * + * @return none + */ +#define os_wake_sleepers(object) \ + wake_up_interruptible(&(object##_qh)); + + /*! @} *//* dkops */ + +/****************************************************************************** + * Function signature-generating macros + *****************************************************************************/ + +/*! + * @defgroup drsigs Driver Signatures + * + * These macros will define the entry point signatures for interrupt handlers; + * driver initialization and shutdown; device open/close; etc. + * + * There are two versions of each macro for a given Driver Entry Point. The + * first version is used to define a function and its implementation in the + * driver.c file, e.g. #OS_DEV_INIT(). + * + * The second form is used whenever a forward declaration (prototype) is + * needed. It has the letters @c _DCL appended to the name of the defintion + * function, and takes only the first two arguments (driver_name and + * function_name). These are not otherwise mentioned in this documenation. + * + * There is a third form used when a reference to a function is required, for + * instance when passing the routine as a pointer to a function. It has the + * letters @c _REF appended to it, and takes only the first two arguments + * (driver_name and function_name). These functions are not otherwise + * mentioned in this documentation. + * + * (Note that these two extra forms are required because of the + * possibility/likelihood of having a 'wrapper function' which invokes the + * generic function with expected arguments. An alternative would be to have a + * generic function which isn't able to get at any arguments directly, but + * would be equipped with macros which could get at information passed in. + * + * Example: + * + * (in a header file) + * @code + * OS_DEV_INIT_DCL(widget, widget_init); + * @endcode + * + * (in an implementation file) + * @code + * OS_DEV_INIT(widget, widget_init) + * { + * os_dev_init_return(TRUE); + * } + * @endcode + * + */ + +/*! @addtogroup drsigs */ +/*! @{ */ + +/*! + * Define a function which will handle device initialization + * + * This is tne driver initialization routine. This is normally where the + * part would be initialized; queues, locks, interrupts handlers defined; + * long-term dynamic memory allocated for driver use; etc. + * + * @param function_name The name of the portable initialization function. + * + * @return A call to #os_dev_init_return() + * + */ +#define OS_DEV_INIT(function_name) \ +module_init(function_name); \ +static int __init function_name (void) + +/*! Make declaration for driver init function. + * @param function_name foo + */ +#define OS_DEV_INIT_DCL(function_name) \ +static int __init function_name (void); + +/*! + * Generate a function reference to the driver's init function. + * @param function_name Name of the OS_DEV_INIT() function. + * + * @return A function pointer. + */ +#define OS_DEV_INIT_REF(function_name) \ +function_name + +/*! + * Define a function which will handle device shutdown + * + * This is the inverse of the #OS_DEV_INIT() routine. + * + * @param function_name The name of the portable driver shutdown routine. + * + * @return A call to #os_dev_shutdown_return() + * + */ +#define OS_DEV_SHUTDOWN(function_name) \ +module_exit(function_name); \ +static void function_name(void) + +/*! + * Generate a function reference to the driver's shutdown function. + * @param function_name Name of the OS_DEV_HUSTDOWN() function. + * + * @return A function pointer. + */ +#define OS_DEV_SHUTDOWN_DCL(function_name) \ +static void function_name(void); + +/*! + * Generate a reference to driver's shutdown function + * @param function_name Name of the OS_DEV_HUSTDOWN() function. +*/ + +#define OS_DEV_SHUTDOWN_REF(function_name) \ +function_name + +/*! + * Define a function which will open the device for a user. + * + * @param function_name The name of the driver open() function + * + * @return A call to #os_dev_open_return() + */ +#define OS_DEV_OPEN(function_name) \ +static int function_name(struct inode* inode_p_, struct file* file_p_) + +/*! + * Declare prototype for an open() function. + * + * @param function_name The name of the OS_DEV_OPEN() function. + */ +#define OS_DEV_OPEN_DCL(function_name) \ +OS_DEV_OPEN(function_name); + +/*! + * Generate a function reference to the driver's open() function. + * @param function_name Name of the OS_DEV_OPEN() function. + * + * @return A function pointer. + */ +#define OS_DEV_OPEN_REF(function_name) \ +function_name + +/*! + * Define a function which will handle a user's ioctl() request + * + * @param function_name The name of the driver ioctl() function + * + * @return A call to #os_dev_ioctl_return() + */ +#define OS_DEV_IOCTL(function_name) \ +static int function_name(struct inode* inode_p_, struct file* file_p_, \ + unsigned int cmd_, unsigned long data_) + +/*! Boo. */ +#define OS_DEV_IOCTL_DCL(function_name) \ +OS_DEV_IOCTL(function_name); + +/*! + * Generate a function reference to the driver's ioctl() function. + * @param function_name Name of the OS_DEV_IOCTL() function. + * + * @return A function pointer. + */ +#define OS_DEV_IOCTL_REF(function_name) \ +function_name + +/*! + * Define a function which will handle a user's mmap() request + * + * @param function_name The name of the driver mmap() function + * + * @return A call to #os_dev_ioctl_return() + */ +#define OS_DEV_MMAP(function_name) \ +int function_name(struct file* file_p_, struct vm_area_struct* vma_) + +#define OS_DEV_MMAP_DCL(function_name) \ +OS_DEV_MMAP(function_name); + +#define OS_DEV_MMAP_REF(function_name) \ +function_name + +/* Retrieve the context to the memory structure that is to be MMAPed */ +#define os_mmap_memory_ctx() (vma_) + +/* Determine the size of the requested MMAP region*/ +#define os_mmap_memory_size() (vma_->vm_end - vma_->vm_start) + +/* Determine the base address of the requested MMAP region*/ +#define os_mmap_user_base() (vma_->vm_start) + +/*! + * Declare prototype for an read() function. + * + * @param function_name The name of the driver read function. + */ +#define OS_DEV_READ_DCL(function_name) \ +OS_DEV_READ(function_name); + +/*! + * Generate a function reference to the driver's read() routine + * @param function_name Name of the OS_DEV_READ() function. + * + * @return A function pointer. + */ +#define OS_DEV_READ_REF(function_name) \ +function_name + +/*! + * Define a function which will handle a user's write() request + * + * @param function_name The name of the driver write() function + * + * @return A call to #os_dev_write_return() + */ +#define OS_DEV_WRITE(function_name) \ +static ssize_t function_name(struct file* file_p_, char* user_buffer_, \ + size_t count_bytes_, loff_t* file_position_) + +/*! + * Declare prototype for an write() function. + * + * @param function_name The name of the driver write function. + */ +#define OS_DEV_WRITE_DCL(function_name) \ +OS_DEV_WRITE(function_name); + +/*! + * Generate a function reference to the driver's write() routine + * @param function_name Name of the OS_DEV_WRITE() function. + * + * @return A function pointer. + */ +#define OS_DEV_WRITE_REF(function_name) \ +function_name + +/*! + * Define a function which will close the device - opposite of OS_DEV_OPEN() + * + * @param function_name The name of the driver close() function + * + * @return A call to #os_dev_close_return() + */ +#define OS_DEV_CLOSE(function_name) \ +static int function_name(struct inode* inode_p_, struct file* file_p_) + +/*! + * Declare prototype for an close() function + * + * @param function_name The name of the driver close() function. + */ +#define OS_DEV_CLOSE_DCL(function_name) \ +OS_DEV_CLOSE(function_name); + +/*! + * Generate a function reference to the driver's close function. + * @param function_name Name of the OS_DEV_CLOSE() function. + * + * @return A function pointer. + */ +#define OS_DEV_CLOSE_REF(function_name) \ +function_name + +/*! + * Define a function which will handle an interrupt + * + * No arguments are available to the generic function. It must not invoke any + * OS functions which are illegal in a ISR. It gets no parameters, and must + * have a call to #os_dev_isr_return() instead of any/all return statements. + * + * Example: + * @code + * OS_DEV_ISR(widget) + * { + * os_dev_isr_return(1); + * } + * @endcode + * + * @param function_name The name of the driver ISR function + * + * @return A call to #os_dev_isr_return() + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +#define OS_DEV_ISR(function_name) \ +static irqreturn_t function_name(int N1_, void* N2_, struct pt_regs* N3_) +#else +#define OS_DEV_ISR(function_name) \ +static irqreturn_t function_name(int N1_, void* N2_) +#endif + +/*! + * Declare prototype for an ISR function. + * + * @param function_name The name of the driver ISR function. + */ +#define OS_DEV_ISR_DCL(function_name) \ +OS_DEV_ISR(function_name); + +/*! + * Generate a function reference to the driver's interrupt service routine + * @param function_name Name of the OS_DEV_ISR() function. + * + * @return A function pointer. + */ +#define OS_DEV_ISR_REF(function_name) \ +function_name + +/*! + * Define a function which will operate as a background task / bottom half. + * + * Tasklet stuff isn't strictly limited to 'Device drivers', but leave it + * this namespace anyway. + * + * @param function_name The name of this background task function + * + * @return A call to #os_dev_task_return() + */ +#define OS_DEV_TASK(function_name) \ +static void function_name(unsigned long data_) + +/*! + * Declare prototype for a background task / bottom half function + * + * @param function_name The name of this background task function + */ +#define OS_DEV_TASK_DCL(function_name) \ +OS_DEV_TASK(function_name); \ +DECLARE_TASKLET(function_name ## let, function_name, 0); + +/*! + * Generate a reference to an #OS_DEV_TASK() function + * + * @param function_name The name of the task being referenced. + */ +#define OS_DEV_TASK_REF(function_name) \ + (function_name ## let) + + /*! @} *//* drsigs */ + +/***************************************************************************** + * Functions/Macros for returning values from Driver Signature routines + *****************************************************************************/ + +/*! + * Return from the #OS_DEV_INIT() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_init_return(code) \ + return code + +/*! + * Return from the #OS_DEV_SHUTDOWN() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_shutdown_return(code) \ + return + +/*! + * Return from the #OS_DEV_ISR() function + * + * The function should verify that it really was supposed to be called, + * and that its device needed attention, in order to properly set the + * return code. + * + * @param code non-zero if interrupt handled, zero otherwise. + * + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +#define os_dev_isr_return(code) \ +do { \ + /* Unused warnings */ \ + (void)N1_; \ + (void)N2_; \ + (void)N3_; \ + \ + return IRQ_RETVAL(code); \ +} while (0) +#else +#define os_dev_isr_return(code) \ +do { \ + /* Unused warnings */ \ + (void)N1_; \ + (void)N2_; \ + \ + return IRQ_RETVAL(code); \ +} while (0) +#endif + +/*! + * Return from the #OS_DEV_OPEN() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_open_return(code) \ +do { \ + int retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)inode_p_; \ + (void)file_p_; \ + \ + return retcode; \ +} while (0) + +/*! + * Return from the #OS_DEV_IOCTL() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_ioctl_return(code) \ +do { \ + int retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)inode_p_; \ + (void)file_p_; \ + (void)cmd_; \ + (void)data_; \ + \ + return retcode; \ +} while (0) + +/*! + * Return from the #OS_DEV_READ() function + * + * @param code Number of bytes read, or an error code to report failure. + * + */ +#define os_dev_read_return(code) \ +do { \ + ssize_t retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)file_p_; \ + (void)user_buffer_; \ + (void)count_bytes_; \ + (void)file_position_; \ + \ + return retcode; \ +} while (0) + +/*! + * Return from the #OS_DEV_WRITE() function + * + * @param code Number of bytes written, or an error code to report failure. + * + */ +#define os_dev_write_return(code) \ +do { \ + ssize_t retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)file_p_; \ + (void)user_buffer_; \ + (void)count_bytes_; \ + (void)file_position_; \ + \ + return retcode; \ +} while (0) + +/*! + * Return from the #OS_DEV_CLOSE() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_close_return(code) \ +do { \ + ssize_t retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)inode_p_; \ + (void)file_p_; \ + \ + return retcode; \ +} while (0) + +/*! + * Start the #OS_DEV_TASK() function + * + * In some implementations, this could be turned into a label for + * the os_dev_task_return() call. + * + * @return none + */ +#define os_dev_task_begin() + +/*! + * Return from the #OS_DEV_TASK() function + * + * In some implementations, this could be turned into a sleep followed + * by a jump back to the os_dev_task_begin() call. + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_task_return(code) \ +do { \ + /* Unused warnings */ \ + (void)data_; \ + \ + return; \ +} while (0) + +/***************************************************************************** + * Functions/Macros for accessing arguments from Driver Signature routines + *****************************************************************************/ + +/*! @defgroup drsigargs Functions for Getting Arguments in Signature functions + * + */ +/* @addtogroup @drsigargs */ +/*! @{ */ +/*! + * Used in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() and + * #OS_DEV_WRITE() routines to check whether user is requesting read + * (permission) + */ +#define os_dev_is_flag_read() \ + (file_p_->f_mode & FMODE_READ) + +/*! + * Used in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() and + * #OS_DEV_WRITE() routines to check whether user is requesting write + * (permission) + */ +#define os_dev_is_flag_write() \ + (file_p_->f_mode & FMODE_WRITE) + +/*! + * Used in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() and + * #OS_DEV_WRITE() routines to check whether user is requesting non-blocking + * I/O. + */ +#define os_dev_is_flag_nonblock() \ + (file_p_->f_flags & (O_NONBLOCK | O_NDELAY)) + +/*! + * Used in #OS_DEV_OPEN() and #OS_DEV_CLOSE() to determine major device being + * accessed. + */ +#define os_dev_get_major() \ + (imajor(inode_p_)) + +/*! + * Used in #OS_DEV_OPEN() and #OS_DEV_CLOSE() to determine minor device being + * accessed. + */ +#define os_dev_get_minor() \ + (iminor(inode_p_)) + +/*! + * Used in #OS_DEV_IOCTL() to determine which operation the user wants + * performed. + * + * @return Value of the operation. + */ +#define os_dev_get_ioctl_op() \ + (cmd_) + +/*! + * Used in #OS_DEV_IOCTL() to return the associated argument for the desired + * operation. + * + * @return A value which can be cast to a struct pointer or used as + * int/long. + */ +#define os_dev_get_ioctl_arg() \ + (data_) + +/*! + * Used in OS_DEV_READ() and OS_DEV_WRITE() routines to access the requested + * byte count. + * + * @return (unsigned) a count of bytes + */ +#define os_dev_get_count() \ + ((unsigned)count_bytes_) + +/*! + * Used in OS_DEV_READ() and OS_DEV_WRITE() routines to return the pointer + * byte count. + * + * @return char* pointer to user buffer + */ +#define os_dev_get_user_buffer() \ + ((void*)user_buffer_) + +/*! + * Used in OS_DEV_READ(), OS_DEV_WRITE(), and OS_DEV_IOCTL() routines to + * get the POSIX flags field for the associated open file). + * + * @return The flags associated with the file. + */ +#define os_dev_get_file_flags() \ + (file_p_->f_flags) + +/*! + * Set the driver's private structure associated with this file/open. + * + * Generally used during #OS_DEV_OPEN(). See #os_dev_get_user_private(). + * + * @param struct_p The driver data structure to associate with this user. + */ +#define os_dev_set_user_private(struct_p) \ + file_p_->private_data = (void*)(struct_p) + +/*! + * Get the driver's private structure associated with this file. + * + * May be used during #OS_DEV_OPEN(), #OS_DEV_READ(), #OS_DEV_WRITE(), + * #OS_DEV_IOCTL(), and #OS_DEV_CLOSE(). See #os_dev_set_user_private(). + * + * @return The driver data structure to associate with this user. + */ +#define os_dev_get_user_private() \ + ((void*)file_p_->private_data) + +/*! + * Get the IRQ associated with this call to the #OS_DEV_ISR() function. + * + * @return The IRQ (integer) interrupt number. + */ +#define os_dev_get_irq() \ + N1_ + + /*! @} *//* drsigargs */ + +/*! + * @defgroup cacheops Cache Operations + * + * These functions are for synchronizing processor cache with RAM. + */ +/*! @addtogroup cacheops */ +/*! @{ */ + +/*! + * Flush and invalidate all cache lines. + */ +#if 0 +#define os_flush_cache_all() \ + flush_cache_all() +#else +/* Call ARM fn directly, in case L2cache=on3 not set */ +#define os_flush_cache_all() \ + v6_flush_kern_cache_all_L2() + +/*! + * ARM-routine to flush all cache. Defined here, because it exists in no + * easy-access header file. ARM-11 with L210 cache only! + */ +extern void v6_flush_kern_cache_all_L2(void); +#endif + +/* + * These macros are using part of the Linux DMA API. They rely on the + * map function to do nothing more than the equivalent clean/inv/flush + * operation at the time of the mapping, and do nothing at an unmapping + * call, which the Sahara driver code will never invoke. + */ + +/*! + * Clean a range of addresses from the cache. That is, write updates back + * to (RAM, next layer). + * + * @param start Starting virtual address + * @param len Number of bytes to flush + * + * @return void + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,21) +#define os_cache_clean_range(start,len) \ + dma_map_single(NULL, (void*)start, len, DMA_TO_DEVICE) +#else +#define os_cache_clean_range(start,len) \ +{ \ + void *s = (void*)start; \ + void *e = s + len; \ + dmac_clean_range(s, e); \ + outer_clean_range(__pa(s), __pa(e)); \ +} +#endif + +/*! + * Invalidate a range of addresses in the cache + * + * @param start Starting virtual address + * @param len Number of bytes to flush + * + * @return void + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,21) +#define os_cache_inv_range(start,len) \ + dma_map_single(NULL, (void*)start, len, DMA_FROM_DEVICE) +#else +#define os_cache_inv_range(start,len) \ +{ \ + void *s = (void*)start; \ + void *e = s + len; \ + dmac_inv_range(s, e); \ + outer_inv_range(__pa(s), __pa(e)); \ +} +#endif + +/*! + * Flush a range of addresses from the cache. That is, perform clean + * and invalidate + * + * @param start Starting virtual address + * @param len Number of bytes to flush + * + * @return void + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,21) +#define os_cache_flush_range(start,len) \ + dma_map_single(NULL, (void*)start, len, DMA_BIDIRECTIONAL) +#else +#define os_cache_flush_range(start,len) \ +{ \ + void *s = (void*)start; \ + void *e = s + len; \ + dmac_flush_range(s, e); \ + outer_flush_range(__pa(s), __pa(e)); \ +} +#endif + + /*! @} *//* cacheops */ + +#endif /* LINUX_PORT_H */ diff --git a/drivers/mxc/security/sahara2/include/platform_abstractions.h b/drivers/mxc/security/sahara2/include/platform_abstractions.h new file mode 100644 index 000000000000..c2f9c489eb0b --- /dev/null +++ b/drivers/mxc/security/sahara2/include/platform_abstractions.h @@ -0,0 +1,15 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @file platform_abstractions.h + */ diff --git a/drivers/mxc/security/sahara2/include/portable_os.h b/drivers/mxc/security/sahara2/include/portable_os.h new file mode 100644 index 000000000000..e904b3222cbb --- /dev/null +++ b/drivers/mxc/security/sahara2/include/portable_os.h @@ -0,0 +1,1453 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef PORTABLE_OS_H +#define PORTABLE_OS_H + +/***************************************************************************/ + +/* + * Add support for your target OS by checking appropriate flags and then + * including the appropriate file. Don't forget to document the conditions + * in the later documentation section at the beginning of the + * DOXYGEN_PORTABLE_OS_DOC. + */ + +#if defined(LINUX_KERNEL) + +#include "linux_port.h" + +#elif defined(PORTABLE_OS) + +#include "check_portability.h" + +#else + +#error Target OS unsupported or unspecified + +#endif + + +/***************************************************************************/ + +/*! + * @file portable_os.h + * + * This file should be included by portable driver code in order to gain access + * to the OS-specific header files. It is the only OS-related header file that + * the writer of a portable driver should need. + * + * This file also contains the documentation for the common API. + * + * Begin reading the documentation for this file at the @ref index "main page". + * + */ + +/*! + * @if USE_MAINPAGE + * @mainpage Generic OS API for STC Drivers + * @endif + * + * @section intro_sec Introduction + * + * This defines the API / kernel programming environment for portable drivers. + * + * This API is broken up into several functional areas. It greatly limits the + * choices of a device-driver author, but at the same time should allow for + * greater portability of the resulting code. + * + * Each kernel-to-driver function (initialization function, interrupt service + * routine, etc.) has a 'portable signature' which must be used, and a specific + * function which must be called to generate the return statement. There is + * one exception, a background task or "bottom half" routine, which instead has + * a specific structure which must be followed. These signatures and function + * definitions are found in @ref drsigs. + * + * None of these kernel-to-driver functions seem to get any arguments passed to + * them. Instead, there are @ref drsigargs which allow one of these functions + * to get at fairly generic parts of its calling arguments, if there are any. + * + * Almost every driver will have some need to call the operating system + * @ref dkops is the list of services which are available to the driver. + * + * + * @subsection warn_sec Warning + * + * The specifics of the types, values of the various enumerations + * (unless specifically stated, like = 0), etc., are only here for illustrative + * purposes. No attempts should be made to make use of any specific knowledge + * gleaned from this documentation. These types are only meant to be passed in + * and out of the API, and their contents are to be handled only by the + * provided OS-specific functions. + * + * Also, note that the function may be provided as macros in some + * implementations, or vice versa. + * + * + * @section dev_sec Writing a Portable Driver + * + * First off, writing a portable driver means calling no function in an OS + * except what is available through this header file. + * + * Secondly, all OS-called functions in your driver must be invoked and + * referenced through the signature routines. + * + * Thirdly, there might be some rules which you can get away with ignoring or + * violating on one OS, yet will cause your code not to be portable to a + * different OS. + * + * + * @section limit_sec Limitations + * + * This API is not expected to handle every type of driver which may be found + * in an operating system. For example, it will not be able to handle the + * usual design for something like a UART driver, where there are multiple + * logical devices to access, because the design usually calls for some sort of + * indication to the #OS_DEV_TASK() function or OS_DEV_ISR() to indicate which + * channel is to be serviced by that instance of the task/function. This sort + * of argument is missing in this API for functions like os_dev_schedule_task() and + * os_register_interrupt(). + * + * + * @section port_guide Porting Guidelines + * + * This section is intended for a developer who needs to port the header file + * to an operating system which is not yet supported. + * + * This interface allows for a lot of flexibility when it comes to porting to + * an operating systems device driver interface. There are three main areas to + * examine: The use of Driver Routine Signatures, the use of Driver Argument + * Access functions, the Calls to Kernel Functions, and Data Types. + * + * + * @subsection port_sig Porting Driver Routine Signatures + * + * The three macros for each function (e.g. #OS_DEV_INIT(), #OS_DEV_INIT_DCL(), + * and #OS_DEV_INIT_REF()) allow the flexibility of having a 'wrapper' function + * with the OS-required signature, which would then call the user's function + * with some different signature. + * + * The first form would lay down the wrapper function, followed by the + * signature for the user function. The second form would lay down just the + * signatures for both functions, and the last function would reference the + * wrapper function, since that is the interface function called by the OS. + * + * Note that the driver author has no visibility at all to the signature of the + * routines. The author can access arguments only through a limited set of + * functions, and must return via another function. + * + * The Return Functions allow a lot of flexibility in converting the return + * value, or not returning a value at all. These will likely be implemented as + * macros. + * + * + * @subsection port_arg Porting Driver Argument Access Functions + * + * The signatures defined by the guide will usually be replaced with macro + * definitions. + * + * + * @subsection port_dki Porting Calls to Kernel Functions + * + * The signatures defined by the guide may be replaced with macro definitions, + * if that makes more sense. + * + * Implementors are free to ignore arguments which are not applicable to their + * OS. + * + * @subsection port_datatypes Porting Data Types + * + * + */ + +/*************************************************************************** + * Compile flags + **************************************************************************/ + +/* + * This compile flag should only be turned on when running doxygen to generate + * the API documentation. + */ +#ifdef DOXYGEN_PORTABLE_OS_DOC + +/*! + * @todo module_init()/module_cleanup() for Linux need to be added to OS + * abstractions. Also need EXPORT_SYMBOL() equivalent?? + * + */ + +/* Drop OS differentation documentation here */ + +/*! + * \#define this flag to build your driver as a Linux driver + */ +#define LINUX + +/* end OS differentation documentation */ + +/*! + * Symbol to give version number of the implementation file. Earliest + * definition is in version 1.1, with value 101 (to mean version 1.1) + */ +#define PORTABLE_OS_VERSION 101 + +/* + * NOTICE: The following definitions (the rest of the file) are not meant ever + * to be compiled. Instead, they are the documentation for the portable OS + * API, to be used by driver writers. + * + * Each individual OS port will define each of these types, functions, or + * macros as appropriate to the target OS. This is why they are under the + * DOXYGEN_PORTABLE_OS_DOC flag. + */ + +/*************************************************************************** + * Type definitions + **************************************************************************/ + +/*! + * Type used for registering and deregistering interrupts. + * + * This is typically an interrupt channel number. + */ +typedef int os_interrupt_id_t; + +/*! + * Type used as handle for a process + * + * See #os_get_process_handle() and #os_send_signal(). + */ +typedef int os_process_handle_t; + +/*! + * Generic return code for functions which need such a thing. + * + * No knowledge should be assumed of the value of any of these symbols except + * that @c OS_ERROR_OK_S is guaranteed to be zero. + * + * @todo Any other named values? What about (-EAGAIN? -ERESTARTSYS? Are they + * too Linux/Unix-specific read()/write() return values) ? + */ +typedef enum { + OS_ERROR_OK_S = 0, /*!< Success */ + OS_ERROR_FAIL_S, /*!< Generic driver failure */ + OS_ERROR_NO_MEMORY_S, /*!< Failure to acquire/use memory */ + OS_ERROR_BAD_ADDRESS_S, /*!< Bad address */ + OS_ERROR_BAD_ARG_S /*!< Bad input argument */ +} os_error_code; + +/*! + * Handle to a lock. + */ +typedef int *os_lock_t; + +/*! + * Context while locking. + */ +typedef int os_lock_context_t; + +/*! + * An object which can be slept on and later used to wake any/all sleepers. + */ +typedef int os_sleep_object_t; + +/*! + * Driver registration handle + */ +typedef void *os_driver_reg_t; + +/*! + * Function signature for an #OS_DEV_INIT() function. + * + * @return A call to os_dev_init_return() function. + */ +typedef void (*os_init_function_t) (void); + +/*! + * Function signature for an #OS_DEV_SHUTDOWN() function. + * + * @return A call to os_dev_shutdown_return() function. + */ +typedef void (*os_shutdown_function_t) (void); + +/*! + * Function signature for a user-driver function. + * + * @return A call to the appropriate os_dev_xxx_return() function. + */ +typedef void (*os_user_function_t) (void); + +/*! + * Function signature for the portable interrupt handler + * + * While it would be nice to know which interrupt is being serviced, the + * Least Common Denominator rule says that no arguments get passed in. + * + * @return A call to #os_dev_isr_return() + */ +typedef void (*os_interrupt_handler_t) (void); + +/*! + * Function signature for a task function + * + * Many task function definitions get some sort of generic argument so that the + * same function can run across many (queues, channels, ...) as separate task + * instances. This has been left out of this API. + * + * This function must be structured as documented by #OS_DEV_TASK(). + * + */ +typedef void (*os_task_fn_t) (void); + +/*! + * Function types which can be associated with driver entry points. These are + * used in os_driver_add_registration(). + * + * Note that init and shutdown are absent. + */ +typedef enum { + OS_FN_OPEN, /*!< open() operation handler. */ + OS_FN_CLOSE, /*!< close() operation handler. */ + OS_FN_READ, /*!< read() operation handler. */ + OS_FN_WRITE, /*!< write() operation handler. */ + OS_FN_IOCTL, /*!< ioctl() operation handler. */ + OS_FN_MMAP /*!< mmap() operation handler. */ +} os_driver_fn_t; + +/*************************************************************************** + * Driver-to-Kernel Operations + **************************************************************************/ + +/*! + * @defgroup dkops Driver-to-Kernel Operations + * + * These are the operations which drivers should call to get the OS to perform + * services. + */ + +/*! @addtogroup dkops */ +/*! @{ */ + +/*! + * Register an interrupt handler. + * + * @param driver_name The name of the driver + * @param interrupt_id The interrupt line to monitor (type + * #os_interrupt_id_t) + * @param function The function to be called to handle an interrupt + * + * @return #os_error_code + */ +os_error_code os_register_interrupt(char *driver_name, + os_interrupt_id_t interrupt_id, + os_interrupt_handler_t function); + +/*! + * Deregister an interrupt handler. + * + * @param interrupt_id The interrupt line to stop monitoring + * + * @return #os_error_code + */ +os_error_code os_deregister_interrupt(os_interrupt_id_t interrupt_id); + +/*! + * Initialize driver registration. + * + * If the driver handles open(), close(), ioctl(), read(), write(), or mmap() + * calls, then it needs to register their location with the kernel so that they + * get associated with the device. + * + * @param handle The handle object to be used with this registration. The + * object must live (be in memory somewhere) at least until + * os_driver_remove_registration() is called. + * + * @return An os error code. + */ +os_error_code os_driver_init_registration(os_driver_reg_t handle); + +/*! + * Add a function registration to driver registration. + * + * @param handle The handle used with #os_driver_init_registration(). + * @param name Which function is being supported. + * @param function The result of a call to a @c _REF version of one of the + * driver function signature macros + * driver function signature macros + * @return void + */ +void os_driver_add_registration(os_driver_reg_t handle, os_driver_fn_t name, + void *function); + +/*! + * Finalize the driver registration with the kernel. + * + * Upon return from this call, the driver may begin receiving calls at the + * defined entry points. + * + * @param handle The handle used with #os_driver_init_registration(). + * @param major The major device number to be associated with the driver. + * If this value is zero, a major number may be assigned. + * See #os_driver_get_major() to determine final value. + * #os_driver_remove_registration(). + * @param driver_name The driver name. Can be used as part of 'device node' + * name on platforms which support such a feature. + * + * @return An error code + */ +os_error_code os_driver_complete_registration(os_driver_reg_t handle, + int major, char *driver_name); + +/*! + * Get driver Major Number from handle after a successful registration. + * + * @param handle A handle which has completed registration. + * + * @return The major number (if any) associated with the handle. + */ +uint32_t os_driver_get_major(os_driver_reg_t handle); + +/*! + * Remove the driver's registration with the kernel. + * + * Upon return from this call, the driver not receive any more calls at the + * defined entry points (other than ISR and shutdown). + * + * @param major The major device number to be associated with the driver. + * @param driver_name The driver name + * + * @return An error code. + */ +os_error_code os_driver_remove_registration(int major, char *driver_name); + +/*! + * Print a message to console / into log file. After the @c msg argument a + * number of printf-style arguments may be added. Types should be limited to + * printf string, char, octal, decimal, and hexadecimal types. (This excludes + * pointers, and floating point). + * + * @param msg The message to print to console / system log + * + * @return (void) + */ +void os_printk(char *msg, ...); + +/*! + * Allocate some kernel memory + * + * @param amount Number of 8-bit bytes to allocate + * @param flags Some indication of purpose of memory (needs definition) + * + * @return Pointer to allocated memory, or NULL if failed. + */ +void *os_alloc_memory(unsigned amount, int flags); + +/*! + * Free some kernel memory + * + * @param location The beginning of the region to be freed. + * + * Do some OSes have separate free() functions which should be + * distinguished by passing in @c flags here, too? Don't some also require the + * size of the buffer being freed? Perhaps separate routines for each + * alloc/free pair (DMAable, etc.)? + */ +void os_free_memory(void *location); + +/*! + * Allocate cache-coherent memory + * + * @param amount Number of bytes to allocate + * @param[out] dma_addrp Location to store physical address of allocated + * memory. + * @param flags Some indication of purpose of memory (needs + * definition). + * + * @return (virtual space) pointer to allocated memory, or NULL if failed. + * + */ +void *os_alloc_coherent(unsigned amount, uint32_t * dma_addrp, int flags); + +/*! + * Free cache-coherent memory + * + * @param size Number of bytes which were allocated. + * @param[out] virt_addr Virtual(kernel) address of memory.to be freed, as + * returned by #os_alloc_coherent(). + * @param[out] dma_addr Physical address of memory.to be freed, as returned + * by #os_alloc_coherent(). + * + * @return void + * + */ +void os_free_coherent(unsigned size, void *virt_addr, uint32_t dma_addr); + +/*! + * Map an I/O space into kernel memory space + * + * @param start The starting address of the (physical / io space) region + * @param range_bytes The number of bytes to map + * + * @return A pointer to the mapped area, or NULL on failure + */ +void *os_map_device(uint32_t start, unsigned range_bytes); + +/*! + * Unmap an I/O space from kernel memory space + * + * @param start The starting address of the (virtual) region + * @param range_bytes The number of bytes to unmap + * + * @return None + */ +void os_unmap_device(void *start, unsigned range_bytes); + +/*! + * Copy data from Kernel space to User space + * + * @param to The target location in user memory + * @param from The source location in kernel memory + * @param size The number of bytes to be copied + * + * @return #os_error_code + */ +os_error_code os_copy_to_user(void *to, void *from, unsigned size); + +/*! + * Copy data from User space to Kernel space + * + * @param to The target location in kernel memory + * @param from The source location in user memory + * @param size The number of bytes to be copied + * + * @return #os_error_code + */ +os_error_code os_copy_from_user(void *to, void *from, unsigned size); + +/*! + * Read an 8-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +uint8_t os_read8(uint8_t * register_address); + +/*! + * Write an 8-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +void os_write8(uint8_t * register_address, uint8_t value); + +/*! + * Read a 16-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +uint16_t os_read16(uint16_t * register_address); + +/*! + * Write a 16-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +void os_write16(uint16_t * register_address, uint16_t value); + +/*! + * Read a 32-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +uint32_t os_read32(uint32_t * register_address); + +/*! + * Write a 32-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +void os_write32(uint32_t * register_address, uint32_t value); + +/*! + * Read a 64-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +uint64_t os_read64(uint64_t * register_address); + +/*! + * Write a 64-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +void os_write64(uint64_t * register_address, uint64_t value); + +/*! + * Prepare a task to execute the given function. This should only be done once + * per task, during the driver's initialization routine. + * + * @param task_fn Name of the OS_DEV_TASK() function to be created. + * + * @return an OS ERROR code. + */ +os_error os_create_task(os_task_fn_t * task_fn); + +/*! + * Run the task associated with an #OS_DEV_TASK() function + * + * The task will begin execution sometime after or during this call. + * + * @param task_fn Name of the OS_DEV_TASK() function to be scheduled. + * + * @return void + */ +void os_dev_schedule_task(os_task_fn_t * task_fn); + +/*! + * Make sure that task is no longer running and will no longer run. + * + * This function will not return until both are true. This is useful when + * shutting down a driver. + * + * @param task_fn Name of the OS_DEV_TASK() funciton to be stopped. + * + */ +void os_stop_task(os_task_fn_t * task_fn); + +/*! + * Delay some number of microseconds + * + * Note that this is a busy-loop, not a suspension of the task/process. + * + * @param msecs The number of microseconds to delay + * + * @return void + */ +void os_mdelay(unsigned long msecs); + +/*! + * Calculate virtual address from physical address + * + * @param pa Physical address + * + * @return virtual address + * + * @note this assumes that addresses are 32 bits wide + */ +void *os_va(uint32_t pa); + +/*! + * Calculate physical address from virtual address + * + * + * @param va Virtual address + * + * @return physical address + * + * @note this assumes that addresses are 32 bits wide + */ +uint32_t os_pa(void *va); + +/*! + * Allocate and initialize a lock, returning a lock handle. + * + * The lock state will be initialized to 'unlocked'. + * + * @return A lock handle, or NULL if an error occurred. + */ +os_lock_t os_lock_alloc_init(void); + +/*! + * Acquire a lock. + * + * This function should only be called from an interrupt service routine. + * + * @param lock_handle A handle to the lock to acquire. + * + * @return void + */ +void os_lock(os_lock_t lock_handle); + +/*! + * Unlock a lock. Lock must have been acquired by #os_lock(). + * + * @param lock_handle A handle to the lock to unlock. + * + * @return void + */ +void os_unlock(os_lock_t lock_handle); + +/*! + * Acquire a lock in non-ISR context + * + * This function will spin until the lock is available. + * + * @param lock_handle A handle of the lock to acquire. + * @param context Place to save the before-lock context + * + * @return void + */ +void os_lock_save_context(os_lock_t lock_handle, os_lock_context_t context); + +/*! + * Release a lock in non-ISR context + * + * @param lock_handle A handle of the lock to release. + * @param context Place where before-lock context was saved. + * + * @return void + */ +void os_unlock_restore_context(os_lock_t lock_handle, + os_lock_context_t context); + +/*! + * Deallocate a lock handle. + * + * @param lock_handle An #os_lock_t that has been allocated. + * + * @return void + */ +void os_lock_deallocate(os_lock_t lock_handle); + +/*! + * Determine process handle + * + * The process handle of the current user is returned. + * + * @return A handle on the current process. + */ +os_process_handle_t os_get_process_handle(); + +/*! + * Send a signal to a process + * + * @param proc A handle to the target process. + * @param sig The POSIX signal to send to that process. + */ +void os_send_signal(os_process_handle_t proc, int sig); + +/*! + * Get some random bytes + * + * @param buf The location to store the random data. + * @param count The number of bytes to store. + * + * @return void + */ +void os_get_random_bytes(void *buf, unsigned count); + +/*! + * Go to sleep on an object. + * + * Example: code = os_sleep(my_queue, available_count == 0, 0); + * + * @param object The object on which to sleep + * @param condition An expression to check for sleep completion. Must be + * coded so that it can be referenced more than once inside + * macro, i.e., no ++ or other modifying expressions. + * @param atomic Non-zero if sleep must not return until condition. + * + * @return error code -- OK or sleep interrupted?? + */ +os_error_code os_sleep(os_sleep_object_t object, unsigned condition, + unsigned atomic); + +/*! + * Wake up whatever is sleeping on sleep object + * + * @param object The object on which things might be sleeping + * + * @return none + */ +void os_wake_sleepers(os_sleep_object_t object); + + /*! @} *//* dkops */ + +/***************************************************************************** + * Function-signature-generating macros + *****************************************************************************/ + +/*! + * @defgroup drsigs Driver Function Signatures + * + * These macros will define the entry point signatures for interrupt handlers; + * driver initialization and shutdown; device open/close; etc. They are to be + * used whenever the Kernel will call into the Driver. They are not + * appropriate for driver calls to other routines in the driver. + * + * There are three versions of each macro for a given Driver Entry Point. The + * first version is used to define a function and its implementation in the + * driver.c file, e.g. #OS_DEV_INIT(). + * + * The second form is used whenever a forward declaration (prototype) is + * needed. It has the letters @c _DCL appended to the name of the definition + * function. These are not otherwise mentioned in this documenation. + * + * There is a third form used when a reference to a function is required, for + * instance when passing the routine as a pointer to a function. It has the + * letters @c _REF appended to the name of the definition function + * (e.g. DEV_IOCTL_REF). + * + * Note that these two extra forms are required because of the possibility of + * having an invisible 'wrapper function' created by the os-specific header + * file which would need to be invoked by the operating system, and which in + * turn would invoke the generic function. + * + * Example: + * + * (in a header file) + * @code + * OS_DEV_INIT_DCL(widget_init); + * OS_DEV_ISR_DCL(widget_isr); + * @endcode + * + * (in an implementation file) + * @code + * OS_DEV_INIT(widget, widget_init) + * { + * + * os_register_interrupt("widget", WIDGET_IRQ, OS_DEV_ISR_REF(widget_isr)); + * + * os_dev_init_return(OS_RETURN_NO_ERROR_S); + * } + * + * OS_DEV_ISR(widget_isr) + * { + * os_dev_isr_return(TRUE); + * } + * @endcode + */ + +/*! @addtogroup drsigs */ +/*! @{ */ + +/*! + * Define a function which will handle device initialization + * + * This is tne driver initialization routine. This is normally where the + * part would be initialized; queues, locks, interrupts handlers defined; + * long-term dynamic memory allocated for driver use; etc. + * + * @param function_name The name of the portable initialization function. + * + * @return A call to #os_dev_init_return() + * + */ +#define OS_DEV_INIT(function_name) + +/*! + * Define a function which will handle device shutdown + * + * This is the reverse of the #OS_DEV_INIT() routine. + * + * @param function_name The name of the portable driver shutdown routine. + * + * @return A call to #os_dev_shutdown_return() + */ +#define OS_DEV_SHUTDOWN(function_name) + +/*! + * Define a function which will open the device for a user. + * + * @param function_name The name of the driver open() function + * + * @return A call to #os_dev_open_return() + */ +#define OS_DEV_OPEN(function_name) + +/*! + * Define a function which will handle a user's ioctl() request + * + * @param function_name The name of the driver ioctl() function + * + * @return A call to #os_dev_ioctl_return() + */ +#define OS_DEV_IOCTL(function_name) + +/*! + * Define a function which will handle a user's read() request + * + * @param function_name The name of the driver read() function + * + * @return A call to #os_dev_read_return() + */ +#define OS_DEV_READ(function_name) + +/*! + * Define a function which will handle a user's write() request + * + * @param function_name The name of the driver write() function + * + * @return A call to #os_dev_write_return() + */ +#define OS_DEV_WRITE(function_name) + +/*! + * Define a function which will handle a user's mmap() request + * + * The mmap() function requests the driver to map some memory into user space. + * + * @todo Determine what support functions are needed for mmap() handling. + * + * @param function_name The name of the driver mmap() function + * + * @return A call to #os_dev_mmap_return() + */ +#define OS_DEV_MMAP(function_name) + +/*! + * Define a function which will close the device - opposite of OS_DEV_OPEN() + * + * @param function_name The name of the driver close() function + * + * @return A call to #os_dev_close_return() + */ +#define OS_DEV_CLOSE(function_name) + +/*! + * Define a function which will handle an interrupt + * + * No arguments are available to the generic function. It must not invoke any + * OS functions which are illegal in a ISR. It gets no parameters, and must + * have a call to #os_dev_isr_return() instead of any/all return statements. + * + * Example: + * @code + * OS_DEV_ISR(widget, widget_isr, WIDGET_IRQ_NUMBER) + * { + * os_dev_isr_return(1); + * } + * @endcode + * + * @param function_name The name of the driver ISR function + * + * @return A call to #os_dev_isr_return() + */ +#define OS_DEV_ISR(function_name) + +/*! + * Define a function which will operate as a background task / bottom half. + * + * The function implementation must be structured in the following manner: + * @code + * OS_DEV_TASK(widget_task) + * { + * OS_DEV_TASK_SETUP(widget_task); + * + * while OS_DEV_TASK_CONDITION(widget_task) } + * + * }; + * } + * @endcode + * + * @todo In some systems the OS_DEV_TASK_CONDITION() will be an action which + * will cause the task to sleep on some event triggered by os_run_task(). In + * others, the macro will reference a variable laid down by + * OS_DEV_TASK_SETUP() to make sure that the loop is only performed once. + * + * @param function_name The name of this background task function + */ +#define OS_DEV_TASK(function_name) + + /*! @} *//* drsigs */ + +/*! @defgroup dclsigs Routines to declare Driver Signature routines + * + * These macros drop prototypes suitable for forward-declaration of + * @ref drsigs "function signatures". + */ + +/*! @addtogroup dclsigs */ +/*! @{ */ + +/*! + * Declare prototype for the device initialization function + * + * @param function_name The name of the portable initialization function. + */ +#define OS_DEV_INIT_DCL(function_name) + +/*! + * Declare prototype for the device shutdown function + * + * @param function_name The name of the portable driver shutdown routine. + * + * @return A call to #os_dev_shutdown_return() + */ +#define OS_DEV_SHUTDOWN_DCL(function_name) + +/*! + * Declare prototype for the open() function. + * + * @param function_name The name of the driver open() function + * + * @return A call to #os_dev_open_return() + */ +#define OS_DEV_OPEN_DCL(function_name) + +/*! + * Declare prototype for the user's ioctl() request function + * + * @param function_name The name of the driver ioctl() function + * + * @return A call to #os_dev_ioctl_return() + */ +#define OS_DEV_IOCTL_DCL(function_name) + +/*! + * Declare prototype for the function a user's read() request + * + * @param function_name The name of the driver read() function + */ +#define OS_DEV_READ_DCL(function_name) + +/*! + * Declare prototype for the user's write() request function + * + * @param function_name The name of the driver write() function + */ +#define OS_DEV_WRITE_DCL(function_name) + +/*! + * Declare prototype for the user's mmap() request function + * + * @param function_name The name of the driver mmap() function + */ +#define OS_DEV_MMAP_DCL(function_name) + +/*! + * Declare prototype for the close function + * + * @param function_name The name of the driver close() function + * + * @return A call to #os_dev_close_return() + */ +#define OS_DEV_CLOSE_DCL(function_name) + +/*! + * Declare prototype for the interrupt handling function + * + * @param function_name The name of the driver ISR function + */ +#define OS_DEV_ISR_DCL(function_name) + +/*! + * Declare prototype for a background task / bottom half function + * + * @param function_name The name of this background task function + */ +#define OS_DEV_TASK_DCL(function_name) + + /*! @} *//* dclsigs */ + +/***************************************************************************** + * Functions for Returning Values from Driver Signature routines + *****************************************************************************/ + +/*! + * @defgroup retfns Functions to Return Values from Driver Signature routines + */ + +/*! @addtogroup retfns */ +/*! @{ */ + +/*! + * Return from the #OS_DEV_INIT() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_init_return(os_error_code code); + +/*! + * Return from the #OS_DEV_SHUTDOWN() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_shutdown_return(os_error_code code); + +/*! + * Return from the #OS_DEV_ISR() function + * + * The function should verify that it really was supposed to be called, + * and that its device needed attention, in order to properly set the + * return code. + * + * @param code non-zero if interrupt handled, zero otherwise. + * + */ +void os_dev_isr_return(int code); + +/*! + * Return from the #OS_DEV_OPEN() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_open_return(os_error_code code); + +/*! + * Return from the #OS_DEV_IOCTL() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_ioctl_return(os_error_code code); + +/*! + * Return from the #OS_DEV_READ() function + * + * @param code Number of bytes read, or an error code to report failure. + * + */ +void os_dev_read_return(os_error_code code); + +/*! + * Return from the #OS_DEV_WRITE() function + * + * @param code Number of bytes written, or an error code to report failure. + * + */ +void os_dev_write_return(os_error_code code); + +/*! + * Return from the #OS_DEV_MMAP() function + * + * @param code Number of bytes written, or an error code to report failure. + * + */ +void os_dev_mmap_return(os_error_code code); + +/*! + * Return from the #OS_DEV_CLOSE() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_close_return(os_error_code code); + +/*! + * Start the #OS_DEV_TASK() function + * + * In some implementations, this could be turned into a label for + * the os_dev_task_return() call. + * + * For a more portable interface, should this take the sleep object as an + * argument??? + * + * @return none + */ +void os_dev_task_begin(void); + +/*! + * Return from the #OS_DEV_TASK() function + * + * In some implementations, this could be turned into a sleep followed + * by a jump back to the os_dev_task_begin() call. + * + * @param code An error code to report success or failure. + * + */ +void os_dev_task_return(os_error_code code); + + /*! @} *//* retfns */ + +/***************************************************************************** + * Functions/Macros for accessing arguments from Driver Signature routines + *****************************************************************************/ + +/*! @defgroup drsigargs Functions for Getting Arguments in Signature functions + * + */ +/* @addtogroup @drsigargs */ +/*! @{ */ + +/*! + * Check whether user is requesting read (permission) on the file/device. + * Usable in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() + * and #OS_DEV_WRITE() routines. + */ +int os_dev_is_flag_read(void); + +/*! + * Check whether user is requesting write (permission) on the file/device. + * Usable in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() + * and #OS_DEV_WRITE() routines. + */ +int os_dev_is_flag_write(void); + +/*! + * Check whether user is requesting non-blocking I/O. Usable in + * #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() and + * #OS_DEV_WRITE() routines. + * + * @todo Specify required behavior when nonblock is requested and (sufficient?) + * data are not available to fulfill the request. + * + */ +int os_dev_is_flag_nonblock(void); + +/*! + * Determine which major device is being accessed. Usable in #OS_DEV_OPEN() + * and #OS_DEV_CLOSE(). + */ +int os_dev_get_major(void); + +/*! + * Determine which minor device is being accessed. Usable in #OS_DEV_OPEN() + * and #OS_DEV_CLOSE(). + */ +int os_dev_get_minor(void); + +/*! + * Determine which operation the user wants performed. Usable in + * #OS_DEV_IOCTL(). + * + * @return Value of the operation. + * + * @todo Define some generic way to define the individual operations. + */ +unsigned os_dev_get_ioctl_op(void); + +/*! + * Retrieve the associated argument for the desired operation. Usable in + * #OS_DEV_IOCTL(). + * + * @return A value which can be cast to a struct pointer or used as + * int/long. + */ +os_dev_ioctl_arg_t os_dev_get_ioctl_arg(void); + +/*! + * Determine the requested byte count. This should be the size of buffer at + * #os_dev_get_user_buffer(). Usable in OS_DEV_READ() and OS_DEV_WRITE() + * routines. + * + * @return A count of bytes + */ +unsigned os_dev_get_count(void); + +/*! + * Get the pointer to the user's data buffer. Usable in OS_DEV_READ(), + * OS_DEV_WRITE(), and OS_DEV_MMAP() routines. + * + * @return Pointer to user buffer (in user space). See #os_copy_to_user() + * and #os_copy_from_user(). + */ +void *os_dev_get_user_buffer(void); + +/*! + * Get the POSIX flags field for the associated open file. Usable in + * OS_DEV_READ(), OS_DEV_WRITE(), and OS_DEV_IOCTL() routines. + * + * @return The flags associated with the file. + */ +unsigned os_dev_get_file_flags(void); + +/*! + * Set the driver's private structure associated with this file/open. + * + * Generally used during #OS_DEV_OPEN(). May also be used during + * #OS_DEV_READ(), #OS_DEV_WRITE(), #OS_DEV_IOCTL(), #OS_DEV_MMAP(), and + * #OS_DEV_CLOSE(). See also #os_dev_get_user_private(). + * + * @param struct_p The driver data structure to associate with this user. + */ +void os_dev_set_user_private(void *struct_p); + +/*! + * Get the driver's private structure associated with this file. + * + * May be used during #OS_DEV_OPEN(), #OS_DEV_READ(), #OS_DEV_WRITE(), + * #OS_DEV_IOCTL(), #OS_DEV_MMAP(), and #OS_DEV_CLOSE(). See + * also #os_dev_set_user_private(). + * + * @return The driver data structure to associate with this user. + */ +void *os_dev_get_user_private(void); + +/*! + * Get the IRQ associated with this call to the #OS_DEV_ISR() function. + * + * @return The IRQ (integer) interrupt number. + */ +int os_dev_get_irq(void); + + /*! @} *//* drsigargs */ + +/***************************************************************************** + * Functions for Generating References to Driver Routines + *****************************************************************************/ + +/*! + * @defgroup drref Functions for Generating References to Driver Routines + * + * These functions will most likely be implemented as macros. They are a + * necessary part of the portable API to guarantee portability. The @c symbol + * type in here is the same symbol passed to the associated + * signature-generating macro. + * + * These macros must be used whenever referring to a + * @ref drsigs "driver signature function", for instance when storing or + * passing a pointer to the function. + */ + +/*! @addtogroup drref */ +/*! @{ */ + +/*! + * Generate a reference to an #OS_DEV_INIT() function + * + * @param function_name The name of the init function being referenced. + * + * @return A reference to the function + */ +os_init_function_t OS_DEV_INIT_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_SHUTDOWN() function + * + * @param function_name The name of the shutdown function being referenced. + * + * @return A reference to the function + */ +os_shutdown_function_t OS_DEV_SHUTDOWN_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_OPEN() function + * + * @param function_name The name of the open function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_OPEN_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_CLOSE() function + * + * @param function_name The name of the close function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_CLOSE_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_READ() function + * + * @param function_name The name of the read function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_READ_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_WRITE() function + * + * @param function_name The name of the write function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_WRITE_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_IOCTL() function + * + * @param function_name The name of the ioctl function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_IOCTL_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_MMAP() function + * + * @param function_name The name of the mmap function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_MMAP_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_ISR() function + * + * @param function_name The name of the isr being referenced. + * + * @return a reference to the function + */ +os_interrupt_handler_t OS_DEV_ISR_REF(symbol function_name); + + /*! @} *//* drref */ + +/*! + * Flush and invalidate all cache lines. + */ +void os_flush_cache_all(void); + +/*! + * Flush a range of addresses from the cache + * + * @param start Starting virtual address + * @param len Number of bytes to flush + */ +void os_cache_flush_range(void *start, uint32_t len); + +/*! + * Invalidate a range of addresses in the cache + * + * @param start Starting virtual address + * @param len Number of bytes to flush + */ +void os_cache_inv_range(void *start, uint32_t len); + +/*! + * Clean a range of addresses from the cache + * + * @param start Starting virtual address + * @param len Number of bytes to flush + */ +void os_cache_clean_range(void *start, uint32_t len); + +/*! + * @example widget.h + */ + +/*! + * @example widget.c + */ + +/*! + * @example rng_driver.h + */ + +/*! + * @example rng_driver.c + */ + +/*! + * @example shw_driver.h + */ + +/*! + * @example shw_driver.c + */ + +#endif /* DOXYGEN_PORTABLE_OS_DOC */ + +#endif /* PORTABLE_OS_H */ diff --git a/drivers/mxc/security/sahara2/include/sah_driver_common.h b/drivers/mxc/security/sahara2/include/sah_driver_common.h new file mode 100644 index 000000000000..7cfd32a30352 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_driver_common.h @@ -0,0 +1,102 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** +* @file sah_driver_common.h +* +* @brief Provides types and defined values for use in the Driver Interface. +* +*/ + +#ifndef SAH_DRIVER_COMMON_H +#define SAH_DRIVER_COMMON_H + +#include "fsl_platform.h" +#include <sahara.h> +#include <adaptor.h> + +/** This specifies the permissions for the device file. It is equivalent to + * chmod 666. + */ +#define SAHARA_DEVICE_MODE S_IFCHR | S_IRUGO | S_IWUGO + +/** +* The status of entries in the Queue. +* +******************************************************************************/ +typedef enum +{ + /** This state indicates that the entry is in the queue and awaits + * execution on SAHARA. */ + SAH_STATE_PENDING, + /** This state indicates that the entry has been written to the SAHARA + * DAR. */ + SAH_STATE_ON_SAHARA, + /** This state indicates that the entry is off of SAHARA, and is awaiting + post-processing. */ + SAH_STATE_OFF_SAHARA, + /** This state indicates that the entry is successfully executed on SAHARA, + and it is finished with post-processing. */ + SAH_STATE_COMPLETE, + /** This state indicates that the entry caused an error or fault on SAHARA, + * and it is finished with post-processing. */ + SAH_STATE_FAILED, + /** This state indicates that the entry was reset via the Reset IO + * Control, and it is finished with post-processing. */ + SAH_STATE_RESET, + /** This state indicates that the entry was signalled from user-space and + * either in the DAR, IDAR or has finished executing pending Bottom Half + * processing. */ + SAH_STATE_IGNORE, + /** This state indicates that the entry was signalled from user-space and + * has been processed by the bottom half. */ + SAH_STATE_IGNORED +} sah_Queue_Status; + +/* any of these conditions being true indicates the descriptor's processing + * is complete */ +#define SAH_DESC_PROCESSED(status) \ + (((status) == SAH_STATE_COMPLETE) || \ + ((status) == SAH_STATE_FAILED ) || \ + ((status) == SAH_STATE_RESET )) + +extern os_lock_t desc_queue_lock; + +extern uint32_t dar_count; +extern uint32_t interrupt_count; +extern uint32_t done1done2_count; +extern uint32_t done1busy2_count; +extern uint32_t done1_count; + +#ifdef FSL_HAVE_SCC2 +extern void *lookup_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base); +#endif + +int sah_get_results_pointers(fsl_shw_uco_t* user_ctx, uint32_t arg); +fsl_shw_return_t sah_get_results_from_pool(volatile fsl_shw_uco_t* user_ctx, + sah_results *arg); +fsl_shw_return_t sah_handle_registration(fsl_shw_uco_t *user_cts); +fsl_shw_return_t sah_handle_deregistration(fsl_shw_uco_t *user_cts); + +int sah_Queue_Manager_Count_Entries(int ignore_state, sah_Queue_Status state); +unsigned long sah_Handle_Poll(sah_Head_Desc *entry); + +#ifdef DIAG_DRV_IF +/****************************************************************************** +* Descriptor and Link dumping functions. +******************************************************************************/ +void sah_Dump_Chain(const sah_Desc *chain, dma_addr_t addr); +#endif /* DIAG_DRV_IF */ + +#endif /* SAH_DRIVER_COMMON_H */ diff --git a/drivers/mxc/security/sahara2/include/sah_hardware_interface.h b/drivers/mxc/security/sahara2/include/sah_hardware_interface.h new file mode 100644 index 000000000000..0933346fc223 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_hardware_interface.h @@ -0,0 +1,99 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** +* @file sah_hardware_interface.h +* +* @brief Provides an interface to the SAHARA hardware registers. +* +*/ + +#ifndef SAH_HARDWARE_INTERFACE_H +#define SAH_HARDWARE_INTERFACE_H + +#include <sah_driver_common.h> +#include <sah_status_manager.h> + +/* These values can be used with sah_HW_Write_Control(). */ +#ifdef SAHARA1 +/** Define platform as Little-Endian */ +#define CTRL_LITTLE_END 0x00000002 +/** Bit to cause endian change in transfers */ +#define CTRL_INT_EN 0x00000004 +/** Set High Assurance mode */ +#define CTRL_HA 0x00000008 +#else +/** Bit to cause byte swapping in (data?) transfers */ +#define CTRL_BYTE_SWAP 0x00000001 +/** Bit to cause halfword swapping in (data?) transfers */ +#define CTRL_HALFWORD_SWAP 0x00000002 +/** Bit to cause endian change in (data?) transfers */ +#define CTRL_INT_EN 0x00000010 +/** Set High Assurance mode */ +#define CTRL_HA 0x00000020 +/** Disable High Assurance */ +#define CTRL_HA_DISABLE 0x00000040 +/** Reseed the RNG CHA */ +#define CTRL_RNG_RESEED 0x00000080 +#endif + + +/* These values can be used with sah_HW_Write_Command(). */ +/** Reset the Sahara */ +#define CMD_RESET 0x00000001 +/** Set Sahara into Batch mode. */ +#define CMD_BATCH 0x00010000 +/** Clear the Sahara interrupt */ +#define CMD_CLR_INT_BIT 0x00000100 +/** Clear the Sahara error */ +#define CMD_CLR_ERROR_BIT 0x00000200 + + +/** error status register contains error */ +#define STATUS_ERROR 0x00000010 + +/** Op status register contains op status */ +#define OP_STATUS 0x00000020 + + +/* High Level functions */ +int sah_HW_Reset(void); +fsl_shw_return_t sah_HW_Set_HA(void); +sah_Execute_Status sah_Wait_On_Sahara(void); + +/* Low Level functions */ +uint32_t sah_HW_Read_Version(void); +uint32_t sah_HW_Read_Control(void); +uint32_t sah_HW_Read_Status(void); +uint32_t sah_HW_Read_Error_Status(void); +uint32_t sah_HW_Read_Op_Status(void); +uint32_t sah_HW_Read_DAR(void); +uint32_t sah_HW_Read_CDAR(void); +uint32_t sah_HW_Read_IDAR(void); +uint32_t sah_HW_Read_Fault_Address(void); +uint32_t sah_HW_Read_MM_Status(void); +uint32_t sah_HW_Read_Config(void); +void sah_HW_Write_Command(uint32_t command); +void sah_HW_Write_Control(uint32_t control); +void sah_HW_Write_DAR(uint32_t pointer); +void sah_HW_Write_Config(uint32_t configuration); + +#if defined DIAG_DRV_IF || defined(DO_DBG) + +void sah_Dump_Words(const char *prefix, const unsigned *data, dma_addr_t addr, + unsigned length); +#endif + +#endif /* SAH_HARDWARE_INTERFACE_H */ + +/* End of sah_hardware_interface.c */ diff --git a/drivers/mxc/security/sahara2/include/sah_interrupt_handler.h b/drivers/mxc/security/sahara2/include/sah_interrupt_handler.h new file mode 100644 index 000000000000..8eb8690ff093 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_interrupt_handler.h @@ -0,0 +1,42 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** +* @file sah_interrupt_handler.h +* +* @brief Provides a hardware interrupt handling mechanism for device driver. +* +*/ +/****************************************************************************** +* +* CAUTION: +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 30/07/03 MW Initial Creation +******************************************************************* +*/ + +#ifndef SAH_INTERRUPT_HANDLER_H +#define SAH_INTERRUPT_HANDLER_H + +#include <sah_driver_common.h> + +/****************************************************************************** +* External function declarations +******************************************************************************/ +int sah_Intr_Init (wait_queue_head_t *wait_queue); +void sah_Intr_Release (void); + +#endif /* SAH_INTERRUPT_HANDLER_H */ diff --git a/drivers/mxc/security/sahara2/include/sah_kernel.h b/drivers/mxc/security/sahara2/include/sah_kernel.h new file mode 100644 index 000000000000..42013272f610 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_kernel.h @@ -0,0 +1,113 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* +* @file sah_kernel.h +* +* @brief Provides definitions for items that user-space and kernel-space share. +*/ +/****************************************************************************** +* +* This file needs to be PORTED to a non-Linux platform +*/ + +#ifndef SAH_KERNEL_H +#define SAH_KERNEL_H + +#if defined(__KERNEL__) + +#if defined(CONFIG_ARCH_MXC91321) || defined(CONFIG_ARCH_MXC91231) \ + || defined(CONFIG_ARCH_MX27) || defined(CONFIG_ARCH_MXC92323) +#include <mach/hardware.h> +#define SAHA_BASE_ADDR SAHARA_BASE_ADDR +#define SAHARA_IRQ MXC_INT_SAHARA +#elif defined(CONFIG_ARCH_MX51) +#include <mach/hardware.h> +#define SAHA_BASE_ADDR SAHARA_BASE_ADDR +#define SAHARA_IRQ MXC_INT_SAHARA_H0 +#else +#include <mach/mx2.h> +#endif + +#endif /* KERNEL */ + +/* IO Controls */ +/* The magic number 'k' is reserved for the SPARC architecture. (See <kernel + * source root>/Documentation/ioctl-number.txt. + * + * Note: Numbers 8-13 were used in a previous version of the API and should + * be avoided. + */ +#define SAH_IOC_MAGIC 'k' +#define SAHARA_HWRESET _IO(SAH_IOC_MAGIC, 0) +#define SAHARA_SET_HA _IO(SAH_IOC_MAGIC, 1) +#define SAHARA_CHK_TEST_MODE _IOR(SAH_IOC_MAGIC,2, int) +#define SAHARA_DAR _IO(SAH_IOC_MAGIC, 3) +#define SAHARA_GET_RESULTS _IO(SAH_IOC_MAGIC, 4) +#define SAHARA_REGISTER _IO(SAH_IOC_MAGIC, 5) +#define SAHARA_DEREGISTER _IO(SAH_IOC_MAGIC, 6) +/* 7 */ +/* 8 */ +/* 9 */ +/* 10 */ +/* 11 */ +/* 12 */ +/* 13 */ + +#define SAHARA_SCC_DROP_PERMS _IOWR(SAH_IOC_MAGIC, 14, scc_partition_info_t) +#define SAHARA_SCC_SFREE _IOWR(SAH_IOC_MAGIC, 15, scc_partition_info_t) + +#define SAHARA_SK_ALLOC _IOWR(SAH_IOC_MAGIC, 16, scc_slot_t) +#define SAHARA_SK_DEALLOC _IOWR(SAH_IOC_MAGIC, 17, scc_slot_t) +#define SAHARA_SK_LOAD _IOWR(SAH_IOC_MAGIC, 18, scc_slot_t) +#define SAHARA_SK_UNLOAD _IOWR(SAH_IOC_MAGIC, 19, scc_slot_t) +#define SAHARA_SK_SLOT_ENC _IOWR(SAH_IOC_MAGIC, 20, scc_slot_t) +#define SAHARA_SK_SLOT_DEC _IOWR(SAH_IOC_MAGIC, 21, scc_slot_t) + +#define SAHARA_SCC_ENCRYPT _IOWR(SAH_IOC_MAGIC, 22, scc_region_t) +#define SAHARA_SCC_DECRYPT _IOWR(SAH_IOC_MAGIC, 23, scc_region_t) +#define SAHARA_GET_CAPS _IOWR(SAH_IOC_MAGIC, 24, fsl_shw_pco_t) + +#define SAHARA_SCC_SSTATUS _IOWR(SAH_IOC_MAGIC, 25, scc_partition_info_t) + +#define SAHARA_SK_READ _IOWR(SAH_IOC_MAGIC, 29, scc_slot_t) + +/*! This is the name that will appear in /proc/interrupts */ +#define SAHARA_NAME "SAHARA" + +/*! + * SAHARA IRQ number. See page 9-239 of TLICS - Motorola Semiconductors H.K. + * TAHITI-Lite IC Specification, Rev 1.1, Feb 2003. + * + * TAHITI has two blocks of 32 interrupts. The SAHARA IRQ is number 27 + * in the second block. This means that the SAHARA IRQ is 27 + 32 = 59. + */ +#ifndef SAHARA_IRQ +#define SAHARA_IRQ (27+32) +#endif + +/*! + * Device file definition. The #ifndef is here to support the unit test code + * by allowing it to specify a different test device. + */ +#ifndef SAHARA_DEVICE_SHORT +#define SAHARA_DEVICE_SHORT "sahara" +#endif + +#ifndef SAHARA_DEVICE +#define SAHARA_DEVICE "/dev/"SAHARA_DEVICE_SHORT +#endif + +#endif /* SAH_KERNEL_H */ + +/* End of sah_kernel.h */ diff --git a/drivers/mxc/security/sahara2/include/sah_memory_mapper.h b/drivers/mxc/security/sahara2/include/sah_memory_mapper.h new file mode 100644 index 000000000000..838ce47cbf85 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_memory_mapper.h @@ -0,0 +1,79 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** +* @file sah_memory_mapper.h +* +* @brief Re-creates SAHARA data structures in Kernel memory such that they are +* suitable for DMA. +* +*/ + +#ifndef SAH_MEMORY_MAPPER_H +#define SAH_MEMORY_MAPPER_H + +#include <sah_driver_common.h> +#include <sah_queue_manager.h> + + +/****************************************************************************** +* External function declarations +******************************************************************************/ +sah_Head_Desc *sah_Copy_Descriptors(fsl_shw_uco_t * user_ctx, + sah_Head_Desc * desc); + +sah_Link *sah_Copy_Links(fsl_shw_uco_t * user_ctx, sah_Link * ptr); + +sah_Head_Desc *sah_Physicalise_Descriptors(sah_Head_Desc * desc); + +sah_Link *sah_Physicalise_Links (sah_Link *ptr); + +sah_Head_Desc *sah_DePhysicalise_Descriptors (sah_Head_Desc *desc); + +sah_Link *sah_DePhysicalise_Links (sah_Link *ptr); + +sah_Link *sah_Make_Links(fsl_shw_uco_t * user_ctx, + sah_Link * ptr, sah_Link ** tail); + + +void sah_Destroy_Descriptors (sah_Head_Desc *desc); + +void sah_Destroy_Links (sah_Link *link); + +void sah_Free_Chained_Descriptors (sah_Head_Desc *desc); + +void sah_Free_Chained_Links (sah_Link *link); + +int sah_Init_Mem_Map (void); + +void sah_Stop_Mem_Map (void); + +int sah_Block_Add_Page (int big); + +sah_Desc *sah_Alloc_Descriptor (void); +sah_Head_Desc *sah_Alloc_Head_Descriptor (void); +void sah_Free_Descriptor (sah_Desc *desc); +void sah_Free_Head_Descriptor (sah_Head_Desc *desc); +sah_Link *sah_Alloc_Link (void); +void sah_Free_Link (sah_Link *link); + +void *wire_user_memory(void *address, uint32_t length, void **page_ctx); +void unwire_user_memory(void **page_ctx); + +os_error_code map_user_memory(struct vm_area_struct *vma, + uint32_t physical_addr, uint32_t size); +os_error_code unmap_user_memory(uint32_t user_addr, uint32_t size); + +#endif /* SAH_MEMORY_MAPPER_H */ + +/* End of sah_memory_mapper.h */ diff --git a/drivers/mxc/security/sahara2/include/sah_queue_manager.h b/drivers/mxc/security/sahara2/include/sah_queue_manager.h new file mode 100644 index 000000000000..2dde5883e193 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_queue_manager.h @@ -0,0 +1,63 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** +* @file sah_queue_manager.h +* +* @brief This file definitions for the Queue Manager. + +* The Queue Manager manages additions and removal from the queue and updates +* the status of queue entries. It also calls sah_HW_* functions to interact +* with the hardware. +* +*/ + +#ifndef SAH_QUEUE_MANAGER_H +#define SAH_QUEUE_MANAGER_H + +#include <sah_driver_common.h> +#include <sah_status_manager.h> + + +/************************* +* Queue Manager Functions +*************************/ +fsl_shw_return_t sah_Queue_Manager_Init(void); +void sah_Queue_Manager_Close(void); +void sah_Queue_Manager_Reset_Entries(void); +void sah_Queue_Manager_Append_Entry(sah_Head_Desc *entry); +void sah_Queue_Manager_Remove_Entry(sah_Head_Desc *entry); + + +/************************* +* Queue Functions +*************************/ +sah_Queue *sah_Queue_Construct(void); +void sah_Queue_Destroy(sah_Queue *this); +void sah_Queue_Append_Entry(sah_Queue *this, sah_Head_Desc *entry); +void sah_Queue_Remove_Entry(sah_Queue *this); +void sah_Queue_Remove_Any_Entry(sah_Queue *this, sah_Head_Desc *entry); +void sah_postprocess_queue(unsigned long reset_flag); + + +/************************* +* Misc Releated Functions +*************************/ + +int sah_blocking_mode(struct sah_Head_Desc *entry); +fsl_shw_return_t sah_convert_error_status(uint32_t error_status); + + +#endif /* SAH_QUEUE_MANAGER_H */ + +/* End of sah_queue_manager.h */ diff --git a/drivers/mxc/security/sahara2/include/sah_status_manager.h b/drivers/mxc/security/sahara2/include/sah_status_manager.h new file mode 100644 index 000000000000..e38731bf4835 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_status_manager.h @@ -0,0 +1,228 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** +* @file sah_status_manager.h +* +* @brief SAHARA Status Manager Types and Function Prototypes +* +* @author Stuart Holloway (SH) +* +*/ + +#ifndef STATUS_MANAGER_H +#define STATUS_MANAGER_H +#include "sah_driver_common.h" +#include "sahara.h" + + +/****************************************************************************** +* User defined data types +******************************************************************************/ +/** +****************************************************************************** +* sah_Execute_Status +* Types read from SAHARA Status Register, with additional state for Op Status +******************************************************************************/ +typedef enum sah_Execute_Status +{ + /** Sahara is Idle. */ + SAH_EXEC_IDLE = 0, + /** SAHARA is busy performing a resest or processing a decriptor chain. */ + SAH_EXEC_BUSY = 1, + /** An error occurred while SAHARA executed the first descriptor. */ + SAH_EXEC_ERROR1 = 2, + /** SAHARA has failed internally. */ + SAH_EXEC_FAULT = 3, + /** SAHARA has finished processing a descriptor chain and is idle. */ + SAH_EXEC_DONE1 = 4, + /** SAHARA has finished processing a descriptor chain, and is processing a + * second chain. */ + SAH_EXEC_DONE1_BUSY2 = 5, + /** SAHARA has finished processing a descriptor chain, but has generated an + * error while processing a second descriptor chain. */ + SAH_EXEC_DONE1_ERROR2 = 6, + /** SAHARA has finished two descriptors. */ + SAH_EXEC_DONE1_DONE2 = 7, + /** SAHARA has stopped, and first descriptor has Op Status, not Err */ + SAH_EXEC_OPSTAT1 = 0x20, +} sah_Execute_Status; + +/** + * When this bit is on in a #sah_Execute_Status, it means that DONE1 is true. + */ +#define SAH_EXEC_DONE1_BIT 4 + +/** + * Bits which make up the Sahara State + */ +#define SAH_EXEC_STATE_MASK 0x00000007 + +/** +******************************************************************************* +* sah_Execute_Error +* Types read from SAHARA Error Status Register +******************************************************************************/ +typedef enum sah_Execute_Error +{ + /** No Error */ + SAH_ERR_NONE = 0, + /** Header is not valid. */ + SAH_ERR_HEADER = 1, + /** Descriptor length is not correct. */ + SAH_ERR_DESC_LENGTH = 2, + /** Length or pointer field is zero while the other is non-zero. */ + SAH_ERR_DESC_POINTER = 3, + /** Length of the link is not a multiple of 4 and is not the last link */ + SAH_ERR_LINK_LENGTH = 4, + /** The data pointer in a link is zero */ + SAH_ERR_LINK_POINTER = 5, + /** Input Buffer reported an overflow */ + SAH_ERR_INPUT_BUFFER = 6, + /** Output Buffer reported an underflow */ + SAH_ERR_OUTPUT_BUFFER = 7, + /** Incorrect data in output buffer after CHA's has signalled 'done'. */ + SAH_ERR_OUTPUT_BUFFER_STARVATION = 8, + /** Internal Hardware Failure. */ + SAH_ERR_INTERNAL_STATE = 9, + /** Current Descriptor was not legal, but cause is unknown. */ + SAH_ERR_GENERAL_DESCRIPTOR = 10, + /** Reserved pointer fields have been set to 1. */ + SAH_ERR_RESERVED_FIELDS = 11, + /** Descriptor address error */ + SAH_ERR_DESCRIPTOR_ADDRESS = 12, + /** Link address error */ + SAH_ERR_LINK_ADDRESS = 13, + /** Processing error in CHA module */ + SAH_ERR_CHA = 14, + /** Processing error during DMA */ + SAH_ERR_DMA = 15 +} sah_Execute_Error; + + +/** +******************************************************************************* +* sah_CHA_Error_Source +* Types read from SAHARA Error Status Register for CHA Error Source +* +******************************************************************************/ +typedef enum sah_CHA_Error_Source +{ + /** No Error indicated in Source CHA Error. */ + SAH_CHA_NO_ERROR = 0, + /** Error in SKHA module. */ + SAH_CHA_SKHA_ERROR = 1, + /** Error in MDHA module. */ + SAH_CHA_MDHA_ERROR = 2, + /** Error in RNG module. */ + SAH_CHA_RNG_ERROR = 3, + /** Error in PKHA module. */ + SAH_CHA_PKHA_ERROR = 4, +} sah_CHA_Error_Source; + +/** +****************************************************************************** +* sah_CHA_Error_Status +* Types read from SAHARA Error Status Register for CHA Error Status +* +******************************************************************************/ +typedef enum sah_CHA_Error_Status +{ + /** No CHA error detected */ + SAH_CHA_NO_ERR = 0x000, + /** Non-empty input buffer when complete. */ + SAH_CHA_IP_BUF = 0x001, + /** Illegal Address Error. */ + SAH_CHA_ADD_ERR = 0x002, + /** Illegal Mode Error. */ + SAH_CHA_MODE_ERR = 0x004, + /** Illegal Data Size Error. */ + SAH_CHA_DATA_SIZE_ERR = 0x008, + /** Illegal Key Size Error. */ + SAH_CHA_KEY_SIZE_ERR = 0x010, + /** Mode/Context/Key written during processing. */ + SAH_CHA_PROC_ERR = 0x020, + /** Context Read During Processing. */ + SAH_CHA_CTX_READ_ERR = 0x040, + /** Internal Hardware Error. */ + SAH_CHA_INTERNAL_HW_ERR = 0x080, + /** Input Buffer not enabled or underflow. */ + SAH_CHA_IP_BUFF_ERR = 0x100, + /** Output Buffer not enabled or overflow. */ + SAH_CHA_OP_BUFF_ERR = 0x200, + /** DES key parity error (SKHA) */ + SAH_CHA_DES_KEY_ERR = 0x400, + /** Reserved error code. */ + SAH_CHA_RES = 0x800 +} sah_CHA_Error_Status; + +/** +***************************************************************************** +* sah_DMA_Error_Status +* Types read from SAHARA Error Status Register for DMA Error Status +******************************************************************************/ +typedef enum sah_DMA_Error_Status +{ + /** No DMA Error Code. */ + SAH_DMA_NO_ERR = 0, + /** AHB terminated a bus cycle with an error. */ + SAH_DMA_AHB_ERR = 2, + /** Internal IP bus cycle was terminated with an error termination. */ + SAH_DMA_IP_ERR = 4, + /** Parity error detected on DMA command. */ + SAH_DMA_PARITY_ERR = 6, + /** DMA was requested to cross a 256 byte internal address boundary. */ + SAH_DMA_BOUNDRY_ERR = 8, + /** DMA controller is busy */ + SAH_DMA_BUSY_ERR = 10, + /** Memory Bounds Error */ + SAH_DMA_RESERVED_ERR = 12, + /** Internal DMA hardware error detected */ + SAH_DMA_INT_ERR = 14 +} sah_DMA_Error_Status; + +/** +***************************************************************************** +* sah_DMA_Error_Size +* Types read from SAHARA Error Status Register for DMA Error Size +* +******************************************************************************/ +typedef enum sah_DMA_Error_Size +{ + /** Error during Byte transfer. */ + SAH_DMA_SIZE_BYTE = 0, + /** Error during Half-word transfer. */ + SAH_DMA_SIZE_HALF_WORD = 1, + /** Error during Word transfer. */ + SAH_DMA_SIZE_WORD = 2, + /** Reserved DMA word size. */ + SAH_DMA_SIZE_RES = 3 +} sah_DMA_Error_Size; + + + + +extern bool sah_dpm_flag; + +/************************* +* Status Manager Functions +*************************/ + +unsigned long sah_Handle_Interrupt(sah_Execute_Status hw_status); +sah_Head_Desc *sah_Find_With_State (sah_Queue_Status status); +int sah_dpm_init(void); +void sah_dpm_close(void); +void sah_Queue_Manager_Prime (sah_Head_Desc *entry); + + +#endif /* STATUS_MANAGER_H */ diff --git a/drivers/mxc/security/sahara2/include/sahara.h b/drivers/mxc/security/sahara2/include/sahara.h new file mode 100644 index 000000000000..28f4e1448c29 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sahara.h @@ -0,0 +1,2265 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file sahara.h + * + * File which implements the FSL_SHW API when used on Sahara + */ +/*! + * @if USE_MAINPAGE + * @mainpage Sahara2 implemtation of FSL Security Hardware API + * @endif + * + */ + +#define _DIAG_DRV_IF +#define _DIAG_SECURITY_FUNC +#define _DIAG_ADAPTOR + +#ifndef SAHARA2_API_H +#define SAHARA2_API_H + +#ifdef DIAG_SECURITY_FUNC +#include <diagnostic.h> +#endif /* DIAG_SECURITY_FUNC */ + +/* This is a Linux flag... ? */ +#ifndef __KERNEL__ +#include <inttypes.h> +#include <stdlib.h> +#include <memory.h> +#else +#include "portable_os.h" +#endif + +/* This definition may need a new name, and needs to go somewhere which + * can determine platform, kernel vs. user, os, etc. + */ +#define copy_bytes(out, in, len) memcpy(out, in, len) + +/* Does this belong here? */ +#ifndef SAHARA_DEVICE +#define SAHARA_DEVICE "/dev/sahara" +#endif + +/*! +******************************************************************************* +* @defgroup lnkflags Link Flags +* +* @brief Flags to show information about link data and link segments +* +******************************************************************************/ +/*! @addtogroup lnkflags + * @{ + */ + +/*! +******************************************************************************* +* This flag indicates that the data in a link is owned by the security +* function component and this memory will be freed by the security function +* component. To be used as part of the flag field of the sah_Link structure. +******************************************************************************/ +#define SAH_OWNS_LINK_DATA 0x01 + +/*! +******************************************************************************* +* The data in a link is not owned by the security function component and +* therefore it will not attempt to free this memory. To be used as part of the +* flag field of the sah_Link structure. +******************************************************************************/ +#define SAH_USES_LINK_DATA 0x02 + +/*! +******************************************************************************* +* The data in this link will change when the descriptor gets executed. +******************************************************************************/ +#define SAH_OUTPUT_LINK 0x04 + +/*! +******************************************************************************* +* The ptr and length in this link are really 'established key' info. They +* are to be converted to ptr/length before putting on request queue. +******************************************************************************/ +#define SAH_KEY_IS_HIDDEN 0x08 + +/*! +******************************************************************************* +* The link structure has been appended to the previous one by the driver. It +* needs to be removed before leaving the driver (and returning to API). +******************************************************************************/ +#define SAH_REWORKED_LINK 0x10 + +/*! +******************************************************************************* +* The length and data fields of this link contain the slot and user id +* used to access the SCC stored key +******************************************************************************/ +#define SAH_STORED_KEY_INFO 0x20 + +/*! +******************************************************************************* +* The Data field points to a physical address, and does not need to be +* processed by the driver. Honored only in Kernel API. +******************************************************************************/ +#define SAH_PREPHYS_DATA 0x40 + +/*! +******************************************************************************* +* The link was inserted during the Physicalise procedure. It is tagged so +* it can be removed during DePhysicalise, thereby returning to the caller an +* intact chain. +******************************************************************************/ +#define SAH_LINK_INSERTED_LINK 0x80 + +/*! +******************************************************************************* +* The Data field points to the location of the key, which is in a secure +* partition held by the user. The memory address needs to be converted to +* kernel space manually, by looking through the partitions that the user holds. +******************************************************************************/ +#define SAH_IN_USER_KEYSTORE 0x100 + +/*! +******************************************************************************* +* sah_Link_Flags +* +* Type to be used for flags associated with a Link in security function. +* These flags are used internally by the security function component only. +* +* Values defined at @ref lnkflags +* +* @brief typedef for flags field of sah_Link +******************************************************************************/ +typedef uint32_t sah_Link_Flags; + +/* +******************************************************************************* +* Security Parameters Related Structures +* +* All of structures associated with API parameters +* +******************************************************************************/ + +/* +******************************************************************************* +* Common Types +* +* All of structures used across several classes of crytography +******************************************************************************/ + +/*! +******************************************************************************* +* @brief Indefinite precision integer used for security operations on SAHARA +* accelerator. The data will always be in little Endian format. +******************************************************************************/ +typedef uint8_t *sah_Int; + +/*! +******************************************************************************* +* @brief Byte array used for block cipher and hash digest/MAC operations on +* SAHARA accelerator. The Endian format will be as specified by the function +* using the sah_Oct_Str. +******************************************************************************/ +typedef uint8_t *sah_Oct_Str; + +/*! + * A queue of descriptor heads -- used to hold requests waiting for user to + * pick up the results. */ +typedef struct sah_Queue { + int count; /*!< # entries in queue */ + struct sah_Head_Desc *head; /*!< first entry in queue */ + struct sah_Head_Desc *tail; /*!< last entry in queue */ +} sah_Queue; + +/****************************************************************************** + * Enumerations + *****************************************************************************/ +/*! + * Flags for the state of the User Context Object (#fsl_shw_uco_t). + */ +typedef enum fsl_shw_user_ctx_flags_t { + /*! + * API will block the caller until operation completes. The result will be + * available in the return code. If this is not set, user will have to get + * results using #fsl_shw_get_results(). + */ + FSL_UCO_BLOCKING_MODE = 0x01, + /*! + * User wants callback (at the function specified with + * #fsl_shw_uco_set_callback()) when the operation completes. This flag is + * valid only if #FSL_UCO_BLOCKING_MODE is not set. + */ + FSL_UCO_CALLBACK_MODE = 0x02, + /*! Do not free descriptor chain after driver (adaptor) finishes */ + FSL_UCO_SAVE_DESC_CHAIN = 0x04, + /*! + * User has made at least one request with callbacks requested, so API is + * ready to handle others. + */ + FSL_UCO_CALLBACK_SETUP_COMPLETE = 0x08, + /*! + * (virtual) pointer to descriptor chain is completely linked with physical + * (DMA) addresses, ready for the hardware. This flag should not be used + * by FSL SHW API programs. + */ + FSL_UCO_CHAIN_PREPHYSICALIZED = 0x10, + /*! + * The user has changed the context but the changes have not been copied to + * the kernel driver. + */ + FSL_UCO_CONTEXT_CHANGED = 0x20, + /*! Internal Use. This context belongs to a user-mode API user. */ + FSL_UCO_USERMODE_USER = 0x40, +} fsl_shw_user_ctx_flags_t; + +/*! + * Return code for FSL_SHW library. + * + * These codes may be returned from a function call. In non-blocking mode, + * they will appear as the status in a Result Object. + */ +typedef enum fsl_shw_return_t { + /*! + * No error. As a function return code in Non-blocking mode, this may + * simply mean that the operation was accepted for eventual execution. + */ + FSL_RETURN_OK_S = 0, + /*! Failure for non-specific reason. */ + FSL_RETURN_ERROR_S, + /*! + * Operation failed because some resource was not able to be allocated. + */ + FSL_RETURN_NO_RESOURCE_S, + /*! Crypto algorithm unrecognized or improper. */ + FSL_RETURN_BAD_ALGORITHM_S, + /*! Crypto mode unrecognized or improper. */ + FSL_RETURN_BAD_MODE_S, + /*! Flag setting unrecognized or inconsistent. */ + FSL_RETURN_BAD_FLAG_S, + /*! Improper or unsupported key length for algorithm. */ + FSL_RETURN_BAD_KEY_LENGTH_S, + /*! Improper parity in a (DES, TDES) key. */ + FSL_RETURN_BAD_KEY_PARITY_S, + /*! + * Improper or unsupported data length for algorithm or internal buffer. + */ + FSL_RETURN_BAD_DATA_LENGTH_S, + /*! Authentication / Integrity Check code check failed. */ + FSL_RETURN_AUTH_FAILED_S, + /*! A memory error occurred. */ + FSL_RETURN_MEMORY_ERROR_S, + /*! An error internal to the hardware occurred. */ + FSL_RETURN_INTERNAL_ERROR_S, + /*! ECC detected Point at Infinity */ + FSL_RETURN_POINT_AT_INFINITY_S, + /*! ECC detected No Point at Infinity */ + FSL_RETURN_POINT_NOT_AT_INFINITY_S, + /*! GCD is One */ + FSL_RETURN_GCD_IS_ONE_S, + /*! GCD is not One */ + FSL_RETURN_GCD_IS_NOT_ONE_S, + /*! Candidate is Prime */ + FSL_RETURN_PRIME_S, + /*! Candidate is not Prime */ + FSL_RETURN_NOT_PRIME_S, + /*! N register loaded improperly with even value */ + FSL_RETURN_EVEN_MODULUS_ERROR_S, + /*! Divisor is zero. */ + FSL_RETURN_DIVIDE_BY_ZERO_ERROR_S, + /*! Bad Exponent or Scalar value for Point Multiply */ + FSL_RETURN_BAD_EXPONENT_ERROR_S, + /*! RNG hardware problem. */ + FSL_RETURN_OSCILLATOR_ERROR_S, + /*! RNG hardware problem. */ + FSL_RETURN_STATISTICS_ERROR_S, +} fsl_shw_return_t; + +/*! + * Algorithm Identifier. + * + * Selection of algorithm will determine how large the block size of the + * algorithm is. Context size is the same length unless otherwise specified. + * Selection of algorithm also affects the allowable key length. + */ +typedef enum fsl_shw_key_alg_t { + /*! + * Key will be used to perform an HMAC. Key size is 1 to 64 octets. Block + * size is 64 octets. + */ + FSL_KEY_ALG_HMAC, + /*! + * Advanced Encryption Standard (Rijndael). Block size is 16 octets. Key + * size is 16 octets. (The single choice of key size is a Sahara platform + * limitation.) + */ + FSL_KEY_ALG_AES, + /*! + * Data Encryption Standard. Block size is 8 octets. Key size is 8 + * octets. + */ + FSL_KEY_ALG_DES, + /*! + * 2- or 3-key Triple DES. Block size is 8 octets. Key size is 16 octets + * for 2-key Triple DES, and 24 octets for 3-key. + */ + FSL_KEY_ALG_TDES, + /*! + * ARC4. No block size. Context size is 259 octets. Allowed key size is + * 1-16 octets. (The choices for key size are a Sahara platform + * limitation.) + */ + FSL_KEY_ALG_ARC4, + /*! + * Private key of a public-private key-pair. Max is 512 bits... + */ + FSL_KEY_PK_PRIVATE, +} fsl_shw_key_alg_t; + +/*! + * Mode selector for Symmetric Ciphers. + * + * The selection of mode determines how a cryptographic algorithm will be + * used to process the plaintext or ciphertext. + * + * For all modes which are run block-by-block (that is, all but + * #FSL_SYM_MODE_STREAM), any partial operations must be performed on a text + * length which is multiple of the block size. Except for #FSL_SYM_MODE_CTR, + * these block-by-block algorithms must also be passed a total number of octets + * which is a multiple of the block size. + * + * In modes which require that the total number of octets of data be a multiple + * of the block size (#FSL_SYM_MODE_ECB and #FSL_SYM_MODE_CBC), and the user + * has a total number of octets which are not a multiple of the block size, the + * user must perform any necessary padding to get to the correct data length. + */ +typedef enum fsl_shw_sym_mode_t { + /*! + * Stream. There is no associated block size. Any request to process data + * may be of any length. This mode is only for ARC4 operations, and is + * also the only mode used for ARC4. + */ + FSL_SYM_MODE_STREAM, + + /*! + * Electronic Codebook. Each block of data is encrypted/decrypted. The + * length of the data stream must be a multiple of the block size. This + * mode may be used for DES, 3DES, and AES. The block size is determined + * by the algorithm. + */ + FSL_SYM_MODE_ECB, + /*! + * Cipher-Block Chaining. Each block of data is encrypted/decrypted and + * then "chained" with the previous block by an XOR function. Requires + * context to start the XOR (previous block). This mode may be used for + * DES, 3DES, and AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CBC, + /*! + * Counter. The counter is encrypted, then XORed with a block of data. + * The counter is then incremented (using modulus arithmetic) for the next + * block. The final operation may be non-multiple of block size. This mode + * may be used for AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CTR, +} fsl_shw_sym_mode_t; + +/*! + * Algorithm selector for Cryptographic Hash functions. + * + * Selection of algorithm determines how large the context and digest will be. + * Context is the same size as the digest (resulting hash), unless otherwise + * specified. + */ +typedef enum fsl_shw_hash_alg_t { + /*! MD5 algorithm. Digest is 16 octets. */ + FSL_HASH_ALG_MD5, + /*! SHA-1 (aka SHA or SHA-160) algorithm. Digest is 20 octets. */ + FSL_HASH_ALG_SHA1, + /*! + * SHA-224 algorithm. Digest is 28 octets, though context is 32 octets. + */ + FSL_HASH_ALG_SHA224, + /*! SHA-256 algorithm. Digest is 32 octets. */ + FSL_HASH_ALG_SHA256 +} fsl_shw_hash_alg_t; + +/*! + * The type of Authentication-Cipher function which will be performed. + */ +typedef enum fsl_shw_acc_mode_t { + /*! + * CBC-MAC for Counter. Requires context and modulus. Final operation may + * be non-multiple of block size. This mode may be used for AES. + */ + FSL_ACC_MODE_CCM, + /*! + * SSL mode. Not supported. Combines HMAC and encrypt (or decrypt). + * Needs one key object for encryption, another for the HMAC. The usual + * hashing and symmetric encryption algorithms are supported. + */ + FSL_ACC_MODE_SSL, +} fsl_shw_acc_mode_t; + +/* REQ-S2LRD-PINTFC-COA-HCO-001 */ +/*! + * Flags which control a Hash operation. + */ +typedef enum fsl_shw_hash_ctx_flags_t { + /*! + * Context is empty. Hash is started from scratch, with a + * message-processed count of zero. + */ + FSL_HASH_FLAGS_INIT = 0x01, + /*! + * Retrieve context from hardware after hashing. If used with the + * #FSL_HASH_FLAGS_FINALIZE flag, the final digest value will be saved in + * the object. + */ + FSL_HASH_FLAGS_SAVE = 0x02, + /*! Place context into hardware before hashing. */ + FSL_HASH_FLAGS_LOAD = 0x04, + /*! + * PAD message and perform final digest operation. If user message is + * pre-padded, this flag should not be used. + */ + FSL_HASH_FLAGS_FINALIZE = 0x08, +} fsl_shw_hash_ctx_flags_t; + +/*! + * Flags which control an HMAC operation. + * + * These may be combined by ORing them together. See #fsl_shw_hmco_set_flags() + * and #fsl_shw_hmco_clear_flags(). + */ +typedef enum fsl_shw_hmac_ctx_flags_t { + /*! + * Message context is empty. HMAC is started from scratch (with key) or + * from precompute of inner hash, depending on whether + * #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT is set. + */ + FSL_HMAC_FLAGS_INIT = 1, + /*! + * Retrieve ongoing context from hardware after hashing. If used with the + * #FSL_HMAC_FLAGS_FINALIZE flag, the final digest value (HMAC) will be + * saved in the object. + */ + FSL_HMAC_FLAGS_SAVE = 2, + /*! Place ongoing context into hardware before hashing. */ + FSL_HMAC_FLAGS_LOAD = 4, + /*! + * PAD message and perform final HMAC operations of inner and outer + * hashes. + */ + FSL_HMAC_FLAGS_FINALIZE = 8, + /*! + * This means that the context contains precomputed inner and outer hash + * values. + */ + FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT = 16, +} fsl_shw_hmac_ctx_flags_t; + +/*! + * Flags to control use of the #fsl_shw_scco_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_scco_set_flags() and #fsl_shw_scco_clear_flags() + */ +typedef enum fsl_shw_sym_ctx_flags_t { + /*! + * Context is empty. In ARC4, this means that the S-Box needs to be + * generated from the key. In #FSL_SYM_MODE_CBC mode, this allows an IV of + * zero to be specified. In #FSL_SYM_MODE_CTR mode, it means that an + * initial CTR value of zero is desired. + */ + FSL_SYM_CTX_INIT = 1, + /*! + * Load context from object into hardware before running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_LOAD = 2, + /*! + * Save context from hardware into object after running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_SAVE = 4, + /*! + * Context (SBox) is to be unwrapped and wrapped on each use. + * This flag is unsupported. + * */ + FSL_SYM_CTX_PROTECT = 8, +} fsl_shw_sym_ctx_flags_t; + +/*! + * Flags which describe the state of the #fsl_shw_sko_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_sko_set_flags() and #fsl_shw_sko_clear_flags() + */ +typedef enum fsl_shw_key_flags_t { + /*! If algorithm is DES or 3DES, do not validate the key parity bits. */ + FSL_SKO_KEY_IGNORE_PARITY = 1, + /*! Clear key is present in the object. */ + FSL_SKO_KEY_PRESENT = 2, + /*! + * Key has been established for use. This feature is not available for all + * platforms, nor for all algorithms and modes. + */ + FSL_SKO_KEY_ESTABLISHED = 4, + /*! + * Key intended for user (software) use; can be read cleartext from the + * keystore. + */ + FSL_SKO_KEY_SW_KEY = 8, +} fsl_shw_key_flags_t; + +/*! + * Type of value which is associated with an established key. + */ +typedef uint64_t key_userid_t; + +/*! + * Flags which describe the state of the #fsl_shw_acco_t. + * + * The @a FSL_ACCO_CTX_INIT and @a FSL_ACCO_CTX_FINALIZE flags, when used + * together, provide for a one-shot operation. + */ +typedef enum fsl_shw_auth_ctx_flags_t { + /*! Initialize Context(s) */ + FSL_ACCO_CTX_INIT = 1, + /*! Load intermediate context(s). This flag is unsupported. */ + FSL_ACCO_CTX_LOAD = 2, + /*! Save intermediate context(s). This flag is unsupported. */ + FSL_ACCO_CTX_SAVE = 4, + /*! Create MAC during this operation. */ + FSL_ACCO_CTX_FINALIZE = 8, + /*! + * Formatting of CCM input data is performed by calls to + * #fsl_shw_ccm_nist_format_ctr_and_iv() and + * #fsl_shw_ccm_nist_update_ctr_and_iv(). + */ + FSL_ACCO_NIST_CCM = 0x10, +} fsl_shw_auth_ctx_flags_t; + +/*! + * The operation which controls the behavior of #fsl_shw_establish_key(). + * + * These values are passed to #fsl_shw_establish_key(). + */ +typedef enum fsl_shw_key_wrap_t { + /*! Generate a key from random values. */ + FSL_KEY_WRAP_CREATE, + /*! Use the provided clear key. */ + FSL_KEY_WRAP_ACCEPT, + /*! Unwrap a previously wrapped key. */ + FSL_KEY_WRAP_UNWRAP +} fsl_shw_key_wrap_t; + +/*! + * Modulus Selector for CTR modes. + * + * The incrementing of the Counter value may be modified by a modulus. If no + * modulus is needed or desired for AES, use #FSL_CTR_MOD_128. + */ +typedef enum fsl_shw_ctr_mod_t { + FSL_CTR_MOD_8, /*!< Run counter with modulus of 2^8. */ + FSL_CTR_MOD_16, /*!< Run counter with modulus of 2^16. */ + FSL_CTR_MOD_24, /*!< Run counter with modulus of 2^24. */ + FSL_CTR_MOD_32, /*!< Run counter with modulus of 2^32. */ + FSL_CTR_MOD_40, /*!< Run counter with modulus of 2^40. */ + FSL_CTR_MOD_48, /*!< Run counter with modulus of 2^48. */ + FSL_CTR_MOD_56, /*!< Run counter with modulus of 2^56. */ + FSL_CTR_MOD_64, /*!< Run counter with modulus of 2^64. */ + FSL_CTR_MOD_72, /*!< Run counter with modulus of 2^72. */ + FSL_CTR_MOD_80, /*!< Run counter with modulus of 2^80. */ + FSL_CTR_MOD_88, /*!< Run counter with modulus of 2^88. */ + FSL_CTR_MOD_96, /*!< Run counter with modulus of 2^96. */ + FSL_CTR_MOD_104, /*!< Run counter with modulus of 2^104. */ + FSL_CTR_MOD_112, /*!< Run counter with modulus of 2^112. */ + FSL_CTR_MOD_120, /*!< Run counter with modulus of 2^120. */ + FSL_CTR_MOD_128 /*!< Run counter with modulus of 2^128. */ +} fsl_shw_ctr_mod_t; + +/*! + * Permissions flags for Secure Partitions + */ +typedef enum fsl_shw_permission_t { +/*! SCM Access Permission: Do not zeroize/deallocate partition on SMN Fail state */ + FSL_PERM_NO_ZEROIZE = 0x80000000, +/*! SCM Access Permission: Enforce trusted key read in */ + FSL_PERM_TRUSTED_KEY_READ = 0x40000000, +/*! SCM Access Permission: Ignore Supervisor/User mode in permission determination */ + FSL_PERM_HD_S = 0x00000800, +/*! SCM Access Permission: Allow Read Access to Host Domain */ + FSL_PERM_HD_R = 0x00000400, +/*! SCM Access Permission: Allow Write Access to Host Domain */ + FSL_PERM_HD_W = 0x00000200, +/*! SCM Access Permission: Allow Execute Access to Host Domain */ + FSL_PERM_HD_X = 0x00000100, +/*! SCM Access Permission: Allow Read Access to Trusted Host Domain */ + FSL_PERM_TH_R = 0x00000040, +/*! SCM Access Permission: Allow Write Access to Trusted Host Domain */ + FSL_PERM_TH_W = 0x00000020, +/*! SCM Access Permission: Allow Read Access to Other/World Domain */ + FSL_PERM_OT_R = 0x00000004, +/*! SCM Access Permission: Allow Write Access to Other/World Domain */ + FSL_PERM_OT_W = 0x00000002, +/*! SCM Access Permission: Allow Execute Access to Other/World Domain */ + FSL_PERM_OT_X = 0x00000001, +} fsl_shw_permission_t; + +typedef enum fsl_shw_cypher_mode_t { + FSL_SHW_CYPHER_MODE_ECB = 1, /*!< ECB mode */ + FSL_SHW_CYPHER_MODE_CBC = 2, /*!< CBC mode */ +} fsl_shw_cypher_mode_t; + +typedef enum fsl_shw_pf_key_t { + FSL_SHW_PF_KEY_IIM, /*!< Present fused IIM key */ + FSL_SHW_PF_KEY_PRG, /*!< Present Program key */ + FSL_SHW_PF_KEY_IIM_PRG, /*!< Present IIM ^ Program key */ + FSL_SHW_PF_KEY_IIM_RND, /*!< Present Random key */ + FSL_SHW_PF_KEY_RND, /*!< Present IIM ^ Random key */ +} fsl_shw_pf_key_t; + +typedef enum fsl_shw_tamper_t { + FSL_SHW_TAMPER_NONE, /*!< No error detected */ + FSL_SHW_TAMPER_WTD, /*!< wire-mesh tampering det */ + FSL_SHW_TAMPER_ETBD, /*!< ext tampering det: input B */ + FSL_SHW_TAMPER_ETAD, /*!< ext tampering det: input A */ + FSL_SHW_TAMPER_EBD, /*!< external boot detected */ + FSL_SHW_TAMPER_SAD, /*!< security alarm detected */ + FSL_SHW_TAMPER_TTD, /*!< temperature tampering det */ + FSL_SHW_TAMPER_CTD, /*!< clock tampering det */ + FSL_SHW_TAMPER_VTD, /*!< voltage tampering det */ + FSL_SHW_TAMPER_MCO, /*!< monotonic counter overflow */ + FSL_SHW_TAMPER_TCO, /*!< time counter overflow */ +} fsl_shw_tamper_t; + +/****************************************************************************** + * Data Structures + *****************************************************************************/ + +/*! + * + * @brief Structure type for descriptors + * + * The first five fields are passed to the hardware. + * + *****************************************************************************/ +#ifndef USE_NEW_PTRS /* Experimental */ + +typedef struct sah_Desc { + uint32_t header; /*!< descriptor header value */ + uint32_t len1; /*!< number of data bytes in 'ptr1' buffer */ + void *ptr1; /*!< pointer to first sah_Link structure */ + uint32_t len2; /*!< number of data bytes in 'ptr2' buffer */ + void *ptr2; /*!< pointer to second sah_Link structure */ + struct sah_Desc *next; /*!< pointer to next descriptor */ +#ifdef __KERNEL__ /* This needs a better test */ + /* These two must be last. See sah_Copy_Descriptors */ + struct sah_Desc *virt_addr; /*!< Virtual (kernel) address of this + descriptor. */ + dma_addr_t dma_addr; /*!< Physical (bus) address of this + descriptor. */ + void *original_ptr1; /*!< user's pointer to ptr1 */ + void *original_ptr2; /*!< user's pointer to ptr2 */ + struct sah_Desc *original_next; /*!< user's pointer to next */ +#endif +} sah_Desc; + +#else + +typedef struct sah_Desc { + uint32_t header; /*!< descriptor header value */ + uint32_t len1; /*!< number of data bytes in 'ptr1' buffer */ + uint32_t hw_ptr1; /*!< pointer to first sah_Link structure */ + uint32_t len2; /*!< number of data bytes in 'ptr2' buffer */ + uint32_t hw_ptr2; /*!< pointer to second sah_Link structure */ + uint32_t hw_next; /*!< pointer to next descriptor */ + struct sah_Link *ptr1; /*!< (virtual) pointer to first sah_Link structure */ + struct sah_Link *ptr2; /*!< (virtual) pointer to first sah_Link structure */ + struct sah_Desc *next; /*!< (virtual) pointer to next descriptor */ +#ifdef __KERNEL__ /* This needs a better test */ + /* These two must be last. See sah_Copy_Descriptors */ + struct sah_Desc *virt_addr; /*!< Virtual (kernel) address of this + descriptor. */ + dma_addr_t dma_addr; /*!< Physical (bus) address of this + descriptor. */ +#endif +} sah_Desc; + +#endif + +/*! +******************************************************************************* +* @brief The first descriptor in a chain +******************************************************************************/ +typedef struct sah_Head_Desc { + sah_Desc desc; /*!< whole struct - must be first */ + struct fsl_shw_uco_t *user_info; /*!< where result pool lives */ + uint32_t user_ref; /*!< at time of request */ + uint32_t uco_flags; /*!< at time of request */ + uint32_t status; /*!< Status of queue entry */ + uint32_t error_status; /*!< If error, register from Sahara */ + uint32_t fault_address; /*!< If error, register from Sahara */ + uint32_t op_status; /*!< If error, register from Sahara */ + fsl_shw_return_t result; /*!< Result of running descriptor */ + struct sah_Head_Desc *next; /*!< Next in queue */ + struct sah_Head_Desc *prev; /*!< previous in queue */ + struct sah_Head_Desc *user_desc; /*!< For API async get_results */ + void *out1_ptr; /*!< For async post-processing */ + void *out2_ptr; /*!< For async post-processing */ + uint32_t out_len; /*!< For async post-processing */ +} sah_Head_Desc; + +/*! + * @brief Structure type for links + * + * The first three fields are used by hardware. + *****************************************************************************/ +#ifndef USE_NEW_PTRS + +typedef struct sah_Link { + size_t len; /*!< len of 'data' buffer in bytes */ + uint8_t *data; /*!< buffer to store data */ + struct sah_Link *next; /*!< pointer to the next sah_Link storing + * data */ + sah_Link_Flags flags; /*!< indicates the component that created the + * data buffer. Security Function internal + * information */ + key_userid_t ownerid; /*!< Auth code for established key */ + uint32_t slot; /*!< Location of the the established key */ +#ifdef __KERNEL__ /* This needs a better test */ + /* These two elements must be last. See sah_Copy_Links() */ + struct sah_Link *virt_addr; + dma_addr_t dma_addr; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + struct page *vm_info; +#endif + uint8_t *original_data; /*!< user's version of data pointer */ + struct sah_Link *original_next; /*!< user's version of next pointer */ +#ifdef SAH_COPY_DATA + uint8_t *copy_data; /*!< Virtual address of acquired buffer */ +#endif +#endif /* kernel-only */ +} sah_Link; + +#else + +typedef struct sah_Link { + /*! len of 'data' buffer in bytes */ + size_t len; + /*! buffer to store data */ + uint32_t hw_data; + /*! Physical address */ + uint32_t hw_next; + /*! + * indicates the component that created the data buffer. Security Function + * internal information + */ + sah_Link_Flags flags; + /*! (virtual) pointer to data */ + uint8_t *data; + /*! (virtual) pointer to the next sah_Link storing data */ + struct sah_Link *next; + /*! Auth code for established key */ + key_userid_t ownerid; + /*! Location of the the established key */ + uint32_t slot; +#ifdef __KERNEL__ /* This needs a better test */ + /* These two elements must be last. See sah_Copy_Links() */ + struct sah_Link *virt_addr; + dma_addr_t dma_addr; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + struct page *vm_info; +#endif +#endif /* kernel-only */ +} sah_Link; + +#endif + +/*! + * Initialization Object + */ +typedef struct fsl_sho_ibo_t { +} fsl_sho_ibo_t; + +/* Imported from Sahara1 driver -- is it needed forever? */ +/*! +******************************************************************************* +* FIELDS +* +* void * ref - parameter to be passed into the memory function calls +* +* void * (*malloc)(void *ref, size_t n) - pointer to user's malloc function +* +* void (*free)(void *ref, void *ptr) - pointer to user's free function +* +* void * (*memcpy)(void *ref, void *dest, const void *src, size_t n) - +* pointer to user's memcpy function +* +* void * (*memset)(void *ref, void *ptr, int ch, size_t n) - pointer to +* user's memset function +* +* @brief Structure for API memory utilities +******************************************************************************/ +typedef struct sah_Mem_Util { + /*! Who knows. Vestigial. */ + void *mu_ref; + /*! Acquire buffer of size n bytes */ + void *(*mu_malloc) (void *ref, size_t n); + /*! Acquire a sah_Head_Desc */ + sah_Head_Desc *(*mu_alloc_head_desc) (void *ref); + /* Acquire a sah_Desc */ + sah_Desc *(*mu_alloc_desc) (void *ref); + /* Acquire a sah_Link */ + sah_Link *(*mu_alloc_link) (void *ref); + /*! Free buffer at ptr */ + void (*mu_free) (void *ref, void *ptr); + /*! Free sah_Head_Desc at ptr */ + void (*mu_free_head_desc) (void *ref, sah_Head_Desc * ptr); + /*! Free sah_Desc at ptr */ + void (*mu_free_desc) (void *ref, sah_Desc * ptr); + /*! Free sah_Link at ptr */ + void (*mu_free_link) (void *ref, sah_Link * ptr); + /*! Funciton which will copy n bytes from src to dest */ + void *(*mu_memcpy) (void *ref, void *dest, const void *src, size_t n); + /*! Set all n bytes of ptr to ch */ + void *(*mu_memset) (void *ref, void *ptr, int ch, size_t n); +} sah_Mem_Util; + +/*! + * Secure Partition information + * + * This holds the context to a single secure partition owned by the user. It + * is only available in the kernel version of the User Context Object. + */ +typedef struct fsl_shw_spo_t { + uint32_t user_base; /*!< Base address (user virtual) */ + void *kernel_base; /*!< Base address (kernel virtual) */ + struct fsl_shw_spo_t *next; /*!< Pointer to the next partition + owned by the user. NULL if this + is the last partition. */ +} fsl_shw_spo_t; + +/* REQ-S2LRD-PINTFC-COA-UCO-001 */ +/*! + * User Context Object + */ +typedef struct fsl_shw_uco_t { + int sahara_openfd; /*!< this should be kernel-only?? */ + sah_Mem_Util *mem_util; /*!< Memory utility fns */ + uint32_t user_ref; /*!< User's reference */ + void (*callback) (struct fsl_shw_uco_t * uco); /*!< User's callback fn */ + uint32_t flags; /*!< from fsl_shw_user_ctx_flags_t */ + unsigned pool_size; /*!< maximum size of user pool */ +#ifdef __KERNEL__ + sah_Queue result_pool; /*!< where non-blocking results go */ + os_process_handle_t process; /*!< remember for signalling User mode */ + fsl_shw_spo_t *partition; /*!< chain of secure partitions owned by + the user */ +#else + struct fsl_shw_uco_t *next; /*!< To allow user-mode chaining of contexts, + for signalling. */ +#endif +} fsl_shw_uco_t; + +/* REQ-S2LRD-PINTFC-API-GEN-006 ?? */ +/*! + * Result object + */ +typedef struct fsl_shw_result_t { + uint32_t user_ref; + fsl_shw_return_t code; + uint32_t detail1; + uint32_t detail2; + sah_Head_Desc *user_desc; +} fsl_shw_result_t; + +/*! + * Keystore Object + */ +typedef struct fsl_shw_kso_t { +#ifdef __KERNEL__ + os_lock_t lock; /*!< Pointer to lock that controls access to + the keystore. */ +#endif + void *user_data; /*!< Pointer to user structure that handles + the internals of the keystore. */ + fsl_shw_return_t(*data_init) (fsl_shw_uco_t * user_ctx, + void **user_data); + void (*data_cleanup) (fsl_shw_uco_t * user_ctx, void **user_data); + fsl_shw_return_t(*slot_verify_access) (void *user_data, + uint64_t owner_id, + uint32_t slot); + fsl_shw_return_t(*slot_alloc) (void *user_data, uint32_t size_bytes, + uint64_t owner_id, uint32_t * slot); + fsl_shw_return_t(*slot_dealloc) (void *user_data, uint64_t owner_id, + uint32_t slot); + void *(*slot_get_address) (void *user_data, uint32_t slot); + uint32_t(*slot_get_base) (void *user_data, uint32_t slot); + uint32_t(*slot_get_offset) (void *user_data, uint32_t slot); + uint32_t(*slot_get_slot_size) (void *user_data, uint32_t slot); +} fsl_shw_kso_t; + +/* REQ-S2LRD-PINTFC-COA-SKO-001 */ +/*! + * Secret Key Context Object + */ +typedef struct fsl_shw_sko_t { + uint32_t flags; + fsl_shw_key_alg_t algorithm; + key_userid_t userid; + uint32_t handle; + uint16_t key_length; + uint8_t key[64]; + struct fsl_shw_kso_t *keystore; /*!< If present, key is in keystore */ +} fsl_shw_sko_t; + +/* REQ-S2LRD-PINTFC-COA-CO-001 */ +/*! + * @brief Platform Capability Object + */ +typedef struct fsl_shw_pco_t { /* Consider turning these constants into symbols */ + int api_major; + int api_minor; + int driver_major; + int driver_minor; + fsl_shw_key_alg_t sym_algorithms[4]; + fsl_shw_sym_mode_t sym_modes[4]; + fsl_shw_hash_alg_t hash_algorithms[4]; + uint8_t sym_support[5][4]; /* indexed by key alg then mode */ + + int scc_driver_major; + int scc_driver_minor; + int scm_version; /*!< Version from SCM Configuration register */ + int smn_version; /*!< Version from SMN Status register */ + int block_size_bytes; /*!< Number of bytes per block of RAM; also + block size of the crypto algorithm. */ + union { + struct { + int black_ram_size_blocks; /*!< Number of blocks of Black RAM */ + int red_ram_size_blocks; /*!< Number of blocks of Red RAM */ + } scc_info; + struct { + int partition_size_bytes; /*!< Number of bytes in each partition */ + int partition_count; /*!< Number of partitions on this platform */ + } scc2_info; + }; +} fsl_shw_pco_t; + +/* REQ-S2LRD-PINTFC-COA-HCO-001 */ +/*! + * Hash Context Object + */ +typedef struct fsl_shw_hco_t { /* fsl_shw_hash_context_object */ + fsl_shw_hash_alg_t algorithm; + uint32_t flags; + uint8_t digest_length; /* in bytes */ + uint8_t context_length; /* in bytes */ + uint8_t context_register_length; /* in bytes */ + uint32_t context[9]; /* largest digest + msg size */ +} fsl_shw_hco_t; + +/*! + * HMAC Context Object + */ +typedef struct fsl_shw_hmco_t { /* fsl_shw_hmac_context_object */ + fsl_shw_hash_alg_t algorithm; + uint32_t flags; + uint8_t digest_length; /*!< in bytes */ + uint8_t context_length; /*!< in bytes */ + uint8_t context_register_length; /*!< in bytes */ + uint32_t ongoing_context[9]; /*!< largest digest + msg + size */ + uint32_t inner_precompute[9]; /*!< largest digest + msg + size */ + uint32_t outer_precompute[9]; /*!< largest digest + msg + size */ +} fsl_shw_hmco_t; + +/* REQ-S2LRD-PINTFC-COA-SCCO-001 */ +/*! + * Symmetric Crypto Context Object Context Object + */ +typedef struct fsl_shw_scco_t { + uint32_t flags; + unsigned block_size_bytes; /* double duty block&ctx size */ + fsl_shw_sym_mode_t mode; + /* Could put modulus plus 16-octet context in union with arc4 + sbox+ptrs... */ + fsl_shw_ctr_mod_t modulus_exp; + uint8_t context[259]; +} fsl_shw_scco_t; + +/*! + * Authenticate-Cipher Context Object + + * An object for controlling the function of, and holding information about, + * data for the authenticate-cipher functions, #fsl_shw_gen_encrypt() and + * #fsl_shw_auth_decrypt(). + */ +typedef struct fsl_shw_acco_t { + uint32_t flags; /*!< See #fsl_shw_auth_ctx_flags_t for + meanings */ + fsl_shw_acc_mode_t mode; /*!< CCM only */ + uint8_t mac_length; /*!< User's value for length */ + unsigned q_length; /*!< NIST parameter - */ + fsl_shw_scco_t cipher_ctx_info; /*!< For running + encrypt/decrypt. */ + union { + fsl_shw_scco_t CCM_ctx_info; /*!< For running the CBC in + AES-CCM. */ + fsl_shw_hco_t hash_ctx_info; /*!< For running the hash */ + } auth_info; /*!< "auth" info struct */ + uint8_t unencrypted_mac[16]; /*!< max block size... */ +} fsl_shw_acco_t; + +/*! + * Used by Sahara API to retrieve completed non-blocking results. + */ +typedef struct sah_results { + unsigned requested; /*!< number of results requested */ + unsigned *actual; /*!< number of results obtained */ + fsl_shw_result_t *results; /*!< pointer to memory to hold results */ +} sah_results; + +/*! + * @typedef scc_partition_status_t + */ +/*! Partition status information. */ +typedef enum fsl_shw_partition_status_t { + FSL_PART_S_UNUSABLE, /*!< Partition not implemented */ + FSL_PART_S_UNAVAILABLE, /*!< Partition owned by other host */ + FSL_PART_S_AVAILABLE, /*!< Partition available */ + FSL_PART_S_ALLOCATED, /*!< Partition owned by host but not engaged + */ + FSL_PART_S_ENGAGED, /*!< Partition owned by host and engaged */ +} fsl_shw_partition_status_t; + +/****************************************************************************** + * Access Macros for Objects + *****************************************************************************/ +/*! + * Get FSL SHW API version + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcmajor A pointer to where the major version + * of the API is to be stored. + * @param[out] pcminor A pointer to where the minor version + * of the API is to be stored. + */ +#define fsl_shw_pco_get_version(pcobject, pcmajor, pcminor) \ +{ \ + *(pcmajor) = (pcobject)->api_major; \ + *(pcminor) = (pcobject)->api_minor; \ +} + +/*! + * Get underlying driver version. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcmajor A pointer to where the major version + * of the driver is to be stored. + * @param[out] pcminor A pointer to where the minor version + * of the driver is to be stored. + */ +#define fsl_shw_pco_get_driver_version(pcobject, pcmajor, pcminor) \ +{ \ + *(pcmajor) = (pcobject)->driver_major; \ + *(pcminor) = (pcobject)->driver_minor; \ +} + +/*! + * Get list of symmetric algorithms supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcalgorithms A pointer to where to store the location of + * the list of algorithms. + * @param[out] pcacount A pointer to where to store the number of + * algorithms in the list at @a algorithms. + */ +#define fsl_shw_pco_get_sym_algorithms(pcobject, pcalgorithms, pcacount) \ +{ \ + *(pcalgorithms) = (pcobject)->sym_algorithms; \ + *(pcacount) = sizeof((pcobject)->sym_algorithms)/4; \ +} + +/*! + * Get list of symmetric modes supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] gsmodes A pointer to where to store the location of + * the list of modes. + * @param[out] gsacount A pointer to where to store the number of + * algorithms in the list at @a modes. + */ +#define fsl_shw_pco_get_sym_modes(pcobject, gsmodes, gsacount) \ +{ \ + *(gsmodes) = (pcobject)->sym_modes; \ + *(gsacount) = sizeof((pcobject)->sym_modes)/4; \ +} + +/*! + * Get list of hash algorithms supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] gsalgorithms A pointer which will be set to the list of + * algorithms. + * @param[out] gsacount The number of algorithms in the list at @a + * algorithms. + */ +#define fsl_shw_pco_get_hash_algorithms(pcobject, gsalgorithms, gsacount) \ +{ \ + *(gsalgorithms) = (pcobject)->hash_algorithms; \ + *(gsacount) = sizeof((pcobject)->hash_algorithms)/4; \ +} + +/*! + * Determine whether the combination of a given symmetric algorithm and a given + * mode is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcalg A Symmetric Cipher algorithm. + * @param pcmode A Symmetric Cipher mode. + * + * @return 0 if combination is not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_sym_supported(pcobject, pcalg, pcmode) \ + ((pcobject)->sym_support[pcalg][pcmode]) + +/*! + * Determine whether a given Encryption-Authentication mode is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcmode The Authentication mode. + * + * @return 0 if mode is not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_auth_supported(pcobject, pcmode) \ + ((pcmode == FSL_ACC_MODE_CCM) ? 1 : 0) + +/*! + * Determine whether Black Keys (key establishment / wrapping) is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * + * @return 0 if wrapping is not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_black_key_supported(pcobject) \ + 1 + +/*! + * Determine whether Programmed Key features are available + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return 1 if Programmed Key features are available, otherwise zero. + */ +#define fsl_shw_pco_check_pk_supported(pcobject) \ + 0 + +/*! + * Determine whether Software Key features are available + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return 1 if Software key features are available, otherwise zero. + */ +#define fsl_shw_pco_check_sw_keys_supported(pcobject) \ + 0 + +/*! + * Get FSL SHW SCC driver version + * + * @param pcobject The Platform Capabilities Object to query. + * @param[out] pcmajor A pointer to where the major version + * of the SCC driver is to be stored. + * @param[out] pcminor A pointer to where the minor version + * of the SCC driver is to be stored. + */ +#define fsl_shw_pco_get_scc_driver_version(pcobject, pcmajor, pcminor) \ +{ \ + *(pcmajor) = (pcobject)->scc_driver_major; \ + *(pcminor) = (pcobject)->scc_driver_minor; \ +} + +/*! + * Get SCM hardware version + * + * @param pcobject The Platform Capabilities Object to query. + * @return The SCM hardware version + */ +#define fsl_shw_pco_get_scm_version(pcobject) \ + ((pcobject)->scm_version) + +/*! + * Get SMN hardware version + * + * @param pcobject The Platform Capabilities Object to query. + * @return The SMN hardware version + */ +#define fsl_shw_pco_get_smn_version(pcobject) \ + ((pcobject)->smn_version) + +/*! + * Get the size of an SCM block, in bytes + * + * @param pcobject The Platform Capabilities Object to query. + * @return The size of an SCM block, in bytes. + */ +#define fsl_shw_pco_get_scm_block_size(pcobject) \ + ((pcobject)->block_size_bytes) + +/*! + * Get size of Black and Red RAM memory + * + * @param pcobject The Platform Capabilities Object to query. + * @param[out] black_size A pointer to where the size of the Black RAM, in + * blocks, is to be placed. + * @param[out] red_size A pointer to where the size of the Red RAM, in + * blocks, is to be placed. + */ +#define fsl_shw_pco_get_smn_size(pcobject, black_size, red_size) \ +{ \ + if ((pcobject)->scm_version == 1) { \ + *(black_size) = (pcobject)->scc_info.black_ram_size_blocks; \ + *(red_size) = (pcobject)->scc_info.red_ram_size_blocks; \ + } else { \ + *(black_size) = 0; \ + *(red_size) = 0; \ + } \ +} + +/*! + * Determine whether Secure Partitions are supported + * + * @param pcobject The Platform Capabilities Object to query. + * + * @return 0 if secure partitions are not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_spo_supported(pcobject) \ + ((pcobject)->scm_version == 2) + +/*! + * Get the size of a Secure Partitions + * + * @param pcobject The Platform Capabilities Object to query. + * + * @return Partition size, in bytes. 0 if Secure Partitions not supported. + */ +#define fsl_shw_pco_get_spo_size_bytes(pcobject) \ + (((pcobject)->scm_version == 2) ? \ + ((pcobject)->scc2_info.partition_size_bytes) : 0 ) + +/*! + * Get the number of Secure Partitions on this platform + * + * @param pcobject The Platform Capabilities Object to query. + * + * @return Number of partitions. 0 if Secure Paritions not supported. Note + * that this returns the total number of partitions, not all may be + * available to the user. + */ +#define fsl_shw_pco_get_spo_count(pcobject) \ + (((pcobject)->scm_version == 2) ? \ + ((pcobject)->scc2_info.partition_count) : 0 ) + +/*! + * Initialize a User Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the User Context Object to initial values, and set the size + * of the results pool. The mode will be set to a default of + * #FSL_UCO_BLOCKING_MODE. + * + * When using non-blocking operations, this sets the maximum number of + * operations which can be outstanding. This number includes the counts of + * operations waiting to start, operation(s) being performed, and results which + * have not been retrieved. + * + * Changes to this value are ignored once user registration has completed. It + * should be set to 1 if only blocking operations will ever be performed. + * + * @param ucontext The User Context object to operate on. + * @param usize The maximum number of operations which can be + * outstanding. + */ +#ifdef __KERNEL__ +#define fsl_shw_uco_init(ucontext, usize) \ +{ \ + (ucontext)->pool_size = usize; \ + (ucontext)->flags = FSL_UCO_BLOCKING_MODE; \ + (ucontext)->sahara_openfd = -1; \ + (ucontext)->mem_util = NULL; \ + (ucontext)->partition = NULL; \ + (ucontext)->callback = NULL; \ +} +#else +#define fsl_shw_uco_init(ucontext, usize) \ +{ \ + (ucontext)->pool_size = usize; \ + (ucontext)->flags = FSL_UCO_BLOCKING_MODE; \ + (ucontext)->sahara_openfd = -1; \ + (ucontext)->mem_util = NULL; \ + (ucontext)->callback = NULL; \ +} +#endif + +/*! + * Set the User Reference for the User Context. + * + * @param ucontext The User Context object to operate on. + * @param uref A value which will be passed back with a result. + */ +#define fsl_shw_uco_set_reference(ucontext, uref) \ + (ucontext)->user_ref = uref + +/*! + * Set the User Reference for the User Context. + * + * @param ucontext The User Context object to operate on. + * @param ucallback The function the API will invoke when an operation + * completes. + */ +#define fsl_shw_uco_set_callback(ucontext, ucallback) \ + (ucontext)->callback = ucallback + +/*! + * Set flags in the User Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param ucontext The User Context object to operate on. + * @param uflags ORed values from #fsl_shw_user_ctx_flags_t. + */ +#define fsl_shw_uco_set_flags(ucontext, uflags) \ + (ucontext)->flags |= (uflags) + +/*! + * Clear flags in the User Context. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param ucontext The User Context object to operate on. + * @param uflags ORed values from #fsl_shw_user_ctx_flags_t. + */ +#define fsl_shw_uco_clear_flags(ucontext, uflags) \ + (ucontext)->flags &= ~(uflags) + +/*! + * Retrieve the reference value from a Result Object. + * + * @param robject The result object to query. + * + * @return The reference associated with the request. + */ +#define fsl_shw_ro_get_reference(robject) \ + (robject)->user_ref + +/*! + * Retrieve the status code from a Result Object. + * + * @param robject The result object to query. + * + * @return The status of the request. + */ +#define fsl_shw_ro_get_status(robject) \ + (robject)->code + +/*! + * Initialize a Secret Key Object. + * + * This function must be called before performing any other operation with + * the Object. + * + * @param skobject The Secret Key Object to be initialized. + * @param skalgorithm DES, AES, etc. + * + */ +#define fsl_shw_sko_init(skobject,skalgorithm) \ +{ \ + (skobject)->algorithm = skalgorithm; \ + (skobject)->flags = 0; \ + (skobject)->keystore = NULL; \ +} + +/*! + * Initialize a Secret Key Object to use a Platform Key register. + * + * This function must be called before performing any other operation with + * the Object. INVALID on this platform. + * + * @param skobject The Secret Key Object to be initialized. + * @param skalgorithm DES, AES, etc. + * @param skhwkey one of the fsl_shw_pf_key_t values. + * + */ +#define fsl_shw_sko_init_pf_key(skobject,skalgorithm,skhwkey) \ +{ \ + (skobject)->algorithm = -1; \ + (skobject)->flags = -1; \ + (skobject)->keystore = NULL; \ +} + +/*! + * Store a cleartext key in the key object. + * + * This has the side effect of setting the #FSL_SKO_KEY_PRESENT flag and + * resetting the #FSL_SKO_KEY_ESTABLISHED flag. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skkey A pointer to the beginning of the key. + * @param skkeylen The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +#define fsl_shw_sko_set_key(skobject, skkey, skkeylen) \ +{ \ + (skobject)->key_length = skkeylen; \ + copy_bytes((skobject)->key, skkey, skkeylen); \ + (skobject)->flags |= FSL_SKO_KEY_PRESENT; \ + (skobject)->flags &= ~FSL_SKO_KEY_ESTABLISHED; \ +} + +/*! + * Set a size for the key. + * + * This function would normally be used when the user wants the key to be + * generated from a random source. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skkeylen The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +#define fsl_shw_sko_set_key_length(skobject, skkeylen) \ + (skobject)->key_length = skkeylen; + +/*! + * Set the User ID associated with the key. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skuserid The User ID to identify authorized users of the key. + */ +#define fsl_shw_sko_set_user_id(skobject, skuserid) \ + (skobject)->userid = (skuserid) + +/*! + * Establish a user Keystore to hold the key. + */ +#define fsl_shw_sko_set_keystore(skobject, user_keystore) \ + (skobject)->keystore = (user_keystore) + +/*! + * Set the establish key handle into a key object. + * + * The @a userid field will be used to validate the access to the unwrapped + * key. This feature is not available for all platforms, nor for all + * algorithms and modes. + * + * The #FSL_SKO_KEY_ESTABLISHED will be set (and the #FSL_SKO_KEY_PRESENT flag + * will be cleared). + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skuserid The User ID to verify this user is an authorized user of + * the key. + * @param skhandle A @a handle from #fsl_shw_sko_get_established_info. + */ +#define fsl_shw_sko_set_established_info(skobject, skuserid, skhandle) \ +{ \ + (skobject)->userid = (skuserid); \ + (skobject)->handle = (skhandle); \ + (skobject)->flags |= FSL_SKO_KEY_ESTABLISHED; \ + (skobject)->flags &= \ + ~(FSL_SKO_KEY_PRESENT); \ +} + +/*! + * Retrieve the established-key handle from a key object. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skhandle The location to store the @a handle of the unwrapped + * key. + */ +#define fsl_shw_sko_get_established_info(skobject, skhandle) \ + *(skhandle) = (skobject)->handle + +/*! + * Extract the algorithm from a key object. + * + * @param skobject The Key Object to be queried. + * @param[out] skalgorithm A pointer to the location to store the algorithm. + */ +#define fsl_shw_sko_get_algorithm(skobject, skalgorithm) \ + *(skalgorithm) = (skobject)->algorithm + +/*! + * Retrieve the cleartext key from a key object that is stored in a user + * keystore. + * + * @param skobject The Key Object to be queried. + * @param[out] skkey A pointer to the location to store the key. NULL + * if the key is not stored in a user keystore. + */ +#define fsl_shw_sko_get_key(skobject, skkey) \ +{ \ + fsl_shw_kso_t* keystore = (skobject)->keystore; \ + if (keystore != NULL) { \ + *(skkey) = keystore->slot_get_address(keystore->user_data, \ + (skobject)->handle); \ + } else { \ + *(skkey) = NULL; \ + } \ +} + +/*! + * Determine the size of a wrapped key based upon the cleartext key's length. + * + * This function can be used to calculate the number of octets that + * #fsl_shw_extract_key() will write into the location at @a covered_key. + * + * If zero is returned at @a length, this means that the key length in + * @a key_info is not supported. + * + * @param wkeyinfo Information about a key to be wrapped. + * @param wkeylen Location to store the length of a wrapped + * version of the key in @a key_info. + */ +#define fsl_shw_sko_calculate_wrapped_size(wkeyinfo, wkeylen) \ +{ \ + register fsl_shw_sko_t* kp = wkeyinfo; \ + register uint32_t kl = kp->key_length; \ + int key_blocks = (kl + 15) / 16; \ + int base_size = 35; /* ICV + T' + ALG + LEN + FLAGS */ \ + \ + *(wkeylen) = base_size + 16 * key_blocks; \ +} + +/*! + * Set some flags in the key object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skflags (One or more) ORed members of #fsl_shw_key_flags_t which + * are to be set. + */ +#define fsl_shw_sko_set_flags(skobject, skflags) \ + (skobject)->flags |= (skflags) + +/*! + * Clear some flags in the key object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skflags (One or more) ORed members of #fsl_shw_key_flags_t + * which are to be reset. + */ +#define fsl_shw_sko_clear_flags(skobject, skflags) \ + (skobject)->flags &= ~(skflags) + +/*! + * Initialize a Hash Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the hash + * context object. + * + * @param hcobject The hash context to operate upon. + * @param hcalgorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +#define fsl_shw_hco_init(hcobject, hcalgorithm) \ +{ \ + (hcobject)->algorithm = hcalgorithm; \ + (hcobject)->flags = 0; \ + switch (hcalgorithm) { \ + case FSL_HASH_ALG_MD5: \ + (hcobject)->digest_length = 16; \ + (hcobject)->context_length = 16; \ + (hcobject)->context_register_length = 24; \ + break; \ + case FSL_HASH_ALG_SHA1: \ + (hcobject)->digest_length = 20; \ + (hcobject)->context_length = 20; \ + (hcobject)->context_register_length = 24; \ + break; \ + case FSL_HASH_ALG_SHA224: \ + (hcobject)->digest_length = 28; \ + (hcobject)->context_length = 32; \ + (hcobject)->context_register_length = 36; \ + break; \ + case FSL_HASH_ALG_SHA256: \ + (hcobject)->digest_length = 32; \ + (hcobject)->context_length = 32; \ + (hcobject)->context_register_length = 36; \ + break; \ + default: \ + /* error ! */ \ + (hcobject)->digest_length = 1; \ + (hcobject)->context_length = 1; \ + (hcobject)->context_register_length = 1; \ + break; \ + } \ +} + +/*! + * Get the current hash value and message length from the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hcobject The hash context to query. + * @param[out] hccontext Pointer to the location of @a length octets where to + * store a copy of the current value of the digest. + * @param hcclength Number of octets of hash value to copy. + * @param[out] hcmsglen Pointer to the location to store the number of octets + * already hashed. + */ +#define fsl_shw_hco_get_digest(hcobject, hccontext, hcclength, hcmsglen) \ +{ \ + copy_bytes(hccontext, (hcobject)->context, hcclength); \ + if ((hcobject)->algorithm == FSL_HASH_ALG_SHA224 \ + || (hcobject)->algorithm == FSL_HASH_ALG_SHA256) { \ + *(hcmsglen) = (hcobject)->context[8]; \ + } else { \ + *(hcmsglen) = (hcobject)->context[5]; \ + } \ +} + +/*! + * Get the hash algorithm from the hash context object. + * + * @param hcobject The hash context to query. + * @param[out] hcalgorithm Pointer to where the algorithm is to be stored. + */ +#define fsl_shw_hco_get_info(hcobject, hcalgorithm) \ +{ \ + *(hcalgorithm) = (hcobject)->algorithm; \ +} + +/*! + * Set the current hash value and message length in the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hcobject The hash context to operate upon. + * @param hccontext Pointer to buffer of appropriate length to copy into + * the hash context object. + * @param hcmsglen The number of octets of the message which have + * already been hashed. + * + */ +#define fsl_shw_hco_set_digest(hcobject, hccontext, hcmsglen) \ +{ \ + copy_bytes((hcobject)->context, hccontext, (hcobject)->context_length); \ + if (((hcobject)->algorithm == FSL_HASH_ALG_SHA224) \ + || ((hcobject)->algorithm == FSL_HASH_ALG_SHA256)) { \ + (hcobject)->context[8] = hcmsglen; \ + } else { \ + (hcobject)->context[5] = hcmsglen; \ + } \ +} + +/*! + * Set flags in a Hash Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The hash context to be operated on. + * @param hcflags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +#define fsl_shw_hco_set_flags(hcobject, hcflags) \ + (hcobject)->flags |= (hcflags) + +/*! + * Clear flags in a Hash Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The hash context to be operated on. + * @param hcflags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +#define fsl_shw_hco_clear_flags(hcobject, hcflags) \ + (hcobject)->flags &= ~(hcflags) + +/*! + * Initialize an HMAC Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the HMAC + * context object. + * + * @param hcobject The HMAC context to operate upon. + * @param hcalgorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +#define fsl_shw_hmco_init(hcobject, hcalgorithm) \ + fsl_shw_hco_init(hcobject, hcalgorithm) + +/*! + * Set flags in an HMAC Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The HMAC context to be operated on. + * @param hcflags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +#define fsl_shw_hmco_set_flags(hcobject, hcflags) \ + (hcobject)->flags |= (hcflags) + +/*! + * Clear flags in an HMAC Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The HMAC context to be operated on. + * @param hcflags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +#define fsl_shw_hmco_clear_flags(hcobject, hcflags) \ + (hcobject)->flags &= ~(hcflags) + +/*! + * Initialize a Symmetric Cipher Context Object. + * + * This function must be called before performing any other operation with the + * Object. This will set the @a mode and @a algorithm and initialize the + * Object. + * + * @param scobject The context object to operate on. + * @param scalg The cipher algorithm this context will be used with. + * @param scmode #FSL_SYM_MODE_CBC, #FSL_SYM_MODE_ECB, etc. + * + */ +#define fsl_shw_scco_init(scobject, scalg, scmode) \ +{ \ + register uint32_t bsb; /* block-size bytes */ \ + \ + switch (scalg) { \ + case FSL_KEY_ALG_AES: \ + bsb = 16; \ + break; \ + case FSL_KEY_ALG_DES: \ + /* fall through */ \ + case FSL_KEY_ALG_TDES: \ + bsb = 8; \ + break; \ + case FSL_KEY_ALG_ARC4: \ + bsb = 259; \ + break; \ + case FSL_KEY_ALG_HMAC: \ + bsb = 1; /* meaningless */ \ + break; \ + default: \ + bsb = 00; \ + } \ + (scobject)->block_size_bytes = bsb; \ + (scobject)->mode = scmode; \ + (scobject)->flags = 0; \ +} + +/*! + * Set the flags for a Symmetric Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param scobject The context object to operate on. + * @param scflags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +#define fsl_shw_scco_set_flags(scobject, scflags) \ + (scobject)->flags |= (scflags) + +/*! + * Clear some flags in a Symmetric Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param scobject The context object to operate on. + * @param scflags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +#define fsl_shw_scco_clear_flags(scobject, scflags) \ + (scobject)->flags &= ~(scflags) + +/*! + * Set the Context (IV) for a Symmetric Cipher Context. + * + * This is to set the context/IV for #FSL_SYM_MODE_CBC mode, or to set the + * context (the S-Box and pointers) for ARC4. The full context size will + * be copied. + * + * @param scobject The context object to operate on. + * @param sccontext A pointer to the buffer which contains the context. + * + */ +#define fsl_shw_scco_set_context(scobject, sccontext) \ + copy_bytes((scobject)->context, sccontext, \ + (scobject)->block_size_bytes) + +/*! + * Get the Context for a Symmetric Cipher Context. + * + * This is to retrieve the context/IV for #FSL_SYM_MODE_CBC mode, or to + * retrieve context (the S-Box and pointers) for ARC4. The full context + * will be copied. + * + * @param scobject The context object to operate on. + * @param[out] sccontext Pointer to location where context will be stored. + */ +#define fsl_shw_scco_get_context(scobject, sccontext) \ + copy_bytes(sccontext, (scobject)->context, (scobject)->block_size_bytes) + +/*! + * Set the Counter Value for a Symmetric Cipher Context. + * + * This will set the Counter Value for CTR mode. + * + * @param scobject The context object to operate on. + * @param sccounter The starting counter value. The number of octets. + * copied will be the block size for the algorithm. + * @param scmodulus The modulus for controlling the incrementing of the + * counter. + * + */ +#define fsl_shw_scco_set_counter_info(scobject, sccounter, scmodulus) \ + { \ + if ((sccounter) != NULL) { \ + copy_bytes((scobject)->context, sccounter, \ + (scobject)->block_size_bytes); \ + } \ + (scobject)->modulus_exp = scmodulus; \ + } + +/*! + * Get the Counter Value for a Symmetric Cipher Context. + * + * This will retrieve the Counter Value is for CTR mode. + * + * @param scobject The context object to query. + * @param[out] sccounter Pointer to location to store the current counter + * value. The number of octets copied will be the + * block size for the algorithm. + * @param[out] scmodulus Pointer to location to store the modulus. + * + */ +#define fsl_shw_scco_get_counter_info(scobject, sccounter, scmodulus) \ + { \ + if ((sccounter) != NULL) { \ + copy_bytes(sccounter, (scobject)->context, \ + (scobject)->block_size_bytes); \ + } \ + if ((scmodulus) != NULL) { \ + *(scmodulus) = (scobject)->modulus_exp; \ + } \ + } + +/*! + * Initialize a Authentication-Cipher Context. + * + * @param acobject Pointer to object to operate on. + * @param acmode The mode for this object (only #FSL_ACC_MODE_CCM + * supported). + */ +#define fsl_shw_acco_init(acobject, acmode) \ + { \ + (acobject)->flags = 0; \ + (acobject)->mode = (acmode); \ + } + +/*! + * Set the flags for a Authentication-Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param acobject Pointer to object to operate on. + * @param acflags The flags to set (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +#define fsl_shw_acco_set_flags(acobject, acflags) \ + (acobject)->flags |= (acflags) + +/*! + * Clear some flags in a Authentication-Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param acobject Pointer to object to operate on. + * @param acflags The flags to reset (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +#define fsl_shw_acco_clear_flags(acobject, acflags) \ + (acobject)->flags &= ~(acflags) + +/*! + * Set up the Authentication-Cipher Object for CCM mode. + * + * This will set the @a auth_object for CCM mode and save the @a ctr, + * and @a mac_length. This function can be called instead of + * #fsl_shw_acco_init(). + * + * The paramater @a ctr is Counter Block 0, (counter value 0), which is for the + * MAC. + * + * @param acobject Pointer to object to operate on. + * @param acalg Cipher algorithm. Only AES is supported. + * @param accounter The initial counter value. + * @param acmaclen The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_acco_set_ccm(acobject, acalg, accounter, acmaclen) \ +{ \ + (acobject)->flags = 0; \ + (acobject)->mode = FSL_ACC_MODE_CCM; \ + (acobject)->auth_info.CCM_ctx_info.block_size_bytes = 16; \ + (acobject)->cipher_ctx_info.block_size_bytes = 16; \ + (acobject)->mac_length = acmaclen; \ + fsl_shw_scco_set_counter_info(&(acobject)->cipher_ctx_info, accounter, \ + FSL_CTR_MOD_128); \ +} + +/*! + * Format the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will also set the IV and CTR values per Appendix A of NIST + * Special Publication 800-38C (May 2004). It will also perform the + * #fsl_shw_acco_set_ccm() operation with information derived from this set of + * parameters. + * + * Note this function assumes the algorithm is AES. It initializes the + * @a auth_object by setting the mode to #FSL_ACC_MODE_CCM and setting the + * flags to be #FSL_ACCO_NIST_CCM. + * + * @param acobject Pointer to object to operate on. + * @param act The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + * @param acad Number of octets of Associated Data (may be zero). + * @param acq A value for the size of the length of @a q field. Valid + * values are 1-8. + * @param acN The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param acQ The value of Q (size of the payload in octets). + * + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_ccm_nist_format_ctr_and_iv(acobject, act, acad, acq, acN, acQ)\ + { \ + uint64_t Q = acQ; \ + uint8_t bflag = ((acad)?0x40:0) | ((((act)-2)/2)<<3) | ((acq)-1); \ + unsigned i; \ + uint8_t* qptr = (acobject)->auth_info.CCM_ctx_info.context + 15; \ + (acobject)->auth_info.CCM_ctx_info.block_size_bytes = 16; \ + (acobject)->cipher_ctx_info.block_size_bytes = 16; \ + (acobject)->mode = FSL_ACC_MODE_CCM; \ + (acobject)->flags = FSL_ACCO_NIST_CCM; \ + \ + /* Store away the MAC length (after calculating actual value */ \ + (acobject)->mac_length = (act); \ + /* Set Flag field in Block 0 */ \ + *((acobject)->auth_info.CCM_ctx_info.context) = bflag; \ + /* Set Nonce field in Block 0 */ \ + copy_bytes((acobject)->auth_info.CCM_ctx_info.context+1, acN, \ + 15-(acq)); \ + /* Set Flag field in ctr */ \ + *((acobject)->cipher_ctx_info.context) = (acq)-1; \ + /* Update the Q (payload length) field of Block0 */ \ + (acobject)->q_length = acq; \ + for (i = 0; i < (acq); i++) { \ + *qptr-- = Q & 0xFF; \ + Q >>= 8; \ + } \ + /* Set the Nonce field of the ctr */ \ + copy_bytes((acobject)->cipher_ctx_info.context+1, acN, 15-(acq)); \ + /* Clear the block counter field of the ctr */ \ + memset((acobject)->cipher_ctx_info.context+16-(acq), 0, (acq)+1); \ + } + +/*! + * Update the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will set the IV and CTR values per Appendix A of NIST Special + * Publication 800-38C (May 2004). + * + * Note this function assumes that #fsl_shw_ccm_nist_format_ctr_and_iv() has + * previously been called on the @a auth_object. + * + * @param acobject Pointer to object to operate on. + * @param acN The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param acQ The value of Q (size of the payload in octets). + * + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_ccm_nist_update_ctr_and_iv(acobject, acN, acQ) \ + { \ + uint64_t Q = acQ; \ + unsigned i; \ + uint8_t* qptr = (acobject)->auth_info.CCM_ctx_info.context + 15; \ + \ + /* Update the Nonce field field of Block0 */ \ + copy_bytes((acobject)->auth_info.CCM_ctx_info.context+1, acN, \ + 15 - (acobject)->q_length); \ + /* Update the Q (payload length) field of Block0 */ \ + for (i = 0; i < (acobject)->q_length; i++) { \ + *qptr-- = Q & 0xFF; \ + Q >>= 8; \ + } \ + /* Update the Nonce field of the ctr */ \ + copy_bytes((acobject)->cipher_ctx_info.context+1, acN, \ + 15 - (acobject)->q_length); \ + } + +/****************************************************************************** + * Library functions + *****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-GEN-003 */ +extern fsl_shw_pco_t *fsl_shw_get_capabilities(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-004 */ +extern fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-005 */ +extern fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-006 */ +extern fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t * user_ctx, + unsigned result_size, + fsl_shw_result_t results[], + unsigned *result_count); + +extern fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key); + +extern fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key); + +extern fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info); + +extern void *fsl_shw_smalloc(fsl_shw_uco_t * user_ctx, + uint32_t size, + const uint8_t * UMID, uint32_t permissions); + +extern fsl_shw_return_t fsl_shw_sfree(fsl_shw_uco_t * user_ctx, void *address); + +extern fsl_shw_return_t fsl_shw_sstatus(fsl_shw_uco_t * user_ctx, + void *address, + fsl_shw_partition_status_t * status); + +extern fsl_shw_return_t fsl_shw_diminish_perms(fsl_shw_uco_t * user_ctx, + void *address, + uint32_t permissions); + +extern fsl_shw_return_t do_scc_engage_partition(fsl_shw_uco_t * user_ctx, + void *address, + const uint8_t * UMID, + uint32_t permissions); + +extern fsl_shw_return_t do_system_keystore_slot_alloc(fsl_shw_uco_t * user_ctx, + uint32_t key_lenth, + uint64_t ownerid, + uint32_t * slot); + +extern fsl_shw_return_t do_system_keystore_slot_dealloc(fsl_shw_uco_t * + user_ctx, + uint64_t ownerid, + uint32_t slot); + +extern fsl_shw_return_t do_system_keystore_slot_load(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + const uint8_t * key, + uint32_t key_length); + +extern fsl_shw_return_t do_system_keystore_slot_read(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + const uint8_t * key); + +extern fsl_shw_return_t do_system_keystore_slot_encrypt(fsl_shw_uco_t * + user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + uint8_t * black_data); + +extern fsl_shw_return_t do_system_keystore_slot_decrypt(fsl_shw_uco_t * + user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + const uint8_t * + black_data); + +extern fsl_shw_return_t +do_scc_encrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode); + +extern fsl_shw_return_t +do_scc_decrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, const uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode); + +extern fsl_shw_return_t +system_keystore_get_slot_info(uint64_t owner_id, uint32_t slot, + uint32_t * address, uint32_t * slot_size_bytes); + +/* REQ-S2LRD-PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +extern fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, + uint8_t * ct); + +/* PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +extern fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, + uint8_t * pt); + +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-005 */ +extern fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len); + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-001 */ +extern fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx); + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-002 */ +extern fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len); + +/* REQ-S2LRD-PINTFC-API-BASIC-RNG-002 */ +extern fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data); + +extern fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data); + +extern fsl_shw_return_t fsl_shw_gen_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value); + +extern fsl_shw_return_t fsl_shw_auth_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * ct, + const uint8_t * auth_value, + uint8_t * payload); + +extern fsl_shw_return_t fsl_shw_read_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * key); + +static inline fsl_shw_return_t fsl_shw_gen_random_pf_key(fsl_shw_uco_t * + user_ctx) +{ + (void)user_ctx; + + return FSL_RETURN_NO_RESOURCE_S; +} + +static inline fsl_shw_return_t fsl_shw_read_tamper_event(fsl_shw_uco_t * + user_ctx, + fsl_shw_tamper_t * + tamperp, + uint64_t * timestampp) +{ + (void)user_ctx; + (void)tamperp; + (void)timestampp; + + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t sah_Append_Desc(const sah_Mem_Util * mu, + sah_Head_Desc ** desc_head, + const uint32_t header, + sah_Link * link1, sah_Link * link2); + +/* Utility Function leftover from sahara1 API */ +void sah_Descriptor_Chain_Destroy(const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/* Utility Function leftover from sahara1 API */ +fsl_shw_return_t sah_Descriptor_Chain_Execute(sah_Head_Desc * desc_chain, + fsl_shw_uco_t * user_ctx); + +fsl_shw_return_t sah_Append_Link(const sah_Mem_Util * mu, + sah_Link * link, + uint8_t * p, + const size_t length, + const sah_Link_Flags flags); + +fsl_shw_return_t sah_Create_Link(const sah_Mem_Util * mu, + sah_Link ** link, + uint8_t * p, + const size_t length, + const sah_Link_Flags flags); + +fsl_shw_return_t sah_Create_Key_Link(const sah_Mem_Util * mu, + sah_Link ** link, + fsl_shw_sko_t * key_info); + +void sah_Destroy_Link(const sah_Mem_Util * mu, sah_Link * link); + +void sah_Postprocess_Results(fsl_shw_uco_t * user_ctx, + sah_results * result_info); + +#endif /* SAHARA2_API_H */ diff --git a/drivers/mxc/security/sahara2/include/sahara2_kernel.h b/drivers/mxc/security/sahara2/include/sahara2_kernel.h new file mode 100644 index 000000000000..b833f0ab8f56 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sahara2_kernel.h @@ -0,0 +1,49 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#define DRIVER_NAME sahara2 + +#define SAHARA_MAJOR_NODE 78 + +#include "portable_os.h" + +#include "platform_abstractions.h" + +/* Forward-declare prototypes using signature macros */ + +OS_DEV_ISR_DCL(sahara2_isr); + +OS_DEV_INIT_DCL(sahara2_init); + +OS_DEV_SHUTDOWN_DCL(sahara2_shutdown); + +OS_DEV_OPEN_DCL(sahara2_open); + +OS_DEV_CLOSE_DCL(sahara2_release); + +OS_DEV_IOCTL_DCL(sahara2_ioctl); + +struct sahara2_kernel_user { + void *command_ring[32]; +}; + +struct sahara2_sym_arg { + char *key; + unsigned key_len; +}; + +/*! These need to be added to Linux / OS abstractions */ +/* +module_init(OS_DEV_INIT_REF(sahara2_init)); +module_cleanup(OS_DEV_SHUTDOWN_REF(sahara2_shutdown)); +*/ diff --git a/drivers/mxc/security/sahara2/include/sf_util.h b/drivers/mxc/security/sahara2/include/sf_util.h new file mode 100644 index 000000000000..c0af0c96ff02 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sf_util.h @@ -0,0 +1,466 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file sf_util.h +* +* @brief Header for Sahara Descriptor-chain building Functions +*/ +#ifndef SF_UTIL_H +#define SF_UTIL_H + +#include <fsl_platform.h> +#include <sahara.h> + +/*! Header value for Sahara Descriptor 1 */ +#define SAH_HDR_SKHA_SET_MODE_IV_KEY 0x10880000 +/*! Header value for Sahara Descriptor 2 */ +#define SAH_HDR_SKHA_SET_MODE_ENC_DEC 0x108D0000 +/*! Header value for Sahara Descriptor 4 */ +#define SAH_HDR_SKHA_ENC_DEC 0x90850000 +/*! Header value for Sahara Descriptor 5 */ +#define SAH_HDR_SKHA_READ_CONTEXT_IV 0x10820000 +/*! Header value for Sahara Descriptor 6 */ +#define SAH_HDR_MDHA_SET_MODE_MD_KEY 0x20880000 +/*! Header value for Sahara Descriptor 8 */ +#define SAH_HDR_MDHA_SET_MODE_HASH 0x208D0000 +/*! Header value for Sahara Descriptor 10 */ +#define SAH_HDR_MDHA_HASH 0xA0850000 +/*! Header value for Sahara Descriptor 11 */ +#define SAH_HDR_MDHA_STORE_DIGEST 0x20820000 +/*! Header value for Sahara Descriptor 18 */ +#define SAH_HDR_RNG_GENERATE 0x308C0000 +/*! Header value for Sahara Descriptor 19 */ +#define SAH_HDR_PKHA_LD_N_E 0xC0800000 +/*! Header value for Sahara Descriptor 20 */ +#define SAH_HDR_PKHA_LD_A_EX_ST_B 0x408D0000 +/*! Header value for Sahara Descriptor 21 */ +#define SAH_HDR_PKHA_LD_N_EX_ST_B 0x408E0000 +/*! Header value for Sahara Descriptor 22 */ +#define SAH_HDR_PKHA_LD_A_B 0xC0830000 +/*! Header value for Sahara Descriptor 23 */ +#define SAH_HDR_PKHA_LD_A0_A1 0x40840000 +/*! Header value for Sahara Descriptor 24 */ +#define SAH_HDR_PKHA_LD_A2_A3 0xC0850000 +/*! Header value for Sahara Descriptor 25 */ +#define SAH_HDR_PKHA_LD_B0_B1 0xC0860000 +/*! Header value for Sahara Descriptor 26 */ +#define SAH_HDR_PKHA_LD_B2_B3 0x40870000 +/*! Header value for Sahara Descriptor 27 */ +#define SAH_HDR_PKHA_ST_A_B 0x40820000 +/*! Header value for Sahara Descriptor 28 */ +#define SAH_HDR_PKHA_ST_A0_A1 0x40880000 +/*! Header value for Sahara Descriptor 29 */ +#define SAH_HDR_PKHA_ST_A2_A3 0xC0890000 +/*! Header value for Sahara Descriptor 30 */ +#define SAH_HDR_PKHA_ST_B0_B1 0xC08A0000 +/*! Header value for Sahara Descriptor 31 */ +#define SAH_HDR_PKHA_ST_B2_B3 0x408B0000 +/*! Header value for Sahara Descriptor 32 */ +#define SAH_HDR_PKHA_EX_ST_B1 0xC08C0000 +/*! Header value for Sahara Descriptor 33 */ +#define SAH_HDR_ARC4_SET_MODE_SBOX 0x90890000 +/*! Header value for Sahara Descriptor 34 */ +#define SAH_HDR_ARC4_READ_SBOX 0x90860000 +/*! Header value for Sahara Descriptor 35 */ +#define SAH_HDR_ARC4_SET_MODE_KEY 0x90830000 +/*! Header value for Sahara Descriptor 36 */ +#define SAH_HDR_PKHA_LD_A3_B0 0x40810000 +/*! Header value for Sahara Descriptor 37 */ +#define SAH_HDR_PKHA_ST_B1_B2 0xC08F0000 +/*! Header value for Sahara Descriptor 38 */ +#define SAH_HDR_SKHA_CBC_ICV 0x10840000 +/*! Header value for Sahara Descriptor 39 */ +#define SAH_HDR_MDHA_ICV_CHECK 0xA08A0000 + +/*! Header bit indicating "Link-List optimization" */ +#define SAH_HDR_LLO 0x01000000 + +#define SAH_SF_DCLS \ + fsl_shw_return_t ret; \ + unsigned sf_executed = 0; \ + sah_Head_Desc* desc_chain = NULL; \ + uint32_t header + +#define SAH_SF_USER_CHECK() \ +do { \ + ret = sah_validate_uco(user_ctx); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} while (0) + +#define SAH_SF_EXECUTE() \ +do { \ + sf_executed = 1; \ + ret = sah_Descriptor_Chain_Execute(desc_chain, user_ctx); \ +} while (0) + +#define SAH_SF_DESC_CLEAN() \ +do { \ + if (!sf_executed || (user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { \ + sah_Descriptor_Chain_Destroy(user_ctx->mem_util, &desc_chain); \ + } \ + (void) header; \ +} while (0) + +/*! Add Descriptor with two inputs */ +#define DESC_IN_IN(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_two_in_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with two vectors */ +#define DESC_D_D(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_two_d_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with input and a key */ +#define DESC_IN_KEY(hdr, len1, ptr1, key2) \ +{ \ + ret = sah_add_in_key_desc(hdr, ptr1, len1, key2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with input and an output */ +#define DESC_IN_OUT(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_in_out_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with input and a key output */ +#define DESC_IN_KEYOUT(hdr, len1, ptr1, key2) \ +{ \ + ret = sah_add_in_keyout_desc(hdr, ptr1, len1, key2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with a key and an output */ +#define DESC_KEY_OUT(hdr, key1, len2, ptr2) \ +{ \ + ret = sah_add_key_out_desc(hdr, key1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with two outputs */ +#define DESC_OUT_OUT(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_two_out_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with output then input pointers */ +#define DESC_OUT_IN(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_out_in_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +#ifdef SAH_SF_DEBUG +/*! Add Descriptor with two outputs */ +#define DBG_DESC(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_two_out_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} +#else +#define DBG_DESC(hdr, len1, ptr1, len2, ptr2) +#endif + +#ifdef __KERNEL__ +#define DESC_DBG_ON ({console_loglevel = 8;}) +#define DESC_DBG_OFF ({console_loglevel = 7;}) +#else +#define DESC_DBG_ON system("echo 8 > /proc/sys/kernel/printk") +#define DESC_DBG_OFF system("echo 7 > /proc/sys/kernel/printk") +#endif + +#define DESC_TEMP_ALLOC(size) \ +({ \ + uint8_t* ptr; \ + ptr = user_ctx->mem_util->mu_malloc(user_ctx->mem_util->mu_ref, \ + size); \ + if (ptr == NULL) { \ + ret = FSL_RETURN_NO_RESOURCE_S; \ + goto out; \ + } \ + \ + ptr; \ +}) + +#define DESC_TEMP_FREE(ptr) \ +({ \ + if ((ptr != NULL) && \ + (!sf_executed || (user_ctx->flags & FSL_UCO_BLOCKING_MODE))) { \ + user_ctx->mem_util-> \ + mu_free(user_ctx->mem_util->mu_ref, ptr); \ + ptr = NULL; \ + } \ +}) + +/* Temporary implementation. This needs to be in internal/secure RAM */ +#define DESC_TEMP_SECURE_ALLOC(size) \ +({ \ + uint8_t* ptr; \ + ptr = user_ctx->mem_util->mu_malloc(user_ctx->mem_util->mu_ref, \ + size); \ + if (ptr == NULL) { \ + ret = FSL_RETURN_NO_RESOURCE_S; \ + goto out; \ + } \ + \ + ptr; \ +}) + +#define DESC_TEMP_SECURE_FREE(ptr, size) \ +({ \ + if ((ptr != NULL) && \ + (!sf_executed || (user_ctx->flags & FSL_UCO_BLOCKING_MODE))) { \ + user_ctx->mem_util->mu_memset(user_ctx->mem_util->mu_ref, \ + ptr, 0, size); \ + \ + user_ctx->mem_util-> \ + mu_free(user_ctx->mem_util->mu_ref, ptr); \ + ptr = NULL; \ + } \ +}) + +extern const uint32_t sah_insert_mdha_algorithm[]; + +/*! @defgroup mdhaflags MDHA Mode Register Values + * + * These are bit fields and combinations of bit fields for setting the Mode + * Register portion of a Sahara Descriptor Header field. + * + * The parity bit has been set to ensure that these values have even parity, + * therefore using an Exclusive-OR operation against an existing header will + * preserve its parity. + * + * @addtogroup mdhaflags + @{ + */ +#define sah_insert_mdha_icv_check 0x80001000 +#define sah_insert_mdha_ssl 0x80000400 +#define sah_insert_mdha_mac_full 0x80000200 +#define sah_insert_mdha_opad 0x80000080 +#define sah_insert_mdha_ipad 0x80000040 +#define sah_insert_mdha_init 0x80000020 +#define sah_insert_mdha_hmac 0x80000008 +#define sah_insert_mdha_pdata 0x80000004 +#define sah_insert_mdha_algorithm_sha224 0x00000003 +#define sah_insert_mdha_algorithm_sha256 0x80000002 +#define sah_insert_mdha_algorithm_md5 0x80000001 +#define sah_insert_mdha_algorithm_sha1 0x00000000 +/*! @} */ + +extern const uint32_t sah_insert_skha_algorithm[]; +extern const uint32_t sah_insert_skha_mode[]; +extern const uint32_t sah_insert_skha_modulus[]; + +/*! @defgroup skhaflags SKHA Mode Register Values + * + * These are bit fields and combinations of bit fields for setting the Mode + * Register portion of a Sahara Descriptor Header field. + * + * The parity bit has been set to ensure that these values have even parity, + * therefore using an Exclusive-OR operation against an existing header will + * preserve its parity. + * + * @addtogroup skhaflags + @{ + */ +/*! */ +#define sah_insert_skha_modulus_128 0x00001e00 +#define sah_insert_skha_no_key_parity 0x80000100 +#define sah_insert_skha_ctr_last_block 0x80000020 +#define sah_insert_skha_suppress_cbc 0x80000020 +#define sah_insert_skha_no_permute 0x80000020 +#define sah_insert_skha_algorithm_arc4 0x00000003 +#define sah_insert_skha_algorithm_tdes 0x80000002 +#define sah_insert_skha_algorithm_des 0x80000001 +#define sah_insert_skha_algorithm_aes 0x00000000 +#define sah_insert_skha_aux0 0x80000020 +#define sah_insert_skha_mode_ctr 0x00000018 +#define sah_insert_skha_mode_ccm 0x80000010 +#define sah_insert_skha_mode_cbc 0x80000008 +#define sah_insert_skha_mode_ecb 0x00000000 +#define sah_insert_skha_encrypt 0x80000004 +#define sah_insert_skha_decrypt 0x00000000 +/*! @} */ + +/*! @defgroup rngflags RNG Mode Register Values + * + */ +/*! */ +#define sah_insert_rng_gen_seed 0x80000001 + +/*! @} */ + +/*! @defgroup pkhaflags PKHA Mode Register Values + * + */ +/*! */ +#define sah_insert_pkha_soft_err_false 0x80000200 +#define sah_insert_pkha_soft_err_true 0x80000100 + +#define sah_insert_pkha_rtn_clr_mem 0x80000001 +#define sah_insert_pkha_rtn_clr_eram 0x80000002 +#define sah_insert_pkha_rtn_mod_exp 0x00000003 +#define sah_insert_pkha_rtn_mod_r2modn 0x80000004 +#define sah_insert_pkha_rtn_mod_rrmodp 0x00000005 +#define sah_insert_pkha_rtn_ec_fp_aff_ptmul 0x00000006 +#define sah_insert_pkha_rtn_ec_f2m_aff_ptmul 0x80000007 +#define sah_insert_pkha_rtn_ec_fp_proj_ptmul 0x80000008 +#define sah_insert_pkha_rtn_ec_f2m_proj_ptmul 0x00000009 +#define sah_insert_pkha_rtn_ec_fp_add 0x0000000A +#define sah_insert_pkha_rtn_ec_fp_double 0x8000000B +#define sah_insert_pkha_rtn_ec_f2m_add 0x0000000C +#define sah_insert_pkha_rtn_ec_f2m_double 0x8000000D +#define sah_insert_pkha_rtn_f2m_r2modn 0x8000000E +#define sah_insert_pkha_rtn_f2m_inv 0x0000000F +#define sah_insert_pkha_rtn_mod_inv 0x80000010 +#define sah_insert_pkha_rtn_rsa_sstep 0x00000011 +#define sah_insert_pkha_rtn_mod_emodn 0x00000012 +#define sah_insert_pkha_rtn_f2m_emodn 0x80000013 +#define sah_insert_pkha_rtn_ec_fp_ptmul 0x00000014 +#define sah_insert_pkha_rtn_ec_f2m_ptmul 0x80000015 +#define sah_insert_pkha_rtn_f2m_gcd 0x80000016 +#define sah_insert_pkha_rtn_mod_gcd 0x00000017 +#define sah_insert_pkha_rtn_f2m_dbl_aff 0x00000018 +#define sah_insert_pkha_rtn_fp_dbl_aff 0x80000019 +#define sah_insert_pkha_rtn_f2m_add_aff 0x8000001A +#define sah_insert_pkha_rtn_fp_add_aff 0x0000001B +#define sah_insert_pkha_rtn_f2m_exp 0x8000001C +#define sah_insert_pkha_rtn_mod_exp_teq 0x0000001D +#define sah_insert_pkha_rtn_rsa_sstep_teq 0x0000001E +#define sah_insert_pkha_rtn_f2m_multn 0x8000001F +#define sah_insert_pkha_rtn_mod_multn 0x80000020 +#define sah_insert_pkha_rtn_mod_add 0x00000021 +#define sah_insert_pkha_rtn_mod_sub 0x00000022 +#define sah_insert_pkha_rtn_mod_mult1_mont 0x80000023 +#define sah_insert_pkha_rtn_mod_mult2_deconv 0x00000024 +#define sah_insert_pkha_rtn_f2m_add 0x80000025 +#define sah_insert_pkha_rtn_f2m_mult1_mont 0x80000026 +#define sah_insert_pkha_rtn_f2m_mult2_deconv 0x00000027 +#define sah_insert_pkha_rtn_miller_rabin 0x00000028 +#define sah_insert_pkha_rtn_mod_amodn 0x00000029 +#define sah_insert_pkha_rtn_f2m_amodn 0x8000002A +/*! @} */ + +/*! Add a descriptor with two input pointers */ +fsl_shw_return_t sah_add_two_in_desc(uint32_t header, + const uint8_t * in1, + uint32_t in1_length, + const uint8_t * in2, + uint32_t in2_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with two 'data' pointers */ +fsl_shw_return_t sah_add_two_d_desc(uint32_t header, + const uint8_t * in1, + uint32_t in1_length, + const uint8_t * in2, + uint32_t in2_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with an input and key pointer */ +fsl_shw_return_t sah_add_in_key_desc(uint32_t header, + const uint8_t * in1, + uint32_t in1_length, + fsl_shw_sko_t * key_info, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with two key pointers */ +fsl_shw_return_t sah_add_key_key_desc(uint32_t header, + fsl_shw_sko_t * key_info1, + fsl_shw_sko_t * key_info2, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with two output pointers */ +fsl_shw_return_t sah_add_two_out_desc(uint32_t header, + uint8_t * out1, + uint32_t out1_length, + uint8_t * out2, + uint32_t out2_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with an input and output pointer */ +fsl_shw_return_t sah_add_in_out_desc(uint32_t header, + const uint8_t * in, uint32_t in_length, + uint8_t * out, uint32_t out_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with an input and key output pointer */ +fsl_shw_return_t sah_add_in_keyout_desc(uint32_t header, + const uint8_t * in, uint32_t in_length, + fsl_shw_sko_t * key_info, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with a key and an output pointer */ +fsl_shw_return_t sah_add_key_out_desc(uint32_t header, + const fsl_shw_sko_t * key_info, + uint8_t * out, uint32_t out_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with an output and input pointer */ +fsl_shw_return_t sah_add_out_in_desc(uint32_t header, + uint8_t * out, uint32_t out_length, + const uint8_t * in, uint32_t in_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Verify that supplied User Context Object is valid */ +fsl_shw_return_t sah_validate_uco(fsl_shw_uco_t * uco); + +#endif /* SF_UTIL_H */ + +/* End of sf_util.h */ diff --git a/drivers/mxc/security/sahara2/km_adaptor.c b/drivers/mxc/security/sahara2/km_adaptor.c new file mode 100644 index 000000000000..50c4eac3c701 --- /dev/null +++ b/drivers/mxc/security/sahara2/km_adaptor.c @@ -0,0 +1,849 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file km_adaptor.c +* +* @brief The Adaptor component provides an interface to the +* driver for a kernel user. +*/ + +#include <adaptor.h> +#include <sf_util.h> +#include <sah_queue_manager.h> +#include <sah_memory_mapper.h> +#include <fsl_shw_keystore.h> +#ifdef FSL_HAVE_SCC +#include <linux/mxc_scc_driver.h> +#elif defined (FSL_HAVE_SCC2) +#include <linux/mxc_scc2_driver.h> +#endif + + +EXPORT_SYMBOL(adaptor_Exec_Descriptor_Chain); +EXPORT_SYMBOL(sah_register); +EXPORT_SYMBOL(sah_deregister); +EXPORT_SYMBOL(sah_get_results); +EXPORT_SYMBOL(fsl_shw_smalloc); +EXPORT_SYMBOL(fsl_shw_sfree); +EXPORT_SYMBOL(fsl_shw_sstatus); +EXPORT_SYMBOL(fsl_shw_diminish_perms); +EXPORT_SYMBOL(do_scc_encrypt_region); +EXPORT_SYMBOL(do_scc_decrypt_region); +EXPORT_SYMBOL(do_system_keystore_slot_alloc); +EXPORT_SYMBOL(do_system_keystore_slot_dealloc); +EXPORT_SYMBOL(do_system_keystore_slot_load); +EXPORT_SYMBOL(do_system_keystore_slot_read); +EXPORT_SYMBOL(do_system_keystore_slot_encrypt); +EXPORT_SYMBOL(do_system_keystore_slot_decrypt); + + +#if defined(DIAG_DRV_IF) || defined(DIAG_MEM) || defined(DIAG_ADAPTOR) +#include <diagnostic.h> +#endif + +#if defined(DIAG_DRV_IF) || defined(DIAG_MEM) || defined(DIAG_ADAPTOR) +#define MAX_DUMP 16 + +#define DIAG_MSG_SIZE 300 +static char Diag_msg[DIAG_MSG_SIZE]; +#endif + +/* This is the wait queue to this mode of driver */ +DECLARE_WAIT_QUEUE_HEAD(Wait_queue_km); + +/*! This matches Sahara2 capabilities... */ +fsl_shw_pco_t sahara2_capabilities = { + 1, 3, /* api version number - major & minor */ + 1, 6, /* driver version number - major & minor */ + { + FSL_KEY_ALG_AES, + FSL_KEY_ALG_DES, + FSL_KEY_ALG_TDES, + FSL_KEY_ALG_ARC4}, + { + FSL_SYM_MODE_STREAM, + FSL_SYM_MODE_ECB, + FSL_SYM_MODE_CBC, + FSL_SYM_MODE_CTR}, + { + FSL_HASH_ALG_MD5, + FSL_HASH_ALG_SHA1, + FSL_HASH_ALG_SHA224, + FSL_HASH_ALG_SHA256}, + /* + * The following table must be set to handle all values of key algorithm + * and sym mode, and be in the correct order.. + */ + { /* Stream, ECB, CBC, CTR */ + {0, 0, 0, 0}, /* HMAC */ + {0, 1, 1, 1}, /* AES */ + {0, 1, 1, 0}, /* DES */ + {0, 1, 1, 0}, /* 3DES */ + {1, 0, 0, 0} /* ARC4 */ + }, + 0, 0, + 0, 0, 0, + {{0, 0}} +}; + +#ifdef DIAG_ADAPTOR +void km_Dump_Chain(const sah_Desc * chain); + +void km_Dump_Region(const char *prefix, const unsigned char *data, + unsigned length); + +static void km_Dump_Link(const char *prefix, const sah_Link * link); + +void km_Dump_Words(const char *prefix, const unsigned *data, unsigned length); +#endif + +/**** Memory routines ****/ + +static void *my_malloc(void *ref, size_t n) +{ + register void *mem; + +#ifndef DIAG_MEM_ERRORS + mem = os_alloc_memory(n, GFP_KERNEL); + +#else + { + uint32_t rand; + /* are we feeling lucky ? */ + os_get_random_bytes(&rand, sizeof(rand)); + if ((rand % DIAG_MEM_CONST) == 0) { + mem = 0; + } else { + mem = os_alloc_memory(n, GFP_ATOMIC); + } + } +#endif /* DIAG_MEM_ERRORS */ + +#ifdef DIAG_MEM + sprintf(Diag_msg, "API kmalloc: %p for %d\n", mem, n); + LOG_KDIAG(Diag_msg); +#endif + ref = 0; /* unused param warning */ + return mem; +} + +static sah_Head_Desc *my_alloc_head_desc(void *ref) +{ + register sah_Head_Desc *ptr; + +#ifndef DIAG_MEM_ERRORS + ptr = sah_Alloc_Head_Descriptor(); + +#else + { + uint32_t rand; + /* are we feeling lucky ? */ + os_get_random_bytes(&rand, sizeof(rand)); + if ((rand % DIAG_MEM_CONST) == 0) { + ptr = 0; + } else { + ptr = sah_Alloc_Head_Descriptor(); + } + } +#endif + ref = 0; + return ptr; +} + +static sah_Desc *my_alloc_desc(void *ref) +{ + register sah_Desc *ptr; + +#ifndef DIAG_MEM_ERRORS + ptr = sah_Alloc_Descriptor(); + +#else + { + uint32_t rand; + /* are we feeling lucky ? */ + os_get_random_bytes(&rand, sizeof(rand)); + if ((rand % DIAG_MEM_CONST) == 0) { + ptr = 0; + } else { + ptr = sah_Alloc_Descriptor(); + } + } +#endif + ref = 0; + return ptr; +} + +static sah_Link *my_alloc_link(void *ref) +{ + register sah_Link *ptr; + +#ifndef DIAG_MEM_ERRORS + ptr = sah_Alloc_Link(); + +#else + { + uint32_t rand; + /* are we feeling lucky ? */ + os_get_random_bytes(&rand, sizeof(rand)); + if ((rand % DIAG_MEM_CONST) == 0) { + ptr = 0; + } else { + ptr = sah_Alloc_Link(); + } + } +#endif + ref = 0; + return ptr; +} + +static void my_free(void *ref, void *ptr) +{ + ref = 0; /* unused param warning */ +#ifdef DIAG_MEM + sprintf(Diag_msg, "API kfree: %p\n", ptr); + LOG_KDIAG(Diag_msg); +#endif + os_free_memory(ptr); +} + +static void my_free_head_desc(void *ref, sah_Head_Desc * ptr) +{ + sah_Free_Head_Descriptor(ptr); +} + +static void my_free_desc(void *ref, sah_Desc * ptr) +{ + sah_Free_Descriptor(ptr); +} + +static void my_free_link(void *ref, sah_Link * ptr) +{ + sah_Free_Link(ptr); +} + +static void *my_memcpy(void *ref, void *dest, const void *src, size_t n) +{ + ref = 0; /* unused param warning */ + return memcpy(dest, src, n); +} + +static void *my_memset(void *ref, void *ptr, int ch, size_t n) +{ + ref = 0; /* unused param warning */ + return memset(ptr, ch, n); +} + +/*! Standard memory manipulation routines for kernel API. */ +static sah_Mem_Util std_kernelmode_mem_util = { + .mu_ref = 0, + .mu_malloc = my_malloc, + .mu_alloc_head_desc = my_alloc_head_desc, + .mu_alloc_desc = my_alloc_desc, + .mu_alloc_link = my_alloc_link, + .mu_free = my_free, + .mu_free_head_desc = my_free_head_desc, + .mu_free_desc = my_free_desc, + .mu_free_link = my_free_link, + .mu_memcpy = my_memcpy, + .mu_memset = my_memset +}; + +fsl_shw_return_t get_capabilities(fsl_shw_uco_t * user_ctx, + fsl_shw_pco_t * capabilities) +{ + scc_config_t *scc_capabilities; + + /* Fill in the Sahara2 capabilities. */ + memcpy(capabilities, &sahara2_capabilities, sizeof(fsl_shw_pco_t)); + + /* Fill in the SCC portion of the capabilities object */ + scc_capabilities = scc_get_configuration(); + capabilities->scc_driver_major = scc_capabilities->driver_major_version; + capabilities->scc_driver_minor = scc_capabilities->driver_minor_version; + capabilities->scm_version = scc_capabilities->scm_version; + capabilities->smn_version = scc_capabilities->smn_version; + capabilities->block_size_bytes = scc_capabilities->block_size_bytes; + +#ifdef FSL_HAVE_SCC + capabilities->scc_info.black_ram_size_blocks = + scc_capabilities->black_ram_size_blocks; + capabilities->scc_info.red_ram_size_blocks = + scc_capabilities->red_ram_size_blocks; +#elif defined(FSL_HAVE_SCC2) + capabilities->scc2_info.partition_size_bytes = + scc_capabilities->partition_size_bytes; + capabilities->scc2_info.partition_count = + scc_capabilities->partition_count; +#endif + + return FSL_RETURN_OK_S; +} + +/*! + * Sends a request to register this user + * + * @brief Sends a request to register this user + * + * @param[in,out] user_ctx part of the structure contains input parameters and + * part is filled in by the driver + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_register(fsl_shw_uco_t * user_ctx) +{ + fsl_shw_return_t status; + + /* this field is used in user mode to indicate a file open has occured. + * it is used here, in kernel mode, to indicate that the uco is registered + */ + user_ctx->sahara_openfd = 0; /* set to 'registered' */ + user_ctx->mem_util = &std_kernelmode_mem_util; + + /* check that uco is valid */ + status = sah_validate_uco(user_ctx); + + /* If life is good, register this user */ + if (status == FSL_RETURN_OK_S) { + status = sah_handle_registration(user_ctx); + } + + if (status != FSL_RETURN_OK_S) { + user_ctx->sahara_openfd = -1; /* set to 'not registered' */ + } + + return status; +} + +/*! + * Sends a request to deregister this user + * + * @brief Sends a request to deregister this user + * + * @param[in,out] user_ctx Info on user being deregistered. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_deregister(fsl_shw_uco_t * user_ctx) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + + if (user_ctx->sahara_openfd == 0) { + status = sah_handle_deregistration(user_ctx); + user_ctx->sahara_openfd = -1; /* set to 'no registered */ + } + + return status; +} + +/*! + * Sends a request to get results for this user + * + * @brief Sends a request to get results for this user + * + * @param[in,out] arg Pointer to structure to collect results + * @param uco User's context + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_get_results(sah_results * arg, fsl_shw_uco_t * uco) +{ + fsl_shw_return_t code = sah_get_results_from_pool(uco, arg); + + if ((code == FSL_RETURN_OK_S) && (arg->actual != 0)) { + sah_Postprocess_Results(uco, arg); + } + + return code; +} + +/*! + * This function writes the Descriptor Chain to the kernel driver. + * + * @brief Writes the Descriptor Chain to the kernel driver. + * + * @param dar A pointer to a Descriptor Chain of type sah_Head_Desc + * @param uco The user context object + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t adaptor_Exec_Descriptor_Chain(sah_Head_Desc * dar, + fsl_shw_uco_t * uco) +{ + sah_Head_Desc *kernel_space_desc = NULL; + fsl_shw_return_t code = FSL_RETURN_OK_S; + int os_error_code = 0; + unsigned blocking_mode = dar->uco_flags & FSL_UCO_BLOCKING_MODE; + +#ifdef DIAG_ADAPTOR + km_Dump_Chain(&dar->desc); +#endif + + dar->user_info = uco; + dar->user_desc = dar; + + /* This code has been shamelessly copied from sah_driver_interface.c */ + /* It needs to be moved somewhere common ... */ + kernel_space_desc = sah_Physicalise_Descriptors(dar); + + if (kernel_space_desc == NULL) { + /* We may have failed due to a -EFAULT as well, but we will return + * -ENOMEM since either way it is a memory related failure. */ + code = FSL_RETURN_NO_RESOURCE_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Physicalise_Descriptors() failed\n"); +#endif + } else { + if (blocking_mode) { +#ifdef SAHARA_POLL_MODE + os_error_code = sah_Handle_Poll(dar); +#else + os_error_code = sah_blocking_mode(dar); +#endif + if (os_error_code != 0) { + code = FSL_RETURN_ERROR_S; + } else { /* status of actual operation */ + code = dar->result; + } + } else { +#ifdef SAHARA_POLL_MODE + sah_Handle_Poll(dar); +#else + /* just put someting in the DAR */ + sah_Queue_Manager_Append_Entry(dar); +#endif /* SAHARA_POLL_MODE */ + } + } + + return code; +} + + +/* System keystore context, defined in sah_driver_interface.c */ +extern fsl_shw_kso_t system_keystore; + +fsl_shw_return_t do_system_keystore_slot_alloc(fsl_shw_uco_t * user_ctx, + uint32_t key_length, + uint64_t ownerid, + uint32_t * slot) +{ + (void)user_ctx; + return keystore_slot_alloc(&system_keystore, key_length, ownerid, slot); +} + +fsl_shw_return_t do_system_keystore_slot_dealloc(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot) +{ + (void)user_ctx; + return keystore_slot_dealloc(&system_keystore, ownerid, slot); +} + +fsl_shw_return_t do_system_keystore_slot_load(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + const uint8_t * key, + uint32_t key_length) +{ + (void)user_ctx; + return keystore_slot_load(&system_keystore, ownerid, slot, + (void *)key, key_length); +} + +fsl_shw_return_t do_system_keystore_slot_read(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + const uint8_t * key) +{ + (void)user_ctx; + return keystore_slot_read(&system_keystore, ownerid, slot, + key_length, (void *)key); +} + +fsl_shw_return_t do_system_keystore_slot_encrypt(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + uint8_t * black_data) +{ + (void)user_ctx; + return keystore_slot_encrypt(NULL, &system_keystore, ownerid, + slot, key_length, black_data); +} + +fsl_shw_return_t do_system_keystore_slot_decrypt(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + const uint8_t * black_data) +{ + (void)user_ctx; + return keystore_slot_decrypt(NULL, &system_keystore, ownerid, + slot, key_length, black_data); +} + +void *fsl_shw_smalloc(fsl_shw_uco_t * user_ctx, + uint32_t size, const uint8_t * UMID, uint32_t permissions) +{ +#ifdef FSL_HAVE_SCC2 + int part_no; + void *part_base; + uint32_t part_phys; + scc_config_t *scc_configuration; + + /* Check that the memory size requested is correct */ + scc_configuration = scc_get_configuration(); + if (size != scc_configuration->partition_size_bytes) { + return NULL; + } + + /* Attempt to grab a partition. */ + if (scc_allocate_partition(0, &part_no, &part_base, &part_phys) + != SCC_RET_OK) { + return NULL; + } + printk(KERN_ALERT "In fsh_shw_smalloc (km): partition_base:%p " + "partition_base_phys: %p\n", part_base, (void *)part_phys); + + /* these bits should be in a separate function */ + printk(KERN_ALERT "writing UMID and MAP to secure the partition\n"); + + scc_engage_partition(part_base, UMID, permissions); + + (void)user_ctx; /* unused param warning */ + + return part_base; +#else /* FSL_HAVE_SCC2 */ + (void)user_ctx; + (void)size; + (void)UMID; + (void)permissions; + return NULL; +#endif /* FSL_HAVE_SCC2 */ + +} + +fsl_shw_return_t fsl_shw_sfree(fsl_shw_uco_t * user_ctx, void *address) +{ + (void)user_ctx; + +#ifdef FSL_HAVE_SCC2 + if (scc_release_partition(address) == SCC_RET_OK) { + return FSL_RETURN_OK_S; + } +#endif + + return FSL_RETURN_ERROR_S; +} + +fsl_shw_return_t fsl_shw_sstatus(fsl_shw_uco_t * user_ctx, + void *address, + fsl_shw_partition_status_t * status) +{ + (void)user_ctx; + +#ifdef FSL_HAVE_SCC2 + *status = scc_partition_status(address); + return FSL_RETURN_OK_S; +#endif + + return FSL_RETURN_ERROR_S; +} + +/* Diminish permissions on some secure memory */ +fsl_shw_return_t fsl_shw_diminish_perms(fsl_shw_uco_t * user_ctx, + void *address, uint32_t permissions) +{ + + (void)user_ctx; /* unused parameter warning */ + +#ifdef FSL_HAVE_SCC2 + if (scc_diminish_permissions(address, permissions) == SCC_RET_OK) { + return FSL_RETURN_OK_S; + } +#endif + return FSL_RETURN_ERROR_S; +} + +/* + * partition_base - physical address of the partition + * offset - offset, in blocks, of the data from the start of the partition + * length - length, in bytes, of the data to be encrypted (multiple of 4) + * black_data - virtual address that the encrypted data should be stored at + * Note that this virtual address must be translatable using the __virt_to_phys + * macro; ie, it can't be a specially mapped address. To do encryption with those + * addresses, use the scc_encrypt_region function directly. This is to make + * this function compatible with the user mode declaration, which does not know + * the physical addresses of the data it is using. + */ +fsl_shw_return_t +do_scc_encrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode) +{ + scc_return_t scc_ret; + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + +#ifdef FSL_HAVE_SCC2 + +#ifdef DIAG_ADAPTOR + uint32_t *owner_32 = (uint32_t *) & (owner_id); + + LOG_KDIAG_ARGS + ("partition base: %p, offset: %i, count: %i, black data: %p\n", + partition_base, offset_bytes, byte_count, (void *)black_data); +#endif + (void)user_ctx; + + os_cache_flush_range(black_data, byte_count); + + scc_ret = + scc_encrypt_region((uint32_t) partition_base, offset_bytes, + byte_count, __virt_to_phys(black_data), IV, + cypher_mode); + + if (scc_ret == SCC_RET_OK) { + retval = FSL_RETURN_OK_S; + } else { + retval = FSL_RETURN_ERROR_S; + } + + /* The SCC2 DMA engine should have written to the black ram, so we need to + * invalidate that region of memory. Note that the red ram is not an + * because it is mapped with the cache disabled. + */ + os_cache_inv_range(black_data, byte_count); + +#else + (void)scc_ret; +#endif /* FSL_HAVE_SCC2 */ + + return retval; +} + +/*! + * Call the proper function to decrypt a region of encrypted secure memory + * + * @brief + * + * @param user_ctx User context of the partition owner (NULL in kernel) + * @param partition_base Base address (physical) of the partition + * @param offset_bytes Offset from base address that the decrypted data + * shall be placed + * @param byte_count Length of the message (bytes) + * @param black_data Pointer to where the encrypted data is stored + * @param owner_id + * + * @return status + */ + +fsl_shw_return_t +do_scc_decrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, const uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode) +{ + scc_return_t scc_ret; + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + +#ifdef FSL_HAVE_SCC2 + +#ifdef DIAG_ADAPTOR + uint32_t *owner_32 = (uint32_t *) & (owner_id); + + LOG_KDIAG_ARGS + ("partition base: %p, offset: %i, count: %i, black data: %p\n", + partition_base, offset_bytes, byte_count, (void *)black_data); +#endif + + (void)user_ctx; + + /* The SCC2 DMA engine will be reading from the black ram, so we need to + * make sure that the data is pushed out of the cache. Note that the red + * ram is not an issue because it is mapped with the cache disabled. + */ + os_cache_flush_range(black_data, byte_count); + + scc_ret = + scc_decrypt_region((uint32_t) partition_base, offset_bytes, + byte_count, + (uint8_t *) __virt_to_phys(black_data), IV, + cypher_mode); + + if (scc_ret == SCC_RET_OK) { + retval = FSL_RETURN_OK_S; + } else { + retval = FSL_RETURN_ERROR_S; + } + +#else + (void)scc_ret; +#endif /* FSL_HAVE_SCC2 */ + + return retval; +} + +#ifdef DIAG_ADAPTOR +/*! + * Dump chain of descriptors to the log. + * + * @brief Dump descriptor chain + * + * @param chain Kernel virtual address of start of chain of descriptors + * + * @return void + */ +void km_Dump_Chain(const sah_Desc * chain) +{ + while (chain != NULL) { + km_Dump_Words("Desc", (unsigned *)chain, + 6 /*sizeof(*chain)/sizeof(unsigned) */ ); + /* place this definition elsewhere */ + if (chain->ptr1) { + if (chain->header & SAH_HDR_LLO) { + km_Dump_Region(" Data1", chain->ptr1, + chain->len1); + } else { + km_Dump_Link(" Link1", chain->ptr1); + } + } + if (chain->ptr2) { + if (chain->header & SAH_HDR_LLO) { + km_Dump_Region(" Data2", chain->ptr2, + chain->len2); + } else { + km_Dump_Link(" Link2", chain->ptr2); + } + } + + chain = chain->next; + } +} + +/*! + * Dump chain of links to the log. + * + * @brief Dump chain of links + * + * @param prefix Text to put in front of dumped data + * @param link Kernel virtual address of start of chain of links + * + * @return void + */ +static void km_Dump_Link(const char *prefix, const sah_Link * link) +{ + while (link != NULL) { + km_Dump_Words(prefix, (unsigned *)link, + 3 /* # words in h/w link */ ); + if (link->flags & SAH_STORED_KEY_INFO) { +#ifdef CAN_DUMP_SCC_DATA + uint32_t len; +#endif + +#ifdef CAN_DUMP_SCC_DATA + { + char buf[50]; + + scc_get_slot_info(link->ownerid, link->slot, (uint32_t *) & link->data, /* RED key address */ + &len); /* key length */ + sprintf(buf, " SCC slot %d: ", link->slot); + km_Dump_Words(buf, + (void *)IO_ADDRESS((uint32_t) + link->data), + link->len / 4); + } +#else + sprintf(Diag_msg, " SCC slot %d", link->slot); + LOG_KDIAG(Diag_msg); +#endif + } else if (link->data != NULL) { + km_Dump_Region(" Data", link->data, link->len); + } + + link = link->next; + } +} + +/*! + * Dump given region of data to the log. + * + * @brief Dump data + * + * @param prefix Text to put in front of dumped data + * @param data Kernel virtual address of start of region to dump + * @param length Amount of data to dump + * + * @return void +*/ +void km_Dump_Region(const char *prefix, const unsigned char *data, + unsigned length) +{ + unsigned count; + char *output; + unsigned data_len; + + sprintf(Diag_msg, "%s (%08X,%u):", prefix, (uint32_t) data, length); + + /* Restrict amount of data to dump */ + if (length > MAX_DUMP) { + data_len = MAX_DUMP; + } else { + data_len = length; + } + + /* We've already printed some text in output buffer, skip over it */ + output = Diag_msg + strlen(Diag_msg); + + for (count = 0; count < data_len; count++) { + if (count % 4 == 0) { + *output++ = ' '; + } + sprintf(output, "%02X", *data++); + output += 2; + } + + LOG_KDIAG(Diag_msg); +} + +/*! + * Dump given wors of data to the log. + * + * @brief Dump data + * + * @param prefix Text to put in front of dumped data + * @param data Kernel virtual address of start of region to dump + * @param word_count Amount of data to dump + * + * @return void +*/ +void km_Dump_Words(const char *prefix, const unsigned *data, + unsigned word_count) +{ + char *output; + + sprintf(Diag_msg, "%s (%08X,%uw): ", prefix, (uint32_t) data, + word_count); + + /* We've already printed some text in output buffer, skip over it */ + output = Diag_msg + strlen(Diag_msg); + + while (word_count--) { + sprintf(output, "%08X ", *data++); + output += 9; + } + + LOG_KDIAG(Diag_msg); +} +#endif diff --git a/drivers/mxc/security/sahara2/sah_driver_interface.c b/drivers/mxc/security/sahara2/sah_driver_interface.c new file mode 100644 index 000000000000..7029bc5f9c00 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_driver_interface.c @@ -0,0 +1,2179 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file sah_driver_interface.c +* +* @brief Provides a Linux Kernel Module interface to the SAHARA h/w device. +* +*/ + +/* SAHARA Includes */ +#include <sah_driver_common.h> +#include <sah_kernel.h> +#include <sah_memory_mapper.h> +#include <sah_queue_manager.h> +#include <sah_status_manager.h> +#include <sah_interrupt_handler.h> +#include <sah_hardware_interface.h> +#include <fsl_shw_keystore.h> +#include <adaptor.h> +#ifdef FSL_HAVE_SCC +#include <linux/mxc_scc_driver.h> +#else +#include <linux/mxc_scc2_driver.h> +#endif + +#ifdef DIAG_DRV_IF +#include <diagnostic.h> +#endif + +#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) +#include <linux/devfs_fs_kernel.h> +#else +#include <linux/proc_fs.h> +#endif + +#include <mach/spba.h> + +#ifdef PERF_TEST +#define interruptible_sleep_on(x) sah_Handle_Interrupt() +#endif + +#define TEST_MODE_OFF 1 +#define TEST_MODE_ON 2 + +/*! Version register on first deployments */ +#define SAHARA_VERSION2 2 +/*! Version register on MX27 */ +#define SAHARA_VERSION3 3 +/*! Version register on MXC92323 */ +#define SAHARA_VERSION4 4 + +/****************************************************************************** +* Module function declarations +******************************************************************************/ + +OS_DEV_INIT_DCL(sah_init); +OS_DEV_SHUTDOWN_DCL(sah_cleanup); +OS_DEV_OPEN_DCL(sah_open); +OS_DEV_CLOSE_DCL(sah_release); +OS_DEV_IOCTL_DCL(sah_ioctl); +OS_DEV_MMAP_DCL(sah_mmap); + +static os_error_code sah_handle_get_capabilities(fsl_shw_uco_t* user_ctx, + uint32_t info); + +static void sah_user_callback(fsl_shw_uco_t * user_ctx); +static os_error_code sah_handle_scc_sfree(fsl_shw_uco_t* user_ctx, + uint32_t info); +static os_error_code sah_handle_scc_sstatus(fsl_shw_uco_t* user_ctx, + uint32_t info); +static os_error_code sah_handle_scc_drop_perms(fsl_shw_uco_t* user_ctx, + uint32_t info); +static os_error_code sah_handle_scc_encrypt(fsl_shw_uco_t* user_ctx, + uint32_t info); +static os_error_code sah_handle_scc_decrypt(fsl_shw_uco_t* user_ctx, + uint32_t info); + +#ifdef FSL_HAVE_SCC2 +static fsl_shw_return_t register_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base, + void *kernel_base); +static fsl_shw_return_t deregister_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base); +#endif + +static os_error_code sah_handle_sk_slot_alloc(uint32_t info); +static os_error_code sah_handle_sk_slot_dealloc(uint32_t info); +static os_error_code sah_handle_sk_slot_load(uint32_t info); +static os_error_code sah_handle_sk_slot_read(uint32_t info); +static os_error_code sah_handle_sk_slot_decrypt(uint32_t info); +static os_error_code sah_handle_sk_slot_encrypt(uint32_t info); + +/*! Boolean flag for whether interrupt handler needs to be released on exit */ +static unsigned interrupt_registered; + +static int handle_sah_ioctl_dar(fsl_shw_uco_t * filp, uint32_t user_space_desc); + +#if !defined(CONFIG_DEVFS_FS) || (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int sah_read_procfs(char *buf, + char **start, + off_t offset, int count, int *eof, void *data); + +static int sah_write_procfs(struct file *file, const char __user * buffer, + unsigned long count, void *data); +#endif + +#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) + +/* This is a handle to the sahara DEVFS entry. */ +static devfs_handle_t Sahara_devfs_handle; + +#else + +/* Major number assigned to our device driver */ +static int Major; + +/* This is a handle to the sahara PROCFS entry */ +static struct proc_dir_entry *Sahara_procfs_handle; + +#endif + +uint32_t sah_hw_version; + +/* This is the wait queue to this driver. Linux declaration. */ +DECLARE_WAIT_QUEUE_HEAD(Wait_queue); + +/* This is a global variable that is used to track how many times the device + * has been opened simultaneously. */ +#ifdef DIAG_DRV_IF +static int Device_in_use = 0; +#endif + +/* This is the system keystore object */ +fsl_shw_kso_t system_keystore; + +/*! + * OS-dependent handle used for registering user interface of a driver. + */ +static os_driver_reg_t reg_handle; + +#ifdef DIAG_DRV_IF +/* This is for sprintf() to use when constructing output. */ +#define DIAG_MSG_SIZE 1024 +static char Diag_msg[DIAG_MSG_SIZE]; +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)) +/** Pointer to Sahara clock information. Initialized during os_dev_init(). */ +static struct clk *sah_clk; +#endif + +/*! +******************************************************************************* +* This function gets called when the module is inserted (insmod) into the +* running kernel. +* +* @brief SAHARA device initialisation function. +* +* @return 0 on success +* @return -EBUSY if the device or proc file entry cannot be created. +* @return OS_ERROR_NO_MEMORY_S if kernel memory could not be allocated. +* @return OS_ERROR_FAIL_S if initialisation of proc entry failed +*/ +OS_DEV_INIT(sah_init) +{ + /* Status variable */ + int os_error_code = 0; + + interrupt_registered = 0; + + /* Enable the SAHARA Clocks */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA : Enabling the IPG and AHB clocks\n") +#endif /*DIAG_DRV_IF */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + mxc_clks_enable(SAHARA2_CLK); +#else + { + sah_clk = clk_get(NULL, "sahara_clk"); + if (sah_clk != ERR_PTR(ENOENT)) + clk_enable(sah_clk); + } +#endif + + /* Check for SPBA need */ +#if defined(CONFIG_ARCH_MXC91231) || defined(CONFIG_ARCH_MXC91321) + /* This needs to be a PLATFORM abstraction */ + if (spba_take_ownership(SPBA_SAHARA, SPBA_MASTER_A)) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Sahara driver could not take ownership of Sahara\n"); +#endif + os_error_code = OS_ERROR_FAIL_S; + } +#endif /* SPBA */ + + if (os_error_code == OS_ERROR_OK_S) { + sah_hw_version = sah_HW_Read_Version(); + os_printk("Sahara HW Version is 0x%08x\n", sah_hw_version); + + /* verify code and hardware are version compatible */ + if ((sah_hw_version != SAHARA_VERSION2) + && (sah_hw_version != SAHARA_VERSION3)) { + if (((sah_hw_version >> 8) & 0xff) != SAHARA_VERSION4) { + os_printk + ("Sahara HW Version was not expected value.\n"); + os_error_code = OS_ERROR_FAIL_S; + } + } + } + + if (os_error_code == OS_ERROR_OK_S) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Calling sah_Init_Mem_Map to initialise " + "memory subsystem."); +#endif + /* Do any memory-routine initialization */ + os_error_code = sah_Init_Mem_Map(); + } + + if (os_error_code == OS_ERROR_OK_S) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Calling sah_HW_Reset() to Initialise the Hardware."); +#endif + /* Initialise the hardware */ + os_error_code = sah_HW_Reset(); + if (os_error_code != OS_ERROR_OK_S) { + os_printk + ("sah_HW_Reset() failed to Initialise the Hardware.\n"); + } + + } + + if (os_error_code == OS_ERROR_OK_S) { +#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) + /* Register the DEVFS entry */ + Sahara_devfs_handle = devfs_register(NULL, + SAHARA_DEVICE_SHORT, + DEVFS_FL_AUTO_DEVNUM, + 0, 0, + SAHARA_DEVICE_MODE, + &Fops, NULL); + if (Sahara_devfs_handle == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("Registering the DEVFS character device failed."); +#endif /* DIAG_DRV_IF */ + os_error_code = -EBUSY; + } +#else /* CONFIG_DEVFS_FS */ + /* Create the PROCFS entry. This is used to report the assigned device + * major number back to user-space. */ +#if 1 + Sahara_procfs_handle = create_proc_entry(SAHARA_DEVICE_SHORT, 0700, /* default mode */ + NULL); /* parent dir */ + if (Sahara_procfs_handle == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Registering the PROCFS interface failed."); +#endif /* DIAG_DRV_IF */ + os_error_code = OS_ERROR_FAIL_S; + } else { + Sahara_procfs_handle->nlink = 1; + Sahara_procfs_handle->data = 0; + Sahara_procfs_handle->read_proc = sah_read_procfs; + Sahara_procfs_handle->write_proc = sah_write_procfs; + } +#endif /* #if 1 */ + } + + if (os_error_code == OS_ERROR_OK_S) { +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("Calling sah_Queue_Manager_Init() to Initialise the Queue " + "Manager."); +#endif + /* Initialise the Queue Manager */ + if (sah_Queue_Manager_Init() != FSL_RETURN_OK_S) { + os_error_code = -ENOMEM; + } + } +#ifndef SAHARA_POLL_MODE + if (os_error_code == OS_ERROR_OK_S) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Calling sah_Intr_Init() to Initialise the Interrupt " + "Handler."); +#endif + /* Initialise the Interrupt Handler */ + os_error_code = sah_Intr_Init(&Wait_queue); + if (os_error_code == OS_ERROR_OK_S) { + interrupt_registered = 1; + } + } +#endif /* ifndef SAHARA_POLL_MODE */ + +#ifdef SAHARA_POWER_MANAGEMENT + if (os_error_code == OS_ERROR_OK_S) { + /* set up dynamic power management (dmp) */ + os_error_code = sah_dpm_init(); + } +#endif + + if (os_error_code == OS_ERROR_OK_S) { + os_driver_init_registration(reg_handle); + os_driver_add_registration(reg_handle, OS_FN_OPEN, + OS_DEV_OPEN_REF(sah_open)); + os_driver_add_registration(reg_handle, OS_FN_IOCTL, + OS_DEV_IOCTL_REF(sah_ioctl)); + os_driver_add_registration(reg_handle, OS_FN_CLOSE, + OS_DEV_CLOSE_REF(sah_release)); + os_driver_add_registration(reg_handle, OS_FN_MMAP, + OS_DEV_MMAP_REF(sah_mmap)); + + os_error_code = + os_driver_complete_registration(reg_handle, Major, + "sahara"); + + if (os_error_code < OS_ERROR_OK_S) { +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Registering the regular " + "character device failed with error code: %d\n", + os_error_code); + LOG_KDIAG(Diag_msg); +#endif + } + } +#endif /* CONFIG_DEVFS_FS */ + + if (os_error_code == OS_ERROR_OK_S) { + /* set up the system keystore, using the default keystore handler */ + fsl_shw_init_keystore_default(&system_keystore); + + if (fsl_shw_establish_keystore(NULL, &system_keystore) + == FSL_RETURN_OK_S) { + os_error_code = OS_ERROR_OK_S; + } else { + os_error_code = OS_ERROR_FAIL_S; + } + + if (os_error_code != OS_ERROR_OK_S) { +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Registering the system keystore " + "failed with error code: %d\n", os_error_code); + LOG_KDIAG(Diag_msg); +#endif + } + } + + if (os_error_code != OS_ERROR_OK_S) { +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) + cleanup_module(); +#else + sah_cleanup(); +#endif + } +#ifdef DIAG_DRV_IF + else { + LOG_KDIAG_ARGS("Sahara major node is %d\n", Major); + } +#endif + +/* Disabling the Clock after the driver has been registered fine. + This is done to save power when Sahara is not in use.*/ +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA : Disabling the clocks\n") +#endif /* DIAG_DRV_IF */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_disable(SAHARA2_CLK); +#else + { + if (sah_clk != ERR_PTR(ENOENT)) + clk_disable(sah_clk); + } +#endif + + os_dev_init_return(os_error_code); +} + +/*! +******************************************************************************* +* This function gets called when the module is removed (rmmod) from the running +* kernel. +* +* @brief SAHARA device clean-up function. +* +* @return void +*/ +OS_DEV_SHUTDOWN(sah_cleanup) +{ + int ret_val = 0; + + printk(KERN_ALERT "Sahara going into cleanup\n"); + + /* clear out the system keystore */ + fsl_shw_release_keystore(NULL, &system_keystore); + + /* Unregister the device */ +#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) + devfs_unregister(Sahara_devfs_handle); +#else + + if (Sahara_procfs_handle != NULL) { + remove_proc_entry(SAHARA_DEVICE_SHORT, NULL); + } + + if (Major >= 0) { + ret_val = os_driver_remove_registration(reg_handle); + } +#ifdef DIAG_DRV_IF + if (ret_val < 0) { + snprintf(Diag_msg, DIAG_MSG_SIZE, "Error while attempting to " + "unregister the device: %d\n", ret_val); + LOG_KDIAG(Diag_msg); + } +#endif + +#endif /* CONFIG_DEVFS_FS */ + sah_Queue_Manager_Close(); + +#ifndef SAHARA_POLL_MODE + if (interrupt_registered) { + sah_Intr_Release(); + interrupt_registered = 0; + } +#endif + sah_Stop_Mem_Map(); +#ifdef SAHARA_POWER_MANAGEMENT + sah_dpm_close(); +#endif + +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA : Disabling the clocks\n") +#endif /* DIAG_DRV_IF */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + mxc_clks_disable(SAHARA2_CLK); +#else + { + if (sah_clk != ERR_PTR(ENOENT)) + clk_disable(sah_clk); + clk_put(sah_clk); + } +#endif + + os_dev_shutdown_return(OS_ERROR_OK_S); +} + +/*! +******************************************************************************* +* This function simply increments the module usage count. +* +* @brief SAHARA device open function. +* +* @param inode Part of the kernel prototype. +* @param file Part of the kernel prototype. +* +* @return 0 - Always returns 0 since any number of calls to this function are +* allowed. +* +*/ +OS_DEV_OPEN(sah_open) +{ + +#if defined(LINUX_VERSION) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10)) + MOD_INC_USE_COUNT; +#endif + +#ifdef DIAG_DRV_IF + Device_in_use++; + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Incrementing module use count to: %d ", Device_in_use); + LOG_KDIAG(Diag_msg); +#endif + + os_dev_set_user_private(NULL); + + /* Return 0 to indicate success */ + os_dev_open_return(0); +} + +/*! +******************************************************************************* +* This function simply decrements the module usage count. +* +* @brief SAHARA device release function. +* +* @param inode Part of the kernel prototype. +* @param file Part of the kernel prototype. +* +* @return 0 - Always returns 0 since this function does not fail. +*/ +OS_DEV_CLOSE(sah_release) +{ + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + +#if defined(LINUX_VERSION) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10)) + MOD_DEC_USE_COUNT; +#endif + +#ifdef DIAG_DRV_IF + Device_in_use--; + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Decrementing module use count to: %d ", Device_in_use); + LOG_KDIAG(Diag_msg); +#endif + + if (user_ctx != NULL) { + sah_handle_deregistration(user_ctx); + os_free_memory(user_ctx); + os_dev_set_user_private(NULL); + } + + /* Return 0 to indicate success */ + os_dev_close_return(OS_ERROR_OK_S); +} + +/*! +******************************************************************************* +* This function provides the IO Controls for the SAHARA driver. Three IO +* Controls are supported: +* +* SAHARA_HWRESET and +* SAHARA_SET_HA +* SAHARA_CHK_TEST_MODE +* +* @brief SAHARA device IO Control function. +* +* @param inode Part of the kernel prototype. +* @param filp Part of the kernel prototype. +* @param cmd Part of the kernel prototype. +* @param arg Part of the kernel prototype. +* +* @return 0 on success +* @return -EBUSY if the HA bit could not be set due to busy hardware. +* @return -ENOTTY if an unsupported IOCTL was attempted on the device. +* @return -EFAULT if put_user() fails +*/ +OS_DEV_IOCTL(sah_ioctl) +{ + int status = 0; + int test_mode; + + switch (os_dev_get_ioctl_op()) { + case SAHARA_HWRESET: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_HWRESET IOCTL."); +#endif + /* We need to reset the hardware. */ + sah_HW_Reset(); + + /* Mark all the entries in the Queue Manager's queue with state + * SAH_STATE_RESET. + */ + sah_Queue_Manager_Reset_Entries(); + + /* Wake up all sleeping write() calls. */ + wake_up_interruptible(&Wait_queue); + break; +#ifdef SAHARA_HA_ENABLED + case SAHARA_SET_HA: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SET_HA IOCTL."); +#endif /* DIAG_DRV_IF */ + if (sah_HW_Set_HA() == ERR_INTERNAL) { + status = -EBUSY; + } + break; +#endif /* SAHARA_HA_ENABLED */ + + case SAHARA_CHK_TEST_MODE: + /* load test_mode */ + test_mode = TEST_MODE_OFF; +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_CHECK_TEST_MODE IOCTL."); + test_mode = TEST_MODE_ON; +#endif /* DIAG_DRV_IF */ +#if defined(KERNEL_TEST) || defined(PERF_TEST) + test_mode = TEST_MODE_ON; +#endif /* KERNEL_TEST || PERF_TEST */ + /* copy test_mode back to user space. put_user() is Linux fn */ + /* compiler warning `register': no problem found so ignored */ + status = put_user(test_mode, (int *)os_dev_get_ioctl_arg()); + break; + + case SAHARA_DAR: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_DAR IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + if (user_ctx != NULL) { + status = + handle_sah_ioctl_dar(user_ctx, + os_dev_get_ioctl_arg + ()); + } else { + status = OS_ERROR_FAIL_S; + } + + } + break; + + case SAHARA_GET_RESULTS: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_GET_RESULTS IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + if (user_ctx != NULL) { + status = + sah_get_results_pointers(user_ctx, + os_dev_get_ioctl_arg + ()); + } else { + status = OS_ERROR_FAIL_S; + } + } + break; + + case SAHARA_REGISTER: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_REGISTER IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + if (user_ctx != NULL) { + status = OS_ERROR_FAIL_S; /* already registered */ + } else { + user_ctx = + os_alloc_memory(sizeof(fsl_shw_uco_t), + GFP_KERNEL); + if (user_ctx == NULL) { + status = OS_ERROR_NO_MEMORY_S; + } else { + /* Copy UCO from user, but only as big as the common UCO */ + if (os_copy_from_user(user_ctx, + (void *) + os_dev_get_ioctl_arg + (), + offsetof + (fsl_shw_uco_t, + result_pool))) { + status = OS_ERROR_FAIL_S; + } else { + os_dev_set_user_private + (user_ctx); + status = + sah_handle_registration + (user_ctx); + } + } + } + } + break; + + /* This ioctl cmd should disappear in favor of a close() routine. */ + case SAHARA_DEREGISTER: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_DEREGISTER IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + if (user_ctx == NULL) { + status = OS_ERROR_FAIL_S; + } else { + status = sah_handle_deregistration(user_ctx); + os_free_memory(user_ctx); + os_dev_set_user_private(NULL); + } + } + break; + case SAHARA_SCC_DROP_PERMS: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SCC_DROP_PERMS IOCTL."); +#endif /* DIAG_DRV_IF */ + { + /* drop permissions on the specified partition */ + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + status = + sah_handle_scc_drop_perms(user_ctx, + os_dev_get_ioctl_arg()); + } + break; + + case SAHARA_SCC_SFREE: + /* Unmap the specified partition from the users space, and then + * free it for use by someone else. + */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SCC_SFREE IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + status = + sah_handle_scc_sfree(user_ctx, + os_dev_get_ioctl_arg()); + } + break; + + case SAHARA_SCC_SSTATUS: + /* Unmap the specified partition from the users space, and then + * free it for use by someone else. + */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SCC_SSTATUS IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + status = + sah_handle_scc_sstatus(user_ctx, + os_dev_get_ioctl_arg()); + } + break; + + case SAHARA_SCC_ENCRYPT: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SCC_ENCRYPT IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + status = + sah_handle_scc_encrypt(user_ctx, + os_dev_get_ioctl_arg()); + } + break; + + case SAHARA_SCC_DECRYPT: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SCC_DECRYPT IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + status = + sah_handle_scc_decrypt(user_ctx, + os_dev_get_ioctl_arg()); + } + break; + + case SAHARA_SK_ALLOC: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SK_ALLOC IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_sk_slot_alloc(os_dev_get_ioctl_arg()); + break; + + case SAHARA_SK_DEALLOC: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SK_DEALLOC IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_sk_slot_dealloc(os_dev_get_ioctl_arg()); + break; + + case SAHARA_SK_LOAD: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SK_LOAD IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_sk_slot_load(os_dev_get_ioctl_arg()); + break; + case SAHARA_SK_READ: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SK_READ IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_sk_slot_read(os_dev_get_ioctl_arg()); + break; + + case SAHARA_SK_SLOT_DEC: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SK_SLOT_DECRYPT IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_sk_slot_decrypt(os_dev_get_ioctl_arg()); + break; + + case SAHARA_SK_SLOT_ENC: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SK_SLOT_ENCRYPT IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_sk_slot_encrypt(os_dev_get_ioctl_arg()); + break; + case SAHARA_GET_CAPS: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_GET_CAPS IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + status = + sah_handle_get_capabilities(user_ctx, + os_dev_get_ioctl_arg()); + } + break; + + default: +#ifdef DIAG_DRV_IF + LOG_KDIAG("Unknown SAHARA IOCTL."); +#endif /* DIAG_DRV_IF */ + status = OS_ERROR_FAIL_S; + } + + os_dev_ioctl_return(status); +} + +/* Fill in the user's capabilities structure */ +static os_error_code sah_handle_get_capabilities(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_FAIL_S; + fsl_shw_pco_t capabilities; + + status = os_copy_from_user(&capabilities, (void *)info, + sizeof(fsl_shw_pco_t)); + + if (status != OS_ERROR_OK_S) { + goto out; + } + + if (get_capabilities(user_ctx, &capabilities) == FSL_RETURN_OK_S) { + status = os_copy_to_user((void *)info, &capabilities, + sizeof(fsl_shw_pco_t)); + } + + out: + return status; +} + +#ifdef FSL_HAVE_SCC2 + +/* Find the kernel-mode address of the partition. + * This can then be passed to the SCC functions. + */ +void *lookup_user_partition(fsl_shw_uco_t * user_ctx, uint32_t user_base) +{ + /* search through the partition chain to find one that matches the user base + * address. + */ + fsl_shw_spo_t *curr = (fsl_shw_spo_t *) user_ctx->partition; + + while (curr != NULL) { + if (curr->user_base == user_base) { + return curr->kernel_base; + } + curr = (fsl_shw_spo_t *) curr->next; + } + return NULL; +} + +/* user_base: userspace base address of the partition + * kernel_base: kernel mode base address of the partition + */ +static fsl_shw_return_t register_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base, + void *kernel_base) +{ + fsl_shw_spo_t *partition_info; + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + if (user_ctx == NULL) { + goto out; + } + + partition_info = os_alloc_memory(sizeof(fsl_shw_spo_t), GFP_KERNEL); + + if (partition_info == NULL) { + goto out; + } + + /* stuff the partition info, then put it at the front of the chain */ + partition_info->user_base = user_base; + partition_info->kernel_base = kernel_base; + partition_info->next = user_ctx->partition; + + user_ctx->partition = (struct fsl_shw_spo_t *)partition_info; + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS + ("partition with user_base=%p, kernel_base=%p registered.", + (void *)user_base, kernel_base); +#endif + + ret = FSL_RETURN_OK_S; + + out: + + return ret; +} + +/* if the partition is in the users list, remove it */ +static fsl_shw_return_t deregister_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base) +{ + fsl_shw_spo_t *curr = (fsl_shw_spo_t *) user_ctx->partition; + fsl_shw_spo_t *last = (fsl_shw_spo_t *) user_ctx->partition; + + while (curr != NULL) { + if (curr->user_base == user_base) { + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS + ("deregister_user_partition: partition with " + "user_base=%p, kernel_base=%p deregistered.\n", + (void *)curr->user_base, curr->kernel_base); +#endif + + if (last == curr) { + user_ctx->partition = curr->next; + os_free_memory(curr); + return FSL_RETURN_OK_S; + } else { + last->next = curr->next; + os_free_memory(curr); + return FSL_RETURN_OK_S; + } + } + last = curr; + curr = (fsl_shw_spo_t *) curr->next; + } + + return FSL_RETURN_ERROR_S; +} + +#endif /* FSL_HAVE_SCC2 */ + +static os_error_code sah_handle_scc_drop_perms(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; +#ifdef FSL_HAVE_SCC2 + scc_return_t scc_ret; + scc_partition_info_t partition_info; + void *kernel_base; + + status = + os_copy_from_user(&partition_info, (void *)info, + sizeof(partition_info)); + + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + kernel_base = lookup_user_partition(user_ctx, partition_info.user_base); + + if (kernel_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("_scc_drop_perms(): failed to find partition\n"); +#endif + goto out; + } + + /* call scc driver to perform the drop */ + scc_ret = scc_diminish_permissions(kernel_base, + partition_info.permissions); + if (scc_ret == SCC_RET_OK) { + status = OS_ERROR_OK_S; + } else { + status = OS_ERROR_FAIL_S; + } + + out: +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +static os_error_code sah_handle_scc_sfree(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; +#ifdef FSL_HAVE_SCC2 + { + scc_partition_info_t partition_info; + void *kernel_base; + int ret; + + status = + os_copy_from_user(&partition_info, (void *)info, + sizeof(partition_info)); + + /* check that the copy was successful */ + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + kernel_base = + lookup_user_partition(user_ctx, partition_info.user_base); + + if (kernel_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("failed to find partition\n"); +#endif /*DIAG_DRV_IF */ + goto out; + } + + /* Unmap the memory region (see sys_munmap in mmap.c) */ + ret = unmap_user_memory(partition_info.user_base, 8192); + + /* If the memory was successfully released */ + if (ret == OS_ERROR_OK_S) { + + /* release the partition */ + scc_release_partition(kernel_base); + + /* and remove it from the users context */ + deregister_user_partition(user_ctx, + partition_info.user_base); + + status = OS_ERROR_OK_S; + } + } + out: +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +static os_error_code sah_handle_scc_sstatus(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; +#ifdef FSL_HAVE_SCC2 + { + scc_partition_info_t partition_info; + void *kernel_base; + + status = + os_copy_from_user(&partition_info, (void *)info, + sizeof(partition_info)); + + /* check that the copy was successful */ + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + kernel_base = + lookup_user_partition(user_ctx, partition_info.user_base); + + if (kernel_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("failed to find partition\n"); +#endif /*DIAG_DRV_IF */ + goto out; + } + + partition_info.status = scc_partition_status(kernel_base); + + status = + os_copy_to_user((void *)info, &partition_info, + sizeof(partition_info)); + } + out: +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +static os_error_code sah_handle_scc_encrypt(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code os_err = OS_ERROR_FAIL_S; +#ifdef FSL_HAVE_SCC2 + { + fsl_shw_return_t retval; + scc_region_t region_info; + void *page_ctx = NULL; + void *black_addr = NULL; + void *partition_base = NULL; + scc_config_t *scc_configuration; + + os_err = + os_copy_from_user(®ion_info, (void *)info, + sizeof(region_info)); + + if (os_err != OS_ERROR_OK_S) { + goto out; + } +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS + ("partition_base: %p, offset: %i, length: %i, black data: %p", + (void *)region_info.partition_base, region_info.offset, + region_info.length, (void *)region_info.black_data); +#endif + + /* validate that the user owns this partition, and look up its handle */ + partition_base = lookup_user_partition(user_ctx, + region_info. + partition_base); + + if (partition_base == NULL) { + retval = FSL_RETURN_ERROR_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("failed to find secure partition\n"); +#endif + goto out; + } + + /* Check that the memory size requested is correct */ + scc_configuration = scc_get_configuration(); + if (region_info.offset + region_info.length > + scc_configuration->partition_size_bytes) { + retval = FSL_RETURN_ERROR_S; + goto out; + } + + /* wire down black data */ + black_addr = wire_user_memory(region_info.black_data, + region_info.length, &page_ctx); + + if (black_addr == NULL) { + retval = FSL_RETURN_ERROR_S; + goto out; + } + + retval = + do_scc_encrypt_region(NULL, partition_base, + region_info.offset, + region_info.length, black_addr, + region_info.IV, + region_info.cypher_mode); + + /* release black data */ + unwire_user_memory(&page_ctx); + + out: + if (os_err == OS_ERROR_OK_S) { + /* Return error code */ + region_info.code = retval; + os_err = + os_copy_to_user((void *)info, ®ion_info, + sizeof(region_info)); + } + } + +#endif + return os_err; +} + +static os_error_code sah_handle_scc_decrypt(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code os_err = OS_ERROR_FAIL_S; +#ifdef FSL_HAVE_SCC2 + { + fsl_shw_return_t retval; + scc_region_t region_info; + void *page_ctx = NULL; + void *black_addr; + void *partition_base; + scc_config_t *scc_configuration; + + os_err = + os_copy_from_user(®ion_info, (void *)info, + sizeof(region_info)); + + if (os_err != OS_ERROR_OK_S) { + goto out; + } +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS + ("partition_base: %p, offset: %i, length: %i, black data: %p", + (void *)region_info.partition_base, region_info.offset, + region_info.length, (void *)region_info.black_data); +#endif + + /* validate that the user owns this partition, and look up its handle */ + partition_base = lookup_user_partition(user_ctx, + region_info. + partition_base); + + if (partition_base == NULL) { + retval = FSL_RETURN_ERROR_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("failed to find partition\n"); +#endif + goto out; + } + + /* Check that the memory size requested is correct */ + scc_configuration = scc_get_configuration(); + if (region_info.offset + region_info.length > + scc_configuration->partition_size_bytes) { + retval = FSL_RETURN_ERROR_S; + goto out; + } + + /* wire down black data */ + black_addr = wire_user_memory(region_info.black_data, + region_info.length, &page_ctx); + + if (black_addr == NULL) { + retval = FSL_RETURN_ERROR_S; + goto out; + } + + retval = + do_scc_decrypt_region(NULL, partition_base, + region_info.offset, + region_info.length, black_addr, + region_info.IV, + region_info.cypher_mode); + + /* release black data */ + unwire_user_memory(&page_ctx); + + out: + if (os_err == OS_ERROR_OK_S) { + /* Return error code */ + region_info.code = retval; + os_err = + os_copy_to_user((void *)info, ®ion_info, + sizeof(region_info)); + } + } + +#endif /* FSL_HAVE_SCC2 */ + return os_err; +} + +/*****************************************************************************/ +/* fn get_user_smid() */ +/*****************************************************************************/ +uint32_t get_user_smid(void *proc) +{ + /* + * A real implementation would have some way to handle signed applications + * which wouild be assigned distinct SMIDs. For the reference + * implementation, we show where this would be determined (here), but + * always provide a fixed answer, thus not separating users at all. + */ + + return 0x42eaae42; +} + +/*! +******************************************************************************* +* This function implements the smalloc() function for userspace programs, by +* making a call to the SCC2 mmap() function that acquires a region of secure +* memory on behalf of the user, and then maps it into the users memory space. +* Currently, the only memory size supported is that of a single SCC2 partition. +* Requests for other sized memory regions will fail. +*/ +OS_DEV_MMAP(sah_mmap) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; + +#ifdef FSL_HAVE_SCC2 + { + scc_return_t scc_ret; + fsl_shw_return_t fsl_ret; + uint32_t partition_registered = FALSE; + + uint32_t user_base; + void *partition_base; + uint32_t smid; + scc_config_t *scc_configuration; + + int part_no = -1; + uint32_t part_phys; + + fsl_shw_uco_t *user_ctx = + (fsl_shw_uco_t *) os_dev_get_user_private(); + + /* Make sure that the user context is valid */ + if (user_ctx == NULL) { + user_ctx = + os_alloc_memory(sizeof(*user_ctx), GFP_KERNEL); + + if (user_ctx == NULL) { + status = OS_ERROR_NO_MEMORY_S; + goto out; + } + + sah_handle_registration(user_ctx); + os_dev_set_user_private(user_ctx); + } + + /* Determine the size of a secure partition */ + scc_configuration = scc_get_configuration(); + + /* Check that the memory size requested is equal to the partition + * size, and that the requested destination is on a page boundary. + */ + if (((os_mmap_user_base() % PAGE_SIZE) != 0) || + (os_mmap_memory_size() != + scc_configuration->partition_size_bytes)) { + status = OS_ERROR_BAD_ARG_S; + goto out; + } + + /* Retrieve the SMID associated with the user */ + smid = get_user_smid(user_ctx->process); + + /* Attempt to allocate a secure partition */ + scc_ret = + scc_allocate_partition(smid, &part_no, &partition_base, + &part_phys); + if (scc_ret != SCC_RET_OK) { + pr_debug + ("SCC mmap() request failed to allocate partition;" + " error %d\n", status); + status = OS_ERROR_FAIL_S; + goto out; + } + + pr_debug("scc_mmap() acquired partition %d at %08x\n", + part_no, part_phys); + + /* Record partition info in the user context */ + user_base = os_mmap_user_base(); + fsl_ret = + register_user_partition(user_ctx, user_base, + partition_base); + + if (fsl_ret != FSL_RETURN_OK_S) { + pr_debug + ("SCC mmap() request failed to register partition with user" + " context, error: %d\n", fsl_ret); + status = OS_ERROR_FAIL_S; + } + + partition_registered = TRUE; + + status = map_user_memory(os_mmap_memory_ctx(), part_phys, + os_mmap_memory_size()); + +#ifdef SHW_DEBUG + if (status == OS_ERROR_OK_S) { + LOG_KDIAG_ARGS + ("Partition allocated: user_base=%p, partition_base=%p.", + (void *)user_base, partition_base); + } +#endif + + out: + /* If there is an error it has to be handled here */ + if (status != OS_ERROR_OK_S) { + /* if the partition was registered with the user, unregister it. */ + if (partition_registered == TRUE) { + deregister_user_partition(user_ctx, user_base); + } + + /* if the partition was allocated, deallocate it */ + if (partition_base != NULL) { + scc_release_partition(partition_base); + } + } + } +#endif /* FSL_HAVE_SCC2 */ + + return status; +} + +/* Find the physical address of a key stored in the system keystore */ +fsl_shw_return_t +system_keystore_get_slot_info(uint64_t owner_id, uint32_t slot, + uint32_t * address, uint32_t * slot_size_bytes) +{ + fsl_shw_return_t retval; + void *kernel_address; + + /* First verify that the key access is valid */ + retval = system_keystore.slot_verify_access(system_keystore.user_data, + owner_id, slot); + + if (retval != FSL_RETURN_OK_S) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("verification failed"); +#endif + return retval; + } + + if (address != NULL) { +#ifdef FSL_HAVE_SCC2 + kernel_address = + system_keystore.slot_get_address(system_keystore.user_data, + slot); + (*address) = scc_virt_to_phys(kernel_address); +#else + kernel_address = + system_keystore.slot_get_address((void *)&owner_id, slot); + (*address) = (uint32_t) kernel_address; +#endif + } + + if (slot_size_bytes != NULL) { +#ifdef FSL_HAVE_SCC2 + *slot_size_bytes = + system_keystore.slot_get_slot_size(system_keystore. + user_data, slot); +#else + *slot_size_bytes = + system_keystore.slot_get_slot_size((void *)&owner_id, slot); +#endif + } + + return retval; +} + +static os_error_code sah_handle_sk_slot_alloc(uint32_t info) +{ + scc_slot_t slot_info; + os_error_code os_err; + scc_return_t scc_ret; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + if (os_err == OS_ERROR_OK_S) { + scc_ret = keystore_slot_alloc(&system_keystore, + slot_info.key_length, + slot_info.ownerid, + &slot_info.slot); + if (scc_ret == SCC_RET_OK) { + slot_info.code = FSL_RETURN_OK_S; + } else if (scc_ret == SCC_RET_INSUFFICIENT_SPACE) { + slot_info.code = FSL_RETURN_NO_RESOURCE_S; + } else { + slot_info.code = FSL_RETURN_ERROR_S; + } + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS("key length: %i, handle: %i\n", + slot_info.key_length, slot_info.slot); +#endif + + /* Return error code and slot info */ + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + + if (os_err != OS_ERROR_OK_S) { + (void)keystore_slot_dealloc(&system_keystore, + slot_info.ownerid, + slot_info.slot); + } + } + + return os_err; +} + +static os_error_code sah_handle_sk_slot_dealloc(uint32_t info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + scc_slot_t slot_info; + os_error_code os_err; + scc_return_t scc_ret; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + + if (os_err == OS_ERROR_OK_S) { + scc_ret = keystore_slot_dealloc(&system_keystore, + slot_info.ownerid, + slot_info.slot); + + if (scc_ret == SCC_RET_OK) { + ret = FSL_RETURN_OK_S; + } else { + ret = FSL_RETURN_ERROR_S; + } + slot_info.code = ret; + + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + } + + return os_err; +} + +static os_error_code sah_handle_sk_slot_load(uint32_t info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + scc_slot_t slot_info; + os_error_code os_err; + uint8_t *key = NULL; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + + if (os_err == OS_ERROR_OK_S) { + /* Allow slop in alloc in case we are rounding up to word multiple */ + key = os_alloc_memory(slot_info.key_length + 3, GFP_KERNEL); + if (key == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + os_err = OS_ERROR_NO_MEMORY_S; + } else { + os_err = os_copy_from_user(key, slot_info.key, + slot_info.key_length); + } + } + + if (os_err == OS_ERROR_OK_S) { + unsigned key_length = slot_info.key_length; + + /* Round up if necessary, as SCC call wants a multiple of 32-bit + * values for the full object being loaded. */ + if ((key_length & 3) != 0) { + key_length += 4 - (key_length & 3); + } + ret = keystore_slot_load(&system_keystore, + slot_info.ownerid, slot_info.slot, key, + key_length); + + slot_info.code = ret; + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + } + + if (key != NULL) { + memset(key, 0, slot_info.key_length); + os_free_memory(key); + } + + return os_err; +} + +static os_error_code sah_handle_sk_slot_read(uint32_t info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + scc_slot_t slot_info; + os_error_code os_err; + uint8_t *key = NULL; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + + if (os_err == OS_ERROR_OK_S) { + + /* This operation is not allowed for user keys */ + slot_info.code = FSL_RETURN_NO_RESOURCE_S; + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + + return os_err; + } + + if (os_err == OS_ERROR_OK_S) { + /* Allow slop in alloc in case we are rounding up to word multiple */ + key = os_alloc_memory(slot_info.key_length + 3, GFP_KERNEL); + if (key == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + os_err = OS_ERROR_NO_MEMORY_S; + } + } + + if (os_err == OS_ERROR_OK_S) { + unsigned key_length = slot_info.key_length; + + /* @bug Do some PERMISSIONS checking - make sure this is SW key */ + + /* Round up if necessary, as SCC call wants a multiple of 32-bit + * values for the full object being loaded. */ + if ((key_length & 3) != 0) { + key_length += 4 - (key_length & 3); + } + ret = keystore_slot_read(&system_keystore, + slot_info.ownerid, slot_info.slot, + key_length, key); + + /* @bug do some error checking */ + + /* Send key back to user */ + os_err = os_copy_to_user(slot_info.key, key, + slot_info.key_length); + + slot_info.code = ret; + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + } + + if (key != NULL) { + memset(key, 0, slot_info.key_length); + os_free_memory(key); + } + + return os_err; +} + +static os_error_code sah_handle_sk_slot_encrypt(uint32_t info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + scc_slot_t slot_info; + os_error_code os_err; + scc_return_t scc_ret; + uint8_t *key = NULL; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + + if (os_err == OS_ERROR_OK_S) { + key = os_alloc_memory(slot_info.key_length, GFP_KERNEL); + if (key == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + } + } + + if (key != NULL) { + + scc_ret = keystore_slot_encrypt(NULL, &system_keystore, + slot_info.ownerid, + slot_info.slot, + slot_info.key_length, key); + + if (scc_ret != SCC_RET_OK) { + ret = FSL_RETURN_ERROR_S; + } else { + os_err = + os_copy_to_user(slot_info.key, key, + slot_info.key_length); + if (os_err != OS_ERROR_OK_S) { + ret = FSL_RETURN_INTERNAL_ERROR_S; + } else { + ret = FSL_RETURN_OK_S; + } + } + + slot_info.code = ret; + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + + memset(key, 0, slot_info.key_length); + os_free_memory(key); + } + + return os_err; +} + +static os_error_code sah_handle_sk_slot_decrypt(uint32_t info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + scc_slot_t slot_info; /*!< decrypt request fields */ + os_error_code os_err; + scc_return_t scc_ret; + uint8_t *key = NULL; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + + if (os_err == OS_ERROR_OK_S) { + key = os_alloc_memory(slot_info.key_length, GFP_KERNEL); + if (key == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + os_err = OS_ERROR_OK_S; + } else { + os_err = os_copy_from_user(key, slot_info.key, + slot_info.key_length); + } + } + + if (os_err == OS_ERROR_OK_S) { + scc_ret = keystore_slot_decrypt(NULL, &system_keystore, + slot_info.ownerid, + slot_info.slot, + slot_info.key_length, key); + if (scc_ret == SCC_RET_OK) { + ret = FSL_RETURN_OK_S; + } else { + ret = FSL_RETURN_ERROR_S; + } + + slot_info.code = ret; + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + } + + if (key != NULL) { + memset(key, 0, slot_info.key_length); + os_free_memory(key); + } + + return os_err; +} + +/*! + * Register a user + * + * @brief Register a user + * + * @param user_ctx information about this user + * + * @return status code + */ +fsl_shw_return_t sah_handle_registration(fsl_shw_uco_t * user_ctx) +{ + /* Initialize the user's result pool (like sah_Queue_Construct() */ + user_ctx->result_pool.head = NULL; + user_ctx->result_pool.tail = NULL; + user_ctx->result_pool.count = 0; + + /* initialize the user's partition chain */ + user_ctx->partition = NULL; + + return FSL_RETURN_OK_S; +} + +/*! + * Deregister a user + * + * @brief Deregister a user + * + * @param user_ctx information about this user + * + * @return status code + */ +fsl_shw_return_t sah_handle_deregistration(fsl_shw_uco_t * user_ctx) +{ + /* NOTE: + * This will release any secure partitions that are held by the user. + * Encryption keys that were placed in the system keystore by the user + * should not be removed here, because they might have been shared with + * another process. The user must be careful to release any that are no + * longer in use. + */ + fsl_shw_return_t ret = FSL_RETURN_OK_S; + +#ifdef FSL_HAVE_SCC2 + fsl_shw_spo_t *partition; + struct mm_struct *mm = current->mm; + + while ((user_ctx->partition != NULL) && (ret == FSL_RETURN_OK_S)) { + + partition = user_ctx->partition; + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS + ("Found an abandoned secure partition at %p, releasing", + partition); +#endif + + /* It appears that current->mm is not valid if this is called from a + * close routine (perhaps only if the program raised an exception that + * caused it to close?) If that is the case, then still free the + * partition, but do not remove it from the memory space (dangerous?) + */ + + if (mm == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("Warning: no mm structure found, not unmapping " + "partition from user memory\n"); +#endif + } else { + /* Unmap the memory region (see sys_munmap in mmap.c) */ + /* Note that this assumes a single memory partition */ + unmap_user_memory(partition->user_base, 8192); + } + + /* If the memory was successfully released */ + if (ret == OS_ERROR_OK_S) { + /* release the partition */ + scc_release_partition(partition->kernel_base); + + /* and remove it from the users context */ + deregister_user_partition(user_ctx, + partition->user_base); + + ret = FSL_RETURN_OK_S; + } else { + ret = FSL_RETURN_ERROR_S; + + goto out; + } + } + out: +#endif /* FSL_HAVE_SCC2 */ + + return ret; +} + +/*! + * Sets up memory to extract results from results pool + * + * @brief Sets up memory to extract results from results pool + * + * @param user_ctx information about this user + * @param[in,out] arg contains input parameters and fields that the driver + * fills in + * + * @return os error code or 0 on success + */ +int sah_get_results_pointers(fsl_shw_uco_t * user_ctx, uint32_t arg) +{ + sah_results results_arg; /* kernel mode usable version of 'arg' */ + fsl_shw_result_t *user_results; /* user mode address of results */ + unsigned *user_actual; /* user mode address of actual number of results */ + unsigned actual; /* local memory of actual number of results */ + int ret_val = OS_ERROR_FAIL_S; + sah_Head_Desc *finished_request; + unsigned int loop; + + /* copy structure from user to kernel space */ + if (!os_copy_from_user(&results_arg, (void *)arg, sizeof(sah_results))) { + /* save user space pointers */ + user_actual = results_arg.actual; /* where count goes */ + user_results = results_arg.results; /* where results goe */ + + /* Set pointer for actual value to temporary kernel memory location */ + results_arg.actual = &actual; + + /* Allocate kernel memory to hold temporary copy of the results */ + results_arg.results = + os_alloc_memory(sizeof(fsl_shw_result_t) * + results_arg.requested, GFP_KERNEL); + + /* if memory allocated, continue */ + if (results_arg.results == NULL) { + ret_val = OS_ERROR_NO_MEMORY_S; + } else { + fsl_shw_return_t get_status; + + /* get the results */ + get_status = + sah_get_results_from_pool(user_ctx, &results_arg); + + /* free the copy of the user space descriptor chain */ + for (loop = 0; loop < actual; ++loop) { + /* get sah_Head_Desc from results and put user address into + * the return structure */ + finished_request = + results_arg.results[loop].user_desc; + results_arg.results[loop].user_desc = + finished_request->user_desc; + /* return the descriptor chain memory to the block free pool */ + sah_Free_Chained_Descriptors(finished_request); + } + + /* if no errors, copy results and then the actual number of results + * back to user space + */ + if (get_status == FSL_RETURN_OK_S) { + if (os_copy_to_user + (user_results, results_arg.results, + actual * sizeof(fsl_shw_result_t)) + || os_copy_to_user(user_actual, &actual, + sizeof(user_actual))) { + ret_val = OS_ERROR_FAIL_S; + } else { + ret_val = 0; /* no error */ + } + } + /* free the allocated memory */ + os_free_memory(results_arg.results); + } + } + + return ret_val; +} + +/*! + * Extracts results from results pool + * + * @brief Extract results from results pool + * + * @param user_ctx information about this user + * @param[in,out] arg contains input parameters and fields that the + * driver fills in + * + * @return status code + */ +fsl_shw_return_t sah_get_results_from_pool(volatile fsl_shw_uco_t * user_ctx, + sah_results * arg) +{ + sah_Head_Desc *finished_request; + unsigned int loop = 0; + os_lock_context_t int_flags; + + /* Get the number of results requested, up to total number of results + * available + */ + do { + /* Protect state of user's result pool until we have retrieved and + * remove the first entry, or determined that the pool is empty. */ + os_lock_save_context(desc_queue_lock, int_flags); + finished_request = user_ctx->result_pool.head; + + if (finished_request != NULL) { + sah_Queue_Remove_Entry((sah_Queue *) & user_ctx-> + result_pool); + os_unlock_restore_context(desc_queue_lock, int_flags); + + /* Prepare to free. */ + (void)sah_DePhysicalise_Descriptors(finished_request); + + arg->results[loop].user_ref = + finished_request->user_ref; + arg->results[loop].code = finished_request->result; + arg->results[loop].detail1 = + finished_request->fault_address; + arg->results[loop].detail2 = 0; + arg->results[loop].user_desc = finished_request; + + loop++; + } else { /* finished_request is NULL */ + /* pool is empty */ + os_unlock_restore_context(desc_queue_lock, int_flags); + } + + } while ((loop < arg->requested) && (finished_request != NULL)); + + /* record number of results actually obtained */ + *arg->actual = loop; + + return FSL_RETURN_OK_S; +} + +/*! + * Converts descriptor chain to kernel space (from user space) and submits + * chain to Sahara for processing + * + * @brief Submits converted descriptor chain to sahara + * + * @param user_ctx Pointer to Kernel version of user's ctx + * @param user_space_desc user space address of descriptor chain that is + * in user space + * + * @return OS status code + */ +static int handle_sah_ioctl_dar(fsl_shw_uco_t * user_ctx, + uint32_t user_space_desc) +{ + int os_error_code = OS_ERROR_FAIL_S; + sah_Head_Desc *desc_chain_head; /* chain in kernel - virtual address */ + + /* This will re-create the linked list so that the SAHARA hardware can + * DMA on it. + */ + desc_chain_head = sah_Copy_Descriptors(user_ctx, + (sah_Head_Desc *) + user_space_desc); + + if (desc_chain_head == NULL) { + /* We may have failed due to a -EFAULT as well, but we will return + * OS_ERROR_NO_MEMORY_S since either way it is a memory related + * failure. + */ + os_error_code = OS_ERROR_NO_MEMORY_S; + } else { + fsl_shw_return_t stat; + + desc_chain_head->user_info = user_ctx; + desc_chain_head->user_desc = (sah_Head_Desc *) user_space_desc; + + if (desc_chain_head->uco_flags & FSL_UCO_BLOCKING_MODE) { +#ifdef SAHARA_POLL_MODE + sah_Handle_Poll(desc_chain_head); +#else + sah_blocking_mode(desc_chain_head); +#endif + stat = desc_chain_head->result; + /* return the descriptor chain memory to the block free pool */ + sah_Free_Chained_Descriptors(desc_chain_head); + /* Tell user how the call turned out */ + + /* Copy 'result' back up to the result member. + * + * The dereference of the different member will cause correct the + * arithmetic to occur on the user-space address because of the + * missing dma/bus locations in the user mode version of the + * sah_Desc structure. */ + os_error_code = + os_copy_to_user((void *)(user_space_desc + + offsetof(sah_Head_Desc, + uco_flags)), + &stat, sizeof(fsl_shw_return_t)); + + } else { /* not blocking mode - queue and forget */ + + if (desc_chain_head->uco_flags & FSL_UCO_CALLBACK_MODE) { + user_ctx->process = os_get_process_handle(); + user_ctx->callback = sah_user_callback; + } +#ifdef SAHARA_POLL_MODE + /* will put results in result pool */ + sah_Handle_Poll(desc_chain_head); +#else + /* just put someting in the DAR */ + sah_Queue_Manager_Append_Entry(desc_chain_head); +#endif + /* assume all went well */ + os_error_code = OS_ERROR_OK_S; + } + } + + return os_error_code; +} + +static void sah_user_callback(fsl_shw_uco_t * user_ctx) +{ + os_send_signal(user_ctx->process, SIGUSR2); +} + +/*! + * This function is called when a thread attempts to read from the /proc/sahara + * file. Upon read, statistics and information about the state of the driver + * are returned in nthe supplied buffer. + * + * @brief SAHARA PROCFS read function. + * + * @param buf Anything written to this buffer will be returned to the + * user-space process that is reading from this proc entry. + * @param start Part of the kernel prototype. + * @param offset Part of the kernel prototype. + * @param count The size of the buf argument. + * @param eof An integer which is set to one to tell the user-space + * process that there is no more data to read. + * @param data Part of the kernel prototype. + * + * @return The number of bytes written to the proc entry. + */ +#if !defined(CONFIG_DEVFS_FS) || (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int sah_read_procfs(char *buf, + char **start, + off_t offset, int count, int *eof, void *data) +{ + int output_bytes = 0; + int in_queue_count = 0; + os_lock_context_t lock_context; + + os_lock_save_context(desc_queue_lock, lock_context); + in_queue_count = sah_Queue_Manager_Count_Entries(TRUE, 0); + os_unlock_restore_context(desc_queue_lock, lock_context); + output_bytes += snprintf(buf, count - output_bytes, "queued: %d\n", + in_queue_count); + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "Descriptors: %d, " + "Interrupts %d (%d Done1Done2, %d Done1Busy2, " + " %d Done1)\n", + dar_count, interrupt_count, done1done2_count, + done1busy2_count, done1_count); + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "Control: %08x\n", sah_HW_Read_Control()); +#if !defined(FSL_HAVE_SAHARA4) || defined(SAHARA4_NO_USE_SQUIB) + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "IDAR: %08x; CDAR: %08x\n", + sah_HW_Read_IDAR(), sah_HW_Read_CDAR()); +#endif +#ifdef DIAG_DRV_STATUS + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "Status: %08x; Error Status: %08x; Op Status: %08x\n", + sah_HW_Read_Status(), + sah_HW_Read_Error_Status(), + sah_HW_Read_Op_Status()); +#endif +#ifdef FSL_HAVE_SAHARA4 + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "MMStat: %08x; Config: %08x\n", + sah_HW_Read_MM_Status(), sah_HW_Read_Config()); +#endif + + /* Signal the end of the file */ + *eof = 1; + + /* To get rid of the unused parameter warnings */ + (void)start; + (void)data; + (void)offset; + + return output_bytes; +} + +static int sah_write_procfs(struct file *file, const char __user * buffer, + unsigned long count, void *data) +{ + + /* Any write to this file will reset all counts. */ + dar_count = interrupt_count = done1done2_count = + done1busy2_count = done1_count = 0; + + (void)file; + (void)buffer; + (void)data; + + return count; +} + +#endif + +#ifndef SAHARA_POLL_MODE +/*! + * Block user call until processing is complete. + * + * @param entry The user's request. + * + * @return An OS error code, or 0 if no error + */ +int sah_blocking_mode(sah_Head_Desc * entry) +{ + int os_error_code = 0; + sah_Queue_Status status; + + /* queue entry, put something in the DAR, if nothing is there currently */ + sah_Queue_Manager_Append_Entry(entry); + + /* get this descriptor chain's current status */ + status = ((volatile sah_Head_Desc *)entry)->status; + + while (!SAH_DESC_PROCESSED(status)) { + extern sah_Queue *main_queue; + + DEFINE_WAIT(sahara_wait); /* create a wait queue entry. Linux */ + + /* enter the wait queue entry into the queue */ + prepare_to_wait(&Wait_queue, &sahara_wait, TASK_INTERRUPTIBLE); + + /* check if this entry has been processed */ + status = ((volatile sah_Head_Desc *)entry)->status; + + if (!SAH_DESC_PROCESSED(status)) { + /* go to sleep - Linux */ + schedule(); + } + + /* un-queue the 'prepare to wait' queue? - Linux */ + finish_wait(&Wait_queue, &sahara_wait); + + /* signal belongs to this thread? */ + if (signal_pending(current)) { /* Linux */ + os_lock_context_t lock_flags; + + /* don't allow access during this check and operation */ + os_lock_save_context(desc_queue_lock, lock_flags); + status = ((volatile sah_Head_Desc *)entry)->status; + if (status == SAH_STATE_PENDING) { + sah_Queue_Remove_Any_Entry(main_queue, entry); + entry->result = FSL_RETURN_INTERNAL_ERROR_S; + ((volatile sah_Head_Desc *)entry)->status = + SAH_STATE_FAILED; + } + os_unlock_restore_context(desc_queue_lock, lock_flags); + } + + status = ((volatile sah_Head_Desc *)entry)->status; + } /* while ... */ + + /* Do this so that caller can free */ + (void)sah_DePhysicalise_Descriptors(entry); + + return os_error_code; +} + +/*! + * If interrupt does not return in a reasonable time, time out, trigger + * interrupt, and continue with process + * + * @param data ignored + */ +void sahara_timeout_handler(unsigned long data) +{ + /* Sahara has not issuing an interrupt, so timed out */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("Sahara HW did not respond. Resetting.\n"); +#endif + /* assume hardware needs resetting */ + sah_Handle_Interrupt(SAH_EXEC_FAULT); + /* wake up sleeping thread to try again */ + wake_up_interruptible(&Wait_queue); +} + +#endif /* ifndef SAHARA_POLL_MODE */ + +/* End of sah_driver_interface.c */ diff --git a/drivers/mxc/security/sahara2/sah_hardware_interface.c b/drivers/mxc/security/sahara2/sah_hardware_interface.c new file mode 100644 index 000000000000..a83ecdd5b805 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_hardware_interface.c @@ -0,0 +1,854 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file sah_hardware_interface.c + * + * @brief Provides an interface to the SAHARA hardware registers. + * + */ + +/* SAHARA Includes */ +#include <sah_driver_common.h> +#include <sah_hardware_interface.h> +#include <sah_memory_mapper.h> +#include <sah_kernel.h> + +#if defined DIAG_DRV_IF || defined(DO_DBG) +#include <diagnostic.h> +#ifndef LOG_KDIAG +#define LOG_KDIAG(x) os_printk("%s\n", x) +#endif + +static void sah_Dump_Link(const char *prefix, const sah_Link * link, + dma_addr_t addr); + +/* This is for sprintf() to use when constructing output. */ +#define DIAG_MSG_SIZE 1024 +/* was 200 */ +#define MAX_DUMP 200 +static char Diag_msg[DIAG_MSG_SIZE]; + +#endif /* DIAG_DRV_IF */ + +/*! + * Number of descriptors sent to Sahara. This value should only be updated + * with the main queue lock held. + */ +uint32_t dar_count; + +/*! The "link-list optimize" bit in the Header of a Descriptor */ +#define SAH_HDR_LLO 0x01000000 + +/* IO_ADDRESS() is Linux macro -- need portable equivalent */ +#define SAHARA_BASE_ADDRESS IO_ADDRESS(SAHA_BASE_ADDR) +#define SAHARA_VERSION_REGISTER_OFFSET 0x000 +#define SAHARA_DAR_REGISTER_OFFSET 0x004 +#define SAHARA_CONTROL_REGISTER_OFFSET 0x008 +#define SAHARA_COMMAND_REGISTER_OFFSET 0x00C +#define SAHARA_STATUS_REGISTER_OFFSET 0x010 +#define SAHARA_ESTATUS_REGISTER_OFFSET 0x014 +#define SAHARA_FLT_ADD_REGISTER_OFFSET 0x018 +#define SAHARA_CDAR_REGISTER_OFFSET 0x01C +#define SAHARA_IDAR_REGISTER_OFFSET 0x020 +#define SAHARA_OSTATUS_REGISTER_OFFSET 0x028 +#define SAHARA_CONFIG_REGISTER_OFFSET 0x02C +#define SAHARA_MM_STAT_REGISTER_OFFSET 0x030 + +/*! Register within Sahara which contains hardware version. (1 or 2). */ +#define SAHARA_VERSION_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_VERSION_REGISTER_OFFSET) + +/*! Register within Sahara which is used to provide new work to the block. */ +#define SAHARA_DAR_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_DAR_REGISTER_OFFSET) + +/*! Register with Sahara which is used for configuration. */ +#define SAHARA_CONTROL_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_CONTROL_REGISTER_OFFSET) + +/*! Register with Sahara which is used for changing status. */ +#define SAHARA_COMMAND_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_COMMAND_REGISTER_OFFSET) + +/*! Register with Sahara which is contains status and state. */ +#define SAHARA_STATUS_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_STATUS_REGISTER_OFFSET) + +/*! Register with Sahara which is contains error status information. */ +#define SAHARA_ESTATUS_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_ESTATUS_REGISTER_OFFSET) + +/*! Register with Sahara which is contains faulting address information. */ +#define SAHARA_FLT_ADD_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_FLT_ADD_REGISTER_OFFSET) + +/*! Register with Sahara which is contains current descriptor address. */ +#define SAHARA_CDAR_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_CDAR_REGISTER_OFFSET) + +/*! Register with Sahara which is contains initial descriptor address (of a + chain). */ +#define SAHARA_IDAR_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_IDAR_REGISTER_OFFSET) + +/*! Register with Sahara which is contains op status information. */ +#define SAHARA_OSTATUS_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_OSTATUS_REGISTER_OFFSET) + +/*! Register with Sahara which is contains configuration information. */ +#define SAHARA_CONFIG_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_CONFIG_REGISTER_OFFSET) + +/*! Register with Sahara which is contains configuration information. */ +#define SAHARA_MM_STAT_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_MM_STAT_REGISTER_OFFSET) + +/* Local Functions */ +#if defined DIAG_DRV_IF || defined DO_DBG +void sah_Dump_Region(const char *prefix, const unsigned char *data, + dma_addr_t addr, unsigned length); + +#endif /* DIAG_DRV_IF */ + +/* time out value when polling SAHARA status register for completion */ +static uint32_t sah_poll_timeout = 0xFFFFFFFF; + +/*! + * Polls Sahara to determine when its current operation is complete + * + * @return last value found in Sahara's status register + */ +sah_Execute_Status sah_Wait_On_Sahara() +{ + uint32_t count = 0; /* ensure we don't get stuck in the loop forever */ + sah_Execute_Status status; /* Sahara's status register */ + uint32_t stat_reg; + + pr_debug("Entered sah_Wait_On_Sahara\n"); + + do { + /* get current status register from Sahara */ + stat_reg = sah_HW_Read_Status(); + status = stat_reg & SAH_EXEC_STATE_MASK; + + /* timeout if SAHARA takes too long to complete */ + if (++count == sah_poll_timeout) { + status = SAH_EXEC_FAULT; + printk("sah_Wait_On_Sahara timed out\n"); + } + + /* stay in loop as long as Sahara is still busy */ + } while ((status == SAH_EXEC_BUSY) || (status == SAH_EXEC_DONE1_BUSY2)); + + if (status == SAH_EXEC_ERROR1) { + if (stat_reg & OP_STATUS) { + status = SAH_EXEC_OPSTAT1; + } + } + + return status; +} /* sah_Wait_on_Sahara() */ + +/*! + * This function resets the SAHARA hardware. The following operations are + * performed: + * 1. Resets SAHARA. + * 2. Requests BATCH mode. + * 3. Enables interrupts. + * 4. Requests Little Endian mode. + * + * @brief SAHARA hardware reset function. + * + * @return void + */ +int sah_HW_Reset(void) +{ + sah_Execute_Status sah_state; + int status; /* this is the value to return to the calling routine */ + uint32_t saha_control = 0; + +#ifndef USE_3WORD_BURST +#ifdef FSL_HAVE_SAHARA2 + saha_control |= (8 << 16); /* Allow 8-word burst */ +#endif +#else +/***************** HARDWARE BUG WORK AROUND ******************/ +/* A burst size of > 4 can cause Sahara DMA to issue invalid AHB transactions + * when crossing 1KB boundaries. By limiting the 'burst size' to 3, these + * invalid transactions will not be generated, but Sahara will still transfer + * data more efficiently than if the burst size were set to 1. + */ + saha_control |= (3 << 16); /* Limit DMA burst size. For versions 2/3 */ +#endif /* USE_3WORD_BURST */ + +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Address of SAHARA_BASE_ADDRESS = 0x%08x\n", + SAHARA_BASE_ADDRESS); + LOG_KDIAG(Diag_msg); + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Sahara Status register before reset: %08x", + sah_HW_Read_Status()); + LOG_KDIAG(Diag_msg); +#endif + + /* Write the Reset & BATCH mode command to the SAHARA Command register. */ + sah_HW_Write_Command(CMD_BATCH | CMD_RESET); +#ifdef SAHARA4_NO_USE_SQUIB + { + uint32_t cfg = sah_HW_Read_Config(); + cfg &= ~0x10000; + sah_HW_Write_Config(cfg); + } +#endif + + sah_poll_timeout = 0x0FFFFFFF; + sah_state = sah_Wait_On_Sahara(); +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Sahara Status register after reset: %08x", + sah_HW_Read_Status()); + LOG_KDIAG(Diag_msg); +#endif + /* on reset completion, check that Sahara is in the idle state */ + status = (sah_state == SAH_EXEC_IDLE) ? 0 : OS_ERROR_FAIL_S; + + /* Set initial value out of reset */ + sah_HW_Write_Control(saha_control); + +#ifndef NO_RESEED_WORKAROUND +/***************** HARDWARE BUG WORK AROUND ******************/ +/* In order to set the 'auto reseed' bit, must first acquire a random value. */ + /* + * to solve a hardware bug, a random number must be generated before + * the 'RNG Auto Reseed' bit can be set. So this generates a random + * number that is thrown away. + * + * Note that the interrupt bit has not been set at this point so + * the result can be polled. + */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("Create and submit Random Number Descriptor"); +#endif + + if (status == OS_ERROR_OK_S) { + /* place to put random number */ + volatile uint32_t *random_data_ptr; + sah_Head_Desc *random_desc; + dma_addr_t desc_dma; + dma_addr_t rand_dma; + const int rnd_cnt = 3; /* how many random 32-bit values to get */ + + /* Get space for data -- assume at least 32-bit aligned! */ + random_data_ptr = os_alloc_memory(rnd_cnt * sizeof(uint32_t), + GFP_ATOMIC); + + random_desc = sah_Alloc_Head_Descriptor(); + + if ((random_data_ptr == NULL) || (random_desc == NULL)) { + status = OS_ERROR_FAIL_S; + } else { + int i; + + /* Clear out values */ + for (i = 0; i < rnd_cnt; i++) { + random_data_ptr[i] = 0; + } + + rand_dma = os_pa(random_data_ptr); + + random_desc->desc.header = 0xB18C0000; /* LLO get random number */ + random_desc->desc.len1 = + rnd_cnt * sizeof(*random_data_ptr); + random_desc->desc.ptr1 = (void *)rand_dma; + random_desc->desc.original_ptr1 = + (void *)random_data_ptr; + + random_desc->desc.len2 = 0; /* not used */ + random_desc->desc.ptr2 = 0; /* not used */ + + random_desc->desc.next = 0; /* chain terminates here */ + random_desc->desc.original_next = 0; /* chain terminates here */ + + desc_dma = random_desc->desc.dma_addr; + + /* Force in-cache data out to RAM */ + os_cache_clean_range(random_data_ptr, + rnd_cnt * + sizeof(*random_data_ptr)); + + /* pass descriptor to Sahara */ + sah_HW_Write_DAR(desc_dma); + + /* + * Wait for RNG to complete (interrupts are disabled at this point + * due to sahara being reset previously) then check for error + */ + sah_state = sah_Wait_On_Sahara(); + /* Force CPU to ignore in-cache and reload from RAM */ + os_cache_inv_range(random_data_ptr, + rnd_cnt * sizeof(*random_data_ptr)); + + /* if it didn't move to done state, an error occured */ + if ( +#ifndef SUBMIT_MULTIPLE_DARS + (sah_state != SAH_EXEC_IDLE) && +#endif + (sah_state != SAH_EXEC_DONE1) + ) { + status = OS_ERROR_FAIL_S; + os_printk + ("(sahara) Failure: state is %08x; random_data is" + " %08x\n", sah_state, *random_data_ptr); + os_printk + ("(sahara) CDAR: %08x, IDAR: %08x, FADR: %08x," + " ESTAT: %08x\n", sah_HW_Read_CDAR(), + sah_HW_Read_IDAR(), + sah_HW_Read_Fault_Address(), + sah_HW_Read_Error_Status()); + } else { + int i; + int seen_rand = 0; + + for (i = 0; i < rnd_cnt; i++) { + if (*random_data_ptr != 0) { + seen_rand = 1; + break; + } + } + if (!seen_rand) { + status = OS_ERROR_FAIL_S; + os_printk + ("(sahara) Error: Random number is zero!\n"); + } + } + } + + if (random_data_ptr) { + os_free_memory((void *)random_data_ptr); + } + if (random_desc) { + sah_Free_Head_Descriptor(random_desc); + } + } +/***************** END HARDWARE BUG WORK AROUND ******************/ +#endif + + if (status == 0) { +#ifdef FSL_HAVE_SAHARA2 + saha_control |= CTRL_RNG_RESEED; +#endif + +#ifndef SAHARA_POLL_MODE + saha_control |= CTRL_INT_EN; /* enable interrupts */ +#else + sah_poll_timeout = SAHARA_POLL_MODE_TIMEOUT; +#endif + +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Setting up Sahara's Control Register: %08x\n", + saha_control); + LOG_KDIAG(Diag_msg); +#endif + + /* Rewrite the setup to the SAHARA Control register */ + sah_HW_Write_Control(saha_control); +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Sahara Status register after control write: %08x", + sah_HW_Read_Status()); + LOG_KDIAG(Diag_msg); +#endif + +#ifdef FSL_HAVE_SAHARA4 + { + uint32_t cfg = sah_HW_Read_Config(); + sah_HW_Write_Config(cfg | 0x100); /* Add RNG auto-reseed */ + } +#endif + } else { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Reset failed\n"); +#endif + } + + return status; +} /* sah_HW_Reset() */ + +/*! + * This function enables High Assurance mode. + * + * @brief SAHARA hardware enable High Assurance mode. + * + * @return FSL_RETURN_OK_S - if HA was set successfully + * @return FSL_RETURN_INTERNAL_ERROR_S - if HA was not set due to SAHARA + * being busy. + */ +fsl_shw_return_t sah_HW_Set_HA(void) +{ + /* This is the value to write to the register */ + uint32_t value; + + /* Read from the control register. */ + value = sah_HW_Read_Control(); + + /* Set the HA bit */ + value |= CTRL_HA; + + /* Write to the control register. */ + sah_HW_Write_Control(value); + + /* Read from the control register. */ + value = sah_HW_Read_Control(); + + return (value & CTRL_HA) ? FSL_RETURN_OK_S : + FSL_RETURN_INTERNAL_ERROR_S; +} + +/*! + * This function reads the SAHARA hardware Version Register. + * + * @brief Read SAHARA hardware Version Register. + * + * @return uint32_t Register value. + */ +uint32_t sah_HW_Read_Version(void) +{ + return os_read32(SAHARA_VERSION_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Control Register. + * + * @brief Read SAHARA hardware Control Register. + * + * @return uint32_t Register value. + */ +uint32_t sah_HW_Read_Control(void) +{ + return os_read32(SAHARA_CONTROL_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Status Register. + * + * @brief Read SAHARA hardware Status Register. + * + * @return uint32_t Register value. + */ +uint32_t sah_HW_Read_Status(void) +{ + return os_read32(SAHARA_STATUS_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Error Status Register. + * + * @brief Read SAHARA hardware Error Status Register. + * + * @return uint32_t Error Status value. + */ +uint32_t sah_HW_Read_Error_Status(void) +{ + return os_read32(SAHARA_ESTATUS_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Op Status Register. + * + * @brief Read SAHARA hardware Op Status Register. + * + * @return uint32_t Op Status value. + */ +uint32_t sah_HW_Read_Op_Status(void) +{ + return os_read32(SAHARA_OSTATUS_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Descriptor Address Register. + * + * @brief Read SAHARA hardware DAR Register. + * + * @return uint32_t DAR value. + */ +uint32_t sah_HW_Read_DAR(void) +{ + return os_read32(SAHARA_DAR_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Current Descriptor Address Register. + * + * @brief Read SAHARA hardware CDAR Register. + * + * @return uint32_t CDAR value. + */ +uint32_t sah_HW_Read_CDAR(void) +{ + return os_read32(SAHARA_CDAR_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Initial Descriptor Address Register. + * + * @brief Read SAHARA hardware IDAR Register. + * + * @return uint32_t IDAR value. + */ +uint32_t sah_HW_Read_IDAR(void) +{ + return os_read32(SAHARA_IDAR_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Fault Address Register. + * + * @brief Read SAHARA Fault Address Register. + * + * @return uint32_t Fault Address value. + */ +uint32_t sah_HW_Read_Fault_Address(void) +{ + return os_read32(SAHARA_FLT_ADD_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Multiple Master Status Register. + * + * @brief Read SAHARA hardware MM Stat Register. + * + * @return uint32_t MM Stat value. + */ +uint32_t sah_HW_Read_MM_Status(void) +{ + return os_read32(SAHARA_MM_STAT_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Configuration Register. + * + * @brief Read SAHARA Configuration Register. + * + * @return uint32_t Configuration value. + */ +uint32_t sah_HW_Read_Config(void) +{ + return os_read32(SAHARA_CONFIG_REGISTER); +} + +/*! + * This function writes a command to the SAHARA hardware Command Register. + * + * @brief Write to SAHARA hardware Command Register. + * + * @param command An unsigned 32bit command value. + * + * @return void + */ +void sah_HW_Write_Command(uint32_t command) +{ + os_write32(SAHARA_COMMAND_REGISTER, command); +} + +/*! + * This function writes a control value to the SAHARA hardware Control + * Register. + * + * @brief Write to SAHARA hardware Control Register. + * + * @param control An unsigned 32bit control value. + * + * @return void + */ +void sah_HW_Write_Control(uint32_t control) +{ + os_write32(SAHARA_CONTROL_REGISTER, control); +} + +/*! + * This function writes a configuration value to the SAHARA hardware Configuration + * Register. + * + * @brief Write to SAHARA hardware Configuration Register. + * + * @param configuration An unsigned 32bit configuration value. + * + * @return void + */ +void sah_HW_Write_Config(uint32_t configuration) +{ + os_write32(SAHARA_CONFIG_REGISTER, configuration); +} + +/*! + * This function writes a descriptor address to the SAHARA Descriptor Address + * Register. + * + * @brief Write to SAHARA Descriptor Address Register. + * + * @param pointer An unsigned 32bit descriptor address value. + * + * @return void + */ +void sah_HW_Write_DAR(uint32_t pointer) +{ + os_write32(SAHARA_DAR_REGISTER, pointer); + dar_count++; +} + +#if defined DIAG_DRV_IF || defined DO_DBG + +static char *interpret_header(uint32_t header) +{ + unsigned desc_type = ((header >> 24) & 0x70) | ((header >> 16) & 0xF); + + switch (desc_type) { + case 0x12: + return "5/SKHA_ST_CTX"; + case 0x13: + return "35/SKHA_LD_MODE_KEY"; + case 0x14: + return "38/SKHA_LD_MODE_IN_CPHR_ST_CTX"; + case 0x15: + return "4/SKHA_IN_CPHR_OUT"; + case 0x16: + return "34/SKHA_ST_SBOX"; + case 0x18: + return "1/SKHA_LD_MODE_IV_KEY"; + case 0x19: + return "33/SKHA_ST_SBOX"; + case 0x1D: + return "2/SKHA_LD_MODE_IN_CPHR_OUT"; + case 0x22: + return "11/MDHA_ST_MD"; + case 0x25: + return "10/MDHA_HASH_ST_MD"; + case 0x28: + return "6/MDHA_LD_MODE_MD_KEY"; + case 0x2A: + return "39/MDHA_ICV"; + case 0x2D: + return "8/MDHA_LD_MODE_HASH_ST_MD"; + case 0x3C: + return "18/RNG_GEN"; + case 0x40: + return "19/PKHA_LD_N_E"; + case 0x41: + return "36/PKHA_LD_A3_B0"; + case 0x42: + return "27/PKHA_ST_A_B"; + case 0x43: + return "22/PKHA_LD_A_B"; + case 0x44: + return "23/PKHA_LD_A0_A1"; + case 0x45: + return "24/PKHA_LD_A2_A3"; + case 0x46: + return "25/PKHA_LD_B0_B1"; + case 0x47: + return "26/PKHA_LD_B2_B3"; + case 0x48: + return "28/PKHA_ST_A0_A1"; + case 0x49: + return "29/PKHA_ST_A2_A3"; + case 0x4A: + return "30/PKHA_ST_B0_B1"; + case 0x4B: + return "31/PKHA_ST_B2_B3"; + case 0x4C: + return "32/PKHA_EX_ST_B1"; + case 0x4D: + return "20/PKHA_LD_A_EX_ST_B"; + case 0x4E: + return "21/PKHA_LD_N_EX_ST_B"; + case 0x4F: + return "37/PKHA_ST_B1_B2"; + default: + return "??/UNKNOWN"; + } +} /* cvt_desc_name() */ + +/*! + * Dump chain of descriptors to the log. + * + * @brief Dump descriptor chain + * + * @param chain Kernel virtual address of start of chain of descriptors + * + * @return void + */ +void sah_Dump_Chain(const sah_Desc * chain, dma_addr_t addr) +{ + int desc_no = 1; + + pr_debug("Chain for Sahara\n"); + + while (chain != NULL) { + char desc_name[50]; + + sprintf(desc_name, "Desc %02d (%s)\n" KERN_DEBUG "Desc ", + desc_no++, interpret_header(chain->header)); + + sah_Dump_Words(desc_name, (unsigned *)chain, addr, + 6 /* #words in h/w link */ ); + if (chain->original_ptr1) { + if (chain->header & SAH_HDR_LLO) { + sah_Dump_Region(" Data1", + (unsigned char *)chain-> + original_ptr1, + (dma_addr_t) chain->ptr1, + chain->len1); + } else { + sah_Dump_Link(" Link1", chain->original_ptr1, + (dma_addr_t) chain->ptr1); + } + } + if (chain->ptr2) { + if (chain->header & SAH_HDR_LLO) { + sah_Dump_Region(" Data2", + (unsigned char *)chain-> + original_ptr2, + (dma_addr_t) chain->ptr2, + chain->len2); + } else { + sah_Dump_Link(" Link2", chain->original_ptr2, + (dma_addr_t) chain->ptr2); + } + } + + addr = (dma_addr_t) chain->next; + chain = (chain->next) ? (chain->original_next) : NULL; + } +} + +/*! + * Dump chain of links to the log. + * + * @brief Dump chain of links + * + * @param prefix Text to put in front of dumped data + * @param link Kernel virtual address of start of chain of links + * + * @return void + */ +static void sah_Dump_Link(const char *prefix, const sah_Link * link, + dma_addr_t addr) +{ +#ifdef DUMP_SCC_DATA + extern uint8_t *sahara_partition_base; + extern dma_addr_t sahara_partition_phys; +#endif + + while (link != NULL) { + sah_Dump_Words(prefix, (unsigned *)link, addr, + 3 /* # words in h/w link */ ); + if (link->flags & SAH_STORED_KEY_INFO) { +#ifdef SAH_DUMP_DATA +#ifdef DUMP_SCC_DATA + sah_Dump_Region(" Data", + (uint8_t *) link->data - + (uint8_t *) sahara_partition_phys + + sahara_partition_base, + (dma_addr_t) link->data, link->len); +#else + pr_debug(" Key Slot %d\n", link->slot); +#endif +#endif + } else { +#ifdef SAH_DUMP_DATA + sah_Dump_Region(" Data", link->original_data, + (dma_addr_t) link->data, link->len); +#endif + } + addr = (dma_addr_t) link->next; + link = link->original_next; + } +} + +/*! + * Dump given region of data to the log. + * + * @brief Dump data + * + * @param prefix Text to put in front of dumped data + * @param data Kernel virtual address of start of region to dump + * @param length Amount of data to dump + * + * @return void + */ +void sah_Dump_Region(const char *prefix, const unsigned char *data, + dma_addr_t addr, unsigned length) +{ + unsigned count; + char *output; + unsigned data_len; + + sprintf(Diag_msg, "%s (%08X,%u):", prefix, addr, length); + + /* Restrict amount of data to dump */ + if (length > MAX_DUMP) { + data_len = MAX_DUMP; + } else { + data_len = length; + } + + /* We've already printed some text in output buffer, skip over it */ + output = Diag_msg + strlen(Diag_msg); + + for (count = 0; count < data_len; count++) { + if ((count % 4) == 0) { + *output++ = ' '; + } + sprintf(output, "%02X", *data++); + output += 2; + } + + pr_debug("%s\n", Diag_msg); +} + +/*! + * Dump given word of data to the log. + * + * @brief Dump data + * + * @param prefix Text to put in front of dumped data + * @param data Kernel virtual address of start of region to dump + * @param word_count Amount of data to dump + * + * @return void + */ +void sah_Dump_Words(const char *prefix, const unsigned *data, dma_addr_t addr, + unsigned word_count) +{ + char *output; + + sprintf(Diag_msg, "%s (%08X,%uw): ", prefix, addr, word_count); + + /* We've already printed some text in output buffer, skip over it */ + output = Diag_msg + strlen(Diag_msg); + + while (word_count--) { + sprintf(output, "%08X ", *data++); + output += 9; + } + + pr_debug("%s\n", Diag_msg); + +} + +#endif /* DIAG_DRV_IF */ + +/* End of sah_hardware_interface.c */ diff --git a/drivers/mxc/security/sahara2/sah_interrupt_handler.c b/drivers/mxc/security/sahara2/sah_interrupt_handler.c new file mode 100644 index 000000000000..3d70fe8d2c64 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_interrupt_handler.c @@ -0,0 +1,216 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file sah_interrupt_handler.c +* +* @brief Provides a hardware interrupt handling mechanism for device driver. +* +* This file needs to be ported for a non-Linux OS. +* +* It gets a call at #sah_Intr_Init() during initialization. +* +* #sah_Intr_Top_Half() is intended to be the Interrupt Service Routine. It +* calls a portable function in another file to process the Sahara status. +* +* #sah_Intr_Bottom_Half() is a 'background' task scheduled by the top half to +* take care of the expensive tasks of the interrupt processing. +* +* The driver shutdown code calls #sah_Intr_Release(). +* +*/ + +#include <portable_os.h> + +/* SAHARA Includes */ +#include <sah_kernel.h> +#include <sah_interrupt_handler.h> +#include <sah_status_manager.h> +#include <sah_hardware_interface.h> +#include <sah_queue_manager.h> + +/*Enable this flag for debugging*/ +#if 0 +#define DIAG_DRV_INTERRUPT +#endif + +#ifdef DIAG_DRV_INTERRUPT +#include <diagnostic.h> +#endif + +/*! + * Number of interrupts received. This value should only be updated during + * interrupt processing. + */ +uint32_t interrupt_count; + +#ifndef SAHARA_POLL_MODE + +#if !defined(LINUX_VERSION_CODE) || LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#define irqreturn_t void +#define IRQ_HANDLED +#define IRQ_RETVAL(x) +#endif + +/* Internal Prototypes */ +static irqreturn_t sah_Intr_Top_Half(int irq, void *dev_id); + +#ifdef KERNEL_TEST +extern void (*SAHARA_INT_PTR) (int, void *); +#endif + +unsigned long reset_flag; +static void sah_Intr_Bottom_Half(unsigned long reset_flag); + +/* This is the Bottom Half Task, (reset flag set to false) */ +DECLARE_TASKLET(BH_task, sah_Intr_Bottom_Half, (unsigned long)&reset_flag); + +/*! This is set by the Initialisation function */ +wait_queue_head_t *int_queue = NULL; + +/*! +******************************************************************************* +* This function registers the Top Half of the interrupt handler with the Kernel +* and the SAHARA IRQ number. +* +* @brief SAHARA Interrupt Handler Initialisation +* +* @param wait_queue Pointer to the wait queue used by driver interface +* +* @return int A return of Zero indicates successful initialisation. +*/ +/****************************************************************************** +* +* CAUTION: NONE +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 30/07/2003 MW Initial Creation +******************************************************************************/ +int sah_Intr_Init(wait_queue_head_t * wait_queue) +{ + +#ifdef DIAG_DRV_INTERRUPT + char err_string[200]; +#endif + + int result; + +#ifdef KERNEL_TEST + SAHARA_INT_PTR = sah_Intr_Top_Half; +#endif + + /* Set queue used by the interrupt handler to match the driver interface */ + int_queue = wait_queue; + + /* Request use of the Interrupt line. */ + result = request_irq(SAHARA_IRQ, + sah_Intr_Top_Half, 0, SAHARA_NAME, NULL); + +#ifdef DIAG_DRV_INTERRUPT + if (result != 0) { + sprintf(err_string, "Cannot use SAHARA interrupt line %d. " + "request_irq() return code is %i.", SAHARA_IRQ, result); + LOG_KDIAG(err_string); + } else { + sprintf(err_string, + "SAHARA driver registered for interrupt %d. ", + SAHARA_IRQ); + LOG_KDIAG(err_string); + } +#endif + + return result; +} + +/*! +******************************************************************************* +* This function releases the Top Half of the interrupt handler. The driver will +* not receive any more interrupts after calling this functions. +* +* @brief SAHARA Interrupt Handler Release +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: NONE +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 30/07/2003 MW Initial Creation +******************************************************************************/ +void sah_Intr_Release(void) +{ + /* Release the Interrupt. */ + free_irq(SAHARA_IRQ, NULL); +} + +/*! +******************************************************************************* +* This function is the Top Half of the interrupt handler. It updates the +* status of any finished descriptor chains and then tries to add any pending +* requests into the hardware. It then queues the bottom half to complete +* operations on the finished chains. +* +* @brief SAHARA Interrupt Handler Top Half +* +* @param irq Part of the kernel prototype. +* @param dev_id Part of the kernel prototype. +* +* @return An IRQ_RETVAL() -- non-zero to that function means 'handled' +*/ +static irqreturn_t sah_Intr_Top_Half(int irq, void *dev_id) +{ +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + LOG_KDIAG("Top half of Sahara's interrupt handler called."); +#endif + + interrupt_count++; + reset_flag = sah_Handle_Interrupt(sah_HW_Read_Status()); + + /* Schedule the Bottom Half of the Interrupt. */ + tasklet_schedule(&BH_task); + + /* To get rid of the unused parameter warnings. */ + irq = 0; + dev_id = NULL; + return IRQ_RETVAL(1); +} + +/*! +******************************************************************************* +* This function is the Bottom Half of the interrupt handler. It calls +* #sah_postprocess_queue() to complete the processing of the Descriptor Chains +* which were finished by the hardware. +* +* @brief SAHARA Interrupt Handler Bottom Half +* +* @param data Part of the kernel prototype. +* +* @return void +*/ +static void sah_Intr_Bottom_Half(unsigned long reset_flag) +{ +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + LOG_KDIAG("Bottom half of Sahara's interrupt handler called."); +#endif + sah_postprocess_queue(*(unsigned long *)reset_flag); + + return; +} + +/* end of sah_interrupt_handler.c */ +#endif /* ifndef SAHARA_POLL_MODE */ diff --git a/drivers/mxc/security/sahara2/sah_memory_mapper.c b/drivers/mxc/security/sahara2/sah_memory_mapper.c new file mode 100644 index 000000000000..29080343ee11 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_memory_mapper.c @@ -0,0 +1,2349 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file sah_memory_mapper.c +* +* @brief Re-creates SAHARA data structures in Kernel memory such that they are +* suitable for DMA. Provides support for kernel API. +* +* This file needs to be ported. +* +* The memory mapper gets a call at #sah_Init_Mem_Map() during driver +* initialization. +* +* The routine #sah_Copy_Descriptors() is used to bring descriptor chains from +* user memory down to kernel memory, relink using physical addresses, and make +* sure that all user data will be accessible by the Sahara DMA. +* #sah_Destroy_Descriptors() does the inverse. +* +* The #sah_Alloc_Block(), #sah_Free_Block(), and #sah_Block_Add_Page() routines +* implement a cache of free blocks used when allocating descriptors and links +* within the kernel. +* +* The memory mapper gets a call at #sah_Stop_Mem_Map() during driver shutdown. +* +*/ + +#include <sah_driver_common.h> +#include <sah_kernel.h> +#include <sah_queue_manager.h> +#include <sah_memory_mapper.h> +#ifdef FSL_HAVE_SCC2 +#include <linux/mxc_scc2_driver.h> +#else +#include <linux/mxc_scc_driver.h> +#endif + +#if defined(DIAG_DRV_IF) || defined(DIAG_MEM) || defined(DO_DBG) +#include <diagnostic.h> +#include <sah_hardware_interface.h> +#endif + +#include <linux/mm.h> /* get_user_pages() */ +#include <linux/pagemap.h> +#include <linux/dmapool.h> + +#include <linux/slab.h> +#include <linux/highmem.h> + +#if defined(DIAG_MEM) || defined(DIAG_DRV_IF) +#define DIAG_MSG_SIZE 1024 +static char Diag_msg[DIAG_MSG_SIZE]; +#endif + +#ifdef LINUX_VERSION_CODE +#define FLUSH_SPECIFIC_DATA_ONLY +#else +#define SELF_MANAGED_POOL +#endif + +#if defined(LINUX_VERSION_CODE) +EXPORT_SYMBOL(sah_Alloc_Link); +EXPORT_SYMBOL(sah_Free_Link); +EXPORT_SYMBOL(sah_Alloc_Descriptor); +EXPORT_SYMBOL(sah_Free_Descriptor); +EXPORT_SYMBOL(sah_Alloc_Head_Descriptor); +EXPORT_SYMBOL(sah_Free_Head_Descriptor); +EXPORT_SYMBOL(sah_Physicalise_Descriptors); +EXPORT_SYMBOL(sah_DePhysicalise_Descriptors); +#endif + +/* Determine if L2 cache support should be built in. */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)) +#ifdef CONFIG_OUTER_CACHE +#define HAS_L2_CACHE +#endif +#else +#ifdef CONFIG_CPU_CACHE_L210 +#define HAS_L2_CACHE +#endif +#endif + +/* Number of bytes the hardware uses out of sah_Link and sah_*Desc structs */ +#define SAH_HW_LINK_LEN 1 +#define SAH_HW_DESC_LEN 24 + +/* Macros for Descriptors */ +#define SAH_LLO_BIT 0x01000000 +#define sah_Desc_Get_LLO(desc) (desc->header & SAH_LLO_BIT) +#define sah_Desc_Set_Header(desc, h) (desc->header = (h)) + +#define sah_Desc_Get_Next(desc) (desc->next) +#define sah_Desc_Set_Next(desc, n) (desc->next = (n)) + +#define sah_Desc_Get_Ptr1(desc) (desc->ptr1) +#define sah_Desc_Get_Ptr2(desc) (desc->ptr2) +#define sah_Desc_Set_Ptr1(desc,p1) (desc->ptr1 = (p1)) +#define sah_Desc_Set_Ptr2(desc,p2) (desc->ptr2 = (p2)) + +#define sah_Desc_Get_Len1(desc) (desc->len1) +#define sah_Desc_Get_Len2(desc) (desc->len2) +#define sah_Desc_Set_Len1(desc,l1) (desc->len1 = (l1)) +#define sah_Desc_Set_Len2(desc,l2) (desc->len2 = (l2)) + +/* Macros for Links */ +#define sah_Link_Get_Next(link) (link->next) +#define sah_Link_Set_Next(link, n) (link->next = (n)) + +#define sah_Link_Get_Data(link) (link->data) +#define sah_Link_Set_Data(link,d) (link->data = (d)) + +#define sah_Link_Get_Len(link) (link->len) +#define sah_Link_Set_Len(link, l) (link->len = (l)) + +#define sah_Link_Get_Flags(link) (link->flags) + +/* Memory block defines */ +/* Warning! This assumes that kernel version of sah_Link + * is larger than kernel version of sah_Desc. + */ +#define MEM_BLOCK_SIZE sizeof(sah_Link) + +/*! Structure for link/descriptor memory blocks in internal pool */ +typedef struct mem_block { + uint8_t data[MEM_BLOCK_SIZE]; /*!< the actual buffer area */ + struct mem_block *next; /*!< next block when in free chain */ + dma_addr_t dma_addr; /*!< physical address of @a data */ +} Mem_Block; + +#define MEM_BLOCK_ENTRIES (PAGE_SIZE / sizeof(Mem_Block)) + +#define MEM_BIG_BLOCK_SIZE sizeof(sah_Head_Desc) + +/*! Structure for head descriptor memory blocks in internal pool */ +typedef struct mem_big_block { + uint8_t data[MEM_BIG_BLOCK_SIZE]; /*!< the actual buffer area */ + struct mem_big_block *next; /*!< next block when in free chain */ + uint32_t dma_addr; /*!< physical address of @a data */ +} Mem_Big_Block; + +#define MEM_BIG_BLOCK_ENTRIES (PAGE_SIZE / sizeof(Mem_Big_Block)) + +/* Shared variables */ + +/*! + * Lock to protect the memory chain composed of #block_free_head and + * #block_free_tail. + */ +static os_lock_t mem_lock; + +#ifndef SELF_MANAGED_POOL +static struct dma_pool *big_dma_pool = NULL; +static struct dma_pool *small_dma_pool = NULL; +#endif + +#ifdef SELF_MANAGED_POOL +/*! + * Memory block free pool - pointer to first block. Chain is protected by + * #mem_lock. + */ +static Mem_Block *block_free_head = NULL; +/*! + * Memory block free pool - pointer to last block. Chain is protected by + * #mem_lock. + */ +static Mem_Block *block_free_tail = NULL; +/*! + * Memory block free pool - pointer to first block. Chain is protected by + * #mem_lock. + */ +static Mem_Big_Block *big_block_free_head = NULL; +/*! + * Memory block free pool - pointer to last block. Chain is protected by + * #mem_lock. +a */ +static Mem_Big_Block *big_block_free_tail = NULL; +#endif /* SELF_MANAGED_POOL */ + +static Mem_Block *sah_Alloc_Block(void); +static void sah_Free_Block(Mem_Block * block); +static Mem_Big_Block *sah_Alloc_Big_Block(void); +static void sah_Free_Big_Block(Mem_Big_Block * block); +#ifdef SELF_MANAGED_POOL +static void sah_Append_Block(Mem_Block * block); +static void sah_Append_Big_Block(Mem_Big_Block * block); +#endif /* SELF_MANAGED_POOL */ + +/* Page context structure. Used by wire_user_memory and unwire_user_memory */ +typedef struct page_ctx_t { + uint32_t count; + struct page **local_pages; +} page_ctx_t; + +/*! +******************************************************************************* +* Map and wire down a region of user memory. +* +* +* @param address Userspace address of the memory to wire +* @param length Length of the memory region to wire +* @param page_ctx Page context, to be passed to unwire_user_memory +* +* @return (if successful) Kernel virtual address of the wired pages +*/ +void *wire_user_memory(void *address, uint32_t length, void **page_ctx) +{ + void *kernel_black_addr = NULL; + int result = -1; + int page_index = 0; + page_ctx_t *page_context; + int nr_pages = 0; + unsigned long start_page; + fsl_shw_return_t status; + + /* Determine the number of pages being used for this link */ + nr_pages = (((unsigned long)(address) & ~PAGE_MASK) + + length + ~PAGE_MASK) >> PAGE_SHIFT; + + start_page = (unsigned long)(address) & PAGE_MASK; + + /* Allocate some memory to keep track of the wired user pages, so that + * they can be deallocated later. The block of memory will contain both + * the structure and the array of pages. + */ + page_context = kmalloc(sizeof(page_ctx_t) + + nr_pages * sizeof(struct page *), GFP_KERNEL); + + if (page_context == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; /* no memory! */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("kmalloc() failed."); +#endif + return NULL; + } + + /* Set the page pointer to point to the allocated region of memory */ + page_context->local_pages = (void *)page_context + sizeof(page_ctx_t); + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS("page_context at: %p, local_pages at: %p", + (void *)page_context, + (void *)(page_context->local_pages)); +#endif + + /* Wire down the pages from user space */ + down_read(¤t->mm->mmap_sem); + result = get_user_pages(current, current->mm, + start_page, nr_pages, WRITE, 0 /* noforce */ , + (page_context->local_pages), NULL); + up_read(¤t->mm->mmap_sem); + + if (result < nr_pages) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("get_user_pages() failed."); +#endif + if (result > 0) { + for (page_index = 0; page_index < result; page_index++) { + page_cache_release((page_context-> + local_pages[page_index])); + } + + kfree(page_context); + } + return NULL; + } + + kernel_black_addr = page_address(page_context->local_pages[0]) + + ((unsigned long)address & ~PAGE_MASK); + + page_context->count = nr_pages; + *page_ctx = page_context; + + return kernel_black_addr; +} + +/*! +******************************************************************************* +* Release and unmap a region of user memory. +* +* @param page_ctx Page context from wire_user_memory +*/ +void unwire_user_memory(void **page_ctx) +{ + int page_index = 0; + struct page_ctx_t *page_context = *page_ctx; + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS("page_context at: %p, first page at:%p, count: %i", + (void *)page_context, + (void *)(page_context->local_pages), + page_context->count); +#endif + + if ((page_context != NULL) && (page_context->local_pages != NULL)) { + for (page_index = 0; page_index < page_context->count; + page_index++) { + page_cache_release(page_context-> + local_pages[page_index]); + } + + kfree(page_context); + *page_ctx = NULL; + } +} + +/*! +******************************************************************************* +* Map some physical memory into a users memory space +* +* @param vma Memory structure to map to +* @param physical_addr Physical address of the memory to be mapped in +* @param size Size of the memory to map (bytes) +* +* @return +*/ +os_error_code +map_user_memory(struct vm_area_struct *vma, uint32_t physical_addr, + uint32_t size) +{ + os_error_code retval; + + /* Map the acquired partition into the user's memory space */ + vma->vm_end = vma->vm_start + size; + + /* set cache policy to uncached so that each write of the UMID and + * permissions get directly to the SCC2 in order to engage it + * properly. Once the permissions have been written, it may be + * useful to provide a service for the user to request a different + * cache policy + */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Make sure that the user cannot fork() a child which will inherit + * this mapping, as it creates a security hole. Likewise, do not + * allow the user to 'expand' his mapping beyond this partition. + */ + vma->vm_flags |= VM_IO | VM_RESERVED | VM_DONTCOPY | VM_DONTEXPAND; + + retval = remap_pfn_range(vma, + vma->vm_start, + __phys_to_pfn(physical_addr), + size, vma->vm_page_prot); + + return retval; +} + +/*! +******************************************************************************* +* Remove some memory from a user's memory space +* +* @param user_addr Userspace address of the memory to be unmapped +* @param size Size of the memory to map (bytes) +* +* @return +*/ +os_error_code unmap_user_memory(uint32_t user_addr, uint32_t size) +{ + os_error_code retval; + struct mm_struct *mm = current->mm; + + /* Unmap the memory region (see sys_munmap in mmap.c) */ + down_write(&mm->mmap_sem); + retval = do_munmap(mm, (unsigned long)user_addr, size); + up_write(&mm->mmap_sem); + + return retval; +} + +/*! +******************************************************************************* +* Free descriptor back to free pool +* +* @brief Free descriptor +* +* @param desc A descriptor allocated with sah_Alloc_Descriptor(). +* +* @return none +* +*/ +void sah_Free_Descriptor(sah_Desc * desc) +{ + memset(desc, 0x45, sizeof(*desc)); + sah_Free_Block((Mem_Block *) desc); +} + +/*! +******************************************************************************* +* Free Head descriptor back to free pool +* +* @brief Free Head descriptor +* +* @param desc A Head descriptor allocated with sah_Alloc_Head_Descriptor(). +* +* @return none +* +*/ +void sah_Free_Head_Descriptor(sah_Head_Desc * desc) +{ + memset(desc, 0x43, sizeof(*desc)); + sah_Free_Big_Block((Mem_Big_Block *) desc); +} + +/*! +******************************************************************************* +* Free link back to free pool +* +* @brief Free link +* +* @param link A link allocated with sah_Alloc_Link(). +* +* @return none +* +*/ +void sah_Free_Link(sah_Link * link) +{ + memset(link, 0x41, sizeof(*link)); + sah_Free_Block((Mem_Block *) link); +} + +/*! +******************************************************************************* +* This function runs through a descriptor chain pointed to by a user-space +* address. It duplicates each descriptor in Kernel space memory and calls +* sah_Copy_Links() to handle any links attached to the descriptors. This +* function cleans-up everything that it created in the case of a failure. +* +* @brief Kernel Descriptor Chain Copier +* +* @param fsl_shw_uco_t The user context to act under +* @param user_head_desc A Head Descriptor pointer from user-space. +* +* @return sah_Head_Desc * - A virtual address of the first descriptor in the +* chain. +* @return NULL - If there was some error. +* +*/ +sah_Head_Desc *sah_Copy_Descriptors(fsl_shw_uco_t * user_ctx, + sah_Head_Desc * user_head_desc) +{ + sah_Desc *curr_desc = NULL; + sah_Desc *prev_desc = NULL; + sah_Desc *next_desc = NULL; + sah_Head_Desc *head_desc = NULL; + sah_Desc *user_desc = NULL; + unsigned long result; + + /* Internal status variable to be used in this function */ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Head_Desc *ret_val = NULL; + + /* This will be set to True when we have finished processing our + * descriptor chain. + */ + int drv_if_done = FALSE; + int is_this_the_head = TRUE; + + do { + /* Allocate memory for this descriptor */ + if (is_this_the_head) { + head_desc = + (sah_Head_Desc *) sah_Alloc_Head_Descriptor(); + +#ifdef DIAG_MEM + sprintf(Diag_msg, + "Alloc_Head_Descriptor returned %p\n", + head_desc); + LOG_KDIAG(Diag_msg); +#endif + if (head_desc == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("sah_Alloc_Head_Descriptor() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_NO_RESOURCE_S; + } else { + void *virt_addr = head_desc->desc.virt_addr; + dma_addr_t dma_addr = head_desc->desc.dma_addr; + + /* Copy the head descriptor from user-space */ + /* Instead of copying the whole structure, + * unneeded bits at the end are left off. + * The user space version is missing virt/dma addrs, which + * means that the copy will be off for flags... */ + result = copy_from_user(head_desc, + user_head_desc, + (sizeof(*head_desc) - + sizeof(head_desc->desc. + dma_addr) - + sizeof(head_desc->desc. + virt_addr) - + sizeof(head_desc->desc. + original_ptr1) - +/* sizeof(head_desc->desc.original_ptr2) - + sizeof(head_desc->status) - + sizeof(head_desc->error_status) - + sizeof(head_desc->fault_address) - + sizeof(head_desc->current_dar) - + sizeof(head_desc->result) - + sizeof(head_desc->next) - + sizeof(head_desc->prev) - + sizeof(head_desc->user_desc) - +*/ sizeof(head_desc->out1_ptr) - + sizeof(head_desc-> + out2_ptr) - + sizeof(head_desc-> + out_len))); + /* there really isn't a 'next' descriptor at this point, so + * set that pointer to NULL, but remember it for if/when there + * is a next */ + next_desc = head_desc->desc.next; + head_desc->desc.next = NULL; + + if (result != 0) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("copy_from_user() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; + /* when destroying the descriptor, skip these links. + * They've not been copied down, so don't exist */ + head_desc->desc.ptr1 = NULL; + head_desc->desc.ptr2 = NULL; + + } else { + /* The kernel DESC has five more words than user DESC, so + * the missing values are in the middle of the HEAD DESC, + * causing values after the missing ones to be at different + * offsets in kernel and user space. + * + * Patch up the problem by moving field two spots. + * This assumes sizeof(pointer) == sizeof(uint32_t). + * Note that 'user_info' is not needed, so not copied. + */ + head_desc->user_ref = + (uint32_t) head_desc->desc.dma_addr; + head_desc->uco_flags = + (uint32_t) head_desc->desc. + original_ptr1; +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS( + "User flags: %x; User Reference: %x", + head_desc->uco_flags, + head_desc->user_ref); +#endif + /* These values were destroyed by the copy. */ + head_desc->desc.virt_addr = virt_addr; + head_desc->desc.dma_addr = dma_addr; + + /* ensure that the save descriptor chain bit is not set. + * the copy of the user space descriptor chain should + * always be deleted */ + head_desc->uco_flags &= + ~FSL_UCO_SAVE_DESC_CHAIN; + + curr_desc = (sah_Desc *) head_desc; + is_this_the_head = FALSE; + } + } + } else { /* not head */ + curr_desc = sah_Alloc_Descriptor(); +#ifdef DIAG_MEM + LOG_KDIAG_ARGS("Alloc_Descriptor returned %p\n", + curr_desc); +#endif + if (curr_desc == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Alloc_Descriptor() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_NO_RESOURCE_S; + } else { + /* need to update the previous descriptors' next field to + * pointer to the current descriptor. */ + prev_desc->original_next = curr_desc; + prev_desc->next = + (sah_Desc *) curr_desc->dma_addr; + + /* Copy the current descriptor from user-space */ + /* The virtual address and DMA address part of the sah_Desc + * struct are not copied to user space */ + result = copy_from_user(curr_desc, user_desc, (sizeof(sah_Desc) - sizeof(dma_addr_t) - /* dma_addr */ + sizeof(uint32_t) - /* virt_addr */ + sizeof(void *) - /* original_ptr1 */ + sizeof(void *) - /* original_ptr2 */ + sizeof(sah_Desc **))); /* original_next */ + /* there really isn't a 'next' descriptor at this point, so + * set that pointer to NULL, but remember it for if/when there + * is a next */ + next_desc = curr_desc->next; + curr_desc->next = NULL; + curr_desc->original_next = NULL; + + if (result != 0) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("copy_from_user() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; + /* when destroying the descriptor chain, skip these links. + * They've not been copied down, so don't exist */ + curr_desc->ptr1 = NULL; + curr_desc->ptr2 = NULL; + } + } + } /* end if (is_this_the_head) */ + + if (status == FSL_RETURN_OK_S) { + if (!(curr_desc->header & SAH_LLO_BIT)) { + /* One or both pointer fields being NULL is a valid + * configuration. */ + if (curr_desc->ptr1 == NULL) { + curr_desc->original_ptr1 = NULL; + } else { + /* pointer fields point to sah_Link structures */ + curr_desc->original_ptr1 = + sah_Copy_Links(user_ctx, curr_desc->ptr1); + if (curr_desc->original_ptr1 == NULL) { + /* This descriptor and any links created successfully + * are cleaned-up at the bottom of this function. */ + drv_if_done = TRUE; + status = + FSL_RETURN_INTERNAL_ERROR_S; + /* mark that link 2 doesn't exist */ + curr_desc->ptr2 = NULL; +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("sah_Copy_Links() failed."); +#endif + } else { + curr_desc->ptr1 = (void *) + ((sah_Link *) curr_desc-> + original_ptr1)->dma_addr; + } + } + + if (status == FSL_RETURN_OK_S) { + if (curr_desc->ptr2 == NULL) { + curr_desc->original_ptr2 = NULL; + } else { + /* pointer fields point to sah_Link structures */ + curr_desc->original_ptr2 = + sah_Copy_Links(user_ctx, curr_desc->ptr2); + if (curr_desc->original_ptr2 == + NULL) { + /* This descriptor and any links created + * successfully are cleaned-up at the bottom of + * this function. */ + drv_if_done = TRUE; + status = + FSL_RETURN_INTERNAL_ERROR_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("sah_Copy_Links() failed."); +#endif + } else { + curr_desc->ptr2 = + (void + *)(((sah_Link *) + curr_desc-> + original_ptr2) + ->dma_addr); + } + } + } + } else { + /* Pointer fields point directly to user buffers. We don't + * support this mode. + */ +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("The LLO bit in the Descriptor Header field was " + "set. This an invalid configuration."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; + } + } + + if (status == FSL_RETURN_OK_S) { + user_desc = next_desc; + prev_desc = curr_desc; + if (user_desc == NULL) { + /* We have reached the end our our descriptor chain */ + drv_if_done = TRUE; + } + } + + } while (drv_if_done == FALSE); + + if (status != FSL_RETURN_OK_S) { + /* Clean-up if failed */ + if (head_desc != NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Error! Calling destroy descriptors!\n"); +#endif + sah_Destroy_Descriptors(head_desc); + } + ret_val = NULL; + } else { + /* Flush the caches */ +#ifndef FLUSH_SPECIFIC_DATA_ONLY + os_flush_cache_all(); +#endif + + /* Success. Return the DMA'able head descriptor. */ + ret_val = head_desc; + + } + + return ret_val; +} /* sah_Copy_Descriptors() */ + +/*! +******************************************************************************* +* This function runs through a sah_Link chain pointed to by a kernel-space +* address. It computes the physical address for each pointer, and converts +* the chain to use these physical addresses. +* +****** +* This function needs to return some indication that the chain could not be +* converted. It also needs to back out any conversion already taken place on +* this chain of links. +* +* Then, of course, sah_Physicalise_Descriptors() will need to recognize that +* an error occured, and then be able to back out any physicalization of the +* chain which had taken place up to that point! +****** +* +* @brief Convert kernel Link chain +* +* @param first_link A sah_Link pointer from kernel space; must not be +* NULL, so error case can be distinguished. +* +* @return sah_Link * A dma'able address of the first descriptor in the +* chain. +* @return NULL If Link chain could not be physicalised, i.e. ERROR +* +*/ +sah_Link *sah_Physicalise_Links(sah_Link * first_link) +{ + sah_Link *link = first_link; + + while (link != NULL) { +#ifdef DO_DBG + sah_Dump_Words("Link", (unsigned *)link, link->dma_addr, 3); +#endif + link->vm_info = NULL; + + /* need to retrieve stored key? */ + if (link->flags & SAH_STORED_KEY_INFO) { + uint32_t max_len = 0; /* max slot length */ + fsl_shw_return_t ret_status; + + /* get length and physical address of stored key */ + ret_status = system_keystore_get_slot_info(link->ownerid, link->slot, (uint32_t *) & link->data, /* RED key address */ + &max_len); + if ((ret_status != FSL_RETURN_OK_S) || (link->len > max_len)) { + /* trying to illegally/incorrectly access a key. Cause the + * error status register to show a Link Length Error by + * putting a zero in the links length. */ + link->len = 0; /* Cause error. Somebody is up to no good. */ + } + } else if (link->flags & SAH_IN_USER_KEYSTORE) { + +#ifdef FSL_HAVE_SCC2 + /* The data field points to the virtual address of the key. Convert + * this to a physical address by modifying the address based + * on where the secure memory was mapped to the kernel. Note: In + * kernel mode, no attempt is made to track or control who owns what + * memory partition. + */ + link->data = (uint8_t *) scc_virt_to_phys(link->data); + + /* Do bounds checking to ensure that the user is not overstepping + * the bounds of their partition. This is a simple implementation + * that assumes the user only owns one partition. It only checks + * to see if the address of the last byte of data steps over a + * page boundary. + */ + +#ifdef DO_DBG + LOG_KDIAG_ARGS("start page: %08x, end page: %08x" + "first addr: %p, last addr: %p, len; %i", + ((uint32_t) (link->data) >> PAGE_SHIFT), + (((uint32_t) link->data + + link->len) >> PAGE_SHIFT), link->data, + link->data + link->len, link->len); +#endif + + if ((((uint32_t) link->data + + link->len) >> PAGE_SHIFT) != + ((uint32_t) link->data >> PAGE_SHIFT)) { + link->len = 0; /* Cause error. Somebody is up to no good. */ + } +#else /* FSL_HAVE_SCC2 */ + + /* User keystores are not valid on non-SCC2 platforms */ + link->len = 0; /* Cause error. Somebody is up to no good. */ + +#endif /* FSL_HAVE_SCC2 */ + + } else { + if (!(link->flags & SAH_PREPHYS_DATA)) { + link->original_data = link->data; + + /* All pointers are virtual right now */ + link->data = (void *)os_pa(link->data); +#ifdef DO_DBG + os_printk("%sput: %p (%d)\n", + (link-> + flags & SAH_OUTPUT_LINK) ? "out" : + "in", link->data, link->len); +#endif + + if (link->flags & SAH_OUTPUT_LINK) { + /* clean and invalidate */ + os_cache_flush_range(link-> + original_data, + link->len); + } else { + os_cache_clean_range(link->original_data, + link->len); + } + } /* not prephys */ + } /* else not key reference */ + +#if defined(NO_OUTPUT_1K_CROSSING) || defined(NO_1K_CROSSING) + if ( +#ifdef NO_OUTPUT_1K_CROSSING + /* Insert extra link if 1k boundary on output pointer + * crossed not at an 8-word boundary */ + (link->flags & SAH_OUTPUT_LINK) && + (((uint32_t) link->data % 32) != 0) && +#endif + ((((uint32_t) link->data & 1023) + link->len) > + 1024)) { + uint32_t full_length = link->len; + sah_Link *new_link = sah_Alloc_Link(); + link->len = 1024 - ((uint32_t) link->data % 1024); + new_link->len = full_length - link->len; + new_link->data = link->data + link->len; + new_link->original_data = + link->original_data + link->len; + new_link->flags = link->flags & ~(SAH_OWNS_LINK_DATA); + new_link->flags |= SAH_LINK_INSERTED_LINK; + new_link->next = link->next; + + link->next = (sah_Link *) new_link->dma_addr; + link->original_next = new_link; + link = new_link; + } +#endif /* ALLOW_OUTPUT_1K_CROSSING */ + + link->original_next = link->next; + if (link->next != NULL) { + link->next = (sah_Link *) link->next->dma_addr; + } +#ifdef DO_DBG + sah_Dump_Words("Link", link, link->dma_addr, 3); +#endif + + link = link->original_next; + } + + return (sah_Link *) first_link->dma_addr; +} /* sah_Physicalise_Links */ + +/*! + * Run through descriptors and links created by KM-API and set the + * dma addresses and 'do not free' flags. + * + * @param first_desc KERNEL VIRTUAL address of first descriptor in chain. + * + * Warning! This ONLY works without LLO flags in headers!!! + * + * @return Virtual address of @a first_desc. + * @return NULL if Descriptor Chain could not be physicalised + */ +sah_Head_Desc *sah_Physicalise_Descriptors(sah_Head_Desc * first_desc) +{ + sah_Desc *desc = &first_desc->desc; + + if (!(first_desc->uco_flags & FSL_UCO_CHAIN_PREPHYSICALIZED)) { + while (desc != NULL) { + sah_Desc *next_desc; + +#ifdef DO_DBG + + sah_Dump_Words("Desc", (unsigned *)desc, desc->dma_addr, 6); +#endif + + desc->original_ptr1 = desc->ptr1; + if (desc->ptr1 != NULL) { + if ((desc->ptr1 = + sah_Physicalise_Links(desc->ptr1)) == + NULL) { + /* Clean up ... */ + sah_DePhysicalise_Descriptors + (first_desc); + first_desc = NULL; + break; + } + } + desc->original_ptr2 = desc->ptr2; + if (desc->ptr2 != NULL) { + if ((desc->ptr2 = + sah_Physicalise_Links(desc->ptr2)) == + NULL) { + /* Clean up ... */ + sah_DePhysicalise_Descriptors + (first_desc); + first_desc = NULL; + break; + } + } + + desc->original_next = desc->next; + next_desc = desc->next; /* save for bottom of while loop */ + if (desc->next != NULL) { + desc->next = (sah_Desc *) desc->next->dma_addr; + } + + desc = next_desc; + } + } + /* not prephysicalized */ +#ifdef DO_DBG + os_printk("Physicalise finished\n"); +#endif + + return first_desc; +} /* sah_Physicalise_Descriptors() */ + +/*! +******************************************************************************* +* This function runs through a sah_Link chain pointed to by a physical address. +* It computes the virtual address for each pointer +* +* @brief Convert physical Link chain +* +* @param first_link A kernel address of a sah_Link +* +* @return sah_Link * A kernal address for the link chain of @c first_link +* @return NULL If there was some error. +* +* @post All links will be chained together by original virtual addresses, +* data pointers will point to virtual addresses. Appropriate cache +* lines will be flushed, memory unwired, etc. +*/ +sah_Link *sah_DePhysicalise_Links(sah_Link * first_link) +{ + sah_Link *link = first_link; + sah_Link *prev_link = NULL; + + /* Loop on virtual link pointer */ + while (link != NULL) { + +#ifdef DO_DBG + sah_Dump_Words("Link", (unsigned *)link, link->dma_addr, 3); +#endif + + /* if this references stored keys, don't want to dephysicalize them */ + if (!(link->flags & SAH_STORED_KEY_INFO) + && !(link->flags & SAH_PREPHYS_DATA) + && !(link->flags & SAH_IN_USER_KEYSTORE)) { + + /* */ + if (link->flags & SAH_OUTPUT_LINK) { + os_cache_inv_range(link->original_data, + link->len); + } + + /* determine if there is a page in user space associated with this + * link */ + if (link->vm_info != NULL) { + /* check that this isn't reserved and contains output */ + if (!PageReserved(link->vm_info) && + (link->flags & SAH_OUTPUT_LINK)) { + + /* Mark to force page eventually to backing store */ + SetPageDirty(link->vm_info); + } + + /* Untie this page from physical memory */ + page_cache_release(link->vm_info); + } else { + /* kernel-mode data */ +#ifdef DO_DBG + os_printk("%sput: %p (%d)\n", + (link-> + flags & SAH_OUTPUT_LINK) ? "out" : + "in", link->original_data, link->len); +#endif + } + link->data = link->original_data; + } +#ifndef ALLOW_OUTPUT_1K_CROSSING + if (link->flags & SAH_LINK_INSERTED_LINK) { + /* Reconsolidate data by merging this link with previous */ + prev_link->len += link->len; + prev_link->next = link->next; + prev_link->original_next = link->original_next; + sah_Free_Link(link); + link = prev_link; + + } +#endif + + if (link->next != NULL) { + link->next = link->original_next; + } + prev_link = link; + link = link->next; + } + + return first_link; +} /* sah_DePhysicalise_Links() */ + +/*! + * Run through descriptors and links that have been Physicalised + * (sah_Physicalise_Descriptors function) and set the dma addresses back + * to KM virtual addresses + * + * @param first_desc Kernel virtual address of first descriptor in chain. + * + * Warning! This ONLY works without LLO flags in headers!!! + */ +sah_Head_Desc *sah_DePhysicalise_Descriptors(sah_Head_Desc * first_desc) +{ + sah_Desc *desc = &first_desc->desc; + + if (!(first_desc->uco_flags & FSL_UCO_CHAIN_PREPHYSICALIZED)) { + while (desc != NULL) { +#ifdef DO_DBG + sah_Dump_Words("Desc", (unsigned *)desc, desc->dma_addr, 6); +#endif + + if (desc->ptr1 != NULL) { + desc->ptr1 = + sah_DePhysicalise_Links(desc-> + original_ptr1); + } + if (desc->ptr2 != NULL) { + desc->ptr2 = + sah_DePhysicalise_Links(desc-> + original_ptr2); + } + if (desc->next != NULL) { + desc->next = desc->original_next; + } + desc = desc->next; + } + } + /* not prephysicalized */ + return first_desc; +} /* sah_DePhysicalise_Descriptors() */ + +/*! +******************************************************************************* +* This walks through a SAHARA descriptor chain and free()'s everything +* that is not NULL. Finally it also unmaps all of the physical memory and +* frees the kiobuf_list Queue. +* +* @brief Kernel Descriptor Chain Destructor +* +* @param head_desc A Descriptor pointer from kernel-space. +* +* @return void +* +*/ +void sah_Free_Chained_Descriptors(sah_Head_Desc * head_desc) +{ + sah_Desc *desc = NULL; + sah_Desc *next_desc = NULL; + int this_is_head = 1; + + desc = &head_desc->desc; + + while (desc != NULL) { + + sah_Free_Chained_Links(desc->ptr1); + sah_Free_Chained_Links(desc->ptr2); + + /* Get a bus pointer to the next Descriptor */ + next_desc = desc->next; + + /* Zero the header and Length fields for security reasons. */ + desc->header = 0; + desc->len1 = 0; + desc->len2 = 0; + + if (this_is_head) { + sah_Free_Head_Descriptor(head_desc); + this_is_head = 0; +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Head_Descriptor: %p\n", + head_desc); + LOG_KDIAG(Diag_msg); +#endif + } else { + /* free this descriptor */ + sah_Free_Descriptor(desc); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Descriptor: %p\n", desc); + LOG_KDIAG(Diag_msg); +#endif + } + + /* Look at the next Descriptor */ + desc = next_desc; + } +} /* sah_Free_Chained_Descriptors() */ + +/*! +******************************************************************************* +* This walks through a SAHARA link chain and frees everything that is +* not NULL, excluding user-space buffers. +* +* @brief Kernel Link Chain Destructor +* +* @param link A Link pointer from kernel-space. This is in bus address +* space. +* +* @return void +* +*/ +void sah_Free_Chained_Links(sah_Link * link) +{ + sah_Link *next_link = NULL; + + while (link != NULL) { + /* Get a bus pointer to the next Link */ + next_link = link->next; + + /* Zero some fields for security reasons. */ + link->data = NULL; + link->len = 0; + link->ownerid = 0; + + /* Free this Link */ +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Link: %p(->%p)\n", link, link->next); + LOG_KDIAG(Diag_msg); +#endif + sah_Free_Link(link); + + /* Move on to the next Link */ + link = next_link; + } +} + +/*! +******************************************************************************* +* This function runs through a link chain pointed to by a user-space +* address. It makes a temporary kernel-space copy of each link in the +* chain and calls sah_Make_Links() to create a set of kernel-side links +* to replace it. +* +* @brief Kernel Link Chain Copier +* +* @param ptr A link pointer from user-space. +* +* @return sah_Link * - The virtual address of the first link in the +* chain. +* @return NULL - If there was some error. +*/ +sah_Link *sah_Copy_Links(fsl_shw_uco_t * user_ctx, sah_Link * ptr) +{ + sah_Link *head_link = NULL; + sah_Link *new_head_link = NULL; + sah_Link *new_tail_link = NULL; + sah_Link *prev_tail_link = NULL; + sah_Link *user_link = ptr; + sah_Link link_copy; + int link_data_length = 0; + + /* Internal status variable to be used in this function */ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *ret_val = NULL; + + /* This will be set to True when we have finished processing our + * link chain. */ + int drv_if_done = FALSE; + int is_this_the_head = TRUE; + int result; + + /* transfer all links, on this link chain, from user space */ + while (drv_if_done == FALSE) { + /* Copy the current link from user-space. The virtual address, DMA + * address, and vm_info fields of the sah_Link struct are not part + * of the user-space structure. They must be the last elements and + * should not be copied. */ + result = copy_from_user(&link_copy, + user_link, (sizeof(sah_Link) - +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + sizeof(struct page *) - /* vm_info */ +#endif + sizeof(dma_addr_t) - /* dma_addr */ + sizeof(uint32_t) - /* virt_addr */ + sizeof(uint8_t *) - /* original_data */ + sizeof(sah_Link *))); /* original_next */ + + if (result != 0) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("copy_from_user() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; + } + + if (status == FSL_RETURN_OK_S) { + /* This will create new links which can be used to replace tmp_link + * in the chain. This will return a new head and tail link. */ + link_data_length = link_data_length + link_copy.len; + new_head_link = + sah_Make_Links(user_ctx, &link_copy, &new_tail_link); + + if (new_head_link == NULL) { + /* If we ran out of memory or a user pointer was invalid */ + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Make_Links() failed."); +#endif + } else { + if (is_this_the_head == TRUE) { + /* Keep a reference to the head link */ + head_link = new_head_link; + is_this_the_head = FALSE; + } else { + /* Need to update the previous links' next field to point + * to the current link. */ + prev_tail_link->next = + (void *)new_head_link->dma_addr; + prev_tail_link->original_next = + new_head_link; + } + } + } + + if (status == FSL_RETURN_OK_S) { + /* Get to the next link in the chain. */ + user_link = link_copy.next; + prev_tail_link = new_tail_link; + + /* Check if the end of the link chain was reached (TRUE) or if + * there is another linked to this one (FALSE) */ + drv_if_done = (user_link == NULL) ? TRUE : FALSE; + } + } /* end while */ + + if (status != FSL_RETURN_OK_S) { + ret_val = NULL; + /* There could be clean-up to do here because we may have made some + * successful iterations through the while loop and as a result, the + * links created by sah_Make_Links() need to be destroyed. + */ + if (head_link != NULL) { + /* Failed somewhere in the while loop and need to clean-up. */ + sah_Destroy_Links(head_link); + } + } else { + /* Success. Return the head link. */ + ret_val = head_link; + } + + return ret_val; +} /* sah_Copy_Links() */ + +/*! +******************************************************************************* +* This function takes an input link pointed to by a user-space address +* and returns a chain of links that span the physical pages pointed +* to by the input link. +* +* @brief Kernel Link Chain Constructor +* +* @param ptr A link pointer from user-space. +* @param tail The address of a link pointer. This is used to return +* the tail link created by this function. +* +* @return sah_Link * - A virtual address of the first link in the +* chain. +* @return NULL - If there was some error. +* +*/ +sah_Link *sah_Make_Links(fsl_shw_uco_t * user_ctx, + sah_Link * ptr, sah_Link ** tail) +{ + int result = -1; + int page_index = 0; + fsl_shw_return_t status = FSL_RETURN_OK_S; + int is_this_the_head = TRUE; + void *buffer_start = NULL; + sah_Link *link = NULL; + sah_Link *prev_link = NULL; + sah_Link *head_link = NULL; + sah_Link *ret_val = NULL; + int buffer_length = 0; + struct page **local_pages = NULL; + int nr_pages = 0; + int write = (sah_Link_Get_Flags(ptr) & SAH_OUTPUT_LINK) ? WRITE : READ; + + /* need to retrieve stored key? */ + if (ptr->flags & SAH_STORED_KEY_INFO) { + fsl_shw_return_t ret_status; + + /* allocate space for this link */ + link = sah_Alloc_Link(); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Alloc_Link returned %p/%p\n", link, + (void *)link->dma_addr); + LOG_KDIAG(Diag_msg); +#endif + + if (link == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Alloc_Link() failed!"); +#endif + return link; + } else { + uint32_t max_len = 0; /* max slot length */ + + /* get length and physical address of stored key */ + ret_status = system_keystore_get_slot_info(ptr->ownerid, ptr->slot, (uint32_t *) & link->data, /* RED key address */ + &max_len); +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS + ("ret_status==SCC_RET_OK? %s. slot: %i. data: %p" + ". len: %i, key length: %i", + (ret_status == FSL_RETURN_OK_S ? "yes" : "no"), + ptr->slot, link->data, max_len, ptr->len); +#endif + + if ((ret_status == FSL_RETURN_OK_S) && (ptr->len <= max_len)) { + /* finish populating the link */ + link->len = ptr->len; + link->flags = ptr->flags & ~SAH_PREPHYS_DATA; + *tail = link; + } else { +#ifdef DIAG_DRV_IF + if (ret_status == FSL_RETURN_OK_S) { + LOG_KDIAG + ("SCC sah_Link key slot reference is too long"); + } else { + LOG_KDIAG + ("SCC sah_Link slot slot reference is invalid"); + } +#endif + sah_Free_Link(link); + status = FSL_RETURN_INTERNAL_ERROR_S; + return NULL; + } + return link; + } + } else if (ptr->flags & SAH_IN_USER_KEYSTORE) { + +#ifdef FSL_HAVE_SCC2 + + void *kernel_base; + + /* allocate space for this link */ + link = sah_Alloc_Link(); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Alloc_Link returned %p/%p\n", link, + (void *)link->dma_addr); + LOG_KDIAG(Diag_msg); +#endif /* DIAG_MEM */ + + if (link == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Alloc_Link() failed!"); +#endif + return link; + } else { + /* link->data points to the virtual address of the key data, however + * this memory does not need to be locked down. + */ + kernel_base = lookup_user_partition(user_ctx, + (uint32_t) ptr-> + data & PAGE_MASK); + + link->data = (uint8_t *) scc_virt_to_phys(kernel_base + + ((unsigned + long)ptr-> + data & + ~PAGE_MASK)); + + /* Do bounds checking to ensure that the user is not overstepping + * the bounds of their partition. This is a simple implementation + * that assumes the user only owns one partition. It only checks + * to see if the address of the last byte of data steps over a + * page boundary. + */ + if ((kernel_base != NULL) && + ((((uint32_t) link->data + + link->len) >> PAGE_SHIFT) == + ((uint32_t) link->data >> PAGE_SHIFT))) { + /* finish populating the link */ + link->len = ptr->len; + link->flags = ptr->flags & ~SAH_PREPHYS_DATA; + *tail = link; + } else { +#ifdef DIAG_DRV_IF + if (kernel_base != NULL) { + LOG_KDIAG + ("SCC sah_Link key slot reference is too long"); + } else { + LOG_KDIAG + ("SCC sah_Link slot slot reference is invalid"); + } +#endif + sah_Free_Link(link); + status = FSL_RETURN_INTERNAL_ERROR_S; + return NULL; + } + return link; + } + +#else /* FSL_HAVE_SCC2 */ + + return NULL; + +#endif /* FSL_HAVE_SCC2 */ + } + + if (ptr->data == NULL) { + /* The user buffer must not be NULL because map_user_kiobuf() cannot + * handle NULL pointer input. + */ + status = FSL_RETURN_BAD_DATA_LENGTH_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Link data pointer is NULL."); +#endif + } + + if (status == FSL_RETURN_OK_S) { + unsigned long start_page = (unsigned long)ptr->data & PAGE_MASK; + + /* determine number of pages being used for this link */ + nr_pages = (((unsigned long)(ptr->data) & ~PAGE_MASK) + + ptr->len + ~PAGE_MASK) >> PAGE_SHIFT; + + /* ptr contains all the 'user space' information, add the pages + * to it also just so everything is in one place */ + local_pages = + kmalloc(nr_pages * sizeof(struct page *), GFP_KERNEL); + + if (local_pages == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; /* no memory! */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("kmalloc() failed."); +#endif + } else { + /* get the actual pages being used in 'user space' */ + + down_read(¤t->mm->mmap_sem); + result = get_user_pages(current, current->mm, + start_page, nr_pages, + write, 0 /* noforce */ , + local_pages, NULL); + up_read(¤t->mm->mmap_sem); + + if (result < nr_pages) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("get_user_pages() failed."); +#endif + if (result > 0) { + for (page_index = 0; + page_index < result; + page_index++) { + page_cache_release(local_pages + [page_index]); + } + } + status = FSL_RETURN_INTERNAL_ERROR_S; + } + } + } + + /* Now we can walk through the list of pages in the buffer */ + if (status == FSL_RETURN_OK_S) { + +#if defined(FLUSH_SPECIFIC_DATA_ONLY) && !defined(HAS_L2_CACHE) + /* + * Now that pages are wired, clear user data from cache lines. When + * there is just an L1 cache, clean based on user virtual for ARM. + */ + if (write == WRITE) { + os_cache_flush_range(ptr->data, ptr->len); + } else { + os_cache_clean_range(ptr->data, ptr->len); + } +#endif + + for (page_index = 0; page_index < nr_pages; page_index++) { + /* Allocate a new link structure */ + link = sah_Alloc_Link(); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Alloc_Link returned %p/%p\n", link, + (void *)link->dma_addr); + LOG_KDIAG(Diag_msg); +#endif + if (link == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Alloc_Link() failed."); +#endif + status = FSL_RETURN_NO_RESOURCE_S; + + /* need to free the rest of the pages. Destroy_Links will take + * care of the ones already assigned to a link */ + for (; page_index < nr_pages; page_index++) { + page_cache_release(local_pages + [page_index]); + } + break; /* exit 'for page_index' loop */ + } + + if (status == FSL_RETURN_OK_S) { + if (is_this_the_head == TRUE) { + /* keep a reference to the head link */ + head_link = link; + /* remember that we have seen the head link */ + is_this_the_head = FALSE; + } else { + /* If this is not the head link then set the previous + * link's next pointer to point to this link */ + prev_link->original_next = link; + prev_link->next = + (sah_Link *) link->dma_addr; + } + + buffer_start = + page_address(local_pages[page_index]); + + if (page_index == 0) { + /* If this is the first page, there might be an + * offset. We need to increment the address by this offset + * so we don't just get the start of the page. + */ + buffer_start += + (unsigned long) + sah_Link_Get_Data(ptr) + & ~PAGE_MASK; + buffer_length = PAGE_SIZE + - + ((unsigned long) + sah_Link_Get_Data(ptr) + & ~PAGE_MASK); + } else { + buffer_length = PAGE_SIZE; + } + + if (page_index == nr_pages - 1) { + /* if this is the last page, we need to adjust + * the buffer_length to account for the last page being + * partially used. + */ + buffer_length -= + nr_pages * PAGE_SIZE - + sah_Link_Get_Len(ptr) - + ((unsigned long) + sah_Link_Get_Data(ptr) & + ~PAGE_MASK); + } +#if defined(FLUSH_SPECIFIC_DATA_ONLY) && defined(HAS_L2_CACHE) + /* + * When there is an L2 cache, clean based on kernel + * virtual.. + */ + if (write == WRITE) { + os_cache_flush_range(buffer_start, + buffer_length); + } else { + os_cache_clean_range(buffer_start, + buffer_length); + } +#endif + + /* Fill in link information */ + link->len = buffer_length; +#if !defined(HAS_L2_CACHE) + /* use original virtual */ + link->original_data = ptr->data; +#else + /* use kernel virtual */ + link->original_data = buffer_start; +#endif + link->data = (void *)os_pa(buffer_start); + link->flags = ptr->flags & ~SAH_PREPHYS_DATA; + link->vm_info = local_pages[page_index]; + prev_link = link; + +#if defined(NO_OUTPUT_1K_CROSSING) || defined(NO_1K_CROSSING) + if ( +#ifdef NO_OUTPUT_1K_CROSSING + /* Insert extra link if 1k boundary on output pointer + * crossed not at an 8-word boundary */ + (link->flags & SAH_OUTPUT_LINK) && + (((uint32_t) buffer_start % 32) != 0) + && +#endif + ((((uint32_t) buffer_start & 1023) + + buffer_length) > 1024)) { + + /* Shorten current link to 1k boundary */ + link->len = + 1024 - + ((uint32_t) buffer_start % 1024); + + /* Get new link to follow it */ + link = sah_Alloc_Link(); + prev_link->len = + 1024 - + ((uint32_t) buffer_start % 1024); + prev_link->original_next = link; + prev_link->next = + (sah_Link *) link->dma_addr; + buffer_length -= prev_link->len; + buffer_start += prev_link->len; + +#if !defined(HAS_L2_CACHE) + /* use original virtual */ + link->original_data = ptr->data; +#else + /* use kernel virtual */ + link->original_data = buffer_start; +#endif + link->data = + (void *)os_pa(buffer_start); + link->vm_info = prev_link->vm_info; + prev_link->vm_info = NULL; /* delay release */ + link->flags = ptr->flags; + link->len = buffer_length; + prev_link = link; + } /* while link would cross 1K boundary */ +#endif /* 1K_CROSSING */ + } + } /* for each page */ + } + + if (local_pages != NULL) { + kfree(local_pages); + } + + if (status != FSL_RETURN_OK_S) { + /* De-allocated any links created, this routine first looks if + * head_link is NULL */ + sah_Destroy_Links(head_link); + + /* Clean-up of the KIOBUF will occur in the * sah_Copy_Descriptors() + * function. + * Clean-up of the Queue entry must occur in the function called + * sah_Copy_Descriptors(). + */ + } else { + + /* Success. Return the head link. */ + ret_val = head_link; + link->original_next = NULL; + /* return the tail link as well */ + *tail = link; + } + + return ret_val; +} /* sah_Make_Links() */ + +/*! +******************************************************************************* +* This walks through a SAHARA descriptor chain and frees everything +* that is not NULL. Finally it also unmaps all of the physical memory and +* frees the kiobuf_list Queue. +* +* @brief Kernel Descriptor Chain Destructor +* +* @param desc A Descriptor pointer from kernel-space. This should be +* in bus address space. +* +* @return void +* +*/ +void sah_Destroy_Descriptors(sah_Head_Desc * head_desc) +{ + sah_Desc *this_desc = (sah_Desc *) head_desc; + sah_Desc *next_desc = NULL; + int this_is_head = 1; + + /* + * Flush the D-cache. This flush is here because the hardware has finished + * processing this descriptor and probably has changed the contents of + * some linked user buffers as a result. This flush will enable + * user-space applications to see the correct data rather than the + * out-of-date cached version. + */ +#ifndef FLUSH_SPECIFIC_DATA_ONLY + os_flush_cache_all(); +#endif + + head_desc = (sah_Head_Desc *) this_desc->virt_addr; + + while (this_desc != NULL) { + if (this_desc->ptr1 != NULL) { + sah_Destroy_Links(this_desc->original_ptr1 + ? this_desc-> + original_ptr1 : this_desc->ptr1); + } + if (this_desc->ptr2 != NULL) { + sah_Destroy_Links(this_desc->original_ptr2 + ? this_desc-> + original_ptr2 : this_desc->ptr2); + } + + /* Get a bus pointer to the next Descriptor */ + next_desc = (this_desc->original_next + ? this_desc->original_next : this_desc->next); + + /* Zero the header and Length fields for security reasons. */ + this_desc->header = 0; + this_desc->len1 = 0; + this_desc->len2 = 0; + + if (this_is_head) { + sah_Free_Head_Descriptor(head_desc); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Head_Descriptor: %p\n", + head_desc); + LOG_KDIAG(Diag_msg); +#endif + this_is_head = 0; + } else { + /* free this descriptor */ + sah_Free_Descriptor(this_desc); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Descriptor: %p\n", this_desc); + LOG_KDIAG(Diag_msg); +#endif + } + + /* Set up for next round. */ + this_desc = (sah_Desc *) next_desc; + } +} + +/*! +******************************************************************************* +* This walks through a SAHARA link chain and frees everything that is +* not NULL excluding user-space buffers. +* +* @brief Kernel Link Chain Destructor +* +* @param link A Link pointer from kernel-space. +* +* @return void +* +*/ +void sah_Destroy_Links(sah_Link * link) +{ + sah_Link *this_link = link; + sah_Link *next_link = NULL; + + while (this_link != NULL) { + + /* if this link indicates an associated page, process it */ + if (this_link->vm_info != NULL) { + /* since this function is only called from the routine that + * creates a kernel copy of the user space descriptor chain, + * there are no pages to dirty. All that is needed is to release + * the page from cache */ + page_cache_release(this_link->vm_info); + } + + /* Get a bus pointer to the next Link */ + next_link = (this_link->original_next + ? this_link->original_next : this_link->next); + + /* Zero the Pointer and Length fields for security reasons. */ + this_link->data = NULL; + this_link->len = 0; + + /* Free this Link */ + sah_Free_Link(this_link); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Link: %p\n", this_link); + LOG_KDIAG(Diag_msg); +#endif + + /* Look at the next Link */ + this_link = next_link; + } +} + +/*! +******************************************************************************* +* @brief Initialize memory manager/mapper. +* +* In 2.4, this function also allocates a kiovec to be used when mapping user +* data to kernel space +* +* @return 0 for success, OS error code on failure +* +*/ +int sah_Init_Mem_Map(void) +{ + int ret = OS_ERROR_FAIL_S; + + mem_lock = os_lock_alloc_init(); + + /* + * If one of these fails, change the calculation in the #define earlier in + * the file to be the other one. + */ + if (sizeof(sah_Link) > MEM_BLOCK_SIZE) { + os_printk("Sahara Driver: sah_Link structure is too large\n"); + } else if (sizeof(sah_Desc) > MEM_BLOCK_SIZE) { + os_printk("Sahara Driver: sah_Desc structure is too large\n"); + } else { + ret = OS_ERROR_OK_S; + } + +#ifndef SELF_MANAGED_POOL + + big_dma_pool = dma_pool_create("sah_big_blocks", NULL, + sizeof(Mem_Big_Block), sizeof(uint32_t), + PAGE_SIZE); + small_dma_pool = dma_pool_create("sah_small_blocks", NULL, + sizeof(Mem_Block), sizeof(uint32_t), + PAGE_SIZE); +#else + +#endif + return ret; +} + +/*! +******************************************************************************* +* @brief Clean up memory manager/mapper. +* +* In 2.4, this function also frees the kiovec used when mapping user data to +* kernel space. +* +* @return none +* +*/ +void sah_Stop_Mem_Map(void) +{ + os_lock_deallocate(mem_lock); + +#ifndef SELF_MANAGED_POOL + if (big_dma_pool != NULL) { + dma_pool_destroy(big_dma_pool); + } + if (small_dma_pool != NULL) { + dma_pool_destroy(small_dma_pool); + } +#endif +} + +/*! +******************************************************************************* +* Allocate Head descriptor from free pool. +* +* @brief Allocate Head descriptor +* +* @return sah_Head_Desc Free descriptor, NULL if no free descriptors available. +* +*/ +sah_Head_Desc *sah_Alloc_Head_Descriptor(void) +{ + Mem_Big_Block *block; + sah_Head_Desc *desc; + + block = sah_Alloc_Big_Block(); + if (block != NULL) { + /* initialize everything */ + desc = (sah_Head_Desc *) block->data; + + desc->desc.virt_addr = (sah_Desc *) desc; + desc->desc.dma_addr = block->dma_addr; + desc->desc.original_ptr1 = NULL; + desc->desc.original_ptr2 = NULL; + desc->desc.original_next = NULL; + + desc->desc.ptr1 = NULL; + desc->desc.ptr2 = NULL; + desc->desc.next = NULL; + } else { + desc = NULL; + } + + return desc; +} + +/*! +******************************************************************************* +* Allocate descriptor from free pool. +* +* @brief Allocate descriptor +* +* @return sah_Desc Free descriptor, NULL if no free descriptors available. +* +*/ +sah_Desc *sah_Alloc_Descriptor(void) +{ + Mem_Block *block; + sah_Desc *desc; + + block = sah_Alloc_Block(); + if (block != NULL) { + /* initialize everything */ + desc = (sah_Desc *) block->data; + + desc->virt_addr = desc; + desc->dma_addr = block->dma_addr; + desc->original_ptr1 = NULL; + desc->original_ptr2 = NULL; + desc->original_next = NULL; + + desc->ptr1 = NULL; + desc->ptr2 = NULL; + desc->next = NULL; + } else { + desc = NULL; + } + + return (desc); +} + +/*! +******************************************************************************* +* Allocate link from free pool. +* +* @brief Allocate link +* +* @return sah_Link Free link, NULL if no free links available. +* +*/ +sah_Link *sah_Alloc_Link(void) +{ + Mem_Block *block; + sah_Link *link; + + block = sah_Alloc_Block(); + if (block != NULL) { + /* initialize everything */ + link = (sah_Link *) block->data; + + link->virt_addr = link; + link->original_next = NULL; + link->original_data = NULL; + /* information found in allocated block */ + link->dma_addr = block->dma_addr; + + /* Sahara link fields */ + link->len = 0; + link->data = NULL; + link->next = NULL; + + /* driver required fields */ + link->flags = 0; + link->vm_info = NULL; + } else { + link = NULL; + } + + return link; +} + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Add a new page to end of block free pool. This will allocate one page and +* fill the pool with entries, appending to the end. +* +* @brief Add page of blocks to block free pool. +* +* @pre This function must be called with the #mem_lock held. +* +* @param big 0 - make blocks big enough for sah_Desc +* non-zero - make blocks big enough for sah_Head_Desc +* +* @return int TRUE if blocks added succeesfully, FALSE otherwise +* +*/ +int sah_Block_Add_Page(int big) +{ + void *page; + int success; + dma_addr_t dma_addr; + unsigned block_index; + uint32_t dma_offset; + unsigned block_entries = + big ? MEM_BIG_BLOCK_ENTRIES : MEM_BLOCK_ENTRIES; + unsigned block_size = big ? sizeof(Mem_Big_Block) : sizeof(Mem_Block); + void *block; + + /* Allocate page of memory */ +#ifndef USE_COHERENT_MEMORY + page = os_alloc_memory(PAGE_SIZE, GFP_ATOMIC | __GFP_DMA); + dma_addr = os_pa(page); +#else + page = os_alloc_coherent(PAGE_SIZE, &dma_addr, GFP_ATOMIC); +#endif + if (page != NULL) { + /* + * Find the difference between the virtual address and the DMA + * address of the page. This is used later to determine the DMA + * address of each individual block. + */ + dma_offset = page - (void *)dma_addr; + + /* Split page into blocks and add to free pool */ + block = page; + for (block_index = 0; block_index < block_entries; + block_index++) { + if (big) { + register Mem_Big_Block *blockp = block; + blockp->dma_addr = + (uint32_t) (block - dma_offset); + sah_Append_Big_Block(blockp); + } else { + register Mem_Block *blockp = block; + blockp->dma_addr = + (uint32_t) (block - dma_offset); + /* sah_Append_Block must be protected with spin locks. This is + * done in sah_Alloc_Block(), which calls + * sah_Block_Add_Page() */ + sah_Append_Block(blockp); + } + block += block_size; + } + success = TRUE; +#ifdef DIAG_MEM + LOG_KDIAG("Succeeded in allocating new page"); +#endif + } else { + success = FALSE; +#ifdef DIAG_MEM + LOG_KDIAG("Failed in allocating new page"); +#endif + } + + return success; +} +#endif /* SELF_MANAGED_POOL */ + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Allocate block from free pool. A block is large enough to fit either a link +* or descriptor. +* +* @brief Allocate memory block +* +* @return Mem_Block Free block, NULL if no free blocks available. +* +*/ +static Mem_Big_Block *sah_Alloc_Big_Block(void) +{ + Mem_Big_Block *block; + os_lock_context_t lock_flags; + + os_lock_save_context(mem_lock, lock_flags); + + /* If the pool is empty, try to allocate more entries */ + if (big_block_free_head == NULL) { + (void)sah_Block_Add_Page(1); + } + + /* Check that the pool now has some free entries */ + if (big_block_free_head != NULL) { + /* Return the head of the free pool */ + block = big_block_free_head; + + big_block_free_head = big_block_free_head->next; + if (big_block_free_head == NULL) { + /* Allocated last entry in pool */ + big_block_free_tail = NULL; + } + } else { + block = NULL; + } + os_unlock_restore_context(mem_lock, lock_flags); + + return block; +} +#else +/*! +******************************************************************************* +* Allocate block from free pool. A block is large enough to fit either a link +* or descriptor. +* +* @brief Allocate memory block +* +* @return Mem_Block Free block, NULL if no free blocks available. +* +*/ +static Mem_Big_Block *sah_Alloc_Big_Block(void) +{ + dma_addr_t dma_addr; + Mem_Big_Block *block = + dma_pool_alloc(big_dma_pool, GFP_ATOMIC, &dma_addr); + + if (block == NULL) { + } else { + block->dma_addr = dma_addr; + } + + return block; +} +#endif + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Allocate block from free pool. A block is large enough to fit either a link +* or descriptor. +* +* @brief Allocate memory block +* +* @return Mem_Block Free block, NULL if no free blocks available. +* +*/ +/****************************************************************************** +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 31/10/2003 RWK PR52734 - Implement functions to allocate +* descriptors and links. Replace +* consistent_alloc() calls. Initial creation. +* +******************************************************************************/ +static Mem_Block *sah_Alloc_Block(void) +{ + Mem_Block *block; + os_lock_context_t lock_flags; + + os_lock_save_context(mem_lock, lock_flags); + + /* If the pool is empty, try to allocate more entries */ + if (block_free_head == NULL) { + (void)sah_Block_Add_Page(0); + } + + /* Check that the pool now has some free entries */ + if (block_free_head != NULL) { + /* Return the head of the free pool */ + block = block_free_head; + + block_free_head = block_free_head->next; + if (block_free_head == NULL) { + /* Allocated last entry in pool */ + block_free_tail = NULL; + } + } else { + block = NULL; + } + os_unlock_restore_context(mem_lock, lock_flags); + + return block; +} +#else +/*! +******************************************************************************* +* Allocate block from free pool. A block is large enough to fit either a link +* or descriptor. +* +* @brief Allocate memory block +* +* @return Mem_Block Free block, NULL if no free blocks available. +* +*/ +/****************************************************************************** +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 31/10/2003 RWK PR52734 - Implement functions to allocate +* descriptors and links. Replace +* consistent_alloc() calls. Initial creation. +* +******************************************************************************/ +static Mem_Block *sah_Alloc_Block(void) +{ + + dma_addr_t dma_addr; + Mem_Block *block = + dma_pool_alloc(small_dma_pool, GFP_ATOMIC, &dma_addr); + + if (block == NULL) { + } else { + block->dma_addr = dma_addr; + } + + return block; +} +#endif + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Free memory block back to free pool +* +* @brief Free memory block +* +* @param block A block allocated with sah_Alloc_Block(). +* +* @return none +* +*/ +static void sah_Free_Block(Mem_Block * block) +{ + os_lock_context_t lock_flags; + + os_lock_save_context(mem_lock, lock_flags); + sah_Append_Block(block); + os_unlock_restore_context(mem_lock, lock_flags); +} +#else +/*! +******************************************************************************* +* Free memory block back to free pool +* +* @brief Free memory block +* +* @param block A block allocated with sah_Alloc_Block(). +* +* @return none +* +*/ +static void sah_Free_Block(Mem_Block * block) +{ + dma_pool_free(small_dma_pool, block, block->dma_addr); +} +#endif + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Free memory block back to free pool +* +* @brief Free memory block +* +* @param block A block allocated with sah_Alloc_Block(). +* +* @return none +* +*/ +static void sah_Free_Big_Block(Mem_Big_Block * block) +{ + os_lock_context_t lock_flags; + + os_lock_save_context(mem_lock, lock_flags); + sah_Append_Big_Block(block); + os_unlock_restore_context(mem_lock, lock_flags); +} +#else +/*! +******************************************************************************* +* Free memory block back to free pool +* +* @brief Free memory block +* +* @param block A block allocated with sah_Alloc_Block(). +* +* @return none +* +*/ +static void sah_Free_Big_Block(Mem_Big_Block * block) +{ + dma_pool_free(big_dma_pool, block, block->dma_addr); +} +#endif + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Append memory block to end of free pool. +* +* @param block A block entry +* +* @return none +* +* @pre This function must be called with the #mem_lock held. +* +* @brief Append memory block to free pool +*/ +static void sah_Append_Big_Block(Mem_Big_Block * block) +{ + + /* Initialise block */ + block->next = NULL; + + /* Remember that block may be the first in the pool */ + if (big_block_free_tail != NULL) { + big_block_free_tail->next = block; + } else { + /* Pool is empty */ + big_block_free_head = block; + } + + big_block_free_tail = block; +} + +/*! +******************************************************************************* +* Append memory block to end of free pool. +* +* @brief Append memory block to free pool +* +* @param block A block entry +* +* @return none +* +* @pre #mem_lock must be held +* +*/ +static void sah_Append_Block(Mem_Block * block) +{ + + /* Initialise block */ + block->next = NULL; + + /* Remember that block may be the first in the pool */ + if (block_free_tail != NULL) { + block_free_tail->next = block; + } else { + /* Pool is empty */ + block_free_head = block; + } + + block_free_tail = block; +} +#endif /* SELF_MANAGED_POOL */ + +/* End of sah_memory_mapper.c */ diff --git a/drivers/mxc/security/sahara2/sah_queue.c b/drivers/mxc/security/sahara2/sah_queue.c new file mode 100644 index 000000000000..0f3e56e4c254 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_queue.c @@ -0,0 +1,249 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file sah_queue.c +* +* @brief This file provides a FIFO Queue implementation. +* +*/ +/****************************************************************************** +* +* CAUTION: +******************************************************************* +*/ + +/* SAHARA Includes */ +#include <sah_queue_manager.h> +#ifdef DIAG_DRV_QUEUE +#include <diagnostic.h> +#endif + +/****************************************************************************** +* Queue Functions +******************************************************************************/ + +/*! +******************************************************************************* +* This function constructs a new sah_Queue. +* +* @brief sah_Queue Constructor +* +* @return A pointer to a newly allocated sah_Queue. +* @return NULL if allocation of memory failed. +*/ +/****************************************************************************** +* +* CAUTION: This function may sleep in low-memory situations, as it uses +* kmalloc ( ..., GFP_KERNEL). +******************************************************************************/ +sah_Queue *sah_Queue_Construct(void) +{ + sah_Queue *q = (sah_Queue *) os_alloc_memory(sizeof(sah_Queue), + GFP_KERNEL); + + if (q != NULL) { + /* Initialise the queue to an empty state. */ + q->head = NULL; + q->tail = NULL; + q->count = 0; + } +#ifdef DIAG_DRV_QUEUE + else { + LOG_KDIAG("kmalloc() failed."); + } +#endif + + return q; +} + +/*! +******************************************************************************* +* This function destroys a sah_Queue. +* +* @brief sah_Queue Destructor +* +* @param q A pointer to a sah_Queue. +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: This function does not free any queue entries. +* +******************************************************************************/ +void sah_Queue_Destroy(sah_Queue * q) +{ +#ifdef DIAG_DRV_QUEUE + if (q == NULL) { + LOG_KDIAG("Trying to kfree() a NULL pointer."); + } else { + if (q->count != 0) { + LOG_KDIAG + ("Trying to destroy a queue that is not empty."); + } + } +#endif + + if (q != NULL) { + os_free_memory(q); + q = NULL; + } +} + +/*! +******************************************************************************* +* This function appends a sah_Head_Desc to the tail of a sah_Queue. +* +* @brief Appends a sah_Head_Desc to a sah_Queue. +* +* @param q A pointer to a sah_Queue to append to. +* @param entry A pointer to a sah_Head_Desc to append. +* +* @pre The #desc_queue_lock must be held before calling this function. +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: NONE +******************************************************************************/ +void sah_Queue_Append_Entry(sah_Queue * q, sah_Head_Desc * entry) +{ + sah_Head_Desc *tail_entry = NULL; + + if ((q == NULL) || (entry == NULL)) { +#ifdef DIAG_DRV_QUEUE + LOG_KDIAG("Null pointer input."); +#endif + return; + } + + if (q->count == 0) { + /* The queue is empty */ + q->head = entry; + q->tail = entry; + entry->next = NULL; + entry->prev = NULL; + } else { + /* The queue is not empty */ + tail_entry = q->tail; + tail_entry->next = entry; + entry->next = NULL; + entry->prev = tail_entry; + q->tail = entry; + } + q->count++; +} + +/*! +******************************************************************************* +* This function a removes a sah_Head_Desc from the head of a sah_Queue. +* +* @brief Removes a sah_Head_Desc from a the head of a sah_Queue. +* +* @param q A pointer to a sah_Queue to remove from. +* +* @pre The #desc_queue_lock must be held before calling this function. +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: This does not kfree() the entry. +******************************************************************************/ +void sah_Queue_Remove_Entry(sah_Queue * q) +{ + sah_Queue_Remove_Any_Entry(q, q->head); +} + +/*! +******************************************************************************* +* This function a removes a sah_Head_Desc from anywhere in a sah_Queue. +* +* @brief Removes a sah_Head_Desc from anywhere in a sah_Queue. +* +* @param qq A pointer to a sah_Queue to remove from. +* @param entry A pointer to a sah_Head_Desc to remove. +* +* @pre The #desc_queue_lock must be held before calling this function. +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: This does not kfree() the entry. Does not check to see if the entry +* actually belongs to the queue. +******************************************************************************/ +void sah_Queue_Remove_Any_Entry(sah_Queue * q, sah_Head_Desc * entry) +{ + sah_Head_Desc *prev_entry = NULL; + sah_Head_Desc *next_entry = NULL; + + if ((q == NULL) || (entry == NULL)) { +#if defined DIAG_DRV_QUEUE && defined DIAG_DURING_INTERRUPT + LOG_KDIAG("Null pointer input."); +#endif + return; + } + + if (q->count == 1) { + /* If q is the only entry in the queue. */ + q->tail = NULL; + q->head = NULL; + q->count = 0; + } else if (q->count > 1) { + /* There are 2 or more entries in the queue. */ + +#if defined DIAG_DRV_QUEUE && defined DIAG_DURING_INTERRUPT + if ((entry->next == NULL) && (entry->prev == NULL)) { + LOG_KDIAG + ("Queue is not empty yet both next and prev pointers" + " are NULL"); + } +#endif + + if (entry->next == NULL) { + /* If this is the end of the queue */ + prev_entry = entry->prev; + prev_entry->next = NULL; + q->tail = prev_entry; + } else if (entry->prev == NULL) { + /* If this is the head of the queue */ + next_entry = entry->next; + next_entry->prev = NULL; + q->head = next_entry; + } else { + /* If this is somewhere in the middle of the queue */ + prev_entry = entry->prev; + next_entry = entry->next; + prev_entry->next = next_entry; + next_entry->prev = prev_entry; + } + q->count--; + } + /* + * Otherwise we are removing an entry from an empty queue. + * Don't do anything in the product code + */ +#if defined DIAG_DRV_QUEUE && defined DIAG_DURING_INTERRUPT + else { + LOG_KDIAG("Trying to remove an entry from an empty queue."); + } +#endif + + entry->next = NULL; + entry->prev = NULL; +} + +/* End of sah_queue.c */ diff --git a/drivers/mxc/security/sahara2/sah_queue_manager.c b/drivers/mxc/security/sahara2/sah_queue_manager.c new file mode 100644 index 000000000000..1602c7043a13 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_queue_manager.c @@ -0,0 +1,1050 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file sah_queue_manager.c + * + * @brief This file provides a Queue Manager implementation. + * + * The Queue Manager manages additions and removal from the queue and updates + * the status of queue entries. It also calls sah_HW_* functions to interract + * with the hardware. +*/ + +#include "portable_os.h" + +/* SAHARA Includes */ +#include <sah_driver_common.h> +#include <sah_queue_manager.h> +#include <sah_status_manager.h> +#include <sah_hardware_interface.h> +#if defined(DIAG_DRV_QUEUE) || defined(DIAG_DRV_STATUS) +#include <diagnostic.h> +#endif +#include <sah_memory_mapper.h> + +#ifdef DIAG_DRV_STATUS + +#define FSL_INVALID_RETURN 13 +#define MAX_RETURN_STRING_LEN 22 +#endif + +/* Defines for parsing value from Error Status register */ +#define SAH_STATUS_MASK 0x07 +#define SAH_ERROR_MASK 0x0F +#define SAH_CHA_ERR_SOURCE_MASK 0x07 +#define SAH_CHA_ERR_STATUS_MASK 0x0FFF +#define SAH_DMA_ERR_STATUS_MASK 0x0F +#define SAH_DMA_ERR_SIZE_MASK 0x03 +#define SAH_DMA_ERR_DIR_MASK 0x01 + +#define SHA_ERROR_STATUS_CONTINUE 0xFFFFFFFF +#define SHA_CHA_ERROR_STATUS_DONE 0xFFFFFFFF + +/* this maps the error status register's error source 4 bit field to the API + * return values. A 0xFFFFFFFF indicates additional fields must be checked to + * determine an appropriate return value */ +static sah_Execute_Error sah_Execute_Error_Array[] = { + FSL_RETURN_ERROR_S, /* SAH_ERR_NONE */ + FSL_RETURN_BAD_FLAG_S, /* SAH_ERR_HEADER */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_DESC_LENGTH */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_DESC_POINTER */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_LINK_LENGTH */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_LINK_POINTER */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_ERR_INPUT_BUFFER */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_ERR_OUTPUT_BUFFER */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_OUTPUT_BUFFER_STARVATION */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_ERR_INTERNAL_STATE */ + FSL_RETURN_ERROR_S, /* SAH_ERR_GENERAL_DESCRIPTOR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_ERR_RESERVED_FIELDS */ + FSL_RETURN_MEMORY_ERROR_S, /* SAH_ERR_DESCRIPTOR_ADDRESS */ + FSL_RETURN_MEMORY_ERROR_S, /* SAH_ERR_LINK_ADDRESS */ + SHA_ERROR_STATUS_CONTINUE, /* SAH_ERR_CHA */ + SHA_ERROR_STATUS_CONTINUE /* SAH_ERR_DMA */ +}; + +static sah_DMA_Error_Status sah_DMA_Error_Status_Array[] = { + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_NO_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_AHB_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_IP_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_PARITY_ERR */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_DMA_BOUNDRY_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_BUSY_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_RESERVED_ERR */ + FSL_RETURN_INTERNAL_ERROR_S /* SAH_DMA_INT_ERR */ +}; + +static sah_CHA_Error_Status sah_CHA_Error_Status_Array[] = { + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_CHA_NO_ERR */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_CHA_IP_BUF */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_CHA_ADD_ERR */ + FSL_RETURN_BAD_MODE_S, /* SAH_CHA_MODE_ERR */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_CHA_DATA_SIZE_ERR */ + FSL_RETURN_BAD_KEY_LENGTH_S, /* SAH_CHA_KEY_SIZE_ERR */ + FSL_RETURN_BAD_MODE_S, /* SAH_CHA_PROC_ERR */ + FSL_RETURN_ERROR_S, /* SAH_CHA_CTX_READ_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_CHA_INTERNAL_HW_ERR */ + FSL_RETURN_MEMORY_ERROR_S, /* SAH_CHA_IP_BUFF_ERR */ + FSL_RETURN_MEMORY_ERROR_S, /* SAH_CHA_OP_BUFF_ERR */ + FSL_RETURN_BAD_KEY_PARITY_S, /* SAH_CHA_DES_KEY_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_CHA_RES */ +}; + +#ifdef DIAG_DRV_STATUS + +char sah_return_text[FSL_INVALID_RETURN][MAX_RETURN_STRING_LEN] = { + "No error", /* FSL_RETURN_OK_S */ + "Error", /* FSL_RETURN_ERROR_S */ + "No resource", /* FSL_RETURN_NO_RESOURCE_S */ + "Bad algorithm", /* FSL_RETURN_BAD_ALGORITHM_S */ + "Bad mode", /* FSL_RETURN_BAD_MODE_S */ + "Bad flag", /* FSL_RETURN_BAD_FLAG_S */ + "Bad key length", /* FSL_RETURN_BAD_KEY_LENGTH_S */ + "Bad key parity", /* FSL_RETURN_BAD_KEY_PARITY_S */ + "Bad data length", /* FSL_RETURN_BAD_DATA_LENGTH_S */ + "Authentication failed", /* FSL_RETURN_AUTH_FAILED_S */ + "Memory error", /* FSL_RETURN_MEMORY_ERROR_S */ + "Internal error", /* FSL_RETURN_INTERNAL_ERROR_S */ + "unknown value", /* default */ +}; + +#endif /* DIAG_DRV_STATUS */ + +/*! + * This lock must be held while performing any queuing or unqueuing functions, + * including reading the first pointer on the queue. It also protects reading + * and writing the Sahara DAR register. It must be held during a read-write + * operation on the DAR so that the 'test-and-set' is atomic. + */ +os_lock_t desc_queue_lock; + +/*! This is the main queue for the driver. This is shared between all threads + * and is not protected by mutexes since the kernel is non-preemptable. */ +sah_Queue *main_queue = NULL; + +/* Internal Prototypes */ +sah_Head_Desc *sah_Find_With_State(sah_Queue_Status state); + +#ifdef DIAG_DRV_STATUS +void sah_Log_Error(uint32_t descriptor, uint32_t error, uint32_t fault_address); +#endif + +extern wait_queue_head_t *int_queue; + +/*! + * This function initialises the Queue Manager + * + * @brief Initialise the Queue Manager + * + * @return FSL_RETURN_OK_S on success; FSL_RETURN_MEMORY_ERROR_S if not + */ +fsl_shw_return_t sah_Queue_Manager_Init(void) +{ + fsl_shw_return_t ret_val = FSL_RETURN_OK_S; + + desc_queue_lock = os_lock_alloc_init(); + + if (main_queue == NULL) { + /* Construct the main queue. */ + main_queue = sah_Queue_Construct(); + + if (main_queue == NULL) { + ret_val = FSL_RETURN_MEMORY_ERROR_S; + } + } else { +#ifdef DIAG_DRV_QUEUE + LOG_KDIAG + ("Trying to initialise the queue manager more than once."); +#endif + } + + return ret_val; +} + +/*! + * This function closes the Queue Manager + * + * @brief Close the Queue Manager + * + * @return void + */ +void sah_Queue_Manager_Close(void) +{ +#ifdef DIAG_DRV_QUEUE + if (main_queue && main_queue->count != 0) { + LOG_KDIAG + ("Trying to close the main queue when it is not empty."); + } +#endif + + if (main_queue) { + /* There is no error checking here because there is no way to handle + it. */ + sah_Queue_Destroy(main_queue); + main_queue = NULL; + } +} + +/*! + * Count the number of entries on the Queue Manager's queue + * + * @param ignore_state If non-zero, the @a state parameter is ignored. + * If zero, only entries matching @a state are counted. + * @param state State of entry to match for counting. + * + * @return Number of entries which matched criteria + */ +int sah_Queue_Manager_Count_Entries(int ignore_state, sah_Queue_Status state) +{ + int count = 0; + sah_Head_Desc *current_entry; + + /* Start at the head */ + current_entry = main_queue->head; + while (current_entry != NULL) { + if (ignore_state || (current_entry->status == state)) { + count++; + } + /* Jump to the next entry. */ + current_entry = current_entry->next; + } + + return count; +} + +/*! + * This function removes an entry from the Queue Manager's queue. The entry to + * be removed can be anywhere in the queue. + * + * @brief Remove an entry from the Queue Manager's queue. + * + * @param entry A pointer to a sah_Head_Desc to remove from the Queue + * Manager's queue. + * + * @pre The #desc_queue_lock must be held before calling this function. + * + * @return void + */ +void sah_Queue_Manager_Remove_Entry(sah_Head_Desc * entry) +{ + if (entry == NULL) { +#ifdef DIAG_DRV_QUEUE + LOG_KDIAG("NULL pointer input."); +#endif + } else { + sah_Queue_Remove_Any_Entry(main_queue, entry); + } +} + +/*! + * This function appends an entry to the Queue Managers queue. It primes SAHARA + * if this entry is the first PENDING entry in the Queue Manager's Queue. + * + * @brief Appends an entry to the Queue Manager's queue. + * + * @param entry A pointer to a sah_Head_Desc to append to the Queue + * Manager's queue. + * + * @pre The #desc_queue_lock may not may be held when calling this function. + * + * @return void + */ +void sah_Queue_Manager_Append_Entry(sah_Head_Desc * entry) +{ + sah_Head_Desc *current_entry; + os_lock_context_t int_flags; + +#ifdef DIAG_DRV_QUEUE + if (entry == NULL) { + LOG_KDIAG("NULL pointer input."); + } +#endif + entry->status = SAH_STATE_PENDING; + os_lock_save_context(desc_queue_lock, int_flags); + sah_Queue_Append_Entry(main_queue, entry); + + /* Prime SAHARA if the operation that was just appended is the only PENDING + * operation in the queue. + */ + current_entry = sah_Find_With_State(SAH_STATE_PENDING); + if (current_entry != NULL) { + if (current_entry == entry) { + sah_Queue_Manager_Prime(entry); + } + } + + os_unlock_restore_context(desc_queue_lock, int_flags); +} + +/*! + * This function marks all entries in the Queue Manager's queue with state + * SAH_STATE_RESET. + * + * @brief Mark all entries with state SAH_STATE_RESET + * + * @return void + * + * @note This feature needs re-visiting + */ +void sah_Queue_Manager_Reset_Entries(void) +{ + sah_Head_Desc *current_entry = NULL; + + /* Start at the head */ + current_entry = main_queue->head; + + while (current_entry != NULL) { + /* Set the state. */ + current_entry->status = SAH_STATE_RESET; + /* Jump to the next entry. */ + current_entry = current_entry->next; + } +} + +/*! + * This function primes SAHARA for the first time or after the queue becomes + * empty. Queue lock must have been set by the caller of this routine. + * + * @brief Prime SAHARA. + * + * @param entry A pointer to a sah_Head_Desc to Prime SAHARA with. + * + * @return void + */ +void sah_Queue_Manager_Prime(sah_Head_Desc * entry) +{ +#ifdef DIAG_DRV_QUEUE + LOG_KDIAG("Priming SAHARA"); + if (entry == NULL) { + LOG_KDIAG("Trying to prime SAHARA with a NULL entry pointer."); + } +#endif + +#ifndef SUBMIT_MULTIPLE_DARS + /* BUG FIX: state machine can transition from Done1 Busy2 directly + * to Idle. To fix that problem, only one DAR is being allowed on + * SAHARA at a time */ + if (sah_Find_With_State(SAH_STATE_ON_SAHARA) != NULL) { + return; + } +#endif + +#ifdef SAHARA_POWER_MANAGEMENT + /* check that dynamic power management is not asserted */ + if (!sah_dpm_flag) { +#endif + + /* Enable the SAHARA Clocks */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA : Enabling the IPG and AHB clocks\n") +#endif /*DIAG_DRV_IF */ + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_enable(SAHARA2_CLK); +#else + { + struct clk *clk = clk_get(NULL, "sahara_clk"); + if (clk != ERR_PTR(ENOENT)) + clk_enable(clk); + clk_put(clk); + } +#endif + + /* Make sure nothing is in the DAR */ + if (sah_HW_Read_DAR() == 0) { +#if defined(DIAG_DRV_IF) + sah_Dump_Chain(&entry->desc, entry->desc.dma_addr); +#endif /* DIAG_DRV_IF */ + + sah_HW_Write_DAR((entry->desc.dma_addr)); + entry->status = SAH_STATE_ON_SAHARA; + } +#ifdef DIAG_DRV_QUEUE + else { + LOG_KDIAG("DAR should be empty when Priming SAHARA"); + } +#endif +#ifdef SAHARA_POWER_MANAGEMENT + } +#endif +} + +#ifndef SAHARA_POLL_MODE + +/*! + * Reset SAHARA, then load the next descriptor on it, if one exists + */ +void sah_reset_sahara_request(void) +{ + sah_Head_Desc *desc; + os_lock_context_t lock_flags; + +#ifdef DIAG_DRV_STATUS + LOG_KDIAG("Sahara required reset from tasklet, replace chip"); +#endif + sah_HW_Reset(); + + /* Now stick in a waiting request */ + os_lock_save_context(desc_queue_lock, lock_flags); + if ((desc = sah_Find_With_State(SAH_STATE_PENDING))) { + sah_Queue_Manager_Prime(desc); + } + os_unlock_restore_context(desc_queue_lock, lock_flags); +} + +/*! + * Post-process a descriptor chain after the hardware has finished with it. + * + * The status of the descriptor could also be checked. (for FATAL or IGNORED). + * + * @param desc_head The finished chain + * @param error A boolean to mark whether hardware reported error + * + * @pre The #desc_queue_lock may not be held when calling this function. + */ +void sah_process_finished_request(sah_Head_Desc * desc_head, unsigned error) +{ + os_lock_context_t lock_flags; + + if (!error) { + desc_head->result = FSL_RETURN_OK_S; + } else if (desc_head->error_status == -1) { + /* Disaster! Sahara has faulted */ + desc_head->result = FSL_RETURN_ERROR_S; + } else { + /* translate from SAHARA error status to fsl_shw return values */ + desc_head->result = + sah_convert_error_status(desc_head->error_status); +#ifdef DIAG_DRV_STATUS + sah_Log_Error(desc_head->current_dar, desc_head->error_status, + desc_head->fault_address); +#endif + } + + /* Show that the request has been processd */ + desc_head->status = error ? SAH_STATE_FAILED : SAH_STATE_COMPLETE; + + if (desc_head->uco_flags & FSL_UCO_BLOCKING_MODE) { + + /* Wake up all processes on Sahara queue */ + wake_up_interruptible(int_queue); + + } else { + os_lock_save_context(desc_queue_lock, lock_flags); + sah_Queue_Append_Entry(&desc_head->user_info->result_pool, + desc_head); + os_unlock_restore_context(desc_queue_lock, lock_flags); + + /* perform callback */ + if (desc_head->uco_flags & FSL_UCO_CALLBACK_MODE) { + desc_head->user_info->callback(desc_head->user_info); + } + } +} /* sah_process_finished_request */ + +/*! Called from bottom half. + * + * @pre The #desc_queue_lock may not be held when calling this function. + */ +void sah_postprocess_queue(unsigned long reset_flag) +{ + + /* if SAHARA needs to be reset, do it here. This starts a descriptor chain + * if one is ready also */ + if (reset_flag) { + sah_reset_sahara_request(); + } + + /* now handle the descriptor chain(s) that has/have completed */ + do { + sah_Head_Desc *first_entry; + os_lock_context_t lock_flags; + + os_lock_save_context(desc_queue_lock, lock_flags); + + first_entry = main_queue->head; + if ((first_entry != NULL) && + (first_entry->status == SAH_STATE_OFF_SAHARA)) { + sah_Queue_Remove_Entry(main_queue); + os_unlock_restore_context(desc_queue_lock, lock_flags); + + sah_process_finished_request(first_entry, + (first_entry-> + error_status != 0)); + } else { + os_unlock_restore_context(desc_queue_lock, lock_flags); + break; + } + } while (1); + + return; +} + +#endif /* ifndef SAHARA_POLL_MODE */ + +/*! + * This is a helper function for Queue Manager. This function finds the first + * entry in the Queue Manager's queue whose state matches the given input + * state. This function starts at the head of the queue and works towards the + * tail. If a matching entry was found, the address of the entry is returned. + * + * @brief Handle the IDLE state. + * + * @param state A sah_Queue_Status value. + * + * @pre The #desc_queue_lock must be held before calling this function. + * + * @return A pointer to a sah_Head_Desc that matches the given state. + * @return NULL otherwise. + */ +sah_Head_Desc *sah_Find_With_State(sah_Queue_Status state) +{ + sah_Head_Desc *current_entry = NULL; + sah_Head_Desc *ret_val = NULL; + int done_looping = FALSE; + + /* Start at the head */ + current_entry = main_queue->head; + + while ((current_entry != NULL) && (done_looping == FALSE)) { + if (current_entry->status == state) { + done_looping = TRUE; + ret_val = current_entry; + } + /* Jump to the next entry. */ + current_entry = current_entry->next; + } + + return ret_val; +} /* sah_postprocess_queue */ + +/*! + * Process the value from the Sahara error status register and convert it into + * an FSL SHW API error code. + * + * Warning, this routine must only be called if an error exists. + * + * @param error_status The value from the error status register. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_convert_error_status(uint32_t error_status) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; /* catchall */ + uint8_t error_source; + uint8_t DMA_error_status; + uint8_t DMA_error_size; + + /* get the error source from the error status register */ + error_source = error_status & SAH_ERROR_MASK; + + /* array size is maximum allowed by mask, so no boundary checking is + * needed here */ + ret = sah_Execute_Error_Array[error_source]; + + /* is this one that needs additional fields checked to determine the + * error condition? */ + if (ret == SHA_ERROR_STATUS_CONTINUE) { + /* check the DMA fields */ + if (error_source == SAH_ERR_DMA) { + /* get the DMA transfer error size. If this indicates that no + * error was detected, something is seriously wrong */ + DMA_error_size = + (error_status >> 9) & SAH_DMA_ERR_SIZE_MASK; + if (DMA_error_size == SAH_DMA_NO_ERR) { + ret = FSL_RETURN_INTERNAL_ERROR_S; + } else { + /* get DMA error status */ + DMA_error_status = (error_status >> 12) & + SAH_DMA_ERR_STATUS_MASK; + + /* the DMA error bits cover all the even numbers. By dividing + * by 2 it can be used as an index into the error array */ + ret = + sah_DMA_Error_Status_Array[DMA_error_status + >> 1]; + } + } else { /* not SAH_ERR_DMA, so must be SAH_ERR_CHA */ + uint16_t CHA_error_status; + uint8_t CHA_error_source; + + /* get CHA Error Source. If this indicates that no error was + * detected, something is seriously wrong */ + CHA_error_source = + (error_status >> 28) & SAH_CHA_ERR_SOURCE_MASK; + if (CHA_error_source == SAH_CHA_NO_ERROR) { + ret = FSL_RETURN_INTERNAL_ERROR_S; + } else { + uint32_t mask = 1; + uint32_t count = 0; + + /* get CHA Error Status */ + CHA_error_status = (error_status >> 16) & + SAH_CHA_ERR_STATUS_MASK; + + /* If more than one bit is set (which shouldn't happen), only + * the first will be captured */ + if (CHA_error_status != 0) { + count = 1; + while (CHA_error_status != mask) { + ++count; + mask <<= 1; + } + } + + ret = sah_CHA_Error_Status_Array[count]; + } + } + } + + return ret; +} + +fsl_shw_return_t sah_convert_op_status(uint32_t op_status) +{ + unsigned op_source = (op_status >> 28) & 0x7; + unsigned op_detail = op_status & 0x3f; + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + switch (op_source) { + case 1: /* SKHA */ + /* Can't this have "ICV" error from CCM ?? */ + break; + case 2: /* MDHA */ + if (op_detail == 1) { + ret = FSL_RETURN_AUTH_FAILED_S; + } + break; + case 3: /* RNGA */ + /* Self-test and Compare errors... what to do? */ + break; + case 4: /* PKHA */ + switch (op_detail) { + case 0x01: + ret = FSL_RETURN_PRIME_S; + break; + case 0x02: + ret = FSL_RETURN_NOT_PRIME_S; + break; + case 0x04: + ret = FSL_RETURN_POINT_AT_INFINITY_S; + break; + case 0x08: + ret = FSL_RETURN_POINT_NOT_AT_INFINITY_S; + break; + case 0x10: + ret = FSL_RETURN_GCD_IS_ONE_S; + break; + case 0x20: + ret = FSL_RETURN_GCD_IS_NOT_ONE_S; + break; + default: + break; + } + break; + default: + break; + } + return ret; +} + +#ifdef DIAG_DRV_STATUS + +/*! + * This function logs the diagnostic information for the given error and + * descriptor address. Only used for diagnostic purposes. + * + * @brief (debug only) Log a description of hardware-detected error. + * + * @param descriptor The descriptor address that caused the error + * @param error The SAHARA error code + * @param fault_address Value from the Fault address register + * + * @return void + */ +void sah_Log_Error(uint32_t descriptor, uint32_t error, uint32_t fault_address) +{ + char *source_text; /* verbose error source from register */ + char *address; /* string buffer for descriptor address */ + char *error_log; /* the complete logging message */ + char *cha_log = NULL; /* string buffer for descriptor address */ + char *dma_log = NULL; /* string buffer for descriptor address */ + + uint16_t cha_error = 0; + uint16_t dma_error = 0; + + uint8_t error_source; + sah_Execute_Error return_code; + + /* log error code and descriptor address */ + error_source = error & SAH_ERROR_MASK; + return_code = sah_Execute_Error_Array[error_source]; + + source_text = os_alloc_memory(64, GFP_KERNEL); + + switch (error_source) { + case SAH_ERR_HEADER: + sprintf(source_text, "%s", "Header is not valid"); + break; + + case SAH_ERR_DESC_LENGTH: + sprintf(source_text, "%s", + "Descriptor length not equal to sum of link lengths"); + break; + + case SAH_ERR_DESC_POINTER: + sprintf(source_text, "%s", "Length or pointer " + "field is zero while the other is non-zero"); + break; + + case SAH_ERR_LINK_LENGTH: + /* note that the Sahara Block Guide 2.7 has an invalid explaination + * of this. It only happens when a link length is zero */ + sprintf(source_text, "%s", "A data length is a link is zero"); + break; + + case SAH_ERR_LINK_POINTER: + sprintf(source_text, "%s", + "The data pointer in a link is zero"); + break; + + case SAH_ERR_INPUT_BUFFER: + sprintf(source_text, "%s", "Input Buffer reported an overflow"); + break; + + case SAH_ERR_OUTPUT_BUFFER: + sprintf(source_text, "%s", + "Output Buffer reported an underflow"); + break; + + case SAH_ERR_OUTPUT_BUFFER_STARVATION: + sprintf(source_text, "%s", "Incorrect data in output " + "buffer after CHA has signalled 'done'"); + break; + + case SAH_ERR_INTERNAL_STATE: + sprintf(source_text, "%s", "Internal Hardware Failure"); + break; + + case SAH_ERR_GENERAL_DESCRIPTOR: + sprintf(source_text, "%s", + "Current Descriptor was not legal, but cause is unknown"); + break; + + case SAH_ERR_RESERVED_FIELDS: + sprintf(source_text, "%s", + "Reserved pointer field is non-zero"); + break; + + case SAH_ERR_DESCRIPTOR_ADDRESS: + sprintf(source_text, "%s", + "Descriptor address not word aligned"); + break; + + case SAH_ERR_LINK_ADDRESS: + sprintf(source_text, "%s", "Link address not word aligned"); + break; + + case SAH_ERR_CHA: + sprintf(source_text, "%s", "CHA Error"); + { + char *cha_module = os_alloc_memory(5, GFP_KERNEL); + char *cha_text = os_alloc_memory(45, GFP_KERNEL); + + cha_error = (error >> 28) & SAH_CHA_ERR_SOURCE_MASK; + + switch (cha_error) { + case SAH_CHA_SKHA_ERROR: + sprintf(cha_module, "%s", "SKHA"); + break; + + case SAH_CHA_MDHA_ERROR: + sprintf(cha_module, "%s", "MDHA"); + break; + + case SAH_CHA_RNG_ERROR: + sprintf(cha_module, "%s", "RNG "); + break; + + case SAH_CHA_PKHA_ERROR: + sprintf(cha_module, "%s", "PKHA"); + break; + + case SAH_CHA_NO_ERROR: + /* can't happen */ + /* no break */ + default: + sprintf(cha_module, "%s", "????"); + break; + } + + cha_error = (error >> 16) & SAH_CHA_ERR_STATUS_MASK; + + /* Log CHA Error Status */ + switch (cha_error) { + case SAH_CHA_IP_BUF: + sprintf(cha_text, "%s", + "Non-empty input buffer when done"); + break; + + case SAH_CHA_ADD_ERR: + sprintf(cha_text, "%s", "Illegal address"); + break; + + case SAH_CHA_MODE_ERR: + sprintf(cha_text, "%s", "Illegal mode"); + break; + + case SAH_CHA_DATA_SIZE_ERR: + sprintf(cha_text, "%s", "Illegal data size"); + break; + + case SAH_CHA_KEY_SIZE_ERR: + sprintf(cha_text, "%s", "Illegal key size"); + break; + + case SAH_CHA_PROC_ERR: + sprintf(cha_text, "%s", + "Mode/Context/Key written during processing"); + break; + + case SAH_CHA_CTX_READ_ERR: + sprintf(cha_text, "%s", + "Context read during processing"); + break; + + case SAH_CHA_INTERNAL_HW_ERR: + sprintf(cha_text, "%s", "Internal hardware"); + break; + + case SAH_CHA_IP_BUFF_ERR: + sprintf(cha_text, "%s", + "Input buffer not enabled or underflow"); + break; + + case SAH_CHA_OP_BUFF_ERR: + sprintf(cha_text, "%s", + "Output buffer not enabled or overflow"); + break; + + case SAH_CHA_DES_KEY_ERR: + sprintf(cha_text, "%s", "DES key parity error"); + break; + + case SAH_CHA_RES: + sprintf(cha_text, "%s", "Reserved"); + break; + + case SAH_CHA_NO_ERR: + /* can't happen */ + /* no break */ + default: + sprintf(cha_text, "%s", "Unknown error"); + break; + } + + cha_log = os_alloc_memory(90, GFP_KERNEL); + sprintf(cha_log, + " Module %s encountered the error: %s.", + cha_module, cha_text); + + os_free_memory(cha_module); + os_free_memory(cha_text); + + { + uint32_t mask = 1; + uint32_t count = 0; + + if (cha_error != 0) { + count = 1; + while (cha_error != mask) { + ++count; + mask <<= 1; + } + } + + return_code = sah_CHA_Error_Status_Array[count]; + } + cha_error = 1; + } + break; + + case SAH_ERR_DMA: + sprintf(source_text, "%s", "DMA Error"); + { + char *dma_direction = os_alloc_memory(6, GFP_KERNEL); + char *dma_size = os_alloc_memory(14, GFP_KERNEL); + char *dma_text = os_alloc_memory(250, GFP_KERNEL); + + if ((dma_direction == NULL) || (dma_size == NULL) || + (dma_text == NULL)) { + LOG_KDIAG + ("No memory allocated for DMA debug messages\n"); + } + + /* log DMA error direction */ + sprintf(dma_direction, "%s", + (((error >> 8) & SAH_DMA_ERR_DIR_MASK) == 1) ? + "read" : "write"); + + /* log the size of the DMA transfer error */ + dma_error = (error >> 9) & SAH_DMA_ERR_SIZE_MASK; + switch (dma_error) { + case SAH_DMA_SIZE_BYTE: + sprintf(dma_size, "%s", "byte"); + break; + + case SAH_DMA_SIZE_HALF_WORD: + sprintf(dma_size, "%s", "half-word"); + break; + + case SAH_DMA_SIZE_WORD: + sprintf(dma_size, "%s", "word"); + break; + + case SAH_DMA_SIZE_RES: + sprintf(dma_size, "%s", "reserved size"); + break; + + default: + sprintf(dma_size, "%s", "unknown size"); + break; + } + + /* log DMA error status */ + dma_error = (error >> 12) & SAH_DMA_ERR_STATUS_MASK; + switch (dma_error) { + case SAH_DMA_NO_ERR: + sprintf(dma_text, "%s", "No DMA Error Code"); + break; + + case SAH_DMA_AHB_ERR: + sprintf(dma_text, "%s", + "AHB terminated a bus cycle with an error"); + break; + + case SAH_DMA_IP_ERR: + sprintf(dma_text, "%s", + "Internal IP bus cycle was terminated with an " + "error termination. This would likely be " + "caused by a descriptor length being too " + "large, and thus accessing an illegal " + "internal address. Verify the length field " + "of the current descriptor"); + break; + + case SAH_DMA_PARITY_ERR: + sprintf(dma_text, "%s", + "Parity error detected on DMA command from " + "Descriptor Decoder. Cause is likely to be " + "internal hardware fault"); + break; + + case SAH_DMA_BOUNDRY_ERR: + sprintf(dma_text, "%s", + "DMA was requested to cross a 256 byte " + "internal address boundary. Cause is likely a " + "descriptor length being too large, thus " + "accessing two different internal hardware " + "blocks"); + break; + + case SAH_DMA_BUSY_ERR: + sprintf(dma_text, "%s", + "Descriptor Decoder has made a DMA request " + "while the DMA controller is busy. Cause is " + "likely due to hardware fault"); + break; + + case SAH_DMA_RESERVED_ERR: + sprintf(dma_text, "%s", "Reserved"); + break; + + case SAH_DMA_INT_ERR: + sprintf(dma_text, "%s", + "Internal DMA hardware error detected. The " + "DMA controller has detected an internal " + "condition which should never occur"); + break; + + default: + sprintf(dma_text, "%s", + "Unknown DMA Error Status Code"); + break; + } + + return_code = + sah_DMA_Error_Status_Array[dma_error >> 1]; + dma_error = 1; + + dma_log = os_alloc_memory(320, GFP_KERNEL); + sprintf(dma_log, + " Occurred during a %s operation of a %s transfer: %s.", + dma_direction, dma_size, dma_text); + + os_free_memory(dma_direction); + os_free_memory(dma_size); + os_free_memory(dma_text); + } + break; + + case SAH_ERR_NONE: + default: + sprintf(source_text, "%s", "Unknown Error Code"); + break; + } + + address = os_alloc_memory(35, GFP_KERNEL); + + /* convert error & descriptor address to strings */ + if (dma_error) { + sprintf(address, "Fault address is 0x%08x", fault_address); + } else { + sprintf(address, "Descriptor bus address is 0x%08x", + descriptor); + } + + if (return_code > FSL_INVALID_RETURN) { + return_code = FSL_INVALID_RETURN; + } + + error_log = os_alloc_memory(250, GFP_KERNEL); + + /* construct final log message */ + sprintf(error_log, "Error source = 0x%08x. Return = %s. %s. %s.", + error, sah_return_text[return_code], address, source_text); + + os_free_memory(source_text); + os_free_memory(address); + + /* log standard messages */ + LOG_KDIAG(error_log); + os_free_memory(error_log); + + /* add additional information if available */ + if (cha_error) { + LOG_KDIAG(cha_log); + os_free_memory(cha_log); + } + + if (dma_error) { + LOG_KDIAG(dma_log); + os_free_memory(dma_log); + } + + return; +} /* sah_Log_Error */ + +#endif /* DIAG_DRV_STATUS */ + +/* End of sah_queue_manager.c */ diff --git a/drivers/mxc/security/sahara2/sah_status_manager.c b/drivers/mxc/security/sahara2/sah_status_manager.c new file mode 100644 index 000000000000..7791f5c45c93 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_status_manager.c @@ -0,0 +1,734 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file sah_status_manager.c +* +* @brief Status Manager Function +* +* This file contains the function which processes the Sahara status register +* during an interrupt. +* +* This file does not need porting. +*/ + +#include "portable_os.h" + +#include <sah_status_manager.h> +#include <sah_hardware_interface.h> +#include <sah_queue_manager.h> +#include <sah_memory_mapper.h> +#include <sah_kernel.h> + +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) +#include <diagnostic.h> +#endif + +/*! Compile-time flag to count various interrupt types. */ +#define DIAG_INT_COUNT + +/*! + * Number of interrupts processed with Done1Done2 status. Updates to this + * value should only be done in interrupt processing. + */ +uint32_t done1_count; + +/*! + * Number of interrupts processed with Done1Busy2 status. Updates to this + * value should only be done in interrupt processing. + */ +uint32_t done1busy2_count; + +/*! + * Number of interrupts processed with Done1Done2 status. Updates to this + * value should only be done in interrupt processing. + */ +uint32_t done1done2_count; + +/*! + * the dynameic power management flag is false when power management is not + * asserted and true when dpm is. + */ +#ifdef SAHARA_POWER_MANAGEMENT +bool sah_dpm_flag = FALSE; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +static int sah_dpm_suspend(struct device *dev, uint32_t state, uint32_t level); +static int sah_dpm_resume(struct device *dev, uint32_t level); +#else +static int sah_dpm_suspend(struct platform_device *dev, pm_message_t state); +static int sah_dpm_resume(struct platform_device *dev); +#endif +#endif + +#ifndef SAHARA_POLL_MODE +/*! +******************************************************************************* +* This functionx processes the status register of the Sahara, updates the state +* of the finished queue entry, and then tries to find more work for Sahara to +* do. +* +* @brief The bulk of the interrupt handling code. +* +* @param hw_status The status register of Sahara at time of interrupt. +* The Clear interrupt bit is already handled by this +* register read prior to entry into this function. +* @return void +*/ +unsigned long sah_Handle_Interrupt(sah_Execute_Status hw_status) +{ + unsigned long reset_flag = 0; /* assume no SAHARA reset needed */ + os_lock_context_t lock_flags; + sah_Head_Desc *current_entry; + + /* HW status at time of interrupt */ + sah_Execute_Status state = hw_status & SAH_EXEC_STATE_MASK; + + do { + uint32_t dar; + +#ifdef DIAG_INT_COUNT + if (state == SAH_EXEC_DONE1) { + done1_count++; + } else if (state == SAH_EXEC_DONE1_BUSY2) { + done1busy2_count++; + } else if (state == SAH_EXEC_DONE1_DONE2) { + done1done2_count++; + } +#endif + + /* if the first entry on sahara has completed... */ + if ((state & SAH_EXEC_DONE1_BIT) || + (state == SAH_EXEC_ERROR1)) { + /* lock queue while searching */ + os_lock_save_context(desc_queue_lock, lock_flags); + current_entry = + sah_Find_With_State(SAH_STATE_ON_SAHARA); + os_unlock_restore_context(desc_queue_lock, lock_flags); + + /* an active descriptor was not found */ + if (current_entry == NULL) { + /* change state to avoid an infinite loop (possible if + * state is SAH_EXEC_DONE1_BUSY2 first time into loop) */ + hw_status = SAH_EXEC_IDLE; +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + LOG_KDIAG + ("Interrupt received with nothing on queue."); +#endif + } else { + /* SAHARA has completed its work on this descriptor chain */ + current_entry->status = SAH_STATE_OFF_SAHARA; + + if (state == SAH_EXEC_ERROR1) { + if (hw_status & STATUS_ERROR) { + /* Gather extra diagnostic information */ + current_entry->fault_address = + sah_HW_Read_Fault_Address(); + /* Read this last - it clears the error */ + current_entry->error_status = + sah_HW_Read_Error_Status(); + current_entry->op_status = 0; +#ifdef FSL_HAVE_SAHARA4 + } else { + current_entry->op_status = + sah_HW_Read_Op_Status(); + current_entry->error_status = 0; +#endif + } + + } else { + /* indicate that no errors were found with descriptor + * chain 1 */ + current_entry->error_status = 0; + current_entry->op_status = 0; + + /* is there a second, successfully, completed descriptor + * chain? (done1/error2 processing is handled later) */ + if (state == SAH_EXEC_DONE1_DONE2) { + os_lock_save_context + (desc_queue_lock, + lock_flags); + current_entry = + sah_Find_With_State + (SAH_STATE_ON_SAHARA); + os_unlock_restore_context + (desc_queue_lock, + lock_flags); + + if (current_entry == NULL) { +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + LOG_KDIAG + ("Done1_Done2 Interrupt received with " + "one entry on queue."); +#endif + } else { + /* indicate no errors in descriptor chain 2 */ + current_entry-> + error_status = 0; + current_entry->status = + SAH_STATE_OFF_SAHARA; + } + } + } + } + +#ifdef SAHARA_POWER_MANAGEMENT + /* check dynamic power management is not asserted */ + if (!sah_dpm_flag) { +#endif + do { + /* protect DAR and main_queue */ + os_lock_save_context(desc_queue_lock, + lock_flags); + dar = sah_HW_Read_DAR(); + /* check if SAHARA has space for another descriptor. SAHARA + * only accepts up to the DAR queue size number of DAR + * entries, after that 'dar' will not be zero until the + * pending interrupt is serviced */ + if (dar == 0) { + current_entry = + sah_Find_With_State + (SAH_STATE_PENDING); + if (current_entry != NULL) { +#ifndef SUBMIT_MULTIPLE_DARS + /* BUG FIX: state machine can transition from Done1 + * Busy2 directly to Idle. To fix that problem, + * only one DAR is being allowed on SAHARA at a + * time. If a high level interrupt has happened, + * there could * be an active descriptor chain */ + if (sah_Find_With_State + (SAH_STATE_ON_SAHARA) + == NULL) { +#endif +#if defined(DIAG_DRV_IF) && defined(DIAG_DURING_INTERRUPT) + sah_Dump_Chain + (¤t_entry-> + desc, + current_entry-> + desc. + dma_addr); +#endif /* DIAG_DRV_IF */ + sah_HW_Write_DAR + (current_entry-> + desc. + dma_addr); + current_entry-> + status = + SAH_STATE_ON_SAHARA; +#ifndef SUBMIT_MULTIPLE_DARS + } + current_entry = NULL; /* exit loop */ +#endif + } + } + os_unlock_restore_context + (desc_queue_lock, lock_flags); + } while ((dar == 0) && (current_entry != NULL)); +#ifdef SAHARA_POWER_MANAGEMENT + } /* sah_device_power_manager */ +#endif + } else { + if (state == SAH_EXEC_FAULT) { + sah_Head_Desc *previous_entry; /* point to chain 1 */ + /* Address of request when fault occured */ + uint32_t bad_dar = sah_HW_Read_IDAR(); + + reset_flag = 1; /* SAHARA needs to be reset */ + + /* get first of possible two descriptor chain that was + * on SAHARA */ + os_lock_save_context(desc_queue_lock, + lock_flags); + previous_entry = + sah_Find_With_State(SAH_STATE_ON_SAHARA); + os_unlock_restore_context(desc_queue_lock, + lock_flags); + + /* if it exists, continue processing the fault */ + if (previous_entry) { + /* assume this chain didn't complete correctly */ + previous_entry->error_status = -1; + previous_entry->status = + SAH_STATE_OFF_SAHARA; + + /* get the second descriptor chain */ + os_lock_save_context(desc_queue_lock, + lock_flags); + current_entry = + sah_Find_With_State + (SAH_STATE_ON_SAHARA); + os_unlock_restore_context + (desc_queue_lock, lock_flags); + + /* if it exists, continue processing both chains */ + if (current_entry) { + /* assume this chain didn't complete correctly */ + current_entry->error_status = + -1; + current_entry->status = + SAH_STATE_OFF_SAHARA; + + /* now see if either can be identified as the one + * in progress when the fault occured */ + if (current_entry->desc. + dma_addr == bad_dar) { + /* the second descriptor chain was active when the + * fault occured, so the first descriptor chain + * was successfull */ + previous_entry-> + error_status = 0; + } else { + if (previous_entry-> + desc.dma_addr == + bad_dar) { + /* if the first chain was in progress when the + * fault occured, the second has not yet been + * touched, so reset it to PENDING */ + current_entry-> + status = + SAH_STATE_PENDING; + } + } + } + } +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + } else { + /* shouldn't ever get here */ + if (state == SAH_EXEC_BUSY) { + LOG_KDIAG + ("Got Sahara interrupt in Busy state"); + } else { + if (state == SAH_EXEC_IDLE) { + LOG_KDIAG + ("Got Sahara interrupt in Idle state"); + } else { + LOG_KDIAG + ("Got Sahara interrupt in unknown state"); + } + } +#endif + } + } + + /* haven't handled the done1/error2 (the error 2 part), so setup to + * do that now. Otherwise, exit loop */ + state = (state == SAH_EXEC_DONE1_ERROR2) ? + SAH_EXEC_ERROR1 : SAH_EXEC_IDLE; + + /* Keep going while further status is available. */ + } while (state == SAH_EXEC_ERROR1); + + /* Disabling Sahara Clock only if the hardware is in idle state and + the DAR queue is empty.*/ + os_lock_save_context(desc_queue_lock, lock_flags); + current_entry = sah_Find_With_State(SAH_STATE_ON_SAHARA); + os_unlock_restore_context(desc_queue_lock, lock_flags); + + if ((current_entry == NULL) && (state == SAH_EXEC_IDLE)) { + +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA : Disabling the clocks\n") +#endif /* DIAG_DRV_IF */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_disable(SAHARA2_CLK); +#else + { + struct clk *clk = clk_get(NULL, "sahara_clk"); + if (clk != ERR_PTR(ENOENT)) + clk_disable(clk); + clk_put(clk); + } +#endif + + } + + return reset_flag; +} + +#endif /* ifndef SAHARA_POLL_MODE */ + +#ifdef SAHARA_POLL_MODE +/*! +******************************************************************************* +* Submits descriptor chain to SAHARA, polls on SAHARA for completion, process +* results, and dephysicalizes chain +* +* @brief Handle poll mode. +* +* @param entry Virtual address of a physicalized chain +* +* @return 0 this function is always successful +*/ + +unsigned long sah_Handle_Poll(sah_Head_Desc * entry) +{ + sah_Execute_Status hw_status; /* Sahara's status register */ + os_lock_context_t lock_flags; + + /* lock SARAHA */ + os_lock_save_context(desc_queue_lock, lock_flags); + +#ifdef SAHARA_POWER_MANAGEMENT + /* check if the dynamic power management is asserted */ + if (sah_dpm_flag) { + /* return that request failed to be processed */ + entry->result = FSL_RETURN_ERROR_S; + entry->fault_address = 0xBAD; + entry->op_status= 0xBAD; + entry->error_status = 0xBAD; + } else { +#endif /* SAHARA_POWER_MANAGEMENT */ + +#if defined(DIAG_DRV_IF) + sah_Dump_Chain(&entry->desc, entry->desc.dma_addr); +#endif /* DIAG_DRV_IF */ + /* Nothing can be in the dar if we got the lock */ + sah_HW_Write_DAR((uint32_t) (entry->desc.dma_addr)); + + /* Wait for SAHARA to finish with this entry */ + hw_status = sah_Wait_On_Sahara(); + + /* if entry completed successfully, mark it as such */ + /**** HARDWARE ERROR WORK AROUND (hw_status == SAH_EXEC_IDLE) *****/ + if ( +#ifndef SUBMIT_MULTIPLE_DARS + (hw_status == SAH_EXEC_IDLE) || (hw_status == SAH_EXEC_DONE1_BUSY2) || /* should not happen */ +#endif + (hw_status == SAH_EXEC_DONE1) + ) { + entry->error_status = 0; + entry->result = FSL_RETURN_OK_S; + } else { + /* SAHARA is reporting an error with entry */ + if (hw_status == SAH_EXEC_ERROR1) { + /* Gather extra diagnostic information */ + entry->fault_address = + sah_HW_Read_Fault_Address(); + /* Read this register last - it clears the error */ + entry->error_status = + sah_HW_Read_Error_Status(); + entry->op_status = 0; + /* translate from SAHARA error status to fsl_shw return values */ + entry->result = + sah_convert_error_status(entry-> + error_status); +#ifdef DIAG_DRV_STATUS + sah_Log_Error(entry->op_status, + entry->error_status, + entry->fault_address); +#endif + } else if (hw_status == SAH_EXEC_OPSTAT1) { + entry->op_status = sah_HW_Read_Op_Status(); + entry->error_status = 0; + entry->result = + sah_convert_op_status(op_status); + } else { + /* SAHARA entered FAULT state (or something bazaar has + * happened) */ + pr_debug + ("Sahara: hw_status = 0x%x; Stat: 0x%08x; IDAR: 0x%08x; " + "CDAR: 0x%08x; FltAdr: 0x%08x; Estat: 0x%08x\n", + hw_status, sah_HW_Read_Status(), + sah_HW_Read_IDAR(), sah_HW_Read_CDAR(), + sah_HW_Read_Fault_Address(), + sah_HW_Read_Error_Status()); +#ifdef DIAG_DRV_IF + { + int old_level = console_loglevel; + console_loglevel = 8; + sah_Dump_Chain(&(entry->desc), + entry->desc.dma_addr); + console_loglevel = old_level; + } +#endif + + entry->error_status = -1; + entry->result = FSL_RETURN_ERROR_S; + sah_HW_Reset(); + } + } +#ifdef SAHARA_POWER_MANAGEMENT + } +#endif + + if (!(entry->uco_flags & FSL_UCO_BLOCKING_MODE)) { + /* put it in results pool to allow get_results to work */ + sah_Queue_Append_Entry(&entry->user_info->result_pool, entry); + if (entry->uco_flags & FSL_UCO_CALLBACK_MODE) { + /* invoke callback */ + entry->user_info->callback(entry->user_info); + } + } else { + /* convert the descriptor link back to virtual memory, mark dirty pages + * if they are from user mode, and release the page cache for user + * pages + */ + entry = sah_DePhysicalise_Descriptors(entry); + } + + os_unlock_restore_context(desc_queue_lock, lock_flags); + + return 0; +} + +#endif /* SAHARA_POLL_MODE */ + +/****************************************************************************** +* The following is the implementation of the Dynamic Power Management +* functionality. +******************************************************************************/ +#ifdef SAHARA_POWER_MANAGEMENT + +static bool sah_dpm_init_flag; + +/* dynamic power management information for the sahara driver */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +static struct device_driver sah_dpm_driver = { + .name = "sahara_", + .bus = &platform_bus_type, +#else +static struct platform_driver sah_dpm_driver = { + .driver.name = "sahara_", + .driver.bus = &platform_bus_type, +#endif + .suspend = sah_dpm_suspend, + .resume = sah_dpm_resume +}; + +/* dynamic power management information for the sahara HW device */ +static struct platform_device sah_dpm_device = { + .name = "sahara_", + .id = 1, +}; + +/*! +******************************************************************************* +* Initilaizes the dynamic power managment functionality +* +* @brief Initialization of the Dynamic Power Management functionality +* +* @return 0 = success; failed otherwise +*/ +int sah_dpm_init() +{ + int status; + + /* dpm is not asserted */ + sah_dpm_flag = FALSE; + + /* register the driver to the kernel */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + status = os_register_to_driver(&sah_dpm_driver); +#else + status = os_register_to_driver(&sah_dpm_driver.driver); +#endif + + if (status == 0) { + /* register a single sahara chip */ + /*status = platform_device_register(&sah_dpm_device); */ + status = os_register_a_device(&sah_dpm_device); + + /* if something went awry, unregister the driver */ + if (status != 0) { + /*driver_unregister(&sah_dpm_driver); */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + os_unregister_from_driver(&sah_dpm_driver); +#else + os_unregister_from_driver(&sah_dpm_driver.driver); +#endif + sah_dpm_init_flag = FALSE; + } else { + /* if everything went okay, flag that life is good */ + sah_dpm_init_flag = TRUE; + } + } + + /* let the kernel know how it went */ + return status; + +} + +/*! +******************************************************************************* +* Unregister the dynamic power managment functionality +* +* @brief Unregister the Dynamic Power Management functionality +* +*/ +void sah_dpm_close() +{ + /* if dynamic power management was initilaized, kill it */ + if (sah_dpm_init_flag == TRUE) { + /*driver_unregister(&sah_dpm_driver); */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + os_unregister_from_driver(&sah_dpm_driver); +#else + os_unregister_from_driver(&sah_dpm_driver.driver); +#endif + /*platform_device_register(&sah_dpm_device); */ + os_unregister_a_device(&sah_dpm_device); + } +} + +/*! +******************************************************************************* +* Callback routine defined by the Linux Device Model / Dynamic Power management +* extension. It sets a global flag to disallow the driver to enter queued items +* into Sahara's DAR. +* +* It allows the current active descriptor chains to complete before it returns +* +* @brief Suspends the driver +* +* @param dev contains device information +* @param state contains state information +* @param level level of shutdown +* +* @return 0 = success; failed otherwise +*/ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +static int sah_dpm_suspend(struct device *dev, uint32_t state, uint32_t level) +#else +static int sah_dpm_suspend(struct platform_device *dev, pm_message_t state) +#endif +{ + sah_Head_Desc *entry = NULL; + os_lock_context_t lock_flags; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + switch (level) { + case SUSPEND_DISABLE: + /* Assert dynamic power management. This stops the driver from + * entering queued requests to Sahara */ + sah_dpm_flag = TRUE; + break; + + case SUSPEND_SAVE_STATE: + break; + + case SUSPEND_POWER_DOWN: + /* hopefully between the DISABLE call and this one, the outstanding + * work Sahara was doing complete. this checks (and waits) for + * those entries that were already active on Sahara to complete */ + /* lock queue while searching */ + os_lock_save_context(desc_queue_lock, lock_flags); + do { + entry = sah_Find_With_State(SAH_STATE_ON_SAHARA); + } while (entry != NULL); + os_unlock_restore_context(desc_queue_lock, lock_flags); + + /* now we kill the clock so the control circuitry isn't sucking + * any power */ + mxc_clks_disable(SAHARA2_CLK); + break; + } +#else + /* Assert dynamic power management. This stops the driver from + * entering queued requests to Sahara */ + sah_dpm_flag = TRUE; + + /* Now wait for any outstanding work Sahara was doing to complete. + * this checks (and waits) for + * those entries that were already active on Sahara to complete */ + do { + /* lock queue while searching */ + os_lock_save_context(desc_queue_lock, lock_flags); + entry = sah_Find_With_State(SAH_STATE_ON_SAHARA); + os_unlock_restore_context(desc_queue_lock, lock_flags); + } while (entry != NULL); + + /* now we kill the clock so the control circuitry isn't sucking + * any power */ + { + struct clk *clk = clk_get(NULL, "sahara_clk"); + if (clk != ERR_PTR(ENOENT)) { + clk_disable(clk); + } + } +#endif + + return 0; +} + +/*! +******************************************************************************* +* Callback routine defined by the Linux Device Model / Dynamic Power management +* extension. It cleears a global flag to allow the driver to enter queued items +* into Sahara's DAR. +* +* It primes the mechanism to start depleting the queue +* +* @brief Resumes the driver +* +* @param dev contains device information +* @param level level of resumption +* +* @return 0 = success; failed otherwise +*/ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +static int sah_dpm_resume(struct device *dev, uint32_t level) +#else +static int sah_dpm_resume(struct platform_device *dev) +#endif +{ + sah_Head_Desc *entry = NULL; + os_lock_context_t lock_flags; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + switch (level) { + case RESUME_POWER_ON: + /* enable Sahara's clock */ + mxc_clks_enable(SAHARA2_CLK); + break; + + case RESUME_RESTORE_STATE: + break; + + case RESUME_ENABLE: + /* Disable dynamic power managment. This allows the driver to put + * entries into Sahara's DAR */ + sah_dpm_flag = FALSE; + + /* find a pending entry to prime the pump */ + os_lock_save_context(desc_queue_lock, lock_flags); + entry = sah_Find_With_State(SAH_STATE_PENDING); + if (entry != NULL) { + sah_Queue_Manager_Prime(entry); + } + os_unlock_restore_context(desc_queue_lock, lock_flags); + break; + } +#else + { + /* enable Sahara's clock */ + struct clk *clk = clk_get(NULL, "sahara_clk"); + + if (clk != ERR_PTR(ENOENT)) { + clk_enable(clk); + } + } + sah_dpm_flag = FALSE; + + /* find a pending entry to prime the pump */ + os_lock_save_context(desc_queue_lock, lock_flags); + entry = sah_Find_With_State(SAH_STATE_PENDING); + if (entry != NULL) { + sah_Queue_Manager_Prime(entry); + } + os_unlock_restore_context(desc_queue_lock, lock_flags); +#endif + return 0; +} + +#endif /* SAHARA_POWER_MANAGEMENT */ diff --git a/drivers/mxc/security/sahara2/sf_util.c b/drivers/mxc/security/sahara2/sf_util.c new file mode 100644 index 000000000000..b1cc2597f183 --- /dev/null +++ b/drivers/mxc/security/sahara2/sf_util.c @@ -0,0 +1,1390 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** +* @file sf_util.c +* +* @brief Security Functions component API - Utility functions +* +* These are the 'Sahara api' functions which are used by the higher-level +* FSL SHW API to build and then execute descriptor chains. +*/ + + +#include "sf_util.h" +#include <adaptor.h> + +#ifdef DIAG_SECURITY_FUNC +#include <diagnostic.h> +#endif /*DIAG_SECURITY_FUNC*/ + + +#ifdef __KERNEL__ +EXPORT_SYMBOL(sah_Append_Desc); +EXPORT_SYMBOL(sah_Append_Link); +EXPORT_SYMBOL(sah_Create_Link); +EXPORT_SYMBOL(sah_Create_Key_Link); +EXPORT_SYMBOL(sah_Destroy_Link); +EXPORT_SYMBOL(sah_Descriptor_Chain_Execute); +EXPORT_SYMBOL(sah_insert_mdha_algorithm); +EXPORT_SYMBOL(sah_insert_skha_algorithm); +EXPORT_SYMBOL(sah_insert_skha_mode); +EXPORT_SYMBOL(sah_insert_skha_modulus); +EXPORT_SYMBOL(sah_Descriptor_Chain_Destroy); +EXPORT_SYMBOL(sah_add_two_in_desc); +EXPORT_SYMBOL(sah_add_in_key_desc); +EXPORT_SYMBOL(sah_add_two_out_desc); +EXPORT_SYMBOL(sah_add_in_out_desc); +EXPORT_SYMBOL(sah_add_key_out_desc); +#endif + +#ifdef DEBUG_REWORK +#ifndef __KERNEL__ +#include <stdio.h> +#define os_printk printf +#endif +#endif + +/** + * Convert fsl_shw_hash_alg_t to mdha mode bits. + * + * Index must be maintained in order of fsl_shw_hash_alg_t enumeration!!! + */ +const uint32_t sah_insert_mdha_algorithm[] = +{ + [FSL_HASH_ALG_MD5] = sah_insert_mdha_algorithm_md5, + [FSL_HASH_ALG_SHA1] = sah_insert_mdha_algorithm_sha1, + [FSL_HASH_ALG_SHA224] = sah_insert_mdha_algorithm_sha224, + [FSL_HASH_ALG_SHA256] = sah_insert_mdha_algorithm_sha256, +}; + +/** + * Header bits for Algorithm field of SKHA header + * + * Index value must be kept in sync with fsl_shw_key_alg_t + */ +const uint32_t sah_insert_skha_algorithm[] = +{ + [FSL_KEY_ALG_HMAC] = 0x00000040, + [FSL_KEY_ALG_AES] = sah_insert_skha_algorithm_aes, + [FSL_KEY_ALG_DES] = sah_insert_skha_algorithm_des, + [FSL_KEY_ALG_TDES] = sah_insert_skha_algorithm_tdes, + [FSL_KEY_ALG_ARC4] = sah_insert_skha_algorithm_arc4, +}; + + +/** + * Header bits for MODE field of SKHA header + * + * Index value must be kept in sync with fsl_shw_sym_mod_t + */ +const uint32_t sah_insert_skha_mode[] = +{ + [FSL_SYM_MODE_STREAM] = sah_insert_skha_mode_ecb, + [FSL_SYM_MODE_ECB] = sah_insert_skha_mode_ecb, + [FSL_SYM_MODE_CBC] = sah_insert_skha_mode_cbc, + [FSL_SYM_MODE_CTR] = sah_insert_skha_mode_ctr, +}; + + +/** + * Header bits to set CTR modulus size. These have parity + * included to allow XOR insertion of values. + * + * @note Must be kept in sync with fsl_shw_ctr_mod_t + */ +const uint32_t sah_insert_skha_modulus[] = +{ + [FSL_CTR_MOD_8] = 0x00000000, /**< 2**8 */ + [FSL_CTR_MOD_16] = 0x80000200, /**< 2**16 */ + [FSL_CTR_MOD_24] = 0x80000400, /**< 2**24 */ + [FSL_CTR_MOD_32] = 0x00000600, /**< 2**32 */ + [FSL_CTR_MOD_40] = 0x80000800, /**< 2**40 */ + [FSL_CTR_MOD_48] = 0x00000a00, /**< 2**48 */ + [FSL_CTR_MOD_56] = 0x00000c00, /**< 2**56 */ + [FSL_CTR_MOD_64] = 0x80000e00, /**< 2**64 */ + [FSL_CTR_MOD_72] = 0x80001000, /**< 2**72 */ + [FSL_CTR_MOD_80] = 0x00001200, /**< 2**80 */ + [FSL_CTR_MOD_88] = 0x00001400, /**< 2**88 */ + [FSL_CTR_MOD_96] = 0x80001600, /**< 2**96 */ + [FSL_CTR_MOD_104] = 0x00001800, /**< 2**104 */ + [FSL_CTR_MOD_112] = 0x80001a00, /**< 2**112 */ + [FSL_CTR_MOD_120] = 0x80001c00, /**< 2**120 */ + [FSL_CTR_MOD_128] = 0x00001e00 /**< 2**128 */ +}; + + +/****************************************************************************** +* Internal function declarations +******************************************************************************/ +static fsl_shw_return_t sah_Create_Desc( + const sah_Mem_Util *mu, + sah_Desc ** desc, + int head, + uint32_t header, + sah_Link * link1, + sah_Link * link2); + + +/** + * Create a descriptor chain using the the header and links passed in as + * parameters. The newly created descriptor will be added to the end of + * the descriptor chain passed. + * + * If @a desc_head points to a NULL value, then a sah_Head_Desc will be created + * as the first descriptor. Otherwise a sah_Desc will be created and appended. + * + * @pre + * + * - None + * + * @post + * + * - A descriptor has been created from the header, link1 and link2. + * + * - The newly created descriptor has been appended to the end of + * desc_head, or its location stored into the location pointed to by + * @a desc_head. + * + * - On allocation failure, @a link1 and @a link2 will be destroyed., and + * @a desc_head will be untouched. + * + * @brief Create and append descriptor chain, inserting header and links + * pointing to link1 and link2 + * + * @param mu Memory functions + * @param header Value of descriptor header to be added + * @param desc_head Pointer to head of descriptor chain to append new desc + * @param link1 Pointer to sah_Link 1 (or NULL) + * @param link2 Pointer to sah_Link 2 (or NULL) + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_Append_Desc( + const sah_Mem_Util *mu, + sah_Head_Desc **desc_head, + const uint32_t header, + sah_Link *link1, + sah_Link *link2) +{ + fsl_shw_return_t status; + sah_Desc *desc; + sah_Desc *desc_ptr; + + + status = sah_Create_Desc(mu, (sah_Desc**)&desc, (*desc_head == NULL), + header, link1, link2); + /* append newly created descriptor to end of current chain */ + if (status == FSL_RETURN_OK_S) { + if (*desc_head == NULL) { + (*desc_head) = (sah_Head_Desc*)desc; + (*desc_head)->out1_ptr = NULL; + (*desc_head)->out2_ptr = NULL; + + } else { + desc_ptr = (sah_Desc*)*desc_head; + while (desc_ptr->next != NULL) { + desc_ptr = desc_ptr->next; + } + desc_ptr->next = desc; + } + } + + return status; +} + + +/** + * Releases the memory allocated by the Security Function library for + * descriptors, links and any internally allocated memory referenced in the + * given chain. Note that memory allocated by user applications is not + * released. + * + * @post The @a desc_head pointer will be set to NULL to prevent further use. + * + * @brief Destroy a descriptor chain and free memory of associated links + * + * @param mu Memory functions + * @param desc_head Pointer to head of descriptor chain to be freed + * + * @return none + */ +void sah_Descriptor_Chain_Destroy ( + const sah_Mem_Util *mu, + sah_Head_Desc **desc_head) +{ + sah_Desc *desc_ptr = &(*desc_head)->desc; + sah_Head_Desc *desc_head_ptr = (sah_Head_Desc *)desc_ptr; + + while (desc_ptr != NULL) { + register sah_Desc *next_desc_ptr; + + if (desc_ptr->ptr1 != NULL) { + sah_Destroy_Link(mu, desc_ptr->ptr1); + } + if (desc_ptr->ptr2 != NULL) { + sah_Destroy_Link(mu, desc_ptr->ptr2); + } + + next_desc_ptr = desc_ptr->next; + + /* Be sure to free head descriptor as such */ + if (desc_ptr == (sah_Desc*)desc_head_ptr) { + mu->mu_free_head_desc(mu->mu_ref, desc_head_ptr); + } else { + mu->mu_free_desc(mu->mu_ref, desc_ptr); + } + + desc_ptr = next_desc_ptr; + } + + *desc_head = NULL; +} + + +#ifndef NO_INPUT_WORKAROUND +/** + * Reworks the link chain + * + * @brief Reworks the link chain + * + * @param mu Memory functions + * @param link Pointer to head of link chain to be reworked + * + * @return none + */ +static fsl_shw_return_t sah_rework_link_chain( + const sah_Mem_Util *mu, + sah_Link* link) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + int found_potential_problem = 0; + uint32_t total_data = 0; +#ifdef DEBUG_REWORK + sah_Link* first_link = link; +#endif + + if ((link->flags & SAH_OUTPUT_LINK)) { + return status; + } + + while (link != NULL) { + total_data += link->len; + + /* Only non-key Input Links are affected by the DMA flush-to-FIFO + * problem */ + + /* If have seen problem and at end of chain... */ + if (found_potential_problem && (link->next == NULL) && + (total_data > 16)) { + /* insert new 1-byte link */ + sah_Link* new_tail_link = mu->mu_alloc_link(mu->mu_ref); + if (new_tail_link == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; + } else { +#ifdef DEBUG_REWORK + sah_Link* dump_link = first_link; + while (dump_link != NULL) { + uint32_t i; + unsigned bytes_to_dump = (dump_link->len > 32) ? + 32 : dump_link->len; + os_printk("(rework)Link %p: %p/%u/%p\n", dump_link, + dump_link->data, dump_link->len, + dump_link->next); + if (!(dump_link->flags & SAH_STORED_KEY_INFO)) { + os_printk("(rework)Data %p: ", dump_link->data); + for (i = 0; i < bytes_to_dump; i++) { + os_printk("%02X ", dump_link->data[i]); + } + os_printk("\n"); + } + else { + os_printk("rework)Data %p: Red key data\n", dump_link); + } + dump_link = dump_link->next; + } +#endif + link->len--; + link->next = new_tail_link; + new_tail_link->len = 1; + new_tail_link->data = link->data+link->len; + new_tail_link->flags = link->flags & ~(SAH_OWNS_LINK_DATA); + new_tail_link->next = NULL; + link = new_tail_link; +#ifdef DEBUG_REWORK + os_printk("(rework)New link chain:\n"); + dump_link = first_link; + while (dump_link != NULL) { + uint32_t i; + unsigned bytes_to_dump = (dump_link->len > 32) ? + 32 : dump_link->len; + + os_printk("Link %p: %p/%u/%p\n", dump_link, + dump_link->data, dump_link->len, + dump_link->next); + if (!(dump_link->flags & SAH_STORED_KEY_INFO)) { + os_printk("Data %p: ", dump_link->data); + for (i = 0; i < bytes_to_dump; i++) { + os_printk("%02X ", dump_link->data[i]); + } + os_printk("\n"); + } + else { + os_printk("Data %p: Red key data\n", dump_link); + } + dump_link = dump_link->next; + } +#endif + } + } else if ((link->len % 4) || ((uint32_t)link->data % 4)) { + found_potential_problem = 1; + } + + link = link->next; + } + + return status; +} + + +/** + * Rework links to avoid H/W bug + * + * @param head Beginning of descriptor chain + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t sah_rework_links( + const sah_Mem_Util *mu, + sah_Head_Desc *head) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Desc* desc = &head->desc; + + while ((status == FSL_RETURN_OK_S) && (desc != NULL)) { + if (desc->header & SAH_HDR_LLO) { + status = FSL_RETURN_ERROR_S; + break; + } + if (desc->ptr1 != NULL) { + status = sah_rework_link_chain(mu, desc->ptr1); + } + if ((status == FSL_RETURN_OK_S) && (desc->ptr2 != NULL)) { + status = sah_rework_link_chain(mu, desc->ptr2); + } + desc = desc->next; + } + + return status; +} +#endif /* NO_INPUT_WORKAROUND */ + + +/** + * Send a descriptor chain to the SAHARA driver for processing. + * + * Note that SAHARA will read the input data from and write the output data + * directly to the locations indicated during construction of the chain. + * + * @pre + * + * - None + * + * @post + * + * - @a head will have been executed on SAHARA + * - @a head Will be freed unless a SAVE flag is set + * + * @brief Execute a descriptor chain + * + * @param head Pointer to head of descriptor chain to be executed + * @param user_ctx The user context on which to execute the descriptor chain. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_Descriptor_Chain_Execute( + sah_Head_Desc *head, + fsl_shw_uco_t *user_ctx) +{ + fsl_shw_return_t status; + + /* Check for null pointer or non-multiple-of-four value */ + if ((head == NULL) || ((uint32_t)head & 0x3)) { + status = FSL_RETURN_ERROR_S; + goto out; + } + +#ifndef NO_INPUT_WORKAROUND + status = sah_rework_links(user_ctx->mem_util, head); + if (status != FSL_RETURN_OK_S) { + goto out; + } +#endif + + /* complete the information in the descriptor chain head node */ + head->user_ref = user_ctx->user_ref; + head->uco_flags = user_ctx->flags; + head->next = NULL; /* driver will use this to link chain heads */ + + status = adaptor_Exec_Descriptor_Chain(head, user_ctx); + +#ifdef DIAG_SECURITY_FUNC + if (status == FSL_RETURN_OK_S) + LOG_DIAG("after exec desc chain: status is ok\n"); + else + LOG_DIAG("after exec desc chain: status is not ok\n"); +#endif + + out: + return status; +} + + +/** + * Create Link + * + * @brief Allocate Memory for Link structure and populate using input + * parameters + * + * @post On allocation failure, @a p will be freed if #SAH_OWNS_LINK_DATA is + * p set in @a flags. + + * @param mu Memory functions + * @param link Pointer to link to be created + * @param p Pointer to data to use in link + * @param length Length of buffer 'p' in bytes + * @param flags Indicates whether memory has been allocated by the calling + * function or the security function + * + * @return FSL_RETURN_OK_S or FSL_RETURN_NO_RESOURCE_S + */ +fsl_shw_return_t sah_Create_Link( + const sah_Mem_Util *mu, + sah_Link **link, + uint8_t *p, + const size_t length, + const sah_Link_Flags flags) +{ + +#ifdef DIAG_SECURITY_FUNC + + char diag[50]; +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + fsl_shw_return_t status = FSL_RETURN_NO_RESOURCE_S; + + + *link = mu->mu_alloc_link(mu->mu_ref); + + /* populate link if memory allocation successful */ + if (*link != NULL) { + (*link)->len = length; + (*link)->data = p; + (*link)->next = NULL; + (*link)->flags = flags; + status = FSL_RETURN_OK_S; + +#ifdef DIAG_SECURITY_FUNC + + LOG_DIAG("Created Link"); + LOG_DIAG("------------"); + sprintf(diag," address = 0x%x", (int) *link); + LOG_DIAG(diag); + sprintf(diag," link->len = %d",(*link)->len); + LOG_DIAG(diag); + sprintf(diag," link->data = 0x%x",(int) (*link)->data); + LOG_DIAG(diag); + sprintf(diag," link->flags = 0x%x",(*link)->flags); + LOG_DIAG(diag); + LOG_DIAG(" link->next = NULL"); +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + + } else { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Allocation of memory for sah_Link failed!\n"); +#endif /*DIAG_SECURITY_FUNC*/ + + /* Free memory previously allocated by the security function layer for + link data. Note that the memory being pointed to will be zeroed before + being freed, for security reasons. */ + if (flags & SAH_OWNS_LINK_DATA) { + mu->mu_memset(mu->mu_ref, p, 0x00, length); + mu->mu_free(mu->mu_ref, p); + } + } + + return status; +} + + +/** + * Create Key Link + * + * @brief Allocate Memory for Link structure and populate using key info + * object + * + * @param mu Memory functions + * @param link Pointer to store address of link to be created + * @param key_info Pointer to Key Info object to be referenced + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_Create_Key_Link( + const sah_Mem_Util *mu, + sah_Link **link, + fsl_shw_sko_t *key_info) +{ +#ifdef DIAG_SECURITY_FUNC_UGLY + char diag[50]; +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + fsl_shw_return_t status = FSL_RETURN_NO_RESOURCE_S; + sah_Link_Flags flags = 0; + + + *link = mu->mu_alloc_link(mu->mu_ref); + + /* populate link if memory allocation successful */ + if (*link != NULL) { + (*link)->len = key_info->key_length; + + if (key_info->flags & FSL_SKO_KEY_PRESENT) { + (*link)->data = key_info->key; + status = FSL_RETURN_OK_S; + } else { + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + + if (key_info->keystore == NULL) { + /* System Keystore */ + (*link)->slot = key_info->handle; + (*link)->ownerid = key_info->userid; + (*link)->data = 0; + flags |= SAH_STORED_KEY_INFO; + status = FSL_RETURN_OK_S; + } else { +#ifdef FSL_HAVE_SCC2 + /* User Keystore */ + fsl_shw_kso_t *keystore = key_info->keystore; + /* Note: the key data is stored here, but the address has to + * be converted to a partition and offset in the kernel. + * This will be calculated in kernel space, based on the + * list of partitions held by the users context. + */ + (*link)->data = + keystore->slot_get_address(keystore->user_data, + key_info->handle); + + flags |= SAH_IN_USER_KEYSTORE; + status = FSL_RETURN_OK_S; +#else + /* User keystores only supported in SCC2 */ + status = FSL_RETURN_BAD_FLAG_S; +#endif /* FSL_HAVE_SCC2 */ + + } + } else { + /* the flag is bad. Should never get here */ + status = FSL_RETURN_BAD_FLAG_S; + } + } + + (*link)->next = NULL; + (*link)->flags = flags; + +#ifdef DIAG_SECURITY_FUNC_UGLY + if (status == FSL_RETURN_OK_S) { + LOG_DIAG("Created Link"); + LOG_DIAG("------------"); + sprintf(diag," address = 0x%x", (int) *link); + LOG_DIAG(diag); + sprintf(diag," link->len = %d", (*link)->len); + LOG_DIAG(diag); + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + sprintf(diag," link->slot = 0x%x", (*link)->slot); + LOG_DIAG(diag); + } else { + sprintf(diag," link->data = 0x%x", (int)(*link)->data); + LOG_DIAG(diag); + } + sprintf(diag," link->flags = 0x%x", (*link)->flags); + LOG_DIAG(diag); + LOG_DIAG(" link->next = NULL"); + } +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + + if (status == FSL_RETURN_BAD_FLAG_S) { + mu->mu_free_link(mu->mu_ref, *link); + *link = NULL; +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creation of sah_Key_Link failed due to bad key flag!\n"); +#endif /*DIAG_SECURITY_FUNC*/ + } + + } else { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Allocation of memory for sah_Key_Link failed!\n"); +#endif /*DIAG_SECURITY_FUNC*/ + } + + return status; +} + + +/** + * Append Link + * + * @brief Allocate Memory for Link structure and append it to the end of + * the link chain. + * + * @post On allocation failure, @a p will be freed if #SAH_OWNS_LINK_DATA is + * p set in @a flags. + * + * @param mu Memory functions + * @param link_head Pointer to (head of) existing link chain + * @param p Pointer to data to use in link + * @param length Length of buffer 'p' in bytes + * @param flags Indicates whether memory has been allocated by the calling + * function or the security function + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_Append_Link( + const sah_Mem_Util *mu, + sah_Link *link_head, + uint8_t *p, + const size_t length, + const sah_Link_Flags flags) +{ + sah_Link* new_link; + fsl_shw_return_t status; + + + status = sah_Create_Link(mu, &new_link, p, length, flags); + + if (status == FSL_RETURN_OK_S) { + /* chase for the tail */ + while (link_head->next != NULL) { + link_head = link_head->next; + } + + /* and add new tail */ + link_head->next = new_link; + } + + return status; +} + + +/** + * Create and populate a single descriptor + * + * The pointer and length fields will be be set based on the chains passed in + * as @a link1 and @a link2. + * + * @param mu Memory utility suite + * @param desc Location to store pointer of new descriptor + * @param head_desc Non-zero if this will be first in chain; zero otherwise + * @param header The Sahara header value to store in the descriptor + * @param link1 A value (or NULL) for the first ptr + * @param link2 A value (or NULL) for the second ptr + * + * @post If allocation succeeded, the @a link1 and @link2 will be linked into + * the descriptor. If allocation failed, those link structues will be + * freed, and the @a desc will be unchanged. + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t sah_Create_Desc( + const sah_Mem_Util *mu, + sah_Desc ** desc, + int head_desc, + uint32_t header, + sah_Link * link1, + sah_Link * link2) +{ + fsl_shw_return_t status = FSL_RETURN_NO_RESOURCE_S; +#ifdef DIAG_SECURITY_FUNC_UGLY + char diag[50]; +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + + + if (head_desc != 0) { + *desc = (sah_Desc *)mu->mu_alloc_head_desc(mu->mu_ref); + } else { + *desc = mu->mu_alloc_desc(mu->mu_ref); + } + + /* populate descriptor if memory allocation successful */ + if ((*desc) != NULL) { + sah_Link* temp_link; + + status = FSL_RETURN_OK_S; + (*desc)->header = header; + + temp_link = (*desc)->ptr1 = link1; + (*desc)->len1 = 0; + while (temp_link != NULL) { + (*desc)->len1 += temp_link->len; + temp_link = temp_link->next; + } + + temp_link = (*desc)->ptr2 = link2; + (*desc)->len2 = 0; + while (temp_link != NULL) { + (*desc)->len2 += temp_link->len; + temp_link = temp_link->next; + } + + (*desc)->next = NULL; + +#ifdef DIAG_SECURITY_FUNC_UGLY + LOG_DIAG("Created Desc"); + LOG_DIAG("------------"); + sprintf(diag," address = 0x%x",(int) *desc); + LOG_DIAG(diag); + sprintf(diag," desc->header = 0x%x",(*desc)->header); + LOG_DIAG(diag); + sprintf(diag," desc->len1 = %d",(*desc)->len1); + LOG_DIAG(diag); + sprintf(diag," desc->ptr1 = 0x%x",(int) ((*desc)->ptr1)); + LOG_DIAG(diag); + sprintf(diag," desc->len2 = %d",(*desc)->len2); + LOG_DIAG(diag); + sprintf(diag," desc->ptr2 = 0x%x",(int) ((*desc)->ptr2)); + LOG_DIAG(diag); + sprintf(diag," desc->next = 0x%x",(int) ((*desc)->next)); + LOG_DIAG(diag); +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + } else { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Allocation of memory for sah_Desc failed!\n"); +#endif /*DIAG_SECURITY_FUNC*/ + + /* Destroy the links, otherwise the memory allocated by the Security + Function layer for the links (and possibly the data within the links) + will be lost */ + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } + + return status; +} + + +/** + * Destroy a link (chain) and free memory + * + * @param mu memory utility suite + * @param link The Link to destroy + * + * @return none + */ +void sah_Destroy_Link( + const sah_Mem_Util *mu, + sah_Link * link) +{ + + while (link != NULL) { + sah_Link * next_link = link->next; + + if (link->flags & SAH_OWNS_LINK_DATA) { + /* zero data for security purposes */ + mu->mu_memset(mu->mu_ref, link->data, 0x00, link->len); + mu->mu_free(mu->mu_ref, link->data); + } + + link->data = NULL; + link->next = NULL; + mu->mu_free_link(mu->mu_ref, link); + + link = next_link; + } +} + + +/** + * Add descriptor where both links are inputs. + * + * @param header The Sahara header value for the descriptor. + * @param in1 The first input buffer (or NULL) + * @param in1_length Size of @a in1 + * @param[out] in2 The second input buffer (or NULL) + * @param in2_length Size of @a in2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_two_in_desc(uint32_t header, + const uint8_t* in1, + uint32_t in1_length, + const uint8_t* in2, + uint32_t in2_length, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link* link1 = NULL; + sah_Link* link2 = NULL; + + if (in1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) in1, in1_length, SAH_USES_LINK_DATA); + } + + if ( (in2 != NULL) && (status == FSL_RETURN_OK_S) ) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) in2, in2_length, + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + +/*! + * Add descriptor where neither link needs sync + * + * @param header The Sahara header value for the descriptor. + * @param in1 The first input buffer (or NULL) + * @param in1_length Size of @a in1 + * @param[out] in2 The second input buffer (or NULL) + * @param in2_length Size of @a in2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_two_d_desc(uint32_t header, + const uint8_t * in1, + uint32_t in1_length, + const uint8_t * in2, + uint32_t in2_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + printk("Entering sah_add_two_d_desc \n"); + + if (in1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) in1, in1_length, + SAH_USES_LINK_DATA); + } + + if ((in2 != NULL) && (status == FSL_RETURN_OK_S)) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) in2, in2_length, + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} /* sah_add_two_d_desc() */ + +/** + * Add descriptor where both links are inputs, the second one being a key. + * + * @param header The Sahara header value for the descriptor. + * @param in1 The first input buffer (or NULL) + * @param in1_length Size of @a in1 + * @param[out] in2 The second input buffer (or NULL) + * @param in2_length Size of @a in2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_in_key_desc(uint32_t header, + const uint8_t* in1, + uint32_t in1_length, + fsl_shw_sko_t* key_info, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + if (in1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) in1, in1_length, + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + goto out; + } + + status = sah_Create_Key_Link(mu, &link2, key_info); + + + if (status != FSL_RETURN_OK_S) { + goto out; + } + + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + +out: + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } + + return status; +} + + +/** + * Create two links using keys allocated in the scc + * + * @param header The Sahara header value for the descriptor. + * @param in1 The first input buffer (or NULL) + * @param in1_length Size of @a in1 + * @param[out] in2 The second input buffer (or NULL) + * @param in2_length Size of @a in2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_key_key_desc(uint32_t header, + fsl_shw_sko_t *key_info1, + fsl_shw_sko_t *key_info2, + const sah_Mem_Util *mu, + sah_Head_Desc **desc_chain) +{ + fsl_shw_return_t status; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + + status = sah_Create_Key_Link(mu, &link1, key_info1); + + if (status == FSL_RETURN_OK_S) { + status = sah_Create_Key_Link(mu, &link2, key_info2); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Add descriptor where first link is input, the second is a changing key + * + * @param header The Sahara header value for the descriptor. + * @param in1 The first input buffer (or NULL) + * @param in1_length Size of @a in1 + * @param[out] in2 The key for output + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_in_keyout_desc(uint32_t header, + const uint8_t* in1, + uint32_t in1_length, + fsl_shw_sko_t* key_info, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + if (in1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) in1, in1_length, + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + goto out; + } + + status = sah_Create_Key_Link(mu, &link2, key_info); + + if (status != FSL_RETURN_OK_S) { + goto out; + } + +link2->flags |= SAH_OUTPUT_LINK; /* mark key for output */ +status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + +out: + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } + + return status; +} + +/** + * Add descriptor where both links are outputs. + * + * @param header The Sahara header value for the descriptor. + * @param out1 The first output buffer (or NULL) + * @param out1_length Size of @a out1 + * @param[out] out2 The second output buffer (or NULL) + * @param out2_length Size of @a out2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_two_out_desc(uint32_t header, + uint8_t* out1, + uint32_t out1_length, + uint8_t* out2, + uint32_t out2_length, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + + if (out1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) out1, out1_length, + SAH_OUTPUT_LINK | SAH_USES_LINK_DATA); + } + + if ( (out2 != NULL) && (status == FSL_RETURN_OK_S) ) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) out2, out2_length, + SAH_OUTPUT_LINK | + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Add descriptor where first link is output, second is output + * + * @param header The Sahara header value for the descriptor. + * @param out1 The first output buffer (or NULL) + * @param out1_length Size of @a out1 + * @param[out] in2 The input buffer (or NULL) + * @param in2_length Size of @a in2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_out_in_desc(uint32_t header, + uint8_t* out1, + uint32_t out1_length, + const uint8_t* in2, + uint32_t in2_length, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + + if (out1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) out1, out1_length, + SAH_OUTPUT_LINK | + SAH_USES_LINK_DATA); + } + + if ( (in2 != NULL) && (status == FSL_RETURN_OK_S) ) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) in2, in2_length, + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Add descriptor where link1 is input buffer, link2 is output buffer. + * + * @param header The Sahara header value for the descriptor. + * @param in The input buffer + * @param in_length Size of the input buffer + * @param[out] out The output buffer + * @param out_length Size of the output buffer + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_in_out_desc(uint32_t header, + const uint8_t* in, uint32_t in_length, + uint8_t* out, uint32_t out_length, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + if (in != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) in, in_length, + SAH_USES_LINK_DATA); + } + + if ((status == FSL_RETURN_OK_S) && (out != NULL)) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) out, out_length, + SAH_OUTPUT_LINK | + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Add descriptor where link1 is a key, link2 is output buffer. + * + * @param header The Sahara header value for the descriptor. + * @param key_info Key information + * @param[out] out The output buffer + * @param out_length Size of the output buffer + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_key_out_desc(uint32_t header, + const fsl_shw_sko_t *key_info, + uint8_t* out, uint32_t out_length, + const sah_Mem_Util *mu, + sah_Head_Desc **desc_chain) +{ + fsl_shw_return_t status; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + status = sah_Create_Key_Link(mu, &link1, (fsl_shw_sko_t *) key_info); + if (status != FSL_RETURN_OK_S) { + goto out; + } + + + if (out != NULL) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) out, out_length, + SAH_OUTPUT_LINK | + SAH_USES_LINK_DATA); + if (status != FSL_RETURN_OK_S) { + goto out; + } + } +status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + +out: + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } + + return status; +} + + +/** + * Sanity checks the user context object fields to ensure that they make some + * sense before passing the uco as a parameter + * + * @brief Verify the user context object + * + * @param uco user context object + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_validate_uco(fsl_shw_uco_t *uco) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + + + /* check if file is opened */ + if (uco->sahara_openfd < 0) { + status = FSL_RETURN_NO_RESOURCE_S; + } else { + /* check flag combination: the only invalid setting of the + * blocking and callback flags is blocking with callback. So check + * for that + */ + if ((uco->flags & (FSL_UCO_BLOCKING_MODE | FSL_UCO_CALLBACK_MODE)) == + (FSL_UCO_BLOCKING_MODE | FSL_UCO_CALLBACK_MODE)) { + status = FSL_RETURN_BAD_FLAG_S; + } else { + /* check that memory utilities have been attached */ + if (uco->mem_util == NULL) { + status = FSL_RETURN_MEMORY_ERROR_S; + } else { + /* must have pool of at least 1, even for blocking mode */ + if (uco->pool_size == 0) { + status = FSL_RETURN_ERROR_S; + } else { + /* if callback flag is set, it better have a callback + * routine */ + if (uco->flags & FSL_UCO_CALLBACK_MODE) { + if (uco->callback == NULL) { + status = FSL_RETURN_INTERNAL_ERROR_S; + } + } + } + } + } + } + + return status; +} + + +/** + * Perform any post-processing on non-blocking results. + * + * For instance, free descriptor chains, compare authentication codes, ... + * + * @param user_ctx User context object + * @param result_info A set of results + */ +void sah_Postprocess_Results(fsl_shw_uco_t* user_ctx, sah_results* result_info) +{ + unsigned i; + + /* for each result returned */ + for (i = 0; i < *result_info->actual; i++) { + sah_Head_Desc* desc = result_info->results[i].user_desc; + uint8_t* out1 = desc->out1_ptr; + uint8_t* out2 = desc->out2_ptr; + uint32_t len = desc->out_len; + + /* + * For now, tne only post-processing besides freeing the + * chain is the need to check the auth code for fsl_shw_auth_decrypt(). + * + * If other uses are required in the future, this code will probably + * need a flag in the sah_Head_Desc (or a function pointer!) to + * determine what needs to be done. + */ + if ((out1 != NULL) && (out2 != NULL)) { + unsigned j; + for (j = 0; j < len; j++) { + if (out1[j] != out2[j]) { + /* Problem detected. Change result. */ + result_info->results[i].code = FSL_RETURN_AUTH_FAILED_S; + break; + } + } + /* free allocated 'calced_auth' */ + user_ctx->mem_util-> + mu_free(user_ctx->mem_util->mu_ref, out1); + } + + /* Free the API-created chain, unless tagged as not-from-API */ + if (! (desc->uco_flags & FSL_UCO_SAVE_DESC_CHAIN)) { + sah_Descriptor_Chain_Destroy(user_ctx->mem_util, &desc); + } + } +} + + +/* End of sf_util.c */ diff --git a/drivers/mxc/security/scc2_driver.c b/drivers/mxc/security/scc2_driver.c new file mode 100644 index 000000000000..5ebd6022f1a5 --- /dev/null +++ b/drivers/mxc/security/scc2_driver.c @@ -0,0 +1,2306 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! @file scc2_driver.c + * + * This is the driver code for the Security Controller version 2 (SCC2). It's + * interaction with the Linux kernel is from calls to #scc_init() when the + * driver is loaded, and #scc_cleanup() should the driver be unloaded. The + * driver uses locking and (task-sleep/task-wakeup) functions from the kernel. + * It also registers itself to handle the interrupt line(s) from the SCC. New + * to this version of the driver is an interface providing access to the secure + * partitions. This is in turn exposed to the API user through the + * fsl_shw_smalloc() series of functions. Other drivers in the kernel may use + * the remaining API functions to get at the services of the SCC. The main + * service provided is the Secure Memory, which allows encoding and decoding of + * secrets with a per-chip secret key. + * + * The SCC is single-threaded, and so is this module. When the scc_crypt() + * routine is called, it will lock out other accesses to the function. If + * another task is already in the module, the subsequent caller will spin on a + * lock waiting for the other access to finish. + * + * Note that long crypto operations could cause a task to spin for a while, + * preventing other kernel work (other than interrupt processing) to get done. + * + * The external (kernel module) interface is through the following functions: + * @li scc_get_configuration() @li scc_crypt() @li scc_zeroize_memories() @li + * scc_monitor_security_failure() @li scc_stop_monitoring_security_failure() + * @li scc_set_sw_alarm() @li scc_read_register() @li scc_write_register() @li + * scc_allocate_partition() @li scc_initialize_partition @li + * scc_release_partition() @li scc_diminish_permissions @li + * scc_encrypt_region() @li scc_decrypt_region() @li scc_virt_to_phys + * + * All other functions are internal to the driver. + */ + +#include "sahara2/include/portable_os.h" +#include "scc2_internals.h" +#include <linux/delay.h> + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + +#include <linux/device.h> +#include <mach/clock.h> +#include <linux/device.h> + +#else + +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> + +#endif + +#include <linux/dmapool.h> + +/** + * This is the set of errors which signal that access to the SCM RAM has + * failed or will fail. + */ +#define SCM_ACCESS_ERRORS \ + (SCM_ERRSTAT_ILM | SCM_ERRSTAT_SUP | SCM_ERRSTAT_ERC_MASK) + +/****************************************************************************** + * + * Global / Static Variables + * + *****************************************************************************/ + +#ifdef SCC_REGISTER_DEBUG + +#define REG_PRINT_BUFFER_SIZE 200 + +static char reg_print_buffer[REG_PRINT_BUFFER_SIZE]; + +typedef char *(*reg_print_routine_t) (uint32_t value, char *print_buffer, + int buf_size); + +#endif + +/** + * This is type void* so that a) it cannot directly be dereferenced, + * and b) pointer arithmetic on it will function in a 'normal way' for + * the offsets in scc_defines.h + * + * scc_base is the location in the iomap where the SCC's registers + * (and memory) start. + * + * The referenced data is declared volatile so that the compiler will + * not make any assumptions about the value of registers in the SCC, + * and thus will always reload the register into CPU memory before + * using it (i.e. wherever it is referenced in the driver). + * + * This value should only be referenced by the #SCC_READ_REGISTER and + * #SCC_WRITE_REGISTER macros and their ilk. All dereferences must be + * 32 bits wide. + */ +static volatile void *scc_base; + +/** Array to hold function pointers registered by + #scc_monitor_security_failure() and processed by + #scc_perform_callbacks() */ +static void (*scc_callbacks[SCC_CALLBACK_SIZE]) (void); +/*SCC need IRAM's base address but use only the partitions allocated for it.*/ +uint32_t scm_ram_phys_base = IRAM_BASE_ADDR; + +void *scm_ram_base = NULL; + +/** Calculated once for quick reference to size of the unreserved space in + * RAM in SCM. + */ +uint32_t scm_memory_size_bytes; + +/** Structure returned by #scc_get_configuration() */ +static scc_config_t scc_configuration = { + .driver_major_version = SCC_DRIVER_MAJOR_VERSION, + .driver_minor_version = SCC_DRIVER_MINOR_VERSION_2, + .scm_version = -1, + .smn_version = -1, + .block_size_bytes = -1, + .partition_size_bytes = -1, + .partition_count = -1, +}; + +/** Internal flag to know whether SCC is in Failed state (and thus many + * registers are unavailable). Once it goes failed, it never leaves it. */ +static volatile enum scc_status scc_availability = SCC_STATUS_INITIAL; + +/** Flag to say whether interrupt handler has been registered for + * SMN interrupt */ +static int smn_irq_set = 0; + +/** Flag to say whether interrupt handler has been registered for + * SCM interrupt */ +static int scm_irq_set = 0; + +/** This lock protects the #scc_callbacks list as well as the @c + * callbacks_performed flag in #scc_perform_callbacks. Since the data this + * protects may be read or written from either interrupt or base level, all + * operations should use the irqsave/irqrestore or similar to make sure that + * interrupts are inhibited when locking from base level. + */ +static os_lock_t scc_callbacks_lock = NULL; + +/** + * Ownership of this lock prevents conflicts on the crypto operation in the + * SCC. + */ +static os_lock_t scc_crypto_lock = NULL; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)) +/** Pointer to SCC's clock information. Initialized during scc_init(). */ +static struct clk *scc_clk = NULL; +#endif + +/** The lookup table for an 8-bit value. Calculated once + * by #scc_init_ccitt_crc(). + */ +static uint16_t scc_crc_lookup_table[256]; + +/****************************************************************************** + * + * Function Implementations - Externally Accessible + * + *****************************************************************************/ + +/** + * Allocate a partition of secure memory + * + * @param smid_value Value to use for the SMID register. Must be 0 for + * kernel mode access. + * @param[out] part_no (If successful) Assigned partition number. + * @param[out] part_base Kernel virtual address of the partition. + * @param[out] part_phys Physical address of the partition. + * + * @return + */ +scc_return_t scc_allocate_partition(uint32_t smid_value, + int *part_no, + void **part_base, uint32_t *part_phys) +{ + uint32_t i; + os_lock_context_t irq_flags = 0; /* for IRQ save/restore */ + int local_part; + scc_return_t retval = SCC_RET_FAIL; + void *base_addr = NULL; + uint32_t reg_value; + + local_part = -1; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + if (scc_availability == SCC_STATUS_UNIMPLEMENTED) { + goto out; + } + + /* ACQUIRE LOCK to prevent others from using crypto or acquiring a + * partition. Note that crypto operations could take a long time, so the + * calling process could potentially spin for some time. + */ + os_lock_save_context(scc_crypto_lock, irq_flags); + + do { + /* Find current state of partition ownership */ + reg_value = SCC_READ_REGISTER(SCM_PART_OWNERS_REG); + + /* Search for a free one */ + for (i = 0; i < scc_configuration.partition_count; i++) { + if (((reg_value >> (SCM_POWN_SHIFT * i)) + & SCM_POWN_MASK) == SCM_POWN_PART_FREE) { + break; /* found a free one */ + } + } + if (i == local_part) { + /* found this one last time, and failed to allocated it */ + pr_debug(KERN_ERR "Partition %d cannot be allocated\n", + i); + goto out; + } + if (i >= scc_configuration.partition_count) { + retval = SCC_RET_INSUFFICIENT_SPACE; /* all used up */ + goto out; + } + + pr_debug + ("SCC2: Attempting to allocate partition %i, owners:%08x\n", + i, SCC_READ_REGISTER(SCM_PART_OWNERS_REG)); + + local_part = i; + /* Store SMID to grab a partition */ + SCC_WRITE_REGISTER(SCM_SMID0_REG + + SCM_SMID_WIDTH * (local_part), smid_value); + mdelay(2); + + /* Now make sure it is ours... ? */ + reg_value = SCC_READ_REGISTER(SCM_PART_OWNERS_REG); + + if (((reg_value >> (SCM_POWN_SHIFT * (local_part))) + & SCM_POWN_MASK) != SCM_POWN_PART_OWNED) { + continue; /* try for another */ + } + base_addr = scm_ram_base + + (local_part * scc_configuration.partition_size_bytes); + break; + } while (1); + +out: + + /* Free the lock */ + os_unlock_restore_context(scc_callbacks_lock, irq_flags); + + /* If the base address was assigned, then a partition was successfully + * acquired. + */ + if (base_addr != NULL) { + pr_debug("SCC2 Part owners: %08x, engaged: %08x\n", + reg_value, SCC_READ_REGISTER(SCM_PART_ENGAGED_REG)); + pr_debug("SCC2 MAP for part %d: %08x\n", + local_part, + SCC_READ_REGISTER(SCM_ACC0_REG + 8 * local_part)); + + /* Copy the partition information to the data structures passed by the + * user. + */ + *part_no = local_part; + *part_base = base_addr; + *part_phys = (uint32_t) scm_ram_phys_base + + (local_part * scc_configuration.partition_size_bytes); + retval = SCC_RET_OK; + + pr_debug + ("SCC2 partition engaged. Kernel address: %p. Physical " + "address: %p, pfn: %08x\n", *part_base, (void *)*part_phys, + __phys_to_pfn(*part_phys)); + } + + return retval; +} /* allocate_partition() */ + +/** + * Release a partition of secure memory + * + * @param part_base Kernel virtual address of the partition to be released. + * + * @return SCC_RET_OK if successful. + */ +scc_return_t scc_release_partition(void *part_base) +{ + uint32_t partition_no; + + if (part_base == NULL) { + return SCC_RET_FAIL; + } + + /* Ensure that this is a proper partition location */ + partition_no = SCM_PART_NUMBER((uint32_t) part_base); + + pr_debug("SCC2: Attempting to release partition %i, owners:%08x\n", + partition_no, SCC_READ_REGISTER(SCM_PART_OWNERS_REG)); + + /* check that the partition is ours to de-establish */ + if (!host_owns_partition(partition_no)) { + return SCC_RET_FAIL; + } + + /* TODO: The state of the zeroize engine (SRS field in the Command Status + * Register) should be examined before issuing the zeroize command here. + * To make the driver thread-safe, a lock should be taken out before + * issuing the check and released after the zeroize command has been + * issued. + */ + + /* Zero the partition to release it */ + scc_write_register(SCM_ZCMD_REG, + (partition_no << SCM_ZCMD_PART_SHIFT) | + (ZCMD_DEALLOC_PART << SCM_ZCMD_CCMD_SHIFT)); + mdelay(2); + + pr_debug("SCC2: done releasing partition %i, owners:%08x\n", + partition_no, SCC_READ_REGISTER(SCM_PART_OWNERS_REG)); + + /* Check that the de-assignment went correctly */ + if (host_owns_partition(partition_no)) { + return SCC_RET_FAIL; + } + + return SCC_RET_OK; +} + +/** + * Diminish the permissions on a partition of secure memory + * + * @param part_base Kernel virtual address of the partition. + * @param permissions ORed values of the type SCM_PERM_* which will be used as + * initial partition permissions. SHW API users should use + * the FSL_PERM_* definitions instead. + * + * @return SCC_RET_OK if successful. + */ +scc_return_t scc_diminish_permissions(void *part_base, uint32_t permissions) +{ + uint32_t partition_no; + uint32_t permissions_requested; + permissions_requested = permissions; + + /* ensure that this is a proper partition location */ + partition_no = SCM_PART_NUMBER((uint32_t) part_base); + + /* invert the permissions, masking out unused bits */ + permissions = (~permissions) & SCM_PERM_MASK; + + /* attempt to diminish the permissions */ + scc_write_register(SCM_ACC0_REG + 8 * partition_no, permissions); + mdelay(2); + + /* Reading it back puts it into the original form */ + permissions = SCC_READ_REGISTER(SCM_ACC0_REG + 8 * partition_no); + if (permissions == permissions_requested) { + pr_debug("scc_partition_diminish_perms: successful\n"); + pr_debug("scc_partition_diminish_perms: successful\n"); + return SCC_RET_OK; + } + pr_debug("scc_partition_diminish_perms: not successful\n"); + + return SCC_RET_FAIL; +} + +extern scc_partition_status_t scc_partition_status(void *part_base) +{ + uint32_t part_no; + uint32_t part_owner; + + /* Determine the partition number from the address */ + part_no = SCM_PART_NUMBER((uint32_t) part_base); + + /* Check if the partition is implemented */ + if (part_no >= scc_configuration.partition_count) { + return SCC_PART_S_UNUSABLE; + } + + /* Determine the value of the partition owners register */ + part_owner = (SCC_READ_REGISTER(SCM_PART_OWNERS_REG) + >> (part_no * SCM_POWN_SHIFT)) & SCM_POWN_MASK; + + switch (part_owner) { + case SCM_POWN_PART_OTHER: + return SCC_PART_S_UNAVAILABLE; + break; + case SCM_POWN_PART_FREE: + return SCC_PART_S_AVAILABLE; + break; + case SCM_POWN_PART_OWNED: + /* could be allocated or engaged*/ + if (partition_engaged(part_no)) { + return SCC_PART_S_ENGAGED; + } else { + return SCC_PART_S_ALLOCATED; + } + break; + case SCM_POWN_PART_UNUSABLE: + default: + return SCC_PART_S_UNUSABLE; + break; + } +} + +/** + * Calculate the physical address from the kernel virtual address. + * + * @param address Kernel virtual address of data in an Secure Partition. + * @return Physical address of said data. + */ +uint32_t scc_virt_to_phys(void *address) +{ + return (uint32_t) address - (uint32_t) scm_ram_base + + (uint32_t) scm_ram_phys_base; +} + +/** + * Engage partition of secure memory + * + * @param part_base (kernel) Virtual + * @param UMID NULL, or 16-byte UMID for partition security + * @param permissions ORed values from fsl_shw_permission_t which + * will be used as initial partiition permissions. + * + * @return SCC_RET_OK if successful. + */ + +scc_return_t +scc_engage_partition(void *part_base, + const uint8_t *UMID, uint32_t permissions) +{ + uint32_t partition_no; + uint8_t *UMID_base = part_base + 0x10; + uint32_t *MAP_base = part_base; + uint8_t i; + + partition_no = SCM_PART_NUMBER((uint32_t) part_base); + + if (!host_owns_partition(partition_no) || + partition_engaged(partition_no) || + !(SCC_READ_REGISTER(SCM_SMID0_REG + (partition_no * 8)) == 0)) { + + return SCC_RET_FAIL; + } + + if (UMID != NULL) { + for (i = 0; i < 16; i++) { + UMID_base[i] = UMID[i]; + } + } + + MAP_base[0] = permissions; + + udelay(20); + + /* Check that the partition was engaged correctly, and that it has the + * proper permissions. + */ + + if ((!partition_engaged(partition_no)) || + (permissions != + SCC_READ_REGISTER(SCM_ACC0_REG + 8 * partition_no))) { + return SCC_RET_FAIL; + } + + return SCC_RET_OK; +} + +/*****************************************************************************/ +/* fn scc_init() */ +/*****************************************************************************/ +/** + * Initialize the driver at boot time or module load time. + * + * Register with the kernel as the interrupt handler for the SCC interrupt + * line(s). + * + * Map the SCC's register space into the driver's memory space. + * + * Query the SCC for its configuration and status. Save the configuration in + * #scc_configuration and save the status in #scc_availability. Called by the + * kernel. + * + * Do any locking/wait queue initialization which may be necessary. + * + * The availability fuse may be checked, depending on platform. + */ +static int scc_init(void) +{ + uint32_t smn_status; + int i; + int return_value = -EIO; /* assume error */ + + if (scc_availability == SCC_STATUS_INITIAL) { + + /* Set this until we get an initial reading */ + scc_availability = SCC_STATUS_CHECKING; + + /* Initialize the constant for the CRC function */ + scc_init_ccitt_crc(); + + /* initialize the callback table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + scc_callbacks[i] = 0; + } + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + mxc_clks_enable(SCC_CLK); +#else + scc_clk = clk_get(NULL, "scc_clk"); + if (scc_clk != ERR_PTR(ENOENT)) { + clk_enable(scc_clk); + } +#endif + + /* Set up the hardware access locks */ + scc_callbacks_lock = os_lock_alloc_init(); + scc_crypto_lock = os_lock_alloc_init(); + if (scc_callbacks_lock == NULL || scc_crypto_lock == NULL) { + os_printk(KERN_ERR + "SCC2: Failed to allocate context locks. Exiting.\n"); + goto out; + } + + /* See whether there is an SCC available */ + if (0 && !SCC_ENABLED()) { + os_printk(KERN_ERR + "SCC2: Fuse for SCC is set to disabled. Exiting.\n"); + goto out; + } + /* Map the SCC (SCM and SMN) memory on the internal bus into + kernel address space */ + scc_base = (void *)IO_ADDRESS(SCC_BASE); + if (scc_base == NULL) { + os_printk(KERN_ERR + "SCC2: Register mapping failed. Exiting.\n"); + goto out; + } + + /* If that worked, we can try to use the SCC */ + /* Get SCM into 'clean' condition w/interrupts cleared & + disabled */ + SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0); + + /* Clear error status register */ + (void)SCC_READ_REGISTER(SCM_ERR_STATUS_REG); + + /* + * There is an SCC. Determine its current state. Side effect + * is to populate scc_config and scc_availability + */ + smn_status = scc_grab_config_values(); + + /* Try to set up interrupt handler(s) */ + if (scc_availability != SCC_STATUS_OK) { + goto out; + } + + if (cpu_is_mx51_rev(CHIP_REV_2_0) < 0) + scm_ram_phys_base += 0x8000; + + scm_ram_base = (void *)ioremap_nocache(scm_ram_phys_base, + scc_configuration. + partition_count * + scc_configuration. + partition_size_bytes); + if (scm_ram_base == NULL) { + os_printk(KERN_ERR + "SCC2: RAM failed to remap: %p for %d bytes\n", + (void *)scm_ram_phys_base, + scc_configuration.partition_count * + scc_configuration.partition_size_bytes); + goto out; + } + pr_debug("SCC2: RAM at Physical %p / Virtual %p\n", + (void *)scm_ram_phys_base, scm_ram_base); + + pr_debug("Secure Partition Table: Found %i partitions\n", + scc_configuration.partition_count); + + if (setup_interrupt_handling() != 0) { + unsigned err_cond; + /** + * The error could be only that the SCM interrupt was + * not set up. This interrupt is always masked, so + * that is not an issue. + * The SMN's interrupt may be shared on that line, it + * may be separate, or it may not be wired. Do what + * is necessary to check its status. + * Although the driver is coded for possibility of not + * having SMN interrupt, the fact that there is one + * means it should be available and used. + */ +#ifdef USE_SMN_INTERRUPT + err_cond = !smn_irq_set; /* Separate. Check SMN binding */ +#elif !defined(NO_SMN_INTERRUPT) + err_cond = !scm_irq_set; /* Shared. Check SCM binding */ +#else + err_cond = FALSE; /* SMN not wired at all. Ignore. */ +#endif + if (err_cond) { + /* setup was not able to set up SMN interrupt */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; + goto out; + } + } + + /* interrupt handling returned non-zero */ + /* Get SMN into 'clean' condition w/interrupts cleared & + enabled */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT + | SMN_COMMAND_ENABLE_INTERRUPT); + + out: + /* + * If status is SCC_STATUS_UNIMPLEMENTED or is still + * SCC_STATUS_CHECKING, could be leaving here with the driver partially + * initialized. In either case, cleanup (which will mark the SCC as + * UNIMPLEMENTED). + */ + if (scc_availability == SCC_STATUS_CHECKING || + scc_availability == SCC_STATUS_UNIMPLEMENTED) { + scc_cleanup(); + } else { + return_value = 0; /* All is well */ + } + } + /* ! STATUS_INITIAL */ + os_printk(KERN_ALERT "SCC2: Driver Status is %s\n", + (scc_availability == SCC_STATUS_INITIAL) ? "INITIAL" : + (scc_availability == SCC_STATUS_CHECKING) ? "CHECKING" : + (scc_availability == + SCC_STATUS_UNIMPLEMENTED) ? "UNIMPLEMENTED" + : (scc_availability == + SCC_STATUS_OK) ? "OK" : (scc_availability == + SCC_STATUS_FAILED) ? "FAILED" : + "UNKNOWN"); + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_disable(SCC_CLK); +#else + if (scc_clk != ERR_PTR(ENOENT)) + clk_disable(scc_clk); +#endif + + return return_value; +} /* scc_init */ + +/*****************************************************************************/ +/* fn scc_cleanup() */ +/*****************************************************************************/ +/** + * Perform cleanup before driver/module is unloaded by setting the machine + * state close to what it was when the driver was loaded. This function is + * called when the kernel is shutting down or when this driver is being + * unloaded. + * + * A driver like this should probably never be unloaded, especially if there + * are other module relying upon the callback feature for monitoring the SCC + * status. + * + * In any case, cleanup the callback table (by clearing out all of the + * pointers). Deregister the interrupt handler(s). Unmap SCC registers. + * + * Note that this will not release any partitions that have been allocated. + * + */ +static void scc_cleanup(void) +{ + int i; + + /******************************************************/ + + /* Mark the driver / SCC as unusable. */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; + + /* Clear out callback table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + scc_callbacks[i] = 0; + } + + /* If SCC has been mapped in, clean it up and unmap it */ + if (scc_base) { + /* For the SCM, disable interrupts. */ + SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0); + + /* For the SMN, clear and disable interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT); + } + + /* Now that interrupts cannot occur, disassociate driver from the interrupt + * lines. + */ + + /* Deregister SCM interrupt handler */ + if (scm_irq_set) { + os_deregister_interrupt(INT_SCC_SCM); + } + + /* Deregister SMN interrupt handler */ + if (smn_irq_set) { +#ifdef USE_SMN_INTERRUPT + os_deregister_interrupt(INT_SCC_SMN); +#endif + } + + /* Finally, release the mapped memory */ + iounmap(scm_ram_base); + + if (scc_callbacks_lock != NULL) + os_lock_deallocate(scc_callbacks_lock); + + if (scc_crypto_lock != NULL) + os_lock_deallocate(scc_crypto_lock); + + /*Disabling SCC Clock*/ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_disable(SCC_CLK); +#else + if (scc_clk != ERR_PTR(ENOENT)) + clk_disable(scc_clk); + clk_put(scc_clk); +#endif + pr_debug("SCC2 driver cleaned up.\n"); + +} /* scc_cleanup */ + +/*****************************************************************************/ +/* fn scc_get_configuration() */ +/*****************************************************************************/ +scc_config_t *scc_get_configuration(void) +{ + /* + * If some other driver calls scc before the kernel does, make sure that + * this driver's initialization is performed. + */ + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /** + * If there is no SCC, yet the driver exists, the value -1 will be in + * the #scc_config_t fields for other than the driver versions. + */ + return &scc_configuration; +} /* scc_get_configuration */ + +/*****************************************************************************/ +/* fn scc_zeroize_memories() */ +/*****************************************************************************/ +scc_return_t scc_zeroize_memories(void) +{ + scc_return_t return_status = SCC_RET_FAIL; + + return return_status; +} /* scc_zeroize_memories */ + +/*****************************************************************************/ +/* fn scc_set_sw_alarm() */ +/*****************************************************************************/ +void scc_set_sw_alarm(void) +{ + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Update scc_availability based on current SMN status. This might + * perform callbacks. + */ + (void)scc_update_state(); + + /* if everything is OK, make it fail */ + if (scc_availability == SCC_STATUS_OK) { + + /* sound the alarm (and disable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_SET_SOFTWARE_ALARM); + + scc_availability = SCC_STATUS_FAILED; /* Remember what we've done */ + + /* In case SMN interrupt is not available, tell the world */ + scc_perform_callbacks(); + } + + return; +} /* scc_set_sw_alarm */ + +/*****************************************************************************/ +/* fn scc_monitor_security_failure() */ +/*****************************************************************************/ +scc_return_t scc_monitor_security_failure(void callback_func(void)) +{ + int i; + os_lock_context_t irq_flags; /* for IRQ save/restore */ + scc_return_t return_status = SCC_RET_TOO_MANY_FUNCTIONS; + int function_stored = FALSE; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Acquire lock of callbacks table. Could be spin_lock_irq() if this + * routine were just called from base (not interrupt) level + */ + os_lock_save_context(scc_callbacks_lock, irq_flags); + + /* Search through table looking for empty slot */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + if (scc_callbacks[i] == callback_func) { + if (function_stored) { + /* Saved duplicate earlier. Clear this later one. */ + scc_callbacks[i] = NULL; + } + /* Exactly one copy is now stored */ + return_status = SCC_RET_OK; + break; + } else if (scc_callbacks[i] == NULL && !function_stored) { + /* Found open slot. Save it and remember */ + scc_callbacks[i] = callback_func; + return_status = SCC_RET_OK; + function_stored = TRUE; + } + } + + /* Free the lock */ + os_unlock_restore_context(scc_callbacks_lock, irq_flags); + + return return_status; +} /* scc_monitor_security_failure */ + +/*****************************************************************************/ +/* fn scc_stop_monitoring_security_failure() */ +/*****************************************************************************/ +void scc_stop_monitoring_security_failure(void callback_func(void)) +{ + os_lock_context_t irq_flags; /* for IRQ save/restore */ + int i; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Acquire lock of callbacks table. Could be spin_lock_irq() if this + * routine were just called from base (not interrupt) level + */ + os_lock_save_context(scc_callbacks_lock, irq_flags); + + /* Search every entry of the table for this function */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + if (scc_callbacks[i] == callback_func) { + scc_callbacks[i] = NULL; /* found instance - clear it out */ + break; + } + } + + /* Free the lock */ + os_unlock_restore_context(scc_callbacks_lock, irq_flags); + + return; +} /* scc_stop_monitoring_security_failure */ + +/*****************************************************************************/ +/* fn scc_read_register() */ +/*****************************************************************************/ +scc_return_t scc_read_register(int register_offset, uint32_t * value) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t smn_status; + uint32_t scm_status; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* First layer of protection -- completely unaccessible SCC */ + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + + /* Second layer -- that offset is valid */ + if (register_offset != SMN_BB_DEC_REG && /* write only! */ + check_register_offset(register_offset) == SCC_RET_OK) { + + /* Get current status / update local state */ + smn_status = scc_update_state(); + scm_status = SCC_READ_REGISTER(SCM_STATUS_REG); + + /* + * Third layer - verify that the register being requested is + * available in the current state of the SCC. + */ + if ((return_status = + check_register_accessible(register_offset, + smn_status, + scm_status)) == + SCC_RET_OK) { + *value = SCC_READ_REGISTER(register_offset); + } + } + } + + return return_status; +} /* scc_read_register */ + +/*****************************************************************************/ +/* fn scc_write_register() */ +/*****************************************************************************/ +scc_return_t scc_write_register(int register_offset, uint32_t value) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t smn_status; + uint32_t scm_status; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* First layer of protection -- completely unaccessible SCC */ + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + + /* Second layer -- that offset is valid */ + if (!((register_offset == SCM_STATUS_REG) || /* These registers are */ + (register_offset == SCM_VERSION_REG) || /* Read Only */ + (register_offset == SMN_BB_CNT_REG) || + (register_offset == SMN_TIMER_REG)) && + check_register_offset(register_offset) == SCC_RET_OK) { + + /* Get current status / update local state */ + smn_status = scc_update_state(); + scm_status = SCC_READ_REGISTER(SCM_STATUS_REG); + + /* + * Third layer - verify that the register being requested is + * available in the current state of the SCC. + */ + if (check_register_accessible + (register_offset, smn_status, scm_status) == 0) { + SCC_WRITE_REGISTER(register_offset, value); + return_status = SCC_RET_OK; + } + } + } + + return return_status; +} /* scc_write_register() */ + +/****************************************************************************** + * + * Function Implementations - Internal + * + *****************************************************************************/ + +/*****************************************************************************/ +/* fn scc_irq() */ +/*****************************************************************************/ +/** + * This is the interrupt handler for the SCC. + * + * This function checks the SMN Status register to see whether it + * generated the interrupt, then it checks the SCM Status register to + * see whether it needs attention. + * + * If an SMN Interrupt is active, then the SCC state set to failure, and + * #scc_perform_callbacks() is invoked to notify any interested parties. + * + * The SCM Interrupt should be masked, as this driver uses polling to determine + * when the SCM has completed a crypto or zeroing operation. Therefore, if the + * interrupt is active, the driver will just clear the interrupt and (re)mask. + */ +OS_DEV_ISR(scc_irq) +{ + uint32_t smn_status; + uint32_t scm_status; + int handled = 0; /* assume interrupt isn't from SMN */ +#if defined(USE_SMN_INTERRUPT) + int smn_irq = INT_SCC_SMN; /* SMN interrupt is on a line by itself */ +#elif defined (NO_SMN_INTERRUPT) + int smn_irq = -1; /* not wired to CPU at all */ +#else + int smn_irq = INT_SCC_SCM; /* SMN interrupt shares a line with SCM */ +#endif + + /* Update current state... This will perform callbacks... */ + smn_status = scc_update_state(); + + /* SMN is on its own interrupt line. Verify the IRQ was triggered + * before clearing the interrupt and marking it handled. */ + if ((os_dev_get_irq() == smn_irq) && + (smn_status & SMN_STATUS_SMN_STATUS_IRQ)) { + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT); + handled++; /* tell kernel that interrupt was handled */ + } + + /* Check on the health of the SCM */ + scm_status = SCC_READ_REGISTER(SCM_STATUS_REG); + + /* The driver masks interrupts, so this should never happen. */ + if (os_dev_get_irq() == INT_SCC_SCM) { + /* but if it does, try to prevent it in the future */ + SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0); + handled++; + } + + /* Any non-zero value of handled lets kernel know we got something */ + os_dev_isr_return(handled); +} + +/*****************************************************************************/ +/* fn scc_perform_callbacks() */ +/*****************************************************************************/ +/** Perform callbacks registered by #scc_monitor_security_failure(). + * + * Make sure callbacks only happen once... Since there may be some reason why + * the interrupt isn't generated, this routine could be called from base(task) + * level. + * + * One at a time, go through #scc_callbacks[] and call any non-null pointers. + */ +static void scc_perform_callbacks(void) +{ + static int callbacks_performed = 0; + unsigned long irq_flags; /* for IRQ save/restore */ + int i; + + /* Acquire lock of callbacks table and callbacks_performed flag */ + os_lock_save_context(scc_callbacks_lock, irq_flags); + + if (!callbacks_performed) { + callbacks_performed = 1; + + /* Loop over all of the entries in the table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + /* If not null, ... */ + if (scc_callbacks[i]) { + scc_callbacks[i] (); /* invoke the callback routine */ + } + } + } + + os_unlock_restore_context(scc_callbacks_lock, irq_flags); + + return; +} + +/*****************************************************************************/ +/* fn scc_update_state() */ +/*****************************************************************************/ +/** + * Make certain SCC is still running. + * + * Side effect is to update #scc_availability and, if the state goes to failed, + * run #scc_perform_callbacks(). + * + * (If #SCC_BRINGUP is defined, bring SCC to secure state if it is found to be + * in health check state) + * + * @return Current value of #SMN_STATUS_REG register. + */ +static uint32_t scc_update_state(void) +{ + uint32_t smn_status_register = SMN_STATE_FAIL; + int smn_state; + + /* if FAIL or UNIMPLEMENTED, don't bother */ + if (scc_availability == SCC_STATUS_CHECKING || + scc_availability == SCC_STATUS_OK) { + + smn_status_register = SCC_READ_REGISTER(SMN_STATUS_REG); + smn_state = smn_status_register & SMN_STATUS_STATE_MASK; + +#ifdef SCC_BRINGUP + /* If in Health Check while booting, try to 'bringup' to Secure mode */ + if (scc_availability == SCC_STATUS_CHECKING && + smn_state == SMN_STATE_HEALTH_CHECK) { + /* Code up a simple algorithm for the ASC */ + SCC_WRITE_REGISTER(SMN_SEQ_START_REG, 0xaaaa); + SCC_WRITE_REGISTER(SMN_SEQ_END_REG, 0x5555); + SCC_WRITE_REGISTER(SMN_SEQ_CHECK_REG, 0x5555); + /* State should be SECURE now */ + smn_status_register = SCC_READ_REGISTER(SMN_STATUS); + smn_state = smn_status_register & SMN_STATUS_STATE_MASK; + } +#endif + + /* + * State should be SECURE or NON_SECURE for operation of the part. If + * FAIL, mark failed (i.e. limited access to registers). Any other + * state, mark unimplemented, as the SCC is unuseable. + */ + if (smn_state == SMN_STATE_SECURE + || smn_state == SMN_STATE_NON_SECURE) { + /* Healthy */ + scc_availability = SCC_STATUS_OK; + } else if (smn_state == SMN_STATE_FAIL) { + scc_availability = SCC_STATUS_FAILED; /* uh oh - unhealthy */ + scc_perform_callbacks(); + os_printk(KERN_ERR "SCC2: SCC went into FAILED mode\n"); + } else { + /* START, ZEROIZE RAM, HEALTH CHECK, or unknown */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; /* unuseable */ + os_printk(KERN_ERR + "SCC2: SCC declared UNIMPLEMENTED\n"); + } + } + /* if availability is initial or ok */ + return smn_status_register; +} + +/*****************************************************************************/ +/* fn scc_init_ccitt_crc() */ +/*****************************************************************************/ +/** + * Populate the partial CRC lookup table. + * + * @return none + * + */ +static void scc_init_ccitt_crc(void) +{ + int dividend; /* index for lookup table */ + uint16_t remainder; /* partial value for a given dividend */ + int bit; /* index into bits of a byte */ + + /* + * Compute the remainder of each possible dividend. + */ + for (dividend = 0; dividend < 256; ++dividend) { + /* + * Start with the dividend followed by zeros. + */ + remainder = dividend << (8); + + /* + * Perform modulo-2 division, a bit at a time. + */ + for (bit = 8; bit > 0; --bit) { + /* + * Try to divide the current data bit. + */ + if (remainder & 0x8000) { + remainder = (remainder << 1) ^ CRC_POLYNOMIAL; + } else { + remainder = (remainder << 1); + } + } + + /* + * Store the result into the table. + */ + scc_crc_lookup_table[dividend] = remainder; + } + +} /* scc_init_ccitt_crc() */ + +/*****************************************************************************/ +/* fn grab_config_values() */ +/*****************************************************************************/ +/** + * grab_config_values() will read the SCM Configuration and SMN Status + * registers and store away version and size information for later use. + * + * @return The current value of the SMN Status register. + */ +static uint32_t scc_grab_config_values(void) +{ + uint32_t scm_version_register; + uint32_t smn_status_register = SMN_STATE_FAIL; + + if (scc_availability != SCC_STATUS_CHECKING) { + goto out; + } + scm_version_register = SCC_READ_REGISTER(SCM_VERSION_REG); + pr_debug("SCC2 Driver: SCM version is 0x%08x\n", scm_version_register); + + /* Get SMN status and update scc_availability */ + smn_status_register = scc_update_state(); + pr_debug("SCC2 Driver: SMN status is 0x%08x\n", smn_status_register); + + /* save sizes and versions information for later use */ + scc_configuration.block_size_bytes = 16; /* BPCP ? */ + scc_configuration.partition_count = + 1 + ((scm_version_register & SCM_VER_NP_MASK) >> SCM_VER_NP_SHIFT); + scc_configuration.partition_size_bytes = + 1 << ((scm_version_register & SCM_VER_BPP_MASK) >> + SCM_VER_BPP_SHIFT); + scc_configuration.scm_version = + (scm_version_register & SCM_VER_MAJ_MASK) >> SCM_VER_MAJ_SHIFT; + scc_configuration.smn_version = + (smn_status_register & SMN_STATUS_VERSION_ID_MASK) + >> SMN_STATUS_VERSION_ID_SHIFT; + if (scc_configuration.scm_version != SCM_MAJOR_VERSION_2) { + scc_availability = SCC_STATUS_UNIMPLEMENTED; /* Unknown version */ + } + + out: + return smn_status_register; +} /* grab_config_values */ + +/*****************************************************************************/ +/* fn setup_interrupt_handling() */ +/*****************************************************************************/ +/** + * Register the SCM and SMN interrupt handlers. + * + * Called from #scc_init() + * + * @return 0 on success + */ +static int setup_interrupt_handling(void) +{ + int smn_error_code = -1; + int scm_error_code = -1; + + /* Disnable SCM interrupts */ + SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0); + +#ifdef USE_SMN_INTERRUPT + /* Install interrupt service routine for SMN. */ + smn_error_code = os_register_interrupt(SCC_DRIVER_NAME, + INT_SCC_SMN, scc_irq); + if (smn_error_code != 0) { + os_printk(KERN_ERR + "SCC2 Driver: Error installing SMN Interrupt Handler: %d\n", + smn_error_code); + } else { + smn_irq_set = 1; /* remember this for cleanup */ + /* Enable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT | + SMN_COMMAND_ENABLE_INTERRUPT); + } +#else + smn_error_code = 0; /* no problems... will handle later */ +#endif + + /* + * Install interrupt service routine for SCM (or both together). + */ + scm_error_code = os_register_interrupt(SCC_DRIVER_NAME, + INT_SCC_SCM, scc_irq); + if (scm_error_code != 0) { +#ifndef MXC + os_printk(KERN_ERR + "SCC2 Driver: Error installing SCM Interrupt Handler: %d\n", + scm_error_code); +#else + os_printk(KERN_ERR + "SCC2 Driver: Error installing SCC Interrupt Handler: %d\n", + scm_error_code); +#endif + } else { + scm_irq_set = 1; /* remember this for cleanup */ +#if defined(USE_SMN_INTERRUPT) && !defined(NO_SMN_INTERRUPT) + /* Enable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT | + SMN_COMMAND_ENABLE_INTERRUPT); +#endif + } + + /* Return an error if one was encountered */ + return scm_error_code ? scm_error_code : smn_error_code; +} /* setup_interrupt_handling */ + +/*****************************************************************************/ +/* fn scc_do_crypto() */ +/*****************************************************************************/ +/** Have the SCM perform the crypto function. + * + * Set up length register, and the store @c scm_control into control register + * to kick off the operation. Wait for completion, gather status, clear + * interrupt / status. + * + * @param byte_count number of bytes to perform in this operation + * @param scm_command Bit values to be set in @c SCM_CCMD_REG register + * + * @return 0 on success, value of #SCM_ERR_STATUS_REG on failure + */ +static uint32_t scc_do_crypto(int byte_count, uint32_t scm_command) +{ + int block_count = byte_count / SCC_BLOCK_SIZE_BYTES(); + uint32_t crypto_status; + scc_return_t ret; + + /* This seems to be necessary in order to allow subsequent cipher + * operations to succeed when a partition is deallocated/reallocated! + */ + (void)SCC_READ_REGISTER(SCM_STATUS_REG); + + /* In length register, 0 means 1, etc. */ + scm_command |= (block_count - 1) << SCM_CCMD_LENGTH_SHIFT; + + /* set modes and kick off the operation */ + SCC_WRITE_REGISTER(SCM_CCMD_REG, scm_command); + + ret = scc_wait_completion(&crypto_status); + + /* Only done bit should be on */ + if (crypto_status & SCM_STATUS_ERR) { + /* Replace with error status instead */ + crypto_status = SCC_READ_REGISTER(SCM_ERR_STATUS_REG); + pr_debug("SCM Failure: 0x%x\n", crypto_status); + if (crypto_status == 0) { + /* That came up 0. Turn on arbitrary bit to signal error. */ + crypto_status = SCM_ERRSTAT_ILM; + } + } else { + crypto_status = 0; + } + pr_debug("SCC2: Done waiting.\n"); + + return crypto_status; +} + +/** + * Encrypt a region of secure memory. + * + * @param part_base Kernel virtual address of the partition. + * @param offset_bytes Offset from the start of the partition to the plaintext + * data. + * @param byte_count Length of the region (octets). + * @param black_data Physical location to store the encrypted data. + * @param IV Value to use for the IV. + * @param cypher_mode Cyphering mode to use, specified by type + * #scc_cypher_mode_t + * + * @return SCC_RET_OK if successful. + */ +scc_return_t +scc_encrypt_region(uint32_t part_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t *black_data, + uint32_t *IV, scc_cypher_mode_t cypher_mode) +{ + os_lock_context_t irq_flags; /* for IRQ save/restore */ + scc_return_t status = SCC_RET_OK; + uint32_t crypto_status; + uint32_t scm_command; + int offset_blocks = offset_bytes / SCC_BLOCK_SIZE_BYTES(); + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_enable(SCC_CLK); +#else + if (scc_clk != ERR_PTR(ENOENT)) + clk_enable(scc_clk); +#endif + + scm_command = ((offset_blocks << SCM_CCMD_OFFSET_SHIFT) | + (SCM_PART_NUMBER(part_base) << SCM_CCMD_PART_SHIFT)); + + switch (cypher_mode) { + case SCC_CYPHER_MODE_CBC: + scm_command |= SCM_CCMD_AES_ENC_CBC; + break; + case SCC_CYPHER_MODE_ECB: + scm_command |= SCM_CCMD_AES_ENC_ECB; + break; + default: + status = SCC_RET_FAIL; + break; + } + + pr_debug("Received encrypt request. SCM_C_BLACK_ST_REG: %p, " + "scm_Command: %08x, length: %i (part_base: %08x, " + "offset: %i)\n", + black_data, scm_command, byte_count, part_base, offset_blocks); + + if (status != SCC_RET_OK) + goto out; + + /* ACQUIRE LOCK to prevent others from using crypto or releasing slot */ + os_lock_save_context(scc_crypto_lock, irq_flags); + + if (status == SCC_RET_OK) { + SCC_WRITE_REGISTER(SCM_C_BLACK_ST_REG, (uint32_t) black_data); + + /* Only write the IV if it will actually be used */ + if (cypher_mode == SCC_CYPHER_MODE_CBC) { + /* Write the IV register */ + SCC_WRITE_REGISTER(SCM_AES_CBC_IV0_REG, *(IV)); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV1_REG, *(IV + 1)); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV2_REG, *(IV + 2)); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV3_REG, *(IV + 3)); + } + + /* Set modes and kick off the encryption */ + crypto_status = scc_do_crypto(byte_count, scm_command); + + if (crypto_status != 0) { + pr_debug("SCM encrypt red crypto failure: 0x%x\n", + crypto_status); + } else { + status = SCC_RET_OK; + pr_debug("SCC2: Encrypted %d bytes\n", byte_count); + } + } + + os_unlock_restore_context(scc_crypto_lock, irq_flags); + +out: +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_disable(SCC_CLK); +#else + if (scc_clk != ERR_PTR(ENOENT)) + clk_disable(scc_clk); +#endif + + return status; +} + +/* Decrypt a region into secure memory + * + * @param part_base Kernel virtual address of the partition. + * @param offset_bytes Offset from the start of the partition to store the + * plaintext data. + * @param byte_counts Length of the region (octets). + * @param black_data Physical location of the encrypted data. + * @param IV Value to use for the IV. + * @param cypher_mode Cyphering mode to use, specified by type + * #scc_cypher_mode_t + * + * @return SCC_RET_OK if successful. + */ +scc_return_t +scc_decrypt_region(uint32_t part_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t *black_data, + uint32_t *IV, scc_cypher_mode_t cypher_mode) +{ + os_lock_context_t irq_flags; /* for IRQ save/restore */ + scc_return_t status = SCC_RET_OK; + uint32_t crypto_status; + uint32_t scm_command; + int offset_blocks = offset_bytes / SCC_BLOCK_SIZE_BYTES(); + + /*Enabling SCC clock.*/ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_enable(SCC_CLK); +#else + if (scc_clk != ERR_PTR(ENOENT)) + clk_enable(scc_clk); +#endif + scm_command = ((offset_blocks << SCM_CCMD_OFFSET_SHIFT) | + (SCM_PART_NUMBER(part_base) << SCM_CCMD_PART_SHIFT)); + + switch (cypher_mode) { + case SCC_CYPHER_MODE_CBC: + scm_command |= SCM_CCMD_AES_DEC_CBC; + break; + case SCC_CYPHER_MODE_ECB: + scm_command |= SCM_CCMD_AES_DEC_ECB; + break; + default: + status = SCC_RET_FAIL; + break; + } + + pr_debug("Received decrypt request. SCM_C_BLACK_ST_REG: %p, " + "scm_Command: %08x, length: %i (part_base: %08x, " + "offset: %i)\n", + black_data, scm_command, byte_count, part_base, offset_blocks); + + if (status != SCC_RET_OK) + goto out; + + /* ACQUIRE LOCK to prevent others from using crypto or releasing slot */ + os_lock_save_context(scc_crypto_lock, irq_flags); + + if (status == SCC_RET_OK) { + status = SCC_RET_FAIL; /* reset expectations */ + SCC_WRITE_REGISTER(SCM_C_BLACK_ST_REG, (uint32_t) black_data); + + /* Write the IV register */ + SCC_WRITE_REGISTER(SCM_AES_CBC_IV0_REG, *(IV)); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV1_REG, *(IV + 1)); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV2_REG, *(IV + 2)); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV3_REG, *(IV + 3)); + + /* Set modes and kick off the decryption */ + crypto_status = scc_do_crypto(byte_count, scm_command); + + if (crypto_status != 0) { + pr_debug("SCM decrypt black crypto failure: 0x%x\n", + crypto_status); + } else { + status = SCC_RET_OK; + pr_debug("SCC2: Decrypted %d bytes\n", byte_count); + } + } + + os_unlock_restore_context(scc_crypto_lock, irq_flags); +out: + /*Disabling the Clock when the driver is not in use.*/ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_disable(SCC_CLK); +#else + if (scc_clk != ERR_PTR(ENOENT)) + clk_disable(scc_clk); +#endif + return status; +} + +/*****************************************************************************/ +/* fn host_owns_partition() */ +/*****************************************************************************/ +/** + * Determine if the host owns a given partition. + * + * @internal + * + * @param part_no Partition number to query + * + * @return TRUE if the host owns the partition, FALSE otherwise. + */ + +static uint32_t host_owns_partition(uint32_t part_no) +{ + uint32_t value; + + if (part_no < scc_configuration.partition_count) { + + /* Check the partition owners register */ + value = SCC_READ_REGISTER(SCM_PART_OWNERS_REG); + if (((value >> (part_no * SCM_POWN_SHIFT)) & SCM_POWN_MASK) + == SCM_POWN_PART_OWNED) + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ +/* fn partition_engaged() */ +/*****************************************************************************/ +/** + * Determine if the given partition is engaged. + * + * @internal + * + * @param part_no Partition number to query + * + * @return TRUE if the partition is engaged, FALSE otherwise. + */ + +static uint32_t partition_engaged(uint32_t part_no) +{ + uint32_t value; + + if (part_no < scc_configuration.partition_count) { + + /* Check the partition engaged register */ + value = SCC_READ_REGISTER(SCM_PART_ENGAGED_REG); + if (((value >> (part_no * SCM_PENG_SHIFT)) & 0x1) + == SCM_PENG_ENGAGED) + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ +/* fn scc_wait_completion() */ +/*****************************************************************************/ +/** + * Poll looking for end-of-cipher indication. Only used + * if @c SCC_SCM_SLEEP is not defined. + * + * @internal + * + * On a Tahiti, crypto under 230 or so bytes is done after the first loop, all + * the way up to five sets of spins for 1024 bytes. (8- and 16-byte functions + * are done when we first look. Zeroizing takes one pass around. + * + * @param scm_status Address of the SCM_STATUS register + * + * @return A return code of type #scc_return_t + */ +static scc_return_t scc_wait_completion(uint32_t * scm_status) +{ + scc_return_t ret; + int done; + int i = 0; + + /* check for completion by polling */ + do { + done = is_cipher_done(scm_status); + if (done) + break; + /* TODO: shorten this delay */ + udelay(1000); + } while (i++ < SCC_CIPHER_MAX_POLL_COUNT); + + pr_debug("SCC2: Polled DONE %d times\n", i); + if (!done) { + ret = SCC_RET_FAIL; + } + + return ret; +} /* scc_wait_completion() */ + +/*****************************************************************************/ +/* fn is_cipher_done() */ +/*****************************************************************************/ +/** + * This function returns non-zero if SCM Status register indicates + * that a cipher has terminated or some other interrupt-generating + * condition has occurred. + * + * @param scm_status Address of the SCM STATUS register + * + * @return 0 if cipher operations are finished + */ +static int is_cipher_done(uint32_t * scm_status) +{ + register unsigned status; + register int cipher_done; + + *scm_status = SCC_READ_REGISTER(SCM_STATUS_REG); + status = (*scm_status & SCM_STATUS_SRS_MASK) >> SCM_STATUS_SRS_SHIFT; + + /* + * Done when SCM is not in 'currently performing a function' states. + */ + cipher_done = ((status != SCM_STATUS_SRS_ZBUSY) + && (status != SCM_STATUS_SRS_CBUSY) + && (status != SCM_STATUS_SRS_ABUSY)); + + return cipher_done; +} /* is_cipher_done() */ + +/*****************************************************************************/ +/* fn offset_within_smn() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SMN register set. + * + * @param[in] register_offset register offset of SMN. + * + * @return 1 if true, 0 if false (not within SMN) + */ +static inline int offset_within_smn(uint32_t register_offset) +{ + return ((register_offset >= SMN_STATUS_REG) + && (register_offset <= SMN_HAC_REG)); +} + +/*****************************************************************************/ +/* fn offset_within_scm() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SCM register set. + * + * @param[in] register_offset Register offset of SCM + * + * @return 1 if true, 0 if false (not within SCM) + */ +static inline int offset_within_scm(uint32_t register_offset) +{ + return 1; /* (register_offset >= SCM_RED_START) + && (register_offset < scm_highest_memory_address); */ +/* Although this would cause trouble for zeroize testing, this change would + * close a security hole which currently allows any kernel program to access + * any location in RED RAM. Perhaps enforce in non-SCC_DEBUG compiles? + && (register_offset <= SCM_INIT_VECTOR_1); */ +} + +/*****************************************************************************/ +/* fn check_register_accessible() */ +/*****************************************************************************/ +/** + * Given the current SCM and SMN status, verify that access to the requested + * register should be OK. + * + * @param[in] register_offset register offset within SCC + * @param[in] smn_status recent value from #SMN_STATUS_REG + * @param[in] scm_status recent value from #SCM_STATUS_REG + * + * @return #SCC_RET_OK if ok, #SCC_RET_FAIL if not + */ +static scc_return_t +check_register_accessible(uint32_t register_offset, uint32_t smn_status, + uint32_t scm_status) +{ + int error_code = SCC_RET_FAIL; + + /* Verify that the register offset passed in is not among the verboten set + * if the SMN is in Fail mode. + */ + if (offset_within_smn(register_offset)) { + if ((smn_status & SMN_STATUS_STATE_MASK) == SMN_STATE_FAIL) { + if (!((register_offset == SMN_STATUS_REG) || + (register_offset == SMN_COMMAND_REG) || + (register_offset == SMN_SEC_VIO_REG))) { + pr_debug + ("SCC2 Driver: Note: Security State is in FAIL state.\n"); + } /* register not a safe one */ + else { + /* SMN is in FAIL, but register is a safe one */ + error_code = SCC_RET_OK; + } + } /* State is FAIL */ + else { + /* State is not fail. All registers accessible. */ + error_code = SCC_RET_OK; + } + } + /* offset within SMN */ + /* Not SCM register. Check for SCM busy. */ + else if (offset_within_scm(register_offset)) { + /* This is the 'cannot access' condition in the SCM */ + if (0 /* (scm_status & SCM_STATUS_BUSY) */ + /* these are always available - rest fail on busy */ + && !((register_offset == SCM_STATUS_REG) || + (register_offset == SCM_ERR_STATUS_REG) || + (register_offset == SCM_INT_CTL_REG) || + (register_offset == SCM_VERSION_REG))) { + pr_debug + ("SCC2 Driver: Note: Secure Memory is in BUSY state.\n"); + } /* status is busy & register inaccessible */ + else { + error_code = SCC_RET_OK; + } + } + /* offset within SCM */ + return error_code; + +} /* check_register_accessible() */ + +/*****************************************************************************/ +/* fn check_register_offset() */ +/*****************************************************************************/ +/** + * Check that the offset is with the bounds of the SCC register set. + * + * @param[in] register_offset register offset of SMN. + * + * #SCC_RET_OK if ok, #SCC_RET_FAIL if not + */ +static scc_return_t check_register_offset(uint32_t register_offset) +{ + int return_value = SCC_RET_FAIL; + + /* Is it valid word offset ? */ + if (SCC_BYTE_OFFSET(register_offset) == 0) { + /* Yes. Is register within SCM? */ + if (offset_within_scm(register_offset)) { + return_value = SCC_RET_OK; /* yes, all ok */ + } + /* Not in SCM. Now look within the SMN */ + else if (offset_within_smn(register_offset)) { + return_value = SCC_RET_OK; /* yes, all ok */ + } + } + + return return_value; +} + +#ifdef SCC_REGISTER_DEBUG + +/** + * Names of the SCC Registers, indexed by register number + */ +static char *scc_regnames[] = { + "SCM_VERSION_REG", + "0x04", + "SCM_INT_CTL_REG", + "SCM_STATUS_REG", + "SCM_ERR_STATUS_REG", + "SCM_FAULT_ADR_REG", + "SCM_PART_OWNERS_REG", + "SCM_PART_ENGAGED_REG", + "SCM_UNIQUE_ID0_REG", + "SCM_UNIQUE_ID1_REG", + "SCM_UNIQUE_ID2_REG", + "SCM_UNIQUE_ID3_REG", + "0x30", + "0x34", + "0x38", + "0x3C", + "0x40", + "0x44", + "0x48", + "0x4C", + "SCM_ZCMD_REG", + "SCM_CCMD_REG", + "SCM_C_BLACK_ST_REG", + "SCM_DBG_STATUS_REG", + "SCM_AES_CBC_IV0_REG", + "SCM_AES_CBC_IV1_REG", + "SCM_AES_CBC_IV2_REG", + "SCM_AES_CBC_IV3_REG", + "0x70", + "0x74", + "0x78", + "0x7C", + "SCM_SMID0_REG", + "SCM_ACC0_REG", + "SCM_SMID1_REG", + "SCM_ACC1_REG", + "SCM_SMID2_REG", + "SCM_ACC2_REG", + "SCM_SMID3_REG", + "SCM_ACC3_REG", + "SCM_SMID4_REG", + "SCM_ACC4_REG", + "SCM_SMID5_REG", + "SCM_ACC5_REG", + "SCM_SMID6_REG", + "SCM_ACC6_REG", + "SCM_SMID7_REG", + "SCM_ACC7_REG", + "SCM_SMID8_REG", + "SCM_ACC8_REG", + "SCM_SMID9_REG", + "SCM_ACC9_REG", + "SCM_SMID10_REG", + "SCM_ACC10_REG", + "SCM_SMID11_REG", + "SCM_ACC11_REG", + "SCM_SMID12_REG", + "SCM_ACC12_REG", + "SCM_SMID13_REG", + "SCM_ACC13_REG", + "SCM_SMID14_REG", + "SCM_ACC14_REG", + "SCM_SMID15_REG", + "SCM_ACC15_REG", + "SMN_STATUS_REG", + "SMN_COMMAND_REG", + "SMN_SEQ_START_REG", + "SMN_SEQ_END_REG", + "SMN_SEQ_CHECK_REG", + "SMN_BB_CNT_REG", + "SMN_BB_INC_REG", + "SMN_BB_DEC_REG", + "SMN_COMPARE_REG", + "SMN_PT_CHK_REG", + "SMN_CT_CHK_REG", + "SMN_TIMER_IV_REG", + "SMN_TIMER_CTL_REG", + "SMN_SEC_VIO_REG", + "SMN_TIMER_REG", + "SMN_HAC_REG" +}; + +/** + * Names of the Secure RAM States + */ +static char *srs_names[] = { + "SRS_Reset", + "SRS_All_Ready", + "SRS_ZeroizeBusy", + "SRS_CipherBusy", + "SRS_AllBusy", + "SRS_ZeroizeDoneCipherReady", + "SRS_CipherDoneZeroizeReady", + "SRS_ZeroizeDoneCipherBusy", + "SRS_CipherDoneZeroizeBusy", + "SRS_UNKNOWN_STATE_9", + "SRS_TransitionalA", + "SRS_TransitionalB", + "SRS_TransitionalC", + "SRS_TransitionalD", + "SRS_AllDone", + "SRS_UNKNOWN_STATE_E", + "SRS_FAIL" +}; + +/** + * Create a text interpretation of the SCM Version Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_version_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, + "Bpp: %u, Bpcb: %u, np: %u, maj: %u, min: %u", + (value & SCM_VER_BPP_MASK) >> SCM_VER_BPP_SHIFT, + ((value & SCM_VER_BPCB_MASK) >> SCM_VER_BPCB_SHIFT) + 1, + ((value & SCM_VER_NP_MASK) >> SCM_VER_NP_SHIFT) + 1, + (value & SCM_VER_MAJ_MASK) >> SCM_VER_MAJ_SHIFT, + (value & SCM_VER_MIN_MASK) >> SCM_VER_MIN_SHIFT); + + return print_buffer; +} + +/** + * Create a text interpretation of the SCM Status Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_status_reg(uint32_t value, char *print_buffer, int buf_size) +{ + + snprintf(print_buffer, buf_size, "%s%s%s%s%s%s%s%s%s%s%s%s%s", + (value & SCM_STATUS_KST_DEFAULT_KEY) ? "KST_DefaultKey " : "", + /* reserved */ + (value & SCM_STATUS_KST_WRONG_KEY) ? "KST_WrongKey " : "", + (value & SCM_STATUS_KST_BAD_KEY) ? "KST_BadKey " : "", + (value & SCM_STATUS_ERR) ? "Error " : "", + (value & SCM_STATUS_MSS_FAIL) ? "MSS_FailState " : "", + (value & SCM_STATUS_MSS_SEC) ? "MSS_SecureState " : "", + (value & SCM_STATUS_RSS_FAIL) ? "RSS_FailState " : "", + (value & SCM_STATUS_RSS_SEC) ? "RSS_SecureState " : "", + (value & SCM_STATUS_RSS_INIT) ? "RSS_Initializing " : "", + (value & SCM_STATUS_UNV) ? "UID_Invalid " : "", + (value & SCM_STATUS_BIG) ? "BigEndian " : "", + (value & SCM_STATUS_USK) ? "SecretKey " : "", + srs_names[(value & SCM_STATUS_SRS_MASK) >> + SCM_STATUS_SRS_SHIFT]); + + return print_buffer; +} + +/** + * Names of the SCM Error Codes + */ +static +char *scm_err_code[] = { + "Unknown_0", + "UnknownAddress", + "UnknownCommand", + "ReadPermErr", + "WritePermErr", + "DMAErr", + "EncBlockLenOvfl", + "KeyNotEngaged", + "ZeroizeCmdQOvfl", + "CipherCmdQOvfl", + "ProcessIntr", + "WrongKey", + "DeviceBusy", + "DMAUnalignedAddr", + "Unknown_E", + "Unknown_F", +}; + +/** + * Names of the SMN States + */ +static char *smn_state_name[] = { + "Start", + "Invalid_01", + "Invalid_02", + "Invalid_03", + "Zeroizing_04", + "Zeroizing", + "HealthCheck", + "HealthCheck_07", + "Invalid_08", + "Fail", + "Secure", + "Invalid_0B", + "NonSecure", + "Invalid_0D", + "Invalid_0E", + "Invalid_0F", + "Invalid_10", + "Invalid_11", + "Invalid_12", + "Invalid_13", + "Invalid_14", + "Invalid_15", + "Invalid_16", + "Invalid_17", + "Invalid_18", + "FailHard", + "Invalid_1A", + "Invalid_1B", + "Invalid_1C", + "Invalid_1D", + "Invalid_1E", + "Invalid_1F" +}; + +/** + * Create a text interpretation of the SCM Error Status Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_err_status_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, + "MID: 0x%x, %s%s ErrorCode: %s, SMSState: %s, SCMState: %s", + (value & SCM_ERRSTAT_MID_MASK) >> SCM_ERRSTAT_MID_SHIFT, + (value & SCM_ERRSTAT_ILM) ? "ILM, " : "", + (value & SCM_ERRSTAT_SUP) ? "SUP, " : "", + scm_err_code[(value & SCM_ERRSTAT_ERC_MASK) >> + SCM_ERRSTAT_ERC_SHIFT], + smn_state_name[(value & SCM_ERRSTAT_SMS_MASK) >> + SCM_ERRSTAT_SMS_SHIFT], + srs_names[(value & SCM_ERRSTAT_SRS_MASK) >> + SCM_ERRSTAT_SRS_SHIFT]); + return print_buffer; +} + +/** + * Create a text interpretation of the SCM Zeroize Command Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_zcmd_reg(uint32_t value, char *print_buffer, int buf_size) +{ + unsigned cmd = (value & SCM_ZCMD_CCMD_MASK) >> SCM_CCMD_CCMD_SHIFT; + + snprintf(print_buffer, buf_size, "%s %u", + (cmd == + ZCMD_DEALLOC_PART) ? "DeallocPartition" : + "(unknown function)", + (value & SCM_ZCMD_PART_MASK) >> SCM_ZCMD_PART_SHIFT); + + return print_buffer; +} + +/** + * Create a text interpretation of the SCM Cipher Command Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_ccmd_reg(uint32_t value, char *print_buffer, int buf_size) +{ + unsigned cmd = (value & SCM_CCMD_CCMD_MASK) >> SCM_CCMD_CCMD_SHIFT; + + snprintf(print_buffer, buf_size, + "%s %u bytes, %s offset 0x%x, in partition %u", + (cmd == SCM_CCMD_AES_DEC_ECB) ? "ECB Decrypt" : (cmd == + SCM_CCMD_AES_ENC_ECB) + ? "ECB Encrypt" : (cmd == + SCM_CCMD_AES_DEC_CBC) ? "CBC Decrypt" : (cmd + == + SCM_CCMD_AES_ENC_CBC) + ? "CBC Encrypt" : "(unknown function)", + 16 + + 16 * ((value & SCM_CCMD_LENGTH_MASK) >> SCM_CCMD_LENGTH_SHIFT), + ((cmd == SCM_CCMD_AES_ENC_CBC) + || (cmd == SCM_CCMD_AES_ENC_ECB)) ? "at" : "to", + 16 * ((value & SCM_CCMD_OFFSET_MASK) >> SCM_CCMD_OFFSET_SHIFT), + (value & SCM_CCMD_PART_MASK) >> SCM_CCMD_PART_SHIFT); + + return print_buffer; +} + +/** + * Create a text interpretation of an SCM Access Permissions Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_acc_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, "%s%s%s%s%s%s%s%s%s%s", + (value & SCM_PERM_NO_ZEROIZE) ? "NO_ZERO " : "", + (value & SCM_PERM_HD_SUP_DISABLE) ? "SUP_DIS " : "", + (value & SCM_PERM_HD_READ) ? "HD_RD " : "", + (value & SCM_PERM_HD_WRITE) ? "HD_WR " : "", + (value & SCM_PERM_HD_EXECUTE) ? "HD_EX " : "", + (value & SCM_PERM_TH_READ) ? "TH_RD " : "", + (value & SCM_PERM_TH_WRITE) ? "TH_WR " : "", + (value & SCM_PERM_OT_READ) ? "OT_RD " : "", + (value & SCM_PERM_OT_WRITE) ? "OT_WR " : "", + (value & SCM_PERM_OT_EXECUTE) ? "OT_EX" : ""); + + return print_buffer; +} + +/** + * Create a text interpretation of the SCM Partitions Engaged Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_part_eng_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + (value & 0x8000) ? "15 " : "", + (value & 0x4000) ? "14 " : "", + (value & 0x2000) ? "13 " : "", + (value & 0x1000) ? "12 " : "", + (value & 0x0800) ? "11 " : "", + (value & 0x0400) ? "10 " : "", + (value & 0x0200) ? "9 " : "", + (value & 0x0100) ? "8 " : "", + (value & 0x0080) ? "7 " : "", + (value & 0x0040) ? "6 " : "", + (value & 0x0020) ? "5 " : "", + (value & 0x0010) ? "4 " : "", + (value & 0x0008) ? "3 " : "", + (value & 0x0004) ? "2 " : "", + (value & 0x0002) ? "1 " : "", (value & 0x0001) ? "0" : ""); + + return print_buffer; +} + +/** + * Create a text interpretation of the SMN Status Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *smn_print_status_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, + "Version %d %s%s%s%s%s%s%s%s%s%s%s%s%s", + (value & SMN_STATUS_VERSION_ID_MASK) >> + SMN_STATUS_VERSION_ID_SHIFT, + (value & SMN_STATUS_ILLEGAL_MASTER) ? "IllMaster " : "", + (value & SMN_STATUS_SCAN_EXIT) ? "ScanExit " : "", + (value & SMN_STATUS_PERIP_INIT) ? "PeripInit " : "", + (value & SMN_STATUS_SMN_ERROR) ? "SMNError " : "", + (value & SMN_STATUS_SOFTWARE_ALARM) ? "SWAlarm " : "", + (value & SMN_STATUS_TIMER_ERROR) ? "TimerErr " : "", + (value & SMN_STATUS_PC_ERROR) ? "PTCTErr " : "", + (value & SMN_STATUS_BITBANK_ERROR) ? "BitbankErr " : "", + (value & SMN_STATUS_ASC_ERROR) ? "ASCErr " : "", + (value & SMN_STATUS_SECURITY_POLICY_ERROR) ? "SecPlcyErr " : + "", + (value & SMN_STATUS_SEC_VIO_ACTIVE_ERROR) ? "SecVioAct " : "", + (value & SMN_STATUS_INTERNAL_BOOT) ? "IntBoot " : "", + smn_state_name[(value & SMN_STATUS_STATE_MASK) >> + SMN_STATUS_STATE_SHIFT]); + + return print_buffer; +} + +/** + * The array, indexed by register number (byte-offset / 4), of print routines + * for the SCC (SCM and SMN) registers. + */ +static reg_print_routine_t reg_printers[] = { + scm_print_version_reg, + NULL, /* 0x04 */ + NULL, /* SCM_INT_CTL_REG */ + scm_print_status_reg, + scm_print_err_status_reg, + NULL, /* SCM_FAULT_ADR_REG */ + NULL, /* SCM_PART_OWNERS_REG */ + scm_print_part_eng_reg, + NULL, /* SCM_UNIQUE_ID0_REG */ + NULL, /* SCM_UNIQUE_ID1_REG */ + NULL, /* SCM_UNIQUE_ID2_REG */ + NULL, /* SCM_UNIQUE_ID3_REG */ + NULL, /* 0x30 */ + NULL, /* 0x34 */ + NULL, /* 0x38 */ + NULL, /* 0x3C */ + NULL, /* 0x40 */ + NULL, /* 0x44 */ + NULL, /* 0x48 */ + NULL, /* 0x4C */ + scm_print_zcmd_reg, + scm_print_ccmd_reg, + NULL, /* SCM_C_BLACK_ST_REG */ + NULL, /* SCM_DBG_STATUS_REG */ + NULL, /* SCM_AES_CBC_IV0_REG */ + NULL, /* SCM_AES_CBC_IV1_REG */ + NULL, /* SCM_AES_CBC_IV2_REG */ + NULL, /* SCM_AES_CBC_IV3_REG */ + NULL, /* 0x70 */ + NULL, /* 0x74 */ + NULL, /* 0x78 */ + NULL, /* 0x7C */ + NULL, /* SCM_SMID0_REG */ + scm_print_acc_reg, /* ACC0 */ + NULL, /* SCM_SMID1_REG */ + scm_print_acc_reg, /* ACC1 */ + NULL, /* SCM_SMID2_REG */ + scm_print_acc_reg, /* ACC2 */ + NULL, /* SCM_SMID3_REG */ + scm_print_acc_reg, /* ACC3 */ + NULL, /* SCM_SMID4_REG */ + scm_print_acc_reg, /* ACC4 */ + NULL, /* SCM_SMID5_REG */ + scm_print_acc_reg, /* ACC5 */ + NULL, /* SCM_SMID6_REG */ + scm_print_acc_reg, /* ACC6 */ + NULL, /* SCM_SMID7_REG */ + scm_print_acc_reg, /* ACC7 */ + NULL, /* SCM_SMID8_REG */ + scm_print_acc_reg, /* ACC8 */ + NULL, /* SCM_SMID9_REG */ + scm_print_acc_reg, /* ACC9 */ + NULL, /* SCM_SMID10_REG */ + scm_print_acc_reg, /* ACC10 */ + NULL, /* SCM_SMID11_REG */ + scm_print_acc_reg, /* ACC11 */ + NULL, /* SCM_SMID12_REG */ + scm_print_acc_reg, /* ACC12 */ + NULL, /* SCM_SMID13_REG */ + scm_print_acc_reg, /* ACC13 */ + NULL, /* SCM_SMID14_REG */ + scm_print_acc_reg, /* ACC14 */ + NULL, /* SCM_SMID15_REG */ + scm_print_acc_reg, /* ACC15 */ + smn_print_status_reg, + NULL, /* SMN_COMMAND_REG */ + NULL, /* SMN_SEQ_START_REG */ + NULL, /* SMN_SEQ_END_REG */ + NULL, /* SMN_SEQ_CHECK_REG */ + NULL, /* SMN_BB_CNT_REG */ + NULL, /* SMN_BB_INC_REG */ + NULL, /* SMN_BB_DEC_REG */ + NULL, /* SMN_COMPARE_REG */ + NULL, /* SMN_PT_CHK_REG */ + NULL, /* SMN_CT_CHK_REG */ + NULL, /* SMN_TIMER_IV_REG */ + NULL, /* SMN_TIMER_CTL_REG */ + NULL, /* SMN_SEC_VIO_REG */ + NULL, /* SMN_TIMER_REG */ + NULL, /* SMN_HAC_REG */ +}; + +/*****************************************************************************/ +/* fn dbg_scc_read_register() */ +/*****************************************************************************/ +/** + * Noisily read a 32-bit value to an SCC register. + * @param offset The address of the register to read. + * + * @return The register value + * */ +uint32_t dbg_scc_read_register(uint32_t offset) +{ + uint32_t value; + char *regname = scc_regnames[offset / 4]; + + value = __raw_readl(scc_base + offset); + pr_debug("SCC2 RD: 0x%03x : 0x%08x (%s) %s\n", offset, value, regname, + reg_printers[offset / 4] + ? reg_printers[offset / 4] (value, reg_print_buffer, + REG_PRINT_BUFFER_SIZE) + : ""); + + return value; +} + +/*****************************************************************************/ +/* fn dbg_scc_write_register() */ +/*****************************************************************************/ +/* + * Noisily read a 32-bit value to an SCC register. + * @param offset The address of the register to written. + * + * @param value The new register value + */ +void dbg_scc_write_register(uint32_t offset, uint32_t value) +{ + char *regname = scc_regnames[offset / 4]; + + pr_debug("SCC2 WR: 0x%03x : 0x%08x (%s) %s\n", offset, value, regname, + reg_printers[offset / 4] + ? reg_printers[offset / 4] (value, reg_print_buffer, + REG_PRINT_BUFFER_SIZE) + : ""); + (void)__raw_writel(value, scc_base + offset); + +} + +#endif /* SCC_REGISTER_DEBUG */ diff --git a/drivers/mxc/security/scc2_internals.h b/drivers/mxc/security/scc2_internals.h new file mode 100644 index 000000000000..475c8dec5a1b --- /dev/null +++ b/drivers/mxc/security/scc2_internals.h @@ -0,0 +1,527 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef SCC_INTERNALS_H +#define SCC_INTERNALS_H + +/** @file scc2_internals.h + * + * @brief This is intended to be the file which contains most or all of the + * code or changes need to port the driver. It also includes other definitions + * needed by the driver. + * + * This header file should only ever be included by scc2_driver.c + * + * Compile-time flags minimally needed: + * + * @li Some sort of platform flag. Currently TAHITI and MXC are understood. + * @li Some start-of-SCC consideration, such as SCC_BASE_ADDR + * + * Some changes which could be made when porting this driver: + * #SCC_SPIN_COUNT + * + */ + +#include <linux/version.h> /* Current version Linux kernel */ +#include <linux/module.h> /* Basic support for loadable modules, + printk */ +#include <linux/init.h> /* module_init, module_exit */ +#include <linux/kernel.h> /* General kernel system calls */ +#include <linux/sched.h> /* for interrupt.h */ +#include <linux/spinlock.h> + +#include <linux/io.h> /* ioremap() */ +#include <linux/interrupt.h> /* IRQ / interrupt definitions */ + + +#include <linux/mxc_scc2_driver.h> + +#if defined(MXC) + +#include <mach/iim.h> +#include <mach/mxc_scc.h> + + +/** + * This macro is used to determine whether the SCC is enabled/available + * on the platform. This macro may need to be ported. + */ +#define SCC_FUSE __raw_readl(IO_ADDRESS(IIM_BASE_ADDR + MXC_IIMHWV1)) +#define SCC_ENABLED() ((SCC_FUSE & MXC_IIMHWV1_SCC_DISABLE) == 0) + +#else /* neither TAHITI nor MXC */ + +#error Do not understand target architecture + +#endif /* TAHITI */ +/** + * Define the number of Stored Keys which the SCC driver will make available. + * Value shall be from 0 to 20. Default is zero (0). + */ +/*#define SCC_KEY_SLOTS 20*/ + + +/* Temporarily define compile-time flags to make Doxygen happy. */ +#ifdef DOXYGEN_HACK +/** @addtogroup scccompileflags */ +/** @{ */ + + +/** @def NO_SMN_INTERRUPT + * The SMN interrupt is not wired to the CPU at all. + */ +#define NO_SMN_INTERRUPT + + +/** + * Register an interrupt handler for the SMN as well as + * the SCM. In some implementations, the SMN is not connected at all (see + * #NO_SMN_INTERRUPT), and in others, it is on the same interrupt line as the + * SCM. When defining this flag, the SMN interrupt should be on a separate + * line from the SCM interrupt. + */ + +#define USE_SMN_INTERRUPT + + +/** + * Turn on generation of run-time operational, debug, and error messages + */ +#define SCC_DEBUG + + +/** + * Turn on generation of run-time logging of access to the SCM and SMN + * registers. + */ +#define SCC_REGISTER_DEBUG + + +/** + * Turn on generation of run-time logging of access to the SCM Red and + * Black memories. Will only work if #SCC_REGISTER_DEBUG is also defined. + */ +#define SCC_RAM_DEBUG + + +/** + * If the driver finds the SCC in HEALTH_CHECK state, go ahead and + * run a quick ASC to bring it to SECURE state. + */ +#define SCC_BRINGUP + + +/** + * Expected to come from platform header files or compile command line. + * This symbol must be the address of the SCC + */ +#define SCC_BASE + +/** + * This must be the interrupt line number of the SCM interrupt. + */ +#define INT_SCM + +/** + * if #USE_SMN_INTERRUPT is defined, this must be the interrupt line number of + * the SMN interrupt. + */ +#define INT_SMN + +/** + * Define the number of Stored Keys which the SCC driver will make available. + * Value shall be from 0 to 20. Default is zero (0). + */ +#define SCC_KEY_SLOTS + +/** + * Make sure that this flag is defined if compiling for a Little-Endian + * platform. Linux Kernel builds provide this flag. + */ +#define __LITTLE_ENDIAN + +/** + * Make sure that this flag is defined if compiling for a Big-Endian platform. + * Linux Kernel builds provide this flag. + */ +#define __BIG_ENDIAN + +/** + * Read a 32-bit register value from a 'peripheral'. Standard Linux/Unix + * macro. + * + * @param offset Bus address of register to be read + * + * @return The value of the register + */ +#define readl(offset) + + +/** + * Write a 32-bit value to a register in a 'peripheral'. Standard Linux/Unix + * macro. + * + * @param value The 32-bit value to store + * @param offset Bus address of register to be written + * + * return (none) + */ +#define writel(value,offset) + + +/** @} */ /* end group scccompileflags */ + +#endif /* DOXYGEN_HACK */ + + +#ifndef SCC_KEY_SLOTS +#define SCC_KEY_SLOTS 0 + +#else + +#if (SCC_KEY_SLOTS < 0) || (SCC_KEY_SLOTS > 20) +#error Bad value for SCC_KEY_SLOTS +#endif + +#endif + + +/** + * Maximum length of key/secret value which can be stored in SCC. + */ +#define SCC_MAX_KEY_SIZE 256 + + +/** + * This is the size, in bytes, of each key slot, and therefore the maximum size + * of the wrapped key. + */ +#define SCC_KEY_SLOT_SIZE 32 + + +/* These come for free with Linux, but may need to be set in a port. */ +#ifndef __BIG_ENDIAN +#ifndef __LITTLE_ENDIAN +#error One of __LITTLE_ENDIAN or __BIG_ENDIAN must be #defined +#endif +#else +#ifdef __LITTLE_ENDIAN +#error Exactly one of __LITTLE_ENDIAN or __BIG_ENDIAN must be #defined +#endif +#endif + + +#ifndef SCC_CALLBACK_SIZE +/** The number of function pointers which can be stored in #scc_callbacks. + * Defaults to 4, can be overridden with compile-line argument. + */ +#define SCC_CALLBACK_SIZE 4 +#endif + + +/** Initial CRC value for CCITT-CRC calculation. */ +#define CRC_CCITT_START 0xFFFF + + +#ifdef TAHITI + +/** + * The SCC_BASE has to be SMN_BASE_ADDR on TAHITI, as the banks of + * registers are swapped in place. + */ +#define SCC_BASE SMN_BASE_ADDR + + +/** The interrupt number for the SCC (SCM only!) on Tahiti */ +#define INT_SCC_SCM 62 + + +/** Tahiti does not have the SMN interrupt wired to the CPU. */ +#define NO_SMN_INTERRUPT + + +#endif /* TAHITI */ + + +/** Number of times to spin between polling of SCC while waiting for cipher + * or zeroizing function to complete. See also #SCC_CIPHER_MAX_POLL_COUNT. */ +#define SCC_SPIN_COUNT 1000 + + +/** Number of times to polling SCC while waiting for cipher + * or zeroizing function to complete. See also #SCC_SPIN_COUNT. */ +#define SCC_CIPHER_MAX_POLL_COUNT 100 + + +/** + * @def SCC_READ_REGISTER + * Read a 32-bit value from an SCC register. Macro which depends upon + * #scc_base. Linux readl()/writel() macros operate on 32-bit quantities, as + * do SCC register reads/writes. + * + * @param offset Register offset within SCC. + * + * @return The value from the SCC's register. + */ +#ifndef SCC_REGISTER_DEBUG +#define SCC_READ_REGISTER(offset) __raw_readl(scc_base+(offset)) +#else +#define SCC_READ_REGISTER(offset) dbg_scc_read_register(offset) +#endif + + +/** + * Write a 32-bit value to an SCC register. Macro depends upon #scc_base. + * Linux readl()/writel() macros operate on 32-bit quantities, as do SCC + * register reads/writes. + * + * @param offset Register offset within SCC. + * @param value 32-bit value to store into the register + * + * @return (void) + */ +#ifndef SCC_REGISTER_DEBUG +#define SCC_WRITE_REGISTER(offset,value) \ + (void)__raw_writel(value, scc_base+(offset)) +#else +#define SCC_WRITE_REGISTER(offset,value) \ + dbg_scc_write_register(offset, value) +#endif + +/** + * Calculate the physical address of a partition from the partition number. + */ +#define SCM_PART_PHYS_ADDRESS(part) \ + ((uint32_t)scm_ram_phys_base + (part*scc_configuration.partition_size_bytes)) + +/** + * Calculate the kernel virtual address of a partition from the partition number. + */ +#define SCM_PART_ADDRESS(part) \ + (scm_ram_base + (part*scc_configuration.partition_size_bytes)) + +/** + * Calculate the partition number from the kernel virtual address. + */ +#define SCM_PART_NUMBER(address) \ + ((address - (uint32_t)scm_ram_base)/scc_configuration.partition_size_bytes) + +/** + * Calculates the byte offset into a word + * @param bp The byte (char*) pointer + * @return The offset (0, 1, 2, or 3) + */ +#define SCC_BYTE_OFFSET(bp) ((uint32_t)(bp) % sizeof(uint32_t)) + + +/** + * Converts (by rounding down) a byte pointer into a word pointer + * @param bp The byte (char*) pointer + * @return The word (uint32_t) as though it were an aligned (uint32_t*) + */ +#define SCC_WORD_PTR(bp) (((uint32_t)(bp)) & ~(sizeof(uint32_t)-1)) + + +/** + * Determine number of bytes in an SCC block + * + * @return Bytes / block + */ +#define SCC_BLOCK_SIZE_BYTES() scc_configuration.block_size_bytes + + +/** + * Maximum number of additional bytes which may be added in CRC+padding mode. + */ +#define PADDING_BUFFER_MAX_BYTES (CRC_SIZE_BYTES + sizeof(scc_block_padding)) + +/** + * Shorthand (clearer, anyway) for number of bytes in a CRC. + */ +#define CRC_SIZE_BYTES (sizeof(crc_t)) + +/** + * The polynomial used in CCITT-CRC calculation + */ +#define CRC_POLYNOMIAL 0x1021 + +/** + * Calculate CRC on one byte of data + * + * @param[in,out] running_crc A value of type crc_t where CRC is kept. This + * must be an rvalue and an lvalue. + * @param[in] byte_value The byte (uint8_t, char) to be put in the CRC + * + * @return none + */ +#define CALC_CRC(byte_value,running_crc) { \ + uint8_t data; \ + data = (0xff&(byte_value)) ^ (running_crc >> 8); \ + running_crc = scc_crc_lookup_table[data] ^ (running_crc << 8); \ +} + +/** Value of 'beginning of padding' marker in driver-provided padding */ +#define SCC_DRIVER_PAD_CHAR 0x80 + + +/** Name of the driver. Used (on Linux, anyway) when registering interrupts */ +#define SCC_DRIVER_NAME "scc" + + +/* Port -- these symbols are defined in Linux 2.6 and later. They are defined + * here for backwards compatibility because this started life as a 2.4 + * driver, and as a guide to portation to other platforms. + */ + +#if !defined(LINUX_VERSION_CODE) || LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + +#define irqreturn_t void /* Return type of an interrupt handler */ + +#define IRQ_HANDLED /* Would be '1' for handled -- as in return IRQ_HANDLED; */ + +#define IRQ_NONE /* would be '0' for not handled -- as in return IRQ_NONE; */ + +#define IRQ_RETVAL(x) /* Return x==0 (not handled) or non-zero (handled) */ + +#endif /* LINUX earlier than 2.5 */ + + +/* These are nice to have around */ +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + + +/** Provide a typedef for the CRC which can be used in encrypt/decrypt */ +typedef uint16_t crc_t; + + +/** Gives high-level view of state of the SCC */ +enum scc_status { + SCC_STATUS_INITIAL, /**< State of driver before ever checking */ + SCC_STATUS_CHECKING, /**< Transient state while driver loading */ + SCC_STATUS_UNIMPLEMENTED, /**< SCC is non-existent or unuseable */ + SCC_STATUS_OK, /**< SCC is in Secure or Default state */ + SCC_STATUS_FAILED /**< In Failed state */ +}; + +/** + * Information about a key slot. + */ +struct scc_key_slot +{ + uint64_t owner_id; /**< Access control value. */ + uint32_t length; /**< Length of value in slot. */ + uint32_t offset; /**< Offset of value from start of RAM. */ + uint32_t status; /**< 0 = unassigned, 1 = assigned. */ + uint32_t part_ctl; /**< for the CCMD register */ +}; + +/* Forward-declare a number routines which are not part of user api */ +static int scc_init(void); +static void scc_cleanup(void); + +/* Forward defines of internal functions */ +OS_DEV_ISR(scc_irq); +/*static irqreturn_t scc_irq(int irq, void *dev_id);*/ +/** Perform callbacks registered by #scc_monitor_security_failure(). + * + * Make sure callbacks only happen once... Since there may be some reason why + * the interrupt isn't generated, this routine could be called from base(task) + * level. + * + * One at a time, go through #scc_callbacks[] and call any non-null pointers. + */ +static void scc_perform_callbacks(void); +/*static uint32_t copy_to_scc(const uint8_t* from, uint32_t to, unsigned long count_bytes, uint16_t* crc); +static uint32_t copy_from_scc(const uint32_t from, uint8_t* to,unsigned long count_bytes, uint16_t* crc); +static scc_return_t scc_strip_padding(uint8_t* from,unsigned* count_bytes_stripped);*/ +static uint32_t scc_update_state(void); +static void scc_init_ccitt_crc(void); +static uint32_t scc_grab_config_values(void); +static int setup_interrupt_handling(void); +/** + * Perform an encryption on the input. If @c verify_crc is true, a CRC must be + * calculated on the plaintext, and appended, with padding, before computing + * the ciphertext. + * + * @param[in] count_in_bytes Count of bytes of plaintext + * @param[in] data_in Pointer to the plaintext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing ciphertext + * @param[in] add_crc Flag for computing CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + */ +/*static scc_return_t scc_encrypt(uint32_t count_in_bytes, uint8_t* data_in, uint32_t scm_control, uint8_t* data_out,int add_crc, unsigned long* count_out_bytes);*/ +/** + * Perform a decryption on the input. If @c verify_crc is true, the last block + * (maybe the two last blocks) is special - it should contain a CRC and + * padding. These must be stripped and verified. + * + * @param[in] count_in_bytes Count of bytes of ciphertext + * @param[in] data_in Pointer to the ciphertext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing plaintext + * @param[in] verify_crc Flag for running CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + + */ +/*static scc_return_t scc_decrypt(uint32_t count_in_bytes, uint8_t* data_in, uint32_t scm_control, uint8_t* data_out, int verify_crc, unsigned long* count_out_bytes);*/ +static uint32_t host_owns_partition(uint32_t part_no); +static uint32_t partition_engaged(uint32_t part_no); + +static scc_return_t scc_wait_completion(uint32_t* scm_status); +static int is_cipher_done(uint32_t* scm_status); +static scc_return_t check_register_accessible (uint32_t offset, + uint32_t smn_status, + uint32_t scm_status); +static scc_return_t check_register_offset(uint32_t offset); +/*uint8_t make_vpu_partition(void);*/ + +#ifdef SCC_REGISTER_DEBUG +static uint32_t dbg_scc_read_register(uint32_t offset); +static void dbg_scc_write_register(uint32_t offset, uint32_t value); +#endif + + +/* For Linux kernel, export the API functions to other kernel modules */ +EXPORT_SYMBOL(scc_get_configuration); +EXPORT_SYMBOL(scc_zeroize_memories); +/*EXPORT_SYMBOL(scc_crypt);*/ +EXPORT_SYMBOL(scc_set_sw_alarm); +EXPORT_SYMBOL(scc_monitor_security_failure); +EXPORT_SYMBOL(scc_stop_monitoring_security_failure); +EXPORT_SYMBOL(scc_read_register); +EXPORT_SYMBOL(scc_write_register); +EXPORT_SYMBOL(scc_allocate_partition); +EXPORT_SYMBOL(scc_engage_partition); +EXPORT_SYMBOL(scc_release_partition); +EXPORT_SYMBOL(scc_diminish_permissions); +EXPORT_SYMBOL(scc_encrypt_region); +EXPORT_SYMBOL(scc_decrypt_region); +/*EXPORT_SYMBOL(make_vpu_partition);*/ +/* Tell Linux where to invoke driver at boot/module load time */ +module_init(scc_init); +/* Tell Linux where to invoke driver on module unload */ +module_exit(scc_cleanup); + + +/* Tell Linux this is not GPL code */ +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Device Driver for SCC (SMN/SCM)"); + + +#endif /* SCC_INTERNALS_H */ diff --git a/drivers/mxc/ssi/Kconfig b/drivers/mxc/ssi/Kconfig new file mode 100644 index 000000000000..4cb581c2f945 --- /dev/null +++ b/drivers/mxc/ssi/Kconfig @@ -0,0 +1,12 @@ +# +# SPI device configuration +# + +menu "MXC SSI support" + +config MXC_SSI + tristate "SSI support" + ---help--- + Say Y to get the SSI services API available on MXC platform. + +endmenu diff --git a/drivers/mxc/ssi/Makefile b/drivers/mxc/ssi/Makefile new file mode 100644 index 000000000000..f9bb4419fc19 --- /dev/null +++ b/drivers/mxc/ssi/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the kernel SSI device drivers. +# + +obj-$(CONFIG_MXC_SSI) += ssimod.o + +ssimod-objs := ssi.o diff --git a/drivers/mxc/ssi/registers.h b/drivers/mxc/ssi/registers.h new file mode 100644 index 000000000000..ba98d96c5274 --- /dev/null +++ b/drivers/mxc/ssi/registers.h @@ -0,0 +1,208 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file ../ssi/registers.h + * @brief This header file contains SSI driver low level definition to access module registers. + * + * @ingroup SSI + */ + +#ifndef __MXC_SSI_REGISTERS_H__ +#define __MXC_SSI_REGISTERS_H__ + +/*! + * This include to define bool type, false and true definitions. + */ +#include <mach/hardware.h> + +#define SPBA_CPU_SSI 0x07 + +#define MXC_SSISTX0 0x00 +#define MXC_SSISTX1 0x04 +#define MXC_SSISRX0 0x08 +#define MXC_SSISRX1 0x0C +#define MXC_SSISCR 0x10 +#define MXC_SSISISR 0x14 +#define MXC_SSISIER 0x18 +#define MXC_SSISTCR 0x1C +#define MXC_SSISRCR 0x20 +#define MXC_SSISTCCR 0x24 +#define MXC_SSISRCCR 0x28 +#define MXC_SSISFCSR 0x2C +#define MXC_SSISTR 0x30 +#define MXC_SSISOR 0x34 +#define MXC_SSISACNT 0x38 +#define MXC_SSISACADD 0x3C +#define MXC_SSISACDAT 0x40 +#define MXC_SSISATAG 0x44 +#define MXC_SSISTMSK 0x48 +#define MXC_SSISRMSK 0x4C + +/* MXC 91221 only */ +#define MXC_SSISACCST 0x50 +#define MXC_SSISACCEN 0x54 +#define MXC_SSISACCDIS 0x58 + +/*! SSI1 registers offset*/ +#define MXC_SSI1STX0 0x00 +#define MXC_SSI1STX1 0x04 +#define MXC_SSI1SRX0 0x08 +#define MXC_SSI1SRX1 0x0C +#define MXC_SSI1SCR 0x10 +#define MXC_SSI1SISR 0x14 +#define MXC_SSI1SIER 0x18 +#define MXC_SSI1STCR 0x1C +#define MXC_SSI1SRCR 0x20 +#define MXC_SSI1STCCR 0x24 +#define MXC_SSI1SRCCR 0x28 +#define MXC_SSI1SFCSR 0x2C +#define MXC_SSI1STR 0x30 +#define MXC_SSI1SOR 0x34 +#define MXC_SSI1SACNT 0x38 +#define MXC_SSI1SACADD 0x3C +#define MXC_SSI1SACDAT 0x40 +#define MXC_SSI1SATAG 0x44 +#define MXC_SSI1STMSK 0x48 +#define MXC_SSI1SRMSK 0x4C + +/* MXC91221 only */ + +#define MXC_SSISACCST 0x50 +#define MXC_SSISACCEN 0x54 +#define MXC_SSISACCDIS 0x58 + +/* Not on MXC91221 */ +/*! SSI2 registers offset*/ +#define MXC_SSI2STX0 0x00 +#define MXC_SSI2STX1 0x04 +#define MXC_SSI2SRX0 0x08 +#define MXC_SSI2SRX1 0x0C +#define MXC_SSI2SCR 0x10 +#define MXC_SSI2SISR 0x14 +#define MXC_SSI2SIER 0x18 +#define MXC_SSI2STCR 0x1C +#define MXC_SSI2SRCR 0x20 +#define MXC_SSI2STCCR 0x24 +#define MXC_SSI2SRCCR 0x28 +#define MXC_SSI2SFCSR 0x2C +#define MXC_SSI2STR 0x30 +#define MXC_SSI2SOR 0x34 +#define MXC_SSI2SACNT 0x38 +#define MXC_SSI2SACADD 0x3C +#define MXC_SSI2SACDAT 0x40 +#define MXC_SSI2SATAG 0x44 +#define MXC_SSI2STMSK 0x48 +#define MXC_SSI2SRMSK 0x4C + +/*! + * SCR Register bit shift definitions + */ +#define SSI_ENABLE_SHIFT 0 +#define SSI_TRANSMIT_ENABLE_SHIFT 1 +#define SSI_RECEIVE_ENABLE_SHIFT 2 +#define SSI_NETWORK_MODE_SHIFT 3 +#define SSI_SYNCHRONOUS_MODE_SHIFT 4 +#define SSI_I2S_MODE_SHIFT 5 +#define SSI_SYSTEM_CLOCK_SHIFT 7 +#define SSI_TWO_CHANNEL_SHIFT 8 +#define SSI_CLOCK_IDLE_SHIFT 9 + +/* MXC91221 only*/ +#define SSI_TX_FRAME_CLOCK_DISABLE_SHIFT 10 +#define SSI_RX_FRAME_CLOCK_DISABLE_SHIFT 11 + +/*! + * STCR & SRCR Registers bit shift definitions + */ +#define SSI_EARLY_FRAME_SYNC_SHIFT 0 +#define SSI_FRAME_SYNC_LENGTH_SHIFT 1 +#define SSI_FRAME_SYNC_INVERT_SHIFT 2 +#define SSI_CLOCK_POLARITY_SHIFT 3 +#define SSI_SHIFT_DIRECTION_SHIFT 4 +#define SSI_CLOCK_DIRECTION_SHIFT 5 +#define SSI_FRAME_DIRECTION_SHIFT 6 +#define SSI_FIFO_ENABLE_0_SHIFT 7 +#define SSI_FIFO_ENABLE_1_SHIFT 8 +#define SSI_BIT_0_SHIFT 9 + +/* MXC91221 only*/ +#define SSI_TX_FRAME_CLOCK_DISABLE_SHIFT 10 +#define SSI_RX_DATA_EXTENSION_SHIFT 10 /*SRCR only */ +/*! + * STCCR & SRCCR Registers bit shift definitions + */ +#define SSI_PRESCALER_MODULUS_SHIFT 0 +#define SSI_FRAME_RATE_DIVIDER_SHIFT 8 +#define SSI_WORD_LENGTH_SHIFT 13 +#define SSI_PRESCALER_RANGE_SHIFT 17 +#define SSI_DIVIDE_BY_TWO_SHIFT 18 +#define SSI_FRAME_DIVIDER_MASK 31 +#define SSI_MIN_FRAME_DIVIDER_RATIO 1 +#define SSI_MAX_FRAME_DIVIDER_RATIO 32 +#define SSI_PRESCALER_MODULUS_MASK 255 +#define SSI_MIN_PRESCALER_MODULUS_RATIO 1 +#define SSI_MAX_PRESCALER_MODULUS_RATIO 256 +#define SSI_WORD_LENGTH_MASK 15 + +#define SSI_IRQ_STATUS_NUMBER 25 + +/*! + * SFCSR Register bit shift definitions + */ +#define SSI_RX_FIFO_1_COUNT_SHIFT 28 +#define SSI_TX_FIFO_1_COUNT_SHIFT 24 +#define SSI_RX_FIFO_1_WATERMARK_SHIFT 20 +#define SSI_TX_FIFO_1_WATERMARK_SHIFT 16 +#define SSI_RX_FIFO_0_COUNT_SHIFT 12 +#define SSI_TX_FIFO_0_COUNT_SHIFT 8 +#define SSI_RX_FIFO_0_WATERMARK_SHIFT 4 +#define SSI_TX_FIFO_0_WATERMARK_SHIFT 0 +#define SSI_MIN_FIFO_WATERMARK 0 +#define SSI_MAX_FIFO_WATERMARK 8 + +/*! + * SSI Option Register (SOR) bit shift definitions + */ +#define SSI_FRAME_SYN_RESET_SHIFT 0 +#define SSI_WAIT_SHIFT 1 +#define SSI_INIT_SHIFT 3 +#define SSI_TRANSMITTER_CLEAR_SHIFT 4 +#define SSI_RECEIVER_CLEAR_SHIFT 5 +#define SSI_CLOCK_OFF_SHIFT 6 +#define SSI_WAIT_STATE_MASK 0x3 + +/*! + * SSI AC97 Control Register (SACNT) bit shift definitions + */ +#define AC97_MODE_ENABLE_SHIFT 0 +#define AC97_VARIABLE_OPERATION_SHIFT 1 +#define AC97_TAG_IN_FIFO_SHIFT 2 +#define AC97_READ_COMMAND_SHIFT 3 +#define AC97_WRITE_COMMAND_SHIFT 4 +#define AC97_FRAME_RATE_DIVIDER_SHIFT 5 +#define AC97_FRAME_RATE_MASK 0x3F + +/*! + * SSI Test Register (STR) bit shift definitions + */ +#define SSI_TEST_MODE_SHIFT 15 +#define SSI_RCK2TCK_SHIFT 14 +#define SSI_RFS2TFS_SHIFT 13 +#define SSI_RXSTATE_SHIFT 8 +#define SSI_TXD2RXD_SHIFT 7 +#define SSI_TCK2RCK_SHIFT 6 +#define SSI_TFS2RFS_SHIFT 5 +#define SSI_TXSTATE_SHIFT 0 + +#endif /* __MXC_SSI_REGISTERS_H__ */ diff --git a/drivers/mxc/ssi/ssi.c b/drivers/mxc/ssi/ssi.c new file mode 100644 index 000000000000..1b3a2729b174 --- /dev/null +++ b/drivers/mxc/ssi/ssi.c @@ -0,0 +1,1221 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ssi.c + * @brief This file contains the implementation of the SSI driver main services + * + * + * @ingroup SSI + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <asm/uaccess.h> +#include <mach/clock.h> + +#include "registers.h" +#include "ssi.h" + +static spinlock_t ssi_lock; +struct mxc_audio_platform_data *ssi_platform_data; + +EXPORT_SYMBOL(ssi_ac97_frame_rate_divider); +EXPORT_SYMBOL(ssi_ac97_get_command_address_register); +EXPORT_SYMBOL(ssi_ac97_get_command_data_register); +EXPORT_SYMBOL(ssi_ac97_get_tag_register); +EXPORT_SYMBOL(ssi_ac97_mode_enable); +EXPORT_SYMBOL(ssi_ac97_tag_in_fifo); +EXPORT_SYMBOL(ssi_ac97_read_command); +EXPORT_SYMBOL(ssi_ac97_set_command_address_register); +EXPORT_SYMBOL(ssi_ac97_set_command_data_register); +EXPORT_SYMBOL(ssi_ac97_set_tag_register); +EXPORT_SYMBOL(ssi_ac97_variable_mode); +EXPORT_SYMBOL(ssi_ac97_write_command); +EXPORT_SYMBOL(ssi_clock_idle_state); +EXPORT_SYMBOL(ssi_clock_off); +EXPORT_SYMBOL(ssi_enable); +EXPORT_SYMBOL(ssi_get_data); +EXPORT_SYMBOL(ssi_get_status); +EXPORT_SYMBOL(ssi_i2s_mode); +EXPORT_SYMBOL(ssi_interrupt_disable); +EXPORT_SYMBOL(ssi_interrupt_enable); +EXPORT_SYMBOL(ssi_network_mode); +EXPORT_SYMBOL(ssi_receive_enable); +EXPORT_SYMBOL(ssi_rx_bit0); +EXPORT_SYMBOL(ssi_rx_clock_direction); +EXPORT_SYMBOL(ssi_rx_clock_divide_by_two); +EXPORT_SYMBOL(ssi_rx_clock_polarity); +EXPORT_SYMBOL(ssi_rx_clock_prescaler); +EXPORT_SYMBOL(ssi_rx_early_frame_sync); +EXPORT_SYMBOL(ssi_rx_fifo_counter); +EXPORT_SYMBOL(ssi_rx_fifo_enable); +EXPORT_SYMBOL(ssi_rx_fifo_full_watermark); +EXPORT_SYMBOL(ssi_rx_flush_fifo); +EXPORT_SYMBOL(ssi_rx_frame_direction); +EXPORT_SYMBOL(ssi_rx_frame_rate); +EXPORT_SYMBOL(ssi_rx_frame_sync_active); +EXPORT_SYMBOL(ssi_rx_frame_sync_length); +EXPORT_SYMBOL(ssi_rx_mask_time_slot); +EXPORT_SYMBOL(ssi_rx_prescaler_modulus); +EXPORT_SYMBOL(ssi_rx_shift_direction); +EXPORT_SYMBOL(ssi_rx_word_length); +EXPORT_SYMBOL(ssi_set_data); +EXPORT_SYMBOL(ssi_set_wait_states); +EXPORT_SYMBOL(ssi_synchronous_mode); +EXPORT_SYMBOL(ssi_system_clock); +EXPORT_SYMBOL(ssi_transmit_enable); +EXPORT_SYMBOL(ssi_two_channel_mode); +EXPORT_SYMBOL(ssi_tx_bit0); +EXPORT_SYMBOL(ssi_tx_clock_direction); +EXPORT_SYMBOL(ssi_tx_clock_divide_by_two); +EXPORT_SYMBOL(ssi_tx_clock_polarity); +EXPORT_SYMBOL(ssi_tx_clock_prescaler); +EXPORT_SYMBOL(ssi_tx_early_frame_sync); +EXPORT_SYMBOL(ssi_tx_fifo_counter); +EXPORT_SYMBOL(ssi_tx_fifo_empty_watermark); +EXPORT_SYMBOL(ssi_tx_fifo_enable); +EXPORT_SYMBOL(ssi_tx_flush_fifo); +EXPORT_SYMBOL(ssi_tx_frame_direction); +EXPORT_SYMBOL(ssi_tx_frame_rate); +EXPORT_SYMBOL(ssi_tx_frame_sync_active); +EXPORT_SYMBOL(ssi_tx_frame_sync_length); +EXPORT_SYMBOL(ssi_tx_mask_time_slot); +EXPORT_SYMBOL(ssi_tx_prescaler_modulus); +EXPORT_SYMBOL(ssi_tx_shift_direction); +EXPORT_SYMBOL(ssi_tx_word_length); +EXPORT_SYMBOL(get_ssi_fifo_addr); + +struct resource *res; +unsigned long base_addr_1; +unsigned long base_addr_2; + +unsigned int get_ssi_fifo_addr(unsigned int ssi, int direction) +{ + unsigned int fifo_addr; + if (direction == 1) { + if (ssi_platform_data->ssi_num == 2) { + fifo_addr = + (ssi == + SSI1) ? (int)(base_addr_1 + + MXC_SSI1STX0) : (int)(base_addr_2 + + MXC_SSI2STX0); + } else { + fifo_addr = (int)(base_addr_1 + MXC_SSI1STX0); + } + } else { + fifo_addr = (int)(base_addr_1 + MXC_SSI1SRX0); + } + return fifo_addr; +} + +void * get_ssi_base_addr(unsigned int ssi) +{ + if (ssi_platform_data->ssi_num == 2) { + if (ssi == SSI1) + return IO_ADDRESS(base_addr_1); + else + return IO_ADDRESS(base_addr_2); + } + return IO_ADDRESS(base_addr_1); +} + +void set_register_bits(unsigned int mask, unsigned int data, + unsigned int offset, unsigned int ssi) +{ + volatile unsigned long reg = 0; + void *base_addr = get_ssi_base_addr(ssi); + unsigned long flags = 0; + + spin_lock_irqsave(&ssi_lock, flags); + reg = __raw_readl(base_addr + offset); + reg = (reg & (~mask)) | data; + __raw_writel(reg, base_addr + offset); + spin_unlock_irqrestore(&ssi_lock, flags); +} + +unsigned long getreg_value(unsigned int offset, unsigned int ssi) +{ + void *base_addr = get_ssi_base_addr(ssi); + return __raw_readl(base_addr + offset); +} + +void set_register(unsigned int data, unsigned int offset, unsigned int ssi) +{ + void *base_addr = get_ssi_base_addr(ssi); + __raw_writel(data, base_addr + offset); +} + +/*! + * This function controls the AC97 frame rate divider. + * + * @param module the module number + * @param frame_rate_divider the AC97 frame rate divider + */ +void ssi_ac97_frame_rate_divider(ssi_mod module, + unsigned char frame_rate_divider) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + reg |= ((frame_rate_divider & AC97_FRAME_RATE_MASK) + << AC97_FRAME_RATE_DIVIDER_SHIFT); + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function gets the AC97 command address register. + * + * @param module the module number + * @return This function returns the command address slot information. + */ +unsigned int ssi_ac97_get_command_address_register(ssi_mod module) +{ + return getreg_value(MXC_SSISACADD, module); +} + +/*! + * This function gets the AC97 command data register. + * + * @param module the module number + * @return This function returns the command data slot information. + */ +unsigned int ssi_ac97_get_command_data_register(ssi_mod module) +{ + return getreg_value(MXC_SSISACDAT, module); +} + +/*! + * This function gets the AC97 tag register. + * + * @param module the module number + * @return This function returns the tag information. + */ +unsigned int ssi_ac97_get_tag_register(ssi_mod module) +{ + return getreg_value(MXC_SSISATAG, module); +} + +/*! + * This function controls the AC97 mode. + * + * @param module the module number + * @param state the AC97 mode state (enabled or disabled) + */ +void ssi_ac97_mode_enable(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_MODE_ENABLE_SHIFT); + } else { + reg &= ~(1 << AC97_MODE_ENABLE_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the AC97 tag in FIFO behavior. + * + * @param module the module number + * @param state the tag in fifo behavior (Tag info stored in Rx FIFO 0 if true, + * Tag info stored in SATAG register otherwise) + */ +void ssi_ac97_tag_in_fifo(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_TAG_IN_FIFO_SHIFT); + } else { + reg &= ~(1 << AC97_TAG_IN_FIFO_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the AC97 read command. + * + * @param module the module number + * @param state the next AC97 command is a read command or not + */ +void ssi_ac97_read_command(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_READ_COMMAND_SHIFT); + } else { + reg &= ~(1 << AC97_READ_COMMAND_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function sets the AC97 command address register. + * + * @param module the module number + * @param address the command address slot information + */ +void ssi_ac97_set_command_address_register(ssi_mod module, unsigned int address) +{ + set_register(address, MXC_SSISACADD, module); +} + +/*! + * This function sets the AC97 command data register. + * + * @param module the module number + * @param data the command data slot information + */ +void ssi_ac97_set_command_data_register(ssi_mod module, unsigned int data) +{ + set_register(data, MXC_SSISACDAT, module); +} + +/*! + * This function sets the AC97 tag register. + * + * @param module the module number + * @param tag the tag information + */ +void ssi_ac97_set_tag_register(ssi_mod module, unsigned int tag) +{ + set_register(tag, MXC_SSISATAG, module); +} + +/*! + * This function controls the AC97 variable mode. + * + * @param module the module number + * @param state the AC97 variable mode state (enabled or disabled) + */ void ssi_ac97_variable_mode(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_VARIABLE_OPERATION_SHIFT); + } else { + reg &= ~(1 << AC97_VARIABLE_OPERATION_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the AC97 write command. + * + * @param module the module number + * @param state the next AC97 command is a write command or not + */ +void ssi_ac97_write_command(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_WRITE_COMMAND_SHIFT); + } else { + reg &= ~(1 << AC97_WRITE_COMMAND_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the idle state of the transmit clock port during SSI internal gated mode. + * + * @param module the module number + * @param state the clock idle state + */ +void ssi_clock_idle_state(ssi_mod module, idle_state state) +{ + set_register_bits(1 << SSI_CLOCK_IDLE_SHIFT, + state << SSI_CLOCK_IDLE_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function turns off/on the ccm_ssi_clk to reduce power consumption. + * + * @param module the module number + * @param state the state for ccm_ssi_clk (true: turn off, else:turn on) + */ +void ssi_clock_off(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_CLOCK_OFF_SHIFT, + state << SSI_CLOCK_OFF_SHIFT, MXC_SSISOR, module); +} + +/*! + * This function enables/disables the SSI module. + * + * @param module the module number + * @param state the state for SSI module + */ +void ssi_enable(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_ENABLE_SHIFT, state << SSI_ENABLE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function gets the data word in the Receive FIFO of the SSI module. + * + * @param module the module number + * @param fifo the Receive FIFO to read + * @return This function returns the read data. + */ +unsigned int ssi_get_data(ssi_mod module, fifo_nb fifo) +{ + unsigned int result = 0; + + if (ssi_fifo_0 == fifo) { + result = getreg_value(MXC_SSISRX0, module); + } else { + result = getreg_value(MXC_SSISRX1, module); + } + + return result; +} + +/*! + * This function returns the status of the SSI module (SISR register) as a combination of status. + * + * @param module the module number + * @return This function returns the status of the SSI module + */ +ssi_status_enable_mask ssi_get_status(ssi_mod module) +{ + unsigned int result; + + result = getreg_value(MXC_SSISISR, module); + result &= ((1 << SSI_IRQ_STATUS_NUMBER) - 1); + + return (ssi_status_enable_mask) result; +} + +/*! + * This function selects the I2S mode of the SSI module. + * + * @param module the module number + * @param mode the I2S mode + */ +void ssi_i2s_mode(ssi_mod module, mode_i2s mode) +{ + set_register_bits(3 << SSI_I2S_MODE_SHIFT, mode << SSI_I2S_MODE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function disables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to disable + */ +void ssi_interrupt_disable(ssi_mod module, ssi_status_enable_mask mask) +{ + set_register_bits(mask, 0, MXC_SSISIER, module); +} + +/*! + * This function enables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to enable + */ +void ssi_interrupt_enable(ssi_mod module, ssi_status_enable_mask mask) +{ + set_register_bits(0, mask, MXC_SSISIER, module); +} + +/*! + * This function enables/disables the network mode of the SSI module. + * + * @param module the module number + * @param state the network mode state + */ +void ssi_network_mode(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_NETWORK_MODE_SHIFT, + state << SSI_NETWORK_MODE_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function enables/disables the receive section of the SSI module. + * + * @param module the module number + * @param state the receive section state + */ +void ssi_receive_enable(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_RECEIVE_ENABLE_SHIFT, + state << SSI_RECEIVE_ENABLE_SHIFT, MXC_SSISCR, + module); +} + +/*! + * This function configures the SSI module to receive data word at bit position 0 or 23 in the Receive shift register. + * + * @param module the module number + * @param state the state to receive at bit 0 + */ +void ssi_rx_bit0(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_BIT_0_SHIFT, state << SSI_BIT_0_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function controls the source of the clock signal used to clock the Receive shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_rx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_CLOCK_DIRECTION_SHIFT, + direction << SSI_CLOCK_DIRECTION_SHIFT, MXC_SSISRCR, + module); +} + +/*! + * This function configures the divide-by-two divider of the SSI module for the receive section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_rx_clock_divide_by_two(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_DIVIDE_BY_TWO_SHIFT, + state << SSI_DIVIDE_BY_TWO_SHIFT, MXC_SSISRCCR, + module); +} + +/*! + * This function controls which bit clock edge is used to clock in data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_rx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity) +{ + set_register_bits(1 << SSI_CLOCK_POLARITY_SHIFT, + polarity << SSI_CLOCK_POLARITY_SHIFT, MXC_SSISRCR, + module); +} + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the receive section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_rx_clock_prescaler(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_PRESCALER_RANGE_SHIFT, + state << SSI_PRESCALER_RANGE_SHIFT, + MXC_SSISRCCR, module); +} + +/*! + * This function controls the early frame sync configuration. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_rx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early) +{ + set_register_bits(1 << SSI_EARLY_FRAME_SYNC_SHIFT, + early << SSI_EARLY_FRAME_SYNC_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function gets the number of data words in the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Rx FIFO. + */ +unsigned char ssi_rx_fifo_counter(ssi_mod module, fifo_nb fifo) +{ + unsigned char result; + result = 0; + + if (ssi_fifo_0 == fifo) { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_RX_FIFO_0_COUNT_SHIFT); + result = result >> SSI_RX_FIFO_0_COUNT_SHIFT; + } else { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_RX_FIFO_1_COUNT_SHIFT); + result = result >> SSI_RX_FIFO_1_COUNT_SHIFT; + } + + return result; +} + +/*! + * This function enables the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enable the state of the fifo, enabled or disabled + */ + +void ssi_rx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enable) +{ + volatile unsigned int reg; + + reg = getreg_value(MXC_SSISRCR, module); + if (enable == true) { + reg |= ((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } else { + reg &= ~((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } + + set_register(reg, MXC_SSISRCR, module); +} + +/*! + * This function controls the threshold at which the RFFx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_fifo_full_watermark(ssi_mod module, + fifo_nb fifo, unsigned char watermark) +{ + int result = -1; + result = -1; + + if ((watermark > SSI_MIN_FIFO_WATERMARK) && + (watermark <= SSI_MAX_FIFO_WATERMARK)) { + if (ssi_fifo_0 == fifo) { + set_register_bits(0xf << SSI_RX_FIFO_0_WATERMARK_SHIFT, + watermark << + SSI_RX_FIFO_0_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } else { + set_register_bits(0xf << SSI_RX_FIFO_1_WATERMARK_SHIFT, + watermark << + SSI_RX_FIFO_1_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } + + result = 0; + } + + return result; +} + +/*! + * This function flushes the Receive FIFOs. + * + * @param module the module number + */ +void ssi_rx_flush_fifo(ssi_mod module) +{ + set_register_bits(0, 1 << SSI_RECEIVER_CLEAR_SHIFT, MXC_SSISOR, module); +} + +/*! + * This function controls the direction of the Frame Sync signal for the receive section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_rx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_FRAME_DIRECTION_SHIFT, + direction << SSI_FRAME_DIRECTION_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function configures the Receive frame rate divider for the receive section. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_frame_rate(ssi_mod module, unsigned char ratio) +{ + int result = -1; + + if ((ratio >= SSI_MIN_FRAME_DIVIDER_RATIO) && + (ratio <= SSI_MAX_FRAME_DIVIDER_RATIO)) { + set_register_bits(SSI_FRAME_DIVIDER_MASK << + SSI_FRAME_RATE_DIVIDER_SHIFT, + (ratio - 1) << SSI_FRAME_RATE_DIVIDER_SHIFT, + MXC_SSISRCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls the Frame Sync active polarity for the receive section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_rx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active) +{ + set_register_bits(1 << SSI_FRAME_SYNC_INVERT_SHIFT, + active << SSI_FRAME_SYNC_INVERT_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the receive section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_rx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length) +{ + set_register_bits(1 << SSI_FRAME_SYNC_LENGTH_SHIFT, + length << SSI_FRAME_SYNC_LENGTH_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function configures the time slot(s) to mask for the receive section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_rx_mask_time_slot(ssi_mod module, unsigned int mask) +{ + set_register_bits(0xFFFFFFFF, mask, MXC_SSISRMSK, module); +} + +/*! + * This function configures the Prescale divider for the receive section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_prescaler_modulus(ssi_mod module, unsigned int divider) +{ + int result = -1; + + if ((divider >= SSI_MIN_PRESCALER_MODULUS_RATIO) && + (divider <= SSI_MAX_PRESCALER_MODULUS_RATIO)) { + + set_register_bits(SSI_PRESCALER_MODULUS_MASK << + SSI_PRESCALER_MODULUS_SHIFT, + (divider - 1) << SSI_PRESCALER_MODULUS_SHIFT, + MXC_SSISRCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls whether the MSB or LSB will be received first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_rx_shift_direction(ssi_mod module, ssi_tx_rx_shift_direction direction) +{ + set_register_bits(1 << SSI_SHIFT_DIRECTION_SHIFT, + direction << SSI_SHIFT_DIRECTION_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function configures the Receive word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_rx_word_length(ssi_mod module, ssi_word_length length) +{ + set_register_bits(SSI_WORD_LENGTH_MASK << SSI_WORD_LENGTH_SHIFT, + length << SSI_WORD_LENGTH_SHIFT, + MXC_SSISRCCR, module); +} + +/*! + * This function sets the data word in the Transmit FIFO of the SSI module. + * + * @param module the module number + * @param fifo the FIFO number + * @param data the data to load in the FIFO + */ + +void ssi_set_data(ssi_mod module, fifo_nb fifo, unsigned int data) +{ + if (ssi_fifo_0 == fifo) { + set_register(data, MXC_SSISTX0, module); + } else { + set_register(data, MXC_SSISTX1, module); + } +} + +/*! + * This function controls the number of wait states between the core and SSI. + * + * @param module the module number + * @param wait the number of wait state(s) + */ +void ssi_set_wait_states(ssi_mod module, ssi_wait_states wait) +{ + set_register_bits(SSI_WAIT_STATE_MASK << SSI_WAIT_SHIFT, + wait << SSI_WAIT_SHIFT, MXC_SSISOR, module); +} + +/*! + * This function enables/disables the synchronous mode of the SSI module. + * + * @param module the module number + * @param state the synchronous mode state + */ +void ssi_synchronous_mode(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_SYNCHRONOUS_MODE_SHIFT, + state << SSI_SYNCHRONOUS_MODE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function allows the SSI module to output the SYS_CLK at the SRCK port. + * + * @param module the module number + * @param state the system clock state + */ +void ssi_system_clock(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_SYSTEM_CLOCK_SHIFT, + state << SSI_SYSTEM_CLOCK_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function enables/disables the transmit section of the SSI module. + * + * @param module the module number + * @param state the transmit section state + */ +void ssi_transmit_enable(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_TRANSMIT_ENABLE_SHIFT, + state << SSI_TRANSMIT_ENABLE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function allows the SSI module to operate in the two channel mode. + * + * @param module the module number + * @param state the two channel mode state + */ +void ssi_two_channel_mode(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_TWO_CHANNEL_SHIFT, + state << SSI_TWO_CHANNEL_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function configures the SSI module to transmit data word from bit position 0 or 23 in the Transmit shift register. + * + * @param module the module number + * @param state the transmit from bit 0 state + */ +void ssi_tx_bit0(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_BIT_0_SHIFT, + state << SSI_BIT_0_SHIFT, MXC_SSISTCR, module); +} + +/*! + * This function controls the direction of the clock signal used to clock the Transmit shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_tx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_CLOCK_DIRECTION_SHIFT, + direction << SSI_CLOCK_DIRECTION_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the divide-by-two divider of the SSI module for the transmit section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_tx_clock_divide_by_two(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_DIVIDE_BY_TWO_SHIFT, + state << SSI_DIVIDE_BY_TWO_SHIFT, + MXC_SSISTCCR, module); +} + +/*! + * This function controls which bit clock edge is used to clock out data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_tx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity) +{ + set_register_bits(1 << SSI_CLOCK_POLARITY_SHIFT, + polarity << SSI_CLOCK_POLARITY_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the transmit section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_tx_clock_prescaler(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_PRESCALER_RANGE_SHIFT, + state << SSI_PRESCALER_RANGE_SHIFT, + MXC_SSISTCCR, module); +} + +/*! + * This function controls the early frame sync configuration for the transmit section. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_tx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early) +{ + set_register_bits(1 << SSI_EARLY_FRAME_SYNC_SHIFT, + early << SSI_EARLY_FRAME_SYNC_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function gets the number of data words in the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Tx FIFO. + */ +unsigned char ssi_tx_fifo_counter(ssi_mod module, fifo_nb fifo) +{ + unsigned char result = 0; + + if (ssi_fifo_0 == fifo) { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_TX_FIFO_0_COUNT_SHIFT); + result >>= SSI_TX_FIFO_0_COUNT_SHIFT; + } else { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_TX_FIFO_1_COUNT_SHIFT); + result >>= SSI_TX_FIFO_1_COUNT_SHIFT; + } + + return result; +} + +/*! + * This function controls the threshold at which the TFEx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_fifo_empty_watermark(ssi_mod module, + fifo_nb fifo, unsigned char watermark) +{ + int result = -1; + + if ((watermark > SSI_MIN_FIFO_WATERMARK) && + (watermark <= SSI_MAX_FIFO_WATERMARK)) { + if (ssi_fifo_0 == fifo) { + set_register_bits(0xf << SSI_TX_FIFO_0_WATERMARK_SHIFT, + watermark << + SSI_TX_FIFO_0_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } else { + set_register_bits(0xf << SSI_TX_FIFO_1_WATERMARK_SHIFT, + watermark << + SSI_TX_FIFO_1_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } + + result = 0; + } + + return result; +} + +/*! + * This function enables the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enable the state of the fifo, enabled or disabled + */ + +void ssi_tx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enable) +{ + unsigned int reg; + + reg = getreg_value(MXC_SSISTCR, module); + if (enable == true) { + reg |= ((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } else { + reg &= ~((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } + + set_register(reg, MXC_SSISTCR, module); +} + +/*! + * This function flushes the Transmit FIFOs. + * + * @param module the module number + */ +void ssi_tx_flush_fifo(ssi_mod module) +{ + set_register_bits(0, 1 << SSI_TRANSMITTER_CLEAR_SHIFT, + MXC_SSISOR, module); +} + +/*! + * This function controls the direction of the Frame Sync signal for the transmit section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_tx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_FRAME_DIRECTION_SHIFT, + direction << SSI_FRAME_DIRECTION_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the Transmit frame rate divider. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_frame_rate(ssi_mod module, unsigned char ratio) +{ + int result = -1; + + if ((ratio >= SSI_MIN_FRAME_DIVIDER_RATIO) && + (ratio <= SSI_MAX_FRAME_DIVIDER_RATIO)) { + + set_register_bits(SSI_FRAME_DIVIDER_MASK << + SSI_FRAME_RATE_DIVIDER_SHIFT, + (ratio - 1) << SSI_FRAME_RATE_DIVIDER_SHIFT, + MXC_SSISTCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls the Frame Sync active polarity for the transmit section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_tx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active) +{ + set_register_bits(1 << SSI_FRAME_SYNC_INVERT_SHIFT, + active << SSI_FRAME_SYNC_INVERT_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the transmit section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_tx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length) +{ + set_register_bits(1 << SSI_FRAME_SYNC_LENGTH_SHIFT, + length << SSI_FRAME_SYNC_LENGTH_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the time slot(s) to mask for the transmit section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_tx_mask_time_slot(ssi_mod module, unsigned int mask) +{ + set_register_bits(0xFFFFFFFF, mask, MXC_SSISTMSK, module); +} + +/*! + * This function configures the Prescale divider for the transmit section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_prescaler_modulus(ssi_mod module, unsigned int divider) +{ + int result = -1; + + if ((divider >= SSI_MIN_PRESCALER_MODULUS_RATIO) && + (divider <= SSI_MAX_PRESCALER_MODULUS_RATIO)) { + + set_register_bits(SSI_PRESCALER_MODULUS_MASK << + SSI_PRESCALER_MODULUS_SHIFT, + (divider - 1) << SSI_PRESCALER_MODULUS_SHIFT, + MXC_SSISTCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls whether the MSB or LSB will be transmitted first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_tx_shift_direction(ssi_mod module, ssi_tx_rx_shift_direction direction) +{ + set_register_bits(1 << SSI_SHIFT_DIRECTION_SHIFT, + direction << SSI_SHIFT_DIRECTION_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the Transmit word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_tx_word_length(ssi_mod module, ssi_word_length length) +{ + set_register_bits(SSI_WORD_LENGTH_MASK << SSI_WORD_LENGTH_SHIFT, + length << SSI_WORD_LENGTH_SHIFT, + MXC_SSISTCCR, module); +} + +/*! + * This function initializes the driver in terms of memory of the soundcard + * and some basic HW clock settings. + * + * @return 0 on success, -1 otherwise. + */ +static int __init ssi_probe(struct platform_device *pdev) +{ + int ret = -1; + ssi_platform_data = + (struct mxc_audio_platform_data *)pdev->dev.platform_data; + if (!ssi_platform_data) { + dev_err(&pdev->dev, "can't get the platform data for SSI\n"); + return -EINVAL; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res) { + dev_err(&pdev->dev, "can't get platform resource - SSI\n"); + ret = -ENOMEM; + goto err; + } + + if (pdev->id == 0) { + base_addr_1 = res->start; + } else if (pdev->id == 1) { + base_addr_2 = res->start; + } + + printk(KERN_INFO "SSI %d module loaded successfully \n", pdev->id + 1); + + return 0; + err: + return -1; + +} + +static int ssi_remove(struct platform_device *dev) +{ + return 0; +} + +static struct platform_driver mxc_ssi_driver = { + .probe = ssi_probe, + .remove = ssi_remove, + .driver = { + .name = "mxc_ssi", + }, +}; + +/*! + * This function implements the init function of the SSI device. + * This function is called when the module is loaded. + * + * @return This function returns 0. + */ +static int __init ssi_init(void) +{ + spin_lock_init(&ssi_lock); + return platform_driver_register(&mxc_ssi_driver); + +} + +/*! + * This function implements the exit function of the SPI device. + * This function is called when the module is unloaded. + * + */ +static void __exit ssi_exit(void) +{ + platform_driver_unregister(&mxc_ssi_driver); + printk(KERN_INFO "SSI module unloaded successfully\n"); +} + +module_init(ssi_init); +module_exit(ssi_exit); + +MODULE_DESCRIPTION("SSI char device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/ssi/ssi.h b/drivers/mxc/ssi/ssi.h new file mode 100644 index 000000000000..22032b6616fa --- /dev/null +++ b/drivers/mxc/ssi/ssi.h @@ -0,0 +1,574 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @defgroup SSI Synchronous Serial Interface (SSI) Driver + */ + + /*! + * @file ssi.h + * @brief This header file contains SSI driver functions prototypes. + * + * @ingroup SSI + */ + +#ifndef __MXC_SSI_H__ +#define __MXC_SSI_H__ + +#include "ssi_types.h" + +/*! + * This function gets the SSI fifo address. + * + * @param ssi ssi number + * @param direction To indicate playback / recording + * @return This function returns the SSI fifo address. + */ +unsigned int get_ssi_fifo_addr(unsigned int ssi, int direction); + +/*! + * This function controls the AC97 frame rate divider. + * + * @param module the module number + * @param frame_rate_divider the AC97 frame rate divider + */ +void ssi_ac97_frame_rate_divider(ssi_mod module, + unsigned char frame_rate_divider); + +/*! + * This function gets the AC97 command address register. + * + * @param module the module number + * @return This function returns the command address slot information. + */ +unsigned int ssi_ac97_get_command_address_register(ssi_mod module); + +/*! + * This function gets the AC97 command data register. + * + * @param module the module number + * @return This function returns the command data slot information. + */ +unsigned int ssi_ac97_get_command_data_register(ssi_mod module); + +/*! + * This function gets the AC97 tag register. + * + * @param module the module number + * @return This function returns the tag information. + */ +unsigned int ssi_ac97_get_tag_register(ssi_mod module); + +/*! + * This function controls the AC97 mode. + * + * @param module the module number + * @param state the AC97 mode state (enabled or disabled) + */ +void ssi_ac97_mode_enable(ssi_mod module, bool state); + +/*! + * This function controls the AC97 tag in FIFO behavior. + * + * @param module the module number + * @param state the tag in fifo behavior (Tag info stored in Rx FIFO 0 if TRUE, + * Tag info stored in SATAG register otherwise) + */ +void ssi_ac97_tag_in_fifo(ssi_mod module, bool state); + +/*! + * This function controls the AC97 read command. + * + * @param module the module number + * @param state the next AC97 command is a read command or not + */ +void ssi_ac97_read_command(ssi_mod module, bool state); + +/*! + * This function sets the AC97 command address register. + * + * @param module the module number + * @param address the command address slot information + */ +void ssi_ac97_set_command_address_register(ssi_mod module, + unsigned int address); + +/*! + * This function sets the AC97 command data register. + * + * @param module the module number + * @param data the command data slot information + */ +void ssi_ac97_set_command_data_register(ssi_mod module, unsigned int data); + +/*! + * This function sets the AC97 tag register. + * + * @param module the module number + * @param tag the tag information + */ +void ssi_ac97_set_tag_register(ssi_mod module, unsigned int tag); + +/*! + * This function controls the AC97 variable mode. + * + * @param module the module number + * @param state the AC97 variable mode state (enabled or disabled) + */ +void ssi_ac97_variable_mode(ssi_mod module, bool state); + +/*! + * This function controls the AC97 write command. + * + * @param module the module number + * @param state the next AC97 command is a write command or not + */ +void ssi_ac97_write_command(ssi_mod module, bool state); + +/*! + * This function controls the idle state of the transmit clock port during SSI internal gated mode. + * + * @param module the module number + * @param state the clock idle state + */ +void ssi_clock_idle_state(ssi_mod module, idle_state state); + +/*! + * This function turns off/on the ccm_ssi_clk to reduce power consumption. + * + * @param module the module number + * @param state the state for ccm_ssi_clk (true: turn off, else:turn on) + */ +void ssi_clock_off(ssi_mod module, bool state); + +/*! + * This function enables/disables the SSI module. + * + * @param module the module number + * @param state the state for SSI module + */ +void ssi_enable(ssi_mod module, bool state); + +/*! + * This function gets the data word in the Receive FIFO of the SSI module. + * + * @param module the module number + * @param fifo the Receive FIFO to read + * @return This function returns the read data. + */ +unsigned int ssi_get_data(ssi_mod module, fifo_nb fifo); + +/*! + * This function returns the status of the SSI module (SISR register) as a combination of status. + * + * @param module the module number + * @return This function returns the status of the SSI module. + */ +ssi_status_enable_mask ssi_get_status(ssi_mod module); + +/*! + * This function selects the I2S mode of the SSI module. + * + * @param module the module number + * @param mode the I2S mode + */ +void ssi_i2s_mode(ssi_mod module, mode_i2s mode); + +/*! + * This function disables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to disable + */ +void ssi_interrupt_disable(ssi_mod module, ssi_status_enable_mask mask); + +/*! + * This function enables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to enable + */ +void ssi_interrupt_enable(ssi_mod module, ssi_status_enable_mask mask); + +/*! + * This function enables/disables the network mode of the SSI module. + * + * @param module the module number + * @param state the network mode state + */ +void ssi_network_mode(ssi_mod module, bool state); + +/*! + * This function enables/disables the receive section of the SSI module. + * + * @param module the module number + * @param state the receive section state + */ +void ssi_receive_enable(ssi_mod module, bool state); + +/*! + * This function configures the SSI module to receive data word at bit position 0 or 23 in the Receive shift register. + * + * @param module the module number + * @param state the state to receive at bit 0 + */ +void ssi_rx_bit0(ssi_mod module, bool state); + +/*! + * This function controls the source of the clock signal used to clock the Receive shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_rx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the divide-by-two divider of the SSI module for the receive section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_rx_clock_divide_by_two(ssi_mod module, bool state); + +/*! + * This function controls which bit clock edge is used to clock in data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_rx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity); + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the receive section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_rx_clock_prescaler(ssi_mod module, bool state); + +/*! + * This function controls the early frame sync configuration. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_rx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early); + +/*! + * This function gets the number of data words in the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Rx FIFO. + */ +unsigned char ssi_rx_fifo_counter(ssi_mod module, fifo_nb fifo); + +/*! + * This function enables the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enabled the state of the fifo, enabled or disabled + */ +void ssi_rx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enabled); + +/*! + * This function controls the threshold at which the RFFx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_fifo_full_watermark(ssi_mod module, + fifo_nb fifo, unsigned char watermark); + +/*! + * This function flushes the Receive FIFOs. + * + * @param module the module number + */ +void ssi_rx_flush_fifo(ssi_mod module); + +/*! + * This function controls the direction of the Frame Sync signal for the receive section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_rx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the Receive frame rate divider for the receive section. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_frame_rate(ssi_mod module, unsigned char ratio); + +/*! + * This function controls the Frame Sync active polarity for the receive section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_rx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active); + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the receive section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_rx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length); + +/*! + * This function configures the time slot(s) to mask for the receive section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_rx_mask_time_slot(ssi_mod module, unsigned int mask); + +/*! + * This function configures the Prescale divider for the receive section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_prescaler_modulus(ssi_mod module, unsigned int divider); + +/*! + * This function controls whether the MSB or LSB will be received first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_rx_shift_direction(ssi_mod module, + ssi_tx_rx_shift_direction direction); + +/*! + * This function configures the Receive word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_rx_word_length(ssi_mod module, ssi_word_length length); + +/*! + * This function sets the data word in the Transmit FIFO of the SSI module. + * + * @param module the module number + * @param fifo the FIFO number + * @param data the data to load in the FIFO + */ +void ssi_set_data(ssi_mod module, fifo_nb fifo, unsigned int data); + +/*! + * This function controls the number of wait states between the core and SSI. + * + * @param module the module number + * @param wait the number of wait state(s) + */ +void ssi_set_wait_states(ssi_mod module, ssi_wait_states wait); + +/*! + * This function enables/disables the synchronous mode of the SSI module. + * + * @param module the module number + * @param state the synchronous mode state + */ +void ssi_synchronous_mode(ssi_mod module, bool state); + +/*! + * This function allows the SSI module to output the SYS_CLK at the SRCK port. + * + * @param module the module number + * @param state the system clock state + */ +void ssi_system_clock(ssi_mod module, bool state); + +/*! + * This function enables/disables the transmit section of the SSI module. + * + * @param module the module number + * @param state the transmit section state + */ +void ssi_transmit_enable(ssi_mod module, bool state); + +/*! + * This function allows the SSI module to operate in the two channel mode. + * + * @param module the module number + * @param state the two channel mode state + */ +void ssi_two_channel_mode(ssi_mod module, bool state); + +/*! + * This function configures the SSI module to transmit data word from bit position 0 or 23 in the Transmit shift register. + * + * @param module the module number + * @param state the transmit from bit 0 state + */ +void ssi_tx_bit0(ssi_mod module, bool state); + +/*! + * This function controls the direction of the clock signal used to clock the Transmit shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_tx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the divide-by-two divider of the SSI module for the transmit section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_tx_clock_divide_by_two(ssi_mod module, bool state); + +/*! + * This function controls which bit clock edge is used to clock out data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_tx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity); + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the transmit section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_tx_clock_prescaler(ssi_mod module, bool state); + +/*! + * This function controls the early frame sync configuration for the transmit section. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_tx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early); + +/*! + * This function gets the number of data words in the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Tx FIFO. + */ +unsigned char ssi_tx_fifo_counter(ssi_mod module, fifo_nb fifo); + +/*! + * This function controls the threshold at which the TFEx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_fifo_empty_watermark(ssi_mod module, fifo_nb fifo, + unsigned char watermark); + +/*! + * This function enables the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enable the state of the FIFO, enabled or disabled + */ +void ssi_tx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enable); + +/*! + * This function flushes the Transmit FIFOs. + * + * @param module the module number + */ +void ssi_tx_flush_fifo(ssi_mod module); + +/*! + * This function controls the direction of the Frame Sync signal for the transmit section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_tx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the Transmit frame rate divider. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_frame_rate(ssi_mod module, unsigned char ratio); + +/*! + * This function controls the Frame Sync active polarity for the transmit section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_tx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active); + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the transmit section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_tx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length); + +/*! + * This function configures the time slot(s) to mask for the transmit section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_tx_mask_time_slot(ssi_mod module, unsigned int mask); + +/*! + * This function configures the Prescale divider for the transmit section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_prescaler_modulus(ssi_mod module, unsigned int divider); + +/*! + * This function controls whether the MSB or LSB will be transmited first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_tx_shift_direction(ssi_mod module, + ssi_tx_rx_shift_direction direction); + +/*! + * This function configures the Transmit word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_tx_word_length(ssi_mod module, ssi_word_length length); + +#endif /* __MXC_SSI_H__ */ diff --git a/drivers/mxc/ssi/ssi_types.h b/drivers/mxc/ssi/ssi_types.h new file mode 100644 index 000000000000..015e65a6d2d2 --- /dev/null +++ b/drivers/mxc/ssi/ssi_types.h @@ -0,0 +1,367 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file ssi_types.h + * @brief This header file contains SSI types. + * + * @ingroup SSI + */ + +#ifndef __MXC_SSI_TYPES_H__ +#define __MXC_SSI_TYPES_H__ + +/*! + * This enumeration describes the FIFO number. + */ +typedef enum { + /*! + * FIFO 0 + */ + ssi_fifo_0 = 0, + /*! + * FIFO 1 + */ + ssi_fifo_1 = 1 +} fifo_nb; + +/*! + * This enumeration describes the clock idle state. + */ +typedef enum { + /*! + * Clock idle state is 1 + */ + clock_idle_state_1 = 0, + /*! + * Clock idle state is 0 + */ + clock_idle_state_0 = 1 +} idle_state; + +/*! + * This enumeration describes I2S mode. + */ +typedef enum { + /*! + * Normal mode + */ + i2s_normal = 0, + /*! + * Master mode + */ + i2s_master = 1, + /*! + * Slave mode + */ + i2s_slave = 2 +} mode_i2s; + +/*! + * This enumeration describes index for both SSI1 and SSI2 modules. + */ +typedef enum { + /*! + * SSI1 index + */ + SSI1 = 0, + /*! + * SSI2 index not present on MXC 91221 and MXC91311 + */ + SSI2 = 1 +} ssi_mod; + +/*! + * This enumeration describes the status/enable bits for interrupt source of the SSI module. + */ +typedef enum { + /*! + * SSI Transmit FIFO 0 empty bit + */ + ssi_tx_fifo_0_empty = 0x00000001, + /*! + * SSI Transmit FIFO 1 empty bit + */ + ssi_tx_fifo_1_empty = 0x00000002, + /*! + * SSI Receive FIFO 0 full bit + */ + ssi_rx_fifo_0_full = 0x00000004, + /*! + * SSI Receive FIFO 1 full bit + */ + ssi_rx_fifo_1_full = 0x00000008, + /*! + * SSI Receive Last Time Slot bit + */ + ssi_rls = 0x00000010, + /*! + * SSI Transmit Last Time Slot bit + */ + ssi_tls = 0x00000020, + /*! + * SSI Receive Frame Sync bit + */ + ssi_rfs = 0x00000040, + /*! + * SSI Transmit Frame Sync bit + */ + ssi_tfs = 0x00000080, + /*! + * SSI Transmitter underrun 0 bit + */ + ssi_transmitter_underrun_0 = 0x00000100, + /*! + * SSI Transmitter underrun 1 bit + */ + ssi_transmitter_underrun_1 = 0x00000200, + /*! + * SSI Receiver overrun 0 bit + */ + ssi_receiver_overrun_0 = 0x00000400, + /*! + * SSI Receiver overrun 1 bit + */ + ssi_receiver_overrun_1 = 0x00000800, + /*! + * SSI Transmit Data register empty 0 bit + */ + ssi_tx_data_reg_empty_0 = 0x00001000, + /*! + * SSI Transmit Data register empty 1 bit + */ + ssi_tx_data_reg_empty_1 = 0x00002000, + + /*! + * SSI Receive Data Ready 0 bit + */ + ssi_rx_data_ready_0 = 0x00004000, + /*! + * SSI Receive Data Ready 1 bit + */ + ssi_rx_data_ready_1 = 0x00008000, + /*! + * SSI Receive tag updated bit + */ + ssi_rx_tag_updated = 0x00010000, + /*! + * SSI Command data register updated bit + */ + ssi_cmd_data_reg_updated = 0x00020000, + /*! + * SSI Command address register updated bit + */ + ssi_cmd_address_reg_updated = 0x00040000, + /*! + * SSI Transmit interrupt enable bit + */ + ssi_tx_interrupt_enable = 0x00080000, + /*! + * SSI Transmit DMA enable bit + */ + ssi_tx_dma_interrupt_enable = 0x00100000, + /*! + * SSI Receive interrupt enable bit + */ + ssi_rx_interrupt_enable = 0x00200000, + /*! + * SSI Receive DMA enable bit + */ + ssi_rx_dma_interrupt_enable = 0x00400000, + /*! + * SSI Tx frame complete enable bit on MXC91221 & MXC91311 only + */ + ssi_tx_frame_complete = 0x00800000, + /*! + * SSI Rx frame complete on MXC91221 & MXC91311 only + */ + ssi_rx_frame_complete = 0x001000000 +} ssi_status_enable_mask; + +/*! + * This enumeration describes the clock edge to clock in or clock out data. + */ +typedef enum { + /*! + * Clock on rising edge + */ + ssi_clock_on_rising_edge = 0, + /*! + * Clock on falling edge + */ + ssi_clock_on_falling_edge = 1 +} ssi_tx_rx_clock_polarity; + +/*! + * This enumeration describes the clock direction. + */ +typedef enum { + /*! + * Clock is external + */ + ssi_tx_rx_externally = 0, + /*! + * Clock is generated internally + */ + ssi_tx_rx_internally = 1 +} ssi_tx_rx_direction; + +/*! + * This enumeration describes the early frame sync behavior. + */ +typedef enum { + /*! + * Frame Sync starts on the first data bit + */ + ssi_frame_sync_first_bit = 0, + /*! + * Frame Sync starts one bit before the first data bit + */ + ssi_frame_sync_one_bit_before = 1 +} ssi_tx_rx_early_frame_sync; + +/*! + * This enumeration describes the Frame Sync active value. + */ +typedef enum { + /*! + * Frame Sync is active when high + */ + ssi_frame_sync_active_high = 0, + /*! + * Frame Sync is active when low + */ + ssi_frame_sync_active_low = 1 +} ssi_tx_rx_frame_sync_active; + +/*! + * This enumeration describes the Frame Sync active length. + */ +typedef enum { + /*! + * Frame Sync is active when high + */ + ssi_frame_sync_one_word = 0, + /*! + * Frame Sync is active when low + */ + ssi_frame_sync_one_bit = 1 +} ssi_tx_rx_frame_sync_length; + +/*! + * This enumeration describes the Tx/Rx frame shift direction. + */ +typedef enum { + /*! + * MSB first + */ + ssi_msb_first = 0, + /*! + * LSB first + */ + ssi_lsb_first = 1 +} ssi_tx_rx_shift_direction; + +/*! + * This enumeration describes the wait state number. + */ +typedef enum { + /*! + * 0 wait state + */ + ssi_waitstates0 = 0x0, + /*! + * 1 wait state + */ + ssi_waitstates1 = 0x1, + /*! + * 2 wait states + */ + ssi_waitstates2 = 0x2, + /*! + * 3 wait states + */ + ssi_waitstates3 = 0x3 +} ssi_wait_states; + +/*! + * This enumeration describes the word length. + */ +typedef enum { + /*! + * 2 bits long + */ + ssi_2_bits = 0x0, + /*! + * 4 bits long + */ + ssi_4_bits = 0x1, + /*! + * 6 bits long + */ + ssi_6_bits = 0x2, + /*! + * 8 bits long + */ + ssi_8_bits = 0x3, + /*! + * 10 bits long + */ + ssi_10_bits = 0x4, + /*! + * 12 bits long + */ + ssi_12_bits = 0x5, + /*! + * 14 bits long + */ + ssi_14_bits = 0x6, + /*! + * 16 bits long + */ + ssi_16_bits = 0x7, + /*! + * 18 bits long + */ + ssi_18_bits = 0x8, + /*! + * 20 bits long + */ + ssi_20_bits = 0x9, + /*! + * 22 bits long + */ + ssi_22_bits = 0xA, + /*! + * 24 bits long + */ + ssi_24_bits = 0xB, + /*! + * 26 bits long + */ + ssi_26_bits = 0xC, + /*! + * 28 bits long + */ + ssi_28_bits = 0xD, + /*! + * 30 bits long + */ + ssi_30_bits = 0xE, + /*! + * 32 bits long + */ + ssi_32_bits = 0xF +} ssi_word_length; + +#endif /* __MXC_SSI_TYPES_H__ */ diff --git a/drivers/mxc/vpu/Kconfig b/drivers/mxc/vpu/Kconfig new file mode 100644 index 000000000000..e41974591b86 --- /dev/null +++ b/drivers/mxc/vpu/Kconfig @@ -0,0 +1,30 @@ +# +# Codec configuration +# + +menu "MXC VPU(Video Processing Unit) support" + +config MXC_VPU + tristate "Support for MXC VPU(Video Processing Unit)" + depends on (ARCH_MX3 || ARCH_MX27 || ARCH_MXC92323 || ARCH_MX37 || ARCH_MX51) + default y + ---help--- + The VPU codec device provides codec function for H.264/MPEG4/H.263, + as well as MPEG2/VC-1/DivX on some platforms. + +config MXC_VPU_IRAM + tristate "Use IRAM as temporary buffer for VPU to enhance performace" + depends on (ARCH_MX37 || ARCH_MX51) + default y + ---help--- + The VPU can use internal RAM as temporary buffer to save external + memroy bandwith, thus to enhance video performance. + +config MXC_VPU_DEBUG + bool "MXC VPU debugging" + depends on MXC_VPU != n + help + This is an option for the developers; most people should + say N here. This enables MXC VPU driver debugging. + +endmenu diff --git a/drivers/mxc/vpu/Makefile b/drivers/mxc/vpu/Makefile new file mode 100644 index 000000000000..88e8f2c084a0 --- /dev/null +++ b/drivers/mxc/vpu/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the VPU drivers. +# + +obj-$(CONFIG_MXC_VPU) += vpu.o +vpu-objs := mxc_vpu.o mxc_vl2cc.o + +ifeq ($(CONFIG_MXC_VPU_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/mxc/vpu/mxc_vl2cc.c b/drivers/mxc/vpu/mxc_vl2cc.c new file mode 100644 index 000000000000..acc40dd01689 --- /dev/null +++ b/drivers/mxc/vpu/mxc_vl2cc.c @@ -0,0 +1,123 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_vl2cc.c + * + * @brief VL2CC initialization and flush operation implementation + * + * @ingroup VL2CC + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <mach/hardware.h> +#include <asm/io.h> + +#define VL2CC_CTRL_OFFSET (0x100) +#define VL2CC_AUXCTRL_OFFSET (0x104) +#define VL2CC_INVWAY_OFFSET (0x77C) +#define VL2CC_CLEANWAY_OFFSET (0x7BC) + +/*! VL2CC clock handle. */ +static struct clk *vl2cc_clk; +static u32 *vl2cc_base; + +/*! + * Initialization function of VL2CC. Remap the VL2CC base address. + * + * @return status 0 success. + */ +int vl2cc_init(u32 vl2cc_hw_base) +{ + vl2cc_base = ioremap(vl2cc_hw_base, SZ_8K - 1); + if (vl2cc_base == NULL) { + printk(KERN_INFO "vl2cc: Unable to ioremap\n"); + return -ENOMEM; + } + + vl2cc_clk = clk_get(NULL, "vl2cc_clk"); + if (IS_ERR(vl2cc_clk)) { + printk(KERN_INFO "vl2cc: Unable to get clock\n"); + iounmap(vl2cc_base); + return -EIO; + } + + printk(KERN_INFO "VL2CC initialized\n"); + return 0; +} + +/*! + * Enable VL2CC hardware + */ +void vl2cc_enable(void) +{ + volatile u32 reg; + + clk_enable(vl2cc_clk); + + /* Disable VL2CC */ + reg = __raw_readl(vl2cc_base + VL2CC_CTRL_OFFSET); + reg &= 0xFFFFFFFE; + __raw_writel(reg, vl2cc_base + VL2CC_CTRL_OFFSET); + + /* Set the latency for data RAM reads, data RAM writes, tag RAM and + * dirty RAM to 1 cycle - write 0x0 to AUX CTRL [11:0] and also + * configure the number of ways to 8 - write 8 to AUX CTRL [16:13] + */ + reg = __raw_readl(vl2cc_base + VL2CC_AUXCTRL_OFFSET); + reg &= 0xFFFE1000; /* Clear [16:13] too */ + reg |= (0x8 << 13); /* [16:13] = 8; */ + __raw_writel(reg, vl2cc_base + VL2CC_AUXCTRL_OFFSET); + + /* Invalidate the VL2CC ways - write 0xff to INV BY WAY and poll the + * register until its value is 0x0 + */ + __raw_writel(0xff, vl2cc_base + VL2CC_INVWAY_OFFSET); + while (__raw_readl(vl2cc_base + VL2CC_INVWAY_OFFSET) != 0x0) ; + + /* Enable VL2CC */ + reg = __raw_readl(vl2cc_base + VL2CC_CTRL_OFFSET); + reg |= 0x1; + __raw_writel(reg, vl2cc_base + VL2CC_CTRL_OFFSET); +} + +/*! + * Flush VL2CC + */ +void vl2cc_flush(void) +{ + __raw_writel(0xff, vl2cc_base + VL2CC_CLEANWAY_OFFSET); + while (__raw_readl(vl2cc_base + VL2CC_CLEANWAY_OFFSET) != 0x0) ; +} + +/*! + * Disable VL2CC + */ +void vl2cc_disable(void) +{ + __raw_writel(0, vl2cc_base + VL2CC_CTRL_OFFSET); + clk_disable(vl2cc_clk); +} + +/*! + * Cleanup VL2CC + */ +void vl2cc_cleanup(void) +{ + clk_put(vl2cc_clk); + iounmap(vl2cc_base); +} diff --git a/drivers/mxc/vpu/mxc_vpu.c b/drivers/mxc/vpu/mxc_vpu.c new file mode 100644 index 000000000000..fa2824e85f77 --- /dev/null +++ b/drivers/mxc/vpu/mxc_vpu.c @@ -0,0 +1,817 @@ +/* + * Copyright 2006-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_vpu.c + * + * @brief VPU system initialization and file operation implementation + * + * @ingroup VPU + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/autoconf.h> +#include <linux/ioport.h> +#include <linux/stat.h> +#include <linux/platform_device.h> +#include <linux/kdev_t.h> +#include <linux/dma-mapping.h> +#include <linux/wait.h> +#include <linux/list.h> +#include <linux/clk.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/sizes.h> +#include <asm/dma-mapping.h> +#include <mach/hardware.h> + +#include <mach/mxc_vpu.h> + +struct vpu_priv { + struct fasync_struct *async_queue; +}; + +/* To track the allocated memory buffer */ +typedef struct memalloc_record { + struct list_head list; + struct vpu_mem_desc mem; +} memalloc_record; + +struct iram_setting { + u32 start; + u32 end; +}; + +static DEFINE_SPINLOCK(vpu_lock); +static LIST_HEAD(head); + +static int vpu_major = 0; +static struct class *vpu_class; +static struct vpu_priv vpu_data; +static u8 open_count = 0; +static struct clk *vpu_clk; +static struct vpu_mem_desc bitwork_mem = { 0 }; +static struct vpu_mem_desc pic_para_mem = { 0 }; +static struct vpu_mem_desc user_data_mem = { 0 }; +static struct vpu_mem_desc share_mem = { 0 }; + +/* IRAM setting */ +static struct iram_setting iram; +/* store SRC base addr */ +static u32 src_base_addr; + +/* implement the blocking ioctl */ +static int codec_done = 0; +static wait_queue_head_t vpu_queue; + +static u32 workctrl_regsave[6]; +static u32 rd_ptr_regsave[4]; +static u32 wr_ptr_regsave[4]; +static u32 dis_flag_regsave[4]; + +#define READ_REG(x) __raw_readl(IO_ADDRESS(VPU_BASE_ADDR+(x))) +#define WRITE_REG(val, x) \ + __raw_writel((val), IO_ADDRESS(VPU_BASE_ADDR+(x))) + +#define SAVE_WORK_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(workctrl_regsave)/2; i++) \ + workctrl_regsave[i] = READ_REG(BIT_WORK_CTRL_BUF_REG(i));\ +} while (0) +#define RESTORE_WORK_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(workctrl_regsave)/2; i++) \ + WRITE_REG(workctrl_regsave[i], BIT_WORK_CTRL_BUF_REG(i));\ +} while (0) +#define SAVE_CTRL_REGS do { \ + int i; \ + for (i = ARRAY_SIZE(workctrl_regsave)/2; \ + i < ARRAY_SIZE(workctrl_regsave); i++) \ + workctrl_regsave[i] = READ_REG(BIT_WORK_CTRL_BUF_REG(i));\ +} while (0) +#define RESTORE_CTRL_REGS do { \ + int i; \ + for (i = ARRAY_SIZE(workctrl_regsave)/2; \ + i < ARRAY_SIZE(workctrl_regsave); i++) \ + WRITE_REG(workctrl_regsave[i], BIT_WORK_CTRL_BUF_REG(i));\ +} while (0) +#define SAVE_RDWR_PTR_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(rd_ptr_regsave); i++) \ + rd_ptr_regsave[i] = READ_REG(BIT_RD_PTR_REG(i)); \ + for (i = 0; i < ARRAY_SIZE(wr_ptr_regsave); i++) \ + wr_ptr_regsave[i] = READ_REG(BIT_WR_PTR_REG(i)); \ +} while (0) +#define RESTORE_RDWR_PTR_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(rd_ptr_regsave); i++) \ + WRITE_REG(rd_ptr_regsave[i], BIT_RD_PTR_REG(i)); \ + for (i = 0; i < ARRAY_SIZE(wr_ptr_regsave); i++) \ + WRITE_REG(wr_ptr_regsave[i], BIT_WR_PTR_REG(i)); \ +} while (0) +#define SAVE_DIS_FLAG_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(dis_flag_regsave); i++) \ + dis_flag_regsave[i] = READ_REG(BIT_FRM_DIS_FLG_REG(i)); \ +} while (0) +#define RESTORE_DIS_FLAG_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(dis_flag_regsave); i++) \ + WRITE_REG(dis_flag_regsave[i], BIT_FRM_DIS_FLG_REG(i)); \ +} while (0) + +/*! + * Private function to alloc dma buffer + * @return status 0 success. + */ +static int vpu_alloc_dma_buffer(struct vpu_mem_desc *mem) +{ + mem->cpu_addr = (unsigned long) + dma_alloc_coherent(NULL, PAGE_ALIGN(mem->size), + (dma_addr_t *) (&mem->phy_addr), + GFP_DMA | GFP_KERNEL); + pr_debug("[ALLOC] mem alloc cpu_addr = 0x%x\n", mem->cpu_addr); + if ((void *)(mem->cpu_addr) == NULL) { + printk(KERN_ERR "Physical memory allocation error!\n"); + return -1; + } + return 0; +} + +/*! + * Private function to free dma buffer + */ +static void vpu_free_dma_buffer(struct vpu_mem_desc *mem) +{ + if (mem->cpu_addr != 0) { + dma_free_coherent(0, PAGE_ALIGN(mem->size), + (void *)mem->cpu_addr, mem->phy_addr); + } +} + +/*! + * Private function to free buffers + * @return status 0 success. + */ +static int vpu_free_buffers(void) +{ + struct memalloc_record *rec, *n; + struct vpu_mem_desc mem; + + list_for_each_entry_safe(rec, n, &head, list) { + mem = rec->mem; + if (mem.cpu_addr != 0) { + vpu_free_dma_buffer(&mem); + pr_debug("[FREE] freed paddr=0x%08X\n", mem.phy_addr); + /* delete from list */ + list_del(&rec->list); + kfree(rec); + } + } + + return 0; +} + +/*! + * @brief vpu interrupt handler + */ +static irqreturn_t vpu_irq_handler(int irq, void *dev_id) +{ + struct vpu_priv *dev = dev_id; + + READ_REG(BIT_INT_STATUS); + WRITE_REG(0x1, BIT_INT_CLEAR); + + if (dev->async_queue) + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + + /* + * Clock is gated on when dec/enc started, gate it off when + * interrupt is received. + */ + clk_disable(vpu_clk); + + codec_done = 1; + wake_up_interruptible(&vpu_queue); + + return IRQ_HANDLED; +} + +/*! + * @brief open function for vpu file operation + * + * @return 0 on success or negative error code on error + */ +static int vpu_open(struct inode *inode, struct file *filp) +{ + spin_lock(&vpu_lock); + if ((open_count++ == 0) && cpu_is_mx32()) + vl2cc_enable(); + filp->private_data = (void *)(&vpu_data); + spin_unlock(&vpu_lock); + return 0; +} + +/*! + * @brief IO ctrl function for vpu file operation + * @param cmd IO ctrl command + * @return 0 on success or negative error code on error + */ +static int vpu_ioctl(struct inode *inode, struct file *filp, u_int cmd, + u_long arg) +{ + int ret = 0; + + switch (cmd) { + case VPU_IOC_PHYMEM_ALLOC: + { + struct memalloc_record *rec; + + rec = kzalloc(sizeof(*rec), GFP_KERNEL); + if (!rec) + return -ENOMEM; + + ret = copy_from_user(&(rec->mem), + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc)); + if (ret) { + kfree(rec); + return -EFAULT; + } + + pr_debug("[ALLOC] mem alloc size = 0x%x\n", + rec->mem.size); + + ret = vpu_alloc_dma_buffer(&(rec->mem)); + if (ret == -1) { + kfree(rec); + printk(KERN_ERR + "Physical memory allocation error!\n"); + break; + } + ret = copy_to_user((void __user *)arg, &(rec->mem), + sizeof(struct vpu_mem_desc)); + if (ret) { + kfree(rec); + ret = -EFAULT; + break; + } + + spin_lock(&vpu_lock); + list_add(&rec->list, &head); + spin_unlock(&vpu_lock); + + break; + } + case VPU_IOC_PHYMEM_FREE: + { + struct memalloc_record *rec, *n; + struct vpu_mem_desc vpu_mem; + + ret = copy_from_user(&vpu_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc)); + if (ret) + return -EACCES; + + pr_debug("[FREE] mem freed cpu_addr = 0x%x\n", + vpu_mem.cpu_addr); + if ((void *)vpu_mem.cpu_addr != NULL) { + vpu_free_dma_buffer(&vpu_mem); + } + + spin_lock(&vpu_lock); + list_for_each_entry_safe(rec, n, &head, list) { + if (rec->mem.cpu_addr == vpu_mem.cpu_addr) { + /* delete from list */ + list_del(&rec->list); + kfree(rec); + break; + } + } + spin_unlock(&vpu_lock); + + break; + } + case VPU_IOC_WAIT4INT: + { + u_long timeout = (u_long) arg; + if (!wait_event_interruptible_timeout + (vpu_queue, codec_done != 0, + msecs_to_jiffies(timeout))) { + printk(KERN_WARNING "VPU blocking: timeout.\n"); + ret = -ETIME; + } else if (signal_pending(current)) { + printk(KERN_WARNING + "VPU interrupt received.\n"); + ret = -ERESTARTSYS; + } + + codec_done = 0; + break; + } + case VPU_IOC_VL2CC_FLUSH: + if (cpu_is_mx32()) { + vl2cc_flush(); + } + break; + case VPU_IOC_IRAM_SETTING: + { + ret = copy_to_user((void __user *)arg, &iram, + sizeof(struct iram_setting)); + if (ret) + ret = -EFAULT; + + break; + } + case VPU_IOC_CLKGATE_SETTING: + { + u32 clkgate_en; + + if (get_user(clkgate_en, (u32 __user *) arg)) + return -EFAULT; + + if (clkgate_en) { + clk_enable(vpu_clk); + } else { + clk_disable(vpu_clk); + } + + break; + } + case VPU_IOC_GET_SHARE_MEM: + { + spin_lock(&vpu_lock); + if (share_mem.cpu_addr != 0) { + ret = copy_to_user((void __user *)arg, + &share_mem, + sizeof(struct vpu_mem_desc)); + spin_unlock(&vpu_lock); + break; + } else { + if (copy_from_user(&share_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc))) { + spin_unlock(&vpu_lock); + return -EFAULT; + } + if (vpu_alloc_dma_buffer(&share_mem) == -1) + ret = -EFAULT; + else { + if (copy_to_user((void __user *)arg, + &share_mem, + sizeof(struct + vpu_mem_desc))) + ret = -EFAULT; + } + } + spin_unlock(&vpu_lock); + break; + } + case VPU_IOC_GET_WORK_ADDR: + { + if (bitwork_mem.cpu_addr != 0) { + ret = + copy_to_user((void __user *)arg, + &bitwork_mem, + sizeof(struct vpu_mem_desc)); + break; + } else { + if (copy_from_user(&bitwork_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc))) + return -EFAULT; + + if (vpu_alloc_dma_buffer(&bitwork_mem) == -1) + ret = -EFAULT; + else if (copy_to_user((void __user *)arg, + &bitwork_mem, + sizeof(struct + vpu_mem_desc))) + ret = -EFAULT; + } + break; + } + case VPU_IOC_GET_PIC_PARA_ADDR: + { + if (pic_para_mem.cpu_addr != 0) { + ret = + copy_to_user((void __user *)arg, + &pic_para_mem, + sizeof(struct vpu_mem_desc)); + break; + } else { + if (copy_from_user(&pic_para_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc))) + return -EFAULT; + + if (vpu_alloc_dma_buffer(&pic_para_mem) == -1) + ret = -EFAULT; + else if (copy_to_user((void __user *)arg, + &pic_para_mem, + sizeof(struct + vpu_mem_desc))) + ret = -EFAULT; + } + break; + } + case VPU_IOC_GET_USER_DATA_ADDR: + { + if (user_data_mem.cpu_addr != 0) { + ret = + copy_to_user((void __user *)arg, + &user_data_mem, + sizeof(struct vpu_mem_desc)); + break; + } else { + if (copy_from_user(&user_data_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc))) + return -EFAULT; + + if (vpu_alloc_dma_buffer(&user_data_mem) == -1) + ret = -EFAULT; + else if (copy_to_user((void __user *)arg, + &user_data_mem, + sizeof(struct + vpu_mem_desc))) + ret = -EFAULT; + } + break; + } + case VPU_IOC_SYS_SW_RESET: + { + if (cpu_is_mx37() || cpu_is_mx51()) { + u32 reg; + + reg = __raw_readl(src_base_addr); + reg |= 0x02; /* SW_VPU_RST_BIT */ + __raw_writel(reg, src_base_addr); + while (__raw_readl(src_base_addr) & 0x02) + ; + } + break; + } + case VPU_IOC_REG_DUMP: + break; + case VPU_IOC_PHYMEM_DUMP: + break; + default: + { + printk(KERN_ERR "No such IOCTL, cmd is %d\n", cmd); + break; + } + } + return ret; +} + +/*! + * @brief Release function for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_release(struct inode *inode, struct file *filp) +{ + spin_lock(&vpu_lock); + if (open_count > 0 && !(--open_count)) { + vpu_free_buffers(); + + if (cpu_is_mx32()) + vl2cc_disable(); + + /* Free shared memory when vpu device is idle */ + vpu_free_dma_buffer(&share_mem); + share_mem.cpu_addr = 0; + } + spin_unlock(&vpu_lock); + + return 0; +} + +/*! + * @brief fasync function for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_fasync(int fd, struct file *filp, int mode) +{ + struct vpu_priv *dev = (struct vpu_priv *)filp->private_data; + return fasync_helper(fd, filp, mode, &dev->async_queue); +} + +/*! + * @brief memory map function of harware registers for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_map_hwregs(struct file *fp, struct vm_area_struct *vm) +{ + unsigned long pfn; + + vm->vm_flags |= VM_IO | VM_RESERVED; + vm->vm_page_prot = pgprot_noncached(vm->vm_page_prot); + pfn = VPU_BASE_ADDR >> PAGE_SHIFT; + pr_debug("size=0x%x, page no.=0x%x\n", + (int)(vm->vm_end - vm->vm_start), (int)pfn); + return remap_pfn_range(vm, vm->vm_start, pfn, vm->vm_end - vm->vm_start, + vm->vm_page_prot) ? -EAGAIN : 0; +} + +/*! + * @brief memory map function of memory for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_map_mem(struct file *fp, struct vm_area_struct *vm) +{ + int request_size; + request_size = vm->vm_end - vm->vm_start; + + pr_debug(" start=0x%x, pgoff=0x%x, size=0x%x\n", + (unsigned int)(vm->vm_start), (unsigned int)(vm->vm_pgoff), + request_size); + + vm->vm_flags |= VM_IO | VM_RESERVED; + vm->vm_page_prot = pgprot_noncached(vm->vm_page_prot); + + return remap_pfn_range(vm, vm->vm_start, vm->vm_pgoff, + request_size, vm->vm_page_prot) ? -EAGAIN : 0; + +} + +/*! + * @brief memory map interface for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_mmap(struct file *fp, struct vm_area_struct *vm) +{ + if (vm->vm_pgoff) + return vpu_map_mem(fp, vm); + else + return vpu_map_hwregs(fp, vm); +} + +struct file_operations vpu_fops = { + .owner = THIS_MODULE, + .open = vpu_open, + .ioctl = vpu_ioctl, + .release = vpu_release, + .fasync = vpu_fasync, + .mmap = vpu_mmap, +}; + +/*! + * This function is called by the driver framework to initialize the vpu device. + * @param dev The device structure for the vpu passed in by the framework. + * @return 0 on success or negative error code on error + */ +static int vpu_dev_probe(struct platform_device *pdev) +{ + int err = 0; + struct device *temp_class; + struct resource *res; + + if (cpu_is_mx32()) { + /* Obtain VL2CC base address */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + printk(KERN_ERR "vpu: unable to get VL2CC base\n"); + return -ENODEV; + } + + err = vl2cc_init(res->start); + if (err != 0) + return err; + } + + if (cpu_is_mx37() || cpu_is_mx51()) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + printk(KERN_ERR "vpu: unable to get VPU IRAM base\n"); + return -ENODEV; + } + iram.start = res->start; + iram.end = res->end; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + printk(KERN_ERR "vpu: unable to get src base addr\n"); + return -ENODEV; + } + src_base_addr = res->start; + } + + vpu_major = register_chrdev(vpu_major, "mxc_vpu", &vpu_fops); + if (vpu_major < 0) { + printk(KERN_ERR "vpu: unable to get a major for VPU\n"); + err = -EBUSY; + goto error; + } + + vpu_class = class_create(THIS_MODULE, "mxc_vpu"); + if (IS_ERR(vpu_class)) { + err = PTR_ERR(vpu_class); + goto err_out_chrdev; + } + + temp_class = device_create(vpu_class, NULL, MKDEV(vpu_major, 0), + NULL, "mxc_vpu"); + if (IS_ERR(temp_class)) { + err = PTR_ERR(temp_class); + goto err_out_class; + } + + vpu_clk = clk_get(&pdev->dev, "vpu_clk"); + if (IS_ERR(vpu_clk)) { + err = -ENOENT; + goto err_out_class; + } + + err = request_irq(MXC_INT_VPU, vpu_irq_handler, 0, "VPU_CODEC_IRQ", + (void *)(&vpu_data)); + if (err) + goto err_out_class; + + printk(KERN_INFO "VPU initialized\n"); + goto out; + + err_out_class: + device_destroy(vpu_class, MKDEV(vpu_major, 0)); + class_destroy(vpu_class); + err_out_chrdev: + unregister_chrdev(vpu_major, "mxc_vpu"); + error: + if (cpu_is_mx32()) { + vl2cc_cleanup(); + } + out: + return err; +} + +#ifdef CONFIG_PM +static int vpu_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (codec_done == 1) + return -EAGAIN; + + clk_enable(vpu_clk); + if (bitwork_mem.cpu_addr != 0) { + SAVE_WORK_REGS; + SAVE_CTRL_REGS; + SAVE_RDWR_PTR_REGS; + SAVE_DIS_FLAG_REGS; + + WRITE_REG(0x1, BIT_BUSY_FLAG); + WRITE_REG(VPU_SLEEP_REG_VALUE, BIT_RUN_COMMAND); + while (READ_REG(BIT_BUSY_FLAG)) ; + } + + clk_disable(vpu_clk); + + if (cpu_is_mx37() || cpu_is_mx51()) + mxc_pg_enable(pdev); + + return 0; +} + +static int vpu_resume(struct platform_device *pdev) +{ + if (cpu_is_mx37() || cpu_is_mx51()) + mxc_pg_disable(pdev); + + clk_enable(vpu_clk); + + if (bitwork_mem.cpu_addr != 0) { + u32 *p = (u32 *) bitwork_mem.cpu_addr; + u32 data; + u16 data_hi; + u16 data_lo; + int i; + + RESTORE_WORK_REGS; + + WRITE_REG(0x0, BIT_RESET_CTRL); + WRITE_REG(0x0, BIT_CODE_RUN); + + /* + * Re-load boot code, from the codebuffer in external RAM. + * Thankfully, we only need 4096 bytes, same for all platforms. + */ + if (cpu_is_mx51()) { + for (i = 0; i < 2048; i += 4) { + data = p[(i / 2) + 1]; + data_hi = (data >> 16) & 0xFFFF; + data_lo = data & 0xFFFF; + WRITE_REG((i << 16) | data_hi, BIT_CODE_DOWN); + WRITE_REG(((i + 1) << 16) | data_lo, + BIT_CODE_DOWN); + + data = p[i / 2]; + data_hi = (data >> 16) & 0xFFFF; + data_lo = data & 0xFFFF; + WRITE_REG(((i + 2) << 16) | data_hi, + BIT_CODE_DOWN); + WRITE_REG(((i + 3) << 16) | data_lo, + BIT_CODE_DOWN); + } + } else { + for (i = 0; i < 2048; i += 2) { + if (cpu_is_mx37()) + data = swab32(p[i / 2]); + else + data = p[i / 2]; + data_hi = (data >> 16) & 0xFFFF; + data_lo = data & 0xFFFF; + + WRITE_REG((i << 16) | data_hi, BIT_CODE_DOWN); + WRITE_REG(((i + 1) << 16) | data_lo, + BIT_CODE_DOWN); + } + } + + RESTORE_CTRL_REGS; + + WRITE_REG(BITVAL_PIC_RUN, BIT_INT_ENABLE); + + WRITE_REG(0x1, BIT_BUSY_FLAG); + WRITE_REG(0x1, BIT_CODE_RUN); + while (READ_REG(BIT_BUSY_FLAG)) ; + + RESTORE_RDWR_PTR_REGS; + RESTORE_DIS_FLAG_REGS; + + WRITE_REG(0x1, BIT_BUSY_FLAG); + WRITE_REG(VPU_WAKE_REG_VALUE, BIT_RUN_COMMAND); + while (READ_REG(BIT_BUSY_FLAG)) ; + } + + clk_disable(vpu_clk); + + return 0; +} +#else +#define vpu_suspend NULL +#define vpu_resume NULL +#endif /* !CONFIG_PM */ + +/*! Driver definition + * + */ +static struct platform_driver mxcvpu_driver = { + .driver = { + .name = "mxc_vpu", + }, + .probe = vpu_dev_probe, + .suspend = vpu_suspend, + .resume = vpu_resume, +}; + +static int __init vpu_init(void) +{ + int ret = platform_driver_register(&mxcvpu_driver); + + init_waitqueue_head(&vpu_queue); + + return ret; +} + +static void __exit vpu_exit(void) +{ + free_irq(MXC_INT_VPU, (void *)(&vpu_data)); + if (vpu_major > 0) { + device_destroy(vpu_class, MKDEV(vpu_major, 0)); + class_destroy(vpu_class); + unregister_chrdev(vpu_major, "mxc_vpu"); + vpu_major = 0; + } + + if (cpu_is_mx32()) { + vl2cc_cleanup(); + } + + vpu_free_dma_buffer(&bitwork_mem); + vpu_free_dma_buffer(&pic_para_mem); + vpu_free_dma_buffer(&user_data_mem); + + clk_put(vpu_clk); + + platform_driver_unregister(&mxcvpu_driver); + return; +} + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Linux VPU driver for Freescale i.MX/MXC"); +MODULE_LICENSE("GPL"); + +module_init(vpu_init); +module_exit(vpu_exit); diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 5ce7cbabd7a7..f5544c33b915 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -1439,7 +1439,7 @@ config FORCEDETH_NAPI config CS89x0 tristate "CS89x0 support" depends on NET_ETHERNET && (ISA || EISA || MACH_IXDP2351 \ - || ARCH_IXDP2X01 || ARCH_PNX010X || MACH_MX31ADS) + || ARCH_IXDP2X01 || ARCH_PNX010X || ARCH_MXC) ---help--- Support for CS89x0 chipset based Ethernet cards. If you have a network (Ethernet) card of this type, say Y and read the @@ -1453,7 +1453,7 @@ config CS89x0 config CS89x0_NONISA_IRQ def_bool y depends on CS89x0 != n - depends on MACH_IXDP2351 || ARCH_IXDP2X01 || ARCH_PNX010X || MACH_MX31ADS + depends on MACH_IXDP2351 || ARCH_IXDP2X01 || ARCH_PNX010X || ARCH_MXC config TC35815 tristate "TOSHIBA TC35815 Ethernet support" @@ -1875,7 +1875,7 @@ config 68360_ENET config FEC bool "FEC ethernet controller (of ColdFire and some i.MX CPUs)" - depends on M523x || M527x || M5272 || M528x || M520x || M532x || MACH_MX27 || ARCH_MX35 + depends on M523x || M527x || M5272 || M528x || M520x || M532x || ARCH_MX25 || MACH_MX27 || ARCH_MX35 || ARCH_MX51 help Say Y here if you want to use the built-in 10/100 Fast ethernet controller on some Motorola ColdFire and Freescale i.MX processors. diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index 33821a81cbf8..b7b101e3a884 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -84,4 +84,13 @@ config CAN_DEBUG_DEVICES a problem with CAN support and want to see more of what is going on. +config CAN_FLEXCAN + tristate "Freescale FlexCAN" + depends on CAN && (ARCH_MX25 || ARCH_MX35) + default m + ---help--- + This select the support of Freescale CAN(FlexCAN). + This driver can also be built as a module. + If unsure, say N. + endmenu diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile index 523a941b358b..22ea529fb46e 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -3,6 +3,7 @@ # obj-$(CONFIG_CAN_VCAN) += vcan.o +obj-$(CONFIG_CAN_FLEXCAN) += flexcan/ obj-$(CONFIG_CAN_DEV) += can-dev.o can-dev-y := dev.o diff --git a/drivers/net/can/flexcan/Makefile b/drivers/net/can/flexcan/Makefile new file mode 100644 index 000000000000..b2dbb4fb2793 --- /dev/null +++ b/drivers/net/can/flexcan/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_CAN_FLEXCAN) += flexcan.o + +flexcan-y := dev.o drv.o mbm.o diff --git a/drivers/net/can/flexcan/dev.c b/drivers/net/can/flexcan/dev.c new file mode 100644 index 000000000000..f8386e5effa9 --- /dev/null +++ b/drivers/net/can/flexcan/dev.c @@ -0,0 +1,619 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dev.c + * + * @brief Driver for Freescale CAN Controller FlexCAN. + * + * @ingroup can + */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/unistd.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/spinlock.h> +#include <linux/device.h> + +#include <linux/module.h> +#include <mach/hardware.h> +#include "flexcan.h" + +enum { + FLEXCAN_ATTR_STATE = 0, + FLEXCAN_ATTR_BITRATE, + FLEXCAN_ATTR_BR_PRESDIV, + FLEXCAN_ATTR_BR_RJW, + FLEXCAN_ATTR_BR_PROPSEG, + FLEXCAN_ATTR_BR_PSEG1, + FLEXCAN_ATTR_BR_PSEG2, + FLEXCAN_ATTR_BR_CLKSRC, + FLEXCAN_ATTR_MAXMB, + FLEXCAN_ATTR_XMIT_MAXMB, + FLEXCAN_ATTR_FIFO, + FLEXCAN_ATTR_WAKEUP, + FLEXCAN_ATTR_SRX_DIS, + FLEXCAN_ATTR_WAK_SRC, + FLEXCAN_ATTR_BCC, + FLEXCAN_ATTR_LOCAL_PRIORITY, + FLEXCAN_ATTR_ABORT, + FLEXCAN_ATTR_LOOPBACK, + FLEXCAN_ATTR_SMP, + FLEXCAN_ATTR_BOFF_REC, + FLEXCAN_ATTR_TSYN, + FLEXCAN_ATTR_LISTEN, + FLEXCAN_ATTR_EXTEND_MSG, + FLEXCAN_ATTR_STANDARD_MSG, +#ifdef CONFIG_CAN_DEBUG_DEVICES + FLEXCAN_ATTR_DUMP_REG, + FLEXCAN_ATTR_DUMP_XMIT_MB, + FLEXCAN_ATTR_DUMP_RX_MB, +#endif + FLEXCAN_ATTR_MAX +}; + +static ssize_t flexcan_show_attr(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t flexcan_set_attr(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count); + +static struct device_attribute flexcan_dev_attr[FLEXCAN_ATTR_MAX] = { + [FLEXCAN_ATTR_STATE] = __ATTR(state, 0444, flexcan_show_attr, NULL), + [FLEXCAN_ATTR_BITRATE] = + __ATTR(bitrate, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BR_PRESDIV] = + __ATTR(br_presdiv, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BR_RJW] = + __ATTR(br_rjw, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BR_PROPSEG] = + __ATTR(br_propseg, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BR_PSEG1] = + __ATTR(br_pseg1, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BR_PSEG2] = + __ATTR(br_pseg2, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BR_CLKSRC] = + __ATTR(br_clksrc, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_MAXMB] = + __ATTR(maxmb, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_XMIT_MAXMB] = + __ATTR(xmit_maxmb, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_FIFO] = + __ATTR(fifo, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_WAKEUP] = + __ATTR(wakeup, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_SRX_DIS] = + __ATTR(srx_dis, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_WAK_SRC] = + __ATTR(wak_src, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BCC] = + __ATTR(bcc, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_LOCAL_PRIORITY] = + __ATTR(local_priority, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_ABORT] = + __ATTR(abort, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_LOOPBACK] = + __ATTR(loopback, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_SMP] = + __ATTR(smp, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BOFF_REC] = + __ATTR(boff_rec, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_TSYN] = + __ATTR(tsyn, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_LISTEN] = + __ATTR(listen, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_EXTEND_MSG] = + __ATTR(ext_msg, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_STANDARD_MSG] = + __ATTR(std_msg, 0644, flexcan_show_attr, flexcan_set_attr), +#ifdef CONFIG_CAN_DEBUG_DEVICES + [FLEXCAN_ATTR_DUMP_REG] = + __ATTR(dump_reg, 0444, flexcan_show_attr, NULL), + [FLEXCAN_ATTR_DUMP_XMIT_MB] = + __ATTR(dump_xmit_mb, 0444, flexcan_show_attr, NULL), + [FLEXCAN_ATTR_DUMP_RX_MB] = + __ATTR(dump_rx_mb, 0444, flexcan_show_attr, NULL), +#endif +}; + +static void flexcan_set_bitrate(struct flexcan_device *flexcan, int bitrate) +{ + /* TODO:: implement in future + * based on the bitrate to get the timing of + * presdiv, pseg1, pseg2, propseg + */ +} + +static void flexcan_update_bitrate(struct flexcan_device *flexcan) +{ + int rate, div; + + if (flexcan->br_clksrc) + rate = clk_get_rate(flexcan->clk); + else { + struct clk *clk; + clk = clk_get(NULL, "ckih"); + if (!clk) + return; + rate = clk_get_rate(clk); + clk_put(clk); + } + if (!rate) + return; + + div = (flexcan->br_presdiv + 1); + div *= + (flexcan->br_propseg + flexcan->br_pseg1 + flexcan->br_pseg2 + 4); + flexcan->bitrate = (rate + div - 1) / div; +} + +#ifdef CONFIG_CAN_DEBUG_DEVICES +static int flexcan_dump_reg(struct flexcan_device *flexcan, char *buf) +{ + int ret = 0; + unsigned int reg; + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + ret += sprintf(buf + ret, "MCR::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_CTRL); + ret += sprintf(buf + ret, "CTRL::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_RXGMASK); + ret += sprintf(buf + ret, "RXGMASK::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_RX14MASK); + ret += sprintf(buf + ret, "RX14MASK::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_RX15MASK); + ret += sprintf(buf + ret, "RX15MASK::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_ECR); + ret += sprintf(buf + ret, "ECR::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_ESR); + ret += sprintf(buf + ret, "ESR::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_IMASK2); + ret += sprintf(buf + ret, "IMASK2::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_IMASK1); + ret += sprintf(buf + ret, "IMASK1::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_IFLAG2); + ret += sprintf(buf + ret, "IFLAG2::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_IFLAG1); + ret += sprintf(buf + ret, "IFLAG1::0x%x\n", reg); + return ret; +} + +static int flexcan_dump_xmit_mb(struct flexcan_device *flexcan, char *buf) +{ + int ret = 0, i; + i = flexcan->xmit_maxmb + 1; + for (; i <= flexcan->maxmb; i++) + ret += + sprintf(buf + ret, + "mb[%d]::CS:0x%x ID:0x%x DATA[1~2]:0x%02x,0x%02x\n", + i, flexcan->hwmb[i].mb_cs.data, + flexcan->hwmb[i].mb_id, flexcan->hwmb[i].mb_data[1], + flexcan->hwmb[i].mb_data[2]); + return ret; +} + +static int flexcan_dump_rx_mb(struct flexcan_device *flexcan, char *buf) +{ + int ret = 0, i; + for (i = 0; i <= flexcan->xmit_maxmb; i++) + ret += + sprintf(buf + ret, + "mb[%d]::CS:0x%x ID:0x%x DATA[1~2]:0x%02x,0x%02x\n", + i, flexcan->hwmb[i].mb_cs.data, + flexcan->hwmb[i].mb_id, flexcan->hwmb[i].mb_data[1], + flexcan->hwmb[i].mb_data[2]); + return ret; +} +#endif + +static ssize_t flexcan_show_state(struct net_device *net, char *buf) +{ + int ret, esr; + struct flexcan_device *flexcan = netdev_priv(net); + ret = sprintf(buf, "%s::", netif_running(net) ? "Start" : "Stop"); + if (netif_carrier_ok(net)) { + esr = __raw_readl(flexcan->io_base + CAN_HW_REG_ESR); + switch ((esr & __ESR_FLT_CONF_MASK) >> __ESR_FLT_CONF_OFF) { + case 0: + ret += sprintf(buf + ret, "normal\n"); + break; + case 1: + ret += sprintf(buf + ret, "error passive\n"); + break; + default: + ret += sprintf(buf + ret, "bus off\n"); + } + } else + ret += sprintf(buf + ret, "bus off\n"); + return ret; +} + +static ssize_t flexcan_show_attr(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int attr_id; + struct net_device *net; + struct flexcan_device *flexcan; + + net = dev_get_drvdata(dev); + BUG_ON(!net); + flexcan = netdev_priv(net); + BUG_ON(!flexcan); + + attr_id = attr - flexcan_dev_attr; + switch (attr_id) { + case FLEXCAN_ATTR_STATE: + return flexcan_show_state(net, buf); + case FLEXCAN_ATTR_BITRATE: + return sprintf(buf, "%d\n", flexcan->bitrate); + case FLEXCAN_ATTR_BR_PRESDIV: + return sprintf(buf, "%d\n", flexcan->br_presdiv + 1); + case FLEXCAN_ATTR_BR_RJW: + return sprintf(buf, "%d\n", flexcan->br_rjw); + case FLEXCAN_ATTR_BR_PROPSEG: + return sprintf(buf, "%d\n", flexcan->br_propseg + 1); + case FLEXCAN_ATTR_BR_PSEG1: + return sprintf(buf, "%d\n", flexcan->br_pseg1 + 1); + case FLEXCAN_ATTR_BR_PSEG2: + return sprintf(buf, "%d\n", flexcan->br_pseg2 + 1); + case FLEXCAN_ATTR_BR_CLKSRC: + return sprintf(buf, "%s\n", flexcan->br_clksrc ? "bus" : "osc"); + case FLEXCAN_ATTR_MAXMB: + return sprintf(buf, "%d\n", flexcan->maxmb + 1); + case FLEXCAN_ATTR_XMIT_MAXMB: + return sprintf(buf, "%d\n", flexcan->xmit_maxmb + 1); + case FLEXCAN_ATTR_FIFO: + return sprintf(buf, "%d\n", flexcan->fifo); + case FLEXCAN_ATTR_WAKEUP: + return sprintf(buf, "%d\n", flexcan->wakeup); + case FLEXCAN_ATTR_SRX_DIS: + return sprintf(buf, "%d\n", flexcan->srx_dis); + case FLEXCAN_ATTR_WAK_SRC: + return sprintf(buf, "%d\n", flexcan->wak_src); + case FLEXCAN_ATTR_BCC: + return sprintf(buf, "%d\n", flexcan->bcc); + case FLEXCAN_ATTR_LOCAL_PRIORITY: + return sprintf(buf, "%d\n", flexcan->lprio); + case FLEXCAN_ATTR_ABORT: + return sprintf(buf, "%d\n", flexcan->abort); + case FLEXCAN_ATTR_LOOPBACK: + return sprintf(buf, "%d\n", flexcan->loopback); + case FLEXCAN_ATTR_SMP: + return sprintf(buf, "%d\n", flexcan->smp); + case FLEXCAN_ATTR_BOFF_REC: + return sprintf(buf, "%d\n", flexcan->boff_rec); + case FLEXCAN_ATTR_TSYN: + return sprintf(buf, "%d\n", flexcan->tsyn); + case FLEXCAN_ATTR_LISTEN: + return sprintf(buf, "%d\n", flexcan->listen); + case FLEXCAN_ATTR_EXTEND_MSG: + return sprintf(buf, "%d\n", flexcan->ext_msg); + case FLEXCAN_ATTR_STANDARD_MSG: + return sprintf(buf, "%d\n", flexcan->std_msg); +#ifdef CONFIG_CAN_DEBUG_DEVICES + case FLEXCAN_ATTR_DUMP_REG: + return flexcan_dump_reg(flexcan, buf); + case FLEXCAN_ATTR_DUMP_XMIT_MB: + return flexcan_dump_xmit_mb(flexcan, buf); + case FLEXCAN_ATTR_DUMP_RX_MB: + return flexcan_dump_rx_mb(flexcan, buf); +#endif + default: + return sprintf(buf, "%s:%p->%p\n", __func__, flexcan_dev_attr, + attr); + } +} + +static ssize_t flexcan_set_attr(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int attr_id, tmp; + struct net_device *net; + struct flexcan_device *flexcan; + + net = dev_get_drvdata(dev); + BUG_ON(!net); + flexcan = netdev_priv(net); + BUG_ON(!flexcan); + + attr_id = attr - flexcan_dev_attr; + + if (mutex_lock_interruptible(&flexcan->mutex)) + return count; + + if (netif_running(net)) + goto set_finish; + + if (attr_id == FLEXCAN_ATTR_BR_CLKSRC) { + if (!strcasecmp(buf, "bus")) + flexcan->br_clksrc = 1; + else if (!strcasecmp(buf, "osc")) + flexcan->br_clksrc = 0; + goto set_finish; + } + + tmp = simple_strtoul(buf, NULL, 0); + switch (attr_id) { + case FLEXCAN_ATTR_BITRATE: + flexcan_set_bitrate(flexcan, tmp); + break; + case FLEXCAN_ATTR_BR_PRESDIV: + if ((tmp > 0) && (tmp <= FLEXCAN_MAX_PRESDIV)) { + flexcan->br_presdiv = tmp - 1; + flexcan_update_bitrate(flexcan); + } + break; + case FLEXCAN_ATTR_BR_RJW: + if ((tmp > 0) && (tmp <= FLEXCAN_MAX_RJW)) + flexcan->br_rjw = tmp - 1; + break; + case FLEXCAN_ATTR_BR_PROPSEG: + if ((tmp > 0) && (tmp <= FLEXCAN_MAX_PROPSEG)) { + flexcan->br_propseg = tmp - 1; + flexcan_update_bitrate(flexcan); + } + break; + case FLEXCAN_ATTR_BR_PSEG1: + if ((tmp > 0) && (tmp <= FLEXCAN_MAX_PSEG1)) { + flexcan->br_pseg1 = tmp - 1; + flexcan_update_bitrate(flexcan); + } + break; + case FLEXCAN_ATTR_BR_PSEG2: + if ((tmp > 0) && (tmp <= FLEXCAN_MAX_PSEG2)) { + flexcan->br_pseg2 = tmp - 1; + flexcan_update_bitrate(flexcan); + } + break; + case FLEXCAN_ATTR_MAXMB: + if ((tmp > 0) && (tmp <= FLEXCAN_MAX_MB)) { + if (flexcan->maxmb != (tmp - 1)) { + flexcan->maxmb = tmp - 1; + if (flexcan->xmit_maxmb < flexcan->maxmb) + flexcan->xmit_maxmb = flexcan->maxmb; + } + } + break; + case FLEXCAN_ATTR_XMIT_MAXMB: + if ((tmp > 0) && (tmp <= (flexcan->maxmb + 1))) { + if (flexcan->xmit_maxmb != (tmp - 1)) + flexcan->xmit_maxmb = tmp - 1; + } + break; + case FLEXCAN_ATTR_FIFO: + flexcan->fifo = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_WAKEUP: + flexcan->wakeup = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_SRX_DIS: + flexcan->srx_dis = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_WAK_SRC: + flexcan->wak_src = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_BCC: + flexcan->bcc = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_LOCAL_PRIORITY: + flexcan->lprio = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_ABORT: + flexcan->abort = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_LOOPBACK: + flexcan->loopback = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_SMP: + flexcan->smp = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_BOFF_REC: + flexcan->boff_rec = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_TSYN: + flexcan->tsyn = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_LISTEN: + flexcan->listen = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_EXTEND_MSG: + flexcan->ext_msg = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_STANDARD_MSG: + flexcan->std_msg = tmp ? 1 : 0; + break; + } + set_finish: + mutex_unlock(&flexcan->mutex); + return count; +} + +static void flexcan_device_default(struct flexcan_device *dev) +{ + dev->br_clksrc = 1; + dev->br_rjw = 2; + dev->br_presdiv = 6; + dev->br_propseg = 4; + dev->br_pseg1 = 4; + dev->br_pseg2 = 7; + + dev->bcc = 1; + dev->srx_dis = 1; + dev->smp = 1; + dev->boff_rec = 1; + + dev->maxmb = FLEXCAN_MAX_MB - 1; + dev->xmit_maxmb = (FLEXCAN_MAX_MB >> 1) - 1; + dev->xmit_mb = dev->maxmb - dev->xmit_maxmb; + + dev->ext_msg = 1; + dev->std_msg = 1; +} + +static int flexcan_device_attach(struct flexcan_device *flexcan) +{ + int ret; + struct resource *res; + struct platform_device *pdev = flexcan->dev; + struct flexcan_platform_data *plat_data = (pdev->dev).platform_data; + + res = platform_get_resource(flexcan->dev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + flexcan->io_base = ioremap(res->start, res->end - res->start + 1); + if (!flexcan->io_base) + return -ENOMEM; + + flexcan->irq = platform_get_irq(flexcan->dev, 0); + if (!flexcan->irq) { + ret = -ENODEV; + goto no_irq_err; + } + + ret = -EINVAL; + if (plat_data) { + if (plat_data->core_reg) { + flexcan->core_reg = regulator_get(&pdev->dev, + plat_data->core_reg); + if (!flexcan->core_reg) + goto plat_err; + } + + if (plat_data->io_reg) { + flexcan->io_reg = regulator_get(&pdev->dev, + plat_data->io_reg); + if (!flexcan->io_reg) + goto plat_err; + } + } + flexcan->clk = clk_get(&(flexcan->dev)->dev, "can_clk"); + flexcan->hwmb = (struct can_hw_mb *)(flexcan->io_base + CAN_MB_BASE); + flexcan->rx_mask = (unsigned int *)(flexcan->io_base + CAN_RXMASK_BASE); + + return 0; + plat_err: + if (flexcan->core_reg) { + regulator_put(flexcan->core_reg); + flexcan->core_reg = NULL; + } + no_irq_err: + if (flexcan->io_base) + iounmap(flexcan->io_base); + return ret; +} + +static void flexcan_device_detach(struct flexcan_device *flexcan) +{ + if (flexcan->clk) { + clk_put(flexcan->clk); + flexcan->clk = NULL; + } + + if (flexcan->io_reg) { + regulator_put(flexcan->io_reg); + flexcan->io_reg = NULL; + } + + if (flexcan->core_reg) { + regulator_put(flexcan->core_reg); + flexcan->core_reg = NULL; + } + + if (flexcan->io_base) + iounmap(flexcan->io_base); +} + +/*! + * @brief The function allocates can device. + * + * @param pdev the pointer of platform device. + * @param setup the initial function pointer of network device. + * + * @return none + */ +struct net_device *flexcan_device_alloc(struct platform_device *pdev, + void (*setup) (struct net_device *dev)) +{ + struct flexcan_device *flexcan; + struct net_device *net; + int i, num; + + net = alloc_netdev(sizeof(*flexcan), "can%d", setup); + if (net == NULL) { + printk(KERN_ERR "Allocate netdevice for FlexCAN fail!\n"); + return NULL; + } + flexcan = netdev_priv(net); + memset(flexcan, 0, sizeof(*flexcan)); + + mutex_init(&flexcan->mutex); + init_timer(&flexcan->timer); + + flexcan->dev = pdev; + if (flexcan_device_attach(flexcan)) { + printk(KERN_ERR "Attach FlexCAN fail!\n"); + free_netdev(net); + return NULL; + } + flexcan_device_default(flexcan); + flexcan_update_bitrate(flexcan); + + num = ARRAY_SIZE(flexcan_dev_attr); + + for (i = 0; i < num; i++) { + if (device_create_file(&pdev->dev, flexcan_dev_attr + i)) { + printk(KERN_ERR "Create attribute file fail!\n"); + break; + } + } + + if (i != num) { + for (; i >= 0; i--) + device_remove_file(&pdev->dev, flexcan_dev_attr + i); + free_netdev(net); + return NULL; + } + dev_set_drvdata(&pdev->dev, net); + return net; +} + +/*! + * @brief The function frees can device. + * + * @param pdev the pointer of platform device. + * + * @return none + */ +void flexcan_device_free(struct platform_device *pdev) +{ + struct net_device *net; + struct flexcan_device *flexcan; + int i, num; + net = (struct net_device *)dev_get_drvdata(&pdev->dev); + + unregister_netdev(net); + flexcan = netdev_priv(net); + del_timer(&flexcan->timer); + + num = ARRAY_SIZE(flexcan_dev_attr); + + for (i = 0; i < num; i++) + device_remove_file(&pdev->dev, flexcan_dev_attr + i); + + flexcan_device_detach(netdev_priv(net)); + free_netdev(net); +} diff --git a/drivers/net/can/flexcan/drv.c b/drivers/net/can/flexcan/drv.c new file mode 100644 index 000000000000..342b52a129c2 --- /dev/null +++ b/drivers/net/can/flexcan/drv.c @@ -0,0 +1,628 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drv.c + * + * @brief Driver for Freescale CAN Controller FlexCAN. + * + * @ingroup can + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/clk.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <mach/hardware.h> +#include "flexcan.h" + +static void flexcan_hw_start(struct flexcan_device *flexcan) +{ + unsigned int reg; + if ((flexcan->maxmb + 1) > 32) { + __raw_writel(0xFFFFFFFF, flexcan->io_base + CAN_HW_REG_IMASK1); + reg = (1 << (flexcan->maxmb - 31)) - 1; + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_IMASK2); + } else { + reg = (1 << (flexcan->maxmb + 1)) - 1; + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_IMASK1); + __raw_writel(0, flexcan->io_base + CAN_HW_REG_IMASK2); + } + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR) & (~__MCR_HALT); + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_MCR); +} + +static void flexcan_hw_stop(struct flexcan_device *flexcan) +{ + unsigned int reg; + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + __raw_writel(reg | __MCR_HALT, flexcan->io_base + CAN_HW_REG_MCR); +} + +static int flexcan_hw_reset(struct flexcan_device *flexcan) +{ + unsigned int reg; + int timeout = 100000; + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + __raw_writel(reg | __MCR_MDIS, flexcan->io_base + CAN_HW_REG_MCR); + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_CTRL); + if (flexcan->br_clksrc) + reg |= __CTRL_CLK_SRC; + else + reg &= ~__CTRL_CLK_SRC; + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_CTRL); + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR) & (~__MCR_MDIS); + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_MCR); + reg |= __MCR_SOFT_RST; + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_MCR); + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + while (reg & __MCR_SOFT_RST) { + if (--timeout <= 0) { + printk(KERN_ERR "Flexcan software Reset Timeouted\n"); + return -1; + } + udelay(10); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + } + return 0; +} + +static inline void flexcan_mcr_setup(struct flexcan_device *flexcan) +{ + unsigned int reg; + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + reg &= ~(__MCR_MAX_MB_MASK | __MCR_WAK_MSK | __MCR_MAX_IDAM_MASK); + + if (flexcan->fifo) + reg |= __MCR_FEN; + else + reg &= ~__MCR_FEN; + + if (flexcan->wakeup) + reg |= __MCR_SLF_WAK | __MCR_WAK_MSK; + else + reg &= ~(__MCR_SLF_WAK | __MCR_WAK_MSK); + + if (flexcan->wak_src) + reg |= __MCR_WAK_SRC; + else + reg &= ~__MCR_WAK_SRC; + + if (flexcan->srx_dis) + reg |= __MCR_SRX_DIS; + else + reg &= ~__MCR_SRX_DIS; + + if (flexcan->bcc) + reg |= __MCR_BCC; + else + reg &= ~__MCR_BCC; + + if (flexcan->lprio) + reg |= __MCR_LPRIO_EN; + else + reg &= ~__MCR_LPRIO_EN; + + if (flexcan->abort) + reg |= __MCR_AEN; + else + reg &= ~__MCR_AEN; + + reg |= (flexcan->maxmb << __MCR_MAX_MB_OFFSET); + reg |= __MCR_DOZE | __MCR_MAX_IDAM_C; + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_MCR); +} + +static inline void flexcan_ctrl_setup(struct flexcan_device *flexcan) +{ + unsigned int reg; + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_CTRL); + reg &= ~(__CTRL_PRESDIV_MASK | __CTRL_RJW_MASK | __CTRL_PSEG1_MASK | + __CTRL_PSEG2_MASK | __CTRL_PROPSEG_MASK); + + if (flexcan->loopback) + reg |= __CTRL_LPB; + else + reg &= ~__CTRL_LPB; + + if (flexcan->smp) + reg |= __CTRL_SMP; + else + reg &= ~__CTRL_SMP; + + if (flexcan->boff_rec) + reg |= __CTRL_BOFF_REC; + else + reg &= ~__CTRL_BOFF_REC; + + if (flexcan->tsyn) + reg |= __CTRL_TSYN; + else + reg &= ~__CTRL_TSYN; + + if (flexcan->listen) + reg |= __CTRL_LOM; + else + reg &= ~__CTRL_LOM; + + reg |= (flexcan->br_presdiv << __CTRL_PRESDIV_OFFSET) | + (flexcan->br_rjw << __CTRL_RJW_OFFSET) | + (flexcan->br_pseg1 << __CTRL_PSEG1_OFFSET) | + (flexcan->br_pseg2 << __CTRL_PSEG2_OFFSET) | + (flexcan->br_propseg << __CTRL_PROPSEG_OFFSET); + + reg &= ~__CTRL_LBUF; + + reg |= __CTRL_TWRN_MSK | __CTRL_RWRN_MSK | __CTRL_BOFF_MSK | + __CTRL_ERR_MSK; + + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_CTRL); +} + +static int flexcan_hw_restart(struct net_device *dev) +{ + unsigned int reg; + struct flexcan_device *flexcan = netdev_priv(dev); + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + if (reg & __MCR_SOFT_RST) + return 1; + + flexcan_mcr_setup(flexcan); + + __raw_writel(0, flexcan->io_base + CAN_HW_REG_IMASK2); + __raw_writel(0, flexcan->io_base + CAN_HW_REG_IMASK1); + + __raw_writel(0xFFFFFFFF, flexcan->io_base + CAN_HW_REG_IFLAG2); + __raw_writel(0xFFFFFFFF, flexcan->io_base + CAN_HW_REG_IFLAG1); + + __raw_writel(0, flexcan->io_base + CAN_HW_REG_ECR); + + flexcan_mbm_init(flexcan); + netif_carrier_on(dev); + flexcan_hw_start(flexcan); + + if (netif_queue_stopped(dev)) + netif_start_queue(dev); + + return 0; +} + +static void flexcan_hw_watch(unsigned long data) +{ + unsigned int reg, ecr; + struct net_device *dev = (struct net_device *)data; + struct flexcan_device *flexcan = dev ? netdev_priv(dev) : NULL; + + BUG_ON(!flexcan); + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + if (reg & __MCR_MDIS) { + if (flexcan_hw_restart(dev)) + mod_timer(&flexcan->timer, HZ / 20); + return; + } + ecr = __raw_readl(flexcan->io_base + CAN_HW_REG_ECR); + if (flexcan->boff_rec) { + if (((reg & __ESR_FLT_CONF_MASK) >> __ESR_FLT_CONF_OFF) > 1) { + reg |= __MCR_SOFT_RST; + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_MCR); + mod_timer(&flexcan->timer, HZ / 20); + return; + } + netif_carrier_on(dev); + } +} + +static void flexcan_hw_busoff(struct net_device *dev) +{ + struct flexcan_device *flexcan = netdev_priv(dev); + unsigned int reg; + + netif_carrier_off(dev); + + flexcan->timer.function = flexcan_hw_watch; + flexcan->timer.data = (unsigned long)dev; + + if (flexcan->boff_rec) { + mod_timer(&flexcan->timer, HZ / 10); + return; + } + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + __raw_writel(reg | __MCR_SOFT_RST, flexcan->io_base + CAN_HW_REG_MCR); + mod_timer(&flexcan->timer, HZ / 20); +} + +static int flexcan_hw_open(struct flexcan_device *flexcan) +{ + if (flexcan_hw_reset(flexcan)) + return -EFAULT; + + flexcan_mcr_setup(flexcan); + flexcan_ctrl_setup(flexcan); + + __raw_writel(0, flexcan->io_base + CAN_HW_REG_IMASK2); + __raw_writel(0, flexcan->io_base + CAN_HW_REG_IMASK1); + + __raw_writel(0xFFFFFFFF, flexcan->io_base + CAN_HW_REG_IFLAG2); + __raw_writel(0xFFFFFFFF, flexcan->io_base + CAN_HW_REG_IFLAG1); + + __raw_writel(0, flexcan->io_base + CAN_HW_REG_ECR); + return 0; +} + +static void flexcan_err_handler(struct net_device *dev) +{ + struct flexcan_device *flexcan = netdev_priv(dev); + struct sk_buff *skb; + struct can_frame *frame; + unsigned int esr, ecr; + + esr = __raw_readl(flexcan->io_base + CAN_HW_REG_ESR); + __raw_writel(esr & __ESR_INTERRUPTS, flexcan->io_base + CAN_HW_REG_ESR); + + if (esr & __ESR_WAK_INT) + return; + + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (!skb) { + printk(KERN_ERR "%s: allocates skb fail in\n", __func__); + return; + } + frame = (struct can_frame *)skb_put(skb, sizeof(*frame)); + memset(frame, 0, sizeof(*frame)); + frame->can_id = CAN_ERR_FLAG | CAN_ERR_CRTL; + frame->can_dlc = CAN_ERR_DLC; + + if (esr & __ESR_TWRN_INT) + frame->data[1] |= CAN_ERR_CRTL_TX_WARNING; + + if (esr & __ESR_RWRN_INT) + frame->data[1] |= CAN_ERR_CRTL_RX_WARNING; + + if (esr & __ESR_BOFF_INT) + frame->can_id |= CAN_ERR_BUSOFF; + + if (esr & __ESR_ERR_INT) { + if (esr & __ESR_BIT1_ERR) + frame->data[2] |= CAN_ERR_PROT_BIT1; + + if (esr & __ESR_BIT0_ERR) + frame->data[2] |= CAN_ERR_PROT_BIT0; + + if (esr & __ESR_ACK_ERR) + frame->can_id |= CAN_ERR_ACK; + + /*TODO:// if (esr & __ESR_CRC_ERR) */ + + if (esr & __ESR_FRM_ERR) + frame->data[2] |= CAN_ERR_PROT_FORM; + + if (esr & __ESR_STF_ERR) + frame->data[2] |= CAN_ERR_PROT_STUFF; + + ecr = __raw_readl(flexcan->io_base + CAN_HW_REG_ECR); + switch ((esr & __ESR_FLT_CONF_MASK) >> __ESR_FLT_CONF_OFF) { + case 0: + if (__ECR_TX_ERR_COUNTER(ecr) >= __ECR_ACTIVE_THRESHOLD) + frame->data[1] |= CAN_ERR_CRTL_TX_WARNING; + if (__ECR_RX_ERR_COUNTER(ecr) >= __ECR_ACTIVE_THRESHOLD) + frame->data[1] |= CAN_ERR_CRTL_RX_WARNING; + break; + case 1: + if (__ECR_TX_ERR_COUNTER(ecr) >= + __ECR_PASSIVE_THRESHOLD) + frame->data[1] |= CAN_ERR_CRTL_TX_PASSIVE; + + if (__ECR_RX_ERR_COUNTER(ecr) >= + __ECR_PASSIVE_THRESHOLD) + frame->data[1] |= CAN_ERR_CRTL_RX_PASSIVE; + break; + default: + frame->can_id |= CAN_ERR_BUSOFF; + } + } + + if (frame->can_id & CAN_ERR_BUSOFF) + flexcan_hw_busoff(dev); + + skb->dev = dev; + skb->ip_summed = CHECKSUM_UNNECESSARY; + netif_receive_skb(skb); +} + +static irqreturn_t flexcan_irq_handler(int irq, void *data) +{ + struct net_device *dev = (struct net_device *)data; + struct flexcan_device *flexcan = dev ? netdev_priv(dev) : NULL; + unsigned int reg; + + BUG_ON(!flexcan); + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_ESR); + if (reg & __ESR_INTERRUPTS) { + flexcan_err_handler(dev); + return IRQ_HANDLED; + } + + flexcan_mbm_isr(dev); + return IRQ_HANDLED; +} + +static int flexcan_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct can_frame *frame = (struct can_frame *)skb->data; + struct flexcan_device *flexcan = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + + BUG_ON(!flexcan); + + if (frame->can_dlc > 8) + return -EINVAL; + + if (!flexcan_mbm_xmit(flexcan, frame)) { + dev_kfree_skb(skb); + stats->tx_bytes += frame->can_dlc; + stats->tx_packets++; + dev->trans_start = jiffies; + return NETDEV_TX_OK; + } + netif_stop_queue(dev); + return NETDEV_TX_BUSY; +} + +static int flexcan_open(struct net_device *dev) +{ + struct flexcan_device *flexcan; + struct platform_device *pdev; + struct flexcan_platform_data *plat_data; + + flexcan = netdev_priv(dev); + BUG_ON(!flexcan); + + pdev = flexcan->dev; + plat_data = (pdev->dev).platform_data; + if (plat_data && plat_data->active) + plat_data->active(pdev->id); + + if (flexcan->clk) + if (clk_enable(flexcan->clk)) + goto clk_err; + + if (flexcan->core_reg) + if (regulator_enable(flexcan->core_reg)) + goto core_reg_err; + + if (flexcan->io_reg) + if (regulator_enable(flexcan->io_reg)) + goto io_reg_err; + + if (plat_data && plat_data->xcvr_enable) + plat_data->xcvr_enable(pdev->id, 1); + + if (request_irq(flexcan->irq, flexcan_irq_handler, IRQF_SAMPLE_RANDOM, + dev->name, dev)) + goto irq_err; + + if (flexcan_hw_open(flexcan)) + goto open_err; + + flexcan_mbm_init(flexcan); + netif_carrier_on(dev); + flexcan_hw_start(flexcan); + return 0; + open_err: + free_irq(flexcan->irq, dev); + irq_err: + if (plat_data && plat_data->xcvr_enable) + plat_data->xcvr_enable(pdev->id, 0); + + if (flexcan->io_reg) + regulator_disable(flexcan->io_reg); + io_reg_err: + if (flexcan->core_reg) + regulator_disable(flexcan->core_reg); + core_reg_err: + if (flexcan->clk) + clk_disable(flexcan->clk); + clk_err: + if (plat_data && plat_data->inactive) + plat_data->inactive(pdev->id); + return -ENODEV; +} + +static int flexcan_stop(struct net_device *dev) +{ + struct flexcan_device *flexcan; + struct platform_device *pdev; + struct flexcan_platform_data *plat_data; + + flexcan = netdev_priv(dev); + + BUG_ON(!flexcan); + + pdev = flexcan->dev; + plat_data = (pdev->dev).platform_data; + + flexcan_hw_stop(flexcan); + + free_irq(flexcan->irq, dev); + + if (plat_data && plat_data->xcvr_enable) + plat_data->xcvr_enable(pdev->id, 0); + + if (flexcan->io_reg) + regulator_disable(flexcan->io_reg); + if (flexcan->core_reg) + regulator_disable(flexcan->core_reg); + if (flexcan->clk) + clk_disable(flexcan->clk); + if (plat_data && plat_data->inactive) + plat_data->inactive(pdev->id); + return 0; +} + +static struct net_device_ops flexcan_netdev_ops = { + .ndo_open = flexcan_open, + .ndo_stop = flexcan_stop, + .ndo_start_xmit = flexcan_start_xmit, +}; + +static void flexcan_setup(struct net_device *dev) +{ + dev->type = ARPHRD_CAN; + dev->mtu = sizeof(struct can_frame); + dev->hard_header_len = 0; + dev->addr_len = 0; + dev->tx_queue_len = FLEXCAN_MAX_MB; + dev->flags = IFF_NOARP; + dev->features = NETIF_F_NO_CSUM; + + dev->netdev_ops = &flexcan_netdev_ops; +} + +static int flexcan_probe(struct platform_device *pdev) +{ + struct net_device *net; + net = flexcan_device_alloc(pdev, flexcan_setup); + if (!net) + return -ENOMEM; + + if (register_netdev(net)) { + flexcan_device_free(pdev); + return -ENODEV; + } + return 0; +} + +static int flexcan_remove(struct platform_device *pdev) +{ + flexcan_device_free(pdev); + return 0; +} + +static int flexcan_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct net_device *net; + struct flexcan_device *flexcan; + struct flexcan_platform_data *plat_data; + net = (struct net_device *)dev_get_drvdata(&pdev->dev); + flexcan = netdev_priv(net); + + BUG_ON(!flexcan); + + if (!(net->flags & IFF_UP)) + return 0; + if (flexcan->wakeup) + set_irq_wake(flexcan->irq, 1); + else { + plat_data = (pdev->dev).platform_data; + + if (plat_data && plat_data->xcvr_enable) + plat_data->xcvr_enable(pdev->id, 0); + + if (flexcan->io_reg) + regulator_disable(flexcan->io_reg); + if (flexcan->core_reg) + regulator_disable(flexcan->core_reg); + if (flexcan->clk) + clk_disable(flexcan->clk); + if (plat_data && plat_data->inactive) + plat_data->inactive(pdev->id); + } + return 0; +} + +static int flexcan_resume(struct platform_device *pdev) +{ + struct net_device *net; + struct flexcan_device *flexcan; + struct flexcan_platform_data *plat_data; + net = (struct net_device *)dev_get_drvdata(&pdev->dev); + flexcan = netdev_priv(net); + + BUG_ON(!flexcan); + + if (!(net->flags & IFF_UP)) + return 0; + + if (flexcan->wakeup) + set_irq_wake(flexcan->irq, 0); + else { + plat_data = (pdev->dev).platform_data; + if (plat_data && plat_data->active) + plat_data->active(pdev->id); + + if (flexcan->clk) { + if (clk_enable(flexcan->clk)) + printk(KERN_ERR "%s:enable clock fail\n", + __func__); + } + + if (flexcan->core_reg) { + if (regulator_enable(flexcan->core_reg)) + printk(KERN_ERR "%s:enable core voltage\n", + __func__); + } + if (flexcan->io_reg) { + if (regulator_enable(flexcan->io_reg)) + printk(KERN_ERR "%s:enable io voltage\n", + __func__); + } + + if (plat_data && plat_data->xcvr_enable) + plat_data->xcvr_enable(pdev->id, 1); + } + return 0; +} + +static struct platform_driver flexcan_driver = { + .driver = { + .name = FLEXCAN_DEVICE_NAME, + }, + .probe = flexcan_probe, + .remove = flexcan_remove, + .suspend = flexcan_suspend, + .resume = flexcan_resume, +}; + +static __init int flexcan_init(void) +{ + pr_info("Freescale FlexCAN Driver \n"); + return platform_driver_register(&flexcan_driver); +} + +static __exit void flexcan_exit(void) +{ + return platform_driver_unregister(&flexcan_driver); +} + +module_init(flexcan_init); +module_exit(flexcan_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/net/can/flexcan/flexcan.h b/drivers/net/can/flexcan/flexcan.h new file mode 100644 index 000000000000..d19cc1ee0620 --- /dev/null +++ b/drivers/net/can/flexcan/flexcan.h @@ -0,0 +1,223 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file flexcan.h + * + * @brief FlexCan definitions. + * + * @ingroup can + */ + +#ifndef __CAN_FLEXCAN_H__ +#define __CAN_FLEXCAN_H__ + +#include <linux/list.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/clk.h> +#include <linux/can.h> +#include <linux/can/core.h> +#include <linux/can/error.h> + +#define FLEXCAN_DEVICE_NAME "FlexCAN" + +struct can_mb_cs { + unsigned int time_stamp:16; + unsigned int length:4; + unsigned int rtr:1; + unsigned int ide:1; + unsigned int srr:1; + unsigned int nouse1:1; + unsigned int code:4; + unsigned int nouse2:4; +}; + +#define CAN_MB_RX_INACTIVE 0x0 +#define CAN_MB_RX_EMPTY 0x4 +#define CAN_MB_RX_FULL 0x2 +#define CAN_MB_RX_OVERRUN 0x6 +#define CAN_MB_RX_BUSY 0x1 + +#define CAN_MB_TX_INACTIVE 0x8 +#define CAN_MB_TX_ABORT 0x9 +#define CAN_MB_TX_ONCE 0xC +#define CAN_MB_TX_REMOTE 0xA + +struct can_hw_mb { + union { + struct can_mb_cs cs; + unsigned int data; + } mb_cs; + unsigned int mb_id; + unsigned char mb_data[8]; +}; + +#define CAN_HW_REG_MCR 0x00 +#define CAN_HW_REG_CTRL 0x04 +#define CAN_HW_REG_TIMER 0x08 +#define CAN_HW_REG_RXGMASK 0x10 +#define CAN_HW_REG_RX14MASK 0x14 +#define CAN_HW_REG_RX15MASK 0x18 +#define CAN_HW_REG_ECR 0x1C +#define CAN_HW_REG_ESR 0x20 +#define CAN_HW_REG_IMASK2 0x24 +#define CAN_HW_REG_IMASK1 0x28 +#define CAN_HW_REG_IFLAG2 0x2C +#define CAN_HW_REG_IFLAG1 0x30 + +#define CAN_MB_BASE 0x0080 +#define CAN_RXMASK_BASE 0x0880 +#define CAN_FIFO_BASE 0xE0 + +#define __MCR_MDIS (1 << 31) +#define __MCR_FRZ (1 << 30) +#define __MCR_FEN (1 << 29) +#define __MCR_HALT (1 << 28) +#define __MCR_NOTRDY (1 << 27) +#define __MCR_WAK_MSK (1 << 26) +#define __MCR_SOFT_RST (1 << 25) +#define __MCR_FRZ_ACK (1 << 24) +#define __MCR_SLF_WAK (1 << 22) +#define __MCR_WRN_EN (1 << 21) +#define __MCR_LPM_ACK (1 << 20) +#define __MCR_WAK_SRC (1 << 19) +#define __MCR_DOZE (1 << 18) +#define __MCR_SRX_DIS (1 << 17) +#define __MCR_BCC (1 << 16) +#define __MCR_LPRIO_EN (1 << 13) +#define __MCR_AEN (1 << 12) +#define __MCR_MAX_IDAM_OFFSET 8 +#define __MCR_MAX_IDAM_MASK (0x3 << __MCR_MAX_IDAM_OFFSET) +#define __MCR_MAX_IDAM_A (0x0 << __MCR_MAX_IDAM_OFFSET) +#define __MCR_MAX_IDAM_B (0x1 << __MCR_MAX_IDAM_OFFSET) +#define __MCR_MAX_IDAM_C (0x2 << __MCR_MAX_IDAM_OFFSET) +#define __MCR_MAX_IDAM_D (0x3 << __MCR_MAX_IDAM_OFFSET) +#define __MCR_MAX_MB_OFFSET 0 +#define __MCR_MAX_MB_MASK (0x3F) + +#define __CTRL_PRESDIV_OFFSET 24 +#define __CTRL_PRESDIV_MASK (0xFF << __CTRL_PRESDIV_OFFSET) +#define __CTRL_RJW_OFFSET 22 +#define __CTRL_RJW_MASK (0x3 << __CTRL_RJW_OFFSET) +#define __CTRL_PSEG1_OFFSET 19 +#define __CTRL_PSEG1_MASK (0x7 << __CTRL_PSEG1_OFFSET) +#define __CTRL_PSEG2_OFFSET 16 +#define __CTRL_PSEG2_MASK (0x7 << __CTRL_PSEG2_OFFSET) +#define __CTRL_BOFF_MSK (0x1 << 15) +#define __CTRL_ERR_MSK (0x1 << 14) +#define __CTRL_CLK_SRC (0x1 << 13) +#define __CTRL_LPB (0x1 << 12) +#define __CTRL_TWRN_MSK (0x1 << 11) +#define __CTRL_RWRN_MSK (0x1 << 10) +#define __CTRL_SMP (0x1 << 7) +#define __CTRL_BOFF_REC (0x1 << 6) +#define __CTRL_TSYN (0x1 << 5) +#define __CTRL_LBUF (0x1 << 4) +#define __CTRL_LOM (0x1 << 3) +#define __CTRL_PROPSEG_OFFSET 0 +#define __CTRL_PROPSEG_MASK (0x7) + +#define __ECR_TX_ERR_COUNTER(x) ((x) & 0xFF) +#define __ECR_RX_ERR_COUNTER(x) (((x) >> 8) & 0xFF) +#define __ECR_PASSIVE_THRESHOLD 128 +#define __ECR_ACTIVE_THRESHOLD 96 + +#define __ESR_TWRN_INT (0x1 << 17) +#define __ESR_RWRN_INT (0x1 << 16) +#define __ESR_BIT1_ERR (0x1 << 15) +#define __ESR_BIT0_ERR (0x1 << 14) +#define __ESR_ACK_ERR (0x1 << 13) +#define __ESR_CRC_ERR (0x1 << 12) +#define __ESR_FRM_ERR (0x1 << 11) +#define __ESR_STF_ERR (0x1 << 10) +#define __ESR_TX_WRN (0x1 << 9) +#define __ESR_RX_WRN (0x1 << 8) +#define __ESR_IDLE (0x1 << 7) +#define __ESR_TXRX (0x1 << 6) +#define __ESR_FLT_CONF_OFF 4 +#define __ESR_FLT_CONF_MASK (0x3 << __ESR_FLT_CONF_OFF) +#define __ESR_BOFF_INT (0x1 << 2) +#define __ESR_ERR_INT (0x1 << 1) +#define __ESR_WAK_INT (0x1) + +#define __ESR_INTERRUPTS (__ESR_WAK_INT | __ESR_ERR_INT | \ + __ESR_BOFF_INT | __ESR_TWRN_INT | \ + __ESR_RWRN_INT) + +#define __FIFO_OV_INT 0x0080 +#define __FIFO_WARN_INT 0x0040 +#define __FIFO_RDY_INT 0x0020 + +struct flexcan_device { + struct mutex mutex; + void *io_base; + struct can_hw_mb *hwmb; + unsigned int *rx_mask; + unsigned int xmit_mb; + unsigned int bitrate; + /* word 1 */ + unsigned int br_presdiv:8; + unsigned int br_rjw:2; + unsigned int br_propseg:3; + unsigned int br_pseg1:3; + unsigned int br_pseg2:3; + unsigned int maxmb:6; + unsigned int xmit_maxmb:6; + unsigned int wd1_resv:1; + + /* word 2 */ + unsigned int fifo:1; + unsigned int wakeup:1; + unsigned int srx_dis:1; + unsigned int wak_src:1; + unsigned int bcc:1; + unsigned int lprio:1; + unsigned int abort:1; + unsigned int br_clksrc:1; + unsigned int loopback:1; + unsigned int smp:1; + unsigned int boff_rec:1; + unsigned int tsyn:1; + unsigned int listen:1; + + unsigned int ext_msg:1; + unsigned int std_msg:1; + + struct timer_list timer; + struct platform_device *dev; + struct regulator *core_reg; + struct regulator *io_reg; + struct clk *clk; + int irq; +}; + +#define FLEXCAN_MAX_FIFO_MB 8 +#define FLEXCAN_MAX_MB 64 +#define FLEXCAN_MAX_PRESDIV 256 +#define FLEXCAN_MAX_RJW 4 +#define FLEXCAN_MAX_PSEG1 8 +#define FLEXCAN_MAX_PSEG2 8 +#define FLEXCAN_MAX_PROPSEG 8 +#define FLEXCAN_MAX_BITRATE 1000000 + +extern struct net_device *flexcan_device_alloc(struct platform_device *pdev, + void (*setup) (struct net_device + *dev)); +extern void flexcan_device_free(struct platform_device *pdev); + +extern void flexcan_mbm_init(struct flexcan_device *flexcan); +extern void flexcan_mbm_isr(struct net_device *dev); +extern int flexcan_mbm_xmit(struct flexcan_device *flexcan, + struct can_frame *frame); +#endif /* __CAN_FLEXCAN_H__ */ diff --git a/drivers/net/can/flexcan/mbm.c b/drivers/net/can/flexcan/mbm.c new file mode 100644 index 000000000000..b0341ba9128e --- /dev/null +++ b/drivers/net/can/flexcan/mbm.c @@ -0,0 +1,347 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mbm.c + * + * @brief Driver for Freescale CAN Controller FlexCAN. + * + * @ingroup can + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include "flexcan.h" + +#define flexcan_swab32(x) \ + (((x) << 24) | ((x) >> 24) |\ + (((x) & (__u32)0x0000ff00UL) << 8) |\ + (((x) & (__u32)0x00ff0000UL) >> 8)) + +static inline void flexcan_memcpy(void *dst, void *src, int len) +{ + int i; + unsigned int *d = (unsigned int *)dst, *s = (unsigned int *)src; + len = (len + 3) >> 2; + for (i = 0; i < len; i++, s++, d++) + *d = flexcan_swab32(*s); +} + +static void flexcan_mb_bottom(struct net_device *dev, int index) +{ + struct flexcan_device *flexcan = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + struct can_hw_mb *hwmb; + struct can_frame *frame; + struct sk_buff *skb; + unsigned int tmp; + + hwmb = flexcan->hwmb + index; + if (flexcan->fifo || (index >= (flexcan->maxmb - flexcan->xmit_maxmb))) { + if (hwmb->mb_cs.cs.code == CAN_MB_TX_ABORT) + hwmb->mb_cs.cs.code = CAN_MB_TX_INACTIVE; + + if (hwmb->mb_cs.cs.code & CAN_MB_TX_INACTIVE) { + if (netif_queue_stopped(dev)) + netif_start_queue(dev); + return; + } + } + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (skb) { + frame = (struct can_frame *)skb_put(skb, sizeof(*frame)); + memset(frame, 0, sizeof(*frame)); + if (hwmb->mb_cs.cs.ide) + frame->can_id = + (hwmb->mb_id & CAN_EFF_MASK) | CAN_EFF_FLAG; + else + frame->can_id = (hwmb->mb_id >> 18) & CAN_SFF_MASK; + + if (hwmb->mb_cs.cs.rtr) + frame->can_id |= CAN_RTR_FLAG; + + frame->can_dlc = hwmb->mb_cs.cs.length; + + if (frame->can_dlc && frame->can_dlc) + flexcan_memcpy(frame->data, hwmb->mb_data, + frame->can_dlc); + + if (flexcan->fifo + || (index >= (flexcan->maxmb - flexcan->xmit_maxmb))) { + hwmb->mb_cs.cs.code = CAN_MB_TX_INACTIVE; + if (netif_queue_stopped(dev)) + netif_start_queue(dev); + } + + tmp = __raw_readl(flexcan->io_base + CAN_HW_REG_TIMER); + + dev->last_rx = jiffies; + stats->rx_packets++; + stats->rx_bytes += frame->can_dlc; + + skb->dev = dev; + skb->protocol = __constant_htons(ETH_P_CAN); + skb->ip_summed = CHECKSUM_UNNECESSARY; + netif_rx(skb); + } else { + tmp = hwmb->mb_cs.data; + tmp = hwmb->mb_id; + tmp = hwmb->mb_data[0]; + if (flexcan->fifo + || (index >= (flexcan->maxmb - flexcan->xmit_maxmb))) { + + hwmb->mb_cs.cs.code = CAN_MB_TX_INACTIVE; + if (netif_queue_stopped(dev)) + netif_start_queue(dev); + } + tmp = __raw_readl(flexcan->io_base + CAN_HW_REG_TIMER); + stats->rx_dropped++; + } +} + +static void flexcan_fifo_isr(struct net_device *dev, unsigned int iflag1) +{ + struct flexcan_device *flexcan = dev ? netdev_priv(dev) : NULL; + struct net_device_stats *stats = &dev->stats; + struct sk_buff *skb; + struct can_hw_mb *hwmb = flexcan->hwmb; + struct can_frame *frame; + unsigned int tmp; + + if (iflag1 & __FIFO_RDY_INT) { + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (skb) { + frame = + (struct can_frame *)skb_put(skb, sizeof(*frame)); + memset(frame, 0, sizeof(*frame)); + if (hwmb->mb_cs.cs.ide) + frame->can_id = + (hwmb->mb_id & CAN_EFF_MASK) | CAN_EFF_FLAG; + else + frame->can_id = + (hwmb->mb_id >> 18) & CAN_SFF_MASK; + + if (hwmb->mb_cs.cs.rtr) + frame->can_id |= CAN_RTR_FLAG; + + frame->can_dlc = hwmb->mb_cs.cs.length; + + if (frame->can_dlc && (frame->can_dlc <= 8)) + flexcan_memcpy(frame->data, hwmb->mb_data, + frame->can_dlc); + tmp = __raw_readl(flexcan->io_base + CAN_HW_REG_TIMER); + + dev->last_rx = jiffies; + + stats->rx_packets++; + stats->rx_bytes += frame->can_dlc; + + skb->dev = dev; + skb->protocol = __constant_htons(ETH_P_CAN); + skb->ip_summed = CHECKSUM_UNNECESSARY; + netif_rx(skb); + } else { + tmp = hwmb->mb_cs.data; + tmp = hwmb->mb_id; + tmp = hwmb->mb_data[0]; + tmp = __raw_readl(flexcan->io_base + CAN_HW_REG_TIMER); + } + } + + if (iflag1 & (__FIFO_OV_INT | __FIFO_WARN_INT)) { + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (skb) { + frame = + (struct can_frame *)skb_put(skb, sizeof(*frame)); + memset(frame, 0, sizeof(*frame)); + frame->can_id = CAN_ERR_FLAG | CAN_ERR_CRTL; + frame->can_dlc = CAN_ERR_DLC; + if (iflag1 & __FIFO_WARN_INT) + frame->data[1] |= + CAN_ERR_CRTL_TX_WARNING | + CAN_ERR_CRTL_RX_WARNING; + if (iflag1 & __FIFO_OV_INT) + frame->data[1] |= CAN_ERR_CRTL_RX_OVERFLOW; + + skb->dev = dev; + skb->protocol = __constant_htons(ETH_P_CAN); + skb->ip_summed = CHECKSUM_UNNECESSARY; + netif_rx(skb); + } + } +} + +/*! + * @brief The function call by CAN ISR to handle mb events. + * + * @param dev the pointer of network device. + * + * @return none + */ +void flexcan_mbm_isr(struct net_device *dev) +{ + int i, iflag1, iflag2, maxmb; + struct flexcan_device *flexcan = dev ? netdev_priv(dev) : NULL; + + if (flexcan->maxmb > 31) { + maxmb = flexcan->maxmb + 1 - 32; + iflag1 = __raw_readl(flexcan->io_base + CAN_HW_REG_IFLAG1) & + __raw_readl(flexcan->io_base + CAN_HW_REG_IMASK1); + iflag2 = __raw_readl(flexcan->io_base + CAN_HW_REG_IFLAG2) & + __raw_readl(flexcan->io_base + CAN_HW_REG_IMASK2); + iflag2 &= (1 << maxmb) - 1; + maxmb = 32; + } else { + maxmb = flexcan->maxmb + 1; + iflag1 = __raw_readl(flexcan->io_base + CAN_HW_REG_IFLAG1) & + __raw_readl(flexcan->io_base + CAN_HW_REG_IMASK1); + iflag1 &= (1 << maxmb) - 1; + iflag2 = 0; + } + + __raw_writel(iflag1, flexcan->io_base + CAN_HW_REG_IFLAG1); + __raw_writel(iflag2, flexcan->io_base + CAN_HW_REG_IFLAG2); + + if (flexcan->fifo) { + flexcan_fifo_isr(dev, iflag1); + iflag1 &= 0xFFFFFF00; + } + for (i = 0; iflag1 && (i < maxmb); i++) { + if (iflag1 & (1 << i)) { + iflag1 &= ~(1 << i); + flexcan_mb_bottom(dev, i); + } + } + + for (i = maxmb; iflag2 && (i <= flexcan->maxmb); i++) { + if (iflag2 & (1 << (i - 32))) { + iflag2 &= ~(1 << (i - 32)); + flexcan_mb_bottom(dev, i); + } + } +} + +/*! + * @brief function to xmit message buffer + * + * @param flexcan the pointer of can hardware device. + * @param frame the pointer of can message frame. + * + * @return Returns 0 if xmit is success. otherwise returns non-zero. + */ +int flexcan_mbm_xmit(struct flexcan_device *flexcan, struct can_frame *frame) +{ + int i = flexcan->xmit_mb; + struct can_hw_mb *hwmb = flexcan->hwmb; + + do { + if (hwmb[i].mb_cs.cs.code == CAN_MB_TX_INACTIVE) + break; + if ((++i) > flexcan->maxmb) { + if (flexcan->fifo) + i = FLEXCAN_MAX_FIFO_MB; + else + i = flexcan->xmit_maxmb + 1; + } + if (i == flexcan->xmit_mb) + return -1; + } while (1); + + flexcan->xmit_mb = i + 1; + if (flexcan->xmit_mb > flexcan->maxmb) { + if (flexcan->fifo) + flexcan->xmit_mb = FLEXCAN_MAX_FIFO_MB; + else + flexcan->xmit_mb = flexcan->xmit_maxmb + 1; + } + + if (frame->can_id & CAN_RTR_FLAG) + hwmb[i].mb_cs.cs.rtr = 1; + else + hwmb[i].mb_cs.cs.rtr = 0; + + if (frame->can_id & CAN_EFF_FLAG) { + hwmb[i].mb_cs.cs.ide = 1; + hwmb[i].mb_cs.cs.srr = 1; + hwmb[i].mb_id = frame->can_id & CAN_EFF_MASK; + } else { + hwmb[i].mb_cs.cs.ide = 0; + hwmb[i].mb_id = (frame->can_id & CAN_SFF_MASK) << 18; + } + + hwmb[i].mb_cs.cs.length = frame->can_dlc; + flexcan_memcpy(hwmb[i].mb_data, frame->data, frame->can_dlc); + hwmb[i].mb_cs.cs.code = CAN_MB_TX_ONCE; + return 0; +} + +/*! + * @brief function to initial message buffer + * + * @param flexcan the pointer of can hardware device. + * + * @return none + */ +void flexcan_mbm_init(struct flexcan_device *flexcan) +{ + struct can_hw_mb *hwmb; + int rx_mb, i; + + /* Set global mask to receive all messages */ + __raw_writel(0, flexcan->io_base + CAN_HW_REG_RXGMASK); + __raw_writel(0, flexcan->io_base + CAN_HW_REG_RX14MASK); + __raw_writel(0, flexcan->io_base + CAN_HW_REG_RX15MASK); + + memset(flexcan->hwmb, 0, sizeof(*hwmb) * FLEXCAN_MAX_MB); + /* Set individual mask to receive all messages */ + memset(flexcan->rx_mask, 0, sizeof(unsigned int) * FLEXCAN_MAX_MB); + + if (flexcan->fifo) + rx_mb = FLEXCAN_MAX_FIFO_MB; + else + rx_mb = flexcan->maxmb - flexcan->xmit_maxmb; + + hwmb = flexcan->hwmb; + if (flexcan->fifo) { + unsigned long *id_table = flexcan->io_base + CAN_FIFO_BASE; + for (i = 0; i < rx_mb; i++) + id_table[i] = 0; + } else { + for (i = 0; i < rx_mb; i++) { + hwmb[i].mb_cs.cs.code = CAN_MB_RX_EMPTY; + /* + * IDE bit can not control by mask registers + * So set message buffer to receive extend + * or standard message. + */ + if (flexcan->ext_msg && flexcan->std_msg) + hwmb[i].mb_cs.cs.ide = i & 1; + else { + if (flexcan->ext_msg) + hwmb[i].mb_cs.cs.ide = 1; + } + } + } + + for (; i <= flexcan->maxmb; i++) + hwmb[i].mb_cs.cs.code = CAN_MB_TX_INACTIVE; + + flexcan->xmit_mb = rx_mb; +} diff --git a/drivers/net/irda/Kconfig b/drivers/net/irda/Kconfig index f76384221422..06151f87dacd 100644 --- a/drivers/net/irda/Kconfig +++ b/drivers/net/irda/Kconfig @@ -387,5 +387,9 @@ config MCS_FIR To compile it as a module, choose M here: the module will be called mcs7780. +config MXC_FIR + tristate "Freescale MXC FIR driver" + depends on ARCH_MXC && IRDA + endmenu diff --git a/drivers/net/irda/Makefile b/drivers/net/irda/Makefile index d82e1e3bd8c8..a54dcdeb3861 100644 --- a/drivers/net/irda/Makefile +++ b/drivers/net/irda/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_VLSI_FIR) += vlsi_ir.o obj-$(CONFIG_VIA_FIR) += via-ircc.o obj-$(CONFIG_PXA_FICP) += pxaficp_ir.o obj-$(CONFIG_MCS_FIR) += mcs7780.o +obj-$(CONFIG_MXC_FIR) += mxc_ir.o obj-$(CONFIG_AU1000_FIR) += au1k_ir.o # SIR drivers obj-$(CONFIG_IRTTY_SIR) += irtty-sir.o sir-dev.o diff --git a/drivers/net/irda/mxc_ir.c b/drivers/net/irda/mxc_ir.c new file mode 100644 index 000000000000..fd5052f3a0ab --- /dev/null +++ b/drivers/net/irda/mxc_ir.c @@ -0,0 +1,1781 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * Based on sa1100_ir.c - Copyright 2000-2001 Russell King + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_ir.c + * + * @brief Driver for the Freescale Semiconductor MXC FIRI. + * + * This driver is based on drivers/net/irda/sa1100_ir.c, by Russell King. + * + * @ingroup FIRI + */ + +/* + * Include Files + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> + +#include <net/irda/irda.h> +#include <net/irda/wrapper.h> +#include <net/irda/irda_device.h> + +#include <asm/irq.h> +#include <asm/dma.h> +#include <mach/hardware.h> +#include <mach/mxc_uart.h> +#include "mxc_ir.h" + +#define IS_SIR(mi) ( (mi)->speed <= 115200 ) +#define IS_MIR(mi) ( (mi)->speed < 4000000 && (mi)->speed >= 576000 ) +#define IS_FIR(mi) ( (mi)->speed >= 4000000 ) + +#define SDMA_START_DELAY() { \ + volatile int j,k;\ + int i;\ + for(i=0;i<10000;i++)\ + k=j;\ + } + +#define IRDA_FRAME_SIZE_LIMIT 2047 +#define UART_BUFF_SIZE 14384 + +#define UART4_UFCR_TXTL 16 +#define UART4_UFCR_RXTL 1 + +#define FIRI_SDMA_TX +#define FIRI_SDMA_RX + +/*! + * This structure is a way for the low level driver to define their own + * \b mxc_irda structure. This structure includes SK buffers, DMA buffers. + * and has other elements that are specifically required by this driver. + */ +struct mxc_irda { + /*! + * This keeps track of device is running or not + */ + unsigned char open; + + /*! + * This holds current FIRI communication speed + */ + int speed; + + /*! + * This holds FIRI communication speed for next packet + */ + int newspeed; + + /*! + * SK buffer for transmitter + */ + struct sk_buff *txskb; + + /*! + * SK buffer for receiver + */ + struct sk_buff *rxskb; + +#ifdef FIRI_SDMA_RX + /*! + * SK buffer for tasklet + */ + struct sk_buff *tskb; +#endif + + /*! + * DMA address for transmitter + */ + dma_addr_t dma_rx_buff_phy; + + /*! + * DMA address for receiver + */ + dma_addr_t dma_tx_buff_phy; + + /*! + * DMA Transmit buffer length + */ + unsigned int dma_tx_buff_len; + + /*! + * DMA channel for transmitter + */ + int txdma_ch; + + /*! + * DMA channel for receiver + */ + int rxdma_ch; + + /*! + * IrDA network device statistics + */ + struct net_device_stats stats; + + /*! + * The device structure used to get FIRI information + */ + struct device *dev; + + /*! + * Resource structure for UART, which will maintain base addresses and IRQs. + */ + struct resource *uart_res; + + /*! + * Base address of UART, used in readl and writel. + */ + void *uart_base; + + /*! + * Resource structure for FIRI, which will maintain base addresses and IRQs. + */ + struct resource *firi_res; + + /*! + * Base address of FIRI, used in readl and writel. + */ + void *firi_base; + + /*! + * UART IRQ number. + */ + int uart_irq; + + /*! + * Second UART IRQ number in case the interrupt lines are not muxed. + */ + int uart_irq1; + + /*! + * UART clock needed for baud rate calculations + */ + struct clk *uart_clk; + + /*! + * UART clock needed for baud rate calculations + */ + unsigned long uart_clk_rate; + + /*! + * FIRI clock needed for baud rate calculations + */ + struct clk *firi_clk; + + /*! + * FIRI IRQ number. + */ + int firi_irq; + + /*! + * IrLAP layer instance + */ + struct irlap_cb *irlap; + + /*! + * Driver supported baudrate capabilities + */ + struct qos_info qos; + + /*! + * Temporary transmit buffer used by the driver + */ + iobuff_t tx_buff; + + /*! + * Temporary receive buffer used by the driver + */ + iobuff_t rx_buff; + + /*! + * Pointer to platform specific data structure. + */ + struct mxc_ir_platform_data *mxc_ir_plat; + + /*! + * This holds the power management status of this module. + */ + int suspend; + +}; + +extern void gpio_firi_active(void *, unsigned int); +extern void gpio_firi_inactive(void); +extern void gpio_firi_init(void); + +void mxc_irda_firi_init(struct mxc_irda *si); +#ifdef FIRI_SDMA_RX +static void mxc_irda_fir_dma_rx_irq(void *id, int error_status, + unsigned int count); +#endif +#ifdef FIRI_SDMA_TX +static void mxc_irda_fir_dma_tx_irq(void *id, int error_status, + unsigned int count); +#endif + +/*! + * This function allocates and maps the receive buffer, + * unless it is already allocated. + * + * @param si FIRI device specific structure. + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_rx_alloc(struct mxc_irda *si) +{ +#ifdef FIRI_SDMA_RX + mxc_dma_requestbuf_t dma_request; +#endif + if (si->rxskb) { + return 0; + } + + si->rxskb = alloc_skb(IRDA_FRAME_SIZE_LIMIT + 1, GFP_ATOMIC); + + if (!si->rxskb) { + dev_err(si->dev, "mxc_ir: out of memory for RX SKB\n"); + return -ENOMEM; + } + + /* + * Align any IP headers that may be contained + * within the frame. + */ + skb_reserve(si->rxskb, 1); + +#ifdef FIRI_SDMA_RX + si->dma_rx_buff_phy = + dma_map_single(si->dev, si->rxskb->data, IRDA_FRAME_SIZE_LIMIT, + DMA_FROM_DEVICE); + + dma_request.num_of_bytes = IRDA_FRAME_SIZE_LIMIT; + dma_request.dst_addr = si->dma_rx_buff_phy; + dma_request.src_addr = si->firi_res->start; + + mxc_dma_config(si->rxdma_ch, &dma_request, 1, MXC_DMA_MODE_READ); +#endif + return 0; +} + +/*! + * This function is called to disable the FIRI dma + * + * @param si FIRI port specific structure. + */ +static void mxc_irda_disabledma(struct mxc_irda *si) +{ + /* Stop all DMA activity. */ +#ifdef FIRI_SDMA_TX + mxc_dma_disable(si->txdma_ch); +#endif +#ifdef FIRI_SDMA_RX + mxc_dma_disable(si->rxdma_ch); +#endif +} + +/*! + * This function is called to set the IrDA communications speed. + * + * @param si FIRI specific structure. + * @param speed new Speed to be configured for. + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_set_speed(struct mxc_irda *si, int speed) +{ + unsigned long flags; + int ret = 0; + unsigned int num, denom, baud; + unsigned int cr; + + dev_dbg(si->dev, "speed:%d\n", speed); + switch (speed) { + case 9600: + case 19200: + case 38400: + case 57600: + case 115200: + dev_dbg(si->dev, "starting SIR\n"); + baud = speed; + if (IS_FIR(si)) { +#ifdef FIRI_SDMA_RX + mxc_dma_disable(si->rxdma_ch); +#endif + cr = readl(si->firi_base + FIRITCR); + cr &= ~FIRITCR_TE; + writel(cr, si->firi_base + FIRITCR); + + cr = readl(si->firi_base + FIRIRCR); + cr &= ~FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + + } + local_irq_save(flags); + + /* Disable Tx and Rx */ + cr = readl(si->uart_base + MXC_UARTUCR2); + cr &= ~(MXC_UARTUCR2_RXEN | MXC_UARTUCR2_TXEN); + writel(cr, si->uart_base + MXC_UARTUCR2); + + gpio_firi_inactive(); + + num = baud / 100 - 1; + denom = si->uart_clk_rate / 1600 - 1; + if ((denom < 65536) && (si->uart_clk_rate > 1600)) { + writel(num, si->uart_base + MXC_UARTUBIR); + writel(denom, si->uart_base + MXC_UARTUBMR); + } + + si->speed = speed; + + writel(0xFFFF, si->uart_base + MXC_UARTUSR1); + writel(0xFFFF, si->uart_base + MXC_UARTUSR2); + + /* Enable Receive Overrun and Data Ready interrupts. */ + cr = readl(si->uart_base + MXC_UARTUCR4); + cr |= (MXC_UARTUCR4_OREN | MXC_UARTUCR4_DREN); + writel(cr, si->uart_base + MXC_UARTUCR4); + + cr = readl(si->uart_base + MXC_UARTUCR2); + cr |= (MXC_UARTUCR2_RXEN | MXC_UARTUCR2_TXEN); + writel(cr, si->uart_base + MXC_UARTUCR2); + + local_irq_restore(flags); + break; + case 4000000: + local_irq_save(flags); + + /* Disable Receive Overrun and Data Ready interrupts. */ + cr = readl(si->uart_base + MXC_UARTUCR4); + cr &= ~(MXC_UARTUCR4_OREN | MXC_UARTUCR4_DREN); + writel(cr, si->uart_base + MXC_UARTUCR4); + + /* Disable Tx and Rx */ + cr = readl(si->uart_base + MXC_UARTUCR2); + cr &= ~(MXC_UARTUCR2_RXEN | MXC_UARTUCR2_TXEN); + writel(cr, si->uart_base + MXC_UARTUCR2); + + /* + * FIR configuration + */ + mxc_irda_disabledma(si); + + cr = readl(si->firi_base + FIRITCR); + cr &= ~FIRITCR_TE; + writel(cr, si->firi_base + FIRITCR); + + gpio_firi_active(si->firi_base + FIRITCR, FIRITCR_TPP); + + si->speed = speed; + + cr = readl(si->firi_base + FIRIRCR); + cr |= FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + + dev_dbg(si->dev, "Going for fast IRDA ...\n"); + ret = mxc_irda_rx_alloc(si); + + /* clear RX status register */ + writel(0xFFFF, si->firi_base + FIRIRSR); +#ifdef FIRI_SDMA_RX + if (si->rxskb) { + mxc_dma_enable(si->rxdma_ch); + } +#endif + local_irq_restore(flags); + + break; + default: + dev_err(si->dev, "speed not supported by FIRI\n"); + break; + } + + return ret; +} + +/*! + * This function is called to set the IrDA communications speed. + * + * @param si FIRI specific structure. + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static inline int mxc_irda_fir_error(struct mxc_irda *si) +{ + struct sk_buff *skb = si->rxskb; + unsigned int dd_error, crc_error, overrun_error; + unsigned int sr; + + if (!skb) { + dev_err(si->dev, "no skb!\n"); + return -1; + } + + sr = readl(si->firi_base + FIRIRSR); + dd_error = sr & FIRIRSR_DDE; + crc_error = sr & FIRIRSR_CRCE; + overrun_error = sr & FIRIRSR_RFO; + + if (!(dd_error | crc_error | overrun_error)) { + return 0; + } + dev_err(si->dev, "dde,crce,rfo=%d,%d,%d.\n", dd_error, crc_error, + overrun_error); + si->stats.rx_errors++; + if (crc_error) { + si->stats.rx_crc_errors++; + } + if (dd_error) { + si->stats.rx_frame_errors++; + } + if (overrun_error) { + si->stats.rx_frame_errors++; + } + writel(sr, si->firi_base + FIRIRSR); + + return -1; +} + +#ifndef FIRI_SDMA_RX +/*! + * FIR interrupt service routine to handle receive. + * + * @param dev pointer to the net_device structure + */ +void mxc_irda_fir_irq_rx(struct net_device *dev) +{ + struct mxc_irda *si = dev->priv; + struct sk_buff *skb = si->rxskb; + unsigned int sr, len; + int i; + unsigned char *p = skb->data; + + /* + * Deal with any receive errors. + */ + if (mxc_irda_fir_error(si) != 0) { + return; + } + + sr = readl(si->firi_base + FIRIRSR); + + if (!(sr & FIRIRSR_RPE)) { + return; + } + + /* + * Coming here indicates that fir rx packet has been successfully recieved. + * And No error happened so far. + */ + writel(sr | FIRIRSR_RPE, si->firi_base + FIRIRSR); + + len = (sr & FIRIRSR_RFP) >> 8; + + /* 4 bytes of CRC */ + len -= 4; + + skb_put(skb, len); + + for (i = 0; i < len; i++) { + *p++ = readb(si->firi_base + FIRIRXFIFO); + } + + /* Discard the four CRC bytes */ + for (i = 0; i < 4; i++) { + readb(si->firi_base + FIRIRXFIFO); + } + + /* + * Deal with the case of packet complete. + */ + skb->dev = dev; + skb->mac.raw = skb->data; + skb->protocol = htons(ETH_P_IRDA); + si->stats.rx_packets++; + si->stats.rx_bytes += len; + netif_rx(skb); + + si->rxskb = NULL; + mxc_irda_rx_alloc(si); + + writel(0xFFFF, si->firi_base + FIRIRSR); + +} +#endif + +/*! + * FIR interrupt service routine to handle transmit. + * + * @param dev pointer to the net_device structure + */ +void mxc_irda_fir_irq_tx(struct net_device *dev) +{ + struct mxc_irda *si = netdev_priv(dev); + struct sk_buff *skb = si->txskb; + unsigned int cr, sr; + + sr = readl(si->firi_base + FIRITSR); + writel(sr, si->firi_base + FIRITSR); + + if (sr & FIRITSR_TC) { + +#ifdef FIRI_SDMA_TX + mxc_dma_disable(si->txdma_ch); +#endif + cr = readl(si->firi_base + FIRITCR); + cr &= ~(FIRITCR_TCIE | FIRITCR_TE); + writel(cr, si->firi_base + FIRITCR); + + if (si->newspeed) { + mxc_irda_set_speed(si, si->newspeed); + si->newspeed = 0; + } + si->txskb = NULL; + + cr = readl(si->firi_base + FIRIRCR); + cr |= FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + + writel(0xFFFF, si->firi_base + FIRIRSR); + /* + * Account and free the packet. + */ + if (skb) { +#ifdef FIRI_SDMA_TX + dma_unmap_single(si->dev, si->dma_tx_buff_phy, skb->len, + DMA_TO_DEVICE); +#endif + si->stats.tx_packets++; + si->stats.tx_bytes += skb->len; + dev_kfree_skb_irq(skb); + } + /* + * Make sure that the TX queue is available for sending + * (for retries). TX has priority over RX at all times. + */ + netif_wake_queue(dev); + } +} + +/*! + * This is FIRI interrupt handler. + * + * @param dev pointer to the net_device structure + */ +void mxc_irda_fir_irq(struct net_device *dev) +{ + struct mxc_irda *si = netdev_priv(dev); + unsigned int sr1, sr2; + + sr1 = readl(si->firi_base + FIRIRSR); + sr2 = readl(si->firi_base + FIRITSR); + + if (sr2 & FIRITSR_TC) + mxc_irda_fir_irq_tx(dev); +#ifndef FIRI_SDMA_RX + if (sr1 & (FIRIRSR_RPE | FIRIRSR_RFO)) + mxc_irda_fir_irq_rx(dev); +#endif + +} + +/*! + * This is the SIR transmit routine. + * + * @param si FIRI specific structure. + * + * @param dev pointer to the net_device structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_sir_txirq(struct mxc_irda *si, struct net_device *dev) +{ + unsigned int sr1, sr2, cr; + unsigned int status; + + sr1 = readl(si->uart_base + MXC_UARTUSR1); + sr2 = readl(si->uart_base + MXC_UARTUSR2); + cr = readl(si->uart_base + MXC_UARTUCR2); + + /* + * Echo cancellation for IRDA Transmit chars + * Disable the receiver and enable Transmit complete. + */ + cr &= ~MXC_UARTUCR2_RXEN; + writel(cr, si->uart_base + MXC_UARTUCR2); + cr = readl(si->uart_base + MXC_UARTUCR4); + cr |= MXC_UARTUCR4_TCEN; + writel(cr, si->uart_base + MXC_UARTUCR4); + + while ((sr1 & MXC_UARTUSR1_TRDY) && si->tx_buff.len) { + + writel(*si->tx_buff.data++, si->uart_base + MXC_UARTUTXD); + si->tx_buff.len -= 1; + sr1 = readl(si->uart_base + MXC_UARTUSR1); + } + + if (si->tx_buff.len == 0) { + si->stats.tx_packets++; + si->stats.tx_bytes += si->tx_buff.data - si->tx_buff.head; + + /*Yoohoo...we are done...Lets stop Tx */ + cr = readl(si->uart_base + MXC_UARTUCR1); + cr &= ~MXC_UARTUCR1_TRDYEN; + writel(cr, si->uart_base + MXC_UARTUCR1); + + do { + status = readl(si->uart_base + MXC_UARTUSR2); + } while (!(status & MXC_UARTUSR2_TXDC)); + + if (si->newspeed) { + mxc_irda_set_speed(si, si->newspeed); + si->newspeed = 0; + } + /* I'm hungry! */ + netif_wake_queue(dev); + + /* Is the transmit complete to reenable the receiver? */ + if (status & MXC_UARTUSR2_TXDC) { + + cr = readl(si->uart_base + MXC_UARTUCR2); + cr |= MXC_UARTUCR2_RXEN; + writel(cr, si->uart_base + MXC_UARTUCR2); + /* Disable the Transmit complete interrupt bit */ + cr = readl(si->uart_base + MXC_UARTUCR4); + cr &= ~MXC_UARTUCR4_TCEN; + writel(cr, si->uart_base + MXC_UARTUCR4); + } + } + + return 0; +} + +/*! + * This is the SIR receive routine. + * + * @param si FIRI specific structure. + * + * @param dev pointer to the net_device structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_sir_rxirq(struct mxc_irda *si, struct net_device *dev) +{ + unsigned int data, status; + volatile unsigned int sr2; + + sr2 = readl(si->uart_base + MXC_UARTUSR2); + while ((sr2 & MXC_UARTUSR2_RDR) == 1) { + data = readl(si->uart_base + MXC_UARTURXD); + status = data & 0xf400; + if (status & MXC_UARTURXD_ERR) { + dev_err(si->dev, "Receive an incorrect data =0x%x.\n", + data); + si->stats.rx_errors++; + if (status & MXC_UARTURXD_OVRRUN) { + si->stats.rx_fifo_errors++; + dev_err(si->dev, "Rx overrun.\n"); + } + if (status & MXC_UARTURXD_FRMERR) { + si->stats.rx_frame_errors++; + dev_err(si->dev, "Rx frame error.\n"); + } + if (status & MXC_UARTURXD_PRERR) { + dev_err(si->dev, "Rx parity error.\n"); + } + /* Other: it is the Break char. + * Do nothing for it. throw out the data. + */ + async_unwrap_char(dev, &si->stats, &si->rx_buff, + (data & 0xFF)); + } else { + /* It is correct data. */ + data &= 0xFF; + async_unwrap_char(dev, &si->stats, &si->rx_buff, data); + + dev->last_rx = jiffies; + } + sr2 = readl(si->uart_base + MXC_UARTUSR2); + + writel(0xFFFF, si->uart_base + MXC_UARTUSR1); + writel(0xFFFF, si->uart_base + MXC_UARTUSR2); + } /*while */ + return 0; + +} + +static irqreturn_t mxc_irda_irq(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct mxc_irda *si = netdev_priv(dev); + + if (IS_FIR(si)) { + mxc_irda_fir_irq(dev); + return IRQ_HANDLED; + } + + if (readl(si->uart_base + MXC_UARTUCR2) & MXC_UARTUCR2_RXEN) { + mxc_irda_sir_rxirq(si, dev); + } + if ((readl(si->uart_base + MXC_UARTUCR1) & MXC_UARTUCR1_TRDYEN) && + (readl(si->uart_base + MXC_UARTUSR1) & MXC_UARTUSR1_TRDY)) { + mxc_irda_sir_txirq(si, dev); + } + + return IRQ_HANDLED; +} + +static irqreturn_t mxc_irda_tx_irq(int irq, void *dev_id) +{ + + struct net_device *dev = dev_id; + struct mxc_irda *si = netdev_priv(dev); + + mxc_irda_sir_txirq(si, dev); + + return IRQ_HANDLED; +} + +static irqreturn_t mxc_irda_rx_irq(int irq, void *dev_id) +{ + + struct net_device *dev = dev_id; + struct mxc_irda *si = netdev_priv(dev); + + /* Clear the aging timer bit */ + writel(MXC_UARTUSR1_AGTIM, si->uart_base + MXC_UARTUSR1); + + mxc_irda_sir_rxirq(si, dev); + + return IRQ_HANDLED; +} + +#ifdef FIRI_SDMA_RX +struct tasklet_struct dma_rx_tasklet; + +static void mxc_irda_rx_task(unsigned long tparam) +{ + struct mxc_irda *si = (struct mxc_irda *)tparam; + struct sk_buff *lskb = si->tskb; + + si->tskb = NULL; + if (lskb) { + lskb->mac_header = lskb->data; + lskb->protocol = htons(ETH_P_IRDA); + netif_rx(lskb); + } +} + +/*! + * Receiver DMA callback routine. + * + * @param id pointer to network device structure + * @param error_status used to pass error status to this callback function + * @param count number of bytes received + */ +static void mxc_irda_fir_dma_rx_irq(void *id, int error_status, + unsigned int count) +{ + struct net_device *dev = id; + struct mxc_irda *si = netdev_priv(dev); + struct sk_buff *skb = si->rxskb; + unsigned int cr; + unsigned int len; + + cr = readl(si->firi_base + FIRIRCR); + cr &= ~FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + cr = readl(si->firi_base + FIRIRCR); + cr |= FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + len = count - 4; /* remove 4 bytes for CRC */ + skb_put(skb, len); + skb->dev = dev; + si->tskb = skb; + tasklet_schedule(&dma_rx_tasklet); + + if (si->dma_rx_buff_phy != 0) + dma_unmap_single(si->dev, si->dma_rx_buff_phy, + IRDA_FRAME_SIZE_LIMIT, DMA_FROM_DEVICE); + + si->rxskb = NULL; + mxc_irda_rx_alloc(si); + + SDMA_START_DELAY(); + writel(0xFFFF, si->firi_base + FIRIRSR); + + if (si->rxskb) { + mxc_dma_enable(si->rxdma_ch); + } +} +#endif + +#ifdef FIRI_SDMA_TX +/*! + * This function is called by SDMA Interrupt Service Routine to indicate + * requested DMA transfer is completed. + * + * @param id pointer to network device structure + * @param error_status used to pass error status to this callback function + * @param count number of bytes sent + */ +static void mxc_irda_fir_dma_tx_irq(void *id, int error_status, + unsigned int count) +{ + struct net_device *dev = id; + struct mxc_irda *si = netdev_priv(dev); + + mxc_dma_disable(si->txdma_ch); +} +#endif + +/*! + * This function is called by Linux IrDA network subsystem to + * transmit the Infrared data packet. The TX DMA channel is configured + * to transfer SK buffer data to FIRI TX FIFO along with DMA transfer + * completion routine. + * + * @param skb The packet that is queued to be sent + * @param dev net_device structure. + * + * @return The function returns 0 on success and a negative value on + * failure. + */ +static int mxc_irda_hard_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct mxc_irda *si = netdev_priv(dev); + int speed = irda_get_next_speed(skb); + unsigned int cr; + + /* + * Does this packet contain a request to change the interface + * speed? If so, remember it until we complete the transmission + * of this frame. + */ + if (speed != si->speed && speed != -1) { + si->newspeed = speed; + } + + /* If this is an empty frame, we can bypass a lot. */ + if (skb->len == 0) { + if (si->newspeed) { + si->newspeed = 0; + mxc_irda_set_speed(si, speed); + } + dev_kfree_skb(skb); + return 0; + } + + /* We must not be transmitting... */ + netif_stop_queue(dev); + if (IS_SIR(si)) { + + si->tx_buff.data = si->tx_buff.head; + si->tx_buff.len = async_wrap_skb(skb, si->tx_buff.data, + si->tx_buff.truesize); + cr = readl(si->uart_base + MXC_UARTUCR1); + cr |= MXC_UARTUCR1_TRDYEN; + writel(cr, si->uart_base + MXC_UARTUCR1); + dev_kfree_skb(skb); + } else { + unsigned int mtt = irda_get_mtt(skb); + unsigned char *p = skb->data; + unsigned int skb_len = skb->len; +#ifdef FIRI_SDMA_TX + mxc_dma_requestbuf_t dma_request; +#else + unsigned int i, sr; +#endif + + skb_len = skb_len + ((4 - (skb_len % 4)) % 4); + + if (si->txskb) { + BUG(); + } + si->txskb = skb; + + /* + * If we have a mean turn-around time, impose the specified + * specified delay. We could shorten this by timing from + * the point we received the packet. + */ + if (mtt) { + udelay(mtt); + } + + cr = readl(si->firi_base + FIRIRCR); + cr &= ~FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + + writel(skb->len - 1, si->firi_base + FIRITCTR); + +#ifdef FIRI_SDMA_TX + /* + * Configure DMA Tx Channel for source and destination addresses, + * Number of bytes in SK buffer to transfer and Transfer complete + * callback function. + */ + si->dma_tx_buff_len = skb_len; + si->dma_tx_buff_phy = + dma_map_single(si->dev, p, skb_len, DMA_TO_DEVICE); + + dma_request.num_of_bytes = skb_len; + dma_request.dst_addr = si->firi_res->start + FIRITXFIFO; + dma_request.src_addr = si->dma_tx_buff_phy; + + mxc_dma_config(si->txdma_ch, &dma_request, 1, + MXC_DMA_MODE_WRITE); + + mxc_dma_enable(si->txdma_ch); +#endif + cr = readl(si->firi_base + FIRITCR); + cr |= FIRITCR_TCIE; + writel(cr, si->firi_base + FIRITCR); + + cr |= FIRITCR_TE; + writel(cr, si->firi_base + FIRITCR); + +#ifndef FIRI_SDMA_TX + for (i = 0; i < skb->len;) { + sr = readl(si->firi_base + FIRITSR); + /* TFP = number of bytes in the TX FIFO for the + * Transmitter + * */ + if ((sr >> 8) < 128) { + writeb(*p, si->firi_base + FIRITXFIFO); + p++; + i++; + } + } +#endif + } + + dev->trans_start = jiffies; + return 0; +} + +/*! + * This function handles network interface ioctls passed to this driver.. + * + * @param dev net device structure + * @param ifreq user request data + * @param cmd command issued + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_ioctl(struct net_device *dev, struct ifreq *ifreq, int cmd) +{ + struct if_irda_req *rq = (struct if_irda_req *)ifreq; + struct mxc_irda *si = netdev_priv(dev); + int ret = -EOPNOTSUPP; + + switch (cmd) { + /* This function will be used by IrLAP to change the speed */ + case SIOCSBANDWIDTH: + dev_dbg(si->dev, "%s:with cmd SIOCSBANDWIDTH\n", __FUNCTION__); + if (capable(CAP_NET_ADMIN)) { + /* + * We are unable to set the speed if the + * device is not running. + */ + if (si->open) { + ret = mxc_irda_set_speed(si, rq->ifr_baudrate); + } else { + dev_err(si->dev, "mxc_ir_ioctl: SIOCSBANDWIDTH:\ + !netif_running\n"); + ret = 0; + } + } + break; + case SIOCSMEDIABUSY: + dev_dbg(si->dev, "%s:with cmd SIOCSMEDIABUSY\n", __FUNCTION__); + ret = -EPERM; + if (capable(CAP_NET_ADMIN)) { + irda_device_set_media_busy(dev, TRUE); + ret = 0; + } + break; + case SIOCGRECEIVING: + rq->ifr_receiving = + IS_SIR(si) ? si->rx_buff.state != OUTSIDE_FRAME : 0; + ret = 0; + break; + default: + break; + } + return ret; +} + +/*! + * Kernel interface routine to get current statistics of the device + * which includes the number bytes/packets transmitted/received, + * receive errors, CRC errors, framing errors etc. + * + * @param dev the net_device structure + * + * @return This function returns IrDA network statistics + */ +static struct net_device_stats *mxc_irda_stats(struct net_device *dev) +{ + struct mxc_irda *si = netdev_priv(dev); + return &si->stats; +} + +/*! + * FIRI init function + * + * @param si FIRI device specific structure. + */ +void mxc_irda_firi_init(struct mxc_irda *si) +{ + unsigned int firi_baud, osf = 6; + unsigned int tcr, rcr, cr; + + si->firi_clk = clk_get(si->dev, "firi_clk"); + firi_baud = clk_round_rate(si->firi_clk, 48004500); + if ((firi_baud < 47995500) || + (clk_set_rate(si->firi_clk, firi_baud) < 0)) { + dev_err(si->dev, "Unable to set FIR clock to 48MHz.\n"); + return; + } + clk_enable(si->firi_clk); + + writel(0xFFFF, si->firi_base + FIRITSR); + writel(0xFFFF, si->firi_base + FIRIRSR); + writel(0x00, si->firi_base + FIRITCR); + writel(0x00, si->firi_base + FIRIRCR); + + /* set _BL & _OSF */ + cr = (osf - 1) | (16 << 5); + writel(cr, si->firi_base + FIRICR); + +#ifdef FIRI_SDMA_TX + tcr = + FIRITCR_TDT_FIR | FIRITCR_TM_FIR | FIRITCR_TCIE | + FIRITCR_PCF | FIRITCR_PC; +#else + tcr = FIRITCR_TM_FIR | FIRITCR_TCIE | FIRITCR_PCF | FIRITCR_PC; +#endif + +#ifdef FIRI_SDMA_RX + rcr = + FIRIRCR_RPEDE | FIRIRCR_RM_FIR | FIRIRCR_RDT_FIR | + FIRIRCR_RPA | FIRIRCR_RPP; +#else + rcr = + FIRIRCR_RPEDE | FIRIRCR_RM_FIR | FIRIRCR_RDT_FIR | FIRIRCR_RPEIE | + FIRIRCR_RPA | FIRIRCR_PAIE | FIRIRCR_RFOIE | FIRIRCR_RPP; +#endif + + writel(tcr, si->firi_base + FIRITCR); + writel(rcr, si->firi_base + FIRIRCR); + cr = 0; + writel(cr, si->firi_base + FIRITCTR); +} + +/*! + * This function initialises the UART. + * + * @param si FIRI port specific structure. + * + * @return The function returns 0 on success. + */ +static int mxc_irda_uart_init(struct mxc_irda *si) +{ + unsigned int per_clk; + unsigned int num, denom, baud, ufcr = 0; + unsigned int cr; + int d = 1; + int uart_ir_mux = 0; + + if (si->mxc_ir_plat) + uart_ir_mux = si->mxc_ir_plat->uart_ir_mux; + /* + * Clear Status Registers 1 and 2 + **/ + writel(0xFFFF, si->uart_base + MXC_UARTUSR1); + writel(0xFFFF, si->uart_base + MXC_UARTUSR2); + + /* Configure the IOMUX for the UART */ + gpio_firi_init(); + + per_clk = clk_get_rate(si->uart_clk); + baud = per_clk / 16; + if (baud > 1500000) { + baud = 1500000; + d = per_clk / ((baud * 16) + 1000); + if (d > 6) { + d = 6; + } + } + clk_enable(si->uart_clk); + + si->uart_clk_rate = per_clk / d; + writel(si->uart_clk_rate / 1000, si->uart_base + MXC_UARTONEMS); + + writel(si->mxc_ir_plat->ir_rx_invert | MXC_UARTUCR4_IRSC, + si->uart_base + MXC_UARTUCR4); + + if (uart_ir_mux) { + writel(MXC_UARTUCR3_RXDMUXSEL | si->mxc_ir_plat->ir_tx_invert | + MXC_UARTUCR3_DSR, si->uart_base + MXC_UARTUCR3); + } else { + writel(si->mxc_ir_plat->ir_tx_invert | MXC_UARTUCR3_DSR, + si->uart_base + MXC_UARTUCR3); + } + + writel(MXC_UARTUCR2_IRTS | MXC_UARTUCR2_CTS | MXC_UARTUCR2_WS | + MXC_UARTUCR2_ATEN | MXC_UARTUCR2_TXEN | MXC_UARTUCR2_RXEN, + si->uart_base + MXC_UARTUCR2); + /* Wait till we are out of software reset */ + do { + cr = readl(si->uart_base + MXC_UARTUCR2); + } while (!(cr & MXC_UARTUCR2_SRST)); + + ufcr |= (UART4_UFCR_TXTL << MXC_UARTUFCR_TXTL_OFFSET) | + ((6 - d) << MXC_UARTUFCR_RFDIV_OFFSET) | UART4_UFCR_RXTL; + writel(ufcr, si->uart_base + MXC_UARTUFCR); + + writel(MXC_UARTUCR1_UARTEN | MXC_UARTUCR1_IREN, + si->uart_base + MXC_UARTUCR1); + + baud = 9600; + num = baud / 100 - 1; + denom = si->uart_clk_rate / 1600 - 1; + + if ((denom < 65536) && (si->uart_clk_rate > 1600)) { + writel(num, si->uart_base + MXC_UARTUBIR); + writel(denom, si->uart_base + MXC_UARTUBMR); + } + + writel(0x0000, si->uart_base + MXC_UARTUTS); + return 0; + +} + +/*! + * This function enables FIRI port. + * + * @param si FIRI port specific structure. + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_startup(struct mxc_irda *si) +{ + int ret = 0; + + mxc_irda_uart_init(si); + mxc_irda_firi_init(si); + + /* configure FIRI device for speed */ + ret = mxc_irda_set_speed(si, si->speed = 9600); + + return ret; +} + +/*! + * When an ifconfig is issued which changes the device flag to include + * IFF_UP this function is called. It is only called when the change + * occurs, not when the interface remains up. The function grabs the interrupt + * resources and registers FIRI interrupt service routines, requests for DMA + * channels, configures the DMA channel. It then initializes the IOMUX + * registers to configure the pins for FIRI signals and finally initializes the + * various FIRI registers and enables the port for reception. + * + * @param dev net device structure that is being opened + * + * @return The function returns 0 for a successful open and non-zero value + * on failure. + */ +static int mxc_irda_start(struct net_device *dev) +{ + struct mxc_irda *si = netdev_priv(dev); + int err; + int ints_muxed = 0; + mxc_dma_device_t dev_id = 0; + + if (si->uart_irq == si->uart_irq1) + ints_muxed = 1; + + si->speed = 9600; + + if (si->uart_irq == si->firi_irq) { + err = + request_irq(si->uart_irq, mxc_irda_irq, 0, dev->name, dev); + if (err) { + dev_err(si->dev, "%s:Failed to request the IRQ\n", + __FUNCTION__); + return err; + } + /* + * The interrupt must remain disabled for now. + */ + disable_irq(si->uart_irq); + } else { + err = + request_irq(si->firi_irq, mxc_irda_irq, 0, dev->name, dev); + if (err) { + dev_err(si->dev, "%s:Failed to request FIRI IRQ\n", + __FUNCTION__); + return err; + } + /* + * The interrupt must remain disabled for now. + */ + disable_irq(si->firi_irq); + if (ints_muxed) { + + err = request_irq(si->uart_irq, mxc_irda_irq, 0, + dev->name, dev); + if (err) { + dev_err(si->dev, + "%s:Failed to request UART IRQ\n", + __FUNCTION__); + goto err_irq1; + } + /* + * The interrupt must remain disabled for now. + */ + disable_irq(si->uart_irq); + } else { + err = request_irq(si->uart_irq, mxc_irda_tx_irq, 0, + dev->name, dev); + if (err) { + dev_err(si->dev, + "%s:Failed to request UART IRQ\n", + __FUNCTION__); + goto err_irq1; + } + err = request_irq(si->uart_irq1, mxc_irda_rx_irq, 0, + dev->name, dev); + if (err) { + dev_err(si->dev, + "%s:Failed to request UART1 IRQ\n", + __FUNCTION__); + goto err_irq2; + } + /* + * The interrupts must remain disabled for now. + */ + disable_irq(si->uart_irq); + disable_irq(si->uart_irq1); + } + } +#ifdef FIRI_SDMA_RX + dev_id = MXC_DMA_FIR_RX; + si->rxdma_ch = mxc_dma_request(dev_id, "MXC FIRI RX"); + if (si->rxdma_ch < 0) { + dev_err(si->dev, "Cannot allocate FIR DMA channel\n"); + goto err_rx_dma; + } + mxc_dma_callback_set(si->rxdma_ch, mxc_irda_fir_dma_rx_irq, + (void *)dev_get_drvdata(si->dev)); +#endif +#ifdef FIRI_SDMA_TX + + dev_id = MXC_DMA_FIR_TX; + si->txdma_ch = mxc_dma_request(dev_id, "MXC FIRI TX"); + if (si->txdma_ch < 0) { + dev_err(si->dev, "Cannot allocate FIR DMA channel\n"); + goto err_tx_dma; + } + mxc_dma_callback_set(si->txdma_ch, mxc_irda_fir_dma_tx_irq, + (void *)dev_get_drvdata(si->dev)); +#endif + /* Setup the serial port port for the initial speed. */ + err = mxc_irda_startup(si); + if (err) { + goto err_startup; + } + + /* Open a new IrLAP layer instance. */ + si->irlap = irlap_open(dev, &si->qos, "mxc"); + err = -ENOMEM; + if (!si->irlap) { + goto err_irlap; + } + + /* Now enable the interrupt and start the queue */ + si->open = 1; + si->suspend = 0; + + if (si->uart_irq == si->firi_irq) { + enable_irq(si->uart_irq); + } else { + enable_irq(si->firi_irq); + if (ints_muxed == 1) { + enable_irq(si->uart_irq); + } else { + enable_irq(si->uart_irq); + enable_irq(si->uart_irq1); + } + } + + netif_start_queue(dev); + return 0; + + err_irlap: + si->open = 0; + mxc_irda_disabledma(si); + err_startup: +#ifdef FIRI_SDMA_TX + mxc_dma_free(si->txdma_ch); + err_tx_dma: +#endif +#ifdef FIRI_SDMA_RX + mxc_dma_free(si->rxdma_ch); + err_rx_dma: +#endif + if (si->uart_irq1 && !ints_muxed) + free_irq(si->uart_irq1, dev); + err_irq2: + if (si->uart_irq != si->firi_irq) + free_irq(si->uart_irq, dev); + err_irq1: + if (si->firi_irq) + free_irq(si->firi_irq, dev); + return err; +} + +/*! + * This function is called when IFF_UP flag has been cleared by the user via + * the ifconfig irda0 down command. This function stops any further + * transmissions being queued, and then disables the interrupts. + * Finally it resets the device. + * @param dev the net_device structure + * + * @return int the function always returns 0 indicating a success. + */ +static int mxc_irda_stop(struct net_device *dev) +{ + struct mxc_irda *si = netdev_priv(dev); + unsigned long flags; + + /* Stop IrLAP */ + if (si->irlap) { + irlap_close(si->irlap); + si->irlap = NULL; + } + + netif_stop_queue(dev); + + /*Save flags and disable the FIRI interrupts.. */ + if (si->open) { + local_irq_save(flags); + disable_irq(si->uart_irq); + free_irq(si->uart_irq, dev); + if (si->uart_irq != si->firi_irq) { + disable_irq(si->firi_irq); + free_irq(si->firi_irq, dev); + if (si->uart_irq1 != si->uart_irq) { + disable_irq(si->uart_irq1); + free_irq(si->uart_irq1, dev); + } + } + local_irq_restore(flags); + si->open = 0; + } +#ifdef FIRI_SDMA_RX + if (si->rxdma_ch) { + mxc_dma_disable(si->rxdma_ch); + mxc_dma_free(si->rxdma_ch); + if (si->dma_rx_buff_phy) { + dma_unmap_single(si->dev, si->dma_rx_buff_phy, + IRDA_FRAME_SIZE_LIMIT, + DMA_FROM_DEVICE); + si->dma_rx_buff_phy = 0; + } + si->rxdma_ch = 0; + } + tasklet_kill(&dma_rx_tasklet); +#endif +#ifdef FIRI_SDMA_TX + if (si->txdma_ch) { + mxc_dma_disable(si->txdma_ch); + mxc_dma_free(si->txdma_ch); + if (si->dma_tx_buff_phy) { + dma_unmap_single(si->dev, si->dma_tx_buff_phy, + si->dma_tx_buff_len, DMA_TO_DEVICE); + si->dma_tx_buff_phy = 0; + } + si->txdma_ch = 0; + } +#endif + return 0; +} + +#ifdef CONFIG_PM +/*! + * This function is called to put the FIRI in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which FIRI + * to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxc_irda_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct mxc_irda *si = netdev_priv(ndev); + unsigned int cr; + unsigned long flags; + + if (!si) { + return 0; + } + if (si->suspend == 1) { + dev_err(si->dev, + " suspend - Device is already suspended ... \n"); + return 0; + } + if (si->open) { + + netif_device_detach(ndev); + mxc_irda_disabledma(si); + + /*Save flags and disable the FIRI interrupts.. */ + local_irq_save(flags); + disable_irq(si->uart_irq); + if (si->uart_irq != si->firi_irq) { + disable_irq(si->firi_irq); + if (si->uart_irq != si->uart_irq1) { + disable_irq(si->uart_irq1); + } + } + local_irq_restore(flags); + + /* Disable Tx and Rx and then disable the UART clock */ + cr = readl(si->uart_base + MXC_UARTUCR2); + cr &= ~(MXC_UARTUCR2_TXEN | MXC_UARTUCR2_RXEN); + writel(cr, si->uart_base + MXC_UARTUCR2); + cr = readl(si->uart_base + MXC_UARTUCR1); + cr &= ~MXC_UARTUCR1_UARTEN; + writel(cr, si->uart_base + MXC_UARTUCR1); + clk_disable(si->uart_clk); + + /*Disable Tx and Rx for FIRI and then disable the FIRI clock.. */ + cr = readl(si->firi_base + FIRITCR); + cr &= ~FIRITCR_TE; + writel(cr, si->firi_base + FIRITCR); + cr = readl(si->firi_base + FIRIRCR); + cr &= ~FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + clk_disable(si->firi_clk); + + gpio_firi_inactive(); + + si->suspend = 1; + si->open = 0; + } + return 0; +} + +/*! + * This function is called to bring the FIRI back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which FIRI + * to resume + * + * @return The function always returns 0. + */ +static int mxc_irda_resume(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct mxc_irda *si = netdev_priv(ndev); + unsigned long flags; + + if (!si) { + return 0; + } + + if (si->suspend == 1 && !si->open) { + + /*Initialise the UART first */ + clk_enable(si->uart_clk); + + /*Now init FIRI */ + gpio_firi_active(si->firi_base + FIRITCR, FIRITCR_TPP); + mxc_irda_startup(si); + + /* Enable the UART and FIRI interrupts.. */ + local_irq_save(flags); + enable_irq(si->uart_irq); + if (si->uart_irq != si->firi_irq) { + enable_irq(si->firi_irq); + if (si->uart_irq != si->uart_irq1) { + enable_irq(si->uart_irq1); + } + } + local_irq_restore(flags); + + /* Let the kernel know that we are alive and kicking.. */ + netif_device_attach(ndev); + + si->suspend = 0; + si->open = 1; + } + return 0; +} +#else +#define mxc_irda_suspend NULL +#define mxc_irda_resume NULL +#endif + +static int mxc_irda_init_iobuf(iobuff_t * io, int size) +{ + io->head = kmalloc(size, GFP_KERNEL | GFP_DMA); + if (io->head != NULL) { + io->truesize = size; + io->in_frame = FALSE; + io->state = OUTSIDE_FRAME; + io->data = io->head; + } + return io->head ? 0 : -ENOMEM; + +} + +static struct net_device_ops mxc_irda_ops = { + .ndo_start_xmit = mxc_irda_hard_xmit, + .ndo_open = mxc_irda_start, + .ndo_stop = mxc_irda_stop, + .ndo_do_ioctl = mxc_irda_ioctl, + .ndo_get_stats = mxc_irda_stats, +}; + +/*! + * This function is called during the driver binding process. + * This function requests for memory, initializes net_device structure and + * registers with kernel. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions + * + * @return The function returns 0 on success and a non-zero value on failure + */ +static int mxc_irda_probe(struct platform_device *pdev) +{ + struct net_device *dev; + struct mxc_irda *si; + struct resource *uart_res, *firi_res; + int uart_irq, firi_irq, uart_irq1; + unsigned int baudrate_mask = 0; + int err; + + uart_res = &pdev->resource[0]; + uart_irq = pdev->resource[1].start; + + firi_res = &pdev->resource[2]; + firi_irq = pdev->resource[3].start; + + uart_irq1 = pdev->resource[4].start; + + if (!uart_res || uart_irq == NO_IRQ || !firi_res || firi_irq == NO_IRQ) { + dev_err(&pdev->dev, "Unable to find resources\n"); + return -ENXIO; + } + + err = + request_mem_region(uart_res->start, SZ_16K, + "MXC_IRDA") ? 0 : -EBUSY; + if (err) { + dev_err(&pdev->dev, "Failed to request UART memory region\n"); + return -ENOMEM; + } + + err = + request_mem_region(firi_res->start, SZ_16K, + "MXC_IRDA") ? 0 : -EBUSY; + if (err) { + dev_err(&pdev->dev, "Failed to request FIRI memory region\n"); + goto err_mem_1; + } + + dev = alloc_irdadev(sizeof(struct mxc_irda)); + if (!dev) { + goto err_mem_2; + } + + si = netdev_priv(dev); + si->dev = &pdev->dev; + + si->mxc_ir_plat = pdev->dev.platform_data; + si->uart_clk = si->mxc_ir_plat->uart_clk; + + si->uart_res = uart_res; + si->firi_res = firi_res; + si->uart_irq = uart_irq; + si->firi_irq = firi_irq; + si->uart_irq1 = uart_irq1; + + si->uart_base = ioremap(uart_res->start, SZ_16K); + si->firi_base = ioremap(firi_res->start, SZ_16K); + + if (!(si->uart_base || si->firi_base)) { + err = -ENOMEM; + goto err_mem_3; + } + + /* + * Initialise the SIR buffers + */ + err = mxc_irda_init_iobuf(&si->rx_buff, UART_BUFF_SIZE); + if (err) { + goto err_mem_4; + } + + err = mxc_irda_init_iobuf(&si->tx_buff, UART_BUFF_SIZE); + if (err) { + goto err_mem_5; + } + + dev->netdev_ops = &mxc_irda_ops; + + irda_init_max_qos_capabilies(&si->qos); + + /* + * We support + * SIR(9600, 19200,38400, 57600 and 115200 bps) + * FIR(4 Mbps) + * Min Turn Time set to 1ms or greater. + */ + baudrate_mask |= IR_9600 | IR_19200 | IR_38400 | IR_57600 | IR_115200; + baudrate_mask |= IR_4000000 << 8; + + si->qos.baud_rate.bits &= baudrate_mask; + si->qos.min_turn_time.bits = 0x7; + + irda_qos_bits_to_value(&si->qos); + +#ifdef FIRI_SDMA_RX + si->tskb = NULL; + tasklet_init(&dma_rx_tasklet, mxc_irda_rx_task, (unsigned long)si); +#endif + err = register_netdev(dev); + if (err == 0) { + platform_set_drvdata(pdev, dev); + } else { + kfree(si->tx_buff.head); + err_mem_5: + kfree(si->rx_buff.head); + err_mem_4: + iounmap(si->uart_base); + iounmap(si->firi_base); + err_mem_3: + free_netdev(dev); + err_mem_2: + release_mem_region(firi_res->start, SZ_16K); + err_mem_1: + release_mem_region(uart_res->start, SZ_16K); + } + return err; +} + +/*! + * Dissociates the driver from the FIRI device. Removes the appropriate FIRI + * port structure from the kernel. + * + * @param pdev the device structure used to give information on which FIRI + * to remove + * + * @return The function always returns 0. + */ +static int mxc_irda_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct mxc_irda *si = netdev_priv(dev); + + if (si->uart_base) + iounmap(si->uart_base); + if (si->firi_base) + iounmap(si->firi_base); + if (si->firi_res->start) + release_mem_region(si->firi_res->start, SZ_16K); + if (si->uart_res->start) + release_mem_region(si->uart_res->start, SZ_16K); + if (si->tx_buff.head) + kfree(si->tx_buff.head); + if (si->rx_buff.head) + kfree(si->rx_buff.head); + + platform_set_drvdata(pdev, NULL); + unregister_netdev(dev); + free_netdev(dev); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcir_driver = { + .driver = { + .name = "mxcir", + }, + .probe = mxc_irda_probe, + .remove = mxc_irda_remove, + .suspend = mxc_irda_suspend, + .resume = mxc_irda_resume, +}; + +/*! + * This function is used to initialize the FIRI driver module. The function + * registers the power management callback functions with the kernel and also + * registers the FIRI callback functions. + * + * @return The function returns 0 on success and a non-zero value on failure. + */ +static int __init mxc_irda_init(void) +{ + return platform_driver_register(&mxcir_driver); +} + +/*! + * This function is used to cleanup all resources before the driver exits. + */ +static void __exit mxc_irda_exit(void) +{ + platform_driver_unregister(&mxcir_driver); +} + +module_init(mxc_irda_init); +module_exit(mxc_irda_exit); + +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("MXC IrDA(SIR/FIR) driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/irda/mxc_ir.h b/drivers/net/irda/mxc_ir.h new file mode 100644 index 000000000000..6b22ca129f27 --- /dev/null +++ b/drivers/net/irda/mxc_ir.h @@ -0,0 +1,133 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __MXC_FIRI_REG_H__ +#define __MXC_FIRI_REG_H__ + +#include <mach/hardware.h> + +/*! + * @defgroup FIRI Fast IR Driver + */ + +/*! + * @file mxc_ir.h + * + * @brief MXC FIRI header file + * + * This file defines base address and bits of FIRI registers + * + * @ingroup FIRI + */ + +/*! + * FIRI maximum packet length + */ +#define FIR_MAX_RXLEN 2047 + +/* + * FIRI Transmitter Control Register + */ +#define FIRITCR 0x00 +/* + * FIRI Transmitter Count Register + */ +#define FIRITCTR 0x04 +/* + * FIRI Receiver Control Register + */ +#define FIRIRCR 0x08 +/* + * FIRI Transmitter Status Register + */ +#define FIRITSR 0x0C +/* + * FIRI Receiver Status Register + */ +#define FIRIRSR 0x10 +/* + * FIRI Transmitter FIFO + */ +#define FIRITXFIFO 0x14 +/* + * FIRI Receiver FIFO + */ +#define FIRIRXFIFO 0x18 +/* + * FIRI Control Register + */ +#define FIRICR 0x1C + +/* + * Bit definitions of Transmitter Controller Register + */ +#define FIRITCR_HAG (1<<24) /* H/W address generator */ +#define FIRITCR_SRF_FIR (0<<13) /* Start field repeat factor */ +#define FIRITCR_SRF_MIR (1<<13) /* Start field Repeat Factor */ +#define FIRITCR_TDT_MIR (2<<10) /* TX trigger for MIR is set to 32 bytes) */ +#define FIRITCR_TDT_FIR (1<<10) /* TX trigger for FIR is set to 16 bytes) */ +#define FIRITCR_TCIE (1<<9) /* TX Complete Interrupt Enable */ +#define FIRITCR_TPEIE (1<<8) /* TX Packet End Interrupt Enable */ +#define FIRITCR_TFUIE (1<<7) /* TX FIFO Under-run Interrupt Enable */ +#define FIRITCR_PCF (1<<6) /* Packet Complete by FIFO */ +#define FIRITCR_PC (1<<5) /* Packet Complete */ +#define FIRITCR_SIP (1<<4) /* TX Enable of SIP */ +#define FIRITCR_TPP (1<<3) /* TX Pulse Polarity bit */ +#define FIRITCR_TM_FIR (0<<1) /* TX Mode 4 Mbps */ +#define FIRITCR_TM_MIR1 (1<<1) /* TX Mode 0.576 Mbps */ +#define FIRITCR_TM_MIR2 (1<<2) /* TX Mode 1.152 Mbps */ +#define FIRITCR_TE (1<<0) /* TX Enable */ + +/* + * Bit definitions of Transmitter Count Register + */ +#define FIRITCTR_TPL 511 /* TX Packet Length set to 512 bytes */ + +/* + * Bit definitions of Receiver Control Register + */ +#define FIRIRCR_RAM (1<<24) /* RX Address Match */ +#define FIRIRCR_RPEDE (1<<11) /* Packet End DMA request Enable */ +#define FIRIRCR_RDT_MIR (2<<8) /* DMA Trigger level(64 bytes in RXFIFO) */ +#define FIRIRCR_RDT_FIR (1<<8) /* DMA Trigger level(16 bytes in RXFIFO) */ +#define FIRIRCR_RPA (1<<7) /* RX Packet Abort */ +#define FIRIRCR_RPEIE (1<<6) /* RX Packet End Interrupt Enable */ +#define FIRIRCR_PAIE (1<<5) /* Packet Abort Interrupt Enable */ +#define FIRIRCR_RFOIE (1<<4) /* RX FIFO Overrun Interrupt Enable */ +#define FIRIRCR_RPP (1<<3) /* RX Pulse Polarity bit */ +#define FIRIRCR_RM_FIR (0<<1) /* 4 Mbps */ +#define FIRIRCR_RM_MIR1 (1<<1) /* 0.576 Mbps */ +#define FIRIRCR_RM_MIR2 (1<<2) /* 1.152 Mbps */ +#define FIRIRCR_RE (1<<0) /* RX Enable */ + +/* Transmitter Status Register */ +#define FIRITSR_TFP 0xFF00 /* Mask for available bytes in TX FIFO */ +#define FIRITSR_TC (1<<3) /* Transmit Complete bit */ +#define FIRITSR_SIPE (1<<2) /* SIP End bit */ +#define FIRITSR_TPE (1<<1) /* Transmit Packet End */ +#define FIRITSR_TFU (1<<0) /* TX FIFO Under-run */ + +/* Receiver Status Register */ +#define FIRIRSR_RFP 0xFF00 /* mask for available bytes RX FIFO */ +#define FIRIRSR_PAS (1<<5) /* preamble search */ +#define FIRIRSR_RPE (1<<4) /* RX Packet End */ +#define FIRIRSR_RFO (1<<3) /* RX FIFO Overrun */ +#define FIRIRSR_BAM (1<<2) /* Broadcast Address Match */ +#define FIRIRSR_CRCE (1<<1) /* CRC error */ +#define FIRIRSR_DDE (1<<0) /* Address, control or data field error */ + +/* FIRI Control Register */ +#define FIRICR_BL (32<<5) /* Burst Length is set to 32 */ +#define FIRICR_OSF (0<<1) /* Over Sampling Factor */ + +#endif /* __MXC_FIRI_REG_H__ */ diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig index fbf965b31c14..c81af3e45b17 100644 --- a/drivers/pcmcia/Kconfig +++ b/drivers/pcmcia/Kconfig @@ -275,6 +275,14 @@ config AT91_CF Say Y here to support the CompactFlash controller on AT91 chips. Or choose M to compile the driver as a module named "at91_cf". +config PCMCIA_MX31ADS + tristate "MX31ADS PCMCIA support" + depends on ARM && MACH_MX31ADS && PCMCIA + help + Say Y here to include support for the Freescale i.MX31 PCMCIA controller. + + This driver is also available as a module called mx31ads_pcmcia. + config ELECTRA_CF tristate "Electra CompactFlash Controller" depends on PCMCIA && PPC_PASEMI diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile index 047394d98ac2..4c1fc112554a 100644 --- a/drivers/pcmcia/Makefile +++ b/drivers/pcmcia/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_OMAP_CF) += omap_cf.o obj-$(CONFIG_BFIN_CFPCMCIA) += bfin_cf_pcmcia.o obj-$(CONFIG_AT91_CF) += at91_cf.o obj-$(CONFIG_ELECTRA_CF) += electra_cf.o +obj-$(CONFIG_PCMCIA_MX31ADS) += mx31ads-pcmcia.o sa11xx_core-y += soc_common.o sa11xx_base.o pxa2xx_core-y += soc_common.o pxa2xx_base.o diff --git a/drivers/pcmcia/mx31ads-pcmcia.c b/drivers/pcmcia/mx31ads-pcmcia.c new file mode 100644 index 000000000000..351cf989f6f2 --- /dev/null +++ b/drivers/pcmcia/mx31ads-pcmcia.c @@ -0,0 +1,1291 @@ +/*====================================================================== + drivers/pcmcia/mx31ads-pcmica.c + + Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + + Device driver for the PCMCIA control functionality of i.Mx31 + microprocessors. + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is John G. Dorsey + <john+@cs.cmu.edu>. Portions created by John G. Dorsey are + Copyright (C) 1999 John G. Dorsey. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU Public License version 2 (the "GPL"), in which + case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/clk.h> + +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/ss.h> +#include <asm/mach-types.h> +#include <mach/pcmcia.h> +#include <linux/interrupt.h> +#include <linux/delay.h> + +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/spinlock.h> + +#include <mach/hardware.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include "mx31ads-pcmcia.h" +#include <linux/irq.h> + +#define MX31ADS_PCMCIA_IRQ MXC_INT_PCMCIA + +/* + * The mapping of window size to bank size value + */ +static bsize_map_t bsize_map[] = { + /* Window size Bank size */ + {POR_1, POR_BSIZE_1}, + {POR_2, POR_BSIZE_2}, + {POR_4, POR_BSIZE_4}, + {POR_8, POR_BSIZE_8}, + {POR_16, POR_BSIZE_16}, + {POR_32, POR_BSIZE_32}, + {POR_64, POR_BSIZE_64}, + {POR_128, POR_BSIZE_128}, + {POR_256, POR_BSIZE_256}, + {POR_512, POR_BSIZE_512}, + + {POR_1K, POR_BSIZE_1K}, + {POR_2K, POR_BSIZE_2K}, + {POR_4K, POR_BSIZE_4K}, + {POR_8K, POR_BSIZE_8K}, + {POR_16K, POR_BSIZE_16K}, + {POR_32K, POR_BSIZE_32K}, + {POR_64K, POR_BSIZE_64K}, + {POR_128K, POR_BSIZE_128K}, + {POR_256K, POR_BSIZE_256K}, + {POR_512K, POR_BSIZE_512K}, + + {POR_1M, POR_BSIZE_1M}, + {POR_2M, POR_BSIZE_2M}, + {POR_4M, POR_BSIZE_4M}, + {POR_8M, POR_BSIZE_8M}, + {POR_16M, POR_BSIZE_16M}, + {POR_32M, POR_BSIZE_32M}, + {POR_64M, POR_BSIZE_64M} +}; + +#define to_mx31ads_pcmcia_socket(x) container_of(x, struct mx31ads_pcmcia_socket, socket) + +/* mx31ads_pcmcia_find_bsize() + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * Find the bsize according to the window size passed in + * + * Return: + */ +static int mx31ads_pcmcia_find_bsize(unsigned long win_size) +{ + int i, nr = sizeof(bsize_map) / sizeof(bsize_map_t); + int bsize = -1; + + for (i = 0; i < nr; i++) { + if (bsize_map[i].win_size == win_size) { + bsize = bsize_map[i].bsize; + break; + } + } + + pr_debug(KERN_INFO "nr = %d bsize = 0x%0x\n", nr, bsize); + if (bsize < 0 || i > nr) { + pr_debug(KERN_INFO "No such bsize\n"); + return -ENODEV; + } + + return bsize; +} + +/* mx31ads_common_pcmcia_sock_init() + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * (Re-)Initialise the socket, turning on status interrupts + * and PCMCIA bus. This must wait for power to stabilise + * so that the card status signals report correctly. + * + * Returns: 0 + */ +static int mx31ads_common_pcmcia_sock_init(struct pcmcia_socket *sock) +{ + struct mx31ads_pcmcia_socket *skt = to_mx31ads_pcmcia_socket(sock); + + pr_debug(KERN_INFO "initializing socket\n"); + + skt->ops->socket_init(skt); + return 0; +} + +/* + * mx31ads_common_pcmcia_config_skt + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * Convert PCMCIA socket state to our socket configure structure. + */ +static int +mx31ads_common_pcmcia_config_skt(struct mx31ads_pcmcia_socket *skt, + socket_state_t * state) +{ + int ret; + + ret = skt->ops->configure_socket(skt, state); + if (ret == 0) { + /* + * This really needs a better solution. The IRQ + * may or may not be claimed by the driver. + */ + if (skt->irq_state != 1 && state->io_irq) { + skt->irq_state = 1; + set_irq_type(skt->irq, IRQF_TRIGGER_FALLING); + } else if (skt->irq_state == 1 && state->io_irq == 0) { + skt->irq_state = 0; + set_irq_type(skt->irq, IRQF_TRIGGER_RISING); + } + + skt->cs_state = *state; + } + + if (ret < 0) + pr_debug(KERN_ERR "mx31ads_common_pcmcia: unable to configure" + " socket\n"); + + return ret; +} + +/* + * mx31ads_common_pcmcia_suspend() + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * Remove power on the socket, disable IRQs from the card. + * Turn off status interrupts, and disable the PCMCIA bus. + * + * Returns: 0 + */ +static int mx31ads_common_pcmcia_suspend(struct pcmcia_socket *sock) +{ + struct mx31ads_pcmcia_socket *skt = to_mx31ads_pcmcia_socket(sock); + int ret; + + pr_debug(KERN_INFO "suspending socket\n"); + + ret = mx31ads_common_pcmcia_config_skt(skt, &dead_socket); + if (ret == 0) + skt->ops->socket_suspend(skt); + + return ret; +} + +static unsigned int mx31ads_common_pcmcia_skt_state(struct mx31ads_pcmcia_socket + *skt) +{ + struct pcmcia_state state; + unsigned int stat; + + memset(&state, 0, sizeof(struct pcmcia_state)); + + skt->ops->socket_state(skt, &state); + + stat = state.detect ? SS_DETECT : 0; + stat |= state.ready ? SS_READY : 0; + stat |= state.wrprot ? SS_WRPROT : 0; + stat |= state.vs_3v ? SS_3VCARD : 0; + stat |= state.vs_Xv ? SS_XVCARD : 0; + + /* The power status of individual sockets is not available + * explicitly from the hardware, so we just remember the state + * and regurgitate it upon request: + */ + stat |= skt->cs_state.Vcc ? SS_POWERON : 0; + + if (skt->cs_state.flags & SS_IOCARD) + stat |= state.bvd1 ? SS_STSCHG : 0; + else { + if (state.bvd1 == 0) + stat |= SS_BATDEAD; + else if (state.bvd2 == 0) + stat |= SS_BATWARN; + } + + pr_debug(KERN_INFO "stat = 0x%08x\n", stat); + + return stat; +} + +/* + * Implements the get_status() operation for the in-kernel PCMCIA + * service (formerly SS_GetStatus in Card Services). Essentially just + * fills in bits in `status' according to internal driver state or + * the value of the voltage detect chipselect register. + * + * As a debugging note, during card startup, the PCMCIA core issues + * three set_socket() commands in a row the first with RESET deasserted, + * the second with RESET asserted, and the last with RESET deasserted + * again. Following the third set_socket(), a get_status() command will + * be issued. The kernel is looking for the SS_READY flag (see + * setup_socket(), reset_socket(), and unreset_socket() in cs.c). + * + * Returns: 0 + */ +static int mx31ads_common_pcmcia_get_status(struct pcmcia_socket *sock, + unsigned int *status) +{ + struct mx31ads_pcmcia_socket *skt = to_mx31ads_pcmcia_socket(sock); + + skt->status = mx31ads_common_pcmcia_skt_state(skt); + *status = skt->status; + + return 0; +} + +/* + * Implements the set_socket() operation for the in-kernel PCMCIA + * service (formerly SS_SetSocket in Card Services). We more or + * less punt all of this work and let the kernel handle the details + * of power configuration, reset, &c. We also record the value of + * `state' in order to regurgitate it to the PCMCIA core later. + * + * Returns: 0 + */ +static int mx31ads_common_pcmcia_set_socket(struct pcmcia_socket *sock, + socket_state_t * state) +{ + struct mx31ads_pcmcia_socket *skt = to_mx31ads_pcmcia_socket(sock); + + pr_debug(KERN_INFO + "mask: %s%s%s%s%s%sflags: %s%s%s%s%s%sVcc %d Vpp %d irq %d\n", + (state->csc_mask == 0) ? "<NONE> " : "", + (state->csc_mask & SS_DETECT) ? "DETECT " : "", + (state->csc_mask & SS_READY) ? "READY " : "", + (state->csc_mask & SS_BATDEAD) ? "BATDEAD " : "", + (state->csc_mask & SS_BATWARN) ? "BATWARN " : "", + (state->csc_mask & SS_STSCHG) ? "STSCHG " : "", + (state->flags == 0) ? "<NONE> " : "", + (state->flags & SS_PWR_AUTO) ? "PWR_AUTO " : "", + (state->flags & SS_IOCARD) ? "IOCARD " : "", + (state->flags & SS_RESET) ? "RESET " : "", + (state->flags & SS_SPKR_ENA) ? "SPKR_ENA " : "", + (state->flags & SS_OUTPUT_ENA) ? "OUTPUT_ENA " : "", + state->Vcc, state->Vpp, state->io_irq); + + pr_debug(KERN_INFO + "csc_mask: %08x flags: %08x Vcc: %d Vpp: %d io_irq: %d\n", + state->csc_mask, state->flags, state->Vcc, state->Vpp, + state->io_irq); + + return mx31ads_common_pcmcia_config_skt(skt, state); +} + +/* + * Set address and profile to window registers PBR, POR, POFR + */ +static int mx31ads_pcmcia_set_window_reg(ulong start, ulong end, u_int window) +{ + int bsize; + ulong size = end - start + 1; + + bsize = mx31ads_pcmcia_find_bsize(size); + if (bsize < 0) { + pr_debug("Cannot set the window register\n"); + return -1; + } + /* Disable the window */ + _reg_PCMCIA_POR(window) &= ~PCMCIA_POR_PV; + + /* Set PBR, POR, POFR */ + _reg_PCMCIA_PBR(window) = start; + _reg_PCMCIA_POR(window) &= ~(PCMCIA_POR_PRS_MASK + | PCMCIA_POR_WPEN + | PCMCIA_POR_WP + | PCMCIA_POR_BSIZE_MASK + | PCMCIA_POR_PPS_8); + _reg_PCMCIA_POR(window) |= bsize | PCMCIA_POR_PPS_16; + + switch (window) { + case IO_WINDOW: + _reg_PCMCIA_POR(window) |= PCMCIA_POR_PRS(PCMCIA_POR_PRS_IO); + break; + + case ATTRIBUTE_MEMORY_WINDOW: + _reg_PCMCIA_POR(window) |= + PCMCIA_POR_PRS(PCMCIA_POR_PRS_ATTRIBUTE); + break; + + case COMMON_MEMORY_WINDOW: + _reg_PCMCIA_POR(window) |= + PCMCIA_POR_PRS(PCMCIA_POR_PRS_COMMON); + break; + + default: + pr_debug("Window %d is not support\n", window); + return -1; + } + _reg_PCMCIA_POFR(window) = 0; + + /* Enable the window */ + _reg_PCMCIA_POR(window) |= PCMCIA_POR_PV; + + return 0; +} + +/* + * Implements the set_io_map() operation for the in-kernel PCMCIA + * service (formerly SS_SetIOMap in Card Services). We configure + * the map speed as requested, but override the address ranges + * supplied by Card Services. + * + * Returns: 0 on success, -1 on error + */ +static int +mx31ads_common_pcmcia_set_io_map(struct pcmcia_socket *sock, + struct pccard_io_map *map) +{ + struct mx31ads_pcmcia_socket *skt = to_mx31ads_pcmcia_socket(sock); + unsigned short speed = map->speed; + + pr_debug("map %u speed %u start 0x%08x stop 0x%08x\n", + map->map, map->speed, map->start, map->stop); + pr_debug("flags: %s%s%s%s%s%s%s%s\n", + (map->flags == 0) ? "<NONE>" : "", + (map->flags & MAP_ACTIVE) ? "ACTIVE " : "", + (map->flags & MAP_16BIT) ? "16BIT " : "", + (map->flags & MAP_AUTOSZ) ? "AUTOSZ " : "", + (map->flags & MAP_0WS) ? "0WS " : "", + (map->flags & MAP_WRPROT) ? "WRPROT " : "", + (map->flags & MAP_USE_WAIT) ? "USE_WAIT " : "", + (map->flags & MAP_PREFETCH) ? "PREFETCH " : ""); + + if (map->map >= MAX_IO_WIN) { + pr_debug(KERN_ERR "%s(): map (%d) out of range\n", __FUNCTION__, + map->map); + return -1; + } + + if (map->flags & MAP_ACTIVE) { + if (speed == 0) + speed = PCMCIA_IO_ACCESS; + } else { + speed = 0; + } + + skt->spd_io[map->map] = speed; + skt->ops->set_timing(skt); + + if (map->stop == 1) + map->stop = PAGE_SIZE - 1; + + skt->socket.io_offset = (unsigned long)skt->virt_io; + map->stop -= map->start; + map->stop += (unsigned long)skt->virt_io; + map->start = (unsigned long)skt->virt_io; + + mx31ads_pcmcia_set_window_reg(skt->res_io.start, skt->res_io.end, + IO_WINDOW); + + pr_debug(KERN_ERR "IO window: _reg_PCMCIA_PBR(%d) = %08x\n", + IO_WINDOW, _reg_PCMCIA_PBR(IO_WINDOW)); + pr_debug(KERN_ERR "IO window: _reg_PCMCIA_POR(%d) = %08x\n", + IO_WINDOW, _reg_PCMCIA_POR(IO_WINDOW)); + + return 0; +} + +/* + * Implements the set_mem_map() operation for the in-kernel PCMCIA + * service (formerly SS_SetMemMap in Card Services). We configure + * the map speed as requested, but override the address ranges + * supplied by Card Services. + * + * Returns: 0 on success, -1 on error + */ +static int +mx31ads_common_pcmcia_set_mem_map(struct pcmcia_socket *sock, + struct pccard_mem_map *map) +{ + struct mx31ads_pcmcia_socket *skt = to_mx31ads_pcmcia_socket(sock); + struct resource *res; + unsigned short speed = map->speed; + + pr_debug + (KERN_INFO + "map %u speed %u card_start %08x flags%08x static_start %08lx\n", + map->map, map->speed, map->card_start, map->flags, + map->static_start); + pr_debug(KERN_INFO "flags: %s%s%s%s%s%s%s%s\n", + (map->flags == 0) ? "<NONE>" : "", + (map->flags & MAP_ACTIVE) ? "ACTIVE " : "", + (map->flags & MAP_16BIT) ? "16BIT " : "", + (map->flags & MAP_AUTOSZ) ? "AUTOSZ " : "", + (map->flags & MAP_0WS) ? "0WS " : "", + (map->flags & MAP_WRPROT) ? "WRPROT " : "", + (map->flags & MAP_ATTRIB) ? "ATTRIB " : "", + (map->flags & MAP_USE_WAIT) ? "USE_WAIT " : ""); + + if (map->map >= MAX_WIN) + return -EINVAL; + + if (map->flags & MAP_ACTIVE) { + if (speed == 0) + speed = 300; + } else { + speed = 0; + } + + if (map->flags & MAP_ATTRIB) { + res = &skt->res_attr; + skt->spd_attr[map->map] = speed; + skt->spd_mem[map->map] = 0; + mx31ads_pcmcia_set_window_reg(res->start, res->end, + ATTRIBUTE_MEMORY_WINDOW); + + pr_debug(KERN_INFO "Attr window: _reg_PCMCIA_PBR(%d) = %08x\n", + ATTRIBUTE_MEMORY_WINDOW, + _reg_PCMCIA_PBR(ATTRIBUTE_MEMORY_WINDOW)); + pr_debug(KERN_INFO "_reg_PCMCIA_POR(%d) = %08x\n", + ATTRIBUTE_MEMORY_WINDOW, + _reg_PCMCIA_POR(ATTRIBUTE_MEMORY_WINDOW)); + + } else { + res = &skt->res_mem; + skt->spd_attr[map->map] = 0; + skt->spd_mem[map->map] = speed; + mx31ads_pcmcia_set_window_reg(res->start, res->end, + COMMON_MEMORY_WINDOW); + + pr_debug(KERN_INFO "Com window: _reg_PCMCIA_PBR(%d) = %08x\n", + COMMON_MEMORY_WINDOW, + _reg_PCMCIA_PBR(COMMON_MEMORY_WINDOW)); + pr_debug(KERN_INFO "Com window: _reg_PCMCIA_POR(%d) = %08x\n", + COMMON_MEMORY_WINDOW, + _reg_PCMCIA_POR(COMMON_MEMORY_WINDOW)); + } + + skt->ops->set_timing(skt); + + map->static_start = res->start + map->card_start; + + return 0; +} + +static struct pccard_operations mx31ads_common_pcmcia_operations = { + .init = mx31ads_common_pcmcia_sock_init, + .suspend = mx31ads_common_pcmcia_suspend, + .get_status = mx31ads_common_pcmcia_get_status, + .set_socket = mx31ads_common_pcmcia_set_socket, + .set_io_map = mx31ads_common_pcmcia_set_io_map, + .set_mem_map = mx31ads_common_pcmcia_set_mem_map, +}; + +/* ============================================================================== */ + +static inline void mx31ads_pcmcia_irq_config(void) +{ + /* Setup irq */ + _reg_PCMCIA_PER = + (PCMCIA_PER_RDYLE | PCMCIA_PER_CDE1 | PCMCIA_PER_CDE2); +} + +static inline void mx31ads_pcmcia_invalidate_windows(void) +{ + int i; + + for (i = 0; i < PCMCIA_WINDOWS; i++) { + _reg_PCMCIA_PBR(i) = 0; + _reg_PCMCIA_POR(i) = 0; + _reg_PCMCIA_POFR(i) = 0; + } +} + +extern void gpio_pcmcia_active(void); +extern void gpio_pcmcia_inactive(void); + +static int mx31ads_pcmcia_hw_init(struct mx31ads_pcmcia_socket *skt) +{ + /* Configure the pins for PCMCIA */ + gpio_pcmcia_active(); + + /* + * enabling interrupts at this time causes a flood of interrupts + * if a card is present, so wait for configure_socket + * to enable them when requested. + * + * mx31ads_pcmcia_irq_config(); + */ + mx31ads_pcmcia_invalidate_windows(); + + /* Register interrupt. */ + skt->irq = MX31ADS_PCMCIA_IRQ; + + return 0; +} + +static void mx31ads_pcmcia_free_irq(struct mx31ads_pcmcia_socket *skt, + unsigned int irq) +{ + free_irq(irq, skt); +} + +static void mx31ads_pcmcia_hw_shutdown(struct mx31ads_pcmcia_socket *skt) +{ + mx31ads_pcmcia_invalidate_windows(); + mx31ads_pcmcia_free_irq(skt, MX31ADS_PCMCIA_IRQ); + + /* Disable the pins */ + gpio_pcmcia_inactive(); +} + +/* + * Get the socket state + */ +static void +mx31ads_pcmcia_socket_state(struct mx31ads_pcmcia_socket *skt, + struct pcmcia_state *state) +{ + unsigned long pins; + + pins = _reg_PCMCIA_PIPR; + pr_debug(KERN_INFO "_reg_PCMCIA_PIPR = 0x%08lx\n", pins); + + state->ready = (pins & PCMCIA_PIPR_RDY) ? 1 : 0; + state->bvd2 = (pins & PCMCIA_PIPR_BVD2) ? 1 : 0; + state->bvd1 = (pins & PCMCIA_PIPR_BVD1) ? 1 : 0; + + if ((pins & PCMCIA_PIPR_CD) == PCMCIA_PIPR_CD) { + state->detect = 0; + skt->cs_state.csc_mask |= SS_INSERTION; + } else { + state->detect = 1; + } + state->detect = (pins & PCMCIA_PIPR_CD) ? 0 : 1; + state->wrprot = (pins & PCMCIA_PIPR_WP) ? 1 : 0; + state->poweron = (pins & PCMCIA_PIPR_POWERON) ? 1 : 0; +#if 0 + if ((pins & PCMCIA_PIPR_CD) == PCMCIA_PIPR_CD) { + state->detect = 0; + skt->cs_state.csc_mask |= SS_INSERTION; + } else { + state->detect = 1; + } + if (pins & PCMCIA_PIPR_VS_5V) { + state->vs_3v = 0; + skt->cs_state.Vcc = 33; + } else { + state->vs_3v = 1; + skt->cs_state.Vcc = 50; + } +#endif + state->vs_3v = (pins & PCMCIA_PIPR_VS_5V) ? 0 : 1; + state->vs_Xv = 0; +} + +static __inline__ void mx31ads_pcmcia_low_power(bool enable) +{ + if (enable) + _reg_PCMCIA_PGCR |= PCMCIA_PGCR_LPMEN; + else + _reg_PCMCIA_PGCR &= ~PCMCIA_PGCR_LPMEN; +} + +static __inline__ void mx31ads_pcmcia_soft_reset(void) +{ + _reg_PCMCIA_PGCR |= PCMCIA_PGCR_RESET; + msleep(2); + + _reg_PCMCIA_PGCR &= ~(PCMCIA_PGCR_RESET | PCMCIA_PGCR_LPMEN); + _reg_PCMCIA_PGCR |= PCMCIA_PGCR_POE; + msleep(2); + pr_debug(KERN_INFO "_reg_PCMCIA_PGCR = %08x\n", _reg_PCMCIA_PGCR); +} + +static int +mx31ads_pcmcia_configure_socket(struct mx31ads_pcmcia_socket *skt, + const socket_state_t * state) +{ + int ret = 0; + + if (state->Vcc != 0 && state->Vcc != 33 && state->Vcc != 50) { + pr_debug(KERN_ERR "mx31ads-pcmcia: unrecognized Vcc %d\n", + state->Vcc); + return -1; + } + + pr_debug(KERN_INFO "PIPR = %x, desired Vcc = %d.%dV\n", + _reg_PCMCIA_PIPR, state->Vcc / 10, state->Vcc % 10); + + if (!(skt->socket.state & SOCKET_PRESENT) && (skt->pre_stat == 1)) { + pr_debug(KERN_INFO "Socket enter low power mode\n"); + skt->pre_stat = 0; + mx31ads_pcmcia_low_power(1); + } + + if (state->flags & SS_RESET) { + mx31ads_pcmcia_soft_reset(); + + /* clean out previous tenant's trash */ + _reg_PCMCIA_PGSR = (PCMCIA_PGSR_NWINE + | PCMCIA_PGSR_LPE + | PCMCIA_PGSR_SE + | PCMCIA_PGSR_CDE | PCMCIA_PGSR_WPE); + } + /* enable interrupts if requested, else turn 'em off */ + if (skt->irq) + mx31ads_pcmcia_irq_config(); + else + _reg_PCMCIA_PER = 0; + + if (skt->socket.state & SOCKET_PRESENT) { + skt->pre_stat = 1; + } + return ret; +} + +static void mx31ads_pcmcia_enable_irq(struct mx31ads_pcmcia_socket *skt, + unsigned int irq) +{ + set_irq_type(irq, IRQF_TRIGGER_RISING); + set_irq_type(irq, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING); +} + +static void mx31ads_pcmcia_disable_irq(struct mx31ads_pcmcia_socket *skt, + unsigned int irq) +{ + set_irq_type(irq, IRQF_TRIGGER_NONE); +} + +/* + * Enable card status IRQs on (re-)initialisation. This can + * be called at initialisation, power management event, or + * pcmcia event. + */ +static void mx31ads_pcmcia_socket_init(struct mx31ads_pcmcia_socket *skt) +{ + mx31ads_pcmcia_soft_reset(); + + mx31ads_pcmcia_enable_irq(skt, MX31ADS_PCMCIA_IRQ); +} + +/* + * Disable card status IRQ on suspend. + */ +static void mx31ads_pcmcia_socket_suspend(struct mx31ads_pcmcia_socket *skt) +{ + mx31ads_pcmcia_disable_irq(skt, MX31ADS_PCMCIA_IRQ); + mx31ads_pcmcia_low_power(1); +} + +/* ==================================================================================== */ + +/* + * PCMCIA strobe hold time + */ +static inline u_int mx31ads_pcmcia_por_psht(u_int pcmcia_cycle_ns, + u_int hclk_cycle_ns) +{ + u_int psht; + + return psht = pcmcia_cycle_ns / hclk_cycle_ns; +} + +/* + * PCMCIA strobe set up time + */ +static inline u_int mx31ads_pcmcia_por_psst(u_int pcmcia_cycle_ns, + u_int hclk_cycle_ns) +{ + u_int psst; + + return psst = pcmcia_cycle_ns / hclk_cycle_ns; +} + +/* + * PCMCIA strobe length time + */ +static inline u_int mx31ads_pcmcia_por_pslt(u_int pcmcia_cycle_ns, + u_int hclk_cycle_ns) +{ + u_int pslt; + + return pslt = pcmcia_cycle_ns / hclk_cycle_ns + 2; +} + +/* + * mx31ads_pcmcia_default_mecr_timing + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * Calculate MECR clock wait states for given CPU clock + * speed and command wait state. This function can be over- + * written by a board specific version. + * + * The default is to simply calculate the BS values as specified in + * the INTEL SA1100 development manual + * "Expansion Memory (PCMCIA) Configuration Register (MECR)" + * that's section 10.2.5 in _my_ version of the manual ;) + */ +static unsigned int mx31ads_pcmcia_default_mecr_timing(struct + mx31ads_pcmcia_socket + *skt, + unsigned int cpu_speed, + unsigned int cmd_time) +{ + return 0; +} + +/* + * Calculate the timing code + */ +static u_int mx31ads_pcmcia_cal_code(u_int speed_ns, u_int clk_ns) +{ + u_int code; + + code = PCMCIA_POR_PSHT(mx31ads_pcmcia_por_psht(speed_ns, clk_ns)) + | PCMCIA_POR_PSST(mx31ads_pcmcia_por_psst(speed_ns, clk_ns)) + | PCMCIA_POR_PSL(mx31ads_pcmcia_por_pslt(speed_ns, clk_ns)); + + return code; +} + +/* + * set MECR value for socket <sock> based on this sockets + * io, mem and attribute space access speed. + * Call board specific BS value calculation to allow boards + * to tweak the BS values. + */ +static int mx31ads_pcmcia_set_window_timing(u_int speed_ns, u_int window, + u_int clk_ns) +{ + u_int code = 0; + + switch (window) { + case IO_WINDOW: + code = mx31ads_pcmcia_cal_code(speed_ns, clk_ns); + break; + case COMMON_MEMORY_WINDOW: + code = mx31ads_pcmcia_cal_code(speed_ns, clk_ns); + break; + case ATTRIBUTE_MEMORY_WINDOW: + code = mx31ads_pcmcia_cal_code(speed_ns, clk_ns); + break; + default: + break; + } + + /* Disable the window */ + _reg_PCMCIA_POR(window) &= ~PCMCIA_POR_PV; + + /* Clear the register fisrt */ + _reg_PCMCIA_POR(window) &= ~(PCMCIA_POR_PSST_MASK + | PCMCIA_POR_PSL_MASK + | PCMCIA_POR_PSHT_MASK); + /* And then set the register */ + _reg_PCMCIA_POR(window) |= code; + + /* Enable the window */ + _reg_PCMCIA_POR(window) |= PCMCIA_POR_PV; + + return 0; +} + +static unsigned short calc_speed(unsigned short *spds, int num, + unsigned short dflt) +{ + unsigned short speed = 0; + int i; + + for (i = 0; i < num; i++) + if (speed < spds[i]) + speed = spds[i]; + if (speed == 0) + speed = dflt; + + return speed; +} + +static void +mx31ads_common_pcmcia_get_timing(struct mx31ads_pcmcia_socket *skt, + struct mx31ads_pcmcia_timing *timing) +{ + timing->io = calc_speed(skt->spd_io, MAX_IO_WIN, PCMCIA_IO_ACCESS); + timing->mem = calc_speed(skt->spd_mem, MAX_WIN, PCMCIA_3V_MEM_ACCESS); + timing->attr = + calc_speed(skt->spd_attr, MAX_WIN, PCMCIA_ATTR_MEM_ACCESS); +} + +static int mx31ads_pcmcia_set_timing(struct mx31ads_pcmcia_socket *skt) +{ + u_int clk_ns; + struct mx31ads_pcmcia_timing timing; + + /* How many nanoseconds */ + clk_ns = (1000 * 1000 * 1000) / clk_get_rate(skt->clk); + pr_debug(KERN_INFO "clk_ns = %d\n", clk_ns); + + mx31ads_common_pcmcia_get_timing(skt, &timing); + pr_debug(KERN_INFO "timing: io %d, mem %d, attr %d\n", timing.io, + timing.mem, timing.attr); + + mx31ads_pcmcia_set_window_timing(timing.io, IO_WINDOW, clk_ns); + mx31ads_pcmcia_set_window_timing(timing.mem, COMMON_MEMORY_WINDOW, + clk_ns); + mx31ads_pcmcia_set_window_timing(timing.attr, ATTRIBUTE_MEMORY_WINDOW, + clk_ns); + + return 0; +} + +static int mx31ads_pcmcia_show_timing(struct mx31ads_pcmcia_socket *skt, + char *buf) +{ + return 0; +} + +static struct pcmcia_low_level mx31ads_pcmcia_ops = { + .owner = THIS_MODULE, + .hw_init = mx31ads_pcmcia_hw_init, + .hw_shutdown = mx31ads_pcmcia_hw_shutdown, + .socket_state = mx31ads_pcmcia_socket_state, + .configure_socket = mx31ads_pcmcia_configure_socket, + + .socket_init = mx31ads_pcmcia_socket_init, + .socket_suspend = mx31ads_pcmcia_socket_suspend, + + .get_timing = mx31ads_pcmcia_default_mecr_timing, + .set_timing = mx31ads_pcmcia_set_timing, + .show_timing = mx31ads_pcmcia_show_timing, +}; + +/* =================================================================================== */ + +LIST_HEAD(mx31ads_pcmcia_sockets); +DECLARE_MUTEX(mx31ads_pcmcia_sockets_lock); + +static DEFINE_SPINLOCK(status_lock); + +struct bittbl { + unsigned int mask; + const char *name; +}; + +static struct bittbl status_bits[] = { + {SS_WRPROT, "SS_WRPROT"}, + {SS_BATDEAD, "SS_BATDEAD"}, + {SS_BATWARN, "SS_BATWARN"}, + {SS_READY, "SS_READY"}, + {SS_DETECT, "SS_DETECT"}, + {SS_POWERON, "SS_POWERON"}, + {SS_STSCHG, "SS_STSCHG"}, + {SS_3VCARD, "SS_3VCARD"}, + {SS_XVCARD, "SS_XVCARD"}, +}; + +static struct bittbl conf_bits[] = { + {SS_PWR_AUTO, "SS_PWR_AUTO"}, + {SS_IOCARD, "SS_IOCARD"}, + {SS_RESET, "SS_RESET"}, + {SS_DMA_MODE, "SS_DMA_MODE"}, + {SS_SPKR_ENA, "SS_SPKR_ENA"}, + {SS_OUTPUT_ENA, "SS_OUTPUT_ENA"}, +}; + +static void +dump_bits(char **p, const char *prefix, unsigned int val, struct bittbl *bits, + int sz) +{ + char *b = *p; + int i; + + b += sprintf(b, "%-9s:", prefix); + for (i = 0; i < sz; i++) + if (val & bits[i].mask) + b += sprintf(b, " %s", bits[i].name); + *b++ = '\n'; + *p = b; +} + +/* + * Implements the /sys/class/pcmcia_socket/??/status file. + * + * Returns: the number of characters added to the buffer + */ +static ssize_t show_status(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct mx31ads_pcmcia_socket *skt = + container_of(dev, struct mx31ads_pcmcia_socket, socket.dev); + char *p = buf; + + p += sprintf(p, "slot : %d\n", skt->nr); + + dump_bits(&p, "status", skt->status, + status_bits, ARRAY_SIZE(status_bits)); + dump_bits(&p, "csc_mask", skt->cs_state.csc_mask, + status_bits, ARRAY_SIZE(status_bits)); + dump_bits(&p, "cs_flags", skt->cs_state.flags, + conf_bits, ARRAY_SIZE(conf_bits)); + + p += sprintf(p, "Vcc : %d\n", skt->cs_state.Vcc); + p += sprintf(p, "Vpp : %d\n", skt->cs_state.Vpp); + p += sprintf(p, "IRQ : %d (%d)\n", skt->cs_state.io_irq, skt->irq); + if (skt->ops->show_timing) + p += skt->ops->show_timing(skt, p); + + return p - buf; +} + +static DEVICE_ATTR(status, S_IRUGO, show_status, NULL); + +static void mx31ads_common_check_status(struct mx31ads_pcmcia_socket *skt) +{ + unsigned int events; + + pr_debug(KERN_INFO "entering PCMCIA monitoring thread\n"); + + do { + unsigned int status; + unsigned long flags; + + status = mx31ads_common_pcmcia_skt_state(skt); + + spin_lock_irqsave(&status_lock, flags); + events = (status ^ skt->status) & skt->cs_state.csc_mask; + skt->status = status; + spin_unlock_irqrestore(&status_lock, flags); + + pr_debug(KERN_INFO "events: %s%s%s%s%s%s\n", + events == 0 ? "<NONE>" : "", + events & SS_DETECT ? "DETECT " : "", + events & SS_READY ? "READY " : "", + events & SS_BATDEAD ? "BATDEAD " : "", + events & SS_BATWARN ? "BATWARN " : "", + events & SS_STSCHG ? "STSCHG " : ""); + + if (events) + pcmcia_parse_events(&skt->socket, events); + } while (events); +} + +/* + * Service routine for socket driver interrupts (requested by the + * low-level PCMCIA init() operation via mx31ads_common_pcmcia_thread()). + * The actual interrupt-servicing work is performed by + * mx31ads_common_pcmcia_thread(), largely because the Card Services event- + * handling code performs scheduling operations which cannot be + * executed from within an interrupt context. + */ +static irqreturn_t mx31ads_common_pcmcia_interrupt(int irq, void *dev) +{ + struct mx31ads_pcmcia_socket *skt = dev; + volatile u32 pscr, pgsr; + + dev_dbg(dev, "servicing IRQ %d\n", irq); + + /* clear interrupt states */ + pscr = _reg_PCMCIA_PSCR; + _reg_PCMCIA_PSCR = pscr; + + pgsr = _reg_PCMCIA_PGSR; + _reg_PCMCIA_PGSR = pgsr; + + mx31ads_common_check_status(skt); + + return IRQ_HANDLED; +} + +/* Let's poll for events in addition to IRQs since IRQ only is unreliable... */ +static void mx31ads_common_pcmcia_poll_event(unsigned long dummy) +{ + struct mx31ads_pcmcia_socket *skt = + (struct mx31ads_pcmcia_socket *)dummy; + pr_debug(KERN_INFO "polling for events\n"); + + mod_timer(&skt->poll_timer, jiffies + PCMCIA_POLL_PERIOD); + + mx31ads_common_check_status(skt); +} + +#define mx31ads_pcmcia_cpufreq_register() +#define mx31ads_pcmcia_cpufreq_unregister() + +static int mx31ads_common_drv_pcmcia_probe(struct platform_device *pdev, + struct pcmcia_low_level *ops) +{ + struct mx31ads_pcmcia_socket *skt; + int vs, value, ret; + struct pccard_io_map map; + + down(&mx31ads_pcmcia_sockets_lock); + + skt = kzalloc(sizeof(struct mx31ads_pcmcia_socket), GFP_KERNEL); + if (!skt) { + ret = -ENOMEM; + goto out; + } + + /* + * Initialise the socket structure. + */ + skt->socket.ops = &mx31ads_common_pcmcia_operations; + skt->socket.owner = ops->owner; + skt->socket.driver_data = skt; + + init_timer(&skt->poll_timer); + skt->poll_timer.function = mx31ads_common_pcmcia_poll_event; + skt->poll_timer.data = (unsigned long)skt; + skt->poll_timer.expires = jiffies + PCMCIA_POLL_PERIOD; + + skt->irq = MX31ADS_PCMCIA_IRQ; + skt->socket.dev.parent = &pdev->dev; + skt->ops = ops; + + skt->clk = clk_get(NULL, "ahb_clk"); + + skt->res_skt.start = _PCMCIA(0); + skt->res_skt.end = _PCMCIA(0) + PCMCIASp - 1; + skt->res_skt.name = MX31ADS_PCMCIA; + skt->res_skt.flags = IORESOURCE_MEM; + + ret = request_resource(&iomem_resource, &skt->res_skt); + if (ret) + goto out_err_1; + + skt->res_io.start = _PCMCIAIO(0); + skt->res_io.end = _PCMCIAIO(0) + PCMCIAIOSp - 1; + skt->res_io.name = "io"; + skt->res_io.flags = IORESOURCE_MEM | IORESOURCE_BUSY; + + ret = request_resource(&skt->res_skt, &skt->res_io); + if (ret) + goto out_err_2; + + skt->res_mem.start = _PCMCIAMem(0); + skt->res_mem.end = _PCMCIAMem(0) + PCMCIAMemSp - 1; + skt->res_mem.name = "memory"; + skt->res_mem.flags = IORESOURCE_MEM; + + ret = request_resource(&skt->res_skt, &skt->res_mem); + if (ret) + goto out_err_3; + + skt->res_attr.start = _PCMCIAAttr(0); + skt->res_attr.end = _PCMCIAAttr(0) + PCMCIAAttrSp - 1; + skt->res_attr.name = "attribute"; + skt->res_attr.flags = IORESOURCE_MEM; + + ret = request_resource(&skt->res_skt, &skt->res_attr); + if (ret) + goto out_err_4; + + skt->virt_io = ioremap(skt->res_io.start, 0x10000); + if (skt->virt_io == NULL) { + ret = -ENOMEM; + goto out_err_5; + } + + if (list_empty(&mx31ads_pcmcia_sockets)) + mx31ads_pcmcia_cpufreq_register(); + + list_add(&skt->node, &mx31ads_pcmcia_sockets); + + /* + * We initialize default socket timing here, because + * we are not guaranteed to see a SetIOMap operation at + * runtime. + */ + ops->set_timing(skt); + + ret = ops->hw_init(skt); + if (ret) + goto out_err_6; + + ret = request_irq(skt->irq, mx31ads_common_pcmcia_interrupt, + IRQF_SHARED | IRQF_DISABLED, "PCMCIA IRQ", skt); + if (ret) + goto out_err_6; + + skt->socket.features = SS_CAP_STATIC_MAP | SS_CAP_PCCARD; + skt->socket.resource_ops = &pccard_static_ops; + skt->socket.irq_mask = 0; + skt->socket.map_size = PCMCIAPrtSp; + skt->socket.pci_irq = skt->irq; + skt->socket.io_offset = (unsigned long)skt->virt_io; + + skt->status = mx31ads_common_pcmcia_skt_state(skt); + skt->pre_stat = 0; + ret = pcmcia_register_socket(&skt->socket); + if (ret) + goto out_err_7; + /* FIXED ME workaround for binding with ide-cs. ide usage io port 0x100~0x107 and 0x10e */ + map.map = 0; + map.flags = MAP_ACTIVE | MAP_16BIT; + map.start = 0; + map.stop = PCMCIAIOSp - 1; + map.speed = 0; + mx31ads_common_pcmcia_set_io_map(&skt->socket, &map); + + vs = _reg_PCMCIA_PIPR & PCMCIA_PIPR_VS; + value = vs & PCMCIA_PIPR_VS_5V ? 50 : 33; + dev_dbg(&pdev->dev, "PCMCIA: Voltage the card supports: %d.%dV\n", + value / 10, value % 10); + + add_timer(&skt->poll_timer); + + ret = device_create_file(&skt->socket.dev, &dev_attr_status); + if (ret < 0) + goto out_err_8; + + platform_set_drvdata(pdev, skt); + ret = 0; + goto out; + + out_err_8: + del_timer_sync(&skt->poll_timer); + pcmcia_unregister_socket(&skt->socket); + + out_err_7: + flush_scheduled_work(); + free_irq(skt->irq, skt); + ops->hw_shutdown(skt); + out_err_6: + list_del(&skt->node); + iounmap(skt->virt_io); + out_err_5: + release_resource(&skt->res_attr); + out_err_4: + release_resource(&skt->res_mem); + out_err_3: + release_resource(&skt->res_io); + out_err_2: + release_resource(&skt->res_skt); + out_err_1: + + kfree(skt); + out: + up(&mx31ads_pcmcia_sockets_lock); + return ret; +} + +static int mx31ads_drv_pcmcia_remove(struct platform_device *pdev) +{ + struct mx31ads_pcmcia_socket *skt = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + + down(&mx31ads_pcmcia_sockets_lock); + + del_timer_sync(&skt->poll_timer); + + pcmcia_unregister_socket(&skt->socket); + + flush_scheduled_work(); + + skt->ops->hw_shutdown(skt); + + mx31ads_common_pcmcia_config_skt(skt, &dead_socket); + + list_del(&skt->node); + iounmap(skt->virt_io); + skt->virt_io = NULL; + release_resource(&skt->res_attr); + release_resource(&skt->res_mem); + release_resource(&skt->res_io); + release_resource(&skt->res_skt); + + if (list_empty(&mx31ads_pcmcia_sockets)) + mx31ads_pcmcia_cpufreq_unregister(); + + up(&mx31ads_pcmcia_sockets_lock); + + kfree(skt); + + return 0; +} + +static int mx31ads_drv_pcmcia_probe(struct platform_device *pdev) +{ + if (!machine_is_mx31ads()) + return -ENODEV; + + return mx31ads_common_drv_pcmcia_probe(pdev, &mx31ads_pcmcia_ops); +} + +static int mx31ads_drv_pcmcia_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return pcmcia_socket_dev_suspend(&pdev->dev, state); +} + +static int mx31ads_drv_pcmcia_resume(struct platform_device *pdev) +{ + return pcmcia_socket_dev_resume(&pdev->dev); +} + +/* + * Low level functions + */ +static struct platform_driver mx31ads_pcmcia_driver = { + .driver = { + .name = MX31ADS_PCMCIA, + }, + .probe = mx31ads_drv_pcmcia_probe, + .remove = mx31ads_drv_pcmcia_remove, + .suspend = mx31ads_drv_pcmcia_suspend, + .resume = mx31ads_drv_pcmcia_resume, +}; + +/* mx31ads_pcmcia_init() + * + */ +static int __init mx31ads_pcmcia_init(void) +{ + int ret; + + if ((ret = platform_driver_register(&mx31ads_pcmcia_driver))) + return ret; + pr_debug(KERN_INFO "PCMCIA: Initialize i.Mx31 pcmcia socket\n"); + + return ret; +} + +/* mx31ads_pcmcia_exit() + * + */ +static void __exit mx31ads_pcmcia_exit(void) +{ + platform_driver_unregister(&mx31ads_pcmcia_driver); +} + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX31 PCMCIA Socket Controller"); +MODULE_LICENSE("GPL"); + +module_init(mx31ads_pcmcia_init); +module_exit(mx31ads_pcmcia_exit); diff --git a/drivers/pcmcia/mx31ads-pcmcia.h b/drivers/pcmcia/mx31ads-pcmcia.h new file mode 100644 index 000000000000..3f7014b3e8bf --- /dev/null +++ b/drivers/pcmcia/mx31ads-pcmcia.h @@ -0,0 +1,155 @@ +/* + * linux/drivers/pcmcia/mx31ads-pcmcia.h + * + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This file contains definitions for the PCMCIA support code common to + * integrated SOCs like the i.Mx31 microprocessors. + */ +#ifndef _ASM_ARCH_PCMCIA +#define _ASM_ARCH_PCMCIA + +/* include the world */ +#include <linux/cpufreq.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/ss.h> +#include <pcmcia/cistpl.h> +#include "cs_internal.h" + +#define MX31ADS_PCMCIA "Mx31ads_pcmcia_socket" + +struct device; +struct pcmcia_low_level; + +/* + * This structure encapsulates per-socket state which we might need to + * use when responding to a Card Services query of some kind. + */ +struct mx31ads_pcmcia_socket { + struct pcmcia_socket socket; + + /* + * Info from low level handler + */ + struct device *dev; + unsigned int nr; + unsigned int irq; + + struct clk *clk; + + /* + * Core PCMCIA state + */ + struct pcmcia_low_level *ops; + + unsigned int status; + unsigned int pre_stat; + socket_state_t cs_state; + + unsigned short spd_io[MAX_IO_WIN]; + unsigned short spd_mem[MAX_WIN]; + unsigned short spd_attr[MAX_WIN]; + + struct resource res_skt; + struct resource res_io; + struct resource res_mem; + struct resource res_attr; + void *virt_io; + + unsigned int irq_state; + + struct timer_list poll_timer; + struct list_head node; +}; + +struct pcmcia_state { + unsigned detect:1, + ready:1, bvd1:1, bvd2:1, wrprot:1, vs_3v:1, vs_Xv:1, poweron:1; +}; + +struct pcmcia_low_level { + struct module *owner; + + /* first socket in system */ + int first; + /* nr of sockets */ + int nr; + + int (*hw_init) (struct mx31ads_pcmcia_socket *); + void (*hw_shutdown) (struct mx31ads_pcmcia_socket *); + + void (*socket_state) (struct mx31ads_pcmcia_socket *, + struct pcmcia_state *); + int (*configure_socket) (struct mx31ads_pcmcia_socket *, + const socket_state_t *); + + /* + * Enable card status IRQs on (re-)initialisation. This can + * be called at initialisation, power management event, or + * pcmcia event. + */ + void (*socket_init) (struct mx31ads_pcmcia_socket *); + + /* + * Disable card status IRQs and PCMCIA bus on suspend. + */ + void (*socket_suspend) (struct mx31ads_pcmcia_socket *); + + /* + * Hardware specific timing routines. + * If provided, the get_timing routine overrides the SOC default. + */ + unsigned int (*get_timing) (struct mx31ads_pcmcia_socket *, + unsigned int, unsigned int); + int (*set_timing) (struct mx31ads_pcmcia_socket *); + int (*show_timing) (struct mx31ads_pcmcia_socket *, char *); + +#ifdef CONFIG_CPU_FREQ + /* + * CPUFREQ support. + */ + int (*frequency_change) (struct mx31ads_pcmcia_socket *, unsigned long, + struct cpufreq_freqs *); +#endif +}; + +struct mx31ads_pcmcia_timing { + unsigned short io; + unsigned short mem; + unsigned short attr; +}; + +typedef struct { + ulong win_size; + int bsize; +} bsize_map_t; + +/* + * The PC Card Standard, Release 7, section 4.13.4, says that twIORD + * has a minimum value of 165ns. Section 4.13.5 says that twIOWR has + * a minimum value of 165ns, as well. Section 4.7.2 (describing + * common and attribute memory write timing) says that twWE has a + * minimum value of 150ns for a 250ns cycle time (for 5V operation; + * see section 4.7.4), or 300ns for a 600ns cycle time (for 3.3V + * operation, also section 4.7.4). Section 4.7.3 says that taOE + * has a maximum value of 150ns for a 300ns cycle time (for 5V + * operation), or 300ns for a 600ns cycle time (for 3.3V operation). + * + * When configuring memory maps, Card Services appears to adopt the policy + * that a memory access time of "0" means "use the default." The default + * PCMCIA I/O command width time is 165ns. The default PCMCIA 5V attribute + * and memory command width time is 150ns; the PCMCIA 3.3V attribute and + * memory command width time is 300ns. + */ +#define PCMCIA_IO_ACCESS (165) +#define PCMCIA_5V_MEM_ACCESS (150) +#define PCMCIA_3V_MEM_ACCESS (300) +#define PCMCIA_ATTR_MEM_ACCESS (300) + +/* + * The socket driver actually works nicely in interrupt-driven form, + * so the (relatively infrequent) polling is "just to be sure." + */ +#define PCMCIA_POLL_PERIOD (2*HZ) +#endif diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index bdbc4f73fcdc..e7b1040b410e 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -103,4 +103,11 @@ config CHARGER_PCF50633 help Say Y to include support for NXP PCF50633 Main Battery Charger. +config BATTERY_STMP3XXX + tristate "Sigmatel STMP3xxx SoC battery charger driver" + depends on ARCH_STMP3XXX + help + Say Y to enable support for the battery charger state machine + for the Sigmatel STMP3xxx based SoC's. + endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 380d17c9ae29..0d6b10db79b7 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -28,3 +28,4 @@ obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o +obj-$(CONFIG_BATTERY_STMP3XXX) += stmp37xx/ diff --git a/drivers/power/stmp37xx/Makefile b/drivers/power/stmp37xx/Makefile new file mode 100644 index 000000000000..25b340e5b014 --- /dev/null +++ b/drivers/power/stmp37xx/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the STMP3xxx battery charger driver +# + +obj-$(CONFIG_BATTERY_STMP3XXX) += stmp3xxx-battery.o + +stmp3xxx-battery-objs := ddi_bc_api.o ddi_bc_hw.o ddi_bc_init.o \ + ddi_bc_ramp.o ddi_bc_sm.o ddi_power_battery.o linux.o diff --git a/drivers/power/stmp37xx/ddi_bc_api.c b/drivers/power/stmp37xx/ddi_bc_api.c new file mode 100644 index 000000000000..5fd062ec4eb9 --- /dev/null +++ b/drivers/power/stmp37xx/ddi_bc_api.c @@ -0,0 +1,566 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +//////////////////////////////////////////////////////////////////////////////// +//! \addtogroup ddi_bc +//! @{ +// +// Copyright (c) 2004-2005 SigmaTel, Inc. +// +//! \file ddi_bc_api.c +//! \brief Contains the Battery Charger API. +//! \date 06/2005 +//! +//! This file contains Battery Charger API. +//! +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////// + +#include "ddi_bc_internal.h" + +//////////////////////////////////////////////////////////////////////////////// +// Variables +//////////////////////////////////////////////////////////////////////////////// + +//! This structure holds the current Battery Charger configuration. + +ddi_bc_Cfg_t g_ddi_bc_Configuration; + +extern uint32_t g_ddi_bc_u32StateTimer; +extern ddi_bc_BrokenReason_t ddi_bc_gBrokenReason; +extern bool bRestartChargeCycle; + +//////////////////////////////////////////////////////////////////////////////// +// Code +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the Battery Charger configuration. +//! +//! \fntype Function +//! +//! This function reports the Battery Charger configuration. +//! +//! Note that, if the Battery Charger has not yet been initialized, the data +//! returned by this function is unknown. +//! +//! \param[in,out] pCfg A pointer to a structure that will receive the data. +//! +//////////////////////////////////////////////////////////////////////////////// +void ddi_bc_QueryCfg(ddi_bc_Cfg_t * pCfg) +{ + + //-------------------------------------------------------------------------- + // Return the current configuration. + //-------------------------------------------------------------------------- + + *pCfg = g_ddi_bc_Configuration; + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Shut down the Battery Charger. +//! +//! \fntype Function +//! +//! This function immediately shuts down the Battery Charger hardware and +//! returns the state machine to the Uninitialized state. Use this function to +//! safely “mummify” the battery charger before retiring it from memory. +//! +//////////////////////////////////////////////////////////////////////////////// +void ddi_bc_ShutDown() +{ + + //-------------------------------------------------------------------------- + // Reset the current ramp. + //-------------------------------------------------------------------------- + + ddi_bc_RampReset(); + + //-------------------------------------------------------------------------- + // Move to the Uninitialized state. + //-------------------------------------------------------------------------- + + g_ddi_bc_State = DDI_BC_STATE_UNINITIALIZED; + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Advances the state machine. +//! +//! \fntype Function +//! +//! This function advances the state machine. +//! +//! \retval DDI_BC_STATUS_SUCCESS If all goes well +//! \retval DDI_BC_STATUS_NOT_INITIALIZED If the Battery Charger is not yet +//! initialized. +//! \retval DDI_BC_STATUS_BROKEN If the battery violated a time-out +//! and has been declared broken. +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_Status_t ddi_bc_StateMachine() +{ + + //-------------------------------------------------------------------------- + // Check if we've been initialized yet. + //-------------------------------------------------------------------------- + + if (g_ddi_bc_State == DDI_BC_STATE_UNINITIALIZED) { + return (DDI_BC_STATUS_NOT_INITIALIZED); + } + //-------------------------------------------------------------------------- + // Execute the function for the current state. + //-------------------------------------------------------------------------- + + return (stateFunctionTable[g_ddi_bc_State] ()); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Get the Battery Charger's current state. +//! +//! \fntype Function +//! +//! This function returns the current state. +//! +//! \retval The current state. +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_State_t ddi_bc_GetState() +{ + //-------------------------------------------------------------------------- + // Return the current state. + //-------------------------------------------------------------------------- + + return (g_ddi_bc_State); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Disable the Battery Charger. +//! +//! \fntype Function +//! +//! This function forces the Battery Charger into the Disabled state. +//! +//! \retval DDI_BC_STATUS_SUCCESS If all goes well +//! \retval DDI_BC_STATUS_NOT_INITIALIZED If the Battery Charger is not yet +//! initialized. +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_Status_t ddi_bc_SetDisable() +{ + + //-------------------------------------------------------------------------- + // Check if we've been initialized yet. + //-------------------------------------------------------------------------- + + if (g_ddi_bc_State == DDI_BC_STATE_UNINITIALIZED) { + return (DDI_BC_STATUS_NOT_INITIALIZED); + } + //-------------------------------------------------------------------------- + // Check if we've been initialized yet. + //-------------------------------------------------------------------------- + + if (g_ddi_bc_State == DDI_BC_STATE_BROKEN) { + return (DDI_BC_STATUS_BROKEN); + } + //-------------------------------------------------------------------------- + // Reset the current ramp. This will jam the current to zero and power off + // the charging hardware. + //-------------------------------------------------------------------------- + + ddi_bc_RampReset(); + + //-------------------------------------------------------------------------- + // Reset the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer = 0; + + //-------------------------------------------------------------------------- + // Move to the Disabled state. + //-------------------------------------------------------------------------- + + g_ddi_bc_State = DDI_BC_STATE_DISABLED; + + //-------------------------------------------------------------------------- + // Return success. + //-------------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Enable the Battery Charger. +//! +//! \fntype Function +//! +//! If the Battery Charger is in the Disabled state, this function moves it to +//! the Waiting to Charge state. +//! +//! \retval DDI_BC_STATUS_SUCCESS If all goes well +//! \retval DDI_BC_STATUS_NOT_INITIALIZED If the Battery Charger is not yet +//! initialized. +//! \retval DDI_BC_STATUS_NOT_DISABLED If the Battery Charger is not +//! disabled. +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_Status_t ddi_bc_SetEnable() +{ + + //-------------------------------------------------------------------------- + // Check if we've been initialized yet. + //-------------------------------------------------------------------------- + + if (g_ddi_bc_State == DDI_BC_STATE_UNINITIALIZED) { + return (DDI_BC_STATUS_NOT_INITIALIZED); + } + //-------------------------------------------------------------------------- + // If we're not in the Disabled state, this is pointless. + //-------------------------------------------------------------------------- + + if (g_ddi_bc_State != DDI_BC_STATE_DISABLED) { + return (DDI_BC_STATUS_NOT_DISABLED); + } + //-------------------------------------------------------------------------- + // Reset the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer = 0; + //-------------------------------------------------------------------------- + // Move to the Waiting to Charge state. + //-------------------------------------------------------------------------- + + g_ddi_bc_State = DDI_BC_STATE_WAITING_TO_CHARGE; + + //-------------------------------------------------------------------------- + // Return success. + //-------------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Declare the battery to be broken. +//! +//! \fntype Function +//! +//! This function forces the Battery Charger into the Broken state. +//! +//! \retval DDI_BC_STATUS_SUCCESS If all goes well +//! \retval DDI_BC_STATUS_NOT_INITIALIZED If the Battery Charger is not yet +//! initialized. +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_Status_t ddi_bc_SetBroken() +{ + + //-------------------------------------------------------------------------- + // Check if we've been initialized yet. + //-------------------------------------------------------------------------- + + if (g_ddi_bc_State == DDI_BC_STATE_UNINITIALIZED) { + return (DDI_BC_STATUS_NOT_INITIALIZED); + } + //-------------------------------------------------------------------------- + // Reset the current ramp. This will jam the current to zero and power off + // the charging hardware. + //-------------------------------------------------------------------------- + + ddi_bc_RampReset(); + + //-------------------------------------------------------------------------- + // Reset the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer = 0; + + //-------------------------------------------------------------------------- + // Move to the Broken state. + //-------------------------------------------------------------------------- + + ddi_bc_gBrokenReason = DDI_BC_BROKEN_CHARGING_TIMEOUT; + + g_ddi_bc_State = DDI_BC_STATE_BROKEN; + + //-------------------------------------------------------------------------- + // Return success. + //-------------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Declare the battery to be fixed. +//! +//! \fntype Function +//! +//! If the Battery Charger is in the Broken state, this function moves it to +//! the Disabled state. +//! +//! \retval DDI_BC_STATUS_SUCCESS If all goes well +//! \retval DDI_BC_STATUS_NOT_INITIALIZED If the Battery Charger is not yet +//! initialized. +//! \retval DDI_BC_STATUS_NOT_BROKEN If the Battery Charger is not broken. +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_Status_t ddi_bc_SetFixed() +{ + + //-------------------------------------------------------------------------- + // Check if we've been initialized yet. + //-------------------------------------------------------------------------- + + if (g_ddi_bc_State == DDI_BC_STATE_UNINITIALIZED) { + return (DDI_BC_STATUS_NOT_INITIALIZED); + } + //-------------------------------------------------------------------------- + // If we're not in the Broken state, this is pointless. + //-------------------------------------------------------------------------- + + if (g_ddi_bc_State != DDI_BC_STATE_BROKEN) { + return (DDI_BC_STATUS_NOT_BROKEN); + } + //-------------------------------------------------------------------------- + // Reset the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer = 0; + + //-------------------------------------------------------------------------- + // Unitialize the Broken Reason + //-------------------------------------------------------------------------- + + ddi_bc_gBrokenReason = DDI_BC_BROKEN_UNINITIALIZED; + + //-------------------------------------------------------------------------- + // Move to the Disabled state. + //-------------------------------------------------------------------------- + + g_ddi_bc_State = DDI_BC_STATE_DISABLED; + + //-------------------------------------------------------------------------- + // Return success. + //-------------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Set the current limit. +//! +//! \fntype Function +//! +//! This function applies a limit to the current that the Battery Charger can +//! draw. +//! +//! \param[in] u16Limit The maximum current the Battery Charger can draw +//! (in mA). +//! +//! \retval The expressible version of the limit. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_bc_SetCurrentLimit(uint16_t u16Limit) +{ + + //-------------------------------------------------------------------------- + // Set the limit and return what is actually expressible. + //-------------------------------------------------------------------------- + + return (ddi_bc_RampSetLimit(u16Limit)); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the current limit. +//! +//! \fntype Function +//! +//! This function reports the limit to the current that the Battery Charger can +//! draw. +//! +//! \retval The current limit. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_bc_GetCurrentLimit(void) +{ + + //-------------------------------------------------------------------------- + // Set the limit and return what is actually expressible. + //-------------------------------------------------------------------------- + + return (ddi_bc_RampGetLimit()); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Set the battery charger state machine period. +//! +//! \fntype Function +//! +//! This function sets a new state machine period. The Period and Slope should +//! be coordinated to achieve the minimal ramp step current which will minimize +//! transients on the system. +//! +//! \param[in] u32StateMachinePeriod (in milliseconds) +//! \param[in] u16CurrentRampSlope (in mA/s) +//! +//! \retval SUCCESS If all goes well +//! \retval ERROR_DDI_BCM_NOT_INITIALIZED If the Battery Charger is not yet +//! initialized. +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_Status_t ddi_bc_SetNewPeriodAndSlope(uint32_t u32StateMachinePeriod, + uint16_t u16CurrentRampSlope) +{ + //-------------------------------------------------------------------------- + // Check if we've been initialized yet. + //-------------------------------------------------------------------------- + bool bDisableRequired; + + if (g_ddi_bc_State == DDI_BC_STATE_UNINITIALIZED) { + return (DDI_BC_STATUS_NOT_INITIALIZED); + } + + if (g_ddi_bc_State == DDI_BC_STATE_DISABLED) + bDisableRequired = false; + else { + bDisableRequired = true; + ddi_bc_SetDisable(); + } + + // Looking at the code, changing the period while the battery charger is running + // doesn't seem to have a negative affect. One could wrap this in the mutex + // or implement further coordination if it did. + g_ddi_bc_Configuration.u32StateMachinePeriod = u32StateMachinePeriod; + g_ddi_bc_Configuration.u16CurrentRampSlope = u16CurrentRampSlope; + + if (bDisableRequired) + ddi_bc_SetEnable(); + + return (DDI_BC_STATUS_SUCCESS); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the state machine period. +//! +//! \fntype Function +//! +//! This function reports the battery charger period. +//! +//! \retval The battery charger period (in milliseconds). +//! +//////////////////////////////////////////////////////////////////////////////// +uint32_t ddi_bc_GetStateMachinePeriod() +{ + return (g_ddi_bc_Configuration.u32StateMachinePeriod); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the current ramp slope. +//! +//! \fntype Function +//! +//! This function reports the current ramp slope. +//! +//! \retval The current ramp slope (in mA/s). +//! +//////////////////////////////////////////////////////////////////////////////// +uint32_t ddi_bc_GetCurrentRampSlope() +{ + return (g_ddi_bc_Configuration.u16CurrentRampSlope); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the time spent in the present state (milliseconds) +//! +//! \fntype Function +//! +//! This function reports the time spent in the present charging state. Note that +//! for the states that actually charge the battery, this time does not include the +//! time spent under alarm conditions such as die termperature alarm or battery +//! temperature alarm. +//! +//! \retval The time spent in the current state in milliseconds. +//! +//////////////////////////////////////////////////////////////////////////////// +uint32_t ddi_bc_GetStateTime(void) +{ + return g_ddi_bc_u32StateTimer; +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the reason for being in the broken state +//! +//! \fntype Function +//! +//! +//! \retval ddi_bc_BrokenReason_t enumeration +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_BrokenReason_t ddi_bc_GetBrokenReason(void) +{ + return ddi_bc_gBrokenReason; +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Restart the charge cycle +//! +//! \fntype Function +//! +//! +//! \retval SUCCESS +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_Status_t ddi_bc_ForceChargingToStart(void) +{ + static int16_t restarts = 0; + + if (restarts < DDI_BC_MAX_RESTART_CYCLES) { + restarts++; + bRestartChargeCycle = true; + } + + return (DDI_BC_STATUS_SUCCESS); +} + +//////////////////////////////////////////////////////////////////////////////// +// End of file +//////////////////////////////////////////////////////////////////////////////// +//! @} diff --git a/drivers/power/stmp37xx/ddi_bc_hw.c b/drivers/power/stmp37xx/ddi_bc_hw.c new file mode 100644 index 000000000000..fab82a738b01 --- /dev/null +++ b/drivers/power/stmp37xx/ddi_bc_hw.c @@ -0,0 +1,474 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include "ddi_bc_internal.h" + +//////////////////////////////////////////////////////////////////////////////// +//! \addtogroup ddi_bc +//! @{ +// +// Copyright (c) 2004-2005 SigmaTel, Inc. +// +//! \file ddi_bc_hw.c +//! \brief Contains the Battery Charger hardware operations. +//! \date 06/2005 +//! +//! This file contains Battery Charger hardware operations. +//! +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Includes and external references +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Variables +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Code +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report if the battery charging hardware is available. +//! +//! \fntype Function +//! +//! This function reports if the battery charging hardware is available by +//! reading the corresponding laser fuse bit. +//! +//! \retval Zero if the battery charging hardware is not available. Non-zero +//! otherwise. +//! +//////////////////////////////////////////////////////////////////////////////// +int ddi_bc_hwBatteryChargerIsEnabled(void) +{ + //TODO: replace ddi_bc_hwBatteryChargerIsEnabled with the function below in the code + return (int)ddi_power_GetBatteryChargerEnabled(); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the battery configuration. +//! +//! \fntype Function +//! +//! This function reports the hardware battery configuration. +//! +//! \retval A value that indicates the battery configuration. +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_BatteryMode_t ddi_bc_hwGetBatteryMode(void) +{ + //TODO: replace ddi_bc_hwGetBatteryMode() with the function below. + return (ddi_bc_BatteryMode_t) ddi_power_GetBatteryMode(); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the bias current source. +//! +//! \fntype Function +//! +//! This function Battery Charger. +//! +//! \retval A value that indicates the current bias current source. +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_BiasCurrentSource_t ddi_bc_hwGetBiasCurrentSource(void) +{ + // TODO: replace ddi_bc_BiasCurrentSource_t() with the function below. + return (ddi_bc_BiasCurrentSource_t) ddi_power_GetBiasCurrentSource(); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Sets the bias current source. +//! +//! \fntype Function +//! +//! This function sets the bias current source used by the battery charger +//! hardware. The battery charger is usually configured to use an externally +//! generated bias current, which is expected to be quite precise. To reduce +//! component count, for example, it can be configured to generate a lesser- +//! quality bias current internally. +//! +//! \param[in] source Indicates the source of the bias current. +//! +//! \retval DDI_BC_STATUS_SUCCESS If the operation succeeded. +//! \retval DDI_BC_STATUS_BAD_ARGUMENT If the given source argument isn't one +//! of the expected values. +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_Status_t ddi_bc_hwSetBiasCurrentSource(ddi_bc_BiasCurrentSource_t source) +{ + ddi_power_BiasCurrentSource_t eSource = + (ddi_power_BiasCurrentSource_t) source; + + //-------------------------------------------------------------------------- + // In the following code, there are two points that can be confusing. Both + // of these problems derive from silliness in the data sheet. + // + // The field of interest here is HW_POWER_BATTCHRG.USE_EXTERN_R. This is + // poorly named for two reasons: + // + // 1) What we're really doing is selecting the source of the bias current. + // If the application chooses to use an external bias current, then + // there will, of course, be a resistor involved (apparently, a good- + // quality one at about 620 Ohms). + // + // 2) If the bit is SET, that tells the hardware to use the INTERNAL bias + // current. If this bit is CLEAR, that tells the hardware to use the + // EXTERNAL bias current. Thus, the actual logic for this bit is + // reversed from the sense indicated by its name. + //-------------------------------------------------------------------------- + + // TODO: replace all ddi_bc_hwSetBiasCurrentSource() with function below. + return (ddi_bc_Status_t) ddi_power_SetBiasCurrentSource(eSource); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the voltage across the battery. +//! +//! \fntype Function +//! +//! This function reports the voltage across the battery. +//! +//! \retval The voltage across the battery, in mV. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_bc_hwGetBatteryVoltage(void) +{ + //TODO: replace ddi_bc_hwGetBattery with function below + return ddi_power_GetBattery(); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report on the presence of the power supply. +//! +//! \fntype Function +//! +//! This function repots on whether or not the 5V power supply is present. +//! +//! \retval Zero if the power supply is not present. Non-zero otherwise. +//! +//////////////////////////////////////////////////////////////////////////////// +int ddi_bc_hwPowerSupplyIsPresent(void) +{ + // TODO: replace ddi_bc_hwPowerSupplyIsPresent with the functino below. + return (int)ddi_power_Get5vPresentFlag(); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the maximum charging current. +//! +//! \fntype Function +//! +//! This function reports the maximum charging current that will be offered to +//! the battery, as currently set in the hardware. +//! +//! \retval The maximum current setting in the hardware. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_bc_hwGetMaxCurrent(void) +{ + // TODO: replace ddi_bc_hwGetMaxCurrent() with the below function + return (uint16_t) ddi_power_GetMaxBatteryChargeCurrent(); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Set the maximum charging current. +//! +//! \fntype Function +//! +//! This function sets the maximum charging current that will be offered to the +//! battery. +//! +//! Note that the hardware has a minimum resolution of 10mA and a maximum +//! expressible value of 780mA (see the data sheet for details). If the given +//! current cannot be expressed exactly, then the largest expressible smaller +//! value will be used. The return reports the actual value that was effected. +//! +//! \param[in] u16Limit The maximum charging current, in mA. +//! +//! \retval The actual value that was effected. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_bc_hwSetMaxCurrent(uint16_t u16Limit) +{ + //TODO: replace ddi_bc_hwSetMaxChargeCurrent + return ddi_power_SetMaxBatteryChargeCurrent(u16Limit); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Set the charging current threshold. +//! +//! \fntype Function +//! +//! This function sets the charging current threshold. When the actual current +//! flow to the battery is less than this threshold, the HW_POWER_STS.CHRGSTS +//! flag is clear. +//! +//! Note that the hardware has a minimum resolution of 10mA and a maximum +//! expressible value of 180mA (see the data sheet for details). If the given +//! current cannot be expressed exactly, then the largest expressible smaller +//! value will be used. The return reports the actual value that was effected. +//! +//! \param[in] u16Threshold The charging current threshold, in mA. +//! +//! \retval The actual value that was effected. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_bc_hwSetCurrentThreshold(uint16_t u16Threshold) +{ + //TODO: replace calls to ddi_bc_hwSetCurrentThreshold with the one below + return ddi_power_SetBatteryChargeCurrentThreshold(u16Threshold); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the charging current threshold. +//! +//! \fntype Function +//! +//! This function reports the charging current threshold. When the actual +//! current flow to the battery is less than this threshold, the +//! HW_POWER_STS.CHRGSTS flag is clear. +//! +//! Note that the hardware has a minimum resolution of 10mA and a maximum +//! expressible value of 180mA (see the data sheet for details). +//! +//! \retval The charging current threshold, in mA. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_bc_hwGetCurrentThreshold(void) +{ + //TODO: replace calls to ddi_bc_hwGetCurrentThreshold with function below + return ddi_power_GetBatteryChargeCurrentThreshold(); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report if the charger hardware power is on. +//! +//! \fntype Function +//! +//! This function reports if the charger hardware power is on. +//! +//! \retval Zero if the charger hardware is not powered. Non-zero otherwise. +//! +//////////////////////////////////////////////////////////////////////////////// +int ddi_bc_hwChargerPowerIsOn(void) +{ + + //-------------------------------------------------------------------------- + // Note that the bit we're looking at is named PWD_BATTCHRG. The "PWD" + // stands for "power down". Thus, when the bit is set, the battery charger + // hardware is POWERED DOWN. + //-------------------------------------------------------------------------- + + //-------------------------------------------------------------------------- + // Read the register and return the result. + //-------------------------------------------------------------------------- + + //TODO: replace ddi_bc_hwChargerPowerIsOn with function below + return ddi_power_GetChargerPowered(); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Turn the charging hardware on or off. +//! +//! \fntype Function +//! +//! This function turns the charging hardware on or off. +//! +//! \param[in] on Indicates whether the charging hardware should be on or off. +//! +//////////////////////////////////////////////////////////////////////////////// +void ddi_bc_hwSetChargerPower(int on) +{ + + //-------------------------------------------------------------------------- + // Note that the bit we're looking at is named PWD_BATTCHRG. The "PWD" + // stands for "power down". Thus, when the bit is set, the battery charger + // hardware is POWERED DOWN. + //-------------------------------------------------------------------------- + + //-------------------------------------------------------------------------- + // Hit the power switch. + //-------------------------------------------------------------------------- + + //TODO: replace ddi_bc_hwSetChargerPower with functino below + ddi_power_SetChargerPowered(on); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Reports if the charging current has fallen below the threshold. +//! +//! \fntype Function +//! +//! This function reports if the charging current that the battery is accepting +//! has fallen below the threshold. +//! +//! Note that this bit is regarded by the hardware guys as very slightly +//! unreliable. They recommend that you don't believe a value of zero until +//! you've sampled it twice. +//! +//! \retval Zero if the battery is accepting less current than indicated by the +//! charging threshold. Non-zero otherwise. +//! +//////////////////////////////////////////////////////////////////////////////// +int ddi_bc_hwGetChargeStatus(void) +{ + return ddi_power_GetChargeStatus(); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report on the die temperature. +//! +//! \fntype Function +//! +//! This function reports on the die temperature. +//! +//! \param[out] pLow The low end of the temperature range. +//! \param[out] pHigh The high end of the temperature range. +//! +//////////////////////////////////////////////////////////////////////////////// +void ddi_bc_hwGetDieTemp(int16_t * pLow, int16_t * pHigh) +{ + // TODO: replace ddi_bc_hwGetDieTemp with function below + ddi_power_GetDieTemp(pLow, pHigh); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the battery temperature reading. +//! +//! \fntype Function +//! +//! This function examines the configured LRADC channel and reports the battery +//! temperature reading. +//! +//! \param[out] pReading A pointer to a variable that will receive the +//! temperature reading. +//! +//! \retval DDI_BC_STATUS_SUCCESS If the operation succeeded. +//! \retval DDI_BC_STATUS_NOT_INITIALIZED If the Battery Charger is not yet +//! initialized. +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_Status_t ddi_bc_hwGetBatteryTemp(uint16_t * pReading) +{ + return (ddi_bc_Status_t)DDI_BC_STATUS_HARDWARE_DISABLED; +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Convert a current in mA to a hardware setting. +//! +//! \fntype Function +//! +//! This function converts a current measurement in mA to a hardware setting +//! used by HW_POWER_BATTCHRG.STOP_ILIMIT or HW_POWER_BATTCHRG.BATTCHRG_I. +//! +//! Note that the hardware has a minimum resolution of 10mA and a maximum +//! expressible value of 780mA (see the data sheet for details). If the given +//! current cannot be expressed exactly, then the largest expressible smaller +//! value will be used. +//! +//! \param[in] u16Current The current of interest. +//! +//! \retval The corresponding setting. +//! +//////////////////////////////////////////////////////////////////////////////// +uint8_t ddi_bc_hwCurrentToSetting(uint16_t u16Current) +{ + return ddi_power_convert_current_to_setting(u16Current); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Convert a hardware current setting to a value in mA. +//! +//! \fntype Function +//! +//! This function converts a setting used by HW_POWER_BATTCHRG.STOP_ILIMIT or +//! HW_POWER_BATTCHRG.BATTCHRG_I into an actual current measurement in mA. +//! +//! Note that the hardware current fields are 6 bits wide. The higher bits in +//! the 8-bit input parameter are ignored. +//! +//! \param[in] u8Setting A hardware current setting. +//! +//! \retval The corresponding current in mA. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_bc_hwSettingToCurrent(uint8_t u8Setting) +{ + return ddi_power_convert_setting_to_current(u8Setting); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Compute the actual current expressible in the hardware. +//! +//! \fntype Function +//! +//! Given a desired current, this function computes the actual current +//! expressible in the hardware. +//! +//! Note that the hardware has a minimum resolution of 10mA and a maximum +//! expressible value of 780mA (see the data sheet for details). If the given +//! current cannot be expressed exactly, then the largest expressible smaller +//! value will be used. +//! +//! \param[in] u16Current The current of interest. +//! +//! \retval The corresponding current in mA. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_bc_hwExpressibleCurrent(uint16_t u16Current) +{ + //TODO: replace the bc function with this one + return ddi_power_ExpressibleCurrent(u16Current); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Checks to see if the DCDC has been manually enabled +//! +//! \fntype Function +//! +//! \retval true if DCDC is ON, false if DCDC is OFF. +//! +//////////////////////////////////////////////////////////////////////////////// +bool ddi_bc_hwIsDcdcOn(void) +{ + return ddi_power_IsDcdcOn(); +} + +//////////////////////////////////////////////////////////////////////////////// +// End of file +//////////////////////////////////////////////////////////////////////////////// +//! @} diff --git a/drivers/power/stmp37xx/ddi_bc_hw.h b/drivers/power/stmp37xx/ddi_bc_hw.h new file mode 100644 index 000000000000..68ef98aa4303 --- /dev/null +++ b/drivers/power/stmp37xx/ddi_bc_hw.h @@ -0,0 +1,93 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +//////////////////////////////////////////////////////////////////////////////// +//! \addtogroup ddi_bc +//! @{ +// +// Copyright (c) 2004-2005 SigmaTel, Inc. +// +//! \file ddi_bc_hw.h +//! \brief Internal header file for Battery Charger hardware operations. +//! \date 06/2005 +//! +//! This file contains internal declarations for Battery Charger hardware +//! operations. +//! +//! \see ddi_bc.c and related files. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _DDI_BC_HW_H +#define _DDI_BC_HW_H + +//////////////////////////////////////////////////////////////////////////////// +// Definitions +//////////////////////////////////////////////////////////////////////////////// + +//! The enumeration of battery modes. + +typedef enum _ddi_bc_BatteryMode { + DDI_BC_BATTERY_MODE_LI_ION_2_CELLS = 0, + DDI_BC_BATTERY_MODE_LI_ION_1_CELL = 1, + DDI_BC_BATTERY_MODE_2_CELLS = 2, + DDI_BC_BATTERY_MODE_1_CELL = 3 +} ddi_bc_BatteryMode_t; + +//! The enumeration of bias current sources. + +typedef enum _ddi_bc_BiasCurrentSource { + DDI_BC_EXTERNAL_BIAS_CURRENT = 0, + DDI_BC_INTERNAL_BIAS_CURRENT = 1, +} ddi_bc_BiasCurrentSource_t; + +//////////////////////////////////////////////////////////////////////////////// +// Prototypes +//////////////////////////////////////////////////////////////////////////////// + +extern int ddi_bc_hwBatteryChargerIsEnabled(void); +extern ddi_bc_BatteryMode_t ddi_bc_hwGetBatteryMode(void); +extern ddi_bc_BiasCurrentSource_t ddi_bc_hwGetBiasCurrentSource(void); +extern ddi_bc_Status_t +ddi_bc_hwSetBiasCurrentSource(ddi_bc_BiasCurrentSource_t); +extern ddi_bc_Status_t ddi_bc_hwSetChargingVoltage(uint16_t); +extern uint16_t ddi_bc_hwGetBatteryVoltage(void); +extern int ddi_bc_hwPowerSupplyIsPresent(void); +extern uint16_t ddi_bc_hwSetMaxCurrent(uint16_t); +extern uint16_t ddi_bc_hwGetMaxCurrent(void); +extern uint16_t ddi_bc_hwSetCurrentThreshold(uint16_t); +extern uint16_t ddi_bc_hwGetCurrentThreshold(void); +extern int ddi_bc_hwChargerPowerIsOn(void); +extern void ddi_bc_hwSetChargerPower(int); +extern int ddi_bc_hwGetChargeStatus(void); +extern void ddi_bc_hwGetDieTemp(int16_t *, int16_t *); +extern ddi_bc_Status_t ddi_bc_hwGetBatteryTemp(uint16_t *); +uint8_t ddi_bc_hwCurrentToSetting(uint16_t); +uint16_t ddi_bc_hwSettingToCurrent(uint8_t); +uint16_t ddi_bc_hwExpressibleCurrent(uint16_t); + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Checks to see if the DCDC has been manually enabled +//! +//! \fntype Function +//! +//! \retval true if DCDC is ON, false if DCDC is OFF. +//! +//////////////////////////////////////////////////////////////////////////////// +bool ddi_bc_hwIsDcdcOn(void); + +//////////////////////////////////////////////////////////////////////////////// +// End of file +//////////////////////////////////////////////////////////////////////////////// +#endif // _DDI_BC_H +//! @} diff --git a/drivers/power/stmp37xx/ddi_bc_init.c b/drivers/power/stmp37xx/ddi_bc_init.c new file mode 100644 index 000000000000..ef323b33b74f --- /dev/null +++ b/drivers/power/stmp37xx/ddi_bc_init.c @@ -0,0 +1,204 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include "ddi_bc_internal.h" + +//////////////////////////////////////////////////////////////////////////////// +//! \addtogroup ddi_bc +//! @{ +// +// Copyright (c) 2004-2005 SigmaTel, Inc. +// +//! \file ddi_bc_init.c +//! \brief Contains the Battery Charger initialization function. +//! \date 06/2005 +//! +//! This file contains Battery Charger initialization function. +//! +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Includes and external references +//////////////////////////////////////////////////////////////////////////////// +#include <mach/ddi_bc.h> +#include "ddi_bc_internal.h" + +//////////////////////////////////////////////////////////////////////////////// +// Code +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +//! \brief Initialize the Battery Charger. +//! +//! \fntype Function +//! +//! This function initializes the Battery Charger. +//! +//! \param[in] pCfg A pointer to the new configuration. +//! +//! \retval DDI_BC_STATUS_SUCCESS +//! If the operation succeeded. +//! \retval DDI_BC_STATUS_ALREADY_INITIALIZED +//! If the Battery Charger is already initialized. +//! \retval DDI_BC_STATUS_HARDWARE_DISABLED +//! If the Battery Charger hardware is disabled by a laser fuse. +//! \retval DDI_BC_STATUS_BAD_BATTERY_MODE +//! If the power supply is set up for a non-rechargeable battery. +//! \retval DDI_BC_STATUS_CLOCK_GATE_CLOSED +//! If the clock gate for the power supply registers is closed. +//! \retval DDI_BC_STATUS_CFG_BAD_CHARGING_VOLTAGE +//! If the charging voltage is not either 4100 or 4200. +//! \retval DDI_BC_STATUS_CFG_BAD_BATTERY_TEMP_CHANNEL +//! If the LRADC channel number for monitoring battery temperature +//! is bad. +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_Status_t ddi_bc_Init(ddi_bc_Cfg_t * pCfg) +{ + + //-------------------------------------------------------------------------- + // We can only be initialized if we're in the Uninitialized state. + //-------------------------------------------------------------------------- + + if (g_ddi_bc_State != DDI_BC_STATE_UNINITIALIZED) { + return (DDI_BC_STATUS_ALREADY_INITIALIZED); + } + //-------------------------------------------------------------------------- + // Check if the battery charger hardware has been disabled by laser fuse. + //-------------------------------------------------------------------------- + + if (!ddi_power_GetBatteryChargerEnabled()) + return (DDI_BC_STATUS_HARDWARE_DISABLED); + + //-------------------------------------------------------------------------- + // Check if the power supply has been set up for a non-rechargeable battery. + //-------------------------------------------------------------------------- + + switch (ddi_power_GetBatteryMode()) { + + case DDI_POWER_BATT_MODE_LIION: + break; + + // TODO: we'll need to do NiMH also + default: + return (DDI_BC_STATUS_BAD_BATTERY_MODE); + //break; + + } + + //-------------------------------------------------------------------------- + // Make sure that the clock gate has been opened for the power supply + // registers. If not, then none of our writes to those registers will + // succeed, which will kind of slow us down... + //-------------------------------------------------------------------------- + + if (ddi_power_GetPowerClkGate()) { + return (DDI_BC_STATUS_CLOCK_GATE_CLOSED); + } + //-------------------------------------------------------------------------- + // Check the incoming configuration for nonsense. + //-------------------------------------------------------------------------- + + // + // Only permitted charging voltage: 4200mV. + // + + if (pCfg->u16ChargingVoltage != DDI_BC_LIION_CHARGING_VOLTAGE) { + return (DDI_BC_STATUS_CFG_BAD_CHARGING_VOLTAGE); + } + // + // There are 8 LRADC channels. + // + + if (pCfg->u8BatteryTempChannel > 7) { + return (DDI_BC_STATUS_CFG_BAD_BATTERY_TEMP_CHANNEL); + } + //-------------------------------------------------------------------------- + // Accept the configuration. + //-------------------------------------------------------------------------- + + //-------------------------------------------------------------------------- + // ddi_bc_Cfg_t.u16ChargingThresholdCurrent is destined for the + // register field HW_POWER_BATTCHRG.STOP_ILIMIT. This 4-bit field + // is unevenly quantized to provide a useful range of currents. A + // side effect of the quantization is that the field can only be + // set to certain unevenly-spaced values. + // + // Here, we use the two functions that manipulate the register field + // to adjust u16ChargingThresholdCurrent to match the quantized value. + //-------------------------------------------------------------------------- + pCfg->u16ChargingThresholdCurrent = + ddi_power_ExpressibleCurrent(pCfg->u16ChargingThresholdCurrent); + + //-------------------------------------------------------------------------- + // ...similar situation with ddi_bc_Cfg_t.u16BatteryTempSafeCurrent and + // u16DieTempSafeCurrent. + //-------------------------------------------------------------------------- + pCfg->u16BatteryTempSafeCurrent = + ddi_power_ExpressibleCurrent(pCfg->u16BatteryTempSafeCurrent); + pCfg->u16DieTempSafeCurrent = + ddi_power_ExpressibleCurrent(pCfg->u16DieTempSafeCurrent); + + g_ddi_bc_Configuration = *pCfg; + + //-------------------------------------------------------------------------- + // Set the bias current source. + //-------------------------------------------------------------------------- + + { + ddi_power_BiasCurrentSource_t Flag; + + Flag = + g_ddi_bc_Configuration.useInternalBias ? + DDI_POWER_INTERNAL_BIAS_CURRENT : + DDI_POWER_EXTERNAL_BIAS_CURRENT; + + ddi_power_SetBiasCurrentSource(Flag); + + } + + //-------------------------------------------------------------------------- + // Turn the charger hardware off. This is a very important initial condition + // because we only flip the power switch on the hardware when we make + // transitions. Baseline, it needs to be off. + //-------------------------------------------------------------------------- + + ddi_power_SetChargerPowered(0); + + //-------------------------------------------------------------------------- + // Reset the current ramp. This will jam the current to zero and power off + // the charging hardware. + //-------------------------------------------------------------------------- + + ddi_bc_RampReset(); + + //-------------------------------------------------------------------------- + // Move to the Disabled state. + //-------------------------------------------------------------------------- + + g_ddi_bc_State = DDI_BC_STATE_DISABLED; + + //-------------------------------------------------------------------------- + // Return success. + //-------------------------------------------------------------------------- +#ifdef CONFIG_POWER_SUPPLY_DEBUG + printk("%s: success\n", __func__); +#endif + return (DDI_BC_STATUS_SUCCESS); + +} + +//////////////////////////////////////////////////////////////////////////////// +// End of file +//////////////////////////////////////////////////////////////////////////////// +//! @} diff --git a/drivers/power/stmp37xx/ddi_bc_internal.h b/drivers/power/stmp37xx/ddi_bc_internal.h new file mode 100644 index 000000000000..c1fb1b20f271 --- /dev/null +++ b/drivers/power/stmp37xx/ddi_bc_internal.h @@ -0,0 +1,52 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +//////////////////////////////////////////////////////////////////////////////// +//! \addtogroup ddi_bc +//! @{ +// +// Copyright (c) 2004-2005 SigmaTel, Inc. +// +//! \file ddi_bc_internal.h +//! \brief Internal header file for the Battery Charger device driver. +//! \date 06/2005 +//! +//! This file contains internal declarations for the Battery Charger device +//! driver. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _DDI_BC_INTERNAL_H +#define _DDI_BC_INTERNAL_H + +//////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////// + +#include <mach/ddi_bc.h> +#include "ddi_bc_hw.h" +#include "ddi_bc_ramp.h" +#include "ddi_bc_sm.h" +#include "ddi_power_battery.h" + +//////////////////////////////////////////////////////////////////////////////// +// Externs +//////////////////////////////////////////////////////////////////////////////// + +extern bool g_ddi_bc_Configured; +extern ddi_bc_Cfg_t g_ddi_bc_Configuration; + +//////////////////////////////////////////////////////////////////////////////// +// End of file +//////////////////////////////////////////////////////////////////////////////// +#endif // _DDI_BC_H +//! @} diff --git a/drivers/power/stmp37xx/ddi_bc_ramp.c b/drivers/power/stmp37xx/ddi_bc_ramp.c new file mode 100644 index 000000000000..1de3a53259f8 --- /dev/null +++ b/drivers/power/stmp37xx/ddi_bc_ramp.c @@ -0,0 +1,724 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +//////////////////////////////////////////////////////////////////////////////// +//! \addtogroup ddi_bc +//! @{ +// +// Copyright (c) 2004-2005 SigmaTel, Inc. +// +//! \file ddi_bc_ramp.c +//! \brief Contains the Battery Charger current ramp controller. +//! \date 06/2005 +//! +//! This file contains Battery Charger current ramp controller. +//! +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Includes and external references +//////////////////////////////////////////////////////////////////////////////// + +#include <mach/ddi_bc.h> +#include "ddi_bc_internal.h" + +//////////////////////////////////////////////////////////////////////////////// +// Definitions +//////////////////////////////////////////////////////////////////////////////// + +//! This is the control structure for the current ramp. + +typedef struct _ddi_bc_RampControl { + + uint32_t u32AccumulatedTime; + + //!< The accumulated time since we last changed the actual + //!< current setting in the hardware. If the time between + //!< steps is quite short, we may have to wait for several steps + //!< before we can actually change the hardware setting. + + uint16_t u16Target; + + //!< The target current, regardless of expressibility. + + uint16_t u16Limit; + + //!< The current limit, regardless of expressibility. + + uint8_t dieTempAlarm:1; + + //!< Indicates if we are operating under a die temperature + //!< alarm. + + uint8_t batteryTempAlarm:1; + + //!< Indicates if we are operating under a battery temperature + //!< alarm. + + uint8_t ambientTempAlarm:1; + + //!< Indicates if we are operating under an ambient temperature + //!< alarm. + +} ddi_bc_RampControl_t; + +//////////////////////////////////////////////////////////////////////////////// +// Variables +//////////////////////////////////////////////////////////////////////////////// + +//! This structure contains control information for the current ramp. + +static ddi_bc_RampControl_t g_RampControl; + +//////////////////////////////////////////////////////////////////////////////// +// Code +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Reset the current ramp. +//! +//! \fntype Function +//! +//! This function resets the current ramp. +//! +//! Note that this function does NOT reset the temperature alarms or the current +//! limit. Those can only be changed explicitly. +//! +//////////////////////////////////////////////////////////////////////////////// +void ddi_bc_RampReset() +{ + + //-------------------------------------------------------------------------- + // Reset the control structure. + //-------------------------------------------------------------------------- + + g_RampControl.u32AccumulatedTime = 0; + g_RampControl.u16Target = 0; + + //-------------------------------------------------------------------------- + // Step the ramp. Note that we don't care if this function returns an error. + // We're stepping the ramp to make sure it takes immediate effect, if + // possible. But, for example, if the Battery Charger is not yet + // initialized, it doesn't matter. + //-------------------------------------------------------------------------- + + ddi_bc_RampStep(0); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Set the target current. +//! +//! \fntype Function +//! +//! This function sets the target current and implements it immediately. +//! +//! Note that this function does NOT reset the temperature alarms. Those can +//! only be reset explicitly. +//! +//! \param[in] u16Target The target current. +//! +//! \retval The expressible version of the target. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_bc_RampSetTarget(uint16_t u16Target) +{ + + //-------------------------------------------------------------------------- + // Set the target. + //-------------------------------------------------------------------------- + + g_RampControl.u16Target = u16Target; + + //-------------------------------------------------------------------------- + // Step the ramp. Note that we don't care if this function returns an error. + // We're stepping the ramp to make sure it takes immediate effect, if + // possible. But, for example, if the Battery Charger is not yet + // initialized, it doesn't matter. + //-------------------------------------------------------------------------- + + ddi_bc_RampStep(0); + + //-------------------------------------------------------------------------- + // Compute and return the expressible target. + //-------------------------------------------------------------------------- + + return (ddi_bc_hwExpressibleCurrent(u16Target)); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the target. +//! +//! \fntype Function +//! +//! This function reports the target. +//! +//! \retval The target. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_bc_RampGetTarget(void) +{ + + //-------------------------------------------------------------------------- + // Return the target. + //-------------------------------------------------------------------------- + + return (g_RampControl.u16Target); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Set the current limit. +//! +//! \fntype Function +//! +//! This function sets the current limit and implements it immediately. +//! +//! \param[in] u16Limit The current limit. +//! +//! \retval The expressible version of the limit. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_bc_RampSetLimit(uint16_t u16Limit) +{ + + //-------------------------------------------------------------------------- + // Set the limit. + //-------------------------------------------------------------------------- + + g_RampControl.u16Limit = u16Limit; + + //-------------------------------------------------------------------------- + // Step the ramp. Note that we don't care if this function returns an error. + // We're stepping the ramp to make sure it takes immediate effect, if + // possible. But, for example, if the Battery Charger is not yet + // initialized, it doesn't matter. + //-------------------------------------------------------------------------- + + ddi_bc_RampStep(0); + + //-------------------------------------------------------------------------- + // Compute and return the expressible limit. + //-------------------------------------------------------------------------- + + return (ddi_bc_hwExpressibleCurrent(u16Limit)); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the current limit. +//! +//! \fntype Function +//! +//! This function reports the current limit. +//! +//! \retval The current limit. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_bc_RampGetLimit(void) +{ + + //-------------------------------------------------------------------------- + // Return the current limit. + //-------------------------------------------------------------------------- + + return (g_RampControl.u16Limit); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Update alarms. +//! +//! \fntype Function +//! +//! This function checks for all alarms and updates the current ramp +//! accordingly. +//! +//////////////////////////////////////////////////////////////////////////////// +void ddi_bc_RampUpdateAlarms() +{ + + // Set to true if something changed and we need to step the ramp right away. + + int iStepTheRamp = 0; + + //-------------------------------------------------------------------------- + // Are we monitoring die temperature? + //-------------------------------------------------------------------------- + + if (g_ddi_bc_Configuration.monitorDieTemp) { + + //---------------------------------------------------------------------- + // Get the die temperature range. + //---------------------------------------------------------------------- + + int16_t i16Low; + int16_t i16High; + + ddi_bc_hwGetDieTemp(&i16Low, &i16High); + + //---------------------------------------------------------------------- + // Now we need to decide if it's time to raise or lower the alarm. The + // first question to ask is: Were we already under an alarm? + //---------------------------------------------------------------------- + + if (g_RampControl.dieTempAlarm) { + + //------------------------------------------------------------------ + // If control arrives here, we were already under an alarm. We'll + // change that if the high end of the temperature range drops below + // the low temperature mark. + //------------------------------------------------------------------ + + if (i16High < g_ddi_bc_Configuration.u8DieTempLow) { + + //-------------------------------------------------------------- + // If control arrives here, we're safe now. Drop the alarm. + //-------------------------------------------------------------- + + g_RampControl.dieTempAlarm = 0; + + iStepTheRamp = !0; + +#ifdef CONFIG_POWER_SUPPLY_DEBUG + printk("Battery charger: releasing " + "die temp alarm: [%d, %d] < %d\r\n", + (int32_t) i16Low, (int32_t) i16High, + (int32_t) g_ddi_bc_Configuration. + u8DieTempLow); +#endif + + } + + } else { + + //------------------------------------------------------------------ + // If control arrives here, we were not under an alarm. We'll change + // that if the high end of the temperature range rises above the + // high temperature mark. + //------------------------------------------------------------------ + + if (i16High >= g_ddi_bc_Configuration.u8DieTempHigh) { + + //-------------------------------------------------------------- + // If control arrives here, we're running too hot. Raise the + // alarm. + //-------------------------------------------------------------- + + g_RampControl.dieTempAlarm = 1; + + iStepTheRamp = !0; + +#ifdef CONFIG_POWER_SUPPLY_DEBUG + printk("Battery charger: declaring " + "die temp alarm: [%d, %d] >= %d\r\n", + (int32_t) i16Low, (int32_t) i16High, + (int32_t) g_ddi_bc_Configuration. + u8DieTempLow); +#endif + } + + } + + } + //-------------------------------------------------------------------------- + // Are we monitoring battery temperature? + //-------------------------------------------------------------------------- + + if (g_ddi_bc_Configuration.monitorBatteryTemp) { + + ddi_bc_Status_t status; + + //---------------------------------------------------------------------- + // Get the battery temperature reading. + //---------------------------------------------------------------------- + + uint16_t u16Reading; + + status = ddi_bc_hwGetBatteryTemp(&u16Reading); + + //---------------------------------------------------------------------- + // If there was a problem, then we ignore the reading. Otherwise, let's + // have a look. + //---------------------------------------------------------------------- + + if (status == DDI_BC_STATUS_SUCCESS) { + + //------------------------------------------------------------------ + // Now we need to decide if it's time to raise or lower the alarm. + // The first question to ask is: Were we already under an alarm? + //------------------------------------------------------------------ + + if (g_RampControl.batteryTempAlarm) { + + //-------------------------------------------------------------- + // If control arrives here, we were already under an alarm. + // We'll change that if the reading drops below the low mark. + //-------------------------------------------------------------- + + if (u16Reading < + g_ddi_bc_Configuration.u16BatteryTempLow) { + + //---------------------------------------------------------- + // If control arrives here, we're safe now. Drop the alarm. + //---------------------------------------------------------- + + g_RampControl.batteryTempAlarm = 0; + + iStepTheRamp = !0; + + } + + } else { + + //-------------------------------------------------------------- + // If control arrives here, we were not under an alarm. We'll + // change that if the reading rises above the high mark. + //-------------------------------------------------------------- + + if (u16Reading >= + g_ddi_bc_Configuration.u16BatteryTempHigh) { + + //---------------------------------------------------------- + // If control arrives here, we're running too hot. Raise the + // alarm. + //---------------------------------------------------------- + + g_RampControl.batteryTempAlarm = 1; + + iStepTheRamp = !0; + + } + + } + + } + + } + //-------------------------------------------------------------------------- + // Do we need to step the ramp? + //-------------------------------------------------------------------------- + + if (iStepTheRamp) + ddi_bc_RampStep(0); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Reports the state of the die temperature alarm. +//! +//! \fntype Function +//! +//! This function reports the state of the die temperature alarm. +//! +//! \retval The state of the die temperature alarm. +//! +//////////////////////////////////////////////////////////////////////////////// +int ddi_bc_RampGetDieTempAlarm(void) +{ + return (g_RampControl.dieTempAlarm); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Reports the state of the battery temperature alarm. +//! +//! \fntype Function +//! +//! This function reports the state of the battery temperature alarm. +//! +//! \retval The state of the battery temperature alarm. +//! +//////////////////////////////////////////////////////////////////////////////// +int ddi_bc_RampGetBatteryTempAlarm(void) +{ + return (g_RampControl.batteryTempAlarm); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Reports the state of the ambient temperature alarm. +//! +//! \fntype Function +//! +//! This function reports the state of the ambient temperature alarm. +//! +//! \retval The state of the ambient temperature alarm. +//! +//////////////////////////////////////////////////////////////////////////////// +int ddi_bc_RampGetAmbientTempAlarm(void) +{ + return (g_RampControl.ambientTempAlarm); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Step the current ramp. +//! +//! \fntype Function +//! +//! This function steps the current ramp forward through the given amount of time. +//! +//! \param[in] u32Time The time increment to add. +//! +//! \retval DDI_BC_STATUS_SUCCESS If the operation succeeded. +//! \retval DDI_BC_STATUS_NOT_INITIALIZED If the Battery Charger is not yet +//! initialized. +//! +//////////////////////////////////////////////////////////////////////////////// +ddi_bc_Status_t ddi_bc_RampStep(uint32_t u32Time) +{ + + uint16_t u16MaxNow; + uint16_t u16Target; + uint16_t u16Cart; + int32_t i32Delta; + + //-------------------------------------------------------------------------- + // Make sure the Battery Charger is initialized. + //-------------------------------------------------------------------------- + + if (g_ddi_bc_State == DDI_BC_STATE_UNINITIALIZED) { + return (DDI_BC_STATUS_NOT_INITIALIZED); + } + //-------------------------------------------------------------------------- + // Figure out how much current the hardware is set to draw right now. + //-------------------------------------------------------------------------- + + u16MaxNow = ddi_bc_hwGetMaxCurrent(); + + //-------------------------------------------------------------------------- + // Start with the target. + //-------------------------------------------------------------------------- + + u16Target = g_RampControl.u16Target; + + //-------------------------------------------------------------------------- + // Check the target against the hard limit. + //-------------------------------------------------------------------------- + + if (u16Target > g_RampControl.u16Limit) + u16Target = g_RampControl.u16Limit; + + //-------------------------------------------------------------------------- + // Check if the die temperature alarm is active. + //-------------------------------------------------------------------------- + + if (g_RampControl.dieTempAlarm) { + + //---------------------------------------------------------------------- + // If control arrives here, we are under a die temperature alarm. Clamp + // the target current. + //---------------------------------------------------------------------- + + if (u16Target > g_ddi_bc_Configuration.u16DieTempSafeCurrent) { + u16Target = + g_ddi_bc_Configuration.u16DieTempSafeCurrent; + } + + } + //-------------------------------------------------------------------------- + // Check if the battery temperature alarm is active. + //-------------------------------------------------------------------------- + + if (g_RampControl.batteryTempAlarm) { + + //---------------------------------------------------------------------- + // If control arrives here, we are under a battery temperature alarm. + // Clamp the target current. + //---------------------------------------------------------------------- + + if (u16Target > + g_ddi_bc_Configuration.u16BatteryTempSafeCurrent) { + u16Target = + g_ddi_bc_Configuration.u16BatteryTempSafeCurrent; + } + + } + //-------------------------------------------------------------------------- + // Now we know the target current. Figure out what is actually expressible + // in the hardware. + //-------------------------------------------------------------------------- + + u16Target = ddi_bc_hwExpressibleCurrent(u16Target); + + //-------------------------------------------------------------------------- + // Compute the difference between the expressible target and what's actually + // set in the hardware right now. + //-------------------------------------------------------------------------- + + i32Delta = ((int32_t) u16Target) - ((int32_t) u16MaxNow); + + //-------------------------------------------------------------------------- + // Check if the delta is zero. + //-------------------------------------------------------------------------- + + if (i32Delta == 0) { + + //---------------------------------------------------------------------- + // If control arrives here, there is no difference between what we want + // and what's set in the hardware. + // + // Before we leave, though, we don't want to leave any accumulated time + // laying around for the next ramp up. Zero it out. + //---------------------------------------------------------------------- + + g_RampControl.u32AccumulatedTime = 0; + + //---------------------------------------------------------------------- + // Return success. + //---------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + + } + //-------------------------------------------------------------------------- + // Check if the delta is negative. + //-------------------------------------------------------------------------- + + if (i32Delta < 0) { + + //---------------------------------------------------------------------- + // If control arrives here, the new target is lower than what's + // currently set in the hardware. Since that means we're *reducing* the + // current draw, we can do it right now. Just gimme a sec here... + //---------------------------------------------------------------------- + + ddi_bc_hwSetMaxCurrent(u16Target); + +#ifdef CONFIG_POWER_SUPPLY_DEBUG + printk("Battery charger: setting max charge " + "current to: %hdmA\r\n", u16Target); +#endif + + //---------------------------------------------------------------------- + // Flip the power switch on the charging hardware according to the new + // current setting. + //---------------------------------------------------------------------- + + ddi_bc_hwSetChargerPower(u16Target != 0); + + //---------------------------------------------------------------------- + // We don't want to leave any accumulated time laying around for the + // next ramp up. Zero it out. + //---------------------------------------------------------------------- + + g_RampControl.u32AccumulatedTime = 0; + + //---------------------------------------------------------------------- + // Return success. + //---------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + + } + //-------------------------------------------------------------------------- + // If control arrives here, the target current is higher than what's set in + // the hardware right now. That means we're going to ramp it up. To do that, + // we're going to "buy" more milliamps by "spending" milliseconds of time. + // Add the time we've "banked" to the time we've been credited in this call. + //-------------------------------------------------------------------------- + + u32Time += g_RampControl.u32AccumulatedTime; + + //-------------------------------------------------------------------------- + // Now we know how much we can spend. How much current will it buy? + //-------------------------------------------------------------------------- + + u16Cart = (g_ddi_bc_Configuration.u16CurrentRampSlope * u32Time) / 1000; + + //-------------------------------------------------------------------------- + // Check how the current we can afford stacks up against the target we want. + //-------------------------------------------------------------------------- + + if ((u16MaxNow + u16Cart) < u16Target) { + + //---------------------------------------------------------------------- + // If control arrives here, we can't afford to buy all the current we + // want. Compute the maximum we can afford, and then figure out what we + // can actually express in the hardware. + //---------------------------------------------------------------------- + + u16Target = ddi_bc_hwExpressibleCurrent(u16MaxNow + u16Cart); + + //---------------------------------------------------------------------- + // Check if the result isn't actually different from what's set in the + // the hardware right now. + //---------------------------------------------------------------------- + + if (u16Target == u16MaxNow) { + + //------------------------------------------------------------------ + // If control arrives here, we are so poor that we can't yet afford + // to buy enough current to make a change in the expressible + // hardware setting. Since we didn't spend any of our time, put the + // new balance back in the bank. + //------------------------------------------------------------------ + + g_RampControl.u32AccumulatedTime = u32Time; + + //------------------------------------------------------------------ + // Leave dispiritedly. + //------------------------------------------------------------------ + + return (DDI_BC_STATUS_SUCCESS); + + } + + } + //-------------------------------------------------------------------------- + // If control arrives here, we can afford to buy enough current to get us + // all the way to the target. Set it. + //-------------------------------------------------------------------------- + + ddi_bc_hwSetMaxCurrent(u16Target); + +#ifdef CONFIG_POWER_SUPPLY_DEBUG + printk("Battery charger: setting max charge" + "current to: %hdmA\r\n", u16Target); +#endif + + //-------------------------------------------------------------------------- + // Flip the power switch on the charging hardware according to the new + // current setting. + //-------------------------------------------------------------------------- + + ddi_bc_hwSetChargerPower(u16Target != 0); + + //-------------------------------------------------------------------------- + // We're at the target, so we're finished buying current. Zero out the + // account. + //-------------------------------------------------------------------------- + + g_RampControl.u32AccumulatedTime = 0; + + //-------------------------------------------------------------------------- + // Return success. + //-------------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + +} + +//////////////////////////////////////////////////////////////////////////////// +// End of file +//////////////////////////////////////////////////////////////////////////////// +//! @} diff --git a/drivers/power/stmp37xx/ddi_bc_ramp.h b/drivers/power/stmp37xx/ddi_bc_ramp.h new file mode 100644 index 000000000000..5111a5496fcf --- /dev/null +++ b/drivers/power/stmp37xx/ddi_bc_ramp.h @@ -0,0 +1,50 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +//////////////////////////////////////////////////////////////////////////////// +//! \addtogroup ddi_bc +//! @{ +// +// Copyright (c) 2004-2005 SigmaTel, Inc. +// +//! \file ddi_bc_ramp.h +//! \brief Internal header file for Battery Charger current ramp controller. +//! \date 06/2005 +//! +//! This file contains internal declarations for Battery current ramp +//! controller. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _DDI_BC_RAMP_H +#define _DDI_BC_RAMP_H + +//////////////////////////////////////////////////////////////////////////////// +// Prototypes +//////////////////////////////////////////////////////////////////////////////// + +extern void ddi_bc_RampReset(void); +extern uint16_t ddi_bc_RampSetTarget(uint16_t); +extern uint16_t ddi_bc_RampGetTarget(void); +extern uint16_t ddi_bc_RampSetLimit(uint16_t); +extern uint16_t ddi_bc_RampGetLimit(void); +extern void ddi_bc_RampUpdateAlarms(void); +extern int ddi_bc_RampGetDieTempAlarm(void); +extern int ddi_bc_RampGetBatteryTempAlarm(void); +extern int ddi_bc_RampGetAmbientTempAlarm(void); +extern ddi_bc_Status_t ddi_bc_RampStep(uint32_t); + +//////////////////////////////////////////////////////////////////////////////// +// End of file +//////////////////////////////////////////////////////////////////////////////// +#endif // _DDI_BC_H +//! @} diff --git a/drivers/power/stmp37xx/ddi_bc_sm.c b/drivers/power/stmp37xx/ddi_bc_sm.c new file mode 100644 index 000000000000..8bd8d2cbb58b --- /dev/null +++ b/drivers/power/stmp37xx/ddi_bc_sm.c @@ -0,0 +1,1122 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +//////////////////////////////////////////////////////////////////////////////// +//! \addtogroup ddi_bc +//! @{ +// +// Copyright (c) 2004-2005 SigmaTel, Inc. +// +//! \file ddi_bc_sm.c +//! \brief Contains the Battery Charger state machine. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////// + +#include <mach/ddi_bc.h> +#include "ddi_bc_internal.h" + +#include <linux/delay.h> + +//////////////////////////////////////////////////////////////////////////////// +// Definitions +//////////////////////////////////////////////////////////////////////////////// + +// This is the minimum time we must charge before we transition from +// the charging state to the topping off. If we reach the +// u16ChargingThresholdCurrent charge curent before then, the battery was +// already full so we can avoid the risk of charging it past .1C for +// too long. + +#define TRANSITION_TO_TOPOFF_MINIMUM_CHARGE_TIME_mS 1 * 60 * 1000 // 1 minute + +// If DCDCs are active, we can't complete the charging cycle. Once +// they are stay off for this amount of time, we will restart the +// charge cycle. +#define DCDC_INACTIVITY_TIMER_THRESHOLD 1 * 60 * 1000 // 1 minute + +//////////////////////////////////////////////////////////////////////////////// +// Variables +//////////////////////////////////////////////////////////////////////////////// + +// The current state. + +ddi_bc_State_t g_ddi_bc_State = DDI_BC_STATE_UNINITIALIZED; + +// This table contains pointers to the functions that implement states. The +// table is indexed by state. Note that it's critically important for this +// table to agree with the state enumeration in ddi_bc.h. + +static ddi_bc_Status_t ddi_bc_Uninitialized(void); +static ddi_bc_Status_t ddi_bc_Broken(void); +static ddi_bc_Status_t ddi_bc_Disabled(void); +static ddi_bc_Status_t ddi_bc_WaitingToCharge(void); +static ddi_bc_Status_t ddi_bc_Conditioning(void); +static ddi_bc_Status_t ddi_bc_Charging(void); +static ddi_bc_Status_t ddi_bc_ToppingOff(void); +static ddi_bc_Status_t ddi_bc_DcdcModeWaitingToCharge(void); + +ddi_bc_Status_t(*const (stateFunctionTable[])) (void) = { +ddi_bc_Uninitialized, + ddi_bc_Broken, + ddi_bc_Disabled, + ddi_bc_WaitingToCharge, + ddi_bc_Conditioning, + ddi_bc_Charging, ddi_bc_ToppingOff, ddi_bc_DcdcModeWaitingToCharge}; + +// Used by states that need to watch the time. +uint32_t g_ddi_bc_u32StateTimer = 0; + +// If DCDCs are active, we can't complete the charging cycle. Once +// they are stay off for this amount of time, we will restart the +// charge cycle. +static uint32_t DcdcInactityTimer = DCDC_INACTIVITY_TIMER_THRESHOLD; + +// Always attempt to charge on first 5V connection even if DCDCs are ON. +bool bRestartChargeCycle = true; + +#ifdef CONFIG_POWER_SUPPLY_DEBUG +static uint16_t u16ExternalBatteryPowerVoltageCheck = 0; +#endif + +ddi_bc_BrokenReason_t ddi_bc_gBrokenReason = DDI_BC_BROKEN_UNINITIALIZED; + +//////////////////////////////////////////////////////////////////////////////// +// Code +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Transition to the DCDC mode Waiting to Charge state. +//! +//! \fntype Function +//! +//! This function implements the transition to the DCDC Mode Waiting +//! to Charge state. +//! +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Transition to the DCDC mode Waiting to Charge state. +//! +//! \fntype Function +//! +//! This function implements the transition to the DCDC Mode Waiting +//! to Charge state. +//! +//////////////////////////////////////////////////////////////////////////////// +static void TransitionToDcdcModeWaitingToCharge(void) +{ + + //-------------------------------------------------------------------------- + // Reset the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer = 0; + + //-------------------------------------------------------------------------- + // Reset the current ramp. + //-------------------------------------------------------------------------- + + ddi_bc_RampReset(); + + //-------------------------------------------------------------------------- + // Move to the Waiting to Charge state. + //-------------------------------------------------------------------------- + + g_ddi_bc_State = DDI_BC_STATE_DCDC_MODE_WAITING_TO_CHARGE; + +#ifdef CONFIG_POWER_SUPPLY_DEBUG + printk("Battery charger: now waiting to charge (DCDC Mode)\n"); +#endif + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Transition to the Waiting to Charge state. +//! +//! \fntype Function +//! +//! This function implements the transition to the Waiting to Charge state. +//! +//////////////////////////////////////////////////////////////////////////////// +static void TransitionToWaitingToCharge(void) +{ + + //-------------------------------------------------------------------------- + // Reset the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer = 0; + + //-------------------------------------------------------------------------- + // Reset the current ramp. + //-------------------------------------------------------------------------- + + ddi_bc_RampReset(); + + //-------------------------------------------------------------------------- + // Move to the Waiting to Charge state. + //-------------------------------------------------------------------------- + + g_ddi_bc_State = DDI_BC_STATE_WAITING_TO_CHARGE; + +#ifdef CONFIG_POWER_SUPPLY_DEBUG + printk("Battery charger: now waiting to charge\n"); +#endif + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Transition to the Conditioning state. +//! +//! \fntype Function +//! +//! This function implements the transition to the Conditioning state. +//! +//////////////////////////////////////////////////////////////////////////////// +static void TransitionToConditioning(void) +{ + + //-------------------------------------------------------------------------- + // Reset the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer = 0; + + //-------------------------------------------------------------------------- + // Set up the current ramp for conditioning. + //-------------------------------------------------------------------------- + + ddi_bc_RampSetTarget(g_ddi_bc_Configuration.u16ConditioningCurrent); + + //-------------------------------------------------------------------------- + // Move to the Conditioning state. + //-------------------------------------------------------------------------- + + g_ddi_bc_State = DDI_BC_STATE_CONDITIONING; + +#ifdef CONFIG_POWER_SUPPLY_DEBUG + printk("Battery charger: now conditioning\n"); +#endif + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Transition to the Charging state. +//! +//! \fntype Function +//! +//! This function implements the transition to the Charging state. +//! +//////////////////////////////////////////////////////////////////////////////// +static void TransitionToCharging(void) +{ + + //-------------------------------------------------------------------------- + // Reset the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer = 0; + + //-------------------------------------------------------------------------- + // Set up the current ramp for charging. + //-------------------------------------------------------------------------- + + ddi_bc_RampSetTarget(g_ddi_bc_Configuration.u16ChargingCurrent); + + //-------------------------------------------------------------------------- + // We'll be finished charging when the current flow drops below this level. + //-------------------------------------------------------------------------- + + ddi_bc_hwSetCurrentThreshold(g_ddi_bc_Configuration. + u16ChargingThresholdCurrent); + + //-------------------------------------------------------------------------- + // Move to the Charging state. + //-------------------------------------------------------------------------- + + g_ddi_bc_State = DDI_BC_STATE_CHARGING; +#ifdef CONFIG_POWER_SUPPLY_DEBUG + printk("Battery charger: now charging\n"); +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Transition to the Topping Off state. +//! +//! \fntype Function +//! +//! This function implements the transition to the Topping Off state. +//! +//////////////////////////////////////////////////////////////////////////////// +static void TransitionToToppingOff(void) +{ + + //-------------------------------------------------------------------------- + // Reset the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer = 0; + + //-------------------------------------------------------------------------- + // Set up the current ramp for topping off. + //-------------------------------------------------------------------------- + + ddi_bc_RampSetTarget(g_ddi_bc_Configuration.u16ChargingCurrent); + + //-------------------------------------------------------------------------- + // Move to the Topping Off state. + //-------------------------------------------------------------------------- + + g_ddi_bc_State = DDI_BC_STATE_TOPPING_OFF; + +#ifdef CONFIG_POWER_SUPPLY_DEBUG + printk("Battery charger: now topping off\n"); +#endif + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Transition to the Broken state. +//! +//! \fntype Function +//! +//! This function implements the transition to the Broken state. +//! +//////////////////////////////////////////////////////////////////////////////// +static void TransitionToBroken(void) +{ + + //-------------------------------------------------------------------------- + // Reset the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer = 0; + + //-------------------------------------------------------------------------- + // Reset the current ramp. + //-------------------------------------------------------------------------- + + ddi_bc_RampReset(); + + //-------------------------------------------------------------------------- + // Move to the Broken state. + //-------------------------------------------------------------------------- + + g_ddi_bc_State = DDI_BC_STATE_BROKEN; + +#ifdef CONFIG_POWER_SUPPLY_DEBUG + printk("Battery charger: declaring a broken battery\n"); +#endif + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Uninitialized state function. +//! +//! \fntype Function +//! +//! This function implements the Uninitialized state. +//! +//////////////////////////////////////////////////////////////////////////////// +static ddi_bc_Status_t ddi_bc_Uninitialized(void) +{ + + //-------------------------------------------------------------------------- + // The first order of business is to update alarms. + //-------------------------------------------------------------------------- + + ddi_bc_RampUpdateAlarms(); + + //-------------------------------------------------------------------------- + // Increment the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer += g_ddi_bc_Configuration.u32StateMachinePeriod; + + //-------------------------------------------------------------------------- + // The only way to leave this state is with a call to ddi_bc_Initialize. So, + // calling this state function does nothing. + //-------------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Broken state function. +//! +//! \fntype Function +//! +//! This function implements the Broken state. +//! +//////////////////////////////////////////////////////////////////////////////// +static ddi_bc_Status_t ddi_bc_Broken(void) +{ + + //-------------------------------------------------------------------------- + // The first order of business is to update alarms. + //-------------------------------------------------------------------------- + + ddi_bc_RampUpdateAlarms(); + + //-------------------------------------------------------------------------- + // Increment the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer += g_ddi_bc_Configuration.u32StateMachinePeriod; + + //-------------------------------------------------------------------------- + // The only way to leave this state is with a call to ddi_bc_SetFixed. So, + // calling this state function does nothing. + //-------------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Disabled state function. +//! +//! \fntype Function +//! +//! This function implements the Disabled state. +//! +//////////////////////////////////////////////////////////////////////////////// +static ddi_bc_Status_t ddi_bc_Disabled(void) +{ + + //-------------------------------------------------------------------------- + // The first order of business is to update alarms. + //-------------------------------------------------------------------------- + + ddi_bc_RampUpdateAlarms(); + + //-------------------------------------------------------------------------- + // Increment the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer += g_ddi_bc_Configuration.u32StateMachinePeriod; + + //-------------------------------------------------------------------------- + // The only way to leave this state is with a call to ddi_bc_SetEnable. So, + // calling this state function does nothing. + //-------------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Waitin to Charge state function. +//! +//! \fntype Function +//! +//! This function implements the Waiting to Charge state. +//! +//////////////////////////////////////////////////////////////////////////////// +static ddi_bc_Status_t ddi_bc_DcdcModeWaitingToCharge(void) +{ + + uint16_t u16BatteryVoltage; + + //-------------------------------------------------------------------------- + // The first order of business is to update alarms. + //-------------------------------------------------------------------------- + + ddi_bc_RampUpdateAlarms(); + + //-------------------------------------------------------------------------- + // Increment the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer += g_ddi_bc_Configuration.u32StateMachinePeriod; + + //-------------------------------------------------------------------------- + // Check if the power supply is present. If not, we're not going anywhere. + //-------------------------------------------------------------------------- + + if (!ddi_bc_hwPowerSupplyIsPresent()) { + TransitionToCharging(); + return (DDI_BC_STATUS_SUCCESS); + } + //-------------------------------------------------------------------------- + // If control arrives here, we're connected to a power supply. Have a look + // at the battery voltage. + //-------------------------------------------------------------------------- + + u16BatteryVoltage = ddi_bc_hwGetBatteryVoltage(); + + //-------------------------------------------------------------------------- + // If topping off has not yet occurred, we do not care if DCDCs are on or not. + // If we have already topped off at least once, then we want to delay so that + // we give the battery a chance to discharge some instead of constantly topping + // it off. + //-------------------------------------------------------------------------- + + if (ddi_bc_hwIsDcdcOn()) { + + //-------------------------------------------------------------------------- + // If DCDCs have turned on, restert the DCDCInactivityTimer; + //-------------------------------------------------------------------------- + DcdcInactityTimer = 0; + + //-------------------------------------------------------------------------- + // If the battery voltage measurement is at or below the LowDcdcBatteryVoltage + // level, we should definetly start charging the battery. The + // LowDcdcBatteryVoltage value should be low enough to account for IR voltage + // drop from the battery under heavy DCDC load. + //-------------------------------------------------------------------------- + + if (u16BatteryVoltage < + g_ddi_bc_Configuration.u16LowDcdcBatteryVoltage_mv) { + bRestartChargeCycle = true; + + } else { + return (DDI_BC_STATUS_SUCCESS); + } + } else if (DcdcInactityTimer < DCDC_INACTIVITY_TIMER_THRESHOLD) { + DcdcInactityTimer += + g_ddi_bc_Configuration.u32StateMachinePeriod; + if (DcdcInactityTimer >= DCDC_INACTIVITY_TIMER_THRESHOLD) { + bRestartChargeCycle = true; + } + } + + if (bRestartChargeCycle) { + bRestartChargeCycle = false; + // start charging + if (u16BatteryVoltage < + g_ddi_bc_Configuration.u16ConditioningThresholdVoltage) { + + //---------------------------------------------------------------------- + // If control arrives here, the battery is very low and it needs to be + // conditioned. + //---------------------------------------------------------------------- + + TransitionToConditioning(); + } else { + + //---------------------------------------------------------------------- + // If control arrives here, the battery isn't too terribly low. + //---------------------------------------------------------------------- + + TransitionToCharging(); + } + + } + + return (DDI_BC_STATUS_SUCCESS); +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Waitin to Charge state function. +//! +//! \fntype Function +//! +//! This function implements the Waiting to Charge state. +//! +//////////////////////////////////////////////////////////////////////////////// +static ddi_bc_Status_t ddi_bc_WaitingToCharge(void) +{ + uint16_t u16BatteryVoltage; + //-------------------------------------------------------------------------- + // The first order of business is to update alarms. + //-------------------------------------------------------------------------- + + ddi_bc_RampUpdateAlarms(); + + //-------------------------------------------------------------------------- + // Increment the state timer. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer += g_ddi_bc_Configuration.u32StateMachinePeriod; + + //-------------------------------------------------------------------------- + // Check if the power supply is present. If not, we're not going anywhere. + //-------------------------------------------------------------------------- + + if (!ddi_bc_hwPowerSupplyIsPresent()) { +#ifdef CONFIG_POWER_SUPPLY_DEBUG + u16ExternalBatteryPowerVoltageCheck = 0; +#endif + return (DDI_BC_STATUS_SUCCESS); + } + //-------------------------------------------------------------------------- + // If control arrives here, we're connected to a power supply. Have a look + // at the battery voltage. + //-------------------------------------------------------------------------- + + u16BatteryVoltage = ddi_bc_hwGetBatteryVoltage(); + +#ifdef CONFIG_POWER_SUPPLY_DEBUG + if (u16ExternalBatteryPowerVoltageCheck) { + if ((u16ExternalBatteryPowerVoltageCheck - u16BatteryVoltage) > + 300) { + //---------------------------------------------------------------------- + // If control arrives here, battery voltage has dropped too quickly after + // the first charge cycle. We think an external voltage regulator is + // connected. If the DCDCs are on and this voltage drops too quickly, + // it will cause large droops and possible brownouts so we disable + // charging. + //---------------------------------------------------------------------- + + ddi_bc_gBrokenReason = + DDI_BC_BROKEN_EXTERNAL_BATTERY_VOLTAGE_DETECTED; + + TransitionToBroken(); + + //---------------------------------------------------------------------- + // Tell our caller the battery appears to be broken. + //---------------------------------------------------------------------- + + return (DDI_BC_STATUS_BROKEN); + } else { + // reset this check + u16ExternalBatteryPowerVoltageCheck = 0; + } + + } +#endif + + //-------------------------------------------------------------------------- + // In addition to 5V, is DCDC on also? If so, swith to DCDC Mode Waiting + // to Charge state. + //-------------------------------------------------------------------------- + + if (ddi_bc_hwIsDcdcOn()) { + TransitionToDcdcModeWaitingToCharge(); + return (DDI_BC_STATUS_SUCCESS); + } + + //-------------------------------------------------------------------------- + // If the battery voltage isn't low, we don't need to be charging it. We + // use a 5% margin to decide. + //-------------------------------------------------------------------------- + + if (!bRestartChargeCycle) { + uint16_t x; + + x = u16BatteryVoltage + (u16BatteryVoltage / 20); + + if (x >= g_ddi_bc_Configuration.u16ChargingVoltage) + return (DDI_BC_STATUS_SUCCESS); + + } + + bRestartChargeCycle = false; + //-------------------------------------------------------------------------- + // If control arrives here, the battery is low. How low? + //-------------------------------------------------------------------------- + + if (u16BatteryVoltage < + g_ddi_bc_Configuration.u16ConditioningThresholdVoltage) { + + //---------------------------------------------------------------------- + // If control arrives here, the battery is very low and it needs to be + // conditioned. + //---------------------------------------------------------------------- + + TransitionToConditioning(); + + } else { + + //---------------------------------------------------------------------- + // If control arrives here, the battery isn't too terribly low. + //---------------------------------------------------------------------- + + TransitionToCharging(); + + } + + //-------------------------------------------------------------------------- + // Return success. + //-------------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Conditioning state function. +//! +//! \fntype Function +//! +//! This function implements the Conditioning state. +//! +//////////////////////////////////////////////////////////////////////////////// +static ddi_bc_Status_t ddi_bc_Conditioning(void) +{ + + //-------------------------------------------------------------------------- + // The first order of business is to update alarms. + //-------------------------------------------------------------------------- + + ddi_bc_RampUpdateAlarms(); + + //-------------------------------------------------------------------------- + // If we're not under an alarm, increment the state timer. + //-------------------------------------------------------------------------- + + if (!ddi_bc_RampGetDieTempAlarm() && !ddi_bc_RampGetBatteryTempAlarm()) { + g_ddi_bc_u32StateTimer += + g_ddi_bc_Configuration.u32StateMachinePeriod; + } + //-------------------------------------------------------------------------- + // Check if the power supply is still around. + //-------------------------------------------------------------------------- + + if (!ddi_bc_hwPowerSupplyIsPresent()) { + + //---------------------------------------------------------------------- + // If control arrives here, the power supply has been removed. Go back + // and wait. + //---------------------------------------------------------------------- + + TransitionToWaitingToCharge(); + + //---------------------------------------------------------------------- + // Return success. + //---------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + + } + + //-------------------------------------------------------------------------- + // If control arrives here, we're still connected to a power supply. + // Check if a battery is connected. If the voltage rises to high with only + // conditioning charge current, we determine that a battery is not connected. + // If that is not the case and a battery is connected, check + // if the battery voltage indicates it still needs conditioning. + //-------------------------------------------------------------------------- + +// if (ddi_bc_hwGetBatteryVoltage() >= 3900) { + if ((ddi_bc_hwGetBatteryVoltage() > + g_ddi_bc_Configuration.u16ConditioningMaxVoltage) && + (ddi_power_GetMaxBatteryChargeCurrent() < + g_ddi_bc_Configuration.u16ConditioningCurrent)) { + //---------------------------------------------------------------------- + // If control arrives here, voltage has risen too quickly for so + // little charge being applied so their must be no battery connected. + //---------------------------------------------------------------------- + + ddi_bc_gBrokenReason = DDI_BC_BROKEN_NO_BATTERY_DETECTED; + + TransitionToBroken(); + + //---------------------------------------------------------------------- + // Tell our caller the battery appears to be broken. + //---------------------------------------------------------------------- + + return (DDI_BC_STATUS_BROKEN); + + } + + if (ddi_bc_hwGetBatteryVoltage() >= + g_ddi_bc_Configuration.u16ConditioningMaxVoltage) { + + //---------------------------------------------------------------------- + // If control arrives here, this battery no longer needs conditioning. + //---------------------------------------------------------------------- + + TransitionToCharging(); + + //---------------------------------------------------------------------- + // Return success. + //---------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + + } + //-------------------------------------------------------------------------- + // Have we been in this state too long? + //-------------------------------------------------------------------------- + + if (g_ddi_bc_u32StateTimer >= + g_ddi_bc_Configuration.u32ConditioningTimeout) { + + //---------------------------------------------------------------------- + // If control arrives here, we've been here too long. + //---------------------------------------------------------------------- + + ddi_bc_gBrokenReason = DDI_BC_BROKEN_CHARGING_TIMEOUT; + + TransitionToBroken(); + + //---------------------------------------------------------------------- + // Tell our caller the battery appears to be broken. + //---------------------------------------------------------------------- + + return (DDI_BC_STATUS_BROKEN); + + } + //-------------------------------------------------------------------------- + // If control arrives here, we're staying in this state. Step the current + // ramp. + //-------------------------------------------------------------------------- + + ddi_bc_RampStep(g_ddi_bc_Configuration.u32StateMachinePeriod); + + //-------------------------------------------------------------------------- + // Return success. + //-------------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Charging state function. +//! +//! \fntype Function +//! +//! This function implements the Charging state. +//! +//////////////////////////////////////////////////////////////////////////////// +static ddi_bc_Status_t ddi_bc_Charging(void) +{ + + //-------------------------------------------------------------------------- + // This variable counts the number of times we've seen the charging status + // bit cleared. + //-------------------------------------------------------------------------- + + static int iStatusCount = 0; + bool bIsDcdcOn; + + //-------------------------------------------------------------------------- + // The first order of business is to update alarms. + //-------------------------------------------------------------------------- + + ddi_bc_RampUpdateAlarms(); + + //-------------------------------------------------------------------------- + // If we're not under an alarm, increment the state timer. + //-------------------------------------------------------------------------- + + if (!ddi_bc_RampGetDieTempAlarm() && !ddi_bc_RampGetBatteryTempAlarm()) { + g_ddi_bc_u32StateTimer += + g_ddi_bc_Configuration.u32StateMachinePeriod; + } + //-------------------------------------------------------------------------- + // Check if the power supply is still around. + //-------------------------------------------------------------------------- + + if (!ddi_bc_hwPowerSupplyIsPresent()) { + + //---------------------------------------------------------------------- + // If control arrives here, the power supply has been removed. Go back + // and wait. + //---------------------------------------------------------------------- + + TransitionToWaitingToCharge(); + + //---------------------------------------------------------------------- + // Return success. + //---------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + + } + //-------------------------------------------------------------------------- + // If control arrives here, we're still connected to a power supply. We need + // to decide now if the battery is still charging, or if it's nearly full. + // If it's still charging, we'll stay in this state. Otherwise, we'll move + // to the Topping Off state. + // + // Most of the time, we decide that the battery is still charging simply by + // checking if the the actual current flow is above the charging threshold + // current (as indicated by the charge status bit). However, if we're + // still ramping up to full charging current, the hardware may still be set + // to deliver an amount that's less than the threshold. In that case, the + // charging status bit would *definitely* show a low charging current, but + // that doesn't mean the battery is ready for topping off. + // + // So, in summary, we will move to the Topping Off state if both of the + // following are true: + // + // 1) The maximum current set in the hardware is greater than the charging + // threshold. + // -AND- + // 2) The actual current flow is also higher than the threshold (as + // indicated by the charge status bit). + // + //-------------------------------------------------------------------------- + + bIsDcdcOn = ddi_bc_hwIsDcdcOn(); + if (bIsDcdcOn) { + ddi_bc_hwSetCurrentThreshold(g_ddi_bc_Configuration. + u16DdcdModeChargingThresholdCurrent); + } else { + ddi_bc_hwSetCurrentThreshold(g_ddi_bc_Configuration. + u16ChargingThresholdCurrent); + } + + { + uint16_t u16ActualProgrammedCurrent = ddi_bc_hwGetMaxCurrent(); + + //---------------------------------------------------------------------- + // Get the Maximum current that we will ramp to. + //---------------------------------------------------------------------- + + //---------------------------------------------------------------------- + // Not all possible values are expressible by the BATTCHRG_I bitfield. + // The following coverts the max current value into the the closest hardware + // expressible bitmask equivalent. Then, it converts this back to the actual + // decimal current value that this bitmask represents. + //---------------------------------------------------------------------- + + uint16_t u16CurrentRampTarget = ddi_bc_RampGetTarget(); + + if (u16CurrentRampTarget > ddi_bc_RampGetLimit()) + u16CurrentRampTarget = ddi_bc_RampGetLimit(); + + //---------------------------------------------------------------------- + // Not all possible values are expressible by the BATTCHRG_I bitfield. + // The following coverts the max current value into the the closest hardware + // expressible bitmask equivalent. Then, it converts this back to the actual + // decimal current value that this bitmask represents. + //---------------------------------------------------------------------- + + u16CurrentRampTarget = + ddi_bc_hwExpressibleCurrent(u16CurrentRampTarget); + + //---------------------------------------------------------------------- + // We want to wait before we check the charge status bit until the ramping + // up is complete. Because the charge status bit is noisy, we want to + // disregard it until the programmed charge currint in BATTCHRG_I is well + // beyond the STOP_ILIMIT value. + //---------------------------------------------------------------------- + if ((u16ActualProgrammedCurrent >= u16CurrentRampTarget) && + !ddi_bc_hwGetChargeStatus()) { + uint8_t u8IlimitThresholdLimit; + //---------------------------------------------------------------------- + // If control arrives here, the hardware flag is telling us that the + // charging current has fallen below the threshold. We need to see this + // happen twice consecutively before we believe it. Increment the count. + //---------------------------------------------------------------------- + + iStatusCount++; + + //---------------------------------------------------------------------- + // If we are in DCDC operting mode, we only need this criteria to be true + // once before we advance to topping off. In 5V only mode, we want 10 + // consecutive times before advancing to topping off. + //---------------------------------------------------------------------- + + if (bIsDcdcOn) + u8IlimitThresholdLimit = 1; + else + u8IlimitThresholdLimit = 10; + + //---------------------------------------------------------------------- + // How many times in a row have we seen this status bit low? + //---------------------------------------------------------------------- + + if (iStatusCount >= u8IlimitThresholdLimit) { + + //------------------------------------------------------------------ + // If control arrives here, we've seen the CHRGSTS bit low too many + // times. This means it's time to move back to the waiting to charge + // state if DCDCs are present or move to the Topping Off state if + // no DCDCs are present. Because we can't measure only the current + // going to the battery when the DCDCs are active, we don't know when + // to start topping off or how long to top off. + // + // First, reset the status count for the next time we're in this + // state. + //------------------------------------------------------------------ + + iStatusCount = 0; + +#ifdef CONFIG_POWER_SUPPLY_DEBUG + u16ExternalBatteryPowerVoltageCheck = + ddi_bc_hwGetBatteryVoltage(); +#endif + + if (bIsDcdcOn) { + // We will restart the charge cycle once the DCDCs are no + // longer present. + DcdcInactityTimer = 0; + + TransitionToWaitingToCharge(); + } else if (g_ddi_bc_u32StateTimer <= + TRANSITION_TO_TOPOFF_MINIMUM_CHARGE_TIME_mS) + { + //------------------------------------------------------------------ + // If we haven't actually didn't have to charge very long + // then the battery was already full. In this case, we do + // not top off so that we do not needlessly overcharge the + // battery. + //------------------------------------------------------------------ + TransitionToWaitingToCharge(); + } else { + + //------------------------------------------------------------------ + // Move to the Topping Off state. + //------------------------------------------------------------------ + + TransitionToToppingOff(); + } + //------------------------------------------------------------------ + // Return success. + //------------------------------------------------------------------ + + return (DDI_BC_STATUS_SUCCESS); + + } + + } else { + + //---------------------------------------------------------------------- + // If control arrives here, the battery is still charging. Clear the + // status count. + //---------------------------------------------------------------------- + + iStatusCount = 0; + + } + + } + + //-------------------------------------------------------------------------- + // Have we been in this state too long? + //-------------------------------------------------------------------------- + + if (g_ddi_bc_u32StateTimer >= g_ddi_bc_Configuration.u32ChargingTimeout) { + + //---------------------------------------------------------------------- + // If control arrives here, we've been here too long. + //---------------------------------------------------------------------- + + ddi_bc_gBrokenReason = DDI_BC_BROKEN_CHARGING_TIMEOUT; + + TransitionToBroken(); + + //---------------------------------------------------------------------- + // Tell our caller the battery appears to be broken. + //---------------------------------------------------------------------- + + return (DDI_BC_STATUS_BROKEN); + + } + //-------------------------------------------------------------------------- + // If control arrives here, we're staying in this state. Step the current + // ramp. + //-------------------------------------------------------------------------- + + ddi_bc_RampStep(g_ddi_bc_Configuration.u32StateMachinePeriod); + + //-------------------------------------------------------------------------- + // Return success. + //-------------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Topping Off state function. +//! +//! \fntype Function +//! +//! This function implements the Topping Off state. +//! +//////////////////////////////////////////////////////////////////////////////// +static ddi_bc_Status_t ddi_bc_ToppingOff(void) +{ + + //-------------------------------------------------------------------------- + // The first order of business is to update alarms. + + //-------------------------------------------------------------------------- + + ddi_bc_RampUpdateAlarms(); + + //-------------------------------------------------------------------------- + // Increment the state timer. Notice that, unlike other states, we increment + // the state timer whether or not we're under an alarm. + //-------------------------------------------------------------------------- + + g_ddi_bc_u32StateTimer += g_ddi_bc_Configuration.u32StateMachinePeriod; + + //-------------------------------------------------------------------------- + // Check if the power supply is still around. + //-------------------------------------------------------------------------- + + if (!ddi_bc_hwPowerSupplyIsPresent()) { + + //---------------------------------------------------------------------- + // If control arrives here, the power supply has been removed. Go back + // and wait. + //--------------------------------------------------------------------- + + TransitionToWaitingToCharge(); + + //---------------------------------------------------------------------- + // Return success. + //---------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + + } + + //-------------------------------------------------------------------------- + // Are we done topping off? + //-------------------------------------------------------------------------- + if (g_ddi_bc_u32StateTimer >= g_ddi_bc_Configuration.u32TopOffPeriod) { + + //---------------------------------------------------------------------- + // If control arrives here, we're done topping off. + //---------------------------------------------------------------------- + + TransitionToWaitingToCharge(); + + } + //-------------------------------------------------------------------------- + // If control arrives here, we're staying in this state. Step the current + // ramp. + //-------------------------------------------------------------------------- + + ddi_bc_RampStep(g_ddi_bc_Configuration.u32StateMachinePeriod); + + //-------------------------------------------------------------------------- + // Return success. + //-------------------------------------------------------------------------- + + return (DDI_BC_STATUS_SUCCESS); + +} + +//////////////////////////////////////////////////////////////////////////////// +// End of file +//////////////////////////////////////////////////////////////////////////////// +//! @} diff --git a/drivers/power/stmp37xx/ddi_bc_sm.h b/drivers/power/stmp37xx/ddi_bc_sm.h new file mode 100644 index 000000000000..0e5fd8a1f144 --- /dev/null +++ b/drivers/power/stmp37xx/ddi_bc_sm.h @@ -0,0 +1,46 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +//////////////////////////////////////////////////////////////////////////////// +//! \addtogroup ddi_bc +//! @{ +// +// Copyright (c) 2004-2005 SigmaTel, Inc. +// +//! \file ddi_bc_sm.h +//! \brief Header file for the Battery Charger state machine. +//! \date 06/2005 +//! +//! This file contains declarations for the Battery Charger state machine. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _DDI_BC_SM_H +#define _DDI_BC_SM_H + +//////////////////////////////////////////////////////////////////////////////// +// Externs +//////////////////////////////////////////////////////////////////////////////// + +//! The current state. + +extern ddi_bc_State_t g_ddi_bc_State; + +//! The state function table. + +extern ddi_bc_Status_t(*const (stateFunctionTable[])) (void); + +//////////////////////////////////////////////////////////////////////////////// +// End of file +//////////////////////////////////////////////////////////////////////////////// +#endif // _DDI_BC_H +//! @} diff --git a/drivers/power/stmp37xx/ddi_power_battery.c b/drivers/power/stmp37xx/ddi_power_battery.c new file mode 100644 index 000000000000..f9079fcce916 --- /dev/null +++ b/drivers/power/stmp37xx/ddi_power_battery.c @@ -0,0 +1,998 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +//////////////////////////////////////////////////////////////////////////////// +//! \addtogroup ddi_power +//! @{ +// +// Copyright(C) 2005 SigmaTel, Inc. +// +//! \file ddi_power_battery.c +//! \brief Implementation file for the power driver battery charger. +//! +//////////////////////////////////////////////////////////////////////////////// +// Includes and external references +//////////////////////////////////////////////////////////////////////////////// +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <asm/processor.h> /* cpu_relax */ +#include <mach/hardware.h> +#include <mach/ddi_bc.h> +#include <mach/lradc.h> +#include <mach/regs-power.h> +#include <mach/regs-lradc.h> +#include <mach/lradc.h> +#include "ddi_bc_internal.h" +#include <mach/platform.h> + +//! \brief Base voltage to start battery calculations for LiIon +#define BATT_BRWNOUT_LIION_BASE_MV 2800 +//! \brief Constant to help with determining whether to round up or +//! not during calculation +#define BATT_BRWNOUT_LIION_CEILING_OFFSET_MV 39 +//! \brief Number of mV to add if rounding up in LiIon mode +#define BATT_BRWNOUT_LIION_LEVEL_STEP_MV 40 +//! \brief Constant value to be calculated by preprocessing +#define BATT_BRWNOUT_LIION_EQN_CONST \ + (BATT_BRWNOUT_LIION_BASE_MV - BATT_BRWNOUT_LIION_CEILING_OFFSET_MV) +//! \brief Base voltage to start battery calculations for Alkaline/NiMH +#define BATT_BRWNOUT_ALKAL_BASE_MV 800 +//! \brief Constant to help with determining whether to round up or +//! not during calculation +#define BATT_BRWNOUT_ALKAL_CEILING_OFFSET_MV 19 +//! \brief Number of mV to add if rounding up in Alkaline/NiMH mode +#define BATT_BRWNOUT_ALKAL_LEVEL_STEP_MV 20 +//! \brief Constant value to be calculated by preprocessing +#define BATT_BRWNOUT_ALKAL_EQN_CONST \ + (BATT_BRWNOUT_ALKAL_BASE_MV - BATT_BRWNOUT_ALKAL_CEILING_OFFSET_MV) + +#define GAIN_CORRECTION 1012 // 1.012 + +/* NOTE: the below define is different for 37xx and 378x */ +#define VBUSVALID_THRESH_4_30V 0x4 +#define LINREG_OFFSET_STEP_BELOW 0x2 +#define BP_POWER_BATTMONITOR_BATT_VAL 16 +#define BP_POWER_CHARGE_BATTCHRG_I 0 +#define BP_POWER_CHARGE_STOP_ILIMIT 8 + +//////////////////////////////////////////////////////////////////////////////// +// Globals & Variables +//////////////////////////////////////////////////////////////////////////////// + +// FIXME +/* We cant use VBUSVALID signal for VDD5V detection, since setting in + * USB driver POWER_DEBUG.VBUSVALIDPIOLOCK bit locks the POWER_STS.VBUSVALID to + * active state for all power states (even if the 5v went away). The + * POWER_CTRL.VBUSVALID_IRQ is also affected and it's impossible to get + * valid information about 5v presence. + */ +/* static ddi_power_5vDetection_t DetectionMethod = + DDI_POWER_5V_VDD5V_GT_VDDIO; */ +static ddi_power_5vDetection_t DetectionMethod = DDI_POWER_5V_VBUSVALID; + +//////////////////////////////////////////////////////////////////////////////// +// Code +//////////////////////////////////////////////////////////////////////////////// + +#if 0 +static void dump_regs(void) +{ + printk("HW_POWER_CHARGE 0x%08x\n", __raw_readl(REGS_POWER_BASE + HW_POWER_CHARGE)); + printk("HW_POWER_STS 0x%08x\n", __raw_readl(REGS_POWER_BASE + HW_POWER_STS)); + printk("HW_POWER_BATTMONITOR 0x%08x\n", __raw_readl(REGS_POWER_BASE + HW_POWER_BATTMONITOR)); +} +#endif + +//! This array maps bit numbers to current increments, as used in the register +//! fields HW_POWER_CHARGE.STOP_ILIMIT and HW_POWER_CHARGE.BATTCHRG_I. +static const uint16_t currentPerBit[] = { 10, 20, 50, 100, 200, 400 }; + +uint16_t ddi_power_convert_current_to_setting(uint16_t u16Current) +{ + int i; + uint16_t u16Mask; + uint16_t u16Setting = 0; + + // Scan across the bit field, adding in current increments. + u16Mask = (0x1 << 5); + + for (i = 5; (i >= 0) && (u16Current > 0); i--, u16Mask >>= 1) { + if (u16Current >= currentPerBit[i]) { + u16Current -= currentPerBit[i]; + u16Setting |= u16Mask; + } + } + + // Return the result. + return(u16Setting); +} + +//////////////////////////////////////////////////////////////////////////////// +//! See hw_power.h for details. +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_power_convert_setting_to_current(uint16_t u16Setting) +{ + int i; + uint16_t u16Mask; + uint16_t u16Current = 0; + + // Scan across the bit field, adding in current increments. + u16Mask = (0x1 << 5); + + for (i = 5; i >= 0; i--, u16Mask >>= 1) { + if (u16Setting & u16Mask) u16Current += currentPerBit[i]; + } + + // Return the result. + return(u16Current); +} + +void ddi_power_Enable5vDetection(void) +{ + u32 val; + // Disable hardware power down when 5V is inserted or removed + stmp3xxx_clearl(BM_POWER_5VCTRL_PWDN_5VBRNOUT, REGS_POWER_BASE + HW_POWER_5VCTRL); + + /* + * Prepare the hardware for the detection method. We used to set + * and clear the VBUSVALID_5VDETECT bit, but that is also used for + * the DCDC 5V detection. It is sufficient to just check the status + * bits to see if 5V is present. + * + * Use VBUSVALID for DCDC 5V detection. The DCDC's detection is + * different than the USB/5V detection used to switch profiles. This + * is used to determine when a handoff should occur. + */ + stmp3xxx_setl(BM_POWER_5VCTRL_VBUSVALID_5VDETECT, REGS_POWER_BASE + HW_POWER_5VCTRL); + + // Set 5V detection threshold to 4.3V for VBUSVALID. + stmp3xxx_setl( + BF(VBUSVALID_THRESH_4_30V, POWER_5VCTRL_VBUSVALID_TRSH), REGS_POWER_BASE + HW_POWER_5VCTRL); + + // gotta set LINREG_OFFSET to STEP_BELOW according to manual + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + val &= ~(BM_POWER_VDDIOCTRL_LINREG_OFFSET); + val |= BF(LINREG_OFFSET_STEP_BELOW, POWER_VDDIOCTRL_LINREG_OFFSET); + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDACTRL); + val &= ~(BM_POWER_VDDACTRL_LINREG_OFFSET); + val |= BF(LINREG_OFFSET_STEP_BELOW, POWER_VDDACTRL_LINREG_OFFSET); + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDACTRL); + + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDDCTRL); + val &= ~(BM_POWER_VDDDCTRL_LINREG_OFFSET); + val |= BF(LINREG_OFFSET_STEP_BELOW, POWER_VDDDCTRL_LINREG_OFFSET); + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDDCTRL); + + /* Clear vbusvalid interrupt flag */ +// stmp3xxx_clearl(BM_POWER_CTRL_VBUSVALID_IRQ, REGS_POWER_BASE + HW_POWER_CTRL); + stmp3xxx_clearl(BM_POWER_CTRL_VDD5V_GT_VDDIO_IRQ, REGS_POWER_BASE + HW_POWER_CTRL); + /* enable vbusvalid irq */ +// stmp3xxx_setl(BM_POWER_CTRL_ENIRQ_VBUS_VALID, REGS_POWER_BASE + HW_POWER_CTRL); + stmp3xxx_setl(BM_POWER_CTRL_ENIRQ_VDD5V_GT_VDDIO, REGS_POWER_BASE + HW_POWER_CTRL); +} + +/* + * This function prepares the hardware for a 5V-to-battery handoff. It assumes + * the current configuration is using 5V as the power source. The 5V + * interrupt will be set up for a 5V removal. + */ +void ddi_power_enable_5v_to_battery_handoff(void) +{ + /* Clear vbusvalid interrupt flag */ +// stmp3xxx_clearl(BM_POWER_CTRL_VBUSVALID_IRQ, REGS_POWER_BASE + HW_POWER_CTRL); + stmp3xxx_clearl(BM_POWER_CTRL_VDD5V_GT_VDDIO_IRQ, REGS_POWER_BASE + HW_POWER_CTRL); + + /* detect 5v unplug */ +// stmp3xxx_clearl(BM_POWER_CTRL_POLARITY_VBUSVALID, REGS_POWER_BASE + HW_POWER_CTRL); + stmp3xxx_clearl(BM_POWER_CTRL_POLARITY_VDD5V_GT_VDDIO, REGS_POWER_BASE + HW_POWER_CTRL); + + // Enable automatic transition to DCDC + stmp3xxx_setl(BM_POWER_5VCTRL_DCDC_XFER, REGS_POWER_BASE + HW_POWER_5VCTRL); +} + +/* + * This function will handle all the power rail transitions necesarry to power + * the chip from the battery when it was previously powered from the 5V power + * source. + */ +void ddi_power_execute_5v_to_battery_handoff(void) +{ + u32 val; + // VDDD has different configurations depending on the battery type + // and battery level. + + // For LiIon battery, we will use the DCDC to power VDDD. + // Use LinReg offset for DCDC mode. + // Turn on the VDDD DCDC output and turn off the VDDD LinReg output. + // Make sure stepping is enabled when using DCDC. + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDDCTRL); + val &= ~(BM_POWER_VDDDCTRL_DISABLE_FET | BM_POWER_VDDDCTRL_ENABLE_LINREG | + BM_POWER_VDDDCTRL_DISABLE_STEPPING | BM_POWER_VDDDCTRL_LINREG_OFFSET); + val |= BF(LINREG_OFFSET_STEP_BELOW, POWER_VDDDCTRL_LINREG_OFFSET); + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDDCTRL); + + // Power VDDA and VDDIO from the DCDC. + // Use LinReg offset for DCDC mode. + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDACTRL); + val &= ~(BM_POWER_VDDACTRL_LINREG_OFFSET); + val |= BF(LINREG_OFFSET_STEP_BELOW, POWER_VDDACTRL_LINREG_OFFSET); + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDACTRL); + + // Turn on the VDDA DCDC converter output and turn off LinReg output. + // Make sure stepping is enabled when using DCDC. + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDACTRL); + val &= ~(BM_POWER_VDDACTRL_DISABLE_FET | + BM_POWER_VDDACTRL_ENABLE_LINREG | + BM_POWER_VDDACTRL_DISABLE_STEPPING); + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDACTRL); + + // Use LinReg offset for DCDC mode. + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + val &= ~(BM_POWER_VDDIOCTRL_LINREG_OFFSET); + val |= BF(LINREG_OFFSET_STEP_BELOW, POWER_VDDIOCTRL_LINREG_OFFSET); + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + // Turn on the VDDIO DCDC output and turn on the LinReg output. + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + val &= ~(BM_POWER_VDDIOCTRL_DISABLE_FET); + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + + stmp3xxx_clearl(BM_POWER_5VCTRL_ILIMIT_EQ_ZERO, REGS_POWER_BASE + HW_POWER_5VCTRL); + // Make sure stepping is enabled when using DCDC. + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + val &= ~(BM_POWER_VDDIOCTRL_DISABLE_STEPPING); + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDIOCTRL); +} + +/* + * This function sets up battery-to-5V handoff. The power switch from + * battery to 5V is automatic. This funtion enables the 5V present detection + * such that the 5V interrupt can be generated if it is enabled. (The interrupt + * handler can inform software the 5V present event.) To deal with noise or + * a high current, this function enables DCDC1/2 based on the battery mode. + */ +void ddi_power_enable_battery_to_5v_handoff(void) +{ + /* Clear vbusvalid interrupt flag */ +// stmp3xxx_clearl(BM_POWER_CTRL_VBUSVALID_IRQ, REGS_POWER_BASE + HW_POWER_CTRL); + stmp3xxx_clearl(BM_POWER_CTRL_VDD5V_GT_VDDIO_IRQ, REGS_POWER_BASE + HW_POWER_CTRL); + + /* detect 5v plug-in */ +// stmp3xxx_setl(BM_POWER_CTRL_POLARITY_VBUSVALID, REGS_POWER_BASE + HW_POWER_CTRL); + stmp3xxx_setl(BM_POWER_CTRL_POLARITY_VDD5V_GT_VDDIO, REGS_POWER_BASE + HW_POWER_CTRL); + + // Force current from 5V to be zero by disabling its entry source. + stmp3xxx_setl(BM_POWER_5VCTRL_ILIMIT_EQ_ZERO, REGS_POWER_BASE + HW_POWER_5VCTRL); + + // Allow DCDC be to active when 5V is present. + stmp3xxx_setl(BM_POWER_5VCTRL_ENABLE_DCDC, REGS_POWER_BASE + HW_POWER_5VCTRL); +} + +/* This function handles the transitions on each of the power rails necessary + * to power the chip from the 5V power supply when it was previously powered + * from the battery power supply. + */ +void ddi_power_execute_battery_to_5v_handoff(void) +{ + u32 val; + // Disable the DCDC during 5V connections. + stmp3xxx_clearl(BM_POWER_5VCTRL_ENABLE_DCDC, REGS_POWER_BASE + HW_POWER_5VCTRL); + + // Power the VDDD/VDDA/VDDIO rail from the linear regulator. The DCDC + // is ready to automatically power the chip when 5V is removed. + // Use this configuration when powering from 5V + + // Use LinReg offset for LinReg mode + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDDCTRL); + val &= ~BM_POWER_VDDDCTRL_LINREG_OFFSET; + val |= BF(LINREG_OFFSET_STEP_BELOW, POWER_VDDDCTRL_LINREG_OFFSET); + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDDCTRL); + + // Turn on the VDDD LinReg and turn on the VDDD DCDC output. The + // ENABLE_DCDC must be cleared to avoid LinReg and DCDC conflict. + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDDCTRL); + val &= ~(BM_POWER_VDDDCTRL_ENABLE_LINREG | BM_POWER_VDDDCTRL_DISABLE_FET); + val |= BM_POWER_VDDDCTRL_ENABLE_LINREG; + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDDCTRL); + // Make sure stepping is disabled when using linear regulators + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDDCTRL); + val &= ~(BM_POWER_VDDDCTRL_DISABLE_STEPPING); + val |= BM_POWER_VDDDCTRL_DISABLE_STEPPING; + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDDCTRL); + + // Use LinReg offset for LinReg mode + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDACTRL); + val &= ~(BM_POWER_VDDACTRL_LINREG_OFFSET); + val |= BF(LINREG_OFFSET_STEP_BELOW, POWER_VDDACTRL_LINREG_OFFSET); + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDACTRL); + + // Turn on the VDDA LinReg output and prepare the DCDC for transfer. + // ENABLE_DCDC must be clear to avoid DCDC and LinReg conflict. + // Make sure stepping is disabled when using linear regulators + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDACTRL); + val &= ~(BM_POWER_VDDACTRL_ENABLE_LINREG | BM_POWER_VDDACTRL_DISABLE_FET | + BM_POWER_VDDACTRL_DISABLE_STEPPING); + val |= BM_POWER_VDDACTRL_ENABLE_LINREG | BM_POWER_VDDACTRL_DISABLE_STEPPING; + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDACTRL); + + // Use LinReg offset for LinReg mode. + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + val &= ~(BM_POWER_VDDIOCTRL_LINREG_OFFSET); + val |= BF(LINREG_OFFSET_STEP_BELOW, POWER_VDDIOCTRL_LINREG_OFFSET); + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + + // Turn on the VDDIO LinReg output and prepare the VDDIO DCDC output. + // ENABLE_DCDC must be cleared to prevent DCDC and LinReg conflict. + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + val &= ~(BM_POWER_VDDIOCTRL_DISABLE_FET); + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + + stmp3xxx_clearl(BM_POWER_5VCTRL_ILIMIT_EQ_ZERO, REGS_POWER_BASE + HW_POWER_5VCTRL); + // Make sure stepping is disabled when using DCDC. + val = __raw_readl(REGS_POWER_BASE + HW_POWER_VDDIOCTRL); + val |= BM_POWER_VDDIOCTRL_DISABLE_STEPPING; + __raw_writel(val, REGS_POWER_BASE + HW_POWER_VDDIOCTRL); +} + +void ddi_power_init_handoff(void) +{ + u32 val; + /* + * The following settings give optimal power supply capability and + * efficiency. Extreme loads will need HALF_FETS cleared and + * possibly DOUBLE_FETS set. The below setting are probably also + * the best for alkaline mode also but more characterization is + * needed to know for sure. + */ + // Increase the RCSCALE_THRESHOLD + stmp3xxx_setl(BM_POWER_LOOPCTRL_RCSCALE_THRESH, + REGS_POWER_BASE + HW_POWER_LOOPCTRL); + // Increase the RCSCALE level for quick DCDC response to dynamic load + stmp3xxx_setl(BF(3, POWER_LOOPCTRL_EN_RCSCALE), + REGS_POWER_BASE + HW_POWER_LOOPCTRL); // 8x + + // Enable half fets for increase efficiency. + stmp3xxx_setl(BM_POWER_MINPWR_HALF_FETS, REGS_POWER_BASE + HW_POWER_MINPWR); + + // enable 5v presence detection + ddi_power_Enable5vDetection(); + + if (ddi_power_Get5vPresentFlag()) + /* It's 5V mode, enable 5V-to-battery handoff */ + ddi_power_enable_5v_to_battery_handoff(); + else + /* It's battery mode, enable battery-to-5V handoff */ + ddi_power_enable_battery_to_5v_handoff(); + + // Finally enable the battery adjust + val = __raw_readl(REGS_POWER_BASE + HW_POWER_BATTMONITOR); + val |= BM_POWER_BATTMONITOR_EN_BATADJ; + __raw_writel(val, REGS_POWER_BASE + HW_POWER_BATTMONITOR); +} + +int ddi_power_init_battery(void) +{ + int ret; + + // Init LRADC channel 7 + ret = hw_lradc_init_ladder(BATTERY_VOLTAGE_CH, + LRADC_DELAY_TRIGGER_BATTERY, + 200); + if (ret) { + printk(KERN_ERR "%s: hw_lradc_init_ladder failed\n", __func__); + return ret; + } + + stmp3xxx_setl(BM_LRADC_CONVERSION_AUTOMATIC, REGS_LRADC_BASE + HW_LRADC_CONVERSION); + + // Set li-ion mode + stmp3xxx_setl(BF(2, LRADC_CONVERSION_SCALE_FACTOR), REGS_LRADC_BASE + HW_LRADC_CONVERSION); + + // Turn off divide-by-two - we already have a divide-by-four + // as part of the hardware + stmp3xxx_clearl( + BF(1 << BATTERY_VOLTAGE_CH, LRADC_CTRL2_DIVIDE_BY_TWO), REGS_LRADC_BASE + HW_LRADC_CTRL2); + + stmp3xxx_setl(BM_POWER_CHARGE_ENABLE_FAULT_DETECT, REGS_POWER_BASE + HW_POWER_CHARGE); + + // kick off the trigger + hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_BATTERY, 1); + + /* prepare handoff */ + ddi_power_init_handoff(); + + return 0; +} + +/* + * Use the the lradc7 channel dedicated for battery voltage measurement to + * get the die temperature from on-chip sensor. + */ +uint16_t MeasureInternalDieTemperature(void) +{ + uint32_t ch8Value, ch9Value; + + /* power up internal tep sensor block */ + stmp3xxx_clearl(BM_LRADC_CTRL2_TEMPSENSE_PWD, REGS_LRADC_BASE + HW_LRADC_CTRL2); + + /* mux to the lradc 8th temp channel */ + stmp3xxx_clearl(BF(0xF, LRADC_CTRL4_LRADC7SELECT), REGS_LRADC_BASE + HW_LRADC_CTRL4); + stmp3xxx_setl(BF(8, LRADC_CTRL4_LRADC7SELECT), REGS_LRADC_BASE + HW_LRADC_CTRL4); + + /* Clear the interrupt flag */ + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC7_IRQ, REGS_LRADC_BASE + HW_LRADC_CTRL1); + stmp3xxx_setl(BF(1 << BATTERY_VOLTAGE_CH, LRADC_CTRL0_SCHEDULE), REGS_LRADC_BASE + HW_LRADC_CTRL0); + // Wait for conversion complete + while (!(__raw_readl(REGS_LRADC_BASE + HW_LRADC_CTRL1) & BM_LRADC_CTRL1_LRADC7_IRQ)) + cpu_relax(); + /* Clear the interrupt flag again */ + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC7_IRQ, REGS_LRADC_BASE + HW_LRADC_CTRL1); + // read temperature value and clr lradc + ch8Value = __raw_readl(REGS_LRADC_BASE + HW_LRADC_CHn(BATTERY_VOLTAGE_CH)) & BM_LRADC_CHn_VALUE; + stmp3xxx_clearl(BM_LRADC_CHn_VALUE, REGS_LRADC_BASE + HW_LRADC_CHn(BATTERY_VOLTAGE_CH)); + + /* mux to the lradc 9th temp channel */ + stmp3xxx_clearl(BF(0xF, LRADC_CTRL4_LRADC7SELECT), REGS_LRADC_BASE + HW_LRADC_CTRL4); + stmp3xxx_setl(BF(9, LRADC_CTRL4_LRADC7SELECT), REGS_LRADC_BASE + HW_LRADC_CTRL4); + + /* Clear the interrupt flag */ + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC7_IRQ, REGS_LRADC_BASE + HW_LRADC_CTRL1); + stmp3xxx_setl(BF(1 << BATTERY_VOLTAGE_CH, LRADC_CTRL0_SCHEDULE), REGS_LRADC_BASE + HW_LRADC_CTRL0); + // Wait for conversion complete + while (!(__raw_readl(REGS_LRADC_BASE + HW_LRADC_CTRL1) & BM_LRADC_CTRL1_LRADC7_IRQ)) + cpu_relax(); + /* Clear the interrupt flag */ + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC7_IRQ, REGS_LRADC_BASE + HW_LRADC_CTRL1); + // read temperature value + ch9Value = __raw_readl(REGS_LRADC_BASE + HW_LRADC_CHn(BATTERY_VOLTAGE_CH)) & BM_LRADC_CHn_VALUE; + stmp3xxx_clearl(BM_LRADC_CHn_VALUE, REGS_LRADC_BASE + HW_LRADC_CHn(BATTERY_VOLTAGE_CH)); + + /* power down temp sensor block */ + stmp3xxx_setl(BM_LRADC_CTRL2_TEMPSENSE_PWD, REGS_LRADC_BASE + HW_LRADC_CTRL2); + + /* mux back to the lradc 7th battery voltage channel */ + stmp3xxx_clearl(BF(0xF, LRADC_CTRL4_LRADC7SELECT), REGS_LRADC_BASE + HW_LRADC_CTRL4); + stmp3xxx_setl(BF(7, LRADC_CTRL4_LRADC7SELECT), REGS_LRADC_BASE + HW_LRADC_CTRL4); + + /* Clear the interrupt flag */ + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC7_IRQ, REGS_LRADC_BASE + HW_LRADC_CTRL1); + stmp3xxx_setl(BF(1 << BATTERY_VOLTAGE_CH, LRADC_CTRL0_SCHEDULE), REGS_LRADC_BASE + HW_LRADC_CTRL0); + // Wait for conversion complete + while (!(__raw_readl(REGS_LRADC_BASE + HW_LRADC_CTRL1) & BM_LRADC_CTRL1_LRADC7_IRQ)) + cpu_relax(); + /* Clear the interrupt flag */ + stmp3xxx_clearl(BM_LRADC_CTRL1_LRADC7_IRQ, REGS_LRADC_BASE + HW_LRADC_CTRL1); + + return (uint16_t)((ch9Value-ch8Value)*GAIN_CORRECTION/4000); +} + + +//////////////////////////////////////////////////////////////////////////////// +//! Name: ddi_power_GetBatteryMode +//! +//! \brief +//////////////////////////////////////////////////////////////////////////////// +ddi_power_BatteryMode_t ddi_power_GetBatteryMode(void) +{ +#if 0 + return (__raw_readl(REGS_POWER_BASE + HW_POWER_STS) & BM_POWER_STS_MODE) ? + DDI_POWER_BATT_MODE_ALKALINE_NIMH : + DDI_POWER_BATT_MODE_LIION; +#endif + return DDI_POWER_BATT_MODE_LIION; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Name: ddi_power_GetBatteryChargerEnabled +//! +//! \brief +//////////////////////////////////////////////////////////////////////////////// +bool ddi_power_GetBatteryChargerEnabled(void) +{ +#if 0 + return (__raw_readl(REGS_POWER_BASE + HW_POWER_STS) & BM_POWER_STS_BATT_CHRG_PRESENT) ? 1 : 0; +#endif + return 1; +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report if the charger hardware power is on. +//! +//! \fntype Function +//! +//! This function reports if the charger hardware power is on. +//! +//! \retval Zero if the charger hardware is not powered. Non-zero otherwise. +//! +//! Note that the bit we're looking at is named PWD_BATTCHRG. The "PWD" +//! stands for "power down". Thus, when the bit is set, the battery charger +//! hardware is POWERED DOWN. +//////////////////////////////////////////////////////////////////////////////// +bool ddi_power_GetChargerPowered(void) +{ + return (__raw_readl(REGS_POWER_BASE + HW_POWER_CHARGE) & BM_POWER_CHARGE_PWD_BATTCHRG) ? 0 : 1; +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Turn the charging hardware on or off. +//! +//! \fntype Function +//! +//! This function turns the charging hardware on or off. +//! +//! \param[in] on Indicates whether the charging hardware should be on or off. +//! +//! Note that the bit we're looking at is named PWD_BATTCHRG. The "PWD" +//! stands for "power down". Thus, when the bit is set, the battery charger +//! hardware is POWERED DOWN. +//////////////////////////////////////////////////////////////////////////////// +void ddi_power_SetChargerPowered(bool bPowerOn) +{ + // Hit the battery charge power switch. + if (bPowerOn) { + stmp3xxx_clearl(BM_POWER_CHARGE_PWD_BATTCHRG, REGS_POWER_BASE + HW_POWER_CHARGE); + stmp3xxx_clearl(BM_POWER_5VCTRL_PWD_CHARGE_4P2, REGS_POWER_BASE + HW_POWER_5VCTRL); + } else { + stmp3xxx_setl(BM_POWER_CHARGE_PWD_BATTCHRG, REGS_POWER_BASE + HW_POWER_CHARGE); + stmp3xxx_setl(BM_POWER_5VCTRL_PWD_CHARGE_4P2, REGS_POWER_BASE + HW_POWER_5VCTRL); + } + +//#ifdef CONFIG_POWER_SUPPLY_DEBUG +#if 0 + printk("Battery charger: charger %s\n", bPowerOn ? "ON!" : "OFF"); + dump_regs(); +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Reports if the charging current has fallen below the threshold. +//! +//! \fntype Function +//! +//! This function reports if the charging current that the battery is accepting +//! has fallen below the threshold. +//! +//! Note that this bit is regarded by the hardware guys as very slightly +//! unreliable. They recommend that you don't believe a value of zero until +//! you've sampled it twice. +//! +//! \retval Zero if the battery is accepting less current than indicated by the +//! charging threshold. Non-zero otherwise. +//! +//////////////////////////////////////////////////////////////////////////////// +int ddi_power_GetChargeStatus(void) +{ + return (__raw_readl(REGS_POWER_BASE + HW_POWER_STS) & BM_POWER_STS_CHRGSTS) ? 1 : 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// Battery Voltage +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the voltage across the battery. +//! +//! \fntype Function +//! +//! This function reports the voltage across the battery. Should return a +//! value in range ~3000 - 4200 mV. +//! +//! \retval The voltage across the battery, in mV. +//! +//////////////////////////////////////////////////////////////////////////////// + +//! \brief Constant value for 8mV steps used in battery translation +#define BATT_VOLTAGE_8_MV 8 + +uint16_t ddi_power_GetBattery(void) +{ + uint32_t u16BattVolt; + + // Get the raw result of battery measurement + u16BattVolt = __raw_readl(REGS_POWER_BASE + HW_POWER_BATTMONITOR); + u16BattVolt &= BM_POWER_BATTMONITOR_BATT_VAL; + u16BattVolt >>= BP_POWER_BATTMONITOR_BATT_VAL; + + // Adjust for 8-mV LSB resolution and return + u16BattVolt *= BATT_VOLTAGE_8_MV; + +//#ifdef CONFIG_POWER_SUPPLY_DEBUG +#if 0 + printk("Battery charger: %u mV\n", u16BattVolt); +#endif + + return u16BattVolt; +} + +#if 0 +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report the voltage across the battery. +//! +//! \fntype Function +//! +//! This function reports the voltage across the battery. +//! +//! \retval The voltage across the battery, in mV. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_power_GetBatteryBrownout(void) +{ + uint32_t u16BatteryBrownoutLevel; + + // Get battery brownout level + u16BatteryBrownoutLevel = __raw_readl(REGS_POWER_BASE + HW_POWER_BATTMONITOR); + u16BatteryBrownoutLevel &= BM_POWER_BATTMONITOR_BRWNOUT_LVL; + u16BatteryBrownoutLevel >>= BP_POWER_BATTMONITOR_BRWNOUT_LVL; + + // Calculate battery brownout level + switch (ddi_power_GetBatteryMode()) { + case DDI_POWER_BATT_MODE_LIION: + u16BatteryBrownoutLevel *= BATT_BRWNOUT_LIION_LEVEL_STEP_MV; + u16BatteryBrownoutLevel += BATT_BRWNOUT_LIION_BASE_MV; + break; + case DDI_POWER_BATT_MODE_ALKALINE_NIMH: + u16BatteryBrownoutLevel *= BATT_BRWNOUT_ALKAL_LEVEL_STEP_MV; + u16BatteryBrownoutLevel += BATT_BRWNOUT_ALKAL_BASE_MV; + break; + default: + u16BatteryBrownoutLevel = 0; + break; + } + return u16BatteryBrownoutLevel; +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Set battery brownout level +//! +//! \fntype Reentrant Function +//! +//! This function sets the battery brownout level in millivolt. It transforms the +//! input brownout value from millivolts to the hardware register bit field value +//! taking the ceiling value in the calculation. +//! +//! \param[in] u16BattBrownout_mV Battery battery brownout level in mV +//! +//! \return SUCCESS +//! +//////////////////////////////////////////////////////////////////////////////// +int ddi_power_SetBatteryBrownout(uint16_t u16BattBrownout_mV) +{ + int16_t i16BrownoutLevel; + int ret = 0; + + // Calculate battery brownout level + switch (ddi_power_GetBatteryMode()) { + case DDI_POWER_BATT_MODE_LIION: + i16BrownoutLevel = u16BattBrownout_mV - + BATT_BRWNOUT_LIION_EQN_CONST; + i16BrownoutLevel /= BATT_BRWNOUT_LIION_LEVEL_STEP_MV; + break; + case DDI_POWER_BATT_MODE_ALKALINE_NIMH: + i16BrownoutLevel = u16BattBrownout_mV - + BATT_BRWNOUT_ALKAL_EQN_CONST; + i16BrownoutLevel /= BATT_BRWNOUT_ALKAL_LEVEL_STEP_MV; + break; + default: + return -EINVAL; + } + + // Do a check to make sure nothing went wrong. + if (i16BrownoutLevel <= 0x0f) { + //Write the battery brownout level + stmp3xxx_setl( + BF(i16BrownoutLevel, POWER_BATTMONITOR_BRWNOUT_LVL), REGS_POWER_BASE + HW_POWER_BATTMONITOR); + } else + ret = -EINVAL; + + return ret; +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Currents +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +//! Name: ddi_power_SetBiasCurrentSource +//! +//! \brief +//////////////////////////////////////////////////////////////////////////////// +int ddi_power_SetBiasCurrentSource(ddi_power_BiasCurrentSource_t eSource) +{ + switch (eSource) { + case DDI_POWER_INTERNAL_BIAS_CURRENT: + stmp3xxx_setl(BM_POWER_CHARGE_USE_EXTERN_R, REGS_POWER_BASE + HW_POWER_CHARGE); + break; + case DDI_POWER_EXTERNAL_BIAS_CURRENT: + stmp3xxx_clearl(BM_POWER_CHARGE_USE_EXTERN_R, REGS_POWER_BASE + HW_POWER_CHARGE); + break; + default: + return -EINVAL; + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Name: ddi_power_GetBiasCurrentSource +//! +//! \brief +//////////////////////////////////////////////////////////////////////////////// +ddi_power_BiasCurrentSource_t ddi_power_GetBiasCurrentSource(void) +{ + return (__raw_readl(REGS_POWER_BASE + HW_POWER_CHARGE) & BM_POWER_CHARGE_USE_EXTERN_R) ? + DDI_POWER_INTERNAL_BIAS_CURRENT : + DDI_POWER_EXTERNAL_BIAS_CURRENT; +} + +//////////////////////////////////////////////////////////////////////////////// +//! Name: ddi_power_SetMaxBatteryChargeCurrent +//! +//! \brief +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_power_SetMaxBatteryChargeCurrent(uint16_t u16MaxCur) +{ + uint32_t u16OldSetting; + uint32_t u16NewSetting; + uint32_t u16ToggleMask; + + // Get the old setting. + u16OldSetting = (__raw_readl(REGS_POWER_BASE + HW_POWER_CHARGE) & BM_POWER_CHARGE_BATTCHRG_I) >> + BP_POWER_CHARGE_BATTCHRG_I; + + // Convert the new threshold into a setting. + u16NewSetting = ddi_power_convert_current_to_setting(u16MaxCur); + + // Write to the toggle register. + u16ToggleMask = __raw_readl(REGS_POWER_BASE + HW_POWER_CHARGE); + u16ToggleMask &= ~BM_POWER_CHARGE_BATTCHRG_I; + u16ToggleMask |= u16NewSetting << BP_POWER_CHARGE_BATTCHRG_I; + + __raw_writel(u16ToggleMask, REGS_POWER_BASE + HW_POWER_CHARGE); + + // Tell the caller what current we're set at now. + return ddi_power_convert_setting_to_current(u16NewSetting); +} + +//////////////////////////////////////////////////////////////////////////////// +//! Name: ddi_power_GetMaxBatteryChargeCurrent +//! +//! \brief +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_power_GetMaxBatteryChargeCurrent(void) +{ + uint32_t u8Bits; + + // Get the raw data from register + u8Bits = (__raw_readl(REGS_POWER_BASE + HW_POWER_CHARGE) & BM_POWER_CHARGE_BATTCHRG_I) >> + BP_POWER_CHARGE_BATTCHRG_I; + + // Translate raw data to current (in mA) and return it + return ddi_power_convert_setting_to_current(u8Bits); +} + +//////////////////////////////////////////////////////////////////////////////// +//! Name: ddi_power_GetMaxChargeCurrent +//! +//! \brief +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_power_SetBatteryChargeCurrentThreshold(uint16_t u16Thresh) +{ + uint32_t u16OldSetting; + uint32_t u16NewSetting; + uint32_t u16ToggleMask; + + //------------------------------------------------------------------- + // See ddi_power_SetMaxBatteryChargeCurrent for an explanation of + // why we're using the toggle register here. + // + // Since this function doesn't have any major hardware effect, + // we could use the usual macros for writing to this bit field. But, + // for the sake of parallel construction and any potentially odd + // effects on the status bit, we use the toggle register in the same + // way as ddi_bc_hwSetMaxCurrent. + //------------------------------------------------------------------- + + //------------------------------------------------------------------- + // The threshold hardware can't express as large a range as the max + // current setting, but we can use the same functions as long as we + // add an extra check here. + // + // Thresholds larger than 180mA can't be expressed. + //------------------------------------------------------------------- + + if (u16Thresh > 180) + u16Thresh = 180; + + //////////////////////////////////////// + // Create the mask + //////////////////////////////////////// + + // Get the old setting. + u16OldSetting = (__raw_readl(REGS_POWER_BASE + HW_POWER_CHARGE) & BM_POWER_CHARGE_STOP_ILIMIT) >> + BP_POWER_CHARGE_STOP_ILIMIT; + + // Convert the new threshold into a setting. + u16NewSetting = ddi_power_convert_current_to_setting(u16Thresh); + + // Compute the toggle mask. + u16ToggleMask = u16OldSetting ^ u16NewSetting; + + ///////////////////////////////////////// + // Write to the register + ///////////////////////////////////////// + + // Write to the toggle register. + u16ToggleMask = __raw_readl(REGS_POWER_BASE + HW_POWER_CHARGE); + u16ToggleMask &= ~BM_POWER_CHARGE_STOP_ILIMIT; + u16ToggleMask |= u16NewSetting << BP_POWER_CHARGE_STOP_ILIMIT; + + // Tell the caller what current we're set at now. + return ddi_power_convert_setting_to_current(u16NewSetting); +} + +//////////////////////////////////////////////////////////////////////////////// +//! Name: ddi_power_GetBatteryChargeCurrentThreshold +//! +//! \brief +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_power_GetBatteryChargeCurrentThreshold(void) +{ + uint32_t u16Threshold; + + u16Threshold = (__raw_readl(REGS_POWER_BASE + HW_POWER_CHARGE) & BM_POWER_CHARGE_STOP_ILIMIT) >> + BP_POWER_CHARGE_STOP_ILIMIT; + + return ddi_power_convert_setting_to_current(u16Threshold); +} + +//////////////////////////////////////////////////////////////////////////////// +// Conversion +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Compute the actual current expressible in the hardware. +//! +//! \fntype Function +//! +//! Given a desired current, this function computes the actual current +//! expressible in the hardware. +//! +//! Note that the hardware has a minimum resolution of 10mA and a maximum +//! expressible value of 780mA (see the data sheet for details). If the given +//! current cannot be expressed exactly, then the largest expressible smaller +//! value will be used. +//! +//! \param[in] u16Current The current of interest. +//! +//! \retval The corresponding current in mA. +//! +//////////////////////////////////////////////////////////////////////////////// +uint16_t ddi_power_ExpressibleCurrent(uint16_t u16Current) +{ + return ddi_power_convert_setting_to_current( + ddi_power_convert_current_to_setting(u16Current)); +} + +//////////////////////////////////////////////////////////////////////////////// +//! Name: ddi_power_Get5VPresent +//! +//! \brief +//////////////////////////////////////////////////////////////////////////////// +bool ddi_power_Get5vPresentFlag(void) +{ + switch (DetectionMethod) { + case DDI_POWER_5V_VBUSVALID: + // Check VBUSVALID for 5V present + return ((__raw_readl(REGS_POWER_BASE + HW_POWER_STS) & BM_POWER_STS_VBUSVALID) != 0); + case DDI_POWER_5V_VDD5V_GT_VDDIO: + // Check VDD5V_GT_VDDIO for 5V present + return ((__raw_readl(REGS_POWER_BASE + HW_POWER_STS) & BM_POWER_STS_VDD5V_GT_VDDIO) != 0); + default: + break; + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Report on the die temperature. +//! +//! \fntype Function +//! +//! This function reports on the die temperature. +//! +//! \param[out] pLow The low end of the temperature range. +//! \param[out] pHigh The high end of the temperature range. +//! +//////////////////////////////////////////////////////////////////////////////// +// Temperature constant +#define TEMP_READING_ERROR_MARGIN 5 +#define KELVIN_TO_CELSIUS_CONST 273 + +void ddi_power_GetDieTemp(int16_t * pLow, int16_t * pHigh) +{ + int16_t i16High, i16Low; + uint16_t u16Reading; + + // Get the reading in Kelvins + u16Reading = MeasureInternalDieTemperature(); + + // Adjust for error margin + i16High = u16Reading + TEMP_READING_ERROR_MARGIN; + i16Low = u16Reading - TEMP_READING_ERROR_MARGIN; + + // Convert to Celsius + i16High -= KELVIN_TO_CELSIUS_CONST; + i16Low -= KELVIN_TO_CELSIUS_CONST; + +//#ifdef CONFIG_POWER_SUPPLY_DEBUG +#if 0 + printk("Battery charger: Die temp %d to %d C\n", i16Low, i16High); +#endif + // Return the results + *pHigh = i16High; + *pLow = i16Low; +} + +/////////////////////////////////////////////////////////////////////////////// +//! +//! \brief Checks to see if the DCDC has been manually enabled +//! +//! \fntype Function +//! +//! \retval true if DCDC is ON, false if DCDC is OFF. +//! +//////////////////////////////////////////////////////////////////////////////// +bool ddi_power_IsDcdcOn(void) +{ + return (__raw_readl(REGS_POWER_BASE + HW_POWER_5VCTRL) & BM_POWER_5VCTRL_ENABLE_DCDC) ? 1 : 0; +} + + +//////////////////////////////////////////////////////////////////////////////// +//! See hw_power.h for details. +//////////////////////////////////////////////////////////////////////////////// +void ddi_power_SetPowerClkGate(bool bGate) +{ + // Gate/Ungate the clock to the power block + if (bGate) { + stmp3xxx_setl(BM_POWER_CTRL_CLKGATE, REGS_POWER_BASE + HW_POWER_CTRL); + } else { + stmp3xxx_clearl(BM_POWER_CTRL_CLKGATE, REGS_POWER_BASE + HW_POWER_CTRL); + } +} + +//////////////////////////////////////////////////////////////////////////////// +//! See hw_power.h for details. +//////////////////////////////////////////////////////////////////////////////// +bool ddi_power_GetPowerClkGate(void) +{ + return (__raw_readl(REGS_POWER_BASE + HW_POWER_CTRL) & BM_POWER_CTRL_CLKGATE) ? 1 : 0; +} + + +//////////////////////////////////////////////////////////////////////////////// +// End of file +//////////////////////////////////////////////////////////////////////////////// +//! @} diff --git a/drivers/power/stmp37xx/ddi_power_battery.h b/drivers/power/stmp37xx/ddi_power_battery.h new file mode 100644 index 000000000000..2d7c1a5411ad --- /dev/null +++ b/drivers/power/stmp37xx/ddi_power_battery.h @@ -0,0 +1,67 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +//! \brief Battery modes +typedef enum { + // 37xx battery modes + //! \brief LiIon battery powers the player + DDI_POWER_BATT_MODE_LIION = 0, + //! \brief Alkaline/NiMH battery powers the player + DDI_POWER_BATT_MODE_ALKALINE_NIMH = 1, +} ddi_power_BatteryMode_t; + + +//! \brief Available sources for bias currents +typedef enum { + //! \brief Use external resistor to generate bias current + DDI_POWER_EXTERNAL_BIAS_CURRENT = 0x0, + //! \brief Use internal resistor to generate bias current + DDI_POWER_INTERNAL_BIAS_CURRENT = 0x1 +} ddi_power_BiasCurrentSource_t; + +//! \brief Possible 5V detection methods +typedef enum { + //! \brief Use VBUSVALID comparator for detection + DDI_POWER_5V_VBUSVALID, + //! \brief Use VDD5V_GT_VDDIO comparison for detection + DDI_POWER_5V_VDD5V_GT_VDDIO +} ddi_power_5vDetection_t; + + +uint16_t ddi_power_convert_current_to_setting(uint16_t u16Current); +uint16_t ddi_power_convert_setting_to_current(uint16_t u16Setting); +void ddi_power_enable_5v_to_battery_handoff(void); +void ddi_power_execute_5v_to_battery_handoff(void); +void ddi_power_enable_battery_to_5v_handoff(void); +void ddi_power_execute_battery_to_5v_handoff(void); +int ddi_power_init_battery(void); +ddi_power_BatteryMode_t ddi_power_GetBatteryMode(void); +bool ddi_power_GetBatteryChargerEnabled(void); +bool ddi_power_GetChargerPowered(void); +void ddi_power_SetChargerPowered(bool bPowerOn); +int ddi_power_GetChargeStatus(void); +uint16_t ddi_power_GetBattery(void); +uint16_t ddi_power_GetBatteryBrownout(void); +int ddi_power_SetBatteryBrownout(uint16_t u16BattBrownout_mV); +int ddi_power_SetBiasCurrentSource(ddi_power_BiasCurrentSource_t eSource); +ddi_power_BiasCurrentSource_t ddi_power_GetBiasCurrentSource(void); +uint16_t ddi_power_SetMaxBatteryChargeCurrent(uint16_t u16MaxCur); +uint16_t ddi_power_GetMaxBatteryChargeCurrent(void); +uint16_t ddi_power_SetBatteryChargeCurrentThreshold(uint16_t u16Thresh); +uint16_t ddi_power_GetBatteryChargeCurrentThreshold(void); +uint16_t ddi_power_ExpressibleCurrent(uint16_t u16Current); +bool ddi_power_Get5vPresentFlag(void); +void ddi_power_GetDieTemp(int16_t * pLow, int16_t * pHigh); +bool ddi_power_IsDcdcOn(void); +void ddi_power_SetPowerClkGate(bool bGate); +bool ddi_power_GetPowerClkGate(void); diff --git a/drivers/power/stmp37xx/linux.c b/drivers/power/stmp37xx/linux.c new file mode 100644 index 000000000000..178255eceea6 --- /dev/null +++ b/drivers/power/stmp37xx/linux.c @@ -0,0 +1,623 @@ +/* + * Linux glue to STMP3xxx battery state machine. + * + * Author: Steve Longerbeam <stevel@embeddedalley.com> + * + * Copyright (C) 2008 EmbeddedAlley Solutions Inc. + * Copyright 2008-2009 Freescale 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, as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <mach/ddi_bc.h> +#include "ddi_bc_internal.h" +#include <linux/regulator/consumer.h> +#include <linux/regulator/driver.h> +#include <mach/regulator.h> +#include <mach/regs-power.h> +#include <mach/regs-usbphy.h> +#include <mach/platform.h> +#include <linux/delay.h> + +#include <linux/interrupt.h> + + +struct stmp3xxx_info { + struct device *dev; + struct regulator *regulator; + + struct power_supply bat; + struct power_supply ac; + struct power_supply usb; + + ddi_bc_Cfg_t *sm_cfg; + struct mutex sm_lock; + struct timer_list sm_timer; + struct work_struct sm_work; + struct resource *vdd5v_irq; + int is_ac_online; +#define USB_ONLINE 0x01 +#define USB_REG_SET 0x02 +#define USB_SM_RESTART 0x04 +#define USB_SHUTDOWN 0x08 +#define USB_N_SEND 0x10 + int is_usb_online; +}; + +#define to_stmp3xxx_info(x) container_of((x), struct stmp3xxx_info, bat) + +/* There is no direct way to detect wall power presence, so assume the AC + * power source is valid if 5V presents and USB device is disconnected. + * If USB device is connected then assume that AC is offline and USB power + * is online. + */ +#define is_usb_plugged()(__raw_readl(REGS_USBPHY_BASE + HW_USBPHY_STATUS) & \ + BM_USBPHY_STATUS_DEVPLUGIN_STATUS) +#define is_ac_online() \ + (ddi_power_Get5vPresentFlag() ? (!is_usb_plugged()) : 0) +#define is_usb_online() \ + (ddi_power_Get5vPresentFlag() ? (!!is_usb_plugged()) : 0) + +/* + * Power properties + */ +static enum power_supply_property stmp3xxx_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int stmp3xxx_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) + /* ac online */ + val->intval = is_ac_online(); + else + /* usb online */ + val->intval = is_usb_online(); + break; + default: + return -EINVAL; + } + + return 0; +} +/* + * Battery properties + */ +static enum power_supply_property stmp3xxx_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TEMP, +}; + +static int stmp3xxx_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct stmp3xxx_info *info = to_stmp3xxx_info(psy); + ddi_bc_State_t state; + ddi_bc_BrokenReason_t reason; + int temp_alarm; + int16_t temp_lo, temp_hi; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + state = ddi_bc_GetState(); + switch (state) { + case DDI_BC_STATE_CONDITIONING: + case DDI_BC_STATE_CHARGING: + case DDI_BC_STATE_TOPPING_OFF: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case DDI_BC_STATE_DISABLED: + val->intval = ddi_power_Get5vPresentFlag() ? + POWER_SUPPLY_STATUS_NOT_CHARGING : + POWER_SUPPLY_STATUS_DISCHARGING; + break; + default: + /* TODO: detect full */ + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + break; + case POWER_SUPPLY_PROP_PRESENT: + /* is battery present */ + state = ddi_bc_GetState(); + switch (state) { + case DDI_BC_STATE_WAITING_TO_CHARGE: + case DDI_BC_STATE_DCDC_MODE_WAITING_TO_CHARGE: + case DDI_BC_STATE_CONDITIONING: + case DDI_BC_STATE_CHARGING: + case DDI_BC_STATE_TOPPING_OFF: + case DDI_BC_STATE_DISABLED: + val->intval = 1; + break; + case DDI_BC_STATE_BROKEN: + val->intval = !(ddi_bc_GetBrokenReason() == + DDI_BC_BROKEN_NO_BATTERY_DETECTED); + break; + default: + val->intval = 0; + break; + } + break; + case POWER_SUPPLY_PROP_HEALTH: + temp_alarm = ddi_bc_RampGetDieTempAlarm(); + if (temp_alarm) { + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + } else { + state = ddi_bc_GetState(); + switch (state) { + case DDI_BC_STATE_BROKEN: + reason = ddi_bc_GetBrokenReason(); + val->intval = + (reason == DDI_BC_BROKEN_CHARGING_TIMEOUT) ? + POWER_SUPPLY_HEALTH_DEAD : + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + case DDI_BC_STATE_UNINITIALIZED: + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + break; + default: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + } + } + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + /* uV */ + val->intval = ddi_power_GetBattery() * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + /* uA */ + val->intval = ddi_power_GetMaxBatteryChargeCurrent() * 1000; + break; + case POWER_SUPPLY_PROP_TEMP: + mutex_lock(&info->sm_lock); + ddi_power_GetDieTemp(&temp_lo, &temp_hi); + mutex_unlock(&info->sm_lock); + val->intval = temp_lo + (temp_hi - temp_lo) / 2; + + break; + default: + return -EINVAL; + } + + return 0; +} + +static void state_machine_timer(unsigned long data) +{ + struct stmp3xxx_info *info = (struct stmp3xxx_info *)data; + ddi_bc_Cfg_t *cfg = info->sm_cfg; + int ret; + + /* schedule next call to state machine */ + mod_timer(&info->sm_timer, + jiffies + msecs_to_jiffies(cfg->u32StateMachinePeriod)); + + ret = schedule_work(&info->sm_work); + if (!ret) + dev_dbg(info->dev, "state machine failed to schedule\n"); + +} +/* + * Assumtion: + * AC power can't be switched to USB w/o system reboot + * and vice-versa + */ +static void state_machine_work(struct work_struct *work) +{ + struct stmp3xxx_info *info = + container_of(work, struct stmp3xxx_info, sm_work); + + mutex_lock(&info->sm_lock); + + if (info->is_usb_online & USB_SHUTDOWN) { + info->is_usb_online = 0; + if (!info->regulator) + goto out; + regulator_set_current_limit(info->regulator, 0, 0); + goto out; + } + + if (is_ac_online()) { + if (info->is_ac_online) + goto done; + + /* ac supply connected */ + dev_info(info->dev, "changed power connection to ac/5v \n"); + + info->is_ac_online = 1; + info->is_usb_online = 0; + ddi_bc_SetCurrentLimit(600 /*mA*/); + ddi_power_execute_battery_to_5v_handoff(); + ddi_power_enable_5v_to_battery_handoff(); + goto done; + } + + if (!is_usb_online()) + goto out; + + if (info->is_usb_online & USB_REG_SET) + goto done; + + info->is_ac_online = 0; + info->is_usb_online |= USB_ONLINE; + + if (!info->regulator) { + info->regulator = regulator_get(NULL, "charger-1"); + if (!info->regulator || IS_ERR(info->regulator)) { + dev_err(info->dev, + "%s: failed to get regulator\n", __func__); + info->regulator = NULL; + ddi_bc_SetCurrentLimit(350 /*mA*/); + ddi_power_execute_battery_to_5v_handoff(); + ddi_power_enable_5v_to_battery_handoff(); + goto done; + } else + regulator_set_mode(info->regulator, + REGULATOR_MODE_FAST); + } + + if (!(info->is_usb_online & USB_N_SEND)) { + info->is_usb_online |= USB_N_SEND; + } + + if (regulator_set_current_limit(info->regulator, 150000, 150000)) { + dev_err(info->dev, "reg_set_current(150000) failed\n"); + + ddi_bc_SetCurrentLimit(0 /*mA*/); + dev_dbg(info->dev, "charge current set to 0\n"); + mod_timer(&info->sm_timer, jiffies + msecs_to_jiffies(1000)); + goto done; + } + + dev_dbg(info->dev, "%s: charge current set to 100mA\n", __func__); + ddi_bc_SetCurrentLimit(100 /*mA*/); + regulator_set_current_limit(info->regulator, 100000, 100000); + if (info->is_usb_online & USB_SM_RESTART) { + info->is_usb_online &= ~USB_SM_RESTART; + ddi_bc_SetEnable(); + } + + info->is_usb_online |= USB_REG_SET; + + dev_info(info->dev, "changed power connection to usb/5v present\n"); + ddi_power_execute_battery_to_5v_handoff(); + ddi_power_enable_5v_to_battery_handoff(); + ddi_bc_SetEnable(); + +done: + ddi_bc_StateMachine(); +out: + mutex_unlock(&info->sm_lock); +} + +static int bc_sm_restart(struct stmp3xxx_info *info) +{ + ddi_bc_Status_t bcret; + int ret = 0; + + mutex_lock(&info->sm_lock); + + /* ungate power clk */ + ddi_power_SetPowerClkGate(0); + + /* + * config battery charger state machine and move it to the Disabled + * state. This must be done before starting the state machine. + */ + bcret = ddi_bc_Init(info->sm_cfg); + if (bcret != DDI_BC_STATUS_SUCCESS) { + dev_err(info->dev, "state machine init failed: %d\n", bcret); + ret = -EIO; + goto out; + } + + /* + * Check what power supply options we have right now. If + * we're able to do any battery charging, then set the + * appropriate current limit and enable. Otherwise, leave + * the battery charger disabled. + */ + if (is_ac_online()) { + /* ac supply connected */ + dev_info(info->dev, "ac/5v present, enabling state machine\n"); + + info->is_ac_online = 1; + info->is_usb_online = 0; + ddi_bc_SetCurrentLimit(600 /*mA*/); + ddi_bc_SetEnable(); + } else if (is_usb_online()) { + /* usb supply connected */ + dev_info(info->dev, "usb/5v present, enabling state machine\n"); + + info->is_ac_online = 0; + info->is_usb_online = USB_ONLINE | USB_SM_RESTART; + } else { + /* not powered */ + dev_info(info->dev, "%s: 5v not present\n", __func__); + + info->is_ac_online = 0; + info->is_usb_online = 0; + ddi_bc_SetDisable(); + } + + /* schedule first call to state machine */ + mod_timer(&info->sm_timer, jiffies + 1); +out: + mutex_unlock(&info->sm_lock); + return ret; +} + +static irqreturn_t stmp3xxx_vdd5v_irq(int irq, void *cookie) +{ + struct stmp3xxx_info *info = (struct stmp3xxx_info *)cookie; + + if (ddi_power_Get5vPresentFlag()) { + dev_info(info->dev, "5v present, reenable state machine\n"); + + ddi_bc_SetEnable(); + + /* + * We only ack/negate the interrupt here, + * as we can't decide yet if we really can + * switch to 5V (USB bits not ready) + */ + ddi_power_enable_5v_to_battery_handoff(); + } else { + dev_info(info->dev, "5v went away, disabling state machine\n"); + + ddi_bc_SetDisable(); + + info->is_ac_online = 0; + if (info->is_usb_online) + info->is_usb_online = USB_SHUTDOWN; + + ddi_power_execute_5v_to_battery_handoff(); + ddi_power_enable_battery_to_5v_handoff(); + mod_timer(&info->sm_timer, jiffies + 1); + + } + + return IRQ_HANDLED; +} + +static int stmp3xxx_bat_probe(struct platform_device *pdev) +{ + struct stmp3xxx_info *info; + int ret = 0; + + if (!pdev->dev.platform_data) { + printk(KERN_ERR "%s: missing platform data\n", __func__); + return -ENODEV; + } + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->vdd5v_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (info->vdd5v_irq == NULL) { + printk(KERN_ERR "%s: failed to get irq resouce\n", __func__); + goto free_info; + } + + platform_set_drvdata(pdev, info); + + info->dev = &pdev->dev; + info->sm_cfg = pdev->dev.platform_data; + + /* initialize bat power_supply struct */ + info->bat.name = "battery"; + info->bat.type = POWER_SUPPLY_TYPE_BATTERY; + info->bat.properties = stmp3xxx_bat_props; + info->bat.num_properties = ARRAY_SIZE(stmp3xxx_bat_props); + info->bat.get_property = stmp3xxx_bat_get_property; + + /* initialize ac power_supply struct */ + info->ac.name = "ac"; + info->ac.type = POWER_SUPPLY_TYPE_MAINS; + info->ac.properties = stmp3xxx_power_props; + info->ac.num_properties = ARRAY_SIZE(stmp3xxx_power_props); + info->ac.get_property = stmp3xxx_power_get_property; + + /* initialize usb power_supply struct */ + info->usb.name = "usb"; + info->usb.type = POWER_SUPPLY_TYPE_USB; + info->usb.properties = stmp3xxx_power_props; + info->usb.num_properties = ARRAY_SIZE(stmp3xxx_power_props); + info->usb.get_property = stmp3xxx_power_get_property; + + init_timer(&info->sm_timer); + info->sm_timer.data = (unsigned long)info; + info->sm_timer.function = state_machine_timer; + + mutex_init(&info->sm_lock); + INIT_WORK(&info->sm_work, state_machine_work); + + /* init LRADC channels to measure battery voltage and die temp */ + ddi_power_init_battery(); + stmp3xxx_clearl(BM_POWER_5VCTRL_ENABLE_LINREG_ILIMIT, REGS_POWER_BASE + HW_POWER_5VCTRL); + + ret = bc_sm_restart(info); + if (ret) + goto free_info; + + ret = request_irq(info->vdd5v_irq->start, + stmp3xxx_vdd5v_irq, IRQF_DISABLED, + pdev->name, info); + if (ret) { + dev_err(info->dev, "failed to request irq\n"); + goto stop_sm; + } + + ret = power_supply_register(&pdev->dev, &info->bat); + if (ret) { + dev_err(info->dev, "failed to register battery\n"); + goto free_irq; + } + + ret = power_supply_register(&pdev->dev, &info->ac); + if (ret) { + dev_err(info->dev, "failed to register ac power supply\n"); + goto unregister_bat; + } + + ret = power_supply_register(&pdev->dev, &info->usb); + if (ret) { + dev_err(info->dev, "failed to register usb power supply\n"); + goto unregister_ac; + } + + /* enable usb device presence detection */ + stmp3xxx_setl(BM_USBPHY_CTRL_ENDEVPLUGINDETECT, REGS_USBPHY_BASE + HW_USBPHY_CTRL); + + return 0; + +unregister_ac: + power_supply_unregister(&info->ac); +unregister_bat: + power_supply_unregister(&info->bat); +free_irq: + free_irq(info->vdd5v_irq->start, pdev); +stop_sm: + ddi_bc_ShutDown(); +free_info: + kfree(info); + return ret; +} + +static int stmp3xxx_bat_remove(struct platform_device *pdev) +{ + struct stmp3xxx_info *info = platform_get_drvdata(pdev); + + if (info->regulator) + regulator_put(info->regulator); + free_irq(info->vdd5v_irq->start, pdev); + ddi_bc_ShutDown(); + power_supply_unregister(&info->usb); + power_supply_unregister(&info->ac); + power_supply_unregister(&info->bat); + return 0; +} + +static void stmp3xxx_bat_shutdown(struct platform_device *pdev) +{ + ddi_bc_ShutDown(); +} + + +#ifdef CONFIG_PM + +static int stmp3xxx_bat_suspend(struct platform_device *pdev, pm_message_t msg) +{ + struct stmp3xxx_info *info = platform_get_drvdata(pdev); + + mutex_lock(&info->sm_lock); + + /* disable 5v irq */ + stmp3xxx_clearl(BM_POWER_CTRL_ENIRQ_VDD5V_GT_VDDIO, REGS_POWER_BASE + HW_POWER_CTRL); + + ddi_bc_SetDisable(); + /* cancel state machine timer */ + del_timer_sync(&info->sm_timer); + + mutex_unlock(&info->sm_lock); + return 0; +} + +static int stmp3xxx_bat_resume(struct platform_device *pdev) +{ + struct stmp3xxx_info *info = platform_get_drvdata(pdev); + ddi_bc_Cfg_t *cfg = info->sm_cfg; + + mutex_lock(&info->sm_lock); + + if (is_ac_online()) { + /* ac supply connected */ + dev_info(info->dev, "ac/5v present, enabling state machine\n"); + + info->is_ac_online = 1; + info->is_usb_online = 0; + ddi_bc_SetCurrentLimit(600 /*mA*/); + ddi_bc_SetEnable(); + } else if (is_usb_online()) { + /* usb supply connected */ + dev_info(info->dev, "usb/5v present, enabling state machine\n"); + + info->is_ac_online = 0; + info->is_usb_online = 1; + ddi_bc_SetCurrentLimit(350 /*mA*/); + ddi_bc_SetEnable(); + } else { + /* not powered */ + dev_info(info->dev, "%s: 5v not present\n", __func__); + + info->is_ac_online = 0; + info->is_usb_online = 0; + } + + /* enable 5v irq */ + stmp3xxx_setl(BM_POWER_CTRL_ENIRQ_VDD5V_GT_VDDIO, REGS_POWER_BASE + HW_POWER_CTRL); + + /* reschedule calls to state machine */ + mod_timer(&info->sm_timer, + jiffies + msecs_to_jiffies(cfg->u32StateMachinePeriod)); + + mutex_unlock(&info->sm_lock); + return 0; +} + +#else +#define stmp3xxx_bat_suspend NULL +#define stmp3xxx_bat_resume NULL +#endif + +static struct platform_driver stmp3xxx_batdrv = { + .probe = stmp3xxx_bat_probe, + .remove = stmp3xxx_bat_remove, + .shutdown = stmp3xxx_bat_shutdown, + .suspend = stmp3xxx_bat_suspend, + .resume = stmp3xxx_bat_resume, + .driver = { + .name = "stmp3xxx-battery", + .owner = THIS_MODULE, + }, +}; + +static int __init stmp3xxx_bat_init(void) +{ + return platform_driver_register(&stmp3xxx_batdrv); +} + +static void __exit stmp3xxx_bat_exit(void) +{ + platform_driver_unregister(&stmp3xxx_batdrv); +} + +module_init(stmp3xxx_bat_init); +module_exit(stmp3xxx_bat_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Steve Longerbeam <stevel@embeddedalley.com>"); +MODULE_DESCRIPTION("Linux glue to STMP3xxx battery state machine"); diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index f4317798e47c..095e741d7e0d 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -117,4 +117,34 @@ config REGULATOR_LP3971 Say Y here to support the voltage regulators and convertors on National Semiconductors LP3971 PMIC +config REGULATOR_MC13892 + tristate "MC13892 Regulator Support" + depends on REGULATOR + depends on MXC_PMIC_MC13892 + default y + +config REGULATOR_MC13783 + tristate "MC13783 Regulator Support" + depends on REGULATOR + depends on MXC_PMIC_MC13783 + default y + +config REGULATOR_MC34704 + tristate "MC34704 Regulator Support" + depends on REGULATOR + depends on MXC_PMIC_MC34704 + default y + +config REGULATOR_STMP3XXX + tristate "STMP3xxx Regulator Support" + depends on REGULATOR + depends on ARCH_STMP3XXX + default y + +config REGULATOR_MC9S08DZ60 + tristate "mc9s08dz60 Regulator Support" + depends on REGULATOR + depends on MXC_PMIC_MC9S08DZ60 + default y + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 4d762c4cccfd..9e958f245be2 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -2,7 +2,6 @@ # Makefile for regulator drivers. # - obj-$(CONFIG_REGULATOR) += core.o obj-$(CONFIG_REGULATOR_FIXED_VOLTAGE) += fixed.o obj-$(CONFIG_REGULATOR_VIRTUAL_CONSUMER) += virtual.o @@ -17,4 +16,11 @@ obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_DA903X) += da903x.o obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o +obj-$(CONFIG_REGULATOR_MC13892) += reg-mc13892.o +obj-$(CONFIG_REGULATOR_MC13783) += reg-mc13783.o +obj-$(CONFIG_REGULATOR_MC34704) += reg-mc34704.o +obj-$(CONFIG_REGULATOR_STMP3XXX) += stmp3xxx.o + +obj-$(CONFIG_REGULATOR_MC9S08DZ60) += reg-mc9s08dz60.o + ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 98c3a74e9949..a11026a53603 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1034,6 +1034,13 @@ struct regulator *regulator_get(struct device *dev, const char *id) goto found; } } + list_for_each_entry(rdev, ®ulator_list, list) { + if (strcmp(rdev->desc->name, id) == 0) { + goto found; + } + } + printk(KERN_ERR "regulator: Unable to get requested regulator: %s\n", + id); mutex_unlock(®ulator_list_mutex); return regulator; diff --git a/drivers/regulator/reg-mc13783.c b/drivers/regulator/reg-mc13783.c new file mode 100644 index 000000000000..1cc0d37481c0 --- /dev/null +++ b/drivers/regulator/reg-mc13783.c @@ -0,0 +1,2662 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/mc13783/core.h> +#include <linux/platform_device.h> +#include <linux/pmic_status.h> +#include <linux/pmic_external.h> + +/* + * Convenience conversion. + * Here atm, maybe there is somewhere better for this. + */ +#define mV_to_uV(mV) (mV * 1000) +#define uV_to_mV(uV) (uV / 1000) +#define V_to_uV(V) (mV_to_uV(V * 1000)) +#define uV_to_V(uV) (uV_to_mV(uV) / 1000) + +/*! + * @enum regulator_voltage_sw + * @brief PMIC regulator SW output voltage. + */ +enum { + SW_0_9V = 0, /*!< 0.900 V */ + SW_0_925V, /*!< 0.925 V */ + SW_0_95V, /*!< 0.950 V */ + SW_0_975V, /*!< 0.975 V */ + SW_1V, /*!< 1.000 V */ + SW_1_025V, /*!< 1.025 V */ + SW_1_05V, /*!< 1.050 V */ + SW_1_075V, /*!< 1.075 V */ + SW_1_1V, /*!< 1.100 V */ + SW_1_125V, /*!< 1.125 V */ + SW_1_15V, /*!< 1.150 V */ + SW_1_175V, /*!< 1.175 V */ + SW_1_2V, /*!< 1.200 V */ + SW_1_225V, /*!< 1.225 V */ + SW_1_25V, /*!< 1.250 V */ + SW_1_275V, /*!< 1.275 V */ + SW_1_3V, /*!< 1.300 V */ + SW_1_325V, /*!< 1.325 V */ + SW_1_35V, /*!< 1.350 V */ + SW_1_375V, /*!< 1.375 V */ + SW_1_4V, /*!< 1.400 V */ + SW_1_425V, /*!< 1.425 V */ + SW_1_45V, /*!< 1.450 V */ + SW_1_475V, /*!< 1.475 V */ + SW_1_5V, /*!< 1.500 V */ + SW_1_525V, /*!< 1.525 V */ + SW_1_55V, /*!< 1.550 V */ + SW_1_575V, /*!< 1.575 V */ + SW_1_6V, /*!< 1.600 V */ + SW_1_625V, /*!< 1.625 V */ + SW_1_65V, /*!< 1.650 V */ + SW_1_675V, /*!< 1.675 V */ + SW_1_7V, /*!< 1.700 V */ + SW_1_8V = 36, /*!< 1.800 V */ + SW_1_85V = 40, /*!< 1.850 V */ + SW_2V = 44, /*!< 2_000 V */ + SW_2_1V = 48, /*!< 2_100 V */ + SW_2_2V = 52, /*!< 2_200 V */ +} regulator_voltage_sw; + +/*! + * @enum regulator_voltage_violo + * @brief PMIC regulator VIOLO output voltage. + */ +enum { + VIOLO_1_2V = 0, /*!< 1.2 V */ + VIOLO_1_3V, /*!< 1.3 V */ + VIOLO_1_5V, /*!< 1.5 V */ + VIOLO_1_8V, /*!< 1.8 V */ +} regulator_voltage_violo; + +/*! + * @enum regulator_voltage_vdig + * @brief PMIC regulator VDIG output voltage. + */ +enum { + VDIG_1_2V = 0, /*!< 1.2 V */ + VDIG_1_3V, /*!< 1.3 V */ + VDIG_1_5V, /*!< 1.5 V */ + VDIG_1_8V, /*!< 1.8 V */ +} regulator_voltage_vdig; + +/*! + * @enum regulator_voltage_vgen + * @brief PMIC regulator VGEN output voltage. + */ +enum { + VGEN_1_2V = 0, /*!< 1.2 V */ + VGEN_1_3V, /*!< 1.3 V */ + VGEN_1_5V, /*!< 1.5 V */ + VGEN_1_8V, /*!< 1.8 V */ + VGEN_1_1V, /*!< 1.1 V */ + VGEN_2V, /*!< 2 V */ + VGEN_2_775V, /*!< 2.775 V */ + VGEN_2_4V, /*!< 2.4 V */ +} regulator_voltage_vgen; + +/*! + * @enum regulator_voltage_vrfdig + * @brief PMIC regulator VRFDIG output voltage. + */ +enum { + VRFDIG_1_2V = 0, /*!< 1.2 V */ + VRFDIG_1_5V, /*!< 1.5 V */ + VRFDIG_1_8V, /*!< 1.8 V */ + VRFDIG_1_875V, /*!< 1.875 V */ +} regulator_voltage_vrfdig; + +/*! + * @enum regulator_voltage_vrfref + * @brief PMIC regulator VRFREF output voltage. + */ +enum { + VRFREF_2_475V = 0, /*!< 2.475 V */ + VRFREF_2_6V, /*!< 2.600 V */ + VRFREF_2_7V, /*!< 2.700 V */ + VRFREF_2_775V, /*!< 2.775 V */ +} regulator_voltage_vrfref; + +/*! + * @enum regulator_voltage_vrfcp + * @brief PMIC regulator VRFCP output voltage. + */ +enum { + VRFCP_2_7V = 0, /*!< 2.700 V */ + VRFCP_2_775V, /*!< 2.775 V */ +} regulator_voltage_vrfcp; + +/*! + * @enum regulator_voltage_vsim + * @brief PMIC linear regulator VSIM output voltage. + */ +enum { + VSIM_1_8V = 0, /*!< 1.8 V */ + VSIM_2_9V, /*!< 2.90 V */ + VSIM_3V = 1, /*!< 3 V */ +} regulator_voltage_vsim; + +/*! + * @enum regulator_voltage_vesim + * @brief PMIC regulator VESIM output voltage. + */ +enum { + VESIM_1_8V = 0, /*!< 1.80 V */ + VESIM_2_9V, /*!< 2.90 V */ +} regulator_voltage_vesim; + +/*! + * @enum regulator_voltage_vcam + * @brief PMIC regulator VCAM output voltage. + */ +enum { + VCAM_1_5V = 0, /*!< 1.50 V */ + VCAM_1_8V, /*!< 1.80 V */ + VCAM_2_5V, /*!< 2.50 V */ + VCAM_2_55V, /*!< 2.55 V */ + VCAM_2_6V, /*!< 2.60 V */ + VCAM_2_75V, /*!< 2.75 V */ + VCAM_2_8V, /*!< 2.80 V */ + VCAM_3V, /*!< 3.00 V */ +} regulator_voltage_vcam; + +/*! + * @enum regulator_voltage_vvib + * @brief PMIC linear regulator V_VIB output voltage. + */ +enum { + VVIB_1_3V = 0, /*!< 1.30 V */ + VVIB_1_8V, /*!< 1.80 V */ + VVIB_2V, /*!< 2 V */ + VVIB_3V, /*!< 3 V */ +} regulator_voltage_vvib; + +/*! + * @enum regulator_voltage_vmmc + * @brief MC13783 PMIC regulator VMMC output voltage. + */ +enum { + VMMC_1_6V = 0, /*!< 1.60 V */ + VMMC_1_8V, /*!< 1.80 V */ + VMMC_2V, /*!< 2.00 V */ + VMMC_2_6V, /*!< 2.60 V */ + VMMC_2_7V, /*!< 2.70 V */ + VMMC_2_8V, /*!< 2.80 V */ + VMMC_2_9V, /*!< 2.90 V */ + VMMC_3V, /*!< 3.00 V */ +} regulator_voltage_vmmc; + +/*! + * @enum regulator_voltage_vrf + * @brief PMIC regulator VRF output voltage. + */ +enum { + VRF_1_5V = 0, /*!< 1.500 V */ + VRF_1_875V, /*!< 1.875 V */ + VRF_2_7V, /*!< 2.700 V */ + VRF_2_775V, /*!< 2.775 V */ +} regulator_voltage_vrf; + +/*! + * @enum regulator_voltage_sw3 + * @brief PMIC Switch mode regulator SW3 output voltages. + */ +enum { + SW3_5V = 0, /*!< 5.0 V */ + SW3_5_5V = 3, /*!< 5.5 V */ +} regulator_voltage_sw3; + +/*! + * The \b TPmicDVSTransitionSpeed enum defines the rate with which the + * voltage transition occurs. + */ +enum { + ESysDependent, + E25mVEach4us, + E25mVEach8us, + E25mvEach16us +} DVS_transition_speed; + +/* + * Reg Regulator Mode 0 + */ +#define VAUDIO_EN_LSH 0 +#define VAUDIO_EN_WID 1 +#define VAUDIO_EN_ENABLE 1 +#define VAUDIO_EN_DISABLE 0 +#define VIOHI_EN_LSH 3 +#define VIOHI_EN_WID 1 +#define VIOHI_EN_ENABLE 1 +#define VIOHI_EN_DISABLE 0 +#define VIOLO_EN_LSH 6 +#define VIOLO_EN_WID 1 +#define VIOLO_EN_ENABLE 1 +#define VIOLO_EN_DISABLE 0 +#define VDIG_EN_LSH 9 +#define VDIG_EN_WID 1 +#define VDIG_EN_ENABLE 1 +#define VDIG_EN_DISABLE 0 +#define VGEN_EN_LSH 12 +#define VGEN_EN_WID 1 +#define VGEN_EN_ENABLE 1 +#define VGEN_EN_DISABLE 0 +#define VRFDIG_EN_LSH 15 +#define VRFDIG_EN_WID 1 +#define VRFDIG_EN_ENABLE 1 +#define VRFDIG_EN_DISABLE 0 +#define VRFREF_EN_LSH 18 +#define VRFREF_EN_WID 1 +#define VRFREF_EN_ENABLE 1 +#define VRFREF_EN_DISABLE 0 +#define VRFCP_EN_LSH 21 +#define VRFCP_EN_WID 1 +#define VRFCP_EN_ENABLE 1 +#define VRFCP_EN_DISABLE 0 + +/* + * Reg Regulator Mode 1 + */ +#define VSIM_EN_LSH 0 +#define VSIM_EN_WID 1 +#define VSIM_EN_ENABLE 1 +#define VSIM_EN_DISABLE 0 +#define VESIM_EN_LSH 3 +#define VESIM_EN_WID 1 +#define VESIM_EN_ENABLE 1 +#define VESIM_EN_DISABLE 0 +#define VCAM_EN_LSH 6 +#define VCAM_EN_WID 1 +#define VCAM_EN_ENABLE 1 +#define VCAM_EN_DISABLE 0 +#define VRFBG_EN_LSH 9 +#define VRFBG_EN_WID 1 +#define VRFBG_EN_ENABLE 1 +#define VRFBG_EN_DISABLE 0 +#define VVIB_EN_LSH 11 +#define VVIB_EN_WID 1 +#define VVIB_EN_ENABLE 1 +#define VVIB_EN_DISABLE 0 +#define VRF1_EN_LSH 12 +#define VRF1_EN_WID 1 +#define VRF1_EN_ENABLE 1 +#define VRF1_EN_DISABLE 0 +#define VRF2_EN_LSH 15 +#define VRF2_EN_WID 1 +#define VRF2_EN_ENABLE 1 +#define VRF2_EN_DISABLE 0 +#define VMMC1_EN_LSH 18 +#define VMMC1_EN_WID 1 +#define VMMC1_EN_ENABLE 1 +#define VMMC1_EN_DISABLE 0 +#define VMMC2_EN_LSH 21 +#define VMMC2_EN_WID 1 +#define VMMC2_EN_ENABLE 1 +#define VMMC2_EN_DISABLE 0 + +/* + * Reg Regulator Setting 0 + */ +#define VIOLO_LSH 2 +#define VIOLO_WID 2 +#define VDIG_LSH 4 +#define VDIG_WID 2 +#define VGEN_LSH 6 +#define VGEN_WID 3 +#define VRFDIG_LSH 9 +#define VRFDIG_WID 2 +#define VRFREF_LSH 11 +#define VRFREF_WID 2 +#define VRFCP_LSH 13 +#define VRFCP_WID 1 +#define VSIM_LSH 14 +#define VSIM_WID 1 +#define VESIM_LSH 15 +#define VESIM_WID 1 +#define VCAM_LSH 16 +#define VCAM_WID 3 + +/* + * Reg Regulator Setting 1 + */ +#define VVIB_LSH 0 +#define VVIB_WID 2 +#define VRF1_LSH 2 +#define VRF1_WID 2 +#define VRF2_LSH 4 +#define VRF2_WID 2 +#define VMMC1_LSH 6 +#define VMMC1_WID 3 +#define VMMC2_LSH 9 +#define VMMC2_WID 3 + +/* + * Reg Switcher 0 + */ +#define SW1A_LSH 0 +#define SW1A_WID 6 +#define SW1A_DVS_LSH 6 +#define SW1A_DVS_WID 6 +#define SW1A_STDBY_LSH 12 +#define SW1A_STDBY_WID 6 + +/* + * Reg Switcher 1 + */ +#define SW1B_LSH 0 +#define SW1B_WID 6 +#define SW1B_DVS_LSH 6 +#define SW1B_DVS_WID 6 +#define SW1B_STDBY_LSH 12 +#define SW1B_STDBY_WID 6 + +/* + * Reg Switcher 2 + */ +#define SW2A_LSH 0 +#define SW2A_WID 6 +#define SW2A_DVS_LSH 6 +#define SW2A_DVS_WID 6 +#define SW2A_STDBY_LSH 12 +#define SW2A_STDBY_WID 6 + +/* + * Reg Switcher 3 + */ +#define SW2B_LSH 0 +#define SW2B_WID 6 +#define SW2B_DVS_LSH 6 +#define SW2B_DVS_WID 6 +#define SW2B_STDBY_LSH 12 +#define SW2B_STDBY_WID 6 + +/* + * Reg Switcher 4 + */ +#define SW1A_MODE_LSH 0 +#define SW1A_MODE_WID 2 +#define SW1A_STBY_MODE_LSH 2 +#define SW1A_STBY_MODE_WID 2 +#define SW1A_DVS_SPEED_LSH 6 +#define SW1A_DVS_SPEED_WID 2 +#define SW1B_MODE_LSH 10 +#define SW1B_MODE_WID 2 +#define SW1B_STBY_MODE_LSH 12 +#define SW1B_STBY_MODE_WID 2 +#define SW1B_DVS_SPEED_LSH 14 +#define SW1B_DVS_SPEED_WID 2 + +/* + * Reg Switcher 5 + */ +#define SW2A_MODE_LSH 0 +#define SW2A_MODE_WID 2 +#define SW2A_STBY_MODE_LSH 2 +#define SW2A_STBY_MODE_WID 2 +#define SW2A_DVS_SPEED_LSH 6 +#define SW2A_DVS_SPEED_WID 2 +#define SW2B_MODE_LSH 10 +#define SW2B_MODE_WID 2 +#define SW2B_STBY_MODE_LSH 12 +#define SW2B_STBY_MODE_WID 2 +#define SW2B_DVS_SPEED_LSH 14 +#define SW2B_DVS_SPEED_WID 2 +#define SW3_LSH 18 +#define SW3_WID 2 +#define SW3_EN_LSH 20 +#define SW3_EN_WID 2 +#define SW3_EN_ENABLE 1 +#define SW3_EN_DISABLE 0 + +/* + * Reg Regulator Misc. + */ +#define GPO1_EN_LSH 6 +#define GPO1_EN_WID 1 +#define GPO1_EN_ENABLE 1 +#define GPO1_EN_DISABLE 0 +#define GPO2_EN_LSH 8 +#define GPO2_EN_WID 1 +#define GPO2_EN_ENABLE 1 +#define GPO2_EN_DISABLE 0 +#define GPO3_EN_LSH 10 +#define GPO3_EN_WID 1 +#define GPO3_EN_ENABLE 1 +#define GPO3_EN_DISABLE 0 +#define GPO4_EN_LSH 12 +#define GPO4_EN_WID 1 +#define GPO4_EN_ENABLE 1 +#define GPO4_EN_DISABLE 0 + +/* + * Switcher mode configuration + */ +#define SW_MODE_SYNC_RECT_EN 0 +#define SW_MODE_PULSE_NO_SKIP_EN 1 +#define SW_MODE_PULSE_SKIP_EN 2 +#define SW_MODE_LOW_POWER_EN 3 + +#define dvs_speed E25mvEach16us + +static int mc13783_vaudio_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VAUDIO_EN, VAUDIO_EN_ENABLE); + register_mask = BITFMASK(VAUDIO_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vaudio_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VAUDIO_EN, VAUDIO_EN_DISABLE); + register_mask = BITFMASK(VAUDIO_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_viohi_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VIOHI_EN, VIOHI_EN_ENABLE); + register_mask = BITFMASK(VIOHI_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_viohi_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VIOHI_EN, VIOHI_EN_DISABLE); + register_mask = BITFMASK(VIOHI_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_violo_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0, register1 = 0; + int voltage, mV = uV / 1000; + + if ((mV >= 1200) && (mV < 1300)) + voltage = VIOLO_1_2V; + else if ((mV >= 1300) && (mV < 1500)) + voltage = VIOLO_1_3V; + else if ((mV >= 1500) && (mV < 1800)) + voltage = VIOLO_1_5V; + else + voltage = VIOLO_1_8V; + + register_val = BITFVAL(VIOLO, voltage); + register_mask = BITFMASK(VIOLO); + register1 = REG_REGULATOR_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_violo_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_0, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VIOLO); + + switch (voltage) { + case VIOLO_1_2V: + mV = 1200; + break; + case VIOLO_1_3V: + mV = 1300; + break; + case VIOLO_1_5V: + mV = 1500; + break; + case VIOLO_1_8V: + mV = 1800; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13783_violo_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VIOLO_EN, VIOLO_EN_ENABLE); + register_mask = BITFMASK(VIOLO_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_violo_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VIOLO_EN, VIOLO_EN_DISABLE); + register_mask = BITFMASK(VIOLO_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vdig_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 1200) && (mV < 1300)) + voltage = VDIG_1_2V; + else if ((mV >= 1300) && (mV < 1500)) + voltage = VDIG_1_3V; + else if ((mV >= 1500) && (mV < 1800)) + voltage = VDIG_1_5V; + else + voltage = VDIG_1_8V; + + register_val = BITFVAL(VDIG, voltage); + register_mask = BITFMASK(VDIG); + register1 = REG_REGULATOR_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vdig_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_0, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VDIG); + + switch (voltage) { + case VDIG_1_2V: + mV = 1200; + break; + case VDIG_1_3V: + mV = 1300; + break; + case VDIG_1_5V: + mV = 1500; + break; + case VDIG_1_8V: + mV = 1800; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13783_vdig_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VDIG_EN, VDIG_EN_ENABLE); + register_mask = BITFMASK(VDIG_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vdig_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VDIG_EN, VDIG_EN_DISABLE); + register_mask = BITFMASK(VDIG_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vgen_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + int vgenid = rdev_get_id(reg); + + printk(KERN_INFO "VGEN ID is %d\n", vgenid); + + if ((mV >= 1100) && (mV < 1200)) + voltage = VGEN_1_1V; + else if ((mV >= 1200) && (mV < 1300)) + voltage = VGEN_1_2V; + else if ((mV >= 1300) && (mV < 1500)) + voltage = VGEN_1_3V; + else if ((mV >= 1500) && (mV < 1800)) + voltage = VGEN_1_5V; + else if ((mV >= 1800) && (mV < 2000)) + voltage = VGEN_1_8V; + else if ((mV >= 2000) && (mV < 2400)) + voltage = VGEN_2V; + else if ((mV >= 2400) && (mV < 2775)) + voltage = VGEN_2_4V; + else + voltage = VGEN_2_775V; + + register_val = BITFVAL(VGEN, voltage); + register_mask = BITFMASK(VGEN); + register1 = REG_REGULATOR_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vgen_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_0, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VGEN); + + switch (voltage) { + case VGEN_1_2V: + mV = 1200; + break; + case VGEN_1_3V: + mV = 1300; + break; + case VGEN_1_5V: + mV = 1500; + break; + case VGEN_1_8V: + mV = 1800; + break; + case VGEN_1_1V: + mV = 1100; + break; + case VGEN_2V: + mV = 2000; + break; + case VGEN_2_775V: + mV = 2775; + break; + case VGEN_2_4V: + mV = 2400; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13783_vgen_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VGEN_EN, VGEN_EN_ENABLE); + register_mask = BITFMASK(VGEN_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vgen_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VGEN_EN, VGEN_EN_DISABLE); + register_mask = BITFMASK(VGEN_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vrfdig_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 1200) && (mV < 1500)) + voltage = VRFDIG_1_2V; + else if ((mV >= 1500) && (mV < 1300)) + voltage = VRFDIG_1_5V; + else if ((mV >= 1800) && (mV < 1875)) + voltage = VRFDIG_1_8V; + else + voltage = VRFDIG_1_875V; + + register_val = BITFVAL(VRFDIG, voltage); + register_mask = BITFMASK(VRFDIG); + register1 = REG_REGULATOR_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vrfdig_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_0, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VRFDIG); + + switch (voltage) { + case VRFDIG_1_2V: + mV = 1200; + break; + case VRFDIG_1_5V: + mV = 1500; + break; + case VRFDIG_1_8V: + mV = 1800; + break; + case VRFDIG_1_875V: + mV = 1875; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13783_vrfdig_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VRFDIG_EN, VRFDIG_EN_ENABLE); + register_mask = BITFMASK(VRFDIG_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vrfdig_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VRFDIG_EN, VRFDIG_EN_DISABLE); + register_mask = BITFMASK(VRFDIG_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vrfref_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 2475) && (mV < 2600)) + voltage = VRFREF_2_475V; + else if ((mV >= 2600) && (mV < 2700)) + voltage = VRFREF_2_6V; + else if ((mV >= 2700) && (mV < 2775)) + voltage = VRFREF_2_7V; + else + voltage = VRFREF_2_775V; + + register_val = BITFVAL(VRFREF, voltage); + register_mask = BITFMASK(VRFREF); + register1 = REG_REGULATOR_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vrfref_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_0, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VRFREF); + + switch (voltage) { + case VRFREF_2_475V: + mV = 2475; + break; + case VRFREF_2_6V: + mV = 2600; + break; + case VRFREF_2_7V: + mV = 2700; + break; + case VRFREF_2_775V: + mV = 2775; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13783_vrfref_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VRFREF_EN, VRFREF_EN_ENABLE); + register_mask = BITFMASK(VRFREF_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vrfref_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VRFREF_EN, VRFREF_EN_DISABLE); + register_mask = BITFMASK(VRFREF_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vrfcp_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 2700) && (mV < 2775)) + voltage = VRFCP_2_7V; + else + voltage = VRFCP_2_775V; + + register_val = BITFVAL(VRFCP, voltage); + register_mask = BITFMASK(VRFCP); + register1 = REG_REGULATOR_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vrfcp_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_0, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VRFCP); + + switch (voltage) { + case VRFCP_2_7V: + mV = 2700; + break; + case VRFCP_2_775V: + mV = 2775; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13783_vrfcp_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VRFCP_EN, VRFCP_EN_ENABLE); + register_mask = BITFMASK(VRFCP_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vrfcp_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VRFCP_EN, VRFCP_EN_DISABLE); + register_mask = BITFMASK(VRFCP_EN); + register1 = REG_REGULATOR_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vsim_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 1800) && (mV < 2900)) + voltage = VSIM_1_8V; + else + voltage = VSIM_2_9V; + + register_val = BITFVAL(VSIM, voltage); + register_mask = BITFMASK(VSIM); + register1 = REG_REGULATOR_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vsim_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_0, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VSIM); + + switch (voltage) { + case VSIM_1_8V: + mV = 1800; + break; + case VSIM_2_9V: + mV = 1900; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13783_vsim_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VSIM_EN, VSIM_EN_ENABLE); + register_mask = BITFMASK(VSIM_EN); + register1 = REG_REGULATOR_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vsim_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VSIM_EN, VSIM_EN_DISABLE); + register_mask = BITFMASK(VSIM_EN); + register1 = REG_REGULATOR_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vesim_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 1800) && (mV < 2900)) + voltage = VESIM_1_8V; + else + voltage = VESIM_2_9V; + + register_val = BITFVAL(VESIM, voltage); + register_mask = BITFMASK(VESIM); + register1 = REG_REGULATOR_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vesim_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_0, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VESIM); + + switch (voltage) { + case VESIM_1_8V: + mV = 1800; + break; + case VESIM_2_9V: + mV = 1900; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13783_vesim_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VESIM_EN, VESIM_EN_ENABLE); + register_mask = BITFMASK(VESIM_EN); + register1 = REG_REGULATOR_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vesim_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VESIM_EN, VESIM_EN_DISABLE); + register_mask = BITFMASK(VESIM_EN); + register1 = REG_REGULATOR_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vcam_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 1500) && (mV < 1800)) + voltage = VCAM_1_5V; + else if ((mV >= 1800) && (mV < 2500)) + voltage = VCAM_1_8V; + else if ((mV >= 2500) && (mV < 2550)) + voltage = VCAM_2_5V; + else if ((mV >= 2550) && (mV < 2600)) + voltage = VCAM_2_55V; + if ((mV >= 2600) && (mV < 2750)) + voltage = VCAM_2_6V; + else if ((mV >= 2750) && (mV < 2800)) + voltage = VCAM_2_75V; + else if ((mV >= 2800) && (mV < 3000)) + voltage = VCAM_2_8V; + else + voltage = VCAM_3V; + + register_val = BITFVAL(VCAM, voltage); + register_mask = BITFMASK(VCAM); + register1 = REG_REGULATOR_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vcam_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_0, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VCAM); + + switch (voltage) { + case VCAM_1_5V: + mV = 1500; + break; + case VCAM_1_8V: + mV = 1800; + break; + case VCAM_2_5V: + mV = 2500; + break; + case VCAM_2_55V: + mV = 2550; + break; + case VCAM_2_6V: + mV = 2600; + break; + case VCAM_2_75V: + mV = 2750; + break; + case VCAM_2_8V: + mV = 2800; + break; + case VCAM_3V: + mV = 3000; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13783_vcam_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VCAM_EN, VCAM_EN_ENABLE); + register_mask = BITFMASK(VCAM_EN); + register1 = REG_REGULATOR_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vcam_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VCAM_EN, VCAM_EN_DISABLE); + register_mask = BITFMASK(VCAM_EN); + register1 = REG_REGULATOR_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vvib_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 1300) && (mV < 1800)) + voltage = VVIB_1_3V; + else if ((mV >= 1800) && (mV < 2000)) + voltage = VVIB_1_8V; + else if ((mV >= 2000) && (mV < 3000)) + voltage = VVIB_2V; + else + voltage = VVIB_3V; + + register_val = BITFVAL(VVIB, voltage); + register_mask = BITFMASK(VVIB); + register1 = REG_REGULATOR_SETTING_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vvib_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_1, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VVIB); + + switch (voltage) { + case VVIB_1_3V: + mV = 1300; + break; + case VVIB_1_8V: + mV = 1800; + break; + case VVIB_2V: + mV = 2000; + break; + case VVIB_3V: + mV = 3000; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13783_vvib_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VVIB_EN, VVIB_EN_ENABLE); + register_mask = BITFMASK(VVIB_EN); + register1 = REG_REGULATOR_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vvib_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VVIB_EN, VVIB_EN_DISABLE); + register_mask = BITFMASK(VVIB_EN); + register1 = REG_REGULATOR_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vrf_set_voltage(struct regulator_dev *reg, int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, rf = rdev_get_id(reg), mV = uV / 1000; + + if ((mV >= 1500) && (mV < 1875)) + voltage = VRF_1_5V; + else if ((mV >= 1875) && (mV < 2700)) + voltage = VRF_1_875V; + else if ((mV >= 2700) && (mV < 2775)) + voltage = VRF_2_7V; + else + voltage = VRF_2_775V; + + switch (rf) { + case MC13783_VRF1: + register_val = BITFVAL(VRF1, voltage); + register_mask = BITFMASK(VRF1); + break; + case MC13783_VRF2: + register_val = BITFVAL(VRF2, voltage); + register_mask = BITFMASK(VRF2); + break; + default: + return -EINVAL; + } + + register1 = REG_REGULATOR_SETTING_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vrf_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, rf = rdev_get_id(reg), mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_1, + ®ister_val, PMIC_ALL_BITS)); + + switch (rf) { + case MC13783_VRF1: + voltage = BITFEXT(register_val, VRF1); + break; + case MC13783_VRF2: + voltage = BITFEXT(register_val, VRF2); + break; + default: + return -EINVAL; + }; + + switch (voltage) { + case VRF_1_5V: + mV = 1500; + break; + case VRF_1_875V: + mV = 1875; + break; + case VRF_2_7V: + mV = 2700; + break; + case VRF_2_775V: + mV = 2775; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13783_vrf_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int vrf = rdev_get_id(reg); + + switch (vrf) { + case MC13783_VRF1: + register_val = BITFVAL(VRF1_EN, VRF1_EN_ENABLE); + register_mask = BITFMASK(VRF1_EN); + break; + case MC13783_VRF2: + register_val = BITFVAL(VRF2_EN, VRF2_EN_ENABLE); + register_mask = BITFMASK(VRF2_EN); + break; + default: + return -EINVAL; + }; + + register1 = REG_REGULATOR_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vrf_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int vrf = rdev_get_id(reg); + + switch (vrf) { + case MC13783_VRF1: + register_val = BITFVAL(VRF1_EN, VRF1_EN_DISABLE); + register_mask = BITFMASK(VRF1_EN); + break; + case MC13783_VRF2: + register_val = BITFVAL(VRF2_EN, VRF2_EN_DISABLE); + register_mask = BITFMASK(VRF2_EN); + break; + default: + return -EINVAL; + }; + + register1 = REG_REGULATOR_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vmmc_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mmc = rdev_get_id(reg), mV = uV / 1000; + + printk(KERN_INFO "VMMC ID is %d\n", mmc); + + if ((mV >= 1600) && (mV < 1800)) + voltage = VMMC_1_6V; + else if ((mV >= 1800) && (mV < 2000)) + voltage = VMMC_1_8V; + else if ((mV >= 2000) && (mV < 2600)) + voltage = VMMC_2V; + else if ((mV >= 2600) && (mV < 2700)) + voltage = VMMC_2_6V; + else if ((mV >= 2700) && (mV < 2800)) + voltage = VMMC_2_7V; + else if ((mV >= 2800) && (mV < 2900)) + voltage = VMMC_2_8V; + else if ((mV >= 2900) && (mV < 3000)) + voltage = VMMC_2_9V; + else + voltage = VMMC_3V; + + switch (mmc) { + case MC13783_VMMC1: + register_val = BITFVAL(VMMC1, voltage); + register_mask = BITFMASK(VMMC1); + break; + case MC13783_VMMC2: + register_val = BITFVAL(VMMC2, voltage); + register_mask = BITFMASK(VMMC2); + break; + default: + return -EINVAL; + } + + register1 = REG_REGULATOR_SETTING_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vmmc_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mmc = rdev_get_id(reg), mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_1, + ®ister_val, PMIC_ALL_BITS)); + + switch (mmc) { + case MC13783_VMMC1: + voltage = BITFEXT(register_val, VMMC1); + break; + case MC13783_VMMC2: + voltage = BITFEXT(register_val, VMMC2); + break; + default: + return -EINVAL; + } + + switch (voltage) { + case VMMC_1_6V: + mV = 1600; + break; + case VMMC_1_8V: + mV = 1800; + break; + case VMMC_2V: + mV = 2000; + break; + case VMMC_2_6V: + mV = 2600; + break; + case VMMC_2_7V: + mV = 2700; + break; + case VMMC_2_8V: + mV = 2800; + break; + case VMMC_2_9V: + mV = 2900; + break; + case VMMC_3V: + mV = 3000; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13783_vmmc_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int vmmc = rdev_get_id(reg); + + switch (vmmc) { + case MC13783_VMMC1: + register_val = BITFVAL(VMMC1_EN, VMMC1_EN_ENABLE); + register_mask = BITFMASK(VMMC1_EN); + break; + case MC13783_VMMC2: + register_val = BITFVAL(VMMC2_EN, VMMC2_EN_ENABLE); + register_mask = BITFMASK(VMMC2_EN); + break; + default: + return -EINVAL; + }; + + register1 = REG_REGULATOR_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_vmmc_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int vmmc = rdev_get_id(reg); + + switch (vmmc) { + case MC13783_VMMC1: + register_val = BITFVAL(VMMC1_EN, VMMC1_EN_DISABLE); + register_mask = BITFMASK(VMMC1_EN); + break; + case MC13783_VMMC2: + register_val = BITFVAL(VMMC2_EN, VMMC2_EN_DISABLE); + register_mask = BITFMASK(VMMC2_EN); + break; + default: + return -EINVAL; + }; + + register1 = REG_REGULATOR_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_gpo_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int gpo = rdev_get_id(reg); + + switch (gpo) { + case MC13783_GPO1: + register_val = BITFVAL(GPO1_EN, GPO1_EN_ENABLE); + register_mask = BITFMASK(GPO1_EN); + break; + case MC13783_GPO2: + register_val = BITFVAL(GPO2_EN, GPO2_EN_ENABLE); + register_mask = BITFMASK(GPO2_EN); + break; + case MC13783_GPO3: + register_val = BITFVAL(GPO3_EN, GPO3_EN_ENABLE); + register_mask = BITFMASK(GPO3_EN); + break; + case MC13783_GPO4: + register_val = BITFVAL(GPO4_EN, GPO4_EN_ENABLE); + register_mask = BITFMASK(GPO4_EN); + break; + default: + return -EINVAL; + }; + + register1 = REG_POWER_MISCELLANEOUS; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_gpo_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int gpo = rdev_get_id(reg); + + switch (gpo) { + case MC13783_GPO1: + register_val = BITFVAL(GPO1_EN, GPO1_EN_DISABLE); + register_mask = BITFMASK(GPO1_EN); + break; + case MC13783_GPO2: + register_val = BITFVAL(GPO2_EN, GPO2_EN_DISABLE); + register_mask = BITFMASK(GPO2_EN); + break; + case MC13783_GPO3: + register_val = BITFVAL(GPO3_EN, GPO3_EN_DISABLE); + register_mask = BITFMASK(GPO3_EN); + break; + case MC13783_GPO4: + register_val = BITFVAL(GPO4_EN, GPO4_EN_DISABLE); + register_mask = BITFMASK(GPO4_EN); + break; + default: + return -EINVAL; + }; + + register1 = REG_POWER_MISCELLANEOUS; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_sw3_set_voltage(struct regulator_dev *reg, int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0, register1 = 0; + int voltage, mV = uV / 1000; + + if ((mV >= 5000) && (mV < 5500)) + voltage = SW3_5V; + else + voltage = SW3_5_5V; + + register_val = BITFVAL(SW3, voltage); + register_mask = BITFMASK(SW3); + register1 = REG_SWITCHERS_5; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_sw3_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_5, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, SW3); + + if (voltage == SW3_5_5V) + mV = 5500; + else + mV = 5000; + + return mV * 1000; +} + +static int mc13783_sw3_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(SW3_EN, SW3_EN_ENABLE); + register_mask = BITFMASK(SW3_EN); + register1 = REG_SWITCHERS_5; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_sw3_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(SW3_EN, SW3_EN_DISABLE); + register_mask = BITFMASK(SW3_EN); + register1 = REG_SWITCHERS_5; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_sw_set_normal_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1 = 0; + int voltage, sw = rdev_get_id(reg), mV = uV / 1000; + + if ((mV >= 900) && (mV < 925)) + voltage = SW_0_9V; + else if ((mV >= 925) && (mV < 950)) + voltage = SW_0_925V; + else if ((mV >= 950) && (mV < 975)) + voltage = SW_0_95V; + else if ((mV >= 975) && (mV < 1000)) + voltage = SW_0_975V; + else if ((mV >= 1000) && (mV < 1025)) + voltage = SW_1V; + else if ((mV >= 1025) && (mV < 1050)) + voltage = SW_1_025V; + else if ((mV >= 1050) && (mV < 1075)) + voltage = SW_1_05V; + else if ((mV >= 1075) && (mV < 1100)) + voltage = SW_1_075V; + else if ((mV >= 1100) && (mV < 1125)) + voltage = SW_1_1V; + else if ((mV >= 1125) && (mV < 1150)) + voltage = SW_1_125V; + else if ((mV >= 1150) && (mV < 1175)) + voltage = SW_1_15V; + else if ((mV >= 1175) && (mV < 1200)) + voltage = SW_1_175V; + else if ((mV >= 1200) && (mV < 1225)) + voltage = SW_1_2V; + else if ((mV >= 1225) && (mV < 1250)) + voltage = SW_1_225V; + else if ((mV >= 1250) && (mV < 1275)) + voltage = SW_1_25V; + else if ((mV >= 1275) && (mV < 1300)) + voltage = SW_1_275V; + else if ((mV >= 1300) && (mV < 1325)) + voltage = SW_1_3V; + else if ((mV >= 1325) && (mV < 1350)) + voltage = SW_1_325V; + else if ((mV >= 1350) && (mV < 1375)) + voltage = SW_1_35V; + else if ((mV >= 1375) && (mV < 1400)) + voltage = SW_1_375V; + else if ((mV >= 1400) && (mV < 1425)) + voltage = SW_1_4V; + else if ((mV >= 1425) && (mV < 1450)) + voltage = SW_1_425V; + else if ((mV >= 1450) && (mV < 1475)) + voltage = SW_1_45V; + else if ((mV >= 1475) && (mV < 1500)) + voltage = SW_1_475V; + else if ((mV >= 1500) && (mV < 1525)) + voltage = SW_1_5V; + else if ((mV >= 1525) && (mV < 1550)) + voltage = SW_1_525V; + else if ((mV >= 1550) && (mV < 1575)) + voltage = SW_1_55V; + else if ((mV >= 1575) && (mV < 1600)) + voltage = SW_1_575V; + else if ((mV >= 1600) && (mV < 1625)) + voltage = SW_1_6V; + else if ((mV >= 1625) && (mV < 1650)) + voltage = SW_1_625V; + else if ((mV >= 1650) && (mV < 1675)) + voltage = SW_1_65V; + else if ((mV >= 1675) && (mV < 1700)) + voltage = SW_1_675V; + else if ((mV >= 1700) && (mV < 1800)) + voltage = SW_1_7V; + else if ((mV >= 1800) && (mV < 1850)) + voltage = SW_1_8V; + else if ((mV >= 1850) && (mV < 2000)) + voltage = SW_1_85V; + else if ((mV >= 2000) && (mV < 2100)) + voltage = SW_2V; + else if ((mV >= 2100) && (mV < 2200)) + voltage = SW_2_1V; + else + voltage = SW_2_2V; + + switch (sw) { + case MC13783_SW1A: + register1 = REG_SWITCHERS_0; + register_val = BITFVAL(SW1A, voltage); + register_mask = BITFMASK(SW1A); + break; + case MC13783_SW1B: + register1 = REG_SWITCHERS_1; + register_val = BITFVAL(SW1B, voltage); + register_mask = BITFMASK(SW1B); + break; + case MC13783_SW2A: + register1 = REG_SWITCHERS_2; + register_val = BITFVAL(SW2A, voltage); + register_mask = BITFMASK(SW2A); + break; + case MC13783_SW2B: + register1 = REG_SWITCHERS_3; + register_val = BITFVAL(SW2B, voltage); + register_mask = BITFMASK(SW2B); + break; + default: + return -EINVAL; + } + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_sw_get_normal_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0, sw = rdev_get_id(reg); + + switch (sw) { + case MC13783_SW1A: + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_0, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, SW1A); + break; + case MC13783_SW1B: + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_1, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, SW1B); + break; + case MC13783_SW2A: + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_2, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, SW2A); + break; + case MC13783_SW2B: + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_3, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, SW2B); + break; + default: + return -EINVAL; + } + + switch (voltage) { + case SW_0_9V: + mV = 900; + break; + case SW_0_925V: + mV = 925; + break; + case SW_0_95V: + mV = 950; + break; + case SW_0_975V: + mV = 975; + break; + case SW_1V: + mV = 1000; + break; + case SW_1_025V: + mV = 1025; + break; + case SW_1_05V: + mV = 1050; + break; + case SW_1_075V: + mV = 1075; + break; + case SW_1_1V: + mV = 1100; + break; + case SW_1_125V: + mV = 1125; + break; + case SW_1_15V: + mV = 1150; + break; + case SW_1_175V: + mV = 1175; + break; + case SW_1_2V: + mV = 1200; + break; + case SW_1_225V: + mV = 1225; + break; + case SW_1_25V: + mV = 1250; + break; + case SW_1_275V: + mV = 1275; + break; + case SW_1_3V: + mV = 1300; + break; + case SW_1_325V: + mV = 1325; + break; + case SW_1_35V: + mV = 1350; + break; + case SW_1_375V: + mV = 1375; + break; + case SW_1_4V: + mV = 1400; + break; + case SW_1_425V: + mV = 1425; + break; + case SW_1_45V: + mV = 1450; + break; + case SW_1_475V: + mV = 1475; + break; + case SW_1_5V: + mV = 1500; + break; + case SW_1_525V: + mV = 1525; + break; + case SW_1_55V: + mV = 1550; + break; + case SW_1_575V: + mV = 1575; + break; + case SW_1_6V: + mV = 1600; + break; + case SW_1_625V: + mV = 1625; + break; + case SW_1_65V: + mV = 1650; + break; + case SW_1_675V: + mV = 1675; + break; + case SW_1_7V: + mV = 1700; + break; + case SW_1_8V: + mV = 1800; + break; + case SW_1_85V: + mV = 1850; + break; + case SW_2V: + mV = 2000; + break; + case SW_2_1V: + mV = 2100; + break; + case SW_2_2V: + mV = 2200; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13783_sw_normal_enable(struct regulator_dev *reg) +{ + return 0; +} + +static int mc13783_sw_normal_disable(struct regulator_dev *reg) +{ + return 0; +} + +static int mc13783_sw_stby_enable(struct regulator_dev *reg) +{ + return 0; +} + +static int mc13783_sw_stby_disable(struct regulator_dev *reg) +{ + return 0; +} + +static int mc13783_sw_set_stby_voltage(struct regulator_dev *reg, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1 = 0; + int voltage, sw = rdev_get_id(reg), mV = uV / 1000; + + if ((mV >= 900) && (mV < 925)) + voltage = SW_0_9V; + else if ((mV >= 925) && (mV < 950)) + voltage = SW_0_925V; + else if ((mV >= 950) && (mV < 975)) + voltage = SW_0_95V; + else if ((mV >= 975) && (mV < 1000)) + voltage = SW_0_975V; + else if ((mV >= 1000) && (mV < 1025)) + voltage = SW_1V; + else if ((mV >= 1025) && (mV < 1050)) + voltage = SW_1_025V; + else if ((mV >= 1050) && (mV < 1075)) + voltage = SW_1_05V; + else if ((mV >= 1075) && (mV < 1100)) + voltage = SW_1_075V; + else if ((mV >= 1100) && (mV < 1125)) + voltage = SW_1_1V; + else if ((mV >= 1125) && (mV < 1150)) + voltage = SW_1_125V; + else if ((mV >= 1150) && (mV < 1175)) + voltage = SW_1_15V; + else if ((mV >= 1175) && (mV < 1200)) + voltage = SW_1_175V; + else if ((mV >= 1200) && (mV < 1225)) + voltage = SW_1_2V; + else if ((mV >= 1225) && (mV < 1250)) + voltage = SW_1_225V; + else if ((mV >= 1250) && (mV < 1275)) + voltage = SW_1_25V; + else if ((mV >= 1275) && (mV < 1300)) + voltage = SW_1_275V; + else if ((mV >= 1300) && (mV < 1325)) + voltage = SW_1_3V; + else if ((mV >= 1325) && (mV < 1350)) + voltage = SW_1_325V; + else if ((mV >= 1350) && (mV < 1375)) + voltage = SW_1_35V; + else if ((mV >= 1375) && (mV < 1400)) + voltage = SW_1_375V; + else if ((mV >= 1400) && (mV < 1425)) + voltage = SW_1_4V; + else if ((mV >= 1425) && (mV < 1450)) + voltage = SW_1_425V; + else if ((mV >= 1450) && (mV < 1475)) + voltage = SW_1_45V; + else if ((mV >= 1475) && (mV < 1500)) + voltage = SW_1_475V; + else if ((mV >= 1500) && (mV < 1525)) + voltage = SW_1_5V; + else if ((mV >= 1525) && (mV < 1550)) + voltage = SW_1_525V; + else if ((mV >= 1550) && (mV < 1575)) + voltage = SW_1_55V; + else if ((mV >= 1575) && (mV < 1600)) + voltage = SW_1_575V; + else if ((mV >= 1600) && (mV < 1625)) + voltage = SW_1_6V; + else if ((mV >= 1625) && (mV < 1650)) + voltage = SW_1_625V; + else if ((mV >= 1650) && (mV < 1675)) + voltage = SW_1_65V; + else if ((mV >= 1675) && (mV < 1700)) + voltage = SW_1_675V; + else if ((mV >= 1700) && (mV < 1800)) + voltage = SW_1_7V; + else if ((mV >= 1800) && (mV < 1850)) + voltage = SW_1_8V; + else if ((mV >= 1850) && (mV < 2000)) + voltage = SW_1_85V; + else if ((mV >= 2000) && (mV < 2100)) + voltage = SW_2V; + else if ((mV >= 2100) && (mV < 2200)) + voltage = SW_2_1V; + else + voltage = SW_2_2V; + + switch (sw) { + case MC13783_SW1A: + register1 = REG_SWITCHERS_0; + register_val = BITFVAL(SW1A_STDBY, voltage); + register_mask = BITFMASK(SW1A_STDBY); + break; + case MC13783_SW1B: + register1 = REG_SWITCHERS_1; + register_val = BITFVAL(SW1B_STDBY, voltage); + register_mask = BITFMASK(SW1B_STDBY); + break; + case MC13783_SW2A: + register1 = REG_SWITCHERS_2; + register_val = BITFVAL(SW2A_STDBY, voltage); + register_mask = BITFMASK(SW2A_STDBY); + break; + case MC13783_SW2B: + register1 = REG_SWITCHERS_3; + register_val = BITFVAL(SW2B_STDBY, voltage); + register_mask = BITFMASK(SW2B_STDBY); + break; + default: + return -EINVAL; + } + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13783_sw_set_normal_mode(struct regulator_dev *reg, + unsigned int mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int register1 = 0; + unsigned int l_mode; + int sw = rdev_get_id(reg); + + switch (mode) { + case REGULATOR_MODE_FAST: + /* SYNC RECT mode */ + l_mode = SW_MODE_SYNC_RECT_EN; + break; + case REGULATOR_MODE_NORMAL: + /* PULSE SKIP mode */ + l_mode = SW_MODE_PULSE_SKIP_EN; + break; + case REGULATOR_MODE_IDLE: + /* LOW POWER mode */ + l_mode = SW_MODE_LOW_POWER_EN; + break; + case REGULATOR_MODE_STANDBY: + /* NO PULSE SKIP mode */ + l_mode = SW_MODE_PULSE_NO_SKIP_EN; + break; + default: + return -EINVAL; + } + + switch (sw) { + case MC13783_SW1A: + reg_val = BITFVAL(SW1A_MODE, l_mode); + reg_mask = BITFMASK(SW1A_MODE); + register1 = REG_SWITCHERS_4; + break; + case MC13783_SW1B: + reg_val = BITFVAL(SW1B_MODE, l_mode); + reg_mask = BITFMASK(SW1B_MODE); + register1 = REG_SWITCHERS_4; + break; + case MC13783_SW2A: + reg_val = BITFVAL(SW2A_MODE, l_mode); + reg_mask = BITFMASK(SW2A_MODE); + register1 = REG_SWITCHERS_5; + break; + case MC13783_SW2B: + reg_val = BITFVAL(SW2B_MODE, l_mode); + reg_mask = BITFMASK(SW2B_MODE); + register1 = REG_SWITCHERS_5; + break; + default: + return -EINVAL; + } + + return pmic_write_reg(register1, reg_val, reg_mask); +} + +static unsigned int mc13783_sw_get_normal_mode(struct regulator_dev *reg) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int register1 = 0; + unsigned int l_mode = 0; + int sw = rdev_get_id(reg); + int ret = 0; + + switch (sw) { + case MC13783_SW1A: + reg_mask = BITFMASK(SW1A_MODE); + register1 = REG_SWITCHERS_4; + break; + case MC13783_SW1B: + reg_mask = BITFMASK(SW1B_MODE); + register1 = REG_SWITCHERS_4; + break; + case MC13783_SW2A: + reg_mask = BITFMASK(SW2A_MODE); + register1 = REG_SWITCHERS_5; + break; + case MC13783_SW2B: + reg_mask = BITFMASK(SW2B_MODE); + register1 = REG_SWITCHERS_5; + break; + default: + return -EINVAL; + } + + ret = pmic_read_reg(register1, ®_val, reg_mask); + if (ret != 0) + return ret; + + switch (sw) { + case MC13783_SW1A: + l_mode = BITFEXT(reg_val, SW1A_MODE); + break; + case MC13783_SW1B: + l_mode = BITFEXT(reg_val, SW1B_MODE); + break; + case MC13783_SW2A: + l_mode = BITFEXT(reg_val, SW2A_MODE); + break; + case MC13783_SW2B: + l_mode = BITFEXT(reg_val, SW2B_MODE); + break; + default: + return -EINVAL; + } + + if (l_mode == SW_MODE_SYNC_RECT_EN) { + return REGULATOR_MODE_FAST; + } else if (l_mode == SW_MODE_PULSE_NO_SKIP_EN) { + return REGULATOR_MODE_STANDBY; + } else if (l_mode == SW_MODE_PULSE_SKIP_EN) { + return REGULATOR_MODE_NORMAL; + } else if (l_mode == SW_MODE_LOW_POWER_EN) { + return REGULATOR_MODE_IDLE; + } else { + return -EINVAL; + } +} + +static int mc13783_sw_set_stby_mode(struct regulator_dev *reg, + unsigned int mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int register1 = 0; + unsigned int l_mode; + int sw = rdev_get_id(reg); + + switch (mode) { + case REGULATOR_MODE_FAST: + /* SYNC RECT mode */ + l_mode = SW_MODE_SYNC_RECT_EN; + break; + case REGULATOR_MODE_NORMAL: + /* PULSE SKIP mode */ + l_mode = SW_MODE_PULSE_SKIP_EN; + break; + case REGULATOR_MODE_IDLE: + /* LOW POWER mode */ + l_mode = SW_MODE_LOW_POWER_EN; + break; + case REGULATOR_MODE_STANDBY: + /* NO PULSE SKIP mode */ + l_mode = SW_MODE_PULSE_NO_SKIP_EN; + break; + default: + return -EINVAL; + } + + switch (sw) { + case MC13783_SW1A: + reg_val = BITFVAL(SW1A_STBY_MODE, l_mode); + reg_mask = BITFMASK(SW1A_STBY_MODE); + register1 = REG_SWITCHERS_4; + break; + case MC13783_SW1B: + reg_val = BITFVAL(SW1B_STBY_MODE, l_mode); + reg_mask = BITFMASK(SW1B_STBY_MODE); + register1 = REG_SWITCHERS_4; + break; + case MC13783_SW2A: + reg_val = BITFVAL(SW2A_STBY_MODE, l_mode); + reg_mask = BITFMASK(SW2A_STBY_MODE); + register1 = REG_SWITCHERS_5; + break; + case MC13783_SW2B: + reg_val = BITFVAL(SW2B_STBY_MODE, l_mode); + reg_mask = BITFMASK(SW2B_STBY_MODE); + register1 = REG_SWITCHERS_5; + break; + default: + return -EINVAL; + } + + return pmic_write_reg(register1, reg_val, reg_mask); +} + +static struct regulator_ops mc13783_vaudio_ops = { + .enable = mc13783_vaudio_enable, + .disable = mc13783_vaudio_disable, +}; + +static struct regulator_ops mc13783_viohi_ops = { + .enable = mc13783_viohi_enable, + .disable = mc13783_viohi_disable, +}; + +static struct regulator_ops mc13783_violo_ops = { + .set_voltage = mc13783_violo_set_voltage, + .get_voltage = mc13783_violo_get_voltage, + .enable = mc13783_violo_enable, + .disable = mc13783_violo_disable, +}; + +static struct regulator_ops mc13783_vdig_ops = { + .set_voltage = mc13783_vdig_set_voltage, + .get_voltage = mc13783_vdig_get_voltage, + .enable = mc13783_vdig_enable, + .disable = mc13783_vdig_disable, +}; + +static struct regulator_ops mc13783_vgen_ops = { + .set_voltage = mc13783_vgen_set_voltage, + .get_voltage = mc13783_vgen_get_voltage, + .enable = mc13783_vgen_enable, + .disable = mc13783_vgen_disable, +}; + +static struct regulator_ops mc13783_vrfdig_ops = { + .set_voltage = mc13783_vrfdig_set_voltage, + .get_voltage = mc13783_vrfdig_get_voltage, + .enable = mc13783_vrfdig_enable, + .disable = mc13783_vrfdig_disable, +}; + +static struct regulator_ops mc13783_vrfref_ops = { + .set_voltage = mc13783_vrfref_set_voltage, + .get_voltage = mc13783_vrfref_get_voltage, + .enable = mc13783_vrfref_enable, + .disable = mc13783_vrfref_disable, +}; + +static struct regulator_ops mc13783_vrfcp_ops = { + .set_voltage = mc13783_vrfcp_set_voltage, + .get_voltage = mc13783_vrfcp_get_voltage, + .enable = mc13783_vrfcp_enable, + .disable = mc13783_vrfcp_disable, +}; + +static struct regulator_ops mc13783_vsim_ops = { + .set_voltage = mc13783_vsim_set_voltage, + .get_voltage = mc13783_vsim_get_voltage, + .enable = mc13783_vsim_enable, + .disable = mc13783_vsim_disable, +}; + +static struct regulator_ops mc13783_vesim_ops = { + .set_voltage = mc13783_vesim_set_voltage, + .get_voltage = mc13783_vesim_get_voltage, + .enable = mc13783_vesim_enable, + .disable = mc13783_vesim_disable, +}; + +static struct regulator_ops mc13783_vcam_ops = { + .set_voltage = mc13783_vcam_set_voltage, + .get_voltage = mc13783_vcam_get_voltage, + .enable = mc13783_vcam_enable, + .disable = mc13783_vcam_disable, +}; + +static struct regulator_ops mc13783_vvib_ops = { + .set_voltage = mc13783_vvib_set_voltage, + .get_voltage = mc13783_vvib_get_voltage, + .enable = mc13783_vvib_enable, + .disable = mc13783_vvib_disable, +}; + +static struct regulator_ops mc13783_vrf_ops = { + .set_voltage = mc13783_vrf_set_voltage, + .get_voltage = mc13783_vrf_get_voltage, + .enable = mc13783_vrf_enable, + .disable = mc13783_vrf_disable, +}; + +static struct regulator_ops mc13783_vmmc_ops = { + .set_voltage = mc13783_vmmc_set_voltage, + .get_voltage = mc13783_vmmc_get_voltage, + .enable = mc13783_vmmc_enable, + .disable = mc13783_vmmc_disable, +}; + +static struct regulator_ops mc13783_gpo_ops = { + .enable = mc13783_gpo_enable, + .disable = mc13783_gpo_disable, +}; + +static struct regulator_ops mc13783_sw3_ops = { + .set_voltage = mc13783_sw3_set_voltage, + .get_voltage = mc13783_sw3_get_voltage, + .enable = mc13783_sw3_enable, + .disable = mc13783_sw3_disable, +}; + +static struct regulator_ops mc13783_sw1_ops = { + .set_voltage = mc13783_sw_set_normal_voltage, + .get_voltage = mc13783_sw_get_normal_voltage, + .get_mode = mc13783_sw_get_normal_mode, + .set_mode = mc13783_sw_set_normal_mode, + .set_suspend_voltage = mc13783_sw_set_stby_voltage, + .set_suspend_enable = mc13783_sw_stby_enable, + .set_suspend_disable = mc13783_sw_stby_disable, + .set_suspend_mode = mc13783_sw_set_stby_mode, +}; + +static struct regulator_ops mc13783_sw_normal_ops = { + .set_voltage = mc13783_sw_set_normal_voltage, + .get_voltage = mc13783_sw_get_normal_voltage, + .get_mode = mc13783_sw_get_normal_mode, + .set_mode = mc13783_sw_set_normal_mode, + .enable = mc13783_sw_normal_enable, + .disable = mc13783_sw_normal_disable, +}; + +static struct regulator_desc reg_mc13783[] = { + { + .name = "SW1A", + .id = MC13783_SW1A, + .ops = &mc13783_sw1_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "SW1B", + .id = MC13783_SW1B, + .ops = &mc13783_sw_normal_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "SW2A", + .id = MC13783_SW2A, + .ops = &mc13783_sw_normal_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "SW2B", + .id = MC13783_SW2B, + .ops = &mc13783_sw_normal_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "SW3", + .id = MC13783_SW3, + .ops = &mc13783_sw3_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VAUDIO", + .id = MC13783_VAUDIO, + .ops = &mc13783_vaudio_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VIOHI", + .id = MC13783_VIOHI, + .ops = &mc13783_viohi_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VIOLO", + .id = MC13783_VIOLO, + .ops = &mc13783_violo_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VDIG", + .id = MC13783_VDIG, + .ops = &mc13783_vdig_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VGEN", + .id = MC13783_VGEN, + .ops = &mc13783_vgen_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VRFDIG", + .id = MC13783_VRFDIG, + .ops = &mc13783_vrfdig_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VRFREF", + .id = MC13783_VRFREF, + .ops = &mc13783_vrfref_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VRFCP", + .id = MC13783_VRFCP, + .ops = &mc13783_vrfcp_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VSIM", + .id = MC13783_VSIM, + .ops = &mc13783_vsim_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VESIM", + .id = MC13783_VESIM, + .ops = &mc13783_vesim_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VCAM", + .id = MC13783_VCAM, + .ops = &mc13783_vcam_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VRFBG", + .id = MC13783_VRFBG, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VVIB", + .id = MC13783_VVIB, + .ops = &mc13783_vvib_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VRF1", + .id = MC13783_VRF1, + .ops = &mc13783_vrf_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VRF2", + .id = MC13783_VRF2, + .ops = &mc13783_vrf_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VMMC1", + .id = MC13783_VMMC1, + .ops = &mc13783_vmmc_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VMMC2", + .id = MC13783_VMMC2, + .ops = &mc13783_vmmc_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "GPO1", + .id = MC13783_GPO1, + .ops = &mc13783_gpo_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "GPO2", + .id = MC13783_GPO2, + .ops = &mc13783_gpo_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "GPO3", + .id = MC13783_GPO3, + .ops = &mc13783_gpo_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "GPO4", + .id = MC13783_GPO4, + .ops = &mc13783_gpo_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, +}; + +/* + * Init and Exit + */ + +static int reg_mc13783_probe(struct platform_device *pdev) +{ + struct regulator_dev *rdev; + + /* register regulator */ + rdev = regulator_register(®_mc13783[pdev->id], &pdev->dev, + pdev->dev.platform_data, + dev_get_drvdata(&pdev->dev)); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register %s\n", + reg_mc13783[pdev->id].name); + return PTR_ERR(rdev); + } + platform_set_drvdata(pdev, rdev); + + return 0; +} + +static int mc13783_regulator_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + + regulator_unregister(rdev); + + return 0; +} + +int mc13783_register_regulator(struct mc13783 *mc13783, int reg, + struct regulator_init_data *initdata) +{ + struct platform_device *pdev; + int ret; + + if (mc13783->pmic.pdev[reg]) + return -EBUSY; + + pdev = platform_device_alloc("mc13783-regulatr", reg); + if (!pdev) + return -ENOMEM; + + mc13783->pmic.pdev[reg] = pdev; + + initdata->driver_data = mc13783; + + pdev->dev.platform_data = initdata; + pdev->dev.parent = mc13783->dev; + ret = platform_device_add(pdev); + + if (ret != 0) { + dev_err(mc13783->dev, "Failed to register regulator %d: %d\n", + reg, ret); + platform_device_del(pdev); + mc13783->pmic.pdev[reg] = NULL; + } + + return ret; +} +EXPORT_SYMBOL_GPL(mc13783_register_regulator); + +static struct platform_driver mc13783_regulator_driver = { + .probe = reg_mc13783_probe, + .remove = mc13783_regulator_remove, + .driver = { + .name = "mc13783-regulatr", + /* o left out due to string length */ + }, +}; + +static int __init mc13783_regulator_subsys_init(void) +{ + return platform_driver_register(&mc13783_regulator_driver); +} +subsys_initcall(mc13783_regulator_subsys_init); + +static void __exit mc13783_regulator_exit(void) +{ + platform_driver_unregister(&mc13783_regulator_driver); +} +module_exit(mc13783_regulator_exit); + + +/* Module information */ +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MC13783 Regulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/reg-mc13892.c b/drivers/regulator/reg-mc13892.c new file mode 100644 index 000000000000..e10cc70b2e02 --- /dev/null +++ b/drivers/regulator/reg-mc13892.c @@ -0,0 +1,1850 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/mc13892/core.h> +#include <linux/platform_device.h> +#include <linux/pmic_status.h> +#include <linux/pmic_external.h> + +/* + * Convenience conversion. + * Here atm, maybe there is somewhere better for this. + */ +#define mV_to_uV(mV) (mV * 1000) +#define uV_to_mV(uV) (uV / 1000) +#define V_to_uV(V) (mV_to_uV(V * 1000)) +#define uV_to_V(uV) (uV_to_mV(uV) / 1000) + +enum { + VDIG_1_05V = 0, + VDIG_1_25V, + VDIG_1_65V, + VDIG_1_80V, +} regulator_voltage_vdig; + +enum { + VPLL_1_05V = 0, + VPLL_1_25V, + VPLL_1_65V, + VPLL_1_80V, +} regulator_voltage_vpll; + +enum { + VGEN1_1_2V = 0, + VGEN1_1_5V, + VGEN1_2_775V, + VGEN1_3_15V, +} regulator_voltage_vgen1; + +enum { + VGEN2_1_2V = 0, + VGEN2_1_5V, + VGEN2_1_6V, + VGEN2_1_8V, + VGEN2_2_7V, + VGEN2_2_8V, + VGEN2_3_0V, + VGEN2_3_15V, +} regulator_voltage_vgen2; + +enum { + VGEN3_1_8V = 0, + VGEN3_2_9V, +} regulator_voltage_vgen3; + +enum { + VSD_1_8V = 0, + VSD_2_0V, + VSD_2_6V, + VSD_2_7V, + VSD_2_8V, + VSD_2_9V, + VSD_3_0V, + VSD_3_15V, +} regulator_voltage_vsd; + +enum { + VCAM_2_5V, + VCAM_2_6V, + VCAM_2_75V, + VCAM_3_0V, +} regulator_voltage_vcam; + +enum { + VAUDIO_2_3V, + VAUDIO_2_5V, + VAUDIO_2_775V, + VAUDIO_3V, +} regulator_voltage_vaudio; + +enum { + VUSB2_2_4V, + VUSB2_2_6V, + VUSB2_2_7V, + VUSB2_2_775V, +} regulator_voltage_vusb2; + +enum { + VVIDEO_2_7V, + VVIDEO_2_775V, + VVIDEO_2_5V, + VVIDEO_2_6V, +} regulator_voltage_vvideo; + +#define VAUDIO_LSH 4 +#define VAUDIO_WID 2 +#define VAUDIO_EN_LSH 15 +#define VAUDIO_EN_WID 1 +#define VAUDIO_EN_ENABLE 1 +#define VAUDIO_EN_DISABLE 0 + +#define VUSB2_LSH 11 +#define VUSB2_WID 2 +#define VUSB2_EN_LSH 18 +#define VUSB2_EN_WID 1 +#define VUSB2_EN_ENABLE 1 +#define VUSB2_EN_DISABLE 0 + +#define VVIDEO_LSH 2 +#define VVIDEO_WID 2 +#define VVIDEO_EN_LSH 12 +#define VVIDEO_EN_WID 1 +#define VVIDEO_EN_ENABLE 1 +#define VVIDEO_EN_DISABLE 0 + +#define SWBST_EN_LSH 20 +#define SWBST_EN_WID 1 +#define SWBST_EN_ENABLE 1 +#define SWBST_EN_DISABLE 0 + +#define VIOHI_EN_LSH 3 +#define VIOHI_EN_WID 1 +#define VIOHI_EN_ENABLE 1 +#define VIOHI_EN_DISABLE 0 + +#define VDIG_LSH 4 +#define VDIG_WID 2 +#define VDIG_EN_LSH 9 +#define VDIG_EN_WID 1 +#define VDIG_EN_ENABLE 1 +#define VDIG_EN_DISABLE 0 + +#define VPLL_LSH 9 +#define VPLL_WID 2 +#define VPLL_EN_LSH 15 +#define VPLL_EN_WID 1 +#define VPLL_EN_ENABLE 1 +#define VPLL_EN_DISABLE 0 + +#define VGEN1_LSH 0 +#define VGEN1_WID 2 +#define VGEN1_EN_LSH 0 +#define VGEN1_EN_WID 1 +#define VGEN1_EN_ENABLE 1 +#define VGEN1_EN_DISABLE 0 + +#define VGEN2_LSH 6 +#define VGEN2_WID 3 +#define VGEN2_EN_LSH 12 +#define VGEN2_EN_WID 1 +#define VGEN2_EN_ENABLE 1 +#define VGEN2_EN_DISABLE 0 + +#define VGEN3_LSH 14 +#define VGEN3_WID 1 +#define VGEN3_EN_LSH 0 +#define VGEN3_EN_WID 1 +#define VGEN3_EN_ENABLE 1 +#define VGEN3_EN_DISABLE 0 + +#define VSD_LSH 6 +#define VSD_WID 3 +#define VSD_EN_LSH 18 +#define VSD_EN_WID 1 +#define VSD_EN_ENABLE 1 +#define VSD_EN_DISABLE 0 + +#define VCAM_LSH 16 +#define VCAM_WID 2 +#define VCAM_EN_LSH 6 +#define VCAM_EN_WID 1 +#define VCAM_EN_ENABLE 1 +#define VCAM_EN_DISABLE 0 +#define VCAM_CONFIG_LSH 9 +#define VCAM_CONFIG_WID 1 +#define VCAM_CONFIG_EXT 1 +#define VCAM_CONFIG_INT 0 + +#define SW1_LSH 0 +#define SW1_WID 5 +#define SW1_DVS_LSH 5 +#define SW1_DVS_WID 5 +#define SW1_STDBY_LSH 10 +#define SW1_STDBY_WID 5 + +#define SW2_LSH 0 +#define SW2_WID 5 +#define SW2_DVS_LSH 5 +#define SW2_DVS_WID 5 +#define SW2_STDBY_LSH 10 +#define SW2_STDBY_WID 5 + +#define SW3_LSH 0 +#define SW3_WID 5 +#define SW3_STDBY_LSH 10 +#define SW3_STDBY_WID 5 + +#define SW4_LSH 0 +#define SW4_WID 5 +#define SW4_STDBY_LSH 10 +#define SW4_STDBY_WID 5 + +#define VUSB_EN_LSH 3 +#define VUSB_EN_WID 1 +#define VUSB_EN_ENABLE 1 +#define VUSB_EN_DISABLE 0 + +#define GPO1_EN_LSH 6 +#define GPO1_EN_WID 1 +#define GPO1_EN_ENABLE 1 +#define GPO1_EN_DISABLE 0 + +#define GPO2_EN_LSH 8 +#define GPO2_EN_WID 1 +#define GPO2_EN_ENABLE 1 +#define GPO2_EN_DISABLE 0 + +#define GPO3_EN_LSH 10 +#define GPO3_EN_WID 1 +#define GPO3_EN_ENABLE 1 +#define GPO3_EN_DISABLE 0 + +#define GPO4_EN_LSH 12 +#define GPO4_EN_WID 1 +#define GPO4_EN_ENABLE 1 +#define GPO4_EN_DISABLE 0 + +#define GPO4_ADIN_LSH 21 +#define GPO4_ADIN_WID 1 +#define GPO4_ADIN_ENABLE 1 +#define GPO4_ADIN_DISABLE 0 + +#define PWGT1SPI_EN_LSH 15 +#define PWGT1SPI_EN_WID 1 +#define PWGT1SPI_EN_ENABLE 0 +#define PWGT1SPI_EN_DISABLE 1 + +#define PWGT2SPI_EN_LSH 16 +#define PWGT2SPI_EN_WID 1 +#define PWGT2SPI_EN_ENABLE 0 +#define PWGT2SPI_EN_DISABLE 1 + +#define SWXHI_LSH 23 +#define SWXHI_WID 1 +#define SWXHI_ON 1 +#define SWXHI_OFF 0 + +static int mc13892_get_sw_hi_bit(int sw) +{ + unsigned int register_val = 0; + unsigned int reg = 0; + + switch (sw) { + case MC13892_SW1: + reg = REG_SW_0; + break; + case MC13892_SW2: + reg = REG_SW_1; + break; + case MC13892_SW3: + reg = REG_SW_2; + break; + case MC13892_SW4: + reg = REG_SW_3; + break; + default: + return -EINVAL; + } + + CHECK_ERROR(pmic_read_reg(reg, ®ister_val, PMIC_ALL_BITS)); + return (register_val & 0x800000) >> SWXHI_LSH; +} + +static int mc13892_get_voltage_value(int *hi, int mV) +{ + int voltage; + + if (mV < 600) + mV = 600; + if (mV > 1850) + mV = 1850; + + if (mV > 1375) + *hi = 1; + if (mV < 1100) + *hi = 0; + + if (*hi == 0) + voltage = (mV - 600) / 25; + else + voltage = (mV - 1100) / 25; + + return voltage; +} + +static int mc13892_get_voltage_mV(int hi, int voltage) +{ + int mV; + + if (hi == 0) + mV = voltage * 25 + 600; + else + mV = voltage * 25 + 1100; + + return mV; +} + +static int mc13892_sw_set_voltage(struct regulator_dev *reg, int MiniV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1 = 0; + int voltage; + int sw = rdev_get_id(reg); + int mV = uV / 1000; + int hi; + + hi = mc13892_get_sw_hi_bit(sw); + voltage = mc13892_get_voltage_value(&hi, mV); + + switch (sw) { + case MC13892_SW1: + register1 = REG_SW_0; + register_val = BITFVAL(SW1, voltage); + register_mask = BITFMASK(SW1); + break; + case MC13892_SW2: + register1 = REG_SW_1; + register_val = BITFVAL(SW2, voltage); + register_mask = BITFMASK(SW2); + break; + case MC13892_SW3: + register1 = REG_SW_2; + register_val = BITFVAL(SW3, voltage); + register_mask = BITFMASK(SW3); + break; + case MC13892_SW4: + register1 = REG_SW_3; + register_val = BITFVAL(SW4, voltage); + register_mask = BITFMASK(SW4); + break; + default: + return -EINVAL; + } + + register_val |= (hi << SWXHI_LSH); + register_mask |= (1 << SWXHI_LSH); + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_sw_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0; + int mV = 0; + int sw = rdev_get_id(reg); + int hi; + + switch (sw) { + case MC13892_SW1: + CHECK_ERROR(pmic_read_reg(REG_SW_0, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, SW1); + break; + case MC13892_SW2: + CHECK_ERROR(pmic_read_reg(REG_SW_1, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, SW2); + break; + case MC13892_SW3: + CHECK_ERROR(pmic_read_reg(REG_SW_2, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, SW3); + break; + case MC13892_SW4: + CHECK_ERROR(pmic_read_reg(REG_SW_3, + ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, SW4); + break; + default: + return -EINVAL; + } + + hi = mc13892_get_sw_hi_bit(sw); + mV = mc13892_get_voltage_mV(hi, voltage); + + return mV * 1000; +} + +static int mc13892_sw_stby_enable(struct regulator_dev *reg) +{ + return 0; +} + +static int mc13892_sw_stby_disable(struct regulator_dev *reg) +{ + return 0; +} + +static int mc13892_sw_stby_set_mode(struct regulator_dev *reg, unsigned int mode) +{ + return 0; +} + +static int mc13892_sw_stby_set_voltage(struct regulator_dev *reg, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1 = 0; + int voltage, mV = uV / 1000, hi; + int sw = rdev_get_id(reg); + + hi = mc13892_get_sw_hi_bit(sw); + voltage = mc13892_get_voltage_value(&hi, mV); + + switch (sw) { + case MC13892_SW1: + register1 = REG_SW_0; + register_val = BITFVAL(SW1_STDBY, voltage); + register_mask = BITFMASK(SW1_STDBY); + break; + case MC13892_SW2: + register1 = REG_SW_1; + register_val = BITFVAL(SW2_STDBY, voltage); + register_mask = BITFMASK(SW2_STDBY); + break; + case MC13892_SW3: + register1 = REG_SW_2; + register_val = BITFVAL(SW3_STDBY, voltage); + register_mask = BITFMASK(SW3_STDBY); + break; + case MC13892_SW4: + register1 = REG_SW_3; + register_val = BITFVAL(SW4_STDBY, voltage); + register_mask = BITFMASK(SW4_STDBY); + break; + default: + return -EINVAL; + } + + register_val |= (hi << SWXHI_LSH); + register_mask |= (1 << SWXHI_LSH); + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_swbst_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(SWBST_EN, SWBST_EN_ENABLE); + register_mask = BITFMASK(SWBST_EN); + register1 = REG_SW_5; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_swbst_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(SWBST_EN, SWBST_EN_DISABLE); + register_mask = BITFMASK(SWBST_EN); + register1 = REG_SW_5; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_viohi_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VIOHI_EN, VIOHI_EN_ENABLE); + register_mask = BITFMASK(VIOHI_EN); + register1 = REG_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_viohi_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VIOHI_EN, VIOHI_EN_DISABLE); + register_mask = BITFMASK(VIOHI_EN); + register1 = REG_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vusb_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VUSB_EN, VUSB_EN_ENABLE); + register_mask = BITFMASK(VUSB_EN); + register1 = REG_USB1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vusb_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VUSB_EN, VUSB_EN_DISABLE); + register_mask = BITFMASK(VUSB_EN); + register1 = REG_USB1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vdig_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 1050) && (mV < 1250)) + voltage = VDIG_1_05V; + else if ((mV >= 1250) && (mV < 1650)) + voltage = VDIG_1_25V; + else if ((mV >= 1650) && (mV < 1800)) + voltage = VDIG_1_65V; + else + voltage = VDIG_1_80V; + + register_val = BITFVAL(VDIG, voltage); + register_mask = BITFMASK(VDIG); + register1 = REG_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vdig_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_SETTING_0, ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VDIG); + + switch (voltage) { + case VDIG_1_05V: + mV = 1050; + break; + case VDIG_1_25V: + mV = 1250; + break; + case VDIG_1_65V: + mV = 1650; + break; + case VDIG_1_80V: + mV = 1800; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13892_vdig_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VDIG_EN, VDIG_EN_ENABLE); + register_mask = BITFMASK(VDIG_EN); + register1 = REG_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vdig_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VDIG_EN, VDIG_EN_DISABLE); + register_mask = BITFMASK(VDIG_EN); + register1 = REG_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vpll_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 1050) && (mV < 1250)) + voltage = VPLL_1_05V; + else if ((mV >= 1250) && (mV < 1650)) + voltage = VPLL_1_25V; + else if ((mV >= 1650) && (mV < 1800)) + voltage = VPLL_1_65V; + else + voltage = VPLL_1_80V; + + register_val = BITFVAL(VPLL, voltage); + register_mask = BITFMASK(VPLL); + register1 = REG_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vpll_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_SETTING_0, ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VPLL); + + switch (voltage) { + case VPLL_1_05V: + mV = 1050; + break; + case VPLL_1_25V: + mV = 1250; + break; + case VPLL_1_65V: + mV = 1650; + break; + case VPLL_1_80V: + mV = 1800; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13892_vpll_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VPLL_EN, VPLL_EN_ENABLE); + register_mask = BITFMASK(VPLL_EN); + register1 = REG_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vpll_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VPLL_EN, VPLL_EN_DISABLE); + register_mask = BITFMASK(VPLL_EN); + register1 = REG_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vaudio_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 2300) && (mV < 2500)) + voltage = VAUDIO_2_3V; + else if ((mV >= 2500) && (mV < 2775)) + voltage = VAUDIO_2_5V; + else if ((mV >= 2775) && (mV < 3000)) + voltage = VAUDIO_2_775V; + else + voltage = VAUDIO_3V; + + register_val = BITFVAL(VAUDIO, voltage); + register_mask = BITFMASK(VAUDIO); + register1 = REG_SETTING_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vaudio_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_SETTING_1, ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VAUDIO); + + switch (voltage) { + case VAUDIO_2_3V: + mV = 2300; + break; + case VAUDIO_2_5V: + mV = 2500; + break; + case VAUDIO_2_775V: + mV = 2775; + break; + case VAUDIO_3V: + mV = 3000; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13892_vaudio_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VAUDIO_EN, VAUDIO_EN_ENABLE); + register_mask = BITFMASK(VAUDIO_EN); + register1 = REG_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vaudio_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VAUDIO_EN, VAUDIO_EN_DISABLE); + register_mask = BITFMASK(VAUDIO_EN); + register1 = REG_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vusb2_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 2400) && (mV < 2600)) + voltage = VUSB2_2_4V; + else if ((mV >= 2600) && (mV < 2700)) + voltage = VUSB2_2_6V; + else if ((mV >= 2700) && (mV < 2775)) + voltage = VUSB2_2_7V; + else + voltage = VUSB2_2_775V; + + register_val = BITFVAL(VUSB2, voltage); + register_mask = BITFMASK(VUSB2); + register1 = REG_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vusb2_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_SETTING_0, ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VUSB2); + + switch (voltage) { + case VUSB2_2_4V: + mV = 2400; + break; + case VUSB2_2_6V: + mV = 2600; + break; + case VUSB2_2_7V: + mV = 2700; + break; + case VUSB2_2_775V: + mV = 2775; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13892_vusb2_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VUSB2_EN, VUSB2_EN_ENABLE); + register_mask = BITFMASK(VUSB2_EN); + register1 = REG_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vusb2_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VUSB2_EN, VUSB2_EN_DISABLE); + register_mask = BITFMASK(VUSB2_EN); + register1 = REG_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vvideo_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 2500) && (mV < 2600)) + voltage = VVIDEO_2_5V; + else if ((mV >= 2600) && (mV < 2700)) + voltage = VVIDEO_2_6V; + else if ((mV >= 2700) && (mV < 2775)) + voltage = VVIDEO_2_7V; + else + voltage = VVIDEO_2_775V; + + register_val = BITFVAL(VVIDEO, voltage); + register_mask = BITFMASK(VVIDEO); + register1 = REG_SETTING_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vvideo_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_SETTING_1, ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VVIDEO); + + switch (voltage) { + case VVIDEO_2_5V: + mV = 2500; + break; + case VVIDEO_2_6V: + mV = 2600; + break; + case VVIDEO_2_7V: + mV = 2700; + break; + case VVIDEO_2_775V: + mV = 2775; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13892_vvideo_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VVIDEO_EN, VVIDEO_EN_ENABLE); + register_mask = BITFMASK(VVIDEO_EN); + register1 = REG_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vvideo_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VVIDEO_EN, VVIDEO_EN_DISABLE); + register_mask = BITFMASK(VVIDEO_EN); + register1 = REG_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vsd_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 1800) && (mV < 2000)) + voltage = VSD_1_8V; + else if ((mV >= 2000) && (mV < 2600)) + voltage = VSD_2_0V; + else if ((mV >= 2600) && (mV < 2700)) + voltage = VSD_2_6V; + else if ((mV >= 2700) && (mV < 2800)) + voltage = VSD_2_7V; + else if ((mV >= 2800) && (mV < 2900)) + voltage = VSD_2_8V; + else if ((mV >= 2900) && (mV < 3000)) + voltage = VSD_2_9V; + else if ((mV >= 3000) && (mV < 3150)) + voltage = VSD_3_0V; + else + voltage = VSD_3_15V; + + register_val = BITFVAL(VSD, voltage); + register_mask = BITFMASK(VSD); + register1 = REG_SETTING_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vsd_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_SETTING_1, ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VSD); + + switch (voltage) { + case VSD_1_8V: + mV = 1800; + break; + case VSD_2_0V: + mV = 2000; + break; + case VSD_2_6V: + mV = 2600; + break; + case VSD_2_7V: + mV = 2700; + break; + case VSD_2_8V: + mV = 2800; + break; + case VSD_2_9V: + mV = 2900; + break; + case VSD_3_0V: + mV = 3000; + break; + case VSD_3_15V: + mV = 3150; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13892_vsd_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VSD_EN, VSD_EN_ENABLE); + register_mask = BITFMASK(VSD_EN); + register1 = REG_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vsd_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VSD_EN, VSD_EN_DISABLE); + register_mask = BITFMASK(VSD_EN); + register1 = REG_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vcam_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 2500) && (mV < 2600)) + voltage = VCAM_2_5V; + else if ((mV >= 2600) && (mV < 2750)) + voltage = VCAM_2_6V; + else if ((mV >= 2750) && (mV < 3000)) + voltage = VCAM_2_75V; + else + voltage = VCAM_3_0V; + + register_val = BITFVAL(VCAM, voltage); + register_mask = BITFMASK(VCAM); + register1 = REG_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vcam_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_SETTING_0, ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VCAM); + + switch (voltage) { + case VCAM_2_5V: + mV = 2500; + break; + case VCAM_2_6V: + mV = 2600; + break; + case VCAM_2_75V: + mV = 2750; + break; + case VCAM_3_0V: + mV = 3000; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13892_vcam_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VCAM_EN, VCAM_EN_ENABLE); + register_mask = BITFMASK(VCAM_EN); + register1 = REG_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vcam_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VCAM_EN, VCAM_EN_DISABLE); + register_mask = BITFMASK(VCAM_EN); + register1 = REG_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vcam_set_mode(struct regulator_dev *reg, unsigned int mode) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + switch (mode) { + case REGULATOR_MODE_FAST: + register_val = BITFVAL(VCAM_CONFIG, VCAM_CONFIG_EXT); + break; + case REGULATOR_MODE_NORMAL: + register_val = BITFVAL(VCAM_CONFIG, VCAM_CONFIG_INT); + break; + default: + return -EINVAL; + } + register_mask = BITFMASK(VCAM_CONFIG); + register1 = REG_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +unsigned int mc13892_vcam_get_mode(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int config = 0, mode = VCAM_CONFIG_INT; + + CHECK_ERROR(pmic_read_reg(REG_MODE_1, ®ister_val, PMIC_ALL_BITS)); + config = BITFEXT(register_val, VCAM_CONFIG); + + switch (config) { + case VCAM_CONFIG_EXT: + mode = REGULATOR_MODE_FAST; + break; + case VCAM_CONFIG_INT: + mode = REGULATOR_MODE_NORMAL; + break; + default: + return -EINVAL; + } + return mode; +} + +static int mc13892_vgen1_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 1200) && (mV < 1500)) + voltage = VGEN1_1_2V; + else if ((mV >= 1500) && (mV < 2775)) + voltage = VGEN1_1_5V; + else if ((mV >= 2775) && (mV < 3150)) + voltage = VGEN1_2_775V; + else + voltage = VGEN1_3_15V; + + register_val = BITFVAL(VGEN1, voltage); + register_mask = BITFMASK(VGEN1); + register1 = REG_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vgen1_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_SETTING_0, ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VGEN1); + + switch (voltage) { + case VGEN1_1_2V: + mV = 1200; + break; + case VGEN1_1_5V: + mV = 1500; + break; + case VGEN1_2_775V: + mV = 2775; + break; + case VGEN1_3_15V: + mV = 3150; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13892_vgen1_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VGEN1_EN, VGEN1_EN_ENABLE); + register_mask = BITFMASK(VGEN1_EN); + register1 = REG_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vgen1_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VGEN1_EN, VGEN1_EN_DISABLE); + register_mask = BITFMASK(VGEN1_EN); + register1 = REG_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vgen2_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 1200) && (mV < 1500)) + voltage = VGEN2_1_2V; + else if ((mV >= 1500) && (mV < 1600)) + voltage = VGEN2_1_5V; + else if ((mV >= 1600) && (mV < 1800)) + voltage = VGEN2_1_6V; + else if ((mV >= 1800) && (mV < 2700)) + voltage = VGEN2_1_8V; + else if ((mV >= 2700) && (mV < 2800)) + voltage = VGEN2_2_7V; + else if ((mV >= 2800) && (mV < 3000)) + voltage = VGEN2_2_8V; + else if ((mV >= 3000) && (mV < 3150)) + voltage = VGEN2_3_0V; + else + voltage = VGEN2_3_15V; + + register_val = BITFVAL(VGEN2, voltage); + register_mask = BITFMASK(VGEN2); + register1 = REG_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vgen2_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_SETTING_0, ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VGEN2); + + switch (voltage) { + case VGEN2_1_2V: + mV = 1200; + break; + case VGEN2_1_5V: + mV = 1500; + break; + case VGEN2_1_6V: + mV = 1600; + break; + case VGEN2_1_8V: + mV = 1800; + break; + case VGEN2_2_7V: + mV = 2700; + break; + case VGEN2_2_8V: + mV = 2800; + break; + case VGEN2_3_0V: + mV = 3000; + break; + case VGEN2_3_15V: + mV = 3150; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13892_vgen2_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VGEN2_EN, VGEN2_EN_ENABLE); + register_mask = BITFMASK(VGEN2_EN); + register1 = REG_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vgen2_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VGEN2_EN, VGEN2_EN_DISABLE); + register_mask = BITFMASK(VGEN2_EN); + register1 = REG_MODE_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vgen3_set_voltage(struct regulator_dev *reg, + int minuV, int uV) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int voltage, mV = uV / 1000; + + if ((mV >= 1800) && (mV < 2900)) + voltage = VGEN3_1_8V; + else + voltage = VGEN3_2_9V; + + register_val = BITFVAL(VGEN3, voltage); + register_mask = BITFMASK(VGEN3); + register1 = REG_SETTING_0; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vgen3_get_voltage(struct regulator_dev *reg) +{ + unsigned int register_val = 0; + int voltage = 0, mV = 0; + + CHECK_ERROR(pmic_read_reg(REG_SETTING_0, ®ister_val, PMIC_ALL_BITS)); + voltage = BITFEXT(register_val, VGEN3); + + switch (voltage) { + case VGEN3_1_8V: + mV = 1800; + break; + case VGEN3_2_9V: + mV = 2900; + break; + default: + return -EINVAL; + } + + return mV * 1000; +} + +static int mc13892_vgen3_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VGEN3_EN, VGEN3_EN_ENABLE); + register_mask = BITFMASK(VGEN3_EN); + register1 = REG_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_vgen3_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + + register_val = BITFVAL(VGEN3_EN, VGEN3_EN_DISABLE); + register_mask = BITFMASK(VGEN3_EN); + register1 = REG_MODE_1; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_gpo_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int gpo = rdev_get_id(reg); + + switch (gpo) { + case MC13892_GPO1: + register_val = BITFVAL(GPO1_EN, GPO1_EN_ENABLE); + register_mask = BITFMASK(GPO1_EN); + break; + case MC13892_GPO2: + register_val = BITFVAL(GPO2_EN, GPO2_EN_ENABLE); + register_mask = BITFMASK(GPO2_EN); + break; + case MC13892_GPO3: + register_val = BITFVAL(GPO3_EN, GPO3_EN_ENABLE); + register_mask = BITFMASK(GPO3_EN); + break; + case MC13892_GPO4: + register_val = BITFVAL(GPO4_EN, GPO4_EN_ENABLE) + + BITFVAL(GPO4_ADIN, GPO4_ADIN_DISABLE); + register_mask = BITFMASK(GPO4_EN) + BITFMASK(GPO4_ADIN); + break; + default: + return -EINVAL; + }; + + register1 = REG_POWER_MISC; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_gpo_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int gpo = rdev_get_id(reg); + + switch (gpo) { + case MC13892_GPO1: + register_val = BITFVAL(GPO1_EN, GPO1_EN_DISABLE); + register_mask = BITFMASK(GPO1_EN); + break; + case MC13892_GPO2: + register_val = BITFVAL(GPO2_EN, GPO2_EN_DISABLE); + register_mask = BITFMASK(GPO2_EN); + break; + case MC13892_GPO3: + register_val = BITFVAL(GPO3_EN, GPO3_EN_DISABLE); + register_mask = BITFMASK(GPO3_EN); + break; + case MC13892_GPO4: + register_val = BITFVAL(GPO4_EN, GPO4_EN_DISABLE); + register_mask = BITFMASK(GPO4_EN); + break; + default: + return -EINVAL; + }; + + register1 = REG_POWER_MISC; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_power_gating_enable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int gpo = rdev_get_id(reg); + + switch (gpo) { + case MC13892_PWGT1: + register_val = BITFVAL(PWGT1SPI_EN, PWGT1SPI_EN_ENABLE); + register_mask = BITFMASK(PWGT1SPI_EN); + break; + case MC13892_PWGT2: + register_val = BITFVAL(PWGT2SPI_EN, PWGT2SPI_EN_ENABLE); + register_mask = BITFMASK(PWGT2SPI_EN); + break; + default: + return -EINVAL; + }; + + register1 = REG_POWER_MISC; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static int mc13892_power_gating_disable(struct regulator_dev *reg) +{ + unsigned int register_val = 0, register_mask = 0; + unsigned int register1; + int gpo = rdev_get_id(reg); + + switch (gpo) { + case MC13892_PWGT1: + register_val = BITFVAL(PWGT1SPI_EN, PWGT1SPI_EN_DISABLE); + register_mask = BITFMASK(PWGT1SPI_EN); + break; + case MC13892_PWGT2: + register_val = BITFVAL(PWGT2SPI_EN, PWGT2SPI_EN_DISABLE); + register_mask = BITFMASK(PWGT2SPI_EN); + break; + default: + return -EINVAL; + }; + + register1 = REG_POWER_MISC; + + return pmic_write_reg(register1, register_val, register_mask); +} + +static struct regulator_ops mc13892_sw_ops = { + .enable = mc13892_sw_stby_enable, + .disable = mc13892_sw_stby_disable, + .set_voltage = mc13892_sw_set_voltage, + .get_voltage = mc13892_sw_get_voltage, + .set_suspend_voltage = mc13892_sw_stby_set_voltage, + .set_suspend_enable = mc13892_sw_stby_enable, + .set_suspend_disable = mc13892_sw_stby_disable, + .set_suspend_mode = mc13892_sw_stby_set_mode, +}; + +static struct regulator_ops mc13892_swbst_ops = { + .enable = mc13892_swbst_enable, + .disable = mc13892_swbst_disable, +}; + +static struct regulator_ops mc13892_viohi_ops = { + .enable = mc13892_viohi_enable, + .disable = mc13892_viohi_disable, +}; + +static struct regulator_ops mc13892_vusb_ops = { + .enable = mc13892_vusb_enable, + .disable = mc13892_vusb_disable, +}; + +static struct regulator_ops mc13892_vdig_ops = { + .set_voltage = mc13892_vdig_set_voltage, + .get_voltage = mc13892_vdig_get_voltage, + .enable = mc13892_vdig_enable, + .disable = mc13892_vdig_disable, +}; + +static struct regulator_ops mc13892_vpll_ops = { + .set_voltage = mc13892_vpll_set_voltage, + .get_voltage = mc13892_vpll_get_voltage, + .enable = mc13892_vpll_enable, + .disable = mc13892_vpll_disable, +}; + +static struct regulator_ops mc13892_vusb2_ops = { + .set_voltage = mc13892_vusb2_set_voltage, + .get_voltage = mc13892_vusb2_get_voltage, + .enable = mc13892_vusb2_enable, + .disable = mc13892_vusb2_disable, +}; + +static struct regulator_ops mc13892_vvideo_ops = { + .set_voltage = mc13892_vvideo_set_voltage, + .get_voltage = mc13892_vvideo_get_voltage, + .enable = mc13892_vvideo_enable, + .disable = mc13892_vvideo_disable, +}; + +static struct regulator_ops mc13892_vaudio_ops = { + .set_voltage = mc13892_vaudio_set_voltage, + .get_voltage = mc13892_vaudio_get_voltage, + .enable = mc13892_vaudio_enable, + .disable = mc13892_vaudio_disable, +}; + +static struct regulator_ops mc13892_vsd_ops = { + .set_voltage = mc13892_vsd_set_voltage, + .get_voltage = mc13892_vsd_get_voltage, + .enable = mc13892_vsd_enable, + .disable = mc13892_vsd_disable, +}; + +static struct regulator_ops mc13892_vcam_ops = { + .set_voltage = mc13892_vcam_set_voltage, + .get_voltage = mc13892_vcam_get_voltage, + .enable = mc13892_vcam_enable, + .disable = mc13892_vcam_disable, + .set_mode = mc13892_vcam_set_mode, + .get_mode = mc13892_vcam_get_mode, +}; + +static struct regulator_ops mc13892_vgen1_ops = { + .set_voltage = mc13892_vgen1_set_voltage, + .get_voltage = mc13892_vgen1_get_voltage, + .enable = mc13892_vgen1_enable, + .disable = mc13892_vgen1_disable, +}; + +static struct regulator_ops mc13892_vgen2_ops = { + .set_voltage = mc13892_vgen2_set_voltage, + .get_voltage = mc13892_vgen2_get_voltage, + .enable = mc13892_vgen2_enable, + .disable = mc13892_vgen2_disable, +}; + +static struct regulator_ops mc13892_vgen3_ops = { + .set_voltage = mc13892_vgen3_set_voltage, + .get_voltage = mc13892_vgen3_get_voltage, + .enable = mc13892_vgen3_enable, + .disable = mc13892_vgen3_disable, +}; + +static struct regulator_ops mc13892_gpo_ops = { + .enable = mc13892_gpo_enable, + .disable = mc13892_gpo_disable, +}; + +static struct regulator_ops mc13892_power_gating_ops = { + .enable = mc13892_power_gating_enable, + .disable = mc13892_power_gating_disable, + +}; + +static struct regulator_desc mc13892_reg[] = { + { + .name = "SW1", + .id = MC13892_SW1, + .ops = &mc13892_sw_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "SW2", + .id = MC13892_SW2, + .ops = &mc13892_sw_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "SW3", + .id = MC13892_SW3, + .ops = &mc13892_sw_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "SW4", + .id = MC13892_SW4, + .ops = &mc13892_sw_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "SWBST", + .id = MC13892_SWBST, + .ops = &mc13892_swbst_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VIOHI", + .id = MC13892_VIOHI, + .ops = &mc13892_viohi_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VPLL", + .id = MC13892_VPLL, + .ops = &mc13892_vpll_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VDIG", + .id = MC13892_VDIG, + .ops = &mc13892_vdig_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VSD", + .id = MC13892_VSD, + .ops = &mc13892_vsd_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VUSB2", + .id = MC13892_VUSB2, + .ops = &mc13892_vusb2_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VVIDEO", + .id = MC13892_VVIDEO, + .ops = &mc13892_vvideo_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VAUDIO", + .id = MC13892_VAUDIO, + .ops = &mc13892_vaudio_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VCAM", + .id = MC13892_VCAM, + .ops = &mc13892_vcam_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VGEN1", + .id = MC13892_VGEN1, + .ops = &mc13892_vgen1_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VGEN2", + .id = MC13892_VGEN2, + .ops = &mc13892_vgen2_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VGEN3", + .id = MC13892_VGEN3, + .ops = &mc13892_vgen3_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "VUSB", + .id = MC13892_VUSB, + .ops = &mc13892_vusb_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "GPO1", + .id = MC13892_GPO1, + .ops = &mc13892_gpo_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "GPO2", + .id = MC13892_GPO2, + .ops = &mc13892_gpo_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "GPO3", + .id = MC13892_GPO3, + .ops = &mc13892_gpo_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "GPO4", + .id = MC13892_GPO4, + .ops = &mc13892_gpo_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "PWGT1", + .id = MC13892_PWGT1, + .ops = &mc13892_power_gating_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "PWGT2", + .id = MC13892_PWGT2, + .ops = &mc13892_power_gating_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, +}; + +static int mc13892_regulator_probe(struct platform_device *pdev) +{ + struct regulator_dev *rdev; + + /* register regulator */ + rdev = regulator_register(&mc13892_reg[pdev->id], &pdev->dev, + pdev->dev.platform_data, + dev_get_drvdata(&pdev->dev)); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register %s\n", + mc13892_reg[pdev->id].name); + return PTR_ERR(rdev); + } + + return 0; +} + + +static int mc13892_regulator_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + + regulator_unregister(rdev); + + return 0; +} + +int mc13892_register_regulator(struct mc13892 *mc13892, int reg, + struct regulator_init_data *initdata) +{ + struct platform_device *pdev; + int ret; + + if (mc13892->pmic.pdev[reg]) + return -EBUSY; + + pdev = platform_device_alloc("mc13892-regulatr", reg); + if (!pdev) + return -ENOMEM; + + mc13892->pmic.pdev[reg] = pdev; + + initdata->driver_data = mc13892; + + pdev->dev.platform_data = initdata; + pdev->dev.parent = mc13892->dev; + platform_set_drvdata(pdev, mc13892); + ret = platform_device_add(pdev); + + if (ret != 0) { + dev_err(mc13892->dev, "Failed to register regulator %d: %d\n", + reg, ret); + platform_device_del(pdev); + mc13892->pmic.pdev[reg] = NULL; + } + + return ret; +} +EXPORT_SYMBOL_GPL(mc13892_register_regulator); + +static struct platform_driver mc13892_regulator_driver = { + .probe = mc13892_regulator_probe, + .remove = mc13892_regulator_remove, + .driver = { + .name = "mc13892-regulatr", + }, +}; + +static int __init mc13892_regulator_init(void) +{ + return platform_driver_register(&mc13892_regulator_driver); +} +subsys_initcall(mc13892_regulator_init); + +static void __exit mc13892_regulator_exit(void) +{ + platform_driver_unregister(&mc13892_regulator_driver); +} +module_exit(mc13892_regulator_exit); + + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MC13892 Regulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/reg-mc34704.c b/drivers/regulator/reg-mc34704.c new file mode 100644 index 000000000000..a3a7e5d7ea90 --- /dev/null +++ b/drivers/regulator/reg-mc34704.c @@ -0,0 +1,289 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/ioctl.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/mc34704/core.h> +#include <linux/platform_device.h> +#include <linux/pmic_status.h> +#include <linux/pmic_external.h> + +#define MC34704_ONOFFA 0x8 +#define MC34704_ONOFFC 0x4 +#define MC34704_ONOFFD 0x2 +#define MC34704_ONOFFE 0x1 + +/* Private data for MC34704 regulators */ + +struct reg_mc34704_priv { + short enable; /* enable bit, if available */ + short v_default; /* default regulator voltage in mV */ + int dvs_min; /* minimum voltage change in units of 2.5% */ + int dvs_max; /* maximum voltage change in units of 2.5% */ + char i2c_dvs; /* i2c DVS register number */ + char i2c_stat; /* i2c status register number */ +}; +struct reg_mc34704_priv mc34704_reg_priv[] = { + { + .v_default = REG1_V_MV, + .dvs_min = REG1_DVS_MIN_PCT / 2.5, + .dvs_max = REG1_DVS_MAX_PCT / 2.5, + .i2c_dvs = 0x4, + .i2c_stat = 0x5, + .enable = MC34704_ONOFFA, + }, + { + .v_default = REG2_V_MV, + .dvs_min = REG2_DVS_MIN_PCT / 2.5, + .dvs_max = REG2_DVS_MAX_PCT / 2.5, + .i2c_dvs = 0x6, + .i2c_stat = 0x7, + }, + { + .v_default = REG3_V_MV, + .dvs_min = REG3_DVS_MIN_PCT / 2.5, + .dvs_max = REG3_DVS_MAX_PCT / 2.5, + .i2c_dvs = 0x8, + .i2c_stat = 0x9, + }, + { + .v_default = REG4_V_MV, + .dvs_min = REG4_DVS_MIN_PCT / 2.5, + .dvs_max = REG4_DVS_MAX_PCT / 2.5, + .i2c_dvs = 0xA, + .i2c_stat = 0xB, + }, + { + .v_default = REG5_V_MV, + .dvs_min = REG5_DVS_MIN_PCT / 2.5, + .dvs_max = REG5_DVS_MAX_PCT / 2.5, + .i2c_dvs = 0xC, + .i2c_stat = 0xE, + .enable = MC34704_ONOFFE, + }, +}; + +static int mc34704_set_voltage(struct regulator_dev *reg, int MiniV, int uV) +{ + struct reg_mc34704_priv *priv = rdev_get_drvdata(reg); + int mV = uV / 1000; + int dV = mV - priv->v_default; + + /* compute dynamic voltage scaling value */ + int dvs = 1000 * dV / priv->v_default / 25; + + /* clip to regulator limits */ + if (dvs > priv->dvs_max) + dvs = priv->dvs_max; + if (dvs < priv->dvs_min) + dvs = priv->dvs_min; + + return pmic_write_reg(priv->i2c_dvs, dvs << 1, 0x1E); +} + +static int mc34704_get_voltage(struct regulator_dev *reg) +{ + int mV; + struct reg_mc34704_priv *priv = rdev_get_drvdata(reg); + int val, dvs; + + CHECK_ERROR(pmic_read_reg(priv->i2c_dvs, &val, 0xF)); + + dvs = (val >> 1) & 0xF; + + /* dvs is 4-bit 2's complement; sign-extend it */ + if (dvs & 8) + dvs |= -1 & ~0xF; + + /* Regulator voltage is adjusted by (dvs * 2.5%) */ + mV = priv->v_default * (1000 + 25 * dvs) / 1000; + + return 1000 * mV; +} + +static int mc34704_enable_reg(struct regulator_dev *reg) +{ + struct reg_mc34704_priv *priv = rdev_get_drvdata(reg); + + if (priv->enable) + return pmic_write_reg(REG_MC34704_GENERAL2, -1, priv->enable); + + return PMIC_ERROR; +} + +static int mc34704_disable_reg(struct regulator_dev *reg) +{ + struct reg_mc34704_priv *priv = rdev_get_drvdata(reg); + + if (priv->enable) + return pmic_write_reg(REG_MC34704_GENERAL2, 0, priv->enable); + + return PMIC_ERROR; +} + +static int mc34704_is_reg_enabled(struct regulator_dev *reg) +{ + struct reg_mc34704_priv *priv = rdev_get_drvdata(reg); + int val; + + if (priv->enable) { + CHECK_ERROR(pmic_read_reg(REG_MC34704_GENERAL2, &val, + priv->enable)); + return val ? 1 : 0; + } else { + return PMIC_ERROR; + } +} + +static struct regulator_ops mc34704_full_ops = { + .set_voltage = mc34704_set_voltage, + .get_voltage = mc34704_get_voltage, + .enable = mc34704_enable_reg, + .disable = mc34704_disable_reg, + .is_enabled = mc34704_is_reg_enabled, +}; + +static struct regulator_ops mc34704_partial_ops = { + .set_voltage = mc34704_set_voltage, + .get_voltage = mc34704_get_voltage, +}; + +static struct regulator_desc reg_mc34704[] = { + { + .name = "REG1_BKLT", + .id = MC34704_BKLT, + .ops = &mc34704_full_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE}, + { + .name = "REG2_CPU", + .id = MC34704_CPU, + .ops = &mc34704_partial_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE}, + { + .name = "REG3_CORE", + .id = MC34704_CORE, + .ops = &mc34704_partial_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE}, + { + .name = "REG4_DDR", + .id = MC34704_DDR, + .ops = &mc34704_partial_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE}, + { + .name = "REG5_PERS", + .id = MC34704_PERS, + .ops = &mc34704_full_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE}, +}; + +static int mc34704_regulator_probe(struct platform_device *pdev) +{ + struct regulator_dev *rdev; + + /* register regulator */ + rdev = regulator_register(®_mc34704[pdev->id], &pdev->dev, + pdev->dev.platform_data, + (void *)&mc34704_reg_priv[pdev->id]); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register %s\n", + reg_mc34704[pdev->id].name); + return PTR_ERR(rdev); + } + + return 0; +} + +static int mc34704_regulator_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + + regulator_unregister(rdev); + + return 0; +} + +int mc34704_register_regulator(struct mc34704 *mc34704, int reg, + struct regulator_init_data *initdata) +{ + struct platform_device *pdev; + int ret; + + if (mc34704->pmic.pdev[reg]) + return -EBUSY; + + pdev = platform_device_alloc("mc34704-regulatr", reg); + if (!pdev) + return -ENOMEM; + + mc34704->pmic.pdev[reg] = pdev; + + initdata->driver_data = mc34704; + + pdev->dev.platform_data = initdata; + pdev->dev.driver_data = &mc34704_reg_priv[reg]; + pdev->dev.parent = mc34704->dev; + platform_set_drvdata(pdev, mc34704); + ret = platform_device_add(pdev); + + if (ret != 0) { + dev_err(mc34704->dev, "Failed to register regulator %d: %d\n", + reg, ret); + platform_device_del(pdev); + mc34704->pmic.pdev[reg] = NULL; + } + + return ret; +} +EXPORT_SYMBOL_GPL(mc34704_register_regulator); + +static struct platform_driver mc34704_regulator_driver = { + .probe = mc34704_regulator_probe, + .remove = mc34704_regulator_remove, + .driver = { + .name = "mc34704-regulatr", + }, +}; + +static int __init mc34704_regulator_init(void) +{ + return platform_driver_register(&mc34704_regulator_driver); +} +subsys_initcall(mc34704_regulator_init); + +static void __exit mc34704_regulator_exit(void) +{ + platform_driver_unregister(&mc34704_regulator_driver); +} +module_exit(mc34704_regulator_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MC34704 Regulator Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/reg-mc9s08dz60.c b/drivers/regulator/reg-mc9s08dz60.c new file mode 100644 index 000000000000..16994b7d2efc --- /dev/null +++ b/drivers/regulator/reg-mc9s08dz60.c @@ -0,0 +1,236 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/mc9s08dz60/core.h> +#include <linux/mfd/mc9s08dz60/pmic.h> +#include <linux/platform_device.h> +#include <linux/pmic_status.h> + +/* lcd */ +static int mc9s08dz60_lcd_enable(struct regulator_dev *reg) +{ + return pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 6, 1); +} + +static int mc9s08dz60_lcd_disable(struct regulator_dev *reg) +{ + return pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 6, 0); +} + +static struct regulator_ops mc9s08dz60_lcd_ops = { + .enable = mc9s08dz60_lcd_enable, + .disable = mc9s08dz60_lcd_disable, +}; + +/* wifi */ +static int mc9s08dz60_wifi_enable(struct regulator_dev *reg) +{ + return pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 5, 1); +} + +static int mc9s08dz60_wifi_disable(struct regulator_dev *reg) +{ + return pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 5, 0); +} + +static struct regulator_ops mc9s08dz60_wifi_ops = { + .enable = mc9s08dz60_wifi_enable, + .disable = mc9s08dz60_wifi_disable, +}; + +/* hdd */ +static int mc9s08dz60_hdd_enable(struct regulator_dev *reg) +{ + return pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 4, 1); +} + +static int mc9s08dz60_hdd_disable(struct regulator_dev *reg) +{ + return pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 4, 0); +} + +static struct regulator_ops mc9s08dz60_hdd_ops = { + .enable = mc9s08dz60_hdd_enable, + .disable = mc9s08dz60_hdd_disable, +}; + +/* gps */ +static int mc9s08dz60_gps_enable(struct regulator_dev *reg) +{ + return pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_2, 0, 1); +} + +static int mc9s08dz60_gps_disable(struct regulator_dev *reg) +{ + return pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_2, 0, 0); +} + +static struct regulator_ops mc9s08dz60_gps_ops = { + .enable = mc9s08dz60_gps_enable, + .disable = mc9s08dz60_gps_disable, +}; + +/* speaker */ +static int mc9s08dz60_speaker_enable(struct regulator_dev *reg) +{ + return pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 0, 1); +} + +static int mc9s08dz60_speaker_disable(struct regulator_dev *reg) +{ + return pmic_gpio_set_bit_val(MCU_GPIO_REG_GPIO_CONTROL_1, 0, 0); +} + +static struct regulator_ops mc9s08dz60_speaker_ops = { + .enable = mc9s08dz60_speaker_enable, + .disable = mc9s08dz60_speaker_disable, +}; + +static struct regulator_desc mc9s08dz60_reg[] = { + { + .name = "LCD", + .id = MC9S08DZ60_LCD, + .ops = &mc9s08dz60_lcd_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "WIFI", + .id = MC9S08DZ60_WIFI, + .ops = &mc9s08dz60_wifi_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "HDD", + .id = MC9S08DZ60_HDD, + .ops = &mc9s08dz60_hdd_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "GPS", + .id = MC9S08DZ60_GPS, + .ops = &mc9s08dz60_gps_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "SPKR", + .id = MC9S08DZ60_SPKR, + .ops = &mc9s08dz60_speaker_ops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + +}; + +static int mc9s08dz60_regulator_probe(struct platform_device *pdev) +{ + struct regulator_dev *rdev; + + /* register regulator */ + rdev = regulator_register(&mc9s08dz60_reg[pdev->id], &pdev->dev, + pdev->dev.platform_data, + dev_get_drvdata(&pdev->dev)); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register %s\n", + mc9s08dz60_reg[pdev->id].name); + return PTR_ERR(rdev); + } + + return 0; +} + + +static int mc9s08dz60_regulator_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + + regulator_unregister(rdev); + + return 0; +} + +int mc9s08dz60_register_regulator(struct mc9s08dz60 *mc9s08dz60, int reg, + struct regulator_init_data *initdata) +{ + struct platform_device *pdev; + int ret; + + if (mc9s08dz60->pmic.pdev[reg]) + return -EBUSY; + + pdev = platform_device_alloc("mc9s08dz60-regu", reg); + if (!pdev) + return -ENOMEM; + + mc9s08dz60->pmic.pdev[reg] = pdev; + + initdata->driver_data = mc9s08dz60; + + pdev->dev.platform_data = initdata; + pdev->dev.parent = mc9s08dz60->dev; + platform_set_drvdata(pdev, mc9s08dz60); + ret = platform_device_add(pdev); + + if (ret != 0) { + dev_err(mc9s08dz60->dev, + "Failed to register regulator %d: %d\n", + reg, ret); + platform_device_del(pdev); + mc9s08dz60->pmic.pdev[reg] = NULL; + } + + return ret; +} +EXPORT_SYMBOL_GPL(mc9s08dz60_register_regulator); + +static struct platform_driver mc9s08dz60_regulator_driver = { + .probe = mc9s08dz60_regulator_probe, + .remove = mc9s08dz60_regulator_remove, + .driver = { + .name = "mc9s08dz60-regu", + }, +}; + +static int __init mc9s08dz60_regulator_init(void) +{ + return platform_driver_register(&mc9s08dz60_regulator_driver); +} +subsys_initcall(mc9s08dz60_regulator_init); + +static void __exit mc9s08dz60_regulator_exit(void) +{ + platform_driver_unregister(&mc9s08dz60_regulator_driver); +} +module_exit(mc9s08dz60_regulator_exit); + + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MC9S08DZ60 Regulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/stmp3xxx.c b/drivers/regulator/stmp3xxx.c new file mode 100644 index 000000000000..137e6e641ea4 --- /dev/null +++ b/drivers/regulator/stmp3xxx.c @@ -0,0 +1,301 @@ +/* + * Freescale STMP378X voltage regulators + * + * Embedded Alley Solutions, Inc <source@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/driver.h> +#include <mach/power.h> +#include <mach/regulator.h> + +static int stmp3xxx_set_voltage(struct regulator_dev *reg, int MiniV, int uv) +{ + struct stmp3xxx_regulator *stmp_reg = rdev_get_drvdata(reg); + + if (stmp_reg->rdata->set_voltage) + return stmp_reg->rdata->set_voltage(stmp_reg, uv); + else + return -ENOTSUPP; +} + + +static int stmp3xxx_get_voltage(struct regulator_dev *reg) +{ + struct stmp3xxx_regulator *stmp_reg = rdev_get_drvdata(reg); + + if (stmp_reg->rdata->get_voltage) + return stmp_reg->rdata->get_voltage(stmp_reg); + else + return -ENOTSUPP; +} + +static int stmp3xxx_set_current(struct regulator_dev *reg, int min_uA, int uA) +{ + struct stmp3xxx_regulator *stmp_reg = rdev_get_drvdata(reg); + + if (stmp_reg->rdata->set_current) + return stmp_reg->rdata->set_current(stmp_reg, uA); + else + return -ENOTSUPP; +} + +static int stmp3xxx_get_current(struct regulator_dev *reg) +{ + struct stmp3xxx_regulator *stmp_reg = rdev_get_drvdata(reg); + + if (stmp_reg->rdata->get_current) + return stmp_reg->rdata->get_current(stmp_reg); + else + return -ENOTSUPP; +} + +static int stmp3xxx_enable(struct regulator_dev *reg) +{ + struct stmp3xxx_regulator *stmp_reg = rdev_get_drvdata(reg); + + return stmp_reg->rdata->enable(stmp_reg); +} + +static int stmp3xxx_disable(struct regulator_dev *reg) +{ + struct stmp3xxx_regulator *stmp_reg = rdev_get_drvdata(reg); + + return stmp_reg->rdata->disable(stmp_reg); +} + +static int stmp3xxx_is_enabled(struct regulator_dev *reg) +{ + struct stmp3xxx_regulator *stmp_reg = rdev_get_drvdata(reg); + + return stmp_reg->rdata->is_enabled(stmp_reg); +} + +static int stmp3xxx_set_mode(struct regulator_dev *reg, unsigned int mode) +{ + struct stmp3xxx_regulator *stmp_reg = rdev_get_drvdata(reg); + + return stmp_reg->rdata->set_mode(stmp_reg, mode); +} + +static unsigned int stmp3xxx_get_mode(struct regulator_dev *reg) +{ + struct stmp3xxx_regulator *stmp_reg = rdev_get_drvdata(reg); + + return stmp_reg->rdata->get_mode(stmp_reg); +} + +static unsigned int stmp3xxx_get_optimum_mode(struct regulator_dev *reg, + int input_uV, int output_uV, int load_uA) +{ + struct stmp3xxx_regulator *stmp_reg = rdev_get_drvdata(reg); + + if (stmp_reg->rdata->get_optimum_mode) + return stmp_reg->rdata->get_optimum_mode(stmp_reg, input_uV, + output_uV, load_uA); + else + return -ENOTSUPP; +} + +static struct regulator_ops stmp3xxx_rops = { + .set_voltage = stmp3xxx_set_voltage, + .get_voltage = stmp3xxx_get_voltage, + .set_current_limit = stmp3xxx_set_current, + .get_current_limit = stmp3xxx_get_current, + .enable = stmp3xxx_enable, + .disable = stmp3xxx_disable, + .is_enabled = stmp3xxx_is_enabled, + .set_mode = stmp3xxx_set_mode, + .get_mode = stmp3xxx_get_mode, + .get_optimum_mode = stmp3xxx_get_optimum_mode, +}; + +static struct regulator_desc stmp3xxx_reg_desc[] = { + { + .name = "vddd", + .id = STMP3XXX_VDDD, + .ops = &stmp3xxx_rops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "vdda", + .id = STMP3XXX_VDDA, + .ops = &stmp3xxx_rops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "vddio", + .id = STMP3XXX_VDDIO, + .ops = &stmp3xxx_rops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "vddd_bo", + .id = STMP3XXX_VDDDBO, + .ops = &stmp3xxx_rops, + .irq = 0, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE + }, + { + .name = "overall_current", + .id = STMP3XXX_OVERALL_CUR, + .ops = &stmp3xxx_rops, + .irq = 0, + .type = REGULATOR_CURRENT, + .owner = THIS_MODULE + }, +}; + +static int reg_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + unsigned long flags; + struct stmp3xxx_regulator *sreg = + container_of(self, struct stmp3xxx_regulator , nb); + + switch (event) { + case STMP3XXX_REG5V_IS_USB: + spin_lock_irqsave(&sreg->lock, flags); + sreg->rdata->max_current = 500000; + spin_unlock_irqrestore(&sreg->lock, flags); + break; + case STMP3XXX_REG5V_NOT_USB: + spin_lock_irqsave(&sreg->lock, flags); + sreg->rdata->max_current = 0x7fffffff; + spin_unlock_irqrestore(&sreg->lock, flags); + break; + } + + return 0; +} + +int stmp3xxx_regulator_probe(struct platform_device *pdev) +{ + struct regulator_desc *rdesc; + struct regulator_dev *rdev; + struct stmp3xxx_regulator *sreg; + struct regulator_init_data *initdata; + + sreg = platform_get_drvdata(pdev); + initdata = pdev->dev.platform_data; + sreg->cur_current = 0; + sreg->next_current = 0; + sreg->cur_voltage = 0; + + init_waitqueue_head(&sreg->wait_q); + spin_lock_init(&sreg->lock); + + if (pdev->id > STMP3XXX_OVERALL_CUR) { + rdesc = kzalloc(sizeof(struct regulator_desc), GFP_KERNEL); + memcpy(rdesc, &stmp3xxx_reg_desc[STMP3XXX_OVERALL_CUR], + sizeof(struct regulator_desc)); + rdesc->name = kstrdup(sreg->rdata->name, GFP_KERNEL); + } else + rdesc = &stmp3xxx_reg_desc[pdev->id]; + + pr_debug("probing regulator %s %s %d\n", + sreg->rdata->name, + rdesc->name, + pdev->id); + + /* register regulator */ + rdev = regulator_register(rdesc, &pdev->dev, + initdata, sreg); + + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register %s\n", + rdesc->name); + return PTR_ERR(rdev); + } + + if (sreg->rdata->max_current) { + struct regulator *regu; + regu = regulator_get(NULL, sreg->rdata->name); + sreg->nb.notifier_call = reg_callback; + regulator_register_notifier(regu, &sreg->nb); + } + + return 0; +} + + +int stmp3xxx_regulator_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + + regulator_unregister(rdev); + + return 0; + +} + +int stmp3xxx_register_regulator( + struct stmp3xxx_regulator *reg_data, int reg, + struct regulator_init_data *initdata) +{ + struct platform_device *pdev; + int ret; + + pdev = platform_device_alloc("stmp3xxx_reg", reg); + if (!pdev) + return -ENOMEM; + + pdev->dev.platform_data = initdata; + + platform_set_drvdata(pdev, reg_data); + ret = platform_device_add(pdev); + + if (ret != 0) { + pr_debug("Failed to register regulator %d: %d\n", + reg, ret); + platform_device_del(pdev); + } + pr_debug("register regulator %s, %d: %d\n", + reg_data->rdata->name, reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(stmp3xxx_register_regulator); + +struct platform_driver stmp3xxx_reg = { + .driver = { + .name = "stmp3xxx_reg", + }, + .probe = stmp3xxx_regulator_probe, + .remove = stmp3xxx_regulator_remove, +}; + +int stmp3xxx_regulator_init(void) +{ + return platform_driver_register(&stmp3xxx_reg); +} + +void stmp3xxx_regulator_exit(void) +{ + platform_driver_unregister(&stmp3xxx_reg); +} + +postcore_initcall(stmp3xxx_regulator_init); +module_exit(stmp3xxx_regulator_exit); diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 81adbdbd5042..9b6c16bf0351 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -717,6 +717,43 @@ config RTC_DRV_PXA This RTC driver uses PXA RTC registers available since pxa27x series (RDxR, RYxR) instead of legacy RCNR, RTAR. +config RTC_MXC + tristate "Freescale MXC Real Time Clock" + depends on ARCH_MXC + depends on RTC_CLASS + help + Support for Freescale RTC MXC + +config RTC_DRV_MXC_V2 + tristate "Freescale MXC Secure Real Time Clock" + depends on ARCH_MXC + depends on RTC_CLASS + help + Support for Freescale SRTC MXC + +config RTC_DRV_IMXDI + tristate "Freescale IMX DryIce Real Time Clock" + depends on ARCH_MXC + depends on RTC_CLASS + help + Support for Freescale IMX DryIce RTC + +config RTC_MC13892 + tristate "Freescale MC13892 Real Time Clock" + depends on ARCH_MXC && MXC_PMIC_MC13892 + depends on RTC_CLASS + help + Support for Freescale MC13892 RTC + +config RTC_DRV_STMP3XXX + tristate "Sigmatel STMP3xxx series SoC RTC" + depends on ARCH_STMP3XXX && RTC_CLASS + help + Say Y here to get support for the real-time clock peripheral + on Sigmatel STMP3xxx series SoCs (tested on STMP3700). + + This driver can also be build as a module. If so, the module + will be called rtc-stmp3xxx. config RTC_DRV_SUN4V bool "SUN4V Hypervisor RTC" diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 3c0f2b2ac927..e327ad2d188e 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -78,3 +78,8 @@ obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o obj-$(CONFIG_RTC_DRV_PS3) += rtc-ps3.o +obj-$(CONFIG_RTC_MXC) += rtc-mxc.o +obj-$(CONFIG_RTC_DRV_MXC_V2) += rtc-mxc_v2.o +obj-$(CONFIG_RTC_DRV_IMXDI) += rtc-imxdi.o +obj-$(CONFIG_RTC_MC13892) += rtc-mc13892.o +obj-$(CONFIG_RTC_DRV_STMP3XXX) += rtc-stmp3xxx.o diff --git a/drivers/rtc/rtc-imxdi.c b/drivers/rtc/rtc-imxdi.c new file mode 100644 index 000000000000..b54fb638c840 --- /dev/null +++ b/drivers/rtc/rtc-imxdi.c @@ -0,0 +1,580 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* based on rtc-mc13892.c */ + +/* + * This driver uses the 47-bit 32 kHz counter in the Freescale DryIce block + * to implement a Linux RTC. Times and alarms are truncated to seconds. + * Since the RTC framework performs API locking via rtc->ops_lock the + * only simultaneous accesses we need to deal with is updating DryIce + * registers while servicing an alarm. + * + * Note that reading the DSR (DryIce Status Register) automatically clears + * the WCF (Write Complete Flag). All DryIce writes are synchronized to the + * LP (Low Power) domain and set the WCF upon completion. Writes to the + * DIER (DryIce Interrupt Enable Register) are the only exception. These + * occur at normal bus speeds and do not set WCF. Periodic interrupts are + * not supported by the hardware. + */ + +/* #define DEBUG */ +/* #define DI_DEBUG_REGIO */ + +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/workqueue.h> + +/* DryIce Register Definitions */ + +#define DTCMR 0x00 /* Time Counter MSB Reg */ +#define DTCLR 0x04 /* Time Counter LSB Reg */ + +#define DCAMR 0x08 /* Clock Alarm MSB Reg */ +#define DCALR 0x0c /* Clock Alarm LSB Reg */ +#define DCAMR_UNSET 0xFFFFFFFF /* doomsday - 1 sec */ + +#define DCR 0x10 /* Control Reg */ +#define DCR_TCE (1 << 3) /* Time Counter Enable */ + +#define DSR 0x14 /* Status Reg */ +#define DSR_WBF (1 << 10) /* Write Busy Flag */ +#define DSR_WNF (1 << 9) /* Write Next Flag */ +#define DSR_WCF (1 << 8) /* Write Complete Flag */ +#define DSR_WEF (1 << 7) /* Write Error Flag */ +#define DSR_CAF (1 << 4) /* Clock Alarm Flag */ +#define DSR_NVF (1 << 1) /* Non-Valid Flag */ +#define DSR_SVF (1 << 0) /* Security Violation Flag */ + +#define DIER 0x18 /* Interrupt Enable Reg */ +#define DIER_WNIE (1 << 9) /* Write Next Interrupt Enable */ +#define DIER_WCIE (1 << 8) /* Write Complete Interrupt Enable */ +#define DIER_WEIE (1 << 7) /* Write Error Interrupt Enable */ +#define DIER_CAIE (1 << 4) /* Clock Alarm Interrupt Enable */ + +#ifndef DI_DEBUG_REGIO +/* dryice read register */ +#define di_read(pdata, reg) __raw_readl((pdata)->ioaddr + (reg)) + +/* dryice write register */ +#define di_write(pdata, val, reg) __raw_writel((val), (pdata)->ioaddr + (reg)) +#else +/* dryice read register - debug version */ +static inline u32 di_read(struct rtc_drv_data *pdata, int reg) +{ + u32 val = __raw_readl(pdata->ioaddr + reg); + pr_info("di_read(0x%02x) = 0x%08x\n", reg, val); + return val; +} + +/* dryice write register - debug version */ +static inline void di_write(struct rtc_drv_data *pdata, u32 val, int reg) +{ + printk(KERN_INFO "di_write(0x%08x, 0x%02x)\n", val, reg); + __raw_writel(val, pdata->ioaddr + reg); +} +#endif + +/* + * dryice write register with wait and error handling. + * all registers, except for DIER, should use this method. + */ +#define di_write_wait_err(pdata, val, reg, rc, label) \ + do { \ + if (di_write_wait((pdata), (val), (reg))) { \ + rc = -EIO; \ + goto label; \ + } \ + } while (0) + +struct rtc_drv_data { + struct platform_device *pdev; /* pointer to platform dev */ + struct rtc_device *rtc; /* pointer to rtc struct */ + unsigned long baseaddr; /* physical bass address */ + void __iomem *ioaddr; /* virtual base address */ + int size; /* size of register region */ + int irq; /* dryice normal irq */ + struct clk *clk; /* dryice clock control */ + u32 dsr; /* copy of dsr reg from isr */ + spinlock_t irq_lock; /* irq resource lock */ + wait_queue_head_t write_wait; /* write-complete queue */ + struct mutex write_mutex; /* force reg writes to be sequential */ + struct work_struct work; /* schedule alarm work */ +}; + +/* + * enable a dryice interrupt + */ +static inline void di_int_enable(struct rtc_drv_data *pdata, u32 intr) +{ + unsigned long flags; + + spin_lock_irqsave(&pdata->irq_lock, flags); + di_write(pdata, di_read(pdata, DIER) | intr, DIER); + spin_unlock_irqrestore(&pdata->irq_lock, flags); +} + +/* + * disable a dryice interrupt + */ +static inline void di_int_disable(struct rtc_drv_data *pdata, u32 intr) +{ + unsigned long flags; + + spin_lock_irqsave(&pdata->irq_lock, flags); + di_write(pdata, di_read(pdata, DIER) & ~intr, DIER); + spin_unlock_irqrestore(&pdata->irq_lock, flags); +} + +/* + * This function attempts to clear the dryice write-error flag. + * + * A dryice write error is similar to a bus fault and should not occur in + * normal operation. Clearing the flag requires another write, so the root + * cause of the problem may need to be fixed before the flag can be cleared. + */ +static void clear_write_error(struct rtc_drv_data *pdata) +{ + int cnt; + + dev_warn(&pdata->pdev->dev, "WARNING: Register write error!\n"); + + for (;;) { + /* clear the write error flag */ + di_write(pdata, DSR_WEF, DSR); + + /* wait for it to take effect */ + for (cnt = 0; cnt < 100; cnt++) { + if ((di_read(pdata, DSR) & DSR_WEF) == 0) + return; + udelay(10); + } + dev_err(&pdata->pdev->dev, + "ERROR: Cannot clear write-error flag!\n"); + } +} + +/* + * Write a dryice register and wait until it completes. + * + * This function uses interrupts to determine when the + * write has completed. + */ +static int di_write_wait(struct rtc_drv_data *pdata, u32 val, int reg) +{ + int ret; + int rc = 0; + + /* serialize register writes */ + mutex_lock(&pdata->write_mutex); + + /* enable the write-complete interrupt */ + di_int_enable(pdata, DIER_WCIE); + + pdata->dsr = 0; + + /* do the register write */ + di_write(pdata, val, reg); + + /* wait for the write to finish */ + ret = wait_event_interruptible_timeout(pdata->write_wait, + pdata->dsr & (DSR_WCF | DSR_WEF), + 1 * HZ); + if (ret == 0) + dev_warn(&pdata->pdev->dev, "Write-wait timeout\n"); + + /* check for write error */ + if (pdata->dsr & DSR_WEF) { + clear_write_error(pdata); + rc = -EIO; + } + mutex_unlock(&pdata->write_mutex); + return rc; +} + +/* + * rtc device ioctl + * + * The rtc framework handles the basic rtc ioctls on behalf + * of the driver by calling the functions registered in the + * rtc_ops structure. + */ +static int dryice_rtc_ioctl(struct device *dev, unsigned int cmd, + unsigned long arg) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + + dev_dbg(dev, "%s(0x%x)\n", __func__, cmd); + switch (cmd) { + case RTC_AIE_OFF: /* alarm disable */ + di_int_disable(pdata, DIER_CAIE); + return 0; + + case RTC_AIE_ON: /* alarm enable */ + di_int_enable(pdata, DIER_CAIE); + return 0; + } + return -ENOIOCTLCMD; +} + +/* + * read the seconds portion of the current time from the dryice time counter + */ +static int dryice_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + unsigned long now; + + dev_dbg(dev, "%s\n", __func__); + now = di_read(pdata, DTCMR); + rtc_time_to_tm(now, tm); + + return 0; +} + +/* + * set the seconds portion of dryice time counter and clear the + * fractional part. + */ +static int dryice_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + unsigned long now; + int rc; + + dev_dbg(dev, "%s\n", __func__); + rc = rtc_tm_to_time(tm, &now); + if (rc == 0) { + /* zero the fractional part first */ + di_write_wait_err(pdata, 0, DTCLR, rc, err); + di_write_wait_err(pdata, now, DTCMR, rc, err); + } +err: + return rc; +} + +/* + * read the seconds portion of the alarm register. + * the fractional part of the alarm register is always zero. + */ +static int dryice_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + u32 dcamr; + + dev_dbg(dev, "%s\n", __func__); + dcamr = di_read(pdata, DCAMR); + rtc_time_to_tm(dcamr, &alarm->time); + + /* alarm is enabled if the interrupt is enabled */ + alarm->enabled = (di_read(pdata, DIER) & DIER_CAIE) != 0; + + /* don't allow the DSR read to mess up DSR_WCF */ + mutex_lock(&pdata->write_mutex); + + /* alarm is pending if the alarm flag is set */ + alarm->pending = (di_read(pdata, DSR) & DSR_CAF) != 0; + + mutex_unlock(&pdata->write_mutex); + + return 0; +} + +/* + * set the seconds portion of dryice alarm register + */ +static int dryice_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + unsigned long now; + unsigned long alarm_time; + int rc; + + dev_dbg(dev, "%s\n", __func__); + rc = rtc_tm_to_time(&alarm->time, &alarm_time); + if (rc) + return rc; + + /* don't allow setting alarm in the past */ + now = di_read(pdata, DTCMR); + if (alarm_time < now) + return -EINVAL; + + /* write the new alarm time */ + di_write_wait_err(pdata, (u32)alarm_time, DCAMR, rc, err); + + if (alarm->enabled) + di_int_enable(pdata, DIER_CAIE); /* enable alarm intr */ + else + di_int_disable(pdata, DIER_CAIE); /* disable alarm intr */ +err: + return rc; +} + +static struct rtc_class_ops dryice_rtc_ops = { + .ioctl = dryice_rtc_ioctl, + .read_time = dryice_rtc_read_time, + .set_time = dryice_rtc_set_time, + .read_alarm = dryice_rtc_read_alarm, + .set_alarm = dryice_rtc_set_alarm, +}; + +/* + * dryice "normal" interrupt handler + */ +static irqreturn_t dryice_norm_irq(int irq, void *dev_id) +{ + struct rtc_drv_data *pdata = dev_id; + u32 dsr, dier; + irqreturn_t rc = IRQ_NONE; + + dier = di_read(pdata, DIER); + + /* handle write complete and write error cases */ + if ((dier & DIER_WCIE)) { + /*If the write wait queue is empty then there is no pending + operations. It means the interrupt is for DryIce -Security. + IRQ must be returned as none.*/ + if (list_empty_careful(&pdata->write_wait.task_list)) + return rc; + + /* DSR_WCF clears itself on DSR read */ + dsr = di_read(pdata, DSR); + if ((dsr & (DSR_WCF | DSR_WEF))) { + /* mask the interrupt */ + di_int_disable(pdata, DIER_WCIE); + + /* save the dsr value for the wait queue */ + pdata->dsr |= dsr; + + wake_up_interruptible(&pdata->write_wait); + rc = IRQ_HANDLED; + } + } + + /* handle the alarm case */ + if ((dier & DIER_CAIE)) { + /* DSR_WCF clears itself on DSR read */ + dsr = di_read(pdata, DSR); + if (dsr & DSR_CAF) { + /* mask the interrupt */ + di_int_disable(pdata, DIER_CAIE); + + /* finish alarm in user context */ + schedule_work(&pdata->work); + rc = IRQ_HANDLED; + } + } + return rc; +} + +/* + * post the alarm event from user context so it can sleep + * on the write completion. + */ +static void dryice_work(struct work_struct *work) +{ + struct rtc_drv_data *pdata = container_of(work, struct rtc_drv_data, + work); + int rc; + + /* dismiss the interrupt (ignore error) */ + di_write_wait_err(pdata, DSR_CAF, DSR, rc, err); +err: + /* + * pass the alarm event to the rtc framework. note that + * rtc_update_irq expects to be called with interrupts off. + */ + local_irq_disable(); + rtc_update_irq(pdata->rtc, 1, RTC_AF | RTC_IRQF); + local_irq_enable(); +} + +/* + * probe for dryice rtc device + */ +static int dryice_rtc_probe(struct platform_device *pdev) +{ + struct rtc_device *rtc; + struct resource *res; + struct rtc_drv_data *pdata = NULL; + void __iomem *ioaddr = NULL; + int rc = 0; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->pdev = pdev; + pdata->irq = -1; + pdata->size = res->end - res->start + 1; + + if (!request_mem_region(res->start, pdata->size, pdev->name)) { + rc = -EBUSY; + goto err; + } + pdata->baseaddr = res->start; + ioaddr = ioremap(pdata->baseaddr, pdata->size); + if (!ioaddr) { + rc = -ENOMEM; + goto err; + } + pdata->ioaddr = ioaddr; + pdata->irq = platform_get_irq(pdev, 0); + + init_waitqueue_head(&pdata->write_wait); + + INIT_WORK(&pdata->work, dryice_work); + + mutex_init(&pdata->write_mutex); + + pdata->clk = clk_get(NULL, "dryice_clk"); + clk_enable(pdata->clk); + + if (pdata->irq >= 0) { + if (request_irq(pdata->irq, dryice_norm_irq, IRQF_SHARED, + pdev->name, pdata) < 0) { + dev_warn(&pdev->dev, "interrupt not available.\n"); + pdata->irq = -1; + goto err; + } + } + + /* + * Initialize dryice hardware + */ + + /* put dryice into valid state */ + if (di_read(pdata, DSR) & DSR_NVF) + di_write_wait_err(pdata, DSR_NVF | DSR_SVF, DSR, rc, err); + + /* mask alarm interrupt */ + di_int_disable(pdata, DIER_CAIE); + + /* initialize alarm */ + di_write_wait_err(pdata, DCAMR_UNSET, DCAMR, rc, err); + di_write_wait_err(pdata, 0, DCALR, rc, err); + + /* clear alarm flag */ + if (di_read(pdata, DSR) & DSR_CAF) + di_write_wait_err(pdata, DSR_CAF, DSR, rc, err); + + /* the timer won't count if it has never been written to */ + if (!di_read(pdata, DTCMR)) + di_write_wait_err(pdata, 0, DTCMR, rc, err); + + /* start keeping time */ + if (!(di_read(pdata, DCR) & DCR_TCE)) + di_write_wait_err(pdata, di_read(pdata, DCR) | DCR_TCE, DCR, + rc, err); + + rtc = rtc_device_register(pdev->name, &pdev->dev, + &dryice_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc)) { + rc = PTR_ERR(rtc); + goto err; + } + pdata->rtc = rtc; + platform_set_drvdata(pdev, pdata); + + return 0; +err: + if (pdata->rtc) + rtc_device_unregister(pdata->rtc); + + if (pdata->irq >= 0) + free_irq(pdata->irq, pdata); + + if (pdata->clk) { + clk_disable(pdata->clk); + clk_put(pdata->clk); + } + + if (pdata->ioaddr) + iounmap(pdata->ioaddr); + + if (pdata->baseaddr) + release_mem_region(pdata->baseaddr, pdata->size); + + kfree(pdata); + + return rc; +} + +static int __exit dryice_rtc_remove(struct platform_device *pdev) +{ + struct rtc_drv_data *pdata = platform_get_drvdata(pdev); + + flush_scheduled_work(); + + if (pdata->rtc) + rtc_device_unregister(pdata->rtc); + + /* mask alarm interrupt */ + di_int_disable(pdata, DIER_CAIE); + + if (pdata->irq >= 0) + free_irq(pdata->irq, pdata); + + if (pdata->clk) { + clk_disable(pdata->clk); + clk_put(pdata->clk); + } + + if (pdata->ioaddr) + iounmap(pdata->ioaddr); + + if (pdata->baseaddr) + release_mem_region(pdata->baseaddr, pdata->size); + + kfree(pdata); + + return 0; +} + +static struct platform_driver dryice_rtc_driver = { + .driver = { + .name = "imxdi_rtc", + .owner = THIS_MODULE, + }, + .probe = dryice_rtc_probe, + .remove = __exit_p(dryice_rtc_remove), +}; + +static int __init dryice_rtc_init(void) +{ + pr_info("IMXDI Realtime Clock Driver (RTC)\n"); + return platform_driver_register(&dryice_rtc_driver); +} + +static void __exit dryice_rtc_exit(void) +{ + platform_driver_unregister(&dryice_rtc_driver); +} + +module_init(dryice_rtc_init); +module_exit(dryice_rtc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IMXDI Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-mc13892.c b/drivers/rtc/rtc-mc13892.c new file mode 100644 index 000000000000..e579c1853a26 --- /dev/null +++ b/drivers/rtc/rtc-mc13892.c @@ -0,0 +1,256 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/rtc.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <linux/pmic_status.h> +#include <linux/pmic_external.h> + +#define RTC_TIME_LSH 0 +#define RTC_DAY_LSH 0 +#define RTCALARM_TIME_LSH 0 +#define RTCALARM_DAY_LSH 0 + +#define RTC_TIME_WID 17 +#define RTC_DAY_WID 15 +#define RTCALARM_TIME_WID 17 +#define RTCALARM_DAY_WID 15 + +static unsigned long rtc_status; + +static int mxc_rtc_open(struct device *dev) +{ + if (test_and_set_bit(1, &rtc_status)) + return -EBUSY; + return 0; +} + +static void mxc_rtc_release(struct device *dev) +{ + clear_bit(1, &rtc_status); +} + +static int mxc_rtc_ioctl(struct device *dev, unsigned int cmd, + unsigned long arg) +{ + switch (cmd) { + case RTC_AIE_OFF: + pr_debug("alarm off\n"); + CHECK_ERROR(pmic_write_reg(REG_RTC_ALARM, 0x100000, 0x100000)); + return 0; + case RTC_AIE_ON: + pr_debug("alarm on\n"); + CHECK_ERROR(pmic_write_reg(REG_RTC_ALARM, 0, 0x100000)); + return 0; + } + + return -ENOIOCTLCMD; +} + +static int mxc_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0, day_reg_val2; + unsigned int mask, value; + unsigned long time; + + do { + mask = BITFMASK(RTC_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY, &value, mask)); + day_reg_val = BITFEXT(value, RTC_DAY); + + mask = BITFMASK(RTC_TIME); + CHECK_ERROR(pmic_read_reg(REG_RTC_TIME, &value, mask)); + tod_reg_val = BITFEXT(value, RTC_TIME); + + mask = BITFMASK(RTC_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY, &value, mask)); + day_reg_val2 = BITFEXT(value, RTC_DAY); + } while (day_reg_val != day_reg_val2); + + time = (unsigned long)((unsigned long)(tod_reg_val & + 0x0001FFFF) + + (unsigned long)(day_reg_val * 86400)); + + rtc_time_to_tm(time, tm); + + return 0; +} + +static int mxc_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val, day_reg_val2 = 0; + unsigned int mask, value; + unsigned long time; + + if (rtc_valid_tm(tm)) + return -1; + + rtc_tm_to_time(tm, &time); + + tod_reg_val = time % 86400; + day_reg_val = time / 86400; + + do { + mask = BITFMASK(RTC_DAY); + value = BITFVAL(RTC_DAY, day_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_DAY, value, mask)); + + mask = BITFMASK(RTC_TIME); + value = BITFVAL(RTC_TIME, tod_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_TIME, value, mask)); + + mask = BITFMASK(RTC_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY, &value, mask)); + day_reg_val2 = BITFEXT(value, RTC_DAY); + } while (day_reg_val != day_reg_val2); + + return 0; +} + +static int mxc_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + unsigned long time; + + mask = BITFMASK(RTCALARM_TIME); + CHECK_ERROR(pmic_read_reg(REG_RTC_ALARM, &value, mask)); + tod_reg_val = BITFEXT(value, RTCALARM_TIME); + + mask = BITFMASK(RTCALARM_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY_ALARM, &value, mask)); + day_reg_val = BITFEXT(value, RTCALARM_DAY); + + time = (unsigned long)((unsigned long)(tod_reg_val & + 0x0001FFFF) + + (unsigned long)(day_reg_val * 86400)); + rtc_time_to_tm(time, &(alrm->time)); + + return 0; +} + +static int mxc_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + unsigned long time; + + if (rtc_valid_tm(&alrm->time)) + return -1; + + rtc_tm_to_time(&alrm->time, &time); + + tod_reg_val = time % 86400; + day_reg_val = time / 86400; + + mask = BITFMASK(RTCALARM_TIME); + value = BITFVAL(RTCALARM_TIME, tod_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_ALARM, value, mask)); + + mask = BITFMASK(RTCALARM_DAY); + value = BITFVAL(RTCALARM_DAY, day_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_DAY_ALARM, value, mask)); + + return 0; +} + +struct rtc_drv_data { + struct rtc_device *rtc; + pmic_event_callback_t event; +}; + +static struct rtc_class_ops mxc_rtc_ops = { + .open = mxc_rtc_open, + .release = mxc_rtc_release, + .ioctl = mxc_rtc_ioctl, + .read_time = mxc_rtc_read_time, + .set_time = mxc_rtc_set_time, + .read_alarm = mxc_rtc_read_alarm, + .set_alarm = mxc_rtc_set_alarm, +}; + +static void mxc_rtc_alarm_int(void *data) +{ + struct rtc_drv_data *pdata = data; + + rtc_update_irq(pdata->rtc, 1, RTC_AF | RTC_IRQF); +} + +static int mxc_rtc_probe(struct platform_device *pdev) +{ + struct rtc_drv_data *pdata = NULL; + + printk(KERN_INFO "mc13892 rtc probe start\n"); + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + + if (!pdata) + return -ENOMEM; + + pdata->event.func = mxc_rtc_alarm_int; + pdata->event.param = pdata; + CHECK_ERROR(pmic_event_subscribe(EVENT_TODAI, pdata->event)); + + device_init_wakeup(&pdev->dev, 1); + pdata->rtc = rtc_device_register(pdev->name, &pdev->dev, + &mxc_rtc_ops, THIS_MODULE); + + platform_set_drvdata(pdev, pdata); + if (IS_ERR(pdata->rtc)) + return -1; + + printk(KERN_INFO "mc13892 rtc probe succeed\n"); + return 0; +} + +static int __exit mxc_rtc_remove(struct platform_device *pdev) +{ + struct rtc_drv_data *pdata = platform_get_drvdata(pdev); + + rtc_device_unregister(pdata->rtc); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_TODAI, pdata->event)); + + return 0; +} + +static struct platform_driver mxc_rtc_driver = { + .driver = { + .name = "pmic_rtc", + }, + .probe = mxc_rtc_probe, + .remove = __exit_p(mxc_rtc_remove), +}; + +static int __init mxc_rtc_init(void) +{ + return platform_driver_register(&mxc_rtc_driver); +} + +static void __exit mxc_rtc_exit(void) +{ + platform_driver_unregister(&mxc_rtc_driver); + +} + +module_init(mxc_rtc_init); +module_exit(mxc_rtc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MC13892 Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-mxc.c b/drivers/rtc/rtc-mxc.c new file mode 100644 index 000000000000..b6b168d8ac6b --- /dev/null +++ b/drivers/rtc/rtc-mxc.c @@ -0,0 +1,806 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * Implementation based on rtc-ds1553.c + */ + +/*! + * @defgroup RTC Real Time Clock (RTC) Driver + */ +/*! + * @file rtc-mxc.c + * @brief Real Time Clock interface + * + * This file contains Real Time Clock interface for Linux. + * + * @ingroup RTC + */ + +#include <linux/rtc.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/uaccess.h> + +#include <mach/hardware.h> +#define RTC_INPUT_CLK_32768HZ (0x00 << 5) +#define RTC_INPUT_CLK_32000HZ (0x01 << 5) +#define RTC_INPUT_CLK_38400HZ (0x02 << 5) + +#define RTC_SW_BIT (1 << 0) +#define RTC_ALM_BIT (1 << 2) +#define RTC_1HZ_BIT (1 << 4) +#define RTC_2HZ_BIT (1 << 7) +#define RTC_SAM0_BIT (1 << 8) +#define RTC_SAM1_BIT (1 << 9) +#define RTC_SAM2_BIT (1 << 10) +#define RTC_SAM3_BIT (1 << 11) +#define RTC_SAM4_BIT (1 << 12) +#define RTC_SAM5_BIT (1 << 13) +#define RTC_SAM6_BIT (1 << 14) +#define RTC_SAM7_BIT (1 << 15) +#define PIT_ALL_ON (RTC_2HZ_BIT | RTC_SAM0_BIT | RTC_SAM1_BIT | \ + RTC_SAM2_BIT | RTC_SAM3_BIT | RTC_SAM4_BIT | \ + RTC_SAM5_BIT | RTC_SAM6_BIT | RTC_SAM7_BIT) + +#define RTC_ENABLE_BIT (1 << 7) + +#define MAX_PIE_NUM 9 +#define MAX_PIE_FREQ 512 +const u32 PIE_BIT_DEF[MAX_PIE_NUM][2] = { + {2, RTC_2HZ_BIT}, + {4, RTC_SAM0_BIT}, + {8, RTC_SAM1_BIT}, + {16, RTC_SAM2_BIT}, + {32, RTC_SAM3_BIT}, + {64, RTC_SAM4_BIT}, + {128, RTC_SAM5_BIT}, + {256, RTC_SAM6_BIT}, + {MAX_PIE_FREQ, RTC_SAM7_BIT}, +}; + +/* Those are the bits from a classic RTC we want to mimic */ +#define RTC_IRQF 0x80 /* any of the following 3 is active */ +#define RTC_PF 0x40 /* Periodic interrupt */ +#define RTC_AF 0x20 /* Alarm interrupt */ +#define RTC_UF 0x10 /* Update interrupt for 1Hz RTC */ + +#define MXC_RTC_TIME 0 +#define MXC_RTC_ALARM 1 + +#define RTC_HOURMIN 0x00 /* 32bit rtc hour/min counter reg */ +#define RTC_SECOND 0x04 /* 32bit rtc seconds counter reg */ +#define RTC_ALRM_HM 0x08 /* 32bit rtc alarm hour/min reg */ +#define RTC_ALRM_SEC 0x0C /* 32bit rtc alarm seconds reg */ +#define RTC_RTCCTL 0x10 /* 32bit rtc control reg */ +#define RTC_RTCISR 0x14 /* 32bit rtc interrupt status reg */ +#define RTC_RTCIENR 0x18 /* 32bit rtc interrupt enable reg */ +#define RTC_STPWCH 0x1C /* 32bit rtc stopwatch min reg */ +#define RTC_DAYR 0x20 /* 32bit rtc days counter reg */ +#define RTC_DAYALARM 0x24 /* 32bit rtc day alarm reg */ +#define RTC_TEST1 0x28 /* 32bit rtc test reg 1 */ +#define RTC_TEST2 0x2C /* 32bit rtc test reg 2 */ +#define RTC_TEST3 0x30 /* 32bit rtc test reg 3 */ + +struct rtc_plat_data { + struct rtc_device *rtc; + void __iomem *ioaddr; + unsigned long baseaddr; + int irq; + struct clk *clk; + unsigned int irqen; + int alrm_sec; + int alrm_min; + int alrm_hour; + int alrm_mday; +}; + +/*! + * @defgroup RTC Real Time Clock (RTC) Driver + */ +/*! + * @file rtc-mxc.c + * @brief Real Time Clock interface + * + * This file contains Real Time Clock interface for Linux. + * + * @ingroup RTC + */ + +#if defined(CONFIG_MXC_PMIC_SC55112_RTC) || defined(CONFIG_MXC_MC13783_RTC) ||\ + defined(CONFIG_MXC_MC9SDZ60_RTC) +#include <linux/pmic_rtc.h> +#else +#define pmic_rtc_get_time(args) MXC_EXTERNAL_RTC_NONE +#define pmic_rtc_set_time(args) MXC_EXTERNAL_RTC_NONE +#define pmic_rtc_loaded() 0 +#endif + +#define RTC_VERSION "1.0" +#define MXC_EXTERNAL_RTC_OK 0 +#define MXC_EXTERNAL_RTC_ERR -1 +#define MXC_EXTERNAL_RTC_NONE -2 + +/*! + * This function reads the RTC value from some external source. + * + * @param second pointer to the returned value in second + * + * @return 0 if successful; non-zero otherwise + */ +int get_ext_rtc_time(u32 * second) +{ + int ret = 0; + struct timeval tmp; + if (!pmic_rtc_loaded()) { + return MXC_EXTERNAL_RTC_NONE; + } + + ret = pmic_rtc_get_time(&tmp); + + if (0 == ret) + *second = tmp.tv_sec; + else + ret = MXC_EXTERNAL_RTC_ERR; + + return ret; +} + +/*! + * This function sets external RTC + * + * @param second value in second to be set to external RTC + * + * @return 0 if successful; non-zero otherwise + */ +int set_ext_rtc_time(u32 second) +{ + int ret = 0; + struct timeval tmp; + + if (!pmic_rtc_loaded()) { + return MXC_EXTERNAL_RTC_NONE; + } + + tmp.tv_sec = second; + + ret = pmic_rtc_set_time(&tmp); + + if (0 != ret) + ret = MXC_EXTERNAL_RTC_ERR; + + return ret; +} + +static u32 rtc_freq = 2; /* minimun value for PIE */ +static unsigned long rtc_status; + +static struct rtc_time g_rtc_alarm = { + .tm_year = 0, + .tm_mon = 0, + .tm_mday = 0, + .tm_hour = 0, + .tm_mon = 0, + .tm_sec = 0, +}; + +static DEFINE_SPINLOCK(rtc_lock); + +/*! + * This function is used to obtain the RTC time or the alarm value in + * second. + * + * @param time_alarm use MXC_RTC_TIME for RTC time value; MXC_RTC_ALARM for alarm value + * + * @return The RTC time or alarm time in second. + */ +static u32 get_alarm_or_time(struct device *dev, int time_alarm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + u32 day, hr, min, sec, hr_min; + if (time_alarm == MXC_RTC_TIME) { + day = readw(ioaddr + RTC_DAYR); + hr_min = readw(ioaddr + RTC_HOURMIN); + sec = readw(ioaddr + RTC_SECOND); + } else if (time_alarm == MXC_RTC_ALARM) { + day = readw(ioaddr + RTC_DAYALARM); + hr_min = (0x0000FFFF) & readw(ioaddr + RTC_ALRM_HM); + sec = readw(ioaddr + RTC_ALRM_SEC); + } else { + panic("wrong value for time_alarm=%d\n", time_alarm); + } + + hr = hr_min >> 8; + min = hr_min & 0x00FF; + + return ((((day * 24 + hr) * 60) + min) * 60 + sec); +} + +/*! + * This function sets the RTC alarm value or the time value. + * + * @param time_alarm the new alarm value to be updated in the RTC + * @param time use MXC_RTC_TIME for RTC time value; MXC_RTC_ALARM for alarm value + */ +static void set_alarm_or_time(struct device *dev, int time_alarm, u32 time) +{ + u32 day, hr, min, sec, temp; + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + day = time / 86400; + time -= day * 86400; + /* time is within a day now */ + hr = time / 3600; + time -= hr * 3600; + /* time is within an hour now */ + min = time / 60; + sec = time - min * 60; + + temp = (hr << 8) + min; + + if (time_alarm == MXC_RTC_TIME) { + writew(day, ioaddr + RTC_DAYR); + writew(sec, ioaddr + RTC_SECOND); + writew(temp, ioaddr + RTC_HOURMIN); + } else if (time_alarm == MXC_RTC_ALARM) { + writew(day, ioaddr + RTC_DAYALARM); + writew(sec, ioaddr + RTC_ALRM_SEC); + writew(temp, ioaddr + RTC_ALRM_HM); + } else { + panic("wrong value for time_alarm=%d\n", time_alarm); + } +} + +/*! + * This function updates the RTC alarm registers and then clears all the + * interrupt status bits. + * + * @param alrm the new alarm value to be updated in the RTC + * + * @return 0 if successful; non-zero otherwise. + */ +static int rtc_update_alarm(struct device *dev, struct rtc_time *alrm) +{ + struct rtc_time alarm_tm, now_tm; + unsigned long now, time; + int ret; + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + + now = get_alarm_or_time(dev, MXC_RTC_TIME); + rtc_time_to_tm(now, &now_tm); + alarm_tm.tm_year = now_tm.tm_year; + alarm_tm.tm_mon = now_tm.tm_mon; + alarm_tm.tm_mday = now_tm.tm_mday; + alarm_tm.tm_hour = alrm->tm_hour; + alarm_tm.tm_min = alrm->tm_min; + alarm_tm.tm_sec = alrm->tm_sec; + rtc_tm_to_time(&now_tm, &now); + rtc_tm_to_time(&alarm_tm, &time); + if (time < now) { + time += 60 * 60 * 24; + rtc_time_to_tm(time, &alarm_tm); + } + ret = rtc_tm_to_time(&alarm_tm, &time); + + /* clear all the interrupt status bits */ + writew(readw(ioaddr + RTC_RTCISR), ioaddr + RTC_RTCISR); + + set_alarm_or_time(dev, MXC_RTC_ALARM, time); + + return ret; +} + +/*! + * This function is the RTC interrupt service routine. + * + * @param irq RTC IRQ number + * @param dev_id device ID which is not used + * + * @return IRQ_HANDLED as defined in the include/linux/interrupt.h file. + */ +static irqreturn_t mxc_rtc_interrupt(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + u32 status; + u32 events = 0; + spin_lock(&rtc_lock); + status = readw(ioaddr + RTC_RTCISR) & readw(ioaddr + RTC_RTCIENR); + /* clear interrupt sources */ + writew(status, ioaddr + RTC_RTCISR); + + /* clear alarm interrupt if it has occurred */ + if (status & RTC_ALM_BIT) { + status &= ~RTC_ALM_BIT; + } + + /* update irq data & counter */ + if (status & RTC_ALM_BIT) { + events |= (RTC_AF | RTC_IRQF); + } + if (status & RTC_1HZ_BIT) { + events |= (RTC_UF | RTC_IRQF); + } + if (status & PIT_ALL_ON) { + events |= (RTC_PF | RTC_IRQF); + } + + if ((status & RTC_ALM_BIT) && rtc_valid_tm(&g_rtc_alarm)) { + rtc_update_alarm(&pdev->dev, &g_rtc_alarm); + } + + spin_unlock(&rtc_lock); + rtc_update_irq(pdata->rtc, 1, events); + return IRQ_HANDLED; +} + +/*! + * This function is used to open the RTC driver by registering the RTC + * interrupt service routine. + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_open(struct device *dev) +{ + if (test_and_set_bit(1, &rtc_status)) + return -EBUSY; + return 0; +} + +/*! + * clear all interrupts and release the IRQ + */ +static void mxc_rtc_release(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + + spin_lock_irq(&rtc_lock); + writew(0, ioaddr + RTC_RTCIENR); /* Disable all rtc interrupts */ + writew(0xFFFFFFFF, ioaddr + RTC_RTCISR); /* Clear all interrupt status */ + spin_unlock_irq(&rtc_lock); + rtc_status = 0; +} + +/*! + * This function is used to support some ioctl calls directly. + * Other ioctl calls are supported indirectly through the + * arm/common/rtctime.c file. + * + * @param cmd ioctl command as defined in include/linux/rtc.h + * @param arg value for the ioctl command + * + * @return 0 if successful or negative value otherwise. + */ +static int mxc_rtc_ioctl(struct device *dev, unsigned int cmd, + unsigned long arg) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + int i; + switch (cmd) { + case RTC_PIE_OFF: + writew((readw(ioaddr + RTC_RTCIENR) & ~PIT_ALL_ON), + ioaddr + RTC_RTCIENR); + return 0; + case RTC_IRQP_SET: + if (arg < 2 || arg > MAX_PIE_FREQ || (arg % 2) != 0) + return -EINVAL; /* Also make sure a power of 2Hz */ + if ((arg > 64) && (!capable(CAP_SYS_RESOURCE))) + return -EACCES; + rtc_freq = arg; + return 0; + case RTC_IRQP_READ: + return put_user(rtc_freq, (u32 *) arg); + case RTC_PIE_ON: + for (i = 0; i < MAX_PIE_NUM; i++) { + if (PIE_BIT_DEF[i][0] == rtc_freq) { + break; + } + } + if (i == MAX_PIE_NUM) { + return -EACCES; + } + spin_lock_irq(&rtc_lock); + writew((readw(ioaddr + RTC_RTCIENR) | PIE_BIT_DEF[i][1]), + ioaddr + RTC_RTCIENR); + spin_unlock_irq(&rtc_lock); + return 0; + case RTC_AIE_OFF: + spin_lock_irq(&rtc_lock); + writew((readw(ioaddr + RTC_RTCIENR) & ~RTC_ALM_BIT), + ioaddr + RTC_RTCIENR); + spin_unlock_irq(&rtc_lock); + return 0; + + case RTC_AIE_ON: + spin_lock_irq(&rtc_lock); + writew((readw(ioaddr + RTC_RTCIENR) | RTC_ALM_BIT), + ioaddr + RTC_RTCIENR); + spin_unlock_irq(&rtc_lock); + return 0; + + case RTC_UIE_OFF: /* UIE is for the 1Hz interrupt */ + spin_lock_irq(&rtc_lock); + writew((readw(ioaddr + RTC_RTCIENR) & ~RTC_1HZ_BIT), + ioaddr + RTC_RTCIENR); + spin_unlock_irq(&rtc_lock); + return 0; + + case RTC_UIE_ON: + spin_lock_irq(&rtc_lock); + writew((readw(ioaddr + RTC_RTCIENR) | RTC_1HZ_BIT), + ioaddr + RTC_RTCIENR); + spin_unlock_irq(&rtc_lock); + return 0; + } + return -ENOIOCTLCMD; +} + +/*! + * This function reads the current RTC time into tm in Gregorian date. + * + * @param tm contains the RTC time value upon return + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + u32 val; + + /* Avoid roll-over from reading the different registers */ + do { + val = get_alarm_or_time(dev, MXC_RTC_TIME); + } while (val != get_alarm_or_time(dev, MXC_RTC_TIME)); + + rtc_time_to_tm(val, tm); + return 0; +} + +/*! + * This function sets the internal RTC time based on tm in Gregorian date. + * + * @param tm the time value to be set in the RTC + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + unsigned long time; + int ret; + ret = rtc_tm_to_time(tm, &time); + if (ret != 0) { + return ret; + } + + /* Avoid roll-over from reading the different registers */ + do { + set_alarm_or_time(dev, MXC_RTC_TIME, time); + } while (time != get_alarm_or_time(dev, MXC_RTC_TIME)); + + ret = set_ext_rtc_time(time); + + if (ret != MXC_EXTERNAL_RTC_OK) { + if (ret == MXC_EXTERNAL_RTC_NONE) { + pr_info("No external RTC\n"); + ret = 0; + } else + pr_info("Failed to set external RTC\n"); + } + + return ret; +} + +/*! + * This function reads the current alarm value into the passed in \b alrm + * argument. It updates the \b alrm's pending field value based on the whether + * an alarm interrupt occurs or not. + * + * @param alrm contains the RTC alarm value upon return + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + + rtc_time_to_tm(get_alarm_or_time(dev, MXC_RTC_ALARM), &alrm->time); + alrm->pending = + ((readw(ioaddr + RTC_RTCISR) & RTC_ALM_BIT) != 0) ? 1 : 0; + + return 0; +} + +/*! + * This function sets the RTC alarm based on passed in alrm. + * + * @param alrm the alarm value to be set in the RTC + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + int ret; + + spin_lock_irq(&rtc_lock); + if (rtc_valid_tm(&alrm->time)) { + if (alrm->time.tm_sec > 59 || + alrm->time.tm_hour > 23 || alrm->time.tm_min > 59) { + ret = -EINVAL; + goto out; + } + ret = rtc_update_alarm(dev, &alrm->time); + } else { + if ((ret = rtc_valid_tm(&alrm->time))) + goto out; + ret = rtc_update_alarm(dev, &alrm->time); + } + + if (ret == 0) { + memcpy(&g_rtc_alarm, &alrm->time, sizeof(struct rtc_time)); + + if (alrm->enabled) { + writew((readw(ioaddr + RTC_RTCIENR) | RTC_ALM_BIT), + ioaddr + RTC_RTCIENR); + } else { + writew((readw(ioaddr + RTC_RTCIENR) & ~RTC_ALM_BIT), + ioaddr + RTC_RTCIENR); + } + } + out: + spin_unlock_irq(&rtc_lock); + + return ret; +} + +/*! + * This function is used to provide the content for the /proc/driver/rtc + * file. + * + * @param buf the buffer to hold the information that the driver wants to write + * + * @return The number of bytes written into the rtc file. + */ +static int mxc_rtc_proc(struct device *dev, struct seq_file *sq) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + char *p = sq->buf; + + p += sprintf(p, "alarm_IRQ\t: %s\n", + (((readw(ioaddr + RTC_RTCIENR)) & RTC_ALM_BIT) != + 0) ? "yes" : "no"); + p += sprintf(p, "update_IRQ\t: %s\n", + (((readw(ioaddr + RTC_RTCIENR)) & RTC_1HZ_BIT) != + 0) ? "yes" : "no"); + p += sprintf(p, "periodic_IRQ\t: %s\n", + (((readw(ioaddr + RTC_RTCIENR)) & PIT_ALL_ON) != + 0) ? "yes" : "no"); + p += sprintf(p, "periodic_freq\t: %d\n", rtc_freq); + + return p - (sq->buf); +} + +/*! + * The RTC driver structure + */ +static struct rtc_class_ops mxc_rtc_ops = { + .open = mxc_rtc_open, + .release = mxc_rtc_release, + .ioctl = mxc_rtc_ioctl, + .read_time = mxc_rtc_read_time, + .set_time = mxc_rtc_set_time, + .read_alarm = mxc_rtc_read_alarm, + .set_alarm = mxc_rtc_set_alarm, + .proc = mxc_rtc_proc, +}; + +/*! MXC RTC Power management control */ + +static struct timespec mxc_rtc_delta; + +static int mxc_rtc_probe(struct platform_device *pdev) +{ + struct clk *clk; + struct timespec tv; + struct resource *res; + struct rtc_device *rtc; + struct rtc_plat_data *pdata = NULL; + u32 reg; + int ret = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->clk = clk_get(&pdev->dev, "rtc_clk"); + clk_enable(pdata->clk); + + pdata->baseaddr = res->start; + pdata->ioaddr = ((void *)(IO_ADDRESS(pdata->baseaddr))); + /* Configure and enable the RTC */ + pdata->irq = platform_get_irq(pdev, 0); + if (pdata->irq >= 0) { + if (request_irq(pdata->irq, mxc_rtc_interrupt, IRQF_SHARED, + pdev->name, pdev) < 0) { + dev_warn(&pdev->dev, "interrupt not available.\n"); + pdata->irq = -1; + } + } + rtc = + rtc_device_register(pdev->name, &pdev->dev, &mxc_rtc_ops, + THIS_MODULE); + if (IS_ERR(rtc)) { + ret = PTR_ERR(rtc); + if (pdata->irq >= 0) + free_irq(pdata->irq, pdev); + kfree(pdata); + return ret; + } + pdata->rtc = rtc; + platform_set_drvdata(pdev, pdata); + tv.tv_nsec = 0; + tv.tv_sec = get_alarm_or_time(&pdev->dev, MXC_RTC_TIME); + clk = clk_get(NULL, "ckil"); + if (clk_get_rate(clk) == 32768) + reg = RTC_INPUT_CLK_32768HZ; + else if (clk_get_rate(clk) == 32000) + reg = RTC_INPUT_CLK_32000HZ; + else if (clk_get_rate(clk) == 38400) + reg = RTC_INPUT_CLK_38400HZ; + else { + printk(KERN_ALERT "rtc clock is not valid"); + return -EINVAL; + } + clk_put(clk); + reg |= RTC_ENABLE_BIT; + writew(reg, (pdata->ioaddr + RTC_RTCCTL)); + if (((readw(pdata->ioaddr + RTC_RTCCTL)) & RTC_ENABLE_BIT) == 0) { + printk(KERN_ALERT "rtc : hardware module can't be enabled!\n"); + return -EPERM; + } + printk("Real TIme clock Driver v%s \n", RTC_VERSION); + return ret; +} + +static int __exit mxc_rtc_remove(struct platform_device *pdev) +{ + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + rtc_device_unregister(pdata->rtc); + if (pdata->irq >= 0) { + free_irq(pdata->irq, pdev); + } + clk_disable(pdata->clk); + clk_put(pdata->clk); + kfree(pdata); + mxc_rtc_release(NULL); + return 0; +} + +/*! + * This function is called to save the system time delta relative to + * the MXC RTC when enterring a low power state. This time delta is + * then used on resume to adjust the system time to account for time + * loss while suspended. + * + * @param pdev not used + * @param state Power state to enter. + * + * @return The function always returns 0. + */ +static int mxc_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct timespec tv; + + /* calculate time delta for suspend */ + /* RTC precision is 1 second; adjust delta for avg 1/2 sec err */ + tv.tv_nsec = NSEC_PER_SEC >> 1; + tv.tv_sec = get_alarm_or_time(&pdev->dev, MXC_RTC_TIME); + set_normalized_timespec(&mxc_rtc_delta, + xtime.tv_sec - tv.tv_sec, + xtime.tv_nsec - tv.tv_nsec); + + return 0; +} + +/*! + * This function is called to correct the system time based on the + * current MXC RTC time relative to the time delta saved during + * suspend. + * + * @param pdev not used + * + * @return The function always returns 0. + */ +static int mxc_rtc_resume(struct platform_device *pdev) +{ + struct timespec tv; + struct timespec ts; + + tv.tv_nsec = 0; + tv.tv_sec = get_alarm_or_time(&pdev->dev, MXC_RTC_TIME); + + /* restore wall clock using delta against this RTC; + * adjust again for avg 1/2 second RTC sampling error + */ + set_normalized_timespec(&ts, + tv.tv_sec + mxc_rtc_delta.tv_sec, + (NSEC_PER_SEC >> 1) + mxc_rtc_delta.tv_nsec); + do_settimeofday(&ts); + + return 0; +} + +/*! + * Contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_rtc_driver = { + .driver = { + .name = "mxc_rtc", + }, + .probe = mxc_rtc_probe, + .remove = __exit_p(mxc_rtc_remove), + .suspend = mxc_rtc_suspend, + .resume = mxc_rtc_resume, +}; + +/*! + * This function creates the /proc/driver/rtc file and registers the device RTC + * in the /dev/misc directory. It also reads the RTC value from external source + * and setup the internal RTC properly. + * + * @return -1 if RTC is failed to initialize; 0 is successful. + */ +static int __init mxc_rtc_init(void) +{ + return platform_driver_register(&mxc_rtc_driver); +} + +/*! + * This function removes the /proc/driver/rtc file and un-registers the + * device RTC from the /dev/misc directory. + */ +static void __exit mxc_rtc_exit(void) +{ + platform_driver_unregister(&mxc_rtc_driver); + +} + +device_initcall_sync(mxc_rtc_init); +module_exit(mxc_rtc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-mxc_v2.c b/drivers/rtc/rtc-mxc_v2.c new file mode 100644 index 000000000000..1b247fb2510a --- /dev/null +++ b/drivers/rtc/rtc-mxc_v2.c @@ -0,0 +1,765 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * Implementation based on rtc-ds1553.c + */ + +/*! + * @defgroup RTC Real Time Clock (RTC) Driver + */ +/*! + * @file rtc-mxc_v2.c + * @brief Real Time Clock interface + * + * This file contains Real Time Clock interface for Linux. + * + * @ingroup RTC + */ + +#include <linux/delay.h> +#include <linux/rtc.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/uaccess.h> +#include <mach/hardware.h> +#include <asm/io.h> + +#define SRTC_LPPDR_INIT 0x41736166 /* init for glitch detect */ + +#define SRTC_LPCR_SWR_LP (1 << 0) /* lp software reset */ +#define SRTC_LPCR_EN_LP (1 << 3) /* lp enable */ +#define SRTC_LPCR_WAE (1 << 4) /* lp wakeup alarm enable */ +#define SRTC_LPCR_SAE (1 << 5) /* lp security alarm enable */ +#define SRTC_LPCR_SI (1 << 6) /* lp security interrupt enable */ +#define SRTC_LPCR_ALP (1 << 7) /* lp alarm flag */ +#define SRTC_LPCR_LTC (1 << 8) /* lp lock time counter */ +#define SRTC_LPCR_LMC (1 << 9) /* lp lock monotonic counter */ +#define SRTC_LPCR_SV (1 << 10) /* lp security violation */ +#define SRTC_LPCR_NSA (1 << 11) /* lp non secure access */ +#define SRTC_LPCR_NVEIE (1 << 12) /* lp non valid state exit int en */ +#define SRTC_LPCR_IEIE (1 << 13) /* lp init state exit int enable */ +#define SRTC_LPCR_NVE (1 << 14) /* lp non valid state exit bit */ +#define SRTC_LPCR_IE (1 << 15) /* lp init state exit bit */ + +#define SRTC_LPCR_ALL_INT_EN (SRTC_LPCR_WAE | SRTC_LPCR_SAE | \ + SRTC_LPCR_SI | SRTC_LPCR_ALP | \ + SRTC_LPCR_NVEIE | SRTC_LPCR_IEIE) + +#define SRTC_LPSR_TRI (1 << 0) /* lp time read invalidate */ +#define SRTC_LPSR_PGD (1 << 1) /* lp power supply glitc detected */ +#define SRTC_LPSR_CTD (1 << 2) /* lp clock tampering detected */ +#define SRTC_LPSR_ALP (1 << 3) /* lp alarm flag */ +#define SRTC_LPSR_MR (1 << 4) /* lp monotonic counter rollover */ +#define SRTC_LPSR_TR (1 << 5) /* lp time rollover */ +#define SRTC_LPSR_EAD (1 << 6) /* lp external alarm detected */ +#define SRTC_LPSR_IT0 (1 << 7) /* lp IIM throttle */ +#define SRTC_LPSR_IT1 (1 << 8) +#define SRTC_LPSR_IT2 (1 << 9) +#define SRTC_LPSR_SM0 (1 << 10) /* lp security mode */ +#define SRTC_LPSR_SM1 (1 << 11) +#define SRTC_LPSR_STATE_LP0 (1 << 12) /* lp state */ +#define SRTC_LPSR_STATE_LP1 (1 << 13) +#define SRTC_LPSR_NVES (1 << 14) /* lp non-valid state exit status */ +#define SRTC_LPSR_IES (1 << 15) /* lp init state exit status */ + +#define MAX_PIE_NUM 15 +#define MAX_PIE_FREQ 32768 +#define MIN_PIE_FREQ 1 + +#define SRTC_PI0 (1 << 0) +#define SRTC_PI1 (1 << 1) +#define SRTC_PI2 (1 << 2) +#define SRTC_PI3 (1 << 3) +#define SRTC_PI4 (1 << 4) +#define SRTC_PI5 (1 << 5) +#define SRTC_PI6 (1 << 6) +#define SRTC_PI7 (1 << 7) +#define SRTC_PI8 (1 << 8) +#define SRTC_PI9 (1 << 9) +#define SRTC_PI10 (1 << 10) +#define SRTC_PI11 (1 << 11) +#define SRTC_PI12 (1 << 12) +#define SRTC_PI13 (1 << 13) +#define SRTC_PI14 (1 << 14) +#define SRTC_PI15 (1 << 15) + +#define PIT_ALL_ON (SRTC_PI1 | SRTC_PI2 | SRTC_PI3 | \ + SRTC_PI4 | SRTC_PI5 | SRTC_PI6 | SRTC_PI7 | \ + SRTC_PI8 | SRTC_PI9 | SRTC_PI10 | SRTC_PI11 | \ + SRTC_PI12 | SRTC_PI13 | SRTC_PI14 | SRTC_PI15) + +#define SRTC_SWR_HP (1 << 0) /* hp software reset */ +#define SRTC_EN_HP (1 << 3) /* hp enable */ +#define SRTC_TS (1 << 4) /* time syncronize hp with lp */ + +#define SRTC_IE_AHP (1 << 16) /* Alarm HP Interrupt Enable bit */ +#define SRTC_IE_WDHP (1 << 18) /* Write Done HP Interrupt Enable bit */ +#define SRTC_IE_WDLP (1 << 19) /* Write Done LP Interrupt Enable bit */ + +#define SRTC_ISR_AHP (1 << 16) /* interrupt status: alarm hp */ +#define SRTC_ISR_WDHP (1 << 18) /* interrupt status: write done hp */ +#define SRTC_ISR_WDLP (1 << 19) /* interrupt status: write done lp */ +#define SRTC_ISR_WPHP (1 << 20) /* interrupt status: write pending hp */ +#define SRTC_ISR_WPLP (1 << 21) /* interrupt status: write pending lp */ + +#define SRTC_LPSCMR 0x00 /* LP Secure Counter MSB Reg */ +#define SRTC_LPSCLR 0x04 /* LP Secure Counter LSB Reg */ +#define SRTC_LPSAR 0x08 /* LP Secure Alarm Reg */ +#define SRTC_LPSMCR 0x0C /* LP Secure Monotonic Counter Reg */ +#define SRTC_LPCR 0x10 /* LP Control Reg */ +#define SRTC_LPSR 0x14 /* LP Status Reg */ +#define SRTC_LPPDR 0x18 /* LP Power Supply Glitch Detector Reg */ +#define SRTC_LPGR 0x1C /* LP General Purpose Reg */ +#define SRTC_HPCMR 0x20 /* HP Counter MSB Reg */ +#define SRTC_HPCLR 0x24 /* HP Counter LSB Reg */ +#define SRTC_HPAMR 0x28 /* HP Alarm MSB Reg */ +#define SRTC_HPALR 0x2C /* HP Alarm LSB Reg */ +#define SRTC_HPCR 0x30 /* HP Control Reg */ +#define SRTC_HPISR 0x34 /* HP Interrupt Status Reg */ +#define SRTC_HPIENR 0x38 /* HP Interrupt Enable Reg */ + +#define SRTC_SECMODE_MASK 0x3 /* the mask of SRTC security mode */ +#define SRTC_SECMODE_LOW 0x0 /* Low Security */ +#define SRTC_SECMODE_MED 0x1 /* Medium Security */ +#define SRTC_SECMODE_HIGH 0x2 /* High Security */ +#define SRTC_SECMODE_RESERVED 0x3 /* Reserved */ + +struct rtc_drv_data { + struct rtc_device *rtc; + void __iomem *ioaddr; + unsigned long baseaddr; + int irq; + struct clk *clk; + bool irq_enable; +}; + +/*! + * @defgroup RTC Real Time Clock (RTC) Driver + */ +/*! + * @file rtc-mxc.c + * @brief Real Time Clock interface + * + * This file contains Real Time Clock interface for Linux. + * + * @ingroup RTC + */ + +static unsigned long rtc_status; + +static DEFINE_SPINLOCK(rtc_lock); + +/*! + * This function does write synchronization for writes to the lp srtc block. + * To take care of the asynchronous CKIL clock, all writes from the IP domain + * will be synchronized to the CKIL domain. + */ +static inline void rtc_write_sync_lp(void __iomem *ioaddr) +{ + unsigned int i, count; + /* Wait for 3 CKIL cycles */ + for (i = 0; i < 3; i++) { + count = __raw_readl(ioaddr + SRTC_LPSCLR); + while + ((__raw_readl(ioaddr + SRTC_LPSCLR)) == count); + } +} + +/*! + * This function updates the RTC alarm registers and then clears all the + * interrupt status bits. + * + * @param alrm the new alarm value to be updated in the RTC + * + * @return 0 if successful; non-zero otherwise. + */ +static int rtc_update_alarm(struct device *dev, struct rtc_time *alrm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + struct rtc_time alarm_tm, now_tm; + unsigned long now, time; + int ret; + + now = __raw_readl(ioaddr + SRTC_LPSCMR); + rtc_time_to_tm(now, &now_tm); + + alarm_tm.tm_year = now_tm.tm_year; + alarm_tm.tm_mon = now_tm.tm_mon; + alarm_tm.tm_mday = now_tm.tm_mday; + + alarm_tm.tm_hour = alrm->tm_hour; + alarm_tm.tm_min = alrm->tm_min; + alarm_tm.tm_sec = alrm->tm_sec; + + rtc_tm_to_time(&now_tm, &now); + rtc_tm_to_time(&alarm_tm, &time); + + if (time < now) { + time += 60 * 60 * 24; + rtc_time_to_tm(time, &alarm_tm); + } + ret = rtc_tm_to_time(&alarm_tm, &time); + + __raw_writel(time, ioaddr + SRTC_LPSAR); + + /* clear alarm interrupt status bit */ + __raw_writel(SRTC_LPSR_ALP, ioaddr + SRTC_LPSR); + + return ret; +} + +/*! + * This function is the RTC interrupt service routine. + * + * @param irq RTC IRQ number + * @param dev_id device ID which is not used + * + * @return IRQ_HANDLED as defined in the include/linux/interrupt.h file. + */ +static irqreturn_t mxc_rtc_interrupt(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct rtc_drv_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + u32 lp_status, lp_cr; + u32 events = 0; + + lp_status = __raw_readl(ioaddr + SRTC_LPSR); + lp_cr = __raw_readl(ioaddr + SRTC_LPCR); + + /* update irq data & counter */ + if (lp_status & SRTC_LPSR_ALP) { + if (lp_cr & SRTC_LPCR_ALP) + events |= (RTC_AF | RTC_IRQF); + + /* disable further lp alarm interrupts */ + lp_cr &= ~(SRTC_LPCR_ALP | SRTC_LPCR_WAE); + } + + /* Update interrupt enables */ + __raw_writel(lp_cr, ioaddr + SRTC_LPCR); + + /* If no interrupts are enabled, turn off interrupts in kernel */ + if (((lp_cr & SRTC_LPCR_ALL_INT_EN) == 0) && (pdata->irq_enable)) { + disable_irq(pdata->irq); + pdata->irq_enable = false; + } + + /* clear interrupt status */ + __raw_writel(lp_status, ioaddr + SRTC_LPSR); + + rtc_update_irq(pdata->rtc, 1, events); + return IRQ_HANDLED; +} + +/*! + * This function is used to open the RTC driver. + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_open(struct device *dev) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + clk_enable(pdata->clk); + + if (test_and_set_bit(1, &rtc_status)) + return -EBUSY; + return 0; +} + +/*! + * clear all interrupts and release the IRQ + */ +static void mxc_rtc_release(struct device *dev) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + unsigned long lock_flags = 0; + + spin_lock_irqsave(&rtc_lock, lock_flags); + + /* Disable all rtc interrupts */ + __raw_writel(__raw_readl(ioaddr + SRTC_LPCR) & ~(SRTC_LPCR_ALL_INT_EN), + ioaddr + SRTC_LPCR); + + /* Clear all interrupt status */ + __raw_writel(0xFFFFFFFF, ioaddr + SRTC_LPSR); + + spin_unlock_irqrestore(&rtc_lock, lock_flags); + + clk_disable(pdata->clk); + + rtc_status = 0; +} + +/*! + * This function is used to support some ioctl calls directly. + * Other ioctl calls are supported indirectly through the + * arm/common/rtctime.c file. + * + * @param cmd ioctl command as defined in include/linux/rtc.h + * @param arg value for the ioctl command + * + * @return 0 if successful or negative value otherwise. + */ +static int mxc_rtc_ioctl(struct device *dev, unsigned int cmd, + unsigned long arg) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + unsigned long lock_flags = 0; + u32 lp_cr; + + switch (cmd) { + case RTC_AIE_OFF: + spin_lock_irqsave(&rtc_lock, lock_flags); + lp_cr = __raw_readl(ioaddr + SRTC_LPCR); + lp_cr &= ~(SRTC_LPCR_ALP | SRTC_LPCR_WAE); + if (((lp_cr & SRTC_LPCR_ALL_INT_EN) == 0) + && (pdata->irq_enable)) { + disable_irq(pdata->irq); + pdata->irq_enable = false; + } + __raw_writel(lp_cr, ioaddr + SRTC_LPCR); + spin_unlock_irqrestore(&rtc_lock, lock_flags); + return 0; + + case RTC_AIE_ON: + spin_lock_irqsave(&rtc_lock, lock_flags); + if (!pdata->irq_enable) { + enable_irq(pdata->irq); + pdata->irq_enable = true; + } + lp_cr = __raw_readl(ioaddr + SRTC_LPCR); + lp_cr |= SRTC_LPCR_ALP | SRTC_LPCR_WAE; + __raw_writel(lp_cr, ioaddr + SRTC_LPCR); + spin_unlock_irqrestore(&rtc_lock, lock_flags); + return 0; + } + + return -ENOIOCTLCMD; +} + +/*! + * This function reads the current RTC time into tm in Gregorian date. + * + * @param tm contains the RTC time value upon return + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + + rtc_time_to_tm(__raw_readl(ioaddr + SRTC_LPSCMR), tm); + return 0; +} + +/*! + * This function sets the internal RTC time based on tm in Gregorian date. + * + * @param tm the time value to be set in the RTC + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + unsigned long time; + int ret; + ret = rtc_tm_to_time(tm, &time); + if (ret != 0) + return ret; + + __raw_writel(time, ioaddr + SRTC_LPSCMR); + rtc_write_sync_lp(ioaddr); + + return 0; +} + +/*! + * This function reads the current alarm value into the passed in \b alrm + * argument. It updates the \b alrm's pending field value based on the whether + * an alarm interrupt occurs or not. + * + * @param alrm contains the RTC alarm value upon return + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + + rtc_time_to_tm(__raw_readl(ioaddr + SRTC_LPSAR), &alrm->time); + alrm->pending = + ((__raw_readl(ioaddr + SRTC_LPSR) & SRTC_LPSR_ALP) != 0) ? 1 : 0; + + return 0; +} + +/*! + * This function sets the RTC alarm based on passed in alrm. + * + * @param alrm the alarm value to be set in the RTC + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + unsigned long lock_flags = 0; + u32 lp_cr; + int ret; + + if (rtc_valid_tm(&alrm->time)) { + if (alrm->time.tm_sec > 59 || + alrm->time.tm_hour > 23 || alrm->time.tm_min > 59) { + return -EINVAL; + } + } + + spin_lock_irqsave(&rtc_lock, lock_flags); + lp_cr = __raw_readl(ioaddr + SRTC_LPCR); + + ret = rtc_update_alarm(dev, &alrm->time); + if (ret) + goto out; + + if (alrm->enabled) + lp_cr |= (SRTC_LPCR_ALP | SRTC_LPCR_WAE); + else + lp_cr &= ~(SRTC_LPCR_ALP | SRTC_LPCR_WAE); + + if (lp_cr & SRTC_LPCR_ALL_INT_EN) { + if (!pdata->irq_enable) { + enable_irq(pdata->irq); + pdata->irq_enable = true; + } + } else { + if (pdata->irq_enable) { + disable_irq(pdata->irq); + pdata->irq_enable = false; + } + } + + __raw_writel(lp_cr, ioaddr + SRTC_LPCR); + +out: + spin_unlock_irqrestore(&rtc_lock, lock_flags); + rtc_write_sync_lp(ioaddr); + return ret; +} + +/*! + * This function is used to provide the content for the /proc/driver/rtc + * file. + * + * @param seq buffer to hold the information that the driver wants to write + * + * @return The number of bytes written into the rtc file. + */ +static int mxc_rtc_proc(struct device *dev, struct seq_file *seq) +{ + struct rtc_drv_data *pdata = dev_get_drvdata(dev); + void __iomem *ioaddr = pdata->ioaddr; + + clk_enable(pdata->clk); + seq_printf(seq, "alarm_IRQ\t: %s\n", + (((__raw_readl(ioaddr + SRTC_LPCR)) & SRTC_LPCR_ALP) != + 0) ? "yes" : "no"); + clk_disable(pdata->clk); + + return 0; +} + +/*! + * The RTC driver structure + */ +static struct rtc_class_ops mxc_rtc_ops = { + .open = mxc_rtc_open, + .release = mxc_rtc_release, + .ioctl = mxc_rtc_ioctl, + .read_time = mxc_rtc_read_time, + .set_time = mxc_rtc_set_time, + .read_alarm = mxc_rtc_read_alarm, + .set_alarm = mxc_rtc_set_alarm, + .proc = mxc_rtc_proc, +}; + +/*! MXC RTC Power management control */ + +static struct timespec mxc_rtc_delta; + +static int mxc_rtc_probe(struct platform_device *pdev) +{ + struct clk *clk; + struct timespec tv; + struct resource *res; + struct rtc_device *rtc; + struct rtc_drv_data *pdata = NULL; + struct mxc_srtc_platform_data *plat_data = NULL; + void __iomem *ioaddr; + void __iomem *srtc_secmode_addr; + int ret = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->clk = clk_get(&pdev->dev, "rtc_clk"); + clk_enable(pdata->clk); + pdata->baseaddr = res->start; + pdata->ioaddr = ioremap(pdata->baseaddr, 0x40); + ioaddr = pdata->ioaddr; + + /* Configure and enable the RTC */ + pdata->irq = platform_get_irq(pdev, 0); + if (pdata->irq >= 0) { + if (request_irq(pdata->irq, mxc_rtc_interrupt, IRQF_SHARED, + pdev->name, pdev) < 0) { + dev_warn(&pdev->dev, "interrupt not available.\n"); + pdata->irq = -1; + } else { + disable_irq(pdata->irq); + pdata->irq_enable = false; + } + } + + clk = clk_get(NULL, "rtc_clk"); + if (clk_get_rate(clk) != 32768) { + printk(KERN_ALERT "rtc clock is not valid"); + ret = -EINVAL; + clk_put(clk); + goto err_out; + } + clk_put(clk); + + /* initialize glitch detect */ + __raw_writel(SRTC_LPPDR_INIT, ioaddr + SRTC_LPPDR); + udelay(100); + + /* clear lp interrupt status */ + __raw_writel(0xFFFFFFFF, ioaddr + SRTC_LPSR); + udelay(100);; + + plat_data = (struct mxc_srtc_platform_data *)pdev->dev.platform_data; + clk = clk_get(NULL, "iim_clk"); + clk_enable(clk); + srtc_secmode_addr = ioremap(plat_data->srtc_sec_mode_addr, 1); + + /* Check SRTC security mode */ + if (((__raw_readl(srtc_secmode_addr) & SRTC_SECMODE_MASK) == + SRTC_SECMODE_LOW) && (cpu_is_mx51_rev(CHIP_REV_1_0) == 1)) { + /* Workaround for MX51 TO1 due to inaccurate CKIL clock */ + __raw_writel(SRTC_LPCR_EN_LP, ioaddr + SRTC_LPCR); + udelay(100); + } else { + /* move out of init state */ + __raw_writel((SRTC_LPCR_IE | SRTC_LPCR_NSA), + ioaddr + SRTC_LPCR); + + udelay(100); + + while ((__raw_readl(ioaddr + SRTC_LPSR) & SRTC_LPSR_IES) == 0); + + /* move out of non-valid state */ + __raw_writel((SRTC_LPCR_IE | SRTC_LPCR_NVE | SRTC_LPCR_NSA | + SRTC_LPCR_EN_LP), ioaddr + SRTC_LPCR); + + udelay(100); + + while ((__raw_readl(ioaddr + SRTC_LPSR) & SRTC_LPSR_NVES) == 0); + + __raw_writel(0xFFFFFFFF, ioaddr + SRTC_LPSR); + udelay(100); + } + clk_disable(clk); + clk_put(clk); + + rtc = rtc_device_register(pdev->name, &pdev->dev, + &mxc_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc)) { + ret = PTR_ERR(rtc); + goto err_out; + } + + pdata->rtc = rtc; + platform_set_drvdata(pdev, pdata); + + tv.tv_nsec = 0; + tv.tv_sec = __raw_readl(ioaddr + SRTC_LPSCMR); + + /* By default, devices should wakeup if they can */ + /* So srtc is set as "should wakeup" as it can */ + device_init_wakeup(&pdev->dev, 1); + + clk_disable(pdata->clk); + + return ret; + +err_out: + clk_disable(pdata->clk); + iounmap(ioaddr); + if (pdata->irq >= 0) + free_irq(pdata->irq, pdev); + kfree(pdata); + return ret; +} + +static int __exit mxc_rtc_remove(struct platform_device *pdev) +{ + struct rtc_drv_data *pdata = platform_get_drvdata(pdev); + rtc_device_unregister(pdata->rtc); + if (pdata->irq >= 0) + free_irq(pdata->irq, pdev); + + clk_disable(pdata->clk); + clk_put(pdata->clk); + kfree(pdata); + return 0; +} + +/*! + * This function is called to save the system time delta relative to + * the MXC RTC when enterring a low power state. This time delta is + * then used on resume to adjust the system time to account for time + * loss while suspended. + * + * @param pdev not used + * @param state Power state to enter. + * + * @return The function always returns 0. + */ +static int mxc_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct rtc_drv_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + struct timespec tv; + + clk_enable(pdata->clk); + if (device_may_wakeup(&pdev->dev)) { + enable_irq_wake(pdata->irq); + } else { + /* calculate time delta for suspend. RTC precision is + 1 second; adjust delta for avg 1/2 sec err */ + tv.tv_nsec = NSEC_PER_SEC >> 1; + tv.tv_sec = __raw_readl(ioaddr + SRTC_LPSCMR); + set_normalized_timespec(&mxc_rtc_delta, + xtime.tv_sec - tv.tv_sec, + xtime.tv_nsec - tv.tv_nsec); + + if (pdata->irq_enable) + disable_irq(pdata->irq); + } + + clk_disable(pdata->clk); + + return 0; +} + +/*! + * This function is called to correct the system time based on the + * current MXC RTC time relative to the time delta saved during + * suspend. + * + * @param pdev not used + * + * @return The function always returns 0. + */ +static int mxc_rtc_resume(struct platform_device *pdev) +{ + struct rtc_drv_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + struct timespec tv; + struct timespec ts; + + clk_enable(pdata->clk); + + if (device_may_wakeup(&pdev->dev)) { + disable_irq_wake(pdata->irq); + } else { + if (pdata->irq_enable) + enable_irq(pdata->irq); + + tv.tv_nsec = 0; + tv.tv_sec = __raw_readl(ioaddr + SRTC_LPSCMR); + + /* + * restore wall clock using delta against this RTC; + * adjust again for avg 1/2 second RTC sampling error + */ + set_normalized_timespec(&ts, + tv.tv_sec + mxc_rtc_delta.tv_sec, + (NSEC_PER_SEC >> 1) + + mxc_rtc_delta.tv_nsec); + do_settimeofday(&ts); + } + + clk_disable(pdata->clk); + return 0; +} + +/*! + * Contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_rtc_driver = { + .driver = { + .name = "mxc_rtc", + }, + .probe = mxc_rtc_probe, + .remove = __exit_p(mxc_rtc_remove), + .suspend = mxc_rtc_suspend, + .resume = mxc_rtc_resume, +}; + +/*! + * This function creates the /proc/driver/rtc file and registers the device RTC + * in the /dev/misc directory. It also reads the RTC value from external source + * and setup the internal RTC properly. + * + * @return -1 if RTC is failed to initialize; 0 is successful. + */ +static int __init mxc_rtc_init(void) +{ + return platform_driver_register(&mxc_rtc_driver); +} + +/*! + * This function removes the /proc/driver/rtc file and un-registers the + * device RTC from the /dev/misc directory. + */ +static void __exit mxc_rtc_exit(void) +{ + platform_driver_unregister(&mxc_rtc_driver); + +} + +module_init(mxc_rtc_init); +module_exit(mxc_rtc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-stmp3xxx.c b/drivers/rtc/rtc-stmp3xxx.c new file mode 100644 index 000000000000..ab39a0bc4e9a --- /dev/null +++ b/drivers/rtc/rtc-stmp3xxx.c @@ -0,0 +1,278 @@ +/* + * Freescale STMP37XX/STMP378X Real Time Clock driver + * + * Copyright (c) 2007 Sigmatel, Inc. + * Peter Hartley, <peter.hartley@sigmatel.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/rtc.h> +#include <linux/bcd.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/uaccess.h> + +#include <mach/stmp3xxx.h> +#include <mach/hardware.h> +#include <mach/platform.h> +#include <mach/irqs.h> +#include <mach/regs-rtc.h> + +struct stmp3xxx_rtc_data { + struct rtc_device *rtc; + unsigned irq_count; +}; + +/* Time read/write */ +static int stmp3xxx_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) +{ + while (__raw_readl(REGS_RTC_BASE + HW_RTC_STAT) & BF(0x80, RTC_STAT_STALE_REGS)) + cpu_relax(); + + rtc_time_to_tm(__raw_readl(REGS_RTC_BASE + HW_RTC_SECONDS), rtc_tm); + return 0; +} + +static int stmp3xxx_rtc_settime(struct device *dev, struct rtc_time *rtc_tm) +{ + unsigned long t; + int rc = rtc_tm_to_time(rtc_tm, &t); + + if (rc == 0) { + __raw_writel(t, REGS_RTC_BASE + HW_RTC_SECONDS); + + /* The datasheet doesn't say which way round the + * NEW_REGS/STALE_REGS bitfields go. In fact it's 0x1=P0, + * 0x2=P1, .., 0x20=P5, 0x40=ALARM, 0x80=SECONDS, + */ + while (__raw_readl(REGS_RTC_BASE + HW_RTC_STAT) & BF(0x80, RTC_STAT_NEW_REGS)) + cpu_relax(); + } + return rc; +} + +static irqreturn_t stmp3xxx_rtc_interrupt(int irq, void *dev_id) +{ + struct platform_device *pdev = to_platform_device(dev_id); + struct stmp3xxx_rtc_data *data = platform_get_drvdata(pdev); + u32 status; + u32 events = 0; + + status = __raw_readl(REGS_RTC_BASE + HW_RTC_CTRL) & + (BM_RTC_CTRL_ALARM_IRQ | BM_RTC_CTRL_ONEMSEC_IRQ); + if (status & BM_RTC_CTRL_ALARM_IRQ) { + stmp3xxx_clearl(BM_RTC_CTRL_ALARM_IRQ, REGS_RTC_BASE + HW_RTC_CTRL); + events |= RTC_AF | RTC_IRQF; + } + if (status & BM_RTC_CTRL_ONEMSEC_IRQ) { + stmp3xxx_clearl(BM_RTC_CTRL_ONEMSEC_IRQ, REGS_RTC_BASE + HW_RTC_CTRL); + if (++data->irq_count % 1000 == 0) { + events |= RTC_UF | RTC_IRQF; + data->irq_count = 0; + } + } + + if (events) + rtc_update_irq(data->rtc, 1, events); + + return IRQ_HANDLED; +} + +static int stmp3xxx_rtc_open(struct device *dev) +{ + int r; + + r = request_irq(IRQ_RTC_ALARM, stmp3xxx_rtc_interrupt, + IRQF_DISABLED, "RTC alarm", dev); + if (r) { + dev_err(dev, "Cannot claim IRQ%d\n", IRQ_RTC_ALARM); + goto fail_1; + } + r = request_irq(IRQ_RTC_1MSEC, stmp3xxx_rtc_interrupt, + IRQF_DISABLED, "RTC tick", dev); + if (r) { + dev_err(dev, "Cannot claim IRQ%d\n", IRQ_RTC_1MSEC); + goto fail_2; + } + + return 0; +fail_2: + free_irq(IRQ_RTC_ALARM, dev); +fail_1: + return r; +} + +static void stmp3xxx_rtc_release(struct device *dev) +{ + stmp3xxx_clearl(BM_RTC_CTRL_ALARM_IRQ_EN | BM_RTC_CTRL_ONEMSEC_IRQ_EN, REGS_RTC_BASE + HW_RTC_CTRL); + free_irq(IRQ_RTC_ALARM, dev); + free_irq(IRQ_RTC_1MSEC, dev); +} + +static int stmp3xxx_rtc_ioctl(struct device *dev, unsigned int cmd, + unsigned long arg) +{ + struct stmp3xxx_rtc_data *data = dev_get_drvdata(dev); + + switch (cmd) { + case RTC_AIE_OFF: + stmp3xxx_clearl(BM_RTC_PERSISTENT0_ALARM_EN | + BM_RTC_PERSISTENT0_ALARM_WAKE_EN, REGS_RTC_BASE + HW_RTC_PERSISTENT0); + stmp3xxx_clearl(BM_RTC_CTRL_ALARM_IRQ_EN, REGS_RTC_BASE + HW_RTC_CTRL); + break; + case RTC_AIE_ON: + stmp3xxx_setl(BM_RTC_PERSISTENT0_ALARM_EN | + BM_RTC_PERSISTENT0_ALARM_WAKE_EN, REGS_RTC_BASE + HW_RTC_PERSISTENT0); + stmp3xxx_setl(BM_RTC_CTRL_ALARM_IRQ_EN, REGS_RTC_BASE + HW_RTC_CTRL); + break; + case RTC_UIE_ON: + data->irq_count = 0; + stmp3xxx_setl(BM_RTC_CTRL_ONEMSEC_IRQ_EN, REGS_RTC_BASE + HW_RTC_CTRL); + break; + case RTC_UIE_OFF: + stmp3xxx_clearl(BM_RTC_CTRL_ONEMSEC_IRQ_EN, REGS_RTC_BASE + HW_RTC_CTRL); + break; + default: + return -ENOIOCTLCMD; + } + + return 0; +} +static int stmp3xxx_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) +{ + u32 t = __raw_readl(REGS_RTC_BASE + HW_RTC_ALARM); + + rtc_time_to_tm(t, &alm->time); + + return 0; +} + +static int stmp3xxx_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) +{ + unsigned long t; + + rtc_tm_to_time(&alm->time, &t); + __raw_writel(t, REGS_RTC_BASE + HW_RTC_ALARM); + return 0; +} + +static struct rtc_class_ops stmp3xxx_rtc_ops = { + .open = stmp3xxx_rtc_open, + .release = stmp3xxx_rtc_release, + .ioctl = stmp3xxx_rtc_ioctl, + .read_time = stmp3xxx_rtc_gettime, + .set_time = stmp3xxx_rtc_settime, + .read_alarm = stmp3xxx_rtc_read_alarm, + .set_alarm = stmp3xxx_rtc_set_alarm, +}; + +static int stmp3xxx_rtc_remove(struct platform_device *dev) +{ + struct stmp3xxx_rtc_data *rtc_data = platform_get_drvdata(dev); + + if (rtc_data) { + rtc_device_unregister(rtc_data->rtc); + kfree(rtc_data); + } + + return 0; +} + +static int stmp3xxx_rtc_probe(struct platform_device *pdev) +{ + u32 hwversion = __raw_readl(REGS_RTC_BASE + HW_RTC_VERSION); + u32 rtc_stat = __raw_readl(REGS_RTC_BASE + HW_RTC_STAT); + struct stmp3xxx_rtc_data *rtc_data = kzalloc(sizeof *rtc_data, + GFP_KERNEL); + + if ((rtc_stat & BM_RTC_STAT_RTC_PRESENT) == 0) + return -ENODEV; + if (!rtc_data) + return -ENOMEM; + + stmp3xxx_reset_block(REGS_RTC_BASE, 1); + + stmp3xxx_clearl(BM_RTC_PERSISTENT0_ALARM_EN | + BM_RTC_PERSISTENT0_ALARM_WAKE_EN | + BM_RTC_PERSISTENT0_ALARM_WAKE, REGS_RTC_BASE + HW_RTC_PERSISTENT0); + + printk(KERN_INFO "STMP3xxx RTC driver v1.0 hardware v%u.%u.%u\n", + (hwversion >> 24), + (hwversion >> 16) & 0xFF, + hwversion & 0xFFFF); + + rtc_data->rtc = rtc_device_register(pdev->name, &pdev->dev, + &stmp3xxx_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc_data->rtc)) { + kfree(rtc_data); + return PTR_ERR(rtc_data->rtc); + } + + platform_set_drvdata(pdev, rtc_data); + + return 0; +} + +#ifdef CONFIG_PM +static int stmp3xxx_rtc_suspend(struct platform_device *dev, pm_message_t state) +{ + return 0; +} + +static int stmp3xxx_rtc_resume(struct platform_device *dev) +{ + stmp3xxx_reset_block(REGS_RTC_BASE, 1); + stmp3xxx_clearl(BM_RTC_PERSISTENT0_ALARM_EN | + BM_RTC_PERSISTENT0_ALARM_WAKE_EN | + BM_RTC_PERSISTENT0_ALARM_WAKE, REGS_RTC_BASE + HW_RTC_PERSISTENT0); + return 0; +} +#else +#define stmp3xxx_rtc_suspend NULL +#define stmp3xxx_rtc_resume NULL +#endif + +static struct platform_driver stmp3xxx_rtcdrv = { + .probe = stmp3xxx_rtc_probe, + .remove = stmp3xxx_rtc_remove, + .suspend = stmp3xxx_rtc_suspend, + .resume = stmp3xxx_rtc_resume, + .driver = { + .name = "stmp3xxx-rtc", + .owner = THIS_MODULE, + }, +}; + +static int __init stmp3xxx_rtc_init(void) +{ + return platform_driver_register(&stmp3xxx_rtcdrv); +} + +static void __exit stmp3xxx_rtc_exit(void) +{ + platform_driver_unregister(&stmp3xxx_rtcdrv); +} + +module_init(stmp3xxx_rtc_init); +module_exit(stmp3xxx_rtc_exit); + +MODULE_DESCRIPTION("STMP3xxx RTC Driver"); +MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/serial/8250.c b/drivers/serial/8250.c index fb867a9f55e9..d5da26f34cee 100644 --- a/drivers/serial/8250.c +++ b/drivers/serial/8250.c @@ -942,6 +942,10 @@ static void autoconfig_16550a(struct uart_8250_port *up) * Check for presence of the EFR when DLAB is set. * Only ST16C650V1 UARTs pass this test. */ +#ifndef CONFIG_ARCH_MXC + /* This test fails as EFR reads 0, but our uart requires LCR=0xBF + * to access EFR. + */ serial_outp(up, UART_LCR, UART_LCR_DLAB); if (serial_in(up, UART_EFR) == 0) { serial_outp(up, UART_EFR, 0xA8); @@ -955,6 +959,7 @@ static void autoconfig_16550a(struct uart_8250_port *up) serial_outp(up, UART_EFR, 0); return; } +#endif /* * Maybe it requires 0xbf to be written to the LCR. @@ -1477,7 +1482,12 @@ static void transmit_chars(struct uart_8250_port *up) count = up->tx_loadsz; do { +#ifdef CONFIG_ARCH_MXC + /* Seems like back-to-back accesses are a problem */ + serial_out_sync(up, UART_TX, xmit->buf[xmit->tail]); +#else serial_out(up, UART_TX, xmit->buf[xmit->tail]); +#endif xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); up->port.icount.tx++; if (uart_circ_empty(xmit)) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 6553833c12db..669c6373e6d6 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -304,6 +304,61 @@ config SERIAL_AMBA_PL010_CONSOLE your boot loader (lilo or loadlin) about how to pass options to the kernel at boot time.) +config SERIAL_MXC + tristate "MXC Internal serial port support" + depends on ARCH_MXC + select SERIAL_CORE + help + This selects the Freescale Semiconductor MXC Internal UART driver. + If unsure, say N. + +config SERIAL_MXC_CONSOLE + bool "Support for console on a MXC/MX27/MX21 Internal serial port" + depends on SERIAL_MXC=y + select SERIAL_CORE_CONSOLE + help + Say Y here if you wish to use an MXC Internal UART as the system + console (the system console is the device which receives all kernel + messages and warnings and which allows logins in single user mode). + + Even if you say Y here, the currently visible framebuffer console + (/dev/tty0) will still be used as the system console by default, but + you can alter that using a kernel command line option such as + "console=ttymxc". (Try "man bootparam" or see the documentation of + your boot loader (lilo or loadlin) about how to pass options to the + kernel at boot time.) + +config SERIAL_STMP_DBG + tristate "STMP debug serial port support" + depends on ARCH_STMP3XXX + select SERIAL_CORE + help + Driver for Sigmatel 36XX/37XX internal debug serial port + +config SERIAL_STMP_DBG_CONSOLE + bool "Support for console on STMP37XX DBG serial port" + depends on SERIAL_STMP_DBG=y + select SERIAL_CORE_CONSOLE + ---help--- + Say Y here if you wish to use the STMP36XX/37XX debug serial port as the + system console (the system console is the device which receives all + kernel messages and warnings and which allows logins in single user + mode). + + Even if you say Y here, the currently visible framebuffer console + (/dev/tty0) will still be used as the system console by default, but + you can alter that using a kernel command line option such as + "console=ttyAM0". (Try "man bootparam" or see the documentation of + your boot loader (lilo or loadlin) about how to pass options to the + kernel at boot time.) + +config SERIAL_STMP_APP + tristate "STMP app serial port support" + depends on ARCH_STMP3XXX + select SERIAL_CORE + help + Driver for Sigmatel 36XX/37XX internal application serial port + config SERIAL_AMBA_PL011 tristate "ARM AMBA PL011 serial port support" depends on ARM_AMBA diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index d5a29981c6c4..289f41c24efe 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -73,6 +73,10 @@ obj-$(CONFIG_SERIAL_ATMEL) += atmel_serial.o obj-$(CONFIG_SERIAL_UARTLITE) += uartlite.o obj-$(CONFIG_SERIAL_MSM) += msm_serial.o obj-$(CONFIG_SERIAL_NETX) += netx-serial.o +obj-$(CONFIG_SERIAL_MXC) += mxc_uart.o +obj-$(CONFIG_SERIAL_MXC_CONSOLE) += mxc_uart_early.o +obj-$(CONFIG_SERIAL_STMP_DBG) += stmp-dbg.o +obj-$(CONFIG_SERIAL_STMP_APP) += stmp-app.o obj-$(CONFIG_SERIAL_OF_PLATFORM) += of_serial.o obj-$(CONFIG_SERIAL_OF_PLATFORM_NWPSERIAL) += nwpserial.o obj-$(CONFIG_SERIAL_KS8695) += serial_ks8695.o diff --git a/drivers/serial/mxc_uart.c b/drivers/serial/mxc_uart.c new file mode 100644 index 000000000000..f212ba96cd5c --- /dev/null +++ b/drivers/serial/mxc_uart.c @@ -0,0 +1,1950 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/serial/mxc_uart.c + * + * @brief Driver for the Freescale Semiconductor MXC serial ports based on + * drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o. + * + * @ingroup UART + */ + +/* + * Include Files + */ +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/tty.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/serial.h> +#include <linux/console.h> +#include <linux/platform_device.h> +#include <linux/sysrq.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/dma.h> +#include <asm/div64.h> +#include <mach/hardware.h> +#include <mach/mxc_uart.h> + +#if defined(CONFIG_SERIAL_MXC_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) +#define SUPPORT_SYSRQ +#endif +#define SERIAL_MXC_MAJOR 207 +#define SERIAL_MXC_MINOR 16 +#define MXC_ISR_PASS_LIMIT 256 +#define UART_CREAD_BIT 256 + +#define MXC_UART_NR 8 + +/* IRDA minimum pulse duration in micro seconds */ +#define MIN_PULSE_DUR 2 +/* + * Transmit DMA buffer size is set to 1024 bytes, this is limited + * by UART_XMIT_SIZE. + */ +#define TXDMA_BUFF_SIZE UART_XMIT_SIZE +/* + * Receive DMA sub-buffer size + */ +#define RXDMA_BUFF_SIZE 128 + +/*! + * This structure is used to store the information for DMA data transfer. + */ +typedef struct { + /*! + * Holds the read channel number. + */ + int rd_channel; + /*! + * Holds the write channel number. + */ + int wr_channel; + /*! + * UART Transmit Event ID + */ + int tx_event_id; + /*! + * UART Receive Event ID + */ + int rx_event_id; + /*! + * DMA Transmit tasklet + */ + struct tasklet_struct dma_tx_tasklet; + /*! + * Flag indicates if the channel is in use + */ + int dma_txchnl_inuse; +} dma_info; + +/*! + * This is used to indicate if we want echo cancellation in the Irda mode. + */ +static int echo_cancel; +extern void gpio_uart_active(int port, int no_irda); +extern void gpio_uart_inactive(int port, int no_irda); +extern void config_uartdma_event(int port); + +static uart_mxc_port *mxc_ports[MXC_UART_NR]; + +/*! + * This array holds the DMA channel information for each MXC UART + */ +static dma_info dma_list[MXC_UART_NR]; + +/*! + * This function is called by the core driver to stop UART transmission. + * This might be due to the TTY layer indicating that the user wants to stop + * transmission. + * + * @param port the port structure for the UART passed in by the core + * driver + */ +static void mxcuart_stop_tx(struct uart_port *port) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + volatile unsigned int cr1; + + cr1 = readl(port->membase + MXC_UARTUCR1); + /* Disable Transmitter rdy interrupt */ + if (umxc->dma_enabled == 1) { + cr1 &= ~MXC_UARTUCR1_TXDMAEN; + } else { + cr1 &= ~MXC_UARTUCR1_TRDYEN; + } + writel(cr1, port->membase + MXC_UARTUCR1); +} + +/*! + * DMA Transmit tasklet method is scheduled on completion of a DMA transmit + * to send out any more data that is available in the UART xmit buffer. + * + * @param arg driver private data + */ +static void dma_tx_do_tasklet(unsigned long arg) +{ + uart_mxc_port *umxc = (uart_mxc_port *) arg; + struct circ_buf *xmit = &umxc->port.info->xmit; + mxc_dma_requestbuf_t writechnl_request; + int tx_num; + unsigned long flags; + + spin_lock_irqsave(&umxc->port.lock, flags); + tx_num = uart_circ_chars_pending(xmit); + if (tx_num > 0) { + if (xmit->tail > xmit->head) { + memcpy(umxc->tx_buf, xmit->buf + xmit->tail, + UART_XMIT_SIZE - xmit->tail); + memcpy(umxc->tx_buf + (UART_XMIT_SIZE - xmit->tail), + xmit->buf, xmit->head); + } else { + memcpy(umxc->tx_buf, xmit->buf + xmit->tail, tx_num); + } + umxc->tx_handle = dma_map_single(umxc->port.dev, umxc->tx_buf, + TXDMA_BUFF_SIZE, + DMA_TO_DEVICE); + + writechnl_request.dst_addr = umxc->port.mapbase + MXC_UARTUTXD; + writechnl_request.src_addr = umxc->tx_handle; + writechnl_request.num_of_bytes = tx_num; + + if ((mxc_dma_config(dma_list[umxc->port.line].wr_channel, + &writechnl_request, 1, + MXC_DMA_MODE_WRITE)) == 0) { + mxc_dma_enable(dma_list[umxc->port.line].wr_channel); + } + } else { + /* No more data available in the xmit queue, clear the flag */ + dma_list[umxc->port.line].dma_txchnl_inuse = 0; + } + spin_unlock_irqrestore(&umxc->port.lock, flags); +} + +/*! + * DMA Write callback is called by the SDMA controller after it has sent out all + * the data from the user buffer. This function updates the xmit buffer pointers. + * + * @param arg driver private data + * @param error any DMA error + * @param count amount of data that was transferred + */ +static void mxcuart_dma_writecallback(void *arg, int error, unsigned int count) +{ + uart_mxc_port *umxc = arg; + struct circ_buf *xmit = &umxc->port.info->xmit; + int tx_num; + + if (error != MXC_DMA_TRANSFER_ERROR) { + tx_num = count; + umxc->port.icount.tx += tx_num; + xmit->tail = (xmit->tail + tx_num) & (UART_XMIT_SIZE - 1); + } + + dma_unmap_single(umxc->port.dev, umxc->tx_handle, TXDMA_BUFF_SIZE, + DMA_TO_DEVICE); + tx_num = uart_circ_chars_pending(xmit); + /* Schedule a tasklet to send out the pending characters */ + if (tx_num > 0) { + tasklet_schedule(&dma_list[umxc->port.line].dma_tx_tasklet); + } else { + dma_list[umxc->port.line].dma_txchnl_inuse = 0; + } + if (tx_num < WAKEUP_CHARS) { + uart_write_wakeup(&umxc->port); + } +} + +/*! + * This function is called by the core driver to start transmitting characters. + * This function enables the transmit interrupts. + * + * @param port the port structure for the UART passed in by the core + * driver + */ +static void mxcuart_start_tx(struct uart_port *port) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + struct circ_buf *xmit = &umxc->port.info->xmit; + volatile unsigned int cr1; + mxc_dma_requestbuf_t writechnl_request; + int tx_num; + + cr1 = readl(port->membase + MXC_UARTUCR1); + /* Enable Transmitter rdy interrupt */ + if (umxc->dma_enabled == 1) { + /* + * If the channel is in use then return immediately and use + * the dma_tx tasklet to transfer queued data when current DMA + * transfer is complete + */ + if (dma_list[umxc->port.line].dma_txchnl_inuse == 1) { + return; + } + tx_num = uart_circ_chars_pending(xmit); + if (tx_num > 0) { + dma_list[umxc->port.line].dma_txchnl_inuse = 1; + if (xmit->tail > xmit->head) { + memcpy(umxc->tx_buf, xmit->buf + xmit->tail, + UART_XMIT_SIZE - xmit->tail); + memcpy(umxc->tx_buf + + (UART_XMIT_SIZE - xmit->tail), xmit->buf, + xmit->head); + } else { + memcpy(umxc->tx_buf, xmit->buf + xmit->tail, + tx_num); + } + umxc->tx_handle = + dma_map_single(umxc->port.dev, umxc->tx_buf, + TXDMA_BUFF_SIZE, DMA_TO_DEVICE); + + writechnl_request.dst_addr = + umxc->port.mapbase + MXC_UARTUTXD; + writechnl_request.src_addr = umxc->tx_handle; + writechnl_request.num_of_bytes = tx_num; + if ((mxc_dma_config + (dma_list[umxc->port.line].wr_channel, + &writechnl_request, 1, + MXC_DMA_MODE_WRITE)) == 0) { + mxc_dma_enable(dma_list[umxc->port.line]. + wr_channel); + } + cr1 |= MXC_UARTUCR1_TXDMAEN; + } + } else { + cr1 |= MXC_UARTUCR1_TRDYEN; + } + writel(cr1, port->membase + MXC_UARTUCR1); +} + +/*! + * This function is called by the core driver to stop receiving characters; the + * port is in the process of being closed. + * + * @param port the port structure for the UART passed in by the core driver + */ +static void mxcuart_stop_rx(struct uart_port *port) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + volatile unsigned int cr1; + + cr1 = readl(port->membase + MXC_UARTUCR1); + if (umxc->dma_enabled == 1) { + cr1 &= ~MXC_UARTUCR1_RXDMAEN; + } else { + cr1 &= ~MXC_UARTUCR1_RRDYEN; + } + writel(cr1, port->membase + MXC_UARTUCR1); +} + +/*! + * This function is called by the core driver to enable the modem status + * interrupts. If the port is configured to be in DTE mode then it enables the + * DCDDELT and RIDELT interrupts in addition to the DTRDEN interrupt. The RTSDEN + * interrupt is enabled only for interrupt-driven hardware flow control. + * + * @param port the port structure for the UART passed in by the core driver + */ +static void mxcuart_enable_ms(struct uart_port *port) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + volatile unsigned int cr1, cr3; + + /* + * RTS interrupt is enabled only if we are using interrupt-driven + * software controlled hardware flow control + */ + if (umxc->hardware_flow == 0) { + cr1 = readl(umxc->port.membase + MXC_UARTUCR1); + cr1 |= MXC_UARTUCR1_RTSDEN; + writel(cr1, umxc->port.membase + MXC_UARTUCR1); + } + cr3 = readl(umxc->port.membase + MXC_UARTUCR3); + cr3 |= MXC_UARTUCR3_DTRDEN; + if (umxc->mode == MODE_DTE) { + cr3 |= MXC_UARTUCR3_DCD | MXC_UARTUCR3_RI; + } + writel(cr3, umxc->port.membase + MXC_UARTUCR3); +} + +/*! + * This function is called from the interrupt service routine if the status bit + * indicates that the receive fifo data level is above the set threshold. The + * function reads the character and queues them into the TTY layers read + * buffer. The function also looks for break characters, parity and framing + * errors in the received character and sets the appropriate flag in the TTY + * receive buffer. + * + * @param umxc the MXC UART port structure, this includes the \b uart_port + * structure and other members that are specific to MXC UARTs + */ +static void mxcuart_rx_chars(uart_mxc_port * umxc) +{ + volatile unsigned int ch, sr2; + unsigned int status, flag, max_count = 256; + + sr2 = readl(umxc->port.membase + MXC_UARTUSR2); + while (((sr2 & MXC_UARTUSR2_RDR) == 1) && (max_count-- > 0)) { + ch = readl(umxc->port.membase + MXC_UARTURXD); + + flag = TTY_NORMAL; + status = ch | UART_CREAD_BIT; + ch &= 0xFF; /* Clear the upper bits */ + umxc->port.icount.rx++; + + /* + * Check to see if there is an error in the received + * character. Perform the appropriate actions based on the + * error bit that was set. + */ + if (status & MXC_UARTURXD_ERR) { + if (status & MXC_UARTURXD_BRK) { + /* + * Clear the frame and parity error bits + * as these always get set on receiving a + * break character + */ + status &= ~(MXC_UARTURXD_FRMERR | + MXC_UARTURXD_PRERR); + umxc->port.icount.brk++; + if (uart_handle_break(&umxc->port)) { + goto ignore_char; + } + } else if (status & MXC_UARTURXD_FRMERR) { + umxc->port.icount.frame++; + } else if (status & MXC_UARTURXD_PRERR) { + umxc->port.icount.parity++; + } + if (status & MXC_UARTURXD_OVRRUN) { + umxc->port.icount.overrun++; + } + + status &= umxc->port.read_status_mask; + + if (status & MXC_UARTURXD_BRK) { + flag = TTY_BREAK; + } else if (status & MXC_UARTURXD_FRMERR) { + flag = TTY_FRAME; + } else if (status & MXC_UARTURXD_PRERR) { + flag = TTY_PARITY; + } + } + + if (uart_handle_sysrq_char(&umxc->port, ch)) { + goto ignore_char; + } + + uart_insert_char(&umxc->port, status, MXC_UARTURXD_OVRRUN, ch, + flag); + ignore_char: + sr2 = readl(umxc->port.membase + MXC_UARTUSR2); + } + tty_flip_buffer_push(umxc->port.info->port.tty); +} + +/*! + * This function is called from the interrupt service routine if the status bit + * indicates that the transmit fifo is emptied below its set threshold and + * requires data. The function pulls characters from the TTY layers write + * buffer and writes it out to the UART transmit fifo. + * + * @param umxc the MXC UART port structure, this includes the \b uart_port + * structure and other members that are specific to MXC UARTs + */ +static void mxcuart_tx_chars(uart_mxc_port * umxc) +{ + struct circ_buf *xmit = &umxc->port.info->xmit; + int count; + + /* + * Transmit the XON/XOFF character if required + */ + if (umxc->port.x_char) { + writel(umxc->port.x_char, umxc->port.membase + MXC_UARTUTXD); + umxc->port.icount.tx++; + umxc->port.x_char = 0; + return; + } + + /* + * Check to see if there is any data to be sent and that the + * port has not been currently stopped by anything. + */ + if (uart_circ_empty(xmit) || uart_tx_stopped(&umxc->port)) { + mxcuart_stop_tx(&umxc->port); + return; + } + + count = umxc->port.fifosize - umxc->tx_threshold; + do { + writel(xmit->buf[xmit->tail], + umxc->port.membase + MXC_UARTUTXD); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + umxc->port.icount.tx++; + if (uart_circ_empty(xmit)) { + break; + } + } while (--count > 0); + + /* + * Check to see if we have flushed enough characters to ask for more + * to be sent to us, if so, we notify the user space that we can + * accept more data + */ + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) { + uart_write_wakeup(&umxc->port); + } + + if (uart_circ_empty(xmit)) { + mxcuart_stop_tx(&umxc->port); + } +} + +/*! + * This function is called from the interrupt service routine if there is a + * change in the modem signals. This function handles these signal changes and + * also clears the appropriate status register bits. + * + * @param umxc the MXC UART port structure, this includes the \b uart_port + * structure and other members that are specific to MXC UARTs + * @param sr1 contents of status register 1 + * @param sr2 contents of status register 2 + */ +static void mxcuart_modem_status(uart_mxc_port * umxc, unsigned int sr1, + unsigned int sr2) +{ + if (umxc->mode == MODE_DTE) { + if (sr2 & MXC_UARTUSR2_DCDDELT) { + uart_handle_dcd_change(&umxc->port, + !(sr2 & MXC_UARTUSR2_DCDIN)); + } + if (sr2 & MXC_UARTUSR2_RIDELT) { + umxc->port.icount.rng++; + } + } + if (sr1 & MXC_UARTUSR1_DTRD) { + umxc->port.icount.dsr++; + } + if ((umxc->hardware_flow == 0) && (sr1 & MXC_UARTUSR1_RTSD)) { + uart_handle_cts_change(&umxc->port, sr1 & MXC_UARTUSR1_RTSS); + } + + wake_up_interruptible(&umxc->port.info->delta_msr_wait); +} + +/*! + * Interrupt service routine registered to handle the muxed ANDed interrupts. + * This routine is registered only in the case where the UART interrupts are + * muxed. + * + * @param irq the interrupt number + * @param dev_id driver private data + * + * @return The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + * \b IRQ_RETVAL is defined in \b include/linux/interrupt.h. + */ +static irqreturn_t mxcuart_int(int irq, void *dev_id) +{ + uart_mxc_port *umxc = dev_id; + volatile unsigned int sr1, sr2, cr1, cr; + unsigned int pass_counter = MXC_ISR_PASS_LIMIT; + unsigned int term_cond = 0; + int handled = 0; + + sr1 = readl(umxc->port.membase + MXC_UARTUSR1); + sr2 = readl(umxc->port.membase + MXC_UARTUSR2); + cr1 = readl(umxc->port.membase + MXC_UARTUCR1); + + do { + /* Clear the bits that triggered the interrupt */ + writel(sr1, umxc->port.membase + MXC_UARTUSR1); + writel(sr2, umxc->port.membase + MXC_UARTUSR2); + /* + * Read if there is data available + */ + if (sr2 & MXC_UARTUSR2_RDR) { + mxcuart_rx_chars(umxc); + } + + if ((sr1 & (MXC_UARTUSR1_RTSD | MXC_UARTUSR1_DTRD)) || + (sr2 & (MXC_UARTUSR2_DCDDELT | MXC_UARTUSR2_RIDELT))) { + mxcuart_modem_status(umxc, sr1, sr2); + } + + /* + * Send data if there is data to be sent + */ + if ((cr1 & MXC_UARTUCR1_TRDYEN) && (sr1 & MXC_UARTUSR1_TRDY)) { + /* Echo cancellation for IRDA Transmit chars */ + if (umxc->ir_mode == IRDA && echo_cancel) { + /* Disable the receiver */ + cr = readl(umxc->port.membase + MXC_UARTUCR2); + cr &= ~MXC_UARTUCR2_RXEN; + writel(cr, umxc->port.membase + MXC_UARTUCR2); + /* Enable Transmit complete intr to reenable RX */ + cr = readl(umxc->port.membase + MXC_UARTUCR4); + cr |= MXC_UARTUCR4_TCEN; + writel(cr, umxc->port.membase + MXC_UARTUCR4); + } + mxcuart_tx_chars(umxc); + } + + if (pass_counter-- == 0) { + break; + } + + sr1 = readl(umxc->port.membase + MXC_UARTUSR1); + sr2 = readl(umxc->port.membase + MXC_UARTUSR2); + + /* Is the transmit complete to reenable the receiver? */ + if (umxc->ir_mode == IRDA && echo_cancel) { + if (sr2 & MXC_UARTUSR2_TXDC) { + cr = readl(umxc->port.membase + MXC_UARTUCR2); + cr |= MXC_UARTUCR2_RXEN; + writel(cr, umxc->port.membase + MXC_UARTUCR2); + /* Disable the Transmit complete interrupt bit */ + cr = readl(umxc->port.membase + MXC_UARTUCR4); + cr &= ~MXC_UARTUCR4_TCEN; + writel(cr, umxc->port.membase + MXC_UARTUCR4); + } + } + + /* + * If there is no data to send or receive and if there is no + * change in the modem status signals then quit the routine + */ + term_cond = sr1 & (MXC_UARTUSR1_RTSD | MXC_UARTUSR1_DTRD); + term_cond |= sr2 & (MXC_UARTUSR2_RDR | MXC_UARTUSR2_DCDDELT); + term_cond |= !(sr2 & MXC_UARTUSR2_TXFE); + } while (term_cond > 0); + + handled = 1; + return IRQ_RETVAL(handled); +} + +/*! + * Interrupt service routine registered to handle the transmit interrupts. This + * routine is registered only in the case where the UART interrupts are not + * muxed. + * + * @param irq the interrupt number + * @param dev_id driver private data + * + * @return The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + * \b IRQ_RETVAL is defined in include/linux/interrupt.h. + */ +static irqreturn_t mxcuart_tx_int(int irq, void *dev_id) +{ + uart_mxc_port *umxc = dev_id; + int handled = 0; + volatile unsigned int sr2, cr; + + /* Echo cancellation for IRDA Transmit chars */ + if (umxc->ir_mode == IRDA && echo_cancel) { + /* Disable the receiver */ + cr = readl(umxc->port.membase + MXC_UARTUCR2); + cr &= ~MXC_UARTUCR2_RXEN; + writel(cr, umxc->port.membase + MXC_UARTUCR2); + /* Enable Transmit complete to reenable receiver */ + cr = readl(umxc->port.membase + MXC_UARTUCR4); + cr |= MXC_UARTUCR4_TCEN; + writel(cr, umxc->port.membase + MXC_UARTUCR4); + } + + mxcuart_tx_chars(umxc); + + /* Is the transmit complete to reenable the receiver? */ + if (umxc->ir_mode == IRDA && echo_cancel) { + sr2 = readl(umxc->port.membase + MXC_UARTUSR2); + if (sr2 & MXC_UARTUSR2_TXDC) { + cr = readl(umxc->port.membase + MXC_UARTUCR2); + cr |= MXC_UARTUCR2_RXEN; + writel(cr, umxc->port.membase + MXC_UARTUCR2); + /* Disable the Transmit complete interrupt bit */ + cr = readl(umxc->port.membase + MXC_UARTUCR4); + cr &= ~MXC_UARTUCR4_TCEN; + writel(cr, umxc->port.membase + MXC_UARTUCR4); + } + } + + handled = 1; + + return IRQ_RETVAL(handled); +} + +/*! + * Interrupt service routine registered to handle the receive interrupts. This + * routine is registered only in the case where the UART interrupts are not + * muxed. + * + * @param irq the interrupt number + * @param dev_id driver private data + * + * @return The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + * \b IRQ_RETVAL is defined in include/linux/interrupt.h. + */ +static irqreturn_t mxcuart_rx_int(int irq, void *dev_id) +{ + uart_mxc_port *umxc = dev_id; + int handled = 0; + + /* Clear the aging timer bit */ + writel(MXC_UARTUSR1_AGTIM, umxc->port.membase + MXC_UARTUSR1); + mxcuart_rx_chars(umxc); + handled = 1; + + return IRQ_RETVAL(handled); +} + +/*! + * Interrupt service routine registered to handle the master interrupts. This + * routine is registered only in the case where the UART interrupts are not + * muxed. + * + * @param irq the interrupt number + * @param dev_id driver private data + * + * @return The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + * \b IRQ_RETVAL is defined in include/linux/interrupt.h. + */ +static irqreturn_t mxcuart_mint_int(int irq, void *dev_id) +{ + uart_mxc_port *umxc = dev_id; + int handled = 0; + volatile unsigned int sr1, sr2; + + sr1 = readl(umxc->port.membase + MXC_UARTUSR1); + sr2 = readl(umxc->port.membase + MXC_UARTUSR2); + /* Clear the modem status interrupt bits */ + writel(MXC_UARTUSR1_RTSD | MXC_UARTUSR1_DTRD, + umxc->port.membase + MXC_UARTUSR1); + writel(MXC_UARTUSR2_DCDDELT | MXC_UARTUSR2_RIDELT, + umxc->port.membase + MXC_UARTUSR2); + mxcuart_modem_status(umxc, sr1, sr2); + handled = 1; + + return IRQ_RETVAL(handled); +} + +/*! + * This function is called by the core driver to test whether the transmitter + * fifo and shift register for the UART port are empty. + * + * @param port the port structure for the UART passed in by the core driver + * + * @return The function returns TIOCSER_TEMT if it is empty, else returns 0. + */ +static unsigned int mxcuart_tx_empty(struct uart_port *port) +{ + volatile unsigned int sr2; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + sr2 = readl(port->membase + MXC_UARTUSR2); + spin_unlock_irqrestore(&port->lock, flags); + + return sr2 & MXC_UARTUSR2_TXDC ? TIOCSER_TEMT : 0; +} + +/*! + * This function is called by the core driver to get the current status of the + * modem input signals. The state of the output signals is not collected. + * + * @param port the port structure for the UART passed in by the core driver + * + * @return The function returns an integer that contains the ORed value of the + * status of all the modem input signals or error. + */ +static unsigned int mxcuart_get_mctrl(struct uart_port *port) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + unsigned int result = 0; + volatile unsigned int sr1, sr2; + + sr1 = readl(umxc->port.membase + MXC_UARTUSR1); + sr2 = readl(umxc->port.membase + MXC_UARTUSR2); + + if (sr1 & MXC_UARTUSR1_RTSS) { + result |= TIOCM_CTS; + } + if (umxc->mode == MODE_DTE) { + if (!(sr2 & MXC_UARTUSR2_DCDIN)) { + result |= TIOCM_CAR; + } + if (!(sr2 & MXC_UARTUSR2_RIIN)) { + result |= TIOCM_RI; + } + } + return result; +} + +/*! + * This function is called by the core driver to set the state of the modem + * control lines. + * + * @param port the port structure for the UART passed in by the core driver + * @param mctrl the state that the modem control lines should be changed to + */ +static void mxcuart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + volatile unsigned int cr2 = 0, cr3 = 0, uts = 0; + + cr2 = readl(port->membase + MXC_UARTUCR2); + cr3 = readl(port->membase + MXC_UARTUCR3); + uts = readl(port->membase + MXC_UARTUTS); + + if (mctrl & TIOCM_RTS) { + /* + * Return to hardware-driven hardware flow control if the + * option is enabled + */ + if (umxc->hardware_flow == 1) { + cr2 |= MXC_UARTUCR2_CTSC; + } else { + cr2 |= MXC_UARTUCR2_CTS; + cr2 &= ~MXC_UARTUCR2_CTSC; + } + } else { + cr2 &= ~(MXC_UARTUCR2_CTS | MXC_UARTUCR2_CTSC); + } + writel(cr2, port->membase + MXC_UARTUCR2); + + if (mctrl & TIOCM_DTR) { + cr3 |= MXC_UARTUCR3_DSR; + } else { + cr3 &= ~MXC_UARTUCR3_DSR; + } + writel(cr3, port->membase + MXC_UARTUCR3); + + if (mctrl & TIOCM_LOOP) { + if (umxc->ir_mode == IRDA) { + echo_cancel = 0; + } else { + uts |= MXC_UARTUTS_LOOP; + } + } else { + if (umxc->ir_mode == IRDA) { + echo_cancel = 1; + } else { + uts &= ~MXC_UARTUTS_LOOP; + } + } + writel(uts, port->membase + MXC_UARTUTS); +} + +/*! + * This function is called by the core driver to control the transmission of + * the break signal. If break_state is non-zero, the break signal is + * transmitted, the signal is terminated when another call is made with + * break_state set to 0. + * + * @param port the port structure for the UART passed in by the core + * driver + * @param break_state the requested state of the break signal + */ +static void mxcuart_break_ctl(struct uart_port *port, int break_state) +{ + volatile unsigned int cr1; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + cr1 = readl(port->membase + MXC_UARTUCR1); + if (break_state == -1) { + cr1 |= MXC_UARTUCR1_SNDBRK; + } else { + cr1 &= ~MXC_UARTUCR1_SNDBRK; + } + writel(cr1, port->membase + MXC_UARTUCR1); + spin_unlock_irqrestore(&port->lock, flags); +} + +/*! + * The read DMA callback, this method is called when the DMA buffer has received its + * data. This functions copies the data to the tty buffer and updates the tty buffer + * pointers. It also queues the DMA buffer back to the DMA system. + * + * @param arg driver private data + * @param error any DMA error + * @param cnt amount of data that was transferred + */ +static void mxcuart_dmaread_callback(void *arg, int error, unsigned int cnt) +{ + uart_mxc_port *umxc = arg; + struct tty_struct *tty = umxc->port.info->port.tty; + int buff_id, flip_cnt, num_bufs; + mxc_dma_requestbuf_t readchnl_request; + mxc_uart_rxdmamap *rx_buf_elem = NULL; + unsigned int sr1, sr2; + char flag; + + num_bufs = umxc->dma_rxbuf_size / RXDMA_BUFF_SIZE; + /* Clear the aging timer bit */ + writel(MXC_UARTUSR1_AGTIM, umxc->port.membase + MXC_UARTUSR1); + + buff_id = umxc->dma_rxbuf_id; + flag = TTY_NORMAL; + + if ((umxc->dma_rxbuf_id += 1) >= num_bufs) { + umxc->dma_rxbuf_id = 0; + } + + rx_buf_elem = (mxc_uart_rxdmamap *) (umxc->rx_dmamap + buff_id); + + if (error == MXC_DMA_TRANSFER_ERROR) { + + sr1 = __raw_readl(umxc->port.membase + MXC_UARTUSR1); + sr2 = __raw_readl(umxc->port.membase + MXC_UARTUSR2); + + if (sr2 & MXC_UARTUSR2_BRCD) { + umxc->port.icount.brk++; + if (uart_handle_break(&umxc->port)) { + goto drop_data; + } + } else if (sr1 & MXC_UARTUSR1_PARITYERR) { + umxc->port.icount.parity++; + } else if (sr1 & MXC_UARTUSR1_FRAMERR) { + umxc->port.icount.frame++; + } else if (sr2 & MXC_UARTUSR2_ORE) { + umxc->port.icount.overrun++; + + } + + if (umxc->port.read_status_mask & MXC_UARTURXD_BRK) { + if (sr2 & MXC_UARTUSR2_BRCD) + flag = TTY_BREAK; + } else if (umxc->port.read_status_mask & MXC_UARTURXD_PRERR) { + if (sr1 & MXC_UARTUSR1_PARITYERR) + flag = TTY_PARITY; + } else if (umxc->port.read_status_mask & MXC_UARTURXD_FRMERR) { + if (sr1 & MXC_UARTUSR1_FRAMERR) + flag = TTY_FRAME; + } else if (umxc->port.read_status_mask & MXC_UARTURXD_OVRRUN) { + if (sr2 & MXC_UARTUSR2_ORE) + flag = TTY_OVERRUN; + } +/* By default clearing all error bits in status reg */ + __raw_writel((MXC_UARTUSR2_BRCD | MXC_UARTUSR2_ORE), + umxc->port.membase + MXC_UARTUSR2); + __raw_writel((MXC_UARTUSR1_PARITYERR | MXC_UARTUSR1_FRAMERR), + umxc->port.membase + MXC_UARTUSR1); + } + + flip_cnt = tty_buffer_request_room(tty, cnt); + + /* Check for space availability in the TTY Flip buffer */ + if (flip_cnt <= 0) { + goto drop_data; + } + umxc->port.icount.rx += flip_cnt; + + tty_insert_flip_string(tty, rx_buf_elem->rx_buf, flip_cnt); + + if (flag != TTY_NORMAL) { + tty_insert_flip_char(tty, 0, flag); + } + + tty_flip_buffer_push(tty); + umxc->port.info->port.tty->real_raw = 1; + + drop_data: + readchnl_request.src_addr = umxc->port.mapbase; + readchnl_request.dst_addr = rx_buf_elem->rx_handle; + readchnl_request.num_of_bytes = RXDMA_BUFF_SIZE; + mxc_dma_config(dma_list[umxc->port.line].rd_channel, &readchnl_request, + 1, MXC_DMA_MODE_READ); + mxc_dma_enable(dma_list[umxc->port.line].rd_channel); +} + +/*! + * Allocates DMA read and write channels, creates DMA read and write buffers and + * sets the channel specific parameters. + * + * @param d_info the structure that holds all the DMA information for a + * particular MXC UART + * @param umxc the MXC UART port structure, this includes the \b uart_port + * structure and other members that are specific to MXC UARTs + * + * @return The function returns 0 on success and a non-zero value on failure. + */ +static int mxcuart_initdma(dma_info * d_info, uart_mxc_port * umxc) +{ + int ret = 0, rxbufs, i, j; + mxc_dma_requestbuf_t *readchnl_reqelem; + mxc_uart_rxdmamap *rx_buf_elem; + + /* Request for the read and write channels */ + d_info->rd_channel = mxc_dma_request(umxc->dma_rx_id, "MXC UART Read"); + if (d_info->rd_channel < 0) { + printk(KERN_ERR "MXC UART: Cannot allocate DMA read channel\n"); + return -1; + } else { + d_info->wr_channel = + mxc_dma_request(umxc->dma_tx_id, "MXC UART Write"); + if (d_info->wr_channel < 0) { + mxc_dma_free(d_info->rd_channel); + printk(KERN_ERR + "MXC UART: Cannot allocate DMA write channel\n"); + return -1; + } + } + + /* Allocate the DMA Transmit Buffer */ + if ((umxc->tx_buf = kmalloc(TXDMA_BUFF_SIZE, GFP_KERNEL)) == NULL) { + ret = -1; + goto err_dma_tx_buff; + } + rxbufs = umxc->dma_rxbuf_size / RXDMA_BUFF_SIZE; + /* Allocate the DMA Virtual Receive Buffer */ + if ((umxc->rx_dmamap = kmalloc(rxbufs * sizeof(mxc_uart_rxdmamap), + GFP_KERNEL)) == NULL) { + ret = -1; + goto err_dma_rx_buff; + } + + /* Allocate the DMA Receive Request structures */ + if ((readchnl_reqelem = + kmalloc(rxbufs * sizeof(mxc_dma_requestbuf_t), + GFP_KERNEL)) == NULL) { + ret = -1; + goto err_request; + } + + for (i = 0; i < rxbufs; i++) { + rx_buf_elem = (mxc_uart_rxdmamap *) (umxc->rx_dmamap + i); + rx_buf_elem->rx_buf = + dma_alloc_coherent(NULL, RXDMA_BUFF_SIZE, + &rx_buf_elem->rx_handle, GFP_DMA); + if (rx_buf_elem->rx_buf == NULL) { + for (j = 0; j < i; j++) { + rx_buf_elem = + (mxc_uart_rxdmamap *) (umxc->rx_dmamap + j); + dma_free_coherent(NULL, RXDMA_BUFF_SIZE, + rx_buf_elem->rx_buf, + rx_buf_elem->rx_handle); + } + ret = -1; + goto cleanup; + } + } + + umxc->dma_rxbuf_id = 0; + /* Setup the DMA read request structures */ + for (i = 0; i < rxbufs; i++) { + rx_buf_elem = (mxc_uart_rxdmamap *) (umxc->rx_dmamap + i); + (readchnl_reqelem + i)->src_addr = umxc->port.mapbase; + (readchnl_reqelem + i)->dst_addr = rx_buf_elem->rx_handle; + (readchnl_reqelem + i)->num_of_bytes = RXDMA_BUFF_SIZE; + } + mxc_dma_config(d_info->rd_channel, readchnl_reqelem, rxbufs, + MXC_DMA_MODE_READ); + mxc_dma_callback_set(d_info->rd_channel, mxcuart_dmaread_callback, + umxc); + mxc_dma_callback_set(d_info->wr_channel, mxcuart_dma_writecallback, + umxc); + + /* Start the read channel */ + mxc_dma_enable(d_info->rd_channel); + kfree(readchnl_reqelem); + tasklet_init(&d_info->dma_tx_tasklet, dma_tx_do_tasklet, + (unsigned long)umxc); + d_info->dma_txchnl_inuse = 0; + return ret; + cleanup: + kfree(readchnl_reqelem); + err_request: + kfree(umxc->rx_dmamap); + err_dma_rx_buff: + kfree(umxc->tx_buf); + err_dma_tx_buff: + mxc_dma_free(d_info->rd_channel); + mxc_dma_free(d_info->wr_channel); + + return ret; +} + +/*! + * Stops DMA and frees the DMA resources + * + * @param d_info the structure that holds all the DMA information for a + * particular MXC UART + * @param umxc the MXC UART port structure, this includes the \b uart_port + * structure and other members that are specific to MXC UARTs + */ +static void mxcuart_freedma(dma_info * d_info, uart_mxc_port * umxc) +{ + int i, rxbufs; + mxc_uart_rxdmamap *rx_buf_elem; + + rxbufs = umxc->dma_rxbuf_size / RXDMA_BUFF_SIZE; + + for (i = 0; i < rxbufs; i++) { + rx_buf_elem = (mxc_uart_rxdmamap *) (umxc->rx_dmamap + i); + dma_free_coherent(NULL, RXDMA_BUFF_SIZE, + rx_buf_elem->rx_buf, rx_buf_elem->rx_handle); + } + kfree(umxc->rx_dmamap); + kfree(umxc->tx_buf); + mxc_dma_free(d_info->rd_channel); + mxc_dma_free(d_info->wr_channel); +} + +/*! + * This function is called to free the interrupts. + * + * @param umxc the MXC UART port structure, this includes the \b uart_port + * structure and other members that are specific to MXC UARTs + */ +static void mxcuart_free_interrupts(uart_mxc_port * umxc) +{ + free_irq(umxc->port.irq, umxc); + if (umxc->ints_muxed == 0) { + free_irq(umxc->irqs[0], umxc); + free_irq(umxc->irqs[1], umxc); + } +} + +/*! + * Calculate and set the UART port clock value + * + * @param umxc the MXC UART port structure, this includes the \b uart_port + * structure and other members that are specific to MXC UARTs + * @param per_clk peripheral clock coming into the MXC UART module + * @param req_baud current baudrate requested + * @param div returns the reference frequency divider value + */ +static void mxcuart_set_ref_freq(uart_mxc_port * umxc, unsigned long per_clk, + unsigned int req_baud, int *div) +{ + unsigned int d = 1; + + /* + * Choose the smallest possible prescaler to maximize + * the chance of using integer scaling. Ensure that + * the calculation won't overflow. Limit the denom + * to 15 bits since a 16-bit denom doesn't work. + */ + if (req_baud < (1 << (31 - (4 + 15)))) + d = per_clk / (req_baud << (4 + 15)) + 1; + + umxc->port.uartclk = per_clk / d; + + /* + * Set the ONEMS register that is used by IR special case bit and + * the Escape character detect logic + */ + writel(umxc->port.uartclk / 1000, umxc->port.membase + MXC_UARTONEMS); + *div = d; +} + +/*! + * This function is called by the core driver to initialize the low-level + * driver. The function grabs the interrupt resources and registers its + * interrupt service routines. It then initializes the IOMUX registers to + * configure the pins for UART signals and finally initializes the various + * UART registers and enables the port for reception. + * + * @param port the port structure for the UART passed in by the core driver + * + * @return The function returns 0 on success and a non-zero value on failure. + */ +static int mxcuart_startup(struct uart_port *port) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + int retval; + volatile unsigned int cr, cr1 = 0, cr2 = 0, ufcr = 0; + + /* + * Some UARTs need separate registrations for the interrupts as + * they do not take the muxed interrupt output to the ARM core + */ + if (umxc->ints_muxed == 1) { + retval = request_irq(umxc->port.irq, mxcuart_int, 0, + "mxcintuart", umxc); + if (retval != 0) { + return retval; + } + } else { + retval = request_irq(umxc->port.irq, mxcuart_tx_int, + 0, "mxcintuart", umxc); + if (retval != 0) { + return retval; + } else { + retval = request_irq(umxc->irqs[0], mxcuart_rx_int, + 0, "mxcintuart", umxc); + if (retval != 0) { + free_irq(umxc->port.irq, umxc); + return retval; + } else { + retval = + request_irq(umxc->irqs[1], mxcuart_mint_int, + 0, "mxcintuart", umxc); + if (retval != 0) { + free_irq(umxc->port.irq, umxc); + free_irq(umxc->irqs[0], umxc); + return retval; + } + } + } + } + + /* Initialize the DMA if we need SDMA data transfer */ + if (umxc->dma_enabled == 1) { + retval = mxcuart_initdma(dma_list + umxc->port.line, umxc); + if (retval != 0) { + printk + (KERN_ERR + "MXC UART: Failed to initialize DMA for UART %d\n", + umxc->port.line); + mxcuart_free_interrupts(umxc); + return retval; + } + /* Configure the GPR register to receive SDMA events */ + config_uartdma_event(umxc->port.line); + } + + /* + * Clear Status Registers 1 and 2 + */ + writel(0xFFFF, umxc->port.membase + MXC_UARTUSR1); + writel(0xFFFF, umxc->port.membase + MXC_UARTUSR2); + + /* Configure the IOMUX for the UART */ + gpio_uart_active(umxc->port.line, umxc->ir_mode); + + /* + * Set the transceiver invert bits if required + */ + if (umxc->ir_mode == IRDA) { + echo_cancel = 1; + writel(umxc->ir_rx_inv | MXC_UARTUCR4_IRSC, umxc->port.membase + + MXC_UARTUCR4); + writel(umxc->rxd_mux | umxc->ir_tx_inv, + umxc->port.membase + MXC_UARTUCR3); + } else { + writel(umxc->rxd_mux, umxc->port.membase + MXC_UARTUCR3); + } + + /* + * Initialize UCR1,2 and UFCR registers + */ + if (umxc->dma_enabled == 1) { + cr2 = (MXC_UARTUCR2_TXEN | MXC_UARTUCR2_RXEN); + } else { + cr2 = + (MXC_UARTUCR2_ATEN | MXC_UARTUCR2_TXEN | MXC_UARTUCR2_RXEN); + } + + writel(cr2, umxc->port.membase + MXC_UARTUCR2); + /* Wait till we are out of software reset */ + do { + cr = readl(umxc->port.membase + MXC_UARTUCR2); + } while (!(cr & MXC_UARTUCR2_SRST)); + + if (umxc->mode == MODE_DTE) { + ufcr |= ((umxc->tx_threshold << MXC_UARTUFCR_TXTL_OFFSET) | + MXC_UARTUFCR_DCEDTE | MXC_UARTUFCR_RFDIV | umxc-> + rx_threshold); + } else { + ufcr |= ((umxc->tx_threshold << MXC_UARTUFCR_TXTL_OFFSET) | + MXC_UARTUFCR_RFDIV | umxc->rx_threshold); + } + writel(ufcr, umxc->port.membase + MXC_UARTUFCR); + + /* + * Finally enable the UART and the Receive interrupts + */ + if (umxc->ir_mode == IRDA) { + cr1 |= MXC_UARTUCR1_IREN; + } + if (umxc->dma_enabled == 1) { + cr1 |= (MXC_UARTUCR1_RXDMAEN | MXC_UARTUCR1_ATDMAEN | + MXC_UARTUCR1_UARTEN); + } else { + cr1 |= (MXC_UARTUCR1_RRDYEN | MXC_UARTUCR1_UARTEN); + } + writel(cr1, umxc->port.membase + MXC_UARTUCR1); + + return 0; +} + +/*! + * This function is called by the core driver for the low-level driver to free + * its resources. The function frees all its interrupts and disables the UART. + * + * @param port the port structure for the UART passed in by the core driver + */ +static void mxcuart_shutdown(struct uart_port *port) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + + /* Disable the IOMUX for the UART */ + gpio_uart_inactive(umxc->port.line, umxc->ir_mode); + mxcuart_free_interrupts(umxc); + /* Disable all interrupts, port and break condition */ + writel(0, umxc->port.membase + MXC_UARTUCR1); + writel(0, umxc->port.membase + MXC_UARTUCR3); + if (umxc->dma_enabled == 1) { + mxcuart_freedma(dma_list + umxc->port.line, umxc); + } +} + +/*! + * This function is called while changing the UART parameters. It is called to + * check if the Infrared special case bit (IRSC) in control register 4 should + * be set. + * + * @param baudrate the desired baudrate + * + * @return The functions returns 0 if the IRSC bit does not have to be set, + * else it returns a 1. + */ +/* +static int mxcuart_setir_special(u_int baudrate) +{ + u_int thresh_val; + + thresh_val = 1000000 / (8 * MIN_PULSE_DUR); + if (baudrate > thresh_val) { + return 0; + } + + return 1; +} +*/ + +/*! + * This function is called by the core driver to change the UART parameters, + * including baudrate, word length, parity, stop bits. The function also updates + * the port structures mask registers to indicate the types of events the user is + * interested in receiving. + * + * @param port the port structure for the UART passed in by the core driver + * @param termios the desired termios settings + * @param old old termios + */ +static void mxcuart_set_termios(struct uart_port *port, + struct ktermios *termios, struct ktermios *old) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + volatile unsigned int cr4 = 0, cr2 = 0, ufcr; + u_int num, denom, baud; + u_int cr2_mask; /* Used to add the changes to CR2 */ + unsigned long flags, per_clk; + int div; + + cr2_mask = ~(MXC_UARTUCR2_IRTS | MXC_UARTUCR2_CTSC | MXC_UARTUCR2_PREN | + MXC_UARTUCR2_PROE | MXC_UARTUCR2_STPB | MXC_UARTUCR2_WS); + + per_clk = clk_get_rate(umxc->clk); + + /* + * Ask the core to get the baudrate, if requested baudrate is not + * between max and min, then either use the baudrate in old termios + * setting. If it's still invalid, we try 9600 baud. + */ + baud = uart_get_baud_rate(&umxc->port, termios, old, 0, per_clk / 16); + /* Set the Reference frequency divider */ + mxcuart_set_ref_freq(umxc, per_clk, baud, &div); + + /* Byte size, default is 8-bit mode */ + switch (termios->c_cflag & CSIZE) { + case CS7: + cr2 = 0; + break; + default: + cr2 = MXC_UARTUCR2_WS; + break; + } + /* Check to see if we need 2 Stop bits */ + if (termios->c_cflag & CSTOPB) { + cr2 |= MXC_UARTUCR2_STPB; + } + + /* Check to see if we need Parity checking */ + if (termios->c_cflag & PARENB) { + cr2 |= MXC_UARTUCR2_PREN; + if (termios->c_cflag & PARODD) { + cr2 |= MXC_UARTUCR2_PROE; + } + } + spin_lock_irqsave(&umxc->port.lock, flags); + + ufcr = readl(umxc->port.membase + MXC_UARTUFCR); + ufcr = (ufcr & (~MXC_UARTUFCR_RFDIV_MASK)) | + ((6 - div) << MXC_UARTUFCR_RFDIV_OFFSET); + writel(ufcr, umxc->port.membase + MXC_UARTUFCR); + + /* + * Update the per-port timeout + */ + uart_update_timeout(&umxc->port, termios->c_cflag, baud); + + umxc->port.read_status_mask = MXC_UARTURXD_OVRRUN; + /* + * Enable appropriate events to be passed to the TTY layer + */ + if (termios->c_iflag & INPCK) { + umxc->port.read_status_mask |= MXC_UARTURXD_FRMERR | + MXC_UARTURXD_PRERR; + } + if (termios->c_iflag & (BRKINT | PARMRK)) { + umxc->port.read_status_mask |= MXC_UARTURXD_BRK; + } + + /* + * Characters to ignore + */ + umxc->port.ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) { + umxc->port.ignore_status_mask |= MXC_UARTURXD_FRMERR | + MXC_UARTURXD_PRERR; + } + if (termios->c_iflag & IGNBRK) { + umxc->port.ignore_status_mask |= MXC_UARTURXD_BRK; + /* + * If we are ignoring parity and break indicators, + * ignore overruns too (for real raw support) + */ + if (termios->c_iflag & IGNPAR) { + umxc->port.ignore_status_mask |= MXC_UARTURXD_OVRRUN; + } + } + + /* + * Ignore all characters if CREAD is not set, still receive characters + * from the port, but throw them away. + */ + if ((termios->c_cflag & CREAD) == 0) { + umxc->port.ignore_status_mask |= UART_CREAD_BIT; + } + + cr4 = readl(umxc->port.membase + MXC_UARTUCR4); + if (UART_ENABLE_MS(port, termios->c_cflag)) { + mxcuart_enable_ms(port); + if (umxc->hardware_flow == 1) { + cr4 = (cr4 & (~MXC_UARTUCR4_CTSTL_MASK)) | + (umxc->cts_threshold << MXC_UARTUCR4_CTSTL_OFFSET); + cr2 |= MXC_UARTUCR2_CTSC; + umxc->port.info->port.tty->hw_stopped = 0; + } else { + cr2 |= MXC_UARTUCR2_IRTS; + } + } else { + cr2 |= MXC_UARTUCR2_IRTS; + } + + /* Add Parity, character length and stop bits information */ + cr2 |= (readl(umxc->port.membase + MXC_UARTUCR2) & cr2_mask); + writel(cr2, umxc->port.membase + MXC_UARTUCR2); + /* + if (umxc->ir_mode == IRDA) { + ret = mxcuart_setir_special(baud); + if (ret == 0) { + cr4 &= ~MXC_UARTUCR4_IRSC; + } else { + cr4 |= MXC_UARTUCR4_IRSC; + } + } */ + writel(cr4, umxc->port.membase + MXC_UARTUCR4); + + /* + * Set baud rate + */ + + /* Use integer scaling, if possible. Limit the denom to 15 bits. */ + num = 0; + denom = (umxc->port.uartclk + 8 * baud) / (16 * baud) - 1; + + /* Use fractional scaling if needed to limit the max error to 0.5% */ + if (denom < 100) { + u64 n64 = (u64) 16 * 0x8000 * baud + (umxc->port.uartclk / 2); + do_div(n64, umxc->port.uartclk); + num = (u_int) n64 - 1; + denom = 0x7fff; + } + writel(num, umxc->port.membase + MXC_UARTUBIR); + writel(denom, umxc->port.membase + MXC_UARTUBMR); + + spin_unlock_irqrestore(&umxc->port.lock, flags); +} + +/*! + * This function is called by the core driver to know the UART type. + * + * @param port the port structure for the UART passed in by the core driver + * + * @return The function returns a pointer to a string describing the UART port. + */ +static const char *mxcuart_type(struct uart_port *port) +{ + return port->type == PORT_IMX ? "Freescale i.MX" : NULL; +} + +/*! + * This function is called by the core driver to release the memory resources + * currently in use by the UART port. + * + * @param port the port structure for the UART passed in by the core driver + */ +static void mxcuart_release_port(struct uart_port *port) +{ + release_mem_region(port->mapbase, SZ_4K); +} + +/*! + * This function is called by the core driver to request memory resources for + * the UART port. + * + * @param port the port structure for the UART passed in by the core driver + * + * @return The function returns \b -EBUSY on failure, else it returns 0. + */ +static int mxcuart_request_port(struct uart_port *port) +{ + return request_mem_region(port->mapbase, SZ_4K, "serial_mxc") + != NULL ? 0 : -EBUSY; +} + +/*! + * This function is called by the core driver to perform any autoconfiguration + * steps required for the UART port. This function sets the port->type field. + * + * @param port the port structure for the UART passed in by the core driver + * @param flags bit mask of the required configuration + */ +static void mxcuart_config_port(struct uart_port *port, int flags) +{ + if ((flags & UART_CONFIG_TYPE) && (mxcuart_request_port(port) == 0)) { + port->type = PORT_IMX; + } +} + +/*! + * This function is called by the core driver to verify that the new serial + * port information contained within \a ser is suitable for this UART port type. + * The function checks to see if the UART port type specified by the user + * application while setting the UART port information matches what is stored + * in the define \b PORT_MXC found in the header file include/linux/serial_core.h + * + * @param port the port structure for the UART passed in by the core driver + * @param ser the new serial port information + * + * @return The function returns 0 on success or \b -EINVAL if the port type + * specified is not equal to \b PORT_MXC. + */ +static int mxcuart_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + int ret = 0; + if (ser->type != PORT_UNKNOWN && ser->type != PORT_IMX) { + ret = -EINVAL; + } + return ret; +} + +/*! + * This function is used to send a high priority XON/XOFF character + * + * @param port the port structure for the UART passed in by the core driver + * @param ch the character to send + */ +static void mxcuart_send_xchar(struct uart_port *port, char ch) +{ + unsigned long flags; + + port->x_char = ch; + if (port->info->port.tty->hw_stopped) { + return; + } + + if (ch) { + spin_lock_irqsave(&port->lock, flags); + port->ops->start_tx(port); + spin_unlock_irqrestore(&port->lock, flags); + } +} + +/*! + * This function is used enable/disable the MXC UART clocks + * + * @param port the port structure for the UART passed in by the core driver + * @param state New PM state + * @param oldstate Current PM state + */ +static void +mxcuart_pm(struct uart_port *port, unsigned int state, unsigned int oldstate) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + + if (state) + clk_disable(umxc->clk); + else + clk_enable(umxc->clk); +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core serial driver to access the UART hardware. The + * structure is passed to serial_core.c file during registration. + */ +static struct uart_ops mxc_ops = { + .tx_empty = mxcuart_tx_empty, + .set_mctrl = mxcuart_set_mctrl, + .get_mctrl = mxcuart_get_mctrl, + .stop_tx = mxcuart_stop_tx, + .start_tx = mxcuart_start_tx, + .stop_rx = mxcuart_stop_rx, + .enable_ms = mxcuart_enable_ms, + .break_ctl = mxcuart_break_ctl, + .startup = mxcuart_startup, + .shutdown = mxcuart_shutdown, + .set_termios = mxcuart_set_termios, + .type = mxcuart_type, + .pm = mxcuart_pm, + .release_port = mxcuart_release_port, + .request_port = mxcuart_request_port, + .config_port = mxcuart_config_port, + .verify_port = mxcuart_verify_port, + .send_xchar = mxcuart_send_xchar, +}; + +#ifdef CONFIG_SERIAL_MXC_CONSOLE + +/* + * Write out a character once the UART is ready + */ +static inline void mxcuart_console_write_char(struct uart_port *port, char ch) +{ + volatile unsigned int status; + + do { + status = readl(port->membase + MXC_UARTUSR1); + } while ((status & MXC_UARTUSR1_TRDY) == 0); + writel(ch, port->membase + MXC_UARTUTXD); +} + +/*! + * This function is called to write the console messages through the UART port. + * + * @param co the console structure + * @param s the log message to be written to the UART + * @param count length of the message + */ +static void mxcuart_console_write(struct console *co, const char *s, + u_int count) +{ + struct uart_port *port = &mxc_ports[co->index]->port; + volatile unsigned int status, oldcr1, oldcr2, oldcr3, cr2, cr3; + int i; + + /* + * First save the control registers and then disable the interrupts + */ + oldcr1 = readl(port->membase + MXC_UARTUCR1); + oldcr2 = readl(port->membase + MXC_UARTUCR2); + oldcr3 = readl(port->membase + MXC_UARTUCR3); + cr2 = + oldcr2 & ~(MXC_UARTUCR2_ATEN | MXC_UARTUCR2_RTSEN | + MXC_UARTUCR2_ESCI); + cr3 = + oldcr3 & ~(MXC_UARTUCR3_DCD | MXC_UARTUCR3_RI | + MXC_UARTUCR3_DTRDEN); + writel(MXC_UARTUCR1_UARTEN, port->membase + MXC_UARTUCR1); + writel(cr2, port->membase + MXC_UARTUCR2); + writel(cr3, port->membase + MXC_UARTUCR3); + /* + * Do each character + */ + for (i = 0; i < count; i++) { + mxcuart_console_write_char(port, s[i]); + if (s[i] == '\n') { + mxcuart_console_write_char(port, '\r'); + } + } + /* + * Finally, wait for the transmitter to become empty + */ + do { + status = readl(port->membase + MXC_UARTUSR2); + } while (!(status & MXC_UARTUSR2_TXDC)); + + /* + * Restore the control registers + */ + writel(oldcr1, port->membase + MXC_UARTUCR1); + writel(oldcr2, port->membase + MXC_UARTUCR2); + writel(oldcr3, port->membase + MXC_UARTUCR3); +} + +/*! + * Initializes the UART port to be used to print console message with the + * options specified. If no options are specified, then the function + * initializes the UART with the default options of baudrate=115200, 8 bit + * word size, no parity, no flow control. + * + * @param co The console structure + * @param options Any console options passed in from the command line + * + * @return The function returns 0 on success or error. + */ +static int __init mxcuart_console_setup(struct console *co, char *options) +{ + uart_mxc_port *umxc; + int baud = 115200; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + volatile unsigned int cr = 0; + + /* + * Check whether an invalid uart number had been specified, and if + * so, search for the first available port that does have console + * support + */ + if (co->index >= MXC_UART_NR) { + co->index = 0; + } + umxc = mxc_ports[co->index]; + + if (umxc == NULL) { + return -ENODEV; + } + + clk_enable(umxc->clk); + + /* initialize port.lock else oops */ + spin_lock_init(&umxc->port.lock); + + /* + * Initialize the UART registers + */ + writel(MXC_UARTUCR1_UARTEN, umxc->port.membase + MXC_UARTUCR1); + /* Enable the transmitter and do a software reset */ + writel(MXC_UARTUCR2_TXEN, umxc->port.membase + MXC_UARTUCR2); + /* Wait till we are out of software reset */ + do { + cr = readl(umxc->port.membase + MXC_UARTUCR2); + } while (!(cr & MXC_UARTUCR2_SRST)); + + writel(0x0, umxc->port.membase + MXC_UARTUCR3); + writel(0x0, umxc->port.membase + MXC_UARTUCR4); + /* Set TXTL to 2, RXTL to 1 and RFDIV to 2 */ + cr = 0x0800 | MXC_UARTUFCR_RFDIV | 0x1; + if (umxc->mode == MODE_DTE) { + cr |= MXC_UARTUFCR_DCEDTE; + } + writel(cr, umxc->port.membase + MXC_UARTUFCR); + writel(0xFFFF, umxc->port.membase + MXC_UARTUSR1); + writel(0xFFFF, umxc->port.membase + MXC_UARTUSR2); + + if (options != NULL) { + uart_parse_options(options, &baud, &parity, &bits, &flow); + } + gpio_uart_active(umxc->port.line, umxc->ir_mode); + return uart_set_options(&umxc->port, co, baud, parity, bits, flow); +} + +static struct uart_driver mxc_reg; + +/*! + * This structure contains the pointers to the UART console functions. It is + * passed as an argument when registering the console. + */ +static struct console mxc_console = { + .name = "ttymxc", + .write = mxcuart_console_write, + .device = uart_console_device, + .setup = mxcuart_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &mxc_reg, +}; + +/*! + * This function registers the console callback functions with the kernel. + */ +static int __init mxcuart_console_init(void) +{ + register_console(&mxc_console); + return 0; +} + +console_initcall(mxcuart_console_init); + +static int __init find_port(struct uart_port *p) +{ + int line; + struct uart_port *port; + for (line = 0; line < MXC_UART_NR; line++) { + if (!mxc_ports[line]) + continue; + port = &mxc_ports[line]->port; + if (uart_match_port(p, port)) + return line; + } + return -ENODEV; +} + +int __init mxc_uart_start_console(struct uart_port *port, char *options) +{ + int line; + line = find_port(port); + if (line < 0) + return -ENODEV; + + add_preferred_console("ttymxc", line, options); + printk("Switching Console to ttymxc%d at %s 0x%lx (options '%s')\n", + line, port->iotype == UPIO_MEM ? "MMIO" : "I/O port", + port->iotype == + UPIO_MEM ? (unsigned long)port->mapbase : (unsigned long)port-> + iobase, options); + + if (!(mxc_console.flags & CON_ENABLED)) { + mxc_console.flags &= ~CON_PRINTBUFFER; + register_console(&mxc_console); + } + return 0; +} + +#define MXC_CONSOLE &mxc_console +#else +#define MXC_CONSOLE NULL +#endif /* CONFIG_SERIAL_MXC_CONSOLE */ + +/*! + * This structure contains the information such as the name of the UART driver + * that appears in the /dev folder, major and minor numbers etc. This structure + * is passed to the serial_core.c file. + */ +static struct uart_driver mxc_reg = { + .owner = THIS_MODULE, + .driver_name = "ttymxc", + .dev_name = "ttymxc", + .major = SERIAL_MXC_MAJOR, + .minor = SERIAL_MXC_MINOR, + .nr = MXC_UART_NR, + .cons = MXC_CONSOLE, +}; + +/*! + * This function is called to put the UART in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which UART + * to suspend + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxcuart_suspend(struct platform_device *pdev, pm_message_t state) +{ + uart_mxc_port *umxc = platform_get_drvdata(pdev); + + if (umxc == NULL) + return 0; /* skip disabled ports */ + + if (umxc->port.info && umxc->port.info->flags & UIF_INITIALIZED) + uart_suspend_port(&mxc_reg, &umxc->port); + + if (umxc->port.info && umxc->port.info->flags & UIF_SUSPENDED) + umxc->port.info->port.tty->hw_stopped = 1; + + return 0; +} + +/*! + * This function is called to bring the UART back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which UART + * to resume + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxcuart_resume(struct platform_device *pdev) +{ + uart_mxc_port *umxc = platform_get_drvdata(pdev); + + if (umxc == NULL) + return 0; /* skip disabled ports */ + + if (umxc->port.info && umxc->port.info->flags & UIF_SUSPENDED) { + umxc->port.info->port.tty->hw_stopped = 0; + uart_resume_port(&mxc_reg, &umxc->port); + } + + return 0; +} + +/*! + * This function is called during the driver binding process. Based on the UART + * that is being probed this function adds the appropriate UART port structure + * in the core driver. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions + * + * @return The function returns 0 if successful; -1 otherwise. + */ +static int mxcuart_probe(struct platform_device *pdev) +{ + int id = pdev->id; + + mxc_ports[id] = pdev->dev.platform_data; + mxc_ports[id]->port.ops = &mxc_ops; + + /* Do not use UARTs that are disabled during integration */ + if (mxc_ports[id]->enabled == 1) { + mxc_ports[id]->port.dev = &pdev->dev; + spin_lock_init(&mxc_ports[id]->port.lock); + /* Enable the low latency flag for DMA UART ports */ + if (mxc_ports[id]->dma_enabled == 1) { + mxc_ports[id]->port.flags |= UPF_LOW_LATENCY; + } + + mxc_ports[id]->clk = clk_get(&pdev->dev, "uart_clk"); + if (mxc_ports[id]->clk == NULL) + return -1; + + uart_add_one_port(&mxc_reg, &mxc_ports[id]->port); + platform_set_drvdata(pdev, mxc_ports[id]); + } + return 0; +} + +/*! + * Dissociates the driver from the UART device. Removes the appropriate UART + * port structure from the core driver. + * + * @param pdev the device structure used to give information on which UART + * to remove + * + * @return The function always returns 0. + */ +static int mxcuart_remove(struct platform_device *pdev) +{ + uart_mxc_port *umxc = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + + if (umxc) { + uart_remove_one_port(&mxc_reg, &umxc->port); + } + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcuart_driver = { + .driver = { + .name = "mxcintuart", + }, + .probe = mxcuart_probe, + .remove = mxcuart_remove, + .suspend = mxcuart_suspend, + .resume = mxcuart_resume, +}; + +/*! + * This function is used to initialize the UART driver module. The function + * registers the power management callback functions with the kernel and also + * registers the UART callback functions with the core serial driver. + * + * @return The function returns 0 on success and a non-zero value on failure. + */ +static int __init mxcuart_init(void) +{ + int ret = 0; + + printk(KERN_INFO "Serial: MXC Internal UART driver\n"); + ret = uart_register_driver(&mxc_reg); + if (ret == 0) { + /* Register the device driver structure. */ + ret = platform_driver_register(&mxcuart_driver); + if (ret != 0) { + uart_unregister_driver(&mxc_reg); + } + } + return ret; +} + +/*! + * This function is used to cleanup all resources before the driver exits. + */ +static void __exit mxcuart_exit(void) +{ + platform_driver_unregister(&mxcuart_driver); + uart_unregister_driver(&mxc_reg); +} + +module_init(mxcuart_init); +module_exit(mxcuart_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC serial port driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/serial/mxc_uart_early.c b/drivers/serial/mxc_uart_early.c new file mode 100644 index 000000000000..0b5ceaef3ec0 --- /dev/null +++ b/drivers/serial/mxc_uart_early.c @@ -0,0 +1,253 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/serial/mxc_uart_early.c + * + * @brief Driver for the Freescale Semiconductor MXC serial ports based on + * drivers/char/8250_early.c, Copyright 2004 Hewlett-Packard Development Company, + * L.P. by Bjorn Helgaasby. + * + * Early serial console for MXC UARTS. + * + * This is for use before the serial driver has initialized, in + * particular, before the UARTs have been discovered and named. + * Instead of specifying the console device as, e.g., "ttymxc0", + * we locate the device directly by its MMIO or I/O port address. + * + * The user can specify the device directly, e.g., + * console=mxcuart,0x43f90000,115200n8 + * or platform code can call early_uart_console_init() to set + * the early UART device. + * + * After the normal serial driver starts, we try to locate the + * matching ttymxc device and start a console there. + */ + +/* + * Include Files + */ + +#include <linux/tty.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/console.h> +#include <linux/serial_core.h> +#include <linux/serial_reg.h> +#include <linux/clk.h> +#include <mach/mxc_uart.h> + +struct mxc_early_uart_device { + struct uart_port port; + char options[16]; /* e.g., 115200n8 */ + unsigned int baud; +}; + +int __init mxc_uart_start_console(struct uart_port *, char *); +static struct mxc_early_uart_device mxc_early_device __initdata; +static int mxc_early_uart_registered __initdata; +static struct clk *clk; + +/* + * Write out a character once the UART is ready + */ +static void __init mxcuart_console_write_char(struct uart_port *port, int ch) +{ + unsigned int status; + + do { + status = readl(port->membase + MXC_UARTUSR1); + } while ((status & MXC_UARTUSR1_TRDY) == 0); + writel(ch, port->membase + MXC_UARTUTXD); +} + +/*! + * This function is called to write the console messages through the UART port. + * + * @param co the console structure + * @param s the log message to be written to the UART + * @param count length of the message + */ +void __init early_mxcuart_console_write(struct console *co, const char *s, + u_int count) +{ + struct uart_port *port = &mxc_early_device.port; + volatile unsigned int status, oldcr1, oldcr2, oldcr3, cr2, cr3; + + /* + * First save the control registers and then disable the interrupts + */ + oldcr1 = readl(port->membase + MXC_UARTUCR1); + oldcr2 = readl(port->membase + MXC_UARTUCR2); + oldcr3 = readl(port->membase + MXC_UARTUCR3); + cr2 = + oldcr2 & ~(MXC_UARTUCR2_ATEN | MXC_UARTUCR2_RTSEN | + MXC_UARTUCR2_ESCI); + cr3 = + oldcr3 & ~(MXC_UARTUCR3_DCD | MXC_UARTUCR3_RI | + MXC_UARTUCR3_DTRDEN); + writel(MXC_UARTUCR1_UARTEN, port->membase + MXC_UARTUCR1); + writel(cr2, port->membase + MXC_UARTUCR2); + writel(cr3, port->membase + MXC_UARTUCR3); + + /* Transmit string */ + uart_console_write(port, s, count, mxcuart_console_write_char); + + /* + * Finally, wait for the transmitter to become empty + */ + do { + status = readl(port->membase + MXC_UARTUSR2); + } while (!(status & MXC_UARTUSR2_TXDC)); + + /* + * Restore the control registers + */ + writel(oldcr1, port->membase + MXC_UARTUCR1); + writel(oldcr2, port->membase + MXC_UARTUCR2); + writel(oldcr3, port->membase + MXC_UARTUCR3); +} + +static unsigned int __init probe_baud(struct uart_port *port) +{ + /* FIXME Return Default Baud Rate */ + return 115200; +} + +static int __init parse_options(struct mxc_early_uart_device *device, + char *options) +{ + struct uart_port *port = &device->port; + int mapsize = 64; + int length; + + if (!options) + return -ENODEV; + + port->uartclk = 5600000; + port->iotype = UPIO_MEM; + port->mapbase = simple_strtoul(options, &options, 0); + port->membase = ioremap(port->mapbase, mapsize); + + if ((options = strchr(options, ','))) { + options++; + device->baud = simple_strtoul(options, NULL, 0); + length = min(strcspn(options, " "), sizeof(device->options)); + strncpy(device->options, options, length); + } else { + device->baud = probe_baud(port); + snprintf(device->options, sizeof(device->options), "%u", + device->baud); + } + printk(KERN_INFO + "MXC_Early serial console at MMIO 0x%x (options '%s')\n", + port->mapbase, device->options); + return 0; +} + +static int __init mxc_early_uart_setup(struct console *console, char *options) +{ + struct mxc_early_uart_device *device = &mxc_early_device; + int err; + if (device->port.membase || device->port.iobase) + return 0; + if ((err = parse_options(device, options)) < 0) + return err; + return 0; +} + +static struct console mxc_early_uart_console __initdata = { + .name = "mxcuart", + .write = early_mxcuart_console_write, + .setup = mxc_early_uart_setup, + .flags = CON_PRINTBUFFER, + .index = -1, +}; + +static int __init mxc_early_uart_console_init(void) +{ + + if (!mxc_early_uart_registered) { + register_console(&mxc_early_uart_console); + mxc_early_uart_registered = 1; + } + + return 0; +} + +int __init mxc_early_serial_console_init(char *cmdline) +{ + char *options; + int err; + int uart_paddr; + + options = strstr(cmdline, "console=mxcuart"); + if (!options) + return -ENODEV; + + /* Extracting MXC UART Uart Port Address from cmdline */ + options = strchr(cmdline, ',') + 1; + uart_paddr = simple_strtoul(options, NULL, 16); + +#ifdef UART1_BASE_ADDR + if (uart_paddr == UART1_BASE_ADDR) + clk = clk_get(NULL, "uart_clk.0"); +#endif +#ifdef UART2_BASE_ADDR + if (uart_paddr == UART2_BASE_ADDR) + clk = clk_get(NULL, "uart_clk.1"); +#endif +#ifdef UART3_BASE_ADDR + if (uart_paddr == UART3_BASE_ADDR) + clk = clk_get(NULL, "uart_clk.2"); +#endif + if (clk == NULL) + return -1; + + /* Enable Early MXC UART Clock */ + clk_enable(clk); + + options = strchr(cmdline, ',') + 1; + if ((err = mxc_early_uart_setup(NULL, options)) < 0) + return err; + return mxc_early_uart_console_init(); +} + +int __init mxc_early_uart_console_switch(void) +{ + struct mxc_early_uart_device *device = &mxc_early_device; + struct uart_port *port = &device->port; + int mmio, line; + + if (!(mxc_early_uart_console.flags & CON_ENABLED)) + return 0; + /* Try to start the normal driver on a matching line. */ + mmio = (port->iotype == UPIO_MEM); + line = mxc_uart_start_console(port, device->options); + + if (line < 0) + printk("No ttymxc device at %s 0x%lx for console\n", + mmio ? "MMIO" : "I/O port", + mmio ? port->mapbase : (unsigned long)port->iobase); + + unregister_console(&mxc_early_uart_console); + if (mmio) + iounmap(port->membase); + + clk_disable(clk); + clk_put(clk); + + return 0; +} + +late_initcall(mxc_early_uart_console_switch); diff --git a/drivers/serial/mxc_uart_reg.h b/drivers/serial/mxc_uart_reg.h new file mode 100644 index 000000000000..c0d1e812fe6a --- /dev/null +++ b/drivers/serial/mxc_uart_reg.h @@ -0,0 +1,128 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MXC_UART_REG_H__ +#define __MXC_UART_REG_H__ + +/* Address offsets of the UART registers */ +#define MXC_UARTURXD 0x000 /* Receive reg */ +#define MXC_UARTUTXD 0x040 /* Transmitter reg */ +#define MXC_UARTUCR1 0x080 /* Control reg 1 */ +#define MXC_UARTUCR2 0x084 /* Control reg 2 */ +#define MXC_UARTUCR3 0x088 /* Control reg 3 */ +#define MXC_UARTUCR4 0x08C /* Control reg 4 */ +#define MXC_UARTUFCR 0x090 /* FIFO control reg */ +#define MXC_UARTUSR1 0x094 /* Status reg 1 */ +#define MXC_UARTUSR2 0x098 /* Status reg 2 */ +#define MXC_UARTUESC 0x09C /* Escape character reg */ +#define MXC_UARTUTIM 0x0A0 /* Escape timer reg */ +#define MXC_UARTUBIR 0x0A4 /* BRM incremental reg */ +#define MXC_UARTUBMR 0x0A8 /* BRM modulator reg */ +#define MXC_UARTUBRC 0x0AC /* Baud rate count reg */ +#define MXC_UARTONEMS 0x0B0 /* One millisecond reg */ +#define MXC_UARTUTS 0x0B4 /* Test reg */ + +/* Bit definations of UCR1 */ +#define MXC_UARTUCR1_ADEN 0x8000 +#define MXC_UARTUCR1_ADBR 0x4000 +#define MXC_UARTUCR1_TRDYEN 0x2000 +#define MXC_UARTUCR1_IDEN 0x1000 +#define MXC_UARTUCR1_RRDYEN 0x0200 +#define MXC_UARTUCR1_RXDMAEN 0x0100 +#define MXC_UARTUCR1_IREN 0x0080 +#define MXC_UARTUCR1_TXMPTYEN 0x0040 +#define MXC_UARTUCR1_RTSDEN 0x0020 +#define MXC_UARTUCR1_SNDBRK 0x0010 +#define MXC_UARTUCR1_TXDMAEN 0x0008 +#define MXC_UARTUCR1_ATDMAEN 0x0004 +#define MXC_UARTUCR1_DOZE 0x0002 +#define MXC_UARTUCR1_UARTEN 0x0001 + +/* Bit definations of UCR2 */ +#define MXC_UARTUCR2_ESCI 0x8000 +#define MXC_UARTUCR2_IRTS 0x4000 +#define MXC_UARTUCR2_CTSC 0x2000 +#define MXC_UARTUCR2_CTS 0x1000 +#define MXC_UARTUCR2_PREN 0x0100 +#define MXC_UARTUCR2_PROE 0x0080 +#define MXC_UARTUCR2_STPB 0x0040 +#define MXC_UARTUCR2_WS 0x0020 +#define MXC_UARTUCR2_RTSEN 0x0010 +#define MXC_UARTUCR2_ATEN 0x0008 +#define MXC_UARTUCR2_TXEN 0x0004 +#define MXC_UARTUCR2_RXEN 0x0002 +#define MXC_UARTUCR2_SRST 0x0001 + +/* Bit definations of UCR3 */ +#define MXC_UARTUCR3_DTREN 0x2000 +#define MXC_UARTUCR3_PARERREN 0x1000 +#define MXC_UARTUCR3_FRAERREN 0x0800 +#define MXC_UARTUCR3_DSR 0x0400 +#define MXC_UARTUCR3_DCD 0x0200 +#define MXC_UARTUCR3_RI 0x0100 +#define MXC_UARTUCR3_RXDSEN 0x0040 +#define MXC_UARTUCR3_AWAKEN 0x0010 +#define MXC_UARTUCR3_DTRDEN 0x0008 +#define MXC_UARTUCR3_RXDMUXSEL 0x0004 +#define MXC_UARTUCR3_INVT 0x0002 + +/* Bit definations of UCR4 */ +#define MXC_UARTUCR4_CTSTL_OFFSET 10 +#define MXC_UARTUCR4_CTSTL_MASK (0x3F << 10) +#define MXC_UARTUCR4_INVR 0x0200 +#define MXC_UARTUCR4_ENIRI 0x0100 +#define MXC_UARTUCR4_REF16 0x0040 +#define MXC_UARTUCR4_IRSC 0x0020 +#define MXC_UARTUCR4_TCEN 0x0008 +#define MXC_UARTUCR4_OREN 0x0002 +#define MXC_UARTUCR4_DREN 0x0001 + +/* Bit definations of UFCR */ +#define MXC_UARTUFCR_RFDIV 0x0200 /* Ref freq div is set to 2 */ +#define MXC_UARTUFCR_RFDIV_OFFSET 7 +#define MXC_UARTUFCR_RFDIV_MASK (0x7 << 7) +#define MXC_UARTUFCR_TXTL_OFFSET 10 +#define MXC_UARTUFCR_DCEDTE 0x0040 + +/* Bit definations of URXD */ +#define MXC_UARTURXD_ERR 0x4000 +#define MXC_UARTURXD_OVRRUN 0x2000 +#define MXC_UARTURXD_FRMERR 0x1000 +#define MXC_UARTURXD_BRK 0x0800 +#define MXC_UARTURXD_PRERR 0x0400 + +/* Bit definations of USR1 */ +#define MXC_UARTUSR1_PARITYERR 0x8000 +#define MXC_UARTUSR1_RTSS 0x4000 +#define MXC_UARTUSR1_TRDY 0x2000 +#define MXC_UARTUSR1_RTSD 0x1000 +#define MXC_UARTUSR1_FRAMERR 0x0400 +#define MXC_UARTUSR1_RRDY 0x0200 +#define MXC_UARTUSR1_AGTIM 0x0100 +#define MXC_UARTUSR1_DTRD 0x0080 +#define MXC_UARTUSR1_AWAKE 0x0010 + +/* Bit definations of USR2 */ +#define MXC_UARTUSR2_TXFE 0x4000 +#define MXC_UARTUSR2_IDLE 0x1000 +#define MXC_UARTUSR2_RIDELT 0x0400 +#define MXC_UARTUSR2_RIIN 0x0200 +#define MXC_UARTUSR2_DCDDELT 0x0040 +#define MXC_UARTUSR2_DCDIN 0x0020 +#define MXC_UARTUSR2_TXDC 0x0008 +#define MXC_UARTUSR2_ORE 0x0002 +#define MXC_UARTUSR2_RDR 0x0001 + +/* Bit definations of UTS */ +#define MXC_UARTUTS_LOOP 0x1000 + +#endif /* __MXC_UART_REG_H__ */ diff --git a/drivers/serial/stmp-app.c b/drivers/serial/stmp-app.c new file mode 100644 index 000000000000..b02d85721d3a --- /dev/null +++ b/drivers/serial/stmp-app.c @@ -0,0 +1,1081 @@ +/* + * Freescale STMP37XX/STMP378X Application UART driver + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/cpufreq.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/uaccess.h> + +#include <asm/cacheflush.h> +#include <mach/hardware.h> +#include <mach/regs-apbx.h> +#include <mach/regs-uartapp.h> +#include <mach/regs-pinctrl.h> +#include <mach/stmp3xxx.h> +#include <mach/platform.h> + +#include <asm/mach-types.h> + +#include "stmp-app.h" + +static int pio_mode /* = 0 */; /* PIO mode = 1, DMA mode = 0 */ + +static struct platform_driver stmp_appuart_driver = { + .probe = stmp_appuart_probe, + .remove = __devexit_p(stmp_appuart_remove), + .suspend = stmp_appuart_suspend, + .resume = stmp_appuart_resume, + .driver = { + .name = "stmp37xx-appuart", + .owner = THIS_MODULE, + }, +}; + +static struct uart_driver stmp_appuart_uart = { + .owner = THIS_MODULE, + .driver_name = "appuart", + .dev_name = "ttySP", + .major = 242, + .minor = 0, + .nr = 1, +}; + +static inline struct stmp_appuart_port *to_appuart(struct uart_port *u) +{ + return container_of(u, struct stmp_appuart_port, port); +} + +static struct uart_ops stmp_appuart_ops = { + .tx_empty = stmp_appuart_tx_empty, + .start_tx = stmp_appuart_start_tx, + .stop_tx = stmp_appuart_stop_tx, + .stop_rx = stmp_appuart_stop_rx, + .enable_ms = stmp_appuart_enable_ms, + .break_ctl = stmp_appuart_break_ctl, + .set_mctrl = stmp_appuart_set_mctrl, + .get_mctrl = stmp_appuart_get_mctrl, + .startup = stmp_appuart_startup, + .shutdown = stmp_appuart_shutdown, + .set_termios = stmp_appuart_settermios, + .type = stmp_appuart_type, + .release_port = stmp_appuart_release_port, + .request_port = stmp_appuart_request_port, + .config_port = stmp_appuart_config_port, + .verify_port = stmp_appuart_verify_port, +}; + +static inline int chr(int c) +{ + if (c < 0x20 || c > 0x7F) + return '#'; + return c; +} + +/* Allocate and initialize rx and tx DMA chains */ +static inline int stmp_appuart_dma_init(struct stmp_appuart_port *s) +{ + int err = 0; + struct stmp3xxx_dma_descriptor *t = &s->tx_desc; +#ifndef RX_CHAIN + struct stmp3xxx_dma_descriptor *r = &s->rx_desc; +#else + int i; +#endif + + err = stmp3xxx_dma_request(s->dma_rx, s->dev, dev_name(s->dev)); + if (err) + goto out; + err = stmp3xxx_dma_request(s->dma_tx, s->dev, dev_name(s->dev)); + if (err) + goto out1; + +#ifndef RX_CHAIN + err = stmp3xxx_dma_allocate_command(s->dma_rx, r); + if (err) + goto out2; +#endif + err = stmp3xxx_dma_allocate_command(s->dma_tx, t); + if (err) + goto out3; + t->virtual_buf_ptr = dma_alloc_coherent(s->dev, + TX_BUFFER_SIZE, + &t->command->buf_ptr, GFP_DMA); + if (!t->virtual_buf_ptr) + goto out4; +#ifdef DEBUG + memset(t->virtual_buf_ptr, 0x4B, TX_BUFFER_SIZE); +#endif + +#ifndef RX_CHAIN + r->virtual_buf_ptr = dma_alloc_coherent(s->dev, + RX_BUFFER_SIZE, + &r->command->buf_ptr, GFP_DMA); + if (!r->virtual_buf_ptr) + goto out5; +#ifdef DEBUG + memset(r->virtual_buf_ptr, 0x4C, RX_BUFFER_SIZE); +#endif +#else + stmp3xxx_dma_make_chain(s->dma_rx, &s->rx_chain, s->rxd, RX_CHAIN); + for (i = 0; i < RX_CHAIN; i++) { + struct stmp3xxx_dma_descriptor *r = s->rxd + i; + + r->command->cmd = + BF(RX_BUFFER_SIZE, APBX_CHn_CMD_XFER_COUNT) | + BF(1, APBX_CHn_CMD_CMDWORDS) | + BM_APBX_CHn_CMD_WAIT4ENDCMD | + BM_APBX_CHn_CMD_SEMAPHORE | + BM_APBX_CHn_CMD_IRQONCMPLT | + BM_APBX_CHn_CMD_CHAIN | + BF_APBX_CHn_CMD_COMMAND(BV_APBX_CHn_CMD_COMMAND__DMA_WRITE); + r->virtual_buf_ptr = dma_alloc_coherent(s->dev, + RX_BUFFER_SIZE, + &r->command->buf_ptr, + GFP_DMA); + r->command->pio_words[0] = /* BM_UARTAPP_CTRL0_RUN | */ + BF(RX_BUFFER_SIZE, UARTAPP_CTRL0_XFER_COUNT) | + BM_UARTAPP_CTRL0_RXTO_ENABLE | + BF(3, UARTAPP_CTRL0_RXTIMEOUT); + } +#endif + return 0; + + /* + * would be necessary on other error paths + + dma_free_coherent( s->dev, RX_BUFFER_SIZE, r->virtual_buf_ptr, + r->command->buf_ptr); + */ +out5: + dma_free_coherent(s->dev, TX_BUFFER_SIZE, t->virtual_buf_ptr, + t->command->buf_ptr); +out4: + stmp3xxx_dma_free_command(s->dma_tx, t); +out3: +#ifndef RX_CHAIN + stmp3xxx_dma_free_command(s->dma_rx, r); +#endif +out2: + stmp3xxx_dma_release(s->dma_tx); +out1: + stmp3xxx_dma_release(s->dma_rx); +out: + WARN_ON(err); + return err; +} + + +static void stmp_appuart_on(struct platform_device *dev) +{ + struct stmp_appuart_port *s = platform_get_drvdata(dev); + + if (!pio_mode) { + /* + Tell DMA to select UART. + Both DMA channels are shared between app UART and IrDA. + Target id of 0 means UART, 1 means IrDA + */ + stmp3xxx_dma_set_alt_target(s->dma_rx, 0); + stmp3xxx_dma_set_alt_target(s->dma_tx, 0); + /* + Reset DMA channels + */ + stmp3xxx_dma_reset_channel(s->dma_rx); + stmp3xxx_dma_reset_channel(s->dma_tx); + stmp3xxx_dma_enable_interrupt(s->dma_rx); + stmp3xxx_dma_enable_interrupt(s->dma_tx); + } +} + +#ifdef CONFIG_CPU_FREQ +static int stmp_appuart_updateclk(struct device *dev, void *clkdata) +{ + struct stmp_appuart_port *s = dev_get_drvdata(dev); + + if (s) { + s->port.uartclk = clk_get_rate(s->clk) * 1000; + /* FIXME: perform actual update */ + } + return 0; +} + +static int stmp_appuart_notifier(struct notifier_block *self, + unsigned long phase, void *p) +{ + int r = 0; + + if ((phase == CPUFREQ_POSTCHANGE) || (phase == CPUFREQ_RESUMECHANGE)) { + /* get new uartclock and setspeed */ + r = driver_for_each_device(&stmp_appuart_driver.driver, + NULL, p, stmp_appuart_updateclk); + } + return (r == 0) ? NOTIFY_OK : NOTIFY_DONE; +} + +static struct notifier_block stmp_appuart_nb = { + .notifier_call = &stmp_appuart_notifier, +}; +#endif /* CONFIG_CPU_FREQ */ + +static int __devinit stmp_appuart_probe(struct platform_device *device) +{ + struct stmp_appuart_port *s; + int err = 0; + struct resource *r; + int i; + u32 version; + int (*pinctl)(int req, int id); + + s = kzalloc(sizeof(struct stmp_appuart_port), GFP_KERNEL); + if (!s) { + err = -ENOMEM; + goto out; + } + + spin_lock_init(&s->lock); + + s->clk = clk_get(NULL, "uart"); + if (IS_ERR(s->clk)) { + err = PTR_ERR(s->clk); + goto out_free; + } + clk_enable(s->clk); + r = platform_get_resource(device, IORESOURCE_MEM, 0); + if (!r) { + err = -ENXIO; + goto out_free_clk; + } + s->port.mapbase = r->start; + s->port.irq = platform_get_irq(device, 0); + s->port.ops = &stmp_appuart_ops; + s->port.iotype = UPIO_MEM; + s->port.line = device->id < 0 ? 0 : device->id; + s->port.fifosize = 16; + s->port.timeout = HZ/10; + s->port.uartclk = clk_get_rate(s->clk) * 1000; + s->port.type = PORT_IMX; + s->port.dev = s->dev = get_device(&device->dev); + s->ctrl = 0; + s->keep_irq = 0; + + r = platform_get_resource(device, IORESOURCE_MEM, 0); + if (!r) { + err = -ENXIO; + goto out_free_clk; + } + + dev_dbg(s->dev, "%s\n", __func__); + for (i = 0; i < ARRAY_SIZE(s->irq); i++) { + s->irq[i] = platform_get_irq(device, i); + dev_dbg(s->dev, "Resources: irq[%d] = %d\n", i, s->irq[i]); + if (s->irq[i] < 0) { + err = s->irq[i]; + goto out_free_clk; + } + } + + r = platform_get_resource(device, IORESOURCE_DMA, 0); + if (!r) { + err = -ENXIO; + goto out_free; + } + s->dma_rx = r->start; + + r = platform_get_resource(device, IORESOURCE_DMA, 1); + if (!r) { + err = -ENXIO; + goto out_free; + } + s->dma_tx = r->start; + + r = platform_get_resource(device, IORESOURCE_MEM, 0); + if (!r) { + err = -ENXIO; + goto out_free; + } + s->mem = (void __iomem *)(r->start - STMP3XXX_REGS_PHBASE + + (u32)STMP3XXX_REGS_BASE); + s->memsize = r->end - r->start; + +#ifdef CONFIG_CPU_FREQ + cpufreq_register_notifier(&stmp_appuart_nb, + CPUFREQ_TRANSITION_NOTIFIER); +#endif + platform_set_drvdata(device, s); + + device_init_wakeup(&device->dev, 1); + + stmp_appuart_dma_init(s); + stmp_appuart_on(device); + + pinctl = device->dev.platform_data; + if (pinctl) { + err = pinctl(1, device->id); + if (err) + goto out_free_clk; + } + + err = uart_add_one_port(&stmp_appuart_uart, &s->port); + if (err) + goto out_free_pins; + + version = __raw_readl(REGS_UARTAPP1_BASE + HW_UARTAPP_VERSION); + printk(KERN_INFO "Found APPUART %d.%d.%d\n", + (version >> 24) & 0xFF, + (version >> 16) & 0xFF, version & 0xFFFF); + return 0; + +out_free_pins: + if (pinctl) + pinctl(0, device->id); +out_free_clk: + clk_put(s->clk); +out_free: + platform_set_drvdata(device, NULL); + kfree(s); +out: + return err; +} + +static int __devexit stmp_appuart_remove(struct platform_device *device) +{ + struct stmp_appuart_port *s; + void (*pinctl)(int req, int id); + + s = platform_get_drvdata(device); + if (s) { + pinctl = device->dev.platform_data; + put_device(s->dev); + clk_disable(s->clk); + clk_put(s->clk); + uart_remove_one_port(&stmp_appuart_uart, &s->port); + if (pinctl) + pinctl(0, device->id); + kfree(s); + platform_set_drvdata(device, NULL); + } + + return 0; +} + +static int stmp_appuart_suspend(struct platform_device *device, + pm_message_t state) +{ +#ifdef CONFIG_PM + struct stmp_appuart_port *s = platform_get_drvdata(device); + + if (!s) + return 0; + s->keep_irq = device_may_wakeup(&device->dev); + uart_suspend_port(&stmp_appuart_uart, &s->port); + if (!s->keep_irq) + clk_disable(s->clk); +#endif + return 0; +} + +static int stmp_appuart_resume(struct platform_device *device) +{ +#ifdef CONFIG_PM + struct stmp_appuart_port *s = platform_get_drvdata(device); + + if (!s) + return 0; + + if (!s->keep_irq) + clk_enable(s->clk); + stmp_appuart_on(device); + uart_resume_port(&stmp_appuart_uart, &s->port); + s->keep_irq = 0; +#endif + return 0; +} + +static int __init stmp_appuart_init() +{ + int r; + + r = uart_register_driver(&stmp_appuart_uart); + if (r) + goto out; + r = platform_driver_register(&stmp_appuart_driver); + if (r) + goto out_err; + return 0; +out_err: + uart_unregister_driver(&stmp_appuart_uart); +out: + return r; +} + +static void __exit stmp_appuart_exit() +{ + platform_driver_unregister(&stmp_appuart_driver); + uart_unregister_driver(&stmp_appuart_uart); +} + +module_init(stmp_appuart_init) +module_exit(stmp_appuart_exit) + +static void stmp_appuart_stop_rx(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + + dev_dbg(s->dev, "%s\n", __func__); + stmp3xxx_clearl(BM_UARTAPP_CTRL2_RXE, s->mem); +} + +static void stmp_appuart_break_ctl(struct uart_port *u, int ctl) +{ + struct stmp_appuart_port *s = to_appuart(u); + + dev_dbg(s->dev, "%s: break = %s\n", __func__, ctl ? "on" : "off"); + if (ctl) + stmp3xxx_setl(BM_UARTAPP_LINECTRL_BRK, + s->mem + HW_UARTAPP_LINECTRL); + else + stmp3xxx_clearl(BM_UARTAPP_LINECTRL_BRK, + s->mem + HW_UARTAPP_LINECTRL); +} + +static void stmp_appuart_enable_ms(struct uart_port *port) +{ + /* just empty */ +} + +static void stmp_appuart_set_mctrl(struct uart_port *u, unsigned mctrl) +{ + struct stmp_appuart_port *s = to_appuart(u); + + u32 ctrl = __raw_readl(s->mem + HW_UARTAPP_CTRL2); + + dev_dbg(s->dev, "%s (%x)\n", __func__, mctrl); + ctrl &= ~BM_UARTAPP_CTRL2_RTS; + if (mctrl & TIOCM_RTS) { + dev_dbg(s->dev, "...RTS\n"); + ctrl |= BM_UARTAPP_CTRL2_RTS; + } + s->ctrl = mctrl; + dev_dbg(s->dev, "...%x; ctrl = %x\n", s->ctrl, ctrl); + __raw_writel(ctrl, s->mem + HW_UARTAPP_CTRL2); +} + +static u32 stmp_appuart_get_mctrl(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + u32 stat = __raw_readl(s->mem + HW_UARTAPP_STAT); + int ctrl2 = __raw_readl(s->mem + HW_UARTAPP_CTRL2); + u32 mctrl = s->ctrl; + + dev_dbg(s->dev, "%s:\n", __func__); + mctrl &= ~TIOCM_CTS; + if (stat & BM_UARTAPP_STAT_CTS) { + dev_dbg(s->dev, "CTS"); + mctrl |= TIOCM_CTS; + } + if (ctrl2 & BM_UARTAPP_CTRL2_RTS) { + dev_dbg(s->dev, "RTS"); + mctrl |= TIOCM_RTS; + } + dev_dbg(s->dev, "...%x\n", mctrl); + return mctrl; +} + +static int stmp_appuart_request_port(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + int err = 0; + + if (!request_mem_region((u32)s->mem, s->memsize, dev_name(s->dev))) + err = -ENXIO; + return err; + +} + +static void stmp_appuart_release_port(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + + release_mem_region((u32)s->mem, s->memsize); +} + +static int stmp_appuart_verify_port(struct uart_port *u, + struct serial_struct *ser) +{ + struct stmp_appuart_port *s = to_appuart(u); + + dev_dbg(s->dev, "%s\n", __func__); + return 0; +} + +static void stmp_appuart_config_port(struct uart_port *u, int flags) +{ + struct stmp_appuart_port *s = to_appuart(u); + + dev_dbg(s->dev, "%s\n", __func__); +} + +static const char *stmp_appuart_type(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + + dev_dbg(s->dev, "%s\n", __func__); + return dev_name(s->dev); +} + +static void stmp_appuart_settermios(struct uart_port *u, + struct ktermios *nw, struct ktermios *old) +{ + static struct ktermios saved; + struct stmp_appuart_port *s = to_appuart(u); + unsigned int cflag; + u32 bm, ctrl, ctrl2, div; + int err = 0; + unsigned baud; + + dev_dbg(s->dev, "%s\n", __func__); + + if (nw) + memcpy(&saved, nw, sizeof *nw); + else + nw = old = &saved; + + cflag = nw->c_cflag; + + ctrl = BM_UARTAPP_LINECTRL_FEN; + ctrl2 = __raw_readl(s->mem + HW_UARTAPP_CTRL2); + + /* byte size */ + switch (cflag & CSIZE) { + case CS5: + bm = 0; + break; + case CS6: + bm = 1; + break; + case CS7: + bm = 2; + break; + case CS8: + bm = 3; + break; + default: + err = -EINVAL; + break; + } + if (err) + goto out; + + dev_dbg(s->dev, "Byte size %d bytes, mask %x\n", + bm + 5, BF(bm, UARTAPP_LINECTRL_WLEN)); + ctrl |= BF(bm, UARTAPP_LINECTRL_WLEN); + + /* parity */ + if (cflag & PARENB) { + dev_dbg(s->dev, "Parity check enabled\n"); + ctrl |= BM_UARTAPP_LINECTRL_PEN | BM_UARTAPP_LINECTRL_SPS; + if ((cflag & PARODD) == 0) { + dev_dbg(s->dev, "(Even) mask = %x\n", + BM_UARTAPP_LINECTRL_PEN | + BM_UARTAPP_LINECTRL_SPS | + BM_UARTAPP_LINECTRL_EPS); + ctrl |= BM_UARTAPP_LINECTRL_EPS; + } else + dev_dbg(s->dev, "(Odd) mask = %x\n", + BM_UARTAPP_LINECTRL_PEN | + BM_UARTAPP_LINECTRL_SPS); + } else + dev_dbg(s->dev, "Parity check disabled.\n"); + + /* figure out the stop bits requested */ + if (cflag & CSTOPB) { + dev_dbg(s->dev, "Stop bits, mask = %x\n", + BM_UARTAPP_LINECTRL_STP2); + ctrl |= BM_UARTAPP_LINECTRL_STP2; + } else + dev_dbg(s->dev, "No stop bits\n"); + + /* figure out the hardware flow control settings */ + if (cflag & CRTSCTS) { + dev_dbg(s->dev, "RTS/CTS flow control\n"); + ctrl2 |= BM_UARTAPP_CTRL2_CTSEN /* | BM_UARTAPP_CTRL2_RTSEN */ ; + } else { + dev_dbg(s->dev, "RTS/CTS disabled\n"); + ctrl2 &= ~BM_UARTAPP_CTRL2_CTSEN; + } + + /* set baud rate */ + baud = uart_get_baud_rate(u, nw, old, 0, u->uartclk); + dev_dbg(s->dev, "Baud rate requested: %d (clk = %d)\n", + baud, u->uartclk); + div = u->uartclk * 32 / baud; + ctrl |= BF(div & 0x3F, UARTAPP_LINECTRL_BAUD_DIVFRAC); + ctrl |= BF(div >> 6, UARTAPP_LINECTRL_BAUD_DIVINT); + + if ((cflag & CREAD) != 0) { + dev_dbg(s->dev, "RX started\n"); + ctrl2 |= BM_UARTAPP_CTRL2_RXE | BM_UARTAPP_CTRL2_RXDMAE; + } + + if (!err) { + dev_dbg(s->dev, "CTRLS = %x + %x\n", ctrl, ctrl2); + __raw_writel(ctrl, + s->mem + HW_UARTAPP_LINECTRL); + __raw_writel(ctrl2, + s->mem + HW_UARTAPP_CTRL2); + } +out: + return /* err */ ; +} + +static int stmp_appuart_free_irqs(struct stmp_appuart_port *s) +{ + int irqn = 0; + + if (s->keep_irq) { + dev_dbg(s->dev, "keep_irq != 0, ignoring\n"); + return 0; + } + for (irqn = 0; irqn < ARRAY_SIZE(s->irq); irqn++) + free_irq(s->irq[irqn], s); + return 0; +} + +void stmp_appuart_rx(struct stmp_appuart_port *s, u8 * rx_buffer, int count) +{ + u8 c; + int flag; + struct tty_struct *tty = s->port.info->port.tty; + u32 stat; + + spin_lock(&s->lock); + stat = __raw_readl(s->mem + HW_UARTAPP_STAT); + + if (count < 0) { + count = + __raw_readl(s->mem + + HW_UARTAPP_STAT) & BM_UARTAPP_STAT_RXCOUNT; + dev_dbg(s->dev, "count = %d\n", count); + } + + for (;;) { + if (!rx_buffer) { + if (stat & BM_UARTAPP_STAT_RXFE) + break; + c = __raw_readl(s->mem + HW_UARTAPP_DATA) & 0xFF; + } else { + if (count-- <= 0) + break; + c = *rx_buffer++; + dev_dbg(s->dev, "Received: %x(%c)\n", c, chr(c)); + } + + flag = TTY_NORMAL; + if (stat & BM_UARTAPP_STAT_BERR) { + stat &= ~BM_UARTAPP_STAT_BERR; + s->port.icount.brk++; + if (uart_handle_break(&s->port)) + goto ignore; + flag = TTY_BREAK; + } else if (stat & BM_UARTAPP_STAT_PERR) { + stat &= ~BM_UARTAPP_STAT_PERR; + s->port.icount.parity++; + flag = TTY_PARITY; + } else if (stat & BM_UARTAPP_STAT_FERR) { + stat &= ~BM_UARTAPP_STAT_FERR; + s->port.icount.frame++; + flag = TTY_FRAME; + } + + if (stat & BM_UARTAPP_STAT_OERR) + s->port.icount.overrun++; + + if (uart_handle_sysrq_char(&s->port, c)) + goto ignore; + + uart_insert_char(&s->port, stat, BM_UARTAPP_STAT_OERR, c, flag); +ignore: + if (pio_mode) { + __raw_writel(stat, s->mem + HW_UARTAPP_STAT); + stat = + __raw_readl(s->mem + HW_UARTAPP_STAT); + } + } + + __raw_writel(stat, s->mem + HW_UARTAPP_STAT); + tty_flip_buffer_push(tty); + spin_unlock(&s->lock); +} + +static inline void stmp_appuart_submit_rx(struct stmp_appuart_port *s) +{ +#ifndef RX_CHAIN + struct stmp3xxx_dma_descriptor *r = &s->rx_desc; + + dev_dbg(s->dev, "Submitting RX DMA request\n"); + r->command->cmd = + BM_APBX_CHn_CMD_HALTONTERMINATE | + BF(RX_BUFFER_SIZE, APBX_CHn_CMD_XFER_COUNT) | + BF(1, APBX_CHn_CMD_CMDWORDS) | + BM_APBX_CHn_CMD_WAIT4ENDCMD | + BM_APBX_CHn_CMD_SEMAPHORE | + BM_APBX_CHn_CMD_IRQONCMPLT | + BF(BV_APBX_CHn_CMD_COMMAND__DMA_WRITE, APBX_CHn_CMD_COMMAND); + r->command->pio_words[0] = + __raw_readl(REGS_UARTAPP1_BASE + + HW_UARTAPP_CTRL0) | BF(RX_BUFFER_SIZE, + UARTAPP_CTRL0_XFER_COUNT) | + BM_UARTAPP_CTRL0_RXTO_ENABLE | BF(3, UARTAPP_CTRL0_RXTIMEOUT); + r->command->pio_words[0] &= ~BM_UARTAPP_CTRL0_RUN; + + stmp3xxx_dma_reset_channel(s->dma_rx); + stmp3xxx_dma_go(s->dma_rx, r, 1); +#endif +} + +static irqreturn_t stmp_appuart_irq_int(int irq, void *context) +{ + u32 istatus; + struct stmp_appuart_port *s = context; + u32 stat = __raw_readl(s->mem + HW_UARTAPP_STAT); + + istatus = __raw_readl(s->mem + HW_UARTAPP_INTR); + dev_dbg(s->dev, "IRQ: int(%d), status = %08X\n", irq, istatus); + + if (istatus & BM_UARTAPP_INTR_CTSMIS) { + uart_handle_cts_change(&s->port, stat & BM_UARTAPP_STAT_CTS); + dev_dbg(s->dev, "CTS change: %x\n", stat & BM_UARTAPP_STAT_CTS); + stmp3xxx_clearl(BM_UARTAPP_INTR_CTSMIS, + s->mem + HW_UARTAPP_INTR); + } + + else if (istatus & BM_UARTAPP_INTR_RTIS) { + dev_dbg(s->dev, "RX timeout, draining out\n"); + stmp_appuart_submit_rx(s); + } + + else + dev_info(s->dev, "Unhandled status %x\n", istatus); + + stmp3xxx_clearl(istatus & 0xFFFF, + s->mem + HW_UARTAPP_INTR); + + return IRQ_HANDLED; +} + +static irqreturn_t stmp_appuart_irq_rx(int irq, void *context) +{ + struct stmp_appuart_port *s = context; + int count = -1; + + stmp3xxx_dma_clear_interrupt(s->dma_rx); + dev_dbg(s->dev, "%s(%d), count = %d\n", __func__, irq, count); + +#ifndef RX_CHAIN + stmp_appuart_rx(s, s->rx_desc.virtual_buf_ptr, count); + stmp_appuart_submit_rx(s); +#else + if (circ_advance_cooked(&s->rx_chain) == 0) { + BUG(); + return IRQ_HANDLED; + } + + circ_advance_active(&s->rx_chain, 1); + while (s->rx_chain.cooked_count) { + stmp_appuart_rx(s, + stmp3xxx_dma_circ_get_cooked_head(&s-> + rx_chain)->virtual_buf_ptr, + -1); + circ_advance_free(&s->rx_chain, 1); + } +#endif + return IRQ_HANDLED; +} + +static void stmp_appuart_submit_tx(struct stmp_appuart_port *s, int size) +{ + struct stmp3xxx_dma_descriptor *d = &s->tx_desc; + + dev_dbg(s->dev, "Submitting TX DMA request, %d bytes\n", size); + d->command->pio_words[0] = + /* BM_UARTAPP_CTRL1_RUN | */ BF(size, UARTAPP_CTRL1_XFER_COUNT); + d->command->cmd = BF(size, APBX_CHn_CMD_XFER_COUNT) | + BF(1, APBX_CHn_CMD_CMDWORDS) | + BM_APBX_CHn_CMD_WAIT4ENDCMD | + BM_APBX_CHn_CMD_SEMAPHORE | + BM_APBX_CHn_CMD_IRQONCMPLT | + BF(BV_APBX_CHn_CMD_COMMAND__DMA_READ, APBX_CHn_CMD_COMMAND); + stmp3xxx_dma_go(s->dma_tx, d, 1); +} + +static irqreturn_t stmp_appuart_irq_tx(int irq, void *context) +{ + struct stmp_appuart_port *s = context; + struct uart_port *u = &s->port; + int bytes; + + stmp3xxx_dma_clear_interrupt(s->dma_tx); + dev_dbg(s->dev, "%s(%d)\n", __func__, irq); + + bytes = stmp_appuart_copy_tx(u, s->tx_desc.virtual_buf_ptr, + TX_BUFFER_SIZE); + if (bytes > 0) { + dev_dbg(s->dev, "Sending %d bytes\n", bytes); + stmp_appuart_submit_tx(s, bytes); + } + return IRQ_HANDLED; +} + +static int stmp_appuart_request_irqs(struct stmp_appuart_port *s) +{ + int err = 0; + + /* + * order counts. resources should be listed in the same order + */ + irq_handler_t handlers[] = { + stmp_appuart_irq_int, + stmp_appuart_irq_rx, + stmp_appuart_irq_tx, + }; + char *handlers_names[] = { + "appuart internal", + "appuart rx", + "appuart tx", + }; + int irqn; + + if (s->keep_irq) { + dev_dbg(s->dev, "keep_irq is set, skipping request_irq"); + return 0; + } + for (irqn = 0; irqn < ARRAY_SIZE(handlers); irqn++) { + err = request_irq(s->irq[irqn], handlers[irqn], + 0, handlers_names[irqn], s); + dev_dbg(s->dev, "Requested IRQ %d with status %d\n", + s->irq[irqn], err); + if (err) + goto out; + } + return 0; +out: + stmp_appuart_free_irqs(s); + return err; +} + +static struct timer_list timer_task; + +static void stmp_appuart_check_rx(unsigned long data) +{ + stmp_appuart_rx((struct stmp_appuart_port *)data, NULL, -1); + mod_timer(&timer_task, jiffies + 2 * HZ); +} + +static int stmp_appuart_startup(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + int err; + + dev_dbg(s->dev, "%s\n", __func__); + + s->tx_buffer_index = 0; + + err = stmp_appuart_request_irqs(s); + if (err) + goto out; + + if (!s->keep_irq) + /* Release the block from reset and start the clocks. */ + stmp3xxx_reset_block(s->mem, 0); + + stmp3xxx_setl(BM_UARTAPP_CTRL2_UARTEN, + s->mem + HW_UARTAPP_CTRL2); + /* Enable the Application UART DMA bits. */ + if (!pio_mode) { + stmp3xxx_setl(BM_UARTAPP_CTRL2_TXDMAE | BM_UARTAPP_CTRL2_RXDMAE + | BM_UARTAPP_CTRL2_DMAONERR, + s->mem + HW_UARTAPP_CTRL2); + /* clear any pending interrupts */ + __raw_writel(0, s->mem + HW_UARTAPP_INTR); + + /* reset all dma channels */ + stmp3xxx_dma_reset_channel(s->dma_tx); + stmp3xxx_dma_reset_channel(s->dma_rx); + } else { + __raw_writel(BM_UARTAPP_INTR_RXIEN | + BM_UARTAPP_INTR_RTIEN, + s->mem + HW_UARTAPP_INTR); + } + stmp3xxx_setl(BM_UARTAPP_INTR_CTSMIEN, + s->mem + HW_UARTAPP_INTR); + + /* + * Enable fifo so all four bytes of a DMA word are written to + * output (otherwise, only the LSB is written, ie. 1 in 4 bytes) + */ + stmp3xxx_setl(BM_UARTAPP_LINECTRL_FEN, s->mem + HW_UARTAPP_LINECTRL); + + if (!pio_mode) { +#ifndef RX_CHAIN + stmp_appuart_submit_rx(s); +#else + circ_clear_chain(&s->rx_chain); + stmp3xxx_dma_go(s->dma_rx, &s->rxd[0], 0); + circ_advance_active(&s->rx_chain, 1); +#endif + } else { + init_timer(&timer_task); + timer_task.function = stmp_appuart_check_rx; + timer_task.expires = jiffies + HZ; + timer_task.data = (unsigned long)s; + add_timer(&timer_task); + } + +out: + return err; +} + +static void stmp_appuart_shutdown(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + + dev_dbg(s->dev, "%s\n", __func__); + + if (!s->keep_irq) + /* set the IP block to RESET; this should disable clock too. */ + stmp3xxx_setl( + BM_UARTAPP_CTRL0_SFTRST, s->mem + HW_UARTAPP_CTRL0); + + if (!pio_mode) { + /* reset all dma channels */ + stmp3xxx_dma_reset_channel(s->dma_tx); + stmp3xxx_dma_reset_channel(s->dma_rx); + } else { + del_timer(&timer_task); + } + stmp_appuart_free_irqs(s); +} + +static unsigned int stmp_appuart_tx_empty(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + + if (pio_mode) + if (__raw_readl(s->mem + HW_UARTAPP_STAT) & + BM_UARTAPP_STAT_TXFE) + return TIOCSER_TEMT; + else + return 0; + else + return stmp3xxx_dma_running(s->dma_tx) ? 0 : TIOCSER_TEMT; +} + +static void stmp_appuart_start_tx(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + int bytes; + + dev_dbg(s->dev, "%s\n", __func__); + + /* enable transmitter */ + stmp3xxx_setl(BM_UARTAPP_CTRL2_TXE, s->mem + HW_UARTAPP_CTRL2); + + if (!pio_mode) { + if (stmp3xxx_dma_running(s->dma_tx)) + return; + bytes = stmp_appuart_copy_tx(u, s->tx_desc.virtual_buf_ptr, + TX_BUFFER_SIZE); + if (bytes <= 0) + return; + + dev_dbg(s->dev, "Started DMA transfer with descriptor %p, " + "command %p, %d bytes long\n", + &s->tx_desc, s->tx_desc.command, bytes); + stmp_appuart_submit_tx(s, bytes); + } else { + int count = 0; + u8 c; + + while (! + (__raw_readl + (s->mem + HW_UARTAPP_STAT) & BM_UARTAPP_STAT_TXFF)) { + if (stmp_appuart_copy_tx(u, &c, 1) <= 0) + break; + dev_dbg(s->dev, "%d: '%c'/%x\n", ++count, chr(c), c); + __raw_writel(c, s->mem + HW_UARTAPP_DATA); + } + } +} + +static void stmp_appuart_stop_tx(struct uart_port *u) +{ + struct stmp_appuart_port *s = to_appuart(u); + + dev_dbg(s->dev, "%s\n", __func__); + stmp3xxx_clearl(BM_UARTAPP_CTRL2_TXE, s->mem + HW_UARTAPP_CTRL2); +} + +static int stmp_appuart_copy_tx(struct uart_port *u, u8 * target, + int tx_buffer_size) +{ + int last = 0, portion; + struct circ_buf *xmit = &u->info->xmit; + + while (last < tx_buffer_size) { /* let's fill the only descriptor */ + if (u->x_char) { + target[last++] = u->x_char; + u->x_char = 0; + } else if (!uart_circ_empty(xmit) && !uart_tx_stopped(u)) { + portion = min((u32) tx_buffer_size, + (u32) uart_circ_chars_pending(xmit)); + portion = min((u32) portion, + (u32) CIRC_CNT_TO_END(xmit->head, + xmit->tail, + UART_XMIT_SIZE)); + memcpy(target + last, &xmit->buf[xmit->tail], portion); + xmit->tail = (xmit->tail + portion) & + (UART_XMIT_SIZE - 1); + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(u); + last += portion; + } else { /* All tx data copied into buffer */ + return last; + } + } + return last; +} + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("stmp3xxx app uart driver"); +MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>"); +module_param(pio_mode, int, 0); diff --git a/drivers/serial/stmp-app.h b/drivers/serial/stmp-app.h new file mode 100644 index 000000000000..938c89bc5214 --- /dev/null +++ b/drivers/serial/stmp-app.h @@ -0,0 +1,82 @@ +/* + * Freescale STMP37XX/STMP378X Application UART driver + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __STMP_APPUART_H +#define __STMP_APPUART_H + +#define RX_BUFFER_SIZE 4 +#define TX_BUFFER_SIZE 0xFFF0 + +#include <mach/dma.h> + +/* #define RX_CHAIN 2 */ + +struct stmp_appuart_port { + int keep_irq; + int irq[3]; + void __iomem *mem; + u32 memsize; + int dma_rx, dma_tx; + struct clk *clk; + struct device *dev; + struct uart_port port; + unsigned tx_buffer_index; + struct stmp3xxx_dma_descriptor tx_desc; +#ifndef RX_CHAIN + struct stmp3xxx_dma_descriptor rx_desc; +#else + struct stmp3xxx_dma_descriptor rxd[RX_CHAIN]; + struct stmp37xx_circ_dma_chain rx_chain; +#endif + + u32 ctrl; + u8 running; + spinlock_t lock; /* protects irq handler */ +}; + +#ifdef CONFIG_CPU_FREQ +static int stmp_appuart_updateclk(struct device *dev, void *clkdata); +static int stmp_appuart_notifier(struct notifier_block *self, + unsigned long phase, void *p); +#endif /* CONFIG_CPU_FREQ */ +static int __init stmp_appuart_probe(struct platform_device *device); +static int stmp_appuart_remove(struct platform_device *device); +static int stmp_appuart_suspend(struct platform_device *device, + pm_message_t state); +static int stmp_appuart_resume(struct platform_device *device); +static int __init stmp_appuart_init(void); +static void __exit stmp_appuart_exit(void); +static int stmp_appuart_request_port(struct uart_port *u); +static void stmp_appuart_release_port(struct uart_port *u); +static int stmp_appuart_verify_port(struct uart_port *u, + struct serial_struct *); +static void stmp_appuart_config_port(struct uart_port *u, int flags); +static const char *stmp_appuart_type(struct uart_port *u); +static void stmp_appuart_settermios(struct uart_port *u, + struct ktermios *nw, struct ktermios *old); +static void stmp_appuart_shutdown(struct uart_port *u); +static int stmp_appuart_startup(struct uart_port *u); +static u32 stmp_appuart_get_mctrl(struct uart_port *u); +static void stmp_appuart_set_mctrl(struct uart_port *u, unsigned mctrl); +static void stmp_appuart_enable_ms(struct uart_port *port); +static void stmp_appuart_break_ctl(struct uart_port *port, int ctl); +static unsigned int stmp_appuart_tx_empty(struct uart_port *u); +static void stmp_appuart_stop_rx(struct uart_port *u); +static void stmp_appuart_start_tx(struct uart_port *u); +static void stmp_appuart_stop_tx(struct uart_port *u); +static int stmp_appuart_copy_tx(struct uart_port *u, u8 *target, int size); +#endif diff --git a/drivers/serial/stmp-dbg.c b/drivers/serial/stmp-dbg.c new file mode 100644 index 000000000000..c8c21b57281e --- /dev/null +++ b/drivers/serial/stmp-dbg.c @@ -0,0 +1,840 @@ +/* + * Freescale STMP37XX/STMP378X Debug UART driver + * + * Based on drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o. + * + * Copyright 1999 ARM Limited + * Copyright (C) 2000 Deep Blue Solutions Ltd. + * Modifications for STMP36XX Debug Serial (c) 2005 Sigmatel Inc + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/autoconf.h> + +#if defined(CONFIG_SERIAL_STMP_DBG_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) +#define SUPPORT_SYSRQ +#endif + +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/sysrq.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/serial_core.h> +#include <linux/serial.h> +#include <linux/io.h> +#include <linux/irq.h> + +#include <mach/regs-uartdbg.h> + +#include <mach/stmp3xxx.h> +#include <mach/platform.h> + +#include "stmp-dbg.h" + +/* treated as variable unless submitted to open-source */ +#define PORT_STMPDBG 100 +#define UART_NR 1 +#define SERIAL_STMPDBG_MAJOR 204 +#define SERIAL_STMPDBG_MINOR 16 + +#define ISR_PASS_LIMIT 256 + +#define STMPDBG_DEVID "Debug UART" + + +static int force_cd = 1; + +static struct uart_driver stmpdbg_reg; + +/* + * We wrap our port structure around the generic uart_port. + */ +struct uart_stmpdbg_port { + struct uart_port port; + struct clk *clk; + unsigned int im; /* interrupt mask */ + unsigned int old_status; + int suspended; +}; + + +static void stmpdbg_stop_tx(struct uart_port *port) +{ + struct uart_stmpdbg_port *uap = (struct uart_stmpdbg_port *)port; + + uap->im &= ~UART011_TXIM; + __raw_writel(uap->im, uap->port.membase + UART011_IMSC); +} + +static void stmpdbg_start_tx(struct uart_port *port) +{ + struct uart_stmpdbg_port *uap = (struct uart_stmpdbg_port *)port; + + uap->im |= UART011_TXIM; + __raw_writel(uap->im, uap->port.membase + UART011_IMSC); +} + +static void stmpdbg_stop_rx(struct uart_port *port) +{ + struct uart_stmpdbg_port *uap = (struct uart_stmpdbg_port *)port; + + uap->im &= ~(UART011_RXIM|UART011_RTIM|UART011_FEIM| + UART011_PEIM|UART011_BEIM|UART011_OEIM); + __raw_writel(uap->im, uap->port.membase + UART011_IMSC); +} + +static void stmpdbg_enable_ms(struct uart_port *port) +{ + struct uart_stmpdbg_port *uap = (struct uart_stmpdbg_port *)port; + + uap->im |= UART011_RIMIM|UART011_CTSMIM|UART011_DCDMIM|UART011_DSRMIM; + __raw_writel(uap->im, uap->port.membase + UART011_IMSC); +} + +static void stmpdbg_rx_chars(struct uart_stmpdbg_port *uap) +{ + struct tty_struct *tty = uap->port.info->port.tty; + unsigned int status, ch, flag, rsr, max_count = 256; + + status = __raw_readl(uap->port.membase + UART01x_FR); + while ((status & UART01x_FR_RXFE) == 0 && max_count--) { +#if 0 + if (tty->flip.count >= TTY_FLIPBUF_SIZE) { + if (tty->low_latency) + tty_flip_buffer_push(tty); + /* + * If this failed then we will throw away the + * bytes but must do so to clear interrupts + */ + } +#endif + + ch = __raw_readl(uap->port.membase + UART01x_DR); + flag = TTY_NORMAL; + uap->port.icount.rx++; + + /* + * Note that the error handling code is + * out of the main execution path + */ + rsr = __raw_readl(uap->port.membase + UART01x_RSR) + | UART_DUMMY_RSR_RX; + if (unlikely(rsr & UART01x_RSR_ANY)) { + if (rsr & UART01x_RSR_BE) { + rsr &= ~(UART01x_RSR_FE | UART01x_RSR_PE); + uap->port.icount.brk++; + if (uart_handle_break(&uap->port)) + goto ignore_char; + } else if (rsr & UART01x_RSR_PE) + uap->port.icount.parity++; + else if (rsr & UART01x_RSR_FE) + uap->port.icount.frame++; + if (rsr & UART01x_RSR_OE) + uap->port.icount.overrun++; + + rsr &= uap->port.read_status_mask; + + if (rsr & UART01x_RSR_BE) + flag = TTY_BREAK; + else if (rsr & UART01x_RSR_PE) + flag = TTY_PARITY; + else if (rsr & UART01x_RSR_FE) + flag = TTY_FRAME; + } + + if (uart_handle_sysrq_char(&uap->port, ch)) + goto ignore_char; + + uart_insert_char(&uap->port, rsr, UART01x_RSR_OE, ch, flag); + +ignore_char: + status = __raw_readl(uap->port.membase + UART01x_FR); + } + tty_flip_buffer_push(tty); + return; +} + +static void stmpdbg_tx_chars(struct uart_stmpdbg_port *uap) +{ + struct circ_buf *xmit = &uap->port.info->xmit; + int count; + + if (uap->port.x_char) { + __raw_writel(uap->port.x_char, uap->port.membase + UART01x_DR); + uap->port.icount.tx++; + uap->port.x_char = 0; + return; + } + if (uart_circ_empty(xmit) || uart_tx_stopped(&uap->port)) { + stmpdbg_stop_tx(&uap->port); + return; + } + + count = uap->port.fifosize >> 1; + do { + __raw_writel(xmit->buf[xmit->tail], + uap->port.membase + UART01x_DR); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + uap->port.icount.tx++; + if (uart_circ_empty(xmit)) + break; + } while (--count > 0); + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(&uap->port); + + if (uart_circ_empty(xmit)) + stmpdbg_stop_tx(&uap->port); +} + +static void stmpdbg_modem_status(struct uart_stmpdbg_port *uap) +{ + unsigned int status, delta; + + status = __raw_readl(uap->port.membase + UART01x_FR) & + UART01x_FR_MODEM_ANY; + + delta = status ^ uap->old_status; + uap->old_status = status; + + if (!delta) + return; + + if (delta & UART01x_FR_DCD) + uart_handle_dcd_change(&uap->port, status & UART01x_FR_DCD); + + if (delta & UART01x_FR_DSR) + uap->port.icount.dsr++; + + if (delta & UART01x_FR_CTS) + uart_handle_cts_change(&uap->port, status & UART01x_FR_CTS); + + wake_up_interruptible(&uap->port.info->delta_msr_wait); +} + +static irqreturn_t stmpdbg_int(int irq, void *dev_id) +{ + struct uart_stmpdbg_port *uap = dev_id; + unsigned int status, pass_counter = ISR_PASS_LIMIT; + int handled = 0; + + spin_lock(&uap->port.lock); + + status = __raw_readl(uap->port.membase + UART011_MIS); + if (status) { + do { + __raw_writel(status & ~(UART011_TXIS|UART011_RTIS| + UART011_RXIS), + uap->port.membase + UART011_ICR); + + if (status & (UART011_RTIS|UART011_RXIS)) + stmpdbg_rx_chars(uap); + if (status & (UART011_DSRMIS|UART011_DCDMIS| + UART011_CTSMIS|UART011_RIMIS)) + stmpdbg_modem_status(uap); + if (status & UART011_TXIS) + stmpdbg_tx_chars(uap); + + if (pass_counter-- == 0) + break; + + status = __raw_readl(uap->port.membase + UART011_MIS); + } while (status != 0); + handled = 1; + } + + spin_unlock(&uap->port.lock); + + return IRQ_RETVAL(handled); +} + +static unsigned int stmpdbg_tx_empty(struct uart_port *port) +{ + struct uart_stmpdbg_port *uap = (struct uart_stmpdbg_port *)port; + unsigned int status = __raw_readl(uap->port.membase + UART01x_FR); + return status & (UART01x_FR_BUSY|UART01x_FR_TXFF) ? 0 : TIOCSER_TEMT; +} + +static unsigned int stmpdbg_get_mctrl(struct uart_port *port) +{ + struct uart_stmpdbg_port *uap = (struct uart_stmpdbg_port *)port; + unsigned int result = 0; + unsigned int status = __raw_readl(uap->port.membase + UART01x_FR); + +#define TEST_AND_SET_BIT(uartbit, tiocmbit) do { \ + if (status & uartbit) \ + result |= tiocmbit; \ + } while (0) + + TEST_AND_SET_BIT(UART01x_FR_DCD, TIOCM_CAR); + TEST_AND_SET_BIT(UART01x_FR_DSR, TIOCM_DSR); + TEST_AND_SET_BIT(UART01x_FR_CTS, TIOCM_CTS); + TEST_AND_SET_BIT(UART011_FR_RI, TIOCM_RNG); +#undef TEST_AND_SET_BIT + if (force_cd) + result |= TIOCM_CAR; + return result; +} + +static void stmpdbg_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + struct uart_stmpdbg_port *uap = (struct uart_stmpdbg_port *)port; + unsigned int cr; + + cr = __raw_readl(uap->port.membase + UART011_CR); + +#define TEST_AND_SET_BIT(tiocmbit, uartbit) do { \ + if (mctrl & tiocmbit) \ + cr |= uartbit; \ + else \ + cr &= ~uartbit; \ + } while (0) + + TEST_AND_SET_BIT(TIOCM_RTS, UART011_CR_RTS); + TEST_AND_SET_BIT(TIOCM_DTR, UART011_CR_DTR); + TEST_AND_SET_BIT(TIOCM_OUT1, UART011_CR_OUT1); + TEST_AND_SET_BIT(TIOCM_OUT2, UART011_CR_OUT2); + TEST_AND_SET_BIT(TIOCM_LOOP, UART011_CR_LBE); +#undef TEST_AND_SET_BIT + + __raw_writel(cr, uap->port.membase + UART011_CR); +} + +static void stmpdbg_break_ctl(struct uart_port *port, int break_state) +{ + struct uart_stmpdbg_port *uap = (struct uart_stmpdbg_port *)port; + unsigned long flags; + unsigned int lcr_h; + + spin_lock_irqsave(&uap->port.lock, flags); + lcr_h = __raw_readl(uap->port.membase + UART011_LCRH); + if (break_state == -1) + lcr_h |= UART01x_LCRH_BRK; + else + lcr_h &= ~UART01x_LCRH_BRK; + __raw_writel(lcr_h, uap->port.membase + UART011_LCRH); + spin_unlock_irqrestore(&uap->port.lock, flags); +} + +static int stmpdbg_startup(struct uart_port *port) +{ + struct uart_stmpdbg_port *uap = (struct uart_stmpdbg_port *)port; + u32 cr, lcr; + int retval; + + /* + * Allocate the IRQ + */ + retval = request_irq(uap->port.irq, stmpdbg_int, 0, STMPDBG_DEVID, uap); + if (retval) + return retval; + + __raw_writel(0, uap->port.membase + UART01x_DR); /* wake up the UART */ + + __raw_writel(UART011_IFLS_RX4_8|UART011_IFLS_TX4_8, + uap->port.membase + UART011_IFLS); + + /* + * Provoke TX FIFO interrupt into asserting. + */ + cr = UART01x_CR_UARTEN | UART011_CR_RXE | UART011_CR_TXE; + __raw_writel(cr, uap->port.membase + UART011_CR); + + lcr = __raw_readl(uap->port.membase + UART011_LCRH); + lcr |= UART01x_LCRH_FEN; + __raw_writel(lcr, uap->port.membase + UART011_LCRH); + + /* + * initialise the old status of the modem signals + */ + uap->old_status = __raw_readl(uap->port.membase + UART01x_FR) & + UART01x_FR_MODEM_ANY; + + /* + * Finally, enable interrupts + */ + spin_lock_irq(&uap->port.lock); + uap->im = UART011_RXIM | UART011_RTIM; + __raw_writel(uap->im, uap->port.membase + UART011_IMSC); + spin_unlock_irq(&uap->port.lock); + + return 0; +} + +static void stmpdbg_shutdown(struct uart_port *port) +{ + struct uart_stmpdbg_port *uap = (struct uart_stmpdbg_port *)port; + unsigned long val; + + /* + * disable all interrupts + */ + spin_lock_irq(&uap->port.lock); + uap->im = 0; + __raw_writel(uap->im, uap->port.membase + UART011_IMSC); + __raw_writel(0xffff, uap->port.membase + UART011_ICR); + spin_unlock_irq(&uap->port.lock); + + free_irq(uap->port.irq, uap); + + /* + * disable the port + */ + __raw_writel(UART01x_CR_UARTEN | UART011_CR_TXE, + uap->port.membase + UART011_CR); + + /* + * disable break condition and fifos + */ + val = __raw_readl(uap->port.membase + UART011_LCRH); + val &= ~(UART01x_LCRH_BRK | UART01x_LCRH_FEN); + __raw_writel(val, uap->port.membase + UART011_LCRH); +} + +static void +stmpdbg_set_termios(struct uart_port *port, struct ktermios *termios, + struct ktermios *old) +{ + unsigned int lcr_h, old_cr; + unsigned long flags; + unsigned int baud, quot; + + /* + * Ask the core to calculate the divisor for us. + */ + baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16); + quot = port->uartclk * 4 / baud; + + switch (termios->c_cflag & CSIZE) { + case CS5: + lcr_h = UART01x_LCRH_WLEN_5; + break; + case CS6: + lcr_h = UART01x_LCRH_WLEN_6; + break; + case CS7: + lcr_h = UART01x_LCRH_WLEN_7; + break; + default: /* CS8 */ + lcr_h = UART01x_LCRH_WLEN_8; + break; + } + if (termios->c_cflag & CSTOPB) + lcr_h |= UART01x_LCRH_STP2; + if (termios->c_cflag & PARENB) { + lcr_h |= UART01x_LCRH_PEN; + if (!(termios->c_cflag & PARODD)) + lcr_h |= UART01x_LCRH_EPS; + } + lcr_h |= UART01x_LCRH_FEN; + + spin_lock_irqsave(&port->lock, flags); + + /* + * Update the per-port timeout. + */ + uart_update_timeout(port, termios->c_cflag, baud); + + port->read_status_mask = UART01x_RSR_OE; + if (termios->c_iflag & INPCK) + port->read_status_mask |= UART01x_RSR_FE | UART01x_RSR_PE; + if (termios->c_iflag & (BRKINT | PARMRK)) + port->read_status_mask |= UART01x_RSR_BE; + + /* + * Characters to ignore + */ + port->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= UART01x_RSR_FE | UART01x_RSR_PE; + if (termios->c_iflag & IGNBRK) { + port->ignore_status_mask |= UART01x_RSR_BE; + /* + * If we're ignoring parity and break indicators, + * ignore overruns too (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= UART01x_RSR_OE; + } + + /* + * Ignore all characters if CREAD is not set. + */ + if ((termios->c_cflag & CREAD) == 0) + port->ignore_status_mask |= UART_DUMMY_RSR_RX; + + if (UART_ENABLE_MS(port, termios->c_cflag)) + stmpdbg_enable_ms(port); + + /* first, disable everything */ + old_cr = __raw_readl(port->membase + UART011_CR); + __raw_writel(0, port->membase + UART011_CR); + + /* Set baud rate */ + __raw_writel(quot & 0x3f, port->membase + UART011_FBRD); + __raw_writel(quot >> 6, port->membase + UART011_IBRD); + + /* + * ----------v----------v----------v----------v----- + * NOTE: MUST BE WRITTEN AFTER UARTLCR_M & UARTLCR_L + * ----------^----------^----------^----------^----- + */ + __raw_writel(lcr_h, port->membase + UART011_LCRH); + __raw_writel(old_cr, port->membase + UART011_CR); + + spin_unlock_irqrestore(&port->lock, flags); +} + +static const char *stmpdbg_type(struct uart_port *port) +{ + return port->type == PORT_STMPDBG ? STMPDBG_DEVID : NULL; +} + + +/* + * Release the memory region(s) being used by 'port' + */ +static void stmpdbg_release_port(struct uart_port *port) +{ + release_mem_region(port->mapbase, UART_PORT_SIZE); +} + +/* + * Request the memory region(s) being used by 'port' + */ +static int stmpdbg_request_port(struct uart_port *port) +{ + return request_mem_region(port->mapbase, UART_PORT_SIZE, STMPDBG_DEVID) + != NULL ? 0 : -EBUSY; +} + +/* + * Configure/autoconfigure the port. + */ +static void stmpdbg_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) { + port->type = PORT_STMPDBG; + stmpdbg_request_port(port); + } +} + +/* + * verify the new serial_struct (for TIOCSSERIAL). + */ +static int stmpdbg_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + int ret = 0; + if (ser->type != PORT_UNKNOWN && ser->type != PORT_STMPDBG) + ret = -EINVAL; + if (ser->irq < 0 || ser->irq >= NR_IRQS) + ret = -EINVAL; + if (ser->baud_base < 9600) + ret = -EINVAL; + return ret; +} + +static struct uart_ops stmpdbg_pops = { + .tx_empty = stmpdbg_tx_empty, + .set_mctrl = stmpdbg_set_mctrl, + .get_mctrl = stmpdbg_get_mctrl, + .stop_tx = stmpdbg_stop_tx, + .start_tx = stmpdbg_start_tx, + .stop_rx = stmpdbg_stop_rx, + .enable_ms = stmpdbg_enable_ms, + .break_ctl = stmpdbg_break_ctl, + .startup = stmpdbg_startup, + .shutdown = stmpdbg_shutdown, + .set_termios = stmpdbg_set_termios, + .type = stmpdbg_type, + .release_port = stmpdbg_release_port, + .request_port = stmpdbg_request_port, + .config_port = stmpdbg_config_port, + .verify_port = stmpdbg_verify_port, +}; + +static struct uart_stmpdbg_port stmpdbg_ports[UART_NR] = { + { + .port = { + /* This *is* the virtual address */ + .membase = (void *)REGS_UARTDBG_BASE + HW_UARTDBGDR, + .mapbase = REGS_UARTDBG_PHYS + HW_UARTDBGDR, + .iotype = SERIAL_IO_MEM, + .irq = IRQ_DEBUG_UART, + .fifosize = 16, + .ops = &stmpdbg_pops, + .flags = ASYNC_BOOT_AUTOCONF, + .line = 0, + .uartclk = 24000000, + }, + } +}; + +#ifdef CONFIG_SERIAL_STMP_DBG_CONSOLE + +static void +stmpdbg_console_write(struct console *co, const char *s, unsigned int count) +{ + struct uart_port *port = &stmpdbg_ports[co->index].port; + unsigned int status, old_cr; + int i; + + /* + * First save the CR then disable the interrupts + */ + old_cr = UART_GET_CR(port); + UART_PUT_CR(port, UART01x_CR_UARTEN); + + /* + * Now, do each character + */ + for (i = 0; i < count; i++) { + do { + status = UART_GET_FR(port); + } while (!UART_TX_READY(status)); + UART_PUT_CHAR(port, s[i]); + if (s[i] == '\n') { + do { + status = UART_GET_FR(port); + } while (!UART_TX_READY(status)); + UART_PUT_CHAR(port, '\r'); + } + } + + /* + * Finally, wait for transmitter to become empty + * and restore the TCR + */ + do { + status = UART_GET_FR(port); + } while (status & UART01x_FR_BUSY); + UART_PUT_CR(port, old_cr); +} + +static void __init +stmpdbg_console_get_options(struct uart_port *port, int *baud, + int *parity, int *bits) +{ + if (UART_GET_CR(port) & UART01x_CR_UARTEN) { + unsigned int lcr_h, quot; + lcr_h = UART_GET_LCRH(port); + + *parity = 'n'; + if (lcr_h & UART01x_LCRH_PEN) { + if (lcr_h & UART01x_LCRH_EPS) + *parity = 'e'; + else + *parity = 'o'; + } + + if ((lcr_h & 0x60) == UART01x_LCRH_WLEN_7) + *bits = 7; + else + *bits = 8; + + quot = UART_GET_LCRL(port) | UART_GET_LCRM(port) << 8; + *baud = port->uartclk / (16 * (quot + 1)); + } +} + +static int __init stmpdbg_console_setup(struct console *co, char *options) +{ + struct uart_port *port; + int baud = 115200; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + + /* + * Check whether an invalid uart number has been specified, and + * if so, search for the first available port that does have + * console support. + */ + if (co->index >= UART_NR) + co->index = 0; + port = &stmpdbg_ports[co->index].port; + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + else + stmpdbg_console_get_options(port, &baud, &parity, &bits); + + return uart_set_options(port, co, baud, parity, bits, flow); +} + + +static struct console stmpdbg_console = { + .name = "ttyAM", + .write = stmpdbg_console_write, + .device = uart_console_device, + .setup = stmpdbg_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &stmpdbg_reg, +}; + +static int __init stmpdbg_console_init(void) +{ + /* + * All port initializations are done statically + */ + register_console(&stmpdbg_console); + return 0; +} +console_initcall(stmpdbg_console_init); + +static int __init stmpdbg_late_console_init(void) +{ + if (!(stmpdbg_console.flags & CON_ENABLED)) + register_console(&stmpdbg_console); + return 0; +} +late_initcall(stmpdbg_late_console_init); + +#endif + +static struct uart_driver stmpdbg_reg = { + .owner = THIS_MODULE, + .driver_name = "ttyAM", + .dev_name = "ttyAM", + .major = SERIAL_STMPDBG_MAJOR, + .minor = SERIAL_STMPDBG_MINOR, + .nr = UART_NR, +#ifdef CONFIG_SERIAL_STMP_DBG_CONSOLE + .cons = &stmpdbg_console, +#endif +}; + +static int __devinit stmpdbguart_probe(struct platform_device *device) +{ + int ret = 0; + int i; + void (*cfg)(int request, int port) = NULL; + + if (device->dev.platform_data) + cfg = device->dev.platform_data; + + device_init_wakeup(&device->dev, 1); + + for (i = 0; i < UART_NR; i++) { + stmpdbg_ports[i].clk = clk_get(NULL, "uart"); + if (IS_ERR(stmpdbg_ports[i].clk)) + continue; + stmpdbg_ports[i].suspended = 0; + stmpdbg_ports[i].port.dev = &device->dev; + stmpdbg_ports[i].port.uartclk = + clk_get_rate(stmpdbg_ports[i].clk) * 1000; + if (cfg) + (*cfg)(1, i); + uart_add_one_port(&stmpdbg_reg, &stmpdbg_ports[i].port); + } + return ret; +} + +static int __devexit stmpdbguart_remove(struct platform_device *device) +{ + int i; + void (*cfg)(int request, int port) = NULL; + + if (device->dev.platform_data) + cfg = device->dev.platform_data; + for (i = 0; i < UART_NR; i++) { + clk_put(stmpdbg_ports[i].clk); + uart_remove_one_port(&stmpdbg_reg, &stmpdbg_ports[0].port); + if (cfg) + (*cfg)(0, i); + } + return 0; +} + +static int stmpdbguart_suspend(struct platform_device *device, + pm_message_t state) +{ +#ifdef CONFIG_PM + int i; + int deep_sleep = (stmp37xx_pm_get_target() != PM_SUSPEND_STANDBY); + + for (i = 0; i < UART_NR; i++) { + if (deep_sleep) { + uart_suspend_port(&stmpdbg_reg, + &stmpdbg_ports[i].port); + clk_disable(stmpdbg_ports[i].clk); + stmpdbg_ports[i].suspended = 1; + } + } +#endif + return 0; +} + +static int stmpdbguart_resume(struct platform_device *device) +{ + int ret = 0; +#ifdef CONFIG_PM + int i; + + for (i = 0; i < UART_NR; i++) { + if (stmpdbg_ports[i].suspended) { + clk_enable(stmpdbg_ports[i].clk); + uart_resume_port(&stmpdbg_reg, &stmpdbg_ports[i].port); + } + stmpdbg_ports[i].suspended = 0; + } +#endif + return ret; +} + +static struct platform_driver stmpdbguart_driver = { + .probe = stmpdbguart_probe, + .remove = __devexit_p(stmpdbguart_remove), + .suspend = stmpdbguart_suspend, + .resume = stmpdbguart_resume, + .driver = { + .name = "stmp3xxx-dbguart", + .owner = THIS_MODULE, + }, +}; + +static int __init stmpdbg_init(void) +{ + int ret; + + ret = uart_register_driver(&stmpdbg_reg); + if (ret) + goto out; + + ret = platform_driver_register(&stmpdbguart_driver); + if (ret) + uart_unregister_driver(&stmpdbg_reg); +out: + return ret; +} + +static void __exit stmpdbg_exit(void) +{ + platform_driver_unregister(&stmpdbguart_driver); + uart_unregister_driver(&stmpdbg_reg); +} + +module_init(stmpdbg_init); +module_exit(stmpdbg_exit); +module_param(force_cd, int, 0644); +MODULE_AUTHOR("ARM Ltd/Deep Blue Solutions Ltd/Sigmatel Inc"); +MODULE_DESCRIPTION("STMP37xx debug uart"); +MODULE_LICENSE("GPL"); diff --git a/drivers/serial/stmp-dbg.h b/drivers/serial/stmp-dbg.h new file mode 100644 index 000000000000..38f3e6747fb3 --- /dev/null +++ b/drivers/serial/stmp-dbg.h @@ -0,0 +1,180 @@ +/* + * Freescale STMP37XX/STMP378X Debug UART driver + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __STMP_DBG_H +#define __STMP_DBG_H +/* + * UART register offsets + */ +#define UART01x_DR 0x00 /* Data read or written */ +#define UART01x_RSR 0x04 /* Receive status register */ +#define UART01x_ECR 0x04 /* Error clear register (write) */ +#define UART010_LCRH 0x08 /* Line control register, MSB */ +#define UART010_LCRM 0x0C /* Line control register, */ +#define UART010_LCRL 0x10 /* Line control register, LSB */ +#define UART010_CR 0x14 /* Control register. */ +#define UART01x_FR 0x18 /* Flag register (Read only). */ +#define UART010_IIR 0x1C /* Interrupt identification */ +#define UART010_ICR 0x1C /* Interrupt clear register */ +#define UART01x_ILPR 0x20 /* IrDA low power counter */ +#define UART011_IBRD 0x24 /* Integer baud rate divisor */ +#define UART011_FBRD 0x28 /* Fractional baud rate divisor */ +#define UART011_LCRH 0x2c /* Line control */ +#define UART011_CR 0x30 /* Control */ +#define UART011_IFLS 0x34 /* Interrupt fifo level select */ +#define UART011_IMSC 0x38 /* Interrupt mask */ +#define UART011_RIS 0x3c /* Raw */ +#define UART011_MIS 0x40 /* Masked */ +#define UART011_ICR 0x44 /* Interrupt clear register */ +#define UART011_DMACR 0x48 /* DMA control register */ + +#define UART011_DR_OE (1 << 11) +#define UART011_DR_BE (1 << 10) +#define UART011_DR_PE (1 << 9) +#define UART011_DR_FE (1 << 8) + +#define UART01x_RSR_OE 0x08 +#define UART01x_RSR_BE 0x04 +#define UART01x_RSR_PE 0x02 +#define UART01x_RSR_FE 0x01 + +#define UART011_FR_RI 0x100 +#define UART011_FR_TXFE 0x080 +#define UART011_FR_RXFF 0x040 +#define UART01x_FR_TXFF 0x020 +#define UART01x_FR_RXFE 0x010 +#define UART01x_FR_BUSY 0x008 +#define UART01x_FR_DCD 0x004 +#define UART01x_FR_DSR 0x002 +#define UART01x_FR_CTS 0x001 +#define UART01x_FR_TMSK (UART01x_FR_TXFF + UART01x_FR_BUSY) + +#define UART011_CR_CTSEN 0x8000 /* CTS hardware flow control */ +#define UART011_CR_RTSEN 0x4000 /* RTS hardware flow control */ +#define UART011_CR_OUT2 0x2000 /* OUT2 */ +#define UART011_CR_OUT1 0x1000 /* OUT1 */ +#define UART011_CR_RTS 0x0800 /* RTS */ +#define UART011_CR_DTR 0x0400 /* DTR */ +#define UART011_CR_RXE 0x0200 /* receive enable */ +#define UART011_CR_TXE 0x0100 /* transmit enable */ +#define UART011_CR_LBE 0x0080 /* loopback enable */ +#define UART010_CR_RTIE 0x0040 +#define UART010_CR_TIE 0x0020 +#define UART010_CR_RIE 0x0010 +#define UART010_CR_MSIE 0x0008 +#define UART01x_CR_IIRLP 0x0004 /* SIR low power mode */ +#define UART01x_CR_SIREN 0x0002 /* SIR enable */ +#define UART01x_CR_UARTEN 0x0001 /* UART enable */ + +#define UART011_LCRH_SPS 0x80 +#define UART01x_LCRH_WLEN_8 0x60 +#define UART01x_LCRH_WLEN_7 0x40 +#define UART01x_LCRH_WLEN_6 0x20 +#define UART01x_LCRH_WLEN_5 0x00 +#define UART01x_LCRH_FEN 0x10 +#define UART01x_LCRH_STP2 0x08 +#define UART01x_LCRH_EPS 0x04 +#define UART01x_LCRH_PEN 0x02 +#define UART01x_LCRH_BRK 0x01 + +#define UART010_IIR_RTIS 0x08 +#define UART010_IIR_TIS 0x04 +#define UART010_IIR_RIS 0x02 +#define UART010_IIR_MIS 0x01 + +#define UART011_IFLS_RX1_8 (0 << 3) +#define UART011_IFLS_RX2_8 (1 << 3) +#define UART011_IFLS_RX4_8 (2 << 3) +#define UART011_IFLS_RX6_8 (3 << 3) +#define UART011_IFLS_RX7_8 (4 << 3) +#define UART011_IFLS_TX1_8 (0 << 0) +#define UART011_IFLS_TX2_8 (1 << 0) +#define UART011_IFLS_TX4_8 (2 << 0) +#define UART011_IFLS_TX6_8 (3 << 0) +#define UART011_IFLS_TX7_8 (4 << 0) + +/* Interrupt masks */ +#define UART011_OEIM (1 << 10) /* overrun error */ +#define UART011_BEIM (1 << 9) /* break error */ +#define UART011_PEIM (1 << 8) /* parity error */ +#define UART011_FEIM (1 << 7) /* framing error */ +#define UART011_RTIM (1 << 6) /* receive timeout */ +#define UART011_TXIM (1 << 5) /* transmit */ +#define UART011_RXIM (1 << 4) /* receive */ +#define UART011_DSRMIM (1 << 3) /* DSR interrupt mask */ +#define UART011_DCDMIM (1 << 2) /* DCD interrupt mask */ +#define UART011_CTSMIM (1 << 1) /* CTS interrupt mask */ +#define UART011_RIMIM (1 << 0) /* RI interrupt mask */ + +/* Interrupt statuses */ +#define UART011_OEIS (1 << 10) /* overrun error */ +#define UART011_BEIS (1 << 9) /* break error */ +#define UART011_PEIS (1 << 8) /* parity error */ +#define UART011_FEIS (1 << 7) /* framing error */ +#define UART011_RTIS (1 << 6) /* receive timeout */ +#define UART011_TXIS (1 << 5) /* transmit */ +#define UART011_RXIS (1 << 4) /* receive */ +#define UART011_DSRMIS (1 << 3) /* DSR */ +#define UART011_DCDMIS (1 << 2) /* DCD */ +#define UART011_CTSMIS (1 << 1) /* CTS */ +#define UART011_RIMIS (1 << 0) /* RI */ + +/* Interrupt clear masks */ +#define UART011_OEIC (1 << 10) /* overrun error */ +#define UART011_BEIC (1 << 9) /* break error */ +#define UART011_PEIC (1 << 8) /* parity error */ +#define UART011_FEIC (1 << 7) /* framing error */ +#define UART011_RTIC (1 << 6) /* receive timeout */ +#define UART011_TXIC (1 << 5) /* transmit */ +#define UART011_RXIC (1 << 4) /* receive */ +#define UART011_DSRMIC (1 << 3) /* DSR */ +#define UART011_DCDMIC (1 << 2) /* DCD */ +#define UART011_CTSMIC (1 << 1) /* CTS */ +#define UART011_RIMIC (1 << 0) /* RI */ + +#define UART011_DMAONERR (1 << 2) /* disable dma on error */ +#define UART011_TXDMAE (1 << 1) /* enable transmit dma */ +#define UART011_RXDMAE (1 << 0) /* enable receive dma */ + +#define UART01x_RSR_ANY (UART01x_RSR_OE | UART01x_RSR_BE | \ + UART01x_RSR_PE | UART01x_RSR_FE) +#define UART01x_FR_MODEM_ANY (UART01x_FR_DCD | UART01x_FR_DSR | \ + UART01x_FR_CTS) + +/* + * Access macros for the AMBA UARTs + */ +#define UART_GET_INT_STATUS(p) readb((p)->membase + UART010_IIR) +#define UART_PUT_ICR(p, c) writel((c), (p)->membase + UART010_ICR) +#define UART_GET_FR(p) readb((p)->membase + UART01x_FR) +#define UART_GET_CHAR(p) readb((p)->membase + UART01x_DR) +#define UART_PUT_CHAR(p, c) writel((c), (p)->membase + UART01x_DR) +#define UART_GET_RSR(p) readb((p)->membase + UART01x_RSR) +#define UART_GET_CR(p) readb((p)->membase + UART010_CR) +#define UART_PUT_CR(p, c) writel((c), (p)->membase + UART010_CR) +#define UART_GET_LCRL(p) readb((p)->membase + UART010_LCRL) +#define UART_PUT_LCRL(p, c) writel((c), (p)->membase + UART010_LCRL) +#define UART_GET_LCRM(p) readb((p)->membase + UART010_LCRM) +#define UART_PUT_LCRM(p, c) writel((c), (p)->membase + UART010_LCRM) +#define UART_GET_LCRH(p) readb((p)->membase + UART010_LCRH) +#define UART_PUT_LCRH(p, c) writel((c), (p)->membase + UART010_LCRH) +#define UART_RX_DATA(s) (((s) & UART01x_FR_RXFE) == 0) +#define UART_TX_READY(s) (((s) & UART01x_FR_TXFF) == 0) +#define UART_TX_EMPTY(p) ((UART_GET_FR(p) & UART01x_FR_TMSK) == 0) + +#define UART_DUMMY_RSR_RX /*256*/0 +#define UART_PORT_SIZE 64 + +#endif /* STMP_DBG_H */ diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 2c733c27db2f..e16915107c6b 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -227,6 +227,40 @@ config SPI_XILINX See the "OPB Serial Peripheral Interface (SPI) (v1.00e)" Product Specification document (DS464) for hardware details. +config SPI_MXC + tristate "MXC CSPI controller as SPI Master" + depends on ARCH_MXC && SPI_MASTER + select SPI_BITBANG + help + This implements the SPI master mode using MXC CSPI. + +config SPI_MXC_TEST_LOOPBACK + bool "LOOPBACK Testing of CSPIs" + depends on SPI_MXC + select SPI_SPIDEV + default n + +config SPI_MXC_SELECT1 + bool "CSPI1" + depends on SPI_MXC + default y + +config SPI_MXC_SELECT2 + bool "CSPI2" + depends on SPI_MXC && (!ARCH_MXC91221) + default n + +config SPI_MXC_SELECT3 + bool "CSPI3" + depends on SPI_MXC && (ARCH_MX3 || ARCH_MX27 || ARCH_MX25 || ARCH_MX37 || ARCH_MX51) + default n + +config SPI_STMP3XXX + tristate "Freescale STMP37xx/378x SPI/SSP controller" + depends on ARCH_STMP3XXX && SPI_MASTER + help + SPI driver for Freescale STMP37xx/378x SoC SSP interface + # # Add new SPI master controllers in alphabetical order above this line # diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 3de408d294ba..b7cfb6245c0b 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -29,8 +29,10 @@ obj-$(CONFIG_SPI_MPC8xxx) += spi_mpc8xxx.o obj-$(CONFIG_SPI_S3C24XX_GPIO) += spi_s3c24xx_gpio.o obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx.o obj-$(CONFIG_SPI_TXX9) += spi_txx9.o +obj-$(CONFIG_SPI_MXC) += mxc_spi.o obj-$(CONFIG_SPI_XILINX) += xilinx_spi.o obj-$(CONFIG_SPI_SH_SCI) += spi_sh_sci.o +obj-$(CONFIG_SPI_STMP3XXX) += spi_stmp.o # ... add above this line ... # SPI protocol drivers (device/link on bus) diff --git a/drivers/spi/mxc_spi.c b/drivers/spi/mxc_spi.c new file mode 100644 index 000000000000..0b622e9c50d1 --- /dev/null +++ b/drivers/spi/mxc_spi.c @@ -0,0 +1,1309 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-licensisr_locke.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup SPI Configurable Serial Peripheral Interface (CSPI) Driver + */ + +/*! + * @file mxc_spi.c + * @brief This file contains the implementation of the SPI master controller services + * + * + * @ingroup SPI + */ + +#include <linux/completion.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <linux/clk.h> +#include <linux/spi/spi.h> +#include <linux/spi/spi_bitbang.h> +#include <mach/hardware.h> + +#define MXC_CSPIRXDATA 0x00 +#define MXC_CSPITXDATA 0x04 +#define MXC_CSPICTRL 0x08 +#define MXC_CSPICONFIG 0x08 +#define MXC_CSPIINT 0x0C + +#define MXC_CSPICTRL_DISABLE 0x0 +#define MXC_CSPICTRL_SLAVE 0x0 +#define MXC_CSPICTRL_CSMASK 0x3 +#define MXC_CSPICTRL_SMC (1 << 3) + +#define MXC_CSPIINT_TEEN_SHIFT 0 +#define MXC_CSPIINT_THEN_SHIFT 1 +#define MXC_CSPIINT_TFEN_SHIFT 2 +#define MXC_CSPIINT_RREN_SHIFT 3 +#define MXC_CSPIINT_RHEN_SHIFT 4 +#define MXC_CSPIINT_RFEN_SHIFT 5 +#define MXC_CSPIINT_ROEN_SHIFT 6 + +#define MXC_HIGHPOL 0x0 +#define MXC_NOPHA 0x0 +#define MXC_LOWSSPOL 0x0 + +#define MXC_CSPISTAT_TE 0 +#define MXC_CSPISTAT_TH 1 +#define MXC_CSPISTAT_TF 2 +#define MXC_CSPISTAT_RR 3 +#define MXC_CSPISTAT_RH 4 +#define MXC_CSPISTAT_RF 5 +#define MXC_CSPISTAT_RO 6 + +#define MXC_CSPIPERIOD_32KHZ (1 << 15) + +/*! + * @struct mxc_spi_unique_def + * @brief This structure contains information that differs with + * SPI master controller hardware version + */ +struct mxc_spi_unique_def { + /* Width of valid bits in MXC_CSPIINT */ + unsigned int intr_bit_shift; + /* Chip Select shift */ + unsigned int cs_shift; + /* Bit count shift */ + unsigned int bc_shift; + /* Bit count mask */ + unsigned int bc_mask; + /* Data Control shift */ + unsigned int drctrl_shift; + /* Transfer Complete shift */ + unsigned int xfer_complete; + /* Bit counnter overflow shift */ + unsigned int bc_overflow; + /* FIFO Size */ + unsigned int fifo_size; + /* Control reg address */ + unsigned int ctrl_reg_addr; + /* Status reg address */ + unsigned int stat_reg_addr; + /* Period reg address */ + unsigned int period_reg_addr; + /* Test reg address */ + unsigned int test_reg_addr; + /* Reset reg address */ + unsigned int reset_reg_addr; + /* SPI mode mask */ + unsigned int mode_mask; + /* SPI enable */ + unsigned int spi_enable; + /* XCH bit */ + unsigned int xch; + /* Spi mode shift */ + unsigned int mode_shift; + /* Spi master mode enable */ + unsigned int master_enable; + /* TX interrupt enable diff */ + unsigned int tx_inten_dif; + /* RX interrupt enable bit diff */ + unsigned int rx_inten_dif; + /* Interrupt status diff */ + unsigned int int_status_dif; + /* Low pol shift */ + unsigned int low_pol_shift; + /* Phase shift */ + unsigned int pha_shift; + /* SS control shift */ + unsigned int ss_ctrl_shift; + /* SS pol shift */ + unsigned int ss_pol_shift; + /* Maximum data rate */ + unsigned int max_data_rate; + /* Data mask */ + unsigned int data_mask; + /* Data shift */ + unsigned int data_shift; + /* Loopback control */ + unsigned int lbc; + /* RX count off */ + unsigned int rx_cnt_off; + /* RX count mask */ + unsigned int rx_cnt_mask; + /* Reset start */ + unsigned int reset_start; + /* SCLK control inactive state shift */ + unsigned int sclk_ctl_shift; +}; + +struct mxc_spi; + +/*! + * Structure to group together all the data buffers and functions + * used in data transfers. + */ +struct mxc_spi_xfer { + /* Transmit buffer */ + const void *tx_buf; + /* Receive buffer */ + void *rx_buf; + /* Data transfered count */ + unsigned int count; + /* Data received count, descending sequence, zero means no more data to + be received */ + unsigned int rx_count; + /* Function to read the FIFO data to rx_buf */ + void (*rx_get) (struct mxc_spi *, u32 val); + /* Function to get the data to be written to FIFO */ + u32(*tx_get) (struct mxc_spi *); +}; + +/*! + * This structure is a way for the low level driver to define their own + * \b spi_master structure. This structure includes the core \b spi_master + * structure that is provided by Linux SPI Framework/driver as an + * element and has other elements that are specifically required by this + * low-level driver. + */ +struct mxc_spi { + /* SPI Master and a simple I/O queue runner */ + struct spi_bitbang mxc_bitbang; + /* Completion flags used in data transfers */ + struct completion xfer_done; + /* Data transfer structure */ + struct mxc_spi_xfer transfer; + /* Resource structure, which will maintain base addresses and IRQs */ + struct resource *res; + /* Base address of CSPI, used in readl and writel */ + void *base; + /* CSPI IRQ number */ + int irq; + /* CSPI Clock id */ + struct clk *clk; + /* CSPI input clock SCLK */ + unsigned long spi_ipg_clk; + /* CSPI registers' bit pattern */ + struct mxc_spi_unique_def *spi_ver_def; + /* Control reg address */ + void *ctrl_addr; + /* Status reg address */ + void *stat_addr; + /* Period reg address */ + void *period_addr; + /* Test reg address */ + void *test_addr; + /* Reset reg address */ + void *reset_addr; + /* Chipselect active function */ + void (*chipselect_active) (int cspi_mode, int status, int chipselect); + /* Chipselect inactive function */ + void (*chipselect_inactive) (int cspi_mode, int status, int chipselect); +}; + +#ifdef CONFIG_SPI_MXC_TEST_LOOPBACK +struct spi_chip_info { + int lb_enable; +}; + +static struct spi_chip_info lb_chip_info = { + .lb_enable = 1, +}; + +static struct spi_board_info loopback_info[] = { +#ifdef CONFIG_SPI_MXC_SELECT1 + { + .modalias = "spidev", + .controller_data = &lb_chip_info, + .irq = 0, + .max_speed_hz = 4000000, + .bus_num = 1, + .chip_select = 4, + }, +#endif +#ifdef CONFIG_SPI_MXC_SELECT2 + { + .modalias = "spidev", + .controller_data = &lb_chip_info, + .irq = 0, + .max_speed_hz = 4000000, + .bus_num = 2, + .chip_select = 4, + }, +#endif +#ifdef CONFIG_SPI_MXC_SELECT3 + { + .modalias = "spidev", + .controller_data = &lb_chip_info, + .irq = 0, + .max_speed_hz = 4000000, + .bus_num = 3, + .chip_select = 4, + }, +#endif +}; +#endif + +static struct mxc_spi_unique_def spi_ver_2_3 = { + .intr_bit_shift = 8, + .cs_shift = 18, + .bc_shift = 20, + .bc_mask = 0xFFF, + .drctrl_shift = 16, + .xfer_complete = (1 << 7), + .bc_overflow = 0, + .fifo_size = 64, + .ctrl_reg_addr = 4, + .stat_reg_addr = 0x18, + .period_reg_addr = 0x1C, + .test_reg_addr = 0x20, + .reset_reg_addr = 0x8, + .mode_mask = 0xF, + .spi_enable = 0x1, + .xch = (1 << 2), + .mode_shift = 4, + .master_enable = 0, + .tx_inten_dif = 0, + .rx_inten_dif = 0, + .int_status_dif = 0, + .low_pol_shift = 4, + .pha_shift = 0, + .ss_ctrl_shift = 8, + .ss_pol_shift = 12, + .max_data_rate = 0xF, + .data_mask = 0xFF, + .data_shift = 8, + .lbc = (1 << 31), + .rx_cnt_off = 8, + .rx_cnt_mask = (0x7F << 8), + .reset_start = 0, + .sclk_ctl_shift = 20, +}; + +static struct mxc_spi_unique_def spi_ver_0_7 = { + .intr_bit_shift = 8, + .cs_shift = 12, + .bc_shift = 20, + .bc_mask = 0xFFF, + .drctrl_shift = 8, + .xfer_complete = (1 << 7), + .bc_overflow = 0, + .fifo_size = 8, + .ctrl_reg_addr = 0, + .stat_reg_addr = 0x14, + .period_reg_addr = 0x18, + .test_reg_addr = 0x1C, + .reset_reg_addr = 0x0, + .mode_mask = 0x1, + .spi_enable = 0x1, + .xch = (1 << 2), + .mode_shift = 1, + .master_enable = 1 << 1, + .tx_inten_dif = 0, + .rx_inten_dif = 0, + .int_status_dif = 0, + .low_pol_shift = 4, + .pha_shift = 5, + .ss_ctrl_shift = 6, + .ss_pol_shift = 7, + .max_data_rate = 0x7, + .data_mask = 0x7, + .data_shift = 16, + .lbc = (1 << 14), + .rx_cnt_off = 4, + .rx_cnt_mask = (0xF << 4), + .reset_start = 1, +}; + +static struct mxc_spi_unique_def spi_ver_0_5 = { + .intr_bit_shift = 9, + .cs_shift = 12, + .bc_shift = 20, + .bc_mask = 0xFFF, + .drctrl_shift = 8, + .xfer_complete = (1 << 8), + .bc_overflow = (1 << 7), + .fifo_size = 8, + .ctrl_reg_addr = 0, + .stat_reg_addr = 0x14, + .period_reg_addr = 0x18, + .test_reg_addr = 0x1C, + .reset_reg_addr = 0x0, + .mode_mask = 0x1, + .spi_enable = 0x1, + .xch = (1 << 2), + .mode_shift = 1, + .master_enable = 1 << 1, + .tx_inten_dif = 0, + .rx_inten_dif = 0, + .int_status_dif = 0, + .low_pol_shift = 4, + .pha_shift = 5, + .ss_ctrl_shift = 6, + .ss_pol_shift = 7, + .max_data_rate = 0x7, + .data_mask = 0x7, + .data_shift = 16, + .lbc = (1 << 14), + .rx_cnt_off = 4, + .rx_cnt_mask = (0xF << 4), + .reset_start = 1, +}; + +static struct mxc_spi_unique_def spi_ver_0_4 = { + .intr_bit_shift = 9, + .cs_shift = 24, + .bc_shift = 8, + .bc_mask = 0x1F, + .drctrl_shift = 20, + .xfer_complete = (1 << 8), + .bc_overflow = (1 << 7), + .fifo_size = 8, + .ctrl_reg_addr = 0, + .stat_reg_addr = 0x14, + .period_reg_addr = 0x18, + .test_reg_addr = 0x1C, + .reset_reg_addr = 0x0, + .mode_mask = 0x1, + .spi_enable = 0x1, + .xch = (1 << 2), + .mode_shift = 1, + .master_enable = 1 << 1, + .tx_inten_dif = 0, + .rx_inten_dif = 0, + .int_status_dif = 0, + .low_pol_shift = 4, + .pha_shift = 5, + .ss_ctrl_shift = 6, + .ss_pol_shift = 7, + .max_data_rate = 0x7, + .data_mask = 0x7, + .data_shift = 16, + .lbc = (1 << 14), + .rx_cnt_off = 4, + .rx_cnt_mask = (0xF << 4), + .reset_start = 1, +}; + +static struct mxc_spi_unique_def spi_ver_0_0 = { + .intr_bit_shift = 18, + .cs_shift = 19, + .bc_shift = 0, + .bc_mask = 0x1F, + .drctrl_shift = 12, + .xfer_complete = (1 << 3), + .bc_overflow = (1 << 8), + .fifo_size = 8, + .ctrl_reg_addr = 0, + .stat_reg_addr = 0x0C, + .period_reg_addr = 0x14, + .test_reg_addr = 0x10, + .reset_reg_addr = 0x1C, + .mode_mask = 0x1, + .spi_enable = (1 << 10), + .xch = (1 << 9), + .mode_shift = 11, + .master_enable = 1 << 11, + .tx_inten_dif = 9, + .rx_inten_dif = 10, + .int_status_dif = 1, + .low_pol_shift = 5, + .pha_shift = 6, + .ss_ctrl_shift = 7, + .ss_pol_shift = 8, + .max_data_rate = 0x10, + .data_mask = 0x1F, + .data_shift = 14, + .lbc = (1 << 14), + .rx_cnt_off = 4, + .rx_cnt_mask = (0xF << 4), + .reset_start = 1, +}; + +extern void gpio_spi_active(int cspi_mod); +extern void gpio_spi_inactive(int cspi_mod); + +#define MXC_SPI_BUF_RX(type) \ +void mxc_spi_buf_rx_##type(struct mxc_spi *master_drv_data, u32 val)\ +{\ + type *rx = master_drv_data->transfer.rx_buf;\ + *rx++ = (type)val;\ + master_drv_data->transfer.rx_buf = rx;\ +} + +#define MXC_SPI_BUF_TX(type) \ +u32 mxc_spi_buf_tx_##type(struct mxc_spi *master_drv_data)\ +{\ + u32 val;\ + const type *tx = master_drv_data->transfer.tx_buf;\ + val = *tx++;\ + master_drv_data->transfer.tx_buf = tx;\ + return val;\ +} + +MXC_SPI_BUF_RX(u8) + MXC_SPI_BUF_TX(u8) + MXC_SPI_BUF_RX(u16) + MXC_SPI_BUF_TX(u16) + MXC_SPI_BUF_RX(u32) + MXC_SPI_BUF_TX(u32) + +/*! + * This function enables CSPI interrupt(s) + * + * @param master_data the pointer to mxc_spi structure + * @param irqs the irq(s) to set (can be a combination) + * + * @return This function returns 0 if successful, -1 otherwise. + */ +static int spi_enable_interrupt(struct mxc_spi *master_data, unsigned int irqs) +{ + if (irqs & ~((1 << master_data->spi_ver_def->intr_bit_shift) - 1)) { + return -1; + } + + __raw_writel((irqs | __raw_readl(MXC_CSPIINT + master_data->ctrl_addr)), + MXC_CSPIINT + master_data->ctrl_addr); + return 0; +} + +/*! + * This function disables CSPI interrupt(s) + * + * @param master_data the pointer to mxc_spi structure + * @param irqs the irq(s) to reset (can be a combination) + * + * @return This function returns 0 if successful, -1 otherwise. + */ +static int spi_disable_interrupt(struct mxc_spi *master_data, unsigned int irqs) +{ + if (irqs & ~((1 << master_data->spi_ver_def->intr_bit_shift) - 1)) { + return -1; + } + + __raw_writel((~irqs & + __raw_readl(MXC_CSPIINT + master_data->ctrl_addr)), + MXC_CSPIINT + master_data->ctrl_addr); + return 0; +} + +/*! + * This function sets the baud rate for the SPI module. + * + * @param master_data the pointer to mxc_spi structure + * @param baud the baud rate + * + * @return This function returns the baud rate divisor. + */ +static unsigned int spi_find_baudrate(struct mxc_spi *master_data, + unsigned int baud) +{ + unsigned int divisor; + unsigned int shift = 0; + + /* Calculate required divisor (rounded) */ + divisor = (master_data->spi_ipg_clk + baud / 2) / baud; + while (divisor >>= 1) + shift++; + + if (master_data->spi_ver_def == &spi_ver_0_0) { + shift = (shift - 1) * 2; + } else if (master_data->spi_ver_def == &spi_ver_2_3) { + shift = shift; + } else { + shift -= 2; + } + + if (shift > master_data->spi_ver_def->max_data_rate) + shift = master_data->spi_ver_def->max_data_rate; + + return (shift << master_data->spi_ver_def->data_shift); +} + +/*! + * This function loads the transmit fifo. + * + * @param base the CSPI base address + * @param count number of words to put in the TxFIFO + * @param master_drv_data spi master structure + */ +static void spi_put_tx_data(void *base, unsigned int count, + struct mxc_spi *master_drv_data) +{ + unsigned int ctrl_reg; + unsigned int data; + int i = 0; + + /* Perform Tx transaction */ + for (i = 0; i < count; i++) { + data = master_drv_data->transfer.tx_get(master_drv_data); + __raw_writel(data, base + MXC_CSPITXDATA); + } + + ctrl_reg = __raw_readl(base + MXC_CSPICTRL); + + ctrl_reg |= master_drv_data->spi_ver_def->xch; + + __raw_writel(ctrl_reg, base + MXC_CSPICTRL); + + return; +} + +/*! + * This function configures the hardware CSPI for the current SPI device. + * It sets the word size, transfer mode, data rate for this device. + * + * @param spi the current SPI device + * @param is_active indicates whether to active/deactivate the current device + */ +void mxc_spi_chipselect(struct spi_device *spi, int is_active) +{ + struct mxc_spi *master_drv_data; + struct mxc_spi_xfer *ptransfer; + struct mxc_spi_unique_def *spi_ver_def; + unsigned int ctrl_reg = 0; + unsigned int config_reg = 0; + unsigned int xfer_len; + unsigned int cs_value; + + if (is_active == BITBANG_CS_INACTIVE) { + /*Need to deselect the slave */ + return; + } + + /* Get the master controller driver data from spi device's master */ + + master_drv_data = spi_master_get_devdata(spi->master); + clk_enable(master_drv_data->clk); + spi_ver_def = master_drv_data->spi_ver_def; + + xfer_len = spi->bits_per_word; + + if (spi_ver_def == &spi_ver_2_3) { + /* Control Register Settings for transfer to this slave */ + ctrl_reg = master_drv_data->spi_ver_def->spi_enable; + ctrl_reg |= + ((spi->chip_select & MXC_CSPICTRL_CSMASK) << spi_ver_def-> + cs_shift); + ctrl_reg |= + (((1 << (spi->chip_select & MXC_CSPICTRL_CSMASK)) & + spi_ver_def->mode_mask) << spi_ver_def->mode_shift); + ctrl_reg |= + spi_find_baudrate(master_drv_data, spi->max_speed_hz); + ctrl_reg |= + (((xfer_len - + 1) & spi_ver_def->bc_mask) << spi_ver_def->bc_shift); + + if (spi->mode & SPI_CPHA) + config_reg |= + (((1 << (spi->chip_select & MXC_CSPICTRL_CSMASK)) & + spi_ver_def->mode_mask) << + spi_ver_def->pha_shift); + + if ((spi->mode & SPI_CPOL)) { + config_reg |= + (((1 << (spi->chip_select & MXC_CSPICTRL_CSMASK)) & + spi_ver_def->mode_mask) << + spi_ver_def->low_pol_shift); + config_reg |= + (((1 << (spi->chip_select & MXC_CSPICTRL_CSMASK)) & + spi_ver_def->mode_mask) << + spi_ver_def->sclk_ctl_shift); + } + cs_value = (__raw_readl(MXC_CSPICONFIG + + master_drv_data->ctrl_addr) >> + spi_ver_def->ss_pol_shift) & spi_ver_def->mode_mask; + if (spi->mode & SPI_CS_HIGH) { + config_reg |= + ((((1 << (spi->chip_select & MXC_CSPICTRL_CSMASK)) & + spi_ver_def->mode_mask) | cs_value) << + spi_ver_def->ss_pol_shift); + } else + config_reg |= + ((~((1 << (spi->chip_select & + MXC_CSPICTRL_CSMASK)) & + spi_ver_def->mode_mask) & cs_value) << + spi_ver_def->ss_pol_shift); + config_reg |= + (((1 << (spi->chip_select & MXC_CSPICTRL_CSMASK)) & + spi_ver_def->mode_mask) << spi_ver_def->ss_ctrl_shift); + __raw_writel(0, master_drv_data->base + MXC_CSPICTRL); + __raw_writel(ctrl_reg, master_drv_data->base + MXC_CSPICTRL); + __raw_writel(config_reg, + MXC_CSPICONFIG + master_drv_data->ctrl_addr); + } else { + /* Control Register Settings for transfer to this slave */ + ctrl_reg = master_drv_data->spi_ver_def->spi_enable; + ctrl_reg |= + (((spi->chip_select & MXC_CSPICTRL_CSMASK) << spi_ver_def-> + cs_shift) | spi_ver_def->mode_mask << + spi_ver_def->mode_shift); + ctrl_reg |= + spi_find_baudrate(master_drv_data, spi->max_speed_hz); + ctrl_reg |= + (((xfer_len - + 1) & spi_ver_def->bc_mask) << spi_ver_def->bc_shift); + if (spi->mode & SPI_CPHA) + ctrl_reg |= + spi_ver_def->mode_mask << spi_ver_def->pha_shift; + if (!(spi->mode & SPI_CPOL)) + ctrl_reg |= + spi_ver_def->mode_mask << spi_ver_def-> + low_pol_shift; + if (spi->mode & SPI_CS_HIGH) + ctrl_reg |= + spi_ver_def->mode_mask << spi_ver_def->ss_pol_shift; + if (spi_ver_def == &spi_ver_0_7) + ctrl_reg |= + spi_ver_def->mode_mask << spi_ver_def-> + ss_ctrl_shift; + + __raw_writel(ctrl_reg, master_drv_data->base + MXC_CSPICTRL); + } + + /* Initialize the functions for transfer */ + ptransfer = &master_drv_data->transfer; + if (xfer_len <= 8) { + ptransfer->rx_get = mxc_spi_buf_rx_u8; + ptransfer->tx_get = mxc_spi_buf_tx_u8; + } else if (xfer_len <= 16) { + ptransfer->rx_get = mxc_spi_buf_rx_u16; + ptransfer->tx_get = mxc_spi_buf_tx_u16; + } else { + ptransfer->rx_get = mxc_spi_buf_rx_u32; + ptransfer->tx_get = mxc_spi_buf_tx_u32; + } +#ifdef CONFIG_SPI_MXC_TEST_LOOPBACK + { + struct spi_chip_info *lb_chip = + (struct spi_chip_info *)spi->controller_data; + if (!lb_chip) + __raw_writel(0, master_drv_data->test_addr); + else if (lb_chip->lb_enable) + __raw_writel(spi_ver_def->lbc, + master_drv_data->test_addr); + } +#endif + clk_disable(master_drv_data->clk); + return; +} + +/*! + * This function is called when an interrupt occurs on the SPI modules. + * It is the interrupt handler for the SPI modules. + * + * @param irq the irq number + * @param dev_id the pointer on the device + * + * @return The function returns IRQ_HANDLED when handled. + */ +static irqreturn_t mxc_spi_isr(int irq, void *dev_id) +{ + struct mxc_spi *master_drv_data = dev_id; + irqreturn_t ret = IRQ_NONE; + unsigned int status; + int fifo_size; + unsigned int pass_counter; + + fifo_size = master_drv_data->spi_ver_def->fifo_size; + pass_counter = fifo_size; + + /* Read the interrupt status register to determine the source */ + status = __raw_readl(master_drv_data->stat_addr); + do { + u32 rx_tmp = + __raw_readl(master_drv_data->base + MXC_CSPIRXDATA); + + if (master_drv_data->transfer.rx_buf) + master_drv_data->transfer.rx_get(master_drv_data, + rx_tmp); + (master_drv_data->transfer.count)--; + (master_drv_data->transfer.rx_count)--; + ret = IRQ_HANDLED; + if (pass_counter-- == 0) { + break; + } + status = __raw_readl(master_drv_data->stat_addr); + } while (status & + (1 << + (MXC_CSPISTAT_RR + + master_drv_data->spi_ver_def->int_status_dif))); + + if (master_drv_data->transfer.rx_count) + return ret; + + if (master_drv_data->transfer.count) { + if (master_drv_data->transfer.tx_buf) { + u32 count = (master_drv_data->transfer.count > + fifo_size) ? fifo_size : + master_drv_data->transfer.count; + master_drv_data->transfer.rx_count = count; + spi_put_tx_data(master_drv_data->base, count, + master_drv_data); + } + } else { + complete(&master_drv_data->xfer_done); + } + + return ret; +} + +/*! + * This function initialize the current SPI device. + * + * @param spi the current SPI device. + * + */ +int mxc_spi_setup(struct spi_device *spi) +{ + if (spi->max_speed_hz < 0) { + return -EINVAL; + } + + if (!spi->bits_per_word) + spi->bits_per_word = 8; + + pr_debug("%s: mode %d, %u bpw, %d hz\n", __FUNCTION__, + spi->mode, spi->bits_per_word, spi->max_speed_hz); + + return 0; +} + +static int mxc_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t) +{ + return 0; +} + +/*! + * This function is called when the data has to transfer from/to the + * current SPI device in poll mode + * + * @param spi the current spi device + * @param t the transfer request - read/write buffer pairs + * + * @return Returns 0 on success. + */ +int mxc_spi_poll_transfer(struct spi_device *spi, struct spi_transfer *t) +{ + struct mxc_spi *master_drv_data = NULL; + int count, i; + volatile unsigned int status; + u32 rx_tmp; + u32 fifo_size; + int chipselect_status; + + mxc_spi_chipselect(spi, BITBANG_CS_ACTIVE); + + /* Get the master controller driver data from spi device's master */ + master_drv_data = spi_master_get_devdata(spi->master); + + chipselect_status = __raw_readl(MXC_CSPICONFIG + + master_drv_data->ctrl_addr); + chipselect_status >>= master_drv_data->spi_ver_def->ss_pol_shift & + master_drv_data->spi_ver_def->mode_mask; + if (master_drv_data->chipselect_active) + master_drv_data->chipselect_active(spi->master->bus_num, + chipselect_status, + (spi->chip_select & + MXC_CSPICTRL_CSMASK) + 1); + + clk_enable(master_drv_data->clk); + + /* Modify the Tx, Rx, Count */ + master_drv_data->transfer.tx_buf = t->tx_buf; + master_drv_data->transfer.rx_buf = t->rx_buf; + master_drv_data->transfer.count = t->len; + fifo_size = master_drv_data->spi_ver_def->fifo_size; + + count = (t->len > fifo_size) ? fifo_size : t->len; + spi_put_tx_data(master_drv_data->base, count, master_drv_data); + + while ((((status = __raw_readl(master_drv_data->test_addr)) & + master_drv_data->spi_ver_def->rx_cnt_mask) >> master_drv_data-> + spi_ver_def->rx_cnt_off) != count) ; + + for (i = 0; i < count; i++) { + rx_tmp = __raw_readl(master_drv_data->base + MXC_CSPIRXDATA); + master_drv_data->transfer.rx_get(master_drv_data, rx_tmp); + } + + clk_disable(master_drv_data->clk); + if (master_drv_data->chipselect_inactive) + master_drv_data->chipselect_inactive(spi->master->bus_num, + chipselect_status, + (spi->chip_select & + MXC_CSPICTRL_CSMASK) + 1); + return 0; +} + +/*! + * This function is called when the data has to transfer from/to the + * current SPI device. It enables the Rx interrupt, initiates the transfer. + * When Rx interrupt occurs, the completion flag is set. It then disables + * the Rx interrupt. + * + * @param spi the current spi device + * @param t the transfer request - read/write buffer pairs + * + * @return Returns 0 on success -1 on failure. + */ +int mxc_spi_transfer(struct spi_device *spi, struct spi_transfer *t) +{ + struct mxc_spi *master_drv_data = NULL; + int count; + int chipselect_status; + u32 fifo_size; + + /* Get the master controller driver data from spi device's master */ + + master_drv_data = spi_master_get_devdata(spi->master); + + chipselect_status = __raw_readl(MXC_CSPICONFIG + + master_drv_data->ctrl_addr); + chipselect_status >>= master_drv_data->spi_ver_def->ss_pol_shift & + master_drv_data->spi_ver_def->mode_mask; + if (master_drv_data->chipselect_active) + master_drv_data->chipselect_active(spi->master->bus_num, + chipselect_status, + (spi->chip_select & + MXC_CSPICTRL_CSMASK) + 1); + + clk_enable(master_drv_data->clk); + /* Modify the Tx, Rx, Count */ + master_drv_data->transfer.tx_buf = t->tx_buf; + master_drv_data->transfer.rx_buf = t->rx_buf; + master_drv_data->transfer.count = t->len; + fifo_size = master_drv_data->spi_ver_def->fifo_size; + INIT_COMPLETION(master_drv_data->xfer_done); + + /* Enable the Rx Interrupts */ + + spi_enable_interrupt(master_drv_data, + 1 << (MXC_CSPIINT_RREN_SHIFT + + master_drv_data->spi_ver_def->rx_inten_dif)); + count = (t->len > fifo_size) ? fifo_size : t->len; + + /* Perform Tx transaction */ + master_drv_data->transfer.rx_count = count; + spi_put_tx_data(master_drv_data->base, count, master_drv_data); + + /* Wait for transfer completion */ + wait_for_completion(&master_drv_data->xfer_done); + + /* Disable the Rx Interrupts */ + + spi_disable_interrupt(master_drv_data, + 1 << (MXC_CSPIINT_RREN_SHIFT + + master_drv_data->spi_ver_def-> + rx_inten_dif)); + + clk_disable(master_drv_data->clk); + if (master_drv_data->chipselect_inactive) + master_drv_data->chipselect_inactive(spi->master->bus_num, + chipselect_status, + (spi->chip_select & + MXC_CSPICTRL_CSMASK) + 1); + return (t->len - master_drv_data->transfer.count); +} + +/*! + * This function releases the current SPI device's resources. + * + * @param spi the current SPI device. + * + */ +void mxc_spi_cleanup(struct spi_device *spi) +{ +} + +/*! + * This function is called during the driver binding process. Based on the CSPI + * hardware module that is being probed this function adds the appropriate SPI module + * structure in the SPI core driver. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions. + * + * @return The function returns 0 on successful registration and initialization + * of CSPI module. Otherwise returns specific error code. + */ +static int mxc_spi_probe(struct platform_device *pdev) +{ + struct mxc_spi_master *mxc_platform_info; + struct spi_master *master; + struct mxc_spi *master_drv_data = NULL; + unsigned int spi_ver; + int ret = -ENODEV; + + /* Get the platform specific data for this master device */ + + mxc_platform_info = (struct mxc_spi_master *)pdev->dev.platform_data; + if (!mxc_platform_info) { + dev_err(&pdev->dev, "can't get the platform data for CSPI\n"); + return -EINVAL; + } + + /* Allocate SPI master controller */ + + master = spi_alloc_master(&pdev->dev, sizeof(struct mxc_spi)); + if (!master) { + dev_err(&pdev->dev, "can't alloc for spi_master\n"); + return -ENOMEM; + } + + /* Set this device's driver data to master */ + + platform_set_drvdata(pdev, master); + + /* Set this master's data from platform_info */ + + master->bus_num = pdev->id + 1; + master->num_chipselect = mxc_platform_info->maxchipselect; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; +#ifdef CONFIG_SPI_MXC_TEST_LOOPBACK + master->num_chipselect += 1; +#endif + /* Set the master controller driver data for this master */ + + master_drv_data = spi_master_get_devdata(master); + master_drv_data->mxc_bitbang.master = spi_master_get(master); + if (mxc_platform_info->chipselect_active) + master_drv_data->chipselect_active = + mxc_platform_info->chipselect_active; + if (mxc_platform_info->chipselect_inactive) + master_drv_data->chipselect_inactive = + mxc_platform_info->chipselect_inactive; + + /* Identify SPI version */ + + spi_ver = mxc_platform_info->spi_version; + if (spi_ver == 7) { + master_drv_data->spi_ver_def = &spi_ver_0_7; + } else if (spi_ver == 5) { + master_drv_data->spi_ver_def = &spi_ver_0_5; + } else if (spi_ver == 4) { + master_drv_data->spi_ver_def = &spi_ver_0_4; + } else if (spi_ver == 0) { + master_drv_data->spi_ver_def = &spi_ver_0_0; + } else if (spi_ver == 23) { + master_drv_data->spi_ver_def = &spi_ver_2_3; + } + + dev_dbg(&pdev->dev, "SPI_REV 0.%d\n", spi_ver); + + /* Set the master bitbang data */ + + master_drv_data->mxc_bitbang.chipselect = mxc_spi_chipselect; + master_drv_data->mxc_bitbang.txrx_bufs = mxc_spi_transfer; + master_drv_data->mxc_bitbang.master->setup = mxc_spi_setup; + master_drv_data->mxc_bitbang.master->cleanup = mxc_spi_cleanup; + master_drv_data->mxc_bitbang.setup_transfer = mxc_spi_setup_transfer; + + /* Initialize the completion object */ + + init_completion(&master_drv_data->xfer_done); + + /* Set the master controller register addresses and irqs */ + + master_drv_data->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!master_drv_data->res) { + dev_err(&pdev->dev, "can't get platform resource for CSPI%d\n", + master->bus_num); + ret = -ENOMEM; + goto err; + } + + if (!request_mem_region(master_drv_data->res->start, + master_drv_data->res->end - + master_drv_data->res->start + 1, pdev->name)) { + dev_err(&pdev->dev, "request_mem_region failed for CSPI%d\n", + master->bus_num); + ret = -ENOMEM; + goto err; + } + + master_drv_data->base = (void *)IO_ADDRESS(master_drv_data->res->start); + if (!master_drv_data->base) { + dev_err(&pdev->dev, "invalid base address for CSPI%d\n", + master->bus_num); + ret = -EINVAL; + goto err1; + } + + master_drv_data->irq = platform_get_irq(pdev, 0); + if (master_drv_data->irq < 0) { + dev_err(&pdev->dev, "can't get IRQ for CSPI%d\n", + master->bus_num); + ret = -EINVAL; + goto err1; + } + + /* Register for SPI Interrupt */ + + ret = request_irq(master_drv_data->irq, mxc_spi_isr, + 0, "CSPI_IRQ", master_drv_data); + if (ret != 0) { + dev_err(&pdev->dev, "request_irq failed for CSPI%d\n", + master->bus_num); + goto err1; + } + + /* Setup any GPIO active */ + + gpio_spi_active(master->bus_num - 1); + + /* Enable the CSPI Clock, CSPI Module, set as a master */ + + master_drv_data->ctrl_addr = + master_drv_data->base + master_drv_data->spi_ver_def->ctrl_reg_addr; + master_drv_data->stat_addr = + master_drv_data->base + master_drv_data->spi_ver_def->stat_reg_addr; + master_drv_data->period_addr = + master_drv_data->base + + master_drv_data->spi_ver_def->period_reg_addr; + master_drv_data->test_addr = + master_drv_data->base + master_drv_data->spi_ver_def->test_reg_addr; + master_drv_data->reset_addr = + master_drv_data->base + + master_drv_data->spi_ver_def->reset_reg_addr; + + master_drv_data->clk = clk_get(&pdev->dev, "cspi_clk"); + clk_enable(master_drv_data->clk); + master_drv_data->spi_ipg_clk = clk_get_rate(master_drv_data->clk); + + __raw_writel(master_drv_data->spi_ver_def->reset_start, + master_drv_data->reset_addr); + udelay(1); + __raw_writel((master_drv_data->spi_ver_def->spi_enable + + master_drv_data->spi_ver_def->master_enable), + master_drv_data->base + MXC_CSPICTRL); + __raw_writel(MXC_CSPIPERIOD_32KHZ, master_drv_data->period_addr); + __raw_writel(0, MXC_CSPIINT + master_drv_data->ctrl_addr); + + /* Start the SPI Master Controller driver */ + + ret = spi_bitbang_start(&master_drv_data->mxc_bitbang); + + if (ret != 0) + goto err2; + + printk(KERN_INFO "CSPI: %s-%d probed\n", pdev->name, pdev->id); + +#ifdef CONFIG_SPI_MXC_TEST_LOOPBACK + { + int i; + struct spi_board_info *bi = &loopback_info[0]; + for (i = 0; i < ARRAY_SIZE(loopback_info); i++, bi++) { + if (bi->bus_num != master->bus_num) + continue; + + dev_info(&pdev->dev, + "registering loopback device '%s'\n", + bi->modalias); + + spi_new_device(master, bi); + } + } +#endif + clk_disable(master_drv_data->clk); + return ret; + + err2: + gpio_spi_inactive(master->bus_num - 1); + clk_disable(master_drv_data->clk); + clk_put(master_drv_data->clk); + free_irq(master_drv_data->irq, master_drv_data); + err1: + release_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start + 1); + err: + spi_master_put(master); + kfree(master); + platform_set_drvdata(pdev, NULL); + return ret; +} + +/*! + * Dissociates the driver from the SPI master controller. Disables the CSPI module. + * It handles the release of SPI resources like IRQ, memory,..etc. + * + * @param pdev the device structure used to give information on which SPI + * to remove + * + * @return The function always returns 0. + */ +static int mxc_spi_remove(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + + if (master) { + struct mxc_spi *master_drv_data = + spi_master_get_devdata(master); + + gpio_spi_inactive(master->bus_num - 1); + + /* Disable the CSPI module */ + clk_enable(master_drv_data->clk); + __raw_writel(MXC_CSPICTRL_DISABLE, + master_drv_data->base + MXC_CSPICTRL); + clk_disable(master_drv_data->clk); + /* Unregister for SPI Interrupt */ + + free_irq(master_drv_data->irq, master_drv_data); + + release_mem_region(master_drv_data->res->start, + master_drv_data->res->end - + master_drv_data->res->start + 1); + + /* Stop the SPI Master Controller driver */ + + spi_bitbang_stop(&master_drv_data->mxc_bitbang); + + spi_master_put(master); + } + + printk(KERN_INFO "CSPI: %s-%d removed\n", pdev->name, pdev->id); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int spi_bitbang_suspend(struct spi_bitbang *bitbang) +{ + unsigned long flags; + unsigned limit = 500; + + spin_lock_irqsave(&bitbang->lock, flags); + while (!list_empty(&bitbang->queue) && limit--) { + spin_unlock_irqrestore(&bitbang->lock, flags); + + dev_dbg(&bitbang->master->dev, "wait for queue\n"); + msleep(10); + + spin_lock_irqsave(&bitbang->lock, flags); + } + if (!list_empty(&bitbang->queue)) { + dev_err(&bitbang->master->dev, "queue didn't empty\n"); + return -EBUSY; + } + spin_unlock_irqrestore(&bitbang->lock, flags); + + return 0; +} + +static void spi_bitbang_resume(struct spi_bitbang *bitbang) +{ + spin_lock_init(&bitbang->lock); + INIT_LIST_HEAD(&bitbang->queue); + + bitbang->busy = 0; +} + +/*! + * This function puts the SPI master controller in low-power mode/state. + * + * @param pdev the device structure used to give information on which SDHC + * to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxc_spi_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct mxc_spi *master_drv_data = spi_master_get_devdata(master); + int ret = 0; + + spi_bitbang_suspend(&master_drv_data->mxc_bitbang); + clk_enable(master_drv_data->clk); + __raw_writel(MXC_CSPICTRL_DISABLE, + master_drv_data->base + MXC_CSPICTRL); + clk_disable(master_drv_data->clk); + gpio_spi_inactive(master->bus_num - 1); + + return ret; +} + +/*! + * This function brings the SPI master controller back from low-power state. + * + * @param pdev the device structure used to give information on which SDHC + * to resume + * + * @return The function always returns 0. + */ +static int mxc_spi_resume(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct mxc_spi *master_drv_data = spi_master_get_devdata(master); + + gpio_spi_active(master->bus_num - 1); + + spi_bitbang_resume(&master_drv_data->mxc_bitbang); + clk_enable(master_drv_data->clk); + __raw_writel(master_drv_data->spi_ver_def->spi_enable, + master_drv_data->base + MXC_CSPICTRL); + clk_disable(master_drv_data->clk); + return 0; +} +#else +#define mxc_spi_suspend NULL +#define mxc_spi_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_spi_driver = { + .driver = { + .name = "mxc_spi", + .bus = &platform_bus_type, + .owner = THIS_MODULE, + }, + .probe = mxc_spi_probe, + .remove = mxc_spi_remove, + .suspend_late = mxc_spi_suspend, + .resume_early = mxc_spi_resume, +}; + +/*! + * This function implements the init function of the SPI device. + * It is called when the module is loaded. It enables the required + * clocks to CSPI module(if any) and activates necessary GPIO pins. + * + * @return This function returns 0. + */ +static int __init mxc_spi_init(void) +{ + pr_debug("Registering the SPI Controller Driver\n"); + return platform_driver_register(&mxc_spi_driver); +} + +/*! + * This function implements the exit function of the SPI device. + * It is called when the module is unloaded. It deactivates the + * the GPIO pin associated with CSPI hardware modules. + * + */ +static void __exit mxc_spi_exit(void) +{ + pr_debug("Unregistering the SPI Controller Driver\n"); + platform_driver_unregister(&mxc_spi_driver); +} + +subsys_initcall(mxc_spi_init); +module_exit(mxc_spi_exit); + +MODULE_DESCRIPTION("SPI Master Controller driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi_stmp.c b/drivers/spi/spi_stmp.c new file mode 100644 index 000000000000..5b1022b24a42 --- /dev/null +++ b/drivers/spi/spi_stmp.c @@ -0,0 +1,691 @@ +/* + * Freescale STMP378X SPI master driver + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <asm/dma.h> + +#include <mach/stmp3xxx.h> +#include <mach/platform.h> +#include <mach/regs-ssp.h> +#include <mach/regs-apbh.h> +#include "spi_stmp.h" + +/* 0 means DMA modei(recommended, default), !0 - PIO mode */ +static int pio /* = 0 */; +static int debug; + +/** + * stmp_spi_init_hw + * + * Initialize the SSP port + */ +static int stmp_spi_init_hw(struct stmp_spi *ss) +{ + int err; + + err = stmp37xx_spi_pins_request((char *)dev_name(ss->master_dev), ss->id); + if (err) + goto out; + + ss->clk = clk_get(NULL, "ssp"); + if (IS_ERR(ss->clk)) { + err = PTR_ERR(ss->clk); + goto out_free_pins; + } + clk_enable(ss->clk); + + stmp3xxx_reset_block((void *)ss->regs, 0); + stmp3xxx_dma_reset_channel(ss->dma); + + return 0; + +out_free_pins: + stmp37xx_spi_pins_release((char *)dev_name(ss->master_dev), ss->id); +out: + return err; +} + +static void stmp_spi_release_hw(struct stmp_spi *ss) +{ + if (ss->clk && !IS_ERR(ss->clk)) { + clk_disable(ss->clk); + clk_put(ss->clk); + } + stmp37xx_spi_pins_release((char *)dev_name(ss->master_dev), ss->id); +} + +static int stmp_spi_setup_transfer(struct spi_device *spi, + struct spi_transfer *t) +{ + u8 bits_per_word; + u32 hz; + struct stmp_spi *ss /* = spi_master_get_devdata(spi->master) */; + u16 rate; + + ss = spi_master_get_devdata(spi->master); + + bits_per_word = spi->bits_per_word; + if (t && t->bits_per_word) + bits_per_word = t->bits_per_word; + + /* + Calculate speed: + - by default, use maximum speed from ssp clk + - if device overrides it, use it + - if transfer specifies other speed, use transfer's one + */ + hz = 1000 * ss->speed_khz / ss->divider; + if (spi->max_speed_hz) + hz = min(hz, spi->max_speed_hz); + if (t && t->speed_hz) + hz = min(hz, t->speed_hz); + + if (hz == 0) { + dev_err(&spi->dev, "Cannot continue with zero clock\n"); + return -EINVAL; + } + + if (bits_per_word != 8) { + dev_err(&spi->dev, "%s, unsupported bits_per_word=%d\n", + __func__, bits_per_word); + return -EINVAL; + } + + dev_dbg(&spi->dev, "Requested clk rate = %uHz, max = %uHz/%d = %uHz\n", + hz, ss->speed_khz, ss->divider, + ss->speed_khz * 1000 / ss->divider); + + if (ss->speed_khz * 1000 / ss->divider < hz) { + dev_err(&spi->dev, "%s, unsupported clock rate %uHz\n", + __func__, hz); + return -EINVAL; + } + + rate = 1000 * ss->speed_khz/ss->divider/hz; + + __raw_writel(BF(ss->divider, SSP_TIMING_CLOCK_DIVIDE) | + BF(rate - 1, SSP_TIMING_CLOCK_RATE), ss->regs + HW_SSP_TIMING); + + __raw_writel(BF(BV_SSP_CTRL1_SSP_MODE__SPI, SSP_CTRL1_SSP_MODE) | + BF(BV_SSP_CTRL1_WORD_LENGTH__EIGHT_BITS, SSP_CTRL1_WORD_LENGTH) | + ((spi->mode & SPI_CPOL) ? BM_SSP_CTRL1_POLARITY : 0) | + ((spi->mode & SPI_CPHA) ? BM_SSP_CTRL1_PHASE : 0) | + (pio ? 0 : BM_SSP_CTRL1_DMA_ENABLE), ss->regs + HW_SSP_CTRL1); + + stmp3xxx_setl(0x00, ss->regs + HW_SSP_CMD0); + + return 0; +} + + +static void stmp_spi_cleanup(struct spi_device *spi) +{ + struct stmp37xx_spi_platform_data *pdata = spi->dev.platform_data; + + if (pdata && pdata->hw_release) + pdata->hw_release(spi); +} + +/* the spi->mode bits understood by this driver: */ +#define MODEBITS (SPI_CPOL | SPI_CPHA) +static int stmp_spi_setup(struct spi_device *spi) +{ + struct stmp37xx_spi_platform_data *pdata; + struct stmp_spi *ss; + int err = 0; + + ss = spi_master_get_devdata(spi->master); + + if (!spi->bits_per_word) + spi->bits_per_word = 8; + + if (spi->mode & ~MODEBITS) { + dev_err(&spi->dev, "%s: unsupported mode bits %x\n", + __func__, spi->mode & ~MODEBITS); + err = -EINVAL; + goto out; + } + + dev_dbg(&spi->dev, "%s, mode %d, %u bits/w\n", + __func__, spi->mode & MODEBITS, spi->bits_per_word); + + pdata = spi->dev.platform_data; + + if (pdata && pdata->hw_init) { + err = pdata->hw_init(spi); + if (err) + goto out; + } + + err = stmp_spi_setup_transfer(spi, NULL); + if (err) + goto out2; + return 0; + +out2: + if (pdata) + pdata->hw_release(spi); +out: + dev_err(&spi->dev, "Failed to setup transfer, error = %d\n", err); + return err; +} + +static inline u32 stmp_spi_cs(unsigned cs) +{ + return ((cs & 1) ? BM_SSP_CTRL0_WAIT_FOR_CMD : 0) | + ((cs & 2) ? BM_SSP_CTRL0_WAIT_FOR_IRQ : 0); +} + +static int stmp_spi_txrx_dma(struct stmp_spi *ss, int cs, + unsigned char *buf, dma_addr_t dma_buf, int len, + int *first, int *last, int write) +{ + u32 c0 = 0; + dma_addr_t spi_buf_dma = dma_buf; + int count, status = 0; + enum dma_data_direction dir = write ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + + c0 |= (*first ? BM_SSP_CTRL0_LOCK_CS : 0); + c0 |= (*last ? BM_SSP_CTRL0_IGNORE_CRC : 0); + c0 |= (write ? 0 : BM_SSP_CTRL0_READ); + c0 |= BM_SSP_CTRL0_DATA_XFER; + + c0 |= stmp_spi_cs(cs); + + c0 |= BF(len, SSP_CTRL0_XFER_COUNT); + + if (!dma_buf) + spi_buf_dma = dma_map_single(ss->master_dev, buf, len, dir); + + ss->d.command->cmd = + BF(len, APBH_CHn_CMD_XFER_COUNT) | + BF(1, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(write ? BV_APBH_CHn_CMD_COMMAND__DMA_READ : + BV_APBH_CHn_CMD_COMMAND__DMA_WRITE, + APBH_CHn_CMD_COMMAND); + ss->d.command->pio_words[0] = c0; + ss->d.command->buf_ptr = spi_buf_dma; + + stmp3xxx_dma_reset_channel(ss->dma); + stmp3xxx_dma_clear_interrupt(ss->dma); + stmp3xxx_dma_enable_interrupt(ss->dma); + init_completion(&ss->done); + stmp3xxx_dma_go(ss->dma, &ss->d, 1); + wait_for_completion(&ss->done); + count = 10000; + while ((__raw_readl(ss->regs + HW_SSP_CTRL0) & BM_SSP_CTRL0_RUN) && count--) + continue; + if (count <= 0) { + printk(KERN_ERR"%c: timeout on line %s:%d\n", + write ? 'W':'C', __func__, __LINE__); + status = -ETIMEDOUT; + } + + if (!dma_buf) + dma_unmap_single(ss->master_dev, spi_buf_dma, len, dir); + + return status; +} + +static inline void stmp_spi_enable(struct stmp_spi *ss) +{ + stmp3xxx_setl(BM_SSP_CTRL0_LOCK_CS, ss->regs + HW_SSP_CTRL0); + stmp3xxx_clearl(BM_SSP_CTRL0_IGNORE_CRC, ss->regs + HW_SSP_CTRL0); +} + +static inline void stmp_spi_disable(struct stmp_spi *ss) +{ + stmp3xxx_clearl(BM_SSP_CTRL0_LOCK_CS, ss->regs + HW_SSP_CTRL0); + stmp3xxx_setl(BM_SSP_CTRL0_IGNORE_CRC, ss->regs + HW_SSP_CTRL0); +} + +static int stmp_spi_txrx_pio(struct stmp_spi *ss, int cs, + unsigned char *buf, int len, + int *first, int *last, int write) +{ + int count; + + if (*first) { + stmp_spi_enable(ss); + *first = 0; + } + + stmp3xxx_setl(stmp_spi_cs(cs), ss->regs + HW_SSP_CTRL0); + + while (len--) { + if (*last && len == 0) { + stmp_spi_disable(ss); + *last = 0; + } + stmp3xxx_clearl(BM_SSP_CTRL0_XFER_COUNT, ss->regs + HW_SSP_CTRL0); + stmp3xxx_setl(1, ss->regs); /* byte-by-byte */ + + if (write) + stmp3xxx_clearl(BM_SSP_CTRL0_READ, ss->regs + HW_SSP_CTRL0); + else + stmp3xxx_setl(BM_SSP_CTRL0_READ, ss->regs + HW_SSP_CTRL0); + + /* Run! */ + stmp3xxx_setl(BM_SSP_CTRL0_RUN, ss->regs + HW_SSP_CTRL0); + count = 10000; + while (((__raw_readl(ss->regs + HW_SSP_CTRL0) & BM_SSP_CTRL0_RUN) == 0) && count--) + continue; + if (count <= 0) { + printk(KERN_ERR"%c: timeout on line %s:%d\n", + write ? 'W':'C', __func__, __LINE__); + break; + } + + if (write) + __raw_writel(*buf, ss->regs + HW_SSP_DATA); + + /* Set TRANSFER */ + stmp3xxx_setl(BM_SSP_CTRL0_DATA_XFER, ss->regs + HW_SSP_CTRL0); + + if (!write) { + count = 10000; + while (count-- && + (__raw_readl(ss->regs + HW_SSP_STATUS) & + BM_SSP_STATUS_FIFO_EMPTY)) + continue; + if (count <= 0) { + printk(KERN_ERR"%c: timeout on line %s:%d\n", + write ? 'W':'C', __func__, __LINE__); + break; + } + *buf = (__raw_readl(ss->regs + HW_SSP_DATA) & 0xFF); + } + + count = 10000; + while ((__raw_readl(ss->regs + HW_SSP_CTRL0) & BM_SSP_CTRL0_RUN) && count--) + continue; + if (count <= 0) { + printk(KERN_ERR"%c: timeout on line %s:%d\n", + write ? 'W':'C', __func__, __LINE__); + break; + } + + /* advance to the next byte */ + buf++; + } + return len < 0 ? 0 : -ETIMEDOUT; +} + +static int stmp_spi_handle_message(struct stmp_spi *ss, struct spi_message *m) +{ + int first, last; + struct spi_transfer *t, *tmp_t; + int status = 0; + int cs; + + first = last = 0; + + cs = m->spi->chip_select; + + list_for_each_entry_safe(t, tmp_t, &m->transfers, transfer_list) { + + stmp_spi_setup_transfer(m->spi, t); + + if (&t->transfer_list == m->transfers.next) + first = !0; + if (&t->transfer_list == m->transfers.prev) + last = !0; + if (t->rx_buf && t->tx_buf) { + pr_debug("%s: cannot send and receive simultaneously\n", + __func__); + return -EINVAL; + } + + /* + REVISIT: + here driver completely ignores setting of t->cs_change + */ + if (t->tx_buf) { + status = pio ? + stmp_spi_txrx_pio(ss, cs, (void *)t->tx_buf, + t->len, &first, &last, 1) : + stmp_spi_txrx_dma(ss, cs, (void *)t->tx_buf, + t->tx_dma, t->len, &first, &last, 1); + if (debug) { + if (t->len < 0x10) + print_hex_dump_bytes("Tx ", + DUMP_PREFIX_OFFSET, + t->tx_buf, t->len); + else + pr_debug("Tx: %d bytes\n", t->len); + } + } + if (t->rx_buf) { + status = pio ? + stmp_spi_txrx_pio(ss, cs, t->rx_buf, + t->len, &first, &last, 0): + stmp_spi_txrx_dma(ss, cs, t->rx_buf, + t->rx_dma, t->len, &first, &last, 0); + if (debug) { + if (t->len < 0x10) + print_hex_dump_bytes("Rx ", + DUMP_PREFIX_OFFSET, + t->rx_buf, t->len); + else + pr_debug("Rx: %d bytes\n", t->len); + } + } + + if (status) + break; + + first = last = 0; + + } + return status; +} + +/** + * stmp_spi_handle + * + * The workhorse of the driver - it handles messages from the list + * + **/ +static void stmp_spi_handle(struct work_struct *w) +{ + struct stmp_spi *ss = container_of(w, struct stmp_spi, work); + unsigned long flags; + struct spi_message *m; + + BUG_ON(w == NULL); + + spin_lock_irqsave(&ss->lock, flags); + while (!list_empty(&ss->queue)) { + m = list_entry(ss->queue.next, struct spi_message, queue); + list_del_init(&m->queue); + spin_unlock_irqrestore(&ss->lock, flags); + + m->status = stmp_spi_handle_message(ss, m); + if (m->complete) + m->complete(m->context); + + spin_lock_irqsave(&ss->lock, flags); + } + spin_unlock_irqrestore(&ss->lock, flags); + + return; +} + +/** + * stmp_spi_transfer + * + * Called indirectly from spi_async, queues all the messages to + * spi_handle_message + * + * @spi: spi device + * @m: message to be queued +**/ +static int stmp_spi_transfer(struct spi_device *spi, struct spi_message *m) +{ + struct stmp_spi *ss = spi_master_get_devdata(spi->master); + unsigned long flags; + + m->status = -EINPROGRESS; + spin_lock_irqsave(&ss->lock, flags); + list_add_tail(&m->queue, &ss->queue); + queue_work(ss->workqueue, &ss->work); + spin_unlock_irqrestore(&ss->lock, flags); + return 0; +} + +static irqreturn_t stmp_spi_irq(int irq, void *dev_id) +{ + struct stmp_spi *ss = dev_id; + + stmp3xxx_dma_clear_interrupt(ss->dma); + complete(&ss->done); + return IRQ_HANDLED; +} + +static irqreturn_t stmp_spi_irq_err(int irq, void *dev_id) +{ + struct stmp_spi *ss = dev_id; + u32 c1, st; + + c1 = __raw_readl(ss->regs + HW_SSP_CTRL1); + st = __raw_readl(ss->regs + HW_SSP_STATUS); + printk(KERN_ERR"IRQ - ERROR!, status = 0x%08X, c1 = 0x%08X\n", st, c1); + stmp3xxx_clearl(c1 & 0xCCCC0000, ss->regs + HW_SSP_CTRL1); + + return IRQ_HANDLED; +} + +static int __init stmp_spi_probe(struct platform_device *dev) +{ + int err = 0; + struct spi_master *master; + struct stmp_spi *ss; + struct resource *r; + u32 mem; + + /* Get resources(memory, IRQ) associated with the device */ + master = spi_alloc_master(&dev->dev, sizeof(struct stmp_spi)); + + if (master == NULL) { + err = -ENOMEM; + goto out0; + } + + platform_set_drvdata(dev, master); + + r = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (r == NULL) { + err = -ENODEV; + goto out_put_master; + } + + ss = spi_master_get_devdata(master); + ss->master_dev = &dev->dev; + ss->id = dev->id; + + INIT_WORK(&ss->work, stmp_spi_handle); + INIT_LIST_HEAD(&ss->queue); + spin_lock_init(&ss->lock); + ss->workqueue = create_singlethread_workqueue(dev_name(&dev->dev)); + master->transfer = stmp_spi_transfer; + master->setup = stmp_spi_setup; + master->cleanup = stmp_spi_cleanup; + + if (!request_mem_region(r->start, + r->end - r->start + 1, dev_name(&dev->dev))) { + err = -ENXIO; + goto out_put_master; + } + mem = r->start; + + ss->regs = r->start - STMP3XXX_REGS_PHBASE + STMP3XXX_REGS_BASE; + + ss->irq = platform_get_irq(dev, 0); + if (ss->irq < 0) { + err = -ENXIO; + goto out_put_master; + } + + r = platform_get_resource(dev, IORESOURCE_DMA, 0); + if (r == NULL) { + err = -ENODEV; + goto out_put_master; + } + + ss->dma = r->start; + err = stmp3xxx_dma_request(ss->dma, &dev->dev, (char *)dev_name(&dev->dev)); + if (err) + goto out_put_master; + + err = stmp3xxx_dma_allocate_command(ss->dma, &ss->d); + if (err) + goto out_free_dma; + + master->bus_num = dev->id; + master->num_chipselect = 1; + + /* SPI controller initializations */ + err = stmp_spi_init_hw(ss); + if (err) { + dev_dbg(&dev->dev, "cannot initialize hardware\n"); + goto out_free_dma_desc; + } + + clk_set_rate(ss->clk, 120000); + ss->speed_khz = clk_get_rate(ss->clk); + ss->divider = 2; + dev_info(&dev->dev, "Max possible speed %d = %ld/%d kHz\n", + ss->speed_khz, clk_get_rate(ss->clk), ss->divider); + + /* Register for SPI Interrupt */ + err = request_irq(ss->irq, stmp_spi_irq, 0, + dev_name(&dev->dev), ss); + if (err) { + dev_dbg(&dev->dev, "request_irq failed, %d\n", err); + goto out_release_hw; + } + err = request_irq(IRQ_SSP_ERROR, stmp_spi_irq_err, IRQF_SHARED, + dev_name(&dev->dev), ss); + if (err) { + dev_dbg(&dev->dev, "request_irq(error) failed, %d\n", err); + goto out_free_irq; + } + + err = spi_register_master(master); + if (err) { + dev_dbg(&dev->dev, "cannot register spi master, %d\n", err); + goto out_free_irq_2; + } + dev_info(&dev->dev, "at 0x%08X mapped to 0x%08X, irq=%d, bus %d, %s\n", + mem, (u32)ss->regs, ss->irq, + master->bus_num, pio ? "PIO" : "DMA"); + return 0; + +out_free_irq_2: + free_irq(IRQ_SSP_ERROR, ss); +out_free_irq: + free_irq(ss->irq, ss); +out_free_dma_desc: + stmp3xxx_dma_free_command(ss->dma, &ss->d); +out_free_dma: + stmp3xxx_dma_release(ss->dma); +out_release_hw: + stmp_spi_release_hw(ss); +out_put_master: + spi_master_put(master); +out0: + return err; +} + +static int __devexit stmp_spi_remove(struct platform_device *dev) +{ + struct stmp_spi *ss; + struct spi_master *master; + + master = platform_get_drvdata(dev); + if (master == NULL) + goto out0; + ss = spi_master_get_devdata(master); + if (ss == NULL) + goto out1; + free_irq(ss->irq, ss); + if (ss->workqueue) + destroy_workqueue(ss->workqueue); + stmp3xxx_dma_free_command(ss->dma, &ss->d); + stmp3xxx_dma_release(ss->dma); + stmp_spi_release_hw(ss); + platform_set_drvdata(dev, 0); +out1: + spi_master_put(master); +out0: + return 0; +} + +#ifdef CONFIG_PM +static int stmp_spi_suspend(struct platform_device *pdev, pm_message_t pmsg) +{ + struct stmp_spi *ss; + struct spi_master *master; + + master = platform_get_drvdata(pdev); + ss = spi_master_get_devdata(master); + + ss->saved_timings = __raw_readl(ss->regs + HW_SSP_TIMING); + clk_disable(ss->clk); + + return 0; +} + +static int stmp_spi_resume(struct platform_device *pdev) +{ + struct stmp_spi *ss; + struct spi_master *master; + + master = platform_get_drvdata(pdev); + ss = spi_master_get_devdata(master); + + clk_enable(ss->clk); + stmp3xxx_clearl(BM_SSP_CTRL0_SFTRST | BM_SSP_CTRL0_CLKGATE, ss->regs + HW_SSP_CTRL0); + stmp3xxx_setl(ss->saved_timings, ss->regs + HW_SSP_TIMING); + + return 0; +} + +#else +#define stmp_spi_suspend NULL +#define stmp_spi_resume NULL +#endif + +static struct platform_driver stmp_spi_driver = { + .probe = stmp_spi_probe, + .remove = __devexit_p(stmp_spi_remove), + .driver = { + .name = "stmp37xx_ssp", + .owner = THIS_MODULE, + }, + .suspend = stmp_spi_suspend, + .resume = stmp_spi_resume, +}; + +static int __init stmp_spi_init(void) +{ + return platform_driver_register(&stmp_spi_driver); +} + +static void __exit stmp_spi_exit(void) +{ + platform_driver_unregister(&stmp_spi_driver); +} + +module_init(stmp_spi_init); +module_exit(stmp_spi_exit); +module_param(pio, int, S_IRUGO); +module_param(debug, int, S_IRUGO); +MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>"); +MODULE_DESCRIPTION("STMP37xx SPI/SSP"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi_stmp.h b/drivers/spi/spi_stmp.h new file mode 100644 index 000000000000..764edd6f9c9f --- /dev/null +++ b/drivers/spi/spi_stmp.h @@ -0,0 +1,51 @@ +/* + * Freescale STMP378X SPI master driver + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __SPI_STMP_H +#define __SPI_STMP_H + +#include <mach/dma.h> + +/* These two come from arch/arm/mach-xxxxx/spi.c */ +int stmp37xx_spi_pins_request(char *id, int ssp); +void stmp37xx_spi_pins_release(char *id, int ssp); + +struct stmp_spi { + int id; + + void __iomem *regs; /* vaddr of the control registers */ + + u32 irq; + u32 dma; + struct stmp3xxx_dma_descriptor d; + + u32 speed_khz; + u32 saved_timings; + u32 divider; + + struct clk *clk; + struct device *master_dev; + + struct work_struct work; + struct workqueue_struct *workqueue; + spinlock_t lock; + struct list_head queue; + + struct completion done; +}; + +#endif /* __SPI_STMP_H */ diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 19cb7d5480d7..7a4e3d3d773d 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -42,3 +42,5 @@ obj-$(CONFIG_USB) += misc/ obj-$(CONFIG_USB_ATM) += atm/ obj-$(CONFIG_USB_SPEEDTOUCH) += atm/ + +obj-$(CONFIG_USB_OTG) += otg/ diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 7f8e83a954ac..83d395de165c 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -209,17 +209,6 @@ config USB_OMAP default USB_GADGET select USB_GADGET_SELECTED -config USB_OTG - boolean "OTG Support" - depends on USB_GADGET_OMAP && ARCH_OMAP_OTG && USB_OHCI_HCD - help - The most notable feature of USB OTG is support for a - "Dual-Role" device, which can act as either a device - or a host. The initial role choice can be changed - later, when two dual-role devices talk to each other. - - Select this only if your OMAP board has a Mini-AB connector. - config USB_GADGET_PXA25X boolean "PXA 25x or IXP 4xx" depends on (ARCH_PXA && PXA25x) || ARCH_IXP4XX @@ -474,6 +463,30 @@ config USB_GOKU default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_ARC + boolean "Freescale USB Device Controller" + depends on ARCH_MXC || ARCH_STMP3XXX + select USB_GADGET_DUALSPEED if USB_GADGET_FSL_1504 || USB_GADGET_FSL_UTMI + help + Some Freescale processors have a USBOTG controller, + which supports device mode. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "arc_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_STATIC_IRAM_PPH + bool "Apply static IRAM patch" + depends on USB_GADGET_ARC && (ARCH_MX37 || ARCH_MX3 || ARCH_MX25) + help + Apply static IRAM patch to peripheral driver. + +config USB_ARC + tristate + depends on USB_GADGET_ARC + default USB_GADGET + select USB_GADGET_SELECTED + config USB_GADGET_LANGWELL boolean "Intel Langwell USB Device Controller" depends on PCI @@ -542,6 +555,69 @@ config USB_GADGET_DUALSPEED Means that gadget drivers should include extra descriptors and code to handle dual-speed controllers. +config USB_GADGET_ARC_OTG + bool "Support for DR peripheral port on Freescale controller" + depends on USB_GADGET_ARC + default y + help + Enable support for the Freescale Dual Role port in peripheral mode. + +choice + prompt "Select transceiver for DR port" + depends on USB_GADGET_ARC_OTG + help + Choose the transceiver to use with the Freescale DR port. + +config USB_GADGET_FSL_MC13783 + bool "Freescale MC13783" + depends on !USB_EHCI_FSL_1301 && !USB_EHCI_FSL_1504 && !USB_EHCI_FSL_UTMI && !MACH_MX25_3DS + ---help--- + Enable support for the Full Speed Freescale MC13783 transceiver. + + The mx27ads, mx31ads and mx32ads boards require modifications + to support this transceiver. + +config USB_GADGET_FSL_1301 + bool "Philips ISP1301" + depends on !USB_EHCI_FSL_MC13783 && !USB_EHCI_FSL_1504 && !USB_EHCI_FSL_UTMI && !MACH_MX25_3DS + ---help--- + Enable support for the Full Speed Philips ISP1301 transceiver. + + This is the factory default for the mx27ads board. + The mx31ads and mx32ads boards require modifications + to support this transceiver. + +config USB_GADGET_FSL_1504 + bool "Philips ISP1504" + depends on !USB_EHCI_FSL_MC13783 && !USB_EHCI_FSL_1301 && !USB_EHCI_FSL_UTMI && !MACH_MX25_3DS + ---help--- + Enable support for the High Speed Philips ISP1504 transceiver. + + This is the factory default for the mx31ads and mx32ads boards. + The mx27ads board requires modifications to support this transceiver. + +config USB_GADGET_FSL_UTMI + bool "On-chip UTMI" + depends on !USB_EHCI_FSL_MC13783 && !USB_EHCI_FSL_1301 && !USB_EHCI_FSL_1504 + ---help--- + Enable support for the High Speed Philips ISP1504 transceiver. + + This is the factory default for the mx35 board. + +endchoice + +config USB_OTG + boolean "OTG Support" + depends on (USB_GADGET_OMAP && ARCH_OMAP_OTG && USB_OHCI_HCD) || \ + (USB_GADGET_ARC && ARCH_MXC && USB_EHCI_HCD) + help + The most notable feature of USB OTG is support for a + "Dual-Role" device, which can act as either a device + or a host. The initial role choice can be changed + later, when two dual-role devices talk to each other. + + Select this only if your OMAP board has a Mini-AB connector. + # # USB Gadget Drivers # @@ -692,6 +768,12 @@ config USB_FILE_STORAGE Say "y" to link the driver statically, or "m" to build a dynamically linked module called "g_file_storage". +config STMP_UTP + bool "UTP over Storage Gadget" + depends on USB_FILE_STORAGE && ARCH_STMP3XXX + help + Freescale's extension to MSC protocol + config USB_FILE_STORAGE_TEST bool "File-backed Storage Gadget testing version" depends on USB_FILE_STORAGE diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index e6017e6bf6da..477114e43372 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_CI13XXX) += ci13xxx_udc.o obj-$(CONFIG_USB_S3C_HSOTG) += s3c-hsotg.o obj-$(CONFIG_USB_LANGWELL) += langwell_udc.o +obj-$(CONFIG_USB_ARC) += arcotg_udc.o # # USB gadget drivers diff --git a/drivers/usb/gadget/arcotg_udc.c b/drivers/usb/gadget/arcotg_udc.c new file mode 100644 index 000000000000..a5aeeca4afc1 --- /dev/null +++ b/drivers/usb/gadget/arcotg_udc.c @@ -0,0 +1,2964 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#undef DEBUG +#undef VERBOSE + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/mm.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/otg.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/dmapool.h> + +#include <asm/byteorder.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/unaligned.h> +#include <asm/dma.h> +#include <asm/cacheflush.h> + +#include "arcotg_udc.h" +#include <mach/arc_otg.h> + +#define DRIVER_DESC "ARC USBOTG Device Controller driver" +#define DRIVER_AUTHOR "Freescale Semiconductor" +#define DRIVER_VERSION "1 August 2005" + +#ifdef CONFIG_PPC_MPC512x +#define BIG_ENDIAN_DESC +#endif + +#ifdef BIG_ENDIAN_DESC +#define cpu_to_hc32(x) (x) +#define hc32_to_cpu(x) (x) +#else +#define cpu_to_hc32(x) cpu_to_le32((x)) +#define hc32_to_cpu(x) le32_to_cpu((x)) +#endif + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +static const char driver_name[] = "fsl-usb2-udc"; +static const char driver_desc[] = DRIVER_DESC; + +volatile static struct usb_dr_device *dr_regs; +volatile static struct usb_sys_interface *usb_sys_regs; + +/* it is initialized in probe() */ +static struct fsl_udc *udc_controller; + +#ifdef POSTPONE_FREE_LAST_DTD +static struct ep_td_struct *last_free_td; +#endif +static const struct usb_endpoint_descriptor +fsl_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = USB_MAX_CTRL_PAYLOAD, +}; +static const size_t g_iram_size = IRAM_TD_PPH_SIZE; + +typedef int (*dev_sus)(struct device *dev, pm_message_t state); +typedef int (*dev_res) (struct device *dev); +static int udc_suspend(struct fsl_udc *udc); +static int fsl_udc_suspend(struct platform_device *pdev, pm_message_t state); +static int fsl_udc_resume(struct platform_device *pdev); +static void fsl_ep_fifo_flush(struct usb_ep *_ep); + +#ifdef CONFIG_USB_OTG +/* Get platform resource from OTG driver */ +extern struct resource *otg_get_resources(void); +#endif + +extern void fsl_platform_set_test_mode(struct fsl_usb2_platform_data *pdata, enum usb_test_mode mode); + +#ifdef CONFIG_PPC32 +#define fsl_readl(addr) in_le32((addr)) +#define fsl_writel(addr, val32) out_le32((val32), (addr)) +#else +#define fsl_readl(addr) readl((addr)) +#define fsl_writel(addr, val32) writel((addr), (val32)) +#endif + +/******************************************************************** + * Internal Used Function +********************************************************************/ + +#ifdef DUMP_QUEUES +static void dump_ep_queue(struct fsl_ep *ep) +{ + int ep_index; + struct fsl_req *req; + struct ep_td_struct *dtd; + + if (list_empty(&ep->queue)) { + pr_debug("udc: empty\n"); + return; + } + + ep_index = ep_index(ep) * 2 + ep_is_in(ep); + pr_debug("udc: ep=0x%p index=%d\n", ep, ep_index); + + list_for_each_entry(req, &ep->queue, queue) { + pr_debug("udc: req=0x%p dTD count=%d\n", req, req->dtd_count); + pr_debug("udc: dTD head=0x%p tail=0x%p\n", req->head, + req->tail); + + dtd = req->head; + + while (dtd) { + if (le32_to_cpu(dtd->next_td_ptr) & DTD_NEXT_TERMINATE) + break; /* end of dTD list */ + + dtd = dtd->next_td_virt; + } + } +} +#else +static inline void dump_ep_queue(struct fsl_ep *ep) +{ +} +#endif + +/*----------------------------------------------------------------- + * done() - retire a request; caller blocked irqs + * @status : request status to be set, only works when + * request is still in progress. + *--------------------------------------------------------------*/ +static void done(struct fsl_ep *ep, struct fsl_req *req, int status) +{ + struct fsl_udc *udc = NULL; + unsigned char stopped = ep->stopped; + struct ep_td_struct *curr_td, *next_td; + int j; + + udc = (struct fsl_udc *)ep->udc; + /* Removed the req from fsl_ep->queue */ + list_del_init(&req->queue); + + /* req.status should be set as -EINPROGRESS in ep_queue() */ + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + /* Free dtd for the request */ + next_td = req->head; + for (j = 0; j < req->dtd_count; j++) { + curr_td = next_td; + if (j != req->dtd_count - 1) { + next_td = curr_td->next_td_virt; +#ifdef POSTPONE_FREE_LAST_DTD + dma_pool_free(udc->td_pool, curr_td, curr_td->td_dma); + } else { + if (last_free_td != NULL) + dma_pool_free(udc->td_pool, last_free_td, + last_free_td->td_dma); + last_free_td = curr_td; + } +#else + } + + dma_pool_free(udc->td_pool, curr_td, curr_td->td_dma); +#endif + } + + if (USE_MSC_WR(req->req.length)) { + req->req.dma -= 1; + memmove(req->req.buf, req->req.buf + 1, MSC_BULK_CB_WRAP_LEN); + } + + if (req->mapped) { + dma_unmap_single(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + req->req.dma = DMA_ADDR_INVALID; + req->mapped = 0; + } else + dma_sync_single_for_cpu(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + + if (status && (status != -ESHUTDOWN)) + VDBG("complete %s req %p stat %d len %u/%u", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + ep->stopped = 1; + + spin_unlock(&ep->udc->lock); + /* complete() is from gadget layer, + * eg fsg->bulk_in_complete() */ + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + + spin_lock(&ep->udc->lock); + ep->stopped = stopped; +} + +/*----------------------------------------------------------------- + * nuke(): delete all requests related to this ep + * called with spinlock held + *--------------------------------------------------------------*/ +static void nuke(struct fsl_ep *ep, int status) +{ + ep->stopped = 1; + + /* Flush fifo */ + fsl_ep_fifo_flush(&ep->ep); + + /* Whether this eq has request linked */ + while (!list_empty(&ep->queue)) { + struct fsl_req *req = NULL; + + req = list_entry(ep->queue.next, struct fsl_req, queue); + done(ep, req, status); + } + dump_ep_queue(ep); +} + +/*------------------------------------------------------------------ + Internal Hardware related function + ------------------------------------------------------------------*/ + +static void dr_phy_low_power_mode(struct fsl_udc *udc, bool enable) +{ + u32 temp; + + if (!device_may_wakeup(&(udc->pdata->pdev->dev))) + return; + + temp = fsl_readl(&dr_regs->portsc1); + if ((enable) && !(temp & PORTSCX_PHY_LOW_POWER_SPD)) { + temp |= PORTSCX_PHY_LOW_POWER_SPD; + fsl_writel(temp, &dr_regs->portsc1); + + if (udc_controller->pdata->usb_clock_for_pm) + udc_controller->pdata->usb_clock_for_pm(false); + } else if ((!enable) && (temp & PORTSCX_PHY_LOW_POWER_SPD)) { + if (udc_controller->pdata->usb_clock_for_pm) + udc_controller->pdata->usb_clock_for_pm(true); + + temp &= ~PORTSCX_PHY_LOW_POWER_SPD; + fsl_writel(temp, &dr_regs->portsc1); + } +} + +static int dr_controller_setup(struct fsl_udc *udc) +{ + unsigned int tmp = 0, portctrl = 0; + unsigned int __attribute((unused)) ctrl = 0; + unsigned long timeout; + struct fsl_usb2_platform_data *pdata; + +#define FSL_UDC_RESET_TIMEOUT 1000 + + /* before here, make sure dr_regs has been initialized */ + if (!udc) + return -EINVAL; + pdata = udc->pdata; + + /* Stop and reset the usb controller */ + tmp = fsl_readl(&dr_regs->usbcmd); + tmp &= ~USB_CMD_RUN_STOP; + fsl_writel(tmp, &dr_regs->usbcmd); + + tmp = fsl_readl(&dr_regs->usbcmd); + tmp |= USB_CMD_CTRL_RESET; + fsl_writel(tmp, &dr_regs->usbcmd); + + /* Wait for reset to complete */ + timeout = jiffies + FSL_UDC_RESET_TIMEOUT; + while (fsl_readl(&dr_regs->usbcmd) & USB_CMD_CTRL_RESET) { + if (time_after(jiffies, timeout)) { + ERR("udc reset timeout! \n"); + return -ETIMEDOUT; + } + cpu_relax(); + } + + /* Set the controller as device mode */ + tmp = fsl_readl(&dr_regs->usbmode); + tmp &= ~USB_MODE_CTRL_MODE_MASK; /* clear mode bits */ + tmp |= USB_MODE_CTRL_MODE_DEVICE; + /* Disable Setup Lockout */ + tmp |= USB_MODE_SETUP_LOCK_OFF; + if (pdata->es) + tmp |= USB_MODE_ES; + fsl_writel(tmp, &dr_regs->usbmode); + + fsl_platform_set_device_mode(pdata); + + /* Clear the setup status */ + fsl_writel(0, &dr_regs->usbsts); + + tmp = udc->ep_qh_dma; + tmp &= USB_EP_LIST_ADDRESS_MASK; + fsl_writel(tmp, &dr_regs->endpointlistaddr); + + VDBG("vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x", + (int)udc->ep_qh, (int)tmp, + fsl_readl(&dr_regs->endpointlistaddr)); + + /* Config PHY interface */ + portctrl = fsl_readl(&dr_regs->portsc1); + portctrl &= ~(PORTSCX_PHY_TYPE_SEL | PORTSCX_PORT_WIDTH); + switch (udc->phy_mode) { + case FSL_USB2_PHY_ULPI: + portctrl |= PORTSCX_PTS_ULPI; + break; + case FSL_USB2_PHY_UTMI_WIDE: + portctrl |= PORTSCX_PTW_16BIT; + /* fall through */ + case FSL_USB2_PHY_UTMI: + portctrl |= PORTSCX_PTS_UTMI; + break; + case FSL_USB2_PHY_SERIAL: + portctrl |= PORTSCX_PTS_FSLS; + break; + default: + return -EINVAL; + } + fsl_writel(portctrl, &dr_regs->portsc1); + + if (pdata->change_ahb_burst) { + /* if usb should not work in default INCRx mode */ + tmp = fsl_readl(&dr_regs->sbuscfg); + tmp = (tmp & ~0x07) | pdata->ahb_burst_mode; + fsl_writel(tmp, &dr_regs->sbuscfg); + } + + if (pdata->have_sysif_regs) { + /* Config control enable i/o output, cpu endian register */ + ctrl = __raw_readl(&usb_sys_regs->control); + ctrl |= USB_CTRL_IOENB; + __raw_writel(ctrl, &usb_sys_regs->control); + } + +#if defined(CONFIG_PPC32) && !defined(CONFIG_NOT_COHERENT_CACHE) + /* Turn on cache snooping hardware, since some PowerPC platforms + * wholly rely on hardware to deal with cache coherent. */ + + if (pdata->have_sysif_regs) { + /* Setup Snooping for all the 4GB space */ + tmp = SNOOP_SIZE_2GB; /* starts from 0x0, size 2G */ + __raw_writel(tmp, &usb_sys_regs->snoop1); + tmp |= 0x80000000; /* starts from 0x8000000, size 2G */ + __raw_writel(tmp, &usb_sys_regs->snoop2); + } +#endif + + return 0; +} + +/* Enable DR irq and set controller to run state */ +static void dr_controller_run(struct fsl_udc *udc) +{ + u32 temp; + + fsl_platform_pullup_enable(udc->pdata); + + /* Enable DR irq reg */ + temp = USB_INTR_INT_EN | USB_INTR_ERR_INT_EN + | USB_INTR_PTC_DETECT_EN | USB_INTR_RESET_EN + | USB_INTR_DEVICE_SUSPEND | USB_INTR_SYS_ERR_EN; + + fsl_writel(temp, &dr_regs->usbintr); + + /* Clear stopped bit */ + udc->stopped = 0; + + /* Set controller to Run */ + if (udc->driver) { + temp = fsl_readl(&dr_regs->usbcmd); + temp |= USB_CMD_RUN_STOP; + fsl_writel(temp, &dr_regs->usbcmd); + } + + return; +} + +static void dr_controller_stop(struct fsl_udc *udc) +{ + unsigned int tmp; + + pr_debug("%s\n", __func__); + + /* if we're in OTG mode, and the Host is currently using the port, + * stop now and don't rip the controller out from under the + * ehci driver + */ + if (udc->gadget.is_otg) { + if (!(fsl_readl(&dr_regs->otgsc) & OTGSC_STS_USB_ID)) { + pr_debug("udc: Leaving early\n"); + return; + } + } + + /* disable all INTR */ + fsl_writel(0, &dr_regs->usbintr); + + /* Set stopped bit for isr */ + udc->stopped = 1; + + /* disable IO output */ +/* usb_sys_regs->control = 0; */ + + fsl_platform_pullup_disable(udc->pdata); + + /* set controller to Stop */ + tmp = fsl_readl(&dr_regs->usbcmd); + tmp &= ~USB_CMD_RUN_STOP; + fsl_writel(tmp, &dr_regs->usbcmd); + + return; +} + +void dr_ep_setup(unsigned char ep_num, unsigned char dir, unsigned char ep_type) +{ + unsigned int tmp_epctrl = 0; + + tmp_epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (dir) { + if (ep_num) + tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; + tmp_epctrl |= EPCTRL_TX_ENABLE; + tmp_epctrl |= ((unsigned int)(ep_type) + << EPCTRL_TX_EP_TYPE_SHIFT); + } else { + if (ep_num) + tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; + tmp_epctrl |= EPCTRL_RX_ENABLE; + tmp_epctrl |= ((unsigned int)(ep_type) + << EPCTRL_RX_EP_TYPE_SHIFT); + } + + fsl_writel(tmp_epctrl, &dr_regs->endptctrl[ep_num]); +} + +static void +dr_ep_change_stall(unsigned char ep_num, unsigned char dir, int value) +{ + u32 tmp_epctrl = 0; + + tmp_epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + + if (value) { + /* set the stall bit */ + if (dir) + tmp_epctrl |= EPCTRL_TX_EP_STALL; + else + tmp_epctrl |= EPCTRL_RX_EP_STALL; + } else { + /* clear the stall bit and reset data toggle */ + if (dir) { + tmp_epctrl &= ~EPCTRL_TX_EP_STALL; + tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; + } else { + tmp_epctrl &= ~EPCTRL_RX_EP_STALL; + tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; + } + } + fsl_writel(tmp_epctrl, &dr_regs->endptctrl[ep_num]); +} + +/* Get stall status of a specific ep + Return: 0: not stalled; 1:stalled */ +static int dr_ep_get_stall(unsigned char ep_num, unsigned char dir) +{ + u32 epctrl; + + epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (dir) + return (epctrl & EPCTRL_TX_EP_STALL) ? 1 : 0; + else + return (epctrl & EPCTRL_RX_EP_STALL) ? 1 : 0; +} + +/******************************************************************** + Internal Structure Build up functions +********************************************************************/ + +/*------------------------------------------------------------------ +* struct_ep_qh_setup(): set the Endpoint Capabilites field of QH + * @zlt: Zero Length Termination Select (1: disable; 0: enable) + * @mult: Mult field + ------------------------------------------------------------------*/ +static void struct_ep_qh_setup(struct fsl_udc *udc, unsigned char ep_num, + unsigned char dir, unsigned char ep_type, + unsigned int max_pkt_len, + unsigned int zlt, unsigned char mult) +{ + struct ep_queue_head *p_QH = &udc->ep_qh[2 * ep_num + dir]; + unsigned int tmp = 0; + + /* set the Endpoint Capabilites in QH */ + switch (ep_type) { + case USB_ENDPOINT_XFER_CONTROL: + /* Interrupt On Setup (IOS). for control ep */ + tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) + | EP_QUEUE_HEAD_IOS; + break; + case USB_ENDPOINT_XFER_ISOC: + tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) + | (mult << EP_QUEUE_HEAD_MULT_POS); + break; + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + tmp = max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS; + break; + default: + VDBG("error ep type is %d", ep_type); + return; + } + if (zlt) + tmp |= EP_QUEUE_HEAD_ZLT_SEL; + p_QH->max_pkt_length = cpu_to_hc32(tmp); + + return; +} + +/* Setup qh structure and ep register for ep0. */ +static void ep0_setup(struct fsl_udc *udc) +{ + /* the intialization of an ep includes: fields in QH, Regs, + * fsl_ep struct */ + struct_ep_qh_setup(udc, 0, USB_RECV, USB_ENDPOINT_XFER_CONTROL, + USB_MAX_CTRL_PAYLOAD, 0, 0); + struct_ep_qh_setup(udc, 0, USB_SEND, USB_ENDPOINT_XFER_CONTROL, + USB_MAX_CTRL_PAYLOAD, 0, 0); + dr_ep_setup(0, USB_RECV, USB_ENDPOINT_XFER_CONTROL); + dr_ep_setup(0, USB_SEND, USB_ENDPOINT_XFER_CONTROL); + + return; + +} + +/*********************************************************************** + Endpoint Management Functions +***********************************************************************/ + +/*------------------------------------------------------------------------- + * when configurations are set, or when interface settings change + * for example the do_set_interface() in gadget layer, + * the driver will enable or disable the relevant endpoints + * ep0 doesn't use this routine. It is always enabled. +-------------------------------------------------------------------------*/ +static int fsl_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct fsl_udc *udc = NULL; + struct fsl_ep *ep = NULL; + unsigned short max = 0; + unsigned char mult = 0, zlt; + int retval = -EINVAL; + unsigned long flags = 0; + + ep = container_of(_ep, struct fsl_ep, ep); + + pr_debug("udc: %s ep.name=%s\n", __func__, ep->ep.name); + /* catch various bogus parameters */ + if (!_ep || !desc || ep->desc + || (desc->bDescriptorType != USB_DT_ENDPOINT)) + return -EINVAL; + + udc = ep->udc; + + if (!udc->driver || (udc->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + + max = le16_to_cpu(desc->wMaxPacketSize); + + /* Disable automatic zlp generation. Driver is reponsible to indicate + * explicitly through req->req.zero. This is needed to enable multi-td + * request. */ + zlt = 1; + + /* Assume the max packet size from gadget is always correct */ + switch (desc->bmAttributes & 0x03) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + /* mult = 0. Execute N Transactions as demonstrated by + * the USB variable length packet protocol where N is + * computed using the Maximum Packet Length (dQH) and + * the Total Bytes field (dTD) */ + mult = 0; + break; + case USB_ENDPOINT_XFER_ISOC: + /* Calculate transactions needed for high bandwidth iso */ + mult = (unsigned char)(1 + ((max >> 11) & 0x03)); + max = max & 0x8ff; /* bit 0~10 */ + /* 3 transactions at most */ + if (mult > 3) + goto en_done; + break; + default: + goto en_done; + } + + spin_lock_irqsave(&udc->lock, flags); + ep->ep.maxpacket = max; + ep->desc = desc; + ep->stopped = 0; + + /* Controller related setup */ + /* Init EPx Queue Head (Ep Capabilites field in QH + * according to max, zlt, mult) */ + struct_ep_qh_setup(udc, (unsigned char) ep_index(ep), + (unsigned char) ((desc->bEndpointAddress & USB_DIR_IN) + ? USB_SEND : USB_RECV), + (unsigned char) (desc->bmAttributes + & USB_ENDPOINT_XFERTYPE_MASK), + max, zlt, mult); + + /* Init endpoint ctrl register */ + dr_ep_setup((unsigned char) ep_index(ep), + (unsigned char) ((desc->bEndpointAddress & USB_DIR_IN) + ? USB_SEND : USB_RECV), + (unsigned char) (desc->bmAttributes + & USB_ENDPOINT_XFERTYPE_MASK)); + + spin_unlock_irqrestore(&udc->lock, flags); + retval = 0; + + VDBG("enabled %s (ep%d%s) maxpacket %d", ep->ep.name, + ep->desc->bEndpointAddress & 0x0f, + (desc->bEndpointAddress & USB_DIR_IN) + ? "in" : "out", max); +en_done: + return retval; +} + +/*--------------------------------------------------------------------- + * @ep : the ep being unconfigured. May not be ep0 + * Any pending and uncomplete req will complete with status (-ESHUTDOWN) +*---------------------------------------------------------------------*/ +static int fsl_ep_disable(struct usb_ep *_ep) +{ + struct fsl_udc *udc = NULL; + struct fsl_ep *ep = NULL; + unsigned long flags = 0; + u32 epctrl; + int ep_num; + + ep = container_of(_ep, struct fsl_ep, ep); + if (!_ep || !ep->desc) { + VDBG("%s not enabled", _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + /* disable ep on controller */ + ep_num = ep_index(ep); + epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (ep_is_in(ep)) + epctrl &= ~EPCTRL_TX_ENABLE; + else + epctrl &= ~EPCTRL_RX_ENABLE; + fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); + + udc = (struct fsl_udc *)ep->udc; + spin_lock_irqsave(&udc->lock, flags); + + /* nuke all pending requests (does flush) */ + nuke(ep, -ESHUTDOWN); + + ep->desc = 0; + ep->stopped = 1; + spin_unlock_irqrestore(&udc->lock, flags); + + VDBG("disabled %s OK", _ep->name); + return 0; +} + +/*--------------------------------------------------------------------- + * allocate a request object used by this endpoint + * the main operation is to insert the req->queue to the eq->queue + * Returns the request, or null if one could not be allocated +*---------------------------------------------------------------------*/ +static struct usb_request * +fsl_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct fsl_req *req = NULL; + + req = kzalloc(sizeof *req, gfp_flags); + if (!req) + return NULL; + + req->req.dma = DMA_ADDR_INVALID; + pr_debug("udc: req=0x%p set req.dma=0x%x\n", req, req->req.dma); + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void fsl_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct fsl_req *req = NULL; + + req = container_of(_req, struct fsl_req, req); + + if (_req) + kfree(req); +} + +static void update_qh(struct fsl_req *req) +{ + struct fsl_ep *ep = req->ep; + int i = ep_index(ep) * 2 + ep_is_in(ep); + u32 temp; + struct ep_queue_head *dQH = &ep->udc->ep_qh[i]; + + /* Write dQH next pointer and terminate bit to 0 */ + temp = req->head->td_dma & EP_QUEUE_HEAD_NEXT_POINTER_MASK; + if (NEED_IRAM(req->ep)) { + /* set next dtd stop bit,ensure only one dtd in this list */ + req->cur->next_td_ptr |= cpu_to_hc32(DTD_NEXT_TERMINATE); + temp = req->cur->td_dma & EP_QUEUE_HEAD_NEXT_POINTER_MASK; + } + dQH->next_dtd_ptr = cpu_to_hc32(temp); + /* Clear active and halt bit */ + temp = cpu_to_hc32(~(EP_QUEUE_HEAD_STATUS_ACTIVE + | EP_QUEUE_HEAD_STATUS_HALT)); + dQH->size_ioc_int_sts &= temp; + + /* Prime endpoint by writing 1 to ENDPTPRIME */ + temp = ep_is_in(ep) + ? (1 << (ep_index(ep) + 16)) + : (1 << (ep_index(ep))); + fsl_writel(temp, &dr_regs->endpointprime); +} + +/*-------------------------------------------------------------------------*/ +static int fsl_queue_td(struct fsl_ep *ep, struct fsl_req *req) +{ + u32 temp, bitmask, tmp_stat; + + /* VDBG("QH addr Register 0x%8x", dr_regs->endpointlistaddr); + VDBG("ep_qh[%d] addr is 0x%8x", i, (u32)&(ep->udc->ep_qh[i])); */ + + bitmask = ep_is_in(ep) + ? (1 << (ep_index(ep) + 16)) + : (1 << (ep_index(ep))); + + /* check if the pipe is empty */ + if (!(list_empty(&ep->queue))) { + /* Add td to the end */ + struct fsl_req *lastreq; + lastreq = list_entry(ep->queue.prev, struct fsl_req, queue); + if (NEED_IRAM(ep)) { + /* only one dtd in dqh */ + lastreq->tail->next_td_ptr = + cpu_to_hc32(req->head->td_dma | DTD_NEXT_TERMINATE); + goto out; + } else { + lastreq->tail->next_td_ptr = + cpu_to_hc32(req->head->td_dma & DTD_ADDR_MASK); + } + /* Read prime bit, if 1 goto done */ + if (fsl_readl(&dr_regs->endpointprime) & bitmask) + goto out; + do { + /* Set ATDTW bit in USBCMD */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp | USB_CMD_ATDTW, &dr_regs->usbcmd); + + /* Read correct status bit */ + tmp_stat = fsl_readl(&dr_regs->endptstatus) & bitmask; + + } while (!(fsl_readl(&dr_regs->usbcmd) & USB_CMD_ATDTW)); + + /* Write ATDTW bit to 0 */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp & ~USB_CMD_ATDTW, &dr_regs->usbcmd); + + if (tmp_stat) + goto out; + } + update_qh(req); +out: + return 0; +} + +/* Fill in the dTD structure + * @req: request that the transfer belongs to + * @length: return actually data length of the dTD + * @dma: return dma address of the dTD + * @is_last: return flag if it is the last dTD of the request + * return: pointer to the built dTD */ +static struct ep_td_struct *fsl_build_dtd(struct fsl_req *req, unsigned *length, + dma_addr_t *dma, int *is_last) +{ + u32 swap_temp; + struct ep_td_struct *dtd; + + /* how big will this transfer be? */ + *length = min(req->req.length - req->req.actual, + (unsigned)EP_MAX_LENGTH_TRANSFER); + if (NEED_IRAM(req->ep)) + *length = min(*length, g_iram_size); + dtd = dma_pool_alloc(udc_controller->td_pool, GFP_KERNEL, dma); + if (dtd == NULL) + return dtd; + + dtd->td_dma = *dma; + /* Clear reserved field */ + swap_temp = hc32_to_cpu(dtd->size_ioc_sts); + swap_temp &= ~DTD_RESERVED_FIELDS; + dtd->size_ioc_sts = cpu_to_hc32(swap_temp); + + /* Init all of buffer page pointers */ + swap_temp = (u32) (req->req.dma + req->req.actual); + if (NEED_IRAM(req->ep)) + swap_temp = (u32) (req->req.dma); + dtd->buff_ptr0 = cpu_to_hc32(swap_temp); + dtd->buff_ptr1 = cpu_to_hc32(swap_temp + 0x1000); + dtd->buff_ptr2 = cpu_to_hc32(swap_temp + 0x2000); + dtd->buff_ptr3 = cpu_to_hc32(swap_temp + 0x3000); + dtd->buff_ptr4 = cpu_to_hc32(swap_temp + 0x4000); + + req->req.actual += *length; + + /* zlp is needed if req->req.zero is set */ + if (req->req.zero) { + if (*length == 0 || (*length % req->ep->ep.maxpacket) != 0) + *is_last = 1; + else + *is_last = 0; + } else if (req->req.length == req->req.actual) + *is_last = 1; + else + *is_last = 0; + + if ((*is_last) == 0) + VDBG("multi-dtd request!\n"); + /* Fill in the transfer size; set active bit */ + swap_temp = ((*length << DTD_LENGTH_BIT_POS) | DTD_STATUS_ACTIVE); + + /* Enable interrupt for the last dtd of a request */ + if (*is_last && !req->req.no_interrupt) + swap_temp |= DTD_IOC; + if (NEED_IRAM(req->ep)) + swap_temp |= DTD_IOC; + + dtd->size_ioc_sts = cpu_to_hc32(swap_temp); + + mb(); + + VDBG("length = %d address= 0x%x", *length, (int)*dma); + + return dtd; +} + +/* Generate dtd chain for a request */ +static int fsl_req_to_dtd(struct fsl_req *req) +{ + unsigned count; + int is_last; + int is_first = 1; + struct ep_td_struct *last_dtd = NULL, *dtd; + dma_addr_t dma; + + if (NEED_IRAM(req->ep)) { + req->oridma = req->req.dma; + /* here, replace user buffer to iram buffer */ + if (ep_is_in(req->ep)) { + req->req.dma = req->ep->udc->iram_buffer[1]; + if ((list_empty(&req->ep->queue))) { + /* copy data only when no bulk in transfer is + running */ + memcpy((char *)req->ep->udc->iram_buffer_v[1], + req->req.buf, min(req->req.length, + g_iram_size)); + } + } else { + req->req.dma = req->ep->udc->iram_buffer[0]; + } + } + + if (USE_MSC_WR(req->req.length)) + req->req.dma += 1; + + do { + dtd = fsl_build_dtd(req, &count, &dma, &is_last); + if (dtd == NULL) + return -ENOMEM; + + if (is_first) { + is_first = 0; + req->head = dtd; + } else { + last_dtd->next_td_ptr = cpu_to_hc32(dma); + last_dtd->next_td_virt = dtd; + } + last_dtd = dtd; + + req->dtd_count++; + } while (!is_last); + + dtd->next_td_ptr = cpu_to_hc32(DTD_NEXT_TERMINATE); + req->cur = req->head; + req->tail = dtd; + + return 0; +} + +/* queues (submits) an I/O request to an endpoint */ +static int +fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct fsl_ep *ep = container_of(_ep, struct fsl_ep, ep); + struct fsl_req *req = container_of(_req, struct fsl_req, req); + struct fsl_udc *udc; + unsigned long flags; + int is_iso = 0; + + /* catch various bogus parameters */ + if (!_req || !req->req.buf || (ep_index(ep) + && !list_empty(&req->queue))) { + VDBG("%s, bad params\n", __func__); + return -EINVAL; + } + if (!_ep || (!ep->desc && ep_index(ep))) { + VDBG("%s, bad ep\n", __func__); + return -EINVAL; + } + if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + if (req->req.length > ep->ep.maxpacket) + return -EMSGSIZE; + is_iso = 1; + } + + udc = ep->udc; + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + req->ep = ep; + + /* map virtual address to hardware */ + if (req->req.dma == DMA_ADDR_INVALID) { + req->req.dma = dma_map_single(ep->udc->gadget.dev.parent, + req->req.buf, + req->req.length, ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + req->mapped = 1; + } else { + dma_sync_single_for_device(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + req->mapped = 0; + } + + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->dtd_count = 0; + if (NEED_IRAM(ep)) { + req->last_one = 0; + req->buffer_offset = 0; + } + + spin_lock_irqsave(&udc->lock, flags); + + /* build dtds and push them to device queue */ + if (!fsl_req_to_dtd(req)) { + fsl_queue_td(ep, req); + } else { + spin_unlock_irqrestore(&udc->lock, flags); + return -ENOMEM; + } + + /* irq handler advances the queue */ + if (req != NULL) + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +/* dequeues (cancels, unlinks) an I/O request from an endpoint */ +static int fsl_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct fsl_ep *ep = container_of(_ep, struct fsl_ep, ep); + struct fsl_req *req; + unsigned long flags; + int ep_num, stopped, ret = 0; + u32 epctrl; + + if (!_ep || !_req) + return -EINVAL; + + spin_lock_irqsave(&ep->udc->lock, flags); + stopped = ep->stopped; + + /* Stop the ep before we deal with the queue */ + ep->stopped = 1; + ep_num = ep_index(ep); + epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (ep_is_in(ep)) + epctrl &= ~EPCTRL_TX_ENABLE; + else + epctrl &= ~EPCTRL_RX_ENABLE; + fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + ret = -EINVAL; + goto out; + } + + /* The request is in progress, or completed but not dequeued */ + if (ep->queue.next == &req->queue) { + _req->status = -ECONNRESET; + fsl_ep_fifo_flush(_ep); /* flush current transfer */ + + /* The request isn't the last request in this ep queue */ + if (req->queue.next != &ep->queue) { + struct ep_queue_head *qh; + struct fsl_req *next_req; + + qh = ep->qh; + next_req = list_entry(req->queue.next, struct fsl_req, + queue); + + /* Point the QH to the first TD of next request */ + fsl_writel((u32) next_req->head, &qh->curr_dtd_ptr); + } + + /* The request hasn't been processed, patch up the TD chain */ + } else { + struct fsl_req *prev_req; + + prev_req = list_entry(req->queue.prev, struct fsl_req, queue); + fsl_writel(fsl_readl(&req->tail->next_td_ptr), + &prev_req->tail->next_td_ptr); + + } + + done(ep, req, -ECONNRESET); + + /* Enable EP */ +out: epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (ep_is_in(ep)) + epctrl |= EPCTRL_TX_ENABLE; + else + epctrl |= EPCTRL_RX_ENABLE; + fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); + ep->stopped = stopped; + + spin_unlock_irqrestore(&ep->udc->lock, flags); + return ret; +} + +/*-------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------- + * modify the endpoint halt feature + * @ep: the non-isochronous endpoint being stalled + * @value: 1--set halt 0--clear halt + * Returns zero, or a negative error code. +*----------------------------------------------------------------*/ +static int fsl_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct fsl_ep *ep = NULL; + unsigned long flags = 0; + int status = -EOPNOTSUPP; /* operation not supported */ + unsigned char ep_dir = 0, ep_num = 0; + struct fsl_udc *udc = NULL; + + ep = container_of(_ep, struct fsl_ep, ep); + udc = ep->udc; + if (!_ep || !ep->desc) { + status = -EINVAL; + goto out; + } + + if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + status = -EOPNOTSUPP; + goto out; + } + + /* Attempt to halt IN ep will fail if any transfer requests + * are still queue */ + if (value && ep_is_in(ep) && !list_empty(&ep->queue)) { + status = -EAGAIN; + goto out; + } + + status = 0; + ep_dir = ep_is_in(ep) ? USB_SEND : USB_RECV; + ep_num = (unsigned char)(ep_index(ep)); + spin_lock_irqsave(&ep->udc->lock, flags); + dr_ep_change_stall(ep_num, ep_dir, value); + spin_unlock_irqrestore(&ep->udc->lock, flags); + + if (ep_index(ep) == 0) { + udc->ep0_dir = 0; + } +out: + VDBG(" %s %s halt stat %d", ep->ep.name, + value ? "set" : "clear", status); + + return status; +} + +static int arcotg_fifo_status(struct usb_ep *_ep) +{ + struct fsl_ep *ep; + struct fsl_udc *udc; + int size = 0; + u32 bitmask; + struct ep_queue_head *d_qh; + + ep = container_of(_ep, struct fsl_ep, ep); + if (!_ep || (!ep->desc && ep_index(ep) != 0)) + return -ENODEV; + + udc = (struct fsl_udc *)ep->udc; + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + d_qh = &ep->udc->ep_qh[ep_index(ep) * 2 + ep_is_in(ep)]; + + bitmask = (ep_is_in(ep)) ? (1 << (ep_index(ep) + 16)) : + (1 << (ep_index(ep))); + + if (fsl_readl(&dr_regs->endptstatus) & bitmask) + size = (d_qh->size_ioc_int_sts & DTD_PACKET_SIZE) + >> DTD_LENGTH_BIT_POS; + + pr_debug("%s %u\n", __func__, size); + return size; +} + +static void fsl_ep_fifo_flush(struct usb_ep *_ep) +{ + struct fsl_ep *ep; + int ep_num, ep_dir; + u32 bits; + unsigned long timeout; +#define FSL_UDC_FLUSH_TIMEOUT 1000 + + if (!_ep) { + return; + } else { + ep = container_of(_ep, struct fsl_ep, ep); + if (!ep->desc) + return; + } + ep_num = ep_index(ep); + ep_dir = ep_is_in(ep) ? USB_SEND : USB_RECV; + + if (ep_num == 0) + bits = (1 << 16) | 1; + else if (ep_dir == USB_SEND) + bits = 1 << (16 + ep_num); + else + bits = 1 << ep_num; + + timeout = jiffies + FSL_UDC_FLUSH_TIMEOUT; + do { + fsl_writel(bits, &dr_regs->endptflush); + + /* Wait until flush complete */ + while (fsl_readl(&dr_regs->endptflush)) { + if (time_after(jiffies, timeout)) { + ERR("ep flush timeout\n"); + return; + } + cpu_relax(); + } + /* See if we need to flush again */ + } while (fsl_readl(&dr_regs->endptstatus) & bits); +} + +static struct usb_ep_ops fsl_ep_ops = { + .enable = fsl_ep_enable, + .disable = fsl_ep_disable, + + .alloc_request = fsl_alloc_request, + .free_request = fsl_free_request, + + .queue = fsl_ep_queue, + .dequeue = fsl_ep_dequeue, + + .set_halt = fsl_ep_set_halt, + .fifo_status = arcotg_fifo_status, + .fifo_flush = fsl_ep_fifo_flush, /* flush fifo */ +}; + +/*------------------------------------------------------------------------- + Gadget Driver Layer Operations +-------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------- + * Get the current frame number (from DR frame_index Reg ) + *----------------------------------------------------------------------*/ +static int fsl_get_frame(struct usb_gadget *gadget) +{ + return (int)(fsl_readl(&dr_regs->frindex) & USB_FRINDEX_MASKS); +} + +/*----------------------------------------------------------------------- + * Tries to wake up the host connected to this gadget + -----------------------------------------------------------------------*/ +static int fsl_wakeup(struct usb_gadget *gadget) +{ + struct fsl_udc *udc = container_of(gadget, struct fsl_udc, gadget); + u32 portsc; + + /* Remote wakeup feature not enabled by host */ + if (!udc->remote_wakeup) + return -ENOTSUPP; + + portsc = fsl_readl(&dr_regs->portsc1); + /* not suspended? */ + if (!(portsc & PORTSCX_PORT_SUSPEND)) + return 0; + /* trigger force resume */ + portsc |= PORTSCX_PORT_FORCE_RESUME; + fsl_writel(portsc, &dr_regs->portsc1); + return 0; +} + +static int can_pullup(struct fsl_udc *udc) +{ + return udc->driver && udc->softconnect && udc->vbus_active; +} + +/* Notify controller that VBUS is powered, Called by whatever + detects VBUS sessions */ +static int fsl_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct fsl_udc *udc; + unsigned long flags; + + udc = container_of(gadget, struct fsl_udc, gadget); + spin_lock_irqsave(&udc->lock, flags); + VDBG("VBUS %s\n", is_active ? "on" : "off"); + udc->vbus_active = (is_active != 0); + if (can_pullup(udc)) + fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + else + fsl_writel((fsl_readl(&dr_regs->usbcmd) & ~USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +/* constrain controller's VBUS power usage + * This call is used by gadget drivers during SET_CONFIGURATION calls, + * reporting how much power the device may consume. For example, this + * could affect how quickly batteries are recharged. + * + * Returns zero on success, else negative errno. + */ +static int fsl_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + struct fsl_udc *udc; + + udc = container_of(gadget, struct fsl_udc, gadget); + if (udc->transceiver) + return otg_set_power(udc->transceiver, mA); + return -ENOTSUPP; +} + +/* Change Data+ pullup status + * this func is used by usb_gadget_connect/disconnet + */ +static int fsl_pullup(struct usb_gadget *gadget, int is_on) +{ + struct fsl_udc *udc; + + udc = container_of(gadget, struct fsl_udc, gadget); + udc->softconnect = (is_on != 0); + if (can_pullup(udc)) + fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + else + fsl_writel((fsl_readl(&dr_regs->usbcmd) & ~USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + + return 0; +} + +/* defined in gadget.h */ +static struct usb_gadget_ops fsl_gadget_ops = { + .get_frame = fsl_get_frame, + .wakeup = fsl_wakeup, +/* .set_selfpowered = fsl_set_selfpowered, */ /* Always selfpowered */ + .vbus_session = fsl_vbus_session, + .vbus_draw = fsl_vbus_draw, + .pullup = fsl_pullup, +}; + +/* Set protocol stall on ep0, protocol stall will automatically be cleared + on new transaction */ +static void ep0stall(struct fsl_udc *udc) +{ + u32 tmp; + + /* must set tx and rx to stall at the same time */ + tmp = fsl_readl(&dr_regs->endptctrl[0]); + tmp |= EPCTRL_TX_EP_STALL | EPCTRL_RX_EP_STALL; + fsl_writel(tmp, &dr_regs->endptctrl[0]); + udc->ep0_dir = 0; +} + +/* Prime a status phase for ep0 */ +static int ep0_prime_status(struct fsl_udc *udc, int direction) +{ + struct fsl_req *req = udc->status_req; + struct fsl_ep *ep; + int status = 0; + + if (direction == EP_DIR_IN) + udc->ep0_dir = USB_DIR_IN; + else + udc->ep0_dir = USB_DIR_OUT; + + ep = &udc->eps[0]; + + req->ep = ep; + req->req.length = 0; + req->req.status = -EINPROGRESS; + + status = fsl_ep_queue(&ep->ep, &req->req, GFP_ATOMIC); + return status; +} + +static inline int udc_reset_ep_queue(struct fsl_udc *udc, u8 pipe) +{ + struct fsl_ep *ep = get_ep_by_pipe(udc, pipe); + + if (!ep->name) + return 0; + + nuke(ep, -ESHUTDOWN); + + return 0; +} + +/* + * ch9 Set address + */ +static void ch9setaddress(struct fsl_udc *udc, u16 value, u16 index, u16 length) +{ + /* Save the new address to device struct */ + udc->device_address = (u8) value; + /* Update usb state */ + udc->usb_state = USB_STATE_ADDRESS; + /* Status phase */ + if (ep0_prime_status(udc, EP_DIR_IN)) + ep0stall(udc); +} + +/* + * ch9 Get status + */ +static void ch9getstatus(struct fsl_udc *udc, u8 request_type, u16 value, + u16 index, u16 length) +{ + u16 tmp = 0; /* Status, cpu endian */ + + struct fsl_req *req; + struct fsl_ep *ep; + int status = 0; + + ep = &udc->eps[0]; + + if ((request_type & USB_RECIP_MASK) == USB_RECIP_DEVICE) { + /* Get device status */ + tmp = 1 << USB_DEVICE_SELF_POWERED; + tmp |= udc->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP; + } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_INTERFACE) { + /* Get interface status */ + /* We don't have interface information in udc driver */ + tmp = 0; + } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_ENDPOINT) { + /* Get endpoint status */ + struct fsl_ep *target_ep; + + target_ep = get_ep_by_pipe(udc, get_pipe_by_windex(index)); + + /* stall if endpoint doesn't exist */ + if (!target_ep->desc) + goto stall; + tmp = dr_ep_get_stall(ep_index(target_ep), ep_is_in(target_ep)) + << USB_ENDPOINT_HALT; + } + + udc->ep0_dir = USB_DIR_IN; + /* Borrow the per device data_req */ + /* status_req had been used to prime status */ + req = udc->data_req; + /* Fill in the reqest structure */ + *((u16 *) req->req.buf) = cpu_to_le16(tmp); + req->ep = ep; + req->req.length = 2; + + status = fsl_ep_queue(&ep->ep, &req->req, GFP_ATOMIC); + if (status) { + udc_reset_ep_queue(udc, 0); + ERR("Can't respond to getstatus request \n"); + goto stall; + } + return; +stall: + ep0stall(udc); + +} + +static void setup_received_irq(struct fsl_udc *udc, + struct usb_ctrlrequest *setup) +{ + u16 wValue = le16_to_cpu(setup->wValue); + u16 wIndex = le16_to_cpu(setup->wIndex); + u16 wLength = le16_to_cpu(setup->wLength); + + udc_reset_ep_queue(udc, 0); + + if (wLength) { + int dir; + dir = EP_DIR_IN; + if (setup->bRequestType & USB_DIR_IN) { + dir = EP_DIR_OUT; + } + if (ep0_prime_status(udc, dir)) + ep0stall(udc); + } + /* We process some stardard setup requests here */ + switch (setup->bRequest) { + case USB_REQ_GET_STATUS: + /* Data+Status phase from udc */ + if ((setup->bRequestType & (USB_DIR_IN | USB_TYPE_MASK)) + != (USB_DIR_IN | USB_TYPE_STANDARD)) + break; + ch9getstatus(udc, setup->bRequestType, wValue, wIndex, wLength); + return; + + case USB_REQ_SET_ADDRESS: + /* Status phase from udc */ + if (setup->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD + | USB_RECIP_DEVICE)) + break; + ch9setaddress(udc, wValue, wIndex, wLength); + return; + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + /* Status phase from udc */ + { + int rc = -EOPNOTSUPP; + u16 ptc = 0; + + if ((setup->bRequestType & (USB_RECIP_MASK | USB_TYPE_MASK)) + == (USB_RECIP_ENDPOINT | USB_TYPE_STANDARD)) { + int pipe = get_pipe_by_windex(wIndex); + struct fsl_ep *ep; + + if (wValue != 0 || wLength != 0 || pipe > udc->max_ep) + break; + ep = get_ep_by_pipe(udc, pipe); + + spin_unlock(&udc->lock); + rc = fsl_ep_set_halt(&ep->ep, + (setup->bRequest == USB_REQ_SET_FEATURE) + ? 1 : 0); + spin_lock(&udc->lock); + + } else if ((setup->bRequestType & (USB_RECIP_MASK + | USB_TYPE_MASK)) == (USB_RECIP_DEVICE + | USB_TYPE_STANDARD)) { + /* Note: The driver has not include OTG support yet. + * This will be set when OTG support is added */ + if (setup->wValue == USB_DEVICE_TEST_MODE) + ptc = setup->wIndex >> 8; + else if (gadget_is_otg(&udc->gadget)) { + if (setup->bRequest == + USB_DEVICE_B_HNP_ENABLE) + udc->gadget.b_hnp_enable = 1; + else if (setup->bRequest == + USB_DEVICE_A_HNP_SUPPORT) + udc->gadget.a_hnp_support = 1; + else if (setup->bRequest == + USB_DEVICE_A_ALT_HNP_SUPPORT) + udc->gadget.a_alt_hnp_support = 1; + } + rc = 0; + } else + break; + + if (rc == 0) { + if (ep0_prime_status(udc, EP_DIR_IN)) + ep0stall(udc); + } + if (ptc) { + u32 tmp; + + mdelay(10); + fsl_platform_set_test_mode(udc->pdata, ptc); + tmp = fsl_readl(&dr_regs->portsc1) | (ptc << 16); + fsl_writel(tmp, &dr_regs->portsc1); + printk(KERN_INFO "udc: switch to test mode 0x%x.\n", ptc); + } + + return; + } + + default: + break; + } + + /* Requests handled by gadget */ + if (wLength) { + /* Data phase from gadget, status phase from udc */ + udc->ep0_dir = (setup->bRequestType & USB_DIR_IN) + ? USB_DIR_IN : USB_DIR_OUT; + spin_unlock(&udc->lock); + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) < 0) { + /* cancel status phase */ + udc_reset_ep_queue(udc, 0); + ep0stall(udc); + } + } else { + /* No data phase, IN status from gadget */ + udc->ep0_dir = USB_DIR_IN; + spin_unlock(&udc->lock); + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) < 0) + ep0stall(udc); + } + spin_lock(&udc->lock); +} + +/* Process request for Data or Status phase of ep0 + * prime status phase if needed */ +static void ep0_req_complete(struct fsl_udc *udc, struct fsl_ep *ep0, + struct fsl_req *req) +{ + if (udc->usb_state == USB_STATE_ADDRESS) { + /* Set the new address */ + u32 new_address = (u32) udc->device_address; + fsl_writel(new_address << USB_DEVICE_ADDRESS_BIT_POS, + &dr_regs->deviceaddr); + } + + done(ep0, req, 0); +} + +/* Tripwire mechanism to ensure a setup packet payload is extracted without + * being corrupted by another incoming setup packet */ +static void tripwire_handler(struct fsl_udc *udc, u8 ep_num, u8 *buffer_ptr) +{ + u32 temp; + struct ep_queue_head *qh; + struct fsl_usb2_platform_data *pdata = udc->pdata; + + qh = &udc->ep_qh[ep_num * 2 + EP_DIR_OUT]; + + /* Clear bit in ENDPTSETUPSTAT */ + temp = fsl_readl(&dr_regs->endptsetupstat); + fsl_writel(temp | (1 << ep_num), &dr_regs->endptsetupstat); + + /* while a hazard exists when setup package arrives */ + do { + /* Set Setup Tripwire */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp | USB_CMD_SUTW, &dr_regs->usbcmd); + + /* Copy the setup packet to local buffer */ + if (pdata->le_setup_buf) { + u32 *p = (u32 *)buffer_ptr; + u32 *s = (u32 *)qh->setup_buffer; + + /* Convert little endian setup buffer to CPU endian */ + *p++ = le32_to_cpu(*s++); + *p = le32_to_cpu(*s); + } else { + memcpy(buffer_ptr, (u8 *) qh->setup_buffer, 8); + } + } while (!(fsl_readl(&dr_regs->usbcmd) & USB_CMD_SUTW)); + + /* Clear Setup Tripwire */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp & ~USB_CMD_SUTW, &dr_regs->usbcmd); +} + +static void iram_process_ep_complete(struct fsl_req *curr_req, + int cur_transfer) +{ + char *buf; + u32 len; + int in = ep_is_in(curr_req->ep); + + if (in) + buf = (char *)udc_controller->iram_buffer_v[1]; + else + buf = (char *)udc_controller->iram_buffer_v[0]; + + if (curr_req->cur->next_td_ptr == cpu_to_hc32(DTD_NEXT_TERMINATE) + || (cur_transfer < g_iram_size) + || (curr_req->req.length == curr_req->req.actual)) + curr_req->last_one = 1; + + if (curr_req->last_one) { + /* the last transfer */ + if (!in) { + memcpy(curr_req->req.buf + curr_req->buffer_offset, buf, + cur_transfer); + } + if (curr_req->tail->next_td_ptr != + cpu_to_hc32(DTD_NEXT_TERMINATE)) { + /* have next request,queue it */ + struct fsl_req *next_req; + next_req = + list_entry(curr_req->queue.next, + struct fsl_req, queue); + if (in) + memcpy(buf, next_req->req.buf, + min(g_iram_size, next_req->req.length)); + update_qh(next_req); + } + curr_req->req.dma = curr_req->oridma; + } else { + /* queue next dtd */ + /* because had next dtd, so should finish */ + /* tranferring g_iram_size data */ + curr_req->buffer_offset += g_iram_size; + /* pervious set stop bit,now clear it */ + curr_req->cur->next_td_ptr &= ~cpu_to_hc32(DTD_NEXT_TERMINATE); + curr_req->cur = curr_req->cur->next_td_virt; + if (in) { + len = + min(curr_req->req.length - curr_req->buffer_offset, + g_iram_size); + memcpy(buf, curr_req->req.buf + curr_req->buffer_offset, + len); + } else { + memcpy(curr_req->req.buf + curr_req->buffer_offset - + g_iram_size, buf, g_iram_size); + } + update_qh(curr_req); + } +} + +/* process-ep_req(): free the completed Tds for this req */ +static int process_ep_req(struct fsl_udc *udc, int pipe, + struct fsl_req *curr_req) +{ + struct ep_td_struct *curr_td; + int td_complete, actual, remaining_length, j, tmp; + int status = 0; + int errors = 0; + struct ep_queue_head *curr_qh = &udc->ep_qh[pipe]; + int direction = pipe % 2; + int total = 0, real_len; + + curr_td = curr_req->head; + td_complete = 0; + actual = curr_req->req.length; + real_len = curr_req->req.length; + + for (j = 0; j < curr_req->dtd_count; j++) { + remaining_length = (hc32_to_cpu(curr_td->size_ioc_sts) + & DTD_PACKET_SIZE) + >> DTD_LENGTH_BIT_POS; + if (NEED_IRAM(curr_req->ep)) { + if (real_len >= g_iram_size) { + actual = g_iram_size; + real_len -= g_iram_size; + } else { /* the last packet */ + actual = real_len; + curr_req->last_one = 1; + } + } + actual -= remaining_length; + total += actual; + + errors = hc32_to_cpu(curr_td->size_ioc_sts) & DTD_ERROR_MASK; + if (errors) { + if (errors & DTD_STATUS_HALTED) { + ERR("dTD error %08x QH=%d\n", errors, pipe); + /* Clear the errors and Halt condition */ + tmp = hc32_to_cpu(curr_qh->size_ioc_int_sts); + tmp &= ~errors; + curr_qh->size_ioc_int_sts = cpu_to_hc32(tmp); + status = -EPIPE; + /* FIXME: continue with next queued TD? */ + + break; + } + if (errors & DTD_STATUS_DATA_BUFF_ERR) { + VDBG("Transfer overflow"); + status = -EPROTO; + break; + } else if (errors & DTD_STATUS_TRANSACTION_ERR) { + VDBG("ISO error"); + status = -EILSEQ; + break; + } else + ERR("Unknown error has occured (0x%x)!\r\n", + errors); + + } else if (hc32_to_cpu(curr_td->size_ioc_sts) + & DTD_STATUS_ACTIVE) { + VDBG("Request not complete"); + status = REQ_UNCOMPLETE; + return status; + } else if (remaining_length) { + if (direction) { + VDBG("Transmit dTD remaining length not zero"); + status = -EPROTO; + break; + } else { + td_complete++; + break; + } + } else { + td_complete++; + VDBG("dTD transmitted successful "); + } + if (NEED_IRAM(curr_req->ep)) + if (curr_td-> + next_td_ptr & cpu_to_hc32(DTD_NEXT_TERMINATE)) + break; + if (j != curr_req->dtd_count - 1) + curr_td = (struct ep_td_struct *)curr_td->next_td_virt; + } + + if (status) + return status; + curr_req->req.actual = total; + if (NEED_IRAM(curr_req->ep)) + iram_process_ep_complete(curr_req, actual); + return 0; +} + +/* Process a DTD completion interrupt */ +static void dtd_complete_irq(struct fsl_udc *udc) +{ + u32 bit_pos; + int i, ep_num, direction, bit_mask, status; + struct fsl_ep *curr_ep; + struct fsl_req *curr_req, *temp_req; + + /* Clear the bits in the register */ + bit_pos = fsl_readl(&dr_regs->endptcomplete); + fsl_writel(bit_pos, &dr_regs->endptcomplete); + + if (!bit_pos) + return; + + for (i = 0; i < udc->max_ep * 2; i++) { + ep_num = i >> 1; + direction = i % 2; + + bit_mask = 1 << (ep_num + 16 * direction); + + if (!(bit_pos & bit_mask)) + continue; + + curr_ep = get_ep_by_pipe(udc, i); + + /* If the ep is configured */ + if (curr_ep->name == NULL) { + INFO("Invalid EP?"); + continue; + } + + /* process the req queue until an uncomplete request */ + list_for_each_entry_safe(curr_req, temp_req, &curr_ep->queue, + queue) { + status = process_ep_req(udc, i, curr_req); + + VDBG("status of process_ep_req= %d, ep = %d", + status, ep_num); + if (status == REQ_UNCOMPLETE) + break; + /* write back status to req */ + curr_req->req.status = status; + + if (ep_num == 0) { + ep0_req_complete(udc, curr_ep, curr_req); + break; + } else { + if (NEED_IRAM(curr_ep)) { + if (curr_req->last_one) + done(curr_ep, curr_req, status); + /* only check the 1th req */ + break; + } else + done(curr_ep, curr_req, status); + } + } + dump_ep_queue(curr_ep); + } +} + +/* Process a port change interrupt */ +static void port_change_irq(struct fsl_udc *udc) +{ + u32 speed; + + if (udc->bus_reset) + udc->bus_reset = 0; + + /* Bus resetting is finished */ + if (!(fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET)) { + /* Get the speed */ + speed = (fsl_readl(&dr_regs->portsc1) + & PORTSCX_PORT_SPEED_MASK); + switch (speed) { + case PORTSCX_PORT_SPEED_HIGH: + udc->gadget.speed = USB_SPEED_HIGH; + break; + case PORTSCX_PORT_SPEED_FULL: + udc->gadget.speed = USB_SPEED_FULL; + break; + case PORTSCX_PORT_SPEED_LOW: + udc->gadget.speed = USB_SPEED_LOW; + break; + default: + udc->gadget.speed = USB_SPEED_UNKNOWN; + break; + } + } + + /* Update USB state */ + if (!udc->resume_state) + udc->usb_state = USB_STATE_DEFAULT; +} + +/* Process suspend interrupt */ +static void suspend_irq(struct fsl_udc *udc) +{ + pr_debug("%s\n", __func__); + + udc->resume_state = udc->usb_state; + udc->usb_state = USB_STATE_SUSPENDED; + + /* report suspend to the driver, serial.c does not support this */ + if (udc->driver->suspend) + udc->driver->suspend(&udc->gadget); +} + +/* Process Wake up interrupt */ +static void wake_up_irq(struct fsl_udc *udc) +{ + pr_debug("%s\n", __func__); + + /* disable wake up irq */ + dr_wake_up_enable(udc, false); +} + +static void bus_resume(struct fsl_udc *udc) +{ + udc->usb_state = udc->resume_state; + udc->resume_state = 0; + + /* report resume to the driver, serial.c does not support this */ + if (udc->driver->resume) + udc->driver->resume(&udc->gadget); +} + +/* Clear up all ep queues */ +static int reset_queues(struct fsl_udc *udc) +{ + u8 pipe; + + for (pipe = 0; pipe < udc->max_pipes; pipe++) + udc_reset_ep_queue(udc, pipe); + + /* report disconnect; the driver is already quiesced */ + udc->driver->disconnect(&udc->gadget); + + return 0; +} + +/* Process reset interrupt */ +static void reset_irq(struct fsl_udc *udc) +{ + u32 temp; + + /* Clear the device address */ + temp = fsl_readl(&dr_regs->deviceaddr); + fsl_writel(temp & ~USB_DEVICE_ADDRESS_MASK, &dr_regs->deviceaddr); + + udc->device_address = 0; + + /* Clear usb state */ + udc->resume_state = 0; + udc->ep0_dir = 0; + udc->remote_wakeup = 0; /* default to 0 on reset */ + udc->gadget.b_hnp_enable = 0; + udc->gadget.a_hnp_support = 0; + udc->gadget.a_alt_hnp_support = 0; + + /* Clear all the setup token semaphores */ + temp = fsl_readl(&dr_regs->endptsetupstat); + fsl_writel(temp, &dr_regs->endptsetupstat); + + /* Clear all the endpoint complete status bits */ + temp = fsl_readl(&dr_regs->endptcomplete); + fsl_writel(temp, &dr_regs->endptcomplete); + + /* Write 1s to the flush register */ + fsl_writel(0xffffffff, &dr_regs->endptflush); + + if (fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET) { + VDBG("Bus reset"); + /* Bus is reseting */ + udc->bus_reset = 1; + /* Reset all the queues, include XD, dTD, EP queue + * head and TR Queue */ + reset_queues(udc); + udc->usb_state = USB_STATE_DEFAULT; + } else { + VDBG("Controller reset"); + /* initialize usb hw reg except for regs for EP, not + * touch usbintr reg */ + dr_controller_setup(udc); + + /* Reset all internal used Queues */ + reset_queues(udc); + + ep0_setup(udc); + + /* Enable DR IRQ reg, Set Run bit, change udc state */ + dr_controller_run(udc); + udc->usb_state = USB_STATE_ATTACHED; + } +} + +/* + * USB device controller interrupt handler + */ +static irqreturn_t fsl_udc_irq(int irq, void *_udc) +{ + struct fsl_udc *udc = _udc; + u32 irq_src; + irqreturn_t status = IRQ_NONE; + unsigned long flags; + + /* when udc is stopped, only handle wake up irq */ + if (udc->stopped) { + if (!device_may_wakeup(&(udc->pdata->pdev->dev))) + return IRQ_NONE; + + spin_lock_irqsave(&udc->lock, flags); + /* check to see if wake up irq */ + irq_src = fsl_readl(&dr_regs->usbctrl); + if (irq_src & USB_CTRL_OTG_WUIR) { + wake_up_irq(udc); + irq_src = fsl_readl(&dr_regs->usbsts) & + fsl_readl(&dr_regs->usbintr); + spin_unlock_irqrestore(&udc->lock, flags); + if (irq_src) + /* Some udc irq to be handled */ + udc->stopped = 0; + else + return IRQ_HANDLED; + } else { + /* If udc is stopped and irq is not wake up */ + spin_unlock_irqrestore(&udc->lock, flags); + return IRQ_NONE; + } + } + + spin_lock_irqsave(&udc->lock, flags); + irq_src = fsl_readl(&dr_regs->usbsts) & fsl_readl(&dr_regs->usbintr); + /* Clear notification bits */ + fsl_writel(irq_src, &dr_regs->usbsts); + + /* VDBG("irq_src [0x%8x]", irq_src); */ + + /* Need to resume? */ + if (udc->usb_state == USB_STATE_SUSPENDED) + if ((fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_SUSPEND) == 0) + bus_resume(udc); + + /* USB Interrupt */ + if (irq_src & USB_STS_INT) { + VDBG("Packet int"); + /* Setup package, we only support ep0 as control ep */ + if (fsl_readl(&dr_regs->endptsetupstat) & EP_SETUP_STATUS_EP0) { + tripwire_handler(udc, 0, + (u8 *) (&udc->local_setup_buff)); + setup_received_irq(udc, &udc->local_setup_buff); + status = IRQ_HANDLED; + } + + /* completion of dtd */ + if (fsl_readl(&dr_regs->endptcomplete)) { + dtd_complete_irq(udc); + status = IRQ_HANDLED; + } + } + + /* SOF (for ISO transfer) */ + if (irq_src & USB_STS_SOF) { + status = IRQ_HANDLED; + } + + /* Port Change */ + if (irq_src & USB_STS_PORT_CHANGE) { + port_change_irq(udc); + status = IRQ_HANDLED; + } + + /* Reset Received */ + if (irq_src & USB_STS_RESET) { + VDBG("reset int"); + reset_irq(udc); + status = IRQ_HANDLED; + } + + /* Sleep Enable (Suspend) */ + if (irq_src & USB_STS_SUSPEND) { + suspend_irq(udc); + status = IRQ_HANDLED; + } + + if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) { + VDBG("Error IRQ %x ", irq_src); + } + + spin_unlock_irqrestore(&udc->lock, flags); + return status; +} + +/*----------------------------------------------------------------* + * Hook to gadget drivers + * Called by initialization code of gadget drivers +*----------------------------------------------------------------*/ +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + int retval = -ENODEV; + unsigned long flags = 0; + + if (!udc_controller) + return -ENODEV; + + if (!driver || (driver->speed != USB_SPEED_FULL + && driver->speed != USB_SPEED_HIGH) + || !driver->bind || !driver->disconnect + || !driver->setup) + return -EINVAL; + + if (udc_controller->driver) + return -EBUSY; + + /* lock is needed but whether should use this lock or another */ + spin_lock_irqsave(&udc_controller->lock, flags); + + driver->driver.bus = 0; + /* hook up the driver */ + udc_controller->driver = driver; + udc_controller->gadget.dev.driver = &driver->driver; + spin_unlock_irqrestore(&udc_controller->lock, flags); + + /* bind udc driver to gadget driver */ + retval = driver->bind(&udc_controller->gadget); + if (retval) { + VDBG("bind to %s --> %d", driver->driver.name, retval); + udc_controller->gadget.dev.driver = 0; + udc_controller->driver = 0; + goto out; + } + + if (udc_controller->transceiver) { + /* Suspend the controller until OTG enable it */ + udc_controller->stopped = 1; + printk(KERN_INFO "Suspend udc for OTG auto detect\n"); + dr_wake_up_enable(udc_controller, true); + dr_phy_low_power_mode(udc_controller, true); + + /* export udc suspend/resume call to OTG */ + udc_controller->gadget.dev.driver->suspend = (dev_sus)fsl_udc_suspend; + udc_controller->gadget.dev.driver->resume = (dev_res)fsl_udc_resume; + + /* connect to bus through transceiver */ + if (udc_controller->transceiver) { + retval = otg_set_peripheral(udc_controller->transceiver, + &udc_controller->gadget); + if (retval < 0) { + ERR("can't bind to transceiver\n"); + driver->unbind(&udc_controller->gadget); + udc_controller->gadget.dev.driver = 0; + udc_controller->driver = 0; + return retval; + } + } + } else { + /* Enable DR IRQ reg and Set usbcmd reg Run bit */ + dr_controller_run(udc_controller); + udc_controller->usb_state = USB_STATE_ATTACHED; + udc_controller->ep0_dir = 0; + } + printk(KERN_INFO "%s: bind to driver %s \n", + udc_controller->gadget.name, driver->driver.name); + +out: + if (retval) + printk(KERN_DEBUG "retval %d \n", retval); + return retval; +} +EXPORT_SYMBOL(usb_gadget_register_driver); + +/* Disconnect from gadget driver */ +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct fsl_ep *loop_ep; + unsigned long flags; + + if (!udc_controller) + return -ENODEV; + + if (!driver || driver != udc_controller->driver || !driver->unbind) + return -EINVAL; + + if (udc_controller->transceiver) + (void)otg_set_peripheral(udc_controller->transceiver, 0); + + /* stop DR, disable intr */ + dr_controller_stop(udc_controller); + + /* in fact, no needed */ + udc_controller->usb_state = USB_STATE_ATTACHED; + udc_controller->ep0_dir = 0; + + /* stand operation */ + spin_lock_irqsave(&udc_controller->lock, flags); + udc_controller->gadget.speed = USB_SPEED_UNKNOWN; + nuke(&udc_controller->eps[0], -ESHUTDOWN); + list_for_each_entry(loop_ep, &udc_controller->gadget.ep_list, + ep.ep_list) + nuke(loop_ep, -ESHUTDOWN); + spin_unlock_irqrestore(&udc_controller->lock, flags); + + /* disconnect gadget before unbinding */ + driver->disconnect(&udc_controller->gadget); + + /* unbind gadget and unhook driver. */ + driver->unbind(&udc_controller->gadget); + udc_controller->gadget.dev.driver = 0; + udc_controller->driver = 0; + + printk(KERN_INFO "unregistered gadget driver '%s'\r\n", + driver->driver.name); + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +/*------------------------------------------------------------------------- + PROC File System Support +-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +#include <linux/seq_file.h> + +static const char proc_filename[] = "driver/fsl_usb2_udc"; + +static int fsl_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *_dev) +{ + char *buf = page; + char *next = buf; + unsigned size = count; + unsigned long flags; + int t, i; + u32 tmp_reg; + struct fsl_ep *ep = NULL; + struct fsl_req *req; + struct fsl_usb2_platform_data *pdata; + + struct fsl_udc *udc = udc_controller; + pdata = udc->pdata; + if (off != 0) + return 0; + + spin_lock_irqsave(&udc->lock, flags); + + /* ------basic driver infomation ---- */ + t = scnprintf(next, size, + DRIVER_DESC "\n" + "%s version: %s\n" + "Gadget driver: %s\n\n", + driver_name, DRIVER_VERSION, + udc->driver ? udc->driver->driver.name : "(none)"); + size -= t; + next += t; + + /* ------ DR Registers ----- */ + tmp_reg = fsl_readl(&dr_regs->usbcmd); + t = scnprintf(next, size, + "USBCMD reg:\n" + "SetupTW: %d\n" + "Run/Stop: %s\n\n", + (tmp_reg & USB_CMD_SUTW) ? 1 : 0, + (tmp_reg & USB_CMD_RUN_STOP) ? "Run" : "Stop"); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->usbsts); + t = scnprintf(next, size, + "USB Status Reg:\n" + "Dr Suspend: %d" "Reset Received: %d" "System Error: %s" + "USB Error Interrupt: %s\n\n", + (tmp_reg & USB_STS_SUSPEND) ? 1 : 0, + (tmp_reg & USB_STS_RESET) ? 1 : 0, + (tmp_reg & USB_STS_SYS_ERR) ? "Err" : "Normal", + (tmp_reg & USB_STS_ERR) ? "Err detected" : "No err"); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->usbintr); + t = scnprintf(next, size, + "USB Intrrupt Enable Reg:\n" + "Sleep Enable: %d" "SOF Received Enable: %d" + "Reset Enable: %d\n" + "System Error Enable: %d" + "Port Change Dectected Enable: %d\n" + "USB Error Intr Enable: %d" "USB Intr Enable: %d\n\n", + (tmp_reg & USB_INTR_DEVICE_SUSPEND) ? 1 : 0, + (tmp_reg & USB_INTR_SOF_EN) ? 1 : 0, + (tmp_reg & USB_INTR_RESET_EN) ? 1 : 0, + (tmp_reg & USB_INTR_SYS_ERR_EN) ? 1 : 0, + (tmp_reg & USB_INTR_PTC_DETECT_EN) ? 1 : 0, + (tmp_reg & USB_INTR_ERR_INT_EN) ? 1 : 0, + (tmp_reg & USB_INTR_INT_EN) ? 1 : 0); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->frindex); + t = scnprintf(next, size, + "USB Frame Index Reg:" "Frame Number is 0x%x\n\n", + (tmp_reg & USB_FRINDEX_MASKS)); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->deviceaddr); + t = scnprintf(next, size, + "USB Device Address Reg:" "Device Addr is 0x%x\n\n", + (tmp_reg & USB_DEVICE_ADDRESS_MASK)); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->endpointlistaddr); + t = scnprintf(next, size, + "USB Endpoint List Address Reg:" + "Device Addr is 0x%x\n\n", + (tmp_reg & USB_EP_LIST_ADDRESS_MASK)); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->portsc1); + t = scnprintf(next, size, + "USB Port Status&Control Reg:\n" + "Port Transceiver Type : %s" "Port Speed: %s \n" + "PHY Low Power Suspend: %s" "Port Reset: %s" + "Port Suspend Mode: %s \n" "Over-current Change: %s" + "Port Enable/Disable Change: %s\n" + "Port Enabled/Disabled: %s" + "Current Connect Status: %s\n\n", ({ + char *s; + switch (tmp_reg & PORTSCX_PTS_FSLS) { + case PORTSCX_PTS_UTMI: + s = "UTMI"; break; + case PORTSCX_PTS_ULPI: + s = "ULPI "; break; + case PORTSCX_PTS_FSLS: + s = "FS/LS Serial"; break; + default: + s = "None"; break; + } + s; }), ({ + char *s; + switch (tmp_reg & PORTSCX_PORT_SPEED_UNDEF) { + case PORTSCX_PORT_SPEED_FULL: + s = "Full Speed"; break; + case PORTSCX_PORT_SPEED_LOW: + s = "Low Speed"; break; + case PORTSCX_PORT_SPEED_HIGH: + s = "High Speed"; break; + default: + s = "Undefined"; break; + } + s; + }), + (tmp_reg & PORTSCX_PHY_LOW_POWER_SPD) ? + "Normal PHY mode" : "Low power mode", + (tmp_reg & PORTSCX_PORT_RESET) ? "In Reset" : + "Not in Reset", + (tmp_reg & PORTSCX_PORT_SUSPEND) ? "In " : "Not in", + (tmp_reg & PORTSCX_OVER_CURRENT_CHG) ? "Dected" : + "No", + (tmp_reg & PORTSCX_PORT_EN_DIS_CHANGE) ? "Disable" : + "Not change", + (tmp_reg & PORTSCX_PORT_ENABLE) ? "Enable" : + "Not correct", + (tmp_reg & PORTSCX_CURRENT_CONNECT_STATUS) ? + "Attached" : "Not-Att"); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->usbmode); + t = scnprintf(next, size, + "USB Mode Reg:" "Controller Mode is : %s\n\n", ({ + char *s; + switch (tmp_reg & USB_MODE_CTRL_MODE_HOST) { + case USB_MODE_CTRL_MODE_IDLE: + s = "Idle"; break; + case USB_MODE_CTRL_MODE_DEVICE: + s = "Device Controller"; break; + case USB_MODE_CTRL_MODE_HOST: + s = "Host Controller"; break; + default: + s = "None"; break; + } + s; + })); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->endptsetupstat); + t = scnprintf(next, size, + "Endpoint Setup Status Reg:" "SETUP on ep 0x%x\n\n", + (tmp_reg & EP_SETUP_STATUS_MASK)); + size -= t; + next += t; + + for (i = 0; i < udc->max_ep / 2; i++) { + tmp_reg = fsl_readl(&dr_regs->endptctrl[i]); + t = scnprintf(next, size, "EP Ctrl Reg [0x%x]: = [0x%x]\n", + i, tmp_reg); + size -= t; + next += t; + } + tmp_reg = fsl_readl(&dr_regs->endpointprime); + t = scnprintf(next, size, "EP Prime Reg = [0x%x]\n", tmp_reg); + size -= t; + next += t; + + if (pdata->have_sysif_regs) { + tmp_reg = usb_sys_regs->snoop1; + t = scnprintf(next, size, "\nSnoop1 Reg = [0x%x]\n\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = usb_sys_regs->control; + t = scnprintf(next, size, "General Control Reg = [0x%x]\n\n", + tmp_reg); + size -= t; + next += t; + } + + /* ------fsl_udc, fsl_ep, fsl_request structure information ----- */ + ep = &udc->eps[0]; + t = scnprintf(next, size, "For %s Maxpkt is 0x%x index is 0x%x\n", + ep->ep.name, ep_maxpacket(ep), ep_index(ep)); + size -= t; + next += t; + + if (list_empty(&ep->queue)) { + t = scnprintf(next, size, "its req queue is empty\n\n"); + size -= t; + next += t; + } else { + list_for_each_entry(req, &ep->queue, queue) { + t = scnprintf(next, size, + "req %p actual 0x%x length 0x%x buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + size -= t; + next += t; + } + } + /* other gadget->eplist ep */ + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { + if (ep->desc) { + t = scnprintf(next, size, + "\nFor %s Maxpkt is 0x%x " + "index is 0x%x\n", + ep->ep.name, ep_maxpacket(ep), + ep_index(ep)); + size -= t; + next += t; + + if (list_empty(&ep->queue)) { + t = scnprintf(next, size, + "its req queue is empty\n\n"); + size -= t; + next += t; + } else { + list_for_each_entry(req, &ep->queue, queue) { + t = scnprintf(next, size, + "req %p actual 0x%x length" + "0x%x buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + size -= t; + next += t; + } /* end for each_entry of ep req */ + } /* end for else */ + } /* end for if(ep->queue) */ + } /* end (ep->desc) */ + + spin_unlock_irqrestore(&udc->lock, flags); + + *eof = 1; + return count - size; +} + +#define create_proc_file() create_proc_read_entry(proc_filename, \ + 0, NULL, fsl_proc_read, NULL) + +#define remove_proc_file() remove_proc_entry(proc_filename, NULL) + +#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ + +#define create_proc_file() do {} while (0) +#define remove_proc_file() do {} while (0) + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + +/*-------------------------------------------------------------------------*/ + +/* Release udc structures */ +static void fsl_udc_release(struct device *dev) +{ + complete(udc_controller->done); + dma_free_coherent(dev, udc_controller->ep_qh_size, + udc_controller->ep_qh, udc_controller->ep_qh_dma); + kfree(udc_controller); +} + +/****************************************************************** + Internal structure setup functions +*******************************************************************/ +/*------------------------------------------------------------------ + * init resource for globle controller + * Return the udc handle on success or NULL on failure + ------------------------------------------------------------------*/ +static int __init struct_udc_setup(struct fsl_udc *udc, + struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + size_t size; + + pdata = pdev->dev.platform_data; + udc->phy_mode = pdata->phy_mode; + + udc->eps = kzalloc(sizeof(struct fsl_ep) * udc->max_ep, GFP_KERNEL); + if (!udc->eps) { + ERR("malloc fsl_ep failed\n"); + return -1; + } + + /* initialized QHs, take care of alignment */ + size = udc->max_ep * sizeof(struct ep_queue_head); + if (size < QH_ALIGNMENT) + size = QH_ALIGNMENT; + else if ((size % QH_ALIGNMENT) != 0) { + size += QH_ALIGNMENT + 1; + size &= ~(QH_ALIGNMENT - 1); + } + udc->ep_qh = dma_alloc_coherent(&pdev->dev, size, + &udc->ep_qh_dma, GFP_KERNEL); + if (!udc->ep_qh) { + ERR("malloc QHs for udc failed\n"); + kfree(udc->eps); + return -1; + } + + udc->ep_qh_size = size; + + /* Initialize ep0 status request structure */ + /* FIXME: fsl_alloc_request() ignores ep argument */ + udc->status_req = container_of(fsl_alloc_request(NULL, GFP_KERNEL), + struct fsl_req, req); + /* allocate a small amount of memory to get valid address */ + udc->status_req->req.buf = kmalloc(8, GFP_KERNEL); + udc->status_req->req.dma = virt_to_phys(udc->status_req->req.buf); + /* Initialize ep0 data request structure */ + udc->data_req = container_of(fsl_alloc_request(NULL, GFP_KERNEL), + struct fsl_req, req); + udc->data_req->req.buf = kmalloc(8, GFP_KERNEL); + udc->data_req->req.dma = virt_to_phys(udc->data_req->req.buf); + + udc->resume_state = USB_STATE_NOTATTACHED; + udc->usb_state = USB_STATE_POWERED; + udc->ep0_dir = 0; + udc->remote_wakeup = 0; /* default to 0 on reset */ + spin_lock_init(&udc->lock); + + return 0; +} + +/*---------------------------------------------------------------- + * Setup the fsl_ep struct for eps + * Link fsl_ep->ep to gadget->ep_list + * ep0out is not used so do nothing here + * ep0in should be taken care + *--------------------------------------------------------------*/ +static int __init struct_ep_setup(struct fsl_udc *udc, unsigned char index, + char *name, int link) +{ + struct fsl_ep *ep = &udc->eps[index]; + + ep->udc = udc; + strcpy(ep->name, name); + ep->ep.name = ep->name; + + ep->ep.ops = &fsl_ep_ops; + ep->stopped = 0; + + /* for ep0: maxP defined in desc + * for other eps, maxP is set by epautoconfig() called by gadget layer + */ + ep->ep.maxpacket = (unsigned short) ~0; + + /* the queue lists any req for this ep */ + INIT_LIST_HEAD(&ep->queue); + + /* gagdet.ep_list used for ep_autoconfig so no ep0 */ + if (link) + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + ep->gadget = &udc->gadget; + ep->qh = &udc->ep_qh[index]; + + return 0; +} + +/* Driver probe function + * all intialization operations implemented here except enabling usb_intr reg + * board setup should have been done in the platform code + */ +static int __init fsl_udc_probe(struct platform_device *pdev) +{ + struct resource *res; + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + int ret = -ENODEV; + unsigned int i; + u32 dccparams; + + if (strcmp(pdev->name, driver_name)) { + VDBG("Wrong device\n"); + return -ENODEV; + } + + udc_controller = kzalloc(sizeof(struct fsl_udc), GFP_KERNEL); + if (udc_controller == NULL) { + ERR("malloc udc failed\n"); + return -ENOMEM; + } + udc_controller->pdata = pdata; + +#ifdef CONFIG_USB_OTG + /* Memory and interrupt resources will be passed from OTG */ + udc_controller->transceiver = otg_get_transceiver(); + if (!udc_controller->transceiver) { + printk(KERN_ERR "Can't find OTG driver!\n"); + ret = -ENODEV; + goto err1a; + } + + res = otg_get_resources(); + if (!res) { + DBG("resource not registered!\n"); + return -ENODEV; + } +#else + if ((pdev->dev.parent) && + (to_platform_device(pdev->dev.parent)->resource)) { + pdev->resource = + to_platform_device(pdev->dev.parent)->resource; + pdev->num_resources = + to_platform_device(pdev->dev.parent)->num_resources; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENXIO; + goto err1a; + } + + if (!request_mem_region(res->start, resource_size(res), + driver_name)) { + ERR("request mem region for %s failed \n", pdev->name); + ret = -EBUSY; + goto err1a; + } +#endif + + if ((pdata->port_enables & FSL_USB2_DONT_REMAP) == 0) { + dr_regs = ioremap(res->start, resource_size(res)); + if (!dr_regs) { + ret = -ENOMEM; + goto err1; + } + udc_controller->dr_remapped = !0; + } else { + dr_regs = (void *)res->start; + dev_warn(&pdev->dev, "does not remap its address space\n"); + } + pdata->regs = (void *)dr_regs; + /* + * do platform specific init: check the clock, grab/config pins, etc. + */ + if (pdata->platform_init && pdata->platform_init(pdev)) { + ret = -ENODEV; + goto err2a; + } + + if (pdata->have_sysif_regs) + usb_sys_regs = (struct usb_sys_interface *) + ((u32)dr_regs + USB_DR_SYS_OFFSET); + + /* Read Device Controller Capability Parameters register */ + dccparams = fsl_readl(&dr_regs->dccparams); + if (!(dccparams & DCCPARAMS_DC)) { + ERR("This SOC doesn't support device role\n"); + ret = -ENODEV; + goto err2; + } + /* Get max device endpoints */ + /* DEN is bidirectional ep number, max_ep doubles the number */ + udc_controller->max_ep = (dccparams & DCCPARAMS_DEN_MASK) * 2; + +#ifdef CONFIG_USB_OTG + res++; + udc_controller->irq = res->start; +#else + udc_controller->irq = platform_get_irq(pdev, 0); +#endif + if (!udc_controller->irq) { + ret = -ENODEV; + goto err2; + } + + ret = request_irq(udc_controller->irq, fsl_udc_irq, IRQF_SHARED, + driver_name, udc_controller); + if (ret != 0) { + ERR("cannot request irq %d err %d \n", + udc_controller->irq, ret); + goto err2; + } + + /* Initialize the udc structure including QH member and other member */ + if (struct_udc_setup(udc_controller, pdev)) { + ERR("Can't initialize udc data structure\n"); + ret = -ENOMEM; + goto err3; + } + + if (!udc_controller->transceiver) { + /* initialize usb hw reg except for regs for EP, + * leave usbintr reg untouched */ + dr_controller_setup(udc_controller); + } + + /* Setup gadget structure */ + udc_controller->gadget.ops = &fsl_gadget_ops; + udc_controller->gadget.is_dualspeed = 1; + udc_controller->gadget.ep0 = &udc_controller->eps[0].ep; + INIT_LIST_HEAD(&udc_controller->gadget.ep_list); + udc_controller->gadget.speed = USB_SPEED_UNKNOWN; + udc_controller->gadget.name = driver_name; + + /* Setup gadget.dev and register with kernel */ + dev_set_name(&udc_controller->gadget.dev, "gadget"); + udc_controller->gadget.dev.release = fsl_udc_release; + udc_controller->gadget.dev.parent = &pdev->dev; + ret = device_register(&udc_controller->gadget.dev); + if (ret < 0) + goto err3; + + if (udc_controller->transceiver) + udc_controller->gadget.is_otg = 1; + + /* setup QH and epctrl for ep0 */ + ep0_setup(udc_controller); + + /* setup udc->eps[] for ep0 */ + struct_ep_setup(udc_controller, 0, "ep0", 0); + /* for ep0: the desc defined here; + * for other eps, gadget layer called ep_enable with defined desc + */ + udc_controller->eps[0].desc = &fsl_ep0_desc; + udc_controller->eps[0].ep.maxpacket = USB_MAX_CTRL_PAYLOAD; + + /* setup the udc->eps[] for non-control endpoints and link + * to gadget.ep_list */ + for (i = 1; i < (int)(udc_controller->max_ep / 2); i++) { + char name[14]; + + sprintf(name, "ep%dout", i); + struct_ep_setup(udc_controller, i * 2, name, 1); + sprintf(name, "ep%din", i); + struct_ep_setup(udc_controller, i * 2 + 1, name, 1); + } + + /* use dma_pool for TD management */ + udc_controller->td_pool = dma_pool_create("udc_td", &pdev->dev, + sizeof(struct ep_td_struct), + DTD_ALIGNMENT, UDC_DMA_BOUNDARY); + if (udc_controller->td_pool == NULL) { + ret = -ENOMEM; + goto err4; + } + if (g_iram_size) { + for (i = 0; i < IRAM_PPH_NTD; i++) { + udc_controller->iram_buffer[i] = + USB_IRAM_BASE_ADDR + i * g_iram_size; + udc_controller->iram_buffer_v[i] = + IO_ADDRESS(udc_controller->iram_buffer[i]); + } + } +#ifdef POSTPONE_FREE_LAST_DTD + last_free_td = NULL; +#endif + create_proc_file(); + return 0; + +err4: + device_unregister(&udc_controller->gadget.dev); +err3: + free_irq(udc_controller->irq, udc_controller); +err2: + if (pdata->platform_uninit) + pdata->platform_uninit(pdata); +err2a: + if (udc_controller->dr_remapped) + iounmap((u8 __iomem *)dr_regs); +err1: + if (!udc_controller->transceiver) + release_mem_region(res->start, resource_size(res)); +err1a: + kfree(udc_controller); + udc_controller = NULL; + return ret; +} + +/* Driver removal function + * Free resources and finish pending transactions + */ +static int __exit fsl_udc_remove(struct platform_device *pdev) +{ + struct resource *res; + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + DECLARE_COMPLETION(done); + + if (!udc_controller) + return -ENODEV; + udc_controller->done = &done; + + /* DR has been stopped in usb_gadget_unregister_driver() */ + remove_proc_file(); + + /* Free allocated memory */ + kfree(udc_controller->status_req->req.buf); + kfree(udc_controller->status_req); + kfree(udc_controller->data_req->req.buf); + kfree(udc_controller->data_req); + kfree(udc_controller->eps); +#ifdef POSTPONE_FREE_LAST_DTD + if (last_free_td != NULL) + dma_pool_free(udc_controller->td_pool, last_free_td, + last_free_td->td_dma); +#endif + dma_pool_destroy(udc_controller->td_pool); + free_irq(udc_controller->irq, udc_controller); + if (udc_controller->dr_remapped) + iounmap((u8 __iomem *)dr_regs); + +#ifndef CONFIG_USB_OTG + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); +#endif + + device_unregister(&udc_controller->gadget.dev); + /* free udc --wait for the release() finished */ + wait_for_completion(&done); + + /* + * do platform specific un-initialization: + * release iomux pins, etc. + */ + if (pdata->platform_uninit) + pdata->platform_uninit(pdata); + + return 0; +} + +static int udc_suspend(struct fsl_udc *udc) +{ + u32 mode, usbcmd; + + mode = fsl_readl(&dr_regs->usbmode) & USB_MODE_CTRL_MODE_MASK; + usbcmd = fsl_readl(&dr_regs->usbcmd); + + pr_debug("%s(): mode 0x%x stopped %d\n", __func__, mode, udc->stopped); + + /* + * If the controller is already stopped, then this must be a + * PM suspend. Remember this fact, so that we will leave the + * controller stopped at PM resume time. + */ + if (udc->stopped) { + pr_debug("gadget already stopped, leaving early\n"); + udc->already_stopped = 1; + return 0; + } + + if (mode != USB_MODE_CTRL_MODE_DEVICE) { + pr_debug("gadget not in device mode, leaving early\n"); + return 0; + } + + udc->stopped = 1; + /* if the suspend is not for switch to host in otg mode */ + if ((!(udc->gadget.is_otg)) || + (fsl_readl(&dr_regs->otgsc) & OTGSC_STS_USB_ID)) { + dr_wake_up_enable(udc, true); + dr_phy_low_power_mode(udc, true); + } + + /* stop the controller */ + usbcmd = fsl_readl(&dr_regs->usbcmd) & ~USB_CMD_RUN_STOP; + fsl_writel(usbcmd, &dr_regs->usbcmd); + + printk(KERN_INFO "USB Gadget suspended\n"); + + return 0; +} + +/*----------------------------------------------------------------- + * Modify Power management attributes + * Used by OTG statemachine to disable gadget temporarily + -----------------------------------------------------------------*/ +static int fsl_udc_suspend(struct platform_device *pdev, pm_message_t state) +{ + if ((udc_controller->usb_state > USB_STATE_POWERED) && + (udc_controller->usb_state < USB_STATE_SUSPENDED)) + return -EBUSY; + + return udc_suspend(udc_controller); +} + +/*----------------------------------------------------------------- + * Invoked on USB resume. May be called in_interrupt. + * Here we start the DR controller and enable the irq + *-----------------------------------------------------------------*/ +static int fsl_udc_resume(struct platform_device *pdev) +{ + pr_debug("%s(): stopped %d already_stopped %d\n", __func__, + udc_controller->stopped, udc_controller->already_stopped); + + /* + * If the controller was stopped at suspend time, then + * don't resume it now. + */ + if (udc_controller->already_stopped) { + udc_controller->already_stopped = 0; + pr_debug("gadget was already stopped, leaving early\n"); + return 0; + } + + /* Enable DR irq reg and set controller Run */ + if (udc_controller->stopped) { + dr_wake_up_enable(udc_controller, false); + dr_phy_low_power_mode(udc_controller, false); + mdelay(1); + + dr_controller_setup(udc_controller); + dr_controller_run(udc_controller); + } + udc_controller->usb_state = USB_STATE_ATTACHED; + udc_controller->ep0_dir = 0; + + printk(KERN_INFO "USB Gadget resumed\n"); + return 0; +} + +/*------------------------------------------------------------------------- + Register entry point for the peripheral controller driver +--------------------------------------------------------------------------*/ + +static struct platform_driver udc_driver = { + .remove = __exit_p(fsl_udc_remove), + /* these suspend and resume are not usb suspend and resume */ + .suspend = fsl_udc_suspend, + .resume = fsl_udc_resume, + .probe = fsl_udc_probe, + .driver = { + .name = driver_name, + .owner = THIS_MODULE, + }, +}; + +static int __init udc_init(void) +{ + printk(KERN_INFO "%s (%s)\n", driver_desc, DRIVER_VERSION); + return platform_driver_register(&udc_driver); +} + +module_init(udc_init); + +static void __exit udc_exit(void) +{ + platform_driver_unregister(&udc_driver); + printk(KERN_INFO "%s unregistered \n", driver_desc); +} + +module_exit(udc_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/arcotg_udc.h b/drivers/usb/gadget/arcotg_udc.h new file mode 100644 index 000000000000..79bedd2f8be0 --- /dev/null +++ b/drivers/usb/gadget/arcotg_udc.h @@ -0,0 +1,701 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file arcotg_udc.h + * @brief Freescale USB device/endpoint management registers + * @ingroup USB + */ + +#ifndef __ARCOTG_UDC_H +#define __ARCOTG_UDC_H + +#include <mach/hardware.h> + +#define TRUE 1 +#define FALSE 0 + +#define MSC_BULK_CB_WRAP_LEN 31 +#ifdef CONFIG_ARCH_MXC +#define USE_MSC_WR(len) (((cpu_is_mx37_rev(CHIP_REV_1_0) == 1) ||\ + (cpu_is_mx51_rev(CHIP_REV_2_0) < 0)) && ((len) == MSC_BULK_CB_WRAP_LEN)) +#else +#define USE_MSC_WR(len) false +#endif + +/* Iram patch */ +#ifdef CONFIG_USB_STATIC_IRAM_PPH +/* size of 1 qTD's buffer,one is for BULK IN and other is BULK OUT */ +#define IRAM_TD_PPH_SIZE (USB_IRAM_SIZE / 2) +#define IRAM_PPH_NTD 2 /* number of TDs in IRAM */ +#else +#define IRAM_TD_PPH_SIZE 0 +#define IRAM_PPH_NTD 0 +#endif + +#ifndef USB_IRAM_BASE_ADDR +#define USB_IRAM_BASE_ADDR 0 +#endif + +#define NEED_IRAM(ep) ((g_iram_size) && \ + ((ep)->desc->bmAttributes == USB_ENDPOINT_XFER_BULK)) + +#ifdef CONFIG_ARCH_MX51 +#define POSTPONE_FREE_LAST_DTD +#else +#undef POSTPONE_FREE_LAST_DTD +#endif + +/* ### define USB registers here + */ +#define USB_MAX_ENDPOINTS 8 +#define USB_MAX_PIPES (USB_MAX_ENDPOINTS*2) +#define USB_MAX_CTRL_PAYLOAD 64 +#define USB_DR_SYS_OFFSET 0x400 + +#define USB_DR_OFFSET 0x3100 + +struct usb_dr_device { + /* Capability register */ + u32 id; + u32 res1[35]; + u32 sbuscfg; /* sbuscfg ahb burst */ + u32 res11[27]; + u16 caplength; /* Capability Register Length */ + u16 hciversion; /* Host Controller Interface Version */ + u32 hcsparams; /* Host Controller Structual Parameters */ + u32 hccparams; /* Host Controller Capability Parameters */ + u32 res2[5]; + u32 dciversion; /* Device Controller Interface Version */ + u32 dccparams; /* Device Controller Capability Parameters */ + u32 res3[6]; + /* Operation register */ + u32 usbcmd; /* USB Command Register */ + u32 usbsts; /* USB Status Register */ + u32 usbintr; /* USB Interrupt Enable Register */ + u32 frindex; /* Frame Index Register */ + u32 res4; + u32 deviceaddr; /* Device Address */ + u32 endpointlistaddr; /* Endpoint List Address Register */ + u32 res5; + u32 burstsize; /* Master Interface Data Burst Size Register */ + u32 txttfilltuning; /* Transmit FIFO Tuning Controls Register */ + u32 res6[6]; + u32 configflag; /* Configure Flag Register */ + u32 portsc1; /* Port 1 Status and Control Register */ + u32 res7[7]; + u32 otgsc; /* On-The-Go Status and Control */ + u32 usbmode; /* USB Mode Register */ + u32 endptsetupstat; /* Endpoint Setup Status Register */ + u32 endpointprime; /* Endpoint Initialization Register */ + u32 endptflush; /* Endpoint Flush Register */ + u32 endptstatus; /* Endpoint Status Register */ + u32 endptcomplete; /* Endpoint Complete Register */ + u32 endptctrl[8 * 2]; /* Endpoint Control Registers */ + u32 res8[256]; +#ifdef CONFIG_ARCH_MX51 + u32 res9[128]; /* i.MX51 start from 0x800 */ +#endif + u32 usbctrl; + u32 otgmirror; + u32 phyctrl0; + u32 phyctrl1; + u32 ctrl1; + u32 uh2ctrl; +}; + + /* non-EHCI USB system interface registers (Big Endian) */ +struct usb_sys_interface { + u32 snoop1; + u32 snoop2; + u32 age_cnt_thresh; /* Age Count Threshold Register */ + u32 pri_ctrl; /* Priority Control Register */ + u32 si_ctrl; /* System Interface Control Register */ + u8 res[236]; + u32 control; /* General Purpose Control Register */ +}; + +/* ep0 transfer state */ +#define WAIT_FOR_SETUP 0 +#define DATA_STATE_XMIT 1 +#define DATA_STATE_NEED_ZLP 2 +#define WAIT_FOR_OUT_STATUS 3 +#define DATA_STATE_RECV 4 + +/* Device Controller Capability Parameter register */ +#define DCCPARAMS_DC 0x00000080 +#define DCCPARAMS_DEN_MASK 0x0000001f + +/* Frame Index Register Bit Masks */ +#define USB_FRINDEX_MASKS (0x3fff) +/* USB CMD Register Bit Masks */ +#define USB_CMD_RUN_STOP (0x00000001) +#define USB_CMD_CTRL_RESET (0x00000002) +#define USB_CMD_PERIODIC_SCHEDULE_EN (0x00000010) +#define USB_CMD_ASYNC_SCHEDULE_EN (0x00000020) +#define USB_CMD_INT_AA_DOORBELL (0x00000040) +#define USB_CMD_ASP (0x00000300) +#define USB_CMD_ASYNC_SCH_PARK_EN (0x00000800) +#define USB_CMD_SUTW (0x00002000) +#define USB_CMD_ATDTW (0x00004000) +#define USB_CMD_ITC (0x00FF0000) + +/* bit 15,3,2 are frame list size */ +#define USB_CMD_FRAME_SIZE_1024 (0x00000000) +#define USB_CMD_FRAME_SIZE_512 (0x00000004) +#define USB_CMD_FRAME_SIZE_256 (0x00000008) +#define USB_CMD_FRAME_SIZE_128 (0x0000000C) +#define USB_CMD_FRAME_SIZE_64 (0x00008000) +#define USB_CMD_FRAME_SIZE_32 (0x00008004) +#define USB_CMD_FRAME_SIZE_16 (0x00008008) +#define USB_CMD_FRAME_SIZE_8 (0x0000800C) + +/* bit 9-8 are async schedule park mode count */ +#define USB_CMD_ASP_00 (0x00000000) +#define USB_CMD_ASP_01 (0x00000100) +#define USB_CMD_ASP_10 (0x00000200) +#define USB_CMD_ASP_11 (0x00000300) +#define USB_CMD_ASP_BIT_POS (8) + +/* bit 23-16 are interrupt threshold control */ +#define USB_CMD_ITC_NO_THRESHOLD (0x00000000) +#define USB_CMD_ITC_1_MICRO_FRM (0x00010000) +#define USB_CMD_ITC_2_MICRO_FRM (0x00020000) +#define USB_CMD_ITC_4_MICRO_FRM (0x00040000) +#define USB_CMD_ITC_8_MICRO_FRM (0x00080000) +#define USB_CMD_ITC_16_MICRO_FRM (0x00100000) +#define USB_CMD_ITC_32_MICRO_FRM (0x00200000) +#define USB_CMD_ITC_64_MICRO_FRM (0x00400000) +#define USB_CMD_ITC_BIT_POS (16) + +/* USB STS Register Bit Masks */ +#define USB_STS_INT (0x00000001) +#define USB_STS_ERR (0x00000002) +#define USB_STS_PORT_CHANGE (0x00000004) +#define USB_STS_FRM_LST_ROLL (0x00000008) +#define USB_STS_SYS_ERR (0x00000010) +#define USB_STS_IAA (0x00000020) +#define USB_STS_RESET (0x00000040) +#define USB_STS_SOF (0x00000080) +#define USB_STS_SUSPEND (0x00000100) +#define USB_STS_HC_HALTED (0x00001000) +#define USB_STS_RCL (0x00002000) +#define USB_STS_PERIODIC_SCHEDULE (0x00004000) +#define USB_STS_ASYNC_SCHEDULE (0x00008000) + +/* USB INTR Register Bit Masks */ +#define USB_INTR_INT_EN (0x00000001) +#define USB_INTR_ERR_INT_EN (0x00000002) +#define USB_INTR_PTC_DETECT_EN (0x00000004) +#define USB_INTR_FRM_LST_ROLL_EN (0x00000008) +#define USB_INTR_SYS_ERR_EN (0x00000010) +#define USB_INTR_ASYN_ADV_EN (0x00000020) +#define USB_INTR_RESET_EN (0x00000040) +#define USB_INTR_SOF_EN (0x00000080) +#define USB_INTR_DEVICE_SUSPEND (0x00000100) + +/* Device Address bit masks */ +#define USB_DEVICE_ADDRESS_MASK (0xFE000000) +#define USB_DEVICE_ADDRESS_BIT_POS (25) + +/* endpoint list address bit masks */ +#define USB_EP_LIST_ADDRESS_MASK (0xfffff800) + +/* PORTSCX Register Bit Masks */ +#define PORTSCX_CURRENT_CONNECT_STATUS (0x00000001) +#define PORTSCX_CONNECT_STATUS_CHANGE (0x00000002) +#define PORTSCX_PORT_ENABLE (0x00000004) +#define PORTSCX_PORT_EN_DIS_CHANGE (0x00000008) +#define PORTSCX_OVER_CURRENT_ACT (0x00000010) +#define PORTSCX_OVER_CURRENT_CHG (0x00000020) +#define PORTSCX_PORT_FORCE_RESUME (0x00000040) +#define PORTSCX_PORT_SUSPEND (0x00000080) +#define PORTSCX_PORT_RESET (0x00000100) +#define PORTSCX_LINE_STATUS_BITS (0x00000C00) +#define PORTSCX_PORT_POWER (0x00001000) +#define PORTSCX_PORT_INDICTOR_CTRL (0x0000C000) +#define PORTSCX_PORT_TEST_CTRL (0x000F0000) +#define PORTSCX_WAKE_ON_CONNECT_EN (0x00100000) +#define PORTSCX_WAKE_ON_CONNECT_DIS (0x00200000) +#define PORTSCX_WAKE_ON_OVER_CURRENT (0x00400000) +#define PORTSCX_PHY_LOW_POWER_SPD (0x00800000) +#define PORTSCX_PORT_FORCE_FULL_SPEED (0x01000000) +#define PORTSCX_PORT_SPEED_MASK (0x0C000000) +#define PORTSCX_PORT_WIDTH (0x10000000) +#define PORTSCX_PHY_TYPE_SEL (0xC0000000) + +/* bit 11-10 are line status */ +#define PORTSCX_LINE_STATUS_SE0 (0x00000000) +#define PORTSCX_LINE_STATUS_JSTATE (0x00000400) +#define PORTSCX_LINE_STATUS_KSTATE (0x00000800) +#define PORTSCX_LINE_STATUS_UNDEF (0x00000C00) +#define PORTSCX_LINE_STATUS_BIT_POS (10) + +/* bit 15-14 are port indicator control */ +#define PORTSCX_PIC_OFF (0x00000000) +#define PORTSCX_PIC_AMBER (0x00004000) +#define PORTSCX_PIC_GREEN (0x00008000) +#define PORTSCX_PIC_UNDEF (0x0000C000) +#define PORTSCX_PIC_BIT_POS (14) + +/* bit 19-16 are port test control */ +#define PORTSCX_PTC_DISABLE (0x00000000) +#define PORTSCX_PTC_JSTATE (0x00010000) +#define PORTSCX_PTC_KSTATE (0x00020000) +#define PORTSCX_PTC_SEQNAK (0x00030000) +#define PORTSCX_PTC_PACKET (0x00040000) +#define PORTSCX_PTC_FORCE_EN (0x00050000) +#define PORTSCX_PTC_BIT_POS (16) + +/* bit 27-26 are port speed */ +#define PORTSCX_PORT_SPEED_FULL (0x00000000) +#define PORTSCX_PORT_SPEED_LOW (0x04000000) +#define PORTSCX_PORT_SPEED_HIGH (0x08000000) +#define PORTSCX_PORT_SPEED_UNDEF (0x0C000000) +#define PORTSCX_SPEED_BIT_POS (26) + +/* bit 28 is parallel transceiver width for UTMI interface */ +#define PORTSCX_PTW (0x10000000) +#define PORTSCX_PTW_8BIT (0x00000000) +#define PORTSCX_PTW_16BIT (0x10000000) + +/* bit 31-30 are port transceiver select */ +#define PORTSCX_PTS_UTMI (0x00000000) +#define PORTSCX_PTS_ULPI (0x80000000) +#define PORTSCX_PTS_FSLS (0xC0000000) +#define PORTSCX_PTS_BIT_POS (30) + +/* USB MODE Register Bit Masks */ +#define USB_MODE_CTRL_MODE_IDLE (0x00000000) +#define USB_MODE_CTRL_MODE_DEVICE (0x00000002) +#define USB_MODE_CTRL_MODE_HOST (0x00000003) +#define USB_MODE_CTRL_MODE_MASK 0x00000003 +#define USB_MODE_CTRL_MODE_RSV (0x00000001) +#define USB_MODE_ES 0x00000004 /* (big) Endian Sel */ +#define USB_MODE_SETUP_LOCK_OFF (0x00000008) +#define USB_MODE_STREAM_DISABLE (0x00000010) +/* Endpoint Flush Register */ +#define EPFLUSH_TX_OFFSET (0x00010000) +#define EPFLUSH_RX_OFFSET (0x00000000) + +/* Endpoint Setup Status bit masks */ +#define EP_SETUP_STATUS_MASK (0x0000003F) +#define EP_SETUP_STATUS_EP0 (0x00000001) + +/* ENDPOINTCTRLx Register Bit Masks */ +#define EPCTRL_TX_ENABLE (0x00800000) +#define EPCTRL_TX_DATA_TOGGLE_RST (0x00400000) /* Not EP0 */ +#define EPCTRL_TX_DATA_TOGGLE_INH (0x00200000) /* Not EP0 */ +#define EPCTRL_TX_TYPE (0x000C0000) +#define EPCTRL_TX_DATA_SOURCE (0x00020000) /* Not EP0 */ +#define EPCTRL_TX_EP_STALL (0x00010000) +#define EPCTRL_RX_ENABLE (0x00000080) +#define EPCTRL_RX_DATA_TOGGLE_RST (0x00000040) /* Not EP0 */ +#define EPCTRL_RX_DATA_TOGGLE_INH (0x00000020) /* Not EP0 */ +#define EPCTRL_RX_TYPE (0x0000000C) +#define EPCTRL_RX_DATA_SINK (0x00000002) /* Not EP0 */ +#define EPCTRL_RX_EP_STALL (0x00000001) + +/* bit 19-18 and 3-2 are endpoint type */ +#define EPCTRL_EP_TYPE_CONTROL (0) +#define EPCTRL_EP_TYPE_ISO (1) +#define EPCTRL_EP_TYPE_BULK (2) +#define EPCTRL_EP_TYPE_INTERRUPT (3) +#define EPCTRL_TX_EP_TYPE_SHIFT (18) +#define EPCTRL_RX_EP_TYPE_SHIFT (2) + +/* SNOOPn Register Bit Masks */ +#define SNOOP_ADDRESS_MASK (0xFFFFF000) +#define SNOOP_SIZE_ZERO (0x00) /* snooping disable */ +#define SNOOP_SIZE_4KB (0x0B) /* 4KB snoop size */ +#define SNOOP_SIZE_8KB (0x0C) +#define SNOOP_SIZE_16KB (0x0D) +#define SNOOP_SIZE_32KB (0x0E) +#define SNOOP_SIZE_64KB (0x0F) +#define SNOOP_SIZE_128KB (0x10) +#define SNOOP_SIZE_256KB (0x11) +#define SNOOP_SIZE_512KB (0x12) +#define SNOOP_SIZE_1MB (0x13) +#define SNOOP_SIZE_2MB (0x14) +#define SNOOP_SIZE_4MB (0x15) +#define SNOOP_SIZE_8MB (0x16) +#define SNOOP_SIZE_16MB (0x17) +#define SNOOP_SIZE_32MB (0x18) +#define SNOOP_SIZE_64MB (0x19) +#define SNOOP_SIZE_128MB (0x1A) +#define SNOOP_SIZE_256MB (0x1B) +#define SNOOP_SIZE_512MB (0x1C) +#define SNOOP_SIZE_1GB (0x1D) +#define SNOOP_SIZE_2GB (0x1E) /* 2GB snoop size */ + +/* pri_ctrl Register Bit Masks */ +#define PRI_CTRL_PRI_LVL1 (0x0000000C) +#define PRI_CTRL_PRI_LVL0 (0x00000003) + +/* si_ctrl Register Bit Masks */ +#define SI_CTRL_ERR_DISABLE (0x00000010) +#define SI_CTRL_IDRC_DISABLE (0x00000008) +#define SI_CTRL_RD_SAFE_EN (0x00000004) +#define SI_CTRL_RD_PREFETCH_DISABLE (0x00000002) +#define SI_CTRL_RD_PREFEFETCH_VAL (0x00000001) + +/* control Register Bit Masks */ +#define USB_CTRL_IOENB (0x00000004) +#define USB_CTRL_ULPI_INT0EN (0x00000001) +#define USB_CTRL_OTG_WUIR (0x80000000) +#define USB_CTRL_OTG_WUIE (0x08000000) +#define USB_CTRL_OTG_VWUE (0x00001000) +#define USB_CTRL_OTG_IWUE (0x00100000) + +/* PHY control0 Register Bit Masks */ +#define PHY_CTRL0_CONF2 (1 << 26) + +/* USB UH2 CTRL Register Bits */ +#define USB_UH2_OVBWK_EN (1 << 6) /* OTG VBUS Wakeup Enable */ +#define USB_UH2_OIDWK_EN (1 << 5) /* OTG ID Wakeup Enable */ +/*! + * Endpoint Queue Head data struct + * Rem: all the variables of qh are LittleEndian Mode + * and NEXT_POINTER_MASK should operate on a LittleEndian, Phy Addr + */ +struct ep_queue_head { + /*! + * Mult(31-30) , Zlt(29) , Max Pkt len and IOS(15) + */ + u32 max_pkt_length; + + /*! + * Current dTD Pointer(31-5) + */ + u32 curr_dtd_ptr; + + /*! + * Next dTD Pointer(31-5), T(0) + */ + u32 next_dtd_ptr; + + /*! + * Total bytes (30-16), IOC (15), MultO(11-10), STS (7-0) + */ + u32 size_ioc_int_sts; + + /*! + * Buffer pointer Page 0 (31-12) + */ + u32 buff_ptr0; + + /*! + * Buffer pointer Page 1 (31-12) + */ + u32 buff_ptr1; + + /*! + * Buffer pointer Page 2 (31-12) + */ + u32 buff_ptr2; + + /*! + * Buffer pointer Page 3 (31-12) + */ + u32 buff_ptr3; + + /*! + * Buffer pointer Page 4 (31-12) + */ + u32 buff_ptr4; + + /*! + * reserved field 1 + */ + u32 res1; + /*! + * Setup data 8 bytes + */ + u8 setup_buffer[8]; /* Setup data 8 bytes */ + + /*! + * reserved field 2,pad out to 64 bytes + */ + u32 res2[4]; +}; + +/* Endpoint Queue Head Bit Masks */ +#define EP_QUEUE_HEAD_MULT_POS (30) +#define EP_QUEUE_HEAD_ZLT_SEL (0x20000000) +#define EP_QUEUE_HEAD_MAX_PKT_LEN_POS (16) +#define EP_QUEUE_HEAD_MAX_PKT_LEN(ep_info) (((ep_info)>>16)&0x07ff) +#define EP_QUEUE_HEAD_IOS (0x00008000) +#define EP_QUEUE_HEAD_NEXT_TERMINATE (0x00000001) +#define EP_QUEUE_HEAD_IOC (0x00008000) +#define EP_QUEUE_HEAD_MULTO (0x00000C00) +#define EP_QUEUE_HEAD_STATUS_HALT (0x00000040) +#define EP_QUEUE_HEAD_STATUS_ACTIVE (0x00000080) +#define EP_QUEUE_CURRENT_OFFSET_MASK (0x00000FFF) +#define EP_QUEUE_HEAD_NEXT_POINTER_MASK 0xFFFFFFE0 +#define EP_QUEUE_FRINDEX_MASK (0x000007FF) +#define EP_MAX_LENGTH_TRANSFER (0x4000) + +/*! + * Endpoint Transfer Descriptor data struct + * Rem: all the variables of td are LittleEndian Mode + * must be 32-byte aligned + */ +struct ep_td_struct { + /*! + * Next TD pointer(31-5), T(0) set indicate invalid + */ + u32 next_td_ptr; + + /*! + * Total bytes (30-16), IOC (15),MultO(11-10), STS (7-0) + */ + u32 size_ioc_sts; + + /*! + * Buffer pointer Page 0 + */ + u32 buff_ptr0; + + /*! + * Buffer pointer Page 1 + */ + u32 buff_ptr1; + + /*! + * Buffer pointer Page 2 + */ + u32 buff_ptr2; + + /*! + * Buffer pointer Page 3 + */ + u32 buff_ptr3; + + /*! + * Buffer pointer Page 4 + */ + u32 buff_ptr4; + + /*! + * dma address of this td + * */ + dma_addr_t td_dma; + + /*! + * virtual address of next td + * */ + struct ep_td_struct *next_td_virt; + + /*! + * make it an even 16 words + * */ + u32 res[7]; +}; + +/*! + * Endpoint Transfer Descriptor bit Masks + */ +#define DTD_NEXT_TERMINATE (0x00000001) +#define DTD_IOC (0x00008000) +#define DTD_STATUS_ACTIVE (0x00000080) +#define DTD_STATUS_HALTED (0x00000040) +#define DTD_STATUS_DATA_BUFF_ERR (0x00000020) +#define DTD_STATUS_TRANSACTION_ERR (0x00000008) +#define DTD_RESERVED_FIELDS (0x80007300) +#define DTD_ADDR_MASK 0xFFFFFFE0 +#define DTD_PACKET_SIZE (0x7FFF0000) +#define DTD_LENGTH_BIT_POS (16) +#define DTD_ERROR_MASK (DTD_STATUS_HALTED | \ + DTD_STATUS_DATA_BUFF_ERR | \ + DTD_STATUS_TRANSACTION_ERR) +/* Alignment requirements; must be a power of two */ +#define DTD_ALIGNMENT 0x20 +#define QH_ALIGNMENT 2048 + +/* Controller dma boundary */ +#define UDC_DMA_BOUNDARY 0x1000 + +/* -----------------------------------------------------------------------*/ +/* ##### enum data +*/ +typedef enum { + e_ULPI, + e_UTMI_8BIT, + e_UTMI_16BIT, + e_SERIAL +} e_PhyInterface; + +/*-------------------------------------------------------------------------*/ + +struct fsl_req { + struct usb_request req; + struct list_head queue; + /* ep_queue() func will add + a request->queue into a udc_ep->queue 'd tail */ + struct fsl_ep *ep; + unsigned mapped; + + struct ep_td_struct *head, *tail; /* For dTD List + this is a BigEndian Virtual addr */ + unsigned int dtd_count; + /* just for IRAM patch */ + dma_addr_t oridma; /* original dma */ + size_t buffer_offset; /* offset of user buffer */ + int last_one; /* mark if reach to last packet */ + struct ep_td_struct *cur; /* current tranfer dtd */ +}; + +#define REQ_UNCOMPLETE (1) + +struct fsl_ep { + struct usb_ep ep; + struct list_head queue; + struct fsl_udc *udc; + struct ep_queue_head *qh; + const struct usb_endpoint_descriptor *desc; + struct usb_gadget *gadget; + + char name[14]; + unsigned stopped:1; +}; + +#define EP_DIR_IN 1 +#define EP_DIR_OUT 0 + +struct fsl_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct fsl_usb2_platform_data *pdata; + struct fsl_ep *eps; + unsigned int max_ep; + unsigned int irq; + + struct usb_ctrlrequest local_setup_buff; + spinlock_t lock; + u32 xcvr_type; + struct otg_transceiver *transceiver; + unsigned softconnect:1; + unsigned vbus_active:1; + unsigned stopped:1; + unsigned remote_wakeup:1; + unsigned already_stopped:1; + unsigned dr_remapped:1; + + struct ep_queue_head *ep_qh; /* Endpoints Queue-Head */ + struct fsl_req *status_req; /* ep0 status request */ + struct fsl_req *data_req; /* ep0 data request */ + struct dma_pool *td_pool; /* dma pool for DTD */ + enum fsl_usb2_phy_modes phy_mode; + + size_t ep_qh_size; /* size after alignment adjustment*/ + dma_addr_t ep_qh_dma; /* dma address of QH */ + + u32 max_pipes; /* Device max pipes */ + u32 max_use_endpts; /* Max endpointes to be used */ + u32 bus_reset; /* Device is bus reseting */ + u32 resume_state; /* USB state to resume */ + u32 usb_state; /* USB current state */ + u32 usb_next_state; /* USB next state */ + u32 ep0_dir; /* Endpoint zero direction: can be + USB_DIR_IN or USB_DIR_OUT */ + u32 usb_sof_count; /* SOF count */ + u32 errors; /* USB ERRORs count */ + u8 device_address; /* Device USB address */ + + struct completion *done; /* to make sure release() is done */ + u32 iram_buffer[IRAM_PPH_NTD]; + void *iram_buffer_v[IRAM_PPH_NTD]; +}; + +/*-------------------------------------------------------------------------*/ + +#ifdef DEBUG +#define DBG(fmt, args...) printk(KERN_DEBUG "[%s] " fmt "\n", \ + __func__, ## args) +#else +#define DBG(fmt, args...) do {} while (0) +#endif + +#if 0 +static void dump_msg(const char *label, const u8 * buf, unsigned int length) +{ + unsigned int start, num, i; + char line[52], *p; + + if (length >= 512) + return; + pr_debug("udc: %s, length %u:\n", label, length); + start = 0; + while (length > 0) { + num = min(length, 16u); + p = line; + for (i = 0; i < num; ++i) { + if (i == 8) + *p++ = ' '; + sprintf(p, " %02x", buf[i]); + p += 3; + } + *p = 0; + printk(KERN_DEBUG "%6x: %s\n", start, line); + buf += num; + start += num; + length -= num; + } +} +#endif + +#ifdef VERBOSE +#define VDBG DBG +#else +#define VDBG(stuff...) do {} while (0) +#endif + +#define ERR(stuff...) printk(KERN_ERR "udc: " stuff) +#define INFO(stuff...) printk(KERN_INFO "udc: " stuff) + +/*-------------------------------------------------------------------------*/ + +/* ### Add board specific defines here + */ + +/* + * ### pipe direction macro from device view + */ +#define USB_RECV (0) /* OUT EP */ +#define USB_SEND (1) /* IN EP */ + +/* + * ### internal used help routines. + */ +#define ep_index(EP) ((EP)->desc->bEndpointAddress&0xF) +#define ep_maxpacket(EP) ((EP)->ep.maxpacket) + +#define ep_is_in(EP) ( (ep_index(EP) == 0) ? (EP->udc->ep0_dir == \ + USB_DIR_IN ):((EP)->desc->bEndpointAddress \ + & USB_DIR_IN)==USB_DIR_IN) + +#define get_ep_by_pipe(udc, pipe) ((pipe == 1)? &udc->eps[0]: \ + &udc->eps[pipe]) +#define get_pipe_by_windex(windex) ((windex & USB_ENDPOINT_NUMBER_MASK) \ + * 2 + ((windex & USB_DIR_IN) ? 1 : 0)) + +/* Bulk only class request */ +#define USB_BULK_RESET_REQUEST 0xff + +#if defined(CONFIG_ARCH_MXC) || defined(CONFIG_ARCH_STMP3XXX) +#include <mach/fsl_usb_gadget.h> +#elif CONFIG_PPC32 +#include <asm/fsl_usb_gadget.h> +#endif + +#endif /* __ARCOTG_UDC_H */ diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index 8e0e9a0b7364..60a154db0cf7 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -92,6 +92,12 @@ #define gadget_is_pxa27x(g) 0 #endif +#ifdef CONFIG_USB_GADGET_ARC +#define gadget_is_arcotg(g) !strcmp("fsl-usb2-udc", (g)->name) +#else +#define gadget_is_arcotg(g) 0 +#endif + #ifdef CONFIG_USB_GADGET_ATMEL_USBA #define gadget_is_atmel_usba(g) !strcmp("atmel_usba_udc", (g)->name) #else @@ -239,6 +245,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x23; else if (gadget_is_langwell(gadget)) return 0x24; + else if (gadget_is_arcotg(gadget)) + return 0x25; return -ENOENT; } diff --git a/drivers/usb/gadget/stmp_updater.c b/drivers/usb/gadget/stmp_updater.c new file mode 100644 index 000000000000..0c74f35ebba1 --- /dev/null +++ b/drivers/usb/gadget/stmp_updater.c @@ -0,0 +1,479 @@ +/* + * Freescale STMP378X UUT driver + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +static u64 get_be64(u8 *buf) +{ + return ((u64)get_unaligned_be32(buf) << 32) | get_unaligned_be32(buf + 4); +} + +static int utp_init(struct fsg_dev *fsg) +{ + init_waitqueue_head(&utp_context.wq); + INIT_LIST_HEAD(&utp_context.read); + INIT_LIST_HEAD(&utp_context.write); + mutex_init(&utp_context.lock); + utp_context.buffer = vmalloc(0x10000); + if (!utp_context.buffer) + return -EIO; + utp_context.utp_version = 0x1ull; + fsg->utp = &utp_context; + return misc_register(&utp_dev); +} + +static void utp_exit(struct fsg_dev *fsg) +{ + vfree(utp_context.buffer); + misc_deregister(&utp_dev); +} + +static struct utp_user_data *utp_user_data_alloc(size_t size) +{ + struct utp_user_data *uud; + + uud = kzalloc(size + sizeof(*uud), GFP_KERNEL); + if (!uud) + return uud; + uud->data.size = size + sizeof(uud->data); + INIT_LIST_HEAD(&uud->link); + return uud; +} + +static void utp_user_data_free(struct utp_user_data *uud) +{ + list_del(&uud->link); + kfree(uud); +} + +#define WAIT_ACTIVITY(queue) \ + wait_event_interruptible(utp_context.wq, !list_empty(&utp_context.queue)) + +static ssize_t utp_file_read(struct file *file, + char __user *buf, + size_t size, + loff_t *off) +{ + struct utp_user_data *uud; + size_t size_to_put; + int free = 0; + + WAIT_ACTIVITY(read); + + mutex_lock(&utp_context.lock); + uud = list_first_entry(&utp_context.read, struct utp_user_data, link); + mutex_unlock(&utp_context.lock); + size_to_put = uud->data.size; + if (size >= size_to_put) + free = !0; + if (copy_to_user(buf, &uud->data, size_to_put)) + return -EACCES; + if (free) + utp_user_data_free(uud); + else { + pr_info("sizeof = %d, size = %d\n", + sizeof(uud->data), + uud->data.size); + + pr_err("Will not free utp_user_data, because buffer size = %d," + "need to put %d\n", size, size_to_put); + } + + return size_to_put; +} + + +static ssize_t utp_file_write(struct file *file, const char __user *buf, + size_t size, loff_t *off) +{ + struct utp_user_data *uud; + + if (size < sizeof(uud->data)) + return -EINVAL; + uud = utp_user_data_alloc(size); + if (copy_from_user(&uud->data, buf, size)) + return -EACCES; + mutex_lock(&utp_context.lock); + list_add_tail(&uud->link, &utp_context.write); + wake_up(&utp_context.wq); + mutex_unlock(&utp_context.lock); + return size; +} + +static int utp_get_sense(struct fsg_dev *fsg) +{ + if (UTP_CTX(fsg)->processed == 0) + return -1; + + UTP_CTX(fsg)->processed = 0; + return 0; +} + +static int utp_do_read(struct fsg_dev *fsg, void *data, size_t size) +{ + struct fsg_buffhd *bh; + int rc; + u32 amount_left; + unsigned int amount; + + /* Get the starting Logical Block Address and check that it's + * not too big */ + + amount_left = size; + if (unlikely(amount_left == 0)) + return -EIO; /* No default reply*/ + + pr_debug("%s: sending %d\n", __func__, size); + for (;;) { + /* Figure out how much we need to read: + * Try to read the remaining amount. + * But don't read more than the buffer size. + * And don't try to read past the end of the file. + * Finally, if we're not at a page boundary, don't read past + * the next page. + * If this means reading 0 then we were asked to read past + * the end of file. */ + amount = min((unsigned int) amount_left, mod_data.buflen); + + /* Wait for the next buffer to become available */ + bh = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + /* If we were asked to read past the end of file, + * end with an empty buffer. */ + if (amount == 0) { + bh->inreq->length = 0; + bh->state = BUF_STATE_FULL; + break; + } + + /* Perform the read */ + pr_info("Copied to %p, %d bytes started from %d\n", + bh->buf, amount, size - amount_left); + memcpy(bh->buf, data + size - amount_left, amount); + amount_left -= amount; + fsg->residue -= amount; + + bh->inreq->length = amount; + bh->state = BUF_STATE_FULL; + + /* Send this buffer and go read some more */ + bh->inreq->zero = 0; + + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + + fsg->next_buffhd_to_fill = bh->next; + + if (amount_left <= 0) + break; + } + + return size - amount_left; +} + +static int utp_do_write(struct fsg_dev *fsg, void *data, size_t size) +{ + struct fsg_buffhd *bh; + int get_some_more; + u32 amount_left_to_req, amount_left_to_write; + unsigned int amount; + int rc; + loff_t offset; + + /* Carry out the file writes */ + get_some_more = 1; + amount_left_to_req = amount_left_to_write = size; + + if (unlikely(amount_left_to_write == 0)) + return -EIO; + + offset = 0; + while (amount_left_to_write > 0) { + + /* Queue a request for more data from the host */ + bh = fsg->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY && get_some_more) { + + /* Figure out how much we want to get: + * Try to get the remaining amount. + * But don't get more than the buffer size. + * And don't try to go past the end of the file. + * If we're not at a page boundary, + * don't go past the next page. + * If this means getting 0, then we were asked + * to write past the end of file. + * Finally, round down to a block boundary. */ + amount = min(amount_left_to_req, mod_data.buflen); + + if (amount == 0) { + get_some_more = 0; + /* cry now */ + continue; + } + + /* Get the next buffer */ + amount_left_to_req -= amount; + if (amount_left_to_req == 0) + get_some_more = 0; + + /* amount is always divisible by 512, hence by + * the bulk-out maxpacket size */ + bh->outreq->length = bh->bulk_out_intended_length = + amount; + bh->outreq->short_not_ok = 1; + start_transfer(fsg, fsg->bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + continue; + } + + /* Write the received data to the backing file */ + bh = fsg->next_buffhd_to_drain; + if (bh->state == BUF_STATE_EMPTY && !get_some_more) + break; /* We stopped early */ + if (bh->state == BUF_STATE_FULL) { + smp_rmb(); + fsg->next_buffhd_to_drain = bh->next; + bh->state = BUF_STATE_EMPTY; + + /* Did something go wrong with the transfer? */ + if (bh->outreq->status != 0) + /* cry again, COMMUNICATION_FAILURE */ + break; + + amount = bh->outreq->actual; + + /* Perform the write */ + memcpy(data + offset, bh->buf, amount); + + offset += amount; + if (signal_pending(current)) + return -EINTR; /* Interrupted!*/ + amount_left_to_write -= amount; + fsg->residue -= amount; + + /* Did the host decide to stop early? */ + if (bh->outreq->actual != bh->outreq->length) { + fsg->short_packet_received = 1; + break; + } + continue; + } + + /* Wait for something to happen */ + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + return -EIO; +} + +static inline void utp_set_sense(struct fsg_dev *fsg, u16 code, u64 reply) +{ + UTP_CTX(fsg)->processed = true; + UTP_CTX(fsg)->sdinfo = reply & 0xFFFFFFFF; + UTP_CTX(fsg)->sdinfo_h = (reply >> 32) & 0xFFFFFFFF; + UTP_CTX(fsg)->sd = (UTP_SENSE_KEY << 16) | code; +} + +static void utp_poll(struct fsg_dev *fsg) +{ + struct utp_context *ctx = UTP_CTX(fsg); + struct utp_user_data *uud = NULL; + + mutex_lock(&ctx->lock); + if (!list_empty(&ctx->write)) + uud = list_first_entry(&ctx->write, struct utp_user_data, link); + mutex_unlock(&ctx->lock); + + if (uud) { + if (uud->data.flags & UTP_FLAG_STATUS) { + pr_debug("%s: exit with status %d\n", __func__, + uud->data.status); + UTP_SS_EXIT(fsg, uud->data.status); + } else { + pr_debug("%s: pass\n", __func__); + UTP_SS_PASS(fsg); + } + utp_user_data_free(uud); + } else { + pr_debug("%s: still busy...\n", __func__); + UTP_SS_BUSY(fsg, --ctx->counter); + } +} + +static int utp_exec(struct fsg_dev *fsg, + char *command, + int cmdsize, + unsigned long long payload) +{ + struct utp_user_data *uud = NULL, *uud2r; + struct utp_context *ctx = UTP_CTX(fsg); + + uud2r = utp_user_data_alloc(cmdsize + 1); + uud2r->data.flags = UTP_FLAG_COMMAND; + uud2r->data.payload = payload; + strncpy(uud2r->data.command, command, cmdsize); + + mutex_lock(&ctx->lock); + list_add_tail(&uud2r->link, &ctx->read); + mutex_unlock(&ctx->lock); + wake_up(&ctx->wq); + + if (command[0] == '!') /* there will be no response */ + return 0; + + WAIT_ACTIVITY(write); + + mutex_lock(&ctx->lock); + if (!list_empty(&ctx->write)) { + uud = list_first_entry(&ctx->write, struct utp_user_data, link); +#ifdef DEBUG + pr_info("UUD:\n\tFlags = %02X\n", uud->data.flags); + if (uud->data.flags & UTP_FLAG_DATA) { + pr_info("\tbufsize = %d\n", uud->data.bufsize); + print_hex_dump(KERN_DEBUG, "\t", DUMP_PREFIX_NONE, + 16, 2, uud->data.data, uud->data.bufsize, true); + } + if (uud->data.flags & UTP_FLAG_REPORT_BUSY) + pr_info("\tBUSY\n"); +#endif + } + mutex_unlock(&ctx->lock); + + if (uud->data.flags & UTP_FLAG_DATA) { + memcpy(ctx->buffer, uud->data.data, uud->data.bufsize); + UTP_SS_SIZE(fsg, uud->data.bufsize); + utp_user_data_free(uud); + return 0; + } + + if (uud->data.flags & UTP_FLAG_REPORT_BUSY) { + utp_user_data_free(uud); + ctx->counter = 0xFFFF; + UTP_SS_BUSY(fsg, ctx->counter); + return 0; + } + + utp_user_data_free(uud); + UTP_SS_PASS(fsg); + + return -1; +} + +static int utp_send_status(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh; + u8 status = USB_STATUS_PASS; + struct bulk_cs_wrap *csw; + int rc; + + /* Wait for the next buffer to become available */ + bh = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + if (fsg->phase_error) { + DBG(fsg, "sending phase-error status\n"); + status = USB_STATUS_PHASE_ERROR; + + } else if ((UTP_CTX(fsg)->sd & 0xFFFF) != UTP_REPLY_PASS) { + status = USB_STATUS_FAIL; + } + + csw = bh->buf; + + /* Store and send the Bulk-only CSW */ + csw->Signature = __constant_cpu_to_le32(USB_BULK_CS_SIG); + csw->Tag = fsg->tag; + csw->Residue = cpu_to_le32(fsg->residue); + csw->Status = status; + + bh->inreq->length = USB_BULK_CS_WRAP_LEN; + bh->inreq->zero = 0; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + return 0; +} + +static int utp_handle_message(struct fsg_dev *fsg, + char *cdb_data, + int default_reply) +{ + struct utp_msg *m = (struct utp_msg *)cdb_data; + void *data = NULL; + int r; + struct utp_user_data *uud2r; + unsigned long long param; + unsigned long tag; + + if (m->f0 != 0xF0) + return default_reply; + + tag = get_unaligned_be32((void *)&m->utp_msg_tag); + param = get_be64((void *)&m->param); + pr_debug("Type 0x%x, tag 0x%08lx, param %llx\n", + m->utp_msg_type, tag, param); + + switch ((enum utp_msg_type)m->utp_msg_type) { + + case UTP_POLL: + if (get_be64((void *)&m->param) == 1) { + pr_debug("%s: version request\n", __func__); + UTP_SS_EXIT(fsg, UTP_CTX(fsg)->utp_version); + break; + } + utp_poll(fsg); + break; + case UTP_EXEC: + pr_debug("%s: EXEC\n", __func__); + data = kzalloc(fsg->data_size, GFP_KERNEL); + utp_do_write(fsg, data, fsg->data_size); + utp_exec(fsg, data, fsg->data_size, param); + kfree(data); + break; + case UTP_GET: + pr_debug("%s: GET, %d bytes\n", __func__, fsg->data_size); + r = utp_do_read(fsg, UTP_CTX(fsg)->buffer, fsg->data_size); + UTP_SS_PASS(fsg); + break; + case UTP_PUT: + pr_debug("%s: PUT, %d bytes\n", __func__, fsg->data_size); + uud2r = utp_user_data_alloc(fsg->data_size); + uud2r->data.bufsize = fsg->data_size; + uud2r->data.flags = UTP_FLAG_DATA; + utp_do_write(fsg, uud2r->data.data, fsg->data_size); + /* don't know what will be written */ + mutex_lock(&UTP_CTX(fsg)->lock); + list_add_tail(&uud2r->link, &UTP_CTX(fsg)->read); + mutex_unlock(&UTP_CTX(fsg)->lock); + wake_up(&UTP_CTX(fsg)->wq); + UTP_SS_PASS(fsg); + break; + } + + utp_send_status(fsg); + return -1; +} + diff --git a/drivers/usb/gadget/stmp_updater.h b/drivers/usb/gadget/stmp_updater.h new file mode 100644 index 000000000000..e1248d1093c8 --- /dev/null +++ b/drivers/usb/gadget/stmp_updater.h @@ -0,0 +1,139 @@ +/* + * Freescale STMP378X UUT driver + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __STMP_UPDATER_H +#define __STMP_UPDATER_H + +#include <linux/miscdevice.h> +#include <linux/list.h> +#include <linux/vmalloc.h> + +static int utp_init(struct fsg_dev *fsg); +static void utp_exit(struct fsg_dev *fsg); +static ssize_t utp_file_read(struct file *file, + char __user *buf, + size_t size, + loff_t *off); + +static ssize_t utp_file_write(struct file *file, + const char __user *buf, + size_t size, + loff_t *off); + +static struct utp_user_data *utp_user_data_alloc(size_t size); +static void utp_user_data_free(struct utp_user_data *uud); +static int utp_get_sense(struct fsg_dev *fsg); +static int utp_do_read(struct fsg_dev *fsg, void *data, size_t size); +static int utp_do_write(struct fsg_dev *fsg, void *data, size_t size); +static inline void utp_set_sense(struct fsg_dev *fsg, u16 code, u64 reply); +static int utp_handle_message(struct fsg_dev *fsg, + char *cdb_data, + int default_reply); + +#define UTP_REPLY_PASS 0 +#define UTP_REPLY_EXIT 0x8001 +#define UTP_REPLY_BUSY 0x8002 +#define UTP_REPLY_SIZE 0x8003 +#define UTP_SENSE_KEY 9 + +#define UTP_MINOR 222 +/* MISC_DYNAMIC_MINOR would be better, but... */ + +#define UTP_COMMAND_SIZE 80 + +#define UTP_SS_EXIT(fsg, r) utp_set_sense(fsg, UTP_REPLY_EXIT, (u64)r) +#define UTP_SS_PASS(fsg) utp_set_sense(fsg, UTP_REPLY_PASS, 0) +#define UTP_SS_BUSY(fsg, r) utp_set_sense(fsg, UTP_REPLY_BUSY, (u64)r) +#define UTP_SS_SIZE(fsg, r) utp_set_sense(fsg, UTP_REPLY_SIZE, (u64)r) + +#pragma pack(1) +struct utp_msg { + u8 f0; + u8 utp_msg_type; + u32 utp_msg_tag; + union { + struct { + u32 param_lsb; + u32 param_msb; + }; + u64 param; + }; +}; + +enum utp_msg_type { + UTP_POLL = 0, + UTP_EXEC, + UTP_GET, + UTP_PUT, +}; + +static struct utp_context { + wait_queue_head_t wq; + struct mutex lock; + struct list_head read; + struct list_head write; + u32 sd, sdinfo, sdinfo_h; /* sense data */ + int processed; + u8 *buffer; + u32 counter; + u64 utp_version; +} utp_context; + +static const struct file_operations utp_fops = { + .open = nonseekable_open, + .read = utp_file_read, + .write = utp_file_write, +}; + +static struct miscdevice utp_dev = { + .minor = UTP_MINOR, + .name = "utp", + .fops = &utp_fops, +}; + +#define UTP_FLAG_COMMAND 0x00000001 +#define UTP_FLAG_DATA 0x00000002 +#define UTP_FLAG_STATUS 0x00000004 +#define UTP_FLAG_REPORT_BUSY 0x10000000 +struct utp_message { + u32 flags; + size_t size; + union { + struct { + u64 payload; + char command[1]; + }; + struct { + size_t bufsize; + u8 data[1]; + }; + u32 status; + }; +}; + +struct utp_user_data { + struct list_head link; + struct utp_message data; +}; +#pragma pack() + +static inline struct utp_context *UTP_CTX(struct fsg_dev *fsg) +{ + return (struct utp_context *)fsg->utp; +} + +#endif /* __STMP_UPDATER_H */ + diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 1a920c70b5a1..bccc47843ccb 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -59,9 +59,100 @@ config USB_EHCI_HCD To compile this driver as a module, choose M here: the module will be called ehci-hcd. +config USB_EHCI_ARC + bool "Support for Freescale controller" + depends on USB_EHCI_HCD && (ARCH_MXC || ARCH_STMP3XXX) + ---help--- + Some Freescale processors have an integrated High Speed + USBOTG controller, which supports EHCI host mode. + + Say "y" here to add support for this controller + to the EHCI HCD driver. + +config USB_EHCI_ARC_H1 + bool "Support for Host1 port on Freescale controller" + depends on USB_EHCI_ARC && (ARCH_MX27 || ARCH_MX3 || ARCH_MX51) + ---help--- + Enable support for the USB Host1 port. + +config USB_EHCI_ARC_H2 + bool "Support for Host2 port on Freescale controller" + depends on USB_EHCI_ARC && \ + (ARCH_MX25 || ARCH_MX27 || ARCH_MX3 || ARCH_MX35 || ARCH_MX51) + ---help--- + Enable support for the USB Host2 port. + +config USB_EHCI_ARC_OTG + bool "Support for DR host port on Freescale controller" + depends on USB_EHCI_ARC + default y + ---help--- + Enable support for the USB OTG port in HS/FS Host mode. + +config USB_STATIC_IRAM + bool "Use IRAM for USB" + depends on USB_EHCI_ARC + ---help--- + Enable this option to use IRAM instead of DRAM for USB + structures and buffers. This option will reduce bus + contention on systems with large (VGA+) framebuffer + devices and heavy USB activity. There are performance + penalties and usage restrictions when using this option. + + If in doubt, say N. + +choice + prompt "Select transceiver for DR port" + depends on USB_EHCI_ARC_OTG + default USB_EHCI_FSL_1504 if ARCH_MX3 + default USB_EHCI_FSL_1301 if ARCH_MX27 + default USB_EHCI_FSL_UTMI if (ARCH_MX25 || ARCH_MX35 || ARCH_MX37 || ARCH_MX51 || ARCH_STMP3XXX) + ---help--- + Choose the transceiver to use with the Freescale DR port. + +config USB_EHCI_FSL_MC13783 + bool "Freescale MC13783" + depends on !MACH_MX25_3DS + ---help--- + Enable support for the Full Speed Freescale MC13783 transceiver. + + The mx27ads, mx31ads and mx32ads boards require modifications + to support this transceiver. + +config USB_EHCI_FSL_1301 + bool "Philips ISP1301" + depends on !MACH_MX25_3DS + ---help--- + Enable support for the Full Speed Philips ISP1301 transceiver. + + This is the factory default for the mx27ads board. + The mx31ads and mx32ads boards require modifications + to support this transceiver. + +config USB_EHCI_FSL_1504 + bool "Philips ISP1504" + depends on MACH_MX27ADS || MACH_MX31ADS || MACH_MX32ADS ||MACH_MX31_3DS + ---help--- + Enable support for the High Speed Philips ISP1504 transceiver. + + This is the factory default for the mx31ads and mx32ads boards. + The mx27ads board requires modifications to support this transceiver. + +config USB_EHCI_FSL_UTMI + bool "Internal UTMI" + depends on (ARCH_MX25 || ARCH_MX35 || ARCH_MX37 || ARCH_MX51 || ARCH_STMP3XXX) + ---help--- + Enable support for the on-chip High Speed UTMI transceiver. + + This is the factory default for the mx35ads board. + +endchoice + + config USB_EHCI_ROOT_HUB_TT bool "Root Hub Transaction Translators" depends on USB_EHCI_HCD + default y if USB_EHCI_ARC ---help--- Some EHCI chips have vendor-specific extensions to integrate transaction translators, so that no OHCI or UHCI companion diff --git a/drivers/usb/host/ehci-arc.c b/drivers/usb/host/ehci-arc.c new file mode 100644 index 000000000000..17a0f5def731 --- /dev/null +++ b/drivers/usb/host/ehci-arc.c @@ -0,0 +1,650 @@ +/* + * Copyright (c) 2005 MontaVista Software + * + * 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. + * + * Ported to 834x by Randy Vinson <rvinson@mvista.com> using code provided + * by Hunter Wu. + */ + +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/usb/otg.h> + +#include "ehci-fsl.h" +#include <mach/fsl_usb.h> + +extern struct resource *otg_get_resources(void); + +static int regs_remapped /* = 0 */; +#undef EHCI_PROC_PTC +#ifdef EHCI_PROC_PTC /* /proc PORTSC:PTC support */ +/* + * write a PORTSC:PTC value to /proc/driver/ehci-ptc + * to put the controller into test mode. + */ +#include <linux/proc_fs.h> +#include <asm/uaccess.h> +#define EFPSL 3 /* ehci fsl proc string length */ + +static int ehci_fsl_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + return 0; +} + +static int ehci_fsl_proc_write(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + int ptc; + u32 portsc; + struct ehci_hcd *ehci = (struct ehci_hcd *) data; + char str[EFPSL] = {0}; + + if (count > EFPSL-1) + return -EINVAL; + + if (copy_from_user(str, buffer, count)) + return -EFAULT; + + str[count] = '\0'; + + ptc = simple_strtoul(str, NULL, 0); + + portsc = ehci_readl(ehci, &ehci->regs->port_status[0]); + portsc &= ~(0xf << 16); + portsc |= (ptc << 16); + printk(KERN_INFO "PTC %x portsc %08x\n", ptc, portsc); + + ehci_writel(ehci, portsc, &ehci->regs->port_status[0]); + + return count; +} + +static int ehci_testmode_init(struct ehci_hcd *ehci) +{ + struct proc_dir_entry *entry; + + entry = create_proc_read_entry("driver/ehci-ptc", 0644, NULL, + ehci_fsl_proc_read, ehci); + if (!entry) + return -ENODEV; + + entry->write_proc = ehci_fsl_proc_write; + return 0; +} +#else +static int ehci_testmode_init(struct ehci_hcd *ehci) +{ + return 0; +} +#endif /* /proc PORTSC:PTC support */ + + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + +/** + * usb_hcd_fsl_probe - initialize FSL-based HCDs + * @drvier: Driver to be used for this HCD + * @pdev: USB Host Controller being probed + * Context: !in_interrupt() + * + * Allocates basic resources for this USB host controller. + * + */ +int usb_hcd_fsl_probe(const struct hc_driver *driver, + struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + struct usb_hcd *hcd; + struct resource *res; + int irq; + int retval; + + pr_debug("initializing FSL-SOC USB Controller\n"); + + /* Need platform data for setup */ + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, + "No platform data for %s.\n", dev_name(&pdev->dev)); + return -ENODEV; + } + + /* + * This is a host mode driver, verify that we're supposed to be + * in host mode. + */ + if (!((pdata->operating_mode == FSL_USB2_DR_HOST) || + (pdata->operating_mode == FSL_USB2_MPH_HOST) || + (pdata->operating_mode == FSL_USB2_DR_OTG))) { + dev_err(&pdev->dev, + "Non Host Mode configured for %s. Wrong driver linked.\n", + dev_name(&pdev->dev)); + return -ENODEV; + } + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + retval = -ENOMEM; + goto err1; + } + +#if defined(CONFIG_USB_OTG) + if (pdata->operating_mode == FSL_USB2_DR_OTG) { + res = otg_get_resources(); + if (!res) { + dev_err(&pdev->dev, + "Found HC with no IRQ. Check %s setup!\n", + dev_name(&pdev->dev)); + return -ENODEV; + } + irq = res[1].start; + hcd->rsrc_start = res[0].start; + hcd->rsrc_len = res[0].end - res[0].start + 1; + } else +#endif + { + if ((pdev->dev.parent) && + (to_platform_device(pdev->dev.parent)->resource)) { + pdev->resource = + to_platform_device(pdev->dev.parent)->resource; + pdev->num_resources = + to_platform_device(pdev->dev.parent)->num_resources; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, + "Found HC with no IRQ. Check %s setup!\n", + dev_name(&pdev->dev)); + return -ENODEV; + } + irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, + driver->description)) { + dev_dbg(&pdev->dev, "controller already in use\n"); + retval = -EBUSY; + goto err2; + } + } + + if (!(pdata->port_enables & FSL_USB2_DONT_REMAP)) { + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + + if (hcd->regs == NULL) { + dev_dbg(&pdev->dev, "error mapping memory\n"); + retval = -EFAULT; + goto err3; + } + regs_remapped = 1; + } else + hcd->regs = (void __iomem *)(u32)(hcd->rsrc_start); + + pdata->regs = hcd->regs; + + /* + * do platform specific init: check the clock, grab/config pins, etc. + */ + if (pdata->platform_init && pdata->platform_init(pdev)) { + retval = -ENODEV; + goto err3; + } + + fsl_platform_set_host_mode(hcd); + hcd->power_budget = pdata->power_budget; + + retval = usb_add_hcd(hcd, irq, IRQF_DISABLED | IRQF_SHARED); + if (retval != 0) + goto err4; + + fsl_platform_set_vbus_power(pdata, 1); + +#ifdef CONFIG_USB_OTG + if (pdata->operating_mode == FSL_USB2_DR_OTG) { + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + dbg("pdev=0x%p hcd=0x%p ehci=0x%p\n", pdev, hcd, ehci); + + ehci->transceiver = otg_get_transceiver(); + dbg("ehci->transceiver=0x%p\n", ehci->transceiver); + + if (ehci->transceiver) { + retval = otg_set_host(ehci->transceiver, + &ehci_to_hcd(ehci)->self); + if (retval) { + if (ehci->transceiver) + put_device(ehci->transceiver->dev); + goto err4; + } + } else { + printk(KERN_ERR "can't find transceiver\n"); + retval = -ENODEV; + goto err4; + } + } +#endif + + if (pdata->suspended) { + pdata->suspended = 0; + if (pdata->already_suspended) + pdata->already_suspended = 0; + } + + fsl_platform_set_ahb_burst(hcd); + ehci_testmode_init(hcd_to_ehci(hcd)); + return retval; + +err4: + if (regs_remapped) + iounmap(hcd->regs); +err3: + if (pdata->operating_mode != FSL_USB2_DR_OTG) + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err2: + usb_put_hcd(hcd); +err1: + dev_err(&pdev->dev, "init %s fail, %d\n", dev_name(&pdev->dev), retval); + if (pdata->platform_uninit) + pdata->platform_uninit(pdata); + return retval; +} + +/* may be called without controller electrically present */ +/* may be called with controller, bus, and devices active */ + +/** + * usb_hcd_fsl_remove - shutdown processing for FSL-based HCDs + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_hcd_fsl_probe(). + * + */ +static void usb_hcd_fsl_remove(struct usb_hcd *hcd, + struct platform_device *pdev) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + /* DDD shouldn't we turn off the power here? */ + fsl_platform_set_vbus_power(pdata, 0); + + if (ehci->transceiver) { + (void)otg_set_host(ehci->transceiver, 0); + put_device(ehci->transceiver->dev); + } else { + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + } + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + + /* + * do platform specific un-initialization: + * release iomux pins, etc. + */ + if (pdata->platform_uninit) + pdata->platform_uninit(pdata); + + if (regs_remapped) + iounmap(hcd->regs); +} + +static void fsl_setup_phy(struct ehci_hcd *ehci, + enum fsl_usb2_phy_modes phy_mode, int port_offset) +{ + u32 portsc; + + portsc = ehci_readl(ehci, &ehci->regs->port_status[port_offset]); + portsc &= ~(PORT_PTS_MSK | PORT_PTS_PTW); + + switch (phy_mode) { + case FSL_USB2_PHY_ULPI: + portsc |= PORT_PTS_ULPI; + break; + case FSL_USB2_PHY_SERIAL: + portsc |= PORT_PTS_SERIAL; + break; + case FSL_USB2_PHY_UTMI_WIDE: + portsc |= PORT_PTS_PTW; + /* fall through */ + case FSL_USB2_PHY_UTMI: + portsc |= PORT_PTS_UTMI; + break; + case FSL_USB2_PHY_NONE: + break; + } + ehci_writel(ehci, portsc, &ehci->regs->port_status[port_offset]); +} + +/* called after powerup, by probe or system-pm "wakeup" */ +static int ehci_fsl_reinit(struct ehci_hcd *ehci) +{ + fsl_platform_usb_setup(ehci); + ehci_port_power(ehci, 0); + + return 0; +} + +/* called during probe() after chip reset completes */ +static int ehci_fsl_setup(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int retval; + + /* EHCI registers start at offset 0x100 */ + ehci->caps = hcd->regs + 0x100; + ehci->regs = hcd->regs + 0x100 + + HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase)); + dbg_hcs_params(ehci, "reset"); + dbg_hcc_params(ehci, "reset"); + + /* cache this readonly data; minimize chip reads */ + ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); + + retval = ehci_halt(ehci); + if (retval) + return retval; + + /* data structure init */ + retval = ehci_init(hcd); + if (retval) + return retval; + + hcd->has_tt = 1; + + ehci->sbrn = 0x20; + + ehci_reset(ehci); + + retval = ehci_fsl_reinit(ehci); + return retval; +} + +static const struct hc_driver ehci_fsl_hc_driver = { + .description = hcd_name, + .product_desc = "Freescale On-Chip EHCI Host Controller", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = ehci_fsl_setup, + .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, + + /* + * 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_bus_suspend, + .bus_resume = ehci_bus_resume, + .start_port_reset = ehci_start_port_reset, + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +static int ehci_fsl_drv_probe(struct platform_device *pdev) +{ + if (usb_disabled()) + return -ENODEV; + + /* FIXME we only want one one probe() not two */ + return usb_hcd_fsl_probe(&ehci_fsl_hc_driver, pdev); +} + +static int ehci_fsl_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + /* FIXME we only want one one remove() not two */ + usb_hcd_fsl_remove(hcd, pdev); + return 0; +} + +#ifdef CONFIG_PM +extern void usb_host_set_wakeup(struct device *wkup_dev, bool para); +/* suspend/resume, section 4.3 */ + +/* These routines rely on the bus (pci, platform, etc) + * to handle powerdown and wakeup, and currently also on + * transceivers that don't need any software attention to set up + * the right sort of wakeup. + * + * They're also used for turning on/off the port when doing OTG. + */ +static int ehci_fsl_drv_suspend(struct platform_device *pdev, + pm_message_t message) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + u32 tmp, port_status; + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + +#ifdef DEBUG + u32 mode = ehci_readl(ehci, hcd->regs + FSL_SOC_USB_USBMODE); + mode &= USBMODE_CM_MASK; + tmp = ehci_readl(ehci, hcd->regs + 0x140); /* usbcmd */ + + printk(KERN_DEBUG "%s('%s'): suspend=%d already_suspended=%d " + "mode=%d usbcmd %08x\n", __func__, pdata->name, + pdata->suspended, pdata->already_suspended, mode, tmp); +#endif + + /* + * If the controller is already suspended, then this must be a + * PM suspend. Remember this fact, so that we will leave the + * controller suspended at PM resume time. + */ + if (pdata->suspended) { + pr_debug("%s: already suspended, leaving early\n", __func__); + pdata->already_suspended = 1; + return 0; + } + + pr_debug("%s: suspending...\n", __func__); + + printk(KERN_INFO "USB Host suspended\n"); + + port_status = ehci_readl(ehci, &ehci->regs->port_status[0]); + hcd->state = HC_STATE_SUSPENDED; + pdev->dev.power.power_state = PMSG_SUSPEND; + + /* ignore non-host interrupts */ + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + /* stop the controller */ + tmp = ehci_readl(ehci, &ehci->regs->command); + tmp &= ~CMD_RUN; + ehci_writel(ehci, tmp, &ehci->regs->command); + + /* save EHCI registers */ + pdata->pm_command = ehci_readl(ehci, &ehci->regs->command); + pdata->pm_command &= ~CMD_RUN; + pdata->pm_status = ehci_readl(ehci, &ehci->regs->status); + pdata->pm_intr_enable = ehci_readl(ehci, &ehci->regs->intr_enable); + pdata->pm_frame_index = ehci_readl(ehci, &ehci->regs->frame_index); + pdata->pm_segment = ehci_readl(ehci, &ehci->regs->segment); + pdata->pm_frame_list = ehci_readl(ehci, &ehci->regs->frame_list); + pdata->pm_async_next = ehci_readl(ehci, &ehci->regs->async_next); + pdata->pm_configured_flag = + ehci_readl(ehci, &ehci->regs->configured_flag); + pdata->pm_portsc = ehci_readl(ehci, &ehci->regs->port_status[0]); + + /* clear the W1C bits */ + pdata->pm_portsc &= cpu_to_hc32(ehci, ~PORT_RWC_BITS); + + pdata->suspended = 1; + + if (!device_may_wakeup(&(pdev->dev))) { + /* clear PP to cut power to the port */ + tmp = ehci_readl(ehci, &ehci->regs->port_status[0]); + tmp &= ~PORT_POWER; + ehci_writel(ehci, tmp, &ehci->regs->port_status[0]); + return 0; + } + + /* device_may_wakeup */ + if (!((ehci->transceiver) && + (readl(hcd->regs + 0x1A4) & (1 << 8)))) { + /* enable remote wake up irq */ + usb_host_set_wakeup(&(pdev->dev), true); + + /* We CAN NOT enable wake up by connetion and disconnection + * concurrently */ + tmp = ehci_readl(ehci, &ehci->regs->port_status[0]); + /* if there is no usb device connectted */ + if (port_status & PORT_CONNECT) { + /* enable wake up by usb device disconnection */ + tmp |= PORT_WKDISC_E; + tmp &= ~(PORT_WKOC_E | PORT_WKCONN_E); + } else { + /* enable wake up by usb device insertion */ + tmp |= PORT_WKCONN_E; + tmp &= ~(PORT_WKOC_E | PORT_WKDISC_E); + } + ehci_writel(ehci, tmp, &ehci->regs->port_status[0]); + + /* Set the port into suspend */ + tmp = ehci_readl(ehci, &ehci->regs->port_status[0]); + tmp |= PORT_SUSPEND; + ehci_writel(ehci, tmp, &ehci->regs->port_status[0]); + + /* Disable PHY clock */ + tmp = ehci_readl(ehci, &ehci->regs->port_status[0]); + tmp |= (1 << 23); + ehci_writel(ehci, tmp, &ehci->regs->port_status[0]); + } + + if (pdata->platform_suspend) + pdata->platform_suspend(pdata); + + return 0; +} + +static int ehci_fsl_drv_resume(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + u32 tmp; + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + pr_debug("%s('%s'): suspend=%d already_suspended=%d\n", __func__, + pdata->name, pdata->suspended, pdata->already_suspended); + + /* + * If the controller was already suspended at suspend time, + * then don't resume it now. + */ + if (pdata->already_suspended) { + pr_debug("already suspended, leaving early\n"); + pdata->already_suspended = 0; + return 0; + } + + if (!pdata->suspended) { + pr_debug("not suspended, leaving early\n"); + return 0; + } + + if (device_may_wakeup(&(pdev->dev))) { + tmp = ehci_readl(ehci, &ehci->regs->port_status[0]); + if (tmp & (1 << 23)) { + tmp &= ~(1 << 23); + ehci_writel(ehci, tmp, &ehci->regs->port_status[0]); + msleep(10); + } + } + + pdata->suspended = 0; + + pr_debug("%s resuming...\n", __func__); + + /* set host mode */ + fsl_platform_set_host_mode(hcd); + + if (pdata->platform_resume) + pdata->platform_resume(pdata); + + /* restore EHCI registers */ + ehci_writel(ehci, pdata->pm_command, &ehci->regs->command); + ehci_writel(ehci, pdata->pm_intr_enable, &ehci->regs->intr_enable); + ehci_writel(ehci, pdata->pm_frame_index, &ehci->regs->frame_index); + ehci_writel(ehci, pdata->pm_segment, &ehci->regs->segment); + ehci_writel(ehci, pdata->pm_frame_list, &ehci->regs->frame_list); + ehci_writel(ehci, pdata->pm_async_next, &ehci->regs->async_next); + ehci_writel(ehci, pdata->pm_configured_flag, + &ehci->regs->configured_flag); + ehci_writel(ehci, pdata->pm_portsc, &ehci->regs->port_status[0]); + + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + hcd->state = HC_STATE_RUNNING; + pdev->dev.power.power_state = PMSG_ON; + + tmp = ehci_readl(ehci, &ehci->regs->command); + tmp |= CMD_RUN; + ehci_writel(ehci, tmp, &ehci->regs->command); + + usb_hcd_resume_root_hub(hcd); + + printk(KERN_INFO "USB Host resumed\n"); + return 0; +} +#endif /* CONFIG_USB_OTG */ + +MODULE_ALIAS("platform:fsl-ehci"); + +static struct platform_driver ehci_fsl_driver = { + .probe = ehci_fsl_drv_probe, + .remove = ehci_fsl_drv_remove, + .shutdown = usb_hcd_platform_shutdown, +#ifdef CONFIG_PM + .suspend = ehci_fsl_drv_suspend, + .resume = ehci_fsl_drv_resume, +#endif + .driver = { + .name = "fsl-ehci", + }, +}; diff --git a/drivers/usb/host/ehci-fsl.h b/drivers/usb/host/ehci-fsl.h index b5e59db53347..8a12eec68a90 100644 --- a/drivers/usb/host/ehci-fsl.h +++ b/drivers/usb/host/ehci-fsl.h @@ -19,6 +19,9 @@ #define _EHCI_FSL_H /* offsets for the non-ehci registers in the FSL SOC USB controller */ +#define FSL_SOC_USB_SBUSCFG 0x90 +#define FSL_SOC_USB_BURSTSIZE 0x160 +#define FSL_SOC_USB_TXFILLTUNING 0x164 #define FSL_SOC_USB_ULPIVP 0x170 #define FSL_SOC_USB_PORTSC1 0x184 #define PORT_PTS_MSK (3<<30) @@ -26,8 +29,12 @@ #define PORT_PTS_ULPI (2<<30) #define PORT_PTS_SERIAL (3<<30) #define PORT_PTS_PTW (1<<28) +#define PORT_PTS_PHCD (1<<23) #define FSL_SOC_USB_PORTSC2 0x188 #define FSL_SOC_USB_USBMODE 0x1a8 +#define USBMODE_CM_HOST (3 << 0) /* controller mode: host */ +#define USBMODE_ES (1 << 2) /* (Big) Endian Select */ + #define FSL_SOC_USB_SNOOP1 0x400 /* NOTE: big-endian */ #define FSL_SOC_USB_SNOOP2 0x404 /* NOTE: big-endian */ #define FSL_SOC_USB_AGECNTTHRSH 0x408 /* NOTE: big-endian */ diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig index aa884d072f0b..8ee386a77182 100644 --- a/drivers/usb/otg/Kconfig +++ b/drivers/usb/otg/Kconfig @@ -59,4 +59,16 @@ config NOP_USB_XCEIV built-in with usb ip or which are autonomous and doesn't require any phy programming such as ISP1x04 etc. +config TRANSCEIVER_MXC_OTG + tristate "USB OTG pin detect support" + depends on (MC13783_MXC || ISP1504_MXC) && USB_GADGET && USB_EHCI_HCD && USB_OTG + help + Support for USB OTG PIN detect on MXC platforms. + +config UTMI_MXC_OTG + tristate "USB OTG pin detect support for UTMI PHY" + depends on USB_EHCI_FSL_UTMI && USB_GADGET && USB_EHCI_HCD && USB_OTG + help + Support for USB OTG PIN detect for UTMI PHY on MXC platforms. + endif # USB || OTG diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile index 208167856529..338caa3525b1 100644 --- a/drivers/usb/otg/Makefile +++ b/drivers/usb/otg/Makefile @@ -10,6 +10,9 @@ obj-$(CONFIG_USB_GPIO_VBUS) += gpio_vbus.o obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o obj-$(CONFIG_TWL4030_USB) += twl4030-usb.o obj-$(CONFIG_NOP_USB_XCEIV) += nop-usb-xceiv.o +fsl_otg_arc-objs := fsl_otg.o otg_fsm.o +obj-$(CONFIG_ISP1504_MXC_OTG) += fsl_otg_arc.o +obj-$(CONFIG_UTMI_MXC_OTG) += fsl_otg_arc.o ccflags-$(CONFIG_USB_DEBUG) += -DDEBUG ccflags-$(CONFIG_USB_GADGET_DEBUG) += -DDEBUG diff --git a/drivers/usb/otg/fsl_otg.c b/drivers/usb/otg/fsl_otg.c new file mode 100644 index 000000000000..d5c8cf0434e3 --- /dev/null +++ b/drivers/usb/otg/fsl_otg.c @@ -0,0 +1,1200 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * + * Author: Li Yang <LeoLi@freescale.com> + * Jerry Huang <Chang-Ming.Huang@freescale.com> + * + * Initialization based on code from Shlomi Gridish. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/proc_fs.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/reboot.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/usb.h> +#include <linux/device.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/workqueue.h> +#include <linux/time.h> +#include <linux/fsl_devices.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/byteorder.h> +#include <asm/uaccess.h> +#include <asm/unaligned.h> + +#include "fsl_otg.h" + +#define CONFIG_USB_OTG_DEBUG_FILES +#define DRIVER_VERSION "$Revision: 1.55 $" +#define DRIVER_AUTHOR "Jerry Huang/Li Yang" +#define DRIVER_DESC "Freescale USB OTG Driver" +#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC + +MODULE_DESCRIPTION("Freescale USB OTG Transceiver Driver"); + +static const char driver_name[] = "fsl-usb2-otg"; + +const pm_message_t otg_suspend_state = { + .event = 1, +}; + +#define HA_DATA_PULSE 1 + +volatile static struct usb_dr_mmap *usb_dr_regs; +static struct fsl_otg *fsl_otg_dev; +static int srp_wait_done; + +/* FSM timers */ +struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, + *b_ase0_brst_tmr, *b_se0_srp_tmr; + +/* Driver specific timers */ +struct fsl_otg_timer *b_data_pulse_tmr, *b_vbus_pulse_tmr, *b_srp_fail_tmr, + *b_srp_wait_tmr, *a_wait_enum_tmr; + +static struct list_head active_timers; + +static struct fsl_otg_config fsl_otg_initdata = { + .otg_port = 1, +}; + +int write_ulpi(u8 addr, u8 data) +{ + u32 temp; + temp = 0x60000000 | (addr << 16) | data; + temp = cpu_to_le32(temp); + usb_dr_regs->ulpiview = temp; + return 0; +} + +/* prototype declaration */ +void fsl_otg_add_timer(void *timer); +void fsl_otg_del_timer(void *timer); + +/* -------------------------------------------------------------*/ +/* Operations that will be called from OTG Finite State Machine */ + +/* Charge vbus for vbus pulsing in SRP */ +void fsl_otg_chrg_vbus(int on) +{ + if (on) + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & + ~OTGSC_CTRL_VBUS_DISCHARGE) | + OTGSC_CTRL_VBUS_CHARGE); + else + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & ~OTGSC_CTRL_VBUS_CHARGE)); +} + +/* Discharge vbus through a resistor to ground */ +void fsl_otg_dischrg_vbus(int on) +{ + if (on) + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK) + | OTGSC_CTRL_VBUS_DISCHARGE); + else + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & + ~OTGSC_CTRL_VBUS_DISCHARGE)); +} + +/* A-device driver vbus, controlled through PP bit in PORTSC */ +void fsl_otg_drv_vbus(int on) +{ +/* if (on) + usb_dr_regs->portsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->portsc) & + ~PORTSC_W1C_BITS) | PORTSC_PORT_POWER); + else + usb_dr_regs->portsc = + cpu_to_le32(le32_to_cpu(usb_dr_regs->portsc) & + ~PORTSC_W1C_BITS & ~PORTSC_PORT_POWER); +*/ +} + +/* + * Pull-up D+, signalling connect by periperal. Also used in + * data-line pulsing in SRP + */ +void fsl_otg_loc_conn(int on) +{ + if (on) + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK) | OTGSC_CTRL_DATA_PULSING); + else + usb_dr_regs->otgsc = + cpu_to_le32(le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & ~OTGSC_CTRL_DATA_PULSING); +} + +/* Generate SOF by host. This is controlled through suspend/resume the + * port. In host mode, controller will automatically send SOF. + * Suspend will block the data on the port. + */ +void fsl_otg_loc_sof(int on) +{ + u32 tmpval; + + tmpval = readl(&fsl_otg_dev->dr_mem_map->portsc) & ~PORTSC_W1C_BITS; + if (on) + tmpval |= PORTSC_PORT_FORCE_RESUME; + else + tmpval |= PORTSC_PORT_SUSPEND; + writel(tmpval, &fsl_otg_dev->dr_mem_map->portsc); + +} + +/* Start SRP pulsing by data-line pulsing, followed with v-bus pulsing. */ +void fsl_otg_start_pulse(void) +{ + srp_wait_done = 0; +#ifdef HA_DATA_PULSE + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK) + | OTGSC_HA_DATA_PULSE); +#else + fsl_otg_loc_conn(1); +#endif + + fsl_otg_add_timer(b_data_pulse_tmr); +} + +void fsl_otg_pulse_vbus(void); + +void b_data_pulse_end(unsigned long foo) +{ +#ifdef HA_DATA_PULSE +#else + fsl_otg_loc_conn(0); +#endif + + /* Do VBUS pulse after data pulse */ + fsl_otg_pulse_vbus(); +} + +void fsl_otg_pulse_vbus(void) +{ + srp_wait_done = 0; + fsl_otg_chrg_vbus(1); + /* start the timer to end vbus charge */ + fsl_otg_add_timer(b_vbus_pulse_tmr); +} + +void b_vbus_pulse_end(unsigned long foo) +{ + fsl_otg_chrg_vbus(0); + + /* As USB3300 using the same a_sess_vld and b_sess_vld voltage + * we need to discharge the bus for a while to distinguish + * residual voltage of vbus pulsing and A device pull up */ + fsl_otg_dischrg_vbus(1); + fsl_otg_add_timer(b_srp_wait_tmr); +} + +void b_srp_end(unsigned long foo) +{ + fsl_otg_dischrg_vbus(0); + srp_wait_done = 1; + + if ((fsl_otg_dev->otg.state == OTG_STATE_B_SRP_INIT) && + fsl_otg_dev->fsm.b_sess_vld) + fsl_otg_dev->fsm.b_srp_done = 1; +} + +/* Workaround for a_host suspending too fast. When a_bus_req=0, + * a_host will start by SRP. It needs to set b_hnp_enable before + * actually suspending to start HNP + */ +void a_wait_enum(unsigned long foo) +{ + VDBG("a_wait_enum timeout\n"); + if (!fsl_otg_dev->otg.host->b_hnp_enable) + fsl_otg_add_timer(a_wait_enum_tmr); + else + otg_statemachine(&fsl_otg_dev->fsm); +} + +/* ------------------------------------------------------*/ + +/* The timeout callback function to set time out bit */ +void set_tmout(unsigned long indicator) +{ + *(int *)indicator = 1; +} + +/* Initialize timers */ +int fsl_otg_init_timers(struct otg_fsm *fsm) +{ + /* FSM used timers */ + a_wait_vrise_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_VRISE, + (unsigned long)&fsm->a_wait_vrise_tmout); + if (a_wait_vrise_tmr == NULL) + return -ENOMEM; + + a_wait_bcon_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_BCON, + (unsigned long)&fsm->a_wait_bcon_tmout); + if (a_wait_bcon_tmr == NULL) + return -ENOMEM; + + a_aidl_bdis_tmr = otg_timer_initializer(&set_tmout, TA_AIDL_BDIS, + (unsigned long)&fsm->a_aidl_bdis_tmout); + if (a_aidl_bdis_tmr == NULL) + return -ENOMEM; + + b_ase0_brst_tmr = otg_timer_initializer(&set_tmout, TB_ASE0_BRST, + (unsigned long)&fsm->b_ase0_brst_tmout); + if (b_ase0_brst_tmr == NULL) + return -ENOMEM; + + b_se0_srp_tmr = otg_timer_initializer(&set_tmout, TB_SE0_SRP, + (unsigned long)&fsm->b_se0_srp); + if (b_se0_srp_tmr == NULL) + return -ENOMEM; + + b_srp_fail_tmr = otg_timer_initializer(&set_tmout, TB_SRP_FAIL, + (unsigned long)&fsm->b_srp_done); + if (b_srp_fail_tmr == NULL) + return -ENOMEM; + + a_wait_enum_tmr = otg_timer_initializer(&a_wait_enum, 10, + (unsigned long)&fsm); + if (a_wait_enum_tmr == NULL) + return -ENOMEM; + + /* device driver used timers */ + b_srp_wait_tmr = otg_timer_initializer(&b_srp_end, TB_SRP_WAIT, 0); + if (b_srp_wait_tmr == NULL) + return -ENOMEM; + + b_data_pulse_tmr = otg_timer_initializer(&b_data_pulse_end, + TB_DATA_PLS, 0); + if (b_data_pulse_tmr == NULL) + return -ENOMEM; + + b_vbus_pulse_tmr = otg_timer_initializer(&b_vbus_pulse_end, + TB_VBUS_PLS, 0); + if (b_vbus_pulse_tmr == NULL) + return -ENOMEM; + + return 0; +} + +/* Uninitialize timers */ +void fsl_otg_uninit_timers(void) +{ + /* FSM used timers */ + if (a_wait_vrise_tmr != NULL) + kfree(a_wait_vrise_tmr); + if (a_wait_bcon_tmr != NULL) + kfree(a_wait_bcon_tmr); + if (a_aidl_bdis_tmr != NULL) + kfree(a_aidl_bdis_tmr); + if (b_ase0_brst_tmr != NULL) + kfree(b_ase0_brst_tmr); + if (b_se0_srp_tmr != NULL) + kfree(b_se0_srp_tmr); + if (b_srp_fail_tmr != NULL) + kfree(b_srp_fail_tmr); + if (a_wait_enum_tmr != NULL) + kfree(a_wait_enum_tmr); + + /* device driver used timers */ + if (b_srp_wait_tmr != NULL) + kfree(b_srp_wait_tmr); + if (b_data_pulse_tmr != NULL) + kfree(b_data_pulse_tmr); + if (b_vbus_pulse_tmr != NULL) + kfree(b_vbus_pulse_tmr); +} + +/* Add timer to timer list */ +void fsl_otg_add_timer(void *gtimer) +{ + struct fsl_otg_timer *timer = (struct fsl_otg_timer *)gtimer; + struct fsl_otg_timer *tmp_timer; + + /* Check if the timer is already in the active list, + * if so update timer count + */ + list_for_each_entry(tmp_timer, &active_timers, list) + if (tmp_timer == timer) { + timer->count = timer->expires; + return; + } + timer->count = timer->expires; + list_add_tail(&timer->list, &active_timers); +} + +/* Remove timer from the timer list; clear timeout status */ +void fsl_otg_del_timer(void *gtimer) +{ + struct fsl_otg_timer *timer = (struct fsl_otg_timer *)gtimer; + struct fsl_otg_timer *tmp_timer, *del_tmp; + + list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) + if (tmp_timer == timer) + list_del(&timer->list); +} + +/* Reduce timer count by 1, and find timeout conditions. + * Called by fsl_otg 1ms timer interrupt + */ +int fsl_otg_tick_timer(void) +{ + struct fsl_otg_timer *tmp_timer, *del_tmp; + int expired = 0; + + list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) { + tmp_timer->count--; + /* check if timer expires */ + if (!tmp_timer->count) { + list_del(&tmp_timer->list); + tmp_timer->function(tmp_timer->data); + expired = 1; + } + } + + return expired; +} + +/* Reset controller, not reset the bus */ +void otg_reset_controller(void) +{ + u32 command; + + command = readl(&usb_dr_regs->usbcmd); + command |= (1 << 1); + writel(command, &usb_dr_regs->usbcmd); + while (readl(&usb_dr_regs->usbcmd) & (1 << 1)) ; +} + +/* Call suspend/resume routines in host driver */ +int fsl_otg_start_host(struct otg_fsm *fsm, int on) +{ + struct otg_transceiver *xceiv = fsm->transceiver; + struct device *dev; + struct fsl_otg *otg_dev = container_of(xceiv, struct fsl_otg, otg); + u32 retval = 0; + + if (!xceiv->host) + return -ENODEV; + dev = xceiv->host->controller; + + /* Update a_vbus_vld state as a_vbus_vld int is disabled + * in device mode + */ + fsm->a_vbus_vld = + (le32_to_cpu(usb_dr_regs->otgsc) & OTGSC_STS_A_VBUS_VALID) ? 1 : 0; + if (on) { + /* start fsl usb host controller */ + if (otg_dev->host_working) + goto end; + else { + otg_reset_controller(); + VDBG("host on......\n"); + if (dev->driver->resume) { + retval = dev->driver->resume(dev); + if (fsm->id) { + /* default-b */ + fsl_otg_drv_vbus(1); + /* Workaround: b_host can't driver + * vbus, but PP in PORTSC needs to + * be 1 for host to work. + * So we set drv_vbus bit in + * transceiver to 0 thru ULPI. */ +#if defined(CONFIG_ISP1504_MXC) + write_ulpi(0x0c, 0x20); +#endif + } + } + + otg_dev->host_working = 1; + } + } else { + /* stop fsl usb host controller */ + if (!otg_dev->host_working) + goto end; + else { + VDBG("host off......\n"); + if (dev && dev->driver) { + retval = dev->driver->suspend(dev, + otg_suspend_state); + if (fsm->id) + /* default-b */ + fsl_otg_drv_vbus(0); + } + otg_dev->host_working = 0; + } + } +end: + return retval; +} + +/* Call suspend and resume function in udc driver + * to stop and start udc driver. + */ +int fsl_otg_start_gadget(struct otg_fsm *fsm, int on) +{ + struct otg_transceiver *xceiv = fsm->transceiver; + struct device *dev; + + if (!xceiv->gadget || !xceiv->gadget->dev.parent) + return -ENODEV; + + VDBG("gadget %s \n", on ? "on" : "off"); + dev = xceiv->gadget->dev.parent; + + if (on) + dev->driver->resume(dev); + else + dev->driver->suspend(dev, otg_suspend_state); + + return 0; +} + +/* Called by initialization code of host driver. Register host controller + * to the OTG. Suspend host for OTG role detection. + */ +static int fsl_otg_set_host(struct otg_transceiver *otg_p, struct usb_bus *host) +{ + struct fsl_otg *otg_dev = container_of(otg_p, struct fsl_otg, otg); + + if (!otg_p || otg_dev != fsl_otg_dev) + return -ENODEV; + + otg_p->host = host; + + otg_dev->fsm.a_bus_drop = 0; + otg_dev->fsm.a_bus_req = 1; + + if (host) { + VDBG("host off......\n"); + + otg_p->host->otg_port = fsl_otg_initdata.otg_port; + otg_p->host->is_b_host = otg_dev->fsm.id; + /* must leave time for khubd to finish its thing + * before yanking the host driver out from under it, + * so suspend the host after a short delay. + */ + otg_dev->host_working = 1; + schedule_delayed_work(&otg_dev->otg_event, 100); + return 0; + } else { /* host driver going away */ + + if (!(le32_to_cpu(otg_dev->dr_mem_map->otgsc) & + OTGSC_STS_USB_ID)) { + /* Mini-A cable connected */ + struct otg_fsm *fsm = &otg_dev->fsm; + + otg_p->state = OTG_STATE_UNDEFINED; + fsm->protocol = PROTO_UNDEF; + } + } + + otg_dev->host_working = 0; + + otg_statemachine(&otg_dev->fsm); + + return 0; +} + +/* Called by initialization code of udc. Register udc to OTG.*/ +static int fsl_otg_set_peripheral(struct otg_transceiver *otg_p, + struct usb_gadget *gadget) +{ + struct fsl_otg *otg_dev = container_of(otg_p, struct fsl_otg, otg); + + VDBG("otg_dev 0x%x\n", (int)otg_dev); + VDBG("fsl_otg_dev 0x%x\n", (int)fsl_otg_dev); + + if (!otg_p || otg_dev != fsl_otg_dev) + return -ENODEV; + + if (!gadget) { + if (!otg_dev->otg.default_a) + otg_p->gadget->ops->vbus_draw(otg_p->gadget, 0); + usb_gadget_vbus_disconnect(otg_dev->otg.gadget); + otg_dev->otg.gadget = 0; + otg_dev->fsm.b_bus_req = 0; + otg_statemachine(&otg_dev->fsm); + return 0; + } +#ifdef DEBUG + /* + * debug the initial state of the ID pin when only + * the gadget driver is loaded and no cable is connected. + * sometimes, we get an ID irq right + * after the udc driver's otg_get_transceiver() call + * that indicates that IDpin=0, which means a Mini-A + * connector is attached. not good. + */ + DBG("before: fsm.id ID pin=%d", otg_dev->fsm.id); + otg_dev->fsm.id = (otg_dev->dr_mem_map->otgsc & OTGSC_STS_USB_ID) ? + 1 : 0; + DBG("after: fsm.id ID pin=%d", otg_dev->fsm.id); + /*if (!otg_dev->fsm.id) { + printk("OTG Control = 0x%x\n", + isp1504_read(ISP1504_OTGCTL, + &otg_dev->dr_mem_map->ulpiview)); + } */ +#endif + + otg_p->gadget = gadget; + otg_p->gadget->is_a_peripheral = !otg_dev->fsm.id; + + otg_dev->fsm.b_bus_req = 1; + + /* start the gadget right away if the ID pin says Mini-B */ + DBG("ID pin=%d\n", otg_dev->fsm.id); + if (otg_dev->fsm.id == 1) { + fsl_otg_start_host(&otg_dev->fsm, 0); + otg_drv_vbus(&otg_dev->fsm, 0); + fsl_otg_start_gadget(&otg_dev->fsm, 1); + } + + return 0; +} + +/* Set OTG port power, only for B-device */ +static int fsl_otg_set_power(struct otg_transceiver *otg_p, unsigned mA) +{ + if (!fsl_otg_dev) + return -ENODEV; + if (otg_p->state == OTG_STATE_B_PERIPHERAL) + printk(KERN_INFO "FSL OTG:Draw %d mA\n", mA); + + return 0; +} + +/* Delayed pin detect interrupt processing. + * + * When the Mini-A cable is disconnected from the board, + * the pin-detect interrupt happens before the disconnnect + * interrupts for the connected device(s). In order to + * process the disconnect interrupt(s) prior to switching + * roles, the pin-detect interrupts are delayed, and handled + * by this routine. + */ +static void fsl_otg_event(struct work_struct *work) +{ + struct fsl_otg *og = container_of(work, struct fsl_otg, otg_event.work); + struct otg_fsm *fsm = &og->fsm; + + if (fsm->id) { /* switch to gadget */ + fsl_otg_start_host(fsm, 0); + otg_drv_vbus(fsm, 0); + fsl_otg_start_gadget(fsm, 1); + } +} + +/* B-device start SRP */ +static int fsl_otg_start_srp(struct otg_transceiver *otg_p) +{ + struct fsl_otg *otg_dev = container_of(otg_p, struct fsl_otg, otg); + + if (!otg_p || otg_dev != fsl_otg_dev + || otg_p->state != OTG_STATE_B_IDLE) + return -ENODEV; + + otg_dev->fsm.b_bus_req = 1; + otg_statemachine(&otg_dev->fsm); + + return 0; +} + +/* A_host suspend will call this function to start hnp */ +static int fsl_otg_start_hnp(struct otg_transceiver *otg_p) +{ + struct fsl_otg *otg_dev = container_of(otg_p, struct fsl_otg, otg); + + if (!otg_p || otg_dev != fsl_otg_dev) + return -ENODEV; + + /* printk("start_hnp.............\n"); */ + /* clear a_bus_req to enter a_suspend state */ + otg_dev->fsm.a_bus_req = 0; + otg_statemachine(&otg_dev->fsm); + + return 0; +} + +/* Interrupt handler. OTG/host/peripheral share the same int line. + * OTG driver clears OTGSC interrupts and leaves USB interrupts + * intact. It needs to have knowledge of some USB interrupts + * such as port change. + */ +irqreturn_t fsl_otg_isr(int irq, void *dev_id) +{ + struct otg_fsm *fsm = &((struct fsl_otg *)dev_id)->fsm; + struct otg_transceiver *otg = &((struct fsl_otg *)dev_id)->otg; + u32 otg_int_src, otg_sc; + + otg_sc = le32_to_cpu(usb_dr_regs->otgsc); + otg_int_src = otg_sc & OTGSC_INTSTS_MASK & (otg_sc >> 8); + + /* Only clear otg interrupts */ + usb_dr_regs->otgsc |= cpu_to_le32(otg_sc & OTGSC_INTSTS_MASK); + + /*FIXME: ID change not generate when init to 0 */ + fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; + otg->default_a = (fsm->id == 0); + + /* process OTG interrupts */ + if (otg_int_src) { + if (otg_int_src & OTGSC_INTSTS_USB_ID) { + fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; + otg->default_a = (fsm->id == 0); + /* clear conn information */ + if (fsm->id) + fsm->b_conn = 0; + else + fsm->a_conn = 0; + + if (otg->host) + otg->host->is_b_host = fsm->id; + if (otg->gadget) + otg->gadget->is_a_peripheral = !fsm->id; + VDBG("ID int (ID is %d)\n", fsm->id); + + if (fsm->id) { /* switch to gadget */ + schedule_delayed_work(&((struct fsl_otg *) + dev_id)->otg_event, + 100); + } else { /* switch to host */ + cancel_delayed_work(& + ((struct fsl_otg *)dev_id)-> + otg_event); + fsl_otg_start_gadget(fsm, 0); + otg_drv_vbus(fsm, 1); + fsl_otg_start_host(fsm, 1); + } + + return IRQ_HANDLED; + } + } + + return IRQ_NONE; +} + +static struct otg_fsm_ops fsl_otg_ops = { + .chrg_vbus = fsl_otg_chrg_vbus, + .drv_vbus = fsl_otg_drv_vbus, + .loc_conn = fsl_otg_loc_conn, + .loc_sof = fsl_otg_loc_sof, + .start_pulse = fsl_otg_start_pulse, + + .add_timer = fsl_otg_add_timer, + .del_timer = fsl_otg_del_timer, + + .start_host = fsl_otg_start_host, + .start_gadget = fsl_otg_start_gadget, +}; + +/* Initialize the global variable fsl_otg_dev and request IRQ for OTG */ +static int fsl_otg_conf(struct platform_device *pdev) +{ + int status; + struct fsl_otg *fsl_otg_tc; + struct fsl_usb2_platform_data *pdata; + + pdata = pdev->dev.platform_data; + + DBG(); + + if (fsl_otg_dev) + return 0; + + /* allocate space to fsl otg device */ + fsl_otg_tc = kzalloc(sizeof(struct fsl_otg), GFP_KERNEL); + if (!fsl_otg_tc) + return -ENODEV; + + INIT_DELAYED_WORK(&fsl_otg_tc->otg_event, fsl_otg_event); + + INIT_LIST_HEAD(&active_timers); + status = fsl_otg_init_timers(&fsl_otg_tc->fsm); + if (status) { + printk(KERN_INFO "Couldn't init OTG timers\n"); + fsl_otg_uninit_timers(); + kfree(fsl_otg_tc); + return status; + } + spin_lock_init(&fsl_otg_tc->fsm.lock); + + /* Set OTG state machine operations */ + fsl_otg_tc->fsm.ops = &fsl_otg_ops; + + /* initialize the otg structure */ + fsl_otg_tc->otg.label = DRIVER_DESC; + fsl_otg_tc->otg.set_host = fsl_otg_set_host; + fsl_otg_tc->otg.set_peripheral = fsl_otg_set_peripheral; + fsl_otg_tc->otg.set_power = fsl_otg_set_power; + fsl_otg_tc->otg.start_hnp = fsl_otg_start_hnp; + fsl_otg_tc->otg.start_srp = fsl_otg_start_srp; + + fsl_otg_dev = fsl_otg_tc; + + /* Store the otg transceiver */ + status = otg_set_transceiver(&fsl_otg_tc->otg); + if (status) { + printk(KERN_WARNING ": unable to register OTG transceiver.\n"); + return status; + } + + return 0; +} + +/* OTG Initialization*/ +int usb_otg_start(struct platform_device *pdev) +{ + struct fsl_otg *p_otg; + struct otg_transceiver *otg_trans = otg_get_transceiver(); + struct otg_fsm *fsm; + volatile unsigned long *p; + int status; + struct resource *res; + u32 temp; + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + p_otg = container_of(otg_trans, struct fsl_otg, otg); + fsm = &p_otg->fsm; + + /* Initialize the state machine structure with default values */ + SET_OTG_STATE(otg_trans, OTG_STATE_UNDEFINED); + fsm->transceiver = &p_otg->otg; + + /* We don't require predefined MEM/IRQ resource index */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + /* We don't request_mem_region here to enable resource sharing + * with host/device */ + + usb_dr_regs = ioremap(res->start, sizeof(struct usb_dr_mmap)); + p_otg->dr_mem_map = (struct usb_dr_mmap *)usb_dr_regs; + pdata->regs = (void *)usb_dr_regs; + + /* request irq */ + p_otg->irq = platform_get_irq(pdev, 0); + status = request_irq(p_otg->irq, fsl_otg_isr, + IRQF_SHARED, driver_name, p_otg); + if (status) { + dev_dbg(p_otg->otg.dev, "can't get IRQ %d, error %d\n", + p_otg->irq, status); + iounmap(p_otg->dr_mem_map); + kfree(p_otg); + return status; + } + + if (pdata->platform_init && pdata->platform_init(pdev) != 0) + return -EINVAL; + + + /* Export DR controller resources */ + otg_set_resources(pdev->resource); + + /* stop the controller */ + temp = readl(&p_otg->dr_mem_map->usbcmd); + temp &= ~USB_CMD_RUN_STOP; + writel(temp, &p_otg->dr_mem_map->usbcmd); + + /* reset the controller */ + temp = readl(&p_otg->dr_mem_map->usbcmd); + temp |= USB_CMD_CTRL_RESET; + writel(temp, &p_otg->dr_mem_map->usbcmd); + + /* wait reset completed */ + while (readl(&p_otg->dr_mem_map->usbcmd) & USB_CMD_CTRL_RESET) ; + + /* configure the VBUSHS as IDLE(both host and device) */ + temp = USB_MODE_STREAM_DISABLE | (pdata->es ? USB_MODE_ES : 0); + writel(temp, &p_otg->dr_mem_map->usbmode); + + /* configure PHY interface */ + temp = readl(&p_otg->dr_mem_map->portsc); + temp &= ~(PORTSC_PHY_TYPE_SEL | PORTSC_PTW); + switch (pdata->phy_mode) { + case FSL_USB2_PHY_ULPI: + temp |= PORTSC_PTS_ULPI; + break; + case FSL_USB2_PHY_UTMI_WIDE: + temp |= PORTSC_PTW_16BIT; + /* fall through */ + case FSL_USB2_PHY_UTMI: + temp |= PORTSC_PTS_UTMI; + /* fall through */ + default: + break; + } + writel(temp, &p_otg->dr_mem_map->portsc); + + if (pdata->have_sysif_regs) { + /* configure control enable IO output, big endian register */ + p = (volatile unsigned long *)(&p_otg->dr_mem_map->control); + temp = *p; + temp |= USB_CTRL_IOENB; + *p = temp; + } + + /* disable all interrupt and clear all OTGSC status */ + temp = readl(&p_otg->dr_mem_map->otgsc); + temp &= ~OTGSC_INTERRUPT_ENABLE_BITS_MASK; + temp |= OTGSC_INTERRUPT_STATUS_BITS_MASK | OTGSC_CTRL_VBUS_DISCHARGE; + writel(temp, &p_otg->dr_mem_map->otgsc); + + + /* + * The identification (id) input is FALSE when a Mini-A plug is inserted + * in the devices Mini-AB receptacle. Otherwise, this input is TRUE. + * Also: record initial state of ID pin + */ + if (le32_to_cpu(p_otg->dr_mem_map->otgsc) & OTGSC_STS_USB_ID) { + p_otg->otg.state = OTG_STATE_UNDEFINED; + p_otg->fsm.id = 1; + } else { + p_otg->otg.state = OTG_STATE_A_IDLE; + p_otg->fsm.id = 0; + } + + DBG("initial ID pin=%d\n", p_otg->fsm.id); + + /* enable OTG ID pin interrupt */ + temp = readl(&p_otg->dr_mem_map->otgsc); + temp |= OTGSC_INTR_USB_ID_EN; + temp &= ~(OTGSC_CTRL_VBUS_DISCHARGE | OTGSC_INTR_1MS_TIMER_EN); + writel(temp, &p_otg->dr_mem_map->otgsc); + + return 0; +} + +/*------------------------------------------------------------------------- + PROC File System Support +-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_OTG_DEBUG_FILES + +#include <linux/seq_file.h> + +static const char proc_filename[] = "driver/isp1504_otg"; + +static int otg_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *_dev) +{ + struct otg_fsm *fsm = &fsl_otg_dev->fsm; + char *buf = page; + char *next = buf; + unsigned size = count; + unsigned long flags; + int t; + u32 tmp_reg; + + if (off != 0) + return 0; + + spin_lock_irqsave(&fsm->lock, flags); + + /* ------basic driver infomation ---- */ + t = scnprintf(next, size, + DRIVER_DESC "\n" "fsl_usb2_otg version: %s\n\n", + DRIVER_VERSION); + size -= t; + next += t; + + /* ------ Registers ----- */ + tmp_reg = le32_to_cpu(usb_dr_regs->otgsc); + t = scnprintf(next, size, "OTGSC reg: %08x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->portsc); + t = scnprintf(next, size, "PORTSC reg: %08x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->usbmode); + t = scnprintf(next, size, "USBMODE reg: %08x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->usbcmd); + t = scnprintf(next, size, "USBCMD reg: %08x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->usbsts); + t = scnprintf(next, size, "USBSTS reg: %08x\n", tmp_reg); + size -= t; + next += t; + + /* ------ State ----- */ + t = scnprintf(next, size, + "OTG state: %s\n\n", + state_string(fsl_otg_dev->otg.state)); + size -= t; + next += t; + +#if 1 || defined DEBUG + /* ------ State Machine Variables ----- */ + t = scnprintf(next, size, "a_bus_req: %d\n", fsm->a_bus_req); + size -= t; + next += t; + + t = scnprintf(next, size, "b_bus_req: %d\n", fsm->b_bus_req); + size -= t; + next += t; + + t = scnprintf(next, size, "a_bus_resume: %d\n", fsm->a_bus_resume); + size -= t; + next += t; + + t = scnprintf(next, size, "a_bus_suspend: %d\n", fsm->a_bus_suspend); + size -= t; + next += t; + + t = scnprintf(next, size, "a_conn: %d\n", fsm->a_conn); + size -= t; + next += t; + + t = scnprintf(next, size, "a_sess_vld: %d\n", fsm->a_sess_vld); + size -= t; + next += t; + + t = scnprintf(next, size, "a_srp_det: %d\n", fsm->a_srp_det); + size -= t; + next += t; + + t = scnprintf(next, size, "a_vbus_vld: %d\n", fsm->a_vbus_vld); + size -= t; + next += t; + + t = scnprintf(next, size, "b_bus_resume: %d\n", fsm->b_bus_resume); + size -= t; + next += t; + + t = scnprintf(next, size, "b_bus_suspend: %d\n", fsm->b_bus_suspend); + size -= t; + next += t; + + t = scnprintf(next, size, "b_conn: %d\n", fsm->b_conn); + size -= t; + next += t; + + t = scnprintf(next, size, "b_se0_srp: %d\n", fsm->b_se0_srp); + size -= t; + next += t; + + t = scnprintf(next, size, "b_sess_end: %d\n", fsm->b_sess_end); + size -= t; + next += t; + + t = scnprintf(next, size, "b_sess_vld: %d\n", fsm->b_sess_vld); + size -= t; + next += t; + + t = scnprintf(next, size, "id: %d\n", fsm->id); + size -= t; + next += t; +#endif + + spin_unlock_irqrestore(&fsm->lock, flags); + + *eof = 1; + return count - size; +} + +#define create_proc_file() create_proc_read_entry(proc_filename, \ + 0, NULL, otg_proc_read, NULL) + +#define remove_proc_file() remove_proc_entry(proc_filename, NULL) + +#else /* !CONFIG_USB_OTG_DEBUG_FILES */ + +#define create_proc_file() do {} while (0) +#define remove_proc_file() do {} while (0) + +#endif /*CONFIG_USB_OTG_DEBUG_FILES */ + +/*----------------------------------------------------------*/ +/* Char driver interface to control some OTG input */ + +/* This function handle some ioctl command,such as get otg + * status and set host suspend + */ +static int fsl_otg_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + u32 retval = 0; + + switch (cmd) { + case GET_OTG_STATUS: + retval = fsl_otg_dev->host_working; + break; + + case SET_A_SUSPEND_REQ: + fsl_otg_dev->fsm.a_suspend_req = arg; + break; + + case SET_A_BUS_DROP: + fsl_otg_dev->fsm.a_bus_drop = arg; + break; + + case SET_A_BUS_REQ: + fsl_otg_dev->fsm.a_bus_req = arg; + break; + + case SET_B_BUS_REQ: + fsl_otg_dev->fsm.b_bus_req = arg; + break; + + default: + break; + } + + otg_statemachine(&fsl_otg_dev->fsm); + + return retval; +} + +static int fsl_otg_open(struct inode *inode, struct file *file) +{ + + return 0; +} + +static int fsl_otg_release(struct inode *inode, struct file *file) +{ + + return 0; +} + +static struct file_operations otg_fops = { + .owner = THIS_MODULE, + .llseek = NULL, + .read = NULL, + .write = NULL, + .ioctl = fsl_otg_ioctl, + .open = fsl_otg_open, + .release = fsl_otg_release, +}; + +static int __init fsl_otg_probe(struct platform_device *pdev) +{ + int status; + struct fsl_usb2_platform_data *pdata; + + DBG("pdev=0x%p\n", pdev); + + if (!pdev) + return -ENODEV; + + if (!pdev->dev.platform_data) + return -ENOMEM; + + pdata = pdev->dev.platform_data; + + /* configure the OTG */ + status = fsl_otg_conf(pdev); + if (status) { + printk(KERN_INFO "Couldn't init OTG module\n"); + return -status; + } + + /* start OTG */ + status = usb_otg_start(pdev); + + if (register_chrdev(FSL_OTG_MAJOR, FSL_OTG_NAME, &otg_fops)) { + printk(KERN_WARNING FSL_OTG_NAME + ": unable to register FSL OTG device\n"); + return -EIO; + } + + create_proc_file(); + return status; +} + +static int fsl_otg_remove(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + otg_set_transceiver(NULL); + free_irq(fsl_otg_dev->irq, fsl_otg_dev); + + iounmap((void *)usb_dr_regs); + + kfree(fsl_otg_dev); + + remove_proc_file(); + + unregister_chrdev(FSL_OTG_MAJOR, FSL_OTG_NAME); + + if (pdata->platform_uninit) + pdata->platform_uninit(pdata); + + return 0; +} + +struct platform_driver fsl_otg_driver = { + .probe = fsl_otg_probe, + .remove = fsl_otg_remove, + .driver = { + .name = driver_name, + .owner = THIS_MODULE, + }, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init fsl_usb_otg_init(void) +{ + printk(KERN_INFO DRIVER_DESC " loaded, %s\n", DRIVER_VERSION); + return platform_driver_register(&fsl_otg_driver); +} + +static void __exit fsl_usb_otg_exit(void) +{ + platform_driver_unregister(&fsl_otg_driver); + printk(KERN_INFO DRIVER_DESC " unloaded\n"); +} + +module_init(fsl_usb_otg_init); +module_exit(fsl_usb_otg_exit); + +MODULE_DESCRIPTION(DRIVER_INFO); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/otg/fsl_otg.h b/drivers/usb/otg/fsl_otg.h new file mode 100644 index 000000000000..03e232112b83 --- /dev/null +++ b/drivers/usb/otg/fsl_otg.h @@ -0,0 +1,413 @@ +/* Copyright 2005-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "otg_fsm.h" +#include <linux/usb/otg.h> +#include <linux/ioctl.h> + + /* USB Command Register Bit Masks */ +#define USB_CMD_RUN_STOP (0x1<<0 ) +#define USB_CMD_CTRL_RESET (0x1<<1 ) +#define USB_CMD_PERIODIC_SCHEDULE_EN (0x1<<4 ) +#define USB_CMD_ASYNC_SCHEDULE_EN (0x1<<5 ) +#define USB_CMD_INT_AA_DOORBELL (0x1<<6 ) +#define USB_CMD_ASP (0x3<<8 ) +#define USB_CMD_ASYNC_SCH_PARK_EN (0x1<<11 ) +#define USB_CMD_SUTW (0x1<<13 ) +#define USB_CMD_ATDTW (0x1<<14 ) +#define USB_CMD_ITC (0xFF<<16) + +/* bit 15,3,2 are frame list size */ +#define USB_CMD_FRAME_SIZE_1024 (0x0<<15 | 0x0<<2) +#define USB_CMD_FRAME_SIZE_512 (0x0<<15 | 0x1<<2) +#define USB_CMD_FRAME_SIZE_256 (0x0<<15 | 0x2<<2) +#define USB_CMD_FRAME_SIZE_128 (0x0<<15 | 0x3<<2) +#define USB_CMD_FRAME_SIZE_64 (0x1<<15 | 0x0<<2) +#define USB_CMD_FRAME_SIZE_32 (0x1<<15 | 0x1<<2) +#define USB_CMD_FRAME_SIZE_16 (0x1<<15 | 0x2<<2) +#define USB_CMD_FRAME_SIZE_8 (0x1<<15 | 0x3<<2) + +/* bit 9-8 are async schedule park mode count */ +#define USB_CMD_ASP_00 (0x0<<8) +#define USB_CMD_ASP_01 (0x1<<8) +#define USB_CMD_ASP_10 (0x2<<8) +#define USB_CMD_ASP_11 (0x3<<8) +#define USB_CMD_ASP_BIT_POS (8) + +/* bit 23-16 are interrupt threshold control */ +#define USB_CMD_ITC_NO_THRESHOLD (0x00<<16) +#define USB_CMD_ITC_1_MICRO_FRM (0x01<<16) +#define USB_CMD_ITC_2_MICRO_FRM (0x02<<16) +#define USB_CMD_ITC_4_MICRO_FRM (0x04<<16) +#define USB_CMD_ITC_8_MICRO_FRM (0x08<<16) +#define USB_CMD_ITC_16_MICRO_FRM (0x10<<16) +#define USB_CMD_ITC_32_MICRO_FRM (0x20<<16) +#define USB_CMD_ITC_64_MICRO_FRM (0x40<<16) +#define USB_CMD_ITC_BIT_POS (16) + +/* USB Status Register Bit Masks */ +#define USB_STS_INT (0x1<<0 ) +#define USB_STS_ERR (0x1<<1 ) +#define USB_STS_PORT_CHANGE (0x1<<2 ) +#define USB_STS_FRM_LST_ROLL (0x1<<3 ) +#define USB_STS_SYS_ERR (0x1<<4 ) +#define USB_STS_IAA (0x1<<5 ) +#define USB_STS_RESET_RECEIVED (0x1<<6 ) +#define USB_STS_SOF (0x1<<7 ) +#define USB_STS_DCSUSPEND (0x1<<8 ) +#define USB_STS_HC_HALTED (0x1<<12) +#define USB_STS_RCL (0x1<<13) +#define USB_STS_PERIODIC_SCHEDULE (0x1<<14) +#define USB_STS_ASYNC_SCHEDULE (0x1<<15) + +/* USB Interrupt Enable Register Bit Masks */ +#define USB_INTR_INT_EN (0x1<<0 ) +#define USB_INTR_ERR_INT_EN (0x1<<1 ) +#define USB_INTR_PC_DETECT_EN (0x1<<2 ) +#define USB_INTR_FRM_LST_ROLL_EN (0x1<<3 ) +#define USB_INTR_SYS_ERR_EN (0x1<<4 ) +#define USB_INTR_ASYN_ADV_EN (0x1<<5 ) +#define USB_INTR_RESET_EN (0x1<<6 ) +#define USB_INTR_SOF_EN (0x1<<7 ) +#define USB_INTR_DEVICE_SUSPEND (0x1<<8 ) + +/* Device Address bit masks */ +#define USB_DEVICE_ADDRESS_MASK (0x7F<<25) +#define USB_DEVICE_ADDRESS_BIT_POS (25) +/* PORTSC Register Bit Masks,Only one PORT in OTG mode*/ +#define PORTSC_CURRENT_CONNECT_STATUS (0x1<<0) +#define PORTSC_CONNECT_STATUS_CHANGE (0x1<<1) +#define PORTSC_PORT_ENABLE (0x1<<2) +#define PORTSC_PORT_EN_DIS_CHANGE (0x1<<3) +#define PORTSC_OVER_CURRENT_ACT (0x1<<4) +#define PORTSC_OVER_CUURENT_CHG (0x1<<5) +#define PORTSC_PORT_FORCE_RESUME (0x1<<6) +#define PORTSC_PORT_SUSPEND (0x1<<7) +#define PORTSC_PORT_RESET (0x1<<8) +#define PORTSC_LINE_STATUS_BITS (0x3<<10) +#define PORTSC_PORT_POWER (0x1<<12) +#define PORTSC_PORT_INDICTOR_CTRL (0x3<<14) +#define PORTSC_PORT_TEST_CTRL (0xF<<16) +#define PORTSC_WAKE_ON_CONNECT_EN (0x1<<20) +#define PORTSC_WAKE_ON_CONNECT_DIS (0x1<<21) +#define PORTSC_WAKE_ON_OVER_CURRENT (0x1<<22) +#define PORTSC_PHY_LOW_POWER_SPD (0x1<<23) +#define PORTSC_PORT_FORCE_FULL_SPEED (0x1<<24) +#define PORTSC_PORT_SPEED_MASK (0x3<<26) +#define PORTSC_TRANSCEIVER_WIDTH (0x1<<28) +#define PORTSC_PHY_TYPE_SEL (0x3<<30) +/* bit 11-10 are line status */ +#define PORTSC_LINE_STATUS_SE0 (0x0<<10) +#define PORTSC_LINE_STATUS_JSTATE (0x1<<10) +#define PORTSC_LINE_STATUS_KSTATE (0x2<<10) +#define PORTSC_LINE_STATUS_UNDEF (0x3<<10) +#define PORTSC_LINE_STATUS_BIT_POS (10) + +/* bit 15-14 are port indicator control */ +#define PORTSC_PIC_OFF (0x0<<14) +#define PORTSC_PIC_AMBER (0x1<<14) +#define PORTSC_PIC_GREEN (0x2<<14) +#define PORTSC_PIC_UNDEF (0x3<<14) +#define PORTSC_PIC_BIT_POS (14) + +/* bit 19-16 are port test control */ +#define PORTSC_PTC_DISABLE (0x0<<16) +#define PORTSC_PTC_JSTATE (0x1<<16) +#define PORTSC_PTC_KSTATE (0x2<<16) +#define PORTSC_PTC_SEQNAK (0x3<<16) +#define PORTSC_PTC_PACKET (0x4<<16) +#define PORTSC_PTC_FORCE_EN (0x5<<16) +#define PORTSC_PTC_BIT_POS (16) + +/* bit 27-26 are port speed */ +#define PORTSC_PORT_SPEED_FULL (0x0<<26) +#define PORTSC_PORT_SPEED_LOW (0x1<<26) +#define PORTSC_PORT_SPEED_HIGH (0x2<<26) +#define PORTSC_PORT_SPEED_UNDEF (0x3<<26) +#define PORTSC_SPEED_BIT_POS (26) + +/* bit 28 is parallel transceiver width for UTMI interface */ +#define PORTSC_PTW (0x1<<28) +#define PORTSC_PTW_8BIT (0x0<<28) +#define PORTSC_PTW_16BIT (0x1<<28) + +/* bit 31-30 are port transceiver select */ +#define PORTSC_PTS_UTMI (0x0<<30) +#define PORTSC_PTS_ULPI (0x2<<30) +#define PORTSC_PTS_FSLS_SERIAL (0x3<<30) +#define PORTSC_PTS_BIT_POS (30) + +#define PORTSC_W1C_BITS \ + (PORTSC_CONNECT_STATUS_CHANGE | \ + PORTSC_PORT_EN_DIS_CHANGE | \ + PORTSC_OVER_CUURENT_CHG) + +/* OTG Status Control Register Bit Masks */ +#define OTGSC_CTRL_VBUS_DISCHARGE (0x1<<0) +#define OTGSC_CTRL_VBUS_CHARGE (0x1<<1) +#define OTGSC_CTRL_OTG_TERMINATION (0x1<<3) +#define OTGSC_CTRL_DATA_PULSING (0x1<<4) +#define OTGSC_CTRL_ID_PULL_EN (0x1<<5) +#define OTGSC_HA_DATA_PULSE (0x1<<6) +#define OTGSC_HA_BA (0x1<<7) +#define OTGSC_STS_USB_ID (0x1<<8) +#define OTGSC_STS_A_VBUS_VALID (0x1<<9) +#define OTGSC_STS_A_SESSION_VALID (0x1<<10) +#define OTGSC_STS_B_SESSION_VALID (0x1<<11) +#define OTGSC_STS_B_SESSION_END (0x1<<12) +#define OTGSC_STS_1MS_TOGGLE (0x1<<13) +#define OTGSC_STS_DATA_PULSING (0x1<<14) +#define OTGSC_INTSTS_USB_ID (0x1<<16) +#define OTGSC_INTSTS_A_VBUS_VALID (0x1<<17) +#define OTGSC_INTSTS_A_SESSION_VALID (0x1<<18) +#define OTGSC_INTSTS_B_SESSION_VALID (0x1<<19) +#define OTGSC_INTSTS_B_SESSION_END (0x1<<20) +#define OTGSC_INTSTS_1MS (0x1<<21) +#define OTGSC_INTSTS_DATA_PULSING (0x1<<22) +#define OTGSC_INTR_USB_ID_EN (0x1<<24) +#define OTGSC_INTR_A_VBUS_VALID_EN (0x1<<25) +#define OTGSC_INTR_A_SESSION_VALID_EN (0x1<<26) +#define OTGSC_INTR_B_SESSION_VALID_EN (0x1<<27) +#define OTGSC_INTR_B_SESSION_END_EN (0x1<<28) +#define OTGSC_INTR_1MS_TIMER_EN (0x1<<29) +#define OTGSC_INTR_DATA_PULSING_EN (0x1<<30) +#define OTGSC_INTSTS_MASK (0x00ff0000) + +/* USB MODE Register Bit Masks */ +#define USB_MODE_CTRL_MODE_IDLE (0x0<<0) +#define USB_MODE_CTRL_MODE_DEVICE (0x2<<0) +#define USB_MODE_CTRL_MODE_HOST (0x3<<0) +#define USB_MODE_CTRL_MODE_RSV (0x1<<0) +#define USB_MODE_SETUP_LOCK_OFF (0x1<<3) +#define USB_MODE_STREAM_DISABLE (0x1<<4) +#define USB_MODE_ES (0x1<<2) /* (big) Endian Select */ + +#define MPC8349_OTG_IRQ (38) +#define CFG_IMMR_BASE (0xfe000000) +#define MPC83xx_USB_DR_BASE (CFG_IMMR_BASE + 0x23000) + +/* control Register Bit Masks */ +#define USB_CTRL_IOENB (0x1<<2) +#define USB_CTRL_ULPI_INT0EN (0x1<<0) + +/* BCSR5 */ +#define BCSR5_INT_USB (0x02) + +/* USB module clk cfg */ +#define SCCR_OFFS (0xA08) +#define SCCR_USB_CLK_DISABLE (0x00000000) /* USB clk disable */ +#define SCCR_USB_MPHCM_11 (0x00c00000) +#define SCCR_USB_MPHCM_01 (0x00400000) +#define SCCR_USB_MPHCM_10 (0x00800000) +#define SCCR_USB_DRCM_11 (0x00300000) +#define SCCR_USB_DRCM_01 (0x00100000) +#define SCCR_USB_DRCM_10 (0x00200000) + +#define SICRL_OFFS (0x114) +#define SICRL_USB0 (0x40000000) +#define SICRL_USB1 (0x20000000) + +#define SICRH_OFFS (0x118) +#define SICRH_USB_UTMI (0x00020000) + +/* OTG interrupt enable bit masks */ +#define OTGSC_INTERRUPT_ENABLE_BITS_MASK \ + (OTGSC_INTR_USB_ID_EN | \ + OTGSC_INTR_1MS_TIMER_EN | \ + OTGSC_INTR_A_VBUS_VALID_EN | \ + OTGSC_INTR_A_SESSION_VALID_EN | \ + OTGSC_INTR_B_SESSION_VALID_EN | \ + OTGSC_INTR_B_SESSION_END_EN | \ + OTGSC_INTR_DATA_PULSING_EN) + +/* OTG interrupt status bit masks */ +#define OTGSC_INTERRUPT_STATUS_BITS_MASK \ + (OTGSC_INTSTS_USB_ID | \ + OTGSC_INTR_1MS_TIMER_EN | \ + OTGSC_INTSTS_A_VBUS_VALID | \ + OTGSC_INTSTS_A_SESSION_VALID | \ + OTGSC_INTSTS_B_SESSION_VALID | \ + OTGSC_INTSTS_B_SESSION_END | \ + OTGSC_INTSTS_DATA_PULSING) + +/* + * A-DEVICE timing constants + */ + +/* Wait for VBUS Rise */ +#define TA_WAIT_VRISE (100) /* a_wait_vrise 100 ms, section: 6.6.5.1 */ + +/* Wait for B-Connect */ +#define TA_WAIT_BCON (10000) /* a_wait_bcon > 1 sec, section: 6.6.5.2 + * This is only used to get out of + * OTG_STATE_A_WAIT_BCON state if there was + * no connection for these many milliseconds + */ + +/* A-Idle to B-Disconnect */ +/* It is necessary for this timer to be more than 750 ms because of a bug in OPT + * test 5.4 in which B OPT disconnects after 750 ms instead of 75ms as stated + * in the test description + */ +#define TA_AIDL_BDIS (5000) /* a_suspend minimum 200 ms, section: 6.6.5.3 */ + +/* B-Idle to A-Disconnect */ +#define TA_BIDL_ADIS (12) /* 3 to 200 ms */ + +/* B-device timing constants */ + + +/* Data-Line Pulse Time*/ +#define TB_DATA_PLS (10) /* b_srp_init,continue 5~10ms, section:5.3.3 */ +#define TB_DATA_PLS_MIN (5) /* minimum 5 ms */ +#define TB_DATA_PLS_MAX (10) /* maximum 10 ms */ + +/* SRP Initiate Time */ +#define TB_SRP_INIT (100) /* b_srp_init,maximum 100 ms, section:5.3.8 */ + +/* SRP Fail Time */ +#define TB_SRP_FAIL (7000) /* b_srp_init,Fail time 5~30s, section:6.8.2.2*/ + +/* SRP result wait time */ +#define TB_SRP_WAIT (60) + +/* VBus time */ +#define TB_VBUS_PLS (30) /* time to keep vbus pulsing asserted */ + +/* Discharge time */ +/* This time should be less than 10ms. It varies from system to system. */ +#define TB_VBUS_DSCHRG (8) + +/* A-SE0 to B-Reset */ +#define TB_ASE0_BRST (20) /* b_wait_acon, mini 3.125 ms,section:6.8.2.4 */ + +/* A bus suspend timer before we can switch to b_wait_aconn */ +#define TB_A_SUSPEND (7) +#define TB_BUS_RESUME (12) + +/* SE0 Time Before SRP */ +#define TB_SE0_SRP (2) /* b_idle,minimum 2 ms, section:5.3.2 */ + + +#define SET_OTG_STATE(otg_ptr, newstate) ((otg_ptr)->state=newstate) + +struct usb_dr_mmap { + /* Capability register */ + u8 res1[256]; + u16 caplength; /* Capability Register Length */ + u16 hciversion; /* Host Controller Interface Version */ + u32 hcsparams; /* Host Controller Structual Parameters */ + u32 hccparams; /* Host Controller Capability Parameters */ + u8 res2[20]; + u32 dciversion; /* Device Controller Interface Version */ + u32 dccparams; /* Device Controller Capability Parameters */ + u8 res3[24]; + /* Operation register */ + u32 usbcmd; /* USB Command Register */ + u32 usbsts; /* USB Status Register */ + u32 usbintr; /* USB Interrupt Enable Register */ + u32 frindex; /* Frame Index Register */ + u8 res4[4]; + u32 deviceaddr; /* Device Address */ + u32 endpointlistaddr; /* Endpoint List Address Register */ + u8 res5[4]; + u32 burstsize; /* Master Interface Data Burst Size Register */ + u32 txttfilltuning; /* Transmit FIFO Tuning Controls Register */ + u8 res6[8]; + u32 ulpiview; /* ULPI register access */ + u8 res7[12]; + u32 configflag; /* Configure Flag Register */ + u32 portsc; /* Port 1 Status and Control Register */ + u8 res8[28]; + u32 otgsc; /* On-The-Go Status and Control */ + u32 usbmode; /* USB Mode Register */ + u32 endptsetupstat; /* Endpoint Setup Status Register */ + u32 endpointprime; /* Endpoint Initialization Register */ + u32 endptflush; /* Endpoint Flush Register */ + u32 endptstatus; /* Endpoint Status Register */ + u32 endptcomplete; /* Endpoint Complete Register */ + u32 endptctrl[6]; /* Endpoint Control Registers */ + u8 res9[552]; + u32 snoop1; + u32 snoop2; + u32 age_cnt_thresh; /* Age Count Threshold Register */ + u32 pri_ctrl; /* Priority Control Register */ + u32 si_ctrl; /* System Interface Control Register */ + u8 res10[236]; +#ifdef CONFIG_ARCH_MX51 + u32 res11[128]; +#endif + u32 control; /* General Purpose Control Register */ +}; + + +struct fsl_otg_timer { + unsigned long expires; /* Number of count increase to timeout */ + unsigned long count; /* Tick counter */ + void (*function)(unsigned long); /* Timeout function */ + unsigned long data; /* Data passed to function */ + struct list_head list; +}; + +struct fsl_otg_timer inline *otg_timer_initializer +(void (*function)(unsigned long), unsigned long expires, unsigned long data) +{ + struct fsl_otg_timer *timer; + timer = kmalloc(sizeof(struct fsl_otg_timer), GFP_KERNEL); + if (timer == NULL) + return NULL; + timer->function = function; + timer->expires = expires; + timer->data = data; + return timer; +} + +struct fsl_otg { + struct otg_transceiver otg; + struct otg_fsm fsm; + struct usb_dr_mmap *dr_mem_map; + struct delayed_work otg_event; + + /*used for usb host */ + struct work_struct work_wq; + u8 host_working; + + int irq; +}; + +struct fsl_otg_config { + u8 otg_port; +}; + +/*For SRP and HNP handle*/ +#define FSL_OTG_MAJOR 66 +#define FSL_OTG_NAME "fsl-usb2-otg" +/*Command to OTG driver(ioctl)*/ +#define OTG_IOCTL_MAGIC FSL_OTG_MAJOR +/*if otg work as host,it should return 1,otherwise it return 0*/ +#define GET_OTG_STATUS _IOR(OTG_IOCTL_MAGIC, 1, int) +#define SET_A_SUSPEND_REQ _IOW(OTG_IOCTL_MAGIC, 2, int) +#define SET_A_BUS_DROP _IOW(OTG_IOCTL_MAGIC, 3, int) +#define SET_A_BUS_REQ _IOW(OTG_IOCTL_MAGIC, 4, int) +#define SET_B_BUS_REQ _IOW(OTG_IOCTL_MAGIC, 5, int) +#define GET_A_SUSPEND_REQ _IOR(OTG_IOCTL_MAGIC, 6, int) +#define GET_A_BUS_DROP _IOR(OTG_IOCTL_MAGIC, 7, int) +#define GET_A_BUS_REQ _IOR(OTG_IOCTL_MAGIC, 8, int) +#define GET_B_BUS_REQ _IOR(OTG_IOCTL_MAGIC, 9, int) + +extern const char *state_string(enum usb_otg_state state); +extern int otg_set_resources(struct resource *resources); diff --git a/drivers/usb/otg/otg_fsm.c b/drivers/usb/otg/otg_fsm.c new file mode 100644 index 000000000000..955b21cdd609 --- /dev/null +++ b/drivers/usb/otg/otg_fsm.c @@ -0,0 +1,371 @@ +/* OTG Finite State Machine from OTG spec + * + * Copyright (C) 2006-2008 Freescale Semiconductor, Inc. + * + * Author: Li Yang <LeoLi@freescale.com> + * Jerry Huang <Chang-Ming.Huang@freescale.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/usb/otg.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/usb.h> +#include <linux/usb/gadget.h> + +#include <asm/types.h> +#include "otg_fsm.h" + + +/* Defined by device specific driver, for different timer implementation */ +extern void *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, + *b_ase0_brst_tmr, *b_se0_srp_tmr, *b_srp_fail_tmr, *a_wait_enum_tmr; + +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"; + } +} + +/* Change USB protocol when there is a protocol change */ +static int otg_set_protocol(struct otg_fsm *fsm, int protocol) +{ + int ret = 0; + + if (fsm->protocol != protocol) { + VDBG("Changing role fsm->protocol= %d; new protocol= %d\n", + fsm->protocol, protocol); + /* stop old protocol */ + if (fsm->protocol == PROTO_HOST) + ret = fsm->ops->start_host(fsm, 0); + else if (fsm->protocol == PROTO_GADGET) + ret = fsm->ops->start_gadget(fsm, 0); + if (ret) + return ret; + + /* start new protocol */ + if (protocol == PROTO_HOST) + ret = fsm->ops->start_host(fsm, 1); + else if (protocol == PROTO_GADGET) + ret = fsm->ops->start_gadget(fsm, 1); + if (ret) + return ret; + + fsm->protocol = protocol; + return 0; + } + + return 0; +} + +static int state_changed; + +/* Called when leaving a state. Do state clean up jobs here */ +void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state) +{ + switch (old_state) { + case OTG_STATE_B_IDLE: + otg_del_timer(fsm, b_se0_srp_tmr); + fsm->b_se0_srp = 0; + break; + case OTG_STATE_B_SRP_INIT: + fsm->b_srp_done = 0; + break; + case OTG_STATE_B_PERIPHERAL: + break; + case OTG_STATE_B_WAIT_ACON: + otg_del_timer(fsm, b_ase0_brst_tmr); + fsm->b_ase0_brst_tmout = 0; + break; + case OTG_STATE_B_HOST: + break; + case OTG_STATE_A_IDLE: + break; + case OTG_STATE_A_WAIT_VRISE: + otg_del_timer(fsm, a_wait_vrise_tmr); + fsm->a_wait_vrise_tmout = 0; + break; + case OTG_STATE_A_WAIT_BCON: + otg_del_timer(fsm, a_wait_bcon_tmr); + fsm->a_wait_bcon_tmout = 0; + break; + case OTG_STATE_A_HOST: + otg_del_timer(fsm, a_wait_enum_tmr); + break; + case OTG_STATE_A_SUSPEND: + otg_del_timer(fsm, a_aidl_bdis_tmr); + fsm->a_aidl_bdis_tmout = 0; + fsm->a_suspend_req = 0; + break; + case OTG_STATE_A_PERIPHERAL: + break; + case OTG_STATE_A_WAIT_VFALL: + otg_del_timer(fsm, a_wait_vrise_tmr); + break; + case OTG_STATE_A_VBUS_ERR: + break; + default: + break; + } +} + +/* Called when entering a state */ +int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state) +{ + state_changed = 1; + if (fsm->transceiver->state == new_state) + return 0; + VDBG("Set state: %s \n", state_string(new_state)); + otg_leave_state(fsm, fsm->transceiver->state); + switch (new_state) { + case OTG_STATE_B_IDLE: + otg_drv_vbus(fsm, 0); + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_UNDEF); + otg_add_timer(fsm, b_se0_srp_tmr); + break; + case OTG_STATE_B_SRP_INIT: + otg_start_pulse(fsm); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_UNDEF); + otg_add_timer(fsm, b_srp_fail_tmr); + break; + case OTG_STATE_B_PERIPHERAL: + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 1); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_GADGET); + break; + case OTG_STATE_B_WAIT_ACON: + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, b_ase0_brst_tmr); + fsm->a_bus_suspend = 0; + break; + case OTG_STATE_B_HOST: + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 1); + otg_set_protocol(fsm, PROTO_HOST); + usb_bus_start_enum(fsm->transceiver->host, + fsm->transceiver->host->otg_port); + break; + case OTG_STATE_A_IDLE: + otg_drv_vbus(fsm, 0); + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + break; + case OTG_STATE_A_WAIT_VRISE: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, a_wait_vrise_tmr); + break; + case OTG_STATE_A_WAIT_BCON: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, a_wait_bcon_tmr); + break; + case OTG_STATE_A_HOST: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 1); + otg_set_protocol(fsm, PROTO_HOST); + /* When HNP is triggered while a_bus_req = 0, a_host will + * suspend too fast to complete a_set_b_hnp_en */ + if (!fsm->a_bus_req || fsm->a_suspend_req) + otg_add_timer(fsm, a_wait_enum_tmr); + break; + case OTG_STATE_A_SUSPEND: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, a_aidl_bdis_tmr); + + break; + case OTG_STATE_A_PERIPHERAL: + otg_loc_conn(fsm, 1); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_GADGET); + otg_drv_vbus(fsm, 1); + break; + case OTG_STATE_A_WAIT_VFALL: + otg_drv_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + break; + case OTG_STATE_A_VBUS_ERR: + otg_drv_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_UNDEF); + break; + default: + break; + } + + fsm->transceiver->state = new_state; + return 0; +} + +/* State change judgement */ +int otg_statemachine(struct otg_fsm *fsm) +{ + enum usb_otg_state state; + unsigned long flags; + + spin_lock_irqsave(&fsm->lock, flags); + + state = fsm->transceiver->state; + state_changed = 0; + /* State machine state change judgement */ + + switch (state) { + case OTG_STATE_UNDEFINED: + VDBG("fsm->id = %d \n", fsm->id); + if (fsm->id) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else + otg_set_state(fsm, OTG_STATE_A_IDLE); + break; + case OTG_STATE_B_IDLE: + if (!fsm->id) + otg_set_state(fsm, OTG_STATE_A_IDLE); + else if (fsm->b_sess_vld && fsm->transceiver->gadget) + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + else if (fsm->b_bus_req && fsm->b_sess_end && fsm->b_se0_srp) + otg_set_state(fsm, OTG_STATE_B_SRP_INIT); + break; + case OTG_STATE_B_SRP_INIT: + if (!fsm->id || fsm->b_srp_done) + otg_set_state(fsm, OTG_STATE_B_IDLE); + break; + case OTG_STATE_B_PERIPHERAL: + if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (fsm->b_bus_req && fsm->transceiver-> + gadget->b_hnp_enable && fsm->a_bus_suspend) + otg_set_state(fsm, OTG_STATE_B_WAIT_ACON); + break; + case OTG_STATE_B_WAIT_ACON: + if (fsm->a_conn) + otg_set_state(fsm, OTG_STATE_B_HOST); + else if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (fsm->a_bus_resume || fsm->b_ase0_brst_tmout) { + fsm->b_ase0_brst_tmout = 0; + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + } + break; + case OTG_STATE_B_HOST: + if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (!fsm->b_bus_req || !fsm->a_conn) + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + break; + case OTG_STATE_A_IDLE: + if (fsm->id) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (!fsm->a_bus_drop && (fsm->a_bus_req || fsm->a_srp_det)) + otg_set_state(fsm, OTG_STATE_A_WAIT_VRISE); + break; + case OTG_STATE_A_WAIT_VRISE: + if (fsm->id || fsm->a_bus_drop || fsm->a_vbus_vld || + fsm->a_wait_vrise_tmout) { + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + } + break; + case OTG_STATE_A_WAIT_BCON: + if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + else if (fsm->b_conn) + otg_set_state(fsm, OTG_STATE_A_HOST); + else if (fsm->id | fsm->a_bus_drop | fsm->a_wait_bcon_tmout) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + break; + case OTG_STATE_A_HOST: + if ((!fsm->a_bus_req || fsm->a_suspend_req) && + fsm->transceiver->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_SUSPEND); + else if (fsm->id || !fsm->b_conn || fsm->a_bus_drop) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_SUSPEND: + if (!fsm->b_conn && fsm->transceiver->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_PERIPHERAL); + else if (!fsm->b_conn && !fsm->transceiver->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (fsm->a_bus_req || fsm->b_bus_resume) + otg_set_state(fsm, OTG_STATE_A_HOST); + else if (fsm->id || fsm->a_bus_drop || fsm->a_aidl_bdis_tmout) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_PERIPHERAL: + if (fsm->id || fsm->a_bus_drop) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + else if (fsm->b_bus_suspend) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_WAIT_VFALL: + if (fsm->id || fsm->a_bus_req || (!fsm->a_sess_vld && + !fsm->b_conn)) + otg_set_state(fsm, OTG_STATE_A_IDLE); + break; + case OTG_STATE_A_VBUS_ERR: + if (fsm->id || fsm->a_bus_drop || fsm->a_clr_err) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + break; + default: + break; + } + spin_unlock_irqrestore(&fsm->lock, flags); + + /* VDBG("quit statemachine, changed = %d \n", state_changed); */ + return state_changed; +} diff --git a/drivers/usb/otg/otg_fsm.h b/drivers/usb/otg/otg_fsm.h new file mode 100644 index 000000000000..cd6894c80403 --- /dev/null +++ b/drivers/usb/otg/otg_fsm.h @@ -0,0 +1,151 @@ +/* Copyright (C) 2006-2008 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if 0 +#define DEBUG 1 +#define VERBOSE 1 +#endif + +#ifdef DEBUG +#define DBG(fmt, args...) printk(KERN_DEBUG "j=%lu [%s] " fmt "\n", jiffies, \ + __func__, ## args) +#else +#define DBG(fmt, args...) do {} while (0) +#endif + +#ifdef VERBOSE +#define VDBG DBG +#else +#define VDBG(stuff...) do {} while (0) +#endif + +#ifdef VERBOSE +#define MPC_LOC printk("Current Location [%s]:[%d]\n", __FILE__, __LINE__) +#else +#define MPC_LOC do {} while (0) +#endif + +#define PROTO_UNDEF (0) +#define PROTO_HOST (1) +#define PROTO_GADGET (2) + +/* OTG state machine according to the OTG spec */ +struct otg_fsm { + /* Input */ + int a_bus_resume; + int a_bus_suspend; + int a_conn; + int a_sess_vld; + int a_srp_det; + int a_vbus_vld; + int b_bus_resume; + int b_bus_suspend; + int b_conn; + int b_se0_srp; + int b_sess_end; + int b_sess_vld; + int id; + + /* Internal variables */ + int a_set_b_hnp_en; + int b_srp_done; + int b_hnp_enable; + + /* Timeout indicator for timers */ + int a_wait_vrise_tmout; + int a_wait_bcon_tmout; + int a_aidl_bdis_tmout; + int b_ase0_brst_tmout; + + /* Informative variables */ + int a_bus_drop; + int a_bus_req; + int a_clr_err; + int a_suspend_req; + int b_bus_req; + + /* Output */ + int drv_vbus; + int loc_conn; + int loc_sof; + + struct otg_fsm_ops *ops; + struct otg_transceiver *transceiver; + + /* Current usb protocol used: 0:undefine; 1:host; 2:client */ + int protocol; + spinlock_t lock; +}; + +struct otg_fsm_ops { + void (*chrg_vbus)(int on); + void (*drv_vbus)(int on); + void (*loc_conn)(int on); + void (*loc_sof)(int on); + void (*start_pulse)(void); + void (*add_timer)(void *timer); + void (*del_timer)(void *timer); + int (*start_host)(struct otg_fsm *fsm, int on); + int (*start_gadget)(struct otg_fsm *fsm, int on); +}; + + +static inline void otg_chrg_vbus(struct otg_fsm *fsm, int on) +{ + fsm->ops->chrg_vbus(on); +} + +static inline void otg_drv_vbus(struct otg_fsm *fsm, int on) +{ + if (fsm->drv_vbus != on) { + fsm->drv_vbus = on; + fsm->ops->drv_vbus(on); + } +} + +static inline void otg_loc_conn(struct otg_fsm *fsm, int on) +{ + if (fsm->loc_conn != on) { + fsm->loc_conn = on; + fsm->ops->loc_conn(on); + } +} + +static inline void otg_loc_sof(struct otg_fsm *fsm, int on) +{ + if (fsm->loc_sof != on) { + fsm->loc_sof = on; + fsm->ops->loc_sof(on); + } +} + +static inline void otg_start_pulse(struct otg_fsm *fsm) +{ + fsm->ops->start_pulse(); +} + +static inline void otg_add_timer(struct otg_fsm *fsm, void *timer) +{ + fsm->ops->add_timer(timer); +} + +static inline void otg_del_timer(struct otg_fsm *fsm, void *timer) +{ + fsm->ops->del_timer(timer); +} + +int otg_statemachine(struct otg_fsm *fsm); diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 3b54b3940178..53de81d71ed2 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -381,6 +381,10 @@ config FB_CLPS711X Say Y to enable the Framebuffer driver for the CLPS7111 and EP7212 processors. +if ARCH_MXC +source "drivers/video/mxc/Kconfig" +endif + config FB_SA1100 bool "SA-1100 LCD support" depends on (FB = y) && ARM && ARCH_SA1100 @@ -1978,6 +1982,16 @@ config FB_PNX4008_DUM_RGB ---help--- Say Y here to enable support for PNX4008 RGB Framebuffer +config FB_STMP37XX + tristate "STMP 37XX LCD Framebuffer driver" + depends on FB && (ARCH_STMP37XX || ARCH_STMP378X) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Say Y here to enable support for the framebuffer driver for the + Sigmatel STMP37XX board. + config FB_IBM_GXT4500 tristate "Framebuffer support for IBM GXT4500P adaptor" depends on FB && PPC diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 01a819f47371..e56f7b139957 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -117,6 +117,8 @@ obj-$(CONFIG_FB_FSL_DIU) += fsl-diu-fb.o obj-$(CONFIG_FB_COBALT) += cobalt_lcdfb.o obj-$(CONFIG_FB_PNX4008_DUM) += pnx4008/ obj-$(CONFIG_FB_PNX4008_DUM_RGB) += pnx4008/ +obj-$(CONFIG_FB_MXC) += mxc/ +obj-$(CONFIG_FB_STMP37XX) += stmp37xxfb.o obj-$(CONFIG_FB_IBM_GXT4500) += gxt4500.o obj-$(CONFIG_FB_PS3) += ps3fb.o obj-$(CONFIG_FB_SM501) += sm501fb.o diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index f9d19be05540..0da9bbbe6c92 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -229,3 +229,44 @@ config BACKLIGHT_SAHARA help If you have a Tabletkiosk Sahara Touch-iT, say y to enable the backlight driver. + +menuconfig BACKLIGHT_MXC + bool "Freescale MXC/i.MX Backlight Drivers" + depends on BACKLIGHT_CLASS_DEVICE && ARCH_MXC + default y + help + If you have a Freescale MC13783 PMIC, say y to enable the + backlight driver. + +config BACKLIGHT_MXC_IPU + tristate "IPU PWM Backlight Driver" + depends on BACKLIGHT_MXC && MXC_IPU_V1 + default y + +config BACKLIGHT_MXC_LCDC + tristate "LCDC PWM Backlight Driver" + depends on BACKLIGHT_MXC && (ARCH_MX21 || ARCH_MX27 || ARCH_MX25) + default y + +config BACKLIGHT_MXC_PMIC + tristate "PMIC Backlight Driver" + depends on BACKLIGHT_MXC && MXC_MC13783_LIGHT && MXC_MC13783_POWER + default y + +config BACKLIGHT_MXC_MC13892 + tristate "Mc13892 Backlight Driver" + depends on BACKLIGHT_MXC && MXC_MC13892_LIGHT + default y + +config BACKLIGHT_STMP37XX + tristate "SigmaTel STMP37xx Backlight Driver" + depends on BACKLIGHT_CLASS_DEVICE && (ARCH_STMP37XX || ARCH_STMP378X) + default y + help + If you have a STMP37xx, say y to enable the + backlight driver. + +config BACKLIGHT_WM8350 + tristate "WM8350 Backlight Driver" + depends on BACKLIGHT_MXC && REGULATOR_WM8350 + default y diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 4eb178c1d684..3b3abd75e2d8 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -25,3 +25,9 @@ obj-$(CONFIG_BACKLIGHT_MBP_NVIDIA) += mbp_nvidia_bl.o obj-$(CONFIG_BACKLIGHT_TOSA) += tosa_bl.o obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o +obj-$(CONFIG_BACKLIGHT_MXC_LCDC) += mxc_lcdc_bl.o +obj-$(CONFIG_BACKLIGHT_MXC_IPU) += mxc_ipu_bl.o +obj-$(CONFIG_BACKLIGHT_MXC_PMIC) += mxc_pmic_bl.o +obj-$(CONFIG_BACKLIGHT_WM8350) += wm8350_bl.o +obj-$(CONFIG_BACKLIGHT_MXC_MC13892) += mxc_mc13892_bl.o +obj-$(CONFIG_BACKLIGHT_STMP37XX) += stmp37xx_bl.o diff --git a/drivers/video/backlight/mxc_ipu_bl.c b/drivers/video/backlight/mxc_ipu_bl.c new file mode 100644 index 000000000000..95b044cdd7e2 --- /dev/null +++ b/drivers/video/backlight/mxc_ipu_bl.c @@ -0,0 +1,155 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @defgroup IPU_BL MXC IPU Backlight Driver + */ +/*! + * @file mxc_ipu_bl.c + * + * @brief Backlight Driver for IPU PWM on Freescale MXC/i.MX platforms. + * + * This file contains API defined in include/linux/clk.h for setting up and + * retrieving clocks. + * + * Based on Sharp's Corgi Backlight Driver + * + * @ingroup IPU_BL + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/fb.h> +#include <linux/backlight.h> +#include <linux/ipu.h> + +#define MXC_MAX_INTENSITY 255 +#define MXC_DEFAULT_INTENSITY 127 +#define MXC_INTENSITY_OFF 0 + +struct mxcbl_dev_data { + int intensity; +}; + +static int fb_id; + +static int mxcbl_send_intensity(struct backlight_device *bd) +{ + int intensity = bd->props.brightness; + struct mxcbl_dev_data *devdata = dev_get_drvdata(&bd->dev); + + if (bd->props.power != FB_BLANK_UNBLANK) + intensity = 0; + if (bd->props.fb_blank != FB_BLANK_UNBLANK) + intensity = 0; + + ipu_sdc_set_brightness(intensity); + + devdata->intensity = intensity; + return 0; +} + +static int mxcbl_get_intensity(struct backlight_device *bd) +{ + struct mxcbl_dev_data *devdata = dev_get_drvdata(&bd->dev); + return devdata->intensity; +} + +static int mxcbl_check_fb(struct fb_info *info) +{ + int id = info->fix.id[4] - '0'; + if (id == fb_id) { + if ((id == 3) && !strcmp(info->fix.id, "DISP3 FG")) { + return 0; + } + return 1; + } + return 0; +} + +static struct backlight_ops mxcbl_ops = { + .get_brightness = mxcbl_get_intensity, + .update_status = mxcbl_send_intensity, + .check_fb = mxcbl_check_fb, +}; + +static int __init mxcbl_probe(struct platform_device *pdev) +{ + struct backlight_device *bd; + struct mxcbl_dev_data *devdata; + int ret = 0; + + devdata = kzalloc(sizeof(struct mxcbl_dev_data), GFP_KERNEL); + if (!devdata) + return -ENOMEM; + fb_id = (int)pdev->dev.platform_data; + + bd = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, devdata, + &mxcbl_ops); + if (IS_ERR(bd)) { + ret = PTR_ERR(bd); + goto err0; + } + platform_set_drvdata(pdev, bd); + + bd->props.brightness = MXC_DEFAULT_INTENSITY; + bd->props.max_brightness = MXC_MAX_INTENSITY; + bd->props.power = FB_BLANK_UNBLANK; + bd->props.fb_blank = FB_BLANK_UNBLANK; + backlight_update_status(bd); + + printk("MXC Backlight Device %s Initialized.\n", dev_name(&pdev->dev)); + return 0; + err0: + kfree(devdata); + return ret; +} + +static int mxcbl_remove(struct platform_device *pdev) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + + bd->props.brightness = MXC_INTENSITY_OFF; + backlight_update_status(bd); + + backlight_device_unregister(bd); + + return 0; +} + +static struct platform_driver mxcbl_driver = { + .probe = mxcbl_probe, + .remove = mxcbl_remove, + .driver = { + .name = "mxc_ipu_bl", + }, +}; + +static int __init mxcbl_init(void) +{ + return platform_driver_register(&mxcbl_driver); +} + +static void __exit mxcbl_exit(void) +{ + platform_driver_unregister(&mxcbl_driver); +} + +late_initcall(mxcbl_init); +module_exit(mxcbl_exit); + +MODULE_DESCRIPTION("Freescale MXC/i.MX IPU PWM Backlight Driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/backlight/mxc_lcdc_bl.c b/drivers/video/backlight/mxc_lcdc_bl.c new file mode 100644 index 000000000000..dce952d11950 --- /dev/null +++ b/drivers/video/backlight/mxc_lcdc_bl.c @@ -0,0 +1,160 @@ +/* + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @defgroup LCDC_BL MXC LCDC Backlight Driver + */ +/*! + * @file mxc_lcdc_bl.c + * + * @brief Backlight Driver for LCDC PWM on Freescale MXC/i.MX platforms. + * + * This file contains API defined in include/linux/clk.h for setting up and + * retrieving clocks. + * + * Based on Sharp's Corgi Backlight Driver + * + * @ingroup LCDC_BL + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/fb.h> +#include <linux/backlight.h> +#include <linux/clk.h> + +#define MXC_MAX_INTENSITY 255 +#define MXC_DEFAULT_INTENSITY 127 +#define MXC_INTENSITY_OFF 0 + +extern void mx2fb_set_brightness(uint8_t); + +struct mxcbl_dev_data { + struct clk *clk; + int intensity; +}; + +static int mxcbl_send_intensity(struct backlight_device *bd) +{ + int intensity = bd->props.brightness; + struct mxcbl_dev_data *devdata = dev_get_drvdata(&bd->dev); + + if (bd->props.power != FB_BLANK_UNBLANK) + intensity = 0; + if (bd->props.fb_blank != FB_BLANK_UNBLANK) + intensity = 0; + + if ((devdata->intensity == 0) && (intensity != 0)) + clk_enable(devdata->clk); + + /* PWM contrast control register */ + mx2fb_set_brightness(intensity); + + if ((devdata->intensity != 0) && (intensity == 0)) + clk_disable(devdata->clk); + + devdata->intensity = intensity; + return 0; +} + +static int mxcbl_get_intensity(struct backlight_device *bd) +{ + struct mxcbl_dev_data *devdata = dev_get_drvdata(&bd->dev); + return devdata->intensity; +} + +static int mxcbl_check_fb(struct fb_info *info) +{ + if (strcmp(info->fix.id, "DISP0 BG") == 0) { + return 1; + } + return 0; +} + +static struct backlight_ops mxcbl_ops = { + .get_brightness = mxcbl_get_intensity, + .update_status = mxcbl_send_intensity, + .check_fb = mxcbl_check_fb, +}; + +static int __init mxcbl_probe(struct platform_device *pdev) +{ + struct backlight_device *bd; + struct mxcbl_dev_data *devdata; + int ret = 0; + + devdata = kzalloc(sizeof(struct mxcbl_dev_data), GFP_KERNEL); + if (!devdata) + return -ENOMEM; + + devdata->clk = clk_get(NULL, "lcdc_clk"); + + bd = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, devdata, + &mxcbl_ops); + if (IS_ERR(bd)) { + ret = PTR_ERR(bd); + goto err0; + } + platform_set_drvdata(pdev, bd); + + bd->props.brightness = MXC_DEFAULT_INTENSITY; + bd->props.max_brightness = MXC_MAX_INTENSITY; + bd->props.power = FB_BLANK_UNBLANK; + bd->props.fb_blank = FB_BLANK_UNBLANK; + mx2fb_set_brightness(MXC_DEFAULT_INTENSITY); + + printk("MXC Backlight Device %s Initialized.\n", dev_name(&pdev->dev)); + return 0; + err0: + kfree(devdata); + return ret; +} + +static int mxcbl_remove(struct platform_device *pdev) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + + bd->props.brightness = MXC_INTENSITY_OFF; + backlight_update_status(bd); + + backlight_device_unregister(bd); + + return 0; +} + +static struct platform_driver mxcbl_driver = { + .probe = mxcbl_probe, + .remove = mxcbl_remove, + .driver = { + .name = "mxc_lcdc_bl", + }, +}; + +static int __init mxcbl_init(void) +{ + return platform_driver_register(&mxcbl_driver); +} + +static void __exit mxcbl_exit(void) +{ + platform_driver_unregister(&mxcbl_driver); +} + +module_init(mxcbl_init); +module_exit(mxcbl_exit); + +MODULE_DESCRIPTION("Freescale MXC/i.MX LCDC PWM Backlight Driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/backlight/mxc_mc13892_bl.c b/drivers/video/backlight/mxc_mc13892_bl.c new file mode 100644 index 000000000000..6640dd5fce70 --- /dev/null +++ b/drivers/video/backlight/mxc_mc13892_bl.c @@ -0,0 +1,177 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/fb.h> +#include <linux/backlight.h> + +#include <linux/pmic_light.h> +#include <linux/pmic_external.h> + +/* +#define MXC_MAX_INTENSITY 255 +#define MXC_DEFAULT_INTENSITY 127 +*/ +/* workaround for atlas hot issue */ +#define MXC_MAX_INTENSITY 128 +#define MXC_DEFAULT_INTENSITY 64 + +#define MXC_INTENSITY_OFF 0 + +struct mxcbl_dev_data { + int intensity; + int suspend; +}; + +static int mxcbl_set_intensity(struct backlight_device *bd) +{ + int brightness = bd->props.brightness; + struct mxcbl_dev_data *devdata = dev_get_drvdata(&bd->dev); + + if (bd->props.power != FB_BLANK_UNBLANK) + brightness = 0; + if (bd->props.fb_blank != FB_BLANK_UNBLANK) + brightness = 0; + if (devdata->suspend) + brightness = 0; + + brightness = brightness / 4; + mc13892_bklit_set_dutycycle(LIT_MAIN, brightness); + devdata->intensity = brightness; + + return 0; +} + +static int mxcbl_get_intensity(struct backlight_device *bd) +{ + struct mxcbl_dev_data *devdata = dev_get_drvdata(&bd->dev); + return devdata->intensity; +} + +static int mxcbl_check_fb(struct fb_info *info) +{ + char *id = info->fix.id; + + if (!strcmp(id, "DISP3 BG")) + return 1; + else + return 0; +} + +static struct backlight_ops bl_ops; + +static int __init mxcbl_probe(struct platform_device *pdev) +{ + int ret = 0; + struct backlight_device *bd; + struct mxcbl_dev_data *devdata; + pmic_version_t pmic_version; + + pr_debug("mc13892 backlight start probe\n"); + + devdata = kzalloc(sizeof(struct mxcbl_dev_data), GFP_KERNEL); + if (!devdata) + return -ENOMEM; + + bl_ops.check_fb = mxcbl_check_fb; + bl_ops.get_brightness = mxcbl_get_intensity; + bl_ops.update_status = mxcbl_set_intensity; + bd = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, devdata, + &bl_ops); + if (IS_ERR(bd)) { + ret = PTR_ERR(bd); + goto err0; + } + + platform_set_drvdata(pdev, bd); + + /* according to LCD spec, current should be 18mA */ + /* workaround for MC13892 TO1.1 crash issue, set current 6mA */ + pmic_version = pmic_get_version(); + if (pmic_version.revision < 20) + mc13892_bklit_set_current(LIT_MAIN, LIT_CURR_6); + else + mc13892_bklit_set_current(LIT_MAIN, LIT_CURR_18); + bd->props.brightness = MXC_DEFAULT_INTENSITY; + bd->props.max_brightness = MXC_MAX_INTENSITY; + bd->props.power = FB_BLANK_UNBLANK; + bd->props.fb_blank = FB_BLANK_UNBLANK; + backlight_update_status(bd); + pr_debug("mc13892 backlight probed successfully\n"); + return 0; + + err0: + kfree(devdata); + return ret; +} + +static int mxcbl_remove(struct platform_device *pdev) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + struct mxcbl_dev_data *devdata = dev_get_drvdata(&bd->dev); + + kfree(devdata); + backlight_device_unregister(bd); + return 0; +} + +static int mxcbl_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + struct mxcbl_dev_data *devdata = dev_get_drvdata(&bd->dev); + + devdata->suspend = 1; + backlight_update_status(bd); + return 0; +} + +static int mxcbl_resume(struct platform_device *pdev) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + struct mxcbl_dev_data *devdata = dev_get_drvdata(&bd->dev); + + devdata->suspend = 0; + backlight_update_status(bd); + return 0; +} + +static struct platform_driver mxcbl_driver = { + .probe = mxcbl_probe, + .remove = mxcbl_remove, + .suspend = mxcbl_suspend, + .resume = mxcbl_resume, + .driver = { + .name = "mxc_mc13892_bl", + }, +}; + +static int __init mxcbl_init(void) +{ + return platform_driver_register(&mxcbl_driver); +} + +static void __exit mxcbl_exit(void) +{ + platform_driver_unregister(&mxcbl_driver); +} + +module_init(mxcbl_init); +module_exit(mxcbl_exit); + +MODULE_DESCRIPTION("Freescale MXC/i.MX PMIC Backlight Driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/backlight/mxc_pmic_bl.c b/drivers/video/backlight/mxc_pmic_bl.c new file mode 100644 index 000000000000..add55596e445 --- /dev/null +++ b/drivers/video/backlight/mxc_pmic_bl.c @@ -0,0 +1,197 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @defgroup PMIC_BL MXC PMIC Backlight Driver + */ +/*! + * @file mxc_pmic_bl.c + * + * @brief PMIC Backlight Driver for Freescale MXC/i.MX platforms. + * + * This file contains API defined in include/linux/clk.h for setting up and + * retrieving clocks. + * + * Based on Sharp's Corgi Backlight Driver + * + * @ingroup PMIC_BL + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/fb.h> +#include <linux/backlight.h> +#include <linux/pmic_light.h> + +#include <mach/pmic_power.h> + +#define MXC_MAX_INTENSITY 255 +#define MXC_DEFAULT_INTENSITY 127 +#define MXC_INTENSITY_OFF 0 + +struct mxcbl_dev_data { + int bl_id; + int intensity; + struct backlight_ops bl_ops; +}; + +static int pmic_bl_use_count; +static int main_fb_id; +static int sec_fb_id; + +static int mxcbl_send_intensity(struct backlight_device *bd) +{ + int intensity = bd->props.brightness; + struct mxcbl_dev_data *devdata = dev_get_drvdata(&bd->dev); + + if (bd->props.power != FB_BLANK_UNBLANK) + intensity = 0; + if (bd->props.fb_blank != FB_BLANK_UNBLANK) + intensity = 0; + + intensity = intensity / 16; + pmic_bklit_set_dutycycle(devdata->bl_id, intensity); + + devdata->intensity = intensity; + return 0; +} + +static int mxcbl_get_intensity(struct backlight_device *bd) +{ + struct mxcbl_dev_data *devdata = dev_get_drvdata(&bd->dev); + return devdata->intensity; +} + +static int mxcbl_check_main_fb(struct fb_info *info) +{ + int id = info->fix.id[4] - '0'; + + if (id == main_fb_id) { + return 1; + } else { + return 0; + } +} + +static int mxcbl_check_sec_fb(struct fb_info *info) +{ + int id = info->fix.id[4] - '0'; + + if (id == sec_fb_id) { + return 1; + } else { + return 0; + } +} + +static int __init mxcbl_probe(struct platform_device *pdev) +{ + int ret = 0; + struct backlight_device *bd; + struct mxcbl_dev_data *devdata; + + devdata = kzalloc(sizeof(struct mxcbl_dev_data), GFP_KERNEL); + if (!devdata) + return -ENOMEM; + devdata->bl_id = pdev->id; + + if (pdev->id == 0) { + devdata->bl_ops.check_fb = mxcbl_check_main_fb; + main_fb_id = (int)pdev->dev.platform_data; + } else { + devdata->bl_ops.check_fb = mxcbl_check_sec_fb; + sec_fb_id = (int)pdev->dev.platform_data; + } + + devdata->bl_ops.get_brightness = mxcbl_get_intensity; + devdata->bl_ops.update_status = mxcbl_send_intensity, + bd = + backlight_device_register(dev_name(&pdev->dev), &pdev->dev, devdata, + &devdata->bl_ops); + if (IS_ERR(bd)) { + ret = PTR_ERR(bd); + goto err0; + } + + platform_set_drvdata(pdev, bd); + + if (pmic_bl_use_count++ == 0) { + pmic_power_regulator_on(SW_SW3); + pmic_power_regulator_set_lp_mode(SW_SW3, LOW_POWER_CTRL_BY_PIN); + + pmic_bklit_tcled_master_enable(); + pmic_bklit_enable_edge_slow(); + pmic_bklit_set_cycle_time(0); + } + + pmic_bklit_set_current(devdata->bl_id, 7); + bd->props.brightness = MXC_DEFAULT_INTENSITY; + bd->props.max_brightness = MXC_MAX_INTENSITY; + bd->props.power = FB_BLANK_UNBLANK; + bd->props.fb_blank = FB_BLANK_UNBLANK; + backlight_update_status(bd); + + printk("MXC Backlight Device %s Initialized.\n", dev_name(&pdev->dev)); + return 0; + err0: + kfree(devdata); + return ret; +} + +static int mxcbl_remove(struct platform_device *pdev) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + + bd->props.brightness = MXC_INTENSITY_OFF; + backlight_update_status(bd); + + if (--pmic_bl_use_count == 0) { + pmic_bklit_tcled_master_disable(); + + pmic_power_regulator_off(SW_SW3); + pmic_power_regulator_set_lp_mode(SW_SW3, LOW_POWER_CTRL_BY_PIN); + } + + backlight_device_unregister(bd); + + printk("MXC Backlight Driver Unloaded\n"); + + return 0; +} + +static struct platform_driver mxcbl_driver = { + .probe = mxcbl_probe, + .remove = mxcbl_remove, + .driver = { + .name = "mxc_pmic_bl", + }, +}; + +static int __init mxcbl_init(void) +{ + return platform_driver_register(&mxcbl_driver); +} + +static void __exit mxcbl_exit(void) +{ + platform_driver_unregister(&mxcbl_driver); +} + +module_init(mxcbl_init); +module_exit(mxcbl_exit); + +MODULE_DESCRIPTION("Freescale MXC/i.MX PMIC Backlight Driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/backlight/stmp37xx_bl.c b/drivers/video/backlight/stmp37xx_bl.c new file mode 100644 index 000000000000..6b4d9a0ccbaa --- /dev/null +++ b/drivers/video/backlight/stmp37xx_bl.c @@ -0,0 +1,378 @@ +/* + * Backlight Driver for Freescale STMP37XX/STMP378X + * + * Embedded Alley Solutions, Inc <source@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/fb.h> +#include <linux/backlight.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/regulator/consumer.h> + +#include <mach/lcdif.h> +#include <mach/regulator.h> + +struct stmp3xxx_bl_data { + struct notifier_block nb; + struct notifier_block reg_nb; + struct notifier_block reg_init_nb; + struct backlight_device *bd; + struct stmp3xxx_platform_bl_data *pdata; + int current_intensity; + int saved_intensity; + int stmp3xxxbl_suspended; + int stmp3xxxbl_constrained; +}; + +static int stmp3xxxbl_do_probe(struct stmp3xxx_bl_data *data, + struct stmp3xxx_platform_bl_data *pdata); +static int stmp3xxxbl_set_intensity(struct backlight_device *bd); +static inline void bl_register_reg(struct stmp3xxx_platform_bl_data *pdata, + struct stmp3xxx_bl_data *data); + + +/* + * If we got here init is done + */ +static int bl_init_reg_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct stmp3xxx_bl_data *bdata; + struct stmp3xxx_platform_bl_data *pdata; + struct regulator *r = regulator_get(NULL, "stmp3xxx-bl-1"); + + bdata = container_of(self, struct stmp3xxx_bl_data, reg_init_nb); + pdata = bdata->pdata; + + if (r && !IS_ERR(r)) + regulator_put(r); + else + goto out; + + bl_register_reg(pdata, bdata); + + if (pdata->regulator) { + + printk(KERN_NOTICE"%s: setting intensity\n", __func__); + + bus_unregister_notifier(&platform_bus_type, + &bdata->reg_init_nb); + mutex_lock(&bdata->bd->ops_lock); + stmp3xxxbl_set_intensity(bdata->bd); + mutex_unlock(&bdata->bd->ops_lock); + } + +out: + return 0; +} + +static int bl_reg_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct stmp3xxx_bl_data *bdata; + struct stmp3xxx_platform_bl_data *pdata; + bdata = container_of(self, struct stmp3xxx_bl_data, reg_nb); + pdata = bdata->pdata; + + mutex_lock(&bdata->bd->ops_lock); + + switch (event) { + case STMP3XXX_REG5V_IS_USB: + bdata->bd->props.max_brightness = pdata->bl_cons_intensity; + bdata->bd->props.brightness = pdata->bl_cons_intensity; + bdata->saved_intensity = bdata->current_intensity; + bdata->stmp3xxxbl_constrained = 1; + break; + case STMP3XXX_REG5V_NOT_USB: + bdata->bd->props.max_brightness = pdata->bl_max_intensity; + bdata->bd->props.brightness = bdata->saved_intensity; + bdata->stmp3xxxbl_constrained = 0; + break; + } + + stmp3xxxbl_set_intensity(bdata->bd); + mutex_unlock(&bdata->bd->ops_lock); + return 0; +} + +static inline void bl_unregister_reg(struct stmp3xxx_platform_bl_data *pdata, + struct stmp3xxx_bl_data *data) +{ + if (!pdata) + return; + if (pdata->regulator) + regulator_unregister_notifier(pdata->regulator, + &data->reg_nb); + if (pdata->regulator) + regulator_put(pdata->regulator); + pdata->regulator = NULL; +} + +static inline void bl_register_reg(struct stmp3xxx_platform_bl_data *pdata, + struct stmp3xxx_bl_data *data) +{ + pdata->regulator = regulator_get(NULL, "stmp3xxx-bl-1"); + if (pdata->regulator && !IS_ERR(pdata->regulator)) { + regulator_set_mode(pdata->regulator, REGULATOR_MODE_FAST); + if (pdata->regulator) { + data->reg_nb.notifier_call = bl_reg_callback; + regulator_register_notifier(pdata->regulator, + &data->reg_nb); + } + } else{ + printk(KERN_ERR "%s: failed to get regulator\n", __func__); + pdata->regulator = NULL; + } + +} + +static int bl_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct stmp3xxx_platform_fb_entry *pentry = data; + struct stmp3xxx_bl_data *bdata; + struct stmp3xxx_platform_bl_data *pdata; + + switch (event) { + case STMP3XXX_LCDIF_PANEL_INIT: + bdata = container_of(self, struct stmp3xxx_bl_data, nb); + pdata = pentry->bl_data; + bdata->pdata = pdata; + if (pdata) { + bl_register_reg(pdata, bdata); + if (!pdata->regulator) { + /* wait for regulator to appear */ + bdata->reg_init_nb.notifier_call = + bl_init_reg_callback; + bus_register_notifier(&platform_bus_type, + &bdata->reg_init_nb); + } + return stmp3xxxbl_do_probe(bdata, pdata); + } + break; + + case STMP3XXX_LCDIF_PANEL_RELEASE: + bdata = container_of(self, struct stmp3xxx_bl_data, nb); + pdata = pentry->bl_data; + if (pdata) { + bus_unregister_notifier(&platform_bus_type, + &bdata->reg_init_nb); + bl_unregister_reg(pdata, bdata); + pdata->free_bl(pdata); + } + bdata->pdata = NULL; + break; + } + return 0; +} + +#ifdef CONFIG_PM +static int stmp3xxxbl_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct stmp3xxx_bl_data *data = platform_get_drvdata(pdev); + struct stmp3xxx_platform_bl_data *pdata = data->pdata; + + data->stmp3xxxbl_suspended = 1; + if (pdata) { + dev_dbg(&pdev->dev, "real suspend\n"); + stmp3xxxbl_set_intensity(data->bd); + } + return 0; +} + +static int stmp3xxxbl_resume(struct platform_device *pdev) +{ + struct stmp3xxx_bl_data *data = platform_get_drvdata(pdev); + struct stmp3xxx_platform_bl_data *pdata = data->pdata; + int ret = 0; + + data->stmp3xxxbl_suspended = 0; + if (pdata) { + dev_dbg(&pdev->dev, "real resume\n"); + pdata->free_bl(pdata); + ret = pdata->init_bl(pdata); + if (ret) + goto out; + stmp3xxxbl_set_intensity(data->bd); + } +out: + return ret; +} +#else +#define stmp3xxxbl_suspend NULL +#define stmp3xxxbl_resume NULL +#endif +/* + * This function should be called with bd->ops_lock held + * Suspend/resume ? + */ +static int stmp3xxxbl_set_intensity(struct backlight_device *bd) +{ + struct platform_device *pdev = dev_get_drvdata(&bd->dev); + struct stmp3xxx_bl_data *data = platform_get_drvdata(pdev); + struct stmp3xxx_platform_bl_data *pdata = data->pdata; + + if (pdata) { + int ret; + + ret = pdata->set_bl_intensity(pdata, bd, + data->stmp3xxxbl_suspended); + if (ret) + bd->props.brightness = data->current_intensity; + else + data->current_intensity = bd->props.brightness; + return ret; + } else + return -ENODEV; +} + +static int stmp3xxxbl_get_intensity(struct backlight_device *bd) +{ + struct platform_device *pdev = dev_get_drvdata(&bd->dev); + struct stmp3xxx_bl_data *data = platform_get_drvdata(pdev); + + return data->current_intensity; +} + +static struct backlight_ops stmp3xxxbl_ops = { + .get_brightness = stmp3xxxbl_get_intensity, + .update_status = stmp3xxxbl_set_intensity, +}; + +static int stmp3xxxbl_do_probe(struct stmp3xxx_bl_data *data, + struct stmp3xxx_platform_bl_data *pdata) +{ + int ret = pdata->init_bl(pdata); + + if (ret) + goto out; + + data->bd->props.power = FB_BLANK_UNBLANK; + data->bd->props.fb_blank = FB_BLANK_UNBLANK; + if (data->stmp3xxxbl_constrained) { + data->bd->props.max_brightness = pdata->bl_cons_intensity; + data->bd->props.brightness = pdata->bl_cons_intensity; + } else { + data->bd->props.max_brightness = pdata->bl_max_intensity; + data->bd->props.brightness = pdata->bl_default_intensity; + } + + data->pdata = pdata; + stmp3xxxbl_set_intensity(data->bd); + +out: + return ret; +} + +static int __init stmp3xxxbl_probe(struct platform_device *pdev) +{ + struct stmp3xxx_bl_data *data; + struct stmp3xxx_platform_bl_data *pdata = pdev->dev.platform_data; + int ret = 0; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto out; + } + data->bd = backlight_device_register(pdev->name, &pdev->dev, pdev, + &stmp3xxxbl_ops); + if (IS_ERR(data->bd)) { + ret = PTR_ERR(data->bd); + goto out_1; + } + + get_device(&pdev->dev); + + data->nb.notifier_call = bl_callback; + stmp3xxx_lcdif_register_client(&data->nb); + platform_set_drvdata(pdev, data); + + if (pdata) { + ret = stmp3xxxbl_do_probe(data, pdata); + if (ret) + goto out_2; + } + + goto out; + +out_2: + put_device(&pdev->dev); +out_1: + kfree(data); +out: + return ret; +} + +static int stmp3xxxbl_remove(struct platform_device *pdev) +{ + struct stmp3xxx_platform_bl_data *pdata = pdev->dev.platform_data; + struct stmp3xxx_bl_data *data = platform_get_drvdata(pdev); + struct backlight_device *bd = data->bd; + + bd->props.power = FB_BLANK_POWERDOWN; + bd->props.fb_blank = FB_BLANK_POWERDOWN; + bd->props.brightness = 0; + data->current_intensity = bd->props.brightness; + + if (pdata) { + pdata->set_bl_intensity(pdata, bd, data->stmp3xxxbl_suspended); + if (pdata->free_bl) + pdata->free_bl(pdata); + } + backlight_device_unregister(bd); + if (pdata->regulator) + regulator_put(pdata->regulator); + put_device(&pdev->dev); + platform_set_drvdata(pdev, NULL); + stmp3xxx_lcdif_unregister_client(&data->nb); + kfree(data); + + return 0; +} + +static struct platform_driver stmp3xxxbl_driver = { + .probe = stmp3xxxbl_probe, + .remove = __devexit_p(stmp3xxxbl_remove), + .suspend = stmp3xxxbl_suspend, + .resume = stmp3xxxbl_resume, + .driver = { + .name = "stmp3xxx-bl", + .owner = THIS_MODULE, + }, +}; + +static int __init stmp3xxx_init(void) +{ + return platform_driver_register(&stmp3xxxbl_driver); +} + +static void __exit stmp3xxx_exit(void) +{ + platform_driver_unregister(&stmp3xxxbl_driver); +} + +module_init(stmp3xxx_init); +module_exit(stmp3xxx_exit); + +MODULE_AUTHOR("Embedded Alley Solutions, Inc <sources@embeddedalley.com>"); +MODULE_DESCRIPTION("STMP3xxx Backlight Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/backlight/wm8350_bl.c b/drivers/video/backlight/wm8350_bl.c new file mode 100644 index 000000000000..88014fba6b7e --- /dev/null +++ b/drivers/video/backlight/wm8350_bl.c @@ -0,0 +1,298 @@ +/* + * Backlight driver for DCDC2 on i.MX32ADS board + * + * Copyright(C) 2007 Wolfson Microelectronics PLC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/mutex.h> +#include <linux/fb.h> +#include <linux/platform_device.h> +#include <linux/backlight.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/wm8350/pmic.h> +#include <linux/mfd/wm8350/bl.h> + +struct wm8350_backlight { + struct backlight_properties props; + struct backlight_device *device; + struct regulator *dcdc; + struct regulator *isink; + struct notifier_block notifier; + struct work_struct work; + struct mutex mutex; + int intensity; + int suspend; + int retries; +}; + +/* hundredths of uA, 405 = 4.05 uA */ +static const int intensity_huA[] = { + 405, 482, 573, 681, 810, 963, 1146, 1362, 1620, 1927, 2291, 2725, + 3240, 3853, 4582, 5449, 6480, 7706, 9164, 10898, 12960, 15412, 18328, + 21796, 25920, 30824, 36656, 43592, 51840, 61648, 73313, 87184, + 103680, 123297, 146626, 174368, 207360, 246594, 293251, 348737, + 414720, 493188, 586503, 697473, 829440, 986376, 1173005, 1394946, + 1658880, 1972752, 2346011, 2789892, 3317760, 3945504, 4692021, + 5579785, 6635520, 7891008, 9384042, 11159570, 13271040, 15782015, + 18768085, 22319140, +}; + +static void bl_work(struct work_struct *work) +{ + struct wm8350_backlight *bl = + container_of(work, struct wm8350_backlight, work); + struct regulator *isink = bl->isink; + + mutex_lock(&bl->mutex); + if (bl->intensity >= 0 && + bl->intensity < ARRAY_SIZE(intensity_huA)) { + bl->retries = 0; + regulator_set_current_limit(isink, + 0, intensity_huA[bl->intensity] / 100); + } else + printk(KERN_ERR "wm8350: Backlight intensity error\n"); + mutex_unlock(&bl->mutex); +} + +static int wm8350_bl_notifier(struct notifier_block *self, + unsigned long event, void *data) +{ + struct wm8350_backlight *bl = + container_of(self, struct wm8350_backlight, notifier); + struct regulator *isink = bl->isink; + + if (event & REGULATOR_EVENT_UNDER_VOLTAGE) + printk(KERN_ERR "wm8350: BL DCDC undervoltage\n"); + if (event & REGULATOR_EVENT_REGULATION_OUT) + printk(KERN_ERR "wm8350: BL ISINK out of regulation\n"); + + mutex_lock(&bl->mutex); + if (bl->retries) { + bl->retries--; + regulator_disable(isink); + regulator_set_current_limit(isink, 0, bl->intensity); + regulator_enable(isink); + } else { + printk(KERN_ERR + "wm8350: BL regulation retry failure - disable\n"); + bl->intensity = 0; + regulator_disable(isink); + } + mutex_unlock(&bl->mutex); + return 0; +} + +static int wm8350_bl_send_intensity(struct backlight_device *bd) +{ + struct wm8350_backlight *bl = + (struct wm8350_backlight *)dev_get_drvdata(&bd->dev); + int intensity = bd->props.brightness; + + if (bd->props.power != FB_BLANK_UNBLANK) + intensity = 0; + if (bd->props.fb_blank != FB_BLANK_UNBLANK) + intensity = 0; + if (bl->suspend) + intensity = 0; + + mutex_lock(&bl->mutex); + bl->intensity = intensity; + mutex_unlock(&bl->mutex); + schedule_work(&bl->work); + + return 0; +} + +#ifdef CONFIG_PM +static int wm8350_bl_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct wm8350_backlight *bl = + (struct wm8350_backlight *)platform_get_drvdata(pdev); + + bl->suspend = 1; + backlight_update_status(bl->device); + return 0; +} + +static int wm8350_bl_resume(struct platform_device *pdev) +{ + struct wm8350_backlight *bl = + (struct wm8350_backlight *)platform_get_drvdata(pdev); + + bl->suspend = 0; + backlight_update_status(bl->device); + return 0; +} +#else +#define wm8350_bl_suspend NULL +#define wm8350_bl_resume NULL +#endif + +static int wm8350_bl_get_intensity(struct backlight_device *bd) +{ + struct wm8350_backlight *bl = + (struct wm8350_backlight *)dev_get_drvdata(&bd->dev); + return bl->intensity; +} + +static struct backlight_ops wm8350_bl_ops = { + .get_brightness = wm8350_bl_get_intensity, + .update_status = wm8350_bl_send_intensity, +}; + +static int wm8350_bl_probe(struct platform_device *pdev) +{ + struct regulator *isink, *dcdc; + struct wm8350_backlight *bl; + struct wm8350_bl_platform_data *pdata = pdev->dev.platform_data; + struct wm8350 *pmic; + int ret; + + if (pdata == NULL) { + printk(KERN_ERR "%s: no platform data\n", __func__); + return -ENODEV; + } + + if (pdata->isink != WM8350_ISINK_A && pdata->isink != WM8350_ISINK_B) { + printk(KERN_ERR "%s: invalid ISINK\n", __func__); + return -EINVAL; + } + if (pdata->dcdc != WM8350_DCDC_2 && pdata->dcdc != WM8350_DCDC_5) { + printk(KERN_ERR "%s: invalid DCDC\n", __func__); + return -EINVAL; + } + + printk(KERN_INFO "wm8350: backlight using %s and %s\n", + pdata->isink == WM8350_ISINK_A ? "ISINKA" : "ISINKB", + pdata->dcdc == WM8350_DCDC_2 ? "DCDC2" : "DCDC5"); + + isink = regulator_get(&pdev->dev, + pdata->isink == WM8350_ISINK_A ? "ISINKA" : "ISINKB"); + if (IS_ERR(isink) || isink == NULL) { + printk(KERN_ERR "%s: cant get ISINK\n", __func__); + return PTR_ERR(isink); + } + + dcdc = regulator_get(&pdev->dev, + pdata->dcdc == WM8350_DCDC_2 ? "DCDC2" : "DCDC5"); + if (IS_ERR(dcdc) || dcdc == NULL) { + printk(KERN_ERR "%s: cant get DCDC\n", __func__); + regulator_put(isink); + return PTR_ERR(dcdc); + } + + bl = kzalloc(sizeof(*bl), GFP_KERNEL); + if (bl == NULL) { + regulator_put(isink); + regulator_put(dcdc); + return -ENOMEM; + } + + mutex_init(&bl->mutex); + INIT_WORK(&bl->work, bl_work); + bl->props.max_brightness = pdata->max_brightness; + bl->props.power = pdata->power; + bl->props.brightness = pdata->brightness; + bl->retries = pdata->retries; + bl->dcdc = dcdc; + bl->isink = isink; + platform_set_drvdata(pdev, bl); + pmic = regulator_get_drvdata(bl->isink); + + wm8350_bl_ops.check_fb = pdata->check_fb; + + bl->device = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, + bl, &wm8350_bl_ops); + if (IS_ERR(bl->device)) { + ret = PTR_ERR(bl->device); + regulator_put(dcdc); + regulator_put(isink); + kfree(bl); + return ret; + } + + bl->notifier.notifier_call = wm8350_bl_notifier; + regulator_register_notifier(dcdc, &bl->notifier); + regulator_register_notifier(isink, &bl->notifier); + bl->device->props = bl->props; + + regulator_set_current_limit(isink, 0, 20000); + + wm8350_isink_set_flash(pmic, pdata->isink, + WM8350_ISINK_FLASH_DISABLE, + WM8350_ISINK_FLASH_TRIG_BIT, + WM8350_ISINK_FLASH_DUR_32MS, + WM8350_ISINK_FLASH_ON_1_00S, + WM8350_ISINK_FLASH_OFF_1_00S, + WM8350_ISINK_FLASH_MODE_EN); + + wm8350_dcdc25_set_mode(pmic, pdata->dcdc, + WM8350_ISINK_MODE_BOOST, WM8350_ISINK_ILIM_NORMAL, + pdata->voltage_ramp, pdata->isink == WM8350_ISINK_A ? + WM8350_DC5_FBSRC_ISINKA : WM8350_DC5_FBSRC_ISINKB); + + wm8350_dcdc_set_slot(pmic, pdata->dcdc, 15, 0, + pdata->dcdc == WM8350_DCDC_2 ? + WM8350_DC2_ERRACT_SHUTDOWN_CONV : WM8350_DC5_ERRACT_NONE); + + regulator_enable(isink); + backlight_update_status(bl->device); + return 0; +} + +static int wm8350_bl_remove(struct platform_device *pdev) +{ + struct wm8350_backlight *bl = + (struct wm8350_backlight *)platform_get_drvdata(pdev); + struct regulator *isink = bl->isink, *dcdc = bl->dcdc; + + bl->intensity = 0; + backlight_update_status(bl->device); + schedule_work(&bl->work); + flush_scheduled_work(); + backlight_device_unregister(bl->device); + + regulator_set_current_limit(isink, 0, 0); + regulator_disable(isink); + regulator_unregister_notifier(isink, &bl->notifier); + regulator_unregister_notifier(dcdc, &bl->notifier); + regulator_put(isink); + regulator_put(dcdc); + return 0; +} + +struct platform_driver imx32ads_backlight_driver = { + .driver = { + .name = "wm8350-bl", + .owner = THIS_MODULE, + }, + .probe = wm8350_bl_probe, + .remove = wm8350_bl_remove, + .suspend = wm8350_bl_suspend, + .resume = wm8350_bl_resume, +}; + +static int __devinit imx32ads_backlight_init(void) +{ + return platform_driver_register(&imx32ads_backlight_driver); +} + +static void imx32ads_backlight_exit(void) +{ + platform_driver_unregister(&imx32ads_backlight_driver); +} + +device_initcall_sync(imx32ads_backlight_init); +module_exit(imx32ads_backlight_exit); + +MODULE_AUTHOR("Liam Girdwood <lg@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("WM8350 Backlight driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/Kconfig b/drivers/video/mxc/Kconfig new file mode 100644 index 000000000000..268879626fc2 --- /dev/null +++ b/drivers/video/mxc/Kconfig @@ -0,0 +1,83 @@ +config FB_MXC + tristate "MXC Framebuffer support" + depends on FB && (MXC_IPU || ARCH_MX21 || ARCH_MX27 || ARCH_MX25) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MODE_HELPERS + default y + help + This is a framebuffer device for the MXC LCD Controller. + See <http://www.linux-fbdev.org/> for information on framebuffer + devices. + + If you plan to use the LCD display with your MXC system, say + Y here. + +config FB_MXC_SYNC_PANEL + depends on FB_MXC + tristate "Synchronous Panel Framebuffer" + default y + +config FB_MXC_EPSON_VGA_SYNC_PANEL + depends on FB_MXC_SYNC_PANEL + tristate "Epson VGA Panel" + default n + +config FB_MXC_TVOUT_TVE + tristate "MXC TVE TV Out Encoder" + depends on FB_MXC_SYNC_PANEL + depends on MXC_IPU_V3 + +config FB_MXC_CLAA_WVGA_SYNC_PANEL + depends on FB_MXC_SYNC_PANEL + tristate "CLAA WVGA Panel" + +config FB_MXC_CH7026 + depends on FB_MXC_SYNC_PANEL + tristate "Chrontel CH7026 VGA Interface Chip" + +config FB_MXC_TVOUT_CH7024 + tristate "CH7024 TV Out Encoder" + depends on FB_MXC_SYNC_PANEL + +config FB_MXC_LOW_PWR_DISPLAY + bool "Low Power Display Refresh Mode" + depends on FB_MXC_SYNC_PANEL && MXC_FB_IRAM + default y + +config FB_MXC_INTERNAL_MEM + bool "Framebuffer in Internal RAM" + depends on FB_MXC_SYNC_PANEL && MXC_FB_IRAM + default y + +config FB_MXC_ASYNC_PANEL + depends on FB_MXC + bool "Asynchronous Panels" + default n + +menu "Asynchronous Panel Type" + depends on FB_MXC_ASYNC_PANEL && FB_MXC + +config FB_MXC_EPSON_PANEL + depends on FB_MXC_ASYNC_PANEL + default n + bool "Epson 176x220 Panel" + +endmenu + +choice + prompt "Async Panel Interface Type" + depends on FB_MXC_ASYNC_PANEL && FB_MXC + default FB_MXC_ASYNC_PANEL_IFC_16_BIT + +config FB_MXC_ASYNC_PANEL_IFC_8_BIT + bool "8-bit Parallel Bus Interface" + +config FB_MXC_ASYNC_PANEL_IFC_16_BIT + bool "16-bit Parallel Bus Interface" + +config FB_MXC_ASYNC_PANEL_IFC_SERIAL + bool "Serial Bus Interface" + +endchoice diff --git a/drivers/video/mxc/Makefile b/drivers/video/mxc/Makefile new file mode 100644 index 000000000000..916b431152f1 --- /dev/null +++ b/drivers/video/mxc/Makefile @@ -0,0 +1,21 @@ +ifeq ($(CONFIG_ARCH_MX21)$(CONFIG_ARCH_MX27)$(CONFIG_ARCH_MX25),y) + obj-$(CONFIG_FB_MXC_TVOUT) += fs453.o + obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mx2fb.o mxcfb_modedb.o + obj-$(CONFIG_FB_MXC_EPSON_PANEL) += mx2fb_epson.o +else +ifeq ($(CONFIG_MXC_IPU_V1),y) + obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mxcfb.o mxcfb_modedb.o +else + obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mxc_ipuv3_fb.o +endif + obj-$(CONFIG_FB_MXC_EPSON_PANEL) += mxcfb_epson.o + obj-$(CONFIG_FB_MXC_EPSON_QVGA_PANEL) += mxcfb_epson_qvga.o + obj-$(CONFIG_FB_MXC_TOSHIBA_QVGA_PANEL) += mxcfb_toshiba_qvga.o + obj-$(CONFIG_FB_MXC_SHARP_128_PANEL) += mxcfb_sharp_128x128.o +endif +obj-$(CONFIG_FB_MXC_EPSON_VGA_SYNC_PANEL) += mxcfb_epson_vga.o +obj-$(CONFIG_FB_MXC_CLAA_WVGA_SYNC_PANEL) += mxcfb_claa_wvga.o +obj-$(CONFIG_FB_MXC_TVOUT_CH7024) += ch7024.o +obj-$(CONFIG_FB_MXC_TVOUT_TVE) += tve.o +obj-$(CONFIG_FB_MXC_CH7026) += mxcfb_ch7026.o +obj-$(CONFIG_FB_MODE_HELPERS) += mxc_edid.o diff --git a/drivers/video/mxc/ch7024.c b/drivers/video/mxc/ch7024.c new file mode 100644 index 000000000000..35ffb7570c2b --- /dev/null +++ b/drivers/video/mxc/ch7024.c @@ -0,0 +1,866 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ch7024.c + * @brief Driver for CH7024 TV encoder + * + * @ingroup Framebuffer + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/sysfs.h> +#include <linux/mxcfb.h> +#include <linux/regulator/consumer.h> +#include <asm/uaccess.h> +#include <asm/atomic.h> +#include <mach/gpio.h> +#include <mach/hw_events.h> + +/*! + * CH7024 registers + */ +#define CH7024_DEVID 0x00 +#define CH7024_REVID 0x01 +#define CH7024_PG 0x02 + +#define CH7024_RESET 0x03 +#define CH7024_POWER 0x04 +#define CH7024_TVHUE 0x05 +#define CH7024_TVSAT 0x06 +#define CH7024_TVCTA 0x07 +#define CH7024_TVBRI 0x08 +#define CH7024_TVSHARP 0x09 +#define CH7024_OUT_FMT 0x0A +#define CH7024_XTAL 0x0B +#define CH7024_IDF1 0x0C +#define CH7024_IDF2 0x0D +#define CH7024_SYNC 0x0E +#define CH7024_TVFILTER1 0x0F +#define CH7024_TVFILTER2 0x10 +#define CH7024_IN_TIMING1 0x11 +#define CH7024_IN_TIMING2 0x12 +#define CH7024_IN_TIMING3 0x13 +#define CH7024_IN_TIMING4 0x14 +#define CH7024_IN_TIMING5 0x15 +#define CH7024_IN_TIMING6 0x16 +#define CH7024_IN_TIMING7 0x17 +#define CH7024_IN_TIMING8 0x18 +#define CH7024_IN_TIMING9 0x19 +#define CH7024_IN_TIMING10 0x1A +#define CH7024_IN_TIMING11 0x1B +#define CH7024_ACIV 0x1C +#define CH7024_CLK_TREE 0x1D +#define CH7024_OUT_TIMING1 0x1E +#define CH7024_OUT_TIMING2 0x1F +#define CH7024_V_POS1 0x20 +#define CH7024_V_POS2 0x21 +#define CH7024_H_POS1 0x22 +#define CH7024_H_POS2 0x23 +#define CH7024_PCLK_A1 0x24 +#define CH7024_PCLK_A2 0x25 +#define CH7024_PCLK_A3 0x26 +#define CH7024_PCLK_A4 0x27 +#define CH7024_CLK_P1 0x28 +#define CH7024_CLK_P2 0x29 +#define CH7024_CLK_P3 0x2A +#define CH7024_CLK_N1 0x2B +#define CH7024_CLK_N2 0x2C +#define CH7024_CLK_N3 0x2D +#define CH7024_CLK_T 0x2E +#define CH7024_PLL1 0x2F +#define CH7024_PLL2 0x30 +#define CH7024_PLL3 0x31 +#define CH7024_SC_FREQ1 0x34 +#define CH7024_SC_FREQ2 0x35 +#define CH7024_SC_FREQ3 0x36 +#define CH7024_SC_FREQ4 0x37 +#define CH7024_DAC_TRIM 0x62 +#define CH7024_DATA_IO 0x63 +#define CH7024_ATT_DISP 0x7E + +/*! + * CH7024 register values + */ +/* video output formats */ +#define CH7024_VOS_NTSC_M 0x0 +#define CH7024_VOS_NTSC_J 0x1 +#define CH7024_VOS_NTSC_443 0x2 +#define CH7024_VOS_PAL_BDGHKI 0x3 +#define CH7024_VOS_PAL_M 0x4 +#define CH7024_VOS_PAL_N 0x5 +#define CH7024_VOS_PAL_NC 0x6 +#define CH7024_VOS_PAL_60 0x7 +/* crystal predefined */ +#define CH7024_XTAL_13MHZ 0x4 +#define CH7024_XTAL_26MHZ 0xB + +/* chip ID */ +#define CH7024_DEVICE_ID 0x45 + +/* clock source define */ +#define CLK_HIGH 0 +#define CLK_LOW 1 + +/* CH7024 presets structs */ +struct ch7024_clock { + u32 A; + u32 P; + u32 N; + u32 T; + u8 PLLN1; + u8 PLLN2; + u8 PLLN3; +}; + +struct ch7024_input_timing { + u32 HTI; + u32 VTI; + u32 HAI; + u32 VAI; + u32 HW; + u32 HO; + u32 VW; + u32 VO; + u32 VOS; +}; + +#define TVOUT_FMT_OFF 0 +#define TVOUT_FMT_NTSC 1 +#define TVOUT_FMT_PAL 2 + +static int enabled; /* enable power on or not */ +static int pm_status; /* status before suspend */ + +static struct i2c_client *ch7024_client; +static struct fb_info *ch7024_fbi; +static int ch7024_cur_mode; +static u32 detect_gpio; +static struct regulator *io_reg; +static struct regulator *core_reg; +static struct regulator *analog_reg; + +static void hp_detect_wq_handler(struct work_struct *); +DECLARE_DELAYED_WORK(ch7024_wq, hp_detect_wq_handler); + +static inline int ch7024_read_reg(u8 reg) +{ + return i2c_smbus_read_byte_data(ch7024_client, reg); +} + +static inline int ch7024_write_reg(u8 reg, u8 word) +{ + return i2c_smbus_write_byte_data(ch7024_client, reg, word); +} + +/** + * PAL B/D/G/H/K/I clock and timting structures + */ +static struct ch7024_clock ch7024_clk_pal = { + .A = 0x0, + .P = 0x36b00, + .N = 0x41eb00, + .T = 0x3f, + .PLLN1 = 0x0, + .PLLN2 = 0x1b, + .PLLN3 = 0x12, +}; + +static struct ch7024_input_timing ch7024_timing_pal = { + .HTI = 950, + .VTI = 560, + .HAI = 640, + .VAI = 480, + .HW = 60, + .HO = 250, + .VW = 40, + .VO = 40, + .VOS = CH7024_VOS_PAL_BDGHKI, +}; + +/** + * NTSC_M clock and timting structures + * TODO: change values to work well. + */ +static struct ch7024_clock ch7024_clk_ntsc = { + .A = 0x0, + .P = 0x2ac90, + .N = 0x36fc90, + .T = 0x3f, + .PLLN1 = 0x0, + .PLLN2 = 0x1b, + .PLLN3 = 0x12, +}; + +static struct ch7024_input_timing ch7024_timing_ntsc = { + .HTI = 801, + .VTI = 554, + .HAI = 640, + .VAI = 480, + .HW = 60, + .HO = 101, + .VW = 20, + .VO = 54, + .VOS = CH7024_VOS_NTSC_M, +}; + +static struct fb_videomode video_modes[] = { + { + /* NTSC TV output */ + "TV-NTSC", 60, 640, 480, 37594, + 0, 101, + 0, 54, + 60, 20, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* PAL TV output */ + "TV-PAL", 50, 640, 480, 37594, + 0, 250, + 0, 40, + 60, 40, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + 0,}, +}; + +/** + * ch7024_setup + * initial the CH7024 chipset by setting register + * @param: + * vos: output video format + * @return: + * 0 successful + * otherwise failed + */ +static int ch7024_setup(int vos) +{ + struct ch7024_input_timing *ch_timing; + struct ch7024_clock *ch_clk; +#ifdef DEBUG_CH7024 + int i, val; +#endif + + /* select output video format */ + if (vos == TVOUT_FMT_PAL) { + ch_timing = &ch7024_timing_pal; + ch_clk = &ch7024_clk_pal; + pr_debug("CH7024: change to PAL video\n"); + } else if (vos == TVOUT_FMT_NTSC) { + ch_timing = &ch7024_timing_ntsc; + ch_clk = &ch7024_clk_ntsc; + pr_debug("CH7024: change to NTSC video\n"); + } else { + + pr_debug("CH7024: no such video format.\n"); + return -EINVAL; + } + ch7024_write_reg(CH7024_RESET, 0x0); + ch7024_write_reg(CH7024_RESET, 0x3); + + ch7024_write_reg(CH7024_POWER, 0x0C); /* power on, disable DAC */ + ch7024_write_reg(CH7024_XTAL, CH7024_XTAL_26MHZ); + ch7024_write_reg(CH7024_SYNC, 0x0D); /* SLAVE mode, and TTL */ + ch7024_write_reg(CH7024_IDF1, 0x00); + ch7024_write_reg(CH7024_TVFILTER1, 0x00); /* set XCH=0 */ + ch7024_write_reg(CH7024_CLK_TREE, 0x9E); /* Invert input clk */ + + /* set input clock and divider */ + /* set PLL */ + ch7024_write_reg(CH7024_PLL1, ch_clk->PLLN1); + ch7024_write_reg(CH7024_PLL2, ch_clk->PLLN2); + ch7024_write_reg(CH7024_PLL3, ch_clk->PLLN3); + /* set A register */ + ch7024_write_reg(CH7024_PCLK_A1, (ch_clk->A >> 24) & 0xFF); + ch7024_write_reg(CH7024_PCLK_A2, (ch_clk->A >> 16) & 0xFF); + ch7024_write_reg(CH7024_PCLK_A3, (ch_clk->A >> 8) & 0xFF); + ch7024_write_reg(CH7024_PCLK_A4, ch_clk->A & 0xFF); + /* set P register */ + ch7024_write_reg(CH7024_CLK_P1, (ch_clk->P >> 16) & 0xFF); + ch7024_write_reg(CH7024_CLK_P2, (ch_clk->P >> 8) & 0xFF); + ch7024_write_reg(CH7024_CLK_P3, ch_clk->P & 0xFF); + /* set N register */ + ch7024_write_reg(CH7024_CLK_N1, (ch_clk->N >> 16) & 0xFF); + ch7024_write_reg(CH7024_CLK_N2, (ch_clk->N >> 8) & 0xFF); + ch7024_write_reg(CH7024_CLK_N3, ch_clk->N & 0xFF); + /* set T register */ + ch7024_write_reg(CH7024_CLK_T, ch_clk->T & 0xFF); + + /* set sub-carrier frequency generation method */ + ch7024_write_reg(CH7024_ACIV, 0x00); /* ACIV = 0, automatical SCF */ + /* TV out pattern and DAC switch */ + ch7024_write_reg(CH7024_OUT_FMT, (0x10 | ch_timing->VOS) & 0xFF); + + /* input settings */ + /* input format, RGB666 */ + ch7024_write_reg(CH7024_IDF2, 0x02); + /* HAI/HTI VAI */ + ch7024_write_reg(CH7024_IN_TIMING1, ((ch_timing->HTI >> 5) & 0x38) | + ((ch_timing->HAI >> 8) & 0x07)); + ch7024_write_reg(CH7024_IN_TIMING2, ch_timing->HAI & 0xFF); + ch7024_write_reg(CH7024_IN_TIMING8, ch_timing->VAI & 0xFF); + /* HTI VTI */ + ch7024_write_reg(CH7024_IN_TIMING3, ch_timing->HTI & 0xFF); + ch7024_write_reg(CH7024_IN_TIMING9, ch_timing->VTI & 0xFF); + /* HW/HO(h) VW */ + ch7024_write_reg(CH7024_IN_TIMING4, ((ch_timing->HW >> 5) & 0x18) | + ((ch_timing->HO >> 8) & 0x7)); + ch7024_write_reg(CH7024_IN_TIMING6, ch_timing->HW & 0xFF); + ch7024_write_reg(CH7024_IN_TIMING11, ch_timing->VW & 0x3F); + /* HO(l) VO/VAI/VTI */ + ch7024_write_reg(CH7024_IN_TIMING5, ch_timing->HO & 0xFF); + ch7024_write_reg(CH7024_IN_TIMING7, ((ch_timing->VO >> 4) & 0x30) | + ((ch_timing->VTI >> 6) & 0x0C) | + ((ch_timing->VAI >> 8) & 0x03)); + ch7024_write_reg(CH7024_IN_TIMING10, ch_timing->VO & 0xFF); + + /* adjust the brightness */ + ch7024_write_reg(CH7024_TVBRI, 0x90); + + ch7024_write_reg(CH7024_OUT_TIMING1, 0x4); + ch7024_write_reg(CH7024_OUT_TIMING2, 0xe0); + + if (vos == TVOUT_FMT_PAL) { + ch7024_write_reg(CH7024_V_POS1, 0x03); + ch7024_write_reg(CH7024_V_POS2, 0x7d); + } else { + ch7024_write_reg(CH7024_V_POS1, 0x02); + ch7024_write_reg(CH7024_V_POS2, 0x7b); + } + + ch7024_write_reg(CH7024_POWER, 0x00); + +#ifdef DEBUG_CH7024 + for (i = 0; i < CH7024_SC_FREQ4; i++) { + + val = ch7024_read_reg(i); + pr_debug("CH7024, reg[0x%x] = %x\n", i, val); + } +#endif + return 0; +} + +/** + * ch7024_enable + * Enable the ch7024 Power to begin TV encoder + */ +static int ch7024_enable(void) +{ + int en = enabled; + + if (!enabled) { + regulator_enable(core_reg); + regulator_enable(io_reg); + regulator_enable(analog_reg); + msleep(200); + enabled = 1; + ch7024_write_reg(CH7024_POWER, 0x00); + pr_debug("CH7024 power on.\n"); + } + return en; +} + +/** + * ch7024_disable + * Disable the ch7024 Power to stop TV encoder + */ +static void ch7024_disable(void) +{ + if (enabled) { + enabled = 0; + ch7024_write_reg(CH7024_POWER, 0x0D); + regulator_disable(analog_reg); + regulator_disable(io_reg); + regulator_disable(core_reg); + pr_debug("CH7024 power off.\n"); + } +} + +static int ch7024_detect(void) +{ + int en; + int detect = 0; + + if (gpio_get_value(detect_gpio) == 1) { + set_irq_type(ch7024_client->irq, IRQF_TRIGGER_FALLING); + + en = ch7024_enable(); + + ch7024_write_reg(CH7024_DAC_TRIM, 0xB4); + msleep(50); + detect = ch7024_read_reg(CH7024_ATT_DISP) & 0x3; + ch7024_write_reg(CH7024_DAC_TRIM, 0x34); + + if (!en) + ch7024_disable(); + } else { + set_irq_type(ch7024_client->irq, IRQF_TRIGGER_RISING); + } + dev_dbg(&ch7024_client->dev, "detect = %d\n", detect); + return (detect); +} + +static irqreturn_t hp_detect_handler(int irq, void *data) +{ + disable_irq(irq); + schedule_delayed_work(&ch7024_wq, 50); + + return IRQ_HANDLED; +} + +static void hp_detect_wq_handler(struct work_struct *work) +{ + int detect; + struct mxc_hw_event event = { HWE_PHONEJACK_PLUG, 0 }; + + detect = ch7024_detect(); + + enable_irq(ch7024_client->irq); + + sysfs_notify(&ch7024_client->dev.kobj, NULL, "headphone"); + + /* send hw event by netlink */ + event.args = detect; + hw_event_send(1, &event); +} + +int ch7024_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + + switch (val) { + case FB_EVENT_FB_REGISTERED: + if ((ch7024_fbi != NULL) || strcmp(fbi->fix.id, "DISP3 BG")) + break; + + ch7024_fbi = fbi; + fb_add_videomode(&video_modes[0], &ch7024_fbi->modelist); + fb_add_videomode(&video_modes[1], &ch7024_fbi->modelist); + break; + case FB_EVENT_MODE_CHANGE: + if (ch7024_fbi != fbi) + break; + + if (!fbi->mode) { + ch7024_disable(); + ch7024_cur_mode = TVOUT_FMT_OFF; + return 0; + } + + if (fb_mode_is_equal(fbi->mode, &video_modes[0])) { + ch7024_cur_mode = TVOUT_FMT_NTSC; + ch7024_enable(); + ch7024_setup(TVOUT_FMT_NTSC); + } else if (fb_mode_is_equal(fbi->mode, &video_modes[1])) { + ch7024_cur_mode = TVOUT_FMT_PAL; + ch7024_enable(); + ch7024_setup(TVOUT_FMT_PAL); + } else { + ch7024_disable(); + ch7024_cur_mode = TVOUT_FMT_OFF; + return 0; + } + break; + case FB_EVENT_BLANK: + if ((ch7024_fbi != fbi) || (ch7024_cur_mode == TVOUT_FMT_OFF)) + return 0; + + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + ch7024_enable(); + ch7024_setup(ch7024_cur_mode); + } else { + ch7024_disable(); + } + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = ch7024_fb_event, +}; + +static ssize_t show_headphone(struct device_driver *dev, char *buf) +{ + int detect; + + detect = ch7024_detect(); + + if (detect == 0) { + strcpy(buf, "none\n"); + } else if (detect == 1) { + strcpy(buf, "cvbs\n"); + } else { + strcpy(buf, "headset\n"); + } + + return strlen(buf); +} + +DRIVER_ATTR(headphone, 0644, show_headphone, NULL); + +static ssize_t show_brightness(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVBRI); + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_brightness(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int brightness = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + if (brightness > 255) + brightness = 255; + + ch7024_write_reg(CH7024_TVBRI, brightness); + + return count; +} + +DRIVER_ATTR(brightness, 0644, show_brightness, store_brightness); + +static ssize_t show_contrast(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVCTA); + + reg *= 2; /* Scale to 0 - 255 */ + + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_contrast(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int contrast = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + contrast /= 2; + if (contrast > 127) + contrast = 127; + + ch7024_write_reg(CH7024_TVCTA, contrast); + + return count; +} + +DRIVER_ATTR(contrast, 0644, show_contrast, store_contrast); + +static ssize_t show_hue(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVHUE); + + reg *= 2; /* Scale to 0 - 255 */ + + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_hue(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int hue = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + hue /= 2; + if (hue > 127) + hue = 127; + + ch7024_write_reg(CH7024_TVHUE, hue); + + return count; +} + +DRIVER_ATTR(hue, 0644, show_hue, store_hue); + +static ssize_t show_saturation(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVSAT); + + reg *= 2; /* Scale to 0 - 255 */ + + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_saturation(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int saturation = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + saturation /= 2; + if (saturation > 127) + saturation = 127; + + ch7024_write_reg(CH7024_TVSAT, saturation); + + return count; +} + +DRIVER_ATTR(saturation, 0644, show_saturation, store_saturation); + +static ssize_t show_sharpness(struct device_driver *dev, char *buf) +{ + u32 reg; + reg = ch7024_read_reg(CH7024_TVSHARP); + + reg *= 32; /* Scale to 0 - 255 */ + + return snprintf(buf, PAGE_SIZE, "%u", reg); +} + +static ssize_t store_sharpness(struct device_driver *dev, const char *buf, + size_t count) +{ + char *endp; + int sharpness = simple_strtoul(buf, &endp, 0); + size_t size = endp - buf; + + if (*endp && isspace(*endp)) + size++; + if (size != count) + return -EINVAL; + + sharpness /= 32; /* Scale to 0 - 7 */ + if (sharpness > 7) + sharpness = 7; + + ch7024_write_reg(CH7024_TVSHARP, sharpness); + + return count; +} + +DRIVER_ATTR(sharpness, 0644, show_sharpness, store_sharpness); + +static int ch7024_probe(struct i2c_client *client, const struct i2c_device_id *dev_id) +{ + int ret, i; + u32 id; + u32 irqtype; + struct mxc_tvout_platform_data *plat_data = client->dev.platform_data; + + ch7024_client = client; + + io_reg = regulator_get(&client->dev, plat_data->io_reg); + core_reg = regulator_get(&client->dev, plat_data->core_reg); + analog_reg = regulator_get(&client->dev, plat_data->analog_reg); + + regulator_enable(io_reg); + regulator_enable(core_reg); + regulator_enable(analog_reg); + msleep(200); + + id = ch7024_read_reg(CH7024_DEVID); + + regulator_disable(core_reg); + regulator_disable(io_reg); + regulator_disable(analog_reg); + + if (id < 0 || id != CH7024_DEVICE_ID) { + printk(KERN_ERR + "ch7024: TV encoder not present: id = %x\n", id); + return -ENODEV; + } + printk(KERN_ERR "ch7024: TV encoder present: id = %x\n", id); + + detect_gpio = plat_data->detect_line; + + if (client->irq > 0) { + if (ch7024_detect() == 0) + irqtype = IRQF_TRIGGER_RISING; + else + irqtype = IRQF_TRIGGER_FALLING; + + ret = request_irq(client->irq, hp_detect_handler, irqtype, + client->name, client); + if (ret < 0) + goto err0; + + ret = driver_create_file(&client->driver->driver, + &driver_attr_headphone); + if (ret < 0) + goto err1; + } + + ret = driver_create_file(&client->driver->driver, + &driver_attr_brightness); + if (ret) + goto err2; + + ret = driver_create_file(&client->driver->driver, + &driver_attr_contrast); + if (ret) + goto err3; + ret = driver_create_file(&client->driver->driver, &driver_attr_hue); + if (ret) + goto err4; + ret = driver_create_file(&client->driver->driver, + &driver_attr_saturation); + if (ret) + goto err5; + ret = driver_create_file(&client->driver->driver, + &driver_attr_sharpness); + if (ret) + goto err6; + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "DISP3 BG") == 0) { + ch7024_fbi = registered_fb[i]; + break; + } + } + if (ch7024_fbi != NULL) { + fb_add_videomode(&video_modes[0], &ch7024_fbi->modelist); + fb_add_videomode(&video_modes[1], &ch7024_fbi->modelist); + } + fb_register_client(&nb); + + return 0; + err6: + driver_remove_file(&client->driver->driver, &driver_attr_saturation); + err5: + driver_remove_file(&client->driver->driver, &driver_attr_hue); + err4: + driver_remove_file(&client->driver->driver, &driver_attr_contrast); + err3: + driver_remove_file(&client->driver->driver, &driver_attr_brightness); + err2: + driver_remove_file(&client->driver->driver, &driver_attr_headphone); + err1: + free_irq(client->irq, client); + err0: + return ret; +} + +static int ch7024_remove(struct i2c_client *client) +{ + free_irq(client->irq, client); + + regulator_put(io_reg); + regulator_put(core_reg); + regulator_put(analog_reg); + + driver_remove_file(&client->driver->driver, &driver_attr_headphone); + driver_remove_file(&client->driver->driver, &driver_attr_brightness); + driver_remove_file(&client->driver->driver, &driver_attr_contrast); + driver_remove_file(&client->driver->driver, &driver_attr_hue); + driver_remove_file(&client->driver->driver, &driver_attr_saturation); + driver_remove_file(&client->driver->driver, &driver_attr_sharpness); + + fb_unregister_client(&nb); + + ch7024_client = 0; + + return 0; +} + +#ifdef CONFIG_PM +/*! + * PM suspend/resume routing + */ +static int ch7024_suspend(struct i2c_client *client, pm_message_t state) +{ + pr_debug("Ch7024 suspend routing..\n"); + if (enabled) { + ch7024_disable(); + pm_status = 1; + } else { + pm_status = 0; + } + return 0; +} + +static int ch7024_resume(struct i2c_client *client) +{ + pr_debug("Ch7024 resume routing..\n"); + if (pm_status) { + ch7024_enable(); + ch7024_setup(ch7024_cur_mode); + } + return 0; +} +#else +#define ch7024_suspend NULL +#define ch7024_resume NULL +#endif + +static const struct i2c_device_id ch7024_id[] = { + { "ch7024", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ch7024_id); + +static struct i2c_driver ch7024_driver = { + .driver = { + .name = "ch7024", + }, + .probe = ch7024_probe, + .remove = ch7024_remove, + .suspend = ch7024_suspend, + .resume = ch7024_resume, + .id_table = ch7024_id, +}; + +static int __init ch7024_init(void) +{ + return i2c_add_driver(&ch7024_driver); +} + +static void __exit ch7024_exit(void) +{ + i2c_del_driver(&ch7024_driver); +} + +module_init(ch7024_init); +module_exit(ch7024_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("CH7024 TV encoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mx2fb.c b/drivers/video/mxc/mx2fb.c new file mode 100644 index 000000000000..4921f96be00a --- /dev/null +++ b/drivers/video/mxc/mx2fb.c @@ -0,0 +1,1347 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer_MX27 Framebuffer Driver for MX27. + */ + +/*! + * @file mx2fb.c + * + * @brief Frame buffer driver for MX27 ADS. + * + * @ingroup Framebuffer_MX27 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/mxcfb.h> +#include <linux/uaccess.h> +#include <mach/hardware.h> + +#include "mx2fb.h" + +#define MX2FB_TYPE_BG 0 +#define MX2FB_TYPE_GW 1 + +extern void gpio_lcdc_active(void); +extern void gpio_lcdc_inactive(void); +extern void board_power_lcd(int on); + +static char *fb_mode = 0; +static int fb_enabled = 0; +static unsigned long default_bpp = 16; +static ATOMIC_NOTIFIER_HEAD(mx2fb_notifier_list); +static struct clk *lcdc_clk; +/*! + * @brief Structure containing the MX2 specific framebuffer information. + */ +struct mx2fb_info { + int type; + char *id; + int registered; + int blank; + unsigned long pseudo_palette[16]; +}; + +/* Framebuffer APIs */ +static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info); +static int mx2fb_set_par(struct fb_info *info); +static int mx2fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info); +static int mx2fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); +static int mx2fb_blank(int blank_mode, struct fb_info *info); +static int mx2fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg); + +/* Driver entries */ +int __init mx2fb_init(void); +void __exit mx2fb_exit(void); +#ifndef MODULE +static int __init mx2fb_setup(char *); +#endif + +/* Internal functions */ +static int __init _init_fbinfo(struct fb_info *info, + struct platform_device *pdev); +static int __init _install_fb(struct fb_info *info, + struct platform_device *pdev); +static void __exit _uninstall_fb(struct fb_info *info); +static int _map_video_memory(struct fb_info *info); +static void _unmap_video_memory(struct fb_info *info); +static void _set_fix(struct fb_info *info); +static void _enable_lcdc(struct fb_info *info); +static void _disable_lcdc(struct fb_info *info); +static void _enable_graphic_window(struct fb_info *info); +static void _disable_graphic_window(struct fb_info *info); +static void _update_lcdc(struct fb_info *info); +static void _request_irq(void); +static void _free_irq(void); + +#ifdef CONFIG_PM +static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state); +static int mx2fb_resume(struct platform_device *pdev); +#else +#define mx2fb_suspend 0 +#define mx2fb_resume 0 +#endif + +static int mx2fb_probe(struct platform_device *pdev); + +#ifdef CONFIG_FB_MXC_TVOUT +#include <linux/video_encoder.h> +/* + * FIXME: VGA mode is not defined by video_encoder.h + * while FS453 supports VGA output. + */ +#ifndef VIDEO_ENCODER_VGA +#define VIDEO_ENCODER_VGA 32 +#endif + +#define MODE_PAL "TV-PAL" +#define MODE_NTSC "TV-NTSC" +#define MODE_VGA "TV-VGA" + +extern int fs453_ioctl(unsigned int cmd, void *arg); +#endif + +struct mx2fb_info mx2fbi_bg = { + .type = MX2FB_TYPE_BG, + .id = "DISP0 BG", + .registered = 0, +}; + +static struct mx2fb_info mx2fbi_gw = { + .type = MX2FB_TYPE_GW, + .id = "DISP0 FG", + .registered = 0, +}; + +/*! Current graphic window information */ +static struct fb_gwinfo g_gwinfo = { + .enabled = 0, + .alpha_value = 255, + .ck_enabled = 0, + .ck_red = 0, + .ck_green = 0, + .ck_blue = 0, + .xpos = 0, + .ypos = 0, +}; + +/*! + * @brief Framebuffer information structures. + * There are up to 3 framebuffers: background, TVout, and graphic window. + * If graphic window is configured, it must be the last framebuffer. + */ +static struct fb_info mx2fb_info[] = { + {.par = &mx2fbi_bg}, + {.par = &mx2fbi_gw}, +}; + +/*! + * @brief This structure contains pointers to the power management + * callback functions. + */ +static struct platform_driver mx2fb_driver = { + .driver = { + .name = "mxc_sdc_fb", + .owner = THIS_MODULE, + .bus = &platform_bus_type, + }, + .probe = mx2fb_probe, + .suspend = mx2fb_suspend, + .resume = mx2fb_resume, +}; + +/*! + * @brief Framebuffer file operations + */ +static struct fb_ops mx2fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mx2fb_check_var, + .fb_set_par = mx2fb_set_par, + .fb_setcolreg = mx2fb_setcolreg, + .fb_blank = mx2fb_blank, + .fb_pan_display = mx2fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + //.fb_cursor = soft_cursor, + .fb_ioctl = mx2fb_ioctl, +}; + +/*! + * @brief Validates a var passed in. + * + * @param var Frame buffer variable screen structure + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Negative errno on error, or zero on success. + * + * Checks to see if the hardware supports the state requested by var passed + * in. This function does not alter the hardware state! If the var passed in + * is slightly off by what the hardware can support then we alter the var + * PASSED in to what we can do. If the hardware doesn't support mode change + * a -EINVAL will be returned by the upper layers. + * + */ +static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + unsigned long htotal, vtotal; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if (var->xoffset < 0) + var->xoffset = 0; + + if (var->yoffset < 0) + var->yoffset = 0; + + if (var->xoffset + info->var.xres > info->var.xres_virtual) + var->xoffset = info->var.xres_virtual - info->var.xres; + + if (var->yoffset + info->var.yres > info->var.yres_virtual) + var->yoffset = info->var.yres_virtual - info->var.yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + if (var->pixclock < 1000) { + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + var->pixclock = (vtotal * htotal * 6UL) / 100UL; + var->pixclock = KHZ2PICOS(var->pixclock); + dev_dbg(info->device, + "pixclock set for 60Hz refresh = %u ps\n", + var->pixclock); + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + /* Copy nonstd field to/from sync for fbset usage */ + var->sync |= var->nonstd; + var->nonstd |= var->sync; + + return 0; +} + +/*! + * @brief Alters the hardware state. + * + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Zero on success others on failure + * + * Using the fb_var_screeninfo in fb_info we set the resolution of this + * particular framebuffer. This function alters the fb_fix_screeninfo stored + * in fb_info. It doesn't not alter var in fb_info since we are using that + * data. This means we depend on the data in var inside fb_info to be + * supported by the hardware. mx2fb_check_var is always called before + * mx2fb_set_par to ensure this. + */ +static int mx2fb_set_par(struct fb_info *info) +{ + unsigned long len; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + _set_fix(info); + + len = info->var.yres_virtual * info->fix.line_length; + if (len > info->fix.smem_len) { + if (info->fix.smem_start) + _unmap_video_memory(info); + + /* Memory allocation for framebuffer */ + if (_map_video_memory(info)) { + dev_err(info->device, "Unable to allocate fb memory\n"); + return -ENOMEM; + } + } + + _update_lcdc(info); + if (info->fbops->fb_blank) + info->fbops->fb_blank(mx2fbi->blank, info); + + return 0; +} + +/*! + * @brief Sets a color register. + * + * @param regno Which register in the CLUT we are programming + * @param red The red value which can be up to 16 bits wide + * @param green The green value which can be up to 16 bits wide + * @param blue The blue value which can be up to 16 bits wide. + * @param transp If supported the alpha value which can be up to + * 16 bits wide. + * @param info Frame buffer info structure + * + * @return Negative errno on error, or zero on success. + * + * Set a single color register. The values supplied have a 16 bit magnitude + * which needs to be scaled in this function for the hardware. Things to take + * into consideration are how many color registers, if any, are supported with + * the current color visual. With truecolor mode no color palettes are + * supported. Here a psuedo palette is created which we store the value in + * pseudo_palette in struct fb_info. For pseudocolor mode we have a limited + * color palette. + */ +static int mx2fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = info->pseudo_palette; + u32 v; + +#define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16) + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); +#undef CNVT_TOHW + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + + pal[regno] = v; + ret = 0; + } + break; + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/*! + * @brief Pans the display. + * + * @param var Frame buffer variable screen structure + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Negative errno on error, or zero on success. + * + * Pan (or wrap, depending on the `vmode' field) the display using the + * 'xoffset' and 'yoffset' fields of the 'var' structure. If the values + * don't fit, return -EINVAL. + */ +static int mx2fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if ((info->var.xoffset == var->xoffset) && + (info->var.yoffset == var->yoffset)) { + return 0; /* No change, do nothing */ + } + + if (var->xoffset < 0 || var->yoffset < 0 + || var->xoffset + info->var.xres > info->var.xres_virtual + || var->yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + + _update_lcdc(info); + + if (var->vmode & FB_VMODE_YWRAP) { + info->var.vmode |= FB_VMODE_YWRAP; + } else { + info->var.vmode &= ~FB_VMODE_YWRAP; + } + + return 0; +} + +/*! + * @brief Blanks the display. + * + * @param blank_mode The blank mode we want. + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Negative errno on error, or zero on success. + * + * Blank the screen if blank_mode != 0, else unblank. Return 0 if blanking + * succeeded, != 0 if un-/blanking failed. + * blank_mode == 2: suspend vsync + * blank_mode == 3: suspend hsync + * blank_mode == 4: powerdown + */ +static int mx2fb_blank(int blank_mode, struct fb_info *info) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + dev_dbg(info->device, "blank mode = %d\n", blank_mode); + + mx2fbi->blank = blank_mode; + + switch (blank_mode) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + _disable_lcdc(info); + break; + case FB_BLANK_UNBLANK: + _enable_lcdc(info); + break; + } + + return 0; +} + +/*! + * @brief Ioctl function to support customized ioctl operations. + * + * @param info Framebuffer structure that represents a single frame buffer + * @param cmd The command number + * @param arg Argument which depends on cmd + * + * @return Negative errno on error, or zero on success. + */ +static int mx2fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + struct mx2fb_gbl_alpha ga; + struct mx2fb_color_key ck; + + switch (cmd) { + case MX2FB_SET_GBL_ALPHA: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* set graphic window information */ + if (copy_from_user((void *)&ga, (void *)arg, sizeof(ga))) + return -EFAULT; + + g_gwinfo.alpha_value = ga.alpha; + + if (g_gwinfo.enabled) + _enable_graphic_window(info); + else + _disable_graphic_window(info); + break; + case MX2FB_SET_CLR_KEY: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* set graphic window information */ + if (copy_from_user((void *)&ck, (void *)arg, sizeof(ck))) + return -EFAULT; + + g_gwinfo.ck_enabled = ck.enable; + g_gwinfo.ck_red = (ck.color_key & 0x003F0000) >> 16; + g_gwinfo.ck_green = (ck.color_key & 0x00003F00) >> 8; + g_gwinfo.ck_blue = ck.color_key & 0x0000003F; + + if (g_gwinfo.enabled) + _enable_graphic_window(info); + else + _disable_graphic_window(info); + break; + case FBIOGET_GWINFO: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* get graphic window information */ + if (copy_to_user((void *)arg, (void *)&g_gwinfo, + sizeof(g_gwinfo))) + return -EFAULT; + break; + case FBIOPUT_GWINFO: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* set graphic window information */ + if (copy_from_user((void *)&g_gwinfo, (void *)arg, + sizeof(g_gwinfo))) + return -EFAULT; + + if (g_gwinfo.enabled) + _enable_graphic_window(info); + else + _disable_graphic_window(info); + break; +#ifdef CONFIG_FB_MXC_TVOUT + case ENCODER_GET_CAPABILITIES:{ + int ret; + struct video_encoder_capability cap; + + if (mx2fbi->type != MX2FB_TYPE_BG) + return -ENODEV; + + ret = fs453_ioctl(cmd, &cap); + if (ret) + return ret; + + if (copy_to_user((void *)arg, &cap, sizeof(cap))) + return -EFAULT; + break; + } + case ENCODER_SET_NORM:{ + int ret; + unsigned long mode; + char *smode; + struct fb_var_screeninfo var; + + if (mx2fbi->type != MX2FB_TYPE_BG) + return -ENODEV; + + if (copy_from_user(&mode, (void *)arg, sizeof(mode))) + return -EFAULT; + if ((ret = fs453_ioctl(cmd, &mode))) + return ret; + + if (mode == VIDEO_ENCODER_PAL) + smode = MODE_PAL; + else if (mode == VIDEO_ENCODER_NTSC) + smode = MODE_NTSC; + else + smode = MODE_VGA; + + var = info->var; + var.nonstd = 0; + ret = fb_find_mode(&var, info, smode, mxcfb_modedb, + mxcfb_modedb_sz, NULL, default_bpp); + if ((ret != 1) && (ret != 2)) /* specified mode not found */ + return -ENODEV; + + info->var = var; + fb_mode = smode; + return mx2fb_set_par(info); + } + case ENCODER_SET_INPUT: + case ENCODER_SET_OUTPUT: + case ENCODER_ENABLE_OUTPUT:{ + unsigned long varg; + + if (mx2fbi->type != MX2FB_TYPE_BG) + return -ENODEV; + + if (copy_from_user(&varg, (void *)arg, sizeof(varg))) + return -EFAULT; + return fs453_ioctl(cmd, &varg); + } +#endif + default: + dev_dbg(info->device, "Unknown ioctl command (0x%08X)\n", cmd); + return -EINVAL; + } + + return 0; +} + +/*! + * @brief Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + * @return Negative errno on error, or zero on success. + */ +static void _set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + strncpy(fix->id, mx2fbi->id, strlen(mx2fbi->id)); + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; +} + +/*! + * @brief Initialize framebuffer information structure. + * + * @param info framebuffer information pointer + * @param pdev pointer to struct device + * @return Negative errno on error, or zero on success. + */ +static int __init _init_fbinfo(struct fb_info *info, + struct platform_device *pdev) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + info->device = &pdev->dev; + info->var.activate = FB_ACTIVATE_NOW; + info->fbops = &mx2fb_ops; + info->flags = FBINFO_FLAG_DEFAULT; + info->pseudo_palette = &mx2fbi->pseudo_palette; + + /* Allocate colormap */ + fb_alloc_cmap(&info->cmap, 16, 0); + + return 0; +} + +/*! + * @brief Install framebuffer into the system. + * + * @param info framebuffer information pointer + * @param pdev pointer to struct device + * @return Negative errno on error, or zero on success. + */ +static int __init _install_fb(struct fb_info *info, + struct platform_device *pdev) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (_init_fbinfo(info, pdev)) + return -EINVAL; + + if (fb_mode == 0) + fb_mode = pdev->dev.platform_data; + + if (!fb_find_mode(&info->var, info, fb_mode, mxcfb_modedb, + mxcfb_modedb_sz, NULL, default_bpp)) { + fb_dealloc_cmap(&info->cmap); + return -EBUSY; + } + + /* Default Y virtual size is 2x panel size */ + /* info->var.yres_virtual = info->var.yres << 1; */ + + if (mx2fbi->type == MX2FB_TYPE_GW) + mx2fbi->blank = FB_BLANK_NORMAL; + else + mx2fbi->blank = FB_BLANK_UNBLANK; + + if (mx2fb_set_par(info)) { + fb_dealloc_cmap(&info->cmap); + return -EINVAL; + } + + if (register_framebuffer(info) < 0) { + _unmap_video_memory(info); + fb_dealloc_cmap(&info->cmap); + return -EINVAL; + } + + mx2fbi->registered = 1; + dev_info(info->device, "fb%d: %s fb device registered successfully.\n", + info->node, info->fix.id); + + return 0; +} + +/*! + * @brief Uninstall framebuffer from the system. + * + * @param info framebuffer information pointer + */ +static void __exit _uninstall_fb(struct fb_info *info) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (!mx2fbi->registered) + return; + + unregister_framebuffer(info); + _unmap_video_memory(info); + if (&info->cmap) + fb_dealloc_cmap(&info->cmap); + + mx2fbi->registered = 0; +} + +/*! + * @brief Allocate memory for framebuffer. + * + * @param info framebuffer information pointer + * @return Negative errno on error, or zero on success. + */ +static int _map_video_memory(struct fb_info *info) +{ + info->fix.smem_len = info->fix.line_length * info->var.yres_virtual; + info->screen_base = dma_alloc_coherent(0, + info->fix.smem_len, + (dma_addr_t *) & info->fix. + smem_start, + GFP_DMA | GFP_KERNEL); + + if (info->screen_base == 0) { + dev_err(info->device, "Unable to allocate fb memory\n"); + return -EBUSY; + } + dev_dbg(info->device, "Allocated fb @ paddr=0x%08lX, size=%d.\n", + info->fix.smem_start, info->fix.smem_len); + + info->screen_size = info->fix.smem_len; + + /* Clear the screen */ + memset((char *)info->screen_base, 0, info->fix.smem_len); + + return 0; +} + +/*! + * @brief Release memory for framebuffer. + * @param info framebuffer information pointer + */ +static void _unmap_video_memory(struct fb_info *info) +{ + dma_free_coherent(0, info->fix.smem_len, info->screen_base, + (dma_addr_t) info->fix.smem_start); + + info->screen_base = 0; + info->fix.smem_start = 0; + info->fix.smem_len = 0; +} + +/*! + * @brief Enable LCD controller. + * @param info framebuffer information pointer + */ +static void _enable_lcdc(struct fb_info *info) +{ + static int first_enable = 1; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + /* + * Graphic window can only be enabled while the HCLK to the LCDC + * is disabled. Once enabled it can subsequently be disabled and + * enabled without turning off the HCLK. + * The graphic window is enabled and then disabled here. So next + * time to enable graphic window the HCLK to LCDC does not need + * to be disabled, and the flicker (due to disabling of HCLK to + * LCDC) is avoided. + */ + if (first_enable) { + _enable_graphic_window(info); + _disable_graphic_window(info); + first_enable = 0; + } + + if (mx2fbi->type == MX2FB_TYPE_GW) + _enable_graphic_window(info); + else if (!fb_enabled) { + clk_enable(lcdc_clk); + gpio_lcdc_active(); + board_power_lcd(1); + fb_enabled++; +#ifdef CONFIG_FB_MXC_TVOUT + if (fb_mode) { + unsigned long mode = 0; + + if (strcmp(fb_mode, MODE_VGA) == 0) + mode = VIDEO_ENCODER_VGA; + else if (strcmp(fb_mode, MODE_NTSC) == 0) + mode = VIDEO_ENCODER_NTSC; + else if (strcmp(fb_mode, MODE_PAL) == 0) + mode = VIDEO_ENCODER_PAL; + if (mode) + fs453_ioctl(ENCODER_SET_NORM, &mode); + } +#endif + } +} + +/*! + * @brief Disable LCD controller. + * @param info framebuffer information pointer + */ +static void _disable_lcdc(struct fb_info *info) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (mx2fbi->type == MX2FB_TYPE_GW) + _disable_graphic_window(info); + else { + if (fb_enabled) { + gpio_lcdc_inactive(); + board_power_lcd(0); + clk_disable(lcdc_clk); + fb_enabled = 0; + } +#ifdef CONFIG_FB_MXC_TVOUT + if (fb_mode) { + int enable = 0; + + if ((strcmp(fb_mode, MODE_VGA) == 0) + || (strcmp(fb_mode, MODE_NTSC) == 0) + || (strcmp(fb_mode, MODE_PAL) == 0)) + fs453_ioctl(ENCODER_ENABLE_OUTPUT, &enable); + } +#endif + } +} + +/*! + * @brief Enable graphic window. + * @param info framebuffer information pointer + */ +static void _enable_graphic_window(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + + g_gwinfo.enabled = 1; + + g_gwinfo.base = (var->yoffset * var->xres_virtual + var->xoffset); + g_gwinfo.base *= (var->bits_per_pixel) / 8; + g_gwinfo.base += info->fix.smem_start; + + g_gwinfo.xres = var->xres; + g_gwinfo.yres = var->yres; + g_gwinfo.xres_virtual = var->xres_virtual; + + mx2_gw_set(&g_gwinfo); +} + +/*! + * @brief Disable graphic window. + * @param info framebuffer information pointer + */ +static void _disable_graphic_window(struct fb_info *info) +{ + unsigned long i = 0; + + g_gwinfo.enabled = 0; + + /* + * Set alpha value to zero and reduce gw size, otherwise the graphic + * window will not be able to be enabled again. + */ + __raw_writel(__raw_readl(LCDC_REG(LCDC_LGWCR)) & 0x00FFFFFF, + LCDC_REG(LCDC_LGWCR)); + __raw_writel(((16 >> 4) << 20) + 16, LCDC_REG(LCDC_LGWSR)); + while (i < 1000) + i++; + + /* Now disable graphic window */ + __raw_writel(__raw_readl(LCDC_REG(LCDC_LGWCR)) & ~0x00400000, + LCDC_REG(LCDC_LGWCR)); + + dev_dbg(info->device, "Graphic window disabled.\n"); +} + +/*! + * @brief Setup graphic window properties. + * @param gwinfo graphic window information pointer + */ +void mx2_gw_set(struct fb_gwinfo *gwinfo) +{ + int width, height, xpos, ypos; + int width_bg, height_bg; + unsigned long lgwcr = 0x00400000; /* Graphic window control register */ + + if (!gwinfo->enabled) { + _disable_graphic_window(0); + return; + } + + /* Graphic window start address register */ + __raw_writel(gwinfo->base, LCDC_REG(LCDC_LGWSAR)); + + /* + * The graphic window width, height, x position and y position + * must be synced up width the background window, otherwise there + * may be flickering. + */ + width_bg = (__raw_readl(LCDC_REG(LCDC_LSR)) & 0x03F00000) >> 16; + height_bg = __raw_readl(LCDC_REG(LCDC_LSR)) & 0x000003FF; + + width = (gwinfo->xres > width_bg) ? width_bg : gwinfo->xres; + height = (gwinfo->yres > height_bg) ? height_bg : gwinfo->yres; + + xpos = gwinfo->xpos; + ypos = gwinfo->ypos; + + if (xpos + width > width_bg) + xpos = width_bg - width; + if (ypos + height > height_bg) + ypos = height_bg - height; + + /* Graphic window size register */ + __raw_writel(((width >> 4) << 20) + height, LCDC_REG(LCDC_LGWSR)); + + /* Graphic window virtual page width register */ + __raw_writel(gwinfo->xres_virtual >> 1, LCDC_REG(LCDC_LGWVPWR)); + + /* Graphic window position register */ + __raw_writel(((xpos & 0x000003FF) << 16) | (ypos & 0x000003FF), + LCDC_REG(LCDC_LGWPR)); + + /* Graphic window panning offset register */ + __raw_writel(0, LCDC_REG(LCDC_LGWPOR)); + + /* Graphic window DMA control register */ + if (cpu_is_mx27_rev(CHIP_REV_2_0) > 0) + __raw_writel(0x00040060, LCDC_REG(LCDC_LGWDCR)); + else + __raw_writel(0x00020010, LCDC_REG(LCDC_LGWDCR)); + + /* Graphic window control register */ + lgwcr |= (gwinfo->alpha_value & 0x000000FF) << 24; + lgwcr |= gwinfo->ck_enabled ? 0x00800000 : 0; + lgwcr |= gwinfo->vs_reversed ? 0x00200000 : 0; + + /* + * Color keying value + * Todo: assume always use RGB565 + */ + lgwcr |= (gwinfo->ck_red & 0x0000003F) << 12; + lgwcr |= (gwinfo->ck_green & 0x0000003F) << 6; + lgwcr |= gwinfo->ck_blue & 0x0000003F; + + __raw_writel(lgwcr, LCDC_REG(LCDC_LGWCR)); + + pr_debug("Graphic window enabled.\n"); +} + +/*! + * @brief Update LCDC registers + * @param info framebuffer information pointer + */ +static void _update_lcdc(struct fb_info *info) +{ + unsigned long base; + unsigned long perclk, pcd, pcr; + struct fb_var_screeninfo *var = &info->var; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (mx2fbi->type == MX2FB_TYPE_GW) { + _enable_graphic_window(info); + return; + } + + base = (var->yoffset * var->xres_virtual + var->xoffset); + base *= (var->bits_per_pixel) / 8; + base += info->fix.smem_start; + + /* Screen start address register */ + __raw_writel(base, LCDC_REG(LCDC_LSSAR)); + + /* Size register */ + dev_dbg(info->device, "xres = %d, yres = %d\n", + info->var.xres, info->var.yres); + __raw_writel(((info->var.xres >> 4) << 20) + info->var.yres, + LCDC_REG(LCDC_LSR)); + + /* Virtual page width register */ + __raw_writel(info->var.xres_virtual >> 1, LCDC_REG(LCDC_LVPWR)); + + /* To setup LCDC pixel clock */ + perclk = clk_round_rate(lcdc_clk, 134000000); + if (clk_set_rate(lcdc_clk, perclk)) { + printk(KERN_INFO "mx2fb: Unable to set clock to %lu\n", perclk); + perclk = clk_get_rate(lcdc_clk); + } + + /* Calculate pixel clock divider, and round to the nearest integer */ + pcd = (perclk * 8 / (PICOS2KHZ(var->pixclock) * 1000UL) + 4) / 8; + if (--pcd > 0x3F) + pcd = 0x3F; + + /* Panel configuration register */ + pcr = 0xFA008B80 | pcd; + pcr |= (var->sync & FB_SYNC_CLK_LAT_FALL) ? 0x00200000 : 0; + pcr |= (var->sync & FB_SYNC_DATA_INVERT) ? 0x01000000 : 0; + pcr |= (var->sync & FB_SYNC_SHARP_MODE) ? 0x00000040 : 0; + pcr |= (var->sync & FB_SYNC_OE_LOW_ACT) ? 0x00100000 : 0; + __raw_writel(pcr, LCDC_REG(LCDC_LPCR)); + + /* Horizontal and vertical configuration register */ + __raw_writel(((var->hsync_len - 1) << 26) + + ((var->right_margin - 1) << 8) + + (var->left_margin - 3), LCDC_REG(LCDC_LHCR)); + __raw_writel((var->vsync_len << 26) + + (var->lower_margin << 8) + + var->upper_margin, LCDC_REG(LCDC_LVCR)); + + /* Sharp configuration register */ + __raw_writel(0x00120300, LCDC_REG(LCDC_LSCR)); + + /* Refresh mode control reigster */ + __raw_writel(0x00000000, LCDC_REG(LCDC_LRMCR)); + + /* DMA control register */ + if (cpu_is_mx27_rev(CHIP_REV_2_0) > 0) + __raw_writel(0x00040060, LCDC_REG(LCDC_LDCR)); + else + __raw_writel(0x00020010, LCDC_REG(LCDC_LDCR)); +} + +/*! + * @brief Set LCD brightness + * @param level brightness level + */ +void mx2fb_set_brightness(uint8_t level) +{ + /* Set LCDC PWM contract control register */ + __raw_writel(0x00A90300 | level, LCDC_REG(LCDC_LPCCR)); +} + +EXPORT_SYMBOL(mx2fb_set_brightness); + +/* + * @brief LCDC interrupt handler + */ +static irqreturn_t mx2fb_isr(int irq, void *dev_id) +{ + struct fb_event event; + unsigned long status = __raw_readl(LCDC_REG(LCDC_LISR)); + + if (status & MX2FB_INT_EOF) { + event.info = &mx2fb_info[0]; + atomic_notifier_call_chain(&mx2fb_notifier_list, + FB_EVENT_MXC_EOF, &event); + } + + if (status & MX2FB_INT_GW_EOF) { + event.info = &mx2fb_info[1]; + atomic_notifier_call_chain(&mx2fb_notifier_list, + FB_EVENT_MXC_EOF, &event); + } + + return IRQ_HANDLED; +} + +/*! + * @brief Config and request LCDC interrupt + */ +static void _request_irq(void) +{ + unsigned long status; + unsigned long flags; + + /* Read to clear the status */ + status = __raw_readl(LCDC_REG(LCDC_LISR)); + + if (request_irq(MXC_INT_LCDC, mx2fb_isr, 0, "LCDC", 0)) + pr_info("Request LCDC IRQ failed.\n"); + else { + spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); + + /* Enable interrupt in case client has registered */ + if (mx2fb_notifier_list.head != NULL) { + unsigned long status; + unsigned long ints = MX2FB_INT_EOF; + + ints |= MX2FB_INT_GW_EOF; + + /* Read to clear the status */ + status = __raw_readl(LCDC_REG(LCDC_LISR)); + + /* Configure interrupt condition for EOF */ + __raw_writel(0x0, LCDC_REG(LCDC_LICR)); + + /* Enable EOF and graphic window EOF interrupt */ + __raw_writel(ints, LCDC_REG(LCDC_LIER)); + } + + spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); + } +} + +/*! + * @brief Free LCDC interrupt handler + */ +static void _free_irq(void) +{ + /* Disable all LCDC interrupt */ + __raw_writel(0x0, LCDC_REG(LCDC_LIER)); + + free_irq(MXC_INT_LCDC, 0); +} + +/*! + * @brief Register a client notifier + * @param nb notifier block to callback on events + */ +int mx2fb_register_client(struct notifier_block *nb) +{ + unsigned long flags; + int ret; + + ret = atomic_notifier_chain_register(&mx2fb_notifier_list, nb); + + spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); + + /* Enable interrupt in case client has registered */ + if (mx2fb_notifier_list.head != NULL) { + unsigned long status; + unsigned long ints = MX2FB_INT_EOF; + + ints |= MX2FB_INT_GW_EOF; + + /* Read to clear the status */ + status = __raw_readl(LCDC_REG(LCDC_LISR)); + + /* Configure interrupt condition for EOF */ + __raw_writel(0x0, LCDC_REG(LCDC_LICR)); + + /* Enable EOF and graphic window EOF interrupt */ + __raw_writel(ints, LCDC_REG(LCDC_LIER)); + } + + spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); + + return ret; +} + +/*! + * @brief Unregister a client notifier + * @param nb notifier block to callback on events + */ +int mx2fb_unregister_client(struct notifier_block *nb) +{ + unsigned long flags; + int ret; + + ret = atomic_notifier_chain_unregister(&mx2fb_notifier_list, nb); + + spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); + + /* Mask interrupt in case no client registered */ + if (mx2fb_notifier_list.head == NULL) + __raw_writel(0x0, LCDC_REG(LCDC_LIER)); + + spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); + + return ret; +} + +#ifdef CONFIG_PM +/* + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/*! + * @brief Suspends the framebuffer and blanks the screen. + * Power management support + */ +static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state) +{ + _disable_lcdc(&mx2fb_info[0]); + + return 0; +} + +/*! + * @brief Resumes the framebuffer and unblanks the screen. + * Power management support + */ +static int mx2fb_resume(struct platform_device *pdev) +{ + _enable_lcdc(&mx2fb_info[0]); + + return 0; +} + +#endif /* CONFIG_PM */ + +/*! + * @brief Probe routine for the framebuffer driver. It is called during the + * driver binding process. + * + * @return Appropriate error code to the kernel common code + */ +static int mx2fb_probe(struct platform_device *pdev) +{ + int ret, i; + + lcdc_clk = clk_get(&pdev->dev, "lcdc_clk"); + + for (i = 0; i < sizeof(mx2fb_info) / sizeof(struct fb_info); i++) { + if ((ret = _install_fb(&mx2fb_info[i], pdev))) { + dev_err(&pdev->dev, + "Failed to register framebuffer %d\n", i); + return ret; + } + } + _request_irq(); + + return 0; +} + +/*! + * @brief Initialization + */ +int __init mx2fb_init(void) +{ + /* + * For kernel boot options (in 'video=xxxfb:<options>' format) + */ +#ifndef MODULE + { + char *option; + + if (fb_get_options("mxcfb", &option)) + return -ENODEV; + mx2fb_setup(option); + } +#endif + return platform_driver_register(&mx2fb_driver); +} + +/*! + * @brief Cleanup + */ +void __exit mx2fb_exit(void) +{ + int i; + + _free_irq(); + for (i = sizeof(mx2fb_info) / sizeof(struct fb_info); i > 0; i--) + _uninstall_fb(&mx2fb_info[i - 1]); + + platform_driver_unregister(&mx2fb_driver); +} + +#ifndef MODULE +/*! + * @brief Setup + * Parse user specified options + * Example: video=mxcfb:240x320,bpp=16,Sharp-QVGA + */ +static int __init mx2fb_setup(char *options) +{ + char *opt; + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "bpp=", 4)) + default_bpp = simple_strtoul(opt + 4, NULL, 0); + else + fb_mode = opt; + } + + return 0; +} +#endif + +/* Modularization */ +module_init(mx2fb_init); +module_exit(mx2fb_exit); + +EXPORT_SYMBOL(mx2_gw_set); +EXPORT_SYMBOL(mx2fb_register_client); +EXPORT_SYMBOL(mx2fb_unregister_client); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MX2 framebuffer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mx2fb.h b/drivers/video/mxc/mx2fb.h new file mode 100644 index 000000000000..ed20d78289ce --- /dev/null +++ b/drivers/video/mxc/mx2fb.h @@ -0,0 +1,141 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx2fb.h + * + * @brief Header file for the MX27 Frame buffer + * + * @ingroup Framebuffer + */ +#ifndef __MX2FB_H__ +#define __MX2FB_H__ + +/*! @brief MX27 LCDC graphic window information */ +struct fb_gwinfo { + /*! Non-zero if graphic window is enabled */ + __u32 enabled; + + /* The fields below are valid only when graphic window is enabled */ + + /*! Graphic window alpha value from 0 to 255 */ + __u32 alpha_value; + + /*! Non-zero if graphic window color keying is enabled. */ + __u32 ck_enabled; + + /* + * The fields ck_red, ck_green and ck_blue are valid only when + * graphic window and the color keying are enabled. They are the + * color component of graphic window color keying. + */ + + /*! Color keying red component */ + __u32 ck_red; + + /*! Color keying green component */ + __u32 ck_green; + + /*! Color keying blue component */ + __u32 ck_blue; + + /*! Graphic window x position */ + __u32 xpos; + + /*! Graphic window y position */ + __u32 ypos; + + /*! Non-zero if graphic window vertical scan in reverse direction. */ + __u32 vs_reversed; + + /* + * The following fields are valid for FBIOGET_GWINFO and + * mx2_gw_set(). FBIOPUT_GWINFO ignores these fields. + */ + __u32 base; /* Graphic window start address */ + __u32 xres; /* Visible x resolution */ + __u32 yres; /* Visible y resolution */ + __u32 xres_virtual; /* Virtual x resolution */ +}; + +/* 0x46E0-0x46FF are reserved for MX27 */ +#define FBIOGET_GWINFO 0x46E0 /*!< Get graphic window information */ +#define FBIOPUT_GWINFO 0x46E1 /*!< Set graphic window information */ + +struct mx2fb_gbl_alpha { + int enable; + int alpha; +}; + +struct mx2fb_color_key { + int enable; + __u32 color_key; +}; + +#define MX2FB_SET_GBL_ALPHA _IOW('M', 0, struct mx2fb_gbl_alpha) +#define MX2FB_SET_CLR_KEY _IOW('M', 1, struct mx2fb_color_key) +#define MX2FB_WAIT_FOR_VSYNC _IOW('F', 0x20, u_int32_t) + +#ifdef __KERNEL__ + +/* + * LCDC register definitions + */ +#define LCDC_LSSAR 0x00 +#define LCDC_LSR 0x04 +#define LCDC_LVPWR 0x08 +#define LCDC_LCPR 0x0C +#define LCDC_LCWHBR 0x10 +#define LCDC_LCCMR 0x14 +#define LCDC_LPCR 0x18 +#define LCDC_LHCR 0x1C +#define LCDC_LVCR 0x20 +#define LCDC_LPOR 0x24 +#define LCDC_LSCR 0x28 +#define LCDC_LPCCR 0x2C +#define LCDC_LDCR 0x30 +#define LCDC_LRMCR 0x34 +#define LCDC_LICR 0x38 +#define LCDC_LIER 0x3C +#define LCDC_LISR 0x40 +#define LCDC_LGWSAR 0x50 +#define LCDC_LGWSR 0x54 +#define LCDC_LGWVPWR 0x58 +#define LCDC_LGWPOR 0x5C +#define LCDC_LGWPR 0x60 +#define LCDC_LGWCR 0x64 +#define LCDC_LGWDCR 0x68 +#define LCDC_LAUSCR 0x80 +#define LCDC_LAUSCCR 0x84 + +#define LCDC_REG(reg) (IO_ADDRESS(LCDC_BASE_ADDR) + reg) + +#define MX2FB_INT_BOF 0x0001 /* Beginning of Frame */ +#define MX2FB_INT_EOF 0x0002 /* End of Frame */ +#define MX2FB_INT_ERR_RES 0x0004 /* Error Response */ +#define MX2FB_INT_UDR_ERR 0x0008 /* Under Run Error */ +#define MX2FB_INT_GW_BOF 0x0010 /* Graphic Window BOF */ +#define MX2FB_INT_GW_EOF 0x0020 /* Graphic Window EOF */ +#define MX2FB_INT_GW_ERR_RES 0x0040 /* Graphic Window ERR_RES */ +#define MX2FB_INT_GW_UDR_ERR 0x0080 /* Graphic Window UDR_ERR */ + +#define FB_EVENT_MXC_EOF 0x8001 /* End of Frame event */ + +int mx2fb_register_client(struct notifier_block *nb); +int mx2fb_unregister_client(struct notifier_block *nb); + +void mx2_gw_set(struct fb_gwinfo *gwinfo); + +#endif /* __KERNEL__ */ + +#endif /* __MX2FB_H__ */ diff --git a/drivers/video/mxc/mxc_edid.c b/drivers/video/mxc/mxc_edid.c new file mode 100644 index 000000000000..23c5470b387f --- /dev/null +++ b/drivers/video/mxc/mxc_edid.c @@ -0,0 +1,88 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxc_edid.c + * + * @brief MXC EDID tools + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/fb.h> + +#define EDID_LENGTH 128 + +static u8 edid[EDID_LENGTH]; + +int read_edid(struct i2c_adapter *adp, + struct fb_var_screeninfo *einfo, + int *dvi) +{ + u8 buf0[2] = {0, 0}; + int dat = 0; + u16 addr = 0x50; + struct i2c_msg msg[2] = { + { + .addr = addr, + .flags = 0, + .len = 1, + .buf = buf0, + }, { + .addr = addr, + .flags = I2C_M_RD, + .len = EDID_LENGTH, + .buf = edid, + }, + }; + + if (adp == NULL || einfo == NULL) + return -EINVAL; + + buf0[0] = 0x00; + memset(&edid, 0, sizeof(edid)); + memset(einfo, 0, sizeof(struct fb_var_screeninfo)); + dat = i2c_transfer(adp, msg, 2); + + /* If 0x50 fails, try 0x37. */ + if (edid[1] == 0x00) { + msg[0].addr = msg[1].addr = 0x37; + dat = i2c_transfer(adp, msg, 2); + } + + if (edid[1] == 0x00) + return -ENOENT; + + *dvi = 0; + if ((edid[20] == 0x80) || (edid[20] == 0x88) || (edid[20] == 0)) + *dvi = 1; + + dat = fb_parse_edid(edid, einfo); + if (dat) + return -dat; + + /* This is valid for version 1.3 of the EDID */ + if ((edid[18] == 1) && (edid[19] == 3)) { + einfo->height = edid[21] * 10; + einfo->width = edid[22] * 10; + } + + return 0; +} diff --git a/drivers/video/mxc/mxc_ipuv3_fb.c b/drivers/video/mxc/mxc_ipuv3_fb.c new file mode 100644 index 000000000000..19f301d2dfb4 --- /dev/null +++ b/drivers/video/mxc/mxc_ipuv3_fb.c @@ -0,0 +1,1593 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <asm/mach-types.h> +#include <asm/uaccess.h> +#include <mach/hardware.h> + +/* + * Driver name + */ +#define MXCFB_NAME "mxc_sdc_fb" +/*! + * Structure containing the MXC specific framebuffer information. + */ +struct mxcfb_info { + int blank; + ipu_channel_t ipu_ch; + int ipu_di; + u32 ipu_di_pix_fmt; + bool overlay; + bool alpha_chan_en; + dma_addr_t alpha_phy_addr0; + dma_addr_t alpha_phy_addr1; + void *alpha_virt_addr0; + void *alpha_virt_addr1; + uint32_t alpha_mem_len; + uint32_t ipu_ch_irq; + uint32_t cur_ipu_buf; + uint32_t cur_ipu_alpha_buf; + + u32 pseudo_palette[16]; + + struct semaphore flip_sem; + struct semaphore alpha_flip_sem; + struct completion vsync_complete; +}; + +struct mxcfb_alloc_list { + struct list_head list; + dma_addr_t phy_addr; + void *cpu_addr; + u32 size; +}; + +enum { + BOTH_ON, + SRC_ON, + TGT_ON, + BOTH_OFF +}; + +static char *fb_mode; +static unsigned long default_bpp = 16; +static bool g_dp_in_use; +LIST_HEAD(fb_alloc_list); +static struct fb_info *mxcfb_info[3]; + +static uint32_t bpp_to_pixfmt(struct fb_info *fbi) +{ + uint32_t pixfmt = 0; + + if (fbi->var.nonstd) + return fbi->var.nonstd; + + switch (fbi->var.bits_per_pixel) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id); +static int mxcfb_blank(int blank, struct fb_info *info); +static int mxcfb_map_video_memory(struct fb_info *fbi); +static int mxcfb_unmap_video_memory(struct fb_info *fbi); + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +static int _setup_disp_channel1(struct fb_info *fbi) +{ + ipu_channel_params_t params; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + memset(¶ms, 0, sizeof(params)); + params.mem_dp_bg_sync.di = mxc_fbi->ipu_di; + + /* + * Assuming interlaced means yuv output, below setting also + * valid for mem_dc_sync. FG should have the same vmode as BG. + */ + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { + struct mxcfb_info *mxc_fbi_tmp; + int i; + + for (i = 0; i < num_registered_fb; i++) { + mxc_fbi_tmp = (struct mxcfb_info *) + (registered_fb[i]->par); + if (mxc_fbi_tmp->ipu_ch == MEM_BG_SYNC) { + fbi->var.vmode = + registered_fb[i]->var.vmode; + break; + } + } + } + if (fbi->var.vmode & FB_VMODE_INTERLACED) { + params.mem_dp_bg_sync.interlaced = true; + params.mem_dp_bg_sync.out_pixel_fmt = + IPU_PIX_FMT_YUV444; + } else { + if (mxc_fbi->ipu_di_pix_fmt) + params.mem_dp_bg_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + else + params.mem_dp_bg_sync.out_pixel_fmt = IPU_PIX_FMT_RGB666; + } + params.mem_dp_bg_sync.in_pixel_fmt = bpp_to_pixfmt(fbi); + if (mxc_fbi->alpha_chan_en) + params.mem_dp_bg_sync.alpha_chan_en = true; + + ipu_init_channel(mxc_fbi->ipu_ch, ¶ms); + + return 0; +} + +static int _setup_disp_channel2(struct fb_info *fbi) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + mxc_fbi->cur_ipu_buf = 1; + sema_init(&mxc_fbi->flip_sem, 1); + if (mxc_fbi->alpha_chan_en) { + mxc_fbi->cur_ipu_alpha_buf = 1; + sema_init(&mxc_fbi->alpha_flip_sem, 1); + } + fbi->var.xoffset = fbi->var.yoffset = 0; + + retval = ipu_init_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi), + fbi->var.xres, fbi->var.yres, + fbi->fix.line_length, + IPU_ROTATE_NONE, + fbi->fix.smem_start + + (fbi->fix.line_length * fbi->var.yres), + fbi->fix.smem_start, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + } + + if (mxc_fbi->alpha_chan_en) { + retval = ipu_init_channel_buffer(mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + IPU_PIX_FMT_GENERIC, + fbi->var.xres, fbi->var.yres, + fbi->var.xres, + IPU_ROTATE_NONE, + mxc_fbi->alpha_phy_addr0, + mxc_fbi->alpha_phy_addr1, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + return retval; + } + } + + return retval; +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval = 0; + u32 mem_len, alpha_mem_len; + ipu_di_signal_cfg_t sig_cfg; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + dev_dbg(fbi->device, "Reconfiguring framebuffer\n"); + + ipu_disable_irq(mxc_fbi->ipu_ch_irq); + ipu_disable_channel(mxc_fbi->ipu_ch, true); + ipu_uninit_channel(mxc_fbi->ipu_ch); + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + mxcfb_set_fix(fbi); + + mem_len = fbi->var.yres_virtual * fbi->fix.line_length; + if (!fbi->fix.smem_start || (mem_len > fbi->fix.smem_len)) { + if (fbi->fix.smem_start) + mxcfb_unmap_video_memory(fbi); + + if (mxcfb_map_video_memory(fbi) < 0) + return -ENOMEM; + } + if (mxc_fbi->alpha_chan_en) { + alpha_mem_len = fbi->var.xres * fbi->var.yres; + if ((!mxc_fbi->alpha_phy_addr0 && !mxc_fbi->alpha_phy_addr1) || + (alpha_mem_len > mxc_fbi->alpha_mem_len)) { + if (mxc_fbi->alpha_phy_addr0) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr0, + mxc_fbi->alpha_phy_addr0); + if (mxc_fbi->alpha_phy_addr1) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr1, + mxc_fbi->alpha_phy_addr1); + + mxc_fbi->alpha_virt_addr0 = + dma_alloc_coherent(fbi->device, + alpha_mem_len, + &mxc_fbi->alpha_phy_addr0, + GFP_DMA | GFP_KERNEL); + + mxc_fbi->alpha_virt_addr1 = + dma_alloc_coherent(fbi->device, + alpha_mem_len, + &mxc_fbi->alpha_phy_addr1, + GFP_DMA | GFP_KERNEL); + if (mxc_fbi->alpha_virt_addr0 == NULL || + mxc_fbi->alpha_virt_addr1 == NULL) { + dev_err(fbi->device, "mxcfb: dma alloc for" + " alpha buffer failed.\n"); + if (mxc_fbi->alpha_virt_addr0) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr0, + mxc_fbi->alpha_phy_addr0); + if (mxc_fbi->alpha_virt_addr1) + dma_free_coherent(fbi->device, + mxc_fbi->alpha_mem_len, + mxc_fbi->alpha_virt_addr1, + mxc_fbi->alpha_phy_addr1); + return -ENOMEM; + } + mxc_fbi->alpha_mem_len = alpha_mem_len; + } + } + + _setup_disp_channel1(fbi); + + if (!mxc_fbi->overlay) { + uint32_t out_pixel_fmt; + + memset(&sig_cfg, 0, sizeof(sig_cfg)); + if (fbi->var.vmode & FB_VMODE_INTERLACED) { + sig_cfg.interlaced = true; + out_pixel_fmt = IPU_PIX_FMT_YUV444; + } else { + if (mxc_fbi->ipu_di_pix_fmt) + out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt; + else + out_pixel_fmt = IPU_PIX_FMT_RGB666; + } + if (fbi->var.vmode & FB_VMODE_ODD_FLD_FIRST) /* PAL */ + sig_cfg.odd_field_first = true; + if (fbi->var.sync & FB_SYNC_EXT) + sig_cfg.ext_clk = true; + if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT) + sig_cfg.Hsync_pol = true; + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) + sig_cfg.Vsync_pol = true; + if (!(fbi->var.sync & FB_SYNC_CLK_LAT_FALL)) + sig_cfg.clk_pol = true; + if (fbi->var.sync & FB_SYNC_DATA_INVERT) + sig_cfg.data_pol = true; + if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT)) + sig_cfg.enable_pol = true; + if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN) + sig_cfg.clkidle_en = true; + + dev_dbg(fbi->device, "pixclock = %ul Hz\n", + (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL)); + + if (ipu_init_sync_panel(mxc_fbi->ipu_di, + (PICOS2KHZ(fbi->var.pixclock)) * 1000UL, + fbi->var.xres, fbi->var.yres, + out_pixel_fmt, + fbi->var.left_margin, + fbi->var.hsync_len, + fbi->var.right_margin, + fbi->var.upper_margin, + fbi->var.vsync_len, + fbi->var.lower_margin, + 0, sig_cfg) != 0) { + dev_err(fbi->device, + "mxcfb: Error initializing panel.\n"); + return -EINVAL; + } + + fbi->mode = + (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + ipu_disp_set_window_pos(mxc_fbi->ipu_ch, 0, 0); + } + + retval = _setup_disp_channel2(fbi); + if (retval) + return retval; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) { + ipu_enable_channel(mxc_fbi->ipu_ch); + } + + return retval; +} + +static int _swap_channels(struct fb_info *fbi, + struct fb_info *fbi_to, bool both_on) +{ + int retval, tmp; + ipu_channel_t old_ch; + struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi->par; + struct mxcfb_info *mxc_fbi_to = (struct mxcfb_info *)fbi_to->par; + + if (both_on) { + ipu_disable_channel(mxc_fbi_to->ipu_ch, true); + ipu_uninit_channel(mxc_fbi_to->ipu_ch); + } + + /* switch the mxc fbi parameters */ + old_ch = mxc_fbi_from->ipu_ch; + mxc_fbi_from->ipu_ch = mxc_fbi_to->ipu_ch; + mxc_fbi_to->ipu_ch = old_ch; + tmp = mxc_fbi_from->ipu_ch_irq; + mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq; + mxc_fbi_to->ipu_ch_irq = tmp; + + _setup_disp_channel1(fbi); + retval = _setup_disp_channel2(fbi); + if (retval) + return retval; + + /* switch between dp and dc, disable old idmac, enable new idmac */ + retval = ipu_swap_channel(old_ch, mxc_fbi_from->ipu_ch); + ipu_uninit_channel(old_ch); + + if (both_on) { + _setup_disp_channel1(fbi_to); + retval = _setup_disp_channel2(fbi_to); + if (retval) + return retval; + ipu_enable_channel(mxc_fbi_to->ipu_ch); + } + + return retval; +} + +static int swap_channels(struct fb_info *fbi) +{ + int i; + int swap_mode; + ipu_channel_t ch_to; + struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi->par; + struct fb_info *fbi_to = NULL; + struct mxcfb_info *mxc_fbi_to; + + /* what's the target channel? */ + if (mxc_fbi_from->ipu_ch == MEM_BG_SYNC) + ch_to = MEM_DC_SYNC; + else + ch_to = MEM_BG_SYNC; + + for (i = 0; i < num_registered_fb; i++) { + mxc_fbi_to = + (struct mxcfb_info *)mxcfb_info[i]->par; + if (mxc_fbi_to->ipu_ch == ch_to) { + fbi_to = mxcfb_info[i]; + break; + } + } + if (fbi_to == NULL) + return -1; + + if (mxc_fbi_from->blank == FB_BLANK_UNBLANK) { + if (mxc_fbi_to->blank == FB_BLANK_UNBLANK) + swap_mode = BOTH_ON; + else + swap_mode = SRC_ON; + } else { + if (mxc_fbi_to->blank == FB_BLANK_UNBLANK) + swap_mode = TGT_ON; + else + swap_mode = BOTH_OFF; + } + + /* tvout di-1: for DC use UYVY, for DP use RGB */ + if (mxc_fbi_from->ipu_di == 1 && ch_to == MEM_DC_SYNC) { + fbi->var.bits_per_pixel = 16; + fbi->var.nonstd = IPU_PIX_FMT_UYVY; + } else if (mxc_fbi_from->ipu_di == 1 && ch_to == MEM_BG_SYNC) { + fbi->var.nonstd = 0; + } else if (mxc_fbi_from->ipu_di == 0 && ch_to == MEM_DC_SYNC) { + fbi_to->var.nonstd = 0; + } else if (mxc_fbi_from->ipu_di == 0 && ch_to == MEM_BG_SYNC) { + fbi->var.bits_per_pixel = 16; + fbi->var.nonstd = IPU_PIX_FMT_UYVY; + } + + switch (swap_mode) { + case BOTH_ON: + /* disable target->switch src->enable target */ + _swap_channels(fbi, fbi_to, true); + break; + case SRC_ON: + /* just switch src */ + _swap_channels(fbi, fbi_to, false); + break; + case TGT_ON: + /* just switch target */ + _swap_channels(fbi_to, fbi, false); + break; + case BOTH_OFF: + /* switch directly, no more need to do */ + mxc_fbi_to->ipu_ch = mxc_fbi_from->ipu_ch; + mxc_fbi_from->ipu_ch = ch_to; + i = mxc_fbi_from->ipu_ch_irq; + mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq; + mxc_fbi_to->ipu_ch_irq = i; + break; + default: + break; + } + + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 vtotal; + u32 htotal; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16) && (var->bits_per_pixel != 8)) + var->bits_per_pixel = default_bpp; + + switch (var->bits_per_pixel) { + case 8: + var->red.length = 3; + var->red.offset = 5; + var->red.msb_right = 0; + + var->green.length = 3; + var->green.offset = 2; + var->green.msb_right = 0; + + var->blue.length = 2; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + if (var->pixclock < 1000) { + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + var->pixclock = (vtotal * htotal * 6UL) / 100UL; + var->pixclock = KHZ2PICOS(var->pixclock); + dev_dbg(info->device, + "pixclock set for 60Hz refresh = %u ps\n", + var->pixclock); + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * Function to handle custom ioctls for MXC framebuffer. + * + * @param inode inode struct + * + * @param file file struct + * + * @param cmd Ioctl command to handle + * + * @param arg User pointer to command arguments + * + * @param fbi framebuffer information pointer + */ +static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + int __user *argp = (void __user *)arg; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + switch (cmd) { + case MXCFB_SET_GBL_ALPHA: + { + struct mxcfb_gbl_alpha ga; + + if (copy_from_user(&ga, (void *)arg, sizeof(ga))) { + retval = -EFAULT; + break; + } + + if (ipu_disp_set_global_alpha(mxc_fbi->ipu_ch, + (bool)ga.enable, + ga.alpha)) { + retval = -EINVAL; + break; + } + + if (ga.enable) + mxc_fbi->alpha_chan_en = false; + + if (ga.enable) + dev_dbg(fbi->device, + "Set global alpha of %s to %d\n", + fbi->fix.id, ga.alpha); + break; + } + case MXCFB_SET_LOC_ALPHA: + { + struct mxcfb_loc_alpha la; + int i; + char *video_plane_idstr = ""; + + if (copy_from_user(&la, (void *)arg, sizeof(la))) { + retval = -EFAULT; + break; + } + + if (ipu_disp_set_global_alpha(mxc_fbi->ipu_ch, + !(bool)la.enable, 0)) { + retval = -EINVAL; + break; + } + + if (la.enable) { + mxc_fbi->alpha_chan_en = true; + + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) + video_plane_idstr = "DISP3 BG"; + else if (mxc_fbi->ipu_ch == MEM_BG_SYNC) + video_plane_idstr = "DISP3 FG"; + + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strcmp(idstr, video_plane_idstr) == 0) { + ((struct mxcfb_info *)(registered_fb[i]->par))->alpha_chan_en = false; + break; + } + } + } else + mxc_fbi->alpha_chan_en = false; + + mxcfb_set_par(fbi); + + la.alpha_phy_addr0 = mxc_fbi->alpha_phy_addr0; + la.alpha_phy_addr1 = mxc_fbi->alpha_phy_addr1; + if (copy_to_user((void *)arg, &la, sizeof(la))) { + retval = -EFAULT; + break; + } + + if (la.enable) + dev_dbg(fbi->device, + "Enable DP local alpha for %s\n", + fbi->fix.id); + break; + } + case MXCFB_SET_LOC_ALP_BUF: + { + unsigned long base; + uint32_t ipu_alp_ch_irq; + + if (!(((mxc_fbi->ipu_ch == MEM_FG_SYNC) || + (mxc_fbi->ipu_ch == MEM_BG_SYNC)) && + (mxc_fbi->alpha_chan_en))) { + dev_err(fbi->device, + "Should use background or overlay " + "framebuffer to set the alpha buffer " + "number\n"); + return -EINVAL; + } + + if (get_user(base, argp)) + return -EFAULT; + + if (base != mxc_fbi->alpha_phy_addr0 && + base != mxc_fbi->alpha_phy_addr1) { + dev_err(fbi->device, + "Wrong alpha buffer physical address " + "%lu\n", base); + return -EINVAL; + } + + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) + ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF; + else + ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF; + + down(&mxc_fbi->alpha_flip_sem); + + mxc_fbi->cur_ipu_alpha_buf = + !mxc_fbi->cur_ipu_alpha_buf; + if (ipu_update_channel_buffer(mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi-> + cur_ipu_alpha_buf, + base) == 0) { + ipu_select_buffer(mxc_fbi->ipu_ch, + IPU_ALPHA_IN_BUFFER, + mxc_fbi->cur_ipu_alpha_buf); + ipu_clear_irq(ipu_alp_ch_irq); + ipu_enable_irq(ipu_alp_ch_irq); + } else { + dev_err(fbi->device, + "Error updating %s SDC alpha buf %d " + "to address=0x%08lX\n", + fbi->fix.id, + mxc_fbi->cur_ipu_alpha_buf, base); + } + break; + } + case MXCFB_SET_CLR_KEY: + { + struct mxcfb_color_key key; + if (copy_from_user(&key, (void *)arg, sizeof(key))) { + retval = -EFAULT; + break; + } + retval = ipu_disp_set_color_key(mxc_fbi->ipu_ch, + key.enable, + key.color_key); + dev_dbg(fbi->device, "Set color key to 0x%08X\n", + key.color_key); + break; + } + case MXCFB_WAIT_FOR_VSYNC: + { + if (mxc_fbi->blank != FB_BLANK_UNBLANK) + break; + + down(&mxc_fbi->flip_sem); + init_completion(&mxc_fbi->vsync_complete); + + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu_ch_irq); + retval = wait_for_completion_interruptible_timeout( + &mxc_fbi->vsync_complete, 1 * HZ); + if (retval == 0) { + dev_err(fbi->device, + "MXCFB_WAIT_FOR_VSYNC: timeout %d\n", + retval); + retval = -ETIME; + } else if (retval > 0) { + retval = 0; + } + break; + } + case FBIO_ALLOC: + { + int size; + struct mxcfb_alloc_list *mem; + + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (mem == NULL) + return -ENOMEM; + + if (get_user(size, argp)) + return -EFAULT; + + mem->size = PAGE_ALIGN(size); + + mem->cpu_addr = dma_alloc_coherent(fbi->device, size, + &mem->phy_addr, + GFP_DMA); + if (mem->cpu_addr == NULL) { + kfree(mem); + return -ENOMEM; + } + + list_add(&mem->list, &fb_alloc_list); + + dev_dbg(fbi->device, "allocated %d bytes @ 0x%08X\n", + mem->size, mem->phy_addr); + + if (put_user(mem->phy_addr, argp)) + return -EFAULT; + + break; + } + case FBIO_FREE: + { + unsigned long offset; + struct mxcfb_alloc_list *mem; + + if (get_user(offset, argp)) + return -EFAULT; + + retval = -EINVAL; + list_for_each_entry(mem, &fb_alloc_list, list) { + if (mem->phy_addr == offset) { + list_del(&mem->list); + dma_free_coherent(fbi->device, + mem->size, + mem->cpu_addr, + mem->phy_addr); + kfree(mem); + retval = 0; + break; + } + } + + break; + } + case MXCFB_SET_OVERLAY_POS: + { + struct mxcfb_pos pos; + struct fb_info *bg_fbi = NULL; + struct mxcfb_info *bg_mxcfbi = NULL; + int i; + + if (mxc_fbi->ipu_ch != MEM_FG_SYNC) { + dev_err(fbi->device, "Should use the overlay " + "framebuffer to set the position of " + "the overlay window\n"); + retval = -EINVAL; + break; + } + + if (copy_from_user(&pos, (void *)arg, sizeof(pos))) { + retval = -EFAULT; + break; + } + + for (i = 0; i < num_registered_fb; i++) { + bg_mxcfbi = + ((struct mxcfb_info *)(registered_fb[i]->par)); + + if (bg_mxcfbi->ipu_ch == MEM_BG_SYNC) { + bg_fbi = registered_fb[i]; + break; + } + } + + if (bg_fbi == NULL) { + dev_err(fbi->device, "Cannot find the " + "background framebuffer\n"); + retval = -ENOENT; + break; + } + + if (fbi->var.xres + pos.x > bg_fbi->var.xres) { + if (bg_fbi->var.xres < fbi->var.xres) + pos.x = 0; + else + pos.x = bg_fbi->var.xres - fbi->var.xres; + } + if (fbi->var.yres + pos.y > bg_fbi->var.yres) { + if (bg_fbi->var.yres < fbi->var.yres) + pos.y = 0; + else + pos.y = bg_fbi->var.yres - fbi->var.yres; + } + + retval = ipu_disp_set_window_pos(mxc_fbi->ipu_ch, + pos.x, pos.y); + + if (copy_to_user((void *)arg, &pos, sizeof(pos))) { + retval = -EFAULT; + break; + } + break; + } + case MXCFB_GET_FB_IPU_CHAN: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_ch, argp)) + return -EFAULT; + break; + } + default: + retval = -EINVAL; + } + return retval; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *info) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + dev_dbg(info->device, "blank = %d\n", blank); + + if (mxc_fbi->blank == blank) + return 0; + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + ipu_disable_channel(mxc_fbi->ipu_ch, true); + ipu_uninit_channel(mxc_fbi->ipu_ch); + break; + case FB_BLANK_UNBLANK: + mxcfb_set_par(info); + break; + } + return 0; +} + +/* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + * + * @param var Variable screen buffer information + * @param info Framebuffer information pointer + */ +static int +mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + u_int y_bottom; + unsigned long base; + + if (var->xoffset > 0) { + dev_dbg(info->device, "x panning not supported\n"); + return -EINVAL; + } + + if ((info->var.xoffset == var->xoffset) && + (info->var.yoffset == var->yoffset)) + return 0; /* No change, do nothing */ + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) + y_bottom += var->yres; + + if (y_bottom > info->var.yres_virtual) + return -EINVAL; + + base = (var->yoffset * var->xres_virtual + var->xoffset); + base *= (var->bits_per_pixel) / 8; + base += info->fix.smem_start; + + dev_dbg(info->device, "Updating SDC BG buf %d address=0x%08lX\n", + mxc_fbi->cur_ipu_buf, base); + + down(&mxc_fbi->flip_sem); + init_completion(&mxc_fbi->vsync_complete); + + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + if (ipu_update_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, base) == 0) { + ipu_select_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu_ch_irq); + } else { + dev_err(info->device, + "Error updating SDC buf %d to address=0x%08lX\n", + mxc_fbi->cur_ipu_buf, base); + } + + dev_dbg(info->device, "Update complete\n"); + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + + return 0; +} + +/* + * Function to handle custom mmap for MXC framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param vma Pointer to vm_area_struct + */ +static int mxcfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma) +{ + bool found = false; + u32 len; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + struct mxcfb_alloc_list *mem; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + if (offset < fbi->fix.smem_len) { + /* mapping framebuffer memory */ + len = fbi->fix.smem_len - offset; + vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT; + } else if ((vma->vm_pgoff == + (mxc_fbi->alpha_phy_addr0 >> PAGE_SHIFT)) || + (vma->vm_pgoff == + (mxc_fbi->alpha_phy_addr1 >> PAGE_SHIFT))) { + len = mxc_fbi->alpha_mem_len; + } else { + list_for_each_entry(mem, &fb_alloc_list, list) { + if (offset == mem->phy_addr) { + found = true; + len = mem->size; + break; + } + } + if (!found) + return -EINVAL; + } + + len = PAGE_ALIGN(len); + if (vma->vm_end - vma->vm_start > len) + return -EINVAL; + + /* make buffers bufferable */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + vma->vm_flags |= VM_IO | VM_RESERVED; + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + dev_dbg(fbi->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + } + + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_pan_display = mxcfb_pan_display, + .fb_ioctl = mxcfb_ioctl, + .fb_mmap = mxcfb_mmap, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + complete(&mxc_fbi->vsync_complete); + up(&mxc_fbi->flip_sem); + ipu_disable_irq(irq); + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_alpha_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + up(&mxc_fbi->alpha_flip_sem); + ipu_disable_irq(irq); + return IRQ_HANDLED; +} + +/* + * Suspends the framebuffer and blanks the screen. Power management support + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + int saved_blank; +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + void *fbmem; +#endif + + acquire_console_sem(); + fb_set_suspend(fbi, 1); + saved_blank = mxc_fbi->blank; + mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + mxc_fbi->blank = saved_blank; + release_console_sem(); + + return 0; +} + +/* + * Resumes the framebuffer and unblanks the screen. Power management support + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + int saved_blank; + + acquire_console_sem(); + saved_blank = mxc_fbi->blank; + mxc_fbi->blank = FB_BLANK_POWERDOWN; + mxcfb_blank(saved_blank, fbi); + fb_set_suspend(fbi, 0); + release_console_sem(); + + return 0; +} + +/* + * Main framebuffer functions + */ + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi) +{ + if (fbi->fix.smem_len < fbi->var.yres_virtual * fbi->fix.line_length) + fbi->fix.smem_len = fbi->var.yres_virtual * + fbi->fix.line_length; + + fbi->screen_base = dma_alloc_writecombine(fbi->device, + fbi->fix.smem_len, + (dma_addr_t *)&fbi->fix.smem_start, + GFP_DMA); + if (fbi->screen_base == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); + fbi->fix.smem_len = 0; + fbi->fix.smem_start = 0; + return -EBUSY; + } + + dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n", + (uint32_t) fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_size = fbi->fix.smem_len; + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + return 0; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ + dma_free_writecombine(fbi->device, fbi->fix.smem_len, + fbi->screen_base, fbi->fix.smem_start); + fbi->screen_base = 0; + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + fbi->var.activate = FB_ACTIVATE_NOW; + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +static ssize_t show_disp_chan(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par; + + if (mxcfbi->ipu_ch == MEM_BG_SYNC) + return sprintf(buf, "2-layer-fb-bg\n"); + else if (mxcfbi->ipu_ch == MEM_FG_SYNC) + return sprintf(buf, "2-layer-fb-fg\n"); + else if (mxcfbi->ipu_ch == MEM_DC_SYNC) + return sprintf(buf, "1-layer-fb\n"); + else + return sprintf(buf, "err: no display chan\n"); +} + +static ssize_t swap_disp_chan(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par; + struct mxcfb_info *fg_mxcfbi = NULL; + + /* swap only happen between DP-BG and DC, while DP-FG disable */ + if (((mxcfbi->ipu_ch == MEM_BG_SYNC) && + (strstr(buf, "1-layer-fb") != NULL)) || + ((mxcfbi->ipu_ch == MEM_DC_SYNC) && + (strstr(buf, "2-layer-fb-bg") != NULL))) { + int i; + + for (i = 0; i < num_registered_fb; i++) { + fg_mxcfbi = + (struct mxcfb_info *)mxcfb_info[i]->par; + if (fg_mxcfbi->ipu_ch == MEM_FG_SYNC) + break; + else + fg_mxcfbi = NULL; + } + if (!fg_mxcfbi || + fg_mxcfbi->blank == FB_BLANK_UNBLANK) { + dev_err(dev, + "Can not switch while fb2(fb-fg) is on.\n"); + return count; + } + + if (swap_channels(info) < 0) + dev_err(dev, "Swap display channel failed.\n"); + } + + return count; +} +DEVICE_ATTR(fsl_disp_property, 644, show_disp_chan, swap_disp_chan); + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + struct mxc_fb_platform_data *plat_data = pdev->dev.platform_data; + struct resource *res; + int ret = 0; + + /* + * Initialize FB structures + */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto err0; + } + mxcfbi = (struct mxcfb_info *)fbi->par; + + if (!g_dp_in_use) { + mxcfbi->ipu_ch_irq = IPU_IRQ_BG_SYNC_EOF; + mxcfbi->ipu_ch = MEM_BG_SYNC; + mxcfbi->blank = FB_BLANK_UNBLANK; + } else { + mxcfbi->ipu_ch_irq = IPU_IRQ_DC_SYNC_EOF; + mxcfbi->ipu_ch = MEM_DC_SYNC; + mxcfbi->blank = FB_BLANK_POWERDOWN; + } + + mxcfbi->ipu_di = pdev->id; + + if (pdev->id == 0) { + ipu_disp_set_global_alpha(mxcfbi->ipu_ch, true, 0x80); + ipu_disp_set_color_key(mxcfbi->ipu_ch, false, 0); + strcpy(fbi->fix.id, "DISP3 BG"); + + if (!g_dp_in_use) + if (ipu_request_irq(IPU_IRQ_BG_ALPHA_SYNC_EOF, + mxcfb_alpha_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(&pdev->dev, "Error registering BG " + "alpha irq handler.\n"); + ret = -EBUSY; + goto err1; + } + g_dp_in_use = true; + } else if (pdev->id == 1) { + strcpy(fbi->fix.id, "DISP3 BG - DI1"); + + if (!g_dp_in_use) + if (ipu_request_irq(IPU_IRQ_BG_ALPHA_SYNC_EOF, + mxcfb_alpha_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(&pdev->dev, "Error registering BG " + "alpha irq handler.\n"); + ret = -EBUSY; + goto err1; + } + g_dp_in_use = true; + } else if (pdev->id == 2) { /* Overlay */ + mxcfbi->ipu_ch_irq = IPU_IRQ_FG_SYNC_EOF; + mxcfbi->ipu_ch = MEM_FG_SYNC; + mxcfbi->ipu_di = -1; + mxcfbi->overlay = true; + mxcfbi->blank = FB_BLANK_POWERDOWN; + + strcpy(fbi->fix.id, "DISP3 FG"); + + if (ipu_request_irq(IPU_IRQ_FG_ALPHA_SYNC_EOF, + mxcfb_alpha_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(&pdev->dev, "Error registering FG alpha irq " + "handler.\n"); + ret = -EBUSY; + goto err1; + } + } + + mxcfb_info[pdev->id] = fbi; + + if (ipu_request_irq(mxcfbi->ipu_ch_irq, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(&pdev->dev, "Error registering BG irq handler.\n"); + ret = -EBUSY; + goto err1; + } + ipu_disable_irq(mxcfbi->ipu_ch_irq); + + /* Default Y virtual size is 2x panel size */ + fbi->var.yres_virtual = fbi->var.yres * 2; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) { + fbi->fix.smem_len = res->end - res->start + 1; + fbi->fix.smem_start = res->start; + fbi->screen_base = ioremap(fbi->fix.smem_start, fbi->fix.smem_len); + } + + /* Need dummy values until real panel is configured */ + fbi->var.xres = 240; + fbi->var.yres = 320; + + if (!fb_mode && plat_data && plat_data->mode_str) + fb_find_mode(&fbi->var, fbi, plat_data->mode_str, NULL, 0, NULL, + default_bpp); + + if (fb_mode) + fb_find_mode(&fbi->var, fbi, fb_mode, NULL, 0, NULL, + default_bpp); + + if (plat_data) { + mxcfbi->ipu_di_pix_fmt = plat_data->interface_pix_fmt; + if (!fb_mode && plat_data->mode) + fb_videomode_to_var(&fbi->var, plat_data->mode); + } + + mxcfb_check_var(&fbi->var, fbi); + mxcfb_set_fix(fbi); + + ret = register_framebuffer(fbi); + if (ret < 0) + goto err2; + + platform_set_drvdata(pdev, fbi); + + ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_property); + if (ret) + dev_err(&pdev->dev, "Error %d on creating file\n", ret); + + return 0; + +err2: + ipu_free_irq(mxcfbi->ipu_ch_irq, fbi); +err1: + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); +err0: + return ret; +} + +static int mxcfb_remove(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = fbi->par; + + if (!fbi) + return 0; + + mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + ipu_free_irq(mxc_fbi->ipu_ch_irq, fbi); + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .remove = mxcfb_remove, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/* + * Parse user specified options (`video=trident:') + * example: + * video=trident:800x600,bpp=16,noaccel + */ +int mxcfb_setup(char *options) +{ + char *opt; + if (!options || !*options) + return 0; + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + if (!strncmp(opt, "bpp=", 4)) + default_bpp = simple_strtoul(opt + 4, NULL, 0); + else + fb_mode = opt; + } + return 0; +} + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +int __init mxcfb_init(void) +{ + int ret = 0; +#ifndef MODULE + char *option = NULL; +#endif + +#ifndef MODULE + if (fb_get_options("mxcfb", &option)) + return -ENODEV; + mxcfb_setup(option); +#endif + + ret = platform_driver_register(&mxcfb_driver); + return ret; +} + +void mxcfb_exit(void) +{ + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxcfb.c b/drivers/video/mxc/mxcfb.c new file mode 100644 index 000000000000..5152a8850148 --- /dev/null +++ b/drivers/video/mxc/mxcfb.c @@ -0,0 +1,1377 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <asm/mach-types.h> +#include <asm/uaccess.h> +#include <mach/hardware.h> + +/* + * Driver name + */ +#define MXCFB_NAME "mxc_sdc_fb" +/*! + * Structure containing the MXC specific framebuffer information. + */ +struct mxcfb_info { + int blank; + ipu_channel_t ipu_ch; + uint32_t ipu_ch_irq; + uint32_t cur_ipu_buf; + + u32 pseudo_palette[16]; + + struct semaphore flip_sem; + spinlock_t fb_lock; +}; + +struct mxcfb_data { + struct fb_info *fbi; + struct fb_info *fbi_ovl; + volatile int32_t vsync_flag; + wait_queue_head_t vsync_wq; + wait_queue_head_t suspend_wq; + bool suspended; + int backlight_level; +}; + +struct mxcfb_alloc_list { + struct list_head list; + dma_addr_t phy_addr; + void *cpu_addr; + u32 size; +}; + +static struct mxcfb_data mxcfb_drv_data; + +static char *fb_mode = NULL; +static unsigned long default_bpp = 16; +#ifdef CONFIG_FB_MXC_INTERNAL_MEM +static struct clk *iram_clk; +#endif +LIST_HEAD(fb_alloc_list); + +static uint32_t bpp_to_pixfmt(int bpp) +{ + uint32_t pixfmt = 0; + switch (bpp) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +extern void gpio_lcd_active(void); +extern void gpio_lcd_inactive(void); +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id); +static int mxcfb_blank(int blank, struct fb_info *info); +static int mxcfb_map_video_memory(struct fb_info *fbi, bool use_internal_ram); +static int mxcfb_unmap_video_memory(struct fb_info *fbi); + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + if (mxc_fbi->ipu_ch == MEM_SDC_FG) + strncpy(fix->id, "DISP3 FG", 8); + else + strncpy(fix->id, "DISP3 BG", 8); + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval; + bool use_iram = false; + u32 mem_len; + ipu_di_signal_cfg_t sig_cfg; + ipu_panel_t mode = IPU_PANEL_TFT; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + ipu_disable_irq(mxc_fbi->ipu_ch_irq); + ipu_disable_channel(mxc_fbi->ipu_ch, true); + ipu_uninit_channel(mxc_fbi->ipu_ch); + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + mxcfb_set_fix(fbi); + + mem_len = fbi->var.yres_virtual * fbi->fix.line_length; + if (mem_len > fbi->fix.smem_len) { + if (fbi->fix.smem_start) + mxcfb_unmap_video_memory(fbi); + +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if (mxc_fbi->ipu_ch == MEM_SDC_BG) { + use_iram = true; + } +#endif + if (mxcfb_map_video_memory(fbi, use_iram) < 0) + return -ENOMEM; + } + + ipu_init_channel(mxc_fbi->ipu_ch, NULL); + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + if (mxc_fbi->ipu_ch == MEM_SDC_BG) { + memset(&sig_cfg, 0, sizeof(sig_cfg)); + if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT) + sig_cfg.Hsync_pol = true; + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) + sig_cfg.Vsync_pol = true; + if (!(fbi->var.sync & FB_SYNC_CLK_LAT_FALL)) + sig_cfg.clk_pol = true; + if (fbi->var.sync & FB_SYNC_DATA_INVERT) + sig_cfg.data_pol = true; + if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT)) + sig_cfg.enable_pol = true; + if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN) + sig_cfg.clkidle_en = true; + if (fbi->var.sync & FB_SYNC_SHARP_MODE) + mode = IPU_PANEL_SHARP_TFT; + + dev_dbg(fbi->device, "pixclock = %ul Hz\n", + (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL)); + + if (ipu_sdc_init_panel(mode, + (PICOS2KHZ(fbi->var.pixclock)) * 1000UL, + fbi->var.xres, fbi->var.yres, + (fbi->var.sync & FB_SYNC_SWAP_RGB) ? + IPU_PIX_FMT_BGR666 : IPU_PIX_FMT_RGB666, + fbi->var.left_margin, + fbi->var.hsync_len, + fbi->var.right_margin, + fbi->var.upper_margin, + fbi->var.vsync_len, + fbi->var.lower_margin, sig_cfg) != 0) { + dev_err(fbi->device, + "mxcfb: Error initializing panel.\n"); + return -EINVAL; + } + + fbi->mode = + (struct fb_videomode *)fb_match_mode(&fbi->var, + &fbi->modelist); + } + + ipu_disp_set_window_pos(mxc_fbi->ipu_ch, 0, 0); + + mxc_fbi->cur_ipu_buf = 1; + sema_init(&mxc_fbi->flip_sem, 1); + fbi->var.xoffset = fbi->var.yoffset = 0; + + retval = ipu_init_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + fbi->var.xres, fbi->var.yres, + fbi->var.xres_virtual, + IPU_ROTATE_NONE, + fbi->fix.smem_start + + (fbi->fix.line_length * fbi->var.yres), + fbi->fix.smem_start, + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + return retval; + } + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) { + ipu_enable_channel(mxc_fbi->ipu_ch); + } + + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 vtotal; + u32 htotal; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if ((info->fix.smem_start == FB_RAM_BASE_ADDR) && + ((var->yres_virtual * var->xres_virtual * var->bits_per_pixel / 8) > + FB_RAM_SIZE)) { + return -EINVAL; + } +#endif + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + if (var->pixclock < 1000) { + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + var->pixclock = (vtotal * htotal * 6UL) / 100UL; + var->pixclock = KHZ2PICOS(var->pixclock); + dev_dbg(info->device, + "pixclock set for 60Hz refresh = %u ps\n", + var->pixclock); + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + /* nonstd used for YUV formats, but only RGB supported */ + var->nonstd = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} +static int +mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * Function to handle custom ioctls for MXC framebuffer. + * + * @param inode inode struct + * + * @param file file struct + * + * @param cmd Ioctl command to handle + * + * @param arg User pointer to command arguments + * + * @param fbi framebuffer information pointer + */ +static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + int __user *argp = (void __user *)arg; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + switch (cmd) { + case MXCFB_SET_GBL_ALPHA: + { + struct mxcfb_gbl_alpha ga; + if (copy_from_user(&ga, (void *)arg, sizeof(ga))) { + retval = -EFAULT; + break; + } + retval = + ipu_sdc_set_global_alpha((bool) ga.enable, + ga.alpha); + dev_dbg(fbi->device, "Set global alpha to %d\n", + ga.alpha); + break; + } + case MXCFB_SET_CLR_KEY: + { + struct mxcfb_color_key key; + if (copy_from_user(&key, (void *)arg, sizeof(key))) { + retval = -EFAULT; + break; + } + retval = ipu_sdc_set_color_key(MEM_SDC_BG, key.enable, + key.color_key); + dev_dbg(fbi->device, "Set color key to 0x%08X\n", + key.color_key); + break; + } + case MXCFB_WAIT_FOR_VSYNC: + { +#ifndef CONFIG_ARCH_MX3 + mxcfb_drv_data.vsync_flag = 0; + ipu_enable_irq(IPU_IRQ_SDC_DISP3_VSYNC); + if (!wait_event_interruptible_timeout + (mxcfb_drv_data.vsync_wq, + mxcfb_drv_data.vsync_flag != 0, 1 * HZ)) { + dev_err(fbi->device, + "MXCFB_WAIT_FOR_VSYNC: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + dev_err(fbi->device, + "MXCFB_WAIT_FOR_VSYNC: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } +#endif + break; + } + case MXCFB_GET_FB_IPU_CHAN: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_ch, argp)) + return -EFAULT; + + break; + } + default: + retval = -EINVAL; + } + return retval; +} + +/* + * Function to handle custom ioctls for MXC framebuffer. + * + * @param inode inode struct + * + * @param file file struct + * + * @param cmd Ioctl command to handle + * + * @param arg User pointer to command arguments + * + * @param fbi framebuffer information pointer + */ +static int mxcfb_ioctl_ovl(struct fb_info *fbi, unsigned int cmd, + unsigned long arg) +{ + int retval = 0; + int __user *argp = (void __user *)arg; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + switch (cmd) { + case FBIO_ALLOC: + { + int size; + struct mxcfb_alloc_list *mem; + + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (mem == NULL) + return -ENOMEM; + + if (get_user(size, argp)) + return -EFAULT; + + mem->size = PAGE_ALIGN(size); + + mem->cpu_addr = dma_alloc_coherent(fbi->device, size, + &mem->phy_addr, + GFP_DMA); + if (mem->cpu_addr == NULL) { + kfree(mem); + return -ENOMEM; + } + + list_add(&mem->list, &fb_alloc_list); + + dev_dbg(fbi->device, "allocated %d bytes @ 0x%08X\n", + mem->size, mem->phy_addr); + + if (put_user(mem->phy_addr, argp)) + return -EFAULT; + + break; + } + case FBIO_FREE: + { + unsigned long offset; + struct mxcfb_alloc_list *mem; + + if (get_user(offset, argp)) + return -EFAULT; + + retval = -EINVAL; + list_for_each_entry(mem, &fb_alloc_list, list) { + if (mem->phy_addr == offset) { + list_del(&mem->list); + dma_free_coherent(fbi->device, + mem->size, + mem->cpu_addr, + mem->phy_addr); + kfree(mem); + retval = 0; + break; + } + } + + break; + } + case MXCFB_SET_OVERLAY_POS: + { + struct mxcfb_pos pos; + if (copy_from_user(&pos, (void *)arg, sizeof(pos))) { + retval = -EFAULT; + break; + } + retval = ipu_disp_set_window_pos(mxc_fbi->ipu_ch, + pos.x, pos.y); + break; + } + case MXCFB_GET_FB_IPU_CHAN: + { + struct mxcfb_info *mxc_fbi = + (struct mxcfb_info *)fbi->par; + + if (put_user(mxc_fbi->ipu_ch, argp)) + return -EFAULT; + + break; + } + default: + retval = -EINVAL; + } + return retval; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *info) +{ + int retval; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + dev_dbg(info->device, "blank = %d\n", blank); + + if (mxc_fbi->blank == blank) + return 0; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + ipu_disable_channel(MEM_SDC_BG, true); + gpio_lcd_inactive(); + break; + case FB_BLANK_UNBLANK: + gpio_lcd_active(); + ipu_enable_channel(MEM_SDC_BG); + break; + } + return 0; +} + +/* + * mxcfb_blank_ovl(): + * Blank the display. + */ +static int mxcfb_blank_ovl(int blank, struct fb_info *info) +{ + int retval; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + dev_dbg(info->device, "ovl blank = %d\n", blank); + + if (mxc_fbi->blank == blank) + return 0; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + ipu_disable_channel(MEM_SDC_FG, true); + break; + case FB_BLANK_UNBLANK: + ipu_enable_channel(MEM_SDC_FG); + break; + } + return 0; +} + +/* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + * + * @param var Variable screen buffer information + * @param info Framebuffer information pointer + */ +static int +mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + unsigned long lock_flags = 0; + int retval; + u_int y_bottom; + unsigned long base; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + if (var->xoffset > 0) { + dev_dbg(info->device, "x panning not supported\n"); + return -EINVAL; + } + + if ((info->var.xoffset == var->xoffset) && + (info->var.yoffset == var->yoffset)) { + return 0; // No change, do nothing + } + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) { + y_bottom += var->yres; + } + + if (y_bottom > info->var.yres_virtual) { + return -EINVAL; + } + + base = (var->yoffset * var->xres_virtual + var->xoffset); + base *= (var->bits_per_pixel) / 8; + base += info->fix.smem_start; + + down(&mxc_fbi->flip_sem); + + spin_lock_irqsave(&mxc_fbi->fb_lock, lock_flags); + + dev_dbg(info->device, "Updating SDC BG buf %d address=0x%08lX\n", + mxc_fbi->cur_ipu_buf, base); + + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + if (ipu_update_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, base) == 0) { + ipu_select_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu_ch_irq); + } else { + dev_err(info->device, + "Error updating SDC buf %d to address=0x%08lX\n", + mxc_fbi->cur_ipu_buf, base); + } + + spin_unlock_irqrestore(&mxc_fbi->fb_lock, lock_flags); + + dev_dbg(info->device, "Update complete\n"); + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) { + info->var.vmode |= FB_VMODE_YWRAP; + } else { + info->var.vmode &= ~FB_VMODE_YWRAP; + } + + return 0; +} + +/* + * Function to handle custom mmap for MXC framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param vma Pointer to vm_area_struct + */ +static int mxcfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma) +{ + bool found = false; + u32 len; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + struct mxcfb_alloc_list *mem; + + if (offset < fbi->fix.smem_len) { + /* mapping framebuffer memory */ + len = fbi->fix.smem_len - offset; + vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT; + } else { + list_for_each_entry(mem, &fb_alloc_list, list) { + if (offset == mem->phy_addr) { + found = true; + len = mem->size; + break; + } + } + if (!found) { + return -EINVAL; + } + } + + len = PAGE_ALIGN(len); + if (vma->vm_end - vma->vm_start > len) { + return -EINVAL; + } + + /* make buffers write-thru cacheable */ + vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) & + ~L_PTE_BUFFERABLE); + + vma->vm_flags |= VM_IO | VM_RESERVED; + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + dev_dbg(fbi->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + + } + + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_pan_display = mxcfb_pan_display, + .fb_ioctl = mxcfb_ioctl, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +static struct fb_ops mxcfb_ovl_ops = { + .owner = THIS_MODULE, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_pan_display = mxcfb_pan_display, + .fb_ioctl = mxcfb_ioctl_ovl, + .fb_mmap = mxcfb_mmap, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank_ovl, +}; + +static irqreturn_t mxcfb_vsync_irq_handler(int irq, void *dev_id) +{ + struct mxcfb_data *fb_data = dev_id; + + ipu_disable_irq(irq); + + fb_data->vsync_flag = 1; + wake_up_interruptible(&fb_data->vsync_wq); + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + up(&mxc_fbi->flip_sem); + ipu_disable_irq(irq); + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM +/* + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/* + * Suspends the framebuffer and blanks the screen. Power management support + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)drv_data->fbi->par; + struct mxcfb_info *mxc_fbi_ovl = + (struct mxcfb_info *)drv_data->fbi_ovl->par; +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + void *fbmem; +#endif + + drv_data->suspended = true; + + acquire_console_sem(); + fb_set_suspend(drv_data->fbi, 1); + fb_set_suspend(drv_data->fbi_ovl, 1); + release_console_sem(); + + if (mxc_fbi_ovl->blank == FB_BLANK_UNBLANK) { + ipu_disable_channel(MEM_SDC_FG, true); + } + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) { +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + if (drv_data->fbi->fix.smem_start != FB_RAM_BASE_ADDR) { + fbmem = ioremap(FB_RAM_BASE_ADDR, FB_RAM_SIZE); + memcpy(fbmem, drv_data->fbi->screen_base, FB_RAM_SIZE); + iounmap(fbmem); + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + ipu_update_channel_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, + FB_RAM_BASE_ADDR); + ipu_select_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + } + ipu_lowpwr_display_enable(); +#else + ipu_disable_channel(MEM_SDC_BG, true); + gpio_lcd_inactive(); +#endif + } + return 0; +} + +/* + * Resumes the framebuffer and unblanks the screen. Power management support + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)drv_data->fbi->par; + struct mxcfb_info *mxc_fbi_ovl = + (struct mxcfb_info *)drv_data->fbi_ovl->par; + + drv_data->suspended = false; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) { +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + ipu_lowpwr_display_disable(); + if (drv_data->fbi->fix.smem_start != FB_RAM_BASE_ADDR) { + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + ipu_update_channel_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, + drv_data->fbi->fix. + smem_start); + ipu_select_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + } +#else + gpio_lcd_active(); + ipu_enable_channel(MEM_SDC_BG); +#endif + } + + if (mxc_fbi_ovl->blank == FB_BLANK_UNBLANK) { + ipu_enable_channel(MEM_SDC_FG); + } + + acquire_console_sem(); + fb_set_suspend(drv_data->fbi, 0); + fb_set_suspend(drv_data->fbi_ovl, 0); + release_console_sem(); + + wake_up_interruptible(&drv_data->suspend_wq); + return 0; +} +#else +#define mxcfb_suspend NULL +#define mxcfb_resume NULL +#endif + +/* + * Main framebuffer functions + */ + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @param use_internal_ram flag on whether to use internal RAM for memory + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi, bool use_internal_ram) +{ + int retval = 0; + +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if (use_internal_ram) { + fbi->fix.smem_len = FB_RAM_SIZE; + fbi->fix.smem_start = FB_RAM_BASE_ADDR; + if (fbi->fix.smem_len < + (fbi->var.yres_virtual * fbi->fix.line_length)) { + dev_err(fbi->device, + "Not enough internal RAM for framebuffer configuration\n"); + retval = -EINVAL; + goto err0; + } + + if (request_mem_region(fbi->fix.smem_start, fbi->fix.smem_len, + fbi->device->driver->name) == NULL) { + dev_err(fbi->device, + "Unable to request internal RAM\n"); + retval = -ENOMEM; + goto err0; + } + + if (!(fbi->screen_base = ioremap(fbi->fix.smem_start, + fbi->fix.smem_len))) { + dev_err(fbi->device, + "Unable to map fb memory to virtual address\n"); + release_mem_region(fbi->fix.smem_start, + fbi->fix.smem_len); + retval = -EIO; + goto err0; + } + + iram_clk = clk_get(NULL, "iram_clk"); + clk_enable(iram_clk); + } else +#endif + { + fbi->fix.smem_len = fbi->var.yres_virtual * + fbi->fix.line_length; + fbi->screen_base = + dma_alloc_writecombine(fbi->device, + fbi->fix.smem_len, + (dma_addr_t *) & fbi->fix.smem_start, + GFP_DMA); + + if (fbi->screen_base == 0) { + dev_err(fbi->device, + "Unable to allocate framebuffer memory\n"); + retval = -EBUSY; + goto err0; + } + } + + dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n", + (uint32_t) fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_size = fbi->fix.smem_len; + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + return 0; + + err0: + fbi->fix.smem_len = 0; + fbi->fix.smem_start = 0; + fbi->screen_base = NULL; + return retval; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if (fbi->fix.smem_start == FB_RAM_BASE_ADDR) { + iounmap(fbi->screen_base); + release_mem_region(fbi->fix.smem_start, fbi->fix.smem_len); + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + clk_disable(iram_clk); + } else +#endif + { + dma_free_writecombine(fbi->device, fbi->fix.smem_len, + fbi->screen_base, fbi->fix.smem_start); + } + fbi->screen_base = 0; + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + fbi->var.activate = FB_ACTIVATE_NOW; + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + spin_lock_init(&mxcfbi->fb_lock); + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + char *mode = pdev->dev.platform_data; + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + struct fb_info *fbi_ovl; + int ret = 0; + + /* + * Initialize FB structures + */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto err0; + } + mxcfbi = (struct mxcfb_info *)fbi->par; + + mxcfbi->ipu_ch_irq = IPU_IRQ_SDC_BG_EOF; + mxcfbi->cur_ipu_buf = 0; + mxcfbi->ipu_ch = MEM_SDC_BG; + + ipu_sdc_set_global_alpha(true, 0xFF); + ipu_sdc_set_color_key(MEM_SDC_BG, false, 0); + + if (ipu_request_irq(IPU_IRQ_SDC_BG_EOF, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(&pdev->dev, "Error registering BG irq handler.\n"); + ret = -EBUSY; + goto err1; + } + ipu_disable_irq(IPU_IRQ_SDC_BG_EOF); + + if (fb_mode == NULL) { + fb_mode = mode; + } + + if (!fb_find_mode(&fbi->var, fbi, fb_mode, mxcfb_modedb, + mxcfb_modedb_sz, NULL, default_bpp)) { + ret = -EBUSY; + goto err2; + } + fb_videomode_to_modelist(mxcfb_modedb, mxcfb_modedb_sz, &fbi->modelist); + + /* Default Y virtual size is 2x panel size */ +#ifndef CONFIG_FB_MXC_INTERNAL_MEM + fbi->var.yres_virtual = fbi->var.yres * 2; +#endif + + mxcfb_drv_data.fbi = fbi; + mxcfb_drv_data.backlight_level = 255; + mxcfb_drv_data.suspended = false; + init_waitqueue_head(&mxcfb_drv_data.suspend_wq); + + mxcfbi->blank = FB_BLANK_NORMAL; + ret = mxcfb_set_par(fbi); + if (ret < 0) { + goto err2; + } + mxcfb_blank(FB_BLANK_UNBLANK, fbi); + + /* + * Register framebuffer + */ + ret = register_framebuffer(fbi); + if (ret < 0) { + goto err2; + } + + /* + * Initialize Overlay FB structures + */ + fbi_ovl = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ovl_ops); + if (!fbi_ovl) { + ret = -ENOMEM; + goto err3; + } + mxcfb_drv_data.fbi_ovl = fbi_ovl; + mxcfbi = (struct mxcfb_info *)fbi_ovl->par; + + mxcfbi->ipu_ch_irq = IPU_IRQ_SDC_FG_EOF; + mxcfbi->cur_ipu_buf = 0; + mxcfbi->ipu_ch = MEM_SDC_FG; + + if (ipu_request_irq(IPU_IRQ_SDC_FG_EOF, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi_ovl) != 0) { + dev_err(fbi->device, "Error registering FG irq handler.\n"); + ret = -EBUSY; + goto err4; + } + ipu_disable_irq(mxcfbi->ipu_ch_irq); + + /* Default Y virtual size is 2x panel size */ + fbi_ovl->var = fbi->var; + fbi_ovl->var.yres_virtual = fbi->var.yres * 2; + + /* Overlay is blanked by default */ + mxcfbi->blank = FB_BLANK_NORMAL; + + ret = mxcfb_set_par(fbi_ovl); + if (ret < 0) { + goto err5; + } + + /* + * Register overlay framebuffer + */ + ret = register_framebuffer(fbi_ovl); + if (ret < 0) { + goto err5; + } + + platform_set_drvdata(pdev, &mxcfb_drv_data); + + init_waitqueue_head(&mxcfb_drv_data.vsync_wq); + if (!cpu_is_mx31() && !cpu_is_mx32()) { + if ((ret = ipu_request_irq(IPU_IRQ_SDC_DISP3_VSYNC, + mxcfb_vsync_irq_handler, + 0, MXCFB_NAME, + &mxcfb_drv_data)) < 0) { + goto err6; + } + ipu_disable_irq(IPU_IRQ_SDC_DISP3_VSYNC); + } + + printk(KERN_INFO "mxcfb: fb registered, using mode %s\n", fb_mode); + return 0; + + err6: + unregister_framebuffer(fbi_ovl); + err5: + ipu_free_irq(IPU_IRQ_SDC_FG_EOF, fbi_ovl); + err4: + fb_dealloc_cmap(&fbi_ovl->cmap); + framebuffer_release(fbi_ovl); + err3: + unregister_framebuffer(fbi); + err2: + ipu_free_irq(IPU_IRQ_SDC_BG_EOF, fbi); + err1: + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + err0: + printk(KERN_ERR "mxcfb: failed to register fb\n"); + return ret; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/* + * Parse user specified options (`video=trident:') + * example: + * video=trident:800x600,bpp=16,noaccel + */ +int mxcfb_setup(char *options) +{ + char *opt; + if (!options || !*options) + return 0; + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + if (!strncmp(opt, "bpp=", 4)) + default_bpp = simple_strtoul(opt + 4, NULL, 0); + else + fb_mode = opt; + } + return 0; +} + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +int __init mxcfb_init(void) +{ + int ret = 0; +#ifndef MODULE + char *option = NULL; +#endif + +#ifndef MODULE + if (fb_get_options("mxcfb", &option)) + return -ENODEV; + mxcfb_setup(option); +#endif + + ret = platform_driver_register(&mxcfb_driver); + return ret; +} + +void mxcfb_exit(void) +{ + struct fb_info *fbi = mxcfb_drv_data.fbi; + + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } + + fbi = mxcfb_drv_data.fbi_ovl; + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } +#ifndef CONFIG_ARCH_MX3 + ipu_free_irq(IPU_IRQ_SDC_DISP3_VSYNC, &mxcfb_drv_data); +#endif + + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC framebuffer driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxcfb_ch7026.c b/drivers/video/mxc/mxcfb_ch7026.c new file mode 100644 index 000000000000..5610b75b7da7 --- /dev/null +++ b/drivers/video/mxc/mxcfb_ch7026.c @@ -0,0 +1,369 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_epson_vga.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/i2c.h> +#include <linux/mxcfb.h> +#include <linux/ipu.h> +#include <mach/hardware.h> + +static struct i2c_client *ch7026_client; + +static int lcd_init(void); +static void lcd_poweron(struct fb_info *info); +static void lcd_poweroff(void); + +static void (*lcd_reset) (void); +static struct regulator *io_reg; +static struct regulator *core_reg; +static struct regulator *analog_reg; + + /* 8 800x600-60 VESA */ +static struct fb_videomode mode = { + NULL, 60, 800, 600, 25000, 88, 40, 23, 1, 128, 4, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA +}; + +static void lcd_init_fb(struct fb_info *info) +{ + struct fb_var_screeninfo var; + + memset(&var, 0, sizeof(var)); + + fb_videomode_to_var(&var, &mode); + + var.activate = FB_ACTIVATE_ALL; + + acquire_console_sem(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + fb_blank(info, FB_BLANK_UNBLANK); + info->flags &= ~FBINFO_MISC_USEREVENT; + release_console_sem(); +} + +static int lcd_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + + if (strcmp(event->info->fix.id, "DISP3 BG - DI1")) + return 0; + + switch (val) { + case FB_EVENT_FB_REGISTERED: + lcd_init_fb(event->info); + lcd_poweron(event->info); + break; + case FB_EVENT_BLANK: + if (*((int *)event->data) == FB_BLANK_UNBLANK) + lcd_poweron(event->info); + else + lcd_poweroff(); + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = lcd_fb_event, +}; + +/*! + * This function is called whenever the SPI slave device is detected. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit lcd_probe(struct device *dev) +{ + int ret = 0; + int i; + struct mxc_lcd_platform_data *plat = dev->platform_data; + + if (plat) { + + io_reg = regulator_get(dev, plat->io_reg); + if (!IS_ERR(io_reg)) { + regulator_set_voltage(io_reg, 1800000, 1800000); + regulator_enable(io_reg); + } else { + io_reg = NULL; + } + + core_reg = regulator_get(dev, plat->core_reg); + if (!IS_ERR(core_reg)) { + regulator_set_voltage(core_reg, 2500000, 2500000); + regulator_enable(core_reg); + } else { + core_reg = NULL; + } + analog_reg = regulator_get(dev, plat->analog_reg); + if (!IS_ERR(analog_reg)) { + regulator_set_voltage(analog_reg, 2775000, 2775000); + regulator_enable(analog_reg); + } else { + analog_reg = NULL; + } + msleep(100); + + lcd_reset = plat->reset; + if (lcd_reset) + lcd_reset(); + } + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "DISP3 BG - DI1") == 0) { + ret = lcd_init(); + if (ret < 0) + goto err; + + lcd_init_fb(registered_fb[i]); + fb_show_logo(registered_fb[i], 0); + lcd_poweron(registered_fb[i]); + } + } + + fb_register_client(&nb); + return 0; +err: + if (io_reg) + regulator_disable(io_reg); + if (core_reg) + regulator_disable(core_reg); + if (analog_reg) + regulator_disable(analog_reg); + + return ret; +} + +static int __devinit ch7026_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + ch7026_client = client; + + return lcd_probe(&client->dev); +} + +static int __devexit ch7026_remove(struct i2c_client *client) +{ + fb_unregister_client(&nb); + lcd_poweroff(); + regulator_put(io_reg); + regulator_put(core_reg); + regulator_put(analog_reg); + + return 0; +} + +static int ch7026_suspend(struct i2c_client *client, pm_message_t message) +{ + return 0; +} + +static int ch7026_resume(struct i2c_client *client) +{ + return 0; +} + +u8 reg_init[][2] = { + { 0x02, 0x01 }, + { 0x02, 0x03 }, + { 0x03, 0x00 }, + { 0x06, 0x6B }, + { 0x08, 0x08 }, + { 0x09, 0x80 }, + { 0x0C, 0x0A }, + { 0x0D, 0x89 }, + { 0x0F, 0x23 }, + { 0x10, 0x20 }, + { 0x11, 0x20 }, + { 0x12, 0x40 }, + { 0x13, 0x28 }, + { 0x14, 0x80 }, + { 0x15, 0x52 }, + { 0x16, 0x58 }, + { 0x17, 0x74 }, + { 0x19, 0x01 }, + { 0x1A, 0x04 }, + { 0x1B, 0x23 }, + { 0x1C, 0x20 }, + { 0x1D, 0x20 }, + { 0x1F, 0x28 }, + { 0x20, 0x80 }, + { 0x21, 0x12 }, + { 0x22, 0x58 }, + { 0x23, 0x74 }, + { 0x25, 0x01 }, + { 0x26, 0x04 }, + { 0x37, 0x20 }, + { 0x39, 0x20 }, + { 0x3B, 0x20 }, + { 0x41, 0xA2 }, + { 0x4D, 0x03 }, + { 0x4E, 0x13 }, + { 0x4F, 0xB1 }, + { 0x50, 0x3B }, + { 0x51, 0x54 }, + { 0x52, 0x12 }, + { 0x53, 0x13 }, + { 0x55, 0xE5 }, + { 0x5E, 0x80 }, + { 0x69, 0x64 }, + { 0x7D, 0x62 }, + { 0x04, 0x00 }, + { 0x06, 0x69 }, + + /* + NOTE: The following five repeated sentences are used here to wait memory initial complete, please don't remove...(you could refer to Appendix A of programming guide document (CH7025(26)B Programming Guide Rev2.03.pdf) for detailed information about memory initialization! + */ + { 0x03, 0x00 }, + { 0x03, 0x00 }, + { 0x03, 0x00 }, + { 0x03, 0x00 }, + { 0x03, 0x00 }, + + { 0x06, 0x68 }, + { 0x02, 0x02 }, + { 0x02, 0x03 }, +}; + +#define REGMAP_LENGTH (sizeof(reg_init) / (2*sizeof(u8))) + +/* + * Send init commands to L4F00242T03 + * + */ +static int lcd_init(void) +{ + int i; + int dat; + + dev_dbg(&ch7026_client->dev, "initializing CH7026\n"); + + /* read device ID */ + msleep(100); + dat = i2c_smbus_read_byte_data(ch7026_client, 0x00); + dev_dbg(&ch7026_client->dev, "read id = 0x%02X\n", dat); + if (dat != 0x54) + return -ENODEV; + + for (i = 0; i < REGMAP_LENGTH; ++i) { + if (i2c_smbus_write_byte_data + (ch7026_client, reg_init[i][0], reg_init[i][1]) < 0) + return -EIO; + } + + return 0; +} + +static int lcd_on; +/* + * Send Power On commands to L4F00242T03 + * + */ +static void lcd_poweron(struct fb_info *info) +{ + u16 data[4]; + u32 refresh; + + if (lcd_on) + return; + + dev_dbg(&ch7026_client->dev, "turning on LCD\n"); + + data[0] = PICOS2KHZ(info->var.pixclock) / 10; + data[2] = info->var.hsync_len + info->var.left_margin + + info->var.xres + info->var.right_margin; + data[3] = info->var.vsync_len + info->var.upper_margin + + info->var.yres + info->var.lower_margin; + + refresh = data[2] * data[3]; + refresh = (PICOS2KHZ(info->var.pixclock) * 1000) / refresh; + data[1] = refresh * 100; + + lcd_on = 1; +} + +/* + * Send Power Off commands to L4F00242T03 + * + */ +static void lcd_poweroff(void) +{ + if (!lcd_on) + return; + + dev_dbg(&ch7026_client->dev, "turning off LCD\n"); + + lcd_on = 0; +} + +static const struct i2c_device_id ch7026_id[] = { + {"ch7026", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ch7026_id); + +static struct i2c_driver ch7026_driver = { + .driver = { + .name = "ch7026", + }, + .probe = ch7026_probe, + .remove = ch7026_remove, + .suspend = ch7026_suspend, + .resume = ch7026_resume, + .id_table = ch7026_id, +}; + +static int __init ch7026_init(void) +{ + return i2c_add_driver(&ch7026_driver); +} + +static void __exit ch7026_exit(void) +{ + i2c_del_driver(&ch7026_driver); +} + +module_init(ch7026_init); +module_exit(ch7026_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("CH7026 VGA driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb_claa_wvga.c b/drivers/video/mxc/mxcfb_claa_wvga.c new file mode 100644 index 000000000000..fde0cafbe291 --- /dev/null +++ b/drivers/video/mxc/mxcfb_claa_wvga.c @@ -0,0 +1,237 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_claa_wvga.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mxcfb.h> +#include <linux/regulator/consumer.h> +#include <mach/hardware.h> + +static void lcd_poweron(void); +static void lcd_poweroff(void); + +static struct platform_device *plcd_dev; +static struct regulator *io_reg; +static struct regulator *core_reg; +static int lcd_on; + +static struct fb_videomode video_modes[] = { + { + /* 800x480 @ 55 Hz , pixel clk @ 25MHz */ + "CLAA-WVGA", 55, 800, 480, 40000, 40, 40, 5, 5, 20, 10, + FB_SYNC_CLK_LAT_FALL, + FB_VMODE_NONINTERLACED, + 0,}, +}; + +static void lcd_init_fb(struct fb_info *info) +{ + struct fb_var_screeninfo var; + + memset(&var, 0, sizeof(var)); + + fb_videomode_to_var(&var, &video_modes[0]); + + var.activate = FB_ACTIVATE_ALL; + var.yres_virtual = var.yres * 2; + + acquire_console_sem(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + info->flags &= ~FBINFO_MISC_USEREVENT; + release_console_sem(); +} + +static int lcd_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + + if (strcmp(event->info->fix.id, "DISP3 BG")) { + return 0; + } + + switch (val) { + case FB_EVENT_FB_REGISTERED: + lcd_init_fb(event->info); + lcd_poweron(); + break; + case FB_EVENT_BLANK: + if ((event->info->var.xres != 800) || + (event->info->var.yres != 480)) { + break; + } + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + lcd_poweron(); + } else { + lcd_poweroff(); + } + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = lcd_fb_event, +}; + +/*! + * This function is called whenever the SPI slave device is detected. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit lcd_probe(struct platform_device *pdev) +{ + int i; + struct mxc_lcd_platform_data *plat = pdev->dev.platform_data; + + if (plat) { + if (plat->reset) + plat->reset(); + + io_reg = regulator_get(&pdev->dev, plat->io_reg); + if (IS_ERR(io_reg)) + io_reg = NULL; + core_reg = regulator_get(&pdev->dev, plat->core_reg); + if (!IS_ERR(core_reg)) { + regulator_set_voltage(io_reg, 1800000, 1800000); + } else { + core_reg = NULL; + } + } + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "DISP3 BG") == 0) { + lcd_init_fb(registered_fb[i]); + fb_show_logo(registered_fb[i], 0); + lcd_poweron(); + } else if (strcmp(registered_fb[i]->fix.id, "DISP3 FG") == 0) { + lcd_init_fb(registered_fb[i]); + } + } + + fb_register_client(&nb); + + plcd_dev = pdev; + + return 0; +} + +static int __devexit lcd_remove(struct platform_device *pdev) +{ + fb_unregister_client(&nb); + lcd_poweroff(); + if (io_reg) + regulator_put(io_reg); + if (core_reg) + regulator_put(core_reg); + + return 0; +} + +#ifdef CONFIG_PM +static int lcd_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int lcd_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define lcd_suspend NULL +#define lcd_resume NULL +#endif + +/*! + * platform driver structure for CLAA WVGA + */ +static struct platform_driver lcd_driver = { + .driver = { + .name = "lcd_claa"}, + .probe = lcd_probe, + .remove = __devexit_p(lcd_remove), + .suspend = lcd_suspend, + .resume = lcd_resume, +}; + +/* + * Send Power On commands to L4F00242T03 + * + */ +static void lcd_poweron(void) +{ + if (lcd_on) + return; + + dev_dbg(&plcd_dev->dev, "turning on LCD\n"); + if (core_reg) + regulator_enable(core_reg); + if (io_reg) + regulator_enable(io_reg); + lcd_on = 1; +} + +/* + * Send Power Off commands to L4F00242T03 + * + */ +static void lcd_poweroff(void) +{ + lcd_on = 0; + dev_dbg(&plcd_dev->dev, "turning off LCD\n"); + if (io_reg) + regulator_disable(io_reg); + if (core_reg) + regulator_disable(core_reg); +} + +static int __init claa_lcd_init(void) +{ + return platform_driver_register(&lcd_driver); +} + +static void __exit claa_lcd_exit(void) +{ + platform_driver_unregister(&lcd_driver); +} + +module_init(claa_lcd_init); +module_exit(claa_lcd_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("CLAA WVGA LCD init driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb_epson.c b/drivers/video/mxc/mxcfb_epson.c new file mode 100644 index 000000000000..25b05e4b1a0e --- /dev/null +++ b/drivers/video/mxc/mxcfb_epson.c @@ -0,0 +1,1158 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxcfb_epson.c + * + * @brief MXC Frame buffer driver for ADC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <mach/hardware.h> +#include <asm/io.h> +#include <asm/mach-types.h> +#include <asm/uaccess.h> +#include <mach/ipu.h> +#include <mach/mxcfb.h> + +#define PARTIAL_REFRESH +#define MXCFB_REFRESH_DEFAULT MXCFB_REFRESH_PARTIAL +/* + * Driver name + */ +#define MXCFB_NAME "MXCFB_EPSON" + +#define MXCFB_SCREEN_TOP_OFFSET 0 +#define MXCFB_SCREEN_LEFT_OFFSET 2 +#define MXCFB_SCREEN_WIDTH 176 +#define MXCFB_SCREEN_HEIGHT 220 + +/*! + * Enum defining Epson panel commands. + */ +enum { + DISON = 0xAF, + DISOFF = 0xAE, + DISCTL = 0xCA, + SD_CSET = 0x15, + SD_PSET = 0x75, + DATCTL = 0xBC, + SLPIN = 0x95, + SLPOUT = 0x94, + DISNOR = 0xA6, + RAMWR = 0x5C, + VOLCTR = 0xC6, + GCP16 = 0xCC, + GCP64 = 0xCB, +}; + +struct mxcfb_info { + int open_count; + int blank; + uint32_t disp_num; + + u32 pseudo_palette[16]; + + int32_t cur_update_mode; + dma_addr_t alloc_start_paddr; + void *alloc_start_vaddr; + u32 alloc_size; + uint32_t snoop_window_size; +}; + +struct mxcfb_data { + struct fb_info *fbi; + volatile int32_t vsync_flag; + wait_queue_head_t vsync_wq; + wait_queue_head_t suspend_wq; + bool suspended; +}; + +static struct mxcfb_data mxcfb_drv_data; +static unsigned long default_bpp = 16; + +void slcd_gpio_config(void); +extern void gpio_lcd_active(void); +static int mxcfb_blank(int blank, struct fb_info *fbi); + +static uint32_t bpp_to_pixfmt(int bpp) +{ + uint32_t pixfmt = 0; + switch (bpp) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +/*! + * This function sets display region in the Epson panel + * + * @param disp display panel to config + * @param x1 x-coordinate of one vertex. + * @param x2 x-coordinate of second vertex. + * @param y1 y-coordinate of one vertex. + * @param y2 y-coordinate of second vertex. + */ +void set_panel_region(int disp, uint32_t x1, uint32_t x2, + uint32_t y1, uint32_t y2) +{ + uint32_t param[8]; + + memset(param, 0, sizeof(uint32_t) * 8); + param[0] = x1; + param[2] = x2; + param[4] = y1; + param[6] = y2; + + // SD_CSET + ipu_adc_write_cmd(disp, CMD, SD_CSET, param, 4); + // SD_PSET + + ipu_adc_write_cmd(disp, CMD, SD_PSET, &(param[4]), 4); +} + +/*! + * Function to create and initiate template command buffer for ADC. This + * template will be written to Panel memory. + */ +static void init_channel_template(int disp) +{ + /* template command buffer for ADC is 32 */ + uint32_t tempCmd[TEMPLATE_BUF_SIZE]; + uint32_t i = 0; + + memset(tempCmd, 0, sizeof(uint32_t) * TEMPLATE_BUF_SIZE); + /* setup update display region */ + /* whole the screen during init */ + /*WRITE Y COORDINATE CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, SD_PSET); + /*WRITE Y START ADDRESS CMND LSB[22:8] */ + tempCmd[i++] = ipu_adc_template_gen(WR_YADDR, 1, SINGLE_STEP, 0x01); + /*WRITE Y START ADDRESS CMND MSB[22:16] */ + tempCmd[i++] = ipu_adc_template_gen(WR_YADDR, 1, SINGLE_STEP, 0x09); + /*WRITE Y STOP ADDRESS CMND LSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, + MXCFB_SCREEN_HEIGHT - 1); + /*WRITE Y STOP ADDRESS CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, 0); + /*WRITE X COORDINATE CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, SD_CSET); + /*WRITE X ADDRESS CMND LSB[7:0] */ + tempCmd[i++] = ipu_adc_template_gen(WR_XADDR, 1, SINGLE_STEP, 0x01); + /*WRITE X ADDRESS CMND MSB[22:8] */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, 0); + /*WRITE X STOP ADDRESS CMND LSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, + MXCFB_SCREEN_WIDTH + 1); + /*WRITE X STOP ADDRESS CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, 0); + /*WRITE MEMORY CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, RAMWR); + /*WRITE DATA CMND and STP */ + tempCmd[i++] = ipu_adc_template_gen(WR_DATA, 1, STOP, 0); + + ipu_adc_write_template(disp, tempCmd, true); +} + +/*! + * Function to initialize the panel. First it resets the panel and then + * initilizes panel. + */ +static void _init_panel(int disp) +{ + uint32_t cmd_param; + uint32_t i; + + gpio_lcd_active(); + slcd_gpio_config(); + + // DATCTL +#ifdef CONFIG_FB_MXC_ASYNC_PANEL_IFC_16_BIT + // 16-bit 565 mode + cmd_param = 0x28; +#else + // 8-bit 666 mode + cmd_param = 0x08; +#endif + ipu_adc_write_cmd(disp, CMD, DATCTL, &cmd_param, 1); + + // Sleep OUT + ipu_adc_write_cmd(disp, CMD, SLPOUT, 0, 0); + + // Set display to white + // Setup page and column addresses + set_panel_region(disp, MXCFB_SCREEN_LEFT_OFFSET, + MXCFB_SCREEN_WIDTH + MXCFB_SCREEN_LEFT_OFFSET - 1, + 0, MXCFB_SCREEN_HEIGHT - 1); + // Do RAM write cmd + ipu_adc_write_cmd(disp, CMD, RAMWR, 0, 0); +#ifdef CONFIG_FB_MXC_ASYNC_PANEL_IFC_16_BIT + for (i = 0; i < (MXCFB_SCREEN_WIDTH * MXCFB_SCREEN_HEIGHT); i++) +#else + for (i = 0; i < (MXCFB_SCREEN_WIDTH * MXCFB_SCREEN_HEIGHT * 3); i++) +#endif + ipu_adc_write_cmd(disp, DAT, 0xFFFF, 0, 0); + + // Pause 80 ms + mdelay(80); + + // Display ON + ipu_adc_write_cmd(disp, CMD, DISON, 0, 0); + // Pause 200 ms + mdelay(200); + + pr_debug("initialized panel\n"); +} + +#ifdef PARTIAL_REFRESH +static irqreturn_t mxcfb_sys2_eof_irq_handler(int irq, void *dev_id) +{ + ipu_channel_params_t params; + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + uint32_t stat[2], seg_size; + uint32_t lsb, msb; + uint32_t update_height, start_line, start_addr, end_line, end_addr; + uint32_t stride_pixels = (fbi->fix.line_length * 8) / + fbi->var.bits_per_pixel; + + ipu_adc_get_snooping_status(&stat[0], &stat[1]); + //DPRINTK("snoop status = 0x%08X%08X\n", stat[1], stat[0]); + + if (!stat[0] && !stat[1]) { + dev_err(fbi->device, "error no bus snooping bits set\n"); + return IRQ_HANDLED; + } + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); + + lsb = ffs(stat[0]); + if (lsb) { + lsb--; + } else { + lsb = ffs(stat[1]); + lsb += 32 - 1; + } + msb = fls(stat[1]); + if (msb) { + msb += 32; + } else { + msb = fls(stat[0]); + } + + seg_size = mxc_fbi->snoop_window_size / 64; + + start_addr = lsb * seg_size; // starting address offset + start_line = start_addr / fbi->fix.line_length; + start_addr = start_line * fbi->fix.line_length; // Addr aligned to line + start_addr += fbi->fix.smem_start; + + end_addr = msb * seg_size; // ending address offset + end_line = end_addr / fbi->fix.line_length; + end_line++; + + if (end_line > fbi->var.yres) { + end_line = fbi->var.yres; + } + + update_height = end_line - start_line; + dev_dbg(fbi->device, "updating rows %d to %d, start addr = 0x%08X\n", + start_line, end_line, start_addr); + + ipu_uninit_channel(ADC_SYS1); + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = start_line; + ipu_init_channel(ADC_SYS1, ¶ms); + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + MXCFB_SCREEN_WIDTH, + update_height, + stride_pixels, + IPU_ROTATE_NONE, (dma_addr_t) start_addr, 0, + 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + ipu_enable_irq(IPU_IRQ_ADC_SYS1_EOF); + + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_sys1_eof_irq_handler(int irq, void *dev_id) +{ + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_channel(ADC_SYS1, false); + + ipu_enable_channel(ADC_SYS2); + ipu_enable_irq(IPU_IRQ_ADC_SYS2_EOF); + + return IRQ_HANDLED; +} +#endif + +/*! + * Function to initialize Asynchronous Display Controller. It also initilizes + * the ADC System 1 channel. Configure ADC display 0 parallel interface for + * the panel. + * + * @param fbi framebuffer information pointer + */ +static void mxcfb_init_panel(struct fb_info *fbi) +{ + int msb; + int panel_stride; + ipu_channel_params_t params; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + +#ifdef CONFIG_FB_MXC_ASYNC_PANEL_IFC_16_BIT + uint32_t pix_fmt = IPU_PIX_FMT_RGB565; + ipu_adc_sig_cfg_t sig = { 0, 0, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_WCS, + IPU_ADC_IFC_MODE_SYS80_TYPE2, + 16, 0, 0, IPU_ADC_SER_NO_RW + }; + mxc_fbi->disp_num = DISP0; +#elif defined(CONFIG_FB_MXC_ASYNC_PANEL_IFC_8_BIT) + uint32_t pix_fmt = IPU_PIX_FMT_RGB666; + ipu_adc_sig_cfg_t sig = { 0, 0, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_WCS, + IPU_ADC_IFC_MODE_SYS80_TYPE2, + 8, 0, 0, IPU_ADC_SER_NO_RW + }; + mxc_fbi->disp_num = DISP0; +#else + uint32_t pix_fmt = IPU_PIX_FMT_RGB565; + ipu_adc_sig_cfg_t sig = { 0, 1, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_SERIAL, + IPU_ADC_IFC_MODE_5WIRE_SERIAL_CLK, + 16, 0, 0, IPU_ADC_SER_NO_RW + }; + fbi->disp_num = DISP1; +#endif + +#ifdef PARTIAL_REFRESH + if (ipu_request_irq(IPU_IRQ_ADC_SYS2_EOF, mxcfb_sys2_eof_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering SYS2 irq handler.\n"); + return; + } + + if (ipu_request_irq(IPU_IRQ_ADC_SYS1_EOF, mxcfb_sys1_eof_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering SYS1 irq handler.\n"); + return; + } + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); +#endif + // Init DI interface + msb = fls(MXCFB_SCREEN_WIDTH); + if (!(MXCFB_SCREEN_WIDTH & ((1UL << msb) - 1))) + msb--; // Already aligned to power 2 + panel_stride = 1UL << msb; + ipu_adc_init_panel(mxc_fbi->disp_num, + MXCFB_SCREEN_WIDTH + MXCFB_SCREEN_LEFT_OFFSET, + MXCFB_SCREEN_HEIGHT, + pix_fmt, panel_stride, sig, XY, 0, VsyncInternal); + + ipu_adc_init_ifc_timing(mxc_fbi->disp_num, true, + 190, 17, 104, 190, 5000000); + ipu_adc_init_ifc_timing(mxc_fbi->disp_num, false, 123, 17, 68, 0, 0); + + // Needed to turn on ADC clock for panel init + memset(¶ms, 0, sizeof(params)); + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET; + ipu_init_channel(ADC_SYS1, ¶ms); + + _init_panel(mxc_fbi->disp_num); + init_channel_template(mxc_fbi->disp_num); +} + +int mxcfb_set_refresh_mode(struct fb_info *fbi, int mode, + struct mxcfb_rect *update_region) +{ + unsigned long start_addr; + int ret_mode; + uint32_t dummy; + ipu_channel_params_t params; + struct mxcfb_rect rect; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + uint32_t stride_pixels = (fbi->fix.line_length * 8) / + fbi->var.bits_per_pixel; + uint32_t memsize = fbi->fix.smem_len; + + if (mxc_fbi->cur_update_mode == mode) + return mode; + + ret_mode = mxc_fbi->cur_update_mode; + + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, 0, 0, 0); +#ifdef PARTIAL_REFRESH + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); + ipu_adc_set_update_mode(ADC_SYS2, IPU_ADC_REFRESH_NONE, 0, 0, 0); +#endif + + ipu_disable_channel(ADC_SYS1, true); + ipu_clear_irq(IPU_IRQ_ADC_SYS1_EOF); +#ifdef PARTIAL_REFRESH + ipu_disable_channel(ADC_SYS2, true); + ipu_clear_irq(IPU_IRQ_ADC_SYS2_EOF); +#endif + ipu_adc_get_snooping_status(&dummy, &dummy); + + mxc_fbi->cur_update_mode = mode; + + switch (mode) { + case MXCFB_REFRESH_OFF: + if (ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); + if (ipu_adc_set_update_mode(ADC_SYS2, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); +#if 0 + ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + 1, 1, 4, + IPU_ROTATE_NONE, + fbi->fix.smem_start, + fbi->fix.smem_start, 0, 0); + ipu_enable_channel(ADC_SYS2); + ipu_select_buffer(ADC_SYS2, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(ADC_SYS2, IPU_INPUT_BUFFER, 1); + msleep(10); +#endif + ipu_uninit_channel(ADC_SYS1); +#ifdef PARTIAL_REFRESH + ipu_uninit_channel(ADC_SYS2); +#endif + break; + case MXCFB_REFRESH_PARTIAL: +#ifdef PARTIAL_REFRESH + ipu_adc_get_snooping_status(&dummy, &dummy); + + params.adc_sys2.disp = DISP0; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = 0; + params.adc_sys2.out_top = 0; + ipu_init_channel(ADC_SYS2, ¶ms); + + if (ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) { + dev_err(fbi->device, "Error enabling auto refesh.\n"); + } + if (ipu_adc_set_update_mode + (ADC_SYS2, IPU_ADC_AUTO_REFRESH_SNOOP, 30, + fbi->fix.smem_start, &memsize) < 0) { + dev_err(fbi->device, "Error enabling auto refesh.\n"); + } + mxc_fbi->snoop_window_size = memsize; + + ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + 1, 1, 4, + IPU_ROTATE_NONE, + fbi->fix.smem_start, 0, 0, 0); + + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET; + ipu_init_channel(ADC_SYS1, ¶ms); + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + MXCFB_SCREEN_WIDTH, MXCFB_SCREEN_HEIGHT, + stride_pixels, IPU_ROTATE_NONE, + fbi->fix.smem_start, 0, 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + ipu_enable_irq(IPU_IRQ_ADC_SYS1_EOF); + break; +#endif + case MXCFB_REFRESH_AUTO: + if (update_region == NULL) { + update_region = ▭ + rect.top = 0; + rect.left = 0; + rect.height = MXCFB_SCREEN_HEIGHT; + rect.width = MXCFB_SCREEN_WIDTH; + } + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET + + update_region->left; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET + + update_region->top; + ipu_init_channel(ADC_SYS1, ¶ms); + + // Address aligned to line + start_addr = update_region->top * fbi->fix.line_length; + start_addr += fbi->fix.smem_start; + start_addr += update_region->left * fbi->var.bits_per_pixel / 8; + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + update_region->width, + update_region->height, stride_pixels, + IPU_ROTATE_NONE, start_addr, 0, 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + + if (ipu_adc_set_update_mode + (ADC_SYS1, IPU_ADC_AUTO_REFRESH_SNOOP, 30, + fbi->fix.smem_start, &memsize) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); + + mxc_fbi->snoop_window_size = memsize; + + break; + } + return ret_mode; +} + +/* + * Open the main framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param user Set if opened by user or clear if opened by kernel + */ +static int mxcfb_open(struct fb_info *fbi, int user) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mxc_fbi->open_count++; + + retval = mxcfb_blank(FB_BLANK_UNBLANK, fbi); + return retval; +} + +/* + * Close the main framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param user Set if opened by user or clear if opened by kernel + */ +static int mxcfb_release(struct fb_info *fbi, int user) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + --mxc_fbi->open_count; + if (mxc_fbi->open_count == 0) { + retval = mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + } + return retval; +} + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + // Set framebuffer id to IPU display number. + strcpy(fix->id, "DISP0 FB"); + fix->id[4] = '0' + mxc_fbi->disp_num; + + // Init settings based on the panel size + fix->line_length = MXCFB_SCREEN_WIDTH * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 0; + fix->ypanstep = 0; + + return 0; +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval = 0; + int mode; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mode = mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + + mxcfb_set_fix(fbi); + + if (mode != MXCFB_REFRESH_OFF) { +#ifdef PARTIAL_REFRESH + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_PARTIAL, NULL); +#else + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_AUTO, NULL); +#endif + } + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi) +{ + if (var->xres > MXCFB_SCREEN_WIDTH) + var->xres = MXCFB_SCREEN_WIDTH; + if (var->yres > MXCFB_SCREEN_HEIGHT) + var->yres = MXCFB_SCREEN_HEIGHT; + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + var->nonstd = 0; + + var->pixclock = -1; + var->left_margin = -1; + var->right_margin = -1; + var->upper_margin = -1; + var->lower_margin = -1; + var->hsync_len = -1; + var->vsync_len = -1; + + var->vmode = FB_VMODE_NONINTERLACED; + var->sync = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int +mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *fbi) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + dev_dbg(fbi->device, "blank = %d\n", blank); + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + break; + case FB_BLANK_UNBLANK: + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_DEFAULT, NULL); + break; + } + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_open = mxcfb_open, + .fb_release = mxcfb_release, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi) +{ + u32 msb; + u32 offset; + struct mxcfb_info *mxcfbi = fbi->par; + + fbi->fix.smem_len = fbi->var.xres_virtual * fbi->var.yres_virtual * 4; + + // Set size to power of 2. + msb = fls(fbi->fix.smem_len); + if (!(fbi->fix.smem_len & ((1UL << msb) - 1))) + msb--; // Already aligned to power 2 + if (msb < 11) + msb = 11; + mxcfbi->alloc_size = (1UL << msb) * 2; + + mxcfbi->alloc_start_vaddr = dma_alloc_coherent(fbi->device, + mxcfbi->alloc_size, + &mxcfbi-> + alloc_start_paddr, + GFP_KERNEL | GFP_DMA); + + if (mxcfbi->alloc_start_vaddr == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); + return -ENOMEM; + } + dev_dbg(fbi->device, "allocated fb memory @ paddr=0x%08X, size=%d.\n", + (uint32_t) mxcfbi->alloc_start_paddr, mxcfbi->alloc_size); + + offset = + ((mxcfbi->alloc_size / 2) - 1) & ~((mxcfbi->alloc_size / 2) - 1); + fbi->fix.smem_start = mxcfbi->alloc_start_paddr + offset; + dev_dbg(fbi->device, "aligned fb start @ paddr=0x%08lX, size=%u.\n", + fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_base = mxcfbi->alloc_start_vaddr + offset; + + /* Clear the screen */ + memset(fbi->screen_base, 0, fbi->fix.smem_len); + return 0; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + dma_free_coherent(fbi->device, mxc_fbi->alloc_size, + mxc_fbi->alloc_start_vaddr, + mxc_fbi->alloc_start_paddr); + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + /* + * Fill in fb_info structure information + */ + fbi->var.xres = fbi->var.xres_virtual = MXCFB_SCREEN_WIDTH; + fbi->var.yres = fbi->var.yres_virtual = MXCFB_SCREEN_HEIGHT; + fbi->var.activate = FB_ACTIVATE_NOW; + mxcfb_check_var(&fbi->var, fbi); + + mxcfb_set_fix(fbi); + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + struct fb_info *fbi; + struct mxcfb_info *mxc_fbi; + int ret; + + platform_set_drvdata(pdev, &mxcfb_drv_data); + + /* + * Initialize FB structures + */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto err0; + } + mxcfb_drv_data.fbi = fbi; + mxc_fbi = fbi->par; + + mxcfb_drv_data.suspended = false; + init_waitqueue_head(&mxcfb_drv_data.suspend_wq); + + /* + * Allocate memory + */ + ret = mxcfb_map_video_memory(fbi); + if (ret < 0) { + goto err1; + } + + mxcfb_init_panel(fbi); + + /* + * Register framebuffer + */ + ret = register_framebuffer(fbi); + if (ret < 0) { + goto err2; + } + + dev_info(&pdev->dev, "%s registered\n", MXCFB_NAME); + + return 0; + + err2: + mxcfb_unmap_video_memory(fbi); + err1: + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + err0: + return ret; +} + +#ifdef CONFIG_PM +/*! + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/*! + * Suspends the framebuffer and blanks the screen. Power management support + * + * @param pdev pointer to device structure. + * @param state state of the device. + * + * @return success + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct fb_info *fbi = drv_data->fbi; + struct mxcfb_info *mxc_fbi = fbi->par; + + drv_data->suspended = true; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + /* Display OFF */ + ipu_adc_write_cmd(mxc_fbi->disp_num, CMD, DISOFF, 0, 0); + + return 0; +} + +/*! + * Resumes the framebuffer and unblanks the screen. Power management support + * + * @param pdev pointer to device structure. + * + * @return success + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct fb_info *fbi = drv_data->fbi; + struct mxcfb_info *mxc_fbi = fbi->par; + + // Display ON + ipu_adc_write_cmd(mxc_fbi->disp_num, CMD, DISON, 0, 0); + drv_data->suspended = false; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_DEFAULT, NULL); + wake_up_interruptible(&drv_data->suspend_wq); + + return 0; +} +#else +#define mxcfb_suspend NULL +#define mxcfb_resume NULL +#endif + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/*! + * Device definition for the Framebuffer + */ +static struct platform_device mxcfb_device = { + .name = MXCFB_NAME, + .id = 0, + .dev = { + .coherent_dma_mask = 0xFFFFFFFF, + } +}; + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +static int mxcfb_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&mxcfb_driver); + if (ret == 0) { + ret = platform_device_register(&mxcfb_device); + if (ret != 0) { + platform_driver_unregister(&mxcfb_driver); + } + } + return ret; +} + +static void mxcfb_exit(void) +{ + struct fb_info *fbi = dev_get_drvdata(&mxcfb_device.dev); + + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } + + platform_device_unregister(&mxcfb_device); + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +EXPORT_SYMBOL(mxcfb_set_refresh_mode); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Epson framebuffer driver"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxcfb_epson_vga.c b/drivers/video/mxc/mxcfb_epson_vga.c new file mode 100644 index 000000000000..42d02f907066 --- /dev/null +++ b/drivers/video/mxc/mxcfb_epson_vga.c @@ -0,0 +1,361 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb_epson_vga.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> +#include <linux/mxcfb.h> +#include <linux/ipu.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> + +static struct spi_device *lcd_spi; +static struct device *lcd_dev; + +static void lcd_init(void); +static void lcd_poweron(void); +static void lcd_poweroff(void); + +static void (*lcd_reset) (void); +static struct regulator *io_reg; +static struct regulator *core_reg; + +static struct fb_videomode video_modes[] = { + { + /* 480x640 @ 60 Hz */ + "Epson-VGA", 60, 480, 640, 41701, 60, 41, 10, 5, 20, 10, + 0, + FB_VMODE_NONINTERLACED, + 0,}, +}; + +static void lcd_init_fb(struct fb_info *info) +{ + struct fb_var_screeninfo var; + + memset(&var, 0, sizeof(var)); + + fb_videomode_to_var(&var, &video_modes[0]); + + if (machine_is_mx31_3ds()) { + var.upper_margin = 0; + var.left_margin = 0; + } + + var.activate = FB_ACTIVATE_ALL; + var.yres_virtual = var.yres * 2; + + acquire_console_sem(); + info->flags |= FBINFO_MISC_USEREVENT; + fb_set_var(info, &var); + info->flags &= ~FBINFO_MISC_USEREVENT; + release_console_sem(); +} + +static int lcd_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + + if (strcmp(event->info->fix.id, "DISP3 BG")) { + return 0; + } + + switch (val) { + case FB_EVENT_FB_REGISTERED: + lcd_init_fb(event->info); + lcd_poweron(); + break; + case FB_EVENT_BLANK: + if ((event->info->var.xres != 480) || + (event->info->var.yres != 640)) { + break; + } + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + lcd_poweron(); + } else { + lcd_poweroff(); + } + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = lcd_fb_event, +}; + +/*! + * This function is called whenever the SPI slave device is detected. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit lcd_probe(struct device *dev) +{ + int i; + struct mxc_lcd_platform_data *plat = dev->platform_data; + + lcd_dev = dev; + + if (plat) { + io_reg = regulator_get(dev, plat->io_reg); + if (!IS_ERR(io_reg)) { + regulator_set_voltage(io_reg, 1800000, 1800000); + regulator_enable(io_reg); + } + core_reg = regulator_get(dev, plat->core_reg); + if (!IS_ERR(core_reg)) { + regulator_set_voltage(core_reg, 2800000, 2800000); + regulator_enable(core_reg); + } + + lcd_reset = plat->reset; + if (lcd_reset) + lcd_reset(); + } + + lcd_init(); + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "DISP3 BG") == 0) { + lcd_init_fb(registered_fb[i]); + fb_show_logo(registered_fb[i], 0); + lcd_poweron(); + } + } + + fb_register_client(&nb); + + return 0; +} + +static int __devinit lcd_plat_probe(struct platform_device *pdev) +{ + ipu_adc_sig_cfg_t sig; + ipu_channel_params_t param; + + memset(&sig, 0, sizeof(sig)); + sig.ifc_width = 9; + sig.clk_pol = 1; + ipu_init_async_panel(0, IPU_PANEL_SERIAL, 90, IPU_PIX_FMT_GENERIC, sig); + + memset(¶m, 0, sizeof(param)); + ipu_init_channel(DIRECT_ASYNC1, ¶m); + + return lcd_probe(&pdev->dev); +} + +static int __devinit lcd_spi_probe(struct spi_device *spi) +{ + lcd_spi = spi; + + spi->bits_per_word = 9; + spi_setup(spi); + + return lcd_probe(&spi->dev); +} + +static int __devexit lcd_remove(struct device *dev) +{ + fb_unregister_client(&nb); + lcd_poweroff(); + regulator_put(io_reg); + regulator_put(core_reg); + + return 0; +} + +static int __devexit lcd_spi_remove(struct spi_device *spi) +{ + int ret = lcd_remove(&spi->dev); + lcd_spi = NULL; + return ret; +} + +static int __devexit lcd_plat_remove(struct platform_device *pdev) +{ + return lcd_remove(&pdev->dev); +} + +static int lcd_suspend(struct spi_device *spi, pm_message_t message) +{ + lcd_poweroff(); + return 0; +} + +static int lcd_resume(struct spi_device *spi) +{ + if (lcd_reset) + lcd_reset(); + + lcd_init(); + lcd_poweron(); + return 0; +} + +/*! + * spi driver structure for LTV350QV + */ +static struct spi_driver lcd_spi_dev_driver = { + + .driver = { + .name = "lcd_spi", + .owner = THIS_MODULE, + }, + .probe = lcd_spi_probe, + .remove = __devexit_p(lcd_spi_remove), + .suspend = lcd_suspend, + .resume = lcd_resume, +}; + +static struct platform_driver lcd_plat_driver = { + .driver = { + .name = "lcd_spi", + .owner = THIS_MODULE, + }, + .probe = lcd_plat_probe, + .remove = __devexit_p(lcd_plat_remove), +}; + +#define param(x) ((x) | 0x100) + +/* + * Send init commands to L4F00242T03 + * + */ +static void lcd_init(void) +{ + const u16 cmd[] = { 0x36, param(0), 0x3A, param(0x60) }; + + dev_dbg(lcd_dev, "initializing LCD\n"); + if (lcd_spi) { + spi_write(lcd_spi, (const u8 *)cmd, ARRAY_SIZE(cmd)); + } else { + ipu_disp_direct_write(DIRECT_ASYNC1, 0x36, 0); + ipu_disp_direct_write(DIRECT_ASYNC1, 0x100, 0); + ipu_disp_direct_write(DIRECT_ASYNC1, 0x3A, 0); + ipu_disp_direct_write(DIRECT_ASYNC1, 0x160, 0); + msleep(1); + ipu_uninit_channel(DIRECT_ASYNC1); + } +} + +static int lcd_on; +/* + * Send Power On commands to L4F00242T03 + * + */ +static void lcd_poweron(void) +{ + const u16 slpout = 0x11; + const u16 dison = 0x29; + ipu_channel_params_t param; + if (lcd_on) + return; + + dev_dbg(lcd_dev, "turning on LCD\n"); + + if (lcd_spi) { + msleep(60); + spi_write(lcd_spi, (const u8 *)&slpout, 1); + msleep(60); + spi_write(lcd_spi, (const u8 *)&dison, 1); + } else { + memset(¶m, 0, sizeof(param)); + ipu_init_channel(DIRECT_ASYNC1, ¶m); + ipu_disp_direct_write(DIRECT_ASYNC1, slpout, 0); + msleep(60); + ipu_disp_direct_write(DIRECT_ASYNC1, dison, 0); + msleep(1); + ipu_uninit_channel(DIRECT_ASYNC1); + } + lcd_on = 1; +} + +/* + * Send Power Off commands to L4F00242T03 + * + */ +static void lcd_poweroff(void) +{ + const u16 slpin = 0x10; + const u16 disoff = 0x28; + ipu_channel_params_t param; + if (!lcd_on) + return; + + dev_dbg(lcd_dev, "turning off LCD\n"); + + if (lcd_spi) { + msleep(60); + spi_write(lcd_spi, (const u8 *)&disoff, 1); + msleep(60); + spi_write(lcd_spi, (const u8 *)&slpin, 1); + } else { + memset(¶m, 0, sizeof(param)); + ipu_init_channel(DIRECT_ASYNC1, ¶m); + ipu_disp_direct_write(DIRECT_ASYNC1, disoff, 0); + msleep(60); + ipu_disp_direct_write(DIRECT_ASYNC1, slpin, 0); + msleep(1); + ipu_uninit_channel(DIRECT_ASYNC1); + } + lcd_on = 0; +} + +static int __init epson_lcd_init(void) +{ + int ret; + + ret = platform_driver_register(&lcd_plat_driver); + if (ret) + return ret; + + return spi_register_driver(&lcd_spi_dev_driver); + +} + +static void __exit epson_lcd_exit(void) +{ + spi_unregister_driver(&lcd_spi_dev_driver); +} + +module_init(epson_lcd_init); +module_exit(epson_lcd_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Epson VGA LCD init driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb_modedb.c b/drivers/video/mxc/mxcfb_modedb.c new file mode 100644 index 000000000000..ad31c6b4f856 --- /dev/null +++ b/drivers/video/mxc/mxcfb_modedb.c @@ -0,0 +1,69 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/mxcfb.h> + +struct fb_videomode mxcfb_modedb[] = { + { + /* 240x320 @ 60 Hz */ + "Sharp-QVGA", 60, 240, 320, 185925, 9, 16, 7, 9, 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE | + FB_SYNC_DATA_INVERT | FB_SYNC_CLK_IDLE_EN, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* 240x33 @ 60 Hz */ + "Sharp-CLI", 60, 240, 33, 185925, 9, 16, 7, 9 + 287, 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE | + FB_SYNC_DATA_INVERT | FB_SYNC_CLK_IDLE_EN, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* 640x480 @ 60 Hz */ + "NEC-VGA", 60, 640, 480, 38255, 144, 0, 34, 40, 1, 1, + FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* 640x480 @ 60 Hz */ + "CPT-VGA", 60, 640, 480, 39683, 45, 114, 33, 11, 1, 1, + FB_SYNC_CLK_LAT_FALL, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* NTSC TV output */ + "TV-NTSC", 60, 640, 480, 37538, + 38, 858 - 640 - 38 - 3, + 36, 518 - 480 - 36 - 1, + 3, 1, + 0, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* PAL TV output */ + "TV-PAL", 50, 640, 480, 37538, + 38, 960 - 640 - 38 - 32, + 32, 555 - 480 - 32 - 3, + 32, 3, + 0, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* TV output VGA mode, 640x480 @ 65 Hz */ + "TV-VGA", 60, 640, 480, 40574, 35, 45, 9, 1, 46, 5, + 0, FB_VMODE_NONINTERLACED, 0, + }, +}; + +int mxcfb_modedb_sz = ARRAY_SIZE(mxcfb_modedb); diff --git a/drivers/video/mxc/tve.c b/drivers/video/mxc/tve.c new file mode 100644 index 000000000000..b32110c91128 --- /dev/null +++ b/drivers/video/mxc/tve.c @@ -0,0 +1,805 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file tve.c + * @brief Driver for i.MX TV encoder + * + * @ingroup Framebuffer + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/clk.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/sysfs.h> +#include <linux/irq.h> +#include <linux/sysfs.h> +#include <linux/platform_device.h> +#include <linux/mxcfb.h> +#include <linux/regulator/consumer.h> +#include <asm/uaccess.h> +#include <asm/atomic.h> +#include <mach/hardware.h> + +#define TVE_ENABLE (1UL) +#define TVE_DAC_FULL_RATE (0UL<<1) +#define TVE_DAC_DIV2_RATE (1UL<<1) +#define TVE_DAC_DIV4_RATE (2UL<<1) +#define TVE_IPU_CLK_ENABLE (1UL<<3) + +#define CD_LM_INT 0x00000001 +#define CD_SM_INT 0x00000002 +#define CD_MON_END_INT 0x00000004 +#define CD_CH_0_LM_ST 0x00000001 +#define CD_CH_0_SM_ST 0x00000010 +#define CD_CH_1_LM_ST 0x00000002 +#define CD_CH_1_SM_ST 0x00000020 +#define CD_CH_2_LM_ST 0x00000004 +#define CD_CH_2_SM_ST 0x00000040 +#define CD_MAN_TRIG 0x00000100 + +#define TVE_STAND_MASK (0x0F<<8) +#define TVE_NTSC_STAND (0UL<<8) +#define TVE_PAL_STAND (3UL<<8) +#define TVE_HD720P60_STAND (4UL<<8) + +#define TVOUT_FMT_OFF 0 +#define TVOUT_FMT_NTSC 1 +#define TVOUT_FMT_PAL 2 +#define TVOUT_FMT_720P60 3 + +static int enabled; /* enable power on or not */ + +static struct fb_info *tve_fbi; + +struct tve_data { + struct platform_device *pdev; + int revision; + int cur_mode; + int output_mode; + int detect; + void *base; + int irq; + struct clk *clk; + struct regulator *dac_reg; + struct regulator *dig_reg; + struct delayed_work cd_work; +} tve; + +struct tve_reg_mapping { + u32 tve_com_conf_reg; + u32 tve_cd_cont_reg; + u32 tve_int_cont_reg; + u32 tve_stat_reg; + u32 tve_mv_cont_reg; +}; + +struct tve_reg_fields_mapping { + u32 cd_en; + u32 cd_trig_mode; + u32 cd_lm_int; + u32 cd_sm_int; + u32 cd_mon_end_int; + u32 cd_man_trig; + u32 sync_ch_mask; + u32 tvout_mode_mask; + u32 sync_ch_offset; + u32 tvout_mode_offset; + u32 cd_ch_stat_offset; +}; + +static struct tve_reg_mapping tve_regs_v1 = { + 0, 0x14, 0x28, 0x2C, 0x48 +}; + +static struct tve_reg_fields_mapping tve_reg_fields_v1 = { + 1, 2, 1, 2, 4, 0x00010000, 0x7000, 0x70, 12, 4, 8 +}; + +static struct tve_reg_mapping tve_regs_v2 = { + 0, 0x34, 0x64, 0x68, 0xDC +}; + +static struct tve_reg_fields_mapping tve_reg_fields_v2 = { + 1, 2, 1, 2, 4, 0x01000000, 0x700000, 0x7000, 20, 12, 16 +}; + + +struct tve_reg_mapping *tve_regs; +struct tve_reg_fields_mapping *tve_reg_fields; + +/* For MX37 need modify some fields in tve_probe */ +static struct fb_videomode video_modes[] = { + { + /* NTSC TV output */ + "TV-NTSC", 60, 720, 480, 74074, + 122, 15, + 18, 26, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT | FB_SYNC_EXT, + FB_VMODE_INTERLACED, + 0,}, + { + /* PAL TV output */ + "TV-PAL", 50, 720, 576, 74074, + 132, 11, + 22, 26, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT | FB_SYNC_EXT, + FB_VMODE_INTERLACED | FB_VMODE_ODD_FLD_FIRST, + 0,}, + { + /* 720p60 TV output */ + "720P60", 60, 1280, 720, 13468, + 260, 109, + 25, 4, + 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT | + FB_SYNC_EXT, + FB_VMODE_NONINTERLACED, + 0,}, +}; + +enum tvout_mode { + TV_OFF, + CVBS0, + CVBS2, + CVBS02, + SVIDEO, + SVIDEO_CVBS, + YPBPR, + RGB +}; + +static unsigned short tvout_mode_to_channel_map[8] = { + 0, /* TV_OFF */ + 1, /* CVBS0 */ + 4, /* CVBS2 */ + 5, /* CVBS02 */ + 1, /* SVIDEO */ + 5, /* SVIDEO_CVBS */ + 1, /* YPBPR */ + 7 /* RGB */ +}; + + +static void tve_set_tvout_mode(int mode) +{ + u32 conf_reg; + + conf_reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + conf_reg &= ~(tve_reg_fields->sync_ch_mask | + tve_reg_fields->tvout_mode_mask); + /* clear sync_ch and tvout_mode fields */ + conf_reg |= + mode << tve_reg_fields-> + tvout_mode_offset | tvout_mode_to_channel_map[mode] << + tve_reg_fields->sync_ch_offset; + __raw_writel(conf_reg, tve.base + tve_regs->tve_com_conf_reg); +} + +static int _is_tvout_mode_hd_compatible(void) +{ + u32 conf_reg, mode; + + conf_reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + mode = (conf_reg >> tve_reg_fields->tvout_mode_offset) & 7; + if (mode == YPBPR || mode == RGB) { + return 1; + } else { + return 0; + } +} + + +/** + * tve_setup + * initial the CH7024 chipset by setting register + * @param: + * vos: output video format + * @return: + * 0 successful + * otherwise failed + */ +static int tve_setup(int mode) +{ + u32 reg; + struct clk *pll3_clk; + unsigned long pll3_clock_rate = 216000000; + + if (tve.cur_mode == mode) + return 0; + + tve.cur_mode = mode; + + switch (mode) { + case TVOUT_FMT_PAL: + case TVOUT_FMT_NTSC: + pll3_clock_rate = 216000000; + break; + case TVOUT_FMT_720P60: + pll3_clock_rate = 297000000; + break; + } + if (enabled) + clk_disable(tve.clk); + + pll3_clk = clk_get(NULL, "pll3"); + clk_disable(pll3_clk); + clk_set_rate(pll3_clk, pll3_clock_rate); + clk_enable(pll3_clk); + + clk_enable(tve.clk); + + /* select output video format */ + if (mode == TVOUT_FMT_PAL) { + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_PAL_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to PAL video\n"); + } else if (mode == TVOUT_FMT_NTSC) { + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_NTSC_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to NTSC video\n"); + } else if (mode == TVOUT_FMT_720P60) { + if (!_is_tvout_mode_hd_compatible()) { + tve_set_tvout_mode(YPBPR); + pr_debug("The TV out mode is HD incompatible. Setting to YPBPR."); + } + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + reg = (reg & ~TVE_STAND_MASK) | TVE_HD720P60_STAND; + __raw_writel(reg, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to 720P60 video\n"); + } else if (mode == TVOUT_FMT_OFF) { + __raw_writel(0x0, tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE: change to OFF video\n"); + } else { + pr_debug("TVE: no such video format.\n"); + if (!enabled) + clk_disable(tve.clk); + return -EINVAL; + } + + if (!enabled) + clk_disable(tve.clk); + + return 0; +} + +/** + * tve_enable + * Enable the tve Power to begin TV encoder + */ +static void tve_enable(void) +{ + u32 reg; + + if (!enabled) { + enabled = 1; + clk_enable(tve.clk); + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + __raw_writel(reg | TVE_IPU_CLK_ENABLE | TVE_ENABLE, + tve.base + tve_regs->tve_com_conf_reg); + pr_debug("TVE power on.\n"); + } + + /* enable interrupt */ + __raw_writel(CD_SM_INT | CD_LM_INT | CD_MON_END_INT, + tve.base + tve_regs->tve_stat_reg); + __raw_writel(CD_SM_INT | CD_LM_INT | CD_MON_END_INT, + tve.base + tve_regs->tve_int_cont_reg); +} + +/** + * tve_disable + * Disable the tve Power to stop TV encoder + */ +static void tve_disable(void) +{ + u32 reg; + + if (enabled) { + enabled = 0; + reg = __raw_readl(tve.base + tve_regs->tve_com_conf_reg); + __raw_writel(reg & ~TVE_ENABLE & ~TVE_IPU_CLK_ENABLE, + tve.base + tve_regs->tve_com_conf_reg); + clk_disable(tve.clk); + pr_debug("TVE power off.\n"); + } +} + +static int tve_update_detect_status(void) +{ + int old_detect = tve.detect; + u32 stat_lm, stat_sm, stat; + u32 int_ctl = __raw_readl(tve.base + tve_regs->tve_int_cont_reg); + u32 cd_cont_reg = + __raw_readl(tve.base + tve_regs->tve_cd_cont_reg); + u32 timeout = 40; + + if ((cd_cont_reg & 0x1) == 0) { + pr_warning("Warning: pls enable TVE CD first!\n"); + return tve.detect; + } + + stat = __raw_readl(tve.base + tve_regs->tve_stat_reg); + while (((stat & CD_MON_END_INT) == 0) && (timeout > 0)) { + msleep(2); + timeout -= 2; + stat = __raw_readl(tve.base + tve_regs->tve_stat_reg); + } + if (((stat & CD_MON_END_INT) == 0) && (timeout <= 0)) { + pr_warning("Warning: get detect resultwithout CD_MON_END_INT!\n"); + return tve.detect; + } + + stat = stat >> tve_reg_fields->cd_ch_stat_offset; + stat_lm = stat & (CD_CH_0_LM_ST | CD_CH_1_LM_ST | CD_CH_2_LM_ST); + if ((stat_lm == (CD_CH_0_LM_ST | CD_CH_1_LM_ST | CD_CH_2_LM_ST)) && + ((stat & (CD_CH_0_SM_ST | CD_CH_1_SM_ST | CD_CH_2_SM_ST)) == 0) + ) { + tve.detect = 3; + tve.output_mode = YPBPR; + } else if ((stat_lm == (CD_CH_0_LM_ST | CD_CH_1_LM_ST)) && + ((stat & (CD_CH_0_SM_ST | CD_CH_1_SM_ST)) == 0)) { + tve.detect = 4; + tve.output_mode = SVIDEO; + } else if (stat_lm == CD_CH_0_LM_ST) { + stat_sm = stat & CD_CH_0_SM_ST; + if (stat_sm != 0) { + /* headset */ + tve.detect = 2; + tve.output_mode = TV_OFF; + } else { + tve.detect = 1; + tve.output_mode = CVBS0; + } + } else if (stat_lm == CD_CH_2_LM_ST) { + stat_sm = stat & CD_CH_2_SM_ST; + if (stat_sm != 0) { + /* headset */ + tve.detect = 2; + tve.output_mode = TV_OFF; + } else { + tve.detect = 1; + tve.output_mode = CVBS2; + } + } else { + /* none */ + tve.detect = 0; + tve.output_mode = TV_OFF; + } + + tve_set_tvout_mode(tve.output_mode); + + /* clear interrupt */ + __raw_writel(CD_MON_END_INT | CD_LM_INT | CD_SM_INT, + tve.base + tve_regs->tve_stat_reg); + + __raw_writel(int_ctl | CD_SM_INT | CD_LM_INT, + tve.base + tve_regs->tve_int_cont_reg); + + if (old_detect != tve.detect) + sysfs_notify(&tve.pdev->dev.kobj, NULL, "headphone"); + + dev_dbg(&tve.pdev->dev, "detect = %d mode = %d\n", + tve.detect, tve.output_mode); + return tve.detect; +} + +static void cd_work_func(struct work_struct *work) +{ + tve_update_detect_status(); +} +#if 0 +static int tve_man_detect(void) +{ + u32 cd_cont; + u32 int_cont; + + if (!enabled) + return -1; + + int_cont = __raw_readl(tve.base + tve_regs->tve_int_cont_reg); + __raw_writel(int_cont & + ~(tve_reg_fields->cd_sm_int | tve_reg_fields->cd_lm_int), + tve.base + tve_regs->tve_int_cont_reg); + + cd_cont = __raw_readl(tve.base + tve_regs->tve_cd_cont_reg); + __raw_writel(cd_cont | tve_reg_fields->cd_trig_mode, + tve.base + tve_regs->tve_cd_cont_reg); + + __raw_writel(tve_reg_fields->cd_sm_int | tve_reg_fields-> + cd_lm_int | tve_reg_fields-> + cd_mon_end_int | tve_reg_fields->cd_man_trig, + tve.base + tve_regs->tve_stat_reg); + + while ((__raw_readl(tve.base + tve_regs->tve_stat_reg) + & tve_reg_fields->cd_mon_end_int) == 0) + msleep(5); + + tve_update_detect_status(); + + __raw_writel(cd_cont, tve.base + tve_regs->tve_cd_cont_reg); + __raw_writel(int_cont, tve.base + tve_regs->tve_int_cont_reg); + + return tve.detect; +} +#endif + +static irqreturn_t tve_detect_handler(int irq, void *data) +{ + u32 int_ctl = __raw_readl(tve.base + tve_regs->tve_int_cont_reg); + + /* disable INT first */ + int_ctl &= ~(CD_SM_INT | CD_LM_INT | CD_MON_END_INT); + __raw_writel(int_ctl, tve.base + tve_regs->tve_int_cont_reg); + + __raw_writel(CD_MON_END_INT | CD_LM_INT | CD_SM_INT, + tve.base + tve_regs->tve_stat_reg); + + schedule_delayed_work(&tve.cd_work, msecs_to_jiffies(1000)); + + return IRQ_HANDLED; +} + +int tve_fb_event(struct notifier_block *nb, unsigned long val, void *v) +{ + struct fb_event *event = v; + struct fb_info *fbi = event->info; + + switch (val) { + case FB_EVENT_FB_REGISTERED: + pr_debug("fb registered event\n"); + if ((tve_fbi != NULL) || strcmp(fbi->fix.id, "DISP3 BG - DI1")) + break; + + tve_fbi = fbi; + fb_add_videomode(&video_modes[0], &tve_fbi->modelist); + fb_add_videomode(&video_modes[1], &tve_fbi->modelist); + fb_add_videomode(&video_modes[2], &tve_fbi->modelist); + break; + case FB_EVENT_MODE_CHANGE: + { + struct fb_videomode cur_mode; + struct fb_videomode *mode; + struct list_head *pos; + struct fb_modelist *modelist; + + if (tve_fbi != fbi) + break; + + fb_var_to_videomode(&cur_mode, &fbi->var); + + list_for_each(pos, &tve_fbi->modelist) { + modelist = list_entry(pos, struct fb_modelist, list); + mode = &modelist->mode; + if (fb_mode_is_equal(&cur_mode, mode)) { + fbi->mode = mode; + break; + } + } + + if (!fbi->mode) { + tve_disable(); + tve.cur_mode = TVOUT_FMT_OFF; + return 0; + } + + pr_debug("fb mode change event: xres=%d, yres=%d\n", + fbi->mode->xres, fbi->mode->yres); + + tve_disable(); + + if (fb_mode_is_equal(fbi->mode, &video_modes[0])) { + tve_setup(TVOUT_FMT_NTSC); + tve_enable(); + } else if (fb_mode_is_equal(fbi->mode, &video_modes[1])) { + tve_setup(TVOUT_FMT_PAL); + tve_enable(); + } else if (fb_mode_is_equal(fbi->mode, &video_modes[2])) { + tve_setup(TVOUT_FMT_720P60); + tve_enable(); + } else { + tve_setup(TVOUT_FMT_OFF); + } + break; + } + case FB_EVENT_BLANK: + if ((tve_fbi != fbi) || (fbi->mode == NULL)) + return 0; + + if (*((int *)event->data) == FB_BLANK_UNBLANK) { + if (fb_mode_is_equal(fbi->mode, &video_modes[0])) { + if (tve.cur_mode != TVOUT_FMT_NTSC) { + tve_disable(); + tve_setup(TVOUT_FMT_NTSC); + } + tve_enable(); + } else if (fb_mode_is_equal(fbi->mode, + &video_modes[1])) { + if (tve.cur_mode != TVOUT_FMT_PAL) { + tve_disable(); + tve_setup(TVOUT_FMT_PAL); + } + tve_enable(); + } else if (fb_mode_is_equal(fbi->mode, + &video_modes[2])) { + if (tve.cur_mode != TVOUT_FMT_720P60) { + tve_disable(); + tve_setup(TVOUT_FMT_720P60); + } + tve_enable(); + } else { + tve_setup(TVOUT_FMT_OFF); + } + } else + tve_disable(); + break; + } + return 0; +} + +static struct notifier_block nb = { + .notifier_call = tve_fb_event, +}; + +static ssize_t show_headphone(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int detect; + + if (!enabled) { + strcpy(buf, "tve power off\n"); + return strlen(buf); + } + + detect = tve_update_detect_status(); + + if (detect == 0) + strcpy(buf, "none\n"); + else if (detect == 1) + strcpy(buf, "cvbs\n"); + else if (detect == 2) + strcpy(buf, "headset\n"); + else if (detect == 3) + strcpy(buf, "component\n"); + else + strcpy(buf, "svideo\n"); + + return strlen(buf); +} + +static DEVICE_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL); + +static int _tve_get_revision(void) +{ + u32 conf_reg; + u32 rev = 0; + + /* find out TVE rev based on the base addr default value + * can be used at the init/probe ONLY */ + conf_reg = __raw_readl(tve.base); + switch (conf_reg) { + case 0x00842000: + rev = 1; + break; + case 0x00100000: + rev = 2; + break; + } + return rev; +} + +static int tve_probe(struct platform_device *pdev) +{ + int ret, i; + struct resource *res; + struct tve_platform_data *plat_data = pdev->dev.platform_data; + u32 conf_reg; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) + return -ENOMEM; + + tve.pdev = pdev; + tve.base = ioremap(res->start, res->end - res->start); + + tve.irq = platform_get_irq(pdev, 0); + if (tve.irq < 0) { + ret = tve.irq; + goto err0; + } + + ret = request_irq(tve.irq, tve_detect_handler, 0, pdev->name, pdev); + if (ret < 0) + goto err0; + + ret = device_create_file(&pdev->dev, &dev_attr_headphone); + if (ret < 0) + goto err1; + + for (i = 0; i < num_registered_fb; i++) { + if (strcmp(registered_fb[i]->fix.id, "DISP3 BG - DI1") == 0) { + tve_fbi = registered_fb[i]; + break; + } + } + + /* adjust video mode for mx37 */ + if (cpu_is_mx37()) { + video_modes[0].left_margin = 121; + video_modes[0].right_margin = 16; + video_modes[0].upper_margin = 17; + video_modes[0].lower_margin = 5; + video_modes[1].left_margin = 131; + video_modes[1].right_margin = 12; + video_modes[1].upper_margin = 21; + video_modes[1].lower_margin = 3; + } + + if (tve_fbi != NULL) { + fb_add_videomode(&video_modes[0], &tve_fbi->modelist); + fb_add_videomode(&video_modes[1], &tve_fbi->modelist); + fb_add_videomode(&video_modes[2], &tve_fbi->modelist); + } + + tve.dac_reg = regulator_get(&pdev->dev, plat_data->dac_reg); + if (!IS_ERR(tve.dac_reg)) { + regulator_set_voltage(tve.dac_reg, 2500000, 2500000); + regulator_enable(tve.dac_reg); + } + + tve.dig_reg = regulator_get(&pdev->dev, plat_data->dig_reg); + if (!IS_ERR(tve.dig_reg)) { + regulator_set_voltage(tve.dig_reg, 1250000, 1250000); + regulator_enable(tve.dig_reg); + } + + tve.clk = clk_get(&pdev->dev, "tve_clk"); + clk_set_rate(tve.clk, 216000000); + clk_enable(tve.clk); + + tve.revision = _tve_get_revision(); + if (tve.revision == 1) { + tve_regs = &tve_regs_v1; + tve_reg_fields = &tve_reg_fields_v1; + } else { + tve_regs = &tve_regs_v2; + tve_reg_fields = &tve_reg_fields_v2; + } + + /* Setup cable detect, for YPrPb mode, default use channel#0 for Y */ + INIT_DELAYED_WORK(&tve.cd_work, cd_work_func); + if (tve.revision == 1) + __raw_writel(0x01067701, tve.base + tve_regs->tve_cd_cont_reg); + else + __raw_writel(0x00770601, tve.base + tve_regs->tve_cd_cont_reg); + + conf_reg = 0; + __raw_writel(conf_reg, tve.base + tve_regs->tve_com_conf_reg); + + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4 * 5); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4 * 4); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4 * 3); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4 * 2); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg - 4); + __raw_writel(0x00000000, tve.base + tve_regs->tve_mv_cont_reg); + + clk_disable(tve.clk); + + ret = fb_register_client(&nb); + if (ret < 0) + goto err2; + + return 0; +err2: + device_remove_file(&pdev->dev, &dev_attr_headphone); +err1: + free_irq(tve.irq, pdev); +err0: + iounmap(tve.base); + return ret; +} + +static int tve_remove(struct platform_device *pdev) +{ + if (enabled) { + clk_disable(tve.clk); + enabled = 0; + } + free_irq(tve.irq, pdev); + device_remove_file(&pdev->dev, &dev_attr_headphone); + fb_unregister_client(&nb); + return 0; +} + +/*! + * PM suspend/resume routing + */ +static int tve_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (enabled) { + __raw_writel(0, tve.base + tve_regs->tve_int_cont_reg); + __raw_writel(0, tve.base + tve_regs->tve_cd_cont_reg); + __raw_writel(0, tve.base + tve_regs->tve_com_conf_reg); + clk_disable(tve.clk); + } + return 0; +} + +static int tve_resume(struct platform_device *pdev) +{ + if (enabled) { + clk_enable(tve.clk); + + /* Setup cable detect */ + if (tve.revision == 1) + __raw_writel(0x01067701, + tve.base + tve_regs->tve_cd_cont_reg); + else + __raw_writel(0x00770601, + tve.base + tve_regs->tve_cd_cont_reg); + + if (tve.cur_mode == TVOUT_FMT_NTSC) { + tve_disable(); + tve.cur_mode = TVOUT_FMT_OFF; + tve_setup(TVOUT_FMT_NTSC); + } else if (tve.cur_mode == TVOUT_FMT_PAL) { + tve_disable(); + tve.cur_mode = TVOUT_FMT_OFF; + tve_setup(TVOUT_FMT_PAL); + } else if (tve.cur_mode == TVOUT_FMT_720P60) { + tve_disable(); + tve.cur_mode = TVOUT_FMT_OFF; + tve_setup(TVOUT_FMT_720P60); + } + tve_enable(); + } + + return 0; +} + +static struct platform_driver tve_driver = { + .driver = { + .name = "tve", + }, + .probe = tve_probe, + .remove = tve_remove, + .suspend = tve_suspend, + .resume = tve_resume, +}; + +static int __init tve_init(void) +{ + return platform_driver_register(&tve_driver); +} + +static void __exit tve_exit(void) +{ + platform_driver_unregister(&tve_driver); +} + +module_init(tve_init); +module_exit(tve_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX TV encoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/stmp37xxfb.c b/drivers/video/stmp37xxfb.c new file mode 100644 index 000000000000..9f414b54a4dc --- /dev/null +++ b/drivers/video/stmp37xxfb.c @@ -0,0 +1,840 @@ +/* + * Freescale STMP37XX/STMP378X framebuffer driver + * + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/uaccess.h> +#include <linux/cpufreq.h> + +#include <mach/hardware.h> +#include <mach/regs-pinctrl.h> +#include <mach/regs-lcdif.h> +#include <mach/regs-clkctrl.h> +#include <mach/regs-apbh.h> +#include <mach/lcdif.h> + +#include <mach/stmp3xxx.h> + +#define NUM_SCREENS 1 + +struct stmp3xxx_fb_data { + struct fb_info info; + struct stmp3xxx_platform_fb_data *pdata; + int is_blank; + ssize_t mem_size; + ssize_t map_size; + dma_addr_t phys_start; + dma_addr_t cur_phys; + int dma_irq; + int err_irq; + void *virt_start; + struct device *dev; + wait_queue_head_t vsync_wait_q; + u32 vsync_count; + void *par; +}; + +/* forward declaration */ +static int stmp3xxxfb_blank(int blank, struct fb_info *info); +static unsigned char *default_panel_name; +static struct stmp3xxx_fb_data *cdata; + +static irqreturn_t lcd_irq_handler(int irq, void *dev_id) +{ + struct stmp3xxx_fb_data *data = dev_id; + u32 status_lcd = __raw_readl(REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + u32 status_apbh = __raw_readl(REGS_APBH_BASE + HW_APBH_CTRL1); + pr_debug("%s: irq %d\n", __func__, irq); + + if (status_apbh & BM_APBH_CTRL1_CH0_CMDCMPLT_IRQ) + stmp3xxx_clearl(BM_APBH_CTRL1_CH0_CMDCMPLT_IRQ, REGS_APBH_BASE + HW_APBH_CTRL1); + + if (status_lcd & BM_LCDIF_CTRL1_VSYNC_EDGE_IRQ) { + pr_debug("%s: VSYNC irq\n", __func__); + data->vsync_count++; + stmp3xxx_clearl(BM_LCDIF_CTRL1_VSYNC_EDGE_IRQ, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + wake_up_interruptible(&data->vsync_wait_q); + } + if (status_lcd & BM_LCDIF_CTRL1_CUR_FRAME_DONE_IRQ) { + pr_debug("%s: frame done irq\n", __func__); + stmp3xxx_clearl(BM_LCDIF_CTRL1_CUR_FRAME_DONE_IRQ, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + data->vsync_count++; + } + if (status_lcd & BM_LCDIF_CTRL1_UNDERFLOW_IRQ) { + pr_debug("%s: underflow irq\n", __func__); + stmp3xxx_clearl(BM_LCDIF_CTRL1_UNDERFLOW_IRQ, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + } + if (status_lcd & BM_LCDIF_CTRL1_OVERFLOW_IRQ) { + pr_debug("%s: overflow irq\n", __func__); + stmp3xxx_clearl(BM_LCDIF_CTRL1_OVERFLOW_IRQ, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + } + return IRQ_HANDLED; +} + +static struct fb_var_screeninfo stmp3xxxfb_default __devinitdata = { + .activate = FB_ACTIVATE_TEST, + .height = -1, + .width = -1, + .pixclock = 20000, + .left_margin = 64, + .right_margin = 64, + .upper_margin = 32, + .lower_margin = 32, + .hsync_len = 64, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct fb_fix_screeninfo stmp3xxxfb_fix __devinitdata = { + .id = "stmp3xxxfb", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .accel = FB_ACCEL_NONE, +}; + +int stmp3xxxfb_get_info(struct fb_var_screeninfo *var, + struct fb_fix_screeninfo *fix) +{ + if (!cdata) + return -ENODEV; + + *var = cdata->info.var; + *fix = cdata->info.fix; + return 0; +} + +void stmp3xxxfb_cfg_pxp(int enable, dma_addr_t pxp_phys) +{ + if (enable) + cdata->pdata->cur->pan_display(pxp_phys); + else + cdata->pdata->cur->pan_display(cdata->cur_phys); +} + +static int stmp3xxxfb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct stmp3xxx_fb_data *data = (struct stmp3xxx_fb_data *)info; + + unsigned long off = vma->vm_pgoff << PAGE_SHIFT; + + if (off < info->fix.smem_len) + return dma_mmap_writecombine(data->dev, vma, + data->virt_start, + data->phys_start, + info->fix.smem_len); + else + return -EINVAL; +} + +static int stmp3xxxfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + if (regno >= 256) /* no. of hw registers */ + return 1; + /* + * Program hardware... do anything you want with transp + */ + + /* grayscale works only partially under directcolor */ + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = + (red * 77 + green * 151 + blue * 28) >> 8; + } + + /* Directcolor: + * var->{color}.offset contains start of bitfield + * var->{color}.length contains length of bitfield + * {hardwarespecific} contains width of RAMDAC + * cmap[X] is programmed to + * (X << red.offset) | (X << green.offset) | (X << blue.offset) + * RAMDAC[X] is programmed to (red, green, blue) + * + * Pseudocolor: + * uses offset = 0 && length = RAMDAC register width. + * var->{color}.offset is 0 + * var->{color}.length contains widht of DAC + * cmap is not used + * RAMDAC[X] is programmed to (red, green, blue) + * Truecolor: + * does not use DAC. Usually 3 are present. + * var->{color}.offset contains start of bitfield + * var->{color}.length contains length of bitfield + * cmap is programmed to + * (red << red.offset) | (green << green.offset) | + * (blue << blue.offset) | (transp << transp.offset) + * RAMDAC does not exist + */ +#define CNVT_TOHW(val, width) ((((val)<<(width))+0x7FFF-(val))>>16) + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + case FB_VISUAL_PSEUDOCOLOR: + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); + break; + case FB_VISUAL_DIRECTCOLOR: + red = CNVT_TOHW(red, 8); /* expect 8 bit DAC */ + green = CNVT_TOHW(green, 8); + blue = CNVT_TOHW(blue, 8); + /* hey, there is bug in transp handling... */ + transp = CNVT_TOHW(transp, 8); + break; + } +#undef CNVT_TOHW + /* Truecolor has hardware independent palette */ + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + + if (regno >= 16) + return 1; + + ((u32 *) (info->pseudo_palette))[regno] = + (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + } + return 0; +} + +static inline u_long get_line_length(int xres_virtual, int bpp) +{ + u_long length; + + length = xres_virtual * bpp; + length = (length + 31) & ~31; + length >>= 3; + return length; +} + +static int get_matching_pentry(struct stmp3xxx_platform_fb_entry *pentry, + void *data, int ret_prev) +{ + struct fb_var_screeninfo *info = data; + pr_debug("%s: %d:%d:%d vs %d:%d:%d\n", __func__, + pentry->x_res, pentry->y_res, pentry->bpp, + info->yres, info->xres, info->bits_per_pixel); + if (pentry->x_res == info->yres && pentry->y_res == info->xres && + pentry->bpp == info->bits_per_pixel) + ret_prev = (int)pentry; + return ret_prev; +} + +static int get_matching_pentry_by_name( + struct stmp3xxx_platform_fb_entry *pentry, + void *data, + int ret_prev) +{ + unsigned char *name = data; + if (!strcmp(pentry->name, name)) + ret_prev = (int)pentry; + return ret_prev; +} + +/* + * This routine actually sets the video mode. It's in here where we + * the hardware state info->par and fix which can be affected by the + * change in par. For this driver it doesn't do much. + * + * XXX: REVISIT + */ +static int stmp3xxxfb_set_par(struct fb_info *info) +{ + struct stmp3xxx_fb_data *data = (struct stmp3xxx_fb_data *)info; + struct stmp3xxx_platform_fb_data *pdata = data->pdata; + struct stmp3xxx_platform_fb_entry *pentry; + pentry = (void *)stmp3xxx_lcd_iterate_pdata(pdata, + get_matching_pentry, + &info->var); + + dev_dbg(data->dev, "%s: xres %d, yres %d, bpp %d\n", + __func__, + info->var.xres, + info->var.yres, + info->var.bits_per_pixel); + if (!pentry) + return -EINVAL; + + info->fix.line_length = get_line_length(info->var.xres_virtual, + info->var.bits_per_pixel); + + if (pentry == pdata->cur || !pdata->cur) + return 0; + + /* release prev panel */ + stmp3xxxfb_blank(FB_BLANK_POWERDOWN, &data->info); + if (pdata->cur->stop_panel) + pdata->cur->stop_panel(); + pdata->cur->release_panel(data->dev, pdata->cur); + + info->fix.smem_len = pentry->y_res * pentry->x_res * pentry->bpp / 8; + info->screen_size = info->fix.smem_len; + memset((void *)info->screen_base, 0, info->screen_size); + + /* init next panel */ + pdata->cur = pentry; + stmp3xxx_init_lcdif(); + pentry->init_panel(data->dev, data->phys_start, info->fix.smem_len, + pentry); + pentry->run_panel(); + stmp3xxxfb_blank(FB_BLANK_UNBLANK, &data->info); + + return 0; +} + +static int stmp3xxxfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + u32 line_length; + struct stmp3xxx_fb_data *data = (struct stmp3xxx_fb_data *)info; + struct stmp3xxx_platform_fb_data *pdata = data->pdata; + + /* + * FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal! + * as FB_VMODE_SMOOTH_XPAN is only used internally + */ + + if (var->vmode & FB_VMODE_CONUPDATE) { + var->vmode |= FB_VMODE_YWRAP; + var->xoffset = info->var.xoffset; + var->yoffset = info->var.yoffset; + } + + pr_debug("%s: xres %d, yres %d, bpp %d\n", __func__, + var->xres, var->yres, var->bits_per_pixel); + /* + * Some very basic checks + */ + if (!var->xres) + var->xres = 1; + if (!var->yres) + var->yres = 1; + if (var->xres > var->xres_virtual) + var->xres_virtual = var->xres; + if (var->yres > var->yres_virtual) + var->yres_virtual = var->yres; + + if (var->xres_virtual < var->xoffset + var->xres) + var->xres_virtual = var->xoffset + var->xres; + if (var->yres_virtual < var->yoffset + var->yres) + var->yres_virtual = var->yoffset + var->yres; + + line_length = get_line_length(var->xres_virtual, var->bits_per_pixel); + dev_dbg(data->dev, + "line_length %d, var->yres_virtual %d, data->mem_size %d\n", + line_length, var->yres_virtual, data->mem_size); + if (line_length * var->yres_virtual > data->map_size) + return -ENOMEM; + + if (!stmp3xxx_lcd_iterate_pdata(pdata, get_matching_pentry, var)) + return -EINVAL; + + if (var->bits_per_pixel == 16) { + /* RGBA 5551 */ + if (var->transp.length) { + var->red.offset = 0; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 10; + var->blue.length = 5; + var->transp.offset = 15; + var->transp.length = 1; + } else { /* RGB 565 */ + var->red.offset = 0; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 11; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + } + } else { + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + } + + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + + return 0; +} + + +static int stmp3xxxfb_wait_for_vsync(u32 channel, struct fb_info *info) +{ + struct stmp3xxx_fb_data *data = (struct stmp3xxx_fb_data *)info; + u32 count = data->vsync_count; + int ret = 0; + + stmp3xxx_setl(BM_LCDIF_CTRL1_VSYNC_EDGE_IRQ_EN, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + ret = wait_event_interruptible_timeout(data->vsync_wait_q, + count != data->vsync_count, HZ / 10); + stmp3xxx_clearl(BM_LCDIF_CTRL1_VSYNC_EDGE_IRQ_EN, REGS_LCDIF_BASE + HW_LCDIF_CTRL1); + if (!ret) { + dev_err(data->dev, "wait for vsync timed out\n"); + ret = -ETIMEDOUT; + } + return ret; +} + +static int stmp3xxxfb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + u32 channel = 0; + int ret = -EINVAL; + + switch (cmd) { + case FBIO_WAITFORVSYNC: + if (!get_user(channel, (__u32 __user *) arg)) + ret = stmp3xxxfb_wait_for_vsync(channel, info); + break; + default: + break; + } + return ret; +} + +static int stmp3xxxfb_blank(int blank, struct fb_info *info) +{ + struct stmp3xxx_fb_data *data = (struct stmp3xxx_fb_data *)info; + int ret = data->pdata->cur->blank_panel ? + data->pdata->cur->blank_panel(blank) : + -ENOTSUPP; + if (ret == 0) + data->is_blank = (blank != FB_BLANK_UNBLANK); + return ret; +} + +static int stmp3xxxfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct stmp3xxx_fb_data *data = (struct stmp3xxx_fb_data *)info; + int ret = 0; + + pr_debug("%s: var->xoffset %d, info->var.xoffset %d\n", + __func__, var->xoffset, info->var.xoffset); + /* check if var is valid; also, xpan is not supported */ + if (!var || (var->xoffset != info->var.xoffset) || + (var->yoffset + var->yres > var->yres_virtual)) { + ret = -EINVAL; + goto out; + } + + if (!data->pdata->cur->pan_display) { + ret = -EINVAL; + goto out; + } + + /* update framebuffer visual */ + data->cur_phys = data->phys_start + + info->fix.line_length * var->yoffset; + data->pdata->cur->pan_display(data->cur_phys); +out: + return ret; +} + +static struct fb_ops stmp3xxxfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = stmp3xxxfb_check_var, + .fb_set_par = stmp3xxxfb_set_par, + .fb_mmap = stmp3xxxfb_mmap, + .fb_setcolreg = stmp3xxxfb_setcolreg, + .fb_ioctl = stmp3xxxfb_ioctl, + .fb_blank = stmp3xxxfb_blank, + .fb_pan_display = stmp3xxxfb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static void init_timings(struct stmp3xxx_fb_data *data) +{ + unsigned phase_time; + unsigned timings; + + /* Just use a phase_time of 1. As optimal as it gets, now. */ + phase_time = 1; + + /* Program all 4 timings the same */ + timings = phase_time; + timings |= timings << 8; + timings |= timings << 16; + __raw_writel(timings, REGS_LCDIF_BASE + HW_LCDIF_TIMING); +} + +#ifdef CONFIG_CPU_FREQ + +struct stmp3xxxfb_notifier_block { + struct stmp3xxx_fb_data *fb_data; + struct notifier_block nb; +}; + +static int stmp3xxxfb_notifier(struct notifier_block *self, + unsigned long phase, void *p) +{ + struct stmp3xxxfb_notifier_block *block = + container_of(self, struct stmp3xxxfb_notifier_block, nb); + struct stmp3xxx_fb_data *data = block->fb_data; + + switch (phase) { + case CPUFREQ_POSTCHANGE: + stmp3xxxfb_blank(FB_BLANK_UNBLANK, &data->info); + break; + + case CPUFREQ_PRECHANGE: + stmp3xxxfb_blank(FB_BLANK_POWERDOWN, &data->info); + break; + + default: + dev_dbg(data->dev, "didn't handle notify %ld\n", phase); + } + + return NOTIFY_DONE; +} + +static struct stmp3xxxfb_notifier_block stmp3xxxfb_nb = { + .nb = { + .notifier_call = stmp3xxxfb_notifier, + }, +}; +#endif /* CONFIG_CPU_FREQ */ + +static int get_max_memsize(struct stmp3xxx_platform_fb_entry *pentry, + void *data, int ret_prev) +{ + struct stmp3xxx_fb_data *fbdata = data; + int sz = pentry->x_res * pentry->y_res * pentry->bpp / 8; + fbdata->mem_size = sz < ret_prev ? ret_prev : sz; + pr_debug("%s: mem_size now %d\n", __func__, fbdata->mem_size); + return fbdata->mem_size; +} + +static int __devinit stmp3xxxfb_probe(struct platform_device *pdev) +{ + int ret = 0; + struct stmp3xxx_fb_data *data; + struct resource *res; + struct fb_info *info; + struct stmp3xxx_platform_fb_data *pdata = pdev->dev.platform_data; + struct stmp3xxx_platform_fb_entry *pentry; + + if (pdata == NULL) { + ret = -ENODEV; + goto out; + } + + if (default_panel_name) { + pentry = (void *)stmp3xxx_lcd_iterate_pdata(pdata, + get_matching_pentry_by_name, + default_panel_name); + if (pentry) { + stmp3xxx_lcd_move_pentry_up(pentry, pdata); + pdata->cur = pentry; + } + } + if (!default_panel_name || !pentry) + pentry = pdata->cur; + if (!pentry || !pentry->init_panel || !pentry->run_panel || + !pentry->release_panel) { + ret = -EINVAL; + goto out; + } + + data = (struct stmp3xxx_fb_data *)framebuffer_alloc( + sizeof(struct stmp3xxx_fb_data) + + sizeof(u32) * 256 - + sizeof(struct fb_info), &pdev->dev); + if (data == NULL) { + ret = -ENOMEM; + goto out; + } + + cdata = data; + data->dev = &pdev->dev; + data->pdata = pdata; + platform_set_drvdata(pdev, data); + info = &data->info; + + dev_dbg(&pdev->dev, "resolution %dx%d, bpp %d\n", pentry->x_res, + pentry->y_res, pentry->bpp); + + stmp3xxx_lcd_iterate_pdata(pdata, get_max_memsize, data); + + data->map_size = PAGE_ALIGN(data->mem_size) * NUM_SCREENS; + dev_dbg(&pdev->dev, "memory to allocate: %d\n", data->map_size); + + data->virt_start = dma_alloc_writecombine(&pdev->dev, + data->map_size, + &data->phys_start, + GFP_KERNEL); + + if (data->virt_start == NULL) { + ret = -ENOMEM; + goto out_dma; + } + dev_dbg(&pdev->dev, "allocated at %p:0x%x\n", data->virt_start, + data->phys_start); + + stmp3xxxfb_default.bits_per_pixel = pentry->bpp; + /* NB: rotated */ + stmp3xxxfb_default.xres = pentry->y_res; + stmp3xxxfb_default.yres = pentry->x_res; + stmp3xxxfb_default.xres_virtual = pentry->y_res; + stmp3xxxfb_default.yres_virtual = data->map_size / + (pentry->y_res * pentry->bpp / 8); + if (stmp3xxxfb_default.yres_virtual >= stmp3xxxfb_default.yres * 2) + stmp3xxxfb_default.yres_virtual = stmp3xxxfb_default.yres * 2; + else + stmp3xxxfb_default.yres_virtual = stmp3xxxfb_default.yres; + + stmp3xxxfb_fix.smem_start = data->phys_start; + stmp3xxxfb_fix.smem_len = pentry->y_res * pentry->x_res * pentry->bpp / + 8; + stmp3xxxfb_fix.ypanstep = 1; + + switch (pentry->bpp) { + case 32: + case 24: + stmp3xxxfb_default.red.offset = 16; + stmp3xxxfb_default.red.length = 8; + stmp3xxxfb_default.green.offset = 8; + stmp3xxxfb_default.green.length = 8; + stmp3xxxfb_default.blue.offset = 0; + stmp3xxxfb_default.blue.length = 8; + break; + + case 16: + stmp3xxxfb_default.red.offset = 11; + stmp3xxxfb_default.red.length = 5; + stmp3xxxfb_default.green.offset = 5; + stmp3xxxfb_default.green.length = 6; + stmp3xxxfb_default.blue.offset = 0; + stmp3xxxfb_default.blue.length = 5; + break; + + default: + dev_err(&pdev->dev, "unsupported bitwidth %d\n", pentry->bpp); + ret = -EINVAL; + goto out_dma; + } + + info->screen_base = data->virt_start; + info->fbops = &stmp3xxxfb_ops; + info->var = stmp3xxxfb_default; + info->fix = stmp3xxxfb_fix; + info->pseudo_palette = &data->par; + data->par = NULL; + info->flags = FBINFO_FLAG_DEFAULT; + + init_waitqueue_head(&data->vsync_wait_q); + data->vsync_count = 0; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&pdev->dev, "cannot get IRQ resource\n"); + ret = -ENODEV; + goto out_dma; + } + data->dma_irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 1); + if (res == NULL) { + dev_err(&pdev->dev, "cannot get IRQ resource\n"); + ret = -ENODEV; + goto out_dma; + } + data->err_irq = res->start; + + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret) + goto out_cmap; + + stmp3xxx_init_lcdif(); + ret = pentry->init_panel(data->dev, data->phys_start, + stmp3xxxfb_fix.smem_len, pentry); + if (ret) { + dev_err(&pdev->dev, "cannot initialize LCD panel\n"); + goto out_panel; + } + dev_dbg(&pdev->dev, "LCD panel initialized\n"); + init_timings(data); + + ret = request_irq(data->dma_irq, lcd_irq_handler, 0, "fb_dma", data); + if (ret) { + dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", + data->dma_irq, ret); + goto out_panel; + } + ret = request_irq(data->err_irq, lcd_irq_handler, 0, "fb_error", data); + if (ret) { + dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", + data->err_irq, ret); + goto out_irq; + } + ret = register_framebuffer(info); + if (ret) + goto out_register; + + pentry->run_panel(); + dev_dbg(&pdev->dev, "LCD DMA channel has been started\n"); + data->cur_phys = data->phys_start; + dev_dbg(&pdev->dev, "LCD running now\n"); + +#ifdef CONFIG_CPU_FREQ + stmp3xxxfb_nb.fb_data = data; + cpufreq_register_notifier(&stmp3xxxfb_nb.nb, + CPUFREQ_TRANSITION_NOTIFIER); +#endif /* CONFIG_CPU_FREQ */ + + goto out; + +out_register: + free_irq(data->err_irq, data); +out_irq: + free_irq(data->dma_irq, data); +out_panel: + fb_dealloc_cmap(&info->cmap); +out_cmap: + dma_free_writecombine(&pdev->dev, data->map_size, data->virt_start, + data->phys_start); +out_dma: + kfree(data); +out: + return ret; +} + +static int stmp3xxxfb_remove(struct platform_device *pdev) +{ + struct stmp3xxx_fb_data *data = platform_get_drvdata(pdev); + struct stmp3xxx_platform_fb_data *pdata = pdev->dev.platform_data; + struct stmp3xxx_platform_fb_entry *pentry = pdata->cur; + + stmp3xxxfb_blank(FB_BLANK_POWERDOWN, &data->info); + if (pentry->stop_panel) + pentry->stop_panel(); + pentry->release_panel(&pdev->dev, pentry); + + unregister_framebuffer(&data->info); + framebuffer_release(&data->info); + fb_dealloc_cmap(&data->info.cmap); + free_irq(data->dma_irq, data); + free_irq(data->err_irq, data); + dma_free_writecombine(&pdev->dev, data->map_size, data->virt_start, + data->phys_start); + kfree(data); + platform_set_drvdata(pdev, NULL); + return 0; +} + +#ifdef CONFIG_PM +static int stmp3xxxfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct stmp3xxx_fb_data *data = platform_get_drvdata(pdev); + struct stmp3xxx_platform_fb_data *pdata = pdev->dev.platform_data; + struct stmp3xxx_platform_fb_entry *pentry = pdata->cur; + int ret; + + ret = stmp3xxxfb_blank(FB_BLANK_POWERDOWN, &data->info); + if (ret) + goto out; + if (pentry->stop_panel) + pentry->stop_panel(); + pentry->release_panel(data->dev, pentry); + +out: + return ret; +} + +static int stmp3xxxfb_resume(struct platform_device *pdev) +{ + struct stmp3xxx_fb_data *data = platform_get_drvdata(pdev); + struct stmp3xxx_platform_fb_data *pdata = pdev->dev.platform_data; + struct stmp3xxx_platform_fb_entry *pentry = pdata->cur; + + stmp3xxx_init_lcdif(); + init_timings(data); + pentry->init_panel(data->dev, data->phys_start, data->info.fix.smem_len, + pentry); + pentry->run_panel(); + stmp3xxxfb_blank(FB_BLANK_UNBLANK, &data->info); + return 0; +} +#else +#define stmp3xxxfb_suspend NULL +#define stmp3xxxfb_resume NULL +#endif + +static struct platform_driver stmp3xxxfb_driver = { + .probe = stmp3xxxfb_probe, + .remove = stmp3xxxfb_remove, + .suspend = stmp3xxxfb_suspend, + .resume = stmp3xxxfb_resume, + .driver = { + .name = "stmp3xxx-fb", + .owner = THIS_MODULE, + }, +}; + +static int __init stmp3xxxfb_init(void) +{ + return platform_driver_register(&stmp3xxxfb_driver); +} + +static void __exit stmp3xxxfb_exit(void) +{ + platform_driver_unregister(&stmp3xxxfb_driver); +} + +module_init(stmp3xxxfb_init); +module_exit(stmp3xxxfb_exit); + +/* + * LCD panel select + */ +static int __init default_panel_select(char *str) +{ + default_panel_name = str; + return 0; +} +__setup("lcd_panel=", default_panel_select); + +MODULE_AUTHOR("Vitaly Wool <vital@embeddedalley.com>"); +MODULE_DESCRIPTION("STMP3xxx Framebuffer Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/w1/masters/mxc_w1.c b/drivers/w1/masters/mxc_w1.c index 65244c02551b..e73d6d20a9fb 100644 --- a/drivers/w1/masters/mxc_w1.c +++ b/drivers/w1/masters/mxc_w1.c @@ -23,6 +23,7 @@ #include <linux/clk.h> #include <linux/delay.h> #include <linux/io.h> +#include <mach/hardware.h> #include "../w1.h" #include "../w1_int.h" @@ -43,6 +44,8 @@ #define MXC_W1_INTERRUPT 0x0A #define MXC_W1_INTERRUPT_EN 0x0C +static DECLARE_COMPLETION(transmit_done); + struct mxc_w1_device { void __iomem *regs; unsigned int clkdiv; @@ -102,10 +105,155 @@ static u8 mxc_w1_ds2_touch_bit(void *data, u8 bit) return ((__raw_readb(ctrl_addr)) >> 3) & 0x1; } +static void mxc_w1_ds2_write_byte(void *data, u8 byte) +{ + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + INIT_COMPLETION(transmit_done); + __raw_writeb(byte, (dev->regs + MXC_W1_TXRX)); + __raw_writeb(0x10, (dev->regs + MXC_W1_INTERRUPT_EN)); + wait_for_completion(&transmit_done); +} +static u8 mxc_w1_ds2_read_byte(void *data) +{ + volatile u8 reg_val; + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + mxc_w1_ds2_write_byte(data, 0xFF); + reg_val = __raw_readb((dev->regs + MXC_W1_TXRX)); + return reg_val; +} +static u8 mxc_w1_read_byte(void *data) +{ + volatile u8 reg_val; + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + reg_val = __raw_readb((dev->regs + MXC_W1_TXRX)); + return reg_val; +} +static irqreturn_t w1_interrupt_handler(int irq, void *data) +{ + u8 reg_val; + irqreturn_t ret = IRQ_NONE; + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + reg_val = __raw_readb((dev->regs + MXC_W1_INTERRUPT)); + if ((reg_val & 0x10)) { + complete(&transmit_done); + reg_val = __raw_readb((dev->regs + MXC_W1_TXRX)); + ret = IRQ_HANDLED; + } + return ret; +} +void search_ROM_accelerator(void *data, struct w1_master *master, u8 search_type, + w1_slave_found_callback cb) +{ + u64 rn[2], last_rn[2], rn2[2]; + u64 rn1, rom_id, temp, temp1; + int i, j, z, w, last_zero, loop; + u8 bit, reg_val, bit2; + u8 byte, byte1; + int disc, prev_disc, last_disc; + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + last_rn[0] = 0; + last_rn[1] = 0; + rom_id = 0; + prev_disc = 0; + loop = 0; + disc = -1; + last_disc = 0; + last_zero = 0; + while (!last_zero) { + /* + * Reset bus and all 1-wire device state machines + * so they can respond to our requests. + * + * Return 0 - device(s) present, 1 - no devices present. + */ + if (mxc_w1_ds2_reset_bus(data)) { + pr_debug("No devices present on the wire.\n"); + break; + } + rn[0] = 0; + rn[1] = 0; + __raw_writeb(0x80, (dev->regs + MXC_W1_CONTROL)); + mdelay(1); + mxc_w1_ds2_write_byte(data, 0xF0); + __raw_writeb(0x02, (dev->regs + MXC_W1_COMMAND)); + memcpy(rn2, last_rn, 16); + z = 0; + w = 0; + for (i = 0; i < 16; i++) { + reg_val = rn2[z] >> (8 * w); + mxc_w1_ds2_write_byte(data, reg_val); + reg_val = mxc_w1_read_byte(data); + if ((reg_val & 0x3) == 0x3) { + pr_debug("Device is Not Responding\n"); + break; + } + for (j = 0; j < 8; j += 2) { + byte = 0xFF; + byte1 = 1; + byte ^= byte1 << j; + bit = (reg_val >> j) & 0x1; + bit2 = (reg_val >> j); + if (bit) { + prev_disc = disc; + disc = 8 * i + j; + reg_val &= byte; + } + } + rn1 = 0; + rn1 = reg_val; + rn[z] |= rn1 << (8 * w); + w++; + if (i == 7) { + z++; + w = 0; + } + } + if ((disc == -1) || (disc == prev_disc)) + last_zero = 1; + if (disc == last_disc) + disc = prev_disc; + z = 0; + rom_id = 0; + for (i = 0, j = 1; i < 64; i++) { + temp = 0; + temp = (rn[z] >> j) & 0x1; + rom_id |= (temp << i); + j += 2; + if (i == 31) { + z++; + j = 1; + } + + } + if (disc > 63) { + last_rn[0] = rn[0]; + temp1 = rn[1]; + loop = disc % 64; + temp = 1; + temp1 |= (temp << (loop + 1)) - 1; + temp1 |= (temp << (loop + 1)); + last_rn[1] = temp1; + + } else { + last_rn[1] = 0; + temp1 = rn[0]; + temp = 1; + temp1 |= (temp << (loop + 1)) - 1; + temp1 |= (temp << (loop + 1)); + last_rn[0] = temp1; + } + last_disc = disc; + cb(master, rom_id); + } +} + static int __init mxc_w1_probe(struct platform_device *pdev) { struct mxc_w1_device *mdev; + struct mxc_w1_config *data = + (struct mxc_w1_config *)pdev->dev.platform_data; struct resource *res; + int irq = 0; int err = 0; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -143,6 +291,18 @@ static int __init mxc_w1_probe(struct platform_device *pdev) mdev->bus_master.data = mdev; mdev->bus_master.reset_bus = mxc_w1_ds2_reset_bus; mdev->bus_master.touch_bit = mxc_w1_ds2_touch_bit; + if (data->search_rom_accelerator) { + mdev->bus_master.write_byte = &mxc_w1_ds2_write_byte; + mdev->bus_master.read_byte = &mxc_w1_ds2_read_byte; + mdev->bus_master.search = &search_ROM_accelerator; + irq = platform_get_irq(pdev, 0); + err = request_irq(irq, w1_interrupt_handler, 0, "mxc_w1", mdev); + if (err) { + pr_debug("OWire:request_irq(%d) returned error %d\n", + irq, err); + goto failed_irq; + } + } err = w1_add_master_device(&mdev->bus_master); @@ -153,6 +313,9 @@ static int __init mxc_w1_probe(struct platform_device *pdev) return 0; failed_add: + if (irq) + free_irq(irq, mdev); +failed_irq: iounmap(mdev->regs); failed_ioremap: release_mem_region(res->start, resource_size(res)); diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig index 1f51366417b9..ad77126801f5 100644 --- a/drivers/w1/slaves/Kconfig +++ b/drivers/w1/slaves/Kconfig @@ -22,12 +22,34 @@ config W1_SLAVE_DS2431 Say Y here if you want to use a 1-wire 1kb EEPROM family device (DS2431) +config W1_SLAVE_DS2751 + tristate "Battery Level sensing support (DS2751)" + depends on W1 + help + Say Y here if you want to use a 1-wire + battery level sensing device (DS2751). + +config W1_SLAVE_DS2751_CRC + bool "Protect DS2751 data with a CRC16" + depends on W1_SLAVE_DS2751 + select CRC16 + help + Say Y here to protect DS2751 data with a CRC16. + Each block has 30 bytes of data and a two byte CRC16. + Full block writes are only allowed if the CRC is valid. + config W1_SLAVE_DS2433 tristate "4kb EEPROM family support (DS2433)" help Say Y here if you want to use a 1-wire 4kb EEPROM family device (DS2433). +config W1_SLAVE_DS2438 + tristate "Smart Battery Monitor (DS2438)" + help + Say Y here if you want to use a 1-wire + Smart Battery Monitor family device (DS2438). + config W1_SLAVE_DS2433_CRC bool "Protect DS2433 data with a CRC16" depends on W1_SLAVE_DS2433 diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile index f1f51f19b129..fd53f53d8681 100644 --- a/drivers/w1/slaves/Makefile +++ b/drivers/w1/slaves/Makefile @@ -8,3 +8,5 @@ obj-$(CONFIG_W1_SLAVE_DS2431) += w1_ds2431.o obj-$(CONFIG_W1_SLAVE_DS2433) += w1_ds2433.o obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o obj-$(CONFIG_W1_SLAVE_BQ27000) += w1_bq27000.o +obj-$(CONFIG_W1_SLAVE_DS2751) += w1_ds2751.o +obj-$(CONFIG_W1_SLAVE_DS2438) += w1_ds2438.o diff --git a/drivers/w1/slaves/w1_ds2438.c b/drivers/w1/slaves/w1_ds2438.c new file mode 100644 index 000000000000..cfe65d7431ab --- /dev/null +++ b/drivers/w1/slaves/w1_ds2438.c @@ -0,0 +1,585 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <asm/types.h> + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/jiffies.h> +#include <linux/workqueue.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/idr.h> +#include <linux/power_supply.h> + +#include "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" +#include "w1_ds2438.h" + +struct ds2438_device_info { + /* DS2438 data, valid after calling ds2438_battery_read_status() */ + unsigned long update_time; /* jiffies when data read */ + char raw[DS2438_PAGE_SIZE]; /* raw DS2438 data */ + int voltage_uV; + int current_uA; + int accum_current_uAh; + int temp_C; + int charge_status; + u8 init:1; + u8 setup:1; + u8 calibrate:1; + u8 input_src:1; + u8 ee_flg:1; + u8 resv_bit:3; + u8 threshold:8; + u16 resv_bytes; + u32 senser; + + struct power_supply bat; + struct device *w1_dev; + struct ds2438_ops ops; + struct workqueue_struct *monitor_wqueue; + struct delayed_work monitor_work; +}; + +#define DS2438_SENSER 25 +#define to_ds2438_device_info(x) container_of((x), struct ds2438_device_info, \ + bat); + + +static enum power_supply_property ds2438_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CHARGE_NOW, +}; + +static char ds2438_sensers_title[] = "DS2438 senserin thousands of resister:"; +static unsigned int cache_time = 1000; +module_param(cache_time, uint, 0644); +MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); + +static ssize_t ds2438_show_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + return sprintf(buf, "%s\n", di->input_src ? "1:VDD" : "0:VAD"); +} + +static ssize_t ds2438_show_senser(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int len; + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + len = sprintf(buf, "%s\n", ds2438_sensers_title); + len += sprintf(buf + len, "%d\n", di->senser); + return len; +} + +static ssize_t ds2438_show_ee(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + return sprintf(buf, "%d\n", di->ee_flg); +} + +static ssize_t ds2438_show_threshold(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + return sprintf(buf, "%d\n", di->threshold); +} + +static ssize_t ds2438_set_input(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + di->input_src = !!simple_strtoul(buf, NULL, 0); + return count; +} + +static ssize_t ds2438_set_senser(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + u32 resister; + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + resister = simple_strtoul(buf, NULL, 0); + if (resister) + di->senser = resister; + return count; +} + +static ssize_t ds2438_set_ee(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + di->ee_flg = !!simple_strtoul(buf, NULL, 0); + di->setup = 1; + return count; +} + +static ssize_t ds2438_set_threshold(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int threshold; + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + threshold = simple_strtoul(buf, NULL, 0); + if (threshold < 256) { + di->threshold = threshold; + di->setup = 1; + return count; + } + return -EINVAL; +} + +static ssize_t ds2438_set_calibrate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + di->calibrate = !!simple_strtoul(buf, NULL, 0); + return count; +} + +static struct device_attribute ds2438_dev_attr[] = { + __ATTR(input_src, 0664, ds2438_show_input, ds2438_set_input), + __ATTR(senser, 0664, ds2438_show_senser, ds2438_set_senser), + __ATTR(ee_flg, 0664, ds2438_show_ee, ds2438_set_ee), + __ATTR(threshold, 0664, ds2438_show_threshold, ds2438_set_threshold), + __ATTR(calibrate, 0220, NULL, ds2438_set_calibrate), +}; + +static void ds2438_setup(struct ds2438_device_info *di) +{ + di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); + di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); + if (di->init && di->setup) { + if (di->ee_flg) + di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_EE; + else + di->raw[PAGE0_STAT_CTRL] &= ~DS2438_CTRL_EE; + if (di->input_src) + di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_AD; + else + di->raw[PAGE0_STAT_CTRL] &= ~DS2438_CTRL_AD; + di->raw[PAGE0_THRESHOLD] = di->threshold; + } else { + di->ee_flg = !!(di->raw[PAGE0_STAT_CTRL] & DS2438_CTRL_EE); + di->input_src = !!(di->raw[PAGE0_STAT_CTRL] & DS2438_CTRL_AD); + di->threshold = di->raw[PAGE0_THRESHOLD]; + di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_IAD | DS2438_CTRL_CA; + } + di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); + if (!di->init) { + di->calibrate = 1; + di->init = 1; + } + di->setup = 0; +} + +static void ds2438_calibrate(struct ds2438_device_info *di) +{ + int current_raw; + /* disable ICA */ + di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); + di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->raw[PAGE0_STAT_CTRL] &= ~DS2438_CTRL_IAD; + di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); + + /* Zero offset */ + di->ops.load_sram(di->w1_dev, PAGE1_ETM); + di->ops.read_page(di->w1_dev, PAGE1_ETM, di->raw); + ds2438_writew(di->raw + PAGE1_OFFSET_LSB, 0); + di->ops.drain_sram(di->w1_dev, PAGE1_ETM_BYTE0); + + /* enable ICA & read current */ + di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); + di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_IAD; + di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); + /*wait current convert about 36HZ */ + mdelay(30); + /* disable ICA */ + di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); + di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->raw[PAGE0_STAT_CTRL] &= ~DS2438_CTRL_IAD; + di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); + /* read current value */ + current_raw = ds2438_readw(di->raw + PAGE0_CURRENT_LSB); + /* write offset by current value */ + di->ops.load_sram(di->w1_dev, PAGE1_ETM); + di->ops.read_page(di->w1_dev, PAGE1_ETM, di->raw); + ds2438_writew(di->raw + PAGE1_OFFSET_LSB, current_raw << 8); + di->ops.write_page(di->w1_dev, PAGE1_ETM, di->raw); + di->ops.drain_sram(di->w1_dev, PAGE1_ETM); + + /*enable ICA again */ + di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); + di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_IAD; + di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); + di->calibrate = 0; +} + +/* + * power supply temperture is in tenths of degree. + */ +static inline int ds2438_get_temp(u16 raw) +{ + int degree, s; + s = !!(raw & 0x8000); + + if (s) + raw = ((~raw & 0x7FFF) + 1); + degree = ((raw >> 8) * 10) + (((raw & 0xFF) * 5) + 63) / 128; + return s ? -degree : degree; +} + +/* + * power supply current is in uA. + */ +static inline int ds2438_get_current(u32 senser, u16 raw) +{ + int s, current_uA; + s = !!(raw & 0xFC00); + /* (x * 1000 * 1000)uA / (4096 * (Rsens / 1000)) */ + raw &= 0x3FF; + current_uA = raw * 125 * 125 * 125; + current_uA /= (senser << 3); + return s ? -current_uA : current_uA; +} + +/* + * power supply current is in uAh. + */ +static inline int ds2438_get_ica(u32 senser, u8 raw) +{ + int charge_uAh; + /* (x * 1000 * 1000)uA / (2048 * (Rsens / 1000)) */ + charge_uAh = (raw * 125 * 125 * 125) >> 4; + charge_uAh /= (senser << 4); + return charge_uAh; +} + +static int ds2438_battery_update_page1(struct ds2438_device_info *di) +{ + int ica_raw; + di->ops.load_sram(di->w1_dev, PAGE1_ETM); + di->ops.read_page(di->w1_dev, PAGE1_ETM, di->raw); + ica_raw = di->raw[PAGE1_ICA]; + di->accum_current_uAh = ds2438_get_ica(di->senser, ica_raw); + return 0; +} + +static int ds2438_battery_read_status(struct ds2438_device_info *di) +{ + u8 status; + int temp_raw, voltage_raw, current_raw; + + if (!(di->init) || di->setup) + ds2438_setup(di); + + if (di->calibrate) + ds2438_calibrate(di); + + if (di->update_time && time_before(jiffies, di->update_time + + msecs_to_jiffies(cache_time))) + return 0; + + di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); + di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); + status = di->raw[PAGE0_STAT_CTRL]; + temp_raw = ds2438_readw(di->raw + PAGE0_TEMP_LSB); + voltage_raw = ds2438_readw(di->raw + PAGE0_VOLTAGE_LSB); + current_raw = ds2438_readw(di->raw + PAGE0_CURRENT_LSB); + di->temp_C = ds2438_get_temp(temp_raw); + di->voltage_uV = voltage_raw * 10000; + di->current_uA = ds2438_get_current(di->senser, current_raw); + + ds2438_battery_update_page1(di); + + if (!(status & DS2438_STAT_TB)) + di->ops.command(di->w1_dev, DS2438_CONVERT_TEMP, 0); + if (!(status & DS2438_STAT_ADB)) + di->ops.command(di->w1_dev, DS2438_CONVERT_VOLT, 0); + di->update_time = jiffies; + return 0; +} + +static void ds2438_battery_update_status(struct ds2438_device_info *di) +{ + int old_charge_status = di->charge_status; + + ds2438_battery_read_status(di); + + if (di->charge_status != old_charge_status) + power_supply_changed(&di->bat); +} + +static void ds2438_battery_work(struct work_struct *work) +{ + struct ds2438_device_info *di = container_of(work, + struct ds2438_device_info, + monitor_work.work); + const int interval = HZ * 60; + + dev_dbg(di->w1_dev, "%s\n", __func__); + + ds2438_battery_update_status(di); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval); +} + +static void ds2438_battery_external_power_changed(struct power_supply *psy) +{ + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + dev_dbg(di->w1_dev, "%s\n", __func__); + + cancel_delayed_work(&di->monitor_work); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ / 10); +} + +static int ds2438_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + return 0; + default: + break; + } + + ds2438_battery_read_status(di); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->voltage_uV; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->current_uA; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = di->temp_C; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = di->accum_current_uAh; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* W1 slave DS2438 famliy operations */ +static int ds2438_read_page(struct device *dev, u8 page, u8 *buf) +{ + struct w1_slave *slave = container_of(dev, struct w1_slave, dev); + if ((page >= DS2438_PAGE_NUM) || (buf == NULL)) + return -EINVAL; + + mutex_lock(&slave->master->mutex); + if (!w1_reset_select_slave(slave)) { + w1_write_8(slave->master, W1_READ_SCRATCHPAD); + w1_write_8(slave->master, page); + w1_read_block(slave->master, buf, DS2438_PAGE_SIZE); + } + mutex_unlock(&slave->master->mutex); + return 0; +} + +static int ds2438_write_page(struct device *dev, u8 page, u8 *buf) +{ + struct w1_slave *slave = container_of(dev, struct w1_slave, dev); + if ((page >= DS2438_PAGE_NUM) || (buf == NULL)) + return -EINVAL; + + mutex_lock(&slave->master->mutex); + if (!w1_reset_select_slave(slave)) { + w1_write_8(slave->master, DS2438_WRITE_SCRATCHPAD); + w1_write_8(slave->master, page); + w1_write_block(slave->master, buf, DS2438_PAGE_SIZE); + } + mutex_unlock(&slave->master->mutex); + return 0; +} + +static int ds2438_command(struct device *dev, u8 command, u8 data) +{ + struct w1_slave *slave = container_of(dev, struct w1_slave, dev); + + mutex_lock(&slave->master->mutex); + if (!w1_reset_select_slave(slave)) { + w1_write_8(slave->master, command); + switch (command) { + case DS2438_COPY_SCRATCHPAD: + case DS2438_RECALL_MEMORY: + w1_write_8(slave->master, data); + } + } + mutex_unlock(&slave->master->mutex); + return 0; +} + +static int ds2438_drain_sram(struct device *dev, u8 page) +{ + return ds2438_command(dev, DS2438_COPY_SCRATCHPAD, page); +} + +static int ds2438_load_sram(struct device *dev, u8 page) +{ + return ds2438_command(dev, DS2438_RECALL_MEMORY, page); +} + +static inline void ds2438_defaut_ops(struct ds2438_ops *ops) +{ + ops->read_page = ds2438_read_page; + ops->write_page = ds2438_write_page; + ops->drain_sram = ds2438_drain_sram; + ops->load_sram = ds2438_load_sram; + ops->command = ds2438_command; +} + +static int ds2438_add_slave(struct w1_slave *slave) +{ + int i, retval = 0; + struct ds2438_device_info *di; + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + retval = -ENOMEM; + goto di_alloc_failed; + } + + di->w1_dev = &slave->dev; + di->bat.name = dev_name(&slave->dev); + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = ds2438_battery_props; + di->bat.num_properties = ARRAY_SIZE(ds2438_battery_props); + di->bat.get_property = ds2438_battery_get_property; + di->bat.external_power_changed = ds2438_battery_external_power_changed; + ds2438_defaut_ops(&di->ops); + di->senser = DS2438_SENSER; + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + + retval = power_supply_register(&slave->dev, &di->bat); + if (retval) { + dev_err(&slave->dev, "failed to register battery\n"); + goto batt_failed; + } + + for (i = 0; i < ARRAY_SIZE(ds2438_dev_attr); i++) { + if (device_create_file(di->bat.dev, ds2438_dev_attr + i)) { + printk(KERN_ERR "Customize attribute file fail!\n"); + break; + } + } + + if (i != ARRAY_SIZE(ds2438_dev_attr)) { + for (; i >= 0; i++) + device_remove_file(di->bat.dev, ds2438_dev_attr + i); + goto workqueue_failed; + } + INIT_DELAYED_WORK(&di->monitor_work, ds2438_battery_work); + di->monitor_wqueue = create_singlethread_workqueue(dev_name(&slave->dev)); + if (!di->monitor_wqueue) { + retval = -ESRCH; + goto workqueue_failed; + } + dev_set_drvdata(&slave->dev, di); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ / 2); + + goto success; + + workqueue_failed: + power_supply_unregister(&di->bat); + batt_failed: + kfree(di); + di_alloc_failed: + success: + return retval; +} + +static void ds2438_remove_slave(struct w1_slave *slave) +{ + struct ds2438_device_info *di = dev_get_drvdata(&slave->dev); + + cancel_rearming_delayed_workqueue(di->monitor_wqueue, + &di->monitor_work); + destroy_workqueue(di->monitor_wqueue); + power_supply_unregister(&di->bat); +} + +static struct w1_family_ops w1_ds2438_fops = { + .add_slave = ds2438_add_slave, + .remove_slave = ds2438_remove_slave, +}; + +static struct w1_family w1_family_ds2438 = { + .fid = W1_FAMILY_DS2438, + .fops = &w1_ds2438_fops, +}; + +static int __init w1_ds2438_init(void) +{ + pr_info("1-wire driver for the DS2438 smart battery monitor\n"); + return w1_register_family(&w1_family_ds2438); +} + +static void __exit w1_ds2438_fini(void) +{ + w1_unregister_family(&w1_family_ds2438); +} + +module_init(w1_ds2438_init); +module_exit(w1_ds2438_fini); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductors Inc"); +MODULE_DESCRIPTION("1-wire DS2438 family, smart battery monitor."); diff --git a/drivers/w1/slaves/w1_ds2438.h b/drivers/w1/slaves/w1_ds2438.h new file mode 100644 index 000000000000..fe22b6ec253b --- /dev/null +++ b/drivers/w1/slaves/w1_ds2438.h @@ -0,0 +1,119 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __W1_DS2438_H__ +#define __W1_DS2438_H__ + +#include <asm/types.h> + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> + +#define DS2438_DEV_NAME "ds2438-battery" + +#define DS2438_PAGE_SIZE 8 +#define DS2438_PAGE_NUM 8 + +#define DS2438_CONVERT_TEMP 0x44 +#define DS2438_CONVERT_VOLT 0xB4 +#define DS2438_WRITE_SCRATCHPAD 0x4E +#define DS2438_COPY_SCRATCHPAD 0x48 +#define DS2438_RECALL_MEMORY 0xB8 + +enum DS2438_PAGE { + PAGE0_CONTROL = 0, + PAGE1_ETM, + PAGE2_STAMP, + PAGE3_RESV3, + PAGE4_RESV4, + PAGE5_RESV5, + PAGE6_RESV6, + PAGE7_CCA, +}; + +enum DS2438_REG { + /* PAGE 0 */ + PAGE0_STAT_CTRL = 0, + PAGE0_TEMP_LSB = 1, + PAGE0_TEMP_MSB = 2, + PAGE0_VOLTAGE_LSB = 3, + PAGE0_VOLTAGE_MSB = 4, + PAGE0_CURRENT_LSB = 5, + PAGE0_CURRENT_MSB = 6, + PAGE0_THRESHOLD = 7, + + /* PAGE 1 */ + PAGE1_ETM_BYTE0 = 0, + PAGE1_ETM_BYTE1 = 1, + PAGE1_ETM_BYTE2 = 2, + PAGE1_ETM_BYTE3 = 3, + PAGE1_ICA = 4, + PAGE1_OFFSET_LSB = 5, + PAGE1_OFFSET_MSB = 6, + + /* PAGE 2 */ + PAGE2_DISCONNECT_BYTE0 = 0, + PAGE2_DISCONNECT_BYTE1 = 1, + PAGE2_DISCONNECT_BYTE2 = 2, + PAGE2_DISCONNECT_BYTE3 = 3, + PAGE2_END_CHARGE_BYTE0 = 4, + PAGE2_END_CHARGE_BYTE1 = 5, + PAGE2_END_CHARGE_BYTE2 = 6, + PAGE2_END_CHARGE_BYTE3 = 7, + + /* PAGE 3 */ + /* PAGE 4 */ + /* PAGE 5 */ + /* PAGE 6 */ + /* PAGE 7 */ + PAGE7_CCA_LSB = 4, + PAGE7_CCA_MSB = 5, + PAGE7_DCA_LSB = 6, + PAGE7_DCA_MSB = 7, +}; + +#define DS2438_CTRL_IAD (1 << 0) +#define DS2438_CTRL_CA (1 << 1) +#define DS2438_CTRL_EE (1 << 2) +#define DS2438_CTRL_AD (1 << 3) +#define DS2438_STAT_TB (1 << 4) +#define DS2438_STAT_NVB (1 << 5) +#define DS2438_STAT_ADB (1 << 6) + +struct ds2438_ops { + int (*read_page) (struct device *, u8, u8 *); + int (*read_byte) (struct device *, u8, u8, u8 *); + int (*read_halfword) (struct device *, u8, u8, u16 *); + int (*read_word) (struct device *, u8, u8, u32 *); + int (*write_page) (struct device *, u8, u8 *); + int (*write_byte) (struct device *, u8, u8, u8); + int (*write_halfword) (struct device *, u8, u8, u16); + int (*write_word) (struct device *, u8, u8, u32); + int (*drain_sram) (struct device *, u8); + int (*load_sram) (struct device *, u8); + int (*command) (struct device *, u8, u8); +}; + +static inline u16 ds2438_readw(u8 *raw) +{ + return ((*(raw + 1)) << 8) | (*raw); +} + +static inline void ds2438_writew(u8 *raw, u16 data) +{ + *raw++ = data & 0xFF; + *raw = (data >> 8) & 0xFF; +} +#endif /* __W1_DS2438_H__ */ diff --git a/drivers/w1/slaves/w1_ds2751.c b/drivers/w1/slaves/w1_ds2751.c new file mode 100644 index 000000000000..9346a21bdc70 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2751.c @@ -0,0 +1,317 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * Implementation based on w1_ds2433.c + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> +#ifdef CONFIG_W1_F51_CRC +#include <linux/crc16.h> + +#define CRC16_INIT 0 +#define CRC16_VALID 0xb001 + +#endif + +#include "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" + +#define W1_EEPROM_SIZE 32 +#define W1_PAGE_SIZE 32 +#define W1_PAGE_BITS 5 +#define W1_PAGE_MASK 0x1F + +#define W1_F51_TIME 300 + +#define W1_F51_READ_EEPROM 0xB8 +#define W1_F51_WRITE_SCRATCH 0x6C +#define W1_F51_READ_SCRATCH 0x69 +#define W1_F51_COPY_SCRATCH 0x48 +#define W1_STATUS_OFFSET 0x0001 +#define W1_EEPROM_OFFSET 0x0007 +#define W1_SPECIAL_OFFSET 0x0008 +#define W1_EEPROM_BLOCK_0 0x0020 +#define W1_EEPROM_BLOCK_1 0x0030 +#define W1_SRAM 0x0080 +struct w1_f51_data { + u8 memory[W1_EEPROM_SIZE]; + u32 validcrc; +}; + +/** + * Check the file size bounds and adjusts count as needed. + * This would not be needed if the file size didn't reset to 0 after a write. + */ +static inline size_t w1_f51_fix_count(loff_t off, size_t count, size_t size) +{ + if (off > size) + return 0; + + if ((off + count) > size) + return (size - off); + + return count; +} + +#ifdef CONFIG_W1_F51_CRC +static int w1_f51_refresh_block(struct w1_slave *sl, struct w1_f51_data *data, + int block) +{ + u8 wrbuf[3]; + int off = block * W1_PAGE_SIZE; + if (data->validcrc & (1 << block)) + return 0; + + if (w1_reset_select_slave(sl)) { + data->validcrc = 0; + return -EIO; + } + wrbuf[0] = W1_F51_READ_EEPROM; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, &data->memory[off], W1_PAGE_SIZE); + + /* cache the block if the CRC is valid */ + if (crc16(CRC16_INIT, &data->memory[off], W1_PAGE_SIZE) == CRC16_VALID) + data->validcrc |= (1 << block); + + return 0; +} +#endif /* CONFIG_W1_F51_CRC */ + +static ssize_t w1_f51_read_bin(struct kobject *kobj, char *buf, loff_t off, + size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); +#ifdef CONFIG_W1_F51_CRC + struct w1_f51_data *data = sl->family_data; + int i, min_page, max_page; +#else + u8 wrbuf[3]; +#endif + + if ((count = w1_f51_fix_count(off, count, W1_EEPROM_SIZE)) == 0) { + return 0; + } + mutex_lock(&sl->master->mutex); +#ifdef CONFIG_W1_F51_CRC + min_page = (off >> W1_PAGE_BITS); + max_page = (off + count - 1) >> W1_PAGE_BITS; + for (i = min_page; i <= max_page; i++) { + if (w1_f51_refresh_block(sl, data, i)) { + count = -EIO; + goto out_up; + } + } + memcpy(buf, &data->memory[off], count); + +#else /* CONFIG_W1_F51_CRC */ + + /* read directly from the EEPROM */ + if (w1_reset_select_slave(sl)) { + count = -EIO; + goto out_up; + } + off = (loff_t) W1_EEPROM_BLOCK_0; + wrbuf[0] = W1_F51_READ_EEPROM; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + w1_write_block(sl->master, wrbuf, 3); + if (w1_reset_select_slave(sl)) { + count = -EIO; + goto out_up; + } + + wrbuf[0] = W1_F51_READ_SCRATCH; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, buf, count); + +#endif /* CONFIG_W1_F51_CRC */ + + out_up: + mutex_unlock(&sl->master->mutex); + return count; +} + +/** + * Writes to the scratchpad and reads it back for verification. + * Then copies the scratchpad to EEPROM. + * The data must be on one page. + * The master must be locked. + * + * @param sl The slave structure + * @param addr Address for the write + * @param len length must be <= (W1_PAGE_SIZE - (addr & W1_PAGE_MASK)) + * @param data The data to write + * @return 0=Success -1=failure + */ +static int w1_f51_write(struct w1_slave *sl, int addr, int len, const u8 * data) +{ + u8 wrbuf[4]; + u8 rdbuf[W1_EEPROM_SIZE + 3]; + u8 es = (addr + len - 1) & 0x1f; + /* Write the data to the scratchpad */ + if (w1_reset_select_slave(sl)) + return -1; + wrbuf[0] = W1_F51_WRITE_SCRATCH; + wrbuf[1] = addr & 0xff; + wrbuf[2] = addr >> 8; + + w1_write_block(sl->master, wrbuf, 3); + w1_write_block(sl->master, data, len); + /* Read the scratchpad and verify */ + if (w1_reset_select_slave(sl)) + return -1; + wrbuf[0] = W1_F51_READ_SCRATCH; + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, rdbuf, len + 3); + /* Compare what was read against the data written */ + if (memcmp(data, &rdbuf[0], len) != 0) { + printk("Error reading the scratch Pad\n"); + return -1; + } + /* Copy the scratchpad to EEPROM */ + if (w1_reset_select_slave(sl)) + return -1; + wrbuf[0] = W1_F51_COPY_SCRATCH; + wrbuf[3] = es; + w1_write_block(sl->master, wrbuf, 4); + /* Sleep for 5 ms to wait for the write to complete */ + msleep(5); + + /* Reset the bus to wake up the EEPROM (this may not be needed) */ + w1_reset_bus(sl->master); + + return 0; +} + +static ssize_t w1_f51_write_bin(struct kobject *kobj, char *buf, loff_t off, + size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int addr; + + if ((count = w1_f51_fix_count(off, count, W1_EEPROM_SIZE)) == 0) + return 0; + off = (loff_t) 0x0020; +#ifdef CONFIG_W1_F51_CRC + /* can only write full blocks in cached mode */ + if ((off & W1_PAGE_MASK) || (count & W1_PAGE_MASK)) { + dev_err(&sl->dev, "invalid offset/count off=%d cnt=%zd\n", + (int)off, count); + return -EINVAL; + } + + /* make sure the block CRCs are valid */ + for (idx = 0; idx < count; idx += W1_PAGE_SIZE) { + if (crc16(CRC16_INIT, &buf[idx], W1_PAGE_SIZE) != CRC16_VALID) { + dev_err(&sl->dev, "bad CRC at offset %d\n", (int)off); + return -EINVAL; + } + } +#endif /* CONFIG_W1_F51_CRC */ + + mutex_lock(&sl->master->mutex); + + /* Can only write data to one page at a time */ + addr = off; + if (w1_f51_write(sl, addr, count, buf) < 0) { + count = -EIO; + goto out_up; + } + + out_up: + mutex_unlock(&sl->master->mutex); + + return count; +} + +static struct bin_attribute w1_f51_bin_attr = { + .attr = { + .name = "eeprom", + .mode = S_IRUGO | S_IWUSR, + .owner = THIS_MODULE, + }, + .size = W1_EEPROM_SIZE, + .read = w1_f51_read_bin, + .write = w1_f51_write_bin, +}; + +static int w1_f51_add_slave(struct w1_slave *sl) +{ + int err; +#ifdef CONFIG_W1_F51_CRC + struct w1_f51_data *data; + data = kmalloc(sizeof(struct w1_f51_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + memset(data, 0, sizeof(struct w1_f51_data)); + sl->family_data = data; + +#endif /* CONFIG_W1_F51_CRC */ + + err = sysfs_create_bin_file(&sl->dev.kobj, &w1_f51_bin_attr); + +#ifdef CONFIG_W1_F51_CRC + if (err) + kfree(data); +#endif /* CONFIG_W1_F51_CRC */ + + return err; +} + +static void w1_f51_remove_slave(struct w1_slave *sl) +{ +#ifdef CONFIG_W1_F51_CRC + kfree(sl->family_data); + sl->family_data = NULL; +#endif /* CONFIG_W1_F51_CRC */ + sysfs_remove_bin_file(&sl->dev.kobj, &w1_f51_bin_attr); +} + +static struct w1_family_ops w1_f51_fops = { + .add_slave = w1_f51_add_slave, + .remove_slave = w1_f51_remove_slave, +}; + +static struct w1_family w1_family_51 = { + .fid = W1_EEPROM_DS2751, + .fops = &w1_f51_fops, +}; + +static int __init w1_f51_init(void) +{ + return w1_register_family(&w1_family_51); +} + +static void __exit w1_f51_fini(void) +{ + w1_unregister_family(&w1_family_51); +} + +module_init(w1_f51_init); +module_exit(w1_f51_fini); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductors Inc"); +MODULE_DESCRIPTION + ("w1 family 51 driver for DS2751, Battery Level Sensing Device"); diff --git a/drivers/w1/w1_family.h b/drivers/w1/w1_family.h index 3ca1b9298f21..c541f9215765 100644 --- a/drivers/w1/w1_family.h +++ b/drivers/w1/w1_family.h @@ -32,8 +32,10 @@ #define W1_THERM_DS18S20 0x10 #define W1_THERM_DS1822 0x22 #define W1_EEPROM_DS2433 0x23 +#define W1_EEPROM_DS2751 0x51 #define W1_THERM_DS18B20 0x28 #define W1_EEPROM_DS2431 0x2D +#define W1_FAMILY_DS2438 0x26 #define W1_FAMILY_DS2760 0x30 #define MAXNAMELEN 32 diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index b1ccc04f3c9a..86f40e8afe4a 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -203,6 +203,27 @@ config PNX4008_WATCHDOG Say N if you are unsure. +config MXC_WATCHDOG + tristate "MXC watchdog" + depends on WATCHDOG && WATCHDOG_NOWAYOUT + depends on ARCH_MXC + help + Watchdog timer embedded into MXC chips. This will + reboot your system when timeout is reached. + + NOTE: once enabled, this timer cannot be disabled. + To compile this driver as a module, choose M here: the + module will be called mxc_wdt. + +config STMP3XXX_WATCHDOG + tristate "Sigmatel STMP3XXX watchdog" + depends on ARCH_STMP3XXX + help + Say Y here if to include support for the watchdog timer + for the Sigmatel STMP37XX/378X SoC. + To compile this driver as a module, choose M here: the + module will be called stmp3xxx_wdt. + config IOP_WATCHDOG tristate "IOP Watchdog" depends on PLAT_IOP diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 3d774294a2b7..a80cb7c65f61 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -38,6 +38,8 @@ obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o obj-$(CONFIG_MPCORE_WATCHDOG) += mpcore_wdt.o obj-$(CONFIG_EP93XX_WATCHDOG) += ep93xx_wdt.o +obj-$(CONFIG_MXC_WATCHDOG) += mxc_wdt.o +obj-$(CONFIG_STMP3XXX_WATCHDOG) += stmp3xxx_wdt.o obj-$(CONFIG_PNX4008_WATCHDOG) += pnx4008_wdt.o obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o diff --git a/drivers/watchdog/mxc_wdt.c b/drivers/watchdog/mxc_wdt.c new file mode 100644 index 000000000000..234ddfc0ebb9 --- /dev/null +++ b/drivers/watchdog/mxc_wdt.c @@ -0,0 +1,381 @@ +/* + * linux/drivers/char/watchdog/mxc_wdt.c + * + * Watchdog driver for FSL MXC. It is based on omap1610_wdt.c + * + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * 2005 (c) MontaVista Software, Inc. All 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 + * + * History: + * + * 20051207: <AKuster@mvista.com> + * Full rewrite based on + * linux-2.6.15-rc5/drivers/char/watchdog/omap_wdt.c + * Add platform resource support + * + */ + +/*! + * @defgroup WDOG Watchdog Timer (WDOG) Driver + */ +/*! + * @file mxc_wdt.c + * + * @brief Watchdog timer driver + * + * @ingroup WDOG + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/reboot.h> +#include <linux/smp_lock.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/moduleparam.h> +#include <linux/clk.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <mach/hardware.h> +#include <asm/irq.h> +#include <asm/bitops.h> + +#include <mach/hardware.h> +#include "mxc_wdt.h" +#define DVR_VER "2.0" + +#define WDOG_SEC_TO_COUNT(s) ((s * 2) << 8) +#define WDOG_COUNT_TO_SEC(c) ((c >> 8) / 2) + +static void __iomem *wdt_base_reg; +static int mxc_wdt_users; +static struct clk *mxc_wdt_clk; + +static unsigned timer_margin = TIMER_MARGIN_DEFAULT; +module_param(timer_margin, uint, 0); +MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)"); + +static unsigned dev_num = 0; + +static void mxc_wdt_ping(void *base) +{ + /* issue the service sequence instructions */ + __raw_writew(WDT_MAGIC_1, base + MXC_WDT_WSR); + __raw_writew(WDT_MAGIC_2, base + MXC_WDT_WSR); +} + +static void mxc_wdt_config(void *base) +{ + u16 val; + + val = __raw_readw(base + MXC_WDT_WCR); + val |= 0xFF00 | WCR_WOE_BIT | WCR_WDA_BIT | WCR_SRS_BIT; + /* enable suspend WDT */ + val |= WCR_WDZST_BIT | WCR_WDBG_BIT; + /* generate reset if wdog times out */ + val &= ~WCR_WRE_BIT; + + __raw_writew(val, base + MXC_WDT_WCR); +} + +static void mxc_wdt_enable(void *base) +{ + u16 val; + + val = __raw_readw(base + MXC_WDT_WCR); + val |= WCR_WDE_BIT; + __raw_writew(val, base + MXC_WDT_WCR); +} + +static void mxc_wdt_disable(void *base) +{ + /* disable not supported by this chip */ +} + +static void mxc_wdt_adjust_timeout(unsigned new_timeout) +{ + if (new_timeout < TIMER_MARGIN_MIN) + new_timeout = TIMER_MARGIN_DEFAULT; + if (new_timeout > TIMER_MARGIN_MAX) + new_timeout = TIMER_MARGIN_MAX; + timer_margin = new_timeout; +} + +static u16 mxc_wdt_get_timeout(void *base) +{ + u16 val; + + val = __raw_readw(base + MXC_WDT_WCR); + return WDOG_COUNT_TO_SEC(val); +} + +static u16 mxc_wdt_get_bootreason(void *base) +{ + u16 val; + + val = __raw_readw(base + MXC_WDT_WRSR); + return val; +} + +static void mxc_wdt_set_timeout(void *base) +{ + u16 val; + val = __raw_readw(base + MXC_WDT_WCR); + val = (val & 0x00FF) | WDOG_SEC_TO_COUNT(timer_margin); + __raw_writew(val, base + MXC_WDT_WCR); + val = __raw_readw(base + MXC_WDT_WCR); + timer_margin = WDOG_COUNT_TO_SEC(val); +} + +/* + * Allow only one task to hold it open + */ + +static int mxc_wdt_open(struct inode *inode, struct file *file) +{ + + if (test_and_set_bit(1, (unsigned long *)&mxc_wdt_users)) + return -EBUSY; + + mxc_wdt_config(wdt_base_reg); + mxc_wdt_set_timeout(wdt_base_reg); + mxc_wdt_enable(wdt_base_reg); + mxc_wdt_ping(wdt_base_reg); + + return 0; +} + +static int mxc_wdt_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer unless NOWAYOUT is defined. + */ +#ifndef CONFIG_WATCHDOG_NOWAYOUT + mxc_wdt_disable(wdt_base_reg); + +#else + printk(KERN_CRIT "mxc_wdt: Unexpected close, not stopping!\n"); +#endif + mxc_wdt_users = 0; + return 0; +} + +static ssize_t +mxc_wdt_write(struct file *file, const char __user * data, + size_t len, loff_t * ppos) +{ + /* Refresh LOAD_TIME. */ + if (len) + mxc_wdt_ping(wdt_base_reg); + return len; +} + +static int +mxc_wdt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int new_margin; + int bootr; + + static struct watchdog_info ident = { + .identity = "MXC Watchdog", + .options = WDIOF_SETTIMEOUT, + .firmware_version = 0, + }; + + switch (cmd) { + default: + return -ENOIOCTLCMD; + case WDIOC_GETSUPPORT: + return copy_to_user((struct watchdog_info __user *)arg, &ident, + sizeof(ident)); + case WDIOC_GETSTATUS: + return put_user(0, (int __user *)arg); + case WDIOC_GETBOOTSTATUS: + bootr = mxc_wdt_get_bootreason(wdt_base_reg); + return put_user(bootr, (int __user *)arg); + case WDIOC_KEEPALIVE: + mxc_wdt_ping(wdt_base_reg); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, (int __user *)arg)) + return -EFAULT; + + mxc_wdt_adjust_timeout(new_margin); + mxc_wdt_disable(wdt_base_reg); + mxc_wdt_set_timeout(wdt_base_reg); + mxc_wdt_enable(wdt_base_reg); + mxc_wdt_ping(wdt_base_reg); + return 0; + + case WDIOC_GETTIMEOUT: + mxc_wdt_ping(wdt_base_reg); + new_margin = mxc_wdt_get_timeout(wdt_base_reg); + return put_user(new_margin, (int __user *)arg); + } +} + +static struct file_operations mxc_wdt_fops = { + .owner = THIS_MODULE, + .write = mxc_wdt_write, + .ioctl = mxc_wdt_ioctl, + .open = mxc_wdt_open, + .release = mxc_wdt_release, +}; + +static struct miscdevice mxc_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &mxc_wdt_fops +}; + +static int __init mxc_wdt_probe(struct platform_device *pdev) +{ + struct resource *res, *mem; + int ret; + + /* reserve static register mappings */ + res = platform_get_resource(pdev, IORESOURCE_MEM, dev_num); + if (!res) + return -ENOENT; + + mem = request_mem_region(res->start, res->end - res->start + 1, + pdev->name); + if (mem == NULL) + return -EBUSY; + + platform_set_drvdata(pdev, mem); + + wdt_base_reg = IO_ADDRESS(res->start); + mxc_wdt_disable(wdt_base_reg); + mxc_wdt_adjust_timeout(timer_margin); + + mxc_wdt_users = 0; + + mxc_wdt_miscdev.this_device = &pdev->dev; + + mxc_wdt_clk = clk_get(NULL, "wdog_clk"); + clk_enable(mxc_wdt_clk); + + ret = misc_register(&mxc_wdt_miscdev); + if (ret) + goto fail; + + pr_info("MXC Watchdog # %d Timer: initial timeout %d sec\n", dev_num, + timer_margin); + + return 0; + + fail: + release_resource(mem); + pr_info("MXC Watchdog Probe failed\n"); + return ret; +} + +static void mxc_wdt_shutdown(struct platform_device *pdev) +{ + mxc_wdt_disable(wdt_base_reg); + pr_info("MXC Watchdog # %d shutdown\n", dev_num); +} + +static int __exit mxc_wdt_remove(struct platform_device *pdev) +{ + struct resource *mem = platform_get_drvdata(pdev); + misc_deregister(&mxc_wdt_miscdev); + release_resource(mem); + pr_info("MXC Watchdog # %d removed\n", dev_num); + return 0; +} + +#ifdef CONFIG_PM + +/* REVISIT ... not clear this is the best way to handle system suspend; and + * it's very inappropriate for selective device suspend (e.g. suspending this + * through sysfs rather than by stopping the watchdog daemon). Also, this + * may not play well enough with NOWAYOUT... + */ + +static int mxc_wdt_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (mxc_wdt_users) { + mxc_wdt_disable(wdt_base_reg); + } + return 0; +} + +static int mxc_wdt_resume(struct platform_device *pdev) +{ + if (mxc_wdt_users) { + mxc_wdt_enable(wdt_base_reg); + mxc_wdt_ping(wdt_base_reg); + } + return 0; +} + +#else +#define mxc_wdt_suspend NULL +#define mxc_wdt_resume NULL +#endif + +static struct platform_driver mxc_wdt_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "mxc_wdt", + }, + .probe = mxc_wdt_probe, + .shutdown = mxc_wdt_shutdown, + .remove = __exit_p(mxc_wdt_remove), + .suspend = mxc_wdt_suspend, + .resume = mxc_wdt_resume, +}; + +static int __init mxc_wdt_init(void) +{ + pr_info("MXC WatchDog Driver %s\n", DVR_VER); + + if ((timer_margin < TIMER_MARGIN_MIN) || + (timer_margin > TIMER_MARGIN_MAX)) { + pr_info("MXC watchdog error. wrong timer_margin %d\n", + timer_margin); + pr_info(" Range: %d to %d seconds\n", TIMER_MARGIN_MIN, + TIMER_MARGIN_MAX); + return -EINVAL; + } + + return platform_driver_register(&mxc_wdt_driver); +} + +static void __exit mxc_wdt_exit(void) +{ + platform_driver_unregister(&mxc_wdt_driver); + pr_info("MXC WatchDog Driver removed\n"); +} + +module_init(mxc_wdt_init); +module_exit(mxc_wdt_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/mxc_wdt.h b/drivers/watchdog/mxc_wdt.h new file mode 100644 index 000000000000..cd09b9acf99f --- /dev/null +++ b/drivers/watchdog/mxc_wdt.h @@ -0,0 +1,37 @@ +/* + * linux/drivers/char/watchdog/mxc_wdt.h + * + * BRIEF MODULE DESCRIPTION + * MXC Watchdog timer register definitions + * + * Author: MontaVista Software, Inc. + * <AKuster@mvista.com> or <source@mvista.com> + * + * 2005 (c) MontaVista Software, Inc. + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef __MXC_WDT_H__ +#define __MXC_WDT_H__ + +#define MXC_WDT_WCR 0x00 +#define MXC_WDT_WSR 0x02 +#define MXC_WDT_WRSR 0x04 +#define WCR_WOE_BIT (1 << 6) +#define WCR_WDA_BIT (1 << 5) +#define WCR_SRS_BIT (1 << 4) +#define WCR_WRE_BIT (1 << 3) +#define WCR_WDE_BIT (1 << 2) +#define WCR_WDBG_BIT (1 << 1) +#define WCR_WDZST_BIT (1 << 0) +#define WDT_MAGIC_1 0x5555 +#define WDT_MAGIC_2 0xAAAA + +#define TIMER_MARGIN_MAX 127 +#define TIMER_MARGIN_DEFAULT 60 /* 60 secs */ +#define TIMER_MARGIN_MIN 1 + +#endif /* __MXC_WDT_H__ */ diff --git a/include/linux/fsl_devices.h b/include/linux/fsl_devices.h index 43fc95d822d5..b41ef9ae4beb 100644 --- a/include/linux/fsl_devices.h +++ b/include/linux/fsl_devices.h @@ -6,7 +6,7 @@ * * Maintainer: Kumar Gala <galak@kernel.crashing.org> * - * Copyright 2004 Freescale Semiconductor, Inc + * Copyright 2004, 2009 Freescale Semiconductor, Inc * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -58,16 +58,54 @@ enum fsl_usb2_phy_modes { FSL_USB2_PHY_SERIAL, }; +struct platform_device; struct fsl_usb2_platform_data { /* board specific information */ enum fsl_usb2_operating_modes operating_mode; enum fsl_usb2_phy_modes phy_mode; unsigned int port_enables; + + char *name; /* pretty print */ + int (*platform_init) (struct platform_device *); + void (*platform_uninit) (struct fsl_usb2_platform_data *); + void __iomem *regs; /* ioremap'd register base */ + u32 xcvr_type; /* PORTSC_PTS_* */ + char *transceiver; /* transceiver name */ + unsigned power_budget; /* for hcd->power_budget */ + struct platform_device *pdev; + struct fsl_xcvr_ops *xcvr_ops; + struct fsl_xcvr_power *xcvr_pwr; + int (*gpio_usb_active) (void); + void (*gpio_usb_inactive) (void); + void (*usb_clock_for_pm) (bool); + void (*platform_suspend)(struct fsl_usb2_platform_data *); + void (*platform_resume)(struct fsl_usb2_platform_data *); + unsigned big_endian_mmio : 1; + unsigned big_endian_desc : 1; + unsigned es : 1; /* need USBMODE:ES */ + unsigned have_sysif_regs : 1; + unsigned le_setup_buf : 1; + unsigned change_ahb_burst:1; + unsigned ahb_burst_mode:3; + unsigned suspended : 1; + unsigned already_suspended : 1; + + /* register save area for suspend/resume */ + u32 pm_command; + u32 pm_status; + u32 pm_intr_enable; + u32 pm_frame_index; + u32 pm_segment; + u32 pm_frame_list; + u32 pm_async_next; + u32 pm_configured_flag; + u32 pm_portsc; }; /* Flags in fsl_usb2_mph_platform_data */ #define FSL_USB2_PORT0_ENABLED 0x00000001 #define FSL_USB2_PORT1_ENABLED 0x00000002 +#define FSL_USB2_DONT_REMAP 0x10000000 struct spi_device; @@ -81,6 +119,20 @@ struct fsl_spi_platform_data { u32 sysclk; }; +struct fsl_ata_platform_data { + int adma_flag; /* AMDA mode is used or not, 1:used.*/ + int udma_mask; /* UDMA modes h/w can handle */ + int mwdma_mask; /* MDMA modes h/w can handle */ + int pio_mask; /* PIO modes h/w can handle */ + int fifo_alarm; /* value for fifo_alarm reg */ + int max_sg; /* longest sglist h/w can handle */ + int (*init)(struct platform_device *pdev); + void (*exit)(void); + char *io_reg; + char *core_reg; +}; + + struct mpc8xx_pcmcia_ops { void(*hw_ctrl)(int slot, int enable); int(*voltage_set)(int slot, int vcc, int vpp); diff --git a/include/linux/imx_adc.h b/include/linux/imx_adc.h new file mode 100644 index 000000000000..5c0e2462b1ef --- /dev/null +++ b/include/linux/imx_adc.h @@ -0,0 +1,275 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef __ASM_ARCH_IMX_ADC_H__ +#define __ASM_ARCH_IMX_ADC_H__ + +/*! + * @defgroup IMX_ADC Digitizer Driver + * @ingroup IMX_DRVRS + */ + +/*! + * @file arch-mxc/imx_adc.h + * @brief This is the header of IMX ADC driver. + * + * @ingroup IMX_ADC + */ + +#include <linux/ioctl.h> + +/*! + * @enum IMX_ADC_STATUS + * @brief Define return values for all IMX_ADC APIs. + * + * These return values are used by all of the IMX_ADC APIs. + * + * @ingroup IMX_ADC + */ +enum IMX_ADC_STATUS { + /*! The requested operation was successfully completed. */ + IMX_ADC_SUCCESS = 0, + /*! The requested operation could not be completed due to an error. */ + IMX_ADC_ERROR = -1, + /*! + * The requested operation failed because one or more of the + * parameters was invalid. + */ + IMX_ADC_PARAMETER_ERROR = -2, + /*! + * The requested operation could not be completed because the ADC + * hardware does not support it. + */ + IMX_ADC_NOT_SUPPORTED = -3, + /*! Error in malloc function */ + IMX_ADC_MALLOC_ERROR = -5, + /*! Error in un-subscribe event */ + IMX_ADC_UNSUBSCRIBE_ERROR = -6, + /*! Event occur and not subscribed */ + IMX_ADC_EVENT_NOT_SUBSCRIBED = -7, + /*! Error - bad call back */ + IMX_ADC_EVENT_CALL_BACK = -8, + /*! + * The requested operation could not be completed because there + * are too many ADC client requests + */ + IMX_ADC_CLIENT_NBOVERFLOW = -9, +}; + +/* + * Macros implementing error handling + */ +#define CHECK_ERROR(a) \ +do { \ + int ret = (a); \ + if (ret != IMX_ADC_SUCCESS) \ + return ret; \ +} while (0) + +#define CHECK_ERROR_KFREE(func, freeptrs) \ +do { \ + int ret = (func); \ + if (ret != IMX_ADC_SUCCESS) { \ + freeptrs; \ + return ret; \ + } \ +} while (0) + +#define MOD_NAME "mxcadc" + +/*! + * @name IOCTL user space interface + */ + +/*! + * Initialize ADC. + * Argument type: none. + */ +#define IMX_ADC_INIT _IO('p', 0xb0) +/*! + * De-initialize ADC. + * Argument type: none. + */ +#define IMX_ADC_DEINIT _IO('p', 0xb1) +/*! + * Convert one channel. + * Argument type: pointer to t_adc_convert_param. + */ +#define IMX_ADC_CONVERT _IOWR('p', 0xb2, int) +/*! + * Convert multiple channels. + * Argument type: pointer to t_adc_convert_param. + */ +#define IMX_ADC_CONVERT_MULTICHANNEL _IOWR('p', 0xb4, int) + +/*! @{ */ +/*! + * @name Touch Screen minimum and maximum values + */ +#define IMX_ADC_DEVICE "/dev/imx_adc" + +/* + * Maximun allowed variation in the three X/Y co-ordinates acquired from + * touch screen + */ +#define DELTA_Y_MAX 100 +#define DELTA_X_MAX 100 + +/* Upon clearing the filter, this is the delay in restarting the filter */ +#define FILTER_MIN_DELAY 4 + +/* Length of X and Y touch screen filters */ +#define FILTLEN 3 + +#define TS_X_MAX 1000 +#define TS_Y_MAX 1000 + +#define TS_X_MIN 80 +#define TS_Y_MIN 80 + +/*! @} */ +/*! + * This enumeration defines input channels for IMX ADC + */ + +enum t_channel { + TS_X_POS, + TS_Y_POS, + GER_PURPOSE_ADC0, + GER_PURPOSE_ADC1, + GER_PURPOSE_ADC2, + GER_PURPOSE_MULTICHNNEL, +}; + +/*! + * This structure is used to report touch screen value. + */ +struct t_touch_screen { + /* Touch Screen X position */ + unsigned int x_position; + /* Touch Screen X position1 */ + unsigned int x_position1; + /* Touch Screen X position2 */ + unsigned int x_position2; + /* Touch Screen X position3 */ + unsigned int x_position3; + /* Touch Screen Y position */ + unsigned int y_position; + /* Touch Screen Y position1 */ + unsigned int y_position1; + /* Touch Screen Y position2 */ + unsigned int y_position2; + /* Touch Screen Y position3 */ + unsigned int y_position3; + /* Touch Screen contact value */ + unsigned int contact_resistance; + /* Flag indicate the data usability */ + unsigned int valid_flag; +}; + +/*! + * This structure is used with IOCTL code \a IMX_ADC_CONVERT, + * \a IMX_ADC_CONVERT_8X and \a IMX_ADC_CONVERT_MULTICHANNEL. + */ + +struct t_adc_convert_param { + /* channel or channels to be sampled. */ + enum t_channel channel; + /* holds up to 16 sampling results */ + unsigned short result[16]; +}; + +/* EXPORTED FUNCTIONS */ + +#ifdef __KERNEL__ +/* Driver data */ +struct imx_adc_data { + u32 irq; + struct clk *adc_clk; +}; + +/*! + * This function initializes all ADC registers with default values. This + * function also registers the interrupt events. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_init(void); + +/*! + * This function disables the ADC, de-registers the interrupt events. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_deinit(void); + +/*! + * This function triggers a conversion and returns one sampling result of one + * channel. + * + * @param channel The channel to be sampled + * @param result The pointer to the conversion result. The memory + * should be allocated by the caller of this function. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ + +enum IMX_ADC_STATUS imx_adc_convert(enum t_channel channel, + unsigned short *result); + +/*! + * This function triggers a conversion and returns sampling results of each + * specified channel. + * + * @param channels This input parameter is bitmap to specify channels + * to be sampled. + * @param result The pointer to array to store sampling result. + * The order of the result in the array is from lowest + * channel number to highest channel number of the + * sampled channels. + * The memory should be allocated by the caller of this + * function. + * Note that the behavior of this function might differ + * from one platform to another regarding especially + * channels order. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ + +enum IMX_ADC_STATUS imx_adc_convert_multichnnel(enum t_channel channels, + unsigned short *result); + +/*! + * This function retrieves the current touch screen operation mode. + * + * @param touch_sample Pointer to touch sample. + * @param wait_tsi if true, we wait until interrupt occurs + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_get_touch_sample(struct t_touch_screen *ts_value, + int wait_tsi); + +/*! + * This function read the touch screen value. + * + * @param touch_sample return value of touch screen + * @param wait_tsi if true, we need wait until interrupt occurs + * @return This function returns 0. + */ +enum IMX_ADC_STATUS imx_adc_read_ts(struct t_touch_screen *touch_sample, + int wait_tsi); + +int is_imx_adc_ready(void); + +#endif /* _KERNEL */ +#endif /* __ASM_ARCH_IMX_ADC_H__ */ diff --git a/include/linux/ipu.h b/include/linux/ipu.h new file mode 100644 index 000000000000..29a24c94e914 --- /dev/null +++ b/include/linux/ipu.h @@ -0,0 +1,1201 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +/*! + * @defgroup IPU MXC Image Processing Unit (IPU) Driver + */ +/*! + * @file arch-mxc/ipu.h + * + * @brief This file contains the IPU driver API declarations. + * + * @ingroup IPU + */ + +#ifndef __ASM_ARCH_IPU_H__ +#define __ASM_ARCH_IPU_H__ + +#include <linux/types.h> +#include <linux/videodev2.h> +#ifdef __KERNEL__ +#include <linux/interrupt.h> +#else +#ifndef __cplusplus +typedef unsigned char bool; +#endif +#define irqreturn_t int +#define dma_addr_t int +#define u32 unsigned int +#define __u32 u32 +#endif + +/*! + * Enumeration of IPU rotation modes + */ +typedef enum { + /* Note the enum values correspond to BAM value */ + IPU_ROTATE_NONE = 0, + IPU_ROTATE_VERT_FLIP = 1, + IPU_ROTATE_HORIZ_FLIP = 2, + IPU_ROTATE_180 = 3, + IPU_ROTATE_90_RIGHT = 4, + IPU_ROTATE_90_RIGHT_VFLIP = 5, + IPU_ROTATE_90_RIGHT_HFLIP = 6, + IPU_ROTATE_90_LEFT = 7, +} ipu_rotate_mode_t; + +/*! + * Enumeration of Post Filter modes + */ +typedef enum { + PF_DISABLE_ALL = 0, + PF_MPEG4_DEBLOCK = 1, + PF_MPEG4_DERING = 2, + PF_MPEG4_DEBLOCK_DERING = 3, + PF_H264_DEBLOCK = 4, +} pf_operation_t; + +/*! + * Enumeration of Synchronous (Memory-less) panel types + */ +typedef enum { + IPU_PANEL_SHARP_TFT, + IPU_PANEL_TFT, +} ipu_panel_t; + +/*! + * Enumeration of VDI MOTION select + */ +typedef enum { + MED_MOTION = 0, + LOW_MOTION = 1, + HIGH_MOTION = 2, +} ipu_motion_sel; + +/* IPU Pixel format definitions */ +/* Four-character-code (FOURCC) */ +#define fourcc(a, b, c, d)\ + (((__u32)(a)<<0)|((__u32)(b)<<8)|((__u32)(c)<<16)|((__u32)(d)<<24)) + +/*! + * @name IPU Pixel Formats + * + * Pixel formats are defined with ASCII FOURCC code. The pixel format codes are + * the same used by V4L2 API. + */ + +/*! @{ */ +/*! @name Generic or Raw Data Formats */ +/*! @{ */ +#define IPU_PIX_FMT_GENERIC fourcc('I', 'P', 'U', '0') /*!< IPU Generic Data */ +#define IPU_PIX_FMT_GENERIC_32 fourcc('I', 'P', 'U', '1') /*!< IPU Generic Data */ +#define IPU_PIX_FMT_LVDS666 fourcc('L', 'V', 'D', '6') /*!< IPU Generic Data */ +#define IPU_PIX_FMT_LVDS888 fourcc('L', 'V', 'D', '8') /*!< IPU Generic Data */ +/*! @} */ +/*! @name RGB Formats */ +/*! @{ */ +#define IPU_PIX_FMT_RGB332 fourcc('R', 'G', 'B', '1') /*!< 8 RGB-3-3-2 */ +#define IPU_PIX_FMT_RGB555 fourcc('R', 'G', 'B', 'O') /*!< 16 RGB-5-5-5 */ +#define IPU_PIX_FMT_RGB565 fourcc('R', 'G', 'B', 'P') /*!< 1 6 RGB-5-6-5 */ +#define IPU_PIX_FMT_RGB666 fourcc('R', 'G', 'B', '6') /*!< 18 RGB-6-6-6 */ +#define IPU_PIX_FMT_BGR666 fourcc('B', 'G', 'R', '6') /*!< 18 BGR-6-6-6 */ +#define IPU_PIX_FMT_BGR24 fourcc('B', 'G', 'R', '3') /*!< 24 BGR-8-8-8 */ +#define IPU_PIX_FMT_RGB24 fourcc('R', 'G', 'B', '3') /*!< 24 RGB-8-8-8 */ +#define IPU_PIX_FMT_BGR32 fourcc('B', 'G', 'R', '4') /*!< 32 BGR-8-8-8-8 */ +#define IPU_PIX_FMT_BGRA32 fourcc('B', 'G', 'R', 'A') /*!< 32 BGR-8-8-8-8 */ +#define IPU_PIX_FMT_RGB32 fourcc('R', 'G', 'B', '4') /*!< 32 RGB-8-8-8-8 */ +#define IPU_PIX_FMT_RGBA32 fourcc('R', 'G', 'B', 'A') /*!< 32 RGB-8-8-8-8 */ +#define IPU_PIX_FMT_ABGR32 fourcc('A', 'B', 'G', 'R') /*!< 32 ABGR-8-8-8-8 */ +/*! @} */ +/*! @name YUV Interleaved Formats */ +/*! @{ */ +#define IPU_PIX_FMT_YUYV fourcc('Y', 'U', 'Y', 'V') /*!< 16 YUV 4:2:2 */ +#define IPU_PIX_FMT_UYVY fourcc('U', 'Y', 'V', 'Y') /*!< 16 YUV 4:2:2 */ +#define IPU_PIX_FMT_Y41P fourcc('Y', '4', '1', 'P') /*!< 12 YUV 4:1:1 */ +#define IPU_PIX_FMT_YUV444 fourcc('Y', '4', '4', '4') /*!< 24 YUV 4:4:4 */ +/* two planes -- one Y, one Cb + Cr interleaved */ +#define IPU_PIX_FMT_NV12 fourcc('N', 'V', '1', '2') /* 12 Y/CbCr 4:2:0 */ +/*! @} */ +/*! @name YUV Planar Formats */ +/*! @{ */ +#define IPU_PIX_FMT_GREY fourcc('G', 'R', 'E', 'Y') /*!< 8 Greyscale */ +#define IPU_PIX_FMT_YVU410P fourcc('Y', 'V', 'U', '9') /*!< 9 YVU 4:1:0 */ +#define IPU_PIX_FMT_YUV410P fourcc('Y', 'U', 'V', '9') /*!< 9 YUV 4:1:0 */ +#define IPU_PIX_FMT_YVU420P fourcc('Y', 'V', '1', '2') /*!< 12 YVU 4:2:0 */ +#define IPU_PIX_FMT_YUV420P fourcc('I', '4', '2', '0') /*!< 12 YUV 4:2:0 */ +#define IPU_PIX_FMT_YUV420P2 fourcc('Y', 'U', '1', '2') /*!< 12 YUV 4:2:0 */ +#define IPU_PIX_FMT_YVU422P fourcc('Y', 'V', '1', '6') /*!< 16 YVU 4:2:2 */ +#define IPU_PIX_FMT_YUV422P fourcc('4', '2', '2', 'P') /*!< 16 YUV 4:2:2 */ +/*! @} */ + +/* IPU Driver channels definitions. */ +/* Note these are different from IDMA channels */ +#ifdef CONFIG_MXC_IPU_V1 +#define _MAKE_CHAN(num, in, out, sec) ((num << 24) | (sec << 16) | (out << 8) | in) +#define IPU_CHAN_ID(ch) (ch >> 24) +#define IPU_CHAN_SEC_DMA(ch) ((uint32_t) (ch >> 16) & 0xFF) +#define IPU_CHAN_OUT_DMA(ch) ((uint32_t) (ch >> 8) & 0xFF) +#define IPU_CHAN_IN_DMA(ch) ((uint32_t) (ch & 0xFF)) + +#else +#define IPU_MAX_CH 32 +#define _MAKE_CHAN(num, v_in, g_in, a_in, out) \ + ((num << 24) | (v_in << 18) | (g_in << 12) | (a_in << 6) | out) +#define _MAKE_ALT_CHAN(ch) (ch | (IPU_MAX_CH << 24)) +#define IPU_CHAN_ID(ch) (ch >> 24) +#define IPU_CHAN_ALT(ch) (ch & 0x02000000) +#define IPU_CHAN_ALPHA_IN_DMA(ch) ((uint32_t) (ch >> 6) & 0x3F) +#define IPU_CHAN_GRAPH_IN_DMA(ch) ((uint32_t) (ch >> 12) & 0x3F) +#define IPU_CHAN_VIDEO_IN_DMA(ch) ((uint32_t) (ch >> 18) & 0x3F) +#define IPU_CHAN_OUT_DMA(ch) ((uint32_t) (ch & 0x3F)) +#define NO_DMA 0x3F +#define ALT 1 +#endif +/*! + * Enumeration of IPU logical channels. An IPU logical channel is defined as a + * combination of an input (memory to IPU), output (IPU to memory), and/or + * secondary input IDMA channels and in some cases an Image Converter task. + * Some channels consist of only an input or output. + */ +typedef enum { + CHAN_NONE = -1, +#ifdef CONFIG_MXC_IPU_V1 + CSI_MEM = _MAKE_CHAN(1, 0xFF, 7, 0xFF), /*!< CSI raw sensor data to memory */ + + CSI_PRP_ENC_MEM = _MAKE_CHAN(2, 0xFF, 0, 0xFF), /*!< CSI to IC Encoder PreProcessing to Memory */ + MEM_PRP_ENC_MEM = _MAKE_CHAN(3, 6, 0, 0xFF), /*!< Memory to IC Encoder PreProcessing to Memory */ + MEM_ROT_ENC_MEM = _MAKE_CHAN(4, 10, 8, 0xFF), /*!< Memory to IC Encoder Rotation to Memory */ + + CSI_PRP_VF_MEM = _MAKE_CHAN(5, 0xFF, 1, 0xFF), /*!< CSI to IC Viewfinder PreProcessing to Memory */ + CSI_PRP_VF_ADC = _MAKE_CHAN(6, 0xFF, 1, 0xFF), /*!< CSI to IC Viewfinder PreProcessing to ADC */ + MEM_PRP_VF_MEM = _MAKE_CHAN(7, 6, 1, 3), /*!< Memory to IC Viewfinder PreProcessing to Memory */ + MEM_PRP_VF_ADC = _MAKE_CHAN(8, 6, 1, 3), /*!< Memory to IC Viewfinder PreProcessing to ADC */ + MEM_ROT_VF_MEM = _MAKE_CHAN(9, 11, 9, 0xFF), /*!< Memory to IC Viewfinder Rotation to Memory */ + + MEM_PP_MEM = _MAKE_CHAN(10, 5, 2, 4), /*!< Memory to IC PostProcessing to Memory */ + MEM_ROT_PP_MEM = _MAKE_CHAN(11, 13, 12, 0xFF), /*!< Memory to IC PostProcessing Rotation to Memory */ + MEM_PP_ADC = _MAKE_CHAN(12, 5, 2, 4), /*!< Memory to IC PostProcessing to ADC */ + + MEM_SDC_BG = _MAKE_CHAN(14, 14, 0xFF, 0xFF), /*!< Memory to SDC Background plane */ + MEM_SDC_FG = _MAKE_CHAN(15, 15, 0xFF, 0xFF), /*!< Memory to SDC Foreground plane */ + MEM_SDC_MASK = _MAKE_CHAN(16, 16, 0xFF, 0xFF), /*!< Memory to SDC Mask */ + + MEM_BG_SYNC = MEM_SDC_BG, + MEM_FG_SYNC = MEM_SDC_FG, + + ADC_SYS1 = _MAKE_CHAN(17, 18, 22, 20), /*!< Memory to ADC System Channel 1 */ + ADC_SYS2 = _MAKE_CHAN(18, 19, 23, 21), /*!< Memory to ADC System Channel 2 */ + + MEM_PF_Y_MEM = _MAKE_CHAN(19, 26, 29, 24), /*!< Y and PF Memory to Post-filter to Y Memory */ + MEM_PF_U_MEM = _MAKE_CHAN(20, 27, 30, 25), /*!< U and PF Memory to Post-filter to U Memory */ + MEM_PF_V_MEM = _MAKE_CHAN(21, 28, 31, 0xFF), /*!< V Memory to Post-filter to V Memory */ + + MEM_DC_SYNC = CHAN_NONE, + DIRECT_ASYNC0 = CHAN_NONE, + DIRECT_ASYNC1 = CHAN_NONE, + MEM_VDI_PRP_VF_MEM_P = CHAN_NONE, + MEM_VDI_PRP_VF_MEM = CHAN_NONE, + MEM_VDI_PRP_VF_MEM_N = CHAN_NONE, +#else + MEM_ROT_ENC_MEM = _MAKE_CHAN(1, 45, NO_DMA, NO_DMA, 48), + MEM_ROT_VF_MEM = _MAKE_CHAN(2, 46, NO_DMA, NO_DMA, 49), + MEM_ROT_PP_MEM = _MAKE_CHAN(3, 47, NO_DMA, NO_DMA, 50), + + MEM_PRP_ENC_MEM = _MAKE_CHAN(4, 12, 14, 17, 20), + MEM_PRP_VF_MEM = _MAKE_CHAN(5, 12, 14, 17, 21), + MEM_PP_MEM = _MAKE_CHAN(6, 11, 15, 18, 22), + + MEM_DC_SYNC = _MAKE_CHAN(7, 28, NO_DMA, NO_DMA, NO_DMA), + MEM_DC_ASYNC = _MAKE_CHAN(8, 41, NO_DMA, NO_DMA, NO_DMA), + MEM_BG_SYNC = _MAKE_CHAN(9, 23, NO_DMA, 51, NO_DMA), + MEM_FG_SYNC = _MAKE_CHAN(10, 27, NO_DMA, 31, NO_DMA), + + MEM_BG_ASYNC0 = _MAKE_CHAN(11, 24, NO_DMA, 52, NO_DMA), + MEM_FG_ASYNC0 = _MAKE_CHAN(12, 29, NO_DMA, 33, NO_DMA), + MEM_BG_ASYNC1 = _MAKE_ALT_CHAN(MEM_BG_ASYNC0), + MEM_FG_ASYNC1 = _MAKE_ALT_CHAN(MEM_FG_ASYNC0), + + DIRECT_ASYNC0 = _MAKE_CHAN(13, NO_DMA, NO_DMA, NO_DMA, NO_DMA), + DIRECT_ASYNC1 = _MAKE_CHAN(14, NO_DMA, NO_DMA, NO_DMA, NO_DMA), + + CSI_MEM0 = _MAKE_CHAN(15, NO_DMA, NO_DMA, NO_DMA, 0), + CSI_MEM1 = _MAKE_CHAN(16, NO_DMA, NO_DMA, NO_DMA, 1), + CSI_MEM2 = _MAKE_CHAN(17, NO_DMA, NO_DMA, NO_DMA, 2), + CSI_MEM3 = _MAKE_CHAN(18, NO_DMA, NO_DMA, NO_DMA, 3), + + CSI_MEM = CSI_MEM0, + + CSI_PRP_ENC_MEM = _MAKE_CHAN(19, NO_DMA, NO_DMA, NO_DMA, 20), + CSI_PRP_VF_MEM = _MAKE_CHAN(20, NO_DMA, NO_DMA, NO_DMA, 21), + + MEM_VDI_PRP_VF_MEM_P = _MAKE_CHAN(21, 8, 14, 17, 21), + MEM_VDI_PRP_VF_MEM = _MAKE_CHAN(22, 9, 14, 17, 21), + MEM_VDI_PRP_VF_MEM_N = _MAKE_CHAN(23, 10, 14, 17, 21), + + MEM_PP_ADC = CHAN_NONE, + ADC_SYS2 = CHAN_NONE, +#endif + +} ipu_channel_t; + +/*! + * Enumeration of types of buffers for a logical channel. + */ +typedef enum { + IPU_OUTPUT_BUFFER = 0, /*!< Buffer for output from IPU */ + IPU_ALPHA_IN_BUFFER = 1, /*!< Buffer for input to IPU */ + IPU_GRAPH_IN_BUFFER = 2, /*!< Buffer for input to IPU */ + IPU_VIDEO_IN_BUFFER = 3, /*!< Buffer for input to IPU */ + IPU_INPUT_BUFFER = IPU_VIDEO_IN_BUFFER, + IPU_SEC_INPUT_BUFFER = IPU_GRAPH_IN_BUFFER, +} ipu_buffer_t; + +#define IPU_PANEL_SERIAL 1 +#define IPU_PANEL_PARALLEL 2 + +/*! + * Enumeration of DI ports for ADC. + */ +typedef enum { + DISP0, + DISP1, + DISP2, + DISP3 +} display_port_t; + +/*! + * Enumeration of ADC channel operation mode. + */ +typedef enum { + Disable, + WriteTemplateNonSeq, + ReadTemplateNonSeq, + WriteTemplateUnCon, + ReadTemplateUnCon, + WriteDataWithRS, + WriteDataWoRS, + WriteCmd +} mcu_mode_t; + +/*! + * Enumeration of ADC channel addressing mode. + */ +typedef enum { + FullWoBE, + FullWithBE, + XY +} display_addressing_t; + +/*! + * Union of initialization parameters for a logical channel. + */ +typedef union { + struct { + uint32_t csi; + bool mipi_en; + uint32_t mipi_id; + } csi_mem; + struct { + uint32_t in_width; + uint32_t in_height; + uint32_t in_pixel_fmt; + uint32_t out_width; + uint32_t out_height; + uint32_t out_pixel_fmt; + uint32_t csi; + } csi_prp_enc_mem; + struct { + uint32_t in_width; + uint32_t in_height; + uint32_t in_pixel_fmt; + uint32_t out_width; + uint32_t out_height; + uint32_t out_pixel_fmt; + } mem_prp_enc_mem; + struct { + uint32_t in_width; + uint32_t in_height; + uint32_t in_pixel_fmt; + uint32_t out_width; + uint32_t out_height; + uint32_t out_pixel_fmt; + } mem_rot_enc_mem; + struct { + uint32_t in_width; + uint32_t in_height; + uint32_t in_pixel_fmt; + uint32_t out_width; + uint32_t out_height; + uint32_t out_pixel_fmt; + bool graphics_combine_en; + bool global_alpha_en; + bool key_color_en; + uint32_t csi; + } csi_prp_vf_mem; + struct { + uint32_t in_width; + uint32_t in_height; + uint32_t in_pixel_fmt; + uint32_t out_width; + uint32_t out_height; + uint32_t out_pixel_fmt; + bool graphics_combine_en; + bool global_alpha_en; + bool key_color_en; + display_port_t disp; + uint32_t out_left; + uint32_t out_top; + } csi_prp_vf_adc; + struct { + uint32_t in_width; + uint32_t in_height; + uint32_t in_pixel_fmt; + uint32_t out_width; + uint32_t out_height; + uint32_t out_pixel_fmt; + bool graphics_combine_en; + bool global_alpha_en; + bool key_color_en; + uint32_t in_g_pixel_fmt; + uint8_t alpha; + uint32_t key_color; + bool alpha_chan_en; + ipu_motion_sel motion_sel; + enum v4l2_field field_fmt; + } mem_prp_vf_mem; + struct { + uint32_t temp; + } mem_prp_vf_adc; + struct { + uint32_t temp; + } mem_rot_vf_mem; + struct { + uint32_t in_width; + uint32_t in_height; + uint32_t in_pixel_fmt; + uint32_t out_width; + uint32_t out_height; + uint32_t out_pixel_fmt; + bool graphics_combine_en; + bool global_alpha_en; + bool key_color_en; + uint32_t in_g_pixel_fmt; + uint8_t alpha; + uint32_t key_color; + bool alpha_chan_en; + } mem_pp_mem; + struct { + uint32_t temp; + } mem_rot_mem; + struct { + uint32_t in_width; + uint32_t in_height; + uint32_t in_pixel_fmt; + uint32_t out_width; + uint32_t out_height; + uint32_t out_pixel_fmt; + bool graphics_combine_en; + bool global_alpha_en; + bool key_color_en; + display_port_t disp; + uint32_t out_left; + uint32_t out_top; + } mem_pp_adc; + struct { + pf_operation_t operation; + } mem_pf_mem; + struct { + uint32_t di; + bool interlaced; + } mem_dc_sync; + struct { + uint32_t temp; + } mem_sdc_fg; + struct { + uint32_t di; + bool interlaced; + uint32_t in_pixel_fmt; + uint32_t out_pixel_fmt; + bool alpha_chan_en; + } mem_dp_bg_sync; + struct { + uint32_t temp; + } mem_sdc_bg; + struct { + uint32_t di; + bool interlaced; + uint32_t in_pixel_fmt; + uint32_t out_pixel_fmt; + bool alpha_chan_en; + } mem_dp_fg_sync; + struct { + uint32_t di; + } direct_async; + struct { + display_port_t disp; + mcu_mode_t ch_mode; + uint32_t out_left; + uint32_t out_top; + } adc_sys1; + struct { + display_port_t disp; + mcu_mode_t ch_mode; + uint32_t out_left; + uint32_t out_top; + } adc_sys2; +} ipu_channel_params_t; + +/*! + * Enumeration of IPU interrupt sources. + */ +enum ipu_irq_line { +#ifdef CONFIG_MXC_IPU_V1 + IPU_IRQ_DC_FC_1 = -1, + + IPU_IRQ_PRP_ENC_OUT_EOF = 0, + IPU_IRQ_PRP_VF_OUT_EOF = 1, + IPU_IRQ_PP_OUT_EOF = 2, + IPU_IRQ_PRP_GRAPH_IN_EOF = 3, + IPU_IRQ_PP_GRAPH_IN_EOF = 4, + IPU_IRQ_PP_IN_EOF = 5, + IPU_IRQ_PRP_IN_EOF = 6, + IPU_IRQ_SENSOR_OUT_EOF = 7, + IPU_IRQ_CSI0_OUT_EOF = IPU_IRQ_SENSOR_OUT_EOF, + IPU_IRQ_PRP_ENC_ROT_OUT_EOF = 8, + IPU_IRQ_PRP_VF_ROT_OUT_EOF = 9, + IPU_IRQ_PRP_ENC_ROT_IN_EOF = 10, + IPU_IRQ_PRP_VF_ROT_IN_EOF = 11, + IPU_IRQ_PP_ROT_OUT_EOF = 12, + IPU_IRQ_PP_ROT_IN_EOF = 13, + IPU_IRQ_BG_SYNC_EOF = 14, + IPU_IRQ_SDC_BG_EOF = IPU_IRQ_BG_SYNC_EOF, + IPU_IRQ_FG_SYNC_EOF = 15, + IPU_IRQ_SDC_FG_EOF = IPU_IRQ_FG_SYNC_EOF, + IPU_IRQ_SDC_MASK_EOF = 16, + IPU_IRQ_SDC_BG_PART_EOF = 17, + IPU_IRQ_ADC_SYS1_WR_EOF = 18, + IPU_IRQ_ADC_SYS2_WR_EOF = 19, + IPU_IRQ_ADC_SYS1_CMD_EOF = 20, + IPU_IRQ_ADC_SYS2_CMD_EOF = 21, + IPU_IRQ_ADC_SYS1_RD_EOF = 22, + IPU_IRQ_ADC_SYS2_RD_EOF = 23, + IPU_IRQ_PF_QP_IN_EOF = 24, + IPU_IRQ_PF_BSP_IN_EOF = 25, + IPU_IRQ_PF_Y_IN_EOF = 26, + IPU_IRQ_PF_U_IN_EOF = 27, + IPU_IRQ_PF_V_IN_EOF = 28, + IPU_IRQ_PF_Y_OUT_EOF = 29, + IPU_IRQ_PF_U_OUT_EOF = 30, + IPU_IRQ_PF_V_OUT_EOF = 31, + + IPU_IRQ_PRP_ENC_OUT_NF = 32, + IPU_IRQ_PRP_VF_OUT_NF = 33, + IPU_IRQ_PP_OUT_NF = 34, + IPU_IRQ_PRP_GRAPH_IN_NF = 35, + IPU_IRQ_PP_GRAPH_IN_NF = 36, + IPU_IRQ_PP_IN_NF = 37, + IPU_IRQ_PRP_IN_NF = 38, + IPU_IRQ_SENSOR_OUT_NF = 39, + IPU_IRQ_PRP_ENC_ROT_OUT_NF = 40, + IPU_IRQ_PRP_VF_ROT_OUT_NF = 41, + IPU_IRQ_PRP_ENC_ROT_IN_NF = 42, + IPU_IRQ_PRP_VF_ROT_IN_NF = 43, + IPU_IRQ_PP_ROT_OUT_NF = 44, + IPU_IRQ_PP_ROT_IN_NF = 45, + IPU_IRQ_SDC_FG_NF = 46, + IPU_IRQ_SDC_BG_NF = 47, + IPU_IRQ_SDC_MASK_NF = 48, + IPU_IRQ_SDC_BG_PART_NF = 49, + IPU_IRQ_ADC_SYS1_WR_NF = 50, + IPU_IRQ_ADC_SYS2_WR_NF = 51, + IPU_IRQ_ADC_SYS1_CMD_NF = 52, + IPU_IRQ_ADC_SYS2_CMD_NF = 53, + IPU_IRQ_ADC_SYS1_RD_NF = 54, + IPU_IRQ_ADC_SYS2_RD_NF = 55, + IPU_IRQ_PF_QP_IN_NF = 56, + IPU_IRQ_PF_BSP_IN_NF = 57, + IPU_IRQ_PF_Y_IN_NF = 58, + IPU_IRQ_PF_U_IN_NF = 59, + IPU_IRQ_PF_V_IN_NF = 60, + IPU_IRQ_PF_Y_OUT_NF = 61, + IPU_IRQ_PF_U_OUT_NF = 62, + IPU_IRQ_PF_V_OUT_NF = 63, + + IPU_IRQ_BREAKRQ = 64, + IPU_IRQ_SDC_BG_OUT_EOF = 65, + IPU_IRQ_BG_SF_END = IPU_IRQ_SDC_BG_OUT_EOF, + IPU_IRQ_SDC_FG_OUT_EOF = 66, + IPU_IRQ_SDC_MASK_OUT_EOF = 67, + IPU_IRQ_ADC_SERIAL_DATA_OUT = 68, + IPU_IRQ_SENSOR_NF = 69, + IPU_IRQ_SENSOR_EOF = 70, + IPU_IRQ_SDC_DISP3_VSYNC = 80, + IPU_IRQ_ADC_DISP0_VSYNC = 81, + IPU_IRQ_ADC_DISP12_VSYNC = 82, + IPU_IRQ_ADC_PRP_EOF = 83, + IPU_IRQ_ADC_PP_EOF = 84, + IPU_IRQ_ADC_SYS1_EOF = 85, + IPU_IRQ_ADC_SYS2_EOF = 86, + + IPU_IRQ_PRP_ENC_OUT_NFB4EOF_ERR = 96, + IPU_IRQ_PRP_VF_OUT_NFB4EOF_ERR = 97, + IPU_IRQ_PP_OUT_NFB4EOF_ERR = 98, + IPU_IRQ_PRP_GRAPH_IN_NFB4EOF_ERR = 99, + IPU_IRQ_PP_GRAPH_IN_NFB4EOF_ERR = 100, + IPU_IRQ_PP_IN_NFB4EOF_ERR = 101, + IPU_IRQ_PRP_IN_NFB4EOF_ERR = 102, + IPU_IRQ_SENSOR_OUT_NFB4EOF_ERR = 103, + IPU_IRQ_PRP_ENC_ROT_OUT_NFB4EOF_ERR = 104, + IPU_IRQ_PRP_VF_ROT_OUT_NFB4EOF_ERR = 105, + IPU_IRQ_PRP_ENC_ROT_IN_NFB4EOF_ERR = 106, + IPU_IRQ_PRP_VF_ROT_IN_NFB4EOF_ERR = 107, + IPU_IRQ_PP_ROT_OUT_NFB4EOF_ERR = 108, + IPU_IRQ_PP_ROT_IN_NFB4EOF_ERR = 109, + IPU_IRQ_SDC_FG_NFB4EOF_ERR = 110, + IPU_IRQ_SDC_BG_NFB4EOF_ERR = 111, + IPU_IRQ_SDC_MASK_NFB4EOF_ERR = 112, + IPU_IRQ_SDC_BG_PART_NFB4EOF_ERR = 113, + IPU_IRQ_ADC_SYS1_WR_NFB4EOF_ERR = 114, + IPU_IRQ_ADC_SYS2_WR_NFB4EOF_ERR = 115, + IPU_IRQ_ADC_SYS1_CMD_NFB4EOF_ERR = 116, + IPU_IRQ_ADC_SYS2_CMD_NFB4EOF_ERR = 117, + IPU_IRQ_ADC_SYS1_RD_NFB4EOF_ERR = 118, + IPU_IRQ_ADC_SYS2_RD_NFB4EOF_ERR = 119, + IPU_IRQ_PF_QP_IN_NFB4EOF_ERR = 120, + IPU_IRQ_PF_BSP_IN_NFB4EOF_ERR = 121, + IPU_IRQ_PF_Y_IN_NFB4EOF_ERR = 122, + IPU_IRQ_PF_U_IN_NFB4EOF_ERR = 123, + IPU_IRQ_PF_V_IN_NFB4EOF_ERR = 124, + IPU_IRQ_PF_Y_OUT_NFB4EOF_ERR = 125, + IPU_IRQ_PF_U_OUT_NFB4EOF_ERR = 126, + IPU_IRQ_PF_V_OUT_NFB4EOF_ERR = 127, + + IPU_IRQ_BAYER_BUFOVF_ERR = 128, + IPU_IRQ_ENC_BUFOVF_ERR = 129, + IPU_IRQ_VF_BUFOVF_ERR = 130, + IPU_IRQ_ADC_PP_TEAR_ERR = 131, + IPU_IRQ_ADC_SYS1_TEAR_ERR = 132, + IPU_IRQ_ADC_SYS2_TEAR_ERR = 133, + IPU_IRQ_SDC_BGD_ERR = 134, + IPU_IRQ_SDC_FGD_ERR = 135, + IPU_IRQ_SDC_MASKD_ERR = 136, + IPU_IRQ_BAYER_FRM_LOST_ERR = 137, + IPU_IRQ_ENC_FRM_LOST_ERR = 138, + IPU_IRQ_VF_FRM_LOST_ERR = 139, + IPU_IRQ_ADC_LOCK_ERR = 140, + IPU_IRQ_DI_LLA_LOCK_ERR = 141, + IPU_IRQ_AHB_M1_ERR = 142, + IPU_IRQ_AHB_M12_ERR = 143, +#else + IPU_IRQ_CSI0_OUT_EOF = 0, + IPU_IRQ_CSI1_OUT_EOF = 1, + IPU_IRQ_CSI2_OUT_EOF = 2, + IPU_IRQ_CSI3_OUT_EOF = 3, + IPU_IRQ_PP_IN_EOF = 11, + IPU_IRQ_PRP_IN_EOF = 12, + IPU_IRQ_PRP_GRAPH_IN_EOF = 14, + IPU_IRQ_PP_GRAPH_IN_EOF = 15, + IPU_IRQ_PRP_ALPHA_IN_EOF = 17, + IPU_IRQ_PP_ALPHA_IN_EOF = 18, + IPU_IRQ_PRP_ENC_OUT_EOF = 20, + IPU_IRQ_PRP_VF_OUT_EOF = 21, + IPU_IRQ_PP_OUT_EOF = 22, + IPU_IRQ_BG_SYNC_EOF = 23, + IPU_IRQ_BG_ASYNC_EOF = 24, + IPU_IRQ_FG_SYNC_EOF = 27, + IPU_IRQ_DC_SYNC_EOF = 28, + IPU_IRQ_FG_ASYNC_EOF = 29, + IPU_IRQ_FG_ALPHA_SYNC_EOF = 31, + + IPU_IRQ_FG_ALPHA_ASYNC_EOF = 33, + IPU_IRQ_DC_READ_EOF = 40, + IPU_IRQ_DC_ASYNC_EOF = 41, + IPU_IRQ_DC_CMD1_EOF = 42, + IPU_IRQ_DC_CMD2_EOF = 43, + IPU_IRQ_DC_MASK_EOF = 44, + IPU_IRQ_PRP_ENC_ROT_IN_EOF = 45, + IPU_IRQ_PRP_VF_ROT_IN_EOF = 46, + IPU_IRQ_PP_ROT_IN_EOF = 47, + IPU_IRQ_PRP_ENC_ROT_OUT_EOF = 48, + IPU_IRQ_PRP_VF_ROT_OUT_EOF = 49, + IPU_IRQ_PP_ROT_OUT_EOF = 50, + IPU_IRQ_BG_ALPHA_SYNC_EOF = 51, + IPU_IRQ_BG_ALPHA_ASYNC_EOF = 52, + + IPU_IRQ_DP_SF_START = 448 + 2, + IPU_IRQ_DP_SF_END = 448 + 3, + IPU_IRQ_BG_SF_END = IPU_IRQ_DP_SF_END, + IPU_IRQ_DC_FC_0 = 448 + 8, + IPU_IRQ_DC_FC_1 = 448 + 9, + IPU_IRQ_DC_FC_2 = 448 + 10, + IPU_IRQ_DC_FC_3 = 448 + 11, + IPU_IRQ_DC_FC_4 = 448 + 12, + IPU_IRQ_DC_FC_6 = 448 + 13, + IPU_IRQ_VSYNC_PRE_0 = 448 + 14, + IPU_IRQ_VSYNC_PRE_1 = 448 + 15, +#endif + + IPU_IRQ_COUNT +}; + +/*! + * Bitfield of Display Interface signal polarities. + */ +typedef struct { + unsigned datamask_en:1; + unsigned ext_clk:1; + unsigned interlaced:1; + unsigned odd_field_first:1; + unsigned clksel_en:1; + unsigned clkidle_en:1; + unsigned data_pol:1; /* true = inverted */ + unsigned clk_pol:1; /* true = rising edge */ + unsigned enable_pol:1; + unsigned Hsync_pol:1; /* true = active high */ + unsigned Vsync_pol:1; +} ipu_di_signal_cfg_t; + +/*! + * Bitfield of CSI signal polarities and modes. + */ + +typedef struct { + unsigned data_width:4; + unsigned clk_mode:3; + unsigned ext_vsync:1; + unsigned Vsync_pol:1; + unsigned Hsync_pol:1; + unsigned pixclk_pol:1; + unsigned data_pol:1; + unsigned sens_clksrc:1; + unsigned pack_tight:1; + unsigned force_eof:1; + unsigned data_en_pol:1; + unsigned data_fmt; + unsigned csi; + unsigned mclk; +} ipu_csi_signal_cfg_t; + +/*! + * Enumeration of CSI data bus widths. + */ +enum { + IPU_CSI_DATA_WIDTH_4, + IPU_CSI_DATA_WIDTH_8, + IPU_CSI_DATA_WIDTH_10, + IPU_CSI_DATA_WIDTH_16, +}; + +/*! + * Enumeration of CSI clock modes. + */ +enum { + IPU_CSI_CLK_MODE_GATED_CLK, + IPU_CSI_CLK_MODE_NONGATED_CLK, + IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE, + IPU_CSI_CLK_MODE_CCIR656_INTERLACED, + IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR, + IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR, + IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR, + IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR, +}; + +enum { + IPU_CSI_MIPI_DI0, + IPU_CSI_MIPI_DI1, + IPU_CSI_MIPI_DI2, + IPU_CSI_MIPI_DI3, +}; + +typedef enum { + RGB, + YCbCr, + YUV +} ipu_color_space_t; + +/*! + * Enumeration of ADC vertical sync mode. + */ +typedef enum { + VsyncNone, + VsyncInternal, + VsyncCSI, + VsyncExternal +} vsync_t; + +typedef enum { + DAT, + CMD +} cmddata_t; + +/*! + * Enumeration of ADC display update mode. + */ +typedef enum { + IPU_ADC_REFRESH_NONE, + IPU_ADC_AUTO_REFRESH, + IPU_ADC_AUTO_REFRESH_SNOOP, + IPU_ADC_SNOOPING, +} ipu_adc_update_mode_t; + +/*! + * Enumeration of ADC display interface types (serial or parallel). + */ +enum { + IPU_ADC_IFC_MODE_SYS80_TYPE1, + IPU_ADC_IFC_MODE_SYS80_TYPE2, + IPU_ADC_IFC_MODE_SYS68K_TYPE1, + IPU_ADC_IFC_MODE_SYS68K_TYPE2, + IPU_ADC_IFC_MODE_3WIRE_SERIAL, + IPU_ADC_IFC_MODE_4WIRE_SERIAL, + IPU_ADC_IFC_MODE_5WIRE_SERIAL_CLK, + IPU_ADC_IFC_MODE_5WIRE_SERIAL_CS, +}; + +enum { + IPU_ADC_IFC_WIDTH_8, + IPU_ADC_IFC_WIDTH_16, +}; + +/*! + * Enumeration of ADC display interface burst mode. + */ +enum { + IPU_ADC_BURST_WCS, + IPU_ADC_BURST_WBLCK, + IPU_ADC_BURST_NONE, + IPU_ADC_BURST_SERIAL, +}; + +/*! + * Enumeration of ADC display interface RW signal timing modes. + */ +enum { + IPU_ADC_SER_NO_RW, + IPU_ADC_SER_RW_BEFORE_RS, + IPU_ADC_SER_RW_AFTER_RS, +}; + +/*! + * Bitfield of ADC signal polarities and modes. + */ +typedef struct { + unsigned data_pol:1; + unsigned clk_pol:1; + unsigned cs_pol:1; + unsigned rs_pol:1; + unsigned addr_pol:1; + unsigned read_pol:1; + unsigned write_pol:1; + unsigned Vsync_pol:1; + unsigned burst_pol:1; + unsigned burst_mode:2; + unsigned ifc_mode:3; + unsigned ifc_width:5; + unsigned ser_preamble_len:4; + unsigned ser_preamble:8; + unsigned ser_rw_mode:2; +} ipu_adc_sig_cfg_t; + +/*! + * Enumeration of ADC template commands. + */ +enum { + RD_DATA, + RD_ACK, + RD_WAIT, + WR_XADDR, + WR_YADDR, + WR_ADDR, + WR_CMND, + WR_DATA, +}; + +/*! + * Enumeration of ADC template command flow control. + */ +enum { + SINGLE_STEP, + PAUSE, + STOP, +}; + + +/*Define template constants*/ +#define ATM_ADDR_RANGE 0x20 /*offset address of DISP */ +#define TEMPLATE_BUF_SIZE 0x20 /*size of template */ + +/*! + * Define to create ADC template command entry. + */ +#define ipu_adc_template_gen(oc, rs, fc, dat) ( ((rs) << 29) | ((fc) << 27) | \ + ((oc) << 24) | (dat) ) + +typedef struct { + u32 reg; + u32 value; +} ipu_lpmc_reg_t; + +#define IPU_LPMC_REG_READ 0x80000000L + +#define CSI_MCLK_VF 1 +#define CSI_MCLK_ENC 2 +#define CSI_MCLK_RAW 4 +#define CSI_MCLK_I2C 8 + +/* Common IPU API */ +int32_t ipu_init_channel(ipu_channel_t channel, ipu_channel_params_t * params); +void ipu_uninit_channel(ipu_channel_t channel); + +static inline bool ipu_can_rotate_in_place(ipu_rotate_mode_t rot) +{ +#ifdef CONFIG_MXC_IPU_V3D + return (rot < IPU_ROTATE_HORIZ_FLIP); +#else + return (rot < IPU_ROTATE_90_RIGHT); +#endif +} + +int32_t ipu_init_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t pixel_fmt, + uint16_t width, uint16_t height, + uint32_t stride, + ipu_rotate_mode_t rot_mode, + dma_addr_t phyaddr_0, dma_addr_t phyaddr_1, + uint32_t u_offset, uint32_t v_offset); + +int32_t ipu_update_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum, dma_addr_t phyaddr); + +int32_t ipu_select_buffer(ipu_channel_t channel, + ipu_buffer_t type, uint32_t bufNum); +int32_t ipu_select_multi_vdi_buffer(uint32_t bufNum); + +int32_t ipu_link_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch); +int32_t ipu_unlink_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch); + +int32_t ipu_is_channel_busy(ipu_channel_t channel); +int32_t ipu_enable_channel(ipu_channel_t channel); +int32_t ipu_disable_channel(ipu_channel_t channel, bool wait_for_stop); +int32_t ipu_swap_channel(ipu_channel_t from_ch, ipu_channel_t to_ch); + +int ipu_lowpwr_display_enable(void); +int ipu_lowpwr_display_disable(void); + +void ipu_enable_irq(uint32_t irq); +void ipu_disable_irq(uint32_t irq); +void ipu_clear_irq(uint32_t irq); +int ipu_request_irq(uint32_t irq, + irqreturn_t(*handler) (int, void *), + uint32_t irq_flags, const char *devname, void *dev_id); +void ipu_free_irq(uint32_t irq, void *dev_id); +bool ipu_get_irq_status(uint32_t irq); +void ipu_set_csc_coefficients(ipu_channel_t channel, int32_t param[][3]); + +/* SDC API */ +int32_t ipu_sdc_init_panel(ipu_panel_t panel, + uint32_t pixel_clk, + uint16_t width, uint16_t height, + uint32_t pixel_fmt, + uint16_t hStartWidth, uint16_t hSyncWidth, + uint16_t hEndWidth, uint16_t vStartWidth, + uint16_t vSyncWidth, uint16_t vEndWidth, + ipu_di_signal_cfg_t sig); + +int32_t ipu_sdc_set_global_alpha(bool enable, uint8_t alpha); +int32_t ipu_sdc_set_color_key(ipu_channel_t channel, bool enable, + uint32_t colorKey); +int32_t ipu_sdc_set_brightness(uint8_t value); + +int32_t ipu_init_sync_panel(int disp, + uint32_t pixel_clk, + uint16_t width, uint16_t height, + uint32_t pixel_fmt, + uint16_t h_start_width, uint16_t h_sync_width, + uint16_t h_end_width, uint16_t v_start_width, + uint16_t v_sync_width, uint16_t v_end_width, + uint32_t v_to_h_sync, ipu_di_signal_cfg_t sig); + +int32_t ipu_disp_set_window_pos(ipu_channel_t channel, int16_t x_pos, + int16_t y_pos); +int32_t ipu_disp_set_global_alpha(ipu_channel_t channel, bool enable, + uint8_t alpha); +int32_t ipu_disp_set_color_key(ipu_channel_t channel, bool enable, + uint32_t colorKey); + +int ipu_init_async_panel(int disp, int type, uint32_t cycle_time, + uint32_t pixel_fmt, ipu_adc_sig_cfg_t sig); +void ipu_disp_direct_write(ipu_channel_t channel, u32 value, u32 offset); +void ipu_reset_disp_panel(void); + +/* ADC API */ +int32_t ipu_adc_write_template(display_port_t disp, uint32_t * pCmd, + bool write); + +int32_t ipu_adc_set_update_mode(ipu_channel_t channel, + ipu_adc_update_mode_t mode, + uint32_t refresh_rate, unsigned long addr, + uint32_t * size); + +int32_t ipu_adc_get_snooping_status(uint32_t * statl, uint32_t * stath); + +int32_t ipu_adc_write_cmd(display_port_t disp, cmddata_t type, + uint32_t cmd, const uint32_t * params, + uint16_t numParams); + +int32_t ipu_adc_init_panel(display_port_t disp, + uint16_t width, uint16_t height, + uint32_t pixel_fmt, + uint32_t stride, + ipu_adc_sig_cfg_t sig, + display_addressing_t addr, + uint32_t vsync_width, vsync_t mode); + +int32_t ipu_adc_init_ifc_timing(display_port_t disp, bool read, + uint32_t cycle_time, + uint32_t up_time, + uint32_t down_time, + uint32_t read_latch_time, uint32_t pixel_clk); + +/* CMOS Sensor Interface API */ +int32_t ipu_csi_init_interface(uint16_t width, uint16_t height, + uint32_t pixel_fmt, ipu_csi_signal_cfg_t sig); + +int32_t ipu_csi_enable_mclk(int src, bool flag, bool wait); + +static inline int32_t ipu_csi_enable_mclk_if(int src, uint32_t csi, + bool flag, bool wait) +{ +#ifdef CONFIG_MXC_IPU_V1 + return ipu_csi_enable_mclk(src, flag, wait); +#else + return ipu_csi_enable_mclk(csi, flag, wait); +#endif +} + +int ipu_csi_read_mclk_flag(void); + +void ipu_csi_flash_strobe(bool flag); + +void ipu_csi_get_window_size(uint32_t *width, uint32_t *height, uint32_t csi); + +void ipu_csi_set_window_size(uint32_t width, uint32_t height, uint32_t csi); + +void ipu_csi_set_window_pos(uint32_t left, uint32_t top, uint32_t csi); + +/* Post Filter functions */ +int32_t ipu_pf_set_pause_row(uint32_t pause_row); + +uint32_t bytes_per_pixel(uint32_t fmt); + +/* New added for IPU-lib functionality*/ +int ipu_open(void); +int ipu_register_generic_isr(int irq, void *dev); +void ipu_close(void); + +typedef struct _ipu_channel_parm { + ipu_channel_t channel; + ipu_channel_params_t params; + bool flag; +} ipu_channel_parm; + +typedef struct _ipu_channel_buf_parm { + ipu_channel_t channel; + ipu_buffer_t type; + uint32_t pixel_fmt; + uint16_t width; + uint16_t height; + uint16_t stride; + ipu_rotate_mode_t rot_mode; + dma_addr_t phyaddr_0; + dma_addr_t phyaddr_1; + uint32_t u_offset; + uint32_t v_offset; + uint32_t bufNum; +} ipu_channel_buf_parm; + +typedef struct _ipu_channel_link { + ipu_channel_t src_ch; + ipu_channel_t dest_ch; +} ipu_channel_link; + +typedef struct _ipu_channel_info { + ipu_channel_t channel; + bool stop; +} ipu_channel_info; + +typedef struct ipu_irq_info { + uint32_t irq; + irqreturn_t(*handler) (int, void *); + uint32_t irq_flags; + char *devname; + void *dev_id; +} ipu_irq_info; + +typedef struct _ipu_sdc_panel_info { + ipu_panel_t panel; + uint32_t pixel_clk; + uint16_t width; + uint16_t height; + uint32_t pixel_fmt; + uint16_t hStartWidth; + uint16_t hSyncWidth; + uint16_t hEndWidth; + uint16_t vStartWidth; + uint16_t vSyncWidth; + uint16_t vEndWidth; + ipu_di_signal_cfg_t signal; +} ipu_sdc_panel_info; + +typedef struct _ipu_sdc_window_pos { + ipu_channel_t channel; + int16_t x_pos; + int16_t y_pos; +} ipu_sdc_window_pos; + +typedef struct _ipu_sdc_global_alpha { + bool enable; + uint8_t alpha; +} ipu_sdc_global_alpha; + +typedef struct _ipu_sdc_color_key { + ipu_channel_t channel; + bool enable; + uint32_t colorKey; +} ipu_sdc_color_key; + +typedef struct _ipu_adc_template { + display_port_t disp; + uint32_t *pCmd; + bool write; +} ipu_adc_template; + +typedef struct _ipu_adc_update { + ipu_channel_t channel; + ipu_adc_update_mode_t mode; + uint32_t refresh_rate; + unsigned long addr; + uint32_t *size; +} ipu_adc_update; + +typedef struct _ipu_adc_snoop { + uint32_t *statl; + uint32_t *stath; +} ipu_adc_snoop; + +typedef struct _ipu_adc_cmd { + display_port_t disp; + cmddata_t type; + uint32_t cmd; + uint32_t *params; + uint16_t numParams; +} ipu_adc_cmd; + +typedef struct _ipu_adc_panel { + display_port_t disp; + uint16_t width; + uint16_t height; + uint32_t pixel_fmt; + uint32_t stride; + ipu_adc_sig_cfg_t signal; + display_addressing_t addr; + uint32_t vsync_width; + vsync_t mode; +} ipu_adc_panel; + +typedef struct _ipu_adc_ifc_timing { + display_port_t disp; + bool read; + uint32_t cycle_time; + uint32_t up_time; + uint32_t down_time; + uint32_t read_latch_time; + uint32_t pixel_clk; +} ipu_adc_ifc_timing; + +typedef struct _ipu_csi_interface { + uint16_t width; + uint16_t height; + uint16_t pixel_fmt; + ipu_csi_signal_cfg_t signal; +} ipu_csi_interface; + +typedef struct _ipu_csi_mclk { + int src; + bool flag; + bool wait; +} ipu_csi_mclk; + +typedef struct _ipu_csi_window { + uint32_t left; + uint32_t top; +} ipu_csi_window; + +typedef struct _ipu_csi_window_size { + uint32_t width; + uint32_t height; +} ipu_csi_window_size; + +typedef struct _ipu_event_info { + int irq; + void *dev; +} ipu_event_info; + +typedef struct _ipu_mem_info { + dma_addr_t paddr; + void *vaddr; + int size; +} ipu_mem_info; + +/* IOCTL commands */ + +#define IPU_INIT_CHANNEL _IOW('I',0x1,ipu_channel_parm) +#define IPU_UNINIT_CHANNEL _IOW('I',0x2,ipu_channel_t) +#define IPU_INIT_CHANNEL_BUFFER _IOW('I',0x3,ipu_channel_buf_parm) +#define IPU_UPDATE_CHANNEL_BUFFER _IOW('I',0x4,ipu_channel_buf_parm) +#define IPU_SELECT_CHANNEL_BUFFER _IOW('I',0x5,ipu_channel_buf_parm) +#define IPU_LINK_CHANNELS _IOW('I',0x6,ipu_channel_link) +#define IPU_UNLINK_CHANNELS _IOW('I',0x7,ipu_channel_link) +#define IPU_ENABLE_CHANNEL _IOW('I',0x8,ipu_channel_t) +#define IPU_DISABLE_CHANNEL _IOW('I',0x9,ipu_channel_info) +#define IPU_ENABLE_IRQ _IOW('I',0xA,int) +#define IPU_DISABLE_IRQ _IOW('I',0xB,int) +#define IPU_CLEAR_IRQ _IOW('I',0xC,int) +#define IPU_FREE_IRQ _IOW('I',0xD,ipu_irq_info) +#define IPU_REQUEST_IRQ_STATUS _IOW('I',0xE,int) +#define IPU_SDC_INIT_PANEL _IOW('I',0xF,ipu_sdc_panel_info) +#define IPU_SDC_SET_WIN_POS _IOW('I',0x10,ipu_sdc_window_pos) +#define IPU_SDC_SET_GLOBAL_ALPHA _IOW('I',0x11,ipu_sdc_global_alpha) +#define IPU_SDC_SET_COLOR_KEY _IOW('I',0x12,ipu_sdc_color_key) +#define IPU_SDC_SET_BRIGHTNESS _IOW('I',0x13,int) +#define IPU_ADC_WRITE_TEMPLATE _IOW('I',0x14,ipu_adc_template) +#define IPU_ADC_UPDATE _IOW('I',0x15,ipu_adc_update) +#define IPU_ADC_SNOOP _IOW('I',0x16,ipu_adc_snoop) +#define IPU_ADC_CMD _IOW('I',0x17,ipu_adc_cmd) +#define IPU_ADC_INIT_PANEL _IOW('I',0x18,ipu_adc_panel) +#define IPU_ADC_IFC_TIMING _IOW('I',0x19,ipu_adc_ifc_timing) +#define IPU_CSI_INIT_INTERFACE _IOW('I',0x1A,ipu_csi_interface) +#define IPU_CSI_ENABLE_MCLK _IOW('I',0x1B,ipu_csi_mclk) +#define IPU_CSI_READ_MCLK_FLAG _IOR('I',0x1C,ipu_csi_mclk) +#define IPU_CSI_FLASH_STROBE _IOW('I',0x1D,ipu_csi_mclk) +#define IPU_CSI_GET_WIN_SIZE _IOR('I',0x1E,ipu_csi_window_size) +#define IPU_CSI_SET_WIN_SIZE _IOW('I',0x1F,ipu_csi_window_size) +#define IPU_CSI_SET_WINDOW _IOW('I',0x20,ipu_csi_window) +#define IPU_PF_SET_PAUSE_ROW _IOW('I',0x21, uint32_t) +#define IPU_REGISTER_GENERIC_ISR _IOW('I', 0x22, ipu_event_info) +#define IPU_GET_EVENT _IOWR('I', 0x23, ipu_event_info) +#define IPU_ALOC_MEM _IOWR('I', 0x24, ipu_mem_info) +#define IPU_FREE_MEM _IOW('I', 0x25, ipu_mem_info) +#define IPU_IS_CHAN_BUSY _IOW('I', 0x26, ipu_channel_t) + +#endif diff --git a/include/linux/major.h b/include/linux/major.h index 6a8ca98c9a96..052b05353110 100644 --- a/include/linux/major.h +++ b/include/linux/major.h @@ -171,6 +171,8 @@ #define VIOTAPE_MAJOR 230 +#define LBA_MAJOR 240 + #define BLOCK_EXT_MAJOR 259 #define SCSI_OSD_MAJOR 260 /* open-osd's OSD scsi device */ diff --git a/include/linux/mfd/mc13783/core.h b/include/linux/mfd/mc13783/core.h new file mode 100644 index 000000000000..0b01d3e1241a --- /dev/null +++ b/include/linux/mfd/mc13783/core.h @@ -0,0 +1,72 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __LINUX_MFD_MC13783_CORE_H_ +#define __LINUX_MFD_MC13783_CORE_H_ + +#include <linux/kernel.h> + +/*! + * brief PMIC regulators. + */ +#define MC13783_SW1A 0 +#define MC13783_SW1B 1 +#define MC13783_SW2A 2 +#define MC13783_SW2B 3 +#define MC13783_SW3 4 +#define MC13783_VAUDIO 5 +#define MC13783_VIOHI 6 +#define MC13783_VIOLO 7 +#define MC13783_VDIG 8 +#define MC13783_VGEN 9 +#define MC13783_VRFDIG 10 +#define MC13783_VRFREF 11 +#define MC13783_VRFCP 12 +#define MC13783_VSIM 13 +#define MC13783_VESIM 14 +#define MC13783_VCAM 15 +#define MC13783_VRFBG 16 +#define MC13783_VVIB 17 +#define MC13783_VRF1 18 +#define MC13783_VRF2 19 +#define MC13783_VMMC1 20 +#define MC13783_VMMC2 21 +#define MC13783_GPO1 22 +#define MC13783_GPO2 23 +#define MC13783_GPO3 24 +#define MC13783_GPO4 25 +#define MC13783_REG_NUM 26 + +struct mc13783; +struct regulator_init_data; + +struct mc13783_pmic { + /* regulator devices */ + struct platform_device *pdev[MC13783_REG_NUM]; +}; + +struct mc13783 { + int rev; /* chip revision */ + + struct device *dev; + + u16 *reg_cache; + + /* Client devices */ + struct mc13783_pmic pmic; +}; + +int mc13783_register_regulator(struct mc13783 *mc13783, int reg, + struct regulator_init_data *initdata); + +#endif diff --git a/include/linux/mfd/mc13892/core.h b/include/linux/mfd/mc13892/core.h new file mode 100644 index 000000000000..6e4ef85f0231 --- /dev/null +++ b/include/linux/mfd/mc13892/core.h @@ -0,0 +1,77 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __LINUX_MFD_MC13892_CORE_H_ +#define __LINUX_MFD_MC13892_CORE_H_ + +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> + +#define MC13892_SW1 0 +#define MC13892_SW2 1 +#define MC13892_SW3 2 +#define MC13892_SW4 3 +#define MC13892_SWBST 4 +#define MC13892_VIOHI 5 +#define MC13892_VPLL 6 +#define MC13892_VDIG 7 +#define MC13892_VSD 8 +#define MC13892_VUSB2 9 +#define MC13892_VVIDEO 10 +#define MC13892_VAUDIO 11 +#define MC13892_VCAM 12 +#define MC13892_VGEN1 13 +#define MC13892_VGEN2 14 +#define MC13892_VGEN3 15 +#define MC13892_VUSB 16 +#define MC13892_GPO1 17 +#define MC13892_GPO2 18 +#define MC13892_GPO3 19 +#define MC13892_GPO4 20 +#define MC13892_PWGT1 21 +#define MC13892_PWGT2 22 +#define MC13892_REG_NUM 23 + +struct mc13892; +struct regulator_init_data; + +struct mc13892_platform_data { + int (*init)(struct mc13892 *); +}; + +struct mc13892_pmic { + /* regulator devices */ + struct platform_device *pdev[MC13892_REG_NUM]; +}; + +struct mc13892 { + int rev; /* chip revision */ + + struct device *dev; + + /* device IO */ + union { + struct i2c_client *i2c_client; + struct spi_device *spi_device; + }; + u16 *reg_cache; + + /* Client devices */ + struct mc13892_pmic pmic; +}; + +int mc13892_register_regulator(struct mc13892 *mc13892, int reg, + struct regulator_init_data *initdata); + +#endif diff --git a/include/linux/mfd/mc34704/core.h b/include/linux/mfd/mc34704/core.h new file mode 100644 index 000000000000..e352e2b6c3d8 --- /dev/null +++ b/include/linux/mfd/mc34704/core.h @@ -0,0 +1,84 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __LINUX_MFD_MC34704_CORE_H_ +#define __LINUX_MFD_MC34704_CORE_H_ + +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> + +#define MC34704_BKLT 0 /* REG1 5V for Backlight */ +#define MC34704_CPU 1 /* REG2 3.3V for CPU & CPU board peripherals */ +#define MC34704_CORE 2 /* REG3 1.45V for CPU Core */ +#define MC34704_DDR 3 /* REG4 1.8V for DDR */ +#define MC34704_PERS 4 /* REG5 3.3V for Personality board */ +#define MC34704_REG6 5 /* REG6 not used */ +#define MC34704_REG7 6 /* REG7 not used */ +#define MC34704_REG8 7 /* REG8 not used */ +#define MC34704_REG_NUM 8 + +/* Regulator voltages in mV and dynamic voltage scaling limits in percent */ + +#define REG1_V_MV 5000 +#define REG1_DVS_MIN_PCT (-10) +#define REG1_DVS_MAX_PCT 10 + +#define REG2_V_MV 3300 +#define REG2_DVS_MIN_PCT (-20) +#define REG2_DVS_MAX_PCT 17.5 + +#define REG3_V_MV 1450 +#define REG3_DVS_MIN_PCT (-20) +#define REG3_DVS_MAX_PCT 17.5 + +#define REG4_V_MV 1800 +#define REG4_DVS_MIN_PCT (-20) +#define REG4_DVS_MAX_PCT 17.5 + +#define REG5_V_MV 3300 +#define REG5_DVS_MIN_PCT (-20) +#define REG5_DVS_MAX_PCT 17.5 + +struct mc34704; +struct regulator_init_data; + +struct mc34704_platform_data { + int (*init) (struct mc34704 *); +}; + +struct mc34704_pmic { + /* regulator devices */ + struct platform_device *pdev[MC34704_REG_NUM]; +}; + +struct mc34704 { + int rev; /* chip revision */ + + struct device *dev; + + /* device IO */ + union { + struct i2c_client *i2c_client; + struct spi_device *spi_device; + }; + u16 *reg_cache; + + /* Client devices */ + struct mc34704_pmic pmic; +}; + +int mc34704_register_regulator(struct mc34704 *mc34704, int reg, + struct regulator_init_data *initdata); + +#endif diff --git a/include/linux/mfd/mc9s08dz60/core.h b/include/linux/mfd/mc9s08dz60/core.h new file mode 100644 index 000000000000..ecc1806c0f90 --- /dev/null +++ b/include/linux/mfd/mc9s08dz60/core.h @@ -0,0 +1,59 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __LINUX_MFD_MC13892_CORE_H_ +#define __LINUX_MFD_MC13892_CORE_H_ + +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> + +#define MC9S08DZ60_LCD 0 +#define MC9S08DZ60_WIFI 1 +#define MC9S08DZ60_HDD 2 +#define MC9S08DZ60_GPS 3 +#define MC9S08DZ60_SPKR 4 +#define MC9S08DZ60_REG_NUM 5 + +struct mc9s08dz60; +struct regulator_init_data; + +struct mc9s08dz60_platform_data { + int (*init)(struct mc9s08dz60 *); +}; + +struct mc9s08dz60_pmic { + /* regulator devices */ + struct platform_device *pdev[MC9S08DZ60_REG_NUM]; +}; + +struct mc9s08dz60 { + int rev; /* chip revision */ + + struct device *dev; + + /* device IO */ + union { + struct i2c_client *i2c_client; + struct spi_device *spi_device; + }; + u16 *reg_cache; + + /* Client devices */ + struct mc9s08dz60_pmic pmic; +}; + +int mc9s08dz60_register_regulator(struct mc9s08dz60 *mc9s08dz60, int reg, + struct regulator_init_data *initdata); + +#endif diff --git a/include/linux/mfd/mc9s08dz60/pmic.h b/include/linux/mfd/mc9s08dz60/pmic.h new file mode 100644 index 000000000000..c1218ad7ebc0 --- /dev/null +++ b/include/linux/mfd/mc9s08dz60/pmic.h @@ -0,0 +1,82 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +#ifndef __LINUX_MCU_PMIC_H_ +#define __LINUX_MCU_PMIC_H_ + +enum { + + /*reg names for mcu */ + REG_MCU_VERSION = 0, + REG_MCU_SECS, + REG_MCU_MINS, + REG_MCU_HRS, + REG_MCU_DAY, + REG_MCU_DATE, + REG_MCU_MONTH, + REG_MCU_YEAR, + REG_MCU_ALARM_SECS, + REG_MCU_ALARM_MINS, + REG_MCU_ALARM_HRS, + REG_MCU_TS_CONTROL, + REG_MCU_X_LOW, + REG_MCU_Y_LOW, + REG_MCU_XY_HIGH, + REG_MCU_X_LEFT_LOW, + REG_MCU_X_LEFT_HIGH, + REG_MCU_X_RIGHT, + REG_MCU_Y_TOP_LOW, + REG_MCU_Y_TOP_HIGH, + REG_MCU_Y_BOTTOM, + REG_MCU_RESET_1, + REG_MCU_RESET_2, + REG_MCU_POWER_CTL, + REG_MCU_DELAY_CONFIG, + REG_MCU_GPIO_1, + REG_MCU_GPIO_2, + REG_MCU_KPD_1, + REG_MCU_KPD_2, + REG_MCU_KPD_CONTROL, + REG_MCU_INT_ENABLE_1, + REG_MCU_INT_ENABLE_2, + REG_MCU_INT_FLAG_1, + REG_MCU_INT_FLAG_2, + REG_MCU_DES_FLAG, +}; + +enum { + + MCU_GPIO_REG_RESET_1, + MCU_GPIO_REG_RESET_2, + MCU_GPIO_REG_POWER_CONTROL, + MCU_GPIO_REG_GPIO_CONTROL_1, + MCU_GPIO_REG_GPIO_CONTROL_2, +}; + +int pmic_gpio_set_bit_val(int reg, unsigned int bit, + unsigned int val); + +int pmic_gpio_get_bit_val(int reg, unsigned int bit, + unsigned int *val); + +int pmic_gpio_get_designation_bit_val(unsigned int bit, + unsigned int *val); + + +int mcu_pmic_read_reg(int reg, unsigned int *reg_value, + unsigned int reg_mask); + +int mcu_pmic_write_reg(int reg, unsigned int reg_value, + unsigned int reg_mask); +#endif diff --git a/include/linux/mfd/wm8350/audio.h b/include/linux/mfd/wm8350/audio.h index d899dc0223ba..c036c54a2dab 100644 --- a/include/linux/mfd/wm8350/audio.h +++ b/include/linux/mfd/wm8350/audio.h @@ -603,6 +603,8 @@ struct wm8350_audio_platform_data { int drain_msecs; /* OFF drain time */ int cap_discharge_msecs; /* Cap ON (from OFF) discharge time */ int vmid_charge_msecs; /* vmid power up time */ + char *regulator1; + char *regulator2; u32 vmid_s_curve:2; /* vmid enable s curve speed */ u32 dis_out4:2; /* out4 discharge speed */ u32 dis_out3:2; /* out3 discharge speed */ diff --git a/include/linux/mfd/wm8350/bl.h b/include/linux/mfd/wm8350/bl.h new file mode 100644 index 000000000000..ded2d22db737 --- /dev/null +++ b/include/linux/mfd/wm8350/bl.h @@ -0,0 +1,32 @@ +/* + * bl.h -- Backlight Driver for Wolfson WM8350 PMIC + * + * Copyright 2009 Wolfson Microelectronics PLC + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __LINUX_MFD_WM8350_BL_H_ +#define __LINUX_MFD_WM8350_BL_H_ + +#include <linux/fb.h> + +/* + * WM8350 Backlight platform data + */ +struct wm8350_bl_platform_data { + int isink; + int dcdc; + int voltage_ramp; + int retries; + int max_brightness; + int power; + int brightness; + int (*check_fb)(struct fb_info *); +}; + +#endif diff --git a/include/linux/mxc_asrc.h b/include/linux/mxc_asrc.h new file mode 100644 index 000000000000..17e97bef003b --- /dev/null +++ b/include/linux/mxc_asrc.h @@ -0,0 +1,210 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx35_asrc.h + * + * @brief MX35 Asynchronous Sample Rate Converter + * + * @ingroup ?? + */ + +#ifndef __MXC_ASRC_H__ +#define __MXC_ASRC_H__ + +#define ASRC_IOC_MAGIC 'C' + +#define ASRC_REQ_PAIR _IOWR(ASRC_IOC_MAGIC, 0, struct asrc_req) +#define ASRC_CONFIG_PAIR _IOWR(ASRC_IOC_MAGIC, 1, struct asrc_config) +#define ASRC_RELEASE_PAIR _IOW(ASRC_IOC_MAGIC, 2, enum asrc_pair_index) +#define ASRC_QUERYBUF _IOWR(ASRC_IOC_MAGIC, 3, struct asrc_buffer) +#define ASRC_Q_INBUF _IOW(ASRC_IOC_MAGIC, 4, struct asrc_buffer) +#define ASRC_DQ_INBUF _IOW(ASRC_IOC_MAGIC, 5, struct asrc_buffer) +#define ASRC_Q_OUTBUF _IOW(ASRC_IOC_MAGIC, 6, struct asrc_buffer) +#define ASRC_DQ_OUTBUF _IOW(ASRC_IOC_MAGIC, 7, struct asrc_buffer) +#define ASRC_START_CONV _IOW(ASRC_IOC_MAGIC, 8, enum asrc_pair_index) +#define ASRC_STOP_CONV _IOW(ASRC_IOC_MAGIC, 9, enum asrc_pair_index) +#define ASRC_STATUS _IOW(ASRC_IOC_MAGIC, 10, struct asrc_status_flags) +#define ASRC_FLUSH _IOW(ASRC_IOC_MAGIC, 11, enum asrc_pair_index) + +enum asrc_pair_index { + ASRC_PAIR_A, + ASRC_PAIR_B, + ASRC_PAIR_C +}; + +enum asrc_inclk { + INCLK_NONE = 0x03, + INCLK_ESAI_RX = 0x00, + INCLK_SSI1_RX = 0x01, + INCLK_SSI2_RX = 0x02, + INCLK_SPDIF_RX = 0x04, + INCLK_MLB_CLK = 0x05, + INCLK_ESAI_TX = 0x08, + INCLK_SSI1_TX = 0x09, + INCLK_SSI2_TX = 0x0a, + INCLK_SPDIF_TX = 0x0c, + INCLK_ASRCK1_CLK = 0x0f, +}; + +enum asrc_outclk { + OUTCLK_NONE = 0x03, + OUTCLK_ESAI_TX = 0x00, + OUTCLK_SSI1_TX = 0x01, + OUTCLK_SSI2_TX = 0x02, + OUTCLK_SPDIF_TX = 0x04, + OUTCLK_MLB_CLK = 0x05, + OUTCLK_ESAI_RX = 0x08, + OUTCLK_SSI1_RX = 0x09, + OUTCLK_SSI2_RX = 0x0a, + OUTCLK_SPDIF_RX = 0x0c, + OUTCLK_ASRCK1_CLK = 0x0f, +}; + +struct asrc_config { + enum asrc_pair_index pair; + unsigned int channel_num; + unsigned int buffer_num; + unsigned int dma_buffer_size; + unsigned int input_sample_rate; + unsigned int output_sample_rate; + unsigned int word_width; + enum asrc_inclk inclk; + enum asrc_outclk outclk; +}; + +struct asrc_pair { + unsigned int start_channel; + unsigned int chn_num; + unsigned int chn_max; + unsigned int active; + unsigned int overload_error; +}; + +struct asrc_req { + unsigned int chn_num; + enum asrc_pair_index index; +}; + +struct asrc_querybuf { + unsigned int buffer_index; + unsigned int input_length; + unsigned int output_length; + unsigned long input_offset; + unsigned long output_offset; +}; + +struct asrc_buffer { + unsigned int index; + unsigned int length; + int buf_valid; +}; + +struct asrc_status_flags { + enum asrc_pair_index index; + unsigned int overload_error; +}; + +#define ASRC_BUF_NA -35 /* ASRC DQ's buffer is NOT available */ +#define ASRC_BUF_AV 35 /* ASRC DQ's buffer is available */ +enum asrc_error_status { + ASRC_TASK_Q_OVERLOAD = 0x01, + ASRC_OUTPUT_TASK_OVERLOAD = 0x02, + ASRC_INPUT_TASK_OVERLOAD = 0x04, + ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08, + ASRC_INPUT_BUFFER_UNDERRUN = 0x10, +}; + +#ifdef __KERNEL__ + +#define ASRC_DMA_BUFFER_NUM 8 + +#define ASRC_ASRCTR_REG 0x00 +#define ASRC_ASRIER_REG 0x04 +#define ASRC_ASRCNCR_REG 0x0C +#define ASRC_ASRCFG_REG 0x10 +#define ASRC_ASRCSR_REG 0x14 +#define ASRC_ASRCDR1_REG 0x18 +#define ASRC_ASRCDR2_REG 0x1C +#define ASRC_ASRSTR_REG 0x20 +#define ASRC_ASRRA_REG 0x24 +#define ASRC_ASRRB_REG 0x28 +#define ASRC_ASRRC_REG 0x2C +#define ASRC_ASRPM1_REG 0x40 +#define ASRC_ASRPM2_REG 0x44 +#define ASRC_ASRPM3_REG 0x48 +#define ASRC_ASRPM4_REG 0x4C +#define ASRC_ASRPM5_REG 0x50 +#define ASRC_ASRTFR1 0x54 +#define ASRC_ASRCCR_REG 0x5C +#define ASRC_ASRDIA_REG 0x60 +#define ASRC_ASRDOA_REG 0x64 +#define ASRC_ASRDIB_REG 0x68 +#define ASRC_ASRDOB_REG 0x6C +#define ASRC_ASRDIC_REG 0x70 +#define ASRC_ASRDOC_REG 0x74 +#define ASRC_ASRIDRHA_REG 0x80 +#define ASRC_ASRIDRLA_REG 0x84 +#define ASRC_ASRIDRHB_REG 0x88 +#define ASRC_ASRIDRLB_REG 0x8C +#define ASRC_ASRIDRHC_REG 0x90 +#define ASRC_ASRIDRLC_REG 0x94 +#define ASRC_ASR76K_REG 0x98 +#define ASRC_ASR56K_REG 0x9C + +struct dma_block { + unsigned int index; + unsigned int length; + unsigned char *dma_vaddr; + dma_addr_t dma_paddr; + struct list_head queue; +}; + +struct asrc_pair_params { + enum asrc_pair_index index; + struct list_head input_queue; + struct list_head input_done_queue; + struct list_head output_queue; + struct list_head output_done_queue; + wait_queue_head_t input_wait_queue; + wait_queue_head_t output_wait_queue; + unsigned int input_counter; + unsigned int output_counter; + unsigned int input_queue_empty; + unsigned int output_queue_empty; + unsigned int input_dma_channel; + unsigned int output_dma_channel; + unsigned int input_buffer_size; + unsigned int output_buffer_size; + unsigned int buffer_num; + unsigned int pair_hold; + unsigned int asrc_active; + struct dma_block input_dma[ASRC_DMA_BUFFER_NUM]; + struct dma_block output_dma[ASRC_DMA_BUFFER_NUM]; + struct semaphore busy_lock; +}; + +struct asrc_data { + struct asrc_pair asrc_pair[3]; +}; + +extern int asrc_req_pair(int chn_num, enum asrc_pair_index *index); +extern void asrc_release_pair(enum asrc_pair_index index); +extern int asrc_config_pair(struct asrc_config *config); +extern void asrc_get_status(struct asrc_status_flags *flags); +extern void asrc_start_conv(enum asrc_pair_index index); +extern void asrc_stop_conv(enum asrc_pair_index index); + +#endif /* __kERNEL__ */ + +#endif /* __MXC_ASRC_H__ */ diff --git a/include/linux/mxc_mlb.h b/include/linux/mxc_mlb.h new file mode 100644 index 000000000000..b38ad8c16702 --- /dev/null +++ b/include/linux/mxc_mlb.h @@ -0,0 +1,51 @@ +/* + * mxc_mlb.h + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef _MXC_MLB_H +#define _MXC_MLB_H + +/* define IOCTL command */ +#define MLB_SET_FPS _IOW('S', 0x10, unsigned int) +#define MLB_GET_VER _IOR('S', 0x11, unsigned long) +#define MLB_SET_DEVADDR _IOR('S', 0x12, unsigned char) +/*! + * set channel address for each logical channel + * the MSB 16bits is for tx channel, the left LSB is for rx channel + */ +#define MLB_CHAN_SETADDR _IOW('S', 0x13, unsigned int) +#define MLB_CHAN_STARTUP _IO('S', 0x14) +#define MLB_CHAN_SHUTDOWN _IO('S', 0x15) +#define MLB_CHAN_GETEVENT _IOR('S', 0x16, unsigned long) + +/*! + * MLB event define + */ +enum { + MLB_EVT_TX_PROTO_ERR_CUR = 1 << 0, + MLB_EVT_TX_BRK_DETECT_CUR = 1 << 1, + MLB_EVT_TX_PROTO_ERR_PREV = 1 << 8, + MLB_EVT_TX_BRK_DETECT_PREV = 1 << 9, + MLB_EVT_RX_PROTO_ERR_CUR = 1 << 16, + MLB_EVT_RX_BRK_DETECT_CUR = 1 << 17, + MLB_EVT_RX_PROTO_ERR_PREV = 1 << 24, + MLB_EVT_RX_BRK_DETECT_PREV = 1 << 25, +}; + +#ifdef __KERNEL__ +extern void gpio_mlb_active(void); +extern void gpio_mlb_inactive(void); +#endif + +#endif /* _MXC_MLB_H */ diff --git a/include/linux/mxc_pf.h b/include/linux/mxc_pf.h new file mode 100644 index 000000000000..6d38642eef12 --- /dev/null +++ b/include/linux/mxc_pf.h @@ -0,0 +1,125 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +/*! + * @defgroup MXC_PF MPEG4/H.264 Post Filter Driver + */ +/*! + * @file arch-mxc/mxc_pf.h + * + * @brief MXC IPU MPEG4/H.264 Post-filtering driver + * + * User-level API for IPU Hardware MPEG4/H.264 Post-filtering. + * + * @ingroup MXC_PF + */ +#ifndef __INCLUDED_MXC_PF_H__ +#define __INCLUDED_MXC_PF_H__ + +#define PF_MAX_BUFFER_CNT 17 + +#define PF_WAIT_Y 0x0001 +#define PF_WAIT_U 0x0002 +#define PF_WAIT_V 0x0004 +#define PF_WAIT_ALL (PF_WAIT_Y|PF_WAIT_U|PF_WAIT_V) + +/*! + * Structure for Post Filter initialization parameters. + */ +typedef struct { + uint16_t pf_mode; /*!< Post filter operation mode */ + uint16_t width; /*!< Width of frame in pixels */ + uint16_t height; /*!< Height of frame in pixels */ + uint16_t stride; /*!< Stride of Y plane in pixels. Stride for U and V planes is half Y stride */ + uint32_t qp_size; + unsigned long qp_paddr; +} pf_init_params; + +/*! + * Structure for Post Filter buffer request parameters. + */ +typedef struct { + int count; /*!< Number of buffers requested */ + __u32 req_size; +} pf_reqbufs_params; + +/*! + * Structure for Post Filter buffer request parameters. + */ +typedef struct { + int index; + int size; /*!< Size of buffer allocated */ + __u32 offset; /*!< Buffer offset in driver memory. Set by QUERYBUF */ + __u32 y_offset; /*!< Optional starting relative offset of Y data + from beginning of buffer. Set to 0 to use default + calculated based on height and stride */ + __u32 u_offset; /*!< Optional starting relative offset of U data + from beginning of buffer. Set to 0 to use default + calculated based on height and stride */ + __u32 v_offset; /*!< Optional starting relative offset of V data + from beginning of buffer. Set to 0 to use default + calculated based on height and stride */ +} pf_buf; + +/*! + * Structure for Post Filter start parameters. + */ +typedef struct { + pf_buf in; /*!< Input buffer address and offsets */ + pf_buf out; /*!< Output buffer address and offsets */ + int qp_buf; + int wait; + uint32_t h264_pause_row; /*!< Row to pause at for H.264 mode. 0 to disable pause */ +} pf_start_params; + +/*! @name User Client Ioctl Interface */ +/*! @{ */ + +/*! + * IOCTL to Initialize the Post Filter. + */ +#define PF_IOCTL_INIT _IOW('F',0x0, pf_init_params) + +/*! + * IOCTL to Uninitialize the Post Filter. + */ +#define PF_IOCTL_UNINIT _IO('F',0x1) + +/*! + * IOCTL to set the buffer mode and allocate buffers if driver allocated. + */ +#define PF_IOCTL_REQBUFS _IOWR('F',0x2, pf_reqbufs_params) + +/*! + * IOCTL to set the buffer mode and allocate buffers if driver allocated. + */ +#define PF_IOCTL_QUERYBUF _IOR('F',0x2, pf_buf) + +/*! + * IOCTL to start post filtering on a frame of data. This ioctl may block until + * processing is done or return immediately. + */ +#define PF_IOCTL_START _IOWR('F',0x3, pf_start_params) + +/*! + * IOCTL to resume post-filtering after an intra frame pause in H.264 mode. + */ +#define PF_IOCTL_RESUME _IOW('F',0x4, int) + +/*! + * IOCTL to wait for post-filtering to complete. + */ +#define PF_IOCTL_WAIT _IOW('F',0x5, int) +/*! @} */ + +#endif /* __INCLUDED_MXC_PF_H__ */ diff --git a/include/linux/mxc_scc2_driver.h b/include/linux/mxc_scc2_driver.h new file mode 100644 index 000000000000..18ff7ae79eb4 --- /dev/null +++ b/include/linux/mxc_scc2_driver.h @@ -0,0 +1,973 @@ + +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef SCC_DRIVER_H +#define SCC_DRIVER_H + +/* + * NAMING CONVENTIONS + * ================== + * (A note to maintainers and other interested parties) + * + * Use scc_ or SCC_ prefix for 'high-level' interface routines and the types + * passed to those routines. Try to avoid #defines in these interfaces. + * + * Use SMN_ or SCM_ prefix for the #defines used with scc_read_register() and + * scc_write_register, or values passed/retrieved from those routines. + */ + +/*! @file mxc_scc2_driver.h + * + * @brief (Header file to use the SCC2 driver.) + * + * The SCC2 driver is available to other kernel modules directly. Secure + * Partition functionality is extended to users through the SHW API. Other + * functionality of the SCC2 is limited to kernel-space users. + * + * With the exception of #scc_monitor_security_failure(), all routines are + * 'synchronous', i.e. they will not return to their caller until the requested + * action is complete, or fails to complete. Some of these functions could + * take quite a while to perform, depending upon the request. + * + * Routines are provided to: + * @li trigger a security-violation alarm - #scc_set_sw_alarm() + * @li get configuration and version information - #scc_get_configuration() + * @li zeroize memory - #scc_zeroize_memories() + * @li Work with secure partitions: #scc_allocate_partition() + * #scc_engage_partition() #scc_diminish_permissions() + * #scc_release_partition() + * @li Encrypt or decrypt regions of data: #scc_encrypt_region() + * #scc_decrypt_region() + * @li monitor the Security Failure alarm - #scc_monitor_security_failure() + * @li stop monitoring Security Failure alarm - + * #scc_stop_monitoring_security_failure() + * @li write registers of the SCC - #scc_write_register() + * @li read registers of the SCC - #scc_read_register() + * + * The SCC2 encrypts and decrypts using Triple DES with an internally stored + * key. When the SCC2 is in Secure mode, it uses its secret, unique-per-chip + * key. When it is in Non-Secure mode, it uses a default key. This ensures + * that secrets stay secret if the SCC2 is not in Secure mode. + * + * Not all functions that could be provided in a 'high level' manner have been + * implemented. Among the missing are interfaces to the ASC/AIC components and + * the timer functions. These and other features must be accessed through + * #scc_read_register() and #scc_write_register(), using the @c \#define values + * provided. + * + * Here is a glossary of acronyms used in the SCC2 driver documentation: + * - CBC - Cipher Block Chaining. A method of performing a block cipher. + * Each block is encrypted using some part of the result of the previous + * block's encryption. It needs an 'initialization vector' to seed the + * operation. + * - ECB - Electronic Code Book. A method of performing a block cipher. + * With a given key, a given block will always encrypt to the same value. + * - DES - Data Encryption Standard. (8-byte) Block cipher algorithm which + * uses 56-bit keys. In SCC2, this key is constant and unique to the device. + * SCC uses the "triple DES" form of this algorithm. + * - AIC - Algorithm Integrity Checker. + * - ASC - Algorithm Sequence Checker. + * - SMN - Security Monitor. The part of the SCC2 responsible for monitoring + * for security problems and notifying the CPU and other PISA components. + * - SCM - Secure Memory. The part of the SCC2 which handles the cryptography. + * - SCC - Security Controller. Central security mechanism for PISA. + * - PISA - Platform-Independent Security Architecture. + */ + +/* Temporarily define compile-time flags to make Doxygen happy. */ +#ifdef DOXYGEN_HACK +/** @defgroup scccompileflags SCC Driver compile-time flags + * + * These preprocessor flags should be set, if desired, in a makefile so + * that they show up on the compiler command line. + */ +/** @addtogroup scccompileflags */ + +/** @{ */ +/** + * Compile-time flag to change @ref smnregs and @ref scmregs + * offset values for the SCC's implementation on the MX.21 board. + * + * This must also be set properly for any code which calls the + * scc_read_register() or scc_write_register() functions or references the + * register offsets. + */ +#define TAHITI +/** @} */ +#undef TAHITI + +#endif /* DOXYGEN_HACK */ + +/*! Major Version of the driver. Used for + scc_configuration->driver_major_version */ +#define SCC_DRIVER_MAJOR_VERSION 2 +/*! Old Minor Version of the driver. */ +#define SCC_DRIVER_MINOR_VERSION_0 0 +/*! Minor Version of the driver. Used for + scc_configuration->driver_minor_version */ +#define SCC_DRIVER_MINOR_VERSION_2 2 + + +/*! + * Interrupt line number of SCM interrupt. + */ +#define INT_SCC_SCM MXC_INT_SCC_SCM + +/*! + * Interrupt line number of the SMN interrupt. + */ +#define INT_SCC_SMN MXC_INT_SCC_SMN + +/** + * @typedef scc_return_t + */ +/** Common status return values from SCC driver functions. */ + typedef enum scc_return_t { + SCC_RET_OK = 0, /**< Function succeeded */ + SCC_RET_FAIL, /**< Non-specific failure */ + SCC_RET_VERIFICATION_FAILED, + /**< Decrypt validation failed */ + SCC_RET_TOO_MANY_FUNCTIONS, + /**< At maximum registered functions */ + SCC_RET_BUSY, /**< SCC is busy and cannot handle request */ + /**< Encryption or decryption failed because@c count_out_bytes + says that @c data_out is too small to hold the value. */ + SCC_RET_INSUFFICIENT_SPACE, + } scc_return_t; + +/** + * @typedef scc_partition_status_t + */ +/** Partition status information. */ + typedef enum scc_partition_status_t { + SCC_PART_S_UNUSABLE, + /**< Partition not implemented */ + SCC_PART_S_UNAVAILABLE, + /**< Partition owned by other host */ + SCC_PART_S_AVAILABLE, + /**< Partition available */ + SCC_PART_S_ALLOCATED, + /**< Partition owned by host but not engaged*/ + SCC_PART_S_ENGAGED, + /**< Partition owned by host and engaged */ + } scc_partition_status_t; + +/** + * Configuration information about SCC and the driver. + * + * This struct/typedef contains information from the SCC and the driver to + * allow the user of the driver to determine the size of the SCC's memories and + * the version of the SCC and the driver. + */ + typedef struct scc_config_t { + int driver_major_version; + /**< Major version of the SCC driver code */ + int driver_minor_version; + /**< Minor version of the SCC driver code */ + int scm_version; /**< Version from SCM Configuration register */ + int smn_version; /**< Version from SMN Status register */ + /**< Number of bytes per block of RAM; also + block size of the crypto algorithm. */ + int block_size_bytes; + int partition_size_bytes; + /**< Number of bytes in each partition */ + int partition_count; + /**< Number of partitions on this platform */ + } scc_config_t; + +/** + * @typedef scc_enc_dec_t + */ +/** + * Determine whether SCC will run its cryptographic + * function as an encryption or decryption. + */ + typedef enum scc_enc_dec_t { + SCC_ENCRYPT, /**< Encrypt (from Red to Black) */ + SCC_DECRYPT /**< Decrypt (from Black to Red) */ + } scc_enc_dec_t; + +/** + * @typedef scc_verify_t + */ +/** + * Tell the driver whether it is responsible for verifying the integrity of a + * secret. During an encryption, using other than #SCC_VERIFY_MODE_NONE will + * cause a check value to be generated and appended to the plaintext before + * encryption. During decryption, the check value will be verified after + * decryption, and then stripped from the message. + */ + typedef enum scc_verify_t { + /** No verification value added or checked. Input plaintext data must be + * be a multiple of the blocksize (#scc_get_configuration()). */ + SCC_VERIFY_MODE_NONE, + /** Driver will generate/validate a 2-byte CCITT CRC. Input plaintext + will be padded to a multiple of the blocksize, adding 3-10 bytes + to the resulting output ciphertext. Upon decryption, this padding + will be stripped, and the CRC will be verified. */ + SCC_VERIFY_MODE_CCITT_CRC + } scc_verify_t; + +/** + * @typedef scc_cypher_mode_t + */ +/** + * Select the cypher mode to use for partition cover/uncover operations. + */ + + typedef enum scc_cypher_mode_t { + SCC_CYPHER_MODE_ECB = 1, + /**< ECB mode */ + SCC_CYPHER_MODE_CBC = 2, + /**< CBC mode */ + } scc_cypher_mode_t; + +/** + * Allocate a partition of secure memory + * + * @param smid_value Value to use for the SMID register. Must be 0 for + * kernel mode ownership. + * @param[out] part_no (If successful) Assigned partition number. + * @param[out] part_base Kernel virtual address of the partition. + * @param[out] part_phys Physical address of the partition. + * + * @return SCC_RET_OK if successful. + */ + extern scc_return_t + scc_allocate_partition(uint32_t smid_value, + int *part_no, + void **part_base, uint32_t *part_phys); + +/* Note: This function has to be run in the same context (userspace or kernel + * mode) as the process that will be using the partition. Because the SCC2 API + * is not accessible in user mode, this function is also provided as a macro in + * in fsl_shw.h. Kernel-mode users that include this file are able to use this + * version of the function without having to include the whole SHW API. If the + * macro definition was defined before we got here, un-define it so this + * version will be used instead. + */ + +#ifdef scc_engage_partition +#undef scc_engage_partition +#endif + +/** + * Engage partition of secure memory + * + * @param part_base (kernel) Virtual + * @param UMID NULL, or 16-byte UMID for partition security + * @param permissions ORed values of the type SCM_PERM_* which will be used as + * initial partition permissions. SHW API users should use + * the FSL_PERM_* definitions instead. + * + * @return SCC_RET_OK if successful. + */ + extern scc_return_t + scc_engage_partition(void *part_base, + const uint8_t *UMID, uint32_t permissions); + +/** + * Release a partition of secure memory + * + * @param part_base Kernel virtual address of the partition to be released. + * + * @return SCC_RET_OK if successful. + */ + extern scc_return_t scc_release_partition(void *part_base); + +/** + * Diminish the permissions on a partition of secure memory + * + * @param part_base Kernel virtual address of the partition. + * + * @param permissions ORed values of the type SCM_PERM_* which will be used as + * initial partition permissions. SHW API users should use + * the FSL_PERM_* definitions instead. + * + * @return SCC_RET_OK if successful. + */ + extern scc_return_t + scc_diminish_permissions(void *part_base, uint32_t permissions); + +/** + * Query the status of a partition of secure memory + * + * @param part_base Kernel virtual address of the partition. + * + * @return SCC_RET_OK if successful. + */ + extern scc_partition_status_t scc_partition_status(void *part_base); + +/** + * Calculate the physical address from the kernel virtual address. + */ + extern uint32_t scc_virt_to_phys(void *address); +/*scc_return_t +scc_verify_slot_access(uint64_t owner_id, uint32_t slot, uint32_t access_len);*/ + + +/** + * Encrypt a region of secure memory. + * + * @param part_base Kernel virtual address of the partition. + * @param offset_bytes Offset from the start of the partition to the plaintext + * data. + * @param byte_count Length of the region (octets). + * @param black_data Physical location to store the encrypted data. + * @param IV Value to use for the Initialization Vector. + * @param cypher_mode Cyphering mode to use, specified by type + * #scc_cypher_mode_t + * + * @return SCC_RET_OK if successful. + */ + extern scc_return_t + scc_encrypt_region(uint32_t part_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t *black_data, + uint32_t *IV, scc_cypher_mode_t cypher_mode); + +/** + * Decrypt a region into secure memory + * + * @param part_base Kernel virtual address of the partition. + * @param offset_bytes Offset from the start of the partition to store the + * plaintext data. + * @param byte_count Length of the region (octets). + * @param black_data Physical location of the encrypted data. + * @param IV Value to use for the Initialization Vector. + * @param cypher_mode Cyphering mode to use, specified by type + * #scc_cypher_mode_t + * + * @return SCC_RET_OK if successful. + */ + extern scc_return_t + scc_decrypt_region(uint32_t part_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t *black_data, + uint32_t *IV, scc_cypher_mode_t cypher_mode); + +/** + * Retrieve configuration information from the SCC. + * + * This function always succeeds. + * + * @return A pointer to the configuration information. This is a pointer to + * static memory and must not be freed. The values never change, and + * the return value will never be null. + */ + extern scc_config_t *scc_get_configuration(void); + +/** + * Zeroize Red and Black memories of the SCC. This will start the Zeroizing + * process. The routine will return when the memories have zeroized or failed + * to do so. The driver will poll waiting for this to occur, so this + * routine must not be called from interrupt level. Some future version of + * driver may elect instead to sleep. + * + * @return 0 or error if initialization fails. + */ + extern scc_return_t scc_zeroize_memories(void); + +/** + * Signal a software alarm to the SCC. This will take the SCC and other PISA + * parts out of Secure mode and into Security Failure mode. The SCC will stay + * in failed mode until a reboot. + * + * @internal + * If the SCC is not already in fail state, simply write the + * #SMN_COMMAND_SET_SOFTWARE_ALARM bit in #SMN_COMMAND_REG. Since there is no + * reason to wait for the interrupt to bounce back, simply act as though + * one did. + */ + extern void scc_set_sw_alarm(void); + +/** + * This routine will register a function to be called should a Security Failure + * be signalled by the SCC (Security Monitor). + * + * The callback function may be called from interrupt level, it may be called + * from some process' task. It should therefore not take a long time to + * perform its operation, and it may not sleep. + * + * @param callback_func Function pointer to routine which will receive + * notification of the security failure. + * @return 0 if function was successfully registered, non-zero on + * failure. See #scc_return_t. + * + * @internal + * There is a fixed global static array which keeps track of the requests to + * monitor the failure. + * + * Add @c callback_func to the first empty slot in #scc_callbacks[]. If there + * is no room, return #SCC_RET_TOO_MANY_FUNCTIONS. + */ + extern scc_return_t scc_monitor_security_failure(void + callback_func(void)); + +/** + * This routine will deregister a function previously registered with + * #scc_monitor_security_failure(). + * + * @param callback_func Function pointer to routine previously registered with + * #scc_stop_monitoring_security_failure(). + */ + extern void scc_stop_monitoring_security_failure(void + callback_func(void)); + +/** + * Read value from an SCC register. + * The offset will be checked for validity (range) as well as whether it is + * accessible (e.g. not busy, not in failed state) at the time of the call. + * + * @param[in] register_offset The (byte) offset within the SCC block + * of the register to be queried. See + * @ref scmregs and @ref smnregs. + * @param[out] value Pointer to where value from the register + * should be placed. + * @return 0 if OK, non-zero on error. See #scc_return_t. + * + * @internal + * Verify that the register_offset is a) valid, b) refers to a readable + * register, and c) the SCC is in a state which would allow a read of this + * register. + */ + extern scc_return_t scc_read_register(int register_offset, + uint32_t * value); + +/** + * Write a new value into an SCC register. + * The offset will be checked for validity (range) as well as whether it is + * accessible (e.g. not busy, not in failed state) at the time of the call. + * + * @param[in] register_offset The (byte) offset within the SCC block + * of the register to be modified. See + * @ref scmregs and @ref smnregs + * @param[in] value The value to store into the register. + * @return 0 if OK, non-zero on error. See #scc_return_t. + * + * @internal + * Verify that the register_offset is a) valid, b) refers to a writeable + * register, and c) the SCC is in a state which would allow a write to this + * register. + */ + extern scc_return_t scc_write_register(int register_offset, + uint32_t value); + +/** + * @defgroup scmregs SCM Registers + * + * These values are offsets into the SCC for the Secure Memory + * (SCM) registers. They are used in the @c register_offset parameter of + * #scc_read_register() and #scc_write_register(). + */ +/** @addtogroup scmregs */ +/** @{ */ +/** Offset of SCM Version ID Register */ +#define SCM_VERSION_REG 0x000 +/** Offset of SCM Interrupt Control Register */ +#define SCM_INT_CTL_REG 0x008 +/** Offset of SCM Status Register */ +#define SCM_STATUS_REG 0x00c +/** Offset of SCM Error Status Register */ +#define SCM_ERR_STATUS_REG 0x010 +/** Offset of SCM Fault Address Register */ +#define SCM_FAULT_ADR_REG 0x014 +/** Offset of SCM Partition Owners Register */ +#define SCM_PART_OWNERS_REG 0x018 +/** Offset of SCM Partitions Engaged Register */ +#define SCM_PART_ENGAGED_REG 0x01c +/** Offset of SCM Unique Number 0 Register */ +#define SCM_UNIQUE_ID0_REG 0x020 +/** Offset of SCM Unique Number 1 Register */ +#define SCM_UNIQUE_ID1_REG 0x024 +/** Offset of SCM Unique Number 2 Register */ +#define SCM_UNIQUE_ID2_REG 0x028 +/** Offset of SCM Unique Number 3 Register */ +#define SCM_UNIQUE_ID3_REG 0x02c +/** Offset of SCM Zeroize Command Register */ +#define SCM_ZCMD_REG 0x050 +/** Offset of SCM Cipher Command Register */ +#define SCM_CCMD_REG 0x054 +/** Offset of SCM Cipher Black RAM Start Address Register */ +#define SCM_C_BLACK_ST_REG 0x058 +/** Offset of SCM Internal Debug Register */ +#define SCM_DBG_STATUS_REG 0x05c +/** Offset of SCM Cipher IV 0 Register */ +#define SCM_AES_CBC_IV0_REG 0x060 +/** Offset of SCM Cipher IV 1 Register */ +#define SCM_AES_CBC_IV1_REG 0x064 +/** Offset of SCM Cipher IV 2 Register */ +#define SCM_AES_CBC_IV2_REG 0x068 +/** Offset of SCM Cipher IV 3 Register */ +#define SCM_AES_CBC_IV3_REG 0x06c +/** Offset of SCM SMID Partition 0 Register */ +#define SCM_SMID0_REG 0x080 +/** Offset of SCM Partition 0 Access Permissions Register */ +#define SCM_ACC0_REG 0x084 +/** Offset of SCM SMID Partition 1 Register */ +#define SCM_SMID1_REG 0x088 +/** Offset of SCM Partition 1 Access Permissions Register */ +#define SCM_ACC1_REG 0x08c +/** Offset of SCM SMID Partition 2 Register */ +#define SCM_SMID2_REG 0x090 +/** Offset of SCM Partition 2 Access Permissions Register */ +#define SCM_ACC2_REG 0x094 +/** Offset of SCM SMID Partition 3 Register */ +#define SCM_SMID3_REG 0x098 +/** Offset of SCM Partition 3 Access Permissions Register */ +#define SCM_ACC3_REG 0x09c +/** Offset of SCM SMID Partition 4 Register */ +#define SCM_SMID4_REG 0x0a0 +/** Offset of SCM Partition 4 Access Permissions Register */ +#define SCM_ACC4_REG 0x0a4 +/** Offset of SCM SMID Partition 5 Register */ +#define SCM_SMID5_REG 0x0a8 +/** Offset of SCM Partition 5 Access Permissions Register */ +#define SCM_ACC5_REG 0x0ac +/** Offset of SCM SMID Partition 6 Register */ +#define SCM_SMID6_REG 0x0b0 +/** Offset of SCM Partition 6 Access Permissions Register */ +#define SCM_ACC6_REG 0x0b4 +/** Offset of SCM SMID Partition 7 Register */ +#define SCM_SMID7_REG 0x0b8 +/** Offset of SCM Partition 7 Access Permissions Register */ +#define SCM_ACC7_REG 0x0bc +/** Offset of SCM SMID Partition 8 Register */ +#define SCM_SMID8_REG 0x0c0 +/** Offset of SCM Partition 8 Access Permissions Register */ +#define SCM_ACC8_REG 0x0c4 +/** Offset of SCM SMID Partition 9 Register */ +#define SCM_SMID9_REG 0x0c8 +/** Offset of SCM Partition 9 Access Permissions Register */ +#define SCM_ACC9_REG 0x0cc +/** Offset of SCM SMID Partition 10 Register */ +#define SCM_SMID10_REG 0x0d0 +/** Offset of SCM Partition 10 Access Permissions Register */ +#define SCM_ACC10_REG 0x0d4 +/** Offset of SCM SMID Partition 11 Register */ +#define SCM_SMID11_REG 0x0d8 +/** Offset of SCM Partition 11 Access Permissions Register */ +#define SCM_ACC11_REG 0x0dc +/** Offset of SCM SMID Partition 12 Register */ +#define SCM_SMID12_REG 0x0e0 +/** Offset of SCM Partition 12 Access Permissions Register */ +#define SCM_ACC12_REG 0x0e4 +/** Offset of SCM SMID Partition 13 Register */ +#define SCM_SMID13_REG 0x0e8 +/** Offset of SCM Partition 13 Access Permissions Register */ +#define SCM_ACC13_REG 0x0ec +/** Offset of SCM SMID Partition 14 Register */ +#define SCM_SMID14_REG 0x0f0 +/** Offset of SCM Partition 14 Access Permissions Register */ +#define SCM_ACC14_REG 0x0f4 +/** Offset of SCM SMID Partition 15 Register */ +#define SCM_SMID15_REG 0x0f8 +/** Offset of SCM Partition 15 Access Permissions Register */ +#define SCM_ACC15_REG 0x0fc +/** @} */ + +/** Number of bytes of register space for the SCM. */ +#define SCM_REG_BANK_SIZE 0x100 + +/** Number of bytes of register space for the SCM. */ +#define SCM_REG_BANK_SIZE 0x100 + +/** Offset of the SMN registers */ +#define SMN_ADDR_OFFSET 0x100 + +/** + * @defgroup smnregs SMN Registers + * + * These values are offsets into the SCC for the Security Monitor + * (SMN) registers. They are used in the @c register_offset parameter of the + * #scc_read_register() and #scc_write_register(). + */ +/** @addtogroup smnregs */ +/** @{ */ +/** Offset of SMN Status Register */ +#define SMN_STATUS_REG (SMN_ADDR_OFFSET+0x00000000) +/** Offset of SMH Command Register */ +#define SMN_COMMAND_REG (SMN_ADDR_OFFSET+0x00000004) +/** Offset of SMH Sequence Start Register */ +#define SMN_SEQ_START_REG (SMN_ADDR_OFFSET+0x00000008) +/** Offset of SMH Sequence End Register */ +#define SMN_SEQ_END_REG (SMN_ADDR_OFFSET+0x0000000c) +/** Offset of SMH Sequence Check Register */ +#define SMN_SEQ_CHECK_REG (SMN_ADDR_OFFSET+0x00000010) +/** Offset of SMH BitBank Count Register */ +#define SMN_BB_CNT_REG (SMN_ADDR_OFFSET+0x00000014) +/** Offset of SMH BitBank Increment Register */ +#define SMN_BB_INC_REG (SMN_ADDR_OFFSET+0x00000018) +/** Offset of SMH BitBank Decrement Register */ +#define SMN_BB_DEC_REG (SMN_ADDR_OFFSET+0x0000001c) +/** Offset of SMH Compare Register */ +#define SMN_COMPARE_REG (SMN_ADDR_OFFSET+0x00000020) +/** Offset of SMH Plaintext Check Register */ +#define SMN_PT_CHK_REG (SMN_ADDR_OFFSET+0x00000024) +/** Offset of SMH Ciphertext Check Register */ +#define SMN_CT_CHK_REG (SMN_ADDR_OFFSET+0x00000028) +/** Offset of SMH Timer Initial Value Register */ +#define SMN_TIMER_IV_REG (SMN_ADDR_OFFSET+0x0000002c) +/** Offset of SMH Timer Control Register */ +#define SMN_TIMER_CTL_REG (SMN_ADDR_OFFSET+0x00000030) +/** Offset of SMH Security Violation Register */ +#define SMN_SEC_VIO_REG (SMN_ADDR_OFFSET+0x00000034) +/** Offset of SMH Timer Register */ +#define SMN_TIMER_REG (SMN_ADDR_OFFSET+0x00000038) +/** Offset of SMH High-Assurance Control Register */ +#define SMN_HAC_REG (SMN_ADDR_OFFSET+0x0000003c) +/** Number of bytes allocated to the SMN registers */ +#define SMN_REG_BANK_SIZE 0x40 +/** @} */ + +/** Number of bytes of total register space for the SCC. */ +#define SCC_ADDRESS_RANGE (SMN_ADDR_OFFSET + SMN_REG_BANK_SIZE) + +/** + * @defgroup smnstatusregdefs SMN Status Register definitions (SMN_STATUS) + */ +/** @addtogroup smnstatusregdefs */ +/** @{ */ +/** SMN version id. */ +#define SMN_STATUS_VERSION_ID_MASK 0xfc000000 +/** number of bits to shift #SMN_STATUS_VERSION_ID_MASK to get it to LSB */ +#define SMN_STATUS_VERSION_ID_SHIFT 28 +/** Illegal bus master access attempted. */ +#define SMN_STATUS_ILLEGAL_MASTER 0x01000000 +/** Scan mode entered/exited since last reset. */ +#define SMN_STATUS_SCAN_EXIT 0x00800000 +/** Some security peripheral is initializing */ +#define SMN_STATUS_PERIP_INIT 0x00010000 +/** Internal error detected in SMN. */ +#define SMN_STATUS_SMN_ERROR 0x00008000 +/** SMN has an outstanding interrupt. */ +#define SMN_STATUS_SMN_STATUS_IRQ 0x00004000 +/** Software Alarm was triggered. */ +#define SMN_STATUS_SOFTWARE_ALARM 0x00002000 +/** Timer has expired. */ +#define SMN_STATUS_TIMER_ERROR 0x00001000 +/** Plaintext/Ciphertext compare failed. */ +#define SMN_STATUS_PC_ERROR 0x00000800 +/** Bit Bank detected overflow or underflow */ +#define SMN_STATUS_BITBANK_ERROR 0x00000400 +/** Algorithm Sequence Check failed. */ +#define SMN_STATUS_ASC_ERROR 0x00000200 +/** Security Policy Block detected error. */ +#define SMN_STATUS_SECURITY_POLICY_ERROR 0x00000100 +/** Security Violation Active error. */ +#define SMN_STATUS_SEC_VIO_ACTIVE_ERROR 0x00000080 +/** Processor booted from internal ROM. */ +#define SMN_STATUS_INTERNAL_BOOT 0x00000020 +/** SMN's internal state. */ +#define SMN_STATUS_STATE_MASK 0x0000001F +/** Number of bits to shift #SMN_STATUS_STATE_MASK to get it to LSB. */ +#define SMN_STATUS_STATE_SHIFT 0 +/** @} */ + +/** + * @defgroup sccscmstates SMN Model Secure State Controller States (SMN_STATE_MASK) + */ +/** @addtogroup sccscmstates */ +/** @{ */ +/** This is the first state of the SMN after power-on reset */ +#define SMN_STATE_START 0x0 +/** The SMN is zeroizing its RAM during reset */ +#define SMN_STATE_ZEROIZE_RAM 0x5 +/** SMN has passed internal checks, and is waiting for Software check-in */ +#define SMN_STATE_HEALTH_CHECK 0x6 +/** Fatal Security Violation. SMN is locked, SCM is inoperative. */ +#define SMN_STATE_FAIL 0x9 +/** SCC is in secure state. SCM is using secret key. */ +#define SMN_STATE_SECURE 0xA +/** Due to non-fatal error, device is not secure. SCM is using default key. */ +#define SMN_STATE_NON_SECURE 0xC +/** @} */ + +/** @{ */ +/** SCM Status bit: Key Status is Default Key in Use */ +#define SCM_STATUS_KST_DEFAULT_KEY 0x80000000 +/** SCM Status bit: Key Status is (reserved) */ +#define SCM_STATUS_KST_RESERVED1 0x40000000 +/** SCM Status bit: Key status is Wrong Key */ +#define SCM_STATUS_KST_WRONG_KEY 0x20000000 +/** SCM Status bit: Bad Key detected */ +#define SCM_STATUS_KST_BAD_KEY 0x10000000 +/** SCM Status bit: Error has occurred */ +#define SCM_STATUS_ERR 0x00008000 +/** SCM Status bit: Monitor State is Failed */ +#define SCM_STATUS_MSS_FAIL 0x00004000 +/** SCM Status bit: Monitor State is Secure */ +#define SCM_STATUS_MSS_SEC 0x00002000 +/** SCM Status bit: Secure Storage is Failed */ +#define SCM_STATUS_RSS_FAIL 0x00000400 +/** SCM Status bit: Secure Storage is Secure */ +#define SCM_STATUS_RSS_SEC 0x00000200 +/** SCM Status bit: Secure Storage is Initializing */ +#define SCM_STATUS_RSS_INIT 0x00000100 +/** SCM Status bit: Unique Number Valid */ +#define SCM_STATUS_UNV 0x00000080 +/** SCM Status bit: Big Endian mode */ +#define SCM_STATUS_BIG 0x00000040 +/** SCM Status bit: Using Secret Key */ +#define SCM_STATUS_USK 0x00000020 +/** SCM Status bit: Ram is being blocked */ +#define SCM_STATUS_BAR 0x00000010 +/** Bit mask of SRS */ +#define SCM_STATUS_SRS_MASK 0x0000000F +/** Number of bits to shift SRS to/from MSb */ +#define SCM_STATUS_SRS_SHIFT 0 +/** @} */ + +#define SCM_STATUS_SRS_RESET 0x0 /**< Reset, Zeroise All */ +#define SCM_STATUS_SRS_READY 0x1 /**< All Ready */ +#define SCM_STATUS_SRS_ZBUSY 0x2 /**< Zeroize Busy (Partition Only) */ +#define SCM_STATUS_SRS_CBUSY 0x3 /**< Cipher Busy */ +#define SCM_STATUS_SRS_ABUSY 0x4 /**< All Busy */ +#define SCM_STATUS_SRS_ZDONE 0x5 /**< Zeroize Done, Cipher Ready */ +#define SCM_STATUS_SRS_CDONE 0x6 /**< Cipher Done, Zeroize Ready */ +#define SCM_STATUS_SRS_ZDONE2 0x7 /**< Zeroize Done, Cipher Busy */ +#define SCM_STATUS_SRS_CDONE2 0x8 /**< Cipher Done, Zeroize Busy */ +#define SCM_STATUS_SRS_ADONE 0xD /**< All Done */ + +/* Format of the SCM VERSION ID REGISTER */ +#define SCM_VER_BPP_MASK 0xFF000000 /**< Bytes Per Partition Mask */ +#define SCM_VER_BPP_SHIFT 24 /**< Bytes Per Partition Shift */ +#define SCM_VER_BPCB_MASK 0x001F0000 /**< Bytes Per Cipher Block Mask */ +#define SCM_VER_BPCB_SHIFT 16 /**< Bytes Per Cipher Block Shift */ +#define SCM_VER_NP_MASK 0x0000F000 /**< Number of Partitions Mask */ +#define SCM_VER_NP_SHIFT 12 /**< Number of Partitions Shift */ +#define SCM_VER_MAJ_MASK 0x00000F00 /**< Major Version Mask */ +#define SCM_VER_MAJ_SHIFT 8 /**< Major Version Shift */ +#define SCM_VER_MIN_MASK 0x000000FF /**< Minor Version Mask */ +#define SCM_VER_MIN_SHIFT 0 /**< Minor Version Shift */ + +/**< SCC Hardware version supported by this driver */ +#define SCM_MAJOR_VERSION_2 2 + +/* Format of the SCM ERROR STATUS REGISTER */ +#define SCM_ERRSTAT_MID_MASK 0x00F00000 /**< Master ID Mask */ +#define SCM_ERRSTAT_MID_SHIFT 20 /**< Master ID Shift */ +#define SCM_ERRSTAT_ILM 0x00080000 /**< Illegal Master */ +#define SCM_ERRSTAT_SUP 0x00008000 /**< Supervisor Access */ +#define SCM_ERRSTAT_ERC_MASK 0x00000F00 /**< Error Code Mask */ +#define SCM_ERRSTAT_ERC_SHIFT 8 /**< Error Code Shift */ +#define SCM_ERRSTAT_SMS_MASK 0x000000F0 /**< Secure Monitor State Mask */ +#define SCM_ERRSTAT_SMS_SHIFT 4 /**< Secure Monitor State Shift */ +#define SCM_ERRSTAT_SRS_MASK 0x0000000F /**< Secure Ram State Mask */ +#define SCM_ERRSTAT_SRS_SHIFT 0 /**< Secure Ram State Shift */ + +/* SCM ERROR STATUS REGISTER ERROR CODES */ +#define SCM_ERCD_UNK_ADDR 0x1 /**< Unknown Address */ +#define SCM_ERCD_UNK_CMD 0x2 /**< Unknown Command */ +#define SCM_ERCD_READ_PERM 0x3 /**< Read Permission Error */ +#define SCM_ERCD_WRITE_PERM 0x4 /**< Write Permission Error */ +#define SCM_ERCD_DMA_ERROR 0x5 /**< DMA Error */ +#define SCM_ERCD_BLK_OVFL 0x6 /**< Encryption Block Length Overflow */ +#define SCM_ERCD_NO_KEY 0x7 /**< Key Not Engaged */ +#define SCM_ERCD_ZRZ_OVFL 0x8 /**< Zeroize Command Queue Overflow */ +#define SCM_ERCD_CPHR_OVFL 0x9 /**< Cipher Command Queue Overflow */ +#define SCM_ERCD_PROC_INTR 0xA /**< Process Interrupted */ +#define SCM_ERCD_WRNG_KEY 0xB /**< Wrong Key */ +#define SCM_ERCD_DEVICE_BUSY 0xC /**< Device Busy */ +#define SCM_ERCD_UNALGN_ADDR 0xD /**< DMA Unaligned Address */ + +/* Format of the CIPHER COMMAND REGISTER */ +#define SCM_CCMD_LENGTH_MASK 0xFFF00000 /**< Cipher Length Mask */ +#define SCM_CCMD_LENGTH_SHIFT 20 /**< Cipher Length Shift */ +#define SCM_CCMD_OFFSET_MASK 0x000FFF00 /**< Block Offset Mask */ +#define SCM_CCMD_OFFSET_SHIFT 8 /**< Block Offset Shift */ +#define SCM_CCMD_PART_MASK 0x000000F0 /**< Partition Number Mask */ +#define SCM_CCMD_PART_SHIFT 4 /**< Partition Number Shift */ +#define SCM_CCMD_CCMD_MASK 0x0000000F /**< Cipher Command Mask */ +#define SCM_CCMD_CCMD_SHIFT 0 /**< Cipher Command Shift */ + +/* Values for SCM_CCMD_CCMD field */ +#define SCM_CCMD_AES_DEC_ECB 1 /**< Decrypt without Chaining (ECB) */ +#define SCM_CCMD_AES_ENC_ECB 3 /**< Encrypt without Chaining (ECB) */ +#define SCM_CCMD_AES_DEC_CBC 5 /**< Decrypt with Chaining (CBC) */ +#define SCM_CCMD_AES_ENC_CBC 7 /**< Encrypt with Chaining (CBC) */ + +#define SCM_CCMD_AES 1 /**< Use AES Mode */ +#define SCM_CCMD_DEC 0 /**< Decrypt */ +#define SCM_CCMD_ENC 2 /**< Encrypt */ +#define SCM_CCMD_ECB 0 /**< Perform operation without chaining (ECB) */ +#define SCM_CCMD_CBC 4 /**< Perform operation with chaining (CBC) */ + +/* Format of the ZEROIZE COMMAND REGISTER */ +#define SCM_ZCMD_PART_MASK 0x000000F0 /**< Target Partition Mask */ +#define SCM_ZCMD_PART_SHIFT 4 /**< Target Partition Shift */ +#define SCM_ZCMD_CCMD_MASK 0x0000000F /**< Zeroize Command Mask */ +#define SCM_ZCMD_CCMD_SHIFT 0 /**< Zeroize Command Shift */ + +/* MASTER ACCESS PERMISSIONS REGISTER */ +/* Note that API users should use the FSL_PERM_ defines instead of these */ +/** SCM Access Permission: Do not zeroize/deallocate partition + on SMN Fail state */ +#define SCM_PERM_NO_ZEROIZE 0x10000000 +/** SCM Access Permission: Ignore Supervisor/User mode + in permission determination */ +#define SCM_PERM_HD_SUP_DISABLE 0x00000800 +/** SCM Access Permission: Allow Read Access to Host Domain */ +#define SCM_PERM_HD_READ 0x00000400 +/** SCM Access Permission: Allow Write Access to Host Domain */ +#define SCM_PERM_HD_WRITE 0x00000200 +/** SCM Access Permission: Allow Execute Access to Host Domain */ +#define SCM_PERM_HD_EXECUTE 0x00000100 +/** SCM Access Permission: Allow Read Access to Trusted Host Domain */ +#define SCM_PERM_TH_READ 0x00000040 +/** SCM Access Permission: Allow Write Access to Trusted Host Domain */ +#define SCM_PERM_TH_WRITE 0x00000020 +/** SCM Access Permission: Allow Read Access to Other/World Domain */ +#define SCM_PERM_OT_READ 0x00000004 +/** SCM Access Permission: Allow Write Access to Other/World Domain */ +#define SCM_PERM_OT_WRITE 0x00000002 +/** SCM Access Permission: Allow Execute Access to Other/World Domain */ +#define SCM_PERM_OT_EXECUTE 0x00000001 +/**< Valid bits that can be set in the Permissions register */ +#define SCM_PERM_MASK 0xC0000F67 + +/* Zeroize Command register definitions */ +#define ZCMD_DEALLOC_PART 3 /**< Deallocate Partition */ +#define Z_INT_EN 0x00000002 /**< Zero Interrupt Enable */ + +/** + * @defgroup scmpartitionownersregdefs SCM Partition Owners Register + */ +/** @addtogroup scmpartitionownersregdefs */ +/** @{ */ +/** Number of bits to shift partition number to get to its field. */ +#define SCM_POWN_SHIFT 2 +/** Mask for a field once the register has been shifted. */ +#define SCM_POWN_MASK 3 +/** Partition is free */ +#define SCM_POWN_PART_FREE 0 +/** Partition is unable to be allocated */ +#define SCM_POWN_PART_UNUSABLE 1 +/** Partition is owned by another master */ +#define SCM_POWN_PART_OTHER 2 +/** Partition is owned by this master */ +#define SCM_POWN_PART_OWNED 3 +/** @} */ + +/** + * @defgroup smnpartitionsengagedregdefs SCM Partitions Engaged Register + */ +/** @addtogroup smnpartitionsengagedregdefs */ +/** @{ */ +/** Number of bits to shift partition number to get to its field. */ +#define SCM_PENG_SHIFT 1 +/** Engaged value for a field once the register has been shifted. */ +#define SCM_PENG_ENGAGED 1 +/** @} */ + +/** Number of bytes between each subsequent SMID register */ +#define SCM_SMID_WIDTH 8 + +/** + * @defgroup smncommandregdefs SMN Command Register Definitions (SMN_COMMAND_REG) + */ +/** @addtogroup smncommandregdefs */ +/** @{ */ + +/** These bits are unimplemented or reserved */ +#define SMN_COMMAND_ZEROS_MASK 0xfffffff0 +#define SMN_COMMAND_CLEAR_INTERRUPT 0x8 /**< Clear SMN Interrupt */ +#define SMN_COMMAND_CLEAR_BIT_BANK 0x4 /**< Clear SMN Bit Bank */ +#define SMN_COMMAND_ENABLE_INTERRUPT 0x2 /**< Enable SMN Interrupts */ +#define SMN_COMMAND_SET_SOFTWARE_ALARM 0x1 /**< Set Software Alarm */ +/** @} */ + +/** + * @defgroup smntimercontroldefs SMN Timer Control Register definitions (SMN_TIMER_CONTROL) + */ +/** @addtogroup smntimercontroldefs */ +/** @{ */ +/** These bits are reserved or zero */ +#define SMN_TIMER_CTRL_ZEROS_MASK 0xfffffffc +/** Load the timer from #SMN_TIMER_IV_REG */ +#define SMN_TIMER_LOAD_TIMER 0x2 +/** Setting to zero stops the Timer */ +#define SMN_TIMER_STOP_MASK 0x1 +/** Setting this value starts the timer */ +#define SMN_TIMER_START_TIMER 0x1 +/** @} */ + +/** + * @defgroup scmchainmodedefs SCM_CHAINING_MODE_MASK - Bit definitions + */ +/** @addtogroup scmchainmodedefs */ +/** @{ */ +#define SCM_CBC_MODE 0x2 /**< Cipher block chaining */ +#define SCM_ECB_MODE 0x0 /**< Electronic codebook. */ +/** @} */ + +/* Bit definitions in the SCM_CIPHER_MODE_MASK */ +/** + * @defgroup scmciphermodedefs SCM_CIPHER_MODE_MASK - Bit definitions + */ +/** @addtogroup scmciphermodedefs */ +/** @{ */ +#define SCM_DECRYPT_MODE 0x1 /**< decrypt from black to red memory */ +#define SCM_ENCRYPT_MODE 0x0 /**< encrypt from red to black memory */ +/** @} */ + +/** + * @defgroup smndbgdetdefs SMN Debug Detector Status Register (SCM_DEBUG_DETECT_STAT) + */ +/** @addtogroup smndbgdetdefs */ +/** @{ */ +#define SMN_DBG_ZEROS_MASK 0xfffff000 /**< These bits are zero or reserved */ +#define SMN_DBG_D12 0x0800 /**< Error detected on Debug Port D12 */ +#define SMN_DBG_D11 0x0400 /**< Error detected on Debug Port D11 */ +#define SMN_DBG_D10 0x0200 /**< Error detected on Debug Port D10 */ +#define SMN_DBG_D9 0x0100 /**< Error detected on Debug Port D9 */ +#define SMN_DBG_D8 0x0080 /**< Error detected on Debug Port D8 */ +#define SMN_DBG_D7 0x0040 /**< Error detected on Debug Port D7 */ +#define SMN_DBG_D6 0x0020 /**< Error detected on Debug Port D6 */ +#define SMN_DBG_D5 0x0010 /**< Error detected on Debug Port D5 */ +#define SMN_DBG_D4 0x0008 /**< Error detected on Debug Port D4 */ +#define SMN_DBG_D3 0x0004 /**< Error detected on Debug Port D3 */ +#define SMN_DBG_D2 0x0002 /**< Error detected on Debug Port D2 */ +#define SMN_DBG_D1 0x0001 /**< Error detected on Debug Port D1 */ +/** @} */ + +/** Mask for the usable bits of the Sequence Start Register + (#SMN_SEQ_START_REG) */ +#define SMN_SEQUENCE_START_MASK 0x0000ffff + +/** Mask for the usable bits of the Sequence End Register + (#SMN_SEQ_END_REG) */ +#define SMN_SEQUENCE_END_MASK 0x0000ffff + +/** Mask for the usable bits of the Sequence Check Register + (#SMN_SEQ_CHECK_REG) */ +#define SMN_SEQUENCE_CHECK_MASK 0x0000ffff + +/** Mask for the usable bits of the Bit Counter Register + (#SMN_BB_CNT_REG) */ +#define SMN_BIT_COUNT_MASK 0x000007ff + +/** Mask for the usable bits of the Bit Bank Increment Size Register + (#SMN_BB_INC_REG) */ +#define SMN_BITBANK_INC_SIZE_MASK 0x000007ff + +/** Mask for the usable bits of the Bit Bank Decrement Register + (#SMN_BB_DEC_REG) */ +#define SMN_BITBANK_DECREMENT_MASK 0x000007ff + +/** Mask for the usable bits of the Compare Size Register + (#SMN_COMPARE_REG) */ +#define SMN_COMPARE_SIZE_MASK 0x0000003f + +/*! @} */ + +#endif /* SCC_DRIVER_H */ diff --git a/include/linux/mxc_scc_driver.h b/include/linux/mxc_scc_driver.h new file mode 100644 index 000000000000..9d3de2f19e8d --- /dev/null +++ b/include/linux/mxc_scc_driver.h @@ -0,0 +1,1031 @@ + +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __ASM_ARCH_MXC_SCC_DRIVER_H__ +#define __ASM_ARCH_MXC_SCC_DRIVER_H__ + +/* Start marker for C++ compilers */ +#ifdef __cplusplus +extern "C" { +#endif + +/* + * NAMING CONVENTIONS + * ================== + * (A note to maintainers and other interested parties) + * + * Use scc_ or SCC_ prefix for 'high-level' interface routines and the types + * passed to those routines. Try to avoid #defines in these interfaces. + * + * Use SMN_ or SCM_ prefix for the #defines used with scc_read_register() and + * scc_write_register, or values passed/retrieved from those routines. + */ + +/*! + * @defgroup MXCSCC SCC Driver + * + * @ingroup MXCSECDRVRS + */ + +/*! + * @file arch-mxc/mxc_scc_driver.h + * + * @brief (Header file to use the SCC driver.) + * + * The SCC driver will only be available to other kernel modules. That is, + * there will be no node file in /dev, no way for a user-mode program to access + * the driver, no way for a user program to access the device directly. + * + * With the exception of #scc_monitor_security_failure(), all routines are + * 'synchronous', i.e. they will not return to their caller until the requested + * action is complete, or fails to complete. Some of these functions could + * take quite a while to perform, depending upon the request. + * + * Routines are provided to: + * @li encrypt or decrypt secrets - #scc_crypt() + * @li trigger a security-violation alarm - #scc_set_sw_alarm() + * @li get configuration and version information - #scc_get_configuration() + * @li zeroize memory - #scc_zeroize_memories() + * @li Work on wrapped and stored secret values: #scc_alloc_slot(), + * #scc_dealloc_slot(), scc_load_slot(), #scc_decrypt_slot(), + * #scc_encrypt_slot(), #scc_get_slot_info() + + * @li monitor the Security Failure alarm - #scc_monitor_security_failure() + * @li stop monitoring Security Failure alarm - + * #scc_stop_monitoring_security_failure() + * @li write registers of the SCC - #scc_write_register() + * @li read registers of the SCC - #scc_read_register() + * + * The driver does not allow "storage" of data in either the Red or Black + * memories. Any decrypted secret is returned to the user, and if the user + * wants to use it at a later point, the encrypted form must again be passed + * to the driver, and it must be decrypted again. + * + * The SCC encrypts and decrypts using Triple DES with an internally stored + * key. When the SCC is in Secure mode, it uses its secret, unique-per-chip + * key. When it is in Non-Secure mode, it uses a default key. This ensures + * that secrets stay secret if the SCC is not in Secure mode. + * + * Not all functions that could be provided in a 'high level' manner have been + * implemented. Among the missing are interfaces to the ASC/AIC components and + * the timer functions. These and other features must be accessed through + * #scc_read_register() and #scc_write_register(), using the @c \#define values + * provided. + * + * Here is a glossary of acronyms used in the SCC driver documentation: + * - CBC - Cipher Block Chaining. A method of performing a block cipher. + * Each block is encrypted using some part of the result of the previous + * block's encryption. It needs an 'initialization vector' to seed the + * operation. + * - ECB - Electronic Code Book. A method of performing a block cipher. + * With a given key, a given block will always encrypt to the same value. + * - DES - Data Encryption Standard. (8-byte) Block cipher algorithm which + * uses 56-bit keys. In SCC, this key is constant and unique to the device. + * SCC uses the "triple DES" form of this algorithm. + * - AIC - Algorithm Integrity Checker. + * - ASC - Algorithm Sequence Checker. + * - SMN - Security Monitor. The part of the SCC responsible for monitoring + * for security problems and notifying the CPU and other PISA components. + * - SCM - Secure Memory. The part of the SCC which handles the cryptography. + * - SCC - Security Controller. Central security mechanism for PISA. + * - PISA - Platform-Independent Security Architecture. + */ + +/* Temporarily define compile-time flags to make Doxygen happy. */ +#ifdef DOXYGEN_HACK +/*! @defgroup scccompileflags SCC Driver compile-time flags + * + * These preprocessor flags should be set, if desired, in a makefile so + * that they show up on the compiler command line. + */ +/*! @addtogroup scccompileflags */ + +/*! @{ */ +/*! + * Compile-time flag to change @ref smnregs and @ref scmregs + * offset values for the SCC's implementation on the MX.21 board. + * + * This must also be set properly for any code which calls the + * scc_read_register() or scc_write_register() functions or references the + * register offsets. + */ +#define TAHITI +/*! @} */ +#undef TAHITI + +#endif /* DOXYGEN_HACK */ + +/*! Major Version of the driver. Used for + scc_configuration->driver_major_version */ +#define SCC_DRIVER_MAJOR_VERSION_1 1 +/*! Old Minor Version of the driver. */ +#define SCC_DRIVER_MINOR_VERSION_0 0 +/*! Old Minor Version of the driver. */ +#define SCC_DRIVER_MINOR_VERSION_4 4 +/*! Old Minor Version of the driver. */ +#define SCC_DRIVER_MINOR_VERSION_5 5 +/*! Old Minor Version of the driver. */ +#define SCC_DRIVER_MINOR_VERSION_6 6 +/*! Minor Version of the driver. Used for + scc_configuration->driver_minor_version */ +#define SCC_DRIVER_MINOR_VERSION_8 8 + + +/*! + * @typedef scc_return_t + */ +/*! Common status return values from SCC driver functions. */ + typedef enum scc_return_t { + SCC_RET_OK = 0, /*!< Function succeeded */ + SCC_RET_FAIL, /*!< Non-specific failure */ + SCC_RET_VERIFICATION_FAILED, /*!< Decrypt validation failed */ + SCC_RET_TOO_MANY_FUNCTIONS, /*!< At maximum registered functions */ + SCC_RET_BUSY, /*!< SCC is busy and cannot handle request */ + SCC_RET_INSUFFICIENT_SPACE, /*!< Encryption or decryption failed because + @c count_out_bytes says that @c data_out is + too small to hold the value. */ + } scc_return_t; + +/*! + * Configuration information about SCC and the driver. + * + * This struct/typedef contains information from the SCC and the driver to + * allow the user of the driver to determine the size of the SCC's memories and + * the version of the SCC and the driver. + */ + typedef struct scc_config_t { + int driver_major_version; /*!< Major version of the SCC driver code */ + int driver_minor_version; /*!< Minor version of the SCC driver code */ + int scm_version; /*!< Version from SCM Configuration register */ + int smn_version; /*!< Version from SMN Status register */ + int block_size_bytes; /*!< Number of bytes per block of RAM; also + block size of the crypto algorithm. */ + int black_ram_size_blocks; /*!< Number of blocks of Black RAM */ + int red_ram_size_blocks; /*!< Number of blocks of Red RAM */ + } scc_config_t; + +/*! + * @typedef scc_enc_dec_t + */ +/*! + * Determine whether SCC will run its cryptographic + * function as an encryption or decryption. Used as an argument to + * #scc_crypt(). + */ + typedef enum scc_enc_dec_t { + SCC_ENCRYPT, /*!< Encrypt (from Red to Black) */ + SCC_DECRYPT /*!< Decrypt (from Black to Red) */ + } scc_enc_dec_t; + +/* + * @typedef scc_crypto_mode_t + */ +/*! + * Determine whether SCC will run its cryptographic function in ECB (electronic + * codebook) or CBC (cipher-block chaining) mode. Used as an argument to + * #scc_crypt(). + */ + typedef enum scc_crypto_mode_t { + SCC_ECB_MODE, /*!< Electronic Codebook Mode */ + SCC_CBC_MODE /*!< Cipher Block Chaining Mode */ + } scc_crypto_mode_t; + +/*! + * @typedef scc_verify_t + */ +/*! + * Tell the driver whether it is responsible for verifying the integrity of a + * secret. During an encryption, using other than #SCC_VERIFY_MODE_NONE will + * cause a check value to be generated and appended to the plaintext before + * encryption. During decryption, the check value will be verified after + * decryption, and then stripped from the message. + */ + typedef enum scc_verify_t { + /*! No verification value added or checked. Input plaintext data must be + * be a multiple of the blocksize (#scc_get_configuration()). */ + SCC_VERIFY_MODE_NONE, + /*! Driver will generate/validate a 2-byte CCITT CRC. Input plaintext will + be padded to a multiple of the blocksize, adding 3-10 bytes to the + resulting output ciphertext. Upon decryption, this padding will be + stripped, and the CRC will be verified. */ + SCC_VERIFY_MODE_CCITT_CRC + } scc_verify_t; + +/*! + * Determine if the given credentials match that of the key slot. + * + * @param[in] owner_id A value which will control access to the slot. + * @param[in] slot Key Slot to query + * @param[in] access_len Length of the key + * + * @return 0 on success, non-zero on failure. See #scc_return_t. + */ + scc_return_t + scc_verify_slot_access(uint64_t owner_id, uint32_t slot, + uint32_t access_len); + +/*! + * Retrieve configuration information from the SCC. + * + * This function always succeeds. + * + * @return A pointer to the configuration information. This is a pointer to + * static memory and must not be freed. The values never change, and + * the return value will never be null. + */ + extern scc_config_t *scc_get_configuration(void); + +/*! + * Zeroize Red and Black memories of the SCC. This will start the Zeroizing + * process. The routine will return when the memories have zeroized or failed + * to do so. The driver will poll waiting for this to occur, so this + * routine must not be called from interrupt level. Some future version of + * driver may elect instead to sleep. + * + * @return 0 or error if initialization fails. + */ + extern scc_return_t scc_zeroize_memories(void); + +/*! + * Perform a Triple DES encryption or decryption operation. + * + * This routine will cause the SCM to perform an encryption or decryption with + * its internal key. If the SCC's #SMN_STATUS register shows that the SCC is + * in #SMN_STATE_SECURE, then the Secret Key will be used. If it is + * #SMN_STATE_NON_SECURE (or health check), then the Default Key will be used. + * + * This function will perform in a variety of ways, depending upon the values + * of @c direction, @c crypto_mode, and @c check_mode. If + * #SCC_VERIFY_MODE_CCITT_CRC mode is requested, upon successful completion, + * the @c count_in_bytes will be different from the returned value of @c + * count_out_bytes. This is because the two-byte CRC and some amount of + * padding (at least one byte) will either be added or stripped. + * + * This function will not return until the SCC has performed the operation (or + * reported failure to do so). It must therefore not be called from interrupt + * level. In the current version, it will poll the SCC for completion. In + * future versions, it may sleep. + * + * @param[in] count_in_bytes The number of bytes to move through the crypto + * function. Must be greater than zero. + * + * @param[in] data_in Pointer to the array of bytes to be used as input + * to the crypto function. + * + * @param[in] init_vector Pointer to the block-sized (8 byte) array of + * bytes which form the initialization vector for + * this operation. A non-null value is required + * when @c crypto_mode has the value #SCC_CBC_MODE; + * the value is ignored in #SCC_ECB_MODE. + * + * @param[in] direction Direct the driver to perform encryption or + * decryption. + * + * @param[in] crypto_mode Run the crypto function in ECB or CBC mode. + * + * @param[in] check_mode During encryption, generate and append a check + * value to the plaintext and pad the resulting + * data. During decryption, validate the plaintext + * with that check value and remove the padding. + * + * @param[in,out] count_out_bytes On input, the number of bytes available for + * copying to @c data_out. On return, the number of + * bytes copied to @c data_out. + * + * @param[out] data_out Pointer to the array of bytes that are where the + * output of the crypto function are to be placed. + * For encryption, this must be able to hold a + * longer ciphertext than the plaintext message at + * @c data_in. The driver will append a 'pad' of + * 1-8 bytes to the message, and if @c check_mode is + * used, additional bytes may be added, the number + * depending upon the type of check being requested. + * + * @return 0 on success, non-zero on failure. See #scc_return_t. + * + * @internal + * This function will verify SCC state and the functions parameters. It will + * acquire the crypto lock, and set up some SCC registers and variables common + * to encryption and decryption. A rough check will be made to verify that + * enough space is available in @c count_out_bytes. Upon success, either the + * #scc_encrypt or #scc_decrypt routine will be called to do the actual work. + * The crypto lock will then be released. + */ +extern scc_return_t scc_crypt(unsigned long count_in_bytes, + const uint8_t * data_in, + const uint8_t * init_vector, + scc_enc_dec_t direction, + scc_crypto_mode_t crypto_mode, + scc_verify_t check_mode, + uint8_t * data_out, + unsigned long *count_out_bytes); + + +/*! + * Allocate a key slot for a stored key (or other stored value). + * + * This feature is to allow decrypted secret values to be kept in RED RAM. + * This can all visibility of the data only by Sahara. + * + * @param value_size_bytes Size, in bytes, of RED key/value. Currently only + * a size up to 32 bytes is supported. + * + * @param owner_id A value which will control access to the slot. + * It must be passed into to any subsequent calls to + * use the assigned slot. + * + * @param[out] slot The slot number for the key. + * + * @return 0 on success, non-zero on failure. See #scc_return_t. + */ + extern scc_return_t scc_alloc_slot(uint32_t value_size_bytes, + uint64_t owner_id, uint32_t * slot); + +/*! + * Deallocate the key slot of a stored key or secret value. + * + * @param owner_id The id which owns the @c slot. + * + * @param slot The slot number for the key. + + * @return 0 on success, non-zero on failure. See #scc_return_t. + */ + extern scc_return_t scc_dealloc_slot(uint64_t owner_id, uint32_t slot); + +/*! + * Load a value into a slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param key_data Data to load into the slot + * @param key_length Length, in bytes, of @c key_data to copy to SCC. + * + * @return SCC_RET_OK on success. SCC_RET_FAIL will be returned if slot + * specified cannot be accessed for any reason, or SCC_RET_INSUFFICIENT_SPACE + * if @c key_length exceeds the size of the slot. + */ + extern scc_return_t scc_load_slot(uint64_t owner_id, uint32_t slot, + const uint8_t * key_data, + uint32_t key_length); +/*! + * Read a value from a slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param key_length Length, in bytes, of @c key_data to copy from SCC. + * @param key_data Location to write the key + * + * @return SCC_RET_OK on success. SCC_RET_FAIL will be returned if slot + * specified cannot be accessed for any reason, or SCC_RET_INSUFFICIENT_SPACE + * if @c key_length exceeds the size of the slot. + */ + extern scc_return_t scc_read_slot(uint64_t owner_id, uint32_t slot, + uint32_t key_length, + uint8_t * key_data); + +/*! + * Allocate a key slot to fit the requested size. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param length Length, in bytes, of @c black_data + * @param black_data Location to store result of encrypting RED data in slot + * + * @return SCC_RET_OK on success, SCC_RET_FAIL if slot specified cannot be + * accessed for any reason. + */ + extern scc_return_t scc_encrypt_slot(uint64_t owner_id, uint32_t slot, + uint32_t length, + uint8_t * black_data); + +/*! + * Decrypt some black data and leave result in the slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param length Length, in bytes, of @c black_data + * @param black_data Location of data to dencrypt and store in slot + * + * @return SCC_RET_OK on success, SCC_RET_FAIL if slot specified cannot be + * accessed for any reason. + */ + extern scc_return_t scc_decrypt_slot(uint64_t owner_id, uint32_t slot, + uint32_t length, + const uint8_t * black_data); + +/*! + * Get attributes of data in RED slot. + * + * @param owner_id The id which owns the @c slot. + * + * @param slot The slot number for the key. + * + * @param[out] address Physical address of RED value. + * + * @param[out] value_size_bytes Length, in bytes, of RED value, + * or NULL if unneeded.. + * + * @param[out] slot_size_bytes Length, in bytes, of slot size, + * or NULL if unneeded.. + * + * @return 0 on success, non-zero on failure. See #scc_return_t. + */ + extern scc_return_t scc_get_slot_info(uint64_t owner_id, uint32_t slot, + uint32_t *address, + uint32_t *value_size_bytes, + uint32_t *slot_size_bytes); + +/*! + * Signal a software alarm to the SCC. This will take the SCC and other PISA + * parts out of Secure mode and into Security Failure mode. The SCC will stay + * in failed mode until a reboot. + * + * @internal + * If the SCC is not already in fail state, simply write the + * #SMN_COMMAND_SET_SOFTWARE_ALARM bit in #SMN_COMMAND. Since there is no + * reason to wait for the interrupt to bounce back, simply act as though + * one did. + */ + extern void scc_set_sw_alarm(void); + +/*! + * This routine will register a function to be called should a Security Failure + * be signalled by the SCC (Security Monitor). + * + * The callback function may be called from interrupt level, it may be called + * from some process' task. It should therefore not take a long time to + * perform its operation, and it may not sleep. + * + * @param callback_func Function pointer to routine which will receive + * notification of the security failure. + * @return 0 if function was successfully registered, non-zero on + * failure. See #scc_return_t. + * + * @internal + * There is a fixed global static array which keeps track of the requests to + * monitor the failure. + * + * Add @c callback_func to the first empty slot in #scc_callbacks[]. If there + * is no room, return #SCC_RET_TOO_MANY_FUNCTIONS. + */ + extern scc_return_t scc_monitor_security_failure(void + callback_func(void)); + +/*! + * This routine will deregister a function previously registered with + * #scc_monitor_security_failure(). + * + * @param callback_func Function pointer to routine previously registered with + * #scc_stop_monitoring_security_failure(). + */ + extern void scc_stop_monitoring_security_failure(void + callback_func(void)); + +/*! + * Read value from an SCC register. + * The offset will be checked for validity (range) as well as whether it is + * accessible (e.g. not busy, not in failed state) at the time of the call. + * + * @param[in] register_offset The (byte) offset within the SCC block + * of the register to be queried. See + * @ref scmregs and @ref smnregs. + * @param[out] value Pointer to where value from the register + * should be placed. + * @return 0 if OK, non-zero on error. See #scc_return_t. + * + * @internal + * Verify that the register_offset is a) valid, b) refers to a readable + * register, and c) the SCC is in a state which would allow a read of this + * register. + */ + extern scc_return_t scc_read_register(int register_offset, + uint32_t * value); + +/*! + * Write a new value into an SCC register. + * The offset will be checked for validity (range) as well as whether it is + * accessible (e.g. not busy, not in failed state) at the time of the call. + * + * @param[in] register_offset The (byte) offset within the SCC block + * of the register to be modified. See + * @ref scmregs and @ref smnregs. + * @param[in] value The value to store into the register. + * @return 0 if OK, non-zero on error. See #scc_return_t. + * + * @internal + * Verify that the register_offset is a) valid, b) refers to a writeable + * register, and c) the SCC is in a state which would allow a write to this + * register. + */ + extern scc_return_t scc_write_register(int register_offset, + uint32_t value); + +/* + * NOTE TO MAINTAINERS + * + * All of the doxygen comments for the register offset values are in this the + * following comment section. Any changes to register names or definitions + * must be reflected in this section and in both the TAHITI and non-TAHITI + *version of the memory map. + */ + +/*! + * @defgroup scmregs SCM Registers + * + * These values are offsets into the SCC for the Secure Memory + * (SCM) registers. They are used in the @c register_offset parameter of + * #scc_read_register() and #scc_write_register(). + */ +/*! @addtogroup scmregs */ +/*! @{ */ +/*! @def SCM_RED_START + * Starting block offset in red memory for cipher function. */ + +/*! @def SCM_BLACK_START + * Starting block offset in black memory for cipher function. */ + +/*! @def SCM_LENGTH + * Number of blocks to process during cipher function */ + +/*! @def SCM_CONTROL + * SCM Control register. + * See @ref scmcontrolregdefs "SCM Control Register definitions" for details. + */ + +/*! @def SCM_STATUS + * SCM Status register. + * See @ref scmstatusregdefs "SCM Status Register Definitions" for details. + */ + +/*! @def SCM_ERROR_STATUS + * SCM Error Status Register. + * See @ref scmerrstatdefs "SCM Error Status Register definitions" for + * details. */ + +/*! @def SCM_INTERRUPT_CTRL + * SCM Interrupt Control Register. + * See @ref scminterruptcontroldefs "SCM Interrupt Control Register definitions" + * for details. + */ + +/*! @def SCM_CONFIGURATION + * SCM Configuration Register. + * See @ref scmconfigdefs "SCM Configuration Register Definitions" for + * details. + */ + +/*! @def SCM_INIT_VECTOR_0 + * Upper Half of the Initialization Vector */ + +/*! @def SCM_INIT_VECTOR_1 + * Lower Half of the Initialization Vector */ + +/*! @def SCM_RED_MEMORY + * Starting location of first block of Red memory */ + +/*! @def SCM_BLACK_MEMORY + * Starting location of first block of Black memory */ + + /*! @} *//* end of SCM group */ + +/*! + * @defgroup smnregs SMN Registers + * + * These values are offsets into the SCC for the Security Monitor + * (SMN) registers. They are used in the @c register_offset parameter of the + * #scc_read_register() and #scc_write_register(). + */ +/*! @addtogroup smnregs */ +/*! @{ */ +/*! @def SMN_STATUS + * Status register for SMN. + * See @ref smnstatusregdefs "SMN Status Register definitions" for further + * information. + */ + +/*! @def SMN_COMMAND + * Command register for SMN. See + * @ref smncommandregdefs "Command Register Definitions" for further + * information. + */ + +/*! @def SMN_SEQUENCE_START + * Sequence Start register for ASC. See #SMN_SEQUENCE_START_MASK + */ + +/*! @def SMN_SEQUENCE_END + * Sequence End register for ASC. See #SMN_SEQUENCE_CHECK_MASK + */ + +/*! @def SMN_SEQUENCE_CHECK + * Sequence Check register for ASC. See #SMN_SEQUENCE_END_MASK + */ + +/*! @def SMN_BIT_COUNT + * Bit Bank Repository for AIC. See #SMN_BIT_COUNT_MASK + */ + +/*! @def SMN_BITBANK_INC_SIZE + * Bit Bank Increment Size for AIC. See #SMN_BITBANK_INC_SIZE_MASK + */ + +/*! @def SMN_BITBANK_DECREMENT + * Bit Bank Decrement for AIC. See #SMN_BITBANK_DECREMENT_MASK + */ + +/*! @def SMN_COMPARE_SIZE + * Compare Size register for Plaintext/Ciphertext checker. See + * #SMN_COMPARE_SIZE_MASK */ + +/*! @def SMN_PLAINTEXT_CHECK + * Plaintext Check register for Plaintext/Ciphertext checker. + */ + +/*! @def SMN_CIPHERTEXT_CHECK + * Ciphertext Check register for Plaintext/Ciphertext checker. + */ + +/*! @def SMN_TIMER_IV + * Timer Initial Value register + */ + +/*! @def SMN_TIMER_CONTROL + * Timer Control register. + * See @ref smntimercontroldefs "SMN Timer Control Register definitions". + */ + +/*! @def SMN_DEBUG_DETECT_STAT + * Debug Detector Status Register + * See @ref smndbgdetdefs "SMN Debug Detector Status Register"for definitions. + */ + +/*! @def SMN_TIMER + * Current value of the Timer Register + */ + + /*! @} *//* end of SMN group */ + +/* + * SCC MEMORY MAP + * + */ + +/* SCM registers */ +#define SCM_RED_START 0x00000000 /* read/write */ +#define SCM_BLACK_START 0x00000004 /* read/write */ +#define SCM_LENGTH 0x00000008 /* read/write */ +#define SCM_CONTROL 0x0000000C /* read/write */ +#define SCM_STATUS 0x00000010 /* read only */ +#define SCM_ERROR_STATUS 0x00000014 /* read/write */ +#define SCM_INTERRUPT_CTRL 0x00000018 /* read/write */ +#define SCM_CONFIGURATION 0x0000001C /* read only */ +#define SCM_INIT_VECTOR_0 0x00000020 /* read/write */ +#define SCM_INIT_VECTOR_1 0x00000024 /* read/write */ +#define SCM_RED_MEMORY 0x00000400 /* read/write */ +#define SCM_BLACK_MEMORY 0x00000800 /* read/write */ + +/* SMN Registers */ +#define SMN_STATUS 0x00001000 /* read/write */ +#define SMN_COMMAND 0x00001004 /* read/write */ +#define SMN_SEQUENCE_START 0x00001008 /* read/write */ +#define SMN_SEQUENCE_END 0x0000100C /* read/write */ +#define SMN_SEQUENCE_CHECK 0x00001010 /* read/write */ +#define SMN_BIT_COUNT 0x00001014 /* read only */ +#define SMN_BITBANK_INC_SIZE 0x00001018 /* read/write */ +#define SMN_BITBANK_DECREMENT 0x0000101C /* write only */ +#define SMN_COMPARE_SIZE 0x00001020 /* read/write */ +#define SMN_PLAINTEXT_CHECK 0x00001024 /* read/write */ +#define SMN_CIPHERTEXT_CHECK 0x00001028 /* read/write */ +#define SMN_TIMER_IV 0x0000102C /* read/write */ +#define SMN_TIMER_CONTROL 0x00001030 /* read/write */ +#define SMN_DEBUG_DETECT_STAT 0x00001034 /* read/write */ +#define SMN_TIMER 0x00001038 /* read only */ + +/*! Total address space of the SCC, in bytes */ +#define SCC_ADDRESS_RANGE 0x103c + +/*! + * @defgroup smnstatusregdefs SMN Status Register definitions (SMN_STATUS) + */ +/*! @addtogroup smnstatusregdefs */ +/*! @{ */ +/*! SMN version id. */ +#define SMN_STATUS_VERSION_ID_MASK 0xfc000000 +/*! number of bits to shift #SMN_STATUS_VERSION_ID_MASK to get it to LSB */ +#define SMN_STATUS_VERSION_ID_SHIFT 26 +/*! Cacheable access to SMN attempted. */ +#define SMN_STATUS_CACHEABLE_ACCESS 0x02000000 +/*! Illegal bus master access attempted. */ +#define SMN_STATUS_ILLEGAL_MASTER 0x01000000 +/*! Scan mode entered/exited since last reset. */ +#define SMN_STATUS_SCAN_EXIT 0x00800000 +/*! Unaligned access attempted. */ +#define SMN_STATUS_UNALIGNED_ACCESS 0x00400000 +/*! Bad byte offset access attempted. */ +#define SMN_STATUS_BYTE_ACCESS 0x00200000 +/*! Illegal address access attempted. */ +#define SMN_STATUS_ILLEGAL_ADDRESS 0x00100000 +/*! User access attempted. */ +#define SMN_STATUS_USER_ACCESS 0x00080000 +/*! SCM is using DEFAULT key. */ +#define SMN_STATUS_DEFAULT_KEY 0x00040000 +/*! SCM detects weak or bad key. */ +#define SMN_STATUS_BAD_KEY 0x00020000 +/*! Illegal access to SCM detected. */ +#define SMN_STATUS_ILLEGAL_ACCESS 0x00010000 +/*! Internal error detected in SCM. */ +#define SMN_STATUS_SCM_ERROR 0x00008000 +/*! SMN has an outstanding interrupt. */ +#define SMN_STATUS_SMN_STATUS_IRQ 0x00004000 +/*! Software Alarm was triggered. */ +#define SMN_STATUS_SOFTWARE_ALARM 0x00002000 +/*! Timer has expired. */ +#define SMN_STATUS_TIMER_ERROR 0x00001000 +/*! Plaintext/Ciphertext compare failed. */ +#define SMN_STATUS_PC_ERROR 0x00000800 +/*! Bit Bank detected overflow or underflow */ +#define SMN_STATUS_BITBANK_ERROR 0x00000400 +/*! Algorithm Sequence Check failed. */ +#define SMN_STATUS_ASC_ERROR 0x00000200 +/*! Security Policy Block detected error. */ +#define SMN_STATUS_SECURITY_POLICY_ERROR 0x00000100 +/*! At least one Debug signal is active. */ +#define SMN_STATUS_DEBUG_ACTIVE 0x00000080 +/*! SCM failed to zeroize its memory. */ +#define SMN_STATUS_ZEROIZE_FAIL 0x00000040 +/*! Processor booted from internal ROM. */ +#define SMN_STATUS_INTERNAL_BOOT 0x00000020 +/*! SMN's internal state. */ +#define SMN_STATUS_STATE_MASK 0x0000001F +/*! Number of bits to shift #SMN_STATUS_STATE_MASK to get it to LSB. */ +#define SMN_STATUS_STATE_SHIFT 0 +/*! @} */ + +/*! + * @defgroup sccscmstates SMN Model Secure State Controller States (SMN_STATE_MASK) + */ +/*! @addtogroup sccscmstates */ +/*! @{ */ +/*! This is the first state of the SMN after power-on reset */ +#define SMN_STATE_START 0x0 +/*! The SMN is zeroizing its RAM during reset */ +#define SMN_STATE_ZEROIZE_RAM 0x5 +/*! SMN has passed internal checks, and is waiting for Software check-in */ +#define SMN_STATE_HEALTH_CHECK 0x6 +/*! Fatal Security Violation. SMN is locked, SCM is inoperative. */ +#define SMN_STATE_FAIL 0x9 +/*! SCC is in secure state. SCM is using secret key. */ +#define SMN_STATE_SECURE 0xA +/*! Due to non-fatal error, device is not secure. SCM is using default key. */ +#define SMN_STATE_NON_SECURE 0xC +/*! @} */ + +/*! + * @defgroup scmconfigdefs SCM Configuration Register definitions (SCM_CONFIGURATION) + **/ +/*! @addtogroup scmconfigdefs */ +/*! @{ */ +/*! Version number of the Secure Memory. */ +#define SCM_CFG_VERSION_ID_MASK 0xf8000000 +/*! Number of bits to shift #SCM_CFG_VERSION_ID_MASK to get it to LSB. */ +#define SCM_CFG_VERSION_ID_SHIFT 27 +/*! Version one value for SCC configuration */ +#define SCM_VERSION_1 1 +/*! Size, in blocks, of Red memory. */ +#define SCM_CFG_BLACK_SIZE_MASK 0x07fe0000 +/*! Number of bits to shift #SCM_CFG_BLACK_SIZE_MASK to get it to LSB. */ +#define SCM_CFG_BLACK_SIZE_SHIFT 17 +/*! Size, in blocks, of Black memory. */ +#define SCM_CFG_RED_SIZE_MASK 0x0001ff80 +/*! Number of bits to shift #SCM_CFG_RED_SIZE_MASK to get it to LSB. */ +#define SCM_CFG_RED_SIZE_SHIFT 7 +/*! Number of bytes per block. */ +#define SCM_CFG_BLOCK_SIZE_MASK 0x0000007f +/*! Number of bits to shift #SCM_CFG_BLOCK_SIZE_MASK to get it to LSB. */ +#define SCM_CFG_BLOCK_SIZE_SHIFT 0 +/*! @} */ + +/*! + * @defgroup smncommandregdefs SMN Command Register Definitions (SMN_COMMAND) + */ +/*! @addtogroup smncommandregdefs */ +/*! @{ */ +#define SMN_COMMAND_ZEROS_MASK 0xffffff70 /*!< These bits are unimplemented + or reserved */ +#define SMN_COMMAND_TAMPER_LOCK 0x10 /*!< Lock Tamper Detect Bit */ +#define SMN_COMMAND_CLEAR_INTERRUPT 0x8 /*!< Clear SMN Interrupt */ +#define SMN_COMMAND_CLEAR_BIT_BANK 0x4 /*!< Clear SMN Bit Bank */ +#define SMN_COMMAND_ENABLE_INTERRUPT 0x2 /*!< Enable SMN Interrupts */ +#define SMN_COMMAND_SET_SOFTWARE_ALARM 0x1 /*!< Set Software Alarm */ +/*! @} */ + +/*! + * @defgroup smntimercontroldefs SMN Timer Control Register definitions (SMN_TIMER_CONTROL) + */ +/*! @addtogroup smntimercontroldefs */ +/*! @{ */ +/*! These bits are reserved or zero */ +#define SMN_TIMER_CTRL_ZEROS_MASK 0xfffffffc +/*! Load the timer from #SMN_TIMER_IV */ +#define SMN_TIMER_LOAD_TIMER 0x2 +/*! Setting to zero stops the Timer */ +#define SMN_TIMER_STOP_MASK 0x1 +/*! Setting this value starts the timer */ +#define SMN_TIMER_START_TIMER 0x1 +/*! @} */ + +/*! + * @defgroup scminterruptcontroldefs SCM Interrupt Control Register definitions (SCM_INTERRUPT_CTRL) + * + * These are the bit definitions for the #SCM_INTERRUPT_CTRL register. + */ +/*! @addtogroup scminterruptcontroldefs */ +/*! @{ */ +/*! Clear SCM memory */ +#define SCM_INTERRUPT_CTRL_ZEROIZE_MEMORY 0x4 +/*! Clear outstanding SCM interrupt */ +#define SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT 0x2 +/*! Inhibit SCM interrupts */ +#define SCM_INTERRUPT_CTRL_MASK_INTERRUPTS 0x1 +/*! @} */ + +/*! + * @defgroup scmcontrolregdefs SCM Control Register definitions (SCM_CONTROL). + * These values are used with the #SCM_CONTROL register. + */ +/*! @addtogroup scmcontrolregdefs */ +/*! @{ */ +/*! These bits are zero or reserved */ +#define SCM_CONTROL_ZEROS_MASK 0xfffffff8 +/*! Setting this will start encrypt/decrypt */ +#define SCM_CONTROL_START_CIPHER 0x04 +/*! CBC/ECB flag. + * See @ref scmchainmodedefs "Chaining Mode bit definitions." + */ +#define SCM_CONTROL_CHAINING_MODE_MASK 0x02 +/*! Encrypt/decrypt choice. + * See @ref scmciphermodedefs "Cipher Mode bit definitions." */ +#define SCM_CONTROL_CIPHER_MODE_MASK 0x01 +/*! @} */ + +/*! + * @defgroup scmchainmodedefs SCM_CHAINING_MODE_MASK - Bit definitions + */ +/*! @addtogroup scmchainmodedefs */ +/*! @{ */ +#define SCM_CBC_MODE 0x2 /*!< Cipher block chaining */ +#define SCM_ECB_MODE 0x0 /*!< Electronic codebook. */ +/*! @} */ + +/* Bit definitions in the SCM_CIPHER_MODE_MASK */ +/*! + * @defgroup scmciphermodedefs SCM_CIPHER_MODE_MASK - Bit definitions + */ +/*! @{ */ +#define SCM_DECRYPT_MODE 0x1 /*!< decrypt from black to red memory */ +#define SCM_ENCRYPT_MODE 0x0 /*!< encrypt from red to black memory */ +/*! @} */ + +/*! + * @defgroup scmstatusregdefs SCM Status Register (SCM_STATUS). + * Bit and field definitions of the SCM_STATUS register. + */ +/*! @addtogroup scmstatusregdefs */ +/*! @{ */ +/*! These bits are zero or reserved */ +#define SCM_STATUS_ZEROS_MASK 0xffffe000 +/*! Ciphering failed due to length error. */ +#define SCM_STATUS_LENGTH_ERROR 0x1000 +/*! SMN has stopped blocking access to the SCM */ +#define SCM_STATUS_BLOCK_ACCESS_REMOVED 0x0800 +/*! Ciphering done. */ +#define SCM_STATUS_CIPHERING_DONE 0x0400 +/*! Zeroizing done. */ +#define SCM_STATUS_ZEROIZING_DONE 0x0200 +/*! SCM wants attention. Interrupt status is available. */ +#define SCM_STATUS_INTERRUPT_STATUS 0x0100 +/*! Secret Key is in use. */ +#define SCM_STATUS_SECRET_KEY 0x0080 +/*! Secret Key is in use. Deprecated. Use #SCM_STATUS_SECRET_KEY. */ +#define SCM_STATUS_DEFAULT_KEY 0x0080 +/*! Internal error to SCM. */ +#define SCM_STATUS_INTERNAL_ERROR 0x0040 +/*! Secret key is not valid. */ +#define SCM_STATUS_BAD_SECRET_KEY 0x0020 +/*! Failed to zeroize memory. */ +#define SCM_STATUS_ZEROIZE_FAILED 0x0010 +/*! SMN is blocking access to Secure Memory. */ +#define SCM_STATUS_SMN_BLOCKING_ACCESS 0x0008 +/*! SCM is current encrypting or decrypting data. */ +#define SCM_STATUS_CIPHERING 0x0004 +/*! SCM is currently zeroizing data. */ +#define SCM_STATUS_ZEROIZING 0x0002 +/*! SCM is busy and access to memory is blocked. */ +#define SCM_STATUS_BUSY 0x0001 +/*! @} */ + +/*! + * @defgroup scmerrstatdefs SCM Error Status Register (SCM_ERROR_STATUS) + * + * These definitions are associated with the SCM Error Status Register + * (SCM_ERROR_STATUS). + */ +/*! @addtogroup scmerrstatdefs */ +/*! @{ */ +/*! These bits are zero or reserved */ +#define SCM_ERR_ZEROS_MASK 0xffffc000 +/*! Cacheable access to SCM was attempted */ +#define SCM_ERR_CACHEABLE_ACCESS 0x2000 +/*! Access attempted by illegal bus master */ +#define SCM_ERR_ILLEGAL_MASTER 0x1000 +/*! Unaligned access attempted */ +#define SCM_ERR_UNALIGNED_ACCESS 0x0800 +/*! Byte or half-word access attempted */ +#define SCM_ERR_BYTE_ACCESS 0x0400 +/*! Illegal address attempted */ +#define SCM_ERR_ILLEGAL_ADDRESS 0x0200 +/*! User access attempted */ +#define SCM_ERR_USER_ACCESS 0x0100 +/*! Access attempted while SCM was using default key */ +#define SCM_ERR_SECRET_KEY_IN_USE 0x0080 +/*! Access attempted while SCM had internal error */ +#define SCM_ERR_INTERNAL_ERROR 0x0040 +/*! Access attempted while SCM was detecting Bad Key */ +#define SCM_ERR_BAD_SECRET_KEY 0x0020 +/*! The SCM failed to Zeroize memory */ +#define SCM_ERR_ZEROIZE_FAILED 0x0010 +/*! Access attempted while SMN was Blocking Access */ +#define SCM_ERR_SMN_BLOCKING_ACCESS 0x0008 +/*! Access attempted while SCM was CIPHERING */ +#define SCM_ERR_CIPHERING 0x0004 +/*! Access attempted while SCM was ZEROIZING */ +#define SCM_ERR_ZEROIZING 0x0002 +/*! Access attempted while SCM was BUSY */ +#define SCM_ERR_BUSY 0x0001 +/*! @} */ + +/*! + * @defgroup smndbgdetdefs SMN Debug Detector Status Register + * (SCM_DEBUG_DETECT_STAT) + */ +/*! @addtogroup smndbgdetdefs */ +/*! @{ */ +#define SMN_DBG_ZEROS_MASK 0xfffff000 /*!< These bits are zero or reserved */ +#define SMN_DBG_D12 0x0800 /*!< Error detected on Debug Port D12 */ +#define SMN_DBG_D11 0x0400 /*!< Error detected on Debug Port D11 */ +#define SMN_DBG_D10 0x0200 /*!< Error detected on Debug Port D10 */ +#define SMN_DBG_D9 0x0100 /*!< Error detected on Debug Port D9 */ +#define SMN_DBG_D8 0x0080 /*!< Error detected on Debug Port D8 */ +#define SMN_DBG_D7 0x0040 /*!< Error detected on Debug Port D7 */ +#define SMN_DBG_D6 0x0020 /*!< Error detected on Debug Port D6 */ +#define SMN_DBG_D5 0x0010 /*!< Error detected on Debug Port D5 */ +#define SMN_DBG_D4 0x0008 /*!< Error detected on Debug Port D4 */ +#define SMN_DBG_D3 0x0004 /*!< Error detected on Debug Port D3 */ +#define SMN_DBG_D2 0x0002 /*!< Error detected on Debug Port D2 */ +#define SMN_DBG_D1 0x0001 /*!< Error detected on Debug Port D1 */ +/*! @} */ + +/*! Mask for the usable bits of the Sequence Start Register + (#SMN_SEQUENCE_START) */ +#define SMN_SEQUENCE_START_MASK 0x0000ffff + +/*! Mask for the usable bits of the Sequence End Register + (#SMN_SEQUENCE_END) */ +#define SMN_SEQUENCE_END_MASK 0x0000ffff + +/*! Mask for the usable bits of the Sequence Check Register + (#SMN_SEQUENCE_CHECK) */ +#define SMN_SEQUENCE_CHECK_MASK 0x0000ffff + +/*! Mask for the usable bits of the Bit Counter Register + (#SMN_BIT_COUNT) */ +#define SMN_BIT_COUNT_MASK 0x000007ff + +/*! Mask for the usable bits of the Bit Bank Increment Size Register + (#SMN_BITBANK_INC_SIZE) */ +#define SMN_BITBANK_INC_SIZE_MASK 0x000007ff + +/*! Mask for the usable bits of the Bit Bank Decrement Register + (#SMN_BITBANK_DECREMENT) */ +#define SMN_BITBANK_DECREMENT_MASK 0x000007ff + +/*! Mask for the usable bits of the Compare Size Register + (#SMN_COMPARE_SIZE) */ +#define SMN_COMPARE_SIZE_MASK 0x0000003f + +/* Close out marker for C++ compilers */ +#ifdef __cplusplus +} +#endif +#endif /* __ASM_ARCH_MXC_SCC_DRIVER_H__ */ diff --git a/include/linux/mxc_si4702.h b/include/linux/mxc_si4702.h new file mode 100644 index 000000000000..55d7fd08df47 --- /dev/null +++ b/include/linux/mxc_si4702.h @@ -0,0 +1,39 @@ +/* + * linux/drivers/char/mxc_si4702.h + * + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Character device driver for SI4702 FM radio + */ + +/* + * @file mxc_si4702.h + * + * @brief SI4702 Radio FM driver + * + * @ingroup Character + */ + +#ifndef _MXC_SI4702_FM_H +#define _MXC_SI4702_FM_H + +/* define IOCTL command */ +#define SI4702_GETVOLUME _IOR('S', 0x10, unsigned int) +#define SI4702_SETVOLUME _IOW('S', 0x11, unsigned int) +#define SI4702_MUTEON _IO('S', 0x12) +#define SI4702_MUTEOFF _IO('S', 0x13) +#define SI4702_SELECT _IOW('S', 0x14, unsigned int) +#define SI4702_SEEK _IOWR('S', 0x15, unsigned int) + +#endif /* _MXC_SI4702_FM_H */ diff --git a/include/linux/mxc_sim_interface.h b/include/linux/mxc_sim_interface.h new file mode 100644 index 000000000000..ee6e50c95675 --- /dev/null +++ b/include/linux/mxc_sim_interface.h @@ -0,0 +1,108 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_sim_interface.h + * + * @brief Driver for Freescale IMX SIM interface + * + */ + +#ifndef MXC_SIM_INTERFACE_H +#define MXC_SIM_INTERFACE_H + +#define SIM_ATR_LENGTH_MAX 32 + +/* Raw ATR SIM_IOCTL_GET_ATR */ +typedef struct { + uint32_t size; /* length of ATR received */ + uint8_t t[SIM_ATR_LENGTH_MAX]; /* raw ATR string received */ +} sim_atr_t; + +/* Communication parameters for SIM_IOCTL_[GET|SET]_PARAM */ +typedef struct { + uint8_t convention; /* direct = 0, indirect = 1 */ + uint8_t FI, DI; /* frequency multiplier and devider indices */ + uint8_t PI1, II; /* programming voltage and current indices */ + uint8_t N; /* extra guard time */ + uint8_t T; /* protocol type: T0 = 0, T1 = 1 */ + uint8_t PI2; /* programming voltage 2 value */ + uint8_t WWT; /* working wait time */ +} sim_param_t; + +/* ISO7816-3 protocols */ +#define SIM_PROTOCOL_T0 0 +#define SIM_PROTOCOL_T1 1 + +/* Transfer data for SIM_IOCTL_XFER */ +typedef struct { + uint8_t *xmt_buffer; /* transmit buffer pointer */ + int32_t xmt_length; /* transmit buffer length */ + uint8_t *rcv_buffer; /* receive buffer pointer */ + int32_t rcv_length; /* receive buffer length */ + int type; /* transfer type: TPDU = 0, PTS = 1 */ + int timeout; /* transfer timeout in milliseconds */ + uint8_t sw1; /* status word 1 */ + uint8_t sw2; /* status word 2 */ +} sim_xfer_t; + +/* Transfer types for SIM_IOCTL_XFER */ +#define SIM_XFER_TYPE_TPDU 0 +#define SIM_XFER_TYPE_PTS 1 + +/* Interface power states */ +#define SIM_POWER_OFF 0 +#define SIM_POWER_ON 1 + +/* Return values for SIM_IOCTL_GET_PRESENSE */ +#define SIM_PRESENT_REMOVED 0 +#define SIM_PRESENT_DETECTED 1 +#define SIM_PRESENT_OPERATIONAL 2 + +/* Return values for SIM_IOCTL_GET_ERROR */ +#define SIM_OK 0 +#define SIM_E_ACCESS 1 +#define SIM_E_TPDUSHORT 2 +#define SIM_E_PTSEMPTY 3 +#define SIM_E_INVALIDXFERTYPE 4 +#define SIM_E_INVALIDXMTLENGTH 5 +#define SIM_E_INVALIDRCVLENGTH 6 +#define SIM_E_NACK 7 +#define SIM_E_TIMEOUT 8 +#define SIM_E_NOCARD 9 +#define SIM_E_PARAM_FI_INVALID 10 +#define SIM_E_PARAM_DI_INVALID 11 +#define SIM_E_PARAM_FBYD_WITHFRACTION 12 +#define SIM_E_PARAM_FBYD_NOTDIVBY8OR12 13 +#define SIM_E_PARAM_DIVISOR_RANGE 14 +#define SIM_E_MALLOC 15 +#define SIM_E_IRQ 16 +#define SIM_E_POWERED_ON 17 +#define SIM_E_POWERED_OFF 18 + +/* ioctl encodings */ +#define SIM_IOCTL_BASE 0xc0 +#define SIM_IOCTL_GET_PRESENSE _IOR(SIM_IOCTL_BASE, 1, int) +#define SIM_IOCTL_GET_ATR _IOR(SIM_IOCTL_BASE, 2, sim_atr_t) +#define SIM_IOCTL_GET_PARAM_ATR _IOR(SIM_IOCTL_BASE, 3, sim_param_t) +#define SIM_IOCTL_GET_PARAM _IOR(SIM_IOCTL_BASE, 4, sim_param_t) +#define SIM_IOCTL_SET_PARAM _IOW(SIM_IOCTL_BASE, 5, sim_param_t) +#define SIM_IOCTL_XFER _IOR(SIM_IOCTL_BASE, 6, sim_xfer_t) +#define SIM_IOCTL_POWER_ON _IO(SIM_IOCTL_BASE, 7) +#define SIM_IOCTL_POWER_OFF _IO(SIM_IOCTL_BASE, 8) +#define SIM_IOCTL_WARM_RESET _IO(SIM_IOCTL_BASE, 9) +#define SIM_IOCTL_COLD_RESET _IO(SIM_IOCTL_BASE, 10) +#define SIM_IOCTL_CARD_LOCK _IO(SIM_IOCTL_BASE, 11) +#define SIM_IOCTL_CARD_EJECT _IO(SIM_IOCTL_BASE, 12) + +#endif diff --git a/include/linux/mxc_v4l2.h b/include/linux/mxc_v4l2.h new file mode 100644 index 000000000000..d474aa2f4c86 --- /dev/null +++ b/include/linux/mxc_v4l2.h @@ -0,0 +1,48 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +/*! + * @file arch-mxc/mxc_v4l2.h + * + * @brief mxc V4L2 private structures + * + * @ingroup MXC_V4L2_CAPTURE + */ + +#ifndef __ASM_ARCH_MXC_V4L2_H__ +#define __ASM_ARCH_MXC_V4L2_H__ + +/* + * For IPUv1 and IPUv3, V4L2_CID_MXC_ROT means encoder ioctl ID. + * And V4L2_CID_MXC_VF_ROT is viewfinder ioctl ID only for IPUv1 and IPUv3. + */ +#define V4L2_CID_MXC_ROT (V4L2_CID_PRIVATE_BASE + 0) +#define V4L2_CID_MXC_FLASH (V4L2_CID_PRIVATE_BASE + 1) +#define V4L2_CID_MXC_VF_ROT (V4L2_CID_PRIVATE_BASE + 2) +#define V4L2_CID_MXC_MOTION (V4L2_CID_PRIVATE_BASE + 3) + +#define V4L2_MXC_ROTATE_NONE 0 +#define V4L2_MXC_ROTATE_VERT_FLIP 1 +#define V4L2_MXC_ROTATE_HORIZ_FLIP 2 +#define V4L2_MXC_ROTATE_180 3 +#define V4L2_MXC_ROTATE_90_RIGHT 4 +#define V4L2_MXC_ROTATE_90_RIGHT_VFLIP 5 +#define V4L2_MXC_ROTATE_90_RIGHT_HFLIP 6 +#define V4L2_MXC_ROTATE_90_LEFT 7 + +struct v4l2_mxc_offset { + uint32_t u_offset; + uint32_t v_offset; +}; + +#endif diff --git a/include/linux/mxcfb.h b/include/linux/mxcfb.h new file mode 100644 index 000000000000..79c23c344dfa --- /dev/null +++ b/include/linux/mxcfb.h @@ -0,0 +1,84 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +/* + * @file arch-mxc/ mxcfb.h + * + * @brief Global header file for the MXC Frame buffer + * + * @ingroup Framebuffer + */ +#ifndef __ASM_ARCH_MXCFB_H__ +#define __ASM_ARCH_MXCFB_H__ + +#include <linux/fb.h> + +#define FB_SYNC_OE_LOW_ACT 0x80000000 +#define FB_SYNC_CLK_LAT_FALL 0x40000000 +#define FB_SYNC_DATA_INVERT 0x20000000 +#define FB_SYNC_CLK_IDLE_EN 0x10000000 +#define FB_SYNC_SHARP_MODE 0x08000000 +#define FB_SYNC_SWAP_RGB 0x04000000 + +struct mxcfb_gbl_alpha { + int enable; + int alpha; +}; + +struct mxcfb_loc_alpha { + int enable; + unsigned long alpha_phy_addr0; + unsigned long alpha_phy_addr1; +}; + +struct mxcfb_color_key { + int enable; + __u32 color_key; +}; + +struct mxcfb_pos { + __u16 x; + __u16 y; +}; + +#define MXCFB_WAIT_FOR_VSYNC _IOW('F', 0x20, u_int32_t) +#define MXCFB_SET_GBL_ALPHA _IOW('F', 0x21, struct mxcfb_gbl_alpha) +#define MXCFB_SET_CLR_KEY _IOW('F', 0x22, struct mxcfb_color_key) +#define MXCFB_SET_OVERLAY_POS _IOWR('F', 0x24, struct mxcfb_pos) +#define MXCFB_GET_FB_IPU_CHAN _IOR('F', 0x25, u_int32_t) +#define MXCFB_SET_LOC_ALPHA _IOWR('F', 0x26, struct mxcfb_loc_alpha) +#define MXCFB_SET_LOC_ALP_BUF _IOW('F', 0x27, unsigned long) + +#ifdef __KERNEL__ + +extern struct fb_videomode mxcfb_modedb[]; +extern int mxcfb_modedb_sz; + +enum { + MXCFB_REFRESH_OFF, + MXCFB_REFRESH_AUTO, + MXCFB_REFRESH_PARTIAL, +}; + +struct mxcfb_rect { + u32 top; + u32 left; + u32 width; + u32 height; +}; + +int mxcfb_set_refresh_mode(struct fb_info *fbi, int mode, + struct mxcfb_rect *update_region); + +#endif /* __KERNEL__ */ +#endif diff --git a/include/linux/pmic_adc.h b/include/linux/pmic_adc.h new file mode 100644 index 000000000000..8cc87293fb3e --- /dev/null +++ b/include/linux/pmic_adc.h @@ -0,0 +1,455 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef __ASM_ARCH_MXC_PMIC_ADC_H__ +#define __ASM_ARCH_MXC_PMIC_ADC_H__ + +/*! + * @defgroup PMIC_ADC PMIC Digitizer Driver + * @ingroup PMIC_DRVRS + */ + +/*! + * @file arch-mxc/pmic_adc.h + * @brief This is the header of PMIC ADC driver. + * + * @ingroup PMIC_ADC + */ + +#include <linux/ioctl.h> +#include <linux/pmic_status.h> +#include <linux/pmic_external.h> + +/*! + * @name IOCTL user space interface + */ + +/*! @{ */ +/*! + * Initialize ADC. + * Argument type: none. + */ +#define PMIC_ADC_INIT _IO('p', 0xb0) +/*! + * De-initialize ADC. + * Argument type: none. + */ +#define PMIC_ADC_DEINIT _IO('p', 0xb1) +/*! + * Convert one channel. + * Argument type: pointer to t_adc_convert_param. + */ +#define PMIC_ADC_CONVERT _IOWR('p', 0xb2, int) +/*! + * Convert one channel eight samples. + * Argument type: pointer to t_adc_convert_param. + */ +#define PMIC_ADC_CONVERT_8X _IOWR('p', 0xb3, int) +/*! + * Convert multiple channels. + * Argument type: pointer to t_adc_convert_param. + */ +#define PMIC_ADC_CONVERT_MULTICHANNEL _IOWR('p', 0xb4, int) +/*! + * Set touch screen operation mode. + * Argument type: t_touch_mode. + */ +#define PMIC_ADC_SET_TOUCH_MODE _IOW('p', 0xb5, int) +/*! + * Get touch screen operation mode. + * Argument type: pointer to t_touch_mode. + */ +#define PMIC_ADC_GET_TOUCH_MODE _IOR('p', 0xb6, int) +/*! + * Get touch screen sample. + * Argument type: pointer to t_touch_sample. + */ +#define PMIC_ADC_GET_TOUCH_SAMPLE _IOWR('p', 0xb7, int) +/*! + * Get battery current. + * Argument type: pointer to unsigned short. + */ +#define PMIC_ADC_GET_BATTERY_CURRENT _IOR('p', 0xb8, int) +/*! + * Activate comparator. + * Argument type: pointer to t_adc_comp_param. + */ +#define PMIC_ADC_ACTIVATE_COMPARATOR _IOW('p', 0xb9, int) +/*! + * De-active comparator. + * Argument type: none. + */ +#define PMIC_ADC_DEACTIVE_COMPARATOR _IOW('p', 0xba, int) + +/*! + * Install touch screen read interface. + */ +#define TOUCH_SCREEN_READ_INSTALL _IOWR('D',4, int) +/*! + * Remove touch screen read interface. + */ +#define TOUCH_SCREEN_READ_UNINSTALL _IOWR('D',5, int) + +/*! @{ */ +/*! + * @name Touch Screen minimum and maximum values + */ +#define TS_X_MIN 80 /*! < Minimum X */ +#define TS_Y_MIN 80 /*! < Minimum Y */ + +#define TS_X_MAX 1000 /*! < Maximum X */ +#define TS_Y_MAX 1000 /*! < Maximum Y */ +/*! @} */ +/*! + * This enumeration defines input channels for PMIC ADC + */ + +typedef enum { + BATTERY_VOLTAGE, + BATTERY_CURRENT, + CHARGE_VOLTAGE, + CHARGE_CURRENT, + APPLICATION_SUPPLY, + TS_X_POS1, + TS_X_POS2, + TS_Y_POS1, + TS_Y_POS2, + GEN_PURPOSE_AD4, + GEN_PURPOSE_AD5, + GEN_PURPOSE_AD6, + GEN_PURPOSE_AD7, + GEN_PURPOSE_AD8, + GEN_PURPOSE_AD9, + GEN_PURPOSE_AD10, + GEN_PURPOSE_AD11, + USB_ID, + LICELL, + RAWEXTBPLUSSENSE, + MPBSENSE, + BATSENSE, + GND, + THERMISTOR, + DIE_TEMP +} t_channel; + +/*! + * This enumeration defines reason of ADC Comparator interrupt. + */ +typedef enum { + /*! + * Greater than WHIGH + */ + GTWHIGH, + /*! + * Less than WLOW + */ + LTWLOW, +} t_comp_exception; + +/*! + * ADC comparator callback function type + */ +typedef void (*t_comparator_cb) (t_comp_exception reason); + +/*! + * This enumeration defines the touch screen operation modes. + */ +typedef enum { + /*! + * Touch Screen X position + */ + TS_X_POSITION = 0, + /*! + * Touch Screen Y position + */ + TS_Y_POSITION = 1, + /*! + * Pressure + */ + TS_PRESSURE = 2, + /*! + * Plate X + */ + TS_PLATE_X = 3, + /*! + * Plate Y + */ + TS_PLATE_Y = 4, + /*! + * Standby + */ + TS_STANDBY = 5, + /*! + * No touch screen, TSX1, TSX2, TSY1 and TSY2 are used as general + * purpose A/D inputs. + */ + TS_NONE = 6, +} t_touch_mode; +/*! + * This structure is used to report touch screen value. + */ +typedef struct { +/*! + * Touch Screen X position + */ + unsigned int x_position; + /*! + * Touch Screen X position1 + */ + unsigned int x_position1; + /*! + * Touch Screen X position2 + */ + unsigned int x_position2; + /*! + * Touch Screen X position3 + */ + unsigned int x_position3; + /*! + * Touch Screen Y position + */ + unsigned int y_position; + /*! + * Touch Screen Y position1 + */ + unsigned int y_position1; + /*! + * Touch Screen Y position2 + */ + unsigned int y_position2; + /*! + * Touch Screen Y position3 + */ + unsigned int y_position3; + /*! + * Touch Screen contact value + */ + unsigned int contact_resistance; +} t_touch_screen; + +/*! + * This enumeration defines ADC conversion modes. + */ +typedef enum { + /*! + * Sample 8 channels, 1 sample per channel + */ + ADC_8CHAN_1X = 0, + /*! + * Sample 1 channel 8 times + */ + ADC_1CHAN_8X, +} t_conversion_mode; + +/*! + * This structure is used with IOCTL code \a PMIC_ADC_CONVERT, + * \a PMIC_ADC_CONVERT_8X and \a PMIC_ADC_CONVERT_MULTICHANNEL. + */ + +typedef struct { + /*! + * channel or channels to be sampled. + */ + t_channel channel; + /*! + * holds up to 16 sampling results + */ + unsigned short result[16]; +} t_adc_convert_param; + +/*! + * This structure is used to activate/deactivate ADC comparator. + */ +typedef struct { + /*! + * wlow. + */ + unsigned char wlow; + /*! + * whigh. + */ + unsigned char whigh; + /*! + * channel to monitor + */ + t_channel channel; + /*! + * callback function. + */ + t_comparator_cb callback; +} t_adc_comp_param; + +/* EXPORTED FUNCTIONS */ + +#ifdef __KERNEL__ +/*! + * This function initializes all ADC registers with default values. This + * function also registers the interrupt events. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_init(void); + +/*! + * This function disables the ADC, de-registers the interrupt events. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_deinit(void); + +/*! + * This function triggers a conversion and returns one sampling result of one + * channel. + * + * @param channel The channel to be sampled + * @param result The pointer to the conversion result. The memory + * should be allocated by the caller of this function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ + +PMIC_STATUS pmic_adc_convert(t_channel channel, unsigned short *result); + +/*! + * This function triggers a conversion and returns eight sampling results of + * one channel. + * + * @param channel The channel to be sampled + * @param result The pointer to array to store eight sampling results. + * The memory should be allocated by the caller of this + * function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ + +PMIC_STATUS pmic_adc_convert_8x(t_channel channel, unsigned short *result); + +/*! + * This function triggers a conversion and returns sampling results of each + * specified channel. + * + * @param channels This input parameter is bitmap to specify channels + * to be sampled. + * @param result The pointer to array to store sampling result. + * The order of the result in the array is from lowest + * channel number to highest channel number of the + * sampled channels. + * The memory should be allocated by the caller of this + * function. + * Note that the behavior of this function might differ + * from one platform to another regarding especially + * channels order. + * + * @return This function returns PMIC_SUCCESS if successful. + */ + +PMIC_STATUS pmic_adc_convert_multichnnel(t_channel channels, + unsigned short *result); + +/*! + * This function sets touch screen operation mode. + * + * @param touch_mode Touch screen operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_set_touch_mode(t_touch_mode touch_mode); + +/*! + * This function retrieves the current touch screen operation mode. + * + * @param touch_mode Pointer to the retrieved touch screen operation + * mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_touch_mode(t_touch_mode * touch_mode); + +/*! + * This function retrieves the current touch screen operation mode. + * + * @param touch_sample Pointer to touch sample. + * @param wait Indicates if this function needs to block or not. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_touch_sample(t_touch_screen * ts_value, int wait); + +/*! + * This function starts a Battery Current mode conversion. + * + * @param mode Conversion mode. + * @param result Battery Current measurement result. + * if \a mode = ADC_8CHAN_1X, the result is \n + * result[0] = (BATTP - BATT_I) \n + * if \a mode = ADC_1CHAN_8X, the result is \n + * result[0] = BATTP \n + * result[1] = BATT_I \n + * result[2] = BATTP \n + * result[3] = BATT_I \n + * result[4] = BATTP \n + * result[5] = BATT_I \n + * result[6] = BATTP \n + * result[7] = BATT_I + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_battery_current(t_conversion_mode mode, + unsigned short *result); + +/*! + * This function actives the comparator. When comparator is activated and ADC + * is enabled, the 8th converted value will be digitally compared against the + * window defined by WLOW and WHIGH registers. + * + * @param low Comparison window low threshold (WLOW). + * @param high Comparison window high threshold (WHIGH). + * @param callback Callback function to be called when the converted + * value is beyond the comparison window. The callback + * function will pass a parameter of type + * \b t_comp_expection to indicate the reason of + * comparator exception. + * + * @return This function returns PMIC_SUCCESS if successful. + */ + +PMIC_STATUS pmic_adc_active_comparator(unsigned char low, + unsigned char high, + t_channel channel, + t_comparator_cb callback); + +/*! + * This function de-actives the comparator. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_deactive_comparator(void); + +/*! + * This function enables the touch screen read interface. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_adc_install_ts(void); + +/*! + * This function disables the touch screen read interface. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_adc_remove_ts(void); + +int is_pmic_adc_ready(void); + +#endif /* _KERNEL */ +#endif /* __ASM_ARCH_MXC_PMIC_ADC_H__ */ diff --git a/include/linux/pmic_battery.h b/include/linux/pmic_battery.h new file mode 100644 index 000000000000..b32136dab186 --- /dev/null +++ b/include/linux/pmic_battery.h @@ -0,0 +1,419 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ +#ifndef __ASM_ARCH_MXC_PMIC_BATTERY_H__ +#define __ASM_ARCH_MXC_PMIC_BATTERY_H__ + +/*! + * @defgroup PMIC_BATTERY PMIC Battery Driver + * @ingroup PMIC_DRVRS + */ + +/*! + * @file arch-mxc/pmic_battery.h + * @brief This is the header of PMIC Battery driver. + * + * @ingroup PMIC_BATTERY + */ + +#include <linux/ioctl.h> +#include <linux/pmic_status.h> +#include <linux/pmic_external.h> + +/*! + * @name IOCTL user space interface + */ + +/*! @{ */ +/*! + * Enable and disable charger. + * Argument type: pointer to t_charger_setting + */ +#define PMIC_BATT_CHARGER_CONTROL _IOW('p', 0xc0, int) +/*! + * Set charger configuration. + * Argument type: pointer to t_charger_setting + */ +#define PMIC_BATT_SET_CHARGER _IOW('p', 0xc1, int) +/*! + * Get charger configuration. + * Argument type: pointer to t_charger_setting + */ +#define PMIC_BATT_GET_CHARGER _IOR('p', 0xc2, int) +/*! + * Get charger current. + * Argument type: pointer to t_charger_setting + */ +#define PMIC_BATT_GET_CHARGER_CURRENT _IOR('p', 0xc3, int) +/*! + * Set EOL control + * Argument type: pointer to t_eol_setting + */ +#define PMIC_BATT_EOL_CONTROL _IOW('p', 0xc4, int) +/*! + * Enable and disable charging LED. + * Argument type: bool + */ +#define PMIC_BATT_LED_CONTROL _IOW('p', 0xc5, int) +/*! + * Enable and disable reverse supply. + * Argument type: bool + */ +#define PMIC_BATT_REV_SUPP_CONTROL _IOW('p', 0xc6, int) +/*! + * Enable and disable unregulated charging mode. + * Argument type: bool + */ +#define PMIC_BATT_UNREG_CONTROL _IOW('p', 0xc7, int) + +/*! + * Set the output controls. + * Argument type: t_control + */ +#define PMIC_BATT_SET_OUT_CONTROL _IOW('p', 0xc8, int) +/*! + * Set the over voltage threshold. + * Argument type: int + */ +#define PMIC_BATT_SET_THRESHOLD _IOW('p', 0xc9, int) + +/*! + * Get the charger voltage. + * Argument type: int + */ +#define PMIC_BATT_GET_CHARGER_VOLTAGE _IOR('p', 0xca, int) +/*! + * Get the battery voltage. + * Argument type: int + */ +#define PMIC_BATT_GET_BATTERY_VOLTAGE _IOR('p', 0xcb, int) +/*! + * Get the battery current. + * Argument type: int + */ +#define PMIC_BATT_GET_BATTERY_CURRENT _IOR('p', 0xcc, int) +/*! + * Get the charger sensor. + * Argument type: int + */ +#define PMIC_BATT_GET_CHARGER_SENSOR _IOR('p', 0xcd, int) +/*! + * Get the battery temperature. + * Argument type: int + */ +#define PMIC_BATT_GET_BATTERY_TEMPERATURE _IOR('p', 0xce, int) +/*! @} */ + +/*! + * This enumeration defines battery chargers. + */ +typedef enum { + BATT_MAIN_CHGR = 0, /*!< Main battery charger */ + BATT_CELL_CHGR, /*!< Cell battery charger */ + BATT_TRCKLE_CHGR /*!< Trickle charger (only available on mc13783) */ +} t_batt_charger; + +/*! + * This enumeration defines the bp threshold. + */ +typedef enum { + BATT_BP_0 = 0, /*!< LOBATL UVDET + 0.2 */ + BATT_BP_1, /*!< LOBATL UVDET + 0.3 */ + BATT_BP_2, /*!< LOBATL UVDET + 0.4 */ + BATT_BP_3 /*!< LOBATL UVDET + 0.5 */ +} t_bp_threshold; + +/*! + * This enumeration of all types of output controls + */ +typedef enum { + /*! + * controlled hardware + */ + CONTROL_HARDWARE = 0, + /*! + * BPFET is driven low, BATTFET is driven high + */ + CONTROL_BPFET_LOW, + /*! + * BPFET is driven high, BATTFET is driven low + */ + CONTROL_BPFET_HIGH, +} t_control; + +/*! + * This enumeration define all battery interrupt + */ +typedef enum { + /*! + * Charge detection interrupt + */ + BAT_IT_CHG_DET, + /*! + * Charge over voltage detection it + */ + BAT_IT_CHG_OVERVOLT, + /*! + * Charge path reverse current it + */ + BAT_IT_CHG_REVERSE, + /*! + * Charge path short circuitin revers supply mode it + */ + BAT_IT_CHG_SHORT_CIRCUIT, + /*! + * Charger has switched its mode (CC to CV or CV to CC) + */ + BAT_IT_CCCV, + /*! + * Charge current has dropped below its threshold + */ + BAT_IT_BELOW_THRESHOLD, +} t_batt_event; + +/*! + * This structure is used for the following battery changer control + * IOCTLs: + * - PMIC_BATT_CHARGER_CONTROL + * - PMIC_BATT_SET_CHARGER + * - PMIC_BATT_GET_CHARGER + */ +typedef struct { + /*! + * Charger + */ + t_batt_charger chgr; + /*! + * Turn on charger + */ + bool on; + /*! + * Charging voltage + */ + unsigned char c_voltage; + /*! + * Charging current + */ + unsigned char c_current; +} t_charger_setting; + +/*! + * This structure is used for EOL setting IOCTL PMIC_BATT_EOL_CONTROL + */ +typedef struct { + /*! + * Enable EOL comparator + */ + bool enable; + /*! + * c_voltage threshold - Used on SC55112 + */ + unsigned char threshold; + /*! + * bp threshold - Used on mc13783 + */ + t_bp_threshold typical; +} t_eol_setting; + +/* EXPORTED FUNCTIONS */ + +#ifdef __KERNEL__ + +/*START: for 3ds hw event*/ +/*! + * Battery event type enum + */ +enum { + BAT_EVENT_CHARGER_PLUG = 0x01, + BAT_EVENT_CHARGER_UNPLUG = 0x02, + BAT_EVENT_CHARGER_OVERVOLTAGE = 0x04, + BAT_EVENT_BATTERY_LOW = 0x08, + BAT_EVENT_POWER_FAILED = 0x10, + BAT_EVENT_CHARGER_FULL = 0x20, +} t_bat_event; +/*END: for 3ds hw event*/ + +/*! + * This function is used to start charging a battery. For different charger, + * different c_voltage and current range are supported. \n + * + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Charging voltage. + * @param c_current Charging current. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_enable_charger(t_batt_charger chgr, + unsigned char c_voltage, + unsigned char c_current); + +/*! + * This function turns off a charger. + * + * @param chgr Charger as defined in \b t_batt_charger. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_disable_charger(t_batt_charger chgr); + +/*! + * This function is used to change the charger setting. + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Charging voltage. + * @param c_current Charging current. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_charger(t_batt_charger chgr, + unsigned char c_voltage, + unsigned char c_current); + +/*! + * This function is used to retrieve the charger setting. + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Output parameter for charging c_voltage setting. + * @param c_current Output parameter for charging current setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_charger_setting(t_batt_charger chgr, + unsigned char *c_voltage, + unsigned char *c_current); + +/*! + * This function is retrieves the main battery charging current. + * + * @param c_current Output parameter for charging current setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ + +PMIC_STATUS pmic_batt_get_charge_current(unsigned short *c_current); + +/*! + * This function enables End-of-Life comparator. + * + * @param threshold End-of-Life threshold. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_enable_eol(unsigned char threshold); + +/*! + * This function enables End-of-Life comparator. + * + * @param typical Falling Edge Threshold threshold. + * @verbatim + BPDET UVDET LOBATL + ____ _____ ___________ + 0 2.6 UVDET + 0.2 + 1 2.6 UVDET + 0.3 + 2 2.6 UVDET + 0.4 + 3 2.6 UVDET + 0.5 + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_bp_enable_eol(t_bp_threshold typical); + +/*! + * This function disables End-of-Life comparator. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_disable_eol(void); + +/*! + * This function sets the output controls. + * It sets the FETOVRD and FETCTRL bits of mc13783 + * + * @param control type of control. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_set_out_control(t_control control); + +/*! + * This function sets over voltage threshold. + * + * @param threshold value of over voltage threshold. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_set_threshold(int threshold); + +/*! + * This function controls charge LED. + * + * @param on If on is true, LED will be turned on, + * or otherwise, LED will be turned off. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_led_control(bool on); + +/*! + * This function sets reverse supply mode. + * + * @param enable If enable is true, reverse supply mode is enable, + * or otherwise, reverse supply mode is disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_reverse_supply(bool enable); + +/*! + * This function sets unregulated charging mode on main battery. + * + * @param enable If enable is true, unregulated charging mode is + * enable, or otherwise, disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_unregulated(bool enable); + +/*! + * This function sets a 5K pull down at CHRGRAW. + * To be used in the dual path charging configuration. + * + * @param enable If enable is true, 5k pull down is + * enable, or otherwise, disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_5k_pull(bool enable); + +/*! + * This function is used to subscribe on battery event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_event_subscribe(t_batt_event event, void *callback); + +/*! + * This function is used to un subscribe on battery event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_event_unsubscribe(t_batt_event event, void *callback); + +#endif /* __KERNEL__ */ + +#endif /* __ASM_ARCH_MXC_PMIC_BATTERY_H__ */ diff --git a/include/linux/pmic_external.h b/include/linux/pmic_external.h new file mode 100644 index 000000000000..96d61b740666 --- /dev/null +++ b/include/linux/pmic_external.h @@ -0,0 +1,1108 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ASM_ARCH_MXC_PMIC_EXTERNAL_H__ +#define __ASM_ARCH_MXC_PMIC_EXTERNAL_H__ + +#ifdef __KERNEL__ +#include <linux/list.h> +#endif + +/*! + * @defgroup PMIC_DRVRS PMIC Drivers + */ + +/*! + * @defgroup PMIC_CORE PMIC Protocol Drivers + * @ingroup PMIC_DRVRS + */ + +/*! + * @file arch-mxc/pmic_external.h + * @brief This file contains interface of PMIC protocol driver. + * + * @ingroup PMIC_CORE + */ + +#include <linux/ioctl.h> +#include <linux/pmic_status.h> + +/*! + * This is the enumeration of versions of PMIC + */ +typedef enum { + PMIC_MC13783 = 1, /*!< MC13783 */ + PMIC_SC55112 = 2, /*!< SC55112 */ + PMIC_MC13892 = 3, + PMIC_MC34704 = 4 +} pmic_id_t; + +/*! + * @struct pmic_version_t + * @brief PMIC version and revision + */ +typedef struct { + /*! + * PMIC version identifier. + */ + pmic_id_t id; + /*! + * Revision of the PMIC. + */ + int revision; +} pmic_version_t; + +/*! + * struct pmic_event_callback_t + * @brief This structure contains callback function pointer and its + * parameter to be used when un/registering and launching a callback + * for an event. + */ +typedef struct { + /*! + * call back function + */ + void (*func) (void *); + + /*! + * call back function parameter + */ + void *param; +} pmic_event_callback_t; + +/*! + * This structure is used with IOCTL. + * It defines register, register value, register mask and event number + */ +typedef struct { + /*! + * register number + */ + int reg; + /*! + * value of register + */ + unsigned int reg_value; + /*! + * mask of bits, only used with PMIC_WRITE_REG + */ + unsigned int reg_mask; +} register_info; + +/*! + * @name IOCTL definitions for sc55112 core driver + */ +/*! @{ */ +/*! Read a PMIC register */ +#define PMIC_READ_REG _IOWR('P', 0xa0, register_info*) +/*! Write a PMIC register */ +#define PMIC_WRITE_REG _IOWR('P', 0xa1, register_info*) +/*! Subscribe a PMIC interrupt event */ +#define PMIC_SUBSCRIBE _IOR('P', 0xa2, int) +/*! Unsubscribe a PMIC interrupt event */ +#define PMIC_UNSUBSCRIBE _IOR('P', 0xa3, int) +/*! Subscribe a PMIC event for user notification*/ +#define PMIC_NOTIFY_USER _IOR('P', 0xa4, int) +/*! Get the PMIC event occured for which user recieved notification */ +#define PMIC_GET_NOTIFY _IOW('P', 0xa5, int) +/*! @} */ + +/*! + * This is PMIC registers valid bits + */ +#define PMIC_ALL_BITS 0xFFFFFF +#define PMIC_MAX_EVENTS 48 + +#define PMIC_ARBITRATION "NULL" + +#ifdef CONFIG_MXC_PMIC_MC13783 +/*! + * This is the enumeration of register names of MC13783 + */ +typedef enum { + /*! + * REG_INTERRUPT_STATUS_0 + */ + REG_INTERRUPT_STATUS_0 = 0, + /*! + * REG_INTERRUPT_MASK_0 + */ + REG_INTERRUPT_MASK_0, + /*! + * REG_INTERRUPT_SENSE_0 + */ + REG_INTERRUPT_SENSE_0, + /*! + * REG_INTERRUPT_STATUS_1 + */ + REG_INTERRUPT_STATUS_1, + /*! + * REG_INTERRUPT_MASK_1 + */ + REG_INTERRUPT_MASK_1, + /*! + * REG_INTERRUPT_SENSE_1 + */ + REG_INTERRUPT_SENSE_1, + /*! + * REG_POWER_UP_MODE_SENSE + */ + REG_POWER_UP_MODE_SENSE, + /*! + * REG_REVISION + */ + REG_REVISION, + /*! + * REG_SEMAPHORE + */ + REG_SEMAPHORE, + /*! + * REG_ARBITRATION_PERIPHERAL_AUDIO + */ + REG_ARBITRATION_PERIPHERAL_AUDIO, + /*! + * REG_ARBITRATION_SWITCHERS + */ + REG_ARBITRATION_SWITCHERS, + /*! + * REG_ARBITRATION_REGULATORS_0 + */ + REG_ARBITRATION_REGULATORS_0, + /*! + * REG_ARBITRATION_REGULATORS_1 + */ + REG_ARBITRATION_REGULATORS_1, + /*! + * REG_POWER_CONTROL_0 + */ + REG_POWER_CONTROL_0, + /*! + * REG_POWER_CONTROL_1 + */ + REG_POWER_CONTROL_1, + /*! + * REG_POWER_CONTROL_2 + */ + REG_POWER_CONTROL_2, + /*! + * REG_REGEN_ASSIGNMENT + */ + REG_REGEN_ASSIGNMENT, + /*! + * REG_CONTROL_SPARE + */ + REG_CONTROL_SPARE, + /*! + * REG_MEMORY_A + */ + REG_MEMORY_A, + /*! + * REG_MEMORY_B + */ + REG_MEMORY_B, + /*! + * REG_RTC_TIME + */ + REG_RTC_TIME, + /*! + * REG_RTC_ALARM + */ + REG_RTC_ALARM, + /*! + * REG_RTC_DAY + */ + REG_RTC_DAY, + /*! + * REG_RTC_DAY_ALARM + */ + REG_RTC_DAY_ALARM, + /*! + * REG_SWITCHERS_0 + */ + REG_SWITCHERS_0, + /*! + * REG_SWITCHERS_1 + */ + REG_SWITCHERS_1, + /*! + * REG_SWITCHERS_2 + */ + REG_SWITCHERS_2, + /*! + * REG_SWITCHERS_3 + */ + REG_SWITCHERS_3, + /*! + * REG_SWITCHERS_4 + */ + REG_SWITCHERS_4, + /*! + * REG_SWITCHERS_5 + */ + REG_SWITCHERS_5, + /*! + * REG_REGULATOR_SETTING_0 + */ + REG_REGULATOR_SETTING_0, + /*! + * REG_REGULATOR_SETTING_1 + */ + REG_REGULATOR_SETTING_1, + /*! + * REG_REGULATOR_MODE_0 + */ + REG_REGULATOR_MODE_0, + /*! + * REG_REGULATOR_MODE_1 + */ + REG_REGULATOR_MODE_1, + /*! + * REG_POWER_MISCELLANEOUS + */ + REG_POWER_MISCELLANEOUS, + /*! + * REG_POWER_SPARE + */ + REG_POWER_SPARE, + /*! + * REG_AUDIO_RX_0 + */ + REG_AUDIO_RX_0, + /*! + * REG_AUDIO_RX_1 + */ + REG_AUDIO_RX_1, + /*! + * REG_AUDIO_TX + */ + REG_AUDIO_TX, + /*! + * REG_AUDIO_SSI_NETWORK + */ + REG_AUDIO_SSI_NETWORK, + /*! + * REG_AUDIO_CODEC + */ + REG_AUDIO_CODEC, + /*! + * REG_AUDIO_STEREO_DAC + */ + REG_AUDIO_STEREO_DAC, + /*! + * REG_AUDIO_SPARE + */ + REG_AUDIO_SPARE, + /*! + * REG_ADC_0 + */ + REG_ADC_0, + /*! + * REG_ADC_1 + */ + REG_ADC_1, + /*! + * REG_ADC_2 + */ + REG_ADC_2, + /*! + * REG_ADC_3 + */ + REG_ADC_3, + /*! + * REG_ADC_4 + */ + REG_ADC_4, + /*! + * REG_CHARGER + */ + REG_CHARGER, + /*! + * REG_USB + */ + REG_USB, + /*! + * REG_CHARGE_USB_SPARE + */ + REG_CHARGE_USB_SPARE, + /*! + * REG_LED_CONTROL_0 + */ + REG_LED_CONTROL_0, + /*! + * REG_LED_CONTROL_1 + */ + REG_LED_CONTROL_1, + /*! + * REG_LED_CONTROL_2 + */ + REG_LED_CONTROL_2, + /*! + * REG_LED_CONTROL_3 + */ + REG_LED_CONTROL_3, + /*! + * REG_LED_CONTROL_4 + */ + REG_LED_CONTROL_4, + /*! + * REG_LED_CONTROL_5 + */ + REG_LED_CONTROL_5, + /*! + * REG_SPARE + */ + REG_SPARE, + /*! + * REG_TRIM_0 + */ + REG_TRIM_0, + /*! + * REG_TRIM_1 + */ + REG_TRIM_1, + /*! + * REG_TEST_0 + */ + REG_TEST_0, + /*! + * REG_TEST_1 + */ + REG_TEST_1, + /*! + * REG_TEST_2 + */ + REG_TEST_2, + /*! + * REG_TEST_3 + */ + REG_TEST_3, + /*! + * REG_NB + */ + REG_NB, +} pmic_reg; + +/*! + * This is event list of mc13783 interrupt + */ + +typedef enum { + /*! + * ADC has finished requested conversions + */ + EVENT_ADCDONEI = 0, + /*! + * ADCBIS has finished requested conversions + */ + EVENT_ADCBISDONEI = 1, + /*! + * Touchscreen wakeup + */ + EVENT_TSI = 2, + /*! + * ADC reading above high limit + */ + EVENT_WHIGHI = 3, + /*! + * ADC reading below low limit + */ + EVENT_WLOWI = 4, + /*! + * Charger attach and removal + */ + EVENT_CHGDETI = 6, + /*! + * Charger over-voltage detection + */ + EVENT_CHGOVI = 7, + /*! + * Charger path reverse current + */ + EVENT_CHGREVI = 8, + /*! + * Charger path short circuit + */ + EVENT_CHGSHORTI = 9, + /*! + * BP regulator current or voltage regulation + */ + EVENT_CCCVI = 10, + /*! + * Charge current below threshold + */ + EVENT_CHRGCURRI = 11, + /*! + * BP turn on threshold detection + */ + EVENT_BPONI = 12, + /*! + * End of life / low battery detect + */ + EVENT_LOBATLI = 13, + /*! + * Low battery warning + */ + EVENT_LOBATHI = 14, + /*! + * USB detect + */ + EVENT_USBI = 16, + /*! + * USB ID Line detect + */ + EVENT_IDI = 19, + /*! + * Single ended 1 detect + */ + EVENT_SE1I = 21, + /*! + * Car-kit detect + */ + EVENT_CKDETI = 22, + /*! + * 1 Hz time-tick + */ + EVENT_E1HZI = 24, + /*! + * Time of day alarm + */ + EVENT_TODAI = 25, + /*! + * ON1B event + */ + EVENT_ONOFD1I = 27, + /*! + * ON2B event + */ + EVENT_ONOFD2I = 28, + /*! + * ON3B event + */ + EVENT_ONOFD3I = 29, + /*! + * System reset + */ + EVENT_SYSRSTI = 30, + /*! + * RTC reset occurred + */ + EVENT_RTCRSTI = 31, + /*! + * Power cut event + */ + EVENT_PCI = 32, + /*! + * Warm start event + */ + EVENT_WARMI = 33, + /*! + * Memory hold event + */ + EVENT_MEMHLDI = 34, + /*! + * Power ready + */ + EVENT_PWRRDYI = 35, + /*! + * Thermal warning lower threshold + */ + EVENT_THWARNLI = 36, + /*! + * Thermal warning higher threshold + */ + EVENT_THWARNHI = 37, + /*! + * Clock source change + */ + EVENT_CLKI = 38, + /*! + * Semaphore + */ + EVENT_SEMAFI = 39, + /*! + * Microphone bias 2 detect + */ + EVENT_MC2BI = 41, + /*! + * Headset attach + */ + EVENT_HSDETI = 42, + /*! + * Stereo headset detect + */ + EVENT_HSLI = 43, + /*! + * Thermal shutdown ALSP + */ + EVENT_ALSPTHI = 44, + /*! + * Short circuit on AHS outputs + */ + EVENT_AHSSHORTI = 45, + /*! + * number of event + */ + EVENT_NB, +} type_event; + +/*! + * This enumeration all senses of MC13783. + */ +typedef enum { + /*! + * Charger attach sense + */ + SENSE_CHGDETS = 6, + /*! + * Charger over-voltage sense + */ + SENSE_CHGOVS, + /*! + * Charger reverse current + * If 1 current flows into phone + */ + SENSE_CHGREVS, + /*! + * Charger short circuit + */ + SENSE_CHGSHORTS, + /*! + * Charger regulator operating mode + */ + SENSE_CCCVS, + /*! + * Charger current below threshold + */ + SENSE_CHGCURRS, + /*! + * BP turn on + */ + SENSE_BPONS, + /*! + * Low bat detect + */ + SENSE_LOBATLS, + /*! + * Low bat warning + */ + SENSE_LOBATHS, + /*! + * UDPS + */ + SENSE_UDPS, + /*! + * USB 4V4 + */ + SENSE_USB4V4S, + /*! + * USB 2V0 + */ + SENSE_USB2V0S, + /*! + * USB 0V8 + */ + SENSE_USB0V8S, + /*! + * ID Floats + */ + SENSE_ID_FLOATS, + /*! + * ID Gnds + */ + SENSE_ID_GNDS, + /*! + * Single ended + */ + SENSE_SE1S, + /*! + * Car-kit detect + */ + SENSE_CKDETS, + /*! + * UDMS + */ + SENSE_UDMS, + /*! + * mic bias detect + */ + SENSE_MC2BS, + /*! + * headset attached + */ + SENSE_HSDETS, + /*! + * ST headset attached + */ + SENSE_HSLS, + /*! + * Thermal shutdown ALSP + */ + SENSE_ALSPTHS, + /*! + * short circuit on AHS + */ + SENSE_AHSSHORTS, + /*! + * ON1B pin is hight + */ + SENSE_ONOFD1S, + /*! + * ON2B pin is hight + */ + SENSE_ONOFD2S, + /*! + * ON3B pin is hight + */ + SENSE_ONOFD3S, + /*! + * System reset power ready + */ + SENSE_PWRRDYS, + /*! + * Thermal warning higher threshold + */ + SENSE_THWARNHS, + /*! + * Thermal warning lower threshold + */ + SENSE_THWARNLS, + /*! + * Clock source is XTAL + */ + SENSE_CLKS, +} t_sensor; + +/*! + * This structure is used to read all sense bits of MC13783. + */ +typedef struct { + /*! + * Charger attach sense + */ + bool sense_chgdets; + /*! + * Charger over-voltage sense + */ + bool sense_chgovs; + /*! + * Charger reverse current + * If 1 current flows into phone + */ + bool sense_chgrevs; + /*! + * Charger short circuit + */ + bool sense_chgshorts; + /*! + * Charger regulator operating mode + */ + bool sense_cccvs; + /*! + * Charger current below threshold + */ + bool sense_chgcurrs; + /*! + * BP turn on + */ + bool sense_bpons; + /*! + * Low bat detect + */ + bool sense_lobatls; + /*! + * Low bat warning + */ + bool sense_lobaths; + /*! + * USB 4V4 + */ + bool sense_usb4v4s; + /*! + * USB 2V0 + */ + bool sense_usb2v0s; + /*! + * USB 0V8 + */ + bool sense_usb0v8s; + /*! + * ID Floats + */ + bool sense_id_floats; + /*! + * ID Gnds + */ + bool sense_id_gnds; + /*! + * Single ended + */ + bool sense_se1s; + /*! + * Car-kit detect + */ + bool sense_ckdets; + /*! + * mic bias detect + */ + bool sense_mc2bs; + /*! + * headset attached + */ + bool sense_hsdets; + /*! + * ST headset attached + */ + bool sense_hsls; + /*! + * Thermal shutdown ALSP + */ + bool sense_alspths; + /*! + * short circuit on AHS + */ + bool sense_ahsshorts; + /*! + * ON1B pin is hight + */ + bool sense_onofd1s; + /*! + * ON2B pin is hight + */ + bool sense_onofd2s; + /*! + * ON3B pin is hight + */ + bool sense_onofd3s; + /*! + * System reset power ready + */ + bool sense_pwrrdys; + /*! + * Thermal warning higher threshold + */ + bool sense_thwarnhs; + /*! + * Thermal warning lower threshold + */ + bool sense_thwarnls; + /*! + * Clock source is XTAL + */ + bool sense_clks; +} t_sensor_bits; + +#endif /*CONFIG_MXC_PMIC_MC13783 */ + +#if defined(CONFIG_MXC_PMIC_MC13892_MODULE) || defined(CONFIG_MXC_PMIC_MC13892) +enum { + REG_INT_STATUS0 = 0, + REG_INT_MASK0, + REG_INT_SENSE0, + REG_INT_STATUS1, + REG_INT_MASK1, + REG_INT_SENSE1, + REG_PU_MODE_S, + REG_IDENTIFICATION, + REG_UNUSED0, + REG_ACC0, + REG_ACC1, /*10 */ + REG_UNUSED1, + REG_UNUSED2, + REG_POWER_CTL0, + REG_POWER_CTL1, + REG_POWER_CTL2, + REG_REGEN_ASSIGN, + REG_UNUSED3, + REG_MEM_A, + REG_MEM_B, + REG_RTC_TIME, /*20 */ + REG_RTC_ALARM, + REG_RTC_DAY, + REG_RTC_DAY_ALARM, + REG_SW_0, + REG_SW_1, + REG_SW_2, + REG_SW_3, + REG_SW_4, + REG_SW_5, + REG_SETTING_0, /*30 */ + REG_SETTING_1, + REG_MODE_0, + REG_MODE_1, + REG_POWER_MISC, + REG_UNUSED4, + REG_UNUSED5, + REG_UNUSED6, + REG_UNUSED7, + REG_UNUSED8, + REG_UNUSED9, /*40 */ + REG_UNUSED10, + REG_UNUSED11, + REG_ADC0, + REG_ADC1, + REG_ADC2, + REG_ADC3, + REG_ADC4, + REG_CHARGE, + REG_USB0, + REG_USB1, /*50 */ + REG_LED_CTL0, + REG_LED_CTL1, + REG_LED_CTL2, + REG_LED_CTL3, + REG_UNUSED12, + REG_UNUSED13, + REG_TRIM0, + REG_TRIM1, + REG_TEST0, + REG_TEST1, /*60 */ + REG_TEST2, + REG_TEST3, + REG_TEST4, +}; + +typedef enum { + EVENT_ADCDONEI = 0, + EVENT_ADCBISDONEI = 1, + EVENT_TSI = 2, + EVENT_VBUSVI = 3, + EVENT_IDFACI = 4, + EVENT_USBOVI = 5, + EVENT_CHGDETI = 6, + EVENT_CHGFAULTI = 7, + EVENT_CHGREVI = 8, + EVENT_CHGRSHORTI = 9, + EVENT_CCCVI = 10, + EVENT_CHGCURRI = 11, + EVENT_BPONI = 12, + EVENT_LOBATLI = 13, + EVENT_LOBATHI = 14, + EVENT_IDFLOATI = 19, + EVENT_IDGNDI = 20, + EVENT_SE1I = 21, + EVENT_CKDETI = 22, + EVENT_1HZI = 24, + EVENT_TODAI = 25, + EVENT_PWRONI = 27, + EVENT_WDIRESETI = 29, + EVENT_SYSRSTI = 30, + EVENT_RTCRSTI = 31, + EVENT_PCI = 32, + EVENT_WARMI = 33, + EVENT_MEMHLDI = 34, + EVENT_THWARNLI = 36, + EVENT_THWARNHI = 37, + EVENT_CLKI = 38, + EVENT_SCPI = 40, + EVENT_LBPI = 44, + EVENT_NB, +} type_event; + +typedef enum { + SENSE_VBUSVS = 3, + SENSE_IDFACS = 4, + SENSE_USBOVS = 5, + SENSE_CHGDETS = 6, + SENSE_CHGREVS = 8, + SENSE_CHGRSHORTS = 9, + SENSE_CCCVS = 10, + SENSE_CHGCURRS = 11, + SENSE_BPONS = 12, + SENSE_LOBATLS = 13, + SENSE_LOBATHS = 14, + SENSE_IDFLOATS = 19, + SENSE_IDGNDS = 20, + SENSE_SE1S = 21, + SENSE_PWRONS = 27, + SENSE_THWARNLS = 36, + SENSE_THWARNHS = 37, + SENSE_CLKS = 38, + SENSE_LBPS = 44, + SENSE_NB, +} t_sensor; + +typedef struct { + bool sense_vbusvs; + bool sense_idfacs; + bool sense_usbovs; + bool sense_chgdets; + bool sense_chgrevs; + bool sense_chgrshorts; + bool sense_cccvs; + bool sense_chgcurrs; + bool sense_bpons; + bool sense_lobatls; + bool sense_lobaths; + bool sense_idfloats; + bool sense_idgnds; + bool sense_se1s; + bool sense_pwrons; + bool sense_thwarnls; + bool sense_thwarnhs; + bool sense_clks; + bool sense_lbps; +} t_sensor_bits; + +extern struct i2c_client *mc13892_client; +int pmic_i2c_24bit_read(struct i2c_client *client, unsigned int reg_num, + unsigned int *value); +int pmic_read(int reg_num, unsigned int *reg_val); +int pmic_write(int reg_num, const unsigned int reg_val); +void gpio_pmic_active(void); +void pmic_event_list_init(void); +void mc13892_power_off(void); + +#endif + +#if defined(CONFIG_MXC_PMIC_MC34704_MODULE) || defined(CONFIG_MXC_PMIC_MC34704) + +typedef enum { + /* register names for mc34704 */ + REG_MC34704_GENERAL1 = 0x01, + REG_MC34704_GENERAL2 = 0x02, + REG_MC34704_GENERAL3 = 0x03, + REG_MC34704_VGSET1 = 0x04, + REG_MC34704_VGSET2 = 0x05, + REG_MC34704_REG2SET1 = 0x06, + REG_MC34704_REG2SET2 = 0x07, + REG_MC34704_REG3SET1 = 0x08, + REG_MC34704_REG3SET2 = 0x09, + REG_MC34704_REG4SET1 = 0x0A, + REG_MC34704_REG4SET2 = 0x0B, + REG_MC34704_REG5SET1 = 0x0C, + REG_MC34704_REG5SET2 = 0x0D, + REG_MC34704_REG5SET3 = 0x0E, + REG_MC34704_REG6SET1 = 0x0F, + REG_MC34704_REG6SET2 = 0x10, + REG_MC34704_REG6SET3 = 0x11, + REG_MC34704_REG7SET1 = 0x12, + REG_MC34704_REG7SET2 = 0x13, + REG_MC34704_REG7SET3 = 0x14, + REG_MC34704_REG8SET1 = 0x15, + REG_MC34704_REG8SET2 = 0x16, + REG_MC34704_REG8SET3 = 0x17, + REG_MC34704_FAULTS = 0x18, + REG_MC34704_I2CSET1 = 0x19, + REG_MC34704_REG3DAC = 0x49, + REG_MC34704_REG7CR0 = 0x58, + REG_MC34704_REG7DAC = 0x59, + REG_NB = 0x60, +} pmic_reg; + +typedef enum { + /* events for mc34704 */ + EVENT_FLT1 = 0, + EVENT_FLT2, + EVENT_FLT3, + EVENT_FLT4, + EVENT_FLT5, + EVENT_FLT6, + EVENT_FLT7, + EVENT_FLT8, + EVENT_NB, +} type_event; + +typedef enum { + MCU_SENSOR_NOT_SUPPORT +} t_sensor; + +typedef enum { + MCU_SENSOR_BIT_NOT_SUPPORT +} t_sensor_bits; + +#endif /* MXC_PMIC_MC34704 */ + +/* EXPORTED FUNCTIONS */ +#ifdef __KERNEL__ + +#if defined(CONFIG_MXC_PMIC) +/*! + * This function is used to determine the PMIC type and its revision. + * + * @return Returns the PMIC type and its revision. + */ +pmic_version_t pmic_get_version(void); + +/*! + * This function is called by PMIC clients to read a register on PMIC. + * + * @param priority priority of access + * @param reg number of register + * @param reg_value return value of register + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_read_reg(int reg, unsigned int *reg_value, + unsigned int reg_mask); +/*! + * This function is called by PMIC clients to write a register on MC13783. + * + * @param priority priority of access + * @param reg number of register + * @param reg_value New value of register + * @param reg_mask Bitmap mask indicating which bits to modify + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_write_reg(int reg, unsigned int reg_value, + unsigned int reg_mask); + +/*! + * This function is called by PMIC clients to subscribe on an event. + * + * @param event_sub structure of event, it contains type of event and callback + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_event_subscribe(type_event event, + pmic_event_callback_t callback); +/*! +* This function is called by PMIC clients to un-subscribe on an event. +* +* @param event_unsub structure of event, it contains type of event and callback +* +* @return This function returns PMIC_SUCCESS if successful. +*/ +PMIC_STATUS pmic_event_unsubscribe(type_event event, + pmic_event_callback_t callback); +/*! +* This function is called to read all sensor bits of PMIC. +* +* @param sensor Sensor to be checked. +* +* @return This function returns true if the sensor bit is high; +* or returns false if the sensor bit is low. +*/ +bool pmic_check_sensor(t_sensor sensor); + +/*! +* This function checks one sensor of PMIC. +* +* @param sensor_bits structure of all sensor bits. +* +* @return This function returns PMIC_SUCCESS if successful. +*/ +PMIC_STATUS pmic_get_sensors(t_sensor_bits * sensor_bits); + +void pmic_event_callback(type_event event); +void pmic_event_list_init(void); + +#endif /*CONFIG_MXC_PMIC*/ +#endif /* __KERNEL__ */ +/* CONFIG_MXC_PMIC_MC13783 || CONFIG_MXC_PMIC_MC9SDZ60 */ + +struct pmic_platform_data { + int (*init)(void *); + int power_key_irq; +}; + +#endif /* __ASM_ARCH_MXC_PMIC_EXTERNAL_H__ */ diff --git a/include/linux/pmic_light.h b/include/linux/pmic_light.h new file mode 100644 index 000000000000..8490dc133ccf --- /dev/null +++ b/include/linux/pmic_light.h @@ -0,0 +1,1082 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ +#ifndef __ASM_ARCH_MXC_PMIC_LIGHT_H__ +#define __ASM_ARCH_MXC_PMIC_LIGHT_H__ + +/*! + * @defgroup PMIC_LIGHT PMIC Light Driver + * @ingroup PMIC_DRVRS + */ + +/*! + * @file arch-mxc/pmic_light.h + * @brief This is the header of PMIC Light driver. + * + * @ingroup PMIC_LIGHT + */ + +#include <linux/ioctl.h> +#include <linux/pmic_status.h> +#include <linux/pmic_external.h> + +/*! + * @name IOCTL user space interface + */ + +/*! @{ */ +/*! + * Enable Backlight. + * Argument type: none. + */ +#define PMIC_BKLIT_ENABLE _IO('p', 0xe0) +/*! + * Disable Backlight. + * Argument type: none. + */ +#define PMIC_BKLIT_DISABLE _IO('p', 0xe1) +/*! + * Set backlight configuration. + * Argument type: pointer to t_bklit_setting_param + */ +#define PMIC_SET_BKLIT _IOW('p', 0xe2, int) +/*! + * Get backlight configuration. + * Argument type: pointer to t_bklit_setting_param + */ +#define PMIC_GET_BKLIT _IOWR('p', 0xe3, int) +/*! + * Ramp up configuration. + * Argument type: t_bklit_channel + */ +#define PMIC_RAMPUP_BKLIT _IOW('p', 0xe4, int) +/*! + * Ramp down configuration. + * Argument type: t_bklit_channel + */ +#define PMIC_RAMPDOWN_BKLIT _IOW('p', 0xe5, int) +/*! + * Enable Tri-color LED. + * Argument type: t_tcled_enable_param + */ +#define PMIC_TCLED_ENABLE _IOW('p', 0xe6, int) +/*! + * Disable Tri-color LED. + * Argument type: t_funlight_bank + */ +#define PMIC_TCLED_DISABLE _IOW('p', 0xe7, int) +/*! + * Start Tri-color LED pattern. + * Argument type: t_fun_param + */ +#define PMIC_TCLED_PATTERN _IOW('p', 0xe8, int) +/*! + * Enable Backlight & tcled. + * Argument type: none. + */ +#define PMIC_BKLIT_TCLED_ENABLE _IO('p', 0xe9) +/*! + * Disable Backlight & tcled. + * Argument type: none. + */ +#define PMIC_BKLIT_TCLED_DISABLE _IO('p', 0xea) +/*! + * Reset ramp up configuration. + * Argument type: t_bklit_channel + */ +#define PMIC_OFF_RAMPUP_BKLIT _IOW('p', 0xeb, int) +/*! + * Reset ramp down configuration. + * Argument type: t_bklit_channel + */ +#define PMIC_OFF_RAMPDOWN_BKLIT _IOW('p', 0xec, int) +/*! + * Set tcled ind configuration. + * Argument type: t_tcled_ind_param + */ +#define PMIC_SET_TCLED _IOW('p', 0xed, int) +/*! + * Get tcled ind configuration. + * Argument type: t_tcled_ind_param + */ +#define PMIC_GET_TCLED _IOWR('p', 0xee, int) +/*! @} */ +/*! + * @enum t_bklit_mode + * @brief Backlight modes. + */ +typedef enum { + BACKLIGHT_CURRENT_CTRL_MODE, /*! < Current control mode */ + BACKLIGHT_TRIODE_MODE /*! < Triode mode */ +} t_bklit_mode; + +/*! + * @enum t_bklit_channel + * @brief Backlight channels. + */ +typedef enum { + BACKLIGHT_LED1, /*! < Backlight channel 1 */ + BACKLIGHT_LED2, /*! < Backlight channel 2 */ + BACKLIGHT_LED3 /*! < Backlight channel 3 */ +} t_bklit_channel; + +/*! + * @enum t_bklit_strobe_mode + * @brief Backlight Strobe Light Pulsing modes. + */ +typedef enum { + /*! + * No Strobe Light Pulsing + */ + BACKLIGHT_STROBE_NONE, + /*! + * Strobe Light Pulsing at 3.3% duty cycle over 300msec (Driver goes + * into Triode Mode with pulses constrained to 10msec.) + */ + BACKLIGHT_STROBE_FAST, + /*! + * Strobe Light Pulsing at 10% duty cycle over 100msec (Driver goes + * into Triode Mode with pulses constrained to 10msec.) + */ + BACKLIGHT_STROBE_SLOW +} t_bklit_strobe_mode; + +/*! + * @struct t_bklit_setting_param + * @brief Backlight setting. + */ + +typedef struct { + t_bklit_channel channel; /*!< Channel */ + t_bklit_mode mode; /*!< Mode */ + t_bklit_strobe_mode strobe; /*!< Strobe mode */ + unsigned char current_level; /*!< Current level */ + unsigned char duty_cycle; /*!< Duty cycle */ + unsigned char cycle_time; /*!< Cycle time */ + bool edge_slow; /*!< Edge Slow */ + bool en_dis; /*!< Enable disable boost mode */ + unsigned int abms; /*!< Adaptive boost + * mode selection */ + unsigned int abr; /*!< Adaptive + * boost reference */ +} t_bklit_setting_param; + +/*! + * @enum t_funlight_bank + * @brief Tri-color LED fun light banks. + */ +typedef enum { + TCLED_FUN_BANK1 = 0, /*! < Fun light bank 1 */ + TCLED_FUN_BANK2, /*! < Fun light bank 2 */ + TCLED_FUN_BANK3 /*! < Fun light bank 3 */ +} t_funlight_bank; + +/*! + * @enum t_tcled_mode + * @brief Tri-color LED operation modes. + * + * The Tri-Color LED Driver circuitry includes 2 modes of operation. In LED + * Indicator Mode, this circuitry operates as Red and Green LED Drivers with + * flasher timing to indicate GSM network status. In Fun Light Mode, this + * circuitry provides expanded capability for current control and distribution + * that supplements the three channels. + */ +typedef enum { + TCLED_IND_MODE = 0, /*! < LED Indicator Mode */ + TCLED_FUN_MODE /*! < Fun Light Mode */ +} t_tcled_mode; + +/*! + * @struct t_tcled_enable_param + * @brief enable setting. + */ +typedef struct { + t_funlight_bank bank; /*!< Bank */ + t_tcled_mode mode; /*!< Mode */ +} t_tcled_enable_param; + +/*! + * @enum t_ind_channel + * @brief Tri-color LED indicator mode channels. + * + */ + +typedef enum { + TCLED_IND_RED = 0, /*! < Red LED */ + TCLED_IND_GREEN, /*! < Green LED */ + TCLED_IND_BLUE /*! < Blue LED */ +} t_ind_channel; + +/*! + * @enum t_funlight_channel + * @brief Tri-color LED fun light mode channels. + * + */ +typedef enum { + TCLED_FUN_CHANNEL1 = 0, /*! < Fun light channel 1 (Red) */ + TCLED_FUN_CHANNEL2, /*! < Fun light channel 2 (Green) */ + TCLED_FUN_CHANNEL3 /*! < Fun light channel 3 (Blue) */ +} t_funlight_channel; + +/*! + * @enum t_tcled_ind_blink_pattern + * @brief Tri-color LED Indicator Mode blinking mode. + */ +typedef enum { + TCLED_IND_OFF = 0, /*! < Continuous off */ + TCLED_IND_BLINK_1, /*! < 1 / 31 */ + TCLED_IND_BLINK_2, /*! < 2 / 31 */ + TCLED_IND_BLINK_3, /*! < 3 / 31 */ + TCLED_IND_BLINK_4, /*! < 4 / 31 */ + TCLED_IND_BLINK_5, /*! < 5 / 31 */ + TCLED_IND_BLINK_6, /*! < 6 / 31 */ + TCLED_IND_BLINK_7, /*! < 7 / 31 */ + TCLED_IND_BLINK_8, /*! < 8 / 31 */ + TCLED_IND_BLINK_9, /*! < 9 / 31 */ + TCLED_IND_BLINK_10, /*! < 10 / 31 */ + TCLED_IND_BLINK_11, /*! < 11 / 31 */ + TCLED_IND_BLINK_12, /*! < 12 / 31 */ + TCLED_IND_BLINK_13, /*! < 13 / 31 */ + TCLED_IND_BLINK_14, /*! < 14 / 31 */ + TCLED_IND_BLINK_15, /*! < 15 / 31 */ + TCLED_IND_BLINK_16, /*! < 16 / 31 */ + TCLED_IND_BLINK_17, /*! < 17 / 31 */ + TCLED_IND_BLINK_18, /*! < 18 / 31 */ + TCLED_IND_BLINK_19, /*! < 19 / 31 */ + TCLED_IND_BLINK_20, /*! < 20 / 31 */ + TCLED_IND_BLINK_21, /*! < 21 / 31 */ + TCLED_IND_BLINK_22, /*! < 22 / 31 */ + TCLED_IND_BLINK_23, /*! < 23 / 31 */ + TCLED_IND_BLINK_24, /*! < 24 / 31 */ + TCLED_IND_BLINK_25, /*! < 25 / 31 */ + TCLED_IND_BLINK_26, /*! < 26 / 31 */ + TCLED_IND_BLINK_27, /*! < 27 / 31 */ + TCLED_IND_BLINK_28, /*! < 28 / 31 */ + TCLED_IND_BLINK_29, /*! < 29 / 31 */ + TCLED_IND_BLINK_30, /*! < 30 / 31 */ + TCLED_IND_ON /*! < Continuous on */ +} t_tcled_ind_blink_pattern; + +/*! + * @enum t_tcled_cur_level + * @brief Tri-color LED current levels. + */ +typedef enum { + TCLED_CUR_LEVEL_1 = 0, /*! < Tri-Color LED current level 1 */ + TCLED_CUR_LEVEL_2, /*! < Tri-Color LED current level 2 */ + TCLED_CUR_LEVEL_3, /*! < Tri-Color LED current level 3 */ + TCLED_CUR_LEVEL_4 /*! < Tri-Color LED current level 4 */ +} t_tcled_cur_level; + +/*! + * @enum t_tcled_fun_cycle_time + * @brief Tri-color LED fun light mode cycle time. + */ +typedef enum { + TC_CYCLE_TIME_1 = 0, /*! < Tri-Color LED cycle time 1 */ + TC_CYCLE_TIME_2, /*! < Tri-Color LED cycle time 2 */ + TC_CYCLE_TIME_3, /*! < Tri-Color LED cycle time 3 */ + TC_CYCLE_TIME_4 /*! < Tri-Color LED cycle time 4 */ +} t_tcled_fun_cycle_time; + +/*! + * @enum t_tcled_fun_speed + * @brief Tri-color LED fun light mode pattern speed. + */ +typedef enum { + TC_OFF = 0, /*! < Tri-Color pattern off */ + TC_SLOW, /*! < Tri-Color slow pattern */ + TC_FAST /*! < Tri-Color fast pattern */ +} t_tcled_fun_speed; + +/*! + * @enum t_tcled_fun_speed + * @brief Tri-color LED fun light mode pattern speed. + */ +typedef enum { + TC_STROBE_OFF = 0, /*! < No strobe */ + TC_STROBE_SLOW, /*! < Slow strobe pattern */ + TC_STROBE_FAST /*! < fast strobe pattern */ +} t_tcled_fun_strobe_speed; + +/*! + * @enum t_chaselight_pattern + * @brief Tri-color LED fun light mode chasing light patterns. + */ +typedef enum { + PMIC_RGB = 0, /*!< R -> G -> B */ + BGR /*!< B -> G -> R */ +} t_chaselight_pattern; + +/*! + * This enumeration of Fun Light Pattern. + */ +typedef enum { + /*! + * Blended ramps slow + */ + BLENDED_RAMPS_SLOW, + /*! + * Blended ramps fast + */ + BLENDED_RAMPS_FAST, + /*! + * Saw ramps slow + */ + SAW_RAMPS_SLOW, + /*! + * Saw ramps fast + */ + SAW_RAMPS_FAST, + /*! + * Blended bowtie slow + */ + BLENDED_BOWTIE_SLOW, + /*! + * Blended bowtie fast + */ + BLENDED_BOWTIE_FAST, + /*! + * Strobe slow + */ + STROBE_SLOW, + /*! + * Strobe fast + */ + STROBE_FAST, + /*! + * Chasing Light RGB Slow + */ + CHASING_LIGHT_RGB_SLOW, + /*! + * Chasing Light RGB fast + */ + CHASING_LIGHT_RGB_FAST, + /*! + * Chasing Light BGR Slow + */ + CHASING_LIGHT_BGR_SLOW, + /*! + * Chasing Light BGR fast + */ + CHASING_LIGHT_BGR_FAST, +} t_fun_pattern; + +/*! + * @struct t_fun_param + * @brief LED fun pattern IOCTL parameter + */ +typedef struct { + t_funlight_bank bank; /*!< TCLED bank */ + t_funlight_channel channel; /*!< TCLED channel */ + t_fun_pattern pattern; /*!< Fun pattern */ +} t_fun_param; + +/*! + * @enum t_led_channel + * @brief LED channels including backlight and tri-color LEDs. + */ +typedef enum { + AUDIO_LED1, /*! < Backlight channel 1 */ + AUDIO_LED2, /*! < Backlight channel 2 */ + AUDIO_LEDR, /*! < Fun light channel 1 (Red) */ + AUDIO_LEDG, /*! < Fun light channel 2 (Green) */ + AUDIO_LEDB /*! < Fun light channel 3 (Blue) */ +} t_led_channel; + +/*! + * @enum t_aud_path + * @brief LED audio modulation in-out audio channels + */ +typedef enum { + MIXED_RX = 0, /*!< Mixed L & R Channel RX audio */ + TX /*!< TX path */ +} t_aud_path; + +/*! + * @enum t_aud_gain + * @brief LED audio modulation in-out audio channels + */ +typedef enum { + GAIN_MINUS6DB = 0, /*!< -6 dB */ + GAIN_0DB, /*!< 0 dB */ + GAIN_6DB, /*!< 6 dB */ + GAIN_12DB /*!< 12 dB */ +} t_aud_gain; + +/*! + * @struct t_tcled_ind_param + * @brief LED parameter + */ +typedef struct { + t_funlight_bank bank; /*! < tcled bank */ + t_ind_channel channel; /*! < tcled channel */ + t_tcled_cur_level level; /*! < tcled current level */ + t_tcled_ind_blink_pattern pattern; /*! < tcled dutty cycle */ + bool skip; /*! < tcled skip */ + bool rampup; /*! < tcled rampup */ + bool rampdown; /*! < tcled rampdown */ + bool half_current; /*! < tcled half current */ +} t_tcled_ind_param; + +#if defined(CONFIG_MXC_PMIC_MC13892) + +enum curr_level { + LIT_CURR_0 = 0, + LIT_CURR_3, + LIT_CURR_6, + LIT_CURR_9, + LIT_CURR_12, + LIT_CURR_15, + LIT_CURR_18, + LIT_CURR_21, + /* below setting only used for main/aux/keypad */ + LIT_CURR_HI_0, + LIT_CURR_HI_6, + LIT_CURR_HI_12, + LIT_CURR_HI_18, + LIT_CURR_HI_24, + LIT_CURR_HI_30, + LIT_CURR_HI_36, + LIT_CURR_HI_42, +}; + +enum lit_channel { + LIT_MAIN = 0, + LIT_AUX, + LIT_KEY, + LIT_RED, + LIT_GREEN, + LIT_BLUE, +}; + +#endif + +/* EXPORTED FUNCTIONS */ +#ifdef __KERNEL__ +/*! + * This function enables backlight & tcled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_tcled_master_enable(void); + +/*! + * This function disables backlight & tcled. + * + * @return This function returns PMIC_SUCCESS if successful + */ +PMIC_STATUS pmic_bklit_tcled_master_disable(void); + +/*! + * This function enables backlight. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_master_enable(void); + +/*! + * This function disables backlight. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_master_disable(void); + +/*! + * This function sets backlight current level. + * + * @param channel Backlight channel + * @param level Backlight current level, as the following table. + * @verbatim + level current + ------ ----------- + 0 0 mA + 1 12 mA + 2 24 mA + 3 36 mA + 4 48 mA + 5 60 mA + 6 72 mA + 7 84 mA + @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_current(t_bklit_channel channel, + unsigned char level); + +/*! + * This function retrives backlight current level. + * + * @param channel Backlight channel + * @param level Pointer to store backlight current level result. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_current(t_bklit_channel channel, + unsigned char *level); + +/*! + * This function sets a backlight channel duty cycle. + * LED perceived brightness for each zone may be individually set by setting + * duty cycle. The default setting is for 0% duty cycle; this keeps all zone + * drivers turned off even after the master enable command. Each LED current + * sink can be turned on and adjusted for brightness with an independent 4 bit + * word for a duty cycle ranging from 0% to 100% in approximately 6.7% steps. + * + * @param channel Backlight channel. + * @param dc Backlight duty cycle, as the following table. + * @verbatim + dc Duty Cycle (% On-time over Cycle Time) + ------ --------------------------------------- + 0 0% + 1 6.7% + 2 13.3% + 3 20% + 4 26.7% + 5 33.3% + 6 40% + 7 46.7% + 8 53.3% + 9 60% + 10 66.7% + 11 73.3% + 12 80% + 13 86.7% + 14 93.3% + 15 100% + @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_dutycycle(t_bklit_channel channel, unsigned char dc); + +/*! + * This function retrives a backlight channel duty cycle. + * + * @param channel Backlight channel. + * @param cycle Pointer to backlight duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_dutycycle(t_bklit_channel channel, + unsigned char *dc); + +/*! + * This function sets a backlight channel cycle time. + * Cycle Time is defined as the period of a complete cycle of + * Time_on + Time_off. The default Cycle Time is set to 0.01 seconds such that + * the 100 Hz on-off cycling is averaged out by the eye to eliminate + * flickering. Additionally, the Cycle Time can be programmed to intentionally + * extend the period of on-off cycles for a visual pulsating or blinking effect. + * + * @param period Backlight cycle time, as the following table. + * @verbatim + period Cycle Time + -------- ------------ + 0 0.01 seconds + 1 0.1 seconds + 2 0.5 seconds + 3 2 seconds + @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_cycle_time(unsigned char period); + +/*! + * This function retrives a backlight channel cycle time setting. + * + * @param period Pointer to save backlight cycle time setting result. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_cycle_time(unsigned char *period); + +/*! + * This function sets backlight operation mode. There are two modes of + * operations: current control and triode mode. + * The Duty Cycle/Cycle Time control is retained in Triode Mode. Audio + * coupling is not available in Triode Mode. + * + * @param channel Backlight channel. + * @param mode Backlight operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_mode(t_bklit_channel channel, t_bklit_mode mode); +/*! + * This function gets backlight operation mode. There are two modes of + * operations: current control and triode mode. + * The Duty Cycle/Cycle Time control is retained in Triode Mode. Audio + * coupling is not available in Triode Mode. + * + * @param channel Backlight channel. + * @param mode Backlight operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_mode(t_bklit_channel channel, t_bklit_mode * mode); +/*! + * This function starts backlight brightness ramp up function; ramp time is + * fixed at 0.5 seconds. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_rampup(t_bklit_channel channel); +/*! + * This function stops backlight brightness ramp up function; + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_off_rampup(t_bklit_channel channel); +/*! + * This function starts backlight brightness ramp down function; ramp time is + * fixed at 0.5 seconds. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_rampdown(t_bklit_channel channel); +/*! + * This function stops backlight brightness ramp down function. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_off_rampdown(t_bklit_channel channel); +/*! + * This function enables backlight analog edge slowing mode. Analog Edge + * Slowing slows down the transient edges to reduce the chance of coupling LED + * modulation activity into other circuits. Rise and fall times will be targeted + * for approximately 50usec. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_enable_edge_slow(void); + +/*! + * This function disables backlight analog edge slowing mode. The backlight + * drivers will default to an “Instant On” mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_disable_edge_slow(void); +/*! + * This function gets backlight analog edge slowing mode. DThe backlight + * + * @param edge Edge slowing mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_edge_slow(bool * edge); +/*! + * This function sets backlight Strobe Light Pulsing mode. + * + * @param channel Backlight channel. + * @param mode Strobe Light Pulsing mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_strobemode(t_bklit_channel channel, + t_bklit_strobe_mode mode); + +/*! + * This function enables tri-color LED. + * + * @param mode Tri-color LED operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_enable(t_tcled_mode mode, t_funlight_bank bank); +/*! + * This function disables tri-color LED. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_disable(t_funlight_bank bank); +/*! + * This function retrives tri-color LED operation mode. + * + * @param mode Pointer to Tri-color LED operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_mode(t_tcled_mode * mode, t_funlight_bank bank); +/*! + * This function sets a tri-color LED channel current level in indicator mode. + * + * @param channel Tri-color LED channel. + * @param level Current level. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_set_current(t_ind_channel channel, + t_tcled_cur_level level, + t_funlight_bank bank); +/*! + * This function retrives a tri-color LED channel current level in indicator mode. + * + * @param channel Tri-color LED channel. + * @param level Pointer to current level. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_get_current(t_ind_channel channel, + t_tcled_cur_level * level, + t_funlight_bank bank); +/*! + * This function sets a tri-color LED channel blinking pattern in indication + * mode. + * + * @param channel Tri-color LED channel. + * @param pattern Blinking pattern. + * @param skip If true, skip a cycle after each cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ + +PMIC_STATUS pmic_tcled_ind_set_blink_pattern(t_ind_channel channel, + t_tcled_ind_blink_pattern pattern, + bool skip, t_funlight_bank bank); +/*! + * This function retrives a tri-color LED channel blinking pattern in + * indication mode. + * + * @param channel Tri-color LED channel. + * @param pattern Pointer to Blinking pattern. + * @param skip Pointer to a boolean variable indicating if skip + * a cycle after each cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_get_blink_pattern(t_ind_channel channel, + t_tcled_ind_blink_pattern * + pattern, bool * skip, + t_funlight_bank bank); +/*! + * This function sets a tri-color LED channel current level in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param level Current level. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_current(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_cur_level level); + +/*! + * This function retrives a tri-color LED channel current level + * in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param level Pointer to current level. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_current(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_cur_level * level); + +/*! + * This function sets tri-color LED cycle time in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param ct Cycle time. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_cycletime(t_funlight_bank bank, + t_tcled_fun_cycle_time ct); + +/*! + * This function retrives tri-color LED cycle time in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param ct Pointer to cycle time. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_cycletime(t_funlight_bank bank, + t_tcled_fun_cycle_time * ct); + +/*! + * This function sets a tri-color LED channel duty cycle in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param dc Duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_dutycycle(t_funlight_bank bank, + t_funlight_channel channel, + unsigned char dc); + +/*! + * This function retrives a tri-color LED channel duty cycle in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param dc Pointer to duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_dutycycle(t_funlight_bank bank, + t_funlight_channel channel, + unsigned char *dc); + +/*! + * This function initiates Blended Ramp fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_blendedramps(t_funlight_bank bank, + t_tcled_fun_speed speed); + +/*! + * This function initiates Saw Ramp fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_sawramps(t_funlight_bank bank, + t_tcled_fun_speed speed); + +/*! + * This function initiates Blended Bowtie fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_blendedbowtie(t_funlight_bank bank, + t_tcled_fun_speed speed); + +/*! + * This function initiates Chasing Lights fun light pattern. + * + * @param bank Tri-color LED bank + * @param pattern Chasing light pattern mode. + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_chasinglightspattern(t_funlight_bank bank, + t_chaselight_pattern pattern, + t_tcled_fun_speed speed); + +/*! + * This function initiates Strobe Mode fun light pattern. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_strobe(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_fun_strobe_speed speed); + +/*! + * This function initiates Tri-color LED brightness Ramp Up function; Ramp time + * is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampup Ramp-up configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_rampup(t_funlight_bank bank, + t_funlight_channel channel, bool rampup); +/*! + * This function gets Tri-color LED brightness Ramp Up function; Ramp time + * is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampup Ramp-up configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_fun_rampup(t_funlight_bank bank, + t_funlight_channel channel, + bool * rampup); + +/*! + * This function initiates Tri-color LED brightness Ramp Down function; Ramp + * time is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampdown Ramp-down configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_rampdown(t_funlight_bank bank, + t_funlight_channel channel, bool rampdown); +/*! + * This function initiates Tri-color LED brightness Ramp Down function; Ramp + * time is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampdown Ramp-down configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_fun_rampdown(t_funlight_bank bank, + t_funlight_channel channel, + bool * rampdown); + +/*! + * This function enables a Tri-color channel triode mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_triode_on(t_funlight_bank bank, + t_funlight_channel channel); + +/*! + * This function disables a Tri-color LED channel triode mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_triode_off(t_funlight_bank bank, + t_funlight_channel channel); + +/*! + * This function enables Tri-color LED edge slowing. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_tcled_enable_edge_slow(void); + +/*! + * This function disables Tri-color LED edge slowing. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_tcled_disable_edge_slow(void); + +/*! + * This function enables Tri-color LED half current mode. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_tcled_enable_half_current(void); + +/*! + * This function disables Tri-color LED half current mode. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_tcled_disable_half_current(void); + +/*! + * This function enables backlight or Tri-color LED audio modulation. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_tcled_enable_audio_modulation(t_led_channel channel, + t_aud_path path, + t_aud_gain gain, + bool lpf_bypass); + +/*! + * This function disables backlight or Tri-color LED audio modulation. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_tcled_disable_audio_modulation(void); +/*! + * This function enables the boost mode. + * Only on mc13783 2.0 or higher + * + * @param en_dis Enable or disable the boost mode + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_set_boost_mode(bool en_dis); + +/*! + * This function gets the boost mode. + * Only on mc13783 2.0 or higher + * + * @param en_dis Enable or disable the boost mode + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_get_boost_mode(bool * en_dis); + +/*! + * This function sets boost mode configuration + * Only on mc13783 2.0 or higher + * + * @param abms Define adaptive boost mode selection + * @param abr Define adaptive boost reference + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_config_boost_mode(unsigned int abms, unsigned int abr); + +/*! + * This function gets boost mode configuration + * Only on mc13783 2.0 or higher + * + * @param abms Define adaptive boost mode selection + * @param abr Define adaptive boost reference + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_gets_boost_mode(unsigned int *abms, unsigned int *abr); + +#if defined(CONFIG_MXC_PMIC_MC13892) + +PMIC_STATUS mc13892_bklit_set_current(enum lit_channel channel, + unsigned char level); +PMIC_STATUS mc13892_bklit_get_current(enum lit_channel channel, + unsigned char *level); +PMIC_STATUS mc13892_bklit_set_dutycycle(enum lit_channel channel, + unsigned char dc); +PMIC_STATUS mc13892_bklit_get_dutycycle(enum lit_channel channel, + unsigned char *dc); +PMIC_STATUS mc13892_bklit_set_ramp(enum lit_channel channel, int flag); +PMIC_STATUS mc13892_bklit_get_ramp(enum lit_channel channel, int *flag); +PMIC_STATUS mc13892_bklit_set_blink_p(enum lit_channel channel, int period); +PMIC_STATUS mc13892_bklit_get_blink_p(enum lit_channel channel, int *period); + +#endif + +#endif /* __KERNEL__ */ + +#endif /* __ASM_ARCH_MXC_PMIC_LIGHT_H__ */ diff --git a/include/linux/pmic_rtc.h b/include/linux/pmic_rtc.h new file mode 100644 index 000000000000..2f7aeda4d3d2 --- /dev/null +++ b/include/linux/pmic_rtc.h @@ -0,0 +1,153 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef __ASM_ARCH_MXC_PMIC_RTC_H__ +#define __ASM_ARCH_MXC_PMIC_RTC_H__ + +/*! + * @defgroup PMIC_RTC PMIC RTC Driver + * @ingroup PMIC_DRVRS + */ + +/*! + * @file arch-mxc/pmic_rtc.h + * @brief This is the header of PMIC RTC driver. + * + * @ingroup PMIC_RTC + */ + +/* + * Includes + */ +#include <linux/ioctl.h> +#include <linux/pmic_status.h> +#include <linux/pmic_external.h> + +#define PMIC_RTC_SET_TIME _IOWR('p',0xd1, int) +#define PMIC_RTC_GET_TIME _IOWR('p',0xd2, int) +#define PMIC_RTC_SET_ALARM _IOWR('p',0xd3, int) +#define PMIC_RTC_GET_ALARM _IOWR('p',0xd4, int) +#define PMIC_RTC_WAIT_ALARM _IOWR('p',0xd5, int) +#define PMIC_RTC_ALARM_REGISTER _IOWR('p',0xd6, int) +#define PMIC_RTC_ALARM_UNREGISTER _IOWR('p',0xd7, int) + +/*! + * This enumeration define all RTC interrupt + */ +typedef enum { + /*! + * Time of day alarm + */ + RTC_IT_ALARM, + /*! + * 1 Hz timetick + */ + RTC_IT_1HZ, + /*! + * RTC reset occurred + */ + RTC_IT_RST, +} t_rtc_int; + +/* + * RTC PMIC API + */ + +/* EXPORTED FUNCTIONS */ +#ifdef __KERNEL__ + +/*! + * This function set the real time clock of PMIC + * + * @param pmic_time value of date and time + * + * @return This function returns PMIC_STATUS if successful. + */ +PMIC_STATUS pmic_rtc_set_time(struct timeval *pmic_time); + +/*! + * This function get the real time clock of PMIC + * + * @param pmic_time return value of date and time + * + * @return This function returns PMIC_STATUS if successful. + */ +PMIC_STATUS pmic_rtc_get_time(struct timeval *pmic_time); + +/*! + * This function set the real time clock alarm of PMIC + * + * @param pmic_time value of date and time + * + * @return This function returns PMIC_STATUS if successful. + */ +PMIC_STATUS pmic_rtc_set_time_alarm(struct timeval *pmic_time); + +/*! + * This function get the real time clock alarm of PMIC + * + * @param pmic_time return value of date and time + * + * @return This function returns PMIC_STATUS if successful. + */ +PMIC_STATUS pmic_rtc_get_time_alarm(struct timeval *pmic_time); + +/*! + * This function wait the Alarm event + * + * @return This function returns PMIC_STATUS if successful. + */ +PMIC_STATUS pmic_rtc_wait_alarm(void); + +/*! + * This function is used to un/subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns PMIC_STATUS if successful. + */ +PMIC_STATUS pmic_rtc_event(t_rtc_int event, void *callback, bool sub); + +/*! + * This function is used to subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns PMIC_STATUS if successful. + */ +PMIC_STATUS pmic_rtc_event_sub(t_rtc_int event, void *callback); + +/*! + * This function is used to un-subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns PMIC_STATUS if successful. + */ +PMIC_STATUS pmic_rtc_event_unsub(t_rtc_int event, void *callback); + +/*! + * This function is used to tell if PMIC RTC has been correctly loaded. + * + * @return This function returns 1 if RTC was successfully loaded + * 0 otherwise. + */ +int pmic_rtc_loaded(void); + +#endif /* __KERNEL__ */ + +#endif /* __ASM_ARCH_MXC_PMIC_RTC_H__ */ diff --git a/include/linux/pmic_status.h b/include/linux/pmic_status.h new file mode 100644 index 000000000000..19410fa03f7b --- /dev/null +++ b/include/linux/pmic_status.h @@ -0,0 +1,82 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ +#ifndef __ASM_ARCH_MXC_PMIC_STATUS_H__ +#define __ASM_ARCH_MXC_PMIC_STATUS_H__ +#include <asm-generic/errno-base.h> +#ifdef __KERNEL__ +#include <asm/uaccess.h> /* copy_{from,to}_user() */ +#endif +/*! + * @file arch-mxc/pmic_status.h + * @brief PMIC APIs return code definition. + * + * @ingroup PMIC_CORE + */ + +/*! + * @enum PMIC_STATUS + * @brief Define return values for all PMIC APIs. + * + * These return values are used by all of the PMIC APIs. + * + * @ingroup PMIC + */ +typedef enum { + PMIC_SUCCESS = 0, /*!< The requested operation was successfully + completed. */ + PMIC_ERROR = -1, /*!< The requested operation could not be completed + due to an error. */ + PMIC_PARAMETER_ERROR = -2, /*!< The requested operation failed because + one or more of the parameters was + invalid. */ + PMIC_NOT_SUPPORTED = -3, /*!< The requested operation could not be + completed because the PMIC hardware + does not support it. */ + PMIC_SYSTEM_ERROR_EINTR = -EINTR, + + PMIC_MALLOC_ERROR = -5, /*!< Error in malloc function */ + PMIC_UNSUBSCRIBE_ERROR = -6, /*!< Error in un-subscribe event */ + PMIC_EVENT_NOT_SUBSCRIBED = -7, /*!< Event occur and not subscribed */ + PMIC_EVENT_CALL_BACK = -8, /*!< Error - bad call back */ + PMIC_CLIENT_NBOVERFLOW = -9, /*!< The requested operation could not be + completed because there are too many + PMIC client requests */ +} PMIC_STATUS; + +/* + * Bitfield macros that use rely on bitfield width/shift information. + */ +#define BITFMASK(field) (((1U << (field ## _WID)) - 1) << (field ## _LSH)) +#define BITFVAL(field, val) ((val) << (field ## _LSH)) +#define BITFEXT(var, bit) ((var & BITFMASK(bit)) >> (bit ## _LSH)) + +/* + * Macros implementing error handling + */ +#define CHECK_ERROR(a) \ +do { \ + int ret = (a); \ + if (ret != PMIC_SUCCESS) \ + return ret; \ +} while (0) + +#define CHECK_ERROR_KFREE(func, freeptrs) \ +do { \ + int ret = (func); \ + if (ret != PMIC_SUCCESS) { \ + freeptrs; \ + return ret; \ + } \ +} while (0); + +#endif /* __ASM_ARCH_MXC_PMIC_STATUS_H__ */ diff --git a/include/linux/soundcard.h b/include/linux/soundcard.h index 1904afedb82f..91b9b7928ab4 100644 --- a/include/linux/soundcard.h +++ b/include/linux/soundcard.h @@ -873,12 +873,16 @@ typedef struct copr_msg { #define SOUND_MIXER_READ_LINE1 MIXER_READ(SOUND_MIXER_LINE1) #define SOUND_MIXER_READ_LINE2 MIXER_READ(SOUND_MIXER_LINE2) #define SOUND_MIXER_READ_LINE3 MIXER_READ(SOUND_MIXER_LINE3) +#define SOUND_MIXER_READ_PHONEIN MIXER_READ(SOUND_MIXER_PHONEIN) +#define SOUND_MIXER_READ_PHONEOUT MIXER_READ(SOUND_MIXER_PHONEOUT) /* Obsolete macros */ #define SOUND_MIXER_READ_MUTE MIXER_READ(SOUND_MIXER_MUTE) #define SOUND_MIXER_READ_ENHANCE MIXER_READ(SOUND_MIXER_ENHANCE) #define SOUND_MIXER_READ_LOUD MIXER_READ(SOUND_MIXER_LOUD) +#define SOUND_MIXER_READ_OUTSRC MIXER_READ(SOUND_MIXER_OUTSRC) +#define SOUND_MIXER_READ_OUTMASK MIXER_READ(SOUND_MIXER_OUTMASK) #define SOUND_MIXER_READ_RECSRC MIXER_READ(SOUND_MIXER_RECSRC) #define SOUND_MIXER_READ_DEVMASK MIXER_READ(SOUND_MIXER_DEVMASK) #define SOUND_MIXER_READ_RECMASK MIXER_READ(SOUND_MIXER_RECMASK) @@ -903,6 +907,8 @@ typedef struct copr_msg { #define SOUND_MIXER_WRITE_LINE1 MIXER_WRITE(SOUND_MIXER_LINE1) #define SOUND_MIXER_WRITE_LINE2 MIXER_WRITE(SOUND_MIXER_LINE2) #define SOUND_MIXER_WRITE_LINE3 MIXER_WRITE(SOUND_MIXER_LINE3) +#define SOUND_MIXER_WRITE_PHONEIN MIXER_WRITE(SOUND_MIXER_PHONEIN) +#define SOUND_MIXER_WRITE_PHONEOUT MIXER_WRITE(SOUND_MIXER_PHONEOUT) /* Obsolete macros */ #define SOUND_MIXER_WRITE_MUTE MIXER_WRITE(SOUND_MIXER_MUTE) @@ -910,6 +916,7 @@ typedef struct copr_msg { #define SOUND_MIXER_WRITE_LOUD MIXER_WRITE(SOUND_MIXER_LOUD) #define SOUND_MIXER_WRITE_RECSRC MIXER_WRITE(SOUND_MIXER_RECSRC) +#define SOUND_MIXER_WRITE_OUTSRC MIXER_WRITE(SOUND_MIXER_OUTSRC) typedef struct mixer_info { diff --git a/include/linux/usb/fsl_xcvr.h b/include/linux/usb/fsl_xcvr.h new file mode 100644 index 000000000000..9a48bb61834c --- /dev/null +++ b/include/linux/usb/fsl_xcvr.h @@ -0,0 +1,44 @@ +#ifndef __LINUX_USB_FSL_XCVR_H +#define __LINUX_USB_FSL_XCVR_H +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +struct fsl_usb2_platform_data; + +enum usb_test_mode{ + USB_TEST_J = 1, + USB_TEST_K = 2, +}; + +/** + * @name: transceiver name + * @xcvr_type: one of PORTSC_PTS_{UTMI,SERIAL,ULPI} + * @init: transceiver- and board-specific initialization function + * @uninit: transceiver- and board-specific uninitialization function + * @set_host: + * @set_device: + * @pullup: enable or disable D+ pullup + * + */ +struct fsl_xcvr_ops { + char *name; + u32 xcvr_type; + + void (*init)(struct fsl_xcvr_ops *ops); + void (*uninit)(struct fsl_xcvr_ops *ops); + void (*suspend)(struct fsl_xcvr_ops *ops); + void (*set_host)(void); + void (*set_device)(void); + void (*set_vbus_power)(struct fsl_xcvr_ops *ops, + struct fsl_usb2_platform_data *pdata, int on); + void (*set_remote_wakeup)(u32 *view); + void (*pullup)(int on); + void(*set_test_mode)(u32 *view, enum usb_test_mode mode); +}; + +struct fsl_xcvr_power { + struct platform_device *usb_pdev; + struct regulator *regu1; + struct regulator *regu2; +}; +#endif diff --git a/sound/arm/Kconfig b/sound/arm/Kconfig index 885683a3b0bd..ae88981852b0 100644 --- a/sound/arm/Kconfig +++ b/sound/arm/Kconfig @@ -39,5 +39,54 @@ config SND_PXA2XX_AC97 Say Y or M if you want to support any AC97 codec attached to the PXA2xx AC97 interface. +config SND_MXC_SPDIF + tristate "MXC SPDIF sound card spport" + select SND_PCM + help + Say Y here to enable SPDIF sound card + +config SND_MXC_PMIC + tristate "MXC PMIC sound system" + depends on ARCH_MXC && MXC_DAM && MXC_SSI && \ + (MXC_MC13783_AUDIO || MXC_PMIC_SC55112_AUDIO) + default y + select SND_PCM + help + Say Y here to include support for soundcards based on the + MC13783 chip. + + To compile this driver as a module, choose M here: the module + will be called snd-mc13783. + +config SND_MXC_PLAYBACK_MIXING + bool "Playback Stream Mixing" + depends on (!ARCH_MX27) && (!ARCH_MXC91131) && ARCH_MXC && MXC_DAM && MXC_SSI && \ + (MXC_MC13783_AUDIO) + default n + select SND_PCM + help + Say Y here to include support mixing for soundcards based on the + MC13783 chip. This supports audio stream mixing on VCODEC for mc13783 based platforms. + Analog mixng as well as Digital mixing can be tested on these platforms. + As of now , mixing of mono files only are supported in Digital Mixing since it is done on VCODEC. + SSI 2 channel mode is used to mix 2 streams on a single SSI. This is supported on all platforms except imx27ads(imx27ads - Analog mixing only). + +config HEADSET_DETECT_ENABLE + bool "Headset Detect Enable" + depends on (!ARCH_MXC91131) && ARCH_MXC && MXC_DAM && MXC_SSI && \ + (MXC_MC13783_AUDIO) + default n + select SND_PCM + help + Say Y here to enable Headset Detect Feature. + +config SND_MXC_PMIC_IRAM + bool "MXC PMIC sound system supports IRAM" + depends on SND_MXC_PMIC && SDMA_IRAM + default n + select SND_PCM + help + It will use IRAM as the DMA buffer of ALSA playback. + endif # SND_ARM diff --git a/sound/arm/Makefile b/sound/arm/Makefile index 5a549ed6c8aa..58569cc2db11 100644 --- a/sound/arm/Makefile +++ b/sound/arm/Makefile @@ -14,3 +14,14 @@ snd-pxa2xx-lib-$(CONFIG_SND_PXA2XX_LIB_AC97) += pxa2xx-ac97-lib.o obj-$(CONFIG_SND_PXA2XX_AC97) += snd-pxa2xx-ac97.o snd-pxa2xx-ac97-objs := pxa2xx-ac97.o + +# +# Define the header file locations for PMIC drivers. +# +CFLAGS_mxc-alsa-pmic.o = -I$(srctree)/drivers/mxc +obj-$(CONFIG_SND_MXC_PMIC) += snd-mxc-alsa.o +snd-mxc-alsa-objs := mxc-alsa-pmic.o mxc-alsa-mixer.o + +CFLGS_mxc_alsa_spdif.o = -I$(TOPDIR)/drivers/mxc +obj-$(CONFIG_SND_MXC_SPDIF) += snd-spdif.o +snd-spdif-objs := mxc-alsa-spdif.o diff --git a/sound/arm/mxc-alsa-common.h b/sound/arm/mxc-alsa-common.h new file mode 100644 index 000000000000..863c0432510b --- /dev/null +++ b/sound/arm/mxc-alsa-common.h @@ -0,0 +1,68 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file mxc-alsa-common.h + * @brief + * @ingroup SOUND_DRV + */ + +#ifndef __MXC_ALSA_COMMON_H__ +#define __MXC_ALSA_COMMON_H__ + +/* Enums typically used by the Mixer support APIs + * Emunerates IP, OP and mixer sources. + */ + +typedef enum { + CODEC_DIR_OUT, + MIXER_OUT +} OUTPUT_SOURCE; + +typedef enum { + OP_NODEV = -1, + OP_EARPIECE, + OP_HANDSFREE, + OP_HEADSET, + OP_LINEOUT, + OP_MAXDEV, + OP_MONO +} OUTPUT_DEVICES; + +typedef enum { + IP_NODEV = -1, + IP_HANDSET, + IP_HEADSET, + IP_LINEIN, + IP_MAXDEV +} INPUT_DEVICES; + +extern int mxc_alsa_create_ctl(struct snd_card *card, void *p_value); + +extern int set_mixer_output_device(PMIC_AUDIO_HANDLE handle, OUTPUT_SOURCE src, + OUTPUT_DEVICES dev, bool enable); +extern int set_mixer_output_volume(PMIC_AUDIO_HANDLE handle, int volume, + OUTPUT_DEVICES dev); +extern int set_mixer_input_device(PMIC_AUDIO_HANDLE handle, INPUT_DEVICES dev, + bool enable); +extern int set_mixer_output_mono_adder(PMIC_AUDIO_MONO_ADDER_MODE mode); +extern int set_mixer_input_gain(PMIC_AUDIO_HANDLE handle, int val); +extern int set_mixer_output_balance(int bal); + +extern int get_mixer_output_device(void); +extern int get_mixer_output_volume(void); +extern int get_mixer_output_mono_adder(void); +extern int get_mixer_output_balance(void); +extern int get_mixer_input_gain(void); +extern int get_mixer_input_device(void); +#endif /* __MXC_ALSA_COMMON_H__ */ diff --git a/sound/arm/mxc-alsa-mixer.c b/sound/arm/mxc-alsa-mixer.c new file mode 100644 index 000000000000..c77120cf48eb --- /dev/null +++ b/sound/arm/mxc-alsa-mixer.c @@ -0,0 +1,410 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file mxc-alsa-mixer.c + * @brief this file implements the mxc sound driver mixer interface for ALSA. + * The mxc sound driver supports mono/stereo recording (there are + * some limitations due to hardware), mono/stereo playback and + * audio mixing. This file implements output switching, volume/balance controls + * mono adder config, I/P dev switching and gain on the PCM streams. + * Recording supports 8000 khz and 16000 khz sample rate. + * Playback supports 8000, 11025, 16000, 22050, 24000, 32000, + * 44100 and 48000 khz for mono and stereo. + * + * @ingroup SOUND_DRV + */ + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <linux/soundcard.h> +#include <mach/pmic_audio.h> +#include "mxc-alsa-common.h" +/*! + * These are the functions implemented in the ALSA PCM driver that + * are used for mixer operations + * + */ + +/*! + * These are the callback functions for mixer controls + * + */ +/* Output device control*/ +static int pmic_mixer_output_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 = 15; + uinfo->value.integer.step = 1; + return 0; +} +static int pmic_mixer_output_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int dev, i; + dev = uvalue->value.integer.value[0]; + for (i = OP_EARPIECE; i < OP_MAXDEV; i++) { + if (dev & (1 << i)) { + set_mixer_output_device(NULL, MIXER_OUT, i, 1); + } else { + set_mixer_output_device(NULL, MIXER_OUT, i, 0); + } + } + return 0; +} +static int pmic_mixer_output_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int val, ret = 0, i = 0; + for (i = OP_EARPIECE; i < OP_MAXDEV; i++) { + val = get_mixer_output_device(); + if (val & SOUND_MASK_PHONEOUT) + ret = ret | 1; + if (val & SOUND_MASK_SPEAKER) + ret = ret | 2; + if (val & SOUND_MASK_VOLUME) + ret = ret | 4; + if (val & SOUND_MASK_PCM) + ret = ret | 8; + uvalue->value.integer.value[0] = ret; + } + return 0; + +} + +/* Input gain control*/ +static int pmic_cap_volume_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 = 100; + uinfo->value.integer.step = 1; + return 0; +} +static int pmic_cap_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int val; + val = get_mixer_input_gain(); + val = val & 0xFF; + uvalue->value.integer.value[0] = val; + return 0; +} + +static int pmic_cap_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + + int vol; + vol = uvalue->value.integer.value[0]; + vol = vol | (vol << 8); + set_mixer_input_gain(NULL, vol); + return 0; +} + +/* Mono adder control*/ +static int pmic_pb_monoconfig_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 = 3; + uinfo->value.integer.step = 1; + return 0; +} +static int pmic_pb_monoconfig_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int mono; + mono = uvalue->value.integer.value[0]; + set_mixer_output_mono_adder(mono); + return 0; +} +static int pmic_pb_monoconfig_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + uvalue->value.integer.value[0] = get_mixer_output_mono_adder(); + return 0; +} + +/*! + * These are the ALSA control structures with init values + * + */ + +/* Input device control*/ +static int pmic_cap_input_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 = 7; + uinfo->value.integer.step = 1; + return 0; +} +static int pmic_cap_input_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int dev, i; + dev = uvalue->value.integer.value[0]; + for (i = IP_HANDSET; i < IP_MAXDEV; i++) { + if (dev & (1 << i)) { + set_mixer_input_device(NULL, i, 1); + } else { + set_mixer_input_device(NULL, i, 0); + } + } + return 0; +} +static int pmic_cap_input_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int val, ret = 0, i = 0; + for (i = IP_HANDSET; i < IP_MAXDEV; i++) { + val = get_mixer_input_device(); + if (val & SOUND_MASK_PHONEIN) + ret = ret | 1; + if (val & SOUND_MASK_MIC) + ret = ret | 2; + if (val & SOUND_MASK_LINE) + ret = ret | 4; + uvalue->value.integer.value[0] = ret; + } + return 0; +} + +/* Volume control*/ +static int pmic_pb_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int volume; + volume = uvalue->value.integer.value[0]; + volume = volume | (volume << 8); + set_mixer_output_volume(NULL, volume, OP_NODEV); + return 0; +} +static int pmic_pb_volume_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 = 1; + uinfo->value.integer.max = 100; + uinfo->value.integer.step = 1; + return 0; +} + +static int pmic_pb_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int val; + val = get_mixer_output_volume(); + val = val & 0xFF; + uvalue->value.integer.value[0] = val; + return 0; +} + +/* Balance control start */ +static int pmic_pb_balance_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 = 100; + uinfo->value.integer.step = 1; + return 0; +} + +static int pmic_pb_balance_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + uvalue->value.integer.value[0] = get_mixer_output_balance(); + return 0; + +} +static int pmic_pb_balance_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int bal; + bal = uvalue->value.integer.value[0]; + set_mixer_output_balance(bal); + return 0; +} + +/* Balance control end */ + +/* loopback control start */ +static int pmic_loopback_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int pmic_loopback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + uvalue->value.integer.value[0] = kcontrol->private_value; + return 0; + +} +static int pmic_loopback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + int changed; + long flag = uvalue->value.integer.value[0]; + changed = + (uvalue->value.integer.value[0] == kcontrol->private_value) ? 0 : 1; + kcontrol->private_value = uvalue->value.integer.value[0]; + if (flag) + pmic_audio_fm_output_enable(true); + else + pmic_audio_fm_output_enable(false); + + return changed; +} + +/* Loopback control end */ + +/* Kcontrol structure definitions */ +struct snd_kcontrol_new pmic_control_pb_vol __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .index = 0x00, + .info = pmic_pb_volume_info, + .get = pmic_pb_volume_get, + .put = pmic_pb_volume_put, + .private_value = 0xffab1, +}; + +struct snd_kcontrol_new pmic_control_pb_bal __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Balance Playback Volume", + .index = 0x00, + .info = pmic_pb_balance_info, + .get = pmic_pb_balance_get, + .put = pmic_pb_balance_put, + .private_value = 0xffab2, +}; +struct snd_kcontrol_new pmic_control_pb_monoconfig __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Monoconfig Playback Volume", + .index = 0x00, + .info = pmic_pb_monoconfig_info, + .get = pmic_pb_monoconfig_get, + .put = pmic_pb_monoconfig_put, + .private_value = 0xffab2, +}; +struct snd_kcontrol_new pmic_control_op_sw __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Output Playback Volume", + .index = 0x00, + .info = pmic_mixer_output_info, + .get = pmic_mixer_output_get, + .put = pmic_mixer_output_put, + .private_value = 0xffab4, +}; + +struct snd_kcontrol_new pmic_control_cap_vol __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Capture Volume", + .index = 0x00, + .info = pmic_cap_volume_info, + .get = pmic_cap_volume_get, + .put = pmic_cap_volume_put, + .private_value = 0xffab5, +}; +struct snd_kcontrol_new pmic_control_ip_sw __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Input Capture Volume", + .index = 0x00, + .info = pmic_cap_input_info, + .get = pmic_cap_input_get, + .put = pmic_cap_input_put, + .private_value = 0xffab5, +}; + +struct snd_kcontrol_new pmic_control_loop_out __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Loopback Line-in", + .index = 0x00, + .info = pmic_loopback_info, + .get = pmic_loopback_get, + .put = pmic_loopback_put, + .private_value = 0, +}; + +/*! + * This function registers the control components of ALSA Mixer + * It is called by ALSA PCM init. + * + * @param card pointer to the ALSA sound card structure. + * + * @return 0 on success, -ve otherwise. + */ +int __devinit mxc_alsa_create_ctl(struct snd_card *card, void *p_value) +{ + int err = 0; + + if ((err = + snd_ctl_add(card, snd_ctl_new1(&pmic_control_op_sw, p_value))) < 0) + return err; + + if ((err = + snd_ctl_add(card, + snd_ctl_new1(&pmic_control_pb_vol, p_value))) < 0) + return err; + if ((err = + snd_ctl_add(card, + snd_ctl_new1(&pmic_control_pb_monoconfig, + p_value))) < 0) + return err; + if ((err = + snd_ctl_add(card, + snd_ctl_new1(&pmic_control_pb_bal, p_value))) < 0) + return err; + if ((err = + snd_ctl_add(card, + snd_ctl_new1(&pmic_control_cap_vol, p_value))) < 0) + return err; + if ((err = + snd_ctl_add(card, snd_ctl_new1(&pmic_control_ip_sw, p_value))) < 0) + return err; + err = snd_ctl_add(card, snd_ctl_new1(&pmic_control_loop_out, p_value)); + if (err < 0) + return err; + + return 0; +} + +EXPORT_SYMBOL(mxc_alsa_create_ctl); diff --git a/sound/arm/mxc-alsa-pmic.c b/sound/arm/mxc-alsa-pmic.c new file mode 100644 index 000000000000..2d24f458925a --- /dev/null +++ b/sound/arm/mxc-alsa-pmic.c @@ -0,0 +1,3790 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @defgroup SOUND_DRV MXC Sound Driver for ALSA + */ + + /*! + * @file mxc-alsa-pmic.c + * @brief this fle mxc-alsa-pmic.c + * @brief this file implements the mxc sound driver interface for ALSA. + * The mxc sound driver supports mono/stereo recording (there are + * some limitations due to hardware), mono/stereo playback and + * audio mixing. + * Recording supports 8000 khz and 16000 khz sample rate. + * Playback supports 8000, 11025, 16000, 22050, 24000, 32000, + * 44100, 48000 and 96000 Hz for mono and stereo. + * This file also handles the software mixer and abstraction APIs + * that control the volume,balance,mono-adder,input and output + * devices for PMIC. + * These mixer controls shall be accessible thru alsa as well as + * OSS emulation modes + * + * @ingroup SOUND_DRV + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/ioctl.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/soundcard.h> +#include <linux/pmic_external.h> +#include <linux/pm.h> +#include <linux/fs.h> +#include <linux/clk.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/control.h> + +#include <mach/pmic_audio.h> +#include <mach/dma.h> +#include <asm/mach-types.h> + +#include <ssi/ssi.h> +#include <ssi/registers.h> +#include <dam/dam.h> + +#include "mxc-alsa-pmic.h" +#include "mxc-alsa-common.h" + +/* + * PMIC driver buffer policy. + * Customize here if the sound is not correct + */ +#define MAX_BUFFER_SIZE (32*1024) +#define DMA_BUF_SIZE (8*1024) +#define MIN_PERIOD_SIZE 64 +#define MIN_PERIOD 2 +#define MAX_PERIOD 255 + +#define AUD_MUX_CONF 0x0031010 +#define MASK_2_TS 0xfffffffc +#define MASK_1_TS 0xfffffffd +#define MASK_1_TS_MIX 0xfffffffc +#define MASK_1_TS_STDAC 0xfffffffe +#define MASK_1_TS_REC 0xfffffffe +#define SOUND_CARD_NAME "MXC" + +#ifdef CONFIG_SND_MXC_PMIC_IRAM +#define MAX_IRAM_SIZE (IRAM_SIZE - CONFIG_SDMA_IRAM_SIZE) +#define DMA_IRAM_SIZE (4*1024) +#define ADMA_BASE_PADDR (IRAM_BASE_ADDR + CONFIG_SDMA_IRAM_SIZE) +#define ADMA_BASE_VADDR (IRAM_BASE_ADDR_VIRT + CONFIG_SDMA_IRAM_SIZE) + +#if (MAX_IRAM_SIZE + CONFIG_SDMA_IRAM_SIZE) > IRAM_SIZE +#error "The IRAM size required has beyond the limitation of IC spec" +#endif + +#if (MAX_IRAM_SIZE&(DMA_IRAM_SIZE-1)) +#error "The IRAM size for DMA ring buffer should be multiples of dma buffer size" +#endif + +#endif /* CONFIG_SND_MXC_PMIC_IRAM */ + +/*! + * These defines enable DMA chaining for playback + * and capture respectively. + */ +#define MXC_SOUND_PLAYBACK_CHAIN_DMA_EN 1 +#define MXC_SOUND_CAPTURE_CHAIN_DMA_EN 1 + +/*! + * ID for this card + */ +static char *id = NULL; + +#define MXC_ALSA_MAX_PCM_DEV 3 +#define MXC_ALSA_MAX_PLAYBACK 3 +#define MXC_ALSA_MAX_CAPTURE 1 + +struct mxc_audio_platform_data *audio_data; +/*! + * This structure is the global configuration of the soundcard + * that are accessed by the mixer as well as by the playback/recording + * stream. This contains various volume, balance, mono adder settings + * + */ +typedef struct audio_mixer_control { + + /*! + * This variable holds the current active output device(s) + */ + int output_device; + + /*! + * This variable holds the current active input device. + */ + int input_device; + + /* Used only for playback/recording on codec .. Use 1 for playback + * and 0 for recording*/ + int direction; + + /*! + * This variable holds the current source for active ouput device(s) + */ + OUTPUT_SOURCE source_for_output[OP_MAXDEV]; + + /*! + * This variable says if a given output device is part of an ongoing + * playback. This variable will be set and reset by the playback stream + * when stream is activated and when stream is closed. This shall also + * be set and reset my mixer functions for enabling/disabling output devs + */ + int output_active[OP_MAXDEV]; + + /*! + * This variable holds the current volume for active input device. + * This maps to the input gain of recording device + */ + int input_volume; + + /*! + * This variable holds the current volume for playback devices. + */ + //int output_volume[OP_MAXDEV]; + int master_volume_out; + + /*! + * This variable holds the balance setting for the mixer out. + * The range is 0 to 100. 50 means both L and R equal. + * < 50 attenuates left side and > 50 attenualtes right side + */ + int mixer_balance; + + /*! + * This variable holds the current mono adder config. + */ + PMIC_AUDIO_MONO_ADDER_MODE mixer_mono_adder; + + /*! + * Semaphore used to control the access to this structure. + */ + struct semaphore sem; + + /*! + * These variables are set by PCM stream and mixer when the voice codec's / ST dac's outputs are + * connected to the analog mixer of PMIC audio chip + */ + int codec_out_to_mixer; + int stdac_out_to_mixer; + + int codec_playback_active; + int codec_capture_active; + int stdac_playback_active; + int mixing_active; + + /*! + * This variable holds the configuration of the headset which was previously enabled. + */ + int old_prof; + + PMIC_AUDIO_HANDLE stdac_handle; + PMIC_AUDIO_HANDLE voice_codec_handle; + +} audio_mixer_control_t; + +/*! + * This structure stores current state of audio configuration + * soundcard wrt a specific stream (playback on different DACs, recording on the codec etc). + * It is used to set/get current values and are NOT accessed by the Mixer. This structure shall + * be retrieved thru pcm substream pointer and hence the mixer component will have no access + * to it. There will be as many structures as the number of streams. In our case it's 3. Codec playback + * STDAC playback and voice codec recording. + * This structure will be used at the beginning of activating a stream to configure audio chip. + * + */ +typedef struct pmic_audio_device { + + PMIC_AUDIO_HANDLE handle; + /*! + * This variable holds the sample rate currently being used. + */ + int sample_rate; + + /*! + * This variable holds the current protocol PMIC is using. + * PMIC can use one of three protocols at any given time: + * normal, network and I2S. + */ + int protocol; + + /*! + * This variables tells us whether PMIC runs in + * master mode (PMIC generates audio clocks)or slave mode (AP side + * generates audio clocks) + * + * Currently the default mode is master mode because PMIC clocks have + * higher precision. + */ + int mode; + + /* This variable holds the value representing the + * base clock PMIC will use to generate internal + * clocks (BCL clock and FrameSync clock) + */ + int pll; + + /*! + * This variable holds the SSI to which PMIC is currently connected. + */ + int ssi; + + /*! + * This variable tell us whether bit clock is inverted or not. + */ + int bcl_inverted; + + /*! + * This variable tell us whether frame clock is inverted or not. + */ + int fs_inverted; + + /*! + * This variable holds the pll used for PMIC audio operations. + */ + int pll_rate; + + /*! + * This variable holds the filter that PMIC is applying to + * CODEC operations. + */ + int codec_filter; + +} pmic_audio_device_t; + +/*! + * This structure represents an audio stream in term of + * channel DMA, HW configuration on PMIC and on AudioMux/SSI + */ +typedef struct audio_stream { + /*! + * identification string + */ + char *id; + + /*! + * numeric identification + */ + int stream_id; + + /*! + * SSI ID on the ARM side + */ + int ssi; + + /*! + * DAM port on the ARM side + */ + int dam_port; + + /*! + * device identifier for DMA + */ + int dma_wchannel; + + /*! + * we are using this stream for transfer now + */ + int active:1; + + /*! + * current transfer period + */ + int period; + + /*! + * current count of transfered periods + */ + int periods; + + /*! + * are we recording - flag used to do DMA trans. for sync + */ + int tx_spin; + + /*! + * Previous offset value for resume + */ + unsigned int old_offset; +#if 0 + /*! + * Path for this stream + */ + device_data_t stream_device; +#endif + + /*! + * pmic audio chip stream specific configuration + */ + pmic_audio_device_t pmic_audio_device; + + /*! + * for locking in DMA operations + */ + spinlock_t dma_lock; + + /*! + * Alsa substream pointer + */ + struct snd_pcm_substream *stream; +} audio_stream_t; + +/*! + * This structure represents the PMIC sound card with its + * 2 streams (StDac and Codecs) and its shared parameters + */ +typedef struct snd_card_mxc_pmic_audio { + /*! + * ALSA sound card handle + */ + struct snd_card *card; + + /*! + * ALSA pcm driver type handle + */ + struct snd_pcm *pcm[MXC_ALSA_MAX_PCM_DEV]; + + /*! + * playback & capture streams handle + * We can support a maximum of two playback streams (voice-codec + * and ST-DAC) and 1 recording stream + */ + audio_stream_t s[MXC_ALSA_MAX_CAPTURE + MXC_ALSA_MAX_PLAYBACK]; + +} mxc_pmic_audio_t; + +/*! + * pmic audio chip parameters for IP/OP and volume controls + */ +audio_mixer_control_t audio_mixer_control; + +/*! + * Global variable that represents the PMIC soundcard + * with its 2 availables stream devices: stdac and codec + */ +mxc_pmic_audio_t *mxc_audio = NULL; + +/*! + * Supported playback rates array + */ +static unsigned int playback_rates_stereo[] = { + 8000, + 11025, + 12000, + 16000, + 22050, + 24000, + 32000, + 44100, + 48000, + 64000, + 96000, +}; + +static unsigned int playback_rates_mono[] = { + 8000, + 16000, +}; + +/*! + * Supported capture rates array + */ +static unsigned int capture_rates[] = { + 8000, + 16000, +}; + +/*! + * this structure represents the sample rates supported + * by PMIC for playback operations on StDac. + */ +static struct snd_pcm_hw_constraint_list hw_playback_rates_stereo = { + .count = ARRAY_SIZE(playback_rates_stereo), + .list = playback_rates_stereo, + .mask = 0, +}; + +#ifdef CONFIG_SND_MXC_PMIC_IRAM +static spinlock_t g_audio_iram_lock = SPIN_LOCK_UNLOCKED; +static int g_audio_iram_en = 1; +static int g_device_opened = 0; +extern void flush_cache_range(struct vm_area_struct *vma, unsigned long start, + unsigned long end); + +static inline int mxc_snd_enable_iram(int enable) +{ + int ret = -EBUSY; + unsigned long flags; + spin_lock_irqsave(&g_audio_iram_lock, flags); + if (!g_device_opened) { + g_audio_iram_en = (enable != 0); + ret = 0; + } + spin_unlock_irqrestore(&g_audio_iram_lock, flags); + return ret; +} + +static inline void mxc_snd_pcm_iram_get(void) +{ + unsigned long flags; + spin_lock_irqsave(&g_audio_iram_lock, flags); + g_audio_iram_en++; + spin_unlock_irqrestore(&g_audio_iram_lock, flags); +} + +static inline void mxc_snd_pcm_iram_put(void) +{ + unsigned long flags; + spin_lock_irqsave(&g_audio_iram_lock, flags); + g_audio_iram_en--; + spin_unlock_irqrestore(&g_audio_iram_lock, flags); +} + +struct snd_dma_buffer g_iram_dmab; + +#endif /* CONFIG_SND_MXC_PMIC_IRAM */ + +/*! + * this structure represents the sample rates supported + * by PMIC for playback operations on Voice codec. + */ +static struct snd_pcm_hw_constraint_list hw_playback_rates_mono = { + .count = ARRAY_SIZE(playback_rates_mono), + .list = playback_rates_mono, + .mask = 0, +}; + +/*! + * this structure represents the sample rates supported + * by PMIC for capture operations on Codec. + */ +static struct snd_pcm_hw_constraint_list hw_capture_rates = { + .count = ARRAY_SIZE(capture_rates), + .list = capture_rates, + .mask = 0, +}; + +#ifdef CONFIG_HEADSET_DETECT_ENABLE +static PMIC_HS_STATE hs_state; + +/*! + *This is used to maintain the state of the Headset*/ +static int headset_state = 0; + +/*Callback for headset event. */ +static void HSCallback(const PMIC_HS_STATE hs_st) +{ + + msleep(10); + + if (headset_state == 1) { + pmic_audio_output_disable_phantom_ground(); + headset_state = 0; + if (audio_mixer_control.stdac_playback_active) + pmic_audio_output_clear_port(audio_mixer_control. + stdac_handle, + STEREO_HEADSET_LEFT | + STEREO_HEADSET_RIGHT); + else if (audio_mixer_control.voice_codec_handle) + pmic_audio_output_clear_port(audio_mixer_control. + voice_codec_handle, + STEREO_HEADSET_LEFT | + STEREO_HEADSET_RIGHT); + + if (audio_mixer_control.old_prof & (SOUND_MASK_PHONEOUT)) { + set_mixer_output_device(NULL, MIXER_OUT, OP_EARPIECE, + 1); + } + if (audio_mixer_control.old_prof & (SOUND_MASK_VOLUME)) { + set_mixer_output_device(NULL, MIXER_OUT, OP_HANDSFREE, + 1); + } + if (audio_mixer_control.old_prof & (SOUND_MASK_SPEAKER)) { +#ifdef CONFIG_HEADSET_DETECT_ENABLE + /*This is a temporary workaround which should be removed later */ + set_mixer_output_device(NULL, MIXER_OUT, OP_MONO, 1); +#else + set_mixer_output_device(NULL, MIXER_OUT, OP_HEADSET, 1); +#endif + } + if (audio_mixer_control.old_prof & (SOUND_MASK_PCM)) { + set_mixer_output_device(NULL, MIXER_OUT, OP_LINEOUT, 1); + } + + } else { + headset_state = 1; + + pmic_audio_output_enable_phantom_ground(); + + audio_mixer_control.old_prof = + audio_mixer_control.output_device; + + if (audio_mixer_control.old_prof & (SOUND_MASK_PHONEOUT)) { + set_mixer_output_device(NULL, MIXER_OUT, OP_EARPIECE, + 0); + } + if (audio_mixer_control.old_prof & (SOUND_MASK_VOLUME)) { + set_mixer_output_device(NULL, MIXER_OUT, OP_HANDSFREE, + 0); + } + if (audio_mixer_control.old_prof & (SOUND_MASK_SPEAKER)) { + set_mixer_output_device(NULL, MIXER_OUT, OP_HEADSET, 0); + } + if (audio_mixer_control.old_prof & (SOUND_MASK_PCM)) { + set_mixer_output_device(NULL, MIXER_OUT, OP_LINEOUT, 0); + } + /*This is a temporary workaround which should be removed later */ + set_mixer_output_device(NULL, MIXER_OUT, OP_MONO, 1); + + } +} + +#endif +/*! + * This function configures audio multiplexer to support + * audio data routing in PMIC master mode. + * + * @param ssi SSI of the ARM to connect to the DAM. + */ +void configure_dam_pmic_master(int ssi) +{ + int source_port; + int target_port; + + if (ssi == SSI1) { + pr_debug("DAM: port 1 -> port 4\n"); + source_port = audio_data->src_port; + + target_port = port_4; + } else { + pr_debug("DAM: port 2 -> port 5\n"); + source_port = port_2; + target_port = port_5; + } + + dam_reset_register(source_port); + dam_reset_register(target_port); + + dam_select_mode(source_port, normal_mode); + dam_select_mode(target_port, internal_network_mode); + + dam_set_synchronous(source_port, true); + dam_set_synchronous(target_port, true); + + dam_select_RxD_source(source_port, target_port); + dam_select_RxD_source(target_port, source_port); + + dam_select_TxFS_direction(source_port, signal_out); + dam_select_TxFS_source(source_port, false, target_port); + + dam_select_TxClk_direction(source_port, signal_out); + dam_select_TxClk_source(source_port, false, target_port); + + dam_select_RxFS_direction(source_port, signal_out); + dam_select_RxFS_source(source_port, false, target_port); + + dam_select_RxClk_direction(source_port, signal_out); + dam_select_RxClk_source(source_port, false, target_port); + + dam_set_internal_network_mode_mask(target_port, 0xfc); + + writel(AUD_MUX_CONF, IO_ADDRESS(AUDMUX_BASE_ADDR) + 0x38); +} + +/*! + * This function configures the SSI in order to receive audio + * from PMIC (recording). Configuration of SSI consists mainly in + * setting the following: + * + * 1) SSI to use (SSI1 or SSI2) + * 2) SSI mode (normal or network. We use always network mode) + * 3) SSI STCCR register settings, which control the sample rate (BCL and + * FS clocks) + * 4) Watermarks for SSI FIFOs as well as timeslots to be used. + * 5) Enable SSI. + * + * @param substream pointer to the structure of the current stream. + */ +void configure_ssi_rx(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + int ssi; + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[substream->pstr->stream]; + ssi = s->ssi; + + pr_debug("configure_ssi_rx: SSI %d\n", ssi + 1); + + ssi_enable(ssi, false); + ssi_synchronous_mode(ssi, true); + ssi_network_mode(ssi, true); + + if (machine_is_mx27ads()) { + ssi_tx_clock_divide_by_two(ssi, 0); + ssi_tx_clock_prescaler(ssi, 0); + ssi_tx_frame_rate(ssi, 2); + } + /* OJO */ + ssi_tx_frame_rate(ssi, 1); + + ssi_tx_early_frame_sync(ssi, ssi_frame_sync_one_bit_before); + ssi_tx_frame_sync_length(ssi, ssi_frame_sync_one_bit); + ssi_tx_word_length(ssi, ssi_16_bits); + + ssi_rx_early_frame_sync(ssi, ssi_frame_sync_one_bit_before); + ssi_rx_frame_sync_length(ssi, ssi_frame_sync_one_bit); + ssi_rx_fifo_enable(ssi, ssi_fifo_0, true); + ssi_rx_bit0(ssi, true); + + ssi_rx_fifo_full_watermark(ssi, ssi_fifo_0, RX_WATERMARK); + + /* We never use the divider by 2 implemented in SSI */ + ssi_rx_clock_divide_by_two(ssi, 0); + + /* Set prescaler range (a fixed divide-by-eight prescaler + * in series with the variable prescaler) to 0 as we don't + * need it. + */ + ssi_rx_clock_prescaler(ssi, 0); + + /* Currently, only supported sample length is 16 bits */ + ssi_rx_word_length(ssi, ssi_16_bits); + + /* set direction of clocks ("externally" means that clocks come + * from PMIC to MCU) + */ + ssi_rx_frame_direction(ssi, ssi_tx_rx_externally); + ssi_rx_clock_direction(ssi, ssi_tx_rx_externally); + + /* Frame Rate Divider Control. + * In Normal mode, this ratio determines the word + * transfer rate. In Network mode, this ration sets + * the number of words per frame. + */ + ssi_tx_frame_rate(ssi, 4); + ssi_rx_frame_rate(ssi, 4); + + ssi_enable(ssi, true); +} + +/*! + * This function configures the SSI in order to + * send data to PMIC. Configuration of SSI consists + * mainly in setting the following: + * + * 1) SSI to use (SSI1 or SSI2) + * 2) SSI mode (normal for normal use e.g. playback, network for mixing) + * 3) SSI STCCR register settings, which control the sample rate (BCL and + * FS clocks) + * 4) Watermarks for SSI FIFOs as well as timeslots to be used. + * 5) Enable SSI. + * + * @param substream pointer to the structure of the current stream. + */ +void configure_ssi_tx(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + struct snd_pcm_runtime *runtime; + int ssi; + int device, stream_id = -1; + device = substream->pcm->device; + if (device == 0) + stream_id = 0; + else if (device == 1) + stream_id = 2; + else + stream_id = 3; + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[stream_id]; + runtime = substream->runtime; + ssi = s->ssi; + + pr_debug("configure_ssi_tx: SSI %d\n", ssi + 1); + + ssi_enable(ssi, false); + ssi_synchronous_mode(ssi, true); + if (runtime->channels == 1) { + if (stream_id == 2) { + ssi_network_mode(ssi, true); + } else { +#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING + ssi_network_mode(ssi, false); +#endif + } + } else { + ssi_network_mode(ssi, true); + } + +#ifdef CONFIG_SND_MXC_PLAYBACK_MIXING + ssi_two_channel_mode(ssi, true); + ssi_tx_fifo_enable(ssi, ssi_fifo_1, true); + ssi_tx_fifo_empty_watermark(ssi, ssi_fifo_1, TX_WATERMARK); +#endif + + ssi_tx_early_frame_sync(ssi, ssi_frame_sync_one_bit_before); + ssi_tx_frame_sync_length(ssi, ssi_frame_sync_one_bit); + ssi_tx_fifo_enable(ssi, ssi_fifo_0, true); + ssi_tx_bit0(ssi, true); + + ssi_tx_fifo_empty_watermark(ssi, ssi_fifo_0, TX_WATERMARK); + + /* We never use the divider by 2 implemented in SSI */ + ssi_tx_clock_divide_by_two(ssi, 0); + + ssi_tx_clock_prescaler(ssi, 0); + + /*Currently, only supported sample length is 16 bits */ + ssi_tx_word_length(ssi, ssi_16_bits); + + /* clocks are being provided by PMIC */ + ssi_tx_frame_direction(ssi, ssi_tx_rx_externally); + ssi_tx_clock_direction(ssi, ssi_tx_rx_externally); + + if (runtime->channels == 1) { +#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING + if (stream_id == 2) { + ssi_tx_frame_rate(ssi, 4); + } else { + ssi_tx_frame_rate(ssi, 1); + } +#else + if (stream_id == 2) { + ssi_tx_frame_rate(ssi, 2); + } +#endif + + } else { + ssi_tx_frame_rate(ssi, 2); + } + + ssi_enable(ssi, true); +} + +/*! + * This function normalizes speed given by the user + * if speed is not supported, the function will + * calculate the nearest one. + * + * @param speed speed requested by the user. + * + * @return The normalized speed. + */ +int adapt_speed(int speed) +{ + + /* speeds from 8k to 96k */ + if (speed >= (64000 + 96000) / 2) { + speed = 96000; + } else if (speed >= (48000 + 64000) / 2) { + speed = 64000; + } else if (speed >= (44100 + 48000) / 2) { + speed = 48000; + } else if (speed >= (32000 + 44100) / 2) { + speed = 44100; + } else if (speed >= (24000 + 32000) / 2) { + speed = 32000; + } else if (speed >= (22050 + 24000) / 2) { + speed = 24000; + } else if (speed >= (16000 + 22050) / 2) { + speed = 22050; + } else if (speed >= (12000 + 16000) / 2) { + speed = 16000; + } else if (speed >= (11025 + 12000) / 2) { + speed = 12000; + } else if (speed >= (8000 + 11025) / 2) { + speed = 11025; + } else { + speed = 8000; + } + return speed; +} + +/*! + * This function get values to be put in PMIC registers. + * This values represents the sample rate that PMIC + * should use for current playback or recording. + * + * @param substream pointer to the structure of the current stream. + */ +void normalize_speed_for_pmic(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + pmic_audio_device_t *pmic_device; + struct snd_pcm_runtime *runtime; + int device, stream_id = -1; + device = substream->pcm->device; + if (device == 0) { + if ((audio_mixer_control.codec_capture_active == 1) + && (substream->stream == 1)) { + stream_id = 1; + } else + stream_id = 0; + } else { + stream_id = 2; + } + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[stream_id]; + pmic_device = &s->pmic_audio_device; + runtime = substream->runtime; + + /* As the driver allows continuous sample rate, we must adapt the rate */ + runtime->rate = adapt_speed(runtime->rate); + + if (pmic_device->handle == audio_mixer_control.voice_codec_handle) { + switch (runtime->rate) { + case 8000: + pmic_device->sample_rate = VCODEC_RATE_8_KHZ; + break; + case 16000: + pmic_device->sample_rate = VCODEC_RATE_16_KHZ; + break; + default: + pmic_device->sample_rate = VCODEC_RATE_8_KHZ; + break; + } + + } else if (pmic_device->handle == audio_mixer_control.stdac_handle) { + switch (runtime->rate) { + case 8000: + pmic_device->sample_rate = STDAC_RATE_8_KHZ; + break; + + case 11025: + pmic_device->sample_rate = STDAC_RATE_11_025_KHZ; + break; + + case 12000: + pmic_device->sample_rate = STDAC_RATE_12_KHZ; + break; + + case 16000: + pmic_device->sample_rate = STDAC_RATE_16_KHZ; + break; + + case 22050: + pmic_device->sample_rate = STDAC_RATE_22_050_KHZ; + break; + + case 24000: + pmic_device->sample_rate = STDAC_RATE_24_KHZ; + break; + + case 32000: + pmic_device->sample_rate = STDAC_RATE_32_KHZ; + break; + + case 44100: + pmic_device->sample_rate = STDAC_RATE_44_1_KHZ; + break; + + case 48000: + pmic_device->sample_rate = STDAC_RATE_48_KHZ; + break; + + case 64000: + pmic_device->sample_rate = STDAC_RATE_64_KHZ; + break; + + case 96000: + pmic_device->sample_rate = STDAC_RATE_96_KHZ; + break; + + default: + pmic_device->sample_rate = STDAC_RATE_8_KHZ; + } + } + +} + +/*! + * This function configures number of channels for next audio operation + * (recording/playback) Number of channels define if sound is stereo + * or mono. + * + * @param substream pointer to the structure of the current stream. + * + */ +void set_pmic_channels(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + struct snd_pcm_runtime *runtime; + int device = -1, stream_id = -1; + + chip = snd_pcm_substream_chip(substream); + device = substream->pcm->device; + + if (device == 0) { + if (substream->pstr->stream == 1) { + stream_id = 1; + } else { + stream_id = 0; + } + } else { + stream_id = 2; + } + s = &chip->s[stream_id]; + runtime = substream->runtime; + + if (runtime->channels == 2) { + ssi_tx_mask_time_slot(s->ssi, MASK_2_TS); + ssi_rx_mask_time_slot(s->ssi, MASK_1_TS_REC); + } else { + if (stream_id == 2) { +#ifdef CONFIG_MXC_PMIC_SC55112 + ssi_tx_mask_time_slot(s->ssi, MASK_1_TS_REC); +#else + + if (audio_mixer_control.mixing_active == 1) + ssi_tx_mask_time_slot(s->ssi, MASK_1_TS_MIX); + else + ssi_tx_mask_time_slot(s->ssi, MASK_1_TS); + +#endif + } else { + + ssi_tx_mask_time_slot(s->ssi, MASK_1_TS_STDAC); + } +#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING + ssi_rx_mask_time_slot(s->ssi, MASK_1_TS_REC); +#endif + + } + +} + +/*! + * This function sets the input device in PMIC. It takes an + * ALSA value and modifies registers using pmic-specific values. + * + * @param handle Handle to the PMIC device opened + * @param val ALSA value. This value defines the input device that + * PMIC should activate to get audio signal (recording) + * @param enable Whether to enable or diable the input + */ +int set_mixer_input_device(PMIC_AUDIO_HANDLE handle, INPUT_DEVICES dev, + bool enable) +{ + + if (down_interruptible(&audio_mixer_control.sem)) + return -EINTR; + if (handle != NULL) { + if (audio_mixer_control.input_device & SOUND_MASK_PHONEIN) { + pmic_audio_vcodec_set_mic(handle, MIC1_LEFT, + MIC1_RIGHT_MIC_MONO); + pmic_audio_vcodec_enable_micbias(handle, MIC_BIAS1); + } else { + pmic_audio_vcodec_set_mic_on_off(handle, + MIC1_LEFT, + MIC1_RIGHT_MIC_MONO); + pmic_audio_vcodec_disable_micbias(handle, MIC_BIAS1); + } + if (audio_mixer_control.input_device & SOUND_MASK_MIC) { + pmic_audio_vcodec_set_mic(handle, NO_MIC, MIC2_AUX); + pmic_audio_vcodec_enable_micbias(handle, MIC_BIAS2); + } else { + pmic_audio_vcodec_set_mic_on_off(handle, NO_MIC, + MIC2_AUX); + pmic_audio_vcodec_disable_micbias(handle, MIC_BIAS2); + } + if (audio_mixer_control.input_device & SOUND_MASK_LINE) { + pmic_audio_vcodec_set_mic(handle, NO_MIC, TXIN_EXT); + } else { + pmic_audio_vcodec_set_mic_on_off(handle, NO_MIC, + TXIN_EXT); + } + up(&audio_mixer_control.sem); + return 0; + + } + switch (dev) { + case IP_HANDSET: + pr_debug("Input: SOUND_MASK_PHONEIN \n"); + if (handle == NULL) { + if (enable) { + if (audio_mixer_control.codec_capture_active) { + handle = + audio_mixer_control. + voice_codec_handle; + pmic_audio_vcodec_set_mic(handle, + MIC1_LEFT, + MIC1_RIGHT_MIC_MONO); + pmic_audio_vcodec_enable_micbias(handle, + MIC_BIAS1); + } + audio_mixer_control.input_device |= + SOUND_MASK_PHONEIN; + } else { + if (audio_mixer_control.codec_capture_active) { + handle = + audio_mixer_control. + voice_codec_handle; + pmic_audio_vcodec_set_mic_on_off(handle, + MIC1_LEFT, + MIC1_RIGHT_MIC_MONO); + pmic_audio_vcodec_disable_micbias + (handle, MIC_BIAS1); + } + audio_mixer_control.input_device &= + ~SOUND_MASK_PHONEIN; + } + } + break; + + case IP_HEADSET: + if (handle == NULL) { + if (enable) { + if (audio_mixer_control.codec_capture_active) { + handle = + audio_mixer_control. + voice_codec_handle; + pmic_audio_vcodec_set_mic(handle, + NO_MIC, + MIC2_AUX); + pmic_audio_vcodec_enable_micbias(handle, + MIC_BIAS2); + } + audio_mixer_control.input_device |= + SOUND_MASK_MIC; + } else { + if (audio_mixer_control.codec_capture_active) { + handle = + audio_mixer_control. + voice_codec_handle; + pmic_audio_vcodec_set_mic_on_off(handle, + NO_MIC, + MIC2_AUX); + pmic_audio_vcodec_disable_micbias + (handle, MIC_BIAS2); + } + audio_mixer_control.input_device &= + ~SOUND_MASK_MIC; + } + // Enable Mic with MIC2_AUX + } + break; + + case IP_LINEIN: + if (handle == NULL) { + if (enable) { + if (audio_mixer_control.codec_capture_active) { + handle = + audio_mixer_control. + voice_codec_handle; + pmic_audio_vcodec_set_mic(handle, + NO_MIC, + TXIN_EXT); + } + audio_mixer_control.input_device |= + SOUND_MASK_LINE; + } else { + if (audio_mixer_control.codec_capture_active) { + handle = + audio_mixer_control. + voice_codec_handle; + pmic_audio_vcodec_set_mic_on_off(handle, + NO_MIC, + TXIN_EXT); + } + audio_mixer_control.input_device &= + ~SOUND_MASK_LINE; + } + } + break; + + default: + up(&audio_mixer_control.sem); + return -1; + break; + } + up(&audio_mixer_control.sem); + return 0; +} + +EXPORT_SYMBOL(set_mixer_input_device); + +int get_mixer_input_device() +{ + int val; + val = audio_mixer_control.input_device; + return val; +} + +EXPORT_SYMBOL(get_mixer_input_device); + +/*! + * This function sets the PMIC input device's gain. + * Note that the gain is the input volume + * + * @param handle Handle to the opened PMIC device + * @param val gain to be applied. This value can go + * from 0 (mute) to 100 (max gain) + */ +int set_mixer_input_gain(PMIC_AUDIO_HANDLE handle, int val) +{ + int leftdb, rightdb; + int left, right; + + left = (val & 0x00ff); + right = ((val & 0xff00) >> 8); + if (down_interruptible(&audio_mixer_control.sem)) + return -EINTR; + leftdb = (left * PMIC_INPUT_VOLUME_MAX) / INPUT_VOLUME_MAX; + rightdb = (right * PMIC_INPUT_VOLUME_MAX) / INPUT_VOLUME_MAX; + audio_mixer_control.input_volume = val; + if (audio_mixer_control.voice_codec_handle == handle) { + pmic_audio_vcodec_set_record_gain(handle, VOLTAGE_TO_VOLTAGE, + leftdb, VOLTAGE_TO_VOLTAGE, + rightdb); + } else if ((handle == NULL) + && (audio_mixer_control.codec_capture_active)) { + pmic_audio_vcodec_set_record_gain(audio_mixer_control. + voice_codec_handle, + VOLTAGE_TO_VOLTAGE, leftdb, + VOLTAGE_TO_VOLTAGE, rightdb); + } + up(&audio_mixer_control.sem); + return 0; +} + +EXPORT_SYMBOL(set_mixer_input_gain); + +int get_mixer_input_gain() +{ + int val; + val = audio_mixer_control.input_volume; + return val; +} + +EXPORT_SYMBOL(get_mixer_input_gain); + +/*! + * This function sets the PMIC output device's volume. + * + * @param handle Handle to the PMIC device opened + * @param volume ALSA value. This value defines the playback volume + * @param dev which output device gets affected by this volume + * + */ + +int set_mixer_output_volume(PMIC_AUDIO_HANDLE handle, int volume, + OUTPUT_DEVICES dev) +{ + int leftdb, rightdb; + int right, left; + + if (down_interruptible(&audio_mixer_control.sem)) + return -EINTR; + left = (volume & 0x00ff); + right = ((volume & 0xff00) >> 8); + + leftdb = (left * PMIC_OUTPUT_VOLUME_MAX) / OUTPUT_VOLUME_MAX; + rightdb = (right * PMIC_OUTPUT_VOLUME_MAX) / OUTPUT_VOLUME_MAX; + if (handle == NULL) { + /* Invoked by mixer */ + audio_mixer_control.master_volume_out = volume; + if (audio_mixer_control.codec_playback_active) + pmic_audio_output_set_pgaGain(audio_mixer_control. + voice_codec_handle, + rightdb); + if (audio_mixer_control.stdac_playback_active) + pmic_audio_output_set_pgaGain(audio_mixer_control. + stdac_handle, rightdb); + + } else { + /* change the required volume */ + audio_mixer_control.master_volume_out = volume; + pmic_audio_output_set_pgaGain(handle, rightdb); + } + up(&audio_mixer_control.sem); + return 0; +} + +EXPORT_SYMBOL(set_mixer_output_volume); + +int get_mixer_output_volume() +{ + int val; + val = audio_mixer_control.master_volume_out; + return val; +} + +EXPORT_SYMBOL(get_mixer_output_volume); + +/*! + * This function sets the PMIC output device's balance. + * + * @param bal Balance to be applied. This value can go + * from 0 (Left atten) to 100 (Right atten) + * 50 is both equal + */ +int set_mixer_output_balance(int bal) +{ + int channel = 0; + PMIC_AUDIO_OUTPUT_BALANCE_GAIN b_gain; + PMIC_AUDIO_HANDLE handle; + if (down_interruptible(&audio_mixer_control.sem)) + return -EINTR; + // Convert ALSA value to PMIC value i.e. atten and channel value + if (bal < 0) + bal = 0; + if (bal > 100) + bal = 100; + if (bal < 50) { + channel = 1; + } else { + bal = 100 - bal; + channel = 0; + } + + b_gain = bal / 8; + + audio_mixer_control.mixer_balance = bal; + if (audio_mixer_control.codec_playback_active) { + handle = audio_mixer_control.voice_codec_handle; + // Use codec's handle to set balance + } else if (audio_mixer_control.stdac_playback_active) { + handle = audio_mixer_control.stdac_handle; + // Use STDac's handle to set balance + } else { + up(&audio_mixer_control.sem); + return 0; + } + if (channel == 0) + pmic_audio_output_set_balance(handle, BAL_GAIN_0DB, b_gain); + else + pmic_audio_output_set_balance(handle, b_gain, BAL_GAIN_0DB); + up(&audio_mixer_control.sem); + return 0; +} + +EXPORT_SYMBOL(set_mixer_output_balance); + +int get_mixer_output_balance() +{ + int val; + val = audio_mixer_control.mixer_balance; + return val; +} + +EXPORT_SYMBOL(get_mixer_output_balance); + +/*! + * This function sets the PMIC output device's mono adder config. + * + * @param mode Mono adder mode to be set + */ +int set_mixer_output_mono_adder(PMIC_AUDIO_MONO_ADDER_MODE mode) +{ + PMIC_AUDIO_HANDLE handle; + if (down_interruptible(&audio_mixer_control.sem)) + return -EINTR; + audio_mixer_control.mixer_mono_adder = mode; + if (audio_mixer_control.codec_playback_active) { + handle = audio_mixer_control.voice_codec_handle; + // Use codec's handle to set balance + pmic_audio_output_enable_mono_adder(audio_mixer_control. + voice_codec_handle, mode); + } else if (audio_mixer_control.stdac_playback_active) { + handle = audio_mixer_control.stdac_handle; + pmic_audio_output_enable_mono_adder(audio_mixer_control. + stdac_handle, mode); + // Use STDac's handle to set balance + } + up(&audio_mixer_control.sem); + return 0; +} + +EXPORT_SYMBOL(set_mixer_output_mono_adder); + +int get_mixer_output_mono_adder() +{ + int val; + val = audio_mixer_control.mixer_mono_adder; + return val; +} + +EXPORT_SYMBOL(get_mixer_output_mono_adder); + +/*! + * This function sets the output device(s) in PMIC. It takes an + * ALSA value and modifies registers using PMIC-specific values. + * + * @param handle handle to the device already opened + * @param src Source connected to o/p device + * @param dev Output device to be enabled + * @param enable Enable or disable the device + * + */ +int set_mixer_output_device(PMIC_AUDIO_HANDLE handle, OUTPUT_SOURCE src, + OUTPUT_DEVICES dev, bool enable) +{ + PMIC_AUDIO_OUTPUT_PORT port; + if (down_interruptible(&audio_mixer_control.sem)) + return -EINTR; + if (!((src == CODEC_DIR_OUT) || (src == MIXER_OUT))) { + up(&audio_mixer_control.sem); + return -1; + } + if (handle != (PMIC_AUDIO_HANDLE) NULL) { + /* Invoked by playback stream */ + if (audio_mixer_control.output_device & SOUND_MASK_PHONEOUT) { + audio_mixer_control.output_active[OP_EARPIECE] = 1; + pmic_audio_output_set_port(handle, MONO_SPEAKER); + } else { + audio_mixer_control.output_active[OP_EARPIECE] = 0; + pmic_audio_output_clear_port(handle, MONO_SPEAKER); + } + if (audio_mixer_control.output_device & SOUND_MASK_SPEAKER) { + audio_mixer_control.output_active[OP_HANDSFREE] = 1; + pmic_audio_output_set_port(handle, MONO_LOUDSPEAKER); + } else { + audio_mixer_control.output_active[OP_HANDSFREE] = 0; + pmic_audio_output_clear_port(handle, MONO_LOUDSPEAKER); + } + if (audio_mixer_control.output_device & SOUND_MASK_VOLUME) { + audio_mixer_control.output_active[OP_HEADSET] = 1; + if (dev != OP_MONO) { + pmic_audio_output_set_port(handle, + STEREO_HEADSET_LEFT | + STEREO_HEADSET_RIGHT); + } else { + pmic_audio_output_set_port(handle, + STEREO_HEADSET_LEFT); + /*This is a temporary workaround which should be removed later */ + } + + } else { + audio_mixer_control.output_active[OP_HEADSET] = 0; + pmic_audio_output_clear_port(handle, + STEREO_HEADSET_LEFT | + STEREO_HEADSET_RIGHT); + } + if (audio_mixer_control.output_device & SOUND_MASK_PCM) { + audio_mixer_control.output_active[OP_LINEOUT] = 1; + pmic_audio_output_set_port(handle, + STEREO_OUT_LEFT | + STEREO_OUT_RIGHT); + } else { + audio_mixer_control.output_active[OP_LINEOUT] = 0; + pmic_audio_output_clear_port(handle, + STEREO_OUT_LEFT | + STEREO_OUT_RIGHT); + } + } else { + switch (dev) { + case OP_EARPIECE: + if (enable) { + audio_mixer_control.output_device |= + SOUND_MASK_PHONEOUT; + audio_mixer_control.source_for_output[dev] = + src; + } else { + audio_mixer_control.output_device &= + ~SOUND_MASK_PHONEOUT; + } + port = MONO_SPEAKER; + break; + case OP_HANDSFREE: + if (enable) { + audio_mixer_control.output_device |= + SOUND_MASK_SPEAKER; + audio_mixer_control.source_for_output[dev] = + src; + } else { + audio_mixer_control.output_device &= + ~SOUND_MASK_SPEAKER; + } + port = MONO_LOUDSPEAKER; + break; + case OP_HEADSET: + if (enable) { + audio_mixer_control.output_device |= + SOUND_MASK_VOLUME; + audio_mixer_control.source_for_output[dev] = + src; + } else { + audio_mixer_control.output_device &= + ~SOUND_MASK_VOLUME; + } + port = STEREO_HEADSET_LEFT | STEREO_HEADSET_RIGHT; + break; + case OP_MONO: + /*This is a temporary workaround which should be removed later */ + if (enable) { + audio_mixer_control.output_device |= + SOUND_MASK_VOLUME; + audio_mixer_control.source_for_output[dev] = + src; + } else { + audio_mixer_control.output_device &= + ~SOUND_MASK_VOLUME; + } + port = STEREO_HEADSET_LEFT; + break; + case OP_LINEOUT: + if (enable) { + audio_mixer_control.output_device |= + SOUND_MASK_PCM; + audio_mixer_control.source_for_output[dev] = + src; + } else { + audio_mixer_control.output_device &= + ~SOUND_MASK_PCM; + } + port = STEREO_OUT_LEFT | STEREO_OUT_RIGHT; + break; + default: + up(&audio_mixer_control.sem); + return -1; + break; + } + /* Invoked by mixer .. little tricky to handle over here */ + if (audio_mixer_control.codec_playback_active) { + if (enable) { + audio_mixer_control.output_active[dev] = 1; + pmic_audio_output_set_port(audio_mixer_control. + voice_codec_handle, + port); + } else { + audio_mixer_control.output_active[dev] = 0; + pmic_audio_output_clear_port + (audio_mixer_control.voice_codec_handle, + port); + } + } + if (audio_mixer_control.stdac_playback_active) { + if (enable) { + audio_mixer_control.output_active[dev] = 1; + pmic_audio_output_set_port(audio_mixer_control. + stdac_handle, port); + } else { + audio_mixer_control.output_active[dev] = 0; + pmic_audio_output_clear_port + (audio_mixer_control.stdac_handle, port); + } + } + + } + up(&audio_mixer_control.sem); + return 0; + // Set O/P device with handle and port + +} + +EXPORT_SYMBOL(set_mixer_output_device); + +int get_mixer_output_device() +{ + int val; + val = audio_mixer_control.output_device; + return val; +} + +EXPORT_SYMBOL(get_mixer_output_device); + +/*! + * This function configures the CODEC for playback/recording. + * + * main configured elements are: + * - audio path on PMIC + * - external clock to generate BC and FS clocks + * - PMIC mode (master or slave) + * - protocol + * - sample rate + * + * @param substream pointer to the structure of the current stream. + * @param stream_id index into the audio_stream array. + */ +void configure_codec(struct snd_pcm_substream *substream, int stream_id) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + pmic_audio_device_t *pmic; + PMIC_AUDIO_HANDLE handle; + int ssi_bus; + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[stream_id]; + pmic = &s->pmic_audio_device; + handle = audio_mixer_control.voice_codec_handle; + + ssi_bus = (pmic->ssi == SSI1) ? AUDIO_DATA_BUS_1 : AUDIO_DATA_BUS_2; + + pmic_audio_vcodec_set_rxtx_timeslot(handle, USE_TS0); + pmic_audio_vcodec_enable_mixer(handle, USE_TS1, VCODEC_MIX_IN_0DB, + VCODEC_MIX_OUT_0DB); + pmic_audio_set_protocol(handle, ssi_bus, pmic->protocol, pmic->mode, + USE_4_TIMESLOTS); + + msleep(20); + pmic_audio_vcodec_set_clock(handle, pmic->pll, pmic->pll_rate, + pmic->sample_rate, NO_INVERT); + msleep(20); + pmic_audio_vcodec_set_config(handle, VCODEC_MASTER_CLOCK_OUTPUTS); + pmic_audio_digital_filter_reset(handle); + msleep(15); + if (stream_id == 2) { + pmic_audio_output_enable_mixer(handle); + set_mixer_output_device(handle, MIXER_OUT, OP_NODEV, 1); + set_mixer_output_volume(handle, + audio_mixer_control.master_volume_out, + OP_HEADSET); + } else { + set_mixer_input_device(handle, IP_NODEV, 1); + set_mixer_input_gain(handle, audio_mixer_control.input_volume); + } + pmic_audio_enable(handle); +} + +/*! + * This function configures the STEREODAC for playback/recording. + * + * main configured elements are: + * - audio path on PMIC + * - external clock to generate BC and FS clocks + * - PMIC mode (master or slave) + * - protocol + * - sample rate + * + * @param substream pointer to the structure of the current stream. + */ +void configure_stereodac(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + int stream_id; + audio_stream_t *s; + pmic_audio_device_t *pmic; + int ssi_bus; + PMIC_AUDIO_HANDLE handle; + struct snd_pcm_runtime *runtime; + + chip = snd_pcm_substream_chip(substream); + stream_id = substream->pstr->stream; + s = &chip->s[stream_id]; + pmic = &s->pmic_audio_device; + handle = pmic->handle; + runtime = substream->runtime; + + if (runtime->channels == 1) { + audio_mixer_control.mixer_mono_adder = MONO_ADD_LEFT_RIGHT; + } else { + audio_mixer_control.mixer_mono_adder = MONO_ADDER_OFF; + } + + ssi_bus = (pmic->ssi == SSI1) ? AUDIO_DATA_BUS_1 : AUDIO_DATA_BUS_2; + pmic_audio_stdac_set_rxtx_timeslot(handle, USE_TS0_TS1); + pmic_audio_stdac_enable_mixer(handle, USE_TS2_TS3, STDAC_NO_MIX, + STDAC_MIX_OUT_0DB); +#ifdef CONFIG_MXC_PMIC_SC55112 + pmic_audio_set_protocol(handle, ssi_bus, pmic->protocol, pmic->mode, + USE_4_TIMESLOTS); +#else + pmic_audio_set_protocol(handle, ssi_bus, pmic->protocol, pmic->mode, + USE_2_TIMESLOTS); +#endif + pmic_audio_stdac_set_clock(handle, pmic->pll, pmic->pll_rate, + pmic->sample_rate, NO_INVERT); + + pmic_audio_stdac_set_config(handle, STDAC_MASTER_CLOCK_OUTPUTS); + pmic_audio_output_enable_mixer(handle); + audio_mixer_control.stdac_out_to_mixer = 1; + pmic_audio_digital_filter_reset(handle); + msleep(10); + pmic_audio_output_enable_phantom_ground(); + set_mixer_output_volume(handle, audio_mixer_control.master_volume_out, + OP_HEADSET); + pmic_audio_output_enable_mono_adder(handle, + audio_mixer_control. + mixer_mono_adder); +#ifdef CONFIG_HEADSET_DETECT_ENABLE + set_mixer_output_device(handle, MIXER_OUT, OP_MONO, 1); +#else + set_mixer_output_device(handle, MIXER_OUT, OP_NODEV, 1); +#endif + pmic_audio_enable(handle); + +} + +/*! + * This function disables CODEC's amplifiers, volume and clock. + * @param handle Handle of voice codec + */ + +void disable_codec(PMIC_AUDIO_HANDLE handle) +{ + pmic_audio_disable(handle); + pmic_audio_vcodec_clear_config(handle, VCODEC_MASTER_CLOCK_OUTPUTS); +} + +/*! + * This function disables STEREODAC's amplifiers, volume and clock. + * @param handle Handle of STdac + * @param + */ + +void disable_stereodac(void) +{ + + audio_mixer_control.stdac_out_to_mixer = 0; +} + +/*! + * This function configures PMIC for recording. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +int configure_pmic_recording(struct snd_pcm_substream *substream) +{ + + configure_codec(substream, 1); + return 0; +} + +/*! + * This function configures PMIC for playing back. + * + * @param substream pointer to the structure of the current stream. + * @param stream_id Index into the audio_stream array . + * + * @return 0 on success, -1 otherwise. + */ + +int configure_pmic_playback(struct snd_pcm_substream *substream, int stream_id) +{ + if (stream_id == 0) { + configure_stereodac(substream); + } else if (stream_id == 2 || stream_id == 3) { + configure_codec(substream, stream_id); + } + return 0; +} + +/*! + * This function shutsdown the PMIC soundcard. + * Nothing to be done here + * + * @param mxc_audio pointer to the sound card structure. + * + * @return + */ +/* +static void mxc_pmic_audio_shutdown(mxc_pmic_audio_t * mxc_audio) +{ + +} +*/ + +/*! + * This function configures the DMA channel used to transfer + * audio from MCU to PMIC + * + * @param substream pointer to the structure of the current stream. + * @param callback pointer to function that will be + * called when a SDMA TX transfer finishes. + * + * @return 0 on success, -1 otherwise. + */ +static int +configure_write_channel(audio_stream_t * s, mxc_dma_callback_t callback, + int stream_id) +{ + int ret = -1; + int channel = -1; +#ifdef CONFIG_SND_MXC_PLAYBACK_MIXING + + int channel1 = -1; +#endif + + if (stream_id == 0) { + if (audio_data->ssi_num == 2) { + channel = + mxc_dma_request(MXC_DMA_SSI2_16BIT_TX0, + "ALSA TX DMA"); + ret = + mxc_dma_callback_set(channel, + (mxc_dma_callback_t) callback, + (void *)s); + if (ret != 0) { + mxc_dma_free(channel); + return -1; + } + s->dma_wchannel = channel; + + } else { + channel = + mxc_dma_request(MXC_DMA_SSI1_16BIT_TX0, + "ALSA TX DMA"); + ret = + mxc_dma_callback_set(channel, + (mxc_dma_callback_t) callback, + (void *)s); + if (ret != 0) { + mxc_dma_free(channel); + return -1; + } + s->dma_wchannel = channel; + } + + } + if (stream_id == 3) { + channel = + mxc_dma_request(MXC_DMA_SSI1_16BIT_TX0, "ALSA TX DMA"); + if (channel < 0) { + pr_debug("error requesting a write dma channel\n"); + return -1; + } + ret = + mxc_dma_callback_set(channel, (mxc_dma_callback_t) callback, + (void *)s); + if (ret != 0) { + mxc_dma_free(channel); + + return -1; + } + s->dma_wchannel = channel; + } +#ifdef CONFIG_SND_MXC_PLAYBACK_MIXING + else if (stream_id == 2) { + channel1 = + mxc_dma_request(MXC_DMA_SSI1_16BIT_TX1, "ALSA TX DMA"); + ret = + mxc_dma_callback_set(channel1, + (mxc_dma_callback_t) callback, + (void *)s); + if (ret != 0) { + mxc_dma_free(channel1); + return -1; + } + s->dma_wchannel = channel1; + } +#else + + if (stream_id == 2) { + channel = + mxc_dma_request(MXC_DMA_SSI1_16BIT_TX0, "ALSA TX DMA"); + ret = + mxc_dma_callback_set(channel, (mxc_dma_callback_t) callback, + (void *)s); + if (ret != 0) { + mxc_dma_free(channel); + return -1; + } + s->dma_wchannel = channel; + } +#endif + /*if (channel < 0) { + pr_debug("error requesting a write dma channel\n"); + return -1; + } */ + + return 0; +} + +/*! + * This function configures the DMA channel used to transfer + * audio from PMIC to MCU + * + * @param substream pointer to the structure of the current stream. + * @param callback pointer to function that will be + * called when a SDMA RX transfer finishes. + * + * @return 0 on success, -1 otherwise. + */ +static int configure_read_channel(audio_stream_t * s, + mxc_dma_callback_t callback) +{ + int ret = -1; + int channel = -1; + + channel = mxc_dma_request(MXC_DMA_SSI1_16BIT_RX0, "ALSA RX DMA"); + if (channel < 0) { + pr_debug("error requesting a read dma channel\n"); + return -1; + } + + ret = + mxc_dma_callback_set(channel, (mxc_dma_callback_t) callback, + (void *)s); + if (ret != 0) { + mxc_dma_free(channel); + return -1; + } + s->dma_wchannel = channel; + + return 0; +} + +/*! + * This function frees the stream structure + * + * @param s pointer to the structure of the current stream. + */ +static void audio_dma_free(audio_stream_t * s) +{ + /* + * There is nothing to be done here since the dma channel has been + * freed either in the callback or in the stop method + */ + +} + +/*! + * This function gets the dma pointer position during record. + * Our DMA implementation does not allow to retrieve this position + * when a transfert is active, so, it answers the middle of + * the current period beeing transfered + * + * @param s pointer to the structure of the current stream. + * + */ +static u_int audio_get_capture_dma_pos(audio_stream_t * s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int offset; + + substream = s->stream; + runtime = substream->runtime; + offset = 0; + + /* tx_spin value is used here to check if a transfert is active */ + if (s->tx_spin) { + offset = (runtime->period_size * (s->periods)) + 0; + if (offset >= runtime->buffer_size) + offset = 0; + pr_debug("MXC: audio_get_dma_pos offset %d\n", offset); + } else { + offset = (runtime->period_size * (s->periods)); + if (offset >= runtime->buffer_size) + offset = 0; + pr_debug("MXC: audio_get_dma_pos BIS offset %d\n", offset); + } + + return offset; +} + +/*! + * This function gets the dma pointer position during playback. + * Our DMA implementation does not allow to retrieve this position + * when a transfert is active, so, it answers the middle of + * the current period beeing transfered + * + * @param s pointer to the structure of the current stream. + * + */ +static u_int audio_get_playback_dma_pos(audio_stream_t * s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int offset; + + substream = s->stream; + runtime = substream->runtime; + offset = 0; + + /* tx_spin value is used here to check if a transfert is active */ + if (s->tx_spin) { + offset = (runtime->period_size * (s->periods)) + 0; + if (offset >= runtime->buffer_size) + offset = 0; + pr_debug("MXC: audio_get_dma_pos offset %d\n", offset); + } else { + offset = (runtime->period_size * (s->periods)); + if (offset >= runtime->buffer_size) + offset = 0; + pr_debug("MXC: audio_get_dma_pos BIS offset %d\n", offset); + } + + return offset; +} + +/*! + * This function is called whenever a new audio block needs to be + * transferred to PMIC. The function receives the address and the size + * of the new block and start a new DMA transfer. + * + * @param substream pointer to the structure of the current stream. + * + */ +static void audio_playback_dma(audio_stream_t * s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size = 0; + unsigned int offset; + int ret = 0; + mxc_dma_requestbuf_t dma_request; +#ifdef CONFIG_SND_MXC_PLAYBACK_MIXING + unsigned int dma_size_mix = 0, offset_mix; + mxc_dma_requestbuf_t dma_request_mix; + int ret1 = 0; +#endif + int device; + int stream_id; + + substream = s->stream; + runtime = substream->runtime; + device = substream->pcm->device; + if (device == 0) { + stream_id = 0; + } else if (device == 1) { + stream_id = 2; + } else { + stream_id = 3; + } + + pr_debug("\nDMA direction %d\(0 is playback 1 is capture)\n", + s->stream_id); + +#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING + memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t)); + +#else + if (stream_id == 2) { + memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t)); + } else if (stream_id == 3) { + memset(&dma_request_mix, 0, sizeof(mxc_dma_requestbuf_t)); + } +#endif + if (s->active) { + if (ssi_get_status(s->ssi) & (ssi_transmitter_underrun_0)) { + ssi_enable(s->ssi, false); + ssi_transmit_enable(s->ssi, false); + ssi_enable(s->ssi, true); + } +#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING + + dma_size = frames_to_bytes(runtime, runtime->period_size); + pr_debug("s->period (%x) runtime->periods (%d)\n", + s->period, runtime->periods); + pr_debug("runtime->period_size (%d) dma_size (%d)\n", + (unsigned int)runtime->period_size, + runtime->dma_bytes); + + offset = dma_size * s->period; + if (snd_BUG_ON(dma_size > DMA_BUF_SIZE)) + return; +#ifdef CONFIG_SND_MXC_PMIC_IRAM + + if (g_audio_iram_en && stream_id == 0) { + dma_request.src_addr = ADMA_BASE_PADDR + offset; + } else +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ + { + + dma_request.src_addr = + (dma_addr_t) (dma_map_single + (NULL, runtime->dma_area + offset, + dma_size, DMA_TO_DEVICE)); + } + if (stream_id == 0) { + dma_request.dst_addr = + (dma_addr_t) get_ssi_fifo_addr(s->ssi, 1); + } else if (stream_id == 2) { + dma_request.dst_addr = + (dma_addr_t) get_ssi_fifo_addr(s->ssi, 1); + } + dma_request.num_of_bytes = dma_size; + pr_debug("MXC: Start DMA offset (%d) size (%d)\n", + offset, runtime->dma_bytes); + + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_WRITE); + ret = mxc_dma_enable(s->dma_wchannel); + ssi_transmit_enable(s->ssi, true); + s->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ + + if (ret) { + pr_debug("audio_process_dma: cannot queue DMA buffer\ + (%i)\n", ret); + return; + } + s->period++; + s->period %= runtime->periods; +#else + + if (stream_id == 2) { + dma_size = + frames_to_bytes(runtime, runtime->period_size); + pr_debug("s->period (%x) runtime->periods (%d)\n", + s->period, runtime->periods); + pr_debug("runtime->period_size (%d) dma_size (%d)\n", + (unsigned int)runtime->period_size, + runtime->dma_bytes); + + offset = dma_size * s->period; + if (snd_BUG_ON(dma_size > DMA_BUF_SIZE)) + return; + + dma_request.src_addr = + (dma_addr_t) (dma_map_single + (NULL, runtime->dma_area + offset, + dma_size, DMA_TO_DEVICE)); + dma_request.dst_addr = + (dma_addr_t) get_ssi_fifo_addr(s->ssi, 1); + dma_request.num_of_bytes = dma_size; + pr_debug("MXC: Start DMA offset (%d) size (%d)\n", + offset, runtime->dma_bytes); + + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_WRITE); + ret = mxc_dma_enable(s->dma_wchannel); + ssi_transmit_enable(s->ssi, true); + s->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ + + if (ret) { + pr_debug("audio_process_dma: cannot queue DMA buffer\ + (%i)\n", + ret); + return; + } + s->period++; + s->period %= runtime->periods; + + } else if (stream_id == 3) { + + dma_size_mix = + frames_to_bytes(runtime, runtime->period_size); + pr_debug("s->period (%x) runtime->periods (%d)\n", + s->period, runtime->periods); + pr_debug("runtime->period_size (%d) dma_size (%d)\n", + (unsigned int)runtime->period_size, + runtime->dma_bytes); + + offset_mix = dma_size_mix * s->period; + if (snd_BUG_ON(dma_size_mix > DMA_BUF_SIZE)) + return; + dma_request_mix.src_addr = + (dma_addr_t) (dma_map_single + (NULL, runtime->dma_area + offset_mix, + dma_size_mix, DMA_TO_DEVICE)); + dma_request_mix.dst_addr = + (dma_addr_t) get_ssi_fifo_addr(s->ssi, 1); + dma_request_mix.num_of_bytes = dma_size_mix; + + pr_debug("MXC: Start DMA offset (%d) size (%d)\n", + offset_mix, runtime->dma_bytes); + + mxc_dma_config(s->dma_wchannel, &dma_request_mix, 1, + MXC_DMA_MODE_WRITE); + ret1 = mxc_dma_enable(s->dma_wchannel); + ssi_transmit_enable(s->ssi, true); + s->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ + + if (ret1) { + pr_debug("audio_process_dma: cannot queue DMA buffer\ + (%i)\n", + ret1); + return; + } + s->period++; + s->period %= runtime->periods; + + } +#endif +#ifdef MXC_SOUND_PLAYBACK_CHAIN_DMA_EN + + if ((s->period > s->periods) && ((s->period - s->periods) > 1)) { + pr_debug + ("audio playback chain dma: already double buffered\n"); + return; + } + + if ((s->period < s->periods) + && ((s->period + runtime->periods - s->periods) > 1)) { + pr_debug + ("audio playback chain dma: already double buffered\n"); + return; + } + + if (s->period == s->periods) { + pr_debug + ("audio playback chain dma: s->period == s->periods\n"); + return; + } + + if (snd_pcm_playback_hw_avail(runtime) < + 2 * runtime->period_size) { + pr_debug + ("audio playback chain dma: available data is not enough\n"); + return; + } + + pr_debug + ("audio playback chain dma:to set up the 2nd dma buffer\n"); + +#ifndef CONFIG_SND_MXC_PLAYBACK_MIXING + offset = dma_size * s->period; +#ifdef CONFIG_SND_MXC_PMIC_IRAM + if (g_audio_iram_en && stream_id == 0) { + dma_request.src_addr = ADMA_BASE_PADDR + offset; + } else +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ + { + + dma_request.src_addr = + (dma_addr_t) (dma_map_single + (NULL, runtime->dma_area + offset, + dma_size, DMA_TO_DEVICE)); + } + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_WRITE); +#else + if (stream_id == 3) { + offset_mix = dma_size_mix * s->period; + dma_request.src_addr = + (dma_addr_t) (dma_map_single + (NULL, + runtime->dma_area + offset_mix, + dma_size, DMA_TO_DEVICE)); + + mxc_dma_config(s->dma_wchannel, &dma_request_mix, 1, + MXC_DMA_MODE_WRITE); + } else { + offset = dma_size * s->period; + dma_request.src_addr = + (dma_addr_t) (dma_map_single + (NULL, + runtime->dma_area + offset, + dma_size, DMA_TO_DEVICE)); + + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_WRITE); + } + +#endif + ret = mxc_dma_enable(s->dma_wchannel); + + s->period++; + s->period %= runtime->periods; +#endif /* MXC_SOUND_PLAYBACK_CHAIN_DMA_EN */ + } +} + +/*! + * This function is called whenever a new audio block needs to be + * transferred from PMIC. The function receives the address and the size + * of the block that will store the audio samples and start a new DMA transfer. + * + * @param substream pointer to the structure of the current stream. + * + */ +static void audio_capture_dma(audio_stream_t * s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int offset; + int ret = 0; + mxc_dma_requestbuf_t dma_request; + + substream = s->stream; + runtime = substream->runtime; + + pr_debug("\nDMA direction %d\ + (0 is playback 1 is capture)\n", s->stream_id); + + memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t)); + + if (s->active) { + dma_size = frames_to_bytes(runtime, runtime->period_size); + pr_debug("s->period (%x) runtime->periods (%d)\n", + s->period, runtime->periods); + pr_debug("runtime->period_size (%d) dma_size (%d)\n", + (unsigned int)runtime->period_size, + runtime->dma_bytes); + + offset = dma_size * s->period; + snd_BUG_ON(dma_size > DMA_BUF_SIZE); + + dma_request.dst_addr = (dma_addr_t) (dma_map_single(NULL, + runtime-> + dma_area + + offset, + dma_size, + DMA_FROM_DEVICE)); + dma_request.src_addr = + (dma_addr_t) get_ssi_fifo_addr(s->ssi, 0); + dma_request.num_of_bytes = dma_size; + + pr_debug("MXC: Start DMA offset (%d) size (%d)\n", offset, + runtime->dma_bytes); + + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_READ); + ret = mxc_dma_enable(s->dma_wchannel); + + s->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ + + if (ret) { + pr_debug("audio_process_dma: cannot queue DMA buffer\ + (%i)\n", ret); + return; + } + s->period++; + s->period %= runtime->periods; + +#ifdef MXC_SOUND_CAPTURE_CHAIN_DMA_EN + if ((s->period > s->periods) && ((s->period - s->periods) > 1)) { + pr_debug + ("audio capture chain dma: already double buffered\n"); + return; + } + + if ((s->period < s->periods) + && ((s->period + runtime->periods - s->periods) > 1)) { + pr_debug + ("audio capture chain dma: already double buffered\n"); + return; + } + + if (s->period == s->periods) { + pr_debug + ("audio capture chain dma: s->period == s->periods\n"); + return; + } + + if (snd_pcm_capture_hw_avail(runtime) < + 2 * runtime->period_size) { + pr_debug + ("audio capture chain dma: available data is not enough\n"); + return; + } + + pr_debug + ("audio capture chain dma:to set up the 2nd dma buffer\n"); + offset = dma_size * s->period; + dma_request.dst_addr = (dma_addr_t) (dma_map_single(NULL, + runtime-> + dma_area + + offset, + dma_size, + DMA_FROM_DEVICE)); + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_READ); + ret = mxc_dma_enable(s->dma_wchannel); + + s->period++; + s->period %= runtime->periods; +#endif /* MXC_SOUND_CAPTURE_CHAIN_DMA_EN */ + } +} + +/*! + * This is a callback which will be called + * when a TX transfer finishes. The call occurs + * in interrupt context. + * + * @param dat pointer to the structure of the current stream. + * + */ +static void audio_playback_dma_callback(void *data, int error, + unsigned int count) +{ + audio_stream_t *s; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int previous_period; + unsigned int offset; + + s = data; + substream = s->stream; + runtime = substream->runtime; + previous_period = s->periods; + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * previous_period; + + s->tx_spin = 0; + s->periods++; + s->periods %= runtime->periods; + + /* + * Give back to the CPU the access to the non cached memory + */ + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_TO_DEVICE); + + /* + * If we are getting a callback for an active stream then we inform + * the PCM middle layer we've finished a period + */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + spin_lock(&s->dma_lock); + + /* + * Trig next DMA transfer + */ + audio_playback_dma(s); + + spin_unlock(&s->dma_lock); + +} + +/*! + * This is a callback which will be called + * when a RX transfer finishes. The call occurs + * in interrupt context. + * + * @param substream pointer to the structure of the current stream. + * + */ +static void audio_capture_dma_callback(void *data, int error, + unsigned int count) +{ + audio_stream_t *s; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int previous_period; + unsigned int offset; + + s = data; + substream = s->stream; + runtime = substream->runtime; + previous_period = s->periods; + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * previous_period; + + s->tx_spin = 0; + s->periods++; + s->periods %= runtime->periods; + + /* + * Give back to the CPU the access to the non cached memory + */ + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_FROM_DEVICE); + + /* + * If we are getting a callback for an active stream then we inform + * the PCM middle layer we've finished a period + */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + spin_lock(&s->dma_lock); + + /* + * Trig next DMA transfer + */ + audio_capture_dma(s); + + spin_unlock(&s->dma_lock); + +} + +/*! + * This function is a dispatcher of command to be executed + * by the driver for playback. + * + * @param substream pointer to the structure of the current stream. + * @param cmd command to be executed + * + * @return 0 on success, -1 otherwise. + */ +static int +snd_mxc_audio_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + mxc_pmic_audio_t *chip; + int stream_id; + audio_stream_t *s; + int err; + int device; + device = substream->pcm->device; + if (device == 0) { + stream_id = 0; + } else if (device == 1) { + stream_id = 2; + } else { + stream_id = 3; + } + + chip = snd_pcm_substream_chip(substream); + //stream_id = substream->pstr->stream; + s = &chip->s[stream_id]; + err = 0; + + /* note local interrupts are already disabled in the midlevel code */ + spin_lock(&s->dma_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + s->tx_spin = 0; + s->active = 1; + audio_playback_dma(s); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + s->active = 0; + break; + default: + err = -EINVAL; + break; + } + spin_unlock(&s->dma_lock); + return err; +} + +/*! + * This function is a dispatcher of command to be executed + * by the driver for capture. + * + * @param substream pointer to the structure of the current stream. + * @param cmd command to be executed + * + * @return 0 on success, -1 otherwise. + */ +static int +snd_mxc_audio_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + mxc_pmic_audio_t *chip; + int stream_id; + audio_stream_t *s; + int err; + + chip = snd_pcm_substream_chip(substream); + stream_id = substream->pstr->stream; + s = &chip->s[stream_id]; + err = 0; + + /* note local interrupts are already disabled in the midlevel code */ + spin_lock(&s->dma_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + s->tx_spin = 0; + s->active = 1; + audio_capture_dma(s); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + s->active = 0; + break; + default: + err = -EINVAL; + break; + } + spin_unlock(&s->dma_lock); + return err; +} + +/*! + * This function configures the hardware to allow audio + * playback operations. It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_audio_playback_prepare(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + int ssi; + int device = -1, stream_id = -1; + + device = substream->pcm->device; + if (device == 0) + stream_id = 0; + else if (device == 1) + stream_id = 2; + else if (device == 2) + stream_id = 3; + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[stream_id]; + ssi = s->ssi; + + normalize_speed_for_pmic(substream); + + configure_dam_pmic_master(ssi); + + configure_ssi_tx(substream); + + ssi_interrupt_enable(ssi, ssi_tx_dma_interrupt_enable); + ssi_interrupt_enable(ssi, ssi_tx_interrupt_enable); + + if (configure_pmic_playback(substream, stream_id) == -1) + pr_debug(KERN_ERR "MXC: PMIC Playback Config FAILED\n"); + ssi_interrupt_enable(ssi, ssi_tx_fifo_0_empty); + ssi_interrupt_enable(ssi, ssi_tx_fifo_1_empty); + /* + ssi_transmit_enable(ssi, true); + */ + msleep(20); + set_pmic_channels(substream); + s->period = 0; + s->periods = 0; + + msleep(100); + + return 0; +} + +/*! + * This function gets the current capture pointer position. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + */ +static +snd_pcm_uframes_t snd_mxc_audio_capture_pointer(struct snd_pcm_substream + *substream) +{ + mxc_pmic_audio_t *chip; + + chip = snd_pcm_substream_chip(substream); + return audio_get_capture_dma_pos(&chip->s[substream->pstr->stream]); +} + +/*! + * This function gets the current playback pointer position. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + */ +static snd_pcm_uframes_t +snd_mxc_audio_playback_pointer(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + int device; + int stream_id; + device = substream->pcm->device; + if (device == 0) + stream_id = 0; + else if (device == 1) + stream_id = 2; + else + stream_id = 3; + chip = snd_pcm_substream_chip(substream); + return audio_get_playback_dma_pos(&chip->s[stream_id]); +} + +/*! + * This structure reprensents the capabilities of the driver + * in capture mode. + * It is used by ALSA framework. + */ +static struct snd_pcm_hardware snd_mxc_pmic_capture = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + 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 = MAX_BUFFER_SIZE, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = DMA_BUF_SIZE, + .periods_min = MIN_PERIOD, + .periods_max = MAX_PERIOD, + .fifo_size = 0, + +}; + +/*! + * This structure reprensents the capabilities of the driver + * in playback mode for ST-Dac. + * It is used by ALSA framework. + */ +static struct snd_pcm_hardware snd_mxc_pmic_playback_stereo = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS), + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = DMA_BUF_SIZE, + .periods_min = MIN_PERIOD, + .periods_max = MAX_PERIOD, + .fifo_size = 0, + +}; + +/*! + * This structure reprensents the capabilities of the driver + * in playback mode for Voice-codec. + * It is used by ALSA framework. + */ +static struct snd_pcm_hardware snd_mxc_pmic_playback_mono = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + 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 = MAX_BUFFER_SIZE, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = DMA_BUF_SIZE, + .periods_min = MIN_PERIOD, + .periods_max = MAX_PERIOD, + .fifo_size = 0, + +}; + +/*! + * This function opens a PMIC audio device in playback mode + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_audio_playback_open(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + struct snd_pcm_runtime *runtime; + int stream_id = -1; + int err; + audio_stream_t *s; + PMIC_AUDIO_HANDLE temp_handle; + int device = -1; + + device = substream->pcm->device; + + chip = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + if (device == 0) + stream_id = 0; + else if (device == 1) + stream_id = 2; + else if (device == 2) { + stream_id = 3; + } + + s = &chip->s[stream_id]; + err = -1; + + if (stream_id == 0) { + if ((audio_data->ssi_num == 1) + && (audio_mixer_control.codec_playback_active + || audio_mixer_control.codec_capture_active)) { + return -EBUSY; + } + + if (PMIC_SUCCESS == pmic_audio_open(&temp_handle, STEREO_DAC)) { + audio_mixer_control.stdac_handle = temp_handle; + audio_mixer_control.stdac_playback_active = 1; + chip->s[stream_id].pmic_audio_device.handle = + temp_handle; + } else { + return -EBUSY; + } + } else if (stream_id == 2) { + if ((audio_data->ssi_num == 1) + && (audio_mixer_control.stdac_playback_active)) { + return -EBUSY; + } + + audio_mixer_control.codec_playback_active = 1; + if (PMIC_SUCCESS == pmic_audio_open(&temp_handle, VOICE_CODEC)) { + audio_mixer_control.voice_codec_handle = temp_handle; + chip->s[stream_id].pmic_audio_device.handle = + temp_handle; + } else { + if (audio_mixer_control.codec_capture_active) { + temp_handle = + audio_mixer_control.voice_codec_handle; + } else { + return -EBUSY; + } + } + + } else if (stream_id == 3) { + audio_mixer_control.mixing_active = 1; + + } + + pmic_audio_antipop_enable(ANTI_POP_RAMP_SLOW); + msleep(250); + + chip->s[stream_id].stream = substream; + + if (stream_id == 0) + runtime->hw = snd_mxc_pmic_playback_stereo; + else if (stream_id == 2) + runtime->hw = snd_mxc_pmic_playback_mono; + + else if (stream_id == 3) { + runtime->hw = snd_mxc_pmic_playback_mono; + + if ((err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_playback_rates_mono)) + < 0) + return err; + } +#ifdef CONFIG_SND_MXC_PMIC_IRAM + if (g_audio_iram_en && stream_id == 0) { + runtime->hw.buffer_bytes_max = MAX_IRAM_SIZE; + runtime->hw.period_bytes_max = DMA_IRAM_SIZE; + } +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ + + if ((err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS)) < + 0) + goto exit_err; + if (stream_id == 0) { + if ((err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_playback_rates_stereo)) + < 0) + goto exit_err; + + } else if (stream_id == 2) { + if ((err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_playback_rates_mono)) + < 0) + goto exit_err; + } + msleep(10); + + /* setup DMA controller for playback */ + if ((err = + configure_write_channel(&mxc_audio->s[stream_id], + audio_playback_dma_callback, + stream_id)) < 0) + goto exit_err; + + /* enable ssi clock */ + clk_enable(audio_data->ssi_clk[s->ssi]); + + return 0; + exit_err: +#ifdef CONFIG_SND_MXC_PMIC_IRAM + if (stream_id == 0) + mxc_snd_pcm_iram_put(); +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ + return err; + +} + +/*! + * This function closes an PMIC audio device for playback. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_audio_playback_close(struct snd_pcm_substream + *substream) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + PMIC_AUDIO_HANDLE handle; + int ssi; + int device, stream_id = -1; + handle = (PMIC_AUDIO_HANDLE) NULL; + device = substream->pcm->device; + if (device == 0) { + stream_id = 0; + } else if (device == 1) { + stream_id = 2; + } else + stream_id = 3; + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[stream_id]; + ssi = s->ssi; + + if (audio_mixer_control.mixing_active == 1) { + goto End; + + } else { + + if (stream_id == 0) { + disable_stereodac(); + audio_mixer_control.stdac_playback_active = 0; + handle = audio_mixer_control.stdac_handle; + audio_mixer_control.stdac_handle = NULL; + chip->s[stream_id].pmic_audio_device.handle = NULL; + } else if ((stream_id == 2) || (stream_id == 3)) { + + audio_mixer_control.codec_playback_active = 0; + handle = audio_mixer_control.voice_codec_handle; + disable_codec(handle); + audio_mixer_control.voice_codec_handle = NULL; + chip->s[stream_id].pmic_audio_device.handle = NULL; + } + + } + pmic_audio_close(handle); + + ssi_transmit_enable(ssi, false); + ssi_interrupt_disable(ssi, ssi_tx_dma_interrupt_enable); + ssi_tx_fifo_enable(ssi, ssi_fifo_0, false); + ssi_enable(ssi, false); + mxc_dma_free((mxc_audio->s[stream_id]).dma_wchannel); + + chip->s[stream_id].stream = NULL; + End: + audio_mixer_control.mixing_active = 0; +#ifdef CONFIG_SND_MXC_PMIC_IRAM + if (stream_id == 0) + mxc_snd_pcm_iram_put(); +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ + /* disable ssi clock */ + clk_disable(audio_data->ssi_clk[ssi]); + + return 0; +} + +/*! + * This function closes a PMIC audio device for capture. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_audio_capture_close(struct snd_pcm_substream *substream) +{ + PMIC_AUDIO_HANDLE handle; + mxc_pmic_audio_t *chip; + audio_stream_t *s; + int ssi; + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[substream->pstr->stream]; + ssi = s->ssi; + + audio_mixer_control.codec_capture_active = 0; + handle = audio_mixer_control.voice_codec_handle; + disable_codec(handle); + audio_mixer_control.voice_codec_handle = NULL; + chip->s[SNDRV_PCM_STREAM_CAPTURE].pmic_audio_device.handle = NULL; + + pmic_audio_close(handle); + + ssi_receive_enable(ssi, false); + ssi_interrupt_disable(ssi, ssi_rx_dma_interrupt_enable); + ssi_rx_fifo_enable(ssi, ssi_fifo_0, false); + ssi_enable(ssi, false); + mxc_dma_free((mxc_audio->s[1]).dma_wchannel); + + chip->s[substream->pstr->stream].stream = NULL; + + /* disable ssi clock */ + clk_disable(audio_data->ssi_clk[ssi]); + + return 0; +} + +/*! + * This function configure the Audio HW in terms of memory allocation. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_audio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime; + int ret = 0, size; + int device, stream_id = -1; +#ifdef CONFIG_SND_MXC_PMIC_IRAM + struct snd_dma_buffer *dmab; +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ + + runtime = substream->runtime; + ret = + snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); + if (ret < 0) + return ret; + size = params_buffer_bytes(hw_params); + device = substream->pcm->device; + if (device == 0) + stream_id = 0; + else if (device == 1) + stream_id = 2; + + runtime->dma_addr = virt_to_phys(runtime->dma_area); + +#ifdef CONFIG_SND_MXC_PMIC_IRAM + if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && g_audio_iram_en + && stream_id == 0) { + if (runtime->dma_buffer_p + && (runtime->dma_buffer_p != &g_iram_dmab)) { + snd_pcm_lib_free_pages(substream); + } + dmab = &g_iram_dmab; + dmab->dev = substream->dma_buffer.dev; + dmab->area = (char *)ADMA_BASE_VADDR; + dmab->addr = ADMA_BASE_PADDR; + dmab->bytes = size; + snd_pcm_set_runtime_buffer(substream, dmab); + runtime->dma_bytes = size; + } else +#endif /* CONFIG_SND_MXC_PMIC_IRAM */ + { + ret = snd_pcm_lib_malloc_pages(substream, size); + if (ret < 0) + return ret; + + runtime->dma_addr = virt_to_phys(runtime->dma_area); + } + + pr_debug("MXC: snd_mxc_audio_hw_params runtime->dma_addr 0x(%x)\n", + (unsigned int)runtime->dma_addr); + pr_debug("MXC: snd_mxc_audio_hw_params runtime->dma_area 0x(%x)\n", + (unsigned int)runtime->dma_area); + pr_debug("MXC: snd_mxc_audio_hw_params runtime->dma_bytes 0x(%x)\n", + (unsigned int)runtime->dma_bytes); + + return ret; +} + +/*! + * This function frees the audio hardware at the end of playback/capture. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_audio_hw_free(struct snd_pcm_substream *substream) +{ +#ifdef CONFIG_SND_MXC_PMIC_IRAM + if (substream->runtime->dma_buffer_p == &g_iram_dmab) { + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; + } else +#endif /* CONFIG_SND_MXC_PMIC_IRAM */ + { + return snd_pcm_lib_free_pages(substream); + } + return 0; +} + +/*! + * This function configures the hardware to allow audio + * capture operations. It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_audio_capture_prepare(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + audio_stream_t *s; + int ssi; + + chip = snd_pcm_substream_chip(substream); + s = &chip->s[substream->pstr->stream]; + ssi = s->ssi; + + normalize_speed_for_pmic(substream); + + pr_debug("substream->pstr->stream %d\n", substream->pstr->stream); + pr_debug("SSI %d\n", ssi + 1); + configure_dam_pmic_master(ssi); + + configure_ssi_rx(substream); + + ssi_interrupt_enable(ssi, ssi_rx_dma_interrupt_enable); + + if (configure_pmic_recording(substream) == -1) + pr_debug(KERN_ERR "MXC: PMIC Record Config FAILED\n"); + + ssi_interrupt_enable(ssi, ssi_rx_fifo_0_full); + ssi_receive_enable(ssi, true); + + msleep(20); + set_pmic_channels(substream); + + s->period = 0; + s->periods = 0; + + return 0; +} + +/*! + * This function opens an PMIC audio device in capture mode + * on Codec. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_audio_capture_open(struct snd_pcm_substream *substream) +{ + mxc_pmic_audio_t *chip; + struct snd_pcm_runtime *runtime; + int stream_id; + int err; + PMIC_AUDIO_HANDLE temp_handle; + audio_stream_t *s; + + chip = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + stream_id = substream->pstr->stream; + s = &chip->s[stream_id]; + err = -1; + + if ((audio_data->ssi_num == 1) + && (audio_mixer_control.stdac_playback_active)) { + return -EBUSY; + } + if (PMIC_SUCCESS == pmic_audio_open(&temp_handle, VOICE_CODEC)) { + audio_mixer_control.voice_codec_handle = temp_handle; + audio_mixer_control.codec_capture_active = 1; + chip->s[SNDRV_PCM_STREAM_CAPTURE].pmic_audio_device.handle = + temp_handle; + } else { + if (audio_mixer_control.codec_playback_active) { + temp_handle = audio_mixer_control.voice_codec_handle; + } else { + return -EBUSY; + } + } + pmic_audio_antipop_enable(ANTI_POP_RAMP_SLOW); + + chip->s[stream_id].stream = substream; + + if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw = snd_mxc_pmic_capture; + } else { + return err; + } + + if ((err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS)) < + 0) { + return err; + } + + if ((err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_capture_rates)) < 0) { + return err; + } + + /* setup DMA controller for Record */ + err = configure_read_channel(&mxc_audio->s[SNDRV_PCM_STREAM_CAPTURE], + audio_capture_dma_callback); + if (err < 0) { + return err; + } + + /* enable ssi clock */ + clk_enable(audio_data->ssi_clk[s->ssi]); + + msleep(50); + + return 0; +} + +#ifdef CONFIG_SND_MXC_PMIC_IRAM +static struct page *snd_mxc_audio_playback_nopage(struct vm_area_struct *area, + unsigned long address, + int *type) +{ + struct snd_pcm_substream *substream = area->vm_private_data; + struct snd_pcm_runtime *runtime; + unsigned long offset; + struct page *page; + void *vaddr; + size_t dma_bytes; + + if (substream == NULL) + return NOPAGE_OOM; + runtime = substream->runtime; + if (g_audio_iram_en) { + return NOPAGE_SIGBUS; + } + offset = area->vm_pgoff << PAGE_SHIFT; + offset += address - area->vm_start; + if (snd_BUG_ON(offset % PAGE_SIZE) != 0) + return NOPAGE_OOM; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); + if (offset > dma_bytes - PAGE_SIZE) + return NOPAGE_SIGBUS; + if (substream->ops->page) { + page = substream->ops->page(substream, offset); + if (!page) + return NOPAGE_OOM; + } else { + vaddr = runtime->dma_area + offset; + page = virt_to_page(vaddr); + } + get_page(page); + if (type) + *type = VM_FAULT_MINOR; + return page; +} + +static struct vm_operations_struct snd_mxc_audio_playback_vm_ops = { + .open = snd_pcm_mmap_data_open, + .close = snd_pcm_mmap_data_close, + .nopage = snd_mxc_audio_playback_nopage, +}; + +#ifdef CONFIG_ARCH_MX3 +static inline int snd_mxc_set_pte_attr(struct mm_struct *mm, + pmd_t * pmd, + unsigned long addr, unsigned long end) +{ + + pte_t *pte; + spinlock_t *ptl; + pte = pte_alloc_map_lock(mm, pmd, addr, &ptl); + + if (!pte) + return -ENOMEM; + do { + BUG_ON(pte_none(*pte)); //The mapping is created. It should not be none. + *(pte - 512) |= 0x83; //Directly modify to non-shared device + } while (pte++, addr += PAGE_SIZE, addr != end); + + pte_unmap_unlock(pte - 1, ptl); + + return 0; + +} + +static int snd_mxc_set_pmd_attr(struct mm_struct *mm, + pud_t * pud, + unsigned long addr, unsigned long end) +{ + + pmd_t *pmd; + unsigned long next; + pmd = pmd_alloc(mm, pud, addr); + + if (!pmd) + return -ENOMEM; + do { + + next = pmd_addr_end(addr, end); + if (snd_mxc_set_pte_attr(mm, pmd, addr, next)) + return -ENOMEM; + + } while (pmd++, addr = next, addr != end); + + return 0; + +} + +static int snd_mxc_set_pud_attr(struct mm_struct *mm, + pgd_t * pgd, + unsigned long addr, unsigned long end) +{ + + pud_t *pud; + unsigned long next; + pud = pud_alloc(mm, pgd, addr); + + if (!pud) + return -ENOMEM; + do { + + next = pud_addr_end(addr, end); + if (snd_mxc_set_pmd_attr(mm, pud, addr, next)) + return -ENOMEM; + + } while (pud++, addr = next, addr != end); + + return 0; + +} + +static inline int snd_mxc_set_pgd_attr(struct vm_area_struct *area) +{ + + int ret = 0; + pgd_t *pgd; + struct mm_struct *mm = current->mm; + unsigned long next, addr = area->vm_start; + + pgd = pgd_offset(mm, addr); + flush_cache_range(area, addr, area->vm_end); + + do { + if (!pgd_present(*pgd)) + return -1; + + next = pgd_addr_end(addr, area->vm_end); + if ((ret = snd_mxc_set_pud_attr(mm, pgd, addr, next))) + break; + + } while (pgd++, addr = next, addr != area->vm_end); + + return ret; + +} + +#else +#define snd_mxc_set_page_attr() (0) +#endif + +static int snd_mxc_audio_playback_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *area) +{ + int ret = 0; + area->vm_ops = &snd_mxc_audio_playback_vm_ops; + area->vm_private_data = substream; + if (g_audio_iram_en) { + unsigned long off = area->vm_pgoff << PAGE_SHIFT; + unsigned long phys = ADMA_BASE_PADDR + off; + unsigned long size = area->vm_end - area->vm_start; + if (off + size > MAX_IRAM_SIZE) { + return -EINVAL; + } + area->vm_page_prot = pgprot_nonshareddev(area->vm_page_prot); + area->vm_flags |= VM_IO; + ret = + remap_pfn_range(area, area->vm_start, phys >> PAGE_SHIFT, + size, area->vm_page_prot); + if (ret == 0) { + ret = snd_mxc_set_pgd_attr(area); + } + + } else { + area->vm_flags |= VM_RESERVED; + } + if (ret == 0) + area->vm_ops->open(area); + return ret; +} + +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ + +/*! + * This structure is the list of operation that the driver + * must provide for the capture interface + */ +static struct snd_pcm_ops snd_card_mxc_audio_capture_ops = { + .open = snd_card_mxc_audio_capture_open, + .close = snd_card_mxc_audio_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_mxc_audio_hw_params, + .hw_free = snd_mxc_audio_hw_free, + .prepare = snd_mxc_audio_capture_prepare, + .trigger = snd_mxc_audio_capture_trigger, + .pointer = snd_mxc_audio_capture_pointer, +}; + +/*! + * This structure is the list of operation that the driver + * must provide for the playback interface + */ +static struct snd_pcm_ops snd_card_mxc_audio_playback_ops = { + .open = snd_card_mxc_audio_playback_open, + .close = snd_card_mxc_audio_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_mxc_audio_hw_params, + .hw_free = snd_mxc_audio_hw_free, + .prepare = snd_mxc_audio_playback_prepare, + .trigger = snd_mxc_audio_playback_trigger, + .pointer = snd_mxc_audio_playback_pointer, +#ifdef CONFIG_SND_MXC_PMIC_IRAM + .mmap = snd_mxc_audio_playback_mmap, +#endif /*CONFIG_SND_MXC_PMIC_IRAM */ +}; + +/*! + * This functions initializes the capture audio device supported by + * PMIC IC. + * + * @param mxc_audio pointer to the sound card structure + * + */ +void init_device_capture(mxc_pmic_audio_t * mxc_audio) +{ + audio_stream_t *audio_stream; + pmic_audio_device_t *pmic_device; + + audio_stream = &mxc_audio->s[SNDRV_PCM_STREAM_CAPTURE]; + pmic_device = &audio_stream->pmic_audio_device; + + /* These parameters defines the identity of + * the device (codec or stereodac) + */ + audio_stream->ssi = SSI1; + audio_stream->dam_port = DAM_PORT_4; + pmic_device->ssi = SSI1; + + pmic_device->mode = BUS_MASTER_MODE; + pmic_device->protocol = NETWORK_MODE; + +#ifdef CONFIG_MXC_PMIC_SC55112 + pmic_device->pll = CLOCK_IN_CLKIN; + pmic_device->pll_rate = VCODEC_CLI_33_6MHZ; +#else + if (machine_is_mx31ads()) { + pmic_device->pll = CLOCK_IN_CLIB; + } else { + pmic_device->pll = CLOCK_IN_CLIA; + } + pmic_device->pll_rate = VCODEC_CLI_26MHZ; +#endif + pmic_device->bcl_inverted = 0; + pmic_device->fs_inverted = 0; + +} + +/*! + * This functions initializes the playback audio device supported by + * PMIC IC. + * + * @param mxc_audio pointer to the sound card structure. + * @param device device ID of PCM instance. + * + */ +void init_device_playback(mxc_pmic_audio_t * mxc_audio, int device) +{ + audio_stream_t *audio_stream; + pmic_audio_device_t *pmic_device; + if (device == 0) + audio_stream = &mxc_audio->s[0]; + else + audio_stream = &mxc_audio->s[2]; + pmic_device = &audio_stream->pmic_audio_device; + + /* These parameters defines the identity of + * the device (codec or stereodac) + */ + if (device == 0) { + if (audio_data->ssi_num == 2) { + audio_stream->ssi = SSI2; + audio_stream->dam_port = DAM_PORT_5; + pmic_device->ssi = SSI2; + } else { + audio_stream->ssi = SSI1; + audio_stream->dam_port = DAM_PORT_4; + pmic_device->ssi = SSI1; + } + + pmic_device->mode = BUS_MASTER_MODE; + pmic_device->protocol = NETWORK_MODE; + +#ifdef CONFIG_MXC_PMIC_SC55112 + pmic_device->pll = CLOCK_IN_CLKIN; + pmic_device->pll_rate = STDAC_CLI_33_6MHZ; +#else + if (machine_is_mx31ads()) { + pmic_device->pll = CLOCK_IN_CLIB; + } else { + pmic_device->pll = CLOCK_IN_CLIA; + } + pmic_device->pll_rate = STDAC_CLI_26MHZ; +#endif + + pmic_device->bcl_inverted = 0; + pmic_device->fs_inverted = 0; + + } else if ((device == 1) || (device == 2)) { + audio_stream->ssi = SSI1; + audio_stream->dam_port = DAM_PORT_4; + pmic_device->ssi = SSI1; + + pmic_device->mode = BUS_MASTER_MODE; + pmic_device->protocol = NETWORK_MODE; + +#ifdef CONFIG_MXC_PMIC_SC55112 + pmic_device->pll = CLOCK_IN_CLKIN; + pmic_device->pll_rate = VCODEC_CLI_33_6MHZ; +#else + if (machine_is_mx31ads()) { + pmic_device->pll = CLOCK_IN_CLIB; + } else { + pmic_device->pll = CLOCK_IN_CLIA; + } + pmic_device->pll_rate = VCODEC_CLI_26MHZ; +#endif + pmic_device->bcl_inverted = 0; + pmic_device->fs_inverted = 0; + } + +} + +/*! + * This functions initializes the mixer related information + * + * @param mxc_audio pointer to the sound card structure. + * + */ +void mxc_pmic_mixer_controls_init(mxc_pmic_audio_t * mxc_audio) +{ + audio_mixer_control_t *audio_control; + int i = 0; + + audio_control = &audio_mixer_control; + + memset(audio_control, 0, sizeof(audio_mixer_control_t)); + sema_init(&audio_control->sem, 1); + + audio_control->input_device = SOUND_MASK_MIC; + audio_control->output_device = SOUND_MASK_VOLUME | SOUND_MASK_PCM; + + /* PMIC has to internal sources that can be routed to output + One is codec direct out and the other is mixer out + Initially we configure all outputs to have no source and + will be later configured either by PCM stream handler or mixer */ + for (i = 0; i < OP_MAXDEV && i != OP_HEADSET; i++) { + audio_control->source_for_output[i] = MIXER_OUT; + } + + /* These bits are initially reset and set when playback begins */ + audio_control->codec_out_to_mixer = 0; + audio_control->stdac_out_to_mixer = 0; + + audio_control->mixer_balance = 50; + if (machine_is_mx31ads()) + audio_control->mixer_mono_adder = STEREO_OPPOSITE_PHASE; + else + audio_control->mixer_mono_adder = MONO_ADDER_OFF; + /* Default values for input and output */ + audio_control->input_volume = ((40 << 8) & 0xff00) | (40 & 0x00ff); + audio_control->master_volume_out = ((50 << 8) & 0xff00) | (50 & 0x00ff); + + if (PMIC_SUCCESS != pmic_audio_set_autodetect(1)) + msleep(30); +} + +/*! + * This functions initializes the 2 audio devices supported by + * PMIC IC. The parameters define the type of device (CODEC or STEREODAC) + * + * @param mxc_audio pointer to the sound card structure. + * @param device device id of the PCM stream. + * + */ +void mxc_pmic_audio_init(mxc_pmic_audio_t * mxc_audio, int device) +{ + if (device == 0) { + mxc_audio->s[SNDRV_PCM_STREAM_PLAYBACK].id = "Audio out"; + mxc_audio->s[SNDRV_PCM_STREAM_PLAYBACK].stream_id = + SNDRV_PCM_STREAM_PLAYBACK; + mxc_audio->s[SNDRV_PCM_STREAM_CAPTURE].id = "Audio in"; + mxc_audio->s[SNDRV_PCM_STREAM_CAPTURE].stream_id = + SNDRV_PCM_STREAM_CAPTURE; + } else if (device == 1) { + mxc_audio->s[2].id = "Audio out"; + mxc_audio->s[2].stream_id = 2; + } else if (device == 2) { + mxc_audio->s[2].id = "Audio out"; + mxc_audio->s[2].stream_id = 3; + } + + init_device_playback(mxc_audio, device); + if (!device) { + init_device_capture(mxc_audio); + } +} + +/*! + * This function the soundcard structure. + * + * @param mxc_audio pointer to the sound card structure. + * @param device the device index (zero based) + * + * @return 0 on success, -1 otherwise. + */ +static int __devinit snd_card_mxc_audio_pcm(mxc_pmic_audio_t *mxc_audio, + int device) +{ + struct snd_pcm *pcm; + int err; + + /* + * Create a new PCM instance with 1 capture stream and 1 playback substream + */ + if ((err = snd_pcm_new(mxc_audio->card, "MXC", device, 1, 1, &pcm)) < 0) { + return err; + } + + /* + * this sets up our initial buffers and sets the dma_type to isa. + * isa works but I'm not sure why (or if) it's the right choice + * this may be too large, trying it for now + */ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), MAX_BUFFER_SIZE * 2, + MAX_BUFFER_SIZE * 2); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_card_mxc_audio_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_card_mxc_audio_capture_ops); + + pcm->private_data = mxc_audio; + pcm->info_flags = 0; + strncpy(pcm->name, SOUND_CARD_NAME, sizeof(pcm->name)); + mxc_audio->pcm[device] = pcm; + mxc_pmic_audio_init(mxc_audio, device); + + /* Allocating a second device for PCM playback on voice codec */ + device = 1; + if ((err = snd_pcm_new(mxc_audio->card, "MXC", device, 1, 0, &pcm)) < 0) { + return err; + } + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), MAX_BUFFER_SIZE * 2, + MAX_BUFFER_SIZE * 2); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_card_mxc_audio_playback_ops); + pcm->private_data = mxc_audio; + pcm->info_flags = 0; + strncpy(pcm->name, SOUND_CARD_NAME, sizeof(pcm->name)); + mxc_audio->pcm[device] = pcm; + mxc_pmic_audio_init(mxc_audio, device); + + device = 2; + if ((err = snd_pcm_new(mxc_audio->card, "MXC", device, 1, 0, &pcm)) < 0) { + return err; + } + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), MAX_BUFFER_SIZE * 2, + MAX_BUFFER_SIZE * 2); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_card_mxc_audio_playback_ops); + pcm->private_data = mxc_audio; + pcm->info_flags = 0; + strncpy(pcm->name, SOUND_CARD_NAME, sizeof(pcm->name)); + mxc_audio->pcm[device] = pcm; + + /* End of allocation */ + /* FGA for record and not hard coded playback */ + mxc_pmic_mixer_controls_init(mxc_audio); + + return 0; +} + +#ifdef CONFIG_PM +/*! + * This function suspends all active streams. + * + * TBD + * + * @param card pointer to the sound card structure. + * @param state requested state + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_audio_suspend(struct platform_device *dev, + pm_message_t state) +{ + struct snd_card *card = platform_get_drvdata(dev); + mxc_pmic_audio_t *chip = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(chip->pcm[0]); + //mxc_alsa_audio_shutdown(chip); + + return 0; +} + +/*! + * This function resumes all suspended streams. + * + * TBD + * + * @param card pointer to the sound card structure. + * @param state requested state + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_audio_resume(struct platform_device *dev) +{ + struct snd_card *card = platform_get_drvdata(dev); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + + return 0; +} +#endif /* COMFIG_PM */ + +/*! + * This function frees the sound card structure + * + * @param card pointer to the sound card structure. + * + * @return 0 on success, -1 otherwise. + */ +void snd_mxc_audio_free(struct snd_card *card) +{ + mxc_pmic_audio_t *chip; + + chip = card->private_data; + audio_dma_free(&chip->s[SNDRV_PCM_STREAM_PLAYBACK]); + audio_dma_free(&chip->s[SNDRV_PCM_STREAM_CAPTURE]); + mxc_audio = NULL; + card->private_data = NULL; + kfree(chip); + +} + +/*! + * This function initializes the driver in terms of memory of the soundcard + * and some basic HW clock settings. + * + * @return 0 on success, -1 otherwise. + */ +static int __devinit mxc_alsa_audio_probe(struct platform_device *pdev) +{ + int err; + struct snd_card *card; + + audio_data = (struct mxc_audio_platform_data *)pdev->dev.platform_data; + if (!audio_data) { + dev_err(&pdev->dev, "Can't get the platform data for ALSA\n"); + return -EINVAL; + } + /* register the soundcard */ + err = snd_card_create(-1, id, THIS_MODULE, sizeof(mxc_pmic_audio_t), &card); + if (err < 0) { + return -ENOMEM; + } + + mxc_audio = kcalloc(1, sizeof(*mxc_audio), GFP_KERNEL); + if (mxc_audio == NULL) { + return -ENOMEM; + } + + card->private_data = (void *)mxc_audio; + card->private_free = snd_mxc_audio_free; + + mxc_audio->card = card; + card->dev = &pdev->dev; + if ((err = snd_card_mxc_audio_pcm(mxc_audio, 0)) < 0) { + goto nodev; + } + + if (0 == mxc_alsa_create_ctl(card, (void *)&audio_mixer_control)) + printk(KERN_INFO "Control ALSA component registered\n"); + +#ifdef CONFIG_HEADSET_DETECT_ENABLE + pmic_audio_antipop_enable(ANTI_POP_RAMP_SLOW); + pmic_audio_set_callback((PMIC_AUDIO_CALLBACK) HSCallback, + HEADSET_DETECTED | HEADSET_REMOVED, &hs_state); +#endif + /* Set autodetect feature in order to allow audio operations */ + + spin_lock_init(&(mxc_audio->s[0].dma_lock)); + spin_lock_init(&(mxc_audio->s[1].dma_lock)); + spin_lock_init(&(mxc_audio->s[2].dma_lock)); + + strcpy(card->driver, "MXC"); + strcpy(card->shortname, "PMIC-audio"); + sprintf(card->longname, "MXC Freescale with PMIC"); + + if ((err = snd_card_register(card)) == 0) { + pr_debug(KERN_INFO "MXC audio support initialized\n"); + platform_set_drvdata(pdev, card); + return 0; + } + + nodev: + snd_card_free(card); + return err; +} + +static int __devexit mxc_alsa_audio_remove(struct platform_device *dev) +{ + snd_card_free(mxc_audio->card); + kfree(mxc_audio); + platform_set_drvdata(dev, NULL); + + return 0; +} + +static struct platform_driver mxc_alsa_audio_driver = { + .probe = mxc_alsa_audio_probe, + .remove = __devexit_p(mxc_alsa_audio_remove), +#ifdef CONFIG_PM + .suspend = snd_mxc_audio_suspend, + .resume = snd_mxc_audio_resume, +#endif + .driver = { + .name = "mxc_alsa", + }, +}; + +static int __init mxc_alsa_audio_init(void) +{ + return platform_driver_register(&mxc_alsa_audio_driver); +} + +/*! + * This function frees the sound driver structure. + * + */ + +static void __exit mxc_alsa_audio_exit(void) +{ + platform_driver_unregister(&mxc_alsa_audio_driver); +} + +module_init(mxc_alsa_audio_init); +module_exit(mxc_alsa_audio_exit); + +MODULE_AUTHOR("FREESCALE SEMICONDUCTOR"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MXC driver for ALSA"); +MODULE_SUPPORTED_DEVICE("{{PMIC}}"); + +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for MXC + PMIC soundcard."); diff --git a/sound/arm/mxc-alsa-pmic.h b/sound/arm/mxc-alsa-pmic.h new file mode 100644 index 000000000000..46c5ca4ae1ab --- /dev/null +++ b/sound/arm/mxc-alsa-pmic.h @@ -0,0 +1,110 @@ +/* + * 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 + * + * Copyright 2007-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + + /*! + * @file mxc-alsa-pmic.h + * @brief + * @ingroup SOUND_DRV + */ + +#ifndef __MXC_ALSA_PMIC_H__ +#define __MXC_ALSA_PMIC_H__ + +#ifdef __KERNEL__ + + /**/ +#define PMIC_MASTER 0x1 +#define PMIC_SLAVE 0x2 + /**/ +#define DAM_PORT_4 port_4 +#define DAM_PORT_5 port_5 + /**/ +#define PMIC_STEREODAC 0x1 +#define PMIC_CODEC 0x2 + /**/ +#define PMIC_AUDIO_ADDER_STEREO 0x1 +#define PMIC_AUDIO_ADDER_STEREO_OPPOSITE 0x2 +#define PMIC_AUDIO_ADDER_MONO 0x4 +#define PMIC_AUDIO_ADDER_MONO_OPPOSITE 0x8 +#define TX_WATERMARK 0x4 +#define RX_WATERMARK 0x6 + /**/ +#define SSI_DEFAULT_FIFO 0x0 +#define DEFAULT_MASTER_CLOCK 0x1 + /**/ +#define SDMA_TXFIFO_WATERMARK 0x4 +#define SDMA_RXFIFO_WATERMARK 0x6 + /**/ +#define TIMESLOTS_2 0x3 +#define TIMESLOTS_4 0x2 +#define SAMPLE_RATE_MAX 0x9 + /**/ +#define CLK_SELECT_SLAVE_BCL 0x7 +#define CLK_SELECT_SLAVE_CLI 0x5 +#define CLK_SELECT_MASTER_CLI 0x4 +/* Volume to balance ratio */ +#define VOLUME_BALANCE_RATIO ((6 + 39) / (21 + 21)) +/* -21dB */ +#define PMIC_BALANCE_MIN 0x0 +/* 0dB*/ +#define PMIC_BALANCE_MAX 0x7 +/* -21dB left */ +#define BALANCE_MIN 0x0 +/* 0db, no balance */ +#define NO_BALANCE 0x32 +/* -21dB right*/ +#define BALANCE_MAX 0x64 +/* -8dB */ +#define PMIC_INPUT_VOLUME_MIN 0x0 +/* +23dB */ +#define PMIC_INPUT_VOLUME_MAX 0x1f +/* -39dB */ +#define PMIC_OUTPUT_VOLUME_MIN PMIC_INPUT_VOLUME_MIN +/* +6dB */ +#define PMIC_OUTPUT_VOLUME_MAX 0xd +/* -8dB */ +#define INPUT_VOLUME_MIN 0x0 +/* +23dB */ +#define INPUT_VOLUME_MAX 0x64 +/* -39dB */ +#define OUTPUT_VOLUME_MIN 0x0 +/* +6dB */ +#define OUTPUT_VOLUME_MAX 0x64 +/* 96 Khz */ +#define SAMPLE_FREQ_MAX 96000 +/* 8 Khz */ +#define SAMPLE_FREQ_MIN 8000 +/*! + * Define channels available on MC13783. This is mainly used + * in mixer interface to control different input/output + * devices + */ +#define MXC_STEREO_OUTPUT ( SOUND_MASK_VOLUME | SOUND_MASK_PCM ) +#define MXC_STEREO_INPUT ( SOUND_MASK_PHONEIN ) +#define MXC_MONO_OUTPUT ( SOUND_MASK_PHONEOUT | SOUND_MASK_SPEAKER ) +#define MXC_MONO_INPUT ( SOUND_MASK_LINE | SOUND_MASK_MIC ) +#define SNDCTL_CLK_SET_MASTER _SIOR('Z', 30, int) +#define SNDCTL_PMIC_READ_OUT_BALANCE _SIOR('Z', 8, int) +//#define SNDCTL_MC13783_WRITE_OUT_BALANCE _SIOWR('Z', 9, int) +#define SNDCTL_PMIC_WRITE_OUT_ADDER _SIOWR('Z', 10, int) +#define SNDCTL_PMIC_READ_OUT_ADDER _SIOR('Z', 11, int) +#define SNDCTL_PMIC_WRITE_CODEC_FILTER _SIOWR('Z', 12, int) +#define SNDCTL_PMIC_READ_CODEC_FILTER _SIOR('Z', 13, int) +#define SNDCTL_DAM_SET_OUT_PORT _SIOWR('Z', 14, int) +#endif /* __KERNEL__ */ +#endif /* __MXC_ALSA_PMIC_H__ */ diff --git a/sound/arm/mxc-alsa-spdif.c b/sound/arm/mxc-alsa-spdif.c new file mode 100644 index 000000000000..15d053f78a5a --- /dev/null +++ b/sound/arm/mxc-alsa-spdif.c @@ -0,0 +1,2266 @@ +/* + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup SOUND_DRV MXC Sound Driver for ALSA + */ + +/*! + * @file mxc-alsa-spdif.c + * @brief this fle mxc-alsa-spdif.c + * @brief this file implements mxc alsa driver for spdif. + * The spdif tx supports consumer channel for linear PCM and + * compressed audio data. The supported sample rates are + * 48KHz, 44.1KHz and 32KHz. + * + * @ingroup SOUND_DRV + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/ioctl.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/soundcard.h> +#include <linux/clk.h> +#ifdef CONFIG_PM +#include <linux/pm.h> +#endif + +#include <mach/dma.h> +#include <asm/mach-types.h> +#include <asm/io.h> +#include <asm/irq.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/asoundef.h> +#include <sound/initval.h> +#include <sound/control.h> + +#define MXC_SPDIF_NAME "MXC_SPDIF" +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for spdif sound card."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for spdif sound card."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable spdif sound card."); + +#define SPDIF_MAX_BUF_SIZE (32*1024) +#define SPDIF_DMA_BUF_SIZE (8*1024) +#define SPDIF_MIN_PERIOD_SIZE 64 +#define SPDIF_MIN_PERIOD 2 +#define SPDIF_MAX_PERIOD 255 + +#define SPDIF_REG_SCR 0x00 +#define SPDIF_REG_SRCD 0x04 +#define SPDIF_REG_SRPC 0x08 +#define SPDIF_REG_SIE 0x0C +#define SPDIF_REG_SIS 0x10 +#define SPDIF_REG_SIC 0x10 +#define SPDIF_REG_SRL 0x14 +#define SPDIF_REG_SRR 0x18 +#define SPDIF_REG_SRCSLH 0x1C +#define SPDIF_REG_SRCSLL 0x20 +#define SPDIF_REG_SQU 0x24 +#define SPDIF_REG_SRQ 0x28 +#define SPDIF_REG_STL 0x2C +#define SPDIF_REG_STR 0x30 +#define SPDIF_REG_STCSCH 0x34 +#define SPDIF_REG_STCSCL 0x38 +#define SPDIF_REG_SRFM 0x44 +#define SPDIF_REG_STC 0x50 + +/* SPDIF Configuration register */ +#define SCR_RXFIFO_CTL_ZERO (1 << 23) +#define SCR_RXFIFO_OFF (1 << 22) +#define SCR_RXFIFO_RST (1 << 21) +#define SCR_RXFIFO_FSEL_BIT (19) +#define SCR_RXFIFO_FSEL_MASK (0x3 << SCR_RXFIFO_FSEL_BIT) +#define SCR_RXFIFO_AUTOSYNC (1 << 18) +#define SCR_TXFIFO_AUTOSYNC (1 << 17) +#define SCR_TXFIFO_ESEL_BIT (15) +#define SCR_TXFIFO_ESEL_MASK (0x3 << SCR_TXFIFO_FSEL_BIT) +#define SCR_LOW_POWER (1 << 13) +#define SCR_SOFT_RESET (1 << 12) +#define SCR_TXFIFO_ZERO (0 << 10) +#define SCR_TXFIFO_NORMAL (1 << 10) +#define SCR_TXFIFO_ONESAMPLE (1 << 11) +#define SCR_DMA_RX_EN (1 << 9) +#define SCR_DMA_TX_EN (1 << 8) +#define SCR_VAL_CLEAR (1 << 5) +#define SCR_TXSEL_OFF (0 << 2) +#define SCR_TXSEL_RX (1 << 2) +#define SCR_TXSEL_NORMAL (0x5 << 2) +#define SCR_USRC_SEL_NONE (0x0) +#define SCR_USRC_SEL_RECV (0x1) +#define SCR_USRC_SEL_CHIP (0x3) + +/* SPDIF CDText control */ +#define SRCD_CD_USER_OFFSET 1 +#define SRCD_CD_USER (1 << SRCD_CD_USER_OFFSET) + +/* SPDIF Phase Configuration register */ +#define SRPC_DPLL_LOCKED (1 << 6) +#define SRPC_CLKSRC_SEL_OFFSET 7 +#define SRPC_CLKSRC_SEL_LOCKED 5 +/* gain sel */ +#define SRPC_GAINSEL_OFFSET 3 + +enum spdif_gainsel { + GAINSEL_MULTI_24 = 0, + GAINSEL_MULTI_16, + GAINSEL_MULTI_12, + GAINSEL_MULTI_8, + GAINSEL_MULTI_6, + GAINSEL_MULTI_4, + GAINSEL_MULTI_3, + GAINSEL_MULTI_MAX, +}; + +#define SPDIF_DEFAULT_GAINSEL GAINSEL_MULTI_8 + +/* SPDIF interrupt mask define */ +#define INT_DPLL_LOCKED (1 << 20) +#define INT_TXFIFO_UNOV (1 << 19) +#define INT_TXFIFO_RESYNC (1 << 18) +#define INT_CNEW (1 << 17) +#define INT_VAL_NOGOOD (1 << 16) +#define INT_SYM_ERR (1 << 15) +#define INT_BIT_ERR (1 << 14) +#define INT_URX_FUL (1 << 10) +#define INT_URX_OV (1 << 9) +#define INT_QRX_FUL (1 << 8) +#define INT_QRX_OV (1 << 7) +#define INT_UQ_SYNC (1 << 6) +#define INT_UQ_ERR (1 << 5) +#define INT_RX_UNOV (1 << 4) +#define INT_RX_RESYNC (1 << 3) +#define INT_LOSS_LOCK (1 << 2) +#define INT_TX_EMPTY (1 << 1) +#define INT_RXFIFO_FUL (1 << 0) + +/* SPDIF Clock register */ +#define STC_SYSCLK_DIV_OFFSET 11 +#define STC_TXCLK_SRC_OFFSET 8 +#define STC_TXCLK_DIV_OFFSET 0 + +#define SPDIF_CSTATUS_BYTE 6 +#define SPDIF_UBITS_SIZE 96 +#define SPDIF_QSUB_SIZE (SPDIF_UBITS_SIZE/8) + +enum spdif_clk_accuracy { + SPDIF_CLK_ACCURACY_LEV2 = 0, + SPDIF_CLK_ACCURACY_LEV1 = 2, + SPDIF_CLK_ACCURACY_LEV3 = 1, + SPDIF_CLK_ACCURACY_RESV = 3 +}; + +/* SPDIF clock source */ +enum spdif_clk_src { + SPDIF_CLK_SRC1 = 0, + SPDIF_CLK_SRC2, + SPDIF_CLK_SRC3, + SPDIF_CLK_SRC4, + SPDIF_CLK_SRC5, +}; + +enum spdif_max_wdl { + SPDIF_MAX_WDL_20, + SPDIF_MAX_WDL_24 +}; + +enum spdif_wdl { + SPDIF_WDL_DEFAULT = 0, + SPDIF_WDL_FIFTH = 4, + SPDIF_WDL_FOURTH = 3, + SPDIF_WDL_THIRD = 2, + SPDIF_WDL_SECOND = 1, + SPDIF_WDL_MAX = 5 +}; + +static unsigned int gainsel_multi[GAINSEL_MULTI_MAX] = { + 24 * 1024, 16 * 1024, 12 * 1024, + 8 * 1024, 6 * 1024, 4 * 1024, + 3 * 1024, +}; + +/*! + * SPDIF control structure + * Defines channel status, subcode and Q sub + */ +struct spdif_mixer_control { + + /* spinlock to access control data */ + spinlock_t ctl_lock; + /* IEC958 channel tx status bit */ + unsigned char ch_status[4]; + /* User bits */ + unsigned char subcode[2 * SPDIF_UBITS_SIZE]; + /* Q subcode part of user bits */ + unsigned char qsub[2 * SPDIF_QSUB_SIZE]; + /* buffer ptrs for writer */ + unsigned int upos; + unsigned int qpos; + /* ready buffer index of the two buffers */ + unsigned int ready_buf; +}; + +/*! + * This structure represents an audio stream in term of + * channel DMA, HW configuration on spdif controller. + */ +struct mxc_spdif_stream { + + /*! + * identification string + */ + char *id; + /*! + * device identifier for DMA + */ + int dma_wchannel; + /*! + * we are using this stream for transfer now + */ + int active:1; + /*! + * current transfer period + */ + int period; + /*! + * current count of transfered periods + */ + int periods; + /*! + * for locking in DMA operations + */ + spinlock_t dma_lock; + /*! + * Alsa substream pointer + */ + struct snd_pcm_substream *stream; +}; + +struct mxc_spdif_device { + /*! + * SPDIF module register base address + */ + unsigned long __iomem *reg_base; + + /*! + * spdif tx available or not + */ + int mxc_spdif_tx; + + /*! + * spdif rx available or not + */ + int mxc_spdif_rx; + + /*! + * spdif 44100 clock src + */ + int spdif_txclk_44100; + + /*! + * spdif 48000 clock src + */ + int spdif_txclk_48000; + + /*! + * ALSA SPDIF sound card handle + */ + struct snd_card *card; + + /*! + * ALSA spdif driver type handle + */ + struct snd_pcm *pcm; + + /*! + * DPLL locked status + */ + atomic_t dpll_locked; + + /*! + * Playback/Capture substream + */ + struct mxc_spdif_stream s[2]; +}; + +static struct spdif_mixer_control mxc_spdif_control; + +static unsigned long spdif_base_addr; + +/* define each spdif interrupt handlers */ +typedef void (*spdif_irq_func_t) (unsigned int bit, void *desc); + +/* spdif irq functions declare */ +static void spdif_irq_fifo(unsigned int bit, void *devid); +static void spdif_irq_dpll_lock(unsigned int bit, void *devid); +static void spdif_irq_uq(unsigned int bit, void *devid); +static void spdif_irq_bit_error(unsigned int bit, void *devid); +static void spdif_irq_sym_error(unsigned int bit, void *devid); +static void spdif_irq_valnogood(unsigned int bit, void *devid); +static void spdif_irq_cnew(unsigned int bit, void *devid); + +/* irq function list */ +static spdif_irq_func_t spdif_irq_handlers[] = { + spdif_irq_fifo, + spdif_irq_fifo, + spdif_irq_dpll_lock, + NULL, + spdif_irq_fifo, + spdif_irq_uq, + spdif_irq_uq, + spdif_irq_uq, + spdif_irq_uq, + spdif_irq_uq, + spdif_irq_uq, + NULL, + NULL, + NULL, + spdif_irq_bit_error, + spdif_irq_sym_error, + spdif_irq_valnogood, + spdif_irq_cnew, + NULL, + spdif_irq_fifo, + spdif_irq_dpll_lock, +}; + +/*! + * @brief Enable/Disable spdif DMA request + * + * This function is called to enable or disable the dma transfer + */ +static void spdif_dma_enable(int txrx, int enable) +{ + unsigned long value; + + value = __raw_readl(SPDIF_REG_SCR + spdif_base_addr) & 0xfffeff; + if (enable) + value |= txrx; + + __raw_writel(value, SPDIF_REG_SCR + spdif_base_addr); + +} + +/*! + * @brief Enable spdif interrupt + * + * This function is called to enable relevant interrupts. + */ +static void spdif_intr_enable(unsigned long intr, int enable) +{ + unsigned long value; + + value = __raw_readl(spdif_base_addr + SPDIF_REG_SIE) & 0xffffff; + if (enable) + value |= intr; + else + value &= ~intr; + + __raw_writel(value, spdif_base_addr + SPDIF_REG_SIE); +} + +/*! + * @brief Set the clock accuracy level in the channel bit + * + * This function is called to set the clock accuracy level + */ +static int spdif_set_clk_accuracy(enum spdif_clk_accuracy level) +{ + unsigned long value; + + value = __raw_readl(SPDIF_REG_STCSCL + spdif_base_addr) & 0xffffcf; + value |= (level << 4); + __raw_writel(value, SPDIF_REG_STCSCL + spdif_base_addr); + + return 0; +} + +/*! + * set SPDIF PhaseConfig register for rx clock + */ +static int spdif_set_rx_clksrc(enum spdif_clk_src clksrc, + enum spdif_gainsel gainsel, int dpll_locked) +{ + unsigned long value; + if (clksrc > SPDIF_CLK_SRC5 || gainsel > GAINSEL_MULTI_3) + return 1; + + value = (dpll_locked ? (clksrc) : + (clksrc + SRPC_CLKSRC_SEL_LOCKED)) << SRPC_CLKSRC_SEL_OFFSET | + (gainsel << SRPC_GAINSEL_OFFSET); + __raw_writel(value, spdif_base_addr + SPDIF_REG_SRPC); + + return 0; +} + +/*! + * Get RX data clock rate + * given the SPDIF bus_clk + */ +static int spdif_get_rxclk_rate(struct clk *bus_clk, enum spdif_gainsel gainsel) +{ + unsigned long freqmeas, phaseconf, busclk_freq = 0; + u64 tmpval64; + enum spdif_clk_src clksrc; + + freqmeas = __raw_readl(spdif_base_addr + SPDIF_REG_SRFM); + phaseconf = __raw_readl(spdif_base_addr + SPDIF_REG_SRPC); + + clksrc = (phaseconf >> SRPC_CLKSRC_SEL_OFFSET) & 0x0F; + if (clksrc < 5 && (phaseconf & SRPC_DPLL_LOCKED)) { + /* get bus clock from system */ + busclk_freq = clk_get_rate(bus_clk); + } + + /* FreqMeas_CLK = (BUS_CLK*FreqMeas[23:0])/2^10/GAINSEL/128 */ + tmpval64 = (u64) busclk_freq * freqmeas; + do_div(tmpval64, gainsel_multi[gainsel]); + do_div(tmpval64, 128 * 1024); + return (int)tmpval64; +} + +/*! + * @brief Set the audio sample rate in the channel status bit + * + * This function is called to set the audio sample rate to be transfered. + */ +static int spdif_set_sample_rate(int src_44100, int src_48000, int sample_rate) +{ + unsigned long cstatus, stc; + + cstatus = __raw_readl(SPDIF_REG_STCSCL + spdif_base_addr) & 0xfffff0; + stc = __raw_readl(SPDIF_REG_STC + spdif_base_addr) & ~0x7FF; + + switch (sample_rate) { + case 44100: + __raw_writel(cstatus, SPDIF_REG_STCSCL + spdif_base_addr); + stc |= (src_44100 << 8) | 0x07; + __raw_writel(stc, SPDIF_REG_STC + spdif_base_addr); + pr_debug("set sample rate to 44100\n"); + break; + case 48000: + cstatus |= 0x04; + __raw_writel(cstatus, SPDIF_REG_STCSCL + spdif_base_addr); + stc |= (src_48000 << 8) | 0x07; + __raw_writel(stc, SPDIF_REG_STC + spdif_base_addr); + pr_debug("set sample rate to 48000\n"); + break; + case 32000: + cstatus |= 0x0c; + __raw_writel(cstatus, SPDIF_REG_STCSCL + spdif_base_addr); + stc |= (src_48000 << 8) | 0x0b; + __raw_writel(stc, SPDIF_REG_STC + spdif_base_addr); + pr_debug("set sample rate to 32000\n"); + break; + } + + return 0; +} + +/*! + * @brief Set the lchannel status bit + * + * This function is called to set the channel status + */ +static int spdif_set_channel_status(int value, unsigned long reg) +{ + __raw_writel(value & 0xffffff, reg + spdif_base_addr); + + return 0; +} + +/*! + * @brief Get spdif interrupt status and clear the interrupt + * + * This function is called to check relevant interrupt status + */ +static int spdif_intr_status(void) +{ + unsigned long value; + + value = __raw_readl(SPDIF_REG_SIS + spdif_base_addr) & 0xffffff; + value &= __raw_readl(spdif_base_addr + SPDIF_REG_SIE); + __raw_writel(value, SPDIF_REG_SIC + spdif_base_addr); + + return value; +} + +/*! + * @brief spdif interrupt handler + */ +static irqreturn_t spdif_isr(int irq, void *dev_id) +{ + unsigned long int_stat; + int line; + + int_stat = spdif_intr_status(); + + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + if (spdif_irq_handlers[line - 1] != NULL) + spdif_irq_handlers[line - 1] (line - 1, dev_id); + } + + return IRQ_HANDLED; +} + +/*! + * Interrupt handlers + * + */ +/*! + * FIFO related interrupts handler + * + * Rx FIFO Full, Underrun/Overrun interrupts + * Tx FIFO Empty, Underrun/Overrun interrupts + */ +static void spdif_irq_fifo(unsigned int bit, void *devid) +{ + +} + +/*! + * DPLL lock related interrupts handler + * + * DPLL locked and lock loss interrupts + */ +static void spdif_irq_dpll_lock(unsigned int bit, void *devid) +{ + struct mxc_spdif_device *chip = (struct mxc_spdif_device *)devid; + unsigned int locked = __raw_readl(spdif_base_addr + SPDIF_REG_SRPC); + + if (locked & SRPC_DPLL_LOCKED) { + pr_debug("SPDIF Rx dpll locked\n"); + atomic_set(&chip->dpll_locked, 1); + } else { + /* INT_LOSS_LOCK */ + pr_debug("SPDIF Rx dpll loss lock\n"); + atomic_set(&chip->dpll_locked, 0); + } +} + +/*! + * U/Q channel related interrupts handler + * + * U/QChannel full, overrun interrupts + * U/QChannel sync error and frame error interrupts + */ +static void spdif_irq_uq(unsigned int bit, void *devid) +{ + unsigned long val; + int index; + struct spdif_mixer_control *ctrl = &mxc_spdif_control; + + bit = 1 << bit; + /* get U/Q channel datas */ + switch (bit) { + + case INT_URX_OV: /* read U data */ + pr_debug("User bit receive overrun\n"); + case INT_URX_FUL: + pr_debug("U bit receive full\n"); + + if (ctrl->upos >= SPDIF_UBITS_SIZE * 2) { + ctrl->upos = 0; + } else if (unlikely((ctrl->upos % SPDIF_UBITS_SIZE) + 3 + > SPDIF_UBITS_SIZE)) { + pr_err("User bit receivce buffer overflow\n"); + break; + } + val = __raw_readl(spdif_base_addr + SPDIF_REG_SQU); + ctrl->subcode[ctrl->upos++] = val >> 16; + ctrl->subcode[ctrl->upos++] = val >> 8; + ctrl->subcode[ctrl->upos++] = val; + + break; + + case INT_QRX_OV: /* read Q data */ + pr_debug("Q bit receive overrun\n"); + case INT_QRX_FUL: + pr_debug("Q bit receive full\n"); + + if (ctrl->qpos >= SPDIF_QSUB_SIZE * 2) { + ctrl->qpos = 0; + } else if (unlikely((ctrl->qpos % SPDIF_QSUB_SIZE) + 3 + > SPDIF_QSUB_SIZE)) { + pr_err("Q bit receivce buffer overflow\n"); + break; + } + val = __raw_readl(spdif_base_addr + SPDIF_REG_SRQ); + ctrl->qsub[ctrl->qpos++] = val >> 16; + ctrl->qsub[ctrl->qpos++] = val >> 8; + ctrl->qsub[ctrl->qpos++] = val; + + break; + + case INT_UQ_ERR: /* read U/Q data and do buffer reset */ + pr_debug("U/Q bit receive error\n"); + val = __raw_readl(spdif_base_addr + SPDIF_REG_SQU); + val = __raw_readl(spdif_base_addr + SPDIF_REG_SRQ); + /* drop this U/Q buffer */ + ctrl->ready_buf = ctrl->upos = ctrl->qpos = 0; + break; + + case INT_UQ_SYNC: /* U/Q buffer reset */ + pr_debug("U/Q sync receive\n"); + + if (ctrl->qpos == 0) + break; + index = (ctrl->qpos - 1) / SPDIF_QSUB_SIZE; + /* set ready to this buffer */ + ctrl->ready_buf = index + 1; + break; + } +} + +/*! + * SPDIF receiver found parity bit error interrupt handler + */ +static void spdif_irq_bit_error(unsigned int bit, void *devid) +{ + pr_debug("SPDIF interrupt parity bit error\n"); +} + +/*! + * SPDIF receiver found illegal symbol interrupt handler + */ +static void spdif_irq_sym_error(unsigned int bit, void *devid) +{ + pr_debug("SPDIF interrupt symbol error\n"); +} + +/*! + * SPDIF validity flag no good interrupt handler + */ +static void spdif_irq_valnogood(unsigned int bit, void *devid) +{ + pr_debug("SPDIF interrupt validate is not good\n"); +} + +/*! + * SPDIF receive change in value of control channel + */ +static void spdif_irq_cnew(unsigned int bit, void *devid) +{ + pr_debug("SPDIF interrupt cstatus new\n"); +} + +/*! + * Do software reset to SPDIF + */ +static void spdif_softreset(void) +{ + unsigned long value = 1; + int cycle = 0; + __raw_writel(SCR_SOFT_RESET, spdif_base_addr + SPDIF_REG_SCR); + while (value && (cycle++ < 10)) { + value = __raw_readl(spdif_base_addr + SPDIF_REG_SCR) & 0x1000; + } + +} + +/*! + * SPDIF RX initial function + */ +static void spdif_rx_init(void) +{ + unsigned long regval; + + regval = __raw_readl(spdif_base_addr + SPDIF_REG_SCR); + /** + * initial and reset SPDIF configuration: + * RxFIFO off + * RxFIFO sel to 8 sample + * Autosync + * Valid bit set + */ + regval &= ~(SCR_RXFIFO_OFF | SCR_RXFIFO_CTL_ZERO | SCR_LOW_POWER); + regval |= (2 << SCR_RXFIFO_FSEL_BIT) | SCR_RXFIFO_AUTOSYNC; + __raw_writel(regval, spdif_base_addr + SPDIF_REG_SCR); +} + +/*! + * SPDIF RX un-initial function + */ +static void spdif_rx_uninit(void) +{ + unsigned long regval; + + /* turn off RX fifo, disable dma and autosync */ + regval = __raw_readl(spdif_base_addr + SPDIF_REG_SCR); + regval |= SCR_RXFIFO_OFF | SCR_RXFIFO_CTL_ZERO; + regval &= ~(SCR_DMA_RX_EN | SCR_RXFIFO_AUTOSYNC); + __raw_writel(regval, spdif_base_addr + SPDIF_REG_SCR); +} + +/*! + * @brief Initialize spdif module + * + * This function is called to set the spdif to initial state. + */ +static void spdif_tx_init(void) +{ + unsigned long regval; + + regval = __raw_readl(spdif_base_addr + SPDIF_REG_SCR); + + regval &= 0xfc32e3; + regval |= SCR_TXFIFO_AUTOSYNC | SCR_TXFIFO_NORMAL | + SCR_TXSEL_NORMAL | SCR_USRC_SEL_CHIP | (2 << SCR_TXFIFO_ESEL_BIT); + __raw_writel(regval, SPDIF_REG_SCR + spdif_base_addr); + + /* Default clock source from EXTAL, divider by 8, generate 44.1kHz + sample rate */ + regval = 0x07; + __raw_writel(regval, SPDIF_REG_STC + spdif_base_addr); + +} + +/*! + * @brief deinitialize spdif module + * + * This function is called to stop the spdif + */ +static void spdif_tx_uninit(void) +{ + unsigned long regval; + + regval = __raw_readl(SPDIF_REG_SCR + spdif_base_addr) & 0xffffe3; + regval |= SCR_TXSEL_OFF; + __raw_writel(regval, SPDIF_REG_SCR + spdif_base_addr); + regval = __raw_readl(SPDIF_REG_STC + spdif_base_addr) & ~0x7FF; + regval |= (0x7 << STC_TXCLK_SRC_OFFSET); + __raw_writel(regval, SPDIF_REG_STC + spdif_base_addr); + +} + +static unsigned int spdif_playback_rates[] = { 32000, 44100, 48000 }; +static unsigned int spdif_capture_rates[] = { + 16000, 32000, 44100, 48000, 64000, 96000 +}; + +/*! + * this structure represents the sample rates supported + * by SPDIF + */ +static struct snd_pcm_hw_constraint_list hw_playback_rates_stereo = { + .count = ARRAY_SIZE(spdif_playback_rates), + .list = spdif_playback_rates, + .mask = 0, +}; + +static struct snd_pcm_hw_constraint_list hw_capture_rates_stereo = { + .count = ARRAY_SIZE(spdif_capture_rates), + .list = spdif_capture_rates, + .mask = 0, +}; + +/*! + * This structure reprensents the capabilities of the driver + * in playback mode. + */ +static struct snd_pcm_hardware snd_spdif_playback_hw = { + .info = + (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_HALF_DUPLEX | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_LE, + .rates = + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = SPDIF_MAX_BUF_SIZE, + .period_bytes_min = SPDIF_MIN_PERIOD_SIZE, + .period_bytes_max = SPDIF_DMA_BUF_SIZE, + .periods_min = SPDIF_MIN_PERIOD, + .periods_max = SPDIF_MAX_PERIOD, + .fifo_size = 0, +}; + +/*! + * This structure reprensents the capabilities of the driver + * in capture mode. + */ +static struct snd_pcm_hardware snd_spdif_capture_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S24_LE, + .rates = + (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 + | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | + SNDRV_PCM_RATE_96000), + .rate_min = 16000, + .rate_max = 96000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = SPDIF_MAX_BUF_SIZE, + .period_bytes_min = SPDIF_MIN_PERIOD_SIZE, + .period_bytes_max = SPDIF_DMA_BUF_SIZE, + .periods_min = SPDIF_MIN_PERIOD, + .periods_max = SPDIF_MAX_PERIOD, + .fifo_size = 0, + +}; + +/*! + * This function configures the DMA channel used to transfer + * audio from MCU to SPDIF or from SPDIF to MCU + * + * @param s pointer to the structure of the current stream. + * @param callback pointer to function that will be + * called when a SDMA TX transfer finishes. + * + * @return 0 on success, -1 otherwise. + */ +static int +spdif_configure_dma_channel(struct mxc_spdif_stream *s, + mxc_dma_callback_t callback) +{ + int ret = -1; + int channel = -1; + + if (s->dma_wchannel != 0) + mxc_dma_free(s->dma_wchannel); + + if (s->stream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + + if (s->stream->runtime->sample_bits > 16) { + channel = + mxc_dma_request(MXC_DMA_SPDIF_32BIT_TX, + "SPDIF TX DMA"); + } else { + channel = + mxc_dma_request(MXC_DMA_SPDIF_16BIT_TX, + "SPDIF TX DMA"); + } + + } else if (s->stream->stream == SNDRV_PCM_STREAM_CAPTURE) { + + channel = mxc_dma_request(MXC_DMA_SPDIF_32BIT_RX, + "SPDIF RX DMA"); + + } + + pr_debug("spdif_configure_dma_channel: %d\n", channel); + + ret = mxc_dma_callback_set(channel, + (mxc_dma_callback_t) callback, (void *)s); + if (ret != 0) { + pr_info("spdif_configure_dma_channel - err\n"); + mxc_dma_free(channel); + return -1; + } + s->dma_wchannel = channel; + return 0; +} + +/*! + * This function gets the dma pointer position during playback/capture. + * Our DMA implementation does not allow to retrieve this position + * when a transfert is active, so, it answers the middle of + * the current period beeing transfered + * + * @param s pointer to the structure of the current stream. + * + */ +static u_int spdif_get_dma_pos(struct mxc_spdif_stream *s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int offset = 0; + substream = s->stream; + runtime = substream->runtime; + + offset = (runtime->period_size * (s->periods)); + if (offset >= runtime->buffer_size) + offset = 0; + pr_debug + ("MXC: spdif_get_dma_pos BIS offset %d, buffer_size %d\n", + offset, (int)runtime->buffer_size); + return offset; +} + +/*! + * This function stops the current dma transfert for playback + * and clears the dma pointers. + * + * @param s pointer to the structure of the current stream. + * + */ +static void spdif_stop_tx(struct mxc_spdif_stream *s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int offset; + + substream = s->stream; + runtime = substream->runtime; + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * s->periods; + + s->active = 0; + s->period = 0; + s->periods = 0; + + /* this stops the dma channel and clears the buffer ptrs */ + mxc_dma_disable(s->dma_wchannel); + spdif_dma_enable(SCR_DMA_TX_EN, 0); + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_TO_DEVICE); +} + +/*! + * This function is called whenever a new audio block needs to be + * transferred to SPDIF. The function receives the address and the size + * of the new block and start a new DMA transfer. + * + * @param s pointer to the structure of the current stream. + * + */ +static void spdif_start_tx(struct mxc_spdif_stream *s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size = 0; + unsigned int offset; + int ret = 0; + mxc_dma_requestbuf_t dma_request; + substream = s->stream; + runtime = substream->runtime; + memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t)); + if (s->active) { + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * s->period; + dma_request.src_addr = + (dma_addr_t) (dma_map_single + (NULL, runtime->dma_area + offset, dma_size, + DMA_TO_DEVICE)); + + dma_request.dst_addr = (dma_addr_t) (SPDIF_BASE_ADDR + 0x2c); + + dma_request.num_of_bytes = dma_size; + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_WRITE); + ret = mxc_dma_enable(s->dma_wchannel); + spdif_dma_enable(SCR_DMA_TX_EN, 1); + if (ret) { + pr_info("audio_process_dma: cannot queue DMA \ + buffer\n"); + return; + } + s->period++; + s->period %= runtime->periods; + + if ((s->period > s->periods) + && ((s->period - s->periods) > 1)) { + pr_debug("audio playback chain dma: already double \ + buffered\n"); + return; + } + + if ((s->period < s->periods) + && ((s->period + runtime->periods - s->periods) > 1)) { + pr_debug("audio playback chain dma: already double \ + buffered\n"); + return; + } + + if (s->period == s->periods) { + pr_debug("audio playback chain dma: s->period == \ + s->periods\n"); + return; + } + + if (snd_pcm_playback_hw_avail(runtime) < + 2 * runtime->period_size) { + pr_debug("audio playback chain dma: available data \ + is not enough\n"); + return; + } + + pr_debug + ("audio playback chain dma:to set up the 2nd dma buffer\n"); + pr_debug("SCR: 0x%08x\n", + __raw_readl(spdif_base_addr + SPDIF_REG_SCR)); + + offset = dma_size * s->period; + dma_request.src_addr = + (dma_addr_t) (dma_map_single + (NULL, runtime->dma_area + offset, dma_size, + DMA_TO_DEVICE)); + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_WRITE); + ret = mxc_dma_enable(s->dma_wchannel); + s->period++; + s->period %= runtime->periods; + + } + return; +} + +/*! + * This is a callback which will be called + * when a TX transfer finishes. The call occurs + * in interrupt context. + * + * @param data pointer to the structure of the current stream + * @param error DMA error flag + * @param count number of bytes transfered by the DMA + */ +static void spdif_tx_callback(void *data, int error, unsigned int count) +{ + struct mxc_spdif_stream *s; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int previous_period; + unsigned int offset; + s = data; + substream = s->stream; + runtime = substream->runtime; + previous_period = s->periods; + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * previous_period; + + spin_lock(&s->dma_lock); + s->periods++; + s->periods %= runtime->periods; + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_TO_DEVICE); + spin_unlock(&s->dma_lock); + + if (s->active) + snd_pcm_period_elapsed(s->stream); + + spin_lock(&s->dma_lock); + spdif_start_tx(s); + spin_unlock(&s->dma_lock); +} + +/*! + * This function is a dispatcher of command to be executed + * by the driver for playback. + * + * @param substream pointer to the structure of the current stream. + * @param cmd command to be executed + * + * @return 0 on success, -1 otherwise. + */ +static int +snd_mxc_spdif_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct mxc_spdif_device *chip; + struct mxc_spdif_stream *s; + int err = 0; + unsigned long flags; + chip = snd_pcm_substream_chip(substream); + s = &chip->s[SNDRV_PCM_STREAM_PLAYBACK]; + + spin_lock_irqsave(&s->dma_lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + s->active = 1; + spdif_start_tx(s); + break; + case SNDRV_PCM_TRIGGER_STOP: + spdif_stop_tx(s); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + s->active = 0; + s->periods = 0; + break; + case SNDRV_PCM_TRIGGER_RESUME: + s->active = 1; + spdif_start_tx(s); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + s->active = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + s->active = 1; + spdif_start_tx(s); + break; + default: + err = -EINVAL; + break; + } + spin_unlock_irqrestore(&s->dma_lock, flags); + return err; +} + +/*! + * This function configures the hardware to allow audio + * playback operations. It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_spdif_playback_prepare(struct snd_pcm_substream *substream) +{ + struct mxc_spdif_device *chip; + struct snd_pcm_runtime *runtime; + int err; + unsigned int ch_status; + + chip = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + spdif_tx_init(); + + ch_status = + ((mxc_spdif_control.ch_status[2] << 16) | (mxc_spdif_control. + ch_status[1] << 8) | + mxc_spdif_control.ch_status[0]); + spdif_set_channel_status(ch_status, SPDIF_REG_STCSCH); + ch_status = mxc_spdif_control.ch_status[3]; + spdif_set_channel_status(ch_status, SPDIF_REG_STCSCL); + spdif_intr_enable(INT_TXFIFO_RESYNC, 1); + spdif_set_sample_rate(chip->spdif_txclk_44100, chip->spdif_txclk_48000, + runtime->rate); + spdif_set_clk_accuracy(SPDIF_CLK_ACCURACY_LEV2); + /* setup DMA controller for spdif tx */ + err = spdif_configure_dma_channel(&chip-> + s[SNDRV_PCM_STREAM_PLAYBACK], + spdif_tx_callback); + if (err < 0) { + pr_info("snd_mxc_spdif_playback_prepare - err < 0\n"); + return err; + } + + /** + * FIXME: dump registers + */ + pr_debug("SCR: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SCR)); + pr_debug("SIE: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIE)); + pr_debug("STC: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_STC)); + return 0; +} + +/*! + * This function gets the current playback pointer position. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + */ +static snd_pcm_uframes_t +snd_mxc_spdif_playback_pointer(struct snd_pcm_substream *substream) +{ + struct mxc_spdif_device *chip; + chip = snd_pcm_substream_chip(substream); + return spdif_get_dma_pos(&chip->s[SNDRV_PCM_STREAM_PLAYBACK]); +} + +static int snd_card_mxc_spdif_playback_open(struct snd_pcm_substream *substream) +{ + struct mxc_spdif_device *chip; + struct snd_pcm_runtime *runtime; + int err; + struct mxc_spdif_platform_data *spdif_data; + + chip = snd_pcm_substream_chip(substream); + + spdif_data = chip->card->dev->platform_data; + /* enable tx clock */ + clk_enable(spdif_data->spdif_clk); + clk_enable(spdif_data->spdif_audio_clk); + + runtime = substream->runtime; + chip->s[SNDRV_PCM_STREAM_PLAYBACK].stream = substream; + runtime->hw = snd_spdif_playback_hw; + err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_playback_rates_stereo); + if (err < 0) + goto failed; + err = + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + goto failed; + + return 0; + failed: + clk_disable(spdif_data->spdif_clk); + return err; +} + +/*! + * This function closes an spdif device for playback. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_spdif_playback_close(struct snd_pcm_substream + *substream) +{ + struct mxc_spdif_device *chip; + struct mxc_spdif_platform_data *spdif_data; + + chip = snd_pcm_substream_chip(substream); + spdif_data = chip->card->dev->platform_data; + + pr_debug("SIS: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIS)); + + spdif_intr_status(); + spdif_intr_enable(INT_TXFIFO_RESYNC, 0); + spdif_tx_uninit(); + clk_disable(spdif_data->spdif_audio_clk); + clk_disable(spdif_data->spdif_clk); + mxc_dma_free(chip->s[SNDRV_PCM_STREAM_PLAYBACK].dma_wchannel); + chip->s[SNDRV_PCM_STREAM_PLAYBACK].dma_wchannel = 0; + + return 0; +} + +/*! TODO: update the dma start/stop callback routine + * This function stops the current dma transfert for capture + * and clears the dma pointers. + * + * @param s pointer to the structure of the current stream. + * + */ +static void spdif_stop_rx(struct mxc_spdif_stream *s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int offset; + + substream = s->stream; + runtime = substream->runtime; + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * s->periods; + + s->active = 0; + s->period = 0; + s->periods = 0; + + /* this stops the dma channel and clears the buffer ptrs */ + mxc_dma_disable(s->dma_wchannel); + spdif_dma_enable(SCR_DMA_RX_EN, 0); + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_FROM_DEVICE); +} + +/*! + * This function is called whenever a new audio block needs to be + * received from SPDIF. The function receives the address and the size + * of the new block and start a new DMA transfer. + * + * @param s pointer to the structure of the current stream. + * + */ +static void spdif_start_rx(struct mxc_spdif_stream *s) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size = 0; + unsigned int offset; + int ret = 0; + mxc_dma_requestbuf_t dma_request; + + substream = s->stream; + runtime = substream->runtime; + memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t)); + + if (s->active) { + dma_size = frames_to_bytes(runtime, runtime->period_size); + pr_debug("s->period (%x) runtime->periods (%d)\n", + s->period, runtime->periods); + pr_debug("runtime->period_size (%d) dma_size (%d)\n", + (unsigned int)runtime->period_size, + runtime->dma_bytes); + + offset = dma_size * s->period; + dma_request.dst_addr = + (dma_addr_t) (dma_map_single + (NULL, runtime->dma_area + offset, dma_size, + DMA_FROM_DEVICE)); + + dma_request.src_addr = + (dma_addr_t) (SPDIF_BASE_ADDR + SPDIF_REG_SRL); + dma_request.num_of_bytes = dma_size; + /* config and enable sdma for RX */ + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_READ); + ret = mxc_dma_enable(s->dma_wchannel); + /* enable SPDIF dma */ + spdif_dma_enable(SCR_DMA_RX_EN, 1); + + if (ret) { + pr_info("audio_process_dma: cannot queue DMA \ + buffer\n"); + return; + } + s->period++; + s->period %= runtime->periods; + + if ((s->period > s->periods) + && ((s->period - s->periods) > 1)) { + pr_debug("audio capture chain dma: already double \ + buffered\n"); + return; + } + + if ((s->period < s->periods) + && ((s->period + runtime->periods - s->periods) > 1)) { + pr_debug("audio capture chain dma: already double \ + buffered\n"); + return; + } + + if (s->period == s->periods) { + pr_debug("audio capture chain dma: s->period == \ + s->periods\n"); + return; + } + + if (snd_pcm_capture_hw_avail(runtime) < + 2 * runtime->period_size) { + pr_debug("audio capture chain dma: available data \ + is not enough\n"); + return; + } + + pr_debug + ("audio playback chain dma:to set up the 2nd dma buffer\n"); + + offset = dma_size * s->period; + dma_request.dst_addr = + (dma_addr_t) (dma_map_single + (NULL, runtime->dma_area + offset, dma_size, + DMA_FROM_DEVICE)); + mxc_dma_config(s->dma_wchannel, &dma_request, 1, + MXC_DMA_MODE_READ); + ret = mxc_dma_enable(s->dma_wchannel); + s->period++; + s->period %= runtime->periods; + + } + return; +} + +/*! + * This is a callback which will be called + * when a RX transfer finishes. The call occurs + * in interrupt context. + * + * @param data pointer to the structure of the current stream + * @param error DMA error flag + * @param count number of bytes transfered by the DMA + */ +static void spdif_rx_callback(void *data, int error, unsigned int count) +{ + struct mxc_spdif_stream *s; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int previous_period; + unsigned int offset; + + s = data; + substream = s->stream; + runtime = substream->runtime; + previous_period = s->periods; + dma_size = frames_to_bytes(runtime, runtime->period_size); + offset = dma_size * previous_period; + + spin_lock(&s->dma_lock); + s->periods++; + s->periods %= runtime->periods; + + dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, + DMA_FROM_DEVICE); + spin_unlock(&s->dma_lock); + + if (s->active) + snd_pcm_period_elapsed(s->stream); + spin_lock(&s->dma_lock); + spdif_start_rx(s); + spin_unlock(&s->dma_lock); +} + +/*! + * This function is a dispatcher of command to be executed + * by the driver for capture. + * + * @param substream pointer to the structure of the current stream. + * @param cmd command to be executed + * + * @return 0 on success, -1 otherwise. + */ +static int +snd_mxc_spdif_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct mxc_spdif_device *chip; + struct mxc_spdif_stream *s; + int err = 0; + unsigned long flags; + chip = snd_pcm_substream_chip(substream); + s = &chip->s[SNDRV_PCM_STREAM_CAPTURE]; + + spin_lock_irqsave(&s->dma_lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + s->active = 1; + spdif_start_rx(s); + break; + case SNDRV_PCM_TRIGGER_STOP: + spdif_stop_rx(s); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + s->active = 0; + s->periods = 0; + break; + case SNDRV_PCM_TRIGGER_RESUME: + s->active = 1; + spdif_start_rx(s); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + s->active = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + s->active = 1; + spdif_start_rx(s); + break; + default: + err = -EINVAL; + break; + } + spin_unlock_irqrestore(&s->dma_lock, flags); + return err; +} + +/*! + * This function configures the hardware to allow audio + * capture operations. It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_spdif_capture_prepare(struct snd_pcm_substream *substream) +{ + struct mxc_spdif_device *chip; + struct mxc_spdif_platform_data *spdif_data; + struct snd_pcm_runtime *runtime; + int err; + + chip = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + spdif_data = chip->card->dev->platform_data; + + spdif_rx_init(); + /* enable interrupts, include DPLL lock */ + spdif_intr_enable(INT_SYM_ERR | INT_BIT_ERR | INT_URX_FUL | + INT_URX_OV | INT_QRX_FUL | INT_QRX_OV | + INT_UQ_SYNC | INT_UQ_ERR | INT_RX_RESYNC | + INT_LOSS_LOCK, 1); + + /* setup rx clock source */ + spdif_set_rx_clksrc(spdif_data->spdif_clkid, SPDIF_DEFAULT_GAINSEL, 1); + + /* setup DMA controller for spdif rx */ + err = spdif_configure_dma_channel(&chip-> + s[SNDRV_PCM_STREAM_CAPTURE], + spdif_rx_callback); + if (err < 0) { + pr_info("snd_mxc_spdif_playback_prepare - err < 0\n"); + return err; + } + + /* Debug: dump registers */ + pr_debug("SCR: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SCR)); + pr_debug("SIE: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIE)); + pr_debug("SRPC: 0x%08x\n", + __raw_readl(spdif_base_addr + SPDIF_REG_SRPC)); + pr_debug("FreqMeas: 0x%08x\n", + __raw_readl(spdif_base_addr + SPDIF_REG_SRFM)); + + return 0; +} + +/*! + * This function gets the current capture pointer position. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + */ +static snd_pcm_uframes_t +snd_mxc_spdif_capture_pointer(struct snd_pcm_substream *substream) +{ + struct mxc_spdif_device *chip; + chip = snd_pcm_substream_chip(substream); + return spdif_get_dma_pos(&chip->s[SNDRV_PCM_STREAM_CAPTURE]); +} + +/*! + * This function opens a spdif device in capture mode + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_spdif_capture_open(struct snd_pcm_substream *substream) +{ + struct mxc_spdif_device *chip; + struct snd_pcm_runtime *runtime; + int err = 0; + struct mxc_spdif_platform_data *spdif_data; + + chip = snd_pcm_substream_chip(substream); + + spdif_data = chip->card->dev->platform_data; + /* enable rx bus clock */ + clk_enable(spdif_data->spdif_clk); + + runtime = substream->runtime; + chip->s[SNDRV_PCM_STREAM_CAPTURE].stream = substream; + runtime->hw = snd_spdif_capture_hw; + + /* set hw param constraints */ + err = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_capture_rates_stereo); + if (err < 0) + goto failed; + err = + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + goto failed; + + /* enable spdif dpll lock interrupt */ + spdif_intr_enable(INT_DPLL_LOCKED, 1); + + return 0; + + failed: + clk_disable(spdif_data->spdif_clk); + return err; +} + +/*! + * This function closes an spdif device for capture. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_spdif_capture_close(struct snd_pcm_substream + *substream) +{ + struct mxc_spdif_device *chip; + struct mxc_spdif_platform_data *spdif_data; + + chip = snd_pcm_substream_chip(substream); + spdif_data = chip->card->dev->platform_data; + + pr_debug("SIS: 0x%08x\n", __raw_readl(spdif_base_addr + SPDIF_REG_SIS)); + pr_debug("SRPC: 0x%08x\n", + __raw_readl(spdif_base_addr + SPDIF_REG_SRPC)); + pr_debug("FreqMeas: 0x%08x\n", + __raw_readl(spdif_base_addr + SPDIF_REG_SRFM)); + + spdif_intr_enable(INT_DPLL_LOCKED | INT_SYM_ERR | INT_BIT_ERR | + INT_URX_FUL | INT_URX_OV | INT_QRX_FUL | INT_QRX_OV | + INT_UQ_SYNC | INT_UQ_ERR | INT_RX_RESYNC | + INT_LOSS_LOCK, 0); + spdif_rx_uninit(); + clk_disable(spdif_data->spdif_clk); + mxc_dma_free(chip->s[SNDRV_PCM_STREAM_CAPTURE].dma_wchannel); + chip->s[SNDRV_PCM_STREAM_CAPTURE].dma_wchannel = 0; + return 0; +} + +/*! + * This function configure the Audio HW in terms of memory allocation. + * It is called by ALSA framework. + * + * @param substream pointer to the structure of the current stream. + * @param hw_params Pointer to hardware paramters structure + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_spdif_hw_params(struct snd_pcm_substream + *substream, struct snd_pcm_hw_params + *hw_params) +{ + struct snd_pcm_runtime *runtime; + int ret = 0; + runtime = substream->runtime; + ret = + snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); + if (ret < 0) { + pr_info("snd_mxc_spdif_hw_params - ret: %d\n", ret); + return ret; + } + runtime->dma_addr = virt_to_phys(runtime->dma_area); + return ret; +} + +/*! + * This function frees the spdif hardware at the end of playback. + * + * @param substream pointer to the structure of the current stream. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_mxc_spdif_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +/*! + * This structure is the list of operation that the driver + * must provide for the playback interface + */ +static struct snd_pcm_ops snd_card_mxc_spdif_playback_ops = { + .open = snd_card_mxc_spdif_playback_open, + .close = snd_card_mxc_spdif_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_mxc_spdif_hw_params, + .hw_free = snd_mxc_spdif_hw_free, + .prepare = snd_mxc_spdif_playback_prepare, + .trigger = snd_mxc_spdif_playback_trigger, + .pointer = snd_mxc_spdif_playback_pointer, +}; + +static struct snd_pcm_ops snd_card_mxc_spdif_capture_ops = { + .open = snd_card_mxc_spdif_capture_open, + .close = snd_card_mxc_spdif_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_mxc_spdif_hw_params, + .hw_free = snd_mxc_spdif_hw_free, + .prepare = snd_mxc_spdif_capture_prepare, + .trigger = snd_mxc_spdif_capture_trigger, + .pointer = snd_mxc_spdif_capture_pointer, +}; + +/*! + * This functions initializes the playback audio device supported by + * spdif + * + * @param mxc_spdif pointer to the sound card structure. + * + */ +void mxc_init_spdif_device(struct mxc_spdif_device *mxc_spdif) +{ + + /* initial spinlock for control data */ + spin_lock_init(&mxc_spdif_control.ctl_lock); + + if (mxc_spdif->mxc_spdif_tx) { + + mxc_spdif->s[SNDRV_PCM_STREAM_PLAYBACK].id = "spdif tx"; + /* init tx channel status default value */ + mxc_spdif_control.ch_status[0] = + IEC958_AES0_CON_NOT_COPYRIGHT | + IEC958_AES0_CON_EMPHASIS_5015; + mxc_spdif_control.ch_status[1] = IEC958_AES1_CON_DIGDIGCONV_ID; + mxc_spdif_control.ch_status[2] = 0x00; + mxc_spdif_control.ch_status[3] = + IEC958_AES3_CON_FS_44100 | IEC958_AES3_CON_CLOCK_1000PPM; + } + if (mxc_spdif->mxc_spdif_rx) { + + /* TODO: Add code here if capture is available */ + mxc_spdif->s[SNDRV_PCM_STREAM_CAPTURE].id = "spdif rx"; + } + +} + +/*! + * MXC SPDIF IEC958 controller(mixer) functions + * + * Channel status get/put control + * User bit value get/put control + * Valid bit value get control + * DPLL lock status get control + * User bit sync mode selection control + * + */ +static int mxc_pb_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 mxc_pb_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + uvalue->value.iec958.status[0] = mxc_spdif_control.ch_status[0]; + uvalue->value.iec958.status[1] = mxc_spdif_control.ch_status[1]; + uvalue->value.iec958.status[2] = mxc_spdif_control.ch_status[2]; + uvalue->value.iec958.status[3] = mxc_spdif_control.ch_status[3]; + return 0; +} + +static int mxc_pb_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + unsigned int ch_status; + mxc_spdif_control.ch_status[0] = uvalue->value.iec958.status[0]; + mxc_spdif_control.ch_status[1] = uvalue->value.iec958.status[1]; + mxc_spdif_control.ch_status[2] = uvalue->value.iec958.status[2]; + mxc_spdif_control.ch_status[3] = uvalue->value.iec958.status[3]; + ch_status = + ((mxc_spdif_control.ch_status[2] << 16) | (mxc_spdif_control. + ch_status[1] << 8) | + mxc_spdif_control.ch_status[0]); + spdif_set_channel_status(ch_status, SPDIF_REG_STCSCH); + ch_status = mxc_spdif_control.ch_status[3]; + spdif_set_channel_status(ch_status, SPDIF_REG_STCSCL); + return 0; +} + +static int snd_mxc_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +/*! + * Get channel status from SPDIF_RX_CCHAN register + */ +static int snd_mxc_spdif_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int cstatus; + + if (!(__raw_readl(spdif_base_addr + SPDIF_REG_SIS) & INT_CNEW)) + return -EAGAIN; + + cstatus = __raw_readl(spdif_base_addr + SPDIF_REG_SRCSLH); + ucontrol->value.iec958.status[0] = (cstatus >> 16) & 0xFF; + ucontrol->value.iec958.status[1] = (cstatus >> 8) & 0xFF; + ucontrol->value.iec958.status[2] = cstatus & 0xFF; + cstatus = __raw_readl(spdif_base_addr + SPDIF_REG_SRCSLL); + ucontrol->value.iec958.status[3] = (cstatus >> 16) & 0xFF; + ucontrol->value.iec958.status[4] = (cstatus >> 8) & 0xFF; + ucontrol->value.iec958.status[5] = cstatus & 0xFF; + + /* clear intr */ + __raw_writel(INT_CNEW, spdif_base_addr + SPDIF_REG_SIC); + + return 0; +} + +/*! + * Get User bits (subcode) from chip value which readed out + * in UChannel register. + */ +static int snd_mxc_spdif_subcode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&mxc_spdif_control.ctl_lock, flags); + if (mxc_spdif_control.ready_buf) { + memcpy(&ucontrol->value.iec958.subcode[0], + &mxc_spdif_control. + subcode[(mxc_spdif_control.ready_buf - + 1) * SPDIF_UBITS_SIZE], SPDIF_UBITS_SIZE); + } else { + ret = -EAGAIN; + } + spin_unlock_irqrestore(&mxc_spdif_control.ctl_lock, flags); + + return ret; +} + +/*! + * Q-subcode infomation. + * the byte size is SPDIF_UBITS_SIZE/8 + */ +static int snd_mxc_spdif_qinfo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = SPDIF_QSUB_SIZE; + return 0; +} + +/*! + * Get Q subcode from chip value which readed out + * in QChannel register. + */ +static int snd_mxc_spdif_qget(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&mxc_spdif_control.ctl_lock, flags); + if (mxc_spdif_control.ready_buf) { + memcpy(&ucontrol->value.bytes.data[0], + &mxc_spdif_control. + qsub[(mxc_spdif_control.ready_buf - + 1) * SPDIF_QSUB_SIZE], SPDIF_QSUB_SIZE); + } else { + ret = -EAGAIN; + } + spin_unlock_irqrestore(&mxc_spdif_control.ctl_lock, flags); + + return ret; +} + +/*! + * Valid bit infomation. + */ +static int snd_mxc_spdif_vbit_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +/*! + * Get valid good bit from interrupt status register. + */ +static int snd_mxc_spdif_vbit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int int_val; + + int_val = __raw_readl(spdif_base_addr + SPDIF_REG_SIS); + ucontrol->value.integer.value[0] = (int_val & INT_VAL_NOGOOD) != 0; + __raw_writel(INT_VAL_NOGOOD, spdif_base_addr + SPDIF_REG_SIC); + + return 0; +} + +/*! + * DPLL lock infomation. + */ +static int snd_mxc_spdif_rxrate_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 = 16000; + uinfo->value.integer.max = 96000; + return 0; +} + +/*! + * Get DPLL lock or not info from stable interrupt status register. + * User application must use this control to get locked, + * then can do next PCM operation + */ +static int snd_mxc_spdif_rxrate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct mxc_spdif_device *chip = snd_kcontrol_chip(kcontrol); + struct mxc_spdif_platform_data *spdif_data; + + spdif_data = chip->card->dev->platform_data; + + if (atomic_read(&chip->dpll_locked)) { + ucontrol->value.integer.value[0] = + spdif_get_rxclk_rate(spdif_data->spdif_clk, + SPDIF_DEFAULT_GAINSEL); + } else { + ucontrol->value.integer.value[0] = 0; + } + return 0; +} + +/*! + * User bit sync mode info + */ +static int snd_mxc_spdif_usync_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +/*! + * User bit sync mode: + * 1 CD User channel subcode + * 0 Non-CD data + */ +static int snd_mxc_spdif_usync_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int int_val; + + int_val = __raw_readl(spdif_base_addr + SPDIF_REG_SRCD); + ucontrol->value.integer.value[0] = (int_val & SRCD_CD_USER) != 0; + return 0; +} + +/*! + * User bit sync mode: + * 1 CD User channel subcode + * 0 Non-CD data + */ +static int snd_mxc_spdif_usync_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int int_val; + + int_val = ucontrol->value.integer.value[0] << SRCD_CD_USER_OFFSET; + __raw_writel(int_val, spdif_base_addr + SPDIF_REG_SRCD); + return 0; +} + +/*! + * MXC SPDIF IEC958 controller defines + */ +static struct snd_kcontrol_new snd_mxc_spdif_ctrls[] = { + /* status cchanel controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = mxc_pb_spdif_info, + .get = mxc_pb_spdif_get, + .put = mxc_pb_spdif_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_mxc_spdif_info, + .get = snd_mxc_spdif_capture_get, + }, + /* user bits controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Subcode Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_mxc_spdif_info, + .get = snd_mxc_spdif_subcode_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Q-subcode Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_mxc_spdif_qinfo, + .get = snd_mxc_spdif_qget, + }, + /* valid bit error controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 V-Bit Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_mxc_spdif_vbit_info, + .get = snd_mxc_spdif_vbit_get, + }, + /* DPLL lock info get controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "RX Sample Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_mxc_spdif_rxrate_info, + .get = snd_mxc_spdif_rxrate_get, + }, + /* User bit sync mode set/get controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 USyncMode CDText", + .access = + SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_mxc_spdif_usync_info, + .get = snd_mxc_spdif_usync_get, + .put = snd_mxc_spdif_usync_put, + }, +}; + +/*! + * This function the soundcard structure. + * + * @param mxc_spdif pointer to the sound card structure. + * + * @return 0 on success, -1 otherwise. + */ +static int snd_card_mxc_spdif_pcm(struct mxc_spdif_device *mxc_spdif) +{ + struct snd_pcm *pcm; + int err; + err = snd_pcm_new(mxc_spdif->card, MXC_SPDIF_NAME, 0, + mxc_spdif->mxc_spdif_tx, + mxc_spdif->mxc_spdif_rx, &pcm); + if (err < 0) + return err; + + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), + SPDIF_MAX_BUF_SIZE * 2, + SPDIF_MAX_BUF_SIZE * 2); + if (mxc_spdif->mxc_spdif_tx) + snd_pcm_set_ops(pcm, + SNDRV_PCM_STREAM_PLAYBACK, + &snd_card_mxc_spdif_playback_ops); + if (mxc_spdif->mxc_spdif_rx) + snd_pcm_set_ops(pcm, + SNDRV_PCM_STREAM_CAPTURE, + &snd_card_mxc_spdif_capture_ops); + pcm->private_data = mxc_spdif; + pcm->info_flags = 0; + strncpy(pcm->name, MXC_SPDIF_NAME, sizeof(pcm->name)); + mxc_spdif->pcm = pcm; + mxc_init_spdif_device(mxc_spdif); + return 0; +} + +extern void gpio_spdif_active(void); + +/*! + * This function initializes the driver in terms of memory of the soundcard + * and some basic HW clock settings. + * + * @param pdev Pointer to the platform device + * @return 0 on success, -1 otherwise. + */ +static int mxc_alsa_spdif_probe(struct platform_device + *pdev) +{ + int err, idx; + static int dev; + struct snd_card *card; + struct mxc_spdif_device *chip; + struct resource *res; + struct snd_kcontrol *kctl; + struct mxc_spdif_platform_data *plat_data; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + /* register the soundcard */ + err = snd_card_create(index[dev], id[dev], THIS_MODULE, + sizeof(struct mxc_spdif_device), &card); + if (err < 0) + return err; + chip = card->private_data; + chip->card = card; + card->dev = &pdev->dev; + chip->reg_base = ioremap(res->start, res->end - res->start + 1); + spdif_base_addr = (unsigned long)chip->reg_base; + plat_data = (struct mxc_spdif_platform_data *)pdev->dev.platform_data; + chip->mxc_spdif_tx = plat_data->spdif_tx; + chip->mxc_spdif_rx = plat_data->spdif_rx; + chip->spdif_txclk_44100 = plat_data->spdif_clk_44100; + chip->spdif_txclk_48000 = plat_data->spdif_clk_48000; + atomic_set(&chip->dpll_locked, 0); + + err = snd_card_mxc_spdif_pcm(chip); + if (err < 0) + goto nodev; + + /*! + * Add controls to the card + */ + for (idx = 0; idx < ARRAY_SIZE(snd_mxc_spdif_ctrls); idx++) { + + kctl = snd_ctl_new1(&snd_mxc_spdif_ctrls[idx], chip); + if (kctl == NULL) { + err = -ENOMEM; + goto nodev; + } + /* check to add control to corresponding substream */ + if (strstr(kctl->id.name, "Playback")) + kctl->id.device = 0; + else + kctl->id.device = 1; + + err = snd_ctl_add(card, kctl); + if (err < 0) + goto nodev; + } + + clk_enable(plat_data->spdif_core_clk); + /*! + * SPDIF interrupt initialization + * software reset to SPDIF + */ + spdif_softreset(); + /* disable all the interrupts */ + spdif_intr_enable(0xffffff, 0); + /* spdif interrupt register and disable */ + if (request_irq(MXC_INT_SPDIF, spdif_isr, 0, "spdif", chip)) { + pr_err("MXC spdif: failed to request irq\n"); + err = -EBUSY; + goto nodev; + } + + if (chip->mxc_spdif_tx) + spin_lock_init(&chip->s[SNDRV_PCM_STREAM_PLAYBACK].dma_lock); + if (chip->mxc_spdif_rx) + spin_lock_init(&chip->s[SNDRV_PCM_STREAM_CAPTURE].dma_lock); + strcpy(card->driver, MXC_SPDIF_NAME); + strcpy(card->shortname, "MXC SPDIF TX/RX"); + sprintf(card->longname, "MXC Freescale with SPDIF"); + + err = snd_card_register(card); + if (err == 0) { + pr_info("MXC spdif support initialized\n"); + platform_set_drvdata(pdev, card); + gpio_spdif_active(); + return 0; + } + + nodev: + snd_card_free(card); + return err; +} + +extern void gpio_spdif_inactive(void); + +/*! + * This function releases the sound card and unmap the io address + * + * @param pdev Pointer to the platform device + * @return 0 on success, -1 otherwise. + */ + +static int mxc_alsa_spdif_remove(struct platform_device *pdev) +{ + struct mxc_spdif_device *chip; + struct snd_card *card; + struct mxc_spdif_platform_data *plat_data; + + card = platform_get_drvdata(pdev); + plat_data = pdev->dev.platform_data; + chip = card->private_data; + free_irq(MXC_INT_SPDIF, chip); + iounmap(chip->reg_base); + + snd_card_free(card); + platform_set_drvdata(pdev, NULL); + + clk_disable(plat_data->spdif_core_clk); + gpio_spdif_inactive(); + + return 0; +} + +#ifdef CONFIG_PM +/*! + * This function suspends all active streams. + * + * TBD + * + * @param card pointer to the sound card structure. + * @param state requested state + * + * @return 0 on success, -1 otherwise. + */ +static int mxc_alsa_spdif_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return 0; +} + +/*! + * This function resumes all suspended streams. + * + * TBD + * + * @param card pointer to the sound card structure. + * @param state requested state + * + * @return 0 on success, -1 otherwise. + */ +static int mxc_alsa_spdif_resume(struct platform_device *pdev) +{ + return 0; +} +#endif + +static struct platform_driver mxc_alsa_spdif_driver = { + .probe = mxc_alsa_spdif_probe, + .remove = mxc_alsa_spdif_remove, +#ifdef CONFIG_PM + .suspend = mxc_alsa_spdif_suspend, + .resume = mxc_alsa_spdif_resume, +#endif + .driver = { + .name = "mxc_alsa_spdif", + }, +}; + +/*! + * This function registers the sound driver structure. + * + */ +static int __init mxc_alsa_spdif_init(void) +{ + return platform_driver_register(&mxc_alsa_spdif_driver); +} + +/*! + * This function frees the sound driver structure. + * + */ +static void __exit mxc_alsa_spdif_exit(void) +{ + platform_driver_unregister(&mxc_alsa_spdif_driver); +} + +module_init(mxc_alsa_spdif_init); +module_exit(mxc_alsa_spdif_exit); +MODULE_AUTHOR("FREESCALE SEMICONDUCTOR"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MXC ALSA driver for SPDIF"); diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index d3e786a9a0a7..09c88271e14a 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -35,6 +35,8 @@ source "sound/soc/s3c24xx/Kconfig" source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/txx9/Kconfig" +source "sound/soc/imx/Kconfig" +source "sound/soc/stmp3xxx/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 6f1e28de23cf..ccb1fa6fe6b1 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -13,3 +13,5 @@ obj-$(CONFIG_SND_SOC) += s3c24xx/ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += txx9/ +obj-$(CONFIG_SND_SOC) += imx/ +obj-$(CONFIG_SND_SOC) += stmp3xxx/ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index bbc97fd76648..43769145740d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -74,6 +74,10 @@ config SND_SOC_AK4104 config SND_SOC_AK4535 tristate +config SND_SOC_AK5702 + tristate + depends on I2C + # Cirrus Logic CS4270 Codec config SND_SOC_CS4270 tristate @@ -176,3 +180,23 @@ config SND_SOC_WM9712 config SND_SOC_WM9713 tristate + +config SND_SOC_SGTL5000 + tristate + depends on I2C + +config SND_SOC_AK4647 + tristate + depends on I2C + +config SND_SOC_STMP378X_CODEC + tristate + depends on SND_SOC + +config SND_SOC_STMP3XXX_SPDIF + tristate + depends on SND_SOC + +config SND_SOC_BLUETOOTH + tristate + depends on SND_SOC diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 8b7530546f4d..6b75d7285191 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -3,6 +3,7 @@ snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o +snd-soc-ak5702-objs := ak5702.o snd-soc-cs4270-objs := cs4270.o snd-soc-l3-objs := l3.o snd-soc-pcm3008-objs := pcm3008.o @@ -34,12 +35,18 @@ snd-soc-wm9081-objs := wm9081.o snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o +snd-soc-sgtl5000-objs := sgtl5000.o +snd-soc-ak4647-objs := ak4647.o +snd-soc-stmp378x-codec-objs := stmp378x_codec.o +snd-soc-stmp3xxx-spdif-objs := stmp3xxx_spdif.o +snd-soc-bluetooth-objs := bluetooth.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o +obj-$(CONFIG_SND_SOC_AK5702) += snd-soc-ak5702.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o @@ -71,3 +78,8 @@ obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o 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_SGTL5000) += snd-soc-sgtl5000.o +obj-$(CONFIG_SND_SOC_AK4647) += snd-soc-ak4647.o +obj-$(CONFIG_SND_SOC_STMP378X_CODEC) += snd-soc-stmp378x-codec.o +obj-$(CONFIG_SND_SOC_STMP3XXX_SPDIF) += snd-soc-stmp3xxx-spdif.o +obj-$(CONFIG_SND_SOC_BLUETOOTH) += snd-soc-bluetooth.o diff --git a/sound/soc/codecs/ak4647.c b/sound/soc/codecs/ak4647.c new file mode 100644 index 000000000000..46e2e8cbcacc --- /dev/null +++ b/sound/soc/codecs/ak4647.c @@ -0,0 +1,799 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ak4647.c + * @brief Driver for AK4647 + * + * @ingroup Sound + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "ak4647.h" + +#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos)) +#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos)) + +static struct i2c_client *ak4647_i2c_client; + +int ak4647_read_reg(unsigned int reg, u8 *value) +{ + s32 retval; + retval = i2c_smbus_read_byte_data(ak4647_i2c_client, reg); + if (-1 == retval) { + pr_err("%s:read reg errorr:reg=%x,val=%x\n", + __func__, reg, *value); + return -1; + } else { + *value = (u8) retval; + return 0; + } +} + +int ak4647_write_reg(unsigned int reg, u8 value) +{ + if (i2c_smbus_write_byte_data(ak4647_i2c_client, reg, value) < 0) { + pr_err("%s:write reg errorr:reg=%x,val=%x\n", + __func__, reg, value); + return -1; + } + return 0; +} + +static unsigned int ak4647_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 value; + ak4647_read_reg(reg, &value); + return value; +} + +static int ak4647_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + return ak4647_write_reg(reg, value); +} + +#define DEBUG_AK4647 0 + +#if DEBUG_AK4647 + +static char *ak4647_reg_names[] = { + "AK4647_PM1", + "AK4647_PM2", + "AK4647_SIG1", + "AK4647_SIG2", + "AK4647_MODE1", + "AK4647_MODE2", + "AK4647_TIMER", + "AK4647_ALC1", + "AK4647_ALC2", + "AK4647_LEFT_INPUT_VOLUME", + "AK4647_LEFT_DGT_VOLUME", + "AK4647_ALC3", + "AK4647_RIGHT_INPUT_VOLUME", + "AK4647_RIGHT_DGT_VOLUME", + "AK4647_MODE3", + "AK4647_MODE4", + "AK4647_PM3", + "AK4647_DGT_FIL_SEL", + "AK4647_FIL3_COEF0", + "AK4647_FIL3_COEF1", + "AK4647_FIL3_COEF2", + "AK4647_FIL3_COEF3", + "AK4647_EQ_COEF0", + "AK4647_EQ_COEF1", + "AK4647_EQ_COEF2", + "AK4647_EQ_COEF3", + "AK4647_EQ_COEF4", + "AK4647_EQ_COEF5", + "AK4647_FIL1_COEF0", + "AK4647_FIL1_COEF1", + "AK4647_FIL1_COEF2", + "AK4647_FIL1_COEF3", +}; + +static void dump_all_regs(void) +{ + int i; + u8 value; + + for (i = AK4647_REG_START; i < AK4647_REG_NUMBER; i++) { + ak4647_read_reg(i, &value); + pr_info("%s = 0x%x\n", ak4647_reg_names[i], value); + } +} +#endif + +static int loopback_xhandle_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +static int loopback_xhandle_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +static const char *ak4647_hp_out[] = { "Stereo", "Mono" }; + +static const char *ak4647_left_in[] = { "LIN1", "LIN2" }; + +static const char *ak4647_right_in[] = { "RIN1", "RIN2" }; + +static const char *ak4647_deemp[] = { "44.1kHz", "Off", "48kHz", "32kHz" }; + +static const struct soc_enum ak4647_enum[] = { + SOC_ENUM_SINGLE(AK4647_MODE4, 2, 2, ak4647_hp_out), + SOC_ENUM_SINGLE(AK4647_MODE3, 0, 4, ak4647_deemp), + SOC_ENUM_SINGLE(AK4647_PM3, 1, 2, ak4647_left_in), + SOC_ENUM_SINGLE(AK4647_PM3, 2, 2, ak4647_right_in), +}; + +#undef snd_soc_info_bool_ext +#define snd_soc_info_bool_ext snd_ctl_boolean_mono_info +static const struct snd_kcontrol_new ak4647_snd_controls[] = { + SOC_ENUM("Headphone Output", ak4647_enum[0]), + SOC_ENUM("Playback Deemphasis", ak4647_enum[1]), + SOC_ENUM("Left Capture Select", ak4647_enum[2]), + SOC_ENUM("Right Capture Select", ak4647_enum[3]), + SOC_SINGLE("Bass Volume", AK4647_MODE3, 2, 3, 0), + SOC_SINGLE("Mic Boost (+20dB) Switch", AK4647_SIG1, 0, 1, 0), + SOC_SINGLE("Mic Bias", AK4647_SIG1, 2, 1, 0), + SOC_SINGLE("ALC Switch", AK4647_ALC1, 5, 1, 0), + SOC_SINGLE("ALC Recovery Time", AK4647_TIMER, 2, 3, 0), + SOC_SINGLE("ALC ZC Time", AK4647_TIMER, 4, 3, 0), + SOC_SINGLE("ALC Volume", AK4647_ALC2, 0, 127, 0), + SOC_SINGLE("Left Capture Volume", AK4647_LEFT_INPUT_VOLUME, 0, 242, 0), + SOC_SINGLE("Right Capture Volume", + AK4647_RIGHT_INPUT_VOLUME, 0, 242, 0), + SOC_SINGLE("Left Playback Volume", AK4647_LEFT_DGT_VOLUME, 0, 255, 1), + SOC_SINGLE("Right Playback Volume", AK4647_RIGHT_DGT_VOLUME, 0, 255, 1), + SOC_SINGLE_BOOL_EXT("Loopback Line-in", 0, + loopback_xhandle_get, loopback_xhandle_put), +}; + +/* Stereo Mixer for HP*/ +static const struct snd_kcontrol_new ak4647_hp_stereo_mixer_controls[] = { + SOC_DAPM_SINGLE("Playback Switch", AK4647_MODE4, 0, 1, 0), + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4647_MODE4, 1, 1, 0), +}; + +/* Stereo Mixer for Line out*/ +static const struct snd_kcontrol_new ak4647_line_stereo_mixer_controls[] = { + SOC_DAPM_SINGLE("Playback Switch", AK4647_SIG1, 4, 1, 0), + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4647_SIG2, 2, 1, 0), +}; + +/* ak4647 dapm widgets */ +static const struct snd_soc_dapm_widget ak4647_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Headphone Mixer", SND_SOC_NOPM, 0, 0, + &ak4647_hp_stereo_mixer_controls[0], + ARRAY_SIZE(ak4647_hp_stereo_mixer_controls)), + SND_SOC_DAPM_MIXER("Lineout Mixer", SND_SOC_NOPM, 0, 0, + &ak4647_line_stereo_mixer_controls[0], + ARRAY_SIZE(ak4647_line_stereo_mixer_controls)), + SND_SOC_DAPM_DAC("DAC", "Playback", AK4647_PM1, 2, 0), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("LOUT"), + + SND_SOC_DAPM_ADC("Left ADC", "Capture", AK4647_PM1, 0, 0), + SND_SOC_DAPM_ADC("Right ADC", "Capture", AK4647_PM3, 0, 0), + SND_SOC_DAPM_PGA("HP R Amp", AK4647_PM2, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP L Amp", AK4647_PM2, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic", AK4647_PM1, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Line Out Amp", AK4647_PM1, 3, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("Mic Bias", AK4647_SIG1, 2, 0), + SND_SOC_DAPM_INPUT("Left Input"), + SND_SOC_DAPM_INPUT("Right Input"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /*headphone stereo mixer */ + {"Headphone Mixer", "Playback Switch", "DAC"}, + {"Headphone Mixer", "Mic Sidetone Switch", "Mic"}, + + /*lineout stereo mixer */ + {"Lineout Mixer", "Playback Switch", "DAC"}, + {"Lineout Mixer", "Mic Sidetone Switch", "Mic"}, + + /* headphone amp */ + {"HP R Amp", NULL, "Headphone Mixer"}, + {"HP L Amp", NULL, "Headphone Mixer"}, + + /* headphone */ + {"HPR", NULL, "HP R Amp"}, + {"HPL", NULL, "HP L Amp"}, + + /* line out */ + {"Line Out Amp", NULL, "Lineout Mixer"}, + {"LOUT", NULL, "Line Out Amp"}, + + /* ADC */ + {"Left ADC", NULL, "Left Input"}, + {"Right ADC", NULL, "Right Input"}, + +}; + +static int ak4647_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, ak4647_dapm_widgets, + ARRAY_SIZE(ak4647_dapm_widgets)); + + /* set up audio path audio_mapnects */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +#define AK4647_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_48000) + +#define AK4647_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +static int ak4647_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + u8 value; + u8 fs = 0; + + /* FS3 is on D5 */ + switch (freq) { + case 8000: + fs = 0x0; + break; + case 11025: + fs = 0x5; + break; + case 16000: + fs = 0x2; + break; + case 22050: + fs = 0x7; + break; + case 32000: + fs = 0x22; + break; + case 44100: + fs = 0x27; + break; + case 48000: + fs = 0x23; + break; + default: + pr_err("unsupported sample rate"); + return -1; + } + + ak4647_read_reg(AK4647_MODE2, &value); + value &= 0xC0; + value |= fs; + ak4647_write_reg(AK4647_MODE2, value); + return 0; +} + +static int ak4647_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + u8 reg_mode1, reg_pm2; + + /* get current values */ + ak4647_read_reg(AK4647_MODE1, ®_mode1); + ak4647_read_reg(AK4647_PM2, ®_pm2); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + SET_BIT_IN_BYTE(reg_pm2, 3); + break; + case SND_SOC_DAIFMT_CBS_CFS: + CLEAR_BIT_IN_BYTE(reg_pm2, 3); + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + SET_BIT_IN_BYTE(reg_mode1, 0); + SET_BIT_IN_BYTE(reg_mode1, 1); + break; + case SND_SOC_DAIFMT_RIGHT_J: + SET_BIT_IN_BYTE(reg_mode1, 0); + CLEAR_BIT_IN_BYTE(reg_mode1, 1); + break; + case SND_SOC_DAIFMT_LEFT_J: + CLEAR_BIT_IN_BYTE(reg_mode1, 0); + SET_BIT_IN_BYTE(reg_mode1, 1); + break; + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + default: + pr_err("dai format %d not supported", fmt); + return -EINVAL; + } + + ak4647_write_reg(AK4647_MODE1, reg_mode1); + ak4647_write_reg(AK4647_PM2, reg_pm2); + return 0; +} + +static int ak4647_set_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) +{ + int retval = 0; + u8 value; + if (AK4647_BCLK_CLKDIV == div_id) { + ak4647_read_reg(AK4647_MODE1, &value); + switch (div) { + case AK4647_BCLK_DIV_32: + CLEAR_BIT_IN_BYTE(value, 3); + ak4647_write_reg(AK4647_MODE1, value); + break; + case AK4647_BCLK_DIV_64: + SET_BIT_IN_BYTE(value, 3); + ak4647_write_reg(AK4647_MODE1, value); + break; + default: + retval = -1; + pr_err("wrong div value for divid %d", div_id); + break; + } + } else if (AK4647_MCLK_CLKDIV == div_id) { + ak4647_read_reg(AK4647_MODE2, &value); + switch (div) { + case AK4647_MCLK_DIV_32: + SET_BIT_IN_BYTE(value, 7); + SET_BIT_IN_BYTE(value, 6); + ak4647_write_reg(AK4647_MODE2, value); + break; + case AK4647_MCLK_DIV_64: + SET_BIT_IN_BYTE(value, 7); + CLEAR_BIT_IN_BYTE(value, 6); + ak4647_write_reg(AK4647_MODE2, value); + break; + case AK4647_MCLK_DIV_128: + CLEAR_BIT_IN_BYTE(value, 7); + SET_BIT_IN_BYTE(value, 6); + ak4647_write_reg(AK4647_MODE2, value); + break; + case AK4647_MCLK_DIV_256: + CLEAR_BIT_IN_BYTE(value, 7); + CLEAR_BIT_IN_BYTE(value, 6); + ak4647_write_reg(AK4647_MODE2, value); + break; + default: + retval = -1; + pr_err("wrong div value for div id %d", div_id); + break; + } + } else { + retval = -1; + pr_err("wrong div id"); + } + + return retval; +} + +static int ak4647_digital_mute(struct snd_soc_dai *dai, int mute) +{ + u8 value; + + ak4647_read_reg(AK4647_MODE3, &value); + + if (mute) + SET_BIT_IN_BYTE(value, 5); + else + CLEAR_BIT_IN_BYTE(value, 5); + ak4647_write_reg(AK4647_MODE3, value); + return 0; +} + +struct ak4647_state_data { + u8 left_capture_vol; + u8 right_capture_vol; +}; +static struct ak4647_state_data ak4647_state; + +static int ak4647_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + u8 value; + /* for playback, save down capture volume */ + if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) { + ak4647_read_reg(AK4647_LEFT_INPUT_VOLUME, &value); + ak4647_state.left_capture_vol = value; + ak4647_read_reg(AK4647_RIGHT_INPUT_VOLUME, &value); + ak4647_state.right_capture_vol = value; + + /* when PMADL=PMADR=0 set IVL &IVR to be 0x91 (0db) */ + ak4647_write_reg(AK4647_LEFT_INPUT_VOLUME, 0x91); + ak4647_write_reg(AK4647_RIGHT_INPUT_VOLUME, 0x91); + } + + /* output digital volume independent */ + ak4647_read_reg(AK4647_MODE3, &value); + CLEAR_BIT_IN_BYTE(value, 4); + ak4647_write_reg(AK4647_MODE3, value); + return 0; +} + +static void ak4647_pcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + u8 value; + ak4647_read_reg(AK4647_PM2, &value); + /* mute */ + CLEAR_BIT_IN_BYTE(value, 6); + ak4647_write_reg(AK4647_PM2, value); + + /* after playback, restore capture volume */ + if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) { + ak4647_write_reg(AK4647_LEFT_INPUT_VOLUME, + ak4647_state.left_capture_vol); + ak4647_write_reg(AK4647_RIGHT_INPUT_VOLUME, + ak4647_state.right_capture_vol); + } +} + +static int ak4647_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + u8 value; + + /* VCOM power on */ + ak4647_write_reg(AK4647_PM1, 0x44); + msleep(30); + + ak4647_read_reg(AK4647_PM2, &value); + /* PLL enabled */ + SET_BIT_IN_BYTE(value, 0); + + ak4647_write_reg(AK4647_PM2, value); + /* wait for PLL locked */ + msleep(40); + + /* don't mute */ + SET_BIT_IN_BYTE(value, 6); + ak4647_write_reg(AK4647_PM2, value); + return 0; +} + +struct snd_soc_dai_ops ak4647_ops = { + .prepare = ak4647_pcm_prepare, + .startup = ak4647_pcm_startup, + .shutdown = ak4647_pcm_shutdown, + .digital_mute = ak4647_digital_mute, + .set_fmt = ak4647_set_dai_fmt, + .set_sysclk = ak4647_set_dai_sysclk, + .set_clkdiv = ak4647_set_clkdiv, +}; + +struct snd_soc_dai ak4647_hifi_dai = { + .name = "ak4647", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AK4647_RATES, + .formats = AK4647_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AK4647_RATES, + .formats = AK4647_FORMATS, + }, + .ops = &ak4647_ops, +}; +EXPORT_SYMBOL_GPL(ak4647_hifi_dai); + +static struct snd_soc_codec *ak4647_codec; + +static int ak4647_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = ak4647_codec; + int ret = 0; + + socdev->card->codec = ak4647_codec; + + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&ak4647_i2c_client->dev, "failed to create pcms\n"); + return ret; + } + + /* setup init value for audio path controls here */ + /* enable DAC to headphone */ + ak4647_write_reg(AK4647_MODE4, 0x9); + /* capture source to be LIN2 and RIN2 */ + ak4647_write_reg(AK4647_PM3, 0x6); + /* MPWR pin up */ + ak4647_write_reg(AK4647_SIG1, 0x5); + + /* MCKI = 12M, default audio interface format as "left-justified" */ + ak4647_write_reg(AK4647_MODE1, 0x62); + + /* ALC disabled */ + ak4647_write_reg(AK4647_ALC1, 0x0); + + ak4647_state.left_capture_vol = 0x91; + ak4647_state.right_capture_vol = 0x91; + + snd_soc_add_controls(codec, ak4647_snd_controls, + ARRAY_SIZE(ak4647_snd_controls)); + ak4647_add_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + pr_err("ak4647: failed to register card\n"); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + return ret; + } + + return 0; +} + +static __devexit int ak4647_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + + snd_soc_unregister_dai(&ak4647_hifi_dai); + snd_soc_unregister_codec(codec); + kfree(codec); + ak4647_codec = NULL; + return 0; +} + +static int ak4647_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct snd_soc_codec *codec; + int ret; + u8 val; + + if (ak4647_codec) { + dev_err(&client->dev, + "Multiple AK4647 devices not supported\n"); + return -ENOMEM; + } + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + i2c_set_clientdata(client, codec); + ak4647_i2c_client = client; + codec->control_data = client; + ret = ak4647_read_reg(AK4647_REG_START, &val); + if (ret < 0) { + pr_err("Device with ID register %x is not a AK4647\n", val); + return -ENODEV; + } + + codec->dev = &client->dev; + codec->name = "ak4647", codec->owner = THIS_MODULE; + codec->owner = THIS_MODULE; + codec->read = ak4647_codec_read; + codec->write = ak4647_codec_write; + codec->dai = &ak4647_hifi_dai; + codec->num_dai = 1; + + ak4647_codec = codec; + ak4647_hifi_dai.dev = &client->dev; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&ak4647_hifi_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); + return ret; + } + + return ret; +} + +static const struct i2c_device_id ak4647_id[] = { + {"ak4647-i2c", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ak4647_id); + +static struct i2c_driver ak4647_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ak4647-i2c", + }, + .probe = ak4647_i2c_probe, + .remove = __devexit_p(ak4647_i2c_remove), + .id_table = ak4647_id, +}; + +int pmic_audio_fm_output_enable(bool enable) +{ + u8 val; + if (enable) { + ak4647_set_dai_fmt(NULL, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + ak4647_set_dai_sysclk(NULL, 0, 44100, 0); + ak4647_set_clkdiv(NULL, 0, 0); + /* VCOM power on */ + ak4647_write_reg(AK4647_PM1, 0x44); + msleep(30); + + ak4647_read_reg(AK4647_PM2, &val); + /* PLL enabled */ + SET_BIT_IN_BYTE(val, 0); + + ak4647_write_reg(AK4647_PM2, val); + /* wait for PLL locked */ + msleep(40); + + /* don't mute */ + SET_BIT_IN_BYTE(val, 6); + ak4647_write_reg(AK4647_PM2, val); + + /* loopback STDO to DAC */ + ak4647_read_reg(AK4647_MODE3, &val); + SET_BIT_IN_BYTE(val, 6); + ak4647_write_reg(AK4647_MODE3, val); + + /* switch to R/L 1 */ + ak4647_read_reg(AK4647_PM3, &val); + CLEAR_BIT_IN_BYTE(val, 1); + CLEAR_BIT_IN_BYTE(val, 2); + ak4647_write_reg(AK4647_PM3, val); + + /* power up ADC */ + ak4647_read_reg(AK4647_PM1, &val); + SET_BIT_IN_BYTE(val, 0); + ak4647_write_reg(AK4647_PM1, val); + ak4647_read_reg(AK4647_PM3, &val); + SET_BIT_IN_BYTE(val, 0); + ak4647_write_reg(AK4647_PM3, val); + + /* power up DAC */ + ak4647_read_reg(AK4647_PM1, &val); + SET_BIT_IN_BYTE(val, 2); + ak4647_write_reg(AK4647_PM1, val); + + msleep(30); + + /* headphone output switch on */ + ak4647_read_reg(AK4647_MODE4, &val); + SET_BIT_IN_BYTE(val, 0); + ak4647_write_reg(AK4647_MODE4, val); + + /* power on headphone amp */ + ak4647_read_reg(AK4647_PM2, &val); + SET_BIT_IN_BYTE(val, 4); + SET_BIT_IN_BYTE(val, 5); + ak4647_write_reg(AK4647_PM2, val); + + ak4647_digital_mute(NULL, 0); + } else { + ak4647_digital_mute(NULL, 1); + + /* disbale loopback */ + ak4647_read_reg(AK4647_MODE3, &val); + CLEAR_BIT_IN_BYTE(val, 6); + SET_BIT_IN_BYTE(val, 5); + ak4647_write_reg(AK4647_MODE3, val); + + /* switch to R/L 2 */ + ak4647_read_reg(AK4647_PM3, &val); + SET_BIT_IN_BYTE(val, 1); + SET_BIT_IN_BYTE(val, 2); + ak4647_write_reg(AK4647_PM3, val); + } + return 0; +} + +static int loopback_xhandle_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = kcontrol->private_value; + return 0; +} + +static int loopback_xhandle_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int changed; + long flag = ucontrol->value.integer.value[0]; + changed = + (ucontrol->value.integer.value[0] == + kcontrol->private_value) ? 0 : 1; + kcontrol->private_value = flag; + if (flag) + pmic_audio_fm_output_enable(true); + else + pmic_audio_fm_output_enable(false); + + return changed; +} + +/* power down chip */ +static int ak4647_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + i2c_del_driver(&ak4647_i2c_driver); + kfree(codec); + + return 0; +} + +static int ak4647_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int ak4647_resume(struct platform_device *pdev) +{ + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ak4647 = { + .probe = ak4647_probe, + .remove = ak4647_remove, + .suspend = ak4647_suspend, + .resume = ak4647_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ak4647); + +static int __init ak4647_modinit(void) +{ + return i2c_add_driver(&ak4647_i2c_driver); +} +module_init(ak4647_modinit); + +static void __exit ak4647_exit(void) +{ + i2c_del_driver(&ak4647_i2c_driver); +} +module_exit(ak4647_exit); + +MODULE_DESCRIPTION("ASoC ak4647 driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4647.h b/sound/soc/codecs/ak4647.h new file mode 100644 index 000000000000..d88832b1dd09 --- /dev/null +++ b/sound/soc/codecs/ak4647.h @@ -0,0 +1,92 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ak4647.h + * @brief Driver for AK4647 + * + * @ingroup Sound + */ +#ifndef _AK4647_H_ +#define _AK4647_H_ + +#ifdef __KERNEL__ + +/*! + * AK4647 registers + */ + +#define AK4647_PM1 0x00 +#define AK4647_PM2 0x01 +#define AK4647_SIG1 0x02 +#define AK4647_SIG2 0x03 +#define AK4647_MODE1 0x04 +#define AK4647_MODE2 0x05 +#define AK4647_TIMER 0x06 +#define AK4647_ALC1 0x07 +#define AK4647_ALC2 0x08 + +#define AK4647_LEFT_INPUT_VOLUME 0x09 +#define AK4647_LEFT_DGT_VOLUME 0x0A +#define AK4647_ALC3 0x0B +#define AK4647_RIGHT_INPUT_VOLUME 0x0C +#define AK4647_RIGHT_DGT_VOLUME 0x0D +#define AK4647_MODE3 0x0E +#define AK4647_MODE4 0x0F +#define AK4647_PM3 0x10 +#define AK4647_DGT_FIL_SEL 0x11 + +/* filter 3 coeffecient*/ + +#define AK4647_FIL3_COEF0 0x12 +#define AK4647_FIL3_COEF1 0x13 +#define AK4647_FIL3_COEF2 0x14 +#define AK4647_FIL3_COEF3 0x15 + +/* eq coeffecient*/ + +#define AK4647_EQ_COEF0 0x16 +#define AK4647_EQ_COEF1 0x17 +#define AK4647_EQ_COEF2 0x18 +#define AK4647_EQ_COEF3 0x19 +#define AK4647_EQ_COEF4 0x1A +#define AK4647_EQ_COEF5 0x1B + +/* filter 3 coeffecient*/ + +#define AK4647_FIL1_COEF0 0x1C +#define AK4647_FIL1_COEF1 0x1D +#define AK4647_FIL1_COEF2 0x1E +#define AK4647_FIL1_COEF3 0x1F + +#define AK4647_REG_START 0x00 +#define AK4647_REG_END 0x1F +#define AK4647_REG_NUMBER 0x20 + +/* clock divider id's */ +#define AK4647_BCLK_CLKDIV 0 +#define AK4647_MCLK_CLKDIV 1 + +/* bit clock div values (AK4647_BCLK_CLKDIV)*/ +#define AK4647_BCLK_DIV_32 0 +#define AK4647_BCLK_DIV_64 1 + +/* m clock div values (AK4647_MCLK_CLKDIV)*/ +#define AK4647_MCLK_DIV_32 0 +#define AK4647_MCLK_DIV_64 1 +#define AK4647_MCLK_DIV_128 2 +#define AK4647_MCLK_DIV_256 3 + +#endif /* __KERNEL__ */ + +#endif /* _AK4647_H_ */ diff --git a/sound/soc/codecs/ak5702.c b/sound/soc/codecs/ak5702.c new file mode 100644 index 000000000000..a34f48e52b3f --- /dev/null +++ b/sound/soc/codecs/ak5702.c @@ -0,0 +1,607 @@ +/* + * ak5702.c -- AK5702 Soc Audio driver + * + * Copyright 2009 Freescale Semiconductor, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "ak5702.h" + +#define AK5702_VERSION "0.1" + +/* codec private data */ +struct ak5702_priv { + unsigned int sysclk; +}; + +/* + * ak5702 register cache + */ +static const u16 ak5702_reg[AK5702_CACHEREGNUM] = { + 0x0000, 0x0024, 0x0000, 0x0001, 0x0023, 0x001f, + 0x0000, 0x0001, 0x0091, 0x0000, 0x00e1, 0x0000, + 0x00a0, 0x0000, 0x0000, 0x0000, 0x0001, 0x0020, + 0x0000, 0x0000, 0x0001, 0x0091, 0x0000, 0x00e1, + 0x0000, +}; + +/* + * read ak5702 register cache + */ +static inline unsigned int ak5702_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg >= AK5702_CACHEREGNUM) + return -1; + return cache[reg]; +} + +static inline unsigned int ak5702_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 data; + data = reg; + + if (codec->hw_write(codec->control_data, &data, 1) != 1) + return -EIO; + if (codec->hw_read(codec->control_data, &data, 1) != 1) + return -EIO; + + return data; +}; + +/* + * write ak5702 register cache + */ +static inline void ak5702_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + + if (reg >= AK5702_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the AK5702 register space + */ +static int ak5702_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + data[0] = reg & 0xff; + data[1] = value & 0xff; + + ak5702_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static const char *ak5702_mic_gain[] = { "0dB", "+15dB", "+30dB", "+36dB" }; +static const char *ak5702_adca_left_type[] = + { "Single-ended", "Full-differential" }; +static const char *ak5702_adca_right_type[] = + { "Single-ended", "Full-differential" }; +static const char *ak5702_adcb_left_type[] = + { "Single-ended", "Full-differential" }; +static const char *ak5702_adcb_right_type[] = + { "Single-ended", "Full-differential" }; +static const char *ak5702_adca_left_input[] = { "LIN1", "LIN2" }; +static const char *ak5702_adca_right_input[] = { "RIN1", "RIN2" }; +static const char *ak5702_adcb_left_input[] = { "LIN3", "LIN4" }; +static const char *ak5702_adcb_right_input[] = { "RIN3", "RIN4" }; + +static const struct soc_enum ak5702_enum[] = { + SOC_ENUM_SINGLE(AK5702_MICG1, 0, 4, ak5702_mic_gain), + SOC_ENUM_SINGLE(AK5702_MICG2, 0, 4, ak5702_mic_gain), + + SOC_ENUM_SINGLE(AK5702_SIG1, 0, 2, ak5702_adca_left_input), + SOC_ENUM_SINGLE(AK5702_SIG1, 1, 2, ak5702_adca_right_input), + SOC_ENUM_SINGLE(AK5702_SIG2, 0, 2, ak5702_adcb_left_input), + SOC_ENUM_SINGLE(AK5702_SIG1, 1, 2, ak5702_adcb_right_input), + + SOC_ENUM_SINGLE(AK5702_SIG1, 2, 2, ak5702_adca_left_type), + SOC_ENUM_SINGLE(AK5702_SIG1, 3, 2, ak5702_adca_right_type), + SOC_ENUM_SINGLE(AK5702_SIG2, 2, 2, ak5702_adcb_left_type), + SOC_ENUM_SINGLE(AK5702_SIG2, 3, 2, ak5702_adcb_right_type), +}; + +static const struct snd_kcontrol_new ak5702_snd_controls[] = { + SOC_SINGLE("ADCA Left Vol", AK5702_LVOL1, 0, 242, 0), + SOC_SINGLE("ADCA Right Vol", AK5702_RVOL1, 0, 242, 0), + SOC_SINGLE("ADCB Left Vol", AK5702_LVOL2, 0, 242, 0), + SOC_SINGLE("ADCB Right Vol", AK5702_RVOL2, 0, 242, 0), + + SOC_ENUM("MIC-AmpA Gain", ak5702_enum[0]), + SOC_ENUM("MIC-AmpB Gain", ak5702_enum[1]), + + SOC_ENUM("ADCA Left Source", ak5702_enum[2]), + SOC_ENUM("ADCA Right Source", ak5702_enum[3]), + SOC_ENUM("ADCB Left Source", ak5702_enum[4]), + SOC_ENUM("ADCB Right Source", ak5702_enum[5]), + + SOC_ENUM("ADCA Left Type", ak5702_enum[6]), + SOC_ENUM("ADCA Right Type", ak5702_enum[7]), + SOC_ENUM("ADCB Left Type", ak5702_enum[8]), + SOC_ENUM("ADCB Right Type", ak5702_enum[9]), +}; + +/* ak5702 dapm widgets */ +static const struct snd_soc_dapm_widget ak5702_dapm_widgets[] = { + SND_SOC_DAPM_ADC("ADCA Left", "Capture", AK5702_PM1, 0, 0), + SND_SOC_DAPM_ADC("ADCA Right", "Capture", AK5702_PM1, 1, 0), + SND_SOC_DAPM_ADC("ADCB Left", "Capture", AK5702_PM2, 0, 0), + SND_SOC_DAPM_ADC("ADCB Right", "Capture", AK5702_PM2, 1, 0), + + SND_SOC_DAPM_INPUT("ADCA Left Input"), + SND_SOC_DAPM_INPUT("ADCA Right Input"), + SND_SOC_DAPM_INPUT("ADCB Left Input"), + SND_SOC_DAPM_INPUT("ADCB Right Input"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"ADCA Left", NULL, "ADCA Left Input"}, + {"ADCA Right", NULL, "ADCA Right Input"}, + {"ADCB Left", NULL, "ADCB Left Input"}, + {"ADCB Right", NULL, "ADCB Right Input"}, +}; + +static int ak5702_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, ak5702_dapm_widgets, + ARRAY_SIZE(ak5702_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int ak5702_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 fs = 0; + u8 value; + + switch (freq) { + case 8000: + fs = 0x0; + break; + case 11025: + fs = 0x05; + break; + case 12000: + fs = 0x01; + break; + case 16000: + fs = 0x02; + break; + case 22050: + fs = 0x07; + break; + case 24000: + fs = 0x03; + break; + case 32000: + fs = 0x0a; + break; + case 44100: + fs = 0x0f; + break; + case 48000: + fs = 0x0b; + break; + default: + return -EINVAL; + } + + value = ak5702_read_reg_cache(codec, AK5702_FS1); + value &= (~AK5702_FS1_FS_MASK); + value |= fs; + ak5702_write(codec, AK5702_FS1, value); + return 0; +} + +static int ak5702_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 fmt1 = 0; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + fmt1 = AK5702_FMT1_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + fmt1 = AK5702_FMT1_MSB; + break; + default: + return -EINVAL; + } + + ak5702_write(codec, AK5702_FMT1, fmt1); + ak5702_write(codec, AK5702_FMT2, AK5702_FMT2_STEREO); + return 0; +} + +static int ak5702_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 reg = 0; + + reg = ak5702_read_reg_cache(codec, AK5702_PLL1); + switch (pll_id) { + case AK5702_PLL_POWERDOWN: + reg &= (~AK5702_PLL1_PM_MASK); + reg |= AK5702_PLL1_POWERDOWN; + break; + case AK5702_PLL_MASTER: + reg &= (~AK5702_PLL1_MODE_MASK); + reg |= AK5702_PLL1_MASTER; + reg |= AK5702_PLL1_POWERUP; + break; + case AK5702_PLL_SLAVE: + reg &= (~AK5702_PLL1_MODE_MASK); + reg |= AK5702_PLL1_SLAVE; + reg |= AK5702_PLL1_POWERUP; + break; + default: + return -ENODEV; + } + + switch (freq_in) { + case 11289600: + reg &= (~AK5702_PLL1_PLL_MASK); + reg |= AK5702_PLL1_11289600; + break; + case 12000000: + reg &= (~AK5702_PLL1_PLL_MASK); + reg |= AK5702_PLL1_12000000; + break; + case 12288000: + reg &= (~AK5702_PLL1_PLL_MASK); + reg |= AK5702_PLL1_12288000; + break; + case 19200000: + reg &= (~AK5702_PLL1_PLL_MASK); + reg |= AK5702_PLL1_19200000; + break; + default: + return -ENODEV; + } + + ak5702_write(codec, AK5702_PLL1, reg); + return 0; +} + +static int ak5702_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 reg = 0; + + if (div_id == AK5702_BCLK_CLKDIV) { + reg = ak5702_read_reg_cache(codec, AK5702_FS1); + switch (div) { + case AK5702_BCLK_DIV_32: + reg &= (~AK5702_FS1_BCKO_MASK); + reg |= AK5702_FS1_BCKO_32FS; + ak5702_write(codec, AK5702_FS1, reg); + break; + case AK5702_BCLK_DIV_64: + reg &= (~AK5702_FS1_BCKO_MASK); + reg |= AK5702_FS1_BCKO_64FS; + ak5702_write(codec, AK5702_FS1, reg); + break; + default: + return -EINVAL; + } + } + return 0; +} + +static int ak5702_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u8 reg = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + reg = ak5702_read_reg_cache(codec, AK5702_PM1); + ak5702_write(codec, AK5702_PM1, reg | AK5702_PM1_PMVCM); + reg = ak5702_read_reg_cache(codec, AK5702_PLL1); + reg = reg | AK5702_PLL1_POWERUP | AK5702_PLL1_MASTER; + ak5702_write(codec, AK5702_PLL1, reg); + break; + case SND_SOC_BIAS_STANDBY: + reg = ak5702_read_reg_cache(codec, AK5702_PM1); + ak5702_write(codec, AK5702_PM1, reg | AK5702_PM1_PMVCM); + reg = ak5702_read_reg_cache(codec, AK5702_PLL1); + ak5702_write(codec, AK5702_PLL1, reg & (~AK5702_PLL1_POWERUP)); + break; + case SND_SOC_BIAS_OFF: + reg = ak5702_read_reg_cache(codec, AK5702_PM1); + ak5702_write(codec, AK5702_PM1, reg & (~AK5702_PM1_PMVCM)); + break; + } + + codec->bias_level = level; + return 0; +} + +static int ak5702_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + ak5702_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int ak5702_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int i; + + /* Bring the codec back up to standby first to minimise pop/clicks */ + ak5702_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + ak5702_set_bias_level(codec, codec->suspend_bias_level); + + /* Sync back everything else */ + for (i = 0; i < ARRAY_SIZE(ak5702_reg); i++) + ak5702_write(codec, i, ak5702_reg[i]); + + return 0; +} + +#define AK5702_RATES SNDRV_PCM_RATE_8000_48000 +#define AK5702_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +struct snd_soc_dai_ops ak5702_ops = { + .set_fmt = ak5702_set_dai_fmt, + .set_sysclk = ak5702_set_dai_sysclk, + .set_clkdiv = ak5702_set_dai_clkdiv, + .set_pll = ak5702_set_dai_pll, +}; + +struct snd_soc_dai ak5702_dai = { + .name = "AK5702", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 4, + .rates = AK5702_RATES, + .formats = AK5702_FORMATS, + }, + .ops = &ak5702_ops, +}; +EXPORT_SYMBOL_GPL(ak5702_dai); + +static struct snd_soc_codec *ak5702_codec; + +static int ak5702_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = ak5702_codec; + int ret = 0; + u8 reg = 0; + + socdev->card->codec = ak5702_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "ak5702: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + reg = ak5702_read_reg_cache(codec, AK5702_PM1); + reg |= AK5702_PM1_PMVCM; + ak5702_write(codec, AK5702_PM1, reg); + + /* initialize ADC */ + reg = AK5702_SIG1_L_LIN1 | AK5702_SIG1_R_RIN2; + ak5702_write(codec, AK5702_SIG1, reg); + reg = AK5702_SIG2_L_LIN3 | AK5702_SIG2_R_RIN4; + ak5702_write(codec, AK5702_SIG2, reg); + + reg = ak5702_read_reg_cache(codec, AK5702_PM1); + reg = reg | AK5702_PM1_PMADAL | AK5702_PM1_PMADAR; + ak5702_write(codec, AK5702_PM1, reg); + reg = ak5702_read_reg_cache(codec, AK5702_PM2); + reg = reg | AK5702_PM2_PMADBL | AK5702_PM2_PMADBR; + ak5702_write(codec, AK5702_PM2, reg); + + /* initialize volume */ + ak5702_write(codec, AK5702_MICG1, AK5702_MICG1_INIT); + ak5702_write(codec, AK5702_MICG2, AK5702_MICG2_INIT); + ak5702_write(codec, AK5702_VOL1, AK5702_VOL1_IVOLAC); + ak5702_write(codec, AK5702_VOL2, AK5702_VOL2_IVOLBC); + ak5702_write(codec, AK5702_LVOL1, AK5702_LVOL1_INIT); + ak5702_write(codec, AK5702_RVOL1, AK5702_RVOL1_INIT); + ak5702_write(codec, AK5702_LVOL2, AK5702_LVOL2_INIT); + ak5702_write(codec, AK5702_RVOL2, AK5702_RVOL2_INIT); + + snd_soc_add_controls(codec, ak5702_snd_controls, + ARRAY_SIZE(ak5702_snd_controls)); + ak5702_add_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ak5702: failed to register card\n"); + goto card_err; + } + + return ret; +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static int ak5702_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ak5702_priv *ak5702; + struct snd_soc_codec *codec; + int ret; + + if (ak5702_codec) { + dev_err(&client->dev, + "Multiple AK5702 devices not supported\n"); + return -ENOMEM; + } + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + ak5702 = kzalloc(sizeof(struct ak5702_priv), GFP_KERNEL); + if (ak5702 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = ak5702; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + i2c_set_clientdata(client, codec); + codec->control_data = client; + + codec->dev = &client->dev; + codec->name = "AK5702"; + codec->owner = THIS_MODULE; + codec->read = ak5702_read_reg_cache; + codec->write = ak5702_write; + codec->set_bias_level = ak5702_set_bias_level; + codec->dai = &ak5702_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(ak5702_reg); + codec->reg_cache = (void *)&ak5702_reg; + if (codec->reg_cache == NULL) + return -ENOMEM; + + codec->hw_write = (hw_write_t) i2c_master_send; + codec->hw_read = (hw_read_t) i2c_master_recv; + + ak5702_codec = codec; + ak5702_dai.dev = &client->dev; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&ak5702_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); + return ret; + } + + return ret; +} + +static __devexit int ak5702_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + struct ak5702_priv *ak5702 = codec->private_data; + + snd_soc_unregister_dai(&ak5702_dai); + snd_soc_unregister_codec(codec); + kfree(codec); + kfree(ak5702); + ak5702_codec = NULL; + return 0; +} + +static const struct i2c_device_id ak5702_i2c_id[] = { + {"ak5702-i2c", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ak5702_i2c_id); + +static struct i2c_driver ak5702_i2c_driver = { + .driver = { + .name = "ak5702-i2c", + .owner = THIS_MODULE, + }, + .probe = ak5702_i2c_probe, + .remove = __devexit_p(ak5702_i2c_remove), + .id_table = ak5702_i2c_id, +}; +#endif + +static int ak5702_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + if (codec->control_data) + ak5702_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&ak5702_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ak5702 = { + .probe = ak5702_probe, + .remove = ak5702_remove, + .suspend = ak5702_suspend, + .resume = ak5702_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ak5702); + +static int __init ak5702_modinit(void) +{ + return i2c_add_driver(&ak5702_i2c_driver); +} +module_init(ak5702_modinit); + +static void __exit ak5702_exit(void) +{ + i2c_del_driver(&ak5702_i2c_driver); +} +module_exit(ak5702_exit); + +MODULE_DESCRIPTION("Soc AK5702 driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak5702.h b/sound/soc/codecs/ak5702.h new file mode 100644 index 000000000000..97af6e45e498 --- /dev/null +++ b/sound/soc/codecs/ak5702.h @@ -0,0 +1,130 @@ +/* + * ak5702.h -- AK5702 Soc Audio driver + * + * Copyright 2009 Freescale Semiconductor, Inc. All rights reserved. + * + * 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 _AK5702_H +#define _AK5702_H + +/* AK5702 register space */ + +#define AK5702_PM1 0x00 +#define AK5702_PLL1 0x01 +#define AK5702_SIG1 0x02 +#define AK5702_MICG1 0x03 +#define AK5702_FMT1 0x04 +#define AK5702_FS1 0x05 +#define AK5702_CLK1 0x06 +#define AK5702_VOL1 0x07 +#define AK5702_LVOL1 0x08 +#define AK5702_RVOL1 0x09 +#define AK5702_TIMER1 0x0a +#define AK5702_ALC11 0x0b +#define AK5702_ALC12 0x0c +#define AK5702_MODE11 0x0d +#define AK5702_MODE12 0x0e +#define AK5702_MODE13 0x0f + +#define AK5702_PM2 0x10 +#define AK5702_PLL2 0x11 +#define AK5702_SIG2 0x12 +#define AK5702_MICG2 0x13 +#define AK5702_FMT2 0x14 +#define AK5702_FS2 0x15 +#define AK5702_CLK2 0x16 +#define AK5702_VOL2 0x17 +#define AK5702_LVOL2 0x18 +#define AK5702_RVOL2 0x19 +#define AK5702_TIMER2 0x1a +#define AK5702_ALC21 0x1b +#define AK5702_ALC22 0x1c +#define AK5702_MODE21 0x1d +#define AK5702_MODE22 0x1e + +#define AK5702_CACHEREGNUM 0x1F + +#define AK5702_PM1_PMADAL 0x01 +#define AK5702_PM1_PMADAR 0x02 +#define AK5702_PM1_PMVCM 0x04 +#define AK5702_PM2_PMADBL 0x01 +#define AK5702_PM2_PMADBR 0x02 + +#define AK5702_PLL1_POWERDOWN 0x0 +#define AK5702_PLL1_POWERUP 0x01 +#define AK5702_PLL1_MASTER 0x02 +#define AK5702_PLL1_SLAVE 0x0 +#define AK5702_PLL1_11289600 0x10 +#define AK5702_PLL1_12000000 0x24 +#define AK5702_PLL1_12288000 0x14 +#define AK5702_PLL1_19200000 0x20 + +#define AK5702_SIG1_L_LIN1 0x0 +#define AK5702_SIG1_L_LIN2 0x01 +#define AK5702_SIG1_R_RIN1 0x0 +#define AK5702_SIG1_R_RIN2 0x02 +#define AK5702_SIG1_PMMPA 0x10 +#define AK5702_SIG2_L_LIN3 0x0 +#define AK5702_SIG2_L_LIN4 0x01 +#define AK5702_SIG2_R_RIN3 0x0 +#define AK5702_SIG2_R_RIN4 0x02 +#define AK5702_SIG2_PMMPB 0x10 + +#define AK5702_MICG1_INIT 0x0 +#define AK5702_MICG2_INIT 0x0 + +#define AK5702_FMT1_I2S 0x23 +#define AK5702_FMT1_MSB 0x22 +#define AK5702_FMT2_STEREO 0x20 +#define AK5702_FS1_BCKO_32FS 0x10 +#define AK5702_FS1_BCKO_64FS 0x20 +#define AK5702_CLK1_PS_256FS 0x0 +#define AK5702_CLK1_PS_128FS 0x01 +#define AK5702_CLK1_PS_64FS 0x02 +#define AK5702_CLK1_PS_32FS 0x03 +#define AK5702_VOL1_IVOLAC 0x01 +#define AK5702_VOL2_IVOLBC 0x01 +#define AK5702_LVOL1_INIT 0x91 +#define AK5702_RVOL1_INIT 0x91 +#define AK5702_LVOL2_INIT 0x91 +#define AK5702_RVOL2_INIT 0x91 + +#define AK5702_PLL1_PM_MASK 0x01 +#define AK5702_PLL1_MODE_MASK 0x02 +#define AK5702_PLL1_PLL_MASK 0x3c +#define AK5702_FS1_BCKO_MASK 0x30 +#define AK5702_FS1_FS_MASK 0x0f +#define AK5702_CLK1_PS_MASK 0x03 + +/* clock divider id */ +#define AK5702_BCLK_CLKDIV 0 +#define AK5702_MCLK_CLKDIV 1 + +/* bit clock div values */ +#define AK5702_BCLK_DIV_32 0 +#define AK5702_BCLK_DIV_64 1 + +/* m clock div values */ +#define AK5702_MCLK_DIV_32 0 +#define AK5702_MCLK_DIV_64 1 +#define AK5702_MCLK_DIV_128 2 +#define AK5702_MCLK_DIV_256 3 + +/* PLL master and slave modes */ +#define AK5702_PLL_POWERDOWN 0 +#define AK5702_PLL_MASTER 1 +#define AK5702_PLL_SLAVE 2 + +struct ak5702_setup_data { + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai ak5702_dai; +extern struct snd_soc_codec_device soc_codec_dev_ak5702; + +#endif diff --git a/sound/soc/codecs/bluetooth.c b/sound/soc/codecs/bluetooth.c new file mode 100644 index 000000000000..ee6c49f67280 --- /dev/null +++ b/sound/soc/codecs/bluetooth.c @@ -0,0 +1,147 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file bluetooth.c + * @brief Driver for bluetooth PCM interface + * + * @ingroup Sound + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#define BLUETOOTH_RATES SNDRV_PCM_RATE_8000 + +#define BLUETOOTH_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +struct snd_soc_dai bt_dai = { + .name = "bluetooth", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = BLUETOOTH_RATES, + .formats = BLUETOOTH_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = BLUETOOTH_RATES, + .formats = BLUETOOTH_FORMATS, + }, +}; +EXPORT_SYMBOL_GPL(bt_dai); + +static int bt_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->card->codec; + int ret = 0; + + codec->name = "bluetooth"; + codec->owner = THIS_MODULE; + codec->dai = &bt_dai; + codec->num_dai = 1; + + snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + pr_err("failed to create bluetooth pcms\n"); + return ret; + } + + ret = snd_soc_init_card(socdev); + strcpy(codec->card->id, "bluetooth"); + + if (ret < 0) { + pr_err("bluetooth: failed to register card\n"); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + return ret; + } + return 0; +} + +static struct snd_soc_device *bt_socdev; + +static int bt_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int ret = 0; + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + socdev->card->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + bt_socdev = socdev; + + ret = bt_init(socdev); + if (ret < 0) { + pr_err("Bluetooth codec initialisation failed\n"); + kfree(codec); + } + + return ret; +} + +/* power down chip */ +static int bt_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + kfree(codec); + + return 0; +} + +static int bt_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int bt_resume(struct platform_device *pdev) +{ + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_bt = { + .probe = bt_probe, + .remove = bt_remove, + .suspend = bt_suspend, + .resume = bt_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_bt); + +MODULE_DESCRIPTION("ASoC bluetooth codec driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c new file mode 100644 index 000000000000..3d754dea05ba --- /dev/null +++ b/sound/soc/codecs/sgtl5000.c @@ -0,0 +1,1227 @@ +/* + * sgtl5000.c -- SGTL5000 ALSA SoC Audio driver + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <mach/hardware.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "sgtl5000.h" + +struct sgtl5000_priv { + int sysclk; + int master; + int fmt; + int rev; + int lrclk; + int capture_channels; + int playback_active; + int capture_active; + struct snd_pcm_substream *master_substream; + struct snd_pcm_substream *slave_substream; +}; + +static int sgtl5000_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level); + +#define SGTL5000_MAX_CACHED_REG SGTL5000_CHIP_SHORT_CTRL +static u16 sgtl5000_regs[(SGTL5000_MAX_CACHED_REG >> 1) + 1]; + +static unsigned int sgtl5000_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + unsigned int offset = reg >> 1; + if (offset >= ARRAY_SIZE(sgtl5000_regs)) + return -EINVAL; + pr_debug("r r:%02x,v:%04x\n", reg, cache[offset]); + return cache[offset]; +} + +static unsigned int sgtl5000_hw_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct i2c_client *client = codec->control_data; + int i2c_ret; + u16 value; + u8 buf0[2], buf1[2]; + u16 addr = client->addr; + u16 flags = client->flags; + struct i2c_msg msg[2] = { + {addr, flags, 2, buf0}, + {addr, flags | I2C_M_RD, 2, buf1}, + }; + + buf0[0] = (reg & 0xff00) >> 8; + buf0[1] = reg & 0xff; + i2c_ret = i2c_transfer(client->adapter, msg, 2); + if (i2c_ret < 0) { + pr_err("%s: read reg error : Reg 0x%02x\n", __func__, reg); + return 0; + } + + value = buf1[0] << 8 | buf1[1]; + + pr_debug("r r:%02x,v:%04x\n", reg, value); + return value; +} + +static unsigned int sgtl5000_read(struct snd_soc_codec *codec, unsigned int reg) +{ + if ((reg == SGTL5000_CHIP_ID) || + (reg == SGTL5000_CHIP_ADCDAC_CTRL) || + (reg == SGTL5000_CHIP_ANA_STATUS) || + (reg > SGTL5000_MAX_CACHED_REG)) + return sgtl5000_hw_read(codec, reg); + else + return sgtl5000_read_reg_cache(codec, reg); +} + +static inline void sgtl5000_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + unsigned int offset = reg >> 1; + if (offset < ARRAY_SIZE(sgtl5000_regs)) + cache[offset] = value; +} + +static int sgtl5000_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct i2c_client *client = codec->control_data; + u16 addr = client->addr; + u16 flags = client->flags; + u8 buf[4]; + int i2c_ret; + struct i2c_msg msg = { addr, flags, 4, buf }; + + sgtl5000_write_reg_cache(codec, reg, value); + pr_debug("w r:%02x,v:%04x\n", reg, value); + buf[0] = (reg & 0xff00) >> 8; + buf[1] = reg & 0xff; + buf[2] = (value & 0xff00) >> 8; + buf[3] = value & 0xff; + + i2c_ret = i2c_transfer(client->adapter, &msg, 1); + if (i2c_ret < 0) { + pr_err("%s: write reg error : Reg 0x%02x = 0x%04x\n", + __func__, reg, value); + return -EIO; + } + + return i2c_ret; +} + +static void sgtl5000_sync_reg_cache(struct snd_soc_codec *codec) +{ + int reg; + for (reg = 0; reg <= SGTL5000_MAX_CACHED_REG; reg += 2) + sgtl5000_write_reg_cache(codec, reg, + sgtl5000_hw_read(codec, reg)); +} + +static int sgtl5000_restore_reg(struct snd_soc_codec *codec, unsigned int reg) +{ + unsigned int cached_val, hw_val; + + cached_val = sgtl5000_read_reg_cache(codec, reg); + hw_val = sgtl5000_hw_read(codec, reg); + + if (hw_val != cached_val) + return sgtl5000_write(codec, reg, cached_val); + + return 0; +} + +static int all_reg[] = { + SGTL5000_CHIP_ID, + SGTL5000_CHIP_DIG_POWER, + SGTL5000_CHIP_CLK_CTRL, + SGTL5000_CHIP_I2S_CTRL, + SGTL5000_CHIP_SSS_CTRL, + SGTL5000_CHIP_ADCDAC_CTRL, + SGTL5000_CHIP_DAC_VOL, + SGTL5000_CHIP_PAD_STRENGTH, + SGTL5000_CHIP_ANA_ADC_CTRL, + SGTL5000_CHIP_ANA_HP_CTRL, + SGTL5000_CHIP_ANA_CTRL, + SGTL5000_CHIP_LINREG_CTRL, + SGTL5000_CHIP_REF_CTRL, + SGTL5000_CHIP_MIC_CTRL, + SGTL5000_CHIP_LINE_OUT_CTRL, + SGTL5000_CHIP_LINE_OUT_VOL, + SGTL5000_CHIP_ANA_POWER, + SGTL5000_CHIP_PLL_CTRL, + SGTL5000_CHIP_CLK_TOP_CTRL, + SGTL5000_CHIP_ANA_STATUS, + SGTL5000_CHIP_SHORT_CTRL, +}; + +#ifdef DEBUG +static void dump_reg(struct snd_soc_codec *codec) +{ + int i, reg; + printk(KERN_DEBUG "dump begin\n"); + for (i = 0; i < 21; i++) { + reg = sgtl5000_read(codec, all_reg[i]); + printk(KERN_DEBUG "d r %04x, v %04x\n", all_reg[i], reg); + } + printk(KERN_DEBUG "dump end\n"); +} +#else +static void dump_reg(struct snd_soc_codec *codec) +{ +} +#endif + +static int dac_mux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = widget->codec; + unsigned int reg; + + if (ucontrol->value.enumerated.item[0]) { + reg = sgtl5000_read(codec, SGTL5000_CHIP_CLK_TOP_CTRL); + reg |= SGTL5000_INT_OSC_EN; + sgtl5000_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, reg); + + if (codec->bias_level != SND_SOC_BIAS_ON) { + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_ON); + } else + snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + + reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_CTRL); + reg &= ~(SGTL5000_LINE_OUT_MUTE | SGTL5000_HP_MUTE); + sgtl5000_write(codec, SGTL5000_CHIP_ANA_CTRL, reg); + } else { + reg = sgtl5000_read(codec, SGTL5000_CHIP_CLK_TOP_CTRL); + reg &= ~SGTL5000_INT_OSC_EN; + sgtl5000_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, reg); + + snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + } + return 0; +} + +static const char *adc_mux_text[] = { + "MIC_IN", "LINE_IN" +}; + +static const char *dac_mux_text[] = { + "DAC", "LINE_IN" +}; + +static const struct soc_enum adc_enum = +SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 2, 2, adc_mux_text); + +static const struct soc_enum dac_enum = +SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 6, 2, dac_mux_text); + +static const struct snd_kcontrol_new adc_mux = +SOC_DAPM_ENUM("ADC Mux", adc_enum); + +static const struct snd_kcontrol_new dac_mux = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC Mux", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE + | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_soc_info_enum_double, + .get = snd_soc_dapm_get_enum_double, + .put = dac_mux_put, + .private_value = (unsigned long)&dac_enum, +}; + +static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("LINE_IN"), + SND_SOC_DAPM_INPUT("MIC_IN"), + + SND_SOC_DAPM_OUTPUT("HP_OUT"), + SND_SOC_DAPM_OUTPUT("LINE_OUT"), + + SND_SOC_DAPM_PGA("HP", SGTL5000_CHIP_ANA_CTRL, 4, 1, NULL, 0), + SND_SOC_DAPM_PGA("LO", SGTL5000_CHIP_ANA_CTRL, 8, 1, NULL, 0), + + SND_SOC_DAPM_MUX("ADC Mux", SND_SOC_NOPM, 0, 0, &adc_mux), + SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, &dac_mux), + + SND_SOC_DAPM_ADC("ADC", "Capture", SGTL5000_CHIP_DIG_POWER, 6, 0), + SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"ADC Mux", "LINE_IN", "LINE_IN"}, + {"ADC Mux", "MIC_IN", "MIC_IN"}, + {"ADC", NULL, "ADC Mux"}, + {"DAC Mux", "DAC", "DAC"}, + {"DAC Mux", "LINE_IN", "LINE_IN"}, + {"LO", NULL, "DAC"}, + {"HP", NULL, "DAC Mux"}, + {"LINE_OUT", NULL, "LO"}, + {"HP_OUT", NULL, "HP"}, +}; + +static int sgtl5000_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, sgtl5000_dapm_widgets, + ARRAY_SIZE(sgtl5000_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int dac_info_volsw(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 = 0xfc - 0x3c; + return 0; +} + +static int dac_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg, l, r; + + reg = sgtl5000_read(codec, SGTL5000_CHIP_DAC_VOL); + l = (reg & SGTL5000_DAC_VOL_LEFT_MASK) << SGTL5000_DAC_VOL_LEFT_SHIFT; + r = (reg & SGTL5000_DAC_VOL_RIGHT_MASK) << SGTL5000_DAC_VOL_RIGHT_SHIFT; + l = l < 0x3c ? 0x3c : l; + l = l > 0xfc ? 0xfc : l; + r = r < 0x3c ? 0x3c : r; + r = r > 0xfc ? 0xfc : r; + l = 0xfc - l; + r = 0xfc - r; + + ucontrol->value.integer.value[0] = l; + ucontrol->value.integer.value[1] = l; + + return 0; +} + +static int dac_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg, l, r; + + l = ucontrol->value.integer.value[0]; + r = ucontrol->value.integer.value[1]; + + l = l < 0 ? 0 : l; + l = l > 0xfc - 0x3c ? 0xfc - 0x3c : l; + r = r < 0 ? 0 : r; + r = r > 0xfc - 0x3c ? 0xfc - 0x3c : r; + l = 0xfc - l; + r = 0xfc - r; + + reg = l << SGTL5000_DAC_VOL_LEFT_SHIFT | + r << SGTL5000_DAC_VOL_RIGHT_SHIFT; + + sgtl5000_write(codec, SGTL5000_CHIP_DAC_VOL, reg); + + return 0; +} + +static const char *mic_gain_text[] = { + "0dB", "20dB", "30dB", "40dB" +}; + +static const char *adc_m6db_text[] = { + "No Change", "Reduced by 6dB" +}; + +static const struct soc_enum mic_gain = +SOC_ENUM_SINGLE(SGTL5000_CHIP_MIC_CTRL, 0, 4, mic_gain_text); + +static const struct soc_enum adc_m6db = +SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_ADC_CTRL, 8, 2, adc_m6db_text); + +static const struct snd_kcontrol_new sgtl5000_snd_controls[] = { + SOC_ENUM("MIC GAIN", mic_gain), + SOC_DOUBLE("Capture Volume", SGTL5000_CHIP_ANA_ADC_CTRL, 0, 4, 0xf, 0), + SOC_ENUM("Capture Vol Reduction", adc_m6db), + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = dac_info_volsw, + .get = dac_get_volsw, + .put = dac_put_volsw, + }, + SOC_DOUBLE("Headphone Volume", SGTL5000_CHIP_ANA_HP_CTRL, 0, 8, 0x7f, + 1), +}; + +static int sgtl5000_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 adcdac_ctrl; + + adcdac_ctrl = sgtl5000_read(codec, SGTL5000_CHIP_ADCDAC_CTRL); + + if (mute) { + adcdac_ctrl |= SGTL5000_DAC_MUTE_LEFT; + adcdac_ctrl |= SGTL5000_DAC_MUTE_RIGHT; + } else { + adcdac_ctrl &= ~SGTL5000_DAC_MUTE_LEFT; + adcdac_ctrl &= ~SGTL5000_DAC_MUTE_RIGHT; + } + + sgtl5000_write(codec, SGTL5000_CHIP_ADCDAC_CTRL, adcdac_ctrl); + if (!mute) + dump_reg(codec); + return 0; +} + +static int sgtl5000_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct sgtl5000_priv *sgtl5000 = codec->private_data; + u16 i2sctl = 0; + pr_debug("%s:fmt=%08x\n", __func__, fmt); + sgtl5000->master = 0; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + i2sctl |= SGTL5000_I2S_MASTER; + sgtl5000->master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + return -EINVAL; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + i2sctl |= SGTL5000_I2S_MODE_PCM; + break; + case SND_SOC_DAIFMT_DSP_B: + i2sctl |= SGTL5000_I2S_MODE_PCM; + i2sctl |= SGTL5000_I2S_LRALIGN; + break; + case SND_SOC_DAIFMT_I2S: + i2sctl |= SGTL5000_I2S_MODE_I2S_LJ; + break; + case SND_SOC_DAIFMT_RIGHT_J: + i2sctl |= SGTL5000_I2S_MODE_RJ; + i2sctl |= SGTL5000_I2S_LRPOL; + break; + case SND_SOC_DAIFMT_LEFT_J: + i2sctl |= SGTL5000_I2S_MODE_I2S_LJ; + i2sctl |= SGTL5000_I2S_LRALIGN; + break; + default: + return -EINVAL; + } + sgtl5000->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + /* Clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_NB_IF: + break; + case SND_SOC_DAIFMT_IB_IF: + case SND_SOC_DAIFMT_IB_NF: + i2sctl |= SGTL5000_I2S_SCLK_INV; + break; + default: + return -EINVAL; + } + sgtl5000_write(codec, SGTL5000_CHIP_I2S_CTRL, i2sctl); + + return 0; +} + +static int sgtl5000_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct sgtl5000_priv *sgtl5000 = codec->private_data; + + switch (clk_id) { + case SGTL5000_SYSCLK: + sgtl5000->sysclk = freq; + break; + case SGTL5000_LRCLK: + sgtl5000->lrclk = freq; + break; + default: + return -EINVAL; + } + return 0; +} + +static int sgtl5000_pcm_prepare(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; + struct sgtl5000_priv *sgtl5000 = codec->private_data; + int reg; + + reg = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg |= SGTL5000_I2S_IN_POWERUP; + else + reg |= SGTL5000_I2S_OUT_POWERUP; + sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, reg); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER); + reg |= SGTL5000_ADC_POWERUP; + if (sgtl5000->capture_channels == 1) + reg &= ~SGTL5000_ADC_STEREO; + else + reg |= SGTL5000_ADC_STEREO; + sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + } + + return 0; +} + +static int sgtl5000_pcm_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; + struct sgtl5000_priv *sgtl5000 = codec->private_data; + struct snd_pcm_runtime *master_runtime; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sgtl5000->playback_active++; + else + sgtl5000->capture_active++; + + /* The DAI has shared clocks so if we already have a playback or + * capture going then constrain this substream to match it. + */ + if (sgtl5000->master_substream) { + master_runtime = sgtl5000->master_substream->runtime; + + pr_debug("Constraining to %d bits\n", + master_runtime->sample_bits); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + master_runtime->sample_bits, + master_runtime->sample_bits); + + sgtl5000->slave_substream = substream; + } else + sgtl5000->master_substream = substream; + + return 0; +} + +static void sgtl5000_pcm_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; + struct sgtl5000_priv *sgtl5000 = codec->private_data; + int reg, dig_pwr, ana_pwr; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sgtl5000->playback_active--; + else + sgtl5000->capture_active--; + + if (sgtl5000->master_substream == substream) + sgtl5000->master_substream = sgtl5000->slave_substream; + + sgtl5000->slave_substream = NULL; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ana_pwr = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER); + ana_pwr &= ~(SGTL5000_ADC_POWERUP | SGTL5000_ADC_STEREO); + sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr); + } + + dig_pwr = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dig_pwr &= ~SGTL5000_I2S_IN_POWERUP; + else + dig_pwr &= ~SGTL5000_I2S_OUT_POWERUP; + sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, dig_pwr); + + if (!sgtl5000->playback_active && !sgtl5000->capture_active) { + reg = sgtl5000_read(codec, SGTL5000_CHIP_I2S_CTRL); + reg &= ~SGTL5000_I2S_MASTER; + sgtl5000_write(codec, SGTL5000_CHIP_I2S_CTRL, reg); + } +} + +/* + * Set PCM DAI bit size and sample rate. + * input: params_rate, params_fmt + */ +static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + 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; + struct sgtl5000_priv *sgtl5000 = codec->private_data; + int channels = params_channels(params); + int clk_ctl = 0; + int pll_ctl = 0; + int i2s_ctl; + int div2 = 0; + int reg; + + pr_debug("%s channels=%d\n", __func__, channels); + + if (!sgtl5000->sysclk) { + pr_err("%s: set sysclk first!\n", __func__); + return -EFAULT; + } + + if (substream == sgtl5000->slave_substream) { + pr_debug("Ignoring hw_params for slave substream\n"); + return 0; + } + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + sgtl5000->capture_channels = channels; + + switch (sgtl5000->lrclk) { + case 32000: + clk_ctl |= SGTL5000_SYS_FS_32k << SGTL5000_SYS_FS_SHIFT; + break; + case 44100: + clk_ctl |= SGTL5000_SYS_FS_44_1k << SGTL5000_SYS_FS_SHIFT; + break; + case 48000: + clk_ctl |= SGTL5000_SYS_FS_48k << SGTL5000_SYS_FS_SHIFT; + break; + case 96000: + clk_ctl |= SGTL5000_SYS_FS_96k << SGTL5000_SYS_FS_SHIFT; + break; + default: + pr_err("%s: sample rate %d not supported\n", __func__, + sgtl5000->lrclk); + return -EFAULT; + } + +#if 0 /* SGTL5000 rev1 has a IC bug to prevent switching to MCLK from PLL. */ + if (fs * 256 == sgtl5000->sysclk) + clk_ctl |= SGTL5000_MCLK_FREQ_256FS << SGTL5000_MCLK_FREQ_SHIFT; + else if (fs * 384 == sgtl5000->sysclk && fs != 96000) + clk_ctl |= SGTL5000_MCLK_FREQ_384FS << SGTL5000_MCLK_FREQ_SHIFT; + else if (fs * 512 == sgtl5000->sysclk && fs != 96000) + clk_ctl |= SGTL5000_MCLK_FREQ_512FS << SGTL5000_MCLK_FREQ_SHIFT; + else +#endif + { + if (!sgtl5000->master) { + pr_err("%s: PLL not supported in slave mode\n", + __func__); + return -EINVAL; + } + clk_ctl |= SGTL5000_MCLK_FREQ_PLL << SGTL5000_MCLK_FREQ_SHIFT; + } + + if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) { + u64 out, t; + unsigned int in, int_div, frac_div; + if (sgtl5000->sysclk > 17000000) { + div2 = 1; + in = sgtl5000->sysclk / 2; + } else { + div2 = 0; + in = sgtl5000->sysclk; + } + if (sgtl5000->lrclk == 44100) + out = 180633600; + else + out = 196608000; + t = do_div(out, in); + int_div = out; + t *= 2048; + do_div(t, in); + frac_div = t; + pll_ctl = int_div << SGTL5000_PLL_INT_DIV_SHIFT | + frac_div << SGTL5000_PLL_FRAC_DIV_SHIFT; + } + + i2s_ctl = sgtl5000_read(codec, SGTL5000_CHIP_I2S_CTRL); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J) + return -EINVAL; + i2s_ctl |= SGTL5000_I2S_DLEN_16 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_32FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + i2s_ctl |= SGTL5000_I2S_DLEN_20 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + case SNDRV_PCM_FORMAT_S24_LE: + i2s_ctl |= SGTL5000_I2S_DLEN_24 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + case SNDRV_PCM_FORMAT_S32_LE: + if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J) + return -EINVAL; + i2s_ctl |= SGTL5000_I2S_DLEN_32 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + default: + return -EINVAL; + } + + pr_debug("fs=%d,clk_ctl=%d,pll_ctl=%d,i2s_ctl=%d,div2=%d\n", + sgtl5000->lrclk, clk_ctl, pll_ctl, i2s_ctl, div2); + + if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) { + sgtl5000_write(codec, SGTL5000_CHIP_PLL_CTRL, pll_ctl); + reg = sgtl5000_read(codec, SGTL5000_CHIP_CLK_TOP_CTRL); + if (div2) + reg |= SGTL5000_INPUT_FREQ_DIV2; + else + reg &= ~SGTL5000_INPUT_FREQ_DIV2; + sgtl5000_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, reg); + reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER); + reg |= SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP; + sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + } + sgtl5000_write(codec, SGTL5000_CHIP_CLK_CTRL, clk_ctl); + sgtl5000_write(codec, SGTL5000_CHIP_I2S_CTRL, i2s_ctl); + + return 0; +} + +static void sgtl5000_mic_bias(struct snd_soc_codec *codec, int enable) +{ + int reg, bias_r = 0; + if (enable) + bias_r = SGTL5000_BIAS_R_4k << SGTL5000_BIAS_R_SHIFT; + reg = sgtl5000_read(codec, SGTL5000_CHIP_MIC_CTRL); + if ((reg & SGTL5000_BIAS_R_MASK) != bias_r) { + reg &= ~SGTL5000_BIAS_R_MASK; + reg |= bias_r; + sgtl5000_write(codec, SGTL5000_CHIP_MIC_CTRL, reg); + } +} + +static int sgtl5000_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg, ana_pwr; + int delay = 0; + pr_debug("dapm level %d\n", level); + switch (level) { + case SND_SOC_BIAS_ON: /* full On */ + if (codec->bias_level == SND_SOC_BIAS_ON) + break; + + sgtl5000_mic_bias(codec, 1); + + reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER); + reg |= SGTL5000_VAG_POWERUP; + sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + msleep(400); + + break; + + case SND_SOC_BIAS_PREPARE: /* partial On */ + if (codec->bias_level == SND_SOC_BIAS_PREPARE) + break; + + sgtl5000_mic_bias(codec, 0); + + /* must power up hp/line out before vag & dac to + avoid pops. */ + reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER); + if (reg & SGTL5000_VAG_POWERUP) + delay = 400; + reg &= ~SGTL5000_VAG_POWERUP; + reg |= SGTL5000_DAC_POWERUP; + reg |= SGTL5000_HP_POWERUP; + reg |= SGTL5000_LINE_OUT_POWERUP; + sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + if (delay) + msleep(delay); + + reg = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER); + reg |= SGTL5000_DAC_EN; + sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, reg); + + break; + + case SND_SOC_BIAS_STANDBY: /* Off, with power */ + /* soc doesn't do PREPARE state after record so make sure + that anything that needs to be turned OFF gets turned off. */ + if (codec->bias_level == SND_SOC_BIAS_STANDBY) + break; + + /* soc calls digital_mute to unmute before record but doesn't + call digital_mute to mute after record. */ + sgtl5000_digital_mute(&sgtl5000_dai, 1); + + sgtl5000_mic_bias(codec, 0); + + reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER); + if (reg & SGTL5000_VAG_POWERUP) { + reg &= ~SGTL5000_VAG_POWERUP; + sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + msleep(400); + } + reg &= ~SGTL5000_DAC_POWERUP; + reg &= ~SGTL5000_HP_POWERUP; + reg &= ~SGTL5000_LINE_OUT_POWERUP; + sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + + reg = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER); + reg &= ~SGTL5000_DAC_EN; + sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, reg); + + break; + + case SND_SOC_BIAS_OFF: /* Off, without power */ + /* must power down hp/line out after vag & dac to + avoid pops. */ + reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER); + ana_pwr = reg; + reg &= ~SGTL5000_VAG_POWERUP; + + /* Workaround for sgtl5000 rev 0x11 chip audio suspend failure + issue on mx25 */ + /* reg &= ~SGTL5000_REFTOP_POWERUP; */ + + sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + msleep(600); + + reg &= ~SGTL5000_HP_POWERUP; + reg &= ~SGTL5000_LINE_OUT_POWERUP; + reg &= ~SGTL5000_DAC_POWERUP; + reg &= ~SGTL5000_ADC_POWERUP; + sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + + /* save ANA POWER register value for resume */ + sgtl5000_write_reg_cache(codec, SGTL5000_CHIP_ANA_POWER, + ana_pwr); + break; + } + codec->bias_level = level; + return 0; +} + +#define SGTL5000_RATES (SNDRV_PCM_RATE_32000 |\ + SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_96000) + +#define SGTL5000_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_dai_ops sgtl5000_ops = { + .prepare = sgtl5000_pcm_prepare, + .startup = sgtl5000_pcm_startup, + .shutdown = sgtl5000_pcm_shutdown, + .hw_params = sgtl5000_pcm_hw_params, + .digital_mute = sgtl5000_digital_mute, + .set_fmt = sgtl5000_set_dai_fmt, + .set_sysclk = sgtl5000_set_dai_sysclk +}; + +struct snd_soc_dai sgtl5000_dai = { + .name = "SGTL5000", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SGTL5000_RATES, + .formats = SGTL5000_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SGTL5000_RATES, + .formats = SGTL5000_FORMATS, + }, + .ops = &sgtl5000_ops, + .symmetric_rates = 1, +}; +EXPORT_SYMBOL_GPL(sgtl5000_dai); + +static int sgtl5000_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int sgtl5000_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + unsigned int i; + + /* Restore refs first in same order as in sgtl5000_init */ + sgtl5000_restore_reg(codec, SGTL5000_CHIP_LINREG_CTRL); + sgtl5000_restore_reg(codec, SGTL5000_CHIP_ANA_POWER); + msleep(10); + sgtl5000_restore_reg(codec, SGTL5000_CHIP_REF_CTRL); + sgtl5000_restore_reg(codec, SGTL5000_CHIP_LINE_OUT_CTRL); + + /* Restore everythine else */ + for (i = 1; i < sizeof(all_reg) / sizeof(int); i++) + sgtl5000_restore_reg(codec, all_reg[i]); + + sgtl5000_write(codec, SGTL5000_DAP_CTRL, 0); + + /* Bring the codec back up to standby first to minimise pop/clicks */ + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + sgtl5000_set_bias_level(codec, codec->suspend_bias_level); + + return 0; +} + +static struct snd_soc_codec *sgtl5000_codec; + +/* + * initialise the SGTL5000 driver + * register the mixer and dsp interfaces with the kernel + */ +static int sgtl5000_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct sgtl5000_platform_data *plat = socdev->codec_data; + struct snd_soc_codec *codec = sgtl5000_codec; + struct sgtl5000_priv *sgtl5000 = codec->private_data; + u16 reg, ana_pwr, lreg_ctrl, ref_ctrl, lo_ctrl, short_ctrl, sss; + int vag; + int ret = 0; + + socdev->card->codec = sgtl5000_codec; + + /* register pcms */ + 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; + } + + /* reset value */ + ana_pwr = SGTL5000_DAC_STEREO | + SGTL5000_LINREG_SIMPLE_POWERUP | + SGTL5000_STARTUP_POWERUP | + SGTL5000_ADC_STEREO | SGTL5000_REFTOP_POWERUP; + lreg_ctrl = 0; + ref_ctrl = 0; + lo_ctrl = 0; + short_ctrl = 0; + sss = SGTL5000_DAC_SEL_I2S_IN << SGTL5000_DAC_SEL_SHIFT; + + /* workaround for rev 0x11: use vddd linear regulator */ + if (!plat->vddd || (sgtl5000->rev >= 0x11)) { + /* set VDDD to 1.2v */ + lreg_ctrl |= 0x8 << SGTL5000_LINREG_VDDD_SHIFT; + /* power internal linear regulator */ + ana_pwr |= SGTL5000_LINEREG_D_POWERUP; + } else { + /* turn of startup power */ + ana_pwr &= ~SGTL5000_STARTUP_POWERUP; + ana_pwr &= ~SGTL5000_LINREG_SIMPLE_POWERUP; + } + if (plat->vddio < 3100 && plat->vdda < 3100) { + /* Enable VDDC charge pump */ + ana_pwr |= SGTL5000_VDDC_CHRGPMP_POWERUP; + } + if (plat->vddio >= 3100 && plat->vdda >= 3100) { + /* VDDC use VDDIO rail */ + lreg_ctrl |= SGTL5000_VDDC_ASSN_OVRD; + if (plat->vddio >= 3100) + lreg_ctrl |= SGTL5000_VDDC_MAN_ASSN_VDDIO << + SGTL5000_VDDC_MAN_ASSN_SHIFT; + } + /* If PLL is powered up (such as on power cycle) leave it on. */ + reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER); + ana_pwr |= reg & (SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP); + + /* set ADC/DAC ref voltage to vdda/2 */ + vag = plat->vdda / 2; + if (vag <= SGTL5000_ANA_GND_BASE) + vag = 0; + else if (vag >= SGTL5000_ANA_GND_BASE + SGTL5000_ANA_GND_STP * + (SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT)) + vag = SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT; + else + vag = (vag - SGTL5000_ANA_GND_BASE) / SGTL5000_ANA_GND_STP; + ref_ctrl |= vag << SGTL5000_ANA_GND_SHIFT; + + /* set line out ref voltage to vddio/2 */ + vag = plat->vddio / 2; + if (vag <= SGTL5000_LINE_OUT_GND_BASE) + vag = 0; + else if (vag >= SGTL5000_LINE_OUT_GND_BASE + SGTL5000_LINE_OUT_GND_STP * + SGTL5000_LINE_OUT_GND_MAX) + vag = SGTL5000_LINE_OUT_GND_MAX; + else + vag = (vag - SGTL5000_LINE_OUT_GND_BASE) / + SGTL5000_LINE_OUT_GND_STP; + lo_ctrl |= vag << SGTL5000_LINE_OUT_GND_SHIFT; + + /* enable small pop */ + ref_ctrl |= SGTL5000_SMALL_POP; + + /* Controls the output bias current for the lineout */ + lo_ctrl |= + (SGTL5000_LINE_OUT_CURRENT_360u << SGTL5000_LINE_OUT_CURRENT_SHIFT); + + /* set short detect */ + /* keep default */ + + /* set routing */ + /* keep default, bypass DAP */ + + sgtl5000_write(codec, SGTL5000_CHIP_LINREG_CTRL, lreg_ctrl); + sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr); + msleep(10); + + /* For rev 0x11, if vddd linear reg has been enabled, we have + to disable simple reg to get proper VDDD voltage. */ + if ((ana_pwr & SGTL5000_LINEREG_D_POWERUP) && (sgtl5000->rev >= 0x11)) { + ana_pwr &= ~SGTL5000_LINREG_SIMPLE_POWERUP; + sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr); + msleep(10); + } + + sgtl5000_write(codec, SGTL5000_CHIP_REF_CTRL, ref_ctrl); + sgtl5000_write(codec, SGTL5000_CHIP_LINE_OUT_CTRL, lo_ctrl); + sgtl5000_write(codec, SGTL5000_CHIP_SHORT_CTRL, short_ctrl); + sgtl5000_write(codec, SGTL5000_CHIP_SSS_CTRL, sss); + sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, 0); + + reg = SGTL5000_DAC_VOL_RAMP_EN | + SGTL5000_DAC_MUTE_RIGHT | SGTL5000_DAC_MUTE_LEFT; + sgtl5000_write(codec, SGTL5000_CHIP_ADCDAC_CTRL, reg); + + if (cpu_is_mx25()) + sgtl5000_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x01df); + else + sgtl5000_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x015f); + + reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_ADC_CTRL); + reg &= ~SGTL5000_ADC_VOL_M6DB; + reg &= ~(SGTL5000_ADC_VOL_LEFT_MASK | SGTL5000_ADC_VOL_RIGHT_MASK); + reg |= (0xf << SGTL5000_ADC_VOL_LEFT_SHIFT) + | (0xf << SGTL5000_ADC_VOL_RIGHT_SHIFT); + sgtl5000_write(codec, SGTL5000_CHIP_ANA_ADC_CTRL, reg); + + reg = SGTL5000_LINE_OUT_MUTE | SGTL5000_HP_MUTE | + SGTL5000_HP_ZCD_EN | SGTL5000_ADC_ZCD_EN; + sgtl5000_write(codec, SGTL5000_CHIP_ANA_CTRL, reg); + + sgtl5000_write(codec, SGTL5000_CHIP_MIC_CTRL, 0); + sgtl5000_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, 0); + /* disable DAP */ + sgtl5000_write(codec, SGTL5000_DAP_CTRL, 0); + /* TODO: initialize DAP */ + + snd_soc_add_controls(codec, sgtl5000_snd_controls, + ARRAY_SIZE(sgtl5000_snd_controls)); + sgtl5000_add_widgets(codec); + + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + printk(KERN_ERR "sgtl5000: failed to register card\n"); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + return ret; + } + + return 0; +} + +/* power down chip */ +static int sgtl5000_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + if (codec->control_data) + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_sgtl5000 = { + .probe = sgtl5000_probe, + .remove = sgtl5000_remove, + .suspend = sgtl5000_suspend, + .resume = sgtl5000_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_sgtl5000); + +static __devinit int sgtl5000_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sgtl5000_priv *sgtl5000; + struct snd_soc_codec *codec; + int ret = 0; + u32 val; + + if (sgtl5000_codec) { + dev_err(&client->dev, + "Multiple SGTL5000 devices not supported\n"); + return -ENOMEM; + } + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + sgtl5000 = kzalloc(sizeof(struct sgtl5000_priv), GFP_KERNEL); + if (sgtl5000 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = sgtl5000; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + i2c_set_clientdata(client, codec); + codec->control_data = client; + + val = sgtl5000_read(codec, SGTL5000_CHIP_ID); + if (((val & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) != + SGTL5000_PARTID_PART_ID) { + pr_err("Device with ID register %x is not a SGTL5000\n", val); + return -ENODEV; + } + + sgtl5000->rev = (val & SGTL5000_REVID_MASK) >> SGTL5000_REVID_SHIFT; + dev_info(&client->dev, "SGTL5000 revision %d\n", sgtl5000->rev); + + codec->dev = &client->dev; + codec->name = "SGTL5000"; + codec->owner = THIS_MODULE; + codec->read = sgtl5000_read_reg_cache; + codec->write = sgtl5000_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = sgtl5000_set_bias_level; + codec->dai = &sgtl5000_dai; + codec->num_dai = 1; + codec->reg_cache_size = sizeof(sgtl5000_regs); + codec->reg_cache_step = 2; + codec->reg_cache = (void *)&sgtl5000_regs; + + sgtl5000_sync_reg_cache(codec); + + sgtl5000_codec = codec; + sgtl5000_dai.dev = &client->dev; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&sgtl5000_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); + return ret; + } + + return ret; +} + +static __devexit int sgtl5000_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + struct sgtl5000_priv *sgtl5000 = codec->private_data; + + snd_soc_unregister_dai(&sgtl5000_dai); + snd_soc_unregister_codec(codec); + kfree(codec); + kfree(sgtl5000); + sgtl5000_codec = NULL; + return 0; +} + +static const struct i2c_device_id sgtl5000_id[] = { + {"sgtl5000-i2c", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, sgtl5000_id); + +static struct i2c_driver sgtl5000_i2c_driver = { + .driver = { + .name = "sgtl5000-i2c", + .owner = THIS_MODULE, + }, + .probe = sgtl5000_i2c_probe, + .remove = __devexit_p(sgtl5000_i2c_remove), + .id_table = sgtl5000_id, +}; + +static int __init sgtl5000_modinit(void) +{ + return i2c_add_driver(&sgtl5000_i2c_driver); +} +module_init(sgtl5000_modinit); + +static void __exit sgtl5000_exit(void) +{ + i2c_del_driver(&sgtl5000_i2c_driver); +} +module_exit(sgtl5000_exit); + +MODULE_DESCRIPTION("ASoC SGTL5000 driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h new file mode 100644 index 000000000000..177be05b486c --- /dev/null +++ b/sound/soc/codecs/sgtl5000.h @@ -0,0 +1,405 @@ +/* + * sgtl5000.h - SGTL5000 audio codec interface + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _SGTL5000_H +#define _SGTL5000_H + +#include <linux/i2c.h> + +extern struct snd_soc_dai sgtl5000_dai; +extern struct snd_soc_codec_device soc_codec_dev_sgtl5000; + +/* + * Register values. + */ +#define SGTL5000_CHIP_ID 0x0000 +#define SGTL5000_CHIP_DIG_POWER 0x0002 +#define SGTL5000_CHIP_CLK_CTRL 0x0004 +#define SGTL5000_CHIP_I2S_CTRL 0x0006 +#define SGTL5000_CHIP_SSS_CTRL 0x000a +#define SGTL5000_CHIP_ADCDAC_CTRL 0x000e +#define SGTL5000_CHIP_DAC_VOL 0x0010 +#define SGTL5000_CHIP_PAD_STRENGTH 0x0014 +#define SGTL5000_CHIP_ANA_ADC_CTRL 0x0020 +#define SGTL5000_CHIP_ANA_HP_CTRL 0x0022 +#define SGTL5000_CHIP_ANA_CTRL 0x0024 +#define SGTL5000_CHIP_LINREG_CTRL 0x0026 +#define SGTL5000_CHIP_REF_CTRL 0x0028 +#define SGTL5000_CHIP_MIC_CTRL 0x002a +#define SGTL5000_CHIP_LINE_OUT_CTRL 0x002c +#define SGTL5000_CHIP_LINE_OUT_VOL 0x002e +#define SGTL5000_CHIP_ANA_POWER 0x0030 +#define SGTL5000_CHIP_PLL_CTRL 0x0032 +#define SGTL5000_CHIP_CLK_TOP_CTRL 0x0034 +#define SGTL5000_CHIP_ANA_STATUS 0x0036 +#define SGTL5000_CHIP_SHORT_CTRL 0x003c +#define SGTL5000_CHIP_ANA_TEST2 0x003a +#define SGTL5000_DAP_CTRL 0x0100 +#define SGTL5000_DAP_PEQ 0x0102 +#define SGTL5000_DAP_BASS_ENHANCE 0x0104 +#define SGTL5000_DAP_BASS_ENHANCE_CTRL 0x0106 +#define SGTL5000_DAP_AUDIO_EQ 0x0108 +#define SGTL5000_DAP_SURROUND 0x010a +#define SGTL5000_DAP_FLT_COEF_ACCESS 0x010c +#define SGTL5000_DAP_COEF_WR_B0_MSB 0x010e +#define SGTL5000_DAP_COEF_WR_B0_LSB 0x0110 +#define SGTL5000_DAP_EQ_BASS_BAND0 0x0116 +#define SGTL5000_DAP_EQ_BASS_BAND1 0x0118 +#define SGTL5000_DAP_EQ_BASS_BAND2 0x011a +#define SGTL5000_DAP_EQ_BASS_BAND3 0x011c +#define SGTL5000_DAP_EQ_BASS_BAND4 0x011e +#define SGTL5000_DAP_MAIN_CHAN 0x0120 +#define SGTL5000_DAP_MIX_CHAN 0x0122 +#define SGTL5000_DAP_AVC_CTRL 0x0124 +#define SGTL5000_DAP_AVC_THRESHOLD 0x0126 +#define SGTL5000_DAP_AVC_ATTACK 0x0128 +#define SGTL5000_DAP_AVC_DECAY 0x012a +#define SGTL5000_DAP_COEF_WR_B1_MSB 0x012c +#define SGTL5000_DAP_COEF_WR_B1_LSB 0x012e +#define SGTL5000_DAP_COEF_WR_B2_MSB 0x0130 +#define SGTL5000_DAP_COEF_WR_B2_LSB 0x0132 +#define SGTL5000_DAP_COEF_WR_A1_MSB 0x0134 +#define SGTL5000_DAP_COEF_WR_A1_LSB 0x0136 +#define SGTL5000_DAP_COEF_WR_A2_MSB 0x0138 +#define SGTL5000_DAP_COEF_WR_A2_LSB 0x013a + +/* + * Field Definitions. + */ + +/* + * SGTL5000_CHIP_ID + */ +#define SGTL5000_PARTID_MASK 0xff00 +#define SGTL5000_PARTID_SHIFT 8 +#define SGTL5000_PARTID_WIDTH 8 +#define SGTL5000_PARTID_PART_ID 0xa0 +#define SGTL5000_REVID_MASK 0x00ff +#define SGTL5000_REVID_SHIFT 0 +#define SGTL5000_REVID_WIDTH 8 + +/* + * SGTL5000_CHIP_DIG_POWER + */ +#define SGTL5000_ADC_EN 0x0040 +#define SGTL5000_DAC_EN 0x0020 +#define SGTL5000_DAP_POWERUP 0x0010 +#define SGTL5000_I2S_OUT_POWERUP 0x0002 +#define SGTL5000_I2S_IN_POWERUP 0x0001 + +/* + * SGTL5000_CHIP_CLK_CTRL + */ +#define SGTL5000_SYS_FS_MASK 0x00c0 +#define SGTL5000_SYS_FS_SHIFT 2 +#define SGTL5000_SYS_FS_WIDTH 2 +#define SGTL5000_SYS_FS_32k 0x0 +#define SGTL5000_SYS_FS_44_1k 0x1 +#define SGTL5000_SYS_FS_48k 0x2 +#define SGTL5000_SYS_FS_96k 0x3 +#define SGTL5000_MCLK_FREQ_MASK 0x0003 +#define SGTL5000_MCLK_FREQ_SHIFT 0 +#define SGTL5000_MCLK_FREQ_WIDTH 2 +#define SGTL5000_MCLK_FREQ_256FS 0x0 +#define SGTL5000_MCLK_FREQ_384FS 0x1 +#define SGTL5000_MCLK_FREQ_512FS 0x2 +#define SGTL5000_MCLK_FREQ_PLL 0x3 + +/* + * SGTL5000_CHIP_I2S_CTRL + */ +#define SGTL5000_I2S_SCLKFREQ_MASK 0x0100 +#define SGTL5000_I2S_SCLKFREQ_SHIFT 8 +#define SGTL5000_I2S_SCLKFREQ_WIDTH 1 +#define SGTL5000_I2S_SCLKFREQ_64FS 0x0 +#define SGTL5000_I2S_SCLKFREQ_32FS 0x1 /* Not for RJ mode */ +#define SGTL5000_I2S_MASTER 0x0080 +#define SGTL5000_I2S_SCLK_INV 0x0040 +#define SGTL5000_I2S_DLEN_MASK 0x0030 +#define SGTL5000_I2S_DLEN_SHIFT 4 +#define SGTL5000_I2S_DLEN_WIDTH 2 +#define SGTL5000_I2S_DLEN_32 0x0 +#define SGTL5000_I2S_DLEN_24 0x1 +#define SGTL5000_I2S_DLEN_20 0x2 +#define SGTL5000_I2S_DLEN_16 0x3 +#define SGTL5000_I2S_MODE_MASK 0x000c +#define SGTL5000_I2S_MODE_SHIFT 2 +#define SGTL5000_I2S_MODE_WIDTH 2 +#define SGTL5000_I2S_MODE_I2S_LJ 0x0 +#define SGTL5000_I2S_MODE_RJ 0x1 +#define SGTL5000_I2S_MODE_PCM 0x2 +#define SGTL5000_I2S_LRALIGN 0x0002 +#define SGTL5000_I2S_LRPOL 0x0001 /* set for which mode */ + +/* + * SGTL5000_CHIP_SSS_CTRL + */ +#define SGTL5000_DAP_MIX_LRSWAP 0x4000 +#define SGTL5000_DAP_LRSWAP 0x2000 +#define SGTL5000_DAC_LRSWAP 0x1000 +#define SGTL5000_I2S_OUT_LRSWAP 0x0400 +#define SGTL5000_DAP_MIX_SEL_MASK 0x0300 +#define SGTL5000_DAP_MIX_SEL_SHIFT 8 +#define SGTL5000_DAP_MIX_SEL_WIDTH 2 +#define SGTL5000_DAP_MIX_SEL_ADC 0x0 +#define SGTL5000_DAP_MIX_SEL_I2S_IN 0x1 +#define SGTL5000_DAP_SEL_MASK 0x00c0 +#define SGTL5000_DAP_SEL_SHIFT 6 +#define SGTL5000_DAP_SEL_WIDTH 2 +#define SGTL5000_DAP_SEL_ADC 0x0 +#define SGTL5000_DAP_SEL_I2S_IN 0x1 +#define SGTL5000_DAC_SEL_MASK 0x0030 +#define SGTL5000_DAC_SEL_SHIFT 4 +#define SGTL5000_DAC_SEL_WIDTH 2 +#define SGTL5000_DAC_SEL_ADC 0x0 +#define SGTL5000_DAC_SEL_I2S_IN 0x1 +#define SGTL5000_DAC_SEL_DAP 0x3 +#define SGTL5000_I2S_OUT_SEL_MASK 0x0003 +#define SGTL5000_I2S_OUT_SEL_SHIFT 0 +#define SGTL5000_I2S_OUT_SEL_WIDTH 2 +#define SGTL5000_I2S_OUT_SEL_ADC 0x0 +#define SGTL5000_I2S_OUT_SEL_I2S_IN 0x1 +#define SGTL5000_I2S_OUT_SEL_DAP 0x3 + +/* + * SGTL5000_CHIP_ADCDAC_CTRL + */ +#define SGTL5000_VOL_BUSY_DAC_RIGHT 0x2000 +#define SGTL5000_VOL_BUSY_DAC_LEFT 0x1000 +#define SGTL5000_DAC_VOL_RAMP_EN 0x0200 +#define SGTL5000_DAC_VOL_RAMP_EXPO 0x0100 +#define SGTL5000_DAC_MUTE_RIGHT 0x0008 +#define SGTL5000_DAC_MUTE_LEFT 0x0004 +#define SGTL5000_ADC_HPF_FREEZE 0x0002 +#define SGTL5000_ADC_HPF_BYPASS 0x0001 + +/* + * SGTL5000_CHIP_DAC_VOL + */ +#define SGTL5000_DAC_VOL_RIGHT_MASK 0xff00 +#define SGTL5000_DAC_VOL_RIGHT_SHIFT 8 +#define SGTL5000_DAC_VOL_RIGHT_WIDTH 8 +#define SGTL5000_DAC_VOL_LEFT_MASK 0x00ff +#define SGTL5000_DAC_VOL_LEFT_SHIFT 0 +#define SGTL5000_DAC_VOL_LEFT_WIDTH 8 + +/* + * SGTL5000_CHIP_PAD_STRENGTH + */ +#define SGTL5000_PAD_I2S_LRCLK_MASK 0x0300 +#define SGTL5000_PAD_I2S_LRCLK_SHIFT 8 +#define SGTL5000_PAD_I2S_LRCLK_WIDTH 2 +#define SGTL5000_PAD_I2S_SCLK_MASK 0x00c0 +#define SGTL5000_PAD_I2S_SCLK_SHIFT 6 +#define SGTL5000_PAD_I2S_SCLK_WIDTH 2 +#define SGTL5000_PAD_I2S_DOUT_MASK 0x0030 +#define SGTL5000_PAD_I2S_DOUT_SHIFT 4 +#define SGTL5000_PAD_I2S_DOUT_WIDTH 2 +#define SGTL5000_PAD_I2C_SDA_MASK 0x000c +#define SGTL5000_PAD_I2C_SDA_SHIFT 2 +#define SGTL5000_PAD_I2C_SDA_WIDTH 2 +#define SGTL5000_PAD_I2C_SCL_MASK 0x0003 +#define SGTL5000_PAD_I2C_SCL_SHIFT 0 +#define SGTL5000_PAD_I2C_SCL_WIDTH 2 + +/* + * SGTL5000_CHIP_ANA_ADC_CTRL + */ +#define SGTL5000_ADC_VOL_M6DB 0x0100 +#define SGTL5000_ADC_VOL_RIGHT_MASK 0x00f0 +#define SGTL5000_ADC_VOL_RIGHT_SHIFT 4 +#define SGTL5000_ADC_VOL_RIGHT_WIDTH 4 +#define SGTL5000_ADC_VOL_LEFT_MASK 0x000f +#define SGTL5000_ADC_VOL_LEFT_SHIFT 0 +#define SGTL5000_ADC_VOL_LEFT_WIDTH 4 + +/* + * SGTL5000_CHIP_ANA_HP_CTRL + */ +#define SGTL5000_HP_VOL_RIGHT_MASK 0x7f00 +#define SGTL5000_HP_VOL_RIGHT_SHIFT 8 +#define SGTL5000_HP_VOL_RIGHT_WIDTH 7 +#define SGTL5000_HP_VOL_LEFT_MASK 0x007f +#define SGTL5000_HP_VOL_LEFT_SHIFT 0 +#define SGTL5000_HP_VOL_LEFT_WIDTH 7 + +/* + * SGTL5000_CHIP_ANA_CTRL + */ +#define SGTL5000_LINE_OUT_MUTE 0x0100 +#define SGTL5000_HP_SEL_MASK 0x0040 +#define SGTL5000_HP_SEL_SHIFT 6 +#define SGTL5000_HP_SEL_WIDTH 1 +#define SGTL5000_HP_SEL_DAC 0x0 +#define SGTL5000_HP_SEL_LINE_IN 0x1 +#define SGTL5000_HP_ZCD_EN 0x0020 +#define SGTL5000_HP_MUTE 0x0010 +#define SGTL5000_ADC_SEL_MASK 0x0004 +#define SGTL5000_ADC_SEL_SHIFT 2 +#define SGTL5000_ADC_SEL_WIDTH 1 +#define SGTL5000_ADC_SEL_MIC 0x0 +#define SGTL5000_ADC_SEL_LINE_IN 0x1 +#define SGTL5000_ADC_ZCD_EN 0x0002 +#define SGTL5000_ADC_MUTE 0x0001 + +/* + * SGTL5000_CHIP_LINREG_CTRL + */ +#define SGTL5000_VDDC_MAN_ASSN_MASK 0x0040 +#define SGTL5000_VDDC_MAN_ASSN_SHIFT 6 +#define SGTL5000_VDDC_MAN_ASSN_WIDTH 1 +#define SGTL5000_VDDC_MAN_ASSN_VDDA 0x0 +#define SGTL5000_VDDC_MAN_ASSN_VDDIO 0x1 +#define SGTL5000_VDDC_ASSN_OVRD 0x0020 +#define SGTL5000_LINREG_VDDD_MASK 0x000f +#define SGTL5000_LINREG_VDDD_SHIFT 0 +#define SGTL5000_LINREG_VDDD_WIDTH 4 + +/* + * SGTL5000_CHIP_REF_CTRL + */ +#define SGTL5000_ANA_GND_MASK 0x01f0 +#define SGTL5000_ANA_GND_SHIFT 4 +#define SGTL5000_ANA_GND_WIDTH 5 +#define SGTL5000_ANA_GND_BASE 800 /* mv */ +#define SGTL5000_ANA_GND_STP 25 /*mv */ +#define SGTL5000_BIAS_CTRL_MASK 0x000e +#define SGTL5000_BIAS_CTRL_SHIFT 1 +#define SGTL5000_BIAS_CTRL_WIDTH 3 +#define SGTL5000_SMALL_POP 0x0001 + +/* + * SGTL5000_CHIP_MIC_CTRL + */ +#define SGTL5000_BIAS_R_MASK 0x0200 +#define SGTL5000_BIAS_R_SHIFT 8 +#define SGTL5000_BIAS_R_WIDTH 2 +#define SGTL5000_BIAS_R_off 0x0 +#define SGTL5000_BIAS_R_2K 0x1 +#define SGTL5000_BIAS_R_4k 0x2 +#define SGTL5000_BIAS_R_8k 0x3 +#define SGTL5000_BIAS_VOLT_MASK 0x0070 +#define SGTL5000_BIAS_VOLT_SHIFT 4 +#define SGTL5000_BIAS_VOLT_WIDTH 3 +#define SGTL5000_MIC_GAIN_MASK 0x0003 +#define SGTL5000_MIC_GAIN_SHIFT 0 +#define SGTL5000_MIC_GAIN_WIDTH 2 + +/* + * SGTL5000_CHIP_LINE_OUT_CTRL + */ +#define SGTL5000_LINE_OUT_CURRENT_MASK 0x0f00 +#define SGTL5000_LINE_OUT_CURRENT_SHIFT 8 +#define SGTL5000_LINE_OUT_CURRENT_WIDTH 4 +#define SGTL5000_LINE_OUT_CURRENT_180u 0x0 +#define SGTL5000_LINE_OUT_CURRENT_270u 0x1 +#define SGTL5000_LINE_OUT_CURRENT_360u 0x3 +#define SGTL5000_LINE_OUT_CURRENT_450u 0x7 +#define SGTL5000_LINE_OUT_CURRENT_540u 0xf +#define SGTL5000_LINE_OUT_GND_MASK 0x003f +#define SGTL5000_LINE_OUT_GND_SHIFT 0 +#define SGTL5000_LINE_OUT_GND_WIDTH 6 +#define SGTL5000_LINE_OUT_GND_BASE 800 /* mv */ +#define SGTL5000_LINE_OUT_GND_STP 25 +#define SGTL5000_LINE_OUT_GND_MAX 0x23 + +/* + * SGTL5000_CHIP_LINE_OUT_VOL + */ +#define SGTL5000_LINE_OUT_VOL_RIGHT_MASK 0x1f00 +#define SGTL5000_LINE_OUT_VOL_RIGHT_SHIFT 8 +#define SGTL5000_LINE_OUT_VOL_RIGHT_WIDTH 5 +#define SGTL5000_LINE_OUT_VOL_LEFT_MASK 0x001f +#define SGTL5000_LINE_OUT_VOL_LEFT_SHIFT 0 +#define SGTL5000_LINE_OUT_VOL_LEFT_WIDTH 5 + +/* + * SGTL5000_CHIP_ANA_POWER + */ +#define SGTL5000_DAC_STEREO 0x4000 +#define SGTL5000_LINREG_SIMPLE_POWERUP 0x2000 +#define SGTL5000_STARTUP_POWERUP 0x1000 +#define SGTL5000_VDDC_CHRGPMP_POWERUP 0x0800 +#define SGTL5000_PLL_POWERUP 0x0400 +#define SGTL5000_LINEREG_D_POWERUP 0x0200 +#define SGTL5000_VCOAMP_POWERUP 0x0100 +#define SGTL5000_VAG_POWERUP 0x0080 +#define SGTL5000_ADC_STEREO 0x0040 +#define SGTL5000_REFTOP_POWERUP 0x0020 +#define SGTL5000_HP_POWERUP 0x0010 +#define SGTL5000_DAC_POWERUP 0x0008 +#define SGTL5000_CAPLESS_HP_POWERUP 0x0004 +#define SGTL5000_ADC_POWERUP 0x0002 +#define SGTL5000_LINE_OUT_POWERUP 0x0001 + +/* + * SGTL5000_CHIP_PLL_CTRL + */ +#define SGTL5000_PLL_INT_DIV_MASK 0xf800 +#define SGTL5000_PLL_INT_DIV_SHIFT 11 +#define SGTL5000_PLL_INT_DIV_WIDTH 5 +#define SGTL5000_PLL_FRAC_DIV_MASK 0x0700 +#define SGTL5000_PLL_FRAC_DIV_SHIFT 0 +#define SGTL5000_PLL_FRAC_DIV_WIDTH 11 + +/* + * SGTL5000_CHIP_CLK_TOP_CTRL + */ +#define SGTL5000_INT_OSC_EN 0x0800 +#define SGTL5000_INPUT_FREQ_DIV2 0x0008 + +/* + * SGTL5000_CHIP_ANA_STATUS + */ +#define SGTL5000_HP_LRSHORT 0x0200 +#define SGTL5000_CAPLESS_SHORT 0x0100 +#define SGTL5000_PLL_LOCKED 0x0010 + +/* + * SGTL5000_CHIP_SHORT_CTRL + */ +#define SGTL5000_LVLADJR_MASK 0x7000 +#define SGTL5000_LVLADJR_SHIFT 12 +#define SGTL5000_LVLADJR_WIDTH 3 +#define SGTL5000_LVLADJL_MASK 0x0700 +#define SGTL5000_LVLADJL_SHIFT 8 +#define SGTL5000_LVLADJL_WIDTH 3 +#define SGTL5000_LVLADJC_MASK 0x0070 +#define SGTL5000_LVLADJC_SHIFT 4 +#define SGTL5000_LVLADJC_WIDTH 3 +#define SGTL5000_LR_SHORT_MOD_MASK 0x000c +#define SGTL5000_LR_SHORT_MOD_SHIFT 2 +#define SGTL5000_LR_SHORT_MOD_WIDTH 2 +#define SGTL5000_CM_SHORT_MOD_MASK 0x0003 +#define SGTL5000_CM_SHORT_MOD_SHIFT 0 +#define SGTL5000_CM_SHORT_MOD_WIDTH 2 + +/* + *SGTL5000_CHIP_ANA_TEST2 + */ +#define SGTL5000_MONO_DAC 0x1000 + +/* + * SGTL5000_DAP_CTRL + */ +#define SGTL5000_DAP_MIX_EN 0x0010 +#define SGTL5000_DAP_EN 0x0001 + +#define SGTL5000_SYSCLK 0x00 +#define SGTL5000_LRCLK 0x01 + +struct sgtl5000_platform_data { + int vddio; /* voltage of VDDIO (mv) */ + int vdda; /* voltage of vdda (mv) */ + int vddd; /* voltage of vddd (mv), 0 if not connected */ +}; + +#endif diff --git a/sound/soc/codecs/stmp378x_codec.c b/sound/soc/codecs/stmp378x_codec.c new file mode 100644 index 000000000000..770163947171 --- /dev/null +++ b/sound/soc/codecs/stmp378x_codec.c @@ -0,0 +1,1051 @@ +/* + * ALSA codec for Freescale STMP378X ADC/DAC + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <asm/dma.h> + +#include <mach/regs-apbx.h> +#include <mach/regs-audioin.h> +#include <mach/regs-audioout.h> +#include <mach/regs-rtc.h> +#include <mach/platform.h> + +#include "stmp378x_codec.h" + +#define BV_AUDIOIN_ADCVOL_SELECT__MIC 0x00 /* missing define */ + +#define STMP378X_VERSION "0.1" +struct stmp378x_codec_priv { + struct clk *clk; + struct snd_soc_codec codec; +}; + +/* + * ALSA API + */ +static void __iomem *adc_regmap[] = { + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_STAT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACSRR, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACDEBUG, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTCTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTSTAT0, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTSTAT1, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DATA, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_VERSION, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_CTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_STAT, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_ADCSRR, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_ADCVOLUME, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_ADCDEBUG, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_ADCVOL, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_MICLINE, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_ANACLKCTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_DATA, +}; + +static u16 stmp378x_audio_regs[ADC_REGNUM]; + +static u8 dac_volumn_control_word[] = { + 0x37, 0x5e, 0x7e, 0x8e, + 0x9e, 0xae, 0xb6, 0xbe, + 0xc6, 0xce, 0xd6, 0xde, + 0xe6, 0xee, 0xf6, 0xfe, +}; + +/* + * ALSA core supports only 16 bit registers. It means we have to simulate it + * by virtually splitting a 32bit ADC/DAC registers into two halves + * high (bits 31:16) and low (bits 15:0). The routins abow detects which part + * of 32bit register is accessed. + */ +static void stmp378x_codec_write_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg < ADC_REGNUM) + cache[reg] = value; +} + +static int stmp378x_codec_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + unsigned int reg_val; + unsigned int mask = 0xffff; + + if (reg >= ADC_REGNUM) + return -EIO; + + stmp378x_codec_write_cache(codec, reg, value); + + if (reg & 0x1) { + mask <<= 16; + value <<= 16; + } + + reg_val = __raw_readl(adc_regmap[reg >> 1]); + reg_val = (reg_val & ~mask) | value; + __raw_writel(reg_val, adc_regmap[reg >> 1]); + + return 0; +} + +static unsigned int stmp378x_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int reg_val; + + if (reg >= ADC_REGNUM) + return -1; + + reg_val = __raw_readl(adc_regmap[reg >> 1]); + if (reg & 1) + reg_val >>= 16; + + return reg_val & 0xffff; +} + +static unsigned int stmp378x_codec_read_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg >= ADC_REGNUM) + return -EINVAL; + return cache[reg]; +} + +static void stmp378x_codec_sync_reg_cache(struct snd_soc_codec *codec) +{ + int reg; + for (reg = 0; reg < ADC_REGNUM; reg += 1) + stmp378x_codec_write_cache(codec, reg, + stmp378x_codec_read(codec, reg)); +} + +static int stmp378x_codec_restore_reg(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int cached_val, hw_val; + + cached_val = stmp378x_codec_read_cache(codec, reg); + hw_val = stmp378x_codec_read(codec, reg); + + if (hw_val != cached_val) + return stmp378x_codec_write(codec, reg, cached_val); + + return 0; +} + +static int dac_info_volsw(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 = 0xf; + return 0; +} + +static int dac_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int reg, l, r; + int i; + + reg = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + + l = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT) >> + BP_AUDIOOUT_DACVOLUME_VOLUME_LEFT; + r = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT) >> + BP_AUDIOOUT_DACVOLUME_VOLUME_RIGHT; + /*Left channel */ + i = 0; + while (i < 16) { + if (l == dac_volumn_control_word[i]) { + ucontrol->value.integer.value[0] = i; + break; + } + i++; + } + if (i == 16) + ucontrol->value.integer.value[0] = i; + /*Right channel */ + i = 0; + while (i < 16) { + if (r == dac_volumn_control_word[i]) { + ucontrol->value.integer.value[1] = i; + break; + } + i++; + } + if (i == 16) + ucontrol->value.integer.value[1] = i; + + return 0; +} + +static int dac_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int reg, l, r; + int i; + + i = ucontrol->value.integer.value[0]; + l = dac_volumn_control_word[i]; + /*Get dac volume for left channel */ + reg = BF(l, AUDIOOUT_DACVOLUME_VOLUME_LEFT); + + i = ucontrol->value.integer.value[1]; + r = dac_volumn_control_word[i]; + /*Get dac volume for right channel */ + reg = reg | BF(r, AUDIOOUT_DACVOLUME_VOLUME_RIGHT); + + /*Clear left/right dac volume */ + stmp3xxx_clearl(BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT | + BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + stmp3xxx_setl(reg, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + + return 0; +} + +static const char *stmp378x_codec_adc_input_sel[] = + { "Mic", "Line In 1", "Head Phone", "Line In 2" }; + +static const char *stmp378x_codec_hp_output_sel[] = { "DAC Out", "Line In 1" }; + +static const char *stmp378x_codec_adc_3d_sel[] = + { "Off", "Low", "Medium", "High" }; + +static const struct soc_enum stmp378x_codec_enum[] = { + SOC_ENUM_SINGLE(ADC_ADCVOL_L, 12, 4, stmp378x_codec_adc_input_sel), + SOC_ENUM_SINGLE(ADC_ADCVOL_L, 4, 4, stmp378x_codec_adc_input_sel), + SOC_ENUM_SINGLE(DAC_HPVOL_H, 0, 2, stmp378x_codec_hp_output_sel), + SOC_ENUM_SINGLE(DAC_CTRL_L, 8, 4, stmp378x_codec_adc_3d_sel), +}; + +/* Codec controls */ +static const struct snd_kcontrol_new stmp378x_snd_controls[] = { + /* Playback Volume */ + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = dac_info_volsw, + .get = dac_get_volsw, + .put = dac_put_volsw, + }, + + SOC_DOUBLE_R("DAC Playback Switch", + DAC_VOLUME_H, DAC_VOLUME_L, 8, 0x01, 1), + SOC_DOUBLE("HP Playback Volume", DAC_HPVOL_L, 8, 0, 0x7F, 1), + SOC_SINGLE("HP Playback Switch", DAC_HPVOL_H, 8, 0x1, 1), + SOC_SINGLE("Speaker Playback Switch", DAC_SPEAKERCTRL_H, 8, 0x1, 1), + + /* Capture Volume */ + SOC_DOUBLE_R("ADC Capture Volume", + ADC_VOLUME_H, ADC_VOLUME_L, 0, 0xFF, 0), + SOC_DOUBLE("ADC PGA Capture Volume", ADC_ADCVOL_L, 8, 0, 0x0F, 0), + SOC_SINGLE("ADC PGA Capture Switch", ADC_ADCVOL_H, 8, 0x1, 1), + SOC_SINGLE("Mic PGA Capture Volume", ADC_MICLINE_L, 0, 0x03, 0), + + /* Virtual 3D effect */ + SOC_ENUM("3D effect", stmp378x_codec_enum[3]), +}; + +/* Left ADC Mux */ +static const struct snd_kcontrol_new stmp378x_left_adc_controls = +SOC_DAPM_ENUM("Route", stmp378x_codec_enum[0]); + +/* Right ADC Mux */ +static const struct snd_kcontrol_new stmp378x_right_adc_controls = +SOC_DAPM_ENUM("Route", stmp378x_codec_enum[1]); + +/* Head Phone Mux */ +static const struct snd_kcontrol_new stmp378x_hp_controls = +SOC_DAPM_ENUM("Route", stmp378x_codec_enum[2]); + +static const struct snd_soc_dapm_widget stmp378x_codec_widgets[] = { + + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", DAC_PWRDN_L, 8, 1), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", DAC_PWRDN_H, 0, 1), + + SND_SOC_DAPM_DAC("DAC", "Playback", DAC_PWRDN_L, 12, 1), + + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &stmp378x_left_adc_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &stmp378x_right_adc_controls), + SND_SOC_DAPM_MUX("HP Mux", SND_SOC_NOPM, 0, 0, + &stmp378x_hp_controls), + + SND_SOC_DAPM_PGA("HP_AMP", DAC_PWRDN_L, 0, 1, NULL, 0), + + SND_SOC_DAPM_PGA("HP_CAPLESS", DAC_PWRDN_L, 4, 1, NULL, 0), + + SND_SOC_DAPM_PGA("SPK_AMP", DAC_PWRDN_H, 8, 1, NULL, 0), + + SND_SOC_DAPM_INPUT("LINE1L"), + SND_SOC_DAPM_INPUT("LINE1R"), + SND_SOC_DAPM_INPUT("LINE2L"), + SND_SOC_DAPM_INPUT("LINE2R"), + SND_SOC_DAPM_INPUT("MIC"), + + SND_SOC_DAPM_OUTPUT("SPEAKER"), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + + /* Left ADC Mux */ + {"Left ADC Mux", "Mic", "MIC"}, + {"Left ADC Mux", "Line In 1", "LINE1L"}, + {"Left ADC Mux", "Line In 2", "LINE2L"}, + {"Left ADC Mux", "Head Phone", "HPL"}, + + /* Right ADC Mux */ + {"Right ADC Mux", "Mic", "MIC"}, + {"Right ADC Mux", "Line In 1", "LINE1R"}, + {"Right ADC Mux", "Line In 2", "LINE2R"}, + {"Right ADC Mux", "Head Phone", "HPR"}, + + /* ADC */ + {"Left ADC", NULL, "Left ADC Mux"}, + {"Right ADC", NULL, "Right ADC Mux"}, + + /* HP Mux */ + {"HP Mux", "DAC Out", "DAC"}, + {"HP Mux", "Line In 1", "LINE1L"}, + {"HP Mux", "Line In 1", "LINE1R"}, + + /* HP output */ + {"HP_CAPLESS", NULL, "HP Mux"}, + {"HP_AMP", NULL, "HP_CAPLESS"}, + {"HPR", NULL, "HP_AMP"}, + {"HPL", NULL, "HP_AMP"}, + + /* Speaker amp */ + {"SPK_AMP", NULL, "DAC"}, + {"SPEAKER", NULL, "SPK_AMP"}, +}; + +static int stmp378x_codec_add_widgets(struct snd_soc_codec *codec) +{ + int ret = 0; + + snd_soc_dapm_new_controls(codec, stmp378x_codec_widgets, + ARRAY_SIZE(stmp378x_codec_widgets)); + + if (ret) { + dev_err(codec->dev, "dapm control register failed\n"); + return ret; + } + /* set up audio path interconnects */ + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + if (ret) { + dev_err(codec->dev, "DAPM route register failed\n"); + return ret; + } + + return snd_soc_dapm_new_widgets(codec); +} + +struct dac_srr { + u32 rate; + u32 basemult; + u32 src_hold; + u32 src_int; + u32 src_frac; +}; + +static struct dac_srr srr_values[] = { + {192000, 0x4, 0x0, 0x0F, 0x13FF}, + {176400, 0x4, 0x0, 0x11, 0x0037}, + {128000, 0x4, 0x0, 0x17, 0x0E00}, + {96000, 0x2, 0x0, 0x0F, 0x13FF}, + {88200, 0x2, 0x0, 0x11, 0x0037}, + {64000, 0x2, 0x0, 0x17, 0x0E00}, + {48000, 0x1, 0x0, 0x0F, 0x13FF}, + {44100, 0x1, 0x0, 0x11, 0x0037}, + {32000, 0x1, 0x0, 0x17, 0x0E00}, + {24000, 0x1, 0x1, 0x0F, 0x13FF}, + {22050, 0x1, 0x1, 0x11, 0x0037}, + {16000, 0x1, 0x1, 0x17, 0x0E00}, + {12000, 0x1, 0x3, 0x0F, 0x13FF}, + {11025, 0x1, 0x3, 0x11, 0x0037}, + {8000, 0x1, 0x3, 0x17, 0x0E00} +}; + +static inline int get_srr_values(int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(srr_values); i++) + if (srr_values[i].rate == rate) + return i; + + return -1; +} + +static int stmp378x_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + 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; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int i; + u32 srr_value = 0; + u32 src_hold = 0; + + i = get_srr_values(params_rate(params)); + if (i < 0) + dev_warn(socdev->dev, "%s doesn't support rate %d\n", + codec->name, params_rate(params)); + else { + src_hold = srr_values[i].src_hold; + + srr_value = + BF(srr_values[i].basemult, AUDIOOUT_DACSRR_BASEMULT) | + BF(srr_values[i].src_int, AUDIOOUT_DACSRR_SRC_INT) | + BF(srr_values[i].src_frac, AUDIOOUT_DACSRR_SRC_FRAC) | + BF(src_hold, AUDIOOUT_DACSRR_SRC_HOLD); + + if (playback) + __raw_writel(srr_value, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACSRR); + else + __raw_writel(srr_value, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCSRR); + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + if (playback) + stmp3xxx_setl(BM_AUDIOOUT_CTRL_WORD_LENGTH, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + else + stmp3xxx_setl(BM_AUDIOIN_CTRL_WORD_LENGTH, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + + break; + + case SNDRV_PCM_FORMAT_S32_LE: + if (playback) + stmp3xxx_clearl(BM_AUDIOOUT_CTRL_WORD_LENGTH, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + else + stmp3xxx_clearl(BM_AUDIOIN_CTRL_WORD_LENGTH, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + + break; + + default: + dev_warn(socdev->dev, "%s doesn't support format %d\n", + codec->name, params_format(params)); + + } + + return 0; +} + +static int stmp378x_codec_dig_mute(struct snd_soc_dai *dai, int mute) +{ + u32 dac_mask = BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | + BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT; + + if (mute) { + stmp3xxx_setl(dac_mask, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + stmp3xxx_setl(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); + stmp3xxx_setl(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL); + } else { + stmp3xxx_clearl(dac_mask, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + stmp3xxx_clearl(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); + stmp3xxx_clearl(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL); + } + return 0; +} + +/* + * Codec initialization + */ +#define VAG_BASE_VALUE ((1400/2 - 625)/25) +static void stmp378x_codec_dac_set_vag(void) +{ + u32 refctrl_val = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); + + refctrl_val &= ~(BM_AUDIOOUT_REFCTRL_VAG_VAL); + refctrl_val &= ~(BM_AUDIOOUT_REFCTRL_VBG_ADJ); + refctrl_val |= BF(VAG_BASE_VALUE, AUDIOOUT_REFCTRL_VAG_VAL) | + BM_AUDIOOUT_REFCTRL_ADJ_VAG | + BF(0xF, AUDIOOUT_REFCTRL_ADC_REFVAL) | + BM_AUDIOOUT_REFCTRL_ADJ_ADC | + BF(0x3, AUDIOOUT_REFCTRL_VBG_ADJ) | BM_AUDIOOUT_REFCTRL_RAISE_REF; + + __raw_writel(refctrl_val, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); +} + +static void +stmp378x_codec_dac_power_on(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Ungate DAC clocks */ + stmp3xxx_clearl(BM_AUDIOOUT_CTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + stmp3xxx_clearl(BM_AUDIOOUT_ANACLKCTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL); + + /* 16 bit word length */ + stmp3xxx_setl(BM_AUDIOOUT_CTRL_WORD_LENGTH, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + + /* Update DAC volume over zero crossings */ + stmp3xxx_setl(BM_AUDIOOUT_DACVOLUME_EN_ZCD, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + /* Mute DAC */ + stmp3xxx_setl(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | + BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + + /* Update HP volume over zero crossings */ + stmp3xxx_setl(BM_AUDIOOUT_HPVOL_EN_MSTR_ZCD, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); + + /* Prepare powering up HP output */ + stmp3xxx_setl(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL); + stmp3xxx_setl(BF(0x2, RTC_PERSISTENT0_SPARE_ANALOG), + REGS_RTC_BASE + HW_RTC_PERSISTENT0); + stmp3xxx_setl(BM_AUDIOOUT_ANACTRL_HP_CLASSAB, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL); + stmp3xxx_clearl(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL); + /* Mute HP output */ + stmp3xxx_setl(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); + + /* Mute speaker amp */ + stmp3xxx_setl(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL); +} + +static void +stmp378x_codec_dac_power_down(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Disable class AB */ + stmp3xxx_clearl(BM_AUDIOOUT_ANACTRL_HP_CLASSAB, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL); + + /* Set hold to ground */ + stmp3xxx_setl(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL); + + /* Mute HP output */ + stmp3xxx_setl(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); + /* Power down HP output */ + stmp3xxx_setl(BM_AUDIOOUT_PWRDN_HEADPHONE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN); + + /* Mute speaker amp */ + stmp3xxx_setl(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL); + /* Power down speaker amp */ + stmp3xxx_setl(BM_AUDIOOUT_PWRDN_SPEAKER, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN); + + /* Mute DAC */ + stmp3xxx_setl(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | + BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + /* Power down DAC */ + stmp3xxx_setl(BM_AUDIOOUT_PWRDN_DAC, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN); + + /* Gate DAC clocks */ + stmp3xxx_setl(BM_AUDIOOUT_ANACLKCTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL); + stmp3xxx_setl(BM_AUDIOOUT_CTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); +} + +static void +stmp378x_codec_adc_power_on(struct stmp378x_codec_priv *stmp378x_adc) +{ + u32 reg; + + /* Ungate ADC clocks */ + stmp3xxx_clearl(BM_AUDIOIN_CTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + stmp3xxx_clearl(BM_AUDIOIN_ANACLKCTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL); + + /* 16 bit word length */ + stmp3xxx_setl(BM_AUDIOIN_CTRL_WORD_LENGTH, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + + /* Unmute ADC channels */ + stmp3xxx_clearl(BM_AUDIOIN_ADCVOL_MUTE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + + /* + * The MUTE_LEFT and MUTE_RIGHT fields need to be cleared. + * They aren't presented in the datasheet, so this is hardcode. + */ + stmp3xxx_clearl(0x01000100, REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME); + + /* Set the Input channel gain 3dB */ + stmp3xxx_clearl(BM_AUDIOIN_ADCVOL_GAIN_LEFT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + stmp3xxx_clearl(BM_AUDIOIN_ADCVOL_GAIN_RIGHT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + stmp3xxx_setl(BF(2, AUDIOIN_ADCVOL_GAIN_LEFT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + stmp3xxx_setl(BF(2, AUDIOIN_ADCVOL_GAIN_RIGHT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + + /* Select default input - Microphone */ + stmp3xxx_clearl(BM_AUDIOIN_ADCVOL_SELECT_LEFT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + stmp3xxx_clearl(BM_AUDIOIN_ADCVOL_SELECT_RIGHT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + stmp3xxx_setl(BF + (BV_AUDIOIN_ADCVOL_SELECT__MIC, + AUDIOIN_ADCVOL_SELECT_LEFT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + stmp3xxx_setl(BF + (BV_AUDIOIN_ADCVOL_SELECT__MIC, + AUDIOIN_ADCVOL_SELECT_RIGHT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + + /* Supply bias voltage to microphone */ + stmp3xxx_setl(BF(2, AUDIOIN_MICLINE_MIC_RESISTOR), + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE); + stmp3xxx_setl(BM_AUDIOIN_MICLINE_MIC_SELECT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE); + + /* Set max ADC volume */ + reg = __raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME); + reg &= ~BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT; + reg &= ~BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT; + reg |= BF(ADC_VOLUME_MAX, AUDIOIN_ADCVOLUME_VOLUME_LEFT); + reg |= BF(ADC_VOLUME_MAX, AUDIOIN_ADCVOLUME_VOLUME_RIGHT); + __raw_writel(reg, REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME); +} + +static void +stmp378x_codec_adc_power_down(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Mute ADC channels */ + stmp3xxx_setl(BM_AUDIOIN_ADCVOL_MUTE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + + /* Power Down ADC */ + stmp3xxx_setl(BM_AUDIOOUT_PWRDN_ADC | BM_AUDIOOUT_PWRDN_RIGHT_ADC, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN); + + /* Gate ADC clocks */ + stmp3xxx_setl(BM_AUDIOIN_CTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + stmp3xxx_setl(BM_AUDIOIN_ANACLKCTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL); + + /* Disable bias voltage to microphone */ + stmp3xxx_setl(BF(0, AUDIOIN_MICLINE_MIC_RESISTOR), + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE); +} + +static void stmp378x_codec_dac_enable(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Move DAC codec out of reset */ + stmp3xxx_clearl(BM_AUDIOOUT_CTRL_SFTRST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + + /* Reduce analog power */ + stmp3xxx_clearl(BM_AUDIOOUT_TEST_HP_I1_ADJ, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST); + stmp3xxx_setl(BF(0x1, AUDIOOUT_TEST_HP_I1_ADJ), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST); + stmp3xxx_setl(BM_AUDIOOUT_REFCTRL_LOW_PWR, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); + stmp3xxx_setl(BM_AUDIOOUT_REFCTRL_XTAL_BGR_BIAS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); + stmp3xxx_clearl(BM_AUDIOOUT_REFCTRL_BIAS_CTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); + stmp3xxx_clearl(BF(0x1, AUDIOOUT_REFCTRL_BIAS_CTRL), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); + + /* Set Vag value */ + stmp378x_codec_dac_set_vag(); + + /* Power on DAC codec */ + stmp378x_codec_dac_power_on(stmp378x_adc); +} + +static void stmp378x_codec_dac_disable(struct stmp378x_codec_priv *stmp378x_adc) +{ + stmp378x_codec_dac_power_down(stmp378x_adc); +} + +static void stmp378x_codec_adc_enable(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Move ADC codec out of reset */ + stmp3xxx_clearl(BM_AUDIOIN_CTRL_SFTRST, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + + /* Power on ADC codec */ + stmp378x_codec_adc_power_on(stmp378x_adc); +} + +static void stmp378x_codec_adc_disable(struct stmp378x_codec_priv *stmp378x_adc) +{ + stmp378x_codec_adc_power_down(stmp378x_adc); +} + +static void stmp378x_codec_startup(struct snd_soc_codec *codec) +{ + struct stmp378x_codec_priv *stmp378x_adc = codec->private_data; + + /* Soft reset DAC block */ + stmp3xxx_setl(BM_AUDIOOUT_CTRL_SFTRST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + while (!(__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL) & + BM_AUDIOOUT_CTRL_CLKGATE)); + + /* Soft reset ADC block */ + stmp3xxx_setl(BM_AUDIOIN_CTRL_SFTRST, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + while (!(__raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL) & + BM_AUDIOIN_CTRL_CLKGATE)) ; + + stmp378x_codec_dac_enable(stmp378x_adc); + stmp378x_codec_adc_enable(stmp378x_adc); + + /*Sync regs and cache */ + stmp378x_codec_sync_reg_cache(codec); + + snd_soc_add_controls(codec, stmp378x_snd_controls, + ARRAY_SIZE(stmp378x_snd_controls)); + + stmp378x_codec_add_widgets(codec); +} + +static void stmp378x_codec_stop(struct snd_soc_codec *codec) +{ + struct stmp378x_codec_priv *stmp378x_adc = codec->private_data; + stmp378x_codec_dac_disable(stmp378x_adc); + stmp378x_codec_adc_disable(stmp378x_adc); +} + +#define STMP378X_ADC_RATES SNDRV_PCM_RATE_8000_192000 +#define STMP378X_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops stmp378x_dai_ops = { + .hw_params = stmp378x_codec_hw_params, + .digital_mute = stmp378x_codec_dig_mute, +}; + +struct snd_soc_dai stmp378x_codec_dai = { + .name = "stmp378x adc/dac", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = STMP378X_ADC_RATES, + .formats = STMP378X_ADC_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = STMP378X_ADC_RATES, + .formats = STMP378X_ADC_FORMATS, + }, + .ops = &stmp378x_dai_ops, +}; + +EXPORT_SYMBOL_GPL(stmp378x_codec_dai); + +static struct snd_soc_codec *stmp378x_codec; + +static int stmp378x_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + socdev->card->codec = stmp378x_codec; + codec = stmp378x_codec; + + /* register pcms */ + 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; + } + + stmp378x_codec_startup(codec); + + /* Register the socdev */ + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card\n"); + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + return ret; + } + + return 0; +} + +static int stmp378x_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + stmp378x_codec_stop(codec); + + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + + return 0; +} + +#ifdef CONFIG_PM +static int stmp378x_codec_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + struct stmp378x_codec_priv *stmp378x_adc; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + stmp378x_adc = codec->private_data; + + stmp378x_codec_dac_disable(stmp378x_adc); + stmp378x_codec_adc_disable(stmp378x_adc); + clk_disable(stmp378x_adc->clk); + ret = 0; + +out: + return ret; +} + +static int stmp378x_codec_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + struct stmp378x_codec_priv *stmp378x_adc; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + stmp378x_adc = codec->private_data; + clk_enable(stmp378x_adc->clk); + + /* Soft reset DAC block */ + stmp3xxx_setl(BM_AUDIOOUT_CTRL_SFTRST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + while (! + (__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL) & + BM_AUDIOOUT_CTRL_CLKGATE)) ; + + /* Soft reset ADC block */ + stmp3xxx_setl(BM_AUDIOIN_CTRL_SFTRST, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + while (! + (__raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL) & + BM_AUDIOIN_CTRL_CLKGATE)) ; + + stmp378x_codec_dac_enable(stmp378x_adc); + stmp378x_codec_adc_enable(stmp378x_adc); + + /*restore registers relevant to amixer controls */ + stmp378x_codec_restore_reg(codec, DAC_CTRL_L); + stmp378x_codec_restore_reg(codec, DAC_VOLUME_L); + stmp378x_codec_restore_reg(codec, DAC_VOLUME_H); + stmp378x_codec_restore_reg(codec, DAC_HPVOL_L); + stmp378x_codec_restore_reg(codec, DAC_HPVOL_H); + stmp378x_codec_restore_reg(codec, DAC_SPEAKERCTRL_H); + stmp378x_codec_restore_reg(codec, ADC_VOLUME_L); + stmp378x_codec_restore_reg(codec, ADC_VOLUME_H); + stmp378x_codec_restore_reg(codec, ADC_ADCVOL_L); + stmp378x_codec_restore_reg(codec, ADC_ADCVOL_H); + stmp378x_codec_restore_reg(codec, ADC_MICLINE_L); + + ret = 0; + +out: + return ret; +} +#else +#define stmp378x_codec_suspend NULL +#define stmp378x_codec_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_codec_device soc_codec_dev_stmp378x = { + .probe = stmp378x_codec_probe, + .remove = stmp378x_codec_remove, + .suspend = stmp378x_codec_suspend, + .resume = stmp378x_codec_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_stmp378x); + +/* codec register, unregister function */ +static int __init stmp378x_audio_probe(struct platform_device *pdev) +{ + struct stmp378x_codec_priv *stmp378x_adc; + struct snd_soc_codec *codec; + int ret = 0; + + dev_info(&pdev->dev, + "STMP378X ADC/DAC Audio Codec %s\n", STMP378X_VERSION); + + stmp378x_adc = kzalloc(sizeof(struct stmp378x_codec_priv), GFP_KERNEL); + if (stmp378x_adc == NULL) + return -ENOMEM; + + codec = &stmp378x_adc->codec; + codec->dev = &pdev->dev; + codec->name = "stmp378x adc/dac"; + codec->owner = THIS_MODULE; + codec->private_data = stmp378x_adc; + codec->read = stmp378x_codec_read; + codec->write = stmp378x_codec_write; + codec->dai = &stmp378x_codec_dai; + codec->num_dai = 1; + codec->reg_cache_size = sizeof(stmp378x_audio_regs) >> 1; + codec->reg_cache_step = 1; + codec->reg_cache = (void *)&stmp378x_audio_regs; + + platform_set_drvdata(pdev, stmp378x_adc); + + stmp378x_codec = codec; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + /* Turn on audio clock */ + stmp378x_adc->clk = clk_get(&pdev->dev, "audio"); + if (IS_ERR(stmp378x_adc->clk)) { + ret = PTR_ERR(stmp378x_adc->clk); + dev_err(&pdev->dev, "%s: Clocks initialization failed\n", __func__); + goto clk_err; + } + clk_enable(stmp378x_adc->clk); + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(&pdev->dev, "failed to register card\n"); + goto card_err; + } + + ret = snd_soc_register_dai(&stmp378x_codec_dai); + if (ret) { + dev_err(&pdev->dev, "failed to register codec dai\n"); + goto dai_err; + } + + return 0; + +dai_err: + snd_soc_unregister_codec(codec); +card_err: + clk_disable(stmp378x_adc->clk); + clk_put(stmp378x_adc->clk); +clk_err: + kfree(stmp378x_adc); + return ret; +} + +static int __devexit stmp378x_audio_remove(struct platform_device *pdev) +{ + struct stmp378x_codec_priv *stmp378x_adc = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = &stmp378x_adc->codec; + + snd_soc_unregister_codec(codec); + + clk_disable(stmp378x_adc->clk); + clk_put(stmp378x_adc->clk); + + kfree(stmp378x_adc); + + return 0; +} + +struct platform_driver stmp378x_audio_driver = { + .driver = { + .name = "stmp378x-audio", + }, + .probe = stmp378x_audio_probe, + .remove = __devexit_p(stmp378x_audio_remove), +}; + +static int __init stmp378x_codec_init(void) +{ + return platform_driver_register(&stmp378x_audio_driver); +} + +static void __exit stmp378x_codec_exit(void) +{ + return platform_driver_unregister(&stmp378x_audio_driver); +} + +module_init(stmp378x_codec_init); +module_exit(stmp378x_codec_exit); + +MODULE_DESCRIPTION("STMP378X ADC/DAC codec"); +MODULE_AUTHOR("Vladislav Buzov"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/stmp378x_codec.h b/sound/soc/codecs/stmp378x_codec.h new file mode 100644 index 000000000000..80fce7273126 --- /dev/null +++ b/sound/soc/codecs/stmp378x_codec.h @@ -0,0 +1,87 @@ +/* + * ALSA codec for Freescale STMP378X + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __STMP378X_CODEC_H +#define __STMP378X_CODEC_H + +#define DAC_CTRL_L 0 +#define DAC_CTRL_H 1 +#define DAC_STAT_L 2 +#define DAC_STAT_H 3 +#define DAC_SRR_L 4 +#define DAC_VOLUME_L 6 +#define DAC_VOLUME_H 7 +#define DAC_DEBUG_L 8 +#define DAC_DEBUG_H 9 +#define DAC_HPVOL_L 10 +#define DAC_HPVOL_H 11 +#define DAC_PWRDN_L 12 +#define DAC_PWRDN_H 13 +#define DAC_REFCTRL_L 14 +#define DAC_REFCTRL_H 15 +#define DAC_ANACTRL_L 16 +#define DAC_ANACTRL_H 17 +#define DAC_TEST_L 18 +#define DAC_TEST_H 19 +#define DAC_BISTCTRL_L 20 +#define DAC_BISTCTRL_H 21 +#define DAC_BISTSTAT0_L 22 +#define DAC_BISTSTAT0_H 23 +#define DAC_BISTSTAT1_L 24 +#define DAC_BISTSTAT1_H 25 +#define DAC_ANACLKCTRL_L 26 +#define DAC_ANACLKCTRL_H 27 +#define DAC_DATA_L 28 +#define DAC_DATA_H 29 +#define DAC_SPEAKERCTRL_L 30 +#define DAC_SPEAKERCTRL_H 31 +#define DAC_VERSION_L 32 +#define DAC_VERSION_H 33 +#define ADC_CTRL_L 34 +#define ADC_CTRL_H 35 +#define ADC_STAT_L 36 +#define ADC_STAT_H 37 +#define ADC_SRR_L 38 +#define ADC_SRR_H 39 +#define ADC_VOLUME_L 40 +#define ADC_VOLUME_H 41 +#define ADC_DEBUG_L 42 +#define ADC_DEBUG_H 43 +#define ADC_ADCVOL_L 44 +#define ADC_ADCVOL_H 45 +#define ADC_MICLINE_L 46 +#define ADC_MICLINE_H 47 +#define ADC_ANACLKCTRL_L 48 +#define ADC_ANACLKCTRL_H 49 +#define ADC_DATA_L 50 +#define ADC_DATA_H 51 + +#define ADC_REGNUM 52 + +#define DAC_VOLUME_MIN 0x37 +#define DAC_VOLUME_MAX 0xFE +#define ADC_VOLUME_MIN 0x37 +#define ADC_VOLUME_MAX 0xFE +#define HP_VOLUME_MAX 0x0 +#define HP_VOLUME_MIN 0x7F +#define LO_VOLUME_MAX 0x0 +#define LO_VOLUME_MIN 0x1F + +extern struct snd_soc_dai stmp378x_codec_dai; +extern struct snd_soc_codec_device soc_codec_dev_stmp378x; + +#endif /* __STMP378X_CODEC_H */ diff --git a/sound/soc/codecs/stmp3xxx_spdif.c b/sound/soc/codecs/stmp3xxx_spdif.c new file mode 100644 index 000000000000..db5627e5e156 --- /dev/null +++ b/sound/soc/codecs/stmp3xxx_spdif.c @@ -0,0 +1,449 @@ +/* + * ALSA SoC STMP3xxx SPDIF transmitter driver + * + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * Copyright 2008-2009 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <asm/dma.h> + +#include <mach/regs-spdif.h> +#include <mach/platform.h> + +#include "stmp3xxx_spdif.h" + +#define STMP3XXX_VERSION "0.1" +struct stmp3xxx_codec_priv { + struct clk *clk; + struct snd_soc_codec codec; +}; + +/* + * ALSA API + */ +static void __iomem *spdif_regmap[] = { + REGS_SPDIF_BASE + HW_SPDIF_CTRL, + REGS_SPDIF_BASE + HW_SPDIF_STAT, + REGS_SPDIF_BASE + HW_SPDIF_FRAMECTRL, + REGS_SPDIF_BASE + HW_SPDIF_SRR, + REGS_SPDIF_BASE + HW_SPDIF_DEBUG, + REGS_SPDIF_BASE + HW_SPDIF_DATA, + REGS_SPDIF_BASE + HW_SPDIF_VERSION, +}; + +/* + * ALSA core supports only 16 bit registers. It means we have to simulate it + * by virtually splitting a 32bit SPDIF registers into two halves + * high (bits 31:16) and low (bits 15:0). The routins abow detects which part + * of 32bit register is accessed. + */ +static int stmp3xxx_codec_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + unsigned int reg_val; + unsigned int mask = 0xffff; + + if (reg >= SPDIF_REGNUM) + return -EIO; + + if (reg & 0x1) { + mask <<= 16; + value <<= 16; + } + + reg_val = __raw_readl(spdif_regmap[reg >> 1]); + reg_val = (reg_val & ~mask) | value; + __raw_writel(reg_val, spdif_regmap[reg >> 1]); + + return 0; +} + +static unsigned int stmp3xxx_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int reg_val; + + if (reg >= SPDIF_REGNUM) + return -1; + + reg_val = __raw_readl(spdif_regmap[reg >> 1]); + if (reg & 1) + reg_val >>= 16; + + return reg_val & 0xffff; +} + +/* Codec controls */ +static const struct snd_kcontrol_new stmp3xxx_snd_controls[] = { + SOC_SINGLE("PRO", SPDIF_FRAMECTRL_L, 0, 0x1, 0), + SOC_SINGLE("AUDIO", SPDIF_FRAMECTRL_L, 1, 0x1, 0), + SOC_SINGLE("COPY", SPDIF_FRAMECTRL_L, 2, 0x1, 0), + SOC_SINGLE("PRE", SPDIF_FRAMECTRL_L, 3, 0x1, 0), + SOC_SINGLE("CC", SPDIF_FRAMECTRL_L, 4, 0x7F, 0), + SOC_SINGLE("L", SPDIF_FRAMECTRL_L, 12, 0x1, 0), + SOC_SINGLE("V", SPDIF_FRAMECTRL_L, 13, 0x1, 0), + SOC_SINGLE("USER DATA", SPDIF_FRAMECTRL_L, 14, 0x1, 0), + SOC_SINGLE("AUTO MUTE", SPDIF_FRAMECTRL_H, 16, 0x1, 0), + SOC_SINGLE("V CONFIG", SPDIF_FRAMECTRL_H, 17, 0x1, 0), +}; + +struct spdif_srr { + u32 rate; + u32 basemult; + u32 rate_factor; +}; + +static struct spdif_srr srr_values[] = { + {96000, 0x2, 0x0BB80}, + {88200, 0x2, 0x0AC44}, + {64000, 0x2, 0x07D00}, + {48000, 0x1, 0x0BB80}, + {44100, 0x1, 0x0AC44}, + {32000, 0x1, 0x07D00}, +}; + +static inline int get_srr_values(int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(srr_values); i++) + if (srr_values[i].rate == rate) + return i; + + return -1; +} + +static int stmp3xxx_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + 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; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int i; + u32 srr_value = 0; + u32 basemult; + + i = get_srr_values(params_rate(params)); + if (i < 0) + printk(KERN_WARNING "%s doesn't support rate %d\n", + codec->name, params_rate(params)); + else { + basemult = srr_values[i].basemult; + + srr_value = BF(basemult, SPDIF_SRR_BASEMULT) | + BF(srr_values[i].rate_factor, SPDIF_SRR_RATE); + + if (playback) + __raw_writel(srr_value, REGS_SPDIF_BASE + HW_SPDIF_SRR); + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + if (playback) + stmp3xxx_setl(BM_SPDIF_CTRL_WORD_LENGTH, REGS_SPDIF_BASE + HW_SPDIF_CTRL); + break; + case SNDRV_PCM_FORMAT_S32_LE: + if (playback) + stmp3xxx_clearl(BM_SPDIF_CTRL_WORD_LENGTH, REGS_SPDIF_BASE + HW_SPDIF_CTRL); + break; + default: + printk(KERN_WARNING "%s doesn't support format %d\n", + codec->name, params_format(params)); + } + + return 0; +} + +static void +stmp3xxx_codec_spdif_enable(struct stmp3xxx_codec_priv *stmp3xxx_spdif) +{ + /* Move SPDIF codec out of reset */ + stmp3xxx_clearl(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL); + + /* Ungate SPDIF clocks */ + stmp3xxx_clearl(BM_SPDIF_CTRL_CLKGATE, REGS_SPDIF_BASE + HW_SPDIF_CTRL); + + /* 16 bit word length */ + stmp3xxx_setl(BM_SPDIF_CTRL_WORD_LENGTH, REGS_SPDIF_BASE + HW_SPDIF_CTRL); +} + +static void +stmp3xxx_codec_spdif_disable(struct stmp3xxx_codec_priv *stmp3xxx_spdif) +{ + /* Gate SPDIF clocks */ + stmp3xxx_setl(BM_SPDIF_CTRL_CLKGATE, REGS_SPDIF_BASE + HW_SPDIF_CTRL); +} + +static void stmp3xxx_codec_init(struct snd_soc_codec *codec) +{ + struct stmp3xxx_codec_priv *stmp3xxx_spdif = codec->private_data; + + /* Soft reset SPDIF block */ + stmp3xxx_setl(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL); + while (!(__raw_readl(REGS_SPDIF_BASE + HW_SPDIF_CTRL) & BM_SPDIF_CTRL_CLKGATE)); + + stmp3xxx_codec_spdif_enable(stmp3xxx_spdif); + + snd_soc_add_controls(codec, stmp3xxx_snd_controls, + ARRAY_SIZE(stmp3xxx_snd_controls)); +} + +static void stmp3xxx_codec_exit(struct snd_soc_codec *codec) +{ + struct stmp3xxx_codec_priv *stmp3xxx_spdif = codec->private_data; + + stmp3xxx_codec_spdif_disable(stmp3xxx_spdif); +} + +#define STMP3XXX_SPDIF_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) +#define STMP3XXX_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai_ops stmp3xxx_spdif_codec_dai_ops = { + .hw_params = stmp3xxx_codec_hw_params, +}; + +struct snd_soc_dai stmp3xxx_spdif_codec_dai = { + .name = "stmp3xxx spdif", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = STMP3XXX_SPDIF_RATES, + .formats = STMP3XXX_SPDIF_FORMATS, + }, + .ops = &stmp3xxx_spdif_codec_dai_ops, +}; +EXPORT_SYMBOL_GPL(stmp3xxx_spdif_codec_dai); + +static struct snd_soc_codec *stmp3xxx_spdif_codec; + +static int stmp3xxx_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + socdev->card->codec = stmp3xxx_spdif_codec; + codec = stmp3xxx_spdif_codec; + + /* register pcms */ + 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; + } + + stmp3xxx_codec_init(codec); + + /* Register the socdev */ + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card\n"); + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + return ret; + } + + return 0; +} + +static int stmp3xxx_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + stmp3xxx_codec_exit(codec); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +#ifdef CONFIG_PM +static int stmp3xxx_codec_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + struct stmp3xxx_codec_priv *stmp3xxx_spdif; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + stmp3xxx_spdif = codec->private_data; + + stmp3xxx_codec_spdif_disable(stmp3xxx_spdif); + clk_disable(stmp3xxx_spdif->clk); + ret = 0; + +out: + return ret; +} + +static int stmp3xxx_codec_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + struct stmp3xxx_codec_priv *stmp3xxx_spdif; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + stmp3xxx_spdif = codec->private_data; + clk_enable(stmp3xxx_spdif->clk); + + /* Soft reset SPDIF block */ + stmp3xxx_setl(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL); + while (!(__raw_readl(REGS_SPDIF_BASE + HW_SPDIF_CTRL) & BM_SPDIF_CTRL_CLKGATE)); + + stmp3xxx_codec_spdif_enable(stmp3xxx_spdif); + + ret = 0; + +out: + return ret; +} +#else +#define stmp3xxx_codec_suspend NULL +#define stmp3xxx_codec_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_codec_device soc_spdif_codec_dev_stmp3xxx = { + .probe = stmp3xxx_codec_probe, + .remove = stmp3xxx_codec_remove, + .suspend = stmp3xxx_codec_suspend, + .resume = stmp3xxx_codec_resume, +}; +EXPORT_SYMBOL_GPL(soc_spdif_codec_dev_stmp3xxx); + +static int __init stmp3xxx_spdif_probe(struct platform_device *pdev) +{ + struct snd_soc_codec *codec; + struct stmp3xxx_codec_priv *stmp3xxx_spdif; + int ret = 0; + + dev_info(&pdev->dev, + "STMP3XXX SPDIF Audio Transmitter %s\n", STMP3XXX_VERSION); + + stmp3xxx_spdif = + kzalloc(sizeof(struct stmp3xxx_codec_priv), GFP_KERNEL); + if (stmp3xxx_spdif == NULL) + return -ENOMEM; + + codec = &stmp3xxx_spdif->codec; + codec->name = "stmp3xxx spdif"; + codec->owner = THIS_MODULE; + codec->private_data = stmp3xxx_spdif; + codec->read = stmp3xxx_codec_read; + codec->write = stmp3xxx_codec_write; + codec->dai = &stmp3xxx_spdif_codec_dai; + codec->num_dai = 1; + + platform_set_drvdata(pdev, stmp3xxx_spdif); + stmp3xxx_spdif_codec = codec; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + /* Turn on audio clock */ + stmp3xxx_spdif->clk = clk_get(&pdev->dev, "spdif"); + if (IS_ERR(stmp3xxx_spdif->clk)) { + ret = PTR_ERR(stmp3xxx_spdif->clk); + dev_err(&pdev->dev, "Clocks initialization failed\n"); + goto clk_err; + } + clk_enable(stmp3xxx_spdif->clk); + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(&pdev->dev, "failed to register card\n"); + goto card_err; + } + + ret = snd_soc_register_dai(&stmp3xxx_spdif_codec_dai); + if (ret) { + dev_err(&pdev->dev, "failed to register card\n"); + goto dai_err; + } + + return 0; + +dai_err: + snd_soc_unregister_codec(codec); +card_err: + clk_disable(stmp3xxx_spdif->clk); + clk_put(stmp3xxx_spdif->clk); +clk_err: + kfree(stmp3xxx_spdif); + return ret; +} +static int __devexit stmp3xxx_spdif_remove(struct platform_device *pdev) +{ + struct stmp3xxx_codec_priv *stmp3xxx_spdif = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = &stmp3xxx_spdif->codec; + + snd_soc_unregister_codec(codec); + + clk_disable(stmp3xxx_spdif->clk); + clk_put(stmp3xxx_spdif->clk); + + kfree(stmp3xxx_spdif); + + return 0; +} + +struct platform_driver stmp3xxx_spdif_driver = { + .driver = { + .name = "stmp3xxx-spdif", + }, + .probe = stmp3xxx_spdif_probe, + .remove = __devexit_p(stmp3xxx_spdif_remove), +}; + +static int __init stmp3xxx_spdif_init(void) +{ + return platform_driver_register(&stmp3xxx_spdif_driver); +} + +static void __exit stmp3xxx_spdif_exit(void) +{ + return platform_driver_unregister(&stmp3xxx_spdif_driver); +} + +module_init(stmp3xxx_spdif_init); +module_exit(stmp3xxx_spdif_exit); + +MODULE_DESCRIPTION("STMP3XXX SPDIF transmitter"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/stmp3xxx_spdif.h b/sound/soc/codecs/stmp3xxx_spdif.h new file mode 100644 index 000000000000..0ae20a7e8cc6 --- /dev/null +++ b/sound/soc/codecs/stmp3xxx_spdif.h @@ -0,0 +1,37 @@ +/* + * ALSA SoC STMP378x SPDIF codec driver + * + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * Copyright 2008-2009 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#ifndef __STMP3XXX_SPDIF_CODEC_H +#define __STMP3XXX_SPDIF_CODEC_H + +#define SPDIF_CTRL_L 0 +#define SPDIF_CTRL_H 1 +#define SPDIF_STAT_L 2 +#define SPDIF_STAT_H 3 +#define SPDIF_FRAMECTRL_L 4 +#define SPDIF_FRAMECTRL_H 5 +#define SPDIF_SRR_L 6 +#define SPDIF_SRR_H 7 +#define SPDIF_DEBUG_L 8 +#define SPDIF_DEBUG_H 9 +#define SPDIF_DATA_L 10 +#define SPDIF_DATA_H 11 +#define SPDIF_VERSION_L 12 +#define SPDIF_VERSION_H 13 + +#define SPDIF_REGNUM 14 + +extern struct snd_soc_dai stmp3xxx_spdif_codec_dai; +extern struct snd_soc_codec_device soc_spdif_codec_dev_stmp3xxx; + +#endif /* __STMP3XXX_SPDIF_CODEC_H */ diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig new file mode 100644 index 000000000000..6f8e690044e2 --- /dev/null +++ b/sound/soc/imx/Kconfig @@ -0,0 +1,71 @@ +config SND_MXC_SOC + tristate "SoC Audio for the Freescale i.MX CPU" + depends on ARCH_MXC && SND + select SND_PCM + help + Say Y or M if you want to add support for codecs attached to + the MXC I2S or SSP interface. You will also need + to select the audio interfaces to support below. + +if SND_MXC_SOC + +config SND_MXC_SOC_SSI + tristate + +config SND_MXC_SOC_ESAI + tristate + +config SND_MXC_SOC_IRAM + bool "Locate Audio DMA playback buffers in IRAM" + help + Say Y if you don't want Audio playback buffers in external ram + +config SND_SOC_IMX_3STACK_WM8350 + tristate "SoC Audio support for IMX - WM8350" + depends on MFD_WM8350 + select SND_MXC_SOC_SSI + select SND_SOC_WM8350 + help + Say Y if you want to add support for SoC audio on IMX 3STACK + with the WM8350. + +config SND_SOC_IMX_3STACK_SGTL5000 + tristate "SoC Audio support for IMX - SGTL5000" + select SND_MXC_SOC_SSI + select SND_SOC_SGTL5000 + help + Say Y if you want to add support for SoC audio on IMX 3STACK + with the SGTL5000. + +config SND_SOC_IMX_3STACK_AK4647 + tristate "SoC Audio support for IMX - AK4647" + select SND_MXC_SOC_SSI + select SND_SOC_AK4647 + help + Say Y if you want to add support for SoC audio on IMX 3STACK + with the AK4647. + +config SND_SOC_IMX_3STACK_WM8580 + tristate "SoC Audio support for IMX - WM8580" + select SND_MXC_SOC_ESAI + select SND_SOC_WM8580 + help + Say Y if you want to add support for Soc audio on IMX 3STACK + with the WM8580 + +config SND_SOC_IMX_3STACK_AK5702 + tristate "SoC Audio support for IMX - AK5702" + select SND_MXC_SOC_ESAI + select SND_SOC_AK5702 + help + Say Y if you want to add support for Soc audio on IMX 3STACK + with the AK5702 + +config SND_SOC_IMX_3STACK_BLUETOOTH + tristate "SoC Audio support for IMX - BLUETOOTH" + select SND_MXC_SOC_SSI + select SND_SOC_BLUETOOTH + help + Say Y if you want to add support for Soc audio on IMX 3STACK + with the BLUETOOTH +endif diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile new file mode 100644 index 000000000000..519377291202 --- /dev/null +++ b/sound/soc/imx/Makefile @@ -0,0 +1,22 @@ +# i.MX Platform Support +snd-soc-imx-objs := imx-pcm.o +snd-soc-imx-ssi-objs := imx-ssi.o +snd-soc-imx-esai-objs := imx-esai.o + +obj-$(CONFIG_SND_MXC_SOC) += snd-soc-imx.o +obj-$(CONFIG_SND_MXC_SOC_SSI) += snd-soc-imx-ssi.o +obj-$(CONFIG_SND_MXC_SOC_ESAI) += snd-soc-imx-esai.o + +# i.MX Machine Support +snd-soc-imx-3stack-wm8350-objs := imx-3stack-wm8350.o +obj-$(CONFIG_SND_SOC_IMX_3STACK_WM8350) += snd-soc-imx-3stack-wm8350.o +snd-soc-imx-3stack-sgtl5000-objs := imx-3stack-sgtl5000.o +obj-$(CONFIG_SND_SOC_IMX_3STACK_SGTL5000) += snd-soc-imx-3stack-sgtl5000.o +snd-soc-imx-3stack-ak4647-objs := imx-3stack-ak4647.o +obj-$(CONFIG_SND_SOC_IMX_3STACK_AK4647) += snd-soc-imx-3stack-ak4647.o +snd-soc-imx-3stack-wm8580-objs := imx-3stack-wm8580.o +obj-$(CONFIG_SND_SOC_IMX_3STACK_WM8580) += snd-soc-imx-3stack-wm8580.o +snd-soc-imx-3stack-ak5702-objs := imx-3stack-ak5702.o +obj-$(CONFIG_SND_SOC_IMX_3STACK_AK5702) += snd-soc-imx-3stack-ak5702.o +snd-soc-imx-3stack-bt-objs := imx-3stack-bt.o +obj-$(CONFIG_SND_SOC_IMX_3STACK_BLUETOOTH) += snd-soc-imx-3stack-bt.o diff --git a/sound/soc/imx/imx-3stack-ak4647.c b/sound/soc/imx/imx-3stack-ak4647.c new file mode 100644 index 000000000000..ed97f8130ec6 --- /dev/null +++ b/sound/soc/imx/imx-3stack-ak4647.c @@ -0,0 +1,437 @@ +/* + * imx-3stack-ak4647.c -- SoC audio for imx_3stack + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <mach/clock.h> +#include <linux/regulator/consumer.h> + +#include "imx-pcm.h" +#include "imx-ssi.h" + +#define AK4647_SSI_MASTER 1 + +extern struct snd_soc_dai ak4647_hifi_dai; +extern struct snd_soc_codec_device soc_codec_dev_ak4647; + +static void headphone_detect_handler(struct work_struct *work); +static DECLARE_WORK(hp_event, headphone_detect_handler); +static int ak4647_jack_func; +static int ak4647_spk_func; + +struct imx_3stack_priv { + struct platform_device *pdev; +}; + +static struct imx_3stack_priv card_priv; + +static void imx_3stack_init_dam(int ssi_port, int dai_port) +{ + /* AK4647 uses SSI1 or SSI2 via AUDMUX port dai_port for audio */ + unsigned int ssi_ptcr = 0; + unsigned int dai_ptcr = 0; + unsigned int ssi_pdcr = 0; + unsigned int dai_pdcr = 0; + + /* reset port ssi_port & dai_port */ + __raw_writel(0, DAM_PTCR(ssi_port)); + __raw_writel(0, DAM_PTCR(dai_port)); + __raw_writel(0, DAM_PDCR(ssi_port)); + __raw_writel(0, DAM_PDCR(dai_port)); + + /* set to synchronous */ + ssi_ptcr |= AUDMUX_PTCR_SYN; + dai_ptcr |= AUDMUX_PTCR_SYN; + +#if AK4647_SSI_MASTER + /* set Rx sources ssi_port <--> dai_port */ + ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port); + dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port); + + /* set Tx frame direction and source dai_port--> ssi_port output */ + ssi_ptcr |= AUDMUX_PTCR_TFSDIR; + ssi_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, dai_port); + + /* set Tx Clock direction and source dai_port--> ssi_port output */ + ssi_ptcr |= AUDMUX_PTCR_TCLKDIR; + ssi_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, dai_port); +#else + /* set Rx sources ssi_port <--> dai_port */ + ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port); + dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port); + + /* set Tx frame direction and source ssi_port --> dai_port output */ + dai_ptcr |= AUDMUX_PTCR_TFSDIR; + dai_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, ssi_port); + + /* set Tx Clock direction and source ssi_port--> dai_port output */ + dai_ptcr |= AUDMUX_PTCR_TCLKDIR; + dai_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, ssi_port); +#endif + + __raw_writel(ssi_ptcr, DAM_PTCR(ssi_port)); + __raw_writel(dai_ptcr, DAM_PTCR(dai_port)); + __raw_writel(ssi_pdcr, DAM_PDCR(ssi_port)); + __raw_writel(dai_pdcr, DAM_PDCR(dai_port)); +} + +static int imx_3stack_hifi_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_link *pcm_link = rtd->dai; + struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai; + struct snd_soc_dai *codec_dai = pcm_link->codec_dai; + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + int ret = 0; + u32 dai_format; + +#if AK4647_SSI_MASTER + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; +#else + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; +#endif + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret < 0) + return ret; + + /* set i.MX active slot mask */ + snd_soc_dai_set_tdm_slot(cpu_dai, + channels == 1 ? 0xfffffffe : 0xfffffffc, 2); + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, dai_format); + if (ret < 0) + return ret; + + /* set the SSI system clock as input (unused) */ + snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, SND_SOC_CLOCK_IN); + + snd_soc_dai_set_sysclk(codec_dai, 0, rate, 0); + + /* set codec BCLK division for sample rate */ + snd_soc_dai_set_clkdiv(codec_dai, 0, 0); + + return 0; +} + +/* + * imx_3stack ak4647 HiFi DAI operations. + */ +static struct snd_soc_ops imx_3stack_hifi_ops = { + .hw_params = imx_3stack_hifi_hw_params, +}; + +static int ak4647_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = ak4647_jack_func; + return 0; +} + +static int ak4647_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + if (ak4647_jack_func == ucontrol->value.integer.value[0]) + return 0; + + ak4647_jack_func = ucontrol->value.integer.value[0]; + + if (ak4647_jack_func) + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + else + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + + snd_soc_dapm_sync(codec); + return 1; +} + +static int ak4647_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = ak4647_spk_func; + return 0; +} + +static int ak4647_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + if (ak4647_spk_func == ucontrol->value.integer.value[0]) + return 0; + + ak4647_spk_func = ucontrol->value.integer.value[0]; + if (ak4647_spk_func) + snd_soc_dapm_enable_pin(codec, "Line Out Jack"); + else + snd_soc_dapm_disable_pin(codec, "Line Out Jack"); + + snd_soc_dapm_sync(codec); + return 1; +} + +static int spk_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct imx_3stack_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + + if (plat->amp_enable == NULL) + return 0; + + if (SND_SOC_DAPM_EVENT_ON(event)) + plat->amp_enable(1); + else + plat->amp_enable(0); + return 0; +} + +/* imx_3stack card dapm widgets */ +static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic1 Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", spk_amp_event), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* mic is connected to mic1 - with bias */ + {"Left Input", NULL, "Mic1 Jack"}, + + /* Line in jack */ + {"Left Input", NULL, "Line In Jack"}, + {"Right Input", NULL, "Line In Jack"}, + + /* Headphone jack */ + {"Headphone Jack", NULL, "HPL"}, + {"Headphone Jack", NULL, "HPR"}, + + /* Line out jack */ + {"Line Out Jack", NULL, "LOUT"}, + + /* Ext Spk */ + {"Ext Spk", NULL, "LOUT"}, + +}; + +static const char *jack_function[] = { "off", "on" }; + +static const char *spk_function[] = { "off", "on" }; + +static const struct soc_enum ak4647_enum[] = { + SOC_ENUM_SINGLE_EXT(2, jack_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), +}; + +static const struct snd_kcontrol_new ak4647_card_controls[] = { + SOC_ENUM_EXT("Jack Function", ak4647_enum[0], ak4647_get_jack, + ak4647_set_jack), + SOC_ENUM_EXT("Speaker Function", ak4647_enum[1], ak4647_get_spk, + ak4647_set_spk), +}; + +static void headphone_detect_handler(struct work_struct *work) +{ + struct imx_3stack_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + + sysfs_notify(&pdev->dev.kobj, NULL, "headphone"); +} + +static irqreturn_t imx_headphone_detect_handler(int irq, void *dev_id) +{ + schedule_work(&hp_event); + return IRQ_HANDLED; + +} + +static ssize_t show_headphone(struct device_driver *dev, char *buf) +{ + struct imx_3stack_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + unsigned int value; + + value = plat->hp_status(); + + if (value == 0) + strcpy(buf, "speaker\n"); + else + strcpy(buf, "headphone\n"); + + return strlen(buf); +} + +DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL); + +static int imx_3stack_ak4647_init(struct snd_soc_codec *codec) +{ + int i, ret; + for (i = 0; i < ARRAY_SIZE(ak4647_card_controls); i++) { + ret = snd_ctl_add(codec->card, + snd_soc_cnew(&ak4647_card_controls[i], + codec, NULL)); + if (ret < 0) + return ret; + } + + snd_soc_dapm_new_controls(codec, imx_3stack_dapm_widgets, + ARRAY_SIZE(imx_3stack_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(codec); + + return 0; +} + +static struct snd_soc_dai_link imx_3stack_dai = { + .name = "ak4647", + .stream_name = "ak4647", + .cpu_dai = &imx_ssi_dai, + .codec_dai = &ak4647_hifi_dai, + .init = imx_3stack_ak4647_init, + .ops = &imx_3stack_hifi_ops, +}; + +static struct snd_soc_card snd_soc_card_imx_3stack = { + .name = "imx-3stack", + .platform = &imx_soc_platform, + .dai_link = &imx_3stack_dai, + .num_links = 1, +}; + +static struct snd_soc_device imx_3stack_snd_devdata = { + .card = &snd_soc_card_imx_3stack, + .codec_dev = &soc_codec_dev_ak4647, +}; + +/* + * This function will register the snd_soc_pcm_link drivers. + * It also registers devices for platform DMA, I2S, SSP and registers an + * I2C driver to probe the codec. + */ +static int __init imx_3stack_ak4647_probe(struct platform_device *pdev) +{ + struct mxc_audio_platform_data *dev_data = pdev->dev.platform_data; + struct imx_3stack_priv *priv = &card_priv; + int ret = 0; + + dev_data->init(); + + if (dev_data->src_port == 1) + imx_ssi_dai.name = "imx-ssi-1"; + else + imx_ssi_dai.name = "imx-ssi-3"; + + imx_ssi_dai.dev = &pdev->dev; + imx_ssi_dai.symmetric_rates = 1; + snd_soc_register_dai(&imx_ssi_dai); + + /* Configure audio port 3 */ + gpio_activate_audio_ports(); + imx_3stack_init_dam(dev_data->src_port, dev_data->ext_port); + + if (request_irq + (dev_data->intr_id_hp, imx_headphone_detect_handler, 0, + "headphone", NULL)) + goto err; + + ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone); + if (ret < 0) + goto sysfs_err; + + priv->pdev = pdev; + return ret; + +sysfs_err: + free_irq(dev_data->intr_id_hp, NULL); +err: + return ret; +} + +static int __devexit imx_3stack_ak4647_remove(struct platform_device *pdev) +{ + struct mxc_audio_platform_data *dev_data = pdev->dev.platform_data; + gpio_inactivate_audio_ports(); + free_irq(dev_data->intr_id_hp, NULL); + driver_remove_file(pdev->dev.driver, &driver_attr_headphone); + return 0; +} + +static struct platform_driver imx_3stack_ak4647_driver = { + .probe = imx_3stack_ak4647_probe, + .remove = __devexit_p(imx_3stack_ak4647_remove), + .driver = { + .name = "imx-3stack-ak4647", + .owner = THIS_MODULE, + }, +}; + +static struct platform_device *imx_3stack_snd_device; + +static int __init imx_3stack_asoc_init(void) +{ + int ret; + + ret = platform_driver_register(&imx_3stack_ak4647_driver); + if (ret < 0) + goto exit; + imx_3stack_snd_device = platform_device_alloc("soc-audio", 3); + if (!imx_3stack_snd_device) + goto err_device_alloc; + platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata); + imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev; + ret = platform_device_add(imx_3stack_snd_device); + if (0 == ret) + goto exit; + + platform_device_put(imx_3stack_snd_device); +err_device_alloc: + platform_driver_unregister(&imx_3stack_ak4647_driver); +exit: + return ret; +} + +static void __exit imx_3stack_asoc_exit(void) +{ + platform_driver_unregister(&imx_3stack_ak4647_driver); + platform_device_unregister(imx_3stack_snd_device); +} + +module_init(imx_3stack_asoc_init); +module_exit(imx_3stack_asoc_exit); + +/* Module information */ +MODULE_DESCRIPTION("ALSA SoC ak4647 imx_3stack"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/imx/imx-3stack-ak5702.c b/sound/soc/imx/imx-3stack-ak5702.c new file mode 100644 index 000000000000..a13a57ab671a --- /dev/null +++ b/sound/soc/imx/imx-3stack-ak5702.c @@ -0,0 +1,224 @@ +/* + * imx-3stack-ak5702.c -- SoC audio for imx_3stack + * + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/regulator/consumer.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include <mach/hardware.h> +#include <mach/clock.h> + +#include "imx-pcm.h" +#include "imx-esai.h" +#include "../codecs/ak5702.h" + +struct imx_3stack_pcm_state { + int lr_clk_active; +}; + +static struct imx_3stack_pcm_state clk_state; + +static int imx_3stack_startup(struct snd_pcm_substream *substream) +{ + clk_state.lr_clk_active++; + return 0; +} + +static void imx_3stack_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *pcm_link = rtd->dai; + struct snd_soc_dai *codec_dai = pcm_link->codec_dai; + + /* disable the PLL if there are no active Rx channels */ + if (!codec_dai->active) + snd_soc_dai_set_pll(codec_dai, 0, 0, 0); + clk_state.lr_clk_active--; +} + +static int imx_3stack_surround_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_link *pcm_link = rtd->dai; + struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai; + struct snd_soc_dai *codec_dai = pcm_link->codec_dai; + unsigned int rate = params_rate(params); + u32 dai_format; + + if (clk_state.lr_clk_active > 1) + return 0; + + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + /* set codec DAI configuration */ + snd_soc_dai_set_fmt(codec_dai, dai_format); + + /* set cpu DAI configuration */ + snd_soc_dai_set_fmt(cpu_dai, dai_format); + + /* set i.MX active slot mask */ + snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffff, 2); + + /* set the ESAI system clock as input */ + snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN); + + /* set codec BCLK division */ + snd_soc_dai_set_clkdiv(codec_dai, AK5702_BCLK_CLKDIV, + AK5702_BCLK_DIV_32); + + snd_soc_dai_set_sysclk(codec_dai, 0, rate, SND_SOC_CLOCK_OUT); + + snd_soc_dai_set_pll(codec_dai, 1, 12000000, 0); + return 0; +} + +/* + * imx_3stack ak5702 DAI opserations. + */ +static struct snd_soc_ops imx_3stack_surround_ops = { + .startup = imx_3stack_startup, + .shutdown = imx_3stack_shutdown, + .hw_params = imx_3stack_surround_hw_params, +}; + +/* imx_3stack card dapm widgets */ +static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line In Jack", NULL), +}; + +/* example card audio map */ +static const struct snd_soc_dapm_route audio_map[] = { + /* Line in jack */ + {"ADCA Left Input", NULL, "Line In Jack"}, + {"ADCA Right Input", NULL, "Line In Jack"}, + {"ADCB Left Input", NULL, "Line In Jack"}, + {"ADCB Right Input", NULL, "Line In Jack"}, +}; + +static int imx_3stack_ak5702_init(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, imx_3stack_dapm_widgets, + ARRAY_SIZE(imx_3stack_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + snd_soc_dapm_sync(codec); + return 0; +} + +static struct snd_soc_dai_link imx_3stack_dai = { + .name = "ak5702", + .stream_name = "ak5702", + .cpu_dai = &imx_esai_dai, + .codec_dai = &ak5702_dai, + .init = imx_3stack_ak5702_init, + .ops = &imx_3stack_surround_ops, +}; + +static int imx_3stack_card_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + kfree(socdev->codec_data); + return 0; +} + +static struct snd_soc_card snd_soc_card_imx_3stack = { + .name = "imx-3stack", + .platform = &imx_soc_platform, + .dai_link = &imx_3stack_dai, + .num_links = 1, + .remove = imx_3stack_card_remove, +}; + +static struct snd_soc_device imx_3stack_snd_devdata = { + .card = &snd_soc_card_imx_3stack, + .codec_dev = &soc_codec_dev_ak5702, +}; + +static int __devinit imx_3stack_ak5702_probe(struct platform_device *pdev) +{ + struct ak5702_setup_data *setup; + + imx_esai_dai.name = "imx-esai-txrx"; + + setup = kzalloc(sizeof(struct ak5702_setup_data), GFP_KERNEL); + setup->i2c_bus = 1; + setup->i2c_address = 0x13; + imx_3stack_snd_devdata.codec_data = setup; + + gpio_activate_esai_ports(); + return 0; +} + +static int imx_3stack_ak5702_remove(struct platform_device *pdev) +{ + gpio_deactivate_esai_ports(); + return 0; +} + +static struct platform_driver imx_3stack_ak5702_driver = { + .probe = imx_3stack_ak5702_probe, + .remove = imx_3stack_ak5702_remove, + .driver = { + .name = "imx-3stack-ak5702", + }, +}; + +static struct platform_device *imx_3stack_snd_device; + +static int __init imx_3stack_asoc_init(void) +{ + int ret; + + ret = platform_driver_register(&imx_3stack_ak5702_driver); + if (ret) + return -ENOMEM; + + imx_3stack_snd_device = platform_device_alloc("soc-audio", -1); + if (!imx_3stack_snd_device) + return -ENOMEM; + + platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata); + imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev; + ret = platform_device_add(imx_3stack_snd_device); + if (ret) + platform_device_put(imx_3stack_snd_device); + + return ret; +} + +static void __exit imx_3stack_asoc_exit(void) +{ + platform_driver_unregister(&imx_3stack_ak5702_driver); + platform_device_unregister(imx_3stack_snd_device); +} + +module_init(imx_3stack_asoc_init); +module_exit(imx_3stack_asoc_exit); + +/* Module information */ +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("ALSA SoC ak5702 imx_3stack"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/imx/imx-3stack-bt.c b/sound/soc/imx/imx-3stack-bt.c new file mode 100644 index 000000000000..ca0679797df8 --- /dev/null +++ b/sound/soc/imx/imx-3stack-bt.c @@ -0,0 +1,252 @@ +/* + * imx-3stack-bt.c -- SoC bluetooth audio for imx_3stack + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "imx-pcm.h" +#include "imx-ssi.h" +#include "imx-3stack-bt.h" + +#define BT_SSI_MASTER 1 + +struct imx_3stack_priv { + struct platform_device *pdev; + int active; +}; + +static struct imx_3stack_priv card_priv; + +static void imx_3stack_init_dam(int ssi_port, int dai_port) +{ + /* bt uses SSI1 or SSI2 via AUDMUX port dai_port for audio */ + unsigned int ssi_ptcr = 0; + unsigned int dai_ptcr = 0; + unsigned int ssi_pdcr = 0; + unsigned int dai_pdcr = 0; + + /* reset port ssi_port & dai_port */ + __raw_writel(0, DAM_PTCR(ssi_port)); + __raw_writel(0, DAM_PTCR(dai_port)); + __raw_writel(0, DAM_PDCR(ssi_port)); + __raw_writel(0, DAM_PDCR(dai_port)); + + /* set to synchronous */ + ssi_ptcr |= AUDMUX_PTCR_SYN; + dai_ptcr |= AUDMUX_PTCR_SYN; + +#if BT_SSI_MASTER + /* set Rx sources ssi_port <--> dai_port */ + ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port); + dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port); + + /* set Tx frame direction and source dai_port--> ssi_port output */ + ssi_ptcr |= AUDMUX_PTCR_TFSDIR; + ssi_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, dai_port); + + /* set Tx Clock direction and source dai_port--> ssi_port output */ + ssi_ptcr |= AUDMUX_PTCR_TCLKDIR; + ssi_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, dai_port); +#else + /* set Rx sources ssi_port <--> dai_port */ + ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port); + dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port); + + /* set Tx frame direction and source ssi_port --> dai_port output */ + dai_ptcr |= AUDMUX_PTCR_TFSDIR; + dai_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, ssi_port); + + /* set Tx Clock direction and source ssi_port--> dai_port output */ + dai_ptcr |= AUDMUX_PTCR_TCLKDIR; + dai_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, ssi_port); +#endif + + __raw_writel(ssi_ptcr, DAM_PTCR(ssi_port)); + __raw_writel(dai_ptcr, DAM_PTCR(dai_port)); + __raw_writel(ssi_pdcr, DAM_PDCR(ssi_port)); + __raw_writel(dai_pdcr, DAM_PDCR(dai_port)); +} + +static int imx_3stack_bt_startup(struct snd_pcm_substream *substream) +{ + struct imx_3stack_priv *priv = &card_priv; + + if (!priv->active) + gpio_activate_bt_audio_port(); + priv->active++; + return 0; +} + +static int imx_3stack_bt_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_link *pcm_link = rtd->dai; + struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai; + unsigned int channels = params_channels(params); + int ret = 0; + u32 dai_format; + +#if BT_SSI_MASTER + dai_format = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_IB_IF | + SND_SOC_DAIFMT_CBM_CFM; +#else + dai_format = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_IB_IF | + SND_SOC_DAIFMT_CBS_CFS; +#endif + + /* set i.MX active slot mask */ + snd_soc_dai_set_tdm_slot(cpu_dai, + channels == 1 ? 0xfffffffe : 0xfffffffc, 2); + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, dai_format); + if (ret < 0) + return ret; + + /* set the SSI system clock as input (unused) */ + snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, SND_SOC_CLOCK_IN); + + return 0; +} + +static void imx_3stack_bt_shutdown(struct snd_pcm_substream *substream) +{ + struct imx_3stack_priv *priv = &card_priv; + + priv->active--; + if (!priv->active) + gpio_inactivate_bt_audio_port(); +} + +/* + * imx_3stack bt DAI opserations. + */ +static struct snd_soc_ops imx_3stack_bt_ops = { + .startup = imx_3stack_bt_startup, + .hw_params = imx_3stack_bt_hw_params, + .shutdown = imx_3stack_bt_shutdown, +}; + +static struct snd_soc_dai_link imx_3stack_dai = { + .name = "bluetooth", + .stream_name = "bluetooth", + .cpu_dai = &imx_ssi_dai, + .codec_dai = &bt_dai, + .ops = &imx_3stack_bt_ops, +}; + +static struct snd_soc_card snd_soc_card_imx_3stack = { + .name = "imx-3stack", + .platform = &imx_soc_platform, + .dai_link = &imx_3stack_dai, + .num_links = 1, +}; + +static struct snd_soc_device imx_3stack_snd_devdata = { + .card = &snd_soc_card_imx_3stack, + .codec_dev = &soc_codec_dev_bt, +}; + +/* + * This function will register the snd_soc_pcm_link drivers. + * It also registers devices for platform DMA, I2S, SSP and registers an + * I2C driver to probe the codec. + */ +static int __init imx_3stack_bt_probe(struct platform_device *pdev) +{ + struct mxc_audio_platform_data *dev_data = pdev->dev.platform_data; + struct imx_3stack_priv *priv = &card_priv; + + /* imx_3stack bt interface */ + imx_ssi_dai.private_data = dev_data; + imx_ssi_dai.dev = &pdev->dev; + + if (dev_data->src_port == 1) + imx_ssi_dai.name = "imx-ssi-1"; + else + imx_ssi_dai.name = "imx-ssi-3"; + + snd_soc_register_dai(&imx_ssi_dai); + + /* Configure audio port */ + imx_3stack_init_dam(dev_data->src_port, dev_data->ext_port); + + priv->pdev = pdev; + priv->active = 0; + return 0; + +} + +static int __devexit imx_3stack_bt_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver imx_3stack_bt_driver = { + .probe = imx_3stack_bt_probe, + .remove = __devexit_p(imx_3stack_bt_remove), + .driver = { + .name = "imx-3stack-bt", + .owner = THIS_MODULE, + }, +}; + +static struct platform_device *imx_3stack_snd_device; + +static int __init imx_3stack_asoc_init(void) +{ + int ret; + + ret = platform_driver_register(&imx_3stack_bt_driver); + if (ret < 0) + goto exit; + imx_3stack_snd_device = platform_device_alloc("soc-audio", 4); + if (!imx_3stack_snd_device) + goto err_device_alloc; + platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata); + imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev; + ret = platform_device_add(imx_3stack_snd_device); + if (0 == ret) + goto exit; + + platform_device_put(imx_3stack_snd_device); +err_device_alloc: + platform_driver_unregister(&imx_3stack_bt_driver); +exit: + return ret; +} + +static void __exit imx_3stack_asoc_exit(void) +{ + platform_driver_unregister(&imx_3stack_bt_driver); + platform_device_unregister(imx_3stack_snd_device); +} + +module_init(imx_3stack_asoc_init); +module_exit(imx_3stack_asoc_exit); + +/* Module information */ +MODULE_DESCRIPTION("ALSA SoC bluetooth imx_3stack"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/imx/imx-3stack-bt.h b/sound/soc/imx/imx-3stack-bt.h new file mode 100644 index 000000000000..4e1d3547dd56 --- /dev/null +++ b/sound/soc/imx/imx-3stack-bt.h @@ -0,0 +1,21 @@ +/* + * imx-3stack-bt.h -- Bluetooth PCM driver header file for Freescale IMX + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef _MXC_BTPCM_H +#define _MXC_BTPCM_H + +extern struct snd_soc_dai bt_dai; +extern struct snd_soc_codec_device soc_codec_dev_bt; +#endif diff --git a/sound/soc/imx/imx-3stack-sgtl5000.c b/sound/soc/imx/imx-3stack-sgtl5000.c new file mode 100644 index 000000000000..9dc060b52301 --- /dev/null +++ b/sound/soc/imx/imx-3stack-sgtl5000.c @@ -0,0 +1,759 @@ +/* + * imx-3stack-sgtl5000.c -- i.MX 3Stack Driver for Freescale SGTL5000 Codec + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All 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. + * + * Revision history + * 21th Oct 2008 Initial version. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/bitops.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include <mach/dma.h> +#include <mach/spba.h> +#include <mach/clock.h> + +#include "../codecs/sgtl5000.h" +#include "imx-ssi.h" +#include "imx-pcm.h" + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) +#include <linux/mxc_asrc.h> + +static unsigned int sgtl5000_rates[] = { + 0, + 32000, + 44100, + 48000, + 96000, +}; + +struct asrc_esai { + unsigned int cpu_dai_rates; + unsigned int codec_dai_rates; + enum asrc_pair_index asrc_index; + unsigned int output_sample_rate; +}; + +static struct asrc_esai asrc_ssi_data; +#endif + +/* SSI BCLK and LRC master */ +#define SGTL5000_SSI_MASTER 1 + +struct imx_3stack_priv { + int sysclk; + int hw; + struct platform_device *pdev; + struct regulator *reg_vddio; + struct regulator *reg_vdda; + struct regulator *reg_vddd; +}; + +static struct imx_3stack_priv card_priv; + +static int imx_3stack_audio_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_link *machine = rtd->dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct snd_soc_dai *codec_dai = machine->codec_dai; + struct imx_3stack_priv *priv = &card_priv; + unsigned int rate = params_rate(params); + int ret = 0; + + unsigned int channels = params_channels(params); + u32 dai_format; + + /* only need to do this once as capture and playback are sync */ + if (priv->hw) + return 0; + priv->hw = 1; + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + if ((asrc_ssi_data.output_sample_rate != 0) + && (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) { + unsigned int asrc_input_rate = rate; + unsigned int channel = params_channels(params); + struct mxc_runtime_data *pcm_data = + substream->runtime->private_data; + struct asrc_config config; + struct mxc_audio_platform_data *plat; + struct imx_3stack_priv *priv = &card_priv; + int retVal = 0; + retVal = asrc_req_pair(channel, &asrc_ssi_data.asrc_index); + if (retVal < 0) { + pr_err("asrc_req_pair fail\n"); + return -1; + } + config.pair = asrc_ssi_data.asrc_index; + config.channel_num = channel; + config.input_sample_rate = asrc_input_rate; + config.output_sample_rate = asrc_ssi_data.output_sample_rate; + config.inclk = INCLK_NONE; + config.word_width = 32; + plat = priv->pdev->dev.platform_data; + if (plat->src_port == 1) + config.outclk = OUTCLK_SSI1_TX; + else + config.outclk = OUTCLK_SSI2_TX; + retVal = asrc_config_pair(&config); + if (retVal < 0) { + pr_err("Fail to config asrc\n"); + asrc_release_pair(asrc_ssi_data.asrc_index); + return retVal; + } + rate = asrc_ssi_data.output_sample_rate; + pcm_data->asrc_index = asrc_ssi_data.asrc_index; + pcm_data->asrc_enable = 1; + } +#endif + + snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, priv->sysclk, 0); + snd_soc_dai_set_sysclk(codec_dai, SGTL5000_LRCLK, rate, 0); + +#if SGTL5000_SSI_MASTER + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; +#else + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; +#endif + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret < 0) + return ret; + + /* set i.MX active slot mask */ + snd_soc_dai_set_tdm_slot(cpu_dai, + channels == 1 ? 0xfffffffe : 0xfffffffc, 2); + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, dai_format); + if (ret < 0) + return ret; + + /* set the SSI system clock as input (unused) */ + snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, SND_SOC_CLOCK_IN); + + return 0; +} + +static int imx_3stack_startup(struct snd_pcm_substream *substream) +{ +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (asrc_ssi_data.output_sample_rate != 0) { + struct snd_soc_pcm_runtime *rtd = + substream->private_data; + struct snd_soc_dai_link *pcm_link = rtd->dai; + struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai; + struct snd_soc_dai *codec_dai = pcm_link->codec_dai; + asrc_ssi_data.cpu_dai_rates = cpu_dai->playback.rates; + asrc_ssi_data.codec_dai_rates = + codec_dai->playback.rates; + cpu_dai->playback.rates = + SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT; + codec_dai->playback.rates = + SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT; + } + } +#endif + return 0; +} + +static void imx_3stack_shutdown(struct snd_pcm_substream *substream) +{ + struct imx_3stack_priv *priv = &card_priv; + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (asrc_ssi_data.output_sample_rate != 0) { + struct snd_soc_pcm_runtime *rtd = + substream->private_data; + struct snd_soc_dai_link *pcm_link = rtd->dai; + struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai; + struct snd_soc_dai *codec_dai = pcm_link->codec_dai; + codec_dai->playback.rates = + asrc_ssi_data.codec_dai_rates; + cpu_dai->playback.rates = asrc_ssi_data.cpu_dai_rates; + asrc_release_pair(asrc_ssi_data.asrc_index); + } + } +#endif + + priv->hw = 0; +} + +/* + * imx_3stack SGTL5000 audio DAI opserations. + */ +static struct snd_soc_ops imx_3stack_ops = { + .startup = imx_3stack_startup, + .shutdown = imx_3stack_shutdown, + .hw_params = imx_3stack_audio_hw_params, +}; + +static void imx_3stack_init_dam(int ssi_port, int dai_port) +{ + unsigned int ssi_ptcr = 0; + unsigned int dai_ptcr = 0; + unsigned int ssi_pdcr = 0; + unsigned int dai_pdcr = 0; + /* SGTL5000 uses SSI1 or SSI2 via AUDMUX port dai_port for audio */ + + /* reset port ssi_port & dai_port */ + __raw_writel(0, DAM_PTCR(ssi_port)); + __raw_writel(0, DAM_PTCR(dai_port)); + __raw_writel(0, DAM_PDCR(ssi_port)); + __raw_writel(0, DAM_PDCR(dai_port)); + + /* set to synchronous */ + ssi_ptcr |= AUDMUX_PTCR_SYN; + dai_ptcr |= AUDMUX_PTCR_SYN; + +#if SGTL5000_SSI_MASTER + /* set Rx sources ssi_port <--> dai_port */ + ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port); + dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port); + + /* set Tx frame direction and source dai_port--> ssi_port output */ + ssi_ptcr |= AUDMUX_PTCR_TFSDIR; + ssi_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, dai_port); + + /* set Tx Clock direction and source dai_port--> ssi_port output */ + ssi_ptcr |= AUDMUX_PTCR_TCLKDIR; + ssi_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, dai_port); +#else + /* set Rx sources ssi_port <--> dai_port */ + ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port); + dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port); + + /* set Tx frame direction and source ssi_port --> dai_port output */ + dai_ptcr |= AUDMUX_PTCR_TFSDIR; + dai_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, ssi_port); + + /* set Tx Clock direction and source ssi_port--> dai_port output */ + dai_ptcr |= AUDMUX_PTCR_TCLKDIR; + dai_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, ssi_port); +#endif + + __raw_writel(ssi_ptcr, DAM_PTCR(ssi_port)); + __raw_writel(dai_ptcr, DAM_PTCR(dai_port)); + __raw_writel(ssi_pdcr, DAM_PDCR(ssi_port)); + __raw_writel(dai_pdcr, DAM_PDCR(dai_port)); +} + +/* imx_3stack machine connections to the codec pins */ +static const struct snd_soc_dapm_route audio_map[] = { + + /* Mic Jack --> MIC_IN (with automatic bias) */ + {"MIC_IN", NULL, "Mic Jack"}, + + /* Line in Jack --> LINE_IN */ + {"LINE_IN", NULL, "Line In Jack"}, + + /* HP_OUT --> Headphone Jack */ + {"Headphone Jack", NULL, "HP_OUT"}, + + /* LINE_OUT --> Ext Speaker */ + {"Ext Spk", NULL, "LINE_OUT"}, +}; + +static int sgtl5000_jack_func; +static int sgtl5000_spk_func; +static int sgtl5000_line_in_func; + +static void headphone_detect_handler(struct work_struct *work) +{ + struct imx_3stack_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + int hp_status; + + sysfs_notify(&pdev->dev.kobj, NULL, "headphone"); + hp_status = plat->hp_status(); + if (hp_status) + set_irq_type(plat->hp_irq, IRQ_TYPE_EDGE_FALLING); + else + set_irq_type(plat->hp_irq, IRQ_TYPE_EDGE_RISING); + enable_irq(plat->hp_irq); +} + +static DECLARE_DELAYED_WORK(hp_event, headphone_detect_handler); + +static irqreturn_t imx_headphone_detect_handler(int irq, void *data) +{ + disable_irq(irq); + schedule_delayed_work(&hp_event, msecs_to_jiffies(200)); + return IRQ_HANDLED; +} + +static ssize_t show_headphone(struct device_driver *dev, char *buf) +{ + struct imx_3stack_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + u16 hp_status; + + /* determine whether hp is plugged in */ + hp_status = plat->hp_status(); + + if (hp_status == 0) + strcpy(buf, "speaker\n"); + else + strcpy(buf, "headphone\n"); + + return strlen(buf); +} + +static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL); + +static const char *jack_function[] = { "off", "on"}; + +static const char *spk_function[] = { "off", "on" }; + +static const char *line_in_function[] = { "off", "on" }; + +static const struct soc_enum sgtl5000_enum[] = { + SOC_ENUM_SINGLE_EXT(2, jack_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, line_in_function), +}; + +static int sgtl5000_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = sgtl5000_jack_func; + return 0; +} + +static int sgtl5000_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (sgtl5000_jack_func == ucontrol->value.enumerated.item[0]) + return 0; + + sgtl5000_jack_func = ucontrol->value.enumerated.item[0]; + if (sgtl5000_jack_func) + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + else + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + + snd_soc_dapm_sync(codec); + return 1; +} + +static int sgtl5000_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = sgtl5000_spk_func; + return 0; +} + +static int sgtl5000_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (sgtl5000_spk_func == ucontrol->value.enumerated.item[0]) + return 0; + + sgtl5000_spk_func = ucontrol->value.enumerated.item[0]; + if (sgtl5000_spk_func) + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + else + snd_soc_dapm_disable_pin(codec, "Ext Spk"); + + snd_soc_dapm_sync(codec); + return 1; +} + +static int sgtl5000_get_line_in(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = sgtl5000_line_in_func; + return 0; +} + +static int sgtl5000_set_line_in(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (sgtl5000_line_in_func == ucontrol->value.enumerated.item[0]) + return 0; + + sgtl5000_line_in_func = ucontrol->value.enumerated.item[0]; + if (sgtl5000_line_in_func) + snd_soc_dapm_enable_pin(codec, "Line In Jack"); + else + snd_soc_dapm_disable_pin(codec, "Line In Jack"); + + snd_soc_dapm_sync(codec); + return 1; +} + +static int spk_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct imx_3stack_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + + if (plat->amp_enable == NULL) + return 0; + + if (SND_SOC_DAPM_EVENT_ON(event)) + plat->amp_enable(1); + else + plat->amp_enable(0); + + return 0; +} + +/* imx_3stack card dapm widgets */ +static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", spk_amp_event), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +static const struct snd_kcontrol_new sgtl5000_machine_controls[] = { + SOC_ENUM_EXT("Jack Function", sgtl5000_enum[0], sgtl5000_get_jack, + sgtl5000_set_jack), + SOC_ENUM_EXT("Speaker Function", sgtl5000_enum[1], sgtl5000_get_spk, + sgtl5000_set_spk), + SOC_ENUM_EXT("Line In Function", sgtl5000_enum[1], sgtl5000_get_line_in, + sgtl5000_set_line_in), +}; + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) +static int asrc_func; + +static const char *asrc_function[] = + { "disable", "32KHz", "44.1KHz", "48KHz", "96KHz" }; + +static const struct soc_enum asrc_enum[] = { + SOC_ENUM_SINGLE_EXT(5, asrc_function), +}; + +static int asrc_get_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = asrc_func; + return 0; +} + +static int asrc_set_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (asrc_func == ucontrol->value.enumerated.item[0]) + return 0; + + asrc_func = ucontrol->value.enumerated.item[0]; + asrc_ssi_data.output_sample_rate = sgtl5000_rates[asrc_func]; + + return 1; +} + +static const struct snd_kcontrol_new asrc_controls[] = { + SOC_ENUM_EXT("ASRC", asrc_enum[0], asrc_get_rate, + asrc_set_rate), +}; +#endif + +static int imx_3stack_sgtl5000_init(struct snd_soc_codec *codec) +{ + int i, ret; + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + for (i = 0; i < ARRAY_SIZE(asrc_controls); i++) { + ret = snd_ctl_add(codec->card, + snd_soc_cnew(&asrc_controls[i], codec, NULL)); + if (ret < 0) + return ret; + } + asrc_ssi_data.output_sample_rate = sgtl5000_rates[asrc_func]; +#endif + + /* Add imx_3stack specific controls */ + for (i = 0; i < ARRAY_SIZE(sgtl5000_machine_controls); i++) { + ret = snd_ctl_add(codec->card, + snd_soc_cnew(&sgtl5000_machine_controls[i], + codec, NULL)); + if (ret < 0) + return ret; + } + + /* Add imx_3stack specific widgets */ + snd_soc_dapm_new_controls(codec, imx_3stack_dapm_widgets, + ARRAY_SIZE(imx_3stack_dapm_widgets)); + + /* Set up imx_3stack specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_disable_pin(codec, "Line In Jack"); + + snd_soc_dapm_sync(codec); + + return 0; +} + +/* imx_3stack digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link imx_3stack_dai = { + .name = "SGTL5000", + .stream_name = "SGTL5000", + .cpu_dai = &imx_ssi_dai, + .codec_dai = &sgtl5000_dai, + .init = imx_3stack_sgtl5000_init, + .ops = &imx_3stack_ops, + .symmetric_rates = 1, +}; + +static int imx_3stack_card_remove(struct platform_device *pdev) +{ + struct imx_3stack_priv *priv = &card_priv; + struct mxc_audio_platform_data *plat; + if (priv->reg_vddio) + regulator_disable(priv->reg_vddio); + if (priv->reg_vddd) + regulator_disable(priv->reg_vddd); + if (priv->reg_vdda) + regulator_disable(priv->reg_vdda); + if (priv->reg_vdda) + regulator_put(priv->reg_vdda); + if (priv->reg_vddio) + regulator_put(priv->reg_vddio); + if (priv->reg_vddd) + regulator_put(priv->reg_vddd); + if (priv->pdev) { + plat = priv->pdev->dev.platform_data; + if (plat->finit) + plat->finit(); + } + + return 0; +} + +static struct snd_soc_card snd_soc_card_imx_3stack = { + .name = "imx-3stack", + .platform = &imx_soc_platform, + .dai_link = &imx_3stack_dai, + .num_links = 1, + .remove = imx_3stack_card_remove, +}; + +static struct snd_soc_device imx_3stack_snd_devdata = { + .card = &snd_soc_card_imx_3stack, + .codec_dev = &soc_codec_dev_sgtl5000, +}; + +static int __devinit imx_3stack_sgtl5000_probe(struct platform_device *pdev) +{ + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + struct imx_3stack_priv *priv = &card_priv; + struct sgtl5000_platform_data *codec_data; + struct regulator *reg; + int ret = 0; + + priv->sysclk = plat->sysclk; + priv->pdev = pdev; + + imx_ssi_dai.private_data = plat; + imx_ssi_dai.dev = &pdev->dev; + + codec_data = kzalloc(sizeof(struct sgtl5000_platform_data), GFP_KERNEL); + if (!codec_data) { + ret = -ENOMEM; + goto err_codec_data; + } + codec_data->vddio = plat->vddio / 1000; /* uV to mV */ + codec_data->vdda = plat->vdda / 1000; + codec_data->vddd = plat->vddd / 1000; + imx_3stack_snd_devdata.codec_data = codec_data; + + gpio_activate_audio_ports(); + imx_3stack_init_dam(plat->src_port, plat->ext_port); + + if (plat->src_port == 2) + imx_ssi_dai.name = "imx-ssi-3"; + else + imx_ssi_dai.name = "imx-ssi-1"; + + imx_ssi_dai.symmetric_rates = 1; + snd_soc_register_dai(&imx_ssi_dai); + + ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone); + if (ret < 0) { + pr_err("%s:failed to create driver_attr_headphone\n", __func__); + goto sysfs_err; + } + + ret = -EINVAL; + if (plat->init && plat->init()) + goto err_plat_init; + if (plat->vddio_reg) { + reg = regulator_get(&pdev->dev, plat->vddio_reg); + if (IS_ERR(reg)) + goto err_reg_vddio; + priv->reg_vddio = reg; + } + if (plat->vdda_reg) { + reg = regulator_get(&pdev->dev, plat->vdda_reg); + if (IS_ERR(reg)) + goto err_reg_vdda; + priv->reg_vdda = reg; + } + if (plat->vddd_reg) { + reg = regulator_get(&pdev->dev, plat->vddd_reg); + if (IS_ERR(reg)) + goto err_reg_vddd; + priv->reg_vddd = reg; + } + + if (priv->reg_vdda) { + ret = regulator_set_voltage(priv->reg_vdda, + plat->vdda, plat->vdda); + regulator_enable(priv->reg_vdda); + } + if (priv->reg_vddio) { + regulator_set_voltage(priv->reg_vddio, + plat->vddio, plat->vddio); + regulator_enable(priv->reg_vddio); + } + if (priv->reg_vddd) { + regulator_set_voltage(priv->reg_vddd, plat->vddd, plat->vddd); + regulator_enable(priv->reg_vddd); + } + + /* The SGTL5000 has an internal reset that is deasserted 8 SYS_MCLK + cycles after all power rails have been brought up. After this time + communication can start */ + msleep(1); + + if (plat->hp_status()) + ret = request_irq(plat->hp_irq, + imx_headphone_detect_handler, + IRQ_TYPE_EDGE_FALLING, pdev->name, priv); + else + ret = request_irq(plat->hp_irq, + imx_headphone_detect_handler, + IRQ_TYPE_EDGE_RISING, pdev->name, priv); + if (ret < 0) { + pr_err("%s: request irq failed\n", __func__); + goto err_card_reg; + } + + sgtl5000_jack_func = 1; + sgtl5000_spk_func = 1; + sgtl5000_line_in_func = 0; + + return 0; + +err_card_reg: + if (priv->reg_vddd) + regulator_put(priv->reg_vddd); +err_reg_vddd: + if (priv->reg_vdda) + regulator_put(priv->reg_vdda); +err_reg_vdda: + if (priv->reg_vddio) + regulator_put(priv->reg_vddio); +err_reg_vddio: + if (plat->finit) + plat->finit(); +err_plat_init: + driver_remove_file(pdev->dev.driver, &driver_attr_headphone); +sysfs_err: + kfree(codec_data); +err_codec_data: + return ret; +} + +static int imx_3stack_sgtl5000_remove(struct platform_device *pdev) +{ + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + struct imx_3stack_priv *priv = &card_priv; + + free_irq(plat->hp_irq, priv); + + driver_remove_file(pdev->dev.driver, &driver_attr_headphone); + + kfree(imx_3stack_snd_devdata.codec_data); + + return 0; +} + +static struct platform_driver imx_3stack_sgtl5000_audio_driver = { + .probe = imx_3stack_sgtl5000_probe, + .remove = imx_3stack_sgtl5000_remove, + .driver = { + .name = "imx-3stack-sgtl5000", + }, +}; + +static struct platform_device *imx_3stack_snd_device; + +static int __init imx_3stack_init(void) +{ + int ret; + + ret = platform_driver_register(&imx_3stack_sgtl5000_audio_driver); + if (ret) + return -ENOMEM; + + imx_3stack_snd_device = platform_device_alloc("soc-audio", 2); + if (!imx_3stack_snd_device) + return -ENOMEM; + + platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata); + imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev; + ret = platform_device_add(imx_3stack_snd_device); + + if (ret) + platform_device_put(imx_3stack_snd_device); + + return ret; +} + +static void __exit imx_3stack_exit(void) +{ + platform_driver_unregister(&imx_3stack_sgtl5000_audio_driver); + platform_device_unregister(imx_3stack_snd_device); +} + +module_init(imx_3stack_init); +module_exit(imx_3stack_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("SGTL5000 Driver for i.MX 3STACK"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/imx/imx-3stack-wm8350.c b/sound/soc/imx/imx-3stack-wm8350.c new file mode 100644 index 000000000000..e51cd907563b --- /dev/null +++ b/sound/soc/imx/imx-3stack-wm8350.c @@ -0,0 +1,704 @@ +/* + * imx-3stack-wm8350.c -- i.MX 3Stack Driver for Wolfson WM8350 Codec + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 19th Jun 2007 Initial version. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/bitops.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/mfd/wm8350/core.h> +#include <linux/mfd/wm8350/audio.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include <mach/dma.h> +#include <mach/spba.h> +#include <mach/clock.h> + +#include "../codecs/wm8350.h" +#include "imx-ssi.h" +#include "imx-pcm.h" + +void gpio_activate_audio_ports(void); + +/* SSI BCLK and LRC master */ +#define WM8350_SSI_MASTER 1 + +struct imx_3stack_priv { + int lr_clk_active; + int playback_active; + int capture_active; + struct platform_device *pdev; + struct wm8350 *wm8350; +}; + +static struct imx_3stack_priv machine_priv; + +struct _wm8350_audio { + unsigned int channels; + snd_pcm_format_t format; + unsigned int rate; + unsigned int sysclk; + unsigned int bclkdiv; + unsigned int clkdiv; + unsigned int lr_rate; +}; + +/* in order of power consumption per rate (lowest first) */ +static const struct _wm8350_audio wm8350_audio[] = { + /* 16bit mono modes */ + + {1, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000, + WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,}, + {1, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000, + WM8350_BCLK_DIV_24, WM8350_DACDIV_6, 32,}, + {1, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000, + WM8350_BCLK_DIV_12, WM8350_DACDIV_3, 32,}, + {1, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000, + WM8350_BCLK_DIV_8, WM8350_DACDIV_2, 32,}, + {1, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000, + WM8350_BCLK_DIV_8, WM8350_DACDIV_2, 32,}, + {1, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600, + WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,}, + {1, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600, + WM8350_BCLK_DIV_16, WM8350_DACDIV_4, 32,}, + {1, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600, + WM8350_BCLK_DIV_8, WM8350_DACDIV_2, 32,}, + {1, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200, + WM8350_BCLK_DIV_8, WM8350_DACDIV_2, 32,}, + + /* 16 bit stereo modes */ + {2, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000, + WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000, + WM8350_BCLK_DIV_24, WM8350_DACDIV_3, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000, + WM8350_BCLK_DIV_12, WM8350_DACDIV_1_5, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600, + WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600, + WM8350_BCLK_DIV_16, WM8350_DACDIV_2, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + + /* 24bit stereo modes */ + {2, SNDRV_PCM_FORMAT_S24_LE, 48000, 12288000, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 96000, 24576000, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 44100, 11289600, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 88200, 22579200, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, +}; + +#if WM8350_SSI_MASTER +static int imx_3stack_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->socdev->card->codec; + struct wm8350 *wm8350 = codec->control_data; + struct imx_3stack_priv *priv = &machine_priv; + + /* In master mode the LR clock can come from either the DAC or ADC. + * We use the LR clock from whatever stream is enabled first. + */ + + if (!priv->lr_clk_active) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_2, + WM8350_LRC_ADC_SEL); + else + wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_2, + WM8350_LRC_ADC_SEL); + } + priv->lr_clk_active++; + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + priv->capture_active = 1; + else + priv->playback_active = 1; + return 0; +} +#else +#define imx_3stack_startup NULL +#endif + +static int imx_3stack_audio_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_link *machine = rtd->dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct snd_soc_dai *codec_dai = machine->codec_dai; + struct imx_3stack_priv *priv = &machine_priv; + int ret = 0; + int i, found = 0; + snd_pcm_format_t format = params_format(params); + unsigned int rate = params_rate(params); + unsigned int channels = params_channels(params); + u32 dai_format; + int clk_id; + + /* only need to do this once as capture and playback are sync */ + if (priv->lr_clk_active > 1) + return 0; + + /* find the correct audio parameters */ + for (i = 0; i < ARRAY_SIZE(wm8350_audio); i++) { + if (rate == wm8350_audio[i].rate && + format == wm8350_audio[i].format && + channels == wm8350_audio[i].channels) { + found = 1; + break; + } + } + if (!found) { + printk(KERN_ERR "%s: invalid params\n", __func__); + return -EINVAL; + } + +#if WM8350_SSI_MASTER + /* codec FLL input is 32768 kHz from MCLK */ + snd_soc_dai_set_pll(codec_dai, 0, 32768, wm8350_audio[i].sysclk); + + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + /* set cpu DAI configuration */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + dai_format &= ~SND_SOC_DAIFMT_INV_MASK; + /* Invert frame to switch mic from right channel to left */ + dai_format |= SND_SOC_DAIFMT_NB_IF; + } + + /* set 32KHZ as the codec system clock for DAC and ADC */ + clk_id = WM8350_MCLK_SEL_PLL_32K; +#else + /* codec FLL input is rate from DAC LRC */ + snd_soc_dai_set_pll(codec_dai, 0, rate, wm8350_audio[i].sysclk); + + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + /* set DAC LRC as the codec system clock for DAC and ADC */ + clk_id = WM8350_MCLK_SEL_PLL_DAC; +#endif + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret < 0) + return ret; + + /* set i.MX active slot mask */ + snd_soc_dai_set_tdm_slot(cpu_dai, + channels == 1 ? 0xfffffffe : 0xfffffffc, + channels); + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, dai_format); + if (ret < 0) + return ret; + + snd_soc_dai_set_sysclk(codec_dai, clk_id, + wm8350_audio[i].sysclk, SND_SOC_CLOCK_IN); + + /* set the SSI system clock as input (unused) */ + snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, SND_SOC_CLOCK_IN); + + /* set codec BCLK division for sample rate */ + snd_soc_dai_set_clkdiv(codec_dai, WM8350_BCLK_CLKDIV, + wm8350_audio[i].bclkdiv); + + /* DAI is synchronous and clocked with DAC LRCLK & ADC LRC */ + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_DACLR_CLKDIV, + wm8350_audio[i].lr_rate); + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_ADCLR_CLKDIV, + wm8350_audio[i].lr_rate); + + /* now configure DAC and ADC clocks */ + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_DAC_CLKDIV, wm8350_audio[i].clkdiv); + + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_ADC_CLKDIV, wm8350_audio[i].clkdiv); + + return 0; +} + +static void imx_3stack_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->socdev->card->codec; + struct snd_soc_dai_link *machine = rtd->dai; + struct snd_soc_dai *codec_dai = machine->codec_dai; + struct imx_3stack_priv *priv = &machine_priv; + struct wm8350 *wm8350 = codec->control_data; + + /* disable the PLL if there are no active Tx or Rx channels */ + if (!codec_dai->active) + snd_soc_dai_set_pll(codec_dai, 0, 0, 0); + priv->lr_clk_active--; + + /* + * We need to keep track of active streams in master mode and + * switch LRC source if necessary. + */ + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + priv->capture_active = 0; + else + priv->playback_active = 0; + + if (priv->capture_active) + wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_2, + WM8350_LRC_ADC_SEL); + else if (priv->playback_active) + wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_2, + WM8350_LRC_ADC_SEL); +} + +/* + * imx_3stack WM8350 HiFi DAI operations. + */ +static struct snd_soc_ops imx_3stack_ops = { + .startup = imx_3stack_startup, + .shutdown = imx_3stack_shutdown, + .hw_params = imx_3stack_audio_hw_params, +}; + +/* need to refine these */ +static struct wm8350_audio_platform_data imx_3stack_wm8350_setup = { + .vmid_discharge_msecs = 1000, + .drain_msecs = 30, + .cap_discharge_msecs = 700, + .vmid_charge_msecs = 700, + .vmid_s_curve = WM8350_S_CURVE_SLOW, + .dis_out4 = WM8350_DISCHARGE_SLOW, + .dis_out3 = WM8350_DISCHARGE_SLOW, + .dis_out2 = WM8350_DISCHARGE_SLOW, + .dis_out1 = WM8350_DISCHARGE_SLOW, + .vroi_out4 = WM8350_TIE_OFF_500R, + .vroi_out3 = WM8350_TIE_OFF_500R, + .vroi_out2 = WM8350_TIE_OFF_500R, + .vroi_out1 = WM8350_TIE_OFF_500R, + .vroi_enable = 0, + .codec_current_on = WM8350_CODEC_ISEL_1_0, + .codec_current_standby = WM8350_CODEC_ISEL_0_5, + .codec_current_charge = WM8350_CODEC_ISEL_1_5, +}; + +static void imx_3stack_init_dam(int ssi_port, int dai_port) +{ + unsigned int ssi_ptcr = 0; + unsigned int dai_ptcr = 0; + unsigned int ssi_pdcr = 0; + unsigned int dai_pdcr = 0; + /* WM8350 uses SSI1 or SSI2 via AUDMUX port dai_port for audio */ + + /* reset port ssi_port & dai_port */ + __raw_writel(0, DAM_PTCR(ssi_port)); + __raw_writel(0, DAM_PTCR(dai_port)); + __raw_writel(0, DAM_PDCR(ssi_port)); + __raw_writel(0, DAM_PDCR(dai_port)); + + /* set to synchronous */ + ssi_ptcr |= AUDMUX_PTCR_SYN; + dai_ptcr |= AUDMUX_PTCR_SYN; + +#if WM8350_SSI_MASTER + /* set Rx sources ssi_port <--> dai_port */ + ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port); + dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port); + + /* set Tx frame direction and source dai_port--> ssi_port output */ + ssi_ptcr |= AUDMUX_PTCR_TFSDIR; + ssi_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, dai_port); + + /* set Tx Clock direction and source dai_port--> ssi_port output */ + ssi_ptcr |= AUDMUX_PTCR_TCLKDIR; + ssi_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, dai_port); +#else + /* set Rx sources ssi_port <--> dai_port */ + ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port); + dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port); + + /* set Tx frame direction and source ssi_port --> dai_port output */ + dai_ptcr |= AUDMUX_PTCR_TFSDIR; + dai_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, ssi_port); + + /* set Tx Clock direction and source ssi_port--> dai_port output */ + dai_ptcr |= AUDMUX_PTCR_TCLKDIR; + dai_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, ssi_port); +#endif + + __raw_writel(ssi_ptcr, DAM_PTCR(ssi_port)); + __raw_writel(dai_ptcr, DAM_PTCR(dai_port)); + __raw_writel(ssi_pdcr, DAM_PDCR(ssi_port)); + __raw_writel(dai_pdcr, DAM_PDCR(dai_port)); +} + +static const struct snd_soc_dapm_route audio_map[] = { + /* SiMIC --> IN1LN (with automatic bias) via SP1 */ + {"IN1RP", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "SiMIC"}, + + /* Mic 1 Jack --> IN1LN and IN1LP (with automatic bias) */ + {"IN1LN", NULL, "Mic Bias"}, + {"IN1LP", NULL, "Mic1 Jack"}, + {"Mic Bias", NULL, "Mic1 Jack"}, + + /* Mic 2 Jack --> IN1RN and IN1RP (with automatic bias) */ + {"IN1RN", NULL, "Mic2 Jack"}, + {"IN1RP", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic2 Jack"}, + + /* Line in Jack --> AUX (L+R) */ + {"IN3R", NULL, "Line In Jack"}, + {"IN3L", NULL, "Line In Jack"}, + + /* Out1 --> Headphone Jack */ + {"Headphone Jack", NULL, "OUT1R"}, + {"Headphone Jack", NULL, "OUT1L"}, + + /* Out1 --> Line Out Jack */ + {"Line Out Jack", NULL, "OUT2R"}, + {"Line Out Jack", NULL, "OUT2L"}, +}; + +static int wm8350_jack_func; +static int wm8350_spk_func; + +static void headphone_detect_handler(struct work_struct *work) +{ + struct imx_3stack_priv *priv = &machine_priv; + struct platform_device *pdev = priv->pdev; + struct wm8350 *wm8350 = priv->wm8350; + + sysfs_notify(&pdev->dev.kobj, NULL, "headphone"); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R); +} + +static DECLARE_DELAYED_WORK(hp_event, headphone_detect_handler); + +static void imx_3stack_jack_handler(struct wm8350 *wm8350, int irq, void *data) +{ + wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R); + schedule_delayed_work(&hp_event, msecs_to_jiffies(200)); +} + +static ssize_t show_headphone(struct device_driver *dev, char *buf) +{ + struct imx_3stack_priv *priv = &machine_priv; + u16 reg; + + reg = wm8350_reg_read(priv->wm8350, WM8350_JACK_PIN_STATUS); + + if (reg & WM8350_JACK_R_LVL) + strcpy(buf, "speaker\n"); + else + strcpy(buf, "headphone\n"); + + return strlen(buf); +} + +static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL); + +static const char *jack_function[] = { "off", "on" +}; + +static const char *spk_function[] = { "off", "on" }; + +static const struct soc_enum wm8350_enum[] = { + SOC_ENUM_SINGLE_EXT(2, jack_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), +}; + +static int wm8350_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = wm8350_jack_func; + return 0; +} + +static int wm8350_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (wm8350_jack_func == ucontrol->value.enumerated.item[0]) + return 0; + + wm8350_jack_func = ucontrol->value.enumerated.item[0]; + if (wm8350_jack_func) + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + else + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + + snd_soc_dapm_sync(codec); + return 1; +} + +static int wm8350_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = wm8350_spk_func; + return 0; +} + +static int wm8350_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (wm8350_spk_func == ucontrol->value.enumerated.item[0]) + return 0; + + wm8350_spk_func = ucontrol->value.enumerated.item[0]; + if (wm8350_spk_func) + snd_soc_dapm_enable_pin(codec, "Line Out Jack"); + else + snd_soc_dapm_disable_pin(codec, "Line Out Jack"); + + snd_soc_dapm_sync(codec); + return 1; +} + +static int spk_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct imx_3stack_priv *priv = &machine_priv; + struct platform_device *pdev = priv->pdev; + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + + if (plat->amp_enable == NULL) + return 0; + + if (SND_SOC_DAPM_EVENT_ON(event)) + plat->amp_enable(1); + else + plat->amp_enable(0); + + return 0; +} + +static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = { + SND_SOC_DAPM_MIC("SiMIC", NULL), + SND_SOC_DAPM_MIC("Mic1 Jack", NULL), + SND_SOC_DAPM_MIC("Mic2 Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", spk_amp_event), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +static const struct snd_kcontrol_new wm8350_machine_controls[] = { + SOC_ENUM_EXT("Jack Function", wm8350_enum[0], wm8350_get_jack, + wm8350_set_jack), + SOC_ENUM_EXT("Speaker Function", wm8350_enum[1], wm8350_get_spk, + wm8350_set_spk), +}; + +static int imx_3stack_wm8350_init(struct snd_soc_codec *codec) +{ + struct imx_3stack_priv *priv = &machine_priv; + struct wm8350 *wm8350 = priv->wm8350; + int i, ret; + + codec->control_data = wm8350; + + /* Add imx_3stack specific controls */ + for (i = 0; i < ARRAY_SIZE(wm8350_machine_controls); i++) { + ret = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8350_machine_controls[i], + codec, NULL)); + if (ret < 0) + return ret; + } + + /* Add imx_3stack specific widgets */ + snd_soc_dapm_new_controls(codec, imx_3stack_dapm_widgets, + ARRAY_SIZE(imx_3stack_dapm_widgets)); + + /* Set up imx_3stack specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(codec); + + return 0; + +} + +/* imx_3stack digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link imx_3stack_dai = { + .name = "WM8350", + .stream_name = "WM8350", + .cpu_dai = &imx_ssi_dai, + .codec_dai = &wm8350_dai, + .init = imx_3stack_wm8350_init, + .ops = &imx_3stack_ops, +}; + +static int imx_3stack_machine_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct imx_3stack_priv *priv = &machine_priv; + struct wm8350 *wm8350 = priv->wm8350; + + socdev->codec_data = wm8350; + wm8350->codec.platform_data = &imx_3stack_wm8350_setup; + + return 0; +} + +static struct snd_soc_card snd_soc_card_imx_3stack = { + .name = "imx-3stack", + .platform = &imx_soc_platform, + .dai_link = &imx_3stack_dai, + .num_links = 1, + .probe = imx_3stack_machine_probe, +}; + +static struct snd_soc_device imx_3stack_snd_devdata = { + .card = &snd_soc_card_imx_3stack, + .codec_dev = &soc_codec_dev_wm8350, +}; + +static int __devinit imx_3stack_wm8350_probe(struct platform_device *pdev) +{ + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + struct imx_3stack_priv *priv = &machine_priv; + struct wm8350 *wm8350 = plat->priv; + int ret = 0; + u16 reg; + + priv->pdev = pdev; + priv->wm8350 = wm8350; + + imx_ssi_dai.private_data = plat; + imx_ssi_dai.dev = &pdev->dev; + + imx_3stack_wm8350_setup.regulator1 = plat->regulator1; + imx_3stack_wm8350_setup.regulator2 = plat->regulator2; + + gpio_activate_audio_ports(); + imx_3stack_init_dam(plat->src_port, plat->ext_port); + + if (plat->src_port == 2) + imx_ssi_dai.name = "imx-ssi-3"; + else + imx_ssi_dai.name = "imx-ssi-1"; + + snd_soc_register_dai(&imx_ssi_dai); + + ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone); + if (ret < 0) { + pr_err("%s:failed to create driver_attr_headphone\n", __func__); + return ret; + } + + /* enable slow clock gen for jack detect */ + reg = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_4); + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_4, reg | WM8350_TOCLK_ENA); + /* enable jack detect */ + reg = wm8350_reg_read(wm8350, WM8350_JACK_DETECT); + wm8350_reg_write(wm8350, WM8350_JACK_DETECT, reg | WM8350_JDR_ENA); + wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R, + imx_3stack_jack_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R); + + wm8350_jack_func = 1; + wm8350_spk_func = 1; + + return 0; +} + +static int imx_3stack_wm8350_remove(struct platform_device *pdev) +{ + struct imx_3stack_priv *priv = &machine_priv; + struct wm8350 *wm8350 = priv->wm8350; + + wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R); + wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R); + + return 0; +} + +static struct platform_driver imx_3stack_wm8350_audio_driver = { + .probe = imx_3stack_wm8350_probe, + .remove = __devexit_p(imx_3stack_wm8350_remove), + .driver = { + .name = "wm8350-imx-3stack-audio", + }, +}; + +static struct platform_device *imx_3stack_snd_device; + +static int __init imx_3stack_init(void) +{ + int ret; + + ret = platform_driver_register(&imx_3stack_wm8350_audio_driver); + if (ret) + return -ENOMEM; + + imx_3stack_snd_device = platform_device_alloc("soc-audio", -1); + if (!imx_3stack_snd_device) + return -ENOMEM; + + platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata); + imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev; + ret = platform_device_add(imx_3stack_snd_device); + + if (ret) + platform_device_put(imx_3stack_snd_device); + + return ret; +} + +static void __exit imx_3stack_exit(void) +{ + platform_driver_unregister(&imx_3stack_wm8350_audio_driver); + platform_device_unregister(imx_3stack_snd_device); +} + +module_init(imx_3stack_init); +module_exit(imx_3stack_exit); + +MODULE_AUTHOR("Liam Girdwood"); +MODULE_DESCRIPTION("PMIC WM8350 Driver for i.MX 3STACK"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/imx/imx-3stack-wm8580.c b/sound/soc/imx/imx-3stack-wm8580.c new file mode 100644 index 000000000000..0dc7b88d1441 --- /dev/null +++ b/sound/soc/imx/imx-3stack-wm8580.c @@ -0,0 +1,436 @@ +/* + * imx-3stack-wm8580.c -- SoC 5.1 audio for imx_3stack + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/regulator/consumer.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include <mach/hardware.h> +#include <mach/clock.h> + +#include "imx-pcm.h" +#include "imx-esai.h" +#include "../codecs/wm8580.h" + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) +#include <linux/mxc_asrc.h> +#endif + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) +static unsigned int asrc_rates[] = { + 0, + 8000, + 11025, + 16000, + 22050, + 32000, + 44100, + 48000, + 64000, + 88200, + 96000, + 176400, + 192000, +}; + +struct asrc_esai { + unsigned int cpu_dai_rates; + unsigned int codec_dai_rates; + enum asrc_pair_index asrc_index; + unsigned int output_sample_rate; +}; + +static struct asrc_esai asrc_esai_data; + +#endif + +struct imx_3stack_pcm_state { + int lr_clk_active; +}; + +extern void gpio_activate_esai_ports(void); +extern void gpio_deactivate_esai_ports(void); + +static struct imx_3stack_pcm_state clk_state; + +static int imx_3stack_startup(struct snd_pcm_substream *substream) +{ + clk_state.lr_clk_active++; +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + if (asrc_esai_data.output_sample_rate != 0) { + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *pcm_link = rtd->dai; + struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai; + struct snd_soc_dai *codec_dai = pcm_link->codec_dai; + asrc_esai_data.cpu_dai_rates = cpu_dai->playback.rates; + asrc_esai_data.codec_dai_rates = codec_dai->playback.rates; + cpu_dai->playback.rates = + SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT; + codec_dai->playback.rates = + SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT; + } +#endif + + return 0; +} + +static void imx_3stack_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *pcm_link = rtd->dai; + struct snd_soc_dai *codec_dai = pcm_link->codec_dai; + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + if (asrc_esai_data.output_sample_rate != 0) { + struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai; + codec_dai->playback.rates = asrc_esai_data.codec_dai_rates; + cpu_dai->playback.rates = asrc_esai_data.cpu_dai_rates; + asrc_release_pair(asrc_esai_data.asrc_index); + } +#endif + + /* disable the PLL if there are no active Tx or Rx channels */ + if (!codec_dai->active) + snd_soc_dai_set_pll(codec_dai, 0, 0, 0); + clk_state.lr_clk_active--; +} + +static int imx_3stack_surround_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_link *pcm_link = rtd->dai; + struct snd_soc_dai *cpu_dai = pcm_link->cpu_dai; + struct snd_soc_dai *codec_dai = pcm_link->codec_dai; + unsigned int rate = params_rate(params); + u32 dai_format; + unsigned int pll_out = 0, lrclk_ratio = 0; + unsigned int channel = params_channels(params); + + if (clk_state.lr_clk_active > 1) + return 0; + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + if (asrc_esai_data.output_sample_rate != 0) { + unsigned int asrc_input_rate = rate; + struct mxc_runtime_data *pcm_data = + substream->runtime->private_data; + struct asrc_config config; + int retVal = 0;; + + retVal = asrc_req_pair(channel, &asrc_esai_data.asrc_index); + if (retVal < 0) { + pr_err("Fail to request asrc pair\n"); + return -1; + } + + config.pair = asrc_esai_data.asrc_index; + config.channel_num = channel; + config.input_sample_rate = asrc_input_rate; + config.output_sample_rate = asrc_esai_data.output_sample_rate; + config.inclk = INCLK_NONE; + config.word_width = 32; + config.outclk = OUTCLK_ESAI_TX; + retVal = asrc_config_pair(&config); + if (retVal < 0) { + pr_err("Fail to config asrc\n"); + asrc_release_pair(asrc_esai_data.asrc_index); + return retVal; + } + rate = asrc_esai_data.output_sample_rate; + pcm_data->asrc_index = asrc_esai_data.asrc_index; + pcm_data->asrc_enable = 1; + } +#endif + + switch (rate) { + case 8000: + lrclk_ratio = 5; + pll_out = 6144000; + break; + case 11025: + lrclk_ratio = 4; + pll_out = 5644800; + break; + case 16000: + lrclk_ratio = 3; + pll_out = 6144000; + break; + case 32000: + lrclk_ratio = 3; + pll_out = 12288000; + break; + case 48000: + lrclk_ratio = 2; + pll_out = 12288000; + break; + case 64000: + lrclk_ratio = 1; + pll_out = 12288000; + break; + case 96000: + lrclk_ratio = 2; + pll_out = 24576000; + break; + case 128000: + lrclk_ratio = 1; + pll_out = 24576000; + break; + case 22050: + lrclk_ratio = 4; + pll_out = 11289600; + break; + case 44100: + lrclk_ratio = 2; + pll_out = 11289600; + break; + case 88200: + lrclk_ratio = 0; + pll_out = 11289600; + break; + case 176400: + lrclk_ratio = 0; + pll_out = 22579200; + break; + case 192000: + lrclk_ratio = 0; + pll_out = 24576000; + break; + default: + pr_info("Rate not support.\n"); + return -EINVAL;; + } + + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + /* set codec DAI configuration */ + snd_soc_dai_set_fmt(codec_dai, dai_format); + + /* set cpu DAI configuration */ + snd_soc_dai_set_fmt(cpu_dai, dai_format); + + /* set i.MX active slot mask */ + snd_soc_dai_set_tdm_slot(cpu_dai, channel == 1 ? 0x1 : 0x3, 2); + + /* set the ESAI system clock as input (unused) */ + snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN); + + snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK, WM8580_CLKSRC_PLLA); + snd_soc_dai_set_clkdiv(codec_dai, WM8580_DAC_CLKSEL, + WM8580_CLKSRC_PLLA); + + /* set codec LRCLK and BCLK */ + snd_soc_dai_set_sysclk(codec_dai, WM8580_BCLK_CLKDIV, 0, + SND_SOC_CLOCK_OUT); + snd_soc_dai_set_sysclk(codec_dai, WM8580_LRCLK_CLKDIV, lrclk_ratio, + SND_SOC_CLOCK_OUT); + + snd_soc_dai_set_pll(codec_dai, 1, 12000000, pll_out); + return 0; +} + +static struct snd_soc_ops imx_3stack_surround_ops = { + .startup = imx_3stack_startup, + .shutdown = imx_3stack_shutdown, + .hw_params = imx_3stack_surround_hw_params, +}; + +static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Line out jack */ + {"Line Out Jack", NULL, "VOUT1L"}, + {"Line Out Jack", NULL, "VOUT1R"}, + {"Line Out Jack", NULL, "VOUT2L"}, + {"Line Out Jack", NULL, "VOUT2R"}, + {"Line Out Jack", NULL, "VOUT3L"}, + {"Line Out Jack", NULL, "VOUT3R"}, +}; + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) +static int asrc_func; + +static const char *asrc_function[] = + { "disable", "8KHz", "11.025KHz", "16KHz", "22.05KHz", "32KHz", "44.1KHz", + "48KHz", "64KHz", "88.2KHz", "96KHz", "176.4KHz", "192KHz" +}; + +static const struct soc_enum asrc_enum[] = { + SOC_ENUM_SINGLE_EXT(13, asrc_function), +}; + +static int asrc_get_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = asrc_func; + return 0; +} + +static int asrc_set_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (asrc_func == ucontrol->value.enumerated.item[0]) + return 0; + + asrc_func = ucontrol->value.enumerated.item[0]; + asrc_esai_data.output_sample_rate = asrc_rates[asrc_func]; + + return 1; +} + +static const struct snd_kcontrol_new asrc_controls[] = { + SOC_ENUM_EXT("ASRC", asrc_enum[0], asrc_get_rate, + asrc_set_rate), +}; + +#endif + +static int imx_3stack_wm8580_init(struct snd_soc_codec *codec) +{ + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + int i; + int ret; + for (i = 0; i < ARRAY_SIZE(asrc_controls); i++) { + ret = snd_ctl_add(codec->card, + snd_soc_cnew(&asrc_controls[i], codec, NULL)); + if (ret < 0) + return ret; + } + asrc_esai_data.output_sample_rate = asrc_rates[asrc_func]; +#endif + + snd_soc_dapm_new_controls(codec, imx_3stack_dapm_widgets, + ARRAY_SIZE(imx_3stack_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(codec); + + return 0; +} + +static struct snd_soc_dai_link imx_3stack_dai = { + .name = "wm8580", + .stream_name = "wm8580", + .cpu_dai = &imx_esai_dai, + .codec_dai = wm8580_dai, + .init = imx_3stack_wm8580_init, + .ops = &imx_3stack_surround_ops, +}; + +static int imx_3stack_card_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + kfree(socdev->codec_data); + return 0; +} + +static struct snd_soc_card snd_soc_card_imx_3stack = { + .name = "imx-3stack", + .platform = &imx_soc_platform, + .dai_link = &imx_3stack_dai, + .num_links = 1, + .remove = imx_3stack_card_remove, +}; + +static struct snd_soc_device imx_3stack_snd_devdata = { + .card = &snd_soc_card_imx_3stack, + .codec_dev = &soc_codec_dev_wm8580, +}; + +/* + * This function will register the snd_soc_pcm_link drivers. + */ +static int __devinit imx_3stack_wm8580_probe(struct platform_device *pdev) +{ + struct wm8580_setup_data *setup; + + imx_esai_dai.name = "imx-esai-txrx"; + + setup = kzalloc(sizeof(struct wm8580_setup_data), GFP_KERNEL); + setup->spi = 1; + imx_3stack_snd_devdata.codec_data = setup; + + /* Configure audio port 3 */ + gpio_activate_esai_ports(); + + return 0; +} + +static int __devexit imx_3stack_wm8580_remove(struct platform_device *pdev) +{ + gpio_deactivate_esai_ports(); + return 0; +} + +static struct platform_driver imx_3stack_wm8580_driver = { + .probe = imx_3stack_wm8580_probe, + .remove = __devexit_p(imx_3stack_wm8580_remove), + .driver = { + .name = "imx-3stack-wm8580", + .owner = THIS_MODULE, + }, +}; + +static struct platform_device *imx_3stack_snd_device; + +static int __init imx_3stack_asoc_init(void) +{ + int ret; + + ret = platform_driver_register(&imx_3stack_wm8580_driver); + if (ret < 0) + goto exit; + imx_3stack_snd_device = platform_device_alloc("soc-audio", 1); + if (!imx_3stack_snd_device) + goto err_device_alloc; + platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata); + imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev; + ret = platform_device_add(imx_3stack_snd_device); + if (0 == ret) + goto exit; + + platform_device_put(imx_3stack_snd_device); + err_device_alloc: + platform_driver_unregister(&imx_3stack_wm8580_driver); + exit: + return ret; +} + +static void __exit imx_3stack_asoc_exit(void) +{ + platform_driver_unregister(&imx_3stack_wm8580_driver); + platform_device_unregister(imx_3stack_snd_device); +} + +module_init(imx_3stack_asoc_init); +module_exit(imx_3stack_asoc_exit); + +/* Module information */ +MODULE_DESCRIPTION("ALSA SoC wm8580 imx_3stack"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/imx/imx-esai.c b/sound/soc/imx/imx-esai.c new file mode 100644 index 000000000000..795e3d4bc704 --- /dev/null +++ b/sound/soc/imx/imx-esai.c @@ -0,0 +1,899 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file imx-esai.c + * @brief this file implements the esai interface + * in according to ASoC architeture + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <mach/dma.h> +#include <mach/clock.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> + +#include "imx-esai.h" +#include "imx-pcm.h" + +/*#define IMX_ESAI_DUMP 1*/ + +#ifdef IMX_ESAI_DUMP +#define ESAI_DUMP() \ + do {pr_info("dump @ %s\n", __func__); \ + pr_info("ecr %x\n", __raw_readl(ESAI_ECR)); \ + pr_info("esr %x\n", __raw_readl(ESAI_ESR)); \ + pr_info("tfcr %x\n", __raw_readl(ESAI_TFCR)); \ + pr_info("tfsr %x\n", __raw_readl(ESAI_TFSR)); \ + pr_info("rfcr %x\n", __raw_readl(ESAI_RFCR)); \ + pr_info("rfsr %x\n", __raw_readl(ESAI_RFSR)); \ + pr_info("tsr %x\n", __raw_readl(ESAI_TSR)); \ + pr_info("saisr %x\n", __raw_readl(ESAI_SAISR)); \ + pr_info("saicr %x\n", __raw_readl(ESAI_SAICR)); \ + pr_info("tcr %x\n", __raw_readl(ESAI_TCR)); \ + pr_info("tccr %x\n", __raw_readl(ESAI_TCCR)); \ + pr_info("rcr %x\n", __raw_readl(ESAI_RCR)); \ + pr_info("rccr %x\n", __raw_readl(ESAI_RCCR)); \ + pr_info("tsma %x\n", __raw_readl(ESAI_TSMA)); \ + pr_info("tsmb %x\n", __raw_readl(ESAI_TSMB)); \ + pr_info("rsma %x\n", __raw_readl(ESAI_RSMA)); \ + pr_info("rsmb %x\n", __raw_readl(ESAI_RSMB)); \ + pr_info("prrc %x\n", __raw_readl(ESAI_PRRC)); \ + pr_info("pcrc %x\n", __raw_readl(ESAI_PCRC)); } while (0); +#else +#define ESAI_DUMP() +#endif + +#define ESAI_IO_BASE_ADDR IO_ADDRESS(ESAI_BASE_ADDR) + +#define ESAI_ETDR (ESAI_IO_BASE_ADDR + 0x00) +#define ESAI_ERDR (ESAI_IO_BASE_ADDR + 0x04) +#define ESAI_ECR (ESAI_IO_BASE_ADDR + 0x08) +#define ESAI_ESR (ESAI_IO_BASE_ADDR + 0x0C) +#define ESAI_TFCR (ESAI_IO_BASE_ADDR + 0x10) +#define ESAI_TFSR (ESAI_IO_BASE_ADDR + 0x14) +#define ESAI_RFCR (ESAI_IO_BASE_ADDR + 0x18) +#define ESAI_RFSR (ESAI_IO_BASE_ADDR + 0x1C) +#define ESAI_TX0 (ESAI_IO_BASE_ADDR + 0x80) +#define ESAI_TX1 (ESAI_IO_BASE_ADDR + 0x84) +#define ESAI_TX2 (ESAI_IO_BASE_ADDR + 0x88) +#define ESAI_TX3 (ESAI_IO_BASE_ADDR + 0x8C) +#define ESAI_TX4 (ESAI_IO_BASE_ADDR + 0x90) +#define ESAI_TX5 (ESAI_IO_BASE_ADDR + 0x94) +#define ESAI_TSR (ESAI_IO_BASE_ADDR + 0x98) +#define ESAI_RX0 (ESAI_IO_BASE_ADDR + 0xA0) +#define ESAI_RX1 (ESAI_IO_BASE_ADDR + 0xA4) +#define ESAI_RX2 (ESAI_IO_BASE_ADDR + 0xA8) +#define ESAI_RX3 (ESAI_IO_BASE_ADDR + 0xAC) +#define ESAI_SAISR (ESAI_IO_BASE_ADDR + 0xCC) +#define ESAI_SAICR (ESAI_IO_BASE_ADDR + 0xD0) +#define ESAI_TCR (ESAI_IO_BASE_ADDR + 0xD4) +#define ESAI_TCCR (ESAI_IO_BASE_ADDR + 0xD8) +#define ESAI_RCR (ESAI_IO_BASE_ADDR + 0xDC) +#define ESAI_RCCR (ESAI_IO_BASE_ADDR + 0xE0) +#define ESAI_TSMA (ESAI_IO_BASE_ADDR + 0xE4) +#define ESAI_TSMB (ESAI_IO_BASE_ADDR + 0xE8) +#define ESAI_RSMA (ESAI_IO_BASE_ADDR + 0xEC) +#define ESAI_RSMB (ESAI_IO_BASE_ADDR + 0xF0) +#define ESAI_PRRC (ESAI_IO_BASE_ADDR + 0xF8) +#define ESAI_PCRC (ESAI_IO_BASE_ADDR + 0xFC) + +#define ESAI_ECR_ETI (1 << 19) +#define ESAI_ECR_ETO (1 << 18) +#define ESAI_ECR_ERI (1 << 17) +#define ESAI_ECR_ERO (1 << 16) +#define ESAI_ECR_ERST (1 << 1) +#define ESAI_ECR_ESAIEN (1 << 0) + +#define ESAI_ESR_TINIT (1 << 10) +#define ESAI_ESR_RFF (1 << 9) +#define ESAI_ESR_TFE (1 << 8) +#define ESAI_ESR_TLS (1 << 7) +#define ESAI_ESR_TDE (1 << 6) +#define ESAI_ESR_TED (1 << 5) +#define ESAI_ESR_TD (1 << 4) +#define ESAI_ESR_RLS (1 << 3) +#define ESAI_ESR_RDE (1 << 2) +#define ESAI_ESR_RED (1 << 1) +#define ESAI_ESR_RD (1 << 0) + +#define ESAI_TFCR_TIEN (1 << 19) +#define ESAI_TFCR_TE5 (1 << 7) +#define ESAI_TFCR_TE4 (1 << 6) +#define ESAI_TFCR_TE3 (1 << 5) +#define ESAI_TFCR_TE2 (1 << 4) +#define ESAI_TFCR_TE1 (1 << 3) +#define ESAI_TFCR_TE0 (1 << 2) +#define ESAI_TFCR_TFR (1 << 1) +#define ESAI_TFCR_TFEN (1 << 0) +#define ESAI_TFCR_TE(x) ((0x3f >> (6 - ((x + 1) >> 1))) << 2) +#define ESAI_TFCR_TE_MASK 0xfff03 +#define ESAI_TFCR_TFWM(x) ((x - 1) << 8) +#define ESAI_TFCR_TWA_MASK 0xf8ffff + +#define ESAI_RFCR_REXT (1 << 19) +#define ESAI_RFCR_RE3 (1 << 5) +#define ESAI_RFCR_RE2 (1 << 4) +#define ESAI_RFCR_RE1 (1 << 3) +#define ESAI_RFCR_RE0 (1 << 2) +#define ESAI_RFCR_RFR (1 << 1) +#define ESAI_RFCR_RFEN (1 << 0) +#define ESAI_RFCR_RE(x) ((0xf >> (4 - ((x + 1) >> 1))) << 3) +#define ESAI_RFCR_RE_MASK 0xfffc3 +#define ESAI_RFCR_RFWM(x) ((x-1) << 8) +#define ESAI_RFCR_RWA_MASK 0xf8ffff + +#define ESAI_WORD_LEN_32 (0x00 << 16) +#define ESAI_WORD_LEN_28 (0x01 << 16) +#define ESAI_WORD_LEN_24 (0x02 << 16) +#define ESAI_WORD_LEN_20 (0x03 << 16) +#define ESAI_WORD_LEN_16 (0x04 << 16) +#define ESAI_WORD_LEN_12 (0x05 << 16) +#define ESAI_WORD_LEN_8 (0x06 << 16) +#define ESAI_WORD_LEN_4 (0x07 << 16) + +#define ESAI_SAISR_TODFE (1 << 17) +#define ESAI_SAISR_TEDE (1 << 16) +#define ESAI_SAISR_TDE (1 << 15) +#define ESAI_SAISR_TUE (1 << 14) +#define ESAI_SAISR_TFS (1 << 13) +#define ESAI_SAISR_RODF (1 << 10) +#define ESAI_SAISR_REDF (1 << 9) +#define ESAI_SAISR_RDF (1 << 8) +#define ESAI_SAISR_ROE (1 << 7) +#define ESAI_SAISR_RFS (1 << 6) +#define ESAI_SAISR_IF2 (1 << 2) +#define ESAI_SAISR_IF1 (1 << 1) +#define ESAI_SAISR_IF0 (1 << 0) + +#define ESAI_SAICR_ALC (1 << 8) +#define ESAI_SAICR_TEBE (1 << 7) +#define ESAI_SAICR_SYNC (1 << 6) +#define ESAI_SAICR_OF2 (1 << 2) +#define ESAI_SAICR_OF1 (1 << 1) +#define ESAI_SAICR_OF0 (1 << 0) + +#define ESAI_TCR_TLIE (1 << 23) +#define ESAI_TCR_TIE (1 << 22) +#define ESAI_TCR_TEDIE (1 << 21) +#define ESAI_TCR_TEIE (1 << 20) +#define ESAI_TCR_TPR (1 << 19) +#define ESAI_TCR_PADC (1 << 17) +#define ESAI_TCR_TFSR (1 << 16) +#define ESAI_TCR_TFSL (1 << 15) +#define ESAI_TCR_TWA (1 << 7) +#define ESAI_TCR_TSHFD_MSB (0 << 6) +#define ESAI_TCR_TSHFD_LSB (1 << 6) +#define ESAI_TCR_TE5 (1 << 5) +#define ESAI_TCR_TE4 (1 << 4) +#define ESAI_TCR_TE3 (1 << 3) +#define ESAI_TCR_TE2 (1 << 2) +#define ESAI_TCR_TE1 (1 << 1) +#define ESAI_TCR_TE0 (1 << 0) +#define ESAI_TCR_TE(x) (0x3f >> (6 - ((x + 1) >> 1))) + +#define ESAI_TCR_TSWS_MASK 0xff83ff +#define ESAI_TCR_TSWS_STL8_WDL8 (0x00 << 10) +#define ESAI_TCR_TSWS_STL12_WDL8 (0x04 << 10) +#define ESAI_TCR_TSWS_STL12_WDL12 (0x01 << 10) +#define ESAI_TCR_TSWS_STL16_WDL8 (0x08 << 10) +#define ESAI_TCR_TSWS_STL16_WDL12 (0x05 << 10) +#define ESAI_TCR_TSWS_STL16_WDL16 (0x02 << 10) +#define ESAI_TCR_TSWS_STL20_WDL8 (0x0c << 10) +#define ESAI_TCR_TSWS_STL20_WDL12 (0x09 << 10) +#define ESAI_TCR_TSWS_STL20_WDL16 (0x06 << 10) +#define ESAI_TCR_TSWS_STL20_WDL20 (0x03 << 10) +#define ESAI_TCR_TSWS_STL24_WDL8 (0x10 << 10) +#define ESAI_TCR_TSWS_STL24_WDL12 (0x0d << 10) +#define ESAI_TCR_TSWS_STL24_WDL16 (0x0a << 10) +#define ESAI_TCR_TSWS_STL24_WDL20 (0x07 << 10) +#define ESAI_TCR_TSWS_STL24_WDL24 (0x1e << 10) +#define ESAI_TCR_TSWS_STL32_WDL8 (0x18 << 10) +#define ESAI_TCR_TSWS_STL32_WDL12 (0x15 << 10) +#define ESAI_TCR_TSWS_STL32_WDL16 (0x12 << 10) +#define ESAI_TCR_TSWS_STL32_WDL20 (0x0f << 10) +#define ESAI_TCR_TSWS_STL32_WDL24 (0x1f << 10) + +#define ESAI_TCR_TMOD_MASK 0xfffcff +#define ESAI_TCR_TMOD_NORMAL (0x00 << 8) +#define ESAI_TCR_TMOD_ONDEMAND (0x01 << 8) +#define ESAI_TCR_TMOD_NETWORK (0x01 << 8) +#define ESAI_TCR_TMOD_RESERVED (0x02 << 8) +#define ESAI_TCR_TMOD_AC97 (0x03 << 8) + +#define ESAI_TCCR_THCKD (1 << 23) +#define ESAI_TCCR_TFSD (1 << 22) +#define ESAI_TCCR_TCKD (1 << 21) +#define ESAI_TCCR_THCKP (1 << 20) +#define ESAI_TCCR_TFSP (1 << 19) +#define ESAI_TCCR_TCKP (1 << 18) + +#define ESAI_TCCR_TPSR_MASK 0xfffeff +#define ESAI_TCCR_TPSR_BYPASS (1 << 8) +#define ESAI_TCCR_TPSR_DIV8 (0 << 8) + +#define ESAI_TCCR_TFP_MASK 0xfc3fff +#define ESAI_TCCR_TFP(x) ((x & 0xf) << 14) + +#define ESAI_TCCR_TDC_MASK 0xffc1ff +#define ESAI_TCCR_TDC(x) (((x) & 0x1f) << 9) + +#define ESAI_TCCR_TPM_MASK 0xffff00 +#define ESAI_TCCR_TPM(x) (x & 0xff) + +#define ESAI_RCR_RLIE (1 << 23) +#define ESAI_RCR_RIE (1 << 22) +#define ESAI_RCR_REDIE (1 << 21) +#define ESAI_RCR_REIE (1 << 20) +#define ESAI_RCR_RPR (1 << 19) +#define ESAI_RCR_RFSR (1 << 16) +#define ESAI_RCR_RFSL (1 << 15) +#define ESAI_RCR_RWA (1 << 7) +#define ESAI_RCR_RSHFD_MSB (0 << 6) +#define ESAI_RCR_RSHFD_LSB (1 << 6) +#define ESAI_RCR_RE3 (1 << 3) +#define ESAI_RCR_RE2 (1 << 2) +#define ESAI_RCR_RE1 (1 << 1) +#define ESAI_RCR_RE0 (1 << 0) +#define ESAI_RCR_RE(x) ((0xf >> (4 - ((x + 1) >> 1))) << 1) + +#define ESAI_RCR_RSWS_MASK 0xff83ff +#define ESAI_RCR_RSWS_STL8_WDL8 (0x00 << 10) +#define ESAI_RCR_RSWS_STL12_WDL8 (0x04 << 10) +#define ESAI_RCR_RSWS_STL12_WDL12 (0x01 << 10) +#define ESAI_RCR_RSWS_STL16_WDL8 (0x08 << 10) +#define ESAI_RCR_RSWS_STL16_WDL12 (0x05 << 10) +#define ESAI_RCR_RSWS_STL16_WDL16 (0x02 << 10) +#define ESAI_RCR_RSWS_STL20_WDL8 (0x0c << 10) +#define ESAI_RCR_RSWS_STL20_WDL12 (0x09 << 10) +#define ESAI_RCR_RSWS_STL20_WDL16 (0x06 << 10) +#define ESAI_RCR_RSWS_STL20_WDL20 (0x03 << 10) +#define ESAI_RCR_RSWS_STL24_WDL8 (0x10 << 10) +#define ESAI_RCR_RSWS_STL24_WDL12 (0x0d << 10) +#define ESAI_RCR_RSWS_STL24_WDL16 (0x0a << 10) +#define ESAI_RCR_RSWS_STL24_WDL20 (0x07 << 10) +#define ESAI_RCR_RSWS_STL24_WDL24 (0x1e << 10) +#define ESAI_RCR_RSWS_STL32_WDL8 (0x18 << 10) +#define ESAI_RCR_RSWS_STL32_WDL12 (0x15 << 10) +#define ESAI_RCR_RSWS_STL32_WDL16 (0x12 << 10) +#define ESAI_RCR_RSWS_STL32_WDL20 (0x0f << 10) +#define ESAI_RCR_RSWS_STL32_WDL24 (0x1f << 10) + +#define ESAI_RCR_RMOD_MASK 0xfffcff +#define ESAI_RCR_RMOD_NORMAL (0x00 << 8) +#define ESAI_RCR_RMOD_ONDEMAND (0x01 << 8) +#define ESAI_RCR_RMOD_NETWORK (0x01 << 8) +#define ESAI_RCR_RMOD_RESERVED (0x02 << 8) +#define ESAI_RCR_RMOD_AC97 (0x03 << 8) + +#define ESAI_RCCR_RHCKD (1 << 23) +#define ESAI_RCCR_RFSD (1 << 22) +#define ESAI_RCCR_RCKD (1 << 21) +#define ESAI_RCCR_RHCKP (1 << 20) +#define ESAI_RCCR_RFSP (1 << 19) +#define ESAI_RCCR_RCKP (1 << 18) + +#define ESAI_RCCR_RPSR_MASK 0xfffeff +#define ESAI_RCCR_RPSR_BYPASS (1 << 8) +#define ESAI_RCCR_RPSR_DIV8 (0 << 8) + +#define ESAI_RCCR_RFP_MASK 0xfc3fff +#define ESAI_RCCR_RFP(x) ((x & 0xf) << 14) + +#define ESAI_RCCR_RDC_MASK 0xffc1ff +#define ESAI_RCCR_RDC(x) (((x) & 0x1f) << 9) + +#define ESAI_RCCR_RPM_MASK 0xffff00 +#define ESAI_RCCR_RPM(x) (x & 0xff) + +#define ESAI_GPIO_ESAI 0xfff + +/* ESAI clock source */ +#define ESAI_CLK_FSYS 0 +#define ESAI_CLK_EXTAL 1 + +/* ESAI clock divider */ +#define ESAI_TX_DIV_PSR 0 +#define ESAI_TX_DIV_PM 1 +#define ESAI_TX_DIV_FP 2 +#define ESAI_RX_DIV_PSR 3 +#define ESAI_RX_DIV_PM 4 +#define ESAI_RX_DIV_FP 5 + +static int imx_esai_txrx_state; + +static int imx_esai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + u32 ecr, tccr, rccr; + + ecr = __raw_readl(ESAI_ECR); + tccr = __raw_readl(ESAI_TCCR); + rccr = __raw_readl(ESAI_RCCR); + + if (dir == SND_SOC_CLOCK_IN) { + if (cpu_dai->id & IMX_DAI_ESAI_TX) + tccr &= + ~(ESAI_TCCR_THCKD | ESAI_TCCR_TCKD | + ESAI_TCCR_TFSD); + if (cpu_dai->id & IMX_DAI_ESAI_RX) + rccr &= + ~(ESAI_RCCR_RHCKD | ESAI_RCCR_RCKD | + ESAI_RCCR_RFSD); + } else { + if (cpu_dai->id & IMX_DAI_ESAI_TX) + tccr |= + ESAI_TCCR_THCKD | ESAI_TCCR_TCKD | ESAI_TCCR_TFSD; + if (cpu_dai->id & IMX_DAI_ESAI_RX) + rccr |= + ESAI_RCCR_RHCKD | ESAI_RCCR_RCKD | ESAI_RCCR_RFSD; + if (clk_id == ESAI_CLK_FSYS) { + if (cpu_dai->id & IMX_DAI_ESAI_TX) + ecr &= ~(ESAI_ECR_ETI | ESAI_ECR_ETO); + if (cpu_dai->id & IMX_DAI_ESAI_RX) + ecr &= ~(ESAI_ECR_ERI | ESAI_ECR_ERO); + } else if (clk_id == ESAI_CLK_EXTAL) { + if (cpu_dai->id & IMX_DAI_ESAI_TX) { + ecr |= ESAI_ECR_ETI; + ecr &= ~ESAI_ECR_ETO; + } + if (cpu_dai->id & IMX_DAI_ESAI_RX) { + ecr |= ESAI_ECR_ERI; + ecr &= ~ESAI_ECR_ERO; + } + } + } + + __raw_writel(ecr, ESAI_ECR); + if (cpu_dai->id & IMX_DAI_ESAI_TX) + __raw_writel(tccr, ESAI_TCCR); + if (cpu_dai->id & IMX_DAI_ESAI_RX) + __raw_writel(rccr, ESAI_RCCR); + + ESAI_DUMP(); + + return 0; +} + +static int imx_esai_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + u32 tccr, rccr; + + tccr = __raw_readl(ESAI_TCCR); + rccr = __raw_readl(ESAI_RCCR); + + switch (div_id) { + case ESAI_TX_DIV_PSR: + tccr &= ESAI_TCCR_TPSR_MASK; + tccr |= div; + break; + case ESAI_TX_DIV_PM: + tccr &= ESAI_TCCR_TPM_MASK; + tccr |= ESAI_TCCR_TPM(div); + break; + case ESAI_TX_DIV_FP: + tccr &= ESAI_TCCR_TFP_MASK; + tccr |= ESAI_TCCR_TFP(div); + break; + case ESAI_RX_DIV_PSR: + rccr &= ESAI_RCCR_RPSR_MASK; + rccr |= div; + break; + case ESAI_RX_DIV_PM: + rccr &= ESAI_RCCR_RPM_MASK; + rccr |= ESAI_RCCR_RPM(div); + break; + case ESAI_RX_DIV_FP: + rccr &= ESAI_RCCR_RFP_MASK; + rccr |= ESAI_RCCR_RFP(div); + break; + return -EINVAL; + } + if (cpu_dai->id & IMX_DAI_ESAI_TX) + __raw_writel(tccr, ESAI_TCCR); + if (cpu_dai->id & IMX_DAI_ESAI_RX) + __raw_writel(rccr, ESAI_RCCR); + return 0; +} + +/* + * ESAI Network Mode or TDM slots configuration. + */ +static int imx_esai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, + unsigned int mask, int slots) +{ + u32 tcr, rcr, tccr, rccr; + + if (cpu_dai->id & IMX_DAI_ESAI_TX) { + tcr = __raw_readl(ESAI_TCR); + tccr = __raw_readl(ESAI_TCCR); + + tcr &= ESAI_TCR_TMOD_MASK; + tcr |= ESAI_TCR_TMOD_NETWORK; + + tccr &= ESAI_TCCR_TDC_MASK; + tccr |= ESAI_TCCR_TDC(slots - 1); + + __raw_writel(tcr, ESAI_TCR); + __raw_writel(tccr, ESAI_TCCR); + __raw_writel((mask & 0xffff), ESAI_TSMA); + __raw_writel(((mask >> 16) & 0xffff), ESAI_TSMB); + } + + if (cpu_dai->id & IMX_DAI_ESAI_RX) { + rcr = __raw_readl(ESAI_RCR); + rccr = __raw_readl(ESAI_RCCR); + + rcr &= ESAI_RCR_RMOD_MASK; + rcr |= ESAI_RCR_RMOD_NETWORK; + + rccr &= ESAI_RCCR_RDC_MASK; + rccr |= ESAI_RCCR_RDC(slots - 1); + + __raw_writel(rcr, ESAI_RCR); + __raw_writel(rccr, ESAI_RCCR); + __raw_writel((mask & 0xffff), ESAI_RSMA); + __raw_writel(((mask >> 16) & 0xffff), ESAI_RSMB); + } + + ESAI_DUMP(); + + return 0; +} + +/* + * ESAI DAI format configuration. + */ +static int imx_esai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + bool sync_mode = cpu_dai->symmetric_rates; + u32 tcr, tccr, rcr, rccr, saicr; + + tcr = __raw_readl(ESAI_TCR); + tccr = __raw_readl(ESAI_TCCR); + rcr = __raw_readl(ESAI_RCR); + rccr = __raw_readl(ESAI_RCCR); + saicr = __raw_readl(ESAI_SAICR); + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* data on rising edge of bclk, frame low 1clk before data */ + tcr &= ~ESAI_TCR_TFSL; + tcr |= ESAI_TCR_TFSR; + rcr &= ~ESAI_RCR_RFSL; + rcr |= ESAI_RCR_RFSR; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* data on rising edge of bclk, frame high with data */ + tcr &= ~(ESAI_TCR_TFSL | ESAI_TCR_TFSR); + rcr &= ~(ESAI_RCR_RFSL | ESAI_RCR_RFSR); + break; + case SND_SOC_DAIFMT_DSP_B: + /* data on rising edge of bclk, frame high with data */ + tcr |= ESAI_TCR_TFSL; + rcr |= ESAI_RCR_RFSL; + break; + case SND_SOC_DAIFMT_DSP_A: + /* data on rising edge of bclk, frame high 1clk before data */ + tcr |= ESAI_TCR_TFSL; + rcr |= ESAI_RCR_RFSL; + break; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + tccr |= ESAI_TCCR_TFSP; + tccr &= ~(ESAI_TCCR_TCKP | ESAI_TCCR_THCKP); + rccr &= ~(ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP); + rccr |= ESAI_RCCR_RFSP; + break; + case SND_SOC_DAIFMT_IB_NF: + tccr &= ~(ESAI_TCCR_TCKP | ESAI_TCCR_THCKP | ESAI_TCCR_TFSP); + rccr &= ~(ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP | ESAI_RCCR_RFSP); + break; + case SND_SOC_DAIFMT_NB_IF: + tccr |= ESAI_TCCR_TCKP | ESAI_TCCR_THCKP | ESAI_TCCR_TFSP; + rccr |= ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP | ESAI_RCCR_RFSP; + break; + case SND_SOC_DAIFMT_NB_NF: + tccr &= ~ESAI_TCCR_TFSP; + tccr |= ESAI_TCCR_TCKP | ESAI_TCCR_THCKP; + rccr &= ~ESAI_RCCR_RFSP; + rccr |= ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP; + break; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + tccr &= ~(ESAI_TCCR_TFSD | ESAI_TCCR_TCKD); + rccr &= ~(ESAI_RCCR_RFSD | ESAI_RCCR_RCKD); + break; + case SND_SOC_DAIFMT_CBS_CFM: + tccr &= ~ESAI_TCCR_TFSD; + tccr |= ESAI_TCCR_TCKD; + rccr &= ~ESAI_RCCR_RFSD; + rccr |= ESAI_RCCR_RCKD; + break; + case SND_SOC_DAIFMT_CBM_CFS: + tccr &= ~ESAI_TCCR_TCKD; + tccr |= ESAI_TCCR_TFSD; + rccr &= ~ESAI_RCCR_RCKD; + rccr |= ESAI_RCCR_RFSD; + break; + case SND_SOC_DAIFMT_CBS_CFS: + tccr |= (ESAI_TCCR_TFSD | ESAI_TCCR_TCKD); + rccr |= (ESAI_RCCR_RFSD | ESAI_RCCR_RCKD); + } + + /* sync */ + if (sync_mode) + saicr |= ESAI_SAICR_SYNC; + else + saicr &= ~ESAI_SAICR_SYNC; + + if (cpu_dai->id & IMX_DAI_ESAI_TX) { + __raw_writel(tcr, ESAI_TCR); + __raw_writel(tccr, ESAI_TCCR); + } + if (cpu_dai->id & IMX_DAI_ESAI_RX) { + __raw_writel(rcr, ESAI_RCR); + __raw_writel(rccr, ESAI_RCCR); + } + + __raw_writel(saicr, ESAI_SAICR); + + ESAI_DUMP(); + return 0; +} + +static struct clk *esai_clk; + +static int fifo_err_counter; + +static irqreturn_t esai_irq(int irq, void *dev_id) +{ + if (fifo_err_counter++ % 1000 == 0) + printk(KERN_ERR + "esai_irq SAISR %x fifo_errs=%d\n", + __raw_readl(ESAI_SAISR), fifo_err_counter); + return IRQ_HANDLED; +} + +static int imx_esai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + if (cpu_dai->playback.active && (cpu_dai->id & IMX_DAI_ESAI_TX)) + return 0; + if (cpu_dai->capture.active && (cpu_dai->id & IMX_DAI_ESAI_RX)) + return 0; + + if (!(imx_esai_txrx_state & IMX_DAI_ESAI_TXRX)) { + if (request_irq(MXC_INT_ESAI, esai_irq, 0, "esai", NULL)) { + pr_err("%s: failure requesting esai irq\n", __func__); + return -EBUSY; + } + clk_enable(esai_clk); + __raw_writel(ESAI_ECR_ERST, ESAI_ECR); + __raw_writel(ESAI_ECR_ESAIEN, ESAI_ECR); + + __raw_writel(ESAI_GPIO_ESAI, ESAI_PRRC); + __raw_writel(ESAI_GPIO_ESAI, ESAI_PCRC); + } + + if (cpu_dai->id & IMX_DAI_ESAI_TX) { + imx_esai_txrx_state |= IMX_DAI_ESAI_TX; + __raw_writel(ESAI_TCR_TPR, ESAI_TCR); + } + if (cpu_dai->id & IMX_DAI_ESAI_RX) { + imx_esai_txrx_state |= IMX_DAI_ESAI_RX; + __raw_writel(ESAI_RCR_RPR, ESAI_RCR); + } + + ESAI_DUMP(); + return 0; +} + +/* + * This function is called to initialize the TX port before enable + * the tx port. + */ +static int imx_esai_hw_tx_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + u32 tcr, tfcr; + unsigned int channels; + + tcr = __raw_readl(ESAI_TCR); + tfcr = __raw_readl(ESAI_TFCR); + + tfcr |= ESAI_TFCR_TFR; + __raw_writel(tfcr, ESAI_TFCR); + tfcr &= ~ESAI_TFCR_TFR; + /* DAI data (word) size */ + tfcr &= ESAI_TFCR_TWA_MASK; + tcr &= ESAI_TCR_TSWS_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + tfcr |= ESAI_WORD_LEN_16; + tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + tfcr |= ESAI_WORD_LEN_20; + tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + tfcr |= ESAI_WORD_LEN_24; + tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL24; + break; + } + + channels = params_channels(params); + tfcr &= ESAI_TFCR_TE_MASK; + tfcr |= ESAI_TFCR_TE(channels); + + tfcr |= ESAI_TFCR_TFWM(64); + + /* Left aligned, Zero padding */ + tcr |= ESAI_TCR_PADC; + + __raw_writel(tcr, ESAI_TCR); + __raw_writel(tfcr, ESAI_TFCR); + + ESAI_DUMP(); + return 0; +} + +/* + * This function is called to initialize the RX port before enable + * the rx port. + */ +static int imx_esai_hw_rx_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + u32 rcr, rfcr; + unsigned int channels; + + rcr = __raw_readl(ESAI_RCR); + rfcr = __raw_readl(ESAI_RFCR); + + rfcr |= ESAI_RFCR_RFR; + __raw_writel(rfcr, ESAI_RFCR); + rfcr &= ~ESAI_RFCR_RFR; + + rfcr &= ESAI_RFCR_RWA_MASK; + rcr &= ESAI_RCR_RSWS_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + rfcr |= ESAI_WORD_LEN_16; + rcr |= ESAI_RCR_RSHFD_MSB | ESAI_RCR_RSWS_STL16_WDL16; + break; + } + + channels = params_channels(params); + rfcr &= ESAI_RFCR_RE_MASK; + rfcr |= ESAI_RFCR_RE(channels); + + rfcr |= ESAI_RFCR_RFWM(64); + + __raw_writel(rcr, ESAI_RCR); + __raw_writel(rfcr, ESAI_RFCR); + return 0; +} + +/* + * This function is called to initialize the TX or RX port, + */ +static int imx_esai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + /* Tx/Rx config */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (__raw_readl(ESAI_TCR) & ESAI_TCR_TE0) + return 0; + return imx_esai_hw_tx_params(substream, params, dai); + } else { + if (__raw_readl(ESAI_RCR) & ESAI_RCR_RE1) + return 0; + return imx_esai_hw_rx_params(substream, params, dai); + } +} + +static int imx_esai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + u32 reg, tfcr = 0, rfcr = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + tfcr = __raw_readl(ESAI_TFCR); + reg = __raw_readl(ESAI_TCR); + } else { + rfcr = __raw_readl(ESAI_RFCR); + reg = __raw_readl(ESAI_RCR); + } + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + tfcr |= ESAI_TFCR_TFEN; + __raw_writel(tfcr, ESAI_TFCR); + reg &= ~ESAI_TCR_TPR; + reg |= ESAI_TCR_TE(substream->runtime->channels); + __raw_writel(reg, ESAI_TCR); + } else { + rfcr |= ESAI_RFCR_RFEN; + __raw_writel(rfcr, ESAI_RFCR); + reg &= ~ESAI_RCR_RPR; + reg |= ESAI_RCR_RE(substream->runtime->channels); + __raw_writel(reg, ESAI_RCR); + } + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg &= ~ESAI_TCR_TE(substream->runtime->channels); + __raw_writel(reg, ESAI_TCR); + reg |= ESAI_TCR_TPR; + __raw_writel(reg, ESAI_TCR); + tfcr |= ESAI_TFCR_TFR; + tfcr &= ~ESAI_TFCR_TFEN; + __raw_writel(tfcr, ESAI_TFCR); + tfcr &= ~ESAI_TFCR_TFR; + __raw_writel(tfcr, ESAI_TFCR); + } else { + reg &= ~ESAI_RCR_RE(substream->runtime->channels); + __raw_writel(reg, ESAI_RCR); + reg |= ESAI_RCR_RPR; + __raw_writel(reg, ESAI_RCR); + rfcr |= ESAI_RFCR_RFR; + rfcr &= ~ESAI_RFCR_RFEN; + __raw_writel(rfcr, ESAI_RFCR); + rfcr &= ~ESAI_RFCR_RFR; + __raw_writel(rfcr, ESAI_RFCR); + } + break; + default: + return -EINVAL; + } + + ESAI_DUMP(); + return 0; +} + +static void imx_esai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (dai->id & IMX_DAI_ESAI_TX) + imx_esai_txrx_state &= ~IMX_DAI_ESAI_TX; + if (dai->id & IMX_DAI_ESAI_RX) + imx_esai_txrx_state &= ~IMX_DAI_ESAI_RX; + + /* shutdown ESAI if neither Tx or Rx is active */ + if (!(imx_esai_txrx_state & IMX_DAI_ESAI_TXRX)) { + free_irq(MXC_INT_ESAI, NULL); + clk_disable(esai_clk); + } +} + +#ifdef CONFIG_PM +static int imx_esai_suspend(struct snd_soc_dai *dai) +{ + if (!dai->active) + return 0; + + /*do we need to disable any clocks */ + return 0; +} + +static int imx_esai_resume(struct snd_soc_dai *dai) +{ + if (!dai->active) + return 0; + + /* do we need to enable any clocks */ + return 0; +} + +#else +#define imx_esai_suspend NULL +#define imx_esai_resume NULL +#endif + +static int imx_esai_probe(struct platform_device *pdev, struct snd_soc_dai *dai) +{ + if (!strcmp("imx-esai-tx", dai->name)) + dai->id = IMX_DAI_ESAI_TX; + else if (!strcmp("imx-esai-rx", dai->name)) + dai->id = IMX_DAI_ESAI_RX; + else if (!strcmp("imx-esai-txrx", dai->name)) + dai->id = IMX_DAI_ESAI_TXRX; + else { + pr_err("%s: invalid device %s\n", __func__, dai->name); + return -ENODEV; + } + + imx_esai_txrx_state = 0; + + esai_clk = clk_get(NULL, "esai_clk"); + + return 0; +} + +static void imx_esai_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + + clk_put(esai_clk); +} + +#define IMX_ESAI_RATES SNDRV_PCM_RATE_8000_192000 + +#define IMX_ESAI_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops imx_esai_dai_ops = { + .startup = imx_esai_startup, + .shutdown = imx_esai_shutdown, + .trigger = imx_esai_trigger, + .hw_params = imx_esai_hw_params, + .set_sysclk = imx_esai_set_dai_sysclk, + .set_clkdiv = imx_esai_set_dai_clkdiv, + .set_fmt = imx_esai_set_dai_fmt, + .set_tdm_slot = imx_esai_set_dai_tdm_slot, +}; + +struct snd_soc_dai imx_esai_dai = { + .name = "imx-esai", + .id = 0, + .probe = imx_esai_probe, + .remove = imx_esai_remove, + .suspend = imx_esai_suspend, + .resume = imx_esai_resume, + .playback = { + .channels_min = 1, + .channels_max = 6, + .rates = IMX_ESAI_RATES, + .formats = IMX_ESAI_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 4, + .rates = IMX_ESAI_RATES, + .formats = IMX_ESAI_FORMATS, + }, + .ops = &imx_esai_dai_ops, +}; + +EXPORT_SYMBOL_GPL(imx_esai_dai); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX ASoC ESAI driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/imx/imx-esai.h b/sound/soc/imx/imx-esai.h new file mode 100644 index 000000000000..8ac2e3fbe6d3 --- /dev/null +++ b/sound/soc/imx/imx-esai.h @@ -0,0 +1,25 @@ +/* + * imx-esai.h -- ESAI driver header file for Freescale IMX + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef _MXC_ESAI_H +#define _MXC_ESAI_H + +#define IMX_DAI_ESAI_TX 0x04 +#define IMX_DAI_ESAI_RX 0x08 +#define IMX_DAI_ESAI_TXRX (IMX_DAI_ESAI_TX | IMX_DAI_ESAI_RX) + +extern struct snd_soc_dai imx_esai_dai; + +#endif diff --git a/sound/soc/imx/imx-pcm.c b/sound/soc/imx/imx-pcm.c new file mode 100644 index 000000000000..d48b40d18844 --- /dev/null +++ b/sound/soc/imx/imx-pcm.c @@ -0,0 +1,700 @@ +/* + * imx-pcm.c -- ALSA SoC interface for the Freescale i.MX3 CPU's + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Based on imx31-pcm.c by Nicolas Pitre, (C) 2004 MontaVista Software, Inc. + * and on mxc-alsa-mc13783 (C) 2006-2009 Freescale. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <mach/dma.h> +#include <mach/spba.h> +#include <mach/clock.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> + +#include "imx-pcm.h" +#include "imx-ssi.h" +#include "imx-esai.h" + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) +#include <linux/delay.h> +#include <linux/mxc_asrc.h> +#endif + +#ifdef CONFIG_SND_MXC_SOC_IRAM +static bool UseIram = 1; +#else +static bool UseIram; +#endif + +/* debug */ +#define IMX_PCM_DEBUG 0 +#if IMX_PCM_DEBUG +#define dbg(format, arg...) printk(format, ## arg) +#else +#define dbg(format, arg...) +#endif + +static const struct snd_pcm_hardware imx_pcm_hardware = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, +#ifdef CONFIG_SND_MXC_SOC_IRAM + .buffer_bytes_max = SND_RAM_SIZE, + .period_bytes_max = SND_RAM_SIZE / 4, +#else + .buffer_bytes_max = 64 * 1024, + .period_bytes_max = 16 * 1024, +#endif + .period_bytes_min = 2 * SZ_1K, + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, +}; + +static uint32_t audio_iram_phys_base_addr; +static void *audio_iram_virt_base_addr; + +static struct vm_operations_struct snd_mxc_audio_playback_vm_ops = { + .open = snd_pcm_mmap_data_open, + .close = snd_pcm_mmap_data_close, +}; + +/* + enable user space access to iram buffer +*/ +static int imx_iram_audio_playback_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *area) +{ + unsigned long off; + unsigned long phys; + unsigned long size; + int ret = 0; + + area->vm_ops = &snd_mxc_audio_playback_vm_ops; + area->vm_private_data = substream; + + off = area->vm_pgoff << PAGE_SHIFT; + phys = audio_iram_phys_base_addr + off; + size = area->vm_end - area->vm_start; + + if (off + size > SND_RAM_SIZE) + return -EINVAL; + + area->vm_page_prot = pgprot_nonshareddev(area->vm_page_prot); + area->vm_flags |= VM_IO; + ret = + remap_pfn_range(area, area->vm_start, phys >> PAGE_SHIFT, + size, area->vm_page_prot); + if (ret == 0) + area->vm_ops->open(area); + + return ret; +} + +/* + Map nbytes in virtual space + bytes -audio iram iram partition size + phys_addr - physical address of iram buffer + returns - virtual address of the iram buffer or NULL if fail +*/ +static void *imx_iram_init(dma_addr_t *phys_addr, size_t bytes) +{ + void *iram_base; + + iram_base = (void *)ioremap((uint32_t) SND_RAM_BASE_ADDR, bytes); + + audio_iram_virt_base_addr = iram_base; + audio_iram_phys_base_addr = (uint32_t) SND_RAM_BASE_ADDR; + *phys_addr = (dma_addr_t) SND_RAM_BASE_ADDR; + + return audio_iram_virt_base_addr; + +} + +/* + destroy the virtual mapping of the iram buffer +*/ + +static void imx_iram_free(void) +{ + iounmap(audio_iram_virt_base_addr); +} + +static int imx_get_sdma_transfer(int format, int dai_port, + struct snd_pcm_substream *substream) +{ + int transfer = -1; + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + if (prtd->asrc_enable == 1) { + if (dai_port == IMX_DAI_SSI0) { + if (prtd->asrc_index == 0) + transfer = MXC_DMA_ASRCA_SSI1_TX0; + else if (prtd->asrc_index == 1) + transfer = MXC_DMA_ASRCB_SSI1_TX0; + } else if (dai_port == IMX_DAI_SSI1) { + if (prtd->asrc_index == 0) + transfer = MXC_DMA_ASRCA_SSI1_TX1; + else if (prtd->asrc_index == 1) + transfer = MXC_DMA_ASRCB_SSI1_TX1; + } else if (dai_port == IMX_DAI_SSI2) { + if (prtd->asrc_index == 0) + transfer = MXC_DMA_ASRCA_SSI2_TX0; + else if (prtd->asrc_index == 1) + transfer = MXC_DMA_ASRCB_SSI2_TX0; + } else if (dai_port == IMX_DAI_SSI3) { + if (prtd->asrc_index == 0) + transfer = MXC_DMA_ASRCA_SSI2_TX1; + else if (prtd->asrc_index == 1) + transfer = MXC_DMA_ASRCB_SSI2_TX1; + } else if (dai_port & IMX_DAI_ESAI_TX) { + if (prtd->asrc_index == 0) + transfer = MXC_DMA_ASRCA_ESAI; + else if (prtd->asrc_index == 1) + transfer = MXC_DMA_ASRCB_ESAI; + else + transfer = MXC_DMA_ASRCC_ESAI; + } + } else { +#endif + + if (dai_port == IMX_DAI_SSI0) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (format == SNDRV_PCM_FORMAT_S16_LE) + transfer = MXC_DMA_SSI1_16BIT_TX0; + else if (format == SNDRV_PCM_FORMAT_S24_LE) + transfer = MXC_DMA_SSI1_24BIT_TX0; + else if (format == SNDRV_PCM_FORMAT_S20_3LE) + transfer = MXC_DMA_SSI1_24BIT_TX0; + } else { + if (format == SNDRV_PCM_FORMAT_S16_LE) + transfer = MXC_DMA_SSI1_16BIT_RX0; + else if (format == SNDRV_PCM_FORMAT_S24_LE) + transfer = MXC_DMA_SSI1_24BIT_RX0; + else if (format == SNDRV_PCM_FORMAT_S20_3LE) + transfer = MXC_DMA_SSI1_24BIT_RX0; + } + } else if (dai_port == IMX_DAI_SSI1) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (format == SNDRV_PCM_FORMAT_S16_LE) + transfer = MXC_DMA_SSI1_16BIT_TX1; + else if (format == SNDRV_PCM_FORMAT_S24_LE) + transfer = MXC_DMA_SSI1_24BIT_TX1; + else if (format == SNDRV_PCM_FORMAT_S20_3LE) + transfer = MXC_DMA_SSI1_24BIT_TX1; + } else { + if (format == SNDRV_PCM_FORMAT_S16_LE) + transfer = MXC_DMA_SSI1_16BIT_RX1; + else if (format == SNDRV_PCM_FORMAT_S24_LE) + transfer = MXC_DMA_SSI1_24BIT_RX1; + else if (format == SNDRV_PCM_FORMAT_S20_3LE) + transfer = MXC_DMA_SSI1_24BIT_RX1; + } + } else if (dai_port == IMX_DAI_SSI2) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (format == SNDRV_PCM_FORMAT_S16_LE) + transfer = MXC_DMA_SSI2_16BIT_TX0; + else if (format == SNDRV_PCM_FORMAT_S24_LE) + transfer = MXC_DMA_SSI2_24BIT_TX0; + else if (format == SNDRV_PCM_FORMAT_S20_3LE) + transfer = MXC_DMA_SSI2_24BIT_TX0; + } else { + if (format == SNDRV_PCM_FORMAT_S16_LE) + transfer = MXC_DMA_SSI2_16BIT_RX0; + else if (format == SNDRV_PCM_FORMAT_S24_LE) + transfer = MXC_DMA_SSI2_24BIT_RX0; + else if (format == SNDRV_PCM_FORMAT_S20_3LE) + transfer = MXC_DMA_SSI2_24BIT_RX0; + } + } else if (dai_port == IMX_DAI_SSI3) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (format == SNDRV_PCM_FORMAT_S16_LE) + transfer = MXC_DMA_SSI2_16BIT_TX1; + else if (format == SNDRV_PCM_FORMAT_S24_LE) + transfer = MXC_DMA_SSI2_24BIT_TX1; + else if (format == SNDRV_PCM_FORMAT_S20_3LE) + transfer = MXC_DMA_SSI2_24BIT_TX1; + } else { + if (format == SNDRV_PCM_FORMAT_S16_LE) + transfer = MXC_DMA_SSI2_16BIT_RX1; + else if (format == SNDRV_PCM_FORMAT_S24_LE) + transfer = MXC_DMA_SSI2_24BIT_RX1; + else if (format == SNDRV_PCM_FORMAT_S20_3LE) + transfer = MXC_DMA_SSI2_24BIT_RX1; + } + } else if ((dai_port & IMX_DAI_ESAI_TX) + || (dai_port & IMX_DAI_ESAI_RX)) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (format == SNDRV_PCM_FORMAT_S16_LE) + transfer = MXC_DMA_ESAI_16BIT_TX; + else if (format == SNDRV_PCM_FORMAT_S24_LE) + transfer = MXC_DMA_ESAI_24BIT_TX; + else if (format == SNDRV_PCM_FORMAT_S20_3LE) + transfer = MXC_DMA_ESAI_24BIT_TX; + } else { + if (format == SNDRV_PCM_FORMAT_S16_LE) + transfer = MXC_DMA_ESAI_16BIT_RX; + else if (format == SNDRV_PCM_FORMAT_S24_LE) + transfer = MXC_DMA_ESAI_24BIT_RX; + else if (format == SNDRV_PCM_FORMAT_S20_3LE) + transfer = MXC_DMA_ESAI_24BIT_RX; + } + } +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + } +#endif + return transfer; +} + +static int dma_new_period(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + unsigned int dma_size = frames_to_bytes(runtime, runtime->period_size); + unsigned int offset = dma_size * prtd->period; + int ret = 0; + mxc_dma_requestbuf_t sdma_request; + + if (!prtd->active) + return 0; + + memset(&sdma_request, 0, sizeof(mxc_dma_requestbuf_t)); + + dbg("period pos ALSA %x DMA %x\n", runtime->periods, prtd->period); + dbg("period size ALSA %x DMA %x Offset %x dmasize %x\n", + (unsigned int)runtime->period_size, + runtime->dma_bytes, offset, dma_size); + dbg("DMA addr %x\n", runtime->dma_addr + offset); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sdma_request.src_addr = + (dma_addr_t) (runtime->dma_addr + offset); + else + sdma_request.dst_addr = + (dma_addr_t) (runtime->dma_addr + offset); + + sdma_request.num_of_bytes = dma_size; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + mxc_dma_config(prtd->dma_wchannel, + &sdma_request, 1, MXC_DMA_MODE_WRITE); + ret = mxc_dma_enable(prtd->dma_wchannel); + } else { + + mxc_dma_config(prtd->dma_wchannel, + &sdma_request, 1, MXC_DMA_MODE_READ); + ret = mxc_dma_enable(prtd->dma_wchannel); + } + prtd->dma_active = 1; + prtd->period++; + prtd->period %= runtime->periods; + + return ret; +} + +static void audio_dma_irq(void *data) +{ + struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + + prtd->dma_active = 0; + prtd->periods++; + prtd->periods %= runtime->periods; + + dbg("irq per %d offset %x\n", prtd->periods, + frames_to_bytes(runtime, runtime->period_size) * prtd->periods); + + if (prtd->active) + snd_pcm_period_elapsed(substream); + dma_new_period(substream); +} + +static int imx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + int ret = 0, channel = 0; + + if (prtd->dma_alloc) { + mxc_dma_free(prtd->dma_wchannel); + prtd->dma_alloc = 0; + } + + /* only allocate the DMA chn once */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + if (prtd->asrc_enable == 1) { + struct dma_channel_info info; + mxc_dma_requestbuf_t sdma_request; + info.asrc.channs = runtime->channels; + if (prtd->dma_asrc) { + mxc_dma_free(prtd->dma_asrc); + prtd->dma_asrc = 0; + } + memset(&sdma_request, 0, sizeof(mxc_dma_requestbuf_t)); + /* num_of_bytes can be set any value except for zero */ + sdma_request.num_of_bytes = 0x40; + channel = + mxc_dma_request_ext(prtd->dma_ch, + "ALSA TX SDMA", &info); + + mxc_dma_config(channel, &sdma_request, + 1, MXC_DMA_MODE_WRITE); + prtd->dma_asrc = channel; + if (prtd->asrc_index == 0) + prtd->dma_ch = MXC_DMA_ASRC_A_RX; + else if (prtd->asrc_index == 1) + prtd->dma_ch = MXC_DMA_ASRC_B_RX; + else + prtd->dma_ch = MXC_DMA_ASRC_C_RX; + + channel = + mxc_dma_request(MXC_DMA_ASRC_A_RX, "ALSA ASRC RX"); + } else + channel = mxc_dma_request(prtd->dma_ch, "ALSA TX SDMA"); +#else + channel = mxc_dma_request(prtd->dma_ch, "ALSA TX SDMA"); +#endif + if (channel < 0) { + pr_err("imx-pcm: error requesting \ + a write dma channel\n"); + return channel; + } + ret = mxc_dma_callback_set(channel, (mxc_dma_callback_t) + audio_dma_irq, (void *)substream); + + } else { + channel = mxc_dma_request(prtd->dma_ch, "ALSA RX SDMA"); + if (channel < 0) { + pr_err("imx-pcm: error requesting \ + a read dma channel\n"); + return channel; + } + ret = mxc_dma_callback_set(channel, (mxc_dma_callback_t) + audio_dma_irq, (void *)substream); + } + prtd->dma_wchannel = channel; + prtd->dma_alloc = 1; + + prtd->period = 0; + prtd->periods = 0; + return 0; +} + +static int imx_pcm_hw_params(struct snd_pcm_substream + *substream, struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + prtd->dma_ch = + imx_get_sdma_transfer(params_format(params), + rtd->dai->cpu_dai->id, substream); + + if (prtd->dma_ch < 0) { + printk(KERN_ERR "imx-pcm: invaild sdma transfer type"); + return -1; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int imx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + + if (prtd->dma_wchannel) { + mxc_dma_free(prtd->dma_wchannel); + prtd->dma_wchannel = 0; + prtd->dma_alloc = 0; + } +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + if ((prtd->asrc_enable == 1) && prtd->dma_asrc) { + mxc_dma_free(prtd->dma_asrc); + prtd->dma_asrc = 0; + } +#endif + + return 0; +} + +static int imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct mxc_runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + prtd->dma_active = 0; + prtd->active = 1; + ret = dma_new_period(substream); + ret = dma_new_period(substream); +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + if (prtd->asrc_enable == 1) { + ret = mxc_dma_enable(prtd->dma_asrc); + asrc_start_conv(prtd->asrc_index); + /* There is underrun, if immediately enable SSI after + start ASRC */ + mdelay(1); + } +#endif + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + prtd->active = 0; +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + if (prtd->asrc_enable == 1) { + mxc_dma_disable(prtd->dma_asrc); + asrc_stop_conv(prtd->asrc_index); + } +#endif + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t imx_pcm_pointer(struct + snd_pcm_substream + *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + unsigned int offset = 0; + + offset = (runtime->period_size * (prtd->periods)); + if (offset >= runtime->buffer_size) + offset = 0; + dbg("pointer offset %x\n", offset); + + return offset; +} + +static int imx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd; + int ret; + + snd_soc_set_runtime_hwparams(substream, &imx_pcm_hardware); + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + prtd = kzalloc(sizeof(struct mxc_runtime_data), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + runtime->private_data = prtd; + return 0; +} + +static int imx_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mxc_runtime_data *prtd = runtime->private_data; + + kfree(prtd); + return 0; +} + +static int +imx_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_dai *cpu_dai = socdev->card->dai_link->cpu_dai; + struct mxc_audio_platform_data *dev_data = cpu_dai->private_data; + int ext_ram = 0; + int ret = 0; + + dbg("+imx_pcm_mmap:" + "UseIram=%d dma_addr=%x dma_area=%x dma_bytes=%d\n", + UseIram, (unsigned int)runtime->dma_addr, + runtime->dma_area, runtime->dma_bytes); + + if (dev_data) + ext_ram = dev_data->ext_ram; + + if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) + || ext_ram || !UseIram) { + ret = + dma_mmap_writecombine(substream->pcm->card-> + dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + return ret; + } else + return imx_iram_audio_playback_mmap(substream, vma); +} + +struct snd_pcm_ops imx_pcm_ops = { + .open = imx_pcm_open, + .close = imx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = imx_pcm_hw_params, + .hw_free = imx_pcm_hw_free, + .prepare = imx_pcm_prepare, + .trigger = imx_pcm_trigger, + .pointer = imx_pcm_pointer, + .mmap = imx_pcm_mmap, +}; + +static int imx_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; + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_dai *cpu_dai = socdev->card->dai_link->cpu_dai; + struct mxc_audio_platform_data *dev_data = cpu_dai->private_data; + int ext_ram = 0; + size_t size = imx_pcm_hardware.buffer_bytes_max; + + if (dev_data) + ext_ram = dev_data->ext_ram; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + + if ((stream == SNDRV_PCM_STREAM_CAPTURE) || ext_ram || !UseIram) + buf->area = + dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + else + buf->area = imx_iram_init(&buf->addr, size); + + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + printk(KERN_INFO "DMA Sound Buffers Allocated:" + "UseIram=%d buf->addr=%x buf->area=%p size=%d\n", + UseIram, buf->addr, buf->area, size); + return 0; +} + +static void imx_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_dai *cpu_dai = socdev->card->dai_link->cpu_dai; + struct mxc_audio_platform_data *dev_data = cpu_dai->private_data; + int ext_ram = 0; + int stream; + + if (dev_data) + ext_ram = dev_data->ext_ram; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + if ((stream == SNDRV_PCM_STREAM_CAPTURE) + || ext_ram || !UseIram) + dma_free_writecombine(pcm->card->dev, + buf->bytes, buf->area, buf->addr); + else + imx_iram_free(); + buf->area = NULL; + } +} + +static u64 imx_pcm_dmamask = 0xffffffff; + +static int imx_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 = &imx_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->playback.channels_min) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->capture.channels_min) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + out: + return ret; +} + +struct snd_soc_platform imx_soc_platform = { + .name = "imx-audio", + .pcm_ops = &imx_pcm_ops, + .pcm_new = imx_pcm_new, + .pcm_free = imx_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(imx_soc_platform); + +static int __init imx_pcm_init(void) +{ + return snd_soc_register_platform(&imx_soc_platform); +} +module_init(imx_pcm_init); + +static void __exit imx_pcm_exit(void) +{ + snd_soc_unregister_platform(&imx_soc_platform); +} +module_exit(imx_pcm_exit); + +MODULE_AUTHOR("Liam Girdwood"); +MODULE_DESCRIPTION("Freescale i.MX3x PCM DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/imx/imx-pcm.h b/sound/soc/imx/imx-pcm.h new file mode 100644 index 000000000000..c22a42b70364 --- /dev/null +++ b/sound/soc/imx/imx-pcm.h @@ -0,0 +1,83 @@ +/* + * imx-pcm.h :- ASoC platform header for Freescale i.MX + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Copyright 2006, 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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 _MXC_PCM_H +#define _MXC_PCM_H + +#include <mach/dma.h> + +/* AUDMUX regs definition */ +#define AUDMUX_IO_BASE_ADDR IO_ADDRESS(AUDMUX_BASE_ADDR) + +#define DAM_PTCR1 ((AUDMUX_IO_BASE_ADDR) + 0x00) +#define DAM_PDCR1 ((AUDMUX_IO_BASE_ADDR) + 0x04) +#define DAM_PTCR2 ((AUDMUX_IO_BASE_ADDR) + 0x08) +#define DAM_PDCR2 ((AUDMUX_IO_BASE_ADDR) + 0x0C) +#define DAM_PTCR3 ((AUDMUX_IO_BASE_ADDR) + 0x10) +#define DAM_PDCR3 ((AUDMUX_IO_BASE_ADDR) + 0x14) +#define DAM_PTCR4 ((AUDMUX_IO_BASE_ADDR) + 0x18) +#define DAM_PDCR4 ((AUDMUX_IO_BASE_ADDR) + 0x1C) +#define DAM_PTCR5 ((AUDMUX_IO_BASE_ADDR) + 0x20) +#define DAM_PDCR5 ((AUDMUX_IO_BASE_ADDR) + 0x24) +#define DAM_PTCR6 ((AUDMUX_IO_BASE_ADDR) + 0x28) +#define DAM_PDCR6 ((AUDMUX_IO_BASE_ADDR) + 0x2C) +#define DAM_PTCR7 ((AUDMUX_IO_BASE_ADDR) + 0x30) +#define DAM_PDCR7 ((AUDMUX_IO_BASE_ADDR) + 0x34) +#define DAM_CNMCR ((AUDMUX_IO_BASE_ADDR) + 0x38) +#define DAM_PTCR(a) ((AUDMUX_IO_BASE_ADDR) + (a-1)*8) +#define DAM_PDCR(a) ((AUDMUX_IO_BASE_ADDR) + 4 + (a-1)*8) + +#define AUDMUX_PTCR_TFSDIR (1 << 31) +#define AUDMUX_PTCR_TFSSEL(x, y) \ + ((x << 30) | (((y - 1) & 0x7) << 27)) +#define AUDMUX_PTCR_TCLKDIR (1 << 26) +#define AUDMUX_PTCR_TCSEL(x, y) \ + ((x << 25) | (((y - 1) & 0x7) << 22)) +#define AUDMUX_PTCR_RFSDIR (1 << 21) +#define AUDMUX_PTCR_RFSSEL(x, y) \ + ((x << 20) | (((y - 1) & 0x7) << 17)) +#define AUDMUX_PTCR_RCLKDIR (1 << 16) +#define AUDMUX_PTCR_RCSEL(x, y) \ + ((x << 15) | (((y - 1) & 0x7) << 12)) +#define AUDMUX_PTCR_SYN (1 << 11) + +#define AUDMUX_FROM_TXFS 0 +#define AUDMUX_FROM_RXFS 1 + +#define AUDMUX_PDCR_RXDSEL(x) (((x - 1) & 0x7) << 13) +#define AUDMUX_PDCR_TXDXEN (1 << 12) +#define AUDMUX_PDCR_MODE(x) (((x) & 0x3) << 8) +#define AUDMUX_PDCR_INNMASK(x) (((x) & 0xff) << 0) + +#define AUDMUX_CNMCR_CEN (1 << 18) +#define AUDMUX_CNMCR_FSPOL (1 << 17) +#define AUDMUX_CNMCR_CLKPOL (1 << 16) +#define AUDMUX_CNMCR_CNTHI(x) (((x) & 0xff) << 8) +#define AUDMUX_CNMCR_CNTLOW(x) (((x) & 0xff) << 0) + + +struct mxc_runtime_data { + int dma_ch; + spinlock_t dma_lock; + int active, period, periods; + int dma_wchannel; + int dma_active; + int dma_alloc; +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_MXC_ASRC_MODULE) + int dma_asrc; + int asrc_index; + int asrc_enable; +#endif +}; + +extern struct snd_soc_platform imx_soc_platform; + +#endif diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c new file mode 100644 index 000000000000..2dba33b8c270 --- /dev/null +++ b/sound/soc/imx/imx-ssi.c @@ -0,0 +1,786 @@ +/* + * imx-ssi.c -- SSI driver for Freescale IMX + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Based on mxc-alsa-mc13783 (C) 2006-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 29th Aug 2006 Initial version. + * + * TODO: + * Need to rework SSI register defs when new defs go into mainline. + * Add support for TDM and FIFO 1. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <mach/dma.h> +#include <mach/clock.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> + +#include "imx-ssi.h" +#include "imx-pcm.h" + +/* private info */ +struct imx_ssi { + bool network_mode; +}; + +static struct imx_ssi imx_ssi_data[IMX_DAI_SSI3]; + +/* debug */ +#define IMX_SSI_DEBUG 0 +#if IMX_SSI_DEBUG +#define dbg(format, arg...) printk(format, ## arg) +#else +#define dbg(format, arg...) +#endif + +#define IMX_SSI_DUMP 0 +#if IMX_SSI_DUMP +#define SSI_DUMP() \ + do { \ + printk(KERN_INFO "dump @ %s\n", __func__); \ + printk(KERN_INFO "scr %x\t, %x\n", \ + __raw_readl(SSI1_SCR), __raw_readl(SSI2_SCR)); \ + printk(KERN_INFO "sisr %x\t, %x\n", \ + __raw_readl(SSI1_SISR), __raw_readl(SSI2_SISR)); \ + printk(KERN_INFO "stcr %x\t, %x\n", \ + __raw_readl(SSI1_STCR), __raw_readl(SSI2_STCR)); \ + printk(KERN_INFO "srcr %x\t, %x\n", \ + __raw_readl(SSI1_SRCR), __raw_readl(SSI2_SRCR)); \ + printk(KERN_INFO "stccr %x\t, %x\n", \ + __raw_readl(SSI1_STCCR), __raw_readl(SSI2_STCCR)); \ + printk(KERN_INFO "srccr %x\t, %x\n", \ + __raw_readl(SSI1_SRCCR), __raw_readl(SSI2_SRCCR)); \ + printk(KERN_INFO "sfcsr %x\t, %x\n", \ + __raw_readl(SSI1_SFCSR), __raw_readl(SSI2_SFCSR)); \ + printk(KERN_INFO "stmsk %x\t, %x\n", \ + __raw_readl(SSI1_STMSK), __raw_readl(SSI2_STMSK)); \ + printk(KERN_INFO "srmsk %x\t, %x\n", \ + __raw_readl(SSI1_SRMSK), __raw_readl(SSI2_SRMSK)); \ + printk(KERN_INFO "sier %x\t, %x\n", \ + __raw_readl(SSI1_SIER), __raw_readl(SSI2_SIER)); \ + } while (0); +#else +#define SSI_DUMP() +#endif + +#define SSI1_PORT 0 +#define SSI2_PORT 1 + +static int ssi_active[2] = { 0, 0 }; + +/* + * SSI system clock configuration. + * Should only be called when port is inactive (i.e. SSIEN = 0). + */ +static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + u32 scr; + + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) + scr = __raw_readl(SSI1_SCR); + else + scr = __raw_readl(SSI2_SCR); + + if (scr & SSI_SCR_SSIEN) + return 0; + + switch (clk_id) { + case IMX_SSP_SYS_CLK: + if (dir == SND_SOC_CLOCK_OUT) + scr |= SSI_SCR_SYS_CLK_EN; + else + scr &= ~SSI_SCR_SYS_CLK_EN; + break; + default: + return -EINVAL; + } + + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) + __raw_writel(scr, SSI1_SCR); + else + __raw_writel(scr, SSI2_SCR); + + return 0; +} + +/* + * SSI Clock dividers + * Should only be called when port is inactive (i.e. SSIEN = 0). + */ +static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + u32 stccr, srccr; + + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) { + if (__raw_readl(SSI1_SCR) & SSI_SCR_SSIEN) + return 0; + + srccr = __raw_readl(SSI1_SRCCR); + stccr = __raw_readl(SSI1_STCCR); + } else { + if (__raw_readl(SSI2_SCR) & SSI_SCR_SSIEN) + return 0; + + srccr = __raw_readl(SSI2_SRCCR); + stccr = __raw_readl(SSI2_STCCR); + } + + switch (div_id) { + case IMX_SSI_TX_DIV_2: + stccr &= ~SSI_STCCR_DIV2; + stccr |= div; + break; + case IMX_SSI_TX_DIV_PSR: + stccr &= ~SSI_STCCR_PSR; + stccr |= div; + break; + case IMX_SSI_TX_DIV_PM: + stccr &= ~0xff; + stccr |= SSI_STCCR_PM(div); + break; + case IMX_SSI_RX_DIV_2: + stccr &= ~SSI_STCCR_DIV2; + stccr |= div; + break; + case IMX_SSI_RX_DIV_PSR: + stccr &= ~SSI_STCCR_PSR; + stccr |= div; + break; + case IMX_SSI_RX_DIV_PM: + stccr &= ~0xff; + stccr |= SSI_STCCR_PM(div); + break; + default: + return -EINVAL; + } + + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) { + __raw_writel(stccr, SSI1_STCCR); + __raw_writel(srccr, SSI1_SRCCR); + } else { + __raw_writel(stccr, SSI2_STCCR); + __raw_writel(srccr, SSI2_SRCCR); + } + return 0; +} + +/* + * SSI Network Mode or TDM slots configuration. + * Should only be called when port is inactive (i.e. SSIEN = 0). + */ +static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, + unsigned int mask, int slots) +{ + bool network_mode = (!(mask & 0x2)); + u32 stmsk, srmsk, stccr; + + imx_ssi_data[cpu_dai->id].network_mode = network_mode; + + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) { + if (__raw_readl(SSI1_SCR) & SSI_SCR_SSIEN) + return 0; + stccr = __raw_readl(SSI1_STCCR); + } else { + if (__raw_readl(SSI2_SCR) & SSI_SCR_SSIEN) + return 0; + stccr = __raw_readl(SSI2_STCCR); + } + + stmsk = srmsk = mask; + stccr &= ~SSI_STCCR_DC_MASK; + stccr |= SSI_STCCR_DC(slots - 1); + + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) { + __raw_writel(stmsk, SSI1_STMSK); + __raw_writel(srmsk, SSI1_SRMSK); + __raw_writel(stccr, SSI1_STCCR); + __raw_writel(stccr, SSI1_SRCCR); + } else { + __raw_writel(stmsk, SSI2_STMSK); + __raw_writel(srmsk, SSI2_SRMSK); + __raw_writel(stccr, SSI2_STCCR); + __raw_writel(stccr, SSI2_SRCCR); + } + + return 0; +} + +/* + * SSI DAI format configuration. + * Should only be called when port is inactive (i.e. SSIEN = 0). + * Note: We don't use the I2S modes but instead manually configure the + * SSI for I2S. + */ +static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + bool sync_mode = cpu_dai->symmetric_rates; + bool network_mode; + u32 stcr = 0, srcr = 0, scr; + + network_mode = imx_ssi_data[cpu_dai->id].network_mode; + + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) + scr = __raw_readl(SSI1_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET); + else + scr = __raw_readl(SSI2_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET); + + if (scr & SSI_SCR_SSIEN) + return 0; + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* data on rising edge of bclk, frame low 1clk before data */ + stcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0; + srcr |= SSI_SRCR_RFSI | SSI_SRCR_REFS | SSI_SRCR_RXBIT0; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* data on rising edge of bclk, frame high with data */ + stcr |= SSI_STCR_TXBIT0; + srcr |= SSI_SRCR_RXBIT0; + break; + case SND_SOC_DAIFMT_DSP_B: + /* data on rising edge of bclk, frame high with data */ + stcr |= SSI_STCR_TFSL; + srcr |= SSI_SRCR_RFSL; + break; + case SND_SOC_DAIFMT_DSP_A: + /* data on rising edge of bclk, frame high 1clk before data */ + stcr |= SSI_STCR_TFSL | SSI_STCR_TEFS; + srcr |= SSI_SRCR_RFSL | SSI_SRCR_REFS; + break; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + stcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI); + srcr &= ~(SSI_SRCR_RSCKP | SSI_SRCR_RFSI); + break; + case SND_SOC_DAIFMT_IB_NF: + stcr |= SSI_STCR_TFSI; + stcr &= ~SSI_STCR_TSCKP; + srcr |= SSI_SRCR_RFSI; + srcr &= ~SSI_SRCR_RSCKP; + break; + case SND_SOC_DAIFMT_NB_IF: + stcr &= ~SSI_STCR_TFSI; + stcr |= SSI_STCR_TSCKP; + srcr &= ~SSI_SRCR_RFSI; + srcr |= SSI_SRCR_RSCKP; + break; + case SND_SOC_DAIFMT_NB_NF: + stcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP; + srcr |= SSI_SRCR_RFSI | SSI_SRCR_RSCKP; + break; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + stcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR; + if (((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) + && network_mode) { + scr &= ~SSI_SCR_I2S_MODE_MASK; + scr |= SSI_SCR_I2S_MODE_MSTR; + } + break; + case SND_SOC_DAIFMT_CBM_CFS: + stcr |= SSI_STCR_TFDIR; + srcr |= SSI_SRCR_RFDIR; + break; + case SND_SOC_DAIFMT_CBS_CFM: + stcr |= SSI_STCR_TXDIR; + srcr |= SSI_SRCR_RXDIR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + if (((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) + && network_mode) { + scr &= ~SSI_SCR_I2S_MODE_MASK; + scr |= SSI_SCR_I2S_MODE_SLAVE; + } + break; + } + + /* sync */ + if (sync_mode) + scr |= SSI_SCR_SYN; + + /* tdm - only for stereo atm */ + if (network_mode) + scr |= SSI_SCR_NET; + + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) { + __raw_writel(stcr, SSI1_STCR); + __raw_writel(srcr, SSI1_SRCR); + __raw_writel(scr, SSI1_SCR); + } else { + __raw_writel(stcr, SSI2_STCR); + __raw_writel(srcr, SSI2_SRCR); + __raw_writel(scr, SSI2_SCR); + } + SSI_DUMP(); + return 0; +} + +static struct clk *ssi1_clk; +static struct clk *ssi2_clk; + +static int imx_ssi_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + /* we cant really change any SSI values after SSI is enabled + * need to fix in software for max flexibility - lrg */ + if (cpu_dai->playback.active || cpu_dai->capture.active) + return 0; + + /* reset the SSI port - Sect 45.4.4 */ + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) { + + if (ssi_active[SSI1_PORT]++) + return 0; + + __raw_writel(0, SSI1_SCR); + ssi1_clk = clk_get(NULL, "ssi_clk.0"); + clk_enable(ssi1_clk); + + /* BIG FAT WARNING + * SDMA FIFO watermark must == SSI FIFO watermark for + * best results. + */ + __raw_writel((SSI_SFCSR_RFWM1(SSI_RXFIFO_WATERMARK) | + SSI_SFCSR_RFWM0(SSI_RXFIFO_WATERMARK) | + SSI_SFCSR_TFWM1(SSI_TXFIFO_WATERMARK) | + SSI_SFCSR_TFWM0(SSI_TXFIFO_WATERMARK)), + SSI1_SFCSR); + __raw_writel(0, SSI1_SIER); + } else { + + if (ssi_active[SSI2_PORT]++) + return 0; + + __raw_writel(0, SSI2_SCR); + ssi2_clk = clk_get(NULL, "ssi_clk.1"); + clk_enable(ssi2_clk); + /* above warning applies here too */ + __raw_writel((SSI_SFCSR_RFWM1(SSI_RXFIFO_WATERMARK) | + SSI_SFCSR_RFWM0(SSI_RXFIFO_WATERMARK) | + SSI_SFCSR_TFWM1(SSI_TXFIFO_WATERMARK) | + SSI_SFCSR_TFWM0(SSI_TXFIFO_WATERMARK)), + SSI2_SFCSR); + __raw_writel(0, SSI2_SIER); + } + + SSI_DUMP(); + return 0; +} + +static int imx_ssi_hw_tx_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + u32 stccr, stcr, sier; + + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) { + stccr = __raw_readl(SSI1_STCCR) & ~SSI_STCCR_WL_MASK; + stcr = __raw_readl(SSI1_STCR); + sier = __raw_readl(SSI1_SIER); + } else { + stccr = __raw_readl(SSI2_STCCR) & ~SSI_STCCR_WL_MASK; + stcr = __raw_readl(SSI2_STCR); + sier = __raw_readl(SSI2_SIER); + } + + /* DAI data (word) size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + stccr |= SSI_STCCR_WL(16); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + stccr |= SSI_STCCR_WL(20); + break; + case SNDRV_PCM_FORMAT_S24_LE: + stccr |= SSI_STCCR_WL(24); + break; + } + + /* enable interrupts */ + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) + stcr |= SSI_STCR_TFEN0; + else + stcr |= SSI_STCR_TFEN1; + sier |= SSI_SIER_TDMAE | SSI_SIER_TIE | SSI_SIER_TUE0_EN; + + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) { + __raw_writel(stcr, SSI1_STCR); + __raw_writel(stccr, SSI1_STCCR); + __raw_writel(sier, SSI1_SIER); + } else { + __raw_writel(stcr, SSI2_STCR); + __raw_writel(stccr, SSI2_STCCR); + __raw_writel(sier, SSI2_SIER); + } + + return 0; +} + +static int imx_ssi_hw_rx_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + bool sync_mode = cpu_dai->symmetric_rates; + u32 srccr, srcr, sier; + + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) { + srccr = + sync_mode ? __raw_readl(SSI1_STCCR) : + __raw_readl(SSI1_SRCCR); + srcr = __raw_readl(SSI1_SRCR); + sier = __raw_readl(SSI1_SIER); + } else { + srccr = + sync_mode ? __raw_readl(SSI2_STCCR) : + __raw_readl(SSI2_SRCCR); + srcr = __raw_readl(SSI2_SRCR); + sier = __raw_readl(SSI2_SIER); + } + srccr &= ~SSI_SRCCR_WL_MASK; + + /* DAI data (word) size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + srccr |= SSI_SRCCR_WL(16); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + srccr |= SSI_SRCCR_WL(20); + break; + case SNDRV_PCM_FORMAT_S24_LE: + srccr |= SSI_SRCCR_WL(24); + break; + } + + /* enable interrupts */ + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) + srcr |= SSI_SRCR_RFEN0; + else + srcr |= SSI_SRCR_RFEN1; + sier |= SSI_SIER_RDMAE | SSI_SIER_RIE | SSI_SIER_ROE0_EN; + + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) { + __raw_writel(srcr, SSI1_SRCR); + if (sync_mode) + __raw_writel(srccr, SSI1_STCCR); + else + __raw_writel(srccr, SSI1_SRCCR); + __raw_writel(sier, SSI1_SIER); + } else { + __raw_writel(srcr, SSI2_SRCR); + if (sync_mode) + __raw_writel(srccr, SSI2_STCCR); + else + __raw_writel(srccr, SSI2_SRCCR); + __raw_writel(sier, SSI2_SIER); + } + return 0; +} + +/* + * Should only be called when port is inactive (i.e. SSIEN = 0), + * although can be called multiple times by upper layers. + */ +static int imx_ssi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + int id; + + id = cpu_dai->id; + + /* Tx/Rx config */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* cant change any parameters when SSI is running */ + if (id == IMX_DAI_SSI0 || id == IMX_DAI_SSI1) { + if ((__raw_readl(SSI1_SCR) & SSI_SCR_SSIEN) && + (__raw_readl(SSI1_SCR) & SSI_SCR_TE)) + return 0; + } else { + if ((__raw_readl(SSI2_SCR) & SSI_SCR_SSIEN) && + (__raw_readl(SSI2_SCR) & SSI_SCR_TE)) + return 0; + } + return imx_ssi_hw_tx_params(substream, params, cpu_dai); + } else { + /* cant change any parameters when SSI is running */ + if (id == IMX_DAI_SSI0 || id == IMX_DAI_SSI1) { + if ((__raw_readl(SSI1_SCR) & SSI_SCR_SSIEN) && + (__raw_readl(SSI1_SCR) & SSI_SCR_RE)) + return 0; + } else { + if ((__raw_readl(SSI2_SCR) & SSI_SCR_SSIEN) && + (__raw_readl(SSI2_SCR) & SSI_SCR_RE)) + return 0; + } + return imx_ssi_hw_rx_params(substream, params, cpu_dai); + } +} + +static int imx_ssi_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + u32 scr; + + /* enable the SSI port, note that no other port config + * should happen after SSIEN is set */ + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) { + scr = __raw_readl(SSI1_SCR); + __raw_writel((scr | SSI_SCR_SSIEN), SSI1_SCR); + } else { + scr = __raw_readl(SSI2_SCR); + __raw_writel((scr | SSI_SCR_SSIEN), SSI2_SCR); + } + SSI_DUMP(); + return 0; +} + +static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + u32 scr; + + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) + scr = __raw_readl(SSI1_SCR); + else + scr = __raw_readl(SSI2_SCR); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (scr & SSI_SCR_RE) { + if (cpu_dai->id == IMX_DAI_SSI0 + || cpu_dai->id == IMX_DAI_SSI1) + __raw_writel(0, SSI1_SCR); + else + __raw_writel(0, SSI2_SCR); + } + scr |= SSI_SCR_TE; + } else + scr |= SSI_SCR_RE; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + scr &= ~SSI_SCR_TE; + else + scr &= ~SSI_SCR_RE; + break; + default: + return -EINVAL; + } + if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI1) + __raw_writel(scr, SSI1_SCR); + else + __raw_writel(scr, SSI2_SCR); + + SSI_DUMP(); + return 0; +} + +static void imx_ssi_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + int id; + + id = cpu_dai->id; + + /* shutdown SSI if neither Tx or Rx is active */ + if (cpu_dai->playback.active || cpu_dai->capture.active) + return; + + if (id == IMX_DAI_SSI0 || id == IMX_DAI_SSI1) { + + if (--ssi_active[SSI1_PORT] > 1) + return; + + __raw_writel(0, SSI1_SCR); + + clk_disable(ssi1_clk); + clk_put(ssi1_clk); + + } else { + if (--ssi_active[SSI2_PORT]) + return; + __raw_writel(0, SSI2_SCR); + clk_disable(ssi2_clk); + clk_put(ssi2_clk); + } +} + +#ifdef CONFIG_PM +static int imx_ssi_suspend(struct snd_soc_dai *dai) +{ + if (!dai->active) + return 0; + + /* do we need to disable any clocks? */ + + return 0; +} + +static int imx_ssi_resume(struct snd_soc_dai *dai) +{ + if (!dai->active) + return 0; + + /* do we need to enable any clocks? */ + + return 0; +} +#else +#define imx_ssi_suspend NULL +#define imx_ssi_resume NULL +#endif + +static int fifo_err_counter; + +static irqreturn_t ssi1_irq(int irq, void *dev_id) +{ + if (fifo_err_counter++ % 1000 == 0) + printk(KERN_ERR "ssi1_irq SISR %x SIER %x fifo_errs=%d\n", + __raw_readl(SSI1_SISR), __raw_readl(SSI1_SIER), + fifo_err_counter); + __raw_writel((SSI_SIER_TUE0_EN | SSI_SIER_ROE0_EN), SSI1_SISR); + return IRQ_HANDLED; +} + +static irqreturn_t ssi2_irq(int irq, void *dev_id) +{ + if (fifo_err_counter++ % 1000 == 0) + printk(KERN_ERR "ssi2_irq SISR %x SIER %x fifo_errs=%d\n", + __raw_readl(SSI2_SISR), __raw_readl(SSI2_SIER), + fifo_err_counter); + __raw_writel((SSI_SIER_TUE0_EN | SSI_SIER_ROE0_EN), SSI2_SISR); + return IRQ_HANDLED; +} + +static int imx_ssi_probe(struct platform_device *pdev, struct snd_soc_dai *dai) +{ + if (!strcmp(dai->name, "imx-ssi-1")) + dai->id = IMX_DAI_SSI0; + else if (!strcmp(dai->name, "imx-ssi-2")) + dai->id = IMX_DAI_SSI1; + else if (!strcmp(dai->name, "imx-ssi-3")) + dai->id = IMX_DAI_SSI2; + else if (!strcmp(dai->name, "imx-ssi-4")) + dai->id = IMX_DAI_SSI3; + else { + printk(KERN_ERR "%s: invalid device %s\n", __func__, dai->name); + return -ENODEV; + } + + if ((!strcmp(dai->name, "imx-ssi-1")) || + (!strcmp(dai->name, "imx-ssi-2"))) + if (request_irq(MXC_INT_SSI1, ssi1_irq, 0, "ssi1", dai)) { + printk(KERN_ERR "%s: failure requesting irq %s\n", + __func__, "ssi1"); + return -EBUSY; + } + + if ((!strcmp(dai->name, "imx-ssi-3")) || + (!strcmp(dai->name, "imx-ssi-4"))) + if (request_irq(MXC_INT_SSI2, ssi2_irq, 0, "ssi2", dai)) { + printk(KERN_ERR "%s: failure requesting irq %s\n", + __func__, "ssi2"); + return -EBUSY; + } + + return 0; +} + +static void imx_ssi_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + if ((!strcmp(dai->name, "imx-ssi-1")) || + (!strcmp(dai->name, "imx-ssi-2"))) + free_irq(MXC_INT_SSI1, dai); + + if ((!strcmp(dai->name, "imx-ssi-3")) || + (!strcmp(dai->name, "imx-ssi-4"))) + free_irq(MXC_INT_SSI2, dai); +} + +#define IMX_SSI_RATES \ + (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) + +#define IMX_SSI_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops imx_ssi_dai_ops = { + .startup = imx_ssi_startup, + .shutdown = imx_ssi_shutdown, + .trigger = imx_ssi_trigger, + .prepare = imx_ssi_prepare, + .hw_params = imx_ssi_hw_params, + .set_sysclk = imx_ssi_set_dai_sysclk, + .set_clkdiv = imx_ssi_set_dai_clkdiv, + .set_fmt = imx_ssi_set_dai_fmt, + .set_tdm_slot = imx_ssi_set_dai_tdm_slot, +}; + +struct snd_soc_dai imx_ssi_dai = { + .name = "imx-ssi", + .id = 0, + .probe = imx_ssi_probe, + .suspend = imx_ssi_suspend, + .remove = imx_ssi_remove, + .resume = imx_ssi_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = IMX_SSI_RATES, + .formats = IMX_SSI_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = IMX_SSI_RATES, + .formats = IMX_SSI_FORMATS, + }, + .ops = &imx_ssi_dai_ops, +}; +EXPORT_SYMBOL_GPL(imx_ssi_dai); + +MODULE_AUTHOR + ("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("i.MX ASoC I2S driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/imx/imx-ssi.h b/sound/soc/imx/imx-ssi.h new file mode 100644 index 000000000000..ffb27e9d2aea --- /dev/null +++ b/sound/soc/imx/imx-ssi.h @@ -0,0 +1,218 @@ +/* + * 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 _IMX_SSI_H +#define _IMX_SSI_H + +#include <mach/hardware.h> + +/* SSI regs definition */ +#define SSI1_IO_BASE_ADDR IO_ADDRESS(SSI1_BASE_ADDR) +#define SSI2_IO_BASE_ADDR IO_ADDRESS(SSI2_BASE_ADDR) + +#define SSI1_STX0 ((SSI1_IO_BASE_ADDR) + 0x00) +#define SSI1_STX1 ((SSI1_IO_BASE_ADDR) + 0x04) +#define SSI1_SRX0 ((SSI1_IO_BASE_ADDR) + 0x08) +#define SSI1_SRX1 ((SSI1_IO_BASE_ADDR) + 0x0c) +#define SSI1_SCR ((SSI1_IO_BASE_ADDR) + 0x10) +#define SSI1_SISR ((SSI1_IO_BASE_ADDR) + 0x14) +#define SSI1_SIER ((SSI1_IO_BASE_ADDR) + 0x18) +#define SSI1_STCR ((SSI1_IO_BASE_ADDR) + 0x1c) +#define SSI1_SRCR ((SSI1_IO_BASE_ADDR) + 0x20) +#define SSI1_STCCR ((SSI1_IO_BASE_ADDR) + 0x24) +#define SSI1_SRCCR ((SSI1_IO_BASE_ADDR) + 0x28) +#define SSI1_SFCSR ((SSI1_IO_BASE_ADDR) + 0x2c) +#define SSI1_STR ((SSI1_IO_BASE_ADDR) + 0x30) +#define SSI1_SOR ((SSI1_IO_BASE_ADDR) + 0x34) +#define SSI1_SACNT ((SSI1_IO_BASE_ADDR) + 0x38) +#define SSI1_SACADD ((SSI1_IO_BASE_ADDR) + 0x3c) +#define SSI1_SACDAT ((SSI1_IO_BASE_ADDR) + 0x40) +#define SSI1_SATAG ((SSI1_IO_BASE_ADDR) + 0x44) +#define SSI1_STMSK ((SSI1_IO_BASE_ADDR) + 0x48) +#define SSI1_SRMSK ((SSI1_IO_BASE_ADDR) + 0x4c) + +#define SSI2_STX0 ((SSI2_IO_BASE_ADDR) + 0x00) +#define SSI2_STX1 ((SSI2_IO_BASE_ADDR) + 0x04) +#define SSI2_SRX0 ((SSI2_IO_BASE_ADDR) + 0x08) +#define SSI2_SRX1 ((SSI2_IO_BASE_ADDR) + 0x0c) +#define SSI2_SCR ((SSI2_IO_BASE_ADDR) + 0x10) +#define SSI2_SISR ((SSI2_IO_BASE_ADDR) + 0x14) +#define SSI2_SIER ((SSI2_IO_BASE_ADDR) + 0x18) +#define SSI2_STCR ((SSI2_IO_BASE_ADDR) + 0x1c) +#define SSI2_SRCR ((SSI2_IO_BASE_ADDR) + 0x20) +#define SSI2_STCCR ((SSI2_IO_BASE_ADDR) + 0x24) +#define SSI2_SRCCR ((SSI2_IO_BASE_ADDR) + 0x28) +#define SSI2_SFCSR ((SSI2_IO_BASE_ADDR) + 0x2c) +#define SSI2_STR ((SSI2_IO_BASE_ADDR) + 0x30) +#define SSI2_SOR ((SSI2_IO_BASE_ADDR) + 0x34) +#define SSI2_SACNT ((SSI2_IO_BASE_ADDR) + 0x38) +#define SSI2_SACADD ((SSI2_IO_BASE_ADDR) + 0x3c) +#define SSI2_SACDAT ((SSI2_IO_BASE_ADDR) + 0x40) +#define SSI2_SATAG ((SSI2_IO_BASE_ADDR) + 0x44) +#define SSI2_STMSK ((SSI2_IO_BASE_ADDR) + 0x48) +#define SSI2_SRMSK ((SSI2_IO_BASE_ADDR) + 0x4c) + +#define SSI_SCR_CLK_IST (1 << 9) +#define SSI_SCR_TCH_EN (1 << 8) +#define SSI_SCR_SYS_CLK_EN (1 << 7) +#define SSI_SCR_I2S_MODE_NORM (0 << 5) +#define SSI_SCR_I2S_MODE_MSTR (1 << 5) +#define SSI_SCR_I2S_MODE_SLAVE (2 << 5) +#define SSI_SCR_SYN (1 << 4) +#define SSI_SCR_NET (1 << 3) +#define SSI_SCR_RE (1 << 2) +#define SSI_SCR_TE (1 << 1) +#define SSI_SCR_SSIEN (1 << 0) +#define SSI_SCR_I2S_MODE_MASK (3 << 5) + +#define SSI_SISR_CMDAU (1 << 18) +#define SSI_SISR_CMDDU (1 << 17) +#define SSI_SISR_RXT (1 << 16) +#define SSI_SISR_RDR1 (1 << 15) +#define SSI_SISR_RDR0 (1 << 14) +#define SSI_SISR_TDE1 (1 << 13) +#define SSI_SISR_TDE0 (1 << 12) +#define SSI_SISR_ROE1 (1 << 11) +#define SSI_SISR_ROE0 (1 << 10) +#define SSI_SISR_TUE1 (1 << 9) +#define SSI_SISR_TUE0 (1 << 8) +#define SSI_SISR_TFS (1 << 7) +#define SSI_SISR_RFS (1 << 6) +#define SSI_SISR_TLS (1 << 5) +#define SSI_SISR_RLS (1 << 4) +#define SSI_SISR_RFF1 (1 << 3) +#define SSI_SISR_RFF0 (1 << 2) +#define SSI_SISR_TFE1 (1 << 1) +#define SSI_SISR_TFE0 (1 << 0) + +#define SSI_SIER_RDMAE (1 << 22) +#define SSI_SIER_RIE (1 << 21) +#define SSI_SIER_TDMAE (1 << 20) +#define SSI_SIER_TIE (1 << 19) +#define SSI_SIER_CMDAU_EN (1 << 18) +#define SSI_SIER_CMDDU_EN (1 << 17) +#define SSI_SIER_RXT_EN (1 << 16) +#define SSI_SIER_RDR1_EN (1 << 15) +#define SSI_SIER_RDR0_EN (1 << 14) +#define SSI_SIER_TDE1_EN (1 << 13) +#define SSI_SIER_TDE0_EN (1 << 12) +#define SSI_SIER_ROE1_EN (1 << 11) +#define SSI_SIER_ROE0_EN (1 << 10) +#define SSI_SIER_TUE1_EN (1 << 9) +#define SSI_SIER_TUE0_EN (1 << 8) +#define SSI_SIER_TFS_EN (1 << 7) +#define SSI_SIER_RFS_EN (1 << 6) +#define SSI_SIER_TLS_EN (1 << 5) +#define SSI_SIER_RLS_EN (1 << 4) +#define SSI_SIER_RFF1_EN (1 << 3) +#define SSI_SIER_RFF0_EN (1 << 2) +#define SSI_SIER_TFE1_EN (1 << 1) +#define SSI_SIER_TFE0_EN (1 << 0) + +#define SSI_STCR_TXBIT0 (1 << 9) +#define SSI_STCR_TFEN1 (1 << 8) +#define SSI_STCR_TFEN0 (1 << 7) +#define SSI_STCR_TFDIR (1 << 6) +#define SSI_STCR_TXDIR (1 << 5) +#define SSI_STCR_TSHFD (1 << 4) +#define SSI_STCR_TSCKP (1 << 3) +#define SSI_STCR_TFSI (1 << 2) +#define SSI_STCR_TFSL (1 << 1) +#define SSI_STCR_TEFS (1 << 0) + +#define SSI_SRCR_RXBIT0 (1 << 9) +#define SSI_SRCR_RFEN1 (1 << 8) +#define SSI_SRCR_RFEN0 (1 << 7) +#define SSI_SRCR_RFDIR (1 << 6) +#define SSI_SRCR_RXDIR (1 << 5) +#define SSI_SRCR_RSHFD (1 << 4) +#define SSI_SRCR_RSCKP (1 << 3) +#define SSI_SRCR_RFSI (1 << 2) +#define SSI_SRCR_RFSL (1 << 1) +#define SSI_SRCR_REFS (1 << 0) + +#define SSI_STCCR_DIV2 (1 << 18) +#define SSI_STCCR_PSR (1 << 15) +#define SSI_STCCR_WL(x) ((((x) - 2) >> 1) << 13) +#define SSI_STCCR_DC(x) (((x) & 0x1f) << 8) +#define SSI_STCCR_PM(x) (((x) & 0xff) << 0) +#define SSI_STCCR_WL_MASK (0xf << 13) +#define SSI_STCCR_DC_MASK (0x1f << 8) +#define SSI_STCCR_PM_MASK (0xff << 0) + +#define SSI_SRCCR_DIV2 (1 << 18) +#define SSI_SRCCR_PSR (1 << 15) +#define SSI_SRCCR_WL(x) ((((x) - 2) >> 1) << 13) +#define SSI_SRCCR_DC(x) (((x) & 0x1f) << 8) +#define SSI_SRCCR_PM(x) (((x) & 0xff) << 0) +#define SSI_SRCCR_WL_MASK (0xf << 13) +#define SSI_SRCCR_DC_MASK (0x1f << 8) +#define SSI_SRCCR_PM_MASK (0xff << 0) + + +#define SSI_SFCSR_RFCNT1(x) (((x) & 0xf) << 28) +#define SSI_SFCSR_TFCNT1(x) (((x) & 0xf) << 24) +#define SSI_SFCSR_RFWM1(x) (((x) & 0xf) << 20) +#define SSI_SFCSR_TFWM1(x) (((x) & 0xf) << 16) +#define SSI_SFCSR_RFCNT0(x) (((x) & 0xf) << 12) +#define SSI_SFCSR_TFCNT0(x) (((x) & 0xf) << 8) +#define SSI_SFCSR_RFWM0(x) (((x) & 0xf) << 4) +#define SSI_SFCSR_TFWM0(x) (((x) & 0xf) << 0) + +#define SSI_STR_TEST (1 << 15) +#define SSI_STR_RCK2TCK (1 << 14) +#define SSI_STR_RFS2TFS (1 << 13) +#define SSI_STR_RXSTATE(x) (((x) & 0xf) << 8) +#define SSI_STR_TXD2RXD (1 << 7) +#define SSI_STR_TCK2RCK (1 << 6) +#define SSI_STR_TFS2RFS (1 << 5) +#define SSI_STR_TXSTATE(x) (((x) & 0xf) << 0) + +#define SSI_SOR_CLKOFF (1 << 6) +#define SSI_SOR_RX_CLR (1 << 5) +#define SSI_SOR_TX_CLR (1 << 4) +#define SSI_SOR_INIT (1 << 3) +#define SSI_SOR_WAIT(x) (((x) & 0x3) << 1) +#define SSI_SOR_SYNRST (1 << 0) + +#define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5) +#define SSI_SACNT_WR (x << 4) +#define SSI_SACNT_RD (x << 3) +#define SSI_SACNT_TIF (x << 2) +#define SSI_SACNT_FV (x << 1) +#define SSI_SACNT_AC97EN (x << 0) + +/* SDMA & SSI watermarks for FIFO's */ +#define SDMA_TXFIFO_WATERMARK 0x4 +#define SDMA_RXFIFO_WATERMARK 0x6 +#define SSI_TXFIFO_WATERMARK 0x4 +#define SSI_RXFIFO_WATERMARK 0x6 + +/* i.MX DAI SSP ID's */ +#define IMX_DAI_SSI0 0 /* SSI1 FIFO 0 */ +#define IMX_DAI_SSI1 1 /* SSI1 FIFO 1 */ +#define IMX_DAI_SSI2 2 /* SSI2 FIFO 0 */ +#define IMX_DAI_SSI3 3 /* SSI2 FIFO 1 */ + +/* SSI clock sources */ +#define IMX_SSP_SYS_CLK 0 + +/* SSI audio dividers */ +#define IMX_SSI_TX_DIV_2 0 +#define IMX_SSI_TX_DIV_PSR 1 +#define IMX_SSI_TX_DIV_PM 2 +#define IMX_SSI_RX_DIV_2 3 +#define IMX_SSI_RX_DIV_PSR 4 +#define IMX_SSI_RX_DIV_PM 5 + + +/* SSI Div 2 */ +#define IMX_SSI_DIV_2_OFF (~SSI_STCCR_DIV2) +#define IMX_SSI_DIV_2_ON SSI_STCCR_DIV2 + +extern struct snd_soc_dai imx_ssi_dai; + +#endif diff --git a/sound/soc/stmp3xxx/Kconfig b/sound/soc/stmp3xxx/Kconfig new file mode 100644 index 000000000000..5a3ee6067f90 --- /dev/null +++ b/sound/soc/stmp3xxx/Kconfig @@ -0,0 +1,31 @@ +config SND_STMP3XXX_SOC + tristate "SoC Audio for the SigmaTel STMP3XXX chips" + depends on ARCH_STMP3XXX && SND_SOC + select SND_PCM + help + Say Y or M if you want to add support for codecs embedded into + the STMP3XXX chips. + +config SND_STMP3XXX_SOC_DAI + tristate + +config SND_STMP3XXX_SOC_SPDIF_DAI + tristate + +config SND_STMP3XXX_SOC_STMP3780_DEVB + tristate "SoC Audio support for STMP3780 Development Board" + depends on SND_STMP3XXX_SOC && ARCH_STMP378X + select SND_STMP3XXX_SOC_DAI + select SND_SOC_STMP378X_CODEC + help + Say Y if you want to add support for SoC audio on stmp3780 development + board with the stmp378x codec. + +config SND_STMP3XXX_SOC_STMP3780_DEVB_SPDIF + tristate "SoC SPDIF support for STMP3780 Development Board" + depends on SND_STMP3XXX_SOC && ARCH_STMP378X + select SND_STMP3XXX_SOC_SPDIF_DAI + select SND_SOC_STMP3XXX_SPDIF + help + Say Y if you want to add support for SoC audio on stmp3780 development + board with the SPDIF transmitter. diff --git a/sound/soc/stmp3xxx/Makefile b/sound/soc/stmp3xxx/Makefile new file mode 100644 index 000000000000..d84ece16be33 --- /dev/null +++ b/sound/soc/stmp3xxx/Makefile @@ -0,0 +1,12 @@ +# STMP3XXX platfrom support +snd-soc-stmp3xxx-objs := stmp3xxx_pcm.o +snd-soc-stmp3xxx-dai-objs := stmp3xxx_dai.o +snd-soc-stmp3xxx-spdif-dai-objs := stmp3xxx_spdif_dai.o +snd-soc-stmp3780-devb-objs := stmp3780_devb.o +snd-soc-stmp3780-devb-spdif-objs := stmp3780_devb_spdif.o + +obj-$(CONFIG_SND_STMP3XXX_SOC) += snd-soc-stmp3xxx.o +obj-$(CONFIG_SND_STMP3XXX_SOC_DAI) += snd-soc-stmp3xxx-dai.o +obj-$(CONFIG_SND_STMP3XXX_SOC_SPDIF_DAI) += snd-soc-stmp3xxx-spdif-dai.o +obj-$(CONFIG_SND_STMP3XXX_SOC_STMP3780_DEVB) += snd-soc-stmp3780-devb.o +obj-$(CONFIG_SND_STMP3XXX_SOC_STMP3780_DEVB_SPDIF) += snd-soc-stmp3780-devb-spdif.o diff --git a/sound/soc/stmp3xxx/stmp3780_devb.c b/sound/soc/stmp3xxx/stmp3780_devb.c new file mode 100644 index 000000000000..e36e4a3e3621 --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3780_devb.c @@ -0,0 +1,92 @@ +/* + * ASoC driver for Freescale STMP3780 development board + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <asm/dma.h> +#include <mach/hardware.h> +#include <mach/regs-apbx.h> + +#include "../codecs/stmp378x_codec.h" +#include "stmp3xxx_dai.h" +#include "stmp3xxx_pcm.h" + +/* stmp3780 devb digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link stmp3780_devb_dai = { + .name = "STMP378X ADC/DAC", + .stream_name = "STMP378X ADC/DAC", + .cpu_dai = &stmp3xxx_adc_dai, + .codec_dai = &stmp378x_codec_dai, +}; + +/* stmp3780 devb audio machine driver */ +static struct snd_soc_card snd_soc_card_stmp3780_devb = { + .name = "STMP3780 Devb", + .platform = &stmp3xxx_soc_platform, + .dai_link = &stmp3780_devb_dai, + .num_links = 1, +}; + +/* stmp3780 devb audio subsystem */ +static struct snd_soc_device stmp3780_devb_snd_devdata = { + .card = &snd_soc_card_stmp3780_devb, + .codec_dev = &soc_codec_dev_stmp378x, +}; + +static struct platform_device *stmp3780_devb_snd_device; + +static int __init stmp3780_devb_init(void) +{ + int ret = 0; + + stmp3780_devb_snd_device = platform_device_alloc("soc-audio", 0); + if (!stmp3780_devb_snd_device) + return -ENOMEM; + + platform_set_drvdata(stmp3780_devb_snd_device, + &stmp3780_devb_snd_devdata); + stmp3780_devb_snd_devdata.dev = &stmp3780_devb_snd_device->dev; + stmp3780_devb_snd_device->dev.platform_data = + &stmp3780_devb_snd_devdata; + + ret = platform_device_add(stmp3780_devb_snd_device); + if (ret) + platform_device_put(stmp3780_devb_snd_device); + + return ret; +} + +static void __exit stmp3780_devb_exit(void) +{ + platform_device_unregister(stmp3780_devb_snd_device); +} + +module_init(stmp3780_devb_init); +module_exit(stmp3780_devb_exit); + +MODULE_AUTHOR("Vladislav Buzov"); +MODULE_DESCRIPTION("STMP3780 development board ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/stmp3xxx/stmp3780_devb_spdif.c b/sound/soc/stmp3xxx/stmp3780_devb_spdif.c new file mode 100644 index 000000000000..4e2377e23370 --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3780_devb_spdif.c @@ -0,0 +1,95 @@ +/* + * ASoC driver for STMP3780 development board + * + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * Copyright 2008-2009 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <asm/dma.h> +#include <mach/hardware.h> +#include <mach/regs-apbx.h> + +#include <mach/stmp3xxx.h> + +#include "../codecs/stmp3xxx_spdif.h" +#include "stmp3xxx_spdif_dai.h" +#include "stmp3xxx_pcm.h" + +extern int spdif_pinmux(int); + +/* stmp3780 devb digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link stmp3780_devb_dai = { + .name = "STMP3XXX SPDIF", + .stream_name = "STMP3XXX SPDIF", + .cpu_dai = &stmp3xxx_spdif_dai, + .codec_dai = &stmp3xxx_spdif_codec_dai, +}; + +/* stmp3780 devb audio machine driver */ +static struct snd_soc_card snd_soc_machine_stmp3780_devb = { + .name = "STMP3780 Devb", + .platform = &stmp3xxx_soc_platform, + .dai_link = &stmp3780_devb_dai, + .num_links = 1, +}; + +/* stmp3780 devb audio subsystem */ +static struct snd_soc_device stmp3780_devb_snd_devdata = { + .card = &snd_soc_machine_stmp3780_devb, + .codec_dev = &soc_spdif_codec_dev_stmp3xxx, +}; + +static struct platform_device *stmp3780_devb_snd_device; + +static int __init stmp3780_devb_init(void) +{ + int ret = 0; + + stmp3780_devb_snd_device = platform_device_alloc("soc-audio", 1); + if (!stmp3780_devb_snd_device) + return -ENOMEM; + + platform_set_drvdata(stmp3780_devb_snd_device, + &stmp3780_devb_snd_devdata); + stmp3780_devb_snd_devdata.dev = &stmp3780_devb_snd_device->dev; + stmp3780_devb_snd_device->dev.platform_data = + &stmp3780_devb_snd_devdata; + + ret = platform_device_add(stmp3780_devb_snd_device); + if (ret) + platform_device_put(stmp3780_devb_snd_device); + + spdif_pinmux(1); + + return ret; +} + +static void __exit stmp3780_devb_exit(void) +{ + spdif_pinmux(0); + platform_device_unregister(stmp3780_devb_snd_device); +} + +module_init(stmp3780_devb_init); +module_exit(stmp3780_devb_exit); + +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_DESCRIPTION("STMP3780 development board ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/stmp3xxx/stmp3xxx_dai.c b/sound/soc/stmp3xxx/stmp3xxx_dai.c new file mode 100644 index 000000000000..742b466a5f70 --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3xxx_dai.c @@ -0,0 +1,241 @@ +/* + * ASoC Audio Layer for Freescale STMP37XX/STMP378X ADC/DAC + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <mach/dma.h> +#include <mach/regs-apbx.h> +#include <mach/regs-audioin.h> +#include <mach/regs-audioout.h> +#include "stmp3xxx_pcm.h" +#include <mach/platform.h> + +#define STMP3XXX_ADC_RATES SNDRV_PCM_RATE_8000_192000 +#define STMP3XXX_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct stmp3xxx_pcm_dma_params stmp3xxx_audio_in = { + .name = "stmp3xxx adc", + .dma_bus = STMP3XXX_BUS_APBX, + .dma_ch = 0, + .irq = IRQ_ADC_DMA, +}; + +struct stmp3xxx_pcm_dma_params stmp3xxx_audio_out = { + .name = "stmp3xxx dac", + .dma_bus = STMP3XXX_BUS_APBX, + .dma_ch = 1, + .irq = IRQ_DAC_DMA, +}; + +static irqreturn_t stmp3xxx_err_irq(int irq, void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + u32 ctrl_reg; + u32 overflow_mask; + u32 underflow_mask; + + if (playback) { + ctrl_reg = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + underflow_mask = BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ; + overflow_mask = BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ; + } else { + ctrl_reg = __raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + underflow_mask = BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ; + overflow_mask = BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ; + } + + if (ctrl_reg & underflow_mask) { + printk(KERN_DEBUG "%s underflow detected\n", + playback ? "DAC" : "ADC"); + + if (playback) + stmp3xxx_clearl( + BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + else + stmp3xxx_clearl( + BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ, REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + + } else if (ctrl_reg & overflow_mask) { + printk(KERN_DEBUG "%s overflow detected\n", + playback ? "DAC" : "ADC"); + + if (playback) + stmp3xxx_clearl( + BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + else + stmp3xxx_clearl(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ, REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + } else + printk(KERN_WARNING "Unknown DAC error interrupt\n"); + + return IRQ_HANDLED; +} + +static int stmp3xxx_adc_trigger(struct snd_pcm_substream *substream, + int cmd, + struct snd_soc_dai *dai) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (playback) + stmp3xxx_setl(BM_AUDIOOUT_CTRL_RUN, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + else + stmp3xxx_setl(BM_AUDIOIN_CTRL_RUN, REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + break; + + case SNDRV_PCM_TRIGGER_STOP: + if (playback) + stmp3xxx_clearl(BM_AUDIOOUT_CTRL_RUN, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + else + stmp3xxx_clearl(BM_AUDIOIN_CTRL_RUN, REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int stmp3xxx_adc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int irq; + int ret; + + if (playback) { + irq = IRQ_DAC_ERROR; + cpu_dai->dma_data = &stmp3xxx_audio_out; + } else { + irq = IRQ_ADC_ERROR; + cpu_dai->dma_data = &stmp3xxx_audio_in; + } + + ret = request_irq(irq, stmp3xxx_err_irq, 0, "STMP3xxx DAC/ADC Error", + substream); + if (ret) { + printk(KERN_ERR "%s: Unable to request ADC/DAC error irq %d\n", + __func__, IRQ_DAC_ERROR); + return ret; + } + + /* Enable error interrupt */ + if (playback) { + stmp3xxx_clearl(BM_AUDIOOUT_CTRL_FIFO_OVERFLOW_IRQ, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + stmp3xxx_clearl(BM_AUDIOOUT_CTRL_FIFO_UNDERFLOW_IRQ, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + stmp3xxx_setl(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + } else { + stmp3xxx_clearl(BM_AUDIOIN_CTRL_FIFO_OVERFLOW_IRQ, REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + stmp3xxx_clearl(BM_AUDIOIN_CTRL_FIFO_UNDERFLOW_IRQ, REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + stmp3xxx_setl(BM_AUDIOIN_CTRL_FIFO_ERROR_IRQ_EN, REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + } + + return 0; +} + +static void stmp3xxx_adc_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + + /* Disable error interrupt */ + if (playback) { + stmp3xxx_clearl(BM_AUDIOOUT_CTRL_FIFO_ERROR_IRQ_EN, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + free_irq(IRQ_DAC_ERROR, substream); + } else { + stmp3xxx_clearl(BM_AUDIOIN_CTRL_FIFO_ERROR_IRQ_EN, REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + free_irq(IRQ_ADC_ERROR, substream); + } +} + +#ifdef CONFIG_PM +static int stmp3xxx_adc_suspend(struct snd_soc_dai *cpu_dai) +{ + return 0; +} + +static int stmp3xxx_adc_resume(struct snd_soc_dai *cpu_dai) +{ + return 0; +} +#else +#define stmp3xxx_adc_suspend NULL +#define stmp3xxx_adc_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_dai_ops stmp3xxx_adc_dai_ops = { + .startup = stmp3xxx_adc_startup, + .shutdown = stmp3xxx_adc_shutdown, + .trigger = stmp3xxx_adc_trigger, +}; + +struct snd_soc_dai stmp3xxx_adc_dai = { + .name = "stmp3xxx adc/dac", + .id = 0, + .suspend = stmp3xxx_adc_suspend, + .resume = stmp3xxx_adc_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = STMP3XXX_ADC_RATES, + .formats = STMP3XXX_ADC_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = STMP3XXX_ADC_RATES, + .formats = STMP3XXX_ADC_FORMATS, + }, + .ops = &stmp3xxx_adc_dai_ops, +}; +EXPORT_SYMBOL_GPL(stmp3xxx_adc_dai); + +static int __init stmp3xxx_dai_init(void) +{ + return snd_soc_register_dai(&stmp3xxx_adc_dai); +} + +static void __exit stmp3xxx_dai_exit(void) +{ + snd_soc_unregister_dai(&stmp3xxx_adc_dai); +} +module_init(stmp3xxx_dai_init); +module_exit(stmp3xxx_dai_exit); + +MODULE_AUTHOR("Vladislav Buzov"); +MODULE_DESCRIPTION("stmp3xxx dac/adc DAI"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/stmp3xxx/stmp3xxx_dai.h b/sound/soc/stmp3xxx/stmp3xxx_dai.h new file mode 100644 index 000000000000..409256a86d15 --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3xxx_dai.h @@ -0,0 +1,21 @@ +/* + * ASoC Audio Layer for Freescale STMP37XX/STMP378X ADC/DAC + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef _STMP3XXX_DEV_H +#define _STMP3XXX_DEV_H +extern struct snd_soc_dai stmp3xxx_adc_dai; +#endif diff --git a/sound/soc/stmp3xxx/stmp3xxx_pcm.c b/sound/soc/stmp3xxx/stmp3xxx_pcm.c new file mode 100644 index 000000000000..21cf86200668 --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3xxx_pcm.c @@ -0,0 +1,461 @@ +/* + * ASoC PCM interface for Freescale STMP37XX/STMP378X ADC/DAC + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <mach/dma.h> +#include <mach/hardware.h> +#include <mach/platform.h> + +#include <mach/regs-apbx.h> + +#include "stmp3xxx_pcm.h" + +static const struct snd_pcm_hardware stmp3xxx_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 255, + .buffer_bytes_max = 64 * 1024, + .fifo_size = 32, +}; + +/* + * Required to request DMA channels + */ +struct device *stmp3xxx_pcm_dev; + +struct stmp3xxx_runtime_data { + u32 dma_ch; + u32 dma_period; + u32 dma_totsize; + + struct stmp3xxx_pcm_dma_params *params; + struct stmp3xxx_dma_descriptor *dma_desc_array; +}; + +static irqreturn_t stmp3xxx_pcm_dma_irq(int irq, void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + struct stmp3xxx_runtime_data *prtd = substream->runtime->private_data; + +#ifdef CONFIG_ARCH_STMP37XX + u32 err_mask = 1 << (16 + prtd->params->dma_ch); +#endif +#ifdef CONFIG_ARCH_STMP378X + u32 err_mask = 1 << prtd->params->dma_ch; +#endif + u32 irq_mask = 1 << prtd->params->dma_ch; + +#ifdef CONFIG_ARCH_STMP37XX + if (__raw_readl(REGS_APBX_BASE + HW_APBX_CTRL1) & err_mask) { +#endif +#ifdef CONFIG_ARCH_STMP378X + if (__raw_readl(REGS_APBX_BASE + HW_APBX_CTRL2) & err_mask) { +#endif + printk(KERN_WARNING "%s: DMA audio channel %d (%s) error\n", + __func__, prtd->params->dma_ch, prtd->params->name); +#ifdef CONFIG_ARCH_STMP37XX + stmp3xxx_clearl(err_mask, REGS_APBX_BASE + HW_APBX_CTRL1); +#endif +#ifdef CONFIG_ARCH_STMP378X + stmp3xxx_clearl(err_mask, REGS_APBX_BASE + HW_APBX_CTRL2); +#endif + } else if (__raw_readl(REGS_APBX_BASE + HW_APBX_CTRL1) & irq_mask) { + stmp3xxx_dma_clear_interrupt(prtd->dma_ch); + snd_pcm_period_elapsed(substream); + } else + printk(KERN_WARNING "%s: Unknown interrupt\n", __func__); + + return IRQ_HANDLED; +} + +/* + * Make a circular DMA descriptor list + */ +static int stmp3xxx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct stmp3xxx_runtime_data *prtd = runtime->private_data; + dma_addr_t dma_buffer_phys; + int periods_num, playback, i; + + playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + periods_num = prtd->dma_totsize / prtd->dma_period; + dma_buffer_phys = runtime->dma_addr; + + /* Reset DMA channel, enable interrupt */ + stmp3xxx_dma_reset_channel(prtd->dma_ch); + + /* Set up a DMA chain to sent DMA buffer */ + for (i = 0; i < periods_num; i++) { + int next = (i + 1) % periods_num; + u32 cmd = 0; + + /* Link with previous command */ + prtd->dma_desc_array[i].command->next = + prtd->dma_desc_array[next].handle; + + prtd->dma_desc_array[i].next_descr = + &prtd->dma_desc_array[next]; + + cmd = BF(prtd->dma_period, APBX_CHn_CMD_XFER_COUNT) | + BM_APBX_CHn_CMD_IRQONCMPLT | + BM_APBX_CHn_CMD_CHAIN; + + /* Set DMA direction */ + if (playback) + cmd |= BF(BV_APBX_CHn_CMD_COMMAND__DMA_READ, + APBX_CHn_CMD_COMMAND); + else + cmd |= BF(BV_APBX_CHn_CMD_COMMAND__DMA_WRITE, + APBX_CHn_CMD_COMMAND); + + prtd->dma_desc_array[i].command->cmd = cmd; + prtd->dma_desc_array[i].command->buf_ptr = dma_buffer_phys; + + /* Next data chunk */ + dma_buffer_phys += prtd->dma_period; + } + + return 0; +} + +/* + * Stop circular DMA descriptor list + * We should not stop DMA in a middle of current transaction once we receive + * stop request from ALSA core. This function finds the next DMA descriptor + * and set it up to decrement DMA channel semaphore. So the current transaction + * is the last data transfer. + */ +static void stmp3xxx_pcm_stop(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct stmp3xxx_runtime_data *prtd = runtime->private_data; + dma_addr_t pos; + int desc; + + /* Freez DMA channel for a moment */ + stmp3xxx_dma_freeze(prtd->dma_ch); + + /* Find current DMA descriptor */ + pos = __raw_readl(REGS_APBX_BASE + + HW_APBX_CHn_BAR(prtd->params->dma_ch)); + desc = (pos - runtime->dma_addr) / prtd->dma_period; + + /* Set up the next descriptor to decrement DMA channel sempahore */ + prtd->dma_desc_array[desc].next_descr->command->cmd + = BM_APBX_CHn_CMD_SEMAPHORE; + + /* Let the current DMA transaction finish */ + stmp3xxx_dma_unfreeze(prtd->dma_ch); +} + +static int stmp3xxx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct stmp3xxx_runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + + switch (cmd) { + + case SNDRV_PCM_TRIGGER_START: + stmp3xxx_dma_go(prtd->dma_ch, prtd->dma_desc_array, 1); + break; + + case SNDRV_PCM_TRIGGER_STOP: + stmp3xxx_pcm_stop(substream); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + stmp3xxx_dma_unfreeze(prtd->dma_ch); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + stmp3xxx_dma_freeze(prtd->dma_ch); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t +stmp3xxx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct stmp3xxx_runtime_data *prtd = runtime->private_data; + unsigned int offset; + dma_addr_t pos; + + pos = __raw_readl(REGS_APBX_BASE + + HW_APBX_CHn_BAR(prtd->params->dma_ch)); + offset = bytes_to_frames(runtime, pos - runtime->dma_addr); + + if (offset >= runtime->buffer_size) + offset = 0; + + return offset; +} + +static int stmp3xxx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct stmp3xxx_runtime_data *prtd = substream->runtime->private_data; + + prtd->dma_period = params_period_bytes(hw_params); + prtd->dma_totsize = params_buffer_bytes(hw_params); + + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int stmp3xxx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int stmp3xxx_pcm_dma_request(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct stmp3xxx_runtime_data *prtd = runtime->private_data; + struct stmp3xxx_pcm_dma_params *dma_data = rtd->dai->cpu_dai->dma_data; + int desc_num = stmp3xxx_pcm_hardware.periods_max; + int desc; + int ret; + + if (!dma_data) + return -ENODEV; + + prtd->params = dma_data; + prtd->dma_ch = STMP3XXX_DMA(dma_data->dma_ch, dma_data->dma_bus); + + ret = stmp3xxx_dma_request(prtd->dma_ch, stmp3xxx_pcm_dev, + prtd->params->name); + if (ret) { + printk(KERN_ERR "%s: Failed to request DMA channel (%d:%d)\n", + __func__, dma_data->dma_bus, dma_data->dma_ch); + return ret; + } + + /* Allocate memory for data and pio DMA descriptors */ + prtd->dma_desc_array = + kzalloc(sizeof(struct stmp3xxx_dma_descriptor) * desc_num, + GFP_KERNEL); + if (prtd->dma_desc_array == NULL) { + printk(KERN_ERR "%s: Unable to allocate memory\n", __func__); + stmp3xxx_dma_release(prtd->dma_ch); + return -ENOMEM; + } + + for (desc = 0; desc < desc_num; desc++) { + ret = stmp3xxx_dma_allocate_command(prtd->dma_ch, + &prtd->dma_desc_array[desc]); + if (ret) { + printk(KERN_ERR"%s Unable to allocate DMA command %d\n", + __func__, desc); + goto err; + } + } + + ret = request_irq(prtd->params->irq, stmp3xxx_pcm_dma_irq, 0, + "STMP3xxx PCM DMA", substream); + if (ret) { + printk(KERN_ERR "%s: Unable to request DMA irq %d\n", __func__, + prtd->params->irq); + goto err; + } + + + /* Enable completion interrupt */ + stmp3xxx_dma_clear_interrupt(prtd->dma_ch); + stmp3xxx_dma_enable_interrupt(prtd->dma_ch); + + return 0; + +err: + while (--desc >= 0) + stmp3xxx_dma_free_command(prtd->dma_ch, + &prtd->dma_desc_array[desc]); + kfree(prtd->dma_desc_array); + stmp3xxx_dma_release(prtd->dma_ch); + + return ret; +} + +static int stmp3xxx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct stmp3xxx_runtime_data *prtd; + int ret; + + /* Ensure that buffer size is a multiple of the period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + snd_soc_set_runtime_hwparams(substream, &stmp3xxx_pcm_hardware); + + prtd = kzalloc(sizeof(struct stmp3xxx_runtime_data), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + runtime->private_data = prtd; + + ret = stmp3xxx_pcm_dma_request(substream); + if (ret) { + printk(KERN_ERR "stmp3xxx_pcm: Failed to request channels\n"); + kfree(prtd); + return ret; + } + + return 0; +} + +static int stmp3xxx_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct stmp3xxx_runtime_data *prtd = runtime->private_data; + int desc_num = stmp3xxx_pcm_hardware.periods_max; + int desc; + + /* Free DMA irq */ + free_irq(prtd->params->irq, substream); + + /* Free DMA channel */ + for (desc = 0; desc < desc_num; desc++) + stmp3xxx_dma_free_command(prtd->dma_ch, + &prtd->dma_desc_array[desc]); + kfree(prtd->dma_desc_array); + stmp3xxx_dma_release(prtd->dma_ch); + + /* Free private runtime data */ + kfree(prtd); + + return 0; +} + +static int stmp3xxx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_coherent(NULL, vma, runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); +} + +struct snd_pcm_ops stmp3xxx_pcm_ops = { + .open = stmp3xxx_pcm_open, + .close = stmp3xxx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = stmp3xxx_pcm_hw_params, + .hw_free = stmp3xxx_pcm_hw_free, + .prepare = stmp3xxx_pcm_prepare, + .trigger = stmp3xxx_pcm_trigger, + .pointer = stmp3xxx_pcm_pointer, + .mmap = stmp3xxx_pcm_mmap, +}; + +static u64 stmp3xxx_pcm_dma_mask = DMA_BIT_MASK(32); + +static int stmp3xxx_pcm_new(struct snd_card *card, + struct snd_soc_dai *dai, struct snd_pcm *pcm) +{ + size_t size = stmp3xxx_pcm_hardware.buffer_bytes_max; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &stmp3xxx_pcm_dma_mask; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, NULL, + size, size); + + return 0; +} + +static void stmp3xxx_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +/* + * We need probe/remove callbacks to setup stmp3xxx_pcm_dev + */ +static int stmp3xxx_pcm_probe(struct platform_device *pdev) +{ + stmp3xxx_pcm_dev = &pdev->dev; + return 0; +} + +static int stmp3xxx_pcm_remove(struct platform_device *pdev) +{ + stmp3xxx_pcm_dev = NULL; + return 0; +} + +struct snd_soc_platform stmp3xxx_soc_platform = { + .name = "STMP3xxx Audio", + .pcm_ops = &stmp3xxx_pcm_ops, + .probe = stmp3xxx_pcm_probe, + .remove = stmp3xxx_pcm_remove, + .pcm_new = stmp3xxx_pcm_new, + .pcm_free = stmp3xxx_pcm_free, +}; +EXPORT_SYMBOL_GPL(stmp3xxx_soc_platform); + +static int __init stmp3xxx_pcm_init(void) +{ + return snd_soc_register_platform(&stmp3xxx_soc_platform); +} + +static void __exit stmp3xxx_pcm_exit(void) +{ + snd_soc_unregister_platform(&stmp3xxx_soc_platform); +} +module_init(stmp3xxx_pcm_init); +module_exit(stmp3xxx_pcm_exit); + +MODULE_AUTHOR("Vladislav Buzov"); +MODULE_DESCRIPTION("STMP3xxx DMA Module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/stmp3xxx/stmp3xxx_pcm.h b/sound/soc/stmp3xxx/stmp3xxx_pcm.h new file mode 100644 index 000000000000..78ac9487353f --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3xxx_pcm.h @@ -0,0 +1,30 @@ +/* + * ASoC PCM interface for Freescale STMP37XX/STMP378X ADC/DAC + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef _STMP3XXX_PCM_H +#define _STMP3XXX_PCM_H + +struct stmp3xxx_pcm_dma_params { + char *name; + int dma_bus; /* DMA bus */ + int dma_ch; /* DMA channel number */ + int irq; /* DMA interrupt number */ +}; + +extern struct snd_soc_platform stmp3xxx_soc_platform; + +#endif diff --git a/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c new file mode 100644 index 000000000000..b09604725b6b --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.c @@ -0,0 +1,207 @@ +/* + * ALSA SoC SPDIF Audio Layer for STMP3xxx processor familiy + * + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * Copyright 2008-2009 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <mach/dma.h> +#include <mach/regs-spdif.h> +#include "stmp3xxx_pcm.h" +#include <mach/platform.h> + +#define STMP3XXX_SPDIF_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) +#define STMP3XXX_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct stmp3xxx_pcm_dma_params stmp3xxx_spdif = { + .name = "stmp3xxx spdif", + .dma_bus = STMP3XXX_BUS_APBX, + .dma_ch = 2, + .irq = IRQ_SPDIF_DMA, +}; + +static irqreturn_t stmp3xxx_err_irq(int irq, void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + u32 ctrl_reg = 0; + u32 overflow_mask; + u32 underflow_mask; + + if (playback) { + ctrl_reg = __raw_readl(REGS_SPDIF_BASE + HW_SPDIF_CTRL); + underflow_mask = BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ; + overflow_mask = BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ; + } + + if (ctrl_reg & underflow_mask) { + printk(KERN_DEBUG "underflow detected SPDIF\n"); + + if (playback) + stmp3xxx_clearl(BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ, + REGS_SPDIF_BASE + HW_SPDIF_CTRL); + } else if (ctrl_reg & overflow_mask) { + printk(KERN_DEBUG "overflow detected SPDIF\n"); + + if (playback) + stmp3xxx_clearl(BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ, + REGS_SPDIF_BASE + HW_SPDIF_CTRL); + } else + printk(KERN_WARNING "Unknown SPDIF error interrupt\n"); + + return IRQ_HANDLED; +} + +static int stmp3xxx_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (playback) + stmp3xxx_setl(BM_SPDIF_CTRL_RUN, + REGS_SPDIF_BASE + HW_SPDIF_CTRL); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (playback) + stmp3xxx_clearl(BM_SPDIF_CTRL_RUN, + REGS_SPDIF_BASE + HW_SPDIF_CTRL); + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + + default: + ret = -EINVAL; + } + + return ret; +} + +static int stmp3xxx_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int irq; + int ret; + + if (playback) { + irq = IRQ_SPDIF_ERROR; + cpu_dai->dma_data = &stmp3xxx_spdif; + } + + ret = request_irq(irq, stmp3xxx_err_irq, 0, "STMP3xxx SPDIF Error", + substream); + if (ret) { + printk(KERN_ERR "%s: Unable to request SPDIF error irq %d\n", + __func__, IRQ_SPDIF_ERROR); + return ret; + } + + /* Enable error interrupt */ + if (playback) { + stmp3xxx_clearl(BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ, + REGS_SPDIF_BASE + HW_SPDIF_CTRL); + stmp3xxx_clearl(BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ, + REGS_SPDIF_BASE + HW_SPDIF_CTRL); + stmp3xxx_setl(BM_SPDIF_CTRL_FIFO_ERROR_IRQ_EN, + REGS_SPDIF_BASE + HW_SPDIF_CTRL); + } + + return 0; +} + +static void stmp3xxx_spdif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + + /* Disable error interrupt */ + if (playback) { + stmp3xxx_clearl(BM_SPDIF_CTRL_FIFO_ERROR_IRQ_EN, + REGS_SPDIF_BASE + HW_SPDIF_CTRL); + free_irq(IRQ_SPDIF_ERROR, substream); + } +} + +#ifdef CONFIG_PM +static int stmp3xxx_spdif_suspend(struct snd_soc_dai *cpu_dai) +{ + return 0; +} + +static int stmp3xxx_spdif_resume(struct snd_soc_dai *cpu_dai) +{ + return 0; +} +#else +#define stmp3xxx_spdif_suspend NULL +#define stmp3xxx_spdif_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_dai_ops stmp3xxx_spdif_dai_ops = { + .startup = stmp3xxx_spdif_startup, + .shutdown = stmp3xxx_spdif_shutdown, + .trigger = stmp3xxx_spdif_trigger, +}; + +struct snd_soc_dai stmp3xxx_spdif_dai = { + .name = "stmp3xxx spdif", + .id = 0, + .suspend = stmp3xxx_spdif_suspend, + .resume = stmp3xxx_spdif_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = STMP3XXX_SPDIF_RATES, + .formats = STMP3XXX_SPDIF_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = STMP3XXX_SPDIF_RATES, + .formats = STMP3XXX_SPDIF_FORMATS, + }, + .ops = &stmp3xxx_spdif_dai_ops, +}; +EXPORT_SYMBOL_GPL(stmp3xxx_spdif_dai); + +static int __init stmp3xxx_spdif_dai_init(void) +{ + return snd_soc_register_dai(&stmp3xxx_spdif_dai); +} + +static void __exit stmp3xxx_spdif_dai_exit(void) +{ + snd_soc_unregister_dai(&stmp3xxx_spdif_dai); +} +module_init(stmp3xxx_spdif_dai_init); +module_exit(stmp3xxx_spdif_dai_exit); + +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_DESCRIPTION("stmp3xxx SPDIF DAI"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h new file mode 100644 index 000000000000..bec3d6fad7c9 --- /dev/null +++ b/sound/soc/stmp3xxx/stmp3xxx_spdif_dai.h @@ -0,0 +1,21 @@ +/* + * ASoC Audio Layer for Freescale STMP3XXX SPDIF transmitter + * + * Author: Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef _STMP3XXX_SPDIF_H +#define _STMP3XXX_SPDIF_H +extern struct snd_soc_dai stmp3xxx_spdif_dai; +#endif |